1157844
{"version":0,"id":"2902daf9f106fa343e80315497e99e844237f5e5e5f833e478449bb06c50f329","main":"/lib/node.js","imports":{},"resolutions":{"/lib/host/choose-bundle.js":{"#package":"/package.json"},"/lib/host/ota.js":{"#package":"/package.json","./choose-bundle.js":"/lib/host/choose-bundle.js","bare-fs":"/node_modules/bare-fs/index.js","bare-path":"/node_modules/bare-path/index.js","hyper-overlay/tree":"/node_modules/hyper-overlay/lib/tree.js","hypercore-crypto":"/node_modules/hypercore-crypto/index.js","pgh-runtime":"/node_modules/pgh-runtime/index.js","pgh/network-fetcher":"/node_modules/pgh/lib/network-fetcher.js","pgh/resolve-release":"/node_modules/pgh/lib/resolve-release.js"},"/lib/host/shell-handoff.js":{"#package":"/package.json"},"/lib/host/utf8.js":{"#package":"/package.json"},"/lib/node.js":{"#package":"/package.json","./host/ota.js":"/lib/host/ota.js","./host/shell-handoff.js":"/lib/host/shell-handoff.js","./host/utf8.js":"/lib/host/utf8.js","./version.js":"/lib/version.js","anproto":"/node_modules/anproto/index.js","bare-fs":"/node_modules/bare-fs/index.js","bare-http1":"/node_modules/bare-http1/index.js","bare-path":"/node_modules/bare-path/index.js","bare-process":"/node_modules/bare-process/index.js","favela-peer/lib/node.js":"/node_modules/favela-peer/lib/node.js","favela-peer/lib/rpc.js":"/node_modules/favela-peer/lib/rpc.js","favela-peer/lib/transport.js":"/node_modules/favela-peer/lib/transport.js","favela-surface/lib/router.js":"/node_modules/favela-surface/lib/router.js","favela-surface/lib/session.js":"/node_modules/favela-surface/lib/session.js","hyperswarm":"/node_modules/hyperswarm/index.js"},"/lib/version.js":{"#package":"/package.json"},"/node_modules/@hyperswarm/secret-stream/index.js":{"#package":"/node_modules/@hyperswarm/secret-stream/package.json","./lib/bridge":"/node_modules/@hyperswarm/secret-stream/lib/bridge.js","./lib/handshake":"/node_modules/@hyperswarm/secret-stream/lib/handshake.js","b4a":"/node_modules/b4a/index.js","hypercore-crypto":"/node_modules/hypercore-crypto/index.js","sodium-secretstream":"/node_modules/sodium-secretstream/index.js","sodium-universal":"/node_modules/sodium-universal/index.js","streamx":"/node_modules/streamx/index.js","timeout-refresh":"/node_modules/timeout-refresh/index.js","unslab":"/node_modules/unslab/index.js"},"/node_modules/@hyperswarm/secret-stream/lib/bridge.js":{"#package":"/node_modules/@hyperswarm/secret-stream/package.json","streamx":"/node_modules/streamx/index.js"},"/node_modules/@hyperswarm/secret-stream/lib/handshake.js":{"#package":"/node_modules/@hyperswarm/secret-stream/package.json","b4a":"/node_modules/b4a/index.js","noise-curve-ed":"/node_modules/noise-curve-ed/index.js","noise-handshake":"/node_modules/noise-handshake/noise.js","sodium-universal":"/node_modules/sodium-universal/index.js"},"/node_modules/@hyperswarm/secret-stream/package.json":{},"/node_modules/adaptive-timeout/index.js":{"#package":"/node_modules/adaptive-timeout/package.json","xache":"/node_modules/xache/index.js"},"/node_modules/adaptive-timeout/package.json":{},"/node_modules/anproto/index.js":{"#package":"/node_modules/anproto/package.json","./lib/an.js":"/node_modules/anproto/lib/an.js","./lib/challenges.js":"/node_modules/anproto/lib/challenges.js"},"/node_modules/anproto/lib/an.js":{"#package":"/node_modules/anproto/package.json","b4a":"/node_modules/anproto/node_modules/b4a/index.js","hypercore-crypto":"/node_modules/anproto/node_modules/hypercore-crypto/index.js","sodium-native":"/node_modules/anproto/node_modules/sodium-native/index.js"},"/node_modules/anproto/lib/challenges.js":{"#package":"/node_modules/anproto/package.json","b4a":"/node_modules/anproto/node_modules/b4a/index.js","hypercore-crypto":"/node_modules/anproto/node_modules/hypercore-crypto/index.js"},"/node_modules/anproto/node_modules/b4a/index.js":{"#package":"/node_modules/anproto/node_modules/b4a/package.json"},"/node_modules/anproto/node_modules/b4a/package.json":{},"/node_modules/anproto/node_modules/bare-ansi-escapes/index.js":{"#package":"/node_modules/anproto/node_modules/bare-ansi-escapes/package.json"},"/node_modules/anproto/node_modules/bare-ansi-escapes/package.json":{},"/node_modules/anproto/node_modules/bare-assert/index.js":{"#package":"/node_modules/anproto/node_modules/bare-assert/package.json","bare-inspect":"/node_modules/anproto/node_modules/bare-inspect/index.js"},"/node_modules/anproto/node_modules/bare-assert/package.json":{},"/node_modules/anproto/node_modules/bare-inspect/binding.js":{"#package":"/node_modules/anproto/node_modules/bare-inspect/package.json",".":"linked:libbare-inspect.3.1.4.so"},"/node_modules/anproto/node_modules/bare-inspect/index.js":{"#package":"/node_modules/anproto/node_modules/bare-inspect/package.json","./binding":"/node_modules/anproto/node_modules/bare-inspect/binding.js","bare-ansi-escapes":"/node_modules/anproto/node_modules/bare-ansi-escapes/index.js","bare-type":"/node_modules/anproto/node_modules/bare-type/index.js"},"/node_modules/anproto/node_modules/bare-inspect/package.json":{},"/node_modules/anproto/node_modules/bare-type/binding.js":{"#package":"/node_modules/anproto/node_modules/bare-type/package.json",".":"linked:libbare-type.1.1.0.so"},"/node_modules/anproto/node_modules/bare-type/index.js":{"#package":"/node_modules/anproto/node_modules/bare-type/package.json","./binding":"/node_modules/anproto/node_modules/bare-type/binding.js"},"/node_modules/anproto/node_modules/bare-type/package.json":{},"/node_modules/anproto/node_modules/compact-encoding/endian.js":{"#package":"/node_modules/anproto/node_modules/compact-encoding/package.json"},"/node_modules/anproto/node_modules/compact-encoding/index.js":{"#package":"/node_modules/anproto/node_modules/compact-encoding/package.json","./endian":"/node_modules/anproto/node_modules/compact-encoding/endian.js","./lexint":"/node_modules/anproto/node_modules/compact-encoding/lexint.js","./raw":"/node_modules/anproto/node_modules/compact-encoding/raw.js","b4a":"/node_modules/anproto/node_modules/b4a/index.js"},"/node_modules/anproto/node_modules/compact-encoding/lexint.js":{"#package":"/node_modules/anproto/node_modules/compact-encoding/package.json"},"/node_modules/anproto/node_modules/compact-encoding/package.json":{},"/node_modules/anproto/node_modules/compact-encoding/raw.js":{"#package":"/node_modules/anproto/node_modules/compact-encoding/package.json","./endian":"/node_modules/anproto/node_modules/compact-encoding/endian.js","b4a":"/node_modules/anproto/node_modules/b4a/index.js"},"/node_modules/anproto/node_modules/hypercore-crypto/index.js":{"#package":"/node_modules/anproto/node_modules/hypercore-crypto/package.json","b4a":"/node_modules/anproto/node_modules/b4a/index.js","compact-encoding":"/node_modules/anproto/node_modules/compact-encoding/index.js","sodium-universal":"/node_modules/anproto/node_modules/sodium-universal/index.js"},"/node_modules/anproto/node_modules/hypercore-crypto/package.json":{},"/node_modules/anproto/node_modules/require-addon/lib/bare.js":{"#package":"/node_modules/anproto/node_modules/require-addon/package.json"},"/node_modules/anproto/node_modules/require-addon/package.json":{},"/node_modules/anproto/node_modules/sodium-native/binding.js":{"#package":"/node_modules/anproto/node_modules/sodium-native/package.json",".":"linked:libsodium-native.5.1.0.so","require-addon":"/node_modules/anproto/node_modules/require-addon/lib/bare.js"},"/node_modules/anproto/node_modules/sodium-native/index.js":{"#package":"/node_modules/anproto/node_modules/sodium-native/package.json","./binding":"/node_modules/anproto/node_modules/sodium-native/binding.js","assert":"/node_modules/anproto/node_modules/bare-assert/index.js","which-runtime":"/node_modules/anproto/node_modules/which-runtime/index.js"},"/node_modules/anproto/node_modules/sodium-native/package.json":{},"/node_modules/anproto/node_modules/sodium-universal/index.js":{"#package":"/node_modules/anproto/node_modules/sodium-universal/package.json","sodium-native":"/node_modules/anproto/node_modules/sodium-native/index.js"},"/node_modules/anproto/node_modules/sodium-universal/package.json":{},"/node_modules/anproto/node_modules/which-runtime/index.js":{"#package":"/node_modules/anproto/node_modules/which-runtime/package.json"},"/node_modules/anproto/node_modules/which-runtime/package.json":{},"/node_modules/anproto/package.json":{},"/node_modules/b4a/index.js":{"#package":"/node_modules/b4a/package.json"},"/node_modules/b4a/package.json":{},"/node_modules/bare-abort/binding.js":{"#package":"/node_modules/bare-abort/package.json",".":"linked:libbare-abort.2.0.13.so"},"/node_modules/bare-abort/index.js":{"#package":"/node_modules/bare-abort/package.json","./binding":"/node_modules/bare-abort/binding.js"},"/node_modules/bare-abort/package.json":{},"/node_modules/bare-ansi-escapes/index.js":{"#package":"/node_modules/bare-ansi-escapes/package.json"},"/node_modules/bare-ansi-escapes/package.json":{},"/node_modules/bare-assert/index.js":{"#package":"/node_modules/bare-assert/package.json","bare-inspect":"/node_modules/bare-inspect/index.js"},"/node_modules/bare-assert/package.json":{},"/node_modules/bare-dns/binding.js":{"#package":"/node_modules/bare-dns/package.json",".":"linked:libbare-dns.2.2.0.so"},"/node_modules/bare-dns/index.js":{"#package":"/node_modules/bare-dns/package.json","./binding":"/node_modules/bare-dns/binding.js","./lib/constants":"/node_modules/bare-dns/lib/constants.js","./lib/errors":"/node_modules/bare-dns/lib/errors.js","./lib/promises":"/node_modules/bare-dns/lib/promises.js","./lib/resolver":"/node_modules/bare-dns/lib/resolver.js"},"/node_modules/bare-dns/lib/constants.js":{"#package":"/node_modules/bare-dns/package.json"},"/node_modules/bare-dns/lib/errors.js":{"#package":"/node_modules/bare-dns/package.json","./constants":"/node_modules/bare-dns/lib/constants.js"},"/node_modules/bare-dns/lib/promises.js":{"#package":"/node_modules/bare-dns/package.json","../":"/node_modules/bare-dns/index.js","./constants":"/node_modules/bare-dns/lib/constants.js","./errors":"/node_modules/bare-dns/lib/errors.js","./resolver":"/node_modules/bare-dns/lib/resolver.js"},"/node_modules/bare-dns/lib/records.js":{"#package":"/node_modules/bare-dns/package.json"},"/node_modules/bare-dns/lib/resolver.js":{"#package":"/node_modules/bare-dns/package.json","../binding":"/node_modules/bare-dns/binding.js","./constants":"/node_modules/bare-dns/lib/constants.js","./errors":"/node_modules/bare-dns/lib/errors.js","./records":"/node_modules/bare-dns/lib/records.js"},"/node_modules/bare-dns/package.json":{},"/node_modules/bare-env/index.js":{"#package":"/node_modules/bare-env/package.json","bare-os":"/node_modules/bare-os/index.js"},"/node_modules/bare-env/package.json":{},"/node_modules/bare-events/index.js":{"#package":"/node_modules/bare-events/package.json","./lib/errors":"/node_modules/bare-events/lib/errors.js"},"/node_modules/bare-events/lib/errors.js":{"#package":"/node_modules/bare-events/package.json"},"/node_modules/bare-events/package.json":{},"/node_modules/bare-fs/binding.js":{"#package":"/node_modules/bare-fs/package.json",".":"linked:libbare-fs.4.8.1.so"},"/node_modules/bare-fs/index.js":{"#package":"/node_modules/bare-fs/package.json","./binding":"/node_modules/bare-fs/binding.js","./lib/constants":"/node_modules/bare-fs/lib/constants.js","./lib/errors":"/node_modules/bare-fs/lib/errors.js","./promises":"/node_modules/bare-fs/promises.js","bare-events":"/node_modules/bare-events/index.js","bare-path":"/node_modules/bare-path/index.js","bare-stream":"/node_modules/bare-stream/index.js","bare-url":"/node_modules/bare-url/index.js","fast-fifo":"/node_modules/fast-fifo/index.js"},"/node_modules/bare-fs/lib/constants.js":{"#package":"/node_modules/bare-fs/package.json","../binding":"/node_modules/bare-fs/binding.js"},"/node_modules/bare-fs/lib/errors.js":{"#package":"/node_modules/bare-fs/package.json","../binding":"/node_modules/bare-fs/binding.js"},"/node_modules/bare-fs/package.json":{},"/node_modules/bare-fs/promises.js":{"#package":"/node_modules/bare-fs/package.json",".":"/node_modules/bare-fs/index.js","bare-events":"/node_modules/bare-events/index.js"},"/node_modules/bare-hrtime/binding.js":{"#package":"/node_modules/bare-hrtime/package.json",".":"linked:libbare-hrtime.2.1.2.so"},"/node_modules/bare-hrtime/index.js":{"#package":"/node_modules/bare-hrtime/package.json","./binding":"/node_modules/bare-hrtime/binding.js"},"/node_modules/bare-hrtime/package.json":{},"/node_modules/bare-http-parser/index.js":{"#package":"/node_modules/bare-http-parser/package.json","./lib/errors":"/node_modules/bare-http-parser/lib/errors.js"},"/node_modules/bare-http-parser/lib/errors.js":{"#package":"/node_modules/bare-http-parser/package.json"},"/node_modules/bare-http-parser/package.json":{},"/node_modules/bare-http1/index.js":{"#package":"/node_modules/bare-http1/package.json","./lib/agent":"/node_modules/bare-http1/lib/agent.js","./lib/client-connection":"/node_modules/bare-http1/lib/client-connection.js","./lib/client-request":"/node_modules/bare-http1/lib/client-request.js","./lib/constants":"/node_modules/bare-http1/lib/constants.js","./lib/errors":"/node_modules/bare-http1/lib/errors.js","./lib/incoming-message":"/node_modules/bare-http1/lib/incoming-message.js","./lib/outgoing-message":"/node_modules/bare-http1/lib/outgoing-message.js","./lib/server":"/node_modules/bare-http1/lib/server.js","./lib/server-connection":"/node_modules/bare-http1/lib/server-connection.js","./lib/server-response":"/node_modules/bare-http1/lib/server-response.js"},"/node_modules/bare-http1/lib/agent.js":{"#package":"/node_modules/bare-http1/package.json","./client-connection":"/node_modules/bare-http1/lib/client-connection.js","./errors":"/node_modules/bare-http1/lib/errors.js","bare-events":"/node_modules/bare-events/index.js","bare-tcp":"/node_modules/bare-tcp/index.js"},"/node_modules/bare-http1/lib/client-connection.js":{"#package":"/node_modules/bare-http1/package.json","./connection":"/node_modules/bare-http1/lib/connection.js","./errors":"/node_modules/bare-http1/lib/errors.js","./headers":"/node_modules/bare-http1/lib/headers.js","./incoming-message":"/node_modules/bare-http1/lib/incoming-message.js","bare-http-parser":"/node_modules/bare-http-parser/index.js","bare-stream":"/node_modules/bare-stream/index.js"},"/node_modules/bare-http1/lib/client-request.js":{"#package":"/node_modules/bare-http1/package.json","./agent":"/node_modules/bare-http1/lib/agent.js","./errors":"/node_modules/bare-http1/lib/errors.js","./headers":"/node_modules/bare-http1/lib/headers.js","./outgoing-message":"/node_modules/bare-http1/lib/outgoing-message.js","./validate":"/node_modules/bare-http1/lib/validate.js"},"/node_modules/bare-http1/lib/connection.js":{"#package":"/node_modules/bare-http1/package.json","./deadline":"/node_modules/bare-http1/lib/deadline.js","bare-http-parser":"/node_modules/bare-http-parser/index.js"},"/node_modules/bare-http1/lib/constants.js":{"#package":"/node_modules/bare-http1/package.json"},"/node_modules/bare-http1/lib/deadline.js":{"#package":"/node_modules/bare-http1/package.json","bare-hrtime":"/node_modules/bare-hrtime/index.js"},"/node_modules/bare-http1/lib/errors.js":{"#package":"/node_modules/bare-http1/package.json"},"/node_modules/bare-http1/lib/headers.js":{"#package":"/node_modules/bare-http1/package.json","./validate":"/node_modules/bare-http1/lib/validate.js"},"/node_modules/bare-http1/lib/incoming-message.js":{"#package":"/node_modules/bare-http1/package.json","./headers":"/node_modules/bare-http1/lib/headers.js","./message":"/node_modules/bare-http1/lib/message.js","bare-stream":"/node_modules/bare-stream/index.js"},"/node_modules/bare-http1/lib/message.js":{"#package":"/node_modules/bare-http1/package.json","./headers":"/node_modules/bare-http1/lib/headers.js","./validate":"/node_modules/bare-http1/lib/validate.js"},"/node_modules/bare-http1/lib/outgoing-message.js":{"#package":"/node_modules/bare-http1/package.json","./errors":"/node_modules/bare-http1/lib/errors.js","./headers":"/node_modules/bare-http1/lib/headers.js","./message":"/node_modules/bare-http1/lib/message.js","./validate":"/node_modules/bare-http1/lib/validate.js","bare-stream":"/node_modules/bare-stream/index.js"},"/node_modules/bare-http1/lib/server-connection.js":{"#package":"/node_modules/bare-http1/package.json","./connection":"/node_modules/bare-http1/lib/connection.js","./errors":"/node_modules/bare-http1/lib/errors.js","./headers":"/node_modules/bare-http1/lib/headers.js","./incoming-message":"/node_modules/bare-http1/lib/incoming-message.js","./server-response":"/node_modules/bare-http1/lib/server-response.js","./socket":"/node_modules/bare-http1/lib/socket.js","bare-http-parser":"/node_modules/bare-http-parser/index.js","bare-stream":"/node_modules/bare-stream/index.js"},"/node_modules/bare-http1/lib/server-response.js":{"#package":"/node_modules/bare-http1/package.json","./constants":"/node_modules/bare-http1/lib/constants.js","./errors":"/node_modules/bare-http1/lib/errors.js","./headers":"/node_modules/bare-http1/lib/headers.js","./outgoing-message":"/node_modules/bare-http1/lib/outgoing-message.js","./socket":"/node_modules/bare-http1/lib/socket.js","./validate":"/node_modules/bare-http1/lib/validate.js"},"/node_modules/bare-http1/lib/server.js":{"#package":"/node_modules/bare-http1/package.json","./server-connection":"/node_modules/bare-http1/lib/server-connection.js","bare-tcp":"/node_modules/bare-tcp/index.js"},"/node_modules/bare-http1/lib/socket.js":{"#package":"/node_modules/bare-http1/package.json","bare-stream":"/node_modules/bare-stream/index.js"},"/node_modules/bare-http1/lib/validate.js":{"#package":"/node_modules/bare-http1/package.json","./errors":"/node_modules/bare-http1/lib/errors.js"},"/node_modules/bare-http1/package.json":{},"/node_modules/bare-inspect/binding.js":{"#package":"/node_modules/bare-inspect/package.json",".":"linked:libbare-inspect.3.1.10.so"},"/node_modules/bare-inspect/index.js":{"#package":"/node_modules/bare-inspect/package.json","./binding":"/node_modules/bare-inspect/binding.js","bare-ansi-escapes":"/node_modules/bare-ansi-escapes/index.js","bare-type":"/node_modules/bare-type/index.js"},"/node_modules/bare-inspect/package.json":{},"/node_modules/bare-os/binding.js":{"#package":"/node_modules/bare-os/package.json",".":"linked:libbare-os.3.9.3.so"},"/node_modules/bare-os/index.js":{"#package":"/node_modules/bare-os/package.json","./binding":"/node_modules/bare-os/binding.js","./lib/constants":"/node_modules/bare-os/lib/constants.js","./lib/errors":"/node_modules/bare-os/lib/errors.js"},"/node_modules/bare-os/lib/constants.js":{"#package":"/node_modules/bare-os/package.json","../binding":"/node_modules/bare-os/binding.js"},"/node_modules/bare-os/lib/errors.js":{"#package":"/node_modules/bare-os/package.json"},"/node_modules/bare-os/package.json":{},"/node_modules/bare-path/binding.js":{"#package":"/node_modules/bare-path/package.json",".":"linked:libbare-path.3.1.2.so"},"/node_modules/bare-path/index.js":{"#package":"/node_modules/bare-path/package.json","./lib/posix":"/node_modules/bare-path/lib/posix.js","./lib/win32":"/node_modules/bare-path/lib/win32.js"},"/node_modules/bare-path/lib/constants.js":{"#package":"/node_modules/bare-path/package.json"},"/node_modules/bare-path/lib/posix.js":{"#package":"/node_modules/bare-path/package.json","../binding":"/node_modules/bare-path/binding.js","./constants":"/node_modules/bare-path/lib/constants.js","./shared":"/node_modules/bare-path/lib/shared.js","./win32":"/node_modules/bare-path/lib/win32.js"},"/node_modules/bare-path/lib/shared.js":{"#package":"/node_modules/bare-path/package.json","./constants":"/node_modules/bare-path/lib/constants.js"},"/node_modules/bare-path/lib/win32.js":{"#package":"/node_modules/bare-path/package.json","../binding":"/node_modules/bare-path/binding.js","./constants":"/node_modules/bare-path/lib/constants.js","./posix":"/node_modules/bare-path/lib/posix.js","./shared":"/node_modules/bare-path/lib/shared.js"},"/node_modules/bare-path/package.json":{},"/node_modules/bare-pipe/binding.js":{"#package":"/node_modules/bare-pipe/package.json",".":"linked:libbare-pipe.4.3.1.so"},"/node_modules/bare-pipe/index.js":{"#package":"/node_modules/bare-pipe/package.json","./binding":"/node_modules/bare-pipe/binding.js","./lib/constants":"/node_modules/bare-pipe/lib/constants.js","./lib/errors":"/node_modules/bare-pipe/lib/errors.js","bare-events":"/node_modules/bare-events/index.js","bare-stream":"/node_modules/bare-stream/index.js"},"/node_modules/bare-pipe/lib/constants.js":{"#package":"/node_modules/bare-pipe/package.json","../binding":"/node_modules/bare-pipe/binding.js"},"/node_modules/bare-pipe/lib/errors.js":{"#package":"/node_modules/bare-pipe/package.json"},"/node_modules/bare-pipe/package.json":{},"/node_modules/bare-posix/package.json":{},"/node_modules/bare-posix/unsupported.js":{"#package":"/node_modules/bare-posix/package.json"},"/node_modules/bare-process/index.js":{"#package":"/node_modules/bare-process/package.json","bare-abort":"/node_modules/bare-abort/index.js","bare-env":"/node_modules/bare-env/index.js","bare-events":"/node_modules/bare-events/index.js","bare-hrtime":"/node_modules/bare-hrtime/index.js","bare-os":"/node_modules/bare-os/index.js","bare-posix":"/node_modules/bare-posix/unsupported.js","bare-signals":"/node_modules/bare-signals/index.js","bare-stdio":"/node_modules/bare-stdio/index.js"},"/node_modules/bare-process/package.json":{},"/node_modules/bare-signals/binding.js":{"#package":"/node_modules/bare-signals/package.json",".":"linked:libbare-signals.5.0.0.so"},"/node_modules/bare-signals/index.js":{"#package":"/node_modules/bare-signals/package.json","./binding":"/node_modules/bare-signals/binding.js","./lib/emitter":"/node_modules/bare-signals/lib/emitter.js","./lib/errors":"/node_modules/bare-signals/lib/errors.js","bare-events":"/node_modules/bare-events/index.js"},"/node_modules/bare-signals/lib/emitter.js":{"#package":"/node_modules/bare-signals/package.json","..":"/node_modules/bare-signals/index.js","bare-events":"/node_modules/bare-events/index.js"},"/node_modules/bare-signals/lib/errors.js":{"#package":"/node_modules/bare-signals/package.json"},"/node_modules/bare-signals/package.json":{},"/node_modules/bare-stdio/binding.js":{"#package":"/node_modules/bare-stdio/package.json",".":"linked:libbare-stdio.1.0.3.so"},"/node_modules/bare-stdio/index.js":{"#package":"/node_modules/bare-stdio/package.json","./binding":"/node_modules/bare-stdio/binding.js","bare-fs":"/node_modules/bare-fs/index.js","bare-pipe":"/node_modules/bare-pipe/index.js","bare-tty":"/node_modules/bare-tty/index.js"},"/node_modules/bare-stdio/package.json":{},"/node_modules/bare-stream/index.js":{"#package":"/node_modules/bare-stream/package.json","./web":"/node_modules/bare-stream/web.js","b4a":"/node_modules/b4a/index.js","streamx":"/node_modules/streamx/index.js"},"/node_modules/bare-stream/package.json":{},"/node_modules/bare-stream/web.js":{"#package":"/node_modules/bare-stream/package.json","streamx":"/node_modules/streamx/index.js","teex":"/node_modules/teex/index.js"},"/node_modules/bare-tcp/binding.js":{"#package":"/node_modules/bare-tcp/package.json",".":"linked:libbare-tcp.2.6.1.so"},"/node_modules/bare-tcp/index.js":{"#package":"/node_modules/bare-tcp/package.json","./binding":"/node_modules/bare-tcp/binding.js","./lib/constants":"/node_modules/bare-tcp/lib/constants.js","./lib/errors":"/node_modules/bare-tcp/lib/errors.js","./lib/ip":"/node_modules/bare-tcp/lib/ip.js","bare-dns":"/node_modules/bare-dns/index.js","bare-events":"/node_modules/bare-events/index.js","bare-stream":"/node_modules/bare-stream/index.js"},"/node_modules/bare-tcp/lib/constants.js":{"#package":"/node_modules/bare-tcp/package.json","../binding":"/node_modules/bare-tcp/binding.js"},"/node_modules/bare-tcp/lib/errors.js":{"#package":"/node_modules/bare-tcp/package.json"},"/node_modules/bare-tcp/lib/ip.js":{"#package":"/node_modules/bare-tcp/package.json"},"/node_modules/bare-tcp/package.json":{},"/node_modules/bare-tty/binding.js":{"#package":"/node_modules/bare-tty/package.json",".":"linked:libbare-tty.5.2.1.so"},"/node_modules/bare-tty/index.js":{"#package":"/node_modules/bare-tty/package.json","./binding":"/node_modules/bare-tty/binding.js","./lib/constants":"/node_modules/bare-tty/lib/constants.js","./lib/errors":"/node_modules/bare-tty/lib/errors.js","bare-signals":"/node_modules/bare-signals/index.js","bare-stream":"/node_modules/bare-stream/index.js"},"/node_modules/bare-tty/lib/constants.js":{"#package":"/node_modules/bare-tty/package.json","../binding":"/node_modules/bare-tty/binding.js"},"/node_modules/bare-tty/lib/errors.js":{"#package":"/node_modules/bare-tty/package.json"},"/node_modules/bare-tty/package.json":{},"/node_modules/bare-type/binding.js":{"#package":"/node_modules/bare-type/package.json",".":"linked:libbare-type.1.1.1.so"},"/node_modules/bare-type/index.js":{"#package":"/node_modules/bare-type/package.json","./binding":"/node_modules/bare-type/binding.js"},"/node_modules/bare-type/package.json":{},"/node_modules/bare-url/binding.js":{"#package":"/node_modules/bare-url/package.json",".":"linked:libbare-url.2.5.4.so"},"/node_modules/bare-url/index.js":{"#package":"/node_modules/bare-url/package.json","./binding":"/node_modules/bare-url/binding.js","./lib/errors":"/node_modules/bare-url/lib/errors.js","./lib/url-search-params":"/node_modules/bare-url/lib/url-search-params.js","bare-path":"/node_modules/bare-path/index.js"},"/node_modules/bare-url/lib/errors.js":{"#package":"/node_modules/bare-url/package.json"},"/node_modules/bare-url/lib/url-search-params.js":{"#package":"/node_modules/bare-url/package.json"},"/node_modules/bare-url/package.json":{},"/node_modules/bits-to-bytes/index.js":{"#package":"/node_modules/bits-to-bytes/package.json","b4a":"/node_modules/b4a/index.js"},"/node_modules/bits-to-bytes/package.json":{},"/node_modules/blind-relay/index.js":{"#package":"/node_modules/blind-relay/package.json","./lib/errors":"/node_modules/blind-relay/lib/errors.js","b4a":"/node_modules/b4a/index.js","bits-to-bytes":"/node_modules/bits-to-bytes/index.js","compact-encoding":"/node_modules/compact-encoding/index.js","compact-encoding-bitfield":"/node_modules/compact-encoding-bitfield/index.js","events":"/node_modules/bare-events/index.js","protomux":"/node_modules/protomux/index.js","sodium-universal":"/node_modules/sodium-universal/index.js","streamx":"/node_modules/streamx/index.js"},"/node_modules/blind-relay/lib/errors.js":{"#package":"/node_modules/blind-relay/package.json"},"/node_modules/blind-relay/package.json":{},"/node_modules/bogon/index.js":{"#package":"/node_modules/bogon/package.json","b4a":"/node_modules/b4a/index.js","compact-encoding":"/node_modules/compact-encoding/index.js"},"/node_modules/bogon/package.json":{},"/node_modules/compact-encoding-bitfield/index.js":{"#package":"/node_modules/compact-encoding-bitfield/package.json","compact-encoding":"/node_modules/compact-encoding/index.js"},"/node_modules/compact-encoding-bitfield/package.json":{},"/node_modules/compact-encoding/endian.js":{"#package":"/node_modules/compact-encoding/package.json"},"/node_modules/compact-encoding/index.js":{"#package":"/node_modules/compact-encoding/package.json","./endian":"/node_modules/compact-encoding/endian.js","./lexint":"/node_modules/compact-encoding/lexint.js","./raw":"/node_modules/compact-encoding/raw.js","b4a":"/node_modules/b4a/index.js"},"/node_modules/compact-encoding/lexint.js":{"#package":"/node_modules/compact-encoding/package.json"},"/node_modules/compact-encoding/package.json":{},"/node_modules/compact-encoding/raw.js":{"#package":"/node_modules/compact-encoding/package.json","./endian":"/node_modules/compact-encoding/endian.js","b4a":"/node_modules/b4a/index.js"},"/node_modules/dht-rpc/index.js":{"#package":"/node_modules/dht-rpc/package.json","./lib/commands":"/node_modules/dht-rpc/lib/commands.js","./lib/errors":"/node_modules/dht-rpc/lib/errors.js","./lib/health":"/node_modules/dht-rpc/lib/health.js","./lib/io":"/node_modules/dht-rpc/lib/io.js","./lib/peer":"/node_modules/dht-rpc/lib/peer.js","./lib/query":"/node_modules/dht-rpc/lib/query.js","./lib/session":"/node_modules/dht-rpc/lib/session.js","b4a":"/node_modules/b4a/index.js","compact-encoding":"/node_modules/compact-encoding/index.js","events":"/node_modules/bare-events/index.js","kademlia-routing-table":"/node_modules/kademlia-routing-table/index.js","nat-sampler":"/node_modules/nat-sampler/index.js","sodium-universal":"/node_modules/sodium-universal/index.js","time-ordered-set":"/node_modules/time-ordered-set/index.js","udx-native":"/node_modules/udx-native/lib/udx.js"},"/node_modules/dht-rpc/lib/commands.js":{"#package":"/node_modules/dht-rpc/package.json"},"/node_modules/dht-rpc/lib/errors.js":{"#package":"/node_modules/dht-rpc/package.json"},"/node_modules/dht-rpc/lib/health.js":{"#package":"/node_modules/dht-rpc/package.json"},"/node_modules/dht-rpc/lib/io.js":{"#package":"/node_modules/dht-rpc/package.json","./errors":"/node_modules/dht-rpc/lib/errors.js","./peer":"/node_modules/dht-rpc/lib/peer.js","adaptive-timeout":"/node_modules/adaptive-timeout/index.js","b4a":"/node_modules/b4a/index.js","compact-encoding":"/node_modules/compact-encoding/index.js","fast-fifo":"/node_modules/fast-fifo/index.js","sodium-universal":"/node_modules/sodium-universal/index.js"},"/node_modules/dht-rpc/lib/peer.js":{"#package":"/node_modules/dht-rpc/package.json","b4a":"/node_modules/b4a/index.js","compact-encoding":"/node_modules/compact-encoding/index.js","sodium-universal":"/node_modules/sodium-universal/index.js"},"/node_modules/dht-rpc/lib/query.js":{"#package":"/node_modules/dht-rpc/package.json","./commands":"/node_modules/dht-rpc/lib/commands.js","./peer":"/node_modules/dht-rpc/lib/peer.js","b4a":"/node_modules/b4a/index.js","streamx":"/node_modules/streamx/index.js"},"/node_modules/dht-rpc/lib/session.js":{"#package":"/node_modules/dht-rpc/package.json"},"/node_modules/dht-rpc/package.json":{},"/node_modules/events-universal/bare.js":{"#package":"/node_modules/events-universal/package.json","bare-events":"/node_modules/bare-events/index.js"},"/node_modules/events-universal/package.json":{},"/node_modules/fast-fifo/fixed-size.js":{"#package":"/node_modules/fast-fifo/package.json"},"/node_modules/fast-fifo/index.js":{"#package":"/node_modules/fast-fifo/package.json","./fixed-size":"/node_modules/fast-fifo/fixed-size.js"},"/node_modules/fast-fifo/package.json":{},"/node_modules/favela-peer/lib/attestation.js":{"#package":"/node_modules/favela-peer/package.json","favela-identity/lib/attestation.js":"/node_modules/favela-peer/node_modules/favela-identity/lib/attestation.js"},"/node_modules/favela-peer/lib/avatars.js":{"#node:fs":"/node_modules/favela-peer/node_modules/bare-fs/index.js","#node:path":"/node_modules/favela-peer/node_modules/bare-path/index.js","#package":"/node_modules/favela-peer/package.json","b4a":"/node_modules/favela-peer/node_modules/b4a/index.js"},"/node_modules/favela-peer/lib/browsers.js":{"#package":"/node_modules/favela-peer/package.json","anproto":"/node_modules/favela-peer/node_modules/anproto/index.js","hyper-identity2":"/node_modules/favela-peer/node_modules/hyper-identity2/index.js"},"/node_modules/favela-peer/lib/device.js":{"#node:fs":"/node_modules/favela-peer/node_modules/bare-fs/index.js","#node:path":"/node_modules/favela-peer/node_modules/bare-path/index.js","#package":"/node_modules/favela-peer/package.json","b4a":"/node_modules/favela-peer/node_modules/b4a/index.js","hypercore-crypto":"/node_modules/favela-peer/node_modules/hypercore-crypto/index.js","hyperident/lib/crypto.js":"/node_modules/favela-peer/node_modules/hyperident/lib/crypto.js","keet-identity-key":"/node_modules/favela-peer/node_modules/keet-identity-key/index.js"},"/node_modules/favela-peer/lib/feed-base.js":{"#package":"/node_modules/favela-peer/package.json","./feed.js":"/node_modules/favela-peer/lib/feed.js","./log.js":"/node_modules/favela-peer/lib/log.js","autobee":"/node_modules/favela-peer/node_modules/autobee/index.js","b4a":"/node_modules/favela-peer/node_modules/b4a/index.js"},"/node_modules/favela-peer/lib/feed.js":{"#package":"/node_modules/favela-peer/package.json","./attestation.js":"/node_modules/favela-peer/lib/attestation.js"},"/node_modules/favela-peer/lib/files.js":{"#node:fs":"/node_modules/favela-peer/node_modules/bare-fs/index.js","#node:path":"/node_modules/favela-peer/node_modules/bare-path/index.js","#package":"/node_modules/favela-peer/package.json","b4a":"/node_modules/favela-peer/node_modules/b4a/index.js"},"/node_modules/favela-peer/lib/graph.js":{"#package":"/node_modules/favela-peer/package.json","./peer.js":"/node_modules/favela-peer/lib/peer.js"},"/node_modules/favela-peer/lib/log.js":{"#package":"/node_modules/favela-peer/package.json","b4a":"/node_modules/favela-peer/node_modules/b4a/index.js"},"/node_modules/favela-peer/lib/mentions.js":{"#package":"/node_modules/favela-peer/package.json"},"/node_modules/favela-peer/lib/mint.js":{"#node:fs":"/node_modules/favela-peer/node_modules/bare-fs/index.js","#node:path":"/node_modules/favela-peer/node_modules/bare-path/index.js","#package":"/node_modules/favela-peer/package.json","./device.js":"/node_modules/favela-peer/lib/device.js","hyperident/lib/crypto.js":"/node_modules/favela-peer/node_modules/hyperident/lib/crypto.js","keet-identity-key":"/node_modules/favela-peer/node_modules/keet-identity-key/index.js"},"/node_modules/favela-peer/lib/mirror.js":{"#package":"/node_modules/favela-peer/package.json","favela-markdown/lib/render.js":"/node_modules/favela-peer/node_modules/favela-markdown/lib/render.js"},"/node_modules/favela-peer/lib/name.js":{"#package":"/node_modules/favela-peer/package.json","hypercore-crypto":"/node_modules/favela-peer/node_modules/hypercore-crypto/index.js"},"/node_modules/favela-peer/lib/namespace.js":{"#package":"/node_modules/favela-peer/package.json","./attestation.js":"/node_modules/favela-peer/lib/attestation.js","autobee":"/node_modules/favela-peer/node_modules/autobee/index.js","b4a":"/node_modules/favela-peer/node_modules/b4a/index.js","hypercore-crypto":"/node_modules/favela-peer/node_modules/hypercore-crypto/index.js"},"/node_modules/favela-peer/lib/node.js":{"#node:fs":"/node_modules/favela-peer/node_modules/bare-fs/index.js","#node:path":"/node_modules/favela-peer/node_modules/bare-path/index.js","#package":"/node_modules/favela-peer/package.json","./attestation.js":"/node_modules/favela-peer/lib/attestation.js","./avatars.js":"/node_modules/favela-peer/lib/avatars.js","./browsers.js":"/node_modules/favela-peer/lib/browsers.js","./device.js":"/node_modules/favela-peer/lib/device.js","./feed-base.js":"/node_modules/favela-peer/lib/feed-base.js","./files.js":"/node_modules/favela-peer/lib/files.js","./mirror.js":"/node_modules/favela-peer/lib/mirror.js","./namespace.js":"/node_modules/favela-peer/lib/namespace.js","./pairing.js":"/node_modules/favela-peer/lib/pairing.js","./peer.js":"/node_modules/favela-peer/lib/peer.js","./record-store.js":"/node_modules/favela-peer/lib/record-store.js","./room.js":"/node_modules/favela-peer/lib/room.js","b4a":"/node_modules/favela-peer/node_modules/b4a/index.js","corestore":"/node_modules/favela-peer/node_modules/corestore/index.js","hyper-overlay":"/node_modules/favela-peer/node_modules/hyper-overlay/index.js"},"/node_modules/favela-peer/lib/pairing.js":{"#package":"/node_modules/favela-peer/package.json","./device.js":"/node_modules/favela-peer/lib/device.js","b4a":"/node_modules/favela-peer/node_modules/b4a/index.js","blind-pairing":"/node_modules/favela-peer/node_modules/blind-pairing/index.js","hypercore-crypto":"/node_modules/favela-peer/node_modules/hypercore-crypto/index.js","hyperswarm":"/node_modules/favela-peer/node_modules/hyperswarm/index.js"},"/node_modules/favela-peer/lib/peer.js":{"#package":"/node_modules/favela-peer/package.json","b4a":"/node_modules/favela-peer/node_modules/b4a/index.js","favela-markdown/lib/reach.js":"/node_modules/favela-peer/node_modules/favela-markdown/lib/reach.js","favela-markdown/lib/strip.js":"/node_modules/favela-peer/node_modules/favela-markdown/lib/strip.js","hypercore-crypto":"/node_modules/favela-peer/node_modules/hypercore-crypto/index.js"},"/node_modules/favela-peer/lib/record-store.js":{"#node:fs":"/node_modules/favela-peer/node_modules/bare-fs/index.js","#node:path":"/node_modules/favela-peer/node_modules/bare-path/index.js","#package":"/node_modules/favela-peer/package.json","b4a":"/node_modules/favela-peer/node_modules/b4a/index.js","favela-markdown/lib/record.js":"/node_modules/favela-peer/node_modules/favela-markdown/lib/record.js"},"/node_modules/favela-peer/lib/room.js":{"#package":"/node_modules/favela-peer/package.json","./attestation.js":"/node_modules/favela-peer/lib/attestation.js","./name.js":"/node_modules/favela-peer/lib/name.js","autobee":"/node_modules/favela-peer/node_modules/autobee/index.js","b4a":"/node_modules/favela-peer/node_modules/b4a/index.js"},"/node_modules/favela-peer/lib/rpc.js":{"#node:fs":"/node_modules/favela-peer/node_modules/bare-fs/index.js","#node:net":"/node_modules/favela-peer/node_modules/bare-net/index.js","#node:path":"/node_modules/favela-peer/node_modules/bare-path/index.js","#package":"/node_modules/favela-peer/package.json","./device.js":"/node_modules/favela-peer/lib/device.js","./graph.js":"/node_modules/favela-peer/lib/graph.js","./mentions.js":"/node_modules/favela-peer/lib/mentions.js","./mint.js":"/node_modules/favela-peer/lib/mint.js","./name.js":"/node_modules/favela-peer/lib/name.js","./peer.js":"/node_modules/favela-peer/lib/peer.js","./transcode.js":"/node_modules/favela-peer/lib/transcode.js","b4a":"/node_modules/favela-peer/node_modules/b4a/index.js","favela-markdown/lib/levels.js":"/node_modules/favela-peer/node_modules/favela-markdown/lib/levels.js"},"/node_modules/favela-peer/lib/transcode.js":{"#node:fs":"/node_modules/favela-peer/node_modules/bare-fs/index.js","#node:os":"/node_modules/favela-peer/node_modules/bare-os/index.js","#node:path":"/node_modules/favela-peer/node_modules/bare-path/index.js","#node:process":"/node_modules/favela-peer/node_modules/bare-process/index.js","#package":"/node_modules/favela-peer/package.json","bare-subprocess":"/node_modules/favela-peer/node_modules/bare-subprocess/index.js"},"/node_modules/favela-peer/lib/transport.js":{"#package":"/node_modules/favela-peer/package.json","protomux":"/node_modules/favela-peer/node_modules/protomux/index.js"},"/node_modules/favela-peer/node_modules/@hyperswarm/secret-stream/index.js":{"#package":"/node_modules/favela-peer/node_modules/@hyperswarm/secret-stream/package.json","./lib/bridge":"/node_modules/favela-peer/node_modules/@hyperswarm/secret-stream/lib/bridge.js","./lib/handshake":"/node_modules/favela-peer/node_modules/@hyperswarm/secret-stream/lib/handshake.js","b4a":"/node_modules/favela-peer/node_modules/b4a/index.js","hypercore-crypto":"/node_modules/favela-peer/node_modules/hypercore-crypto/index.js","sodium-secretstream":"/node_modules/favela-peer/node_modules/sodium-secretstream/index.js","sodium-universal":"/node_modules/favela-peer/node_modules/sodium-universal/index.js","streamx":"/node_modules/favela-peer/node_modules/streamx/index.js","timeout-refresh":"/node_modules/favela-peer/node_modules/timeout-refresh/index.js","unslab":"/node_modules/favela-peer/node_modules/unslab/index.js"},"/node_modules/favela-peer/node_modules/@hyperswarm/secret-stream/lib/bridge.js":{"#package":"/node_modules/favela-peer/node_modules/@hyperswarm/secret-stream/package.json","streamx":"/node_modules/favela-peer/node_modules/streamx/index.js"},"/node_modules/favela-peer/node_modules/@hyperswarm/secret-stream/lib/handshake.js":{"#package":"/node_modules/favela-peer/node_modules/@hyperswarm/secret-stream/package.json","b4a":"/node_modules/favela-peer/node_modules/b4a/index.js","noise-curve-ed":"/node_modules/favela-peer/node_modules/noise-curve-ed/index.js","noise-handshake":"/node_modules/favela-peer/node_modules/noise-handshake/noise.js","sodium-universal":"/node_modules/favela-peer/node_modules/sodium-universal/index.js"},"/node_modules/favela-peer/node_modules/@hyperswarm/secret-stream/package.json":{},"/node_modules/favela-peer/node_modules/adaptive-timeout/index.js":{"#package":"/node_modules/favela-peer/node_modules/adaptive-timeout/package.json","xache":"/node_modules/favela-peer/node_modules/xache/index.js"},"/node_modules/favela-peer/node_modules/adaptive-timeout/package.json":{},"/node_modules/favela-peer/node_modules/anproto/index.js":{"#package":"/node_modules/favela-peer/node_modules/anproto/package.json","./lib/an.js":"/node_modules/favela-peer/node_modules/anproto/lib/an.js","./lib/challenges.js":"/node_modules/favela-peer/node_modules/anproto/lib/challenges.js"},"/node_modules/favela-peer/node_modules/anproto/lib/an.js":{"#package":"/node_modules/favela-peer/node_modules/anproto/package.json","b4a":"/node_modules/favela-peer/node_modules/anproto/node_modules/b4a/index.js","hypercore-crypto":"/node_modules/favela-peer/node_modules/anproto/node_modules/hypercore-crypto/index.js","sodium-native":"/node_modules/favela-peer/node_modules/anproto/node_modules/sodium-native/index.js"},"/node_modules/favela-peer/node_modules/anproto/lib/challenges.js":{"#package":"/node_modules/favela-peer/node_modules/anproto/package.json","b4a":"/node_modules/favela-peer/node_modules/anproto/node_modules/b4a/index.js","hypercore-crypto":"/node_modules/favela-peer/node_modules/anproto/node_modules/hypercore-crypto/index.js"},"/node_modules/favela-peer/node_modules/anproto/node_modules/b4a/index.js":{"#package":"/node_modules/favela-peer/node_modules/anproto/node_modules/b4a/package.json"},"/node_modules/favela-peer/node_modules/anproto/node_modules/b4a/package.json":{},"/node_modules/favela-peer/node_modules/anproto/node_modules/bare-ansi-escapes/index.js":{"#package":"/node_modules/favela-peer/node_modules/anproto/node_modules/bare-ansi-escapes/package.json"},"/node_modules/favela-peer/node_modules/anproto/node_modules/bare-ansi-escapes/package.json":{},"/node_modules/favela-peer/node_modules/anproto/node_modules/bare-assert/index.js":{"#package":"/node_modules/favela-peer/node_modules/anproto/node_modules/bare-assert/package.json","bare-inspect":"/node_modules/favela-peer/node_modules/anproto/node_modules/bare-inspect/index.js"},"/node_modules/favela-peer/node_modules/anproto/node_modules/bare-assert/package.json":{},"/node_modules/favela-peer/node_modules/anproto/node_modules/bare-inspect/binding.js":{"#package":"/node_modules/favela-peer/node_modules/anproto/node_modules/bare-inspect/package.json",".":"linked:libbare-inspect.3.1.4.so"},"/node_modules/favela-peer/node_modules/anproto/node_modules/bare-inspect/index.js":{"#package":"/node_modules/favela-peer/node_modules/anproto/node_modules/bare-inspect/package.json","./binding":"/node_modules/favela-peer/node_modules/anproto/node_modules/bare-inspect/binding.js","bare-ansi-escapes":"/node_modules/favela-peer/node_modules/anproto/node_modules/bare-ansi-escapes/index.js","bare-type":"/node_modules/favela-peer/node_modules/anproto/node_modules/bare-type/index.js"},"/node_modules/favela-peer/node_modules/anproto/node_modules/bare-inspect/package.json":{},"/node_modules/favela-peer/node_modules/anproto/node_modules/bare-type/binding.js":{"#package":"/node_modules/favela-peer/node_modules/anproto/node_modules/bare-type/package.json",".":"linked:libbare-type.1.1.0.so"},"/node_modules/favela-peer/node_modules/anproto/node_modules/bare-type/index.js":{"#package":"/node_modules/favela-peer/node_modules/anproto/node_modules/bare-type/package.json","./binding":"/node_modules/favela-peer/node_modules/anproto/node_modules/bare-type/binding.js"},"/node_modules/favela-peer/node_modules/anproto/node_modules/bare-type/package.json":{},"/node_modules/favela-peer/node_modules/anproto/node_modules/compact-encoding/endian.js":{"#package":"/node_modules/favela-peer/node_modules/anproto/node_modules/compact-encoding/package.json"},"/node_modules/favela-peer/node_modules/anproto/node_modules/compact-encoding/index.js":{"#package":"/node_modules/favela-peer/node_modules/anproto/node_modules/compact-encoding/package.json","./endian":"/node_modules/favela-peer/node_modules/anproto/node_modules/compact-encoding/endian.js","./lexint":"/node_modules/favela-peer/node_modules/anproto/node_modules/compact-encoding/lexint.js","./raw":"/node_modules/favela-peer/node_modules/anproto/node_modules/compact-encoding/raw.js","b4a":"/node_modules/favela-peer/node_modules/anproto/node_modules/b4a/index.js"},"/node_modules/favela-peer/node_modules/anproto/node_modules/compact-encoding/lexint.js":{"#package":"/node_modules/favela-peer/node_modules/anproto/node_modules/compact-encoding/package.json"},"/node_modules/favela-peer/node_modules/anproto/node_modules/compact-encoding/package.json":{},"/node_modules/favela-peer/node_modules/anproto/node_modules/compact-encoding/raw.js":{"#package":"/node_modules/favela-peer/node_modules/anproto/node_modules/compact-encoding/package.json","./endian":"/node_modules/favela-peer/node_modules/anproto/node_modules/compact-encoding/endian.js","b4a":"/node_modules/favela-peer/node_modules/anproto/node_modules/b4a/index.js"},"/node_modules/favela-peer/node_modules/anproto/node_modules/hypercore-crypto/index.js":{"#package":"/node_modules/favela-peer/node_modules/anproto/node_modules/hypercore-crypto/package.json","b4a":"/node_modules/favela-peer/node_modules/anproto/node_modules/b4a/index.js","compact-encoding":"/node_modules/favela-peer/node_modules/anproto/node_modules/compact-encoding/index.js","sodium-universal":"/node_modules/favela-peer/node_modules/anproto/node_modules/sodium-universal/index.js"},"/node_modules/favela-peer/node_modules/anproto/node_modules/hypercore-crypto/package.json":{},"/node_modules/favela-peer/node_modules/anproto/node_modules/require-addon/lib/bare.js":{"#package":"/node_modules/favela-peer/node_modules/anproto/node_modules/require-addon/package.json"},"/node_modules/favela-peer/node_modules/anproto/node_modules/require-addon/package.json":{},"/node_modules/favela-peer/node_modules/anproto/node_modules/sodium-native/binding.js":{"#package":"/node_modules/favela-peer/node_modules/anproto/node_modules/sodium-native/package.json",".":"linked:libsodium-native.5.1.0.so","require-addon":"/node_modules/favela-peer/node_modules/anproto/node_modules/require-addon/lib/bare.js"},"/node_modules/favela-peer/node_modules/anproto/node_modules/sodium-native/index.js":{"#package":"/node_modules/favela-peer/node_modules/anproto/node_modules/sodium-native/package.json","./binding":"/node_modules/favela-peer/node_modules/anproto/node_modules/sodium-native/binding.js","assert":"/node_modules/favela-peer/node_modules/anproto/node_modules/bare-assert/index.js","which-runtime":"/node_modules/favela-peer/node_modules/anproto/node_modules/which-runtime/index.js"},"/node_modules/favela-peer/node_modules/anproto/node_modules/sodium-native/package.json":{},"/node_modules/favela-peer/node_modules/anproto/node_modules/sodium-universal/index.js":{"#package":"/node_modules/favela-peer/node_modules/anproto/node_modules/sodium-universal/package.json","sodium-native":"/node_modules/favela-peer/node_modules/anproto/node_modules/sodium-native/index.js"},"/node_modules/favela-peer/node_modules/anproto/node_modules/sodium-universal/package.json":{},"/node_modules/favela-peer/node_modules/anproto/node_modules/which-runtime/index.js":{"#package":"/node_modules/favela-peer/node_modules/anproto/node_modules/which-runtime/package.json"},"/node_modules/favela-peer/node_modules/anproto/node_modules/which-runtime/package.json":{},"/node_modules/favela-peer/node_modules/anproto/package.json":{},"/node_modules/favela-peer/node_modules/autobee-encryption/index.js":{"#package":"/node_modules/favela-peer/node_modules/autobee-encryption/package.json","b4a":"/node_modules/favela-peer/node_modules/b4a/index.js","compact-encoding":"/node_modules/favela-peer/node_modules/autobee-encryption/node_modules/compact-encoding/index.js","hypercore-crypto":"/node_modules/favela-peer/node_modules/hypercore-crypto/index.js","hypercore/lib/default-encryption.js":"/node_modules/favela-peer/node_modules/hypercore/lib/default-encryption.js","sodium-universal":"/node_modules/favela-peer/node_modules/sodium-universal/index.js"},"/node_modules/favela-peer/node_modules/autobee-encryption/node_modules/compact-encoding/endian.js":{"#package":"/node_modules/favela-peer/node_modules/autobee-encryption/node_modules/compact-encoding/package.json"},"/node_modules/favela-peer/node_modules/autobee-encryption/node_modules/compact-encoding/index.js":{"#package":"/node_modules/favela-peer/node_modules/autobee-encryption/node_modules/compact-encoding/package.json","./endian":"/node_modules/favela-peer/node_modules/autobee-encryption/node_modules/compact-encoding/endian.js","./lexint":"/node_modules/favela-peer/node_modules/autobee-encryption/node_modules/compact-encoding/lexint.js","./raw":"/node_modules/favela-peer/node_modules/autobee-encryption/node_modules/compact-encoding/raw.js","b4a":"/node_modules/favela-peer/node_modules/b4a/index.js"},"/node_modules/favela-peer/node_modules/autobee-encryption/node_modules/compact-encoding/lexint.js":{"#package":"/node_modules/favela-peer/node_modules/autobee-encryption/node_modules/compact-encoding/package.json"},"/node_modules/favela-peer/node_modules/autobee-encryption/node_modules/compact-encoding/package.json":{},"/node_modules/favela-peer/node_modules/autobee-encryption/node_modules/compact-encoding/raw.js":{"#package":"/node_modules/favela-peer/node_modules/autobee-encryption/node_modules/compact-encoding/package.json","./endian":"/node_modules/favela-peer/node_modules/autobee-encryption/node_modules/compact-encoding/endian.js","b4a":"/node_modules/favela-peer/node_modules/b4a/index.js"},"/node_modules/favela-peer/node_modules/autobee-encryption/package.json":{},"/node_modules/favela-peer/node_modules/autobee-wakeup/index.js":{"#package":"/node_modules/favela-peer/node_modules/autobee-wakeup/package.json","b4a":"/node_modules/favela-peer/node_modules/b4a/index.js","core-coupler":"/node_modules/favela-peer/node_modules/core-coupler/index.js","protomux-wakeup":"/node_modules/favela-peer/node_modules/protomux-wakeup/index.js","ready-resource":"/node_modules/favela-peer/node_modules/ready-resource/index.js"},"/node_modules/favela-peer/node_modules/autobee-wakeup/package.json":{},"/node_modules/favela-peer/node_modules/autobee/encoding/legacy.js":{"#package":"/node_modules/favela-peer/node_modules/autobee/package.json","compact-encoding":"/node_modules/favela-peer/node_modules/compact-encoding/index.js"},"/node_modules/favela-peer/node_modules/autobee/encoding/spec/autobee/index.js":{"#package":"/node_modules/favela-peer/node_modules/autobee/package.json","../../legacy.js":"/node_modules/favela-peer/node_modules/autobee/encoding/legacy.js","hyperschema/runtime":"/node_modules/favela-peer/node_modules/hyperschema/runtime.cjs"},"/node_modules/favela-peer/node_modules/autobee/index.js":{"#package":"/node_modules/favela-peer/node_modules/autobee/package.json","./lib/apply-calls.js":"/node_modules/favela-peer/node_modules/autobee/lib/apply-calls.js","./lib/apply-view.js":"/node_modules/favela-peer/node_modules/autobee/lib/apply-view.js","./lib/asserts.js":"/node_modules/favela-peer/node_modules/autobee/lib/asserts.js","./lib/boot.js":"/node_modules/favela-peer/node_modules/autobee/lib/boot.js","./lib/constants.js":"/node_modules/favela-peer/node_modules/autobee/lib/constants.js","./lib/encoding.js":"/node_modules/favela-peer/node_modules/autobee/lib/encoding.js","./lib/fast-forward.js":"/node_modules/favela-peer/node_modules/autobee/lib/fast-forward.js","./lib/migrations.js":"/node_modules/favela-peer/node_modules/autobee/lib/migrations.js","./lib/system.js":"/node_modules/favela-peer/node_modules/autobee/lib/system.js","./lib/topo.js":"/node_modules/favela-peer/node_modules/autobee/lib/topo.js","./lib/trusted.js":"/node_modules/favela-peer/node_modules/autobee/lib/trusted.js","./lib/updates.js":"/node_modules/favela-peer/node_modules/autobee/lib/updates.js","./lib/witness.js":"/node_modules/favela-peer/node_modules/autobee/lib/witness.js","./lib/writers.js":"/node_modules/favela-peer/node_modules/autobee/lib/writers.js","autobee-encryption":"/node_modules/favela-peer/node_modules/autobee-encryption/index.js","autobee-wakeup":"/node_modules/favela-peer/node_modules/autobee-wakeup/index.js","b4a":"/node_modules/favela-peer/node_modules/b4a/index.js","compact-encoding":"/node_modules/favela-peer/node_modules/compact-encoding/index.js","hyperbee2":"/node_modules/favela-peer/node_modules/hyperbee2/index.js","hypercore":"/node_modules/favela-peer/node_modules/hypercore/index.js","hypercore-crypto":"/node_modules/favela-peer/node_modules/hypercore-crypto/index.js","hypercore-id-encoding":"/node_modules/favela-peer/node_modules/hypercore-id-encoding/index.js","ready-guard":"/node_modules/favela-peer/node_modules/ready-guard/index.js","ready-resource":"/node_modules/favela-peer/node_modules/ready-resource/index.js","resolve-reject-promise":"/node_modules/favela-peer/node_modules/resolve-reject-promise/index.js","safety-catch":"/node_modules/favela-peer/node_modules/safety-catch/index.js"},"/node_modules/favela-peer/node_modules/autobee/lib/apply-calls.js":{"#package":"/node_modules/favela-peer/node_modules/autobee/package.json","hypercore-id-encoding":"/node_modules/favela-peer/node_modules/hypercore-id-encoding/index.js"},"/node_modules/favela-peer/node_modules/autobee/lib/apply-view.js":{"#package":"/node_modules/favela-peer/node_modules/autobee/package.json"},"/node_modules/favela-peer/node_modules/autobee/lib/asserts.js":{"#package":"/node_modules/favela-peer/node_modules/autobee/package.json"},"/node_modules/favela-peer/node_modules/autobee/lib/boot.js":{"#package":"/node_modules/favela-peer/node_modules/autobee/package.json","./encoding.js":"/node_modules/favela-peer/node_modules/autobee/lib/encoding.js","./migrations.js":"/node_modules/favela-peer/node_modules/autobee/lib/migrations.js","b4a":"/node_modules/favela-peer/node_modules/b4a/index.js"},"/node_modules/favela-peer/node_modules/autobee/lib/constants.js":{"#package":"/node_modules/favela-peer/node_modules/autobee/package.json"},"/node_modules/favela-peer/node_modules/autobee/lib/encoding.js":{"#package":"/node_modules/favela-peer/node_modules/autobee/package.json","../encoding/spec/autobee":"/node_modules/favela-peer/node_modules/autobee/encoding/spec/autobee/index.js","./constants":"/node_modules/favela-peer/node_modules/autobee/lib/constants.js","autobee-encryption":"/node_modules/favela-peer/node_modules/autobee-encryption/index.js","b4a":"/node_modules/favela-peer/node_modules/b4a/index.js","compact-encoding":"/node_modules/favela-peer/node_modules/compact-encoding/index.js","hypercore-crypto":"/node_modules/favela-peer/node_modules/hypercore-crypto/index.js"},"/node_modules/favela-peer/node_modules/autobee/lib/fast-forward.js":{"#package":"/node_modules/favela-peer/node_modules/autobee/package.json","./constants.js":"/node_modules/favela-peer/node_modules/autobee/lib/constants.js","./migrations.js":"/node_modules/favela-peer/node_modules/autobee/lib/migrations.js","./system.js":"/node_modules/favela-peer/node_modules/autobee/lib/system.js","b4a":"/node_modules/favela-peer/node_modules/b4a/index.js","hypercore":"/node_modules/favela-peer/node_modules/hypercore/index.js","safety-catch":"/node_modules/favela-peer/node_modules/safety-catch/index.js"},"/node_modules/favela-peer/node_modules/autobee/lib/migrations.js":{"#package":"/node_modules/favela-peer/node_modules/autobee/package.json","../encoding/spec/autobee":"/node_modules/favela-peer/node_modules/autobee/encoding/spec/autobee/index.js","./asserts.js":"/node_modules/favela-peer/node_modules/autobee/lib/asserts.js","./constants.js":"/node_modules/favela-peer/node_modules/autobee/lib/constants.js","./encoding.js":"/node_modules/favela-peer/node_modules/autobee/lib/encoding.js","./fast-forward.js":"/node_modules/favela-peer/node_modules/autobee/lib/fast-forward.js","autobee-encryption":"/node_modules/favela-peer/node_modules/autobee-encryption/index.js","b4a":"/node_modules/favela-peer/node_modules/b4a/index.js","compact-encoding":"/node_modules/favela-peer/node_modules/compact-encoding/index.js","hyperbee2/lib/encoding.js":"/node_modules/favela-peer/node_modules/hyperbee2/lib/encoding.js","hypercore-crypto":"/node_modules/favela-peer/node_modules/hypercore-crypto/index.js"},"/node_modules/favela-peer/node_modules/autobee/lib/promotions.js":{"#package":"/node_modules/favela-peer/node_modules/autobee/package.json","./encoding.js":"/node_modules/favela-peer/node_modules/autobee/lib/encoding.js","b4a":"/node_modules/favela-peer/node_modules/b4a/index.js"},"/node_modules/favela-peer/node_modules/autobee/lib/system.js":{"#package":"/node_modules/favela-peer/node_modules/autobee/package.json","./constants":"/node_modules/favela-peer/node_modules/autobee/lib/constants.js","./encoding.js":"/node_modules/favela-peer/node_modules/autobee/lib/encoding.js","./promotions.js":"/node_modules/favela-peer/node_modules/autobee/lib/promotions.js","./topo.js":"/node_modules/favela-peer/node_modules/autobee/lib/topo.js","b4a":"/node_modules/favela-peer/node_modules/b4a/index.js","hyperbee2":"/node_modules/favela-peer/node_modules/hyperbee2/index.js"},"/node_modules/favela-peer/node_modules/autobee/lib/topo.js":{"#package":"/node_modules/favela-peer/node_modules/autobee/package.json","./asserts.js":"/node_modules/favela-peer/node_modules/autobee/lib/asserts.js","./constants.js":"/node_modules/favela-peer/node_modules/autobee/lib/constants.js","./encoding.js":"/node_modules/favela-peer/node_modules/autobee/lib/encoding.js","./writers.js":"/node_modules/favela-peer/node_modules/autobee/lib/writers.js","b4a":"/node_modules/favela-peer/node_modules/b4a/index.js"},"/node_modules/favela-peer/node_modules/autobee/lib/triggers.js":{"#package":"/node_modules/favela-peer/node_modules/autobee/package.json"},"/node_modules/favela-peer/node_modules/autobee/lib/trusted.js":{"#package":"/node_modules/favela-peer/node_modules/autobee/package.json","b4a":"/node_modules/favela-peer/node_modules/b4a/index.js"},"/node_modules/favela-peer/node_modules/autobee/lib/updates.js":{"#package":"/node_modules/favela-peer/node_modules/autobee/package.json"},"/node_modules/favela-peer/node_modules/autobee/lib/witness.js":{"#package":"/node_modules/favela-peer/node_modules/autobee/package.json","./encoding.js":"/node_modules/favela-peer/node_modules/autobee/lib/encoding.js","b4a":"/node_modules/favela-peer/node_modules/b4a/index.js"},"/node_modules/favela-peer/node_modules/autobee/lib/writers.js":{"#package":"/node_modules/favela-peer/node_modules/autobee/package.json","./constants.js":"/node_modules/favela-peer/node_modules/autobee/lib/constants.js","./encoding.js":"/node_modules/favela-peer/node_modules/autobee/lib/encoding.js","./triggers.js":"/node_modules/favela-peer/node_modules/autobee/lib/triggers.js","./witness.js":"/node_modules/favela-peer/node_modules/autobee/lib/witness.js","autobee-encryption":"/node_modules/favela-peer/node_modules/autobee-encryption/index.js","b4a":"/node_modules/favela-peer/node_modules/b4a/index.js"},"/node_modules/favela-peer/node_modules/autobee/package.json":{},"/node_modules/favela-peer/node_modules/b4a/index.js":{"#package":"/node_modules/favela-peer/node_modules/b4a/package.json"},"/node_modules/favela-peer/node_modules/b4a/package.json":{},"/node_modules/favela-peer/node_modules/bare-abort/binding.js":{"#package":"/node_modules/favela-peer/node_modules/bare-abort/package.json",".":"linked:libbare-abort.2.0.13.so"},"/node_modules/favela-peer/node_modules/bare-abort/index.js":{"#package":"/node_modules/favela-peer/node_modules/bare-abort/package.json","./binding":"/node_modules/favela-peer/node_modules/bare-abort/binding.js"},"/node_modules/favela-peer/node_modules/bare-abort/package.json":{},"/node_modules/favela-peer/node_modules/bare-ansi-escapes/index.js":{"#package":"/node_modules/favela-peer/node_modules/bare-ansi-escapes/package.json"},"/node_modules/favela-peer/node_modules/bare-ansi-escapes/package.json":{},"/node_modules/favela-peer/node_modules/bare-assert/index.js":{"#package":"/node_modules/favela-peer/node_modules/bare-assert/package.json","bare-inspect":"/node_modules/favela-peer/node_modules/bare-inspect/index.js"},"/node_modules/favela-peer/node_modules/bare-assert/package.json":{},"/node_modules/favela-peer/node_modules/bare-buffer/binding.js":{"#package":"/node_modules/favela-peer/node_modules/bare-buffer/package.json",".":"linked:libbare-buffer.3.7.1.so"},"/node_modules/favela-peer/node_modules/bare-buffer/index.js":{"#package":"/node_modules/favela-peer/node_modules/bare-buffer/package.json","./binding":"/node_modules/favela-peer/node_modules/bare-buffer/binding.js","./lib/ascii":"/node_modules/favela-peer/node_modules/bare-buffer/lib/ascii.js","./lib/base64":"/node_modules/favela-peer/node_modules/bare-buffer/lib/base64.js","./lib/base64url":"/node_modules/favela-peer/node_modules/bare-buffer/lib/base64url.js","./lib/checked":"/node_modules/favela-peer/node_modules/bare-buffer/lib/checked.js","./lib/constants":"/node_modules/favela-peer/node_modules/bare-buffer/lib/constants.js","./lib/hex":"/node_modules/favela-peer/node_modules/bare-buffer/lib/hex.js","./lib/latin1":"/node_modules/favela-peer/node_modules/bare-buffer/lib/latin1.js","./lib/utf16le":"/node_modules/favela-peer/node_modules/bare-buffer/lib/utf16le.js","./lib/utf8":"/node_modules/favela-peer/node_modules/bare-buffer/lib/utf8.js"},"/node_modules/favela-peer/node_modules/bare-buffer/lib/ascii.js":{"#package":"/node_modules/favela-peer/node_modules/bare-buffer/package.json","../binding":"/node_modules/favela-peer/node_modules/bare-buffer/binding.js","./checked":"/node_modules/favela-peer/node_modules/bare-buffer/lib/checked.js"},"/node_modules/favela-peer/node_modules/bare-buffer/lib/base64.js":{"#package":"/node_modules/favela-peer/node_modules/bare-buffer/package.json","../binding":"/node_modules/favela-peer/node_modules/bare-buffer/binding.js","./checked":"/node_modules/favela-peer/node_modules/bare-buffer/lib/checked.js"},"/node_modules/favela-peer/node_modules/bare-buffer/lib/base64url.js":{"#package":"/node_modules/favela-peer/node_modules/bare-buffer/package.json","../binding":"/node_modules/favela-peer/node_modules/bare-buffer/binding.js","./checked":"/node_modules/favela-peer/node_modules/bare-buffer/lib/checked.js"},"/node_modules/favela-peer/node_modules/bare-buffer/lib/checked.js":{"#package":"/node_modules/favela-peer/node_modules/bare-buffer/package.json"},"/node_modules/favela-peer/node_modules/bare-buffer/lib/constants.js":{"#package":"/node_modules/favela-peer/node_modules/bare-buffer/package.json","../binding":"/node_modules/favela-peer/node_modules/bare-buffer/binding.js"},"/node_modules/favela-peer/node_modules/bare-buffer/lib/hex.js":{"#package":"/node_modules/favela-peer/node_modules/bare-buffer/package.json","../binding":"/node_modules/favela-peer/node_modules/bare-buffer/binding.js","./checked":"/node_modules/favela-peer/node_modules/bare-buffer/lib/checked.js"},"/node_modules/favela-peer/node_modules/bare-buffer/lib/latin1.js":{"#package":"/node_modules/favela-peer/node_modules/bare-buffer/package.json","../binding":"/node_modules/favela-peer/node_modules/bare-buffer/binding.js","./checked":"/node_modules/favela-peer/node_modules/bare-buffer/lib/checked.js"},"/node_modules/favela-peer/node_modules/bare-buffer/lib/utf16le.js":{"#package":"/node_modules/favela-peer/node_modules/bare-buffer/package.json","../binding":"/node_modules/favela-peer/node_modules/bare-buffer/binding.js","./checked":"/node_modules/favela-peer/node_modules/bare-buffer/lib/checked.js"},"/node_modules/favela-peer/node_modules/bare-buffer/lib/utf8.js":{"#package":"/node_modules/favela-peer/node_modules/bare-buffer/package.json","../binding":"/node_modules/favela-peer/node_modules/bare-buffer/binding.js","./checked":"/node_modules/favela-peer/node_modules/bare-buffer/lib/checked.js"},"/node_modules/favela-peer/node_modules/bare-buffer/package.json":{},"/node_modules/favela-peer/node_modules/bare-dns/binding.js":{"#package":"/node_modules/favela-peer/node_modules/bare-dns/package.json",".":"linked:libbare-dns.2.2.0.so"},"/node_modules/favela-peer/node_modules/bare-dns/index.js":{"#package":"/node_modules/favela-peer/node_modules/bare-dns/package.json","./binding":"/node_modules/favela-peer/node_modules/bare-dns/binding.js","./lib/constants":"/node_modules/favela-peer/node_modules/bare-dns/lib/constants.js","./lib/errors":"/node_modules/favela-peer/node_modules/bare-dns/lib/errors.js","./lib/promises":"/node_modules/favela-peer/node_modules/bare-dns/lib/promises.js","./lib/resolver":"/node_modules/favela-peer/node_modules/bare-dns/lib/resolver.js"},"/node_modules/favela-peer/node_modules/bare-dns/lib/constants.js":{"#package":"/node_modules/favela-peer/node_modules/bare-dns/package.json"},"/node_modules/favela-peer/node_modules/bare-dns/lib/errors.js":{"#package":"/node_modules/favela-peer/node_modules/bare-dns/package.json","./constants":"/node_modules/favela-peer/node_modules/bare-dns/lib/constants.js"},"/node_modules/favela-peer/node_modules/bare-dns/lib/promises.js":{"#package":"/node_modules/favela-peer/node_modules/bare-dns/package.json","../":"/node_modules/favela-peer/node_modules/bare-dns/index.js","./constants":"/node_modules/favela-peer/node_modules/bare-dns/lib/constants.js","./errors":"/node_modules/favela-peer/node_modules/bare-dns/lib/errors.js","./resolver":"/node_modules/favela-peer/node_modules/bare-dns/lib/resolver.js"},"/node_modules/favela-peer/node_modules/bare-dns/lib/records.js":{"#package":"/node_modules/favela-peer/node_modules/bare-dns/package.json"},"/node_modules/favela-peer/node_modules/bare-dns/lib/resolver.js":{"#package":"/node_modules/favela-peer/node_modules/bare-dns/package.json","../binding":"/node_modules/favela-peer/node_modules/bare-dns/binding.js","./constants":"/node_modules/favela-peer/node_modules/bare-dns/lib/constants.js","./errors":"/node_modules/favela-peer/node_modules/bare-dns/lib/errors.js","./records":"/node_modules/favela-peer/node_modules/bare-dns/lib/records.js"},"/node_modules/favela-peer/node_modules/bare-dns/package.json":{},"/node_modules/favela-peer/node_modules/bare-env/index.js":{"#package":"/node_modules/favela-peer/node_modules/bare-env/package.json","bare-os":"/node_modules/favela-peer/node_modules/bare-os/index.js"},"/node_modules/favela-peer/node_modules/bare-env/package.json":{},"/node_modules/favela-peer/node_modules/bare-events/index.js":{"#package":"/node_modules/favela-peer/node_modules/bare-events/package.json","./lib/errors":"/node_modules/favela-peer/node_modules/bare-events/lib/errors.js"},"/node_modules/favela-peer/node_modules/bare-events/lib/errors.js":{"#package":"/node_modules/favela-peer/node_modules/bare-events/package.json"},"/node_modules/favela-peer/node_modules/bare-events/package.json":{},"/node_modules/favela-peer/node_modules/bare-fs/binding.js":{"#package":"/node_modules/favela-peer/node_modules/bare-fs/package.json",".":"linked:libbare-fs.4.8.1.so"},"/node_modules/favela-peer/node_modules/bare-fs/index.js":{"#package":"/node_modules/favela-peer/node_modules/bare-fs/package.json","./binding":"/node_modules/favela-peer/node_modules/bare-fs/binding.js","./lib/constants":"/node_modules/favela-peer/node_modules/bare-fs/lib/constants.js","./lib/errors":"/node_modules/favela-peer/node_modules/bare-fs/lib/errors.js","./promises":"/node_modules/favela-peer/node_modules/bare-fs/promises.js","bare-events":"/node_modules/favela-peer/node_modules/bare-events/index.js","bare-path":"/node_modules/favela-peer/node_modules/bare-path/index.js","bare-stream":"/node_modules/favela-peer/node_modules/bare-stream/index.js","bare-url":"/node_modules/favela-peer/node_modules/bare-url/index.js","fast-fifo":"/node_modules/favela-peer/node_modules/fast-fifo/index.js"},"/node_modules/favela-peer/node_modules/bare-fs/lib/constants.js":{"#package":"/node_modules/favela-peer/node_modules/bare-fs/package.json","../binding":"/node_modules/favela-peer/node_modules/bare-fs/binding.js"},"/node_modules/favela-peer/node_modules/bare-fs/lib/errors.js":{"#package":"/node_modules/favela-peer/node_modules/bare-fs/package.json","../binding":"/node_modules/favela-peer/node_modules/bare-fs/binding.js"},"/node_modules/favela-peer/node_modules/bare-fs/package.json":{},"/node_modules/favela-peer/node_modules/bare-fs/promises.js":{"#package":"/node_modules/favela-peer/node_modules/bare-fs/package.json",".":"/node_modules/favela-peer/node_modules/bare-fs/index.js","bare-events":"/node_modules/favela-peer/node_modules/bare-events/index.js"},"/node_modules/favela-peer/node_modules/bare-hrtime/binding.js":{"#package":"/node_modules/favela-peer/node_modules/bare-hrtime/package.json",".":"linked:libbare-hrtime.2.1.2.so"},"/node_modules/favela-peer/node_modules/bare-hrtime/index.js":{"#package":"/node_modules/favela-peer/node_modules/bare-hrtime/package.json","./binding":"/node_modules/favela-peer/node_modules/bare-hrtime/binding.js"},"/node_modules/favela-peer/node_modules/bare-hrtime/package.json":{},"/node_modules/favela-peer/node_modules/bare-inspect/binding.js":{"#package":"/node_modules/favela-peer/node_modules/bare-inspect/package.json",".":"linked:libbare-inspect.3.1.10.so"},"/node_modules/favela-peer/node_modules/bare-inspect/index.js":{"#package":"/node_modules/favela-peer/node_modules/bare-inspect/package.json","./binding":"/node_modules/favela-peer/node_modules/bare-inspect/binding.js","bare-ansi-escapes":"/node_modules/favela-peer/node_modules/bare-ansi-escapes/index.js","bare-type":"/node_modules/favela-peer/node_modules/bare-type/index.js"},"/node_modules/favela-peer/node_modules/bare-inspect/package.json":{},"/node_modules/favela-peer/node_modules/bare-net/index.js":{"#package":"/node_modules/favela-peer/node_modules/bare-net/package.json","./lib/constants":"/node_modules/favela-peer/node_modules/bare-net/lib/constants.js","./lib/errors":"/node_modules/favela-peer/node_modules/bare-net/lib/errors.js","bare-events":"/node_modules/favela-peer/node_modules/bare-events/index.js","bare-pipe":"/node_modules/favela-peer/node_modules/bare-pipe/index.js","bare-stream":"/node_modules/favela-peer/node_modules/bare-stream/index.js","bare-tcp":"/node_modules/favela-peer/node_modules/bare-tcp/index.js"},"/node_modules/favela-peer/node_modules/bare-net/lib/constants.js":{"#package":"/node_modules/favela-peer/node_modules/bare-net/package.json"},"/node_modules/favela-peer/node_modules/bare-net/lib/errors.js":{"#package":"/node_modules/favela-peer/node_modules/bare-net/package.json"},"/node_modules/favela-peer/node_modules/bare-net/package.json":{},"/node_modules/favela-peer/node_modules/bare-os/binding.js":{"#package":"/node_modules/favela-peer/node_modules/bare-os/package.json",".":"linked:libbare-os.3.9.3.so"},"/node_modules/favela-peer/node_modules/bare-os/index.js":{"#package":"/node_modules/favela-peer/node_modules/bare-os/package.json","./binding":"/node_modules/favela-peer/node_modules/bare-os/binding.js","./lib/constants":"/node_modules/favela-peer/node_modules/bare-os/lib/constants.js","./lib/errors":"/node_modules/favela-peer/node_modules/bare-os/lib/errors.js"},"/node_modules/favela-peer/node_modules/bare-os/lib/constants.js":{"#package":"/node_modules/favela-peer/node_modules/bare-os/package.json","../binding":"/node_modules/favela-peer/node_modules/bare-os/binding.js"},"/node_modules/favela-peer/node_modules/bare-os/lib/errors.js":{"#package":"/node_modules/favela-peer/node_modules/bare-os/package.json"},"/node_modules/favela-peer/node_modules/bare-os/package.json":{},"/node_modules/favela-peer/node_modules/bare-path/binding.js":{"#package":"/node_modules/favela-peer/node_modules/bare-path/package.json",".":"linked:libbare-path.3.1.2.so"},"/node_modules/favela-peer/node_modules/bare-path/index.js":{"#package":"/node_modules/favela-peer/node_modules/bare-path/package.json","./lib/posix":"/node_modules/favela-peer/node_modules/bare-path/lib/posix.js","./lib/win32":"/node_modules/favela-peer/node_modules/bare-path/lib/win32.js"},"/node_modules/favela-peer/node_modules/bare-path/lib/constants.js":{"#package":"/node_modules/favela-peer/node_modules/bare-path/package.json"},"/node_modules/favela-peer/node_modules/bare-path/lib/posix.js":{"#package":"/node_modules/favela-peer/node_modules/bare-path/package.json","../binding":"/node_modules/favela-peer/node_modules/bare-path/binding.js","./constants":"/node_modules/favela-peer/node_modules/bare-path/lib/constants.js","./shared":"/node_modules/favela-peer/node_modules/bare-path/lib/shared.js","./win32":"/node_modules/favela-peer/node_modules/bare-path/lib/win32.js"},"/node_modules/favela-peer/node_modules/bare-path/lib/shared.js":{"#package":"/node_modules/favela-peer/node_modules/bare-path/package.json","./constants":"/node_modules/favela-peer/node_modules/bare-path/lib/constants.js"},"/node_modules/favela-peer/node_modules/bare-path/lib/win32.js":{"#package":"/node_modules/favela-peer/node_modules/bare-path/package.json","../binding":"/node_modules/favela-peer/node_modules/bare-path/binding.js","./constants":"/node_modules/favela-peer/node_modules/bare-path/lib/constants.js","./posix":"/node_modules/favela-peer/node_modules/bare-path/lib/posix.js","./shared":"/node_modules/favela-peer/node_modules/bare-path/lib/shared.js"},"/node_modules/favela-peer/node_modules/bare-path/package.json":{},"/node_modules/favela-peer/node_modules/bare-pipe/binding.js":{"#package":"/node_modules/favela-peer/node_modules/bare-pipe/package.json",".":"linked:libbare-pipe.4.3.1.so"},"/node_modules/favela-peer/node_modules/bare-pipe/index.js":{"#package":"/node_modules/favela-peer/node_modules/bare-pipe/package.json","./binding":"/node_modules/favela-peer/node_modules/bare-pipe/binding.js","./lib/constants":"/node_modules/favela-peer/node_modules/bare-pipe/lib/constants.js","./lib/errors":"/node_modules/favela-peer/node_modules/bare-pipe/lib/errors.js","bare-events":"/node_modules/favela-peer/node_modules/bare-events/index.js","bare-stream":"/node_modules/favela-peer/node_modules/bare-stream/index.js"},"/node_modules/favela-peer/node_modules/bare-pipe/lib/constants.js":{"#package":"/node_modules/favela-peer/node_modules/bare-pipe/package.json","../binding":"/node_modules/favela-peer/node_modules/bare-pipe/binding.js"},"/node_modules/favela-peer/node_modules/bare-pipe/lib/errors.js":{"#package":"/node_modules/favela-peer/node_modules/bare-pipe/package.json"},"/node_modules/favela-peer/node_modules/bare-pipe/package.json":{},"/node_modules/favela-peer/node_modules/bare-posix/package.json":{},"/node_modules/favela-peer/node_modules/bare-posix/unsupported.js":{"#package":"/node_modules/favela-peer/node_modules/bare-posix/package.json"},"/node_modules/favela-peer/node_modules/bare-process/index.js":{"#package":"/node_modules/favela-peer/node_modules/bare-process/package.json","bare-abort":"/node_modules/favela-peer/node_modules/bare-abort/index.js","bare-env":"/node_modules/favela-peer/node_modules/bare-env/index.js","bare-events":"/node_modules/favela-peer/node_modules/bare-events/index.js","bare-hrtime":"/node_modules/favela-peer/node_modules/bare-hrtime/index.js","bare-os":"/node_modules/favela-peer/node_modules/bare-os/index.js","bare-posix":"/node_modules/favela-peer/node_modules/bare-posix/unsupported.js","bare-signals":"/node_modules/favela-peer/node_modules/bare-signals/index.js","bare-stdio":"/node_modules/favela-peer/node_modules/bare-stdio/index.js"},"/node_modules/favela-peer/node_modules/bare-process/package.json":{},"/node_modules/favela-peer/node_modules/bare-signals/binding.js":{"#package":"/node_modules/favela-peer/node_modules/bare-signals/package.json",".":"linked:libbare-signals.5.0.0.so"},"/node_modules/favela-peer/node_modules/bare-signals/index.js":{"#package":"/node_modules/favela-peer/node_modules/bare-signals/package.json","./binding":"/node_modules/favela-peer/node_modules/bare-signals/binding.js","./lib/emitter":"/node_modules/favela-peer/node_modules/bare-signals/lib/emitter.js","./lib/errors":"/node_modules/favela-peer/node_modules/bare-signals/lib/errors.js","bare-events":"/node_modules/favela-peer/node_modules/bare-events/index.js"},"/node_modules/favela-peer/node_modules/bare-signals/lib/emitter.js":{"#package":"/node_modules/favela-peer/node_modules/bare-signals/package.json","..":"/node_modules/favela-peer/node_modules/bare-signals/index.js","bare-events":"/node_modules/favela-peer/node_modules/bare-events/index.js"},"/node_modules/favela-peer/node_modules/bare-signals/lib/errors.js":{"#package":"/node_modules/favela-peer/node_modules/bare-signals/package.json"},"/node_modules/favela-peer/node_modules/bare-signals/package.json":{},"/node_modules/favela-peer/node_modules/bare-stdio/binding.js":{"#package":"/node_modules/favela-peer/node_modules/bare-stdio/package.json",".":"linked:libbare-stdio.1.0.3.so"},"/node_modules/favela-peer/node_modules/bare-stdio/index.js":{"#package":"/node_modules/favela-peer/node_modules/bare-stdio/package.json","./binding":"/node_modules/favela-peer/node_modules/bare-stdio/binding.js","bare-fs":"/node_modules/favela-peer/node_modules/bare-fs/index.js","bare-pipe":"/node_modules/favela-peer/node_modules/bare-pipe/index.js","bare-tty":"/node_modules/favela-peer/node_modules/bare-tty/index.js"},"/node_modules/favela-peer/node_modules/bare-stdio/package.json":{},"/node_modules/favela-peer/node_modules/bare-stream/index.js":{"#package":"/node_modules/favela-peer/node_modules/bare-stream/package.json","./web":"/node_modules/favela-peer/node_modules/bare-stream/web.js","b4a":"/node_modules/favela-peer/node_modules/b4a/index.js","streamx":"/node_modules/favela-peer/node_modules/streamx/index.js"},"/node_modules/favela-peer/node_modules/bare-stream/package.json":{},"/node_modules/favela-peer/node_modules/bare-stream/web.js":{"#package":"/node_modules/favela-peer/node_modules/bare-stream/package.json","streamx":"/node_modules/favela-peer/node_modules/streamx/index.js","teex":"/node_modules/favela-peer/node_modules/teex/index.js"},"/node_modules/favela-peer/node_modules/bare-structured-clone/binding.js":{"#package":"/node_modules/favela-peer/node_modules/bare-structured-clone/package.json",".":"linked:libbare-structured-clone.1.6.0.so"},"/node_modules/favela-peer/node_modules/bare-structured-clone/index.js":{"#package":"/node_modules/favela-peer/node_modules/bare-structured-clone/package.json","./binding":"/node_modules/favela-peer/node_modules/bare-structured-clone/binding.js","./lib/constants":"/node_modules/favela-peer/node_modules/bare-structured-clone/lib/constants.js","./lib/errors":"/node_modules/favela-peer/node_modules/bare-structured-clone/lib/errors.js","bare-buffer":"/node_modules/favela-peer/node_modules/bare-buffer/index.js","bare-type":"/node_modules/favela-peer/node_modules/bare-type/index.js","bare-url":"/node_modules/favela-peer/node_modules/bare-url/index.js","compact-encoding":"/node_modules/favela-peer/node_modules/compact-encoding/index.js"},"/node_modules/favela-peer/node_modules/bare-structured-clone/lib/constants.js":{"#package":"/node_modules/favela-peer/node_modules/bare-structured-clone/package.json"},"/node_modules/favela-peer/node_modules/bare-structured-clone/lib/errors.js":{"#package":"/node_modules/favela-peer/node_modules/bare-structured-clone/package.json"},"/node_modules/favela-peer/node_modules/bare-structured-clone/package.json":{},"/node_modules/favela-peer/node_modules/bare-subprocess/binding.js":{"#package":"/node_modules/favela-peer/node_modules/bare-subprocess/package.json",".":"linked:libbare-subprocess.6.1.0.so"},"/node_modules/favela-peer/node_modules/bare-subprocess/index.js":{"#package":"/node_modules/favela-peer/node_modules/bare-subprocess/package.json","./binding":"/node_modules/favela-peer/node_modules/bare-subprocess/binding.js","./lib/channel":"/node_modules/favela-peer/node_modules/bare-subprocess/lib/channel.js","./lib/constants":"/node_modules/favela-peer/node_modules/bare-subprocess/lib/constants.js","./lib/errors":"/node_modules/favela-peer/node_modules/bare-subprocess/lib/errors.js","./lib/framers/advanced":"/node_modules/favela-peer/node_modules/bare-subprocess/lib/framers/advanced.js","./lib/framers/json":"/node_modules/favela-peer/node_modules/bare-subprocess/lib/framers/json.js","bare-env":"/node_modules/favela-peer/node_modules/bare-env/index.js","bare-events":"/node_modules/favela-peer/node_modules/bare-events/index.js","bare-os":"/node_modules/favela-peer/node_modules/bare-os/index.js","bare-pipe":"/node_modules/favela-peer/node_modules/bare-pipe/index.js","bare-url":"/node_modules/favela-peer/node_modules/bare-url/index.js"},"/node_modules/favela-peer/node_modules/bare-subprocess/lib/channel.js":{"#package":"/node_modules/favela-peer/node_modules/bare-subprocess/package.json","./errors":"/node_modules/favela-peer/node_modules/bare-subprocess/lib/errors.js","bare-pipe":"/node_modules/favela-peer/node_modules/bare-pipe/index.js","bare-tcp":"/node_modules/favela-peer/node_modules/bare-tcp/index.js"},"/node_modules/favela-peer/node_modules/bare-subprocess/lib/constants.js":{"#package":"/node_modules/favela-peer/node_modules/bare-subprocess/package.json","bare-os":"/node_modules/favela-peer/node_modules/bare-os/index.js"},"/node_modules/favela-peer/node_modules/bare-subprocess/lib/errors.js":{"#package":"/node_modules/favela-peer/node_modules/bare-subprocess/package.json"},"/node_modules/favela-peer/node_modules/bare-subprocess/lib/framers/advanced.js":{"#package":"/node_modules/favela-peer/node_modules/bare-subprocess/package.json","../errors":"/node_modules/favela-peer/node_modules/bare-subprocess/lib/errors.js","bare-structured-clone":"/node_modules/favela-peer/node_modules/bare-structured-clone/index.js"},"/node_modules/favela-peer/node_modules/bare-subprocess/lib/framers/json.js":{"#package":"/node_modules/favela-peer/node_modules/bare-subprocess/package.json","../errors":"/node_modules/favela-peer/node_modules/bare-subprocess/lib/errors.js"},"/node_modules/favela-peer/node_modules/bare-subprocess/package.json":{},"/node_modules/favela-peer/node_modules/bare-tcp/binding.js":{"#package":"/node_modules/favela-peer/node_modules/bare-tcp/package.json",".":"linked:libbare-tcp.2.6.1.so"},"/node_modules/favela-peer/node_modules/bare-tcp/index.js":{"#package":"/node_modules/favela-peer/node_modules/bare-tcp/package.json","./binding":"/node_modules/favela-peer/node_modules/bare-tcp/binding.js","./lib/constants":"/node_modules/favela-peer/node_modules/bare-tcp/lib/constants.js","./lib/errors":"/node_modules/favela-peer/node_modules/bare-tcp/lib/errors.js","./lib/ip":"/node_modules/favela-peer/node_modules/bare-tcp/lib/ip.js","bare-dns":"/node_modules/favela-peer/node_modules/bare-dns/index.js","bare-events":"/node_modules/favela-peer/node_modules/bare-events/index.js","bare-stream":"/node_modules/favela-peer/node_modules/bare-stream/index.js"},"/node_modules/favela-peer/node_modules/bare-tcp/lib/constants.js":{"#package":"/node_modules/favela-peer/node_modules/bare-tcp/package.json","../binding":"/node_modules/favela-peer/node_modules/bare-tcp/binding.js"},"/node_modules/favela-peer/node_modules/bare-tcp/lib/errors.js":{"#package":"/node_modules/favela-peer/node_modules/bare-tcp/package.json"},"/node_modules/favela-peer/node_modules/bare-tcp/lib/ip.js":{"#package":"/node_modules/favela-peer/node_modules/bare-tcp/package.json"},"/node_modules/favela-peer/node_modules/bare-tcp/package.json":{},"/node_modules/favela-peer/node_modules/bare-tty/binding.js":{"#package":"/node_modules/favela-peer/node_modules/bare-tty/package.json",".":"linked:libbare-tty.5.2.1.so"},"/node_modules/favela-peer/node_modules/bare-tty/index.js":{"#package":"/node_modules/favela-peer/node_modules/bare-tty/package.json","./binding":"/node_modules/favela-peer/node_modules/bare-tty/binding.js","./lib/constants":"/node_modules/favela-peer/node_modules/bare-tty/lib/constants.js","./lib/errors":"/node_modules/favela-peer/node_modules/bare-tty/lib/errors.js","bare-signals":"/node_modules/favela-peer/node_modules/bare-signals/index.js","bare-stream":"/node_modules/favela-peer/node_modules/bare-stream/index.js"},"/node_modules/favela-peer/node_modules/bare-tty/lib/constants.js":{"#package":"/node_modules/favela-peer/node_modules/bare-tty/package.json","../binding":"/node_modules/favela-peer/node_modules/bare-tty/binding.js"},"/node_modules/favela-peer/node_modules/bare-tty/lib/errors.js":{"#package":"/node_modules/favela-peer/node_modules/bare-tty/package.json"},"/node_modules/favela-peer/node_modules/bare-tty/package.json":{},"/node_modules/favela-peer/node_modules/bare-type/binding.js":{"#package":"/node_modules/favela-peer/node_modules/bare-type/package.json",".":"linked:libbare-type.1.1.1.so"},"/node_modules/favela-peer/node_modules/bare-type/index.js":{"#package":"/node_modules/favela-peer/node_modules/bare-type/package.json","./binding":"/node_modules/favela-peer/node_modules/bare-type/binding.js"},"/node_modules/favela-peer/node_modules/bare-type/package.json":{},"/node_modules/favela-peer/node_modules/bare-url/binding.js":{"#package":"/node_modules/favela-peer/node_modules/bare-url/package.json",".":"linked:libbare-url.2.5.4.so"},"/node_modules/favela-peer/node_modules/bare-url/index.js":{"#package":"/node_modules/favela-peer/node_modules/bare-url/package.json","./binding":"/node_modules/favela-peer/node_modules/bare-url/binding.js","./lib/errors":"/node_modules/favela-peer/node_modules/bare-url/lib/errors.js","./lib/url-search-params":"/node_modules/favela-peer/node_modules/bare-url/lib/url-search-params.js","bare-path":"/node_modules/favela-peer/node_modules/bare-path/index.js"},"/node_modules/favela-peer/node_modules/bare-url/lib/errors.js":{"#package":"/node_modules/favela-peer/node_modules/bare-url/package.json"},"/node_modules/favela-peer/node_modules/bare-url/lib/url-search-params.js":{"#package":"/node_modules/favela-peer/node_modules/bare-url/package.json"},"/node_modules/favela-peer/node_modules/bare-url/package.json":{},"/node_modules/favela-peer/node_modules/big-sparse-array/index.js":{"#package":"/node_modules/favela-peer/node_modules/big-sparse-array/package.json"},"/node_modules/favela-peer/node_modules/big-sparse-array/package.json":{},"/node_modules/favela-peer/node_modules/bip39-mnemonic/index.js":{"#package":"/node_modules/favela-peer/node_modules/bip39-mnemonic/package.json","./wordlist":"/node_modules/favela-peer/node_modules/bip39-mnemonic/wordlist/index.js","b4a":"/node_modules/favela-peer/node_modules/b4a/index.js","nanoassert":"/node_modules/favela-peer/node_modules/nanoassert/index.js","sodium-universal":"/node_modules/favela-peer/node_modules/sodium-universal/index.js"},"/node_modules/favela-peer/node_modules/bip39-mnemonic/package.json":{},"/node_modules/favela-peer/node_modules/bip39-mnemonic/wordlist/chinese_simplified.json":{"#package":"/node_modules/favela-peer/node_modules/bip39-mnemonic/package.json"},"/node_modules/favela-peer/node_modules/bip39-mnemonic/wordlist/chinese_traditional.json":{"#package":"/node_modules/favela-peer/node_modules/bip39-mnemonic/package.json"},"/node_modules/favela-peer/node_modules/bip39-mnemonic/wordlist/czech.json":{"#package":"/node_modules/favela-peer/node_modules/bip39-mnemonic/package.json"},"/node_modules/favela-peer/node_modules/bip39-mnemonic/wordlist/english.json":{"#package":"/node_modules/favela-peer/node_modules/bip39-mnemonic/package.json"},"/node_modules/favela-peer/node_modules/bip39-mnemonic/wordlist/french.json":{"#package":"/node_modules/favela-peer/node_modules/bip39-mnemonic/package.json"},"/node_modules/favela-peer/node_modules/bip39-mnemonic/wordlist/index.js":{"#package":"/node_modules/favela-peer/node_modules/bip39-mnemonic/package.json","./chinese_simplified.json":"/node_modules/favela-peer/node_modules/bip39-mnemonic/wordlist/chinese_simplified.json","./chinese_traditional.json":"/node_modules/favela-peer/node_modules/bip39-mnemonic/wordlist/chinese_traditional.json","./czech.json":"/node_modules/favela-peer/node_modules/bip39-mnemonic/wordlist/czech.json","./english.json":"/node_modules/favela-peer/node_modules/bip39-mnemonic/wordlist/english.json","./french.json":"/node_modules/favela-peer/node_modules/bip39-mnemonic/wordlist/french.json","./italian.json":"/node_modules/favela-peer/node_modules/bip39-mnemonic/wordlist/italian.json","./japanese.json":"/node_modules/favela-peer/node_modules/bip39-mnemonic/wordlist/japanese.json","./korean.json":"/node_modules/favela-peer/node_modules/bip39-mnemonic/wordlist/korean.json","./lookup.json":"/node_modules/favela-peer/node_modules/bip39-mnemonic/wordlist/lookup.json","./portuguese.json":"/node_modules/favela-peer/node_modules/bip39-mnemonic/wordlist/portuguese.json","./russian.json":"/node_modules/favela-peer/node_modules/bip39-mnemonic/wordlist/russian.json","./spanish.json":"/node_modules/favela-peer/node_modules/bip39-mnemonic/wordlist/spanish.json","./turkish.json":"/node_modules/favela-peer/node_modules/bip39-mnemonic/wordlist/turkish.json"},"/node_modules/favela-peer/node_modules/bip39-mnemonic/wordlist/italian.json":{"#package":"/node_modules/favela-peer/node_modules/bip39-mnemonic/package.json"},"/node_modules/favela-peer/node_modules/bip39-mnemonic/wordlist/japanese.json":{"#package":"/node_modules/favela-peer/node_modules/bip39-mnemonic/package.json"},"/node_modules/favela-peer/node_modules/bip39-mnemonic/wordlist/korean.json":{"#package":"/node_modules/favela-peer/node_modules/bip39-mnemonic/package.json"},"/node_modules/favela-peer/node_modules/bip39-mnemonic/wordlist/lookup.json":{"#package":"/node_modules/favela-peer/node_modules/bip39-mnemonic/package.json"},"/node_modules/favela-peer/node_modules/bip39-mnemonic/wordlist/portuguese.json":{"#package":"/node_modules/favela-peer/node_modules/bip39-mnemonic/package.json"},"/node_modules/favela-peer/node_modules/bip39-mnemonic/wordlist/russian.json":{"#package":"/node_modules/favela-peer/node_modules/bip39-mnemonic/package.json"},"/node_modules/favela-peer/node_modules/bip39-mnemonic/wordlist/spanish.json":{"#package":"/node_modules/favela-peer/node_modules/bip39-mnemonic/package.json"},"/node_modules/favela-peer/node_modules/bip39-mnemonic/wordlist/turkish.json":{"#package":"/node_modules/favela-peer/node_modules/bip39-mnemonic/package.json"},"/node_modules/favela-peer/node_modules/bits-to-bytes/index.js":{"#package":"/node_modules/favela-peer/node_modules/bits-to-bytes/package.json","b4a":"/node_modules/favela-peer/node_modules/b4a/index.js"},"/node_modules/favela-peer/node_modules/bits-to-bytes/package.json":{},"/node_modules/favela-peer/node_modules/blind-pairing-core/index.js":{"#package":"/node_modules/favela-peer/node_modules/blind-pairing-core/package.json","./lib/errors":"/node_modules/favela-peer/node_modules/blind-pairing-core/lib/errors.js","./lib/messages":"/node_modules/favela-peer/node_modules/blind-pairing-core/lib/messages.js","b4a":"/node_modules/favela-peer/node_modules/b4a/index.js","compact-encoding":"/node_modules/favela-peer/node_modules/compact-encoding/index.js","events":"/node_modules/favela-peer/node_modules/bare-events/index.js","hypercore-crypto":"/node_modules/favela-peer/node_modules/hypercore-crypto/index.js","sodium-universal":"/node_modules/favela-peer/node_modules/sodium-universal/index.js"},"/node_modules/favela-peer/node_modules/blind-pairing-core/lib/errors.js":{"#package":"/node_modules/favela-peer/node_modules/blind-pairing-core/package.json"},"/node_modules/favela-peer/node_modules/blind-pairing-core/lib/messages.js":{"#package":"/node_modules/favela-peer/node_modules/blind-pairing-core/package.json","bogon":"/node_modules/favela-peer/node_modules/bogon/index.js","compact-encoding":"/node_modules/favela-peer/node_modules/compact-encoding/index.js"},"/node_modules/favela-peer/node_modules/blind-pairing-core/package.json":{},"/node_modules/favela-peer/node_modules/blind-pairing/index.js":{"#package":"/node_modules/favela-peer/node_modules/blind-pairing/package.json","b4a":"/node_modules/favela-peer/node_modules/b4a/index.js","blind-pairing-core":"/node_modules/favela-peer/node_modules/blind-pairing-core/index.js","compact-encoding":"/node_modules/favela-peer/node_modules/compact-encoding/index.js","debounceify":"/node_modules/favela-peer/node_modules/debounceify/index.js","hypercore-crypto":"/node_modules/favela-peer/node_modules/hypercore-crypto/index.js","is-options":"/node_modules/favela-peer/node_modules/is-options/index.js","protomux":"/node_modules/favela-peer/node_modules/protomux/index.js","ready-resource":"/node_modules/favela-peer/node_modules/ready-resource/index.js","safety-catch":"/node_modules/favela-peer/node_modules/safety-catch/index.js","xache":"/node_modules/favela-peer/node_modules/xache/index.js"},"/node_modules/favela-peer/node_modules/blind-pairing/package.json":{},"/node_modules/favela-peer/node_modules/blind-relay/index.js":{"#package":"/node_modules/favela-peer/node_modules/blind-relay/package.json","./lib/errors":"/node_modules/favela-peer/node_modules/blind-relay/lib/errors.js","b4a":"/node_modules/favela-peer/node_modules/b4a/index.js","bits-to-bytes":"/node_modules/favela-peer/node_modules/bits-to-bytes/index.js","compact-encoding":"/node_modules/favela-peer/node_modules/compact-encoding/index.js","compact-encoding-bitfield":"/node_modules/favela-peer/node_modules/compact-encoding-bitfield/index.js","events":"/node_modules/favela-peer/node_modules/bare-events/index.js","protomux":"/node_modules/favela-peer/node_modules/protomux/index.js","sodium-universal":"/node_modules/favela-peer/node_modules/sodium-universal/index.js","streamx":"/node_modules/favela-peer/node_modules/streamx/index.js"},"/node_modules/favela-peer/node_modules/blind-relay/lib/errors.js":{"#package":"/node_modules/favela-peer/node_modules/blind-relay/package.json"},"/node_modules/favela-peer/node_modules/blind-relay/package.json":{},"/node_modules/favela-peer/node_modules/bogon/index.js":{"#package":"/node_modules/favela-peer/node_modules/bogon/package.json","b4a":"/node_modules/favela-peer/node_modules/b4a/index.js","compact-encoding":"/node_modules/favela-peer/node_modules/compact-encoding/index.js"},"/node_modules/favela-peer/node_modules/bogon/package.json":{},"/node_modules/favela-peer/node_modules/compact-encoding-bitfield/index.js":{"#package":"/node_modules/favela-peer/node_modules/compact-encoding-bitfield/package.json","compact-encoding":"/node_modules/favela-peer/node_modules/compact-encoding/index.js"},"/node_modules/favela-peer/node_modules/compact-encoding-bitfield/package.json":{},"/node_modules/favela-peer/node_modules/compact-encoding/endian.js":{"#package":"/node_modules/favela-peer/node_modules/compact-encoding/package.json"},"/node_modules/favela-peer/node_modules/compact-encoding/index.js":{"#package":"/node_modules/favela-peer/node_modules/compact-encoding/package.json","./endian":"/node_modules/favela-peer/node_modules/compact-encoding/endian.js","./lexint":"/node_modules/favela-peer/node_modules/compact-encoding/lexint.js","./raw":"/node_modules/favela-peer/node_modules/compact-encoding/raw.js","b4a":"/node_modules/favela-peer/node_modules/b4a/index.js"},"/node_modules/favela-peer/node_modules/compact-encoding/lexint.js":{"#package":"/node_modules/favela-peer/node_modules/compact-encoding/package.json"},"/node_modules/favela-peer/node_modules/compact-encoding/package.json":{},"/node_modules/favela-peer/node_modules/compact-encoding/raw.js":{"#package":"/node_modules/favela-peer/node_modules/compact-encoding/package.json","./endian":"/node_modules/favela-peer/node_modules/compact-encoding/endian.js","b4a":"/node_modules/favela-peer/node_modules/b4a/index.js"},"/node_modules/favela-peer/node_modules/core-coupler/index.js":{"#package":"/node_modules/favela-peer/node_modules/core-coupler/package.json","safety-catch":"/node_modules/favela-peer/node_modules/safety-catch/index.js"},"/node_modules/favela-peer/node_modules/core-coupler/package.json":{},"/node_modules/favela-peer/node_modules/corestore/index.js":{"#package":"/node_modules/favela-peer/node_modules/corestore/package.json","./lib/audit.js":"/node_modules/favela-peer/node_modules/corestore/lib/audit.js","./lib/notify.js":"/node_modules/favela-peer/node_modules/corestore/lib/notify.js","b4a":"/node_modules/favela-peer/node_modules/b4a/index.js","hypercore":"/node_modules/favela-peer/node_modules/hypercore/index.js","hypercore-crypto":"/node_modules/favela-peer/node_modules/hypercore-crypto/index.js","hypercore-errors":"/node_modules/favela-peer/node_modules/hypercore-errors/index.js","hypercore-id-encoding":"/node_modules/favela-peer/node_modules/hypercore-id-encoding/index.js","ready-resource":"/node_modules/favela-peer/node_modules/ready-resource/index.js","sodium-universal":"/node_modules/favela-peer/node_modules/sodium-universal/index.js","which-runtime":"/node_modules/favela-peer/node_modules/which-runtime/index.js"},"/node_modules/favela-peer/node_modules/corestore/lib/audit.js":{"#package":"/node_modules/favela-peer/node_modules/corestore/package.json"},"/node_modules/favela-peer/node_modules/corestore/lib/notify.js":{"#package":"/node_modules/favela-peer/node_modules/corestore/package.json","events":"/node_modules/favela-peer/node_modules/bare-events/index.js","streamx":"/node_modules/favela-peer/node_modules/streamx/index.js"},"/node_modules/favela-peer/node_modules/corestore/package.json":{},"/node_modules/favela-peer/node_modules/debounceify/index.js":{"#package":"/node_modules/favela-peer/node_modules/debounceify/package.json"},"/node_modules/favela-peer/node_modules/debounceify/package.json":{},"/node_modules/favela-peer/node_modules/device-file/index.js":{"#package":"/node_modules/favela-peer/node_modules/device-file/package.json","b4a":"/node_modules/favela-peer/node_modules/b4a/index.js","fd-lock":"/node_modules/favela-peer/node_modules/fd-lock/index.js","fs":"/node_modules/favela-peer/node_modules/bare-fs/index.js","fs-native-extensions":"/node_modules/favela-peer/node_modules/fs-native-extensions/index.js","path":"/node_modules/favela-peer/node_modules/bare-path/index.js","ready-resource":"/node_modules/favela-peer/node_modules/ready-resource/index.js"},"/node_modules/favela-peer/node_modules/device-file/package.json":{},"/node_modules/favela-peer/node_modules/dht-rpc/index.js":{"#package":"/node_modules/favela-peer/node_modules/dht-rpc/package.json","./lib/commands":"/node_modules/favela-peer/node_modules/dht-rpc/lib/commands.js","./lib/errors":"/node_modules/favela-peer/node_modules/dht-rpc/lib/errors.js","./lib/health":"/node_modules/favela-peer/node_modules/dht-rpc/lib/health.js","./lib/io":"/node_modules/favela-peer/node_modules/dht-rpc/lib/io.js","./lib/peer":"/node_modules/favela-peer/node_modules/dht-rpc/lib/peer.js","./lib/query":"/node_modules/favela-peer/node_modules/dht-rpc/lib/query.js","./lib/session":"/node_modules/favela-peer/node_modules/dht-rpc/lib/session.js","b4a":"/node_modules/favela-peer/node_modules/b4a/index.js","compact-encoding":"/node_modules/favela-peer/node_modules/compact-encoding/index.js","events":"/node_modules/favela-peer/node_modules/bare-events/index.js","kademlia-routing-table":"/node_modules/favela-peer/node_modules/kademlia-routing-table/index.js","nat-sampler":"/node_modules/favela-peer/node_modules/nat-sampler/index.js","sodium-universal":"/node_modules/favela-peer/node_modules/sodium-universal/index.js","time-ordered-set":"/node_modules/favela-peer/node_modules/time-ordered-set/index.js","udx-native":"/node_modules/favela-peer/node_modules/udx-native/lib/udx.js"},"/node_modules/favela-peer/node_modules/dht-rpc/lib/commands.js":{"#package":"/node_modules/favela-peer/node_modules/dht-rpc/package.json"},"/node_modules/favela-peer/node_modules/dht-rpc/lib/errors.js":{"#package":"/node_modules/favela-peer/node_modules/dht-rpc/package.json"},"/node_modules/favela-peer/node_modules/dht-rpc/lib/health.js":{"#package":"/node_modules/favela-peer/node_modules/dht-rpc/package.json"},"/node_modules/favela-peer/node_modules/dht-rpc/lib/io.js":{"#package":"/node_modules/favela-peer/node_modules/dht-rpc/package.json","./errors":"/node_modules/favela-peer/node_modules/dht-rpc/lib/errors.js","./peer":"/node_modules/favela-peer/node_modules/dht-rpc/lib/peer.js","adaptive-timeout":"/node_modules/favela-peer/node_modules/adaptive-timeout/index.js","b4a":"/node_modules/favela-peer/node_modules/b4a/index.js","compact-encoding":"/node_modules/favela-peer/node_modules/compact-encoding/index.js","fast-fifo":"/node_modules/favela-peer/node_modules/fast-fifo/index.js","sodium-universal":"/node_modules/favela-peer/node_modules/sodium-universal/index.js"},"/node_modules/favela-peer/node_modules/dht-rpc/lib/peer.js":{"#package":"/node_modules/favela-peer/node_modules/dht-rpc/package.json","b4a":"/node_modules/favela-peer/node_modules/b4a/index.js","compact-encoding":"/node_modules/favela-peer/node_modules/compact-encoding/index.js","sodium-universal":"/node_modules/favela-peer/node_modules/sodium-universal/index.js"},"/node_modules/favela-peer/node_modules/dht-rpc/lib/query.js":{"#package":"/node_modules/favela-peer/node_modules/dht-rpc/package.json","./commands":"/node_modules/favela-peer/node_modules/dht-rpc/lib/commands.js","./peer":"/node_modules/favela-peer/node_modules/dht-rpc/lib/peer.js","b4a":"/node_modules/favela-peer/node_modules/b4a/index.js","streamx":"/node_modules/favela-peer/node_modules/streamx/index.js"},"/node_modules/favela-peer/node_modules/dht-rpc/lib/session.js":{"#package":"/node_modules/favela-peer/node_modules/dht-rpc/package.json"},"/node_modules/favela-peer/node_modules/dht-rpc/package.json":{},"/node_modules/favela-peer/node_modules/events-universal/bare.js":{"#package":"/node_modules/favela-peer/node_modules/events-universal/package.json","bare-events":"/node_modules/favela-peer/node_modules/bare-events/index.js"},"/node_modules/favela-peer/node_modules/events-universal/package.json":{},"/node_modules/favela-peer/node_modules/fast-fifo/fixed-size.js":{"#package":"/node_modules/favela-peer/node_modules/fast-fifo/package.json"},"/node_modules/favela-peer/node_modules/fast-fifo/index.js":{"#package":"/node_modules/favela-peer/node_modules/fast-fifo/package.json","./fixed-size":"/node_modules/favela-peer/node_modules/fast-fifo/fixed-size.js"},"/node_modules/favela-peer/node_modules/fast-fifo/package.json":{},"/node_modules/favela-peer/node_modules/favela-identity/lib/attestation.js":{"#package":"/node_modules/favela-peer/node_modules/favela-identity/package.json","b4a":"/node_modules/favela-peer/node_modules/favela-identity/node_modules/b4a/index.js","compact-encoding":"/node_modules/favela-peer/node_modules/favela-identity/node_modules/compact-encoding/index.js","keet-identity-key":"/node_modules/favela-peer/node_modules/favela-identity/node_modules/keet-identity-key/index.js","keet-identity-key/lib/encoding":"/node_modules/favela-peer/node_modules/favela-identity/node_modules/keet-identity-key/lib/encoding.js"},"/node_modules/favela-peer/node_modules/favela-identity/node_modules/b4a/index.js":{"#package":"/node_modules/favela-peer/node_modules/favela-identity/node_modules/b4a/package.json"},"/node_modules/favela-peer/node_modules/favela-identity/node_modules/b4a/package.json":{},"/node_modules/favela-peer/node_modules/favela-identity/node_modules/compact-encoding/endian.js":{"#package":"/node_modules/favela-peer/node_modules/favela-identity/node_modules/compact-encoding/package.json"},"/node_modules/favela-peer/node_modules/favela-identity/node_modules/compact-encoding/index.js":{"#package":"/node_modules/favela-peer/node_modules/favela-identity/node_modules/compact-encoding/package.json","./endian":"/node_modules/favela-peer/node_modules/favela-identity/node_modules/compact-encoding/endian.js","./lexint":"/node_modules/favela-peer/node_modules/favela-identity/node_modules/compact-encoding/lexint.js","./raw":"/node_modules/favela-peer/node_modules/favela-identity/node_modules/compact-encoding/raw.js","b4a":"/node_modules/favela-peer/node_modules/favela-identity/node_modules/b4a/index.js"},"/node_modules/favela-peer/node_modules/favela-identity/node_modules/compact-encoding/lexint.js":{"#package":"/node_modules/favela-peer/node_modules/favela-identity/node_modules/compact-encoding/package.json"},"/node_modules/favela-peer/node_modules/favela-identity/node_modules/compact-encoding/package.json":{},"/node_modules/favela-peer/node_modules/favela-identity/node_modules/compact-encoding/raw.js":{"#package":"/node_modules/favela-peer/node_modules/favela-identity/node_modules/compact-encoding/package.json","./endian":"/node_modules/favela-peer/node_modules/favela-identity/node_modules/compact-encoding/endian.js","b4a":"/node_modules/favela-peer/node_modules/favela-identity/node_modules/b4a/index.js"},"/node_modules/favela-peer/node_modules/favela-identity/node_modules/keet-identity-key/index.js":{"#package":"/node_modules/favela-peer/node_modules/favela-identity/node_modules/keet-identity-key/package.json","./lib/crypto":"/node_modules/favela-peer/node_modules/favela-identity/node_modules/keet-identity-key/lib/crypto.js","./lib/encoding":"/node_modules/favela-peer/node_modules/favela-identity/node_modules/keet-identity-key/lib/encoding.js","./lib/keychain":"/node_modules/favela-peer/node_modules/favela-identity/node_modules/keet-identity-key/lib/keychain.js","b4a":"/node_modules/favela-peer/node_modules/favela-identity/node_modules/b4a/index.js","compact-encoding":"/node_modules/favela-peer/node_modules/favela-identity/node_modules/compact-encoding/index.js"},"/node_modules/favela-peer/node_modules/favela-identity/node_modules/keet-identity-key/lib/crypto.js":{"#package":"/node_modules/favela-peer/node_modules/favela-identity/node_modules/keet-identity-key/package.json","b4a":"/node_modules/favela-peer/node_modules/favela-identity/node_modules/b4a/index.js","sodium-universal":"/node_modules/favela-peer/node_modules/sodium-universal/index.js"},"/node_modules/favela-peer/node_modules/favela-identity/node_modules/keet-identity-key/lib/encoding.js":{"#package":"/node_modules/favela-peer/node_modules/favela-identity/node_modules/keet-identity-key/package.json","compact-encoding":"/node_modules/favela-peer/node_modules/favela-identity/node_modules/compact-encoding/index.js"},"/node_modules/favela-peer/node_modules/favela-identity/node_modules/keet-identity-key/lib/keychain.js":{"#package":"/node_modules/favela-peer/node_modules/favela-identity/node_modules/keet-identity-key/package.json","./crypto":"/node_modules/favela-peer/node_modules/favela-identity/node_modules/keet-identity-key/lib/crypto.js","b4a":"/node_modules/favela-peer/node_modules/favela-identity/node_modules/b4a/index.js","bip39-mnemonic":"/node_modules/favela-peer/node_modules/bip39-mnemonic/index.js","compact-encoding":"/node_modules/favela-peer/node_modules/favela-identity/node_modules/compact-encoding/index.js","nanoassert":"/node_modules/favela-peer/node_modules/nanoassert/index.js","sodium-hmac":"/node_modules/favela-peer/node_modules/sodium-hmac/index.js","sodium-universal":"/node_modules/favela-peer/node_modules/sodium-universal/index.js"},"/node_modules/favela-peer/node_modules/favela-identity/node_modules/keet-identity-key/package.json":{},"/node_modules/favela-peer/node_modules/favela-identity/package.json":{},"/node_modules/favela-peer/node_modules/favela-markdown/lib/dialect.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/package.json","./uri.js":"/node_modules/favela-peer/node_modules/favela-markdown/lib/uri.js","mdast-util-directive":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-directive/index.js","mdast-util-gfm-footnote":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-gfm-footnote/index.js","mdast-util-gfm-strikethrough":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-gfm-strikethrough/index.js","mdast-util-gfm-table":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-gfm-table/index.js","micromark-extension-directive":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-extension-directive/index.js","micromark-extension-gfm-footnote":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-extension-gfm-footnote/index.js","micromark-extension-gfm-strikethrough":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-extension-gfm-strikethrough/index.js","micromark-extension-gfm-table":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-extension-gfm-table/index.js"},"/node_modules/favela-peer/node_modules/favela-markdown/lib/levels.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/package.json"},"/node_modules/favela-peer/node_modules/favela-markdown/lib/reach.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/package.json"},"/node_modules/favela-peer/node_modules/favela-markdown/lib/record.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/package.json"},"/node_modules/favela-peer/node_modules/favela-markdown/lib/render.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/package.json","./dialect.js":"/node_modules/favela-peer/node_modules/favela-markdown/lib/dialect.js","mdast-util-from-markdown":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-from-markdown/index.js"},"/node_modules/favela-peer/node_modules/favela-markdown/lib/strip.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/package.json","./uri.js":"/node_modules/favela-peer/node_modules/favela-markdown/lib/uri.js","mdast-util-directive":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-directive/index.js","mdast-util-from-markdown":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-from-markdown/index.js","mdast-util-gfm-footnote":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-gfm-footnote/index.js","mdast-util-gfm-strikethrough":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-gfm-strikethrough/index.js","mdast-util-gfm-table":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-gfm-table/index.js","micromark-extension-directive":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-extension-directive/index.js","micromark-extension-gfm-footnote":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-extension-gfm-footnote/index.js","micromark-extension-gfm-strikethrough":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-extension-gfm-strikethrough/index.js","micromark-extension-gfm-table":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-extension-gfm-table/index.js"},"/node_modules/favela-peer/node_modules/favela-markdown/lib/uri.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/package.json"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/ccount/index.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/ccount/package.json"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/ccount/package.json":{},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/character-entities-html4/index.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/character-entities-html4/package.json"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/character-entities-html4/package.json":{},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/character-entities-legacy/index.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/character-entities-legacy/package.json"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/character-entities-legacy/package.json":{},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/character-entities/index.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/character-entities/package.json"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/character-entities/package.json":{},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/character-reference-invalid/index.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/character-reference-invalid/package.json"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/character-reference-invalid/package.json":{},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/decode-named-character-reference/index.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/decode-named-character-reference/package.json","character-entities":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/character-entities/index.js"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/decode-named-character-reference/package.json":{},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/devlop/lib/default.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/devlop/package.json"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/devlop/package.json":{},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/is-alphabetical/index.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/is-alphabetical/package.json"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/is-alphabetical/package.json":{},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/is-alphanumerical/index.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/is-alphanumerical/package.json","is-alphabetical":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/is-alphabetical/index.js","is-decimal":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/is-decimal/index.js"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/is-alphanumerical/package.json":{},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/is-decimal/index.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/is-decimal/package.json"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/is-decimal/package.json":{},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/is-hexadecimal/index.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/is-hexadecimal/package.json"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/is-hexadecimal/package.json":{},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/longest-streak/index.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/longest-streak/package.json"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/longest-streak/package.json":{},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/markdown-table/index.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/markdown-table/package.json"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/markdown-table/package.json":{},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-directive/index.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-directive/package.json","./lib/index.js":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-directive/lib/index.js"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-directive/lib/index.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-directive/package.json","ccount":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/ccount/index.js","devlop":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/devlop/lib/default.js","parse-entities":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/parse-entities/index.js","stringify-entities":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/stringify-entities/index.js","unist-util-visit-parents":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/unist-util-visit-parents/index.js"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-directive/package.json":{},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-from-markdown/index.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-from-markdown/package.json","./lib/index.js":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-from-markdown/lib/index.js"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-from-markdown/lib/index.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-from-markdown/package.json","decode-named-character-reference":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/decode-named-character-reference/index.js","mdast-util-to-string":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-string/index.js","micromark":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark/index.js","micromark-util-decode-numeric-character-reference":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-util-decode-numeric-character-reference/index.js","micromark-util-decode-string":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-util-decode-string/index.js","micromark-util-normalize-identifier":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-util-normalize-identifier/index.js","unist-util-stringify-position":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/unist-util-stringify-position/index.js"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-from-markdown/package.json":{},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-gfm-footnote/index.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-gfm-footnote/package.json","./lib/index.js":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-gfm-footnote/lib/index.js"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-gfm-footnote/lib/index.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-gfm-footnote/package.json","devlop":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/devlop/lib/default.js","micromark-util-normalize-identifier":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-util-normalize-identifier/index.js"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-gfm-footnote/package.json":{},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-gfm-strikethrough/index.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-gfm-strikethrough/package.json","./lib/index.js":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-gfm-strikethrough/lib/index.js"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-gfm-strikethrough/lib/index.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-gfm-strikethrough/package.json"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-gfm-strikethrough/package.json":{},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-gfm-table/index.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-gfm-table/package.json","./lib/index.js":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-gfm-table/lib/index.js"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-gfm-table/lib/index.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-gfm-table/package.json","devlop":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/devlop/lib/default.js","markdown-table":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/markdown-table/index.js","mdast-util-to-markdown":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/index.js"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-gfm-table/package.json":{},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-phrasing/index.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-phrasing/package.json","./lib/index.js":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-phrasing/lib/index.js"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-phrasing/lib/index.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-phrasing/package.json","unist-util-is":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/unist-util-is/index.js"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-phrasing/package.json":{},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/index.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/package.json","./lib/handle/index.js":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/handle/index.js","./lib/index.js":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/index.js"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/configure.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/package.json"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/handle/blockquote.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/package.json"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/handle/break.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/package.json","../util/pattern-in-scope.js":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/util/pattern-in-scope.js"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/handle/code.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/package.json","../util/check-fence.js":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/util/check-fence.js","../util/format-code-as-indented.js":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/util/format-code-as-indented.js","longest-streak":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/longest-streak/index.js"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/handle/definition.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/package.json","../util/check-quote.js":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/util/check-quote.js"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/handle/emphasis.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/package.json","../util/check-emphasis.js":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/util/check-emphasis.js","../util/encode-character-reference.js":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/util/encode-character-reference.js","../util/encode-info.js":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/util/encode-info.js"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/handle/heading.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/package.json","../util/encode-character-reference.js":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/util/encode-character-reference.js","../util/format-heading-as-setext.js":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/util/format-heading-as-setext.js"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/handle/html.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/package.json"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/handle/image-reference.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/package.json"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/handle/image.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/package.json","../util/check-quote.js":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/util/check-quote.js"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/handle/index.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/package.json","./blockquote.js":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/handle/blockquote.js","./break.js":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/handle/break.js","./code.js":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/handle/code.js","./definition.js":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/handle/definition.js","./emphasis.js":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/handle/emphasis.js","./heading.js":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/handle/heading.js","./html.js":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/handle/html.js","./image-reference.js":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/handle/image-reference.js","./image.js":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/handle/image.js","./inline-code.js":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/handle/inline-code.js","./link-reference.js":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/handle/link-reference.js","./link.js":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/handle/link.js","./list-item.js":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/handle/list-item.js","./list.js":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/handle/list.js","./paragraph.js":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/handle/paragraph.js","./root.js":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/handle/root.js","./strong.js":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/handle/strong.js","./text.js":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/handle/text.js","./thematic-break.js":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/handle/thematic-break.js"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/handle/inline-code.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/package.json"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/handle/link-reference.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/package.json"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/handle/link.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/package.json","../util/check-quote.js":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/util/check-quote.js","../util/format-link-as-autolink.js":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/util/format-link-as-autolink.js"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/handle/list-item.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/package.json","../util/check-bullet.js":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/util/check-bullet.js","../util/check-list-item-indent.js":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/util/check-list-item-indent.js"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/handle/list.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/package.json","../util/check-bullet-ordered.js":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/util/check-bullet-ordered.js","../util/check-bullet-other.js":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/util/check-bullet-other.js","../util/check-bullet.js":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/util/check-bullet.js","../util/check-rule.js":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/util/check-rule.js"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/handle/paragraph.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/package.json"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/handle/root.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/package.json","mdast-util-phrasing":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-phrasing/index.js"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/handle/strong.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/package.json","../util/check-strong.js":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/util/check-strong.js","../util/encode-character-reference.js":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/util/encode-character-reference.js","../util/encode-info.js":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/util/encode-info.js"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/handle/text.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/package.json"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/handle/thematic-break.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/package.json","../util/check-rule-repetition.js":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/util/check-rule-repetition.js","../util/check-rule.js":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/util/check-rule.js"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/index.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/package.json","./configure.js":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/configure.js","./handle/index.js":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/handle/index.js","./join.js":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/join.js","./unsafe.js":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/unsafe.js","./util/association.js":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/util/association.js","./util/compile-pattern.js":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/util/compile-pattern.js","./util/container-flow.js":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/util/container-flow.js","./util/container-phrasing.js":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/util/container-phrasing.js","./util/indent-lines.js":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/util/indent-lines.js","./util/safe.js":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/util/safe.js","./util/track.js":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/util/track.js","zwitch":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/zwitch/index.js"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/join.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/package.json","./util/format-code-as-indented.js":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/util/format-code-as-indented.js","./util/format-heading-as-setext.js":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/util/format-heading-as-setext.js"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/unsafe.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/package.json"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/util/association.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/package.json","micromark-util-decode-string":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-util-decode-string/index.js"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/util/check-bullet-ordered.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/package.json"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/util/check-bullet-other.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/package.json","./check-bullet.js":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/util/check-bullet.js"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/util/check-bullet.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/package.json"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/util/check-emphasis.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/package.json"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/util/check-fence.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/package.json"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/util/check-list-item-indent.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/package.json"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/util/check-quote.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/package.json"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/util/check-rule-repetition.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/package.json"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/util/check-rule.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/package.json"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/util/check-strong.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/package.json"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/util/compile-pattern.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/package.json"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/util/container-flow.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/package.json"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/util/container-phrasing.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/package.json","./encode-character-reference.js":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/util/encode-character-reference.js"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/util/encode-character-reference.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/package.json"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/util/encode-info.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/package.json","micromark-util-classify-character":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-util-classify-character/index.js"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/util/format-code-as-indented.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/package.json"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/util/format-heading-as-setext.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/package.json","mdast-util-to-string":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-string/index.js","unist-util-visit":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/unist-util-visit/index.js"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/util/format-link-as-autolink.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/package.json","mdast-util-to-string":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-string/index.js"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/util/indent-lines.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/package.json"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/util/pattern-in-scope.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/package.json"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/util/safe.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/package.json","./encode-character-reference.js":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/util/encode-character-reference.js","./pattern-in-scope.js":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/util/pattern-in-scope.js"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/util/track.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/package.json"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/package.json":{},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-string/index.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-string/package.json","./lib/index.js":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-string/lib/index.js"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-string/lib/index.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-string/package.json"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-string/package.json":{},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-core-commonmark/index.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-core-commonmark/package.json","./lib/attention.js":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-core-commonmark/lib/attention.js","./lib/autolink.js":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-core-commonmark/lib/autolink.js","./lib/blank-line.js":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-core-commonmark/lib/blank-line.js","./lib/block-quote.js":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-core-commonmark/lib/block-quote.js","./lib/character-escape.js":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-core-commonmark/lib/character-escape.js","./lib/character-reference.js":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-core-commonmark/lib/character-reference.js","./lib/code-fenced.js":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-core-commonmark/lib/code-fenced.js","./lib/code-indented.js":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-core-commonmark/lib/code-indented.js","./lib/code-text.js":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-core-commonmark/lib/code-text.js","./lib/content.js":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-core-commonmark/lib/content.js","./lib/definition.js":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-core-commonmark/lib/definition.js","./lib/hard-break-escape.js":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-core-commonmark/lib/hard-break-escape.js","./lib/heading-atx.js":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-core-commonmark/lib/heading-atx.js","./lib/html-flow.js":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-core-commonmark/lib/html-flow.js","./lib/html-text.js":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-core-commonmark/lib/html-text.js","./lib/label-end.js":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-core-commonmark/lib/label-end.js","./lib/label-start-image.js":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-core-commonmark/lib/label-start-image.js","./lib/label-start-link.js":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-core-commonmark/lib/label-start-link.js","./lib/line-ending.js":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-core-commonmark/lib/line-ending.js","./lib/list.js":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-core-commonmark/lib/list.js","./lib/setext-underline.js":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-core-commonmark/lib/setext-underline.js","./lib/thematic-break.js":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-core-commonmark/lib/thematic-break.js"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-core-commonmark/lib/attention.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-core-commonmark/package.json","micromark-util-chunked":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-util-chunked/index.js","micromark-util-classify-character":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-util-classify-character/index.js","micromark-util-resolve-all":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-util-resolve-all/index.js"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-core-commonmark/lib/autolink.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-core-commonmark/package.json","micromark-util-character":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-util-character/index.js"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-core-commonmark/lib/blank-line.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-core-commonmark/package.json","micromark-factory-space":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-factory-space/index.js","micromark-util-character":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-util-character/index.js"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-core-commonmark/lib/block-quote.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-core-commonmark/package.json","micromark-factory-space":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-factory-space/index.js","micromark-util-character":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-util-character/index.js"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-core-commonmark/lib/character-escape.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-core-commonmark/package.json","micromark-util-character":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-util-character/index.js"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-core-commonmark/lib/character-reference.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-core-commonmark/package.json","decode-named-character-reference":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/decode-named-character-reference/index.js","micromark-util-character":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-util-character/index.js"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-core-commonmark/lib/code-fenced.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-core-commonmark/package.json","micromark-factory-space":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-factory-space/index.js","micromark-util-character":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-util-character/index.js"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-core-commonmark/lib/code-indented.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-core-commonmark/package.json","micromark-factory-space":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-factory-space/index.js","micromark-util-character":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-util-character/index.js"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-core-commonmark/lib/code-text.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-core-commonmark/package.json","micromark-util-character":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-util-character/index.js"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-core-commonmark/lib/content.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-core-commonmark/package.json","micromark-factory-space":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-factory-space/index.js","micromark-util-character":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-util-character/index.js","micromark-util-subtokenize":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-util-subtokenize/index.js"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-core-commonmark/lib/definition.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-core-commonmark/package.json","micromark-factory-destination":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-factory-destination/index.js","micromark-factory-label":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-factory-label/index.js","micromark-factory-space":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-factory-space/index.js","micromark-factory-title":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-factory-title/index.js","micromark-factory-whitespace":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-factory-whitespace/index.js","micromark-util-character":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-util-character/index.js","micromark-util-normalize-identifier":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-util-normalize-identifier/index.js"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-core-commonmark/lib/hard-break-escape.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-core-commonmark/package.json","micromark-util-character":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-util-character/index.js"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-core-commonmark/lib/heading-atx.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-core-commonmark/package.json","micromark-factory-space":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-factory-space/index.js","micromark-util-character":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-util-character/index.js","micromark-util-chunked":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-util-chunked/index.js"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-core-commonmark/lib/html-flow.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-core-commonmark/package.json","./blank-line.js":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-core-commonmark/lib/blank-line.js","micromark-util-character":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-util-character/index.js","micromark-util-html-tag-name":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-util-html-tag-name/index.js"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-core-commonmark/lib/html-text.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-core-commonmark/package.json","micromark-factory-space":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-factory-space/index.js","micromark-util-character":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-util-character/index.js"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-core-commonmark/lib/label-end.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-core-commonmark/package.json","micromark-factory-destination":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-factory-destination/index.js","micromark-factory-label":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-factory-label/index.js","micromark-factory-title":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-factory-title/index.js","micromark-factory-whitespace":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-factory-whitespace/index.js","micromark-util-character":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-util-character/index.js","micromark-util-chunked":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-util-chunked/index.js","micromark-util-normalize-identifier":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-util-normalize-identifier/index.js","micromark-util-resolve-all":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-util-resolve-all/index.js"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-core-commonmark/lib/label-start-image.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-core-commonmark/package.json","./label-end.js":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-core-commonmark/lib/label-end.js"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-core-commonmark/lib/label-start-link.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-core-commonmark/package.json","./label-end.js":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-core-commonmark/lib/label-end.js"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-core-commonmark/lib/line-ending.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-core-commonmark/package.json","micromark-factory-space":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-factory-space/index.js","micromark-util-character":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-util-character/index.js"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-core-commonmark/lib/list.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-core-commonmark/package.json","./blank-line.js":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-core-commonmark/lib/blank-line.js","./thematic-break.js":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-core-commonmark/lib/thematic-break.js","micromark-factory-space":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-factory-space/index.js","micromark-util-character":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-util-character/index.js"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-core-commonmark/lib/setext-underline.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-core-commonmark/package.json","micromark-factory-space":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-factory-space/index.js","micromark-util-character":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-util-character/index.js"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-core-commonmark/lib/thematic-break.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-core-commonmark/package.json","micromark-factory-space":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-factory-space/index.js","micromark-util-character":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-util-character/index.js"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-core-commonmark/package.json":{},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-extension-directive/index.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-extension-directive/package.json","./lib/html.js":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-extension-directive/lib/html.js","./lib/syntax.js":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-extension-directive/lib/syntax.js"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-extension-directive/lib/directive-container.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-extension-directive/package.json","./factory-attributes.js":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-extension-directive/lib/factory-attributes.js","./factory-label.js":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-extension-directive/lib/factory-label.js","./factory-name.js":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-extension-directive/lib/factory-name.js","micromark-factory-space":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-factory-space/index.js","micromark-util-character":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-util-character/index.js"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-extension-directive/lib/directive-leaf.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-extension-directive/package.json","./factory-attributes.js":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-extension-directive/lib/factory-attributes.js","./factory-label.js":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-extension-directive/lib/factory-label.js","./factory-name.js":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-extension-directive/lib/factory-name.js","micromark-factory-space":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-factory-space/index.js","micromark-util-character":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-util-character/index.js"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-extension-directive/lib/directive-text.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-extension-directive/package.json","./factory-attributes.js":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-extension-directive/lib/factory-attributes.js","./factory-label.js":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-extension-directive/lib/factory-label.js","./factory-name.js":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-extension-directive/lib/factory-name.js"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-extension-directive/lib/factory-attributes.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-extension-directive/package.json","micromark-factory-space":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-factory-space/index.js","micromark-factory-whitespace":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-factory-whitespace/index.js","micromark-util-character":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-util-character/index.js"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-extension-directive/lib/factory-label.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-extension-directive/package.json","micromark-util-character":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-util-character/index.js"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-extension-directive/lib/factory-name.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-extension-directive/package.json","micromark-util-character":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-util-character/index.js"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-extension-directive/lib/html.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-extension-directive/package.json","parse-entities":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/parse-entities/index.js"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-extension-directive/lib/syntax.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-extension-directive/package.json","./directive-container.js":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-extension-directive/lib/directive-container.js","./directive-leaf.js":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-extension-directive/lib/directive-leaf.js","./directive-text.js":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-extension-directive/lib/directive-text.js"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-extension-directive/package.json":{},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-extension-gfm-footnote/index.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-extension-gfm-footnote/package.json","./lib/html.js":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-extension-gfm-footnote/lib/html.js","./lib/syntax.js":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-extension-gfm-footnote/lib/syntax.js"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-extension-gfm-footnote/lib/html.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-extension-gfm-footnote/package.json","micromark-util-normalize-identifier":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-util-normalize-identifier/index.js","micromark-util-sanitize-uri":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-util-sanitize-uri/index.js"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-extension-gfm-footnote/lib/syntax.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-extension-gfm-footnote/package.json","micromark-core-commonmark":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-core-commonmark/index.js","micromark-factory-space":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-factory-space/index.js","micromark-util-character":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-util-character/index.js","micromark-util-normalize-identifier":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-util-normalize-identifier/index.js"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-extension-gfm-footnote/package.json":{},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-extension-gfm-strikethrough/index.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-extension-gfm-strikethrough/package.json","./lib/html.js":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-extension-gfm-strikethrough/lib/html.js","./lib/syntax.js":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-extension-gfm-strikethrough/lib/syntax.js"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-extension-gfm-strikethrough/lib/html.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-extension-gfm-strikethrough/package.json"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-extension-gfm-strikethrough/lib/syntax.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-extension-gfm-strikethrough/package.json","micromark-util-chunked":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-util-chunked/index.js","micromark-util-classify-character":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-util-classify-character/index.js","micromark-util-resolve-all":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-util-resolve-all/index.js"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-extension-gfm-strikethrough/package.json":{},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-extension-gfm-table/index.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-extension-gfm-table/package.json","./lib/html.js":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-extension-gfm-table/lib/html.js","./lib/syntax.js":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-extension-gfm-table/lib/syntax.js"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-extension-gfm-table/lib/edit-map.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-extension-gfm-table/package.json"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-extension-gfm-table/lib/html.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-extension-gfm-table/package.json"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-extension-gfm-table/lib/infer.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-extension-gfm-table/package.json"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-extension-gfm-table/lib/syntax.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-extension-gfm-table/package.json","./edit-map.js":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-extension-gfm-table/lib/edit-map.js","./infer.js":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-extension-gfm-table/lib/infer.js","micromark-factory-space":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-factory-space/index.js","micromark-util-character":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-util-character/index.js"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-extension-gfm-table/package.json":{},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-factory-destination/index.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-factory-destination/package.json","micromark-util-character":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-util-character/index.js"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-factory-destination/package.json":{},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-factory-label/index.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-factory-label/package.json","micromark-util-character":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-util-character/index.js"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-factory-label/package.json":{},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-factory-space/index.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-factory-space/package.json","micromark-util-character":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-util-character/index.js"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-factory-space/package.json":{},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-factory-title/index.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-factory-title/package.json","micromark-factory-space":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-factory-space/index.js","micromark-util-character":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-util-character/index.js"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-factory-title/package.json":{},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-factory-whitespace/index.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-factory-whitespace/package.json","micromark-factory-space":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-factory-space/index.js","micromark-util-character":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-util-character/index.js"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-factory-whitespace/package.json":{},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-util-character/index.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-util-character/package.json"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-util-character/package.json":{},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-util-chunked/index.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-util-chunked/package.json"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-util-chunked/package.json":{},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-util-classify-character/index.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-util-classify-character/package.json","micromark-util-character":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-util-character/index.js"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-util-classify-character/package.json":{},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-util-combine-extensions/index.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-util-combine-extensions/package.json","micromark-util-chunked":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-util-chunked/index.js"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-util-combine-extensions/package.json":{},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-util-decode-numeric-character-reference/index.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-util-decode-numeric-character-reference/package.json"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-util-decode-numeric-character-reference/package.json":{},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-util-decode-string/index.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-util-decode-string/package.json","decode-named-character-reference":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/decode-named-character-reference/index.js","micromark-util-decode-numeric-character-reference":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-util-decode-numeric-character-reference/index.js"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-util-decode-string/package.json":{},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-util-encode/index.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-util-encode/package.json"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-util-encode/package.json":{},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-util-html-tag-name/index.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-util-html-tag-name/package.json"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-util-html-tag-name/package.json":{},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-util-normalize-identifier/index.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-util-normalize-identifier/package.json"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-util-normalize-identifier/package.json":{},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-util-resolve-all/index.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-util-resolve-all/package.json"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-util-resolve-all/package.json":{},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-util-sanitize-uri/index.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-util-sanitize-uri/package.json","micromark-util-character":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-util-character/index.js","micromark-util-encode":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-util-encode/index.js"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-util-sanitize-uri/package.json":{},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-util-subtokenize/index.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-util-subtokenize/package.json","./lib/splice-buffer.js":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-util-subtokenize/lib/splice-buffer.js","micromark-util-chunked":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-util-chunked/index.js"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-util-subtokenize/lib/splice-buffer.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-util-subtokenize/package.json"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-util-subtokenize/package.json":{},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark/index.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark/package.json","./lib/compile.js":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark/lib/compile.js","./lib/parse.js":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark/lib/parse.js","./lib/postprocess.js":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark/lib/postprocess.js","./lib/preprocess.js":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark/lib/preprocess.js"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark/lib/compile.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark/package.json","decode-named-character-reference":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/decode-named-character-reference/index.js","micromark-util-chunked":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-util-chunked/index.js","micromark-util-combine-extensions":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-util-combine-extensions/index.js","micromark-util-decode-numeric-character-reference":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-util-decode-numeric-character-reference/index.js","micromark-util-encode":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-util-encode/index.js","micromark-util-normalize-identifier":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-util-normalize-identifier/index.js","micromark-util-sanitize-uri":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-util-sanitize-uri/index.js"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark/lib/constructs.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark/package.json","./initialize/text.js":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark/lib/initialize/text.js","micromark-core-commonmark":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-core-commonmark/index.js"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark/lib/create-tokenizer.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark/package.json","micromark-util-character":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-util-character/index.js","micromark-util-chunked":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-util-chunked/index.js","micromark-util-resolve-all":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-util-resolve-all/index.js"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark/lib/initialize/content.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark/package.json","micromark-factory-space":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-factory-space/index.js","micromark-util-character":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-util-character/index.js"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark/lib/initialize/document.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark/package.json","micromark-factory-space":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-factory-space/index.js","micromark-util-character":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-util-character/index.js","micromark-util-chunked":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-util-chunked/index.js"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark/lib/initialize/flow.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark/package.json","micromark-core-commonmark":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-core-commonmark/index.js","micromark-factory-space":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-factory-space/index.js","micromark-util-character":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-util-character/index.js"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark/lib/initialize/text.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark/package.json"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark/lib/parse.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark/package.json","./constructs.js":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark/lib/constructs.js","./create-tokenizer.js":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark/lib/create-tokenizer.js","./initialize/content.js":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark/lib/initialize/content.js","./initialize/document.js":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark/lib/initialize/document.js","./initialize/flow.js":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark/lib/initialize/flow.js","./initialize/text.js":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark/lib/initialize/text.js","micromark-util-combine-extensions":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-util-combine-extensions/index.js"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark/lib/postprocess.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark/package.json","micromark-util-subtokenize":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-util-subtokenize/index.js"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark/lib/preprocess.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark/package.json"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark/package.json":{},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/parse-entities/index.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/parse-entities/package.json","./lib/index.js":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/parse-entities/lib/index.js"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/parse-entities/lib/index.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/parse-entities/package.json","character-entities-legacy":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/character-entities-legacy/index.js","character-reference-invalid":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/character-reference-invalid/index.js","decode-named-character-reference":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/decode-named-character-reference/index.js","is-alphanumerical":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/is-alphanumerical/index.js","is-decimal":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/is-decimal/index.js","is-hexadecimal":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/is-hexadecimal/index.js"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/parse-entities/package.json":{},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/stringify-entities/index.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/stringify-entities/package.json","./lib/index.js":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/stringify-entities/lib/index.js"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/stringify-entities/lib/constant/dangerous.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/stringify-entities/package.json"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/stringify-entities/lib/core.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/stringify-entities/package.json"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/stringify-entities/lib/index.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/stringify-entities/package.json","./core.js":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/stringify-entities/lib/core.js","./util/format-basic.js":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/stringify-entities/lib/util/format-basic.js","./util/format-smart.js":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/stringify-entities/lib/util/format-smart.js"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/stringify-entities/lib/util/format-basic.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/stringify-entities/package.json"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/stringify-entities/lib/util/format-smart.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/stringify-entities/package.json","./to-decimal.js":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/stringify-entities/lib/util/to-decimal.js","./to-hexadecimal.js":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/stringify-entities/lib/util/to-hexadecimal.js","./to-named.js":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/stringify-entities/lib/util/to-named.js"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/stringify-entities/lib/util/to-decimal.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/stringify-entities/package.json"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/stringify-entities/lib/util/to-hexadecimal.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/stringify-entities/package.json"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/stringify-entities/lib/util/to-named.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/stringify-entities/package.json","../constant/dangerous.js":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/stringify-entities/lib/constant/dangerous.js","character-entities-html4":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/character-entities-html4/index.js","character-entities-legacy":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/character-entities-legacy/index.js"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/stringify-entities/package.json":{},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/unist-util-is/index.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/unist-util-is/package.json","./lib/index.js":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/unist-util-is/lib/index.js"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/unist-util-is/lib/index.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/unist-util-is/package.json"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/unist-util-is/package.json":{},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/unist-util-stringify-position/index.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/unist-util-stringify-position/package.json","./lib/index.js":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/unist-util-stringify-position/lib/index.js"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/unist-util-stringify-position/lib/index.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/unist-util-stringify-position/package.json"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/unist-util-stringify-position/package.json":{},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/unist-util-visit-parents/index.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/unist-util-visit-parents/package.json","./lib/index.js":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/unist-util-visit-parents/lib/index.js"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/unist-util-visit-parents/lib/color.node.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/unist-util-visit-parents/package.json"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/unist-util-visit-parents/lib/index.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/unist-util-visit-parents/package.json","unist-util-is":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/unist-util-is/index.js","unist-util-visit-parents/do-not-use-color":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/unist-util-visit-parents/lib/color.node.js"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/unist-util-visit-parents/package.json":{},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/unist-util-visit/index.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/unist-util-visit/package.json","./lib/index.js":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/unist-util-visit/lib/index.js"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/unist-util-visit/lib/index.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/unist-util-visit/package.json","unist-util-visit-parents":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/unist-util-visit-parents/index.js"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/unist-util-visit/package.json":{},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/zwitch/index.js":{"#package":"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/zwitch/package.json"},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/zwitch/package.json":{},"/node_modules/favela-peer/node_modules/favela-markdown/package.json":{},"/node_modules/favela-peer/node_modules/fd-lock/index.js":{"#package":"/node_modules/favela-peer/node_modules/fd-lock/package.json","fs":"/node_modules/favela-peer/node_modules/bare-fs/index.js","fs-native-extensions":"/node_modules/favela-peer/node_modules/fs-native-extensions/index.js","ready-resource":"/node_modules/favela-peer/node_modules/ready-resource/index.js","resource-on-exit":"/node_modules/favela-peer/node_modules/resource-on-exit/index.js"},"/node_modules/favela-peer/node_modules/fd-lock/package.json":{},"/node_modules/favela-peer/node_modules/flat-tree/index.js":{"#package":"/node_modules/favela-peer/node_modules/flat-tree/package.json"},"/node_modules/favela-peer/node_modules/flat-tree/package.json":{},"/node_modules/favela-peer/node_modules/fs-native-extensions/binding.js":{"#package":"/node_modules/favela-peer/node_modules/fs-native-extensions/package.json",".":"linked:libfs-native-extensions.1.5.1.so","require-addon":"/node_modules/favela-peer/node_modules/require-addon/lib/bare.js"},"/node_modules/favela-peer/node_modules/fs-native-extensions/index.js":{"#package":"/node_modules/favela-peer/node_modules/fs-native-extensions/package.json","./binding":"/node_modules/favela-peer/node_modules/fs-native-extensions/binding.js","which-runtime":"/node_modules/favela-peer/node_modules/which-runtime/index.js"},"/node_modules/favela-peer/node_modules/fs-native-extensions/package.json":{},"/node_modules/favela-peer/node_modules/hyper-identity2/index.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/package.json","./lib/adapt-host.js":"/node_modules/favela-peer/node_modules/hyper-identity2/lib/adapt-host.js","./lib/registry.js":"/node_modules/favela-peer/node_modules/hyper-identity2/lib/registry.js","./lib/view-shim.js":"/node_modules/favela-peer/node_modules/hyper-identity2/lib/view-shim.js","hyper-identity-core":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyper-identity-core/index.js"},"/node_modules/favela-peer/node_modules/hyper-identity2/lib/adapt-host.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/package.json"},"/node_modules/favela-peer/node_modules/hyper-identity2/lib/registry.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/package.json","./adapt-host.js":"/node_modules/favela-peer/node_modules/hyper-identity2/lib/adapt-host.js","./view-shim.js":"/node_modules/favela-peer/node_modules/hyper-identity2/lib/view-shim.js","autobee":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/autobee/index.js","hyper-identity-core/registry":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyper-identity-core/lib/registry.js"},"/node_modules/favela-peer/node_modules/hyper-identity2/lib/view-shim.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/package.json","b4a":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/b4a/index.js"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/@hyperswarm/secret-stream/index.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/@hyperswarm/secret-stream/package.json","./lib/bridge":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/@hyperswarm/secret-stream/lib/bridge.js","./lib/handshake":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/@hyperswarm/secret-stream/lib/handshake.js","b4a":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/b4a/index.js","hypercore-crypto":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore-crypto/index.js","sodium-secretstream":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/sodium-secretstream/index.js","sodium-universal":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/sodium-universal/index.js","streamx":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/streamx/index.js","timeout-refresh":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/timeout-refresh/index.js","unslab":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/unslab/index.js"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/@hyperswarm/secret-stream/lib/bridge.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/@hyperswarm/secret-stream/package.json","streamx":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/streamx/index.js"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/@hyperswarm/secret-stream/lib/handshake.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/@hyperswarm/secret-stream/package.json","b4a":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/b4a/index.js","noise-curve-ed":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/noise-curve-ed/index.js","noise-handshake":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/noise-handshake/noise.js","sodium-universal":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/sodium-universal/index.js"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/@hyperswarm/secret-stream/package.json":{},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/autobee-encryption/index.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/autobee-encryption/package.json","b4a":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/b4a/index.js","compact-encoding":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/autobee-encryption/node_modules/compact-encoding/index.js","hypercore-crypto":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore-crypto/index.js","hypercore/lib/default-encryption.js":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore/lib/default-encryption.js","sodium-universal":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/sodium-universal/index.js"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/autobee-encryption/node_modules/compact-encoding/endian.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/autobee-encryption/node_modules/compact-encoding/package.json"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/autobee-encryption/node_modules/compact-encoding/index.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/autobee-encryption/node_modules/compact-encoding/package.json","./endian":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/autobee-encryption/node_modules/compact-encoding/endian.js","./lexint":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/autobee-encryption/node_modules/compact-encoding/lexint.js","./raw":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/autobee-encryption/node_modules/compact-encoding/raw.js","b4a":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/b4a/index.js"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/autobee-encryption/node_modules/compact-encoding/lexint.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/autobee-encryption/node_modules/compact-encoding/package.json"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/autobee-encryption/node_modules/compact-encoding/package.json":{},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/autobee-encryption/node_modules/compact-encoding/raw.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/autobee-encryption/node_modules/compact-encoding/package.json","./endian":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/autobee-encryption/node_modules/compact-encoding/endian.js","b4a":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/b4a/index.js"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/autobee-encryption/package.json":{},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/autobee-wakeup/index.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/autobee-wakeup/package.json","b4a":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/b4a/index.js","core-coupler":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/core-coupler/index.js","protomux-wakeup":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/protomux-wakeup/index.js","ready-resource":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/ready-resource/index.js"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/autobee-wakeup/package.json":{},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/autobee/encoding/legacy.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/autobee/package.json","compact-encoding":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/compact-encoding/index.js"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/autobee/encoding/spec/autobee/index.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/autobee/package.json","../../legacy.js":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/autobee/encoding/legacy.js","hyperschema/runtime":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyperschema/runtime.cjs"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/autobee/index.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/autobee/package.json","./lib/apply-calls.js":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/autobee/lib/apply-calls.js","./lib/apply-view.js":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/autobee/lib/apply-view.js","./lib/asserts.js":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/autobee/lib/asserts.js","./lib/boot.js":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/autobee/lib/boot.js","./lib/constants.js":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/autobee/lib/constants.js","./lib/encoding.js":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/autobee/lib/encoding.js","./lib/fast-forward.js":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/autobee/lib/fast-forward.js","./lib/migrations.js":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/autobee/lib/migrations.js","./lib/system.js":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/autobee/lib/system.js","./lib/topo.js":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/autobee/lib/topo.js","./lib/trusted.js":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/autobee/lib/trusted.js","./lib/updates.js":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/autobee/lib/updates.js","./lib/witness.js":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/autobee/lib/witness.js","./lib/writers.js":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/autobee/lib/writers.js","autobee-encryption":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/autobee-encryption/index.js","autobee-wakeup":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/autobee-wakeup/index.js","b4a":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/b4a/index.js","compact-encoding":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/compact-encoding/index.js","hyperbee2":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyperbee2/index.js","hypercore":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore/index.js","hypercore-crypto":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore-crypto/index.js","hypercore-id-encoding":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore-id-encoding/index.js","ready-guard":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/ready-guard/index.js","ready-resource":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/ready-resource/index.js","resolve-reject-promise":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/resolve-reject-promise/index.js","safety-catch":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/safety-catch/index.js"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/autobee/lib/apply-calls.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/autobee/package.json","hypercore-id-encoding":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore-id-encoding/index.js"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/autobee/lib/apply-view.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/autobee/package.json"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/autobee/lib/asserts.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/autobee/package.json"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/autobee/lib/boot.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/autobee/package.json","./encoding.js":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/autobee/lib/encoding.js","./migrations.js":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/autobee/lib/migrations.js","b4a":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/b4a/index.js"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/autobee/lib/constants.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/autobee/package.json"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/autobee/lib/encoding.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/autobee/package.json","../encoding/spec/autobee":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/autobee/encoding/spec/autobee/index.js","./constants":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/autobee/lib/constants.js","autobee-encryption":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/autobee-encryption/index.js","b4a":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/b4a/index.js","compact-encoding":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/compact-encoding/index.js","hypercore-crypto":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore-crypto/index.js"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/autobee/lib/fast-forward.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/autobee/package.json","./constants.js":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/autobee/lib/constants.js","./migrations.js":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/autobee/lib/migrations.js","./system.js":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/autobee/lib/system.js","b4a":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/b4a/index.js","hypercore":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore/index.js","safety-catch":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/safety-catch/index.js"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/autobee/lib/migrations.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/autobee/package.json","../encoding/spec/autobee":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/autobee/encoding/spec/autobee/index.js","./asserts.js":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/autobee/lib/asserts.js","./constants.js":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/autobee/lib/constants.js","./encoding.js":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/autobee/lib/encoding.js","./fast-forward.js":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/autobee/lib/fast-forward.js","autobee-encryption":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/autobee-encryption/index.js","b4a":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/b4a/index.js","compact-encoding":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/compact-encoding/index.js","hyperbee2/lib/encoding.js":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyperbee2/lib/encoding.js","hypercore-crypto":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore-crypto/index.js"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/autobee/lib/promotions.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/autobee/package.json","./encoding.js":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/autobee/lib/encoding.js","b4a":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/b4a/index.js"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/autobee/lib/system.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/autobee/package.json","./constants":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/autobee/lib/constants.js","./encoding.js":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/autobee/lib/encoding.js","./promotions.js":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/autobee/lib/promotions.js","./topo.js":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/autobee/lib/topo.js","b4a":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/b4a/index.js","hyperbee2":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyperbee2/index.js"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/autobee/lib/topo.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/autobee/package.json","./asserts.js":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/autobee/lib/asserts.js","./constants.js":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/autobee/lib/constants.js","./encoding.js":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/autobee/lib/encoding.js","./writers.js":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/autobee/lib/writers.js","b4a":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/b4a/index.js"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/autobee/lib/triggers.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/autobee/package.json"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/autobee/lib/trusted.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/autobee/package.json","b4a":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/b4a/index.js"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/autobee/lib/updates.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/autobee/package.json"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/autobee/lib/witness.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/autobee/package.json","./encoding.js":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/autobee/lib/encoding.js","b4a":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/b4a/index.js"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/autobee/lib/writers.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/autobee/package.json","./constants.js":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/autobee/lib/constants.js","./encoding.js":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/autobee/lib/encoding.js","./triggers.js":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/autobee/lib/triggers.js","./witness.js":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/autobee/lib/witness.js","autobee-encryption":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/autobee-encryption/index.js","b4a":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/b4a/index.js"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/autobee/package.json":{},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/b4a/index.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/b4a/package.json"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/b4a/package.json":{},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/bare-ansi-escapes/index.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/bare-ansi-escapes/package.json"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/bare-ansi-escapes/package.json":{},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/bare-assert/index.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/bare-assert/package.json","bare-inspect":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/bare-inspect/index.js"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/bare-assert/package.json":{},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/bare-events/index.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/bare-events/package.json","./lib/errors":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/bare-events/lib/errors.js"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/bare-events/lib/errors.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/bare-events/package.json"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/bare-events/package.json":{},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/bare-fs/binding.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/bare-fs/package.json",".":"linked:libbare-fs.4.8.1.so"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/bare-fs/index.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/bare-fs/package.json","./binding":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/bare-fs/binding.js","./lib/constants":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/bare-fs/lib/constants.js","./lib/errors":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/bare-fs/lib/errors.js","./promises":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/bare-fs/promises.js","bare-events":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/bare-events/index.js","bare-path":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/bare-path/index.js","bare-stream":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/bare-stream/index.js","bare-url":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/bare-url/index.js","fast-fifo":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/fast-fifo/index.js"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/bare-fs/lib/constants.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/bare-fs/package.json","../binding":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/bare-fs/binding.js"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/bare-fs/lib/errors.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/bare-fs/package.json","../binding":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/bare-fs/binding.js"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/bare-fs/package.json":{},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/bare-fs/promises.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/bare-fs/package.json",".":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/bare-fs/index.js","bare-events":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/bare-events/index.js"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/bare-inspect/binding.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/bare-inspect/package.json",".":"linked:libbare-inspect.3.1.10.so"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/bare-inspect/index.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/bare-inspect/package.json","./binding":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/bare-inspect/binding.js","bare-ansi-escapes":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/bare-ansi-escapes/index.js","bare-type":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/bare-type/index.js"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/bare-inspect/package.json":{},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/bare-path/binding.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/bare-path/package.json",".":"linked:libbare-path.3.1.2.so"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/bare-path/index.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/bare-path/package.json","./lib/posix":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/bare-path/lib/posix.js","./lib/win32":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/bare-path/lib/win32.js"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/bare-path/lib/constants.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/bare-path/package.json"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/bare-path/lib/posix.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/bare-path/package.json","../binding":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/bare-path/binding.js","./constants":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/bare-path/lib/constants.js","./shared":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/bare-path/lib/shared.js","./win32":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/bare-path/lib/win32.js"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/bare-path/lib/shared.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/bare-path/package.json","./constants":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/bare-path/lib/constants.js"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/bare-path/lib/win32.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/bare-path/package.json","../binding":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/bare-path/binding.js","./constants":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/bare-path/lib/constants.js","./posix":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/bare-path/lib/posix.js","./shared":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/bare-path/lib/shared.js"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/bare-path/package.json":{},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/bare-stream/index.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/bare-stream/package.json","./web":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/bare-stream/web.js","b4a":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/b4a/index.js","streamx":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/streamx/index.js"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/bare-stream/package.json":{},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/bare-stream/web.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/bare-stream/package.json","streamx":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/streamx/index.js","teex":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/teex/index.js"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/bare-type/binding.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/bare-type/package.json",".":"linked:libbare-type.1.1.1.so"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/bare-type/index.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/bare-type/package.json","./binding":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/bare-type/binding.js"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/bare-type/package.json":{},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/bare-url/binding.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/bare-url/package.json",".":"linked:libbare-url.2.5.4.so"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/bare-url/index.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/bare-url/package.json","./binding":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/bare-url/binding.js","./lib/errors":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/bare-url/lib/errors.js","./lib/url-search-params":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/bare-url/lib/url-search-params.js","bare-path":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/bare-path/index.js"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/bare-url/lib/errors.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/bare-url/package.json"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/bare-url/lib/url-search-params.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/bare-url/package.json"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/bare-url/package.json":{},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/big-sparse-array/index.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/big-sparse-array/package.json"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/big-sparse-array/package.json":{},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/compact-encoding/endian.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/compact-encoding/package.json"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/compact-encoding/index.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/compact-encoding/package.json","./endian":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/compact-encoding/endian.js","./lexint":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/compact-encoding/lexint.js","./raw":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/compact-encoding/raw.js","b4a":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/b4a/index.js"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/compact-encoding/lexint.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/compact-encoding/package.json"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/compact-encoding/package.json":{},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/compact-encoding/raw.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/compact-encoding/package.json","./endian":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/compact-encoding/endian.js","b4a":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/b4a/index.js"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/core-coupler/index.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/core-coupler/package.json","safety-catch":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/safety-catch/index.js"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/core-coupler/package.json":{},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/device-file/index.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/device-file/package.json","b4a":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/b4a/index.js","fd-lock":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/fd-lock/index.js","fs":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/bare-fs/index.js","fs-native-extensions":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/fs-native-extensions/index.js","path":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/bare-path/index.js","ready-resource":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/ready-resource/index.js"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/device-file/package.json":{},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/events-universal/bare.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/events-universal/package.json","bare-events":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/bare-events/index.js"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/events-universal/package.json":{},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/fast-fifo/fixed-size.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/fast-fifo/package.json"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/fast-fifo/index.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/fast-fifo/package.json","./fixed-size":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/fast-fifo/fixed-size.js"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/fast-fifo/package.json":{},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/fd-lock/index.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/fd-lock/package.json","fs":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/bare-fs/index.js","fs-native-extensions":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/fs-native-extensions/index.js","ready-resource":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/ready-resource/index.js","resource-on-exit":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/resource-on-exit/index.js"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/fd-lock/package.json":{},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/flat-tree/index.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/flat-tree/package.json"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/flat-tree/package.json":{},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/fs-native-extensions/binding.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/fs-native-extensions/package.json",".":"linked:libfs-native-extensions.1.5.1.so","require-addon":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/require-addon/lib/bare.js"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/fs-native-extensions/index.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/fs-native-extensions/package.json","./binding":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/fs-native-extensions/binding.js","which-runtime":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/which-runtime/index.js"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/fs-native-extensions/package.json":{},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyper-identity-core/index.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyper-identity-core/package.json","./lib/apply.js":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyper-identity-core/lib/apply.js","./lib/operations.js":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyper-identity-core/lib/operations.js","./lib/registry.js":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyper-identity-core/lib/registry.js"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyper-identity-core/lib/apply.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyper-identity-core/package.json","./operations.js":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyper-identity-core/lib/operations.js","b4a":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyper-identity-core/node_modules/b4a/index.js"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyper-identity-core/lib/operations.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyper-identity-core/package.json","b4a":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyper-identity-core/node_modules/b4a/index.js"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyper-identity-core/lib/registry.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyper-identity-core/package.json","./apply.js":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyper-identity-core/lib/apply.js","./operations.js":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyper-identity-core/lib/operations.js","b4a":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyper-identity-core/node_modules/b4a/index.js","hypercore-crypto":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyper-identity-core/node_modules/hypercore-crypto/index.js"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyper-identity-core/node_modules/b4a/index.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyper-identity-core/node_modules/b4a/package.json"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyper-identity-core/node_modules/b4a/package.json":{},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyper-identity-core/node_modules/bare-ansi-escapes/index.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyper-identity-core/node_modules/bare-ansi-escapes/package.json"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyper-identity-core/node_modules/bare-ansi-escapes/package.json":{},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyper-identity-core/node_modules/bare-assert/index.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyper-identity-core/node_modules/bare-assert/package.json","bare-inspect":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyper-identity-core/node_modules/bare-inspect/index.js"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyper-identity-core/node_modules/bare-assert/package.json":{},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyper-identity-core/node_modules/bare-inspect/binding.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyper-identity-core/node_modules/bare-inspect/package.json",".":"linked:libbare-inspect.3.1.10.so"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyper-identity-core/node_modules/bare-inspect/index.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyper-identity-core/node_modules/bare-inspect/package.json","./binding":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyper-identity-core/node_modules/bare-inspect/binding.js","bare-ansi-escapes":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyper-identity-core/node_modules/bare-ansi-escapes/index.js","bare-type":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyper-identity-core/node_modules/bare-type/index.js"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyper-identity-core/node_modules/bare-inspect/package.json":{},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyper-identity-core/node_modules/bare-type/binding.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyper-identity-core/node_modules/bare-type/package.json",".":"linked:libbare-type.1.1.1.so"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyper-identity-core/node_modules/bare-type/index.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyper-identity-core/node_modules/bare-type/package.json","./binding":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyper-identity-core/node_modules/bare-type/binding.js"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyper-identity-core/node_modules/bare-type/package.json":{},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyper-identity-core/node_modules/compact-encoding/endian.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyper-identity-core/node_modules/compact-encoding/package.json"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyper-identity-core/node_modules/compact-encoding/index.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyper-identity-core/node_modules/compact-encoding/package.json","./endian":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyper-identity-core/node_modules/compact-encoding/endian.js","./lexint":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyper-identity-core/node_modules/compact-encoding/lexint.js","./raw":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyper-identity-core/node_modules/compact-encoding/raw.js","b4a":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyper-identity-core/node_modules/b4a/index.js"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyper-identity-core/node_modules/compact-encoding/lexint.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyper-identity-core/node_modules/compact-encoding/package.json"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyper-identity-core/node_modules/compact-encoding/package.json":{},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyper-identity-core/node_modules/compact-encoding/raw.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyper-identity-core/node_modules/compact-encoding/package.json","./endian":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyper-identity-core/node_modules/compact-encoding/endian.js","b4a":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyper-identity-core/node_modules/b4a/index.js"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyper-identity-core/node_modules/hypercore-crypto/index.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyper-identity-core/node_modules/hypercore-crypto/package.json","b4a":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyper-identity-core/node_modules/b4a/index.js","compact-encoding":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyper-identity-core/node_modules/compact-encoding/index.js","sodium-universal":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyper-identity-core/node_modules/sodium-universal/index.js"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyper-identity-core/node_modules/hypercore-crypto/package.json":{},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyper-identity-core/node_modules/require-addon/lib/bare.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyper-identity-core/node_modules/require-addon/package.json"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyper-identity-core/node_modules/require-addon/package.json":{},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyper-identity-core/node_modules/sodium-native/binding.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyper-identity-core/node_modules/sodium-native/package.json",".":"linked:libsodium-native.5.1.0.so","require-addon":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyper-identity-core/node_modules/require-addon/lib/bare.js"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyper-identity-core/node_modules/sodium-native/index.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyper-identity-core/node_modules/sodium-native/package.json","./binding":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyper-identity-core/node_modules/sodium-native/binding.js","assert":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyper-identity-core/node_modules/bare-assert/index.js","which-runtime":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyper-identity-core/node_modules/which-runtime/index.js"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyper-identity-core/node_modules/sodium-native/package.json":{},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyper-identity-core/node_modules/sodium-universal/index.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyper-identity-core/node_modules/sodium-universal/package.json","sodium-native":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyper-identity-core/node_modules/sodium-native/index.js"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyper-identity-core/node_modules/sodium-universal/package.json":{},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyper-identity-core/node_modules/which-runtime/index.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyper-identity-core/node_modules/which-runtime/package.json"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyper-identity-core/node_modules/which-runtime/package.json":{},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyper-identity-core/package.json":{},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyperbee2/index.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyperbee2/package.json","./lib/cache.js":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyperbee2/lib/cache.js","./lib/changes.js":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyperbee2/lib/changes.js","./lib/context.js":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyperbee2/lib/context.js","./lib/diff.js":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyperbee2/lib/diff.js","./lib/inflate.js":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyperbee2/lib/inflate.js","./lib/ranges.js":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyperbee2/lib/ranges.js","./lib/session-config.js":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyperbee2/lib/session-config.js","./lib/tree.js":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyperbee2/lib/tree.js","./lib/write.js":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyperbee2/lib/write.js","b4a":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/b4a/index.js","events":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/bare-events/index.js","hypercore":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore/index.js"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyperbee2/lib/cache.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyperbee2/package.json"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyperbee2/lib/changes.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyperbee2/package.json","hypercore":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore/index.js","streamx":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/streamx/index.js"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyperbee2/lib/compat.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyperbee2/package.json","./compression":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyperbee2/lib/compression.js","b4a":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/b4a/index.js","protocol-buffers-encodings":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/protocol-buffers-encodings/index.js"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyperbee2/lib/compression.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyperbee2/package.json"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyperbee2/lib/context.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyperbee2/package.json","./encoding.js":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyperbee2/lib/encoding.js","./tree.js":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyperbee2/lib/tree.js","b4a":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/b4a/index.js","compact-encoding":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/compact-encoding/index.js","hypercore-errors":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore-errors/index.js","scope-lock":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/scope-lock/index.js"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyperbee2/lib/diff.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyperbee2/package.json","./ranges.js":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyperbee2/lib/ranges.js","b4a":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/b4a/index.js","hypercore":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore/index.js","streamx":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/streamx/index.js"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyperbee2/lib/encoding.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyperbee2/package.json","../spec/hyperschema":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyperbee2/spec/hyperschema/index.js","./compat.js":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyperbee2/lib/compat.js","./compression.js":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyperbee2/lib/compression.js","./tree.js":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyperbee2/lib/tree.js","compact-encoding":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/compact-encoding/index.js"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyperbee2/lib/inflate.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyperbee2/package.json","./compression.js":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyperbee2/lib/compression.js","./tree.js":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyperbee2/lib/tree.js","b4a":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/b4a/index.js"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyperbee2/lib/ranges.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyperbee2/package.json","b4a":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/b4a/index.js","hypercore":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore/index.js","streamx":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/streamx/index.js"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyperbee2/lib/session-config.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyperbee2/package.json"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyperbee2/lib/tree.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyperbee2/package.json","./compression.js":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyperbee2/lib/compression.js","b4a":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/b4a/index.js"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyperbee2/lib/write.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyperbee2/package.json","./compat.js":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyperbee2/lib/compat.js","./compression.js":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyperbee2/lib/compression.js","./encoding.js":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyperbee2/lib/encoding.js","./inflate.js":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyperbee2/lib/inflate.js","./tree.js":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyperbee2/lib/tree.js","b4a":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/b4a/index.js"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyperbee2/package.json":{},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyperbee2/spec/hyperschema/index.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyperbee2/package.json","hyperschema/runtime":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyperschema/runtime.cjs"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore-crypto/index.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore-crypto/package.json","b4a":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/b4a/index.js","compact-encoding":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/compact-encoding/index.js","sodium-universal":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/sodium-universal/index.js"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore-crypto/package.json":{},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore-errors/index.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore-errors/package.json","hypercore-id-encoding":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore-id-encoding/index.js"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore-errors/package.json":{},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore-id-encoding/index.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore-id-encoding/package.json","b4a":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/b4a/index.js","z32":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/z32/index.js"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore-id-encoding/package.json":{},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore-storage/encoding/external.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore-storage/package.json"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore-storage/encoding/spec/hyperschema/index.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore-storage/package.json","../../external.js":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore-storage/encoding/external.js","hyperschema/runtime":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyperschema/runtime.cjs"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore-storage/index.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore-storage/package.json","./lib/keys.js":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore-storage/lib/keys.js","./lib/streams.js":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore-storage/lib/streams.js","./lib/tx.js":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore-storage/lib/tx.js","./lib/view.js":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore-storage/lib/view.js","./migrations/0":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore-storage/migrations/0/index.js","./migrations/1":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore-storage/migrations/1/index.js","device-file":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/device-file/index.js","fs":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/bare-fs/index.js","path":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/bare-path/index.js","resolve-reject-promise":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/resolve-reject-promise/index.js","rocksdb-native":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/rocksdb-native/index.js","scope-lock":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/scope-lock/index.js","xache":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/xache/index.js"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore-storage/lib/block-dependency-stream.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore-storage/package.json","./keys":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore-storage/lib/keys.js","streamx":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/streamx/index.js"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore-storage/lib/close-error-stream.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore-storage/package.json","streamx":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/streamx/index.js"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore-storage/lib/keys.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore-storage/package.json","b4a":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/b4a/index.js","compact-encoding":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/compact-encoding/index.js","index-encoder":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/index-encoder/index.js"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore-storage/lib/streams.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore-storage/package.json","../encoding/spec/hyperschema":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore-storage/encoding/spec/hyperschema/index.js","./block-dependency-stream.js":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore-storage/lib/block-dependency-stream.js","./keys.js":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore-storage/lib/keys.js","b4a":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/b4a/index.js"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore-storage/lib/tx.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore-storage/package.json","../encoding/spec/hyperschema":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore-storage/encoding/spec/hyperschema/index.js","./keys.js":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore-storage/lib/keys.js","./view.js":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore-storage/lib/view.js","b4a":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/b4a/index.js","compact-encoding":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/compact-encoding/index.js","flat-tree":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/flat-tree/index.js"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore-storage/lib/view.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore-storage/package.json","./close-error-stream.js":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore-storage/lib/close-error-stream.js","b4a":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/b4a/index.js","streamx":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/streamx/index.js"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore-storage/migrations/0/index.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore-storage/package.json","../../lib/tx.js":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore-storage/lib/tx.js","../../lib/view.js":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore-storage/lib/view.js","./messages.js":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore-storage/migrations/0/messages.js","b4a":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/b4a/index.js","compact-encoding":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/compact-encoding/index.js","flat-tree":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/flat-tree/index.js","fs":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/bare-fs/index.js","hypercore-crypto":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore-crypto/index.js","path":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/bare-path/index.js","streamx":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/streamx/index.js"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore-storage/migrations/0/messages.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore-storage/package.json","b4a":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/b4a/index.js","compact-encoding":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/compact-encoding/index.js"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore-storage/migrations/1/index.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore-storage/package.json","../../encoding/spec/hyperschema":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore-storage/encoding/spec/hyperschema/index.js","../../lib/keys.js":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore-storage/lib/keys.js","../../lib/tx.js":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore-storage/lib/tx.js","../../lib/view.js":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore-storage/lib/view.js","compact-encoding":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/compact-encoding/index.js"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore-storage/package.json":{},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore/index.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore/package.json","./lib/caps":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore/lib/caps.js","./lib/core":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore/lib/core.js","./lib/default-encryption":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore/lib/default-encryption.js","./lib/download":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore/lib/download.js","./lib/feature-flags":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore/lib/feature-flags.js","./lib/fully-remote-proof":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore/lib/fully-remote-proof.js","./lib/info":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore/lib/info.js","./lib/inspect":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore/lib/inspect.js","./lib/mark-bitfield":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore/lib/mark-bitfield.js","./lib/merkle-tree":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore/lib/merkle-tree.js","./lib/replicator":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore/lib/replicator.js","./lib/streams":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore/lib/streams.js","./lib/verifier":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore/lib/verifier.js","./lib/wants":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore/lib/wants.js","@hyperswarm/secret-stream":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/@hyperswarm/secret-stream/index.js","b4a":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/b4a/index.js","compact-encoding":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/compact-encoding/index.js","events":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/bare-events/index.js","flat-tree":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/flat-tree/index.js","hypercore-crypto":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore-crypto/index.js","hypercore-errors":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore-errors/index.js","hypercore-id-encoding":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore-id-encoding/index.js","hypercore-storage":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore-storage/index.js","is-options":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/is-options/index.js","nanoassert":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/nanoassert/index.js","protomux":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/protomux/index.js","safety-catch":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/safety-catch/index.js","unslab":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/unslab/index.js"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore/lib/audit.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore/package.json","./merkle-tree":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore/lib/merkle-tree.js","b4a":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/b4a/index.js","flat-tree":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/flat-tree/index.js","hypercore-crypto":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore-crypto/index.js"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore/lib/bit-interlude.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore/package.json","./compat":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore/lib/compat.js","b4a":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/b4a/index.js"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore/lib/bitfield.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore/package.json","./compat":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore/lib/compat.js","b4a":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/b4a/index.js","big-sparse-array":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/big-sparse-array/index.js"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore/lib/caps.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore/package.json","b4a":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/b4a/index.js","compact-encoding":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/compact-encoding/index.js","hypercore-crypto":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore-crypto/index.js","sodium-universal":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/sodium-universal/index.js"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore/lib/compat.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore/package.json","quickbit-universal":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/quickbit-universal/index.js","quickbit-universal/fallback":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/quickbit-universal/fallback.js"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore/lib/copy-prologue.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore/package.json","./bitfield":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore/lib/bitfield.js","b4a":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/b4a/index.js","flat-tree":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/flat-tree/index.js","hypercore-crypto":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore-crypto/index.js","quickbit-universal":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/quickbit-universal/index.js"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore/lib/core.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore/package.json","./audit":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore/lib/audit.js","./bit-interlude":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore/lib/bit-interlude.js","./bitfield":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore/lib/bitfield.js","./copy-prologue":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore/lib/copy-prologue.js","./merkle-tree":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore/lib/merkle-tree.js","./mutex":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore/lib/mutex.js","./remote-bitfield":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore/lib/remote-bitfield.js","./replicator":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore/lib/replicator.js","./session-state":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore/lib/session-state.js","./verifier":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore/lib/verifier.js","b4a":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/b4a/index.js","hypercore-crypto":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore-crypto/index.js","hypercore-errors":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore-errors/index.js","unslab":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/unslab/index.js","z32":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/z32/index.js"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore/lib/default-encryption.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore/package.json","./caps":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore/lib/caps.js","b4a":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/b4a/index.js","compact-encoding":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/compact-encoding/index.js","sodium-universal":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/sodium-universal/index.js"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore/lib/download.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore/package.json"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore/lib/feature-flags.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore/package.json"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore/lib/fully-remote-proof.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore/package.json","./merkle-tree.js":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore/lib/merkle-tree.js","./messages.js":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore/lib/messages.js","b4a":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/b4a/index.js","compact-encoding":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/compact-encoding/index.js","flat-tree":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/flat-tree/index.js","hypercore-crypto":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore-crypto/index.js"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore/lib/hotswap-queue.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore/package.json"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore/lib/info.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore/package.json"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore/lib/inspect.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore/package.json","b4a":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/b4a/index.js"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore/lib/mark-bitfield.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore/package.json","./compat":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore/lib/compat.js","./mutex":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore/lib/mutex.js","b4a":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/b4a/index.js","big-sparse-array":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/big-sparse-array/index.js","streamx":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/streamx/index.js"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore/lib/merkle-tree.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore/package.json","./caps":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore/lib/caps.js","b4a":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/b4a/index.js","flat-tree":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/flat-tree/index.js","hypercore-crypto":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore-crypto/index.js","hypercore-errors":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore-errors/index.js","unslab":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/unslab/index.js"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore/lib/messages.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore/package.json","./caps":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore/lib/caps.js","compact-encoding":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/compact-encoding/index.js","hypercore-errors":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore-errors/index.js","unslab":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/unslab/index.js"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore/lib/multisig.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore/package.json","./merkle-tree":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore/lib/merkle-tree.js","./messages":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore/lib/messages.js","b4a":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/b4a/index.js","compact-encoding":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/compact-encoding/index.js","flat-tree":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/flat-tree/index.js"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore/lib/mutex.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore/package.json"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore/lib/receiver-queue.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore/package.json","fast-fifo":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/fast-fifo/index.js"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore/lib/remote-bitfield.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore/package.json","./compat":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore/lib/compat.js","big-sparse-array":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/big-sparse-array/index.js"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore/lib/replicator.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore/package.json","./caps":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore/lib/caps.js","./hotswap-queue":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore/lib/hotswap-queue.js","./merkle-tree":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore/lib/merkle-tree.js","./messages":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore/lib/messages.js","./mutex":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore/lib/mutex.js","./receiver-queue":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore/lib/receiver-queue.js","./remote-bitfield":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore/lib/remote-bitfield.js","./wants":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore/lib/wants.js","b4a":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/b4a/index.js","flat-tree":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/flat-tree/index.js","hypercore-errors":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore-errors/index.js","random-array-iterator":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/random-array-iterator/index.js","safety-catch":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/safety-catch/index.js"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore/lib/session-state.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore/package.json","./bitfield":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore/lib/bitfield.js","./merkle-tree":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore/lib/merkle-tree.js","./mutex":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore/lib/mutex.js","b4a":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/b4a/index.js","flat-tree":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/flat-tree/index.js","hypercore-crypto":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore-crypto/index.js","hypercore-errors":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore-errors/index.js","hypercore-storage":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore-storage/index.js","nanoassert":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/nanoassert/index.js","quickbit-universal":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/quickbit-universal/index.js"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore/lib/streams.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore/package.json","streamx":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/streamx/index.js"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore/lib/verifier.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore/package.json","./caps":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore/lib/caps.js","./messages":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore/lib/messages.js","./multisig":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore/lib/multisig.js","b4a":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/b4a/index.js","compact-encoding":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/compact-encoding/index.js","flat-tree":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/flat-tree/index.js","hypercore-crypto":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore-crypto/index.js","hypercore-errors":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore-errors/index.js","unslab":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/unslab/index.js"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore/lib/wants.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore/package.json"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore/package.json":{},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyperschema/package.json":{},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyperschema/runtime.cjs":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyperschema/package.json","compact-encoding":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/compact-encoding/index.js"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/index-encoder/index.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/index-encoder/package.json","b4a":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/b4a/index.js"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/index-encoder/package.json":{},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/is-options/index.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/is-options/package.json","b4a":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/b4a/index.js"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/is-options/package.json":{},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/nanoassert/index.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/nanoassert/package.json"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/nanoassert/package.json":{},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/noise-curve-ed/index.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/noise-curve-ed/package.json","b4a":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/b4a/index.js","nanoassert":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/nanoassert/index.js","sodium-universal":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/sodium-universal/index.js"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/noise-curve-ed/package.json":{},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/noise-handshake/cipher.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/noise-handshake/package.json","b4a":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/b4a/index.js","sodium-universal":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/sodium-universal/index.js"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/noise-handshake/dh.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/noise-handshake/package.json","b4a":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/b4a/index.js","nanoassert":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/nanoassert/index.js","sodium-universal":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/sodium-universal/index.js"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/noise-handshake/hkdf.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/noise-handshake/package.json","./hmac":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/noise-handshake/hmac.js","b4a":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/b4a/index.js"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/noise-handshake/hmac.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/noise-handshake/package.json","b4a":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/b4a/index.js","sodium-universal":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/sodium-universal/index.js"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/noise-handshake/noise.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/noise-handshake/package.json","./hkdf":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/noise-handshake/hkdf.js","./symmetric-state":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/noise-handshake/symmetric-state.js","b4a":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/b4a/index.js","nanoassert":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/nanoassert/index.js"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/noise-handshake/package.json":{},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/noise-handshake/symmetric-state.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/noise-handshake/package.json","./cipher":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/noise-handshake/cipher.js","./dh":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/noise-handshake/dh.js","./hkdf":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/noise-handshake/hkdf.js","b4a":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/b4a/index.js","nanoassert":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/nanoassert/index.js","sodium-universal":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/sodium-universal/index.js"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/protocol-buffers-encodings/index.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/protocol-buffers-encodings/package.json","b4a":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/b4a/index.js","signed-varint":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/signed-varint/index.js","varint":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/varint/index.js"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/protocol-buffers-encodings/package.json":{},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/protomux-wakeup/index.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/protomux-wakeup/package.json","./spec/hyperschema":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/protomux-wakeup/spec/hyperschema/index.js","b4a":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/b4a/index.js","hypercore-crypto":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore-crypto/index.js","protomux":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/protomux/index.js"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/protomux-wakeup/package.json":{},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/protomux-wakeup/spec/hyperschema/index.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/protomux-wakeup/package.json","hyperschema/runtime":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyperschema/runtime.cjs"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/protomux/index.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/protomux/package.json","b4a":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/b4a/index.js","compact-encoding":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/compact-encoding/index.js","queue-tick":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/queue-tick/process-next-tick.js","safety-catch":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/safety-catch/index.js","unslab":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/unslab/index.js"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/protomux/package.json":{},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/queue-tick/package.json":{},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/queue-tick/process-next-tick.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/queue-tick/package.json","./queue-microtask":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/queue-tick/queue-microtask.js"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/queue-tick/queue-microtask.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/queue-tick/package.json"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/quickbit-native/binding.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/quickbit-native/package.json",".":"linked:libquickbit-native.2.4.8.so","require-addon":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/require-addon/lib/bare.js"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/quickbit-native/index.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/quickbit-native/package.json","./binding":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/quickbit-native/binding.js"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/quickbit-native/package.json":{},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/quickbit-universal/fallback.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/quickbit-universal/package.json","simdle-universal":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/simdle-universal/index.js"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/quickbit-universal/index.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/quickbit-universal/package.json","./fallback":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/quickbit-universal/fallback.js","quickbit-native":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/quickbit-native/index.js"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/quickbit-universal/package.json":{},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/random-array-iterator/index.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/random-array-iterator/package.json"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/random-array-iterator/package.json":{},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/ready-guard/index.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/ready-guard/package.json","resolve-reject-promise":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/resolve-reject-promise/index.js"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/ready-guard/package.json":{},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/ready-resource/index.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/ready-resource/package.json","events":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/bare-events/index.js"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/ready-resource/package.json":{},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/refcounter/index.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/refcounter/package.json"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/refcounter/package.json":{},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/require-addon/lib/bare.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/require-addon/package.json"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/require-addon/package.json":{},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/resolve-reject-promise/index.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/resolve-reject-promise/package.json"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/resolve-reject-promise/package.json":{},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/resource-on-exit/index.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/resource-on-exit/package.json"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/resource-on-exit/package.json":{},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/rocksdb-native/binding.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/rocksdb-native/package.json",".":"linked:librocksdb-native.3.17.4.so","require-addon":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/require-addon/lib/bare.js"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/rocksdb-native/index.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/rocksdb-native/package.json","./lib/column-family":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/rocksdb-native/lib/column-family.js","./lib/constants":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/rocksdb-native/lib/constants.js","./lib/filter-policy":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/rocksdb-native/lib/filter-policy.js","./lib/iterator":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/rocksdb-native/lib/iterator.js","./lib/snapshot":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/rocksdb-native/lib/snapshot.js","./lib/state":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/rocksdb-native/lib/state.js"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/rocksdb-native/lib/batch.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/rocksdb-native/package.json","../binding":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/rocksdb-native/binding.js","compact-encoding":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/compact-encoding/index.js"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/rocksdb-native/lib/column-family.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/rocksdb-native/package.json","../binding":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/rocksdb-native/binding.js","./constants":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/rocksdb-native/lib/constants.js","./filter-policy":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/rocksdb-native/lib/filter-policy.js"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/rocksdb-native/lib/constants.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/rocksdb-native/package.json"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/rocksdb-native/lib/filter-policy.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/rocksdb-native/package.json"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/rocksdb-native/lib/iterator.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/rocksdb-native/package.json","../binding":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/rocksdb-native/binding.js","compact-encoding":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/compact-encoding/index.js","streamx":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/streamx/index.js"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/rocksdb-native/lib/snapshot.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/rocksdb-native/package.json","../binding":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/rocksdb-native/binding.js"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/rocksdb-native/lib/state.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/rocksdb-native/package.json","../binding":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/rocksdb-native/binding.js","./batch":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/rocksdb-native/lib/batch.js","./column-family":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/rocksdb-native/lib/column-family.js","./constants":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/rocksdb-native/lib/constants.js","compact-encoding":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/compact-encoding/index.js","ready-resource":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/ready-resource/index.js","refcounter":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/refcounter/index.js","resolve-reject-promise":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/resolve-reject-promise/index.js","signal-promise":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/signal-promise/index.js"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/rocksdb-native/package.json":{},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/safety-catch/index.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/safety-catch/package.json"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/safety-catch/package.json":{},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/scope-lock/index.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/scope-lock/package.json"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/scope-lock/package.json":{},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/signal-promise/index.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/signal-promise/package.json"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/signal-promise/package.json":{},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/signed-varint/index.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/signed-varint/package.json","varint":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/varint/index.js"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/signed-varint/package.json":{},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/simdle-native/binding.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/simdle-native/package.json",".":"linked:libsimdle-native.1.3.9.so","require-addon":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/require-addon/lib/bare.js"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/simdle-native/index.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/simdle-native/package.json","./binding":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/simdle-native/binding.js","b4a":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/b4a/index.js"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/simdle-native/package.json":{},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/simdle-universal/fallback.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/simdle-universal/package.json","./scalar":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/simdle-universal/scalar.js","b4a":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/b4a/index.js"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/simdle-universal/index.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/simdle-universal/package.json","./fallback":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/simdle-universal/fallback.js","simdle-native":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/simdle-native/index.js"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/simdle-universal/package.json":{},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/simdle-universal/scalar.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/simdle-universal/package.json"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/sodium-native/binding.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/sodium-native/package.json",".":"linked:libsodium-native.5.1.0.so","require-addon":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/require-addon/lib/bare.js"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/sodium-native/index.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/sodium-native/package.json","./binding":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/sodium-native/binding.js","assert":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/bare-assert/index.js","which-runtime":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/which-runtime/index.js"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/sodium-native/package.json":{},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/sodium-secretstream/index.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/sodium-secretstream/package.json","b4a":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/b4a/index.js","sodium-universal":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/sodium-universal/index.js"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/sodium-secretstream/package.json":{},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/sodium-universal/index.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/sodium-universal/package.json","sodium-native":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/sodium-native/index.js"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/sodium-universal/package.json":{},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/streamx/index.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/streamx/package.json","./lib/errors.js":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/streamx/lib/errors.js","events-universal":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/events-universal/bare.js","fast-fifo":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/fast-fifo/index.js","text-decoder":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/text-decoder/index.js"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/streamx/lib/errors.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/streamx/package.json"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/streamx/package.json":{},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/teex/index.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/teex/package.json","streamx":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/streamx/index.js"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/teex/package.json":{},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/text-decoder/index.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/text-decoder/package.json","./lib/pass-through-decoder":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/text-decoder/lib/pass-through-decoder.js","./lib/utf8-decoder":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/text-decoder/lib/utf8-decoder.js"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/text-decoder/lib/pass-through-decoder.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/text-decoder/package.json","b4a":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/b4a/index.js"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/text-decoder/lib/utf8-decoder.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/text-decoder/package.json","b4a":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/b4a/index.js"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/text-decoder/package.json":{},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/timeout-refresh/browser.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/timeout-refresh/package.json"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/timeout-refresh/index.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/timeout-refresh/package.json","./browser":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/timeout-refresh/browser.js","./node":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/timeout-refresh/node.js"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/timeout-refresh/node.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/timeout-refresh/package.json"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/timeout-refresh/package.json":{},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/unslab/index.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/unslab/package.json","b4a":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/b4a/index.js"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/unslab/package.json":{},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/varint/decode.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/varint/package.json"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/varint/encode.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/varint/package.json"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/varint/index.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/varint/package.json","./decode.js":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/varint/decode.js","./encode.js":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/varint/encode.js","./length.js":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/varint/length.js"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/varint/length.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/varint/package.json"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/varint/package.json":{},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/which-runtime/index.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/which-runtime/package.json"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/which-runtime/package.json":{},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/xache/index.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/xache/package.json"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/xache/package.json":{},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/z32/index.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/z32/package.json","b4a":"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/b4a/index.js"},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/z32/package.json":{},"/node_modules/favela-peer/node_modules/hyper-identity2/package.json":{},"/node_modules/favela-peer/node_modules/hyper-overlay/index.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-overlay/package.json","./lib/chunk-scheduler.js":"/node_modules/favela-peer/node_modules/hyper-overlay/lib/chunk-scheduler.js","./lib/chunker.js":"/node_modules/favela-peer/node_modules/hyper-overlay/lib/chunker.js","./lib/file-index.js":"/node_modules/favela-peer/node_modules/hyper-overlay/lib/file-index.js","./lib/ignore.js":"/node_modules/favela-peer/node_modules/hyper-overlay/lib/ignore.js","./lib/manifest.js":"/node_modules/favela-peer/node_modules/hyper-overlay/lib/manifest.js","./lib/messages.js":"/node_modules/favela-peer/node_modules/hyper-overlay/lib/messages.js","./lib/overlay.js":"/node_modules/favela-peer/node_modules/hyper-overlay/lib/overlay.js","./lib/pointer.js":"/node_modules/favela-peer/node_modules/hyper-overlay/lib/pointer.js","./lib/protocol.js":"/node_modules/favela-peer/node_modules/hyper-overlay/lib/protocol.js","./lib/sync-engine.js":"/node_modules/favela-peer/node_modules/hyper-overlay/lib/sync-engine.js","./lib/transfer.js":"/node_modules/favela-peer/node_modules/hyper-overlay/lib/transfer.js","./lib/tree.js":"/node_modules/favela-peer/node_modules/hyper-overlay/lib/tree.js"},"/node_modules/favela-peer/node_modules/hyper-overlay/lib/chunk-scheduler.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-overlay/package.json","./chunker.js":"/node_modules/favela-peer/node_modules/hyper-overlay/lib/chunker.js"},"/node_modules/favela-peer/node_modules/hyper-overlay/lib/chunker.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-overlay/package.json","compact-encoding":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/compact-encoding/index.js","hypercore-crypto":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/hypercore-crypto/index.js","sodium-universal":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/sodium-universal/index.js"},"/node_modules/favela-peer/node_modules/hyper-overlay/lib/file-index.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-overlay/package.json","hyperbee":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/hyperbee/index.js","ready-resource":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/ready-resource/index.js"},"/node_modules/favela-peer/node_modules/hyper-overlay/lib/ignore.js":{"#fs":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-fs/index.js","#package":"/node_modules/favela-peer/node_modules/hyper-overlay/package.json","#path":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-path/index.js"},"/node_modules/favela-peer/node_modules/hyper-overlay/lib/manifest.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-overlay/package.json","#zlib":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-zlib/index.js"},"/node_modules/favela-peer/node_modules/hyper-overlay/lib/messages.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-overlay/package.json","compact-encoding":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/compact-encoding/index.js"},"/node_modules/favela-peer/node_modules/hyper-overlay/lib/overlay.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-overlay/package.json","./chunker.js":"/node_modules/favela-peer/node_modules/hyper-overlay/lib/chunker.js","./file-index.js":"/node_modules/favela-peer/node_modules/hyper-overlay/lib/file-index.js","./protocol.js":"/node_modules/favela-peer/node_modules/hyper-overlay/lib/protocol.js","./sync-engine.js":"/node_modules/favela-peer/node_modules/hyper-overlay/lib/sync-engine.js","./transfer.js":"/node_modules/favela-peer/node_modules/hyper-overlay/lib/transfer.js","bare-fs":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-fs/index.js","bare-os":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-os/index.js","bare-path":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-path/index.js","hypercore-crypto":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/hypercore-crypto/index.js","ready-resource":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/ready-resource/index.js"},"/node_modules/favela-peer/node_modules/hyper-overlay/lib/pointer.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-overlay/package.json"},"/node_modules/favela-peer/node_modules/hyper-overlay/lib/protocol.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-overlay/package.json","./chunk-scheduler.js":"/node_modules/favela-peer/node_modules/hyper-overlay/lib/chunk-scheduler.js","./chunker.js":"/node_modules/favela-peer/node_modules/hyper-overlay/lib/chunker.js","./messages.js":"/node_modules/favela-peer/node_modules/hyper-overlay/lib/messages.js","bare-fs":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-fs/index.js","bare-path":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-path/index.js"},"/node_modules/favela-peer/node_modules/hyper-overlay/lib/sync-engine.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-overlay/package.json","ready-resource":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/ready-resource/index.js"},"/node_modules/favela-peer/node_modules/hyper-overlay/lib/transfer.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-overlay/package.json","./chunker.js":"/node_modules/favela-peer/node_modules/hyper-overlay/lib/chunker.js","bare-fs":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-fs/index.js","bare-os":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-os/index.js","bare-path":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-path/index.js","compact-encoding":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/compact-encoding/index.js","sodium-universal":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/sodium-universal/index.js"},"/node_modules/favela-peer/node_modules/hyper-overlay/lib/tree.js":{"#fs":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-fs/index.js","#package":"/node_modules/favela-peer/node_modules/hyper-overlay/package.json","#path":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-path/index.js","compact-encoding":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/compact-encoding/index.js","hypercore-crypto":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/hypercore-crypto/index.js"},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/b4a/index.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/b4a/package.json"},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/b4a/package.json":{},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-ansi-escapes/index.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-ansi-escapes/package.json"},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-ansi-escapes/package.json":{},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-assert/index.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-assert/package.json","bare-inspect":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-inspect/index.js"},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-assert/package.json":{},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-events/index.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-events/package.json","./lib/errors":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-events/lib/errors.js"},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-events/lib/errors.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-events/package.json"},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-events/package.json":{},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-fs/binding.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-fs/package.json",".":"linked:libbare-fs.4.7.2.so"},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-fs/index.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-fs/package.json","./binding":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-fs/binding.js","./lib/constants":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-fs/lib/constants.js","./lib/errors":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-fs/lib/errors.js","./promises":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-fs/promises.js","bare-events":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-events/index.js","bare-path":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-path/index.js","bare-stream":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-stream/index.js","bare-url":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-url/index.js","fast-fifo":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/fast-fifo/index.js"},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-fs/lib/constants.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-fs/package.json","../binding":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-fs/binding.js"},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-fs/lib/errors.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-fs/package.json","bare-os":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-os/index.js"},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-fs/package.json":{},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-fs/promises.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-fs/package.json",".":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-fs/index.js","bare-events":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-events/index.js"},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-inspect/binding.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-inspect/package.json",".":"linked:libbare-inspect.3.1.4.so"},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-inspect/index.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-inspect/package.json","./binding":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-inspect/binding.js","bare-ansi-escapes":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-ansi-escapes/index.js","bare-type":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-type/index.js"},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-inspect/package.json":{},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-os/binding.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-os/package.json",".":"linked:libbare-os.3.9.1.so"},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-os/index.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-os/package.json","./binding":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-os/binding.js","./lib/constants":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-os/lib/constants.js","./lib/errors":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-os/lib/errors.js"},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-os/lib/constants.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-os/package.json","../binding":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-os/binding.js"},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-os/lib/errors.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-os/package.json"},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-os/package.json":{},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-path/index.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-path/package.json","./lib/posix":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-path/lib/posix.js","./lib/win32":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-path/lib/win32.js"},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-path/lib/constants.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-path/package.json"},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-path/lib/posix.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-path/package.json","./constants":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-path/lib/constants.js","./shared":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-path/lib/shared.js","./win32":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-path/lib/win32.js","bare-os":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-os/index.js"},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-path/lib/shared.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-path/package.json","./constants":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-path/lib/constants.js"},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-path/lib/win32.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-path/package.json","./constants":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-path/lib/constants.js","./posix":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-path/lib/posix.js","./shared":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-path/lib/shared.js","bare-os":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-os/index.js"},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-path/package.json":{},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-stream/index.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-stream/package.json","./web":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-stream/web.js","b4a":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/b4a/index.js","streamx":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/streamx/index.js"},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-stream/package.json":{},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-stream/web.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-stream/package.json","streamx":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/streamx/index.js","teex":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/teex/index.js"},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-type/binding.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-type/package.json",".":"linked:libbare-type.1.1.0.so"},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-type/index.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-type/package.json","./binding":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-type/binding.js"},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-type/package.json":{},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-url/binding.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-url/package.json",".":"linked:libbare-url.2.4.5.so"},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-url/index.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-url/package.json","./binding":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-url/binding.js","./lib/errors":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-url/lib/errors.js","./lib/url-search-params":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-url/lib/url-search-params.js","bare-path":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-path/index.js"},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-url/lib/errors.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-url/package.json"},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-url/lib/url-search-params.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-url/package.json"},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-url/package.json":{},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-zlib/binding.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-zlib/package.json",".":"linked:libbare-zlib.1.4.0.so"},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-zlib/index.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-zlib/package.json","./binding":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-zlib/binding.js","./lib/constants":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-zlib/lib/constants.js","./lib/errors":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-zlib/lib/errors.js","bare-stream":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-stream/index.js"},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-zlib/lib/constants.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-zlib/package.json","../binding":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-zlib/binding.js"},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-zlib/lib/errors.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-zlib/package.json"},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-zlib/package.json":{},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/codecs/index.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/codecs/package.json","b4a":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/b4a/index.js"},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/codecs/package.json":{},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/compact-encoding/endian.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/compact-encoding/package.json"},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/compact-encoding/index.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/compact-encoding/package.json","./endian":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/compact-encoding/endian.js","./lexint":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/compact-encoding/lexint.js","./raw":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/compact-encoding/raw.js","b4a":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/b4a/index.js"},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/compact-encoding/lexint.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/compact-encoding/package.json"},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/compact-encoding/package.json":{},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/compact-encoding/raw.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/compact-encoding/package.json","./endian":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/compact-encoding/endian.js","b4a":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/b4a/index.js"},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/debounceify/index.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/debounceify/package.json"},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/debounceify/package.json":{},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/events-universal/bare.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/events-universal/package.json","bare-events":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-events/index.js"},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/events-universal/package.json":{},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/fast-fifo/fixed-size.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/fast-fifo/package.json"},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/fast-fifo/index.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/fast-fifo/package.json","./fixed-size":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/fast-fifo/fixed-size.js"},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/fast-fifo/package.json":{},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/hyperbee/index.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/hyperbee/package.json","./iterators/diff":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/hyperbee/iterators/diff.js","./iterators/history":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/hyperbee/iterators/history.js","./iterators/local":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/hyperbee/iterators/local.js","./iterators/range":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/hyperbee/iterators/range.js","./lib/extension":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/hyperbee/lib/extension.js","./lib/messages":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/hyperbee/lib/messages.js","b4a":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/b4a/index.js","codecs":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/codecs/index.js","debounceify":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/debounceify/index.js","hypercore-errors":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/hypercore-errors/index.js","mutexify/promise":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/mutexify/promise.js","rache":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/rache/index.js","ready-resource":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/ready-resource/index.js","resolve-reject-promise":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/resolve-reject-promise/index.js","safety-catch":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/safety-catch/index.js","streamx":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/streamx/index.js","unslab":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/unslab/index.js"},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/hyperbee/iterators/diff.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/hyperbee/package.json","b4a":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/b4a/index.js"},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/hyperbee/iterators/history.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/hyperbee/package.json"},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/hyperbee/iterators/local.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/hyperbee/package.json"},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/hyperbee/iterators/range.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/hyperbee/package.json","b4a":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/b4a/index.js"},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/hyperbee/lib/extension.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/hyperbee/package.json","./messages":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/hyperbee/lib/messages.js"},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/hyperbee/lib/messages.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/hyperbee/package.json","b4a":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/b4a/index.js","protocol-buffers-encodings":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/protocol-buffers-encodings/index.js"},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/hyperbee/package.json":{},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/hypercore-crypto/index.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/hypercore-crypto/package.json","b4a":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/b4a/index.js","compact-encoding":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/compact-encoding/index.js","sodium-universal":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/sodium-universal/index.js"},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/hypercore-crypto/package.json":{},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/hypercore-errors/index.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/hypercore-errors/package.json","hypercore-id-encoding":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/hypercore-id-encoding/index.js"},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/hypercore-errors/package.json":{},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/hypercore-id-encoding/index.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/hypercore-id-encoding/package.json","b4a":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/b4a/index.js","z32":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/z32/index.js"},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/hypercore-id-encoding/package.json":{},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/mutexify/index.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/mutexify/package.json","queue-tick":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/queue-tick/process-next-tick.js"},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/mutexify/package.json":{},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/mutexify/promise.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/mutexify/package.json",".":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/mutexify/index.js"},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/protocol-buffers-encodings/index.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/protocol-buffers-encodings/package.json","b4a":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/b4a/index.js","signed-varint":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/signed-varint/index.js","varint":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/varint/index.js"},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/protocol-buffers-encodings/package.json":{},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/queue-tick/package.json":{},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/queue-tick/process-next-tick.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/queue-tick/package.json","./queue-microtask":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/queue-tick/queue-microtask.js"},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/queue-tick/queue-microtask.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/queue-tick/package.json"},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/rache/index.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/rache/package.json"},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/rache/package.json":{},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/ready-resource/index.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/ready-resource/package.json","events":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-events/index.js"},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/ready-resource/package.json":{},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/require-addon/lib/bare.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/require-addon/package.json"},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/require-addon/package.json":{},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/resolve-reject-promise/index.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/resolve-reject-promise/package.json"},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/resolve-reject-promise/package.json":{},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/safety-catch/index.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/safety-catch/package.json"},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/safety-catch/package.json":{},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/signed-varint/index.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/signed-varint/package.json","varint":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/varint/index.js"},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/signed-varint/package.json":{},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/sodium-native/binding.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/sodium-native/package.json",".":"linked:libsodium-native.5.1.0.so","require-addon":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/require-addon/lib/bare.js"},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/sodium-native/index.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/sodium-native/package.json","./binding":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/sodium-native/binding.js","assert":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-assert/index.js","which-runtime":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/which-runtime/index.js"},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/sodium-native/package.json":{},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/sodium-universal/index.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/sodium-universal/package.json","sodium-native":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/sodium-native/index.js"},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/sodium-universal/package.json":{},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/streamx/index.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/streamx/package.json","./lib/errors.js":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/streamx/lib/errors.js","events-universal":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/events-universal/bare.js","fast-fifo":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/fast-fifo/index.js","text-decoder":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/text-decoder/index.js"},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/streamx/lib/errors.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/streamx/package.json"},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/streamx/package.json":{},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/teex/index.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/teex/package.json","streamx":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/streamx/index.js"},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/teex/package.json":{},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/text-decoder/index.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/text-decoder/package.json","./lib/pass-through-decoder":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/text-decoder/lib/pass-through-decoder.js","./lib/utf8-decoder":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/text-decoder/lib/utf8-decoder.js"},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/text-decoder/lib/pass-through-decoder.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/text-decoder/package.json","b4a":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/b4a/index.js"},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/text-decoder/lib/utf8-decoder.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/text-decoder/package.json","b4a":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/b4a/index.js"},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/text-decoder/package.json":{},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/unslab/index.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/unslab/package.json","b4a":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/b4a/index.js"},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/unslab/package.json":{},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/varint/decode.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/varint/package.json"},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/varint/encode.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/varint/package.json"},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/varint/index.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/varint/package.json","./decode.js":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/varint/decode.js","./encode.js":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/varint/encode.js","./length.js":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/varint/length.js"},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/varint/length.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/varint/package.json"},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/varint/package.json":{},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/which-runtime/index.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/which-runtime/package.json"},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/which-runtime/package.json":{},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/z32/index.js":{"#package":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/z32/package.json","b4a":"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/b4a/index.js"},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/z32/package.json":{},"/node_modules/favela-peer/node_modules/hyper-overlay/package.json":{},"/node_modules/favela-peer/node_modules/hyperbee2/index.js":{"#package":"/node_modules/favela-peer/node_modules/hyperbee2/package.json","./lib/cache.js":"/node_modules/favela-peer/node_modules/hyperbee2/lib/cache.js","./lib/changes.js":"/node_modules/favela-peer/node_modules/hyperbee2/lib/changes.js","./lib/context.js":"/node_modules/favela-peer/node_modules/hyperbee2/lib/context.js","./lib/diff.js":"/node_modules/favela-peer/node_modules/hyperbee2/lib/diff.js","./lib/inflate.js":"/node_modules/favela-peer/node_modules/hyperbee2/lib/inflate.js","./lib/ranges.js":"/node_modules/favela-peer/node_modules/hyperbee2/lib/ranges.js","./lib/session-config.js":"/node_modules/favela-peer/node_modules/hyperbee2/lib/session-config.js","./lib/tree.js":"/node_modules/favela-peer/node_modules/hyperbee2/lib/tree.js","./lib/write.js":"/node_modules/favela-peer/node_modules/hyperbee2/lib/write.js","b4a":"/node_modules/favela-peer/node_modules/b4a/index.js","events":"/node_modules/favela-peer/node_modules/bare-events/index.js","hypercore":"/node_modules/favela-peer/node_modules/hypercore/index.js"},"/node_modules/favela-peer/node_modules/hyperbee2/lib/cache.js":{"#package":"/node_modules/favela-peer/node_modules/hyperbee2/package.json"},"/node_modules/favela-peer/node_modules/hyperbee2/lib/changes.js":{"#package":"/node_modules/favela-peer/node_modules/hyperbee2/package.json","hypercore":"/node_modules/favela-peer/node_modules/hypercore/index.js","streamx":"/node_modules/favela-peer/node_modules/streamx/index.js"},"/node_modules/favela-peer/node_modules/hyperbee2/lib/compat.js":{"#package":"/node_modules/favela-peer/node_modules/hyperbee2/package.json","./compression":"/node_modules/favela-peer/node_modules/hyperbee2/lib/compression.js","b4a":"/node_modules/favela-peer/node_modules/b4a/index.js","protocol-buffers-encodings":"/node_modules/favela-peer/node_modules/protocol-buffers-encodings/index.js"},"/node_modules/favela-peer/node_modules/hyperbee2/lib/compression.js":{"#package":"/node_modules/favela-peer/node_modules/hyperbee2/package.json"},"/node_modules/favela-peer/node_modules/hyperbee2/lib/context.js":{"#package":"/node_modules/favela-peer/node_modules/hyperbee2/package.json","./encoding.js":"/node_modules/favela-peer/node_modules/hyperbee2/lib/encoding.js","./tree.js":"/node_modules/favela-peer/node_modules/hyperbee2/lib/tree.js","b4a":"/node_modules/favela-peer/node_modules/b4a/index.js","compact-encoding":"/node_modules/favela-peer/node_modules/compact-encoding/index.js","hypercore-errors":"/node_modules/favela-peer/node_modules/hypercore-errors/index.js","scope-lock":"/node_modules/favela-peer/node_modules/scope-lock/index.js"},"/node_modules/favela-peer/node_modules/hyperbee2/lib/diff.js":{"#package":"/node_modules/favela-peer/node_modules/hyperbee2/package.json","./ranges.js":"/node_modules/favela-peer/node_modules/hyperbee2/lib/ranges.js","b4a":"/node_modules/favela-peer/node_modules/b4a/index.js","hypercore":"/node_modules/favela-peer/node_modules/hypercore/index.js","streamx":"/node_modules/favela-peer/node_modules/streamx/index.js"},"/node_modules/favela-peer/node_modules/hyperbee2/lib/encoding.js":{"#package":"/node_modules/favela-peer/node_modules/hyperbee2/package.json","../spec/hyperschema":"/node_modules/favela-peer/node_modules/hyperbee2/spec/hyperschema/index.js","./compat.js":"/node_modules/favela-peer/node_modules/hyperbee2/lib/compat.js","./compression.js":"/node_modules/favela-peer/node_modules/hyperbee2/lib/compression.js","./tree.js":"/node_modules/favela-peer/node_modules/hyperbee2/lib/tree.js","compact-encoding":"/node_modules/favela-peer/node_modules/compact-encoding/index.js"},"/node_modules/favela-peer/node_modules/hyperbee2/lib/inflate.js":{"#package":"/node_modules/favela-peer/node_modules/hyperbee2/package.json","./compression.js":"/node_modules/favela-peer/node_modules/hyperbee2/lib/compression.js","./tree.js":"/node_modules/favela-peer/node_modules/hyperbee2/lib/tree.js","b4a":"/node_modules/favela-peer/node_modules/b4a/index.js"},"/node_modules/favela-peer/node_modules/hyperbee2/lib/ranges.js":{"#package":"/node_modules/favela-peer/node_modules/hyperbee2/package.json","b4a":"/node_modules/favela-peer/node_modules/b4a/index.js","hypercore":"/node_modules/favela-peer/node_modules/hypercore/index.js","streamx":"/node_modules/favela-peer/node_modules/streamx/index.js"},"/node_modules/favela-peer/node_modules/hyperbee2/lib/session-config.js":{"#package":"/node_modules/favela-peer/node_modules/hyperbee2/package.json"},"/node_modules/favela-peer/node_modules/hyperbee2/lib/tree.js":{"#package":"/node_modules/favela-peer/node_modules/hyperbee2/package.json","./compression.js":"/node_modules/favela-peer/node_modules/hyperbee2/lib/compression.js","b4a":"/node_modules/favela-peer/node_modules/b4a/index.js"},"/node_modules/favela-peer/node_modules/hyperbee2/lib/write.js":{"#package":"/node_modules/favela-peer/node_modules/hyperbee2/package.json","./compat.js":"/node_modules/favela-peer/node_modules/hyperbee2/lib/compat.js","./compression.js":"/node_modules/favela-peer/node_modules/hyperbee2/lib/compression.js","./encoding.js":"/node_modules/favela-peer/node_modules/hyperbee2/lib/encoding.js","./inflate.js":"/node_modules/favela-peer/node_modules/hyperbee2/lib/inflate.js","./tree.js":"/node_modules/favela-peer/node_modules/hyperbee2/lib/tree.js","b4a":"/node_modules/favela-peer/node_modules/b4a/index.js"},"/node_modules/favela-peer/node_modules/hyperbee2/package.json":{},"/node_modules/favela-peer/node_modules/hyperbee2/spec/hyperschema/index.js":{"#package":"/node_modules/favela-peer/node_modules/hyperbee2/package.json","hyperschema/runtime":"/node_modules/favela-peer/node_modules/hyperschema/runtime.cjs"},"/node_modules/favela-peer/node_modules/hypercore-crypto/index.js":{"#package":"/node_modules/favela-peer/node_modules/hypercore-crypto/package.json","b4a":"/node_modules/favela-peer/node_modules/b4a/index.js","compact-encoding":"/node_modules/favela-peer/node_modules/compact-encoding/index.js","sodium-universal":"/node_modules/favela-peer/node_modules/sodium-universal/index.js"},"/node_modules/favela-peer/node_modules/hypercore-crypto/package.json":{},"/node_modules/favela-peer/node_modules/hypercore-errors/index.js":{"#package":"/node_modules/favela-peer/node_modules/hypercore-errors/package.json","hypercore-id-encoding":"/node_modules/favela-peer/node_modules/hypercore-id-encoding/index.js"},"/node_modules/favela-peer/node_modules/hypercore-errors/package.json":{},"/node_modules/favela-peer/node_modules/hypercore-id-encoding/index.js":{"#package":"/node_modules/favela-peer/node_modules/hypercore-id-encoding/package.json","b4a":"/node_modules/favela-peer/node_modules/b4a/index.js","z32":"/node_modules/favela-peer/node_modules/z32/index.js"},"/node_modules/favela-peer/node_modules/hypercore-id-encoding/package.json":{},"/node_modules/favela-peer/node_modules/hypercore-storage/encoding/external.js":{"#package":"/node_modules/favela-peer/node_modules/hypercore-storage/package.json"},"/node_modules/favela-peer/node_modules/hypercore-storage/encoding/spec/hyperschema/index.js":{"#package":"/node_modules/favela-peer/node_modules/hypercore-storage/package.json","../../external.js":"/node_modules/favela-peer/node_modules/hypercore-storage/encoding/external.js","hyperschema/runtime":"/node_modules/favela-peer/node_modules/hyperschema/runtime.cjs"},"/node_modules/favela-peer/node_modules/hypercore-storage/index.js":{"#package":"/node_modules/favela-peer/node_modules/hypercore-storage/package.json","./lib/keys.js":"/node_modules/favela-peer/node_modules/hypercore-storage/lib/keys.js","./lib/streams.js":"/node_modules/favela-peer/node_modules/hypercore-storage/lib/streams.js","./lib/tx.js":"/node_modules/favela-peer/node_modules/hypercore-storage/lib/tx.js","./lib/view.js":"/node_modules/favela-peer/node_modules/hypercore-storage/lib/view.js","./migrations/0":"/node_modules/favela-peer/node_modules/hypercore-storage/migrations/0/index.js","./migrations/1":"/node_modules/favela-peer/node_modules/hypercore-storage/migrations/1/index.js","device-file":"/node_modules/favela-peer/node_modules/device-file/index.js","fs":"/node_modules/favela-peer/node_modules/bare-fs/index.js","path":"/node_modules/favela-peer/node_modules/bare-path/index.js","resolve-reject-promise":"/node_modules/favela-peer/node_modules/resolve-reject-promise/index.js","rocksdb-native":"/node_modules/favela-peer/node_modules/rocksdb-native/index.js","scope-lock":"/node_modules/favela-peer/node_modules/scope-lock/index.js","xache":"/node_modules/favela-peer/node_modules/xache/index.js"},"/node_modules/favela-peer/node_modules/hypercore-storage/lib/block-dependency-stream.js":{"#package":"/node_modules/favela-peer/node_modules/hypercore-storage/package.json","./keys":"/node_modules/favela-peer/node_modules/hypercore-storage/lib/keys.js","streamx":"/node_modules/favela-peer/node_modules/streamx/index.js"},"/node_modules/favela-peer/node_modules/hypercore-storage/lib/close-error-stream.js":{"#package":"/node_modules/favela-peer/node_modules/hypercore-storage/package.json","streamx":"/node_modules/favela-peer/node_modules/streamx/index.js"},"/node_modules/favela-peer/node_modules/hypercore-storage/lib/keys.js":{"#package":"/node_modules/favela-peer/node_modules/hypercore-storage/package.json","b4a":"/node_modules/favela-peer/node_modules/b4a/index.js","compact-encoding":"/node_modules/favela-peer/node_modules/compact-encoding/index.js","index-encoder":"/node_modules/favela-peer/node_modules/index-encoder/index.js"},"/node_modules/favela-peer/node_modules/hypercore-storage/lib/streams.js":{"#package":"/node_modules/favela-peer/node_modules/hypercore-storage/package.json","../encoding/spec/hyperschema":"/node_modules/favela-peer/node_modules/hypercore-storage/encoding/spec/hyperschema/index.js","./block-dependency-stream.js":"/node_modules/favela-peer/node_modules/hypercore-storage/lib/block-dependency-stream.js","./keys.js":"/node_modules/favela-peer/node_modules/hypercore-storage/lib/keys.js","b4a":"/node_modules/favela-peer/node_modules/b4a/index.js"},"/node_modules/favela-peer/node_modules/hypercore-storage/lib/tx.js":{"#package":"/node_modules/favela-peer/node_modules/hypercore-storage/package.json","../encoding/spec/hyperschema":"/node_modules/favela-peer/node_modules/hypercore-storage/encoding/spec/hyperschema/index.js","./keys.js":"/node_modules/favela-peer/node_modules/hypercore-storage/lib/keys.js","./view.js":"/node_modules/favela-peer/node_modules/hypercore-storage/lib/view.js","b4a":"/node_modules/favela-peer/node_modules/b4a/index.js","compact-encoding":"/node_modules/favela-peer/node_modules/compact-encoding/index.js","flat-tree":"/node_modules/favela-peer/node_modules/flat-tree/index.js"},"/node_modules/favela-peer/node_modules/hypercore-storage/lib/view.js":{"#package":"/node_modules/favela-peer/node_modules/hypercore-storage/package.json","./close-error-stream.js":"/node_modules/favela-peer/node_modules/hypercore-storage/lib/close-error-stream.js","b4a":"/node_modules/favela-peer/node_modules/b4a/index.js","streamx":"/node_modules/favela-peer/node_modules/streamx/index.js"},"/node_modules/favela-peer/node_modules/hypercore-storage/migrations/0/index.js":{"#package":"/node_modules/favela-peer/node_modules/hypercore-storage/package.json","../../lib/tx.js":"/node_modules/favela-peer/node_modules/hypercore-storage/lib/tx.js","../../lib/view.js":"/node_modules/favela-peer/node_modules/hypercore-storage/lib/view.js","./messages.js":"/node_modules/favela-peer/node_modules/hypercore-storage/migrations/0/messages.js","b4a":"/node_modules/favela-peer/node_modules/b4a/index.js","compact-encoding":"/node_modules/favela-peer/node_modules/compact-encoding/index.js","flat-tree":"/node_modules/favela-peer/node_modules/flat-tree/index.js","fs":"/node_modules/favela-peer/node_modules/bare-fs/index.js","hypercore-crypto":"/node_modules/favela-peer/node_modules/hypercore-crypto/index.js","path":"/node_modules/favela-peer/node_modules/bare-path/index.js","streamx":"/node_modules/favela-peer/node_modules/streamx/index.js"},"/node_modules/favela-peer/node_modules/hypercore-storage/migrations/0/messages.js":{"#package":"/node_modules/favela-peer/node_modules/hypercore-storage/package.json","b4a":"/node_modules/favela-peer/node_modules/b4a/index.js","compact-encoding":"/node_modules/favela-peer/node_modules/compact-encoding/index.js"},"/node_modules/favela-peer/node_modules/hypercore-storage/migrations/1/index.js":{"#package":"/node_modules/favela-peer/node_modules/hypercore-storage/package.json","../../encoding/spec/hyperschema":"/node_modules/favela-peer/node_modules/hypercore-storage/encoding/spec/hyperschema/index.js","../../lib/keys.js":"/node_modules/favela-peer/node_modules/hypercore-storage/lib/keys.js","../../lib/tx.js":"/node_modules/favela-peer/node_modules/hypercore-storage/lib/tx.js","../../lib/view.js":"/node_modules/favela-peer/node_modules/hypercore-storage/lib/view.js","compact-encoding":"/node_modules/favela-peer/node_modules/compact-encoding/index.js"},"/node_modules/favela-peer/node_modules/hypercore-storage/package.json":{},"/node_modules/favela-peer/node_modules/hypercore/index.js":{"#package":"/node_modules/favela-peer/node_modules/hypercore/package.json","./lib/caps":"/node_modules/favela-peer/node_modules/hypercore/lib/caps.js","./lib/core":"/node_modules/favela-peer/node_modules/hypercore/lib/core.js","./lib/default-encryption":"/node_modules/favela-peer/node_modules/hypercore/lib/default-encryption.js","./lib/download":"/node_modules/favela-peer/node_modules/hypercore/lib/download.js","./lib/feature-flags":"/node_modules/favela-peer/node_modules/hypercore/lib/feature-flags.js","./lib/fully-remote-proof":"/node_modules/favela-peer/node_modules/hypercore/lib/fully-remote-proof.js","./lib/info":"/node_modules/favela-peer/node_modules/hypercore/lib/info.js","./lib/inspect":"/node_modules/favela-peer/node_modules/hypercore/lib/inspect.js","./lib/mark-bitfield":"/node_modules/favela-peer/node_modules/hypercore/lib/mark-bitfield.js","./lib/merkle-tree":"/node_modules/favela-peer/node_modules/hypercore/lib/merkle-tree.js","./lib/replicator":"/node_modules/favela-peer/node_modules/hypercore/lib/replicator.js","./lib/streams":"/node_modules/favela-peer/node_modules/hypercore/lib/streams.js","./lib/verifier":"/node_modules/favela-peer/node_modules/hypercore/lib/verifier.js","./lib/wants":"/node_modules/favela-peer/node_modules/hypercore/lib/wants.js","@hyperswarm/secret-stream":"/node_modules/favela-peer/node_modules/@hyperswarm/secret-stream/index.js","b4a":"/node_modules/favela-peer/node_modules/b4a/index.js","compact-encoding":"/node_modules/favela-peer/node_modules/compact-encoding/index.js","events":"/node_modules/favela-peer/node_modules/bare-events/index.js","flat-tree":"/node_modules/favela-peer/node_modules/flat-tree/index.js","hypercore-crypto":"/node_modules/favela-peer/node_modules/hypercore-crypto/index.js","hypercore-errors":"/node_modules/favela-peer/node_modules/hypercore-errors/index.js","hypercore-id-encoding":"/node_modules/favela-peer/node_modules/hypercore-id-encoding/index.js","hypercore-storage":"/node_modules/favela-peer/node_modules/hypercore-storage/index.js","is-options":"/node_modules/favela-peer/node_modules/is-options/index.js","nanoassert":"/node_modules/favela-peer/node_modules/nanoassert/index.js","protomux":"/node_modules/favela-peer/node_modules/protomux/index.js","safety-catch":"/node_modules/favela-peer/node_modules/safety-catch/index.js","unslab":"/node_modules/favela-peer/node_modules/unslab/index.js"},"/node_modules/favela-peer/node_modules/hypercore/lib/audit.js":{"#package":"/node_modules/favela-peer/node_modules/hypercore/package.json","./merkle-tree":"/node_modules/favela-peer/node_modules/hypercore/lib/merkle-tree.js","b4a":"/node_modules/favela-peer/node_modules/b4a/index.js","flat-tree":"/node_modules/favela-peer/node_modules/flat-tree/index.js","hypercore-crypto":"/node_modules/favela-peer/node_modules/hypercore-crypto/index.js"},"/node_modules/favela-peer/node_modules/hypercore/lib/bit-interlude.js":{"#package":"/node_modules/favela-peer/node_modules/hypercore/package.json","./compat":"/node_modules/favela-peer/node_modules/hypercore/lib/compat.js","b4a":"/node_modules/favela-peer/node_modules/b4a/index.js"},"/node_modules/favela-peer/node_modules/hypercore/lib/bitfield.js":{"#package":"/node_modules/favela-peer/node_modules/hypercore/package.json","./compat":"/node_modules/favela-peer/node_modules/hypercore/lib/compat.js","b4a":"/node_modules/favela-peer/node_modules/b4a/index.js","big-sparse-array":"/node_modules/favela-peer/node_modules/big-sparse-array/index.js"},"/node_modules/favela-peer/node_modules/hypercore/lib/caps.js":{"#package":"/node_modules/favela-peer/node_modules/hypercore/package.json","b4a":"/node_modules/favela-peer/node_modules/b4a/index.js","compact-encoding":"/node_modules/favela-peer/node_modules/compact-encoding/index.js","hypercore-crypto":"/node_modules/favela-peer/node_modules/hypercore-crypto/index.js","sodium-universal":"/node_modules/favela-peer/node_modules/sodium-universal/index.js"},"/node_modules/favela-peer/node_modules/hypercore/lib/compat.js":{"#package":"/node_modules/favela-peer/node_modules/hypercore/package.json","quickbit-universal":"/node_modules/favela-peer/node_modules/quickbit-universal/index.js","quickbit-universal/fallback":"/node_modules/favela-peer/node_modules/quickbit-universal/fallback.js"},"/node_modules/favela-peer/node_modules/hypercore/lib/copy-prologue.js":{"#package":"/node_modules/favela-peer/node_modules/hypercore/package.json","./bitfield":"/node_modules/favela-peer/node_modules/hypercore/lib/bitfield.js","b4a":"/node_modules/favela-peer/node_modules/b4a/index.js","flat-tree":"/node_modules/favela-peer/node_modules/flat-tree/index.js","hypercore-crypto":"/node_modules/favela-peer/node_modules/hypercore-crypto/index.js","quickbit-universal":"/node_modules/favela-peer/node_modules/quickbit-universal/index.js"},"/node_modules/favela-peer/node_modules/hypercore/lib/core.js":{"#package":"/node_modules/favela-peer/node_modules/hypercore/package.json","./audit":"/node_modules/favela-peer/node_modules/hypercore/lib/audit.js","./bit-interlude":"/node_modules/favela-peer/node_modules/hypercore/lib/bit-interlude.js","./bitfield":"/node_modules/favela-peer/node_modules/hypercore/lib/bitfield.js","./copy-prologue":"/node_modules/favela-peer/node_modules/hypercore/lib/copy-prologue.js","./merkle-tree":"/node_modules/favela-peer/node_modules/hypercore/lib/merkle-tree.js","./mutex":"/node_modules/favela-peer/node_modules/hypercore/lib/mutex.js","./remote-bitfield":"/node_modules/favela-peer/node_modules/hypercore/lib/remote-bitfield.js","./replicator":"/node_modules/favela-peer/node_modules/hypercore/lib/replicator.js","./session-state":"/node_modules/favela-peer/node_modules/hypercore/lib/session-state.js","./verifier":"/node_modules/favela-peer/node_modules/hypercore/lib/verifier.js","b4a":"/node_modules/favela-peer/node_modules/b4a/index.js","hypercore-crypto":"/node_modules/favela-peer/node_modules/hypercore-crypto/index.js","hypercore-errors":"/node_modules/favela-peer/node_modules/hypercore-errors/index.js","unslab":"/node_modules/favela-peer/node_modules/unslab/index.js","z32":"/node_modules/favela-peer/node_modules/z32/index.js"},"/node_modules/favela-peer/node_modules/hypercore/lib/default-encryption.js":{"#package":"/node_modules/favela-peer/node_modules/hypercore/package.json","./caps":"/node_modules/favela-peer/node_modules/hypercore/lib/caps.js","b4a":"/node_modules/favela-peer/node_modules/b4a/index.js","compact-encoding":"/node_modules/favela-peer/node_modules/compact-encoding/index.js","sodium-universal":"/node_modules/favela-peer/node_modules/sodium-universal/index.js"},"/node_modules/favela-peer/node_modules/hypercore/lib/download.js":{"#package":"/node_modules/favela-peer/node_modules/hypercore/package.json"},"/node_modules/favela-peer/node_modules/hypercore/lib/feature-flags.js":{"#package":"/node_modules/favela-peer/node_modules/hypercore/package.json"},"/node_modules/favela-peer/node_modules/hypercore/lib/fully-remote-proof.js":{"#package":"/node_modules/favela-peer/node_modules/hypercore/package.json","./merkle-tree.js":"/node_modules/favela-peer/node_modules/hypercore/lib/merkle-tree.js","./messages.js":"/node_modules/favela-peer/node_modules/hypercore/lib/messages.js","b4a":"/node_modules/favela-peer/node_modules/b4a/index.js","compact-encoding":"/node_modules/favela-peer/node_modules/compact-encoding/index.js","flat-tree":"/node_modules/favela-peer/node_modules/flat-tree/index.js","hypercore-crypto":"/node_modules/favela-peer/node_modules/hypercore-crypto/index.js"},"/node_modules/favela-peer/node_modules/hypercore/lib/hotswap-queue.js":{"#package":"/node_modules/favela-peer/node_modules/hypercore/package.json"},"/node_modules/favela-peer/node_modules/hypercore/lib/info.js":{"#package":"/node_modules/favela-peer/node_modules/hypercore/package.json"},"/node_modules/favela-peer/node_modules/hypercore/lib/inspect.js":{"#package":"/node_modules/favela-peer/node_modules/hypercore/package.json","b4a":"/node_modules/favela-peer/node_modules/b4a/index.js"},"/node_modules/favela-peer/node_modules/hypercore/lib/mark-bitfield.js":{"#package":"/node_modules/favela-peer/node_modules/hypercore/package.json","./compat":"/node_modules/favela-peer/node_modules/hypercore/lib/compat.js","./mutex":"/node_modules/favela-peer/node_modules/hypercore/lib/mutex.js","b4a":"/node_modules/favela-peer/node_modules/b4a/index.js","big-sparse-array":"/node_modules/favela-peer/node_modules/big-sparse-array/index.js","streamx":"/node_modules/favela-peer/node_modules/streamx/index.js"},"/node_modules/favela-peer/node_modules/hypercore/lib/merkle-tree.js":{"#package":"/node_modules/favela-peer/node_modules/hypercore/package.json","./caps":"/node_modules/favela-peer/node_modules/hypercore/lib/caps.js","b4a":"/node_modules/favela-peer/node_modules/b4a/index.js","flat-tree":"/node_modules/favela-peer/node_modules/flat-tree/index.js","hypercore-crypto":"/node_modules/favela-peer/node_modules/hypercore-crypto/index.js","hypercore-errors":"/node_modules/favela-peer/node_modules/hypercore-errors/index.js","unslab":"/node_modules/favela-peer/node_modules/unslab/index.js"},"/node_modules/favela-peer/node_modules/hypercore/lib/messages.js":{"#package":"/node_modules/favela-peer/node_modules/hypercore/package.json","./caps":"/node_modules/favela-peer/node_modules/hypercore/lib/caps.js","compact-encoding":"/node_modules/favela-peer/node_modules/compact-encoding/index.js","hypercore-errors":"/node_modules/favela-peer/node_modules/hypercore-errors/index.js","unslab":"/node_modules/favela-peer/node_modules/unslab/index.js"},"/node_modules/favela-peer/node_modules/hypercore/lib/multisig.js":{"#package":"/node_modules/favela-peer/node_modules/hypercore/package.json","./merkle-tree":"/node_modules/favela-peer/node_modules/hypercore/lib/merkle-tree.js","./messages":"/node_modules/favela-peer/node_modules/hypercore/lib/messages.js","b4a":"/node_modules/favela-peer/node_modules/b4a/index.js","compact-encoding":"/node_modules/favela-peer/node_modules/compact-encoding/index.js","flat-tree":"/node_modules/favela-peer/node_modules/flat-tree/index.js"},"/node_modules/favela-peer/node_modules/hypercore/lib/mutex.js":{"#package":"/node_modules/favela-peer/node_modules/hypercore/package.json"},"/node_modules/favela-peer/node_modules/hypercore/lib/receiver-queue.js":{"#package":"/node_modules/favela-peer/node_modules/hypercore/package.json","fast-fifo":"/node_modules/favela-peer/node_modules/fast-fifo/index.js"},"/node_modules/favela-peer/node_modules/hypercore/lib/remote-bitfield.js":{"#package":"/node_modules/favela-peer/node_modules/hypercore/package.json","./compat":"/node_modules/favela-peer/node_modules/hypercore/lib/compat.js","big-sparse-array":"/node_modules/favela-peer/node_modules/big-sparse-array/index.js"},"/node_modules/favela-peer/node_modules/hypercore/lib/replicator.js":{"#package":"/node_modules/favela-peer/node_modules/hypercore/package.json","./caps":"/node_modules/favela-peer/node_modules/hypercore/lib/caps.js","./hotswap-queue":"/node_modules/favela-peer/node_modules/hypercore/lib/hotswap-queue.js","./merkle-tree":"/node_modules/favela-peer/node_modules/hypercore/lib/merkle-tree.js","./messages":"/node_modules/favela-peer/node_modules/hypercore/lib/messages.js","./mutex":"/node_modules/favela-peer/node_modules/hypercore/lib/mutex.js","./receiver-queue":"/node_modules/favela-peer/node_modules/hypercore/lib/receiver-queue.js","./remote-bitfield":"/node_modules/favela-peer/node_modules/hypercore/lib/remote-bitfield.js","./wants":"/node_modules/favela-peer/node_modules/hypercore/lib/wants.js","b4a":"/node_modules/favela-peer/node_modules/b4a/index.js","flat-tree":"/node_modules/favela-peer/node_modules/flat-tree/index.js","hypercore-errors":"/node_modules/favela-peer/node_modules/hypercore-errors/index.js","random-array-iterator":"/node_modules/favela-peer/node_modules/random-array-iterator/index.js","safety-catch":"/node_modules/favela-peer/node_modules/safety-catch/index.js"},"/node_modules/favela-peer/node_modules/hypercore/lib/session-state.js":{"#package":"/node_modules/favela-peer/node_modules/hypercore/package.json","./bitfield":"/node_modules/favela-peer/node_modules/hypercore/lib/bitfield.js","./merkle-tree":"/node_modules/favela-peer/node_modules/hypercore/lib/merkle-tree.js","./mutex":"/node_modules/favela-peer/node_modules/hypercore/lib/mutex.js","b4a":"/node_modules/favela-peer/node_modules/b4a/index.js","flat-tree":"/node_modules/favela-peer/node_modules/flat-tree/index.js","hypercore-crypto":"/node_modules/favela-peer/node_modules/hypercore-crypto/index.js","hypercore-errors":"/node_modules/favela-peer/node_modules/hypercore-errors/index.js","hypercore-storage":"/node_modules/favela-peer/node_modules/hypercore-storage/index.js","nanoassert":"/node_modules/favela-peer/node_modules/nanoassert/index.js","quickbit-universal":"/node_modules/favela-peer/node_modules/quickbit-universal/index.js"},"/node_modules/favela-peer/node_modules/hypercore/lib/streams.js":{"#package":"/node_modules/favela-peer/node_modules/hypercore/package.json","streamx":"/node_modules/favela-peer/node_modules/streamx/index.js"},"/node_modules/favela-peer/node_modules/hypercore/lib/verifier.js":{"#package":"/node_modules/favela-peer/node_modules/hypercore/package.json","./caps":"/node_modules/favela-peer/node_modules/hypercore/lib/caps.js","./messages":"/node_modules/favela-peer/node_modules/hypercore/lib/messages.js","./multisig":"/node_modules/favela-peer/node_modules/hypercore/lib/multisig.js","b4a":"/node_modules/favela-peer/node_modules/b4a/index.js","compact-encoding":"/node_modules/favela-peer/node_modules/compact-encoding/index.js","flat-tree":"/node_modules/favela-peer/node_modules/flat-tree/index.js","hypercore-crypto":"/node_modules/favela-peer/node_modules/hypercore-crypto/index.js","hypercore-errors":"/node_modules/favela-peer/node_modules/hypercore-errors/index.js","unslab":"/node_modules/favela-peer/node_modules/unslab/index.js"},"/node_modules/favela-peer/node_modules/hypercore/lib/wants.js":{"#package":"/node_modules/favela-peer/node_modules/hypercore/package.json"},"/node_modules/favela-peer/node_modules/hypercore/package.json":{},"/node_modules/favela-peer/node_modules/hyperdht-address/index.js":{"#package":"/node_modules/favela-peer/node_modules/hyperdht-address/package.json","./spec/hyperschema":"/node_modules/favela-peer/node_modules/hyperdht-address/spec/hyperschema/index.js","compact-encoding":"/node_modules/favela-peer/node_modules/compact-encoding/index.js"},"/node_modules/favela-peer/node_modules/hyperdht-address/package.json":{},"/node_modules/favela-peer/node_modules/hyperdht-address/spec/hyperschema/index.js":{"#package":"/node_modules/favela-peer/node_modules/hyperdht-address/package.json","hyperschema/runtime":"/node_modules/favela-peer/node_modules/hyperschema/runtime.cjs"},"/node_modules/favela-peer/node_modules/hyperdht/index.js":{"#package":"/node_modules/favela-peer/node_modules/hyperdht/package.json","./lib/connect":"/node_modules/favela-peer/node_modules/hyperdht/lib/connect.js","./lib/connection-pool":"/node_modules/favela-peer/node_modules/hyperdht/lib/connection-pool.js","./lib/constants":"/node_modules/favela-peer/node_modules/hyperdht/lib/constants.js","./lib/crypto":"/node_modules/favela-peer/node_modules/hyperdht/lib/crypto.js","./lib/errors":"/node_modules/favela-peer/node_modules/hyperdht/lib/errors.js","./lib/messages":"/node_modules/favela-peer/node_modules/hyperdht/lib/messages.js","./lib/persistent":"/node_modules/favela-peer/node_modules/hyperdht/lib/persistent.js","./lib/raw-stream-set":"/node_modules/favela-peer/node_modules/hyperdht/lib/raw-stream-set.js","./lib/router":"/node_modules/favela-peer/node_modules/hyperdht/lib/router.js","./lib/server":"/node_modules/favela-peer/node_modules/hyperdht/lib/server.js","./lib/socket-pool":"/node_modules/favela-peer/node_modules/hyperdht/lib/socket-pool.js","b4a":"/node_modules/favela-peer/node_modules/b4a/index.js","compact-encoding":"/node_modules/favela-peer/node_modules/compact-encoding/index.js","dht-rpc":"/node_modules/favela-peer/node_modules/dht-rpc/index.js","safety-catch":"/node_modules/favela-peer/node_modules/safety-catch/index.js","sodium-universal":"/node_modules/favela-peer/node_modules/sodium-universal/index.js","xache":"/node_modules/favela-peer/node_modules/xache/index.js"},"/node_modules/favela-peer/node_modules/hyperdht/lib/announcer.js":{"#package":"/node_modules/favela-peer/node_modules/hyperdht/package.json","./constants":"/node_modules/favela-peer/node_modules/hyperdht/lib/constants.js","./encode":"/node_modules/favela-peer/node_modules/hyperdht/lib/encode.js","./messages":"/node_modules/favela-peer/node_modules/hyperdht/lib/messages.js","./persistent":"/node_modules/favela-peer/node_modules/hyperdht/lib/persistent.js","./sleeper":"/node_modules/favela-peer/node_modules/hyperdht/lib/sleeper.js","compact-encoding":"/node_modules/favela-peer/node_modules/compact-encoding/index.js","safety-catch":"/node_modules/favela-peer/node_modules/safety-catch/index.js","signal-promise":"/node_modules/favela-peer/node_modules/signal-promise/index.js"},"/node_modules/favela-peer/node_modules/hyperdht/lib/connect.js":{"#package":"/node_modules/favela-peer/node_modules/hyperdht/package.json","./constants":"/node_modules/favela-peer/node_modules/hyperdht/lib/constants.js","./crypto":"/node_modules/favela-peer/node_modules/hyperdht/lib/crypto.js","./errors":"/node_modules/favela-peer/node_modules/hyperdht/lib/errors.js","./holepuncher":"/node_modules/favela-peer/node_modules/hyperdht/lib/holepuncher.js","./noise-wrap":"/node_modules/favela-peer/node_modules/hyperdht/lib/noise-wrap.js","./relay-connection":"/node_modules/favela-peer/node_modules/hyperdht/lib/relay-connection.js","./secure-payload":"/node_modules/favela-peer/node_modules/hyperdht/lib/secure-payload.js","./semaphore":"/node_modules/favela-peer/node_modules/hyperdht/lib/semaphore.js","./sleeper":"/node_modules/favela-peer/node_modules/hyperdht/lib/sleeper.js","@hyperswarm/secret-stream":"/node_modules/favela-peer/node_modules/@hyperswarm/secret-stream/index.js","b4a":"/node_modules/favela-peer/node_modules/b4a/index.js","blind-relay":"/node_modules/favela-peer/node_modules/blind-relay/index.js","bogon":"/node_modules/favela-peer/node_modules/bogon/index.js","hypercore-id-encoding":"/node_modules/favela-peer/node_modules/hypercore-id-encoding/index.js","hyperdht-address":"/node_modules/favela-peer/node_modules/hyperdht-address/index.js","safety-catch":"/node_modules/favela-peer/node_modules/safety-catch/index.js","unslab":"/node_modules/favela-peer/node_modules/unslab/index.js"},"/node_modules/favela-peer/node_modules/hyperdht/lib/connection-pool.js":{"#package":"/node_modules/favela-peer/node_modules/hyperdht/package.json","./errors":"/node_modules/favela-peer/node_modules/hyperdht/lib/errors.js","b4a":"/node_modules/favela-peer/node_modules/b4a/index.js","events":"/node_modules/favela-peer/node_modules/bare-events/index.js"},"/node_modules/favela-peer/node_modules/hyperdht/lib/constants.js":{"#package":"/node_modules/favela-peer/node_modules/hyperdht/package.json","hypercore-crypto":"/node_modules/favela-peer/node_modules/hypercore-crypto/index.js"},"/node_modules/favela-peer/node_modules/hyperdht/lib/crypto.js":{"#package":"/node_modules/favela-peer/node_modules/hyperdht/package.json","b4a":"/node_modules/favela-peer/node_modules/b4a/index.js","sodium-universal":"/node_modules/favela-peer/node_modules/sodium-universal/index.js"},"/node_modules/favela-peer/node_modules/hyperdht/lib/encode.js":{"#package":"/node_modules/favela-peer/node_modules/hyperdht/package.json","b4a":"/node_modules/favela-peer/node_modules/b4a/index.js","compact-encoding":"/node_modules/favela-peer/node_modules/compact-encoding/index.js"},"/node_modules/favela-peer/node_modules/hyperdht/lib/errors.js":{"#package":"/node_modules/favela-peer/node_modules/hyperdht/package.json"},"/node_modules/favela-peer/node_modules/hyperdht/lib/holepuncher.js":{"#package":"/node_modules/favela-peer/node_modules/hyperdht/package.json","./constants":"/node_modules/favela-peer/node_modules/hyperdht/lib/constants.js","./nat":"/node_modules/favela-peer/node_modules/hyperdht/lib/nat.js","./sleeper":"/node_modules/favela-peer/node_modules/hyperdht/lib/sleeper.js","b4a":"/node_modules/favela-peer/node_modules/b4a/index.js"},"/node_modules/favela-peer/node_modules/hyperdht/lib/messages.js":{"#package":"/node_modules/favela-peer/node_modules/hyperdht/package.json","compact-encoding":"/node_modules/favela-peer/node_modules/compact-encoding/index.js"},"/node_modules/favela-peer/node_modules/hyperdht/lib/nat.js":{"#package":"/node_modules/favela-peer/node_modules/hyperdht/package.json","../lib/constants":"/node_modules/favela-peer/node_modules/hyperdht/lib/constants.js"},"/node_modules/favela-peer/node_modules/hyperdht/lib/noise-wrap.js":{"#package":"/node_modules/favela-peer/node_modules/hyperdht/package.json","./constants":"/node_modules/favela-peer/node_modules/hyperdht/lib/constants.js","./errors":"/node_modules/favela-peer/node_modules/hyperdht/lib/errors.js","./messages":"/node_modules/favela-peer/node_modules/hyperdht/lib/messages.js","@hyperswarm/secret-stream":"/node_modules/favela-peer/node_modules/@hyperswarm/secret-stream/index.js","b4a":"/node_modules/favela-peer/node_modules/b4a/index.js","compact-encoding":"/node_modules/favela-peer/node_modules/compact-encoding/index.js","noise-curve-ed":"/node_modules/favela-peer/node_modules/noise-curve-ed/index.js","noise-handshake":"/node_modules/favela-peer/node_modules/noise-handshake/noise.js","sodium-universal":"/node_modules/favela-peer/node_modules/sodium-universal/index.js"},"/node_modules/favela-peer/node_modules/hyperdht/lib/persistent.js":{"#package":"/node_modules/favela-peer/node_modules/hyperdht/package.json","./constants":"/node_modules/favela-peer/node_modules/hyperdht/lib/constants.js","./encode":"/node_modules/favela-peer/node_modules/hyperdht/lib/encode.js","./messages":"/node_modules/favela-peer/node_modules/hyperdht/lib/messages.js","b4a":"/node_modules/favela-peer/node_modules/b4a/index.js","compact-encoding":"/node_modules/favela-peer/node_modules/compact-encoding/index.js","record-cache":"/node_modules/favela-peer/node_modules/record-cache/index.js","sodium-universal":"/node_modules/favela-peer/node_modules/sodium-universal/index.js","unslab":"/node_modules/favela-peer/node_modules/unslab/index.js","xache":"/node_modules/favela-peer/node_modules/xache/index.js"},"/node_modules/favela-peer/node_modules/hyperdht/lib/raw-stream-set.js":{"#package":"/node_modules/favela-peer/node_modules/hyperdht/package.json"},"/node_modules/favela-peer/node_modules/hyperdht/lib/relay-connection.js":{"#package":"/node_modules/favela-peer/node_modules/hyperdht/package.json","b4a":"/node_modules/favela-peer/node_modules/b4a/index.js","safety-catch":"/node_modules/favela-peer/node_modules/safety-catch/index.js"},"/node_modules/favela-peer/node_modules/hyperdht/lib/router.js":{"#package":"/node_modules/favela-peer/node_modules/hyperdht/package.json","./constants":"/node_modules/favela-peer/node_modules/hyperdht/lib/constants.js","./errors":"/node_modules/favela-peer/node_modules/hyperdht/lib/errors.js","./messages":"/node_modules/favela-peer/node_modules/hyperdht/lib/messages.js","b4a":"/node_modules/favela-peer/node_modules/b4a/index.js","compact-encoding":"/node_modules/favela-peer/node_modules/compact-encoding/index.js","safety-catch":"/node_modules/favela-peer/node_modules/safety-catch/index.js","xache":"/node_modules/favela-peer/node_modules/xache/index.js"},"/node_modules/favela-peer/node_modules/hyperdht/lib/secure-payload.js":{"#package":"/node_modules/favela-peer/node_modules/hyperdht/package.json","./messages":"/node_modules/favela-peer/node_modules/hyperdht/lib/messages.js","b4a":"/node_modules/favela-peer/node_modules/b4a/index.js","sodium-universal":"/node_modules/favela-peer/node_modules/sodium-universal/index.js"},"/node_modules/favela-peer/node_modules/hyperdht/lib/semaphore.js":{"#package":"/node_modules/favela-peer/node_modules/hyperdht/package.json"},"/node_modules/favela-peer/node_modules/hyperdht/lib/server.js":{"#package":"/node_modules/favela-peer/node_modules/hyperdht/package.json","./announcer":"/node_modules/favela-peer/node_modules/hyperdht/lib/announcer.js","./constants":"/node_modules/favela-peer/node_modules/hyperdht/lib/constants.js","./crypto":"/node_modules/favela-peer/node_modules/hyperdht/lib/crypto.js","./errors":"/node_modules/favela-peer/node_modules/hyperdht/lib/errors.js","./holepuncher":"/node_modules/favela-peer/node_modules/hyperdht/lib/holepuncher.js","./noise-wrap":"/node_modules/favela-peer/node_modules/hyperdht/lib/noise-wrap.js","./relay-connection":"/node_modules/favela-peer/node_modules/hyperdht/lib/relay-connection.js","./secure-payload":"/node_modules/favela-peer/node_modules/hyperdht/lib/secure-payload.js","@hyperswarm/secret-stream":"/node_modules/favela-peer/node_modules/@hyperswarm/secret-stream/index.js","b4a":"/node_modules/favela-peer/node_modules/b4a/index.js","blind-relay":"/node_modules/favela-peer/node_modules/blind-relay/index.js","bogon":"/node_modules/favela-peer/node_modules/bogon/index.js","events":"/node_modules/favela-peer/node_modules/bare-events/index.js","safety-catch":"/node_modules/favela-peer/node_modules/safety-catch/index.js"},"/node_modules/favela-peer/node_modules/hyperdht/lib/sleeper.js":{"#package":"/node_modules/favela-peer/node_modules/hyperdht/package.json"},"/node_modules/favela-peer/node_modules/hyperdht/lib/socket-pool.js":{"#package":"/node_modules/favela-peer/node_modules/hyperdht/package.json","b4a":"/node_modules/favela-peer/node_modules/b4a/index.js"},"/node_modules/favela-peer/node_modules/hyperdht/package.json":{},"/node_modules/favela-peer/node_modules/hyperident/lib/crypto.js":{"#package":"/node_modules/favela-peer/node_modules/hyperident/package.json","node:crypto":"/node_modules/favela-peer/node_modules/hyperident/node_modules/bare-crypto/index.js","sodium-native":"/node_modules/favela-peer/node_modules/hyperident/node_modules/sodium-native/index.js","sodium-universal":"/node_modules/favela-peer/node_modules/hyperident/node_modules/sodium-universal/index.js"},"/node_modules/favela-peer/node_modules/hyperident/node_modules/b4a/index.js":{"#package":"/node_modules/favela-peer/node_modules/hyperident/node_modules/b4a/package.json"},"/node_modules/favela-peer/node_modules/hyperident/node_modules/b4a/package.json":{},"/node_modules/favela-peer/node_modules/hyperident/node_modules/bare-ansi-escapes/index.js":{"#package":"/node_modules/favela-peer/node_modules/hyperident/node_modules/bare-ansi-escapes/package.json"},"/node_modules/favela-peer/node_modules/hyperident/node_modules/bare-ansi-escapes/package.json":{},"/node_modules/favela-peer/node_modules/hyperident/node_modules/bare-assert/index.js":{"#package":"/node_modules/favela-peer/node_modules/hyperident/node_modules/bare-assert/package.json","bare-inspect":"/node_modules/favela-peer/node_modules/hyperident/node_modules/bare-inspect/index.js"},"/node_modules/favela-peer/node_modules/hyperident/node_modules/bare-assert/package.json":{},"/node_modules/favela-peer/node_modules/hyperident/node_modules/bare-crypto/binding.js":{"#package":"/node_modules/favela-peer/node_modules/hyperident/node_modules/bare-crypto/package.json",".":"linked:libbare-crypto.1.13.4.so"},"/node_modules/favela-peer/node_modules/hyperident/node_modules/bare-crypto/index.js":{"#package":"/node_modules/favela-peer/node_modules/hyperident/node_modules/bare-crypto/package.json","./lib/cipher":"/node_modules/favela-peer/node_modules/hyperident/node_modules/bare-crypto/lib/cipher.js","./lib/constants":"/node_modules/favela-peer/node_modules/hyperident/node_modules/bare-crypto/lib/constants.js","./lib/hash":"/node_modules/favela-peer/node_modules/hyperident/node_modules/bare-crypto/lib/hash.js","./lib/hmac":"/node_modules/favela-peer/node_modules/hyperident/node_modules/bare-crypto/lib/hmac.js","./lib/key":"/node_modules/favela-peer/node_modules/hyperident/node_modules/bare-crypto/lib/key.js","./lib/pbkdf2":"/node_modules/favela-peer/node_modules/hyperident/node_modules/bare-crypto/lib/pbkdf2.js","./lib/random":"/node_modules/favela-peer/node_modules/hyperident/node_modules/bare-crypto/lib/random.js","./lib/signature":"/node_modules/favela-peer/node_modules/hyperident/node_modules/bare-crypto/lib/signature.js","./web":"/node_modules/favela-peer/node_modules/hyperident/node_modules/bare-crypto/web.js"},"/node_modules/favela-peer/node_modules/hyperident/node_modules/bare-crypto/lib/cipher.js":{"#package":"/node_modules/favela-peer/node_modules/hyperident/node_modules/bare-crypto/package.json","../binding":"/node_modules/favela-peer/node_modules/hyperident/node_modules/bare-crypto/binding.js","./constants":"/node_modules/favela-peer/node_modules/hyperident/node_modules/bare-crypto/lib/constants.js","bare-assert":"/node_modules/favela-peer/node_modules/hyperident/node_modules/bare-assert/index.js","bare-stream":"/node_modules/favela-peer/node_modules/hyperident/node_modules/bare-stream/index.js"},"/node_modules/favela-peer/node_modules/hyperident/node_modules/bare-crypto/lib/constants.js":{"#package":"/node_modules/favela-peer/node_modules/hyperident/node_modules/bare-crypto/package.json","../binding":"/node_modules/favela-peer/node_modules/hyperident/node_modules/bare-crypto/binding.js","./errors":"/node_modules/favela-peer/node_modules/hyperident/node_modules/bare-crypto/lib/errors.js","bare-assert":"/node_modules/favela-peer/node_modules/hyperident/node_modules/bare-assert/index.js"},"/node_modules/favela-peer/node_modules/hyperident/node_modules/bare-crypto/lib/errors.js":{"#package":"/node_modules/favela-peer/node_modules/hyperident/node_modules/bare-crypto/package.json"},"/node_modules/favela-peer/node_modules/hyperident/node_modules/bare-crypto/lib/hash.js":{"#package":"/node_modules/favela-peer/node_modules/hyperident/node_modules/bare-crypto/package.json","../binding":"/node_modules/favela-peer/node_modules/hyperident/node_modules/bare-crypto/binding.js","./constants":"/node_modules/favela-peer/node_modules/hyperident/node_modules/bare-crypto/lib/constants.js","bare-assert":"/node_modules/favela-peer/node_modules/hyperident/node_modules/bare-assert/index.js","bare-stream":"/node_modules/favela-peer/node_modules/hyperident/node_modules/bare-stream/index.js"},"/node_modules/favela-peer/node_modules/hyperident/node_modules/bare-crypto/lib/hmac.js":{"#package":"/node_modules/favela-peer/node_modules/hyperident/node_modules/bare-crypto/package.json","../binding":"/node_modules/favela-peer/node_modules/hyperident/node_modules/bare-crypto/binding.js","./constants":"/node_modules/favela-peer/node_modules/hyperident/node_modules/bare-crypto/lib/constants.js","bare-assert":"/node_modules/favela-peer/node_modules/hyperident/node_modules/bare-assert/index.js","bare-stream":"/node_modules/favela-peer/node_modules/hyperident/node_modules/bare-stream/index.js"},"/node_modules/favela-peer/node_modules/hyperident/node_modules/bare-crypto/lib/key.js":{"#package":"/node_modules/favela-peer/node_modules/hyperident/node_modules/bare-crypto/package.json","../binding":"/node_modules/favela-peer/node_modules/hyperident/node_modules/bare-crypto/binding.js","./constants":"/node_modules/favela-peer/node_modules/hyperident/node_modules/bare-crypto/lib/constants.js"},"/node_modules/favela-peer/node_modules/hyperident/node_modules/bare-crypto/lib/pbkdf2.js":{"#package":"/node_modules/favela-peer/node_modules/hyperident/node_modules/bare-crypto/package.json","../binding":"/node_modules/favela-peer/node_modules/hyperident/node_modules/bare-crypto/binding.js","./constants":"/node_modules/favela-peer/node_modules/hyperident/node_modules/bare-crypto/lib/constants.js","bare-assert":"/node_modules/favela-peer/node_modules/hyperident/node_modules/bare-assert/index.js"},"/node_modules/favela-peer/node_modules/hyperident/node_modules/bare-crypto/lib/random.js":{"#package":"/node_modules/favela-peer/node_modules/hyperident/node_modules/bare-crypto/package.json","../binding":"/node_modules/favela-peer/node_modules/hyperident/node_modules/bare-crypto/binding.js","bare-assert":"/node_modules/favela-peer/node_modules/hyperident/node_modules/bare-assert/index.js"},"/node_modules/favela-peer/node_modules/hyperident/node_modules/bare-crypto/lib/signature.js":{"#package":"/node_modules/favela-peer/node_modules/hyperident/node_modules/bare-crypto/package.json","../binding":"/node_modules/favela-peer/node_modules/hyperident/node_modules/bare-crypto/binding.js","./constants":"/node_modules/favela-peer/node_modules/hyperident/node_modules/bare-crypto/lib/constants.js","./key":"/node_modules/favela-peer/node_modules/hyperident/node_modules/bare-crypto/lib/key.js","bare-assert":"/node_modules/favela-peer/node_modules/hyperident/node_modules/bare-assert/index.js"},"/node_modules/favela-peer/node_modules/hyperident/node_modules/bare-crypto/lib/web/algorithm/ed25519.js":{"#package":"/node_modules/favela-peer/node_modules/hyperident/node_modules/bare-crypto/package.json","../../..":"/node_modules/favela-peer/node_modules/hyperident/node_modules/bare-crypto/index.js","../../../binding":"/node_modules/favela-peer/node_modules/hyperident/node_modules/bare-crypto/binding.js","../../errors":"/node_modules/favela-peer/node_modules/hyperident/node_modules/bare-crypto/lib/errors.js","../../key":"/node_modules/favela-peer/node_modules/hyperident/node_modules/bare-crypto/lib/key.js","../crypto-key":"/node_modules/favela-peer/node_modules/hyperident/node_modules/bare-crypto/lib/web/crypto-key.js"},"/node_modules/favela-peer/node_modules/hyperident/node_modules/bare-crypto/lib/web/algorithm/hmac.js":{"#package":"/node_modules/favela-peer/node_modules/hyperident/node_modules/bare-crypto/package.json","../../..":"/node_modules/favela-peer/node_modules/hyperident/node_modules/bare-crypto/index.js","../../errors":"/node_modules/favela-peer/node_modules/hyperident/node_modules/bare-crypto/lib/errors.js","../crypto-key":"/node_modules/favela-peer/node_modules/hyperident/node_modules/bare-crypto/lib/web/crypto-key.js"},"/node_modules/favela-peer/node_modules/hyperident/node_modules/bare-crypto/lib/web/algorithm/pbkdf2.js":{"#package":"/node_modules/favela-peer/node_modules/hyperident/node_modules/bare-crypto/package.json","../../..":"/node_modules/favela-peer/node_modules/hyperident/node_modules/bare-crypto/index.js","../../errors":"/node_modules/favela-peer/node_modules/hyperident/node_modules/bare-crypto/lib/errors.js","../crypto-key":"/node_modules/favela-peer/node_modules/hyperident/node_modules/bare-crypto/lib/web/crypto-key.js"},"/node_modules/favela-peer/node_modules/hyperident/node_modules/bare-crypto/lib/web/algorithm/sha.js":{"#package":"/node_modules/favela-peer/node_modules/hyperident/node_modules/bare-crypto/package.json","../../..":"/node_modules/favela-peer/node_modules/hyperident/node_modules/bare-crypto/index.js"},"/node_modules/favela-peer/node_modules/hyperident/node_modules/bare-crypto/lib/web/crypto-key.js":{"#package":"/node_modules/favela-peer/node_modules/hyperident/node_modules/bare-crypto/package.json"},"/node_modules/favela-peer/node_modules/hyperident/node_modules/bare-crypto/package.json":{},"/node_modules/favela-peer/node_modules/hyperident/node_modules/bare-crypto/web.js":{"#package":"/node_modules/favela-peer/node_modules/hyperident/node_modules/bare-crypto/package.json",".":"/node_modules/favela-peer/node_modules/hyperident/node_modules/bare-crypto/index.js","./lib/errors":"/node_modules/favela-peer/node_modules/hyperident/node_modules/bare-crypto/lib/errors.js","./lib/web/algorithm/ed25519":"/node_modules/favela-peer/node_modules/hyperident/node_modules/bare-crypto/lib/web/algorithm/ed25519.js","./lib/web/algorithm/hmac":"/node_modules/favela-peer/node_modules/hyperident/node_modules/bare-crypto/lib/web/algorithm/hmac.js","./lib/web/algorithm/pbkdf2":"/node_modules/favela-peer/node_modules/hyperident/node_modules/bare-crypto/lib/web/algorithm/pbkdf2.js","./lib/web/algorithm/sha":"/node_modules/favela-peer/node_modules/hyperident/node_modules/bare-crypto/lib/web/algorithm/sha.js","./lib/web/crypto-key":"/node_modules/favela-peer/node_modules/hyperident/node_modules/bare-crypto/lib/web/crypto-key.js"},"/node_modules/favela-peer/node_modules/hyperident/node_modules/bare-events/index.js":{"#package":"/node_modules/favela-peer/node_modules/hyperident/node_modules/bare-events/package.json","./lib/errors":"/node_modules/favela-peer/node_modules/hyperident/node_modules/bare-events/lib/errors.js"},"/node_modules/favela-peer/node_modules/hyperident/node_modules/bare-events/lib/errors.js":{"#package":"/node_modules/favela-peer/node_modules/hyperident/node_modules/bare-events/package.json"},"/node_modules/favela-peer/node_modules/hyperident/node_modules/bare-events/package.json":{},"/node_modules/favela-peer/node_modules/hyperident/node_modules/bare-inspect/binding.js":{"#package":"/node_modules/favela-peer/node_modules/hyperident/node_modules/bare-inspect/package.json",".":"linked:libbare-inspect.3.1.4.so"},"/node_modules/favela-peer/node_modules/hyperident/node_modules/bare-inspect/index.js":{"#package":"/node_modules/favela-peer/node_modules/hyperident/node_modules/bare-inspect/package.json","./binding":"/node_modules/favela-peer/node_modules/hyperident/node_modules/bare-inspect/binding.js","bare-ansi-escapes":"/node_modules/favela-peer/node_modules/hyperident/node_modules/bare-ansi-escapes/index.js","bare-type":"/node_modules/favela-peer/node_modules/hyperident/node_modules/bare-type/index.js"},"/node_modules/favela-peer/node_modules/hyperident/node_modules/bare-inspect/package.json":{},"/node_modules/favela-peer/node_modules/hyperident/node_modules/bare-stream/index.js":{"#package":"/node_modules/favela-peer/node_modules/hyperident/node_modules/bare-stream/package.json","./web":"/node_modules/favela-peer/node_modules/hyperident/node_modules/bare-stream/web.js","streamx":"/node_modules/favela-peer/node_modules/hyperident/node_modules/streamx/index.js"},"/node_modules/favela-peer/node_modules/hyperident/node_modules/bare-stream/package.json":{},"/node_modules/favela-peer/node_modules/hyperident/node_modules/bare-stream/web.js":{"#package":"/node_modules/favela-peer/node_modules/hyperident/node_modules/bare-stream/package.json","streamx":"/node_modules/favela-peer/node_modules/hyperident/node_modules/streamx/index.js","teex":"/node_modules/favela-peer/node_modules/hyperident/node_modules/teex/index.js"},"/node_modules/favela-peer/node_modules/hyperident/node_modules/bare-type/binding.js":{"#package":"/node_modules/favela-peer/node_modules/hyperident/node_modules/bare-type/package.json",".":"linked:libbare-type.1.1.0.so"},"/node_modules/favela-peer/node_modules/hyperident/node_modules/bare-type/index.js":{"#package":"/node_modules/favela-peer/node_modules/hyperident/node_modules/bare-type/package.json","./binding":"/node_modules/favela-peer/node_modules/hyperident/node_modules/bare-type/binding.js"},"/node_modules/favela-peer/node_modules/hyperident/node_modules/bare-type/package.json":{},"/node_modules/favela-peer/node_modules/hyperident/node_modules/events-universal/bare.js":{"#package":"/node_modules/favela-peer/node_modules/hyperident/node_modules/events-universal/package.json","bare-events":"/node_modules/favela-peer/node_modules/hyperident/node_modules/bare-events/index.js"},"/node_modules/favela-peer/node_modules/hyperident/node_modules/events-universal/package.json":{},"/node_modules/favela-peer/node_modules/hyperident/node_modules/fast-fifo/fixed-size.js":{"#package":"/node_modules/favela-peer/node_modules/hyperident/node_modules/fast-fifo/package.json"},"/node_modules/favela-peer/node_modules/hyperident/node_modules/fast-fifo/index.js":{"#package":"/node_modules/favela-peer/node_modules/hyperident/node_modules/fast-fifo/package.json","./fixed-size":"/node_modules/favela-peer/node_modules/hyperident/node_modules/fast-fifo/fixed-size.js"},"/node_modules/favela-peer/node_modules/hyperident/node_modules/fast-fifo/package.json":{},"/node_modules/favela-peer/node_modules/hyperident/node_modules/require-addon/lib/bare.js":{"#package":"/node_modules/favela-peer/node_modules/hyperident/node_modules/require-addon/package.json"},"/node_modules/favela-peer/node_modules/hyperident/node_modules/require-addon/package.json":{},"/node_modules/favela-peer/node_modules/hyperident/node_modules/sodium-native/binding.js":{"#package":"/node_modules/favela-peer/node_modules/hyperident/node_modules/sodium-native/package.json",".":"linked:libsodium-native.5.1.0.so","require-addon":"/node_modules/favela-peer/node_modules/hyperident/node_modules/require-addon/lib/bare.js"},"/node_modules/favela-peer/node_modules/hyperident/node_modules/sodium-native/index.js":{"#package":"/node_modules/favela-peer/node_modules/hyperident/node_modules/sodium-native/package.json","./binding":"/node_modules/favela-peer/node_modules/hyperident/node_modules/sodium-native/binding.js","assert":"/node_modules/favela-peer/node_modules/hyperident/node_modules/bare-assert/index.js","which-runtime":"/node_modules/favela-peer/node_modules/hyperident/node_modules/which-runtime/index.js"},"/node_modules/favela-peer/node_modules/hyperident/node_modules/sodium-native/package.json":{},"/node_modules/favela-peer/node_modules/hyperident/node_modules/sodium-universal/index.js":{"#package":"/node_modules/favela-peer/node_modules/hyperident/node_modules/sodium-universal/package.json","sodium-native":"/node_modules/favela-peer/node_modules/hyperident/node_modules/sodium-native/index.js"},"/node_modules/favela-peer/node_modules/hyperident/node_modules/sodium-universal/package.json":{},"/node_modules/favela-peer/node_modules/hyperident/node_modules/streamx/index.js":{"#package":"/node_modules/favela-peer/node_modules/hyperident/node_modules/streamx/package.json","events-universal":"/node_modules/favela-peer/node_modules/hyperident/node_modules/events-universal/bare.js","fast-fifo":"/node_modules/favela-peer/node_modules/hyperident/node_modules/fast-fifo/index.js","text-decoder":"/node_modules/favela-peer/node_modules/hyperident/node_modules/text-decoder/index.js"},"/node_modules/favela-peer/node_modules/hyperident/node_modules/streamx/package.json":{},"/node_modules/favela-peer/node_modules/hyperident/node_modules/teex/index.js":{"#package":"/node_modules/favela-peer/node_modules/hyperident/node_modules/teex/package.json","streamx":"/node_modules/favela-peer/node_modules/hyperident/node_modules/streamx/index.js"},"/node_modules/favela-peer/node_modules/hyperident/node_modules/teex/package.json":{},"/node_modules/favela-peer/node_modules/hyperident/node_modules/text-decoder/index.js":{"#package":"/node_modules/favela-peer/node_modules/hyperident/node_modules/text-decoder/package.json","./lib/pass-through-decoder":"/node_modules/favela-peer/node_modules/hyperident/node_modules/text-decoder/lib/pass-through-decoder.js","./lib/utf8-decoder":"/node_modules/favela-peer/node_modules/hyperident/node_modules/text-decoder/lib/utf8-decoder.js"},"/node_modules/favela-peer/node_modules/hyperident/node_modules/text-decoder/lib/pass-through-decoder.js":{"#package":"/node_modules/favela-peer/node_modules/hyperident/node_modules/text-decoder/package.json","b4a":"/node_modules/favela-peer/node_modules/hyperident/node_modules/b4a/index.js"},"/node_modules/favela-peer/node_modules/hyperident/node_modules/text-decoder/lib/utf8-decoder.js":{"#package":"/node_modules/favela-peer/node_modules/hyperident/node_modules/text-decoder/package.json","b4a":"/node_modules/favela-peer/node_modules/hyperident/node_modules/b4a/index.js"},"/node_modules/favela-peer/node_modules/hyperident/node_modules/text-decoder/package.json":{},"/node_modules/favela-peer/node_modules/hyperident/node_modules/which-runtime/index.js":{"#package":"/node_modules/favela-peer/node_modules/hyperident/node_modules/which-runtime/package.json"},"/node_modules/favela-peer/node_modules/hyperident/node_modules/which-runtime/package.json":{},"/node_modules/favela-peer/node_modules/hyperident/package.json":{},"/node_modules/favela-peer/node_modules/hyperschema/package.json":{},"/node_modules/favela-peer/node_modules/hyperschema/runtime.cjs":{"#package":"/node_modules/favela-peer/node_modules/hyperschema/package.json","compact-encoding":"/node_modules/favela-peer/node_modules/compact-encoding/index.js"},"/node_modules/favela-peer/node_modules/hyperswarm/index.js":{"#package":"/node_modules/favela-peer/node_modules/hyperswarm/package.json","./lib/connection-set":"/node_modules/favela-peer/node_modules/hyperswarm/lib/connection-set.js","./lib/peer-discovery":"/node_modules/favela-peer/node_modules/hyperswarm/lib/peer-discovery.js","./lib/peer-info":"/node_modules/favela-peer/node_modules/hyperswarm/lib/peer-info.js","./lib/retry-timer":"/node_modules/favela-peer/node_modules/hyperswarm/lib/retry-timer.js","b4a":"/node_modules/favela-peer/node_modules/b4a/index.js","events":"/node_modules/favela-peer/node_modules/bare-events/index.js","hyperdht":"/node_modules/favela-peer/node_modules/hyperdht/index.js","shuffled-priority-queue":"/node_modules/favela-peer/node_modules/shuffled-priority-queue/index.js","streamx":"/node_modules/favela-peer/node_modules/streamx/index.js","unslab":"/node_modules/favela-peer/node_modules/unslab/index.js"},"/node_modules/favela-peer/node_modules/hyperswarm/lib/bulk-timer.js":{"#package":"/node_modules/favela-peer/node_modules/hyperswarm/package.json"},"/node_modules/favela-peer/node_modules/hyperswarm/lib/connection-set.js":{"#package":"/node_modules/favela-peer/node_modules/hyperswarm/package.json","b4a":"/node_modules/favela-peer/node_modules/b4a/index.js"},"/node_modules/favela-peer/node_modules/hyperswarm/lib/peer-discovery.js":{"#package":"/node_modules/favela-peer/node_modules/hyperswarm/package.json","b4a":"/node_modules/favela-peer/node_modules/b4a/index.js","safety-catch":"/node_modules/favela-peer/node_modules/safety-catch/index.js"},"/node_modules/favela-peer/node_modules/hyperswarm/lib/peer-info.js":{"#package":"/node_modules/favela-peer/node_modules/hyperswarm/package.json","b4a":"/node_modules/favela-peer/node_modules/b4a/index.js","events":"/node_modules/favela-peer/node_modules/bare-events/index.js","unslab":"/node_modules/favela-peer/node_modules/unslab/index.js"},"/node_modules/favela-peer/node_modules/hyperswarm/lib/retry-timer.js":{"#package":"/node_modules/favela-peer/node_modules/hyperswarm/package.json","./bulk-timer":"/node_modules/favela-peer/node_modules/hyperswarm/lib/bulk-timer.js"},"/node_modules/favela-peer/node_modules/hyperswarm/package.json":{},"/node_modules/favela-peer/node_modules/index-encoder/index.js":{"#package":"/node_modules/favela-peer/node_modules/index-encoder/package.json","b4a":"/node_modules/favela-peer/node_modules/b4a/index.js"},"/node_modules/favela-peer/node_modules/index-encoder/package.json":{},"/node_modules/favela-peer/node_modules/is-options/index.js":{"#package":"/node_modules/favela-peer/node_modules/is-options/package.json","b4a":"/node_modules/favela-peer/node_modules/b4a/index.js"},"/node_modules/favela-peer/node_modules/is-options/package.json":{},"/node_modules/favela-peer/node_modules/kademlia-routing-table/index.js":{"#package":"/node_modules/favela-peer/node_modules/kademlia-routing-table/package.json","events":"/node_modules/favela-peer/node_modules/bare-events/index.js"},"/node_modules/favela-peer/node_modules/kademlia-routing-table/package.json":{},"/node_modules/favela-peer/node_modules/keet-identity-key/index.js":{"#package":"/node_modules/favela-peer/node_modules/keet-identity-key/package.json","./lib/crypto":"/node_modules/favela-peer/node_modules/keet-identity-key/lib/crypto.js","./lib/encoding":"/node_modules/favela-peer/node_modules/keet-identity-key/lib/encoding.js","./lib/keychain":"/node_modules/favela-peer/node_modules/keet-identity-key/lib/keychain.js","b4a":"/node_modules/favela-peer/node_modules/b4a/index.js","compact-encoding":"/node_modules/favela-peer/node_modules/compact-encoding/index.js"},"/node_modules/favela-peer/node_modules/keet-identity-key/lib/crypto.js":{"#package":"/node_modules/favela-peer/node_modules/keet-identity-key/package.json","b4a":"/node_modules/favela-peer/node_modules/b4a/index.js","sodium-universal":"/node_modules/favela-peer/node_modules/sodium-universal/index.js"},"/node_modules/favela-peer/node_modules/keet-identity-key/lib/encoding.js":{"#package":"/node_modules/favela-peer/node_modules/keet-identity-key/package.json","compact-encoding":"/node_modules/favela-peer/node_modules/compact-encoding/index.js"},"/node_modules/favela-peer/node_modules/keet-identity-key/lib/keychain.js":{"#package":"/node_modules/favela-peer/node_modules/keet-identity-key/package.json","./crypto":"/node_modules/favela-peer/node_modules/keet-identity-key/lib/crypto.js","b4a":"/node_modules/favela-peer/node_modules/b4a/index.js","bip39-mnemonic":"/node_modules/favela-peer/node_modules/bip39-mnemonic/index.js","compact-encoding":"/node_modules/favela-peer/node_modules/compact-encoding/index.js","nanoassert":"/node_modules/favela-peer/node_modules/nanoassert/index.js","sodium-hmac":"/node_modules/favela-peer/node_modules/sodium-hmac/index.js","sodium-universal":"/node_modules/favela-peer/node_modules/sodium-universal/index.js"},"/node_modules/favela-peer/node_modules/keet-identity-key/package.json":{},"/node_modules/favela-peer/node_modules/nanoassert/index.js":{"#package":"/node_modules/favela-peer/node_modules/nanoassert/package.json"},"/node_modules/favela-peer/node_modules/nanoassert/package.json":{},"/node_modules/favela-peer/node_modules/nat-sampler/index.js":{"#package":"/node_modules/favela-peer/node_modules/nat-sampler/package.json"},"/node_modules/favela-peer/node_modules/nat-sampler/package.json":{},"/node_modules/favela-peer/node_modules/noise-curve-ed/index.js":{"#package":"/node_modules/favela-peer/node_modules/noise-curve-ed/package.json","b4a":"/node_modules/favela-peer/node_modules/b4a/index.js","nanoassert":"/node_modules/favela-peer/node_modules/nanoassert/index.js","sodium-universal":"/node_modules/favela-peer/node_modules/sodium-universal/index.js"},"/node_modules/favela-peer/node_modules/noise-curve-ed/package.json":{},"/node_modules/favela-peer/node_modules/noise-handshake/cipher.js":{"#package":"/node_modules/favela-peer/node_modules/noise-handshake/package.json","b4a":"/node_modules/favela-peer/node_modules/b4a/index.js","sodium-universal":"/node_modules/favela-peer/node_modules/sodium-universal/index.js"},"/node_modules/favela-peer/node_modules/noise-handshake/dh.js":{"#package":"/node_modules/favela-peer/node_modules/noise-handshake/package.json","b4a":"/node_modules/favela-peer/node_modules/b4a/index.js","nanoassert":"/node_modules/favela-peer/node_modules/nanoassert/index.js","sodium-universal":"/node_modules/favela-peer/node_modules/sodium-universal/index.js"},"/node_modules/favela-peer/node_modules/noise-handshake/hkdf.js":{"#package":"/node_modules/favela-peer/node_modules/noise-handshake/package.json","./hmac":"/node_modules/favela-peer/node_modules/noise-handshake/hmac.js","b4a":"/node_modules/favela-peer/node_modules/b4a/index.js"},"/node_modules/favela-peer/node_modules/noise-handshake/hmac.js":{"#package":"/node_modules/favela-peer/node_modules/noise-handshake/package.json","b4a":"/node_modules/favela-peer/node_modules/b4a/index.js","sodium-universal":"/node_modules/favela-peer/node_modules/sodium-universal/index.js"},"/node_modules/favela-peer/node_modules/noise-handshake/noise.js":{"#package":"/node_modules/favela-peer/node_modules/noise-handshake/package.json","./hkdf":"/node_modules/favela-peer/node_modules/noise-handshake/hkdf.js","./symmetric-state":"/node_modules/favela-peer/node_modules/noise-handshake/symmetric-state.js","b4a":"/node_modules/favela-peer/node_modules/b4a/index.js","nanoassert":"/node_modules/favela-peer/node_modules/nanoassert/index.js"},"/node_modules/favela-peer/node_modules/noise-handshake/package.json":{},"/node_modules/favela-peer/node_modules/noise-handshake/symmetric-state.js":{"#package":"/node_modules/favela-peer/node_modules/noise-handshake/package.json","./cipher":"/node_modules/favela-peer/node_modules/noise-handshake/cipher.js","./dh":"/node_modules/favela-peer/node_modules/noise-handshake/dh.js","./hkdf":"/node_modules/favela-peer/node_modules/noise-handshake/hkdf.js","b4a":"/node_modules/favela-peer/node_modules/b4a/index.js","nanoassert":"/node_modules/favela-peer/node_modules/nanoassert/index.js","sodium-universal":"/node_modules/favela-peer/node_modules/sodium-universal/index.js"},"/node_modules/favela-peer/node_modules/protocol-buffers-encodings/index.js":{"#package":"/node_modules/favela-peer/node_modules/protocol-buffers-encodings/package.json","b4a":"/node_modules/favela-peer/node_modules/b4a/index.js","signed-varint":"/node_modules/favela-peer/node_modules/signed-varint/index.js","varint":"/node_modules/favela-peer/node_modules/varint/index.js"},"/node_modules/favela-peer/node_modules/protocol-buffers-encodings/package.json":{},"/node_modules/favela-peer/node_modules/protomux-wakeup/index.js":{"#package":"/node_modules/favela-peer/node_modules/protomux-wakeup/package.json","./spec/hyperschema":"/node_modules/favela-peer/node_modules/protomux-wakeup/spec/hyperschema/index.js","b4a":"/node_modules/favela-peer/node_modules/b4a/index.js","hypercore-crypto":"/node_modules/favela-peer/node_modules/hypercore-crypto/index.js","protomux":"/node_modules/favela-peer/node_modules/protomux/index.js"},"/node_modules/favela-peer/node_modules/protomux-wakeup/package.json":{},"/node_modules/favela-peer/node_modules/protomux-wakeup/spec/hyperschema/index.js":{"#package":"/node_modules/favela-peer/node_modules/protomux-wakeup/package.json","hyperschema/runtime":"/node_modules/favela-peer/node_modules/hyperschema/runtime.cjs"},"/node_modules/favela-peer/node_modules/protomux/index.js":{"#package":"/node_modules/favela-peer/node_modules/protomux/package.json","b4a":"/node_modules/favela-peer/node_modules/b4a/index.js","compact-encoding":"/node_modules/favela-peer/node_modules/compact-encoding/index.js","queue-tick":"/node_modules/favela-peer/node_modules/queue-tick/process-next-tick.js","safety-catch":"/node_modules/favela-peer/node_modules/safety-catch/index.js","unslab":"/node_modules/favela-peer/node_modules/unslab/index.js"},"/node_modules/favela-peer/node_modules/protomux/package.json":{},"/node_modules/favela-peer/node_modules/queue-tick/package.json":{},"/node_modules/favela-peer/node_modules/queue-tick/process-next-tick.js":{"#package":"/node_modules/favela-peer/node_modules/queue-tick/package.json","./queue-microtask":"/node_modules/favela-peer/node_modules/queue-tick/queue-microtask.js"},"/node_modules/favela-peer/node_modules/queue-tick/queue-microtask.js":{"#package":"/node_modules/favela-peer/node_modules/queue-tick/package.json"},"/node_modules/favela-peer/node_modules/quickbit-native/binding.js":{"#package":"/node_modules/favela-peer/node_modules/quickbit-native/package.json",".":"linked:libquickbit-native.2.4.8.so","require-addon":"/node_modules/favela-peer/node_modules/require-addon/lib/bare.js"},"/node_modules/favela-peer/node_modules/quickbit-native/index.js":{"#package":"/node_modules/favela-peer/node_modules/quickbit-native/package.json","./binding":"/node_modules/favela-peer/node_modules/quickbit-native/binding.js"},"/node_modules/favela-peer/node_modules/quickbit-native/package.json":{},"/node_modules/favela-peer/node_modules/quickbit-universal/fallback.js":{"#package":"/node_modules/favela-peer/node_modules/quickbit-universal/package.json","simdle-universal":"/node_modules/favela-peer/node_modules/simdle-universal/index.js"},"/node_modules/favela-peer/node_modules/quickbit-universal/index.js":{"#package":"/node_modules/favela-peer/node_modules/quickbit-universal/package.json","./fallback":"/node_modules/favela-peer/node_modules/quickbit-universal/fallback.js","quickbit-native":"/node_modules/favela-peer/node_modules/quickbit-native/index.js"},"/node_modules/favela-peer/node_modules/quickbit-universal/package.json":{},"/node_modules/favela-peer/node_modules/random-array-iterator/index.js":{"#package":"/node_modules/favela-peer/node_modules/random-array-iterator/package.json"},"/node_modules/favela-peer/node_modules/random-array-iterator/package.json":{},"/node_modules/favela-peer/node_modules/ready-guard/index.js":{"#package":"/node_modules/favela-peer/node_modules/ready-guard/package.json","resolve-reject-promise":"/node_modules/favela-peer/node_modules/resolve-reject-promise/index.js"},"/node_modules/favela-peer/node_modules/ready-guard/package.json":{},"/node_modules/favela-peer/node_modules/ready-resource/index.js":{"#package":"/node_modules/favela-peer/node_modules/ready-resource/package.json","events":"/node_modules/favela-peer/node_modules/bare-events/index.js"},"/node_modules/favela-peer/node_modules/ready-resource/package.json":{},"/node_modules/favela-peer/node_modules/record-cache/index.js":{"#package":"/node_modules/favela-peer/node_modules/record-cache/package.json","b4a":"/node_modules/favela-peer/node_modules/b4a/index.js"},"/node_modules/favela-peer/node_modules/record-cache/package.json":{},"/node_modules/favela-peer/node_modules/refcounter/index.js":{"#package":"/node_modules/favela-peer/node_modules/refcounter/package.json"},"/node_modules/favela-peer/node_modules/refcounter/package.json":{},"/node_modules/favela-peer/node_modules/require-addon/lib/bare.js":{"#package":"/node_modules/favela-peer/node_modules/require-addon/package.json"},"/node_modules/favela-peer/node_modules/require-addon/package.json":{},"/node_modules/favela-peer/node_modules/resolve-reject-promise/index.js":{"#package":"/node_modules/favela-peer/node_modules/resolve-reject-promise/package.json"},"/node_modules/favela-peer/node_modules/resolve-reject-promise/package.json":{},"/node_modules/favela-peer/node_modules/resource-on-exit/index.js":{"#package":"/node_modules/favela-peer/node_modules/resource-on-exit/package.json"},"/node_modules/favela-peer/node_modules/resource-on-exit/package.json":{},"/node_modules/favela-peer/node_modules/rocksdb-native/binding.js":{"#package":"/node_modules/favela-peer/node_modules/rocksdb-native/package.json",".":"linked:librocksdb-native.3.17.4.so","require-addon":"/node_modules/favela-peer/node_modules/require-addon/lib/bare.js"},"/node_modules/favela-peer/node_modules/rocksdb-native/index.js":{"#package":"/node_modules/favela-peer/node_modules/rocksdb-native/package.json","./lib/column-family":"/node_modules/favela-peer/node_modules/rocksdb-native/lib/column-family.js","./lib/constants":"/node_modules/favela-peer/node_modules/rocksdb-native/lib/constants.js","./lib/filter-policy":"/node_modules/favela-peer/node_modules/rocksdb-native/lib/filter-policy.js","./lib/iterator":"/node_modules/favela-peer/node_modules/rocksdb-native/lib/iterator.js","./lib/snapshot":"/node_modules/favela-peer/node_modules/rocksdb-native/lib/snapshot.js","./lib/state":"/node_modules/favela-peer/node_modules/rocksdb-native/lib/state.js"},"/node_modules/favela-peer/node_modules/rocksdb-native/lib/batch.js":{"#package":"/node_modules/favela-peer/node_modules/rocksdb-native/package.json","../binding":"/node_modules/favela-peer/node_modules/rocksdb-native/binding.js","compact-encoding":"/node_modules/favela-peer/node_modules/compact-encoding/index.js"},"/node_modules/favela-peer/node_modules/rocksdb-native/lib/column-family.js":{"#package":"/node_modules/favela-peer/node_modules/rocksdb-native/package.json","../binding":"/node_modules/favela-peer/node_modules/rocksdb-native/binding.js","./constants":"/node_modules/favela-peer/node_modules/rocksdb-native/lib/constants.js","./filter-policy":"/node_modules/favela-peer/node_modules/rocksdb-native/lib/filter-policy.js"},"/node_modules/favela-peer/node_modules/rocksdb-native/lib/constants.js":{"#package":"/node_modules/favela-peer/node_modules/rocksdb-native/package.json"},"/node_modules/favela-peer/node_modules/rocksdb-native/lib/filter-policy.js":{"#package":"/node_modules/favela-peer/node_modules/rocksdb-native/package.json"},"/node_modules/favela-peer/node_modules/rocksdb-native/lib/iterator.js":{"#package":"/node_modules/favela-peer/node_modules/rocksdb-native/package.json","../binding":"/node_modules/favela-peer/node_modules/rocksdb-native/binding.js","compact-encoding":"/node_modules/favela-peer/node_modules/compact-encoding/index.js","streamx":"/node_modules/favela-peer/node_modules/streamx/index.js"},"/node_modules/favela-peer/node_modules/rocksdb-native/lib/snapshot.js":{"#package":"/node_modules/favela-peer/node_modules/rocksdb-native/package.json","../binding":"/node_modules/favela-peer/node_modules/rocksdb-native/binding.js"},"/node_modules/favela-peer/node_modules/rocksdb-native/lib/state.js":{"#package":"/node_modules/favela-peer/node_modules/rocksdb-native/package.json","../binding":"/node_modules/favela-peer/node_modules/rocksdb-native/binding.js","./batch":"/node_modules/favela-peer/node_modules/rocksdb-native/lib/batch.js","./column-family":"/node_modules/favela-peer/node_modules/rocksdb-native/lib/column-family.js","./constants":"/node_modules/favela-peer/node_modules/rocksdb-native/lib/constants.js","compact-encoding":"/node_modules/favela-peer/node_modules/compact-encoding/index.js","ready-resource":"/node_modules/favela-peer/node_modules/ready-resource/index.js","refcounter":"/node_modules/favela-peer/node_modules/refcounter/index.js","resolve-reject-promise":"/node_modules/favela-peer/node_modules/resolve-reject-promise/index.js","signal-promise":"/node_modules/favela-peer/node_modules/signal-promise/index.js"},"/node_modules/favela-peer/node_modules/rocksdb-native/package.json":{},"/node_modules/favela-peer/node_modules/safety-catch/index.js":{"#package":"/node_modules/favela-peer/node_modules/safety-catch/package.json"},"/node_modules/favela-peer/node_modules/safety-catch/package.json":{},"/node_modules/favela-peer/node_modules/scope-lock/index.js":{"#package":"/node_modules/favela-peer/node_modules/scope-lock/package.json"},"/node_modules/favela-peer/node_modules/scope-lock/package.json":{},"/node_modules/favela-peer/node_modules/shuffled-priority-queue/index.js":{"#package":"/node_modules/favela-peer/node_modules/shuffled-priority-queue/package.json","unordered-set":"/node_modules/favela-peer/node_modules/unordered-set/index.js"},"/node_modules/favela-peer/node_modules/shuffled-priority-queue/package.json":{},"/node_modules/favela-peer/node_modules/signal-promise/index.js":{"#package":"/node_modules/favela-peer/node_modules/signal-promise/package.json"},"/node_modules/favela-peer/node_modules/signal-promise/package.json":{},"/node_modules/favela-peer/node_modules/signed-varint/index.js":{"#package":"/node_modules/favela-peer/node_modules/signed-varint/package.json","varint":"/node_modules/favela-peer/node_modules/varint/index.js"},"/node_modules/favela-peer/node_modules/signed-varint/package.json":{},"/node_modules/favela-peer/node_modules/simdle-native/binding.js":{"#package":"/node_modules/favela-peer/node_modules/simdle-native/package.json",".":"linked:libsimdle-native.1.3.9.so","require-addon":"/node_modules/favela-peer/node_modules/require-addon/lib/bare.js"},"/node_modules/favela-peer/node_modules/simdle-native/index.js":{"#package":"/node_modules/favela-peer/node_modules/simdle-native/package.json","./binding":"/node_modules/favela-peer/node_modules/simdle-native/binding.js","b4a":"/node_modules/favela-peer/node_modules/b4a/index.js"},"/node_modules/favela-peer/node_modules/simdle-native/package.json":{},"/node_modules/favela-peer/node_modules/simdle-universal/fallback.js":{"#package":"/node_modules/favela-peer/node_modules/simdle-universal/package.json","./scalar":"/node_modules/favela-peer/node_modules/simdle-universal/scalar.js","b4a":"/node_modules/favela-peer/node_modules/b4a/index.js"},"/node_modules/favela-peer/node_modules/simdle-universal/index.js":{"#package":"/node_modules/favela-peer/node_modules/simdle-universal/package.json","./fallback":"/node_modules/favela-peer/node_modules/simdle-universal/fallback.js","simdle-native":"/node_modules/favela-peer/node_modules/simdle-native/index.js"},"/node_modules/favela-peer/node_modules/simdle-universal/package.json":{},"/node_modules/favela-peer/node_modules/simdle-universal/scalar.js":{"#package":"/node_modules/favela-peer/node_modules/simdle-universal/package.json"},"/node_modules/favela-peer/node_modules/sodium-hmac/hash/sha256.js":{"#package":"/node_modules/favela-peer/node_modules/sodium-hmac/package.json","sodium-universal":"/node_modules/favela-peer/node_modules/sodium-universal/index.js"},"/node_modules/favela-peer/node_modules/sodium-hmac/hash/sha512.js":{"#package":"/node_modules/favela-peer/node_modules/sodium-hmac/package.json","sodium-universal":"/node_modules/favela-peer/node_modules/sodium-universal/index.js"},"/node_modules/favela-peer/node_modules/sodium-hmac/index.js":{"#package":"/node_modules/favela-peer/node_modules/sodium-hmac/package.json","./hash/sha256":"/node_modules/favela-peer/node_modules/sodium-hmac/hash/sha256.js","./hash/sha512":"/node_modules/favela-peer/node_modules/sodium-hmac/hash/sha512.js","b4a":"/node_modules/favela-peer/node_modules/b4a/index.js"},"/node_modules/favela-peer/node_modules/sodium-hmac/package.json":{},"/node_modules/favela-peer/node_modules/sodium-native/binding.js":{"#package":"/node_modules/favela-peer/node_modules/sodium-native/package.json",".":"linked:libsodium-native.5.1.0.so","require-addon":"/node_modules/favela-peer/node_modules/require-addon/lib/bare.js"},"/node_modules/favela-peer/node_modules/sodium-native/index.js":{"#package":"/node_modules/favela-peer/node_modules/sodium-native/package.json","./binding":"/node_modules/favela-peer/node_modules/sodium-native/binding.js","assert":"/node_modules/favela-peer/node_modules/bare-assert/index.js","which-runtime":"/node_modules/favela-peer/node_modules/which-runtime/index.js"},"/node_modules/favela-peer/node_modules/sodium-native/package.json":{},"/node_modules/favela-peer/node_modules/sodium-secretstream/index.js":{"#package":"/node_modules/favela-peer/node_modules/sodium-secretstream/package.json","b4a":"/node_modules/favela-peer/node_modules/b4a/index.js","sodium-universal":"/node_modules/favela-peer/node_modules/sodium-universal/index.js"},"/node_modules/favela-peer/node_modules/sodium-secretstream/package.json":{},"/node_modules/favela-peer/node_modules/sodium-universal/index.js":{"#package":"/node_modules/favela-peer/node_modules/sodium-universal/package.json","sodium-native":"/node_modules/favela-peer/node_modules/sodium-native/index.js"},"/node_modules/favela-peer/node_modules/sodium-universal/package.json":{},"/node_modules/favela-peer/node_modules/streamx/index.js":{"#package":"/node_modules/favela-peer/node_modules/streamx/package.json","./lib/errors.js":"/node_modules/favela-peer/node_modules/streamx/lib/errors.js","events-universal":"/node_modules/favela-peer/node_modules/events-universal/bare.js","fast-fifo":"/node_modules/favela-peer/node_modules/fast-fifo/index.js","text-decoder":"/node_modules/favela-peer/node_modules/text-decoder/index.js"},"/node_modules/favela-peer/node_modules/streamx/lib/errors.js":{"#package":"/node_modules/favela-peer/node_modules/streamx/package.json"},"/node_modules/favela-peer/node_modules/streamx/package.json":{},"/node_modules/favela-peer/node_modules/teex/index.js":{"#package":"/node_modules/favela-peer/node_modules/teex/package.json","streamx":"/node_modules/favela-peer/node_modules/streamx/index.js"},"/node_modules/favela-peer/node_modules/teex/package.json":{},"/node_modules/favela-peer/node_modules/text-decoder/index.js":{"#package":"/node_modules/favela-peer/node_modules/text-decoder/package.json","./lib/pass-through-decoder":"/node_modules/favela-peer/node_modules/text-decoder/lib/pass-through-decoder.js","./lib/utf8-decoder":"/node_modules/favela-peer/node_modules/text-decoder/lib/utf8-decoder.js"},"/node_modules/favela-peer/node_modules/text-decoder/lib/pass-through-decoder.js":{"#package":"/node_modules/favela-peer/node_modules/text-decoder/package.json","b4a":"/node_modules/favela-peer/node_modules/b4a/index.js"},"/node_modules/favela-peer/node_modules/text-decoder/lib/utf8-decoder.js":{"#package":"/node_modules/favela-peer/node_modules/text-decoder/package.json","b4a":"/node_modules/favela-peer/node_modules/b4a/index.js"},"/node_modules/favela-peer/node_modules/text-decoder/package.json":{},"/node_modules/favela-peer/node_modules/time-ordered-set/index.js":{"#package":"/node_modules/favela-peer/node_modules/time-ordered-set/package.json"},"/node_modules/favela-peer/node_modules/time-ordered-set/package.json":{},"/node_modules/favela-peer/node_modules/timeout-refresh/browser.js":{"#package":"/node_modules/favela-peer/node_modules/timeout-refresh/package.json"},"/node_modules/favela-peer/node_modules/timeout-refresh/index.js":{"#package":"/node_modules/favela-peer/node_modules/timeout-refresh/package.json","./browser":"/node_modules/favela-peer/node_modules/timeout-refresh/browser.js","./node":"/node_modules/favela-peer/node_modules/timeout-refresh/node.js"},"/node_modules/favela-peer/node_modules/timeout-refresh/node.js":{"#package":"/node_modules/favela-peer/node_modules/timeout-refresh/package.json"},"/node_modules/favela-peer/node_modules/timeout-refresh/package.json":{},"/node_modules/favela-peer/node_modules/udx-native/binding.js":{"#package":"/node_modules/favela-peer/node_modules/udx-native/package.json",".":"linked:libudx-native.1.21.1.so","require-addon":"/node_modules/favela-peer/node_modules/require-addon/lib/bare.js"},"/node_modules/favela-peer/node_modules/udx-native/lib/ip.js":{"#package":"/node_modules/favela-peer/node_modules/udx-native/package.json"},"/node_modules/favela-peer/node_modules/udx-native/lib/network-interfaces.js":{"#package":"/node_modules/favela-peer/node_modules/udx-native/package.json","../binding":"/node_modules/favela-peer/node_modules/udx-native/binding.js","b4a":"/node_modules/favela-peer/node_modules/b4a/index.js","events":"/node_modules/favela-peer/node_modules/bare-events/index.js"},"/node_modules/favela-peer/node_modules/udx-native/lib/socket.js":{"#package":"/node_modules/favela-peer/node_modules/udx-native/package.json","../binding":"/node_modules/favela-peer/node_modules/udx-native/binding.js","./ip":"/node_modules/favela-peer/node_modules/udx-native/lib/ip.js","b4a":"/node_modules/favela-peer/node_modules/b4a/index.js","events":"/node_modules/favela-peer/node_modules/bare-events/index.js"},"/node_modules/favela-peer/node_modules/udx-native/lib/stream.js":{"#package":"/node_modules/favela-peer/node_modules/udx-native/package.json","../binding":"/node_modules/favela-peer/node_modules/udx-native/binding.js","./ip":"/node_modules/favela-peer/node_modules/udx-native/lib/ip.js","b4a":"/node_modules/favela-peer/node_modules/b4a/index.js","streamx":"/node_modules/favela-peer/node_modules/streamx/index.js"},"/node_modules/favela-peer/node_modules/udx-native/lib/udx.js":{"#package":"/node_modules/favela-peer/node_modules/udx-native/package.json","../binding":"/node_modules/favela-peer/node_modules/udx-native/binding.js","./ip":"/node_modules/favela-peer/node_modules/udx-native/lib/ip.js","./network-interfaces":"/node_modules/favela-peer/node_modules/udx-native/lib/network-interfaces.js","./socket":"/node_modules/favela-peer/node_modules/udx-native/lib/socket.js","./stream":"/node_modules/favela-peer/node_modules/udx-native/lib/stream.js","b4a":"/node_modules/favela-peer/node_modules/b4a/index.js"},"/node_modules/favela-peer/node_modules/udx-native/package.json":{},"/node_modules/favela-peer/node_modules/unordered-set/index.js":{"#package":"/node_modules/favela-peer/node_modules/unordered-set/package.json"},"/node_modules/favela-peer/node_modules/unordered-set/package.json":{},"/node_modules/favela-peer/node_modules/unslab/index.js":{"#package":"/node_modules/favela-peer/node_modules/unslab/package.json","b4a":"/node_modules/favela-peer/node_modules/b4a/index.js"},"/node_modules/favela-peer/node_modules/unslab/package.json":{},"/node_modules/favela-peer/node_modules/varint/decode.js":{"#package":"/node_modules/favela-peer/node_modules/varint/package.json"},"/node_modules/favela-peer/node_modules/varint/encode.js":{"#package":"/node_modules/favela-peer/node_modules/varint/package.json"},"/node_modules/favela-peer/node_modules/varint/index.js":{"#package":"/node_modules/favela-peer/node_modules/varint/package.json","./decode.js":"/node_modules/favela-peer/node_modules/varint/decode.js","./encode.js":"/node_modules/favela-peer/node_modules/varint/encode.js","./length.js":"/node_modules/favela-peer/node_modules/varint/length.js"},"/node_modules/favela-peer/node_modules/varint/length.js":{"#package":"/node_modules/favela-peer/node_modules/varint/package.json"},"/node_modules/favela-peer/node_modules/varint/package.json":{},"/node_modules/favela-peer/node_modules/which-runtime/index.js":{"#package":"/node_modules/favela-peer/node_modules/which-runtime/package.json"},"/node_modules/favela-peer/node_modules/which-runtime/package.json":{},"/node_modules/favela-peer/node_modules/xache/index.js":{"#package":"/node_modules/favela-peer/node_modules/xache/package.json"},"/node_modules/favela-peer/node_modules/xache/package.json":{},"/node_modules/favela-peer/node_modules/z32/index.js":{"#package":"/node_modules/favela-peer/node_modules/z32/package.json","b4a":"/node_modules/favela-peer/node_modules/b4a/index.js"},"/node_modules/favela-peer/node_modules/z32/package.json":{},"/node_modules/favela-peer/package.json":{},"/node_modules/favela-surface/lib/multipart.js":{"#package":"/node_modules/favela-surface/package.json","b4a":"/node_modules/favela-surface/node_modules/b4a/index.js"},"/node_modules/favela-surface/lib/pages.js":{"#package":"/node_modules/favela-surface/package.json","favela-markdown/lib/render.js":"/node_modules/favela-surface/node_modules/favela-markdown/lib/render.js"},"/node_modules/favela-surface/lib/router.js":{"#node:fs":"/node_modules/favela-surface/node_modules/bare-fs/index.js","#node:os":"/node_modules/favela-surface/node_modules/bare-os/index.js","#node:path":"/node_modules/favela-surface/node_modules/bare-path/index.js","#package":"/node_modules/favela-surface/package.json","./multipart.js":"/node_modules/favela-surface/lib/multipart.js","./pages.js":"/node_modules/favela-surface/lib/pages.js","./session.js":"/node_modules/favela-surface/lib/session.js","b4a":"/node_modules/favela-surface/node_modules/b4a/index.js","favela-markdown/lib/render.js":"/node_modules/favela-surface/node_modules/favela-markdown/lib/render.js","favela-markdown/lib/uri.js":"/node_modules/favela-surface/node_modules/favela-markdown/lib/uri.js"},"/node_modules/favela-surface/lib/session.js":{"#package":"/node_modules/favela-surface/package.json","bare-crypto":"/node_modules/favela-surface/node_modules/bare-crypto/index.js"},"/node_modules/favela-surface/node_modules/b4a/index.js":{"#package":"/node_modules/favela-surface/node_modules/b4a/package.json"},"/node_modules/favela-surface/node_modules/b4a/package.json":{},"/node_modules/favela-surface/node_modules/bare-ansi-escapes/index.js":{"#package":"/node_modules/favela-surface/node_modules/bare-ansi-escapes/package.json"},"/node_modules/favela-surface/node_modules/bare-ansi-escapes/package.json":{},"/node_modules/favela-surface/node_modules/bare-assert/index.js":{"#package":"/node_modules/favela-surface/node_modules/bare-assert/package.json","bare-inspect":"/node_modules/favela-surface/node_modules/bare-inspect/index.js"},"/node_modules/favela-surface/node_modules/bare-assert/package.json":{},"/node_modules/favela-surface/node_modules/bare-crypto/binding.js":{"#package":"/node_modules/favela-surface/node_modules/bare-crypto/package.json",".":"linked:libbare-crypto.1.15.3.so"},"/node_modules/favela-surface/node_modules/bare-crypto/index.js":{"#package":"/node_modules/favela-surface/node_modules/bare-crypto/package.json","./lib/cipher":"/node_modules/favela-surface/node_modules/bare-crypto/lib/cipher.js","./lib/constants":"/node_modules/favela-surface/node_modules/bare-crypto/lib/constants.js","./lib/hash":"/node_modules/favela-surface/node_modules/bare-crypto/lib/hash.js","./lib/hmac":"/node_modules/favela-surface/node_modules/bare-crypto/lib/hmac.js","./lib/key":"/node_modules/favela-surface/node_modules/bare-crypto/lib/key.js","./lib/pbkdf2":"/node_modules/favela-surface/node_modules/bare-crypto/lib/pbkdf2.js","./lib/random":"/node_modules/favela-surface/node_modules/bare-crypto/lib/random.js","./lib/signature":"/node_modules/favela-surface/node_modules/bare-crypto/lib/signature.js","./lib/timing":"/node_modules/favela-surface/node_modules/bare-crypto/lib/timing.js","./web":"/node_modules/favela-surface/node_modules/bare-crypto/web.js"},"/node_modules/favela-surface/node_modules/bare-crypto/lib/cipher.js":{"#package":"/node_modules/favela-surface/node_modules/bare-crypto/package.json","../binding":"/node_modules/favela-surface/node_modules/bare-crypto/binding.js","./constants":"/node_modules/favela-surface/node_modules/bare-crypto/lib/constants.js","bare-assert":"/node_modules/favela-surface/node_modules/bare-assert/index.js","bare-stream":"/node_modules/favela-surface/node_modules/bare-stream/index.js"},"/node_modules/favela-surface/node_modules/bare-crypto/lib/constants.js":{"#package":"/node_modules/favela-surface/node_modules/bare-crypto/package.json","../binding":"/node_modules/favela-surface/node_modules/bare-crypto/binding.js","./errors":"/node_modules/favela-surface/node_modules/bare-crypto/lib/errors.js","bare-assert":"/node_modules/favela-surface/node_modules/bare-assert/index.js"},"/node_modules/favela-surface/node_modules/bare-crypto/lib/errors.js":{"#package":"/node_modules/favela-surface/node_modules/bare-crypto/package.json"},"/node_modules/favela-surface/node_modules/bare-crypto/lib/hash.js":{"#package":"/node_modules/favela-surface/node_modules/bare-crypto/package.json","../binding":"/node_modules/favela-surface/node_modules/bare-crypto/binding.js","./constants":"/node_modules/favela-surface/node_modules/bare-crypto/lib/constants.js","bare-assert":"/node_modules/favela-surface/node_modules/bare-assert/index.js","bare-stream":"/node_modules/favela-surface/node_modules/bare-stream/index.js"},"/node_modules/favela-surface/node_modules/bare-crypto/lib/hmac.js":{"#package":"/node_modules/favela-surface/node_modules/bare-crypto/package.json","../binding":"/node_modules/favela-surface/node_modules/bare-crypto/binding.js","./constants":"/node_modules/favela-surface/node_modules/bare-crypto/lib/constants.js","bare-assert":"/node_modules/favela-surface/node_modules/bare-assert/index.js","bare-stream":"/node_modules/favela-surface/node_modules/bare-stream/index.js"},"/node_modules/favela-surface/node_modules/bare-crypto/lib/key.js":{"#package":"/node_modules/favela-surface/node_modules/bare-crypto/package.json","../binding":"/node_modules/favela-surface/node_modules/bare-crypto/binding.js","./constants":"/node_modules/favela-surface/node_modules/bare-crypto/lib/constants.js"},"/node_modules/favela-surface/node_modules/bare-crypto/lib/pbkdf2.js":{"#package":"/node_modules/favela-surface/node_modules/bare-crypto/package.json","../binding":"/node_modules/favela-surface/node_modules/bare-crypto/binding.js","./constants":"/node_modules/favela-surface/node_modules/bare-crypto/lib/constants.js","bare-assert":"/node_modules/favela-surface/node_modules/bare-assert/index.js"},"/node_modules/favela-surface/node_modules/bare-crypto/lib/random.js":{"#package":"/node_modules/favela-surface/node_modules/bare-crypto/package.json","../binding":"/node_modules/favela-surface/node_modules/bare-crypto/binding.js","bare-assert":"/node_modules/favela-surface/node_modules/bare-assert/index.js"},"/node_modules/favela-surface/node_modules/bare-crypto/lib/signature.js":{"#package":"/node_modules/favela-surface/node_modules/bare-crypto/package.json","../binding":"/node_modules/favela-surface/node_modules/bare-crypto/binding.js","./constants":"/node_modules/favela-surface/node_modules/bare-crypto/lib/constants.js","./key":"/node_modules/favela-surface/node_modules/bare-crypto/lib/key.js","bare-assert":"/node_modules/favela-surface/node_modules/bare-assert/index.js"},"/node_modules/favela-surface/node_modules/bare-crypto/lib/timing.js":{"#package":"/node_modules/favela-surface/node_modules/bare-crypto/package.json","../binding":"/node_modules/favela-surface/node_modules/bare-crypto/binding.js","bare-assert":"/node_modules/favela-surface/node_modules/bare-assert/index.js"},"/node_modules/favela-surface/node_modules/bare-crypto/lib/web/algorithm/ed25519.js":{"#package":"/node_modules/favela-surface/node_modules/bare-crypto/package.json","../../..":"/node_modules/favela-surface/node_modules/bare-crypto/index.js","../../../binding":"/node_modules/favela-surface/node_modules/bare-crypto/binding.js","../../errors":"/node_modules/favela-surface/node_modules/bare-crypto/lib/errors.js","../../key":"/node_modules/favela-surface/node_modules/bare-crypto/lib/key.js","../crypto-key":"/node_modules/favela-surface/node_modules/bare-crypto/lib/web/crypto-key.js"},"/node_modules/favela-surface/node_modules/bare-crypto/lib/web/algorithm/hmac.js":{"#package":"/node_modules/favela-surface/node_modules/bare-crypto/package.json","../../..":"/node_modules/favela-surface/node_modules/bare-crypto/index.js","../../errors":"/node_modules/favela-surface/node_modules/bare-crypto/lib/errors.js","../../key":"/node_modules/favela-surface/node_modules/bare-crypto/lib/key.js","../crypto-key":"/node_modules/favela-surface/node_modules/bare-crypto/lib/web/crypto-key.js"},"/node_modules/favela-surface/node_modules/bare-crypto/lib/web/algorithm/pbkdf2.js":{"#package":"/node_modules/favela-surface/node_modules/bare-crypto/package.json","../../..":"/node_modules/favela-surface/node_modules/bare-crypto/index.js","../../errors":"/node_modules/favela-surface/node_modules/bare-crypto/lib/errors.js","../../key":"/node_modules/favela-surface/node_modules/bare-crypto/lib/key.js","../crypto-key":"/node_modules/favela-surface/node_modules/bare-crypto/lib/web/crypto-key.js"},"/node_modules/favela-surface/node_modules/bare-crypto/lib/web/algorithm/sha.js":{"#package":"/node_modules/favela-surface/node_modules/bare-crypto/package.json","../../..":"/node_modules/favela-surface/node_modules/bare-crypto/index.js"},"/node_modules/favela-surface/node_modules/bare-crypto/lib/web/crypto-key.js":{"#package":"/node_modules/favela-surface/node_modules/bare-crypto/package.json"},"/node_modules/favela-surface/node_modules/bare-crypto/package.json":{},"/node_modules/favela-surface/node_modules/bare-crypto/web.js":{"#package":"/node_modules/favela-surface/node_modules/bare-crypto/package.json",".":"/node_modules/favela-surface/node_modules/bare-crypto/index.js","./lib/errors":"/node_modules/favela-surface/node_modules/bare-crypto/lib/errors.js","./lib/web/algorithm/ed25519":"/node_modules/favela-surface/node_modules/bare-crypto/lib/web/algorithm/ed25519.js","./lib/web/algorithm/hmac":"/node_modules/favela-surface/node_modules/bare-crypto/lib/web/algorithm/hmac.js","./lib/web/algorithm/pbkdf2":"/node_modules/favela-surface/node_modules/bare-crypto/lib/web/algorithm/pbkdf2.js","./lib/web/algorithm/sha":"/node_modules/favela-surface/node_modules/bare-crypto/lib/web/algorithm/sha.js","./lib/web/crypto-key":"/node_modules/favela-surface/node_modules/bare-crypto/lib/web/crypto-key.js"},"/node_modules/favela-surface/node_modules/bare-events/index.js":{"#package":"/node_modules/favela-surface/node_modules/bare-events/package.json","./lib/errors":"/node_modules/favela-surface/node_modules/bare-events/lib/errors.js"},"/node_modules/favela-surface/node_modules/bare-events/lib/errors.js":{"#package":"/node_modules/favela-surface/node_modules/bare-events/package.json"},"/node_modules/favela-surface/node_modules/bare-events/package.json":{},"/node_modules/favela-surface/node_modules/bare-fs/binding.js":{"#package":"/node_modules/favela-surface/node_modules/bare-fs/package.json",".":"linked:libbare-fs.4.8.1.so"},"/node_modules/favela-surface/node_modules/bare-fs/index.js":{"#package":"/node_modules/favela-surface/node_modules/bare-fs/package.json","./binding":"/node_modules/favela-surface/node_modules/bare-fs/binding.js","./lib/constants":"/node_modules/favela-surface/node_modules/bare-fs/lib/constants.js","./lib/errors":"/node_modules/favela-surface/node_modules/bare-fs/lib/errors.js","./promises":"/node_modules/favela-surface/node_modules/bare-fs/promises.js","bare-events":"/node_modules/favela-surface/node_modules/bare-events/index.js","bare-path":"/node_modules/favela-surface/node_modules/bare-path/index.js","bare-stream":"/node_modules/favela-surface/node_modules/bare-stream/index.js","bare-url":"/node_modules/favela-surface/node_modules/bare-url/index.js","fast-fifo":"/node_modules/favela-surface/node_modules/fast-fifo/index.js"},"/node_modules/favela-surface/node_modules/bare-fs/lib/constants.js":{"#package":"/node_modules/favela-surface/node_modules/bare-fs/package.json","../binding":"/node_modules/favela-surface/node_modules/bare-fs/binding.js"},"/node_modules/favela-surface/node_modules/bare-fs/lib/errors.js":{"#package":"/node_modules/favela-surface/node_modules/bare-fs/package.json","../binding":"/node_modules/favela-surface/node_modules/bare-fs/binding.js"},"/node_modules/favela-surface/node_modules/bare-fs/package.json":{},"/node_modules/favela-surface/node_modules/bare-fs/promises.js":{"#package":"/node_modules/favela-surface/node_modules/bare-fs/package.json",".":"/node_modules/favela-surface/node_modules/bare-fs/index.js","bare-events":"/node_modules/favela-surface/node_modules/bare-events/index.js"},"/node_modules/favela-surface/node_modules/bare-inspect/binding.js":{"#package":"/node_modules/favela-surface/node_modules/bare-inspect/package.json",".":"linked:libbare-inspect.3.1.10.so"},"/node_modules/favela-surface/node_modules/bare-inspect/index.js":{"#package":"/node_modules/favela-surface/node_modules/bare-inspect/package.json","./binding":"/node_modules/favela-surface/node_modules/bare-inspect/binding.js","bare-ansi-escapes":"/node_modules/favela-surface/node_modules/bare-ansi-escapes/index.js","bare-type":"/node_modules/favela-surface/node_modules/bare-type/index.js"},"/node_modules/favela-surface/node_modules/bare-inspect/package.json":{},"/node_modules/favela-surface/node_modules/bare-os/binding.js":{"#package":"/node_modules/favela-surface/node_modules/bare-os/package.json",".":"linked:libbare-os.3.9.3.so"},"/node_modules/favela-surface/node_modules/bare-os/index.js":{"#package":"/node_modules/favela-surface/node_modules/bare-os/package.json","./binding":"/node_modules/favela-surface/node_modules/bare-os/binding.js","./lib/constants":"/node_modules/favela-surface/node_modules/bare-os/lib/constants.js","./lib/errors":"/node_modules/favela-surface/node_modules/bare-os/lib/errors.js"},"/node_modules/favela-surface/node_modules/bare-os/lib/constants.js":{"#package":"/node_modules/favela-surface/node_modules/bare-os/package.json","../binding":"/node_modules/favela-surface/node_modules/bare-os/binding.js"},"/node_modules/favela-surface/node_modules/bare-os/lib/errors.js":{"#package":"/node_modules/favela-surface/node_modules/bare-os/package.json"},"/node_modules/favela-surface/node_modules/bare-os/package.json":{},"/node_modules/favela-surface/node_modules/bare-path/binding.js":{"#package":"/node_modules/favela-surface/node_modules/bare-path/package.json",".":"linked:libbare-path.3.1.2.so"},"/node_modules/favela-surface/node_modules/bare-path/index.js":{"#package":"/node_modules/favela-surface/node_modules/bare-path/package.json","./lib/posix":"/node_modules/favela-surface/node_modules/bare-path/lib/posix.js","./lib/win32":"/node_modules/favela-surface/node_modules/bare-path/lib/win32.js"},"/node_modules/favela-surface/node_modules/bare-path/lib/constants.js":{"#package":"/node_modules/favela-surface/node_modules/bare-path/package.json"},"/node_modules/favela-surface/node_modules/bare-path/lib/posix.js":{"#package":"/node_modules/favela-surface/node_modules/bare-path/package.json","../binding":"/node_modules/favela-surface/node_modules/bare-path/binding.js","./constants":"/node_modules/favela-surface/node_modules/bare-path/lib/constants.js","./shared":"/node_modules/favela-surface/node_modules/bare-path/lib/shared.js","./win32":"/node_modules/favela-surface/node_modules/bare-path/lib/win32.js"},"/node_modules/favela-surface/node_modules/bare-path/lib/shared.js":{"#package":"/node_modules/favela-surface/node_modules/bare-path/package.json","./constants":"/node_modules/favela-surface/node_modules/bare-path/lib/constants.js"},"/node_modules/favela-surface/node_modules/bare-path/lib/win32.js":{"#package":"/node_modules/favela-surface/node_modules/bare-path/package.json","../binding":"/node_modules/favela-surface/node_modules/bare-path/binding.js","./constants":"/node_modules/favela-surface/node_modules/bare-path/lib/constants.js","./posix":"/node_modules/favela-surface/node_modules/bare-path/lib/posix.js","./shared":"/node_modules/favela-surface/node_modules/bare-path/lib/shared.js"},"/node_modules/favela-surface/node_modules/bare-path/package.json":{},"/node_modules/favela-surface/node_modules/bare-stream/index.js":{"#package":"/node_modules/favela-surface/node_modules/bare-stream/package.json","./web":"/node_modules/favela-surface/node_modules/bare-stream/web.js","b4a":"/node_modules/favela-surface/node_modules/b4a/index.js","streamx":"/node_modules/favela-surface/node_modules/streamx/index.js"},"/node_modules/favela-surface/node_modules/bare-stream/package.json":{},"/node_modules/favela-surface/node_modules/bare-stream/web.js":{"#package":"/node_modules/favela-surface/node_modules/bare-stream/package.json","streamx":"/node_modules/favela-surface/node_modules/streamx/index.js","teex":"/node_modules/favela-surface/node_modules/teex/index.js"},"/node_modules/favela-surface/node_modules/bare-type/binding.js":{"#package":"/node_modules/favela-surface/node_modules/bare-type/package.json",".":"linked:libbare-type.1.1.1.so"},"/node_modules/favela-surface/node_modules/bare-type/index.js":{"#package":"/node_modules/favela-surface/node_modules/bare-type/package.json","./binding":"/node_modules/favela-surface/node_modules/bare-type/binding.js"},"/node_modules/favela-surface/node_modules/bare-type/package.json":{},"/node_modules/favela-surface/node_modules/bare-url/binding.js":{"#package":"/node_modules/favela-surface/node_modules/bare-url/package.json",".":"linked:libbare-url.2.5.4.so"},"/node_modules/favela-surface/node_modules/bare-url/index.js":{"#package":"/node_modules/favela-surface/node_modules/bare-url/package.json","./binding":"/node_modules/favela-surface/node_modules/bare-url/binding.js","./lib/errors":"/node_modules/favela-surface/node_modules/bare-url/lib/errors.js","./lib/url-search-params":"/node_modules/favela-surface/node_modules/bare-url/lib/url-search-params.js","bare-path":"/node_modules/favela-surface/node_modules/bare-path/index.js"},"/node_modules/favela-surface/node_modules/bare-url/lib/errors.js":{"#package":"/node_modules/favela-surface/node_modules/bare-url/package.json"},"/node_modules/favela-surface/node_modules/bare-url/lib/url-search-params.js":{"#package":"/node_modules/favela-surface/node_modules/bare-url/package.json"},"/node_modules/favela-surface/node_modules/bare-url/package.json":{},"/node_modules/favela-surface/node_modules/events-universal/bare.js":{"#package":"/node_modules/favela-surface/node_modules/events-universal/package.json","bare-events":"/node_modules/favela-surface/node_modules/bare-events/index.js"},"/node_modules/favela-surface/node_modules/events-universal/package.json":{},"/node_modules/favela-surface/node_modules/fast-fifo/fixed-size.js":{"#package":"/node_modules/favela-surface/node_modules/fast-fifo/package.json"},"/node_modules/favela-surface/node_modules/fast-fifo/index.js":{"#package":"/node_modules/favela-surface/node_modules/fast-fifo/package.json","./fixed-size":"/node_modules/favela-surface/node_modules/fast-fifo/fixed-size.js"},"/node_modules/favela-surface/node_modules/fast-fifo/package.json":{},"/node_modules/favela-surface/node_modules/favela-markdown/lib/dialect.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/package.json","./uri.js":"/node_modules/favela-surface/node_modules/favela-markdown/lib/uri.js","mdast-util-directive":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-directive/index.js","mdast-util-gfm-footnote":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-gfm-footnote/index.js","mdast-util-gfm-strikethrough":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-gfm-strikethrough/index.js","mdast-util-gfm-table":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-gfm-table/index.js","micromark-extension-directive":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-extension-directive/index.js","micromark-extension-gfm-footnote":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-extension-gfm-footnote/index.js","micromark-extension-gfm-strikethrough":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-extension-gfm-strikethrough/index.js","micromark-extension-gfm-table":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-extension-gfm-table/index.js"},"/node_modules/favela-surface/node_modules/favela-markdown/lib/render.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/package.json","./dialect.js":"/node_modules/favela-surface/node_modules/favela-markdown/lib/dialect.js","mdast-util-from-markdown":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-from-markdown/index.js"},"/node_modules/favela-surface/node_modules/favela-markdown/lib/uri.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/package.json"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/ccount/index.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/ccount/package.json"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/ccount/package.json":{},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/character-entities-html4/index.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/character-entities-html4/package.json"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/character-entities-html4/package.json":{},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/character-entities-legacy/index.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/character-entities-legacy/package.json"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/character-entities-legacy/package.json":{},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/character-entities/index.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/character-entities/package.json"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/character-entities/package.json":{},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/character-reference-invalid/index.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/character-reference-invalid/package.json"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/character-reference-invalid/package.json":{},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/decode-named-character-reference/index.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/decode-named-character-reference/package.json","character-entities":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/character-entities/index.js"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/decode-named-character-reference/package.json":{},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/devlop/lib/default.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/devlop/package.json"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/devlop/package.json":{},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/is-alphabetical/index.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/is-alphabetical/package.json"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/is-alphabetical/package.json":{},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/is-alphanumerical/index.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/is-alphanumerical/package.json","is-alphabetical":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/is-alphabetical/index.js","is-decimal":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/is-decimal/index.js"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/is-alphanumerical/package.json":{},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/is-decimal/index.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/is-decimal/package.json"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/is-decimal/package.json":{},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/is-hexadecimal/index.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/is-hexadecimal/package.json"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/is-hexadecimal/package.json":{},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/longest-streak/index.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/longest-streak/package.json"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/longest-streak/package.json":{},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/markdown-table/index.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/markdown-table/package.json"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/markdown-table/package.json":{},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-directive/index.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-directive/package.json","./lib/index.js":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-directive/lib/index.js"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-directive/lib/index.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-directive/package.json","ccount":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/ccount/index.js","devlop":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/devlop/lib/default.js","parse-entities":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/parse-entities/index.js","stringify-entities":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/stringify-entities/index.js","unist-util-visit-parents":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/unist-util-visit-parents/index.js"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-directive/package.json":{},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-from-markdown/index.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-from-markdown/package.json","./lib/index.js":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-from-markdown/lib/index.js"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-from-markdown/lib/index.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-from-markdown/package.json","decode-named-character-reference":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/decode-named-character-reference/index.js","mdast-util-to-string":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-string/index.js","micromark":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark/index.js","micromark-util-decode-numeric-character-reference":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-util-decode-numeric-character-reference/index.js","micromark-util-decode-string":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-util-decode-string/index.js","micromark-util-normalize-identifier":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-util-normalize-identifier/index.js","unist-util-stringify-position":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/unist-util-stringify-position/index.js"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-from-markdown/package.json":{},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-gfm-footnote/index.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-gfm-footnote/package.json","./lib/index.js":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-gfm-footnote/lib/index.js"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-gfm-footnote/lib/index.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-gfm-footnote/package.json","devlop":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/devlop/lib/default.js","micromark-util-normalize-identifier":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-util-normalize-identifier/index.js"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-gfm-footnote/package.json":{},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-gfm-strikethrough/index.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-gfm-strikethrough/package.json","./lib/index.js":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-gfm-strikethrough/lib/index.js"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-gfm-strikethrough/lib/index.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-gfm-strikethrough/package.json"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-gfm-strikethrough/package.json":{},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-gfm-table/index.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-gfm-table/package.json","./lib/index.js":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-gfm-table/lib/index.js"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-gfm-table/lib/index.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-gfm-table/package.json","devlop":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/devlop/lib/default.js","markdown-table":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/markdown-table/index.js","mdast-util-to-markdown":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/index.js"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-gfm-table/package.json":{},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-phrasing/index.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-phrasing/package.json","./lib/index.js":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-phrasing/lib/index.js"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-phrasing/lib/index.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-phrasing/package.json","unist-util-is":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/unist-util-is/index.js"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-phrasing/package.json":{},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/index.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/package.json","./lib/handle/index.js":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/handle/index.js","./lib/index.js":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/index.js"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/configure.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/package.json"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/handle/blockquote.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/package.json"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/handle/break.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/package.json","../util/pattern-in-scope.js":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/util/pattern-in-scope.js"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/handle/code.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/package.json","../util/check-fence.js":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/util/check-fence.js","../util/format-code-as-indented.js":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/util/format-code-as-indented.js","longest-streak":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/longest-streak/index.js"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/handle/definition.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/package.json","../util/check-quote.js":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/util/check-quote.js"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/handle/emphasis.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/package.json","../util/check-emphasis.js":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/util/check-emphasis.js","../util/encode-character-reference.js":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/util/encode-character-reference.js","../util/encode-info.js":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/util/encode-info.js"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/handle/heading.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/package.json","../util/encode-character-reference.js":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/util/encode-character-reference.js","../util/format-heading-as-setext.js":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/util/format-heading-as-setext.js"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/handle/html.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/package.json"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/handle/image-reference.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/package.json"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/handle/image.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/package.json","../util/check-quote.js":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/util/check-quote.js"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/handle/index.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/package.json","./blockquote.js":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/handle/blockquote.js","./break.js":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/handle/break.js","./code.js":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/handle/code.js","./definition.js":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/handle/definition.js","./emphasis.js":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/handle/emphasis.js","./heading.js":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/handle/heading.js","./html.js":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/handle/html.js","./image-reference.js":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/handle/image-reference.js","./image.js":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/handle/image.js","./inline-code.js":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/handle/inline-code.js","./link-reference.js":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/handle/link-reference.js","./link.js":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/handle/link.js","./list-item.js":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/handle/list-item.js","./list.js":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/handle/list.js","./paragraph.js":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/handle/paragraph.js","./root.js":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/handle/root.js","./strong.js":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/handle/strong.js","./text.js":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/handle/text.js","./thematic-break.js":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/handle/thematic-break.js"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/handle/inline-code.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/package.json"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/handle/link-reference.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/package.json"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/handle/link.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/package.json","../util/check-quote.js":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/util/check-quote.js","../util/format-link-as-autolink.js":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/util/format-link-as-autolink.js"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/handle/list-item.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/package.json","../util/check-bullet.js":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/util/check-bullet.js","../util/check-list-item-indent.js":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/util/check-list-item-indent.js"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/handle/list.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/package.json","../util/check-bullet-ordered.js":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/util/check-bullet-ordered.js","../util/check-bullet-other.js":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/util/check-bullet-other.js","../util/check-bullet.js":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/util/check-bullet.js","../util/check-rule.js":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/util/check-rule.js"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/handle/paragraph.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/package.json"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/handle/root.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/package.json","mdast-util-phrasing":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-phrasing/index.js"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/handle/strong.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/package.json","../util/check-strong.js":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/util/check-strong.js","../util/encode-character-reference.js":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/util/encode-character-reference.js","../util/encode-info.js":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/util/encode-info.js"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/handle/text.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/package.json"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/handle/thematic-break.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/package.json","../util/check-rule-repetition.js":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/util/check-rule-repetition.js","../util/check-rule.js":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/util/check-rule.js"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/index.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/package.json","./configure.js":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/configure.js","./handle/index.js":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/handle/index.js","./join.js":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/join.js","./unsafe.js":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/unsafe.js","./util/association.js":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/util/association.js","./util/compile-pattern.js":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/util/compile-pattern.js","./util/container-flow.js":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/util/container-flow.js","./util/container-phrasing.js":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/util/container-phrasing.js","./util/indent-lines.js":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/util/indent-lines.js","./util/safe.js":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/util/safe.js","./util/track.js":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/util/track.js","zwitch":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/zwitch/index.js"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/join.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/package.json","./util/format-code-as-indented.js":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/util/format-code-as-indented.js","./util/format-heading-as-setext.js":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/util/format-heading-as-setext.js"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/unsafe.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/package.json"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/util/association.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/package.json","micromark-util-decode-string":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-util-decode-string/index.js"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/util/check-bullet-ordered.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/package.json"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/util/check-bullet-other.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/package.json","./check-bullet.js":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/util/check-bullet.js"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/util/check-bullet.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/package.json"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/util/check-emphasis.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/package.json"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/util/check-fence.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/package.json"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/util/check-list-item-indent.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/package.json"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/util/check-quote.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/package.json"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/util/check-rule-repetition.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/package.json"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/util/check-rule.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/package.json"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/util/check-strong.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/package.json"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/util/compile-pattern.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/package.json"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/util/container-flow.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/package.json"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/util/container-phrasing.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/package.json","./encode-character-reference.js":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/util/encode-character-reference.js"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/util/encode-character-reference.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/package.json"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/util/encode-info.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/package.json","micromark-util-classify-character":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-util-classify-character/index.js"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/util/format-code-as-indented.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/package.json"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/util/format-heading-as-setext.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/package.json","mdast-util-to-string":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-string/index.js","unist-util-visit":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/unist-util-visit/index.js"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/util/format-link-as-autolink.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/package.json","mdast-util-to-string":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-string/index.js"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/util/indent-lines.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/package.json"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/util/pattern-in-scope.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/package.json"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/util/safe.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/package.json","./encode-character-reference.js":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/util/encode-character-reference.js","./pattern-in-scope.js":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/util/pattern-in-scope.js"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/util/track.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/package.json"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/package.json":{},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-string/index.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-string/package.json","./lib/index.js":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-string/lib/index.js"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-string/lib/index.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-string/package.json"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-string/package.json":{},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-core-commonmark/index.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-core-commonmark/package.json","./lib/attention.js":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-core-commonmark/lib/attention.js","./lib/autolink.js":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-core-commonmark/lib/autolink.js","./lib/blank-line.js":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-core-commonmark/lib/blank-line.js","./lib/block-quote.js":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-core-commonmark/lib/block-quote.js","./lib/character-escape.js":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-core-commonmark/lib/character-escape.js","./lib/character-reference.js":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-core-commonmark/lib/character-reference.js","./lib/code-fenced.js":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-core-commonmark/lib/code-fenced.js","./lib/code-indented.js":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-core-commonmark/lib/code-indented.js","./lib/code-text.js":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-core-commonmark/lib/code-text.js","./lib/content.js":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-core-commonmark/lib/content.js","./lib/definition.js":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-core-commonmark/lib/definition.js","./lib/hard-break-escape.js":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-core-commonmark/lib/hard-break-escape.js","./lib/heading-atx.js":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-core-commonmark/lib/heading-atx.js","./lib/html-flow.js":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-core-commonmark/lib/html-flow.js","./lib/html-text.js":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-core-commonmark/lib/html-text.js","./lib/label-end.js":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-core-commonmark/lib/label-end.js","./lib/label-start-image.js":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-core-commonmark/lib/label-start-image.js","./lib/label-start-link.js":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-core-commonmark/lib/label-start-link.js","./lib/line-ending.js":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-core-commonmark/lib/line-ending.js","./lib/list.js":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-core-commonmark/lib/list.js","./lib/setext-underline.js":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-core-commonmark/lib/setext-underline.js","./lib/thematic-break.js":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-core-commonmark/lib/thematic-break.js"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-core-commonmark/lib/attention.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-core-commonmark/package.json","micromark-util-chunked":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-util-chunked/index.js","micromark-util-classify-character":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-util-classify-character/index.js","micromark-util-resolve-all":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-util-resolve-all/index.js"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-core-commonmark/lib/autolink.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-core-commonmark/package.json","micromark-util-character":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-util-character/index.js"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-core-commonmark/lib/blank-line.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-core-commonmark/package.json","micromark-factory-space":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-factory-space/index.js","micromark-util-character":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-util-character/index.js"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-core-commonmark/lib/block-quote.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-core-commonmark/package.json","micromark-factory-space":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-factory-space/index.js","micromark-util-character":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-util-character/index.js"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-core-commonmark/lib/character-escape.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-core-commonmark/package.json","micromark-util-character":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-util-character/index.js"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-core-commonmark/lib/character-reference.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-core-commonmark/package.json","decode-named-character-reference":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/decode-named-character-reference/index.js","micromark-util-character":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-util-character/index.js"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-core-commonmark/lib/code-fenced.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-core-commonmark/package.json","micromark-factory-space":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-factory-space/index.js","micromark-util-character":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-util-character/index.js"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-core-commonmark/lib/code-indented.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-core-commonmark/package.json","micromark-factory-space":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-factory-space/index.js","micromark-util-character":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-util-character/index.js"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-core-commonmark/lib/code-text.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-core-commonmark/package.json","micromark-util-character":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-util-character/index.js"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-core-commonmark/lib/content.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-core-commonmark/package.json","micromark-factory-space":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-factory-space/index.js","micromark-util-character":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-util-character/index.js","micromark-util-subtokenize":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-util-subtokenize/index.js"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-core-commonmark/lib/definition.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-core-commonmark/package.json","micromark-factory-destination":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-factory-destination/index.js","micromark-factory-label":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-factory-label/index.js","micromark-factory-space":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-factory-space/index.js","micromark-factory-title":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-factory-title/index.js","micromark-factory-whitespace":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-factory-whitespace/index.js","micromark-util-character":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-util-character/index.js","micromark-util-normalize-identifier":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-util-normalize-identifier/index.js"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-core-commonmark/lib/hard-break-escape.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-core-commonmark/package.json","micromark-util-character":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-util-character/index.js"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-core-commonmark/lib/heading-atx.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-core-commonmark/package.json","micromark-factory-space":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-factory-space/index.js","micromark-util-character":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-util-character/index.js","micromark-util-chunked":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-util-chunked/index.js"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-core-commonmark/lib/html-flow.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-core-commonmark/package.json","./blank-line.js":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-core-commonmark/lib/blank-line.js","micromark-util-character":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-util-character/index.js","micromark-util-html-tag-name":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-util-html-tag-name/index.js"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-core-commonmark/lib/html-text.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-core-commonmark/package.json","micromark-factory-space":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-factory-space/index.js","micromark-util-character":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-util-character/index.js"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-core-commonmark/lib/label-end.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-core-commonmark/package.json","micromark-factory-destination":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-factory-destination/index.js","micromark-factory-label":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-factory-label/index.js","micromark-factory-title":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-factory-title/index.js","micromark-factory-whitespace":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-factory-whitespace/index.js","micromark-util-character":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-util-character/index.js","micromark-util-chunked":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-util-chunked/index.js","micromark-util-normalize-identifier":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-util-normalize-identifier/index.js","micromark-util-resolve-all":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-util-resolve-all/index.js"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-core-commonmark/lib/label-start-image.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-core-commonmark/package.json","./label-end.js":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-core-commonmark/lib/label-end.js"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-core-commonmark/lib/label-start-link.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-core-commonmark/package.json","./label-end.js":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-core-commonmark/lib/label-end.js"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-core-commonmark/lib/line-ending.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-core-commonmark/package.json","micromark-factory-space":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-factory-space/index.js","micromark-util-character":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-util-character/index.js"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-core-commonmark/lib/list.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-core-commonmark/package.json","./blank-line.js":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-core-commonmark/lib/blank-line.js","./thematic-break.js":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-core-commonmark/lib/thematic-break.js","micromark-factory-space":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-factory-space/index.js","micromark-util-character":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-util-character/index.js"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-core-commonmark/lib/setext-underline.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-core-commonmark/package.json","micromark-factory-space":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-factory-space/index.js","micromark-util-character":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-util-character/index.js"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-core-commonmark/lib/thematic-break.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-core-commonmark/package.json","micromark-factory-space":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-factory-space/index.js","micromark-util-character":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-util-character/index.js"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-core-commonmark/package.json":{},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-extension-directive/index.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-extension-directive/package.json","./lib/html.js":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-extension-directive/lib/html.js","./lib/syntax.js":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-extension-directive/lib/syntax.js"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-extension-directive/lib/directive-container.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-extension-directive/package.json","./factory-attributes.js":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-extension-directive/lib/factory-attributes.js","./factory-label.js":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-extension-directive/lib/factory-label.js","./factory-name.js":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-extension-directive/lib/factory-name.js","micromark-factory-space":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-factory-space/index.js","micromark-util-character":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-util-character/index.js"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-extension-directive/lib/directive-leaf.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-extension-directive/package.json","./factory-attributes.js":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-extension-directive/lib/factory-attributes.js","./factory-label.js":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-extension-directive/lib/factory-label.js","./factory-name.js":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-extension-directive/lib/factory-name.js","micromark-factory-space":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-factory-space/index.js","micromark-util-character":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-util-character/index.js"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-extension-directive/lib/directive-text.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-extension-directive/package.json","./factory-attributes.js":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-extension-directive/lib/factory-attributes.js","./factory-label.js":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-extension-directive/lib/factory-label.js","./factory-name.js":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-extension-directive/lib/factory-name.js"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-extension-directive/lib/factory-attributes.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-extension-directive/package.json","micromark-factory-space":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-factory-space/index.js","micromark-factory-whitespace":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-factory-whitespace/index.js","micromark-util-character":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-util-character/index.js"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-extension-directive/lib/factory-label.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-extension-directive/package.json","micromark-util-character":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-util-character/index.js"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-extension-directive/lib/factory-name.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-extension-directive/package.json","micromark-util-character":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-util-character/index.js"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-extension-directive/lib/html.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-extension-directive/package.json","parse-entities":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/parse-entities/index.js"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-extension-directive/lib/syntax.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-extension-directive/package.json","./directive-container.js":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-extension-directive/lib/directive-container.js","./directive-leaf.js":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-extension-directive/lib/directive-leaf.js","./directive-text.js":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-extension-directive/lib/directive-text.js"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-extension-directive/package.json":{},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-extension-gfm-footnote/index.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-extension-gfm-footnote/package.json","./lib/html.js":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-extension-gfm-footnote/lib/html.js","./lib/syntax.js":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-extension-gfm-footnote/lib/syntax.js"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-extension-gfm-footnote/lib/html.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-extension-gfm-footnote/package.json","micromark-util-normalize-identifier":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-util-normalize-identifier/index.js","micromark-util-sanitize-uri":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-util-sanitize-uri/index.js"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-extension-gfm-footnote/lib/syntax.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-extension-gfm-footnote/package.json","micromark-core-commonmark":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-core-commonmark/index.js","micromark-factory-space":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-factory-space/index.js","micromark-util-character":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-util-character/index.js","micromark-util-normalize-identifier":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-util-normalize-identifier/index.js"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-extension-gfm-footnote/package.json":{},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-extension-gfm-strikethrough/index.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-extension-gfm-strikethrough/package.json","./lib/html.js":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-extension-gfm-strikethrough/lib/html.js","./lib/syntax.js":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-extension-gfm-strikethrough/lib/syntax.js"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-extension-gfm-strikethrough/lib/html.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-extension-gfm-strikethrough/package.json"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-extension-gfm-strikethrough/lib/syntax.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-extension-gfm-strikethrough/package.json","micromark-util-chunked":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-util-chunked/index.js","micromark-util-classify-character":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-util-classify-character/index.js","micromark-util-resolve-all":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-util-resolve-all/index.js"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-extension-gfm-strikethrough/package.json":{},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-extension-gfm-table/index.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-extension-gfm-table/package.json","./lib/html.js":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-extension-gfm-table/lib/html.js","./lib/syntax.js":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-extension-gfm-table/lib/syntax.js"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-extension-gfm-table/lib/edit-map.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-extension-gfm-table/package.json"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-extension-gfm-table/lib/html.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-extension-gfm-table/package.json"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-extension-gfm-table/lib/infer.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-extension-gfm-table/package.json"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-extension-gfm-table/lib/syntax.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-extension-gfm-table/package.json","./edit-map.js":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-extension-gfm-table/lib/edit-map.js","./infer.js":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-extension-gfm-table/lib/infer.js","micromark-factory-space":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-factory-space/index.js","micromark-util-character":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-util-character/index.js"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-extension-gfm-table/package.json":{},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-factory-destination/index.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-factory-destination/package.json","micromark-util-character":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-util-character/index.js"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-factory-destination/package.json":{},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-factory-label/index.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-factory-label/package.json","micromark-util-character":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-util-character/index.js"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-factory-label/package.json":{},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-factory-space/index.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-factory-space/package.json","micromark-util-character":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-util-character/index.js"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-factory-space/package.json":{},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-factory-title/index.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-factory-title/package.json","micromark-factory-space":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-factory-space/index.js","micromark-util-character":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-util-character/index.js"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-factory-title/package.json":{},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-factory-whitespace/index.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-factory-whitespace/package.json","micromark-factory-space":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-factory-space/index.js","micromark-util-character":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-util-character/index.js"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-factory-whitespace/package.json":{},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-util-character/index.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-util-character/package.json"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-util-character/package.json":{},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-util-chunked/index.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-util-chunked/package.json"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-util-chunked/package.json":{},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-util-classify-character/index.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-util-classify-character/package.json","micromark-util-character":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-util-character/index.js"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-util-classify-character/package.json":{},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-util-combine-extensions/index.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-util-combine-extensions/package.json","micromark-util-chunked":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-util-chunked/index.js"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-util-combine-extensions/package.json":{},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-util-decode-numeric-character-reference/index.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-util-decode-numeric-character-reference/package.json"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-util-decode-numeric-character-reference/package.json":{},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-util-decode-string/index.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-util-decode-string/package.json","decode-named-character-reference":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/decode-named-character-reference/index.js","micromark-util-decode-numeric-character-reference":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-util-decode-numeric-character-reference/index.js"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-util-decode-string/package.json":{},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-util-encode/index.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-util-encode/package.json"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-util-encode/package.json":{},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-util-html-tag-name/index.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-util-html-tag-name/package.json"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-util-html-tag-name/package.json":{},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-util-normalize-identifier/index.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-util-normalize-identifier/package.json"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-util-normalize-identifier/package.json":{},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-util-resolve-all/index.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-util-resolve-all/package.json"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-util-resolve-all/package.json":{},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-util-sanitize-uri/index.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-util-sanitize-uri/package.json","micromark-util-character":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-util-character/index.js","micromark-util-encode":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-util-encode/index.js"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-util-sanitize-uri/package.json":{},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-util-subtokenize/index.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-util-subtokenize/package.json","./lib/splice-buffer.js":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-util-subtokenize/lib/splice-buffer.js","micromark-util-chunked":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-util-chunked/index.js"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-util-subtokenize/lib/splice-buffer.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-util-subtokenize/package.json"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-util-subtokenize/package.json":{},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark/index.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark/package.json","./lib/compile.js":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark/lib/compile.js","./lib/parse.js":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark/lib/parse.js","./lib/postprocess.js":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark/lib/postprocess.js","./lib/preprocess.js":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark/lib/preprocess.js"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark/lib/compile.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark/package.json","decode-named-character-reference":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/decode-named-character-reference/index.js","micromark-util-chunked":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-util-chunked/index.js","micromark-util-combine-extensions":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-util-combine-extensions/index.js","micromark-util-decode-numeric-character-reference":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-util-decode-numeric-character-reference/index.js","micromark-util-encode":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-util-encode/index.js","micromark-util-normalize-identifier":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-util-normalize-identifier/index.js","micromark-util-sanitize-uri":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-util-sanitize-uri/index.js"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark/lib/constructs.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark/package.json","./initialize/text.js":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark/lib/initialize/text.js","micromark-core-commonmark":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-core-commonmark/index.js"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark/lib/create-tokenizer.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark/package.json","micromark-util-character":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-util-character/index.js","micromark-util-chunked":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-util-chunked/index.js","micromark-util-resolve-all":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-util-resolve-all/index.js"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark/lib/initialize/content.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark/package.json","micromark-factory-space":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-factory-space/index.js","micromark-util-character":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-util-character/index.js"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark/lib/initialize/document.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark/package.json","micromark-factory-space":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-factory-space/index.js","micromark-util-character":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-util-character/index.js","micromark-util-chunked":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-util-chunked/index.js"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark/lib/initialize/flow.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark/package.json","micromark-core-commonmark":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-core-commonmark/index.js","micromark-factory-space":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-factory-space/index.js","micromark-util-character":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-util-character/index.js"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark/lib/initialize/text.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark/package.json"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark/lib/parse.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark/package.json","./constructs.js":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark/lib/constructs.js","./create-tokenizer.js":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark/lib/create-tokenizer.js","./initialize/content.js":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark/lib/initialize/content.js","./initialize/document.js":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark/lib/initialize/document.js","./initialize/flow.js":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark/lib/initialize/flow.js","./initialize/text.js":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark/lib/initialize/text.js","micromark-util-combine-extensions":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-util-combine-extensions/index.js"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark/lib/postprocess.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark/package.json","micromark-util-subtokenize":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-util-subtokenize/index.js"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark/lib/preprocess.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark/package.json"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark/package.json":{},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/parse-entities/index.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/parse-entities/package.json","./lib/index.js":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/parse-entities/lib/index.js"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/parse-entities/lib/index.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/parse-entities/package.json","character-entities-legacy":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/character-entities-legacy/index.js","character-reference-invalid":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/character-reference-invalid/index.js","decode-named-character-reference":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/decode-named-character-reference/index.js","is-alphanumerical":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/is-alphanumerical/index.js","is-decimal":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/is-decimal/index.js","is-hexadecimal":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/is-hexadecimal/index.js"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/parse-entities/package.json":{},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/stringify-entities/index.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/stringify-entities/package.json","./lib/index.js":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/stringify-entities/lib/index.js"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/stringify-entities/lib/constant/dangerous.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/stringify-entities/package.json"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/stringify-entities/lib/core.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/stringify-entities/package.json"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/stringify-entities/lib/index.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/stringify-entities/package.json","./core.js":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/stringify-entities/lib/core.js","./util/format-basic.js":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/stringify-entities/lib/util/format-basic.js","./util/format-smart.js":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/stringify-entities/lib/util/format-smart.js"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/stringify-entities/lib/util/format-basic.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/stringify-entities/package.json"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/stringify-entities/lib/util/format-smart.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/stringify-entities/package.json","./to-decimal.js":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/stringify-entities/lib/util/to-decimal.js","./to-hexadecimal.js":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/stringify-entities/lib/util/to-hexadecimal.js","./to-named.js":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/stringify-entities/lib/util/to-named.js"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/stringify-entities/lib/util/to-decimal.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/stringify-entities/package.json"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/stringify-entities/lib/util/to-hexadecimal.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/stringify-entities/package.json"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/stringify-entities/lib/util/to-named.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/stringify-entities/package.json","../constant/dangerous.js":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/stringify-entities/lib/constant/dangerous.js","character-entities-html4":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/character-entities-html4/index.js","character-entities-legacy":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/character-entities-legacy/index.js"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/stringify-entities/package.json":{},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/unist-util-is/index.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/unist-util-is/package.json","./lib/index.js":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/unist-util-is/lib/index.js"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/unist-util-is/lib/index.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/unist-util-is/package.json"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/unist-util-is/package.json":{},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/unist-util-stringify-position/index.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/unist-util-stringify-position/package.json","./lib/index.js":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/unist-util-stringify-position/lib/index.js"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/unist-util-stringify-position/lib/index.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/unist-util-stringify-position/package.json"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/unist-util-stringify-position/package.json":{},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/unist-util-visit-parents/index.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/unist-util-visit-parents/package.json","./lib/index.js":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/unist-util-visit-parents/lib/index.js"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/unist-util-visit-parents/lib/color.node.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/unist-util-visit-parents/package.json"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/unist-util-visit-parents/lib/index.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/unist-util-visit-parents/package.json","unist-util-is":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/unist-util-is/index.js","unist-util-visit-parents/do-not-use-color":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/unist-util-visit-parents/lib/color.node.js"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/unist-util-visit-parents/package.json":{},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/unist-util-visit/index.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/unist-util-visit/package.json","./lib/index.js":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/unist-util-visit/lib/index.js"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/unist-util-visit/lib/index.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/unist-util-visit/package.json","unist-util-visit-parents":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/unist-util-visit-parents/index.js"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/unist-util-visit/package.json":{},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/zwitch/index.js":{"#package":"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/zwitch/package.json"},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/zwitch/package.json":{},"/node_modules/favela-surface/node_modules/favela-markdown/package.json":{},"/node_modules/favela-surface/node_modules/streamx/index.js":{"#package":"/node_modules/favela-surface/node_modules/streamx/package.json","./lib/errors.js":"/node_modules/favela-surface/node_modules/streamx/lib/errors.js","events-universal":"/node_modules/favela-surface/node_modules/events-universal/bare.js","fast-fifo":"/node_modules/favela-surface/node_modules/fast-fifo/index.js","text-decoder":"/node_modules/favela-surface/node_modules/text-decoder/index.js"},"/node_modules/favela-surface/node_modules/streamx/lib/errors.js":{"#package":"/node_modules/favela-surface/node_modules/streamx/package.json"},"/node_modules/favela-surface/node_modules/streamx/package.json":{},"/node_modules/favela-surface/node_modules/teex/index.js":{"#package":"/node_modules/favela-surface/node_modules/teex/package.json","streamx":"/node_modules/favela-surface/node_modules/streamx/index.js"},"/node_modules/favela-surface/node_modules/teex/package.json":{},"/node_modules/favela-surface/node_modules/text-decoder/index.js":{"#package":"/node_modules/favela-surface/node_modules/text-decoder/package.json","./lib/pass-through-decoder":"/node_modules/favela-surface/node_modules/text-decoder/lib/pass-through-decoder.js","./lib/utf8-decoder":"/node_modules/favela-surface/node_modules/text-decoder/lib/utf8-decoder.js"},"/node_modules/favela-surface/node_modules/text-decoder/lib/pass-through-decoder.js":{"#package":"/node_modules/favela-surface/node_modules/text-decoder/package.json","b4a":"/node_modules/favela-surface/node_modules/b4a/index.js"},"/node_modules/favela-surface/node_modules/text-decoder/lib/utf8-decoder.js":{"#package":"/node_modules/favela-surface/node_modules/text-decoder/package.json","b4a":"/node_modules/favela-surface/node_modules/b4a/index.js"},"/node_modules/favela-surface/node_modules/text-decoder/package.json":{},"/node_modules/favela-surface/package.json":{},"/node_modules/hyper-overlay/lib/tree.js":{"#fs":"/node_modules/hyper-overlay/node_modules/bare-fs/index.js","#package":"/node_modules/hyper-overlay/package.json","#path":"/node_modules/hyper-overlay/node_modules/bare-path/index.js","compact-encoding":"/node_modules/hyper-overlay/node_modules/compact-encoding/index.js","hypercore-crypto":"/node_modules/hyper-overlay/node_modules/hypercore-crypto/index.js"},"/node_modules/hyper-overlay/node_modules/b4a/index.js":{"#package":"/node_modules/hyper-overlay/node_modules/b4a/package.json"},"/node_modules/hyper-overlay/node_modules/b4a/package.json":{},"/node_modules/hyper-overlay/node_modules/bare-ansi-escapes/index.js":{"#package":"/node_modules/hyper-overlay/node_modules/bare-ansi-escapes/package.json"},"/node_modules/hyper-overlay/node_modules/bare-ansi-escapes/package.json":{},"/node_modules/hyper-overlay/node_modules/bare-assert/index.js":{"#package":"/node_modules/hyper-overlay/node_modules/bare-assert/package.json","bare-inspect":"/node_modules/hyper-overlay/node_modules/bare-inspect/index.js"},"/node_modules/hyper-overlay/node_modules/bare-assert/package.json":{},"/node_modules/hyper-overlay/node_modules/bare-events/index.js":{"#package":"/node_modules/hyper-overlay/node_modules/bare-events/package.json","./lib/errors":"/node_modules/hyper-overlay/node_modules/bare-events/lib/errors.js"},"/node_modules/hyper-overlay/node_modules/bare-events/lib/errors.js":{"#package":"/node_modules/hyper-overlay/node_modules/bare-events/package.json"},"/node_modules/hyper-overlay/node_modules/bare-events/package.json":{},"/node_modules/hyper-overlay/node_modules/bare-fs/binding.js":{"#package":"/node_modules/hyper-overlay/node_modules/bare-fs/package.json",".":"linked:libbare-fs.4.7.2.so"},"/node_modules/hyper-overlay/node_modules/bare-fs/index.js":{"#package":"/node_modules/hyper-overlay/node_modules/bare-fs/package.json","./binding":"/node_modules/hyper-overlay/node_modules/bare-fs/binding.js","./lib/constants":"/node_modules/hyper-overlay/node_modules/bare-fs/lib/constants.js","./lib/errors":"/node_modules/hyper-overlay/node_modules/bare-fs/lib/errors.js","./promises":"/node_modules/hyper-overlay/node_modules/bare-fs/promises.js","bare-events":"/node_modules/hyper-overlay/node_modules/bare-events/index.js","bare-path":"/node_modules/hyper-overlay/node_modules/bare-path/index.js","bare-stream":"/node_modules/hyper-overlay/node_modules/bare-stream/index.js","bare-url":"/node_modules/hyper-overlay/node_modules/bare-url/index.js","fast-fifo":"/node_modules/hyper-overlay/node_modules/fast-fifo/index.js"},"/node_modules/hyper-overlay/node_modules/bare-fs/lib/constants.js":{"#package":"/node_modules/hyper-overlay/node_modules/bare-fs/package.json","../binding":"/node_modules/hyper-overlay/node_modules/bare-fs/binding.js"},"/node_modules/hyper-overlay/node_modules/bare-fs/lib/errors.js":{"#package":"/node_modules/hyper-overlay/node_modules/bare-fs/package.json","bare-os":"/node_modules/hyper-overlay/node_modules/bare-os/index.js"},"/node_modules/hyper-overlay/node_modules/bare-fs/package.json":{},"/node_modules/hyper-overlay/node_modules/bare-fs/promises.js":{"#package":"/node_modules/hyper-overlay/node_modules/bare-fs/package.json",".":"/node_modules/hyper-overlay/node_modules/bare-fs/index.js","bare-events":"/node_modules/hyper-overlay/node_modules/bare-events/index.js"},"/node_modules/hyper-overlay/node_modules/bare-inspect/binding.js":{"#package":"/node_modules/hyper-overlay/node_modules/bare-inspect/package.json",".":"linked:libbare-inspect.3.1.4.so"},"/node_modules/hyper-overlay/node_modules/bare-inspect/index.js":{"#package":"/node_modules/hyper-overlay/node_modules/bare-inspect/package.json","./binding":"/node_modules/hyper-overlay/node_modules/bare-inspect/binding.js","bare-ansi-escapes":"/node_modules/hyper-overlay/node_modules/bare-ansi-escapes/index.js","bare-type":"/node_modules/hyper-overlay/node_modules/bare-type/index.js"},"/node_modules/hyper-overlay/node_modules/bare-inspect/package.json":{},"/node_modules/hyper-overlay/node_modules/bare-os/binding.js":{"#package":"/node_modules/hyper-overlay/node_modules/bare-os/package.json",".":"linked:libbare-os.3.9.1.so"},"/node_modules/hyper-overlay/node_modules/bare-os/index.js":{"#package":"/node_modules/hyper-overlay/node_modules/bare-os/package.json","./binding":"/node_modules/hyper-overlay/node_modules/bare-os/binding.js","./lib/constants":"/node_modules/hyper-overlay/node_modules/bare-os/lib/constants.js","./lib/errors":"/node_modules/hyper-overlay/node_modules/bare-os/lib/errors.js"},"/node_modules/hyper-overlay/node_modules/bare-os/lib/constants.js":{"#package":"/node_modules/hyper-overlay/node_modules/bare-os/package.json","../binding":"/node_modules/hyper-overlay/node_modules/bare-os/binding.js"},"/node_modules/hyper-overlay/node_modules/bare-os/lib/errors.js":{"#package":"/node_modules/hyper-overlay/node_modules/bare-os/package.json"},"/node_modules/hyper-overlay/node_modules/bare-os/package.json":{},"/node_modules/hyper-overlay/node_modules/bare-path/index.js":{"#package":"/node_modules/hyper-overlay/node_modules/bare-path/package.json","./lib/posix":"/node_modules/hyper-overlay/node_modules/bare-path/lib/posix.js","./lib/win32":"/node_modules/hyper-overlay/node_modules/bare-path/lib/win32.js"},"/node_modules/hyper-overlay/node_modules/bare-path/lib/constants.js":{"#package":"/node_modules/hyper-overlay/node_modules/bare-path/package.json"},"/node_modules/hyper-overlay/node_modules/bare-path/lib/posix.js":{"#package":"/node_modules/hyper-overlay/node_modules/bare-path/package.json","./constants":"/node_modules/hyper-overlay/node_modules/bare-path/lib/constants.js","./shared":"/node_modules/hyper-overlay/node_modules/bare-path/lib/shared.js","./win32":"/node_modules/hyper-overlay/node_modules/bare-path/lib/win32.js","bare-os":"/node_modules/hyper-overlay/node_modules/bare-os/index.js"},"/node_modules/hyper-overlay/node_modules/bare-path/lib/shared.js":{"#package":"/node_modules/hyper-overlay/node_modules/bare-path/package.json","./constants":"/node_modules/hyper-overlay/node_modules/bare-path/lib/constants.js"},"/node_modules/hyper-overlay/node_modules/bare-path/lib/win32.js":{"#package":"/node_modules/hyper-overlay/node_modules/bare-path/package.json","./constants":"/node_modules/hyper-overlay/node_modules/bare-path/lib/constants.js","./posix":"/node_modules/hyper-overlay/node_modules/bare-path/lib/posix.js","./shared":"/node_modules/hyper-overlay/node_modules/bare-path/lib/shared.js","bare-os":"/node_modules/hyper-overlay/node_modules/bare-os/index.js"},"/node_modules/hyper-overlay/node_modules/bare-path/package.json":{},"/node_modules/hyper-overlay/node_modules/bare-stream/index.js":{"#package":"/node_modules/hyper-overlay/node_modules/bare-stream/package.json","./web":"/node_modules/hyper-overlay/node_modules/bare-stream/web.js","b4a":"/node_modules/hyper-overlay/node_modules/b4a/index.js","streamx":"/node_modules/hyper-overlay/node_modules/streamx/index.js"},"/node_modules/hyper-overlay/node_modules/bare-stream/package.json":{},"/node_modules/hyper-overlay/node_modules/bare-stream/web.js":{"#package":"/node_modules/hyper-overlay/node_modules/bare-stream/package.json","streamx":"/node_modules/hyper-overlay/node_modules/streamx/index.js","teex":"/node_modules/hyper-overlay/node_modules/teex/index.js"},"/node_modules/hyper-overlay/node_modules/bare-type/binding.js":{"#package":"/node_modules/hyper-overlay/node_modules/bare-type/package.json",".":"linked:libbare-type.1.1.0.so"},"/node_modules/hyper-overlay/node_modules/bare-type/index.js":{"#package":"/node_modules/hyper-overlay/node_modules/bare-type/package.json","./binding":"/node_modules/hyper-overlay/node_modules/bare-type/binding.js"},"/node_modules/hyper-overlay/node_modules/bare-type/package.json":{},"/node_modules/hyper-overlay/node_modules/bare-url/binding.js":{"#package":"/node_modules/hyper-overlay/node_modules/bare-url/package.json",".":"linked:libbare-url.2.4.5.so"},"/node_modules/hyper-overlay/node_modules/bare-url/index.js":{"#package":"/node_modules/hyper-overlay/node_modules/bare-url/package.json","./binding":"/node_modules/hyper-overlay/node_modules/bare-url/binding.js","./lib/errors":"/node_modules/hyper-overlay/node_modules/bare-url/lib/errors.js","./lib/url-search-params":"/node_modules/hyper-overlay/node_modules/bare-url/lib/url-search-params.js","bare-path":"/node_modules/hyper-overlay/node_modules/bare-path/index.js"},"/node_modules/hyper-overlay/node_modules/bare-url/lib/errors.js":{"#package":"/node_modules/hyper-overlay/node_modules/bare-url/package.json"},"/node_modules/hyper-overlay/node_modules/bare-url/lib/url-search-params.js":{"#package":"/node_modules/hyper-overlay/node_modules/bare-url/package.json"},"/node_modules/hyper-overlay/node_modules/bare-url/package.json":{},"/node_modules/hyper-overlay/node_modules/compact-encoding/endian.js":{"#package":"/node_modules/hyper-overlay/node_modules/compact-encoding/package.json"},"/node_modules/hyper-overlay/node_modules/compact-encoding/index.js":{"#package":"/node_modules/hyper-overlay/node_modules/compact-encoding/package.json","./endian":"/node_modules/hyper-overlay/node_modules/compact-encoding/endian.js","./lexint":"/node_modules/hyper-overlay/node_modules/compact-encoding/lexint.js","./raw":"/node_modules/hyper-overlay/node_modules/compact-encoding/raw.js","b4a":"/node_modules/hyper-overlay/node_modules/b4a/index.js"},"/node_modules/hyper-overlay/node_modules/compact-encoding/lexint.js":{"#package":"/node_modules/hyper-overlay/node_modules/compact-encoding/package.json"},"/node_modules/hyper-overlay/node_modules/compact-encoding/package.json":{},"/node_modules/hyper-overlay/node_modules/compact-encoding/raw.js":{"#package":"/node_modules/hyper-overlay/node_modules/compact-encoding/package.json","./endian":"/node_modules/hyper-overlay/node_modules/compact-encoding/endian.js","b4a":"/node_modules/hyper-overlay/node_modules/b4a/index.js"},"/node_modules/hyper-overlay/node_modules/events-universal/bare.js":{"#package":"/node_modules/hyper-overlay/node_modules/events-universal/package.json","bare-events":"/node_modules/hyper-overlay/node_modules/bare-events/index.js"},"/node_modules/hyper-overlay/node_modules/events-universal/package.json":{},"/node_modules/hyper-overlay/node_modules/fast-fifo/fixed-size.js":{"#package":"/node_modules/hyper-overlay/node_modules/fast-fifo/package.json"},"/node_modules/hyper-overlay/node_modules/fast-fifo/index.js":{"#package":"/node_modules/hyper-overlay/node_modules/fast-fifo/package.json","./fixed-size":"/node_modules/hyper-overlay/node_modules/fast-fifo/fixed-size.js"},"/node_modules/hyper-overlay/node_modules/fast-fifo/package.json":{},"/node_modules/hyper-overlay/node_modules/hypercore-crypto/index.js":{"#package":"/node_modules/hyper-overlay/node_modules/hypercore-crypto/package.json","b4a":"/node_modules/hyper-overlay/node_modules/b4a/index.js","compact-encoding":"/node_modules/hyper-overlay/node_modules/compact-encoding/index.js","sodium-universal":"/node_modules/hyper-overlay/node_modules/sodium-universal/index.js"},"/node_modules/hyper-overlay/node_modules/hypercore-crypto/package.json":{},"/node_modules/hyper-overlay/node_modules/require-addon/lib/bare.js":{"#package":"/node_modules/hyper-overlay/node_modules/require-addon/package.json"},"/node_modules/hyper-overlay/node_modules/require-addon/package.json":{},"/node_modules/hyper-overlay/node_modules/sodium-native/binding.js":{"#package":"/node_modules/hyper-overlay/node_modules/sodium-native/package.json",".":"linked:libsodium-native.5.1.0.so","require-addon":"/node_modules/hyper-overlay/node_modules/require-addon/lib/bare.js"},"/node_modules/hyper-overlay/node_modules/sodium-native/index.js":{"#package":"/node_modules/hyper-overlay/node_modules/sodium-native/package.json","./binding":"/node_modules/hyper-overlay/node_modules/sodium-native/binding.js","assert":"/node_modules/hyper-overlay/node_modules/bare-assert/index.js","which-runtime":"/node_modules/hyper-overlay/node_modules/which-runtime/index.js"},"/node_modules/hyper-overlay/node_modules/sodium-native/package.json":{},"/node_modules/hyper-overlay/node_modules/sodium-universal/index.js":{"#package":"/node_modules/hyper-overlay/node_modules/sodium-universal/package.json","sodium-native":"/node_modules/hyper-overlay/node_modules/sodium-native/index.js"},"/node_modules/hyper-overlay/node_modules/sodium-universal/package.json":{},"/node_modules/hyper-overlay/node_modules/streamx/index.js":{"#package":"/node_modules/hyper-overlay/node_modules/streamx/package.json","./lib/errors.js":"/node_modules/hyper-overlay/node_modules/streamx/lib/errors.js","events-universal":"/node_modules/hyper-overlay/node_modules/events-universal/bare.js","fast-fifo":"/node_modules/hyper-overlay/node_modules/fast-fifo/index.js","text-decoder":"/node_modules/hyper-overlay/node_modules/text-decoder/index.js"},"/node_modules/hyper-overlay/node_modules/streamx/lib/errors.js":{"#package":"/node_modules/hyper-overlay/node_modules/streamx/package.json"},"/node_modules/hyper-overlay/node_modules/streamx/package.json":{},"/node_modules/hyper-overlay/node_modules/teex/index.js":{"#package":"/node_modules/hyper-overlay/node_modules/teex/package.json","streamx":"/node_modules/hyper-overlay/node_modules/streamx/index.js"},"/node_modules/hyper-overlay/node_modules/teex/package.json":{},"/node_modules/hyper-overlay/node_modules/text-decoder/index.js":{"#package":"/node_modules/hyper-overlay/node_modules/text-decoder/package.json","./lib/pass-through-decoder":"/node_modules/hyper-overlay/node_modules/text-decoder/lib/pass-through-decoder.js","./lib/utf8-decoder":"/node_modules/hyper-overlay/node_modules/text-decoder/lib/utf8-decoder.js"},"/node_modules/hyper-overlay/node_modules/text-decoder/lib/pass-through-decoder.js":{"#package":"/node_modules/hyper-overlay/node_modules/text-decoder/package.json","b4a":"/node_modules/hyper-overlay/node_modules/b4a/index.js"},"/node_modules/hyper-overlay/node_modules/text-decoder/lib/utf8-decoder.js":{"#package":"/node_modules/hyper-overlay/node_modules/text-decoder/package.json","b4a":"/node_modules/hyper-overlay/node_modules/b4a/index.js"},"/node_modules/hyper-overlay/node_modules/text-decoder/package.json":{},"/node_modules/hyper-overlay/node_modules/which-runtime/index.js":{"#package":"/node_modules/hyper-overlay/node_modules/which-runtime/package.json"},"/node_modules/hyper-overlay/node_modules/which-runtime/package.json":{},"/node_modules/hyper-overlay/package.json":{},"/node_modules/hypercore-crypto/index.js":{"#package":"/node_modules/hypercore-crypto/package.json","b4a":"/node_modules/b4a/index.js","compact-encoding":"/node_modules/compact-encoding/index.js","sodium-universal":"/node_modules/sodium-universal/index.js"},"/node_modules/hypercore-crypto/package.json":{},"/node_modules/hypercore-id-encoding/index.js":{"#package":"/node_modules/hypercore-id-encoding/package.json","b4a":"/node_modules/b4a/index.js","z32":"/node_modules/z32/index.js"},"/node_modules/hypercore-id-encoding/package.json":{},"/node_modules/hyperdht-address/index.js":{"#package":"/node_modules/hyperdht-address/package.json","./spec/hyperschema":"/node_modules/hyperdht-address/spec/hyperschema/index.js","compact-encoding":"/node_modules/compact-encoding/index.js"},"/node_modules/hyperdht-address/package.json":{},"/node_modules/hyperdht-address/spec/hyperschema/index.js":{"#package":"/node_modules/hyperdht-address/package.json","hyperschema/runtime":"/node_modules/hyperschema/runtime.cjs"},"/node_modules/hyperdht/index.js":{"#package":"/node_modules/hyperdht/package.json","./lib/connect":"/node_modules/hyperdht/lib/connect.js","./lib/connection-pool":"/node_modules/hyperdht/lib/connection-pool.js","./lib/constants":"/node_modules/hyperdht/lib/constants.js","./lib/crypto":"/node_modules/hyperdht/lib/crypto.js","./lib/errors":"/node_modules/hyperdht/lib/errors.js","./lib/messages":"/node_modules/hyperdht/lib/messages.js","./lib/persistent":"/node_modules/hyperdht/lib/persistent.js","./lib/raw-stream-set":"/node_modules/hyperdht/lib/raw-stream-set.js","./lib/router":"/node_modules/hyperdht/lib/router.js","./lib/server":"/node_modules/hyperdht/lib/server.js","./lib/socket-pool":"/node_modules/hyperdht/lib/socket-pool.js","b4a":"/node_modules/b4a/index.js","compact-encoding":"/node_modules/compact-encoding/index.js","dht-rpc":"/node_modules/dht-rpc/index.js","safety-catch":"/node_modules/safety-catch/index.js","sodium-universal":"/node_modules/sodium-universal/index.js","xache":"/node_modules/xache/index.js"},"/node_modules/hyperdht/lib/announcer.js":{"#package":"/node_modules/hyperdht/package.json","./constants":"/node_modules/hyperdht/lib/constants.js","./encode":"/node_modules/hyperdht/lib/encode.js","./messages":"/node_modules/hyperdht/lib/messages.js","./persistent":"/node_modules/hyperdht/lib/persistent.js","./sleeper":"/node_modules/hyperdht/lib/sleeper.js","compact-encoding":"/node_modules/compact-encoding/index.js","safety-catch":"/node_modules/safety-catch/index.js","signal-promise":"/node_modules/signal-promise/index.js"},"/node_modules/hyperdht/lib/connect.js":{"#package":"/node_modules/hyperdht/package.json","./constants":"/node_modules/hyperdht/lib/constants.js","./crypto":"/node_modules/hyperdht/lib/crypto.js","./errors":"/node_modules/hyperdht/lib/errors.js","./holepuncher":"/node_modules/hyperdht/lib/holepuncher.js","./noise-wrap":"/node_modules/hyperdht/lib/noise-wrap.js","./relay-connection":"/node_modules/hyperdht/lib/relay-connection.js","./secure-payload":"/node_modules/hyperdht/lib/secure-payload.js","./semaphore":"/node_modules/hyperdht/lib/semaphore.js","./sleeper":"/node_modules/hyperdht/lib/sleeper.js","@hyperswarm/secret-stream":"/node_modules/@hyperswarm/secret-stream/index.js","b4a":"/node_modules/b4a/index.js","blind-relay":"/node_modules/blind-relay/index.js","bogon":"/node_modules/bogon/index.js","hypercore-id-encoding":"/node_modules/hypercore-id-encoding/index.js","hyperdht-address":"/node_modules/hyperdht-address/index.js","safety-catch":"/node_modules/safety-catch/index.js","unslab":"/node_modules/unslab/index.js"},"/node_modules/hyperdht/lib/connection-pool.js":{"#package":"/node_modules/hyperdht/package.json","./errors":"/node_modules/hyperdht/lib/errors.js","b4a":"/node_modules/b4a/index.js","events":"/node_modules/bare-events/index.js"},"/node_modules/hyperdht/lib/constants.js":{"#package":"/node_modules/hyperdht/package.json","hypercore-crypto":"/node_modules/hypercore-crypto/index.js"},"/node_modules/hyperdht/lib/crypto.js":{"#package":"/node_modules/hyperdht/package.json","b4a":"/node_modules/b4a/index.js","sodium-universal":"/node_modules/sodium-universal/index.js"},"/node_modules/hyperdht/lib/encode.js":{"#package":"/node_modules/hyperdht/package.json","b4a":"/node_modules/b4a/index.js","compact-encoding":"/node_modules/compact-encoding/index.js"},"/node_modules/hyperdht/lib/errors.js":{"#package":"/node_modules/hyperdht/package.json"},"/node_modules/hyperdht/lib/holepuncher.js":{"#package":"/node_modules/hyperdht/package.json","./constants":"/node_modules/hyperdht/lib/constants.js","./nat":"/node_modules/hyperdht/lib/nat.js","./sleeper":"/node_modules/hyperdht/lib/sleeper.js","b4a":"/node_modules/b4a/index.js"},"/node_modules/hyperdht/lib/messages.js":{"#package":"/node_modules/hyperdht/package.json","compact-encoding":"/node_modules/compact-encoding/index.js"},"/node_modules/hyperdht/lib/nat.js":{"#package":"/node_modules/hyperdht/package.json","../lib/constants":"/node_modules/hyperdht/lib/constants.js"},"/node_modules/hyperdht/lib/noise-wrap.js":{"#package":"/node_modules/hyperdht/package.json","./constants":"/node_modules/hyperdht/lib/constants.js","./errors":"/node_modules/hyperdht/lib/errors.js","./messages":"/node_modules/hyperdht/lib/messages.js","@hyperswarm/secret-stream":"/node_modules/@hyperswarm/secret-stream/index.js","b4a":"/node_modules/b4a/index.js","compact-encoding":"/node_modules/compact-encoding/index.js","noise-curve-ed":"/node_modules/noise-curve-ed/index.js","noise-handshake":"/node_modules/noise-handshake/noise.js","sodium-universal":"/node_modules/sodium-universal/index.js"},"/node_modules/hyperdht/lib/persistent.js":{"#package":"/node_modules/hyperdht/package.json","./constants":"/node_modules/hyperdht/lib/constants.js","./encode":"/node_modules/hyperdht/lib/encode.js","./messages":"/node_modules/hyperdht/lib/messages.js","b4a":"/node_modules/b4a/index.js","compact-encoding":"/node_modules/compact-encoding/index.js","record-cache":"/node_modules/record-cache/index.js","sodium-universal":"/node_modules/sodium-universal/index.js","unslab":"/node_modules/unslab/index.js","xache":"/node_modules/xache/index.js"},"/node_modules/hyperdht/lib/raw-stream-set.js":{"#package":"/node_modules/hyperdht/package.json"},"/node_modules/hyperdht/lib/relay-connection.js":{"#package":"/node_modules/hyperdht/package.json","b4a":"/node_modules/b4a/index.js","safety-catch":"/node_modules/safety-catch/index.js"},"/node_modules/hyperdht/lib/router.js":{"#package":"/node_modules/hyperdht/package.json","./constants":"/node_modules/hyperdht/lib/constants.js","./errors":"/node_modules/hyperdht/lib/errors.js","./messages":"/node_modules/hyperdht/lib/messages.js","b4a":"/node_modules/b4a/index.js","compact-encoding":"/node_modules/compact-encoding/index.js","safety-catch":"/node_modules/safety-catch/index.js","xache":"/node_modules/xache/index.js"},"/node_modules/hyperdht/lib/secure-payload.js":{"#package":"/node_modules/hyperdht/package.json","./messages":"/node_modules/hyperdht/lib/messages.js","b4a":"/node_modules/b4a/index.js","sodium-universal":"/node_modules/sodium-universal/index.js"},"/node_modules/hyperdht/lib/semaphore.js":{"#package":"/node_modules/hyperdht/package.json"},"/node_modules/hyperdht/lib/server.js":{"#package":"/node_modules/hyperdht/package.json","./announcer":"/node_modules/hyperdht/lib/announcer.js","./constants":"/node_modules/hyperdht/lib/constants.js","./crypto":"/node_modules/hyperdht/lib/crypto.js","./errors":"/node_modules/hyperdht/lib/errors.js","./holepuncher":"/node_modules/hyperdht/lib/holepuncher.js","./noise-wrap":"/node_modules/hyperdht/lib/noise-wrap.js","./relay-connection":"/node_modules/hyperdht/lib/relay-connection.js","./secure-payload":"/node_modules/hyperdht/lib/secure-payload.js","@hyperswarm/secret-stream":"/node_modules/@hyperswarm/secret-stream/index.js","b4a":"/node_modules/b4a/index.js","blind-relay":"/node_modules/blind-relay/index.js","bogon":"/node_modules/bogon/index.js","events":"/node_modules/bare-events/index.js","safety-catch":"/node_modules/safety-catch/index.js"},"/node_modules/hyperdht/lib/sleeper.js":{"#package":"/node_modules/hyperdht/package.json"},"/node_modules/hyperdht/lib/socket-pool.js":{"#package":"/node_modules/hyperdht/package.json","b4a":"/node_modules/b4a/index.js"},"/node_modules/hyperdht/package.json":{},"/node_modules/hyperschema/package.json":{},"/node_modules/hyperschema/runtime.cjs":{"#package":"/node_modules/hyperschema/package.json","compact-encoding":"/node_modules/compact-encoding/index.js"},"/node_modules/hyperswarm/index.js":{"#package":"/node_modules/hyperswarm/package.json","./lib/connection-set":"/node_modules/hyperswarm/lib/connection-set.js","./lib/peer-discovery":"/node_modules/hyperswarm/lib/peer-discovery.js","./lib/peer-info":"/node_modules/hyperswarm/lib/peer-info.js","./lib/retry-timer":"/node_modules/hyperswarm/lib/retry-timer.js","b4a":"/node_modules/b4a/index.js","events":"/node_modules/bare-events/index.js","hyperdht":"/node_modules/hyperdht/index.js","shuffled-priority-queue":"/node_modules/shuffled-priority-queue/index.js","streamx":"/node_modules/streamx/index.js","unslab":"/node_modules/unslab/index.js"},"/node_modules/hyperswarm/lib/bulk-timer.js":{"#package":"/node_modules/hyperswarm/package.json"},"/node_modules/hyperswarm/lib/connection-set.js":{"#package":"/node_modules/hyperswarm/package.json","b4a":"/node_modules/b4a/index.js"},"/node_modules/hyperswarm/lib/peer-discovery.js":{"#package":"/node_modules/hyperswarm/package.json","b4a":"/node_modules/b4a/index.js","safety-catch":"/node_modules/safety-catch/index.js"},"/node_modules/hyperswarm/lib/peer-info.js":{"#package":"/node_modules/hyperswarm/package.json","b4a":"/node_modules/b4a/index.js","events":"/node_modules/bare-events/index.js","unslab":"/node_modules/unslab/index.js"},"/node_modules/hyperswarm/lib/retry-timer.js":{"#package":"/node_modules/hyperswarm/package.json","./bulk-timer":"/node_modules/hyperswarm/lib/bulk-timer.js"},"/node_modules/hyperswarm/package.json":{},"/node_modules/kademlia-routing-table/index.js":{"#package":"/node_modules/kademlia-routing-table/package.json","events":"/node_modules/bare-events/index.js"},"/node_modules/kademlia-routing-table/package.json":{},"/node_modules/nanoassert/index.js":{"#package":"/node_modules/nanoassert/package.json"},"/node_modules/nanoassert/package.json":{},"/node_modules/nat-sampler/index.js":{"#package":"/node_modules/nat-sampler/package.json"},"/node_modules/nat-sampler/package.json":{},"/node_modules/noise-curve-ed/index.js":{"#package":"/node_modules/noise-curve-ed/package.json","b4a":"/node_modules/b4a/index.js","nanoassert":"/node_modules/nanoassert/index.js","sodium-universal":"/node_modules/sodium-universal/index.js"},"/node_modules/noise-curve-ed/package.json":{},"/node_modules/noise-handshake/cipher.js":{"#package":"/node_modules/noise-handshake/package.json","b4a":"/node_modules/b4a/index.js","sodium-universal":"/node_modules/sodium-universal/index.js"},"/node_modules/noise-handshake/dh.js":{"#package":"/node_modules/noise-handshake/package.json","b4a":"/node_modules/b4a/index.js","nanoassert":"/node_modules/nanoassert/index.js","sodium-universal":"/node_modules/sodium-universal/index.js"},"/node_modules/noise-handshake/hkdf.js":{"#package":"/node_modules/noise-handshake/package.json","./hmac":"/node_modules/noise-handshake/hmac.js","b4a":"/node_modules/b4a/index.js"},"/node_modules/noise-handshake/hmac.js":{"#package":"/node_modules/noise-handshake/package.json","b4a":"/node_modules/b4a/index.js","sodium-universal":"/node_modules/sodium-universal/index.js"},"/node_modules/noise-handshake/noise.js":{"#package":"/node_modules/noise-handshake/package.json","./hkdf":"/node_modules/noise-handshake/hkdf.js","./symmetric-state":"/node_modules/noise-handshake/symmetric-state.js","b4a":"/node_modules/b4a/index.js","nanoassert":"/node_modules/nanoassert/index.js"},"/node_modules/noise-handshake/package.json":{},"/node_modules/noise-handshake/symmetric-state.js":{"#package":"/node_modules/noise-handshake/package.json","./cipher":"/node_modules/noise-handshake/cipher.js","./dh":"/node_modules/noise-handshake/dh.js","./hkdf":"/node_modules/noise-handshake/hkdf.js","b4a":"/node_modules/b4a/index.js","nanoassert":"/node_modules/nanoassert/index.js","sodium-universal":"/node_modules/sodium-universal/index.js"},"/node_modules/pgh-runtime/index.js":{"#node:events":"/node_modules/pgh-runtime/node_modules/bare-events/index.js","#package":"/node_modules/pgh-runtime/package.json","hyper-overlay/tree":"/node_modules/pgh-runtime/node_modules/hyper-overlay/lib/tree.js"},"/node_modules/pgh-runtime/node_modules/bare-events/index.js":{"#package":"/node_modules/pgh-runtime/node_modules/bare-events/package.json","./lib/errors":"/node_modules/pgh-runtime/node_modules/bare-events/lib/errors.js"},"/node_modules/pgh-runtime/node_modules/bare-events/lib/errors.js":{"#package":"/node_modules/pgh-runtime/node_modules/bare-events/package.json"},"/node_modules/pgh-runtime/node_modules/bare-events/package.json":{},"/node_modules/pgh-runtime/node_modules/hyper-overlay/lib/tree.js":{"#fs":"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/bare-fs/index.js","#package":"/node_modules/pgh-runtime/node_modules/hyper-overlay/package.json","#path":"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/bare-path/index.js","compact-encoding":"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/compact-encoding/index.js","hypercore-crypto":"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/hypercore-crypto/index.js"},"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/b4a/index.js":{"#package":"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/b4a/package.json"},"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/b4a/package.json":{},"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/bare-ansi-escapes/index.js":{"#package":"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/bare-ansi-escapes/package.json"},"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/bare-ansi-escapes/package.json":{},"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/bare-assert/index.js":{"#package":"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/bare-assert/package.json","bare-inspect":"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/bare-inspect/index.js"},"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/bare-assert/package.json":{},"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/bare-events/index.js":{"#package":"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/bare-events/package.json","./lib/errors":"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/bare-events/lib/errors.js"},"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/bare-events/lib/errors.js":{"#package":"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/bare-events/package.json"},"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/bare-events/package.json":{},"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/bare-fs/binding.js":{"#package":"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/bare-fs/package.json",".":"linked:libbare-fs.4.7.2.so"},"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/bare-fs/index.js":{"#package":"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/bare-fs/package.json","./binding":"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/bare-fs/binding.js","./lib/constants":"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/bare-fs/lib/constants.js","./lib/errors":"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/bare-fs/lib/errors.js","./promises":"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/bare-fs/promises.js","bare-events":"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/bare-events/index.js","bare-path":"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/bare-path/index.js","bare-stream":"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/bare-stream/index.js","bare-url":"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/bare-url/index.js","fast-fifo":"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/fast-fifo/index.js"},"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/bare-fs/lib/constants.js":{"#package":"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/bare-fs/package.json","../binding":"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/bare-fs/binding.js"},"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/bare-fs/lib/errors.js":{"#package":"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/bare-fs/package.json","bare-os":"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/bare-os/index.js"},"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/bare-fs/package.json":{},"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/bare-fs/promises.js":{"#package":"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/bare-fs/package.json",".":"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/bare-fs/index.js","bare-events":"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/bare-events/index.js"},"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/bare-inspect/binding.js":{"#package":"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/bare-inspect/package.json",".":"linked:libbare-inspect.3.1.4.so"},"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/bare-inspect/index.js":{"#package":"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/bare-inspect/package.json","./binding":"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/bare-inspect/binding.js","bare-ansi-escapes":"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/bare-ansi-escapes/index.js","bare-type":"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/bare-type/index.js"},"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/bare-inspect/package.json":{},"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/bare-os/binding.js":{"#package":"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/bare-os/package.json",".":"linked:libbare-os.3.9.1.so"},"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/bare-os/index.js":{"#package":"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/bare-os/package.json","./binding":"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/bare-os/binding.js","./lib/constants":"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/bare-os/lib/constants.js","./lib/errors":"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/bare-os/lib/errors.js"},"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/bare-os/lib/constants.js":{"#package":"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/bare-os/package.json","../binding":"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/bare-os/binding.js"},"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/bare-os/lib/errors.js":{"#package":"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/bare-os/package.json"},"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/bare-os/package.json":{},"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/bare-path/index.js":{"#package":"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/bare-path/package.json","./lib/posix":"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/bare-path/lib/posix.js","./lib/win32":"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/bare-path/lib/win32.js"},"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/bare-path/lib/constants.js":{"#package":"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/bare-path/package.json"},"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/bare-path/lib/posix.js":{"#package":"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/bare-path/package.json","./constants":"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/bare-path/lib/constants.js","./shared":"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/bare-path/lib/shared.js","./win32":"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/bare-path/lib/win32.js","bare-os":"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/bare-os/index.js"},"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/bare-path/lib/shared.js":{"#package":"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/bare-path/package.json","./constants":"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/bare-path/lib/constants.js"},"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/bare-path/lib/win32.js":{"#package":"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/bare-path/package.json","./constants":"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/bare-path/lib/constants.js","./posix":"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/bare-path/lib/posix.js","./shared":"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/bare-path/lib/shared.js","bare-os":"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/bare-os/index.js"},"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/bare-path/package.json":{},"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/bare-stream/index.js":{"#package":"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/bare-stream/package.json","./web":"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/bare-stream/web.js","b4a":"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/b4a/index.js","streamx":"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/streamx/index.js"},"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/bare-stream/package.json":{},"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/bare-stream/web.js":{"#package":"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/bare-stream/package.json","streamx":"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/streamx/index.js","teex":"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/teex/index.js"},"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/bare-type/binding.js":{"#package":"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/bare-type/package.json",".":"linked:libbare-type.1.1.0.so"},"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/bare-type/index.js":{"#package":"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/bare-type/package.json","./binding":"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/bare-type/binding.js"},"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/bare-type/package.json":{},"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/bare-url/binding.js":{"#package":"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/bare-url/package.json",".":"linked:libbare-url.2.4.5.so"},"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/bare-url/index.js":{"#package":"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/bare-url/package.json","./binding":"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/bare-url/binding.js","./lib/errors":"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/bare-url/lib/errors.js","./lib/url-search-params":"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/bare-url/lib/url-search-params.js","bare-path":"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/bare-path/index.js"},"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/bare-url/lib/errors.js":{"#package":"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/bare-url/package.json"},"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/bare-url/lib/url-search-params.js":{"#package":"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/bare-url/package.json"},"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/bare-url/package.json":{},"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/compact-encoding/endian.js":{"#package":"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/compact-encoding/package.json"},"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/compact-encoding/index.js":{"#package":"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/compact-encoding/package.json","./endian":"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/compact-encoding/endian.js","./lexint":"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/compact-encoding/lexint.js","./raw":"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/compact-encoding/raw.js","b4a":"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/b4a/index.js"},"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/compact-encoding/lexint.js":{"#package":"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/compact-encoding/package.json"},"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/compact-encoding/package.json":{},"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/compact-encoding/raw.js":{"#package":"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/compact-encoding/package.json","./endian":"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/compact-encoding/endian.js","b4a":"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/b4a/index.js"},"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/events-universal/bare.js":{"#package":"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/events-universal/package.json","bare-events":"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/bare-events/index.js"},"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/events-universal/package.json":{},"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/fast-fifo/fixed-size.js":{"#package":"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/fast-fifo/package.json"},"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/fast-fifo/index.js":{"#package":"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/fast-fifo/package.json","./fixed-size":"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/fast-fifo/fixed-size.js"},"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/fast-fifo/package.json":{},"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/hypercore-crypto/index.js":{"#package":"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/hypercore-crypto/package.json","b4a":"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/b4a/index.js","compact-encoding":"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/compact-encoding/index.js","sodium-universal":"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/sodium-universal/index.js"},"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/hypercore-crypto/package.json":{},"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/require-addon/lib/bare.js":{"#package":"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/require-addon/package.json"},"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/require-addon/package.json":{},"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/sodium-native/binding.js":{"#package":"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/sodium-native/package.json",".":"linked:libsodium-native.5.1.0.so","require-addon":"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/require-addon/lib/bare.js"},"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/sodium-native/index.js":{"#package":"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/sodium-native/package.json","./binding":"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/sodium-native/binding.js","assert":"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/bare-assert/index.js","which-runtime":"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/which-runtime/index.js"},"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/sodium-native/package.json":{},"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/sodium-universal/index.js":{"#package":"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/sodium-universal/package.json","sodium-native":"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/sodium-native/index.js"},"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/sodium-universal/package.json":{},"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/streamx/index.js":{"#package":"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/streamx/package.json","./lib/errors.js":"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/streamx/lib/errors.js","events-universal":"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/events-universal/bare.js","fast-fifo":"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/fast-fifo/index.js","text-decoder":"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/text-decoder/index.js"},"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/streamx/lib/errors.js":{"#package":"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/streamx/package.json"},"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/streamx/package.json":{},"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/teex/index.js":{"#package":"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/teex/package.json","streamx":"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/streamx/index.js"},"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/teex/package.json":{},"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/text-decoder/index.js":{"#package":"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/text-decoder/package.json","./lib/pass-through-decoder":"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/text-decoder/lib/pass-through-decoder.js","./lib/utf8-decoder":"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/text-decoder/lib/utf8-decoder.js"},"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/text-decoder/lib/pass-through-decoder.js":{"#package":"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/text-decoder/package.json","b4a":"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/b4a/index.js"},"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/text-decoder/lib/utf8-decoder.js":{"#package":"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/text-decoder/package.json","b4a":"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/b4a/index.js"},"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/text-decoder/package.json":{},"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/which-runtime/index.js":{"#package":"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/which-runtime/package.json"},"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/which-runtime/package.json":{},"/node_modules/pgh-runtime/node_modules/hyper-overlay/package.json":{},"/node_modules/pgh-runtime/package.json":{},"/node_modules/pgh/lib/network-fetcher.js":{"#node:fs":"/node_modules/pgh/node_modules/bare-fs/index.js","#node:os":"/node_modules/pgh/node_modules/bare-os/index.js","#node:path":"/node_modules/pgh/node_modules/bare-path/index.js","#package":"/node_modules/pgh/package.json","./release-registry.js":"/node_modules/pgh/lib/release-registry.js","b4a":"/node_modules/pgh/node_modules/b4a/index.js","corestore":"/node_modules/pgh/node_modules/corestore/index.js","hyper-overlay/file-index":"/node_modules/pgh/node_modules/hyper-overlay/lib/file-index.js","hyper-overlay/protocol":"/node_modules/pgh/node_modules/hyper-overlay/lib/protocol.js","hyper-overlay/transfer":"/node_modules/pgh/node_modules/hyper-overlay/lib/transfer.js","hyper-overlay/tree":"/node_modules/pgh/node_modules/hyper-overlay/lib/tree.js","hypercore-crypto":"/node_modules/pgh/node_modules/hypercore-crypto/index.js","hyperswarm":"/node_modules/pgh/node_modules/hyperswarm/index.js","protomux":"/node_modules/pgh/node_modules/protomux/index.js"},"/node_modules/pgh/lib/release-registry.js":{"#package":"/node_modules/pgh/package.json","autobase":"/node_modules/pgh/node_modules/autobase/index.js","b4a":"/node_modules/pgh/node_modules/b4a/index.js","hyperbee":"/node_modules/pgh/node_modules/hyperbee/index.js","hypercore-crypto":"/node_modules/pgh/node_modules/hypercore-crypto/index.js","sodium-native":"/node_modules/pgh/node_modules/sodium-native/index.js"},"/node_modules/pgh/lib/resolve-release.js":{"#node:fs":"/node_modules/pgh/node_modules/bare-fs/index.js","#node:os":"/node_modules/pgh/node_modules/bare-os/index.js","#node:path":"/node_modules/pgh/node_modules/bare-path/index.js","#node:process":"/node_modules/pgh/node_modules/bare-process/index.js","#package":"/node_modules/pgh/package.json","./release-registry.js":"/node_modules/pgh/lib/release-registry.js","b4a":"/node_modules/pgh/node_modules/b4a/index.js","corestore":"/node_modules/pgh/node_modules/corestore/index.js","hyper-svc":"/node_modules/pgh/node_modules/hyper-svc/index.js","hyperswarm":"/node_modules/pgh/node_modules/hyperswarm/index.js"},"/node_modules/pgh/node_modules/@hyperswarm/secret-stream/index.js":{"#package":"/node_modules/pgh/node_modules/@hyperswarm/secret-stream/package.json","./lib/bridge":"/node_modules/pgh/node_modules/@hyperswarm/secret-stream/lib/bridge.js","./lib/handshake":"/node_modules/pgh/node_modules/@hyperswarm/secret-stream/lib/handshake.js","b4a":"/node_modules/pgh/node_modules/b4a/index.js","hypercore-crypto":"/node_modules/pgh/node_modules/hypercore-crypto/index.js","sodium-secretstream":"/node_modules/pgh/node_modules/sodium-secretstream/index.js","sodium-universal":"/node_modules/pgh/node_modules/sodium-universal/index.js","streamx":"/node_modules/pgh/node_modules/streamx/index.js","timeout-refresh":"/node_modules/pgh/node_modules/timeout-refresh/index.js","unslab":"/node_modules/pgh/node_modules/unslab/index.js"},"/node_modules/pgh/node_modules/@hyperswarm/secret-stream/lib/bridge.js":{"#package":"/node_modules/pgh/node_modules/@hyperswarm/secret-stream/package.json","streamx":"/node_modules/pgh/node_modules/streamx/index.js"},"/node_modules/pgh/node_modules/@hyperswarm/secret-stream/lib/handshake.js":{"#package":"/node_modules/pgh/node_modules/@hyperswarm/secret-stream/package.json","b4a":"/node_modules/pgh/node_modules/b4a/index.js","noise-curve-ed":"/node_modules/pgh/node_modules/noise-curve-ed/index.js","noise-handshake":"/node_modules/pgh/node_modules/noise-handshake/noise.js","sodium-universal":"/node_modules/pgh/node_modules/sodium-universal/index.js"},"/node_modules/pgh/node_modules/@hyperswarm/secret-stream/package.json":{},"/node_modules/pgh/node_modules/adaptive-timeout/index.js":{"#package":"/node_modules/pgh/node_modules/adaptive-timeout/package.json","xache":"/node_modules/pgh/node_modules/xache/index.js"},"/node_modules/pgh/node_modules/adaptive-timeout/package.json":{},"/node_modules/pgh/node_modules/autobase/encoding/legacy.js":{"#package":"/node_modules/pgh/node_modules/autobase/package.json","compact-encoding":"/node_modules/pgh/node_modules/compact-encoding/index.js","index-encoder":"/node_modules/pgh/node_modules/index-encoder/index.js"},"/node_modules/pgh/node_modules/autobase/encoding/spec/autobase/index.js":{"#package":"/node_modules/pgh/node_modules/autobase/package.json","../../legacy.js":"/node_modules/pgh/node_modules/autobase/encoding/legacy.js","hyperschema/runtime":"/node_modules/pgh/node_modules/hyperschema/runtime.cjs"},"/node_modules/pgh/node_modules/autobase/index.js":{"#package":"/node_modules/pgh/node_modules/autobase/package.json","./lib/active-writers.js":"/node_modules/pgh/node_modules/autobase/lib/active-writers.js","./lib/append-batch.js":"/node_modules/pgh/node_modules/autobase/lib/append-batch.js","./lib/apply-calls.js":"/node_modules/pgh/node_modules/autobase/lib/apply-calls.js","./lib/apply-state.js":"/node_modules/pgh/node_modules/autobase/lib/apply-state.js","./lib/boot.js":"/node_modules/pgh/node_modules/autobase/lib/boot.js","./lib/caps.js":"/node_modules/pgh/node_modules/autobase/lib/caps.js","./lib/encryption.js":"/node_modules/pgh/node_modules/autobase/lib/encryption.js","./lib/fast-forward.js":"/node_modules/pgh/node_modules/autobase/lib/fast-forward.js","./lib/linearizer.js":"/node_modules/pgh/node_modules/autobase/lib/linearizer.js","./lib/local-state.js":"/node_modules/pgh/node_modules/autobase/lib/local-state.js","./lib/messages.js":"/node_modules/pgh/node_modules/autobase/lib/messages.js","./lib/store.js":"/node_modules/pgh/node_modules/autobase/lib/store.js","./lib/system.js":"/node_modules/pgh/node_modules/autobase/lib/system.js","./lib/timer.js":"/node_modules/pgh/node_modules/autobase/lib/timer.js","./lib/updates.js":"/node_modules/pgh/node_modules/autobase/lib/updates.js","./lib/values.js":"/node_modules/pgh/node_modules/autobase/lib/values.js","./lib/wakeup.js":"/node_modules/pgh/node_modules/autobase/lib/wakeup.js","./lib/writer.js":"/node_modules/pgh/node_modules/autobase/lib/writer.js","b4a":"/node_modules/pgh/node_modules/b4a/index.js","compact-encoding":"/node_modules/pgh/node_modules/compact-encoding/index.js","core-coupler":"/node_modules/pgh/node_modules/core-coupler/index.js","debounceify":"/node_modules/pgh/node_modules/debounceify/index.js","encryption-encoding":"/node_modules/pgh/node_modules/encryption-encoding/index.js","hypercore":"/node_modules/pgh/node_modules/hypercore/index.js","hypercore-id-encoding":"/node_modules/pgh/node_modules/hypercore-id-encoding/index.js","nanoassert":"/node_modules/pgh/node_modules/nanoassert/index.js","protomux-wakeup":"/node_modules/pgh/node_modules/protomux-wakeup/index.js","ready-resource":"/node_modules/pgh/node_modules/ready-resource/index.js","resolve-reject-promise":"/node_modules/pgh/node_modules/resolve-reject-promise/index.js","safety-catch":"/node_modules/pgh/node_modules/safety-catch/index.js","scope-lock":"/node_modules/pgh/node_modules/scope-lock/index.js","signal-promise":"/node_modules/pgh/node_modules/signal-promise/index.js"},"/node_modules/pgh/node_modules/autobase/lib/active-writers.js":{"#package":"/node_modules/pgh/node_modules/autobase/package.json","b4a":"/node_modules/pgh/node_modules/b4a/index.js"},"/node_modules/pgh/node_modules/autobase/lib/append-batch.js":{"#package":"/node_modules/pgh/node_modules/autobase/package.json","signal-promise":"/node_modules/pgh/node_modules/signal-promise/index.js"},"/node_modules/pgh/node_modules/autobase/lib/apply-calls.js":{"#package":"/node_modules/pgh/node_modules/autobase/package.json"},"/node_modules/pgh/node_modules/autobase/lib/apply-state.js":{"#package":"/node_modules/pgh/node_modules/autobase/package.json","./apply-calls.js":"/node_modules/pgh/node_modules/autobase/lib/apply-calls.js","./caps.js":"/node_modules/pgh/node_modules/autobase/lib/caps.js","./encryption.js":"/node_modules/pgh/node_modules/autobase/lib/encryption.js","./fork.js":"/node_modules/pgh/node_modules/autobase/lib/fork.js","./local-state.js":"/node_modules/pgh/node_modules/autobase/lib/local-state.js","./messages.js":"/node_modules/pgh/node_modules/autobase/lib/messages.js","./system.js":"/node_modules/pgh/node_modules/autobase/lib/system.js","./updates.js":"/node_modules/pgh/node_modules/autobase/lib/updates.js","./values.js":"/node_modules/pgh/node_modules/autobase/lib/values.js","b4a":"/node_modules/pgh/node_modules/b4a/index.js","compact-encoding":"/node_modules/pgh/node_modules/compact-encoding/index.js","hypercore":"/node_modules/pgh/node_modules/hypercore/index.js","hypercore-crypto":"/node_modules/pgh/node_modules/hypercore-crypto/index.js","hypercore/lib/multisig.js":"/node_modules/pgh/node_modules/hypercore/lib/multisig.js","nanoassert":"/node_modules/pgh/node_modules/nanoassert/index.js","ready-resource":"/node_modules/pgh/node_modules/ready-resource/index.js","safety-catch":"/node_modules/pgh/node_modules/safety-catch/index.js"},"/node_modules/pgh/node_modules/autobase/lib/boot.js":{"#package":"/node_modules/pgh/node_modules/autobase/package.json","./messages.js":"/node_modules/pgh/node_modules/autobase/lib/messages.js","compact-encoding":"/node_modules/pgh/node_modules/compact-encoding/index.js","encryption-encoding":"/node_modules/pgh/node_modules/encryption-encoding/index.js"},"/node_modules/pgh/node_modules/autobase/lib/caps.js":{"#package":"/node_modules/pgh/node_modules/autobase/package.json","hypercore-crypto":"/node_modules/pgh/node_modules/hypercore-crypto/index.js"},"/node_modules/pgh/node_modules/autobase/lib/clock.js":{"#package":"/node_modules/pgh/node_modules/autobase/package.json","tiny-buffer-map":"/node_modules/pgh/node_modules/tiny-buffer-map/index.js"},"/node_modules/pgh/node_modules/autobase/lib/consensus.js":{"#package":"/node_modules/pgh/node_modules/autobase/package.json","./clock.js":"/node_modules/pgh/node_modules/autobase/lib/clock.js","tiny-buffer-map":"/node_modules/pgh/node_modules/tiny-buffer-map/index.js"},"/node_modules/pgh/node_modules/autobase/lib/encryption.js":{"#package":"/node_modules/pgh/node_modules/autobase/package.json","./caps.js":"/node_modules/pgh/node_modules/autobase/lib/caps.js","./messages.js":"/node_modules/pgh/node_modules/autobase/lib/messages.js","./system.js":"/node_modules/pgh/node_modules/autobase/lib/system.js","b4a":"/node_modules/pgh/node_modules/b4a/index.js","compact-encoding":"/node_modules/pgh/node_modules/compact-encoding/index.js","hypercore-crypto":"/node_modules/pgh/node_modules/hypercore-crypto/index.js","hypercore/lib/default-encryption.js":"/node_modules/pgh/node_modules/hypercore/lib/default-encryption.js","ready-resource":"/node_modules/pgh/node_modules/ready-resource/index.js","resolve-reject-promise":"/node_modules/pgh/node_modules/resolve-reject-promise/index.js","sodium-universal":"/node_modules/pgh/node_modules/sodium-universal/index.js"},"/node_modules/pgh/node_modules/autobase/lib/fast-forward.js":{"#package":"/node_modules/pgh/node_modules/autobase/package.json","./encryption.js":"/node_modules/pgh/node_modules/autobase/lib/encryption.js","./system.js":"/node_modules/pgh/node_modules/autobase/lib/system.js","resolve-reject-promise":"/node_modules/pgh/node_modules/resolve-reject-promise/index.js","safety-catch":"/node_modules/pgh/node_modules/safety-catch/index.js"},"/node_modules/pgh/node_modules/autobase/lib/fork.js":{"#package":"/node_modules/pgh/node_modules/autobase/package.json","./encryption.js":"/node_modules/pgh/node_modules/autobase/lib/encryption.js","./messages.js":"/node_modules/pgh/node_modules/autobase/lib/messages.js","./system.js":"/node_modules/pgh/node_modules/autobase/lib/system.js","compact-encoding":"/node_modules/pgh/node_modules/compact-encoding/index.js"},"/node_modules/pgh/node_modules/autobase/lib/linearizer.js":{"#package":"/node_modules/pgh/node_modules/autobase/package.json","./clock.js":"/node_modules/pgh/node_modules/autobase/lib/clock.js","./consensus.js":"/node_modules/pgh/node_modules/autobase/lib/consensus.js","./topolist.js":"/node_modules/pgh/node_modules/autobase/lib/topolist.js","b4a":"/node_modules/pgh/node_modules/b4a/index.js","nanoassert":"/node_modules/pgh/node_modules/nanoassert/index.js"},"/node_modules/pgh/node_modules/autobase/lib/local-state.js":{"#package":"/node_modules/pgh/node_modules/autobase/package.json","./messages.js":"/node_modules/pgh/node_modules/autobase/lib/messages.js","b4a":"/node_modules/pgh/node_modules/b4a/index.js","compact-encoding":"/node_modules/pgh/node_modules/compact-encoding/index.js"},"/node_modules/pgh/node_modules/autobase/lib/messages.js":{"#package":"/node_modules/pgh/node_modules/autobase/package.json","../encoding/spec/autobase":"/node_modules/pgh/node_modules/autobase/encoding/spec/autobase/index.js"},"/node_modules/pgh/node_modules/autobase/lib/node-buffer.js":{"#package":"/node_modules/pgh/node_modules/autobase/package.json"},"/node_modules/pgh/node_modules/autobase/lib/store.js":{"#package":"/node_modules/pgh/node_modules/autobase/package.json","./messages.js":"/node_modules/pgh/node_modules/autobase/lib/messages.js","b4a":"/node_modules/pgh/node_modules/b4a/index.js","compact-encoding":"/node_modules/pgh/node_modules/compact-encoding/index.js","hypercore":"/node_modules/pgh/node_modules/hypercore/index.js","hypercore-crypto":"/node_modules/pgh/node_modules/hypercore-crypto/index.js"},"/node_modules/pgh/node_modules/autobase/lib/system.js":{"#package":"/node_modules/pgh/node_modules/autobase/package.json","./caps.js":"/node_modules/pgh/node_modules/autobase/lib/caps.js","./messages.js":"/node_modules/pgh/node_modules/autobase/lib/messages.js","b4a":"/node_modules/pgh/node_modules/b4a/index.js","compact-encoding":"/node_modules/pgh/node_modules/compact-encoding/index.js","hyperbee":"/node_modules/pgh/node_modules/hyperbee/index.js","hypercore-crypto":"/node_modules/pgh/node_modules/hypercore-crypto/index.js","ready-resource":"/node_modules/pgh/node_modules/ready-resource/index.js","sub-encoder":"/node_modules/pgh/node_modules/sub-encoder/index.js"},"/node_modules/pgh/node_modules/autobase/lib/timer.js":{"#package":"/node_modules/pgh/node_modules/autobase/package.json","safety-catch":"/node_modules/pgh/node_modules/safety-catch/index.js"},"/node_modules/pgh/node_modules/autobase/lib/topolist.js":{"#package":"/node_modules/pgh/node_modules/autobase/package.json","b4a":"/node_modules/pgh/node_modules/b4a/index.js","nanoassert":"/node_modules/pgh/node_modules/nanoassert/index.js"},"/node_modules/pgh/node_modules/autobase/lib/updates.js":{"#package":"/node_modules/pgh/node_modules/autobase/package.json"},"/node_modules/pgh/node_modules/autobase/lib/values.js":{"#package":"/node_modules/pgh/node_modules/autobase/package.json","./caps.js":"/node_modules/pgh/node_modules/autobase/lib/caps.js","./encryption.js":"/node_modules/pgh/node_modules/autobase/lib/encryption.js","./messages.js":"/node_modules/pgh/node_modules/autobase/lib/messages.js","b4a":"/node_modules/pgh/node_modules/b4a/index.js","compact-encoding":"/node_modules/pgh/node_modules/compact-encoding/index.js","hypercore-crypto":"/node_modules/pgh/node_modules/hypercore-crypto/index.js"},"/node_modules/pgh/node_modules/autobase/lib/wakeup.js":{"#package":"/node_modules/pgh/node_modules/autobase/package.json","b4a":"/node_modules/pgh/node_modules/b4a/index.js","compact-encoding":"/node_modules/pgh/node_modules/compact-encoding/index.js","ready-resource":"/node_modules/pgh/node_modules/ready-resource/index.js","safety-catch":"/node_modules/pgh/node_modules/safety-catch/index.js"},"/node_modules/pgh/node_modules/autobase/lib/writer.js":{"#package":"/node_modules/pgh/node_modules/autobase/package.json","./linearizer.js":"/node_modules/pgh/node_modules/autobase/lib/linearizer.js","./node-buffer.js":"/node_modules/pgh/node_modules/autobase/lib/node-buffer.js","b4a":"/node_modules/pgh/node_modules/b4a/index.js","compact-encoding":"/node_modules/pgh/node_modules/compact-encoding/index.js","nanoassert":"/node_modules/pgh/node_modules/nanoassert/index.js","ready-resource":"/node_modules/pgh/node_modules/ready-resource/index.js","signal-promise":"/node_modules/pgh/node_modules/signal-promise/index.js"},"/node_modules/pgh/node_modules/autobase/package.json":{},"/node_modules/pgh/node_modules/b4a/index.js":{"#package":"/node_modules/pgh/node_modules/b4a/package.json"},"/node_modules/pgh/node_modules/b4a/package.json":{},"/node_modules/pgh/node_modules/bare-abort/binding.js":{"#package":"/node_modules/pgh/node_modules/bare-abort/package.json",".":"linked:libbare-abort.2.0.13.so"},"/node_modules/pgh/node_modules/bare-abort/index.js":{"#package":"/node_modules/pgh/node_modules/bare-abort/package.json","./binding":"/node_modules/pgh/node_modules/bare-abort/binding.js"},"/node_modules/pgh/node_modules/bare-abort/package.json":{},"/node_modules/pgh/node_modules/bare-ansi-escapes/index.js":{"#package":"/node_modules/pgh/node_modules/bare-ansi-escapes/package.json"},"/node_modules/pgh/node_modules/bare-ansi-escapes/package.json":{},"/node_modules/pgh/node_modules/bare-assert/index.js":{"#package":"/node_modules/pgh/node_modules/bare-assert/package.json","bare-inspect":"/node_modules/pgh/node_modules/bare-inspect/index.js"},"/node_modules/pgh/node_modules/bare-assert/package.json":{},"/node_modules/pgh/node_modules/bare-env/index.js":{"#package":"/node_modules/pgh/node_modules/bare-env/package.json","bare-os":"/node_modules/pgh/node_modules/bare-os/index.js"},"/node_modules/pgh/node_modules/bare-env/package.json":{},"/node_modules/pgh/node_modules/bare-events/index.js":{"#package":"/node_modules/pgh/node_modules/bare-events/package.json","./lib/errors":"/node_modules/pgh/node_modules/bare-events/lib/errors.js"},"/node_modules/pgh/node_modules/bare-events/lib/errors.js":{"#package":"/node_modules/pgh/node_modules/bare-events/package.json"},"/node_modules/pgh/node_modules/bare-events/package.json":{},"/node_modules/pgh/node_modules/bare-fs/binding.js":{"#package":"/node_modules/pgh/node_modules/bare-fs/package.json",".":"linked:libbare-fs.4.7.2.so"},"/node_modules/pgh/node_modules/bare-fs/index.js":{"#package":"/node_modules/pgh/node_modules/bare-fs/package.json","./binding":"/node_modules/pgh/node_modules/bare-fs/binding.js","./lib/constants":"/node_modules/pgh/node_modules/bare-fs/lib/constants.js","./lib/errors":"/node_modules/pgh/node_modules/bare-fs/lib/errors.js","./promises":"/node_modules/pgh/node_modules/bare-fs/promises.js","bare-events":"/node_modules/pgh/node_modules/bare-events/index.js","bare-path":"/node_modules/pgh/node_modules/bare-path/index.js","bare-stream":"/node_modules/pgh/node_modules/bare-stream/index.js","bare-url":"/node_modules/pgh/node_modules/bare-url/index.js","fast-fifo":"/node_modules/pgh/node_modules/fast-fifo/index.js"},"/node_modules/pgh/node_modules/bare-fs/lib/constants.js":{"#package":"/node_modules/pgh/node_modules/bare-fs/package.json","../binding":"/node_modules/pgh/node_modules/bare-fs/binding.js"},"/node_modules/pgh/node_modules/bare-fs/lib/errors.js":{"#package":"/node_modules/pgh/node_modules/bare-fs/package.json","bare-os":"/node_modules/pgh/node_modules/bare-os/index.js"},"/node_modules/pgh/node_modules/bare-fs/package.json":{},"/node_modules/pgh/node_modules/bare-fs/promises.js":{"#package":"/node_modules/pgh/node_modules/bare-fs/package.json",".":"/node_modules/pgh/node_modules/bare-fs/index.js","bare-events":"/node_modules/pgh/node_modules/bare-events/index.js"},"/node_modules/pgh/node_modules/bare-hrtime/binding.js":{"#package":"/node_modules/pgh/node_modules/bare-hrtime/package.json",".":"linked:libbare-hrtime.2.1.1.so"},"/node_modules/pgh/node_modules/bare-hrtime/index.js":{"#package":"/node_modules/pgh/node_modules/bare-hrtime/package.json","./binding":"/node_modules/pgh/node_modules/bare-hrtime/binding.js"},"/node_modules/pgh/node_modules/bare-hrtime/package.json":{},"/node_modules/pgh/node_modules/bare-inspect/binding.js":{"#package":"/node_modules/pgh/node_modules/bare-inspect/package.json",".":"linked:libbare-inspect.3.1.4.so"},"/node_modules/pgh/node_modules/bare-inspect/index.js":{"#package":"/node_modules/pgh/node_modules/bare-inspect/package.json","./binding":"/node_modules/pgh/node_modules/bare-inspect/binding.js","bare-ansi-escapes":"/node_modules/pgh/node_modules/bare-ansi-escapes/index.js","bare-type":"/node_modules/pgh/node_modules/bare-type/index.js"},"/node_modules/pgh/node_modules/bare-inspect/package.json":{},"/node_modules/pgh/node_modules/bare-os/binding.js":{"#package":"/node_modules/pgh/node_modules/bare-os/package.json",".":"linked:libbare-os.3.9.1.so"},"/node_modules/pgh/node_modules/bare-os/index.js":{"#package":"/node_modules/pgh/node_modules/bare-os/package.json","./binding":"/node_modules/pgh/node_modules/bare-os/binding.js","./lib/constants":"/node_modules/pgh/node_modules/bare-os/lib/constants.js","./lib/errors":"/node_modules/pgh/node_modules/bare-os/lib/errors.js"},"/node_modules/pgh/node_modules/bare-os/lib/constants.js":{"#package":"/node_modules/pgh/node_modules/bare-os/package.json","../binding":"/node_modules/pgh/node_modules/bare-os/binding.js"},"/node_modules/pgh/node_modules/bare-os/lib/errors.js":{"#package":"/node_modules/pgh/node_modules/bare-os/package.json"},"/node_modules/pgh/node_modules/bare-os/package.json":{},"/node_modules/pgh/node_modules/bare-path/index.js":{"#package":"/node_modules/pgh/node_modules/bare-path/package.json","./lib/posix":"/node_modules/pgh/node_modules/bare-path/lib/posix.js","./lib/win32":"/node_modules/pgh/node_modules/bare-path/lib/win32.js"},"/node_modules/pgh/node_modules/bare-path/lib/constants.js":{"#package":"/node_modules/pgh/node_modules/bare-path/package.json"},"/node_modules/pgh/node_modules/bare-path/lib/posix.js":{"#package":"/node_modules/pgh/node_modules/bare-path/package.json","./constants":"/node_modules/pgh/node_modules/bare-path/lib/constants.js","./shared":"/node_modules/pgh/node_modules/bare-path/lib/shared.js","./win32":"/node_modules/pgh/node_modules/bare-path/lib/win32.js","bare-os":"/node_modules/pgh/node_modules/bare-os/index.js"},"/node_modules/pgh/node_modules/bare-path/lib/shared.js":{"#package":"/node_modules/pgh/node_modules/bare-path/package.json","./constants":"/node_modules/pgh/node_modules/bare-path/lib/constants.js"},"/node_modules/pgh/node_modules/bare-path/lib/win32.js":{"#package":"/node_modules/pgh/node_modules/bare-path/package.json","./constants":"/node_modules/pgh/node_modules/bare-path/lib/constants.js","./posix":"/node_modules/pgh/node_modules/bare-path/lib/posix.js","./shared":"/node_modules/pgh/node_modules/bare-path/lib/shared.js","bare-os":"/node_modules/pgh/node_modules/bare-os/index.js"},"/node_modules/pgh/node_modules/bare-path/package.json":{},"/node_modules/pgh/node_modules/bare-process/index.js":{"#package":"/node_modules/pgh/node_modules/bare-process/package.json","bare-abort":"/node_modules/pgh/node_modules/bare-abort/index.js","bare-env":"/node_modules/pgh/node_modules/bare-env/index.js","bare-events":"/node_modules/pgh/node_modules/bare-events/index.js","bare-hrtime":"/node_modules/pgh/node_modules/bare-hrtime/index.js","bare-os":"/node_modules/pgh/node_modules/bare-os/index.js","bare-signals":"/node_modules/pgh/node_modules/bare-signals/index.js","bare-stdio":"/node_modules/pgh/node_modules/bare-stdio/index.js"},"/node_modules/pgh/node_modules/bare-process/package.json":{},"/node_modules/pgh/node_modules/bare-signals/binding.js":{"#package":"/node_modules/pgh/node_modules/bare-signals/package.json",".":"linked:libbare-signals.4.2.0.so"},"/node_modules/pgh/node_modules/bare-signals/index.js":{"#package":"/node_modules/pgh/node_modules/bare-signals/package.json","./binding":"/node_modules/pgh/node_modules/bare-signals/binding.js","./lib/emitter":"/node_modules/pgh/node_modules/bare-signals/lib/emitter.js","./lib/errors":"/node_modules/pgh/node_modules/bare-signals/lib/errors.js","bare-events":"/node_modules/pgh/node_modules/bare-events/index.js","bare-os":"/node_modules/pgh/node_modules/bare-os/index.js"},"/node_modules/pgh/node_modules/bare-signals/lib/emitter.js":{"#package":"/node_modules/pgh/node_modules/bare-signals/package.json","..":"/node_modules/pgh/node_modules/bare-signals/index.js","bare-events":"/node_modules/pgh/node_modules/bare-events/index.js"},"/node_modules/pgh/node_modules/bare-signals/lib/errors.js":{"#package":"/node_modules/pgh/node_modules/bare-signals/package.json"},"/node_modules/pgh/node_modules/bare-signals/package.json":{},"/node_modules/pgh/node_modules/bare-stdio/binding.js":{"#package":"/node_modules/pgh/node_modules/bare-stdio/package.json",".":"linked:libbare-stdio.1.0.2.so"},"/node_modules/pgh/node_modules/bare-stdio/index.js":{"#package":"/node_modules/pgh/node_modules/bare-stdio/package.json","./binding":"/node_modules/pgh/node_modules/bare-stdio/binding.js","bare-fs":"/node_modules/pgh/node_modules/bare-fs/index.js","bare-pipe":"/node_modules/pgh/node_modules/bare-stdio/node_modules/bare-pipe/index.js","bare-tty":"/node_modules/pgh/node_modules/bare-tty/index.js"},"/node_modules/pgh/node_modules/bare-stdio/node_modules/bare-pipe/binding.js":{"#package":"/node_modules/pgh/node_modules/bare-stdio/node_modules/bare-pipe/package.json",".":"linked:libbare-pipe.4.2.0.so"},"/node_modules/pgh/node_modules/bare-stdio/node_modules/bare-pipe/index.js":{"#package":"/node_modules/pgh/node_modules/bare-stdio/node_modules/bare-pipe/package.json","./binding":"/node_modules/pgh/node_modules/bare-stdio/node_modules/bare-pipe/binding.js","./lib/constants":"/node_modules/pgh/node_modules/bare-stdio/node_modules/bare-pipe/lib/constants.js","./lib/errors":"/node_modules/pgh/node_modules/bare-stdio/node_modules/bare-pipe/lib/errors.js","bare-events":"/node_modules/pgh/node_modules/bare-events/index.js","bare-stream":"/node_modules/pgh/node_modules/bare-stream/index.js"},"/node_modules/pgh/node_modules/bare-stdio/node_modules/bare-pipe/lib/constants.js":{"#package":"/node_modules/pgh/node_modules/bare-stdio/node_modules/bare-pipe/package.json","../binding":"/node_modules/pgh/node_modules/bare-stdio/node_modules/bare-pipe/binding.js"},"/node_modules/pgh/node_modules/bare-stdio/node_modules/bare-pipe/lib/errors.js":{"#package":"/node_modules/pgh/node_modules/bare-stdio/node_modules/bare-pipe/package.json"},"/node_modules/pgh/node_modules/bare-stdio/node_modules/bare-pipe/package.json":{},"/node_modules/pgh/node_modules/bare-stdio/package.json":{},"/node_modules/pgh/node_modules/bare-stream/index.js":{"#package":"/node_modules/pgh/node_modules/bare-stream/package.json","./web":"/node_modules/pgh/node_modules/bare-stream/web.js","streamx":"/node_modules/pgh/node_modules/streamx/index.js"},"/node_modules/pgh/node_modules/bare-stream/package.json":{},"/node_modules/pgh/node_modules/bare-stream/web.js":{"#package":"/node_modules/pgh/node_modules/bare-stream/package.json","streamx":"/node_modules/pgh/node_modules/streamx/index.js","teex":"/node_modules/pgh/node_modules/teex/index.js"},"/node_modules/pgh/node_modules/bare-tty/binding.js":{"#package":"/node_modules/pgh/node_modules/bare-tty/package.json",".":"linked:libbare-tty.5.1.0.so"},"/node_modules/pgh/node_modules/bare-tty/index.js":{"#package":"/node_modules/pgh/node_modules/bare-tty/package.json","./binding":"/node_modules/pgh/node_modules/bare-tty/binding.js","./lib/constants":"/node_modules/pgh/node_modules/bare-tty/lib/constants.js","bare-signals":"/node_modules/pgh/node_modules/bare-signals/index.js","bare-stream":"/node_modules/pgh/node_modules/bare-stream/index.js"},"/node_modules/pgh/node_modules/bare-tty/lib/constants.js":{"#package":"/node_modules/pgh/node_modules/bare-tty/package.json","../binding":"/node_modules/pgh/node_modules/bare-tty/binding.js"},"/node_modules/pgh/node_modules/bare-tty/package.json":{},"/node_modules/pgh/node_modules/bare-type/binding.js":{"#package":"/node_modules/pgh/node_modules/bare-type/package.json",".":"linked:libbare-type.1.1.0.so"},"/node_modules/pgh/node_modules/bare-type/index.js":{"#package":"/node_modules/pgh/node_modules/bare-type/package.json","./binding":"/node_modules/pgh/node_modules/bare-type/binding.js"},"/node_modules/pgh/node_modules/bare-type/package.json":{},"/node_modules/pgh/node_modules/bare-url/binding.js":{"#package":"/node_modules/pgh/node_modules/bare-url/package.json",".":"linked:libbare-url.2.4.5.so"},"/node_modules/pgh/node_modules/bare-url/index.js":{"#package":"/node_modules/pgh/node_modules/bare-url/package.json","./binding":"/node_modules/pgh/node_modules/bare-url/binding.js","./lib/errors":"/node_modules/pgh/node_modules/bare-url/lib/errors.js","./lib/url-search-params":"/node_modules/pgh/node_modules/bare-url/lib/url-search-params.js","bare-path":"/node_modules/pgh/node_modules/bare-path/index.js"},"/node_modules/pgh/node_modules/bare-url/lib/errors.js":{"#package":"/node_modules/pgh/node_modules/bare-url/package.json"},"/node_modules/pgh/node_modules/bare-url/lib/url-search-params.js":{"#package":"/node_modules/pgh/node_modules/bare-url/package.json"},"/node_modules/pgh/node_modules/bare-url/package.json":{},"/node_modules/pgh/node_modules/big-sparse-array/index.js":{"#package":"/node_modules/pgh/node_modules/big-sparse-array/package.json"},"/node_modules/pgh/node_modules/big-sparse-array/package.json":{},"/node_modules/pgh/node_modules/bits-to-bytes/index.js":{"#package":"/node_modules/pgh/node_modules/bits-to-bytes/package.json","b4a":"/node_modules/pgh/node_modules/b4a/index.js"},"/node_modules/pgh/node_modules/bits-to-bytes/package.json":{},"/node_modules/pgh/node_modules/blind-relay/index.js":{"#package":"/node_modules/pgh/node_modules/blind-relay/package.json","./lib/errors":"/node_modules/pgh/node_modules/blind-relay/lib/errors.js","b4a":"/node_modules/pgh/node_modules/b4a/index.js","bits-to-bytes":"/node_modules/pgh/node_modules/bits-to-bytes/index.js","compact-encoding":"/node_modules/pgh/node_modules/compact-encoding/index.js","compact-encoding-bitfield":"/node_modules/pgh/node_modules/compact-encoding-bitfield/index.js","events":"/node_modules/pgh/node_modules/bare-events/index.js","protomux":"/node_modules/pgh/node_modules/protomux/index.js","sodium-universal":"/node_modules/pgh/node_modules/sodium-universal/index.js","streamx":"/node_modules/pgh/node_modules/streamx/index.js"},"/node_modules/pgh/node_modules/blind-relay/lib/errors.js":{"#package":"/node_modules/pgh/node_modules/blind-relay/package.json"},"/node_modules/pgh/node_modules/blind-relay/package.json":{},"/node_modules/pgh/node_modules/bogon/index.js":{"#package":"/node_modules/pgh/node_modules/bogon/package.json","b4a":"/node_modules/pgh/node_modules/b4a/index.js","compact-encoding":"/node_modules/pgh/node_modules/compact-encoding/index.js"},"/node_modules/pgh/node_modules/bogon/package.json":{},"/node_modules/pgh/node_modules/codecs/index.js":{"#package":"/node_modules/pgh/node_modules/codecs/package.json","b4a":"/node_modules/pgh/node_modules/b4a/index.js"},"/node_modules/pgh/node_modules/codecs/package.json":{},"/node_modules/pgh/node_modules/compact-encoding-bitfield/index.js":{"#package":"/node_modules/pgh/node_modules/compact-encoding-bitfield/package.json","compact-encoding":"/node_modules/pgh/node_modules/compact-encoding/index.js"},"/node_modules/pgh/node_modules/compact-encoding-bitfield/package.json":{},"/node_modules/pgh/node_modules/compact-encoding/endian.js":{"#package":"/node_modules/pgh/node_modules/compact-encoding/package.json"},"/node_modules/pgh/node_modules/compact-encoding/index.js":{"#package":"/node_modules/pgh/node_modules/compact-encoding/package.json","./endian":"/node_modules/pgh/node_modules/compact-encoding/endian.js","./lexint":"/node_modules/pgh/node_modules/compact-encoding/lexint.js","./raw":"/node_modules/pgh/node_modules/compact-encoding/raw.js","b4a":"/node_modules/pgh/node_modules/b4a/index.js"},"/node_modules/pgh/node_modules/compact-encoding/lexint.js":{"#package":"/node_modules/pgh/node_modules/compact-encoding/package.json"},"/node_modules/pgh/node_modules/compact-encoding/package.json":{},"/node_modules/pgh/node_modules/compact-encoding/raw.js":{"#package":"/node_modules/pgh/node_modules/compact-encoding/package.json","./endian":"/node_modules/pgh/node_modules/compact-encoding/endian.js","b4a":"/node_modules/pgh/node_modules/b4a/index.js"},"/node_modules/pgh/node_modules/core-coupler/index.js":{"#package":"/node_modules/pgh/node_modules/core-coupler/package.json","safety-catch":"/node_modules/pgh/node_modules/safety-catch/index.js"},"/node_modules/pgh/node_modules/core-coupler/package.json":{},"/node_modules/pgh/node_modules/corestore/index.js":{"#package":"/node_modules/pgh/node_modules/corestore/package.json","./lib/audit.js":"/node_modules/pgh/node_modules/corestore/lib/audit.js","./lib/notify.js":"/node_modules/pgh/node_modules/corestore/lib/notify.js","b4a":"/node_modules/pgh/node_modules/b4a/index.js","hypercore":"/node_modules/pgh/node_modules/hypercore/index.js","hypercore-crypto":"/node_modules/pgh/node_modules/hypercore-crypto/index.js","hypercore-errors":"/node_modules/pgh/node_modules/hypercore-errors/index.js","hypercore-id-encoding":"/node_modules/pgh/node_modules/hypercore-id-encoding/index.js","ready-resource":"/node_modules/pgh/node_modules/ready-resource/index.js","sodium-universal":"/node_modules/pgh/node_modules/sodium-universal/index.js","which-runtime":"/node_modules/pgh/node_modules/which-runtime/index.js"},"/node_modules/pgh/node_modules/corestore/lib/audit.js":{"#package":"/node_modules/pgh/node_modules/corestore/package.json"},"/node_modules/pgh/node_modules/corestore/lib/notify.js":{"#package":"/node_modules/pgh/node_modules/corestore/package.json","events":"/node_modules/pgh/node_modules/bare-events/index.js","streamx":"/node_modules/pgh/node_modules/streamx/index.js"},"/node_modules/pgh/node_modules/corestore/package.json":{},"/node_modules/pgh/node_modules/debounceify/index.js":{"#package":"/node_modules/pgh/node_modules/debounceify/package.json"},"/node_modules/pgh/node_modules/debounceify/package.json":{},"/node_modules/pgh/node_modules/device-file/index.js":{"#package":"/node_modules/pgh/node_modules/device-file/package.json","b4a":"/node_modules/pgh/node_modules/b4a/index.js","fd-lock":"/node_modules/pgh/node_modules/fd-lock/index.js","fs":"/node_modules/pgh/node_modules/bare-fs/index.js","fs-native-extensions":"/node_modules/pgh/node_modules/fs-native-extensions/index.js","path":"/node_modules/pgh/node_modules/bare-path/index.js","ready-resource":"/node_modules/pgh/node_modules/ready-resource/index.js"},"/node_modules/pgh/node_modules/device-file/package.json":{},"/node_modules/pgh/node_modules/dht-rpc/index.js":{"#package":"/node_modules/pgh/node_modules/dht-rpc/package.json","./lib/commands":"/node_modules/pgh/node_modules/dht-rpc/lib/commands.js","./lib/errors":"/node_modules/pgh/node_modules/dht-rpc/lib/errors.js","./lib/health":"/node_modules/pgh/node_modules/dht-rpc/lib/health.js","./lib/io":"/node_modules/pgh/node_modules/dht-rpc/lib/io.js","./lib/peer":"/node_modules/pgh/node_modules/dht-rpc/lib/peer.js","./lib/query":"/node_modules/pgh/node_modules/dht-rpc/lib/query.js","./lib/session":"/node_modules/pgh/node_modules/dht-rpc/lib/session.js","b4a":"/node_modules/pgh/node_modules/b4a/index.js","compact-encoding":"/node_modules/pgh/node_modules/compact-encoding/index.js","events":"/node_modules/pgh/node_modules/bare-events/index.js","kademlia-routing-table":"/node_modules/pgh/node_modules/kademlia-routing-table/index.js","nat-sampler":"/node_modules/pgh/node_modules/nat-sampler/index.js","sodium-universal":"/node_modules/pgh/node_modules/sodium-universal/index.js","time-ordered-set":"/node_modules/pgh/node_modules/time-ordered-set/index.js","udx-native":"/node_modules/pgh/node_modules/udx-native/lib/udx.js"},"/node_modules/pgh/node_modules/dht-rpc/lib/commands.js":{"#package":"/node_modules/pgh/node_modules/dht-rpc/package.json"},"/node_modules/pgh/node_modules/dht-rpc/lib/errors.js":{"#package":"/node_modules/pgh/node_modules/dht-rpc/package.json"},"/node_modules/pgh/node_modules/dht-rpc/lib/health.js":{"#package":"/node_modules/pgh/node_modules/dht-rpc/package.json"},"/node_modules/pgh/node_modules/dht-rpc/lib/io.js":{"#package":"/node_modules/pgh/node_modules/dht-rpc/package.json","./errors":"/node_modules/pgh/node_modules/dht-rpc/lib/errors.js","./peer":"/node_modules/pgh/node_modules/dht-rpc/lib/peer.js","adaptive-timeout":"/node_modules/pgh/node_modules/adaptive-timeout/index.js","b4a":"/node_modules/pgh/node_modules/b4a/index.js","compact-encoding":"/node_modules/pgh/node_modules/compact-encoding/index.js","fast-fifo":"/node_modules/pgh/node_modules/fast-fifo/index.js","sodium-universal":"/node_modules/pgh/node_modules/sodium-universal/index.js"},"/node_modules/pgh/node_modules/dht-rpc/lib/peer.js":{"#package":"/node_modules/pgh/node_modules/dht-rpc/package.json","b4a":"/node_modules/pgh/node_modules/b4a/index.js","compact-encoding":"/node_modules/pgh/node_modules/compact-encoding/index.js","sodium-universal":"/node_modules/pgh/node_modules/sodium-universal/index.js"},"/node_modules/pgh/node_modules/dht-rpc/lib/query.js":{"#package":"/node_modules/pgh/node_modules/dht-rpc/package.json","./commands":"/node_modules/pgh/node_modules/dht-rpc/lib/commands.js","./peer":"/node_modules/pgh/node_modules/dht-rpc/lib/peer.js","b4a":"/node_modules/pgh/node_modules/b4a/index.js","streamx":"/node_modules/pgh/node_modules/streamx/index.js"},"/node_modules/pgh/node_modules/dht-rpc/lib/session.js":{"#package":"/node_modules/pgh/node_modules/dht-rpc/package.json"},"/node_modules/pgh/node_modules/dht-rpc/package.json":{},"/node_modules/pgh/node_modules/encryption-encoding/index.js":{"#package":"/node_modules/pgh/node_modules/encryption-encoding/package.json","./spec/hyperschema":"/node_modules/pgh/node_modules/encryption-encoding/spec/hyperschema/index.js"},"/node_modules/pgh/node_modules/encryption-encoding/package.json":{},"/node_modules/pgh/node_modules/encryption-encoding/spec/hyperschema/index.js":{"#package":"/node_modules/pgh/node_modules/encryption-encoding/package.json","hyperschema/runtime":"/node_modules/pgh/node_modules/hyperschema/runtime.cjs"},"/node_modules/pgh/node_modules/events-universal/bare.js":{"#package":"/node_modules/pgh/node_modules/events-universal/package.json","bare-events":"/node_modules/pgh/node_modules/bare-events/index.js"},"/node_modules/pgh/node_modules/events-universal/package.json":{},"/node_modules/pgh/node_modules/fast-fifo/fixed-size.js":{"#package":"/node_modules/pgh/node_modules/fast-fifo/package.json"},"/node_modules/pgh/node_modules/fast-fifo/index.js":{"#package":"/node_modules/pgh/node_modules/fast-fifo/package.json","./fixed-size":"/node_modules/pgh/node_modules/fast-fifo/fixed-size.js"},"/node_modules/pgh/node_modules/fast-fifo/package.json":{},"/node_modules/pgh/node_modules/fd-lock/index.js":{"#package":"/node_modules/pgh/node_modules/fd-lock/package.json","fs":"/node_modules/pgh/node_modules/bare-fs/index.js","fs-native-extensions":"/node_modules/pgh/node_modules/fs-native-extensions/index.js","ready-resource":"/node_modules/pgh/node_modules/ready-resource/index.js","resource-on-exit":"/node_modules/pgh/node_modules/resource-on-exit/index.js"},"/node_modules/pgh/node_modules/fd-lock/package.json":{},"/node_modules/pgh/node_modules/flat-tree/index.js":{"#package":"/node_modules/pgh/node_modules/flat-tree/package.json"},"/node_modules/pgh/node_modules/flat-tree/package.json":{},"/node_modules/pgh/node_modules/fs-native-extensions/binding.js":{"#package":"/node_modules/pgh/node_modules/fs-native-extensions/package.json",".":"linked:libfs-native-extensions.1.5.0.so","require-addon":"/node_modules/pgh/node_modules/require-addon/lib/bare.js"},"/node_modules/pgh/node_modules/fs-native-extensions/index.js":{"#package":"/node_modules/pgh/node_modules/fs-native-extensions/package.json","./binding":"/node_modules/pgh/node_modules/fs-native-extensions/binding.js","which-runtime":"/node_modules/pgh/node_modules/which-runtime/index.js"},"/node_modules/pgh/node_modules/fs-native-extensions/package.json":{},"/node_modules/pgh/node_modules/hyper-overlay/lib/chunk-scheduler.js":{"#package":"/node_modules/pgh/node_modules/hyper-overlay/package.json","./chunker.js":"/node_modules/pgh/node_modules/hyper-overlay/lib/chunker.js"},"/node_modules/pgh/node_modules/hyper-overlay/lib/chunker.js":{"#package":"/node_modules/pgh/node_modules/hyper-overlay/package.json","compact-encoding":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/compact-encoding/index.js","hypercore-crypto":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/hypercore-crypto/index.js","sodium-universal":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/sodium-universal/index.js"},"/node_modules/pgh/node_modules/hyper-overlay/lib/file-index.js":{"#package":"/node_modules/pgh/node_modules/hyper-overlay/package.json","hyperbee":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/hyperbee/index.js","ready-resource":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/ready-resource/index.js"},"/node_modules/pgh/node_modules/hyper-overlay/lib/messages.js":{"#package":"/node_modules/pgh/node_modules/hyper-overlay/package.json","compact-encoding":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/compact-encoding/index.js"},"/node_modules/pgh/node_modules/hyper-overlay/lib/protocol.js":{"#package":"/node_modules/pgh/node_modules/hyper-overlay/package.json","./chunk-scheduler.js":"/node_modules/pgh/node_modules/hyper-overlay/lib/chunk-scheduler.js","./chunker.js":"/node_modules/pgh/node_modules/hyper-overlay/lib/chunker.js","./messages.js":"/node_modules/pgh/node_modules/hyper-overlay/lib/messages.js","bare-fs":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/bare-fs/index.js","bare-path":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/bare-path/index.js"},"/node_modules/pgh/node_modules/hyper-overlay/lib/transfer.js":{"#package":"/node_modules/pgh/node_modules/hyper-overlay/package.json","./chunker.js":"/node_modules/pgh/node_modules/hyper-overlay/lib/chunker.js","bare-fs":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/bare-fs/index.js","bare-os":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/bare-os/index.js","bare-path":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/bare-path/index.js","compact-encoding":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/compact-encoding/index.js","sodium-universal":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/sodium-universal/index.js"},"/node_modules/pgh/node_modules/hyper-overlay/lib/tree.js":{"#fs":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/bare-fs/index.js","#package":"/node_modules/pgh/node_modules/hyper-overlay/package.json","#path":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/bare-path/index.js","compact-encoding":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/compact-encoding/index.js","hypercore-crypto":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/hypercore-crypto/index.js"},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/b4a/index.js":{"#package":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/b4a/package.json"},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/b4a/package.json":{},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/bare-ansi-escapes/index.js":{"#package":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/bare-ansi-escapes/package.json"},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/bare-ansi-escapes/package.json":{},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/bare-assert/index.js":{"#package":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/bare-assert/package.json","bare-inspect":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/bare-inspect/index.js"},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/bare-assert/package.json":{},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/bare-events/index.js":{"#package":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/bare-events/package.json","./lib/errors":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/bare-events/lib/errors.js"},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/bare-events/lib/errors.js":{"#package":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/bare-events/package.json"},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/bare-events/package.json":{},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/bare-fs/binding.js":{"#package":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/bare-fs/package.json",".":"linked:libbare-fs.4.7.2.so"},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/bare-fs/index.js":{"#package":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/bare-fs/package.json","./binding":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/bare-fs/binding.js","./lib/constants":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/bare-fs/lib/constants.js","./lib/errors":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/bare-fs/lib/errors.js","./promises":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/bare-fs/promises.js","bare-events":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/bare-events/index.js","bare-path":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/bare-path/index.js","bare-stream":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/bare-stream/index.js","bare-url":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/bare-url/index.js","fast-fifo":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/fast-fifo/index.js"},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/bare-fs/lib/constants.js":{"#package":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/bare-fs/package.json","../binding":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/bare-fs/binding.js"},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/bare-fs/lib/errors.js":{"#package":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/bare-fs/package.json","bare-os":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/bare-os/index.js"},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/bare-fs/package.json":{},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/bare-fs/promises.js":{"#package":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/bare-fs/package.json",".":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/bare-fs/index.js","bare-events":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/bare-events/index.js"},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/bare-inspect/binding.js":{"#package":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/bare-inspect/package.json",".":"linked:libbare-inspect.3.1.4.so"},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/bare-inspect/index.js":{"#package":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/bare-inspect/package.json","./binding":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/bare-inspect/binding.js","bare-ansi-escapes":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/bare-ansi-escapes/index.js","bare-type":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/bare-type/index.js"},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/bare-inspect/package.json":{},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/bare-os/binding.js":{"#package":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/bare-os/package.json",".":"linked:libbare-os.3.9.1.so"},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/bare-os/index.js":{"#package":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/bare-os/package.json","./binding":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/bare-os/binding.js","./lib/constants":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/bare-os/lib/constants.js","./lib/errors":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/bare-os/lib/errors.js"},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/bare-os/lib/constants.js":{"#package":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/bare-os/package.json","../binding":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/bare-os/binding.js"},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/bare-os/lib/errors.js":{"#package":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/bare-os/package.json"},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/bare-os/package.json":{},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/bare-path/index.js":{"#package":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/bare-path/package.json","./lib/posix":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/bare-path/lib/posix.js","./lib/win32":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/bare-path/lib/win32.js"},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/bare-path/lib/constants.js":{"#package":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/bare-path/package.json"},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/bare-path/lib/posix.js":{"#package":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/bare-path/package.json","./constants":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/bare-path/lib/constants.js","./shared":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/bare-path/lib/shared.js","./win32":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/bare-path/lib/win32.js","bare-os":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/bare-os/index.js"},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/bare-path/lib/shared.js":{"#package":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/bare-path/package.json","./constants":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/bare-path/lib/constants.js"},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/bare-path/lib/win32.js":{"#package":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/bare-path/package.json","./constants":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/bare-path/lib/constants.js","./posix":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/bare-path/lib/posix.js","./shared":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/bare-path/lib/shared.js","bare-os":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/bare-os/index.js"},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/bare-path/package.json":{},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/bare-stream/index.js":{"#package":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/bare-stream/package.json","./web":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/bare-stream/web.js","b4a":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/b4a/index.js","streamx":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/streamx/index.js"},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/bare-stream/package.json":{},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/bare-stream/web.js":{"#package":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/bare-stream/package.json","streamx":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/streamx/index.js","teex":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/teex/index.js"},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/bare-type/binding.js":{"#package":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/bare-type/package.json",".":"linked:libbare-type.1.1.0.so"},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/bare-type/index.js":{"#package":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/bare-type/package.json","./binding":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/bare-type/binding.js"},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/bare-type/package.json":{},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/bare-url/binding.js":{"#package":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/bare-url/package.json",".":"linked:libbare-url.2.4.5.so"},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/bare-url/index.js":{"#package":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/bare-url/package.json","./binding":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/bare-url/binding.js","./lib/errors":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/bare-url/lib/errors.js","./lib/url-search-params":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/bare-url/lib/url-search-params.js","bare-path":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/bare-path/index.js"},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/bare-url/lib/errors.js":{"#package":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/bare-url/package.json"},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/bare-url/lib/url-search-params.js":{"#package":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/bare-url/package.json"},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/bare-url/package.json":{},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/codecs/index.js":{"#package":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/codecs/package.json","b4a":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/b4a/index.js"},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/codecs/package.json":{},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/compact-encoding/endian.js":{"#package":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/compact-encoding/package.json"},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/compact-encoding/index.js":{"#package":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/compact-encoding/package.json","./endian":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/compact-encoding/endian.js","./lexint":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/compact-encoding/lexint.js","./raw":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/compact-encoding/raw.js","b4a":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/b4a/index.js"},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/compact-encoding/lexint.js":{"#package":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/compact-encoding/package.json"},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/compact-encoding/package.json":{},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/compact-encoding/raw.js":{"#package":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/compact-encoding/package.json","./endian":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/compact-encoding/endian.js","b4a":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/b4a/index.js"},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/debounceify/index.js":{"#package":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/debounceify/package.json"},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/debounceify/package.json":{},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/events-universal/bare.js":{"#package":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/events-universal/package.json","bare-events":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/bare-events/index.js"},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/events-universal/package.json":{},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/fast-fifo/fixed-size.js":{"#package":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/fast-fifo/package.json"},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/fast-fifo/index.js":{"#package":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/fast-fifo/package.json","./fixed-size":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/fast-fifo/fixed-size.js"},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/fast-fifo/package.json":{},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/hyperbee/index.js":{"#package":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/hyperbee/package.json","./iterators/diff":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/hyperbee/iterators/diff.js","./iterators/history":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/hyperbee/iterators/history.js","./iterators/local":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/hyperbee/iterators/local.js","./iterators/range":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/hyperbee/iterators/range.js","./lib/extension":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/hyperbee/lib/extension.js","./lib/messages":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/hyperbee/lib/messages.js","b4a":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/b4a/index.js","codecs":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/codecs/index.js","debounceify":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/debounceify/index.js","hypercore-errors":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/hypercore-errors/index.js","mutexify/promise":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/mutexify/promise.js","rache":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/rache/index.js","ready-resource":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/ready-resource/index.js","resolve-reject-promise":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/resolve-reject-promise/index.js","safety-catch":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/safety-catch/index.js","streamx":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/streamx/index.js","unslab":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/unslab/index.js"},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/hyperbee/iterators/diff.js":{"#package":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/hyperbee/package.json","b4a":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/b4a/index.js"},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/hyperbee/iterators/history.js":{"#package":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/hyperbee/package.json"},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/hyperbee/iterators/local.js":{"#package":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/hyperbee/package.json"},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/hyperbee/iterators/range.js":{"#package":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/hyperbee/package.json","b4a":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/b4a/index.js"},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/hyperbee/lib/extension.js":{"#package":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/hyperbee/package.json","./messages":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/hyperbee/lib/messages.js"},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/hyperbee/lib/messages.js":{"#package":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/hyperbee/package.json","b4a":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/b4a/index.js","protocol-buffers-encodings":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/protocol-buffers-encodings/index.js"},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/hyperbee/package.json":{},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/hypercore-crypto/index.js":{"#package":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/hypercore-crypto/package.json","b4a":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/b4a/index.js","compact-encoding":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/compact-encoding/index.js","sodium-universal":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/sodium-universal/index.js"},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/hypercore-crypto/package.json":{},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/hypercore-errors/index.js":{"#package":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/hypercore-errors/package.json","hypercore-id-encoding":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/hypercore-id-encoding/index.js"},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/hypercore-errors/package.json":{},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/hypercore-id-encoding/index.js":{"#package":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/hypercore-id-encoding/package.json","b4a":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/b4a/index.js","z32":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/z32/index.js"},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/hypercore-id-encoding/package.json":{},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/mutexify/index.js":{"#package":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/mutexify/package.json","queue-tick":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/queue-tick/process-next-tick.js"},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/mutexify/package.json":{},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/mutexify/promise.js":{"#package":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/mutexify/package.json",".":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/mutexify/index.js"},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/protocol-buffers-encodings/index.js":{"#package":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/protocol-buffers-encodings/package.json","b4a":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/b4a/index.js","signed-varint":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/signed-varint/index.js","varint":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/varint/index.js"},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/protocol-buffers-encodings/package.json":{},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/queue-tick/package.json":{},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/queue-tick/process-next-tick.js":{"#package":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/queue-tick/package.json","./queue-microtask":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/queue-tick/queue-microtask.js"},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/queue-tick/queue-microtask.js":{"#package":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/queue-tick/package.json"},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/rache/index.js":{"#package":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/rache/package.json"},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/rache/package.json":{},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/ready-resource/index.js":{"#package":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/ready-resource/package.json","events":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/bare-events/index.js"},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/ready-resource/package.json":{},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/require-addon/lib/bare.js":{"#package":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/require-addon/package.json"},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/require-addon/package.json":{},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/resolve-reject-promise/index.js":{"#package":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/resolve-reject-promise/package.json"},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/resolve-reject-promise/package.json":{},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/safety-catch/index.js":{"#package":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/safety-catch/package.json"},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/safety-catch/package.json":{},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/signed-varint/index.js":{"#package":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/signed-varint/package.json","varint":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/varint/index.js"},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/signed-varint/package.json":{},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/sodium-native/binding.js":{"#package":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/sodium-native/package.json",".":"linked:libsodium-native.5.1.0.so","require-addon":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/require-addon/lib/bare.js"},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/sodium-native/index.js":{"#package":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/sodium-native/package.json","./binding":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/sodium-native/binding.js","assert":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/bare-assert/index.js","which-runtime":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/which-runtime/index.js"},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/sodium-native/package.json":{},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/sodium-universal/index.js":{"#package":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/sodium-universal/package.json","sodium-native":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/sodium-native/index.js"},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/sodium-universal/package.json":{},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/streamx/index.js":{"#package":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/streamx/package.json","./lib/errors.js":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/streamx/lib/errors.js","events-universal":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/events-universal/bare.js","fast-fifo":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/fast-fifo/index.js","text-decoder":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/text-decoder/index.js"},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/streamx/lib/errors.js":{"#package":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/streamx/package.json"},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/streamx/package.json":{},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/teex/index.js":{"#package":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/teex/package.json","streamx":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/streamx/index.js"},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/teex/package.json":{},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/text-decoder/index.js":{"#package":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/text-decoder/package.json","./lib/pass-through-decoder":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/text-decoder/lib/pass-through-decoder.js","./lib/utf8-decoder":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/text-decoder/lib/utf8-decoder.js"},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/text-decoder/lib/pass-through-decoder.js":{"#package":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/text-decoder/package.json","b4a":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/b4a/index.js"},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/text-decoder/lib/utf8-decoder.js":{"#package":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/text-decoder/package.json","b4a":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/b4a/index.js"},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/text-decoder/package.json":{},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/unslab/index.js":{"#package":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/unslab/package.json","b4a":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/b4a/index.js"},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/unslab/package.json":{},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/varint/decode.js":{"#package":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/varint/package.json"},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/varint/encode.js":{"#package":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/varint/package.json"},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/varint/index.js":{"#package":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/varint/package.json","./decode.js":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/varint/decode.js","./encode.js":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/varint/encode.js","./length.js":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/varint/length.js"},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/varint/length.js":{"#package":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/varint/package.json"},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/varint/package.json":{},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/which-runtime/index.js":{"#package":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/which-runtime/package.json"},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/which-runtime/package.json":{},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/z32/index.js":{"#package":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/z32/package.json","b4a":"/node_modules/pgh/node_modules/hyper-overlay/node_modules/b4a/index.js"},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/z32/package.json":{},"/node_modules/pgh/node_modules/hyper-overlay/package.json":{},"/node_modules/pgh/node_modules/hyper-svc/index.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/package.json","./lib/client.js":"/node_modules/pgh/node_modules/hyper-svc/lib/client.js","./lib/frame.js":"/node_modules/pgh/node_modules/hyper-svc/lib/frame.js","./lib/service.js":"/node_modules/pgh/node_modules/hyper-svc/lib/service.js","./lib/transports/swarm-rpc.js":"/node_modules/pgh/node_modules/hyper-svc/lib/transports/swarm-rpc.js"},"/node_modules/pgh/node_modules/hyper-svc/lib/client.js":{"#node:fs/promises":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-fs/promises.js","#node:http":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-http1/index.js","#node:url":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-url/index.js","#package":"/node_modules/pgh/node_modules/hyper-svc/package.json","./frame.js":"/node_modules/pgh/node_modules/hyper-svc/lib/frame.js","./transports/swarm-rpc.js":"/node_modules/pgh/node_modules/hyper-svc/lib/transports/swarm-rpc.js","./uds-backend.js":"/node_modules/pgh/node_modules/hyper-svc/lib/uds-backend.js","b4a":"/node_modules/pgh/node_modules/hyper-svc/node_modules/b4a/index.js","hypercore-crypto":"/node_modules/pgh/node_modules/hyper-svc/node_modules/hypercore-crypto/index.js","hyperswarm":"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperswarm/index.js","protomux":"/node_modules/pgh/node_modules/hyper-svc/node_modules/protomux/index.js"},"/node_modules/pgh/node_modules/hyper-svc/lib/frame.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/package.json","b4a":"/node_modules/pgh/node_modules/hyper-svc/node_modules/b4a/index.js"},"/node_modules/pgh/node_modules/hyper-svc/lib/service.js":{"#node:process":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-process/index.js","#package":"/node_modules/pgh/node_modules/hyper-svc/package.json","./transports/http.js":"/node_modules/pgh/node_modules/hyper-svc/lib/transports/http.js","./transports/swarm.js":"/node_modules/pgh/node_modules/hyper-svc/lib/transports/swarm.js","./transports/uds.js":"/node_modules/pgh/node_modules/hyper-svc/lib/transports/uds.js","ready-resource":"/node_modules/pgh/node_modules/hyper-svc/node_modules/ready-resource/index.js"},"/node_modules/pgh/node_modules/hyper-svc/lib/transports/http.js":{"#node:http":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-http1/index.js","#package":"/node_modules/pgh/node_modules/hyper-svc/package.json","b4a":"/node_modules/pgh/node_modules/hyper-svc/node_modules/b4a/index.js"},"/node_modules/pgh/node_modules/hyper-svc/lib/transports/swarm-rpc.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/package.json","b4a":"/node_modules/pgh/node_modules/hyper-svc/node_modules/b4a/index.js","compact-encoding":"/node_modules/pgh/node_modules/hyper-svc/node_modules/compact-encoding/index.js","protomux":"/node_modules/pgh/node_modules/hyper-svc/node_modules/protomux/index.js"},"/node_modules/pgh/node_modules/hyper-svc/lib/transports/swarm.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/package.json","./swarm-rpc.js":"/node_modules/pgh/node_modules/hyper-svc/lib/transports/swarm-rpc.js","b4a":"/node_modules/pgh/node_modules/hyper-svc/node_modules/b4a/index.js","hyperswarm":"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperswarm/index.js","protomux":"/node_modules/pgh/node_modules/hyper-svc/node_modules/protomux/index.js"},"/node_modules/pgh/node_modules/hyper-svc/lib/transports/uds.js":{"#node:fs/promises":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-fs/promises.js","#node:process":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-process/index.js","#package":"/node_modules/pgh/node_modules/hyper-svc/package.json","../frame.js":"/node_modules/pgh/node_modules/hyper-svc/lib/frame.js","../uds-backend.js":"/node_modules/pgh/node_modules/hyper-svc/lib/uds-backend.js"},"/node_modules/pgh/node_modules/hyper-svc/lib/uds-backend.js":{"#node:net":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-net/index.js","#package":"/node_modules/pgh/node_modules/hyper-svc/package.json","bare-abstract-socket":"deferred:bare-abstract-socket"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/@hyperswarm/secret-stream/index.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/@hyperswarm/secret-stream/package.json","./lib/bridge":"/node_modules/pgh/node_modules/hyper-svc/node_modules/@hyperswarm/secret-stream/lib/bridge.js","./lib/handshake":"/node_modules/pgh/node_modules/hyper-svc/node_modules/@hyperswarm/secret-stream/lib/handshake.js","b4a":"/node_modules/pgh/node_modules/hyper-svc/node_modules/b4a/index.js","hypercore-crypto":"/node_modules/pgh/node_modules/hyper-svc/node_modules/hypercore-crypto/index.js","sodium-secretstream":"/node_modules/pgh/node_modules/hyper-svc/node_modules/sodium-secretstream/index.js","sodium-universal":"/node_modules/pgh/node_modules/hyper-svc/node_modules/sodium-universal/index.js","streamx":"/node_modules/pgh/node_modules/hyper-svc/node_modules/streamx/index.js","timeout-refresh":"/node_modules/pgh/node_modules/hyper-svc/node_modules/timeout-refresh/index.js","unslab":"/node_modules/pgh/node_modules/hyper-svc/node_modules/unslab/index.js"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/@hyperswarm/secret-stream/lib/bridge.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/@hyperswarm/secret-stream/package.json","streamx":"/node_modules/pgh/node_modules/hyper-svc/node_modules/streamx/index.js"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/@hyperswarm/secret-stream/lib/handshake.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/@hyperswarm/secret-stream/package.json","b4a":"/node_modules/pgh/node_modules/hyper-svc/node_modules/b4a/index.js","noise-curve-ed":"/node_modules/pgh/node_modules/hyper-svc/node_modules/noise-curve-ed/index.js","noise-handshake":"/node_modules/pgh/node_modules/hyper-svc/node_modules/noise-handshake/noise.js","sodium-universal":"/node_modules/pgh/node_modules/hyper-svc/node_modules/sodium-universal/index.js"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/@hyperswarm/secret-stream/package.json":{},"/node_modules/pgh/node_modules/hyper-svc/node_modules/adaptive-timeout/index.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/adaptive-timeout/package.json","xache":"/node_modules/pgh/node_modules/hyper-svc/node_modules/xache/index.js"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/adaptive-timeout/package.json":{},"/node_modules/pgh/node_modules/hyper-svc/node_modules/b4a/index.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/b4a/package.json"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/b4a/package.json":{},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-abort/binding.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-abort/package.json",".":"linked:libbare-abort.2.0.13.so"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-abort/index.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-abort/package.json","./binding":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-abort/binding.js"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-abort/package.json":{},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-ansi-escapes/index.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-ansi-escapes/package.json"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-ansi-escapes/package.json":{},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-assert/index.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-assert/package.json","bare-inspect":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-inspect/index.js"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-assert/package.json":{},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-dns/binding.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-dns/package.json",".":"linked:libbare-dns.2.1.4.so"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-dns/index.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-dns/package.json","./binding":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-dns/binding.js"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-dns/package.json":{},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-env/index.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-env/package.json","bare-os":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-os/index.js"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-env/package.json":{},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-events/index.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-events/package.json","./lib/errors":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-events/lib/errors.js"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-events/lib/errors.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-events/package.json"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-events/package.json":{},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-fs/binding.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-fs/package.json",".":"linked:libbare-fs.4.7.2.so"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-fs/index.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-fs/package.json","./binding":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-fs/binding.js","./lib/constants":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-fs/lib/constants.js","./lib/errors":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-fs/lib/errors.js","./promises":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-fs/promises.js","bare-events":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-events/index.js","bare-path":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-path/index.js","bare-stream":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-stream/index.js","bare-url":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-url/index.js","fast-fifo":"/node_modules/pgh/node_modules/hyper-svc/node_modules/fast-fifo/index.js"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-fs/lib/constants.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-fs/package.json","../binding":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-fs/binding.js"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-fs/lib/errors.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-fs/package.json","bare-os":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-os/index.js"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-fs/package.json":{},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-fs/promises.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-fs/package.json",".":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-fs/index.js","bare-events":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-events/index.js"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-hrtime/binding.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-hrtime/package.json",".":"linked:libbare-hrtime.2.1.1.so"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-hrtime/index.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-hrtime/package.json","./binding":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-hrtime/binding.js"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-hrtime/package.json":{},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-http-parser/index.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-http-parser/package.json","./lib/errors":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-http-parser/lib/errors.js"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-http-parser/lib/errors.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-http-parser/package.json"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-http-parser/package.json":{},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-http1/index.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-http1/package.json","./lib/agent":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-http1/lib/agent.js","./lib/client-connection":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-http1/lib/client-connection.js","./lib/client-request":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-http1/lib/client-request.js","./lib/constants":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-http1/lib/constants.js","./lib/errors":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-http1/lib/errors.js","./lib/incoming-message":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-http1/lib/incoming-message.js","./lib/outgoing-message":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-http1/lib/outgoing-message.js","./lib/server":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-http1/lib/server.js","./lib/server-connection":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-http1/lib/server-connection.js","./lib/server-response":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-http1/lib/server-response.js"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-http1/lib/agent.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-http1/package.json","./client-connection":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-http1/lib/client-connection.js","./errors":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-http1/lib/errors.js","bare-events":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-events/index.js","bare-tcp":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-tcp/index.js"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-http1/lib/client-connection.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-http1/package.json","./errors":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-http1/lib/errors.js","./incoming-message":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-http1/lib/incoming-message.js","bare-http-parser":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-http-parser/index.js"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-http1/lib/client-request.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-http1/package.json","./agent":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-http1/lib/agent.js","./outgoing-message":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-http1/lib/outgoing-message.js"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-http1/lib/constants.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-http1/package.json"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-http1/lib/errors.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-http1/package.json"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-http1/lib/incoming-message.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-http1/package.json","bare-stream":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-stream/index.js"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-http1/lib/outgoing-message.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-http1/package.json","./errors":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-http1/lib/errors.js","bare-stream":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-stream/index.js"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-http1/lib/server-connection.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-http1/package.json","./incoming-message":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-http1/lib/incoming-message.js","./server-response":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-http1/lib/server-response.js","bare-http-parser":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-http-parser/index.js","bare-stream":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-stream/index.js"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-http1/lib/server-response.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-http1/package.json","./constants":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-http1/lib/constants.js","./outgoing-message":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-http1/lib/outgoing-message.js","bare-stream":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-stream/index.js"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-http1/lib/server.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-http1/package.json","./server-connection":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-http1/lib/server-connection.js","bare-tcp":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-tcp/index.js"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-http1/package.json":{},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-inspect/binding.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-inspect/package.json",".":"linked:libbare-inspect.3.1.4.so"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-inspect/index.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-inspect/package.json","./binding":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-inspect/binding.js","bare-ansi-escapes":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-ansi-escapes/index.js","bare-type":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-type/index.js"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-inspect/package.json":{},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-net/index.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-net/package.json","bare-events":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-events/index.js","bare-pipe":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-net/node_modules/bare-pipe/index.js","bare-stream":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-stream/index.js","bare-tcp":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-net/node_modules/bare-tcp/index.js"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-net/node_modules/bare-dns/binding.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-net/node_modules/bare-dns/package.json",".":"linked:libbare-dns.1.0.5.so"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-net/node_modules/bare-dns/index.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-net/node_modules/bare-dns/package.json","./binding":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-net/node_modules/bare-dns/binding.js"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-net/node_modules/bare-dns/package.json":{},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-net/node_modules/bare-pipe/binding.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-net/node_modules/bare-pipe/package.json",".":"linked:libbare-pipe.3.3.8.so"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-net/node_modules/bare-pipe/index.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-net/node_modules/bare-pipe/package.json","./binding":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-net/node_modules/bare-pipe/binding.js","./lib/constants":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-net/node_modules/bare-pipe/lib/constants.js","./lib/errors":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-net/node_modules/bare-pipe/lib/errors.js","bare-events":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-events/index.js","bare-stream":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-stream/index.js"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-net/node_modules/bare-pipe/lib/constants.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-net/node_modules/bare-pipe/package.json"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-net/node_modules/bare-pipe/lib/errors.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-net/node_modules/bare-pipe/package.json"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-net/node_modules/bare-pipe/package.json":{},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-net/node_modules/bare-tcp/binding.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-net/node_modules/bare-tcp/package.json",".":"linked:libbare-tcp.1.9.1.so"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-net/node_modules/bare-tcp/index.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-net/node_modules/bare-tcp/package.json","./binding":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-net/node_modules/bare-tcp/binding.js","./lib/constants":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-net/node_modules/bare-tcp/lib/constants.js","./lib/errors":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-net/node_modules/bare-tcp/lib/errors.js","./lib/ip":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-net/node_modules/bare-tcp/lib/ip.js","bare-dns":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-net/node_modules/bare-dns/index.js","bare-events":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-events/index.js","bare-stream":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-stream/index.js"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-net/node_modules/bare-tcp/lib/constants.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-net/node_modules/bare-tcp/package.json"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-net/node_modules/bare-tcp/lib/errors.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-net/node_modules/bare-tcp/package.json"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-net/node_modules/bare-tcp/lib/ip.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-net/node_modules/bare-tcp/package.json"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-net/node_modules/bare-tcp/package.json":{},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-net/package.json":{},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-os/binding.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-os/package.json",".":"linked:libbare-os.3.9.1.so"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-os/index.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-os/package.json","./binding":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-os/binding.js","./lib/constants":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-os/lib/constants.js","./lib/errors":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-os/lib/errors.js"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-os/lib/constants.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-os/package.json","../binding":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-os/binding.js"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-os/lib/errors.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-os/package.json"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-os/package.json":{},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-path/index.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-path/package.json","./lib/posix":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-path/lib/posix.js","./lib/win32":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-path/lib/win32.js"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-path/lib/constants.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-path/package.json"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-path/lib/posix.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-path/package.json","./constants":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-path/lib/constants.js","./shared":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-path/lib/shared.js","./win32":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-path/lib/win32.js","bare-os":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-os/index.js"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-path/lib/shared.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-path/package.json","./constants":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-path/lib/constants.js"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-path/lib/win32.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-path/package.json","./constants":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-path/lib/constants.js","./posix":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-path/lib/posix.js","./shared":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-path/lib/shared.js","bare-os":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-os/index.js"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-path/package.json":{},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-pipe/binding.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-pipe/package.json",".":"linked:libbare-pipe.4.1.5.so"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-pipe/index.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-pipe/package.json","./binding":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-pipe/binding.js","./lib/constants":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-pipe/lib/constants.js","./lib/errors":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-pipe/lib/errors.js","bare-events":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-events/index.js","bare-stream":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-stream/index.js"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-pipe/lib/constants.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-pipe/package.json"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-pipe/lib/errors.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-pipe/package.json"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-pipe/package.json":{},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-process/index.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-process/package.json","bare-abort":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-abort/index.js","bare-env":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-env/index.js","bare-events":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-events/index.js","bare-hrtime":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-hrtime/index.js","bare-os":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-os/index.js","bare-signals":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-signals/index.js","bare-stdio":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-stdio/index.js"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-process/package.json":{},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-signals/binding.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-signals/package.json",".":"linked:libbare-signals.4.2.0.so"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-signals/index.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-signals/package.json","./binding":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-signals/binding.js","./lib/emitter":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-signals/lib/emitter.js","./lib/errors":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-signals/lib/errors.js","bare-events":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-events/index.js","bare-os":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-os/index.js"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-signals/lib/emitter.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-signals/package.json","..":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-signals/index.js","bare-events":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-events/index.js"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-signals/lib/errors.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-signals/package.json"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-signals/package.json":{},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-stdio/binding.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-stdio/package.json",".":"linked:libbare-stdio.1.0.2.so"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-stdio/index.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-stdio/package.json","./binding":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-stdio/binding.js","bare-fs":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-fs/index.js","bare-pipe":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-pipe/index.js","bare-tty":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-tty/index.js"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-stdio/package.json":{},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-stream/index.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-stream/package.json","./web":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-stream/web.js","streamx":"/node_modules/pgh/node_modules/hyper-svc/node_modules/streamx/index.js"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-stream/package.json":{},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-stream/web.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-stream/package.json","streamx":"/node_modules/pgh/node_modules/hyper-svc/node_modules/streamx/index.js","teex":"/node_modules/pgh/node_modules/hyper-svc/node_modules/teex/index.js"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-tcp/binding.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-tcp/package.json",".":"linked:libbare-tcp.2.2.12.so"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-tcp/index.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-tcp/package.json","./binding":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-tcp/binding.js","./lib/constants":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-tcp/lib/constants.js","./lib/errors":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-tcp/lib/errors.js","./lib/ip":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-tcp/lib/ip.js","bare-dns":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-dns/index.js","bare-events":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-events/index.js","bare-stream":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-stream/index.js"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-tcp/lib/constants.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-tcp/package.json"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-tcp/lib/errors.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-tcp/package.json"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-tcp/lib/ip.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-tcp/package.json"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-tcp/package.json":{},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-tty/binding.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-tty/package.json",".":"linked:libbare-tty.5.1.0.so"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-tty/index.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-tty/package.json","./binding":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-tty/binding.js","./lib/constants":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-tty/lib/constants.js","bare-signals":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-signals/index.js","bare-stream":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-stream/index.js"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-tty/lib/constants.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-tty/package.json","../binding":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-tty/binding.js"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-tty/package.json":{},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-type/binding.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-type/package.json",".":"linked:libbare-type.1.1.0.so"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-type/index.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-type/package.json","./binding":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-type/binding.js"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-type/package.json":{},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-url/binding.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-url/package.json",".":"linked:libbare-url.2.4.2.so"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-url/index.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-url/package.json","./binding":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-url/binding.js","./lib/errors":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-url/lib/errors.js","./lib/url-search-params":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-url/lib/url-search-params.js","bare-path":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-path/index.js"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-url/lib/errors.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-url/package.json"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-url/lib/url-search-params.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-url/package.json"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-url/package.json":{},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bits-to-bytes/index.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bits-to-bytes/package.json","b4a":"/node_modules/pgh/node_modules/hyper-svc/node_modules/b4a/index.js"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bits-to-bytes/package.json":{},"/node_modules/pgh/node_modules/hyper-svc/node_modules/blind-relay/index.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/blind-relay/package.json","./lib/errors":"/node_modules/pgh/node_modules/hyper-svc/node_modules/blind-relay/lib/errors.js","b4a":"/node_modules/pgh/node_modules/hyper-svc/node_modules/b4a/index.js","bits-to-bytes":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bits-to-bytes/index.js","compact-encoding":"/node_modules/pgh/node_modules/hyper-svc/node_modules/blind-relay/node_modules/compact-encoding/index.js","compact-encoding-bitfield":"/node_modules/pgh/node_modules/hyper-svc/node_modules/compact-encoding-bitfield/index.js","events":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-events/index.js","protomux":"/node_modules/pgh/node_modules/hyper-svc/node_modules/protomux/index.js","sodium-universal":"/node_modules/pgh/node_modules/hyper-svc/node_modules/sodium-universal/index.js","streamx":"/node_modules/pgh/node_modules/hyper-svc/node_modules/streamx/index.js"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/blind-relay/lib/errors.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/blind-relay/package.json"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/blind-relay/node_modules/compact-encoding/endian.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/blind-relay/node_modules/compact-encoding/package.json"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/blind-relay/node_modules/compact-encoding/index.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/blind-relay/node_modules/compact-encoding/package.json","./endian":"/node_modules/pgh/node_modules/hyper-svc/node_modules/blind-relay/node_modules/compact-encoding/endian.js","./lexint":"/node_modules/pgh/node_modules/hyper-svc/node_modules/blind-relay/node_modules/compact-encoding/lexint.js","./raw":"/node_modules/pgh/node_modules/hyper-svc/node_modules/blind-relay/node_modules/compact-encoding/raw.js","b4a":"/node_modules/pgh/node_modules/hyper-svc/node_modules/b4a/index.js"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/blind-relay/node_modules/compact-encoding/lexint.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/blind-relay/node_modules/compact-encoding/package.json"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/blind-relay/node_modules/compact-encoding/package.json":{},"/node_modules/pgh/node_modules/hyper-svc/node_modules/blind-relay/node_modules/compact-encoding/raw.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/blind-relay/node_modules/compact-encoding/package.json","./endian":"/node_modules/pgh/node_modules/hyper-svc/node_modules/blind-relay/node_modules/compact-encoding/endian.js","b4a":"/node_modules/pgh/node_modules/hyper-svc/node_modules/b4a/index.js"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/blind-relay/package.json":{},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bogon/index.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bogon/package.json","b4a":"/node_modules/pgh/node_modules/hyper-svc/node_modules/b4a/index.js","compact-encoding":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bogon/node_modules/compact-encoding/index.js","compact-encoding-net":"/node_modules/pgh/node_modules/hyper-svc/node_modules/compact-encoding-net/index.js"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bogon/node_modules/compact-encoding/endian.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bogon/node_modules/compact-encoding/package.json"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bogon/node_modules/compact-encoding/index.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bogon/node_modules/compact-encoding/package.json","./endian":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bogon/node_modules/compact-encoding/endian.js","./lexint":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bogon/node_modules/compact-encoding/lexint.js","./raw":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bogon/node_modules/compact-encoding/raw.js","b4a":"/node_modules/pgh/node_modules/hyper-svc/node_modules/b4a/index.js"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bogon/node_modules/compact-encoding/lexint.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bogon/node_modules/compact-encoding/package.json"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bogon/node_modules/compact-encoding/package.json":{},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bogon/node_modules/compact-encoding/raw.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bogon/node_modules/compact-encoding/package.json","./endian":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bogon/node_modules/compact-encoding/endian.js","b4a":"/node_modules/pgh/node_modules/hyper-svc/node_modules/b4a/index.js"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bogon/package.json":{},"/node_modules/pgh/node_modules/hyper-svc/node_modules/compact-encoding-bitfield/index.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/compact-encoding-bitfield/package.json","compact-encoding":"/node_modules/pgh/node_modules/hyper-svc/node_modules/compact-encoding-bitfield/node_modules/compact-encoding/index.js"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/compact-encoding-bitfield/node_modules/compact-encoding/endian.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/compact-encoding-bitfield/node_modules/compact-encoding/package.json"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/compact-encoding-bitfield/node_modules/compact-encoding/index.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/compact-encoding-bitfield/node_modules/compact-encoding/package.json","./endian":"/node_modules/pgh/node_modules/hyper-svc/node_modules/compact-encoding-bitfield/node_modules/compact-encoding/endian.js","./lexint":"/node_modules/pgh/node_modules/hyper-svc/node_modules/compact-encoding-bitfield/node_modules/compact-encoding/lexint.js","./raw":"/node_modules/pgh/node_modules/hyper-svc/node_modules/compact-encoding-bitfield/node_modules/compact-encoding/raw.js","b4a":"/node_modules/pgh/node_modules/hyper-svc/node_modules/b4a/index.js"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/compact-encoding-bitfield/node_modules/compact-encoding/lexint.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/compact-encoding-bitfield/node_modules/compact-encoding/package.json"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/compact-encoding-bitfield/node_modules/compact-encoding/package.json":{},"/node_modules/pgh/node_modules/hyper-svc/node_modules/compact-encoding-bitfield/node_modules/compact-encoding/raw.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/compact-encoding-bitfield/node_modules/compact-encoding/package.json","./endian":"/node_modules/pgh/node_modules/hyper-svc/node_modules/compact-encoding-bitfield/node_modules/compact-encoding/endian.js","b4a":"/node_modules/pgh/node_modules/hyper-svc/node_modules/b4a/index.js"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/compact-encoding-bitfield/package.json":{},"/node_modules/pgh/node_modules/hyper-svc/node_modules/compact-encoding-net/index.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/compact-encoding-net/package.json","compact-encoding":"/node_modules/pgh/node_modules/hyper-svc/node_modules/compact-encoding-net/node_modules/compact-encoding/index.js"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/compact-encoding-net/node_modules/compact-encoding/endian.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/compact-encoding-net/node_modules/compact-encoding/package.json"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/compact-encoding-net/node_modules/compact-encoding/index.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/compact-encoding-net/node_modules/compact-encoding/package.json","./endian":"/node_modules/pgh/node_modules/hyper-svc/node_modules/compact-encoding-net/node_modules/compact-encoding/endian.js","./lexint":"/node_modules/pgh/node_modules/hyper-svc/node_modules/compact-encoding-net/node_modules/compact-encoding/lexint.js","./raw":"/node_modules/pgh/node_modules/hyper-svc/node_modules/compact-encoding-net/node_modules/compact-encoding/raw.js","b4a":"/node_modules/pgh/node_modules/hyper-svc/node_modules/b4a/index.js"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/compact-encoding-net/node_modules/compact-encoding/lexint.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/compact-encoding-net/node_modules/compact-encoding/package.json"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/compact-encoding-net/node_modules/compact-encoding/package.json":{},"/node_modules/pgh/node_modules/hyper-svc/node_modules/compact-encoding-net/node_modules/compact-encoding/raw.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/compact-encoding-net/node_modules/compact-encoding/package.json","./endian":"/node_modules/pgh/node_modules/hyper-svc/node_modules/compact-encoding-net/node_modules/compact-encoding/endian.js","b4a":"/node_modules/pgh/node_modules/hyper-svc/node_modules/b4a/index.js"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/compact-encoding-net/package.json":{},"/node_modules/pgh/node_modules/hyper-svc/node_modules/compact-encoding/endian.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/compact-encoding/package.json"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/compact-encoding/index.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/compact-encoding/package.json","./endian":"/node_modules/pgh/node_modules/hyper-svc/node_modules/compact-encoding/endian.js","./lexint":"/node_modules/pgh/node_modules/hyper-svc/node_modules/compact-encoding/lexint.js","./raw":"/node_modules/pgh/node_modules/hyper-svc/node_modules/compact-encoding/raw.js","b4a":"/node_modules/pgh/node_modules/hyper-svc/node_modules/b4a/index.js"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/compact-encoding/lexint.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/compact-encoding/package.json"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/compact-encoding/package.json":{},"/node_modules/pgh/node_modules/hyper-svc/node_modules/compact-encoding/raw.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/compact-encoding/package.json","./endian":"/node_modules/pgh/node_modules/hyper-svc/node_modules/compact-encoding/endian.js","b4a":"/node_modules/pgh/node_modules/hyper-svc/node_modules/b4a/index.js"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/dht-rpc/index.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/dht-rpc/package.json","./lib/commands":"/node_modules/pgh/node_modules/hyper-svc/node_modules/dht-rpc/lib/commands.js","./lib/errors":"/node_modules/pgh/node_modules/hyper-svc/node_modules/dht-rpc/lib/errors.js","./lib/health":"/node_modules/pgh/node_modules/hyper-svc/node_modules/dht-rpc/lib/health.js","./lib/io":"/node_modules/pgh/node_modules/hyper-svc/node_modules/dht-rpc/lib/io.js","./lib/peer":"/node_modules/pgh/node_modules/hyper-svc/node_modules/dht-rpc/lib/peer.js","./lib/query":"/node_modules/pgh/node_modules/hyper-svc/node_modules/dht-rpc/lib/query.js","./lib/session":"/node_modules/pgh/node_modules/hyper-svc/node_modules/dht-rpc/lib/session.js","b4a":"/node_modules/pgh/node_modules/hyper-svc/node_modules/b4a/index.js","compact-encoding":"/node_modules/pgh/node_modules/hyper-svc/node_modules/dht-rpc/node_modules/compact-encoding/index.js","events":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-events/index.js","kademlia-routing-table":"/node_modules/pgh/node_modules/hyper-svc/node_modules/kademlia-routing-table/index.js","nat-sampler":"/node_modules/pgh/node_modules/hyper-svc/node_modules/nat-sampler/index.js","sodium-universal":"/node_modules/pgh/node_modules/hyper-svc/node_modules/sodium-universal/index.js","time-ordered-set":"/node_modules/pgh/node_modules/hyper-svc/node_modules/time-ordered-set/index.js","udx-native":"/node_modules/pgh/node_modules/hyper-svc/node_modules/udx-native/lib/udx.js"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/dht-rpc/lib/commands.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/dht-rpc/package.json"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/dht-rpc/lib/errors.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/dht-rpc/package.json"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/dht-rpc/lib/health.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/dht-rpc/package.json"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/dht-rpc/lib/io.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/dht-rpc/package.json","./errors":"/node_modules/pgh/node_modules/hyper-svc/node_modules/dht-rpc/lib/errors.js","./peer":"/node_modules/pgh/node_modules/hyper-svc/node_modules/dht-rpc/lib/peer.js","adaptive-timeout":"/node_modules/pgh/node_modules/hyper-svc/node_modules/adaptive-timeout/index.js","b4a":"/node_modules/pgh/node_modules/hyper-svc/node_modules/b4a/index.js","compact-encoding":"/node_modules/pgh/node_modules/hyper-svc/node_modules/dht-rpc/node_modules/compact-encoding/index.js","fast-fifo":"/node_modules/pgh/node_modules/hyper-svc/node_modules/fast-fifo/index.js","sodium-universal":"/node_modules/pgh/node_modules/hyper-svc/node_modules/sodium-universal/index.js"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/dht-rpc/lib/peer.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/dht-rpc/package.json","b4a":"/node_modules/pgh/node_modules/hyper-svc/node_modules/b4a/index.js","compact-encoding":"/node_modules/pgh/node_modules/hyper-svc/node_modules/dht-rpc/node_modules/compact-encoding/index.js","compact-encoding-net":"/node_modules/pgh/node_modules/hyper-svc/node_modules/compact-encoding-net/index.js","sodium-universal":"/node_modules/pgh/node_modules/hyper-svc/node_modules/sodium-universal/index.js"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/dht-rpc/lib/query.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/dht-rpc/package.json","./commands":"/node_modules/pgh/node_modules/hyper-svc/node_modules/dht-rpc/lib/commands.js","./peer":"/node_modules/pgh/node_modules/hyper-svc/node_modules/dht-rpc/lib/peer.js","b4a":"/node_modules/pgh/node_modules/hyper-svc/node_modules/b4a/index.js","streamx":"/node_modules/pgh/node_modules/hyper-svc/node_modules/streamx/index.js"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/dht-rpc/lib/session.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/dht-rpc/package.json"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/dht-rpc/node_modules/compact-encoding/endian.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/dht-rpc/node_modules/compact-encoding/package.json"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/dht-rpc/node_modules/compact-encoding/index.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/dht-rpc/node_modules/compact-encoding/package.json","./endian":"/node_modules/pgh/node_modules/hyper-svc/node_modules/dht-rpc/node_modules/compact-encoding/endian.js","./lexint":"/node_modules/pgh/node_modules/hyper-svc/node_modules/dht-rpc/node_modules/compact-encoding/lexint.js","./raw":"/node_modules/pgh/node_modules/hyper-svc/node_modules/dht-rpc/node_modules/compact-encoding/raw.js","b4a":"/node_modules/pgh/node_modules/hyper-svc/node_modules/b4a/index.js"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/dht-rpc/node_modules/compact-encoding/lexint.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/dht-rpc/node_modules/compact-encoding/package.json"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/dht-rpc/node_modules/compact-encoding/package.json":{},"/node_modules/pgh/node_modules/hyper-svc/node_modules/dht-rpc/node_modules/compact-encoding/raw.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/dht-rpc/node_modules/compact-encoding/package.json","./endian":"/node_modules/pgh/node_modules/hyper-svc/node_modules/dht-rpc/node_modules/compact-encoding/endian.js","b4a":"/node_modules/pgh/node_modules/hyper-svc/node_modules/b4a/index.js"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/dht-rpc/package.json":{},"/node_modules/pgh/node_modules/hyper-svc/node_modules/events-universal/bare.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/events-universal/package.json","bare-events":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-events/index.js"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/events-universal/package.json":{},"/node_modules/pgh/node_modules/hyper-svc/node_modules/fast-fifo/fixed-size.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/fast-fifo/package.json"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/fast-fifo/index.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/fast-fifo/package.json","./fixed-size":"/node_modules/pgh/node_modules/hyper-svc/node_modules/fast-fifo/fixed-size.js"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/fast-fifo/package.json":{},"/node_modules/pgh/node_modules/hyper-svc/node_modules/hypercore-crypto/index.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/hypercore-crypto/package.json","b4a":"/node_modules/pgh/node_modules/hyper-svc/node_modules/b4a/index.js","compact-encoding":"/node_modules/pgh/node_modules/hyper-svc/node_modules/hypercore-crypto/node_modules/compact-encoding/index.js","sodium-universal":"/node_modules/pgh/node_modules/hyper-svc/node_modules/sodium-universal/index.js"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/hypercore-crypto/node_modules/compact-encoding/endian.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/hypercore-crypto/node_modules/compact-encoding/package.json"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/hypercore-crypto/node_modules/compact-encoding/index.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/hypercore-crypto/node_modules/compact-encoding/package.json","./endian":"/node_modules/pgh/node_modules/hyper-svc/node_modules/hypercore-crypto/node_modules/compact-encoding/endian.js","./lexint":"/node_modules/pgh/node_modules/hyper-svc/node_modules/hypercore-crypto/node_modules/compact-encoding/lexint.js","./raw":"/node_modules/pgh/node_modules/hyper-svc/node_modules/hypercore-crypto/node_modules/compact-encoding/raw.js","b4a":"/node_modules/pgh/node_modules/hyper-svc/node_modules/b4a/index.js"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/hypercore-crypto/node_modules/compact-encoding/lexint.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/hypercore-crypto/node_modules/compact-encoding/package.json"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/hypercore-crypto/node_modules/compact-encoding/package.json":{},"/node_modules/pgh/node_modules/hyper-svc/node_modules/hypercore-crypto/node_modules/compact-encoding/raw.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/hypercore-crypto/node_modules/compact-encoding/package.json","./endian":"/node_modules/pgh/node_modules/hyper-svc/node_modules/hypercore-crypto/node_modules/compact-encoding/endian.js","b4a":"/node_modules/pgh/node_modules/hyper-svc/node_modules/b4a/index.js"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/hypercore-crypto/package.json":{},"/node_modules/pgh/node_modules/hyper-svc/node_modules/hypercore-id-encoding/index.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/hypercore-id-encoding/package.json","b4a":"/node_modules/pgh/node_modules/hyper-svc/node_modules/b4a/index.js","z32":"/node_modules/pgh/node_modules/hyper-svc/node_modules/z32/index.js"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/hypercore-id-encoding/package.json":{},"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperdht-address/index.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperdht-address/package.json","./spec/hyperschema":"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperdht-address/spec/hyperschema/index.js","compact-encoding":"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperdht-address/node_modules/compact-encoding/index.js"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperdht-address/node_modules/compact-encoding/endian.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperdht-address/node_modules/compact-encoding/package.json"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperdht-address/node_modules/compact-encoding/index.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperdht-address/node_modules/compact-encoding/package.json","./endian":"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperdht-address/node_modules/compact-encoding/endian.js","./lexint":"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperdht-address/node_modules/compact-encoding/lexint.js","./raw":"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperdht-address/node_modules/compact-encoding/raw.js","b4a":"/node_modules/pgh/node_modules/hyper-svc/node_modules/b4a/index.js"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperdht-address/node_modules/compact-encoding/lexint.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperdht-address/node_modules/compact-encoding/package.json"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperdht-address/node_modules/compact-encoding/package.json":{},"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperdht-address/node_modules/compact-encoding/raw.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperdht-address/node_modules/compact-encoding/package.json","./endian":"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperdht-address/node_modules/compact-encoding/endian.js","b4a":"/node_modules/pgh/node_modules/hyper-svc/node_modules/b4a/index.js"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperdht-address/package.json":{},"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperdht-address/spec/hyperschema/index.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperdht-address/package.json","hyperschema/runtime":"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperschema/runtime.cjs"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperdht/index.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperdht/package.json","./lib/connect":"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperdht/lib/connect.js","./lib/connection-pool":"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperdht/lib/connection-pool.js","./lib/constants":"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperdht/lib/constants.js","./lib/crypto":"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperdht/lib/crypto.js","./lib/errors":"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperdht/lib/errors.js","./lib/messages":"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperdht/lib/messages.js","./lib/persistent":"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperdht/lib/persistent.js","./lib/raw-stream-set":"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperdht/lib/raw-stream-set.js","./lib/router":"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperdht/lib/router.js","./lib/server":"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperdht/lib/server.js","./lib/socket-pool":"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperdht/lib/socket-pool.js","b4a":"/node_modules/pgh/node_modules/hyper-svc/node_modules/b4a/index.js","compact-encoding":"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperdht/node_modules/compact-encoding/index.js","dht-rpc":"/node_modules/pgh/node_modules/hyper-svc/node_modules/dht-rpc/index.js","safety-catch":"/node_modules/pgh/node_modules/hyper-svc/node_modules/safety-catch/index.js","sodium-universal":"/node_modules/pgh/node_modules/hyper-svc/node_modules/sodium-universal/index.js","xache":"/node_modules/pgh/node_modules/hyper-svc/node_modules/xache/index.js"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperdht/lib/announcer.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperdht/package.json","./constants":"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperdht/lib/constants.js","./encode":"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperdht/lib/encode.js","./messages":"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperdht/lib/messages.js","./persistent":"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperdht/lib/persistent.js","./sleeper":"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperdht/lib/sleeper.js","compact-encoding":"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperdht/node_modules/compact-encoding/index.js","safety-catch":"/node_modules/pgh/node_modules/hyper-svc/node_modules/safety-catch/index.js","signal-promise":"/node_modules/pgh/node_modules/hyper-svc/node_modules/signal-promise/index.js"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperdht/lib/connect.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperdht/package.json","./constants":"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperdht/lib/constants.js","./crypto":"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperdht/lib/crypto.js","./errors":"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperdht/lib/errors.js","./holepuncher":"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperdht/lib/holepuncher.js","./noise-wrap":"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperdht/lib/noise-wrap.js","./secure-payload":"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperdht/lib/secure-payload.js","./semaphore":"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperdht/lib/semaphore.js","./sleeper":"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperdht/lib/sleeper.js","@hyperswarm/secret-stream":"/node_modules/pgh/node_modules/hyper-svc/node_modules/@hyperswarm/secret-stream/index.js","b4a":"/node_modules/pgh/node_modules/hyper-svc/node_modules/b4a/index.js","blind-relay":"/node_modules/pgh/node_modules/hyper-svc/node_modules/blind-relay/index.js","bogon":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bogon/index.js","hypercore-id-encoding":"/node_modules/pgh/node_modules/hyper-svc/node_modules/hypercore-id-encoding/index.js","hyperdht-address":"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperdht-address/index.js","safety-catch":"/node_modules/pgh/node_modules/hyper-svc/node_modules/safety-catch/index.js","unslab":"/node_modules/pgh/node_modules/hyper-svc/node_modules/unslab/index.js"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperdht/lib/connection-pool.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperdht/package.json","./errors":"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperdht/lib/errors.js","b4a":"/node_modules/pgh/node_modules/hyper-svc/node_modules/b4a/index.js","events":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-events/index.js"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperdht/lib/constants.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperdht/package.json","hypercore-crypto":"/node_modules/pgh/node_modules/hyper-svc/node_modules/hypercore-crypto/index.js"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperdht/lib/crypto.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperdht/package.json","b4a":"/node_modules/pgh/node_modules/hyper-svc/node_modules/b4a/index.js","sodium-universal":"/node_modules/pgh/node_modules/hyper-svc/node_modules/sodium-universal/index.js"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperdht/lib/encode.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperdht/package.json","b4a":"/node_modules/pgh/node_modules/hyper-svc/node_modules/b4a/index.js","compact-encoding":"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperdht/node_modules/compact-encoding/index.js"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperdht/lib/errors.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperdht/package.json"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperdht/lib/holepuncher.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperdht/package.json","./constants":"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperdht/lib/constants.js","./nat":"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperdht/lib/nat.js","./sleeper":"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperdht/lib/sleeper.js","b4a":"/node_modules/pgh/node_modules/hyper-svc/node_modules/b4a/index.js"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperdht/lib/messages.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperdht/package.json","compact-encoding":"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperdht/node_modules/compact-encoding/index.js"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperdht/lib/nat.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperdht/package.json","../lib/constants":"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperdht/lib/constants.js"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperdht/lib/noise-wrap.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperdht/package.json","./constants":"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperdht/lib/constants.js","./errors":"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperdht/lib/errors.js","./messages":"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperdht/lib/messages.js","@hyperswarm/secret-stream":"/node_modules/pgh/node_modules/hyper-svc/node_modules/@hyperswarm/secret-stream/index.js","b4a":"/node_modules/pgh/node_modules/hyper-svc/node_modules/b4a/index.js","compact-encoding":"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperdht/node_modules/compact-encoding/index.js","noise-curve-ed":"/node_modules/pgh/node_modules/hyper-svc/node_modules/noise-curve-ed/index.js","noise-handshake":"/node_modules/pgh/node_modules/hyper-svc/node_modules/noise-handshake/noise.js","sodium-universal":"/node_modules/pgh/node_modules/hyper-svc/node_modules/sodium-universal/index.js"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperdht/lib/persistent.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperdht/package.json","./constants":"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperdht/lib/constants.js","./encode":"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperdht/lib/encode.js","./messages":"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperdht/lib/messages.js","b4a":"/node_modules/pgh/node_modules/hyper-svc/node_modules/b4a/index.js","compact-encoding":"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperdht/node_modules/compact-encoding/index.js","record-cache":"/node_modules/pgh/node_modules/hyper-svc/node_modules/record-cache/index.js","sodium-universal":"/node_modules/pgh/node_modules/hyper-svc/node_modules/sodium-universal/index.js","unslab":"/node_modules/pgh/node_modules/hyper-svc/node_modules/unslab/index.js","xache":"/node_modules/pgh/node_modules/hyper-svc/node_modules/xache/index.js"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperdht/lib/raw-stream-set.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperdht/package.json"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperdht/lib/router.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperdht/package.json","./constants":"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperdht/lib/constants.js","./errors":"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperdht/lib/errors.js","./messages":"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperdht/lib/messages.js","b4a":"/node_modules/pgh/node_modules/hyper-svc/node_modules/b4a/index.js","compact-encoding":"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperdht/node_modules/compact-encoding/index.js","safety-catch":"/node_modules/pgh/node_modules/hyper-svc/node_modules/safety-catch/index.js","xache":"/node_modules/pgh/node_modules/hyper-svc/node_modules/xache/index.js"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperdht/lib/secure-payload.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperdht/package.json","./messages":"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperdht/lib/messages.js","b4a":"/node_modules/pgh/node_modules/hyper-svc/node_modules/b4a/index.js","sodium-universal":"/node_modules/pgh/node_modules/hyper-svc/node_modules/sodium-universal/index.js"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperdht/lib/semaphore.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperdht/package.json"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperdht/lib/server.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperdht/package.json","./announcer":"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperdht/lib/announcer.js","./constants":"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperdht/lib/constants.js","./crypto":"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperdht/lib/crypto.js","./errors":"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperdht/lib/errors.js","./holepuncher":"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperdht/lib/holepuncher.js","./noise-wrap":"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperdht/lib/noise-wrap.js","./secure-payload":"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperdht/lib/secure-payload.js","@hyperswarm/secret-stream":"/node_modules/pgh/node_modules/hyper-svc/node_modules/@hyperswarm/secret-stream/index.js","b4a":"/node_modules/pgh/node_modules/hyper-svc/node_modules/b4a/index.js","blind-relay":"/node_modules/pgh/node_modules/hyper-svc/node_modules/blind-relay/index.js","bogon":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bogon/index.js","events":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-events/index.js","safety-catch":"/node_modules/pgh/node_modules/hyper-svc/node_modules/safety-catch/index.js"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperdht/lib/sleeper.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperdht/package.json"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperdht/lib/socket-pool.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperdht/package.json","b4a":"/node_modules/pgh/node_modules/hyper-svc/node_modules/b4a/index.js"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperdht/node_modules/compact-encoding/endian.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperdht/node_modules/compact-encoding/package.json"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperdht/node_modules/compact-encoding/index.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperdht/node_modules/compact-encoding/package.json","./endian":"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperdht/node_modules/compact-encoding/endian.js","./lexint":"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperdht/node_modules/compact-encoding/lexint.js","./raw":"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperdht/node_modules/compact-encoding/raw.js","b4a":"/node_modules/pgh/node_modules/hyper-svc/node_modules/b4a/index.js"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperdht/node_modules/compact-encoding/lexint.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperdht/node_modules/compact-encoding/package.json"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperdht/node_modules/compact-encoding/package.json":{},"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperdht/node_modules/compact-encoding/raw.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperdht/node_modules/compact-encoding/package.json","./endian":"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperdht/node_modules/compact-encoding/endian.js","b4a":"/node_modules/pgh/node_modules/hyper-svc/node_modules/b4a/index.js"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperdht/package.json":{},"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperschema/node_modules/compact-encoding/endian.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperschema/node_modules/compact-encoding/package.json"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperschema/node_modules/compact-encoding/index.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperschema/node_modules/compact-encoding/package.json","./endian":"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperschema/node_modules/compact-encoding/endian.js","./lexint":"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperschema/node_modules/compact-encoding/lexint.js","./raw":"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperschema/node_modules/compact-encoding/raw.js","b4a":"/node_modules/pgh/node_modules/hyper-svc/node_modules/b4a/index.js"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperschema/node_modules/compact-encoding/lexint.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperschema/node_modules/compact-encoding/package.json"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperschema/node_modules/compact-encoding/package.json":{},"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperschema/node_modules/compact-encoding/raw.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperschema/node_modules/compact-encoding/package.json","./endian":"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperschema/node_modules/compact-encoding/endian.js","b4a":"/node_modules/pgh/node_modules/hyper-svc/node_modules/b4a/index.js"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperschema/package.json":{},"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperschema/runtime.cjs":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperschema/package.json","compact-encoding":"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperschema/node_modules/compact-encoding/index.js"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperswarm/index.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperswarm/package.json","./lib/connection-set":"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperswarm/lib/connection-set.js","./lib/peer-discovery":"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperswarm/lib/peer-discovery.js","./lib/peer-info":"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperswarm/lib/peer-info.js","./lib/retry-timer":"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperswarm/lib/retry-timer.js","b4a":"/node_modules/pgh/node_modules/hyper-svc/node_modules/b4a/index.js","events":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-events/index.js","hyperdht":"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperdht/index.js","shuffled-priority-queue":"/node_modules/pgh/node_modules/hyper-svc/node_modules/shuffled-priority-queue/index.js","streamx":"/node_modules/pgh/node_modules/hyper-svc/node_modules/streamx/index.js","unslab":"/node_modules/pgh/node_modules/hyper-svc/node_modules/unslab/index.js"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperswarm/lib/bulk-timer.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperswarm/package.json"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperswarm/lib/connection-set.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperswarm/package.json","b4a":"/node_modules/pgh/node_modules/hyper-svc/node_modules/b4a/index.js"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperswarm/lib/peer-discovery.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperswarm/package.json","b4a":"/node_modules/pgh/node_modules/hyper-svc/node_modules/b4a/index.js","safety-catch":"/node_modules/pgh/node_modules/hyper-svc/node_modules/safety-catch/index.js"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperswarm/lib/peer-info.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperswarm/package.json","b4a":"/node_modules/pgh/node_modules/hyper-svc/node_modules/b4a/index.js","events":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-events/index.js","unslab":"/node_modules/pgh/node_modules/hyper-svc/node_modules/unslab/index.js"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperswarm/lib/retry-timer.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperswarm/package.json","./bulk-timer":"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperswarm/lib/bulk-timer.js"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperswarm/package.json":{},"/node_modules/pgh/node_modules/hyper-svc/node_modules/kademlia-routing-table/index.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/kademlia-routing-table/package.json","events":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-events/index.js"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/kademlia-routing-table/package.json":{},"/node_modules/pgh/node_modules/hyper-svc/node_modules/nanoassert/index.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/nanoassert/package.json"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/nanoassert/package.json":{},"/node_modules/pgh/node_modules/hyper-svc/node_modules/nat-sampler/index.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/nat-sampler/package.json"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/nat-sampler/package.json":{},"/node_modules/pgh/node_modules/hyper-svc/node_modules/noise-curve-ed/index.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/noise-curve-ed/package.json","b4a":"/node_modules/pgh/node_modules/hyper-svc/node_modules/b4a/index.js","nanoassert":"/node_modules/pgh/node_modules/hyper-svc/node_modules/nanoassert/index.js","sodium-universal":"/node_modules/pgh/node_modules/hyper-svc/node_modules/sodium-universal/index.js"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/noise-curve-ed/package.json":{},"/node_modules/pgh/node_modules/hyper-svc/node_modules/noise-handshake/cipher.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/noise-handshake/package.json","b4a":"/node_modules/pgh/node_modules/hyper-svc/node_modules/b4a/index.js","sodium-universal":"/node_modules/pgh/node_modules/hyper-svc/node_modules/sodium-universal/index.js"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/noise-handshake/dh.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/noise-handshake/package.json","b4a":"/node_modules/pgh/node_modules/hyper-svc/node_modules/b4a/index.js","nanoassert":"/node_modules/pgh/node_modules/hyper-svc/node_modules/nanoassert/index.js","sodium-universal":"/node_modules/pgh/node_modules/hyper-svc/node_modules/sodium-universal/index.js"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/noise-handshake/hkdf.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/noise-handshake/package.json","./hmac":"/node_modules/pgh/node_modules/hyper-svc/node_modules/noise-handshake/hmac.js","b4a":"/node_modules/pgh/node_modules/hyper-svc/node_modules/b4a/index.js"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/noise-handshake/hmac.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/noise-handshake/package.json","b4a":"/node_modules/pgh/node_modules/hyper-svc/node_modules/b4a/index.js","sodium-universal":"/node_modules/pgh/node_modules/hyper-svc/node_modules/sodium-universal/index.js"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/noise-handshake/noise.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/noise-handshake/package.json","./hkdf":"/node_modules/pgh/node_modules/hyper-svc/node_modules/noise-handshake/hkdf.js","./symmetric-state":"/node_modules/pgh/node_modules/hyper-svc/node_modules/noise-handshake/symmetric-state.js","b4a":"/node_modules/pgh/node_modules/hyper-svc/node_modules/b4a/index.js","nanoassert":"/node_modules/pgh/node_modules/hyper-svc/node_modules/nanoassert/index.js"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/noise-handshake/package.json":{},"/node_modules/pgh/node_modules/hyper-svc/node_modules/noise-handshake/symmetric-state.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/noise-handshake/package.json","./cipher":"/node_modules/pgh/node_modules/hyper-svc/node_modules/noise-handshake/cipher.js","./dh":"/node_modules/pgh/node_modules/hyper-svc/node_modules/noise-handshake/dh.js","./hkdf":"/node_modules/pgh/node_modules/hyper-svc/node_modules/noise-handshake/hkdf.js","b4a":"/node_modules/pgh/node_modules/hyper-svc/node_modules/b4a/index.js","nanoassert":"/node_modules/pgh/node_modules/hyper-svc/node_modules/nanoassert/index.js","sodium-universal":"/node_modules/pgh/node_modules/hyper-svc/node_modules/sodium-universal/index.js"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/protomux/index.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/protomux/package.json","b4a":"/node_modules/pgh/node_modules/hyper-svc/node_modules/b4a/index.js","compact-encoding":"/node_modules/pgh/node_modules/hyper-svc/node_modules/compact-encoding/index.js","queue-tick":"/node_modules/pgh/node_modules/hyper-svc/node_modules/queue-tick/process-next-tick.js","safety-catch":"/node_modules/pgh/node_modules/hyper-svc/node_modules/safety-catch/index.js","unslab":"/node_modules/pgh/node_modules/hyper-svc/node_modules/unslab/index.js"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/protomux/package.json":{},"/node_modules/pgh/node_modules/hyper-svc/node_modules/queue-tick/package.json":{},"/node_modules/pgh/node_modules/hyper-svc/node_modules/queue-tick/process-next-tick.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/queue-tick/package.json","./queue-microtask":"/node_modules/pgh/node_modules/hyper-svc/node_modules/queue-tick/queue-microtask.js"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/queue-tick/queue-microtask.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/queue-tick/package.json"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/ready-resource/index.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/ready-resource/package.json","events":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-events/index.js"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/ready-resource/package.json":{},"/node_modules/pgh/node_modules/hyper-svc/node_modules/record-cache/index.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/record-cache/package.json","b4a":"/node_modules/pgh/node_modules/hyper-svc/node_modules/b4a/index.js"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/record-cache/package.json":{},"/node_modules/pgh/node_modules/hyper-svc/node_modules/require-addon/lib/bare.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/require-addon/package.json"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/require-addon/package.json":{},"/node_modules/pgh/node_modules/hyper-svc/node_modules/safety-catch/index.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/safety-catch/package.json"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/safety-catch/package.json":{},"/node_modules/pgh/node_modules/hyper-svc/node_modules/shuffled-priority-queue/index.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/shuffled-priority-queue/package.json","unordered-set":"/node_modules/pgh/node_modules/hyper-svc/node_modules/unordered-set/index.js"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/shuffled-priority-queue/package.json":{},"/node_modules/pgh/node_modules/hyper-svc/node_modules/signal-promise/index.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/signal-promise/package.json"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/signal-promise/package.json":{},"/node_modules/pgh/node_modules/hyper-svc/node_modules/sodium-native/binding.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/sodium-native/package.json",".":"linked:libsodium-native.5.1.0.so","require-addon":"/node_modules/pgh/node_modules/hyper-svc/node_modules/require-addon/lib/bare.js"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/sodium-native/index.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/sodium-native/package.json","./binding":"/node_modules/pgh/node_modules/hyper-svc/node_modules/sodium-native/binding.js","assert":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-assert/index.js","which-runtime":"/node_modules/pgh/node_modules/hyper-svc/node_modules/which-runtime/index.js"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/sodium-native/package.json":{},"/node_modules/pgh/node_modules/hyper-svc/node_modules/sodium-secretstream/index.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/sodium-secretstream/package.json","b4a":"/node_modules/pgh/node_modules/hyper-svc/node_modules/b4a/index.js","sodium-universal":"/node_modules/pgh/node_modules/hyper-svc/node_modules/sodium-universal/index.js"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/sodium-secretstream/package.json":{},"/node_modules/pgh/node_modules/hyper-svc/node_modules/sodium-universal/index.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/sodium-universal/package.json","sodium-native":"/node_modules/pgh/node_modules/hyper-svc/node_modules/sodium-native/index.js"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/sodium-universal/package.json":{},"/node_modules/pgh/node_modules/hyper-svc/node_modules/streamx/index.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/streamx/package.json","events-universal":"/node_modules/pgh/node_modules/hyper-svc/node_modules/events-universal/bare.js","fast-fifo":"/node_modules/pgh/node_modules/hyper-svc/node_modules/fast-fifo/index.js","text-decoder":"/node_modules/pgh/node_modules/hyper-svc/node_modules/text-decoder/index.js"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/streamx/package.json":{},"/node_modules/pgh/node_modules/hyper-svc/node_modules/teex/index.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/teex/package.json","streamx":"/node_modules/pgh/node_modules/hyper-svc/node_modules/streamx/index.js"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/teex/package.json":{},"/node_modules/pgh/node_modules/hyper-svc/node_modules/text-decoder/index.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/text-decoder/package.json","./lib/pass-through-decoder":"/node_modules/pgh/node_modules/hyper-svc/node_modules/text-decoder/lib/pass-through-decoder.js","./lib/utf8-decoder":"/node_modules/pgh/node_modules/hyper-svc/node_modules/text-decoder/lib/utf8-decoder.js"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/text-decoder/lib/pass-through-decoder.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/text-decoder/package.json","b4a":"/node_modules/pgh/node_modules/hyper-svc/node_modules/b4a/index.js"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/text-decoder/lib/utf8-decoder.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/text-decoder/package.json","b4a":"/node_modules/pgh/node_modules/hyper-svc/node_modules/b4a/index.js"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/text-decoder/package.json":{},"/node_modules/pgh/node_modules/hyper-svc/node_modules/time-ordered-set/index.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/time-ordered-set/package.json"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/time-ordered-set/package.json":{},"/node_modules/pgh/node_modules/hyper-svc/node_modules/timeout-refresh/browser.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/timeout-refresh/package.json"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/timeout-refresh/index.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/timeout-refresh/package.json","./browser":"/node_modules/pgh/node_modules/hyper-svc/node_modules/timeout-refresh/browser.js","./node":"/node_modules/pgh/node_modules/hyper-svc/node_modules/timeout-refresh/node.js"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/timeout-refresh/node.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/timeout-refresh/package.json"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/timeout-refresh/package.json":{},"/node_modules/pgh/node_modules/hyper-svc/node_modules/udx-native/binding.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/udx-native/package.json",".":"linked:libudx-native.1.19.2.so","require-addon":"/node_modules/pgh/node_modules/hyper-svc/node_modules/require-addon/lib/bare.js"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/udx-native/lib/ip.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/udx-native/package.json"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/udx-native/lib/network-interfaces.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/udx-native/package.json","../binding":"/node_modules/pgh/node_modules/hyper-svc/node_modules/udx-native/binding.js","b4a":"/node_modules/pgh/node_modules/hyper-svc/node_modules/b4a/index.js","events":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-events/index.js"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/udx-native/lib/socket.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/udx-native/package.json","../binding":"/node_modules/pgh/node_modules/hyper-svc/node_modules/udx-native/binding.js","./ip":"/node_modules/pgh/node_modules/hyper-svc/node_modules/udx-native/lib/ip.js","b4a":"/node_modules/pgh/node_modules/hyper-svc/node_modules/b4a/index.js","events":"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-events/index.js"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/udx-native/lib/stream.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/udx-native/package.json","../binding":"/node_modules/pgh/node_modules/hyper-svc/node_modules/udx-native/binding.js","./ip":"/node_modules/pgh/node_modules/hyper-svc/node_modules/udx-native/lib/ip.js","b4a":"/node_modules/pgh/node_modules/hyper-svc/node_modules/b4a/index.js","streamx":"/node_modules/pgh/node_modules/hyper-svc/node_modules/streamx/index.js"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/udx-native/lib/udx.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/udx-native/package.json","../binding":"/node_modules/pgh/node_modules/hyper-svc/node_modules/udx-native/binding.js","./ip":"/node_modules/pgh/node_modules/hyper-svc/node_modules/udx-native/lib/ip.js","./network-interfaces":"/node_modules/pgh/node_modules/hyper-svc/node_modules/udx-native/lib/network-interfaces.js","./socket":"/node_modules/pgh/node_modules/hyper-svc/node_modules/udx-native/lib/socket.js","./stream":"/node_modules/pgh/node_modules/hyper-svc/node_modules/udx-native/lib/stream.js","b4a":"/node_modules/pgh/node_modules/hyper-svc/node_modules/b4a/index.js"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/udx-native/package.json":{},"/node_modules/pgh/node_modules/hyper-svc/node_modules/unordered-set/index.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/unordered-set/package.json"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/unordered-set/package.json":{},"/node_modules/pgh/node_modules/hyper-svc/node_modules/unslab/index.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/unslab/package.json","b4a":"/node_modules/pgh/node_modules/hyper-svc/node_modules/b4a/index.js"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/unslab/package.json":{},"/node_modules/pgh/node_modules/hyper-svc/node_modules/which-runtime/index.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/which-runtime/package.json"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/which-runtime/package.json":{},"/node_modules/pgh/node_modules/hyper-svc/node_modules/xache/index.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/xache/package.json"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/xache/package.json":{},"/node_modules/pgh/node_modules/hyper-svc/node_modules/z32/index.js":{"#package":"/node_modules/pgh/node_modules/hyper-svc/node_modules/z32/package.json","b4a":"/node_modules/pgh/node_modules/hyper-svc/node_modules/b4a/index.js"},"/node_modules/pgh/node_modules/hyper-svc/node_modules/z32/package.json":{},"/node_modules/pgh/node_modules/hyper-svc/package.json":{},"/node_modules/pgh/node_modules/hyperbee/index.js":{"#package":"/node_modules/pgh/node_modules/hyperbee/package.json","./iterators/diff":"/node_modules/pgh/node_modules/hyperbee/iterators/diff.js","./iterators/history":"/node_modules/pgh/node_modules/hyperbee/iterators/history.js","./iterators/local":"/node_modules/pgh/node_modules/hyperbee/iterators/local.js","./iterators/range":"/node_modules/pgh/node_modules/hyperbee/iterators/range.js","./lib/extension":"/node_modules/pgh/node_modules/hyperbee/lib/extension.js","./lib/messages":"/node_modules/pgh/node_modules/hyperbee/lib/messages.js","b4a":"/node_modules/pgh/node_modules/b4a/index.js","codecs":"/node_modules/pgh/node_modules/codecs/index.js","debounceify":"/node_modules/pgh/node_modules/debounceify/index.js","hypercore-errors":"/node_modules/pgh/node_modules/hypercore-errors/index.js","mutexify/promise":"/node_modules/pgh/node_modules/mutexify/promise.js","rache":"/node_modules/pgh/node_modules/rache/index.js","ready-resource":"/node_modules/pgh/node_modules/ready-resource/index.js","resolve-reject-promise":"/node_modules/pgh/node_modules/resolve-reject-promise/index.js","safety-catch":"/node_modules/pgh/node_modules/safety-catch/index.js","streamx":"/node_modules/pgh/node_modules/streamx/index.js","unslab":"/node_modules/pgh/node_modules/unslab/index.js"},"/node_modules/pgh/node_modules/hyperbee/iterators/diff.js":{"#package":"/node_modules/pgh/node_modules/hyperbee/package.json","b4a":"/node_modules/pgh/node_modules/b4a/index.js"},"/node_modules/pgh/node_modules/hyperbee/iterators/history.js":{"#package":"/node_modules/pgh/node_modules/hyperbee/package.json"},"/node_modules/pgh/node_modules/hyperbee/iterators/local.js":{"#package":"/node_modules/pgh/node_modules/hyperbee/package.json"},"/node_modules/pgh/node_modules/hyperbee/iterators/range.js":{"#package":"/node_modules/pgh/node_modules/hyperbee/package.json","b4a":"/node_modules/pgh/node_modules/b4a/index.js"},"/node_modules/pgh/node_modules/hyperbee/lib/extension.js":{"#package":"/node_modules/pgh/node_modules/hyperbee/package.json","./messages":"/node_modules/pgh/node_modules/hyperbee/lib/messages.js"},"/node_modules/pgh/node_modules/hyperbee/lib/messages.js":{"#package":"/node_modules/pgh/node_modules/hyperbee/package.json","b4a":"/node_modules/pgh/node_modules/b4a/index.js","protocol-buffers-encodings":"/node_modules/pgh/node_modules/protocol-buffers-encodings/index.js"},"/node_modules/pgh/node_modules/hyperbee/package.json":{},"/node_modules/pgh/node_modules/hypercore-crypto/index.js":{"#package":"/node_modules/pgh/node_modules/hypercore-crypto/package.json","b4a":"/node_modules/pgh/node_modules/b4a/index.js","compact-encoding":"/node_modules/pgh/node_modules/compact-encoding/index.js","sodium-universal":"/node_modules/pgh/node_modules/sodium-universal/index.js"},"/node_modules/pgh/node_modules/hypercore-crypto/package.json":{},"/node_modules/pgh/node_modules/hypercore-errors/index.js":{"#package":"/node_modules/pgh/node_modules/hypercore-errors/package.json","hypercore-id-encoding":"/node_modules/pgh/node_modules/hypercore-id-encoding/index.js"},"/node_modules/pgh/node_modules/hypercore-errors/package.json":{},"/node_modules/pgh/node_modules/hypercore-id-encoding/index.js":{"#package":"/node_modules/pgh/node_modules/hypercore-id-encoding/package.json","b4a":"/node_modules/pgh/node_modules/b4a/index.js","z32":"/node_modules/pgh/node_modules/z32/index.js"},"/node_modules/pgh/node_modules/hypercore-id-encoding/package.json":{},"/node_modules/pgh/node_modules/hypercore-storage/encoding/external.js":{"#package":"/node_modules/pgh/node_modules/hypercore-storage/package.json"},"/node_modules/pgh/node_modules/hypercore-storage/encoding/spec/hyperschema/index.js":{"#package":"/node_modules/pgh/node_modules/hypercore-storage/package.json","../../external.js":"/node_modules/pgh/node_modules/hypercore-storage/encoding/external.js","hyperschema/runtime":"/node_modules/pgh/node_modules/hyperschema/runtime.cjs"},"/node_modules/pgh/node_modules/hypercore-storage/index.js":{"#package":"/node_modules/pgh/node_modules/hypercore-storage/package.json","./lib/keys.js":"/node_modules/pgh/node_modules/hypercore-storage/lib/keys.js","./lib/streams.js":"/node_modules/pgh/node_modules/hypercore-storage/lib/streams.js","./lib/tx.js":"/node_modules/pgh/node_modules/hypercore-storage/lib/tx.js","./lib/view.js":"/node_modules/pgh/node_modules/hypercore-storage/lib/view.js","./migrations/0":"/node_modules/pgh/node_modules/hypercore-storage/migrations/0/index.js","./migrations/1":"/node_modules/pgh/node_modules/hypercore-storage/migrations/1/index.js","device-file":"/node_modules/pgh/node_modules/device-file/index.js","fs":"/node_modules/pgh/node_modules/bare-fs/index.js","path":"/node_modules/pgh/node_modules/bare-path/index.js","resolve-reject-promise":"/node_modules/pgh/node_modules/resolve-reject-promise/index.js","rocksdb-native":"/node_modules/pgh/node_modules/rocksdb-native/index.js","scope-lock":"/node_modules/pgh/node_modules/scope-lock/index.js"},"/node_modules/pgh/node_modules/hypercore-storage/lib/block-dependency-stream.js":{"#package":"/node_modules/pgh/node_modules/hypercore-storage/package.json","./keys":"/node_modules/pgh/node_modules/hypercore-storage/lib/keys.js","streamx":"/node_modules/pgh/node_modules/streamx/index.js"},"/node_modules/pgh/node_modules/hypercore-storage/lib/close-error-stream.js":{"#package":"/node_modules/pgh/node_modules/hypercore-storage/package.json","streamx":"/node_modules/pgh/node_modules/streamx/index.js"},"/node_modules/pgh/node_modules/hypercore-storage/lib/keys.js":{"#package":"/node_modules/pgh/node_modules/hypercore-storage/package.json","b4a":"/node_modules/pgh/node_modules/b4a/index.js","compact-encoding":"/node_modules/pgh/node_modules/compact-encoding/index.js","index-encoder":"/node_modules/pgh/node_modules/index-encoder/index.js"},"/node_modules/pgh/node_modules/hypercore-storage/lib/streams.js":{"#package":"/node_modules/pgh/node_modules/hypercore-storage/package.json","../encoding/spec/hyperschema":"/node_modules/pgh/node_modules/hypercore-storage/encoding/spec/hyperschema/index.js","./block-dependency-stream.js":"/node_modules/pgh/node_modules/hypercore-storage/lib/block-dependency-stream.js","./keys.js":"/node_modules/pgh/node_modules/hypercore-storage/lib/keys.js","b4a":"/node_modules/pgh/node_modules/b4a/index.js"},"/node_modules/pgh/node_modules/hypercore-storage/lib/tx.js":{"#package":"/node_modules/pgh/node_modules/hypercore-storage/package.json","../encoding/spec/hyperschema":"/node_modules/pgh/node_modules/hypercore-storage/encoding/spec/hyperschema/index.js","./keys.js":"/node_modules/pgh/node_modules/hypercore-storage/lib/keys.js","./view.js":"/node_modules/pgh/node_modules/hypercore-storage/lib/view.js","b4a":"/node_modules/pgh/node_modules/b4a/index.js","compact-encoding":"/node_modules/pgh/node_modules/compact-encoding/index.js","flat-tree":"/node_modules/pgh/node_modules/flat-tree/index.js"},"/node_modules/pgh/node_modules/hypercore-storage/lib/view.js":{"#package":"/node_modules/pgh/node_modules/hypercore-storage/package.json","./close-error-stream.js":"/node_modules/pgh/node_modules/hypercore-storage/lib/close-error-stream.js","b4a":"/node_modules/pgh/node_modules/b4a/index.js","streamx":"/node_modules/pgh/node_modules/streamx/index.js"},"/node_modules/pgh/node_modules/hypercore-storage/migrations/0/index.js":{"#package":"/node_modules/pgh/node_modules/hypercore-storage/package.json","../../lib/tx.js":"/node_modules/pgh/node_modules/hypercore-storage/lib/tx.js","../../lib/view.js":"/node_modules/pgh/node_modules/hypercore-storage/lib/view.js","./messages.js":"/node_modules/pgh/node_modules/hypercore-storage/migrations/0/messages.js","b4a":"/node_modules/pgh/node_modules/b4a/index.js","compact-encoding":"/node_modules/pgh/node_modules/compact-encoding/index.js","flat-tree":"/node_modules/pgh/node_modules/flat-tree/index.js","fs":"/node_modules/pgh/node_modules/bare-fs/index.js","hypercore-crypto":"/node_modules/pgh/node_modules/hypercore-crypto/index.js","path":"/node_modules/pgh/node_modules/bare-path/index.js","streamx":"/node_modules/pgh/node_modules/streamx/index.js"},"/node_modules/pgh/node_modules/hypercore-storage/migrations/0/messages.js":{"#package":"/node_modules/pgh/node_modules/hypercore-storage/package.json","b4a":"/node_modules/pgh/node_modules/b4a/index.js","compact-encoding":"/node_modules/pgh/node_modules/compact-encoding/index.js"},"/node_modules/pgh/node_modules/hypercore-storage/migrations/1/index.js":{"#package":"/node_modules/pgh/node_modules/hypercore-storage/package.json","../../encoding/spec/hyperschema":"/node_modules/pgh/node_modules/hypercore-storage/encoding/spec/hyperschema/index.js","../../lib/keys.js":"/node_modules/pgh/node_modules/hypercore-storage/lib/keys.js","../../lib/tx.js":"/node_modules/pgh/node_modules/hypercore-storage/lib/tx.js","../../lib/view.js":"/node_modules/pgh/node_modules/hypercore-storage/lib/view.js","compact-encoding":"/node_modules/pgh/node_modules/compact-encoding/index.js"},"/node_modules/pgh/node_modules/hypercore-storage/package.json":{},"/node_modules/pgh/node_modules/hypercore/index.js":{"#package":"/node_modules/pgh/node_modules/hypercore/package.json","./lib/caps":"/node_modules/pgh/node_modules/hypercore/lib/caps.js","./lib/core":"/node_modules/pgh/node_modules/hypercore/lib/core.js","./lib/default-encryption":"/node_modules/pgh/node_modules/hypercore/lib/default-encryption.js","./lib/download":"/node_modules/pgh/node_modules/hypercore/lib/download.js","./lib/feature-flags":"/node_modules/pgh/node_modules/hypercore/lib/feature-flags.js","./lib/fully-remote-proof":"/node_modules/pgh/node_modules/hypercore/lib/fully-remote-proof.js","./lib/info":"/node_modules/pgh/node_modules/hypercore/lib/info.js","./lib/inspect":"/node_modules/pgh/node_modules/hypercore/lib/inspect.js","./lib/mark-bitfield":"/node_modules/pgh/node_modules/hypercore/lib/mark-bitfield.js","./lib/merkle-tree":"/node_modules/pgh/node_modules/hypercore/lib/merkle-tree.js","./lib/replicator":"/node_modules/pgh/node_modules/hypercore/lib/replicator.js","./lib/streams":"/node_modules/pgh/node_modules/hypercore/lib/streams.js","./lib/verifier":"/node_modules/pgh/node_modules/hypercore/lib/verifier.js","./lib/wants":"/node_modules/pgh/node_modules/hypercore/lib/wants.js","@hyperswarm/secret-stream":"/node_modules/pgh/node_modules/@hyperswarm/secret-stream/index.js","b4a":"/node_modules/pgh/node_modules/b4a/index.js","compact-encoding":"/node_modules/pgh/node_modules/compact-encoding/index.js","events":"/node_modules/pgh/node_modules/bare-events/index.js","flat-tree":"/node_modules/pgh/node_modules/flat-tree/index.js","hypercore-crypto":"/node_modules/pgh/node_modules/hypercore-crypto/index.js","hypercore-errors":"/node_modules/pgh/node_modules/hypercore-errors/index.js","hypercore-id-encoding":"/node_modules/pgh/node_modules/hypercore-id-encoding/index.js","hypercore-storage":"/node_modules/pgh/node_modules/hypercore-storage/index.js","is-options":"/node_modules/pgh/node_modules/is-options/index.js","nanoassert":"/node_modules/pgh/node_modules/nanoassert/index.js","protomux":"/node_modules/pgh/node_modules/protomux/index.js","safety-catch":"/node_modules/pgh/node_modules/safety-catch/index.js","unslab":"/node_modules/pgh/node_modules/unslab/index.js"},"/node_modules/pgh/node_modules/hypercore/lib/audit.js":{"#package":"/node_modules/pgh/node_modules/hypercore/package.json","./merkle-tree":"/node_modules/pgh/node_modules/hypercore/lib/merkle-tree.js","b4a":"/node_modules/pgh/node_modules/b4a/index.js","flat-tree":"/node_modules/pgh/node_modules/flat-tree/index.js","hypercore-crypto":"/node_modules/pgh/node_modules/hypercore-crypto/index.js"},"/node_modules/pgh/node_modules/hypercore/lib/bit-interlude.js":{"#package":"/node_modules/pgh/node_modules/hypercore/package.json","./compat":"/node_modules/pgh/node_modules/hypercore/lib/compat.js","b4a":"/node_modules/pgh/node_modules/b4a/index.js"},"/node_modules/pgh/node_modules/hypercore/lib/bitfield.js":{"#package":"/node_modules/pgh/node_modules/hypercore/package.json","./compat":"/node_modules/pgh/node_modules/hypercore/lib/compat.js","b4a":"/node_modules/pgh/node_modules/b4a/index.js","big-sparse-array":"/node_modules/pgh/node_modules/big-sparse-array/index.js"},"/node_modules/pgh/node_modules/hypercore/lib/caps.js":{"#package":"/node_modules/pgh/node_modules/hypercore/package.json","b4a":"/node_modules/pgh/node_modules/b4a/index.js","compact-encoding":"/node_modules/pgh/node_modules/compact-encoding/index.js","hypercore-crypto":"/node_modules/pgh/node_modules/hypercore-crypto/index.js","sodium-universal":"/node_modules/pgh/node_modules/sodium-universal/index.js"},"/node_modules/pgh/node_modules/hypercore/lib/compat.js":{"#package":"/node_modules/pgh/node_modules/hypercore/package.json","quickbit-universal":"/node_modules/pgh/node_modules/quickbit-universal/index.js","quickbit-universal/fallback":"/node_modules/pgh/node_modules/quickbit-universal/fallback.js"},"/node_modules/pgh/node_modules/hypercore/lib/copy-prologue.js":{"#package":"/node_modules/pgh/node_modules/hypercore/package.json","./bitfield":"/node_modules/pgh/node_modules/hypercore/lib/bitfield.js","b4a":"/node_modules/pgh/node_modules/b4a/index.js","flat-tree":"/node_modules/pgh/node_modules/flat-tree/index.js","hypercore-crypto":"/node_modules/pgh/node_modules/hypercore-crypto/index.js","quickbit-universal":"/node_modules/pgh/node_modules/quickbit-universal/index.js"},"/node_modules/pgh/node_modules/hypercore/lib/core.js":{"#package":"/node_modules/pgh/node_modules/hypercore/package.json","./audit":"/node_modules/pgh/node_modules/hypercore/lib/audit.js","./bit-interlude":"/node_modules/pgh/node_modules/hypercore/lib/bit-interlude.js","./bitfield":"/node_modules/pgh/node_modules/hypercore/lib/bitfield.js","./copy-prologue":"/node_modules/pgh/node_modules/hypercore/lib/copy-prologue.js","./merkle-tree":"/node_modules/pgh/node_modules/hypercore/lib/merkle-tree.js","./mutex":"/node_modules/pgh/node_modules/hypercore/lib/mutex.js","./remote-bitfield":"/node_modules/pgh/node_modules/hypercore/lib/remote-bitfield.js","./replicator":"/node_modules/pgh/node_modules/hypercore/lib/replicator.js","./session-state":"/node_modules/pgh/node_modules/hypercore/lib/session-state.js","./verifier":"/node_modules/pgh/node_modules/hypercore/lib/verifier.js","b4a":"/node_modules/pgh/node_modules/b4a/index.js","hypercore-crypto":"/node_modules/pgh/node_modules/hypercore-crypto/index.js","hypercore-errors":"/node_modules/pgh/node_modules/hypercore-errors/index.js","unslab":"/node_modules/pgh/node_modules/unslab/index.js","z32":"/node_modules/pgh/node_modules/z32/index.js"},"/node_modules/pgh/node_modules/hypercore/lib/default-encryption.js":{"#package":"/node_modules/pgh/node_modules/hypercore/package.json","./caps":"/node_modules/pgh/node_modules/hypercore/lib/caps.js","b4a":"/node_modules/pgh/node_modules/b4a/index.js","compact-encoding":"/node_modules/pgh/node_modules/compact-encoding/index.js","sodium-universal":"/node_modules/pgh/node_modules/sodium-universal/index.js"},"/node_modules/pgh/node_modules/hypercore/lib/download.js":{"#package":"/node_modules/pgh/node_modules/hypercore/package.json"},"/node_modules/pgh/node_modules/hypercore/lib/feature-flags.js":{"#package":"/node_modules/pgh/node_modules/hypercore/package.json"},"/node_modules/pgh/node_modules/hypercore/lib/fully-remote-proof.js":{"#package":"/node_modules/pgh/node_modules/hypercore/package.json","./merkle-tree.js":"/node_modules/pgh/node_modules/hypercore/lib/merkle-tree.js","./messages.js":"/node_modules/pgh/node_modules/hypercore/lib/messages.js","b4a":"/node_modules/pgh/node_modules/b4a/index.js","compact-encoding":"/node_modules/pgh/node_modules/compact-encoding/index.js","flat-tree":"/node_modules/pgh/node_modules/flat-tree/index.js","hypercore-crypto":"/node_modules/pgh/node_modules/hypercore-crypto/index.js"},"/node_modules/pgh/node_modules/hypercore/lib/hotswap-queue.js":{"#package":"/node_modules/pgh/node_modules/hypercore/package.json"},"/node_modules/pgh/node_modules/hypercore/lib/info.js":{"#package":"/node_modules/pgh/node_modules/hypercore/package.json"},"/node_modules/pgh/node_modules/hypercore/lib/inspect.js":{"#package":"/node_modules/pgh/node_modules/hypercore/package.json","b4a":"/node_modules/pgh/node_modules/b4a/index.js"},"/node_modules/pgh/node_modules/hypercore/lib/mark-bitfield.js":{"#package":"/node_modules/pgh/node_modules/hypercore/package.json","./compat":"/node_modules/pgh/node_modules/hypercore/lib/compat.js","b4a":"/node_modules/pgh/node_modules/b4a/index.js","big-sparse-array":"/node_modules/pgh/node_modules/big-sparse-array/index.js","streamx":"/node_modules/pgh/node_modules/streamx/index.js"},"/node_modules/pgh/node_modules/hypercore/lib/merkle-tree.js":{"#package":"/node_modules/pgh/node_modules/hypercore/package.json","./caps":"/node_modules/pgh/node_modules/hypercore/lib/caps.js","b4a":"/node_modules/pgh/node_modules/b4a/index.js","flat-tree":"/node_modules/pgh/node_modules/flat-tree/index.js","hypercore-crypto":"/node_modules/pgh/node_modules/hypercore-crypto/index.js","hypercore-errors":"/node_modules/pgh/node_modules/hypercore-errors/index.js","unslab":"/node_modules/pgh/node_modules/unslab/index.js"},"/node_modules/pgh/node_modules/hypercore/lib/messages.js":{"#package":"/node_modules/pgh/node_modules/hypercore/package.json","./caps":"/node_modules/pgh/node_modules/hypercore/lib/caps.js","compact-encoding":"/node_modules/pgh/node_modules/compact-encoding/index.js","hypercore-errors":"/node_modules/pgh/node_modules/hypercore-errors/index.js","unslab":"/node_modules/pgh/node_modules/unslab/index.js"},"/node_modules/pgh/node_modules/hypercore/lib/multisig.js":{"#package":"/node_modules/pgh/node_modules/hypercore/package.json","./merkle-tree":"/node_modules/pgh/node_modules/hypercore/lib/merkle-tree.js","./messages":"/node_modules/pgh/node_modules/hypercore/lib/messages.js","b4a":"/node_modules/pgh/node_modules/b4a/index.js","compact-encoding":"/node_modules/pgh/node_modules/compact-encoding/index.js","flat-tree":"/node_modules/pgh/node_modules/flat-tree/index.js"},"/node_modules/pgh/node_modules/hypercore/lib/mutex.js":{"#package":"/node_modules/pgh/node_modules/hypercore/package.json"},"/node_modules/pgh/node_modules/hypercore/lib/receiver-queue.js":{"#package":"/node_modules/pgh/node_modules/hypercore/package.json","fast-fifo":"/node_modules/pgh/node_modules/fast-fifo/index.js"},"/node_modules/pgh/node_modules/hypercore/lib/remote-bitfield.js":{"#package":"/node_modules/pgh/node_modules/hypercore/package.json","./compat":"/node_modules/pgh/node_modules/hypercore/lib/compat.js","big-sparse-array":"/node_modules/pgh/node_modules/big-sparse-array/index.js"},"/node_modules/pgh/node_modules/hypercore/lib/replicator.js":{"#package":"/node_modules/pgh/node_modules/hypercore/package.json","./caps":"/node_modules/pgh/node_modules/hypercore/lib/caps.js","./hotswap-queue":"/node_modules/pgh/node_modules/hypercore/lib/hotswap-queue.js","./merkle-tree":"/node_modules/pgh/node_modules/hypercore/lib/merkle-tree.js","./messages":"/node_modules/pgh/node_modules/hypercore/lib/messages.js","./mutex":"/node_modules/pgh/node_modules/hypercore/lib/mutex.js","./receiver-queue":"/node_modules/pgh/node_modules/hypercore/lib/receiver-queue.js","./remote-bitfield":"/node_modules/pgh/node_modules/hypercore/lib/remote-bitfield.js","./wants":"/node_modules/pgh/node_modules/hypercore/lib/wants.js","b4a":"/node_modules/pgh/node_modules/b4a/index.js","flat-tree":"/node_modules/pgh/node_modules/flat-tree/index.js","hypercore-errors":"/node_modules/pgh/node_modules/hypercore-errors/index.js","random-array-iterator":"/node_modules/pgh/node_modules/random-array-iterator/index.js","safety-catch":"/node_modules/pgh/node_modules/safety-catch/index.js"},"/node_modules/pgh/node_modules/hypercore/lib/session-state.js":{"#package":"/node_modules/pgh/node_modules/hypercore/package.json","./bitfield":"/node_modules/pgh/node_modules/hypercore/lib/bitfield.js","./merkle-tree":"/node_modules/pgh/node_modules/hypercore/lib/merkle-tree.js","./mutex":"/node_modules/pgh/node_modules/hypercore/lib/mutex.js","b4a":"/node_modules/pgh/node_modules/b4a/index.js","flat-tree":"/node_modules/pgh/node_modules/flat-tree/index.js","hypercore-crypto":"/node_modules/pgh/node_modules/hypercore-crypto/index.js","hypercore-errors":"/node_modules/pgh/node_modules/hypercore-errors/index.js","hypercore-storage":"/node_modules/pgh/node_modules/hypercore-storage/index.js","nanoassert":"/node_modules/pgh/node_modules/nanoassert/index.js","quickbit-universal":"/node_modules/pgh/node_modules/quickbit-universal/index.js"},"/node_modules/pgh/node_modules/hypercore/lib/streams.js":{"#package":"/node_modules/pgh/node_modules/hypercore/package.json","streamx":"/node_modules/pgh/node_modules/streamx/index.js"},"/node_modules/pgh/node_modules/hypercore/lib/verifier.js":{"#package":"/node_modules/pgh/node_modules/hypercore/package.json","./caps":"/node_modules/pgh/node_modules/hypercore/lib/caps.js","./messages":"/node_modules/pgh/node_modules/hypercore/lib/messages.js","./multisig":"/node_modules/pgh/node_modules/hypercore/lib/multisig.js","b4a":"/node_modules/pgh/node_modules/b4a/index.js","compact-encoding":"/node_modules/pgh/node_modules/compact-encoding/index.js","flat-tree":"/node_modules/pgh/node_modules/flat-tree/index.js","hypercore-crypto":"/node_modules/pgh/node_modules/hypercore-crypto/index.js","hypercore-errors":"/node_modules/pgh/node_modules/hypercore-errors/index.js","unslab":"/node_modules/pgh/node_modules/unslab/index.js"},"/node_modules/pgh/node_modules/hypercore/lib/wants.js":{"#package":"/node_modules/pgh/node_modules/hypercore/package.json"},"/node_modules/pgh/node_modules/hypercore/package.json":{},"/node_modules/pgh/node_modules/hyperdht-address/index.js":{"#package":"/node_modules/pgh/node_modules/hyperdht-address/package.json","./spec/hyperschema":"/node_modules/pgh/node_modules/hyperdht-address/spec/hyperschema/index.js","compact-encoding":"/node_modules/pgh/node_modules/compact-encoding/index.js"},"/node_modules/pgh/node_modules/hyperdht-address/package.json":{},"/node_modules/pgh/node_modules/hyperdht-address/spec/hyperschema/index.js":{"#package":"/node_modules/pgh/node_modules/hyperdht-address/package.json","hyperschema/runtime":"/node_modules/pgh/node_modules/hyperschema/runtime.cjs"},"/node_modules/pgh/node_modules/hyperdht/index.js":{"#package":"/node_modules/pgh/node_modules/hyperdht/package.json","./lib/connect":"/node_modules/pgh/node_modules/hyperdht/lib/connect.js","./lib/connection-pool":"/node_modules/pgh/node_modules/hyperdht/lib/connection-pool.js","./lib/constants":"/node_modules/pgh/node_modules/hyperdht/lib/constants.js","./lib/crypto":"/node_modules/pgh/node_modules/hyperdht/lib/crypto.js","./lib/errors":"/node_modules/pgh/node_modules/hyperdht/lib/errors.js","./lib/messages":"/node_modules/pgh/node_modules/hyperdht/lib/messages.js","./lib/persistent":"/node_modules/pgh/node_modules/hyperdht/lib/persistent.js","./lib/raw-stream-set":"/node_modules/pgh/node_modules/hyperdht/lib/raw-stream-set.js","./lib/router":"/node_modules/pgh/node_modules/hyperdht/lib/router.js","./lib/server":"/node_modules/pgh/node_modules/hyperdht/lib/server.js","./lib/socket-pool":"/node_modules/pgh/node_modules/hyperdht/lib/socket-pool.js","b4a":"/node_modules/pgh/node_modules/b4a/index.js","compact-encoding":"/node_modules/pgh/node_modules/compact-encoding/index.js","dht-rpc":"/node_modules/pgh/node_modules/dht-rpc/index.js","safety-catch":"/node_modules/pgh/node_modules/safety-catch/index.js","sodium-universal":"/node_modules/pgh/node_modules/sodium-universal/index.js","xache":"/node_modules/pgh/node_modules/xache/index.js"},"/node_modules/pgh/node_modules/hyperdht/lib/announcer.js":{"#package":"/node_modules/pgh/node_modules/hyperdht/package.json","./constants":"/node_modules/pgh/node_modules/hyperdht/lib/constants.js","./encode":"/node_modules/pgh/node_modules/hyperdht/lib/encode.js","./messages":"/node_modules/pgh/node_modules/hyperdht/lib/messages.js","./persistent":"/node_modules/pgh/node_modules/hyperdht/lib/persistent.js","./sleeper":"/node_modules/pgh/node_modules/hyperdht/lib/sleeper.js","compact-encoding":"/node_modules/pgh/node_modules/compact-encoding/index.js","safety-catch":"/node_modules/pgh/node_modules/safety-catch/index.js","signal-promise":"/node_modules/pgh/node_modules/signal-promise/index.js"},"/node_modules/pgh/node_modules/hyperdht/lib/connect.js":{"#package":"/node_modules/pgh/node_modules/hyperdht/package.json","./constants":"/node_modules/pgh/node_modules/hyperdht/lib/constants.js","./crypto":"/node_modules/pgh/node_modules/hyperdht/lib/crypto.js","./errors":"/node_modules/pgh/node_modules/hyperdht/lib/errors.js","./holepuncher":"/node_modules/pgh/node_modules/hyperdht/lib/holepuncher.js","./noise-wrap":"/node_modules/pgh/node_modules/hyperdht/lib/noise-wrap.js","./secure-payload":"/node_modules/pgh/node_modules/hyperdht/lib/secure-payload.js","./semaphore":"/node_modules/pgh/node_modules/hyperdht/lib/semaphore.js","./sleeper":"/node_modules/pgh/node_modules/hyperdht/lib/sleeper.js","@hyperswarm/secret-stream":"/node_modules/pgh/node_modules/@hyperswarm/secret-stream/index.js","b4a":"/node_modules/pgh/node_modules/b4a/index.js","blind-relay":"/node_modules/pgh/node_modules/blind-relay/index.js","bogon":"/node_modules/pgh/node_modules/bogon/index.js","hypercore-id-encoding":"/node_modules/pgh/node_modules/hypercore-id-encoding/index.js","hyperdht-address":"/node_modules/pgh/node_modules/hyperdht-address/index.js","safety-catch":"/node_modules/pgh/node_modules/safety-catch/index.js","unslab":"/node_modules/pgh/node_modules/unslab/index.js"},"/node_modules/pgh/node_modules/hyperdht/lib/connection-pool.js":{"#package":"/node_modules/pgh/node_modules/hyperdht/package.json","./errors":"/node_modules/pgh/node_modules/hyperdht/lib/errors.js","b4a":"/node_modules/pgh/node_modules/b4a/index.js","events":"/node_modules/pgh/node_modules/bare-events/index.js"},"/node_modules/pgh/node_modules/hyperdht/lib/constants.js":{"#package":"/node_modules/pgh/node_modules/hyperdht/package.json","hypercore-crypto":"/node_modules/pgh/node_modules/hypercore-crypto/index.js"},"/node_modules/pgh/node_modules/hyperdht/lib/crypto.js":{"#package":"/node_modules/pgh/node_modules/hyperdht/package.json","b4a":"/node_modules/pgh/node_modules/b4a/index.js","sodium-universal":"/node_modules/pgh/node_modules/sodium-universal/index.js"},"/node_modules/pgh/node_modules/hyperdht/lib/encode.js":{"#package":"/node_modules/pgh/node_modules/hyperdht/package.json","b4a":"/node_modules/pgh/node_modules/b4a/index.js","compact-encoding":"/node_modules/pgh/node_modules/compact-encoding/index.js"},"/node_modules/pgh/node_modules/hyperdht/lib/errors.js":{"#package":"/node_modules/pgh/node_modules/hyperdht/package.json"},"/node_modules/pgh/node_modules/hyperdht/lib/holepuncher.js":{"#package":"/node_modules/pgh/node_modules/hyperdht/package.json","./constants":"/node_modules/pgh/node_modules/hyperdht/lib/constants.js","./nat":"/node_modules/pgh/node_modules/hyperdht/lib/nat.js","./sleeper":"/node_modules/pgh/node_modules/hyperdht/lib/sleeper.js","b4a":"/node_modules/pgh/node_modules/b4a/index.js"},"/node_modules/pgh/node_modules/hyperdht/lib/messages.js":{"#package":"/node_modules/pgh/node_modules/hyperdht/package.json","compact-encoding":"/node_modules/pgh/node_modules/compact-encoding/index.js"},"/node_modules/pgh/node_modules/hyperdht/lib/nat.js":{"#package":"/node_modules/pgh/node_modules/hyperdht/package.json","../lib/constants":"/node_modules/pgh/node_modules/hyperdht/lib/constants.js"},"/node_modules/pgh/node_modules/hyperdht/lib/noise-wrap.js":{"#package":"/node_modules/pgh/node_modules/hyperdht/package.json","./constants":"/node_modules/pgh/node_modules/hyperdht/lib/constants.js","./errors":"/node_modules/pgh/node_modules/hyperdht/lib/errors.js","./messages":"/node_modules/pgh/node_modules/hyperdht/lib/messages.js","@hyperswarm/secret-stream":"/node_modules/pgh/node_modules/@hyperswarm/secret-stream/index.js","b4a":"/node_modules/pgh/node_modules/b4a/index.js","compact-encoding":"/node_modules/pgh/node_modules/compact-encoding/index.js","noise-curve-ed":"/node_modules/pgh/node_modules/noise-curve-ed/index.js","noise-handshake":"/node_modules/pgh/node_modules/noise-handshake/noise.js","sodium-universal":"/node_modules/pgh/node_modules/sodium-universal/index.js"},"/node_modules/pgh/node_modules/hyperdht/lib/persistent.js":{"#package":"/node_modules/pgh/node_modules/hyperdht/package.json","./constants":"/node_modules/pgh/node_modules/hyperdht/lib/constants.js","./encode":"/node_modules/pgh/node_modules/hyperdht/lib/encode.js","./messages":"/node_modules/pgh/node_modules/hyperdht/lib/messages.js","b4a":"/node_modules/pgh/node_modules/b4a/index.js","compact-encoding":"/node_modules/pgh/node_modules/compact-encoding/index.js","record-cache":"/node_modules/pgh/node_modules/record-cache/index.js","sodium-universal":"/node_modules/pgh/node_modules/sodium-universal/index.js","unslab":"/node_modules/pgh/node_modules/unslab/index.js","xache":"/node_modules/pgh/node_modules/xache/index.js"},"/node_modules/pgh/node_modules/hyperdht/lib/raw-stream-set.js":{"#package":"/node_modules/pgh/node_modules/hyperdht/package.json"},"/node_modules/pgh/node_modules/hyperdht/lib/router.js":{"#package":"/node_modules/pgh/node_modules/hyperdht/package.json","./constants":"/node_modules/pgh/node_modules/hyperdht/lib/constants.js","./errors":"/node_modules/pgh/node_modules/hyperdht/lib/errors.js","./messages":"/node_modules/pgh/node_modules/hyperdht/lib/messages.js","b4a":"/node_modules/pgh/node_modules/b4a/index.js","compact-encoding":"/node_modules/pgh/node_modules/compact-encoding/index.js","safety-catch":"/node_modules/pgh/node_modules/safety-catch/index.js","xache":"/node_modules/pgh/node_modules/xache/index.js"},"/node_modules/pgh/node_modules/hyperdht/lib/secure-payload.js":{"#package":"/node_modules/pgh/node_modules/hyperdht/package.json","./messages":"/node_modules/pgh/node_modules/hyperdht/lib/messages.js","b4a":"/node_modules/pgh/node_modules/b4a/index.js","sodium-universal":"/node_modules/pgh/node_modules/sodium-universal/index.js"},"/node_modules/pgh/node_modules/hyperdht/lib/semaphore.js":{"#package":"/node_modules/pgh/node_modules/hyperdht/package.json"},"/node_modules/pgh/node_modules/hyperdht/lib/server.js":{"#package":"/node_modules/pgh/node_modules/hyperdht/package.json","./announcer":"/node_modules/pgh/node_modules/hyperdht/lib/announcer.js","./constants":"/node_modules/pgh/node_modules/hyperdht/lib/constants.js","./crypto":"/node_modules/pgh/node_modules/hyperdht/lib/crypto.js","./errors":"/node_modules/pgh/node_modules/hyperdht/lib/errors.js","./holepuncher":"/node_modules/pgh/node_modules/hyperdht/lib/holepuncher.js","./noise-wrap":"/node_modules/pgh/node_modules/hyperdht/lib/noise-wrap.js","./secure-payload":"/node_modules/pgh/node_modules/hyperdht/lib/secure-payload.js","@hyperswarm/secret-stream":"/node_modules/pgh/node_modules/@hyperswarm/secret-stream/index.js","b4a":"/node_modules/pgh/node_modules/b4a/index.js","blind-relay":"/node_modules/pgh/node_modules/blind-relay/index.js","bogon":"/node_modules/pgh/node_modules/bogon/index.js","events":"/node_modules/pgh/node_modules/bare-events/index.js","safety-catch":"/node_modules/pgh/node_modules/safety-catch/index.js"},"/node_modules/pgh/node_modules/hyperdht/lib/sleeper.js":{"#package":"/node_modules/pgh/node_modules/hyperdht/package.json"},"/node_modules/pgh/node_modules/hyperdht/lib/socket-pool.js":{"#package":"/node_modules/pgh/node_modules/hyperdht/package.json","b4a":"/node_modules/pgh/node_modules/b4a/index.js"},"/node_modules/pgh/node_modules/hyperdht/package.json":{},"/node_modules/pgh/node_modules/hyperschema/package.json":{},"/node_modules/pgh/node_modules/hyperschema/runtime.cjs":{"#package":"/node_modules/pgh/node_modules/hyperschema/package.json","compact-encoding":"/node_modules/pgh/node_modules/compact-encoding/index.js"},"/node_modules/pgh/node_modules/hyperswarm/index.js":{"#package":"/node_modules/pgh/node_modules/hyperswarm/package.json","./lib/connection-set":"/node_modules/pgh/node_modules/hyperswarm/lib/connection-set.js","./lib/peer-discovery":"/node_modules/pgh/node_modules/hyperswarm/lib/peer-discovery.js","./lib/peer-info":"/node_modules/pgh/node_modules/hyperswarm/lib/peer-info.js","./lib/retry-timer":"/node_modules/pgh/node_modules/hyperswarm/lib/retry-timer.js","b4a":"/node_modules/pgh/node_modules/b4a/index.js","events":"/node_modules/pgh/node_modules/bare-events/index.js","hyperdht":"/node_modules/pgh/node_modules/hyperdht/index.js","shuffled-priority-queue":"/node_modules/pgh/node_modules/shuffled-priority-queue/index.js","streamx":"/node_modules/pgh/node_modules/streamx/index.js","unslab":"/node_modules/pgh/node_modules/unslab/index.js"},"/node_modules/pgh/node_modules/hyperswarm/lib/bulk-timer.js":{"#package":"/node_modules/pgh/node_modules/hyperswarm/package.json"},"/node_modules/pgh/node_modules/hyperswarm/lib/connection-set.js":{"#package":"/node_modules/pgh/node_modules/hyperswarm/package.json","b4a":"/node_modules/pgh/node_modules/b4a/index.js"},"/node_modules/pgh/node_modules/hyperswarm/lib/peer-discovery.js":{"#package":"/node_modules/pgh/node_modules/hyperswarm/package.json","b4a":"/node_modules/pgh/node_modules/b4a/index.js","safety-catch":"/node_modules/pgh/node_modules/safety-catch/index.js"},"/node_modules/pgh/node_modules/hyperswarm/lib/peer-info.js":{"#package":"/node_modules/pgh/node_modules/hyperswarm/package.json","b4a":"/node_modules/pgh/node_modules/b4a/index.js","events":"/node_modules/pgh/node_modules/bare-events/index.js","unslab":"/node_modules/pgh/node_modules/unslab/index.js"},"/node_modules/pgh/node_modules/hyperswarm/lib/retry-timer.js":{"#package":"/node_modules/pgh/node_modules/hyperswarm/package.json","./bulk-timer":"/node_modules/pgh/node_modules/hyperswarm/lib/bulk-timer.js"},"/node_modules/pgh/node_modules/hyperswarm/package.json":{},"/node_modules/pgh/node_modules/index-encoder/index.js":{"#package":"/node_modules/pgh/node_modules/index-encoder/package.json","b4a":"/node_modules/pgh/node_modules/b4a/index.js"},"/node_modules/pgh/node_modules/index-encoder/package.json":{},"/node_modules/pgh/node_modules/is-options/index.js":{"#package":"/node_modules/pgh/node_modules/is-options/package.json","b4a":"/node_modules/pgh/node_modules/b4a/index.js"},"/node_modules/pgh/node_modules/is-options/package.json":{},"/node_modules/pgh/node_modules/kademlia-routing-table/index.js":{"#package":"/node_modules/pgh/node_modules/kademlia-routing-table/package.json","events":"/node_modules/pgh/node_modules/bare-events/index.js"},"/node_modules/pgh/node_modules/kademlia-routing-table/package.json":{},"/node_modules/pgh/node_modules/mutexify/index.js":{"#package":"/node_modules/pgh/node_modules/mutexify/package.json","queue-tick":"/node_modules/pgh/node_modules/queue-tick/process-next-tick.js"},"/node_modules/pgh/node_modules/mutexify/package.json":{},"/node_modules/pgh/node_modules/mutexify/promise.js":{"#package":"/node_modules/pgh/node_modules/mutexify/package.json",".":"/node_modules/pgh/node_modules/mutexify/index.js"},"/node_modules/pgh/node_modules/nanoassert/index.js":{"#package":"/node_modules/pgh/node_modules/nanoassert/package.json"},"/node_modules/pgh/node_modules/nanoassert/package.json":{},"/node_modules/pgh/node_modules/nat-sampler/index.js":{"#package":"/node_modules/pgh/node_modules/nat-sampler/package.json"},"/node_modules/pgh/node_modules/nat-sampler/package.json":{},"/node_modules/pgh/node_modules/noise-curve-ed/index.js":{"#package":"/node_modules/pgh/node_modules/noise-curve-ed/package.json","b4a":"/node_modules/pgh/node_modules/b4a/index.js","nanoassert":"/node_modules/pgh/node_modules/nanoassert/index.js","sodium-universal":"/node_modules/pgh/node_modules/sodium-universal/index.js"},"/node_modules/pgh/node_modules/noise-curve-ed/package.json":{},"/node_modules/pgh/node_modules/noise-handshake/cipher.js":{"#package":"/node_modules/pgh/node_modules/noise-handshake/package.json","b4a":"/node_modules/pgh/node_modules/b4a/index.js","sodium-universal":"/node_modules/pgh/node_modules/sodium-universal/index.js"},"/node_modules/pgh/node_modules/noise-handshake/dh.js":{"#package":"/node_modules/pgh/node_modules/noise-handshake/package.json","b4a":"/node_modules/pgh/node_modules/b4a/index.js","nanoassert":"/node_modules/pgh/node_modules/nanoassert/index.js","sodium-universal":"/node_modules/pgh/node_modules/sodium-universal/index.js"},"/node_modules/pgh/node_modules/noise-handshake/hkdf.js":{"#package":"/node_modules/pgh/node_modules/noise-handshake/package.json","./hmac":"/node_modules/pgh/node_modules/noise-handshake/hmac.js","b4a":"/node_modules/pgh/node_modules/b4a/index.js"},"/node_modules/pgh/node_modules/noise-handshake/hmac.js":{"#package":"/node_modules/pgh/node_modules/noise-handshake/package.json","b4a":"/node_modules/pgh/node_modules/b4a/index.js","sodium-universal":"/node_modules/pgh/node_modules/sodium-universal/index.js"},"/node_modules/pgh/node_modules/noise-handshake/noise.js":{"#package":"/node_modules/pgh/node_modules/noise-handshake/package.json","./hkdf":"/node_modules/pgh/node_modules/noise-handshake/hkdf.js","./symmetric-state":"/node_modules/pgh/node_modules/noise-handshake/symmetric-state.js","b4a":"/node_modules/pgh/node_modules/b4a/index.js","nanoassert":"/node_modules/pgh/node_modules/nanoassert/index.js"},"/node_modules/pgh/node_modules/noise-handshake/package.json":{},"/node_modules/pgh/node_modules/noise-handshake/symmetric-state.js":{"#package":"/node_modules/pgh/node_modules/noise-handshake/package.json","./cipher":"/node_modules/pgh/node_modules/noise-handshake/cipher.js","./dh":"/node_modules/pgh/node_modules/noise-handshake/dh.js","./hkdf":"/node_modules/pgh/node_modules/noise-handshake/hkdf.js","b4a":"/node_modules/pgh/node_modules/b4a/index.js","nanoassert":"/node_modules/pgh/node_modules/nanoassert/index.js","sodium-universal":"/node_modules/pgh/node_modules/sodium-universal/index.js"},"/node_modules/pgh/node_modules/protocol-buffers-encodings/index.js":{"#package":"/node_modules/pgh/node_modules/protocol-buffers-encodings/package.json","b4a":"/node_modules/pgh/node_modules/b4a/index.js","signed-varint":"/node_modules/pgh/node_modules/signed-varint/index.js","varint":"/node_modules/pgh/node_modules/varint/index.js"},"/node_modules/pgh/node_modules/protocol-buffers-encodings/package.json":{},"/node_modules/pgh/node_modules/protomux-wakeup/index.js":{"#package":"/node_modules/pgh/node_modules/protomux-wakeup/package.json","./spec/hyperschema":"/node_modules/pgh/node_modules/protomux-wakeup/spec/hyperschema/index.js","b4a":"/node_modules/pgh/node_modules/b4a/index.js","hypercore-crypto":"/node_modules/pgh/node_modules/hypercore-crypto/index.js","protomux":"/node_modules/pgh/node_modules/protomux/index.js"},"/node_modules/pgh/node_modules/protomux-wakeup/package.json":{},"/node_modules/pgh/node_modules/protomux-wakeup/spec/hyperschema/index.js":{"#package":"/node_modules/pgh/node_modules/protomux-wakeup/package.json","hyperschema/runtime":"/node_modules/pgh/node_modules/hyperschema/runtime.cjs"},"/node_modules/pgh/node_modules/protomux/index.js":{"#package":"/node_modules/pgh/node_modules/protomux/package.json","b4a":"/node_modules/pgh/node_modules/b4a/index.js","compact-encoding":"/node_modules/pgh/node_modules/compact-encoding/index.js","queue-tick":"/node_modules/pgh/node_modules/queue-tick/process-next-tick.js","safety-catch":"/node_modules/pgh/node_modules/safety-catch/index.js","unslab":"/node_modules/pgh/node_modules/unslab/index.js"},"/node_modules/pgh/node_modules/protomux/package.json":{},"/node_modules/pgh/node_modules/queue-tick/package.json":{},"/node_modules/pgh/node_modules/queue-tick/process-next-tick.js":{"#package":"/node_modules/pgh/node_modules/queue-tick/package.json","./queue-microtask":"/node_modules/pgh/node_modules/queue-tick/queue-microtask.js"},"/node_modules/pgh/node_modules/queue-tick/queue-microtask.js":{"#package":"/node_modules/pgh/node_modules/queue-tick/package.json"},"/node_modules/pgh/node_modules/quickbit-native/binding.js":{"#package":"/node_modules/pgh/node_modules/quickbit-native/package.json",".":"linked:libquickbit-native.2.4.8.so","require-addon":"/node_modules/pgh/node_modules/require-addon/lib/bare.js"},"/node_modules/pgh/node_modules/quickbit-native/index.js":{"#package":"/node_modules/pgh/node_modules/quickbit-native/package.json","./binding":"/node_modules/pgh/node_modules/quickbit-native/binding.js"},"/node_modules/pgh/node_modules/quickbit-native/package.json":{},"/node_modules/pgh/node_modules/quickbit-universal/fallback.js":{"#package":"/node_modules/pgh/node_modules/quickbit-universal/package.json","simdle-universal":"/node_modules/pgh/node_modules/simdle-universal/index.js"},"/node_modules/pgh/node_modules/quickbit-universal/index.js":{"#package":"/node_modules/pgh/node_modules/quickbit-universal/package.json","./fallback":"/node_modules/pgh/node_modules/quickbit-universal/fallback.js","quickbit-native":"/node_modules/pgh/node_modules/quickbit-native/index.js"},"/node_modules/pgh/node_modules/quickbit-universal/package.json":{},"/node_modules/pgh/node_modules/rache/index.js":{"#package":"/node_modules/pgh/node_modules/rache/package.json"},"/node_modules/pgh/node_modules/rache/package.json":{},"/node_modules/pgh/node_modules/random-array-iterator/index.js":{"#package":"/node_modules/pgh/node_modules/random-array-iterator/package.json"},"/node_modules/pgh/node_modules/random-array-iterator/package.json":{},"/node_modules/pgh/node_modules/ready-resource/index.js":{"#package":"/node_modules/pgh/node_modules/ready-resource/package.json","events":"/node_modules/pgh/node_modules/bare-events/index.js"},"/node_modules/pgh/node_modules/ready-resource/package.json":{},"/node_modules/pgh/node_modules/record-cache/index.js":{"#package":"/node_modules/pgh/node_modules/record-cache/package.json","b4a":"/node_modules/pgh/node_modules/b4a/index.js"},"/node_modules/pgh/node_modules/record-cache/package.json":{},"/node_modules/pgh/node_modules/refcounter/index.js":{"#package":"/node_modules/pgh/node_modules/refcounter/package.json"},"/node_modules/pgh/node_modules/refcounter/package.json":{},"/node_modules/pgh/node_modules/require-addon/lib/bare.js":{"#package":"/node_modules/pgh/node_modules/require-addon/package.json"},"/node_modules/pgh/node_modules/require-addon/package.json":{},"/node_modules/pgh/node_modules/resolve-reject-promise/index.js":{"#package":"/node_modules/pgh/node_modules/resolve-reject-promise/package.json"},"/node_modules/pgh/node_modules/resolve-reject-promise/package.json":{},"/node_modules/pgh/node_modules/resource-on-exit/index.js":{"#package":"/node_modules/pgh/node_modules/resource-on-exit/package.json"},"/node_modules/pgh/node_modules/resource-on-exit/package.json":{},"/node_modules/pgh/node_modules/rocksdb-native/binding.js":{"#package":"/node_modules/pgh/node_modules/rocksdb-native/package.json",".":"linked:librocksdb-native.3.15.2.so","require-addon":"/node_modules/pgh/node_modules/require-addon/lib/bare.js"},"/node_modules/pgh/node_modules/rocksdb-native/index.js":{"#package":"/node_modules/pgh/node_modules/rocksdb-native/package.json","./lib/column-family":"/node_modules/pgh/node_modules/rocksdb-native/lib/column-family.js","./lib/constants":"/node_modules/pgh/node_modules/rocksdb-native/lib/constants.js","./lib/filter-policy":"/node_modules/pgh/node_modules/rocksdb-native/lib/filter-policy.js","./lib/iterator":"/node_modules/pgh/node_modules/rocksdb-native/lib/iterator.js","./lib/snapshot":"/node_modules/pgh/node_modules/rocksdb-native/lib/snapshot.js","./lib/state":"/node_modules/pgh/node_modules/rocksdb-native/lib/state.js"},"/node_modules/pgh/node_modules/rocksdb-native/lib/batch.js":{"#package":"/node_modules/pgh/node_modules/rocksdb-native/package.json","../binding":"/node_modules/pgh/node_modules/rocksdb-native/binding.js","compact-encoding":"/node_modules/pgh/node_modules/compact-encoding/index.js"},"/node_modules/pgh/node_modules/rocksdb-native/lib/column-family.js":{"#package":"/node_modules/pgh/node_modules/rocksdb-native/package.json","../binding":"/node_modules/pgh/node_modules/rocksdb-native/binding.js","./constants":"/node_modules/pgh/node_modules/rocksdb-native/lib/constants.js","./filter-policy":"/node_modules/pgh/node_modules/rocksdb-native/lib/filter-policy.js"},"/node_modules/pgh/node_modules/rocksdb-native/lib/constants.js":{"#package":"/node_modules/pgh/node_modules/rocksdb-native/package.json"},"/node_modules/pgh/node_modules/rocksdb-native/lib/filter-policy.js":{"#package":"/node_modules/pgh/node_modules/rocksdb-native/package.json"},"/node_modules/pgh/node_modules/rocksdb-native/lib/iterator.js":{"#package":"/node_modules/pgh/node_modules/rocksdb-native/package.json","../binding":"/node_modules/pgh/node_modules/rocksdb-native/binding.js","compact-encoding":"/node_modules/pgh/node_modules/compact-encoding/index.js","streamx":"/node_modules/pgh/node_modules/streamx/index.js"},"/node_modules/pgh/node_modules/rocksdb-native/lib/snapshot.js":{"#package":"/node_modules/pgh/node_modules/rocksdb-native/package.json","../binding":"/node_modules/pgh/node_modules/rocksdb-native/binding.js"},"/node_modules/pgh/node_modules/rocksdb-native/lib/state.js":{"#package":"/node_modules/pgh/node_modules/rocksdb-native/package.json","../binding":"/node_modules/pgh/node_modules/rocksdb-native/binding.js","./batch":"/node_modules/pgh/node_modules/rocksdb-native/lib/batch.js","./column-family":"/node_modules/pgh/node_modules/rocksdb-native/lib/column-family.js","./constants":"/node_modules/pgh/node_modules/rocksdb-native/lib/constants.js","compact-encoding":"/node_modules/pgh/node_modules/compact-encoding/index.js","ready-resource":"/node_modules/pgh/node_modules/ready-resource/index.js","refcounter":"/node_modules/pgh/node_modules/refcounter/index.js","resolve-reject-promise":"/node_modules/pgh/node_modules/resolve-reject-promise/index.js","signal-promise":"/node_modules/pgh/node_modules/signal-promise/index.js"},"/node_modules/pgh/node_modules/rocksdb-native/package.json":{},"/node_modules/pgh/node_modules/safety-catch/index.js":{"#package":"/node_modules/pgh/node_modules/safety-catch/package.json"},"/node_modules/pgh/node_modules/safety-catch/package.json":{},"/node_modules/pgh/node_modules/scope-lock/index.js":{"#package":"/node_modules/pgh/node_modules/scope-lock/package.json"},"/node_modules/pgh/node_modules/scope-lock/package.json":{},"/node_modules/pgh/node_modules/shuffled-priority-queue/index.js":{"#package":"/node_modules/pgh/node_modules/shuffled-priority-queue/package.json","unordered-set":"/node_modules/pgh/node_modules/unordered-set/index.js"},"/node_modules/pgh/node_modules/shuffled-priority-queue/package.json":{},"/node_modules/pgh/node_modules/signal-promise/index.js":{"#package":"/node_modules/pgh/node_modules/signal-promise/package.json"},"/node_modules/pgh/node_modules/signal-promise/package.json":{},"/node_modules/pgh/node_modules/signed-varint/index.js":{"#package":"/node_modules/pgh/node_modules/signed-varint/package.json","varint":"/node_modules/pgh/node_modules/varint/index.js"},"/node_modules/pgh/node_modules/signed-varint/package.json":{},"/node_modules/pgh/node_modules/simdle-native/binding.js":{"#package":"/node_modules/pgh/node_modules/simdle-native/package.json",".":"linked:libsimdle-native.1.3.9.so","require-addon":"/node_modules/pgh/node_modules/require-addon/lib/bare.js"},"/node_modules/pgh/node_modules/simdle-native/index.js":{"#package":"/node_modules/pgh/node_modules/simdle-native/package.json","./binding":"/node_modules/pgh/node_modules/simdle-native/binding.js","b4a":"/node_modules/pgh/node_modules/b4a/index.js"},"/node_modules/pgh/node_modules/simdle-native/package.json":{},"/node_modules/pgh/node_modules/simdle-universal/fallback.js":{"#package":"/node_modules/pgh/node_modules/simdle-universal/package.json","./scalar":"/node_modules/pgh/node_modules/simdle-universal/scalar.js","b4a":"/node_modules/pgh/node_modules/b4a/index.js"},"/node_modules/pgh/node_modules/simdle-universal/index.js":{"#package":"/node_modules/pgh/node_modules/simdle-universal/package.json","./fallback":"/node_modules/pgh/node_modules/simdle-universal/fallback.js","simdle-native":"/node_modules/pgh/node_modules/simdle-native/index.js"},"/node_modules/pgh/node_modules/simdle-universal/package.json":{},"/node_modules/pgh/node_modules/simdle-universal/scalar.js":{"#package":"/node_modules/pgh/node_modules/simdle-universal/package.json"},"/node_modules/pgh/node_modules/sodium-native/binding.js":{"#package":"/node_modules/pgh/node_modules/sodium-native/package.json",".":"linked:libsodium-native.5.1.0.so","require-addon":"/node_modules/pgh/node_modules/require-addon/lib/bare.js"},"/node_modules/pgh/node_modules/sodium-native/index.js":{"#package":"/node_modules/pgh/node_modules/sodium-native/package.json","./binding":"/node_modules/pgh/node_modules/sodium-native/binding.js","assert":"/node_modules/pgh/node_modules/bare-assert/index.js","which-runtime":"/node_modules/pgh/node_modules/which-runtime/index.js"},"/node_modules/pgh/node_modules/sodium-native/package.json":{},"/node_modules/pgh/node_modules/sodium-secretstream/index.js":{"#package":"/node_modules/pgh/node_modules/sodium-secretstream/package.json","b4a":"/node_modules/pgh/node_modules/b4a/index.js","sodium-universal":"/node_modules/pgh/node_modules/sodium-universal/index.js"},"/node_modules/pgh/node_modules/sodium-secretstream/package.json":{},"/node_modules/pgh/node_modules/sodium-universal/index.js":{"#package":"/node_modules/pgh/node_modules/sodium-universal/package.json","sodium-native":"/node_modules/pgh/node_modules/sodium-native/index.js"},"/node_modules/pgh/node_modules/sodium-universal/package.json":{},"/node_modules/pgh/node_modules/streamx/index.js":{"#package":"/node_modules/pgh/node_modules/streamx/package.json","./lib/errors.js":"/node_modules/pgh/node_modules/streamx/lib/errors.js","events-universal":"/node_modules/pgh/node_modules/events-universal/bare.js","fast-fifo":"/node_modules/pgh/node_modules/fast-fifo/index.js","text-decoder":"/node_modules/pgh/node_modules/text-decoder/index.js"},"/node_modules/pgh/node_modules/streamx/lib/errors.js":{"#package":"/node_modules/pgh/node_modules/streamx/package.json"},"/node_modules/pgh/node_modules/streamx/package.json":{},"/node_modules/pgh/node_modules/sub-encoder/index.js":{"#package":"/node_modules/pgh/node_modules/sub-encoder/package.json","b4a":"/node_modules/pgh/node_modules/b4a/index.js","codecs":"/node_modules/pgh/node_modules/codecs/index.js"},"/node_modules/pgh/node_modules/sub-encoder/package.json":{},"/node_modules/pgh/node_modules/teex/index.js":{"#package":"/node_modules/pgh/node_modules/teex/package.json","streamx":"/node_modules/pgh/node_modules/streamx/index.js"},"/node_modules/pgh/node_modules/teex/package.json":{},"/node_modules/pgh/node_modules/text-decoder/index.js":{"#package":"/node_modules/pgh/node_modules/text-decoder/package.json","./lib/pass-through-decoder":"/node_modules/pgh/node_modules/text-decoder/lib/pass-through-decoder.js","./lib/utf8-decoder":"/node_modules/pgh/node_modules/text-decoder/lib/utf8-decoder.js"},"/node_modules/pgh/node_modules/text-decoder/lib/pass-through-decoder.js":{"#package":"/node_modules/pgh/node_modules/text-decoder/package.json","b4a":"/node_modules/pgh/node_modules/b4a/index.js"},"/node_modules/pgh/node_modules/text-decoder/lib/utf8-decoder.js":{"#package":"/node_modules/pgh/node_modules/text-decoder/package.json","b4a":"/node_modules/pgh/node_modules/b4a/index.js"},"/node_modules/pgh/node_modules/text-decoder/package.json":{},"/node_modules/pgh/node_modules/time-ordered-set/index.js":{"#package":"/node_modules/pgh/node_modules/time-ordered-set/package.json"},"/node_modules/pgh/node_modules/time-ordered-set/package.json":{},"/node_modules/pgh/node_modules/timeout-refresh/browser.js":{"#package":"/node_modules/pgh/node_modules/timeout-refresh/package.json"},"/node_modules/pgh/node_modules/timeout-refresh/index.js":{"#package":"/node_modules/pgh/node_modules/timeout-refresh/package.json","./browser":"/node_modules/pgh/node_modules/timeout-refresh/browser.js","./node":"/node_modules/pgh/node_modules/timeout-refresh/node.js"},"/node_modules/pgh/node_modules/timeout-refresh/node.js":{"#package":"/node_modules/pgh/node_modules/timeout-refresh/package.json"},"/node_modules/pgh/node_modules/timeout-refresh/package.json":{},"/node_modules/pgh/node_modules/tiny-buffer-map/index.js":{"#package":"/node_modules/pgh/node_modules/tiny-buffer-map/package.json","b4a":"/node_modules/pgh/node_modules/b4a/index.js"},"/node_modules/pgh/node_modules/tiny-buffer-map/package.json":{},"/node_modules/pgh/node_modules/udx-native/binding.js":{"#package":"/node_modules/pgh/node_modules/udx-native/package.json",".":"linked:libudx-native.1.20.6.so","require-addon":"/node_modules/pgh/node_modules/require-addon/lib/bare.js"},"/node_modules/pgh/node_modules/udx-native/lib/ip.js":{"#package":"/node_modules/pgh/node_modules/udx-native/package.json"},"/node_modules/pgh/node_modules/udx-native/lib/network-interfaces.js":{"#package":"/node_modules/pgh/node_modules/udx-native/package.json","../binding":"/node_modules/pgh/node_modules/udx-native/binding.js","b4a":"/node_modules/pgh/node_modules/b4a/index.js","events":"/node_modules/pgh/node_modules/bare-events/index.js"},"/node_modules/pgh/node_modules/udx-native/lib/socket.js":{"#package":"/node_modules/pgh/node_modules/udx-native/package.json","../binding":"/node_modules/pgh/node_modules/udx-native/binding.js","./ip":"/node_modules/pgh/node_modules/udx-native/lib/ip.js","b4a":"/node_modules/pgh/node_modules/b4a/index.js","events":"/node_modules/pgh/node_modules/bare-events/index.js"},"/node_modules/pgh/node_modules/udx-native/lib/stream.js":{"#package":"/node_modules/pgh/node_modules/udx-native/package.json","../binding":"/node_modules/pgh/node_modules/udx-native/binding.js","./ip":"/node_modules/pgh/node_modules/udx-native/lib/ip.js","b4a":"/node_modules/pgh/node_modules/b4a/index.js","streamx":"/node_modules/pgh/node_modules/streamx/index.js"},"/node_modules/pgh/node_modules/udx-native/lib/udx.js":{"#package":"/node_modules/pgh/node_modules/udx-native/package.json","../binding":"/node_modules/pgh/node_modules/udx-native/binding.js","./ip":"/node_modules/pgh/node_modules/udx-native/lib/ip.js","./network-interfaces":"/node_modules/pgh/node_modules/udx-native/lib/network-interfaces.js","./socket":"/node_modules/pgh/node_modules/udx-native/lib/socket.js","./stream":"/node_modules/pgh/node_modules/udx-native/lib/stream.js","b4a":"/node_modules/pgh/node_modules/b4a/index.js"},"/node_modules/pgh/node_modules/udx-native/package.json":{},"/node_modules/pgh/node_modules/unordered-set/index.js":{"#package":"/node_modules/pgh/node_modules/unordered-set/package.json"},"/node_modules/pgh/node_modules/unordered-set/package.json":{},"/node_modules/pgh/node_modules/unslab/index.js":{"#package":"/node_modules/pgh/node_modules/unslab/package.json","b4a":"/node_modules/pgh/node_modules/b4a/index.js"},"/node_modules/pgh/node_modules/unslab/package.json":{},"/node_modules/pgh/node_modules/varint/decode.js":{"#package":"/node_modules/pgh/node_modules/varint/package.json"},"/node_modules/pgh/node_modules/varint/encode.js":{"#package":"/node_modules/pgh/node_modules/varint/package.json"},"/node_modules/pgh/node_modules/varint/index.js":{"#package":"/node_modules/pgh/node_modules/varint/package.json","./decode.js":"/node_modules/pgh/node_modules/varint/decode.js","./encode.js":"/node_modules/pgh/node_modules/varint/encode.js","./length.js":"/node_modules/pgh/node_modules/varint/length.js"},"/node_modules/pgh/node_modules/varint/length.js":{"#package":"/node_modules/pgh/node_modules/varint/package.json"},"/node_modules/pgh/node_modules/varint/package.json":{},"/node_modules/pgh/node_modules/which-runtime/index.js":{"#package":"/node_modules/pgh/node_modules/which-runtime/package.json"},"/node_modules/pgh/node_modules/which-runtime/package.json":{},"/node_modules/pgh/node_modules/xache/index.js":{"#package":"/node_modules/pgh/node_modules/xache/package.json"},"/node_modules/pgh/node_modules/xache/package.json":{},"/node_modules/pgh/node_modules/z32/index.js":{"#package":"/node_modules/pgh/node_modules/z32/package.json","b4a":"/node_modules/pgh/node_modules/b4a/index.js"},"/node_modules/pgh/node_modules/z32/package.json":{},"/node_modules/pgh/package.json":{},"/node_modules/protomux/index.js":{"#package":"/node_modules/protomux/package.json","b4a":"/node_modules/b4a/index.js","compact-encoding":"/node_modules/compact-encoding/index.js","queue-tick":"/node_modules/queue-tick/process-next-tick.js","safety-catch":"/node_modules/safety-catch/index.js","unslab":"/node_modules/unslab/index.js"},"/node_modules/protomux/package.json":{},"/node_modules/queue-tick/package.json":{},"/node_modules/queue-tick/process-next-tick.js":{"#package":"/node_modules/queue-tick/package.json","./queue-microtask":"/node_modules/queue-tick/queue-microtask.js"},"/node_modules/queue-tick/queue-microtask.js":{"#package":"/node_modules/queue-tick/package.json"},"/node_modules/record-cache/index.js":{"#package":"/node_modules/record-cache/package.json","b4a":"/node_modules/b4a/index.js"},"/node_modules/record-cache/package.json":{},"/node_modules/require-addon/lib/bare.js":{"#package":"/node_modules/require-addon/package.json"},"/node_modules/require-addon/package.json":{},"/node_modules/safety-catch/index.js":{"#package":"/node_modules/safety-catch/package.json"},"/node_modules/safety-catch/package.json":{},"/node_modules/shuffled-priority-queue/index.js":{"#package":"/node_modules/shuffled-priority-queue/package.json","unordered-set":"/node_modules/unordered-set/index.js"},"/node_modules/shuffled-priority-queue/package.json":{},"/node_modules/signal-promise/index.js":{"#package":"/node_modules/signal-promise/package.json"},"/node_modules/signal-promise/package.json":{},"/node_modules/sodium-native/binding.js":{"#package":"/node_modules/sodium-native/package.json",".":"linked:libsodium-native.5.1.0.so","require-addon":"/node_modules/require-addon/lib/bare.js"},"/node_modules/sodium-native/index.js":{"#package":"/node_modules/sodium-native/package.json","./binding":"/node_modules/sodium-native/binding.js","assert":"/node_modules/bare-assert/index.js","which-runtime":"/node_modules/which-runtime/index.js"},"/node_modules/sodium-native/package.json":{},"/node_modules/sodium-secretstream/index.js":{"#package":"/node_modules/sodium-secretstream/package.json","b4a":"/node_modules/b4a/index.js","sodium-universal":"/node_modules/sodium-universal/index.js"},"/node_modules/sodium-secretstream/package.json":{},"/node_modules/sodium-universal/index.js":{"#package":"/node_modules/sodium-universal/package.json","sodium-native":"/node_modules/sodium-native/index.js"},"/node_modules/sodium-universal/package.json":{},"/node_modules/streamx/index.js":{"#package":"/node_modules/streamx/package.json","./lib/errors.js":"/node_modules/streamx/lib/errors.js","events-universal":"/node_modules/events-universal/bare.js","fast-fifo":"/node_modules/fast-fifo/index.js","text-decoder":"/node_modules/text-decoder/index.js"},"/node_modules/streamx/lib/errors.js":{"#package":"/node_modules/streamx/package.json"},"/node_modules/streamx/package.json":{},"/node_modules/teex/index.js":{"#package":"/node_modules/teex/package.json","streamx":"/node_modules/streamx/index.js"},"/node_modules/teex/package.json":{},"/node_modules/text-decoder/index.js":{"#package":"/node_modules/text-decoder/package.json","./lib/pass-through-decoder":"/node_modules/text-decoder/lib/pass-through-decoder.js","./lib/utf8-decoder":"/node_modules/text-decoder/lib/utf8-decoder.js"},"/node_modules/text-decoder/lib/pass-through-decoder.js":{"#package":"/node_modules/text-decoder/package.json","b4a":"/node_modules/b4a/index.js"},"/node_modules/text-decoder/lib/utf8-decoder.js":{"#package":"/node_modules/text-decoder/package.json","b4a":"/node_modules/b4a/index.js"},"/node_modules/text-decoder/package.json":{},"/node_modules/time-ordered-set/index.js":{"#package":"/node_modules/time-ordered-set/package.json"},"/node_modules/time-ordered-set/package.json":{},"/node_modules/timeout-refresh/browser.js":{"#package":"/node_modules/timeout-refresh/package.json"},"/node_modules/timeout-refresh/index.js":{"#package":"/node_modules/timeout-refresh/package.json","./browser":"/node_modules/timeout-refresh/browser.js","./node":"/node_modules/timeout-refresh/node.js"},"/node_modules/timeout-refresh/node.js":{"#package":"/node_modules/timeout-refresh/package.json"},"/node_modules/timeout-refresh/package.json":{},"/node_modules/udx-native/binding.js":{"#package":"/node_modules/udx-native/package.json",".":"linked:libudx-native.1.21.1.so","require-addon":"/node_modules/require-addon/lib/bare.js"},"/node_modules/udx-native/lib/ip.js":{"#package":"/node_modules/udx-native/package.json"},"/node_modules/udx-native/lib/network-interfaces.js":{"#package":"/node_modules/udx-native/package.json","../binding":"/node_modules/udx-native/binding.js","b4a":"/node_modules/b4a/index.js","events":"/node_modules/bare-events/index.js"},"/node_modules/udx-native/lib/socket.js":{"#package":"/node_modules/udx-native/package.json","../binding":"/node_modules/udx-native/binding.js","./ip":"/node_modules/udx-native/lib/ip.js","b4a":"/node_modules/b4a/index.js","events":"/node_modules/bare-events/index.js"},"/node_modules/udx-native/lib/stream.js":{"#package":"/node_modules/udx-native/package.json","../binding":"/node_modules/udx-native/binding.js","./ip":"/node_modules/udx-native/lib/ip.js","b4a":"/node_modules/b4a/index.js","streamx":"/node_modules/streamx/index.js"},"/node_modules/udx-native/lib/udx.js":{"#package":"/node_modules/udx-native/package.json","../binding":"/node_modules/udx-native/binding.js","./ip":"/node_modules/udx-native/lib/ip.js","./network-interfaces":"/node_modules/udx-native/lib/network-interfaces.js","./socket":"/node_modules/udx-native/lib/socket.js","./stream":"/node_modules/udx-native/lib/stream.js","b4a":"/node_modules/b4a/index.js"},"/node_modules/udx-native/package.json":{},"/node_modules/unordered-set/index.js":{"#package":"/node_modules/unordered-set/package.json"},"/node_modules/unordered-set/package.json":{},"/node_modules/unslab/index.js":{"#package":"/node_modules/unslab/package.json","b4a":"/node_modules/b4a/index.js"},"/node_modules/unslab/package.json":{},"/node_modules/which-runtime/index.js":{"#package":"/node_modules/which-runtime/package.json"},"/node_modules/which-runtime/package.json":{},"/node_modules/xache/index.js":{"#package":"/node_modules/xache/package.json"},"/node_modules/xache/package.json":{},"/node_modules/z32/index.js":{"#package":"/node_modules/z32/package.json","b4a":"/node_modules/b4a/index.js"},"/node_modules/z32/package.json":{},"/package.json":{}},"addons":["linked:libbare-abort.2.0.13.so","linked:libbare-buffer.3.7.1.so","linked:libbare-crypto.1.13.4.so","linked:libbare-crypto.1.15.3.so","linked:libbare-dns.1.0.5.so","linked:libbare-dns.2.1.4.so","linked:libbare-dns.2.2.0.so","linked:libbare-fs.4.7.2.so","linked:libbare-fs.4.8.1.so","linked:libbare-hrtime.2.1.1.so","linked:libbare-hrtime.2.1.2.so","linked:libbare-inspect.3.1.10.so","linked:libbare-inspect.3.1.4.so","linked:libbare-os.3.9.1.so","linked:libbare-os.3.9.3.so","linked:libbare-path.3.1.2.so","linked:libbare-pipe.3.3.8.so","linked:libbare-pipe.4.1.5.so","linked:libbare-pipe.4.2.0.so","linked:libbare-pipe.4.3.1.so","linked:libbare-signals.4.2.0.so","linked:libbare-signals.5.0.0.so","linked:libbare-stdio.1.0.2.so","linked:libbare-stdio.1.0.3.so","linked:libbare-structured-clone.1.6.0.so","linked:libbare-subprocess.6.1.0.so","linked:libbare-tcp.1.9.1.so","linked:libbare-tcp.2.2.12.so","linked:libbare-tcp.2.6.1.so","linked:libbare-tty.5.1.0.so","linked:libbare-tty.5.2.1.so","linked:libbare-type.1.1.0.so","linked:libbare-type.1.1.1.so","linked:libbare-url.2.4.2.so","linked:libbare-url.2.4.5.so","linked:libbare-url.2.5.4.so","linked:libbare-zlib.1.4.0.so","linked:libfs-native-extensions.1.5.0.so","linked:libfs-native-extensions.1.5.1.so","linked:libquickbit-native.2.4.8.so","linked:librocksdb-native.3.15.2.so","linked:librocksdb-native.3.17.4.so","linked:libsimdle-native.1.3.9.so","linked:libsodium-native.5.1.0.so","linked:libudx-native.1.19.2.so","linked:libudx-native.1.20.6.so","linked:libudx-native.1.21.1.so"],"assets":[],"files":{"/lib/host/choose-bundle.js":{"offset":0,"length":5108,"mode":420},"/lib/host/ota.js":{"offset":5108,"length":32008,"mode":420},"/lib/host/shell-handoff.js":{"offset":37116,"length":5802,"mode":420},"/lib/host/utf8.js":{"offset":42918,"length":4296,"mode":420},"/lib/node.js":{"offset":47214,"length":19535,"mode":420},"/lib/version.js":{"offset":66749,"length":164,"mode":420},"/node_modules/@hyperswarm/secret-stream/index.js":{"offset":66913,"length":16722,"mode":420},"/node_modules/@hyperswarm/secret-stream/lib/bridge.js":{"offset":83635,"length":1273,"mode":420},"/node_modules/@hyperswarm/secret-stream/lib/handshake.js":{"offset":84908,"length":1950,"mode":420},"/node_modules/@hyperswarm/secret-stream/package.json":{"offset":86858,"length":1132,"mode":420},"/node_modules/adaptive-timeout/index.js":{"offset":87990,"length":2424,"mode":420},"/node_modules/adaptive-timeout/package.json":{"offset":90414,"length":972,"mode":420},"/node_modules/anproto/index.js":{"offset":91386,"length":410,"mode":420},"/node_modules/anproto/lib/an.js":{"offset":91796,"length":4516,"mode":420},"/node_modules/anproto/lib/challenges.js":{"offset":96312,"length":2757,"mode":420},"/node_modules/anproto/node_modules/b4a/index.js":{"offset":99069,"length":4054,"mode":420},"/node_modules/anproto/node_modules/b4a/package.json":{"offset":103123,"length":1140,"mode":420},"/node_modules/anproto/node_modules/bare-ansi-escapes/index.js":{"offset":104263,"length":2255,"mode":420},"/node_modules/anproto/node_modules/bare-ansi-escapes/package.json":{"offset":106518,"length":1178,"mode":420},"/node_modules/anproto/node_modules/bare-assert/index.js":{"offset":107696,"length":2192,"mode":420},"/node_modules/anproto/node_modules/bare-assert/package.json":{"offset":109888,"length":835,"mode":420},"/node_modules/anproto/node_modules/bare-inspect/binding.js":{"offset":110723,"length":33,"mode":420},"/node_modules/anproto/node_modules/bare-inspect/index.js":{"offset":110756,"length":23856,"mode":420},"/node_modules/anproto/node_modules/bare-inspect/package.json":{"offset":134612,"length":1041,"mode":420},"/node_modules/anproto/node_modules/bare-type/binding.js":{"offset":135653,"length":33,"mode":420},"/node_modules/anproto/node_modules/bare-type/index.js":{"offset":135686,"length":4566,"mode":420},"/node_modules/anproto/node_modules/bare-type/package.json":{"offset":140252,"length":938,"mode":420},"/node_modules/anproto/node_modules/compact-encoding/endian.js":{"offset":141190,"length":105,"mode":420},"/node_modules/anproto/node_modules/compact-encoding/index.js":{"offset":141295,"length":24454,"mode":420},"/node_modules/anproto/node_modules/compact-encoding/lexint.js":{"offset":165749,"length":2851,"mode":420},"/node_modules/anproto/node_modules/compact-encoding/package.json":{"offset":168600,"length":786,"mode":420},"/node_modules/anproto/node_modules/compact-encoding/raw.js":{"offset":169386,"length":4093,"mode":420},"/node_modules/anproto/node_modules/hypercore-crypto/index.js":{"offset":173479,"length":5040,"mode":420},"/node_modules/anproto/node_modules/hypercore-crypto/package.json":{"offset":178519,"length":771,"mode":420},"/node_modules/anproto/node_modules/require-addon/lib/bare.js":{"offset":179290,"length":45,"mode":420},"/node_modules/anproto/node_modules/require-addon/package.json":{"offset":179335,"length":1449,"mode":420},"/node_modules/anproto/node_modules/sodium-native/binding.js":{"offset":180784,"length":89,"mode":420},"/node_modules/anproto/node_modules/sodium-native/index.js":{"offset":180873,"length":107894,"mode":420},"/node_modules/anproto/node_modules/sodium-native/package.json":{"offset":288767,"length":1478,"mode":420},"/node_modules/anproto/node_modules/sodium-universal/index.js":{"offset":290245,"length":42,"mode":420},"/node_modules/anproto/node_modules/sodium-universal/package.json":{"offset":290287,"length":1217,"mode":420},"/node_modules/anproto/node_modules/which-runtime/index.js":{"offset":291504,"length":1231,"mode":420},"/node_modules/anproto/node_modules/which-runtime/package.json":{"offset":292735,"length":602,"mode":420},"/node_modules/anproto/package.json":{"offset":293337,"length":461,"mode":420},"/node_modules/b4a/index.js":{"offset":293798,"length":4054,"mode":420},"/node_modules/b4a/package.json":{"offset":297852,"length":1245,"mode":420},"/node_modules/bare-abort/binding.js":{"offset":299097,"length":33,"mode":420},"/node_modules/bare-abort/index.js":{"offset":299130,"length":69,"mode":420},"/node_modules/bare-abort/package.json":{"offset":299199,"length":886,"mode":420},"/node_modules/bare-ansi-escapes/index.js":{"offset":300085,"length":2255,"mode":420},"/node_modules/bare-ansi-escapes/package.json":{"offset":302340,"length":1178,"mode":420},"/node_modules/bare-assert/index.js":{"offset":303518,"length":2192,"mode":420},"/node_modules/bare-assert/package.json":{"offset":305710,"length":835,"mode":420},"/node_modules/bare-dns/binding.js":{"offset":306545,"length":33,"mode":420},"/node_modules/bare-dns/index.js":{"offset":306578,"length":3228,"mode":420},"/node_modules/bare-dns/lib/constants.js":{"offset":309806,"length":851,"mode":420},"/node_modules/bare-dns/lib/errors.js":{"offset":310657,"length":1242,"mode":420},"/node_modules/bare-dns/lib/promises.js":{"offset":311899,"length":4824,"mode":420},"/node_modules/bare-dns/lib/records.js":{"offset":316723,"length":3754,"mode":420},"/node_modules/bare-dns/lib/resolver.js":{"offset":320477,"length":5504,"mode":420},"/node_modules/bare-dns/package.json":{"offset":325981,"length":1578,"mode":420},"/node_modules/bare-env/index.js":{"offset":327559,"length":1017,"mode":420},"/node_modules/bare-env/package.json":{"offset":328576,"length":944,"mode":420},"/node_modules/bare-events/index.js":{"offset":329520,"length":8885,"mode":420},"/node_modules/bare-events/lib/errors.js":{"offset":338405,"length":634,"mode":420},"/node_modules/bare-events/package.json":{"offset":339039,"length":1504,"mode":420},"/node_modules/bare-fs/binding.js":{"offset":340543,"length":33,"mode":420},"/node_modules/bare-fs/index.js":{"offset":340576,"length":59172,"mode":420},"/node_modules/bare-fs/lib/constants.js":{"offset":399748,"length":1494,"mode":420},"/node_modules/bare-fs/lib/errors.js":{"offset":401242,"length":1166,"mode":420},"/node_modules/bare-fs/package.json":{"offset":402408,"length":1654,"mode":420},"/node_modules/bare-fs/promises.js":{"offset":404062,"length":2401,"mode":420},"/node_modules/bare-hrtime/binding.js":{"offset":406463,"length":33,"mode":420},"/node_modules/bare-hrtime/index.js":{"offset":406496,"length":319,"mode":420},"/node_modules/bare-hrtime/package.json":{"offset":406815,"length":1020,"mode":420},"/node_modules/bare-http-parser/index.js":{"offset":407835,"length":25418,"mode":420},"/node_modules/bare-http-parser/lib/errors.js":{"offset":433253,"length":1106,"mode":420},"/node_modules/bare-http-parser/package.json":{"offset":434359,"length":907,"mode":420},"/node_modules/bare-http1/index.js":{"offset":435266,"length":3587,"mode":420},"/node_modules/bare-http1/lib/agent.js":{"offset":438853,"length":13220,"mode":420},"/node_modules/bare-http1/lib/client-connection.js":{"offset":452073,"length":11368,"mode":420},"/node_modules/bare-http1/lib/client-request.js":{"offset":463441,"length":9140,"mode":420},"/node_modules/bare-http1/lib/connection.js":{"offset":472581,"length":4588,"mode":420},"/node_modules/bare-http1/lib/constants.js":{"offset":477169,"length":2764,"mode":420},"/node_modules/bare-http1/lib/deadline.js":{"offset":479933,"length":2112,"mode":420},"/node_modules/bare-http1/lib/errors.js":{"offset":482045,"length":2308,"mode":420},"/node_modules/bare-http1/lib/headers.js":{"offset":484353,"length":4980,"mode":420},"/node_modules/bare-http1/lib/incoming-message.js":{"offset":489333,"length":2406,"mode":420},"/node_modules/bare-http1/lib/message.js":{"offset":491739,"length":1036,"mode":420},"/node_modules/bare-http1/lib/outgoing-message.js":{"offset":492775,"length":10463,"mode":420},"/node_modules/bare-http1/lib/server-connection.js":{"offset":503238,"length":17081,"mode":420},"/node_modules/bare-http1/lib/server-response.js":{"offset":520319,"length":7370,"mode":420},"/node_modules/bare-http1/lib/server.js":{"offset":527689,"length":3937,"mode":420},"/node_modules/bare-http1/lib/socket.js":{"offset":531626,"length":376,"mode":420},"/node_modules/bare-http1/lib/validate.js":{"offset":532002,"length":4313,"mode":420},"/node_modules/bare-http1/package.json":{"offset":536315,"length":1539,"mode":420},"/node_modules/bare-inspect/binding.js":{"offset":537854,"length":33,"mode":420},"/node_modules/bare-inspect/index.js":{"offset":537887,"length":28811,"mode":420},"/node_modules/bare-inspect/package.json":{"offset":566698,"length":1157,"mode":420},"/node_modules/bare-os/binding.js":{"offset":567855,"length":33,"mode":420},"/node_modules/bare-os/index.js":{"offset":567888,"length":5216,"mode":420},"/node_modules/bare-os/lib/constants.js":{"offset":573104,"length":143,"mode":420},"/node_modules/bare-os/lib/errors.js":{"offset":573247,"length":453,"mode":420},"/node_modules/bare-os/package.json":{"offset":573700,"length":1257,"mode":420},"/node_modules/bare-path/binding.js":{"offset":574957,"length":33,"mode":420},"/node_modules/bare-path/index.js":{"offset":574990,"length":287,"mode":420},"/node_modules/bare-path/lib/constants.js":{"offset":575277,"length":247,"mode":420},"/node_modules/bare-path/lib/posix.js":{"offset":575524,"length":6399,"mode":420},"/node_modules/bare-path/lib/shared.js":{"offset":581923,"length":2148,"mode":420},"/node_modules/bare-path/lib/win32.js":{"offset":584071,"length":13987,"mode":420},"/node_modules/bare-path/package.json":{"offset":598058,"length":1106,"mode":420},"/node_modules/bare-pipe/binding.js":{"offset":599164,"length":33,"mode":420},"/node_modules/bare-pipe/index.js":{"offset":599197,"length":19312,"mode":420},"/node_modules/bare-pipe/lib/constants.js":{"offset":618509,"length":426,"mode":420},"/node_modules/bare-pipe/lib/errors.js":{"offset":618935,"length":1059,"mode":420},"/node_modules/bare-pipe/package.json":{"offset":619994,"length":1586,"mode":420},"/node_modules/bare-posix/package.json":{"offset":621580,"length":1091,"mode":420},"/node_modules/bare-posix/unsupported.js":{"offset":622671,"length":723,"mode":420},"/node_modules/bare-process/index.js":{"offset":623394,"length":3189,"mode":420},"/node_modules/bare-process/package.json":{"offset":626583,"length":1283,"mode":420},"/node_modules/bare-signals/binding.js":{"offset":627866,"length":33,"mode":420},"/node_modules/bare-signals/index.js":{"offset":627899,"length":1412,"mode":420},"/node_modules/bare-signals/lib/emitter.js":{"offset":629311,"length":1260,"mode":420},"/node_modules/bare-signals/lib/errors.js":{"offset":630571,"length":479,"mode":420},"/node_modules/bare-signals/package.json":{"offset":631050,"length":1227,"mode":420},"/node_modules/bare-stdio/binding.js":{"offset":632277,"length":33,"mode":420},"/node_modules/bare-stdio/index.js":{"offset":632310,"length":1525,"mode":420},"/node_modules/bare-stdio/package.json":{"offset":633835,"length":1232,"mode":420},"/node_modules/bare-stream/index.js":{"offset":635067,"length":10904,"mode":420},"/node_modules/bare-stream/package.json":{"offset":645971,"length":1676,"mode":420},"/node_modules/bare-stream/web.js":{"offset":647647,"length":13980,"mode":420},"/node_modules/bare-tcp/binding.js":{"offset":661627,"length":33,"mode":420},"/node_modules/bare-tcp/index.js":{"offset":661660,"length":24428,"mode":420},"/node_modules/bare-tcp/lib/constants.js":{"offset":686088,"length":285,"mode":420},"/node_modules/bare-tcp/lib/errors.js":{"offset":686373,"length":1067,"mode":420},"/node_modules/bare-tcp/lib/ip.js":{"offset":687440,"length":2204,"mode":420},"/node_modules/bare-tcp/package.json":{"offset":689644,"length":1551,"mode":420},"/node_modules/bare-tty/binding.js":{"offset":691195,"length":33,"mode":420},"/node_modules/bare-tty/index.js":{"offset":691228,"length":6989,"mode":420},"/node_modules/bare-tty/lib/constants.js":{"offset":698217,"length":459,"mode":420},"/node_modules/bare-tty/lib/errors.js":{"offset":698676,"length":550,"mode":420},"/node_modules/bare-tty/package.json":{"offset":699226,"length":1404,"mode":420},"/node_modules/bare-type/binding.js":{"offset":700630,"length":33,"mode":420},"/node_modules/bare-type/index.js":{"offset":700663,"length":4719,"mode":420},"/node_modules/bare-type/package.json":{"offset":705382,"length":1053,"mode":420},"/node_modules/bare-url/binding.js":{"offset":706435,"length":33,"mode":420},"/node_modules/bare-url/index.js":{"offset":706468,"length":17970,"mode":420},"/node_modules/bare-url/lib/errors.js":{"offset":724438,"length":778,"mode":420},"/node_modules/bare-url/lib/url-search-params.js":{"offset":725216,"length":7104,"mode":420},"/node_modules/bare-url/package.json":{"offset":732320,"length":1219,"mode":420},"/node_modules/bits-to-bytes/index.js":{"offset":733539,"length":3083,"mode":420},"/node_modules/bits-to-bytes/package.json":{"offset":736622,"length":708,"mode":420},"/node_modules/blind-relay/index.js":{"offset":737330,"length":13411,"mode":420},"/node_modules/blind-relay/lib/errors.js":{"offset":750741,"length":1036,"mode":420},"/node_modules/blind-relay/package.json":{"offset":751777,"length":1231,"mode":420},"/node_modules/bogon/index.js":{"offset":753008,"length":4004,"mode":420},"/node_modules/bogon/package.json":{"offset":757012,"length":656,"mode":420},"/node_modules/compact-encoding-bitfield/index.js":{"offset":757668,"length":1967,"mode":420},"/node_modules/compact-encoding-bitfield/package.json":{"offset":759635,"length":1109,"mode":420},"/node_modules/compact-encoding/endian.js":{"offset":760744,"length":103,"mode":420},"/node_modules/compact-encoding/index.js":{"offset":760847,"length":27021,"mode":420},"/node_modules/compact-encoding/lexint.js":{"offset":787868,"length":2837,"mode":420},"/node_modules/compact-encoding/package.json":{"offset":790705,"length":968,"mode":420},"/node_modules/compact-encoding/raw.js":{"offset":791673,"length":3977,"mode":420},"/node_modules/dht-rpc/index.js":{"offset":795650,"length":29079,"mode":420},"/node_modules/dht-rpc/lib/commands.js":{"offset":824729,"length":107,"mode":420},"/node_modules/dht-rpc/lib/errors.js":{"offset":824836,"length":719,"mode":420},"/node_modules/dht-rpc/lib/health.js":{"offset":825555,"length":2998,"mode":420},"/node_modules/dht-rpc/lib/io.js":{"offset":828553,"length":16605,"mode":420},"/node_modules/dht-rpc/lib/peer.js":{"offset":845158,"length":583,"mode":420},"/node_modules/dht-rpc/lib/query.js":{"offset":845741,"length":9899,"mode":420},"/node_modules/dht-rpc/lib/session.js":{"offset":855640,"length":1103,"mode":420},"/node_modules/dht-rpc/package.json":{"offset":856743,"length":1396,"mode":420},"/node_modules/events-universal/bare.js":{"offset":858139,"length":40,"mode":420},"/node_modules/events-universal/package.json":{"offset":858179,"length":908,"mode":420},"/node_modules/fast-fifo/fixed-size.js":{"offset":859087,"length":875,"mode":420},"/node_modules/fast-fifo/index.js":{"offset":859962,"length":972,"mode":420},"/node_modules/fast-fifo/package.json":{"offset":860934,"length":682,"mode":420},"/node_modules/favela-peer/lib/attestation.js":{"offset":861616,"length":1201,"mode":420},"/node_modules/favela-peer/lib/avatars.js":{"offset":862817,"length":6740,"mode":420},"/node_modules/favela-peer/lib/browsers.js":{"offset":869557,"length":12229,"mode":420},"/node_modules/favela-peer/lib/device.js":{"offset":881786,"length":14728,"mode":420},"/node_modules/favela-peer/lib/feed-base.js":{"offset":896514,"length":16309,"mode":420},"/node_modules/favela-peer/lib/feed.js":{"offset":912823,"length":15538,"mode":420},"/node_modules/favela-peer/lib/files.js":{"offset":928361,"length":9935,"mode":420},"/node_modules/favela-peer/lib/graph.js":{"offset":938296,"length":6456,"mode":420},"/node_modules/favela-peer/lib/log.js":{"offset":944752,"length":8568,"mode":420},"/node_modules/favela-peer/lib/mentions.js":{"offset":953320,"length":4689,"mode":420},"/node_modules/favela-peer/lib/mint.js":{"offset":958009,"length":6375,"mode":420},"/node_modules/favela-peer/lib/mirror.js":{"offset":964384,"length":11465,"mode":420},"/node_modules/favela-peer/lib/name.js":{"offset":975849,"length":4245,"mode":420},"/node_modules/favela-peer/lib/namespace.js":{"offset":980094,"length":44597,"mode":420},"/node_modules/favela-peer/lib/node.js":{"offset":1024691,"length":38540,"mode":420},"/node_modules/favela-peer/lib/pairing.js":{"offset":1063231,"length":8250,"mode":420},"/node_modules/favela-peer/lib/peer.js":{"offset":1071481,"length":46881,"mode":420},"/node_modules/favela-peer/lib/record-store.js":{"offset":1118362,"length":12399,"mode":420},"/node_modules/favela-peer/lib/room.js":{"offset":1130761,"length":25537,"mode":420},"/node_modules/favela-peer/lib/rpc.js":{"offset":1156298,"length":38277,"mode":420},"/node_modules/favela-peer/lib/transcode.js":{"offset":1194575,"length":6797,"mode":420},"/node_modules/favela-peer/lib/transport.js":{"offset":1201372,"length":5029,"mode":420},"/node_modules/favela-peer/node_modules/@hyperswarm/secret-stream/index.js":{"offset":1206401,"length":16722,"mode":420},"/node_modules/favela-peer/node_modules/@hyperswarm/secret-stream/lib/bridge.js":{"offset":1223123,"length":1273,"mode":420},"/node_modules/favela-peer/node_modules/@hyperswarm/secret-stream/lib/handshake.js":{"offset":1224396,"length":1950,"mode":420},"/node_modules/favela-peer/node_modules/@hyperswarm/secret-stream/package.json":{"offset":1226346,"length":1132,"mode":420},"/node_modules/favela-peer/node_modules/adaptive-timeout/index.js":{"offset":1227478,"length":2424,"mode":420},"/node_modules/favela-peer/node_modules/adaptive-timeout/package.json":{"offset":1229902,"length":972,"mode":420},"/node_modules/favela-peer/node_modules/anproto/index.js":{"offset":1230874,"length":410,"mode":420},"/node_modules/favela-peer/node_modules/anproto/lib/an.js":{"offset":1231284,"length":4516,"mode":420},"/node_modules/favela-peer/node_modules/anproto/lib/challenges.js":{"offset":1235800,"length":2757,"mode":420},"/node_modules/favela-peer/node_modules/anproto/node_modules/b4a/index.js":{"offset":1238557,"length":4054,"mode":420},"/node_modules/favela-peer/node_modules/anproto/node_modules/b4a/package.json":{"offset":1242611,"length":1140,"mode":420},"/node_modules/favela-peer/node_modules/anproto/node_modules/bare-ansi-escapes/index.js":{"offset":1243751,"length":2255,"mode":420},"/node_modules/favela-peer/node_modules/anproto/node_modules/bare-ansi-escapes/package.json":{"offset":1246006,"length":1178,"mode":420},"/node_modules/favela-peer/node_modules/anproto/node_modules/bare-assert/index.js":{"offset":1247184,"length":2192,"mode":420},"/node_modules/favela-peer/node_modules/anproto/node_modules/bare-assert/package.json":{"offset":1249376,"length":835,"mode":420},"/node_modules/favela-peer/node_modules/anproto/node_modules/bare-inspect/binding.js":{"offset":1250211,"length":33,"mode":420},"/node_modules/favela-peer/node_modules/anproto/node_modules/bare-inspect/index.js":{"offset":1250244,"length":23856,"mode":420},"/node_modules/favela-peer/node_modules/anproto/node_modules/bare-inspect/package.json":{"offset":1274100,"length":1041,"mode":420},"/node_modules/favela-peer/node_modules/anproto/node_modules/bare-type/binding.js":{"offset":1275141,"length":33,"mode":420},"/node_modules/favela-peer/node_modules/anproto/node_modules/bare-type/index.js":{"offset":1275174,"length":4566,"mode":420},"/node_modules/favela-peer/node_modules/anproto/node_modules/bare-type/package.json":{"offset":1279740,"length":938,"mode":420},"/node_modules/favela-peer/node_modules/anproto/node_modules/compact-encoding/endian.js":{"offset":1280678,"length":105,"mode":420},"/node_modules/favela-peer/node_modules/anproto/node_modules/compact-encoding/index.js":{"offset":1280783,"length":24454,"mode":420},"/node_modules/favela-peer/node_modules/anproto/node_modules/compact-encoding/lexint.js":{"offset":1305237,"length":2851,"mode":420},"/node_modules/favela-peer/node_modules/anproto/node_modules/compact-encoding/package.json":{"offset":1308088,"length":786,"mode":420},"/node_modules/favela-peer/node_modules/anproto/node_modules/compact-encoding/raw.js":{"offset":1308874,"length":4093,"mode":420},"/node_modules/favela-peer/node_modules/anproto/node_modules/hypercore-crypto/index.js":{"offset":1312967,"length":5040,"mode":420},"/node_modules/favela-peer/node_modules/anproto/node_modules/hypercore-crypto/package.json":{"offset":1318007,"length":771,"mode":420},"/node_modules/favela-peer/node_modules/anproto/node_modules/require-addon/lib/bare.js":{"offset":1318778,"length":45,"mode":420},"/node_modules/favela-peer/node_modules/anproto/node_modules/require-addon/package.json":{"offset":1318823,"length":1449,"mode":420},"/node_modules/favela-peer/node_modules/anproto/node_modules/sodium-native/binding.js":{"offset":1320272,"length":89,"mode":420},"/node_modules/favela-peer/node_modules/anproto/node_modules/sodium-native/index.js":{"offset":1320361,"length":107894,"mode":420},"/node_modules/favela-peer/node_modules/anproto/node_modules/sodium-native/package.json":{"offset":1428255,"length":1478,"mode":420},"/node_modules/favela-peer/node_modules/anproto/node_modules/sodium-universal/index.js":{"offset":1429733,"length":42,"mode":420},"/node_modules/favela-peer/node_modules/anproto/node_modules/sodium-universal/package.json":{"offset":1429775,"length":1217,"mode":420},"/node_modules/favela-peer/node_modules/anproto/node_modules/which-runtime/index.js":{"offset":1430992,"length":1231,"mode":420},"/node_modules/favela-peer/node_modules/anproto/node_modules/which-runtime/package.json":{"offset":1432223,"length":602,"mode":420},"/node_modules/favela-peer/node_modules/anproto/package.json":{"offset":1432825,"length":461,"mode":420},"/node_modules/favela-peer/node_modules/autobee-encryption/index.js":{"offset":1433286,"length":7351,"mode":420},"/node_modules/favela-peer/node_modules/autobee-encryption/node_modules/compact-encoding/endian.js":{"offset":1440637,"length":105,"mode":420},"/node_modules/favela-peer/node_modules/autobee-encryption/node_modules/compact-encoding/index.js":{"offset":1440742,"length":24454,"mode":420},"/node_modules/favela-peer/node_modules/autobee-encryption/node_modules/compact-encoding/lexint.js":{"offset":1465196,"length":2851,"mode":420},"/node_modules/favela-peer/node_modules/autobee-encryption/node_modules/compact-encoding/package.json":{"offset":1468047,"length":786,"mode":420},"/node_modules/favela-peer/node_modules/autobee-encryption/node_modules/compact-encoding/raw.js":{"offset":1468833,"length":4093,"mode":420},"/node_modules/favela-peer/node_modules/autobee-encryption/package.json":{"offset":1472926,"length":1064,"mode":420},"/node_modules/favela-peer/node_modules/autobee-wakeup/index.js":{"offset":1473990,"length":3089,"mode":420},"/node_modules/favela-peer/node_modules/autobee-wakeup/package.json":{"offset":1477079,"length":1077,"mode":420},"/node_modules/favela-peer/node_modules/autobee/encoding/legacy.js":{"offset":1478156,"length":7704,"mode":420},"/node_modules/favela-peer/node_modules/autobee/encoding/spec/autobee/index.js":{"offset":1485860,"length":35796,"mode":420},"/node_modules/favela-peer/node_modules/autobee/index.js":{"offset":1521656,"length":43745,"mode":420},"/node_modules/favela-peer/node_modules/autobee/lib/apply-calls.js":{"offset":1565401,"length":1448,"mode":420},"/node_modules/favela-peer/node_modules/autobee/lib/apply-view.js":{"offset":1566849,"length":580,"mode":420},"/node_modules/favela-peer/node_modules/autobee/lib/asserts.js":{"offset":1567429,"length":523,"mode":420},"/node_modules/favela-peer/node_modules/autobee/lib/boot.js":{"offset":1567952,"length":4542,"mode":420},"/node_modules/favela-peer/node_modules/autobee/lib/constants.js":{"offset":1572494,"length":277,"mode":420},"/node_modules/favela-peer/node_modules/autobee/lib/encoding.js":{"offset":1572771,"length":6508,"mode":420},"/node_modules/favela-peer/node_modules/autobee/lib/fast-forward.js":{"offset":1579279,"length":9985,"mode":420},"/node_modules/favela-peer/node_modules/autobee/lib/migrations.js":{"offset":1589264,"length":13464,"mode":420},"/node_modules/favela-peer/node_modules/autobee/lib/promotions.js":{"offset":1602728,"length":5709,"mode":420},"/node_modules/favela-peer/node_modules/autobee/lib/system.js":{"offset":1608437,"length":12039,"mode":420},"/node_modules/favela-peer/node_modules/autobee/lib/topo.js":{"offset":1620476,"length":9753,"mode":420},"/node_modules/favela-peer/node_modules/autobee/lib/triggers.js":{"offset":1630229,"length":1249,"mode":420},"/node_modules/favela-peer/node_modules/autobee/lib/trusted.js":{"offset":1631478,"length":1020,"mode":420},"/node_modules/favela-peer/node_modules/autobee/lib/updates.js":{"offset":1632498,"length":815,"mode":420},"/node_modules/favela-peer/node_modules/autobee/lib/witness.js":{"offset":1633313,"length":1857,"mode":420},"/node_modules/favela-peer/node_modules/autobee/lib/writers.js":{"offset":1635170,"length":19204,"mode":420},"/node_modules/favela-peer/node_modules/autobee/package.json":{"offset":1654374,"length":1293,"mode":420},"/node_modules/favela-peer/node_modules/b4a/index.js":{"offset":1655667,"length":4054,"mode":420},"/node_modules/favela-peer/node_modules/b4a/package.json":{"offset":1659721,"length":1245,"mode":420},"/node_modules/favela-peer/node_modules/bare-abort/binding.js":{"offset":1660966,"length":33,"mode":420},"/node_modules/favela-peer/node_modules/bare-abort/index.js":{"offset":1660999,"length":69,"mode":420},"/node_modules/favela-peer/node_modules/bare-abort/package.json":{"offset":1661068,"length":886,"mode":420},"/node_modules/favela-peer/node_modules/bare-ansi-escapes/index.js":{"offset":1661954,"length":2255,"mode":420},"/node_modules/favela-peer/node_modules/bare-ansi-escapes/package.json":{"offset":1664209,"length":1178,"mode":420},"/node_modules/favela-peer/node_modules/bare-assert/index.js":{"offset":1665387,"length":2192,"mode":420},"/node_modules/favela-peer/node_modules/bare-assert/package.json":{"offset":1667579,"length":835,"mode":420},"/node_modules/favela-peer/node_modules/bare-buffer/binding.js":{"offset":1668414,"length":33,"mode":420},"/node_modules/favela-peer/node_modules/bare-buffer/index.js":{"offset":1668447,"length":38743,"mode":420},"/node_modules/favela-peer/node_modules/bare-buffer/lib/ascii.js":{"offset":1707190,"length":1275,"mode":420},"/node_modules/favela-peer/node_modules/bare-buffer/lib/base64.js":{"offset":1708465,"length":741,"mode":420},"/node_modules/favela-peer/node_modules/bare-buffer/lib/base64url.js":{"offset":1709206,"length":744,"mode":420},"/node_modules/favela-peer/node_modules/bare-buffer/lib/checked.js":{"offset":1709950,"length":735,"mode":420},"/node_modules/favela-peer/node_modules/bare-buffer/lib/constants.js":{"offset":1710685,"length":74,"mode":420},"/node_modules/favela-peer/node_modules/bare-buffer/lib/hex.js":{"offset":1710759,"length":602,"mode":420},"/node_modules/favela-peer/node_modules/bare-buffer/lib/latin1.js":{"offset":1711361,"length":514,"mode":420},"/node_modules/favela-peer/node_modules/bare-buffer/lib/utf16le.js":{"offset":1711875,"length":608,"mode":420},"/node_modules/favela-peer/node_modules/bare-buffer/lib/utf8.js":{"offset":1712483,"length":684,"mode":420},"/node_modules/favela-peer/node_modules/bare-buffer/package.json":{"offset":1713167,"length":1352,"mode":420},"/node_modules/favela-peer/node_modules/bare-dns/binding.js":{"offset":1714519,"length":33,"mode":420},"/node_modules/favela-peer/node_modules/bare-dns/index.js":{"offset":1714552,"length":3228,"mode":420},"/node_modules/favela-peer/node_modules/bare-dns/lib/constants.js":{"offset":1717780,"length":851,"mode":420},"/node_modules/favela-peer/node_modules/bare-dns/lib/errors.js":{"offset":1718631,"length":1242,"mode":420},"/node_modules/favela-peer/node_modules/bare-dns/lib/promises.js":{"offset":1719873,"length":4824,"mode":420},"/node_modules/favela-peer/node_modules/bare-dns/lib/records.js":{"offset":1724697,"length":3754,"mode":420},"/node_modules/favela-peer/node_modules/bare-dns/lib/resolver.js":{"offset":1728451,"length":5504,"mode":420},"/node_modules/favela-peer/node_modules/bare-dns/package.json":{"offset":1733955,"length":1578,"mode":420},"/node_modules/favela-peer/node_modules/bare-env/index.js":{"offset":1735533,"length":1017,"mode":420},"/node_modules/favela-peer/node_modules/bare-env/package.json":{"offset":1736550,"length":944,"mode":420},"/node_modules/favela-peer/node_modules/bare-events/index.js":{"offset":1737494,"length":8885,"mode":420},"/node_modules/favela-peer/node_modules/bare-events/lib/errors.js":{"offset":1746379,"length":634,"mode":420},"/node_modules/favela-peer/node_modules/bare-events/package.json":{"offset":1747013,"length":1504,"mode":420},"/node_modules/favela-peer/node_modules/bare-fs/binding.js":{"offset":1748517,"length":33,"mode":420},"/node_modules/favela-peer/node_modules/bare-fs/index.js":{"offset":1748550,"length":59172,"mode":420},"/node_modules/favela-peer/node_modules/bare-fs/lib/constants.js":{"offset":1807722,"length":1494,"mode":420},"/node_modules/favela-peer/node_modules/bare-fs/lib/errors.js":{"offset":1809216,"length":1166,"mode":420},"/node_modules/favela-peer/node_modules/bare-fs/package.json":{"offset":1810382,"length":1654,"mode":420},"/node_modules/favela-peer/node_modules/bare-fs/promises.js":{"offset":1812036,"length":2401,"mode":420},"/node_modules/favela-peer/node_modules/bare-hrtime/binding.js":{"offset":1814437,"length":33,"mode":420},"/node_modules/favela-peer/node_modules/bare-hrtime/index.js":{"offset":1814470,"length":319,"mode":420},"/node_modules/favela-peer/node_modules/bare-hrtime/package.json":{"offset":1814789,"length":1020,"mode":420},"/node_modules/favela-peer/node_modules/bare-inspect/binding.js":{"offset":1815809,"length":33,"mode":420},"/node_modules/favela-peer/node_modules/bare-inspect/index.js":{"offset":1815842,"length":28811,"mode":420},"/node_modules/favela-peer/node_modules/bare-inspect/package.json":{"offset":1844653,"length":1157,"mode":420},"/node_modules/favela-peer/node_modules/bare-net/index.js":{"offset":1845810,"length":9496,"mode":420},"/node_modules/favela-peer/node_modules/bare-net/lib/constants.js":{"offset":1855306,"length":125,"mode":420},"/node_modules/favela-peer/node_modules/bare-net/lib/errors.js":{"offset":1855431,"length":377,"mode":420},"/node_modules/favela-peer/node_modules/bare-net/package.json":{"offset":1855808,"length":1245,"mode":420},"/node_modules/favela-peer/node_modules/bare-os/binding.js":{"offset":1857053,"length":33,"mode":420},"/node_modules/favela-peer/node_modules/bare-os/index.js":{"offset":1857086,"length":5216,"mode":420},"/node_modules/favela-peer/node_modules/bare-os/lib/constants.js":{"offset":1862302,"length":143,"mode":420},"/node_modules/favela-peer/node_modules/bare-os/lib/errors.js":{"offset":1862445,"length":453,"mode":420},"/node_modules/favela-peer/node_modules/bare-os/package.json":{"offset":1862898,"length":1257,"mode":420},"/node_modules/favela-peer/node_modules/bare-path/binding.js":{"offset":1864155,"length":33,"mode":420},"/node_modules/favela-peer/node_modules/bare-path/index.js":{"offset":1864188,"length":287,"mode":420},"/node_modules/favela-peer/node_modules/bare-path/lib/constants.js":{"offset":1864475,"length":247,"mode":420},"/node_modules/favela-peer/node_modules/bare-path/lib/posix.js":{"offset":1864722,"length":6399,"mode":420},"/node_modules/favela-peer/node_modules/bare-path/lib/shared.js":{"offset":1871121,"length":2148,"mode":420},"/node_modules/favela-peer/node_modules/bare-path/lib/win32.js":{"offset":1873269,"length":13987,"mode":420},"/node_modules/favela-peer/node_modules/bare-path/package.json":{"offset":1887256,"length":1106,"mode":420},"/node_modules/favela-peer/node_modules/bare-pipe/binding.js":{"offset":1888362,"length":33,"mode":420},"/node_modules/favela-peer/node_modules/bare-pipe/index.js":{"offset":1888395,"length":19312,"mode":420},"/node_modules/favela-peer/node_modules/bare-pipe/lib/constants.js":{"offset":1907707,"length":426,"mode":420},"/node_modules/favela-peer/node_modules/bare-pipe/lib/errors.js":{"offset":1908133,"length":1059,"mode":420},"/node_modules/favela-peer/node_modules/bare-pipe/package.json":{"offset":1909192,"length":1586,"mode":420},"/node_modules/favela-peer/node_modules/bare-posix/package.json":{"offset":1910778,"length":1091,"mode":420},"/node_modules/favela-peer/node_modules/bare-posix/unsupported.js":{"offset":1911869,"length":723,"mode":420},"/node_modules/favela-peer/node_modules/bare-process/index.js":{"offset":1912592,"length":3189,"mode":420},"/node_modules/favela-peer/node_modules/bare-process/package.json":{"offset":1915781,"length":1283,"mode":420},"/node_modules/favela-peer/node_modules/bare-signals/binding.js":{"offset":1917064,"length":33,"mode":420},"/node_modules/favela-peer/node_modules/bare-signals/index.js":{"offset":1917097,"length":1412,"mode":420},"/node_modules/favela-peer/node_modules/bare-signals/lib/emitter.js":{"offset":1918509,"length":1260,"mode":420},"/node_modules/favela-peer/node_modules/bare-signals/lib/errors.js":{"offset":1919769,"length":479,"mode":420},"/node_modules/favela-peer/node_modules/bare-signals/package.json":{"offset":1920248,"length":1227,"mode":420},"/node_modules/favela-peer/node_modules/bare-stdio/binding.js":{"offset":1921475,"length":33,"mode":420},"/node_modules/favela-peer/node_modules/bare-stdio/index.js":{"offset":1921508,"length":1525,"mode":420},"/node_modules/favela-peer/node_modules/bare-stdio/package.json":{"offset":1923033,"length":1232,"mode":420},"/node_modules/favela-peer/node_modules/bare-stream/index.js":{"offset":1924265,"length":10904,"mode":420},"/node_modules/favela-peer/node_modules/bare-stream/package.json":{"offset":1935169,"length":1676,"mode":420},"/node_modules/favela-peer/node_modules/bare-stream/web.js":{"offset":1936845,"length":13980,"mode":420},"/node_modules/favela-peer/node_modules/bare-structured-clone/binding.js":{"offset":1950825,"length":33,"mode":420},"/node_modules/favela-peer/node_modules/bare-structured-clone/index.js":{"offset":1950858,"length":35381,"mode":420},"/node_modules/favela-peer/node_modules/bare-structured-clone/lib/constants.js":{"offset":1986239,"length":1161,"mode":420},"/node_modules/favela-peer/node_modules/bare-structured-clone/lib/errors.js":{"offset":1987400,"length":1074,"mode":420},"/node_modules/favela-peer/node_modules/bare-structured-clone/package.json":{"offset":1988474,"length":1584,"mode":420},"/node_modules/favela-peer/node_modules/bare-subprocess/binding.js":{"offset":1990058,"length":33,"mode":420},"/node_modules/favela-peer/node_modules/bare-subprocess/index.js":{"offset":1990091,"length":10093,"mode":420},"/node_modules/favela-peer/node_modules/bare-subprocess/lib/channel.js":{"offset":2000184,"length":2303,"mode":420},"/node_modules/favela-peer/node_modules/bare-subprocess/lib/constants.js":{"offset":2002487,"length":69,"mode":420},"/node_modules/favela-peer/node_modules/bare-subprocess/lib/errors.js":{"offset":2002556,"length":1338,"mode":420},"/node_modules/favela-peer/node_modules/bare-subprocess/lib/framers/advanced.js":{"offset":2003894,"length":2363,"mode":420},"/node_modules/favela-peer/node_modules/bare-subprocess/lib/framers/json.js":{"offset":2006257,"length":2045,"mode":420},"/node_modules/favela-peer/node_modules/bare-subprocess/package.json":{"offset":2008302,"length":1883,"mode":420},"/node_modules/favela-peer/node_modules/bare-tcp/binding.js":{"offset":2010185,"length":33,"mode":420},"/node_modules/favela-peer/node_modules/bare-tcp/index.js":{"offset":2010218,"length":24428,"mode":420},"/node_modules/favela-peer/node_modules/bare-tcp/lib/constants.js":{"offset":2034646,"length":285,"mode":420},"/node_modules/favela-peer/node_modules/bare-tcp/lib/errors.js":{"offset":2034931,"length":1067,"mode":420},"/node_modules/favela-peer/node_modules/bare-tcp/lib/ip.js":{"offset":2035998,"length":2204,"mode":420},"/node_modules/favela-peer/node_modules/bare-tcp/package.json":{"offset":2038202,"length":1551,"mode":420},"/node_modules/favela-peer/node_modules/bare-tty/binding.js":{"offset":2039753,"length":33,"mode":420},"/node_modules/favela-peer/node_modules/bare-tty/index.js":{"offset":2039786,"length":6989,"mode":420},"/node_modules/favela-peer/node_modules/bare-tty/lib/constants.js":{"offset":2046775,"length":459,"mode":420},"/node_modules/favela-peer/node_modules/bare-tty/lib/errors.js":{"offset":2047234,"length":550,"mode":420},"/node_modules/favela-peer/node_modules/bare-tty/package.json":{"offset":2047784,"length":1404,"mode":420},"/node_modules/favela-peer/node_modules/bare-type/binding.js":{"offset":2049188,"length":33,"mode":420},"/node_modules/favela-peer/node_modules/bare-type/index.js":{"offset":2049221,"length":4719,"mode":420},"/node_modules/favela-peer/node_modules/bare-type/package.json":{"offset":2053940,"length":1053,"mode":420},"/node_modules/favela-peer/node_modules/bare-url/binding.js":{"offset":2054993,"length":33,"mode":420},"/node_modules/favela-peer/node_modules/bare-url/index.js":{"offset":2055026,"length":17970,"mode":420},"/node_modules/favela-peer/node_modules/bare-url/lib/errors.js":{"offset":2072996,"length":778,"mode":420},"/node_modules/favela-peer/node_modules/bare-url/lib/url-search-params.js":{"offset":2073774,"length":7104,"mode":420},"/node_modules/favela-peer/node_modules/bare-url/package.json":{"offset":2080878,"length":1219,"mode":420},"/node_modules/favela-peer/node_modules/big-sparse-array/index.js":{"offset":2082097,"length":2471,"mode":420},"/node_modules/favela-peer/node_modules/big-sparse-array/package.json":{"offset":2084568,"length":636,"mode":420},"/node_modules/favela-peer/node_modules/bip39-mnemonic/index.js":{"offset":2085204,"length":4554,"mode":420},"/node_modules/favela-peer/node_modules/bip39-mnemonic/package.json":{"offset":2089758,"length":789,"mode":420},"/node_modules/favela-peer/node_modules/bip39-mnemonic/wordlist/chinese_simplified.json":{"offset":2090547,"length":12289,"mode":420},"/node_modules/favela-peer/node_modules/bip39-mnemonic/wordlist/chinese_traditional.json":{"offset":2102836,"length":12289,"mode":420},"/node_modules/favela-peer/node_modules/bip39-mnemonic/wordlist/czech.json":{"offset":2115125,"length":19042,"mode":420},"/node_modules/favela-peer/node_modules/bip39-mnemonic/wordlist/english.json":{"offset":2134167,"length":17213,"mode":420},"/node_modules/favela-peer/node_modules/bip39-mnemonic/wordlist/french.json":{"offset":2151380,"length":20874,"mode":420},"/node_modules/favela-peer/node_modules/bip39-mnemonic/wordlist/index.js":{"offset":2172254,"length":1406,"mode":420},"/node_modules/favela-peer/node_modules/bip39-mnemonic/wordlist/italian.json":{"offset":2173660,"length":20130,"mode":420},"/node_modules/favela-peer/node_modules/bip39-mnemonic/wordlist/japanese.json":{"offset":2193790,"length":30520,"mode":420},"/node_modules/favela-peer/node_modules/bip39-mnemonic/wordlist/korean.json":{"offset":2224310,"length":41929,"mode":420},"/node_modules/favela-peer/node_modules/bip39-mnemonic/wordlist/lookup.json":{"offset":2266239,"length":592224,"mode":420},"/node_modules/favela-peer/node_modules/bip39-mnemonic/wordlist/portuguese.json":{"offset":2858463,"length":19768,"mode":420},"/node_modules/favela-peer/node_modules/bip39-mnemonic/wordlist/russian.json":{"offset":2878231,"length":30635,"mode":420},"/node_modules/favela-peer/node_modules/bip39-mnemonic/wordlist/spanish.json":{"offset":2908866,"length":18093,"mode":420},"/node_modules/favela-peer/node_modules/bip39-mnemonic/wordlist/turkish.json":{"offset":2926959,"length":19421,"mode":420},"/node_modules/favela-peer/node_modules/bits-to-bytes/index.js":{"offset":2946380,"length":3083,"mode":420},"/node_modules/favela-peer/node_modules/bits-to-bytes/package.json":{"offset":2949463,"length":708,"mode":420},"/node_modules/favela-peer/node_modules/blind-pairing-core/index.js":{"offset":2950171,"length":10961,"mode":420},"/node_modules/favela-peer/node_modules/blind-pairing-core/lib/errors.js":{"offset":2961132,"length":725,"mode":420},"/node_modules/favela-peer/node_modules/blind-pairing-core/lib/messages.js":{"offset":2961857,"length":6120,"mode":420},"/node_modules/favela-peer/node_modules/blind-pairing-core/package.json":{"offset":2967977,"length":1062,"mode":420},"/node_modules/favela-peer/node_modules/blind-pairing/index.js":{"offset":2969039,"length":16910,"mode":420},"/node_modules/favela-peer/node_modules/blind-pairing/package.json":{"offset":2985949,"length":1000,"mode":420},"/node_modules/favela-peer/node_modules/blind-relay/index.js":{"offset":2986949,"length":13411,"mode":420},"/node_modules/favela-peer/node_modules/blind-relay/lib/errors.js":{"offset":3000360,"length":1036,"mode":420},"/node_modules/favela-peer/node_modules/blind-relay/package.json":{"offset":3001396,"length":1231,"mode":420},"/node_modules/favela-peer/node_modules/bogon/index.js":{"offset":3002627,"length":4004,"mode":420},"/node_modules/favela-peer/node_modules/bogon/package.json":{"offset":3006631,"length":656,"mode":420},"/node_modules/favela-peer/node_modules/compact-encoding-bitfield/index.js":{"offset":3007287,"length":1967,"mode":420},"/node_modules/favela-peer/node_modules/compact-encoding-bitfield/package.json":{"offset":3009254,"length":1109,"mode":420},"/node_modules/favela-peer/node_modules/compact-encoding/endian.js":{"offset":3010363,"length":103,"mode":420},"/node_modules/favela-peer/node_modules/compact-encoding/index.js":{"offset":3010466,"length":27021,"mode":420},"/node_modules/favela-peer/node_modules/compact-encoding/lexint.js":{"offset":3037487,"length":2837,"mode":420},"/node_modules/favela-peer/node_modules/compact-encoding/package.json":{"offset":3040324,"length":968,"mode":420},"/node_modules/favela-peer/node_modules/compact-encoding/raw.js":{"offset":3041292,"length":3977,"mode":420},"/node_modules/favela-peer/node_modules/core-coupler/index.js":{"offset":3045269,"length":2243,"mode":420},"/node_modules/favela-peer/node_modules/core-coupler/package.json":{"offset":3047512,"length":599,"mode":420},"/node_modules/favela-peer/node_modules/corestore/index.js":{"offset":3048111,"length":19755,"mode":420},"/node_modules/favela-peer/node_modules/corestore/lib/audit.js":{"offset":3067866,"length":475,"mode":420},"/node_modules/favela-peer/node_modules/corestore/lib/notify.js":{"offset":3068341,"length":630,"mode":420},"/node_modules/favela-peer/node_modules/corestore/package.json":{"offset":3068971,"length":1350,"mode":420},"/node_modules/favela-peer/node_modules/debounceify/index.js":{"offset":3070321,"length":585,"mode":420},"/node_modules/favela-peer/node_modules/debounceify/package.json":{"offset":3070906,"length":567,"mode":420},"/node_modules/favela-peer/node_modules/device-file/index.js":{"offset":3071473,"length":6394,"mode":420},"/node_modules/favela-peer/node_modules/device-file/package.json":{"offset":3077867,"length":1190,"mode":420},"/node_modules/favela-peer/node_modules/dht-rpc/index.js":{"offset":3079057,"length":29079,"mode":420},"/node_modules/favela-peer/node_modules/dht-rpc/lib/commands.js":{"offset":3108136,"length":107,"mode":420},"/node_modules/favela-peer/node_modules/dht-rpc/lib/errors.js":{"offset":3108243,"length":719,"mode":420},"/node_modules/favela-peer/node_modules/dht-rpc/lib/health.js":{"offset":3108962,"length":2998,"mode":420},"/node_modules/favela-peer/node_modules/dht-rpc/lib/io.js":{"offset":3111960,"length":16605,"mode":420},"/node_modules/favela-peer/node_modules/dht-rpc/lib/peer.js":{"offset":3128565,"length":583,"mode":420},"/node_modules/favela-peer/node_modules/dht-rpc/lib/query.js":{"offset":3129148,"length":9899,"mode":420},"/node_modules/favela-peer/node_modules/dht-rpc/lib/session.js":{"offset":3139047,"length":1103,"mode":420},"/node_modules/favela-peer/node_modules/dht-rpc/package.json":{"offset":3140150,"length":1396,"mode":420},"/node_modules/favela-peer/node_modules/events-universal/bare.js":{"offset":3141546,"length":40,"mode":420},"/node_modules/favela-peer/node_modules/events-universal/package.json":{"offset":3141586,"length":908,"mode":420},"/node_modules/favela-peer/node_modules/fast-fifo/fixed-size.js":{"offset":3142494,"length":875,"mode":420},"/node_modules/favela-peer/node_modules/fast-fifo/index.js":{"offset":3143369,"length":972,"mode":420},"/node_modules/favela-peer/node_modules/fast-fifo/package.json":{"offset":3144341,"length":682,"mode":420},"/node_modules/favela-peer/node_modules/favela-identity/lib/attestation.js":{"offset":3145023,"length":7294,"mode":420},"/node_modules/favela-peer/node_modules/favela-identity/node_modules/b4a/index.js":{"offset":3152317,"length":4054,"mode":420},"/node_modules/favela-peer/node_modules/favela-identity/node_modules/b4a/package.json":{"offset":3156371,"length":1245,"mode":420},"/node_modules/favela-peer/node_modules/favela-identity/node_modules/compact-encoding/endian.js":{"offset":3157616,"length":103,"mode":420},"/node_modules/favela-peer/node_modules/favela-identity/node_modules/compact-encoding/index.js":{"offset":3157719,"length":27021,"mode":420},"/node_modules/favela-peer/node_modules/favela-identity/node_modules/compact-encoding/lexint.js":{"offset":3184740,"length":2837,"mode":420},"/node_modules/favela-peer/node_modules/favela-identity/node_modules/compact-encoding/package.json":{"offset":3187577,"length":968,"mode":420},"/node_modules/favela-peer/node_modules/favela-identity/node_modules/compact-encoding/raw.js":{"offset":3188545,"length":3977,"mode":420},"/node_modules/favela-peer/node_modules/favela-identity/node_modules/keet-identity-key/index.js":{"offset":3192522,"length":6320,"mode":420},"/node_modules/favela-peer/node_modules/favela-identity/node_modules/keet-identity-key/lib/crypto.js":{"offset":3198842,"length":552,"mode":420},"/node_modules/favela-peer/node_modules/favela-identity/node_modules/keet-identity-key/lib/encoding.js":{"offset":3199394,"length":3695,"mode":420},"/node_modules/favela-peer/node_modules/favela-identity/node_modules/keet-identity-key/lib/keychain.js":{"offset":3203089,"length":3080,"mode":420},"/node_modules/favela-peer/node_modules/favela-identity/node_modules/keet-identity-key/package.json":{"offset":3206169,"length":887,"mode":420},"/node_modules/favela-peer/node_modules/favela-identity/package.json":{"offset":3207056,"length":939,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/lib/dialect.js":{"offset":3207995,"length":3478,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/lib/levels.js":{"offset":3211473,"length":7815,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/lib/reach.js":{"offset":3219288,"length":4631,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/lib/record.js":{"offset":3223919,"length":4977,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/lib/render.js":{"offset":3228896,"length":14215,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/lib/strip.js":{"offset":3243111,"length":21499,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/lib/uri.js":{"offset":3264610,"length":5592,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/ccount/index.js":{"offset":3270202,"length":619,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/ccount/package.json":{"offset":3270821,"length":1839,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/character-entities-html4/index.js":{"offset":3272660,"length":3908,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/character-entities-html4/package.json":{"offset":3276568,"length":2057,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/character-entities-legacy/index.js":{"offset":3278625,"length":1316,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/character-entities-legacy/package.json":{"offset":3279941,"length":2068,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/character-entities/index.js":{"offset":3282009,"length":37342,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/character-entities/package.json":{"offset":3319351,"length":2014,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/character-reference-invalid/index.js":{"offset":3321365,"length":558,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/character-reference-invalid/package.json":{"offset":3321923,"length":2233,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/decode-named-character-reference/index.js":{"offset":3324156,"length":634,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/decode-named-character-reference/package.json":{"offset":3324790,"length":2331,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/devlop/lib/default.js":{"offset":3327121,"length":133,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/devlop/package.json":{"offset":3327254,"length":2114,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/is-alphabetical/index.js":{"offset":3329368,"length":445,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/is-alphabetical/package.json":{"offset":3329813,"length":1873,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/is-alphanumerical/index.js":{"offset":3331686,"length":406,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/is-alphanumerical/package.json":{"offset":3332092,"length":2005,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/is-decimal/index.js":{"offset":3334097,"length":374,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/is-decimal/package.json":{"offset":3334471,"length":1848,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/is-hexadecimal/index.js":{"offset":3336319,"length":503,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/is-hexadecimal/package.json":{"offset":3336822,"length":1868,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/longest-streak/index.js":{"offset":3338690,"length":823,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/longest-streak/package.json":{"offset":3339513,"length":1819,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/markdown-table/index.js":{"offset":3341332,"length":10270,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/markdown-table/package.json":{"offset":3351602,"length":1903,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-directive/index.js":{"offset":3353505,"length":116,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-directive/lib/index.js":{"offset":3353621,"length":13110,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-directive/package.json":{"offset":3366731,"length":2993,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-from-markdown/index.js":{"offset":3369724,"length":89,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-from-markdown/lib/index.js":{"offset":3369813,"length":29022,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-from-markdown/package.json":{"offset":3398835,"length":2839,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-gfm-footnote/index.js":{"offset":3401674,"length":127,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-gfm-footnote/lib/index.js":{"offset":3401801,"length":5112,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-gfm-footnote/package.json":{"offset":3406913,"length":2796,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-gfm-strikethrough/index.js":{"offset":3409709,"length":132,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-gfm-strikethrough/lib/index.js":{"offset":3409841,"length":2661,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-gfm-strikethrough/package.json":{"offset":3412502,"length":2516,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-gfm-table/index.js":{"offset":3415018,"length":114,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-gfm-table/lib/index.js":{"offset":3415132,"length":8185,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-gfm-table/package.json":{"offset":3423317,"length":2537,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-phrasing/index.js":{"offset":3425854,"length":40,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-phrasing/lib/index.js":{"offset":3425894,"length":1072,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-phrasing/package.json":{"offset":3426966,"length":1970,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/index.js":{"offset":3428936,"length":154,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/configure.js":{"offset":3429090,"length":1429,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/handle/blockquote.js":{"offset":3430519,"length":645,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/handle/break.js":{"offset":3431164,"length":705,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/handle/code.js":{"offset":3431869,"length":1757,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/handle/definition.js":{"offset":3433626,"length":1761,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/handle/emphasis.js":{"offset":3435387,"length":1671,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/handle/heading.js":{"offset":3437058,"length":2033,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/handle/html.js":{"offset":3439091,"length":237,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/handle/image-reference.js":{"offset":3439328,"length":1647,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/handle/image.js":{"offset":3440975,"length":1853,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/handle/index.js":{"offset":3442828,"length":1040,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/handle/inline-code.js":{"offset":3443868,"length":2146,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/handle/link-reference.js":{"offset":3446014,"length":1652,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/handle/link.js":{"offset":3447666,"length":2663,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/handle/list-item.js":{"offset":3450329,"length":1614,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/handle/list.js":{"offset":3451943,"length":2961,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/handle/paragraph.js":{"offset":3454904,"length":467,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/handle/root.js":{"offset":3455371,"length":575,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/handle/strong.js":{"offset":3455946,"length":1665,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/handle/text.js":{"offset":3457611,"length":320,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/handle/thematic-break.js":{"offset":3457931,"length":571,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/index.js":{"offset":3458502,"length":4808,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/join.js":{"offset":3463310,"length":1093,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/unsafe.js":{"offset":3464403,"length":5518,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/util/association.js":{"offset":3469921,"length":1113,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/util/check-bullet-ordered.js":{"offset":3471034,"length":472,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/util/check-bullet-other.js":{"offset":3471506,"length":853,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/util/check-bullet.js":{"offset":3472359,"length":468,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/util/check-emphasis.js":{"offset":3472827,"length":456,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/util/check-fence.js":{"offset":3473283,"length":443,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/util/check-list-item-indent.js":{"offset":3473726,"length":512,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/util/check-quote.js":{"offset":3474238,"length":442,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/util/check-rule-repetition.js":{"offset":3474680,"length":480,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/util/check-rule.js":{"offset":3475160,"length":460,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/util/check-strong.js":{"offset":3475620,"length":446,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/util/compile-pattern.js":{"offset":3476066,"length":577,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/util/container-flow.js":{"offset":3476643,"length":1828,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/util/container-phrasing.js":{"offset":3478471,"length":3668,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/util/encode-character-reference.js":{"offset":3482139,"length":272,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/util/encode-info.js":{"offset":3482411,"length":3216,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/util/format-code-as-indented.js":{"offset":3485627,"length":568,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/util/format-heading-as-setext.js":{"offset":3486195,"length":746,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/util/format-link-as-autolink.js":{"offset":3486941,"length":1002,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/util/indent-lines.js":{"offset":3487943,"length":613,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/util/pattern-in-scope.js":{"offset":3488556,"length":771,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/util/safe.js":{"offset":3489327,"length":4649,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/util/track.js":{"offset":3493976,"length":1263,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/package.json":{"offset":3495239,"length":3174,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-string/index.js":{"offset":3498413,"length":104,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-string/lib/index.js":{"offset":3498517,"length":2626,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/mdast-util-to-string/package.json":{"offset":3501143,"length":1918,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-core-commonmark/index.js":{"offset":3503061,"length":1151,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-core-commonmark/lib/attention.js":{"offset":3504212,"length":7172,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-core-commonmark/lib/autolink.js":{"offset":3511384,"length":4817,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-core-commonmark/lib/blank-line.js":{"offset":3516201,"length":1165,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-core-commonmark/lib/block-quote.js":{"offset":3517366,"length":2731,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-core-commonmark/lib/character-escape.js":{"offset":3520097,"length":1173,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-core-commonmark/lib/character-reference.js":{"offset":3521270,"length":3300,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-core-commonmark/lib/code-fenced.js":{"offset":3524570,"length":9281,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-core-commonmark/lib/code-indented.js":{"offset":3533851,"length":4038,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-core-commonmark/lib/code-text.js":{"offset":3537889,"length":4840,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-core-commonmark/lib/content.js":{"offset":3542729,"length":3290,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-core-commonmark/lib/definition.js":{"offset":3546019,"length":5653,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-core-commonmark/lib/hard-break-escape.js":{"offset":3551672,"length":984,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-core-commonmark/lib/heading-atx.js":{"offset":3552656,"length":4386,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-core-commonmark/lib/html-flow.js":{"offset":3557042,"length":18627,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-core-commonmark/lib/html-text.js":{"offset":3575669,"length":13364,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-core-commonmark/lib/label-end.js":{"offset":3589033,"length":12658,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-core-commonmark/lib/label-start-image.js":{"offset":3601691,"length":1945,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-core-commonmark/lib/label-start-link.js":{"offset":3603636,"length":1116,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-core-commonmark/lib/line-ending.js":{"offset":3604752,"length":676,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-core-commonmark/lib/list.js":{"offset":3605428,"length":6251,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-core-commonmark/lib/setext-underline.js":{"offset":3611679,"length":4282,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-core-commonmark/lib/thematic-break.js":{"offset":3615961,"length":1873,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-core-commonmark/package.json":{"offset":3617834,"length":2027,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-extension-directive/index.js":{"offset":3619861,"length":139,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-extension-directive/lib/directive-container.js":{"offset":3620000,"length":7143,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-extension-directive/lib/directive-leaf.js":{"offset":3627143,"length":2619,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-extension-directive/lib/directive-text.js":{"offset":3629762,"length":2416,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-extension-directive/lib/factory-attributes.js":{"offset":3632178,"length":7547,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-extension-directive/lib/factory-label.js":{"offset":3639725,"length":2625,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-extension-directive/lib/factory-name.js":{"offset":3642350,"length":1034,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-extension-directive/lib/html.js":{"offset":3643384,"length":6728,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-extension-directive/lib/syntax.js":{"offset":3650112,"length":590,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-extension-directive/package.json":{"offset":3650702,"length":3295,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-extension-gfm-footnote/index.js":{"offset":3653997,"length":164,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-extension-gfm-footnote/lib/html.js":{"offset":3654161,"length":6371,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-extension-gfm-footnote/lib/syntax.js":{"offset":3660532,"length":12479,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-extension-gfm-footnote/package.json":{"offset":3673011,"length":3422,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-extension-gfm-strikethrough/index.js":{"offset":3676433,"length":152,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-extension-gfm-strikethrough/lib/html.js":{"offset":3676585,"length":551,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-extension-gfm-strikethrough/lib/syntax.js":{"offset":3677136,"length":4763,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-extension-gfm-strikethrough/package.json":{"offset":3681899,"length":3306,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-extension-gfm-table/index.js":{"offset":3685205,"length":89,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-extension-gfm-table/lib/edit-map.js":{"offset":3685294,"length":5284,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-extension-gfm-table/lib/html.js":{"offset":3690578,"length":3820,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-extension-gfm-table/lib/infer.js":{"offset":3694398,"length":1511,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-extension-gfm-table/lib/syntax.js":{"offset":3695909,"length":20730,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-extension-gfm-table/package.json":{"offset":3716639,"length":3205,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-factory-destination/index.js":{"offset":3719844,"length":4398,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-factory-destination/package.json":{"offset":3724242,"length":1377,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-factory-label/index.js":{"offset":3725619,"length":3210,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-factory-label/package.json":{"offset":3728829,"length":1461,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-factory-space/index.js":{"offset":3730290,"length":1514,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-factory-space/package.json":{"offset":3731804,"length":1287,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-factory-title/index.js":{"offset":3733091,"length":3063,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-factory-title/package.json":{"offset":3736154,"length":1403,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-factory-whitespace/index.js":{"offset":3737557,"length":1203,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-factory-whitespace/package.json":{"offset":3738760,"length":1387,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-util-character/index.js":{"offset":3740147,"length":7068,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-util-character/package.json":{"offset":3747215,"length":1290,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-util-chunked/index.js":{"offset":3748505,"length":2334,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-util-chunked/package.json":{"offset":3750839,"length":1269,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-util-classify-character/index.js":{"offset":3752108,"length":803,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-util-classify-character/package.json":{"offset":3752911,"length":1406,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-util-combine-extensions/index.js":{"offset":3754317,"length":3318,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-util-combine-extensions/package.json":{"offset":3757635,"length":1244,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-util-decode-numeric-character-reference/index.js":{"offset":3758879,"length":1061,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-util-decode-numeric-character-reference/package.json":{"offset":3759940,"length":1363,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-util-decode-string/index.js":{"offset":3761303,"length":1250,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-util-decode-string/package.json":{"offset":3762553,"length":1517,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-util-encode/index.js":{"offset":3764070,"length":757,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-util-encode/package.json":{"offset":3764827,"length":1119,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-util-html-tag-name/index.js":{"offset":3765946,"length":1680,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-util-html-tag-name/package.json":{"offset":3767626,"length":1084,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-util-normalize-identifier/index.js":{"offset":3768710,"length":1248,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-util-normalize-identifier/package.json":{"offset":3769958,"length":1374,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-util-resolve-all/index.js":{"offset":3771332,"length":784,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-util-resolve-all/package.json":{"offset":3772116,"length":1115,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-util-sanitize-uri/index.js":{"offset":3773231,"length":3159,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-util-sanitize-uri/package.json":{"offset":3776390,"length":1341,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-util-subtokenize/index.js":{"offset":3777731,"length":6851,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-util-subtokenize/lib/splice-buffer.js":{"offset":3784582,"length":7617,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark-util-subtokenize/package.json":{"offset":3792199,"length":1379,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark/index.js":{"offset":3793578,"length":1893,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark/lib/compile.js":{"offset":3795471,"length":26755,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark/lib/constructs.js":{"offset":3822226,"length":1964,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark/lib/create-tokenizer.js":{"offset":3824190,"length":15175,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark/lib/initialize/content.js":{"offset":3839365,"length":1721,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark/lib/initialize/document.js":{"offset":3841086,"length":10860,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark/lib/initialize/flow.js":{"offset":3851946,"length":1507,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark/lib/initialize/text.js":{"offset":3853453,"length":5566,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark/lib/parse.js":{"offset":3859019,"length":1435,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark/lib/postprocess.js":{"offset":3860454,"length":314,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark/lib/preprocess.js":{"offset":3860768,"length":3063,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/micromark/package.json":{"offset":3863831,"length":2498,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/parse-entities/index.js":{"offset":3866329,"length":145,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/parse-entities/lib/index.js":{"offset":3866474,"length":11738,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/parse-entities/package.json":{"offset":3878212,"length":2392,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/stringify-entities/index.js":{"offset":3880604,"length":160,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/stringify-entities/lib/constant/dangerous.js":{"offset":3880764,"length":290,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/stringify-entities/lib/core.js":{"offset":3881054,"length":3040,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/stringify-entities/lib/index.js":{"offset":3884094,"length":961,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/stringify-entities/lib/util/format-basic.js":{"offset":3885055,"length":192,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/stringify-entities/lib/util/format-smart.js":{"offset":3885247,"length":2478,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/stringify-entities/lib/util/to-decimal.js":{"offset":3887725,"length":392,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/stringify-entities/lib/util/to-hexadecimal.js":{"offset":3888117,"length":436,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/stringify-entities/lib/util/to-named.js":{"offset":3888553,"length":1290,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/stringify-entities/package.json":{"offset":3889843,"length":2238,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/unist-util-is/index.js":{"offset":3892081,"length":217,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/unist-util-is/lib/index.js":{"offset":3892298,"length":8989,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/unist-util-is/package.json":{"offset":3901287,"length":2640,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/unist-util-stringify-position/index.js":{"offset":3903927,"length":49,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/unist-util-stringify-position/lib/index.js":{"offset":3903976,"length":2123,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/unist-util-stringify-position/package.json":{"offset":3906099,"length":2022,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/unist-util-visit-parents/index.js":{"offset":3908121,"length":108,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/unist-util-visit-parents/lib/color.node.js":{"offset":3908229,"length":120,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/unist-util-visit-parents/lib/index.js":{"offset":3908349,"length":11752,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/unist-util-visit-parents/package.json":{"offset":3920101,"length":2627,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/unist-util-visit/index.js":{"offset":3922728,"length":101,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/unist-util-visit/lib/index.js":{"offset":3922829,"length":9582,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/unist-util-visit/package.json":{"offset":3932411,"length":2544,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/zwitch/index.js":{"offset":3934955,"length":3787,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/node_modules/zwitch/package.json":{"offset":3938742,"length":1767,"mode":420},"/node_modules/favela-peer/node_modules/favela-markdown/package.json":{"offset":3940509,"length":1131,"mode":420},"/node_modules/favela-peer/node_modules/fd-lock/index.js":{"offset":3941640,"length":1755,"mode":420},"/node_modules/favela-peer/node_modules/fd-lock/package.json":{"offset":3943395,"length":1099,"mode":420},"/node_modules/favela-peer/node_modules/flat-tree/index.js":{"offset":3944494,"length":8164,"mode":420},"/node_modules/favela-peer/node_modules/flat-tree/package.json":{"offset":3952658,"length":631,"mode":420},"/node_modules/favela-peer/node_modules/fs-native-extensions/binding.js":{"offset":3953289,"length":90,"mode":420},"/node_modules/favela-peer/node_modules/fs-native-extensions/index.js":{"offset":3953379,"length":7239,"mode":420},"/node_modules/favela-peer/node_modules/fs-native-extensions/package.json":{"offset":3960618,"length":1790,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/index.js":{"offset":3962408,"length":789,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/lib/adapt-host.js":{"offset":3963197,"length":1052,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/lib/registry.js":{"offset":3964249,"length":2543,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/lib/view-shim.js":{"offset":3966792,"length":8093,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/@hyperswarm/secret-stream/index.js":{"offset":3974885,"length":16722,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/@hyperswarm/secret-stream/lib/bridge.js":{"offset":3991607,"length":1273,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/@hyperswarm/secret-stream/lib/handshake.js":{"offset":3992880,"length":1950,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/@hyperswarm/secret-stream/package.json":{"offset":3994830,"length":1132,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/autobee-encryption/index.js":{"offset":3995962,"length":7351,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/autobee-encryption/node_modules/compact-encoding/endian.js":{"offset":4003313,"length":105,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/autobee-encryption/node_modules/compact-encoding/index.js":{"offset":4003418,"length":24454,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/autobee-encryption/node_modules/compact-encoding/lexint.js":{"offset":4027872,"length":2851,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/autobee-encryption/node_modules/compact-encoding/package.json":{"offset":4030723,"length":786,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/autobee-encryption/node_modules/compact-encoding/raw.js":{"offset":4031509,"length":4093,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/autobee-encryption/package.json":{"offset":4035602,"length":1064,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/autobee-wakeup/index.js":{"offset":4036666,"length":3089,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/autobee-wakeup/package.json":{"offset":4039755,"length":1077,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/autobee/encoding/legacy.js":{"offset":4040832,"length":7704,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/autobee/encoding/spec/autobee/index.js":{"offset":4048536,"length":35796,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/autobee/index.js":{"offset":4084332,"length":43745,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/autobee/lib/apply-calls.js":{"offset":4128077,"length":1448,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/autobee/lib/apply-view.js":{"offset":4129525,"length":580,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/autobee/lib/asserts.js":{"offset":4130105,"length":523,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/autobee/lib/boot.js":{"offset":4130628,"length":4542,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/autobee/lib/constants.js":{"offset":4135170,"length":277,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/autobee/lib/encoding.js":{"offset":4135447,"length":6508,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/autobee/lib/fast-forward.js":{"offset":4141955,"length":9985,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/autobee/lib/migrations.js":{"offset":4151940,"length":13464,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/autobee/lib/promotions.js":{"offset":4165404,"length":5709,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/autobee/lib/system.js":{"offset":4171113,"length":12039,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/autobee/lib/topo.js":{"offset":4183152,"length":9753,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/autobee/lib/triggers.js":{"offset":4192905,"length":1249,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/autobee/lib/trusted.js":{"offset":4194154,"length":1020,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/autobee/lib/updates.js":{"offset":4195174,"length":815,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/autobee/lib/witness.js":{"offset":4195989,"length":1857,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/autobee/lib/writers.js":{"offset":4197846,"length":19204,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/autobee/package.json":{"offset":4217050,"length":1293,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/b4a/index.js":{"offset":4218343,"length":4054,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/b4a/package.json":{"offset":4222397,"length":1245,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/bare-ansi-escapes/index.js":{"offset":4223642,"length":2255,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/bare-ansi-escapes/package.json":{"offset":4225897,"length":1178,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/bare-assert/index.js":{"offset":4227075,"length":2192,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/bare-assert/package.json":{"offset":4229267,"length":835,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/bare-events/index.js":{"offset":4230102,"length":8885,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/bare-events/lib/errors.js":{"offset":4238987,"length":634,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/bare-events/package.json":{"offset":4239621,"length":1504,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/bare-fs/binding.js":{"offset":4241125,"length":33,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/bare-fs/index.js":{"offset":4241158,"length":59172,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/bare-fs/lib/constants.js":{"offset":4300330,"length":1494,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/bare-fs/lib/errors.js":{"offset":4301824,"length":1166,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/bare-fs/package.json":{"offset":4302990,"length":1654,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/bare-fs/promises.js":{"offset":4304644,"length":2401,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/bare-inspect/binding.js":{"offset":4307045,"length":33,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/bare-inspect/index.js":{"offset":4307078,"length":28811,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/bare-inspect/package.json":{"offset":4335889,"length":1157,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/bare-path/binding.js":{"offset":4337046,"length":33,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/bare-path/index.js":{"offset":4337079,"length":287,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/bare-path/lib/constants.js":{"offset":4337366,"length":247,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/bare-path/lib/posix.js":{"offset":4337613,"length":6399,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/bare-path/lib/shared.js":{"offset":4344012,"length":2148,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/bare-path/lib/win32.js":{"offset":4346160,"length":13987,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/bare-path/package.json":{"offset":4360147,"length":1106,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/bare-stream/index.js":{"offset":4361253,"length":10904,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/bare-stream/package.json":{"offset":4372157,"length":1676,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/bare-stream/web.js":{"offset":4373833,"length":13980,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/bare-type/binding.js":{"offset":4387813,"length":33,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/bare-type/index.js":{"offset":4387846,"length":4719,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/bare-type/package.json":{"offset":4392565,"length":1053,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/bare-url/binding.js":{"offset":4393618,"length":33,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/bare-url/index.js":{"offset":4393651,"length":17970,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/bare-url/lib/errors.js":{"offset":4411621,"length":778,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/bare-url/lib/url-search-params.js":{"offset":4412399,"length":7104,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/bare-url/package.json":{"offset":4419503,"length":1219,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/big-sparse-array/index.js":{"offset":4420722,"length":2471,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/big-sparse-array/package.json":{"offset":4423193,"length":636,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/compact-encoding/endian.js":{"offset":4423829,"length":103,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/compact-encoding/index.js":{"offset":4423932,"length":27021,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/compact-encoding/lexint.js":{"offset":4450953,"length":2837,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/compact-encoding/package.json":{"offset":4453790,"length":968,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/compact-encoding/raw.js":{"offset":4454758,"length":3977,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/core-coupler/index.js":{"offset":4458735,"length":2243,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/core-coupler/package.json":{"offset":4460978,"length":599,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/device-file/index.js":{"offset":4461577,"length":6394,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/device-file/package.json":{"offset":4467971,"length":1190,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/events-universal/bare.js":{"offset":4469161,"length":40,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/events-universal/package.json":{"offset":4469201,"length":908,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/fast-fifo/fixed-size.js":{"offset":4470109,"length":875,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/fast-fifo/index.js":{"offset":4470984,"length":972,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/fast-fifo/package.json":{"offset":4471956,"length":682,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/fd-lock/index.js":{"offset":4472638,"length":1755,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/fd-lock/package.json":{"offset":4474393,"length":1099,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/flat-tree/index.js":{"offset":4475492,"length":8164,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/flat-tree/package.json":{"offset":4483656,"length":631,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/fs-native-extensions/binding.js":{"offset":4484287,"length":90,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/fs-native-extensions/index.js":{"offset":4484377,"length":7239,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/fs-native-extensions/package.json":{"offset":4491616,"length":1790,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyper-identity-core/index.js":{"offset":4493406,"length":797,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyper-identity-core/lib/apply.js":{"offset":4494203,"length":26856,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyper-identity-core/lib/operations.js":{"offset":4521059,"length":8352,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyper-identity-core/lib/registry.js":{"offset":4529411,"length":37621,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyper-identity-core/node_modules/b4a/index.js":{"offset":4567032,"length":4054,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyper-identity-core/node_modules/b4a/package.json":{"offset":4571086,"length":1245,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyper-identity-core/node_modules/bare-ansi-escapes/index.js":{"offset":4572331,"length":2255,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyper-identity-core/node_modules/bare-ansi-escapes/package.json":{"offset":4574586,"length":1178,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyper-identity-core/node_modules/bare-assert/index.js":{"offset":4575764,"length":2192,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyper-identity-core/node_modules/bare-assert/package.json":{"offset":4577956,"length":835,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyper-identity-core/node_modules/bare-inspect/binding.js":{"offset":4578791,"length":33,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyper-identity-core/node_modules/bare-inspect/index.js":{"offset":4578824,"length":28811,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyper-identity-core/node_modules/bare-inspect/package.json":{"offset":4607635,"length":1157,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyper-identity-core/node_modules/bare-type/binding.js":{"offset":4608792,"length":33,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyper-identity-core/node_modules/bare-type/index.js":{"offset":4608825,"length":4719,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyper-identity-core/node_modules/bare-type/package.json":{"offset":4613544,"length":1053,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyper-identity-core/node_modules/compact-encoding/endian.js":{"offset":4614597,"length":103,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyper-identity-core/node_modules/compact-encoding/index.js":{"offset":4614700,"length":27021,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyper-identity-core/node_modules/compact-encoding/lexint.js":{"offset":4641721,"length":2837,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyper-identity-core/node_modules/compact-encoding/package.json":{"offset":4644558,"length":968,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyper-identity-core/node_modules/compact-encoding/raw.js":{"offset":4645526,"length":3977,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyper-identity-core/node_modules/hypercore-crypto/index.js":{"offset":4649503,"length":5031,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyper-identity-core/node_modules/hypercore-crypto/package.json":{"offset":4654534,"length":1007,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyper-identity-core/node_modules/require-addon/lib/bare.js":{"offset":4655541,"length":45,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyper-identity-core/node_modules/require-addon/package.json":{"offset":4655586,"length":1449,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyper-identity-core/node_modules/sodium-native/binding.js":{"offset":4657035,"length":89,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyper-identity-core/node_modules/sodium-native/index.js":{"offset":4657124,"length":107894,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyper-identity-core/node_modules/sodium-native/package.json":{"offset":4765018,"length":1478,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyper-identity-core/node_modules/sodium-universal/index.js":{"offset":4766496,"length":42,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyper-identity-core/node_modules/sodium-universal/package.json":{"offset":4766538,"length":1217,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyper-identity-core/node_modules/which-runtime/index.js":{"offset":4767755,"length":1469,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyper-identity-core/node_modules/which-runtime/package.json":{"offset":4769224,"length":863,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyper-identity-core/package.json":{"offset":4770087,"length":713,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyperbee2/index.js":{"offset":4770800,"length":7859,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyperbee2/lib/cache.js":{"offset":4778659,"length":1946,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyperbee2/lib/changes.js":{"offset":4780605,"length":1895,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyperbee2/lib/compat.js":{"offset":4782500,"length":9370,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyperbee2/lib/compression.js":{"offset":4791870,"length":5400,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyperbee2/lib/context.js":{"offset":4797270,"length":7111,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyperbee2/lib/diff.js":{"offset":4804381,"length":3687,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyperbee2/lib/encoding.js":{"offset":4808068,"length":3065,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyperbee2/lib/inflate.js":{"offset":4811133,"length":4788,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyperbee2/lib/ranges.js":{"offset":4815921,"length":5120,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyperbee2/lib/session-config.js":{"offset":4821041,"length":1062,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyperbee2/lib/tree.js":{"offset":4822103,"length":6409,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyperbee2/lib/write.js":{"offset":4828512,"length":17092,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyperbee2/package.json":{"offset":4845604,"length":1256,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyperbee2/spec/hyperschema/index.js":{"offset":4846860,"length":15227,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore-crypto/index.js":{"offset":4862087,"length":5031,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore-crypto/package.json":{"offset":4867118,"length":1007,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore-errors/index.js":{"offset":4868125,"length":4896,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore-errors/package.json":{"offset":4873021,"length":688,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore-id-encoding/index.js":{"offset":4873709,"length":923,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore-id-encoding/package.json":{"offset":4874632,"length":799,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore-storage/encoding/external.js":{"offset":4875431,"length":264,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore-storage/encoding/spec/hyperschema/index.js":{"offset":4875695,"length":16796,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore-storage/index.js":{"offset":4892491,"length":33493,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore-storage/lib/block-dependency-stream.js":{"offset":4925984,"length":2557,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore-storage/lib/close-error-stream.js":{"offset":4928541,"length":276,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore-storage/lib/keys.js":{"offset":4928817,"length":8751,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore-storage/lib/streams.js":{"offset":4937568,"length":5686,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore-storage/lib/tx.js":{"offset":4943254,"length":11683,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore-storage/lib/view.js":{"offset":4954937,"length":8379,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore-storage/migrations/0/index.js":{"offset":4963316,"length":20125,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore-storage/migrations/0/messages.js":{"offset":4983441,"length":26402,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore-storage/migrations/1/index.js":{"offset":5009843,"length":1181,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore-storage/package.json":{"offset":5011024,"length":1827,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore/index.js":{"offset":5012851,"length":41043,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore/lib/audit.js":{"offset":5053894,"length":3834,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore/lib/bit-interlude.js":{"offset":5057728,"length":4257,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore/lib/bitfield.js":{"offset":5061985,"length":12115,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore/lib/caps.js":{"offset":5074100,"length":1537,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore/lib/compat.js":{"offset":5075637,"length":477,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore/lib/copy-prologue.js":{"offset":5076114,"length":5999,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore/lib/core.js":{"offset":5082113,"length":28389,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore/lib/default-encryption.js":{"offset":5110502,"length":3453,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore/lib/download.js":{"offset":5113955,"length":1278,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore/lib/feature-flags.js":{"offset":5115233,"length":38,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore/lib/fully-remote-proof.js":{"offset":5115271,"length":3470,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore/lib/hotswap-queue.js":{"offset":5118741,"length":1498,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore/lib/info.js":{"offset":5120239,"length":1517,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore/lib/inspect.js":{"offset":5121756,"length":1983,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore/lib/mark-bitfield.js":{"offset":5123739,"length":2707,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore/lib/merkle-tree.js":{"offset":5126446,"length":34353,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore/lib/messages.js":{"offset":5160799,"length":28313,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore/lib/multisig.js":{"offset":5189112,"length":2851,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore/lib/mutex.js":{"offset":5191963,"length":1028,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore/lib/receiver-queue.js":{"offset":5192991,"length":1413,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore/lib/remote-bitfield.js":{"offset":5194404,"length":8124,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore/lib/replicator.js":{"offset":5202528,"length":91358,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore/lib/session-state.js":{"offset":5293886,"length":36297,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore/lib/streams.js":{"offset":5330183,"length":3034,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore/lib/verifier.js":{"offset":5333217,"length":9990,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore/lib/wants.js":{"offset":5343207,"length":7204,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hypercore/package.json":{"offset":5350411,"length":2196,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyperschema/package.json":{"offset":5352607,"length":1642,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/hyperschema/runtime.cjs":{"offset":5354249,"length":54,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/index-encoder/index.js":{"offset":5354303,"length":10596,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/index-encoder/package.json":{"offset":5364899,"length":928,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/is-options/index.js":{"offset":5365827,"length":140,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/is-options/package.json":{"offset":5365967,"length":605,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/nanoassert/index.js":{"offset":5366572,"length":438,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/nanoassert/package.json":{"offset":5367010,"length":647,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/noise-curve-ed/index.js":{"offset":5367657,"length":1642,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/noise-curve-ed/package.json":{"offset":5369299,"length":808,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/noise-handshake/cipher.js":{"offset":5370107,"length":2817,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/noise-handshake/dh.js":{"offset":5372924,"length":1439,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/noise-handshake/hkdf.js":{"offset":5374363,"length":1092,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/noise-handshake/hmac.js":{"offset":5375455,"length":1273,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/noise-handshake/noise.js":{"offset":5376728,"length":6437,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/noise-handshake/package.json":{"offset":5383165,"length":638,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/noise-handshake/symmetric-state.js":{"offset":5383803,"length":2206,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/protocol-buffers-encodings/index.js":{"offset":5386009,"length":6489,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/protocol-buffers-encodings/package.json":{"offset":5392498,"length":719,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/protomux-wakeup/index.js":{"offset":5393217,"length":16824,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/protomux-wakeup/package.json":{"offset":5410041,"length":972,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/protomux-wakeup/spec/hyperschema/index.js":{"offset":5411013,"length":3335,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/protomux/index.js":{"offset":5414348,"length":19741,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/protomux/package.json":{"offset":5434089,"length":1059,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/queue-tick/package.json":{"offset":5435148,"length":669,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/queue-tick/process-next-tick.js":{"offset":5435817,"length":160,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/queue-tick/queue-microtask.js":{"offset":5435977,"length":108,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/quickbit-native/binding.js":{"offset":5436085,"length":90,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/quickbit-native/index.js":{"offset":5436175,"length":4683,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/quickbit-native/package.json":{"offset":5440858,"length":1104,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/quickbit-universal/fallback.js":{"offset":5441962,"length":10204,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/quickbit-universal/index.js":{"offset":5452166,"length":442,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/quickbit-universal/package.json":{"offset":5452608,"length":908,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/random-array-iterator/index.js":{"offset":5453516,"length":1001,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/random-array-iterator/package.json":{"offset":5454517,"length":700,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/ready-guard/index.js":{"offset":5455217,"length":645,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/ready-guard/package.json":{"offset":5455862,"length":842,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/ready-resource/index.js":{"offset":5456704,"length":1091,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/ready-resource/package.json":{"offset":5457795,"length":812,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/refcounter/index.js":{"offset":5458607,"length":611,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/refcounter/package.json":{"offset":5459218,"length":451,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/require-addon/lib/bare.js":{"offset":5459669,"length":45,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/require-addon/package.json":{"offset":5459714,"length":1449,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/resolve-reject-promise/index.js":{"offset":5461163,"length":443,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/resolve-reject-promise/package.json":{"offset":5461606,"length":581,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/resource-on-exit/index.js":{"offset":5462187,"length":361,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/resource-on-exit/package.json":{"offset":5462548,"length":496,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/rocksdb-native/binding.js":{"offset":5463044,"length":90,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/rocksdb-native/index.js":{"offset":5463134,"length":5304,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/rocksdb-native/lib/batch.js":{"offset":5468438,"length":9605,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/rocksdb-native/lib/column-family.js":{"offset":5478043,"length":2930,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/rocksdb-native/lib/constants.js":{"offset":5480973,"length":621,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/rocksdb-native/lib/filter-policy.js":{"offset":5481594,"length":437,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/rocksdb-native/lib/iterator.js":{"offset":5482031,"length":4311,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/rocksdb-native/lib/snapshot.js":{"offset":5486342,"length":503,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/rocksdb-native/lib/state.js":{"offset":5486845,"length":13334,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/rocksdb-native/package.json":{"offset":5500179,"length":1863,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/safety-catch/index.js":{"offset":5502042,"length":543,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/safety-catch/package.json":{"offset":5502585,"length":547,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/scope-lock/index.js":{"offset":5503132,"length":1821,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/scope-lock/package.json":{"offset":5504953,"length":611,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/signal-promise/index.js":{"offset":5505564,"length":1252,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/signal-promise/package.json":{"offset":5506816,"length":503,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/signed-varint/index.js":{"offset":5507319,"length":435,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/signed-varint/package.json":{"offset":5507754,"length":523,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/simdle-native/binding.js":{"offset":5508277,"length":90,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/simdle-native/index.js":{"offset":5508367,"length":3474,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/simdle-native/package.json":{"offset":5511841,"length":1115,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/simdle-universal/fallback.js":{"offset":5512956,"length":5137,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/simdle-universal/index.js":{"offset":5518093,"length":103,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/simdle-universal/package.json":{"offset":5518196,"length":879,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/simdle-universal/scalar.js":{"offset":5519075,"length":469,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/sodium-native/binding.js":{"offset":5519544,"length":89,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/sodium-native/index.js":{"offset":5519633,"length":107894,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/sodium-native/package.json":{"offset":5627527,"length":1478,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/sodium-secretstream/index.js":{"offset":5629005,"length":2257,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/sodium-secretstream/package.json":{"offset":5631262,"length":657,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/sodium-universal/index.js":{"offset":5631919,"length":42,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/sodium-universal/package.json":{"offset":5631961,"length":1217,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/streamx/index.js":{"offset":5633178,"length":34248,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/streamx/lib/errors.js":{"offset":5667426,"length":1096,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/streamx/package.json":{"offset":5668522,"length":1225,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/teex/index.js":{"offset":5669747,"length":1107,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/teex/package.json":{"offset":5670854,"length":608,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/text-decoder/index.js":{"offset":5671462,"length":1371,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/text-decoder/lib/pass-through-decoder.js":{"offset":5672833,"length":269,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/text-decoder/lib/utf8-decoder.js":{"offset":5673102,"length":4429,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/text-decoder/package.json":{"offset":5677531,"length":990,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/timeout-refresh/browser.js":{"offset":5678521,"length":1098,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/timeout-refresh/index.js":{"offset":5679619,"length":184,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/timeout-refresh/node.js":{"offset":5679803,"length":928,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/timeout-refresh/package.json":{"offset":5680731,"length":619,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/unslab/index.js":{"offset":5681350,"length":913,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/unslab/package.json":{"offset":5682263,"length":613,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/varint/decode.js":{"offset":5682876,"length":508,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/varint/encode.js":{"offset":5683384,"length":452,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/varint/index.js":{"offset":5683836,"length":134,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/varint/length.js":{"offset":5683970,"length":471,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/varint/package.json":{"offset":5684441,"length":511,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/which-runtime/index.js":{"offset":5684952,"length":1469,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/which-runtime/package.json":{"offset":5686421,"length":863,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/xache/index.js":{"offset":5687284,"length":2477,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/xache/package.json":{"offset":5689761,"length":672,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/z32/index.js":{"offset":5690433,"length":2654,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/node_modules/z32/package.json":{"offset":5693087,"length":701,"mode":420},"/node_modules/favela-peer/node_modules/hyper-identity2/package.json":{"offset":5693788,"length":713,"mode":420},"/node_modules/favela-peer/node_modules/hyper-overlay/index.js":{"offset":5694501,"length":2334,"mode":420},"/node_modules/favela-peer/node_modules/hyper-overlay/lib/chunk-scheduler.js":{"offset":5696835,"length":18133,"mode":420},"/node_modules/favela-peer/node_modules/hyper-overlay/lib/chunker.js":{"offset":5714968,"length":14180,"mode":420},"/node_modules/favela-peer/node_modules/hyper-overlay/lib/file-index.js":{"offset":5729148,"length":20487,"mode":420},"/node_modules/favela-peer/node_modules/hyper-overlay/lib/ignore.js":{"offset":5749635,"length":4047,"mode":420},"/node_modules/favela-peer/node_modules/hyper-overlay/lib/manifest.js":{"offset":5753682,"length":2360,"mode":420},"/node_modules/favela-peer/node_modules/hyper-overlay/lib/messages.js":{"offset":5756042,"length":15242,"mode":420},"/node_modules/favela-peer/node_modules/hyper-overlay/lib/overlay.js":{"offset":5771284,"length":26332,"mode":420},"/node_modules/favela-peer/node_modules/hyper-overlay/lib/pointer.js":{"offset":5797616,"length":3986,"mode":420},"/node_modules/favela-peer/node_modules/hyper-overlay/lib/protocol.js":{"offset":5801602,"length":50117,"mode":420},"/node_modules/favela-peer/node_modules/hyper-overlay/lib/sync-engine.js":{"offset":5851719,"length":12006,"mode":420},"/node_modules/favela-peer/node_modules/hyper-overlay/lib/transfer.js":{"offset":5863725,"length":33515,"mode":420},"/node_modules/favela-peer/node_modules/hyper-overlay/lib/tree.js":{"offset":5897240,"length":14077,"mode":420},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/b4a/index.js":{"offset":5911317,"length":4054,"mode":420},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/b4a/package.json":{"offset":5915371,"length":1245,"mode":420},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-ansi-escapes/index.js":{"offset":5916616,"length":2255,"mode":420},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-ansi-escapes/package.json":{"offset":5918871,"length":1178,"mode":420},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-assert/index.js":{"offset":5920049,"length":2192,"mode":420},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-assert/package.json":{"offset":5922241,"length":835,"mode":420},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-events/index.js":{"offset":5923076,"length":8307,"mode":420},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-events/lib/errors.js":{"offset":5931383,"length":671,"mode":420},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-events/package.json":{"offset":5932054,"length":1504,"mode":420},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-fs/binding.js":{"offset":5933558,"length":33,"mode":420},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-fs/index.js":{"offset":5933591,"length":58907,"mode":420},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-fs/lib/constants.js":{"offset":5992498,"length":1494,"mode":420},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-fs/lib/errors.js":{"offset":5993992,"length":1163,"mode":420},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-fs/package.json":{"offset":5995155,"length":1654,"mode":420},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-fs/promises.js":{"offset":5996809,"length":2401,"mode":420},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-inspect/binding.js":{"offset":5999210,"length":33,"mode":420},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-inspect/index.js":{"offset":5999243,"length":23856,"mode":420},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-inspect/package.json":{"offset":6023099,"length":1041,"mode":420},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-os/binding.js":{"offset":6024140,"length":33,"mode":420},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-os/index.js":{"offset":6024173,"length":3224,"mode":420},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-os/lib/constants.js":{"offset":6027397,"length":143,"mode":420},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-os/lib/errors.js":{"offset":6027540,"length":453,"mode":420},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-os/package.json":{"offset":6027993,"length":1257,"mode":420},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-path/index.js":{"offset":6029250,"length":287,"mode":420},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-path/lib/constants.js":{"offset":6029537,"length":247,"mode":420},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-path/lib/posix.js":{"offset":6029784,"length":6005,"mode":420},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-path/lib/shared.js":{"offset":6035789,"length":1926,"mode":420},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-path/lib/win32.js":{"offset":6037715,"length":13591,"mode":420},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-path/package.json":{"offset":6051306,"length":1036,"mode":420},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-stream/index.js":{"offset":6052342,"length":10904,"mode":420},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-stream/package.json":{"offset":6063246,"length":1676,"mode":420},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-stream/web.js":{"offset":6064922,"length":13980,"mode":420},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-type/binding.js":{"offset":6078902,"length":33,"mode":420},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-type/index.js":{"offset":6078935,"length":4566,"mode":420},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-type/package.json":{"offset":6083501,"length":938,"mode":420},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-url/binding.js":{"offset":6084439,"length":33,"mode":420},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-url/index.js":{"offset":6084472,"length":9449,"mode":420},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-url/lib/errors.js":{"offset":6093921,"length":778,"mode":420},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-url/lib/url-search-params.js":{"offset":6094699,"length":4331,"mode":420},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-url/package.json":{"offset":6099030,"length":1219,"mode":420},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-zlib/binding.js":{"offset":6100249,"length":33,"mode":420},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-zlib/index.js":{"offset":6100282,"length":8166,"mode":420},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-zlib/lib/constants.js":{"offset":6108448,"length":74,"mode":420},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-zlib/lib/errors.js":{"offset":6108522,"length":1096,"mode":420},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/bare-zlib/package.json":{"offset":6109618,"length":1463,"mode":420},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/codecs/index.js":{"offset":6111081,"length":2416,"mode":420},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/codecs/package.json":{"offset":6113497,"length":655,"mode":420},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/compact-encoding/endian.js":{"offset":6114152,"length":105,"mode":420},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/compact-encoding/index.js":{"offset":6114257,"length":26135,"mode":420},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/compact-encoding/lexint.js":{"offset":6140392,"length":2851,"mode":420},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/compact-encoding/package.json":{"offset":6143243,"length":866,"mode":420},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/compact-encoding/raw.js":{"offset":6144109,"length":3977,"mode":420},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/debounceify/index.js":{"offset":6148086,"length":585,"mode":420},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/debounceify/package.json":{"offset":6148671,"length":567,"mode":420},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/events-universal/bare.js":{"offset":6149238,"length":40,"mode":420},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/events-universal/package.json":{"offset":6149278,"length":908,"mode":420},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/fast-fifo/fixed-size.js":{"offset":6150186,"length":875,"mode":420},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/fast-fifo/index.js":{"offset":6151061,"length":972,"mode":420},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/fast-fifo/package.json":{"offset":6152033,"length":682,"mode":420},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/hyperbee/index.js":{"offset":6152715,"length":46445,"mode":420},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/hyperbee/iterators/diff.js":{"offset":6199160,"length":5358,"mode":420},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/hyperbee/iterators/history.js":{"offset":6204518,"length":1631,"mode":420},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/hyperbee/iterators/local.js":{"offset":6206149,"length":1170,"mode":420},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/hyperbee/iterators/range.js":{"offset":6207319,"length":5869,"mode":420},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/hyperbee/lib/extension.js":{"offset":6213188,"length":3488,"mode":420},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/hyperbee/lib/messages.js":{"offset":6216676,"length":28162,"mode":420},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/hyperbee/package.json":{"offset":6244838,"length":1585,"mode":420},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/hypercore-crypto/index.js":{"offset":6246423,"length":5031,"mode":420},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/hypercore-crypto/package.json":{"offset":6251454,"length":1007,"mode":420},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/hypercore-errors/index.js":{"offset":6252461,"length":4896,"mode":420},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/hypercore-errors/package.json":{"offset":6257357,"length":688,"mode":420},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/hypercore-id-encoding/index.js":{"offset":6258045,"length":923,"mode":420},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/hypercore-id-encoding/package.json":{"offset":6258968,"length":799,"mode":420},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/mutexify/index.js":{"offset":6259767,"length":536,"mode":420},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/mutexify/package.json":{"offset":6260303,"length":679,"mode":420},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/mutexify/promise.js":{"offset":6260982,"length":324,"mode":420},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/protocol-buffers-encodings/index.js":{"offset":6261306,"length":6489,"mode":420},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/protocol-buffers-encodings/package.json":{"offset":6267795,"length":719,"mode":420},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/queue-tick/package.json":{"offset":6268514,"length":669,"mode":420},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/queue-tick/process-next-tick.js":{"offset":6269183,"length":160,"mode":420},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/queue-tick/queue-microtask.js":{"offset":6269343,"length":108,"mode":420},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/rache/index.js":{"offset":6269451,"length":2463,"mode":420},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/rache/package.json":{"offset":6271914,"length":602,"mode":420},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/ready-resource/index.js":{"offset":6272516,"length":1091,"mode":420},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/ready-resource/package.json":{"offset":6273607,"length":812,"mode":420},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/require-addon/lib/bare.js":{"offset":6274419,"length":45,"mode":420},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/require-addon/package.json":{"offset":6274464,"length":1449,"mode":420},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/resolve-reject-promise/index.js":{"offset":6275913,"length":443,"mode":420},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/resolve-reject-promise/package.json":{"offset":6276356,"length":581,"mode":420},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/safety-catch/index.js":{"offset":6276937,"length":543,"mode":420},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/safety-catch/package.json":{"offset":6277480,"length":547,"mode":420},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/signed-varint/index.js":{"offset":6278027,"length":435,"mode":420},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/signed-varint/package.json":{"offset":6278462,"length":523,"mode":420},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/sodium-native/binding.js":{"offset":6278985,"length":89,"mode":420},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/sodium-native/index.js":{"offset":6279074,"length":107894,"mode":420},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/sodium-native/package.json":{"offset":6386968,"length":1478,"mode":420},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/sodium-universal/index.js":{"offset":6388446,"length":42,"mode":420},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/sodium-universal/package.json":{"offset":6388488,"length":1217,"mode":420},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/streamx/index.js":{"offset":6389705,"length":34248,"mode":420},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/streamx/lib/errors.js":{"offset":6423953,"length":1096,"mode":420},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/streamx/package.json":{"offset":6425049,"length":1225,"mode":420},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/teex/index.js":{"offset":6426274,"length":1107,"mode":420},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/teex/package.json":{"offset":6427381,"length":608,"mode":420},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/text-decoder/index.js":{"offset":6427989,"length":1371,"mode":420},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/text-decoder/lib/pass-through-decoder.js":{"offset":6429360,"length":269,"mode":420},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/text-decoder/lib/utf8-decoder.js":{"offset":6429629,"length":4429,"mode":420},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/text-decoder/package.json":{"offset":6434058,"length":990,"mode":420},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/unslab/index.js":{"offset":6435048,"length":913,"mode":420},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/unslab/package.json":{"offset":6435961,"length":613,"mode":420},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/varint/decode.js":{"offset":6436574,"length":508,"mode":420},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/varint/encode.js":{"offset":6437082,"length":452,"mode":420},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/varint/index.js":{"offset":6437534,"length":134,"mode":420},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/varint/length.js":{"offset":6437668,"length":471,"mode":420},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/varint/package.json":{"offset":6438139,"length":511,"mode":420},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/which-runtime/index.js":{"offset":6438650,"length":1469,"mode":420},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/which-runtime/package.json":{"offset":6440119,"length":863,"mode":420},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/z32/index.js":{"offset":6440982,"length":2654,"mode":420},"/node_modules/favela-peer/node_modules/hyper-overlay/node_modules/z32/package.json":{"offset":6443636,"length":701,"mode":420},"/node_modules/favela-peer/node_modules/hyper-overlay/package.json":{"offset":6444337,"length":2132,"mode":420},"/node_modules/favela-peer/node_modules/hyperbee2/index.js":{"offset":6446469,"length":7859,"mode":420},"/node_modules/favela-peer/node_modules/hyperbee2/lib/cache.js":{"offset":6454328,"length":1946,"mode":420},"/node_modules/favela-peer/node_modules/hyperbee2/lib/changes.js":{"offset":6456274,"length":1895,"mode":420},"/node_modules/favela-peer/node_modules/hyperbee2/lib/compat.js":{"offset":6458169,"length":9370,"mode":420},"/node_modules/favela-peer/node_modules/hyperbee2/lib/compression.js":{"offset":6467539,"length":5400,"mode":420},"/node_modules/favela-peer/node_modules/hyperbee2/lib/context.js":{"offset":6472939,"length":7111,"mode":420},"/node_modules/favela-peer/node_modules/hyperbee2/lib/diff.js":{"offset":6480050,"length":3687,"mode":420},"/node_modules/favela-peer/node_modules/hyperbee2/lib/encoding.js":{"offset":6483737,"length":3065,"mode":420},"/node_modules/favela-peer/node_modules/hyperbee2/lib/inflate.js":{"offset":6486802,"length":4788,"mode":420},"/node_modules/favela-peer/node_modules/hyperbee2/lib/ranges.js":{"offset":6491590,"length":5120,"mode":420},"/node_modules/favela-peer/node_modules/hyperbee2/lib/session-config.js":{"offset":6496710,"length":1062,"mode":420},"/node_modules/favela-peer/node_modules/hyperbee2/lib/tree.js":{"offset":6497772,"length":6409,"mode":420},"/node_modules/favela-peer/node_modules/hyperbee2/lib/write.js":{"offset":6504181,"length":17092,"mode":420},"/node_modules/favela-peer/node_modules/hyperbee2/package.json":{"offset":6521273,"length":1256,"mode":420},"/node_modules/favela-peer/node_modules/hyperbee2/spec/hyperschema/index.js":{"offset":6522529,"length":15227,"mode":420},"/node_modules/favela-peer/node_modules/hypercore-crypto/index.js":{"offset":6537756,"length":5031,"mode":420},"/node_modules/favela-peer/node_modules/hypercore-crypto/package.json":{"offset":6542787,"length":1007,"mode":420},"/node_modules/favela-peer/node_modules/hypercore-errors/index.js":{"offset":6543794,"length":4896,"mode":420},"/node_modules/favela-peer/node_modules/hypercore-errors/package.json":{"offset":6548690,"length":688,"mode":420},"/node_modules/favela-peer/node_modules/hypercore-id-encoding/index.js":{"offset":6549378,"length":923,"mode":420},"/node_modules/favela-peer/node_modules/hypercore-id-encoding/package.json":{"offset":6550301,"length":799,"mode":420},"/node_modules/favela-peer/node_modules/hypercore-storage/encoding/external.js":{"offset":6551100,"length":264,"mode":420},"/node_modules/favela-peer/node_modules/hypercore-storage/encoding/spec/hyperschema/index.js":{"offset":6551364,"length":16796,"mode":420},"/node_modules/favela-peer/node_modules/hypercore-storage/index.js":{"offset":6568160,"length":33493,"mode":420},"/node_modules/favela-peer/node_modules/hypercore-storage/lib/block-dependency-stream.js":{"offset":6601653,"length":2557,"mode":420},"/node_modules/favela-peer/node_modules/hypercore-storage/lib/close-error-stream.js":{"offset":6604210,"length":276,"mode":420},"/node_modules/favela-peer/node_modules/hypercore-storage/lib/keys.js":{"offset":6604486,"length":8751,"mode":420},"/node_modules/favela-peer/node_modules/hypercore-storage/lib/streams.js":{"offset":6613237,"length":5686,"mode":420},"/node_modules/favela-peer/node_modules/hypercore-storage/lib/tx.js":{"offset":6618923,"length":11683,"mode":420},"/node_modules/favela-peer/node_modules/hypercore-storage/lib/view.js":{"offset":6630606,"length":8379,"mode":420},"/node_modules/favela-peer/node_modules/hypercore-storage/migrations/0/index.js":{"offset":6638985,"length":20125,"mode":420},"/node_modules/favela-peer/node_modules/hypercore-storage/migrations/0/messages.js":{"offset":6659110,"length":26402,"mode":420},"/node_modules/favela-peer/node_modules/hypercore-storage/migrations/1/index.js":{"offset":6685512,"length":1181,"mode":420},"/node_modules/favela-peer/node_modules/hypercore-storage/package.json":{"offset":6686693,"length":1827,"mode":420},"/node_modules/favela-peer/node_modules/hypercore/index.js":{"offset":6688520,"length":41043,"mode":420},"/node_modules/favela-peer/node_modules/hypercore/lib/audit.js":{"offset":6729563,"length":3834,"mode":420},"/node_modules/favela-peer/node_modules/hypercore/lib/bit-interlude.js":{"offset":6733397,"length":4257,"mode":420},"/node_modules/favela-peer/node_modules/hypercore/lib/bitfield.js":{"offset":6737654,"length":12115,"mode":420},"/node_modules/favela-peer/node_modules/hypercore/lib/caps.js":{"offset":6749769,"length":1537,"mode":420},"/node_modules/favela-peer/node_modules/hypercore/lib/compat.js":{"offset":6751306,"length":477,"mode":420},"/node_modules/favela-peer/node_modules/hypercore/lib/copy-prologue.js":{"offset":6751783,"length":5999,"mode":420},"/node_modules/favela-peer/node_modules/hypercore/lib/core.js":{"offset":6757782,"length":28389,"mode":420},"/node_modules/favela-peer/node_modules/hypercore/lib/default-encryption.js":{"offset":6786171,"length":3453,"mode":420},"/node_modules/favela-peer/node_modules/hypercore/lib/download.js":{"offset":6789624,"length":1278,"mode":420},"/node_modules/favela-peer/node_modules/hypercore/lib/feature-flags.js":{"offset":6790902,"length":38,"mode":420},"/node_modules/favela-peer/node_modules/hypercore/lib/fully-remote-proof.js":{"offset":6790940,"length":3470,"mode":420},"/node_modules/favela-peer/node_modules/hypercore/lib/hotswap-queue.js":{"offset":6794410,"length":1498,"mode":420},"/node_modules/favela-peer/node_modules/hypercore/lib/info.js":{"offset":6795908,"length":1517,"mode":420},"/node_modules/favela-peer/node_modules/hypercore/lib/inspect.js":{"offset":6797425,"length":1983,"mode":420},"/node_modules/favela-peer/node_modules/hypercore/lib/mark-bitfield.js":{"offset":6799408,"length":2707,"mode":420},"/node_modules/favela-peer/node_modules/hypercore/lib/merkle-tree.js":{"offset":6802115,"length":34353,"mode":420},"/node_modules/favela-peer/node_modules/hypercore/lib/messages.js":{"offset":6836468,"length":28313,"mode":420},"/node_modules/favela-peer/node_modules/hypercore/lib/multisig.js":{"offset":6864781,"length":2851,"mode":420},"/node_modules/favela-peer/node_modules/hypercore/lib/mutex.js":{"offset":6867632,"length":1028,"mode":420},"/node_modules/favela-peer/node_modules/hypercore/lib/receiver-queue.js":{"offset":6868660,"length":1413,"mode":420},"/node_modules/favela-peer/node_modules/hypercore/lib/remote-bitfield.js":{"offset":6870073,"length":8124,"mode":420},"/node_modules/favela-peer/node_modules/hypercore/lib/replicator.js":{"offset":6878197,"length":91358,"mode":420},"/node_modules/favela-peer/node_modules/hypercore/lib/session-state.js":{"offset":6969555,"length":36297,"mode":420},"/node_modules/favela-peer/node_modules/hypercore/lib/streams.js":{"offset":7005852,"length":3034,"mode":420},"/node_modules/favela-peer/node_modules/hypercore/lib/verifier.js":{"offset":7008886,"length":9990,"mode":420},"/node_modules/favela-peer/node_modules/hypercore/lib/wants.js":{"offset":7018876,"length":7204,"mode":420},"/node_modules/favela-peer/node_modules/hypercore/package.json":{"offset":7026080,"length":2196,"mode":420},"/node_modules/favela-peer/node_modules/hyperdht-address/index.js":{"offset":7028276,"length":357,"mode":420},"/node_modules/favela-peer/node_modules/hyperdht-address/package.json":{"offset":7028633,"length":1046,"mode":420},"/node_modules/favela-peer/node_modules/hyperdht-address/spec/hyperschema/index.js":{"offset":7029679,"length":2106,"mode":420},"/node_modules/favela-peer/node_modules/hyperdht/index.js":{"offset":7031785,"length":16732,"mode":420},"/node_modules/favela-peer/node_modules/hyperdht/lib/announcer.js":{"offset":7048517,"length":7533,"mode":420},"/node_modules/favela-peer/node_modules/hyperdht/lib/connect.js":{"offset":7056050,"length":25203,"mode":420},"/node_modules/favela-peer/node_modules/hyperdht/lib/connection-pool.js":{"offset":7081253,"length":3050,"mode":420},"/node_modules/favela-peer/node_modules/hyperdht/lib/constants.js":{"offset":7084303,"length":1178,"mode":420},"/node_modules/favela-peer/node_modules/hyperdht/lib/crypto.js":{"offset":7085481,"length":632,"mode":420},"/node_modules/favela-peer/node_modules/hyperdht/lib/encode.js":{"offset":7086113,"length":446,"mode":420},"/node_modules/favela-peer/node_modules/hyperdht/lib/errors.js":{"offset":7086559,"length":3713,"mode":420},"/node_modules/favela-peer/node_modules/hyperdht/lib/holepuncher.js":{"offset":7090272,"length":10003,"mode":420},"/node_modules/favela-peer/node_modules/hyperdht/lib/messages.js":{"offset":7100275,"length":12057,"mode":420},"/node_modules/favela-peer/node_modules/hyperdht/lib/nat.js":{"offset":7112332,"length":4714,"mode":420},"/node_modules/favela-peer/node_modules/hyperdht/lib/noise-wrap.js":{"offset":7117046,"length":1669,"mode":420},"/node_modules/favela-peer/node_modules/hyperdht/lib/persistent.js":{"offset":7118715,"length":8361,"mode":420},"/node_modules/favela-peer/node_modules/hyperdht/lib/raw-stream-set.js":{"offset":7127076,"length":1511,"mode":420},"/node_modules/favela-peer/node_modules/hyperdht/lib/relay-connection.js":{"offset":7128587,"length":2062,"mode":420},"/node_modules/favela-peer/node_modules/hyperdht/lib/router.js":{"offset":7130649,"length":6978,"mode":420},"/node_modules/favela-peer/node_modules/hyperdht/lib/secure-payload.js":{"offset":7137627,"length":1491,"mode":420},"/node_modules/favela-peer/node_modules/hyperdht/lib/semaphore.js":{"offset":7139118,"length":1577,"mode":420},"/node_modules/favela-peer/node_modules/hyperdht/lib/server.js":{"offset":7140695,"length":22554,"mode":420},"/node_modules/favela-peer/node_modules/hyperdht/lib/sleeper.js":{"offset":7163249,"length":690,"mode":420},"/node_modules/favela-peer/node_modules/hyperdht/lib/socket-pool.js":{"offset":7163939,"length":4507,"mode":420},"/node_modules/favela-peer/node_modules/hyperdht/package.json":{"offset":7168446,"length":2007,"mode":420},"/node_modules/favela-peer/node_modules/hyperident/lib/crypto.js":{"offset":7170453,"length":7586,"mode":420},"/node_modules/favela-peer/node_modules/hyperident/node_modules/b4a/index.js":{"offset":7178039,"length":4054,"mode":420},"/node_modules/favela-peer/node_modules/hyperident/node_modules/b4a/package.json":{"offset":7182093,"length":1140,"mode":420},"/node_modules/favela-peer/node_modules/hyperident/node_modules/bare-ansi-escapes/index.js":{"offset":7183233,"length":2255,"mode":420},"/node_modules/favela-peer/node_modules/hyperident/node_modules/bare-ansi-escapes/package.json":{"offset":7185488,"length":1178,"mode":420},"/node_modules/favela-peer/node_modules/hyperident/node_modules/bare-assert/index.js":{"offset":7186666,"length":2192,"mode":420},"/node_modules/favela-peer/node_modules/hyperident/node_modules/bare-assert/package.json":{"offset":7188858,"length":835,"mode":420},"/node_modules/favela-peer/node_modules/hyperident/node_modules/bare-crypto/binding.js":{"offset":7189693,"length":33,"mode":420},"/node_modules/favela-peer/node_modules/hyperident/node_modules/bare-crypto/index.js":{"offset":7189726,"length":1611,"mode":420},"/node_modules/favela-peer/node_modules/hyperident/node_modules/bare-crypto/lib/cipher.js":{"offset":7191337,"length":8314,"mode":420},"/node_modules/favela-peer/node_modules/hyperident/node_modules/bare-crypto/lib/constants.js":{"offset":7199651,"length":2504,"mode":420},"/node_modules/favela-peer/node_modules/hyperident/node_modules/bare-crypto/lib/errors.js":{"offset":7202155,"length":957,"mode":420},"/node_modules/favela-peer/node_modules/hyperident/node_modules/bare-crypto/lib/hash.js":{"offset":7203112,"length":2121,"mode":420},"/node_modules/favela-peer/node_modules/hyperident/node_modules/bare-crypto/lib/hmac.js":{"offset":7205233,"length":1346,"mode":420},"/node_modules/favela-peer/node_modules/hyperident/node_modules/bare-crypto/lib/key.js":{"offset":7206579,"length":1067,"mode":420},"/node_modules/favela-peer/node_modules/hyperident/node_modules/bare-crypto/lib/pbkdf2.js":{"offset":7207646,"length":945,"mode":420},"/node_modules/favela-peer/node_modules/hyperident/node_modules/bare-crypto/lib/random.js":{"offset":7208591,"length":2024,"mode":420},"/node_modules/favela-peer/node_modules/hyperident/node_modules/bare-crypto/lib/signature.js":{"offset":7210615,"length":1395,"mode":420},"/node_modules/favela-peer/node_modules/hyperident/node_modules/bare-crypto/lib/web/algorithm/ed25519.js":{"offset":7212010,"length":6918,"mode":420},"/node_modules/favela-peer/node_modules/hyperident/node_modules/bare-crypto/lib/web/algorithm/hmac.js":{"offset":7218928,"length":4925,"mode":420},"/node_modules/favela-peer/node_modules/hyperident/node_modules/bare-crypto/lib/web/algorithm/pbkdf2.js":{"offset":7223853,"length":1467,"mode":420},"/node_modules/favela-peer/node_modules/hyperident/node_modules/bare-crypto/lib/web/algorithm/sha.js":{"offset":7225320,"length":299,"mode":420},"/node_modules/favela-peer/node_modules/hyperident/node_modules/bare-crypto/lib/web/crypto-key.js":{"offset":7225619,"length":970,"mode":420},"/node_modules/favela-peer/node_modules/hyperident/node_modules/bare-crypto/package.json":{"offset":7226589,"length":1504,"mode":420},"/node_modules/favela-peer/node_modules/hyperident/node_modules/bare-crypto/web.js":{"offset":7228093,"length":7537,"mode":420},"/node_modules/favela-peer/node_modules/hyperident/node_modules/bare-events/index.js":{"offset":7235630,"length":8033,"mode":420},"/node_modules/favela-peer/node_modules/hyperident/node_modules/bare-events/lib/errors.js":{"offset":7243663,"length":671,"mode":420},"/node_modules/favela-peer/node_modules/hyperident/node_modules/bare-events/package.json":{"offset":7244334,"length":1399,"mode":420},"/node_modules/favela-peer/node_modules/hyperident/node_modules/bare-inspect/binding.js":{"offset":7245733,"length":33,"mode":420},"/node_modules/favela-peer/node_modules/hyperident/node_modules/bare-inspect/index.js":{"offset":7245766,"length":23856,"mode":420},"/node_modules/favela-peer/node_modules/hyperident/node_modules/bare-inspect/package.json":{"offset":7269622,"length":1041,"mode":420},"/node_modules/favela-peer/node_modules/hyperident/node_modules/bare-stream/index.js":{"offset":7270663,"length":10913,"mode":420},"/node_modules/favela-peer/node_modules/hyperident/node_modules/bare-stream/package.json":{"offset":7281576,"length":1579,"mode":420},"/node_modules/favela-peer/node_modules/hyperident/node_modules/bare-stream/web.js":{"offset":7283155,"length":13712,"mode":420},"/node_modules/favela-peer/node_modules/hyperident/node_modules/bare-type/binding.js":{"offset":7296867,"length":33,"mode":420},"/node_modules/favela-peer/node_modules/hyperident/node_modules/bare-type/index.js":{"offset":7296900,"length":4566,"mode":420},"/node_modules/favela-peer/node_modules/hyperident/node_modules/bare-type/package.json":{"offset":7301466,"length":938,"mode":420},"/node_modules/favela-peer/node_modules/hyperident/node_modules/events-universal/bare.js":{"offset":7302404,"length":40,"mode":420},"/node_modules/favela-peer/node_modules/hyperident/node_modules/events-universal/package.json":{"offset":7302444,"length":908,"mode":420},"/node_modules/favela-peer/node_modules/hyperident/node_modules/fast-fifo/fixed-size.js":{"offset":7303352,"length":875,"mode":420},"/node_modules/favela-peer/node_modules/hyperident/node_modules/fast-fifo/index.js":{"offset":7304227,"length":972,"mode":420},"/node_modules/favela-peer/node_modules/hyperident/node_modules/fast-fifo/package.json":{"offset":7305199,"length":682,"mode":420},"/node_modules/favela-peer/node_modules/hyperident/node_modules/require-addon/lib/bare.js":{"offset":7305881,"length":45,"mode":420},"/node_modules/favela-peer/node_modules/hyperident/node_modules/require-addon/package.json":{"offset":7305926,"length":1449,"mode":420},"/node_modules/favela-peer/node_modules/hyperident/node_modules/sodium-native/binding.js":{"offset":7307375,"length":89,"mode":420},"/node_modules/favela-peer/node_modules/hyperident/node_modules/sodium-native/index.js":{"offset":7307464,"length":107894,"mode":420},"/node_modules/favela-peer/node_modules/hyperident/node_modules/sodium-native/package.json":{"offset":7415358,"length":1478,"mode":420},"/node_modules/favela-peer/node_modules/hyperident/node_modules/sodium-universal/index.js":{"offset":7416836,"length":42,"mode":420},"/node_modules/favela-peer/node_modules/hyperident/node_modules/sodium-universal/package.json":{"offset":7416878,"length":1217,"mode":420},"/node_modules/favela-peer/node_modules/hyperident/node_modules/streamx/index.js":{"offset":7418095,"length":33510,"mode":420},"/node_modules/favela-peer/node_modules/hyperident/node_modules/streamx/package.json":{"offset":7451605,"length":912,"mode":420},"/node_modules/favela-peer/node_modules/hyperident/node_modules/teex/index.js":{"offset":7452517,"length":1107,"mode":420},"/node_modules/favela-peer/node_modules/hyperident/node_modules/teex/package.json":{"offset":7453624,"length":608,"mode":420},"/node_modules/favela-peer/node_modules/hyperident/node_modules/text-decoder/index.js":{"offset":7454232,"length":1371,"mode":420},"/node_modules/favela-peer/node_modules/hyperident/node_modules/text-decoder/lib/pass-through-decoder.js":{"offset":7455603,"length":269,"mode":420},"/node_modules/favela-peer/node_modules/hyperident/node_modules/text-decoder/lib/utf8-decoder.js":{"offset":7455872,"length":4429,"mode":420},"/node_modules/favela-peer/node_modules/hyperident/node_modules/text-decoder/package.json":{"offset":7460301,"length":990,"mode":420},"/node_modules/favela-peer/node_modules/hyperident/node_modules/which-runtime/index.js":{"offset":7461291,"length":1231,"mode":420},"/node_modules/favela-peer/node_modules/hyperident/node_modules/which-runtime/package.json":{"offset":7462522,"length":602,"mode":420},"/node_modules/favela-peer/node_modules/hyperident/package.json":{"offset":7463124,"length":1779,"mode":420},"/node_modules/favela-peer/node_modules/hyperschema/package.json":{"offset":7464903,"length":1642,"mode":420},"/node_modules/favela-peer/node_modules/hyperschema/runtime.cjs":{"offset":7466545,"length":54,"mode":420},"/node_modules/favela-peer/node_modules/hyperswarm/index.js":{"offset":7466599,"length":18589,"mode":420},"/node_modules/favela-peer/node_modules/hyperswarm/lib/bulk-timer.js":{"offset":7485188,"length":728,"mode":420},"/node_modules/favela-peer/node_modules/hyperswarm/lib/connection-set.js":{"offset":7485916,"length":823,"mode":420},"/node_modules/favela-peer/node_modules/hyperswarm/lib/peer-discovery.js":{"offset":7486739,"length":9296,"mode":420},"/node_modules/favela-peer/node_modules/hyperswarm/lib/peer-info.js":{"offset":7496035,"length":2688,"mode":420},"/node_modules/favela-peer/node_modules/hyperswarm/lib/retry-timer.js":{"offset":7498723,"length":1889,"mode":420},"/node_modules/favela-peer/node_modules/hyperswarm/package.json":{"offset":7500612,"length":1515,"mode":420},"/node_modules/favela-peer/node_modules/index-encoder/index.js":{"offset":7502127,"length":10596,"mode":420},"/node_modules/favela-peer/node_modules/index-encoder/package.json":{"offset":7512723,"length":928,"mode":420},"/node_modules/favela-peer/node_modules/is-options/index.js":{"offset":7513651,"length":140,"mode":420},"/node_modules/favela-peer/node_modules/is-options/package.json":{"offset":7513791,"length":605,"mode":420},"/node_modules/favela-peer/node_modules/kademlia-routing-table/index.js":{"offset":7514396,"length":4145,"mode":420},"/node_modules/favela-peer/node_modules/kademlia-routing-table/package.json":{"offset":7518541,"length":967,"mode":420},"/node_modules/favela-peer/node_modules/keet-identity-key/index.js":{"offset":7519508,"length":6320,"mode":420},"/node_modules/favela-peer/node_modules/keet-identity-key/lib/crypto.js":{"offset":7525828,"length":552,"mode":420},"/node_modules/favela-peer/node_modules/keet-identity-key/lib/encoding.js":{"offset":7526380,"length":3695,"mode":420},"/node_modules/favela-peer/node_modules/keet-identity-key/lib/keychain.js":{"offset":7530075,"length":3080,"mode":420},"/node_modules/favela-peer/node_modules/keet-identity-key/package.json":{"offset":7533155,"length":887,"mode":420},"/node_modules/favela-peer/node_modules/nanoassert/index.js":{"offset":7534042,"length":438,"mode":420},"/node_modules/favela-peer/node_modules/nanoassert/package.json":{"offset":7534480,"length":647,"mode":420},"/node_modules/favela-peer/node_modules/nat-sampler/index.js":{"offset":7535127,"length":1550,"mode":420},"/node_modules/favela-peer/node_modules/nat-sampler/package.json":{"offset":7536677,"length":608,"mode":420},"/node_modules/favela-peer/node_modules/noise-curve-ed/index.js":{"offset":7537285,"length":1642,"mode":420},"/node_modules/favela-peer/node_modules/noise-curve-ed/package.json":{"offset":7538927,"length":808,"mode":420},"/node_modules/favela-peer/node_modules/noise-handshake/cipher.js":{"offset":7539735,"length":2817,"mode":420},"/node_modules/favela-peer/node_modules/noise-handshake/dh.js":{"offset":7542552,"length":1439,"mode":420},"/node_modules/favela-peer/node_modules/noise-handshake/hkdf.js":{"offset":7543991,"length":1092,"mode":420},"/node_modules/favela-peer/node_modules/noise-handshake/hmac.js":{"offset":7545083,"length":1273,"mode":420},"/node_modules/favela-peer/node_modules/noise-handshake/noise.js":{"offset":7546356,"length":6437,"mode":420},"/node_modules/favela-peer/node_modules/noise-handshake/package.json":{"offset":7552793,"length":638,"mode":420},"/node_modules/favela-peer/node_modules/noise-handshake/symmetric-state.js":{"offset":7553431,"length":2206,"mode":420},"/node_modules/favela-peer/node_modules/protocol-buffers-encodings/index.js":{"offset":7555637,"length":6489,"mode":420},"/node_modules/favela-peer/node_modules/protocol-buffers-encodings/package.json":{"offset":7562126,"length":719,"mode":420},"/node_modules/favela-peer/node_modules/protomux-wakeup/index.js":{"offset":7562845,"length":16824,"mode":420},"/node_modules/favela-peer/node_modules/protomux-wakeup/package.json":{"offset":7579669,"length":972,"mode":420},"/node_modules/favela-peer/node_modules/protomux-wakeup/spec/hyperschema/index.js":{"offset":7580641,"length":3335,"mode":420},"/node_modules/favela-peer/node_modules/protomux/index.js":{"offset":7583976,"length":19741,"mode":420},"/node_modules/favela-peer/node_modules/protomux/package.json":{"offset":7603717,"length":1059,"mode":420},"/node_modules/favela-peer/node_modules/queue-tick/package.json":{"offset":7604776,"length":669,"mode":420},"/node_modules/favela-peer/node_modules/queue-tick/process-next-tick.js":{"offset":7605445,"length":160,"mode":420},"/node_modules/favela-peer/node_modules/queue-tick/queue-microtask.js":{"offset":7605605,"length":108,"mode":420},"/node_modules/favela-peer/node_modules/quickbit-native/binding.js":{"offset":7605713,"length":90,"mode":420},"/node_modules/favela-peer/node_modules/quickbit-native/index.js":{"offset":7605803,"length":4683,"mode":420},"/node_modules/favela-peer/node_modules/quickbit-native/package.json":{"offset":7610486,"length":1104,"mode":420},"/node_modules/favela-peer/node_modules/quickbit-universal/fallback.js":{"offset":7611590,"length":10204,"mode":420},"/node_modules/favela-peer/node_modules/quickbit-universal/index.js":{"offset":7621794,"length":442,"mode":420},"/node_modules/favela-peer/node_modules/quickbit-universal/package.json":{"offset":7622236,"length":908,"mode":420},"/node_modules/favela-peer/node_modules/random-array-iterator/index.js":{"offset":7623144,"length":1001,"mode":420},"/node_modules/favela-peer/node_modules/random-array-iterator/package.json":{"offset":7624145,"length":700,"mode":420},"/node_modules/favela-peer/node_modules/ready-guard/index.js":{"offset":7624845,"length":645,"mode":420},"/node_modules/favela-peer/node_modules/ready-guard/package.json":{"offset":7625490,"length":842,"mode":420},"/node_modules/favela-peer/node_modules/ready-resource/index.js":{"offset":7626332,"length":1091,"mode":420},"/node_modules/favela-peer/node_modules/ready-resource/package.json":{"offset":7627423,"length":812,"mode":420},"/node_modules/favela-peer/node_modules/record-cache/index.js":{"offset":7628235,"length":3668,"mode":420},"/node_modules/favela-peer/node_modules/record-cache/package.json":{"offset":7631903,"length":612,"mode":420},"/node_modules/favela-peer/node_modules/refcounter/index.js":{"offset":7632515,"length":611,"mode":420},"/node_modules/favela-peer/node_modules/refcounter/package.json":{"offset":7633126,"length":451,"mode":420},"/node_modules/favela-peer/node_modules/require-addon/lib/bare.js":{"offset":7633577,"length":45,"mode":420},"/node_modules/favela-peer/node_modules/require-addon/package.json":{"offset":7633622,"length":1449,"mode":420},"/node_modules/favela-peer/node_modules/resolve-reject-promise/index.js":{"offset":7635071,"length":443,"mode":420},"/node_modules/favela-peer/node_modules/resolve-reject-promise/package.json":{"offset":7635514,"length":581,"mode":420},"/node_modules/favela-peer/node_modules/resource-on-exit/index.js":{"offset":7636095,"length":361,"mode":420},"/node_modules/favela-peer/node_modules/resource-on-exit/package.json":{"offset":7636456,"length":496,"mode":420},"/node_modules/favela-peer/node_modules/rocksdb-native/binding.js":{"offset":7636952,"length":90,"mode":420},"/node_modules/favela-peer/node_modules/rocksdb-native/index.js":{"offset":7637042,"length":5304,"mode":420},"/node_modules/favela-peer/node_modules/rocksdb-native/lib/batch.js":{"offset":7642346,"length":9605,"mode":420},"/node_modules/favela-peer/node_modules/rocksdb-native/lib/column-family.js":{"offset":7651951,"length":2930,"mode":420},"/node_modules/favela-peer/node_modules/rocksdb-native/lib/constants.js":{"offset":7654881,"length":621,"mode":420},"/node_modules/favela-peer/node_modules/rocksdb-native/lib/filter-policy.js":{"offset":7655502,"length":437,"mode":420},"/node_modules/favela-peer/node_modules/rocksdb-native/lib/iterator.js":{"offset":7655939,"length":4311,"mode":420},"/node_modules/favela-peer/node_modules/rocksdb-native/lib/snapshot.js":{"offset":7660250,"length":503,"mode":420},"/node_modules/favela-peer/node_modules/rocksdb-native/lib/state.js":{"offset":7660753,"length":13334,"mode":420},"/node_modules/favela-peer/node_modules/rocksdb-native/package.json":{"offset":7674087,"length":1863,"mode":420},"/node_modules/favela-peer/node_modules/safety-catch/index.js":{"offset":7675950,"length":543,"mode":420},"/node_modules/favela-peer/node_modules/safety-catch/package.json":{"offset":7676493,"length":547,"mode":420},"/node_modules/favela-peer/node_modules/scope-lock/index.js":{"offset":7677040,"length":1821,"mode":420},"/node_modules/favela-peer/node_modules/scope-lock/package.json":{"offset":7678861,"length":611,"mode":420},"/node_modules/favela-peer/node_modules/shuffled-priority-queue/index.js":{"offset":7679472,"length":2607,"mode":420},"/node_modules/favela-peer/node_modules/shuffled-priority-queue/package.json":{"offset":7682079,"length":691,"mode":420},"/node_modules/favela-peer/node_modules/signal-promise/index.js":{"offset":7682770,"length":1252,"mode":420},"/node_modules/favela-peer/node_modules/signal-promise/package.json":{"offset":7684022,"length":503,"mode":420},"/node_modules/favela-peer/node_modules/signed-varint/index.js":{"offset":7684525,"length":435,"mode":420},"/node_modules/favela-peer/node_modules/signed-varint/package.json":{"offset":7684960,"length":523,"mode":420},"/node_modules/favela-peer/node_modules/simdle-native/binding.js":{"offset":7685483,"length":90,"mode":420},"/node_modules/favela-peer/node_modules/simdle-native/index.js":{"offset":7685573,"length":3474,"mode":420},"/node_modules/favela-peer/node_modules/simdle-native/package.json":{"offset":7689047,"length":1115,"mode":420},"/node_modules/favela-peer/node_modules/simdle-universal/fallback.js":{"offset":7690162,"length":5137,"mode":420},"/node_modules/favela-peer/node_modules/simdle-universal/index.js":{"offset":7695299,"length":103,"mode":420},"/node_modules/favela-peer/node_modules/simdle-universal/package.json":{"offset":7695402,"length":879,"mode":420},"/node_modules/favela-peer/node_modules/simdle-universal/scalar.js":{"offset":7696281,"length":469,"mode":420},"/node_modules/favela-peer/node_modules/sodium-hmac/hash/sha256.js":{"offset":7696750,"length":284,"mode":420},"/node_modules/favela-peer/node_modules/sodium-hmac/hash/sha512.js":{"offset":7697034,"length":284,"mode":420},"/node_modules/favela-peer/node_modules/sodium-hmac/index.js":{"offset":7697318,"length":2063,"mode":420},"/node_modules/favela-peer/node_modules/sodium-hmac/package.json":{"offset":7699381,"length":670,"mode":420},"/node_modules/favela-peer/node_modules/sodium-native/binding.js":{"offset":7700051,"length":89,"mode":420},"/node_modules/favela-peer/node_modules/sodium-native/index.js":{"offset":7700140,"length":107894,"mode":420},"/node_modules/favela-peer/node_modules/sodium-native/package.json":{"offset":7808034,"length":1478,"mode":420},"/node_modules/favela-peer/node_modules/sodium-secretstream/index.js":{"offset":7809512,"length":2257,"mode":420},"/node_modules/favela-peer/node_modules/sodium-secretstream/package.json":{"offset":7811769,"length":657,"mode":420},"/node_modules/favela-peer/node_modules/sodium-universal/index.js":{"offset":7812426,"length":42,"mode":420},"/node_modules/favela-peer/node_modules/sodium-universal/package.json":{"offset":7812468,"length":1217,"mode":420},"/node_modules/favela-peer/node_modules/streamx/index.js":{"offset":7813685,"length":34248,"mode":420},"/node_modules/favela-peer/node_modules/streamx/lib/errors.js":{"offset":7847933,"length":1096,"mode":420},"/node_modules/favela-peer/node_modules/streamx/package.json":{"offset":7849029,"length":1225,"mode":420},"/node_modules/favela-peer/node_modules/teex/index.js":{"offset":7850254,"length":1107,"mode":420},"/node_modules/favela-peer/node_modules/teex/package.json":{"offset":7851361,"length":608,"mode":420},"/node_modules/favela-peer/node_modules/text-decoder/index.js":{"offset":7851969,"length":1371,"mode":420},"/node_modules/favela-peer/node_modules/text-decoder/lib/pass-through-decoder.js":{"offset":7853340,"length":269,"mode":420},"/node_modules/favela-peer/node_modules/text-decoder/lib/utf8-decoder.js":{"offset":7853609,"length":4429,"mode":420},"/node_modules/favela-peer/node_modules/text-decoder/package.json":{"offset":7858038,"length":990,"mode":420},"/node_modules/favela-peer/node_modules/time-ordered-set/index.js":{"offset":7859028,"length":1444,"mode":420},"/node_modules/favela-peer/node_modules/time-ordered-set/package.json":{"offset":7860472,"length":666,"mode":420},"/node_modules/favela-peer/node_modules/timeout-refresh/browser.js":{"offset":7861138,"length":1098,"mode":420},"/node_modules/favela-peer/node_modules/timeout-refresh/index.js":{"offset":7862236,"length":184,"mode":420},"/node_modules/favela-peer/node_modules/timeout-refresh/node.js":{"offset":7862420,"length":928,"mode":420},"/node_modules/favela-peer/node_modules/timeout-refresh/package.json":{"offset":7863348,"length":619,"mode":420},"/node_modules/favela-peer/node_modules/udx-native/binding.js":{"offset":7863967,"length":90,"mode":420},"/node_modules/favela-peer/node_modules/udx-native/lib/ip.js":{"offset":7864057,"length":2204,"mode":420},"/node_modules/favela-peer/node_modules/udx-native/lib/network-interfaces.js":{"offset":7866261,"length":1341,"mode":420},"/node_modules/favela-peer/node_modules/udx-native/lib/socket.js":{"offset":7867602,"length":7557,"mode":420},"/node_modules/favela-peer/node_modules/udx-native/lib/stream.js":{"offset":7875159,"length":13050,"mode":420},"/node_modules/favela-peer/node_modules/udx-native/lib/udx.js":{"offset":7888209,"length":2868,"mode":420},"/node_modules/favela-peer/node_modules/udx-native/package.json":{"offset":7891077,"length":1665,"mode":420},"/node_modules/favela-peer/node_modules/unordered-set/index.js":{"offset":7892742,"length":677,"mode":420},"/node_modules/favela-peer/node_modules/unordered-set/package.json":{"offset":7893419,"length":654,"mode":420},"/node_modules/favela-peer/node_modules/unslab/index.js":{"offset":7894073,"length":913,"mode":420},"/node_modules/favela-peer/node_modules/unslab/package.json":{"offset":7894986,"length":613,"mode":420},"/node_modules/favela-peer/node_modules/varint/decode.js":{"offset":7895599,"length":508,"mode":420},"/node_modules/favela-peer/node_modules/varint/encode.js":{"offset":7896107,"length":452,"mode":420},"/node_modules/favela-peer/node_modules/varint/index.js":{"offset":7896559,"length":134,"mode":420},"/node_modules/favela-peer/node_modules/varint/length.js":{"offset":7896693,"length":471,"mode":420},"/node_modules/favela-peer/node_modules/varint/package.json":{"offset":7897164,"length":511,"mode":420},"/node_modules/favela-peer/node_modules/which-runtime/index.js":{"offset":7897675,"length":1469,"mode":420},"/node_modules/favela-peer/node_modules/which-runtime/package.json":{"offset":7899144,"length":863,"mode":420},"/node_modules/favela-peer/node_modules/xache/index.js":{"offset":7900007,"length":2477,"mode":420},"/node_modules/favela-peer/node_modules/xache/package.json":{"offset":7902484,"length":672,"mode":420},"/node_modules/favela-peer/node_modules/z32/index.js":{"offset":7903156,"length":2654,"mode":420},"/node_modules/favela-peer/node_modules/z32/package.json":{"offset":7905810,"length":701,"mode":420},"/node_modules/favela-peer/package.json":{"offset":7906511,"length":2116,"mode":420},"/node_modules/favela-surface/lib/multipart.js":{"offset":7908627,"length":3295,"mode":420},"/node_modules/favela-surface/lib/pages.js":{"offset":7911922,"length":109101,"mode":420},"/node_modules/favela-surface/lib/router.js":{"offset":8021023,"length":55465,"mode":420},"/node_modules/favela-surface/lib/session.js":{"offset":8076488,"length":5264,"mode":420},"/node_modules/favela-surface/node_modules/b4a/index.js":{"offset":8081752,"length":4054,"mode":420},"/node_modules/favela-surface/node_modules/b4a/package.json":{"offset":8085806,"length":1245,"mode":420},"/node_modules/favela-surface/node_modules/bare-ansi-escapes/index.js":{"offset":8087051,"length":2255,"mode":420},"/node_modules/favela-surface/node_modules/bare-ansi-escapes/package.json":{"offset":8089306,"length":1178,"mode":420},"/node_modules/favela-surface/node_modules/bare-assert/index.js":{"offset":8090484,"length":2192,"mode":420},"/node_modules/favela-surface/node_modules/bare-assert/package.json":{"offset":8092676,"length":835,"mode":420},"/node_modules/favela-surface/node_modules/bare-crypto/binding.js":{"offset":8093511,"length":33,"mode":420},"/node_modules/favela-surface/node_modules/bare-crypto/index.js":{"offset":8093544,"length":1706,"mode":420},"/node_modules/favela-surface/node_modules/bare-crypto/lib/cipher.js":{"offset":8095250,"length":8357,"mode":420},"/node_modules/favela-surface/node_modules/bare-crypto/lib/constants.js":{"offset":8103607,"length":2504,"mode":420},"/node_modules/favela-surface/node_modules/bare-crypto/lib/errors.js":{"offset":8106111,"length":957,"mode":420},"/node_modules/favela-surface/node_modules/bare-crypto/lib/hash.js":{"offset":8107068,"length":2121,"mode":420},"/node_modules/favela-surface/node_modules/bare-crypto/lib/hmac.js":{"offset":8109189,"length":1311,"mode":420},"/node_modules/favela-surface/node_modules/bare-crypto/lib/key.js":{"offset":8110500,"length":2535,"mode":420},"/node_modules/favela-surface/node_modules/bare-crypto/lib/pbkdf2.js":{"offset":8113035,"length":1059,"mode":420},"/node_modules/favela-surface/node_modules/bare-crypto/lib/random.js":{"offset":8114094,"length":2014,"mode":420},"/node_modules/favela-surface/node_modules/bare-crypto/lib/signature.js":{"offset":8116108,"length":1369,"mode":420},"/node_modules/favela-surface/node_modules/bare-crypto/lib/timing.js":{"offset":8117477,"length":660,"mode":420},"/node_modules/favela-surface/node_modules/bare-crypto/lib/web/algorithm/ed25519.js":{"offset":8118137,"length":7039,"mode":420},"/node_modules/favela-surface/node_modules/bare-crypto/lib/web/algorithm/hmac.js":{"offset":8125176,"length":5356,"mode":420},"/node_modules/favela-surface/node_modules/bare-crypto/lib/web/algorithm/pbkdf2.js":{"offset":8130532,"length":1563,"mode":420},"/node_modules/favela-surface/node_modules/bare-crypto/lib/web/algorithm/sha.js":{"offset":8132095,"length":299,"mode":420},"/node_modules/favela-surface/node_modules/bare-crypto/lib/web/crypto-key.js":{"offset":8132394,"length":1185,"mode":420},"/node_modules/favela-surface/node_modules/bare-crypto/package.json":{"offset":8133579,"length":1618,"mode":420},"/node_modules/favela-surface/node_modules/bare-crypto/web.js":{"offset":8135197,"length":7537,"mode":420},"/node_modules/favela-surface/node_modules/bare-events/index.js":{"offset":8142734,"length":8885,"mode":420},"/node_modules/favela-surface/node_modules/bare-events/lib/errors.js":{"offset":8151619,"length":634,"mode":420},"/node_modules/favela-surface/node_modules/bare-events/package.json":{"offset":8152253,"length":1504,"mode":420},"/node_modules/favela-surface/node_modules/bare-fs/binding.js":{"offset":8153757,"length":33,"mode":420},"/node_modules/favela-surface/node_modules/bare-fs/index.js":{"offset":8153790,"length":59172,"mode":420},"/node_modules/favela-surface/node_modules/bare-fs/lib/constants.js":{"offset":8212962,"length":1494,"mode":420},"/node_modules/favela-surface/node_modules/bare-fs/lib/errors.js":{"offset":8214456,"length":1166,"mode":420},"/node_modules/favela-surface/node_modules/bare-fs/package.json":{"offset":8215622,"length":1654,"mode":420},"/node_modules/favela-surface/node_modules/bare-fs/promises.js":{"offset":8217276,"length":2401,"mode":420},"/node_modules/favela-surface/node_modules/bare-inspect/binding.js":{"offset":8219677,"length":33,"mode":420},"/node_modules/favela-surface/node_modules/bare-inspect/index.js":{"offset":8219710,"length":28811,"mode":420},"/node_modules/favela-surface/node_modules/bare-inspect/package.json":{"offset":8248521,"length":1157,"mode":420},"/node_modules/favela-surface/node_modules/bare-os/binding.js":{"offset":8249678,"length":33,"mode":420},"/node_modules/favela-surface/node_modules/bare-os/index.js":{"offset":8249711,"length":5216,"mode":420},"/node_modules/favela-surface/node_modules/bare-os/lib/constants.js":{"offset":8254927,"length":143,"mode":420},"/node_modules/favela-surface/node_modules/bare-os/lib/errors.js":{"offset":8255070,"length":453,"mode":420},"/node_modules/favela-surface/node_modules/bare-os/package.json":{"offset":8255523,"length":1257,"mode":420},"/node_modules/favela-surface/node_modules/bare-path/binding.js":{"offset":8256780,"length":33,"mode":420},"/node_modules/favela-surface/node_modules/bare-path/index.js":{"offset":8256813,"length":287,"mode":420},"/node_modules/favela-surface/node_modules/bare-path/lib/constants.js":{"offset":8257100,"length":247,"mode":420},"/node_modules/favela-surface/node_modules/bare-path/lib/posix.js":{"offset":8257347,"length":6399,"mode":420},"/node_modules/favela-surface/node_modules/bare-path/lib/shared.js":{"offset":8263746,"length":2148,"mode":420},"/node_modules/favela-surface/node_modules/bare-path/lib/win32.js":{"offset":8265894,"length":13987,"mode":420},"/node_modules/favela-surface/node_modules/bare-path/package.json":{"offset":8279881,"length":1106,"mode":420},"/node_modules/favela-surface/node_modules/bare-stream/index.js":{"offset":8280987,"length":10904,"mode":420},"/node_modules/favela-surface/node_modules/bare-stream/package.json":{"offset":8291891,"length":1676,"mode":420},"/node_modules/favela-surface/node_modules/bare-stream/web.js":{"offset":8293567,"length":13980,"mode":420},"/node_modules/favela-surface/node_modules/bare-type/binding.js":{"offset":8307547,"length":33,"mode":420},"/node_modules/favela-surface/node_modules/bare-type/index.js":{"offset":8307580,"length":4719,"mode":420},"/node_modules/favela-surface/node_modules/bare-type/package.json":{"offset":8312299,"length":1053,"mode":420},"/node_modules/favela-surface/node_modules/bare-url/binding.js":{"offset":8313352,"length":33,"mode":420},"/node_modules/favela-surface/node_modules/bare-url/index.js":{"offset":8313385,"length":17970,"mode":420},"/node_modules/favela-surface/node_modules/bare-url/lib/errors.js":{"offset":8331355,"length":778,"mode":420},"/node_modules/favela-surface/node_modules/bare-url/lib/url-search-params.js":{"offset":8332133,"length":7104,"mode":420},"/node_modules/favela-surface/node_modules/bare-url/package.json":{"offset":8339237,"length":1219,"mode":420},"/node_modules/favela-surface/node_modules/events-universal/bare.js":{"offset":8340456,"length":40,"mode":420},"/node_modules/favela-surface/node_modules/events-universal/package.json":{"offset":8340496,"length":908,"mode":420},"/node_modules/favela-surface/node_modules/fast-fifo/fixed-size.js":{"offset":8341404,"length":875,"mode":420},"/node_modules/favela-surface/node_modules/fast-fifo/index.js":{"offset":8342279,"length":972,"mode":420},"/node_modules/favela-surface/node_modules/fast-fifo/package.json":{"offset":8343251,"length":682,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/lib/dialect.js":{"offset":8343933,"length":3478,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/lib/render.js":{"offset":8347411,"length":14215,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/lib/uri.js":{"offset":8361626,"length":5592,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/ccount/index.js":{"offset":8367218,"length":619,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/ccount/package.json":{"offset":8367837,"length":1839,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/character-entities-html4/index.js":{"offset":8369676,"length":3908,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/character-entities-html4/package.json":{"offset":8373584,"length":2057,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/character-entities-legacy/index.js":{"offset":8375641,"length":1316,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/character-entities-legacy/package.json":{"offset":8376957,"length":2068,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/character-entities/index.js":{"offset":8379025,"length":37342,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/character-entities/package.json":{"offset":8416367,"length":2014,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/character-reference-invalid/index.js":{"offset":8418381,"length":558,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/character-reference-invalid/package.json":{"offset":8418939,"length":2233,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/decode-named-character-reference/index.js":{"offset":8421172,"length":634,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/decode-named-character-reference/package.json":{"offset":8421806,"length":2331,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/devlop/lib/default.js":{"offset":8424137,"length":133,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/devlop/package.json":{"offset":8424270,"length":2114,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/is-alphabetical/index.js":{"offset":8426384,"length":445,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/is-alphabetical/package.json":{"offset":8426829,"length":1873,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/is-alphanumerical/index.js":{"offset":8428702,"length":406,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/is-alphanumerical/package.json":{"offset":8429108,"length":2005,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/is-decimal/index.js":{"offset":8431113,"length":374,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/is-decimal/package.json":{"offset":8431487,"length":1848,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/is-hexadecimal/index.js":{"offset":8433335,"length":503,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/is-hexadecimal/package.json":{"offset":8433838,"length":1868,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/longest-streak/index.js":{"offset":8435706,"length":823,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/longest-streak/package.json":{"offset":8436529,"length":1819,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/markdown-table/index.js":{"offset":8438348,"length":10270,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/markdown-table/package.json":{"offset":8448618,"length":1903,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-directive/index.js":{"offset":8450521,"length":116,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-directive/lib/index.js":{"offset":8450637,"length":13110,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-directive/package.json":{"offset":8463747,"length":2993,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-from-markdown/index.js":{"offset":8466740,"length":89,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-from-markdown/lib/index.js":{"offset":8466829,"length":29022,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-from-markdown/package.json":{"offset":8495851,"length":2839,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-gfm-footnote/index.js":{"offset":8498690,"length":127,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-gfm-footnote/lib/index.js":{"offset":8498817,"length":5112,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-gfm-footnote/package.json":{"offset":8503929,"length":2796,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-gfm-strikethrough/index.js":{"offset":8506725,"length":132,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-gfm-strikethrough/lib/index.js":{"offset":8506857,"length":2661,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-gfm-strikethrough/package.json":{"offset":8509518,"length":2516,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-gfm-table/index.js":{"offset":8512034,"length":114,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-gfm-table/lib/index.js":{"offset":8512148,"length":8185,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-gfm-table/package.json":{"offset":8520333,"length":2537,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-phrasing/index.js":{"offset":8522870,"length":40,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-phrasing/lib/index.js":{"offset":8522910,"length":1072,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-phrasing/package.json":{"offset":8523982,"length":1970,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/index.js":{"offset":8525952,"length":154,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/configure.js":{"offset":8526106,"length":1429,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/handle/blockquote.js":{"offset":8527535,"length":645,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/handle/break.js":{"offset":8528180,"length":705,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/handle/code.js":{"offset":8528885,"length":1757,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/handle/definition.js":{"offset":8530642,"length":1761,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/handle/emphasis.js":{"offset":8532403,"length":1671,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/handle/heading.js":{"offset":8534074,"length":2033,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/handle/html.js":{"offset":8536107,"length":237,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/handle/image-reference.js":{"offset":8536344,"length":1647,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/handle/image.js":{"offset":8537991,"length":1853,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/handle/index.js":{"offset":8539844,"length":1040,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/handle/inline-code.js":{"offset":8540884,"length":2146,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/handle/link-reference.js":{"offset":8543030,"length":1652,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/handle/link.js":{"offset":8544682,"length":2663,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/handle/list-item.js":{"offset":8547345,"length":1614,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/handle/list.js":{"offset":8548959,"length":2961,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/handle/paragraph.js":{"offset":8551920,"length":467,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/handle/root.js":{"offset":8552387,"length":575,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/handle/strong.js":{"offset":8552962,"length":1665,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/handle/text.js":{"offset":8554627,"length":320,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/handle/thematic-break.js":{"offset":8554947,"length":571,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/index.js":{"offset":8555518,"length":4808,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/join.js":{"offset":8560326,"length":1093,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/unsafe.js":{"offset":8561419,"length":5518,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/util/association.js":{"offset":8566937,"length":1113,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/util/check-bullet-ordered.js":{"offset":8568050,"length":472,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/util/check-bullet-other.js":{"offset":8568522,"length":853,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/util/check-bullet.js":{"offset":8569375,"length":468,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/util/check-emphasis.js":{"offset":8569843,"length":456,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/util/check-fence.js":{"offset":8570299,"length":443,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/util/check-list-item-indent.js":{"offset":8570742,"length":512,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/util/check-quote.js":{"offset":8571254,"length":442,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/util/check-rule-repetition.js":{"offset":8571696,"length":480,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/util/check-rule.js":{"offset":8572176,"length":460,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/util/check-strong.js":{"offset":8572636,"length":446,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/util/compile-pattern.js":{"offset":8573082,"length":577,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/util/container-flow.js":{"offset":8573659,"length":1828,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/util/container-phrasing.js":{"offset":8575487,"length":3668,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/util/encode-character-reference.js":{"offset":8579155,"length":272,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/util/encode-info.js":{"offset":8579427,"length":3216,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/util/format-code-as-indented.js":{"offset":8582643,"length":568,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/util/format-heading-as-setext.js":{"offset":8583211,"length":746,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/util/format-link-as-autolink.js":{"offset":8583957,"length":1002,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/util/indent-lines.js":{"offset":8584959,"length":613,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/util/pattern-in-scope.js":{"offset":8585572,"length":771,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/util/safe.js":{"offset":8586343,"length":4649,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/lib/util/track.js":{"offset":8590992,"length":1263,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-markdown/package.json":{"offset":8592255,"length":3174,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-string/index.js":{"offset":8595429,"length":104,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-string/lib/index.js":{"offset":8595533,"length":2626,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/mdast-util-to-string/package.json":{"offset":8598159,"length":1918,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-core-commonmark/index.js":{"offset":8600077,"length":1151,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-core-commonmark/lib/attention.js":{"offset":8601228,"length":7172,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-core-commonmark/lib/autolink.js":{"offset":8608400,"length":4817,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-core-commonmark/lib/blank-line.js":{"offset":8613217,"length":1165,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-core-commonmark/lib/block-quote.js":{"offset":8614382,"length":2731,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-core-commonmark/lib/character-escape.js":{"offset":8617113,"length":1173,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-core-commonmark/lib/character-reference.js":{"offset":8618286,"length":3300,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-core-commonmark/lib/code-fenced.js":{"offset":8621586,"length":9281,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-core-commonmark/lib/code-indented.js":{"offset":8630867,"length":4038,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-core-commonmark/lib/code-text.js":{"offset":8634905,"length":4840,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-core-commonmark/lib/content.js":{"offset":8639745,"length":3290,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-core-commonmark/lib/definition.js":{"offset":8643035,"length":5653,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-core-commonmark/lib/hard-break-escape.js":{"offset":8648688,"length":984,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-core-commonmark/lib/heading-atx.js":{"offset":8649672,"length":4386,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-core-commonmark/lib/html-flow.js":{"offset":8654058,"length":18627,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-core-commonmark/lib/html-text.js":{"offset":8672685,"length":13364,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-core-commonmark/lib/label-end.js":{"offset":8686049,"length":12658,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-core-commonmark/lib/label-start-image.js":{"offset":8698707,"length":1945,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-core-commonmark/lib/label-start-link.js":{"offset":8700652,"length":1116,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-core-commonmark/lib/line-ending.js":{"offset":8701768,"length":676,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-core-commonmark/lib/list.js":{"offset":8702444,"length":6251,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-core-commonmark/lib/setext-underline.js":{"offset":8708695,"length":4282,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-core-commonmark/lib/thematic-break.js":{"offset":8712977,"length":1873,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-core-commonmark/package.json":{"offset":8714850,"length":2027,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-extension-directive/index.js":{"offset":8716877,"length":139,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-extension-directive/lib/directive-container.js":{"offset":8717016,"length":7143,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-extension-directive/lib/directive-leaf.js":{"offset":8724159,"length":2619,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-extension-directive/lib/directive-text.js":{"offset":8726778,"length":2416,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-extension-directive/lib/factory-attributes.js":{"offset":8729194,"length":7547,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-extension-directive/lib/factory-label.js":{"offset":8736741,"length":2625,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-extension-directive/lib/factory-name.js":{"offset":8739366,"length":1034,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-extension-directive/lib/html.js":{"offset":8740400,"length":6728,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-extension-directive/lib/syntax.js":{"offset":8747128,"length":590,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-extension-directive/package.json":{"offset":8747718,"length":3295,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-extension-gfm-footnote/index.js":{"offset":8751013,"length":164,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-extension-gfm-footnote/lib/html.js":{"offset":8751177,"length":6371,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-extension-gfm-footnote/lib/syntax.js":{"offset":8757548,"length":12479,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-extension-gfm-footnote/package.json":{"offset":8770027,"length":3422,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-extension-gfm-strikethrough/index.js":{"offset":8773449,"length":152,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-extension-gfm-strikethrough/lib/html.js":{"offset":8773601,"length":551,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-extension-gfm-strikethrough/lib/syntax.js":{"offset":8774152,"length":4763,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-extension-gfm-strikethrough/package.json":{"offset":8778915,"length":3306,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-extension-gfm-table/index.js":{"offset":8782221,"length":89,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-extension-gfm-table/lib/edit-map.js":{"offset":8782310,"length":5284,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-extension-gfm-table/lib/html.js":{"offset":8787594,"length":3820,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-extension-gfm-table/lib/infer.js":{"offset":8791414,"length":1511,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-extension-gfm-table/lib/syntax.js":{"offset":8792925,"length":20730,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-extension-gfm-table/package.json":{"offset":8813655,"length":3205,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-factory-destination/index.js":{"offset":8816860,"length":4398,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-factory-destination/package.json":{"offset":8821258,"length":1377,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-factory-label/index.js":{"offset":8822635,"length":3210,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-factory-label/package.json":{"offset":8825845,"length":1461,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-factory-space/index.js":{"offset":8827306,"length":1514,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-factory-space/package.json":{"offset":8828820,"length":1287,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-factory-title/index.js":{"offset":8830107,"length":3063,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-factory-title/package.json":{"offset":8833170,"length":1403,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-factory-whitespace/index.js":{"offset":8834573,"length":1203,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-factory-whitespace/package.json":{"offset":8835776,"length":1387,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-util-character/index.js":{"offset":8837163,"length":7068,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-util-character/package.json":{"offset":8844231,"length":1290,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-util-chunked/index.js":{"offset":8845521,"length":2334,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-util-chunked/package.json":{"offset":8847855,"length":1269,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-util-classify-character/index.js":{"offset":8849124,"length":803,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-util-classify-character/package.json":{"offset":8849927,"length":1406,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-util-combine-extensions/index.js":{"offset":8851333,"length":3318,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-util-combine-extensions/package.json":{"offset":8854651,"length":1244,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-util-decode-numeric-character-reference/index.js":{"offset":8855895,"length":1061,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-util-decode-numeric-character-reference/package.json":{"offset":8856956,"length":1363,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-util-decode-string/index.js":{"offset":8858319,"length":1250,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-util-decode-string/package.json":{"offset":8859569,"length":1517,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-util-encode/index.js":{"offset":8861086,"length":757,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-util-encode/package.json":{"offset":8861843,"length":1119,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-util-html-tag-name/index.js":{"offset":8862962,"length":1680,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-util-html-tag-name/package.json":{"offset":8864642,"length":1084,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-util-normalize-identifier/index.js":{"offset":8865726,"length":1248,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-util-normalize-identifier/package.json":{"offset":8866974,"length":1374,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-util-resolve-all/index.js":{"offset":8868348,"length":784,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-util-resolve-all/package.json":{"offset":8869132,"length":1115,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-util-sanitize-uri/index.js":{"offset":8870247,"length":3159,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-util-sanitize-uri/package.json":{"offset":8873406,"length":1341,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-util-subtokenize/index.js":{"offset":8874747,"length":6851,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-util-subtokenize/lib/splice-buffer.js":{"offset":8881598,"length":7617,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark-util-subtokenize/package.json":{"offset":8889215,"length":1379,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark/index.js":{"offset":8890594,"length":1893,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark/lib/compile.js":{"offset":8892487,"length":26755,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark/lib/constructs.js":{"offset":8919242,"length":1964,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark/lib/create-tokenizer.js":{"offset":8921206,"length":15175,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark/lib/initialize/content.js":{"offset":8936381,"length":1721,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark/lib/initialize/document.js":{"offset":8938102,"length":10860,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark/lib/initialize/flow.js":{"offset":8948962,"length":1507,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark/lib/initialize/text.js":{"offset":8950469,"length":5566,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark/lib/parse.js":{"offset":8956035,"length":1435,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark/lib/postprocess.js":{"offset":8957470,"length":314,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark/lib/preprocess.js":{"offset":8957784,"length":3063,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/micromark/package.json":{"offset":8960847,"length":2498,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/parse-entities/index.js":{"offset":8963345,"length":145,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/parse-entities/lib/index.js":{"offset":8963490,"length":11738,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/parse-entities/package.json":{"offset":8975228,"length":2392,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/stringify-entities/index.js":{"offset":8977620,"length":160,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/stringify-entities/lib/constant/dangerous.js":{"offset":8977780,"length":290,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/stringify-entities/lib/core.js":{"offset":8978070,"length":3040,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/stringify-entities/lib/index.js":{"offset":8981110,"length":961,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/stringify-entities/lib/util/format-basic.js":{"offset":8982071,"length":192,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/stringify-entities/lib/util/format-smart.js":{"offset":8982263,"length":2478,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/stringify-entities/lib/util/to-decimal.js":{"offset":8984741,"length":392,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/stringify-entities/lib/util/to-hexadecimal.js":{"offset":8985133,"length":436,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/stringify-entities/lib/util/to-named.js":{"offset":8985569,"length":1290,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/stringify-entities/package.json":{"offset":8986859,"length":2238,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/unist-util-is/index.js":{"offset":8989097,"length":217,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/unist-util-is/lib/index.js":{"offset":8989314,"length":8989,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/unist-util-is/package.json":{"offset":8998303,"length":2640,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/unist-util-stringify-position/index.js":{"offset":9000943,"length":49,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/unist-util-stringify-position/lib/index.js":{"offset":9000992,"length":2123,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/unist-util-stringify-position/package.json":{"offset":9003115,"length":2022,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/unist-util-visit-parents/index.js":{"offset":9005137,"length":108,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/unist-util-visit-parents/lib/color.node.js":{"offset":9005245,"length":120,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/unist-util-visit-parents/lib/index.js":{"offset":9005365,"length":11752,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/unist-util-visit-parents/package.json":{"offset":9017117,"length":2627,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/unist-util-visit/index.js":{"offset":9019744,"length":101,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/unist-util-visit/lib/index.js":{"offset":9019845,"length":9582,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/unist-util-visit/package.json":{"offset":9029427,"length":2544,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/zwitch/index.js":{"offset":9031971,"length":3787,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/node_modules/zwitch/package.json":{"offset":9035758,"length":1767,"mode":420},"/node_modules/favela-surface/node_modules/favela-markdown/package.json":{"offset":9037525,"length":1131,"mode":420},"/node_modules/favela-surface/node_modules/streamx/index.js":{"offset":9038656,"length":34248,"mode":420},"/node_modules/favela-surface/node_modules/streamx/lib/errors.js":{"offset":9072904,"length":1096,"mode":420},"/node_modules/favela-surface/node_modules/streamx/package.json":{"offset":9074000,"length":1225,"mode":420},"/node_modules/favela-surface/node_modules/teex/index.js":{"offset":9075225,"length":1107,"mode":420},"/node_modules/favela-surface/node_modules/teex/package.json":{"offset":9076332,"length":608,"mode":420},"/node_modules/favela-surface/node_modules/text-decoder/index.js":{"offset":9076940,"length":1371,"mode":420},"/node_modules/favela-surface/node_modules/text-decoder/lib/pass-through-decoder.js":{"offset":9078311,"length":269,"mode":420},"/node_modules/favela-surface/node_modules/text-decoder/lib/utf8-decoder.js":{"offset":9078580,"length":4429,"mode":420},"/node_modules/favela-surface/node_modules/text-decoder/package.json":{"offset":9083009,"length":990,"mode":420},"/node_modules/favela-surface/package.json":{"offset":9083999,"length":1546,"mode":420},"/node_modules/hyper-overlay/lib/tree.js":{"offset":9085545,"length":14077,"mode":420},"/node_modules/hyper-overlay/node_modules/b4a/index.js":{"offset":9099622,"length":4054,"mode":420},"/node_modules/hyper-overlay/node_modules/b4a/package.json":{"offset":9103676,"length":1245,"mode":420},"/node_modules/hyper-overlay/node_modules/bare-ansi-escapes/index.js":{"offset":9104921,"length":2255,"mode":420},"/node_modules/hyper-overlay/node_modules/bare-ansi-escapes/package.json":{"offset":9107176,"length":1178,"mode":420},"/node_modules/hyper-overlay/node_modules/bare-assert/index.js":{"offset":9108354,"length":2192,"mode":420},"/node_modules/hyper-overlay/node_modules/bare-assert/package.json":{"offset":9110546,"length":835,"mode":420},"/node_modules/hyper-overlay/node_modules/bare-events/index.js":{"offset":9111381,"length":8307,"mode":420},"/node_modules/hyper-overlay/node_modules/bare-events/lib/errors.js":{"offset":9119688,"length":671,"mode":420},"/node_modules/hyper-overlay/node_modules/bare-events/package.json":{"offset":9120359,"length":1504,"mode":420},"/node_modules/hyper-overlay/node_modules/bare-fs/binding.js":{"offset":9121863,"length":33,"mode":420},"/node_modules/hyper-overlay/node_modules/bare-fs/index.js":{"offset":9121896,"length":58907,"mode":420},"/node_modules/hyper-overlay/node_modules/bare-fs/lib/constants.js":{"offset":9180803,"length":1494,"mode":420},"/node_modules/hyper-overlay/node_modules/bare-fs/lib/errors.js":{"offset":9182297,"length":1163,"mode":420},"/node_modules/hyper-overlay/node_modules/bare-fs/package.json":{"offset":9183460,"length":1654,"mode":420},"/node_modules/hyper-overlay/node_modules/bare-fs/promises.js":{"offset":9185114,"length":2401,"mode":420},"/node_modules/hyper-overlay/node_modules/bare-inspect/binding.js":{"offset":9187515,"length":33,"mode":420},"/node_modules/hyper-overlay/node_modules/bare-inspect/index.js":{"offset":9187548,"length":23856,"mode":420},"/node_modules/hyper-overlay/node_modules/bare-inspect/package.json":{"offset":9211404,"length":1041,"mode":420},"/node_modules/hyper-overlay/node_modules/bare-os/binding.js":{"offset":9212445,"length":33,"mode":420},"/node_modules/hyper-overlay/node_modules/bare-os/index.js":{"offset":9212478,"length":3224,"mode":420},"/node_modules/hyper-overlay/node_modules/bare-os/lib/constants.js":{"offset":9215702,"length":143,"mode":420},"/node_modules/hyper-overlay/node_modules/bare-os/lib/errors.js":{"offset":9215845,"length":453,"mode":420},"/node_modules/hyper-overlay/node_modules/bare-os/package.json":{"offset":9216298,"length":1257,"mode":420},"/node_modules/hyper-overlay/node_modules/bare-path/index.js":{"offset":9217555,"length":287,"mode":420},"/node_modules/hyper-overlay/node_modules/bare-path/lib/constants.js":{"offset":9217842,"length":247,"mode":420},"/node_modules/hyper-overlay/node_modules/bare-path/lib/posix.js":{"offset":9218089,"length":6005,"mode":420},"/node_modules/hyper-overlay/node_modules/bare-path/lib/shared.js":{"offset":9224094,"length":1926,"mode":420},"/node_modules/hyper-overlay/node_modules/bare-path/lib/win32.js":{"offset":9226020,"length":13591,"mode":420},"/node_modules/hyper-overlay/node_modules/bare-path/package.json":{"offset":9239611,"length":1036,"mode":420},"/node_modules/hyper-overlay/node_modules/bare-stream/index.js":{"offset":9240647,"length":10904,"mode":420},"/node_modules/hyper-overlay/node_modules/bare-stream/package.json":{"offset":9251551,"length":1676,"mode":420},"/node_modules/hyper-overlay/node_modules/bare-stream/web.js":{"offset":9253227,"length":13980,"mode":420},"/node_modules/hyper-overlay/node_modules/bare-type/binding.js":{"offset":9267207,"length":33,"mode":420},"/node_modules/hyper-overlay/node_modules/bare-type/index.js":{"offset":9267240,"length":4566,"mode":420},"/node_modules/hyper-overlay/node_modules/bare-type/package.json":{"offset":9271806,"length":938,"mode":420},"/node_modules/hyper-overlay/node_modules/bare-url/binding.js":{"offset":9272744,"length":33,"mode":420},"/node_modules/hyper-overlay/node_modules/bare-url/index.js":{"offset":9272777,"length":9449,"mode":420},"/node_modules/hyper-overlay/node_modules/bare-url/lib/errors.js":{"offset":9282226,"length":778,"mode":420},"/node_modules/hyper-overlay/node_modules/bare-url/lib/url-search-params.js":{"offset":9283004,"length":4331,"mode":420},"/node_modules/hyper-overlay/node_modules/bare-url/package.json":{"offset":9287335,"length":1219,"mode":420},"/node_modules/hyper-overlay/node_modules/compact-encoding/endian.js":{"offset":9288554,"length":105,"mode":420},"/node_modules/hyper-overlay/node_modules/compact-encoding/index.js":{"offset":9288659,"length":26135,"mode":420},"/node_modules/hyper-overlay/node_modules/compact-encoding/lexint.js":{"offset":9314794,"length":2851,"mode":420},"/node_modules/hyper-overlay/node_modules/compact-encoding/package.json":{"offset":9317645,"length":866,"mode":420},"/node_modules/hyper-overlay/node_modules/compact-encoding/raw.js":{"offset":9318511,"length":3977,"mode":420},"/node_modules/hyper-overlay/node_modules/events-universal/bare.js":{"offset":9322488,"length":40,"mode":420},"/node_modules/hyper-overlay/node_modules/events-universal/package.json":{"offset":9322528,"length":908,"mode":420},"/node_modules/hyper-overlay/node_modules/fast-fifo/fixed-size.js":{"offset":9323436,"length":875,"mode":420},"/node_modules/hyper-overlay/node_modules/fast-fifo/index.js":{"offset":9324311,"length":972,"mode":420},"/node_modules/hyper-overlay/node_modules/fast-fifo/package.json":{"offset":9325283,"length":682,"mode":420},"/node_modules/hyper-overlay/node_modules/hypercore-crypto/index.js":{"offset":9325965,"length":5031,"mode":420},"/node_modules/hyper-overlay/node_modules/hypercore-crypto/package.json":{"offset":9330996,"length":1007,"mode":420},"/node_modules/hyper-overlay/node_modules/require-addon/lib/bare.js":{"offset":9332003,"length":45,"mode":420},"/node_modules/hyper-overlay/node_modules/require-addon/package.json":{"offset":9332048,"length":1449,"mode":420},"/node_modules/hyper-overlay/node_modules/sodium-native/binding.js":{"offset":9333497,"length":89,"mode":420},"/node_modules/hyper-overlay/node_modules/sodium-native/index.js":{"offset":9333586,"length":107894,"mode":420},"/node_modules/hyper-overlay/node_modules/sodium-native/package.json":{"offset":9441480,"length":1478,"mode":420},"/node_modules/hyper-overlay/node_modules/sodium-universal/index.js":{"offset":9442958,"length":42,"mode":420},"/node_modules/hyper-overlay/node_modules/sodium-universal/package.json":{"offset":9443000,"length":1217,"mode":420},"/node_modules/hyper-overlay/node_modules/streamx/index.js":{"offset":9444217,"length":34248,"mode":420},"/node_modules/hyper-overlay/node_modules/streamx/lib/errors.js":{"offset":9478465,"length":1096,"mode":420},"/node_modules/hyper-overlay/node_modules/streamx/package.json":{"offset":9479561,"length":1225,"mode":420},"/node_modules/hyper-overlay/node_modules/teex/index.js":{"offset":9480786,"length":1107,"mode":420},"/node_modules/hyper-overlay/node_modules/teex/package.json":{"offset":9481893,"length":608,"mode":420},"/node_modules/hyper-overlay/node_modules/text-decoder/index.js":{"offset":9482501,"length":1371,"mode":420},"/node_modules/hyper-overlay/node_modules/text-decoder/lib/pass-through-decoder.js":{"offset":9483872,"length":269,"mode":420},"/node_modules/hyper-overlay/node_modules/text-decoder/lib/utf8-decoder.js":{"offset":9484141,"length":4429,"mode":420},"/node_modules/hyper-overlay/node_modules/text-decoder/package.json":{"offset":9488570,"length":990,"mode":420},"/node_modules/hyper-overlay/node_modules/which-runtime/index.js":{"offset":9489560,"length":1469,"mode":420},"/node_modules/hyper-overlay/node_modules/which-runtime/package.json":{"offset":9491029,"length":863,"mode":420},"/node_modules/hyper-overlay/package.json":{"offset":9491892,"length":2132,"mode":420},"/node_modules/hypercore-crypto/index.js":{"offset":9494024,"length":5031,"mode":420},"/node_modules/hypercore-crypto/package.json":{"offset":9499055,"length":1007,"mode":420},"/node_modules/hypercore-id-encoding/index.js":{"offset":9500062,"length":923,"mode":420},"/node_modules/hypercore-id-encoding/package.json":{"offset":9500985,"length":799,"mode":420},"/node_modules/hyperdht-address/index.js":{"offset":9501784,"length":357,"mode":420},"/node_modules/hyperdht-address/package.json":{"offset":9502141,"length":1046,"mode":420},"/node_modules/hyperdht-address/spec/hyperschema/index.js":{"offset":9503187,"length":2106,"mode":420},"/node_modules/hyperdht/index.js":{"offset":9505293,"length":16732,"mode":420},"/node_modules/hyperdht/lib/announcer.js":{"offset":9522025,"length":7533,"mode":420},"/node_modules/hyperdht/lib/connect.js":{"offset":9529558,"length":25203,"mode":420},"/node_modules/hyperdht/lib/connection-pool.js":{"offset":9554761,"length":3050,"mode":420},"/node_modules/hyperdht/lib/constants.js":{"offset":9557811,"length":1178,"mode":420},"/node_modules/hyperdht/lib/crypto.js":{"offset":9558989,"length":632,"mode":420},"/node_modules/hyperdht/lib/encode.js":{"offset":9559621,"length":446,"mode":420},"/node_modules/hyperdht/lib/errors.js":{"offset":9560067,"length":3713,"mode":420},"/node_modules/hyperdht/lib/holepuncher.js":{"offset":9563780,"length":10003,"mode":420},"/node_modules/hyperdht/lib/messages.js":{"offset":9573783,"length":12057,"mode":420},"/node_modules/hyperdht/lib/nat.js":{"offset":9585840,"length":4714,"mode":420},"/node_modules/hyperdht/lib/noise-wrap.js":{"offset":9590554,"length":1669,"mode":420},"/node_modules/hyperdht/lib/persistent.js":{"offset":9592223,"length":8361,"mode":420},"/node_modules/hyperdht/lib/raw-stream-set.js":{"offset":9600584,"length":1511,"mode":420},"/node_modules/hyperdht/lib/relay-connection.js":{"offset":9602095,"length":2062,"mode":420},"/node_modules/hyperdht/lib/router.js":{"offset":9604157,"length":6978,"mode":420},"/node_modules/hyperdht/lib/secure-payload.js":{"offset":9611135,"length":1491,"mode":420},"/node_modules/hyperdht/lib/semaphore.js":{"offset":9612626,"length":1577,"mode":420},"/node_modules/hyperdht/lib/server.js":{"offset":9614203,"length":22554,"mode":420},"/node_modules/hyperdht/lib/sleeper.js":{"offset":9636757,"length":690,"mode":420},"/node_modules/hyperdht/lib/socket-pool.js":{"offset":9637447,"length":4507,"mode":420},"/node_modules/hyperdht/package.json":{"offset":9641954,"length":2007,"mode":420},"/node_modules/hyperschema/package.json":{"offset":9643961,"length":1642,"mode":420},"/node_modules/hyperschema/runtime.cjs":{"offset":9645603,"length":54,"mode":420},"/node_modules/hyperswarm/index.js":{"offset":9645657,"length":18589,"mode":420},"/node_modules/hyperswarm/lib/bulk-timer.js":{"offset":9664246,"length":728,"mode":420},"/node_modules/hyperswarm/lib/connection-set.js":{"offset":9664974,"length":823,"mode":420},"/node_modules/hyperswarm/lib/peer-discovery.js":{"offset":9665797,"length":9296,"mode":420},"/node_modules/hyperswarm/lib/peer-info.js":{"offset":9675093,"length":2688,"mode":420},"/node_modules/hyperswarm/lib/retry-timer.js":{"offset":9677781,"length":1889,"mode":420},"/node_modules/hyperswarm/package.json":{"offset":9679670,"length":1515,"mode":420},"/node_modules/kademlia-routing-table/index.js":{"offset":9681185,"length":4145,"mode":420},"/node_modules/kademlia-routing-table/package.json":{"offset":9685330,"length":967,"mode":420},"/node_modules/nanoassert/index.js":{"offset":9686297,"length":438,"mode":420},"/node_modules/nanoassert/package.json":{"offset":9686735,"length":647,"mode":420},"/node_modules/nat-sampler/index.js":{"offset":9687382,"length":1550,"mode":420},"/node_modules/nat-sampler/package.json":{"offset":9688932,"length":608,"mode":420},"/node_modules/noise-curve-ed/index.js":{"offset":9689540,"length":1642,"mode":420},"/node_modules/noise-curve-ed/package.json":{"offset":9691182,"length":808,"mode":420},"/node_modules/noise-handshake/cipher.js":{"offset":9691990,"length":2817,"mode":420},"/node_modules/noise-handshake/dh.js":{"offset":9694807,"length":1439,"mode":420},"/node_modules/noise-handshake/hkdf.js":{"offset":9696246,"length":1092,"mode":420},"/node_modules/noise-handshake/hmac.js":{"offset":9697338,"length":1273,"mode":420},"/node_modules/noise-handshake/noise.js":{"offset":9698611,"length":6437,"mode":420},"/node_modules/noise-handshake/package.json":{"offset":9705048,"length":638,"mode":420},"/node_modules/noise-handshake/symmetric-state.js":{"offset":9705686,"length":2206,"mode":420},"/node_modules/pgh-runtime/index.js":{"offset":9707892,"length":11375,"mode":420},"/node_modules/pgh-runtime/node_modules/bare-events/index.js":{"offset":9719267,"length":8307,"mode":420},"/node_modules/pgh-runtime/node_modules/bare-events/lib/errors.js":{"offset":9727574,"length":671,"mode":420},"/node_modules/pgh-runtime/node_modules/bare-events/package.json":{"offset":9728245,"length":1504,"mode":420},"/node_modules/pgh-runtime/node_modules/hyper-overlay/lib/tree.js":{"offset":9729749,"length":14077,"mode":420},"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/b4a/index.js":{"offset":9743826,"length":4054,"mode":420},"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/b4a/package.json":{"offset":9747880,"length":1245,"mode":420},"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/bare-ansi-escapes/index.js":{"offset":9749125,"length":2255,"mode":420},"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/bare-ansi-escapes/package.json":{"offset":9751380,"length":1178,"mode":420},"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/bare-assert/index.js":{"offset":9752558,"length":2192,"mode":420},"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/bare-assert/package.json":{"offset":9754750,"length":835,"mode":420},"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/bare-events/index.js":{"offset":9755585,"length":8307,"mode":420},"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/bare-events/lib/errors.js":{"offset":9763892,"length":671,"mode":420},"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/bare-events/package.json":{"offset":9764563,"length":1504,"mode":420},"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/bare-fs/binding.js":{"offset":9766067,"length":33,"mode":420},"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/bare-fs/index.js":{"offset":9766100,"length":58907,"mode":420},"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/bare-fs/lib/constants.js":{"offset":9825007,"length":1494,"mode":420},"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/bare-fs/lib/errors.js":{"offset":9826501,"length":1163,"mode":420},"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/bare-fs/package.json":{"offset":9827664,"length":1654,"mode":420},"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/bare-fs/promises.js":{"offset":9829318,"length":2401,"mode":420},"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/bare-inspect/binding.js":{"offset":9831719,"length":33,"mode":420},"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/bare-inspect/index.js":{"offset":9831752,"length":23856,"mode":420},"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/bare-inspect/package.json":{"offset":9855608,"length":1041,"mode":420},"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/bare-os/binding.js":{"offset":9856649,"length":33,"mode":420},"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/bare-os/index.js":{"offset":9856682,"length":3224,"mode":420},"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/bare-os/lib/constants.js":{"offset":9859906,"length":143,"mode":420},"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/bare-os/lib/errors.js":{"offset":9860049,"length":453,"mode":420},"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/bare-os/package.json":{"offset":9860502,"length":1257,"mode":420},"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/bare-path/index.js":{"offset":9861759,"length":287,"mode":420},"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/bare-path/lib/constants.js":{"offset":9862046,"length":247,"mode":420},"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/bare-path/lib/posix.js":{"offset":9862293,"length":6005,"mode":420},"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/bare-path/lib/shared.js":{"offset":9868298,"length":1926,"mode":420},"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/bare-path/lib/win32.js":{"offset":9870224,"length":13591,"mode":420},"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/bare-path/package.json":{"offset":9883815,"length":1036,"mode":420},"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/bare-stream/index.js":{"offset":9884851,"length":10904,"mode":420},"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/bare-stream/package.json":{"offset":9895755,"length":1676,"mode":420},"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/bare-stream/web.js":{"offset":9897431,"length":13980,"mode":420},"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/bare-type/binding.js":{"offset":9911411,"length":33,"mode":420},"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/bare-type/index.js":{"offset":9911444,"length":4566,"mode":420},"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/bare-type/package.json":{"offset":9916010,"length":938,"mode":420},"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/bare-url/binding.js":{"offset":9916948,"length":33,"mode":420},"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/bare-url/index.js":{"offset":9916981,"length":9449,"mode":420},"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/bare-url/lib/errors.js":{"offset":9926430,"length":778,"mode":420},"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/bare-url/lib/url-search-params.js":{"offset":9927208,"length":4331,"mode":420},"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/bare-url/package.json":{"offset":9931539,"length":1219,"mode":420},"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/compact-encoding/endian.js":{"offset":9932758,"length":105,"mode":420},"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/compact-encoding/index.js":{"offset":9932863,"length":26135,"mode":420},"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/compact-encoding/lexint.js":{"offset":9958998,"length":2851,"mode":420},"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/compact-encoding/package.json":{"offset":9961849,"length":866,"mode":420},"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/compact-encoding/raw.js":{"offset":9962715,"length":3977,"mode":420},"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/events-universal/bare.js":{"offset":9966692,"length":40,"mode":420},"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/events-universal/package.json":{"offset":9966732,"length":908,"mode":420},"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/fast-fifo/fixed-size.js":{"offset":9967640,"length":875,"mode":420},"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/fast-fifo/index.js":{"offset":9968515,"length":972,"mode":420},"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/fast-fifo/package.json":{"offset":9969487,"length":682,"mode":420},"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/hypercore-crypto/index.js":{"offset":9970169,"length":5031,"mode":420},"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/hypercore-crypto/package.json":{"offset":9975200,"length":1007,"mode":420},"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/require-addon/lib/bare.js":{"offset":9976207,"length":45,"mode":420},"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/require-addon/package.json":{"offset":9976252,"length":1449,"mode":420},"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/sodium-native/binding.js":{"offset":9977701,"length":89,"mode":420},"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/sodium-native/index.js":{"offset":9977790,"length":107894,"mode":420},"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/sodium-native/package.json":{"offset":10085684,"length":1478,"mode":420},"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/sodium-universal/index.js":{"offset":10087162,"length":42,"mode":420},"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/sodium-universal/package.json":{"offset":10087204,"length":1217,"mode":420},"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/streamx/index.js":{"offset":10088421,"length":34248,"mode":420},"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/streamx/lib/errors.js":{"offset":10122669,"length":1096,"mode":420},"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/streamx/package.json":{"offset":10123765,"length":1225,"mode":420},"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/teex/index.js":{"offset":10124990,"length":1107,"mode":420},"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/teex/package.json":{"offset":10126097,"length":608,"mode":420},"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/text-decoder/index.js":{"offset":10126705,"length":1371,"mode":420},"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/text-decoder/lib/pass-through-decoder.js":{"offset":10128076,"length":269,"mode":420},"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/text-decoder/lib/utf8-decoder.js":{"offset":10128345,"length":4429,"mode":420},"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/text-decoder/package.json":{"offset":10132774,"length":990,"mode":420},"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/which-runtime/index.js":{"offset":10133764,"length":1469,"mode":420},"/node_modules/pgh-runtime/node_modules/hyper-overlay/node_modules/which-runtime/package.json":{"offset":10135233,"length":863,"mode":420},"/node_modules/pgh-runtime/node_modules/hyper-overlay/package.json":{"offset":10136096,"length":2132,"mode":420},"/node_modules/pgh-runtime/package.json":{"offset":10138228,"length":683,"mode":420},"/node_modules/pgh/lib/network-fetcher.js":{"offset":10138911,"length":20230,"mode":420},"/node_modules/pgh/lib/release-registry.js":{"offset":10159141,"length":12849,"mode":420},"/node_modules/pgh/lib/resolve-release.js":{"offset":10171990,"length":10913,"mode":420},"/node_modules/pgh/node_modules/@hyperswarm/secret-stream/index.js":{"offset":10182903,"length":16722,"mode":420},"/node_modules/pgh/node_modules/@hyperswarm/secret-stream/lib/bridge.js":{"offset":10199625,"length":1273,"mode":420},"/node_modules/pgh/node_modules/@hyperswarm/secret-stream/lib/handshake.js":{"offset":10200898,"length":1950,"mode":420},"/node_modules/pgh/node_modules/@hyperswarm/secret-stream/package.json":{"offset":10202848,"length":1132,"mode":420},"/node_modules/pgh/node_modules/adaptive-timeout/index.js":{"offset":10203980,"length":2424,"mode":420},"/node_modules/pgh/node_modules/adaptive-timeout/package.json":{"offset":10206404,"length":972,"mode":420},"/node_modules/pgh/node_modules/autobase/encoding/legacy.js":{"offset":10207376,"length":8969,"mode":420},"/node_modules/pgh/node_modules/autobase/encoding/spec/autobase/index.js":{"offset":10216345,"length":18905,"mode":420},"/node_modules/pgh/node_modules/autobase/index.js":{"offset":10235250,"length":59358,"mode":420},"/node_modules/pgh/node_modules/autobase/lib/active-writers.js":{"offset":10294608,"length":736,"mode":420},"/node_modules/pgh/node_modules/autobase/lib/append-batch.js":{"offset":10295344,"length":1426,"mode":420},"/node_modules/pgh/node_modules/autobase/lib/apply-calls.js":{"offset":10296770,"length":2275,"mode":420},"/node_modules/pgh/node_modules/autobase/lib/apply-state.js":{"offset":10299045,"length":46121,"mode":420},"/node_modules/pgh/node_modules/autobase/lib/boot.js":{"offset":10345166,"length":4599,"mode":420},"/node_modules/pgh/node_modules/autobase/lib/caps.js":{"offset":10349765,"length":505,"mode":420},"/node_modules/pgh/node_modules/autobase/lib/clock.js":{"offset":10350270,"length":749,"mode":420},"/node_modules/pgh/node_modules/autobase/lib/consensus.js":{"offset":10351019,"length":11754,"mode":420},"/node_modules/pgh/node_modules/autobase/lib/encryption.js":{"offset":10362773,"length":9444,"mode":420},"/node_modules/pgh/node_modules/autobase/lib/fast-forward.js":{"offset":10372217,"length":5714,"mode":420},"/node_modules/pgh/node_modules/autobase/lib/fork.js":{"offset":10377931,"length":4148,"mode":420},"/node_modules/pgh/node_modules/autobase/lib/linearizer.js":{"offset":10382079,"length":9696,"mode":420},"/node_modules/pgh/node_modules/autobase/lib/local-state.js":{"offset":10391775,"length":2013,"mode":420},"/node_modules/pgh/node_modules/autobase/lib/messages.js":{"offset":10393788,"length":827,"mode":420},"/node_modules/pgh/node_modules/autobase/lib/node-buffer.js":{"offset":10394615,"length":1563,"mode":420},"/node_modules/pgh/node_modules/autobase/lib/store.js":{"offset":10396178,"length":14341,"mode":420},"/node_modules/pgh/node_modules/autobase/lib/system.js":{"offset":10410519,"length":20176,"mode":420},"/node_modules/pgh/node_modules/autobase/lib/timer.js":{"offset":10430695,"length":2937,"mode":420},"/node_modules/pgh/node_modules/autobase/lib/topolist.js":{"offset":10433632,"length":5108,"mode":420},"/node_modules/pgh/node_modules/autobase/lib/updates.js":{"offset":10438740,"length":1109,"mode":420},"/node_modules/pgh/node_modules/autobase/lib/values.js":{"offset":10439849,"length":1539,"mode":420},"/node_modules/pgh/node_modules/autobase/lib/wakeup.js":{"offset":10441388,"length":4034,"mode":420},"/node_modules/pgh/node_modules/autobase/lib/writer.js":{"offset":10445422,"length":8901,"mode":420},"/node_modules/pgh/node_modules/autobase/package.json":{"offset":10454323,"length":2327,"mode":420},"/node_modules/pgh/node_modules/b4a/index.js":{"offset":10456650,"length":4054,"mode":420},"/node_modules/pgh/node_modules/b4a/package.json":{"offset":10460704,"length":1245,"mode":420},"/node_modules/pgh/node_modules/bare-abort/binding.js":{"offset":10461949,"length":33,"mode":420},"/node_modules/pgh/node_modules/bare-abort/index.js":{"offset":10461982,"length":69,"mode":420},"/node_modules/pgh/node_modules/bare-abort/package.json":{"offset":10462051,"length":886,"mode":420},"/node_modules/pgh/node_modules/bare-ansi-escapes/index.js":{"offset":10462937,"length":2255,"mode":420},"/node_modules/pgh/node_modules/bare-ansi-escapes/package.json":{"offset":10465192,"length":1178,"mode":420},"/node_modules/pgh/node_modules/bare-assert/index.js":{"offset":10466370,"length":2192,"mode":420},"/node_modules/pgh/node_modules/bare-assert/package.json":{"offset":10468562,"length":835,"mode":420},"/node_modules/pgh/node_modules/bare-env/index.js":{"offset":10469397,"length":1023,"mode":420},"/node_modules/pgh/node_modules/bare-env/package.json":{"offset":10470420,"length":649,"mode":420},"/node_modules/pgh/node_modules/bare-events/index.js":{"offset":10471069,"length":8307,"mode":420},"/node_modules/pgh/node_modules/bare-events/lib/errors.js":{"offset":10479376,"length":671,"mode":420},"/node_modules/pgh/node_modules/bare-events/package.json":{"offset":10480047,"length":1504,"mode":420},"/node_modules/pgh/node_modules/bare-fs/binding.js":{"offset":10481551,"length":33,"mode":420},"/node_modules/pgh/node_modules/bare-fs/index.js":{"offset":10481584,"length":58907,"mode":420},"/node_modules/pgh/node_modules/bare-fs/lib/constants.js":{"offset":10540491,"length":1494,"mode":420},"/node_modules/pgh/node_modules/bare-fs/lib/errors.js":{"offset":10541985,"length":1163,"mode":420},"/node_modules/pgh/node_modules/bare-fs/package.json":{"offset":10543148,"length":1654,"mode":420},"/node_modules/pgh/node_modules/bare-fs/promises.js":{"offset":10544802,"length":2401,"mode":420},"/node_modules/pgh/node_modules/bare-hrtime/binding.js":{"offset":10547203,"length":33,"mode":420},"/node_modules/pgh/node_modules/bare-hrtime/index.js":{"offset":10547236,"length":319,"mode":420},"/node_modules/pgh/node_modules/bare-hrtime/package.json":{"offset":10547555,"length":905,"mode":420},"/node_modules/pgh/node_modules/bare-inspect/binding.js":{"offset":10548460,"length":33,"mode":420},"/node_modules/pgh/node_modules/bare-inspect/index.js":{"offset":10548493,"length":23856,"mode":420},"/node_modules/pgh/node_modules/bare-inspect/package.json":{"offset":10572349,"length":1041,"mode":420},"/node_modules/pgh/node_modules/bare-os/binding.js":{"offset":10573390,"length":33,"mode":420},"/node_modules/pgh/node_modules/bare-os/index.js":{"offset":10573423,"length":3224,"mode":420},"/node_modules/pgh/node_modules/bare-os/lib/constants.js":{"offset":10576647,"length":143,"mode":420},"/node_modules/pgh/node_modules/bare-os/lib/errors.js":{"offset":10576790,"length":453,"mode":420},"/node_modules/pgh/node_modules/bare-os/package.json":{"offset":10577243,"length":1257,"mode":420},"/node_modules/pgh/node_modules/bare-path/index.js":{"offset":10578500,"length":287,"mode":420},"/node_modules/pgh/node_modules/bare-path/lib/constants.js":{"offset":10578787,"length":247,"mode":420},"/node_modules/pgh/node_modules/bare-path/lib/posix.js":{"offset":10579034,"length":6005,"mode":420},"/node_modules/pgh/node_modules/bare-path/lib/shared.js":{"offset":10585039,"length":1926,"mode":420},"/node_modules/pgh/node_modules/bare-path/lib/win32.js":{"offset":10586965,"length":13591,"mode":420},"/node_modules/pgh/node_modules/bare-path/package.json":{"offset":10600556,"length":1036,"mode":420},"/node_modules/pgh/node_modules/bare-process/index.js":{"offset":10601592,"length":2689,"mode":420},"/node_modules/pgh/node_modules/bare-process/package.json":{"offset":10604281,"length":1255,"mode":420},"/node_modules/pgh/node_modules/bare-signals/binding.js":{"offset":10605536,"length":33,"mode":420},"/node_modules/pgh/node_modules/bare-signals/index.js":{"offset":10605569,"length":1517,"mode":420},"/node_modules/pgh/node_modules/bare-signals/lib/emitter.js":{"offset":10607086,"length":1278,"mode":420},"/node_modules/pgh/node_modules/bare-signals/lib/errors.js":{"offset":10608364,"length":479,"mode":420},"/node_modules/pgh/node_modules/bare-signals/package.json":{"offset":10608843,"length":1112,"mode":420},"/node_modules/pgh/node_modules/bare-stdio/binding.js":{"offset":10609955,"length":33,"mode":420},"/node_modules/pgh/node_modules/bare-stdio/index.js":{"offset":10609988,"length":1525,"mode":420},"/node_modules/pgh/node_modules/bare-stdio/node_modules/bare-pipe/binding.js":{"offset":10611513,"length":33,"mode":420},"/node_modules/pgh/node_modules/bare-stdio/node_modules/bare-pipe/index.js":{"offset":10611546,"length":14060,"mode":420},"/node_modules/pgh/node_modules/bare-stdio/node_modules/bare-pipe/lib/constants.js":{"offset":10625606,"length":352,"mode":420},"/node_modules/pgh/node_modules/bare-stdio/node_modules/bare-pipe/lib/errors.js":{"offset":10625958,"length":699,"mode":420},"/node_modules/pgh/node_modules/bare-stdio/node_modules/bare-pipe/package.json":{"offset":10626657,"length":1396,"mode":420},"/node_modules/pgh/node_modules/bare-stdio/package.json":{"offset":10628053,"length":1319,"mode":420},"/node_modules/pgh/node_modules/bare-stream/index.js":{"offset":10629372,"length":10913,"mode":420},"/node_modules/pgh/node_modules/bare-stream/package.json":{"offset":10640285,"length":1655,"mode":420},"/node_modules/pgh/node_modules/bare-stream/web.js":{"offset":10641940,"length":13980,"mode":420},"/node_modules/pgh/node_modules/bare-tty/binding.js":{"offset":10655920,"length":33,"mode":420},"/node_modules/pgh/node_modules/bare-tty/index.js":{"offset":10655953,"length":4469,"mode":420},"/node_modules/pgh/node_modules/bare-tty/lib/constants.js":{"offset":10660422,"length":442,"mode":420},"/node_modules/pgh/node_modules/bare-tty/package.json":{"offset":10660864,"length":1097,"mode":420},"/node_modules/pgh/node_modules/bare-type/binding.js":{"offset":10661961,"length":33,"mode":420},"/node_modules/pgh/node_modules/bare-type/index.js":{"offset":10661994,"length":4566,"mode":420},"/node_modules/pgh/node_modules/bare-type/package.json":{"offset":10666560,"length":938,"mode":420},"/node_modules/pgh/node_modules/bare-url/binding.js":{"offset":10667498,"length":33,"mode":420},"/node_modules/pgh/node_modules/bare-url/index.js":{"offset":10667531,"length":9449,"mode":420},"/node_modules/pgh/node_modules/bare-url/lib/errors.js":{"offset":10676980,"length":778,"mode":420},"/node_modules/pgh/node_modules/bare-url/lib/url-search-params.js":{"offset":10677758,"length":4331,"mode":420},"/node_modules/pgh/node_modules/bare-url/package.json":{"offset":10682089,"length":1219,"mode":420},"/node_modules/pgh/node_modules/big-sparse-array/index.js":{"offset":10683308,"length":2471,"mode":420},"/node_modules/pgh/node_modules/big-sparse-array/package.json":{"offset":10685779,"length":636,"mode":420},"/node_modules/pgh/node_modules/bits-to-bytes/index.js":{"offset":10686415,"length":3083,"mode":420},"/node_modules/pgh/node_modules/bits-to-bytes/package.json":{"offset":10689498,"length":708,"mode":420},"/node_modules/pgh/node_modules/blind-relay/index.js":{"offset":10690206,"length":13411,"mode":420},"/node_modules/pgh/node_modules/blind-relay/lib/errors.js":{"offset":10703617,"length":1036,"mode":420},"/node_modules/pgh/node_modules/blind-relay/package.json":{"offset":10704653,"length":1231,"mode":420},"/node_modules/pgh/node_modules/bogon/index.js":{"offset":10705884,"length":4004,"mode":420},"/node_modules/pgh/node_modules/bogon/package.json":{"offset":10709888,"length":656,"mode":420},"/node_modules/pgh/node_modules/codecs/index.js":{"offset":10710544,"length":2416,"mode":420},"/node_modules/pgh/node_modules/codecs/package.json":{"offset":10712960,"length":655,"mode":420},"/node_modules/pgh/node_modules/compact-encoding-bitfield/index.js":{"offset":10713615,"length":1967,"mode":420},"/node_modules/pgh/node_modules/compact-encoding-bitfield/package.json":{"offset":10715582,"length":1109,"mode":420},"/node_modules/pgh/node_modules/compact-encoding/endian.js":{"offset":10716691,"length":105,"mode":420},"/node_modules/pgh/node_modules/compact-encoding/index.js":{"offset":10716796,"length":26135,"mode":420},"/node_modules/pgh/node_modules/compact-encoding/lexint.js":{"offset":10742931,"length":2851,"mode":420},"/node_modules/pgh/node_modules/compact-encoding/package.json":{"offset":10745782,"length":866,"mode":420},"/node_modules/pgh/node_modules/compact-encoding/raw.js":{"offset":10746648,"length":3977,"mode":420},"/node_modules/pgh/node_modules/core-coupler/index.js":{"offset":10750625,"length":2243,"mode":420},"/node_modules/pgh/node_modules/core-coupler/package.json":{"offset":10752868,"length":599,"mode":420},"/node_modules/pgh/node_modules/corestore/index.js":{"offset":10753467,"length":19823,"mode":420},"/node_modules/pgh/node_modules/corestore/lib/audit.js":{"offset":10773290,"length":475,"mode":420},"/node_modules/pgh/node_modules/corestore/lib/notify.js":{"offset":10773765,"length":630,"mode":420},"/node_modules/pgh/node_modules/corestore/package.json":{"offset":10774395,"length":1350,"mode":420},"/node_modules/pgh/node_modules/debounceify/index.js":{"offset":10775745,"length":585,"mode":420},"/node_modules/pgh/node_modules/debounceify/package.json":{"offset":10776330,"length":567,"mode":420},"/node_modules/pgh/node_modules/device-file/index.js":{"offset":10776897,"length":6394,"mode":420},"/node_modules/pgh/node_modules/device-file/package.json":{"offset":10783291,"length":1190,"mode":420},"/node_modules/pgh/node_modules/dht-rpc/index.js":{"offset":10784481,"length":29079,"mode":420},"/node_modules/pgh/node_modules/dht-rpc/lib/commands.js":{"offset":10813560,"length":107,"mode":420},"/node_modules/pgh/node_modules/dht-rpc/lib/errors.js":{"offset":10813667,"length":719,"mode":420},"/node_modules/pgh/node_modules/dht-rpc/lib/health.js":{"offset":10814386,"length":2998,"mode":420},"/node_modules/pgh/node_modules/dht-rpc/lib/io.js":{"offset":10817384,"length":16605,"mode":420},"/node_modules/pgh/node_modules/dht-rpc/lib/peer.js":{"offset":10833989,"length":583,"mode":420},"/node_modules/pgh/node_modules/dht-rpc/lib/query.js":{"offset":10834572,"length":9899,"mode":420},"/node_modules/pgh/node_modules/dht-rpc/lib/session.js":{"offset":10844471,"length":1103,"mode":420},"/node_modules/pgh/node_modules/dht-rpc/package.json":{"offset":10845574,"length":1396,"mode":420},"/node_modules/pgh/node_modules/encryption-encoding/index.js":{"offset":10846970,"length":378,"mode":420},"/node_modules/pgh/node_modules/encryption-encoding/package.json":{"offset":10847348,"length":1234,"mode":420},"/node_modules/pgh/node_modules/encryption-encoding/spec/hyperschema/index.js":{"offset":10848582,"length":1749,"mode":420},"/node_modules/pgh/node_modules/events-universal/bare.js":{"offset":10850331,"length":40,"mode":420},"/node_modules/pgh/node_modules/events-universal/package.json":{"offset":10850371,"length":908,"mode":420},"/node_modules/pgh/node_modules/fast-fifo/fixed-size.js":{"offset":10851279,"length":875,"mode":420},"/node_modules/pgh/node_modules/fast-fifo/index.js":{"offset":10852154,"length":972,"mode":420},"/node_modules/pgh/node_modules/fast-fifo/package.json":{"offset":10853126,"length":682,"mode":420},"/node_modules/pgh/node_modules/fd-lock/index.js":{"offset":10853808,"length":1661,"mode":420},"/node_modules/pgh/node_modules/fd-lock/package.json":{"offset":10855469,"length":1099,"mode":420},"/node_modules/pgh/node_modules/flat-tree/index.js":{"offset":10856568,"length":8164,"mode":420},"/node_modules/pgh/node_modules/flat-tree/package.json":{"offset":10864732,"length":631,"mode":420},"/node_modules/pgh/node_modules/fs-native-extensions/binding.js":{"offset":10865363,"length":90,"mode":420},"/node_modules/pgh/node_modules/fs-native-extensions/index.js":{"offset":10865453,"length":7239,"mode":420},"/node_modules/pgh/node_modules/fs-native-extensions/package.json":{"offset":10872692,"length":1790,"mode":420},"/node_modules/pgh/node_modules/hyper-overlay/lib/chunk-scheduler.js":{"offset":10874482,"length":18133,"mode":420},"/node_modules/pgh/node_modules/hyper-overlay/lib/chunker.js":{"offset":10892615,"length":14180,"mode":420},"/node_modules/pgh/node_modules/hyper-overlay/lib/file-index.js":{"offset":10906795,"length":20487,"mode":420},"/node_modules/pgh/node_modules/hyper-overlay/lib/messages.js":{"offset":10927282,"length":15242,"mode":420},"/node_modules/pgh/node_modules/hyper-overlay/lib/protocol.js":{"offset":10942524,"length":50117,"mode":420},"/node_modules/pgh/node_modules/hyper-overlay/lib/transfer.js":{"offset":10992641,"length":33515,"mode":420},"/node_modules/pgh/node_modules/hyper-overlay/lib/tree.js":{"offset":11026156,"length":14077,"mode":420},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/b4a/index.js":{"offset":11040233,"length":4054,"mode":420},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/b4a/package.json":{"offset":11044287,"length":1245,"mode":420},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/bare-ansi-escapes/index.js":{"offset":11045532,"length":2255,"mode":420},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/bare-ansi-escapes/package.json":{"offset":11047787,"length":1178,"mode":420},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/bare-assert/index.js":{"offset":11048965,"length":2192,"mode":420},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/bare-assert/package.json":{"offset":11051157,"length":835,"mode":420},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/bare-events/index.js":{"offset":11051992,"length":8307,"mode":420},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/bare-events/lib/errors.js":{"offset":11060299,"length":671,"mode":420},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/bare-events/package.json":{"offset":11060970,"length":1504,"mode":420},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/bare-fs/binding.js":{"offset":11062474,"length":33,"mode":420},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/bare-fs/index.js":{"offset":11062507,"length":58907,"mode":420},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/bare-fs/lib/constants.js":{"offset":11121414,"length":1494,"mode":420},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/bare-fs/lib/errors.js":{"offset":11122908,"length":1163,"mode":420},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/bare-fs/package.json":{"offset":11124071,"length":1654,"mode":420},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/bare-fs/promises.js":{"offset":11125725,"length":2401,"mode":420},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/bare-inspect/binding.js":{"offset":11128126,"length":33,"mode":420},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/bare-inspect/index.js":{"offset":11128159,"length":23856,"mode":420},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/bare-inspect/package.json":{"offset":11152015,"length":1041,"mode":420},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/bare-os/binding.js":{"offset":11153056,"length":33,"mode":420},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/bare-os/index.js":{"offset":11153089,"length":3224,"mode":420},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/bare-os/lib/constants.js":{"offset":11156313,"length":143,"mode":420},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/bare-os/lib/errors.js":{"offset":11156456,"length":453,"mode":420},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/bare-os/package.json":{"offset":11156909,"length":1257,"mode":420},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/bare-path/index.js":{"offset":11158166,"length":287,"mode":420},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/bare-path/lib/constants.js":{"offset":11158453,"length":247,"mode":420},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/bare-path/lib/posix.js":{"offset":11158700,"length":6005,"mode":420},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/bare-path/lib/shared.js":{"offset":11164705,"length":1926,"mode":420},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/bare-path/lib/win32.js":{"offset":11166631,"length":13591,"mode":420},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/bare-path/package.json":{"offset":11180222,"length":1036,"mode":420},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/bare-stream/index.js":{"offset":11181258,"length":10904,"mode":420},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/bare-stream/package.json":{"offset":11192162,"length":1676,"mode":420},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/bare-stream/web.js":{"offset":11193838,"length":13980,"mode":420},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/bare-type/binding.js":{"offset":11207818,"length":33,"mode":420},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/bare-type/index.js":{"offset":11207851,"length":4566,"mode":420},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/bare-type/package.json":{"offset":11212417,"length":938,"mode":420},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/bare-url/binding.js":{"offset":11213355,"length":33,"mode":420},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/bare-url/index.js":{"offset":11213388,"length":9449,"mode":420},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/bare-url/lib/errors.js":{"offset":11222837,"length":778,"mode":420},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/bare-url/lib/url-search-params.js":{"offset":11223615,"length":4331,"mode":420},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/bare-url/package.json":{"offset":11227946,"length":1219,"mode":420},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/codecs/index.js":{"offset":11229165,"length":2416,"mode":420},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/codecs/package.json":{"offset":11231581,"length":655,"mode":420},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/compact-encoding/endian.js":{"offset":11232236,"length":105,"mode":420},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/compact-encoding/index.js":{"offset":11232341,"length":26135,"mode":420},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/compact-encoding/lexint.js":{"offset":11258476,"length":2851,"mode":420},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/compact-encoding/package.json":{"offset":11261327,"length":866,"mode":420},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/compact-encoding/raw.js":{"offset":11262193,"length":3977,"mode":420},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/debounceify/index.js":{"offset":11266170,"length":585,"mode":420},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/debounceify/package.json":{"offset":11266755,"length":567,"mode":420},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/events-universal/bare.js":{"offset":11267322,"length":40,"mode":420},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/events-universal/package.json":{"offset":11267362,"length":908,"mode":420},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/fast-fifo/fixed-size.js":{"offset":11268270,"length":875,"mode":420},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/fast-fifo/index.js":{"offset":11269145,"length":972,"mode":420},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/fast-fifo/package.json":{"offset":11270117,"length":682,"mode":420},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/hyperbee/index.js":{"offset":11270799,"length":46445,"mode":420},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/hyperbee/iterators/diff.js":{"offset":11317244,"length":5358,"mode":420},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/hyperbee/iterators/history.js":{"offset":11322602,"length":1631,"mode":420},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/hyperbee/iterators/local.js":{"offset":11324233,"length":1170,"mode":420},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/hyperbee/iterators/range.js":{"offset":11325403,"length":5869,"mode":420},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/hyperbee/lib/extension.js":{"offset":11331272,"length":3488,"mode":420},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/hyperbee/lib/messages.js":{"offset":11334760,"length":28162,"mode":420},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/hyperbee/package.json":{"offset":11362922,"length":1585,"mode":420},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/hypercore-crypto/index.js":{"offset":11364507,"length":5031,"mode":420},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/hypercore-crypto/package.json":{"offset":11369538,"length":1007,"mode":420},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/hypercore-errors/index.js":{"offset":11370545,"length":4896,"mode":420},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/hypercore-errors/package.json":{"offset":11375441,"length":688,"mode":420},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/hypercore-id-encoding/index.js":{"offset":11376129,"length":923,"mode":420},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/hypercore-id-encoding/package.json":{"offset":11377052,"length":799,"mode":420},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/mutexify/index.js":{"offset":11377851,"length":536,"mode":420},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/mutexify/package.json":{"offset":11378387,"length":679,"mode":420},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/mutexify/promise.js":{"offset":11379066,"length":324,"mode":420},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/protocol-buffers-encodings/index.js":{"offset":11379390,"length":6489,"mode":420},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/protocol-buffers-encodings/package.json":{"offset":11385879,"length":719,"mode":420},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/queue-tick/package.json":{"offset":11386598,"length":669,"mode":420},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/queue-tick/process-next-tick.js":{"offset":11387267,"length":160,"mode":420},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/queue-tick/queue-microtask.js":{"offset":11387427,"length":108,"mode":420},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/rache/index.js":{"offset":11387535,"length":2463,"mode":420},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/rache/package.json":{"offset":11389998,"length":602,"mode":420},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/ready-resource/index.js":{"offset":11390600,"length":1091,"mode":420},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/ready-resource/package.json":{"offset":11391691,"length":812,"mode":420},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/require-addon/lib/bare.js":{"offset":11392503,"length":45,"mode":420},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/require-addon/package.json":{"offset":11392548,"length":1449,"mode":420},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/resolve-reject-promise/index.js":{"offset":11393997,"length":443,"mode":420},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/resolve-reject-promise/package.json":{"offset":11394440,"length":581,"mode":420},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/safety-catch/index.js":{"offset":11395021,"length":543,"mode":420},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/safety-catch/package.json":{"offset":11395564,"length":547,"mode":420},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/signed-varint/index.js":{"offset":11396111,"length":435,"mode":420},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/signed-varint/package.json":{"offset":11396546,"length":523,"mode":420},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/sodium-native/binding.js":{"offset":11397069,"length":89,"mode":420},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/sodium-native/index.js":{"offset":11397158,"length":107894,"mode":420},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/sodium-native/package.json":{"offset":11505052,"length":1478,"mode":420},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/sodium-universal/index.js":{"offset":11506530,"length":42,"mode":420},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/sodium-universal/package.json":{"offset":11506572,"length":1217,"mode":420},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/streamx/index.js":{"offset":11507789,"length":34248,"mode":420},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/streamx/lib/errors.js":{"offset":11542037,"length":1096,"mode":420},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/streamx/package.json":{"offset":11543133,"length":1225,"mode":420},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/teex/index.js":{"offset":11544358,"length":1107,"mode":420},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/teex/package.json":{"offset":11545465,"length":608,"mode":420},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/text-decoder/index.js":{"offset":11546073,"length":1371,"mode":420},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/text-decoder/lib/pass-through-decoder.js":{"offset":11547444,"length":269,"mode":420},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/text-decoder/lib/utf8-decoder.js":{"offset":11547713,"length":4429,"mode":420},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/text-decoder/package.json":{"offset":11552142,"length":990,"mode":420},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/unslab/index.js":{"offset":11553132,"length":913,"mode":420},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/unslab/package.json":{"offset":11554045,"length":613,"mode":420},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/varint/decode.js":{"offset":11554658,"length":508,"mode":420},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/varint/encode.js":{"offset":11555166,"length":452,"mode":420},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/varint/index.js":{"offset":11555618,"length":134,"mode":420},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/varint/length.js":{"offset":11555752,"length":471,"mode":420},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/varint/package.json":{"offset":11556223,"length":511,"mode":420},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/which-runtime/index.js":{"offset":11556734,"length":1469,"mode":420},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/which-runtime/package.json":{"offset":11558203,"length":863,"mode":420},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/z32/index.js":{"offset":11559066,"length":2654,"mode":420},"/node_modules/pgh/node_modules/hyper-overlay/node_modules/z32/package.json":{"offset":11561720,"length":701,"mode":420},"/node_modules/pgh/node_modules/hyper-overlay/package.json":{"offset":11562421,"length":2132,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/index.js":{"offset":11564553,"length":424,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/lib/client.js":{"offset":11564977,"length":14860,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/lib/frame.js":{"offset":11579837,"length":1862,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/lib/service.js":{"offset":11581699,"length":11770,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/lib/transports/http.js":{"offset":11593469,"length":7998,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/lib/transports/swarm-rpc.js":{"offset":11601467,"length":4427,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/lib/transports/swarm.js":{"offset":11605894,"length":8308,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/lib/transports/uds.js":{"offset":11614202,"length":8779,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/lib/uds-backend.js":{"offset":11622981,"length":1568,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/@hyperswarm/secret-stream/index.js":{"offset":11624549,"length":16722,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/@hyperswarm/secret-stream/lib/bridge.js":{"offset":11641271,"length":1273,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/@hyperswarm/secret-stream/lib/handshake.js":{"offset":11642544,"length":1950,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/@hyperswarm/secret-stream/package.json":{"offset":11644494,"length":1132,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/adaptive-timeout/index.js":{"offset":11645626,"length":2424,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/adaptive-timeout/package.json":{"offset":11648050,"length":972,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/b4a/index.js":{"offset":11649022,"length":4054,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/b4a/package.json":{"offset":11653076,"length":1245,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-abort/binding.js":{"offset":11654321,"length":33,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-abort/index.js":{"offset":11654354,"length":69,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-abort/package.json":{"offset":11654423,"length":886,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-ansi-escapes/index.js":{"offset":11655309,"length":2255,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-ansi-escapes/package.json":{"offset":11657564,"length":1178,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-assert/index.js":{"offset":11658742,"length":2192,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-assert/package.json":{"offset":11660934,"length":835,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-dns/binding.js":{"offset":11661769,"length":33,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-dns/index.js":{"offset":11661802,"length":1345,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-dns/package.json":{"offset":11663147,"length":968,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-env/index.js":{"offset":11664115,"length":1023,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-env/package.json":{"offset":11665138,"length":649,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-events/index.js":{"offset":11665787,"length":8033,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-events/lib/errors.js":{"offset":11673820,"length":671,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-events/package.json":{"offset":11674491,"length":1399,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-fs/binding.js":{"offset":11675890,"length":33,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-fs/index.js":{"offset":11675923,"length":58907,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-fs/lib/constants.js":{"offset":11734830,"length":1494,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-fs/lib/errors.js":{"offset":11736324,"length":1163,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-fs/package.json":{"offset":11737487,"length":1654,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-fs/promises.js":{"offset":11739141,"length":2401,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-hrtime/binding.js":{"offset":11741542,"length":33,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-hrtime/index.js":{"offset":11741575,"length":319,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-hrtime/package.json":{"offset":11741894,"length":905,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-http-parser/index.js":{"offset":11742799,"length":17480,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-http-parser/lib/errors.js":{"offset":11760279,"length":845,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-http-parser/package.json":{"offset":11761124,"length":793,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-http1/index.js":{"offset":11761917,"length":2188,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-http1/lib/agent.js":{"offset":11764105,"length":4498,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-http1/lib/client-connection.js":{"offset":11768603,"length":3663,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-http1/lib/client-request.js":{"offset":11772266,"length":3286,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-http1/lib/constants.js":{"offset":11775552,"length":2016,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-http1/lib/errors.js":{"offset":11777568,"length":634,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-http1/lib/incoming-message.js":{"offset":11778202,"length":1568,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-http1/lib/outgoing-message.js":{"offset":11779770,"length":1291,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-http1/lib/server-connection.js":{"offset":11781061,"length":4041,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-http1/lib/server-response.js":{"offset":11785102,"length":3183,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-http1/lib/server.js":{"offset":11788285,"length":977,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-http1/package.json":{"offset":11789262,"length":1510,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-inspect/binding.js":{"offset":11790772,"length":33,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-inspect/index.js":{"offset":11790805,"length":23856,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-inspect/package.json":{"offset":11814661,"length":1041,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-net/index.js":{"offset":11815702,"length":6868,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-net/node_modules/bare-dns/binding.js":{"offset":11822570,"length":33,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-net/node_modules/bare-dns/index.js":{"offset":11822603,"length":860,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-net/node_modules/bare-dns/package.json":{"offset":11823463,"length":772,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-net/node_modules/bare-pipe/binding.js":{"offset":11824235,"length":33,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-net/node_modules/bare-pipe/index.js":{"offset":11824268,"length":10947,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-net/node_modules/bare-pipe/lib/constants.js":{"offset":11835215,"length":188,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-net/node_modules/bare-pipe/lib/errors.js":{"offset":11835403,"length":668,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-net/node_modules/bare-pipe/package.json":{"offset":11836071,"length":962,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-net/node_modules/bare-tcp/binding.js":{"offset":11837033,"length":33,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-net/node_modules/bare-tcp/index.js":{"offset":11837066,"length":12710,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-net/node_modules/bare-tcp/lib/constants.js":{"offset":11849776,"length":167,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-net/node_modules/bare-tcp/lib/errors.js":{"offset":11849943,"length":799,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-net/node_modules/bare-tcp/lib/ip.js":{"offset":11850742,"length":2179,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-net/node_modules/bare-tcp/package.json":{"offset":11852921,"length":959,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-net/package.json":{"offset":11853880,"length":793,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-os/binding.js":{"offset":11854673,"length":33,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-os/index.js":{"offset":11854706,"length":3224,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-os/lib/constants.js":{"offset":11857930,"length":143,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-os/lib/errors.js":{"offset":11858073,"length":453,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-os/package.json":{"offset":11858526,"length":1257,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-path/index.js":{"offset":11859783,"length":306,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-path/lib/constants.js":{"offset":11860089,"length":247,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-path/lib/posix.js":{"offset":11860336,"length":5991,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-path/lib/shared.js":{"offset":11866327,"length":1888,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-path/lib/win32.js":{"offset":11868215,"length":13427,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-path/package.json":{"offset":11881642,"length":796,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-pipe/binding.js":{"offset":11882438,"length":33,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-pipe/index.js":{"offset":11882471,"length":10769,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-pipe/lib/constants.js":{"offset":11893240,"length":208,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-pipe/lib/errors.js":{"offset":11893448,"length":599,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-pipe/package.json":{"offset":11894047,"length":1256,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-process/index.js":{"offset":11895303,"length":2689,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-process/package.json":{"offset":11897992,"length":1255,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-signals/binding.js":{"offset":11899247,"length":33,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-signals/index.js":{"offset":11899280,"length":1517,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-signals/lib/emitter.js":{"offset":11900797,"length":1278,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-signals/lib/errors.js":{"offset":11902075,"length":479,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-signals/package.json":{"offset":11902554,"length":1112,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-stdio/binding.js":{"offset":11903666,"length":33,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-stdio/index.js":{"offset":11903699,"length":1525,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-stdio/package.json":{"offset":11905224,"length":1319,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-stream/index.js":{"offset":11906543,"length":10913,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-stream/package.json":{"offset":11917456,"length":1655,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-stream/web.js":{"offset":11919111,"length":13980,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-tcp/binding.js":{"offset":11933091,"length":33,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-tcp/index.js":{"offset":11933124,"length":15054,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-tcp/lib/constants.js":{"offset":11948178,"length":167,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-tcp/lib/errors.js":{"offset":11948345,"length":711,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-tcp/lib/ip.js":{"offset":11949056,"length":2204,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-tcp/package.json":{"offset":11951260,"length":1243,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-tty/binding.js":{"offset":11952503,"length":33,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-tty/index.js":{"offset":11952536,"length":4469,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-tty/lib/constants.js":{"offset":11957005,"length":442,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-tty/package.json":{"offset":11957447,"length":1097,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-type/binding.js":{"offset":11958544,"length":33,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-type/index.js":{"offset":11958577,"length":4566,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-type/package.json":{"offset":11963143,"length":938,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-url/binding.js":{"offset":11964081,"length":33,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-url/index.js":{"offset":11964114,"length":9308,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-url/lib/errors.js":{"offset":11973422,"length":778,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-url/lib/url-search-params.js":{"offset":11974200,"length":4331,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bare-url/package.json":{"offset":11978531,"length":1219,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bits-to-bytes/index.js":{"offset":11979750,"length":3083,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bits-to-bytes/package.json":{"offset":11982833,"length":708,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/blind-relay/index.js":{"offset":11983541,"length":13256,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/blind-relay/lib/errors.js":{"offset":11996797,"length":1036,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/blind-relay/node_modules/compact-encoding/endian.js":{"offset":11997833,"length":105,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/blind-relay/node_modules/compact-encoding/index.js":{"offset":11997938,"length":24454,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/blind-relay/node_modules/compact-encoding/lexint.js":{"offset":12022392,"length":2851,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/blind-relay/node_modules/compact-encoding/package.json":{"offset":12025243,"length":786,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/blind-relay/node_modules/compact-encoding/raw.js":{"offset":12026029,"length":4093,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/blind-relay/package.json":{"offset":12030122,"length":1232,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bogon/index.js":{"offset":12031354,"length":4052,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bogon/node_modules/compact-encoding/endian.js":{"offset":12035406,"length":105,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bogon/node_modules/compact-encoding/index.js":{"offset":12035511,"length":24454,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bogon/node_modules/compact-encoding/lexint.js":{"offset":12059965,"length":2851,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bogon/node_modules/compact-encoding/package.json":{"offset":12062816,"length":786,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bogon/node_modules/compact-encoding/raw.js":{"offset":12063602,"length":4093,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/bogon/package.json":{"offset":12067695,"length":657,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/compact-encoding-bitfield/index.js":{"offset":12068352,"length":1963,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/compact-encoding-bitfield/node_modules/compact-encoding/endian.js":{"offset":12070315,"length":105,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/compact-encoding-bitfield/node_modules/compact-encoding/index.js":{"offset":12070420,"length":24454,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/compact-encoding-bitfield/node_modules/compact-encoding/lexint.js":{"offset":12094874,"length":2851,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/compact-encoding-bitfield/node_modules/compact-encoding/package.json":{"offset":12097725,"length":786,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/compact-encoding-bitfield/node_modules/compact-encoding/raw.js":{"offset":12098511,"length":4093,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/compact-encoding-bitfield/package.json":{"offset":12102604,"length":827,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/compact-encoding-net/index.js":{"offset":12103431,"length":4109,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/compact-encoding-net/node_modules/compact-encoding/endian.js":{"offset":12107540,"length":105,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/compact-encoding-net/node_modules/compact-encoding/index.js":{"offset":12107645,"length":24454,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/compact-encoding-net/node_modules/compact-encoding/lexint.js":{"offset":12132099,"length":2851,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/compact-encoding-net/node_modules/compact-encoding/package.json":{"offset":12134950,"length":786,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/compact-encoding-net/node_modules/compact-encoding/raw.js":{"offset":12135736,"length":4093,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/compact-encoding-net/package.json":{"offset":12139829,"length":734,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/compact-encoding/endian.js":{"offset":12140563,"length":105,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/compact-encoding/index.js":{"offset":12140668,"length":26135,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/compact-encoding/lexint.js":{"offset":12166803,"length":2851,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/compact-encoding/package.json":{"offset":12169654,"length":866,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/compact-encoding/raw.js":{"offset":12170520,"length":3977,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/dht-rpc/index.js":{"offset":12174497,"length":29079,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/dht-rpc/lib/commands.js":{"offset":12203576,"length":107,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/dht-rpc/lib/errors.js":{"offset":12203683,"length":719,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/dht-rpc/lib/health.js":{"offset":12204402,"length":2998,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/dht-rpc/lib/io.js":{"offset":12207400,"length":16605,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/dht-rpc/lib/peer.js":{"offset":12224005,"length":631,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/dht-rpc/lib/query.js":{"offset":12224636,"length":9899,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/dht-rpc/lib/session.js":{"offset":12234535,"length":1103,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/dht-rpc/node_modules/compact-encoding/endian.js":{"offset":12235638,"length":105,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/dht-rpc/node_modules/compact-encoding/index.js":{"offset":12235743,"length":24454,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/dht-rpc/node_modules/compact-encoding/lexint.js":{"offset":12260197,"length":2851,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/dht-rpc/node_modules/compact-encoding/package.json":{"offset":12263048,"length":786,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/dht-rpc/node_modules/compact-encoding/raw.js":{"offset":12263834,"length":4093,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/dht-rpc/package.json":{"offset":12267927,"length":1397,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/events-universal/bare.js":{"offset":12269324,"length":40,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/events-universal/package.json":{"offset":12269364,"length":908,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/fast-fifo/fixed-size.js":{"offset":12270272,"length":875,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/fast-fifo/index.js":{"offset":12271147,"length":972,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/fast-fifo/package.json":{"offset":12272119,"length":682,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/hypercore-crypto/index.js":{"offset":12272801,"length":5040,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/hypercore-crypto/node_modules/compact-encoding/endian.js":{"offset":12277841,"length":105,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/hypercore-crypto/node_modules/compact-encoding/index.js":{"offset":12277946,"length":24454,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/hypercore-crypto/node_modules/compact-encoding/lexint.js":{"offset":12302400,"length":2851,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/hypercore-crypto/node_modules/compact-encoding/package.json":{"offset":12305251,"length":786,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/hypercore-crypto/node_modules/compact-encoding/raw.js":{"offset":12306037,"length":4093,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/hypercore-crypto/package.json":{"offset":12310130,"length":771,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/hypercore-id-encoding/index.js":{"offset":12310901,"length":923,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/hypercore-id-encoding/package.json":{"offset":12311824,"length":799,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperdht-address/index.js":{"offset":12312623,"length":357,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperdht-address/node_modules/compact-encoding/endian.js":{"offset":12312980,"length":105,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperdht-address/node_modules/compact-encoding/index.js":{"offset":12313085,"length":24454,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperdht-address/node_modules/compact-encoding/lexint.js":{"offset":12337539,"length":2851,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperdht-address/node_modules/compact-encoding/package.json":{"offset":12340390,"length":786,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperdht-address/node_modules/compact-encoding/raw.js":{"offset":12341176,"length":4093,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperdht-address/package.json":{"offset":12345269,"length":1047,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperdht-address/spec/hyperschema/index.js":{"offset":12346316,"length":2106,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperdht/index.js":{"offset":12348422,"length":16611,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperdht/lib/announcer.js":{"offset":12365033,"length":7533,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperdht/lib/connect.js":{"offset":12372566,"length":24788,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperdht/lib/connection-pool.js":{"offset":12397354,"length":3050,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperdht/lib/constants.js":{"offset":12400404,"length":1276,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperdht/lib/crypto.js":{"offset":12401680,"length":632,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperdht/lib/encode.js":{"offset":12402312,"length":446,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperdht/lib/errors.js":{"offset":12402758,"length":3712,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperdht/lib/holepuncher.js":{"offset":12406470,"length":10003,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperdht/lib/messages.js":{"offset":12416473,"length":11789,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperdht/lib/nat.js":{"offset":12428262,"length":4714,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperdht/lib/noise-wrap.js":{"offset":12432976,"length":1669,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperdht/lib/persistent.js":{"offset":12434645,"length":8361,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperdht/lib/raw-stream-set.js":{"offset":12443006,"length":1511,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperdht/lib/router.js":{"offset":12444517,"length":6978,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperdht/lib/secure-payload.js":{"offset":12451495,"length":1491,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperdht/lib/semaphore.js":{"offset":12452986,"length":1577,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperdht/lib/server.js":{"offset":12454563,"length":21300,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperdht/lib/sleeper.js":{"offset":12475863,"length":690,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperdht/lib/socket-pool.js":{"offset":12476553,"length":4343,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperdht/node_modules/compact-encoding/endian.js":{"offset":12480896,"length":105,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperdht/node_modules/compact-encoding/index.js":{"offset":12481001,"length":24454,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperdht/node_modules/compact-encoding/lexint.js":{"offset":12505455,"length":2851,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperdht/node_modules/compact-encoding/package.json":{"offset":12508306,"length":786,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperdht/node_modules/compact-encoding/raw.js":{"offset":12509092,"length":4093,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperdht/package.json":{"offset":12513185,"length":2007,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperschema/node_modules/compact-encoding/endian.js":{"offset":12515192,"length":105,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperschema/node_modules/compact-encoding/index.js":{"offset":12515297,"length":24454,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperschema/node_modules/compact-encoding/lexint.js":{"offset":12539751,"length":2851,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperschema/node_modules/compact-encoding/package.json":{"offset":12542602,"length":786,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperschema/node_modules/compact-encoding/raw.js":{"offset":12543388,"length":4093,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperschema/package.json":{"offset":12547481,"length":1556,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperschema/runtime.cjs":{"offset":12549037,"length":54,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperswarm/index.js":{"offset":12549091,"length":18589,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperswarm/lib/bulk-timer.js":{"offset":12567680,"length":728,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperswarm/lib/connection-set.js":{"offset":12568408,"length":823,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperswarm/lib/peer-discovery.js":{"offset":12569231,"length":9296,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperswarm/lib/peer-info.js":{"offset":12578527,"length":2688,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperswarm/lib/retry-timer.js":{"offset":12581215,"length":1889,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/hyperswarm/package.json":{"offset":12583104,"length":1515,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/kademlia-routing-table/index.js":{"offset":12584619,"length":4145,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/kademlia-routing-table/package.json":{"offset":12588764,"length":967,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/nanoassert/index.js":{"offset":12589731,"length":438,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/nanoassert/package.json":{"offset":12590169,"length":647,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/nat-sampler/index.js":{"offset":12590816,"length":1550,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/nat-sampler/package.json":{"offset":12592366,"length":608,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/noise-curve-ed/index.js":{"offset":12592974,"length":1642,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/noise-curve-ed/package.json":{"offset":12594616,"length":808,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/noise-handshake/cipher.js":{"offset":12595424,"length":2817,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/noise-handshake/dh.js":{"offset":12598241,"length":1439,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/noise-handshake/hkdf.js":{"offset":12599680,"length":1092,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/noise-handshake/hmac.js":{"offset":12600772,"length":1273,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/noise-handshake/noise.js":{"offset":12602045,"length":6437,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/noise-handshake/package.json":{"offset":12608482,"length":638,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/noise-handshake/symmetric-state.js":{"offset":12609120,"length":2206,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/protomux/index.js":{"offset":12611326,"length":19741,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/protomux/package.json":{"offset":12631067,"length":1059,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/queue-tick/package.json":{"offset":12632126,"length":669,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/queue-tick/process-next-tick.js":{"offset":12632795,"length":160,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/queue-tick/queue-microtask.js":{"offset":12632955,"length":108,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/ready-resource/index.js":{"offset":12633063,"length":1091,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/ready-resource/package.json":{"offset":12634154,"length":812,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/record-cache/index.js":{"offset":12634966,"length":3668,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/record-cache/package.json":{"offset":12638634,"length":612,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/require-addon/lib/bare.js":{"offset":12639246,"length":45,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/require-addon/package.json":{"offset":12639291,"length":1449,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/safety-catch/index.js":{"offset":12640740,"length":543,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/safety-catch/package.json":{"offset":12641283,"length":547,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/shuffled-priority-queue/index.js":{"offset":12641830,"length":2607,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/shuffled-priority-queue/package.json":{"offset":12644437,"length":691,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/signal-promise/index.js":{"offset":12645128,"length":1252,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/signal-promise/package.json":{"offset":12646380,"length":503,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/sodium-native/binding.js":{"offset":12646883,"length":89,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/sodium-native/index.js":{"offset":12646972,"length":107894,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/sodium-native/package.json":{"offset":12754866,"length":1478,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/sodium-secretstream/index.js":{"offset":12756344,"length":2257,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/sodium-secretstream/package.json":{"offset":12758601,"length":657,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/sodium-universal/index.js":{"offset":12759258,"length":42,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/sodium-universal/package.json":{"offset":12759300,"length":1217,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/streamx/index.js":{"offset":12760517,"length":33510,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/streamx/package.json":{"offset":12794027,"length":912,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/teex/index.js":{"offset":12794939,"length":1107,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/teex/package.json":{"offset":12796046,"length":608,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/text-decoder/index.js":{"offset":12796654,"length":1371,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/text-decoder/lib/pass-through-decoder.js":{"offset":12798025,"length":269,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/text-decoder/lib/utf8-decoder.js":{"offset":12798294,"length":4429,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/text-decoder/package.json":{"offset":12802723,"length":990,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/time-ordered-set/index.js":{"offset":12803713,"length":1444,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/time-ordered-set/package.json":{"offset":12805157,"length":666,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/timeout-refresh/browser.js":{"offset":12805823,"length":1098,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/timeout-refresh/index.js":{"offset":12806921,"length":184,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/timeout-refresh/node.js":{"offset":12807105,"length":928,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/timeout-refresh/package.json":{"offset":12808033,"length":619,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/udx-native/binding.js":{"offset":12808652,"length":90,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/udx-native/lib/ip.js":{"offset":12808742,"length":2204,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/udx-native/lib/network-interfaces.js":{"offset":12810946,"length":1341,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/udx-native/lib/socket.js":{"offset":12812287,"length":7557,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/udx-native/lib/stream.js":{"offset":12819844,"length":12706,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/udx-native/lib/udx.js":{"offset":12832550,"length":2868,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/udx-native/package.json":{"offset":12835418,"length":1615,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/unordered-set/index.js":{"offset":12837033,"length":677,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/unordered-set/package.json":{"offset":12837710,"length":654,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/unslab/index.js":{"offset":12838364,"length":913,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/unslab/package.json":{"offset":12839277,"length":613,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/which-runtime/index.js":{"offset":12839890,"length":1231,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/which-runtime/package.json":{"offset":12841121,"length":602,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/xache/index.js":{"offset":12841723,"length":2378,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/xache/package.json":{"offset":12844101,"length":584,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/z32/index.js":{"offset":12844685,"length":2654,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/node_modules/z32/package.json":{"offset":12847339,"length":701,"mode":420},"/node_modules/pgh/node_modules/hyper-svc/package.json":{"offset":12848040,"length":1873,"mode":420},"/node_modules/pgh/node_modules/hyperbee/index.js":{"offset":12849913,"length":46445,"mode":420},"/node_modules/pgh/node_modules/hyperbee/iterators/diff.js":{"offset":12896358,"length":5358,"mode":420},"/node_modules/pgh/node_modules/hyperbee/iterators/history.js":{"offset":12901716,"length":1631,"mode":420},"/node_modules/pgh/node_modules/hyperbee/iterators/local.js":{"offset":12903347,"length":1170,"mode":420},"/node_modules/pgh/node_modules/hyperbee/iterators/range.js":{"offset":12904517,"length":5869,"mode":420},"/node_modules/pgh/node_modules/hyperbee/lib/extension.js":{"offset":12910386,"length":3488,"mode":420},"/node_modules/pgh/node_modules/hyperbee/lib/messages.js":{"offset":12913874,"length":28162,"mode":420},"/node_modules/pgh/node_modules/hyperbee/package.json":{"offset":12942036,"length":1585,"mode":420},"/node_modules/pgh/node_modules/hypercore-crypto/index.js":{"offset":12943621,"length":5031,"mode":420},"/node_modules/pgh/node_modules/hypercore-crypto/package.json":{"offset":12948652,"length":1007,"mode":420},"/node_modules/pgh/node_modules/hypercore-errors/index.js":{"offset":12949659,"length":4896,"mode":420},"/node_modules/pgh/node_modules/hypercore-errors/package.json":{"offset":12954555,"length":688,"mode":420},"/node_modules/pgh/node_modules/hypercore-id-encoding/index.js":{"offset":12955243,"length":923,"mode":420},"/node_modules/pgh/node_modules/hypercore-id-encoding/package.json":{"offset":12956166,"length":799,"mode":420},"/node_modules/pgh/node_modules/hypercore-storage/encoding/external.js":{"offset":12956965,"length":264,"mode":420},"/node_modules/pgh/node_modules/hypercore-storage/encoding/spec/hyperschema/index.js":{"offset":12957229,"length":16796,"mode":420},"/node_modules/pgh/node_modules/hypercore-storage/index.js":{"offset":12974025,"length":32609,"mode":420},"/node_modules/pgh/node_modules/hypercore-storage/lib/block-dependency-stream.js":{"offset":13006634,"length":2557,"mode":420},"/node_modules/pgh/node_modules/hypercore-storage/lib/close-error-stream.js":{"offset":13009191,"length":276,"mode":420},"/node_modules/pgh/node_modules/hypercore-storage/lib/keys.js":{"offset":13009467,"length":8751,"mode":420},"/node_modules/pgh/node_modules/hypercore-storage/lib/streams.js":{"offset":13018218,"length":5686,"mode":420},"/node_modules/pgh/node_modules/hypercore-storage/lib/tx.js":{"offset":13023904,"length":10227,"mode":420},"/node_modules/pgh/node_modules/hypercore-storage/lib/view.js":{"offset":13034131,"length":8379,"mode":420},"/node_modules/pgh/node_modules/hypercore-storage/migrations/0/index.js":{"offset":13042510,"length":20125,"mode":420},"/node_modules/pgh/node_modules/hypercore-storage/migrations/0/messages.js":{"offset":13062635,"length":26402,"mode":420},"/node_modules/pgh/node_modules/hypercore-storage/migrations/1/index.js":{"offset":13089037,"length":1181,"mode":420},"/node_modules/pgh/node_modules/hypercore-storage/package.json":{"offset":13090218,"length":1804,"mode":420},"/node_modules/pgh/node_modules/hypercore/index.js":{"offset":13092022,"length":40191,"mode":420},"/node_modules/pgh/node_modules/hypercore/lib/audit.js":{"offset":13132213,"length":3834,"mode":420},"/node_modules/pgh/node_modules/hypercore/lib/bit-interlude.js":{"offset":13136047,"length":4257,"mode":420},"/node_modules/pgh/node_modules/hypercore/lib/bitfield.js":{"offset":13140304,"length":12115,"mode":420},"/node_modules/pgh/node_modules/hypercore/lib/caps.js":{"offset":13152419,"length":1537,"mode":420},"/node_modules/pgh/node_modules/hypercore/lib/compat.js":{"offset":13153956,"length":477,"mode":420},"/node_modules/pgh/node_modules/hypercore/lib/copy-prologue.js":{"offset":13154433,"length":5999,"mode":420},"/node_modules/pgh/node_modules/hypercore/lib/core.js":{"offset":13160432,"length":28356,"mode":420},"/node_modules/pgh/node_modules/hypercore/lib/default-encryption.js":{"offset":13188788,"length":3453,"mode":420},"/node_modules/pgh/node_modules/hypercore/lib/download.js":{"offset":13192241,"length":1278,"mode":420},"/node_modules/pgh/node_modules/hypercore/lib/feature-flags.js":{"offset":13193519,"length":38,"mode":420},"/node_modules/pgh/node_modules/hypercore/lib/fully-remote-proof.js":{"offset":13193557,"length":3470,"mode":420},"/node_modules/pgh/node_modules/hypercore/lib/hotswap-queue.js":{"offset":13197027,"length":1498,"mode":420},"/node_modules/pgh/node_modules/hypercore/lib/info.js":{"offset":13198525,"length":1517,"mode":420},"/node_modules/pgh/node_modules/hypercore/lib/inspect.js":{"offset":13200042,"length":1983,"mode":420},"/node_modules/pgh/node_modules/hypercore/lib/mark-bitfield.js":{"offset":13202025,"length":2589,"mode":420},"/node_modules/pgh/node_modules/hypercore/lib/merkle-tree.js":{"offset":13204614,"length":34353,"mode":420},"/node_modules/pgh/node_modules/hypercore/lib/messages.js":{"offset":13238967,"length":28313,"mode":420},"/node_modules/pgh/node_modules/hypercore/lib/multisig.js":{"offset":13267280,"length":2851,"mode":420},"/node_modules/pgh/node_modules/hypercore/lib/mutex.js":{"offset":13270131,"length":1028,"mode":420},"/node_modules/pgh/node_modules/hypercore/lib/receiver-queue.js":{"offset":13271159,"length":1413,"mode":420},"/node_modules/pgh/node_modules/hypercore/lib/remote-bitfield.js":{"offset":13272572,"length":8124,"mode":420},"/node_modules/pgh/node_modules/hypercore/lib/replicator.js":{"offset":13280696,"length":87688,"mode":420},"/node_modules/pgh/node_modules/hypercore/lib/session-state.js":{"offset":13368384,"length":36078,"mode":420},"/node_modules/pgh/node_modules/hypercore/lib/streams.js":{"offset":13404462,"length":3034,"mode":420},"/node_modules/pgh/node_modules/hypercore/lib/verifier.js":{"offset":13407496,"length":9926,"mode":420},"/node_modules/pgh/node_modules/hypercore/lib/wants.js":{"offset":13417422,"length":7204,"mode":420},"/node_modules/pgh/node_modules/hypercore/package.json":{"offset":13424626,"length":2196,"mode":420},"/node_modules/pgh/node_modules/hyperdht-address/index.js":{"offset":13426822,"length":357,"mode":420},"/node_modules/pgh/node_modules/hyperdht-address/package.json":{"offset":13427179,"length":1046,"mode":420},"/node_modules/pgh/node_modules/hyperdht-address/spec/hyperschema/index.js":{"offset":13428225,"length":2106,"mode":420},"/node_modules/pgh/node_modules/hyperdht/index.js":{"offset":13430331,"length":16611,"mode":420},"/node_modules/pgh/node_modules/hyperdht/lib/announcer.js":{"offset":13446942,"length":7533,"mode":420},"/node_modules/pgh/node_modules/hyperdht/lib/connect.js":{"offset":13454475,"length":24788,"mode":420},"/node_modules/pgh/node_modules/hyperdht/lib/connection-pool.js":{"offset":13479263,"length":3050,"mode":420},"/node_modules/pgh/node_modules/hyperdht/lib/constants.js":{"offset":13482313,"length":1276,"mode":420},"/node_modules/pgh/node_modules/hyperdht/lib/crypto.js":{"offset":13483589,"length":632,"mode":420},"/node_modules/pgh/node_modules/hyperdht/lib/encode.js":{"offset":13484221,"length":446,"mode":420},"/node_modules/pgh/node_modules/hyperdht/lib/errors.js":{"offset":13484667,"length":3712,"mode":420},"/node_modules/pgh/node_modules/hyperdht/lib/holepuncher.js":{"offset":13488379,"length":10003,"mode":420},"/node_modules/pgh/node_modules/hyperdht/lib/messages.js":{"offset":13498382,"length":12057,"mode":420},"/node_modules/pgh/node_modules/hyperdht/lib/nat.js":{"offset":13510439,"length":4714,"mode":420},"/node_modules/pgh/node_modules/hyperdht/lib/noise-wrap.js":{"offset":13515153,"length":1669,"mode":420},"/node_modules/pgh/node_modules/hyperdht/lib/persistent.js":{"offset":13516822,"length":8361,"mode":420},"/node_modules/pgh/node_modules/hyperdht/lib/raw-stream-set.js":{"offset":13525183,"length":1511,"mode":420},"/node_modules/pgh/node_modules/hyperdht/lib/router.js":{"offset":13526694,"length":6978,"mode":420},"/node_modules/pgh/node_modules/hyperdht/lib/secure-payload.js":{"offset":13533672,"length":1491,"mode":420},"/node_modules/pgh/node_modules/hyperdht/lib/semaphore.js":{"offset":13535163,"length":1577,"mode":420},"/node_modules/pgh/node_modules/hyperdht/lib/server.js":{"offset":13536740,"length":21300,"mode":420},"/node_modules/pgh/node_modules/hyperdht/lib/sleeper.js":{"offset":13558040,"length":690,"mode":420},"/node_modules/pgh/node_modules/hyperdht/lib/socket-pool.js":{"offset":13558730,"length":4343,"mode":420},"/node_modules/pgh/node_modules/hyperdht/package.json":{"offset":13563073,"length":2007,"mode":420},"/node_modules/pgh/node_modules/hyperschema/package.json":{"offset":13565080,"length":1555,"mode":420},"/node_modules/pgh/node_modules/hyperschema/runtime.cjs":{"offset":13566635,"length":54,"mode":420},"/node_modules/pgh/node_modules/hyperswarm/index.js":{"offset":13566689,"length":18589,"mode":420},"/node_modules/pgh/node_modules/hyperswarm/lib/bulk-timer.js":{"offset":13585278,"length":728,"mode":420},"/node_modules/pgh/node_modules/hyperswarm/lib/connection-set.js":{"offset":13586006,"length":823,"mode":420},"/node_modules/pgh/node_modules/hyperswarm/lib/peer-discovery.js":{"offset":13586829,"length":9296,"mode":420},"/node_modules/pgh/node_modules/hyperswarm/lib/peer-info.js":{"offset":13596125,"length":2688,"mode":420},"/node_modules/pgh/node_modules/hyperswarm/lib/retry-timer.js":{"offset":13598813,"length":1889,"mode":420},"/node_modules/pgh/node_modules/hyperswarm/package.json":{"offset":13600702,"length":1515,"mode":420},"/node_modules/pgh/node_modules/index-encoder/index.js":{"offset":13602217,"length":10596,"mode":420},"/node_modules/pgh/node_modules/index-encoder/package.json":{"offset":13612813,"length":928,"mode":420},"/node_modules/pgh/node_modules/is-options/index.js":{"offset":13613741,"length":140,"mode":420},"/node_modules/pgh/node_modules/is-options/package.json":{"offset":13613881,"length":605,"mode":420},"/node_modules/pgh/node_modules/kademlia-routing-table/index.js":{"offset":13614486,"length":4145,"mode":420},"/node_modules/pgh/node_modules/kademlia-routing-table/package.json":{"offset":13618631,"length":967,"mode":420},"/node_modules/pgh/node_modules/mutexify/index.js":{"offset":13619598,"length":536,"mode":420},"/node_modules/pgh/node_modules/mutexify/package.json":{"offset":13620134,"length":679,"mode":420},"/node_modules/pgh/node_modules/mutexify/promise.js":{"offset":13620813,"length":324,"mode":420},"/node_modules/pgh/node_modules/nanoassert/index.js":{"offset":13621137,"length":438,"mode":420},"/node_modules/pgh/node_modules/nanoassert/package.json":{"offset":13621575,"length":647,"mode":420},"/node_modules/pgh/node_modules/nat-sampler/index.js":{"offset":13622222,"length":1550,"mode":420},"/node_modules/pgh/node_modules/nat-sampler/package.json":{"offset":13623772,"length":608,"mode":420},"/node_modules/pgh/node_modules/noise-curve-ed/index.js":{"offset":13624380,"length":1642,"mode":420},"/node_modules/pgh/node_modules/noise-curve-ed/package.json":{"offset":13626022,"length":808,"mode":420},"/node_modules/pgh/node_modules/noise-handshake/cipher.js":{"offset":13626830,"length":2817,"mode":420},"/node_modules/pgh/node_modules/noise-handshake/dh.js":{"offset":13629647,"length":1439,"mode":420},"/node_modules/pgh/node_modules/noise-handshake/hkdf.js":{"offset":13631086,"length":1092,"mode":420},"/node_modules/pgh/node_modules/noise-handshake/hmac.js":{"offset":13632178,"length":1273,"mode":420},"/node_modules/pgh/node_modules/noise-handshake/noise.js":{"offset":13633451,"length":6437,"mode":420},"/node_modules/pgh/node_modules/noise-handshake/package.json":{"offset":13639888,"length":638,"mode":420},"/node_modules/pgh/node_modules/noise-handshake/symmetric-state.js":{"offset":13640526,"length":2206,"mode":420},"/node_modules/pgh/node_modules/protocol-buffers-encodings/index.js":{"offset":13642732,"length":6489,"mode":420},"/node_modules/pgh/node_modules/protocol-buffers-encodings/package.json":{"offset":13649221,"length":719,"mode":420},"/node_modules/pgh/node_modules/protomux-wakeup/index.js":{"offset":13649940,"length":16824,"mode":420},"/node_modules/pgh/node_modules/protomux-wakeup/package.json":{"offset":13666764,"length":972,"mode":420},"/node_modules/pgh/node_modules/protomux-wakeup/spec/hyperschema/index.js":{"offset":13667736,"length":3335,"mode":420},"/node_modules/pgh/node_modules/protomux/index.js":{"offset":13671071,"length":19741,"mode":420},"/node_modules/pgh/node_modules/protomux/package.json":{"offset":13690812,"length":1059,"mode":420},"/node_modules/pgh/node_modules/queue-tick/package.json":{"offset":13691871,"length":669,"mode":420},"/node_modules/pgh/node_modules/queue-tick/process-next-tick.js":{"offset":13692540,"length":160,"mode":420},"/node_modules/pgh/node_modules/queue-tick/queue-microtask.js":{"offset":13692700,"length":108,"mode":420},"/node_modules/pgh/node_modules/quickbit-native/binding.js":{"offset":13692808,"length":90,"mode":420},"/node_modules/pgh/node_modules/quickbit-native/index.js":{"offset":13692898,"length":4683,"mode":420},"/node_modules/pgh/node_modules/quickbit-native/package.json":{"offset":13697581,"length":1104,"mode":420},"/node_modules/pgh/node_modules/quickbit-universal/fallback.js":{"offset":13698685,"length":10204,"mode":420},"/node_modules/pgh/node_modules/quickbit-universal/index.js":{"offset":13708889,"length":442,"mode":420},"/node_modules/pgh/node_modules/quickbit-universal/package.json":{"offset":13709331,"length":908,"mode":420},"/node_modules/pgh/node_modules/rache/index.js":{"offset":13710239,"length":2463,"mode":420},"/node_modules/pgh/node_modules/rache/package.json":{"offset":13712702,"length":602,"mode":420},"/node_modules/pgh/node_modules/random-array-iterator/index.js":{"offset":13713304,"length":1001,"mode":420},"/node_modules/pgh/node_modules/random-array-iterator/package.json":{"offset":13714305,"length":700,"mode":420},"/node_modules/pgh/node_modules/ready-resource/index.js":{"offset":13715005,"length":1091,"mode":420},"/node_modules/pgh/node_modules/ready-resource/package.json":{"offset":13716096,"length":812,"mode":420},"/node_modules/pgh/node_modules/record-cache/index.js":{"offset":13716908,"length":3668,"mode":420},"/node_modules/pgh/node_modules/record-cache/package.json":{"offset":13720576,"length":612,"mode":420},"/node_modules/pgh/node_modules/refcounter/index.js":{"offset":13721188,"length":611,"mode":420},"/node_modules/pgh/node_modules/refcounter/package.json":{"offset":13721799,"length":451,"mode":420},"/node_modules/pgh/node_modules/require-addon/lib/bare.js":{"offset":13722250,"length":45,"mode":420},"/node_modules/pgh/node_modules/require-addon/package.json":{"offset":13722295,"length":1449,"mode":420},"/node_modules/pgh/node_modules/resolve-reject-promise/index.js":{"offset":13723744,"length":443,"mode":420},"/node_modules/pgh/node_modules/resolve-reject-promise/package.json":{"offset":13724187,"length":581,"mode":420},"/node_modules/pgh/node_modules/resource-on-exit/index.js":{"offset":13724768,"length":361,"mode":420},"/node_modules/pgh/node_modules/resource-on-exit/package.json":{"offset":13725129,"length":496,"mode":420},"/node_modules/pgh/node_modules/rocksdb-native/binding.js":{"offset":13725625,"length":90,"mode":420},"/node_modules/pgh/node_modules/rocksdb-native/index.js":{"offset":13725715,"length":4758,"mode":420},"/node_modules/pgh/node_modules/rocksdb-native/lib/batch.js":{"offset":13730473,"length":9605,"mode":420},"/node_modules/pgh/node_modules/rocksdb-native/lib/column-family.js":{"offset":13740078,"length":2930,"mode":420},"/node_modules/pgh/node_modules/rocksdb-native/lib/constants.js":{"offset":13743008,"length":399,"mode":420},"/node_modules/pgh/node_modules/rocksdb-native/lib/filter-policy.js":{"offset":13743407,"length":437,"mode":420},"/node_modules/pgh/node_modules/rocksdb-native/lib/iterator.js":{"offset":13743844,"length":4311,"mode":420},"/node_modules/pgh/node_modules/rocksdb-native/lib/snapshot.js":{"offset":13748155,"length":503,"mode":420},"/node_modules/pgh/node_modules/rocksdb-native/lib/state.js":{"offset":13748658,"length":10338,"mode":420},"/node_modules/pgh/node_modules/rocksdb-native/package.json":{"offset":13758996,"length":1863,"mode":420},"/node_modules/pgh/node_modules/safety-catch/index.js":{"offset":13760859,"length":543,"mode":420},"/node_modules/pgh/node_modules/safety-catch/package.json":{"offset":13761402,"length":547,"mode":420},"/node_modules/pgh/node_modules/scope-lock/index.js":{"offset":13761949,"length":1821,"mode":420},"/node_modules/pgh/node_modules/scope-lock/package.json":{"offset":13763770,"length":611,"mode":420},"/node_modules/pgh/node_modules/shuffled-priority-queue/index.js":{"offset":13764381,"length":2607,"mode":420},"/node_modules/pgh/node_modules/shuffled-priority-queue/package.json":{"offset":13766988,"length":691,"mode":420},"/node_modules/pgh/node_modules/signal-promise/index.js":{"offset":13767679,"length":1252,"mode":420},"/node_modules/pgh/node_modules/signal-promise/package.json":{"offset":13768931,"length":503,"mode":420},"/node_modules/pgh/node_modules/signed-varint/index.js":{"offset":13769434,"length":435,"mode":420},"/node_modules/pgh/node_modules/signed-varint/package.json":{"offset":13769869,"length":523,"mode":420},"/node_modules/pgh/node_modules/simdle-native/binding.js":{"offset":13770392,"length":90,"mode":420},"/node_modules/pgh/node_modules/simdle-native/index.js":{"offset":13770482,"length":3474,"mode":420},"/node_modules/pgh/node_modules/simdle-native/package.json":{"offset":13773956,"length":1115,"mode":420},"/node_modules/pgh/node_modules/simdle-universal/fallback.js":{"offset":13775071,"length":5137,"mode":420},"/node_modules/pgh/node_modules/simdle-universal/index.js":{"offset":13780208,"length":103,"mode":420},"/node_modules/pgh/node_modules/simdle-universal/package.json":{"offset":13780311,"length":879,"mode":420},"/node_modules/pgh/node_modules/simdle-universal/scalar.js":{"offset":13781190,"length":469,"mode":420},"/node_modules/pgh/node_modules/sodium-native/binding.js":{"offset":13781659,"length":89,"mode":420},"/node_modules/pgh/node_modules/sodium-native/index.js":{"offset":13781748,"length":107894,"mode":420},"/node_modules/pgh/node_modules/sodium-native/package.json":{"offset":13889642,"length":1478,"mode":420},"/node_modules/pgh/node_modules/sodium-secretstream/index.js":{"offset":13891120,"length":2257,"mode":420},"/node_modules/pgh/node_modules/sodium-secretstream/package.json":{"offset":13893377,"length":657,"mode":420},"/node_modules/pgh/node_modules/sodium-universal/index.js":{"offset":13894034,"length":42,"mode":420},"/node_modules/pgh/node_modules/sodium-universal/package.json":{"offset":13894076,"length":1217,"mode":420},"/node_modules/pgh/node_modules/streamx/index.js":{"offset":13895293,"length":34248,"mode":420},"/node_modules/pgh/node_modules/streamx/lib/errors.js":{"offset":13929541,"length":1096,"mode":420},"/node_modules/pgh/node_modules/streamx/package.json":{"offset":13930637,"length":1080,"mode":420},"/node_modules/pgh/node_modules/sub-encoder/index.js":{"offset":13931717,"length":1968,"mode":420},"/node_modules/pgh/node_modules/sub-encoder/package.json":{"offset":13933685,"length":908,"mode":420},"/node_modules/pgh/node_modules/teex/index.js":{"offset":13934593,"length":1107,"mode":420},"/node_modules/pgh/node_modules/teex/package.json":{"offset":13935700,"length":608,"mode":420},"/node_modules/pgh/node_modules/text-decoder/index.js":{"offset":13936308,"length":1371,"mode":420},"/node_modules/pgh/node_modules/text-decoder/lib/pass-through-decoder.js":{"offset":13937679,"length":269,"mode":420},"/node_modules/pgh/node_modules/text-decoder/lib/utf8-decoder.js":{"offset":13937948,"length":4429,"mode":420},"/node_modules/pgh/node_modules/text-decoder/package.json":{"offset":13942377,"length":990,"mode":420},"/node_modules/pgh/node_modules/time-ordered-set/index.js":{"offset":13943367,"length":1444,"mode":420},"/node_modules/pgh/node_modules/time-ordered-set/package.json":{"offset":13944811,"length":666,"mode":420},"/node_modules/pgh/node_modules/timeout-refresh/browser.js":{"offset":13945477,"length":1098,"mode":420},"/node_modules/pgh/node_modules/timeout-refresh/index.js":{"offset":13946575,"length":184,"mode":420},"/node_modules/pgh/node_modules/timeout-refresh/node.js":{"offset":13946759,"length":928,"mode":420},"/node_modules/pgh/node_modules/timeout-refresh/package.json":{"offset":13947687,"length":619,"mode":420},"/node_modules/pgh/node_modules/tiny-buffer-map/index.js":{"offset":13948306,"length":936,"mode":420},"/node_modules/pgh/node_modules/tiny-buffer-map/package.json":{"offset":13949242,"length":650,"mode":420},"/node_modules/pgh/node_modules/udx-native/binding.js":{"offset":13949892,"length":90,"mode":420},"/node_modules/pgh/node_modules/udx-native/lib/ip.js":{"offset":13949982,"length":2204,"mode":420},"/node_modules/pgh/node_modules/udx-native/lib/network-interfaces.js":{"offset":13952186,"length":1341,"mode":420},"/node_modules/pgh/node_modules/udx-native/lib/socket.js":{"offset":13953527,"length":7557,"mode":420},"/node_modules/pgh/node_modules/udx-native/lib/stream.js":{"offset":13961084,"length":13050,"mode":420},"/node_modules/pgh/node_modules/udx-native/lib/udx.js":{"offset":13974134,"length":2868,"mode":420},"/node_modules/pgh/node_modules/udx-native/package.json":{"offset":13977002,"length":1665,"mode":420},"/node_modules/pgh/node_modules/unordered-set/index.js":{"offset":13978667,"length":677,"mode":420},"/node_modules/pgh/node_modules/unordered-set/package.json":{"offset":13979344,"length":654,"mode":420},"/node_modules/pgh/node_modules/unslab/index.js":{"offset":13979998,"length":913,"mode":420},"/node_modules/pgh/node_modules/unslab/package.json":{"offset":13980911,"length":613,"mode":420},"/node_modules/pgh/node_modules/varint/decode.js":{"offset":13981524,"length":508,"mode":420},"/node_modules/pgh/node_modules/varint/encode.js":{"offset":13982032,"length":452,"mode":420},"/node_modules/pgh/node_modules/varint/index.js":{"offset":13982484,"length":134,"mode":420},"/node_modules/pgh/node_modules/varint/length.js":{"offset":13982618,"length":471,"mode":420},"/node_modules/pgh/node_modules/varint/package.json":{"offset":13983089,"length":511,"mode":420},"/node_modules/pgh/node_modules/which-runtime/index.js":{"offset":13983600,"length":1469,"mode":420},"/node_modules/pgh/node_modules/which-runtime/package.json":{"offset":13985069,"length":863,"mode":420},"/node_modules/pgh/node_modules/xache/index.js":{"offset":13985932,"length":2378,"mode":420},"/node_modules/pgh/node_modules/xache/package.json":{"offset":13988310,"length":584,"mode":420},"/node_modules/pgh/node_modules/z32/index.js":{"offset":13988894,"length":2654,"mode":420},"/node_modules/pgh/node_modules/z32/package.json":{"offset":13991548,"length":701,"mode":420},"/node_modules/pgh/package.json":{"offset":13992249,"length":4094,"mode":420},"/node_modules/protomux/index.js":{"offset":13996343,"length":19741,"mode":420},"/node_modules/protomux/package.json":{"offset":14016084,"length":1059,"mode":420},"/node_modules/queue-tick/package.json":{"offset":14017143,"length":669,"mode":420},"/node_modules/queue-tick/process-next-tick.js":{"offset":14017812,"length":160,"mode":420},"/node_modules/queue-tick/queue-microtask.js":{"offset":14017972,"length":108,"mode":420},"/node_modules/record-cache/index.js":{"offset":14018080,"length":3668,"mode":420},"/node_modules/record-cache/package.json":{"offset":14021748,"length":612,"mode":420},"/node_modules/require-addon/lib/bare.js":{"offset":14022360,"length":45,"mode":420},"/node_modules/require-addon/package.json":{"offset":14022405,"length":1449,"mode":420},"/node_modules/safety-catch/index.js":{"offset":14023854,"length":543,"mode":420},"/node_modules/safety-catch/package.json":{"offset":14024397,"length":547,"mode":420},"/node_modules/shuffled-priority-queue/index.js":{"offset":14024944,"length":2607,"mode":420},"/node_modules/shuffled-priority-queue/package.json":{"offset":14027551,"length":691,"mode":420},"/node_modules/signal-promise/index.js":{"offset":14028242,"length":1252,"mode":420},"/node_modules/signal-promise/package.json":{"offset":14029494,"length":503,"mode":420},"/node_modules/sodium-native/binding.js":{"offset":14029997,"length":89,"mode":420},"/node_modules/sodium-native/index.js":{"offset":14030086,"length":107894,"mode":420},"/node_modules/sodium-native/package.json":{"offset":14137980,"length":1478,"mode":420},"/node_modules/sodium-secretstream/index.js":{"offset":14139458,"length":2257,"mode":420},"/node_modules/sodium-secretstream/package.json":{"offset":14141715,"length":657,"mode":420},"/node_modules/sodium-universal/index.js":{"offset":14142372,"length":42,"mode":420},"/node_modules/sodium-universal/package.json":{"offset":14142414,"length":1217,"mode":420},"/node_modules/streamx/index.js":{"offset":14143631,"length":34248,"mode":420},"/node_modules/streamx/lib/errors.js":{"offset":14177879,"length":1096,"mode":420},"/node_modules/streamx/package.json":{"offset":14178975,"length":1225,"mode":420},"/node_modules/teex/index.js":{"offset":14180200,"length":1107,"mode":420},"/node_modules/teex/package.json":{"offset":14181307,"length":608,"mode":420},"/node_modules/text-decoder/index.js":{"offset":14181915,"length":1371,"mode":420},"/node_modules/text-decoder/lib/pass-through-decoder.js":{"offset":14183286,"length":269,"mode":420},"/node_modules/text-decoder/lib/utf8-decoder.js":{"offset":14183555,"length":4429,"mode":420},"/node_modules/text-decoder/package.json":{"offset":14187984,"length":990,"mode":420},"/node_modules/time-ordered-set/index.js":{"offset":14188974,"length":1444,"mode":420},"/node_modules/time-ordered-set/package.json":{"offset":14190418,"length":666,"mode":420},"/node_modules/timeout-refresh/browser.js":{"offset":14191084,"length":1098,"mode":420},"/node_modules/timeout-refresh/index.js":{"offset":14192182,"length":184,"mode":420},"/node_modules/timeout-refresh/node.js":{"offset":14192366,"length":928,"mode":420},"/node_modules/timeout-refresh/package.json":{"offset":14193294,"length":619,"mode":420},"/node_modules/udx-native/binding.js":{"offset":14193913,"length":90,"mode":420},"/node_modules/udx-native/lib/ip.js":{"offset":14194003,"length":2204,"mode":420},"/node_modules/udx-native/lib/network-interfaces.js":{"offset":14196207,"length":1341,"mode":420},"/node_modules/udx-native/lib/socket.js":{"offset":14197548,"length":7557,"mode":420},"/node_modules/udx-native/lib/stream.js":{"offset":14205105,"length":13050,"mode":420},"/node_modules/udx-native/lib/udx.js":{"offset":14218155,"length":2868,"mode":420},"/node_modules/udx-native/package.json":{"offset":14221023,"length":1665,"mode":420},"/node_modules/unordered-set/index.js":{"offset":14222688,"length":677,"mode":420},"/node_modules/unordered-set/package.json":{"offset":14223365,"length":654,"mode":420},"/node_modules/unslab/index.js":{"offset":14224019,"length":913,"mode":420},"/node_modules/unslab/package.json":{"offset":14224932,"length":613,"mode":420},"/node_modules/which-runtime/index.js":{"offset":14225545,"length":1469,"mode":420},"/node_modules/which-runtime/package.json":{"offset":14227014,"length":863,"mode":420},"/node_modules/xache/index.js":{"offset":14227877,"length":2477,"mode":420},"/node_modules/xache/package.json":{"offset":14230354,"length":672,"mode":420},"/node_modules/z32/index.js":{"offset":14231026,"length":2654,"mode":420},"/node_modules/z32/package.json":{"offset":14233680,"length":701,"mode":420},"/package.json":{"offset":14234381,"length":2359,"mode":420}}}
// Which bundle this boot runs — the staged one, or the APK's.
//
// ⛔ ITS OWN MODULE, WITH NO IMPORTS, because it is needed on BOTH sides of the bridge and they share no
// filesystem API. The decision is made in React Native (RNFS) before the worklet starts; the staging that
// feeds it happens inside the worklet (bare-fs). lib/ota.js imports bare-fs at module top level, so
// importing it from the RN side would break the app on load — a mistake worth naming, because the split
// is not tidiness, it is the only reason this can be called where it has to be called.
//
// Pure and dependency-free, like lib/ipc-client.js and lib/lifecycle.js, and testable without a handset —
// which is the only place the consequences of getting this wrong actually show up.

/**
 * @param {{current: ?{treeHash: string, file: string, builtAt: ?number}, pending: ?{treeHash: string},
 *          failed: string[], assetBuiltAt: ?number}} state
 * @returns {{use:'staged'|'asset', hash:?string, file:?string, markPending:boolean, condemn:?string, reason:string}}
 *
 * ⛔ A STAGED BUNDLE NOTHING RUNS IS NOT AN UPDATE, and running one blindly is how a phone gets bricked.
 * There is no adb on a user's handset and no UI left if the node cannot boot, so the only safe shape is a
 * watchdog: record the intent BEFORE starting, clear it once the node reports ready, and treat a marker
 * that survived a boot as proof that bundle does not work.
 *
 * The failure this exists to prevent is the retry loop — a bad bundle chosen again on every boot, dying
 * identically each time, with no way in from outside.
 */
export function chooseBundle ({ current = null, pending = null, failed = [], assetBuiltAt = null } = {}) {
  const asset = (reason, condemn = null) => ({ use: 'asset', hash: null, file: null, markPending: false, condemn, reason })

  // A pending marker means the LAST boot started a bundle and never reached ready. Condemn whatever it
  // names — which is not necessarily what is staged now, so it is read from the marker, not from current.
  const condemn = pending && pending.treeHash ? pending.treeHash : null
  const dead = new Set([...(Array.isArray(failed) ? failed : []), ...(condemn ? [condemn] : [])])

  if (!current || !current.treeHash || !current.file) return asset('nothing staged', condemn)
  // A newer bundle still gets its chance: refusing every staged bundle after one bad build would make the
  // phone permanently un-updatable by a single bad release, and the fix for a bad release is the next one.
  if (dead.has(current.treeHash)) return asset('staged bundle previously failed to boot', condemn)

  // ⛔ AN APK INSTALL MUST SUPERSEDE AN OLDER STAGED BUNDLE. Staged used to win unconditionally, and tree
  // hashes carry no ordering — so installing a newer APK left the phone running the JS staged before it,
  // until the next OTA pass happened to catch up. Observed twice on a handset while shipping today: the
  // new shell booted, the new bundle sat in assets, and the old staged one ran anyway.
  //
  // Build times are the only comparable thing the two sides share: the CI stamps the APK's bundle
  // (`worklet.bundle.built`) and lib/ota.js records the release's build time in the staged marker. Both
  // absent means an older shell or an older staged marker, and the historic behaviour (staged wins) is
  // kept — never guess an ordering out of nothing.
  const staged = Number(current.builtAt) || 0
  const shipped = Number(assetBuiltAt) || 0
  if (shipped && staged && shipped > staged) {
    return asset('the app was updated more recently than the staged bundle', condemn)
  }
  // ⛔ AND AN UNDATED STAGED BUNDLE LOSES TO A STAMPED APK TOO.
  //
  // Requiring BOTH times made a whole class of staged bundles permanently unbeatable: the HTTPS fetcher's
  // getLatestRelease returned { name, version, treeHash } and no builtAt, so every bundle it ever staged
  // has a marker with no build time. `staged` is 0, the clause above cannot fire, and the phone keeps
  // running that JS through every future APK install.
  //
  // It is not hypothetical and the symptom is the worst kind — the install succeeds, the version looks
  // right, and the code does not run. Four APKs were installed on a handset on 2026-08-25, each carrying
  // fixes, and none took effect: a bundle staged from 0.1.6 won every boot. Most of an evening went into
  // diagnosing "my changes are not on the phone".
  //
  // An APK install is a PHYSICAL, DELIBERATE act by someone holding the device. A staged bundle that
  // cannot say when it was built cannot be shown to be newer than that, so the tie goes to the APK.
  // A staged bundle that CAN say, and is newer, still wins — OTA has to keep working, and that is the
  // case immediately above.
  if (shipped && !staged) {
    return asset('the staged bundle carries no build time; an installed APK is the more recent evidence', condemn)
  }

  return {
    use: 'staged',
    hash: current.treeHash,
    file: current.file,
    markPending: true,
    condemn,
    reason: 'staged bundle available'
  }
}
// android-ota — keep the JS current on a phone, so the APK only has to change when native code does.
//
// THE SPLIT THIS EXISTS TO CREATE. An APK carries two very different things: ~77 MB of native libraries
// that change rarely (bare-kit + our addons), and a worklet bundle that changes constantly. Today proved
// the cost of not having this: a favela-web fix reached the handset only by rebuilding a 153 MB APK
// (150s of CI) and sideloading it, because favela-android vendors favela-web at build time.
//
// ⛔ WHAT WAS HERE BEFORE HAD NEVER RUN. pgh-runtime was `--defer`red out of the bundle and absent from
// dependencies, so `startOta`'s dynamic import always failed and this file returned null after logging
// "updates disabled". The code underneath was free to be wrong, and was: `getTree` returned
// `{entries: []}` for every hash, and pgh-runtime's installTree hashes the entries it is handed and
// rejects the tree unless they reproduce the root hash — a guard that exists so a peer cannot answer "I
// don't have it" with an empty directory. So it could not have downloaded a bundle even with the
// dependency present. It also took `versions[versions.length - 1]` as "newest", and this forge lists
// newest FIRST, so it would have pinned a phone to the oldest build on the server and called it current.
//
// ⛔ THE TWO "RELEASE" SYSTEMS ARE NOT INTERCHANGEABLE:
//   - pgh-sidecar's ReleaseRegistry: an Autobase of METADATA. Replicates P2P, never produces a file.
//   - pear-git-v3's ReleaseManager: real files on disk, served over HTTPS at
//     /git/releases/<repo>/<version>/<file>. This is the one that can hand a phone bytes.
// A phone needs BYTES, so this targets the second.
//
// ⚠ Artifacts are per-daemon. 7rnx.net/git is load-balanced and a release built on one backend is not
// automatically complete on the other (measured 2026-08-18: a 153 MB APK never reached the second
// backend while its build.log and manifest did). So `base` must point at a backend that actually holds
// the bytes, and every fetch is hash-verified rather than trusted because of where it came from.
import fs from 'bare-fs'
import path from 'bare-path'
import crypto from 'hypercore-crypto'
import { computeTreeHash, KIND_FILE } from 'hyper-overlay/tree'

const DEFAULT_BASE = 'https://7rnx.net/git'
// ⛔ THIS REPO'S OWN NAME, and the reason is not tidiness. This file was copied verbatim from
// favela-android, where the default is `favela-android` — and an OTA client that keeps the source
// repo's name fetches ANOTHER APP'S worklet bundle and stages it as its own. That bundle boots
// hyper-proto-peer and favela-web against this app's storage, which is a different node entirely.
// The registry below is a publisher identity shared by every hyper-fleeky app, so the repo name is
// the ONLY thing distinguishing one app's releases from another's here. Wrong default, wrong app.
const DEFAULT_REPO = 'favela-surface-android'
// The publisher's release registry — the hex in every hyper-fleeky app's `pgh.upgrade`
// (hyper-proto-peer, atproto-bridge, hypernote, favela-web, pgh-sidecar all carry this exact value).
//
// ⛔ A CONSTANT, AND DELIBERATELY NOT A LIST. This is a publisher IDENTITY, not a set of hosts: the
// serving topic comes from the resolved release's own serverKey, so adding, moving or rotating a backend
// changes nothing here. A hand-maintained list of hosts would be the frontedDids anti-pattern
// (hyper-social #54 §8) on the one component that cannot be fixed remotely.
const DEFAULT_REGISTRY = '1dd36bdfa9cb7f7d84adbd07f017e7200f7f90a3a546cd0761fac2a1f8a13e86'
const DEFAULT_INTERVAL = 6 * 60 * 60 * 1000 // 6h — a phone is not a server; do not poll it awake
// How long to let a COLD registry replica settle before reading it. See swarmFetcher.
const COLD_SETTLE_MS = 30000
const BUNDLE_NAME = 'worklet.bundle'

// ⛔ THE STACK, NOT JUST THE MESSAGE. Three separate homedir defaults have now produced the SAME
// ENOENT on a handset, each masked by the one before it, and each cost a build-and-sideload cycle to
// guess at. `err.message` names the path and not the caller, so it cannot distinguish them. A device
// with no log anyone can read has to carry the frame itself.
const frame = (err) => {
  const st = (err && err.stack) || ''
  const lines = String(st).split('\n').slice(1, 4).map((l) => l.trim().replace(/^at\s+/, ''))
  return lines.length ? ' @ ' + lines.join(' < ') : ''
}

const hashOf = (bytes) => crypto.data(Buffer.from(bytes)).toString('hex')

// The running bundle's hash, if the environment happens to say. Guarded because `process` is a shim in
// the worklet and absent everywhere else; an OTA client is not the place to discover that.
function envBundleHash () {
  try { return (typeof process !== 'undefined' && process.env) ? process.env.FAVELA_BUNDLE_HASH : null } catch { return null }
}

/**
 * The one-file tree for a bundle: what CI publishes as tree.json and what the phone verifies against.
 *
 * Deliberately built with hyper-overlay's own encoder rather than by hand — pgh-runtime verifies with
 * `computeTreeHash`, so producing the hash any other way would be a second definition of the format that
 * agrees until it doesn't.
 */
export function treeFor (bytes, name = BUNDLE_NAME) {
  const contentHash = hashOf(bytes)
  const entries = [{ kind: KIND_FILE, exec: 0, name, childHash: contentHash }]
  return { entries, treeHash: computeTreeHash(entries), contentHash, size: bytes.length }
}

/**
 * Newest release by BUILD TIME, not by position in the list.
 *
 * The old code took the last element and commented "newest last by publish order". `pgh deploy log`
 * returns newest FIRST, so that read the oldest build on the server. Ordering the caller cannot see is
 * not a contract; the timestamp is.
 */
export function pickNewest (versions) {
  const list = (Array.isArray(versions) ? versions : []).filter(Boolean)
  if (!list.length) return null
  return list.reduce((a, b) => ((Number(b.builtAt) || 0) > (Number(a.builtAt) || 0) ? b : a))
}

/**
 * A pgh-runtime fetcher over the forge's HTTPS release routes.
 *
 * Shape required by pgh-runtime: { getLatestRelease(name), getTree(hash), getContent(hash) }.
 */
export function bundleFetcher ({ base = DEFAULT_BASE, repo = DEFAULT_REPO, doFetch }) {
  // treeHash -> { version, entries }, and contentHash -> { version, name }. Populated when a tree.json is
  // read, because installTree asks for a tree and then for content BY HASH, with no version in hand.
  const trees = new Map()
  const contents = new Map()

  const releaseUrl = (version, file) => `${base}/releases/${repo}/${version}/${file}`

  async function readTree (version) {
    const res = await doFetch(releaseUrl(version, 'tree.json'))
    if (!res || res.ok === false) throw new Error(`no tree.json for ${version}`)
    const body = await res.json()
    const entries = (body && body.entries) || []
    // Trust the ENTRIES, not the advertised hash: recompute it. installTree checks this too, but a
    // fetcher that propagated a hash it never verified would hand the guard its own answer.
    const treeHash = computeTreeHash(entries)
    trees.set(treeHash, { version, entries })
    for (const e of entries) contents.set(e.childHash, { version, name: e.name })
    return { treeHash, entries }
  }

  // Named, because getTree's fallback calls back into getLatestRelease — one discovery path, not two.
  const api = {
    // ⛔ `release-index`, NOT `list-repo-releases`. The latter is behind the anonymous-RPC gate (pear-git
    // org #12: the whole /rpc/ surface was world-callable until it became opt-in), so a phone — anonymous
    // by construction — gets 403 forever. Measured on the handset: the check SUCCEEDS having found
    // nothing, prints no error, and is indistinguishable from being up to date.
    //
    // release-index is already in ANONYMOUS_METHODS, and for the same use: "Release discovery for a
    // ReleasePuller… The handler filters its response by repoAccess for anonymous callers, so a private
    // repo's versions and hashes stay hidden." A phone updating itself is a puller. Opening a second
    // method would have been inventing a path around one that already exists.
    async getLatestRelease (name) {
      const res = await doFetch(`${base}/rpc/release-index`, {
        method: 'POST',
        headers: { 'content-type': 'application/json' },
        body: '{}'
      })
      if (!res || res.ok === false) return null
      const body = await res.json()
      // hyper-svc wraps HTTP /rpc/ responses in { result }. The old code read body.versions and would
      // have found undefined even if the gate had let it through.
      const index = (body && body.result) || body || {}

      // Group this repo's files by version. The index carries every artifact, so it can answer the
      // question that matters: which versions are actually COMPLETE?
      const byVersion = new Map()
      for (const f of index.files || []) {
        if (!f || f.repo !== repo || !f.version) continue
        if (!byVersion.has(f.version)) byVersion.set(f.version, { version: f.version, builtAt: 0, names: new Set() })
        const v = byVersion.get(f.version)
        v.names.add(String(f.path || '').split('/').pop())
        if (Number(f.mtimeMs) > v.builtAt) v.builtAt = Number(f.mtimeMs)
      }

      // ⛔ SKIP INCOMPLETE RELEASES. Artifacts replicate per-file between forge backends and partial
      // arrivals are real — measured 2026-08-18: master-5d6d7e4 served build.log, manifest.json and the
      // APK while tree.json and worklet.bundle never arrived. Taking "newest" blindly would pin the phone
      // to a version it can never install, and retry it forever. Newest COMPLETE is the honest rule.
      const complete = [...byVersion.values()].filter((v) => v.names.has('tree.json') && v.names.has(BUNDLE_NAME))
      const newest = pickNewest(complete)
      if (!newest) return null

      // A release with no readable tree is not an error worth crashing a boot over.
      let tree
      try { tree = await readTree(newest.version) } catch { return null }
      return { name, version: newest.version, treeHash: tree.treeHash }
    },

    // ⛔ FETCH, DO NOT JUDGE. This returns whatever the release declares and lets installTree's
    // computeTreeHash guard accept or reject it. An earlier cut gated here too and had to be pulled: a
    // fetcher that verifies is a SECOND definition of the check, and the two agree right up until they
    // do not — and it made the fetcher order-dependent, answering only for a tree getLatestRelease had
    // pre-warmed. One guard, in the layer that owns it.
    async getTree (rootHash) {
      const hit = trees.get(rootHash)
      if (hit) return { entries: hit.entries, size: hit.entries.length }
      // Not seen yet — the caller went straight to installTree. Read the newest release's tree and hand
      // it over; if it is not the one asked for, installTree says so in the words an operator needs.
      // Same discovery path as getLatestRelease — the gated route would 403 here too.
      const latest = await api.getLatestRelease(repo)
      if (!latest) throw new Error('no complete release for ' + repo)
      const again = trees.get(latest.treeHash)
      if (!again) throw new Error('no tree for ' + latest.version)
      return { entries: again.entries, size: again.entries.length }
    },

    async getContent (contentHash) {
      const where = contents.get(contentHash)
      if (!where) throw new Error('unknown content ' + String(contentHash).slice(0, 12) + '…')
      const res = await doFetch(releaseUrl(where.version, where.name))
      if (!res || res.ok === false) throw new Error('fetch failed: ' + where.name)
      const bytes = new Uint8Array(await res.arrayBuffer())
      // ⛔ THE ONLY THING PROTECTING THIS PHONE. Releases are unsigned (`signed: false` in every publish)
      // and the forge is load-balanced HTTPS, so "it downloaded" says nothing about WHAT downloaded. The
      // bundle becomes the code that holds the DID signing key. Verify, or do not install.
      const got = hashOf(bytes)
      if (got !== contentHash) {
        throw new Error(`content hash mismatch for ${where.name}: expected ${contentHash.slice(0, 12)}…, got ${got.slice(0, 12)}…`)
      }
      return bytes
    }
  }

  return api
}

/**
 * Store-only: write the bundle, and let the NEXT start pick it up.
 *
 * Applying a new bundle to a running node would mean swapping the code under itself. A phone restarts
 * constantly anyway, so "active on next start" costs nothing and avoids a class of live-reload bugs.
 *
 * ⛔ NO MATERIALIZER. pgh's node materializer HARDLINKS, and SELinux denies `link` device-wide on
 * Android — which is what made a 269-dep closure balloon to ~5.7 GB on celltron. One file, written
 * directly, is the whole of what a bundle needs.
 */
export function bundleStore ({ dir }) {
  const markerPath = (treeHash) => path.join(dir, treeHash + '.json')
  // The release currently being installed. pgh-runtime's installTree does not pass the release down to
  // the store, but it emits `updating` with it immediately beforehand — so the caller notes it here and
  // materialize can stamp the staged marker with a comparable build time. Without that the shell has no
  // way to tell a staged bundle from the one inside a freshly installed APK.
  let pendingRelease = null
  const readMarker = (treeHash) => {
    try { return JSON.parse(fs.readFileSync(markerPath(treeHash), 'utf8')) } catch { return null }
  }

  return {
    init () { fs.mkdirSync(dir, { recursive: true }) },

    /** Told by startOta from the `updating` event, just before installTree runs. */
    noteRelease (r) { pendingRelease = r || null },

    /**
     * ⛔ THE BYTES, NOT JUST THE MARKER. A marker written before the file it names is the same bug the
     * forge has been living with all week — an index advertising a release whose artifact never arrived.
     * So the marker is written LAST and this checks the file too.
     */
    hasTree (treeHash) {
      const m = readMarker(treeHash)
      if (!m || !m.file) return false
      try { return fs.statSync(path.join(dir, m.file)).size > 0 } catch { return false }
    },

    /** Where the staged bundle for this tree lives, or null. */
    pathFor (treeHash) {
      const m = readMarker(treeHash)
      return m && m.file ? path.join(dir, m.file) : null
    },

    async materialize (rootHash, entries, { fetchContent } = {}) {
      // ⛔ RECORD A BUILD TIME, WHATEVER THE RELEASE CALLS IT. chooseBundle compares this against the
      // APK's stamp, and a staged bundle with no time is one an APK install cannot supersede — the
      // failure that left a handset running 0.1.6's JS through four installs on 2026-08-25.
      //
      // The two release shapes name it differently and neither is wrong: a forge release carries
      // `builtAt`, the pgh ReleaseRegistry carries an ISO `publishedAt`. Reading only one is how the
      // swarm fetcher would have inherited the very bug it was written to replace.
      const builtAt = (pendingRelease && (
        (Number(pendingRelease.builtAt) || 0) ||
        (Date.parse(pendingRelease.publishedAt || '') || 0)
      )) || null
      this.init()
      // ⛔ BY NAME, NEVER BY POSITION. This used to write EVERY file entry and keep whichever landed
      // last. That was safe only while a tree had exactly one entry, and the ci.yaml note said what
      // happens otherwise: "a two-entry tree stages a 154 MB APK as the worklet bundle. The OTA client
      // on a handset is exactly what cannot be fixed remotely."
      //
      // It stopped being hypothetical when CI started publishing to the release registry: `pgh publish`
      // requires a package.json, so the published tree is { package.json, worklet.bundle } and the winner
      // depended on an ordering nothing here controls or asserts. Selecting by name also stops the phone
      // downloading content it will discard — the old loop fetched every entry before dropping all but one.
      const want = (entries || []).find((e) => e && e.kind === KIND_FILE && e.name === BUNDLE_NAME)
      if (!want) {
        const names = (entries || []).filter((e) => e && e.kind === KIND_FILE).map((e) => e.name)
        throw new Error('tree contains no ' + BUNDLE_NAME + ' to stage (files: ' + (names.join(', ') || 'none') + ')')
      }
      const bytes = await fetchContent(want.childHash)
      // Write to .part and rename: a phone is killed constantly, and a truncated bundle that hasTree
      // then trusted would be a node that cannot boot with no way to say why.
      const dest = path.join(dir, want.childHash + '.bundle')
      const part = dest + '.part'
      fs.writeFileSync(part, Buffer.from(bytes))
      fs.renameSync(part, dest)
      const file = want.childHash + '.bundle'
      // `builtAt` is what lets the SHELL compare a staged bundle with the one inside the APK. Without it
      // an APK install loses to whatever was staged before it — chooseBundle prefers staged, and tree
      // hashes carry no ordering, so a phone kept running the older JS until the next OTA pass caught up.
      fs.writeFileSync(markerPath(rootHash), JSON.stringify({ treeHash: rootHash, file, builtAt }, null, 2) + '\n')
      // current.json is how the REACT NATIVE side learns what is staged: it runs before the worklet and
      // cannot read this store's code (bare-fs), only its files. Written last, after the bytes and the
      // marker, so it never advertises something that is not fully on disk.
      fs.writeFileSync(path.join(dir, 'current.json'), JSON.stringify({ treeHash: rootHash, file, builtAt }, null, 2) + '\n')
    }
  }
}

// The boot-time choice lives in its own dependency-free module so the React Native side can import
// it too (this file imports bare-fs, which does not exist there). Re-exported for callers inside the
// worklet, so there is one name for it.
export { chooseBundle } from './choose-bundle.js'

/**
 * THE FETCHER EVERY OTHER hyper-fleeky APP USES — Hyperswarm + hyper-overlay. No HTTPS, no backend.
 *
 * ⛔ WHAT THIS REPLACES, AND WHY IT WAS WRONG. The HTTPS fetcher spoke to https://7rnx.net/git — a
 * LOAD-BALANCED pair of forge daemons — for discovery, tree AND content. pgh-runtime never asked for
 * that: it is transport-agnostic and REQUIRES an injected fetcher, and pgh has shipped the Hyperswarm
 * implementation the whole time. favela-android was the only app not using it, so hyper-overlay was
 * reduced to a hashing library here while every other app used it as the transport it is.
 *
 * The cost was not theoretical. A release lands on the backend that BUILT it and replicates to the other
 * asynchronously; the phone asks the load balancer and gets whichever. Measured 2026-08-25: 0.1.6
 * replicated in ~15s, 0.1.7 had NOT replicated after 600s — the phone correctly reported no update and
 * was stuck. Measured 2026-08-18: a 153 MB APK never reached the second backend at all.
 *
 * TWO PIECES, both already exported by pgh:
 *   makeGetLatestRelease({ registryKeyHex })  replicates the PUBLISHER's registry over Hyperswarm —
 *                                            resolve-release.js path 2, written for exactly this case
 *                                            ("every standalone install"), which a handset is.
 *   NetworkFetcher({ topic: serverKey })      tree + content over hyper-overlay's protocol.
 *
 * MEASURED end-to-end against the real published release before this shipped: resolve 13.2s cold,
 * getTree 26ms, and the 38,135,946-byte worklet bundle in 8.4s.
 *
 * ⛔ RESOLVE ONCE. The 13s is registry replication from cold and it dominates everything else; fetches
 * after it are milliseconds. A phone that re-resolved per wake would pay it every time, so the topic is
 * learned once per boot — the same thing pgh's own runtime-factory does. A publisher that moves to a new
 * serverKey is a restart, not a case worth carrying state for.
 *
 * ANDROID: `preferSidecar: false`. resolve-release tries a pgh-sidecar UDS first because on a normal
 * machine both paths open the same registry corestore and a direct open loses an fd-lock race. A handset
 * has no sidecar and never will, so the probe is pure latency — and SELinux makes pathname sockets their
 * own hazard here.
 */
export async function swarmFetcher ({ registryKeyHex = DEFAULT_REGISTRY, repo = DEFAULT_REPO, timeout = 120000, destDir = null, swarm = null, storeDir = null, latest: opts_latest = null } = {}) {
  const { makeGetLatestRelease } = await import('pgh/resolve-release')
  const { NetworkFetcher } = await import('pgh/network-fetcher')

  // ⛔ storeDir, OR THE REGISTRY REPLICA GOES TO A HOME DIRECTORY THAT DOES NOT EXIST HERE.
  // Measured on a handset: ENOENT stat'ing the default path, caught into "no swarm fetcher —
  // updates disabled this boot". The phone could never update and said so only once the app was
  // taught to surface its own last OTA line.
  // ⛔ settleMs, AND IT IS THE REASON A PHONE NEVER UPDATED. The default is 500ms — correct for a
  // WARM replica, which is what every machine that has ever run pgh has at the default path. A
  // handset has no replica: it must pull the registry autobase from the swarm on every boot, and
  // 500ms is not enough to see a single block. `resolveRelease` then returns null and this throws
  // "no published release with a serverKey", which reads like a publishing problem and is not.
  //
  // ⛔ MEASURED, and the measurement is the whole point: with the SAME key and a cold store,
  // settleMs 500 -> null, settleMs 20000 -> the real entry with its serverKey. Every earlier
  // "OTA works" check I ran was resolving from tabertron's warm ~/.config/pgh replica and never
  // touched the swarm at all — a green light from an instrument pointed at the wrong thing.
  //
  // ⚠ The cost is a slow check, not a slow boot: startOta is awaited but non-fatal, the interval is
  // hours, and a check that answers in 30s having actually replicated beats one that answers in
  // 500ms having seen nothing.
  const getLatestRelease = makeGetLatestRelease({
    registryKeyHex, preferSidecar: false, storeDir, settleMs: COLD_SETTLE_MS
  })
  const latest = opts_latest || await getLatestRelease(repo)
  if (!latest || !latest.serverKey) throw new Error('no published release with a serverKey for ' + repo)

  // ⛔ localRegistryDir, THE SECOND HOMEDIR IN THIS PATH. NetworkFetcher defaults it to
  // `homedir()/.config/pgh/release-registry` and stats it — on Android that is `/data/config/…`,
  // which an unprivileged app cannot stat at all, so it throws rather than returning false.
  //
  // ⚠ IT WAS MASKED. In 0.0.10 `resolveRelease`'s own homedir default failed FIRST, so fixing that
  // one simply moved the ENOENT here and it looked like the same bug returning. Two independent
  // defaults, one symptom — which is why the fix has to be "no homedir anywhere in this path"
  // rather than "fix the one that threw".
  const fetcher = new NetworkFetcher({
    topic: latest.serverKey, timeout, destDir, swarm, localRegistryDir: path.join(destDir, '..', 'registry-local')
  })
  // NetworkFetcher's own getLatestRelease reads a LOCAL registry, which a phone does not have. Its header
  // calls the replacement a follow-on; the follow-on is the resolver above, and this is the same
  // substitution runtime-factory makes for the same reason.
  fetcher.getLatestRelease = getLatestRelease
  await fetcher.ready()
  return { fetcher, latest }
}

/**
 * Start the bundle updater. Returns { stop() } or null when OTA is unavailable.
 *
 * Never throws: the caller boots a node either way.
 */
export async function startOta ({ storage, say = () => {}, repo = DEFAULT_REPO, interval = DEFAULT_INTERVAL, currentTreeHash = null, registryKeyHex = DEFAULT_REGISTRY, swarm = null, fetcher: injectedFetcher = null } = {}) {
  let PghRuntime = null
  try {
    ;({ PghRuntime } = await import('pgh-runtime'))
  } catch {
    say('ota: pgh-runtime not bundled — updates disabled (add the dep to enable)')
    return null
  }

  const dir = path.join(storage, 'bundles')
  const store = bundleStore({ dir })
  store.init()

  // THE FETCHER IS INJECTED, exactly as pgh-runtime injects one into PghRuntime — one transport seam, in
  // the layer that owns it. Production passes nothing and gets the swarm; tests pass a double and can
  // exercise the store/runtime wiring without a DHT, which is the coverage that caught the two-week
  // "OTA never ran" bug and must not be lost with the transport.
  //
  // ⛔ NO HTTPS FALLBACK, DELIBERATELY. A fallback means the phone silently keeps using the load-balanced
  // path whenever the swarm is slow — which is precisely when the swarm is the thing to debug, and
  // precisely the failure HTTPS already has. Two transports is also two sets of failure modes on the one
  // component that cannot be fixed remotely. If this cannot reach a peer, OTA is off for that boot and
  // says so; a sideloaded APK stays the recovery route, as it already is for every native change.
  // ⛔ EVENT-DRIVEN, NOT POLLED — and the poll was not merely slow, it was WRONG. A one-shot resolve
  // has to guess how long replication takes: `resolveRelease` waits `settleMs` because "flush
  // returns when peers are connected, not when the log has caught up". That guess is 500ms, correct
  // for a warm replica and far too short for a cold one, so a handset resolved null every boot and
  // reported "no published release with a serverKey" — a publishing failure that never happened.
  //
  // MEASURED against the same registry with a cold store: one-shot settleMs 500 -> null, settleMs
  // 20000 -> the entry, WATCH -> the entry in 2777ms with no settle at all. A held replica is TOLD
  // when blocks land; it does not estimate. That removes the class, rather than retuning the guess.
  //
  // ⚠ Nothing here blocks boot. The watch is opened, `startOta` returns, and the runtime is built by
  // the first release event whenever it arrives — which is also the reconciliation, since the watch
  // reads once itself for a replica that was already caught up.
  let watch = null
  let runtime = null
  let started = null
  // Hoisted: `wire()` builds it, `stop()` has to close it, and only what WE created (an injected
  // fetcher belongs to its caller — closing someone else's handle is a use-after-close in the suite
  // that was meant to catch exactly that).
  let activeFetcher = injectedFetcher
  const ownsFetcher = !injectedFetcher

  // Build the runtime once we have a release to point it at. Idempotent: later events only check.
  const wire = async (entry) => {
    if (started) { try { await runtime.checkNow() } catch { /* the next event retries */ } ; return }
    if (!activeFetcher) {
      try {
        // ⛔ `storeDir` UNDER THIS APP'S STORAGE. The default derives from the home directory, which
        // Android does not have — measured on a handset as ENOENT, surfacing as "no swarm fetcher".
        // ⛔ NO storeDir WHEN WE ALREADY HOLD THE ENTRY. The watch has that Corestore open and
        // RocksDB's lock is EXCLUSIVE — resolve-release's own header says the registry store "takes
        // an EXCLUSIVE" lock. Opening it a second time fails with "File descriptor could not be
        // locked", which surfaces as an OTA error rather than as the double-open it is. With
        // `latest` supplied there is nothing left to resolve, so there is nothing to open.
        ;({ fetcher: activeFetcher } = await swarmFetcher({
          registryKeyHex, repo, destDir: path.join(dir, '.fetch'), swarm, latest: entry
        }))
      } catch (err) {
        say('ota: no swarm fetcher (' + ((err && err.message) || err) + ')' + frame(err))
        return
      }
    }
    runtime = new PghRuntime({
      name: repo,
      currentTreeHash: currentTreeHash || envBundleHash() || null,
      fetcher: activeFetcher,
      store,
      // ⛔ NO INTERVAL. The watch is the signal; a timer beside it would be an unconditional sweep,
      // and hyper-social #68 §2 removed exactly that from delivery because a sweep CONCEALS a dead
      // hook — the update appears to work, so nobody notices nothing fires on the event.
      interval: 0
    })
    runtime.on('updating', (r) => { store.noteRelease(r); say('ota: downloading ' + (r && r.version)) })
    runtime.on('updated', (i) => say('ota: staged ' + i.newTreeHash + ' — active on next start'))
    runtime.on('error', (e) => say('ota: ' + ((e && e.message) || e) + frame(e)))
    started = runtime.start()
    await started
  }

  if (injectedFetcher) {
    // A test double: no registry to watch, so wire it straight away and keep the old shape.
    await wire(null)
  } else {
    const { watchLatestRelease } = await import('pgh/resolve-release')
    try {
      watch = await watchLatestRelease({
        registryKeyHex,
        name: repo,
        storeDir: path.join(dir, 'registry'),
        onRelease: (entry) => wire(entry).catch((e) => say('ota: ' + ((e && e.message) || e)))
      })
      say('ota: watching ' + repo + ' — releases arrive as events, not on a timer')
    } catch (err) {
      say('ota: cannot watch the registry (' + ((err && err.message) || err) + ')' + frame(err))
      return null
    }
  }
  // ⛔ `checkNow` IS EXPOSED. The 6-hour timer is a poll on a phone that is awake for seconds at a time,
  // so the interval is almost never when the user is actually looking — the one moment a check is worth
  // making is when they ask. pgh-runtime already has the public method (checkNow, index.js:107); this
  // just stops throwing the handle away, which is the whole reason the app had no update control.
  // ⛔ CLOSE THE SWARM WE OPENED. stop() used to stop only the poll loop, leaving NetworkFetcher's
  // Hyperswarm node and corestore open for the life of the process. On a server that is untidy; on a
  // phone it is a DHT node and a set of sockets held awake by something the user has switched off. It is
  // the same shape as the hyper-svc UDS leak that reached ~205k sockets and ~14 GB on stortron — a
  // handle nobody closed because the thing that created it was not the thing that owned it.
  //
  // Caught empirically: the real end-to-end probe finished staging in 23.5s and then never exited, and
  // `timeout` killed it at 600s (EXIT=124). A stop() that leaves the process alive is not a stop.
  //
  // OWNERSHIP: only close what we created. An injected fetcher belongs to the caller — a test owns its
  // double's lifecycle, and closing someone else's handle is how you get a use-after-close in the suite
  // that was meant to be catching this.
  return {
    stop: async () => {
      // ⛔ THE WATCH TOO. It holds a Hyperswarm and a Corestore, and a swarm left running is a DHT
      // node kept awake by something the user switched off — the leak shape that reached ~205k
      // sockets on stortron because the thing that created a handle was not the thing that owned it.
      try { if (watch) await watch.close() } catch { /* stopping must not throw on the way out */ }
      try { if (runtime && runtime.stop) await runtime.stop() } catch { /* same */ }
      if (ownsFetcher && activeFetcher && typeof activeFetcher.close === 'function') {
        try { await activeFetcher.close() } catch { /* same */ }
      }
    },
    store,
    // ⚠ Before the first release event there is no runtime, and that is not an error — it means the
    // registry has not answered yet. Saying so beats throwing at whoever pressed the button.
    checkNow: async () => {
      if (!runtime) { say('ota: no release seen yet — still waiting on the registry'); return null }
      return runtime.checkNow()
    }
  }
}
// What the native shell tells the node at boot, and what the node tells the settings page back.
//
// ⛔ WHY A FILE AND NOT argv. The obvious way to hand the running bundle's hash to the worklet is a
// second argv element. It is a trap: the SHELL (the APK) and the BUNDLE (arrives by OTA) version
// independently, and lib/node.js reads storage as `Bare.argv[argv.length - 1]`. A new shell passing
// `[storage, hash]` to an OLD bundle would hand it the hash as its storage path — a node booting into
// an empty directory, which is the worst failure this app has. A file is additive in both directions:
// an old bundle never reads it, a new bundle finds it missing and says so.
//
// Two files, two directions, both tiny:
//
//   <storage>/shell.json      shell → node.  { ota, pair, bundle, dns }
//   <storage>/app-update.json node → page.   { ota, pair, running, staged, checkedAt }
//
// Nothing here does I/O: the callers own that (RNFS on one side, bare-fs on the other), which is what
// makes both shapes testable with no handset.

export const SHELL_FILE = 'shell.json'
export const STATE_FILE = 'app-update.json'

const isHash = (v) => typeof v === 'string' && /^[0-9a-f]{16,128}$/i.test(v)

// Addresses only — this list becomes FAVELA_NAMESERVERS and is handed to connect(). A hostname would
// need a resolver to resolve, which is the thing we are obtaining, and `8.8.8.8:53` is a connect() to a
// host that does not exist. A character-class test admits both, so the families are parsed apart.
//
// ⚠ Deliberately duplicated from favela-web's lib/dns-txt.js rather than imported: this module is also
// loaded by the React Native side, which does not bundle favela-web, and a cross-package import here
// would make the app shell depend on the worklet's dependency graph to open a settings screen.
function isIpv4 (s) {
  const parts = s.split('.')
  return parts.length === 4 && parts.every((o) => /^\d{1,3}$/.test(o) && Number(o) <= 255)
}
function isIpv6 (s) {
  if (!s.includes(':')) return false
  if ((s.match(/::/g) || []).length > 1) return false
  const parts = s.split(':')
  if (parts.length > 8) return false
  return parts.every((p, i) => {
    if (p === '') return true
    if (i === parts.length - 1 && p.includes('.')) return isIpv4(p)
    return /^[0-9a-fA-F]{1,4}$/.test(p)
  })
}
const isAddr = (v) => typeof v === 'string' && v.length > 0 && v.length <= 45 && (isIpv4(v) || isIpv6(v))

/**
 * Read what the shell said. Tolerant by construction: every field is optional and a malformed file is
 * the same as no file, because a node that refuses to boot over its own handoff blob is unrecoverable
 * from a user's phone.
 *
 * `ota` is a CAPABILITY CLAIM, not a preference: it means "this shell has a message handler that can
 * service check/restart". The settings page renders its buttons only when it is true, so an old shell
 * running a new bundle shows no controls rather than dead ones.
 */
export function parseShell (text) {
  let raw = null
  try { raw = typeof text === 'string' ? JSON.parse(text) : text } catch { raw = null }
  if (!raw || typeof raw !== 'object') return { ota: false, pair: false, bundle: null, dns: [] }
  return {
    ota: raw.ota === true,
    // A SEPARATE capability from `ota`, deliberately. The page arrives by OTA and this shell by APK, so
    // "can service update buttons" and "can open the pairing screen" are answered by whatever shell is
    // actually running — a page must never render a control the shell beneath it cannot honour.
    pair: raw.pair === true,
    bundle: isHash(raw.bundle) ? String(raw.bundle) : null,
    dns: Array.isArray(raw.dns) ? raw.dns.filter(isAddr).map(String) : []
  }
}

/** The shell's side of the same shape. Written before every worklet start, never appended to. */
export function encodeShell ({ ota = false, pair = false, bundle = null, dns = [] } = {}) {
  return JSON.stringify({
    ota: ota === true,
    pair: pair === true,
    bundle: isHash(bundle) ? String(bundle) : null,
    dns: Array.isArray(dns) ? dns.filter(isAddr).map(String) : []
  })
}

/**
 * What the settings page renders.
 *
 * ⛔ `running` COMES FROM THE SHELL, not from the OTA store. The store knows what is STAGED; only the
 * side that chose a file and started the runtime knows what is EXECUTING. Deriving "running" from
 * current.json is how the OTA log came to announce it had staged the bundle it was already running.
 *
 * `checkedAt` is an ISO string because favela-web's relTime() parses one.
 */
export function shellState ({ shell = null, staged = null, checkedAt = null } = {}) {
  const s = shell || { ota: false, pair: false, bundle: null }
  return {
    ota: s.ota === true,
    pair: s.pair === true,
    running: isHash(s.bundle) ? String(s.bundle) : null,
    staged: isHash(staged) ? String(staged) : null,
    checkedAt: typeof checkedAt === 'string' && checkedAt ? checkedAt : null
  }
}

/**
 * A message from the settings page, arriving through the WebView bridge.
 *
 * The page is served by our own loopback server and the WebView refuses off-origin navigation, but this
 * still parses UNTRUSTED input: it is a string from a web page reaching a function that can restart the
 * node, and one that opens a ceremony granting a signing key. So it is an allow-list of three named
 * verbs and nothing else — no pass-through of an op name to a handler map, which is how a bridge like
 * this turns into a remote-call surface.
 */
export function parseShellMessage (data) {
  let raw = null
  try { raw = typeof data === 'string' ? JSON.parse(data) : data } catch { return null }
  if (!raw || typeof raw !== 'object' || raw.t !== 'app-update') return null
  if (raw.op === 'check' || raw.op === 'restart' || raw.op === 'pair') return { op: raw.op }
  return null
}
// UTF-8 ↔ bytes, dependency-free.
//
// ⛔ WHY THIS EXISTS AT ALL. react-native-bare-kit's IPC is a byte stream on both sides and does NOT
// convert: `_read` pushes `new Uint8Array(data)` (index.js:59) and `_write` reads `.buffer` /
// `.byteOffset` / `.byteLength` off whatever it is given (index.js:65). So:
//
//   - `data.toString()` on a read chunk is Array.prototype.toString — it yields "123,34,116,..." (the
//     byte VALUES, comma-separated), not text. This silently fed garbage to the message parser and is
//     what made a fully-booted node look like "worklet did not become ready in 120000ms".
//   - writing a string writes nothing coherent, because a string has no `.buffer`.
//
// ⛔ NOT `b4a` and NOT `Buffer`. b4a's react-native entry does `require('react-native-b4a')` inside a
// try/catch, and Metro resolves statically — an unresolvable require fails the BUILD, catch or no
// catch. `Buffer` is not a React Native global. TextDecoder is not guaranteed present either. Hence
// hand-rolled, and hence testable off-device (test/utf8.test.mjs).

/**
 * Encode a string to UTF-8 bytes.
 */
export function encodeUtf8 (str) {
  const out = []
  for (let i = 0; i < str.length; i++) {
    let c = str.charCodeAt(i)
    // Surrogate pair → one code point.
    if (c >= 0xd800 && c <= 0xdbff && i + 1 < str.length) {
      const next = str.charCodeAt(i + 1)
      if (next >= 0xdc00 && next <= 0xdfff) {
        c = 0x10000 + ((c - 0xd800) << 10) + (next - 0xdc00)
        i++
      }
    }
    if (c < 0x80) out.push(c)
    else if (c < 0x800) out.push(0xc0 | (c >> 6), 0x80 | (c & 0x3f))
    else if (c < 0x10000) out.push(0xe0 | (c >> 12), 0x80 | ((c >> 6) & 0x3f), 0x80 | (c & 0x3f))
    else {
      out.push(0xf0 | (c >> 18), 0x80 | ((c >> 12) & 0x3f), 0x80 | ((c >> 6) & 0x3f), 0x80 | (c & 0x3f))
    }
  }
  return new Uint8Array(out)
}

/**
 * Decode COMPLETE UTF-8 bytes to a string. For a whole buffer that is known not to end mid-character
 * (a facet slice, a config file) — a stream needs createUtf8Decoder below instead.
 */
export function decodeUtf8 (bytes) {
  const d = createUtf8Decoder()
  return d(bytes)
}

/**
 * A STATEFUL decoder, because this decodes a STREAM.
 *
 * IPC chunk boundaries are not character boundaries any more than they are message boundaries: a
 * multi-byte character can straddle two reads. A stateless per-chunk decode would corrupt exactly those
 * characters (the node's own log lines contain an em dash, so this is not hypothetical). Incomplete
 * trailing sequences are therefore held back until their remaining bytes arrive.
 *
 * Returns a function: (Uint8Array) => string.
 */
export function createUtf8Decoder () {
  let pending = new Uint8Array(0)

  return function decode (chunk) {
    let bytes = chunk
    if (pending.length > 0) {
      bytes = new Uint8Array(pending.length + chunk.length)
      bytes.set(pending, 0)
      bytes.set(chunk, pending.length)
      pending = new Uint8Array(0)
    }

    // Find the start of a trailing incomplete sequence, if any, and hold it back.
    let end = bytes.length
    for (let i = bytes.length - 1; i >= 0 && i >= bytes.length - 3; i--) {
      const b = bytes[i]
      if ((b & 0xc0) === 0x80) continue          // continuation byte — keep walking back
      const need = b < 0x80 ? 1 : b < 0xe0 ? 2 : b < 0xf0 ? 3 : 4
      if (i + need > bytes.length) end = i       // sequence is cut off by the chunk boundary
      break
    }
    if (end < bytes.length) {
      pending = bytes.slice(end)
      bytes = bytes.subarray(0, end)
    }

    let str = ''
    for (let i = 0; i < bytes.length;) {
      const b = bytes[i]
      let c
      if (b < 0x80) { c = b; i += 1 } else if (b < 0xe0) {
        c = ((b & 0x1f) << 6) | (bytes[i + 1] & 0x3f); i += 2
      } else if (b < 0xf0) {
        c = ((b & 0x0f) << 12) | ((bytes[i + 1] & 0x3f) << 6) | (bytes[i + 2] & 0x3f); i += 3
      } else {
        c = ((b & 0x07) << 18) | ((bytes[i + 1] & 0x3f) << 12) | ((bytes[i + 2] & 0x3f) << 6) |
            (bytes[i + 3] & 0x3f)
        i += 4
      }
      if (c > 0xffff) {
        c -= 0x10000
        str += String.fromCharCode(0xd800 + (c >> 10), 0xdc00 + (c & 0x3ff))
      } else {
        str += String.fromCharCode(c)
      }
    }
    return str
  }
}
// favela-surface-android worklet — a full favela-peer node, with favela-surface rendering its pages.
//
// This runs INSIDE a bare-kit worklet. There is no shell, no $HOME and no env: Android hands the
// app-private directory in as argv, and the React Native side talks to it over bare-kit's IPC.
//
// ── ⛔ One process, and that is the whole design ───────────────────────────────────────────────
//
// On a desktop, favela-surface reaches favela-peer over a unix socket. Here it does not, and not as
// a compromise:
//
//   - `favela-peer/lib/rpc.js` exports `dispatch(node, cmd, a)` next to `createServer(node, sock)`.
//     `dispatch` IS the interface; the socket is one ADAPTER over it, correct where the two halves
//     are genuinely two processes. A worklet is one thread, so there is nothing left to adapt.
//   - A pathname unix socket could not be created here anyway. Measured with socat on real
//     hardware: `bind(AF_UNIX /path/x.sock)` -> EACCES, because Android SELinux forbids an
//     unprivileged domain creating a `sock_file`.
//   - Loopback TCP between them would work and is worse: a socket file carries permissions, and a
//     loopback port on Android is reachable by every other app on the device.
//
// ⇒ A protocol crosses a boundary. bare-kit spends one where there is a real one — native to JS —
// and pays with a socketpair rather than a pathname socket. Inside the thread, we call the function.
//
// ⚠ The HTTP server below is a genuine boundary: a WebView is a browser and speaks HTTP. It binds
// loopback only, and serves the same pages a desktop gets.

import fs from 'bare-fs'
import process from 'bare-process'
import { join } from 'bare-path'
import http from 'bare-http1'
import Hyperswarm from 'hyperswarm'
import { FavelaNode } from 'favela-peer/lib/node.js'
import { SwarmTransport } from 'favela-peer/lib/transport.js'
import { dispatch } from 'favela-peer/lib/rpc.js'
import { route } from 'favela-surface/lib/router.js'
import { Sessions } from 'favela-surface/lib/session.js'
import { an } from 'anproto'
import { createUtf8Decoder } from './host/utf8.js'
// Stamped at build time by scripts/gen-version.mjs — the bundle carries no readable package.json.
import { VERSION, BUILT_AT } from './version.js'

/* global BareKit, Bare */

const IPC = typeof BareKit !== 'undefined' ? BareKit.IPC : null

// ⛔ Every line goes to the console TOO, never only over IPC. A worklet whose UI has not attached
// yet — or has gone away — still needs its boot story readable in `adb logcat`, and favela-android
// learned that the expensive way: sending only over IPC meant the reason a boot failed existed for
// the two seconds before anyone could ask for it.
function send (obj) {
  const line = JSON.stringify(obj)
  console.log(line)
  if (!IPC) return
  try { IPC.write(Buffer.from(line + '\n')) } catch { /* UI gone; the node keeps running */ }
}

const stage = (name, extra = {}) => send({ t: 'log', stage: name, ...extra })

// Held so the shell's `check` verb reaches the running OTA client rather than starting a second one.
let otaHandle = null
let refreshUpdateState = () => null
let lastOtaNote = null

/**
 * The in-process daemon client — what `route` is handed instead of a socket client.
 *
 * ⛔ The same `call(cmd, args) -> Promise` shape favela-surface's own `Client` has, so that file
 * never learns which it got. This is not an emulation of the socket: `dispatch` is the layer the
 * socket server itself calls.
 */
export function inProcessClient (node) {
  return { call: (cmd, args = {}) => dispatch(node, cmd, args) }
}

/**
 * The loopback surface. Split out from `boot` so it can be exercised off-device: given any node,
 * it stands up the real routes over real HTTP with no bare-kit, no worklet and no Android.
 *
 * ⚠ Binds 127.0.0.1 explicitly. bare-http1's `listen(port)` defaults to all interfaces on some
 * platforms, and a favela node answering on a phone's Wi-Fi address is not what anyone asked for.
 */
export async function serveSurface (node, { port = 0, sessions = new Sessions(), appUpdate = null } = {}) {
  const client = inProcessClient(node)
  const server = http.createServer((req, res) => {
    // ⛔ THE EMBEDDER SUPPLIES IT. favela-surface's router reads no environment by design (its own
    // test asserts that), so the shell state reaches the settings page through ctx or not at all.
    // On a desktop nothing supplies it and the section correctly does not render.
    route(req, res, { client, sessions, appUpdate }).catch((err) => {
      res.writeHead(502, { 'content-type': 'text/plain; charset=utf-8' })
      res.end(String((err && err.message) || err))
    })
  })
  await new Promise((resolve, reject) => {
    server.once('error', reject)
    server.listen(port, '127.0.0.1', resolve)
  })
  const bound = server.address().port
  return { server, sessions, client, port: bound, url: 'http://127.0.0.1:' + bound }
}

// The app's own browser identity, on disk beside the node's storage.
//
// ⛔ A browser is a CONTROLLER, not a device. It cannot hold a writer core or verify a chain, so its
// key never enters the attestation chain — favela-peer's `lib/browsers.js` says exactly this, and it
// is why attesting one would hand a WebView the power to mint devices. The app authenticates; the
// daemon writes.
const BROWSER_KEY = 'browser.key'

/**
 * Load or mint this app's anproto keypair.
 *
 * ⚠ PERSISTED, and it has to be. A key regenerated each boot would claim the instance on first run
 * and then be a stranger on the second — permanently locked out, because TOFU closes on the first
 * claim and never reopens, not even for the claimant. 0600, and it is the one secret this app keeps
 * that is not the node's own.
 */
async function browserKeypair (storage) {
  const p = join(storage, BROWSER_KEY)
  try {
    const kp = fs.readFileSync(p, 'utf8').trim()
    if (kp.length === 132) return kp
  } catch { /* first run */ }
  const kp = await an.gen()
  fs.writeFileSync(p, kp + '\n', { mode: 0o600 })
  return kp
}

/**
 * Prove this app's key to the daemon, claiming the instance on first run.
 *
 * ⛔ OVER THE IN-PROCESS CLIENT, never over HTTP — and that is a security property, not tidiness.
 * TOFU accepts the first key to prove itself, and favela-peer's own note says the safety of that
 * "rests ENTIRELY on the surface binding localhost". On Android every app shares loopback, so a
 * claim made over the listener could in principle be raced by anything else on the device that
 * found the port. Claiming through `dispatch` happens before the listener exists and cannot be
 * reached from outside this process at all.
 *
 * The order matters and is not arbitrary:
 *   1. `browser-status` — already known? Then just authenticate. Re-claiming is not possible.
 *   2. revoked? Say so plainly. A revoked identity stays registered, so this is terminal without
 *      a human, and pretending otherwise would loop.
 *   3. unclaimed? Claim it. This is the fresh-install case.
 *   4. otherwise someone else holds it, and an invite is the only way in.
 *
 * ⚠ `an.sign` signs a HASH, not the challenge — `_proveKey` compares `an.parse(opened).hash`
 * against `an.hash(challenge)`. Signing the raw string produces a valid signature over the wrong
 * thing, which verifies and then fails the hash check.
 */
export async function signIn (node, storage) {
  const keypair = await browserKeypair(storage)
  const publicKey = an.publicKey(keypair)
  const call = (cmd, args = {}) => dispatch(node, cmd, args)

  const prove = async () => {
    const { challenge } = await call('browser-challenge')
    const signature = await an.sign(await an.hash(challenge), keypair)
    return { publicKey, signature, challenge }
  }

  const st = await call('browser-status', { publicKey })
  if (st && st.revoked) {
    throw new Error('this app\'s browser identity was revoked — a revoked identity stays registered, ' +
      'so it needs an invite from the CLI or another signed-in browser')
  }
  if (st && st.known) {
    await call('browser-auth', await prove())
    return { publicKey, claimed: false }
  }

  const { unclaimed } = await call('browser-unclaimed')
  if (!unclaimed) {
    throw new Error('another browser already claimed this node — TOFU closes on the first claim and ' +
      'never reopens, so this app needs a one-time invite')
  }
  await call('browser-claim', await prove())
  return { publicKey, claimed: true }
}

// ── boot ──────────────────────────────────────────────────────────────────────────────────────

const state = { ready: false, feed: null, url: null, error: null, browserKey: null }
let surface = null
let node = null

export async function boot (storage) {
  fs.mkdirSync(storage, { recursive: true })
  stage('storage', { storage })

  const swarm = new Hyperswarm()
  node = new FavelaNode(storage, { transport: null, swarm })
  // ⛔ ready() FIRST: it builds the overlay, and the transport needs it — the bytes ride the same
  // connections as the log, so a transport without the overlay replicates half the system. Same
  // order favela-peer's own index.js uses, for the same reason.
  await node.ready()
  stage('node ready', { feed: node.key.toString('hex') })

  node.transport = new SwarmTransport(swarm, { overlay: node.overlay })
  await node.transport.join(node.peer.feed)
  for (const [, feed] of node._feeds) await node.transport.join(feed)
  stage('swarm joined')

  // ⛔ BEFORE the listener. See `signIn`: claiming over a loopback port on Android could be raced
  // by any other app on the device that found it, and claiming through `dispatch` cannot.
  const who = await signIn(node, storage)
  state.browserKey = who.publicKey
  stage(who.claimed ? 'claimed this node' : 'signed in')

  // Reads the file the OTA block below writes. A function rather than a value: settings is loaded
  // long after boot, and a snapshot taken here would show the state at start-up for ever.
  const appUpdate = () => {
    try { return JSON.parse(fs.readFileSync(join(storage, 'app-update.json'), 'utf8')) } catch { return null }
  }
  surface = await serveSurface(node, { port: 0, appUpdate })
  state.ready = true
  state.feed = node.key.toString('hex')
  state.url = surface.url

  // ⛔ PROVE it serves rather than assuming the import worked — favela-android found that an import
  // succeeding says nothing about a listener answering. A ready the UI cannot use is worse than a
  // failure it can read.
  send({ t: 'ready', feed: state.feed, url: state.url })

  // ⛔ START OTA, and note this is what makes `lib/host/ota.js` exist at all. It was copied here with
  // the rest of the host layer and nothing imported it — so bare-pack, which walks the MODULE graph
  // from this entry, never reached it and `pgh-runtime` was absent from the bundle. The file's own
  // header records the same defect happening once before: "WHAT WAS HERE BEFORE HAD NEVER RUN."
  //
  // ⛔ AFTER `ready`, never before. Updating is a background concern and a phone that cannot reach the
  // swarm must still show its own records — favela-android learned this as "a ready the UI cannot use
  // is worse than a failure it can read". Hence try/catch: OTA is non-fatal, always.
  //
  // ⚠ `currentTreeHash` is NOT passed, because this app has no shell handoff yet — the RN side does not
  // yet tell the worklet which bundle it booted. The consequence is stated rather than discovered:
  // pgh-runtime's "already running this tree" test cannot fire, so a boot will log `staged — active on
  // next start` naming a bundle it is already running. No bytes move (installTree short-circuits on
  // hasTree), but the log reads like an update that never lands, and favela-android lost an evening to
  // exactly that. Wiring `shell-handoff.js` is the next piece and closes it.
  try {
    const { parseShell, shellState, SHELL_FILE, STATE_FILE } = await import('./host/shell-handoff.js')
    // ⛔ THE SHELL ALREADY TELLS US THIS AND WE WERE NOT LISTENING. `worklet.js` writes shell.json with
    // the bundle hash it actually booted; reading it is what lets pgh-runtime's "already running this
    // tree" test fire. Without it every check re-stages the bundle already running and logs
    // `staged — active on next start`, which reads as an update that never lands — favela-android lost
    // an evening to exactly that misreading.
    let shell = { ota: false, pair: false, bundle: null }
    try { shell = parseShell(fs.readFileSync(join(storage, SHELL_FILE), 'utf8')) } catch {}

    const { startOta } = await import('./host/ota.js')
    // ⛔ THE NODE'S LAST WORD IS KEPT, not just logged. `say` lines are the only place that
    // distinguishes "no swarm fetcher", "downloading 0.0.6" and "previously failed to boot" — and a
    // handset has no log anyone can read, so they went nowhere. Without this, a device could report
    // only that nothing happened, which is what several hours of guessing were spent on.
    const ota = await startOta({
      storage,
      say: (m) => { lastOtaNote = String(m); send({ t: 'log', msg: m }) },
      currentTreeHash: shell.bundle
    })

    // ⛔ ON DISK, NOT AN IPC REPLY. The settings page is HTML served by favela-surface, not React
    // Native, so it must be able to render this SERVER-side. A reply channel would mean two sources of
    // truth for the same three fields — favela-android's note, and the reason this is a file.
    const writeState = () => {
      let staged = null
      try { staged = (JSON.parse(fs.readFileSync(join(storage, 'bundles', 'current.json'), 'utf8')) || {}).treeHash } catch {}
      // ⛔ THE VERSION IS THE HALF A PERSON CAN ACT ON. Two tree hashes tell you something changed;
      // they do not tell you WHICH build you are running, so "is my update installed?" stayed
      // unanswerable from the device. `version` is what a release page shows.
      const st = {
        ...shellState({ shell, staged, checkedAt: new Date().toISOString() }),
        available: !!ota,
        version: VERSION,
        builtAt: BUILT_AT,
        note: lastOtaNote
      }
      try { fs.writeFileSync(join(storage, STATE_FILE), JSON.stringify(st)) } catch { /* status only */ }
      // ⚠ RETURNED as well as written. `storage` is a parameter of boot(), not module scope, so the
      // IPC handler cannot rebuild this path — and reaching for it there is a ReferenceError that
      // would only appear when someone pressed the button.
      return st
    }
    // ⛔ AN ENV VAR, because favela-surface renders this SERVER-side and must not learn what a worklet
    // is. Same contract favela-android uses with favela-web: the shell writes a file, the page reads
    // it, and neither knows anything about the other's process model.
    process.env.FAVELA_SHELL_STATE = join(storage, STATE_FILE)
    writeState()
    otaHandle = ota
    refreshUpdateState = writeState
  } catch (e) {
    send({ t: 'log', msg: 'ota unavailable (non-fatal): ' + ((e && e.message) || String(e)) })
  }

  return state
}

// ── the IPC surface ───────────────────────────────────────────────────────────────────────────
//
// ⛔ Deliberately tiny. favela-android grew a bespoke verb per feature and ended up with a crippled
// cousin of favela-web built from the same parts. Everything a person sees is served as HTML over
// loopback; IPC carries only what HTML cannot: where the surface is, and whether it is up.

async function onRequest (msg) {
  const { id, op, params } = msg || {}
  try {
    let result
    switch (op) {
      case 'status':
        result = { ...state }
        break
      // ⛔ The WebView cannot be handed a cookie from outside — the session cookie is HttpOnly, so
      // `document.cookie` cannot write it and no native API portably can. The cookie must arrive on
      // an HTTP response, which is what `/auth/handoff` is for. This mints a session and a ONE-TIME
      // token for it, and returns the URL that turns the token into a cookie.
      //
      // ⚠ The token is spent on first load. A WebView that reloads needs a NEW one, which is why
      // this is an op the shell can call again rather than a value handed over once at boot.
      case 'session': {
        if (!surface) throw new Error('the surface is not serving yet')
        if (!state.browserKey) throw new Error('this app has not signed in to its own node')
        const id = surface.sessions.create(state.browserKey)
        const token = surface.sessions.createHandoff(id)
        const next = typeof params === 'object' && params && typeof params.next === 'string' ? params.next : '/'
        result = { url: state.url + '/auth/handoff?token=' + token + '&next=' + encodeURIComponent(next) }
        break
      }
      // ⛔ THE SHELL'S `check` VERB LANDS HERE. It drives the RUNNING OTA client — starting a second
      // one would open its own swarm and fetcher, and the two would race to stage into one directory.
      // Returns the refreshed state so the page can re-render without a round trip through the file.
      case 'check-update': {
        if (otaHandle && otaHandle.checkNow) await otaHandle.checkNow()
        result = refreshUpdateState()
        break
      }
      case 'url':
        // The WebView needs a URL and nothing else. A port of 0 at boot means the OS chose one, so
        // this is the only place that knows it.
        if (!state.url) throw new Error('the surface is not serving yet')
        result = { url: state.url }
        break
      default:
        throw new Error('unknown op: ' + op)
    }
    send({ id, result })
  } catch (e) {
    send({ id, error: (e && e.message) || String(e) })
  }
}

// ⛔ `IPC.on('data')`, NOT `IPC.read(cb)`. BareKit.IPC is a streamx Duplex whose `read()` takes no
// callback; a pump that passed one fired once, read nothing and never re-armed, so a worklet
// consumed zero inbound bytes for its whole life while still logging happily. It looked alive and
// answered nothing. favela-android paid for this line.
//
// The decoder is stateful because chunk boundaries are not character boundaries.
if (IPC) {
  let buf = ''
  const decode = createUtf8Decoder()
  IPC.on('data', (data) => {
    buf += decode(data)
    let i
    while ((i = buf.indexOf('\n')) >= 0) {
      const line = buf.slice(0, i); buf = buf.slice(i + 1)
      if (!line.trim()) continue
      try { onRequest(JSON.parse(line)) } catch { send({ error: 'bad request json' }) }
    }
  })
}

// Android hands the app-private directory in as the last argv — there is no $HOME and no env here.
// ⚠ Only when actually running as a worklet: imported by a test, this file must not boot anything.
if (IPC && typeof Bare !== 'undefined') {
  const storage = Bare.argv[Bare.argv.length - 1] || join('/data/local/tmp', 'favela')
  boot(storage).catch((e) => {
    state.error = (e && e.message) || String(e)
    send({ t: 'fatal', error: (e && e.stack) || String(e) })
  })
}

export default { boot, serveSurface, inProcessClient }
// GENERATED by scripts/gen-version.mjs — do not edit.
export const VERSION = "0.0.14"
export const BUILT_AT = 1789063558198
export default { VERSION, BUILT_AT }
const { Pull, Push, HEADERBYTES, KEYBYTES, ABYTES } = require('sodium-secretstream')
const sodium = require('sodium-universal')
const crypto = require('hypercore-crypto')
const { Duplex, Writable, getStreamError } = require('streamx')
const b4a = require('b4a')
const Timeout = require('timeout-refresh')
const unslab = require('unslab')
const Bridge = require('./lib/bridge')
const Handshake = require('./lib/handshake')

const IDHEADERBYTES = HEADERBYTES + 32
const [NS_INITIATOR, NS_RESPONDER, NS_SEND] = crypto.namespace('hyperswarm/secret-stream', 3)
const MAX_ATOMIC_WRITE = 256 * 256 * 256 - 1

module.exports = class NoiseSecretStream extends Duplex {
  constructor(isInitiator, rawStream, opts = {}) {
    super({ mapWritable: toBuffer })

    if (typeof isInitiator !== 'boolean') {
      throw new Error('isInitiator should be a boolean')
    }

    this.noiseStream = this
    this.isInitiator = isInitiator
    this.rawStream = null

    this.publicKey = opts.publicKey || null
    this.remotePublicKey = opts.remotePublicKey || null
    this.handshakeHash = null
    this.connected = false
    this.keepAlive = opts.keepAlive || 0
    this.timeout = 0
    this.enableSend = opts.enableSend !== false

    // pointer for upstream to set data here if they want
    this.userData = null

    let openedDone = null
    this.opened = new Promise((resolve) => {
      openedDone = resolve
    })

    this.rawBytesWritten = 0
    this.rawBytesRead = 0

    // metadata used by 'hyperdht'
    this.relay = null
    this.puncher = null

    // unwrapped raw stream
    this._rawStream = null

    // handshake state
    this._handshake = null
    this._handshakePattern = opts.pattern || null
    this._handshakeDone = null

    // message parsing state
    this._state = 0
    this._len = 0
    this._tmp = 1
    this._message = null

    this._openedDone = openedDone
    this._startDone = null
    this._drainDone = null
    this._outgoingPlain = null
    this._outgoingWrapped = null
    this._utp = null
    this._setup = true
    this._ended = 2
    this._encrypt = null
    this._decrypt = null
    this._timeoutTimer = null
    this._keepAliveTimer = null
    this._sendState = null

    if (opts.autoStart !== false) this.start(rawStream, opts)

    // wiggle it to trigger open immediately (TODO add streamx option for this)
    this.resume()
    this.pause()
  }

  static keyPair(seed) {
    return Handshake.keyPair(seed)
  }

  static id(handshakeHash, isInitiator, id) {
    return streamId(handshakeHash, isInitiator, id)
  }

  setTimeout(ms) {
    if (!ms) ms = 0

    this._clearTimeout()
    this.timeout = ms

    if (!ms || this.rawStream === null) return

    this._timeoutTimer = Timeout.once(ms, destroyTimeout, this)
    this._timeoutTimer.unref()
  }

  setKeepAlive(ms) {
    if (!ms) ms = 0

    this._clearKeepAlive()

    this.keepAlive = ms

    if (!ms || this.rawStream === null) return

    this._keepAliveTimer = Timeout.on(ms, sendKeepAlive, this)
    this._keepAliveTimer.unref()
  }

  sendKeepAlive() {
    const empty = this.alloc(0)
    this.write(empty)
  }

  start(rawStream, opts = {}) {
    if (rawStream) {
      this.rawStream = rawStream
      this._rawStream = rawStream
      if (typeof this.rawStream.setContentSize === 'function') {
        this._utp = rawStream
      }
    } else {
      this.rawStream = new Bridge(this)
      this._rawStream = this.rawStream.reverse
    }

    this.rawStream.on('error', this._onrawerror.bind(this))
    this.rawStream.on('close', this._onrawclose.bind(this))

    this._startHandshake(opts.handshake, opts.keyPair || null)
    this._continueOpen(null)

    if (this.destroying) return

    if (opts.data) this._onrawdata(opts.data)
    if (opts.ended) this._onrawend()

    if (this.keepAlive > 0 && this._keepAliveTimer === null) {
      this.setKeepAlive(this.keepAlive)
    }

    if (this.timeout > 0 && this._timeoutTimer === null) {
      this.setTimeout(this.timeout)
    }
  }

  async flush() {
    if ((await this.opened) === false) return false
    if ((await Writable.drained(this)) === false) return false
    if (this.destroying) return false

    if (this.rawStream !== null && this.rawStream.flush) {
      return await this.rawStream.flush()
    }

    return true
  }

  _continueOpen(err) {
    if (err) this.destroy(err)
    if (this._startDone === null) return
    const done = this._startDone
    this._startDone = null
    this._open(done)
  }

  _onkeypairpromise(p) {
    const self = this
    const cont = this._continueOpen.bind(this)

    p.then(onkeypair, cont)

    function onkeypair(kp) {
      self._onkeypair(kp)
      cont(null)
    }
  }

  _onkeypair(keyPair) {
    const pattern = this._handshakePattern || 'XX'
    const remotePublicKey = this.remotePublicKey

    this._handshake = new Handshake(this.isInitiator, keyPair, remotePublicKey, pattern)
    this.publicKey = this._handshake.keyPair.publicKey
  }

  _startHandshake(handshake, keyPair) {
    if (handshake) {
      const { tx, rx, hash, publicKey, remotePublicKey } = handshake
      this._setupSecretStream(tx, rx, hash, publicKey, remotePublicKey)
      return
    }

    if (!keyPair) keyPair = Handshake.keyPair()

    if (typeof keyPair.then === 'function') {
      this._onkeypairpromise(keyPair)
    } else {
      this._onkeypair(keyPair)
    }
  }

  _onrawerror(err) {
    this.destroy(err)
  }

  _onrawclose() {
    if (this._ended !== 0) this.destroy()
  }

  _onrawdata(data) {
    let offset = 0

    if (this._timeoutTimer !== null) {
      this._timeoutTimer.refresh()
    }

    do {
      switch (this._state) {
        case 0: {
          while (this._tmp !== 0x1000000 && offset < data.byteLength) {
            const v = data[offset++]
            this._len += this._tmp * v
            this._tmp *= 256
          }

          if (this._tmp === 0x1000000) {
            this._tmp = 0
            this._state = 1
            const unprocessed = data.byteLength - offset
            if (unprocessed < this._len && this._utp !== null)
              this._utp.setContentSize(this._len - unprocessed)
          }

          break
        }

        case 1: {
          const missing = this._len - this._tmp
          const end = missing + offset

          if (this._message === null && end <= data.byteLength) {
            this._message = data.subarray(offset, end)
            offset += missing
            this._incoming()
            break
          }

          const unprocessed = data.byteLength - offset

          if (this._message === null) {
            this._message = b4a.allocUnsafe(this._len)
          }

          b4a.copy(data, this._message, this._tmp, offset)
          this._tmp += unprocessed

          if (end <= data.byteLength) {
            offset += missing
            this._incoming()
          } else {
            offset += unprocessed
          }

          break
        }
      }
    } while (offset < data.byteLength && !this.destroying)
  }

  _onrawend() {
    this._ended--
    this.push(null)
  }

  _onrawdrain() {
    const drain = this._drainDone
    if (drain === null) return
    this._drainDone = null
    drain()
  }

  _read(cb) {
    this.rawStream.resume()
    cb(null)
  }

  _incoming() {
    const message = this._message

    this._state = 0
    this._len = 0
    this._tmp = 1
    this._message = null

    if (this._setup === true) {
      if (this._handshake) {
        this._onhandshakert(this._handshake.recv(message))
      } else {
        if (message.byteLength !== IDHEADERBYTES) {
          this.destroy(new Error('Invalid header message received'))
          return
        }

        const remoteId = message.subarray(0, 32)
        const expectedId = streamId(this.handshakeHash, !this.isInitiator)
        const header = message.subarray(32)

        if (!b4a.equals(expectedId, remoteId)) {
          this.destroy(new Error('Invalid header received'))
          return
        }

        this._decrypt.init(header)
        this._setup = false // setup is now done
      }
      return
    }

    if (message.byteLength < ABYTES) {
      this.destroy(new Error('Invalid message received'))
      return
    }

    this.rawBytesRead += message.byteLength

    const plain = message.subarray(1, message.byteLength - ABYTES + 1)

    try {
      this._decrypt.next(message, plain)
    } catch (err) {
      this.destroy(err)
      return
    }

    // If keep alive is selective, eat the empty buffers (ie assume the other side has it enabled also)
    if (plain.byteLength === 0 && this.keepAlive !== 0) return

    if (this.push(plain) === false) {
      this.rawStream.pause()
    }
  }

  _onhandshakert(h) {
    if (this._handshakeDone === null) return

    if (h !== null) {
      if (h.data) this._rawStream.write(h.data)
      if (!h.tx) return
    }

    const done = this._handshakeDone
    const publicKey = this._handshake.keyPair.publicKey

    this._handshakeDone = null
    this._handshake = null

    if (h === null) return done(new Error('Noise handshake failed'))

    this._setupSecretStream(h.tx, h.rx, h.hash, publicKey, h.remotePublicKey)
    this._resolveOpened(true)
    done(null)
  }

  _setupSecretStream(tx, rx, handshakeHash, publicKey, remotePublicKey) {
    const buf = b4a.allocUnsafeSlow(3 + IDHEADERBYTES)
    writeUint24le(IDHEADERBYTES, buf)

    this._encrypt = new Push(unslab(tx.subarray(0, KEYBYTES)), undefined, buf.subarray(3 + 32))
    this._decrypt = new Pull(unslab(rx.subarray(0, KEYBYTES)))

    this.publicKey = publicKey
    this.remotePublicKey = remotePublicKey
    this.handshakeHash = handshakeHash

    const id = buf.subarray(3, 3 + 32)
    streamId(handshakeHash, this.isInitiator, id)

    // initialize secretbox state for unordered messages
    this._setupSecretSend(handshakeHash)

    this.emit('handshake')
    // if rawStream is a bridge, also emit it there
    if (this.rawStream !== this._rawStream) this.rawStream.emit('handshake')

    if (this.destroying) return

    this._rawStream.write(buf)
  }

  _setupSecretSend(handshakeHash) {
    this._sendState = b4a.allocUnsafeSlow(32 + 32 + 8 + 8)
    const encrypt = this._sendState.subarray(0, 32) // secrets
    const decrypt = this._sendState.subarray(32, 64)
    const counter = this._sendState.subarray(64, 72) // nonce
    const initial = this._sendState.subarray(72)

    const inputs = this.isInitiator
      ? [
          [NS_INITIATOR, NS_SEND],
          [NS_RESPONDER, NS_SEND]
        ]
      : [
          [NS_RESPONDER, NS_SEND],
          [NS_INITIATOR, NS_SEND]
        ]

    sodium.crypto_generichash_batch(encrypt, inputs[0], handshakeHash)
    sodium.crypto_generichash_batch(decrypt, inputs[1], handshakeHash)

    sodium.randombytes_buf(initial)
    counter.set(initial)
  }

  _open(cb) {
    // no autostart or no handshake yet
    if (this._rawStream === null || (this._handshake === null && this._encrypt === null)) {
      this._startDone = cb
      return
    }

    this._rawStream.on('data', this._onrawdata.bind(this))
    this._rawStream.on('end', this._onrawend.bind(this))
    this._rawStream.on('drain', this._onrawdrain.bind(this))

    if (this.enableSend) this._rawStream.on('message', this._onmessage.bind(this))

    if (this._encrypt !== null) {
      this._resolveOpened(true)
      return cb(null)
    }

    this._handshakeDone = cb

    if (this.isInitiator) this._onhandshakert(this._handshake.send())
  }

  _predestroy() {
    if (this.rawStream) {
      const error = getStreamError(this)
      this.rawStream.destroy(error)
    }

    if (this._startDone !== null) {
      const done = this._startDone
      this._startDone = null
      done(new Error('Stream destroyed'))
    }

    if (this._handshakeDone !== null) {
      const done = this._handshakeDone
      this._handshakeDone = null
      done(new Error('Stream destroyed'))
    }

    if (this._drainDone !== null) {
      const done = this._drainDone
      this._drainDone = null
      done(new Error('Stream destroyed'))
    }
  }

  _write(data, cb) {
    let wrapped = this._outgoingWrapped

    if (data !== this._outgoingPlain) {
      wrapped = b4a.allocUnsafe(data.byteLength + 3 + ABYTES)
      wrapped.set(data, 4)
    } else {
      this._outgoingWrapped = this._outgoingPlain = null
    }

    if (wrapped.byteLength - 3 > MAX_ATOMIC_WRITE) {
      return cb(
        new Error(
          'Message is too large for an atomic write. Max size is ' + MAX_ATOMIC_WRITE + ' bytes.'
        )
      )
    }
    this.rawBytesWritten += wrapped.byteLength

    writeUint24le(wrapped.byteLength - 3, wrapped)
    // offset 4 so we can do it in-place
    this._encrypt.next(wrapped.subarray(4, 4 + data.byteLength), wrapped.subarray(3))

    if (this._keepAliveTimer !== null) this._keepAliveTimer.refresh()

    if (this._rawStream.write(wrapped) === false) {
      this._drainDone = cb
    } else {
      cb(null)
    }
  }

  _final(cb) {
    this._clearKeepAlive()
    this._ended--
    this._rawStream.end()
    cb(null)
  }

  _resolveOpened(val) {
    if (this._openedDone === null) return
    const opened = this._openedDone
    this._openedDone = null
    opened(val)
    if (!val) return
    this.connected = true
    this.emit('connect')
  }

  _clearTimeout() {
    if (this._timeoutTimer === null) return
    this._timeoutTimer.destroy()
    this._timeoutTimer = null
    this.timeout = 0
  }

  _clearKeepAlive() {
    if (this._keepAliveTimer === null) return
    this._keepAliveTimer.destroy()
    this._keepAliveTimer = null
    this.keepAlive = 0
  }

  _destroy(cb) {
    this._clearKeepAlive()
    this._clearTimeout()
    this._resolveOpened(false)
    cb(null)
  }

  _boxMessage(buffer) {
    const MB = sodium.crypto_secretbox_MACBYTES // 16
    const NB = sodium.crypto_secretbox_NONCEBYTES // 24

    const counter = this._sendState.subarray(64, 72)
    sodium.sodium_increment(counter)
    if (b4a.equals(counter, this._sendState.subarray(72))) {
      this.destroy(new Error('udp send nonce exchausted'))
      return
    }

    const secret = this._sendState.subarray(0, 32)
    const envelope = b4a.allocUnsafe(8 + MB + buffer.byteLength)
    const nonce = envelope.subarray(0, NB)
    const ciphertext = envelope.subarray(8)

    b4a.fill(nonce, 0) // pad suffix
    nonce.set(counter)

    sodium.crypto_secretbox_easy(ciphertext, buffer, nonce, secret)
    return envelope
  }

  send(buffer) {
    if (!this._sendState) return
    if (!this.rawStream?.send) return // udx-stream expected

    const message = this._boxMessage(buffer)
    return this.rawStream.send(message)
  }

  trySend(buffer) {
    if (!this._sendState) return
    if (!this.rawStream?.trySend) return // udx-stream expected

    const message = this._boxMessage(buffer)
    this.rawStream.trySend(message)
  }

  _onmessage(buffer) {
    if (!this._sendState) return // messages before handshake are dropped

    const MB = sodium.crypto_secretbox_MACBYTES // 16
    const NB = sodium.crypto_secretbox_NONCEBYTES // 24

    if (buffer.byteLength < NB) return // Invalid message

    const nonce = b4a.allocUnsafe(NB)
    b4a.fill(nonce, 0)
    nonce.set(buffer.subarray(0, 8))

    const secret = this._sendState.subarray(32, 64)
    const ciphertext = buffer.subarray(8)
    const plain = buffer.subarray(8, buffer.byteLength - MB)

    if (ciphertext.byteLength < MB) return // invalid message

    const success = sodium.crypto_secretbox_open_easy(plain, ciphertext, nonce, secret)

    if (success) this.emit('message', plain)
  }

  alloc(len) {
    const buf = b4a.allocUnsafe(len + 3 + ABYTES)
    this._outgoingWrapped = buf
    this._outgoingPlain = buf.subarray(4, buf.byteLength - ABYTES + 1)
    return this._outgoingPlain
  }

  toJSON() {
    return {
      isInitiator: this.isInitiator,
      publicKey: this.publicKey && b4a.toString(this.publicKey, 'hex'),
      remotePublicKey: this.remotePublicKey && b4a.toString(this.remotePublicKey, 'hex'),
      connected: this.connected,
      destroying: this.destroying,
      destroyed: this.destroyed,
      rawStream: this.rawStream && this.rawStream.toJSON ? this.rawStream.toJSON() : null
    }
  }
}

function writeUint24le(n, buf) {
  buf[0] = n & 255
  buf[1] = (n >>> 8) & 255
  buf[2] = (n >>> 16) & 255
}

function streamId(handshakeHash, isInitiator, out = b4a.allocUnsafe(32)) {
  sodium.crypto_generichash(out, isInitiator ? NS_INITIATOR : NS_RESPONDER, handshakeHash)
  return out
}

function toBuffer(data) {
  return typeof data === 'string' ? b4a.from(data) : data
}

function destroyTimeout() {
  this.destroy(new Error('Stream timed out'))
}

function sendKeepAlive() {
  const empty = this.alloc(0)
  this.write(empty)
}
const { Duplex, Writable } = require('streamx')

class ReversePassThrough extends Duplex {
  constructor(s) {
    super()
    this._stream = s
    this._ondrain = null
  }

  _write(data, cb) {
    if (this._stream.push(data) === false) {
      this._stream._ondrain = cb
    } else {
      cb(null)
    }
  }

  _final(cb) {
    this._stream.push(null)
    cb(null)
  }

  _read(cb) {
    const ondrain = this._ondrain
    this._ondrain = null
    if (ondrain) ondrain()
    cb(null)
  }
}

module.exports = class Bridge extends Duplex {
  constructor(noiseStream) {
    super()

    this.noiseStream = noiseStream

    this._ondrain = null
    this.reverse = new ReversePassThrough(this)
  }

  get publicKey() {
    return this.noiseStream.publicKey
  }

  get remotePublicKey() {
    return this.noiseStream.remotePublicKey
  }

  get handshakeHash() {
    return this.noiseStream.handshakeHash
  }

  flush() {
    return Writable.drained(this)
  }

  _read(cb) {
    const ondrain = this._ondrain
    this._ondrain = null
    if (ondrain) ondrain()
    cb(null)
  }

  _write(data, cb) {
    if (this.reverse.push(data) === false) {
      this.reverse._ondrain = cb
    } else {
      cb(null)
    }
  }

  _final(cb) {
    this.reverse.push(null)
    cb(null)
  }
}
const sodium = require('sodium-universal')
const curve = require('noise-curve-ed')
const Noise = require('noise-handshake')
const b4a = require('b4a')

const EMPTY = b4a.alloc(0)

module.exports = class Handshake {
  constructor(isInitiator, keyPair, remotePublicKey, pattern) {
    this.isInitiator = isInitiator
    this.keyPair = keyPair
    this.noise = new Noise(pattern, isInitiator, keyPair, { curve })
    this.noise.initialise(EMPTY, remotePublicKey)
    this.destroyed = false
  }

  static keyPair(seed) {
    const publicKey = b4a.alloc(32)
    const secretKey = b4a.alloc(64)
    if (seed) sodium.crypto_sign_seed_keypair(publicKey, secretKey, seed)
    else sodium.crypto_sign_keypair(publicKey, secretKey)
    return { publicKey, secretKey }
  }

  recv(data) {
    try {
      this.noise.recv(data)
      if (this.noise.complete) return this._return(null)
      return this.send()
    } catch {
      this.destroy()
      return null
    }
  }

  // note that the data returned here is framed so we don't have to do an extra copy
  // when sending it...
  send() {
    try {
      const data = this.noise.send()
      const wrap = b4a.allocUnsafe(data.byteLength + 3)

      writeUint24le(data.byteLength, wrap)
      wrap.set(data, 3)

      return this._return(wrap)
    } catch {
      this.destroy()
      return null
    }
  }

  destroy() {
    if (this.destroyed) return
    this.destroyed = true
  }

  _return(data) {
    const tx = this.noise.complete ? b4a.toBuffer(this.noise.tx) : null
    const rx = this.noise.complete ? b4a.toBuffer(this.noise.rx) : null
    const hash = this.noise.complete ? b4a.toBuffer(this.noise.hash) : null
    const remotePublicKey = this.noise.complete ? b4a.toBuffer(this.noise.rs) : null

    return {
      data,
      remotePublicKey,
      hash,
      tx,
      rx
    }
  }
}

function writeUint24le(n, buf) {
  buf[0] = n & 255
  buf[1] = (n >>> 8) & 255
  buf[2] = (n >>> 16) & 255
}
{
  "name": "@hyperswarm/secret-stream",
  "version": "6.9.1",
  "description": "Secret stream backed by Noise and libsodium's secretstream",
  "main": "index.js",
  "files": [
    "index.js",
    "lib/**.js"
  ],
  "dependencies": {
    "b4a": "^1.1.0",
    "hypercore-crypto": "^3.3.1",
    "noise-curve-ed": "^2.0.1",
    "noise-handshake": "^4.0.0",
    "sodium-secretstream": "^1.1.0",
    "sodium-universal": "^5.0.0",
    "streamx": "^2.14.0",
    "timeout-refresh": "^2.0.0",
    "unslab": "^1.3.0"
  },
  "devDependencies": {
    "brittle": "^3.3.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^2.0.0",
    "udx-native": "^1.13.2"
  },
  "scripts": {
    "format": "prettier --write .",
    "lint": "prettier --check .",
    "test": "brittle test.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/holepunchto/hyperswarm-secret-stream.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/hyperswarm-secret-stream/issues"
  },
  "homepage": "https://github.com/holepunchto/hyperswarm-secret-stream"
}
const Cache = require('xache')

module.exports = class AdaptiveTimeout {
  constructor(opts = {}) {
    this._cache = new Cache({
      maxSize: opts.maxSize || 65536,
      maxAge: opts.maxAge || 10 * 60 * 1000 // 10 minutes
    })
    this._fallback = opts.fallback || AdaptiveTimeout.TimeoutExponential
    this._min = opts.min ?? 300
    this._max = opts.max || 4000
    this._jitter = opts.jitter ?? 256
  }

  // Default - aggressive ramp
  static TimeoutAggressive = [500, 750, 1000, 1500, 2000]

  // Linear - steady increase
  static TimeoutLinear = [500, 1000, 1500, 2000, 2500]

  // Exponential - slow start, rapid backoff
  static TimeoutExponential = [250, 500, 1000, 2000, 4000]

  // Gentle - conservative, patient
  static TimeoutGentle = [1000, 1250, 1500, 1750, 2000]

  // Fast - rapid fire retries
  static TimeoutFast = [200, 400, 600, 800, 1000]

  // U-shape - long, short, long
  static TimeoutUShape = [1500, 750, 500, 750, 1500]

  // Inverse U - short, long, short
  static TimeoutInverseU = [500, 1000, 1500, 1000, 500]

  // Sawtooth - alternating fast/slow
  static TimeoutSawtooth = [500, 1500, 500, 1500, 500]

  // Plateau - quick ramp then steady
  static TimeoutPlateau = [500, 1000, 2000, 2000, 2000]

  // Logarithmic - diminishing increases
  static TimeoutLogarithmic = [500, 1000, 1300, 1500, 1600]

  getValue(key) {
    return this._cache.get(key)
  }

  put(key, value) {
    let p = this._cache.get(key)

    if (!p) {
      p = { avg: value, variance: value >> 1 }
    } else {
      // blend 75% old + 25% new deviation
      p.variance += (Math.abs(p.avg - value) - p.variance) >> 2
      // blend 87.5% old + 12.5% new sample
      p.avg += (value - p.avg) >> 3
    }

    this._cache.set(key, p)

    return p
  }

  get(key, attempt = 1) {
    const p = this._cache.get(key)
    const jitter = (Math.random() * this._jitter) | 0

    if (p) {
      // known - use adaptive with linear backoff
      const base = p.avg + (p.variance << 1)
      const backoff = base * attempt
      return Math.min(Math.max(backoff + jitter, this._min), this._max)
    } else {
      // unknown - use aggressive fallback
      const base = this._fallback[Math.min(attempt - 1, this._fallback.length - 1)]
      return base + jitter
    }
  }

  has(key) {
    return this._cache.has(key)
  }

  delete(key) {
    return this._cache.delete(key)
  }

  clear() {
    this._cache.clear()
  }
}
{
  "name": "adaptive-timeout",
  "version": "1.0.1",
  "description": "adaptive-timeout",
  "main": "index.js",
  "exports": {
    "./package": "./package.json",
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    }
  },
  "files": [
    "package.json",
    "index.js",
    "index.d.ts"
  ],
  "devDependencies": {
    "brittle": "^3.19.0",
    "lunte": "^1.2.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^2.0.0"
  },
  "scripts": {
    "format": "prettier . --write",
    "test": "prettier . --check && lunte && brittle-bare test/index.js",
    "lint": "lunte"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/holepunchto/adaptive-timeout.git"
  },
  "author": "Holepunch Inc",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/adaptive-timeout/issues"
  },
  "homepage": "https://github.com/holepunchto/adaptive-timeout",
  "dependencies": {
    "xache": "^1.2.1"
  }
}
/**
 * anproto — wire-compatible implementation of evbogue's ANProto
 *
 * https://anproto.com — the Authenticated Non-networked protocol
 *
 * Exports:
 *   an         — the canonical 4 functions: gen, hash, sign, open (+ helpers)
 *   Challenges — replay-protection helper for HTTP login flows (not part of spec)
 */

export { an } from './lib/an.js'
export { Challenges } from './lib/challenges.js'
/**
 * an.js — wire-compatible implementation of evbogue's ANProto
 *
 * Spec: https://anproto.com  (canonical at github.com/evbogue/ANProto)
 *
 * Same wire format as the canonical TweetNaCl-based implementation, but
 * uses sodium-native (via hypercore-crypto) so we don't carry a second
 * Ed25519 implementation. Both libraries implement the same Ed25519
 * standard and produce byte-identical signatures for the same inputs.
 *
 * The four canonical functions:
 *
 *   gen()              → 132-char keypair string (44 pubkey b64 + 88 secret b64)
 *   hash(data)         → 44-char base64 SHA-256
 *   sign(hash, kp)     → pubkey(44) + base64(combined-mode-sign(timestamp + hash))
 *   open(message)      → "{timestamp}{hash}" string (throws if invalid)
 */

import crypto from 'hypercore-crypto'
import sodium from 'sodium-native'
import b4a from 'b4a'

const SIG_BYTES = 64 // Ed25519 detached signature size (crypto_sign_BYTES)

export const an = {}

/**
 * Generate a new Ed25519 keypair.
 * @returns {Promise<string>} 132-char string: base64(pubkey 32B) + base64(secret 64B)
 */
an.gen = async () => {
  const { publicKey, secretKey } = crypto.keyPair()
  return b4a.toString(publicKey, 'base64') + b4a.toString(secretKey, 'base64')
}

/**
 * SHA-256 hash data.
 * @param {string|Buffer} data
 * @returns {Promise<string>} 44-char base64 SHA-256 hash
 */
an.hash = async (data) => {
  const buf = typeof data === 'string' ? b4a.from(data, 'utf8') : data
  const out = b4a.alloc(32)
  sodium.crypto_hash_sha256(out, buf)
  return b4a.toString(out, 'base64')
}

/**
 * Sign a hash with a keypair, producing a wire-format ANProto signature.
 * The signed message is "{Date.now()}{hash}" — timestamp prefixed.
 *
 * Wire format: pubkey(44 b64) + base64(combined_mode_sign(message))
 * where combined_mode_sign(m) = signature(64B) || m
 *
 * @param {string} hash - 44-char base64 hash from an.hash()
 * @param {string} keypair - 132-char keypair string from an.gen()
 * @returns {Promise<string>} wire-format signature
 */
an.sign = async (hash, keypair) => {
  const ts = Date.now()
  const message = b4a.from(String(ts) + hash, 'utf8')
  const secretKey = b4a.from(keypair.substring(44), 'base64')
  const sig = crypto.sign(message, secretKey) // detached, 64 bytes
  // Combined-mode envelope: signature || message
  const combined = b4a.concat([sig, message])
  return keypair.substring(0, 44) + b4a.toString(combined, 'base64')
}

/**
 * Verify and open an ANProto signature, returning the embedded message.
 * The returned string is "{timestamp}{hash}" — caller can split at offset 13.
 *
 * @param {string} message - wire-format signature from an.sign()
 * @returns {Promise<string>} the original signed message ("{timestamp}{hash}")
 * @throws {Error} if the signature is invalid
 */
an.open = async (message) => {
  if (typeof message !== 'string' || message.length < 44) {
    throw new Error('Invalid ANProto message (too short)')
  }
  const publicKey = b4a.from(message.substring(0, 44), 'base64')
  const combined = b4a.from(message.substring(44), 'base64')
  if (publicKey.length !== 32) throw new Error('Invalid ANProto pubkey length')
  if (combined.length < SIG_BYTES) throw new Error('Invalid ANProto combined-mode envelope')

  const sig = combined.subarray(0, SIG_BYTES)
  const body = combined.subarray(SIG_BYTES)

  if (!crypto.verify(body, sig, publicKey)) {
    throw new Error('Invalid ANProto signature')
  }
  return b4a.toString(body, 'utf8')
}

// ── Convenience helpers (not part of canonical spec, but commonly needed) ──

/**
 * Extract the public key from a keypair string.
 * @param {string} keypair - 132-char keypair string
 * @returns {string} 44-char base64 public key
 */
an.publicKey = (keypair) => {
  if (typeof keypair !== 'string' || keypair.length !== 132) {
    throw new Error('Invalid keypair (expected 132 chars)')
  }
  return keypair.substring(0, 44)
}

/**
 * Parse an opened message into its timestamp and hash components.
 * Canonical format: 13-digit timestamp followed by 44-char base64 hash.
 * @param {string} opened - the string returned by an.open()
 * @returns {{ timestamp: number, hash: string }}
 */
an.parse = (opened) => {
  if (typeof opened !== 'string' || opened.length < 13) {
    throw new Error('Invalid opened message')
  }
  const tsMatch = opened.match(/^(\d{13})(.+)$/)
  if (!tsMatch) throw new Error('Invalid opened message format')
  return {
    timestamp: parseInt(tsMatch[1], 10),
    hash: tsMatch[2]
  }
}
/**
 * Challenges — in-memory replay protection helper for ANProto login flows
 *
 * Not part of canonical ANProto. A small helper for HTTP login flows where
 * the server issues a challenge string, the client signs hash(challenge),
 * and the server consumes the challenge to prevent replay.
 *
 * Usage:
 *   const challenges = new Challenges({ ttl: 5 * 60 * 1000 })
 *   const challenge = challenges.generate()      // server issues
 *   // ... browser signs hash(challenge), POSTs sig ...
 *   if (!challenges.consume(challenge)) reject() // server consumes (single-use)
 */

import crypto from 'hypercore-crypto'
import b4a from 'b4a'

const DEFAULT_TTL = 5 * 60 * 1000 // 5 minutes
const DEFAULT_CLEANUP_INTERVAL = 60 * 1000 // 1 minute

export class Challenges {
  constructor (opts = {}) {
    this.ttl = opts.ttl || DEFAULT_TTL
    this._active = new Map() // challenge string → expiresAt
    this._cleanupTimer = setInterval(() => this._cleanup(), opts.cleanupInterval || DEFAULT_CLEANUP_INTERVAL)
    if (this._cleanupTimer.unref) this._cleanupTimer.unref()
  }

  /**
   * Issue a new challenge string. The caller sends this to the client; the
   * client signs hash(challenge) and returns the signature.
   * @returns {string} 44-char base64 random challenge
   */
  generate () {
    const challenge = b4a.toString(crypto.randomBytes(32), 'base64')
    this._active.set(challenge, Date.now() + this.ttl)
    return challenge
  }

  /**
   * Consume a challenge. Returns true if the challenge was active and not
   * yet consumed; false otherwise. Single-use — calling consume() removes
   * the challenge from the active set.
   * @param {string} challenge
   * @returns {boolean}
   */
  consume (challenge) {
    const expiresAt = this._active.get(challenge)
    if (expiresAt === undefined) return false
    if (expiresAt < Date.now()) {
      this._active.delete(challenge)
      return false
    }
    this._active.delete(challenge)
    return true
  }

  /**
   * Check if a challenge is active without consuming it.
   * @param {string} challenge
   * @returns {boolean}
   */
  has (challenge) {
    const expiresAt = this._active.get(challenge)
    if (expiresAt === undefined) return false
    return expiresAt >= Date.now()
  }

  /**
   * Number of active (unexpired) challenges.
   */
  get size () {
    return this._active.size
  }

  _cleanup () {
    const now = Date.now()
    for (const [challenge, expiresAt] of this._active) {
      if (expiresAt < now) this._active.delete(challenge)
    }
  }

  /**
   * Stop the cleanup timer. Call when shutting down.
   */
  destroy () {
    if (this._cleanupTimer) {
      clearInterval(this._cleanupTimer)
      this._cleanupTimer = null
    }
    this._active.clear()
  }
}
function isBuffer(value) {
  return Buffer.isBuffer(value) || value instanceof Uint8Array
}

function isEncoding(encoding) {
  return Buffer.isEncoding(encoding)
}

function alloc(size, fill, encoding) {
  return Buffer.alloc(size, fill, encoding)
}

function allocUnsafe(size) {
  return Buffer.allocUnsafe(size)
}

function allocUnsafeSlow(size) {
  return Buffer.allocUnsafeSlow(size)
}

function byteLength(string, encoding) {
  return Buffer.byteLength(string, encoding)
}

function compare(a, b) {
  return Buffer.compare(a, b)
}

function concat(buffers, totalLength) {
  return Buffer.concat(buffers, totalLength)
}

function copy(source, target, targetStart, start, end) {
  return toBuffer(source).copy(target, targetStart, start, end)
}

function equals(a, b) {
  return toBuffer(a).equals(b)
}

function fill(buffer, value, offset, end, encoding) {
  return toBuffer(buffer).fill(value, offset, end, encoding)
}

function from(value, encodingOrOffset, length) {
  return Buffer.from(value, encodingOrOffset, length)
}

function includes(buffer, value, byteOffset, encoding) {
  return toBuffer(buffer).includes(value, byteOffset, encoding)
}

function indexOf(buffer, value, byfeOffset, encoding) {
  return toBuffer(buffer).indexOf(value, byfeOffset, encoding)
}

function lastIndexOf(buffer, value, byteOffset, encoding) {
  return toBuffer(buffer).lastIndexOf(value, byteOffset, encoding)
}

function swap16(buffer) {
  return toBuffer(buffer).swap16()
}

function swap32(buffer) {
  return toBuffer(buffer).swap32()
}

function swap64(buffer) {
  return toBuffer(buffer).swap64()
}

function toBuffer(buffer) {
  if (Buffer.isBuffer(buffer)) return buffer
  return Buffer.from(buffer.buffer, buffer.byteOffset, buffer.byteLength)
}

function toString(buffer, encoding, start, end) {
  return toBuffer(buffer).toString(encoding, start, end)
}

function write(buffer, string, offset, length, encoding) {
  return toBuffer(buffer).write(string, offset, length, encoding)
}

function readDoubleBE(buffer, offset) {
  return toBuffer(buffer).readDoubleBE(offset)
}

function readDoubleLE(buffer, offset) {
  return toBuffer(buffer).readDoubleLE(offset)
}

function readFloatBE(buffer, offset) {
  return toBuffer(buffer).readFloatBE(offset)
}

function readFloatLE(buffer, offset) {
  return toBuffer(buffer).readFloatLE(offset)
}

function readInt32BE(buffer, offset) {
  return toBuffer(buffer).readInt32BE(offset)
}

function readInt32LE(buffer, offset) {
  return toBuffer(buffer).readInt32LE(offset)
}

function readUInt32BE(buffer, offset) {
  return toBuffer(buffer).readUInt32BE(offset)
}

function readUInt32LE(buffer, offset) {
  return toBuffer(buffer).readUInt32LE(offset)
}

function writeDoubleBE(buffer, value, offset) {
  return toBuffer(buffer).writeDoubleBE(value, offset)
}

function writeDoubleLE(buffer, value, offset) {
  return toBuffer(buffer).writeDoubleLE(value, offset)
}

function writeFloatBE(buffer, value, offset) {
  return toBuffer(buffer).writeFloatBE(value, offset)
}

function writeFloatLE(buffer, value, offset) {
  return toBuffer(buffer).writeFloatLE(value, offset)
}

function writeInt32BE(buffer, value, offset) {
  return toBuffer(buffer).writeInt32BE(value, offset)
}

function writeInt32LE(buffer, value, offset) {
  return toBuffer(buffer).writeInt32LE(value, offset)
}

function writeUInt32BE(buffer, value, offset) {
  return toBuffer(buffer).writeUInt32BE(value, offset)
}

function writeUInt32LE(buffer, value, offset) {
  return toBuffer(buffer).writeUInt32LE(value, offset)
}

module.exports = {
  isBuffer,
  isEncoding,
  alloc,
  allocUnsafe,
  allocUnsafeSlow,
  byteLength,
  compare,
  concat,
  copy,
  equals,
  fill,
  from,
  includes,
  indexOf,
  lastIndexOf,
  swap16,
  swap32,
  swap64,
  toBuffer,
  toString,
  write,
  readDoubleBE,
  readDoubleLE,
  readFloatBE,
  readFloatLE,
  readInt32BE,
  readInt32LE,
  readUInt32BE,
  readUInt32LE,
  writeDoubleBE,
  writeDoubleLE,
  writeFloatBE,
  writeFloatLE,
  writeInt32BE,
  writeInt32LE,
  writeUInt32BE,
  writeUInt32LE
}
{
  "name": "b4a",
  "version": "1.8.0",
  "description": "Bridging the gap between buffers and typed arrays",
  "exports": {
    "./package": "./package.json",
    ".": {
      "react-native": "./react-native.js",
      "browser": "./browser.js",
      "default": "./index.js"
    }
  },
  "files": [
    "browser.js",
    "index.js",
    "react-native.js",
    "lib"
  ],
  "scripts": {
    "test": "npm run lint && npm run test:bare && npm run test:node",
    "test:bare": "bare test.mjs",
    "test:node": "node test.mjs",
    "lint": "prettier . --check"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/b4a.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/b4a/issues"
  },
  "homepage": "https://github.com/holepunchto/b4a#readme",
  "devDependencies": {
    "brittle": "^3.5.2",
    "nanobench": "^3.0.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^1.0.0"
  },
  "peerDependencies": {
    "react-native-b4a": "*"
  },
  "peerDependenciesMeta": {
    "react-native-b4a": {
      "optional": true
    }
  }
}
const ESC = '\x1b'
const CSI = ESC + '['
const SGR = (n) => CSI + n + 'm'

exports.constants = {
  ESC,
  CSI,
  SGR
}

// https://en.wikipedia.org/wiki/ANSI_escape_code#CSI_(Control_Sequence_Introducer)_sequences

exports.cursorHide = CSI + '?25l'
exports.cursorShow = CSI + '?25h'

exports.cursorUp = function cursorUp(n = 1) {
  return CSI + n + 'A'
}

exports.cursorDown = function cursorDown(n = 1) {
  return CSI + n + 'B'
}

exports.cursorForward = function cursorForward(n = 1) {
  return CSI + n + 'C'
}

exports.cursorBack = function cursorBack(n = 1) {
  return CSI + n + 'D'
}

exports.cursorNextLine = function cursorNextLine(n = 1) {
  return CSI + n + 'E'
}

exports.cursorPreviousLine = function cursorPreviousLine(n = 1) {
  return CSI + n + 'F'
}

exports.cursorPosition = function cursorPosition(column, row = 0) {
  if (row === 0) return CSI + (column + 1) + 'G'

  return CSI + (row + 1) + ';' + (column + 1) + 'H'
}

exports.eraseDisplayEnd = CSI + 'J'
exports.eraseDisplayStart = CSI + '1J'
exports.eraseDisplay = CSI + '2J'
exports.eraseLineEnd = CSI + 'K'
exports.eraseLineStart = CSI + '1K'
exports.eraseLine = CSI + '2K'

exports.scrollUp = function scrollUp(n = 1) {
  return CSI + n + 'S'
}

exports.scrollDown = function scrollDown(n = 1) {
  return CSI + n + 'T'
}

// https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_(Select_Graphic_Rendition)_parameters

exports.modifierReset = SGR(0)
exports.modifierBold = SGR(1)
exports.modifierDim = SGR(2)
exports.modifierItalic = SGR(3)
exports.modifierUnderline = SGR(4)
exports.modifierNormal = SGR(22)
exports.modifierNotItalic = SGR(23)
exports.modifierNotUnderline = SGR(24)

// https://en.wikipedia.org/wiki/ANSI_escape_code#Colors

exports.colorBlack = SGR(30)
exports.colorRed = SGR(31)
exports.colorGreen = SGR(32)
exports.colorYellow = SGR(33)
exports.colorBlue = SGR(34)
exports.colorMagenta = SGR(35)
exports.colorCyan = SGR(36)
exports.colorWhite = SGR(37)
exports.colorDefault = SGR(39)
exports.colorBrightBlack = SGR(90)
exports.colorBrightRed = SGR(91)
exports.colorBrightGreen = SGR(92)
exports.colorBrightYellow = SGR(93)
exports.colorBrightBlue = SGR(94)
exports.colorBrightMagenta = SGR(95)
exports.colorBrightCyan = SGR(96)
exports.colorBrightWhite = SGR(97)
{
  "name": "bare-ansi-escapes",
  "version": "2.2.3",
  "description": "Parse and produce ANSI escape sequences",
  "exports": {
    "./package": "./package.json",
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    },
    "./key-decoder": {
      "types": "./key-decoder.d.ts",
      "default": "./key-decoder.js"
    }
  },
  "files": [
    "index.js",
    "index.d.ts",
    "key-decoder.js",
    "key-decoder.d.ts"
  ],
  "scripts": {
    "test": "prettier . --check && bare test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-ansi-escapes.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-ansi-escapes/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-ansi-escapes#readme",
  "dependencies": {
    "bare-stream": "^2.6.5"
  },
  "devDependencies": {
    "bare-buffer": "^3.0.2",
    "brittle": "^3.3.2",
    "prettier": "^3.4.2",
    "prettier-config-standard": "^7.0.0"
  },
  "peerDependencies": {
    "bare-buffer": "*"
  },
  "peerDependenciesMeta": {
    "bare-buffer": {
      "optional": true
    }
  }
}
const inspect = require('bare-inspect')

class AssertionError extends Error {
  constructor(opts = {}) {
    let { message = null, actual, expected, operator } = opts

    if (message === null) {
      message = `${inspect(actual)} ${operator} ${inspect(expected)}`
    }

    super(message)

    this.actual = actual
    this.expected = expected
    this.operator = operator
  }

  get name() {
    return 'AssertionError'
  }

  get code() {
    'ASSERTION'
  }
}

function assertFail(opts, fn) {
  if (opts.message instanceof Error) throw opts.message

  const err = new AssertionError(opts)

  if (Error.captureStackTrace) Error.captureStackTrace(err, fn)

  throw err
}

module.exports = exports = function assert(actual, message) {
  if (actual) return

  assertFail({ message, actual, expected: true, operator: '==' }, assert)
}

exports.AssertionError = AssertionError

exports.fail = function fail(message) {
  if (message === undefined) message = 'Failed'

  assertFail({ message, operator: 'fail' }, fail)
}

exports.ok = function ok(actual, message) {
  if (actual) return

  assertFail({ message, actual, expected: true, operator: '==' }, ok)
}

exports.notOk = function ok(actual, message) {
  if (!actual) return

  assertFail({ message, actual, expected: false, operator: '==' }, ok)
}

exports.equal = function equal(actual, expected, message) {
  if (actual == expected || (actual !== actual && expected !== expected)) {
    return
  }

  assertFail({ message, actual, expected, operator: '==' }, equal)
}

exports.notEqual = function notEqual(actual, expected, message) {
  if (actual != expected && (actual === actual || expected === expected)) {
    return
  }

  assertFail({ message, actual, expected, operator: '!=' }, notEqual)
}

exports.strictEqual = function strictEqual(actual, expected, message) {
  if (Object.is(actual, expected)) return

  assertFail(
    { message, actual, expected, operator: 'strictEqual' },
    strictEqual
  )
}

exports.notStrictEqual = function notStrictEqual(actual, expected, message) {
  if (!Object.is(actual, expected)) return

  assertFail(
    { message, actual, expected, operator: 'notStrictEqual' },
    notStrictEqual
  )
}
{
  "name": "bare-assert",
  "version": "1.2.0",
  "description": "Assertion library for JavaScript",
  "exports": {
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    },
    "./package": "./package.json"
  },
  "files": [
    "index.js",
    "index.d.ts"
  ],
  "scripts": {
    "test": "prettier . --check && bare test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-assert.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-assert/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-assert#readme",
  "dependencies": {
    "bare-inspect": "^3.1.2"
  },
  "devDependencies": {
    "brittle": "^3.1.1",
    "prettier": "^3.4.2",
    "prettier-config-standard": "^7.0.0"
  }
}
module.exports = require.addon()
const ansiEscapes = require('bare-ansi-escapes')
const getType = require('bare-type')
const binding = require('./binding')

const PLAIN_KEY = /^[a-zA-Z_][a-zA-Z_0-9]*$/

const defaultDepth = 2
const defaultBreakLength = 80
const defaultMaxArrayLength = 40

module.exports = exports = function inspect(value, opts = {}) {
  const {
    colors = false,
    depth = defaultDepth,
    breakLength = defaultBreakLength,
    stylize = defaultStylize(colors)
  } = opts

  const references = new InspectRefMap()

  const tree = inspectValue(value, 0, {
    colors,
    depth,
    breakLength,
    stylize,
    references
  })

  return tree.toString()
}

exports.styles = {
  bigint: ansiEscapes.colorYellow,
  boolean: ansiEscapes.colorYellow,
  date: ansiEscapes.colorMagenta,
  module: ansiEscapes.modifierUnderline,
  name: ansiEscapes.modifierReset,
  null: ansiEscapes.modifierBold,
  number: ansiEscapes.colorYellow,
  regexp: ansiEscapes.colorRed,
  special: ansiEscapes.colorCyan,
  string: ansiEscapes.colorGreen,
  symbol: ansiEscapes.colorGreen,
  undefined: ansiEscapes.colorBrightBlack
}

const styles = exports.styles

function defaultStylize(colors) {
  return function stylize(value, style) {
    const color = colors && styles[style]

    if (color) return color + value + ansiEscapes.modifierReset

    return value
  }
}

class InspectRefMap {
  constructor() {
    this.refs = new WeakMap()
    this.ids = new WeakMap()
    this.nextId = 1
  }

  has(object) {
    return this.refs.has(object)
  }

  get(object) {
    return this.refs.get(object) || null
  }

  set(object, ref) {
    this.refs.set(object, ref)
  }

  id(object) {
    let id = this.ids.get(object)
    if (id) return id

    id = this.nextId++
    this.ids.set(object, id)

    return id
  }
}

class InspectNode {
  constructor(depth, length, opts) {
    const { breakLength = defaultBreakLength, breakAlways = false } = opts

    this.depth = depth
    this.length = length
    this.breakLength = breakLength
    this.breakAlways = breakAlways
  }

  pad(n, string) {
    return string.padStart(n, ' ')
  }

  indent(n, string) {
    return '  '.repeat(n) + string
  }
}

class InspectRef extends InspectNode {
  constructor(depth, opts) {
    super(depth, '[circular *]'.length, opts)

    this.refs = opts.references
    this.count = 0
    this.circular = false
    this.color = opts.colors && styles.special
  }

  get id() {
    return this.refs.id(this)
  }

  increment() {
    return ++this.count
  }

  decrement() {
    return --this.count
  }

  toString(opts = {}) {
    const { offset = 0, pad = 0, indent = 0 } = opts

    let value = this.pad(pad, '[circular *' + this.id + ']')

    if (this.color) value = this.color + value + ansiEscapes.modifierReset

    return offset ? value : this.indent(indent, value)
  }
}

class InspectLeaf extends InspectNode {
  constructor(value, color, depth, opts) {
    const length = value.length

    if (value.includes('\n')) {
      value = value.replaceAll('\n', '\n' + '  '.repeat(depth))

      opts = { ...opts, breakAlways: true }
    }

    super(depth, length, opts)

    this.value = value
    this.color = opts.colors && color
  }

  toString(opts = {}) {
    const { offset = 0, pad = 0, indent = 0 } = opts

    let value = this.pad(pad, this.value)

    if (this.color) value = this.color + value + ansiEscapes.modifierReset

    return offset ? value : this.indent(indent, value)
  }
}

class InspectPair extends InspectNode {
  constructor(delim, left, right, depth, opts) {
    const length = left.length + delim.length + right.length

    if (left.breakAlways || right.breakAlways) {
      opts = { ...opts, breakAlways: true }
    }

    super(depth, length, opts)

    this.delim = delim
    this.left = left
    this.right = right
  }

  toString(opts = {}) {
    const { indent = 0 } = opts

    return this.indent(
      indent,
      this.left +
        this.delim +
        this.right.toString({
          indent,
          offset: this.left.length + this.delim.length
        })
    )
  }
}

class InspectSuspension extends InspectNode {
  constructor(overflow, depth, opts) {
    const label = `... ${overflow} more`

    super(depth, label.length, opts)

    this.label = label
  }

  toString(opts = {}) {
    const { indent = 0 } = opts

    return this.indent(indent, this.label)
  }
}

class InspectSequence extends InspectNode {
  constructor(header, footer, delim, values, ref, depth, opts) {
    const { tabulate = false } = opts

    const length =
      (ref.circular ? '<ref *>'.length + 1 : 0) +
      header.length +
      values.reduce(
        (length, value, i) =>
          length + value.length + (i === 0 ? 0 : delim.length),
        0
      ) +
      footer.length

    if (values.some((value) => value.breakAlways)) {
      opts = { ...opts, breakAlways: true }
    }

    super(depth, length, opts)

    this.header = header
    this.footer = footer
    this.delim = delim
    this.values = values
    this.ref = ref
    this.tabulate = tabulate
  }

  toString(opts = {}) {
    const { offset = 0, indent = 0 } = opts

    const split =
      this.breakAlways ||
      (this.values.length &&
        (offset + this.length > this.breakLength ||
          indent * 2 + this.length > this.breakLength))

    let header = this.header

    if (this.ref.circular) {
      header = '<ref *' + this.ref.id + '> ' + header
    }

    if (this.values.length === 0) {
      header = header.trimEnd()
    }

    if (offset === 0) {
      header = this.indent(indent, header)
    }

    if (split) {
      header = header.trimEnd() + '\n'
    }

    let string = header

    let columns = 1
    let pad = 0

    if (this.tabulate) {
      const widest = this.values.reduce(
        (length, value) =>
          value.breakAlways ? length : Math.max(length, value.length),
        0
      )

      if (widest) {
        columns = Math.max(
          columns,
          Math.floor(
            (this.breakLength - indent * 2) / (widest + this.delim.length)
          )
        )

        if (columns > 1) pad = widest
      }
    }

    for (let i = 0, n = this.values.length, offset = 0; i < n; i++) {
      const value = this.values[i]

      if (split) {
        let part

        if (i % columns === 0 || value.breakAlways) {
          part = value.toString({ indent: indent + 1, pad })
        } else {
          part = value.toString({ pad })
        }

        string += part

        if (i < n - 1) {
          if (i % columns === columns - 1 || this.values[i + 1].breakAlways) {
            string += this.delim.trimEnd() + '\n'
          } else {
            string += this.delim
          }
        }
      } else {
        if (i > 0) string += this.delim

        string += value.toString({ offset })

        offset += value.length
      }
    }

    let footer = this.footer

    if (this.values.length === 0) {
      footer = footer.trimStart()
    }

    if (split) {
      string += '\n' + this.indent(indent, footer.trimStart())
    } else {
      string += footer
    }

    return string
  }
}

function inspectValue(value, depth, opts) {
  const type = getType(value)

  if (type.isUndefined()) return inspectUndefined(depth, opts)
  if (type.isNull()) return inspectNull(depth, opts)
  if (type.isBoolean()) return inspectBoolean(value, depth, opts)
  if (type.isNumber()) return inspectNumber(value, depth, opts)
  if (type.isBigInt()) return inspectBigInt(value, depth, opts)
  if (type.isString()) return inspectString(value, depth, opts)
  if (type.isSymbol()) return inspectSymbol(value, depth, opts)
  if (type.isObject()) return inspectObject(type, value, depth, opts)
  if (type.isFunction()) return inspectFunction(type, value, depth, opts)
  if (type.isExternal()) return inspectExternal(value, opts, opts)
}

function inspectUndefined(depth, opts) {
  return new InspectLeaf('undefined', styles.undefined, depth, opts)
}

function inspectNull(depth, opts) {
  return new InspectLeaf('null', styles.null, depth, opts)
}

function inspectBoolean(value, depth, opts) {
  return new InspectLeaf(value.toString(), styles.boolean, depth, opts)
}

function inspectNumber(value, depth, opts) {
  let string

  if (Object.is(value, -0)) {
    string = '-0'
  } else {
    string = value.toString(10)
  }

  return new InspectLeaf(string, styles.number, depth, opts)
}

function inspectBigInt(value, depth, opts) {
  return new InspectLeaf(value.toString(10) + 'n', styles.bigint, depth, opts)
}

const STRING_ESCAPES =
  /[\ud800-\udbff][\udc00-\udfff]|[\u0000-\u001f'\\\ud800-\udfff]/g

function inspectString(value, depth, opts) {
  // https://tc39.es/ecma262/multipage/structured-data.html#sec-quotejsonstring
  const string = value.replace(STRING_ESCAPES, (match) => {
    if (match.length === 2) return match

    switch (match) {
      case "'":
        return "\\'"
      case '\\':
        return '\\\\'
      case '\b':
        return '\\b'
      case '\t':
        return '\\t'
      case '\n':
        return '\\n'
      case '\f':
        return '\\f'
      case '\r':
        return '\\r'
      default:
        return '\\u' + match.charCodeAt(0).toString(16).padStart(4, '0')
    }
  })

  return new InspectLeaf("'" + string + "'", styles.string, depth, opts)
}

function inspectSymbol(value, depth, opts) {
  return new InspectLeaf(value.toString(), styles.symbol, depth, opts)
}

function inspectKey(value, depth, opts) {
  if (PLAIN_KEY.test(value)) {
    return new InspectLeaf(value, null, depth, opts)
  } else {
    return inspectValue(value, depth, opts)
  }
}

function inspectObject(type, object, depth, opts) {
  const refs = opts.references

  let ref = refs.get(object)
  if (ref === null) {
    ref = new InspectRef(depth, opts)
    refs.set(object, ref)
  } else if (ref.count) {
    ref.circular = true
    return ref
  }

  const maxDepth = typeof opts.depth === 'number' ? opts.depth : Infinity

  if (maxDepth < depth) {
    const constructor = object.constructor

    return new InspectLeaf(
      '[' +
        (constructor && constructor.name ? constructor.name : 'Object') +
        ']',
      styles.special,
      depth,
      opts
    )
  }

  const inspect =
    object[Symbol.for('bare.inspect')] ||
    object[Symbol.for('nodejs.util.inspect.custom')]

  if (typeof inspect === 'function') {
    const value = inspect.call(
      object,
      typeof opts.depth === 'number' ? opts.depth - depth : null,
      {
        colors: opts.colors,
        breakLength: opts.breakLength,
        stylize: opts.stylize
      },
      exports
    )

    if (typeof value === 'object' && value !== null) {
      refs.set(value, ref)
    }

    if (typeof value !== 'string') {
      return inspectValue(value, depth, opts)
    }

    return new InspectLeaf(value, null, depth, opts)
  }

  if (type.isArray()) return inspectArray(object, ref, depth, opts)
  if (type.isDate()) return inspectDate(object, ref, depth, opts)
  if (type.isRegExp()) return inspectRegExp(object, ref, depth, opts)
  if (type.isError()) return inspectError(object, ref, depth, opts)
  if (type.isPromise()) return inspectPromise(object, ref, depth, opts)
  if (type.isMap()) return inspectMap(object, ref, depth, opts)
  if (type.isSet()) return inspectSet(object, ref, depth, opts)
  if (type.isWeakMap()) return inspectWeakMap(object, ref, depth, opts)
  if (type.isWeakSet()) return inspectWeakSet(object, ref, depth, opts)
  if (type.isWeakRef()) return inspectWeakRef(object, ref, depth, opts)
  if (type.isArrayBuffer()) return inspectArrayBuffer(object, ref, depth, opts)
  if (type.isSharedArrayBuffer())
    return inspectSharedArrayBuffer(object, ref, depth, opts)
  if (type.isTypedArray()) return inspectTypedArray(object, ref, depth, opts)
  if (type.isDataView()) return inspectDataView(object, ref, depth, opts)

  ref.increment()

  const values = []

  for (const key in object) {
    if (key === 'constructor') continue

    values.push(
      new InspectPair(
        ': ',
        inspectKey(key, depth + 1, opts),
        inspectValue(object[key], depth + 1, opts),
        depth + 1,
        opts
      )
    )
  }

  ref.decrement()

  let header = '{ '

  const tag = object[Symbol.toStringTag]

  if (tag) header = '[' + tag + '] ' + header

  if (object.constructor) {
    const name = object.constructor.name

    if (name && name !== 'Object') {
      header = object.constructor.name + ' ' + header
    }
  }

  return new InspectSequence(header, ' }', ', ', values, ref, depth, opts)
}

function inspectArray(array, ref, depth, opts) {
  const { maxArrayLength = defaultMaxArrayLength } = opts

  ref.increment()

  const values = []

  let remaining = Math.max(maxArrayLength, 0)

  for (let i = 0, n = array.length; i < n; i++) {
    if (remaining-- === 0) {
      values.push(
        new InspectSuspension(array.length - values.length, depth + 1, {
          ...opts,
          breakAlways: true
        })
      )

      break
    }

    values.push(inspectValue(array[i], depth + 1, opts))
  }

  for (const key of binding.getOwnNonIndexPropertyNames(array)) {
    values.push(
      new InspectPair(
        ': ',
        inspectKey(key, depth + 1, opts),
        inspectValue(array[key], depth + 1, opts),
        depth + 1,
        { ...opts, breakAlways: remaining < 0 }
      )
    )
  }

  ref.decrement()

  let header = '[ '

  if (array.constructor.name !== 'Array') {
    header = array.constructor.name + '(' + array.length + ') ' + header
  }

  return new InspectSequence(header, ' ]', ', ', values, ref, depth, {
    ...opts,
    tabulate: true
  })
}

function inspectDate(date, ref, depth, opts) {
  return new InspectLeaf(date.toISOString(), styles.date, depth, opts)
}

function inspectRegExp(regExp, ref, depth, opts) {
  return new InspectLeaf(regExp.toString(), styles.regexp, depth, opts)
}

function inspectError(error, ref, depth, opts) {
  let header

  if ('stack' in error) {
    header = error.stack

    if (depth > 0) {
      header = header.replaceAll('\n', '\n' + '  '.repeat(depth))
    }
  } else {
    header = error.toString()
  }

  const builtins = ['cause']

  if (error.name === 'AggregateError') {
    builtins.push('errors')
  } else if (error.name === 'SuppressedError') {
    builtins.push('error', 'suppressed')
  }

  const values = []

  for (const key of builtins) {
    if (key in error === false) continue

    values.push(
      new InspectPair(
        ': ',
        new InspectLeaf('[' + key + ']', null, depth + 1, opts),
        inspectValue(error[key], depth + 1, opts),
        depth + 1,
        opts
      )
    )
  }

  for (const key in error) {
    if (key === 'constructor' || builtins.includes(key)) continue

    values.push(
      new InspectPair(
        ': ',
        inspectKey(key, depth + 1, opts),
        inspectValue(error[key], depth + 1, opts),
        depth + 1,
        opts
      )
    )
  }

  if (values.length === 0) return new InspectLeaf(header, null, depth, opts)

  return new InspectSequence(
    header + ' {',
    ' }',
    ', ',
    values,
    ref,
    depth,
    opts
  )
}

function inspectPromise(promise, ref, depth, opts) {
  ref.increment()

  const state = binding.getPromiseState(promise)

  const values = []

  switch (state) {
    case 0: // Pending
      values.push(new InspectLeaf('<pending>', styles.special, depth, opts))
      break

    case 1: // Fulfilled
      values.push(inspectValue(binding.getPromiseResult(promise), depth, opts))
      break

    case 2: // Rejected
      values.push(
        new InspectLeaf('<rejected>', styles.special, depth, opts),
        inspectValue(binding.getPromiseResult(promise), depth, opts)
      )
  }

  ref.decrement()

  const header = promise.constructor.name + ' { '

  return new InspectSequence(header, ' }', ' ', values, ref, depth, opts)
}

function inspectMap(map, ref, depth, opts) {
  const {
    maxArrayLength = defaultMaxArrayLength,
    maxMapLength = maxArrayLength
  } = opts

  ref.increment()

  const values = []

  let remaining = maxMapLength

  for (const entry of map) {
    if (remaining-- === 0) {
      values.push(
        new InspectSuspension(map.size - values.length, depth + 1, {
          ...opts,
          breakAlways: true
        })
      )
      break
    }

    values.push(
      new InspectPair(
        ' => ',
        inspectValue(entry[0], depth + 1, opts),
        inspectValue(entry[1], depth + 1, opts),
        depth + 1,
        opts
      )
    )
  }

  for (const key in map) {
    if (key === 'constructor') continue

    const value = inspectValue(map[key], depth + 1, opts)

    values.push(
      new InspectPair(
        ': ',
        inspectKey(key, depth + 1, opts),
        value,
        depth + 1,
        { ...opts, breakAlways: remaining < 0 }
      )
    )
  }

  ref.decrement()

  const header = map.constructor.name + '(' + map.size + ') { '

  return new InspectSequence(header, ' }', ', ', values, ref, depth, {
    ...opts,
    tabulate: true
  })
}

function inspectSet(set, ref, depth, opts) {
  const {
    maxArrayLength = defaultMaxArrayLength,
    maxSetLength = maxArrayLength
  } = opts

  ref.increment()

  const values = []

  let remaining = maxSetLength

  for (const entry of set) {
    if (remaining-- === 0) {
      values.push(
        new InspectSuspension(set.size - values.length, depth + 1, {
          ...opts,
          breakAlways: true
        })
      )
      break
    }

    values.push(inspectValue(entry, depth + 1, opts))
  }

  for (const key in set) {
    if (key === 'constructor') continue

    const value = inspectValue(set[key], depth + 1, opts)

    values.push(
      new InspectPair(
        ': ',
        inspectKey(key, depth + 1, opts),
        value,
        depth + 1,
        { ...opts, breakAlways: remaining < 0 }
      )
    )
  }

  ref.decrement()

  const header = set.constructor.name + '(' + set.size + ') { '

  return new InspectSequence(header, ' }', ', ', values, ref, depth, {
    ...opts,
    tabulate: true
  })
}

function inspectWeakMap(weakMap, ref, depth, opts) {
  const header = weakMap.constructor.name + ' { '

  return new InspectSequence(
    header,
    ' }',
    ' ',
    [new InspectLeaf('<items unknown>', styles.special, depth + 1, opts)],
    ref,
    depth,
    opts
  )
}

function inspectWeakSet(weakSet, ref, depth, opts) {
  const header = weakSet.constructor.name + ' { '

  return new InspectSequence(
    header,
    ' }',
    ' ',
    [new InspectLeaf('<items unknown>', styles.special, depth + 1, opts)],
    ref,
    depth,
    opts
  )
}

function inspectWeakRef(weakRef, ref, depth, opts) {
  const target = weakRef.deref()

  let value

  if (target === undefined) {
    value = new InspectLeaf('<cleared>', styles.special, depth + 1, opts)
  } else {
    value = inspectValue(target, depth + 1, opts)
  }

  const header = weakRef.constructor.name + ' { '

  return new InspectSequence(header, ' }', ' ', [value], ref, depth, opts)
}

function inspectArrayBuffer(arrayBuffer, ref, depth, opts) {
  ref.increment()

  const values = []

  for (const key of ['byteLength']) {
    values.push(
      new InspectPair(
        ': ',
        inspectKey(key, depth + 1, opts),
        inspectValue(arrayBuffer[key], depth + 1, opts),
        depth + 1,
        opts
      )
    )
  }

  ref.decrement()

  const header = arrayBuffer.constructor.name + ' { '

  return new InspectSequence(header, ' }', ', ', values, ref, depth, opts)
}

function inspectSharedArrayBuffer(sharedArrayBuffer, ref, depth, opts) {
  ref.increment()

  const values = []

  for (const key of ['byteLength']) {
    values.push(
      new InspectPair(
        ': ',
        inspectKey(key, depth + 1, opts),
        inspectValue(sharedArrayBuffer[key], depth + 1, opts),
        depth + 1,
        opts
      )
    )
  }

  ref.decrement()

  const header = sharedArrayBuffer.constructor.name + ' { '

  return new InspectSequence(header, ' }', ', ', values, ref, depth, opts)
}

function inspectTypedArray(typedArray, ref, depth, opts) {
  if (Buffer.isBuffer(typedArray)) {
    return inspectBuffer(typedArray, ref, depth, opts)
  }

  const {
    maxArrayLength = defaultMaxArrayLength,
    maxTypedArrayLength = maxArrayLength
  } = opts

  ref.increment()

  const values = []

  let remaining = Math.max(maxTypedArrayLength, 0)

  for (let i = 0, n = typedArray.length; i < n; i++) {
    if (remaining-- === 0) {
      values.push(
        new InspectSuspension(typedArray.length - values.length, depth + 1, {
          ...opts,
          breakAlways: true
        })
      )

      break
    }

    values.push(inspectValue(typedArray[i], depth + 1, opts))
  }

  for (const key of binding.getOwnNonIndexPropertyNames(typedArray)) {
    values.push(
      new InspectPair(
        ': ',
        inspectKey(key, depth + 1, opts),
        inspectValue(typedArray[key], depth + 1, opts),
        depth + 1,
        { ...opts, breakAlways: remaining < 0 }
      )
    )
  }

  ref.decrement()

  const header = typedArray.constructor.name + '(' + typedArray.length + ') [ '

  return new InspectSequence(header, ' ]', ', ', values, ref, depth, {
    ...opts,
    tabulate: true
  })
}

function inspectBuffer(buffer, ref, depth, opts) {
  const {
    maxArrayLength = defaultMaxArrayLength,
    maxBufferLength = maxArrayLength
  } = opts

  ref.increment()

  const values = []

  let remaining = Math.max(maxBufferLength, 0)

  for (let i = 0, n = buffer.byteLength; i < n; i++) {
    if (remaining-- === 0) {
      values.push(
        new InspectSuspension(buffer.length - values.length, depth + 1, {
          ...opts,
          breakAlways: true
        })
      )

      break
    }

    values.push(
      new InspectLeaf(
        buffer[i].toString(16).padStart(2, '0'),
        null,
        depth + 1,
        opts
      )
    )
  }

  for (const key of binding.getOwnNonIndexPropertyNames(buffer)) {
    values.push(
      new InspectPair(
        ': ',
        inspectKey(key, depth + 1, opts),
        inspectValue(buffer[key], depth + 1, opts),
        depth + 1,
        { ...opts, breakAlways: remaining < 0 }
      )
    )
  }

  ref.decrement()

  return new InspectSequence('<Buffer ', '>', ' ', values, ref, depth, {
    ...opts,
    tabulate: true
  })
}

function inspectDataView(dataView, ref, depth, opts) {
  ref.increment()

  const values = []

  for (const key of ['byteLength', 'byteOffset', 'buffer']) {
    values.push(
      new InspectPair(
        ': ',
        inspectKey(key, depth + 1, opts),
        inspectValue(dataView[key], depth + 1, opts),
        depth + 1,
        opts
      )
    )
  }

  for (const key of binding.getOwnNonIndexPropertyNames(dataView)) {
    values.push(
      new InspectPair(
        ': ',
        inspectKey(key, depth + 1, opts),
        inspectValue(dataView[key], depth + 1, opts),
        depth + 1,
        opts
      )
    )
  }

  ref.decrement()

  const header = dataView.constructor.name + ' { '

  return new InspectSequence(header, ' }', ', ', values, ref, depth, opts)
}

function inspectFunction(type, fn, depth, opts) {
  if (fn.toString().startsWith('class')) return inspectClass(fn, depth, opts)

  let tag = 'function'

  if (type.isGeneratorFunction()) tag = 'generator ' + tag
  if (type.isAsyncFunction()) tag = 'async ' + tag

  return new InspectLeaf(
    '[' + tag + ' ' + (fn.name ? fn.name : '(anonymous)') + ']',
    styles.special,
    depth,
    opts
  )
}

function inspectClass(ctor, depth, opts) {
  return new InspectLeaf(
    '[class ' + (ctor.name ? ctor.name : '(anonymous)') + ']',
    styles.special,
    depth,
    opts
  )
}

function inspectExternal(external, depth, opts) {
  return new InspectLeaf(
    '[external 0x' + binding.getExternal(external).toString(16) + ']',
    styles.special,
    depth,
    opts
  )
}
{
  "name": "bare-inspect",
  "version": "3.1.4",
  "description": "Inspect objects as strings for debugging",
  "exports": {
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    },
    "./package": "./package.json"
  },
  "files": [
    "index.js",
    "index.d.ts",
    "binding.c",
    "binding.js",
    "CMakeLists.txt",
    "prebuilds"
  ],
  "addon": true,
  "scripts": {
    "test": "prettier . --check && bare test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-inspect.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-inspect/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-inspect#readme",
  "engines": {
    "bare": ">=1.18.0"
  },
  "dependencies": {
    "bare-ansi-escapes": "^2.1.0",
    "bare-type": "^1.0.0"
  },
  "devDependencies": {
    "brittle": "^3.3.2",
    "cmake-bare": "^1.1.6",
    "prettier": "^3.4.1",
    "prettier-config-standard": "^7.0.0"
  }
}
module.exports = require.addon()
const binding = require('./binding')

const t = binding.constants

class Type {
  constructor(type) {
    this._type = type
  }

  isUndefined() {
    return this._type === t.UNDEFINED
  }

  isNull() {
    return this._type === t.NULL
  }

  isBoolean() {
    return this._type === t.BOOLEAN
  }

  isNumber() {
    return (this._type & 0xff) === t.NUMBER
  }

  isInt32() {
    return (this._type & (0xff | t.INT32)) === (t.NUMBER | t.INT32)
  }

  isUint32() {
    return (this._type & (0xff | t.UINT32)) === (t.NUMBER | t.UINT32)
  }

  isString() {
    return this._type === t.STRING
  }

  isSymbol() {
    return this._type === t.SYMBOL
  }

  isObject() {
    return (this._type & 0xff) === t.OBJECT
  }

  isArray() {
    return this._type === (t.OBJECT | t.ARRAY)
  }

  isArguments() {
    return this._type === (t.OBJECT | t.ARGUMENTS)
  }

  isDate() {
    return this._type === (t.OBJECT | t.DATE)
  }

  isRegExp() {
    return this._type === (t.OBJECT | t.REGEXP)
  }

  isError() {
    return this._type === (t.OBJECT | t.ERROR)
  }

  isPromise() {
    return this._type === (t.OBJECT | t.PROMISE)
  }

  isProxy() {
    return this._type === (t.OBJECT | t.PROXY)
  }

  isGenerator() {
    return this._type === (t.OBJECT | t.GENERATOR)
  }

  isMap() {
    return this._type === (t.OBJECT | t.MAP)
  }

  isSet() {
    return this._type === (t.OBJECT | t.SET)
  }

  isWeakMap() {
    return this._type === (t.OBJECT | t.WEAK_MAP)
  }

  isWeakSet() {
    return this._type === (t.OBJECT | t.WEAK_SET)
  }

  isWeakRef() {
    return this._type === (t.OBJECT | t.WEAK_REF)
  }

  isArrayBuffer() {
    return this._type === (t.OBJECT | t.ARRAYBUFFER)
  }

  isSharedArrayBuffer() {
    return this._type === (t.OBJECT | t.SHAREDARRAYBUFFER)
  }

  isTypedArray() {
    return (this._type & 0xffff) === (t.OBJECT | t.TYPEDARRAY)
  }

  isInt8Array() {
    return this._type === (t.OBJECT | t.TYPEDARRAY | t.INT8ARRAY)
  }

  isUint8Array() {
    return this._type === (t.OBJECT | t.TYPEDARRAY | t.UINT8ARRAY)
  }

  isUint8ClampedArray() {
    return this._type === (t.OBJECT | t.TYPEDARRAY | t.UINT8CLAMPEDARRAY)
  }

  isInt16Array() {
    return this._type === (t.OBJECT | t.TYPEDARRAY | t.INT16ARRAY)
  }

  isUint16Array() {
    return this._type === (t.OBJECT | t.TYPEDARRAY | t.UINT16ARRAY)
  }

  isInt32Array() {
    return this._type === (t.OBJECT | t.TYPEDARRAY | t.INT32ARRAY)
  }

  isUint32Array() {
    return this._type === (t.OBJECT | t.TYPEDARRAY | t.UINT32ARRAY)
  }

  isFloat16Array() {
    return this._type === (t.OBJECT | t.TYPEDARRAY | t.FLOAT16ARRAY)
  }

  isFloat32Array() {
    return this._type === (t.OBJECT | t.TYPEDARRAY | t.FLOAT32ARRAY)
  }

  isFloat64Array() {
    return this._type === (t.OBJECT | t.TYPEDARRAY | t.FLOAT64ARRAY)
  }

  isBigInt64Array() {
    return this._type === (t.OBJECT | t.TYPEDARRAY | t.BIGINT64ARRAY)
  }

  isBigUint64Array() {
    return this._type === (t.OBJECT | t.TYPEDARRAY | t.BIGUINT64ARRAY)
  }

  isDataView() {
    return this._type === (t.OBJECT | t.DATAVIEW)
  }

  isModuleNamespace() {
    return this._type === (t.OBJECT | t.MODULE_NAMESPACE)
  }

  isFunction() {
    return (this._type & 0xff) === t.FUNCTION
  }

  isAsyncFunction() {
    return (
      (this._type & (0xff | t.ASYNC_FUNCTION)) ===
      (t.FUNCTION | t.ASYNC_FUNCTION)
    )
  }

  isGeneratorFunction() {
    return (
      (this._type & (0xff | t.GENERATOR_FUNCTION)) ===
      (t.FUNCTION | t.GENERATOR_FUNCTION)
    )
  }

  isExternal() {
    return this._type === t.EXTERNAL
  }

  isBigInt() {
    return this._type === t.BIGINT
  }
}

module.exports = exports = function type(value) {
  switch (typeof value) {
    case 'undefined':
      return new Type(t.UNDEFINED)
    case 'boolean':
      return new Type(t.BOOLEAN)
    case 'number':
      return new Type(
        Number.isSafeInteger(value) ? binding.type(value) : t.NUMBER
      )
    case 'string':
      return new Type(t.STRING)
    case 'symbol':
      return new Type(t.SYMBOL)
    case 'object':
      return new Type(value === null ? t.NULL : binding.type(value))
    case 'function':
      return new Type(binding.type(value))
    case 'bigint':
      return new Type(t.BIGINT)
  }
}

exports.createTag = function createTag(...components) {
  const tag = new Uint32Array(4)
  for (let i = 0; i < 4; i++) tag[i] = components[i] || 0
  return tag
}

exports.addTag = function addTag(object, tag) {
  binding.addTag(object, tag)
}

exports.checkTag = function checkTag(object, tag) {
  return binding.checkTag(object, tag)
}
{
  "name": "bare-type",
  "version": "1.1.0",
  "description": "Cross-realm type predicates for Bare",
  "exports": {
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    },
    "./package": "./package.json"
  },
  "files": [
    "index.js",
    "index.d.ts",
    "binding.c",
    "binding.js",
    "CMakeLists.txt",
    "prebuilds"
  ],
  "addon": true,
  "scripts": {
    "test": "prettier . --check && bare test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-type.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-type/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-type#readme",
  "engines": {
    "bare": ">=1.2.0"
  },
  "devDependencies": {
    "brittle": "^3.4.0",
    "cmake-bare": "^1.1.6",
    "prettier": "^3.4.1",
    "prettier-config-standard": "^7.0.0"
  }
}
const LE = (exports.LE =
  new Uint8Array(new Uint16Array([0xff]).buffer)[0] === 0xff)

exports.BE = !LE
const b4a = require('b4a')

const { BE } = require('./endian')

exports.state = function (start = 0, end = 0, buffer = null) {
  return { start, end, buffer }
}

const raw = (exports.raw = require('./raw'))

const uint = (exports.uint = {
  preencode(state, n) {
    state.end += n <= 0xfc ? 1 : n <= 0xffff ? 3 : n <= 0xffffffff ? 5 : 9
  },
  encode(state, n) {
    if (n <= 0xfc) uint8.encode(state, n)
    else if (n <= 0xffff) {
      state.buffer[state.start++] = 0xfd
      uint16.encode(state, n)
    } else if (n <= 0xffffffff) {
      state.buffer[state.start++] = 0xfe
      uint32.encode(state, n)
    } else {
      state.buffer[state.start++] = 0xff
      uint64.encode(state, n)
    }
  },
  decode(state) {
    const a = uint8.decode(state)
    if (a <= 0xfc) return a
    if (a === 0xfd) return uint16.decode(state)
    if (a === 0xfe) return uint32.decode(state)
    return uint64.decode(state)
  }
})

const uint8 = (exports.uint8 = {
  preencode(state, n) {
    state.end += 1
  },
  encode(state, n) {
    validateUint(n)
    state.buffer[state.start++] = n
  },
  decode(state) {
    if (state.start >= state.end) throw new Error('Out of bounds')
    return state.buffer[state.start++]
  }
})

const uint16 = (exports.uint16 = {
  preencode(state, n) {
    state.end += 2
  },
  encode(state, n) {
    validateUint(n)
    state.buffer[state.start++] = n
    state.buffer[state.start++] = n >>> 8
  },
  decode(state) {
    if (state.end - state.start < 2) throw new Error('Out of bounds')
    return state.buffer[state.start++] + state.buffer[state.start++] * 0x100
  }
})

const uint24 = (exports.uint24 = {
  preencode(state, n) {
    state.end += 3
  },
  encode(state, n) {
    validateUint(n)
    state.buffer[state.start++] = n
    state.buffer[state.start++] = n >>> 8
    state.buffer[state.start++] = n >>> 16
  },
  decode(state) {
    if (state.end - state.start < 3) throw new Error('Out of bounds')
    return (
      state.buffer[state.start++] +
      state.buffer[state.start++] * 0x100 +
      state.buffer[state.start++] * 0x10000
    )
  }
})

const uint32 = (exports.uint32 = {
  preencode(state, n) {
    state.end += 4
  },
  encode(state, n) {
    validateUint(n)
    state.buffer[state.start++] = n
    state.buffer[state.start++] = n >>> 8
    state.buffer[state.start++] = n >>> 16
    state.buffer[state.start++] = n >>> 24
  },
  decode(state) {
    if (state.end - state.start < 4) throw new Error('Out of bounds')
    return (
      state.buffer[state.start++] +
      state.buffer[state.start++] * 0x100 +
      state.buffer[state.start++] * 0x10000 +
      state.buffer[state.start++] * 0x1000000
    )
  }
})

const uint40 = (exports.uint40 = {
  preencode(state, n) {
    state.end += 5
  },
  encode(state, n) {
    validateUint(n)
    const r = Math.floor(n / 0x100)
    uint8.encode(state, n)
    uint32.encode(state, r)
  },
  decode(state) {
    if (state.end - state.start < 5) throw new Error('Out of bounds')
    return uint8.decode(state) + 0x100 * uint32.decode(state)
  }
})

const uint48 = (exports.uint48 = {
  preencode(state, n) {
    state.end += 6
  },
  encode(state, n) {
    validateUint(n)
    const r = Math.floor(n / 0x10000)
    uint16.encode(state, n)
    uint32.encode(state, r)
  },
  decode(state) {
    if (state.end - state.start < 6) throw new Error('Out of bounds')
    return uint16.decode(state) + 0x10000 * uint32.decode(state)
  }
})

const uint56 = (exports.uint56 = {
  preencode(state, n) {
    state.end += 7
  },
  encode(state, n) {
    validateUint(n)
    const r = Math.floor(n / 0x1000000)
    uint24.encode(state, n)
    uint32.encode(state, r)
  },
  decode(state) {
    if (state.end - state.start < 7) throw new Error('Out of bounds')
    return uint24.decode(state) + 0x1000000 * uint32.decode(state)
  }
})

const uint64 = (exports.uint64 = {
  preencode(state, n) {
    state.end += 8
  },
  encode(state, n) {
    validateUint(n)
    const r = Math.floor(n / 0x100000000)
    uint32.encode(state, n)
    uint32.encode(state, r)
  },
  decode(state) {
    if (state.end - state.start < 8) throw new Error('Out of bounds')
    return uint32.decode(state) + 0x100000000 * uint32.decode(state)
  }
})

const int = (exports.int = zigZagInt(uint))
exports.int8 = zigZagInt(uint8)
exports.int16 = zigZagInt(uint16)
exports.int24 = zigZagInt(uint24)
exports.int32 = zigZagInt(uint32)
exports.int40 = zigZagInt(uint40)
exports.int48 = zigZagInt(uint48)
exports.int56 = zigZagInt(uint56)
exports.int64 = zigZagInt(uint64)

const biguint64 = (exports.biguint64 = {
  preencode(state, n) {
    state.end += 8
  },
  encode(state, n) {
    const view = new DataView(
      state.buffer.buffer,
      state.start + state.buffer.byteOffset,
      8
    )
    view.setBigUint64(0, n, true) // little endian
    state.start += 8
  },
  decode(state) {
    if (state.end - state.start < 8) throw new Error('Out of bounds')
    const view = new DataView(
      state.buffer.buffer,
      state.start + state.buffer.byteOffset,
      8
    )
    const n = view.getBigUint64(0, true) // little endian
    state.start += 8
    return n
  }
})

exports.bigint64 = zigZagBigInt(biguint64)

const biguint = (exports.biguint = {
  preencode(state, n) {
    let len = 0
    for (let m = n; m; m = m >> 64n) len++
    uint.preencode(state, len)
    state.end += 8 * len
  },
  encode(state, n) {
    let len = 0
    for (let m = n; m; m = m >> 64n) len++
    uint.encode(state, len)
    const view = new DataView(
      state.buffer.buffer,
      state.start + state.buffer.byteOffset,
      8 * len
    )
    for (let m = n, i = 0; m; m = m >> 64n, i += 8) {
      view.setBigUint64(i, BigInt.asUintN(64, m), true) // little endian
    }
    state.start += 8 * len
  },
  decode(state) {
    const len = uint.decode(state)
    if (state.end - state.start < 8 * len) throw new Error('Out of bounds')
    const view = new DataView(
      state.buffer.buffer,
      state.start + state.buffer.byteOffset,
      8 * len
    )
    let n = 0n
    for (let i = len - 1; i >= 0; i--)
      n = (n << 64n) + view.getBigUint64(i * 8, true) // little endian
    state.start += 8 * len
    return n
  }
})

exports.bigint = zigZagBigInt(biguint)

exports.lexint = require('./lexint')

exports.float32 = {
  preencode(state, n) {
    state.end += 4
  },
  encode(state, n) {
    const view = new DataView(
      state.buffer.buffer,
      state.start + state.buffer.byteOffset,
      4
    )
    view.setFloat32(0, n, true) // little endian
    state.start += 4
  },
  decode(state) {
    if (state.end - state.start < 4) throw new Error('Out of bounds')
    const view = new DataView(
      state.buffer.buffer,
      state.start + state.buffer.byteOffset,
      4
    )
    const float = view.getFloat32(0, true) // little endian
    state.start += 4
    return float
  }
}

exports.float64 = {
  preencode(state, n) {
    state.end += 8
  },
  encode(state, n) {
    const view = new DataView(
      state.buffer.buffer,
      state.start + state.buffer.byteOffset,
      8
    )
    view.setFloat64(0, n, true) // little endian
    state.start += 8
  },
  decode(state) {
    if (state.end - state.start < 8) throw new Error('Out of bounds')
    const view = new DataView(
      state.buffer.buffer,
      state.start + state.buffer.byteOffset,
      8
    )
    const float = view.getFloat64(0, true) // little endian
    state.start += 8
    return float
  }
}

const buffer = (exports.buffer = {
  preencode(state, b) {
    if (b) uint8array.preencode(state, b)
    else state.end++
  },
  encode(state, b) {
    if (b) uint8array.encode(state, b)
    else state.buffer[state.start++] = 0
  },
  decode(state) {
    const len = uint.decode(state)
    if (len === 0) return null
    if (state.end - state.start < len) throw new Error('Out of bounds')
    return state.buffer.subarray(state.start, (state.start += len))
  }
})

exports.binary = {
  ...buffer,
  preencode(state, b) {
    if (typeof b === 'string') utf8.preencode(state, b)
    else buffer.preencode(state, b)
  },
  encode(state, b) {
    if (typeof b === 'string') utf8.encode(state, b)
    else buffer.encode(state, b)
  }
}

exports.arraybuffer = {
  preencode(state, b) {
    uint.preencode(state, b.byteLength)
    state.end += b.byteLength
  },
  encode(state, b) {
    uint.encode(state, b.byteLength)

    const view = new Uint8Array(b)

    state.buffer.set(view, state.start)
    state.start += b.byteLength
  },
  decode(state) {
    const len = uint.decode(state)

    const b = new ArrayBuffer(len)
    const view = new Uint8Array(b)

    view.set(state.buffer.subarray(state.start, (state.start += len)))

    return b
  }
}

function typedarray(TypedArray, swap) {
  const n = TypedArray.BYTES_PER_ELEMENT

  return {
    preencode(state, b) {
      uint.preencode(state, b.length)
      state.end += b.byteLength
    },
    encode(state, b) {
      uint.encode(state, b.length)

      const view = new Uint8Array(b.buffer, b.byteOffset, b.byteLength)

      if (BE && swap) swap(view)

      state.buffer.set(view, state.start)
      state.start += b.byteLength
    },
    decode(state) {
      const len = uint.decode(state)

      let b = state.buffer.subarray(state.start, (state.start += len * n))
      if (b.byteLength !== len * n) throw new Error('Out of bounds')
      if (b.byteOffset % n !== 0) b = new Uint8Array(b)

      if (BE && swap) swap(b)

      return new TypedArray(b.buffer, b.byteOffset, b.byteLength / n)
    }
  }
}

const uint8array = (exports.uint8array = typedarray(Uint8Array))
exports.uint16array = typedarray(Uint16Array, b4a.swap16)
exports.uint32array = typedarray(Uint32Array, b4a.swap32)

exports.int8array = typedarray(Int8Array)
exports.int16array = typedarray(Int16Array, b4a.swap16)
exports.int32array = typedarray(Int32Array, b4a.swap32)

exports.biguint64array = typedarray(BigUint64Array, b4a.swap64)
exports.bigint64array = typedarray(BigInt64Array, b4a.swap64)

exports.float32array = typedarray(Float32Array, b4a.swap32)
exports.float64array = typedarray(Float64Array, b4a.swap64)

function string(encoding) {
  return {
    preencode(state, s) {
      const len = b4a.byteLength(s, encoding)
      uint.preencode(state, len)
      state.end += len
    },
    encode(state, s) {
      const len = b4a.byteLength(s, encoding)
      uint.encode(state, len)
      b4a.write(state.buffer, s, state.start, encoding)
      state.start += len
    },
    decode(state) {
      const len = uint.decode(state)
      if (state.end - state.start < len) throw new Error('Out of bounds')
      return b4a.toString(
        state.buffer,
        encoding,
        state.start,
        (state.start += len)
      )
    },
    fixed(n) {
      return {
        preencode(state) {
          state.end += n
        },
        encode(state, s) {
          b4a.write(state.buffer, s, state.start, n, encoding)
          state.start += n
        },
        decode(state) {
          if (state.end - state.start < n) throw new Error('Out of bounds')
          return b4a.toString(
            state.buffer,
            encoding,
            state.start,
            (state.start += n)
          )
        }
      }
    }
  }
}

const utf8 = (exports.string = exports.utf8 = string('utf-8'))
exports.ascii = string('ascii')
exports.hex = string('hex')
exports.base64 = string('base64')
exports.ucs2 = exports.utf16le = string('utf16le')

exports.bool = {
  preencode(state, b) {
    state.end++
  },
  encode(state, b) {
    state.buffer[state.start++] = b ? 1 : 0
  },
  decode(state) {
    if (state.start >= state.end) throw Error('Out of bounds')
    return state.buffer[state.start++] === 1
  }
}

const fixed = (exports.fixed = function fixed(n) {
  return {
    preencode(state, s) {
      if (s.byteLength !== n) throw new Error('Incorrect buffer size')
      state.end += n
    },
    encode(state, s) {
      state.buffer.set(s, state.start)
      state.start += n
    },
    decode(state) {
      if (state.end - state.start < n) throw new Error('Out of bounds')
      return state.buffer.subarray(state.start, (state.start += n))
    }
  }
})

exports.fixed32 = fixed(32)
exports.fixed64 = fixed(64)

exports.array = function array(enc) {
  return {
    preencode(state, list) {
      uint.preencode(state, list.length)
      for (let i = 0; i < list.length; i++) enc.preencode(state, list[i])
    },
    encode(state, list) {
      uint.encode(state, list.length)
      for (let i = 0; i < list.length; i++) enc.encode(state, list[i])
    },
    decode(state) {
      const len = uint.decode(state)
      if (len > 0x100000) throw new Error('Array is too big')
      const arr = new Array(len)
      for (let i = 0; i < len; i++) arr[i] = enc.decode(state)
      return arr
    }
  }
}

exports.frame = function frame(enc) {
  const dummy = exports.state()

  return {
    preencode(state, m) {
      const end = state.end
      enc.preencode(state, m)
      uint.preencode(state, state.end - end)
    },
    encode(state, m) {
      dummy.end = 0
      enc.preencode(dummy, m)
      uint.encode(state, dummy.end)
      enc.encode(state, m)
    },
    decode(state) {
      const end = state.end
      const len = uint.decode(state)
      state.end = state.start + len
      const m = enc.decode(state)
      state.start = state.end
      state.end = end
      return m
    }
  }
}

exports.date = {
  preencode(state, d) {
    int.preencode(state, d.getTime())
  },
  encode(state, d) {
    int.encode(state, d.getTime())
  },
  decode(state, d) {
    return new Date(int.decode(state))
  }
}

exports.json = {
  preencode(state, v) {
    utf8.preencode(state, JSON.stringify(v))
  },
  encode(state, v) {
    utf8.encode(state, JSON.stringify(v))
  },
  decode(state) {
    return JSON.parse(utf8.decode(state))
  }
}

exports.ndjson = {
  preencode(state, v) {
    utf8.preencode(state, JSON.stringify(v) + '\n')
  },
  encode(state, v) {
    utf8.encode(state, JSON.stringify(v) + '\n')
  },
  decode(state) {
    return JSON.parse(utf8.decode(state))
  }
}

// simple helper for when you want to just express nothing
exports.none = {
  preencode(state, n) {
    // do nothing
  },
  encode(state, n) {
    // do nothing
  },
  decode(state) {
    return null
  }
}

// "any" encoders here for helping just structure any object without schematising it

const anyArray = {
  preencode(state, arr) {
    uint.preencode(state, arr.length)
    for (let i = 0; i < arr.length; i++) {
      any.preencode(state, arr[i])
    }
  },
  encode(state, arr) {
    uint.encode(state, arr.length)
    for (let i = 0; i < arr.length; i++) {
      any.encode(state, arr[i])
    }
  },
  decode(state) {
    const arr = []
    let len = uint.decode(state)
    while (len-- > 0) {
      arr.push(any.decode(state))
    }
    return arr
  }
}

const anyObject = {
  preencode(state, o) {
    const keys = Object.keys(o)
    uint.preencode(state, keys.length)
    for (const key of keys) {
      utf8.preencode(state, key)
      any.preencode(state, o[key])
    }
  },
  encode(state, o) {
    const keys = Object.keys(o)
    uint.encode(state, keys.length)
    for (const key of keys) {
      utf8.encode(state, key)
      any.encode(state, o[key])
    }
  },
  decode(state) {
    let len = uint.decode(state)
    const o = {}
    while (len-- > 0) {
      const key = utf8.decode(state)
      o[key] = any.decode(state)
    }
    return o
  }
}

const anyTypes = [
  exports.none,
  exports.bool,
  exports.string,
  exports.buffer,
  exports.uint,
  exports.int,
  exports.float64,
  anyArray,
  anyObject,
  exports.date
]

const any = (exports.any = {
  preencode(state, o) {
    const t = getType(o)
    uint.preencode(state, t)
    anyTypes[t].preencode(state, o)
  },
  encode(state, o) {
    const t = getType(o)
    uint.encode(state, t)
    anyTypes[t].encode(state, o)
  },
  decode(state) {
    const t = uint.decode(state)
    if (t >= anyTypes.length) throw new Error('Unknown type: ' + t)
    return anyTypes[t].decode(state)
  }
})

const port = (exports.port = uint16)

const address = (host, family) => {
  return {
    preencode(state, m) {
      host.preencode(state, m.host)
      port.preencode(state, m.port)
    },
    encode(state, m) {
      host.encode(state, m.host)
      port.encode(state, m.port)
    },
    decode(state) {
      return {
        host: host.decode(state),
        family,
        port: port.decode(state)
      }
    }
  }
}

const ipv4 = (exports.ipv4 = {
  preencode(state) {
    state.end += 4
  },
  encode(state, string) {
    const start = state.start
    const end = start + 4

    let i = 0

    while (i < string.length) {
      let n = 0
      let c

      while (
        i < string.length &&
        (c = string.charCodeAt(i++)) !== /* . */ 0x2e
      ) {
        n = n * 10 + (c - /* 0 */ 0x30)
      }

      state.buffer[state.start++] = n
    }

    state.start = end
  },
  decode(state) {
    if (state.end - state.start < 4) throw new Error('Out of bounds')
    return (
      state.buffer[state.start++] +
      '.' +
      state.buffer[state.start++] +
      '.' +
      state.buffer[state.start++] +
      '.' +
      state.buffer[state.start++]
    )
  }
})

exports.ipv4Address = address(ipv4, 4)

const ipv6 = (exports.ipv6 = {
  preencode(state) {
    state.end += 16
  },
  encode(state, string) {
    const start = state.start
    const end = start + 16

    let i = 0
    let split = null

    while (i < string.length) {
      let n = 0
      let c

      while (
        i < string.length &&
        (c = string.charCodeAt(i++)) !== /* : */ 0x3a
      ) {
        if (c >= 0x30 && c <= 0x39) n = n * 0x10 + (c - /* 0 */ 0x30)
        else if (c >= 0x41 && c <= 0x46) n = n * 0x10 + (c - /* A */ 0x41 + 10)
        else if (c >= 0x61 && c <= 0x66) n = n * 0x10 + (c - /* a */ 0x61 + 10)
      }

      state.buffer[state.start++] = n >>> 8
      state.buffer[state.start++] = n

      if (i < string.length && string.charCodeAt(i) === /* : */ 0x3a) {
        i++
        split = state.start
      }
    }

    if (split !== null) {
      const offset = end - state.start
      state.buffer
        .copyWithin(split + offset, split)
        .fill(0, split, split + offset)
    }

    state.start = end
  },
  decode(state) {
    if (state.end - state.start < 16) throw new Error('Out of bounds')
    return (
      (
        state.buffer[state.start++] * 256 +
        state.buffer[state.start++]
      ).toString(16) +
      ':' +
      (
        state.buffer[state.start++] * 256 +
        state.buffer[state.start++]
      ).toString(16) +
      ':' +
      (
        state.buffer[state.start++] * 256 +
        state.buffer[state.start++]
      ).toString(16) +
      ':' +
      (
        state.buffer[state.start++] * 256 +
        state.buffer[state.start++]
      ).toString(16) +
      ':' +
      (
        state.buffer[state.start++] * 256 +
        state.buffer[state.start++]
      ).toString(16) +
      ':' +
      (
        state.buffer[state.start++] * 256 +
        state.buffer[state.start++]
      ).toString(16) +
      ':' +
      (
        state.buffer[state.start++] * 256 +
        state.buffer[state.start++]
      ).toString(16) +
      ':' +
      (
        state.buffer[state.start++] * 256 +
        state.buffer[state.start++]
      ).toString(16)
    )
  }
})

exports.ipv6Address = address(ipv6, 6)

const ip = (exports.ip = {
  preencode(state, string) {
    const family = string.includes(':') ? 6 : 4
    uint8.preencode(state, family)
    if (family === 4) ipv4.preencode(state)
    else ipv6.preencode(state)
  },
  encode(state, string) {
    const family = string.includes(':') ? 6 : 4
    uint8.encode(state, family)
    if (family === 4) ipv4.encode(state, string)
    else ipv6.encode(state, string)
  },
  decode(state) {
    const family = uint8.decode(state)
    if (family === 4) return ipv4.decode(state)
    else return ipv6.decode(state)
  }
})

exports.ipAddress = {
  preencode(state, m) {
    ip.preencode(state, m.host)
    port.preencode(state, m.port)
  },
  encode(state, m) {
    ip.encode(state, m.host)
    port.encode(state, m.port)
  },
  decode(state) {
    const family = uint8.decode(state)
    return {
      host: family === 4 ? ipv4.decode(state) : ipv6.decode(state),
      family,
      port: port.decode(state)
    }
  }
}

const record = (exports.record = function (keyEncoding, valueEncoding) {
  return {
    preencode(state, v) {
      const keys = Object.keys(v)
      uint.preencode(state, keys.length)
      for (const k of keys) {
        keyEncoding.preencode(state, k)
        valueEncoding.preencode(state, v[k])
      }
    },
    encode(state, v) {
      const keys = Object.keys(v)
      uint.encode(state, keys.length)
      for (const k of keys) {
        keyEncoding.encode(state, k)
        valueEncoding.encode(state, v[k])
      }
    },
    decode(state) {
      const out = Object.create(null)
      const keys = uint.decode(state)
      for (let i = 0; i < keys; i++) {
        out[keyEncoding.decode(state)] = valueEncoding.decode(state)
      }
      return out
    }
  }
})

exports.stringRecord = record(utf8, utf8)

function getType(o) {
  if (o === null || o === undefined) return 0
  if (typeof o === 'boolean') return 1
  if (typeof o === 'string') return 2
  if (b4a.isBuffer(o)) return 3
  if (typeof o === 'number') {
    if (Number.isInteger(o)) return o >= 0 ? 4 : 5
    return 6
  }
  if (Array.isArray(o)) return 7
  if (o instanceof Date) return 9
  if (typeof o === 'object') return 8

  throw new Error('Unsupported type for ' + o)
}

exports.from = function from(enc) {
  if (typeof enc === 'string') return fromNamed(enc)
  if (enc.preencode) return enc
  if (enc.encodingLength) return fromAbstractEncoder(enc)
  return fromCodec(enc)
}

function fromNamed(enc) {
  switch (enc) {
    case 'ascii':
      return raw.ascii
    case 'utf-8':
    case 'utf8':
      return raw.utf8
    case 'hex':
      return raw.hex
    case 'base64':
      return raw.base64
    case 'utf16-le':
    case 'utf16le':
    case 'ucs-2':
    case 'ucs2':
      return raw.ucs2
    case 'ndjson':
      return raw.ndjson
    case 'json':
      return raw.json
    case 'binary':
    default:
      return raw.binary
  }
}

function fromCodec(enc) {
  let tmpM = null
  let tmpBuf = null

  return {
    preencode(state, m) {
      tmpM = m
      tmpBuf = enc.encode(m)
      state.end += tmpBuf.byteLength
    },
    encode(state, m) {
      raw.encode(state, m === tmpM ? tmpBuf : enc.encode(m))
      tmpM = tmpBuf = null
    },
    decode(state) {
      return enc.decode(raw.decode(state))
    }
  }
}

function fromAbstractEncoder(enc) {
  return {
    preencode(state, m) {
      state.end += enc.encodingLength(m)
    },
    encode(state, m) {
      enc.encode(m, state.buffer, state.start)
      state.start += enc.encode.bytes
    },
    decode(state) {
      const m = enc.decode(state.buffer, state.start, state.end)
      state.start += enc.decode.bytes
      return m
    }
  }
}

exports.encode = function encode(enc, m) {
  const state = exports.state()
  enc.preencode(state, m)
  state.buffer = b4a.allocUnsafe(state.end)
  enc.encode(state, m)
  return state.buffer
}

exports.decode = function decode(enc, buffer) {
  return enc.decode(exports.state(0, buffer.byteLength, buffer))
}

function zigZagInt(enc) {
  return {
    preencode(state, n) {
      enc.preencode(state, zigZagEncodeInt(n))
    },
    encode(state, n) {
      enc.encode(state, zigZagEncodeInt(n))
    },
    decode(state) {
      return zigZagDecodeInt(enc.decode(state))
    }
  }
}

function zigZagDecodeInt(n) {
  return n === 0 ? n : (n & 1) === 0 ? n / 2 : -(n + 1) / 2
}

function zigZagEncodeInt(n) {
  // 0, -1, 1, -2, 2, ...
  return n < 0 ? 2 * -n - 1 : n === 0 ? 0 : 2 * n
}

function zigZagBigInt(enc) {
  return {
    preencode(state, n) {
      enc.preencode(state, zigZagEncodeBigInt(n))
    },
    encode(state, n) {
      enc.encode(state, zigZagEncodeBigInt(n))
    },
    decode(state) {
      return zigZagDecodeBigInt(enc.decode(state))
    }
  }
}

function zigZagDecodeBigInt(n) {
  return n === 0n ? n : (n & 1n) === 0n ? n / 2n : -(n + 1n) / 2n
}

function zigZagEncodeBigInt(n) {
  // 0, -1, 1, -2, 2, ...
  return n < 0n ? 2n * -n - 1n : n === 0n ? 0n : 2n * n
}

function validateUint(n) {
  if (n >= 0 === false /* Handles NaN as well */)
    throw new Error('uint must be positive')
}
module.exports = {
  preencode,
  encode,
  decode
}

function preencode(state, num) {
  if (num < 251) {
    state.end++
  } else if (num < 256) {
    state.end += 2
  } else if (num < 0x10000) {
    state.end += 3
  } else if (num < 0x1000000) {
    state.end += 4
  } else if (num < 0x100000000) {
    state.end += 5
  } else {
    state.end++
    const exp = Math.floor(Math.log(num) / Math.log(2)) - 32
    preencode(state, exp)
    state.end += 6
  }
}

function encode(state, num) {
  const max = 251
  const x = num - max

  if (num < max) {
    state.buffer[state.start++] = num
  } else if (num < 256) {
    state.buffer[state.start++] = max
    state.buffer[state.start++] = x
  } else if (num < 0x10000) {
    state.buffer[state.start++] = max + 1
    state.buffer[state.start++] = (x >> 8) & 0xff
    state.buffer[state.start++] = x & 0xff
  } else if (num < 0x1000000) {
    state.buffer[state.start++] = max + 2
    state.buffer[state.start++] = x >> 16
    state.buffer[state.start++] = (x >> 8) & 0xff
    state.buffer[state.start++] = x & 0xff
  } else if (num < 0x100000000) {
    state.buffer[state.start++] = max + 3
    state.buffer[state.start++] = x >> 24
    state.buffer[state.start++] = (x >> 16) & 0xff
    state.buffer[state.start++] = (x >> 8) & 0xff
    state.buffer[state.start++] = x & 0xff
  } else {
    // need to use Math here as bitwise ops are 32 bit
    const exp = Math.floor(Math.log(x) / Math.log(2)) - 32
    state.buffer[state.start++] = 0xff

    encode(state, exp)
    const rem = x / Math.pow(2, exp - 11)

    for (let i = 5; i >= 0; i--) {
      state.buffer[state.start++] = (rem / Math.pow(2, 8 * i)) & 0xff
    }
  }
}

function decode(state) {
  const max = 251

  if (state.end - state.start < 1) throw new Error('Out of bounds')

  const flag = state.buffer[state.start++]

  if (flag < max) return flag

  if (state.end - state.start < flag - max + 1) {
    throw new Error('Out of bounds.')
  }

  if (flag < 252) {
    return state.buffer[state.start++] + max
  }

  if (flag < 253) {
    return (
      (state.buffer[state.start++] << 8) + state.buffer[state.start++] + max
    )
  }

  if (flag < 254) {
    return (
      (state.buffer[state.start++] << 16) +
      (state.buffer[state.start++] << 8) +
      state.buffer[state.start++] +
      max
    )
  }

  // << 24 result may be interpreted as negative
  if (flag < 255) {
    return (
      state.buffer[state.start++] * 0x1000000 +
      (state.buffer[state.start++] << 16) +
      (state.buffer[state.start++] << 8) +
      state.buffer[state.start++] +
      max
    )
  }

  const exp = decode(state)

  if (state.end - state.start < 6) throw new Error('Out of bounds')

  let rem = 0
  for (let i = 5; i >= 0; i--) {
    rem += state.buffer[state.start++] * Math.pow(2, 8 * i)
  }

  return rem * Math.pow(2, exp - 11) + max
}
{
  "name": "compact-encoding",
  "version": "2.19.2",
  "description": "A series of compact encoding schemes for building small and fast parsers and serializers",
  "main": "index.js",
  "dependencies": {
    "b4a": "^1.3.0"
  },
  "devDependencies": {
    "brittle": "^3.0.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^1.0.0"
  },
  "scripts": {
    "format": "prettier . --write",
    "test": "prettier . --check && brittle test.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/holepunchto/compact-encoding.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/compact-encoding/issues"
  },
  "homepage": "https://github.com/holepunchto/compact-encoding"
}
const b4a = require('b4a')

const { BE } = require('./endian')

exports = module.exports = {
  preencode(state, b) {
    state.end += b.byteLength
  },
  encode(state, b) {
    state.buffer.set(b, state.start)
    state.start += b.byteLength
  },
  decode(state) {
    const b = state.buffer.subarray(state.start, state.end)
    state.start = state.end
    return b
  }
}

const buffer = (exports.buffer = {
  preencode(state, b) {
    if (b) uint8array.preencode(state, b)
    else state.end++
  },
  encode(state, b) {
    if (b) uint8array.encode(state, b)
    else state.buffer[state.start++] = 0
  },
  decode(state) {
    const b = state.buffer.subarray(state.start)
    if (b.byteLength === 0) return null
    state.start = state.end
    return b
  }
})

exports.binary = {
  ...buffer,
  preencode(state, b) {
    if (typeof b === 'string') utf8.preencode(state, b)
    else buffer.preencode(state, b)
  },
  encode(state, b) {
    if (typeof b === 'string') utf8.encode(state, b)
    else buffer.encode(state, b)
  }
}

exports.arraybuffer = {
  preencode(state, b) {
    state.end += b.byteLength
  },
  encode(state, b) {
    const view = new Uint8Array(b)

    state.buffer.set(view, state.start)
    state.start += b.byteLength
  },
  decode(state) {
    const b = new ArrayBuffer(state.end - state.start)
    const view = new Uint8Array(b)

    view.set(state.buffer.subarray(state.start))

    state.start = state.end

    return b
  }
}

function typedarray(TypedArray, swap) {
  const n = TypedArray.BYTES_PER_ELEMENT

  return {
    preencode(state, b) {
      state.end += b.byteLength
    },
    encode(state, b) {
      const view = new Uint8Array(b.buffer, b.byteOffset, b.byteLength)

      if (BE && swap) swap(view)

      state.buffer.set(view, state.start)
      state.start += b.byteLength
    },
    decode(state) {
      let b = state.buffer.subarray(state.start)
      if (b.byteOffset % n !== 0) b = new Uint8Array(b)

      if (BE && swap) swap(b)

      state.start = state.end

      return new TypedArray(b.buffer, b.byteOffset, b.byteLength / n)
    }
  }
}

const uint8array = (exports.uint8array = typedarray(Uint8Array))
exports.uint16array = typedarray(Uint16Array, b4a.swap16)
exports.uint32array = typedarray(Uint32Array, b4a.swap32)

exports.int8array = typedarray(Int8Array)
exports.int16array = typedarray(Int16Array, b4a.swap16)
exports.int32array = typedarray(Int32Array, b4a.swap32)

exports.biguint64array = typedarray(BigUint64Array, b4a.swap64)
exports.bigint64array = typedarray(BigInt64Array, b4a.swap64)

exports.float32array = typedarray(Float32Array, b4a.swap32)
exports.float64array = typedarray(Float64Array, b4a.swap64)

function string(encoding) {
  return {
    preencode(state, s) {
      state.end += b4a.byteLength(s, encoding)
    },
    encode(state, s) {
      state.start += b4a.write(state.buffer, s, state.start, encoding)
    },
    decode(state) {
      const s = b4a.toString(state.buffer, encoding, state.start)
      state.start = state.end
      return s
    }
  }
}

const utf8 = (exports.string = exports.utf8 = string('utf-8'))
exports.ascii = string('ascii')
exports.hex = string('hex')
exports.base64 = string('base64')
exports.ucs2 = exports.utf16le = string('utf16le')

exports.array = function array(enc) {
  return {
    preencode(state, list) {
      for (const value of list) enc.preencode(state, value)
    },
    encode(state, list) {
      for (const value of list) enc.encode(state, value)
    },
    decode(state) {
      const arr = []
      while (state.start < state.end) arr.push(enc.decode(state))
      return arr
    }
  }
}

exports.json = {
  preencode(state, v) {
    utf8.preencode(state, JSON.stringify(v))
  },
  encode(state, v) {
    utf8.encode(state, JSON.stringify(v))
  },
  decode(state) {
    return JSON.parse(utf8.decode(state))
  }
}

exports.ndjson = {
  preencode(state, v) {
    utf8.preencode(state, JSON.stringify(v) + '\n')
  },
  encode(state, v) {
    utf8.encode(state, JSON.stringify(v) + '\n')
  },
  decode(state) {
    return JSON.parse(utf8.decode(state))
  }
}
const sodium = require('sodium-universal')
const c = require('compact-encoding')
const b4a = require('b4a')

// https://en.wikipedia.org/wiki/Merkle_tree#Second_preimage_attack
const LEAF_TYPE = b4a.from([0])
const PARENT_TYPE = b4a.from([1])
const ROOT_TYPE = b4a.from([2])

const HYPERCORE = b4a.from('hypercore')

exports.keyPair = function (seed) {
  // key pairs might stay around for a while, so better not to use a default slab to avoid retaining it completely
  const slab = b4a.allocUnsafeSlow(sodium.crypto_sign_PUBLICKEYBYTES + sodium.crypto_sign_SECRETKEYBYTES)
  const publicKey = slab.subarray(0, sodium.crypto_sign_PUBLICKEYBYTES)
  const secretKey = slab.subarray(sodium.crypto_sign_PUBLICKEYBYTES)

  if (seed) sodium.crypto_sign_seed_keypair(publicKey, secretKey, seed)
  else sodium.crypto_sign_keypair(publicKey, secretKey)

  return {
    publicKey,
    secretKey
  }
}

exports.validateKeyPair = function (keyPair) {
  const pk = b4a.allocUnsafe(sodium.crypto_sign_PUBLICKEYBYTES)
  sodium.crypto_sign_ed25519_sk_to_pk(pk, keyPair.secretKey)
  return b4a.equals(pk, keyPair.publicKey)
}

exports.sign = function (message, secretKey) {
  // Dedicated slab for the signature, to avoid retaining unneeded mem and for security
  const signature = b4a.allocUnsafeSlow(sodium.crypto_sign_BYTES)
  sodium.crypto_sign_detached(signature, message, secretKey)
  return signature
}

exports.verify = function (message, signature, publicKey) {
  if (signature.byteLength !== sodium.crypto_sign_BYTES) return false
  if (publicKey.byteLength !== sodium.crypto_sign_PUBLICKEYBYTES) return false
  return sodium.crypto_sign_verify_detached(signature, message, publicKey)
}

exports.encrypt = function (message, publicKey) {
  const ciphertext = b4a.alloc(message.byteLength + sodium.crypto_box_SEALBYTES)
  sodium.crypto_box_seal(ciphertext, message, publicKey)
  return ciphertext
}

exports.decrypt = function (ciphertext, keyPair) {
  if (ciphertext.byteLength < sodium.crypto_box_SEALBYTES) return null

  const plaintext = b4a.alloc(ciphertext.byteLength - sodium.crypto_box_SEALBYTES)

  if (!sodium.crypto_box_seal_open(plaintext, ciphertext, keyPair.publicKey, keyPair.secretKey)) {
    return null
  }

  return plaintext
}

exports.encryptionKeyPair = function (seed) {
  const publicKey = b4a.alloc(sodium.crypto_box_PUBLICKEYBYTES)
  const secretKey = b4a.alloc(sodium.crypto_box_SECRETKEYBYTES)

  if (seed) {
    sodium.crypto_box_seed_keypair(publicKey, secretKey, seed)
  } else {
    sodium.crypto_box_keypair(publicKey, secretKey)
  }

  return {
    publicKey,
    secretKey
  }
}

exports.data = function (data) {
  const out = b4a.allocUnsafe(32)

  sodium.crypto_generichash_batch(out, [
    LEAF_TYPE,
    c.encode(c.uint64, data.byteLength),
    data
  ])

  return out
}

exports.parent = function (a, b) {
  if (a.index > b.index) {
    const tmp = a
    a = b
    b = tmp
  }

  const out = b4a.allocUnsafe(32)

  sodium.crypto_generichash_batch(out, [
    PARENT_TYPE,
    c.encode(c.uint64, a.size + b.size),
    a.hash,
    b.hash
  ])

  return out
}

exports.tree = function (roots, out) {
  const buffers = new Array(3 * roots.length + 1)
  let j = 0

  buffers[j++] = ROOT_TYPE

  for (let i = 0; i < roots.length; i++) {
    const r = roots[i]
    buffers[j++] = r.hash
    buffers[j++] = c.encode(c.uint64, r.index)
    buffers[j++] = c.encode(c.uint64, r.size)
  }

  if (!out) out = b4a.allocUnsafe(32)
  sodium.crypto_generichash_batch(out, buffers)
  return out
}

exports.hash = function (data, out) {
  if (!out) out = b4a.allocUnsafe(32)
  if (!Array.isArray(data)) data = [data]

  sodium.crypto_generichash_batch(out, data)

  return out
}

exports.randomBytes = function (n) {
  const buf = b4a.allocUnsafe(n)
  sodium.randombytes_buf(buf)
  return buf
}

exports.discoveryKey = function (key) {
  if (!key || key.byteLength !== 32) throw new Error('Must pass a 32 byte buffer')
  // Discovery keys might stay around for a while, so better not to use slab memory (for better gc)
  const digest = b4a.allocUnsafeSlow(32)
  sodium.crypto_generichash(digest, HYPERCORE, key)
  return digest
}

if (sodium.sodium_free) {
  exports.free = function (secureBuf) {
    if (secureBuf.secure) sodium.sodium_free(secureBuf)
  }
} else {
  exports.free = function () {}
}

exports.namespace = function (name, count) {
  const ids = typeof count === 'number' ? range(count) : count

  // Namespaces are long-lived, so better to use a dedicated slab
  const buf = b4a.allocUnsafeSlow(32 * ids.length)

  const list = new Array(ids.length)

  // ns is emhemeral, so default slab
  const ns = b4a.allocUnsafe(33)
  sodium.crypto_generichash(ns.subarray(0, 32), typeof name === 'string' ? b4a.from(name) : name)

  for (let i = 0; i < list.length; i++) {
    list[i] = buf.subarray(32 * i, 32 * i + 32)
    ns[32] = ids[i]
    sodium.crypto_generichash(list[i], ns)
  }

  return list
}

function range (count) {
  const arr = new Array(count)
  for (let i = 0; i < count; i++) arr[i] = i
  return arr
}
{
  "name": "hypercore-crypto",
  "version": "3.6.1",
  "description": "The crypto primitives used in hypercore, extracted into a separate module",
  "main": "index.js",
  "files": [
    "index.js"
  ],
  "dependencies": {
    "b4a": "^1.6.6",
    "compact-encoding": "^2.15.0",
    "sodium-universal": "^5.0.0"
  },
  "devDependencies": {
    "brittle": "^3.5.0",
    "standard": "^17.1.0"
  },
  "scripts": {
    "test": "standard && brittle test.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/mafintosh/hypercore-crypto.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/mafintosh/hypercore-crypto/issues"
  },
  "homepage": "https://github.com/mafintosh/hypercore-crypto"
}
module.exports = require.addon.bind(require)
{
  "name": "require-addon",
  "version": "1.2.0",
  "description": "Import native addons across JavaScript runtimes",
  "exports": {
    "./package": "./package.json",
    ".": {
      "bare": "./lib/bare.js",
      "node": "./lib/node.js",
      "default": "./lib/default.js"
    }
  },
  "imports": {
    "fs": {
      "bare": "bare-fs",
      "default": "fs"
    },
    "path": {
      "bare": "bare-path",
      "default": "path"
    },
    "url": {
      "bare": "bare-url",
      "default": "url"
    }
  },
  "files": [
    "lib"
  ],
  "scripts": {
    "test": "npm run lint && npm run test:bare && npm run test:node",
    "test:bare": "bare test.js",
    "test:node": "node test.js",
    "lint": "prettier --check .",
    "format": "prettier --write ."
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/require-addon.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/require-addon/issues"
  },
  "homepage": "https://github.com/holepunchto/require-addon#readme",
  "engines": {
    "bare": ">=1.10.0"
  },
  "dependencies": {
    "bare-addon-resolve": "^1.3.0"
  },
  "devDependencies": {
    "bare-bundle": "^1.8.1",
    "bare-bundle-evaluate": "^1.1.0",
    "bare-fs": "^4.0.0",
    "bare-path": "^3.0.0",
    "bare-url": "^2.1.0",
    "brittle": "^3.7.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^2.0.0"
  }
}
require.addon = require('require-addon')
module.exports = require.addon('.', __filename)
const assert = require('assert')
const binding = require('./binding')
const { isNode } = require('which-runtime')

const OPTIONAL = Buffer.from(new ArrayBuffer(0))

module.exports = exports = { ...binding }

// memory

exports.sodium_memzero = function (buf) {
  assert(ArrayBuffer.isView(buf), 'buf must be a typed array')

  binding.sodium_memzero(buf)
}

exports.sodium_mlock = function (buf) {
  assert(ArrayBuffer.isView(buf), 'buf must be a typed array')

  const res = binding.sodium_mlock(buf)

  if (res !== 0) throw new Error('memory lock failed')
}

exports.sodium_munlock = function (buf) {
  assert(ArrayBuffer.isView(buf), 'buf must be a typed array')

  const res = binding.sodium_munlock(buf)

  if (res !== 0) throw new Error('memory unlock failed')
}

exports.sodium_malloc = function (size) {
  assert(size >= 0, 'invalid size')

  const buf = Buffer.from(binding.sodium_malloc(size))
  buf.secure = true
  return buf
}

exports.sodium_free = function (buf) {
  if (!buf || !buf.secure) return

  binding.sodium_free(buf.buffer)
}

exports.sodium_mprotect_noaccess = function (buf) {
  const res = binding.sodium_mprotect_noaccess(buf.buffer)

  if (res !== 0) throw new Error('failed to lock buffer')
}

exports.sodium_mprotect_readonly = function (buf) {
  const res = binding.sodium_mprotect_readonly(buf.buffer)

  if (res !== 0) throw new Error('failed to unlock buffer')
}

exports.sodium_mprotect_readwrite = function (buf) {
  const res = binding.sodium_mprotect_readwrite(buf.buffer)

  if (res !== 0) throw new Error('failed to unlock buffer')
}

// crypto_randombytes

exports.randombytes_buf = function (buffer) {
  assert(ArrayBuffer.isView(buffer), 'buffer must be a typed array')

  binding.randombytes_buf(buffer.buffer, buffer.byteOffset, buffer.byteLength)
}

exports.randombytes_buf_deterministic = function (buffer, seed) {
  assert(ArrayBuffer.isView(buffer), 'buffer must be a typed array')
  assert(ArrayBuffer.isView(seed), 'seed must be a typed array')
  assert(
    seed.byteLength === binding.randombytes_SEEDBYTES,
    "seed must be 'randombytes_SEEDBYTES' bytes"
  )

  binding.randombytes_buf_deterministic(
    buffer.buffer,
    buffer.byteOffset,
    buffer.byteLength,

    seed.buffer,
    seed.byteOffset,
    seed.byteLength
  )
}

// sodium_helpers

exports.sodium_memcmp = function (a, b) {
  assert(ArrayBuffer.isView(a), 'a must be a typed array')
  assert(ArrayBuffer.isView(b), 'b must be a typed array')
  assert(a.byteLength === b.byteLength, 'buffers must be of same length')

  return binding.sodium_memcmp(a, b)
}

exports.sodium_add = function (a, b) {
  assert(ArrayBuffer.isView(a), 'a must be a typed array')
  assert(ArrayBuffer.isView(b), 'b must be a typed array')
  assert(a.byteLength === b.byteLength, 'buffers must be of same length')

  binding.sodium_add(a, b)
}

exports.sodium_sub = function (a, b) {
  assert(ArrayBuffer.isView(a), 'a must be a typed array')
  assert(ArrayBuffer.isView(b), 'b must be a typed array')
  assert(a.byteLength === b.byteLength, 'buffers must be of same length')

  binding.sodium_sub(a, b)
}

exports.sodium_compare = function (a, b) {
  assert(ArrayBuffer.isView(a), 'a must be a typed array')
  assert(ArrayBuffer.isView(b), 'b must be a typed array')
  assert(a.byteLength === b.byteLength, 'buffers must be of same length')

  return binding.sodium_compare(a, b)
}

exports.sodium_is_zero = function (buffer, length) {
  if (length === undefined) length = buffer.byteLength

  assert(ArrayBuffer.isView(buffer), 'buffer must be a typed array')
  assert(length >= 0 && length <= buffer.byteLength, 'invalid length')

  return binding.sodium_is_zero(buffer, length)
}

exports.sodium_pad = function (buffer, unpaddedBuflen, blockSize) {
  assert(ArrayBuffer.isView(buffer), 'buffer must be a typed array')
  assert(unpaddedBuflen <= buffer.byteLength, 'unpadded length cannot exceed buffer length')
  assert(blockSize <= buffer.byteLength, 'block size cannot exceed buffer length')
  assert(blockSize >= 1, 'block size must be at least 1 byte')
  assert(
    buffer.byteLength >= unpaddedBuflen + (blockSize - (unpaddedBuflen % blockSize)),
    'buf not long enough'
  )

  return binding.sodium_pad(buffer, unpaddedBuflen, blockSize)
}

exports.sodium_unpad = function (buffer, paddedBuflen, blockSize) {
  assert(ArrayBuffer.isView(buffer), 'buffer must be a typed array')
  assert(paddedBuflen <= buffer.byteLength, 'unpadded length cannot exceed buffer length')
  assert(blockSize <= buffer.byteLength, 'block size cannot exceed buffer length')
  assert(blockSize >= 1, 'block size must be at least 1 byte')

  return binding.sodium_unpad(buffer, paddedBuflen, blockSize)
}

// crypto_sign

exports.crypto_sign_keypair = function (pk, sk) {
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(
    pk.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
    "pk must be 'crypto_sign_PUBLICKEYBYTES' bytes"
  )
  assert(
    sk.byteLength === binding.crypto_sign_SECRETKEYBYTES,
    "sk must be 'crypto_sign_SECRETKEYBYTES' bytes"
  )

  const res = binding.crypto_sign_keypair(pk, sk)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_sign_seed_keypair = function (pk, sk, seed) {
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(ArrayBuffer.isView(seed), 'seed must be a typed array')
  assert(
    pk.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
    "pk must be 'crypto_sign_PUBLICKEYBYTES' bytes"
  )
  assert(
    sk.byteLength === binding.crypto_sign_SECRETKEYBYTES,
    "sk must be 'crypto_sign_SECRETKEYBYTES' bytes"
  )
  assert(
    seed.byteLength === binding.crypto_sign_SEEDBYTES,
    "seed must be 'crypto_sign_SEEDBYTES' bytes"
  )

  const res = binding.crypto_sign_seed_keypair(pk, sk, seed)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_sign = function (sm, m, sk) {
  assert(ArrayBuffer.isView(sm), 'sm must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(
    sm.byteLength === binding.crypto_sign_BYTES + m.byteLength,
    "sm must be 'm.byteLength + crypto_sign_BYTES' bytes"
  )
  assert(
    sk.byteLength === binding.crypto_sign_SECRETKEYBYTES,
    "sk must be 'crypto_sign_SECRETKEYBYTES' bytes"
  )

  const res = binding.crypto_sign(sm, m, sk)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_sign_open = function (m, sm, pk) {
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(sm), 'sm must be a typed array')
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(
    sm.byteLength >= binding.crypto_sign_BYTES,
    "sm must be at least 'crypto_sign_BYTES' bytes"
  )
  assert(
    m.byteLength === sm.byteLength - binding.crypto_sign_BYTES,
    "m must be 'sm.byteLength - crypto_sign_BYTES' bytes"
  )
  assert(
    pk.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
    "pk must be 'crypto_sign_PUBLICKEYBYTES' bytes"
  )

  const res = binding.crypto_sign_open(m, sm, pk)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_sign_open = function (m, sm, pk) {
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(sm), 'sm must be a typed array')
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(
    sm.byteLength >= binding.crypto_sign_BYTES,
    "sm must be at least 'crypto_sign_BYTES' bytes"
  )
  assert(
    m.byteLength === sm.byteLength - binding.crypto_sign_BYTES,
    "m must be 'sm.byteLength - crypto_sign_BYTES' bytes"
  )
  assert(
    pk.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
    "pk must be 'crypto_sign_PUBLICKEYBYTES' bytes"
  )

  return binding.crypto_sign_open(m, sm, pk)
}

exports.crypto_sign_detached = function (sig, m, sk) {
  assert(ArrayBuffer.isView(sig), 'sig must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(sig.byteLength === binding.crypto_sign_BYTES, "sig must be 'crypto_sign_BYTES' bytes")
  assert(
    sk.byteLength === binding.crypto_sign_SECRETKEYBYTES,
    "sk must be 'crypto_sign_SECRETKEYBYTES' bytes"
  )

  const res = binding.crypto_sign_detached(sig, m, sk)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_sign_verify_detached = function (sig, m, pk) {
  assert(ArrayBuffer.isView(sig), 'sig must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(
    sig.byteLength >= binding.crypto_sign_BYTES,
    "sig must be at least 'crypto_sign_BYTES' bytes"
  )
  assert(
    pk.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
    "pk must be 'crypto_sign_PUBLICKEYBYTES' bytes"
  )

  return binding.crypto_sign_verify_detached(
    sig.buffer,
    sig.byteOffset,
    sig.byteLength,

    m.buffer,
    m.byteOffset,
    m.byteLength,

    pk.buffer,
    pk.byteOffset,
    pk.byteLength
  )
}

exports.crypto_sign_ed25519_sk_to_pk = function (pk, sk) {
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(
    pk.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
    "pk must be 'crypto_sign_PUBLICKEYBYTES' bytes"
  )
  assert(
    sk.byteLength === binding.crypto_sign_SECRETKEYBYTES,
    "sk must be 'crypto_sign_SECRETKEYBYTES' bytes"
  )

  const res = binding.crypto_sign_ed25519_sk_to_pk(pk, sk)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_sign_ed25519_pk_to_curve25519 = function (x25519pk, ed25519pk) {
  assert(ArrayBuffer.isView(x25519pk), 'x25519pk must be a typed array')
  assert(ArrayBuffer.isView(ed25519pk), 'ed25519pk must be a typed array')
  assert(
    x25519pk.byteLength === binding.crypto_box_PUBLICKEYBYTES,
    "x25519pk must be 'crypto_box_PUBLICKEYBYTES' bytes"
  )
  assert(
    ed25519pk.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
    "ed25519pk must be 'crypto_sign_PUBLICKEYBYTES' bytes"
  )

  const res = binding.crypto_sign_ed25519_pk_to_curve25519(x25519pk, ed25519pk)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_sign_ed25519_sk_to_curve25519 = function (x25519sk, ed25519sk) {
  assert(ArrayBuffer.isView(x25519sk), 'x25519sk must be a typed array')
  assert(ArrayBuffer.isView(ed25519sk), 'ed25519sk must be a typed array')
  assert(
    x25519sk.byteLength === binding.crypto_box_SECRETKEYBYTES,
    "x25519sk must be 'crypto_box_SECRETKEYBYTES' bytes"
  )

  const edLen = ed25519sk.byteLength

  assert(
    edLen === binding.crypto_sign_SECRETKEYBYTES || edLen === binding.crypto_box_SECRETKEYBYTES,
    "ed25519sk must be 'crypto_sign_SECRETKEYBYTES' or 'crypto_sign_SECRETKEYBYTES - crypto_sign_PUBLICKEYBYTES' bytes"
  )

  const res = binding.crypto_sign_ed25519_sk_to_curve25519(x25519sk, ed25519sk)

  if (res !== 0) throw new Error('status: ' + res)
}

// crypto_box

exports.crypto_box_keypair = function (pk, sk) {
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(
    pk.byteLength === binding.crypto_box_PUBLICKEYBYTES,
    "pk must be 'crypto_box_PUBLICKEYBYTES' bytes"
  )
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(
    sk.byteLength === binding.crypto_box_SECRETKEYBYTES,
    "sk must be 'crypto_box_SECRETKEYBYTES' bytes"
  )

  const res = binding.crypto_box_keypair(pk, sk)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_box_seed_keypair = function (pk, sk, seed) {
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(
    pk.byteLength === binding.crypto_box_PUBLICKEYBYTES,
    "pk must be 'crypto_box_PUBLICKEYBYTES' bytes"
  )
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(
    sk.byteLength === binding.crypto_box_SECRETKEYBYTES,
    "sk must be 'crypto_box_SECRETKEYBYTES' bytes"
  )
  assert(ArrayBuffer.isView(seed), 'seed must be a typed array')
  assert(
    seed.byteLength === binding.crypto_box_SEEDBYTES,
    "seed must be 'crypto_box_SEEDBYTES' bytes"
  )

  const res = binding.crypto_box_seed_keypair(pk, sk, seed)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_box_easy = function (c, m, n, pk, sk) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(
    c.byteLength === m.byteLength + exports.crypto_box_MACBYTES,
    "c must be 'm.byteLength + crypto_box_MACBYTES' bytes"
  )
  assert(n.byteLength === exports.crypto_box_NONCEBYTES, "n must be 'crypto_box_NONCEBYTES' bytes")
  assert(
    pk.byteLength === exports.crypto_box_PUBLICKEYBYTES,
    "pk must be 'crypto_box_PUBLICKEYBYTES' bytes"
  )
  assert(
    sk.byteLength === exports.crypto_box_SECRETKEYBYTES,
    "sk must be 'crypto_box_SECRETKEYBYTES' bytes"
  )

  const res = binding.crypto_box_easy(c, m, n, pk, sk)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_box_detached = function (c, mac, m, n, pk, sk) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(mac), 'mac must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(mac.byteLength === exports.crypto_box_MACBYTES, "mac must be 'crypto_box_MACBYTES' bytes")
  assert(n.byteLength === exports.crypto_box_NONCEBYTES, "n must be 'crypto_box_NONCEBYTES' bytes")
  assert(
    pk.byteLength === exports.crypto_box_PUBLICKEYBYTES,
    "pk must be 'crypto_box_PUBLICKEYBYTES' bytes"
  )
  assert(
    sk.byteLength === exports.crypto_box_SECRETKEYBYTES,
    "sk must be 'crypto_box_SECRETKEYBYTES' bytes"
  )

  const res = binding.crypto_box_detached(c, mac, m, n, pk, sk)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_box_open_easy = function (m, c, n, pk, sk) {
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(
    c.byteLength >= exports.crypto_box_MACBYTES,
    "c must be at least 'crypto_box_MACBYTES' bytes"
  )
  assert(
    m.byteLength === c.byteLength - exports.crypto_box_MACBYTES,
    "m must be 'c.byteLength - crypto_box_MACBYTES' bytes"
  )
  assert(n.byteLength === exports.crypto_box_NONCEBYTES, "n must be 'crypto_box_NONCEBYTES' bytes")
  assert(
    pk.byteLength === exports.crypto_box_PUBLICKEYBYTES,
    "pk must be 'crypto_box_PUBLICKEYBYTES' bytes"
  )
  assert(
    sk.byteLength === exports.crypto_box_SECRETKEYBYTES,
    "sk must be 'crypto_box_SECRETKEYBYTES' bytes"
  )

  return binding.crypto_box_open_easy(m, c, n, pk, sk)
}

exports.crypto_box_open_detached = function (m, c, mac, n, pk, sk) {
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(mac), 'mac must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(m.byteLength === c.byteLength, "m must be 'c.byteLength' bytes")
  assert(mac.byteLength === exports.crypto_box_MACBYTES, "mac must be 'crypto_box_MACBYTES' bytes")
  assert(n.byteLength === exports.crypto_box_NONCEBYTES, "n must be 'crypto_box_NONCEBYTES' bytes")
  assert(
    pk.byteLength === exports.crypto_box_PUBLICKEYBYTES,
    "pk must be 'crypto_box_PUBLICKEYBYTES' bytes"
  )
  assert(
    sk.byteLength === exports.crypto_box_SECRETKEYBYTES,
    "sk must be 'crypto_box_SECRETKEYBYTES' bytes"
  )

  return binding.crypto_box_open_detached(m, c, mac, n, pk, sk)
}

exports.crypto_box_seal = function (c, m, pk) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(
    c.byteLength === m.byteLength + exports.crypto_box_SEALBYTES,
    "c must be 'm.byteLength + crypto_box_SEALBYTES' bytes"
  )
  assert(
    pk.byteLength === exports.crypto_box_PUBLICKEYBYTES,
    "pk must be 'crypto_box_PUBLICKEYBYTES' bytes"
  )

  const res = binding.crypto_box_seal(c, m, pk)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_box_seal_open = function (m, c, pk, sk) {
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(
    c.byteLength >= exports.crypto_box_SEALBYTES,
    "c must be at least 'crypto_box_SEALBYTES' bytes"
  )
  assert(
    m.byteLength === c.byteLength - exports.crypto_box_SEALBYTES,
    "m must be 'c.byteLength - crypto_box_SEALBYTES' bytes"
  )
  assert(
    pk.byteLength === exports.crypto_box_PUBLICKEYBYTES,
    "pk must be 'crypto_box_PUBLICKEYBYTES' bytes"
  )
  assert(
    sk.byteLength === exports.crypto_box_SECRETKEYBYTES,
    "sk must be 'crypto_box_SECRETKEYBYTES' bytes"
  )

  return binding.crypto_box_seal_open(
    m.buffer,
    m.byteOffset,
    m.byteLength,

    c.buffer,
    c.byteOffset,
    c.byteLength,

    pk.buffer,
    pk.byteOffset,
    pk.byteLength,

    sk.buffer,
    sk.byteOffset,
    sk.byteLength
  )
}

// crypto_secretbox

exports.crypto_secretbox_easy = function (c, m, n, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    c.byteLength === m.byteLength + binding.crypto_secretbox_MACBYTES,
    "c must be 'm.byteLength + crypto_secretbox_MACBYTES' bytes"
  )
  assert(
    n.byteLength === binding.crypto_secretbox_NONCEBYTES,
    "n must be 'crypto_secretbox_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_secretbox_KEYBYTES,
    "k must be 'crypto_secretbox_KEYBYTES' bytes"
  )

  const res = binding.crypto_secretbox_easy(c, m, n, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_secretbox_open_easy = function (m, c, n, k) {
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    c.byteLength >= binding.crypto_secretbox_MACBYTES,
    "c must be at least 'crypto_secretbox_MACBYTES' bytes"
  )
  assert(
    m.byteLength === c.byteLength - binding.crypto_secretbox_MACBYTES,
    "m must be 'c.byteLength - crypto_secretbox_MACBYTES' bytes"
  )
  assert(
    n.byteLength === binding.crypto_secretbox_NONCEBYTES,
    "n must be 'crypto_secretbox_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_secretbox_KEYBYTES,
    "k must be 'crypto_secretbox_KEYBYTES' bytes"
  )

  return binding.crypto_secretbox_open_easy(m, c, n, k)
}

exports.crypto_secretbox_detached = function (c, mac, m, n, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(mac), 'mac must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(
    mac.byteLength === binding.crypto_secretbox_MACBYTES,
    "mac must be 'crypto_secretbox_MACBYTES' bytes"
  )
  assert(
    n.byteLength === binding.crypto_secretbox_NONCEBYTES,
    "n must be 'crypto_secretbox_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_secretbox_KEYBYTES,
    "k must be 'crypto_secretbox_KEYBYTES' bytes"
  )

  const res = binding.crypto_secretbox_detached(c, mac, m, n, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_secretbox_open_detached = function (m, c, mac, n, k) {
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(mac), 'mac must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(m.byteLength === c.byteLength, "m must be 'c.byteLength' bytes")
  assert(
    mac.byteLength === binding.crypto_secretbox_MACBYTES,
    "mac must be 'crypto_secretbox_MACBYTES' bytes"
  )
  assert(
    n.byteLength === binding.crypto_secretbox_NONCEBYTES,
    "n must be 'crypto_secretbox_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_secretbox_KEYBYTES,
    "k must be 'crypto_secretbox_KEYBYTES' bytes"
  )

  return binding.crypto_secretbox_open_detached(m, c, mac, n, k)
}

// crypto_generichash

exports.crypto_generichash = function (output, input, key) {
  if (!key) key = OPTIONAL

  assert(ArrayBuffer.isView(output), 'output must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(
    output.byteLength >= binding.crypto_generichash_BYTES_MIN &&
      output.byteLength <= binding.crypto_generichash_BYTES_MAX,
    'output must be between crypto_generichash_BYTES_MIN and crypto_generichash_BYTES_MAX bytes'
  )

  if (key !== OPTIONAL) {
    assert(ArrayBuffer.isView(key), 'key must be a typed array')
    assert(
      key.byteLength >= binding.crypto_generichash_KEYBYTES_MIN &&
        key.byteLength <= binding.crypto_generichash_KEYBYTES_MAX,
      'key must be between crypto_generichash_KEYBYTES_MIN and crypto_generichash_KEYBYTES_MAX bytes'
    )
  }

  const res = binding.crypto_generichash(
    output.buffer,
    output.byteOffset,
    output.byteLength,

    input.buffer,
    input.byteOffset,
    input.byteLength,

    key.buffer,
    key.byteOffset,
    key.byteLength
  )

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_generichash_batch = function (output, batch, key) {
  assert(ArrayBuffer.isView(output), 'output must be a typed array')

  if (isNode || batch.length < 4) {
    const res = binding.crypto_generichash_batch(output, batch, !!key, key || OPTIONAL)
    if (res !== 0) throw new Error('status: ' + res)
  } else {
    const state = Buffer.alloc(binding.crypto_generichash_STATEBYTES)

    exports.crypto_generichash_init(state, key, output.byteLength)

    for (const buf of batch) {
      exports.crypto_generichash_update(state, buf)
    }

    exports.crypto_generichash_final(state, output)
  }
}

exports.crypto_generichash_keygen = function (key) {
  assert(ArrayBuffer.isView(key), 'key must be a typed array')
  assert(
    key.byteLength === binding.crypto_generichash_KEYBYTES,
    "key must be 'crypto_generichash_KEYBYTES' bytes"
  )

  const res = binding.crypto_generichash_keygen(key.buffer, key.byteOffset, key.byteLength)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_generichash_init = function (state, key, outputLength) {
  if (!key) key = OPTIONAL

  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_generichash_STATEBYTES,
    "state must be 'crypto_generichash_STATEBYTES' bytes"
  )

  const res = binding.crypto_generichash_init(
    state.buffer,
    state.byteOffset,
    state.byteLength,

    key.buffer,
    key.byteOffset,
    key.byteLength,

    outputLength
  )

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_generichash_update = function (state, input) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(
    state.byteLength === binding.crypto_generichash_STATEBYTES,
    "state must be 'crypto_generichash_STATEBYTES' bytes"
  )

  const res = binding.crypto_generichash_update(
    state.buffer,
    state.byteOffset,
    state.byteLength,

    input.buffer,
    input.byteOffset,
    input.byteLength
  )

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_generichash_final = function (state, output) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(ArrayBuffer.isView(output), 'output must be a typed array')
  assert(
    state.byteLength === binding.crypto_generichash_STATEBYTES,
    "state must be 'crypto_generichash_STATEBYTES' bytes"
  )

  const res = binding.crypto_generichash_final(
    state.buffer,
    state.byteOffset,
    state.byteLength,

    output.buffer,
    output.byteOffset,
    output.byteLength
  )

  if (res !== 0) throw new Error('status: ' + res)
}

// secretstream

exports.crypto_secretstream_xchacha20poly1305_keygen = function (k) {
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_secretstream_xchacha20poly1305_KEYBYTES,
    "k must be 'crypto_secretstream_xchacha20poly1305_KEYBYTES' bytes"
  )

  binding.crypto_secretstream_xchacha20poly1305_keygen(k.buffer, k.byteOffset, k.byteLength)
}

exports.crypto_secretstream_xchacha20poly1305_init_push = function (state, header, k) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(ArrayBuffer.isView(header), 'header must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    state.byteLength === binding.crypto_secretstream_xchacha20poly1305_STATEBYTES,
    "state must be 'crypto_secretstream_xchacha20poly1305_STATEBYTES' bytes"
  )
  assert(
    header.byteLength === binding.crypto_secretstream_xchacha20poly1305_HEADERBYTES,
    "header must be 'crypto_secretstream_xchacha20poly1305_HEADERBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_secretstream_xchacha20poly1305_KEYBYTES,
    "k must be 'crypto_secretstream_xchacha20poly1305_KEYBYTES' bytes"
  )

  const res = binding.crypto_secretstream_xchacha20poly1305_init_push(
    state.buffer,
    state.byteOffset,
    state.byteLength,

    header.buffer,
    header.byteOffset,
    header.byteLength,

    k.buffer,
    k.byteOffset,
    k.byteLength
  )

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_secretstream_xchacha20poly1305_init_pull = function (state, header, k) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(ArrayBuffer.isView(header), 'header must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    state.byteLength === binding.crypto_secretstream_xchacha20poly1305_STATEBYTES,
    "state must be 'crypto_secretstream_xchacha20poly1305_STATEBYTES' bytes"
  )
  assert(
    header.byteLength === binding.crypto_secretstream_xchacha20poly1305_HEADERBYTES,
    "header must be 'crypto_secretstream_xchacha20poly1305_HEADERBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_secretstream_xchacha20poly1305_KEYBYTES,
    "k must be 'crypto_secretstream_xchacha20poly1305_KEYBYTES' bytes"
  )

  const res = binding.crypto_secretstream_xchacha20poly1305_init_pull(
    state.buffer,
    state.byteOffset,
    state.byteLength,

    header.buffer,
    header.byteOffset,
    header.byteLength,

    k.buffer,
    k.byteOffset,
    k.byteLength
  )

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_secretstream_xchacha20poly1305_push = function (state, c, m, ad, tag) {
  if (!ad) ad = OPTIONAL

  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(
    state.byteLength === binding.crypto_secretstream_xchacha20poly1305_STATEBYTES,
    "state must be 'crypto_secretstream_xchacha20poly1305_STATEBYTES' bytes"
  )
  assert(
    c.byteLength === m.byteLength + binding.crypto_secretstream_xchacha20poly1305_ABYTES,
    "c must be 'm.byteLength + crypto_secretstream_xchacha20poly1305_ABYTES' bytes"
  )

  const res = binding.crypto_secretstream_xchacha20poly1305_push(
    state.buffer,
    state.byteOffset,
    state.byteLength,

    c.buffer,
    c.byteOffset,
    c.byteLength,

    m.buffer,
    m.byteOffset,
    m.byteLength,

    ad.buffer,
    ad.byteOffset,
    ad.byteLength,

    tag
  )

  if (res < 0) throw new Error('push failed')

  return res
}

exports.crypto_secretstream_xchacha20poly1305_pull = function (state, m, tag, c, ad) {
  if (!ad) ad = OPTIONAL

  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_secretstream_xchacha20poly1305_STATEBYTES,
    "state must be 'crypto_secretstream_xchacha20poly1305_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(tag), 'tag must be a typed array')
  assert(tag.byteLength === 1, 'tag must be 1 byte')

  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(
    c.byteLength >= binding.crypto_secretstream_xchacha20poly1305_ABYTES,
    "c must be at least 'crypto_secretstream_xchacha20poly1305_ABYTES' bytes"
  )
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(
    m.byteLength === c.byteLength - binding.crypto_secretstream_xchacha20poly1305_ABYTES,
    "m must be 'c.byteLength - crypto_secretstream_xchacha20poly1305_ABYTES' bytes"
  )

  const res = binding.crypto_secretstream_xchacha20poly1305_pull(
    state.buffer,
    state.byteOffset,
    state.byteLength,

    m.buffer,
    m.byteOffset,
    m.byteLength,

    tag.buffer,
    tag.byteOffset,
    tag.byteLength,

    c.buffer,
    c.byteOffset,
    c.byteLength,

    ad.buffer,
    ad.byteOffset,
    ad.byteLength
  )

  if (res < 0) throw new Error('pull failed')

  return res
}

exports.crypto_secretstream_xchacha20poly1305_rekey = function (state) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_secretstream_xchacha20poly1305_STATEBYTES,
    "state must be 'crypto_secretstream_xchacha20poly1305_STATEBYTES' bytes"
  )

  binding.crypto_secretstream_xchacha20poly1305_rekey(
    state.buffer,
    state.byteOffset,
    state.byteLength
  )
}

// crypto_stream

exports.crypto_stream = function (c, n, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    n.byteLength === binding.crypto_stream_NONCEBYTES,
    "n must be 'crypto_stream_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_KEYBYTES,
    "k must be 'crypto_stream_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream(c, n, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_xor = function (c, m, n, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(
    n.byteLength === binding.crypto_stream_NONCEBYTES,
    "n must be 'crypto_stream_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_KEYBYTES,
    "k must be 'crypto_stream_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_xor(
    c.buffer,
    c.byteOffset,
    c.byteLength,

    m.buffer,
    m.byteOffset,
    m.byteLength,

    n.buffer,
    n.byteOffset,
    n.byteLength,

    k.buffer,
    k.byteOffset,
    k.byteLength
  )

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_chacha20 = function (c, n, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    n.byteLength === binding.crypto_stream_chacha20_NONCEBYTES,
    "n must be 'crypto_stream_chacha20_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_chacha20_KEYBYTES,
    "k must be 'crypto_stream_chacha20_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_chacha20(c, n, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_chacha20_xor = function (c, m, n, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(
    n.byteLength === binding.crypto_stream_chacha20_NONCEBYTES,
    "n must be 'crypto_stream_chacha20_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_chacha20_KEYBYTES,
    "k must be 'crypto_stream_chacha20_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_chacha20_xor(c, m, n, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_chacha20_xor_ic = function (c, m, n, ic, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(
    n.byteLength === binding.crypto_stream_chacha20_NONCEBYTES,
    "n must be 'crypto_stream_chacha20_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_chacha20_KEYBYTES,
    "k must be 'crypto_stream_chacha20_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_chacha20_xor_ic(c, m, n, ic, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_chacha20_ietf = function (c, n, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    n.byteLength === binding.crypto_stream_chacha20_ietf_NONCEBYTES,
    "n must be 'crypto_stream_chacha20_ietf_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_chacha20_ietf_KEYBYTES,
    "k must be 'crypto_stream_chacha20_ietf_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_chacha20_ietf(c, n, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_chacha20_ietf_xor = function (c, m, n, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(
    n.byteLength === binding.crypto_stream_chacha20_ietf_NONCEBYTES,
    "n must be 'crypto_stream_chacha20_ietf_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_chacha20_ietf_KEYBYTES,
    "k must be 'crypto_stream_chacha20_ietf_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_chacha20_ietf_xor(c, m, n, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_chacha20_ietf_xor_ic = function (c, m, n, ic, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(
    n.byteLength === binding.crypto_stream_chacha20_ietf_NONCEBYTES,
    "n must be 'crypto_stream_chacha20_ietf_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_chacha20_ietf_KEYBYTES,
    "k must be 'crypto_stream_chacha20_ietf_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_chacha20_ietf_xor_ic(c, m, n, ic, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_xchacha20 = function (c, n, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    n.byteLength === binding.crypto_stream_xchacha20_NONCEBYTES,
    "n must be 'crypto_stream_xchacha20_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_xchacha20_KEYBYTES,
    "k must be 'crypto_stream_xchacha20_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_xchacha20(c, n, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_xchacha20_xor = function (c, m, n, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(
    n.byteLength === binding.crypto_stream_xchacha20_NONCEBYTES,
    "n must be 'crypto_stream_xchacha20_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_xchacha20_KEYBYTES,
    "k must be 'crypto_stream_xchacha20_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_xchacha20_xor(c, m, n, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_xchacha20_xor_ic = function (c, m, n, ic, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(
    n.byteLength === binding.crypto_stream_xchacha20_NONCEBYTES,
    "n must be 'crypto_stream_xchacha20_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_xchacha20_KEYBYTES,
    "k must be 'crypto_stream_xchacha20_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_xchacha20_xor_ic(c, m, n, ic, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_salsa20 = function (c, n, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    n.byteLength === binding.crypto_stream_salsa20_NONCEBYTES,
    "n must be 'crypto_stream_salsa20_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_salsa20_KEYBYTES,
    "k must be 'crypto_stream_salsa20_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_salsa20(c, n, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_salsa20_xor = function (c, m, n, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(
    n.byteLength === binding.crypto_stream_salsa20_NONCEBYTES,
    "n must be 'crypto_stream_salsa20_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_salsa20_KEYBYTES,
    "k must be 'crypto_stream_salsa20_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_salsa20_xor(c, m, n, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_salsa20_xor_ic = function (c, m, n, ic, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(
    n.byteLength === binding.crypto_stream_salsa20_NONCEBYTES,
    "n must be 'crypto_stream_salsa20_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_salsa20_KEYBYTES,
    "k must be 'crypto_stream_salsa20_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_salsa20_xor_ic(c, m, n, ic, k)

  if (res !== 0) throw new Error('status: ' + res)
}

// crypto_auth

exports.crypto_auth = function (out, input, k) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(out.byteLength === binding.crypto_auth_BYTES, "out must be 'crypto_auth_BYTES' bytes")
  assert(k.byteLength === binding.crypto_auth_KEYBYTES, "k must be 'crypto_auth_KEYBYTES' bytes")

  const res = binding.crypto_auth(out, input, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_auth_verify = function (h, input, k) {
  assert(ArrayBuffer.isView(h), 'h must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(h.byteLength === binding.crypto_auth_BYTES, "h must be 'crypto_auth_BYTES' bytes")
  assert(k.byteLength === binding.crypto_auth_KEYBYTES, "k must be 'crypto_auth_KEYBYTES' bytes")

  return binding.crypto_auth_verify(h, input, k)
}

// crypto_onetimeauth

exports.crypto_onetimeauth = function (out, input, k) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    out.byteLength === binding.crypto_onetimeauth_BYTES,
    "out must be 'crypto_onetimeauth_BYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_onetimeauth_KEYBYTES,
    "k must be 'crypto_onetimeauth_KEYBYTES' bytes"
  )

  const res = binding.crypto_onetimeauth(out, input, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_onetimeauth_init = function (state, k) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    state.byteLength === binding.crypto_onetimeauth_STATEBYTES,
    "state must be 'crypto_onetimeauth_STATEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_onetimeauth_KEYBYTES,
    "k must be 'crypto_onetimeauth_KEYBYTES' bytes"
  )

  const res = binding.crypto_onetimeauth_init(state, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_onetimeauth_update = function (state, input) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(
    state.byteLength === binding.crypto_onetimeauth_STATEBYTES,
    "state must be 'crypto_onetimeauth_STATEBYTES' bytes"
  )

  const res = binding.crypto_onetimeauth_update(state, input)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_onetimeauth_final = function (state, out) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(
    state.byteLength === binding.crypto_onetimeauth_STATEBYTES,
    "state must be 'crypto_onetimeauth_STATEBYTES' bytes"
  )
  assert(
    out.byteLength === binding.crypto_onetimeauth_BYTES,
    "out must be 'crypto_onetimeauth_BYTES' bytes"
  )

  const res = binding.crypto_onetimeauth_final(state, out)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_onetimeauth_verify = function (h, input, k) {
  assert(ArrayBuffer.isView(h), 'h must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    h.byteLength === binding.crypto_onetimeauth_BYTES,
    "h must be 'crypto_onetimeauth_BYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_onetimeauth_KEYBYTES,
    "k must be 'crypto_onetimeauth_KEYBYTES' bytes"
  )

  return binding.crypto_onetimeauth_verify(h, input, k)
}

// crypto_pwhash

exports.crypto_pwhash = function (out, passwd, salt, opslimit, memlimit, alg) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(ArrayBuffer.isView(salt), 'salt must be a typed array')
  assert(
    out.byteLength >= binding.crypto_pwhash_BYTES_MIN,
    "out must be at least 'crypto_pwhash_BYTES_MIN' bytes"
  )
  assert(
    out.byteLength <= binding.crypto_pwhash_BYTES_MAX,
    "out must be at most 'crypto_pwhash_BYTES_MAX' bytes"
  )
  assert(
    salt.byteLength === binding.crypto_pwhash_SALTBYTES,
    "salt must be 'crypto_pwhash_SALTBYTES' bytes"
  )
  assert(
    opslimit >= binding.crypto_pwhash_OPSLIMIT_MIN,
    "opslimit must be at least 'crypto_pwhash_OPSLIMIT_MIN'"
  )
  assert(
    opslimit <= binding.crypto_pwhash_OPSLIMIT_MAX,
    "opslimit must be at most 'crypto_pwhash_OPSLIMIT_MAX'"
  )
  assert(
    memlimit >= binding.crypto_pwhash_MEMLIMIT_MIN,
    "memlimit must be at least 'crypto_pwhash_MEMLIMIT_MIN'"
  )
  assert(
    memlimit <= binding.crypto_pwhash_MEMLIMIT_MAX,
    "memlimit must be at most 'crypto_pwhash_MEMLIMIT_MAX'"
  )
  assert(alg >= 1 && alg <= 2, 'alg must be either Argon2i 1.3 or Argon2id 1.3')

  const res = binding.crypto_pwhash(out, passwd, salt, opslimit, memlimit, alg)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_pwhash_async = function (
  out,
  passwd,
  salt,
  opslimit,
  memlimit,
  alg,
  callback = undefined
) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(ArrayBuffer.isView(salt), 'salt must be a typed array')
  assert(
    out.byteLength >= binding.crypto_pwhash_BYTES_MIN,
    "out must be at least 'crypto_pwhash_BYTES_MIN' bytes"
  )
  assert(
    out.byteLength <= binding.crypto_pwhash_BYTES_MAX,
    "out must be at most 'crypto_pwhash_BYTES_MAX' bytes"
  )
  assert(
    salt.byteLength === binding.crypto_pwhash_SALTBYTES,
    "salt must be 'crypto_pwhash_SALTBYTES' bytes"
  )
  assert(
    opslimit >= binding.crypto_pwhash_OPSLIMIT_MIN,
    "opslimit must be at least 'crypto_pwhash_OPSLIMIT_MIN'"
  )
  assert(
    opslimit <= binding.crypto_pwhash_OPSLIMIT_MAX,
    "opslimit must be at most 'crypto_pwhash_OPSLIMIT_MAX'"
  )
  assert(
    memlimit >= binding.crypto_pwhash_MEMLIMIT_MIN,
    "memlimit must be at least 'crypto_pwhash_MEMLIMIT_MIN'"
  )
  assert(
    memlimit <= binding.crypto_pwhash_MEMLIMIT_MAX,
    "memlimit must be at most 'crypto_pwhash_MEMLIMIT_MAX'"
  )
  assert(alg >= 1 && alg <= 2, 'alg must be either Argon2i 1.3 or Argon2id 1.3')

  const [done, promise] = checkStatus(callback)

  binding.crypto_pwhash_async(
    out.buffer,
    out.byteOffset,
    out.byteLength,

    passwd.buffer,
    passwd.byteOffset,
    passwd.byteLength,

    salt.buffer,
    salt.byteOffset,
    salt.byteLength,

    opslimit,
    memlimit,
    alg,

    done
  )

  return promise
}

exports.crypto_pwhash_str = function (out, passwd, opslimit, memlimit) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(
    out.byteLength === binding.crypto_pwhash_STRBYTES,
    "out must be 'crypto_pwhash_STRBYTES' bytes"
  )
  assert(typeof opslimit === 'number', 'opslimit must be a number')
  assert(
    opslimit >= binding.crypto_pwhash_OPSLIMIT_MIN,
    "opslimit must be at least 'crypto_pwhash_OPSLIMIT_MIN'"
  )
  assert(
    opslimit <= binding.crypto_pwhash_OPSLIMIT_MAX,
    "opslimit must be at most 'crypto_pwhash_OPSLIMIT_MAX'"
  )
  assert(typeof memlimit === 'number', 'memlimit must be a number')
  assert(
    memlimit >= binding.crypto_pwhash_MEMLIMIT_MIN,
    "memlimit must be at least 'crypto_pwhash_MEMLIMIT_MIN'"
  )
  assert(
    memlimit <= binding.crypto_pwhash_MEMLIMIT_MAX,
    "memlimit must be at most 'crypto_pwhash_MEMLIMIT_MAX'"
  )

  const res = binding.crypto_pwhash_str(out, passwd, opslimit, memlimit)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_pwhash_str_async = function (out, passwd, opslimit, memlimit, callback = undefined) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(
    out.byteLength === binding.crypto_pwhash_STRBYTES,
    "out must be 'crypto_pwhash_STRBYTES' bytes"
  )
  assert(passwd.byteLength > 0, 'passwd must not be empty')
  assert(typeof opslimit === 'number', 'opslimit must be a number')
  assert(
    opslimit >= binding.crypto_pwhash_OPSLIMIT_MIN,
    "opslimit must be at least 'crypto_pwhash_OPSLIMIT_MIN'"
  )
  assert(
    opslimit <= binding.crypto_pwhash_OPSLIMIT_MAX,
    "opslimit must be at most 'crypto_pwhash_OPSLIMIT_MAX'"
  )
  assert(typeof memlimit === 'number', 'memlimit must be a number')
  assert(
    memlimit >= binding.crypto_pwhash_MEMLIMIT_MIN,
    "memlimit must be at least 'crypto_pwhash_MEMLIMIT_MIN'"
  )
  assert(
    memlimit <= binding.crypto_pwhash_MEMLIMIT_MAX,
    "memlimit must be at most 'crypto_pwhash_MEMLIMIT_MAX'"
  )

  const [done, promise] = checkStatus(callback)

  binding.crypto_pwhash_str_async(
    out.buffer,
    out.byteOffset,
    out.byteLength,

    passwd.buffer,
    passwd.byteOffset,
    passwd.byteLength,

    opslimit,
    memlimit,

    done
  )

  return promise
}

exports.crypto_pwhash_str_verify = function (str, passwd) {
  assert(ArrayBuffer.isView(str), 'str must be a typed array')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(
    str.byteLength === binding.crypto_pwhash_STRBYTES,
    "str must be 'crypto_pwhash_STRBYTES' bytes"
  )

  return binding.crypto_pwhash_str_verify(str, passwd)
}

exports.crypto_pwhash_str_verify_async = function (str, passwd, callback = undefined) {
  assert(ArrayBuffer.isView(str), 'str must be a typed array')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(
    str.byteLength === binding.crypto_pwhash_STRBYTES,
    "str must be 'crypto_pwhash_STRBYTES' bytes"
  )
  assert(passwd.byteLength > 0, 'passwd must not be empty')

  const [done, promise] = checkStatus(callback, true)

  binding.crypto_pwhash_str_verify_async(
    str.buffer,
    str.byteOffset,
    str.byteLength,

    passwd.buffer,
    passwd.byteOffset,
    passwd.byteLength,

    done
  )

  return promise
}

exports.crypto_pwhash_str_needs_rehash = function (str, opslimit, memlimit) {
  assert(ArrayBuffer.isView(str), 'str must be a typed array')
  assert(
    str.byteLength === binding.crypto_pwhash_STRBYTES,
    "str must be 'crypto_pwhash_STRBYTES' bytes"
  )
  assert(
    opslimit >= binding.crypto_pwhash_OPSLIMIT_MIN,
    "opslimit must be at least 'crypto_pwhash_OPSLIMIT_MIN'"
  )
  assert(
    opslimit <= binding.crypto_pwhash_OPSLIMIT_MAX,
    "opslimit must be at most 'crypto_pwhash_OPSLIMIT_MAX'"
  )
  assert(
    memlimit >= binding.crypto_pwhash_MEMLIMIT_MIN,
    "memlimit must be at least 'crypto_pwhash_MEMLIMIT_MIN'"
  )
  assert(
    memlimit <= binding.crypto_pwhash_MEMLIMIT_MAX,
    "memlimit must be at most 'crypto_pwhash_MEMLIMIT_MAX'"
  )

  return binding.crypto_pwhash_str_needs_rehash(str, opslimit, memlimit)
}

exports.crypto_pwhash_scryptsalsa208sha256 = function (out, passwd, salt, opslimit, memlimit) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(ArrayBuffer.isView(salt), 'salt must be a typed array')
  assert(
    out.byteLength >= binding.crypto_pwhash_scryptsalsa208sha256_BYTES_MIN,
    "out must be at least 'crypto_pwhash_scryptsalsa208sha256_BYTES_MIN' bytes"
  )
  assert(
    out.byteLength <= binding.crypto_pwhash_scryptsalsa208sha256_BYTES_MAX,
    "out must be at most 'crypto_pwhash_scryptsalsa208sha256_BYTES_MAX' bytes"
  )
  assert(
    salt.byteLength === binding.crypto_pwhash_scryptsalsa208sha256_SALTBYTES,
    "salt must be 'crypto_pwhash_scryptsalsa208sha256_SALTBYTES' bytes"
  )
  assert(
    opslimit >= binding.crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MIN,
    "opslimit must be at least 'crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MIN'"
  )
  assert(
    opslimit <= binding.crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MAX,
    "opslimit must be at most 'crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MAX'"
  )
  assert(
    memlimit >= binding.crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MIN,
    "memlimit must be at least 'crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MIN'"
  )
  assert(
    memlimit <= binding.crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MAX,
    "memlimit must be at most 'crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MAX'"
  )

  const res = binding.crypto_pwhash_scryptsalsa208sha256(out, passwd, salt, opslimit, memlimit)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_pwhash_scryptsalsa208sha256_async = function (
  out,
  passwd,
  salt,
  opslimit,
  memlimit,
  callback = undefined
) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(ArrayBuffer.isView(salt), 'salt must be a typed array')
  assert(
    out.byteLength >= binding.crypto_pwhash_scryptsalsa208sha256_BYTES_MIN,
    "out must be at least 'crypto_pwhash_scryptsalsa208sha256_BYTES_MIN' bytes"
  )
  assert(
    out.byteLength <= binding.crypto_pwhash_scryptsalsa208sha256_BYTES_MAX,
    "out must be at most 'crypto_pwhash_scryptsalsa208sha256_BYTES_MAX' bytes"
  )
  assert(passwd.byteLength > 0, 'passwd must not be empty')
  assert(
    salt.byteLength === binding.crypto_pwhash_scryptsalsa208sha256_SALTBYTES,
    "salt must be 'crypto_pwhash_scryptsalsa208sha256_SALTBYTES' bytes"
  )
  assert(
    opslimit >= binding.crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MIN,
    "opslimit must be at least 'crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MIN'"
  )
  assert(
    opslimit <= binding.crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MAX,
    "opslimit must be at most 'crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MAX'"
  )
  assert(
    memlimit >= binding.crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MIN,
    "memlimit must be at least 'crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MIN'"
  )
  assert(
    memlimit <= binding.crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MAX,
    "memlimit must be at most 'crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MAX'"
  )

  const [done, promise] = checkStatus(callback)

  binding.crypto_pwhash_scryptsalsa208sha256_async(
    out.buffer,
    out.byteOffset,
    out.byteLength,

    passwd.buffer,
    passwd.byteOffset,
    passwd.byteLength,

    salt.buffer,
    salt.byteOffset,
    salt.byteLength,

    opslimit,
    memlimit,

    done
  )

  return promise
}

exports.crypto_pwhash_scryptsalsa208sha256_str_async = function (
  out,
  passwd,
  opslimit,
  memlimit,
  callback = undefined
) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(
    out.byteLength === binding.crypto_pwhash_scryptsalsa208sha256_STRBYTES,
    "out must be 'crypto_pwhash_scryptsalsa208sha256_STRBYTES' bytes"
  )
  assert(passwd.byteLength > 0, 'passwd must not be empty')
  assert(
    opslimit >= binding.crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MIN,
    "opslimit must be at least 'crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MIN'"
  )
  assert(
    opslimit <= binding.crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MAX,
    "opslimit must be at most 'crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MAX'"
  )
  assert(
    memlimit >= binding.crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MIN,
    "memlimit must be at least 'crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MIN'"
  )
  assert(
    memlimit <= binding.crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MAX,
    "memlimit must be at most 'crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MAX'"
  )

  const [done, promise] = checkStatus(callback)

  binding.crypto_pwhash_scryptsalsa208sha256_str_async(
    out.buffer,
    out.byteOffset,
    out.byteLength,

    passwd.buffer,
    passwd.byteOffset,
    passwd.byteLength,

    opslimit,
    memlimit,

    done
  )

  return promise
}

exports.crypto_pwhash_scryptsalsa208sha256_str = function (out, passwd, opslimit, memlimit) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(
    out.byteLength === binding.crypto_pwhash_scryptsalsa208sha256_STRBYTES,
    "out must be 'crypto_pwhash_scryptsalsa208sha256_STRBYTES' bytes"
  )
  assert(passwd.byteLength > 0, 'passwd must not be empty')
  assert(
    opslimit >= binding.crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MIN,
    "opslimit must be at least 'crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MIN'"
  )
  assert(
    opslimit <= binding.crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MAX,
    "opslimit must be at most 'crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MAX'"
  )
  assert(
    memlimit >= binding.crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MIN,
    "memlimit must be at least 'crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MIN'"
  )
  assert(
    memlimit <= binding.crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MAX,
    "memlimit must be at most 'crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MAX'"
  )

  const res = binding.crypto_pwhash_scryptsalsa208sha256_str(out, passwd, opslimit, memlimit)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_pwhash_scryptsalsa208sha256_str_verify_async = function (
  str,
  passwd,
  callback = undefined
) {
  assert(ArrayBuffer.isView(str), 'str must be a typed array')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(
    str.byteLength === binding.crypto_pwhash_scryptsalsa208sha256_STRBYTES,
    "str must be 'crypto_pwhash_scryptsalsa208sha256_STRBYTES' bytes"
  )
  assert(passwd.byteLength > 0, 'passwd must not be empty')

  const [done, promise] = checkStatus(callback, true)

  binding.crypto_pwhash_scryptsalsa208sha256_str_verify_async(
    str.buffer,
    str.byteOffset,
    str.byteLength,

    passwd.buffer,
    passwd.byteOffset,
    passwd.byteLength,

    done
  )

  return promise
}

exports.crypto_pwhash_scryptsalsa208sha256_str_verify = function (str, passwd) {
  assert(ArrayBuffer.isView(str), 'str must be a typed array')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(
    str.byteLength === binding.crypto_pwhash_scryptsalsa208sha256_STRBYTES,
    "str must be 'crypto_pwhash_scryptsalsa208sha256_STRBYTES' bytes"
  )
  assert(passwd.byteLength > 0, 'passwd must not be empty')

  return binding.crypto_pwhash_scryptsalsa208sha256_str_verify(str, passwd)
}

exports.crypto_pwhash_scryptsalsa208sha256_str_needs_rehash = function (str, opslimit, memlimit) {
  assert(ArrayBuffer.isView(str), 'str must be a typed array')
  assert(
    str.byteLength === binding.crypto_pwhash_scryptsalsa208sha256_STRBYTES,
    "str must be 'crypto_pwhash_scryptsalsa208sha256_STRBYTES' bytes"
  )
  assert(
    opslimit >= binding.crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MIN,
    "opslimit must be at least 'crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MIN'"
  )
  assert(
    opslimit <= binding.crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MAX,
    "opslimit must be at most 'crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MAX'"
  )
  assert(
    memlimit >= binding.crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MIN,
    "memlimit must be at least 'crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MIN'"
  )
  assert(
    memlimit <= binding.crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MAX,
    "memlimit must be at most 'crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MAX'"
  )

  return binding.crypto_pwhash_scryptsalsa208sha256_str_needs_rehash(str, opslimit, memlimit)
}

// crypto_kx

exports.crypto_kx_keypair = function (pk, sk) {
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(
    pk.byteLength === binding.crypto_kx_PUBLICKEYBYTES,
    "pk must be 'crypto_kx_PUBLICKEYBYTES' bytes"
  )
  assert(
    sk.byteLength === binding.crypto_kx_SECRETKEYBYTES,
    "sk must be 'crypto_kx_SECRETKEYBYTES' bytes"
  )

  const res = binding.crypto_kx_keypair(pk, sk)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_kx_seed_keypair = function (pk, sk, seed) {
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(ArrayBuffer.isView(seed), 'seed must be a typed array')
  assert(
    pk.byteLength === binding.crypto_kx_PUBLICKEYBYTES,
    "pk must be 'crypto_kx_PUBLICKEYBYTES' bytes"
  )
  assert(
    sk.byteLength === binding.crypto_kx_SECRETKEYBYTES,
    "sk must be 'crypto_kx_SECRETKEYBYTES' bytes"
  )
  assert(
    seed.byteLength === binding.crypto_kx_SEEDBYTES,
    "seed must be 'crypto_kx_SEEDBYTES' bytes"
  )

  const res = binding.crypto_kx_seed_keypair(pk, sk, seed)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_kx_client_session_keys = function (rx, tx, clientPk, clientSk, serverPk) {
  if (!rx) rx = undefined
  if (!tx) tx = undefined

  assert(rx || tx, 'at least one session key must be specified')

  if (rx) {
    assert(ArrayBuffer.isView(rx), 'rx must be a typed array')
    assert(
      rx.byteLength === binding.crypto_kx_SESSIONKEYBYTES,
      "rx must be 'crypto_kx_SESSIONKEYBYTES' bytes"
    )
  }

  if (tx) {
    assert(ArrayBuffer.isView(tx), 'tx must be a typed array')
    assert(
      tx.byteLength === binding.crypto_kx_SESSIONKEYBYTES,
      "tx must be 'crypto_kx_SESSIONKEYBYTES' bytes"
    )
  }

  assert(ArrayBuffer.isView(clientPk), 'clientPk must be a typed array')
  assert(ArrayBuffer.isView(clientSk), 'clientSk must be a typed array')
  assert(ArrayBuffer.isView(serverPk), 'serverPk must be a typed array')
  assert(
    clientPk.byteLength === binding.crypto_kx_PUBLICKEYBYTES,
    "clientPk must be 'crypto_kx_PUBLICKEYBYTES' bytes"
  )
  assert(
    clientSk.byteLength === binding.crypto_kx_SECRETKEYBYTES,
    "clientSk must be 'crypto_kx_SECRETKEYBYTES' bytes"
  )
  assert(
    serverPk.byteLength === binding.crypto_kx_PUBLICKEYBYTES,
    "serverPk must be 'crypto_kx_PUBLICKEYBYTES' bytes"
  )

  const res = binding.crypto_kx_client_session_keys(rx, tx, clientPk, clientSk, serverPk)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_kx_server_session_keys = function (rx, tx, serverPk, serverSk, clientPk) {
  if (!rx) rx = undefined
  if (!tx) tx = undefined

  assert(rx || tx, 'at least one session key must be specified')

  if (rx) {
    assert(ArrayBuffer.isView(rx), 'rx must be a typed array')
    assert(
      rx.byteLength === binding.crypto_kx_SESSIONKEYBYTES,
      "rx must be 'crypto_kx_SESSIONKEYBYTES' bytes"
    )
  }

  if (tx) {
    assert(ArrayBuffer.isView(tx), 'tx must be a typed array')
    assert(
      tx.byteLength === binding.crypto_kx_SESSIONKEYBYTES,
      "tx must be 'crypto_kx_SESSIONKEYBYTES' bytes"
    )
  }

  assert(ArrayBuffer.isView(serverPk), 'serverPk must be a typed array')
  assert(ArrayBuffer.isView(serverSk), 'serverSk must be a typed array')
  assert(ArrayBuffer.isView(clientPk), 'clientPk must be a typed array')
  assert(
    serverPk.byteLength === binding.crypto_kx_PUBLICKEYBYTES,
    "serverPk must be 'crypto_kx_PUBLICKEYBYTES' bytes"
  )
  assert(
    serverSk.byteLength === binding.crypto_kx_SECRETKEYBYTES,
    "serverSk must be 'crypto_kx_SECRETKEYBYTES' bytes"
  )
  assert(
    clientPk.byteLength === binding.crypto_kx_PUBLICKEYBYTES,
    "clientPk must be 'crypto_kx_PUBLICKEYBYTES' bytes"
  )

  const res = binding.crypto_kx_server_session_keys(rx, tx, serverPk, serverSk, clientPk)

  if (res !== 0) throw new Error('status: ' + res)
}

// crypto_scalarmult

exports.crypto_scalarmult_base = function (q, n) {
  assert(ArrayBuffer.isView(q), 'q must be a typed array')
  assert(
    q.byteLength === binding.crypto_scalarmult_BYTES,
    "q must be 'crypto_scalarmult_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.crypto_scalarmult_SCALARBYTES,
    "n must be 'crypto_scalarmult_SCALARBYTES' bytes"
  )

  const res = binding.crypto_scalarmult_base(q, n)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_scalarmult = function (q, n, p) {
  assert(ArrayBuffer.isView(q), 'q must be a typed array')
  assert(
    q.byteLength === binding.crypto_scalarmult_BYTES,
    "q must be 'crypto_scalarmult_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.crypto_scalarmult_SCALARBYTES,
    "n must be 'crypto_scalarmult_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(p), 'p must be a typed array')
  assert(
    p.byteLength === binding.crypto_scalarmult_BYTES,
    "p must be 'crypto_scalarmult_BYTES' bytes"
  )

  const res = binding.crypto_scalarmult(q, n, p)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_scalarmult_ed25519_base = function (q, n) {
  assert(ArrayBuffer.isView(q), 'q must be a typed array')
  assert(
    q.byteLength === binding.crypto_scalarmult_ed25519_BYTES,
    "q must be 'crypto_scalarmult_ed25519_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.crypto_scalarmult_ed25519_SCALARBYTES,
    "n must be 'crypto_scalarmult_ed25519_SCALARBYTES' bytes"
  )

  const res = binding.crypto_scalarmult_ed25519_base(q, n)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_scalarmult_ed25519 = function (q, n, p) {
  assert(ArrayBuffer.isView(q), 'q must be a typed array')
  assert(
    q.byteLength === binding.crypto_scalarmult_ed25519_BYTES,
    "q must be 'crypto_scalarmult_ed25519_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.crypto_scalarmult_ed25519_SCALARBYTES,
    "n must be 'crypto_scalarmult_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(p), 'p must be a typed array')
  assert(
    p.byteLength === binding.crypto_scalarmult_ed25519_BYTES,
    "p must be 'crypto_scalarmult_ed25519_BYTES' bytes"
  )

  const res = binding.crypto_scalarmult_ed25519(q, n, p)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_core_ed25519_is_valid_point = function (p) {
  assert(ArrayBuffer.isView(p), 'p must be a typed array')
  assert(
    p.byteLength === binding.crypto_core_ed25519_BYTES,
    "p must be 'crypto_core_ed25519_BYTES' bytes"
  )

  return binding.crypto_core_ed25519_is_valid_point(p)
}

exports.crypto_core_ed25519_from_uniform = function (p, r) {
  assert(ArrayBuffer.isView(p), 'p must be a typed array')
  assert(
    p.byteLength === binding.crypto_core_ed25519_BYTES,
    "p must be 'crypto_core_ed25519_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(r), 'r must be a typed array')
  assert(
    r.byteLength === binding.crypto_core_ed25519_UNIFORMBYTES,
    "r must be 'crypto_core_ed25519_UNIFORMBYTES' bytes"
  )

  const res = binding.crypto_core_ed25519_from_uniform(p, r)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_scalarmult_ed25519_base_noclamp = function (q, n) {
  assert(ArrayBuffer.isView(q), 'q must be a typed array')
  assert(
    q.byteLength === binding.crypto_scalarmult_ed25519_BYTES,
    "q must be 'crypto_scalarmult_ed25519_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.crypto_scalarmult_ed25519_SCALARBYTES,
    "n must be 'crypto_scalarmult_ed25519_SCALARBYTES' bytes"
  )

  const res = binding.crypto_scalarmult_ed25519_base_noclamp(q, n)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_scalarmult_ed25519_noclamp = function (q, n, p) {
  assert(ArrayBuffer.isView(q), 'q must be a typed array')
  assert(
    q.byteLength === binding.crypto_scalarmult_ed25519_BYTES,
    "q must be 'crypto_scalarmult_ed25519_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.crypto_scalarmult_ed25519_SCALARBYTES,
    "n must be 'crypto_scalarmult_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(p), 'p must be a typed array')
  assert(
    p.byteLength === binding.crypto_scalarmult_ed25519_BYTES,
    "p must be 'crypto_scalarmult_ed25519_BYTES' bytes"
  )

  const res = binding.crypto_scalarmult_ed25519_noclamp(q, n, p)

  if (res !== 0) throw new Error('status: ' + res)
}

// crypto_core

exports.crypto_core_ed25519_add = function (r, p, q) {
  assert(ArrayBuffer.isView(r), 'r must be a typed array')
  assert(
    r.byteLength === binding.crypto_core_ed25519_BYTES,
    "r must be 'crypto_core_ed25519_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(p), 'p must be a typed array')
  assert(
    p.byteLength === binding.crypto_core_ed25519_BYTES,
    "p must be 'crypto_core_ed25519_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(q), 'q must be a typed array')
  assert(
    q.byteLength === binding.crypto_core_ed25519_BYTES,
    "q must be 'crypto_core_ed25519_BYTES' bytes"
  )

  const res = binding.crypto_core_ed25519_add(r, p, q)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_core_ed25519_sub = function (r, p, q) {
  assert(ArrayBuffer.isView(r), 'r must be a typed array')
  assert(
    r.byteLength === binding.crypto_core_ed25519_BYTES,
    "r must be 'crypto_core_ed25519_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(p), 'p must be a typed array')
  assert(
    p.byteLength === binding.crypto_core_ed25519_BYTES,
    "p must be 'crypto_core_ed25519_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(q), 'q must be a typed array')
  assert(
    q.byteLength === binding.crypto_core_ed25519_BYTES,
    "q must be 'crypto_core_ed25519_BYTES' bytes"
  )

  const res = binding.crypto_core_ed25519_sub(r, p, q)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_core_ed25519_scalar_random = function (r) {
  assert(ArrayBuffer.isView(r), 'r must be a typed array')
  assert(
    r.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "r must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )

  binding.crypto_core_ed25519_scalar_random(r)
}

exports.crypto_core_ed25519_scalar_reduce = function (r, s) {
  assert(ArrayBuffer.isView(r), 'r must be a typed array')
  assert(
    r.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "r must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(s), 's must be a typed array')
  assert(
    s.byteLength === binding.crypto_core_ed25519_NONREDUCEDSCALARBYTES,
    "s must be 'crypto_core_ed25519_NONREDUCEDSCALARBYTES' bytes"
  )

  binding.crypto_core_ed25519_scalar_reduce(r, s)
}

exports.crypto_core_ed25519_scalar_invert = function (recip, s) {
  assert(ArrayBuffer.isView(recip), 'recip must be a typed array')
  assert(
    recip.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "recip must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(s), 's must be a typed array')
  assert(
    s.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "s must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )

  binding.crypto_core_ed25519_scalar_invert(recip, s)
}

exports.crypto_core_ed25519_scalar_negate = function (neg, s) {
  assert(ArrayBuffer.isView(neg), 'neg must be a typed array')
  assert(
    neg.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "neg must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(s), 's must be a typed array')
  assert(
    s.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "s must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )

  binding.crypto_core_ed25519_scalar_negate(neg, s)
}

exports.crypto_core_ed25519_scalar_complement = function (comp, s) {
  assert(ArrayBuffer.isView(comp), 'comp must be a typed array')
  assert(
    comp.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "comp must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(s), 's must be a typed array')
  assert(
    s.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "s must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )

  binding.crypto_core_ed25519_scalar_complement(comp, s)
}

exports.crypto_core_ed25519_scalar_add = function (z, x, y) {
  assert(ArrayBuffer.isView(z), 'z must be a typed array')
  assert(
    z.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "z must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(x), 'x must be a typed array')
  assert(
    x.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "x must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(y), 'y must be a typed array')
  assert(
    y.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "y must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )

  binding.crypto_core_ed25519_scalar_add(z, x, y)
}

exports.crypto_core_ed25519_scalar_sub = function (z, x, y) {
  assert(ArrayBuffer.isView(z), 'z must be a typed array')
  assert(
    z.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "z must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(x), 'x must be a typed array')
  assert(
    x.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "x must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(y), 'y must be a typed array')
  assert(
    y.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "y must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )

  binding.crypto_core_ed25519_scalar_sub(z, x, y)
}

// crypto_shorthash

exports.crypto_shorthash = function (out, input, k) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(
    out.byteLength === binding.crypto_shorthash_BYTES,
    "out must be 'crypto_shorthash_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_shorthash_KEYBYTES,
    "k must be 'crypto_shorthash_KEYBYTES' bytes"
  )

  const res = binding.crypto_shorthash(out, input, k)

  if (res !== 0) throw new Error('status: ' + res)
}

// crypto_kdf

exports.crypto_kdf_keygen = function (key) {
  assert(ArrayBuffer.isView(key), 'key must be a typed array')
  assert(key.byteLength === binding.crypto_kdf_KEYBYTES, "key must be 'crypto_kdf_KEYBYTES' bytes")

  binding.crypto_kdf_keygen(key)
}

exports.crypto_kdf_derive_from_key = function (subkey, subkeyId, ctx, key) {
  assert(ArrayBuffer.isView(subkey), 'subkey must be a typed array')
  assert(
    subkey.byteLength >= binding.crypto_kdf_BYTES_MIN,
    "subkey must be at least 'crypto_kdf_BYTES_MIN' bytes"
  )
  assert(
    subkey.byteLength <= binding.crypto_kdf_BYTES_MAX,
    "subkey must be at most 'crypto_kdf_BYTES_MAX' bytes"
  )
  assert(ArrayBuffer.isView(ctx), 'ctx must be a typed array')
  assert(
    ctx.byteLength === binding.crypto_kdf_CONTEXTBYTES,
    "ctx must be 'crypto_kdf_CONTEXTBYTES' bytes"
  )
  assert(ArrayBuffer.isView(key), 'key must be a typed array')
  assert(key.byteLength === binding.crypto_kdf_KEYBYTES, "key must be 'crypto_kdf_KEYBYTES' bytes")

  const res = binding.crypto_kdf_derive_from_key(subkey, subkeyId, ctx, key)

  if (res !== 0) throw new Error('status: ' + res)
}

// crypto_hash

exports.crypto_hash = function (out, input) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(out.byteLength === binding.crypto_hash_BYTES, "out must be 'crypto_hash_BYTES' bytes")

  const res = binding.crypto_hash(out, input)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_hash_sha256 = function (out, input) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(
    out.byteLength === binding.crypto_hash_sha256_BYTES,
    "out must be 'crypto_hash_sha256_BYTES' bytes"
  )

  const res = binding.crypto_hash_sha256(out, input)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_hash_sha256_init = function (state) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_hash_sha256_STATEBYTES,
    "state must be 'crypto_hash_sha256_STATEBYTES' bytes"
  )

  const res = binding.crypto_hash_sha256_init(state)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_hash_sha256_update = function (state, input) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(
    state.byteLength === binding.crypto_hash_sha256_STATEBYTES,
    "state must be 'crypto_hash_sha256_STATEBYTES' bytes"
  )

  const res = binding.crypto_hash_sha256_update(state, input)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_hash_sha256_final = function (state, out) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_hash_sha256_STATEBYTES,
    "state must be 'crypto_hash_sha256_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(
    out.byteLength === binding.crypto_hash_sha256_BYTES,
    "out must be 'crypto_hash_sha256_BYTES' bytes"
  )

  const res = binding.crypto_hash_sha256_final(state, out)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_hash_sha512 = function (out, input) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(
    out.byteLength === binding.crypto_hash_sha512_BYTES,
    "out must be 'crypto_hash_sha512_BYTES' bytes"
  )

  const res = binding.crypto_hash_sha512(out, input)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_hash_sha512_init = function (state) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_hash_sha512_STATEBYTES,
    "state must be 'crypto_hash_sha512_STATEBYTES' bytes"
  )

  const res = binding.crypto_hash_sha512_init(state)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_hash_sha512_update = function (state, input) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(
    state.byteLength === binding.crypto_hash_sha512_STATEBYTES,
    "state must be 'crypto_hash_sha512_STATEBYTES' bytes"
  )

  const res = binding.crypto_hash_sha512_update(state, input)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_hash_sha512_final = function (state, out) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_hash_sha512_STATEBYTES,
    "state must be 'crypto_hash_sha512_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(
    out.byteLength === binding.crypto_hash_sha512_BYTES,
    "out must be 'crypto_hash_sha512_BYTES' bytes"
  )

  const res = binding.crypto_hash_sha512_final(state, out)

  if (res !== 0) throw new Error('status: ' + res)
}

// crypto_aead

exports.crypto_aead_xchacha20poly1305_ietf_keygen = function (k) {
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_aead_xchacha20poly1305_ietf_KEYBYTES,
    "k must be 'crypto_aead_xchacha20poly1305_ietf_KEYBYTES' bytes"
  )

  binding.crypto_aead_xchacha20poly1305_ietf_keygen(k)
}

exports.crypto_aead_xchacha20poly1305_ietf_encrypt = function (c, m, ad, nsec, npub, k) {
  if (!ad) ad = undefined

  assert(nsec === null, 'nsec must always be set to null')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(
    c.byteLength === m.byteLength + binding.crypto_aead_xchacha20poly1305_ietf_ABYTES,
    "c must be 'm.byteLength + crypto_aead_xchacha20poly1305_ietf_ABYTES' bytes"
  )
  assert(c.byteLength <= 0xffffffff, 'c.byteLength must be a 32bit integer')
  assert(ArrayBuffer.isView(npub), 'npub must be a typed array')
  assert(
    npub.byteLength === binding.crypto_aead_xchacha20poly1305_ietf_NPUBBYTES,
    "npub must be 'crypto_aead_xchacha20poly1305_ietf_NPUBBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_aead_xchacha20poly1305_ietf_KEYBYTES,
    "k must be 'crypto_aead_xchacha20poly1305_ietf_KEYBYTES' bytes"
  )

  const res = binding.crypto_aead_xchacha20poly1305_ietf_encrypt(c, m, ad, npub, k)

  if (res < 0) throw new Error('could not encrypt data')

  return res
}

exports.crypto_aead_xchacha20poly1305_ietf_decrypt = function (m, nsec, c, ad, npub, k) {
  if (!ad) ad = undefined

  assert(nsec === null, 'nsec must always be set to null')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(
    m.byteLength === c.byteLength - binding.crypto_aead_xchacha20poly1305_ietf_ABYTES,
    "m must be 'c.byteLength - crypto_aead_xchacha20poly1305_ietf_ABYTES' bytes"
  )
  assert(m.byteLength <= 0xffffffff, 'm.byteLength must be a 32bit integer')
  assert(ArrayBuffer.isView(npub), 'npub must be a typed array')
  assert(
    npub.byteLength === binding.crypto_aead_xchacha20poly1305_ietf_NPUBBYTES,
    "npub must be 'crypto_aead_xchacha20poly1305_ietf_NPUBBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_aead_xchacha20poly1305_ietf_KEYBYTES,
    "k must be 'crypto_aead_xchacha20poly1305_ietf_KEYBYTES' bytes"
  )

  const res = binding.crypto_aead_xchacha20poly1305_ietf_decrypt(m, c, ad, npub, k)

  if (res < 0) throw new Error('could not verify data')

  return res
}

exports.crypto_aead_xchacha20poly1305_ietf_encrypt_detached = function (
  c,
  mac,
  m,
  ad,
  nsec,
  npub,
  k
) {
  if (!ad) ad = undefined

  assert(nsec === null, 'nsec must always be set to null')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(ArrayBuffer.isView(mac), 'mac must be a typed array')
  assert(
    mac.byteLength === binding.crypto_aead_xchacha20poly1305_ietf_ABYTES,
    "mac must be 'crypto_aead_xchacha20poly1305_ietf_ABYTES' bytes"
  )
  assert(ArrayBuffer.isView(npub), 'npub must be a typed array')
  assert(
    npub.byteLength === binding.crypto_aead_xchacha20poly1305_ietf_NPUBBYTES,
    "npub must be 'crypto_aead_xchacha20poly1305_ietf_NPUBBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_aead_xchacha20poly1305_ietf_KEYBYTES,
    "k must be 'crypto_aead_xchacha20poly1305_ietf_KEYBYTES' bytes"
  )

  const res = binding.crypto_aead_xchacha20poly1305_ietf_encrypt_detached(c, mac, m, ad, npub, k)

  if (res < 0) throw new Error('could not encrypt data')

  return res
}

exports.crypto_aead_xchacha20poly1305_ietf_decrypt_detached = function (
  m,
  nsec,
  c,
  mac,
  ad,
  npub,
  k
) {
  if (!ad) ad = undefined

  assert(nsec === null, 'nsec must always be set to null')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(m.byteLength === c.byteLength, "m must be 'c.byteLength' bytes")
  assert(ArrayBuffer.isView(mac), 'mac must be a typed array')
  assert(
    mac.byteLength === binding.crypto_aead_xchacha20poly1305_ietf_ABYTES,
    "mac must be 'crypto_aead_xchacha20poly1305_ietf_ABYTES' bytes"
  )
  assert(ArrayBuffer.isView(npub), 'npub must be a typed array')
  assert(
    npub.byteLength === binding.crypto_aead_xchacha20poly1305_ietf_NPUBBYTES,
    "npub must be 'crypto_aead_xchacha20poly1305_ietf_NPUBBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_aead_xchacha20poly1305_ietf_KEYBYTES,
    "k must be 'crypto_aead_xchacha20poly1305_ietf_KEYBYTES' bytes"
  )

  const res = binding.crypto_aead_xchacha20poly1305_ietf_decrypt_detached(m, c, mac, ad, npub, k)

  if (res !== 0) throw new Error('could not verify data')
}

exports.crypto_aead_chacha20poly1305_ietf_keygen = function (k) {
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_aead_chacha20poly1305_ietf_KEYBYTES,
    "k must be 'crypto_aead_chacha20poly1305_ietf_KEYBYTES' bytes"
  )

  binding.crypto_aead_chacha20poly1305_ietf_keygen(k)
}

exports.crypto_aead_chacha20poly1305_ietf_encrypt = function (c, m, ad, nsec, npub, k) {
  if (!ad) ad = undefined

  assert(nsec === null, 'nsec must always be set to null')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(
    c.byteLength === m.byteLength + binding.crypto_aead_chacha20poly1305_ietf_ABYTES,
    "c must be 'm.byteLength + crypto_aead_chacha20poly1305_ietf_ABYTES' bytes"
  )
  assert(c.byteLength <= 0xffffffff, 'c.byteLength must be a 32bit integer')
  assert(ArrayBuffer.isView(npub), 'npub must be a typed array')
  assert(
    npub.byteLength === binding.crypto_aead_chacha20poly1305_ietf_NPUBBYTES,
    "npub must be 'crypto_aead_chacha20poly1305_ietf_NPUBBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_aead_chacha20poly1305_ietf_KEYBYTES,
    "k must be 'crypto_aead_chacha20poly1305_ietf_KEYBYTES' bytes"
  )

  const res = binding.crypto_aead_chacha20poly1305_ietf_encrypt(c, m, ad, npub, k)

  if (res < 0) throw new Error('could not encrypt data')

  return res
}

exports.crypto_aead_chacha20poly1305_ietf_decrypt = function (m, nsec, c, ad, npub, k) {
  if (!ad) ad = undefined

  assert(nsec === null, 'nsec must always be set to null')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(
    m.byteLength === c.byteLength - binding.crypto_aead_chacha20poly1305_ietf_ABYTES,
    "m must be 'c.byteLength - crypto_aead_chacha20poly1305_ietf_ABYTES' bytes"
  )
  assert(m.byteLength <= 0xffffffff, 'm.byteLength must be a 32bit integer')
  assert(ArrayBuffer.isView(npub), 'npub must be a typed array')
  assert(
    npub.byteLength === binding.crypto_aead_chacha20poly1305_ietf_NPUBBYTES,
    "npub must be 'crypto_aead_chacha20poly1305_ietf_NPUBBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_aead_chacha20poly1305_ietf_KEYBYTES,
    "k must be 'crypto_aead_chacha20poly1305_ietf_KEYBYTES' bytes"
  )

  const res = binding.crypto_aead_chacha20poly1305_ietf_decrypt(m, c, ad, npub, k)

  if (res < 0) throw new Error('could not verify data')

  return res
}

exports.crypto_aead_chacha20poly1305_ietf_encrypt_detached = function (
  c,
  mac,
  m,
  ad,
  nsec,
  npub,
  k
) {
  if (!ad) ad = undefined

  assert(nsec === null, 'nsec must always be set to null')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(ArrayBuffer.isView(mac), 'mac must be a typed array')
  assert(
    mac.byteLength === binding.crypto_aead_chacha20poly1305_ietf_ABYTES,
    "mac must be 'crypto_aead_chacha20poly1305_ietf_ABYTES' bytes"
  )
  assert(ArrayBuffer.isView(npub), 'npub must be a typed array')
  assert(
    npub.byteLength === binding.crypto_aead_chacha20poly1305_ietf_NPUBBYTES,
    "npub must be 'crypto_aead_chacha20poly1305_ietf_NPUBBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_aead_chacha20poly1305_ietf_KEYBYTES,
    "k must be 'crypto_aead_chacha20poly1305_ietf_KEYBYTES' bytes"
  )

  const res = binding.crypto_aead_chacha20poly1305_ietf_encrypt_detached(c, mac, m, ad, npub, k)

  if (res < 0) throw new Error('could not encrypt data')

  return res
}

exports.crypto_aead_chacha20poly1305_ietf_decrypt_detached = function (
  m,
  nsec,
  c,
  mac,
  ad,
  npub,
  k
) {
  if (!ad) ad = undefined

  assert(nsec === null, 'nsec must always be set to null')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(m.byteLength === c.byteLength, "m must be 'c.byteLength' bytes")
  assert(ArrayBuffer.isView(mac), 'mac must be a typed array')
  assert(
    mac.byteLength === binding.crypto_aead_chacha20poly1305_ietf_ABYTES,
    "mac must be 'crypto_aead_chacha20poly1305_ietf_ABYTES' bytes"
  )
  assert(ArrayBuffer.isView(npub), 'npub must be a typed array')
  assert(
    npub.byteLength === binding.crypto_aead_chacha20poly1305_ietf_NPUBBYTES,
    "npub must be 'crypto_aead_chacha20poly1305_ietf_NPUBBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_aead_chacha20poly1305_ietf_KEYBYTES,
    "k must be 'crypto_aead_chacha20poly1305_ietf_KEYBYTES' bytes"
  )

  const res = binding.crypto_aead_chacha20poly1305_ietf_decrypt_detached(m, c, mac, ad, npub, k)

  if (res !== 0) throw new Error('could not verify data')
}

// crypto_stream

exports.crypto_stream_xor_wrap_init = function (state, n, k) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.sn_crypto_stream_xor_STATEBYTES,
    "state must be 'sn_crypto_stream_xor_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.crypto_stream_NONCEBYTES,
    "n must be 'crypto_stream_NONCEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_stream_KEYBYTES,
    "k must be 'crypto_stream_KEYBYTES' bytes"
  )

  binding.crypto_stream_xor_wrap_init(state, n, k)
}

exports.crypto_stream_xor_wrap_update = function (state, c, m) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.sn_crypto_stream_xor_STATEBYTES,
    "state must be 'sn_crypto_stream_xor_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")

  binding.crypto_stream_xor_wrap_update(state, c, m)
}

exports.crypto_stream_xor_wrap_final = function (state) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.sn_crypto_stream_xor_STATEBYTES,
    "state must be 'sn_crypto_stream_xor_STATEBYTES' bytes"
  )

  binding.crypto_stream_xor_wrap_final(state)
}

exports.crypto_stream_chacha20_xor_wrap_init = function (state, n, k) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_stream_chacha20_xor_STATEBYTES,
    "state must be 'crypto_stream_chacha20_xor_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.crypto_stream_chacha20_NONCEBYTES,
    "n must be 'crypto_stream_chacha20_NONCEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_stream_chacha20_KEYBYTES,
    "k must be 'crypto_stream_chacha20_KEYBYTES' bytes"
  )

  binding.crypto_stream_chacha20_xor_wrap_init(state, n, k)
}

exports.crypto_stream_chacha20_xor_wrap_update = function (state, c, m) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_stream_chacha20_xor_STATEBYTES,
    "state must be 'crypto_stream_chacha20_xor_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")

  binding.crypto_stream_chacha20_xor_wrap_update(state, c, m)
}

exports.crypto_stream_chacha20_xor_wrap_final = function (state) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_stream_chacha20_xor_STATEBYTES,
    "state must be 'crypto_stream_chacha20_xor_STATEBYTES' bytes"
  )

  binding.crypto_stream_chacha20_xor_wrap_final(state)
}

exports.crypto_stream_chacha20_ietf_xor_wrap_init = function (state, n, k) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_stream_chacha20_ietf_xor_STATEBYTES,
    "state must be 'crypto_stream_chacha20_ietf_xor_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.crypto_stream_chacha20_ietf_NONCEBYTES,
    "n must be 'crypto_stream_chacha20_ietf_NONCEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_stream_chacha20_ietf_KEYBYTES,
    "k must be 'crypto_stream_chacha20_ietf_KEYBYTES' bytes"
  )

  binding.crypto_stream_chacha20_ietf_xor_wrap_init(state, n, k)
}

exports.crypto_stream_chacha20_ietf_xor_wrap_update = function (state, c, m) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_stream_chacha20_ietf_xor_STATEBYTES,
    "state must be 'crypto_stream_chacha20_ietf_xor_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")

  binding.crypto_stream_chacha20_ietf_xor_wrap_update(state, c, m)
}

exports.crypto_stream_chacha20_ietf_xor_wrap_final = function (state) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_stream_chacha20_ietf_xor_STATEBYTES,
    "state must be 'crypto_stream_chacha20_ietf_xor_STATEBYTES' bytes"
  )

  binding.crypto_stream_chacha20_ietf_xor_wrap_final(state)
}

exports.crypto_stream_xchacha20_xor_wrap_init = function (state, n, k) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_stream_xchacha20_xor_STATEBYTES,
    "state must be 'crypto_stream_xchacha20_xor_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.crypto_stream_xchacha20_NONCEBYTES,
    "n must be 'crypto_stream_xchacha20_NONCEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_stream_xchacha20_KEYBYTES,
    "k must be 'crypto_stream_xchacha20_KEYBYTES' bytes"
  )

  binding.crypto_stream_xchacha20_xor_wrap_init(state, n, k)
}

exports.crypto_stream_xchacha20_xor_wrap_update = function (state, c, m) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_stream_xchacha20_xor_STATEBYTES,
    "state must be 'crypto_stream_xchacha20_xor_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")

  binding.crypto_stream_xchacha20_xor_wrap_update(state, c, m)
}

exports.crypto_stream_xchacha20_xor_wrap_final = function (state) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_stream_xchacha20_xor_STATEBYTES,
    "state must be 'crypto_stream_xchacha20_xor_STATEBYTES' bytes"
  )

  binding.crypto_stream_xchacha20_xor_wrap_final(state)
}

exports.crypto_stream_salsa20_xor_wrap_init = function (state, n, k) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_stream_salsa20_xor_STATEBYTES,
    "state must be 'crypto_stream_salsa20_xor_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.crypto_stream_salsa20_NONCEBYTES,
    "n must be 'crypto_stream_salsa20_NONCEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_stream_salsa20_KEYBYTES,
    "k must be 'crypto_stream_salsa20_KEYBYTES' bytes"
  )

  binding.crypto_stream_salsa20_xor_wrap_init(state, n, k)
}

exports.crypto_stream_salsa20_xor_wrap_update = function (state, c, m) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_stream_salsa20_xor_STATEBYTES,
    "state must be 'crypto_stream_salsa20_xor_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")

  binding.crypto_stream_salsa20_xor_wrap_update(state, c, m)
}

exports.crypto_stream_salsa20_xor_wrap_final = function (state) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_stream_salsa20_xor_STATEBYTES,
    "state must be 'crypto_stream_salsa20_xor_STATEBYTES' bytes"
  )

  binding.crypto_stream_salsa20_xor_wrap_final(state)
}

// experimental

exports.extension_tweak_ed25519_base = function (n, p, ns) {
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "n must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(p), 'p must be a typed array')
  assert(
    p.byteLength === binding.extension_tweak_ed25519_BYTES,
    "p must be 'extension_tweak_ed25519_BYTES' bytes"
  )

  binding.extension_tweak_ed25519_base(n, p, ns)
}

exports.extension_tweak_ed25519_sign_detached = function (sig, m, scalar, pk) {
  assert(ArrayBuffer.isView(sig), 'sig must be a typed array')
  assert(sig.byteLength === binding.crypto_sign_BYTES, "sig must be 'crypto_sign_BYTES' bytes")
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(scalar), 'scalar must be a typed array')
  assert(
    scalar.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "scalar must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )

  if (pk) {
    assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
    assert(
      pk.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
      "pk must be 'crypto_sign_PUBLICKEYBYTES' bytes"
    )
  }

  const res = binding.extension_tweak_ed25519_sign_detached(sig, m, scalar, pk)

  if (res !== 0) throw new Error('failed to compute signature')
}

exports.extension_tweak_ed25519_sk_to_scalar = function (n, sk) {
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "n must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(
    sk.byteLength === binding.crypto_sign_SECRETKEYBYTES,
    "sk must be 'crypto_sign_SECRETKEYBYTES' bytes"
  )

  binding.extension_tweak_ed25519_sk_to_scalar(n, sk)
}

exports.extension_tweak_ed25519_scalar = function (scalarOut, scalar, ns) {
  assert(ArrayBuffer.isView(scalarOut), 'scalarOut must be a typed array')
  assert(
    scalarOut.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "scalarOut must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(scalar), 'scalar must be a typed array')
  assert(
    scalar.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "scalar must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )

  binding.extension_tweak_ed25519_scalar(scalarOut, scalar, ns)
}

exports.extension_tweak_ed25519_pk = function (tpk, pk, ns) {
  assert(ArrayBuffer.isView(tpk), 'tpk must be a typed array')
  assert(
    tpk.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
    "tpk must be 'crypto_sign_PUBLICKEYBYTES' bytes"
  )
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(
    pk.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
    "pk must be 'crypto_sign_PUBLICKEYBYTES' bytes"
  )

  const res = binding.extension_tweak_ed25519_pk(tpk, pk, ns)

  if (res !== 0) throw new Error('failed to tweak public key')
}

exports.extension_tweak_ed25519_keypair = function (pk, scalarOut, scalarIn, ns) {
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(
    pk.byteLength === binding.extension_tweak_ed25519_BYTES,
    "pk must be 'extension_tweak_ed25519_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(scalarOut), 'scalarOut must be a typed array')
  assert(
    scalarOut.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "scalarOut must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(scalarIn), 'scalarIn must be a typed array')
  assert(
    scalarIn.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "scalarIn must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )

  binding.extension_tweak_ed25519_keypair(pk, scalarOut, scalarIn, ns)
}

exports.extension_tweak_ed25519_scalar_add = function (scalarOut, scalar, n) {
  assert(ArrayBuffer.isView(scalarOut), 'scalarOut must be a typed array')
  assert(
    scalarOut.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "scalarOut must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(scalar), 'scalar must be a typed array')
  assert(
    scalar.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "scalar must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "n must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )

  binding.extension_tweak_ed25519_scalar_add(scalarOut, scalar, n)
}

exports.extension_tweak_ed25519_pk_add = function (tpk, pk, p) {
  assert(ArrayBuffer.isView(tpk), 'tpk must be a typed array')
  assert(
    tpk.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
    "tpk must be 'crypto_sign_PUBLICKEYBYTES' bytes"
  )
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(
    pk.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
    "pk must be 'crypto_sign_PUBLICKEYBYTES' bytes"
  )
  assert(ArrayBuffer.isView(p), 'p must be a typed array')
  assert(
    p.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
    "p must be 'crypto_sign_PUBLICKEYBYTES' bytes"
  )

  const res = binding.extension_tweak_ed25519_pk_add(tpk, pk, p)

  if (res !== 0) throw new Error('failed to add tweak to public key')
}

exports.extension_tweak_ed25519_keypair_add = function (pk, scalarOut, scalarIn, tweak) {
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(
    pk.byteLength === binding.extension_tweak_ed25519_BYTES,
    "pk must be 'extension_tweak_ed25519_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(scalarOut), 'scalarOut must be a typed array')
  assert(
    scalarOut.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "scalarOut must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(scalarIn), 'scalarIn must be a typed array')
  assert(
    scalarIn.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "scalarIn must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(tweak), 'tweak must be a typed array')
  assert(
    tweak.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "tweak must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )

  const res = binding.extension_tweak_ed25519_keypair_add(pk, scalarOut, scalarIn, tweak)

  if (res !== 0) throw new Error('failed to add tweak to keypair')
}

exports.extension_pbkdf2_sha512_async = function (out, passwd, salt, iter, outlen, callback) {
  assert(
    iter >= binding.extension_pbkdf2_sha512_ITERATIONS_MIN,
    "iter must be at least 'extension_pbkdf2_sha512_ITERATIONS_MIN'"
  )
  assert(
    outlen <= binding.extension_pbkdf2_sha512_BYTES_MAX,
    "outlen must be at most 'extension_pbkdf2_sha512_BYTES_MAX'"
  )
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(out.byteLength >= outlen, "out must be at least 'outlen' bytes")
  assert(out.byteLength > 0, 'out must not be empty')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(passwd.byteLength > 0, 'passwd must not be empty')
  assert(ArrayBuffer.isView(salt), 'salt must be a typed array')
  assert(salt.byteLength > 0, 'salt must not be empty')

  const [done, promise] = checkStatus(callback)

  binding.extension_pbkdf2_sha512_async(
    out.buffer,
    out.byteOffset,
    out.byteLength,

    passwd.buffer,
    passwd.byteOffset,
    passwd.byteLength,

    salt.buffer,
    salt.byteOffset,
    salt.byteLength,

    iter,
    outlen,

    done
  )

  return promise
}

exports.extension_pbkdf2_sha512 = function (out, passwd, salt, iter, outlen) {
  assert(
    iter >= binding.extension_pbkdf2_sha512_ITERATIONS_MIN,
    "iter must be at least 'extension_pbkdf2_sha512_ITERATIONS_MIN'"
  )
  assert(
    outlen <= binding.extension_pbkdf2_sha512_BYTES_MAX,
    "outlen must be at most 'extension_pbkdf2_sha512_BYTES_MAX'"
  )
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(out.byteLength >= outlen, "out must be at least 'outlen' bytes")
  assert(out.byteLength > 0, 'out must not be empty')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(passwd.byteLength > 0, 'passwd must not be empty')
  assert(ArrayBuffer.isView(salt), 'salt must be a typed array')
  assert(salt.byteLength > 0, 'salt must not be empty')

  const res = binding.extension_pbkdf2_sha512(out, passwd, salt, iter, outlen)

  if (res !== 0) throw new Error('failed to add tweak to public key')
}

function checkStatus(callback, booleanResult = false) {
  let done, promise

  if (typeof callback === 'function') {
    done = function (status) {
      if (booleanResult) callback(null, status === 0)
      else if (status === 0) callback(null)
      else callback(new Error('status: ' + status))
    }
  } else {
    promise = new Promise(function (resolve, reject) {
      done = function (status) {
        if (booleanResult) resolve(status === 0)
        else if (status === 0) resolve()
        else reject(new Error('status: ' + status))
      }
    })
  }

  return [done, promise]
}
{
  "name": "sodium-native",
  "version": "5.1.0",
  "description": "Low level bindings for libsodium",
  "main": "index.js",
  "files": [
    "index.js",
    "binding.cc",
    "binding.js",
    "extensions",
    "prebuilds",
    "CMakeLists.txt"
  ],
  "imports": {
    "assert": {
      "bare": "bare-assert",
      "node": "assert"
    }
  },
  "addon": true,
  "dependencies": {
    "bare-assert": "^1.2.0",
    "require-addon": "^1.1.0",
    "which-runtime": "^1.2.1"
  },
  "devDependencies": {
    "bare-compat-napi": "^1.3.5",
    "brittle": "^3.16.2",
    "cmake-bare": "^1.6.1",
    "cmake-fetch": "^1.4.3",
    "cmake-napi": "^1.2.1",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^2.0.0"
  },
  "scripts": {
    "test": "prettier . --check && npm run test:node && npm run test:bare",
    "test:node": "node test/all.js",
    "test:bare": "bare test/all.js"
  },
  "standard": {
    "ignore": [
      "/test/fixtures/*.js"
    ]
  },
  "engines": {
    "bare": ">=1.16.0"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/sodium-native.git"
  },
  "contributors": [
    "Emil Bay <github@tixz.dk> (http://bayes.dk)",
    "Mathias Buus <mathiasbuus@gmail.com> (https://mafinto.sh)",
    "Christophe Diederichs <chm-diederichs@hyperdivision.dk>"
  ],
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/holepunchto/sodium-native/issues"
  },
  "homepage": "https://github.com/holepunchto/sodium-native"
}
module.exports = require('sodium-native')
{
  "name": "sodium-universal",
  "version": "5.0.1",
  "description": "Universal wrapper for sodium-javascript and sodium-native working in Node.js and the Browser",
  "main": "index.js",
  "dependencies": {
    "sodium-native": "^5.0.1"
  },
  "peerDependencies": {
    "sodium-javascript": "~0.8.0"
  },
  "peerDependenciesMeta": {
    "sodium-javascript": {
      "optional": true
    }
  },
  "scripts": {
    "prepublish": "./build-scripts/generate.js"
  },
  "browser": {
    "sodium-native": "sodium-javascript"
  },
  "browserify": {
    "transform": [
      "./build-scripts/transform.js"
    ]
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/sodium-universal.git"
  },
  "keywords": [
    "libsodium",
    "sodium",
    "sodium-native",
    "sodium-javascript",
    "browserify"
  ],
  "contributors": [
    "Emil Bay <github@tixz.dk> (http://bayes.dk)",
    "Mathias Buus <mathiasbuus@gmail.com> (https://mafinto.sh)",
    "Christophe Diederichs <chm-diederichs@hyperdivision.dk>"
  ],
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/holepunchto/sodium-universal/issues"
  },
  "homepage": "https://github.com/holepunchto/sodium-universal#readme"
}
const { runtime, platform, arch } = typeof Bare !== 'undefined'
  ? { runtime: 'bare', platform: global.Bare.platform, arch: global.Bare.arch }
  : typeof process !== 'undefined'
    ? { runtime: 'node', platform: global.process.platform, arch: global.process.arch }
    : typeof Window !== 'undefined'
      ? { runtime: 'browser', platform: 'unknown', arch: 'unknown' }
      : { runtime: 'unknown', platform: 'unknown', arch: 'unknown' }

exports.runtime = runtime
exports.platform = platform
exports.arch = arch
exports.isBare = runtime === 'bare'
exports.isBareKit = exports.isBare && typeof BareKit !== 'undefined'
exports.isPear = typeof Pear !== 'undefined'
exports.isNode = runtime === 'node'
exports.isBrowser = runtime === 'browser'
exports.isWindows = platform === 'win32'
exports.isLinux = platform === 'linux'
exports.isMac = platform === 'darwin'
exports.isIOS = platform === 'ios' || platform === 'ios-simulator'
exports.isAndroid = platform === 'android'
exports.isElectron = typeof process !== 'undefined' && !!global.process.versions?.electron
exports.isElectronRenderer = exports.isElectron && global.process.type === 'renderer'
exports.isElectronWorker = exports.isElectron && global.process.type === 'worker'
{
  "name": "which-runtime",
  "version": "1.3.2",
  "description": "Detect if you are in Bare or Node and which os etc",
  "main": "index.js",
  "files": [
    "index.js"
  ],
  "dependencies": {},
  "devDependencies": {
    "standard": "^17.0.0"
  },
  "scripts": {
    "test": "standard"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/holepunchto/which-runtime.git"
  },
  "author": "Holepunch Inc.",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/which-runtime/issues"
  },
  "homepage": "https://github.com/holepunchto/which-runtime"
}
{
  "name": "anproto",
  "version": "0.1.0",
  "description": "Wire-compatible implementation of evbogue's ANProto (Authenticated Non-networked protocol) — Ed25519 signed timestamp+hash messages",
  "type": "module",
  "main": "index.js",
  "scripts": {
    "test": "brittle test/*.test.js"
  },
  "dependencies": {
    "b4a": "^1.6.0",
    "hypercore-crypto": "^3.4.0",
    "sodium-native": "^5.0.0"
  },
  "devDependencies": {
    "brittle": "^3.0.0"
  }
}
function isBuffer(value) {
  return Buffer.isBuffer(value) || value instanceof Uint8Array
}

function isEncoding(encoding) {
  return Buffer.isEncoding(encoding)
}

function alloc(size, fill, encoding) {
  return Buffer.alloc(size, fill, encoding)
}

function allocUnsafe(size) {
  return Buffer.allocUnsafe(size)
}

function allocUnsafeSlow(size) {
  return Buffer.allocUnsafeSlow(size)
}

function byteLength(string, encoding) {
  return Buffer.byteLength(string, encoding)
}

function compare(a, b) {
  return Buffer.compare(a, b)
}

function concat(buffers, totalLength) {
  return Buffer.concat(buffers, totalLength)
}

function copy(source, target, targetStart, start, end) {
  return toBuffer(source).copy(target, targetStart, start, end)
}

function equals(a, b) {
  return toBuffer(a).equals(b)
}

function fill(buffer, value, offset, end, encoding) {
  return toBuffer(buffer).fill(value, offset, end, encoding)
}

function from(value, encodingOrOffset, length) {
  return Buffer.from(value, encodingOrOffset, length)
}

function includes(buffer, value, byteOffset, encoding) {
  return toBuffer(buffer).includes(value, byteOffset, encoding)
}

function indexOf(buffer, value, byfeOffset, encoding) {
  return toBuffer(buffer).indexOf(value, byfeOffset, encoding)
}

function lastIndexOf(buffer, value, byteOffset, encoding) {
  return toBuffer(buffer).lastIndexOf(value, byteOffset, encoding)
}

function swap16(buffer) {
  return toBuffer(buffer).swap16()
}

function swap32(buffer) {
  return toBuffer(buffer).swap32()
}

function swap64(buffer) {
  return toBuffer(buffer).swap64()
}

function toBuffer(buffer) {
  if (Buffer.isBuffer(buffer)) return buffer
  return Buffer.from(buffer.buffer, buffer.byteOffset, buffer.byteLength)
}

function toString(buffer, encoding, start, end) {
  return toBuffer(buffer).toString(encoding, start, end)
}

function write(buffer, string, offset, length, encoding) {
  return toBuffer(buffer).write(string, offset, length, encoding)
}

function readDoubleBE(buffer, offset) {
  return toBuffer(buffer).readDoubleBE(offset)
}

function readDoubleLE(buffer, offset) {
  return toBuffer(buffer).readDoubleLE(offset)
}

function readFloatBE(buffer, offset) {
  return toBuffer(buffer).readFloatBE(offset)
}

function readFloatLE(buffer, offset) {
  return toBuffer(buffer).readFloatLE(offset)
}

function readInt32BE(buffer, offset) {
  return toBuffer(buffer).readInt32BE(offset)
}

function readInt32LE(buffer, offset) {
  return toBuffer(buffer).readInt32LE(offset)
}

function readUInt32BE(buffer, offset) {
  return toBuffer(buffer).readUInt32BE(offset)
}

function readUInt32LE(buffer, offset) {
  return toBuffer(buffer).readUInt32LE(offset)
}

function writeDoubleBE(buffer, value, offset) {
  return toBuffer(buffer).writeDoubleBE(value, offset)
}

function writeDoubleLE(buffer, value, offset) {
  return toBuffer(buffer).writeDoubleLE(value, offset)
}

function writeFloatBE(buffer, value, offset) {
  return toBuffer(buffer).writeFloatBE(value, offset)
}

function writeFloatLE(buffer, value, offset) {
  return toBuffer(buffer).writeFloatLE(value, offset)
}

function writeInt32BE(buffer, value, offset) {
  return toBuffer(buffer).writeInt32BE(value, offset)
}

function writeInt32LE(buffer, value, offset) {
  return toBuffer(buffer).writeInt32LE(value, offset)
}

function writeUInt32BE(buffer, value, offset) {
  return toBuffer(buffer).writeUInt32BE(value, offset)
}

function writeUInt32LE(buffer, value, offset) {
  return toBuffer(buffer).writeUInt32LE(value, offset)
}

module.exports = {
  isBuffer,
  isEncoding,
  alloc,
  allocUnsafe,
  allocUnsafeSlow,
  byteLength,
  compare,
  concat,
  copy,
  equals,
  fill,
  from,
  includes,
  indexOf,
  lastIndexOf,
  swap16,
  swap32,
  swap64,
  toBuffer,
  toString,
  write,
  readDoubleBE,
  readDoubleLE,
  readFloatBE,
  readFloatLE,
  readInt32BE,
  readInt32LE,
  readUInt32BE,
  readUInt32LE,
  writeDoubleBE,
  writeDoubleLE,
  writeFloatBE,
  writeFloatLE,
  writeInt32BE,
  writeInt32LE,
  writeUInt32BE,
  writeUInt32LE
}
{
  "name": "b4a",
  "version": "1.8.1",
  "description": "Bridging the gap between buffers and typed arrays",
  "exports": {
    "./package": "./package.json",
    ".": {
      "react-native": "./react-native.js",
      "browser": "./browser.js",
      "default": "./index.js"
    }
  },
  "files": [
    "browser.js",
    "index.js",
    "react-native.js",
    "lib"
  ],
  "scripts": {
    "format": "prettier --write . && lunte --fix",
    "lint": "prettier --check . && lunte",
    "test": "npm run test:bare && npm run test:node",
    "test:bare": "brittle-bare --coverage test.mjs",
    "test:node": "brittle-node --coverage test.mjs"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/b4a.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/b4a/issues"
  },
  "homepage": "https://github.com/holepunchto/b4a#readme",
  "devDependencies": {
    "brittle": "^3.5.2",
    "lunte": "^1.7.0",
    "nanobench": "^3.0.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^2.0.0"
  },
  "peerDependencies": {
    "react-native-b4a": "*"
  },
  "peerDependenciesMeta": {
    "react-native-b4a": {
      "optional": true
    }
  }
}
module.exports = require.addon()
const binding = require('./binding')

module.exports = binding.abort
{
  "name": "bare-abort",
  "version": "2.0.13",
  "description": "Cause abnormal program termination and generate a crash report",
  "exports": {
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    },
    "./package": "./package.json"
  },
  "files": [
    "index.js",
    "index.d.ts",
    "binding.c",
    "binding.js",
    "CMakeLists.txt",
    "prebuilds"
  ],
  "addon": true,
  "scripts": {
    "test": "prettier . --check"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-abort.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-abort/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-abort#readme",
  "devDependencies": {
    "cmake-bare": "^1.1.7",
    "prettier": "^3.4.2",
    "prettier-config-standard": "^7.0.0"
  }
}
const ESC = '\x1b'
const CSI = ESC + '['
const SGR = (n) => CSI + n + 'm'

exports.constants = {
  ESC,
  CSI,
  SGR
}

// https://en.wikipedia.org/wiki/ANSI_escape_code#CSI_(Control_Sequence_Introducer)_sequences

exports.cursorHide = CSI + '?25l'
exports.cursorShow = CSI + '?25h'

exports.cursorUp = function cursorUp(n = 1) {
  return CSI + n + 'A'
}

exports.cursorDown = function cursorDown(n = 1) {
  return CSI + n + 'B'
}

exports.cursorForward = function cursorForward(n = 1) {
  return CSI + n + 'C'
}

exports.cursorBack = function cursorBack(n = 1) {
  return CSI + n + 'D'
}

exports.cursorNextLine = function cursorNextLine(n = 1) {
  return CSI + n + 'E'
}

exports.cursorPreviousLine = function cursorPreviousLine(n = 1) {
  return CSI + n + 'F'
}

exports.cursorPosition = function cursorPosition(column, row = 0) {
  if (row === 0) return CSI + (column + 1) + 'G'

  return CSI + (row + 1) + ';' + (column + 1) + 'H'
}

exports.eraseDisplayEnd = CSI + 'J'
exports.eraseDisplayStart = CSI + '1J'
exports.eraseDisplay = CSI + '2J'
exports.eraseLineEnd = CSI + 'K'
exports.eraseLineStart = CSI + '1K'
exports.eraseLine = CSI + '2K'

exports.scrollUp = function scrollUp(n = 1) {
  return CSI + n + 'S'
}

exports.scrollDown = function scrollDown(n = 1) {
  return CSI + n + 'T'
}

// https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_(Select_Graphic_Rendition)_parameters

exports.modifierReset = SGR(0)
exports.modifierBold = SGR(1)
exports.modifierDim = SGR(2)
exports.modifierItalic = SGR(3)
exports.modifierUnderline = SGR(4)
exports.modifierNormal = SGR(22)
exports.modifierNotItalic = SGR(23)
exports.modifierNotUnderline = SGR(24)

// https://en.wikipedia.org/wiki/ANSI_escape_code#Colors

exports.colorBlack = SGR(30)
exports.colorRed = SGR(31)
exports.colorGreen = SGR(32)
exports.colorYellow = SGR(33)
exports.colorBlue = SGR(34)
exports.colorMagenta = SGR(35)
exports.colorCyan = SGR(36)
exports.colorWhite = SGR(37)
exports.colorDefault = SGR(39)
exports.colorBrightBlack = SGR(90)
exports.colorBrightRed = SGR(91)
exports.colorBrightGreen = SGR(92)
exports.colorBrightYellow = SGR(93)
exports.colorBrightBlue = SGR(94)
exports.colorBrightMagenta = SGR(95)
exports.colorBrightCyan = SGR(96)
exports.colorBrightWhite = SGR(97)
{
  "name": "bare-ansi-escapes",
  "version": "2.2.3",
  "description": "Parse and produce ANSI escape sequences",
  "exports": {
    "./package": "./package.json",
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    },
    "./key-decoder": {
      "types": "./key-decoder.d.ts",
      "default": "./key-decoder.js"
    }
  },
  "files": [
    "index.js",
    "index.d.ts",
    "key-decoder.js",
    "key-decoder.d.ts"
  ],
  "scripts": {
    "test": "prettier . --check && bare test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-ansi-escapes.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-ansi-escapes/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-ansi-escapes#readme",
  "dependencies": {
    "bare-stream": "^2.6.5"
  },
  "devDependencies": {
    "bare-buffer": "^3.0.2",
    "brittle": "^3.3.2",
    "prettier": "^3.4.2",
    "prettier-config-standard": "^7.0.0"
  },
  "peerDependencies": {
    "bare-buffer": "*"
  },
  "peerDependenciesMeta": {
    "bare-buffer": {
      "optional": true
    }
  }
}
const inspect = require('bare-inspect')

class AssertionError extends Error {
  constructor(opts = {}) {
    let { message = null, actual, expected, operator } = opts

    if (message === null) {
      message = `${inspect(actual)} ${operator} ${inspect(expected)}`
    }

    super(message)

    this.actual = actual
    this.expected = expected
    this.operator = operator
  }

  get name() {
    return 'AssertionError'
  }

  get code() {
    'ASSERTION'
  }
}

function assertFail(opts, fn) {
  if (opts.message instanceof Error) throw opts.message

  const err = new AssertionError(opts)

  if (Error.captureStackTrace) Error.captureStackTrace(err, fn)

  throw err
}

module.exports = exports = function assert(actual, message) {
  if (actual) return

  assertFail({ message, actual, expected: true, operator: '==' }, assert)
}

exports.AssertionError = AssertionError

exports.fail = function fail(message) {
  if (message === undefined) message = 'Failed'

  assertFail({ message, operator: 'fail' }, fail)
}

exports.ok = function ok(actual, message) {
  if (actual) return

  assertFail({ message, actual, expected: true, operator: '==' }, ok)
}

exports.notOk = function ok(actual, message) {
  if (!actual) return

  assertFail({ message, actual, expected: false, operator: '==' }, ok)
}

exports.equal = function equal(actual, expected, message) {
  if (actual == expected || (actual !== actual && expected !== expected)) {
    return
  }

  assertFail({ message, actual, expected, operator: '==' }, equal)
}

exports.notEqual = function notEqual(actual, expected, message) {
  if (actual != expected && (actual === actual || expected === expected)) {
    return
  }

  assertFail({ message, actual, expected, operator: '!=' }, notEqual)
}

exports.strictEqual = function strictEqual(actual, expected, message) {
  if (Object.is(actual, expected)) return

  assertFail(
    { message, actual, expected, operator: 'strictEqual' },
    strictEqual
  )
}

exports.notStrictEqual = function notStrictEqual(actual, expected, message) {
  if (!Object.is(actual, expected)) return

  assertFail(
    { message, actual, expected, operator: 'notStrictEqual' },
    notStrictEqual
  )
}
{
  "name": "bare-assert",
  "version": "1.2.0",
  "description": "Assertion library for JavaScript",
  "exports": {
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    },
    "./package": "./package.json"
  },
  "files": [
    "index.js",
    "index.d.ts"
  ],
  "scripts": {
    "test": "prettier . --check && bare test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-assert.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-assert/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-assert#readme",
  "dependencies": {
    "bare-inspect": "^3.1.2"
  },
  "devDependencies": {
    "brittle": "^3.1.1",
    "prettier": "^3.4.2",
    "prettier-config-standard": "^7.0.0"
  }
}
module.exports = require.addon()
const binding = require('./binding')
const constants = require('./lib/constants')
const errors = require('./lib/errors')
const DNSResolver = require('./lib/resolver')

exports.Resolver = DNSResolver

function onlookup(err, addresses) {
  const req = this

  if (err) return req.cb(err, null, 0)

  const { address, family } = addresses[0]

  return req.cb(null, address, family)
}

function onlookupall(err, addresses) {
  const req = this

  if (err) return req.cb(err, null)

  return req.cb(null, addresses)
}

exports.lookup = function lookup(hostname, opts = {}, cb) {
  if (typeof opts === 'function') {
    cb = opts
    opts = {}
  }

  let { family = 0, all = false } = opts

  if (typeof family === 'string') {
    switch (family) {
      case 'IPv4':
        family = 4
        break
      case 'IPv6':
        family = 6
        break
      default:
        family = 0
    }
  }

  const req = {
    cb,
    handle: null
  }

  req.handle = binding.lookup(hostname, family || 0, all, req, all ? onlookupall : onlookup)
}

exports.getServers = function getServers() {
  return DNSResolver.global.getServers()
}

exports.setServers = function setServers(servers) {
  return DNSResolver.global.setServers(servers)
}

exports.setLocalAddress = function setLocalAddress(ipv4, ipv6) {
  return DNSResolver.global.setLocalAddress(ipv4, ipv6)
}

exports.cancel = function cancel() {
  return DNSResolver.global.cancel()
}

exports.resolve = function resolve(hostname, rrtype, cb) {
  return DNSResolver.global.resolve(hostname, rrtype, cb)
}

exports.resolve4 = function resolve4(hostname, opts, cb) {
  return DNSResolver.global.resolve4(hostname, opts, cb)
}

exports.resolve6 = function resolve6(hostname, opts, cb) {
  return DNSResolver.global.resolve6(hostname, opts, cb)
}

exports.resolveAny = function resolveAny(hostname, cb) {
  return DNSResolver.global.resolveAny(hostname, cb)
}

exports.resolveCaa = function resolveCaa(hostname, cb) {
  return DNSResolver.global.resolveCaa(hostname, cb)
}

exports.resolveCname = function resolveCname(hostname, cb) {
  return DNSResolver.global.resolveCname(hostname, cb)
}

exports.resolveMx = function resolveMx(hostname, cb) {
  return DNSResolver.global.resolveMx(hostname, cb)
}

exports.resolveNaptr = function resolveNaptr(hostname, cb) {
  return DNSResolver.global.resolveNaptr(hostname, cb)
}

exports.resolveNs = function resolveNs(hostname, cb) {
  return DNSResolver.global.resolveNs(hostname, cb)
}

exports.resolvePtr = function resolvePtr(hostname, cb) {
  return DNSResolver.global.resolvePtr(hostname, cb)
}

exports.resolveSoa = function resolveSoa(hostname, cb) {
  return DNSResolver.global.resolveSoa(hostname, cb)
}

exports.resolveSrv = function resolveSrv(hostname, cb) {
  return DNSResolver.global.resolveSrv(hostname, cb)
}

exports.resolveTlsa = function resolveTlsa(hostname, cb) {
  return DNSResolver.global.resolveTlsa(hostname, cb)
}

exports.resolveTxt = function resolveTxt(hostname, cb) {
  return DNSResolver.global.resolveTxt(hostname, cb)
}

exports.reverse = function reverse(ip, cb) {
  return DNSResolver.global.reverse(ip, cb)
}

exports.constants = constants
exports.errors = errors
exports.promises = require('./lib/promises')
// Resource record types, as understood by the underlying resolver.
exports.type = {
  A: 1,
  NS: 2,
  CNAME: 5,
  SOA: 6,
  PTR: 12,
  HINFO: 13,
  MX: 15,
  TXT: 16,
  SIG: 24,
  AAAA: 28,
  SRV: 33,
  NAPTR: 35,
  OPT: 41,
  TLSA: 52,
  SVCB: 64,
  HTTPS: 65,
  ANY: 255,
  URI: 256,
  CAA: 257
}

// Resolver status codes, indexed to match the values reported by the binding.
exports.status = {
  SUCCESS: 0,
  ENODATA: 1,
  EFORMERR: 2,
  ESERVFAIL: 3,
  ENOTFOUND: 4,
  ENOTIMP: 5,
  EREFUSED: 6,
  EBADQUERY: 7,
  EBADNAME: 8,
  EBADFAMILY: 9,
  EBADRESP: 10,
  ECONNREFUSED: 11,
  ETIMEOUT: 12,
  EOF: 13,
  EFILE: 14,
  ENOMEM: 15,
  EDESTRUCTION: 16,
  EBADSTR: 17,
  EBADFLAGS: 18,
  ENONAME: 19,
  EBADHINTS: 20,
  ENOTINITIALIZED: 21,
  ELOADIPHLPAPI: 22,
  EADDRGETNETWORKPARAMS: 23,
  ECANCELLED: 24,
  ESERVICE: 25,
  ENOSERVER: 26
}
const constants = require('./constants')

// The status codes indexed by their value, for turning the status a query
// reports into the code its error carries.
const codes = []

for (const [name, value] of Object.entries(constants.status)) {
  codes[value] = name
}

module.exports = class DNSError extends Error {
  constructor(msg, fn = DNSError, code = fn.name) {
    super(`${code}: ${msg}`)

    this.code = code

    if (Error.captureStackTrace) Error.captureStackTrace(this, fn)
  }

  get name() {
    return 'DNSError'
  }

  static RESOLVER_DESTROYED(msg = 'Resolver is destroyed') {
    return new DNSError(msg, DNSError.RESOLVER_DESTROYED)
  }

  static UNKNOWN_RECORD_TYPE(msg = 'Unknown resource record type') {
    return new DNSError(msg, DNSError.UNKNOWN_RECORD_TYPE)
  }

  static INVALID_IP_ADDRESS(msg = 'Invalid IP address') {
    return new DNSError(msg, DNSError.INVALID_IP_ADDRESS)
  }

  static query(status, syscall, hostname = null) {
    const code = codes[status] || 'UNKNOWN'

    const err = new DNSError(
      `${syscall}` + (hostname === null ? '' : ` ${hostname}`),
      DNSError.query,
      code
    )

    err.syscall = syscall

    if (hostname !== null) err.hostname = hostname

    return err
  }
}
const constants = require('./constants')
const errors = require('./errors')
const DNSResolver = require('./resolver')

class DNSPromiseResolver {
  constructor(opts) {
    this._resolver = new DNSResolver(opts)
  }

  getServers() {
    return this._resolver.getServers()
  }

  setServers(servers) {
    return this._resolver.setServers(servers)
  }

  setLocalAddress(ipv4, ipv6) {
    return this._resolver.setLocalAddress(ipv4, ipv6)
  }

  cancel() {
    return this._resolver.cancel()
  }

  destroy() {
    return this._resolver.destroy()
  }

  resolve(hostname, rrtype) {
    return promisify(this._resolver, 'resolve', hostname, rrtype)
  }

  resolve4(hostname, opts) {
    return promisify(this._resolver, 'resolve4', hostname, opts)
  }

  resolve6(hostname, opts) {
    return promisify(this._resolver, 'resolve6', hostname, opts)
  }

  resolveAny(hostname) {
    return promisify(this._resolver, 'resolveAny', hostname)
  }

  resolveCaa(hostname) {
    return promisify(this._resolver, 'resolveCaa', hostname)
  }

  resolveCname(hostname) {
    return promisify(this._resolver, 'resolveCname', hostname)
  }

  resolveMx(hostname) {
    return promisify(this._resolver, 'resolveMx', hostname)
  }

  resolveNaptr(hostname) {
    return promisify(this._resolver, 'resolveNaptr', hostname)
  }

  resolveNs(hostname) {
    return promisify(this._resolver, 'resolveNs', hostname)
  }

  resolvePtr(hostname) {
    return promisify(this._resolver, 'resolvePtr', hostname)
  }

  resolveSoa(hostname) {
    return promisify(this._resolver, 'resolveSoa', hostname)
  }

  resolveSrv(hostname) {
    return promisify(this._resolver, 'resolveSrv', hostname)
  }

  resolveTlsa(hostname) {
    return promisify(this._resolver, 'resolveTlsa', hostname)
  }

  resolveTxt(hostname) {
    return promisify(this._resolver, 'resolveTxt', hostname)
  }

  reverse(ip) {
    return promisify(this._resolver, 'reverse', ip)
  }

  static global = new this()
}

function promisify(resolver, method, ...args) {
  return new Promise((resolve, reject) => {
    resolver[method](...args, (err, result) => {
      if (err) reject(err)
      else resolve(result)
    })
  })
}

exports.Resolver = DNSPromiseResolver

exports.lookup = function lookup(hostname, opts = {}) {
  const dns = require('../')

  return new Promise((resolve, reject) => {
    if (opts && opts.all) {
      dns.lookup(hostname, opts, (err, addresses) => {
        if (err) reject(err)
        else resolve(addresses)
      })
    } else {
      dns.lookup(hostname, opts, (err, address, family) => {
        if (err) reject(err)
        else resolve({ address, family })
      })
    }
  })
}

exports.getServers = function getServers() {
  return DNSPromiseResolver.global.getServers()
}

exports.setServers = function setServers(servers) {
  return DNSPromiseResolver.global.setServers(servers)
}

exports.setLocalAddress = function setLocalAddress(ipv4, ipv6) {
  return DNSPromiseResolver.global.setLocalAddress(ipv4, ipv6)
}

exports.cancel = function cancel() {
  return DNSPromiseResolver.global.cancel()
}

exports.resolve = function resolve(hostname, rrtype) {
  return DNSPromiseResolver.global.resolve(hostname, rrtype)
}

exports.resolve4 = function resolve4(hostname, opts) {
  return DNSPromiseResolver.global.resolve4(hostname, opts)
}

exports.resolve6 = function resolve6(hostname, opts) {
  return DNSPromiseResolver.global.resolve6(hostname, opts)
}

exports.resolveAny = function resolveAny(hostname) {
  return DNSPromiseResolver.global.resolveAny(hostname)
}

exports.resolveCaa = function resolveCaa(hostname) {
  return DNSPromiseResolver.global.resolveCaa(hostname)
}

exports.resolveCname = function resolveCname(hostname) {
  return DNSPromiseResolver.global.resolveCname(hostname)
}

exports.resolveMx = function resolveMx(hostname) {
  return DNSPromiseResolver.global.resolveMx(hostname)
}

exports.resolveNaptr = function resolveNaptr(hostname) {
  return DNSPromiseResolver.global.resolveNaptr(hostname)
}

exports.resolveNs = function resolveNs(hostname) {
  return DNSPromiseResolver.global.resolveNs(hostname)
}

exports.resolvePtr = function resolvePtr(hostname) {
  return DNSPromiseResolver.global.resolvePtr(hostname)
}

exports.resolveSoa = function resolveSoa(hostname) {
  return DNSPromiseResolver.global.resolveSoa(hostname)
}

exports.resolveSrv = function resolveSrv(hostname) {
  return DNSPromiseResolver.global.resolveSrv(hostname)
}

exports.resolveTlsa = function resolveTlsa(hostname) {
  return DNSPromiseResolver.global.resolveTlsa(hostname)
}

exports.resolveTxt = function resolveTxt(hostname) {
  return DNSPromiseResolver.global.resolveTxt(hostname)
}

exports.reverse = function reverse(ip) {
  return DNSPromiseResolver.global.reverse(ip)
}

exports.constants = constants
exports.errors = errors
// Shapes a parsed DNS record into the result each `resolve*()` method returns.
// The binding hands back every field a record type carries, named as the
// resolver names it.

function answers(record, type) {
  const result = []

  for (const rr of record.answers) {
    if (rr.type === type) result.push(rr)
  }

  return result
}

function string(value) {
  return value === null ? '' : Buffer.from(value).toString()
}

function buffer(value) {
  return value === null ? Buffer.alloc(0) : Buffer.from(value)
}

// Each shape returns `null` when the answer section holds no record of the
// requested type, which the caller reports as `ENODATA`.
function list(type, fn) {
  return (record) => {
    const rrs = answers(record, type)

    return rrs.length === 0 ? null : rrs.map(fn)
  }
}

const address = (rr) => rr.addr
const addressWithTTL = (rr) => ({ address: rr.addr, ttl: rr.ttl })

exports.a = (ttl) => list('A', ttl ? addressWithTTL : address)
exports.aaaa = (ttl) => list('AAAA', ttl ? addressWithTTL : address)

exports.cname = list('CNAME', (rr) => rr.cname)
exports.ns = list('NS', (rr) => rr.nsdname)
exports.ptr = list('PTR', (rr) => rr.dname)
exports.txt = list('TXT', (rr) => rr.data)

exports.mx = list('MX', (rr) => ({
  priority: rr.preference,
  exchange: rr.exchange
}))

exports.srv = list('SRV', (rr) => ({
  priority: rr.priority,
  weight: rr.weight,
  port: rr.port,
  name: rr.target
}))

exports.naptr = list('NAPTR', (rr) => ({
  flags: rr.flags,
  service: rr.services,
  regexp: rr.regexp,
  replacement: rr.replacement,
  order: rr.order,
  preference: rr.preference
}))

exports.caa = list('CAA', (rr) => ({
  critical: rr.critical,
  [rr.tag]: string(rr.value)
}))

exports.tlsa = list('TLSA', (rr) => ({
  certUsage: rr.cert_usage,
  selector: rr.selector,
  match: rr.match,
  data: buffer(rr.data)
}))

// Unlike every other type, a start of authority resolves to a single record
// rather than a list.
exports.soa = (record) => {
  const rrs = answers(record, 'SOA')

  return rrs.length === 0 ? null : soa(rrs[0])
}

function soa(rr) {
  return {
    nsname: rr.mname,
    hostmaster: rr.rname,
    serial: rr.serial,
    refresh: rr.refresh,
    retry: rr.retry,
    expire: rr.expire,
    minttl: rr.minimum
  }
}

// A wildcard query may come back with a mix of record types, each tagged with
// its type so that the caller can tell them apart.
exports.any = (record) => {
  const result = []

  for (const rr of record.answers) {
    switch (rr.type) {
      case 'A':
        result.push({ type: 'A', address: rr.addr, ttl: rr.ttl })
        break
      case 'AAAA':
        result.push({ type: 'AAAA', address: rr.addr, ttl: rr.ttl })
        break
      case 'CNAME':
        result.push({ type: 'CNAME', value: rr.cname })
        break
      case 'NS':
        result.push({ type: 'NS', value: rr.nsdname })
        break
      case 'PTR':
        result.push({ type: 'PTR', value: rr.dname })
        break
      case 'TXT':
        result.push({ type: 'TXT', entries: rr.data })
        break
      case 'MX':
        result.push({ type: 'MX', priority: rr.preference, exchange: rr.exchange })
        break
      case 'SRV':
        result.push({
          type: 'SRV',
          priority: rr.priority,
          weight: rr.weight,
          port: rr.port,
          name: rr.target
        })
        break
      case 'NAPTR':
        result.push({
          type: 'NAPTR',
          flags: rr.flags,
          service: rr.services,
          regexp: rr.regexp,
          replacement: rr.replacement,
          order: rr.order,
          preference: rr.preference
        })
        break
      case 'SOA':
        result.push({ type: 'SOA', ...soa(rr) })
        break
    }
  }

  return result
}
const binding = require('../binding')
const constants = require('./constants')
const errors = require('./errors')
const records = require('./records')

const { type, status } = constants

module.exports = class DNSResolver {
  constructor(opts = {}) {
    const { servers = null, localAddress = null } = opts

    this._handle = binding.initResolver()

    if (servers !== null) this.setServers(servers)

    if (localAddress !== null) {
      this.setLocalAddress(localAddress.ipv4, localAddress.ipv6)
    }
  }

  getServers() {
    if (this._handle === null) throw errors.RESOLVER_DESTROYED()

    const servers = binding.getServers(this._handle)

    return servers === '' ? [] : servers.split(',')
  }

  setServers(servers) {
    if (this._handle === null) throw errors.RESOLVER_DESTROYED()

    binding.setServers(this._handle, servers.join(','))
  }

  setLocalAddress(ipv4 = null, ipv6 = null) {
    if (this._handle === null) throw errors.RESOLVER_DESTROYED()

    binding.setLocalAddress(this._handle, ipv4, ipv6)
  }

  // Aborts every query outstanding on this resolver. Each pending callback is
  // invoked with `ECANCELLED`, releasing the loop.
  cancel() {
    if (this._handle === null) return

    binding.cancel(this._handle)
  }

  // Cancels every query outstanding on this resolver, so that none is left
  // without an answer, and releases the resources it holds.
  destroy() {
    if (this._handle === null) return

    binding.destroyResolver(this._handle)
    this._handle = null
  }

  resolve(hostname, rrtype = 'A', cb = noop) {
    if (typeof rrtype === 'function') {
      cb = rrtype
      rrtype = 'A'
    }

    switch (rrtype) {
      case 'A':
        return this.resolve4(hostname, cb)
      case 'AAAA':
        return this.resolve6(hostname, cb)
      case 'ANY':
        return this.resolveAny(hostname, cb)
      case 'CAA':
        return this.resolveCaa(hostname, cb)
      case 'CNAME':
        return this.resolveCname(hostname, cb)
      case 'MX':
        return this.resolveMx(hostname, cb)
      case 'NAPTR':
        return this.resolveNaptr(hostname, cb)
      case 'NS':
        return this.resolveNs(hostname, cb)
      case 'PTR':
        return this.resolvePtr(hostname, cb)
      case 'SOA':
        return this.resolveSoa(hostname, cb)
      case 'SRV':
        return this.resolveSrv(hostname, cb)
      case 'TLSA':
        return this.resolveTlsa(hostname, cb)
      case 'TXT':
        return this.resolveTxt(hostname, cb)
      default:
        throw errors.UNKNOWN_RECORD_TYPE(`Unknown resource record type '${rrtype}'`)
    }
  }

  resolve4(hostname, opts = {}, cb = noop) {
    if (typeof opts === 'function') {
      cb = opts
      opts = {}
    }

    const { ttl = false } = opts

    this._query(hostname, type.A, 'queryA', records.a(ttl), cb)
  }

  resolve6(hostname, opts = {}, cb = noop) {
    if (typeof opts === 'function') {
      cb = opts
      opts = {}
    }

    const { ttl = false } = opts

    this._query(hostname, type.AAAA, 'queryAaaa', records.aaaa(ttl), cb)
  }

  resolveAny(hostname, cb = noop) {
    this._query(hostname, type.ANY, 'queryAny', records.any, cb)
  }

  resolveCaa(hostname, cb = noop) {
    this._query(hostname, type.CAA, 'queryCaa', records.caa, cb)
  }

  resolveCname(hostname, cb = noop) {
    this._query(hostname, type.CNAME, 'queryCname', records.cname, cb)
  }

  resolveMx(hostname, cb = noop) {
    this._query(hostname, type.MX, 'queryMx', records.mx, cb)
  }

  resolveNaptr(hostname, cb = noop) {
    this._query(hostname, type.NAPTR, 'queryNaptr', records.naptr, cb)
  }

  resolveNs(hostname, cb = noop) {
    this._query(hostname, type.NS, 'queryNs', records.ns, cb)
  }

  resolvePtr(hostname, cb = noop) {
    this._query(hostname, type.PTR, 'queryPtr', records.ptr, cb)
  }

  resolveSoa(hostname, cb = noop) {
    this._query(hostname, type.SOA, 'querySoa', records.soa, cb)
  }

  resolveSrv(hostname, cb = noop) {
    this._query(hostname, type.SRV, 'querySrv', records.srv, cb)
  }

  resolveTlsa(hostname, cb = noop) {
    this._query(hostname, type.TLSA, 'queryTlsa', records.tlsa, cb)
  }

  resolveTxt(hostname, cb = noop) {
    this._query(hostname, type.TXT, 'queryTxt', records.txt, cb)
  }

  // Resolves a pointer record, which is a plain query against the reverse zone
  // for the address. Unlike Node.js, the hosts file is not consulted first, so
  // an address that is only named there does not resolve.
  reverse(ip, cb = noop) {
    let name

    try {
      name = binding.addrToPtr(ip)
    } catch {
      throw errors.INVALID_IP_ADDRESS(`Invalid IP address '${ip}'`)
    }

    this._query(name, type.PTR, 'getHostByAddr', records.ptr, cb, ip)
  }

  _query(name, rrtype, syscall, shape, cb, hostname = name) {
    if (this._handle === null) throw errors.RESOLVER_DESTROYED()

    const req = { hostname, syscall, shape, cb }

    binding.query(this._handle, name, rrtype, req, onquery)
  }

  static global = new this()
}

// The binding hands over an answer on a turn of the loop of its own, including
// one it served from its cache, so a callback never runs before the call that
// issued it.
function onquery(code, record) {
  const req = this

  if (code !== status.SUCCESS) {
    return req.cb(errors.query(code, req.syscall, req.hostname), null)
  }

  const result = req.shape(record)

  if (result === null) {
    return req.cb(errors.query(status.ENODATA, req.syscall, req.hostname), null)
  }

  req.cb(null, result)
}

function noop() {}
{
  "name": "bare-dns",
  "version": "2.2.0",
  "description": "Domain name resolution for JavaScript",
  "exports": {
    "./package": "./package.json",
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    },
    "./promises": {
      "types": "./lib/promises.d.ts",
      "default": "./lib/promises.js"
    },
    "./constants": {
      "types": "./lib/constants.d.ts",
      "default": "./lib/constants.js"
    },
    "./errors": {
      "types": "./lib/errors.d.ts",
      "default": "./lib/errors.js"
    }
  },
  "files": [
    "index.js",
    "index.d.ts",
    "binding.c",
    "binding.js",
    "CMakeLists.txt",
    "lib",
    "prebuilds"
  ],
  "addon": true,
  "scripts": {
    "format": "prettier --write . && lunte --fix",
    "lint": "prettier --check . && lunte",
    "test": "brittle-bare --coverage test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-dns.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-dns/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-dns",
  "engines": {
    "bare": ">=1.7.0"
  },
  "devDependencies": {
    "bare-buffer": "^3.0.2",
    "bare-dgram": "^1.0.1",
    "brittle": "^3.3.2",
    "cmake-bare": "^1.1.6",
    "cmake-fetch": "^1.3.0",
    "lunte": "^1.8.4",
    "prettier": "^3.4.1",
    "prettier-config-holepunch": "^2.0.0"
  },
  "peerDependencies": {
    "bare-buffer": "*"
  },
  "peerDependenciesMeta": {
    "bare-buffer": {
      "optional": true
    }
  }
}
const os = require('bare-os')

module.exports = new Proxy(Object.create(null), {
  ownKeys(target) {
    return os.getEnvKeys()
  },

  get(target, property) {
    if (typeof property !== 'string') return

    return os.getEnv(property)
  },

  has(target, property) {
    if (typeof property !== 'string') return false

    return os.hasEnv(property)
  },

  set(target, property, value) {
    if (typeof property !== 'string') return

    const type = typeof value

    if (type !== 'string' && type !== 'number' && type !== 'boolean') {
      throw new Error('Environment variable must be of type string, number, or boolean')
    }

    value = String(value)

    os.setEnv(property, value)

    return true
  },

  deleteProperty(target, property) {
    if (typeof property !== 'string') return

    os.unsetEnv(property)
  },

  getOwnPropertyDescriptor(target, property) {
    return {
      value: this.get(target, property),
      enumerable: true,
      configurable: true,
      writable: true
    }
  }
})
{
  "name": "bare-env",
  "version": "3.0.1",
  "description": "Environment variable support for JavaScript",
  "exports": {
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    },
    "./package": "./package.json"
  },
  "files": [
    "index.js",
    "index.d.ts"
  ],
  "scripts": {
    "format": "prettier --write . && lunte --fix",
    "lint": "prettier --check . && lunte",
    "test": "brittle-bare --coverage test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-env.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-env/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-env#readme",
  "dependencies": {
    "bare-os": "^3.0.1"
  },
  "devDependencies": {
    "brittle": "^3.2.1",
    "lunte": "^1.8.3",
    "prettier": "^3.4.2",
    "prettier-config-holepunch": "^2.0.0"
  }
}
const errors = require('./lib/errors')

class EventListener {
  constructor() {
    this.list = []
    this.count = 0
  }

  append(ctx, name, fn, once) {
    this.count++
    ctx.emit('newListener', name, fn) // Emit BEFORE adding
    this.list.push([fn, once, false])
  }

  prepend(ctx, name, fn, once) {
    this.count++
    ctx.emit('newListener', name, fn) // Emit BEFORE adding
    this.list.unshift([fn, once, false])
  }

  remove(ctx, name, fn) {
    for (let i = this.list.length - 1; i >= 0; i--) {
      if (this.list[i][0] === fn) return this.removeAt(ctx, name, i)
    }
  }

  removeAt(ctx, name, i) {
    const fn = this.list[i][0]

    this.list.splice(i, 1)

    if (this.count === 1) delete ctx._events[name]

    ctx.emit('removeListener', name, fn) // Emit AFTER removing

    this.count--
  }

  removeAll(ctx, name) {
    const list = [...this.list]
    this.list = []

    if (this.count === list.length) delete ctx._events[name]

    for (let i = list.length - 1; i >= 0; i--) {
      ctx.emit('removeListener', name, list[i][0]) // Emit AFTER removing
    }

    this.count -= list.length
  }

  emit(ctx, name, ...args) {
    const list = [...this.list]

    for (let i = 0, n = list.length; i < n; i++) {
      const l = list[i]

      if (l[1] === true) {
        // A reentrant emit may already have fired this listener, in which case
        // it must not be fired again.
        if (l[2] === true) continue

        l[2] = true

        // Remove this entry, rather than the first one with the same listener,
        // as the same listener may have been added more than once.
        const j = this.list.indexOf(l)

        if (j !== -1) this.removeAt(ctx, name, j)
      }

      Reflect.apply(l[0], ctx, args)
    }

    return list.length > 0
  }
}

function appendListener(ctx, name, fn, once) {
  if (ctx._events === undefined) ctx._events = Object.create(null)
  const e = ctx._events[name] || (ctx._events[name] = new EventListener())
  e.append(ctx, name, fn, once)
  return ctx
}

function prependListener(ctx, name, fn, once) {
  if (ctx._events === undefined) ctx._events = Object.create(null)
  const e = ctx._events[name] || (ctx._events[name] = new EventListener())
  e.prepend(ctx, name, fn, once)
  return ctx
}

function removeListener(ctx, name, fn) {
  if (ctx._events === undefined) return ctx
  const e = ctx._events[name]
  if (e !== undefined) e.remove(ctx, name, fn)
  return ctx
}

function throwUnhandledError(...args) {
  let err

  if (args.length > 0) err = args[0]

  if (err instanceof Error === false) {
    err = errors.UNHANDLED_ERROR(err)

    if (Error.captureStackTrace) Error.captureStackTrace(err, exports.prototype.emit)
  }

  queueMicrotask(() => {
    throw err
  })
}

module.exports = exports = class EventEmitter {
  constructor() {
    this._events = Object.create(null)
  }

  addListener(name, fn) {
    return appendListener(this, name, fn, false)
  }

  addOnceListener(name, fn) {
    return appendListener(this, name, fn, true)
  }

  prependListener(name, fn) {
    return prependListener(this, name, fn, false)
  }

  prependOnceListener(name, fn) {
    return prependListener(this, name, fn, true)
  }

  removeListener(name, fn) {
    return removeListener(this, name, fn)
  }

  on(name, fn) {
    return appendListener(this, name, fn, false)
  }

  once(name, fn) {
    return appendListener(this, name, fn, true)
  }

  off(name, fn) {
    return removeListener(this, name, fn)
  }

  emit(name, ...args) {
    if (name === 'error' && (this._events === undefined || this._events.error === undefined)) {
      throwUnhandledError(...args)
    }

    if (this._events === undefined) return false
    const e = this._events[name]
    return e === undefined ? false : e.emit(this, name, ...args)
  }

  listeners(name) {
    if (this._events === undefined) return []
    const e = this._events[name]
    return e === undefined ? [] : e.list.map((l) => l[0])
  }

  rawListeners(name) {
    if (this._events === undefined) return []
    const e = this._events[name]
    return e === undefined ? [] : e.list.map((l) => l[0])
  }

  eventNames() {
    if (this._events === undefined) return []
    return Reflect.ownKeys(this._events)
  }

  listenerCount(name) {
    if (this._events === undefined) return 0
    const e = this._events[name]
    return e === undefined ? 0 : e.list.length
  }

  getMaxListeners() {
    return EventEmitter.defaultMaxListeners
  }

  setMaxListeners(n) {
    return this
  }

  removeAllListeners(name) {
    if (this._events === undefined) return this

    if (arguments.length === 0) {
      for (const key of Reflect.ownKeys(this._events)) {
        if (key === 'removeListener') continue
        this.removeAllListeners(key)
      }
      this.removeAllListeners('removeListener')
    } else {
      const e = this._events[name]
      if (e !== undefined) e.removeAll(this, name)
    }
    return this
  }
}

exports.EventEmitter = exports

exports.errors = errors

exports.defaultMaxListeners = 10

exports.on = function on(emitter, name, opts = {}) {
  const { signal } = opts

  if (signal && signal.aborted) {
    throw errors.OPERATION_ABORTED(signal.reason)
  }

  let error = null
  let done = false

  const events = []
  const promises = []

  if (name !== 'error') emitter.on('error', onerror)

  if (signal) signal.addEventListener('abort', onabort)

  emitter.on(name, onevent)

  return {
    next() {
      if (events.length) {
        return Promise.resolve({ value: events.shift(), done: false })
      }

      if (error) {
        const err = error

        error = null

        return Promise.reject(err)
      }

      if (done) return onclose()

      return new Promise((resolve, reject) => promises.push({ resolve, reject }))
    },

    return() {
      return onclose()
    },

    throw(err) {
      return onerror(err)
    },

    [Symbol.asyncIterator]() {
      return this
    }
  }

  function onevent(...args) {
    if (promises.length) {
      promises.shift().resolve({ value: args, done: false })
    } else {
      events.push(args)
    }
  }

  function onerror(err) {
    if (promises.length) {
      promises.shift().reject(err)
    } else {
      error = err
    }

    return onclose()
  }

  function onabort() {
    return onerror(errors.OPERATION_ABORTED(signal.reason))
  }

  function onclose() {
    emitter.off(name, onevent)

    if (name !== 'error') emitter.off('error', onerror)

    if (signal) signal.removeEventListener('abort', onabort)

    done = true

    const result = { done: true }

    while (promises.length) promises.shift().resolve(result)

    return Promise.resolve(result)
  }
}

exports.once = function once(emitter, name, opts = {}) {
  const { signal } = opts

  if (signal && signal.aborted) {
    return Promise.reject(errors.OPERATION_ABORTED(signal.reason))
  }

  return new Promise((resolve, reject) => {
    if (name !== 'error') emitter.on('error', onerror)

    if (signal) signal.addEventListener('abort', onabort)

    emitter.once(name, onevent)

    function onevent(...args) {
      if (name !== 'error') emitter.off('error', onerror)

      if (signal) signal.removeEventListener('abort', onabort)

      resolve(args)
    }

    function onerror(err) {
      emitter.off(name, onevent)

      if (name !== 'error') emitter.off('error', onerror)

      if (signal) signal.removeEventListener('abort', onabort)

      reject(err)
    }

    function onabort() {
      onerror(errors.OPERATION_ABORTED(signal.reason))
    }
  })
}

exports.forward = function forward(from, to, names, opts = {}) {
  if (Array.isArray(names) === false) names = [names]

  const { emit = to.emit.bind(to) } = opts

  const listeners = names.map(
    (name) =>
      function onevent(...args) {
        emit(name, ...args)
      }
  )

  for (let i = 0, n = names.length; i < n; i++) {
    if (to.listenerCount(names[i]) > 0) from.on(names[i], listeners[i])
  }

  to.on('newListener', (name) => {
    const i = names.indexOf(name)

    if (i !== -1 && to.listenerCount(name) === 0) {
      from.on(name, listeners[i])
    }
  }).on('removeListener', (name) => {
    const i = names.indexOf(name)

    if (i !== -1 && to.listenerCount(name) === 0) {
      from.off(name, listeners[i])
    }
  })
}

exports.listenerCount = function listenerCount(emitter, name) {
  return emitter.listenerCount(name)
}

exports.getMaxListeners = function getMaxListeners(emitter) {
  if (typeof emitter.getMaxListeners === 'function') {
    return emitter.getMaxListeners()
  }

  return exports.defaultMaxListeners
}

exports.setMaxListeners = function setMaxListeners(n, ...emitters) {
  if (emitters.length === 0) exports.defaultMaxListeners = n
  else {
    for (const emitter of emitters) {
      if (typeof emitter.setMaxListeners === 'function') {
        emitter.setMaxListeners(n)
      }
    }
  }
}
module.exports = class EventEmitterError extends Error {
  constructor(msg, fn = EventEmitterError, opts = {}) {
    const { code = fn.name } = opts

    super(`${code}: ${msg}`, opts)

    this.code = code

    if (Error.captureStackTrace) Error.captureStackTrace(this, fn)
  }

  get name() {
    return 'EventEmitterError'
  }

  static OPERATION_ABORTED(cause, msg = 'Operation aborted') {
    return new EventEmitterError(msg, EventEmitterError.OPERATION_ABORTED, { cause })
  }

  static UNHANDLED_ERROR(cause, msg = 'Unhandled error') {
    return new EventEmitterError(msg, EventEmitterError.UNHANDLED_ERROR, { cause })
  }
}
{
  "name": "bare-events",
  "version": "2.9.2",
  "description": "Event emitters for JavaScript",
  "exports": {
    "./package": "./package.json",
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    },
    "./global": {
      "types": "./global.d.ts",
      "default": "./global.js"
    },
    "./web": {
      "types": "./web.d.ts",
      "default": "./web.js"
    },
    "./errors": "./lib/errors.js"
  },
  "files": [
    "index.js",
    "index.d.ts",
    "global.js",
    "global.d.ts",
    "web.js",
    "web.d.ts",
    "lib"
  ],
  "scripts": {
    "format": "prettier --write . && lunte --fix",
    "lint": "prettier --check . && lunte",
    "test": "npm run test:bare && npm run test:node",
    "test:bare": "brittle-bare --coverage test.js",
    "test:node": "brittle-node --coverage test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-events.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-events/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-events#readme",
  "devDependencies": {
    "bare-abort-controller": "^1.0.0",
    "brittle": "^3.3.2",
    "lunte": "^1.8.0",
    "prettier": "^3.4.2",
    "prettier-config-holepunch": "^2.0.0",
    "uncaughts": "^1.1.1"
  },
  "peerDependencies": {
    "bare-abort-controller": "*"
  },
  "peerDependenciesMeta": {
    "bare-abort-controller": {
      "optional": true
    }
  }
}
module.exports = require.addon()
const FIFO = require('fast-fifo')
const EventEmitter = require('bare-events')
const path = require('bare-path')
const { isURL, fileURLToPath } = require('bare-url')
const { Readable, Writable } = require('bare-stream')
const binding = require('./binding')
const constants = require('./lib/constants')
const FileError = require('./lib/errors')

const isWindows = Bare.platform === 'win32'

exports.constants = constants

class FileRequest {
  static borrow() {
    if (this._free.length > 0) return this._free.pop()
    return new FileRequest()
  }

  static return(req) {
    if (this._free.length < 32) this._free.push(req.reset())
    else req.destroy()
  }

  constructor() {
    this._reset()
    this._handle = binding.requestInit(this, this._onresult)
  }

  get handle() {
    return this._handle
  }

  retain(value) {
    this._retain = value // Tie the lifetime of `value` to the lifetime of `this`
  }

  reset() {
    if (this._handle === null) return this

    binding.requestReset(this._handle)

    this._reset()

    return this
  }

  destroy() {
    if (this._handle === null) return this

    binding.requestDestroy(this._handle)

    this._reset()
    this._handle = null

    return this
  }

  then(resolve, reject) {
    return this._promise.then(resolve, reject)
  }

  return() {
    if (this._handle === null) return this

    FileRequest.return(this)

    return this
  }

  _reset() {
    this._promise = new Promise((resolve, reject) => {
      this._resolve = resolve
      this._reject = reject
    })
    this._retain = null
  }

  _onresult(err, status) {
    if (err) this._reject(err)
    else this._resolve(status)
  }
}

FileRequest._free = []

function ok(result, cb) {
  if (typeof result === 'function') {
    cb = result
    result = undefined
  }

  if (cb) cb(null, result)
  else return result
}

function fail(err, cb) {
  if (cb) cb(err)
  else throw err
}

function done(err, result, cb) {
  if (typeof result === 'function') {
    cb = result
    result = undefined
  }

  if (err) fail(err, cb)
  else return ok(result, cb)
}

async function open(filepath, flags = 'r', mode = 0o666, cb) {
  if (typeof flags === 'function') {
    cb = flags
    flags = 'r'
    mode = 0o666
  } else if (typeof mode === 'function') {
    cb = mode
    mode = 0o666
  }

  if (typeof flags === 'string') flags = toFlags(flags)
  if (typeof mode === 'string') mode = toMode(mode)

  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  let fd
  let err = null
  try {
    binding.open(req.handle, filepath, flags, mode)

    fd = await req
  } catch (e) {
    err = new FileError(e.message, {
      operation: 'open',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }

  return done(err, fd, cb)
}

function openSync(filepath, flags = 'r', mode = 0o666) {
  if (typeof flags === 'string') flags = toFlags(flags)
  if (typeof mode === 'string') mode = toMode(mode)

  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  try {
    return binding.openSync(req.handle, filepath, flags, mode)
  } catch (e) {
    throw new FileError(e.message, {
      operation: 'open',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }
}

async function close(fd, cb) {
  const req = FileRequest.borrow()

  let err = null
  try {
    binding.close(req.handle, fd)

    await req
  } catch (e) {
    err = new FileError(e.message, { operation: 'close', code: e.code, fd })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function closeSync(fd) {
  const req = FileRequest.borrow()

  try {
    binding.closeSync(req.handle, fd)
  } catch (e) {
    throw new FileError(e.message, { operation: 'close', code: e.code, fd })
  } finally {
    req.return()
  }
}

async function access(filepath, mode = constants.F_OK, cb) {
  if (typeof mode === 'function') {
    cb = mode
    mode = constants.F_OK
  }

  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  let err = null
  try {
    binding.access(req.handle, filepath, mode)

    await req
  } catch (e) {
    err = new FileError(e.message, {
      operation: 'access',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function accessSync(filepath, mode = constants.F_OK) {
  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  try {
    binding.accessSync(req.handle, filepath, mode)
  } catch (e) {
    throw new FileError(e.message, {
      operation: 'access',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }
}

async function exists(filepath, cb) {
  let ok = true
  try {
    await access(filepath)
  } catch {
    ok = false
  }

  return done(null, ok, cb)
}

function existsSync(filepath) {
  try {
    accessSync(filepath)
  } catch {
    return false
  }

  return true
}

async function read(fd, buffer, offset = 0, len = buffer.byteLength - offset, pos = -1, cb) {
  if (typeof offset === 'function') {
    cb = offset
    offset = 0
    len = buffer.byteLength
    pos = -1
  } else if (typeof len === 'function') {
    cb = len
    len = buffer.byteLength - offset
    pos = -1
  } else if (typeof pos === 'function') {
    cb = pos
    pos = -1
  }

  if (typeof pos !== 'number') pos = -1

  const req = FileRequest.borrow()

  let bytes
  let err = null
  try {
    binding.read(req.handle, fd, buffer, offset, len, pos)

    req.retain(buffer)

    bytes = await req
  } catch (e) {
    err = new FileError(e.message, { operation: 'read', code: e.code, fd })
  } finally {
    req.return()
  }

  return done(err, bytes, cb)
}

function readSync(fd, buffer, offset = 0, len = buffer.byteLength - offset, pos = -1) {
  const req = FileRequest.borrow()

  try {
    return binding.readSync(req.handle, fd, buffer, offset, len, pos)
  } catch (e) {
    throw new FileError(e.message, { operation: 'read', code: e.code, fd })
  } finally {
    req.return()
  }
}

async function readv(fd, buffers, pos = -1, cb) {
  if (typeof pos === 'function') {
    cb = pos
    pos = -1
  }

  if (typeof pos !== 'number') pos = -1

  const req = FileRequest.borrow()

  let bytes
  let err = null
  try {
    binding.readv(req.handle, fd, buffers, pos)

    req.retain(buffers)

    bytes = await req
  } catch (e) {
    err = new FileError(e.message, { operation: 'readv', code: e.code, fd })
  } finally {
    req.return()
  }

  return done(err, bytes, cb)
}

function readvSync(fd, buffers, pos = -1) {
  if (typeof pos !== 'number') pos = -1

  const req = FileRequest.borrow()

  try {
    return binding.readvSync(req.handle, fd, buffers, pos)
  } catch (e) {
    throw new FileError(e.message, { operation: 'readv', code: e.code, fd })
  } finally {
    req.return()
  }
}

async function write(fd, data, offset, len, pos = -1, cb) {
  if (typeof data === 'string') {
    let encoding = len
    cb = pos
    pos = offset

    if (typeof pos === 'function') {
      cb = pos
      pos = -1
      encoding = 'utf8'
    } else if (typeof encoding === 'function') {
      cb = encoding
      encoding = 'utf8'
    }

    if (typeof pos === 'string') {
      encoding = pos
      pos = -1
    }

    data = Buffer.from(data, encoding)
    offset = 0
    len = data.byteLength
  } else if (typeof offset === 'function') {
    cb = offset
    offset = 0
    len = data.byteLength
    pos = -1
  } else if (typeof len === 'function') {
    cb = len
    len = data.byteLength - offset
    pos = -1
  } else if (typeof pos === 'function') {
    cb = pos
    pos = -1
  }

  if (typeof offset !== 'number') offset = 0
  if (typeof len !== 'number') len = data.byteLength - offset
  if (typeof pos !== 'number') pos = -1

  const req = FileRequest.borrow()

  let bytes
  let err = null
  try {
    binding.write(req.handle, fd, data, offset, len, pos)

    req.retain(data)

    bytes = await req
  } catch (e) {
    err = new FileError(e.message, { operation: 'write', code: e.code, fd })
  } finally {
    req.return()
  }

  return done(err, bytes, cb)
}

function writeSync(fd, data, offset, len, pos = -1) {
  if (typeof data === 'string') {
    let encoding = len
    pos = offset

    if (typeof pos === 'string') {
      encoding = pos
      pos = -1
    }

    data = Buffer.from(data, encoding)
    offset = 0
    len = data.byteLength
  }

  if (typeof offset !== 'number') offset = 0
  if (typeof len !== 'number') len = data.byteLength - offset
  if (typeof pos !== 'number') pos = -1

  const req = FileRequest.borrow()

  try {
    return binding.writeSync(req.handle, fd, data, offset, len, pos)
  } catch (e) {
    throw new FileError(e.message, { operation: 'write', code: e.code, fd })
  } finally {
    req.return()
  }
}

async function writev(fd, buffers, pos = -1, cb) {
  if (typeof pos === 'function') {
    cb = pos
    pos = -1
  }

  if (typeof pos !== 'number') pos = -1

  const req = FileRequest.borrow()

  let bytes
  let err = null
  try {
    binding.writev(req.handle, fd, buffers, pos)

    req.retain(buffers)

    bytes = await req
  } catch (e) {
    err = new FileError(e.message, { operation: 'writev', code: e.code, fd })
  } finally {
    req.return()
  }

  return done(err, bytes, cb)
}

function writevSync(fd, buffers, pos = -1) {
  if (typeof pos !== 'number') pos = -1

  const req = FileRequest.borrow()

  try {
    return binding.writevSync(req.handle, fd, buffers, pos)
  } catch (e) {
    throw new FileError(e.message, { operation: 'writev', code: e.code, fd })
  } finally {
    req.return()
  }
}

async function stat(filepath, cb) {
  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  let st
  let err = null
  try {
    binding.stat(req.handle, filepath)

    await req

    st = new Stats(...binding.requestResultStat(req.handle))
  } catch (e) {
    err = new FileError(e.message, { operation: 'stat', code: e.code, path: filepath })
  } finally {
    req.return()
  }

  return done(err, st, cb)
}

function statSync(filepath) {
  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  try {
    binding.statSync(req.handle, filepath)

    return new Stats(...binding.requestResultStat(req.handle))
  } catch (e) {
    throw new FileError(e.message, { operation: 'stat', code: e.code, path: filepath })
  } finally {
    req.return()
  }
}

async function lstat(filepath, cb) {
  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  let st
  let err = null
  try {
    binding.lstat(req.handle, filepath)

    await req

    st = new Stats(...binding.requestResultStat(req.handle))
  } catch (e) {
    err = new FileError(e.message, { operation: 'lstat', code: e.code, path: filepath })
  } finally {
    req.return()
  }

  return done(err, st, cb)
}

function lstatSync(filepath) {
  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  try {
    binding.lstatSync(req.handle, filepath)

    return new Stats(...binding.requestResultStat(req.handle))
  } catch (e) {
    throw new FileError(e.message, { operation: 'lstat', code: e.code, path: filepath })
  } finally {
    req.return()
  }
}

async function fstat(fd, cb) {
  const req = FileRequest.borrow()

  let st
  let err = null
  try {
    binding.fstat(req.handle, fd)

    await req

    st = new Stats(...binding.requestResultStat(req.handle))
  } catch (e) {
    err = new FileError(e.message, { operation: 'fstat', code: e.code, fd })
  } finally {
    req.return()
  }

  return done(err, st, cb)
}

function fstatSync(fd) {
  const req = FileRequest.borrow()

  try {
    binding.fstatSync(req.handle, fd)

    return new Stats(...binding.requestResultStat(req.handle))
  } catch (e) {
    throw new FileError(e.message, { operation: 'fstat', code: e.code, fd })
  } finally {
    req.return()
  }
}

async function statfs(filepath, cb) {
  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  let st
  let err = null
  try {
    binding.statfs(req.handle, filepath)

    await req

    st = new StatFs(...binding.requestResultStatfs(req.handle))
  } catch (e) {
    err = new FileError(e.message, { operation: 'statfs', code: e.code, path: filepath })
  } finally {
    req.return()
  }

  return done(err, st, cb)
}

function statfsSync(filepath) {
  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  try {
    binding.statfsSync(req.handle, filepath)

    return new StatFs(...binding.requestResultStatfs(req.handle))
  } catch (e) {
    throw new FileError(e.message, { operation: 'statfs', code: e.code, path: filepath })
  } finally {
    req.return()
  }
}

async function ftruncate(fd, len = 0, cb) {
  if (typeof len === 'function') {
    cb = len
    len = 0
  }

  if (typeof len !== 'number') len = 0

  const req = FileRequest.borrow()

  let err = null
  try {
    binding.ftruncate(req.handle, fd, len)

    await req
  } catch (e) {
    err = new FileError(e.message, { operation: 'ftruncate', code: e.code, fd })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function ftruncateSync(fd, len = 0) {
  if (typeof len !== 'number') len = 0

  const req = FileRequest.borrow()

  try {
    binding.ftruncateSync(req.handle, fd, len)
  } catch (e) {
    throw new FileError(e.message, { operation: 'ftruncate', code: e.code, fd })
  } finally {
    req.return()
  }
}

async function truncate(filepath, len, cb) {
  let fd = -1
  let err

  try {
    fd = await open(filepath, 'r+')

    await ftruncate(fd, len)
  } catch (e) {
    err = e
  } finally {
    if (fd !== -1) await close(fd)
  }

  return done(err, cb)
}

function truncateSync(filepath, len) {
  let fd = -1
  let err

  try {
    fd = openSync(filepath, 'r+')

    ftruncateSync(fd, len)
  } catch (e) {
    err = e
  } finally {
    if (fd !== -1) closeSync(fd)
  }
}

async function chmod(filepath, mode, cb) {
  if (typeof mode === 'string') mode = toMode(mode)

  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  let err = null
  try {
    binding.chmod(req.handle, filepath, mode)

    await req
  } catch (e) {
    err = new FileError(e.message, {
      operation: 'chmod',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function chmodSync(filepath, mode) {
  if (typeof mode === 'string') mode = toMode(mode)

  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  try {
    binding.chmodSync(req.handle, filepath, mode)
  } catch (e) {
    throw new FileError(e.message, {
      operation: 'chmod',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }
}

async function fchmod(fd, mode, cb) {
  if (typeof mode === 'string') mode = toMode(mode)

  const req = FileRequest.borrow()

  let err = null
  try {
    binding.fchmod(req.handle, fd, mode)

    await req
  } catch (e) {
    err = new FileError(e.message, { operation: 'fchmod', code: e.code, fd })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function fchmodSync(fd, mode) {
  if (typeof mode === 'string') mode = toMode(mode)

  const req = FileRequest.borrow()

  try {
    binding.fchmodSync(req.handle, fd, mode)
  } catch (e) {
    throw new FileError(e.message, { operation: 'fchmod', code: e.code, fd })
  } finally {
    req.return()
  }
}

async function chown(filepath, uid, gid, cb) {
  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  let err = null
  try {
    binding.chown(req.handle, filepath, uid, gid)

    await req
  } catch (e) {
    err = new FileError(e.message, { operation: 'chown', code: e.code, path: filepath })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function chownSync(filepath, uid, gid) {
  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  try {
    binding.chownSync(req.handle, filepath, uid, gid)
  } catch (e) {
    throw new FileError(e.message, { operation: 'chownSync', code: e.code, path: filepath })
  } finally {
    req.return()
  }
}

async function lchown(filepath, uid, gid, cb) {
  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  let err = null
  try {
    binding.lchown(req.handle, filepath, uid, gid)

    await req
  } catch (e) {
    err = new FileError(e.message, { operation: 'lchown', code: e.code, path: filepath })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function lchownSync(filepath, uid, gid) {
  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  try {
    binding.lchownSync(req.handle, filepath, uid, gid)
  } catch (e) {
    throw new FileError(e.message, { operation: 'lchownSync', code: e.code, path: filepath })
  } finally {
    req.return()
  }
}

async function fchown(fd, uid, gid, cb) {
  const req = FileRequest.borrow()

  let err = null
  try {
    binding.fchown(req.handle, fd, uid, gid)

    await req
  } catch (e) {
    err = new FileError(e.message, { operation: 'fchown', code: e.code, fd })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function fchownSync(fd, uid, gid) {
  const req = FileRequest.borrow()

  try {
    binding.fchownSync(req.handle, fd, uid, gid)
  } catch (e) {
    throw new FileError(e.message, { operation: 'fchownSync', code: e.code, fd })
  } finally {
    req.return()
  }
}

async function utimes(filepath, atime, mtime, cb) {
  if (typeof atime !== 'number') atime = atime.getTime() / 1000
  if (typeof mtime !== 'number') mtime = mtime.getTime() / 1000

  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  let err = null
  try {
    binding.utimes(req.handle, filepath, atime, mtime)

    await req
  } catch (e) {
    err = new FileError(e.message, { operation: 'utimes', code: e.code, path: filepath })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function utimesSync(filepath, atime, mtime) {
  if (typeof atime !== 'number') atime = atime.getTime() / 1000
  if (typeof mtime !== 'number') mtime = mtime.getTime() / 1000

  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  try {
    binding.utimesSync(req.handle, filepath, atime, mtime)
  } catch (e) {
    throw new FileError(e.message, { operation: 'utimes', code: e.code, path: filepath })
  } finally {
    req.return()
  }
}

async function lutimes(filepath, atime, mtime, cb) {
  if (typeof atime !== 'number') atime = atime.getTime() / 1000
  if (typeof mtime !== 'number') mtime = mtime.getTime() / 1000

  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  let err = null
  try {
    binding.lutimes(req.handle, filepath, atime, mtime)

    await req
  } catch (e) {
    err = new FileError(e.message, { operation: 'lutimes', code: e.code, path: filepath })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function lutimesSync(filepath, atime, mtime) {
  if (typeof atime !== 'number') atime = atime.getTime() / 1000
  if (typeof mtime !== 'number') mtime = mtime.getTime() / 1000

  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  try {
    binding.lutimesSync(req.handle, filepath, atime, mtime)
  } catch (e) {
    throw new FileError(e.message, { operation: 'lutimes', code: e.code, path: filepath })
  } finally {
    req.return()
  }
}

async function futimes(fd, atime, mtime, cb) {
  if (typeof atime !== 'number') atime = atime.getTime() / 1000
  if (typeof mtime !== 'number') mtime = mtime.getTime() / 1000

  const req = FileRequest.borrow()

  let err = null
  try {
    binding.futimes(req.handle, fd, atime, mtime)

    await req
  } catch (e) {
    err = new FileError(e.message, { operation: 'futimes', code: e.code, fd })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function futimesSync(fd, atime, mtime) {
  if (typeof atime !== 'number') atime = atime.getTime() / 1000
  if (typeof mtime !== 'number') mtime = mtime.getTime() / 1000

  const req = FileRequest.borrow()

  try {
    binding.futimesSync(req.handle, fd, atime, mtime)
  } catch (e) {
    throw new FileError(e.message, { operation: 'futimesSync', code: e.code, fd })
  } finally {
    req.return()
  }
}

async function link(src, dst, cb) {
  src = toNamespacedPath(src)
  dst = toNamespacedPath(dst)

  const req = FileRequest.borrow()

  let err = null
  try {
    binding.link(req.handle, src, dst)

    await req
  } catch (e) {
    err = new FileError(e.message, { operation: 'link', code: e.code, path: src, destination: dst })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function linkSync(src, dst) {
  src = toNamespacedPath(src)
  dst = toNamespacedPath(dst)

  const req = FileRequest.borrow()

  try {
    binding.linkSync(req.handle, src, dst)
  } catch (e) {
    throw new FileError(e.message, {
      operation: 'linkSync',
      code: e.code,
      path: src,
      destination: dst
    })
  } finally {
    req.return()
  }
}

async function mkdir(filepath, opts, cb) {
  if (typeof opts === 'function') {
    cb = opts
    opts = { mode: 0o777 }
  }

  if (typeof opts === 'number') opts = { mode: opts }
  else if (!opts) opts = {}

  const mode = typeof opts.mode === 'number' ? opts.mode : 0o777

  filepath = toNamespacedPath(filepath)

  if (opts.recursive) {
    let err = null
    try {
      try {
        await mkdir(filepath, { mode })
      } catch (err) {
        if (err.code !== 'ENOENT') {
          if (!(await stat(filepath)).isDirectory()) throw err
        } else {
          while (filepath.endsWith(path.sep)) filepath = filepath.slice(0, -1)
          const i = filepath.lastIndexOf(path.sep)
          if (i <= 0) throw err

          await mkdir(filepath.slice(0, i), { mode, recursive: true })

          try {
            await mkdir(filepath, { mode })
          } catch (err) {
            if (!(await stat(filepath)).isDirectory()) throw err
          }
        }
      }
    } catch (e) {
      err = e
    }

    return done(err, cb)
  }

  const req = FileRequest.borrow()

  let err = null
  try {
    binding.mkdir(req.handle, filepath, mode)

    await req
  } catch (e) {
    err = new FileError(e.message, {
      operation: 'mkdir',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function mkdirSync(filepath, opts) {
  if (typeof opts === 'number') opts = { mode: opts }
  else if (!opts) opts = {}

  const mode = typeof opts.mode === 'number' ? opts.mode : 0o777

  filepath = toNamespacedPath(filepath)

  if (opts.recursive) {
    try {
      mkdirSync(filepath, { mode })
    } catch (err) {
      if (err.code !== 'ENOENT') {
        if (!statSync(filepath).isDirectory()) throw err
      } else {
        while (filepath.endsWith(path.sep)) filepath = filepath.slice(0, -1)
        const i = filepath.lastIndexOf(path.sep)
        if (i <= 0) throw err

        mkdirSync(filepath.slice(0, i), { mode, recursive: true })

        try {
          mkdirSync(filepath, { mode })
        } catch (err) {
          if (!statSync(filepath).isDirectory()) throw err
        }
      }
    }

    return
  }

  const req = FileRequest.borrow()

  try {
    binding.mkdirSync(req.handle, filepath, mode)
  } catch (e) {
    throw new FileError(e.message, {
      operation: 'mkdir',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }
}

async function mkdtemp(prefix, cb) {
  prefix = toNamespacedPath(prefix)

  const req = FileRequest.borrow()

  let res
  let err = null
  try {
    binding.mkdtemp(req.handle, prefix + 'XXXXXX')

    await req

    res = binding.requestResultPath(req.handle)
  } catch (e) {
    err = new FileError(e.message, { operation: 'mkdtemp', code: e.code, path: prefix })
  } finally {
    req.return()
  }

  return done(err, res, cb)
}

function mkdtempSync(prefix) {
  prefix = toNamespacedPath(prefix)

  const req = FileRequest.borrow()

  try {
    binding.mkdtempSync(req.handle, prefix + 'XXXXXX')

    return binding.requestResultPath(req.handle)
  } catch (e) {
    throw new FileError(e.message, { operation: 'mkdtempSync', code: e.code, path: prefix })
  } finally {
    req.return()
  }
}

async function rmdir(filepath, cb) {
  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  let err = null
  try {
    binding.rmdir(req.handle, filepath)

    await req
  } catch (e) {
    err = new FileError(e.message, {
      operation: 'rmdir',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function rmdirSync(filepath) {
  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  try {
    binding.rmdirSync(req.handle, filepath)
  } catch (e) {
    throw new FileError(e.message, {
      operation: 'rmdir',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }
}

async function rm(filepath, opts, cb) {
  if (typeof opts === 'function') {
    cb = opts
    opts = {}
  }

  if (!opts) opts = {}

  filepath = toNamespacedPath(filepath)

  let err = null
  try {
    const st = await lstat(filepath)

    if (st.isDirectory()) {
      if (opts.recursive) {
        try {
          await rmdir(filepath)
        } catch (err) {
          if (err.code !== 'ENOTEMPTY') throw err

          const files = await readdir(filepath)

          for (const file of files) {
            await rm(filepath + path.sep + file, opts)
          }

          await rmdir(filepath)
        }
      } else {
        throw new FileError('is a directory', {
          operation: 'rm',
          code: 'EISDIR',
          path: filepath
        })
      }
    } else {
      await unlink(filepath)
    }
  } catch (e) {
    if (e.code !== 'ENOENT' || !opts.force) err = e
  }

  return done(err, cb)
}

function rmSync(filepath, opts) {
  if (!opts) opts = {}

  filepath = toNamespacedPath(filepath)

  try {
    const st = lstatSync(filepath)

    if (st.isDirectory()) {
      if (opts.recursive) {
        try {
          rmdirSync(filepath)
        } catch (err) {
          if (err.code !== 'ENOTEMPTY') throw err

          const files = readdirSync(filepath)

          for (const file of files) {
            rmSync(filepath + path.sep + file, opts)
          }

          rmdirSync(filepath)
        }
      } else {
        throw new FileError('is a directory', {
          operation: 'rm',
          code: 'EISDIR',
          path: filepath
        })
      }
    } else {
      unlinkSync(filepath)
    }
  } catch (err) {
    if (err.code !== 'ENOENT' || !opts.force) throw err
  }
}

async function unlink(filepath, cb) {
  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  let err = null
  try {
    binding.unlink(req.handle, filepath)

    await req
  } catch (e) {
    err = new FileError(e.message, {
      operation: 'unlink',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function unlinkSync(filepath) {
  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  try {
    binding.unlinkSync(req.handle, filepath)
  } catch (e) {
    throw new FileError(e.message, {
      operation: 'unlink',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }
}

async function rename(src, dst, cb) {
  src = toNamespacedPath(src)
  dst = toNamespacedPath(dst)

  const req = FileRequest.borrow()

  let err = null
  try {
    binding.rename(req.handle, src, dst)

    await req
  } catch (e) {
    err = new FileError(e.message, {
      operation: 'rename',
      code: e.code,
      path: src,
      destination: dst
    })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function renameSync(src, dst) {
  src = toNamespacedPath(src)
  dst = toNamespacedPath(dst)

  const req = FileRequest.borrow()

  try {
    binding.renameSync(req.handle, src, dst)
  } catch (e) {
    throw new FileError(e.message, {
      operation: 'rename',
      code: e.code,
      path: src,
      destination: dst
    })
  } finally {
    req.return()
  }
}

async function copyFile(src, dst, mode = 0, cb) {
  if (typeof mode === 'function') {
    cb = mode
    mode = 0
  }

  src = toNamespacedPath(src)
  dst = toNamespacedPath(dst)

  const req = FileRequest.borrow()

  let err = null
  try {
    binding.copyfile(req.handle, src, dst, mode)

    await req
  } catch (e) {
    err = new FileError(e.message, {
      operation: 'copyfile',
      code: e.code,
      path: src,
      destination: dst
    })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function copyFileSync(src, dst, mode = 0) {
  src = toNamespacedPath(src)
  dst = toNamespacedPath(dst)

  const req = FileRequest.borrow()

  try {
    binding.copyfileSync(req.handle, src, dst, mode)
  } catch (e) {
    throw new FileError(e.message, {
      operation: 'copyfile',
      code: e.code,
      path: src,
      destination: dst
    })
  } finally {
    req.return()
  }
}

async function cp(src, dst, opts, cb) {
  if (typeof opts === 'function') {
    cb = opts
    opts = {}
  }

  if (!opts) opts = {}

  src = toNamespacedPath(src)
  dst = toNamespacedPath(dst)

  let err = null
  try {
    const st = await lstat(src)

    if (st.isDirectory()) {
      if (opts.recursive !== true) {
        throw new FileError('is a directory', { operation: 'cp', code: 'EISDIR', path: src })
      }

      try {
        await lstat(dst)
      } catch (e) {
        if (e.code !== 'ENOENT') throw e

        await mkdir(dst, { mode: st.mode, recursive: true })
      }

      const dir = await opendir(src)
      for await (const { name } of dir) {
        await cp(path.join(src, name), path.join(dst, name), opts)
      }
    } else if (st.isFile()) {
      await copyFile(src, dst)
      await chmod(dst, st.mode)
    }
  } catch (e) {
    err = e
  }

  return done(err, cb)
}

function cpSync(src, dst, opts = {}) {
  src = toNamespacedPath(src)
  dst = toNamespacedPath(dst)

  const st = lstatSync(src)

  if (st.isDirectory()) {
    if (opts.recursive !== true) {
      throw new FileError('is a directory', { operation: 'cp', code: 'EISDIR', path: src })
    }

    try {
      lstatSync(dst)
    } catch (e) {
      if (e.code !== 'ENOENT') throw e

      mkdirSync(dst, { mode: st.mode, recursive: true })
    }

    const dir = opendirSync(src)
    for (const { name } of dir) {
      cpSync(path.join(src, name), path.join(dst, name), opts)
    }
  } else if (st.isFile()) {
    copyFileSync(src, dst)
    chmodSync(dst, st.mode)
  }
}

async function realpath(filepath, opts, cb) {
  if (typeof opts === 'function') {
    cb = opts
    opts = {}
  }

  if (typeof opts === 'string') opts = { encoding: opts }
  else if (!opts) opts = {}

  const { encoding = 'utf8' } = opts

  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  let res
  let err = null
  try {
    binding.realpath(req.handle, filepath)

    await req

    res = Buffer.from(binding.requestResultString(req.handle))

    if (encoding !== 'buffer') res = res.toString(encoding)
  } catch (e) {
    err = new FileError(e.message, {
      operation: 'realpath',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }

  return done(err, res, cb)
}

function realpathSync(filepath, opts) {
  if (typeof opts === 'string') opts = { encoding: opts }
  else if (!opts) opts = {}

  const { encoding = 'utf8' } = opts

  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  try {
    binding.realpathSync(req.handle, filepath)

    let res = Buffer.from(binding.requestResultString(req.handle))

    if (encoding !== 'buffer') res = res.toString(encoding)

    return res
  } catch (e) {
    throw new FileError(e.message, {
      operation: 'realpath',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }
}

async function readlink(filepath, opts, cb) {
  if (typeof opts === 'function') {
    cb = opts
    opts = {}
  }

  if (typeof opts === 'string') opts = { encoding: opts }
  else if (!opts) opts = {}

  const { encoding = 'utf8' } = opts

  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  let res
  let err = null
  try {
    binding.readlink(req.handle, filepath)

    await req

    res = Buffer.from(binding.requestResultString(req.handle))

    if (encoding !== 'buffer') res = res.toString(encoding)
  } catch (e) {
    err = new FileError(e.message, {
      operation: 'readlink',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }

  return done(err, res, cb)
}

function readlinkSync(filepath, opts) {
  if (typeof opts === 'string') opts = { encoding: opts }
  else if (!opts) opts = {}

  const { encoding = 'utf8' } = opts

  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  try {
    binding.readlinkSync(req.handle, filepath)

    let res = Buffer.from(binding.requestResultString(req.handle))

    if (encoding !== 'buffer') res = res.toString(encoding)

    return res
  } catch (e) {
    throw new FileError(e.message, {
      operation: 'readlink',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }
}

async function fsync(fd, cb) {
  const req = FileRequest.borrow()

  let err = null
  try {
    binding.fsync(req.handle, fd)

    await req
  } catch (e) {
    err = new FileError(e.message, { operation: 'fsync', code: e.code, fd })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function fsyncSync(fd) {
  const req = FileRequest.borrow()

  try {
    binding.fsyncSync(req.handle, fd)
  } catch (e) {
    throw new FileError(e.message, { operation: 'fsyncSync', code: e.code, fd })
  } finally {
    req.return()
  }
}

async function fdatasync(fd, cb) {
  const req = FileRequest.borrow()

  let err = null
  try {
    binding.fdatasync(req.handle, fd)

    await req
  } catch (e) {
    err = new FileError(e.message, { operation: 'fdatasync', code: e.code, fd })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function fdatasyncSync(fd) {
  const req = FileRequest.borrow()

  try {
    binding.fdatasyncSync(req.handle, fd)
  } catch (e) {
    throw new FileError(e.message, { operation: 'fdatasyncSync', code: e.code, fd })
  } finally {
    req.return()
  }
}

function normalizeSymlinkTarget(target, type, filepath) {
  if (isWindows) {
    if (type === constants.UV_FS_SYMLINK_JUNCTION) target = path.resolve(filepath, '..', target)

    if (path.isAbsolute(target)) return path.toNamespacedPath(target)

    return target.replace(/\//g, path.sep)
  }

  return target
}

async function symlink(target, filepath, type, cb) {
  if (typeof type === 'function') {
    cb = type
    type = null
  }

  filepath = toNamespacedPath(filepath)

  if (typeof type === 'string') {
    switch (type) {
      case 'dir':
        type = constants.UV_FS_SYMLINK_DIR
        break
      case 'junction':
        type = constants.UV_FS_SYMLINK_JUNCTION
        break
      case 'file':
      default:
        type = 0
        break
    }
  } else if (typeof type !== 'number') {
    if (isWindows) {
      target = path.resolve(filepath, '..', target)

      try {
        type = (await stat(target)).isDirectory()
          ? constants.UV_FS_SYMLINK_DIR
          : constants.UV_FS_SYMLINK_JUNCTION
      } catch {
        type = 0
      }
    } else {
      type = 0
    }
  }

  target = normalizeSymlinkTarget(target, type, filepath)

  const req = FileRequest.borrow()

  let err = null
  try {
    binding.symlink(req.handle, target, filepath, type)

    await req
  } catch (e) {
    err = new FileError(e.message, {
      operation: 'symlink',
      code: e.code,
      path: target,
      destination: filepath
    })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function symlinkSync(target, filepath, type) {
  filepath = toNamespacedPath(filepath)

  if (typeof type === 'string') {
    switch (type) {
      case 'dir':
        type = constants.UV_FS_SYMLINK_DIR
        break
      case 'junction':
        type = constants.UV_FS_SYMLINK_JUNCTION
        break
      case 'file':
      default:
        type = 0
        break
    }
  } else if (typeof type !== 'number') {
    if (isWindows) {
      target = path.resolve(filepath, '..', target)

      try {
        type = statSync(target).isDirectory()
          ? constants.UV_FS_SYMLINK_DIR
          : constants.UV_FS_SYMLINK_JUNCTION
      } catch {
        type = 0
      }
    } else {
      type = 0
    }
  }

  target = normalizeSymlinkTarget(target, type, filepath)

  const req = FileRequest.borrow()

  try {
    binding.symlinkSync(req.handle, target, filepath, type)
  } catch (e) {
    throw new FileError(e.message, {
      operation: 'symlink',
      code: e.code,
      path: target,
      destination: filepath
    })
  } finally {
    req.return()
  }
}

async function opendir(filepath, opts, cb) {
  if (typeof opts === 'function') {
    cb = opts
    opts = {}
  }

  if (typeof opts === 'string') opts = { encoding: opts }
  else if (!opts) opts = {}

  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  let dir
  let err = null
  try {
    binding.opendir(req.handle, filepath)

    await req

    dir = new Dir(filepath, binding.requestResultDir(req.handle), opts)
  } catch (e) {
    err = new FileError(e.message, {
      operation: 'opendir',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }

  return done(err, dir, cb)
}

function opendirSync(filepath, opts) {
  if (typeof opts === 'string') opts = { encoding: opts }
  else if (!opts) opts = {}

  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  try {
    binding.opendirSync(req.handle, filepath)

    return new Dir(filepath, binding.requestResultDir(req.handle), opts)
  } catch (e) {
    throw new FileError(e.message, {
      operation: 'opendir',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }
}

async function readdir(filepath, opts, cb) {
  if (typeof opts === 'function') {
    cb = opts
    opts = {}
  }

  if (typeof opts === 'string') opts = { encoding: opts }
  else if (!opts) opts = {}

  const { withFileTypes = false, recursive = false } = opts

  filepath = toNamespacedPath(filepath)

  const queue = [filepath]
  let result = []
  let err = null
  try {
    while (queue.length !== 0) {
      const dir = await opendir(queue.pop())

      for await (const entry of dir) {
        const entryPath = path.join(entry.parentPath, entry.name)

        if (withFileTypes) {
          result.push(entry)
        } else {
          result.push(path.relative(filepath, entryPath))
        }

        if (recursive && entry.isDirectory()) queue.push(entryPath)
      }
    }
  } catch (e) {
    result = []
    err = e
  }

  return done(err, result, cb)
}

function readdirSync(filepath, opts) {
  if (typeof opts === 'string') opts = { encoding: opts }
  else if (!opts) opts = {}

  const { withFileTypes = false, recursive = false } = opts

  filepath = toNamespacedPath(filepath)

  const queue = [filepath]
  const result = []

  while (queue.length !== 0) {
    const dir = opendirSync(queue.pop(), opts)

    for (const entry of dir) {
      const entryPath = path.join(entry.parentPath, entry.name)

      if (withFileTypes) {
        result.push(entry)
      } else {
        result.push(path.relative(filepath, entryPath))
      }

      if (recursive && entry.isDirectory()) queue.push(entryPath)
    }
  }

  return result
}

async function readFile(filepath, opts, cb) {
  if (typeof opts === 'function') {
    cb = opts
    opts = {}
  }

  if (typeof opts === 'string') opts = { encoding: opts }
  else if (!opts) opts = {}

  const { encoding = 'buffer' } = opts

  let fd = -1
  let buffer = null
  let err = null
  try {
    fd = await open(filepath, opts.flag || 'r')

    const st = await fstat(fd)

    let len = 0

    if (st.size === 0) {
      const buffers = []

      while (true) {
        buffer = Buffer.allocUnsafe(8192)
        const r = await read(fd, buffer)
        len += r
        if (r === 0) break
        buffers.push(buffer.subarray(0, r))
      }

      buffer = Buffer.concat(buffers)
    } else {
      buffer = Buffer.allocUnsafe(st.size)

      while (true) {
        const r = await read(fd, len ? buffer.subarray(len) : buffer)
        len += r
        if (r === 0 || len === buffer.byteLength) break
      }

      if (len !== buffer.byteLength) buffer = buffer.subarray(0, len)
    }

    if (encoding !== 'buffer') buffer = buffer.toString(encoding)
  } catch (e) {
    err = e
  } finally {
    if (fd !== -1) await close(fd)
  }

  return done(err, buffer, cb)
}

function readFileSync(filepath, opts) {
  if (typeof opts === 'string') opts = { encoding: opts }
  else if (!opts) opts = {}

  const { encoding = 'buffer' } = opts

  let fd = -1
  try {
    fd = openSync(filepath, opts.flag || 'r')

    const st = fstatSync(fd)

    let buffer
    let len = 0

    if (st.size === 0) {
      const buffers = []

      while (true) {
        buffer = Buffer.allocUnsafe(8192)
        const r = readSync(fd, buffer)
        len += r
        if (r === 0) break
        buffers.push(buffer.subarray(0, r))
      }

      buffer = Buffer.concat(buffers)
    } else {
      buffer = Buffer.allocUnsafe(st.size)

      while (true) {
        const r = readSync(fd, len ? buffer.subarray(len) : buffer)
        len += r
        if (r === 0 || len === buffer.byteLength) break
      }

      if (len !== buffer.byteLength) buffer = buffer.subarray(0, len)
    }

    if (encoding !== 'buffer') buffer = buffer.toString(encoding)

    return buffer
  } finally {
    if (fd !== -1) closeSync(fd)
  }
}

async function writeFile(filepath, data, opts, cb) {
  if (typeof opts === 'function') {
    cb = opts
    opts = {}
  }

  if (typeof opts === 'string') opts = { encoding: opts }
  else if (!opts) opts = {}

  if (typeof data === 'string') data = Buffer.from(data, opts.encoding)

  let fd = -1
  let len = 0
  let err = null
  try {
    fd = await open(filepath, opts.flag || 'w', opts.mode || 0o666)

    while (true) {
      len += await write(fd, len ? data.subarray(len) : data)
      if (len === data.byteLength) break
    }
  } catch (e) {
    err = e
  } finally {
    if (fd !== -1) await close(fd)
  }

  return done(err, len, cb)
}

function writeFileSync(filepath, data, opts) {
  if (typeof opts === 'string') opts = { encoding: opts }
  else if (!opts) opts = {}

  if (typeof data === 'string') data = Buffer.from(data, opts.encoding)

  let fd = -1
  try {
    fd = openSync(filepath, opts.flag || 'w', opts.mode || 0o666)

    let len = 0

    while (true) {
      len += writeSync(fd, len ? data.subarray(len) : data)
      if (len === data.byteLength) break
    }
  } finally {
    if (fd !== -1) closeSync(fd)
  }
}

function appendFile(filepath, data, opts, cb) {
  if (typeof opts === 'function') {
    cb = opts
    opts = {}
  }

  if (typeof opts === 'string') opts = { encoding: opts }
  else if (!opts) opts = {}

  if (!opts.flag) opts = { ...opts, flag: 'a' }

  return writeFile(filepath, data, opts, cb)
}

function appendFileSync(filepath, data, opts) {
  if (typeof opts === 'string') opts = { encoding: opts }
  else if (!opts) opts = {}

  if (!opts.flag) opts = { ...opts, flag: 'a' }

  return writeFileSync(filepath, data, opts)
}

function watch(filepath, opts, cb) {
  if (typeof opts === 'function') {
    cb = opts
    opts = {}
  }

  if (typeof opts === 'string') opts = { encoding: opts }
  else if (!opts) opts = {}

  filepath = toNamespacedPath(filepath)

  return new Watcher(filepath, opts, cb)
}

class Stats {
  constructor(
    dev,
    mode,
    nlink,
    uid,
    gid,
    rdev,
    blksize,
    ino,
    size,
    blocks,
    atimeMs,
    mtimeMs,
    ctimeMs,
    birthtimeMs
  ) {
    this.dev = dev
    this.mode = mode
    this.nlink = nlink
    this.uid = uid
    this.gid = gid
    this.rdev = rdev
    this.blksize = blksize
    this.ino = ino
    this.size = size
    this.blocks = blocks
    this.atimeMs = atimeMs
    this.mtimeMs = mtimeMs
    this.ctimeMs = ctimeMs
    this.birthtimeMs = birthtimeMs
    this.atime = new Date(atimeMs)
    this.mtime = new Date(mtimeMs)
    this.ctime = new Date(ctimeMs)
    this.birthtime = new Date(birthtimeMs)
  }

  isDirectory() {
    return (this.mode & constants.S_IFMT) === constants.S_IFDIR
  }

  isFile() {
    return (this.mode & constants.S_IFMT) === constants.S_IFREG
  }

  isBlockDevice() {
    return (this.mode & constants.S_IFMT) === constants.S_IFBLK
  }

  isCharacterDevice() {
    return (this.mode & constants.S_IFMT) === constants.S_IFCHR
  }

  isFIFO() {
    return (this.mode & constants.S_IFMT) === constants.S_IFIFO
  }

  isSymbolicLink() {
    return (this.mode & constants.S_IFMT) === constants.S_IFLNK
  }

  isSocket() {
    return (this.mode & constants.S_IFMT) === constants.S_IFSOCK
  }
}

class StatFs {
  constructor(type, bsize, blocks, bfree, bavail, files, ffree, frsize) {
    this.type = type
    this.bsize = bsize
    this.blocks = blocks
    this.bfree = bfree
    this.bavail = bavail
    this.files = files
    this.ffree = ffree
    this.frsize = frsize
  }
}

class Dir {
  constructor(path, handle, opts = {}) {
    const { encoding = 'utf8', bufferSize = 32 } = opts

    this.path = path

    this._encoding = encoding
    this._capacity = bufferSize
    this._buffer = new FIFO()
    this._ended = false
    this._handle = handle
  }

  async read(cb) {
    if (this._buffer.length) return ok(this._buffer.shift(), cb)
    if (this._ended) return ok(null, cb)

    const req = FileRequest.borrow()

    let entries
    let err = null
    try {
      req.retain(binding.readdir(req.handle, this._handle, this._capacity))

      await req

      entries = binding.requestResultDirents(req.handle)
    } catch (e) {
      err = new FileError(e.message, {
        operation: 'readdir',
        code: e.code,
        path: this.path
      })
    } finally {
      req.return()
    }

    if (err) return fail(err, cb)

    if (entries.length === 0) {
      this._ended = true

      return ok(null, cb)
    }

    for (const entry of entries) {
      let name = Buffer.from(entry.name)

      if (this._encoding !== 'buffer') name = name.toString(this._encoding)

      this._buffer.push(new Dirent(this.path, name, entry.type))
    }

    return ok(this._buffer.shift(), cb)
  }

  readSync() {
    if (this._buffer.length) return this._buffer.shift()
    if (this._ended) return null

    const req = FileRequest.borrow()

    let entries
    try {
      req.retain(binding.readdirSync(req.handle, this._handle, this._capacity))

      entries = binding.requestResultDirents(req.handle)
    } catch (e) {
      throw new FileError(e.message, {
        operation: 'readdir',
        code: e.code,
        path: this.path
      })
    } finally {
      req.return()
    }

    if (entries.length === 0) {
      this._ended = true

      return null
    }

    for (const entry of entries) {
      let name = Buffer.from(entry.name)

      if (this._encoding !== 'buffer') name = name.toString(this._encoding)

      this._buffer.push(new Dirent(this.path, name, entry.type))
    }

    return this._buffer.shift()
  }

  async close(cb) {
    const req = FileRequest.borrow()

    let err = null
    try {
      binding.closedir(req.handle, this._handle)

      await req
    } catch (e) {
      err = new FileError(e.message, {
        operation: 'closedir',
        code: e.code,
        path: this.path
      })
    } finally {
      req.return()
    }

    this._handle = null

    return done(err, cb)
  }

  closeSync() {
    const req = FileRequest.borrow()

    try {
      binding.closedirSync(req.handle, this._handle)
    } catch (e) {
      throw new FileError(e.message, {
        operation: 'closedir',
        code: e.code,
        path: this.path
      })
    } finally {
      req.return()
    }

    this._handle = null
  }

  [Symbol.dispose]() {
    this.closeSync()
  }

  async [Symbol.asyncDispose]() {
    await this.close()
  }

  *[Symbol.iterator]() {
    while (true) {
      const entry = this.readSync()
      if (entry === null) break
      yield entry
    }

    this.closeSync()
  }

  async *[Symbol.asyncIterator]() {
    while (true) {
      const entry = await this.read()
      if (entry === null) break
      yield entry
    }

    await this.close()
  }
}

class Dirent {
  constructor(parentPath, name, type) {
    this.parentPath = parentPath
    this.name = name
    this.type = type
  }

  isFile() {
    return this.type === constants.UV_DIRENT_FILE
  }

  isDirectory() {
    return this.type === constants.UV_DIRENT_DIR
  }

  isSymbolicLink() {
    return this.type === constants.UV_DIRENT_LINK
  }

  isFIFO() {
    return this.type === constants.UV_DIRENT_FIFO
  }

  isSocket() {
    return this.type === constants.UV_DIRENT_SOCKET
  }

  isCharacterDevice() {
    return this.type === constants.UV_DIRENT_CHAR
  }

  isBlockDevice() {
    return this.type === constants.UV_DIRENT_BLOCK
  }
}

class FileReadStream extends Readable {
  constructor(path, opts = {}) {
    const { eagerOpen = true } = opts

    super({ eagerOpen, ...opts })

    this.path = path
    this.fd = typeof opts.fd === 'number' ? opts.fd : -1
    this.flags = opts.flags || 'r'
    this.mode = opts.mode || 0o666

    this._offset = opts.start || 0
    this._missing = 0

    if (opts.length) {
      this._missing = opts.length
    } else if (typeof opts.end === 'number') {
      this._missing = opts.end - this._offset + 1
    } else {
      this._missing = -1
    }
  }

  async _open(cb) {
    let err

    if (this.fd === -1) {
      err = null
      try {
        this.fd = await open(this.path, this.flags, this.mode)
      } catch (e) {
        err = e
      }

      if (err) return cb(err)
    }

    let st
    err = null
    try {
      st = await fstat(this.fd)
    } catch (e) {
      err = e
    }

    if (err) return cb(err)

    this._isCharacterDevice = st.isCharacterDevice()

    if (this._isCharacterDevice) {
      this._missing = Infinity
    } else {
      if (this._missing === -1) this._missing = st.size

      if (st.size < this._offset) {
        this._offset = st.size
        this._missing = 0
      } else if (st.size < this._offset + this._missing) {
        this._missing = st.size - this._offset
      }
    }

    cb(null)
  }

  async _read(size) {
    if (this._missing <= 0) return this.push(null)

    const data = Buffer.allocUnsafe(Math.min(this._missing, size))

    let len
    let err = null
    try {
      len = await read(
        this.fd,
        data,
        0,
        data.byteLength,
        this._isCharacterDevice ? -1 : this._offset
      )
    } catch (e) {
      err = e
    }

    if (err) return this.destroy(err)

    if (len === 0) return this.push(null)

    if (this._missing < len) len = this._missing

    this._missing -= len
    this._offset += len

    this.push(data.subarray(0, len))
  }

  async _destroy(err, cb) {
    if (this.fd === -1) return cb(err)

    try {
      await close(this.fd)
    } catch (e) {
      err = err || e
    }

    cb(err)
  }
}

class FileWriteStream extends Writable {
  constructor(path, opts = {}) {
    const { eagerOpen = true } = opts

    super({ eagerOpen, ...opts })

    this.path = path
    this.fd = typeof opts.fd === 'number' ? opts.fd : -1
    this.flags = opts.flags || 'w'
    this.mode = opts.mode || 0o666
  }

  async _open(cb) {
    if (this.fd !== -1) return cb(null)

    let err = null
    try {
      this.fd = await open(this.path, this.flags, this.mode)
    } catch (e) {
      err = e
    }

    cb(err)
  }

  async _writev(batch, cb) {
    let err = null
    try {
      await writev(
        this.fd,
        batch.map(({ chunk }) => chunk)
      )
    } catch (e) {
      err = e
    }

    cb(err)
  }

  async _destroy(err, cb) {
    if (this.fd === -1) return cb(err)

    try {
      await close(this.fd)
    } catch (e) {
      err = err || e
    }

    cb(err)
  }
}

class Watcher extends EventEmitter {
  constructor(path, opts, onchange) {
    if (typeof opts === 'function') {
      onchange = opts
      opts = {}
    }

    if (!opts) opts = {}

    const { persistent = true, recursive = false, encoding = 'utf8' } = opts

    super()

    this._closed = false
    this._encoding = encoding
    this._handle = binding.watcherInit(path, recursive, this, this._onevent, this._onclose)

    if (!persistent) this.unref()

    if (onchange) this.on('change', onchange)
  }

  close() {
    if (this._closed) return
    this._closed = true

    binding.watcherClose(this._handle)
  }

  ref() {
    if (this._handle) binding.watcherRef(this._handle)
    return this
  }

  unref() {
    if (this._handle) binding.watcherUnref(this._handle)
    return this
  }

  [Symbol.asyncIterator]() {
    const buffer = []
    let done = false
    let error = null
    let next = null

    this.on('change', (eventType, filename) => {
      if (next) {
        next.resolve({ done: false, value: { eventType, filename } })
        next = null
      } else {
        buffer.push({ eventType, filename })
      }
    })
      .on('error', (err) => {
        done = true
        error = err

        if (next) {
          next.reject(error)
          next = null
        }
      })
      .on('close', () => {
        done = true

        if (next) {
          next.resolve({ done })
          next = null
        }
      })

    return {
      next: () =>
        new Promise((resolve, reject) => {
          if (error) return reject(error)

          if (buffer.length) return resolve({ done: false, value: buffer.shift() })

          if (done) return resolve({ done })

          next = { resolve, reject }
        })
    }
  }

  _onevent(err, events, filename) {
    if (err) {
      this.close()
      this.emit('error', err)
    } else {
      const path =
        this._encoding === 'buffer'
          ? Buffer.from(filename)
          : Buffer.from(filename).toString(this._encoding)

      if (events & binding.UV_RENAME) {
        this.emit('change', 'rename', path)
      }

      if (events & binding.UV_CHANGE) {
        this.emit('change', 'change', path)
      }
    }
  }

  _onclose() {
    this._handle = null

    this.emit('close')
  }
}

exports.access = access
exports.appendFile = appendFile
exports.chmod = chmod
exports.chown = chown
exports.close = close
exports.copyFile = copyFile
exports.cp = cp
exports.exists = exists
exports.fchmod = fchmod
exports.fchown = fchown
exports.fdatasync = fdatasync
exports.fstat = fstat
exports.fsync = fsync
exports.ftruncate = ftruncate
exports.futimes = futimes
exports.lchown = lchown
exports.lutimes = lutimes
exports.link = link
exports.lstat = lstat
exports.mkdir = mkdir
exports.mkdtemp = mkdtemp
exports.open = open
exports.opendir = opendir
exports.read = read
exports.readFile = readFile
exports.readdir = readdir
exports.readlink = readlink
exports.readv = readv
exports.realpath = realpath
exports.rename = rename
exports.rm = rm
exports.rmdir = rmdir
exports.stat = stat
exports.statfs = statfs
exports.symlink = symlink
exports.truncate = truncate
exports.unlink = unlink
exports.utimes = utimes
exports.watch = watch
exports.write = write
exports.writeFile = writeFile
exports.writev = writev

exports.accessSync = accessSync
exports.appendFileSync = appendFileSync
exports.chmodSync = chmodSync
exports.chownSync = chownSync
exports.closeSync = closeSync
exports.copyFileSync = copyFileSync
exports.cpSync = cpSync
exports.existsSync = existsSync
exports.fchmodSync = fchmodSync
exports.fchownSync = fchownSync
exports.fdatasyncSync = fdatasyncSync
exports.fstatSync = fstatSync
exports.fsyncSync = fsyncSync
exports.ftruncateSync = ftruncateSync
exports.futimesSync = futimesSync
exports.lchownSync = lchownSync
exports.lutimesSync = lutimesSync
exports.linkSync = linkSync
exports.lstatSync = lstatSync
exports.mkdirSync = mkdirSync
exports.mkdtempSync = mkdtempSync
exports.openSync = openSync
exports.opendirSync = opendirSync
exports.readFileSync = readFileSync
exports.readSync = readSync
exports.readdirSync = readdirSync
exports.readlinkSync = readlinkSync
exports.readvSync = readvSync
exports.realpathSync = realpathSync
exports.renameSync = renameSync
exports.rmSync = rmSync
exports.rmdirSync = rmdirSync
exports.statSync = statSync
exports.statfsSync = statfsSync
exports.symlinkSync = symlinkSync
exports.truncateSync = truncateSync
exports.unlinkSync = unlinkSync
exports.utimesSync = utimesSync
exports.writeFileSync = writeFileSync
exports.writeSync = writeSync
exports.writevSync = writevSync

exports.promises = require('./promises')

exports.Stats = Stats
exports.StatFs = StatFs
exports.Dir = Dir
exports.Dirent = Dirent
exports.Watcher = Watcher

exports.ReadStream = FileReadStream

exports.createReadStream = function createReadStream(path, opts) {
  return new FileReadStream(path, opts)
}

exports.WriteStream = FileWriteStream

exports.createWriteStream = function createWriteStream(path, opts) {
  return new FileWriteStream(path, opts)
}

function toNamespacedPath(filepath) {
  if (typeof filepath !== 'string') {
    if (isURL(filepath)) filepath = fileURLToPath(filepath)
    else filepath = filepath.toString()
  }

  return path.toNamespacedPath(filepath)
}

function toFlags(flags) {
  switch (flags) {
    case 'r':
      return constants.O_RDONLY
    case 'rs': // Fall through.
    case 'sr':
      return constants.O_RDONLY | constants.O_SYNC
    case 'r+':
      return constants.O_RDWR
    case 'rs+': // Fall through.
    case 'sr+':
      return constants.O_RDWR | constants.O_SYNC

    case 'w':
      return constants.O_TRUNC | constants.O_CREAT | constants.O_WRONLY
    case 'wx': // Fall through.
    case 'xw':
      return constants.O_TRUNC | constants.O_CREAT | constants.O_WRONLY | constants.O_EXCL

    case 'w+':
      return constants.O_TRUNC | constants.O_CREAT | constants.O_RDWR
    case 'wx+': // Fall through.
    case 'xw+':
      return constants.O_TRUNC | constants.O_CREAT | constants.O_RDWR | constants.O_EXCL

    case 'a':
      return constants.O_APPEND | constants.O_CREAT | constants.O_WRONLY
    case 'ax': // Fall through.
    case 'xa':
      return constants.O_APPEND | constants.O_CREAT | constants.O_WRONLY | constants.O_EXCL
    case 'as': // Fall through.
    case 'sa':
      return constants.O_APPEND | constants.O_CREAT | constants.O_WRONLY | constants.O_SYNC

    case 'a+':
      return constants.O_APPEND | constants.O_CREAT | constants.O_RDWR
    case 'ax+': // Fall through.
    case 'xa+':
      return constants.O_APPEND | constants.O_CREAT | constants.O_RDWR | constants.O_EXCL
    case 'as+': // Fall through.
    case 'sa+':
      return constants.O_APPEND | constants.O_CREAT | constants.O_RDWR | constants.O_SYNC
    default:
      return 0
  }
}

function toMode(mode) {
  return parseInt(mode, 8)
}
const binding = require('../binding')

module.exports = {
  O_RDWR: binding.O_RDWR,
  O_RDONLY: binding.O_RDONLY,
  O_WRONLY: binding.O_WRONLY,
  O_CREAT: binding.O_CREAT,
  O_TRUNC: binding.O_TRUNC,
  O_APPEND: binding.O_APPEND,

  F_OK: binding.F_OK || 0,
  R_OK: binding.R_OK || 0,
  W_OK: binding.W_OK || 0,
  X_OK: binding.X_OK || 0,

  S_IFMT: binding.S_IFMT,
  S_IFREG: binding.S_IFREG,
  S_IFDIR: binding.S_IFDIR,
  S_IFCHR: binding.S_IFCHR,
  S_IFLNK: binding.S_IFLNK,
  S_IFBLK: binding.S_IFBLK || 0,
  S_IFIFO: binding.S_IFIFO || 0,
  S_IFSOCK: binding.S_IFSOCK || 0,

  S_IRUSR: binding.S_IRUSR || 0,
  S_IWUSR: binding.S_IWUSR || 0,
  S_IXUSR: binding.S_IXUSR || 0,
  S_IRGRP: binding.S_IRGRP || 0,
  S_IWGRP: binding.S_IWGRP || 0,
  S_IXGRP: binding.S_IXGRP || 0,
  S_IROTH: binding.S_IROTH || 0,
  S_IWOTH: binding.S_IWOTH || 0,
  S_IXOTH: binding.S_IXOTH || 0,

  UV_DIRENT_UNKNOWN: binding.UV_DIRENT_UNKNOWN,
  UV_DIRENT_FILE: binding.UV_DIRENT_FILE,
  UV_DIRENT_DIR: binding.UV_DIRENT_DIR,
  UV_DIRENT_LINK: binding.UV_DIRENT_LINK,
  UV_DIRENT_FIFO: binding.UV_DIRENT_FIFO,
  UV_DIRENT_SOCKET: binding.UV_DIRENT_SOCKET,
  UV_DIRENT_CHAR: binding.UV_DIRENT_CHAR,
  UV_DIRENT_BLOCK: binding.UV_DIRENT_BLOCK,

  COPYFILE_EXCL: binding.UV_FS_COPYFILE_EXCL,
  COPYFILE_FICLONE: binding.UV_FS_COPYFILE_FICLONE,
  COPYFILE_FICLONE_FORCE: binding.UV_FS_COPYFILE_FICLONE_FORCE,
  UV_FS_SYMLINK_DIR: binding.UV_FS_SYMLINK_DIR,
  UV_FS_SYMLINK_JUNCTION: binding.UV_FS_SYMLINK_JUNCTION
}
const binding = require('../binding')

module.exports = class FileError extends Error {
  constructor(msg, opts = {}) {
    const { code, operation = null, path = null, destination = null, fd = -1 } = opts

    if (operation !== null) msg += describe(operation, opts)

    super(`${code}: ${msg}`)

    this.code = code

    if (operation !== null) this.operation = operation
    if (path !== null) this.path = path
    if (destination !== null) this.destination = destination
    if (fd !== -1) this.fd = fd
  }

  get name() {
    return 'FileError'
  }

  // For Node.js compatibility
  get errno() {
    return binding.errnos[this.code]
  }

  // For Node.js compatibility
  get syscall() {
    return this.operation
  }

  // For Node.js compatibility
  get dest() {
    return this.destination
  }
}

function describe(operation, opts) {
  const { path = null, destination = null, fd = -1 } = opts

  let result = `, ${operation}`

  if (path !== null) {
    result += ` ${JSON.stringify(path)}`

    if (destination !== null) {
      result += ` -> ${JSON.stringify(destination)}`
    }
  } else if (fd !== -1) {
    result += ` ${fd}`
  }

  return result
}
{
  "name": "bare-fs",
  "version": "4.8.1",
  "description": "Native file system operations for Bare",
  "exports": {
    "./package": "./package.json",
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    },
    "./promises": {
      "types": "./promises.d.ts",
      "default": "./promises.js"
    },
    "./constants": {
      "types": "./lib/constants.d.ts",
      "default": "./lib/constants.js"
    }
  },
  "files": [
    "index.js",
    "index.d.ts",
    "promises.js",
    "promises.d.ts",
    "binding.c",
    "binding.js",
    "CMakeLists.txt",
    "lib",
    "prebuilds"
  ],
  "addon": true,
  "scripts": {
    "format": "prettier --write . && lunte --fix",
    "lint": "prettier --check . && lunte",
    "test": "brittle-bare --coverage test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-fs.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-fs/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-fs#readme",
  "engines": {
    "bare": ">=1.28.0"
  },
  "dependencies": {
    "bare-events": "^2.5.4",
    "bare-path": "^3.0.0",
    "bare-stream": "^2.6.4",
    "bare-url": "^2.2.2",
    "fast-fifo": "^1.3.2"
  },
  "devDependencies": {
    "bare-buffer": "^3.0.2",
    "bare-crypto": "^1.11.2",
    "brittle": "^3.1.1",
    "cmake-bare": "^1.1.7",
    "lunte": "^1.6.0",
    "prettier": "^3.4.1",
    "prettier-config-holepunch": "^2.0.0"
  },
  "peerDependencies": {
    "bare-buffer": "*"
  },
  "peerDependenciesMeta": {
    "bare-buffer": {
      "optional": true
    }
  }
}
const EventEmitter = require('bare-events')
const fs = require('.')

class FileHandle extends EventEmitter {
  constructor(fd) {
    super()

    this.fd = fd
  }

  async close() {
    await fs.close(this.fd)

    this.fd = -1
    this.emit('close')
  }

  async read(buffer, ...args) {
    return {
      bytesRead: await fs.read(this.fd, buffer, ...args),
      buffer
    }
  }

  async readv(buffers, ...args) {
    return {
      bytesRead: await fs.readv(this.fd, buffers, ...args),
      buffers
    }
  }

  async write(buffer, ...args) {
    return {
      bytesWritten: await fs.write(this.fd, buffer, ...args),
      buffer
    }
  }

  async writev(buffers, ...args) {
    return {
      bytesWritten: await fs.writev(this.fd, buffers, ...args),
      buffers
    }
  }

  async stat() {
    return fs.fstat(this.fd)
  }

  async chmod(mode) {
    await fs.fchmod(this.fd, mode)
  }

  async chown(uid, gid) {
    await fs.fchown(this.fd, uid, gid)
  }

  async datasync() {
    return fs.fdatasync(this.fd)
  }

  async sync() {
    return fs.fsync(this.fd)
  }

  async truncate(len) {
    await fs.ftruncate(this.fd, len)
  }

  async utimes(atime, mtime) {
    await fs.futimes(this.fd, atime, mtime)
  }

  createReadStream(opts) {
    return fs.createReadStream(null, { ...opts, fd: this.fd })
  }

  createWriteStream(opts) {
    return fs.createWriteStream(null, { ...opts, fd: this.fd })
  }

  async [Symbol.asyncDispose]() {
    await this.close()
  }
}

exports.open = async function open(filepath, flags, mode) {
  return new FileHandle(await fs.open(filepath, flags, mode))
}

exports.access = fs.access
exports.appendFile = fs.appendFile
exports.chmod = fs.chmod
exports.chown = fs.chown
exports.constants = fs.constants
exports.copyFile = fs.copyFile
exports.cp = fs.cp
exports.lchown = fs.lchown
exports.lutimes = fs.lutimes
exports.link = fs.link
exports.lstat = fs.lstat
exports.mkdir = fs.mkdir
exports.mkdtemp = fs.mkdtemp
exports.opendir = fs.opendir
exports.readFile = fs.readFile
exports.readdir = fs.readdir
exports.readlink = fs.readlink
exports.realpath = fs.realpath
exports.rename = fs.rename
exports.rm = fs.rm
exports.rmdir = fs.rmdir
exports.stat = fs.stat
exports.statfs = fs.statfs
exports.truncate = fs.truncate
exports.symlink = fs.symlink
exports.unlink = fs.unlink
exports.utimes = fs.utimes
exports.watch = fs.watch
exports.writeFile = fs.writeFile
module.exports = require.addon()
const binding = require('./binding')

module.exports = exports = function hrtime(past) {
  let now = binding.hrtime()

  if (past) now -= BigInt(past[0]) * 1000000000n + BigInt(past[1])

  return [Number(now / 1000000000n), Number(now % 1000000000n)]
}

exports.bigint = function hrtime() {
  return binding.hrtime()
}
{
  "name": "bare-hrtime",
  "version": "2.1.2",
  "description": "High-resolution timers for JavaScript",
  "exports": {
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    },
    "./package": "./package.json"
  },
  "files": [
    "index.js",
    "index.d.ts",
    "binding.c",
    "binding.js",
    "CMakeLists.txt",
    "prebuilds"
  ],
  "addon": true,
  "scripts": {
    "format": "prettier --write . && lunte --fix",
    "lint": "prettier --check . && lunte",
    "test": "brittle-bare --coverage test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-hrtime.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-hrtime/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-hrtime#readme",
  "devDependencies": {
    "brittle": "^3.2.1",
    "cmake-bare": "^1.1.6",
    "lunte": "^1.8.4",
    "prettier": "^3.4.2",
    "prettier-config-holepunch": "^2.0.0"
  }
}
const errors = require('./lib/errors')

const constants = {
  REQUEST: 1,
  RESPONSE: 2,
  DATA: 3,
  END: 4
}

const TAB = 0x09
const LF = 0x0a
const CR = 0x0d
const SP = 0x20
const ZERO = 0x30
const NINE = 0x39
const UPPER_A = 0x41
const UPPER_F = 0x46
const UPPER_Z = 0x5a
const LOWER_A = 0x61
const LOWER_F = 0x66
const COLON = 0x3a
const SEMICOLON = 0x3b
const SLASH = 0x2f

const MAX_CHUNK_SIZE_LENGTH = 16
const STATUS_CODE_LENGTH = 3

const MAX_APPLY_LENGTH = 4096

// Header states
const FIRST_TOKEN = 0
const LEADING_LINE_LF = 1
const REQUEST_URL = 2
const REQUEST_VERSION = 3
const STATUS_CODE = 4
const STATUS_REASON = 5
const FIRST_LINE_LF = 6
const HEADER_START = 7
const HEADER_NAME = 8
const HEADER_VALUE_WS = 9
const HEADER_VALUE = 10
const HEADER_LINE_LF = 11
const HEADER_END_LF = 12

// Body states
const BODY = 13
const BODY_EOF = 14
const CHUNK_SIZE = 15
const CHUNK_SIZE_LF = 16
const CHUNK_DATA = 17
const CHUNK_DATA_CR = 18
const CHUNK_DATA_LF = 19
const CHUNK_EXTENSION = 20

// Trailing states
const LAST_CHUNK_LF = 21
const TRAILER_START = 22
const TRAILER_NAME = 23
const TRAILER_VALUE = 24
const TRAILER_LINE_LF = 25
const TRAILER_END_LF = 26

// The connection has been handed over to another protocol and the remaining
// bytes are no longer ours to parse.
const TUNNEL = 27

const METHODS = new Set([
  'ACL',
  'BIND',
  'CHECKOUT',
  'CONNECT',
  'COPY',
  'DELETE',
  'GET',
  'HEAD',
  'LINK',
  'LOCK',
  'M-SEARCH',
  'MERGE',
  'MKACTIVITY',
  'MKCALENDAR',
  'MKCOL',
  'MOVE',
  'NOTIFY',
  'OPTIONS',
  'PATCH',
  'POST',
  'PROPFIND',
  'PROPPATCH',
  'PURGE',
  'PUT',
  'QUERY',
  'REBIND',
  'REPORT',
  'SEARCH',
  'SOURCE',
  'SUBSCRIBE',
  'TRACE',
  'UNBIND',
  'UNLINK',
  'UNLOCK',
  'UNSUBSCRIBE'
])

module.exports = exports = class HTTPParser {
  constructor(opts = {}) {
    const { maxHeaderSize = 16384, maxHeadersCount = 2000 } = opts

    this._maxHeaderSize = maxHeaderSize
    this._maxHeadersCount = maxHeadersCount
    this._state = FIRST_TOKEN
    this._buffer = []
    this._bufferIndex = 0
    this._byteIndex = 0
    this._buffered = 0
    this._accumulator = []
    this._fatal = null

    this._isResponse = false
    this._method = ''
    this._url = ''
    this._version = ''
    this._code = 0
    this._reason = ''

    this._headerName = ''
    this._headers = Object.create(null)
    this._headerCount = 0
    this._headerSize = 0

    this._remaining = 0
    this._skipBody = false
  }

  skipBody() {
    this._skipBody = true
  }

  *push(data, encoding) {
    if (this._fatal !== null) throw this._fatal

    if (typeof data === 'string') data = Buffer.from(data, encoding)

    if (data.byteLength > 0) {
      this._buffer.push(data)
      this._buffered += data.byteLength
    }

    try {
      yield* this._parse()
    } catch (err) {
      this._fatal = err
      throw err
    }

    this._compact()
  }

  *end() {
    if (this._fatal !== null) throw this._fatal

    const state = this._state
    const pending = this._accumulator.length > 0

    this._state = FIRST_TOKEN
    this._accumulator = []
    this._headerName = ''
    this._headerSize = 0
    this._remaining = 0
    this._skipBody = false

    if (state === BODY_EOF) {
      yield { type: constants.END }

      return
    }

    if (state === TUNNEL || (state === FIRST_TOKEN && pending === false)) return

    this._fatal = errors.INVALID_MESSAGE('Message truncated by end of stream')

    throw this._fatal
  }

  drain() {
    const buffers = this._buffer
    const bufferIndex = this._bufferIndex
    const byteIndex = this._byteIndex

    this._buffer = []
    this._bufferIndex = 0
    this._byteIndex = 0
    this._buffered = 0

    if (bufferIndex >= buffers.length) return Buffer.alloc(0)

    buffers[bufferIndex] = buffers[bufferIndex].subarray(byteIndex)

    const remaining = buffers.slice(bufferIndex)

    if (remaining.length === 0) return Buffer.alloc(0)
    if (remaining.length === 1) return remaining[0]

    return Buffer.concat(remaining)
  }

  _compact() {
    if (this._bufferIndex > 0) {
      this._buffer = this._buffer.slice(this._bufferIndex)
      this._bufferIndex = 0
    }

    if (this._byteIndex > 0 && this._buffer.length > 0) {
      this._buffer[0] = this._buffer[0].subarray(this._byteIndex)
      this._byteIndex = 0
    }
  }

  _consume(n) {
    this._buffered -= n

    const current = this._buffer[this._bufferIndex]

    if (this._byteIndex + n <= current.byteLength) {
      const slice = current.subarray(this._byteIndex, this._byteIndex + n)

      this._byteIndex += n

      if (this._byteIndex >= current.byteLength) {
        this._bufferIndex++
        this._byteIndex = 0
      }

      return slice
    }

    const result = Buffer.allocUnsafe(n)

    let written = 0

    while (written < n) {
      const buffer = this._buffer[this._bufferIndex]
      const available = buffer.byteLength - this._byteIndex
      const take = Math.min(available, n - written)

      buffer.copy(result, written, this._byteIndex, this._byteIndex + take)

      written += take

      this._byteIndex += take

      if (this._byteIndex >= buffer.byteLength) {
        this._bufferIndex++
        this._byteIndex = 0
      }
    }

    return result
  }

  _buildString() {
    const accumulator = this._accumulator

    this._accumulator = []

    if (accumulator.length <= MAX_APPLY_LENGTH) {
      return String.fromCharCode.apply(null, accumulator)
    }

    let string = ''

    for (let i = 0, n = accumulator.length; i < n; i += MAX_APPLY_LENGTH) {
      string += String.fromCharCode.apply(null, accumulator.slice(i, i + MAX_APPLY_LENGTH))
    }

    return string
  }

  // Zero turns a limit off, as a caller that wants no limit has no other way of
  // saying so and would otherwise get one that refuses every message.
  _checkHeaderSize() {
    if (this._maxHeaderSize === 0) return

    if (++this._headerSize > this._maxHeaderSize) {
      throw errors.HEADER_OVERFLOW('Header exceeds limit of ' + this._maxHeaderSize + ' bytes')
    }
  }

  _checkHeaderCount() {
    if (this._maxHeadersCount === 0) return

    if (++this._headerCount > this._maxHeadersCount) {
      throw errors.HEADER_OVERFLOW('Header count exceeds limit of ' + this._maxHeadersCount)
    }
  }

  _storeHeader(name, value) {
    let end = value.length

    while (end > 0 && (value.charCodeAt(end - 1) === SP || value.charCodeAt(end - 1) === TAB)) {
      end--
    }

    if (end < value.length) value = value.substring(0, end)

    this._checkHeaderCount()

    switch (name) {
      case '__proto__':
      case 'constructor':
      case 'prototype':
        throw errors.INVALID_HEADER("Unsafe header name '" + name + "'")

      case 'host':
      case 'content-length':
      case 'transfer-encoding':
        if (name in this._headers) {
          throw errors.INVALID_HEADER("Duplicate header '" + name + "'")
        }

        this._headers[name] = value
        break

      case 'set-cookie':
        if (name in this._headers) this._headers[name].push(value)
        else this._headers[name] = [value]
        break

      default: {
        const delimiter = name === 'cookie' ? '; ' : ', '

        if (name in this._headers) {
          this._headers[name] += delimiter + value
        } else {
          this._headers[name] = value
        }
      }
    }
  }

  _checkTrailer(name) {
    switch (name) {
      // A trailer arrives after the headers have already been surfaced, so a
      // framing or routing header here could never be acted upon and is only
      // ever an attempt to smuggle one past the consumer.
      case 'host':
      case 'content-length':
      case 'transfer-encoding':
        throw errors.INVALID_HEADER("Header '" + name + "' must not appear in a trailer")
    }

    this._checkHeaderCount()
  }

  *_parse() {
    while (true) {
      if (this._state === TUNNEL) return

      if (this._state === BODY) {
        if (this._buffered === 0) return

        const available = Math.min(this._buffered, this._remaining)
        const data = this._consume(available)

        this._remaining -= available

        const ended = this._remaining === 0

        if (ended) this._state = FIRST_TOKEN

        yield { type: constants.DATA, data }

        if (ended) yield { type: constants.END }

        continue
      }

      if (this._state === BODY_EOF) {
        if (this._buffered === 0) return

        yield { type: constants.DATA, data: this._consume(this._buffered) }

        continue
      }

      if (this._state === CHUNK_DATA) {
        if (this._buffered === 0) return

        const available = Math.min(this._buffered, this._remaining)
        const data = this._consume(available)

        this._remaining -= available

        if (this._remaining === 0) this._state = CHUNK_DATA_CR

        yield { type: constants.DATA, data }

        continue
      }

      if (this._buffered === 0) return

      const byte = this._buffer[this._bufferIndex][this._byteIndex++]

      this._buffered--

      if (this._byteIndex >= this._buffer[this._bufferIndex].byteLength) {
        this._bufferIndex++
        this._byteIndex = 0
      }

      switch (this._state) {
        case FIRST_TOKEN: {
          this._checkHeaderSize()

          if (byte === SP) {
            const token = this._buildString()

            if (token.length === 0) throw errors.INVALID_MESSAGE()

            this._isResponse = token.startsWith('HTTP/')

            if (this._isResponse) {
              if (token !== 'HTTP/1.0' && token !== 'HTTP/1.1') {
                throw errors.INVALID_MESSAGE()
              }

              this._version = token
              this._state = STATUS_CODE
            } else {
              if (METHODS.has(token) === false) {
                throw errors.INVALID_METHOD("Unsupported method '" + token + "'")
              }

              this._method = token
              this._state = REQUEST_URL
            }
          } else if (byte === CR) {
            // RFC 9112 asks that an empty line before the start line be
            // ignored, since a peer that ends a message with a stray CRLF
            // leaves one behind. How many may be spent is bounded by the
            // header size, which counts them like any other byte.
            if (this._accumulator.length > 0) throw errors.INVALID_MESSAGE()

            this._state = LEADING_LINE_LF
          } else if (isTokenByte(byte)) {
            this._accumulator.push(byte)
          } else if (
            byte === SLASH &&
            this._accumulator.length === 4 &&
            this._accumulator[0] === 0x48 &&
            this._accumulator[1] === 0x54 &&
            this._accumulator[2] === 0x54 &&
            this._accumulator[3] === 0x50
          ) {
            this._accumulator.push(byte)
          } else {
            throw errors.INVALID_MESSAGE()
          }

          break
        }

        case LEADING_LINE_LF: {
          if (byte !== LF) throw errors.INVALID_MESSAGE()

          this._state = FIRST_TOKEN

          break
        }

        case REQUEST_URL: {
          this._checkHeaderSize()

          if (byte === SP) {
            this._url = this._buildString()

            if (this._url.length === 0) throw errors.INVALID_MESSAGE()

            this._state = REQUEST_VERSION
          } else if (byte === CR) {
            throw errors.INVALID_MESSAGE()
          } else if (isVisibleByte(byte)) {
            this._accumulator.push(byte)
          } else {
            throw errors.INVALID_MESSAGE()
          }

          break
        }

        case REQUEST_VERSION: {
          this._checkHeaderSize()

          if (byte === CR) {
            this._version = this._buildString()

            if (this._version !== 'HTTP/1.0' && this._version !== 'HTTP/1.1') {
              throw errors.INVALID_MESSAGE()
            }

            this._state = FIRST_LINE_LF
          } else if (isVisibleByte(byte)) {
            this._accumulator.push(byte)
          } else {
            throw errors.INVALID_MESSAGE()
          }

          break
        }

        case STATUS_CODE: {
          this._checkHeaderSize()

          if (byte === SP || byte === CR) {
            // RFC 9112 fixes the status code at exactly three digits, which
            // also rules out the leading zeroes that would let a peer dress one
            // code up as another.
            if (this._accumulator.length !== STATUS_CODE_LENGTH) {
              throw errors.INVALID_MESSAGE()
            }

            let code = 0

            for (let i = 0; i < STATUS_CODE_LENGTH; i++) {
              code = code * 10 + this._accumulator[i]
            }

            this._accumulator = []

            if (code < 100) throw errors.INVALID_MESSAGE()

            this._code = code

            // The space and the reason phrase after it are both routinely left
            // out in practice, even though RFC 9112 requires the space.
            if (byte === CR) {
              this._reason = ''
              this._state = FIRST_LINE_LF
            } else {
              this._state = STATUS_REASON
            }
          } else if (byte >= ZERO && byte <= NINE) {
            if (this._accumulator.length === STATUS_CODE_LENGTH) {
              throw errors.INVALID_MESSAGE()
            }

            this._accumulator.push(byte - ZERO)
          } else {
            throw errors.INVALID_MESSAGE()
          }

          break
        }

        case STATUS_REASON: {
          this._checkHeaderSize()

          if (byte === CR) {
            this._reason = this._buildString()
            this._state = FIRST_LINE_LF
          } else if (isFieldByte(byte)) {
            this._accumulator.push(byte)
          } else {
            throw errors.INVALID_MESSAGE()
          }

          break
        }

        case FIRST_LINE_LF: {
          if (byte !== LF) throw errors.INVALID_MESSAGE()

          this._headers = Object.create(null)
          this._headerCount = 0
          this._state = HEADER_START

          break
        }

        case HEADER_START: {
          this._checkHeaderSize()

          if (byte === CR) {
            this._state = HEADER_END_LF
          } else if (byte !== COLON && isTokenByte(byte)) {
            this._accumulator.push(byte >= UPPER_A && byte <= UPPER_Z ? byte + 0x20 : byte)
            this._state = HEADER_NAME
          } else {
            throw errors.INVALID_HEADER()
          }

          break
        }

        case HEADER_NAME: {
          this._checkHeaderSize()

          if (byte === COLON) {
            this._headerName = this._buildString()
            this._state = HEADER_VALUE_WS
          } else if (byte !== COLON && isTokenByte(byte)) {
            this._accumulator.push(byte >= UPPER_A && byte <= UPPER_Z ? byte + 0x20 : byte)
          } else {
            throw errors.INVALID_HEADER()
          }

          break
        }

        case HEADER_VALUE_WS: {
          this._checkHeaderSize()

          if (byte === SP || byte === TAB) break

          if (byte === CR) {
            this._storeHeader(this._headerName, '')
            this._headerName = ''
            this._state = HEADER_LINE_LF

            break
          }

          if (!isFieldByte(byte)) throw errors.INVALID_HEADER()

          this._accumulator.push(byte)
          this._state = HEADER_VALUE

          break
        }

        case HEADER_VALUE: {
          this._checkHeaderSize()

          if (byte === CR) {
            this._storeHeader(this._headerName, this._buildString())
            this._headerName = ''
            this._state = HEADER_LINE_LF
          } else if (isFieldByte(byte)) {
            this._accumulator.push(byte)
          } else {
            throw errors.INVALID_HEADER()
          }

          break
        }

        case HEADER_LINE_LF: {
          if (byte !== LF) throw errors.INVALID_HEADER()

          this._state = HEADER_START

          break
        }

        case HEADER_END_LF: {
          if (byte !== LF) throw errors.INVALID_MESSAGE()

          const headers = Object.assign(Object.create(null), this._headers)

          if (!this._isResponse && this._version === 'HTTP/1.1' && !('host' in headers)) {
            throw errors.INVALID_HEADER("Header 'Host' is missing")
          }

          // Resolve the framing before the message is surfaced so that a
          // consumer never gets to act on a message we go on to reject.
          const transferEncoding = headers['transfer-encoding']
          const contentLength = headers['content-length']

          let chunked = false
          let length = -1

          // A header that is present but empty still has to be validated, so
          // test for presence rather than truthiness.
          if (transferEncoding !== undefined) {
            const encodings = transferEncoding.split(',')

            let chunkedCount = 0

            for (let i = 0, n = encodings.length; i < n; i++) {
              if (encodings[i].trim().toLowerCase() === 'chunked') chunkedCount++
            }

            const lastEncoding = encodings[encodings.length - 1].trim().toLowerCase()

            if (lastEncoding !== 'chunked') {
              throw errors.INVALID_MESSAGE("'Transfer-Encoding' must end with 'chunked'")
            }

            if (chunkedCount > 1) {
              throw errors.INVALID_MESSAGE(
                "'chunked' must not appear more than once in 'Transfer-Encoding'"
              )
            }

            if (contentLength !== undefined) {
              throw errors.INVALID_MESSAGE(
                "Conflicting 'Content-Length' and 'Transfer-Encoding' headers"
              )
            }

            // An HTTP/1.0 peer cannot be assumed to understand chunked framing,
            // so the two disagreeing about where the body ends is a smuggling
            // opportunity.
            if (this._version === 'HTTP/1.0') {
              throw errors.INVALID_MESSAGE("'Transfer-Encoding' requires HTTP/1.1")
            }

            chunked = true
          } else if (contentLength !== undefined) {
            if (contentLength.length === 0) throw errors.INVALID_CONTENT_LENGTH()

            length = 0

            for (let i = 0, n = contentLength.length; i < n; i++) {
              const c = contentLength.charCodeAt(i)

              if (c < ZERO || c > NINE) throw errors.INVALID_CONTENT_LENGTH()

              length = length * 10 + (c - ZERO)
            }

            if (!Number.isSafeInteger(length)) throw errors.INVALID_CONTENT_LENGTH()
          }

          // A response to a `HEAD` request, an informational response, and a
          // `204` or `304` response all carry no body whatever their framing
          // headers say. Reading one would consume the response that follows.
          const bodyless =
            this._isResponse &&
            (this._skipBody || this._code < 200 || this._code === 204 || this._code === 304)

          this._skipBody = false
          this._headerSize = 0

          if (this._isResponse) {
            yield {
              type: constants.RESPONSE,
              version: this._version,
              code: this._code,
              reason: this._reason,
              headers
            }
          } else {
            yield {
              type: constants.REQUEST,
              version: this._version,
              method: this._method,
              url: this._url,
              headers
            }
          }

          // A `CONNECT` request and a `101` response both hand the connection
          // over to another protocol. Stop parsing rather than misreading the
          // bytes that follow, and leave them for `end()`.
          if (this._isResponse ? this._code === 101 : this._method === 'CONNECT') {
            this._state = TUNNEL

            yield { type: constants.END }

            break
          }

          if (bodyless || length === 0) {
            this._state = FIRST_TOKEN

            yield { type: constants.END }
          } else if (chunked) {
            this._state = CHUNK_SIZE
          } else if (length > 0) {
            this._state = BODY
            this._remaining = length
          } else if (this._isResponse) {
            // A response with no framing headers runs until the connection
            // closes. Treating it as empty would turn its body into the next
            // response.
            this._state = BODY_EOF
          } else {
            this._state = FIRST_TOKEN

            yield { type: constants.END }
          }

          break
        }

        case CHUNK_SIZE: {
          if (byte === CR || byte === SEMICOLON) {
            if (this._accumulator.length === 0) throw errors.INVALID_CHUNK_LENGTH()

            let length = 0

            for (let i = 0, n = this._accumulator.length; i < n; i++) {
              length = length * 16 + this._accumulator[i]
            }

            this._accumulator = []

            if (!Number.isSafeInteger(length)) throw errors.INVALID_CHUNK_LENGTH()

            this._remaining = length

            if (byte === SEMICOLON) {
              this._state = CHUNK_EXTENSION
            } else if (length === 0) {
              this._state = LAST_CHUNK_LF
            } else {
              this._state = CHUNK_SIZE_LF
            }
          } else if (isHex(byte)) {
            if (this._accumulator.length >= MAX_CHUNK_SIZE_LENGTH) {
              throw errors.INVALID_CHUNK_LENGTH()
            }

            this._accumulator.push(hexValue(byte))
          } else {
            throw errors.INVALID_CHUNK_LENGTH()
          }

          break
        }

        case CHUNK_EXTENSION: {
          this._checkHeaderSize()

          if (byte === CR) {
            this._state = this._remaining === 0 ? LAST_CHUNK_LF : CHUNK_SIZE_LF
          } else if (!isFieldByte(byte)) {
            throw errors.INVALID_CHUNK_LENGTH()
          }

          break
        }

        case CHUNK_SIZE_LF: {
          if (byte !== LF) throw errors.INVALID_CHUNK_LENGTH()

          this._state = CHUNK_DATA

          break
        }

        case CHUNK_DATA_CR: {
          if (byte !== CR) throw errors.INVALID_MESSAGE('Expected CRLF after chunk data')

          this._state = CHUNK_DATA_LF

          break
        }

        case CHUNK_DATA_LF: {
          if (byte !== LF) throw errors.INVALID_MESSAGE('Expected CRLF after chunk data')

          this._state = CHUNK_SIZE
          this._headerSize = 0

          break
        }

        case LAST_CHUNK_LF: {
          if (byte !== LF) throw errors.INVALID_CHUNK_LENGTH()

          this._state = TRAILER_START

          break
        }

        case TRAILER_START: {
          this._checkHeaderSize()

          if (byte === CR) {
            this._state = TRAILER_END_LF
          } else if (byte !== COLON && isTokenByte(byte)) {
            this._accumulator.push(byte >= UPPER_A && byte <= UPPER_Z ? byte + 0x20 : byte)
            this._state = TRAILER_NAME
          } else {
            throw errors.INVALID_HEADER()
          }

          break
        }

        case TRAILER_NAME: {
          this._checkHeaderSize()

          if (byte === COLON) {
            this._checkTrailer(this._buildString())
            this._state = TRAILER_VALUE
          } else if (byte !== COLON && isTokenByte(byte)) {
            this._accumulator.push(byte >= UPPER_A && byte <= UPPER_Z ? byte + 0x20 : byte)
          } else {
            throw errors.INVALID_HEADER()
          }

          break
        }

        case TRAILER_VALUE: {
          this._checkHeaderSize()

          if (byte === CR) this._state = TRAILER_LINE_LF
          else if (!isFieldByte(byte)) throw errors.INVALID_HEADER()

          break
        }

        case TRAILER_LINE_LF: {
          if (byte !== LF) throw errors.INVALID_HEADER()

          this._state = TRAILER_START

          break
        }

        case TRAILER_END_LF: {
          if (byte !== LF) throw errors.INVALID_MESSAGE()

          this._state = FIRST_TOKEN
          this._headerSize = 0

          yield { type: constants.END }

          break
        }

        default:
          throw errors.INVALID_MESSAGE()
      }
    }
  }
}

exports.constants = constants

const TOKEN_BYTES = Buffer.from([
  // 0x00-0x1f (control characters) + 0x20 (space)
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  // 0x21-0x41: ! " # $ % & ' ( ) * + , - . / 0-9 : ; < = > ? @ A
  1, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1,
  // 0x42-0x62: B-Z [ \ ] ^ _ ` a b
  1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1,
  // 0x63-0x7f: c-z { | } ~ DEL
  1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0
])

function isTokenByte(b) {
  return TOKEN_BYTES[b] === 1
}

function isFieldByte(b) {
  return b === TAB || (b >= 0x20 && b <= 0x7e) || b >= 0x80
}

function isVisibleByte(b) {
  return b >= 0x21 && b <= 0x7e
}

function isHex(b) {
  return (
    (b >= ZERO && b <= NINE) || (b >= UPPER_A && b <= UPPER_F) || (b >= LOWER_A && b <= LOWER_F)
  )
}

function hexValue(b) {
  if (b >= ZERO && b <= NINE) return b - ZERO
  if (b >= UPPER_A && b <= UPPER_F) return b - UPPER_A + 10
  return b - LOWER_A + 10
}
module.exports = class HTTPParserError extends Error {
  constructor(msg, fn = HTTPParserError, code = fn.name) {
    super(`${code}: ${msg}`)

    this.code = code

    if (Error.captureStackTrace) Error.captureStackTrace(this, fn)
  }

  get name() {
    return 'HTTPParserError'
  }

  static INVALID_MESSAGE(msg = 'Invalid HTTP message') {
    return new HTTPParserError(msg, HTTPParserError.INVALID_MESSAGE)
  }

  static INVALID_HEADER(msg = 'Invalid HTTP header') {
    return new HTTPParserError(msg, HTTPParserError.INVALID_HEADER)
  }

  static HEADER_OVERFLOW(msg = 'HTTP headers too large') {
    return new HTTPParserError(msg, HTTPParserError.HEADER_OVERFLOW)
  }

  static INVALID_METHOD(msg = 'Invalid HTTP method') {
    return new HTTPParserError(msg, HTTPParserError.INVALID_METHOD)
  }

  static INVALID_CONTENT_LENGTH(msg = 'Invalid HTTP Content-Length') {
    return new HTTPParserError(msg, HTTPParserError.INVALID_CONTENT_LENGTH)
  }

  static INVALID_CHUNK_LENGTH(msg = 'Invalid HTTP chunk length') {
    return new HTTPParserError(msg, HTTPParserError.INVALID_CHUNK_LENGTH)
  }
}
{
  "name": "bare-http-parser",
  "version": "2.1.4",
  "description": "Streaming HTTP request and response parser for Bare",
  "exports": {
    "./package": "./package.json",
    ".": "./index.js",
    "./errors": "./lib/errors.js"
  },
  "files": [
    "index.js",
    "lib"
  ],
  "scripts": {
    "format": "prettier --write . && lunte --fix",
    "lint": "prettier --check . && lunte",
    "test": "brittle-bare --coverage test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-http-parser.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-http-parser/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-http-parser#readme",
  "devDependencies": {
    "brittle": "^3.3.0",
    "lunte": "^1.8.0",
    "prettier": "^3.4.1",
    "prettier-config-holepunch": "^2.0.0"
  }
}
const errors = require('./lib/errors')

exports.IncomingMessage = require('./lib/incoming-message')
exports.OutgoingMessage = require('./lib/outgoing-message')

exports.Agent = require('./lib/agent')
exports.globalAgent = exports.Agent.global

exports.Server = require('./lib/server')
exports.ServerResponse = require('./lib/server-response')
exports.ServerConnection = require('./lib/server-connection')

exports.ClientRequest = require('./lib/client-request')
exports.ClientConnection = require('./lib/client-connection')

exports.constants = require('./lib/constants')
exports.errors = errors

exports.METHODS = Object.values(exports.constants.method) // For Node.js compatibility
exports.STATUS_CODES = exports.constants.status // For Node.js compatibility

exports.createServer = function createServer(opts, onrequest) {
  return new exports.Server(opts, onrequest)
}

exports.request = function request(url, opts, onresponse) {
  if (typeof opts === 'function') {
    onresponse = opts
    opts = {}
  } else if (opts === undefined || opts === null) {
    opts = {}
  }

  if (typeof url === 'string') url = new URL(url)

  if (isURL(url)) {
    // Every part of the URL is only a default that the options given alongside
    // it may override, as Node.js does.
    const given = opts

    opts = { ...url, ...given }

    if (given.protocol === undefined) opts.protocol = url.protocol
    if (given.path === undefined) opts.path = url.pathname + url.search
    if (given.port === undefined) opts.port = url.port ? parseInt(url.port, 10) : defaultPort(url)

    // The host may be named either way round, and Node.js resolves `hostname`
    // ahead of `host`. A URL always carries a hostname, so one the caller named
    // only as `host` does not displace it. What the caller did name is put back
    // in case a URL-like object carried its own into the merge.
    opts.host = given.host
    opts.hostname = given.hostname === undefined ? hostname(url) : given.hostname

    // A URL may carry credentials, which the request sends as an
    // `Authorization` header, as Node.js does.
    if (opts.auth === undefined && (url.username || url.password)) {
      opts.auth = `${decodeURIComponent(url.username)}:${decodeURIComponent(url.password)}`
    }
  } else {
    opts = { ...url }
  }

  // For Node.js compatibility
  opts.host = opts.hostname || opts.host
  opts.port = typeof opts.port === 'string' ? parseInt(opts.port, 10) : opts.port

  validateProtocol(opts.protocol)

  return new exports.ClientRequest(opts, onresponse)
}

exports.get = function get(url, opts, onresponse) {
  const req = exports.request(url, opts, onresponse)
  req.end()
  return req
}

// https://url.spec.whatwg.org/#default-port
function defaultPort(url) {
  switch (url.protocol) {
    case 'ftp:':
      return 21
    case 'http:':
    case 'ws:':
      return 80
    case 'https:':
    case 'wss:':
      return 443
  }

  return null
}

function validateProtocol(protocol) {
  if (protocol === undefined || protocol === null) return

  if (protocol !== 'http:' && protocol !== 'ws:') {
    throw errors.INVALID_PROTOCOL(`Unsupported protocol: ${JSON.stringify(protocol)}`)
  }
}

function hostname(url) {
  const host = url.hostname

  return host.charCodeAt(0) === 91 /* [ */ ? host.slice(1, -1) : host
}

// https://url.spec.whatwg.org/#api
function isURL(url) {
  return (
    url !== null &&
    typeof url === 'object' &&
    typeof url.protocol === 'string' &&
    typeof url.hostname === 'string' &&
    typeof url.pathname === 'string' &&
    typeof url.search === 'string'
  )
}
const EventEmitter = require('bare-events')
const tcp = require('bare-tcp')
const HTTPClientConnection = require('./client-connection')
const errors = require('./errors')

// How far ahead of the wait a peer announced a pooled socket is given up, so
// that one is never handed to a request in the moment the peer reclaims it.
const KEEP_ALIVE_TIMEOUT_BUFFER = 1000

class HTTPSocketSet {
  constructor() {
    this._sockets = new Map()
    this._size = 0
  }

  get size() {
    return this._size
  }

  add(name, socket) {
    const sockets = this._sockets.get(name)

    this._size++

    if (sockets === undefined) this._sockets.set(name, [socket])
    else sockets.push(socket)
  }

  count(name) {
    const sockets = this._sockets.get(name)

    return sockets === undefined ? 0 : sockets.length
  }

  pop(name) {
    const sockets = this._sockets.get(name)
    if (sockets === undefined || sockets.length === 0) return null

    this._size--

    const last = sockets.pop()

    if (sockets.length === 0) this._sockets.delete(name)

    return last
  }

  delete(name, socket) {
    const sockets = this._sockets.get(name)
    if (sockets === undefined) return false

    const i = sockets.indexOf(socket)
    if (i === -1) return false

    this._size--

    const last = sockets.pop()
    if (last !== socket) sockets[i] = last

    if (sockets.length === 0) this._sockets.delete(name)

    return true
  }

  *sockets() {
    for (const sockets of this._sockets.values()) {
      yield* sockets
    }
  }
}

class HTTPAgent extends EventEmitter {
  static _agents = new Set()

  constructor(opts = {}) {
    super()

    const {
      keepAlive = false,
      keepAliveMsecs = 1000,
      defaultPort = 80,
      maxSockets = Infinity,
      maxFreeSockets = 256,
      maxTotalSockets = Infinity
    } = opts

    this._suspended = false
    this._resuming = null

    this._sockets = new HTTPSocketSet()
    this._freeSockets = new HTTPSocketSet()

    // Requests that are waiting for a socket, as the agent is already holding
    // as many as it may open for them.
    this._queued = new Map()

    this._keepAlive = keepAlive ? (typeof keepAlive === 'number' ? keepAlive : keepAliveMsecs) : -1
    this._defaultPort = defaultPort

    this._maxSockets = maxSockets
    this._maxFreeSockets = maxFreeSockets
    this._maxTotalSockets = maxTotalSockets

    this._opts = { ...opts }
  }

  get suspended() {
    return this._suspended
  }

  get resumed() {
    return this._resuming ? this._resuming.promise : null
  }

  get sockets() {
    return this._sockets.sockets()
  }

  get freeSockets() {
    return this._freeSockets.sockets()
  }

  get defaultPort() {
    return this._defaultPort
  }

  get keepAlive() {
    return this._keepAlive !== -1
  }

  get maxSockets() {
    return this._maxSockets
  }

  set maxSockets(value) {
    this._maxSockets = value
  }

  get maxFreeSockets() {
    return this._maxFreeSockets
  }

  set maxFreeSockets(value) {
    this._maxFreeSockets = value
  }

  get maxTotalSockets() {
    return this._maxTotalSockets
  }

  set maxTotalSockets(value) {
    this._maxTotalSockets = value
  }

  get _held() {
    return this._sockets.size + this._freeSockets.size
  }

  createConnection(opts) {
    if (this._suspended) throw errors.AGENT_SUSPENDED()

    return tcp.createConnection(opts)
  }

  reuseSocket(socket, req) {
    socket.ref()

    // The socket was left holding the deadline it was pooled under, which is
    // one the peer may have named rather than one this side chose. A request
    // that takes the socket back is not idle, so the wait goes back to the one
    // the agent was given, and the request sets its own over that.
    socket.setTimeout(this._opts.timeout > 0 ? this._opts.timeout : 0)
  }

  keepSocketAlive(socket) {
    if (this._keepAlive === -1) return false

    const timeout = this._idleTimeout(socket)

    // The peer gives the connection up again too soon for it to be worth
    // keeping: a request handed one that is already on its way out is a request
    // that has to be made again.
    if (timeout === 0) return false

    socket.setKeepAlive(true, this._keepAlive)
    socket.setTimeout(timeout === -1 ? 0 : timeout)
    socket.unref()

    return true
  }

  getName(opts) {
    let name = opts.host || 'localhost'

    name += ':'
    if (opts.port) name += opts.port

    name += ':'
    if (opts.localAddress) name += opts.localAddress

    // Only appended when it was asked for, so that a request that did not care
    // which family it got is not held apart from one that took the same
    // connection anyway.
    if (opts.family === 4 || opts.family === 6) name += `:${opts.family}`

    if (opts.socketPath) name += `:${opts.socketPath}`

    return name
  }

  addRequest(req, opts) {
    opts = { ...opts, ...this._opts }

    const name = this.getName(opts)

    let socket = null

    while ((socket = this._freeSockets.pop(name)) !== null) {
      if (socket.destroying === false) break
    }

    if (socket !== null) {
      this._sockets.add(name, socket)

      this.reuseSocket(socket, req)
    } else if (this._full(name)) {
      // As many sockets are open as the agent may hold, so the request waits
      // for one of them to come free rather than opening another.
      return this._enqueue(name, req, opts)
    } else {
      socket = this._connect(name, opts)
    }

    this._serve(socket, req, opts)
  }

  suspend() {
    if (this._suspended) return

    this._resuming = Promise.withResolvers()
    this._suspended = true

    this.destroy()
  }

  resume() {
    if (this._resuming === null) return

    this._resuming.resolve()
    this._resuming = null
    this._suspended = false
  }

  destroy() {
    for (const socket of this._sockets.sockets()) socket.destroy()
    for (const socket of this._freeSockets.sockets()) socket.destroy()
  }

  // How long a socket that has come free may sit in the pool: the agent's own
  // wait, held down to however long the peer said it keeps the connection.
  // Zero means the peer keeps it for too little time to be worth pooling at
  // all, and -1 that nothing bounds the wait.
  _idleTimeout(socket) {
    const timeout = this._opts.timeout > 0 ? this._opts.timeout : -1

    const connection = HTTPClientConnection.for(socket)
    const announced = connection === null ? -1 : connection.keepAliveTimeout

    if (announced === -1) return timeout

    // Given up a little ahead of the peer, as a socket handed to a request just
    // as the peer takes it away is one that request loses.
    const bounded = announced - KEEP_ALIVE_TIMEOUT_BUFFER

    if (bounded <= 0) return 0

    return timeout === -1 || bounded < timeout ? bounded : timeout
  }

  // Whether the agent is holding as many sockets as it may open for this name,
  // in which case a request that cannot have one of them waits for one.
  _full(name) {
    return (
      this._held >= this._maxTotalSockets ||
      this._sockets.count(name) + this._freeSockets.count(name) >= this._maxSockets
    )
  }

  // Whether a socket that has come free is one the agent may keep, or one more
  // than it holds room for and so is let go of instead. One that would sit on
  // the last slot the agent has while a request on another name waits for one
  // is let go of as well, as keeping it would leave that request waiting for
  // as long as the pool held on to it.
  _poolable(name) {
    return (
      this._freeSockets.count(name) < this._maxFreeSockets &&
      this._full(name) === false &&
      (this._queued.size === 0 || this._held + 1 < this._maxTotalSockets)
    )
  }

  // Hands a request the socket it goes out on, which may be one it waited for.
  _serve(socket, req, opts) {
    HTTPClientConnection.from(socket, opts).request(req)

    req._onsocket(socket)
  }

  _enqueue(name, req, opts) {
    const queued = this._queued.get(name)
    const pending = { name, req, opts }

    if (queued === undefined) this._queued.set(name, [pending])
    else queued.push(pending)

    req._wait()

    // A request that is given up on while it waits is owed nothing.
    req.on('close', () => this._remove(pending))
  }

  _remove(pending) {
    const queued = this._queued.get(pending.name)
    if (queued === undefined) return

    const i = queued.indexOf(pending)
    if (i === -1) return

    queued.splice(i, 1)

    if (queued.length === 0) this._queued.delete(pending.name)
  }

  // The next request still waiting on this name, or null when none is.
  _shift(name) {
    const queued = this._queued.get(name)
    if (queued === undefined) return null

    let pending = null

    while (queued.length > 0) {
      const next = queued.shift()

      if (next.req.destroying === false) {
        pending = next
        break
      }
    }

    if (queued.length === 0) this._queued.delete(name)

    return pending
  }

  // The next request that is waiting and that the agent now has room to open a
  // socket for, or null when there is none. The name the room came free on is
  // taken first, so that a request is not left behind one on another origin.
  _next(name) {
    if (this._full(name) === false) {
      const pending = this._shift(name)

      if (pending !== null) return pending
    }

    for (const other of [...this._queued.keys()]) {
      if (other === name || this._full(other)) continue

      const pending = this._shift(other)

      if (pending !== null) return pending
    }

    return null
  }

  // Opens sockets for the requests that are waiting, as far as the agent now
  // has room for them.
  _drain(name) {
    while (true) {
      const pending = this._next(name)

      if (pending === null) return

      let socket

      // The agent may have been suspended since the request was made, in which
      // case it is told rather than left waiting on a socket that is never
      // going to be opened for it.
      try {
        socket = this._connect(pending.name, pending.opts)
      } catch (err) {
        pending.req.destroy(err)
        continue
      }

      this._serve(socket, pending.req, pending.opts)
    }
  }

  // Opens a socket and keeps track of it for as long as the agent has it.
  _connect(name, opts) {
    const agent = this
    const socket = this.createConnection(opts)

    socket
      .on('error', noop) // Someone needs to handle it
      .on('free', onfree)
      .on('end', onhalfclose)
      .on('finish', onhalfclose)
      .on('close', onremove)
      .on('timeout', ontimeout)
      .on('agentRemove', onhandover)

    function onfree() {
      if (socket.destroying) return

      if (agent._sockets.delete(name, socket) === false) return

      // A request that has been waiting for a socket takes this one, rather
      // than being left in the queue while it goes into the pool.
      const pending = agent._shift(name)

      // Never actually free, as it goes straight from one request to the next.
      if (pending !== null) {
        agent._sockets.add(name, socket)

        agent.reuseSocket(socket, pending.req)

        return agent._serve(socket, pending.req, pending.opts)
      }

      if (agent._poolable(name) && agent.keepSocketAlive(socket)) {
        agent._freeSockets.add(name, socket)
      } else {
        socket.end()
      }

      agent.emit('free', socket)
    }

    function onhalfclose() {
      socket.off('free', onfree)

      agent._freeSockets.delete(name, socket)

      socket.destroy()
    }

    function onremove() {
      socket.off('free', onfree)

      agent._sockets.delete(name, socket)
      agent._freeSockets.delete(name, socket)

      // A socket going away is room for a request that has been waiting on one.
      agent._drain(name)

      if (agent._held === 0) HTTPAgent._agents.delete(agent)
    }

    // The socket has been handed over to another protocol and is no longer the
    // agent's to pool, to count, or to take down when either side of it closes.
    // It may outlive every request the agent has, so a slot it went on holding
    // would be one no request could ever have back. Only the error handler is
    // left, as whoever took the socket may still have none of their own.
    function onhandover() {
      socket
        .off('free', onfree)
        .off('end', onhalfclose)
        .off('finish', onhalfclose)
        .off('close', onremove)
        .off('timeout', ontimeout)
        .off('agentRemove', onhandover)

      onremove()
    }

    function ontimeout() {
      // A socket that is in use belongs to whoever is using it, and the timeout
      // is theirs to act on. Only an idle one is reclaimed here, since nobody
      // else is listening for it.
      if (agent._freeSockets.delete(name, socket)) socket.destroy()
    }

    // The agent is only tracked while it holds sockets of its own.
    if (this._held === 0) HTTPAgent._agents.add(this)

    this._sockets.add(name, socket)

    return socket
  }

  static _onidle() {
    for (const agent of this._agents) {
      agent.destroy()
    }
  }
}

HTTPAgent.global = new HTTPAgent({ keepAlive: 1000, timeout: 5000 })

module.exports = HTTPAgent

Bare.on('idle', HTTPAgent._onidle.bind(HTTPAgent))

function noop() {}
const { isEnding, isFinished } = require('bare-stream')
const HTTPParser = require('bare-http-parser')
const HTTPConnection = require('./connection')
const HTTPIncomingMessage = require('./incoming-message')
const errors = require('./errors')
const { get, isClosing, isUpgrading } = require('./headers')

const {
  constants: { REQUEST, RESPONSE, DATA, END }
} = HTTPParser

module.exports = class HTTPClientConnection extends HTTPConnection {
  static _connections = new WeakMap()

  static from(socket, opts) {
    return this.for(socket) || new this(socket, opts)
  }

  constructor(socket, opts = {}) {
    const { IncomingMessage = HTTPIncomingMessage } = opts

    super(socket, opts)

    this._IncomingMessage = IncomingMessage

    // Whether the peer has said that it is going to close the connection, in
    // which case the socket must not go back into the agent's pool.
    this._close = false

    // How long the peer said it holds on to the connection, in milliseconds, or
    // -1 when it said nothing. A socket kept for longer than the peer keeps it
    // is one the next request goes out on just as it is being taken away.
    this._keepAliveTimeout = -1

    // Whether an interim 1xx response is being parsed, whose end does not
    // complete the request.
    this._interim = false

    // Whether the request in hand has been answered.
    this._responded = false

    this._attach()
  }

  get keepAliveTimeout() {
    return this._keepAliveTimeout
  }

  request(req) {
    this._req = req
    this._responded = false

    // A response to HEAD is framed as though it had a body but never carries
    // one, and only the request knows that.
    if (req.method === 'HEAD') this._parser.skipBody()
  }

  // The response has arrived in full, so both messages let go of the socket
  // rather than take it down with them when they are destroyed.
  _complete() {
    // A request that never finished writing would go on sending a body onto a
    // socket the peer has already moved past, so the connection cannot carry
    // another exchange.
    if (this._req !== null && isFinished(this._req) === false) this._close = true

    // Anything the peer sent past the response would be read as the answer to
    // whatever request the connection carried next.
    if (this._pending.length > 0 || this._parser.drain().byteLength > 0) this._close = true

    if (this._res !== null) {
      this._res._socket = null
      this._res._complete = true
      this._res.push(null)
    }

    if (this._req !== null) {
      this._req._socket = null
      this._req.destroy()
    }
  }

  // Cuts short whatever is in flight, reporting the error to the response if
  // there is one, as its consumer is the one waiting on it, and to the request
  // otherwise.
  _fail(err = errors.CONNECTION_LOST()) {
    const req = this._req
    const res = this._res
    const responded = this._responded

    this._req = null
    this._res = null

    this._pending = []
    this._ops = null

    // A response whose body has already arrived in full is left alone, as its
    // consumer may not have read all of it yet.
    if (res !== null && isEnding(res) === false) res._abort(err)

    // Once the request has been answered the response is what its consumer is
    // waiting on, whether it is still holding the response or has already let
    // go of it.
    if (req !== null) {
      if (responded) req.destroy()
      else req.destroy(err)
    }
  }

  _continue() {
    while (this._stopped === false) {
      let op

      // Input that cannot be parsed, or that answers a request the connection
      // is not carrying, leaves it in no state to go on from. Dispatching is
      // left outside, as what a consumer makes of a message it has been handed
      // is its own affair and so is anything it throws over one.
      try {
        op = this._pullOp()

        if (op !== null) {
          if (op.type === RESPONSE) this._expectResponse()
          // A request is not something a peer answers with, and one consumed in
          // silence would let it choose how many of the bytes that follow this
          // side reads as a body rather than as the response it is waiting for.
          else if (op.type === REQUEST) throw errors.UNEXPECTED_REQUEST()
        }
      } catch (err) {
        return this._socket.destroy(err)
      }

      if (op === null) break

      this._dispatch(op)

      if (this._backpressure) return this._pause()
    }

    if (this._stopped) return

    this._resume()

    if (this._ended) this._endInput()
  }

  _endInput() {
    this._ended = false

    let complete = false

    // A response whose body is framed by the connection closing is completed by
    // the close. Anything else still in flight has been cut short.
    try {
      for (const op of this._parser.end()) {
        if (op.type === END) complete = true
      }
    } catch {
      // Reported as a lost connection below.
    }

    if (complete) this._complete()
    else this._fail()
  }

  _dispatch(op) {
    switch (op.type) {
      case RESPONSE:
        return this._response(op)

      case DATA:
        // A push that does not fit is the signal to stop reading the socket
        // until the consumer has caught up.
        if (this._res && this._res.push(op.data) === false) this._backpressure = true
        break

      case END:
        // The end of an interim response does not complete the request.
        if (this._interim) {
          this._interim = false
          break
        }

        this._complete()
        break
    }
  }

  // A peer that answers of its own accord, or answers the same request twice, is
  // trying to get a response of its choosing paired up with whatever request
  // comes next on the connection.
  _expectResponse() {
    if (this._req === null || this._res !== null) throw errors.UNEXPECTED_RESPONSE()
  }

  _response(op) {
    const httpVersion = op.version === 'HTTP/1.0' ? '1.0' : '1.1'

    // Only a `101` switches protocols. A peer that names an upgrade alongside
    // any other status is answering the request rather than handing the
    // connection over, and must not be able to take the socket away from the
    // consumer waiting on a response, as Node.js does not let it.
    const upgrade = op.code === 101 && isUpgrading(op.headers)

    // Whatever a CONNECT is answered with is the answer about the tunnel, so
    // everything past the headers belongs to what the two ends made of it
    // rather than to HTTP, as Node.js has it. A refusal reaches the consumer
    // with the response that explains it rather than as no event at all.
    const tunnel = this._req.method === 'CONNECT'

    // A 101 that names no protocol has nothing to hand the connection over to,
    // so it is delivered as the response it claims to be, as Node.js does.
    const switching = op.code === 101 && upgrade === false

    // An interim 1xx response is not the response to the request; the real one
    // still follows, so the request is left open.
    if (
      upgrade === false &&
      tunnel === false &&
      op.code >= 100 &&
      op.code < 200 &&
      op.code !== 101
    ) {
      return this._information(op, httpVersion)
    }

    const req = this._req

    req.on('close', () => {
      // Only the request the connection is currently on has any say in this. A
      // connection that goes back into the pool may already have been handed the
      // next request by the time this one closes.
      if (this._req !== req) return

      this._req = null
    })

    const res = (this._res = new this._IncomingMessage(this._socket, {
      headers: op.headers,
      httpVersion,
      statusCode: op.code,
      statusMessage: op.reason
    }))

    this._responded = true

    res._demand = this._demand

    // The parser stops for good at a 101, so a connection that carried one
    // cannot carry another exchange and must not be offered for one.
    this._close = switching || isClosing(op.headers, httpVersion)

    this._keepAliveTimeout = keepAliveTimeout(op.headers)

    // A socket that is handed over to another protocol is no longer ours to hand
    // back to the agent, so the handler that does so is registered below rather
    // than above this.
    if (upgrade || tunnel) {
      return this._handover(this._parser.drain(), tunnel ? 'connect' : 'upgrade')
    }

    res.on('close', () => {
      // Only the response the connection is currently on has any say in what
      // becomes of the socket.
      if (this._res !== res) return

      this._res = null
      this._idle = true

      if (this._close) this._socket.end()
      else this._socket.emit('free')
    })

    req.emit('response', res)
  }

  _information(op, httpVersion) {
    const req = this._req

    this._interim = true

    // The parser spends `skipBody` on the first set of headers it completes, and
    // an interim response is not the one the request was waiting for.
    if (req.method === 'HEAD') this._parser.skipBody()

    // A 100 answers an expectation the request announced, and is what a caller
    // that is holding its body back is waiting for. Reported as an event of its
    // own, ahead of the one every interim response gets, as Node.js does.
    if (op.code === 100) req.emit('continue')

    req.emit('information', {
      httpVersion,
      statusCode: op.code,
      statusMessage: op.reason,
      headers: op.headers
    })
  }

  _handover(data, event) {
    const req = this._req
    const res = this._res
    const head = this._takeover(data)

    // The socket belongs to whatever protocol is taking it over now, so whoever
    // opened it is told to stop counting it as one of theirs.
    this._socket.emit('agentRemove')

    res._upgrade = req._upgrade = true
    res._complete = true

    const handled = req.emit(event, res, this._socket, head)

    res.push(null)
    req.destroy()

    if (!handled) this._socket.destroy()
  }

  _detach() {
    super._detach()

    HTTPClientConnection._connections.delete(this._socket)
  }

  _onerror(err) {
    this._fail(err)
  }

  _onclose() {
    // Input that was held back while the body was not being read still frames
    // the response, so it is finished off before the close is reported and the
    // rest of the body is thrown away.
    if (this._ended) {
      this._backpressure = false

      this._continue()
    }

    // No error of its own, as the close may well have been initiated locally.
    this._fail()
  }

  _ondrain() {
    if (this._req) this._req._continueWrite()
  }

  _ontimeout() {
    if (this._req) this._req.emit('timeout')
  }
}

// How long the peer says it holds an idle connection open for, in milliseconds.
// https://datatracker.ietf.org/doc/html/draft-thomson-hybi-http-timeout-03
function keepAliveTimeout(headers) {
  const value = get(headers, 'keep-alive')

  if (value === undefined) return -1

  for (const parameter of String(value).split(',')) {
    const match = /^\s*timeout\s*=\s*(\d+)\s*$/.exec(parameter)

    if (match === null) continue

    const ms = Number(match[1]) * 1000

    // A wait longer than a timer can be given is one nothing was said about, as
    // the peer is not going to reclaim the connection before this side does.
    return Number.isSafeInteger(ms) && ms <= 0x7fffffff ? ms : -1
  }

  return -1
}
const HTTPAgent = require('./agent')
const HTTPOutgoingMessage = require('./outgoing-message').Duplex
const errors = require('./errors')
const { get, has, normalize, single } = require('./headers')
const validate = require('./validate')

// A content length of no length, however the caller happened to spell it.
const ZERO_RE = /^0+$/

module.exports = class HTTPClientRequest extends HTTPOutgoingMessage {
  constructor(opts = {}, onresponse = null) {
    if (typeof opts === 'function') {
      onresponse = opts
      opts = {}
    }

    opts = opts ? { ...opts } : {}

    const agent = opts.agent === false ? new HTTPAgent() : opts.agent || HTTPAgent.global
    const method = opts.method || 'GET'
    const path = opts.path || '/'
    const defaultPort = opts.defaultPort || agent.defaultPort || 80
    const host = (opts.host = opts.host || 'localhost')
    const port = (opts.port = opts.port || defaultPort)

    validate.validateHost(host)
    validate.validateMethod(method)
    validate.validatePath(path)

    // The request target of a CONNECT is the authority of the tunnel rather
    // than a path, and RFC 9110 has `Host` name that same authority: a proxy
    // that routes on the field would otherwise be told about itself. A caller
    // that named no target has no authority to mirror, so the one the socket
    // was opened to stands in.
    const target = method === 'CONNECT' && opts.path ? path : hostHeader(host, port, defaultPort)

    const fields = normalize({ host: target, ...opts.headers })

    // Credentials named alongside the request, or carried by the URL it was made
    // from, are sent as a header, unless the caller set one for itself.
    if (opts.auth !== undefined && opts.auth !== null && has(fields, 'authorization') === false) {
      fields.authorization = basicAuth(opts.auth)
    }

    super()

    this._headers = fields
    this._method = method
    this._path = path

    // Whether the agent means to take the socket back once the exchange is
    // done, which the peer is only told about when it does not.
    this._keepAlive = agent.keepAlive

    // GET and HEAD requests carry no body unless one is written, in which case
    // it is framed like any other.
    this._expectsBody = method !== 'GET' && method !== 'HEAD'

    // An agent that is already holding as many sockets as it may open has none
    // to give until one comes free, so the request waits and whatever is
    // written meanwhile waits with it.
    this._waiting = false
    this._parked = null

    // Kept here rather than left to the socket, as the agent's own options
    // override the request's and would otherwise displace it.
    this._timeout = opts.timeout === undefined ? null : opts.timeout

    agent.addRequest(this, opts)

    if (onresponse) this.once('response', onresponse)
  }

  get method() {
    return this._method
  }

  get path() {
    return this._path
  }

  // For Node.js compatibility
  abort() {
    return this.destroy()
  }

  // A socket the agent has yet to hand over cannot be given a timeout, so one
  // set before it arrives is kept until it can be.
  setTimeout(ms, ontimeout) {
    this._timeout = ms

    return super.setTimeout(ms, ontimeout)
  }

  // Called by the agent when it has no socket to give the request yet.
  _wait() {
    this._waiting = true
  }

  // And called with the one the request goes out on, whether that was to be had
  // straight away or had to be waited for.
  _onsocket(socket) {
    this._socket = socket
    this._waiting = false

    if (this._timeout !== null) socket.setTimeout(this._timeout)

    this._unpark()

    if (has(this._headers, 'expect')) this._sendExpectation()
  }

  // A caller that announced an expectation is waiting to be answered before it
  // sends its body, and the peer cannot answer headers it has not been sent, so
  // the headers go out ahead of the body rather than with it. Node.js sends them
  // ahead for the same reason.
  _sendExpectation() {
    // Left to a task of its own so that the caller keeps the tick it made the
    // request in to set headers of its own, as it would have with any other
    // request and as Node.js allows.
    queueMicrotask(() => {
      if (this._headersSent || this.destroying || this._socket === null) return

      // An expectation is an offer of a body, so the headers have to frame one
      // even though none has been written yet.
      this._writing = true

      try {
        this.flushHeaders()
      } catch (err) {
        // Nothing is going to be written that would otherwise report this, as
        // the caller is waiting on an answer that is never coming.
        this.destroy(err)
      }
    })
  }

  // Lets go of a write that was waiting on a socket. One let go of because the
  // request is being destroyed finds none and reports it, as any other write
  // onto a message without a socket does.
  _unpark() {
    const parked = this._parked

    if (parked === null) return

    this._parked = null

    parked()
  }

  _write(data, encoding, cb) {
    if (this._waiting) {
      this._parked = () => super._write(data, encoding, cb)

      return
    }

    super._write(data, encoding, cb)
  }

  _final(cb) {
    if (this._waiting) {
      this._parked = () => super._final(cb)

      return
    }

    super._final(cb)
  }

  _predestroy() {
    super._predestroy()

    this._waiting = false

    this._unpark()
  }

  // Everything past a `CONNECT` request line belongs to the tunnel rather than
  // to HTTP, so a request that opens one is not framed at all. An upgrade is not
  // a tunnel yet: RFC 9110 has the peer switch protocols only once the request
  // has been received in full, so its body is framed like any other and the
  // peer is left in no doubt as to where the handover begins.
  _isTunnel() {
    return this._method === 'CONNECT'
  }

  _header() {
    validate.validateMethod(this._method)
    validate.validatePath(this._path)

    let h = `${this._method} ${this._path} HTTP/1.1\r\n` + this._fields()

    // A connection that is not going back into a pool carries nothing after
    // this exchange, and a peer that is not told holds it open for as long as
    // its own keep-alive allows. A tunnel is left alone, as the socket is being
    // given to another protocol rather than given up.
    if (
      this._keepAlive === false &&
      this._isTunnel() === false &&
      has(this._headers, 'connection') === false
    ) {
      h += 'Connection: close\r\n'
    }

    return h + '\r\n'
  }

  _frame(length = -1) {
    if (this._isTunnel()) return

    // A method that carries no body by default is sent without any framing at
    // all unless one is actually being written, since a peer that is promised a
    // body of unknown length reads whatever follows the headers as one. A
    // length the caller announced that the absent body already satisfies is the
    // exception, as it leaves the peer nothing to wait for.
    if (
      length <= 0 &&
      this._expectsBody === false &&
      this._writing === false &&
      announcesEmptyBody(this._headers) === false
    ) {
      return this._frameNone()
    }

    super._frame(length)
  }

  _frameEmpty() {
    this._frame(0)
  }

  // Sends the headers with nothing to follow them, dropping any framing the
  // caller announced: nothing is going to arrive, and the peer would either wait
  // for a body that never comes or read the next request as one. A length of
  // zero is what holds anything written afterwards to that, as there is no
  // longer anywhere to frame it.
  _frameNone() {
    delete this._headers['transfer-encoding']
    delete this._headers['content-length']

    this._length = 0
  }

  _mismatch(written) {
    // Nothing was announced, so there is nothing for the body to be measured
    // against: there is simply nowhere left to put it.
    if (this._length === 0 && has(this._headers, 'content-length') === false) {
      return errors.CONTENT_LENGTH_MISMATCH(
        `Body is ${written} bytes but the request was sent without one`
      )
    }

    return super._mismatch(written)
  }
}

// Whether these fields announce a body of no length, which is exactly what a
// message that carries none has. Anything else the caller announced describes a
// body that is not coming, and only one spelling of no body counts, so that a
// length the message could not be held to is dropped rather than sent.
function announcesEmptyBody(fields) {
  if (has(fields, 'transfer-encoding')) return false

  const length = single(get(fields, 'content-length'))

  if (typeof length === 'number') return length === 0

  return typeof length === 'string' && ZERO_RE.test(length)
}

// https://www.rfc-editor.org/rfc/rfc7617
function basicAuth(credentials) {
  return `Basic ${Buffer.from(String(credentials)).toString('base64')}`
}

function hostHeader(host, port, defaultPort) {
  const i = host.indexOf(':')

  if (i !== -1 && host.includes(':', i + 1) && host.charCodeAt(0) !== 91 /* [ */) {
    host = `[${host}]`
  }

  if (port && +port !== defaultPort) {
    host += ':' + port
  }

  return host
}
const HTTPParser = require('bare-http-parser')
const Deadline = require('./deadline')

const EMPTY = Buffer.alloc(0)

// The socket side of an HTTP connection: reading a stream of messages off a
// socket, whichever end of the exchange this is.
module.exports = class HTTPConnection {
  static for(socket) {
    return this._connections.get(socket) || null
  }

  constructor(socket, opts = {}) {
    const { maxHeaderSize, maxHeadersCount } = opts

    this._socket = socket
    this._parser = new HTTPParser({ maxHeaderSize, maxHeadersCount })

    this._req = null
    this._res = null

    this._idle = true

    // Data that has not been handed to the parser yet, and the operations that
    // the parser is currently yielding.
    this._pending = []
    this._ops = null

    this._paused = false
    this._ended = false
    this._detached = false

    // Whether the body is arriving faster than it is being read, in which case
    // the socket is left paused until the consumer asks for more.
    this._backpressure = false

    this._deadlines = []

    this._onerror = this._onerror.bind(this)
    this._onclose = this._onclose.bind(this)
    this._onend = this._onend.bind(this)
    this._ondata = this._ondata.bind(this)
    this._ondrain = this._ondrain.bind(this)
    this._ontimeout = this._ontimeout.bind(this)
    this._demand = this._demand.bind(this)
  }

  get socket() {
    return this._socket
  }

  get req() {
    return this._req
  }

  get res() {
    return this._res
  }

  get idle() {
    return this._idle
  }

  // Whether the connection has stopped reading for good. A subclass may have
  // more reasons to stop than being detached.
  get _stopped() {
    return this._detached
  }

  // Deadlines made here are suspended for as long as the socket is, so that
  // none of them count time spent waiting on this side.
  _deadline(ms, onexpire) {
    const deadline = new Deadline(ms, onexpire)

    this._deadlines.push(deadline)

    return deadline
  }

  _clearDeadlines() {
    for (const deadline of this._deadlines) deadline.disarm()
  }

  // The next operation the parser has for the input in hand, or null when it
  // has none left. Throws if the input cannot be parsed.
  _pullOp() {
    while (true) {
      if (this._ops === null) {
        if (this._pending.length === 0) return null

        this._ops = this._parser.push(this._pending.shift())
      }

      let next

      try {
        next = this._ops.next()
      } catch (err) {
        this._ops = null

        throw err
      }

      if (next.done === false) return next.value

      this._ops = null
    }
  }

  _demand() {
    if (this._backpressure === false) return

    this._backpressure = false

    this._continue()
  }

  // Detaches from the socket and hands back everything that was read past the
  // message, which belongs to whatever protocol is taking the socket over,
  // whether the parser had got to it yet or not.
  _takeover(data) {
    const pending = this._pending

    this._pending = []
    this._ops = null

    this._detach()

    const head = data || EMPTY

    return pending.length === 0 ? head : Buffer.concat([head, ...pending])
  }

  _pause() {
    if (this._paused) return

    this._paused = true
    this._socket.pause()

    for (const deadline of this._deadlines) deadline.suspend()
  }

  _resume() {
    if (this._paused === false) return

    // The body is not being read, so there is still a reason to leave the
    // socket alone.
    if (this._backpressure) return

    this._paused = false
    this._socket.resume()

    for (const deadline of this._deadlines) deadline.resume()
  }

  _attach() {
    this._socket
      .on('error', this._onerror)
      .on('close', this._onclose)
      .on('end', this._onend)
      .on('data', this._ondata)
      .on('drain', this._ondrain)
      .on('timeout', this._ontimeout)

    this.constructor._connections.set(this._socket, this)
  }

  _detach() {
    this._detached = true

    this._clearDeadlines()

    // Whoever takes the socket over may not be ready for it in this tick, and
    // a socket left flowing with nobody listening reads its data and drops it.
    this._socket.pause()

    this._socket
      .off('error', this._onerror)
      .off('close', this._onclose)
      .off('end', this._onend)
      .off('data', this._ondata)
      .off('drain', this._ondrain)
      .off('timeout', this._ontimeout)
  }

  _onend() {
    this._ended = true

    this._continue()
  }

  _ondata(data) {
    this._idle = false

    this._pending.push(data)

    this._continue()
  }
}
module.exports = {
  // Every method a request may name, which is what `bare-http-parser` accepts on
  // the way in and so what a server here will serve. Kept in step with it.
  method: {
    ACL: 'ACL',
    BIND: 'BIND',
    CHECKOUT: 'CHECKOUT',
    CONNECT: 'CONNECT',
    COPY: 'COPY',
    DELETE: 'DELETE',
    GET: 'GET',
    HEAD: 'HEAD',
    LINK: 'LINK',
    LOCK: 'LOCK',
    'M-SEARCH': 'M-SEARCH',
    MERGE: 'MERGE',
    MKACTIVITY: 'MKACTIVITY',
    MKCALENDAR: 'MKCALENDAR',
    MKCOL: 'MKCOL',
    MOVE: 'MOVE',
    NOTIFY: 'NOTIFY',
    OPTIONS: 'OPTIONS',
    PATCH: 'PATCH',
    POST: 'POST',
    PROPFIND: 'PROPFIND',
    PROPPATCH: 'PROPPATCH',
    PURGE: 'PURGE',
    PUT: 'PUT',
    QUERY: 'QUERY',
    REBIND: 'REBIND',
    REPORT: 'REPORT',
    SEARCH: 'SEARCH',
    SOURCE: 'SOURCE',
    SUBSCRIBE: 'SUBSCRIBE',
    TRACE: 'TRACE',
    UNBIND: 'UNBIND',
    UNLINK: 'UNLINK',
    UNLOCK: 'UNLOCK',
    UNSUBSCRIBE: 'UNSUBSCRIBE'
  },
  status: {
    100: 'Continue',
    101: 'Switching Protocols',
    102: 'Processing',
    103: 'Early Hints',
    200: 'OK',
    201: 'Created',
    202: 'Accepted',
    203: 'Non-Authoritative Information',
    204: 'No Content',
    205: 'Reset Content',
    206: 'Partial Content',
    207: 'Multi-Status',
    208: 'Already Reported',
    226: 'IM Used',
    300: 'Multiple Choices',
    301: 'Moved Permanently',
    302: 'Found',
    303: 'See Other',
    304: 'Not Modified',
    305: 'Use Proxy',
    307: 'Temporary Redirect',
    308: 'Permanent Redirect',
    400: 'Bad Request',
    401: 'Unauthorized',
    402: 'Payment Required',
    403: 'Forbidden',
    404: 'Not Found',
    405: 'Method Not Allowed',
    406: 'Not Acceptable',
    407: 'Proxy Authentication Required',
    408: 'Request Timeout',
    409: 'Conflict',
    410: 'Gone',
    411: 'Length Required',
    412: 'Precondition Failed',
    413: 'Payload Too Large',
    414: 'URI Too Long',
    415: 'Unsupported Media Type',
    416: 'Range Not Satisfiable',
    417: 'Expectation Failed',
    418: "I'm a Teapot",
    421: 'Misdirected Request',
    422: 'Unprocessable Entity',
    423: 'Locked',
    424: 'Failed Dependency',
    425: 'Too Early',
    426: 'Upgrade Required',
    428: 'Precondition Required',
    429: 'Too Many Requests',
    431: 'Request Header Fields Too Large',
    451: 'Unavailable For Legal Reasons',
    500: 'Internal Server Error',
    501: 'Not Implemented',
    502: 'Bad Gateway',
    503: 'Service Unavailable',
    504: 'Gateway Timeout',
    505: 'HTTP Version Not Supported',
    506: 'Variant Also Negotiates',
    507: 'Insufficient Storage',
    508: 'Loop Detected',
    509: 'Bandwidth Limit Exceeded',
    510: 'Not Extended',
    511: 'Network Authentication Required'
  }
}
// Measured against the monotonic clock, as a deadline that counted wall clock
// time would be cut short or drawn out by a step in the system time.
const hrtime = require('bare-hrtime')

const MILLISECOND = 1000000n

module.exports = class Deadline {
  constructor(ms, onexpire) {
    // Anything that is not a wait of some length is no wait at all. A timer
    // takes `Infinity` and `NaN` to mean the next tick, which would turn either
    // one into a deadline that expires immediately rather than never.
    this._ms = Number.isFinite(ms) && ms > 0 ? Math.ceil(ms) : 0
    this._onexpire = onexpire

    // Counted in nanoseconds, as a deadline that only ever counted whole
    // milliseconds would lose the remainder of every wait it was suspended
    // part way through, and so never advance at all under a peer that suspends
    // it more than once a millisecond.
    this._total = BigInt(this._ms) * MILLISECOND
    this._remaining = this._total
    this._since = 0n
    this._timer = null
    this._armed = false
  }

  get armed() {
    return this._armed
  }

  arm() {
    this._stop()

    this._armed = true
    this._remaining = this._total

    this._start()
  }

  disarm() {
    this._stop()

    this._armed = false
    this._remaining = this._total
  }

  suspend() {
    this._stop()
  }

  resume() {
    if (this._armed) this._start()
  }

  _start() {
    if (this._ms === 0 || this._timer !== null) return

    this._since = hrtime.bigint()

    // Rounded up, as a wait shorter than the clock the timer runs on must not
    // be treated as no wait at all.
    const ms = Number((this._remaining + MILLISECOND - 1n) / MILLISECOND)

    this._timer = setTimeout(this._onexpire, ms)
    this._timer.unref()
  }

  _stop() {
    if (this._timer === null) return

    clearTimeout(this._timer)
    this._timer = null

    const elapsed = hrtime.bigint() - this._since

    // Never all the way to zero, as that would be indistinguishable from a
    // deadline that has not started counting yet.
    this._remaining = elapsed >= this._remaining ? 1n : this._remaining - elapsed
  }
}
module.exports = class HTTPError extends Error {
  constructor(msg, fn = HTTPError, code = fn.name) {
    super(`${code}: ${msg}`)
    this.code = code

    if (Error.captureStackTrace) {
      Error.captureStackTrace(this, fn)
    }
  }

  get name() {
    return 'HTTPError'
  }

  static NOT_IMPLEMENTED(msg = 'Method not implemented') {
    return new HTTPError(msg, HTTPError.NOT_IMPLEMENTED)
  }

  static CONNECTION_LOST(msg = 'Socket hung up') {
    return new HTTPError(msg, HTTPError.CONNECTION_LOST)
  }

  static AGENT_SUSPENDED(msg = 'Agent is suspended') {
    return new HTTPError(msg, HTTPError.AGENT_SUSPENDED)
  }

  static INVALID_HEADER_NAME(msg = 'Invalid header name') {
    return new HTTPError(msg, HTTPError.INVALID_HEADER_NAME)
  }

  static INVALID_HEADER_VALUE(msg = 'Invalid header value') {
    return new HTTPError(msg, HTTPError.INVALID_HEADER_VALUE)
  }

  static INVALID_STATUS_CODE(msg = 'Invalid status code') {
    return new HTTPError(msg, HTTPError.INVALID_STATUS_CODE)
  }

  static HEADERS_SENT(msg = 'Headers have already been sent') {
    return new HTTPError(msg, HTTPError.HEADERS_SENT)
  }

  static INVALID_CONTENT_LENGTH(msg = 'Invalid content length') {
    return new HTTPError(msg, HTTPError.INVALID_CONTENT_LENGTH)
  }

  static CONTENT_LENGTH_MISMATCH(msg = 'Body does not match its content length') {
    return new HTTPError(msg, HTTPError.CONTENT_LENGTH_MISMATCH)
  }

  static INVALID_BODY(msg = 'Invalid body') {
    return new HTTPError(msg, HTTPError.INVALID_BODY)
  }

  static INVALID_TRANSFER_ENCODING(msg = 'Invalid transfer encoding') {
    return new HTTPError(msg, HTTPError.INVALID_TRANSFER_ENCODING)
  }

  static BODY_TOO_LARGE(msg = 'Body is too large') {
    return new HTTPError(msg, HTTPError.BODY_TOO_LARGE)
  }

  static UNEXPECTED_RESPONSE(msg = 'Response received without a request') {
    return new HTTPError(msg, HTTPError.UNEXPECTED_RESPONSE)
  }

  static UNEXPECTED_REQUEST(msg = 'Request received in place of a response') {
    return new HTTPError(msg, HTTPError.UNEXPECTED_REQUEST)
  }

  static REQUEST_TIMEOUT(msg = 'Request timed out') {
    return new HTTPError(msg, HTTPError.REQUEST_TIMEOUT)
  }

  static INVALID_PROTOCOL(msg = 'Invalid protocol') {
    return new HTTPError(msg, HTTPError.INVALID_PROTOCOL)
  }
}
const validate = require('./validate')

// The header bags are created without a prototype so that a name that collides
// with something on `Object.prototype` can neither be mistaken for a header
// that was set nor, in the case of `__proto__`, change the bag itself.
exports.bag = function () {
  return Object.create(null)
}

// Returns a copy of the headers with every name lowercased, so that a header
// can only ever be set once no matter which casing the caller used. Sending a
// header twice is a framing hazard for anything that reads Content-Length.
exports.normalize = function (headers) {
  const normalized = exports.bag()

  if (headers) {
    for (const name of Object.keys(headers)) {
      // Validated as it was given, so that a name only a fold of case makes a
      // token is refused here as it is by `setHeader`.
      validate.validateHeaderName(name)

      const n = name.toLowerCase()

      validate.validateHeaderValue(n, headers[name])

      normalized[n] = headers[name]
    }
  }

  return normalized
}

// Only a header that was actually set counts, never one inherited from a
// prototype the bag may have been given by its creator.
exports.has = function (headers, name) {
  return Object.hasOwn(headers, name)
}

exports.get = function (headers, name) {
  return Object.hasOwn(headers, name) ? headers[name] : undefined
}

// Whether a field's value is the one token given and nothing besides. Unlike
// `hasToken` a list is refused, which for `Transfer-Encoding` is the difference
// between a body this side knows how to frame and one it does not.
exports.isToken = function (value, token) {
  if (Array.isArray(value)) return value.length === 1 && exports.isToken(value[0], token)

  return String(value).trim().toLowerCase() === token
}

// The one value a field carries, for a field that may only ever carry one. A
// list of exactly one names the same value however it was spelled, and anything
// longer describes a header sent more than once, which for `Content-Length` is
// what a peer reading a different message out of the same bytes depends on.
exports.single = function (value) {
  return Array.isArray(value) && value.length === 1 ? value[0] : value
}

// Headers such as `Connection` carry a comma separated list of tokens, which
// are case insensitive.
exports.hasToken = function (value, token) {
  if (value === undefined || value === null) return false

  if (Array.isArray(value)) {
    for (const element of value) {
      if (exports.hasToken(element, token)) return true
    }

    return false
  }

  for (const t of String(value).split(',')) {
    if (t.trim().toLowerCase() === token) return true
  }

  return false
}

// Whether these fields ask for the connection to be handed over to another
// protocol. Both the `Upgrade` header naming it and a `Connection` header
// listing the token are required, as Node.js requires: going on the latter
// alone would let an ordinary message take the socket away from its consumer.
exports.isUpgrading = function (headers) {
  return (
    exports.has(headers, 'upgrade') &&
    exports.hasToken(exports.get(headers, 'connection'), 'upgrade')
  )
}

// Whether a peer that sent these fields means to close the connection once the
// message is done. HTTP/1.0 has no persistent connections unless the peer asks
// for them, and a peer of any version may bow out with `Connection: close`.
exports.isClosing = function (headers, httpVersion) {
  const connection = exports.get(headers, 'connection')

  if (exports.hasToken(connection, 'close')) return true

  return httpVersion === '1.0' && exports.hasToken(connection, 'keep-alive') === false
}

// Serializes one field into the one or more lines it occupies. A value given as
// an array is a list, and a list is repeated across lines rather than folded
// onto one, because `Set-Cookie` cannot be folded at all: its own value
// contains the commas that would otherwise separate the elements. `Cookie` is
// the exception, as its list separator is `; ` and it may only appear once.
exports.serialize = function (name, value) {
  const n = httpCase(name)

  if (Array.isArray(value)) {
    if (value.length === 0) return ''

    const elements = new Array(value.length)

    for (let i = 0, m = value.length; i < m; i++) elements[i] = toField(name, value[i])

    if (isCookie(name) && elements.length > 1) return n + ': ' + elements.join('; ') + '\r\n'

    let s = ''

    for (const element of elements) s += n + ': ' + element + '\r\n'

    return s
  }

  return n + ': ' + toField(name, value) + '\r\n'
}

function toField(name, value) {
  value = String(value)

  validate.validateHeaderFieldValue(name, value)

  return value
}

function isCookie(name) {
  return name.toLowerCase() === 'cookie'
}

function httpCase(name) {
  const parts = name.split('-')

  for (let i = 0, n = parts.length; i < n; i++) {
    parts[i] = parts[i].slice(0, 1).toUpperCase() + parts[i].slice(1)
  }

  return parts.join('-')
}
const { Readable } = require('bare-stream')
const HTTPMessage = require('./message')
const { bag, normalize } = require('./headers')

module.exports = class HTTPIncomingMessage extends HTTPMessage(Readable) {
  constructor(socket = null, opts = {}) {
    super(socket)

    // Whether the message has let go of the socket, which happens as soon as
    // the whole of it has arrived.
    this._detached = false

    // Whether the whole of the message arrived, as against the peer having gone
    // away part way through it. The stream closes either way, so this is what
    // tells a consumer which of the two it was handed.
    this._complete = false

    this._headers = opts.headers || bag()
    this._httpVersion = opts.httpVersion || '1.1'

    // Server options
    this._method = opts.method || ''
    this._url = opts.url || ''

    // Client options
    this._statusCode = opts.statusCode || 0
    this._statusMessage = opts.statusMessage || ''

    // Called when the consumer is ready for more of the body, so that the
    // connection it came from can go back to reading the socket.
    this._demand = null
  }

  get headers() {
    return this._headers
  }

  set headers(value) {
    this._headers = normalize(value)
  }

  get method() {
    return this._method
  }

  set method(value) {
    this._method = value
  }

  get url() {
    return this._url
  }

  set url(value) {
    this._url = value
  }

  get statusCode() {
    return this._statusCode
  }

  set statusCode(value) {
    this._statusCode = value
  }

  get statusMessage() {
    return this._statusMessage
  }

  set statusMessage(value) {
    this._statusMessage = value
  }

  get httpVersion() {
    return this._httpVersion
  }

  get complete() {
    return this._complete
  }

  // Cuts the message short. The failure is only reported as an error if someone
  // is there to hear it, as Node.js does, since an unhandled `error` would take
  // the process down instead. A consumer with nobody listening still has
  // `complete` to tell it that the message it was handed stops short.
  _abort(err = null) {
    if (err !== null && this.listenerCount('error') > 0) this.destroy(err)
    else this.destroy()
  }

  _read() {
    if (this._demand) this._demand()
  }

  _predestroy() {
    if (this._upgrade === false && this._detached === false && this._socket !== null) {
      this._socket.destroy()
    }
  }
}
const { bag, get, has } = require('./headers')
const { validateHeaderNameType } = require('./validate')

// The members that every message has, incoming or outgoing, whichever kind of
// stream it is carried by.
module.exports = function HTTPMessage(Stream) {
  return class HTTPMessage extends Stream {
    constructor(socket = null) {
      super()

      this._socket = socket
      this._upgrade = false
      this._headers = bag()
    }

    get socket() {
      return this._socket
    }

    get upgrade() {
      return this._upgrade
    }

    getHeader(name) {
      validateHeaderNameType(name)

      return get(this._headers, name.toLowerCase())
    }

    getHeaders() {
      return Object.assign(bag(), this._headers)
    }

    hasHeader(name) {
      validateHeaderNameType(name)

      return has(this._headers, name.toLowerCase())
    }

    setTimeout(ms, ontimeout) {
      if (ontimeout) this.once('timeout', ontimeout)

      if (this._socket !== null) this._socket.setTimeout(ms)

      return this
    }
  }
}
const { Writable, Duplex, isFinishing } = require('bare-stream')
const HTTPMessage = require('./message')
const errors = require('./errors')
const headers = require('./headers')
const validate = require('./validate')

const CHUNK_DELIMITER = Buffer.from('\r\n')
const CHUNK_TERMINATOR = Buffer.from('0\r\n\r\n')

module.exports = exports = HTTPOutgoingMessage(Writable)

exports.Duplex = HTTPOutgoingMessage(Duplex)

function HTTPOutgoingMessage(Stream) {
  return class HTTPOutgoingMessage extends HTTPMessage(Stream) {
    constructor(socket = null) {
      super(socket)

      this._headersSent = false

      // Whether the body is framed with chunked transfer encoding. Decided when
      // the headers are flushed, as a body that is written in one go can be
      // framed with a content length instead.
      this._chunked = false

      // Whether the framing has been decided, which happens once, before the
      // headers go out; a body with no framing at all runs into whatever
      // follows it on the connection.
      this._framed = false

      // The length the body was announced with, if any, and how much of it has
      // gone out. A body that does not match what the peer was told to read
      // runs over into the next message.
      this._length = -1
      this._written = 0

      // Whether a body is on its way, which is what tells a message that would
      // otherwise carry none apart from one that is only having its headers
      // sent ahead of time.
      this._writing = false

      this._error = null

      this._pendingWrite = null
    }

    get headersSent() {
      return this._headersSent
    }

    get headers() {
      return this.getHeaders()
    }

    set headers(value) {
      if (this._headersSent) throw errors.HEADERS_SENT()

      this._headers = headers.normalize(value)
    }

    setHeader(name, value) {
      if (this._headersSent) throw errors.HEADERS_SENT()

      validate.validateHeaderName(name)

      const n = name.toLowerCase()

      validate.validateHeaderValue(n, value)

      this._headers[n] = value
    }

    appendHeader(name, value) {
      if (this._headersSent) throw errors.HEADERS_SENT()

      validate.validateHeaderName(name)

      const n = name.toLowerCase()

      validate.validateHeaderValue(n, value)

      const existing = headers.get(this._headers, n)

      this._headers[n] = existing === undefined ? value : [].concat(existing, value)
    }

    flushHeaders() {
      if (this._headersSent === true || this._socket === null) return

      // A caller that sends the headers itself, ahead of writing anything, has
      // the body framed for it here.
      this._frameBody()

      this._socket.write(Buffer.from(this._header(), 'latin1'))
      this._headersSent = true
    }

    write(data, encoding, cb) {
      if (this._headersSent === false && this._socket !== null && this._error === null) {
        // The headers have to frame a body, however little is known about it
        // yet, as one is on its way.
        this._writing = true

        // A message that cannot be framed or serialized must not go out half
        // written, so the failure is held for the stream to report rather than
        // thrown from underneath the caller.
        try {
          this.flushHeaders()
        } catch (err) {
          this._error = err
        }
      }

      const flushed = super.write(data, encoding, cb)

      return this._error === null && flushed
    }

    // Takes over the framing headers the caller set for itself, so that exactly
    // one way of delimiting the body ever reaches the wire. Returns whether the
    // caller framed the message, in which case the library leaves it alone.
    _reframe() {
      const encoding = headers.get(this._headers, 'transfer-encoding')

      if (encoding !== undefined) {
        // Chunked is the only transfer coding implemented here, and `_fields`
        // announces it, so the caller's header is taken over rather than sent a
        // second time alongside it.
        if (headers.isToken(encoding, 'chunked') === false) {
          throw errors.INVALID_TRANSFER_ENCODING(
            `Unsupported transfer encoding: ${JSON.stringify(encoding)}`
          )
        }

        delete this._headers['transfer-encoding']

        // RFC 9112 gives Transfer-Encoding precedence over Content-Length, so a
        // peer that reads the other one reads a different message out of the
        // same bytes. Only ever one of them goes out.
        delete this._headers['content-length']

        this._chunked = true

        return true
      }

      const length = headers.single(headers.get(this._headers, 'content-length'))

      if (length !== undefined) {
        this._length = validate.validateContentLength(length)

        // Written back so that the peer reads the same number that the body is
        // held to, whichever way the caller happened to spell it.
        this._headers['content-length'] = this._length.toString()

        return true
      }

      return false
    }

    _frameBody(length = -1) {
      if (this._framed) return

      this._frame(length)

      // A length is only given when the whole body is in hand, so one that does
      // not match what the peer is being told to read is caught before any of
      // the message has gone out.
      if (
        length !== -1 &&
        this._length !== -1 &&
        length !== this._length &&
        this._discardBody() === false
      ) {
        throw this._mismatch(length)
      }

      this._framed = true
    }

    _frameBodyEmpty() {
      if (this._framed) return

      this._frameEmpty()

      // Nothing having been written is a body of no length, so an announced one
      // that asks the peer to read further is caught here rather than after the
      // headers promising it have gone out.
      if (this._length > 0 && this._discardBody() === false) {
        throw this._mismatch(0)
      }

      this._framed = true
    }

    _frame(length = -1) {
      if (this._reframe()) return

      if (length === -1) this._chunked = true
      else this._frameLength(length)
    }

    _frameLength(length) {
      this._headers['content-length'] = length.toString()
      this._length = length
    }

    // Called when the message ends with nothing having been written.
    _frameEmpty() {
      this._frame(0)
    }

    // Whether the body is held back from the socket. A message that may not
    // carry one still says how long the one it stands in for would have been.
    _discardBody() {
      return false
    }

    // Hook for whatever a message does once its body is on the wire.
    _end() {}

    _header() {
      throw errors.NOT_IMPLEMENTED()
    }

    // Serializes the header fields, which are validated again here so that
    // nothing reaches the wire unchecked.
    _fields() {
      let h = ''

      for (const name of Object.keys(this._headers)) {
        const value = this._headers[name]

        validate.validateHeaderName(name)
        validate.validateHeaderValue(name, value)

        h += headers.serialize(name, value)
      }

      if (this._chunked) h += 'Transfer-Encoding: chunked\r\n'

      return h
    }

    _mismatch(written) {
      return errors.CONTENT_LENGTH_MISMATCH(
        `Body is ${written} bytes but Content-Length is ${this._length}`
      )
    }

    _write(data, encoding, cb) {
      if (this._error !== null) return cb(this._error)

      if (this._socket === null) return cb(errors.CONNECTION_LOST('Message has no socket'))

      // Anything that is not bytes has no length to frame the message with, and
      // would otherwise be announced as a body of `NaN` bytes.
      if (ArrayBuffer.isView(data) === false) {
        return cb(errors.INVALID_BODY(`Body must be a string or a view, got ${typeof data}`))
      }

      if (this._headersSent === false) {
        try {
          this._frameBody(bodyLength(this, data))

          this.flushHeaders()
        } catch (err) {
          return cb(err)
        }
      }

      if (this._discardBody()) return cb(null)

      const written = this._written + data.byteLength

      // Anything past the announced length is read by the peer as the start of
      // the next message on the connection.
      if (this._length !== -1 && written > this._length) return cb(this._mismatch(written))

      this._written = written

      // A zero length chunk is what terminates a chunked body, so writing one
      // before the end would finish the message early and turn whatever came
      // after it into a message of its own.
      if (data.byteLength === 0) return cb(null)

      if (this._chunked) this._socket.write(Buffer.from(`${data.byteLength.toString(16)}\r\n`))

      let flushed = this._socket.write(data)

      // The delimiter is small enough to nearly always fit, so it must not be
      // what decides whether the caller is asked to wait.
      if (this._chunked) flushed = this._socket.write(CHUNK_DELIMITER) && flushed

      if (flushed) cb(null)
      else this._pendingWrite = cb
    }

    _final(cb) {
      if (this._error !== null) return cb(this._error)

      if (this._socket === null) return cb(errors.CONNECTION_LOST('Message has no socket'))

      if (this._headersSent === false) {
        try {
          this._frameBodyEmpty()

          this.flushHeaders()
        } catch (err) {
          return cb(err)
        }
      }

      if (this._discardBody() === false) {
        // A body that stops short of the announced length leaves the peer
        // waiting for the rest, and reading whatever follows as though it were.
        if (this._length !== -1 && this._written !== this._length) {
          return cb(this._mismatch(this._written))
        }

        if (this._chunked) this._socket.write(CHUNK_TERMINATOR)
      }

      this._end()

      cb(null)
    }

    _predestroy() {
      if (this._upgrade === false && this._socket !== null) this._socket.destroy()

      this._continueWrite()
    }

    _continueWrite() {
      if (this._pendingWrite === null) return

      const cb = this._pendingWrite

      this._pendingWrite = null

      cb(null)
    }
  }
}

// The length of the body, when the whole of it is in hand, or -1 when more may
// still be written.
function bodyLength(message, data) {
  if (isFinishing(message) === false) return -1

  return data.byteLength + message._writableState.buffered
}
const { isEnding, isFinished, isWritable } = require('bare-stream')
const HTTPParser = require('bare-http-parser')
const HTTPConnection = require('./connection')
const HTTPIncomingMessage = require('./incoming-message')
const HTTPServerResponse = require('./server-response')
const errors = require('./errors')
const { has, hasToken, isClosing, isUpgrading } = require('./headers')
const { destroySoon } = require('./socket')

const {
  constants: { REQUEST, RESPONSE, DATA, END }
} = HTTPParser

const BAD_REQUEST = Buffer.from(
  'HTTP/1.1 400 Bad Request\r\nConnection: close\r\nContent-Length: 0\r\n\r\n'
)

const REQUEST_TIMEOUT = Buffer.from(
  'HTTP/1.1 408 Request Timeout\r\nConnection: close\r\nContent-Length: 0\r\n\r\n'
)

const HEADERS_TOO_LARGE = Buffer.from(
  'HTTP/1.1 431 Request Header Fields Too Large\r\nConnection: close\r\nContent-Length: 0\r\n\r\n'
)

const PAYLOAD_TOO_LARGE = Buffer.from(
  'HTTP/1.1 413 Payload Too Large\r\nConnection: close\r\nContent-Length: 0\r\n\r\n'
)

const CONTINUE = Buffer.from('HTTP/1.1 100 Continue\r\n\r\n')

module.exports = class HTTPServerConnection extends HTTPConnection {
  static _connections = new WeakMap()

  constructor(server, socket, opts = {}) {
    const { IncomingMessage = HTTPIncomingMessage, ServerResponse = HTTPServerResponse } = opts

    super(socket, {
      maxHeaderSize: server.maxHeaderSize,
      maxHeadersCount: server.maxHeadersCount
    })

    this._server = server

    this._IncomingMessage = IncomingMessage
    this._ServerResponse = ServerResponse

    // An operation that has been held back until the connection is free again.
    this._op = null

    this._failed = false
    this._closed = false

    // Whether the connection is closing once the message in hand is answered,
    // whether the peer asked for it or the response gave it up.
    this._closing = false

    // Whether the request in hand has asked for the connection, which is handed
    // over once the whole of it has arrived.
    this._upgrading = false
    this._upgradeBody = 0

    // Whether the request has arrived in full.
    this._received = false

    // Whether the rest of the request body is being thrown away because it was
    // answered without being read.
    this._drained = false

    this._onresponseerror = this._onresponseerror.bind(this)

    // A peer that never finishes sending its request headers would otherwise
    // hold on to the connection for as long as it liked.
    this._headersDeadline = this._deadline(server.headersTimeout, () => this._headersTimeout())

    // And one that finishes the headers but dribbles out the body would hold on
    // to a request as well.
    this._requestDeadline = this._deadline(server.requestTimeout, () => this._requestTimeout())

    // Between requests nothing is being waited on but the next one, which is
    // worth far less patience than an unfinished request: the peer is not
    // mid-anything and is only holding resources here.
    this._keepAliveDeadline = this._deadline(server.keepAliveTimeout, () =>
      this._keepAliveTimeout()
    )

    this._attach()

    if (server.timeout) socket.setTimeout(server.timeout)

    this._headersDeadline.arm()
  }

  get server() {
    return this._server
  }

  get idle() {
    // A connection that has been handed over to another protocol is never idle
    // again.
    return this._idle && this._detached === false
  }

  // Whether the connection has stopped reading for good.
  get _stopped() {
    return this._failed || this._detached || this._closed
  }

  // A request that has arrived in full is left alone: the whole of it is in the
  // hands of its consumer, however much of it they have read, and cutting it
  // short here would throw away a body that `complete` says arrived.
  _abort(err = null) {
    const req = this._req

    if (req === null || isEnding(req) || req.destroying) return

    // Detached before the request is destroyed, as `_predestroy` would otherwise
    // take the socket down with it and throw away whatever response was queued
    // for the peer.
    req._detached = true

    req._abort(err)
  }

  // Gives up on the connection, so that nothing more is read off it and nothing
  // that was read is acted on.
  _stop() {
    this._failed = true

    this._op = null
    this._ops = null
    this._pending = []

    this._clearDeadlines()

    // Nothing more is going to be acted on, so whatever the peer is still
    // sending is left on the socket rather than held here for as long as the
    // socket takes to go away.
    this._pause()
  }

  // Gives up on the connection and takes it down once whatever was written has
  // gone out, so that anything the peer sent behind the last message it will be
  // answered is dropped rather than acted on.
  _hangUp() {
    this._stop()

    destroySoon(this._socket)
  }

  _fail(err, response) {
    this._stop()

    if (this._server.emit('clientError', err, this._socket) === false) {
      // A response that has already begun must not have this one spliced into
      // the middle of it: the peer would count the status line that follows
      // towards the body it was promised.
      if (isWritable(this._socket) && (this._res === null || this._res.headersSent === false)) {
        this._socket.write(response)
      }
    }

    this._abort(err)

    destroySoon(this._socket)
  }

  _continue() {
    while (this._stopped === false) {
      let op = this._op

      if (op !== null) {
        if (this._res !== null) return this._pause()

        this._op = null
      } else {
        try {
          op = this._pullOp()
        } catch (err) {
          return this._failParse(err)
        }

        if (op === null) break

        if (op.type === REQUEST) {
          // The headers are in, whether or not the request can be answered yet.
          this._headersDeadline.disarm()
          this._requestDeadline.arm()

          if (this._res !== null) {
            this._op = op

            return this._pause()
          }
        }
      }

      // Nothing pipelined behind the last message this connection will answer
      // can be answered either, and acting on it anyway is what lets a request
      // an intermediary stopped forwarding be served here.
      if (op.type === REQUEST && this._closing) return this._hangUp()

      this._dispatch(op)

      if (this._backpressure) return this._pause()
    }

    if (this._stopped) return

    this._resume()

    if (this._ended) this._endInput()
  }

  _resume() {
    // A request that is waiting on the response before it must not be read past.
    if (this._op !== null) return

    super._resume()
  }

  _endInput() {
    this._ended = false

    try {
      for (const op of this._parser.end()) this._dispatch(op)
    } catch (err) {
      this._failParse(err)
    }
  }

  _dispatch(op) {
    switch (op.type) {
      case REQUEST:
        return this._request(op)

      // The parser reads whichever kind of message the input looks like, and a
      // response is not one a server can answer. Left alone it would be
      // consumed in silence, declared body and all, so where this side thinks
      // the next message begins would rest on bytes no request could carry.
      case RESPONSE:
        return this._fail(
          errors.UNEXPECTED_RESPONSE('Response received in place of a request'),
          BAD_REQUEST
        )

      case DATA:
        // A push that does not fit is the signal to stop reading the socket
        // until the consumer has caught up. A body held for an upgrade has no
        // consumer until the message it belongs to is finished, so it is taken
        // whole, up to a limit, rather than stalling the handover.
        if (this._upgrading) {
          this._upgradeBody += op.data.byteLength

          if (this._refuseOversizedBody(this._upgradeBody)) return
        }

        if (this._req && this._req.push(op.data) === false && this._upgrading === false) {
          this._backpressure = true
        }
        break

      case END:
        // The request is complete, so an upgrade it asked for takes effect and
        // everything past it belongs to the protocol taking over.
        if (this._upgrading) return this._handover(this._parser.drain(), 'upgrade')

        if (this._req) {
          this._req._detached = true
          this._req._complete = true
          this._req.push(null)
        }

        this._requestDeadline.disarm()
        this._received = true

        this._checkIdle()
        break
    }
  }

  _request(op) {
    this._received = false
    this._drained = false

    const httpVersion = op.version === 'HTTP/1.0' ? '1.0' : '1.1'

    // A peer that says it is closing has nothing left to send once this message
    // is answered, so nothing behind it is read.
    if (isClosing(op.headers, httpVersion)) this._closing = true

    const req = (this._req = new this._IncomingMessage(this._socket, {
      headers: op.headers,
      httpVersion,
      method: op.method,
      url: op.url
    }))

    req._demand = this._demand

    req.on('close', () => {
      // Only the request the connection is currently on has any say in this.
      // One that is closed late, after the next request has been handed the
      // connection, would otherwise leave it without the rest of its body.
      if (this._req !== req) return

      this._req = null

      this._checkIdle()
    })

    // Eagerly open the request stream
    req.resume()
    req.pause()

    // A tunnel is asked for by the method itself, and everything past the
    // request line belongs to it rather than to HTTP.
    if (op.method === 'CONNECT') return this._handover(this._parser.drain(), 'connect')

    // An upgrade only takes effect once the request is complete, so the body
    // belongs to the request and only what follows it to the new protocol. One
    // that nobody is there to take is answered as an ordinary request, as
    // Node.js does.
    if (isUpgrading(op.headers) && this._server.listenerCount('upgrade') > 0) {
      const expect = req.httpVersion === '1.0' ? undefined : op.headers.expect

      if (expect === undefined || hasToken(expect, '100-continue')) {
        const announced = op.headers['content-length']

        // The handover waits for the whole request, and a client that announced
        // an expectation holds its body back until it is answered, so the two
        // ends would be left waiting on each other. Only a request that
        // announced a body has one being held back; a 100 for any other would
        // only get in the way of the response that hands the connection over.
        if (
          expect !== undefined &&
          (announced !== undefined || has(op.headers, 'transfer-encoding'))
        ) {
          this._socket.write(CONTINUE)
        }

        this._upgrading = true
        this._upgradeBody = 0

        if (announced !== undefined) this._refuseOversizedBody(Number(announced))

        return
      }

      // An expectation this side cannot meet is left to the ordinary path,
      // which refuses it rather than hand the connection over to a protocol
      // whose first bytes are a body that is never going to arrive.
    }

    const res = (this._res = new this._ServerResponse(this._socket, req, {
      keepAliveTimeout: this._server.keepAliveTimeout
    }))

    res.on('error', this._onresponseerror)

    res.on('close', () => {
      // Only the response the connection is currently on has any say in this,
      // as the request it answered has for its own close.
      if (this._res !== res) return

      this._res = null

      // The response may have given the connection up even where the request
      // did not ask for it.
      if (res._close) this._closing = true

      this._drain()

      this._checkIdle()

      // The connection is free again, so any request that was held back can now
      // be answered.
      this._continue()
    })

    // A client that announced an expectation may be holding its body back until
    // it has been answered. An HTTP/1.0 client is left out, as RFC 9110 gives it
    // no way to understand a 1xx and it would read the 100 as the response
    // itself.
    if (op.headers.expect !== undefined && req.httpVersion !== '1.0') {
      return this._expect(op.headers.expect, req, res)
    }

    this._server.emit('request', req, res)
  }

  _expect(expect, req, res) {
    if (hasToken(expect, '100-continue') === false) {
      if (this._server.listenerCount('checkExpectation') > 0) {
        return this._server.emit('checkExpectation', req, res)
      }

      // Nothing here understands the expectation, and RFC 9110 gives the peer no
      // way to go on without one, so the request is refused.
      res.writeHead(417)
      res.end()

      return
    }

    if (this._server.listenerCount('checkContinue') > 0) {
      return this._server.emit('checkContinue', req, res)
    }

    res.writeContinue()

    this._server.emit('request', req, res)
  }

  _checkIdle() {
    if (this._stopped) return

    // A request whose body has arrived in full is no longer holding on to the
    // connection, however long its consumer takes to read what it was given.
    if (this._res !== null || (this._req !== null && this._received === false)) return

    // A request that is only waiting for the connection to free up is already
    // in hand and about to be answered.
    if (this._op !== null) return

    this._idle = true

    if (this._server.closing) return destroySoon(this._socket)

    // Only started once, as going idle a second time for the same request, when
    // its consumer catches up, must not buy the peer any more time.
    if (this._keepAliveDeadline.armed === false) this._keepAliveDeadline.arm()
  }

  _refuseOversizedBody(length) {
    const max = this._server.maxUpgradeBodySize

    if (max === 0 || length <= max) return false

    this._fail(
      errors.BODY_TOO_LARGE(`Upgrade body exceeds limit of ${max} bytes`),
      PAYLOAD_TOO_LARGE
    )

    return true
  }

  // Throws away the rest of a request body that nobody is reading, as it still
  // has to come off the socket before the next request can be read.
  _drain() {
    const req = this._req

    if (req === null || this._received || this._drained) return

    if (req.listenerCount('data') > 0 || req.listenerCount('readable') > 0) return

    this._drained = true

    req.resume()
  }

  _failParse(err) {
    this._fail(err, err.code === 'HEADER_OVERFLOW' ? HEADERS_TOO_LARGE : BAD_REQUEST)
  }

  // Only ever armed while a request is on its way: from the moment the
  // connection opens until the first one arrives, and from the first byte of
  // every request after that. A connection that is merely being held between
  // requests is on the keep-alive deadline instead, so there is nothing here but
  // a request the peer never finished sending.
  _headersTimeout() {
    this._fail(errors.REQUEST_TIMEOUT('Timed out waiting for the request headers'), REQUEST_TIMEOUT)
  }

  _requestTimeout() {
    this._fail(errors.REQUEST_TIMEOUT('Timed out waiting for the request body'), REQUEST_TIMEOUT)
  }

  // The connection is only being held for another request, so there is nothing
  // to finish: it is given up on outright rather than merely taken down, as a
  // request that had already arrived would otherwise still be acted on, over a
  // socket that is on its way out.
  _keepAliveTimeout() {
    this._hangUp()
  }

  _handover(data, event) {
    const req = this._req
    const head = this._takeover(data)

    req._upgrade = true
    req._complete = true

    const handled = this._server.emit(event, req, this._socket, head)

    req.push(null)

    if (!handled) this._socket.destroy()
  }

  _onerror(err) {
    this._server.emit('clientError', err, this._socket)
  }

  _onclose() {
    this._closed = true

    this._clearDeadlines()

    this._abort(errors.CONNECTION_LOST())

    if (this._res && !isFinished(this._res)) this._res.destroy()
  }

  _ondata(data) {
    // The peer has started on another request, so it is the headers that are on
    // the clock now rather than the connection sitting idle.
    if (this._keepAliveDeadline.armed) {
      this._keepAliveDeadline.disarm()

      if (this._headersDeadline.armed === false) this._headersDeadline.arm()
    }

    super._ondata(data)
  }

  _ondrain() {
    if (this._res) this._res._continueWrite()
  }

  _ontimeout() {
    const reqTimeout = this._req && this._req.emit('timeout')
    const resTimeout = this._res && this._res.emit('timeout')
    const serverTimeout = this._server.emit('timeout', this._socket)

    if (!reqTimeout && !resTimeout && !serverTimeout) this._socket.destroy()
  }

  _onresponseerror(err) {
    this._stop()

    this._server.emit('clientError', err, this._socket)

    this._abort(err)

    // Whatever the peer was promised cannot be finished now, so there is nothing
    // to be gained by letting the rest of the response go out.
    this._socket.destroy()
  }
}
const HTTPOutgoingMessage = require('./outgoing-message')
const constants = require('./constants')
const errors = require('./errors')
const { has, hasToken, isClosing, single } = require('./headers')
const { destroySoon } = require('./socket')
const validate = require('./validate')

const CONTINUE = Buffer.from('HTTP/1.1 100 Continue\r\n\r\n')

module.exports = class HTTPServerResponse extends HTTPOutgoingMessage {
  constructor(socket, req, opts = {}) {
    const { keepAliveTimeout = 0 } = opts

    super(socket)

    this._req = req

    this._statusCode = 200
    this._statusMessage = null

    // HTTP/1.0 has neither chunked transfer encoding nor persistent connections
    // by default, so a body of unknown length can only be delimited by closing
    // the connection.
    this._chunkable = req.httpVersion !== '1.0'

    this._close = isClosing(req.headers, req.httpVersion)

    // How long a connection this side keeps is worth holding on to, which the
    // peer is told so that it does not send another request into one that is
    // about to be reclaimed.
    this._keepAliveTimeout = keepAliveTimeout

    this._onlyHeaders = req.method === 'HEAD'

    // A client that asked whether to send its body, and was never told, cannot
    // be assumed to have sent it.
    this._expectContinue = this._chunkable && hasToken(req.headers.expect, '100-continue')
    this._sent100 = false
  }

  get req() {
    return this._req
  }

  get statusCode() {
    return this._statusCode
  }

  set statusCode(value) {
    validate.validateStatusCode(value)

    this._statusCode = value
  }

  get statusMessage() {
    return this._statusMessage
  }

  set statusMessage(value) {
    this._statusMessage = validate.validateStatusMessage(value)
  }

  writeHead(statusCode, statusMessage = null, headers = {}) {
    if (this._headersSent) throw errors.HEADERS_SENT()

    if (typeof statusMessage === 'object' && statusMessage !== null) {
      headers = statusMessage
      statusMessage = null
    }

    validate.validateStatusCode(statusCode)

    this._statusCode = statusCode
    this._statusMessage = statusMessage ? validate.validateStatusMessage(statusMessage) : null

    // Merged through the setter so that the names are lowercased. The fields
    // may be given as a bag, as a flat list of alternating names and values, or
    // as a list of pairs, all three of which Node.js accepts. An array is an
    // object, so the list forms have to be told apart from the bag first.
    if (Array.isArray(headers)) {
      if (headers.length > 0 && Array.isArray(headers[0])) {
        // Only this form can name the same field twice, as `Set-Cookie` needs.
        for (const [name, value] of headers) this.appendHeader(name, value)
      } else {
        if (headers.length % 2 !== 0) {
          throw errors.INVALID_HEADER_VALUE('Header list must hold a value for every name')
        }

        for (let i = 0; i < headers.length; i += 2) this.setHeader(headers[i], headers[i + 1])
      }
    } else if (headers) {
      for (const name of Object.keys(headers)) this.setHeader(name, headers[name])
    }

    return this
  }

  writeContinue() {
    if (this._headersSent) throw errors.HEADERS_SENT()

    if (this._socket === null) return

    this._socket.write(CONTINUE)

    this._sent100 = true
  }

  _bodyless() {
    return this._statusCode < 200 || this._statusCode === 204 || this._statusCode === 304
  }

  _discardBody() {
    return this._onlyHeaders || this._bodyless()
  }

  _frame(length = -1) {
    // A message that may not carry a body must not announce one either, and
    // none is written for it, so there is nothing left to delimit. A 304 stands
    // in for a real response, so a length set for it describes the body it is
    // replacing and is left as it is.
    if (this._bodyless()) {
      this._chunked = false

      delete this._headers['transfer-encoding']

      if (this._statusCode !== 304) delete this._headers['content-length']
      else if (has(this._headers, 'content-length')) {
        // Never framed against a body, but the peer still reads it, so it has to
        // be a count of bytes and nothing else.
        this._headers['content-length'] = validate
          .validateContentLength(single(this._headers['content-length']))
          .toString()
      }

      return
    }

    if (this._reframe()) return

    if (length !== -1) return this._frameLength(length)

    // A response to HEAD never carries a body, so there is nothing to delimit:
    // announcing chunked would promise a terminator that is never written, and
    // closing would give up a connection that is still perfectly good.
    if (this._onlyHeaders) return

    // Without chunked encoding the only way to delimit a body of unknown length
    // is to close the connection once it has been written.
    if (this._chunkable) this._chunked = true
    else this._close = true
  }

  // A zero length is what nothing having been written means, except for HEAD,
  // where it would describe the resource rather than the response.
  _frameEmpty() {
    this._frame(this._onlyHeaders ? -1 : 0)
  }

  _header() {
    validate.validateStatusCode(this._statusCode)

    let statusMessage = validate.validateStatusMessage(this._statusMessage)

    if (statusMessage === null) {
      statusMessage = constants.status[this._statusCode] || 'unknown'
    }

    // Whether the caller gave the connection up for itself, in which case its
    // field is left as it is, however many other tokens it names.
    const announcedClose = hasToken(this._headers.connection, 'close')

    if (announcedClose) this._close = true

    if (this._expectContinue && this._sent100 === false) this._close = true

    // A connection this side is giving up has to say so, whatever the caller
    // named: a peer that is told the connection is kept sends its next request
    // into one that is already gone, and a body delimited by the close would
    // run past where such a peer stops reading.
    if (this._close && announcedClose === false) delete this._headers['connection']

    let h = `HTTP/1.1 ${this._statusCode} ${statusMessage}\r\n` + this._fields()

    // The peer has no other way of knowing where an unframed body ends, or that
    // the connection is not going to be reused. An HTTP/1.0 peer closes the
    // connection unless it is told that this side is keeping it, so asking for
    // one is only half of the agreement.
    if (has(this._headers, 'connection') === false) {
      if (this._close) h += 'Connection: close\r\n'
      else {
        h += 'Connection: keep-alive\r\n'

        // Only ever rounded down, as a peer that is told more time than it has
        // sends its next request into a connection that is already gone. That
        // leaves nothing to say of a wait under a second, and saying `timeout=0`
        // would read as an instruction not to reuse the connection at all.
        const timeout = Math.floor(this._keepAliveTimeout / 1000)

        if (timeout > 0) h += `Keep-Alive: timeout=${timeout}\r\n`
      }
    }

    if (has(this._headers, 'date') === false) {
      h += `Date: ${new Date().toUTCString()}\r\n`
    }

    return h + '\r\n'
  }

  _end() {
    if (this._close) destroySoon(this._socket)
  }

  _predestroy() {
    super._predestroy()

    this._req.destroy()
  }
}
const TCPServer = require('bare-tcp').Server
const HTTPServerConnection = require('./server-connection')

// A peer only needs a moment to send a request line and its headers.
const DEFAULT_HEADERS_TIMEOUT = 60000

// A body may legitimately take a while to arrive, so this is only a backstop
// against one that never finishes.
const DEFAULT_REQUEST_TIMEOUT = 300000

// A connection with nothing on it is kept only long enough to be worth reusing.
const DEFAULT_KEEP_ALIVE_TIMEOUT = 5000

// The request line and the headers have to be held whole before the request can
// be acted on, so what a peer may send before it says anything useful is capped.
// Zero disables either of them.
const DEFAULT_MAX_HEADER_SIZE = 16384
const DEFAULT_MAX_HEADERS_COUNT = 2000

// An upgrade body is held whole until the handover, with nothing reading it in
// the meantime to push back on it, so how much may be held is capped as well.
const DEFAULT_MAX_UPGRADE_BODY_SIZE = 65536

module.exports = class HTTPServer extends TCPServer {
  constructor(opts = {}, onrequest) {
    if (typeof opts === 'function') {
      onrequest = opts
      opts = {}
    } else if (opts === null) {
      opts = {}
    }

    const {
      readBufferSize,
      keepAlive,
      keepAliveInitialDelay,
      noDelay,
      maxConnections,
      headersTimeout = DEFAULT_HEADERS_TIMEOUT,
      requestTimeout = DEFAULT_REQUEST_TIMEOUT,
      keepAliveTimeout = DEFAULT_KEEP_ALIVE_TIMEOUT,
      maxHeaderSize = DEFAULT_MAX_HEADER_SIZE,
      maxHeadersCount = DEFAULT_MAX_HEADERS_COUNT,
      maxUpgradeBodySize = DEFAULT_MAX_UPGRADE_BODY_SIZE
    } = opts

    // Half open connections are not forwarded, as a client that has stopped
    // writing has nothing left to ask for.
    super({
      readBufferSize,
      keepAlive,
      keepAliveInitialDelay,
      noDelay,
      maxConnections,
      allowHalfOpen: false
    })

    this._timeout = 0

    // A request that is never finished would otherwise hold on to a connection
    // for as long as the peer cared to keep it. None of these count time spent
    // waiting on this side, and zero disables them.
    this._headersTimeout = headersTimeout
    this._requestTimeout = requestTimeout
    this._keepAliveTimeout = keepAliveTimeout

    this._maxHeaderSize = maxHeaderSize
    this._maxHeadersCount = maxHeadersCount
    this._maxUpgradeBodySize = maxUpgradeBodySize

    this.on('connection', (socket) => {
      new HTTPServerConnection(this, socket, opts)
    })

    if (onrequest) this.on('request', onrequest)
  }

  get timeout() {
    return this._timeout
  }

  get headersTimeout() {
    return this._headersTimeout
  }

  set headersTimeout(value) {
    this._headersTimeout = value
  }

  get requestTimeout() {
    return this._requestTimeout
  }

  set requestTimeout(value) {
    this._requestTimeout = value
  }

  get keepAliveTimeout() {
    return this._keepAliveTimeout
  }

  set keepAliveTimeout(value) {
    this._keepAliveTimeout = value
  }

  get maxHeaderSize() {
    return this._maxHeaderSize
  }

  set maxHeaderSize(value) {
    this._maxHeaderSize = value
  }

  get maxHeadersCount() {
    return this._maxHeadersCount
  }

  set maxHeadersCount(value) {
    this._maxHeadersCount = value
  }

  get maxUpgradeBodySize() {
    return this._maxUpgradeBodySize
  }

  set maxUpgradeBodySize(value) {
    this._maxUpgradeBodySize = value
  }

  setTimeout(ms = 0, ontimeout) {
    if (ontimeout) this.on('timeout', ontimeout)

    this._timeout = ms

    return this
  }

  close(onclose) {
    super.close(onclose)

    this.closeIdleConnections()

    return this
  }

  closeIdleConnections() {
    for (const socket of this.connections) {
      const connection = HTTPServerConnection.for(socket)

      if (connection === null || connection.idle) socket.destroy()
    }
  }

  closeAllConnections() {
    for (const socket of this.connections) socket.destroy()
  }
}
const { isFinished, isWritable } = require('bare-stream')

// Closes the write side and then takes the socket down once the last of it has
// gone out.
exports.destroySoon = function destroySoon(socket) {
  if (socket.destroying) return

  if (isWritable(socket)) socket.end()

  if (isFinished(socket)) socket.destroy()
  else socket.once('finish', () => socket.destroy())
}
const errors = require('./errors')

// RFC 9110 token = 1*tchar
const TOKEN_RE = /^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/

// A bare CR or LF would terminate or split the current line outright, and the
// remaining control characters are the ones intermediaries disagree about, so
// they are refused for the same reason Node.js refuses them: whatever this side
// meant by them, the next hop may read the message differently.
const VALUE_INVALID_RE = /[^\t\x20-\x7e\x80-\xff]/

// The request-target sits on the request line, so any whitespace would split
// it, and anything outside Latin-1 has to be percent encoded before it can be
// sent. Matches what Node.js accepts, less the one whitespace character that
// falls inside the range.
const PATH_INVALID_RE = /[^\u0021-\u00ff]|\s/

// A valid token that would reach the `Object.prototype` setter of the same name
// if it were ever used as a property. The header bags are created without a
// prototype, but the name is refused outright so that it cannot be reintroduced
// by a caller that builds a bag of its own. `bare-http-parser` refuses it on the
// way in for the same reason.
const UNSAFE_NAMES = new Set(['__proto__'])

// RFC 9110 Content-Length = 1*DIGIT, so no sign, no whitespace, and nothing
// that `parseInt` would otherwise accept the leading digits of.
const DIGITS_RE = /^[0-9]+$/

exports.validateHeaderNameType = function (name) {
  if (typeof name !== 'string') {
    throw errors.INVALID_HEADER_NAME(`Invalid header name: ${JSON.stringify(name)}`)
  }
}

exports.validateHeaderName = function (name) {
  if (typeof name !== 'string' || !TOKEN_RE.test(name)) {
    throw errors.INVALID_HEADER_NAME(`Invalid header name: ${JSON.stringify(name)}`)
  }

  if (UNSAFE_NAMES.has(name.toLowerCase())) {
    throw errors.INVALID_HEADER_NAME(`Unsafe header name: ${JSON.stringify(name)}`)
  }
}

exports.validateHeaderValue = function (name, value) {
  if (value === undefined) {
    throw errors.INVALID_HEADER_VALUE(`Missing header value for ${JSON.stringify(name)}`)
  }

  if (value === null) return

  // A field whose value is a list may be given as an array, in which case every
  // element goes on the wire and so every element has to be checked.
  if (Array.isArray(value)) {
    for (const element of value) exports.validateHeaderValue(name, element)
    return
  }

  exports.validateHeaderFieldValue(name, String(value))
}

exports.validateHeaderFieldValue = function (name, value) {
  if (VALUE_INVALID_RE.test(value)) {
    throw errors.INVALID_HEADER_VALUE(
      `Invalid character in header value for ${JSON.stringify(name)}`
    )
  }
}

exports.validateStatusCode = function (value) {
  if (!Number.isInteger(value) || value < 100 || value > 999) {
    throw errors.INVALID_STATUS_CODE(`Invalid status code: ${JSON.stringify(value)}`)
  }
}

exports.validateStatusMessage = function (value) {
  if (value === undefined || value === null) return null

  const message = String(value)

  if (VALUE_INVALID_RE.test(message)) {
    throw errors.INVALID_HEADER_VALUE('Invalid character in status message')
  }

  return message
}

exports.validatePath = function (value) {
  if (typeof value !== 'string' || value.length === 0 || PATH_INVALID_RE.test(value)) {
    throw errors.INVALID_HEADER_VALUE(`Invalid character in request path: ${JSON.stringify(value)}`)
  }
}

exports.validateHost = function (value) {
  if (typeof value !== 'string') {
    throw errors.INVALID_HEADER_VALUE(`Invalid host: ${JSON.stringify(value)}`)
  }
}

exports.validateMethod = function (value) {
  if (typeof value !== 'string' || !TOKEN_RE.test(value)) {
    throw errors.INVALID_HEADER_NAME(`Invalid HTTP method: ${JSON.stringify(value)}`)
  }
}

exports.validateContentLength = function (value) {
  if (typeof value === 'number') {
    if (!Number.isSafeInteger(value) || value < 0) {
      throw errors.INVALID_CONTENT_LENGTH(`Invalid content length: ${JSON.stringify(value)}`)
    }

    return value
  }

  if (typeof value !== 'string' || !DIGITS_RE.test(value)) {
    throw errors.INVALID_CONTENT_LENGTH(`Invalid content length: ${JSON.stringify(value)}`)
  }

  const length = Number(value)

  if (!Number.isSafeInteger(length)) {
    throw errors.INVALID_CONTENT_LENGTH(`Invalid content length: ${JSON.stringify(value)}`)
  }

  return length
}
{
  "name": "bare-http1",
  "version": "4.6.1",
  "description": "Native HTTP/1 library for JavaScript",
  "exports": {
    "./package": "./package.json",
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    },
    "./constants": {
      "types": "./lib/constants.d.ts",
      "default": "./lib/constants.js"
    },
    "./errors": {
      "types": "./lib/errors.d.ts",
      "default": "./lib/errors.js"
    }
  },
  "files": [
    "index.js",
    "index.d.ts",
    "lib"
  ],
  "scripts": {
    "format": "prettier --write . && lunte --fix",
    "lint": "prettier --check . && lunte",
    "test": "brittle-bare --coverage test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-http1.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-http1/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-http1#readme",
  "dependencies": {
    "bare-events": "^2.6.0",
    "bare-hrtime": "^2.1.1",
    "bare-http-parser": "^2.1.0",
    "bare-stream": "^2.10.0",
    "bare-tcp": "^2.2.0"
  },
  "devDependencies": {
    "bare-buffer": "^3.0.2",
    "bare-url": "^2.1.3",
    "brittle": "^3.3.0",
    "lunte": "^1.6.0",
    "prettier": "^3.4.1",
    "prettier-config-holepunch": "^2.0.0"
  },
  "peerDependencies": {
    "bare-buffer": "*",
    "bare-url": "*"
  },
  "peerDependenciesMeta": {
    "bare-buffer": {
      "optional": true
    },
    "bare-url": {
      "optional": true
    }
  }
}
module.exports = require.addon()
const ansiEscapes = require('bare-ansi-escapes')
const getType = require('bare-type')
const binding = require('./binding')

const defaultDepth = 2
const defaultBreakLength = 80
const defaultMaxArrayLength = 40

module.exports = exports = function inspect(value, opts = {}) {
  const {
    colors = false,
    depth = defaultDepth,
    breakLength = defaultBreakLength,
    stylize = defaultStylize(colors)
  } = opts

  const references = new InspectRefMap()

  const tree = inspectValue(value, 0, {
    colors,
    depth,
    breakLength,
    stylize,
    references
  })

  return tree.toString()
}

exports.styles = {
  bigint: ansiEscapes.colorYellow,
  boolean: ansiEscapes.colorYellow,
  date: ansiEscapes.colorMagenta,
  module: ansiEscapes.modifierUnderline,
  name: ansiEscapes.modifierReset,
  null: ansiEscapes.modifierBold,
  number: ansiEscapes.colorYellow,
  regexp: ansiEscapes.colorRed,
  special: ansiEscapes.colorCyan,
  string: ansiEscapes.colorGreen,
  symbol: ansiEscapes.colorGreen,
  undefined: ansiEscapes.colorBrightBlack
}

const styles = exports.styles

function defaultStylize(colors) {
  return function stylize(value, style) {
    const color = colors && styles[style]

    if (color) return color + value + ansiEscapes.modifierReset

    return value
  }
}

class InspectRefMap {
  constructor() {
    this.refs = new WeakMap()
    this.ids = new WeakMap()
    this.nextId = 1
  }

  has(object) {
    return this.refs.has(object)
  }

  get(object) {
    return this.refs.get(object) || null
  }

  set(object, ref) {
    this.refs.set(object, ref)
  }

  id(object) {
    let id = this.ids.get(object)
    if (id) return id

    id = this.nextId++
    this.ids.set(object, id)

    return id
  }
}

class InspectNode {
  constructor(depth, length, opts) {
    const { breakLength = defaultBreakLength, breakAlways = false } = opts

    this.depth = depth
    this.length = length
    this.breakLength = breakLength
    this.breakAlways = breakAlways
  }

  pad(n, string) {
    return string.padStart(n, ' ')
  }

  indent(n, string) {
    return '  '.repeat(n) + string
  }
}

class InspectRef extends InspectNode {
  constructor(depth, opts) {
    super(depth, '[circular *]'.length, opts)

    this.refs = opts.references
    this.count = 0
    this.circular = false
    this.color = opts.colors && styles.special
  }

  get id() {
    return this.refs.id(this)
  }

  increment() {
    return ++this.count
  }

  decrement() {
    return --this.count
  }

  toString(opts = {}) {
    const { offset = 0, pad = 0, indent = 0 } = opts

    let value = this.pad(pad, '[circular *' + this.id + ']')

    if (this.color) value = this.color + value + ansiEscapes.modifierReset

    return offset ? value : this.indent(indent, value)
  }
}

class InspectLeaf extends InspectNode {
  constructor(value, color, depth, opts) {
    const length = value.length

    if (value.includes('\n')) {
      value = value.replaceAll('\n', '\n' + '  '.repeat(depth))

      opts = { ...opts, breakAlways: true }
    }

    super(depth, length, opts)

    this.value = value
    this.color = opts.colors && color
  }

  toString(opts = {}) {
    const { offset = 0, pad = 0, indent = 0 } = opts

    let value = this.pad(pad, this.value)

    if (this.color) value = this.color + value + ansiEscapes.modifierReset

    return offset ? value : this.indent(indent, value)
  }
}

class InspectPair extends InspectNode {
  constructor(delim, left, right, depth, opts) {
    const length = left.length + delim.length + right.length

    if (left.breakAlways || right.breakAlways) {
      opts = { ...opts, breakAlways: true }
    }

    super(depth, length, opts)

    this.delim = delim
    this.left = left
    this.right = right
  }

  toString(opts = {}) {
    const { indent = 0 } = opts

    return this.indent(
      indent,
      this.left +
        this.delim +
        this.right.toString({
          indent,
          offset: this.left.length + this.delim.length
        })
    )
  }
}

class InspectSuspension extends InspectNode {
  constructor(overflow, depth, opts) {
    const label = `... ${overflow} more`

    super(depth, label.length, opts)

    this.label = label
  }

  toString(opts = {}) {
    const { indent = 0 } = opts

    return this.indent(indent, this.label)
  }
}

class InspectSequence extends InspectNode {
  constructor(header, footer, delim, values, ref, depth, opts) {
    const { tabulate = false } = opts

    const length =
      (ref.circular ? '<ref *>'.length + 1 : 0) +
      header.length +
      values.reduce((length, value, i) => length + value.length + (i === 0 ? 0 : delim.length), 0) +
      footer.length

    if (values.some((value) => value.breakAlways)) {
      opts = { ...opts, breakAlways: true }
    }

    super(depth, length, opts)

    this.header = header
    this.footer = footer
    this.delim = delim
    this.values = values
    this.ref = ref
    this.tabulate = tabulate
  }

  toString(opts = {}) {
    const { offset = 0, indent = 0 } = opts

    const split =
      this.breakAlways ||
      (this.values.length &&
        (offset + this.length > this.breakLength || indent * 2 + this.length > this.breakLength))

    let header = this.header

    if (this.ref.circular) {
      header = '<ref *' + this.ref.id + '> ' + header
    }

    if (this.values.length === 0) {
      header = header.trimEnd()
    }

    if (offset === 0) {
      header = this.indent(indent, header)
    }

    if (split) {
      header = header.trimEnd() + '\n'
    }

    let string = header

    let columns = 1
    let pad = 0

    if (this.tabulate) {
      const widest = this.values.reduce(
        (length, value) => (value.breakAlways ? length : Math.max(length, value.length)),
        0
      )

      if (widest) {
        columns = Math.max(
          columns,
          Math.floor((this.breakLength - indent * 2) / (widest + this.delim.length))
        )

        if (columns > 1) pad = widest
      }
    }

    for (let i = 0, n = this.values.length, offset = 0; i < n; i++) {
      const value = this.values[i]

      if (split) {
        let part

        if (i % columns === 0 || value.breakAlways) {
          part = value.toString({ indent: indent + 1, pad })
        } else {
          part = value.toString({ pad })
        }

        string += part

        if (i < n - 1) {
          if (i % columns === columns - 1 || this.values[i + 1].breakAlways) {
            string += this.delim.trimEnd() + '\n'
          } else {
            string += this.delim
          }
        }
      } else {
        if (i > 0) string += this.delim

        string += value.toString({ offset })

        // TODO Lunte thinks this refers to `const { offset }...` further up
        offset += value.length // eslint-disable-line
      }
    }

    let footer = this.footer

    if (this.values.length === 0) {
      footer = footer.trimStart()
    }

    if (split) {
      string += '\n' + this.indent(indent, footer.trimStart())
    } else {
      string += footer
    }

    return string
  }
}

function inspectValue(value, depth, opts) {
  const type = getType(value)

  if (type.isUndefined()) return inspectUndefined(depth, opts)
  if (type.isNull()) return inspectNull(depth, opts)
  if (type.isBoolean()) return inspectBoolean(value, depth, opts)
  if (type.isNumber()) return inspectNumber(value, depth, opts)
  if (type.isBigInt()) return inspectBigInt(value, depth, opts)
  if (type.isString()) return inspectString(value, depth, opts)
  if (type.isSymbol()) return inspectSymbol(value, depth, opts)
  if (type.isObject()) return inspectObject(type, value, depth, opts)
  if (type.isFunction()) return inspectFunction(type, value, depth, opts)
  if (type.isExternal()) return inspectExternal(value, depth, opts)
}

function inspectUndefined(depth, opts) {
  return new InspectLeaf('undefined', styles.undefined, depth, opts)
}

function inspectNull(depth, opts) {
  return new InspectLeaf('null', styles.null, depth, opts)
}

function inspectBoolean(value, depth, opts) {
  return new InspectLeaf(value.toString(), styles.boolean, depth, opts)
}

function inspectNumber(value, depth, opts) {
  let string

  if (Object.is(value, -0)) {
    string = '-0'
  } else {
    string = value.toString(10)
  }

  return new InspectLeaf(string, styles.number, depth, opts)
}

function inspectBigInt(value, depth, opts) {
  return new InspectLeaf(value.toString(10) + 'n', styles.bigint, depth, opts)
}

const STRING_ESCAPES = /[\ud800-\udbff][\udc00-\udfff]|[\u0000-\u001f'\\\ud800-\udfff]/g

function inspectString(value, depth, opts) {
  // https://tc39.es/ecma262/multipage/structured-data.html#sec-quotejsonstring
  const string = value.replace(STRING_ESCAPES, (match) => {
    if (match.length === 2) return match

    switch (match) {
      case "'":
        return "\\'"
      case '\\':
        return '\\\\'
      case '\b':
        return '\\b'
      case '\t':
        return '\\t'
      case '\n':
        return '\\n'
      case '\f':
        return '\\f'
      case '\r':
        return '\\r'
      default:
        return '\\u' + match.charCodeAt(0).toString(16).padStart(4, '0')
    }
  })

  return new InspectLeaf("'" + string + "'", styles.string, depth, opts)
}

function inspectSymbol(value, depth, opts) {
  return new InspectLeaf(value.toString(), styles.symbol, depth, opts)
}

const PLAIN_KEY = /^[a-zA-Z_][a-zA-Z_0-9]*$/

function inspectKey(value, depth, opts) {
  if (typeof value === 'symbol') {
    return inspectValue(value, depth, opts)
  } else if (PLAIN_KEY.test(value)) {
    return new InspectLeaf(value, null, depth, opts)
  } else {
    return inspectValue(value, depth, opts)
  }
}

function inspectObject(type, object, depth, opts) {
  const refs = opts.references

  let ref = refs.get(object)
  if (ref === null) {
    ref = new InspectRef(depth, opts)
    refs.set(object, ref)
  } else if (ref.count) {
    ref.circular = true
    return ref
  }

  const maxDepth = typeof opts.depth === 'number' ? opts.depth : Infinity

  if (maxDepth < depth) {
    const name = nameOf(object)

    return new InspectLeaf('[' + (name || 'Object') + ']', styles.special, depth, opts)
  }

  const { value: inspect } = attempt(
    () => object[Symbol.for('bare.inspect')] || object[Symbol.for('nodejs.util.inspect.custom')]
  )

  if (typeof inspect === 'function' && isPrototype(object) === false) {
    const { value, threw, error } = attempt(() =>
      inspect.call(
        object,
        typeof opts.depth === 'number' ? opts.depth - depth : null,
        {
          colors: opts.colors,
          breakLength: opts.breakLength,
          stylize: opts.stylize
        },
        exports
      )
    )

    if (threw) {
      return new InspectLeaf(
        '<inspect threw ' + errorLabel(error) + '>',
        styles.special,
        depth,
        opts
      )
    }

    if (typeof value === 'object' && value !== null) {
      refs.set(value, ref)
    }

    if (typeof value !== 'string') {
      return inspectValue(value, depth, opts)
    }

    return new InspectLeaf(value, null, depth, opts)
  }

  if (type.isArray()) return inspectArray(object, ref, depth, opts)
  if (type.isDate()) return inspectDate(object, ref, depth, opts)
  if (type.isRegExp()) return inspectRegExp(object, ref, depth, opts)
  if (type.isError()) return inspectError(object, ref, depth, opts)
  if (type.isPromise()) return inspectPromise(object, ref, depth, opts)
  if (type.isMap()) return inspectMap(object, ref, depth, opts)
  if (type.isSet()) return inspectSet(object, ref, depth, opts)
  if (type.isWeakMap()) return inspectWeakMap(object, ref, depth, opts)
  if (type.isWeakSet()) return inspectWeakSet(object, ref, depth, opts)
  if (type.isWeakRef()) return inspectWeakRef(object, ref, depth, opts)
  if (type.isArrayBuffer()) return inspectArrayBuffer(object, ref, depth, opts)
  if (type.isSharedArrayBuffer()) return inspectSharedArrayBuffer(object, ref, depth, opts)
  if (type.isTypedArray()) return inspectTypedArray(object, ref, depth, opts)
  if (type.isDataView()) return inspectDataView(object, ref, depth, opts)

  ref.increment()

  const values = []

  for (const key of keysOf(object)) {
    if (key === 'constructor') continue

    values.push(
      new InspectPair(
        ': ',
        inspectKey(key, depth + 1, opts),
        inspectProperty(object, key, depth + 1, opts),
        depth + 1,
        opts
      )
    )
  }

  for (const key of symbolsOf(object)) {
    values.push(
      new InspectPair(
        ': ',
        inspectKey(key, depth + 1, opts),
        inspectProperty(object, key, depth + 1, opts),
        depth + 1,
        opts
      )
    )
  }

  ref.decrement()

  let header = '{ '

  const { value: tag } = attempt(() => object[Symbol.toStringTag])

  if (typeof tag === 'string' && tag) header = '[' + tag + '] ' + header

  const name = nameOf(object)

  if (name && name !== 'Object') header = name + ' ' + header

  return new InspectSequence(header, ' }', ', ', values, ref, depth, opts)
}

function inspectArray(array, ref, depth, opts) {
  const { maxArrayLength = defaultMaxArrayLength } = opts

  ref.increment()

  const values = []

  let remaining = Math.max(maxArrayLength, 0)

  for (let i = 0, n = array.length; i < n; i++) {
    if (remaining-- === 0) {
      values.push(
        new InspectSuspension(array.length - values.length, depth + 1, {
          ...opts,
          breakAlways: true
        })
      )

      break
    }

    values.push(inspectProperty(array, i, depth + 1, opts))
  }

  for (const key of nonIndexKeysOf(array)) {
    values.push(
      new InspectPair(
        ': ',
        inspectKey(key, depth + 1, opts),
        inspectProperty(array, key, depth + 1, opts),
        depth + 1,
        { ...opts, breakAlways: remaining < 0 }
      )
    )
  }

  ref.decrement()

  let header = '[ '

  const name = nameOf(array)

  if (name && name !== 'Array') header = name + '(' + array.length + ') ' + header

  return new InspectSequence(header, ' ]', ', ', values, ref, depth, {
    ...opts,
    tabulate: true
  })
}

function inspectProperty(object, key, depth, opts) {
  const descriptor = descriptorOf(object, key)

  if (descriptor === null) {
    return new InspectLeaf('<unreadable>', styles.special, depth, opts)
  }

  if (descriptor === undefined) return inspectUndefined(depth, opts)

  if ('value' in descriptor === false) {
    let label = '[getter/setter]'

    if (descriptor.get === undefined) label = '[setter]'
    else if (descriptor.set === undefined) label = '[getter]'

    return new InspectLeaf(label, styles.special, depth, opts)
  }

  return inspectValue(descriptor.value, depth, opts)
}

function inspectDate(date, ref, depth, opts) {
  const { value: time } = attempt(() => date.getTime())

  if (Number.isNaN(time)) {
    return new InspectLeaf('Invalid Date', styles.date, depth, opts)
  }

  const { value: string } = attempt(() => date.toISOString())

  return new InspectLeaf(string || '[Date]', styles.date, depth, opts)
}

function inspectRegExp(regExp, ref, depth, opts) {
  const { value: string } = attempt(() => regExp.toString())

  return new InspectLeaf(string || '[RegExp]', styles.regexp, depth, opts)
}

function inspectError(error, ref, depth, opts) {
  let header = attempt(() => error.stack).value

  if (typeof header === 'string') {
    // Not all engines put the `<name>: <message>` line at the top of `stack`,
    // so add it when it's missing.
    const title = attempt(() => error.toString()).value

    if (typeof title === 'string' && !header.startsWith(title)) {
      header = title + '\n' + header
    }

    if (depth > 0) {
      header = header.replaceAll('\n', '\n' + '  '.repeat(depth))
    }
  } else {
    header = attempt(() => error.toString()).value

    if (typeof header !== 'string') header = errorLabel(error)
  }

  const builtins = ['cause']

  const { value: name } = attempt(() => error.name)

  if (name === 'AggregateError') {
    builtins.push('errors')
  } else if (name === 'SuppressedError') {
    builtins.push('error', 'suppressed')
  }

  const values = []

  for (const key of builtins) {
    if (descriptorOf(error, key) === undefined) continue

    values.push(
      new InspectPair(
        ': ',
        new InspectLeaf('[' + key + ']', null, depth + 1, opts),
        inspectProperty(error, key, depth + 1, opts),
        depth + 1,
        opts
      )
    )
  }

  for (const key of keysOf(error)) {
    if (key === 'constructor' || builtins.includes(key)) continue

    values.push(
      new InspectPair(
        ': ',
        inspectKey(key, depth + 1, opts),
        inspectProperty(error, key, depth + 1, opts),
        depth + 1,
        opts
      )
    )
  }

  if (values.length === 0) return new InspectLeaf(header, null, depth, opts)

  return new InspectSequence(header + ' {', ' }', ', ', values, ref, depth, opts)
}

function inspectPromise(promise, ref, depth, opts) {
  ref.increment()

  const state = binding.getPromiseState(promise)

  const values = []

  switch (state) {
    case 0: // Pending
      values.push(new InspectLeaf('<pending>', styles.special, depth, opts))
      break

    case 1: // Fulfilled
      values.push(inspectValue(binding.getPromiseResult(promise), depth, opts))
      break

    case 2: // Rejected
      values.push(
        new InspectLeaf('<rejected>', styles.special, depth, opts),
        inspectValue(binding.getPromiseResult(promise), depth, opts)
      )
  }

  ref.decrement()

  const header = (nameOf(promise) || 'Promise') + ' { '

  return new InspectSequence(header, ' }', ' ', values, ref, depth, opts)
}

function inspectMap(map, ref, depth, opts) {
  const { maxArrayLength = defaultMaxArrayLength, maxMapLength = maxArrayLength } = opts

  ref.increment()

  const values = []

  let remaining = maxMapLength

  const { value: size } = attempt(() => map.size)

  attempt(() => {
    for (const entry of map) {
      if (remaining-- === 0) {
        values.push(
          new InspectSuspension(size - values.length, depth + 1, {
            ...opts,
            breakAlways: true
          })
        )
        break
      }

      values.push(
        new InspectPair(
          ' => ',
          inspectValue(entry[0], depth + 1, opts),
          inspectValue(entry[1], depth + 1, opts),
          depth + 1,
          opts
        )
      )
    }
  })

  for (const key of keysOf(map)) {
    if (key === 'constructor') continue

    const value = inspectProperty(map, key, depth + 1, opts)

    values.push(
      new InspectPair(': ', inspectKey(key, depth + 1, opts), value, depth + 1, {
        ...opts,
        breakAlways: remaining < 0
      })
    )
  }

  ref.decrement()

  const header = (nameOf(map) || 'Map') + '(' + size + ') { '

  return new InspectSequence(header, ' }', ', ', values, ref, depth, {
    ...opts,
    tabulate: true
  })
}

function inspectSet(set, ref, depth, opts) {
  const { maxArrayLength = defaultMaxArrayLength, maxSetLength = maxArrayLength } = opts

  ref.increment()

  const values = []

  let remaining = maxSetLength

  const { value: size } = attempt(() => set.size)

  attempt(() => {
    for (const entry of set) {
      if (remaining-- === 0) {
        values.push(
          new InspectSuspension(size - values.length, depth + 1, {
            ...opts,
            breakAlways: true
          })
        )
        break
      }

      values.push(inspectValue(entry, depth + 1, opts))
    }
  })

  for (const key of keysOf(set)) {
    if (key === 'constructor') continue

    const value = inspectProperty(set, key, depth + 1, opts)

    values.push(
      new InspectPair(': ', inspectKey(key, depth + 1, opts), value, depth + 1, {
        ...opts,
        breakAlways: remaining < 0
      })
    )
  }

  ref.decrement()

  const header = (nameOf(set) || 'Set') + '(' + size + ') { '

  return new InspectSequence(header, ' }', ', ', values, ref, depth, {
    ...opts,
    tabulate: true
  })
}

function inspectWeakMap(weakMap, ref, depth, opts) {
  const header = (nameOf(weakMap) || 'WeakMap') + ' { '

  return new InspectSequence(
    header,
    ' }',
    ' ',
    [new InspectLeaf('<items unknown>', styles.special, depth + 1, opts)],
    ref,
    depth,
    opts
  )
}

function inspectWeakSet(weakSet, ref, depth, opts) {
  const header = (nameOf(weakSet) || 'WeakSet') + ' { '

  return new InspectSequence(
    header,
    ' }',
    ' ',
    [new InspectLeaf('<items unknown>', styles.special, depth + 1, opts)],
    ref,
    depth,
    opts
  )
}

function inspectWeakRef(weakRef, ref, depth, opts) {
  const { value: target } = attempt(() => weakRef.deref())

  let value

  if (target === undefined) {
    value = new InspectLeaf('<cleared>', styles.special, depth + 1, opts)
  } else {
    value = inspectValue(target, depth + 1, opts)
  }

  const header = (nameOf(weakRef) || 'WeakRef') + ' { '

  return new InspectSequence(header, ' }', ' ', [value], ref, depth, opts)
}

function inspectArrayBuffer(arrayBuffer, ref, depth, opts) {
  ref.increment()

  const values = []

  for (const key of ['byteLength']) {
    values.push(
      new InspectPair(
        ': ',
        inspectKey(key, depth + 1, opts),
        inspectValue(arrayBuffer[key], depth + 1, opts),
        depth + 1,
        opts
      )
    )
  }

  ref.decrement()

  const header = (nameOf(arrayBuffer) || 'ArrayBuffer') + ' { '

  return new InspectSequence(header, ' }', ', ', values, ref, depth, opts)
}

function inspectSharedArrayBuffer(sharedArrayBuffer, ref, depth, opts) {
  ref.increment()

  const values = []

  for (const key of ['byteLength']) {
    values.push(
      new InspectPair(
        ': ',
        inspectKey(key, depth + 1, opts),
        inspectValue(sharedArrayBuffer[key], depth + 1, opts),
        depth + 1,
        opts
      )
    )
  }

  ref.decrement()

  const header = (nameOf(sharedArrayBuffer) || 'SharedArrayBuffer') + ' { '

  return new InspectSequence(header, ' }', ', ', values, ref, depth, opts)
}

function inspectTypedArray(typedArray, ref, depth, opts) {
  if (Buffer.isBuffer(typedArray)) {
    return inspectBuffer(typedArray, ref, depth, opts)
  }

  const { maxArrayLength = defaultMaxArrayLength, maxTypedArrayLength = maxArrayLength } = opts

  ref.increment()

  const values = []

  let remaining = Math.max(maxTypedArrayLength, 0)

  for (let i = 0, n = typedArray.length; i < n; i++) {
    if (remaining-- === 0) {
      values.push(
        new InspectSuspension(typedArray.length - values.length, depth + 1, {
          ...opts,
          breakAlways: true
        })
      )

      break
    }

    values.push(inspectValue(typedArray[i], depth + 1, opts))
  }

  for (const key of nonIndexKeysOf(typedArray)) {
    values.push(
      new InspectPair(
        ': ',
        inspectKey(key, depth + 1, opts),
        inspectProperty(typedArray, key, depth + 1, opts),
        depth + 1,
        { ...opts, breakAlways: remaining < 0 }
      )
    )
  }

  ref.decrement()

  const header = (nameOf(typedArray) || 'TypedArray') + '(' + typedArray.length + ') [ '

  return new InspectSequence(header, ' ]', ', ', values, ref, depth, {
    ...opts,
    tabulate: true
  })
}

function inspectBuffer(buffer, ref, depth, opts) {
  const { maxArrayLength = defaultMaxArrayLength, maxBufferLength = maxArrayLength } = opts

  ref.increment()

  const values = []

  let remaining = Math.max(maxBufferLength, 0)

  for (let i = 0, n = buffer.byteLength; i < n; i++) {
    if (remaining-- === 0) {
      values.push(
        new InspectSuspension(buffer.length - values.length, depth + 1, {
          ...opts,
          breakAlways: true
        })
      )

      break
    }

    values.push(new InspectLeaf(buffer[i].toString(16).padStart(2, '0'), null, depth + 1, opts))
  }

  for (const key of nonIndexKeysOf(buffer)) {
    values.push(
      new InspectPair(
        ': ',
        inspectKey(key, depth + 1, opts),
        inspectProperty(buffer, key, depth + 1, opts),
        depth + 1,
        { ...opts, breakAlways: remaining < 0 }
      )
    )
  }

  ref.decrement()

  return new InspectSequence('<Buffer ', '>', ' ', values, ref, depth, {
    ...opts,
    tabulate: true
  })
}

function inspectDataView(dataView, ref, depth, opts) {
  ref.increment()

  const values = []

  for (const key of ['byteLength', 'byteOffset', 'buffer']) {
    values.push(
      new InspectPair(
        ': ',
        inspectKey(key, depth + 1, opts),
        inspectValue(dataView[key], depth + 1, opts),
        depth + 1,
        opts
      )
    )
  }

  for (const key of nonIndexKeysOf(dataView)) {
    values.push(
      new InspectPair(
        ': ',
        inspectKey(key, depth + 1, opts),
        inspectProperty(dataView, key, depth + 1, opts),
        depth + 1,
        opts
      )
    )
  }

  ref.decrement()

  const header = (nameOf(dataView) || 'DataView') + ' { '

  return new InspectSequence(header, ' }', ', ', values, ref, depth, opts)
}

function inspectFunction(type, fn, depth, opts) {
  const { value: source } = attempt(() => fn.toString())

  if (typeof source === 'string' && source.startsWith('class')) {
    return inspectClass(fn, depth, opts)
  }

  let tag = 'function'

  if (type.isGeneratorFunction()) tag = 'generator ' + tag
  if (type.isAsyncFunction()) tag = 'async ' + tag

  const { value: name } = attempt(() => fn.name)

  return new InspectLeaf(
    '[' + tag + ' ' + (name ? name : '(anonymous)') + ']',
    styles.special,
    depth,
    opts
  )
}

function inspectClass(ctor, depth, opts) {
  const { value: name } = attempt(() => ctor.name)

  return new InspectLeaf(
    '[class ' + (name ? name : '(anonymous)') + ']',
    styles.special,
    depth,
    opts
  )
}

// Externals are labelled by an opaque, per-process token so that their address
// is never disclosed.
const externalTokens = new WeakMap()

let nextExternalToken = 1

function inspectExternal(external, depth, opts) {
  let token = externalTokens.get(external)

  if (token === undefined) {
    token = nextExternalToken++

    externalTokens.set(external, token)
  }

  return new InspectLeaf('[external #' + token + ']', styles.special, depth, opts)
}

// An inspection must not fail on the value it inspects, so everything read from
// that value is read through one of the helpers below. Values are read for
// their own sake, which is why an accessor is reported rather than called, but
// the labels an inspection puts around them are read as they are written.

function attempt(fn) {
  try {
    return { value: fn(), threw: false, error: null }
  } catch (err) {
    return { value: undefined, threw: true, error: err }
  }
}

// The name a value is labelled with, which is the name of its constructor.
function nameOf(object) {
  const { value: name } = attempt(() => object.constructor.name)

  return typeof name === 'string' ? name : ''
}

// A prototype inherits the custom inspection written for its instances, which
// must not be called on the prototype itself as it is not one of them.
function isPrototype(object) {
  const { value: prototype } = attempt(() => object.constructor.prototype)

  return prototype === object
}

function keysOf(object) {
  const keys = []

  // A partial list is the best that can be had from a value that stops
  // answering halfway through.
  attempt(() => {
    for (const key in object) keys.push(key)
  })

  return keys
}

function symbolsOf(object) {
  const symbols = []

  for (const symbol of attempt(() => Object.getOwnPropertySymbols(object)).value || []) {
    const { value: descriptor, threw } = attempt(() =>
      Object.getOwnPropertyDescriptor(object, symbol)
    )

    if (threw || descriptor === undefined || descriptor.enumerable) symbols.push(symbol)
  }

  return symbols
}

function nonIndexKeysOf(object) {
  return attempt(() => binding.getOwnNonIndexPropertyNames(object)).value || []
}

// Returns the descriptor, `undefined` if the property is absent, or `null` if
// the lookup itself could not be made, as with a proxy that throws.
function descriptorOf(object, key) {
  let target = object

  while (target !== null && target !== undefined) {
    const { value: descriptor, threw } = attempt(() => Object.getOwnPropertyDescriptor(target, key))

    if (threw) return null

    if (descriptor) return descriptor

    const { value: prototype, threw: unreadable } = attempt(() => Object.getPrototypeOf(target))

    if (unreadable) return null

    target = prototype
  }

  return undefined
}

function errorLabel(err) {
  if (err === null || typeof err !== 'object') return 'a non-error'

  const { value: message } = attempt(() => err.message)

  const name = nameOf(err) || 'Error'

  return typeof message === 'string' && message ? name + ': ' + message : name
}
{
  "name": "bare-inspect",
  "version": "3.1.10",
  "description": "Inspect objects as strings for debugging",
  "exports": {
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    },
    "./package": "./package.json"
  },
  "files": [
    "index.js",
    "index.d.ts",
    "binding.c",
    "binding.js",
    "CMakeLists.txt",
    "prebuilds"
  ],
  "addon": true,
  "scripts": {
    "format": "prettier --write . && lunte --fix",
    "lint": "prettier --check . && lunte",
    "test": "brittle-bare --coverage test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-inspect.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-inspect/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-inspect#readme",
  "engines": {
    "bare": ">=1.18.0"
  },
  "dependencies": {
    "bare-ansi-escapes": "^2.1.0",
    "bare-type": "^1.0.0"
  },
  "devDependencies": {
    "brittle": "^3.3.2",
    "cmake-bare": "^1.1.6",
    "lunte": "^1.8.4",
    "prettier": "^3.4.1",
    "prettier-config-holepunch": "^2.0.0"
  }
}
module.exports = require.addon()
const binding = require('./binding')
const errors = require('./lib/errors')
const constants = require('./lib/constants')

exports.constants = constants
exports.errors = errors

exports.EOL = binding.platform === 'win32' ? '\r\n' : '\n'

exports.devNull = binding.platform === 'win32' ? '\\\\.\\nul' : '/dev/null'

exports.platform = function platform() {
  return binding.platform
}

exports.arch = function arch() {
  return binding.arch
}

exports.type = function type() {
  return binding.type()
}

exports.version = function version() {
  return binding.version()
}

exports.release = function release() {
  return binding.release()
}

exports.machine = function machine() {
  return binding.machine()
}

exports.execPath = function execPath() {
  return binding.execPath()
}

exports.pid = function pid() {
  return binding.pid()
}

exports.ppid = function ppid() {
  return binding.ppid()
}

exports.cwd = function cwd() {
  return binding.cwd()
}

exports.chdir = function chdir(dir) {
  validateString(dir, 'Directory')

  binding.chdir(dir)
}

exports.tmpdir = function tmpdir() {
  return binding.tmpdir()
}

exports.homedir = function homedir() {
  return binding.homedir()
}

exports.hostname = function hostname() {
  return binding.hostname()
}

exports.userInfo = function userInfo(uid) {
  if (uid !== undefined) validateInteger(uid, 'User ID')

  return binding.userInfo(uid)
}

exports.groupInfo = function groupInfo(gid) {
  if (gid !== undefined) validateInteger(gid, 'Group ID')

  return binding.groupInfo(gid)
}

exports.networkInterfaces = function networkInterfaces() {
  const result = {}

  for (const entry of binding.networkInterfaces()) {
    const { name, ...properties } = entry

    if (result[name]) result[name].push(properties)
    else result[name] = [properties]
  }

  return result
}

exports.kill = function kill(pid, signal = constants.signals.SIGTERM) {
  validateInteger(pid, 'Process ID')

  if (typeof signal === 'string') {
    if (signal in constants.signals === false) {
      throw errors.UNKNOWN_SIGNAL('Unknown signal: ' + signal)
    }

    signal = constants.signals[signal]
  } else {
    validateInteger(signal, 'Signal')
  }

  binding.kill(pid, signal)
}

exports.endianness = function endianness() {
  return binding.isLittleEndian ? 'LE' : 'BE'
}

exports.availableParallelism = function availableParallelism() {
  return binding.availableParallelism()
}

exports.cpuUsage = function cpuUsage(previous) {
  const current = binding.cpuUsage()

  if (previous) {
    return {
      user: current.user - previous.user,
      system: current.system - previous.system
    }
  }

  return current
}

exports.threadCpuUsage = function threadCpuUsage(previous) {
  const current = binding.threadCpuUsage()

  if (previous) {
    return {
      user: current.user - previous.user,
      system: current.system - previous.system
    }
  }

  return current
}

exports.resourceUsage = function resourceUsage() {
  return binding.resourceUsage()
}

exports.memoryUsage = function memoryUsage() {
  return binding.memoryUsage()
}

exports.freemem = function freemem() {
  return binding.freemem()
}

exports.totalmem = function totalmem() {
  return binding.totalmem()
}

exports.availableMemory = function availableMemory() {
  return binding.availableMemory()
}

exports.constrainedMemory = function constrainedMemory() {
  return binding.constrainedMemory()
}

exports.uptime = function uptime() {
  return binding.uptime()
}

exports.loadavg = function loadavg() {
  return binding.loadavg()
}

exports.cpus = function cpus() {
  return binding.cpus()
}

exports.getProcessTitle = function getProcessTitle() {
  return binding.getProcessTitle()
}

exports.setProcessTitle = function setProcessTitle(title) {
  if (typeof title !== 'string') title = title.toString()

  if (title.length >= 256) {
    throw errors.TITLE_OVERFLOW('Process title is too long')
  }

  binding.setProcessTitle(title)
}

exports.getPriority = function getPriority(pid = 0) {
  validateInteger(pid, 'Process ID')

  return binding.getPriority(pid)
}

exports.setPriority = function setPriority(pid, priority) {
  if (priority === undefined) {
    priority = pid
    pid = 0
  }

  validateInteger(pid, 'Process ID')
  validateInteger(priority, 'Priority')

  binding.setPriority(pid, priority)
}

exports.getEnvKeys = function getEnvKeys() {
  return binding.getEnvKeys()
}

exports.getEnv = function getEnv(name) {
  validateString(name, 'Name')

  return binding.getEnv(name)
}

exports.hasEnv = function hasEnv(name) {
  validateString(name, 'Name')

  return binding.hasEnv(name)
}

exports.setEnv = function setEnv(name, value) {
  validateString(name, 'Name')
  validateString(value, 'Value')

  binding.setEnv(name, value)
}

exports.unsetEnv = function unsetEnv(name) {
  validateString(name, 'Name')

  binding.unsetEnv(name)
}

function validateString(value, name) {
  if (typeof value !== 'string') {
    throw new TypeError(`${name} must be a string. Received type ${typeof value} (${value})`)
  }
}

function validateInteger(value, name) {
  if (!Number.isInteger(value)) {
    throw new TypeError(`${name} must be an integer. Received type ${typeof value} (${value})`)
  }
}
const binding = require('../binding')

module.exports = {
  signals: binding.signals,
  errnos: binding.errnos,
  priority: binding.priority
}
module.exports = class OSError extends Error {
  constructor(msg, fn = OSError, code = fn.name) {
    super(`${code}: ${msg}`)
    this.code = code

    if (Error.captureStackTrace) {
      Error.captureStackTrace(this, fn)
    }
  }

  get name() {
    return 'OSError'
  }

  static UNKNOWN_SIGNAL(msg) {
    return new OSError(msg, OSError.UNKNOWN_SIGNAL)
  }

  static TITLE_OVERFLOW(msg) {
    return new OSError(msg, OSError.TITLE_OVERFLOW)
  }
}
{
  "name": "bare-os",
  "version": "3.9.3",
  "description": "Operating system utilities for Bare",
  "exports": {
    "./package": "./package.json",
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    },
    "./constants": {
      "types": "./lib/constants.d.ts",
      "default": "./lib/constants.js"
    },
    "./errors": {
      "types": "./lib/errors.d.ts",
      "default": "./lib/errors.js"
    }
  },
  "files": [
    "index.js",
    "index.d.ts",
    "binding.c",
    "binding.js",
    "CMakeLists.txt",
    "lib",
    "prebuilds"
  ],
  "addon": true,
  "scripts": {
    "format": "prettier --write . && lunte --fix",
    "lint": "prettier --check . && lunte",
    "test": "brittle-bare --coverage test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-os.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-os/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-os#readme",
  "engines": {
    "bare": ">=1.14.0"
  },
  "devDependencies": {
    "brittle": "^3.1.1",
    "cmake-bare": "^1.1.6",
    "lunte": "^1.6.0",
    "prettier": "^3.4.2",
    "prettier-config-holepunch": "^2.0.0"
  }
}
module.exports = require.addon()
// This export SHOULD NOT be shortened in any way as having the full
// `module.exports = require(...)` statement is crucial for synthesizing
// ESM exports.

if (Bare.platform === 'win32') {
  module.exports = require('./lib/win32')
} else {
  module.exports = require('./lib/posix')
}
module.exports = {
  CHAR_UPPERCASE_A: 0x41,
  CHAR_LOWERCASE_A: 0x61,
  CHAR_UPPERCASE_Z: 0x5a,
  CHAR_LOWERCASE_Z: 0x7a,
  CHAR_DOT: 0x2e,
  CHAR_FORWARD_SLASH: 0x2f,
  CHAR_BACKWARD_SLASH: 0x5c,
  CHAR_COLON: 0x3a,
  CHAR_QUESTION_MARK: 0x3f
}
const binding = require('../binding')

const { normalizeString, validateString } = require('./shared')
const { CHAR_DOT, CHAR_FORWARD_SLASH } = require('./constants')

function isPosixPathSeparator(code) {
  return code === CHAR_FORWARD_SLASH
}

exports.win32 = require('./win32')
exports.posix = exports

exports.sep = '/'
exports.delimiter = ':'

exports.resolve = function resolve(...args) {
  let resolvedPath = ''
  let resolvedAbsolute = false

  for (let i = args.length - 1; i >= -1 && !resolvedAbsolute; i--) {
    const path = i >= 0 ? args[i] : binding.cwd()

    if (i >= 0) validateString(path, `paths[${i}]`)

    if (path.length === 0) {
      continue
    }

    resolvedPath = `${path}/${resolvedPath}`
    resolvedAbsolute = path.charCodeAt(0) === CHAR_FORWARD_SLASH
  }

  resolvedPath = normalizeString(resolvedPath, !resolvedAbsolute, '/', isPosixPathSeparator)

  if (resolvedAbsolute) {
    return `/${resolvedPath}`
  }

  return resolvedPath.length > 0 ? resolvedPath : '.'
}

exports.normalize = function normalize(path) {
  validateString(path, 'path')

  if (path.length === 0) return '.'

  const isAbsolute = path.charCodeAt(0) === CHAR_FORWARD_SLASH
  const trailingSeparator = path.charCodeAt(path.length - 1) === CHAR_FORWARD_SLASH

  path = normalizeString(path, !isAbsolute, '/', isPosixPathSeparator)

  if (path.length === 0) {
    if (isAbsolute) return '/'
    return trailingSeparator ? './' : '.'
  }

  if (trailingSeparator) path += '/'

  return isAbsolute ? `/${path}` : path
}

exports.isAbsolute = function isAbsolute(path) {
  validateString(path, 'path')

  return path.length > 0 && path.charCodeAt(0) === CHAR_FORWARD_SLASH
}

exports.join = function join(...args) {
  if (args.length === 0) return '.'
  let joined
  for (let i = 0; i < args.length; ++i) {
    const arg = args[i]
    validateString(arg, 'path')
    if (arg.length > 0) {
      if (joined === undefined) joined = arg
      else joined += `/${arg}`
    }
  }
  if (joined === undefined) return '.'
  return exports.normalize(joined)
}

exports.relative = function relative(from, to) {
  validateString(from, 'from')
  validateString(to, 'to')

  if (from === to) return ''

  from = exports.resolve(from)
  to = exports.resolve(to)

  if (from === to) return ''

  const fromStart = 1
  const fromEnd = from.length
  const fromLen = fromEnd - fromStart
  const toStart = 1
  const toLen = to.length - toStart

  const length = fromLen < toLen ? fromLen : toLen
  let lastCommonSep = -1
  let i = 0
  for (; i < length; i++) {
    const fromCode = from.charCodeAt(fromStart + i)
    if (fromCode !== to.charCodeAt(toStart + i)) {
      break
    } else if (fromCode === CHAR_FORWARD_SLASH) {
      lastCommonSep = i
    }
  }
  if (i === length) {
    if (toLen > length) {
      if (to.charCodeAt(toStart + i) === CHAR_FORWARD_SLASH) {
        return to.substring(toStart + i + 1)
      }
      if (i === 0) {
        return to.substring(toStart + i)
      }
    } else if (fromLen > length) {
      if (from.charCodeAt(fromStart + i) === CHAR_FORWARD_SLASH) {
        lastCommonSep = i
      } else if (i === 0) {
        lastCommonSep = 0
      }
    }
  }

  let out = ''
  for (i = fromStart + lastCommonSep + 1; i <= fromEnd; ++i) {
    if (i === fromEnd || from.charCodeAt(i) === CHAR_FORWARD_SLASH) {
      out += out.length === 0 ? '..' : '/..'
    }
  }

  return `${out}${to.substring(toStart + lastCommonSep)}`
}

exports.toNamespacedPath = function toNamespacedPath(path) {
  return path
}

exports.dirname = function dirname(path) {
  validateString(path, 'path')

  if (path.length === 0) return '.'
  const hasRoot = path.charCodeAt(0) === CHAR_FORWARD_SLASH
  let end = -1
  let matchedSlash = true
  for (let i = path.length - 1; i >= 1; --i) {
    if (path.charCodeAt(i) === CHAR_FORWARD_SLASH) {
      if (!matchedSlash) {
        end = i
        break
      }
    } else {
      matchedSlash = false
    }
  }

  if (end === -1) return hasRoot ? '/' : '.'
  if (hasRoot && end === 1) return '//'
  return path.substring(0, end)
}

exports.basename = function basename(path, suffix) {
  if (suffix !== undefined) validateString(suffix, 'suffix')
  validateString(path, 'path')

  let start = 0
  let end = -1
  let matchedSlash = true

  if (suffix !== undefined && suffix.length > 0 && suffix.length <= path.length) {
    if (suffix === path) {
      return ''
    }
    let extIdx = suffix.length - 1
    let firstNonSlashEnd = -1
    for (let i = path.length - 1; i >= 0; --i) {
      const code = path.charCodeAt(i)
      if (code === CHAR_FORWARD_SLASH) {
        if (!matchedSlash) {
          start = i + 1
          break
        }
      } else {
        if (firstNonSlashEnd === -1) {
          matchedSlash = false
          firstNonSlashEnd = i + 1
        }
        if (extIdx >= 0) {
          if (code === suffix.charCodeAt(extIdx)) {
            if (--extIdx === -1) {
              end = i
            }
          } else {
            extIdx = -1
            end = firstNonSlashEnd
          }
        }
      }
    }

    if (start === end) end = firstNonSlashEnd
    else if (end === -1) end = path.length
    return path.substring(start, end)
  }

  for (let i = path.length - 1; i >= 0; --i) {
    if (path.charCodeAt(i) === CHAR_FORWARD_SLASH) {
      if (!matchedSlash) {
        start = i + 1
        break
      }
    } else if (end === -1) {
      matchedSlash = false
      end = i + 1
    }
  }

  if (end === -1) return ''
  return path.substring(start, end)
}

exports.extname = function extname(path) {
  validateString(path, 'path')

  let startDot = -1
  let startPart = 0
  let end = -1
  let matchedSlash = true
  let preDotState = 0
  for (let i = path.length - 1; i >= 0; --i) {
    const code = path.charCodeAt(i)
    if (code === CHAR_FORWARD_SLASH) {
      if (!matchedSlash) {
        startPart = i + 1
        break
      }
      continue
    }
    if (end === -1) {
      matchedSlash = false
      end = i + 1
    }
    if (code === CHAR_DOT) {
      if (startDot === -1) startDot = i
      else if (preDotState !== 1) preDotState = 1
    } else if (startDot !== -1) {
      preDotState = -1
    }
  }

  if (
    startDot === -1 ||
    end === -1 ||
    preDotState === 0 ||
    (preDotState === 1 && startDot === end - 1 && startDot === startPart + 1)
  ) {
    return ''
  }
  return path.substring(startDot, end)
}
const { CHAR_DOT, CHAR_FORWARD_SLASH } = require('./constants')

exports.validateString = function validateString(value, name) {
  if (typeof value !== 'string') {
    throw new TypeError(
      `The "${name}" argument must be of type string. Received type ${typeof value}`
    )
  }
}

exports.normalizeString = function normalizeString(
  path,
  allowAboveRoot,
  separator,
  isPathSeparator
) {
  let res = ''
  let lastSegmentLength = 0
  let lastSlash = -1
  let dots = 0
  let code = 0
  for (let i = 0; i <= path.length; ++i) {
    if (i < path.length) {
      code = path.charCodeAt(i)
    } else if (isPathSeparator(code)) {
      break
    } else {
      code = CHAR_FORWARD_SLASH
    }

    if (isPathSeparator(code)) {
      if (lastSlash === i - 1 || dots === 1);
      else if (dots === 2) {
        if (
          res.length < 2 ||
          lastSegmentLength !== 2 ||
          res.charCodeAt(res.length - 1) !== CHAR_DOT ||
          res.charCodeAt(res.length - 2) !== CHAR_DOT
        ) {
          if (res.length > 2) {
            const lastSlashIndex = res.lastIndexOf(separator)
            if (lastSlashIndex === -1) {
              res = ''
              lastSegmentLength = 0
            } else {
              res = res.substring(0, lastSlashIndex)
              lastSegmentLength = res.length - 1 - res.lastIndexOf(separator)
            }
            lastSlash = i
            dots = 0
            continue
          } else if (res.length !== 0) {
            res = ''
            lastSegmentLength = 0
            lastSlash = i
            dots = 0
            continue
          }
        }
        if (allowAboveRoot) {
          res += res.length > 0 ? `${separator}..` : '..'
          lastSegmentLength = 2
        }
      } else {
        if (res.length > 0) {
          res += `${separator}${path.substring(lastSlash + 1, i)}`
        } else {
          res = path.substring(lastSlash + 1, i)
        }
        lastSegmentLength = i - lastSlash - 1
      }
      lastSlash = i
      dots = 0
    } else if (code === CHAR_DOT && dots !== -1) {
      ++dots
    } else {
      dots = -1
    }
  }
  return res
}
const binding = require('../binding')

const { normalizeString, validateString } = require('./shared')
const {
  CHAR_UPPERCASE_A,
  CHAR_LOWERCASE_A,
  CHAR_UPPERCASE_Z,
  CHAR_LOWERCASE_Z,
  CHAR_DOT,
  CHAR_FORWARD_SLASH,
  CHAR_BACKWARD_SLASH,
  CHAR_COLON,
  CHAR_QUESTION_MARK
} = require('./constants')

function isWindowsPathSeparator(code) {
  return code === CHAR_FORWARD_SLASH || code === CHAR_BACKWARD_SLASH
}

function isWindowsDeviceRoot(code) {
  return (
    (code >= CHAR_UPPERCASE_A && code <= CHAR_UPPERCASE_Z) ||
    (code >= CHAR_LOWERCASE_A && code <= CHAR_LOWERCASE_Z)
  )
}

exports.posix = require('./posix')
exports.win32 = exports

exports.sep = '\\'
exports.delimiter = ';'

exports.resolve = function resolve(...args) {
  let resolvedDevice = ''
  let resolvedTail = ''
  let resolvedAbsolute = false

  for (let i = args.length - 1; i >= -1; i--) {
    let path
    if (i >= 0) {
      path = args[i]

      validateString(path, `paths[${i}]`)

      if (path.length === 0) continue
    } else if (resolvedDevice.length === 0) {
      path = binding.cwd()
    } else {
      path = binding.cwd(resolvedDevice)

      if (
        path === undefined ||
        (path.substring(0, 2).toLowerCase() !== resolvedDevice.toLowerCase() &&
          path.charCodeAt(2) === CHAR_BACKWARD_SLASH)
      ) {
        path = `${resolvedDevice}\\`
      }
    }

    const len = path.length
    let rootEnd = 0
    let device = ''
    let isAbsolute = false
    const code = path.charCodeAt(0)

    if (len === 1) {
      if (isWindowsPathSeparator(code)) {
        rootEnd = 1
        isAbsolute = true
      }
    } else if (isWindowsPathSeparator(code)) {
      isAbsolute = true

      if (isWindowsPathSeparator(path.charCodeAt(1))) {
        let j = 2
        let last = j
        while (j < len && !isWindowsPathSeparator(path.charCodeAt(j))) {
          j++
        }
        if (j < len && j !== last) {
          const firstPart = path.substring(last, j)
          last = j
          while (j < len && isWindowsPathSeparator(path.charCodeAt(j))) {
            j++
          }
          if (j < len && j !== last) {
            last = j
            while (j < len && !isWindowsPathSeparator(path.charCodeAt(j))) {
              j++
            }
            if (j === len || j !== last) {
              device = `\\\\${firstPart}\\${path.substring(last, j)}`
              rootEnd = j
            }
          }
        }
      } else {
        rootEnd = 1
      }
    } else if (isWindowsDeviceRoot(code) && path.charCodeAt(1) === CHAR_COLON) {
      device = path.substring(0, 2)
      rootEnd = 2
      if (len > 2 && isWindowsPathSeparator(path.charCodeAt(2))) {
        isAbsolute = true
        rootEnd = 3
      }
    }

    if (device.length > 0) {
      if (resolvedDevice.length > 0) {
        if (device.toLowerCase() !== resolvedDevice.toLowerCase()) {
          continue
        }
      } else {
        resolvedDevice = device
      }
    }

    if (resolvedAbsolute) {
      if (resolvedDevice.length > 0) {
        break
      }
    } else {
      resolvedTail = `${path.substring(rootEnd)}\\${resolvedTail}`
      resolvedAbsolute = isAbsolute
      if (isAbsolute && resolvedDevice.length > 0) {
        break
      }
    }
  }

  resolvedTail = normalizeString(resolvedTail, !resolvedAbsolute, '\\', isWindowsPathSeparator)

  return resolvedAbsolute
    ? `${resolvedDevice}\\${resolvedTail}`
    : `${resolvedDevice}${resolvedTail}` || '.'
}

exports.normalize = function normalize(path) {
  validateString(path, 'path')

  const len = path.length
  if (len === 0) return '.'
  let rootEnd = 0
  let device
  let isAbsolute = false
  const code = path.charCodeAt(0)

  if (len === 1) {
    return code === CHAR_FORWARD_SLASH ? '\\' : path
  }

  if (isWindowsPathSeparator(code)) {
    isAbsolute = true

    if (isWindowsPathSeparator(path.charCodeAt(1))) {
      let j = 2
      let last = j
      while (j < len && !isWindowsPathSeparator(path.charCodeAt(j))) {
        j++
      }
      if (j < len && j !== last) {
        const firstPart = path.substring(last, j)
        last = j
        while (j < len && isWindowsPathSeparator(path.charCodeAt(j))) {
          j++
        }
        if (j < len && j !== last) {
          last = j
          while (j < len && !isWindowsPathSeparator(path.charCodeAt(j))) {
            j++
          }
          if (j === len) {
            return `\\\\${firstPart}\\${path.substring(last)}\\`
          }
          if (j !== last) {
            device = `\\\\${firstPart}\\${path.substring(last, j)}`
            rootEnd = j
          }
        }
      }
    } else {
      rootEnd = 1
    }
  } else if (isWindowsDeviceRoot(code) && path.charCodeAt(1) === CHAR_COLON) {
    device = path.substring(0, 2)
    rootEnd = 2
    if (len > 2 && isWindowsPathSeparator(path.charCodeAt(2))) {
      isAbsolute = true
      rootEnd = 3
    }
  }

  let tail =
    rootEnd < len
      ? normalizeString(path.substring(rootEnd), !isAbsolute, '\\', isWindowsPathSeparator)
      : ''
  if (tail.length === 0 && !isAbsolute) {
    tail = '.'
  }
  if (tail.length > 0 && isWindowsPathSeparator(path.charCodeAt(len - 1))) {
    tail += '\\'
  }
  if (device === undefined) {
    return isAbsolute ? `\\${tail}` : tail
  }
  return isAbsolute ? `${device}\\${tail}` : `${device}${tail}`
}

exports.isAbsolute = function isAbsolute(path) {
  validateString(path, 'path')

  const len = path.length
  if (len === 0) return false

  const code = path.charCodeAt(0)

  return (
    isWindowsPathSeparator(code) ||
    (len > 2 &&
      isWindowsDeviceRoot(code) &&
      path.charCodeAt(1) === CHAR_COLON &&
      isWindowsPathSeparator(path.charCodeAt(2)))
  )
}

exports.join = function join(...args) {
  if (args.length === 0) return '.'

  let joined
  let firstPart
  for (let i = 0; i < args.length; ++i) {
    const arg = args[i]
    validateString(arg, 'path')
    if (arg.length > 0) {
      if (joined === undefined) joined = firstPart = arg
      else joined += `\\${arg}`
    }
  }

  if (joined === undefined) return '.'

  let needsReplace = true
  let slashCount = 0
  if (isWindowsPathSeparator(firstPart.charCodeAt(0))) {
    ++slashCount
    const firstLen = firstPart.length
    if (firstLen > 1 && isWindowsPathSeparator(firstPart.charCodeAt(1))) {
      ++slashCount
      if (firstLen > 2) {
        if (isWindowsPathSeparator(firstPart.charCodeAt(2))) {
          ++slashCount
        } else {
          needsReplace = false
        }
      }
    }
  }
  if (needsReplace) {
    while (slashCount < joined.length && isWindowsPathSeparator(joined.charCodeAt(slashCount))) {
      slashCount++
    }

    if (slashCount >= 2) {
      joined = `\\${joined.substring(slashCount)}`
    }
  }

  return exports.normalize(joined)
}

exports.relative = function relative(from, to) {
  validateString(from, 'from')
  validateString(to, 'to')

  if (from === to) return ''

  const fromOrig = exports.resolve(from)
  const toOrig = exports.resolve(to)

  if (fromOrig === toOrig) return ''

  from = fromOrig.toLowerCase()
  to = toOrig.toLowerCase()

  if (from === to) return ''

  let fromStart = 0
  while (fromStart < from.length && from.charCodeAt(fromStart) === CHAR_BACKWARD_SLASH) {
    fromStart++
  }
  let fromEnd = from.length
  while (fromEnd - 1 > fromStart && from.charCodeAt(fromEnd - 1) === CHAR_BACKWARD_SLASH) {
    fromEnd--
  }
  const fromLen = fromEnd - fromStart

  let toStart = 0
  while (toStart < to.length && to.charCodeAt(toStart) === CHAR_BACKWARD_SLASH) {
    toStart++
  }
  let toEnd = to.length
  while (toEnd - 1 > toStart && to.charCodeAt(toEnd - 1) === CHAR_BACKWARD_SLASH) {
    toEnd--
  }
  const toLen = toEnd - toStart

  const length = fromLen < toLen ? fromLen : toLen
  let lastCommonSep = -1
  let i = 0
  for (; i < length; i++) {
    const fromCode = from.charCodeAt(fromStart + i)
    if (fromCode !== to.charCodeAt(toStart + i)) {
      break
    } else if (fromCode === CHAR_BACKWARD_SLASH) {
      lastCommonSep = i
    }
  }

  if (i !== length) {
    if (lastCommonSep === -1) return toOrig
  } else {
    if (toLen > length) {
      if (to.charCodeAt(toStart + i) === CHAR_BACKWARD_SLASH) {
        return toOrig.substring(toStart + i + 1)
      }
      if (i === 2) {
        return toOrig.substring(toStart + i)
      }
    }
    if (fromLen > length) {
      if (from.charCodeAt(fromStart + i) === CHAR_BACKWARD_SLASH) {
        lastCommonSep = i
      } else if (i === 2) {
        lastCommonSep = 3
      }
    }
    if (lastCommonSep === -1) lastCommonSep = 0
  }

  let out = ''
  for (i = fromStart + lastCommonSep + 1; i <= fromEnd; ++i) {
    if (i === fromEnd || from.charCodeAt(i) === CHAR_BACKWARD_SLASH) {
      out += out.length === 0 ? '..' : '\\..'
    }
  }

  toStart += lastCommonSep

  if (out.length > 0) {
    return `${out}${toOrig.substring(toStart, toEnd)}`
  }
  if (toOrig.charCodeAt(toStart) === CHAR_BACKWARD_SLASH) {
    ++toStart
  }
  return toOrig.substring(toStart, toEnd)
}

exports.toNamespacedPath = function toNamespacedPath(path) {
  if (typeof path !== 'string' || path.length === 0) return path

  const resolvedPath = exports.resolve(path)

  if (resolvedPath.length <= 2) return path

  if (resolvedPath.charCodeAt(0) === CHAR_BACKWARD_SLASH) {
    if (resolvedPath.charCodeAt(1) === CHAR_BACKWARD_SLASH) {
      const code = resolvedPath.charCodeAt(2)
      if (code !== CHAR_QUESTION_MARK && code !== CHAR_DOT) {
        return `\\\\?\\UNC\\${resolvedPath.substring(2)}`
      }
    }
  } else if (
    isWindowsDeviceRoot(resolvedPath.charCodeAt(0)) &&
    resolvedPath.charCodeAt(1) === CHAR_COLON &&
    resolvedPath.charCodeAt(2) === CHAR_BACKWARD_SLASH
  ) {
    return `\\\\?\\${resolvedPath}`
  }

  return path
}

exports.dirname = function dirname(path) {
  validateString(path, 'path')

  const len = path.length
  if (len === 0) return '.'
  let rootEnd = -1
  let offset = 0
  const code = path.charCodeAt(0)

  if (len === 1) {
    return isWindowsPathSeparator(code) ? path : '.'
  }

  if (isWindowsPathSeparator(code)) {
    rootEnd = offset = 1

    if (isWindowsPathSeparator(path.charCodeAt(1))) {
      let j = 2
      let last = j
      while (j < len && !isWindowsPathSeparator(path.charCodeAt(j))) {
        j++
      }
      if (j < len && j !== last) {
        last = j
        while (j < len && isWindowsPathSeparator(path.charCodeAt(j))) {
          j++
        }
        if (j < len && j !== last) {
          last = j
          while (j < len && !isWindowsPathSeparator(path.charCodeAt(j))) {
            j++
          }
          if (j === len) {
            return path
          }
          if (j !== last) {
            rootEnd = offset = j + 1
          }
        }
      }
    }
  } else if (isWindowsDeviceRoot(code) && path.charCodeAt(1) === CHAR_COLON) {
    rootEnd = len > 2 && isWindowsPathSeparator(path.charCodeAt(2)) ? 3 : 2
    offset = rootEnd
  }

  let end = -1
  let matchedSlash = true
  for (let i = len - 1; i >= offset; --i) {
    if (isWindowsPathSeparator(path.charCodeAt(i))) {
      if (!matchedSlash) {
        end = i
        break
      }
    } else {
      matchedSlash = false
    }
  }

  if (end === -1) {
    if (rootEnd === -1) return '.'

    end = rootEnd
  }
  return path.substring(0, end)
}

exports.basename = function basename(path, suffix) {
  if (suffix !== undefined) validateString(suffix, 'suffix')
  validateString(path, 'path')

  let start = 0
  let end = -1
  let matchedSlash = true

  if (
    path.length >= 2 &&
    isWindowsDeviceRoot(path.charCodeAt(0)) &&
    path.charCodeAt(1) === CHAR_COLON
  ) {
    start = 2
  }

  if (suffix !== undefined && suffix.length > 0 && suffix.length <= path.length) {
    if (suffix === path) return ''
    let extIdx = suffix.length - 1
    let firstNonSlashEnd = -1
    for (let i = path.length - 1; i >= start; --i) {
      const code = path.charCodeAt(i)
      if (isWindowsPathSeparator(code)) {
        if (!matchedSlash) {
          start = i + 1
          break
        }
      } else {
        if (firstNonSlashEnd === -1) {
          matchedSlash = false
          firstNonSlashEnd = i + 1
        }
        if (extIdx >= 0) {
          if (code === suffix.charCodeAt(extIdx)) {
            if (--extIdx === -1) {
              end = i
            }
          } else {
            extIdx = -1
            end = firstNonSlashEnd
          }
        }
      }
    }

    if (start === end) end = firstNonSlashEnd
    else if (end === -1) end = path.length
    return path.substring(start, end)
  }
  for (let i = path.length - 1; i >= start; --i) {
    if (isWindowsPathSeparator(path.charCodeAt(i))) {
      if (!matchedSlash) {
        start = i + 1
        break
      }
    } else if (end === -1) {
      matchedSlash = false
      end = i + 1
    }
  }

  if (end === -1) return ''
  return path.substring(start, end)
}

exports.extname = function extname(path) {
  validateString(path, 'path')

  let start = 0
  let startDot = -1
  let startPart = 0
  let end = -1
  let matchedSlash = true
  let preDotState = 0

  if (
    path.length >= 2 &&
    path.charCodeAt(1) === CHAR_COLON &&
    isWindowsDeviceRoot(path.charCodeAt(0))
  ) {
    start = startPart = 2
  }

  for (let i = path.length - 1; i >= start; --i) {
    const code = path.charCodeAt(i)
    if (isWindowsPathSeparator(code)) {
      if (!matchedSlash) {
        startPart = i + 1
        break
      }
      continue
    }
    if (end === -1) {
      matchedSlash = false
      end = i + 1
    }
    if (code === CHAR_DOT) {
      if (startDot === -1) startDot = i
      else if (preDotState !== 1) preDotState = 1
    } else if (startDot !== -1) {
      preDotState = -1
    }
  }

  if (
    startDot === -1 ||
    end === -1 ||
    preDotState === 0 ||
    (preDotState === 1 && startDot === end - 1 && startDot === startPart + 1)
  ) {
    return ''
  }
  return path.substring(startDot, end)
}
{
  "name": "bare-path",
  "version": "3.1.2",
  "description": "Path manipulation library for JavaScript",
  "exports": {
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    },
    "./package": "./package.json",
    "./posix": "./lib/posix.js",
    "./win32": "./lib/win32.js"
  },
  "files": [
    "index.js",
    "index.d.ts",
    "binding.c",
    "binding.js",
    "CMakeLists.txt",
    "lib",
    "prebuilds",
    "NOTICE"
  ],
  "addon": true,
  "scripts": {
    "format": "prettier --write . && lunte --fix",
    "lint": "prettier --check . && lunte",
    "test": "brittle-bare --coverage test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-path.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-path/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-path#readme",
  "devDependencies": {
    "brittle": "^3.3.2",
    "cmake-bare": "^1.1.6",
    "lunte": "^1.8.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^2.0.0"
  }
}
module.exports = require.addon()
const EventEmitter = require('bare-events')
const { Duplex, isFinished, isReadable, isWritable } = require('bare-stream')
const binding = require('./binding')
const constants = require('./lib/constants')
const errors = require('./lib/errors')

const defaultReadBufferSize = 65536
const empty = Buffer.alloc(0)

const ipcHandle = Symbol.for('bare.ipc.handle')
const ipcAccept = Symbol.for('bare.ipc.accept')

module.exports = exports = class Pipe extends Duplex {
  constructor(path, opts = {}) {
    if (typeof path === 'object' && path !== null) {
      opts = path
      path = null
    }

    const {
      readBufferSize = defaultReadBufferSize,
      allowHalfOpen = true,
      eagerOpen = true,
      ipc = false
    } = opts

    validateInteger(readBufferSize, 'Read buffer size', 1, 0x7fffffff)

    super({ eagerOpen: !!eagerOpen })

    this._state = 0

    this._allowHalfOpen = !!allowHalfOpen
    this._ipc = !!ipc

    this._fd = -1
    this._path = null

    this._error = null

    this._pendingOpen = null
    this._pendingWrite = null
    this._pendingWriteBatch = null
    this._pendingWriteSegments = null
    this._pendingWriteIdx = 0
    this._pendingFinal = null
    this._pendingDestroy = null

    this._handleQueue = []
    this._handleQueueSize = 0

    this._buffer = Buffer.alloc(readBufferSize)

    this._handle = binding.init(
      this._buffer,
      this._ipc,
      this,
      noop,
      this._onconnect,
      this._onread,
      this._onwrite,
      this._onfinal,
      this._onhandle,
      this._onclose
    )

    if (typeof path === 'number') {
      this.open(path)
    } else if (typeof path === 'string') {
      this.connect(path)
    }
  }

  get connecting() {
    return (this._state & constants.state.CONNECTING) !== 0
  }

  get pending() {
    if (this._state & (constants.state.CLOSING | constants.state.CLOSED)) return true

    return (this._state & constants.state.CONNECTED) === 0
  }

  get readyState() {
    if (this._state & constants.state.CONNECTING) return 'opening'

    if (this._state & constants.state.CONNECTED) {
      const readable = (this._state & constants.state.READABLE) !== 0 && isReadable(this)
      const writable =
        (this._state & constants.state.WRITABLE) !== 0 && isWritable(this) && !isFinished(this)

      if (readable && writable) return 'open'
      if (readable) return 'readOnly'
      if (writable) return 'writeOnly'
    }

    return 'closed'
  }

  get [ipcHandle]() {
    return this._handle
  }

  connect(path, opts = {}, onconnect) {
    if ((this._state & constants.state.CLOSING) !== 0 || this._error !== null) {
      throw errors.PIPE_IS_CLOSED('Pipe is closed')
    }

    if (this._state & (constants.state.CONNECTING | constants.state.CONNECTED)) {
      throw errors.PIPE_ALREADY_CONNECTED('Pipe is already connected')
    }

    if (typeof opts === 'function') {
      onconnect = opts
      opts = {}
    }

    if (typeof path === 'object' && path !== null) {
      opts = path
      path = opts.path
    }

    validatePath(path)

    this._state |= constants.state.CONNECTING

    try {
      binding.connect(this._handle, path)

      this._path = path

      if (onconnect) this.once('connect', onconnect)
    } catch (err) {
      queueMicrotask(() => this._failConnect(err))
    }

    return this
  }

  open(fd, opts = {}, onconnect) {
    if ((this._state & constants.state.CLOSING) !== 0 || this._error !== null) {
      throw errors.PIPE_IS_CLOSED('Pipe is closed')
    }

    if (this._state & (constants.state.CONNECTING | constants.state.CONNECTED)) {
      throw errors.PIPE_ALREADY_CONNECTED('Pipe is already connected')
    }

    if (typeof opts === 'function') {
      onconnect = opts
      opts = {}
    }

    if (typeof fd === 'object' && fd !== null) {
      opts = fd
      fd = opts.fd
    }

    validateFd(fd)

    try {
      const status = binding.open(this._handle, fd)

      this._fd = fd

      this._state |= constants.state.CONNECTED

      if (status & binding.READABLE) {
        this._state |= constants.state.READABLE
      } else {
        this.push(null)
      }

      if (status & binding.WRITABLE) {
        this._state |= constants.state.WRITABLE
      } else {
        this.end()
      }

      if (onconnect) this.once('connect', onconnect)
    } catch (err) {
      this._error = err

      queueMicrotask(() => {
        if (this._pendingOpen) this._continueOpen(err)
        else this.destroy(err)
      })

      return this
    }

    this._continueOpen()

    queueMicrotask(() => {
      if (this._state & constants.state.CLOSING) return

      this.emit('connect')
    })

    return this
  }

  write(chunk, encoding, handle, cb) {
    if (typeof encoding === 'function') {
      cb = encoding
      encoding = undefined
      handle = null
    } else if (typeof encoding === 'object' && encoding !== null) {
      if (typeof handle === 'function') cb = handle
      handle = encoding
      encoding = undefined
    } else if (typeof handle === 'function') {
      cb = handle
      handle = null
    }

    if (handle) toIPCHandle(handle)

    if ((this._state & constants.state.CLOSING) === 0) {
      if (handle) this._handleQueueSize++

      this._handleQueue.push(handle || null)
    }

    if (encoding) return super.write(chunk, encoding, cb)

    return super.write(chunk, cb)
  }

  accept(target) {
    const handle = toIPCHandle(target)

    binding.accept(this._handle, handle)

    if (typeof target[ipcAccept] === 'function') target[ipcAccept]()

    return target
  }

  ref() {
    this._state &= ~constants.state.UNREFED

    if (this._state & constants.state.CLOSING) return this

    binding.ref(this._handle)

    return this
  }

  unref() {
    this._state |= constants.state.UNREFED

    if (this._state & constants.state.CLOSING) return this

    binding.unref(this._handle)

    return this
  }

  [ipcAccept]() {
    this._onaccept()
  }

  _open(cb) {
    if (this._state & constants.state.CONNECTED) return cb(null)

    this._pendingOpen = cb
  }

  _read() {
    if ((this._state & constants.state.READING) === 0) {
      this._state |= constants.state.READING

      binding.resume(this._handle)
    }
  }

  _writev(batch, cb) {
    this._pendingWrite = cb
    this._pendingWriteBatch = batch

    try {
      coerceBatch(batch)
    } catch (err) {
      return this._continueWrite(err)
    }

    if (this._handleQueueSize === 0) {
      this._handleQueue = []
      this._pendingWriteSegments = null

      try {
        binding.writev(
          this._handle,
          batch.map(({ chunk }) => chunk),
          null
        )
      } catch (err) {
        this._continueWrite(err)
      }

      return
    }

    const handles = this._handleQueue.splice(0, batch.length)

    // Fill any holes in the batch for messages that don't carry a handle. Each
    // message that does carry a handle decrements the handle queue size.
    for (let i = 0; i < batch.length; i++) {
      if (handles[i] === undefined) handles[i] = null
      else if (handles[i] !== null) this._handleQueueSize--
    }

    // A handle rides along with a single write, so the batch is split into runs
    // of handleless messages and one message per handle.
    const segments = []

    let i = 0
    while (i < batch.length) {
      if (handles[i] !== null) {
        segments.push({ chunks: [batch[i]], handle: handles[i] })
        i++
      } else {
        const start = i
        while (i < batch.length && handles[i] === null) i++
        segments.push({ chunks: batch.slice(start, i), handle: null })
      }
    }

    this._pendingWriteSegments = segments
    this._pendingWriteIdx = 0

    this._writeNextSegment()
  }

  _writeNextSegment() {
    const segment = this._pendingWriteSegments[this._pendingWriteIdx]

    const chunks = []
    for (let i = 0; i < segment.chunks.length; i++) {
      chunks.push(segment.chunks[i].chunk)
    }

    let sendHandle = null

    try {
      // The target was checked when the write was queued, but it may have been
      // invalidated since.
      if (segment.handle !== null) sendHandle = toIPCHandle(segment.handle)

      binding.writev(this._handle, chunks, sendHandle)
    } catch (err) {
      this._continueWrite(err)
    }
  }

  _final(cb) {
    const duplex = constants.state.READABLE | constants.state.WRITABLE

    if ((this._state & duplex) !== duplex) return cb(null)

    this._pendingFinal = cb

    try {
      binding.end(this._handle)
    } catch (err) {
      this._continueFinal(err.code === 'ENOTCONN' ? null : err)
    }
  }

  _predestroy() {
    if (this._state & constants.state.CLOSING) return
    this._state |= constants.state.CLOSING
    this._state &= ~constants.state.CONNECTING

    this._clearHandleQueue()

    binding.close(this._handle)
  }

  _destroy(err, cb) {
    if (this._state & constants.state.CLOSED) return cb(err)

    this._pendingDestroy = cb

    if (this._state & constants.state.CLOSING) return
    this._state |= constants.state.CLOSING
    this._state &= ~constants.state.CONNECTING

    this._clearHandleQueue()

    binding.close(this._handle)
  }

  _clearHandleQueue() {
    this._handleQueue = []
    this._handleQueueSize = 0
  }

  _continueOpen(err) {
    if (this._pendingOpen === null) return
    const cb = this._pendingOpen
    this._pendingOpen = null
    cb(err)
  }

  _continueWrite(err) {
    if (this._pendingWrite === null) return

    // A batch that carries handles is written one segment at a time, so it only
    // settles once the last segment has been written or one of them failed.
    if (this._pendingWriteSegments !== null) {
      this._pendingWriteIdx++

      if (err === null && this._pendingWriteIdx < this._pendingWriteSegments.length) {
        return this._writeNextSegment()
      }
    }

    const cb = this._pendingWrite
    this._pendingWrite = null
    this._pendingWriteBatch = null
    this._pendingWriteSegments = null
    this._pendingWriteIdx = 0
    cb(err)
  }

  _continueFinal(err) {
    if (this._pendingFinal === null) return
    const cb = this._pendingFinal
    this._pendingFinal = null
    cb(err)
  }

  _continueDestroy() {
    if (this._pendingDestroy === null) return
    const cb = this._pendingDestroy
    this._pendingDestroy = null
    cb(null)
  }

  _failConnect(err) {
    if (this._state & constants.state.CLOSING) return

    this._state &= ~constants.state.CONNECTING

    if (this._pendingOpen) this._continueOpen(err)
    else this.destroy(err)
  }

  _onconnect(err) {
    if (err) {
      this._failConnect(err)
      return
    }

    this._state |= constants.state.CONNECTED | constants.state.READABLE | constants.state.WRITABLE
    this._state &= ~constants.state.CONNECTING

    this._continueOpen()

    this.emit('connect')
  }

  _onaccept() {
    this._state |= constants.state.CONNECTED | constants.state.READABLE | constants.state.WRITABLE

    this._continueOpen()
  }

  _onread(err, read) {
    if (err) {
      this.destroy(err)
      return
    }

    if (read === 0) {
      this.push(null)
      if (this._allowHalfOpen === false) this.end()
      return
    }

    // Unpooled, as a read too small to be given a buffer of its own would
    // otherwise pin the whole pool it came from for as long as its consumer
    // holds on to it.
    const copy = Buffer.allocUnsafeSlow(read)
    copy.set(this._buffer.subarray(0, read))

    if (this.push(copy) === false && this.destroying === false) {
      this._state &= ~constants.state.READING

      binding.pause(this._handle)
    }
  }

  _onwrite(err) {
    this._continueWrite(err)
  }

  _onfinal() {
    this._continueFinal(null)
  }

  _onhandle(type) {
    this.emit('handle', type)
  }

  _onclose() {
    this._state |= constants.state.CLOSED

    this._continueOpen()
    this._continueDestroy()
  }

  _onspawn(readable, writable) {
    this._state |= constants.state.CONNECTED

    if (readable) {
      this._state |= constants.state.READABLE
    } else {
      this.push(null)
    }

    if (writable) {
      this._state |= constants.state.WRITABLE
    } else {
      this.end()
    }

    this._continueOpen()
  }
}

exports.Pipe = exports

exports.Server = class PipeServer extends EventEmitter {
  constructor(opts = {}, onconnection) {
    if (typeof opts === 'function') {
      onconnection = opts
      opts = {}
    }

    super()

    const {
      readBufferSize = defaultReadBufferSize,
      allowHalfOpen = true,
      pauseOnConnect = false,
      ipc = false
    } = opts

    validateInteger(readBufferSize, 'Read buffer size', 1, 0x7fffffff)

    this._state = 0

    this._readBufferSize = readBufferSize
    this._allowHalfOpen = !!allowHalfOpen
    this._pauseOnConnect = !!pauseOnConnect
    this._ipc = !!ipc

    this._path = null
    this._connections = new Set()

    this._error = null
    this._handle = null

    if (onconnection) this.on('connection', onconnection)
  }

  get listening() {
    return (this._state & constants.state.BOUND) !== 0
  }

  get closing() {
    return (this._state & constants.state.CLOSING) !== 0
  }

  address() {
    if ((this._state & constants.state.BOUND) === 0) return null

    return this._path
  }

  listen(path, backlog = 511, opts = {}, onlistening) {
    if (this._state & constants.state.CLOSING) {
      throw errors.SERVER_IS_CLOSED('Server is closed')
    }

    if (this._state & (constants.state.BINDING | constants.state.BOUND)) {
      throw errors.SERVER_ALREADY_LISTENING('Server is already listening')
    }

    if (typeof backlog === 'function') {
      onlistening = backlog
      backlog = 511
    } else if (typeof opts === 'function') {
      onlistening = opts
      opts = {}
    }

    if (typeof path === 'object' && path !== null) {
      opts = path
      path = opts.path
      backlog = defaultTo(opts.backlog, 511)
    }

    if (backlog === null || backlog === 0) backlog = 511

    validatePath(path)
    validateInteger(backlog, 'Backlog', 0, 0x7fffffff)

    this._state |= constants.state.BINDING
    this._state &= ~constants.state.CLOSED

    this._handle = binding.init(
      empty,
      this._ipc,
      this,
      this._onconnection,
      noop,
      noop,
      noop,
      noop,
      noop,
      this._onclose
    )

    if (this._state & constants.state.UNREFED) binding.unref(this._handle)

    try {
      binding.bind(this._handle, path, backlog)

      this._path = path

      this._state |= constants.state.BOUND
      this._state &= ~constants.state.BINDING

      if (onlistening) this.once('listening', onlistening)

      queueMicrotask(() => {
        if (this._state & constants.state.CLOSING) return

        this.emit('listening')
      })
    } catch (err) {
      this._error = err

      binding.close(this._handle)
    }

    return this
  }

  close(onclose) {
    if (this._state & constants.state.CLOSED) {
      if (onclose) queueMicrotask(onclose)

      return this
    }

    if (onclose) this.once('close', onclose)

    if (this._state & constants.state.CLOSING) return this
    this._state |= constants.state.CLOSING
    this._state &= ~(constants.state.BINDING | constants.state.BOUND)

    if (this._handle !== null) binding.close(this._handle)
    else this._closeMaybe()

    return this
  }

  ref() {
    this._state &= ~constants.state.UNREFED

    if (this._handle !== null) binding.ref(this._handle)

    return this
  }

  unref() {
    this._state |= constants.state.UNREFED

    if (this._handle !== null) binding.unref(this._handle)

    return this
  }

  _closeMaybe() {
    if ((this._state & constants.state.CLOSING) === 0) return
    if (this._state & constants.state.CLOSED) return
    if (this._handle !== null || this._connections.size > 0) return

    this._state |= constants.state.CLOSED
    this._state &= ~constants.state.CLOSING

    queueMicrotask(() => this.emit('close'))
  }

  _onconnection(err) {
    if (err) {
      this.emit('error', err)
      return
    }

    if (this._state & constants.state.CLOSING) return

    const pipe = new exports.Pipe({
      readBufferSize: this._readBufferSize,
      allowHalfOpen: this._allowHalfOpen,
      eagerOpen: !this._pauseOnConnect,
      ipc: this._ipc
    })

    try {
      binding.accept(this._handle, pipe._handle)

      pipe._path = this._path

      pipe._onaccept()

      this._connections.add(pipe)

      pipe.on('close', () => {
        this._connections.delete(pipe)
        this._closeMaybe()
      })
    } catch (err) {
      pipe.destroy()

      this.emit('error', err)
      return
    }

    this.emit('connection', pipe)
  }

  _onclose() {
    const err = this._error

    this._state &= ~(constants.state.BINDING | constants.state.BOUND)
    this._error = null
    this._handle = null
    this._path = null

    if (err) this.emit('error', err)

    this._closeMaybe()
  }
}

exports.constants = constants
exports.errors = errors

exports.createConnection = function createConnection(path, opts, onconnect) {
  if (typeof opts === 'function') {
    onconnect = opts
    opts = {}
  }

  if (typeof path === 'object' && path !== null) {
    opts = path
    path = opts.path
  }

  return new exports.Pipe(opts).connect(path, opts, onconnect)
}

exports.createServer = function createServer(opts, onconnection) {
  return new exports.Server(opts, onconnection)
}

exports.pipe = function pipe() {
  return binding.pipe()
}

function toIPCHandle(target) {
  const handle = target[ipcHandle]

  if (handle === undefined) {
    throw errors.INVALID_IPC_TARGET('Target does not implement the IPC handle protocol')
  }

  if (handle instanceof ArrayBuffer === false) {
    throw errors.INVALID_IPC_TARGET('Target does not provide a native IPC handle')
  }

  return handle
}

function validatePath(path) {
  if (typeof path !== 'string') {
    throw errors.INVALID_PATH(`Path must be a string, got ${typeof path}`)
  }

  validatePathLength(path)
}

function validatePathLength(path) {
  const length = Buffer.byteLength(path)

  if (length > constants.path.MAX_LENGTH) {
    throw errors.INVALID_PATH(
      `Path must be at most ${constants.path.MAX_LENGTH} bytes, got ${length}`
    )
  }
}

function validateFd(fd) {
  if (typeof fd !== 'number') {
    throw errors.INVALID_FD(`File descriptor must be a number, got ${typeof fd}`)
  }

  if (!Number.isInteger(fd) || fd < 0 || fd > 0x7fffffff) {
    throw errors.INVALID_FD(
      `File descriptor must be an integer between 0 and ${0x7fffffff}, got ${fd}`
    )
  }
}

function validateInteger(value, name, min, max) {
  if (typeof value !== 'number') {
    throw errors.INVALID_ARGUMENT(`${name} must be a number, got ${typeof value}`)
  }

  if (!Number.isInteger(value) || value < min || value > max) {
    throw errors.INVALID_ARGUMENT(
      `${name} must be an integer between ${min} and ${max}, got ${value}`
    )
  }
}

function coerceBatch(batch) {
  for (let i = 0; i < batch.length; i++) {
    const chunk = batch[i].chunk

    if (ArrayBuffer.isView(chunk) === false) {
      throw errors.INVALID_ARGUMENT(`Chunk must be a string or a view, got ${typeof chunk}`)
    }

    batch[i].chunk = Buffer.coerce(chunk)
  }
}

function defaultTo(value, fallback) {
  return value === undefined || value === null ? fallback : value
}

function noop() {}
const binding = require('../binding')

module.exports = {
  state: {
    CONNECTING: 0x1,
    CONNECTED: 0x2,
    BINDING: 0x4,
    BOUND: 0x8,
    READING: 0x10,
    CLOSING: 0x20,
    CLOSED: 0x40,
    UNREFED: 0x80,
    READABLE: 0x100,
    WRITABLE: 0x200
  },
  path: {
    MAX_LENGTH: binding.MAX_PATH_LENGTH
  },
  handle: {
    NAMED_PIPE: binding.UV_NAMED_PIPE,
    TCP: binding.UV_TCP,
    UDP: binding.UV_UDP
  }
}
module.exports = class PipeError extends Error {
  constructor(msg, fn = PipeError, code = fn.name) {
    super(`${code}: ${msg}`)
    this.code = code

    if (Error.captureStackTrace) {
      Error.captureStackTrace(this, fn)
    }
  }

  get name() {
    return 'PipeError'
  }

  static PIPE_ALREADY_CONNECTED(msg) {
    return new PipeError(msg, PipeError.PIPE_ALREADY_CONNECTED)
  }

  static PIPE_IS_CLOSED(msg) {
    return new PipeError(msg, PipeError.PIPE_IS_CLOSED)
  }

  static SERVER_ALREADY_LISTENING(msg) {
    return new PipeError(msg, PipeError.SERVER_ALREADY_LISTENING)
  }

  static SERVER_IS_CLOSED(msg) {
    return new PipeError(msg, PipeError.SERVER_IS_CLOSED)
  }

  static INVALID_ARGUMENT(msg) {
    return new PipeError(msg, PipeError.INVALID_ARGUMENT)
  }

  static INVALID_FD(msg) {
    return new PipeError(msg, PipeError.INVALID_FD)
  }

  static INVALID_IPC_TARGET(msg) {
    return new PipeError(msg, PipeError.INVALID_IPC_TARGET)
  }

  static INVALID_PATH(msg) {
    return new PipeError(msg, PipeError.INVALID_PATH)
  }
}
{
  "name": "bare-pipe",
  "version": "4.3.1",
  "description": "Native I/O pipes for JavaScript",
  "exports": {
    "./package": "./package.json",
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    },
    "./constants": {
      "types": "./lib/constants.d.ts",
      "default": "./lib/constants.js"
    },
    "./errors": {
      "types": "./lib/errors.d.ts",
      "default": "./lib/errors.js"
    }
  },
  "files": [
    "index.js",
    "index.d.ts",
    "binding.c",
    "binding.js",
    "CMakeLists.txt",
    "lib",
    "prebuilds"
  ],
  "addon": true,
  "scripts": {
    "format": "prettier --write . && lunte --fix",
    "lint": "prettier --check . && lunte",
    "test": "brittle-bare --coverage test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-pipe.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-pipe/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-pipe#readme",
  "engines": {
    "bare": ">=1.16.0"
  },
  "dependencies": {
    "bare-events": "^2.5.4",
    "bare-stream": "^2.6.4"
  },
  "devDependencies": {
    "bare-buffer": "^3.0.0",
    "bare-fs": "^4.0.0",
    "bare-path": "^3.0.0",
    "bare-tcp": "^2.5.4",
    "brittle": "^3.19.1",
    "cmake-bare": "^1.1.6",
    "lunte": "^1.8.0",
    "prettier": "^3.4.1",
    "prettier-config-holepunch": "^2.0.0"
  },
  "peerDependencies": {
    "bare-buffer": "*"
  },
  "peerDependenciesMeta": {
    "bare-buffer": {
      "optional": true
    }
  }
}
{
  "name": "bare-posix",
  "version": "1.0.1",
  "description": "POSIX-specific bindings for Bare",
  "exports": {
    ".": {
      "types": "./index.d.ts",
      "android": "./unsupported.js",
      "win32": "./unsupported.js",
      "default": "./index.js"
    },
    "./package": "./package.json"
  },
  "files": [
    "index.js",
    "index.d.ts",
    "unsupported.js",
    "binding.c",
    "binding.js",
    "CMakeLists.txt",
    "prebuilds"
  ],
  "addon": true,
  "scripts": {
    "format": "prettier --write .",
    "lint": "prettier --check . && lunte",
    "test": "brittle-bare --coverage test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-posix.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-posix/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-posix#readme",
  "devDependencies": {
    "brittle": "^3.19.1",
    "cmake-bare": "^1.7.7",
    "lunte": "^1.6.0",
    "prettier": "^3.8.2",
    "prettier-config-holepunch": "^2.0.0"
  }
}
exports.getgid = function getgid() {
  return -1
}

exports.setgid = function setgid() {
  throw new Error('Platform not supported')
}

exports.getegid = function getegid() {
  return -1
}

exports.setegid = function setgid() {
  throw new Error('Platform not supported')
}

exports.getuid = function getuid() {
  return -1
}

exports.setuid = function setgid() {
  throw new Error('Platform not supported')
}

exports.geteuid = function geteuid() {
  return -1
}

exports.seteuid = function setgid() {
  throw new Error('Platform not supported')
}

exports.getgroups = function getgroups() {
  return []
}

exports.getgrnam = function getgrnam() {
  return null
}

exports.getpwnam = function getpwnam() {
  return null
}
const abort = require('bare-abort')
const EventEmitter = require('bare-events')
const Signal = require('bare-signals')
const os = require('bare-os')
const env = require('bare-env')
const hrtime = require('bare-hrtime')
const stdio = require('bare-stdio')
const posix = require('bare-posix')

class Process extends EventEmitter {
  constructor() {
    super()

    this._startTime = hrtime.bigint()

    EventEmitter.forward(Bare, this, [
      'uncaughtException',
      'unhandledRejection',
      'beforeExit',
      'exit',
      'suspend',
      'wakeup',
      'idle',
      'resume'
    ])

    const signals = new Signal.Emitter()

    signals.unref()

    EventEmitter.forward(signals, this, Object.keys(os.constants.signals))
  }

  get stdin() {
    return stdio.in
  }

  get stdout() {
    return stdio.out
  }

  get stderr() {
    return stdio.err
  }

  get platform() {
    return os.platform()
  }

  get arch() {
    return os.arch()
  }

  get title() {
    return os.getProcessTitle()
  }

  set title(title) {
    os.setProcessTitle(title)
  }

  get pid() {
    return os.pid()
  }

  get ppid() {
    return os.ppid()
  }

  get argv() {
    return Bare.argv
  }

  get execArgv() {
    return []
  }

  get execPath() {
    return os.execPath()
  }

  get exitCode() {
    return Bare.exitCode
  }

  set exitCode(code) {
    Bare.exitCode = code
  }

  get version() {
    return Bare.version
  }

  get versions() {
    return Bare.versions
  }

  get env() {
    return env
  }

  get hrtime() {
    return hrtime
  }

  abort() {
    abort()
  }

  exit(code) {
    Bare.exit(code)
  }

  suspend() {
    Bare.suspend()
  }

  resume() {
    Bare.resume()
  }

  cwd() {
    return os.cwd()
  }

  chdir(directory) {
    os.chdir(directory)
  }

  kill(pid, signal) {
    os.kill(pid, signal)
  }

  uptime() {
    return Number(hrtime.bigint() - this._startTime) / 1e9
  }

  cpuUsage(previous) {
    return os.cpuUsage(previous)
  }

  threadCpuUsage(previous) {
    return os.threadCpuUsage(previous)
  }

  resourceUsage() {
    return os.resourceUsage()
  }

  availableMemory() {
    return os.availableMemory()
  }

  constrainedMemory() {
    return os.constrainedMemory()
  }

  memoryUsage() {
    return os.memoryUsage()
  }

  getgid() {
    return posix.getgid()
  }

  setgid(id) {
    return posix.setgid(id)
  }

  getegid() {
    return posix.getegid()
  }

  setegid(id) {
    return posix.setegid(id)
  }

  getuid() {
    return posix.getuid()
  }

  setuid(id) {
    return posix.setuid(id)
  }

  geteuid() {
    return posix.geteuid()
  }

  seteuid(id) {
    return posix.seteuid(id)
  }

  getgroups() {
    return posix.getgroups()
  }

  nextTick(cb, ...args) {
    queueMicrotask(cb.bind(null, ...args))
  }

  [Symbol.for('bare.inspect')]() {
    return {
      __proto__: { constructor: Process },

      platform: this.platform,
      arch: this.arch,
      title: this.title,
      pid: this.pid,
      ppid: this.ppid,
      argv: this.argv,
      execPath: this.execPath,
      exitCode: this.exitCode,
      version: this.version,
      versions: this.versions,
      env: this.env
    }
  }
}

module.exports = new Process()
{
  "name": "bare-process",
  "version": "4.5.1",
  "description": "Node.js-compatible process control for Bare",
  "exports": {
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    },
    "./global": {
      "types": "./global.d.ts",
      "default": "./global.js"
    },
    "./package": "./package.json"
  },
  "files": [
    "index.js",
    "index.d.ts",
    "global.js",
    "global.d.ts"
  ],
  "scripts": {
    "format": "prettier --write . && lunte --fix",
    "lint": "prettier --check . && lunte",
    "test": "brittle-bare --coverage test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-process.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-process/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-process#readme",
  "dependencies": {
    "bare-abort": "^2.0.13",
    "bare-env": "^3.0.0",
    "bare-events": "^2.3.1",
    "bare-hrtime": "^2.0.0",
    "bare-os": "^3.7.1",
    "bare-posix": "^1.0.1",
    "bare-signals": "^5.0.0",
    "bare-stdio": "^1.0.1"
  },
  "devDependencies": {
    "brittle": "^3.2.1",
    "lunte": "^1.6.0",
    "prettier": "^3.4.1",
    "prettier-config-holepunch": "^2.0.0"
  }
}
module.exports = require.addon()
const EventEmitter = require('bare-events')
const binding = require('./binding')
const errors = require('./lib/errors')

const signals = binding.signals

module.exports = exports = class Signal extends EventEmitter {
  constructor(signum) {
    super()

    if (typeof signum === 'string') {
      if (signum in signals === false) {
        throw errors.UNKNOWN_SIGNAL(`Unknown signal '${signum}'`)
      }

      signum = signals[signum]
    }

    this._signum = signum
    this._closing = null
    this._handle = binding.init(this, this._onsignal, this._onclose)
  }

  start() {
    if (this._closing) throw errors.SIGNAL_CLOSED('Signal is closed')
    binding.start(this._handle, this._signum)
    return this
  }

  stop() {
    if (this._closing) return this
    binding.stop(this._handle)
    return this
  }

  ref() {
    if (this._closing) return this
    binding.ref(this._handle)
    return this
  }

  unref() {
    if (this._closing) return this
    binding.unref(this._handle)
    return this
  }

  close() {
    if (this._closing) return this._closing
    this._closing = EventEmitter.once(this, 'close')

    binding.close(this._handle)

    return this._closing
  }

  _onsignal() {
    this.emit('signal', this._signum)
  }

  _onclose() {
    this._handle = null

    this.emit('close')
  }
}

exports.Emitter = require('./lib/emitter')

exports.constants = signals
exports.errors = errors
const EventEmitter = require('bare-events')
const Signal = require('..')

module.exports = class SignalEmitter extends EventEmitter {
  constructor() {
    super()

    this._signals = new Map()
    this._unrefed = false

    this.on('newListener', this._onnewlistener).on('removeListener', this._onremovelistener)
  }

  ref() {
    this._unrefed = false
    for (const signal of this._signals.values()) signal.ref()
    return this
  }

  unref() {
    this._unrefed = true
    for (const signal of this._signals.values()) signal.unref()
    return this
  }

  _onnewlistener(name) {
    if (name === 'newListener' || name === 'removeListener') return

    if (this.listenerCount(name) === 0) {
      const signal = new Signal(name)

      signal.on('signal', this._onsignal.bind(this, name)).start()

      if (this._unrefed) signal.unref()

      this._signals.set(name, signal)
    }
  }

  _onremovelistener(name) {
    if (name === 'newListener' || name === 'removeListener') return

    if (this.listenerCount(name) === 0) {
      const signal = this._signals.get(name)

      if (this._unrefed) signal.ref()

      signal.close()

      this._signals.delete(name)
    }
  }

  _onsignal(name) {
    this.emit(name, name, Signal.constants[name])
  }
}
module.exports = class SignalError extends Error {
  constructor(msg, fn = SignalError, code = fn.name) {
    super(`${code}: ${msg}`)
    this.code = code

    if (Error.captureStackTrace) {
      Error.captureStackTrace(this, fn)
    }
  }

  get name() {
    return 'SignalError'
  }

  static UNKNOWN_SIGNAL(msg) {
    return new SignalError(msg, SignalError.UNKNOWN_SIGNAL)
  }

  static SIGNAL_CLOSED(msg) {
    return new SignalError(msg, SignalError.SIGNAL_CLOSED)
  }
}
{
  "name": "bare-signals",
  "version": "5.0.0",
  "description": "Native signal handling for JavaScript",
  "exports": {
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    },
    "./package": "./package.json",
    "./errors": "./lib/errors.js",
    "./emitter": "./lib/emitter.js"
  },
  "files": [
    "index.js",
    "index.d.ts",
    "binding.c",
    "binding.js",
    "CMakeLists.txt",
    "lib",
    "prebuilds"
  ],
  "addon": true,
  "scripts": {
    "format": "prettier --write . && lunte --fix",
    "lint": "prettier --check . && lunte",
    "test": "brittle-bare --coverage test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-signals.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-signals/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-signals#readme",
  "engines": {
    "bare": ">=1.7.0"
  },
  "dependencies": {
    "bare-events": "^2.5.3"
  },
  "devDependencies": {
    "bare-os": "^3.3.1",
    "brittle": "^3.2.1",
    "cmake-bare": "^1.1.6",
    "lunte": "^1.8.3",
    "prettier": "^3.4.1",
    "prettier-config-holepunch": "^2.0.0"
  }
}
module.exports = require.addon()
const fs = require('bare-fs')
const tty = require('bare-tty')
const Pipe = require('bare-pipe')
const binding = require('./binding')

const { TTY, NAMED_PIPE } = binding

class IO {
  constructor() {
    this._in = null
    this._out = null
    this._err = null
  }

  get in() {
    if (this._in === null) {
      switch (binding.guessType(0)) {
        case TTY:
          this._in = new tty.ReadStream(0)
          break
        case NAMED_PIPE:
          this._in = new Pipe(0, { eagerOpen: false })
          break
        default:
          this._in = fs.createReadStream(null, { fd: 0, eagerOpen: false })
      }
    }

    return this._in
  }

  get out() {
    if (this._out === null) {
      switch (binding.guessType(1)) {
        case TTY:
          this._out = new tty.WriteStream(1)
          break
        case NAMED_PIPE:
          this._out = new Pipe(1, { eagerOpen: false })
          this._out.unref()
          break
        default:
          this._out = fs.createWriteStream(null, { fd: 1, eagerOpen: false })
      }
    }

    return this._out
  }

  get err() {
    if (this._err === null) {
      switch (binding.guessType(2)) {
        case TTY:
          this._err = new tty.WriteStream(2)
          break
        case NAMED_PIPE:
          this._err = new Pipe(2, { eagerOpen: false })
          this._err.unref()
          break
        default:
          this._err = fs.createWriteStream(null, { fd: 2, eagerOpen: false })
      }
    }

    return this._err
  }
}

module.exports = new IO()
{
  "name": "bare-stdio",
  "version": "1.0.3",
  "description": "Standard input/output streams for Bare",
  "exports": {
    "./package": "./package.json",
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    }
  },
  "files": [
    "index.js",
    "index.d.ts",
    "binding.c",
    "binding.js",
    "CMakeLists.txt",
    "prebuilds"
  ],
  "addon": true,
  "scripts": {
    "format": "prettier --write . && lunte --fix",
    "lint": "prettier --check . && lunte",
    "test": "brittle-bare --coverage test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-stdio.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-stdio/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-stdio#readme",
  "dependencies": {
    "bare-fs": "^4.5.2",
    "bare-pipe": "^4.1.5",
    "bare-tty": "^5.0.3"
  },
  "devDependencies": {
    "bare-os": "^3.6.2",
    "bare-path": "^3.0.0",
    "bare-subprocess": "^6.0.0",
    "brittle": "^3.19.1",
    "cmake-bare": "^1.7.7",
    "lunte": "^1.4.0",
    "prettier": "^3.8.1",
    "prettier-config-holepunch": "^2.0.0",
    "tt-native": "^1.1.1"
  }
}
const b4a = require('b4a')
const stream = require('streamx')
const { ReadableStream, WritableStream } = require('./web')

const defaultEncoding = 'utf8'

module.exports = exports = stream.Stream

exports.pipeline = stream.pipeline

exports.isStream = stream.isStream
exports.isEnding = stream.isEnding
exports.isEnded = stream.isEnded
exports.isFinishing = stream.isFinishing
exports.isFinished = stream.isFinished
exports.isDisturbed = stream.isDisturbed

exports.isErrored = function isErrored(stream) {
  return exports.getStreamError(stream) !== null
}

exports.isReadable = function isReadable(stream) {
  return stream.readable && !stream.destroying && !exports.isEnded(stream)
}

exports.isWritable = function isWritable(stream) {
  return stream.writable && !stream.destroying && !exports.isFinishing(stream)
}

exports.getStreamError = stream.getStreamError

exports.addAbortSignal = function addAbortSignal(signal, stream) {
  function onAbort() {
    stream.destroy(signal.reason)
  }

  if (signal.aborted) onAbort()
  else signal.addEventListener('abort', onAbort)

  return stream
}

exports.Stream = exports

exports.Readable = class Readable extends stream.Readable {
  constructor(opts = {}) {
    super({
      ...opts,
      byteLength: null,
      byteLengthReadable: null,
      map: null,
      mapReadable: null
    })

    if (this._construct) this._open = this._construct

    if (this._read !== stream.Readable.prototype._read) {
      this._read = read.bind(this, this._read)
    }

    if (this._destroy !== stream.Stream.prototype._destroy) {
      this._destroy = destroy.bind(this, this._destroy)
    }
  }

  get closed() {
    return !exports.isReadable(this)
  }

  get errored() {
    return stream.getStreamError(this)
  }

  push(chunk, encoding) {
    if (typeof chunk === 'string') {
      chunk = b4a.from(chunk, encoding || defaultEncoding)
    }

    return super.push(chunk)
  }

  unshift(chunk, encoding) {
    if (typeof chunk === 'string') {
      chunk = b4a.from(chunk, encoding || defaultEncoding)
    }

    super.unshift(chunk)
  }

  static fromWeb(readableStream, opts = {}) {
    const stream = readableStream._stream

    if (opts.encoding) stream.setEncoding(opts.encoding)
    if (opts.signal) exports.addAbortSignal(opts.signal, stream)

    return stream
  }

  static toWeb(readable, opts = {}) {
    return new ReadableStream(readable, opts.strategy)
  }

  async [Symbol.asyncDispose]() {
    if (!this.destroyed) this.destroy()

    await new Promise((resolve) => exports.finished(this, resolve))
  }
}

exports.Writable = class Writable extends stream.Writable {
  constructor(opts = {}) {
    super({
      ...opts,
      byteLength: null,
      byteLengthWritable,
      map: null,
      mapWritable: null
    })

    if (this._construct) this._open = this._construct

    if (this._write !== stream.Writable.prototype._write) {
      this._write = write.bind(this, this._write)
    }

    if (this._destroy !== stream.Stream.prototype._destroy) {
      this._destroy = destroy.bind(this, this._destroy)
    }
  }

  get closed() {
    return !exports.isWritable(this)
  }

  get errored() {
    return stream.getStreamError(this)
  }

  write(chunk, encoding, cb) {
    if (typeof encoding === 'function') {
      cb = encoding
      encoding = null
    }

    if (typeof chunk === 'string') {
      encoding = encoding || defaultEncoding
      chunk = b4a.from(chunk, encoding)
    } else {
      encoding = 'buffer'
    }

    const result = super.write({ chunk, encoding })

    if (cb) stream.Writable.drained(this).then(() => cb(null), cb)

    return result
  }

  end(chunk, encoding, cb) {
    if (typeof chunk === 'function') {
      cb = chunk
      chunk = null
    } else if (typeof encoding === 'function') {
      cb = encoding
      encoding = null
    }

    if (typeof chunk === 'string') {
      encoding = encoding || defaultEncoding
      chunk = b4a.from(chunk, encoding || defaultEncoding)
    } else {
      encoding = 'buffer'
    }

    const result =
      chunk !== undefined && chunk !== null ? super.end({ chunk, encoding }) : super.end()

    if (cb) this.once('finish', () => cb(null))

    return result
  }

  static fromWeb(writableStream, opts = {}) {
    const stream = writableStream._stream

    if (opts.signal) exports.addAbortSignal(opts.signal, stream)

    return stream
  }

  static toWeb(writable) {
    return new WritableStream(writable)
  }

  async [Symbol.asyncDispose]() {
    if (!this.destroyed) this.destroy()

    await new Promise((resolve) => exports.finished(this, resolve))
  }
}

exports.Duplex = class Duplex extends stream.Duplex {
  constructor(opts = {}) {
    super({
      ...opts,
      byteLength: null,
      byteLengthReadable: null,
      byteLengthWritable,
      map: null,
      mapReadable: null,
      mapWritable: null
    })

    if (this._construct) this._open = this._construct

    if (this._read !== stream.Readable.prototype._read) {
      this._read = read.bind(this, this._read)
    }

    if (this._write !== stream.Duplex.prototype._write) {
      this._write = write.bind(this, this._write)
    }

    if (this._destroy !== stream.Stream.prototype._destroy) {
      this._destroy = destroy.bind(this, this._destroy)
    }
  }

  push(chunk, encoding) {
    if (typeof chunk === 'string') {
      chunk = b4a.from(chunk, encoding || defaultEncoding)
    }

    return super.push(chunk)
  }

  unshift(chunk, encoding) {
    if (typeof chunk === 'string') {
      chunk = b4a.from(chunk, encoding || defaultEncoding)
    }

    super.unshift(chunk)
  }

  write(chunk, encoding, cb) {
    if (typeof encoding === 'function') {
      cb = encoding
      encoding = null
    }

    if (typeof chunk === 'string') {
      encoding = encoding || defaultEncoding
      chunk = b4a.from(chunk, encoding)
    } else {
      encoding = 'buffer'
    }

    const result = super.write({ chunk, encoding })

    if (cb) stream.Writable.drained(this).then(() => cb(null), cb)

    return result
  }

  end(chunk, encoding, cb) {
    if (typeof chunk === 'function') {
      cb = chunk
      chunk = null
    } else if (typeof encoding === 'function') {
      cb = encoding
      encoding = null
    }

    if (typeof chunk === 'string') {
      encoding = encoding || defaultEncoding
      chunk = b4a.from(chunk, encoding)
    } else {
      encoding = 'buffer'
    }

    const result =
      chunk !== undefined && chunk !== null ? super.end({ chunk, encoding }) : super.end()

    if (cb) this.once('finish', () => cb(null))

    return result
  }

  static fromWeb({ readable: readableStream, writable: writableStream }, opts) {
    const readable = exports.Readable.fromWeb(readableStream, opts)
    const writable = exports.Writable.fromWeb(writableStream, opts)

    const duplex = new exports.Duplex({
      write(data, encoding, cb) {
        writable.write(data, encoding, cb)
      }
    })

    readable
      .on('data', (data) => duplex.push(data))
      .on('end', () => duplex.push(null))
      .on('error', (err) => duplex.destroy(err))

    writable.on('finish', () => duplex.end()).on('error', (err) => duplex.destroy(err))

    return duplex
  }

  static toWeb(duplex) {
    const readableStream = exports.Readable.toWeb(duplex)
    const writableStream = exports.Writable.toWeb(duplex)

    return { readable: readableStream, writable: writableStream }
  }
}

class DuplexSide extends exports.Duplex {
  constructor(opts) {
    super(opts)

    this._otherSide = null
    this._cb = null
  }

  _read() {
    const cb = this._cb
    if (!cb) return

    this._cb = null
    cb()
  }

  _write(chunk, encoding, cb) {
    this._otherSide.push(chunk, encoding)
    this._otherSide._cb = cb
  }

  _final(cb) {
    this._otherSide.on('end', cb)
    this._otherSide.push(null)
  }
}

exports.duplexPair = function duplexPair(opts) {
  const sideA = new DuplexSide(opts)
  const sideB = new DuplexSide(opts)

  sideA._otherSide = sideB
  sideB._otherSide = sideA

  return [sideA, sideB]
}

exports.Transform = class Transform extends stream.Transform {
  constructor(opts = {}) {
    super({
      ...opts,
      byteLength: null,
      byteLengthReadable: null,
      byteLengthWritable,
      map: null,
      mapReadable: null,
      mapWritable: null
    })

    if (this._transform !== stream.Transform.prototype._transform) {
      this._transform = transform.bind(this, this._transform)
    } else {
      this._transform = passthrough
    }
  }

  push(chunk, encoding) {
    if (typeof chunk === 'string') {
      chunk = b4a.from(chunk, encoding || defaultEncoding)
    }

    return super.push(chunk)
  }

  unshift(chunk, encoding) {
    if (typeof chunk === 'string') {
      chunk = b4a.from(chunk, encoding || defaultEncoding)
    }

    super.unshift(chunk)
  }

  write(chunk, encoding, cb) {
    if (typeof encoding === 'function') {
      cb = encoding
      encoding = null
    }

    if (typeof chunk === 'string') {
      encoding = encoding || defaultEncoding
      chunk = b4a.from(chunk, encoding)
    } else {
      encoding = 'buffer'
    }

    const result = super.write({ chunk, encoding })

    if (cb) stream.Writable.drained(this).then(() => cb(null), cb)

    return result
  }

  end(chunk, encoding, cb) {
    if (typeof chunk === 'function') {
      cb = chunk
      chunk = null
    } else if (typeof encoding === 'function') {
      cb = encoding
      encoding = null
    }

    if (typeof chunk === 'string') {
      encoding = encoding || defaultEncoding
      chunk = b4a.from(chunk, encoding)
    } else {
      encoding = 'buffer'
    }

    const result =
      chunk !== undefined && chunk !== null ? super.end({ chunk, encoding }) : super.end()

    if (cb) this.once('finish', () => cb(null))

    return result
  }
}

exports.PassThrough = class PassThrough extends exports.Transform {}

exports.finished = function finished(stream, opts, cb) {
  if (typeof opts === 'function') {
    cb = opts
    opts = {}
  }

  if (!opts) opts = {}

  const { cleanup = false } = opts

  const done = () => {
    cb(exports.getStreamError(stream, { all: true }))

    if (cleanup) detach()
  }

  const detach = () => {
    stream.off('close', done)
    stream.off('error', noop)
  }

  if (stream.destroyed) {
    done()
  } else {
    stream.on('close', done)
    stream.on('error', noop)
  }

  return detach
}

function read(read, cb) {
  read.call(this, 65536)

  cb(null)
}

function write(write, data, cb) {
  write.call(this, data.chunk, data.encoding, cb)
}

function transform(transform, data, cb) {
  transform.call(this, data.chunk, data.encoding, cb)
}

function destroy(destroy, cb) {
  destroy.call(this, exports.getStreamError(this), cb)
}

function passthrough(data, cb) {
  cb(null, data.chunk)
}

function byteLengthWritable(data) {
  return data.chunk.byteLength
}

function noop() {}
{
  "name": "bare-stream",
  "version": "2.13.4",
  "description": "Streaming data for JavaScript",
  "exports": {
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    },
    "./package": "./package.json",
    "./promises": "./promises.js",
    "./web": {
      "types": "./web.d.ts",
      "default": "./web.js"
    },
    "./global": {
      "types": "./global.d.ts",
      "default": "./global.js"
    }
  },
  "files": [
    "index.js",
    "index.d.ts",
    "promises.js",
    "web.js",
    "web.d.ts",
    "global.js",
    "global.d.ts"
  ],
  "scripts": {
    "format": "prettier --write . && lunte --fix",
    "lint": "prettier --check . && lunte",
    "test": "brittle-bare --coverage test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-stream.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-stream/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-stream#readme",
  "dependencies": {
    "b4a": "^1.8.1",
    "streamx": "^2.25.0",
    "teex": "^1.0.1"
  },
  "devDependencies": {
    "bare-abort-controller": "^1.1.0",
    "bare-buffer": "^3.0.0",
    "bare-events": "^2.5.4",
    "brittle": "^3.5.2",
    "lunte": "^1.6.0",
    "prettier": "^3.3.3",
    "prettier-config-holepunch": "^2.0.0"
  },
  "peerDependencies": {
    "bare-abort-controller": "*",
    "bare-buffer": "*",
    "bare-events": "*"
  },
  "peerDependenciesMeta": {
    "bare-abort-controller": {
      "optional": true
    },
    "bare-buffer": {
      "optional": true
    },
    "bare-events": {
      "optional": true
    }
  }
}
const { Readable, Writable, Transform, getStreamError, isStreamx, isDisturbed } = require('streamx')
const tee = require('teex')

const readableKind = Symbol.for('bare.stream.readable.kind')
const writableKind = Symbol.for('bare.stream.writable.kind')
const transformKind = Symbol.for('bare.stream.transform.kind')

// https://streams.spec.whatwg.org/#readablestreamdefaultreader
exports.ReadableStreamDefaultReader = class ReadableStreamDefaultReader {
  constructor(stream) {
    this._stream = stream
    this._stream._stream.once('close', onclose).once('error', onerror)

    const closed = Promise.withResolvers()

    // Avoid unhandled exceptions
    closed.promise.catch(noop)

    this._closed = closed

    function onclose() {
      closed.resolve()
    }

    function onerror(err) {
      closed.reject(err)
    }
  }

  get closed() {
    return this._closed.promise
  }

  read() {
    const stream = this._stream._stream

    return new Promise((resolve, reject) => {
      const err = getStreamError(stream)

      if (err) return reject(err)

      if (stream.destroyed) {
        return resolve({ value: undefined, done: true })
      }

      const value = stream.read()

      if (value !== null) {
        return resolve({ value, done: false })
      }

      stream.once('readable', onreadable).once('close', onclose).once('error', onerror)

      function onreadable() {
        const value = stream.read()

        ondone(null, value === null ? { value: undefined, done: true } : { value, done: false })
      }

      function onclose() {
        ondone(null, { value: undefined, done: true })
      }

      function onerror(err) {
        ondone(err, null)
      }

      function ondone(err, value) {
        stream.off('readable', onreadable).off('close', onclose).off('error', onerror)

        if (err) reject(err)
        else resolve(value)
      }
    })
  }

  releaseLock() {
    this._closed.reject(new TypeError('Reader was released'))
    this._stream._releaseLock()
    this._stream = null
  }

  cancel(reason = new TypeError('Stream was cancelled')) {
    const stream = this._stream._stream

    if (stream.destroyed) return Promise.resolve()

    return new Promise((resolve) =>
      stream.once('close', resolve).once('error', noop).destroy(reason)
    )
  }
}

// https://streams.spec.whatwg.org/#readablestreamdefaultcontroller
exports.ReadableStreamDefaultController = class ReadableStreamDefaultController {
  constructor(stream) {
    this._stream = stream
  }

  get desiredSize() {
    const stream = this._stream._stream

    return stream._readableState.highWaterMark - stream._readableState.buffered
  }

  enqueue(data) {
    this._stream._stream.push(data)
  }

  close() {
    this._stream._stream.push(null)
  }

  error(err) {
    this._stream._stream.destroy(err)
  }
}

// https://streams.spec.whatwg.org/#readablestream
class ReadableStream {
  static get [readableKind]() {
    return 0 // Compatibility version
  }

  static from(iterable) {
    return new ReadableStream(Readable.from(iterable))
  }

  constructor(underlyingSource = {}, queuingStrategy) {
    if (isStreamx(underlyingSource)) {
      this._stream = underlyingSource
    } else {
      if (queuingStrategy === undefined) {
        queuingStrategy = new exports.CountQueuingStrategy()
      }

      const { start, pull, cancel } = underlyingSource
      const { highWaterMark = 1, size = defaultSize } = queuingStrategy

      this._stream = new Readable({ highWaterMark, byteLength: size })

      const controller = new exports.ReadableStreamDefaultController(this)

      try {
        let starting = Promise.resolve()

        if (start) starting = forwardError(start.call(this, controller), controller)

        if (pull) {
          this._stream._read = this._read.bind(this, starting, pull.bind(this, controller))
        }

        if (cancel) {
          this._stream.once('error', cancel.bind(this))
        }
      } catch (err) {
        controller.error(err)
      }
    }

    this._reader = null
  }

  get [readableKind]() {
    return ReadableStream[readableKind]
  }

  get locked() {
    return this._reader !== null
  }

  getReader() {
    if (this.locked) throw new TypeError('Stream is locked')

    this._reader = new exports.ReadableStreamDefaultReader(this)

    return this._reader
  }

  cancel(reason = new TypeError('Stream was cancelled')) {
    const stream = this._stream

    if (stream.destroyed) return Promise.resolve()

    if (this.locked) return Promise.reject(new TypeError('Stream is locked'))

    return new Promise((resolve) =>
      stream.once('close', resolve).once('error', noop).destroy(reason)
    )
  }

  tee() {
    const [a, b] = tee(this._stream)

    return [new ReadableStream(a), new ReadableStream(b)]
  }

  pipeTo(destination) {
    return new Promise((resolve, reject) =>
      this._stream.pipe(destination._stream, (err) => {
        err ? reject(err) : resolve()
      })
    )
  }

  [Symbol.asyncIterator]() {
    return this._stream[Symbol.asyncIterator]()
  }

  _releaseLock() {
    this._reader = null
  }

  async _read(starting, pull, cb) {
    await starting

    let err = null
    try {
      await pull()
    } catch (e) {
      err = e
    }
    cb(err)
  }
}

function defaultSize() {
  return 1
}

exports.ReadableStream = ReadableStream

// https://streams.spec.whatwg.org/#countqueuingstrategy
exports.CountQueuingStrategy = class CountQueuingStrategy {
  constructor(opts = {}) {
    const { highWaterMark = 1 } = opts

    this.highWaterMark = highWaterMark
  }

  size(chunk) {
    return 1
  }
}

// https://streams.spec.whatwg.org/#bytelengthqueuingstrategy
exports.ByteLengthQueuingStrategy = class ByteLengthQueuingStrategy {
  constructor(opts = {}) {
    const { highWaterMark = 16384 } = opts

    this.highWaterMark = highWaterMark
  }

  size(chunk) {
    return chunk.byteLength
  }
}

exports.isReadableStream = function isReadableStream(value) {
  if (value instanceof ReadableStream) return true

  return (
    typeof value === 'object' &&
    value !== null &&
    value[readableKind] === ReadableStream[readableKind]
  )
}

// https://streams.spec.whatwg.org/#readablestream-errored
exports.isReadableStreamErrored = function isReadableStreamErrored(stream) {
  return getStreamError(stream._stream) !== null
}

// https://streams.spec.whatwg.org/#is-readable-stream-disturbed
exports.isReadableStreamDisturbed = function isReadableStreamDisturbed(stream) {
  return isDisturbed(stream._stream)
}

// https://streams.spec.whatwg.org/#writablestreamdefaultwriter
exports.WritableStreamDefaultWriter = class WritableStreamDefaultWriter {
  constructor(stream) {
    this._stream = stream
    this._stream._stream.once('close', onclose).once('error', onerror)

    const closed = Promise.withResolvers()

    // Avoid unhandled exceptions
    closed.promise.catch(noop)

    this._closed = closed

    function onclose() {
      closed.resolve()
    }

    function onerror(err) {
      closed.reject(err)
    }
  }

  get desiredSize() {
    const stream = this._stream._stream

    return stream._writableState.highWaterMark - stream._writableState.buffered
  }

  get closed() {
    return this._closed.promise
  }

  get ready() {
    const stream = this._stream._stream

    if (getStreamError(stream)) return Promise.reject()

    return Writable.drained(stream).then()
  }

  async write(chunk) {
    const stream = this._stream._stream

    let err = getStreamError(stream)
    if (err) return Promise.reject(err)

    stream.write(chunk)

    await Writable.drained(stream)

    err = getStreamError(stream)
    if (err) return Promise.reject(err)
  }

  releaseLock() {
    this._closed.reject(new TypeError('Writer was released'))
    this._stream._releaseLock()
    this._stream = null
  }

  close() {
    const stream = this._stream._stream

    if (stream.destroyed) return Promise.resolve()

    return new Promise((resolve) => stream.once('close', resolve).end())
  }

  abort(reason = new TypeError('Stream was aborted')) {
    const stream = this._stream._stream

    if (stream.destroyed) return Promise.resolve()

    return new Promise((resolve) => stream.once('close', resolve).destroy(reason))
  }
}

// https://streams.spec.whatwg.org/#writablestreamdefaultcontroller
exports.WritableStreamDefaultController = class WritableStreamDefaultController {
  constructor(stream) {
    this._stream = stream
  }

  error(err) {
    this._stream._stream.destroy(err)
  }
}

// https://streams.spec.whatwg.org/#writablestream
class WritableStream {
  static get [writableKind]() {
    return 0 // Compatibility version
  }

  constructor(underlyingSink = {}, queuingStrategy = {}) {
    if (isStreamx(underlyingSink)) {
      this._stream = underlyingSink
    } else {
      if (queuingStrategy === undefined) {
        queuingStrategy = new exports.CountQueuingStrategy()
      }

      const { start, write, close, abort } = underlyingSink
      const { highWaterMark = 1, size = defaultSize } = queuingStrategy

      this._stream = new Writable({ highWaterMark, byteLength: size })

      const controller = new exports.WritableStreamDefaultController(this)

      this._controller = controller

      try {
        let starting = Promise.resolve()

        if (start) starting = forwardError(start.call(this, controller), controller)

        if (write) {
          this._stream._write = this._write.bind(this, starting, write.bind(this))
        }

        if (close) {
          this._stream._destroy = this._destroy.bind(this, close.call(this))
        }

        if (abort) {
          this._stream.once('error', abort.bind(this))
        }
      } catch (err) {
        controller.error(err)
      }
    }

    this._writer = null
  }

  get [writableKind]() {
    return WritableStream[writableKind]
  }

  get locked() {
    return this._writer !== null
  }

  getWriter() {
    if (this.locked) throw new TypeError('Stream is locked')

    this._writer = new exports.WritableStreamDefaultWriter(this)

    return this._writer
  }

  abort(reason = new TypeError('Stream was aborted')) {
    if (this._stream.destroyed) return Promise.resolve()

    if (this.locked) return Promise.reject(new TypeError('Stream is locked'))

    return new Promise((resolve) => this._stream.once('close', resolve).destroy(reason))
  }

  close() {
    if (this._stream.destroyed) return Promise.resolve()

    if (this.locked) return Promise.reject(new TypeError('Stream is locked'))

    return new Promise((resolve) => this._stream.once('close', resolve).end())
  }

  _releaseLock() {
    this._writer = null
  }

  async _write(starting, write, data, cb) {
    await starting

    let err = null
    try {
      await write(data, this._controller)
    } catch (e) {
      err = e
    }
    cb(err)
  }

  async _destroy(closing, cb) {
    let err = null
    try {
      await closing
    } catch (e) {
      err = e
    }
    cb(err)
  }
}

exports.WritableStream = WritableStream

exports.isWritableStream = function isWritableStream(value) {
  if (value instanceof WritableStream) return true

  return (
    typeof value === 'object' &&
    value !== null &&
    value[writableKind] === WritableStream[writableKind]
  )
}

// https://streams.spec.whatwg.org/#transformstreamdefaultcontroller
exports.TransformStreamDefaultController = class TransformStreamDefaultController {
  constructor(stream) {
    this._stream = stream
  }

  get desiredSize() {
    const stream = this._stream._stream

    return stream._readableState.highWaterMark - stream._readableState.buffered
  }

  enqueue(data) {
    this._stream._stream.push(data)
  }

  error(err) {
    this._stream._stream.destroy(err)
  }

  terminate() {
    const stream = this._stream._stream

    stream.push(null)
    stream.destroy(new TypeError('Stream has been terminated'))
  }
}

// https://streams.spec.whatwg.org/#transformstream
class TransformStream {
  static get [transformKind]() {
    return 0 // Compatibility version
  }

  constructor(transformer = {}, writableStrategy = {}, readableStrategy = {}) {
    if (isStreamx(transformer)) {
      this._stream = transformer
    } else {
      const { start, transform, flush } = transformer

      this._stream = new Transform({ ...writableStrategy, ...readableStrategy })

      const controller = new exports.TransformStreamDefaultController(this)

      this._controller = controller

      try {
        let starting = Promise.resolve()

        if (start) starting = forwardError(start.call(this, controller), controller)

        if (transform) {
          this._stream._transform = this._transform.bind(this, starting, transform.bind(this))
        }

        if (flush) {
          this._stream._flush = this._flush.bind(this, flush.call(this, this._controller))
        }
      } catch (err) {
        controller.error(err)
      }
    }

    this._writable = new WritableStream(this._stream)
    this._readable = new ReadableStream(this._stream)
  }

  get [transformKind]() {
    return TransformStream[transformKind]
  }

  get writable() {
    return this._writable
  }

  get readable() {
    return this._readable
  }

  async _transform(starting, transform, data, cb) {
    await starting

    let err = null
    try {
      await transform(data, this._controller)
    } catch (e) {
      err = e
    }
    cb(err)
  }

  async _flush(flush, cb) {
    let err = null
    try {
      await flush
    } catch (e) {
      err = e
    }
    cb(err)
  }
}

exports.TransformStream = TransformStream

exports.isTransformStream = function isTransformStream(value) {
  if (value instanceof TransformStream) return true

  return (
    typeof value === 'object' &&
    value !== null &&
    value[transformKind] === TransformStream[transformKind]
  )
}

async function forwardError(promise, controller) {
  try {
    await promise
  } catch (err) {
    controller.error(err)
  }
}

function noop() {}
module.exports = require.addon()
const EventEmitter = require('bare-events')
const { Duplex, isFinished, isReadable, isWritable } = require('bare-stream')
const dns = require('bare-dns')
const binding = require('./binding')
const constants = require('./lib/constants')
const errors = require('./lib/errors')
const ip = require('./lib/ip')

const defaultReadBufferSize = 65536
const empty = Buffer.alloc(0)

const ipcHandle = Symbol.for('bare.ipc.handle')
const ipcAccept = Symbol.for('bare.ipc.accept')

exports.Socket = class TCPSocket extends Duplex {
  constructor(opts = {}) {
    const { readBufferSize = defaultReadBufferSize, allowHalfOpen = true, eagerOpen = true } = opts

    validateInteger(readBufferSize, 'Read buffer size', 1, 0x7fffffff)

    super({ eagerOpen: !!eagerOpen })

    this._state = 0

    this._allowHalfOpen = !!allowHalfOpen

    this._keepAlive = false
    this._keepAliveInitialDelay = 0
    this._noDelay = false

    this._localAddress = null
    this._remoteAddress = null

    this._pendingOpen = null
    this._pendingWrite = null
    this._pendingWriteBatch = null
    this._pendingFinal = null
    this._pendingDestroy = null

    this._timer = null
    this._timeout = 0

    this._buffer = Buffer.alloc(readBufferSize)

    this._addresses = null
    this._errors = null

    this._handle = binding.init(
      this._buffer,
      this,
      noop,
      this._onconnect,
      this._onreset,
      this._onread,
      this._onwrite,
      this._onfinal,
      this._onclose
    )
  }

  get connecting() {
    return (this._state & constants.state.CONNECTING) !== 0
  }

  get pending() {
    if (this._state & (constants.state.CLOSING | constants.state.CLOSED)) return true

    return (this._state & constants.state.CONNECTED) === 0
  }

  get timeout() {
    return this._timeout || undefined // For Node.js compatibility
  }

  get readyState() {
    if (this._state & constants.state.CONNECTING) return 'opening'

    if (this._state & constants.state.CONNECTED) {
      const readable = isReadable(this)
      const writable = isWritable(this) && !isFinished(this)

      if (readable && writable) return 'open'
      if (readable) return 'readOnly'
      if (writable) return 'writeOnly'
    }

    return 'closed'
  }

  get keepAlive() {
    return this._keepAlive
  }

  get keepAliveInitialDelay() {
    return this._keepAliveInitialDelay
  }

  get noDelay() {
    return this._noDelay
  }

  get localAddress() {
    if (this._localAddress) return this._localAddress.address
  }

  get localFamily() {
    if (this._localAddress) return `IPv${this._localAddress.family}`
  }

  get localPort() {
    if (this._localAddress) return this._localAddress.port
  }

  get remoteAddress() {
    if (this._remoteAddress) return this._remoteAddress.address
  }

  get remoteFamily() {
    if (this._remoteAddress) return `IPv${this._remoteAddress.family}`
  }

  get remotePort() {
    if (this._remoteAddress) return this._remoteAddress.port
  }

  get [ipcHandle]() {
    return this._handle
  }

  address() {
    if (this._localAddress === null) return null

    const { address, family, port } = this._localAddress

    return { address, family: `IPv${family}`, port }
  }

  connect(port, host = 'localhost', opts = {}, onconnect) {
    if (this._state & constants.state.CLOSING) {
      throw errors.SOCKET_IS_CLOSED('Socket is closed')
    }

    if (this._state & (constants.state.CONNECTING | constants.state.CONNECTED)) {
      throw errors.SOCKET_ALREADY_CONNECTED('Socket is already connected')
    }

    if (typeof host === 'function') {
      onconnect = host
      host = 'localhost'
    } else if (typeof opts === 'function') {
      onconnect = opts
      opts = {}
    }

    if (typeof port === 'object' && port !== null) {
      opts = port
      port = defaultTo(opts.port, 0)
      host = opts.host || 'localhost'
    }

    if (!host) host = 'localhost'

    validateHost(host, 'Host')
    validatePort(port)

    const {
      lookup = dns.lookup,
      hints,
      family = 0,
      keepAlive = false,
      keepAliveInitialDelay = 0,
      noDelay = false,
      timeout
    } = opts

    if (keepAlive) this.setKeepAlive(true, keepAliveInitialDelay)
    if (noDelay) this.setNoDelay(true)
    if (timeout) this.setTimeout(timeout)

    this._state |= constants.state.CONNECTING

    if (ip.isIP(host) === 0) {
      lookup(host, { all: true, family, hints }, (err, addresses) => {
        if (this._state & constants.state.CLOSING) return

        if (!err) err = lookupError(addresses, host)

        if (err) {
          this._state &= ~constants.state.CONNECTING

          this.emit('lookup', err, null, 0, host)

          if (this._pendingOpen) this._continueOpen(err)
          else this.destroy(err)
          return
        }

        for (const { address, family } of addresses) {
          this.emit('lookup', null, address, family, host)
        }

        const [{ address }, ...rest] = addresses

        if (rest.length > 0) {
          this._addresses = rest.map(({ address }) => [port, address])

          this._errors = []
        }

        this._connect(port, address, onconnect)
      })

      return this
    }

    this._connect(port, host, onconnect)

    return this
  }

  _connect(port, host, onconnect) {
    this._state |= constants.state.CONNECTING

    try {
      binding.connect(this._handle, port, host, ip.isIP(host))

      if (onconnect) this.once('connect', onconnect)
    } catch (err) {
      queueMicrotask(() => this._failConnect(err))
    }
  }

  open(fd, opts = {}, onconnect) {
    if (this._state & constants.state.CLOSING) {
      throw errors.SOCKET_IS_CLOSED('Socket is closed')
    }

    if (this._state & (constants.state.CONNECTING | constants.state.CONNECTED)) {
      throw errors.SOCKET_ALREADY_CONNECTED('Socket is already connected')
    }

    if (typeof opts === 'function') {
      onconnect = opts
      opts = {}
    }

    if (typeof fd === 'object' && fd !== null) {
      opts = fd
      fd = opts.fd
    }

    validateFd(fd)

    const { keepAlive = false, keepAliveInitialDelay = 0, noDelay = false, timeout } = opts

    if (keepAlive) this.setKeepAlive(true, keepAliveInitialDelay)
    if (noDelay) this.setNoDelay(true)
    if (timeout) this.setTimeout(timeout)

    try {
      binding.open(this._handle, fd)

      this._updateAddresses()

      this._state |= constants.state.CONNECTED

      if (this._keepAlive) this.setKeepAlive(this._keepAlive, this._keepAliveInitialDelay)
      if (this._noDelay) this.setNoDelay(this._noDelay)

      if (onconnect) this.once('connect', onconnect)
    } catch (err) {
      queueMicrotask(() => {
        if (this._pendingOpen) this._continueOpen(err)
        else this.destroy(err)
      })

      return this
    }

    this._continueOpen()

    queueMicrotask(() => {
      if (this._state & constants.state.CLOSING) return

      this.emit('connect')
    })

    return this
  }

  setKeepAlive(enable = false, delay = 0) {
    if (typeof enable === 'number') {
      delay = enable
      enable = true
    }

    validateInteger(delay, 'Keep alive initial delay', 0, 0x7fffffff)

    enable = !!enable

    this._keepAlive = enable
    this._keepAliveInitialDelay = delay

    if (this._state & constants.state.CONNECTED) {
      binding.keepalive(this._handle, enable, Math.floor(delay / 1000))
    }

    return this
  }

  setNoDelay(enable = true) {
    enable = !!enable

    this._noDelay = enable

    if (this._state & constants.state.CONNECTED) {
      binding.nodelay(this._handle, enable)
    }

    return this
  }

  setTimeout(ms, ontimeout) {
    validateInteger(ms, 'Timeout', 0, 0x7fffffff)

    clearTimeout(this._timer)

    this._timer = null

    if (ms === 0) {
      if (ontimeout) this.removeListener('timeout', ontimeout)
    } else {
      if (ontimeout) this.once('timeout', ontimeout)

      this._timer = setTimeout(() => this.emit('timeout'), ms)
      this._timer.unref()
    }

    this._timeout = ms

    return this
  }

  ref() {
    this._state &= ~constants.state.UNREFED

    if (this._state & constants.state.CLOSING) return this

    binding.ref(this._handle)

    return this
  }

  unref() {
    this._state |= constants.state.UNREFED

    if (this._state & constants.state.CLOSING) return this

    binding.unref(this._handle)

    return this
  }

  [ipcAccept]() {
    this._onaccept()
  }

  _open(cb) {
    if (this._state & constants.state.CONNECTED) return cb(null)

    this._pendingOpen = cb
  }

  _read() {
    if ((this._state & constants.state.READING) === 0) {
      this._state |= constants.state.READING

      binding.resume(this._handle)
    }
  }

  _writev(batch, cb) {
    this._pendingWrite = cb
    this._pendingWriteBatch = batch

    try {
      coerceBatch(batch)

      binding.writev(
        this._handle,
        batch.map(({ chunk }) => chunk)
      )
    } catch (err) {
      this._continueWrite(err)
    }
  }

  _final(cb) {
    this._pendingFinal = cb

    try {
      binding.end(this._handle)
    } catch (err) {
      this._onfinal(err)
    }
  }

  _predestroy() {
    if (this._state & constants.state.CLOSING) return
    this._state |= constants.state.CLOSING
    this._state &= ~constants.state.CONNECTING

    binding.close(this._handle)
  }

  _destroy(err, cb) {
    if (this._state & constants.state.CLOSED) return cb(err)

    this._pendingDestroy = cb

    if (this._state & constants.state.CLOSING) return
    this._state |= constants.state.CLOSING
    this._state &= ~constants.state.CONNECTING

    binding.close(this._handle)
  }

  _continueOpen(err) {
    if (this._pendingOpen === null) return
    const cb = this._pendingOpen
    this._pendingOpen = null
    cb(err)
  }

  _continueWrite(err) {
    if (this._pendingWrite === null) return
    const cb = this._pendingWrite
    this._pendingWrite = null
    this._pendingWriteBatch = null
    cb(err)
  }

  _continueFinal(err) {
    if (this._pendingFinal === null) return
    const cb = this._pendingFinal
    this._pendingFinal = null
    cb(err)
  }

  _continueDestroy() {
    if (this._pendingDestroy === null) return
    const cb = this._pendingDestroy
    this._pendingDestroy = null
    cb(null)
  }

  _reset() {
    this._state &= constants.state.UNREFED

    this._localAddress = null
    this._remoteAddress = null

    binding.reset(this._handle)
  }

  _updateAddresses() {
    try {
      this._localAddress = binding.address(this._handle, true)
    } catch {
      this._localAddress = null
    }

    try {
      this._remoteAddress = binding.address(this._handle, false)
    } catch {
      this._remoteAddress = null
    }
  }

  _failConnect(err) {
    if (this._state & constants.state.CLOSING) return

    if (this._addresses !== null) {
      this._errors.push(err)

      if (this._addresses.length > 0) return this._reset()

      err = this._errors.length === 1 ? this._errors[0] : new AggregateError(this._errors)
    }

    this._state &= ~constants.state.CONNECTING

    if (this._pendingOpen) this._continueOpen(err)
    else this.destroy(err)
  }

  _onconnect(err) {
    if (err) {
      this._failConnect(err)
      return
    }

    this._updateAddresses()

    this._state |= constants.state.CONNECTED
    this._state &= ~constants.state.CONNECTING

    if (this._keepAlive) this.setKeepAlive(this._keepAlive, this._keepAliveInitialDelay)
    if (this._noDelay) this.setNoDelay(this._noDelay)

    this._continueOpen()

    this.emit('connect')
  }

  _onaccept() {
    this._updateAddresses()

    this._state |= constants.state.CONNECTED

    if (this._keepAlive) this.setKeepAlive(this._keepAlive, this._keepAliveInitialDelay)
    if (this._noDelay) this.setNoDelay(this._noDelay)

    this._continueOpen()
  }

  _onreset(err) {
    if (err) {
      this._errors.push(err)
      this.destroy(this._errors.length === 1 ? this._errors[0] : new AggregateError(this._errors))
      return
    }

    if (this._state & constants.state.UNREFED) binding.unref(this._handle)

    this._connect(...this._addresses.shift())
  }

  _onread(err, read) {
    if (this._timer) this._timer.refresh()

    if (err) {
      this.destroy(err)
      return
    }

    if (read === 0) {
      this.push(null)
      if (this._allowHalfOpen === false) this.end()
      return
    }

    // Unpooled, as a read too small to be given a buffer of its own would
    // otherwise pin the whole pool it came from for as long as its consumer
    // holds on to it.
    const copy = Buffer.allocUnsafeSlow(read)
    copy.set(this._buffer.subarray(0, read))

    if (this.push(copy) === false && this.destroying === false) {
      this._state &= ~constants.state.READING

      binding.pause(this._handle)
    }
  }

  _onwrite(err) {
    if (this._timer) this._timer.refresh()

    this._continueWrite(err)
  }

  _onfinal(err) {
    this._continueFinal(err === null || err.code === 'ENOTCONN' ? null : err)
  }

  _onclose() {
    this._state |= constants.state.CLOSED

    clearTimeout(this._timer)

    this._continueOpen()
    this._continueDestroy()
  }
}

exports.Server = class TCPServer extends EventEmitter {
  constructor(opts = {}, onconnection) {
    if (typeof opts === 'function') {
      onconnection = opts
      opts = {}
    }

    super()

    const {
      readBufferSize = defaultReadBufferSize,
      allowHalfOpen = true,
      keepAlive = false,
      keepAliveInitialDelay = 0,
      noDelay = false,
      pauseOnConnect = false,
      maxConnections = Infinity
    } = opts

    validateInteger(readBufferSize, 'Read buffer size', 1, 0x7fffffff)
    validateMaxConnections(maxConnections)

    this._state = 0

    this._readBufferSize = readBufferSize
    this._allowHalfOpen = !!allowHalfOpen
    this._keepAlive = !!keepAlive
    this._keepAliveInitialDelay = keepAliveInitialDelay
    this._noDelay = !!noDelay
    this._pauseOnConnect = !!pauseOnConnect
    this._maxConnections = maxConnections

    this._address = null
    this._connections = new Set()

    this._attempt = 0

    this._error = null
    this._handle = null

    if (onconnection) this.on('connection', onconnection)
  }

  get listening() {
    return (this._state & constants.state.BOUND) !== 0
  }

  get closing() {
    return (this._state & constants.state.CLOSING) !== 0
  }

  get connections() {
    return this._connections
  }

  get maxConnections() {
    return this._maxConnections
  }

  set maxConnections(value) {
    validateMaxConnections(value)

    this._maxConnections = value
  }

  address() {
    if ((this._state & constants.state.BOUND) === 0) return null

    const { address, family, port } = this._address

    return { address, family: `IPv${family}`, port }
  }

  listen(port = 0, host = 'localhost', backlog = 511, opts = {}, onlistening) {
    if (this._state & constants.state.CLOSING) {
      throw errors.SERVER_IS_CLOSED('Server is closed')
    }

    if (this._state & (constants.state.BINDING | constants.state.BOUND)) {
      throw errors.SERVER_ALREADY_LISTENING('Server is already listening')
    }

    if (typeof port === 'function') {
      onlistening = port
      port = 0
    } else if (typeof host === 'function') {
      onlistening = host
      host = 'localhost'
    } else if (typeof backlog === 'function') {
      onlistening = backlog
      backlog = 511
    } else if (typeof opts === 'function') {
      onlistening = opts
      opts = {}
    }

    if (typeof port === 'object' && port !== null) {
      opts = port
      port = defaultTo(opts.port, 0)
      host = opts.host || 'localhost'
      backlog = defaultTo(opts.backlog, 511)
    }

    if (!host) host = 'localhost'
    if (backlog === null || backlog === 0) backlog = 511

    validateHost(host, 'Host')
    validateInteger(backlog, 'Backlog', 0, 0x7fffffff)
    validatePort(port)

    this._state |= constants.state.BINDING
    this._state &= ~constants.state.CLOSED

    const attempt = ++this._attempt

    const { lookup = dns.lookup, hints, family = 0 } = opts

    const type = ip.isIP(host)

    if (type === 0) {
      lookup(host, { family, hints }, (err, address, family) => {
        if (attempt !== this._attempt) return

        this._state &= ~constants.state.BINDING

        if (!err) err = resolvedHostError(address)

        if (err) {
          this.emit('lookup', err, null, 0, host)

          return this.emit('error', err)
        }

        this.emit('lookup', null, address, family, host)

        this.listen(port, address, backlog, { ...opts, family }, onlistening)
      })

      return this
    }

    this._handle = binding.init(
      empty,
      this,
      this._onconnection,
      noop,
      noop,
      noop,
      noop,
      noop,
      this._onclose
    )

    if (this._state & constants.state.UNREFED) binding.unref(this._handle)

    try {
      binding.bind(this._handle, port, host, type, backlog)

      this._address = binding.address(this._handle, true)

      this._state |= constants.state.BOUND
      this._state &= ~constants.state.BINDING

      if (onlistening) this.once('listening', onlistening)

      queueMicrotask(() => {
        if (this._state & constants.state.CLOSING) return

        this.emit('listening')
      })
    } catch (err) {
      this._error = err

      binding.close(this._handle)
    }

    return this
  }

  close(onclose) {
    if (this._state & constants.state.CLOSED) {
      if (onclose) queueMicrotask(onclose)

      return this
    }

    if (onclose) this.once('close', onclose)

    if (this._state & constants.state.CLOSING) return this
    this._state |= constants.state.CLOSING
    this._state &= ~(constants.state.BINDING | constants.state.BOUND)

    this._attempt++

    if (this._handle !== null) binding.close(this._handle)
    else this._closeMaybe()

    return this
  }

  ref() {
    this._state &= ~constants.state.UNREFED

    if (this._handle !== null) binding.ref(this._handle)

    return this
  }

  unref() {
    this._state |= constants.state.UNREFED

    if (this._handle !== null) binding.unref(this._handle)

    return this
  }

  _closeMaybe() {
    if ((this._state & constants.state.CLOSING) === 0) return
    if (this._state & constants.state.CLOSED) return
    if (this._handle !== null || this._connections.size > 0) return

    this._state |= constants.state.CLOSED
    this._state &= ~constants.state.CLOSING

    queueMicrotask(() => this.emit('close'))
  }

  _onconnection(err) {
    if (err) {
      this.emit('error', err)
      return
    }

    if (this._state & constants.state.CLOSING) return

    const overLimit = this._maxConnections > 0 && this._connections.size >= this._maxConnections

    const socket = new exports.Socket({
      readBufferSize: overLimit ? 1 : this._readBufferSize,
      allowHalfOpen: this._allowHalfOpen,
      eagerOpen: !this._pauseOnConnect
    })

    let info = null
    try {
      binding.accept(this._handle, socket._handle)

      socket._onaccept()

      if (overLimit) {
        info = {
          localAddress: socket.localAddress,
          localPort: socket.localPort,
          localFamily: socket.localFamily,
          remoteAddress: socket.remoteAddress,
          remotePort: socket.remotePort,
          remoteFamily: socket.remoteFamily
        }

        socket.destroy()
      } else {
        this._connections.add(socket)

        if (this._keepAlive) socket.setKeepAlive(this._keepAlive, this._keepAliveInitialDelay)
        if (this._noDelay) socket.setNoDelay(this._noDelay)

        socket.on('close', () => {
          this._connections.delete(socket)
          this._closeMaybe()
        })
      }
    } catch (err) {
      socket.destroy()

      this.emit('error', err)
      return
    }

    if (info !== null) this.emit('drop', info)
    else this.emit('connection', socket)
  }

  _onclose() {
    const err = this._error

    this._state &= ~(constants.state.BINDING | constants.state.BOUND)
    this._error = null
    this._handle = null
    this._address = null

    if (err) this.emit('error', err)

    this._closeMaybe()
  }
}

exports.constants = constants
exports.errors = errors

exports.isIP = ip.isIP
exports.isIPv4 = ip.isIPv4
exports.isIPv6 = ip.isIPv6

exports.createConnection = function createConnection(port, host, opts, onconnect) {
  if (typeof host === 'function') {
    onconnect = host
    host = 'localhost'
  } else if (typeof opts === 'function') {
    onconnect = opts
    opts = {}
  }

  if (typeof port === 'object' && port !== null) {
    opts = port
    port = defaultTo(opts.port, 0)
    host = opts.host || 'localhost'
  }

  return new exports.Socket(opts).connect(port, host, opts, onconnect)
}

// For Node.js compatibility
exports.connect = exports.createConnection

exports.createServer = function createServer(opts, onconnection) {
  return new exports.Server(opts, onconnection)
}

exports.socketpair = function socketpair() {
  return binding.socketpair()
}

function resolvedHostError(address) {
  if (typeof address !== 'string') {
    return errors.INVALID_HOST(`Resolved address must be a string, got ${typeof address}`)
  }

  if (ip.isIP(address) === 0) {
    return errors.INVALID_HOST(`Resolved address must be an IP address, got "${address}"`)
  }

  const length = Buffer.byteLength(address)

  if (length > constants.address.MAX_LENGTH) {
    return errors.INVALID_HOST(
      `Resolved address must be at most ${constants.address.MAX_LENGTH} bytes, got ${length}`
    )
  }

  return null
}

function lookupError(addresses, host) {
  if (!Array.isArray(addresses)) {
    return errors.INVALID_HOST(`Resolved addresses must be an array, got ${typeof addresses}`)
  }

  if (addresses.length === 0) {
    const err = new Error(`No address found for host "${host}"`)
    err.code = 'ENOTFOUND'

    return err
  }

  for (const entry of addresses) {
    if (typeof entry !== 'object' || entry === null) {
      return errors.INVALID_HOST(`Resolved addresses must contain objects, got ${typeof entry}`)
    }

    const err = resolvedHostError(entry.address)

    if (err) return err
  }

  return null
}

function validateHost(host, name) {
  if (typeof host !== 'string') {
    throw errors.INVALID_HOST(`${name} must be a string, got ${typeof host}`)
  }

  if (ip.isIP(host) === 0) return

  validateAddressLength(host, name)
}

function validateAddressLength(address, name) {
  const length = Buffer.byteLength(address)

  if (length > constants.address.MAX_LENGTH) {
    throw errors.INVALID_HOST(
      `${name} must be at most ${constants.address.MAX_LENGTH} bytes, got ${length}`
    )
  }
}

function validatePort(port) {
  if (typeof port !== 'number') {
    throw errors.INVALID_PORT(`Port must be a number, got ${typeof port}`)
  }

  if (!Number.isInteger(port) || port < 0 || port > 0xffff) {
    throw errors.INVALID_PORT(`Port must be an integer between 0 and 65535, got ${port}`)
  }
}

function validateMaxConnections(value) {
  if (typeof value !== 'number') {
    throw errors.INVALID_ARGUMENT(`Max connections must be a number, got ${typeof value}`)
  }

  if (value === Infinity) return

  if (!Number.isInteger(value) || value < 0 || value > 0x7fffffff) {
    throw errors.INVALID_ARGUMENT(
      `Max connections must be a non-negative integer or Infinity, got ${value}`
    )
  }
}

function validateFd(fd) {
  if (typeof fd !== 'number') {
    throw errors.INVALID_FD(`File descriptor must be a number, got ${typeof fd}`)
  }

  if (!Number.isInteger(fd) || fd < 0 || fd > 0x7fffffff) {
    throw errors.INVALID_FD(
      `File descriptor must be an integer between 0 and ${0x7fffffff}, got ${fd}`
    )
  }
}

function validateInteger(value, name, min, max) {
  if (typeof value !== 'number') {
    throw errors.INVALID_ARGUMENT(`${name} must be a number, got ${typeof value}`)
  }

  if (!Number.isInteger(value) || value < min || value > max) {
    throw errors.INVALID_ARGUMENT(
      `${name} must be an integer between ${min} and ${max}, got ${value}`
    )
  }
}

function coerceBatch(batch) {
  for (let i = 0; i < batch.length; i++) {
    const chunk = batch[i].chunk

    if (ArrayBuffer.isView(chunk) === false) {
      throw errors.INVALID_ARGUMENT(`Chunk must be a string or a view, got ${typeof chunk}`)
    }

    batch[i].chunk = Buffer.coerce(chunk)
  }
}

function defaultTo(value, fallback) {
  return value === undefined || value === null ? fallback : value
}

function noop() {}
const binding = require('../binding')

module.exports = {
  state: {
    CONNECTING: 0x1,
    CONNECTED: 0x2,
    BINDING: 0x4,
    BOUND: 0x8,
    READING: 0x10,
    CLOSING: 0x20,
    CLOSED: 0x40,
    UNREFED: 0x80
  },
  address: {
    MAX_LENGTH: binding.MAX_ADDRESS_LENGTH
  }
}
module.exports = class TCPError extends Error {
  constructor(msg, fn = TCPError, code = fn.name) {
    super(`${code}: ${msg}`)
    this.code = code

    if (Error.captureStackTrace) {
      Error.captureStackTrace(this, fn)
    }
  }

  get name() {
    return 'TCPError'
  }

  static SOCKET_ALREADY_CONNECTED(msg) {
    return new TCPError(msg, TCPError.SOCKET_ALREADY_CONNECTED)
  }

  static SOCKET_IS_CLOSED(msg) {
    return new TCPError(msg, TCPError.SOCKET_IS_CLOSED)
  }

  static SERVER_ALREADY_LISTENING(msg) {
    return new TCPError(msg, TCPError.SERVER_ALREADY_LISTENING)
  }

  static SERVER_IS_CLOSED(msg) {
    return new TCPError(msg, TCPError.SERVER_IS_CLOSED)
  }

  static INVALID_ARGUMENT(msg) {
    return new TCPError(msg, TCPError.INVALID_ARGUMENT)
  }

  static INVALID_FD(msg) {
    return new TCPError(msg, TCPError.INVALID_FD)
  }

  static INVALID_HOST(msg = 'Unrecognizable host format') {
    return new TCPError(msg, TCPError.INVALID_HOST)
  }

  static INVALID_PORT(msg) {
    return new TCPError(msg, TCPError.INVALID_PORT)
  }
}
// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.

const v4Seg = '(?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])'
const v4Str = `(${v4Seg}[.]){3}${v4Seg}`
const IPv4Pattern = new RegExp(`^${v4Str}$`)

const v6Seg = '(?:[0-9a-fA-F]{1,4})'
const IPv6Pattern = new RegExp(
  '^(' +
    `(?:${v6Seg}:){7}(?:${v6Seg}|:)|` +
    `(?:${v6Seg}:){6}(?:${v4Str}|:${v6Seg}|:)|` +
    `(?:${v6Seg}:){5}(?::${v4Str}|(:${v6Seg}){1,2}|:)|` +
    `(?:${v6Seg}:){4}(?:(:${v6Seg}){0,1}:${v4Str}|(:${v6Seg}){1,3}|:)|` +
    `(?:${v6Seg}:){3}(?:(:${v6Seg}){0,2}:${v4Str}|(:${v6Seg}){1,4}|:)|` +
    `(?:${v6Seg}:){2}(?:(:${v6Seg}){0,3}:${v4Str}|(:${v6Seg}){1,5}|:)|` +
    `(?:${v6Seg}:){1}(?:(:${v6Seg}){0,4}:${v4Str}|(:${v6Seg}){1,6}|:)|` +
    `(?::((?::${v6Seg}){0,5}:${v4Str}|(?::${v6Seg}){1,7}|:))` +
    ')(%[0-9a-zA-Z-.:]{1,})?$'
)

const isIPv4 = (exports.isIPv4 = function isIPv4(host) {
  return IPv4Pattern.test(host)
})

const isIPv6 = (exports.isIPv6 = function isIPv6(host) {
  return IPv6Pattern.test(host)
})

exports.isIP = function isIP(host) {
  if (isIPv4(host)) return 4
  if (isIPv6(host)) return 6
  return 0
}
{
  "name": "bare-tcp",
  "version": "2.6.1",
  "description": "Native TCP sockets for JavaScript",
  "exports": {
    "./package": "./package.json",
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    },
    "./constants": {
      "types": "./lib/constants.d.ts",
      "default": "./lib/constants.js"
    },
    "./errors": {
      "types": "./lib/errors.d.ts",
      "default": "./lib/errors.js"
    }
  },
  "files": [
    "index.js",
    "index.d.ts",
    "binding.c",
    "binding.js",
    "CMakeLists.txt",
    "lib",
    "prebuilds"
  ],
  "addon": true,
  "scripts": {
    "format": "prettier --write . && lunte --fix",
    "lint": "prettier --check . && lunte",
    "test": "brittle-bare --coverage test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-tcp.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-tcp/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-tcp#readme",
  "engines": {
    "bare": ">=1.16.0"
  },
  "dependencies": {
    "bare-dns": "^2.0.4",
    "bare-events": "^2.5.4",
    "bare-stream": "^2.6.4"
  },
  "devDependencies": {
    "bare-os": "^3.9.3",
    "bare-pipe": "^4.2.3",
    "brittle": "^3.19.1",
    "cmake-bare": "^1.1.6",
    "lunte": "^1.8.0",
    "prettier": "^3.4.1",
    "prettier-config-holepunch": "^2.0.0"
  },
  "peerDependencies": {
    "bare-pipe": "*"
  },
  "peerDependenciesMeta": {
    "bare-pipe": {
      "optional": true
    }
  }
}
module.exports = require.addon()
const { Readable, Writable } = require('bare-stream')
const Signal = require('bare-signals')
const binding = require('./binding')
const constants = require('./lib/constants')
const errors = require('./lib/errors')

const defaultReadBufferSize = 65536
const empty = Buffer.alloc(0)
const modes = new Set([constants.mode.NORMAL, constants.mode.RAW, constants.mode.IO])

exports.ReadStream = class TTYReadStream extends Readable {
  constructor(fd, opts = {}) {
    super(opts)

    const { readBufferSize = defaultReadBufferSize } = opts

    validateFd(fd)
    validateInteger(readBufferSize, 'Read buffer size', 1, 0x7fffffff)

    this._fd = fd
    this._state = 0
    this._buffer = Buffer.alloc(readBufferSize)

    this._pendingDestroy = null

    this._handle = binding.init(fd, this._buffer, this, noop, this._onread, this._onclose, false)
  }

  get fd() {
    return this._fd
  }

  get isTTY() {
    return true
  }

  setMode(mode) {
    validateMode(mode)

    this._alive()

    binding.setMode(this._handle, mode)

    return this
  }

  setRawMode(enabled) {
    return this.setMode(enabled ? constants.mode.RAW : constants.mode.NORMAL)
  }

  _alive() {
    if (this._state & constants.state.CLOSING) {
      throw errors.STREAM_IS_CLOSED('Stream is closed')
    }
  }

  _read() {
    if ((this._state & constants.state.READING) === 0) {
      this._state |= constants.state.READING

      binding.resume(this._handle)
    }
  }

  _predestroy() {
    this._close()
  }

  _destroy(err, cb) {
    if (this._state & constants.state.CLOSED) return cb(err)

    this._pendingDestroy = cb

    this._close()
  }

  _close() {
    if (this._state & constants.state.CLOSING) return
    this._state |= constants.state.CLOSING

    binding.close(this._handle)
  }

  _continueDestroy() {
    if (this._pendingDestroy === null) return
    const cb = this._pendingDestroy
    this._pendingDestroy = null
    cb(null)
  }

  _onread(err, read) {
    if (err) {
      this.destroy(err)
      return
    }

    if (read === 0) {
      this.push(null)
      return
    }

    // Unpooled, as a read too small to be given a buffer of its own would
    // otherwise pin the whole pool it came from for as long as its consumer
    // holds on to it.
    const copy = Buffer.allocUnsafeSlow(read)
    copy.set(this._buffer.subarray(0, read))

    if (this.push(copy) === false && this.destroying === false) {
      this._state &= ~constants.state.READING

      binding.pause(this._handle)
    }
  }

  _onclose() {
    this._state |= constants.state.CLOSED

    this._continueDestroy()
  }
}

exports.WriteStream = class TTYWriteStream extends Writable {
  constructor(fd, opts = {}) {
    super(opts)

    validateFd(fd)

    this._fd = fd
    this._state = 0
    this._size = null

    this._pendingWrite = null
    this._pendingWriteBatch = null
    this._pendingDestroy = null

    this._handle = binding.init(fd, empty, this, this._onwrite, noop, this._onclose, true)

    try {
      this._refreshSize()
    } catch (err) {
      this._close()

      throw err
    }

    if (TTYWriteStream._streams.size === 0) TTYWriteStream._resize.start()

    TTYWriteStream._streams.add(this)
  }

  get fd() {
    return this._fd
  }

  get isTTY() {
    return true
  }

  get columns() {
    return this._size[0]
  }

  get rows() {
    return this._size[1]
  }

  getWindowSize() {
    this._alive()

    return [this._size[0], this._size[1]]
  }

  _refreshSize() {
    const size = binding.getWindowSize(this._handle)

    const changed = this._size === null || size[0] !== this._size[0] || size[1] !== this._size[1]

    this._size = size

    return changed
  }

  _alive() {
    if (this._state & constants.state.CLOSING) {
      throw errors.STREAM_IS_CLOSED('Stream is closed')
    }
  }

  _writev(batch, cb) {
    this._pendingWrite = cb
    this._pendingWriteBatch = batch

    try {
      coerceBatch(batch)

      binding.writev(
        this._handle,
        batch.map(({ chunk }) => chunk)
      )
    } catch (err) {
      this._continueWrite(err)
    }
  }

  _predestroy() {
    this._close()
  }

  _destroy(err, cb) {
    if (this._state & constants.state.CLOSED) return cb(err)

    this._pendingDestroy = cb

    this._close()
  }

  _close() {
    if (this._state & constants.state.CLOSING) return
    this._state |= constants.state.CLOSING

    binding.close(this._handle)

    TTYWriteStream._streams.delete(this)

    if (TTYWriteStream._streams.size === 0) TTYWriteStream._resize.stop()
  }

  _continueWrite(err) {
    if (this._pendingWrite === null) return
    const cb = this._pendingWrite
    this._pendingWrite = null
    this._pendingWriteBatch = null
    cb(err)
  }

  _continueDestroy() {
    if (this._pendingDestroy === null) return
    const cb = this._pendingDestroy
    this._pendingDestroy = null
    cb(null)
  }

  _onwrite(err) {
    this._continueWrite(err)
  }

  _onclose() {
    this._state |= constants.state.CLOSED

    this._continueDestroy()
  }

  _onresize() {
    let changed

    try {
      changed = this._refreshSize()
    } catch (err) {
      this.emit('error', err)
      return
    }

    if (changed) this.emit('resize')
  }

  static _streams = new Set()

  static _resize = new Signal('SIGWINCH')
}

exports.constants = constants

exports.errors = errors

exports.isTTY = function isTTY(fd) {
  return isValidFd(fd) && binding.isTTY(fd)
}

exports.isatty = exports.isTTY // For Node.js compatibility

exports.WriteStream._resize
  .on('signal', () => {
    for (const stream of exports.WriteStream._streams) {
      stream._onresize()
    }
  })
  .unref()

function isValidFd(fd) {
  return typeof fd === 'number' && Number.isInteger(fd) && fd >= 0 && fd <= 0x7fffffff
}

function validateFd(fd) {
  if (typeof fd !== 'number') {
    throw errors.INVALID_FD(`File descriptor must be a number, got ${typeof fd}`)
  }

  if (!isValidFd(fd)) {
    throw errors.INVALID_FD(
      `File descriptor must be an integer between 0 and ${0x7fffffff}, got ${fd}`
    )
  }
}

function validateMode(mode) {
  if (typeof mode !== 'number') {
    throw errors.INVALID_ARGUMENT(`Mode must be a number, got ${typeof mode}`)
  }

  if (!modes.has(mode)) {
    throw errors.INVALID_ARGUMENT(`Mode must be one of ${[...modes].join(', ')}, got ${mode}`)
  }
}

function validateInteger(value, name, min, max) {
  if (typeof value !== 'number') {
    throw errors.INVALID_ARGUMENT(`${name} must be a number, got ${typeof value}`)
  }

  if (!Number.isInteger(value) || value < min || value > max) {
    throw errors.INVALID_ARGUMENT(
      `${name} must be an integer between ${min} and ${max}, got ${value}`
    )
  }
}

function coerceBatch(batch) {
  for (let i = 0; i < batch.length; i++) {
    const chunk = batch[i].chunk

    if (ArrayBuffer.isView(chunk) === false) {
      throw errors.INVALID_ARGUMENT(`Chunk must be a string or a view, got ${typeof chunk}`)
    }

    batch[i].chunk = Buffer.coerce(chunk)
  }
}

function noop() {}
const binding = require('../binding')

module.exports = exports = {
  mode: {
    NORMAL: binding.MODE_NORMAL,
    RAW: binding.MODE_RAW,
    IO: binding.MODE_IO || 0
  },

  state: {
    READING: 0x1,
    CLOSING: 0x2,
    CLOSED: 0x4
  }
}

/** @deprecated Use `mode.NORMAL` */
exports.MODE_NORMAL = exports.mode.NORMAL
/** @deprecated Use `mode.RAW` */
exports.MODE_RAW = exports.mode.RAW
/** @deprecated Use `mode.IO` */
exports.MODE_IO = exports.mode.IO
module.exports = class TTYError extends Error {
  constructor(msg, fn = TTYError, code = fn.name) {
    super(`${code}: ${msg}`)
    this.code = code

    if (Error.captureStackTrace) {
      Error.captureStackTrace(this, fn)
    }
  }

  get name() {
    return 'TTYError'
  }

  static STREAM_IS_CLOSED(msg) {
    return new TTYError(msg, TTYError.STREAM_IS_CLOSED)
  }

  static INVALID_FD(msg) {
    return new TTYError(msg, TTYError.INVALID_FD)
  }

  static INVALID_ARGUMENT(msg) {
    return new TTYError(msg, TTYError.INVALID_ARGUMENT)
  }
}
{
  "name": "bare-tty",
  "version": "5.2.1",
  "description": "Native TTY streams for JavaScript",
  "exports": {
    "./package": "./package.json",
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    },
    "./constants": {
      "types": "./lib/constants.d.ts",
      "default": "./lib/constants.js"
    },
    "./errors": {
      "types": "./lib/errors.d.ts",
      "default": "./lib/errors.js"
    }
  },
  "files": [
    "index.js",
    "index.d.ts",
    "binding.c",
    "binding.js",
    "CMakeLists.txt",
    "lib",
    "prebuilds"
  ],
  "addon": true,
  "scripts": {
    "format": "prettier --write . && lunte --fix",
    "lint": "prettier --check . && lunte",
    "test": "brittle-bare --coverage test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-tty.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-tty/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-tty#readme",
  "engines": {
    "bare": ">=1.16.0"
  },
  "dependencies": {
    "bare-events": "^2.2.0",
    "bare-signals": "^5.0.0",
    "bare-stream": "^2.0.0"
  },
  "devDependencies": {
    "bare-subprocess": "^6.0.0",
    "brittle": "^3.2.1",
    "cmake-bare": "^1.1.6",
    "lunte": "^1.8.0",
    "prettier": "^3.4.1",
    "prettier-config-holepunch": "^2.0.0"
  }
}
module.exports = require.addon()
const binding = require('./binding')

const t = binding.constants

class Type {
  constructor(type) {
    this._type = type
  }

  isUndefined() {
    return this._type === t.UNDEFINED
  }

  isNull() {
    return this._type === t.NULL
  }

  isBoolean() {
    return this._type === t.BOOLEAN
  }

  isNumber() {
    return (this._type & 0xff) === t.NUMBER
  }

  isInt32() {
    return (this._type & (0xff | t.INT32)) === (t.NUMBER | t.INT32)
  }

  isUint32() {
    return (this._type & (0xff | t.UINT32)) === (t.NUMBER | t.UINT32)
  }

  isString() {
    return this._type === t.STRING
  }

  isSymbol() {
    return this._type === t.SYMBOL
  }

  isObject() {
    return (this._type & 0xff) === t.OBJECT
  }

  isArray() {
    return this._type === (t.OBJECT | t.ARRAY)
  }

  isArguments() {
    return this._type === (t.OBJECT | t.ARGUMENTS)
  }

  isDate() {
    return this._type === (t.OBJECT | t.DATE)
  }

  isRegExp() {
    return this._type === (t.OBJECT | t.REGEXP)
  }

  isError() {
    return this._type === (t.OBJECT | t.ERROR)
  }

  isPromise() {
    return this._type === (t.OBJECT | t.PROMISE)
  }

  isProxy() {
    return this._type === (t.OBJECT | t.PROXY)
  }

  isGenerator() {
    return this._type === (t.OBJECT | t.GENERATOR)
  }

  isMap() {
    return this._type === (t.OBJECT | t.MAP)
  }

  isSet() {
    return this._type === (t.OBJECT | t.SET)
  }

  isWeakMap() {
    return this._type === (t.OBJECT | t.WEAK_MAP)
  }

  isWeakSet() {
    return this._type === (t.OBJECT | t.WEAK_SET)
  }

  isWeakRef() {
    return this._type === (t.OBJECT | t.WEAK_REF)
  }

  isArrayBuffer() {
    return this._type === (t.OBJECT | t.ARRAYBUFFER)
  }

  isSharedArrayBuffer() {
    return this._type === (t.OBJECT | t.SHAREDARRAYBUFFER)
  }

  isTypedArray() {
    return (this._type & 0xffff) === (t.OBJECT | t.TYPEDARRAY)
  }

  isInt8Array() {
    return this._type === (t.OBJECT | t.TYPEDARRAY | t.INT8ARRAY)
  }

  isUint8Array() {
    return this._type === (t.OBJECT | t.TYPEDARRAY | t.UINT8ARRAY)
  }

  isUint8ClampedArray() {
    return this._type === (t.OBJECT | t.TYPEDARRAY | t.UINT8CLAMPEDARRAY)
  }

  isInt16Array() {
    return this._type === (t.OBJECT | t.TYPEDARRAY | t.INT16ARRAY)
  }

  isUint16Array() {
    return this._type === (t.OBJECT | t.TYPEDARRAY | t.UINT16ARRAY)
  }

  isInt32Array() {
    return this._type === (t.OBJECT | t.TYPEDARRAY | t.INT32ARRAY)
  }

  isUint32Array() {
    return this._type === (t.OBJECT | t.TYPEDARRAY | t.UINT32ARRAY)
  }

  isFloat16Array() {
    return this._type === (t.OBJECT | t.TYPEDARRAY | t.FLOAT16ARRAY)
  }

  isFloat32Array() {
    return this._type === (t.OBJECT | t.TYPEDARRAY | t.FLOAT32ARRAY)
  }

  isFloat64Array() {
    return this._type === (t.OBJECT | t.TYPEDARRAY | t.FLOAT64ARRAY)
  }

  isBigInt64Array() {
    return this._type === (t.OBJECT | t.TYPEDARRAY | t.BIGINT64ARRAY)
  }

  isBigUint64Array() {
    return this._type === (t.OBJECT | t.TYPEDARRAY | t.BIGUINT64ARRAY)
  }

  isDataView() {
    return this._type === (t.OBJECT | t.DATAVIEW)
  }

  isModuleNamespace() {
    return this._type === (t.OBJECT | t.MODULE_NAMESPACE)
  }

  isFunction() {
    return (this._type & 0xff) === t.FUNCTION
  }

  isAsyncFunction() {
    return (this._type & (0xff | t.ASYNC_FUNCTION)) === (t.FUNCTION | t.ASYNC_FUNCTION)
  }

  isGeneratorFunction() {
    return (this._type & (0xff | t.GENERATOR_FUNCTION)) === (t.FUNCTION | t.GENERATOR_FUNCTION)
  }

  isExternal() {
    return this._type === t.EXTERNAL
  }

  isBigInt() {
    return this._type === t.BIGINT
  }
}

// Classifies a number the way the engine does, but without crossing into the
// addon. A number is an `int32` or a `uint32` if it is an integer within the
// respective range, with negative zero being representable as neither.
function numberType(value) {
  if (!Number.isInteger(value)) return t.NUMBER

  // Negative zero passes both range checks below, so exclude it up front.
  if (value === 0 && 1 / value < 0) return t.NUMBER

  let type = t.NUMBER

  if (value >= -0x80000000 && value <= 0x7fffffff) type |= t.INT32
  if (value >= 0 && value <= 0xffffffff) type |= t.UINT32

  return type
}

module.exports = function type(value) {
  switch (typeof value) {
    case 'undefined':
      return new Type(t.UNDEFINED)
    case 'boolean':
      return new Type(t.BOOLEAN)
    case 'number':
      return new Type(numberType(value))
    case 'string':
      return new Type(t.STRING)
    case 'symbol':
      return new Type(t.SYMBOL)
    case 'object':
      return new Type(value === null ? t.NULL : binding.type(value))
    case 'function':
      return new Type(binding.type(value))
    case 'bigint':
      return new Type(t.BIGINT)
  }
}
{
  "name": "bare-type",
  "version": "1.1.1",
  "description": "Cross-realm type predicates for Bare",
  "exports": {
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    },
    "./package": "./package.json"
  },
  "files": [
    "index.js",
    "index.d.ts",
    "binding.c",
    "binding.js",
    "CMakeLists.txt",
    "prebuilds"
  ],
  "addon": true,
  "scripts": {
    "format": "prettier --write . && lunte --fix",
    "lint": "prettier --check . && lunte",
    "test": "brittle-bare --coverage test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-type.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-type/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-type#readme",
  "engines": {
    "bare": ">=1.2.0"
  },
  "devDependencies": {
    "brittle": "^3.4.0",
    "cmake-bare": "^1.1.6",
    "lunte": "^1.8.4",
    "prettier": "^3.4.1",
    "prettier-config-holepunch": "^2.0.0"
  }
}
module.exports = require.addon()
const path = require('bare-path')
const binding = require('./binding')
const errors = require('./lib/errors')
const URLSearchParams = require('./lib/url-search-params')

const kind = Symbol.for('bare.url.kind')

const isWindows = Bare.platform === 'win32'

// Scratch buffer that the binding writes the parsed component offsets into. A
// single shared buffer is reused across every parse.
//
// The offsets are copied out into fields on the URL immediately after a
// successful parse, so nothing observes the buffer across calls.
const components = new Uint32Array(8)

// The value used for a component that is not present in the URL.
const unset = 0xffffffff

// The schemes the parser treats specially. A URL cannot be switched between a
// special and a non-special scheme, and a backslash only terminates a host for
// the former.
const special = new Set(['ftp', 'file', 'http', 'https', 'ws', 'wss'])

// https://url.spec.whatwg.org/#scheme-start-state
const scheme = /^[a-z][a-z0-9+\-.]*$/

// ASCII tab and newline are removed from input before it is parsed rather than
// percent-encoded like the other C0 controls.
const whitespaceAll = /[\t\n\r]/g

// The characters that terminate a host, and so bound the value the host and
// hostname setters accept.
const hostEnd = /[/\\?#]/
const hostEndOpaque = /[/?#]/

// The delimiters that would let a setter's value escape the component it is
// spliced into. Everything else is left to the reparse, which applies the full
// percent-encode set for the component. As elsewhere in this package, each set
// needs two patterns because test() advances a global pattern's lastIndex.
//
// Credentials are not run through the parser and so keep their tabs and
// newlines, percent-encoded, rather than having them stripped.
const userinfoDelimiter = /[\t\n\r/\\?#@:]/
const userinfoDelimiterAll = /[\t\n\r/\\?#@:]/g

const pathDelimiter = /[?#]/
const pathDelimiterAll = /[?#]/g

// A leading or trailing run of C0 control or space in a value that ends up at
// either end of the href. The parser strips those, but only when parsing a URL
// as a whole, so a setter has to encode its own.
const edges = /^[\u0000-\u0020]+|[\u0000-\u0020]+$/g

const escapes = {
  '\t': '%09',
  '\n': '%0A',
  '\r': '%0D',
  '/': '%2F',
  '\\': '%5C',
  ':': '%3A',
  '?': '%3F',
  '@': '%40',
  '#': '%23'
}

// The characters that pathToFileURL() has to percent-encode itself. A backslash
// is a path separator on Windows and so is left alone there.
const reserved = isWindows ? /[%#?\n\r\t]/ : /[%#?\n\r\t\\]/

class URL {
  static get [kind]() {
    return 0 // Compatibility version
  }

  constructor(input, base, opts = {}) {
    if (arguments.length === 0) throw errors.INVALID_URL()

    input = String(input)

    if (base !== undefined) base = String(base)

    this._href = undefined
    this._schemeEnd = 0
    this._usernameEnd = 0
    this._hostStart = 0
    this._hostEnd = 0
    this._pathStart = 0
    this._queryStart = 0
    this._fragmentStart = 0
    this._params = null

    this._parse(input, base, opts.throw !== false)
  }

  get [kind]() {
    return URL[kind]
  }

  // https://url.spec.whatwg.org/#dom-url-href

  get href() {
    return this._href
  }

  set href(value) {
    // Unlike every other setter, the href setter reports a parse failure rather
    // than leaving the URL untouched.
    this._parse(String(value), null, true)

    if (this._params) this._params._parse(this.search)
  }

  // https://url.spec.whatwg.org/#dom-url-protocol

  get protocol() {
    return this._slice(0, this._schemeEnd) + ':'
  }

  set protocol(value) {
    value = strip(String(value))

    const end = value.indexOf(':')

    if (end !== -1) value = value.slice(0, end)

    value = value.toLowerCase()

    if (!scheme.test(value)) return

    const current = this._slice(0, this._schemeEnd)

    if (special.has(current) !== special.has(value)) return

    if (value === 'file' && (this.username || this.password || this.port)) {
      return
    }

    if (current === 'file' && this._hostStart === this._hostEnd) return

    this._update(this._replace(value, 0, this._schemeEnd))
  }

  // https://url.spec.whatwg.org/#dom-url-username

  get username() {
    return this._slice(this._schemeEnd + 3 /* :// */, this._usernameEnd)
  }

  set username(value) {
    if (cannotHaveCredentialsOrPort(this)) {
      return
    }

    value = encodeUserinfo(String(value))

    if (!hasCredentials(this)) value += '@'

    this._update(this._replace(value, this._schemeEnd + 3 /* :// */, this._usernameEnd))
  }

  // https://url.spec.whatwg.org/#dom-url-password

  get password() {
    return this._href.slice(this._usernameEnd + 1 /* : */, this._hostStart - 1 /* @ */)
  }

  set password(value) {
    if (cannotHaveCredentialsOrPort(this)) {
      return
    }

    value = ':' + encodeUserinfo(String(value))

    let end = this._hostStart - 1 /* @ */

    if (!hasCredentials(this)) {
      value += '@'
      end = this._usernameEnd
    }

    this._update(this._replace(value, this._usernameEnd, end))
  }

  // https://url.spec.whatwg.org/#dom-url-host

  get host() {
    return this._slice(this._hostStart, this._pathStart)
  }

  set host(value) {
    if (hasOpaquePath(this)) {
      return
    }

    const protocol = this._slice(0, this._schemeEnd)

    value = truncateHost(protocol, String(value))

    // An `@` would make the reparse read the value as credentials rather than
    // as a host, so it is rejected outright.
    if (value.includes('@')) return

    const separator = portSeparator(value)

    let end = this._hostEnd

    // A port in the value is parsed separately so that an invalid one leaves
    // the existing port in place rather than rejecting the host along with it.
    if (separator !== -1) {
      // A file URL cannot have a port, so a value carrying one is rejected
      // rather than split.
      if (protocol === 'file') return

      const port = parsePort(value.slice(separator + 1))

      value = value.slice(0, separator)

      if (port !== null) {
        value += port
        end = this._pathStart
      }
    }

    if (value === '' && cannotHaveEmptyHost(protocol)) return

    this._update(this._replace(value, this._hostStart, end))
  }

  // https://url.spec.whatwg.org/#dom-url-hostname

  get hostname() {
    return this._slice(this._hostStart, this._hostEnd)
  }

  set hostname(value) {
    if (hasOpaquePath(this)) {
      return
    }

    const protocol = this._slice(0, this._schemeEnd)

    value = truncateHost(protocol, String(value))

    // A port cannot be set through this setter, and a value that carries one is
    // rejected outright rather than truncated. An `@` would make the reparse
    // read the value as credentials rather than as a host.
    if (value.includes('@') || portSeparator(value) !== -1) return

    if (value === '' && cannotHaveEmptyHost(protocol)) return

    this._update(this._replace(value, this._hostStart, this._hostEnd))
  }

  // https://url.spec.whatwg.org/#dom-url-port

  get port() {
    return this._slice(this._hostEnd + 1 /* : */, this._pathStart)
  }

  set port(value) {
    if (cannotHaveCredentialsOrPort(this)) {
      return
    }

    value = strip(String(value))

    if (value !== '') {
      value = parsePort(value)

      if (value === null) return
    }

    this._update(this._replace(value, this._hostEnd, this._pathStart))
  }

  // https://url.spec.whatwg.org/#dom-url-pathname

  get pathname() {
    return this._slice(this._pathStart, this._queryStart - 1 /* ? */)
  }

  set pathname(value) {
    if (hasOpaquePath(this)) {
      return
    }

    value = encodePath(encodeEdges(String(value)))

    // An empty path is left alone, as only a special scheme is required to have
    // one and the reparse inserts it there.
    if (value !== '' && value[0] !== '/' && value[0] !== '\\') {
      value = '/' + value
    }

    this._update(this._replace(value, this._pathStart, this._queryStart - 1 /* ? */))
  }

  // https://url.spec.whatwg.org/#dom-url-search

  get search() {
    return this._slice(this._queryStart - 1 /* ? */, this._fragmentStart - 1 /* # */)
  }

  set search(value) {
    value = String(value)

    if (value !== '') {
      if (value[0] === '?') value = value.slice(1)

      value = '?' + encodeQuery(encodeEdges(value))
    }

    this._update(
      this._replace(value, this._queryStart - 1 /* ? */, this._fragmentStart - 1 /* # */)
    )
  }

  // https://url.spec.whatwg.org/#dom-url-searchparams

  get searchParams() {
    if (this._params === null) {
      this._params = new URLSearchParams(this.search, this)
    }

    return this._params
  }

  // https://url.spec.whatwg.org/#dom-url-hash

  get hash() {
    return this._slice(this._fragmentStart - 1 /* # */)
  }

  set hash(value) {
    value = String(value)

    // The fragment runs to the end of the URL, so nothing in it can escape into
    // another component and no delimiter needs encoding here.
    if (value !== '') {
      if (value[0] === '#') value = value.slice(1)

      value = '#' + encodeEdges(value)
    }

    this._update(this._replace(value, this._fragmentStart - 1 /* # */))
  }

  toString() {
    return this._href
  }

  toJSON() {
    return this._href
  }

  [Symbol.for('bare.inspect')]() {
    return {
      __proto__: { constructor: URL },

      href: this.href,
      protocol: this.protocol,
      username: this.username,
      password: this.password,
      host: this.host,
      hostname: this.hostname,
      port: this.port,
      pathname: this.pathname,
      search: this.search,
      searchParams: this.searchParams,
      hash: this.hash
    }
  }

  _slice(start, end = this._href.length) {
    return this._href.slice(start, end)
  }

  _replace(replacement, start, end = this._href.length) {
    return this._slice(0, start) + replacement + this._slice(end)
  }

  _parse(input, base, shouldThrow) {
    let href

    try {
      href = binding.parse(input, base || null, components, shouldThrow)
    } catch (err) {
      if (err instanceof TypeError || err.code !== undefined) throw err

      throw errors.INVALID_URL(`Invalid URL '${input}'`, input)
    }

    if (href === undefined) return

    this._href = href
    this._schemeEnd = components[0]
    this._usernameEnd = components[1]
    this._hostStart = components[2]
    this._hostEnd = components[3]
    this._pathStart = components[5]

    const queryStart = components[6]
    const fragmentStart = components[7]

    const end = href.length + 1

    this._queryStart = queryStart === unset ? end : queryStart
    this._fragmentStart = fragmentStart === unset ? end : fragmentStart
  }

  _update(input) {
    try {
      this._parse(input, null, true)
    } catch (err) {
      if (err instanceof TypeError) throw err

      return
    }

    if (this._params) this._params._parse(this.search)
  }
}

module.exports = exports = URL

// https://url.spec.whatwg.org/#url-opaque-path
function hasOpaquePath(url) {
  return url.pathname[0] !== '/'
}

// https://url.spec.whatwg.org/#cannot-have-a-username-password-port
function cannotHaveCredentialsOrPort(url) {
  return url.hostname === '' || url.protocol === 'file:'
}

// Whether the URL carries a userinfo section, and so an `@` before its host.
function hasCredentials(url) {
  return url._hostStart !== url._usernameEnd
}

// Almost no input contains any of these, so it is worth ruling all three out
// before rewriting anything.
function strip(value) {
  if (value.indexOf('\t') === -1 && value.indexOf('\n') === -1 && value.indexOf('\r') === -1) {
    return value
  }

  return value.replace(whitespaceAll, '')
}

// https://url.spec.whatwg.org/#host-state
//
// Host parsing stops at the first character that starts another component, so
// anything from there on is dropped rather than spliced into the host.
function truncateHost(protocol, value) {
  const end = value.search(special.has(protocol) ? hostEnd : hostEndOpaque)

  return end === -1 ? value : value.slice(0, end)
}

// Only a special scheme other than file has to have a host.
function cannotHaveEmptyHost(protocol) {
  return protocol !== 'file' && special.has(protocol)
}

// The index of the colon that separates a host from its port, disregarding the
// colons of an IPv6 address, or -1 if the host carries no port.
function portSeparator(host) {
  return host.indexOf(':', host[0] === '[' ? host.indexOf(']') : 0)
}

// https://url.spec.whatwg.org/#port-state
//
// Parsing stops at the first character that is not a digit, so a value with no
// leading digits, or one that overflows, leaves the port as it was.
function parsePort(value) {
  value = /^\d*/.exec(value)[0]

  if (value === '' || Number(value) > 65535) return null

  return ':' + value
}

function encodeUserinfo(value) {
  if (!userinfoDelimiter.test(value)) return value

  return value.replace(userinfoDelimiterAll, (match) => escapes[match])
}

function encodePath(value) {
  if (!pathDelimiter.test(value)) return value

  return value.replace(pathDelimiterAll, (match) => escapes[match])
}

function encodeQuery(value) {
  if (value.indexOf('#') === -1) return value

  return value.replaceAll('#', '%23')
}

// Percent-encodes a leading or trailing run of C0 control or space, which the
// parser would otherwise strip from a value that lands at either end of the
// href. Every component that can end a URL encodes them anyway, so this only
// brings the encoding forward.
function encodeEdges(value) {
  const len = value.length

  if (len === 0) return value
  if (value.charCodeAt(0) > 0x20 && value.charCodeAt(len - 1) > 0x20) return value

  return value.replace(edges, (match) => {
    let encoded = ''

    for (let i = 0, n = match.length; i < n; i++) {
      encoded += '%' + match.charCodeAt(i).toString(16).padStart(2, '0').toUpperCase()
    }

    return encoded
  })
}

exports.URL = URL
exports.URLSearchParams = URLSearchParams

exports.errors = errors

exports.isURL = function isURL(value) {
  if (value instanceof URL) return true

  return typeof value === 'object' && value !== null && value[kind] === URL[kind]
}

exports.isURLSearchParams = URLSearchParams.isURLSearchParams

// https://url.spec.whatwg.org/#dom-url-parse
exports.parse = function parse(input, base) {
  const url = new URL(input, base, { throw: false })
  return url._href ? url : null
}

// https://url.spec.whatwg.org/#dom-url-canparse
exports.canParse = function canParse(input, base) {
  return binding.canParse(String(input), base ? String(base) : null)
}

exports.fileURLToPath = function fileURLToPath(url) {
  if (typeof url === 'string') {
    url = new URL(url)
  }

  if (url.protocol !== 'file:') {
    throw errors.INVALID_URL_SCHEME('The URL must use the file: protocol')
  }

  if (!isWindows && url.hostname) {
    throw errors.INVALID_FILE_URL_HOST("The file: URL host must be 'localhost' or empty")
  }

  const encoded = url.pathname

  // Every check below looks for a percent encoded sequence, as does the decoding
  // that follows, so a path without any can skip all of them.
  const hasEncoded = encoded.includes('%')

  if (hasEncoded) {
    if (isWindows) {
      if (/%2f|%5c/i.test(encoded)) {
        throw errors.INVALID_FILE_URL_PATH(
          'The file: URL path must not include encoded \\ or / characters'
        )
      }
    } else if (/%2f/i.test(encoded)) {
      throw errors.INVALID_FILE_URL_PATH('The file: URL path must not include encoded / characters')
    }

    if (/%00/i.test(encoded)) {
      throw errors.INVALID_FILE_URL_PATH(
        'The file: URL path must not include encoded NUL characters'
      )
    }
  }

  const pathname = path.normalize(hasEncoded ? decodeURIComponent(encoded) : encoded)

  if (isWindows) {
    if (url.hostname) return '\\\\' + url.hostname + pathname

    const letter = pathname.charCodeAt(1) | 0x20

    if (letter < 0x61 /* a */ || letter > 0x7a /* z */ || pathname.charCodeAt(2) !== 0x3a /* : */) {
      throw errors.INVALID_FILE_URL_PATH('The file: URL path must be absolute')
    }

    return pathname.slice(1)
  }

  return pathname
}

exports.pathToFileURL = function pathToFileURL(pathname) {
  let resolved = path.resolve(pathname)

  if (pathname[pathname.length - 1] === '/') {
    resolved += '/'
  } else if (isWindows && pathname[pathname.length - 1] === '\\') {
    resolved += '\\'
  }

  // Paths hardly ever contain any of these, so one pass to rule them out is
  // cheaper than the six or seven replacements it stands in for.
  if (reserved.test(resolved)) {
    resolved = resolved
      .replaceAll('%', '%25') // Must be first
      .replaceAll('#', '%23')
      .replaceAll('?', '%3f')
      .replaceAll('\n', '%0a')
      .replaceAll('\r', '%0d')
      .replaceAll('\t', '%09')

    if (!isWindows) {
      resolved = resolved.replaceAll('\\', '%5c')
    }
  }

  return new URL('file:' + resolved)
}

exports.format = function format(parts) {
  const { protocol, auth, host, hostname, port, pathname, search, query, hash, slashes } = parts

  let result = ''

  if (typeof protocol === 'string') {
    result += protocol

    if (protocol[protocol.length - 1] !== ':') {
      result += ':'
    }

    if (slashes === true || /https?|ftp|gopher|file/.test(protocol)) {
      result += '//'
    }
  }

  if (typeof auth === 'string') {
    if (host || hostname) result += auth + '@'
  }

  if (typeof host === 'string') result += host
  else {
    result += hostname

    if (port) result += ':' + port
  }

  if (typeof pathname === 'string' && pathname !== '') {
    if (pathname[0] !== '/') result += '/'
    result += pathname
  }

  if (typeof search === 'string') {
    if (search[0] !== '?') result += '?'
    result += search
  } else if (typeof query === 'object' && query !== null) {
    result += '?' + new URLSearchParams(query)
  }

  if (typeof hash === 'string') {
    if (hash[0] !== '#') result += '#'
    result += hash
  }

  return result
}
module.exports = class URLError extends Error {
  constructor(msg, fn = URLError, code = fn.name) {
    super(`${code}: ${msg}`)

    this.code = code

    if (Error.captureStackTrace) Error.captureStackTrace(this, fn)
  }

  get name() {
    return 'URLError'
  }

  static INVALID_URL(msg, input) {
    const err = new URLError(msg, URLError.INVALID_URL)

    err.input = input

    return err
  }

  static INVALID_URL_SCHEME(msg = 'Invalid URL') {
    return new URLError(msg, URLError.INVALID_URL_SCHEME)
  }

  static INVALID_FILE_URL_HOST(msg = 'Invalid file: URL host') {
    return new URLError(msg, URLError.INVALID_FILE_URL_HOST)
  }

  static INVALID_FILE_URL_PATH(msg = 'Invalid file: URL path') {
    return new URLError(msg, URLError.INVALID_FILE_URL_PATH)
  }
}
const kind = Symbol.for('bare.url.search-params.kind')

// The URL each instance writes back to, if any. Kept module private so that the
// setter it drives cannot be pointed at an arbitrary object.
const urls = new WeakMap()

class URLSearchParams {
  static get [kind]() {
    return 0 // Compatibility version
  }

  // https://url.spec.whatwg.org/#dom-urlsearchparams-urlsearchparams
  constructor(init, url = null) {
    this._params = null

    if (url) urls.set(this, url)

    if (typeof init === 'string') {
      this._parse(init)
    } else {
      this._params = new Map()

      if (init) {
        for (const [name, value] of typeof init[Symbol.iterator] === 'function'
          ? init
          : Object.entries(init)) {
          this.append(name, value)
        }
      }
    }
  }

  get [kind]() {
    return URLSearchParams[kind]
  }

  // https://url.spec.whatwg.org/#dom-urlsearchparams-size
  get size() {
    let size = 0

    for (const values of this._params.values()) size += values.length

    return size
  }

  // https://url.spec.whatwg.org/#dom-urlsearchparams-append
  append(name, value = null) {
    if (value === null) return

    let list = this._params.get(name)

    if (list === undefined) {
      list = []
      this._params.set(name, list)
    }

    list.push(value)

    this._update()
  }

  // https://url.spec.whatwg.org/#dom-urlsearchparams-delete
  delete(name, value = null) {
    if (value === null) this._params.delete(name)
    else {
      let list = this._params.get(name)

      if (list === undefined) return

      list = list.filter((found) => found !== value)

      if (list.length === 0) this._params.delete(name)
      else this._params.set(name, list)
    }

    this._update()
  }

  // https://url.spec.whatwg.org/#dom-urlsearchparams-get
  get(name) {
    const list = this._params.get(name)

    if (list === undefined) return null

    return list[0]
  }

  // https://url.spec.whatwg.org/#dom-urlsearchparams-getall
  getAll(name) {
    const list = this._params.get(name)

    if (list === undefined) return []

    return Array.from(list)
  }

  // https://url.spec.whatwg.org/#dom-urlsearchparams-has
  has(name, value = null) {
    const list = this._params.get(name)

    if (list === undefined) return false

    if (value === null) return true

    return list.includes(value)
  }

  // https://url.spec.whatwg.org/#dom-urlsearchparams-set
  set(name, value = null) {
    if (value === null) this._params.delete(name)
    else this._params.set(name, [value])

    this._update()
  }

  toString() {
    return this._serialize()
  }

  toJSON() {
    return [...this]
  }

  *[Symbol.iterator]() {
    for (const [name, values] of this._params) {
      for (const value of values) yield [name, value]
    }
  }

  [Symbol.for('bare.inspect')]() {
    const object = {
      __proto__: { constructor: URLSearchParams }
    }

    for (const [name, values] of this._params) {
      if (values.length === 1) object[name] = values[0]
      else object[name] = values
    }

    return object
  }

  // https://url.spec.whatwg.org/#concept-urlsearchparams-update
  _update() {
    const url = urls.get(this)

    if (url === undefined) return

    url.search = this._serialize()
  }

  // https://url.spec.whatwg.org/#concept-urlencoded-parser
  _parse(input) {
    const params = new Map()

    this._params = params

    const len = input.length

    let start = input.charCodeAt(0) === 0x3f /* ? */ ? 1 : 0

    while (start < len) {
      let end = input.indexOf('&', start)
      if (end === -1) end = len

      if (end !== start) {
        let split = input.indexOf('=', start)
        if (split === -1 || split > end) split = end

        const name = decode(input.slice(start, split))
        const value = split === end ? '' : decode(input.slice(split + 1, end))

        const list = params.get(name)

        if (list === undefined) params.set(name, [value])
        else list.push(value)
      }

      start = end + 1
    }
  }

  // https://url.spec.whatwg.org/#concept-urlencoded-serializer
  _serialize() {
    let result = ''
    let separator = ''

    for (const [name, values] of this._params) {
      // The name is shared by all of its values, so it is only encoded once.
      const encoded = encode(String(name))

      for (const value of values) {
        result += separator + encoded + '=' + encode(String(value))
        separator = '&'
      }
    }

    return result
  }
}

module.exports = exports = URLSearchParams

exports.isURLSearchParams = function isURLSearchParams(value) {
  if (value instanceof URLSearchParams) return true

  return typeof value === 'object' && value !== null && value[kind] === URLSearchParams[kind]
}

// https://url.spec.whatwg.org/#concept-urlencoded-byte-serializer
//
// Everything except ASCII alphanumerics and `*-._` is percent-encoded, and a
// space becomes `+` rather than `%20`. Names and values usually consist entirely
// of characters that are left alone, so a component is tested as a whole before
// any of it is rewritten.
const unencoded = /^[\w*.-]*$/

// As above, but allowing spaces, which need nothing more than turning into `+`.
// Spaces are by far the most common reason for a component to need rewriting at
// all, so recognizing them here spares those components a trip through
// encodeURIComponent().
const spaced = /^[\w*. -]*$/

// The characters that encodeURIComponent() leaves alone but the urlencoded byte
// serializer does not. Two patterns are needed because a global regular
// expression cannot be shared between test() and replace(), as test() advances
// its lastIndex.
const extra = /[!'()~]/
const extraAll = /[!'()~]/g

const escapes = {
  '!': '%21',
  "'": '%27',
  '(': '%28',
  ')': '%29',
  '~': '%7E'
}

// A surrogate pair, or a surrogate that is not part of one and so encodes no
// scalar value. Pairs are matched first so that only the capture group can hold
// a lone surrogate; a lookbehind would say this more directly but isn't
// supported by every engine.
const lone = /[\ud800-\udbff][\udc00-\udfff]|([\ud800-\udbff]|[\udc00-\udfff])/g

function encode(component) {
  if (unencoded.test(component)) return component

  if (spaced.test(component)) return component.replaceAll(' ', '+')

  let encoded

  try {
    encoded = encodeURIComponent(component)
  } catch {
    // encodeURIComponent() rejects lone surrogates, whereas the UTF-8 encoder the
    // serializer is defined in terms of replaces them with U+FFFD.
    encoded = encodeURIComponent(
      component.replace(lone, (match, single) => (single === undefined ? match : '\ufffd'))
    )
  }

  if (encoded.includes('%20')) encoded = encoded.replaceAll('%20', '+')

  if (extra.test(encoded)) encoded = encoded.replace(extraAll, (match) => escapes[match])

  return encoded
}

// https://url.spec.whatwg.org/#percent-decode-after-encoding
function decode(component) {
  if (component.indexOf('+') !== -1) component = component.replaceAll('+', ' ')

  if (component.indexOf('%') === -1) return component

  return decodeURIComponent(component)
}
{
  "name": "bare-url",
  "version": "2.5.4",
  "description": "WHATWG URL implementation for JavaScript",
  "exports": {
    "./package": "./package.json",
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    },
    "./global": {
      "types": "./global.d.ts",
      "default": "./global.js"
    }
  },
  "files": [
    "index.js",
    "index.d.ts",
    "global.js",
    "global.d.ts",
    "binding.c",
    "binding.js",
    "CMakeLists.txt",
    "lib",
    "prebuilds"
  ],
  "addon": true,
  "scripts": {
    "format": "prettier --write . && lunte --fix",
    "lint": "prettier --check . && lunte",
    "test": "brittle-bare --coverage test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-url.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-url/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-url",
  "dependencies": {
    "bare-path": "^3.0.0"
  },
  "devDependencies": {
    "brittle": "^3.3.2",
    "cmake-bare": "^1.8.1",
    "cmake-fetch": "^1.0.0",
    "lunte": "^1.6.0",
    "prettier": "^3.3.3",
    "prettier-config-holepunch": "^2.0.0"
  }
}
const b4a = require('b4a')

function byteLength (size) {
  return Math.ceil(size / 8)
}

function get (buffer, bit) {
  const n = buffer.BYTES_PER_ELEMENT * 8

  const offset = bit & (n - 1)
  const i = (bit - offset) / n

  return (buffer[i] & (1 << offset)) !== 0
}

function set (buffer, bit, value = true) {
  const n = buffer.BYTES_PER_ELEMENT * 8

  const offset = bit & (n - 1)
  const i = (bit - offset) / n
  const mask = 1 << offset

  if (value) {
    if ((buffer[i] & mask) !== 0) return false
  } else {
    if ((buffer[i] & mask) === 0) return false
  }

  buffer[i] ^= mask
  return true
}

function setRange (buffer, start, end, value = true) {
  const n = buffer.BYTES_PER_ELEMENT * 8

  let remaining = end - start
  let offset = start & (n - 1)
  let i = (start - offset) / n

  let changed = false

  while (remaining > 0) {
    const mask = (2 ** Math.min(remaining, n - offset) - 1) << offset

    if (value) {
      if ((buffer[i] & mask) !== mask) {
        buffer[i] |= mask
        changed = true
      }
    } else {
      if ((buffer[i] & mask) !== 0) {
        buffer[i] &= ~mask
        changed = true
      }
    }

    remaining -= n - offset
    offset = 0
    i++
  }

  return changed
}

function fill (buffer, value, start = 0, end = buffer.byteLength * 8) {
  const n = buffer.BYTES_PER_ELEMENT * 8
  let i, j

  {
    const offset = start & (n - 1)
    i = (start - offset) / n

    if (offset !== 0) {
      const mask = (2 ** Math.min(n - offset, end - start) - 1) << offset

      if (value) buffer[i] |= mask
      else buffer[i] &= ~mask

      i++
    }
  }

  {
    const offset = end & (n - 1)
    j = (end - offset) / n

    if (offset !== 0 && j >= i) {
      const mask = (2 ** offset) - 1

      if (value) buffer[j] |= mask
      else buffer[j] &= ~mask
    }
  }

  return buffer.fill(value ? (2 ** n) - 1 : 0, i, j)
}

function toggle (buffer, bit) {
  const n = buffer.BYTES_PER_ELEMENT * 8

  const offset = bit & (n - 1)
  const i = (bit - offset) / n
  const mask = 1 << offset

  buffer[i] ^= mask
  return (buffer[i] & mask) !== 0
}

function remove (buffer, bit) {
  return set(buffer, bit, false)
}

function removeRange (buffer, start, end) {
  return setRange(buffer, start, end, false)
}

function indexOf (buffer, value, position = 0) {
  for (let i = position, n = buffer.byteLength * 8; i < n; i++) {
    if (get(buffer, i) === value) return i
  }

  return -1
}

function lastIndexOf (buffer, value, position = buffer.byteLength * 8 - 1) {
  for (let i = position; i >= 0; i--) {
    if (get(buffer, i) === value) return i
  }

  return -1
}

function of (...bits) {
  return from(bits)
}

function from (bits) {
  const buffer = b4a.alloc(byteLength(bits.length))
  for (let i = 0; i < bits.length; i++) set(buffer, i, bits[i])
  return buffer
}

function * iterator (buffer) {
  for (let i = 0, n = buffer.byteLength * 8; i < n; i++) yield get(buffer, i)
}

module.exports = {
  byteLength,
  get,
  set,
  setRange,
  fill,
  toggle,
  remove,
  removeRange,
  indexOf,
  lastIndexOf,
  of,
  from,
  iterator
}
{
  "name": "bits-to-bytes",
  "version": "1.3.0",
  "description": "Functions for doing bit manipulation of typed arrays",
  "main": "index.js",
  "files": [
    "index.js"
  ],
  "scripts": {
    "test": "standard && brittle test.mjs"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bits-to-bytes.git"
  },
  "author": "Kasper Isager Dalsgarð <kasper@funktionel.co>",
  "license": "ISC",
  "bugs": {
    "url": "https://github.com/holepunchto/bits-to-bytes/issues"
  },
  "homepage": "https://github.com/holepunchto/bits-to-bytes#readme",
  "dependencies": {
    "b4a": "^1.5.0"
  },
  "devDependencies": {
    "brittle": "^2.3.1",
    "standard": "^17.0.0"
  }
}
const EventEmitter = require('events')
const Protomux = require('protomux')
const { Readable } = require('streamx')
const sodium = require('sodium-universal')
const b4a = require('b4a')
const c = require('compact-encoding')
const bitfield = require('compact-encoding-bitfield')
const bits = require('bits-to-bytes')
const errors = require('./lib/errors')

exports.Server = class BlindRelayServer extends EventEmitter {
  constructor(opts = {}) {
    super()

    const { createStream } = opts

    this._createStream = createStream
    this._pairing = new Map()
    this._activePairingRefs = new Map()
    this._sessions = new Set()
    this.stats = createStats()
  }

  get sessions() {
    return this._sessions[Symbol.iterator]()
  }

  accept(stream, opts) {
    const session = new BlindRelaySession(this, stream, opts)

    this._sessions.add(session)
    this.stats.sessions.accepted++

    return session
  }

  async close() {
    const ending = []

    for (const session of this._sessions) {
      ending.push(session.end())
    }

    await Promise.all(ending)

    this._pairing.clear()
  }

  _trackActiveLink(keyString) {
    const refs = this._activePairingRefs.get(keyString) || 0

    if (refs === 0) this.stats.pairings.active++

    this._activePairingRefs.set(keyString, refs + 1)
  }

  _untrackActiveLink(keyString) {
    const refs = this._activePairingRefs.get(keyString)

    if (refs === undefined) return

    if (refs === 1) {
      this._activePairingRefs.delete(keyString)
      this.stats.pairings.active--
    } else {
      this._activePairingRefs.set(keyString, refs - 1)
    }
  }
}

class BlindRelaySession extends EventEmitter {
  constructor(server, stream, opts = {}) {
    super()

    const { id, handshake, handshakeEncoding } = opts

    this._server = server
    this._mux = Protomux.from(stream)

    this._channel = this._mux.createChannel({
      protocol: 'blind-relay',
      id,
      handshake: handshake ? handshakeEncoding || c.raw : null,
      onopen: this._onopen.bind(this),
      onclose: this._onclose.bind(this),
      ondestroy: this._ondestroy.bind(this)
    })

    this._pair = this._channel.addMessage({
      encoding: m.pair,
      onmessage: this._onpair.bind(this)
    })

    this._unpair = this._channel.addMessage({
      encoding: m.unpair,
      onmessage: this._onunpair.bind(this)
    })

    this._ending = null
    this._destroyed = false
    this._error = null
    this._pairing = new Set()
    this._links = new Map()

    this._onerror = (err) => {
      this._server.stats.streams.errors++
      this.emit('error', err)
    }

    this._channel.open(handshake)
  }

  get closed() {
    return this._channel.closed
  }

  get mux() {
    return this._mux
  }

  get stream() {
    return this._mux.stream
  }

  _onopen() {
    this._server.stats.sessions.opened++
    this.emit('open')
  }

  _onclose() {
    this._ending = Promise.resolve()

    const err = this._error || errors.CHANNEL_CLOSED()
    this._server.stats.pairings.cancelled += this._pairing.size

    for (const token of this._pairing) {
      if (this._server._pairing.delete(token.toString('hex'))) {
        this._server.stats.pairings.pending--
      }
    }

    for (const link of this._links.values()) {
      link.destroy(err)
    }

    this._pairing.clear()
    this._links.clear()

    this._server._sessions.delete(this)
    this._server.stats.sessions.closed++

    this.emit('close')
  }

  _ondestroy() {
    this._destroyed = true
    this.emit('destroy')
  }

  _onpair({ isInitiator, token, id: remoteId }) {
    const keyString = token.toString('hex')

    let pair = this._server._pairing.get(keyString)

    if (pair === undefined) {
      pair = new BlindRelayPair(token)
      this._server._pairing.set(keyString, pair)
      this._server.stats.pairings.pending++
    } else if (pair.links[+isInitiator]) return

    this._pairing.add(keyString)
    this._server.stats.pairings.requested++

    pair.links[+isInitiator] = new BlindRelayLink(this, isInitiator, remoteId)

    if (!pair.paired) return

    this._server._pairing.delete(keyString)
    this._server.stats.pairings.pending--
    this._server.stats.pairings.matched++

    // 1st pass: Create the raw streams needed for each end of the link.
    for (const link of pair.links) {
      link.createStream()
    }

    // 2nd pass: Connect the raw streams and set up handlers.
    for (const link of pair.links) {
      link.remote = pair.remote(link.isInitiator)
      link.activate(keyString)
    }

    // 3rd pass: Let either end of the link know the streams were set up.
    for (const { isInitiator, session, remoteId, stream } of pair.links) {
      session._pair.send({
        isInitiator,
        token,
        id: stream.id,
        seq: 0
      })

      session._endMaybe()

      session.emit('pair', isInitiator, token, stream, remoteId)
    }
  }

  _onunpair({ token }) {
    const keyString = token.toString('hex')

    const pair = this._server._pairing.get(keyString)

    if (pair) {
      for (const link of pair.links) {
        if (link) link.session._pairing.delete(keyString)
      }

      this._server._pairing.delete(keyString)
      this._server.stats.pairings.pending--
      this._server.stats.pairings.cancelled++
      return
    }

    const link = this._links.get(keyString)

    if (link) {
      link.destroyPair(errors.PAIRING_CANCELLED())
      this._server.stats.pairings.cancelled++
    }
  }

  cork() {
    this._channel.cork()
  }

  uncork() {
    this._channel.uncork()
  }

  async end() {
    if (this._ending) return this._ending

    this._ending = EventEmitter.once(this, 'close')
    this._endMaybe()

    return this._ending
  }

  _endMaybe() {
    if (this._ending && this._pairing.size === 0) {
      this._channel.close()
    }
  }

  destroy(err) {
    if (this._destroyed) return
    this._destroyed = true

    this._error = err || errors.CHANNEL_DESTROYED()
    this._channel.close()
  }
}

class BlindRelayPair {
  constructor(token) {
    this.token = token
    this.links = [null, null]
  }

  get paired() {
    return this.links[0] !== null && this.links[1] !== null
  }

  remote(isInitiator) {
    return this.links[isInitiator ? 0 : 1]
  }
}

class BlindRelayLink {
  constructor(session, isInitiator, remoteId) {
    this.session = session
    this.isInitiator = isInitiator
    this.remoteId = remoteId
    this.stream = null
    this.remote = null
    this.keyString = null
    this._onclose = null
  }

  createStream() {
    if (this.stream) return

    this.stream = this.session._server._createStream({
      firewall: this._onfirewall.bind(this)
    })
  }

  activate(keyString) {
    const stream = this.stream

    this.keyString = keyString
    this._onclose = () => {
      if (this.stream !== stream) return

      this.stream = null
      unlink(this)
      this.session._links.delete(keyString)
      this.session._server._untrackActiveLink(keyString)
      this.session._server.stats.streams.closed++
    }

    stream.on('error', this.session._onerror).on('close', this._onclose).relayTo(this.remote.stream)

    this.session._pairing.delete(keyString)
    this.session._links.set(keyString, this)
    this.session._server._trackActiveLink(keyString)
    this.session._server.stats.streams.opened++
  }

  destroy(err) {
    const stream = this.stream

    if (!stream) return

    this.stream = null

    stream.off('close', this._onclose)
    this._onclose = null

    stream.off('error', this.session._onerror)
    unlink(this)
    this.session._links.delete(this.keyString)
    this.session._server._untrackActiveLink(this.keyString)
    this.session._server.stats.streams.closed++
    stream.on('error', noop).destroy(err)
  }

  destroyPair(err) {
    const remote = this.remote

    this.destroy(err)
    if (remote) remote.destroy(err)
  }

  _onfirewall(socket, port, host) {
    this.stream.connect(socket, this.remoteId, port, host)

    return false
  }
}

exports.Client = class BlindRelayClient extends EventEmitter {
  static _clients = new WeakMap()

  static from(stream, opts) {
    let client = this._clients.get(stream)
    if (client) return client
    client = new this(stream, opts)
    this._clients.set(stream, client)
    return client
  }

  constructor(stream, opts = {}) {
    super()

    const { id, handshake, handshakeEncoding } = opts

    this._mux = Protomux.from(stream)

    this._channel = this._mux.createChannel({
      protocol: 'blind-relay',
      id,
      handshake: handshake ? handshakeEncoding || c.raw : null,
      onopen: this._onopen.bind(this),
      onclose: this._onclose.bind(this),
      ondestroy: this._ondestroy.bind(this)
    })

    this._pair = this._channel.addMessage({
      encoding: m.pair,
      onmessage: this._onpair.bind(this)
    })

    this._unpair = this._channel.addMessage({
      encoding: m.unpair
    })

    this._ending = false
    this._destroyed = false
    this._error = null
    this._requests = new Map()

    this._channel.open(handshake)
  }

  get closed() {
    return this._channel.closed
  }

  get mux() {
    return this._mux
  }

  get stream() {
    return this._mux.stream
  }

  get requests() {
    return this._requests.values()
  }

  _onopen() {
    this.emit('open')
  }

  _onclose() {
    this._ending = Promise.resolve()

    const err = this._error || errors.CHANNEL_CLOSED()

    for (const request of this._requests.values()) {
      request.destroy(err)
    }

    this._requests.clear()

    this.constructor._clients.delete(this.stream)

    this.emit('close')
  }

  _ondestroy() {
    this._destroyed = true
    this.emit('destroy')
  }

  _onpair({ isInitiator, token, id: remoteId }) {
    const request = this._requests.get(token.toString('hex'))

    if (request === undefined || request.isInitiator !== isInitiator) return

    request.push(remoteId)
    request.push(null)

    this.emit('pair', request.isInitiator, request.token, request.stream, remoteId)
  }

  pair(isInitiator, token, stream) {
    if (this._destroyed) throw errors.CHANNEL_DESTROYED()

    const keyString = token.toString('hex')

    if (this._requests.has(keyString)) throw errors.ALREADY_PAIRING()

    const request = new BlindRelayRequest(this, isInitiator, token, stream)

    this._requests.set(keyString, request)

    return request
  }

  unpair(token) {
    if (this._destroyed) throw errors.CHANNEL_DESTROYED()

    const request = this._requests.get(token.toString('hex'))

    if (request) request.destroy(errors.PAIRING_CANCELLED())

    this._unpair.send({ token })
  }

  cork() {
    this._channel.cork()
  }

  uncork() {
    this._channel.uncork()
  }

  async end() {
    if (this._ending) return this._ending

    this._ending = EventEmitter.once(this, 'close')
    this._endMaybe()

    return this._ending
  }

  _endMaybe() {
    if (this._ending && this._requests.size === 0) {
      this._channel.close()
    }
  }

  destroy(err) {
    if (this._destroyed) return
    this._destroyed = true

    this._error = err || errors.CHANNEL_DESTROYED()
    this._channel.close()
  }
}

class BlindRelayRequest extends Readable {
  constructor(client, isInitiator, token, stream) {
    super()

    this.client = client
    this.isInitiator = isInitiator
    this.token = token
    this.stream = stream
  }

  _open(cb) {
    if (this.client._destroyed) return cb(errors.CHANNEL_DESTROYED())

    this.client._pair.send({
      isInitiator: this.isInitiator,
      token: this.token,
      id: this.stream.id,
      seq: 0
    })

    cb(null)
  }

  _destroy(cb) {
    this.client._requests.delete(this.token.toString('hex'))

    cb(null)

    this.client._endMaybe()
  }
}

exports.token = function token(buf = b4a.allocUnsafe(32)) {
  sodium.randombytes_buf(buf)
  return buf
}

function noop() {}

function unlink(link) {
  const remote = link.remote

  link.remote = null

  if (remote && remote.remote === link) {
    remote.remote = null
  }
}

function createStats() {
  const stats = {
    sessions: {
      accepted: 0,
      opened: 0,
      closed: 0,
      get active() {
        return this.accepted - this.closed
      }
    },
    pairings: {
      requested: 0,
      matched: 0,
      cancelled: 0,
      pending: 0,
      active: 0
    },
    streams: {
      opened: 0,
      closed: 0,
      errors: 0,
      get active() {
        return this.opened - this.closed
      }
    }
  }

  return stats
}

const m = (exports.messages = {})

const flags = bitfield(7)

m.pair = {
  preencode(state, m) {
    flags.preencode(state)
    c.fixed32.preencode(state, m.token)
    c.uint.preencode(state, m.id)
    c.uint.preencode(state, m.seq)
  },
  encode(state, m) {
    flags.encode(state, bits.of(m.isInitiator))
    c.fixed32.encode(state, m.token)
    c.uint.encode(state, m.id)
    c.uint.encode(state, m.seq)
  },
  decode(state) {
    const [isInitiator] = bits.iterator(flags.decode(state))

    return {
      isInitiator,
      token: c.fixed32.decode(state),
      id: c.uint.decode(state),
      seq: c.uint.decode(state)
    }
  }
}

m.unpair = {
  preencode(state, m) {
    flags.preencode(state)
    c.fixed32.preencode(state, m.token)
  },
  encode(state, m) {
    flags.encode(state, bits.of())
    c.fixed32.encode(state, m.token)
  },
  decode(state) {
    flags.decode(state)

    return {
      token: c.fixed32.decode(state)
    }
  }
}
module.exports = class BlindRelayError extends Error {
  constructor(msg, code, fn = BlindRelayError) {
    super(`${code}: ${msg}`)
    this.code = code

    if (Error.captureStackTrace) {
      Error.captureStackTrace(this, fn)
    }
  }

  get name() {
    return 'BlindRelayError'
  }

  static DUPLICATE_CHANNEL(msg = 'Duplicate channel') {
    return new BlindRelayError(msg, 'DUPLICATE_CHANNEL', BlindRelayError.DUPLICATE_CHANNEL)
  }

  static CHANNEL_CLOSED(msg = 'Channel closed') {
    return new BlindRelayError(msg, 'CHANNEL_CLOSED', BlindRelayError.CHANNEL_CLOSED)
  }

  static CHANNEL_DESTROYED(msg = 'Channel destroyed') {
    return new BlindRelayError(msg, 'CHANNEL_DESTROYED', BlindRelayError.CHANNEL_DESTROYED)
  }

  static ALREADY_PAIRING(msg = 'Already pairing') {
    return new BlindRelayError(msg, 'ALREADY_PAIRING', BlindRelayError.ALREADY_PAIRING)
  }

  static PAIRING_CANCELLED(msg = 'Pairing cancelled') {
    return new BlindRelayError(msg, 'PAIRING_CANCELLED', BlindRelayError.PAIRING_CANCELLED)
  }
}
{
  "name": "blind-relay",
  "version": "1.6.1",
  "description": "Blind relay for UDX over Protomux channels",
  "main": "index.js",
  "files": [
    "index.js",
    "lib"
  ],
  "scripts": {
    "format": "prettier --write .",
    "lint": "prettier --check . && lunte",
    "test": "npm run test:bare",
    "test:bare": "brittle-bare --coverage test.mjs"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/blind-relay.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/blind-relay/issues"
  },
  "homepage": "https://github.com/holepunchto/blind-relay#readme",
  "imports": {
    "events": {
      "bare": "bare-events",
      "default": "events"
    }
  },
  "dependencies": {
    "b4a": "^1.6.4",
    "bare-events": "^2.2.0",
    "bits-to-bytes": "^1.3.0",
    "compact-encoding": "^3.0.0",
    "compact-encoding-bitfield": "^1.0.0",
    "protomux": "^3.5.1",
    "sodium-universal": "^5.0.0",
    "streamx": "^2.15.1"
  },
  "devDependencies": {
    "brittle": "^3.2.1",
    "hyperdht": "^6.6.1",
    "lunte": "^1.0.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^2.0.0",
    "udx-native": "^1.6.1"
  }
}
// https://ipinfo.io/bogon

const b4a = require('b4a')
const c = require('compact-encoding')

module.exports = exports = function isBogon (ip) {
  return isBogonIP(ensureBuffer(ip))
}

exports.isBogon = exports

exports.isPrivate = function isPrivate (ip) {
  return isPrivateIP(ensureBuffer(ip))
}

exports.isReserved = function isReserved (ip) {
  return isReservedIP(ensureBuffer(ip))
}

function isBogonIP (ip) {
  return isPrivateIP(ip) || isReservedIP(ip)
}

function isPrivateIP (ip) {
  return ip.byteLength === 4 ? isPrivateIPv4(ip) : false // IPv6 has no private IPs
}

function isPrivateIPv4 (ip) {
  return (
    // 10.0.0.0/8  Private-use networks
    (ip[0] === 10) ||
    // 100.64.0.0/10 Carrier-grade NAT
    (ip[0] === 100 && ip[1] >= 64 && ip[1] <= 127) ||
    // 127.0.0.0/8 Loopback + Name collision occurrence (127.0.53.53)
    (ip[0] === 127) ||
    // 169.254.0.0/16  Link local
    (ip[0] === 169 && ip[1] === 254) ||
    // 172.16.0.0/12 Private-use networks
    (ip[0] === 172 && ip[1] >= 16 && ip[1] <= 31) ||
    // 192.168.0.0/16  Private-use networks
    (ip[0] === 192 && ip[1] === 168)
  )
}

function isReservedIP (ip) {
  return ip.byteLength === 4 ? isReservedIPv4(ip) : isReservedIPv6(ip)
}

function isReservedIPv4 (ip) {
  return (
    // 0.0.0.0/8 "This" network
    (ip[0] === 0) ||
    // 192.0.0.0/24  IETF protocol assignments
    (ip[0] === 192 && ip[1] === 0 && ip[2] === 0) ||
    // 192.0.2.0/24  TEST-NET-1
    (ip[0] === 192 && ip[1] === 0 && ip[2] === 2) ||
    // 198.18.0.0/15 Network interconnect device benchmark testing
    (ip[0] === 198 && ip[1] >= 18 && ip[1] <= 19) ||
    // 198.51.100.0/24 TEST-NET-2
    (ip[0] === 198 && ip[1] === 51 && ip[2] === 100) ||
    // 203.0.113.0/24  TEST-NET-3
    (ip[0] === 203 && ip[1] === 0 && ip[2] === 113) ||
    // 224.0.0.0/4 Multicast
    (ip[0] >= 224 && ip[0] <= 239) ||
    // 240.0.0.0/4 Reserved for future use
    (ip[0] >= 240) ||
    // 255.255.255.255/32
    (ip[0] === 255 && ip[1] === 255 && ip[2] === 255 && ip[3] === 255)
  )
}

function isReservedIPv6 (ip) {
  return (
    // ::/128 Node-scope unicast unspecified address
    // ::1/128 Node-scope unicast loopback address
    (
      ip[0] === 0 && ip[1] === 0 && ip[2] === 0 && ip[3] === 0 && ip[4] === 0 &&
      ip[5] === 0 && ip[6] === 0 && ip[7] === 0 && ip[8] === 0 && ip[9] === 0 &&
      ip[10] === 0 && ip[11] === 0 && ip[12] === 0 && ip[13] === 0 && ip[14] === 0 &&
      ip[15] <= 1
    ) ||
    // ::ffff:0:0/96 IPv4-mapped addresses
    // ::/96 IPv4-compatible addresses
    (
      ip[0] === 0 && ip[1] === 0 && ip[2] === 0 && ip[3] === 0 && ip[4] === 0 &&
      ip[5] === 0 && ip[6] === 0 && ip[7] === 0 && ip[8] === 0 && ip[9] === 0 &&
      (ip[10] === 0 || ip[10] === 0xff) &&
      (ip[11] === 0 || ip[11] === 0xff)
    ) ||
    // 100::/64 Remotely triggered black hole addresses
    (ip[0] === 0x01 && ip[1] === 0 && ip[2] === 0 && ip[3] === 0 && ip[4] === 0 && ip[5] === 0 && ip[6] === 0 && ip[7] === 0) ||
    // 2001:10::/28 Overlay routable cryptographic hash identifiers (ORCHID)
    (ip[0] === 0x20 && ip[1] === 0x01 && ip[2] === 0 && ip[3] >= 0x10 && ip[3] <= 0x1f) ||
    // 2001:20::/28 Overlay routable cryptographic hash identifiers version 2 (ORCHIDv2)
    (ip[0] === 0x20 && ip[1] === 0x01 && ip[2] === 0 && ip[3] >= 0x20 && ip[3] <= 0x2f) ||
    // 2001:db8::/32 Documentation prefix
    (ip[0] === 0x20 && ip[1] === 0x01 && ip[2] === 0x0d && ip[3] === 0xb8) ||
    // fc00::/7 Unique local addresses (ULA)
    (ip[0] >= 0xfc && ip[0] <= 0xfd) ||
    // fe80::/10 Link-local unicast
    (ip[0] === 0xfe && ip[1] >= 0x80 && ip[1] <= 0xbf) ||
    // ff00::/8 Multicast
    (ip[0] === 0xff)
  )
}

const state = c.state(0, 0, b4a.allocUnsafe(1 /* family */ + 16))

function ensureBuffer (ip) {
  if (b4a.isBuffer(ip)) return ip

  c.ip.preencode(state, ip)
  c.ip.encode(state, ip)

  const buffer = state.buffer.subarray(1 /* family */, state.end)

  state.start = 0
  state.end = 0

  return buffer
}
{
  "name": "bogon",
  "version": "1.3.0",
  "description": "Check if an IP is a bogon",
  "main": "index.js",
  "scripts": {
    "test": "standard && brittle test.mjs"
  },
  "dependencies": {
    "compact-encoding": "^3.0.0",
    "compact-encoding-net": "^1.2.0"
  },
  "devDependencies": {
    "brittle": "^3.0.4",
    "nanobench": "^2.1.1",
    "standard": "^17.0.0"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/mafintosh/bogon.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/mafintosh/bogon/issues"
  },
  "homepage": "https://github.com/mafintosh/bogon"
}
const c = require('compact-encoding')

module.exports = function bitfield(length) {
  if (length > 64) {
    throw new RangeError('Bitfield cannot be larger than 64 bits')
  }

  let byteLength
  if (length < 8) byteLength = 1
  else if (length <= 16) byteLength = 2
  else if (length <= 32) byteLength = 4
  else byteLength = 8

  return {
    preencode(state) {
      state.end++ // Length byte, used for data when byteLength === 1

      if (byteLength === 1);
      else if (byteLength === 2) c.uint16.preencode(state)
      else if (byteLength === 4) c.uint32.preencode(state)
      else c.uint64.preencode(state)
    },

    encode(state, b) {
      if (byteLength === 1);
      else if (byteLength === 2) c.uint8.encode(state, 0xfd)
      else if (byteLength === 4) c.uint8.encode(state, 0xfe)
      else c.uint8.encode(state, 0xff)

      if (typeof b === 'number') {
        if (byteLength === 1) c.uint8.encode(state, b)
        else if (byteLength === 2) c.uint16.encode(state, b)
        else if (byteLength === 4) c.uint32.encode(state, b)
        else c.uint64.encode(state, b)
      } else {
        state.buffer.set(b, state.start)

        if (b.byteLength < byteLength) {
          // Zero-fill the rest of the byte length.
          state.buffer.fill(
            0,
            state.start + b.byteLength,
            state.start + byteLength
          )
        }

        state.start += byteLength
      }
    },

    decode(state) {
      const byte = state.buffer[state.start]

      let byteLength
      if (byte <= 0xfc) byteLength = 1
      else if (byte === 0xfd) byteLength = 2
      else if (byte === 0xfe) byteLength = 4
      else byteLength = 8

      if (byteLength > 1) state.start++ // Skip the length byte

      if (state.end - state.start < byteLength) throw new Error('Out of bounds')

      const b = state.buffer.subarray(state.start, (state.start += byteLength))

      return length <= 8 ? b.subarray(0, 1) : b
    }
  }
}
{
  "name": "compact-encoding-bitfield",
  "version": "1.1.0",
  "description": "Compact codec for bitfields",
  "main": "index.js",
  "files": [
    "index.js"
  ],
  "scripts": {
    "format": "prettier --write .",
    "lint": "prettier --check . && lunte",
    "test": "npm run test:node && npm run test:bare",
    "test:node": "brittle-node --coverage test.mjs",
    "test:bare": "brittle-bare --coverage test.mjs"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/compact-encoding/compact-encoding-bitfield.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/compact-encoding/compact-encoding-bitfield/issues"
  },
  "homepage": "https://github.com/compact-encoding/compact-encoding-bitfield#readme",
  "imports": {
    "util": {
      "bare": "bare-utils",
      "default": "util"
    }
  },
  "dependencies": {
    "compact-encoding": "^3.0.0"
  },
  "devDependencies": {
    "bare-utils": "^1.5.1",
    "brittle": "^3.1.0",
    "lunte": "^1.3.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^1.0.0"
  }
}
const LE = (exports.LE = new Uint8Array(new Uint16Array([0xff]).buffer)[0] === 0xff)

exports.BE = !LE
const b4a = require('b4a')

const { BE } = require('./endian')

// Zig-zag doubles the magnitude of a value before it is written as a uint, so
// an int only reaches half as far as a uint of the same width does.
const MAX_SAFE_INT = 2 ** 52 - 1
const MIN_SAFE_INT = -(2 ** 52)

exports.state = function (start = 0, end = 0, buffer = null) {
  return { start, end, buffer }
}

const raw = (exports.raw = require('./raw'))

const uint = (exports.uint = {
  preencode(state, n) {
    state.end += n <= 0xfc ? 1 : n <= 0xffff ? 3 : n <= 0xffffffff ? 5 : 9
  },
  encode(state, n) {
    if (n <= 0xfc) uint8.encode(state, n)
    else if (n <= 0xffff) {
      state.buffer[state.start++] = 0xfd
      uint16.encode(state, n)
    } else if (n <= 0xffffffff) {
      state.buffer[state.start++] = 0xfe
      uint32.encode(state, n)
    } else {
      state.buffer[state.start++] = 0xff
      uint64.encode(state, n)
    }
  },
  decode(state) {
    const a = uint8.decode(state)
    if (a <= 0xfc) return a
    if (a === 0xfd) return uint16.decode(state)
    if (a === 0xfe) return uint32.decode(state)
    return uint64.decode(state)
  }
})

const uint8 = (exports.uint8 = {
  preencode(state, n) {
    state.end += 1
  },
  encode(state, n) {
    validateUint(n)
    state.buffer[state.start++] = n
  },
  decode(state) {
    if (state.start >= state.end) throw new Error('Out of bounds')
    return state.buffer[state.start++]
  }
})

const uint16 = (exports.uint16 = {
  preencode(state, n) {
    state.end += 2
  },
  encode(state, n) {
    validateUint(n)
    state.buffer[state.start++] = n
    state.buffer[state.start++] = n >>> 8
  },
  decode(state) {
    if (state.end - state.start < 2) throw new Error('Out of bounds')
    return state.buffer[state.start++] + state.buffer[state.start++] * 0x100
  }
})

const uint24 = (exports.uint24 = {
  preencode(state, n) {
    state.end += 3
  },
  encode(state, n) {
    validateUint(n)
    state.buffer[state.start++] = n
    state.buffer[state.start++] = n >>> 8
    state.buffer[state.start++] = n >>> 16
  },
  decode(state) {
    if (state.end - state.start < 3) throw new Error('Out of bounds')
    return (
      state.buffer[state.start++] +
      state.buffer[state.start++] * 0x100 +
      state.buffer[state.start++] * 0x10000
    )
  }
})

const uint32 = (exports.uint32 = {
  preencode(state, n) {
    state.end += 4
  },
  encode(state, n) {
    validateUint(n)
    state.buffer[state.start++] = n
    state.buffer[state.start++] = n >>> 8
    state.buffer[state.start++] = n >>> 16
    state.buffer[state.start++] = n >>> 24
  },
  decode(state) {
    if (state.end - state.start < 4) throw new Error('Out of bounds')
    return (
      state.buffer[state.start++] +
      state.buffer[state.start++] * 0x100 +
      state.buffer[state.start++] * 0x10000 +
      state.buffer[state.start++] * 0x1000000
    )
  }
})

const uint32be = (exports.uint32be = {
  preencode(state, n) {
    state.end += 4
  },
  encode(state, n) {
    validateUint(n)
    state.buffer[state.start++] = n >>> 24
    state.buffer[state.start++] = n >>> 16
    state.buffer[state.start++] = n >>> 8
    state.buffer[state.start++] = n
  },
  decode(state) {
    if (state.end - state.start < 4) throw new Error('Out of bounds')
    return (
      state.buffer[state.start++] * 0x1000000 +
      state.buffer[state.start++] * 0x10000 +
      state.buffer[state.start++] * 0x100 +
      state.buffer[state.start++]
    )
  }
})

const uint40 = (exports.uint40 = {
  preencode(state, n) {
    state.end += 5
  },
  encode(state, n) {
    validateUint(n)
    const r = Math.floor(n / 0x100)
    uint8.encode(state, n)
    uint32.encode(state, r)
  },
  decode(state) {
    if (state.end - state.start < 5) throw new Error('Out of bounds')
    return uint8.decode(state) + 0x100 * uint32.decode(state)
  }
})

const uint48 = (exports.uint48 = {
  preencode(state, n) {
    state.end += 6
  },
  encode(state, n) {
    validateUint(n)
    const r = Math.floor(n / 0x10000)
    uint16.encode(state, n)
    uint32.encode(state, r)
  },
  decode(state) {
    if (state.end - state.start < 6) throw new Error('Out of bounds')
    return uint16.decode(state) + 0x10000 * uint32.decode(state)
  }
})

const uint56 = (exports.uint56 = {
  preencode(state, n) {
    state.end += 7
  },
  encode(state, n) {
    validateUint(n)
    const r = Math.floor(n / 0x1000000)
    uint24.encode(state, n)
    uint32.encode(state, r)
  },
  decode(state) {
    if (state.end - state.start < 7) throw new Error('Out of bounds')
    return validateSafeUint(uint24.decode(state) + 0x1000000 * uint32.decode(state))
  }
})

const uint64 = (exports.uint64 = {
  preencode(state, n) {
    state.end += 8
  },
  encode(state, n) {
    validateUint(n)
    const r = Math.floor(n / 0x100000000)
    uint32.encode(state, n)
    uint32.encode(state, r)
  },
  decode(state) {
    if (state.end - state.start < 8) throw new Error('Out of bounds')
    return validateSafeUint(uint32.decode(state) + 0x100000000 * uint32.decode(state))
  }
})

exports.uint64be = {
  preencode(state, n) {
    state.end += 8
  },
  encode(state, n) {
    validateUint(n)
    const r = Math.floor(n / 0x100000000)
    uint32be.encode(state, r)
    uint32be.encode(state, n)
  },
  decode(state) {
    if (state.end - state.start < 8) throw new Error('Out of bounds')
    return validateSafeUint(0x100000000 * uint32be.decode(state) + uint32be.decode(state))
  }
}

const int = (exports.int = zigZagInt(uint))
exports.int8 = zigZagInt(uint8)
exports.int16 = zigZagInt(uint16)
exports.int24 = zigZagInt(uint24)
exports.int32 = zigZagInt(uint32)
exports.int40 = zigZagInt(uint40)
exports.int48 = zigZagInt(uint48)
exports.int56 = zigZagInt(uint56)
exports.int64 = zigZagInt(uint64)

const biguint64 = (exports.biguint64 = {
  preencode(state, n) {
    state.end += 8
  },
  encode(state, n) {
    const view = new DataView(state.buffer.buffer, state.start + state.buffer.byteOffset, 8)
    view.setBigUint64(0, n, true) // little endian
    state.start += 8
  },
  decode(state) {
    if (state.end - state.start < 8) throw new Error('Out of bounds')
    const view = new DataView(state.buffer.buffer, state.start + state.buffer.byteOffset, 8)
    const n = view.getBigUint64(0, true) // little endian
    state.start += 8
    return n
  }
})

exports.bigint64 = zigZagBigInt(biguint64)

const biguint = (exports.biguint = {
  preencode(state, n) {
    let len = 0
    for (let m = n; m; m = m >> 64n) len++
    uint.preencode(state, len)
    state.end += 8 * len
  },
  encode(state, n) {
    let len = 0
    for (let m = n; m; m = m >> 64n) len++
    uint.encode(state, len)
    const view = new DataView(state.buffer.buffer, state.start + state.buffer.byteOffset, 8 * len)
    for (let m = n, i = 0; m; m = m >> 64n, i += 8) {
      view.setBigUint64(i, BigInt.asUintN(64, m), true) // little endian
    }
    state.start += 8 * len
  },
  decode(state) {
    const len = uint.decode(state)
    if (state.end - state.start < 8 * len) throw new Error('Out of bounds')
    const view = new DataView(state.buffer.buffer, state.start + state.buffer.byteOffset, 8 * len)
    let n = 0n
    for (let i = len - 1; i >= 0; i--) n = (n << 64n) + view.getBigUint64(i * 8, true) // little endian
    state.start += 8 * len
    return n
  }
})

exports.bigint = zigZagBigInt(biguint)

exports.lexint = require('./lexint')

exports.float32 = {
  preencode(state, n) {
    state.end += 4
  },
  encode(state, n) {
    const view = new DataView(state.buffer.buffer, state.start + state.buffer.byteOffset, 4)
    view.setFloat32(0, n, true) // little endian
    state.start += 4
  },
  decode(state) {
    if (state.end - state.start < 4) throw new Error('Out of bounds')
    const view = new DataView(state.buffer.buffer, state.start + state.buffer.byteOffset, 4)
    const float = view.getFloat32(0, true) // little endian
    state.start += 4
    return float
  }
}

exports.float64 = {
  preencode(state, n) {
    state.end += 8
  },
  encode(state, n) {
    const view = new DataView(state.buffer.buffer, state.start + state.buffer.byteOffset, 8)
    view.setFloat64(0, n, true) // little endian
    state.start += 8
  },
  decode(state) {
    if (state.end - state.start < 8) throw new Error('Out of bounds')
    const view = new DataView(state.buffer.buffer, state.start + state.buffer.byteOffset, 8)
    const float = view.getFloat64(0, true) // little endian
    state.start += 8
    return float
  }
}

const buffer = (exports.buffer = {
  preencode(state, b) {
    uint8array.preencode(state, b)
  },
  encode(state, b) {
    uint8array.encode(state, b)
  },
  decode(state) {
    const len = uint.decode(state)
    if (state.end - state.start < len) throw new Error('Out of bounds')
    return state.buffer.subarray(state.start, (state.start += len))
  }
})

exports.optionalBuffer = {
  preencode(state, b) {
    if (b) uint8array.preencode(state, b)
    else state.end++
  },
  encode(state, b) {
    if (b) uint8array.encode(state, b)
    else state.buffer[state.start++] = 0
  },
  decode(state) {
    const len = uint.decode(state)
    if (len === 0) return null
    if (state.end - state.start < len) throw new Error('Out of bounds')
    return state.buffer.subarray(state.start, (state.start += len))
  }
}

exports.binary = {
  ...buffer,
  preencode(state, b) {
    if (typeof b === 'string') utf8.preencode(state, b)
    else buffer.preencode(state, b)
  },
  encode(state, b) {
    if (typeof b === 'string') utf8.encode(state, b)
    else buffer.encode(state, b)
  }
}

exports.arraybuffer = {
  preencode(state, b) {
    uint.preencode(state, b.byteLength)
    state.end += b.byteLength
  },
  encode(state, b) {
    uint.encode(state, b.byteLength)

    const view = new Uint8Array(b)

    state.buffer.set(view, state.start)
    state.start += b.byteLength
  },
  decode(state) {
    const len = uint.decode(state)

    const b = new ArrayBuffer(len)
    const view = new Uint8Array(b)

    view.set(state.buffer.subarray(state.start, (state.start += len)))

    return b
  }
}

exports.bitarray = {
  preencode(state, m) {
    uint.preencode(state, m.length)
    state.end += Math.ceil(m.length / 8)
  },
  encode(state, m) {
    uint.encode(state, m.length)
    for (let i = 0; i < m.length; i += 8) {
      let byte = 0
      for (let j = 0; j < 8 && i + j < m.length; j++) {
        if (m[i + j]) byte |= 1 << j
      }
      state.buffer[state.start++] = byte
    }
  },
  decode(state) {
    const n = uint.decode(state)
    if (state.end - state.start < Math.ceil(n / 8)) throw new Error('Out of bounds')
    const m = new Array(n)
    for (let i = 0; i < n; i += 8) {
      const byte = state.buffer[state.start++]
      for (let j = 0; j < 8 && i + j < n; j++) {
        m[i + j] = (byte & (1 << j)) !== 0
      }
    }
    return m
  }
}

function typedarray(TypedArray, swap) {
  const n = TypedArray.BYTES_PER_ELEMENT

  return {
    preencode(state, b) {
      uint.preencode(state, b.length)
      state.end += b.byteLength
    },
    encode(state, b) {
      uint.encode(state, b.length)

      const view = new Uint8Array(b.buffer, b.byteOffset, b.byteLength)

      if (BE && swap) swap(view)

      state.buffer.set(view, state.start)
      state.start += b.byteLength
    },
    decode(state) {
      const len = uint.decode(state)

      let b = state.buffer.subarray(state.start, (state.start += len * n))
      if (b.byteLength !== len * n) throw new Error('Out of bounds')
      if (b.byteOffset % n !== 0) b = new Uint8Array(b)

      if (BE && swap) swap(b)

      return new TypedArray(b.buffer, b.byteOffset, b.byteLength / n)
    }
  }
}

const uint8array = (exports.uint8array = typedarray(Uint8Array))
exports.uint16array = typedarray(Uint16Array, b4a.swap16)
exports.uint32array = typedarray(Uint32Array, b4a.swap32)

exports.int8array = typedarray(Int8Array)
exports.int16array = typedarray(Int16Array, b4a.swap16)
exports.int32array = typedarray(Int32Array, b4a.swap32)

exports.biguint64array = typedarray(BigUint64Array, b4a.swap64)
exports.bigint64array = typedarray(BigInt64Array, b4a.swap64)

exports.float32array = typedarray(Float32Array, b4a.swap32)
exports.float64array = typedarray(Float64Array, b4a.swap64)

function string(encoding) {
  return {
    preencode(state, s) {
      const len = b4a.byteLength(s, encoding)
      uint.preencode(state, len)
      state.end += len
    },
    encode(state, s) {
      const len = b4a.byteLength(s, encoding)
      uint.encode(state, len)
      b4a.write(state.buffer, s, state.start, encoding)
      state.start += len
    },
    decode(state) {
      const len = uint.decode(state)
      if (state.end - state.start < len) throw new Error('Out of bounds')
      return b4a.toString(state.buffer, encoding, state.start, (state.start += len))
    },
    fixed(n) {
      return {
        preencode(state) {
          state.end += n
        },
        encode(state, s) {
          b4a.write(state.buffer, s, state.start, n, encoding)
          state.start += n
        },
        decode(state) {
          if (state.end - state.start < n) throw new Error('Out of bounds')
          return b4a.toString(state.buffer, encoding, state.start, (state.start += n))
        }
      }
    }
  }
}

const utf8 = (exports.string = exports.utf8 = string('utf-8'))
exports.ascii = string('ascii')
exports.hex = string('hex')
exports.base64 = string('base64')
exports.ucs2 = exports.utf16le = string('utf16le')

exports.bool = {
  preencode(state, b) {
    state.end++
  },
  encode(state, b) {
    state.buffer[state.start++] = b ? 1 : 0
  },
  decode(state) {
    if (state.start >= state.end) throw Error('Out of bounds')
    return state.buffer[state.start++] === 1
  }
}

const fixed = (exports.fixed = function fixed(n) {
  return {
    preencode(state, s) {
      if (s.byteLength !== n) throw new Error('Incorrect buffer size')
      state.end += n
    },
    encode(state, s) {
      state.buffer.set(s, state.start)
      state.start += n
    },
    decode(state) {
      if (state.end - state.start < n) throw new Error('Out of bounds')
      return state.buffer.subarray(state.start, (state.start += n))
    }
  }
})

exports.fixed32 = fixed(32)
exports.fixed64 = fixed(64)

exports.array = function array(enc) {
  return {
    preencode(state, list) {
      uint.preencode(state, list.length)
      for (let i = 0; i < list.length; i++) enc.preencode(state, list[i])
    },
    encode(state, list) {
      uint.encode(state, list.length)
      for (let i = 0; i < list.length; i++) enc.encode(state, list[i])
    },
    decode(state) {
      const len = uint.decode(state)
      if (len > 0x100000) throw new Error('Array is too big')
      const arr = new Array(len)
      for (let i = 0; i < len; i++) arr[i] = enc.decode(state)
      return arr
    }
  }
}

exports.frame = function frame(enc) {
  const dummy = exports.state()

  return {
    preencode(state, m) {
      const end = state.end
      enc.preencode(state, m)
      uint.preencode(state, state.end - end)
    },
    encode(state, m) {
      dummy.end = 0
      enc.preencode(dummy, m)
      uint.encode(state, dummy.end)
      enc.encode(state, m)
    },
    decode(state) {
      const end = state.end
      const len = uint.decode(state)
      state.end = state.start + len
      const m = enc.decode(state)
      state.start = state.end
      state.end = end
      return m
    }
  }
}

exports.date = {
  preencode(state, d) {
    int.preencode(state, d.getTime())
  },
  encode(state, d) {
    int.encode(state, d.getTime())
  },
  decode(state, d) {
    return new Date(int.decode(state))
  }
}

exports.json = {
  preencode(state, v) {
    utf8.preencode(state, JSON.stringify(v))
  },
  encode(state, v) {
    utf8.encode(state, JSON.stringify(v))
  },
  decode(state) {
    return JSON.parse(utf8.decode(state))
  }
}

exports.ndjson = {
  preencode(state, v) {
    utf8.preencode(state, JSON.stringify(v) + '\n')
  },
  encode(state, v) {
    utf8.encode(state, JSON.stringify(v) + '\n')
  },
  decode(state) {
    return JSON.parse(utf8.decode(state))
  }
}

// simple helper for when you want to just express nothing
exports.none = {
  preencode(state, n) {
    // do nothing
  },
  encode(state, n) {
    // do nothing
  },
  decode(state) {
    return null
  }
}

// "any" encoders here for helping just structure any object without schematising it

const anyArray = {
  preencode(state, arr) {
    uint.preencode(state, arr.length)
    for (let i = 0; i < arr.length; i++) {
      any.preencode(state, arr[i])
    }
  },
  encode(state, arr) {
    uint.encode(state, arr.length)
    for (let i = 0; i < arr.length; i++) {
      any.encode(state, arr[i])
    }
  },
  decode(state) {
    const arr = []
    let len = uint.decode(state)
    while (len-- > 0) {
      arr.push(any.decode(state))
    }
    return arr
  }
}

const anyObject = {
  preencode(state, o) {
    const keys = Object.keys(o)
    uint.preencode(state, keys.length)
    for (const key of keys) {
      utf8.preencode(state, key)
      any.preencode(state, o[key])
    }
  },
  encode(state, o) {
    const keys = Object.keys(o)
    uint.encode(state, keys.length)
    for (const key of keys) {
      utf8.encode(state, key)
      any.encode(state, o[key])
    }
  },
  decode(state) {
    let len = uint.decode(state)
    const o = {}
    while (len-- > 0) {
      const key = utf8.decode(state)
      o[key] = any.decode(state)
    }
    return o
  }
}

const anyTypes = [
  exports.none,
  exports.bool,
  exports.string,
  exports.buffer,
  exports.uint,
  exports.int,
  exports.float64,
  anyArray,
  anyObject,
  exports.date
]

const any = (exports.any = {
  preencode(state, o) {
    const t = getType(o)
    uint.preencode(state, t)
    anyTypes[t].preencode(state, o)
  },
  encode(state, o) {
    const t = getType(o)
    uint.encode(state, t)
    anyTypes[t].encode(state, o)
  },
  decode(state) {
    const t = uint.decode(state)
    if (t >= anyTypes.length) throw new Error('Unknown type: ' + t)
    return anyTypes[t].decode(state)
  }
})

const port = (exports.port = uint16)

const address = (host, family) => {
  return {
    preencode(state, m) {
      host.preencode(state, m.host)
      port.preencode(state, m.port)
    },
    encode(state, m) {
      host.encode(state, m.host)
      port.encode(state, m.port)
    },
    decode(state) {
      return {
        host: host.decode(state),
        family,
        port: port.decode(state)
      }
    }
  }
}

const ipv4 = (exports.ipv4 = {
  preencode(state) {
    state.end += 4
  },
  encode(state, string) {
    const start = state.start
    const end = start + 4

    let i = 0

    while (i < string.length) {
      let n = 0
      let c

      while (i < string.length && (c = string.charCodeAt(i++)) !== /* . */ 0x2e) {
        n = n * 10 + (c - /* 0 */ 0x30)
      }

      state.buffer[state.start++] = n
    }

    state.start = end
  },
  decode(state) {
    if (state.end - state.start < 4) throw new Error('Out of bounds')
    return (
      state.buffer[state.start++] +
      '.' +
      state.buffer[state.start++] +
      '.' +
      state.buffer[state.start++] +
      '.' +
      state.buffer[state.start++]
    )
  }
})

exports.ipv4Address = address(ipv4, 4)

const ipv6 = (exports.ipv6 = {
  preencode(state) {
    state.end += 16
  },
  encode(state, string) {
    const start = state.start
    const end = start + 16

    let i = 0
    let split = null

    while (i < string.length) {
      let n = 0
      let c

      while (i < string.length && (c = string.charCodeAt(i++)) !== /* : */ 0x3a) {
        if (c >= 0x30 && c <= 0x39) n = n * 0x10 + (c - /* 0 */ 0x30)
        else if (c >= 0x41 && c <= 0x46) n = n * 0x10 + (c - /* A */ 0x41 + 10)
        else if (c >= 0x61 && c <= 0x66) n = n * 0x10 + (c - /* a */ 0x61 + 10)
      }

      state.buffer[state.start++] = n >>> 8
      state.buffer[state.start++] = n

      if (i < string.length && string.charCodeAt(i) === /* : */ 0x3a) {
        i++
        split = state.start
      }
    }

    if (split !== null) {
      const offset = end - state.start
      state.buffer.copyWithin(split + offset, split).fill(0, split, split + offset)
    }

    state.start = end
  },
  decode(state) {
    if (state.end - state.start < 16) throw new Error('Out of bounds')
    return (
      (state.buffer[state.start++] * 256 + state.buffer[state.start++]).toString(16) +
      ':' +
      (state.buffer[state.start++] * 256 + state.buffer[state.start++]).toString(16) +
      ':' +
      (state.buffer[state.start++] * 256 + state.buffer[state.start++]).toString(16) +
      ':' +
      (state.buffer[state.start++] * 256 + state.buffer[state.start++]).toString(16) +
      ':' +
      (state.buffer[state.start++] * 256 + state.buffer[state.start++]).toString(16) +
      ':' +
      (state.buffer[state.start++] * 256 + state.buffer[state.start++]).toString(16) +
      ':' +
      (state.buffer[state.start++] * 256 + state.buffer[state.start++]).toString(16) +
      ':' +
      (state.buffer[state.start++] * 256 + state.buffer[state.start++]).toString(16)
    )
  }
})

exports.ipv6Address = address(ipv6, 6)

const ip = (exports.ip = {
  preencode(state, string) {
    const family = string.includes(':') ? 6 : 4
    uint8.preencode(state, family)
    if (family === 4) ipv4.preencode(state)
    else ipv6.preencode(state)
  },
  encode(state, string) {
    const family = string.includes(':') ? 6 : 4
    uint8.encode(state, family)
    if (family === 4) ipv4.encode(state, string)
    else ipv6.encode(state, string)
  },
  decode(state) {
    const family = uint8.decode(state)
    if (family === 4) return ipv4.decode(state)
    else return ipv6.decode(state)
  }
})

exports.ipAddress = {
  preencode(state, m) {
    ip.preencode(state, m.host)
    port.preencode(state, m.port)
  },
  encode(state, m) {
    ip.encode(state, m.host)
    port.encode(state, m.port)
  },
  decode(state) {
    const family = uint8.decode(state)
    return {
      host: family === 4 ? ipv4.decode(state) : ipv6.decode(state),
      family,
      port: port.decode(state)
    }
  }
}

const record = (exports.record = function (keyEncoding, valueEncoding) {
  return {
    preencode(state, v) {
      const keys = Object.keys(v)
      uint.preencode(state, keys.length)
      for (const k of keys) {
        keyEncoding.preencode(state, k)
        valueEncoding.preencode(state, v[k])
      }
    },
    encode(state, v) {
      const keys = Object.keys(v)
      uint.encode(state, keys.length)
      for (const k of keys) {
        keyEncoding.encode(state, k)
        valueEncoding.encode(state, v[k])
      }
    },
    decode(state) {
      const out = Object.create(null)
      const keys = uint.decode(state)
      for (let i = 0; i < keys; i++) {
        out[keyEncoding.decode(state)] = valueEncoding.decode(state)
      }
      return out
    }
  }
})

exports.stringRecord = record(utf8, utf8)

function getType(o) {
  if (o === null || o === undefined) return 0
  if (typeof o === 'boolean') return 1
  if (typeof o === 'string') return 2
  if (b4a.isBuffer(o)) return 3
  if (typeof o === 'number') {
    if (Number.isInteger(o)) return o >= 0 ? 4 : 5
    return 6
  }
  if (Array.isArray(o)) return 7
  if (o instanceof Date) return 9
  if (typeof o === 'object') return 8

  throw new Error('Unsupported type for ' + o)
}

exports.from = function from(enc) {
  if (typeof enc === 'string') return fromNamed(enc)
  if (enc.preencode) return enc
  if (enc.encodingLength) return fromAbstractEncoder(enc)
  return fromCodec(enc)
}

function fromNamed(enc) {
  switch (enc) {
    case 'ascii':
      return raw.ascii
    case 'utf-8':
    case 'utf8':
      return raw.utf8
    case 'hex':
      return raw.hex
    case 'base64':
      return raw.base64
    case 'utf16-le':
    case 'utf16le':
    case 'ucs-2':
    case 'ucs2':
      return raw.ucs2
    case 'ndjson':
      return raw.ndjson
    case 'json':
      return raw.json
    case 'binary':
    default:
      return raw.binary
  }
}

function fromCodec(enc) {
  let tmpM = null
  let tmpBuf = null

  return {
    preencode(state, m) {
      tmpM = m
      tmpBuf = enc.encode(m)
      state.end += tmpBuf.byteLength
    },
    encode(state, m) {
      raw.encode(state, m === tmpM ? tmpBuf : enc.encode(m))
      tmpM = tmpBuf = null
    },
    decode(state) {
      return enc.decode(raw.decode(state))
    }
  }
}

function fromAbstractEncoder(enc) {
  return {
    preencode(state, m) {
      state.end += enc.encodingLength(m)
    },
    encode(state, m) {
      enc.encode(m, state.buffer, state.start)
      state.start += enc.encode.bytes
    },
    decode(state) {
      const m = enc.decode(state.buffer, state.start, state.end)
      state.start += enc.decode.bytes
      return m
    }
  }
}

exports.encode = function encode(enc, m) {
  const state = exports.state()
  enc.preencode(state, m)
  state.buffer = b4a.allocUnsafe(state.end)
  enc.encode(state, m)
  return state.buffer
}

exports.decode = function decode(enc, buffer) {
  return enc.decode(exports.state(0, buffer.byteLength, buffer))
}

function zigZagInt(enc) {
  return {
    preencode(state, n) {
      enc.preencode(state, zigZagEncodeInt(n))
    },
    encode(state, n) {
      enc.encode(state, zigZagEncodeInt(n))
    },
    decode(state) {
      return zigZagDecodeInt(enc.decode(state))
    }
  }
}

function zigZagDecodeInt(n) {
  return n === 0 ? n : (n & 1) === 0 ? n / 2 : -(n + 1) / 2
}

function zigZagEncodeInt(n) {
  validateInt(n)
  // 0, -1, 1, -2, 2, ...
  return n < 0 ? 2 * -n - 1 : n === 0 ? 0 : 2 * n
}

function zigZagBigInt(enc) {
  return {
    preencode(state, n) {
      enc.preencode(state, zigZagEncodeBigInt(n))
    },
    encode(state, n) {
      enc.encode(state, zigZagEncodeBigInt(n))
    },
    decode(state) {
      return zigZagDecodeBigInt(enc.decode(state))
    }
  }
}

function zigZagDecodeBigInt(n) {
  return n === 0n ? n : (n & 1n) === 0n ? n / 2n : -(n + 1n) / 2n
}

function zigZagEncodeBigInt(n) {
  // 0, -1, 1, -2, 2, ...
  return n < 0n ? 2n * -n - 1n : n === 0n ? 0n : 2n * n
}

function validateSafeUint(n) {
  if (n <= Number.MAX_SAFE_INTEGER) return n // Handles NaN as well

  throw outsideUintRange()
}

function validateUint(n) {
  if (n >= 0 && n <= Number.MAX_SAFE_INTEGER) return n // Handles NaN as well

  throw outsideUintRange()
}

function validateInt(n) {
  if (n >= MIN_SAFE_INT && n <= MAX_SAFE_INT) return n // Handles NaN as well

  throw outsideIntRange()
}

// The validations above sit on the hottest paths in the library and are small
// enough to be inlined, which building a message inline would put a stop to.
// Kept out here, the message costs nothing until it is actually thrown.

function outsideUintRange() {
  return new Error(`uint must be between 0 and ${Number.MAX_SAFE_INTEGER}, use biguint`)
}

function outsideIntRange() {
  return new Error(`int must be between ${MIN_SAFE_INT} and ${MAX_SAFE_INT}, use bigint`)
}
module.exports = {
  preencode,
  encode,
  decode
}

function preencode(state, num) {
  if (num < 251) {
    state.end++
  } else if (num < 256) {
    state.end += 2
  } else if (num < 0x10000) {
    state.end += 3
  } else if (num < 0x1000000) {
    state.end += 4
  } else if (num < 0x100000000) {
    state.end += 5
  } else {
    state.end++
    const exp = Math.floor(Math.log(num) / Math.log(2)) - 32
    preencode(state, exp)
    state.end += 6
  }
}

function encode(state, num) {
  const max = 251
  const x = num - max

  if (num < max) {
    state.buffer[state.start++] = num
  } else if (num < 256) {
    state.buffer[state.start++] = max
    state.buffer[state.start++] = x
  } else if (num < 0x10000) {
    state.buffer[state.start++] = max + 1
    state.buffer[state.start++] = (x >> 8) & 0xff
    state.buffer[state.start++] = x & 0xff
  } else if (num < 0x1000000) {
    state.buffer[state.start++] = max + 2
    state.buffer[state.start++] = x >> 16
    state.buffer[state.start++] = (x >> 8) & 0xff
    state.buffer[state.start++] = x & 0xff
  } else if (num < 0x100000000) {
    state.buffer[state.start++] = max + 3
    state.buffer[state.start++] = x >> 24
    state.buffer[state.start++] = (x >> 16) & 0xff
    state.buffer[state.start++] = (x >> 8) & 0xff
    state.buffer[state.start++] = x & 0xff
  } else {
    // need to use Math here as bitwise ops are 32 bit
    const exp = Math.floor(Math.log(x) / Math.log(2)) - 32
    state.buffer[state.start++] = 0xff

    encode(state, exp)
    const rem = x / Math.pow(2, exp - 11)

    for (let i = 5; i >= 0; i--) {
      state.buffer[state.start++] = (rem / Math.pow(2, 8 * i)) & 0xff
    }
  }
}

function decode(state) {
  const max = 251

  if (state.end - state.start < 1) throw new Error('Out of bounds')

  const flag = state.buffer[state.start++]

  if (flag < max) return flag

  if (state.end - state.start < flag - max + 1) {
    throw new Error('Out of bounds.')
  }

  if (flag < 252) {
    return state.buffer[state.start++] + max
  }

  if (flag < 253) {
    return (state.buffer[state.start++] << 8) + state.buffer[state.start++] + max
  }

  if (flag < 254) {
    return (
      (state.buffer[state.start++] << 16) +
      (state.buffer[state.start++] << 8) +
      state.buffer[state.start++] +
      max
    )
  }

  // << 24 result may be interpreted as negative
  if (flag < 255) {
    return (
      state.buffer[state.start++] * 0x1000000 +
      (state.buffer[state.start++] << 16) +
      (state.buffer[state.start++] << 8) +
      state.buffer[state.start++] +
      max
    )
  }

  const exp = decode(state)

  if (state.end - state.start < 6) throw new Error('Out of bounds')

  let rem = 0
  for (let i = 5; i >= 0; i--) {
    rem += state.buffer[state.start++] * Math.pow(2, 8 * i)
  }

  return rem * Math.pow(2, exp - 11) + max
}
{
  "name": "compact-encoding",
  "version": "3.4.0",
  "description": "A series of compact encoding schemes for building small and fast parsers and serializers",
  "main": "index.js",
  "types": "index.d.ts",
  "files": [
    "endian.js",
    "endian.d.ts",
    "index.js",
    "index.d.ts",
    "lexint.js",
    "lexint.d.ts",
    "raw.js",
    "raw.d.ts"
  ],
  "dependencies": {
    "b4a": "^1.3.0"
  },
  "devDependencies": {
    "brittle": "^4.0.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^2.0.0"
  },
  "scripts": {
    "format": "prettier . --write",
    "test": "prettier . --check && brittle-node test.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/holepunchto/compact-encoding.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/compact-encoding/issues"
  },
  "homepage": "https://github.com/holepunchto/compact-encoding"
}
const b4a = require('b4a')

const { BE } = require('./endian')

exports = module.exports = {
  preencode(state, b) {
    state.end += b.byteLength
  },
  encode(state, b) {
    state.buffer.set(b, state.start)
    state.start += b.byteLength
  },
  decode(state) {
    const b = state.buffer.subarray(state.start, state.end)
    state.start = state.end
    return b
  }
}

const buffer = (exports.buffer = {
  preencode(state, b) {
    uint8array.preencode(state, b)
  },
  encode(state, b) {
    uint8array.encode(state, b)
  },
  decode(state) {
    const b = state.buffer.subarray(state.start)
    state.start = state.end
    return b
  }
})

exports.binary = {
  ...buffer,
  preencode(state, b) {
    if (typeof b === 'string') utf8.preencode(state, b)
    else buffer.preencode(state, b)
  },
  encode(state, b) {
    if (typeof b === 'string') utf8.encode(state, b)
    else buffer.encode(state, b)
  }
}

exports.arraybuffer = {
  preencode(state, b) {
    state.end += b.byteLength
  },
  encode(state, b) {
    const view = new Uint8Array(b)

    state.buffer.set(view, state.start)
    state.start += b.byteLength
  },
  decode(state) {
    const b = new ArrayBuffer(state.end - state.start)
    const view = new Uint8Array(b)

    view.set(state.buffer.subarray(state.start))

    state.start = state.end

    return b
  }
}

function typedarray(TypedArray, swap) {
  const n = TypedArray.BYTES_PER_ELEMENT

  return {
    preencode(state, b) {
      state.end += b.byteLength
    },
    encode(state, b) {
      const view = new Uint8Array(b.buffer, b.byteOffset, b.byteLength)

      if (BE && swap) swap(view)

      state.buffer.set(view, state.start)
      state.start += b.byteLength
    },
    decode(state) {
      let b = state.buffer.subarray(state.start)
      if (b.byteOffset % n !== 0) b = new Uint8Array(b)

      if (BE && swap) swap(b)

      state.start = state.end

      return new TypedArray(b.buffer, b.byteOffset, b.byteLength / n)
    }
  }
}

const uint8array = (exports.uint8array = typedarray(Uint8Array))
exports.uint16array = typedarray(Uint16Array, b4a.swap16)
exports.uint32array = typedarray(Uint32Array, b4a.swap32)

exports.int8array = typedarray(Int8Array)
exports.int16array = typedarray(Int16Array, b4a.swap16)
exports.int32array = typedarray(Int32Array, b4a.swap32)

exports.biguint64array = typedarray(BigUint64Array, b4a.swap64)
exports.bigint64array = typedarray(BigInt64Array, b4a.swap64)

exports.float32array = typedarray(Float32Array, b4a.swap32)
exports.float64array = typedarray(Float64Array, b4a.swap64)

function string(encoding) {
  return {
    preencode(state, s) {
      state.end += b4a.byteLength(s, encoding)
    },
    encode(state, s) {
      state.start += b4a.write(state.buffer, s, state.start, encoding)
    },
    decode(state) {
      const s = b4a.toString(state.buffer, encoding, state.start)
      state.start = state.end
      return s
    }
  }
}

const utf8 = (exports.string = exports.utf8 = string('utf-8'))
exports.ascii = string('ascii')
exports.hex = string('hex')
exports.base64 = string('base64')
exports.ucs2 = exports.utf16le = string('utf16le')

exports.array = function array(enc) {
  return {
    preencode(state, list) {
      for (const value of list) enc.preencode(state, value)
    },
    encode(state, list) {
      for (const value of list) enc.encode(state, value)
    },
    decode(state) {
      const arr = []
      while (state.start < state.end) arr.push(enc.decode(state))
      return arr
    }
  }
}

exports.json = {
  preencode(state, v) {
    utf8.preencode(state, JSON.stringify(v))
  },
  encode(state, v) {
    utf8.encode(state, JSON.stringify(v))
  },
  decode(state) {
    return JSON.parse(utf8.decode(state))
  }
}

exports.ndjson = {
  preencode(state, v) {
    utf8.preencode(state, JSON.stringify(v) + '\n')
  },
  encode(state, v) {
    utf8.encode(state, JSON.stringify(v) + '\n')
  },
  decode(state) {
    return JSON.parse(utf8.decode(state))
  }
}
const { EventEmitter } = require('events')
const Table = require('kademlia-routing-table')
const TOS = require('time-ordered-set')
const UDX = require('udx-native')
const sodium = require('sodium-universal')
const c = require('compact-encoding')
const NatSampler = require('nat-sampler')
const b4a = require('b4a')
const NetworkHealth = require('./lib/health')
const IO = require('./lib/io')
const Query = require('./lib/query')
const Session = require('./lib/session')
const peer = require('./lib/peer')
const { UNKNOWN_COMMAND, INVALID_TOKEN } = require('./lib/errors')
const { PING, PING_NAT, FIND_NODE, DOWN_HINT, DELAYED_PING } = require('./lib/commands')

const TMP = b4a.allocUnsafe(32)
const TICK_INTERVAL = 5000
const SLEEPING_INTERVAL = 3 * TICK_INTERVAL
const STABLE_TICKS = 240 // if nothing major bad happens in ~20mins we can consider this node stable (if nat is friendly)
const MORE_STABLE_TICKS = 3 * STABLE_TICKS
const REFRESH_TICKS = 60 // refresh every ~5min when idle
const RECENT_NODE = 12 // we've heard from a node less than 1min ago
const OLD_NODE = 360 // if an node has been around more than 30 min we consider it old

const DEFAULTS = {
  concurrency: 10,
  maxWindow: IO.DEFAULT_MAX_WINDOW,
  maxPingDelay: 10_000
}

class DHT extends EventEmitter {
  constructor(opts = {}) {
    super()

    this.bootstrapNodes = opts.bootstrap === false ? [] : (opts.bootstrap || []).map(parseNode)
    this.table = new Table(randomBytes(32))
    this.nodes = new TOS()
    this.udx = opts.udx || new UDX()
    this.io = new IO(this.table, this.udx, {
      ...opts,
      onrequest: this._onrequest.bind(this),
      onresponse: this._onresponse.bind(this),
      ontimeout: this._ontimeout.bind(this)
    })
    this.health = new NetworkHealth(this)

    this.concurrency = opts.concurrency || DEFAULTS.concurrency
    this.maxPingDelay = opts.maxPingDelay || DEFAULTS.maxPingDelay
    this.bootstrapped = false
    this.ephemeral = true
    this.firewalled = this.io.firewalled
    this.adaptive = typeof opts.ephemeral !== 'boolean' && opts.adaptive !== false
    this.destroyed = false
    this.suspended = false
    this.online = true
    this.degraded = false
    this.stats = {
      queries: { active: 0, total: 0 },
      requests: this.io.stats.requests,
      commands: {
        ping: this.io.stats.commands[PING],
        pingNat: this.io.stats.commands[PING_NAT],
        findNode: this.io.stats.commands[FIND_NODE],
        downHint: this.io.stats.commands[DOWN_HINT]
      }
    }

    this._nat = new NatSampler()
    this._quickFirewall = opts.quickFirewall !== false
    this._forcePersistent = opts.ephemeral === false
    this._repinging = 0
    this._checks = 0
    this._tick = randomOffset(100) // make sure to random offset all the network ticks
    this._refreshTicks = randomOffset(REFRESH_TICKS)
    this._stableTicks = this.adaptive ? STABLE_TICKS : 0
    this._tickInterval = setInterval(this._ontick.bind(this), TICK_INTERVAL)
    this._lastTick = Date.now()
    this._lastHost = null
    this._filterNode = opts.filterNode || opts.addNode || null // opts.addNode is deprecating, use opts.filterNode instead
    this._onrow = (row) => row.on('full', (node) => this._onfullrow(node, row))
    this._nonePersistentSamples = []
    this._bootstrapping = this._bootstrap()
    this._bootstrapping.catch(noop)
    this._sendDownHints = opts.sendDownHints !== false
    this._downHintsRateLimit =
      opts.downHintsRateLimit !== undefined ? opts.downHintsRateLimit : 10 * 5
    this._downHintsSentPerTick = 0
    this._pendingTimers = new Set()

    this.table.on('row', this._onrow)

    this.io.networkInterfaces.on('change', (interfaces) => this._onnetworkchange(interfaces))

    if (opts.nodes) {
      for (let i = opts.nodes.length - 1; i >= 0; i--) {
        this.addNode(opts.nodes[i])
      }
    }
  }

  static DEFAULTS = DEFAULTS

  static bootstrapper(port, host, opts) {
    if (!port) throw new Error('Port is required')
    if (!host) throw new Error('Host is required')
    if (host === '0.0.0.0' || host === '::') throw new Error('Invalid host')
    if (!UDX.isIPv4(host)) throw new Error('Host must be a IPv4 address')

    const dht = new this({
      port,
      ephemeral: false,
      firewalled: false,
      anyPort: false,
      bootstrap: [],
      ...opts
    })
    dht._nat.add(host, port)
    return dht
  }

  get id() {
    return this.ephemeral ? null : this.table.id
  }

  get host() {
    return this._nat.host
  }

  get port() {
    return this._nat.port
  }

  get randomized() {
    return this._nat.host !== null && this._nat.port === 0
  }

  get socket() {
    return this.firewalled ? this.io.clientSocket : this.io.serverSocket
  }

  get config() {
    return {
      concurrency: this.concurrency,
      maxWindow: this.io.congestion._maxWindow,
      randomPunchInterval: this._randomPunchInterval,
      connectionKeepAlive: this.connectionKeepAlive,
      sendDownHints: this._sendDownHints,
      downHintsRateLimit: this._downHintsRateLimit
    }
  }

  onmessage(socket, buf, rinfo) {
    if (buf.byteLength > 1) this.io.onmessage(socket, buf, rinfo)
  }

  bind() {
    return this.io.bind()
  }

  async suspend({ log = noop } = {}) {
    log('Suspending waiting for io bind...')
    await this.io.bind()
    log('Done, continuing')
    if (this.suspended || this.destroyed) return
    this.suspended = true
    clearInterval(this._tickInterval)
    log('Done, suspending io')
    await this.io.suspend({ log })
    log('Done, dht suspended')
    this.emit('suspend')
  }

  async resume({ log = noop } = {}) {
    if (!this.suspended || this.destroyed) return
    this.suspended = false
    this._tickInterval = setInterval(this._ontick.bind(this), TICK_INTERVAL)
    this._onwakeup()
    log('Resuming io')
    await this.io.resume()
    log('Done, dht resumed')
    this.io.networkInterfaces.on('change', (interfaces) => this._onnetworkchange(interfaces))
    this.refresh()
    this.emit('resume')
  }

  address() {
    const socket = this.socket
    return socket ? socket.address() : null
  }

  localAddress() {
    if (!this.io.serverSocket) return null

    return {
      host: localIP(this.udx),
      port: this.io.serverSocket.address().port
    }
  }

  remoteAddress() {
    if (!this.host) return null
    if (!this.port) return null
    if (this.firewalled) return null
    if (!this.io.serverSocket) return null

    const port = this.io.serverSocket.address().port
    if (port !== this.port) return null

    return {
      host: this.host,
      port
    }
  }

  addNode({ host, port }) {
    this._addNode({
      id: peer.id(host, port),
      port,
      host,
      token: null,
      to: null,
      sampled: 0,
      added: this._tick,
      pinged: 0,
      seen: 0,
      downHints: 0,
      prev: null,
      next: null
    })
  }

  toArray(opts) {
    const limit = opts && opts.limit
    if (limit === 0) return []
    return this.nodes.toArray({ limit, reverse: true }).map(({ host, port }) => ({ host, port }))
  }

  async fullyBootstrapped() {
    return this._bootstrapping
  }

  ready() {
    // Deprecating, use fullyBootstrapped instead (removed on next major)
    return this.fullyBootstrapped()
  }

  findNode(target, opts) {
    if (this.destroyed) throw new Error('Node destroyed')
    this._refreshTicks = REFRESH_TICKS
    return new Query(this, target, true, FIND_NODE, null, opts)
  }

  query({ target, command, value }, opts) {
    if (this.destroyed) throw new Error('Node destroyed')
    this._refreshTicks = REFRESH_TICKS
    return new Query(this, target, false, command, value || null, opts)
  }

  ping({ host, port }, opts) {
    let value = null

    if (opts && opts.size && opts.size > 0) value = b4a.alloc(opts.size)

    const req = this.io.createRequest(
      { id: null, host, port },
      null,
      true,
      PING,
      null,
      value,
      (opts && opts.session) || null,
      opts && opts.ttl
    )
    return this._requestToPromise(req, opts)
  }

  delayedPing({ host, port }, delayMs, opts) {
    if (delayMs > this.maxPingDelay) {
      throw new Error(`Delay exceeds max delay: ${this.maxPingDelay}ms`)
    }

    const value = b4a.allocUnsafe(4)
    c.uint32.encode({ start: 0, end: 4, buffer: value }, delayMs)

    const req = this.io.createRequest(
      { id: null, host, port },
      null,
      true,
      DELAYED_PING,
      null,
      value,
      (opts && opts.session) || null,
      opts && opts.ttl
    )
    // add 1 second to the timeout to account for network overhead
    req.timeout = delayMs + 1_000
    return this._requestToPromise(req, opts)
  }

  async rttStats() {
    const stats = {
      successes: 0,
      errors: 0,
      responses: {
        avgRtt: 0,
        errors: 0,
        avgCloserNodes: 0
      },
      closestReplies: {
        avgRtt: 0,
        errors: 0,
        avgCloserNodes: 0
      }
    }

    if (this.nodes.latest) {
      const q = this.findNode(this.nodes.latest.id)

      let responseCount = 0
      let closestCount = 0

      for await (const msg of q) {
        stats.responses.avgRtt += msg.rtt
        stats.responses.errors += msg.error
        stats.responses.avgCloserNodes += msg.closerNodes?.length || 0
        responseCount++
      }
      stats.responses.avgRtt /= responseCount
      stats.responses.avgCloserNodes /= responseCount

      for await (const msg of q.closestReplies) {
        stats.closestReplies.avgRtt += msg.rtt
        stats.closestReplies.errors += msg.error
        stats.closestReplies.avgCloserNodes += msg.closerNodes?.length || 0
        closestCount++
      }
      stats.closestReplies.avgRtt /= closestCount
      stats.closestReplies.avgCloserNodes /= closestCount

      stats.successes = q.successes
      stats.errors = q.errors
    }

    return stats
  }

  request({ token = null, command, target = null, value = null }, { host, port }, opts) {
    const req = this.io.createRequest(
      { id: null, host, port },
      token,
      false,
      command,
      target,
      value,
      (opts && opts.session) || null,
      opts && opts.ttl
    )
    return this._requestToPromise(req, opts)
  }

  session() {
    return new Session(this)
  }

  _requestToPromise(req, opts) {
    if (req === null) return Promise.reject(new Error('Node destroyed'))

    if (opts && opts.socket) req.socket = opts.socket
    if (opts && opts.retry === false) req.retries = 0

    return new Promise((resolve, reject) => {
      req.onresponse = resolve
      req.onerror = reject
      req.send()
    })
  }

  async _bootstrap() {
    const self = this

    await Promise.resolve() // wait a tick, so apis can be used from the outside
    await this.io.bind()

    this.emit('listening')

    // TODO: some papers describe more advanced ways of bootstrapping - we should prob look into that

    let first = this.firewalled && this._quickFirewall && !this._forcePersistent
    let testNat = false

    const onlyFirewall = !this._forcePersistent

    for (let i = 0; i < 2; i++) {
      await this._backgroundQuery(this.table.id).on('data', ondata).finished()

      if (this.bootstrapped || (!testNat && !this._forcePersistent)) break
      if (!(await this._updateNetworkState(onlyFirewall))) break
    }

    if (this.bootstrapped) return
    this.bootstrapped = true

    this.emit('ready')

    function ondata(data) {
      // Simple QUICK nat heuristic.
      // If we get ONE positive nat ping before the bootstrap query finishes
      // then we always to a nat test, no matter if we are adaptive...
      // This should be expanded in the future to try more than one node etc, not always hit the first etc
      // If this fails, then nbd, as the onstable hook will pick it up later.

      if (!first) return
      first = false

      const value = b4a.allocUnsafe(2)
      c.uint16.encode({ start: 0, end: 2, buffer: value }, self.io.serverSocket.address().port)

      self._request(
        data.from,
        false,
        true,
        PING_NAT,
        null,
        value,
        null,
        () => {
          testNat = true
        },
        noop
      )
    }
  }

  refresh() {
    const node = this.table.random()
    this._backgroundQuery(node ? node.id : this.table.id).on('error', noop)
  }

  async destroy() {
    const emitClose = !this.destroyed
    this.destroyed = true
    clearInterval(this._tickInterval)
    for (const timer of this._pendingTimers) {
      clearTimeout(timer)
    }
    await this.io.destroy()
    if (emitClose) this.emit('close')
  }

  _request(to, force, internal, command, target, value, session, onresponse, onerror) {
    if (internal && !this._sendDownHints && command === DOWN_HINT) return null
    const req = this.io.createRequest(to, null, internal, command, target, value, session)
    if (req === null) return null

    req.onresponse = onresponse
    req.onerror = onerror
    req.send(force)

    return req
  }

  _natAdd(host, port) {
    const prevHost = this._nat.host
    const prevPort = this._nat.port

    this._nat.add(host, port)

    if (prevHost === this._nat.host && prevPort === this._nat.port) return

    this.emit('nat-update', this._nat.host, this._nat.port)
  }

  // we don't check that this is a bootstrap node but we limit the sample size to very few nodes, so fine
  _sampleBootstrapMaybe(from, to) {
    if (this._nonePersistentSamples.length >= Math.max(1, this.bootstrapNodes.length)) return
    const id = from.host + ':' + from.port
    if (this._nonePersistentSamples.indexOf(id) > -1) return
    this._nonePersistentSamples.push(id)
    this._natAdd(to.host, to.port)
  }

  _addNodeFromNetwork(sample, from, to) {
    if (this._filterNode !== null && !this._filterNode(from)) {
      return
    }

    if (from.id === null) {
      this._sampleBootstrapMaybe(from, to)
      return
    }

    const oldNode = this.table.get(from.id)

    // refresh it, if we've seen this before
    if (oldNode) {
      if (sample && (oldNode.sampled === 0 || this._tick - oldNode.sampled >= OLD_NODE)) {
        oldNode.to = to
        oldNode.sampled = this._tick
        this._natAdd(to.host, to.port)
      }

      oldNode.pinged = oldNode.seen = this._tick
      this.nodes.add(oldNode)
      return
    }

    this._addNode({
      id: from.id,
      port: from.port,
      host: from.host,
      to,
      sampled: 0,
      added: this._tick,
      pinged: this._tick, // last time we interacted with them
      seen: this._tick, // last time we heard from them
      downHints: 0,
      prev: null,
      next: null
    })
  }

  _addNode(node) {
    if (this.nodes.has(node) || b4a.equals(node.id, this.table.id)) return

    node.added = node.pinged = node.seen = this._tick

    if (!this.table.add(node)) return
    this.nodes.add(node)

    if (node.to && node.sampled === 0) {
      node.sampled = this._tick
      this._natAdd(node.to.host, node.to.port)
    }

    this.emit('add-node', node)
  }

  _removeStaleNode(node, lastSeen) {
    if (node.seen <= lastSeen) this._removeNode(node)
  }

  _removeNode(node) {
    if (!this.nodes.has(node)) return

    this.table.remove(node.id)
    this.nodes.remove(node)

    this.emit('remove-node', node)
  }

  _onwakeup() {
    this._tick += 2 * OLD_NODE // bump the tick enough that everything appears old.
    this._tick += 8 - (this._tick & 7) - 2 // triggers a series of pings in two ticks
    this._stableTicks = MORE_STABLE_TICKS
    this._refreshTicks = 1 // triggers a refresh next tick (allow network time to wake up also)
    this._lastHost = null // clear network cache check
    this.health.reset()

    if (this.adaptive) {
      // TODO: re-enable this as soon as we find out why this is over triggering in some edge cases
      // this.firewalled = true
      // this.io.firewalled = true

      if (!this.ephemeral) {
        this.ephemeral = true
        this.io.ephemeral = true
        this.emit('ephemeral')
      }
    }

    this.emit('wakeup')
  }

  _onfullrow(newNode, row) {
    if (!this.bootstrapped || this._repinging >= 3) return

    let oldest = null
    for (const node of row.nodes) {
      if (node.pinged === this._tick) continue
      if (
        oldest === null ||
        oldest.pinged > node.pinged ||
        (oldest.pinged === node.pinged && oldest.added > node.added)
      ) {
        oldest = node
      }
    }

    if (oldest === null) return
    if (this._tick - oldest.pinged < RECENT_NODE && this._tick - oldest.added > OLD_NODE) return

    this._repingAndSwap(newNode, oldest)
  }

  _onnetworkchange(interfaces) {
    this.emit('network-change', interfaces)
    this.emit('network-update')
  }

  _repingAndSwap(newNode, oldNode) {
    const self = this
    const lastSeen = oldNode.seen

    oldNode.pinged = this._tick

    this._repinging++
    this._request(
      { id: null, host: oldNode.host, port: oldNode.port },
      false,
      true,
      PING,
      null,
      null,
      null,
      onsuccess,
      onswap
    )

    function onsuccess(m) {
      if (oldNode.seen <= lastSeen) return onswap()
      self._repinging--
    }

    function onswap(e) {
      self._repinging--
      self._removeNode(oldNode)
      self._addNode(newNode)
    }
  }

  _onrequest(req, external) {
    if (req.from.id !== null) {
      this._addNodeFromNetwork(!external, req.from, req.to)
    }

    if (req.internal) {
      switch (req.command) {
        // standard keep alive call
        case PING: {
          req.sendReply(0, null, false, false)
          return
        }
        case DELAYED_PING: {
          this._ondelayedping(req)
          return
        }
        // check if the other side can receive a message to their other socket
        case PING_NAT: {
          if (req.value === null || req.value.byteLength < 2) return
          const port = c.uint16.decode({ start: 0, end: 2, buffer: req.value })
          if (port === 0) return
          req.from.port = port
          req.sendReply(0, null, false, false)
          return
        }
        // empty dht reply back
        case FIND_NODE: {
          if (!req.target) return
          req.sendReply(0, null, false, true)
          return
        }
        // "this is node you sent me is down" - let's try to ping it
        case DOWN_HINT: {
          if (req.value === null || req.value.byteLength < 6) return
          if (this._checks < 10) {
            sodium.crypto_generichash(TMP, req.value.subarray(0, 6))
            const node = this.table.get(TMP)
            if (node && (node.pinged < this._tick || node.downHints === 0)) {
              node.downHints++
              this._check(node)
            }
          }
          req.sendReply(0, null, false, false)
          return
        }
      }

      req.sendReply(UNKNOWN_COMMAND, null, false, req.target !== null)
      return
    }

    // ask the user to handle it or reply back with a bad command
    if (this.onrequest(req) === false) {
      req.sendReply(UNKNOWN_COMMAND, null, false, req.target !== null)
    }
  }

  onrequest(req) {
    return this.emit('request', req)
  }

  _ondelayedping(req) {
    if (req.value === null || req.value.byteLength < 4) return
    const delayMs = c.uint32.decode({ start: 0, end: 4, buffer: req.value })
    if (delayMs > this.maxPingDelay) return
    const timer = setTimeout(() => {
      if (this.destroyed) return
      this._pendingTimers.delete(timer)
      req.sendReply(0, null, false, false)
    }, delayMs)
    this._pendingTimers.add(timer)
  }

  _onresponse(res, external) {
    this._addNodeFromNetwork(!external, res.from, res.to)
  }

  _ontimeout(req) {
    if (!req.to.id) return
    const node = this.table.get(req.to.id)
    if (node) this._removeNode(node)
  }

  _pingSome() {
    let cnt = this.io.inflight.length > 2 ? 3 : 5
    let oldest = this.nodes.oldest

    // tiny dht, pinged the bootstrap again
    if (!oldest) {
      this.refresh()
      return
    }

    // we've recently pinged the oldest one, so only trigger a couple of repings
    if (this._tick - oldest.pinged < RECENT_NODE) {
      cnt = 2
    }

    while (cnt--) {
      if (!oldest || this._tick === oldest.pinged) continue
      this._check(oldest)
      oldest = oldest.next
    }
  }

  _check(node) {
    node.pinged = this._tick

    const lastSeen = node.seen
    const onresponse = () => {
      this._checks--
      this._removeStaleNode(node, lastSeen)
    }
    const onerror = () => {
      this._checks--
      this._removeNode(node)
    }

    this._checks++
    this._request(
      { id: null, host: node.host, port: node.port },
      false,
      true,
      PING,
      null,
      null,
      null,
      onresponse,
      onerror
    )
  }

  _ontick() {
    const time = Date.now()

    if (time - this._lastTick > SLEEPING_INTERVAL && this.suspended === false) {
      this._onwakeup()
    } else {
      this._tick++
    }

    this._lastTick = time

    if (!this.bootstrapped || this.suspended) return

    if (this.adaptive && this.ephemeral && --this._stableTicks <= 0) {
      if (this._lastHost === this._nat.host) {
        // do not recheck the same network...
        this._stableTicks = MORE_STABLE_TICKS
      } else {
        this._updateNetworkState() // the promise returned here never fails so just ignore it
      }
    }

    if ((this._tick & 7) === 0) {
      this._pingSome()
    }

    if (
      ((this._tick & 63) === 0 && this.nodes.length < this.table.k) ||
      --this._refreshTicks <= 0
    ) {
      this.refresh()
    }

    this._downHintsSentPerTick = 0
    this.health.update()
  }

  async _updateNetworkState(onlyFirewall = false) {
    if (!this.ephemeral) return false
    if (onlyFirewall && !this.firewalled) return false

    const { host, port } = this._nat

    if (!onlyFirewall) {
      // remember what host we checked and reset the counter
      this._stableTicks = MORE_STABLE_TICKS
      this._lastHost = host
    }

    // check if we have a consistent host and port
    if (host === null || port === 0) {
      return false
    }

    const natSampler = this.firewalled ? new NatSampler() : this._nat

    // ask remote nodes to ping us on our server socket to see if we have the port open
    const firewalled = this.firewalled && (await this._checkIfFirewalled(natSampler))
    if (firewalled) return false

    this.firewalled = this.io.firewalled = false

    // incase it's called in parallel for some reason, or if our nat status somehow changed
    if (!this.ephemeral || host !== this._nat.host || port !== this._nat.port) return false
    // if the firewall probe returned a different host / non consistent port, bail as well
    if (natSampler.host !== host || natSampler.port === 0) return false

    const id = peer.id(natSampler.host, natSampler.port)

    if (!onlyFirewall) {
      this.ephemeral = this.io.ephemeral = false
    }

    if (natSampler !== this._nat) {
      const prevHost = this._nat.host
      const prevPort = this._nat.port

      this._nonePersistentSamples = []
      this._nat = natSampler

      if (prevHost !== this._nat.host || prevPort !== this._nat.port) {
        this.emit('nat-update', this._nat.host, this._nat.port)
      }
    }

    // TODO: we should make this a bit more defensive in terms of using more
    // resources to make sure that the new routing table contains as many alive nodes
    // as possible, vs blindly copying them over...

    // all good! copy over the old routing table to the new one
    if (!b4a.equals(this.table.id, id)) {
      const nodes = this.table.toArray()

      this.table = this.io.table = new Table(id)

      for (const node of nodes) {
        if (b4a.equals(node.id, id)) continue
        if (!this.table.add(node)) this.nodes.remove(node)
      }

      this.table.on('row', this._onrow)

      // we need to rebootstrap/refresh since we updated our id
      if (this.bootstrapped) this.refresh()
    }

    if (!this.ephemeral) {
      this.emit('persistent')
    }

    return true
  }

  async *_resolveBootstrapNodes() {
    for (let { host, port } of this.bootstrapNodes) {
      let doLookup = false

      if (host.indexOf('@') === -1) {
        doLookup = true
      } else {
        const [suggestedIP, fallbackHost] = host.split('@')
        try {
          await this.ping({ host: suggestedIP, port })
          host = suggestedIP
        } catch {
          host = fallbackHost
          doLookup = true
        }
      }

      if (doLookup) {
        try {
          host = UDX.isIPv4(host) ? host : (await this.udx.lookup(host, { family: 4 })).host
        } catch {
          continue
        }
      }

      yield {
        id: peer.id(host, port),
        host,
        port
      }
    }
  }

  async _addBootstrapNodes(nodes) {
    for await (const node of this._resolveBootstrapNodes()) {
      nodes.push(node)
    }
  }

  async _checkIfFirewalled(natSampler = new NatSampler()) {
    const nodes = []
    for (let node = this.nodes.latest; node && nodes.length < 5; node = node.prev) {
      nodes.push(node)
    }

    if (nodes.length < 5) await this._addBootstrapNodes(nodes)
    // if no nodes are available, including bootstrappers - bail
    if (nodes.length === 0) return true

    const hosts = new Set()
    const value = b4a.allocUnsafe(2)

    c.uint16.encode({ start: 0, end: 2, buffer: value }, this.io.serverSocket.address().port)

    // double check they actually came on the server socket...
    this.io.serverSocket.on('message', onmessage)

    const pongs = await requestAll(this, true, PING_NAT, value, nodes)

    let count = 0
    for (const res of pongs) {
      if (hosts.has(res.from.host)) {
        count++
        natSampler.add(res.to.host, res.to.port)
      }
    }

    this.io.serverSocket.removeListener('message', onmessage)

    // if we got no or very few replies, consider it a fluke
    if (count < (nodes.length >= 5 ? 3 : 1)) return true

    // check that the server socket has the same ip as the client socket
    if (natSampler.host === null || this._nat.host !== natSampler.host) return true

    // check that the local port of the server socket is the same as the remote port
    // TODO: we might want a flag to opt out of this heuristic for specific remapped port servers
    if (natSampler.port === 0 || natSampler.port !== this.io.serverSocket.address().port) {
      return true
    }

    return false

    function onmessage(_, { host }) {
      hosts.add(host)
    }
  }

  _backgroundQuery(target) {
    this._refreshTicks = REFRESH_TICKS

    const backgroundCon = Math.min(this.concurrency, Math.max(2, (this.concurrency / 8) | 0))
    const q = new Query(this, target, true, FIND_NODE, null, {
      concurrency: backgroundCon
    })

    q.on('data', () => {
      // yield to other traffic
      q.concurrency = this.io.inflight.length < 3 ? this.concurrency : backgroundCon
    })

    return q
  }

  // called by health
  _online() {
    if (this.online && !this.degraded) return
    this.online = true
    this.degraded = false
    this.emit('network-update')
  }

  // called by health
  _degraded() {
    if (this.degraded) return
    this.online = true
    this.degraded = true
    this.emit('network-update')
  }

  // called by health
  _offline() {
    if (!this.online) return
    this.online = false
    this.degraded = false
    this.emit('network-update')
  }
}

DHT.OK = 0
DHT.ERROR_UNKNOWN_COMMAND = UNKNOWN_COMMAND
DHT.ERROR_INVALID_TOKEN = INVALID_TOKEN

module.exports = DHT

function localIP(udx, family = 4) {
  let host = null

  for (const n of udx.networkInterfaces()) {
    if (n.family !== family || n.internal) continue

    // mac really likes en0, mb a better way but this shouldnt be bad anywhere so return now
    if (n.name === 'en0') return n.host

    // otherwise pick the first non internal host (let the loop continue in case we see en0)
    if (host === null) host = n.host
  }

  return host || (family === 4 ? '127.0.0.1' : '::1')
}

function parseNode(s) {
  if (typeof s === 'object') return s
  if (typeof s === 'number') return { host: '127.0.0.1', port: s }
  const [host, port] = s.split(':')
  if (!port) throw new Error('Bootstrap node format is host:port')

  return {
    host,
    port: Number(port)
  }
}

function randomBytes(n) {
  const b = b4a.alloc(n)
  sodium.randombytes_buf(b)
  return b
}

function randomOffset(n) {
  return n - ((Math.random() * 0.5 * n) | 0)
}

function requestAll(dht, internal, command, value, nodes) {
  let missing = nodes.length
  const replies = []

  return new Promise((resolve) => {
    for (const node of nodes) {
      const req = dht._request(
        node,
        false,
        internal,
        command,
        null,
        value,
        null,
        onsuccess,
        onerror
      )
      if (!req) return resolve(replies)
    }

    function onsuccess(res) {
      replies.push(res)
      if (--missing === 0) resolve(replies)
    }

    function onerror() {
      if (--missing === 0) resolve(replies)
    }
  })
}

function noop() {}
exports.PING = 0
exports.PING_NAT = 1
exports.FIND_NODE = 2
exports.DOWN_HINT = 3
exports.DELAYED_PING = 4
module.exports = class DHTError extends Error {
  constructor(msg, code, fn = DHTError) {
    super(`${code}: ${msg}`)
    this.code = code

    if (Error.captureStackTrace) {
      Error.captureStackTrace(this, fn)
    }
  }

  get name() {
    return 'DHTError'
  }

  static UNKNOWN_COMMAND = 1
  static INVALID_TOKEN = 2

  static REQUEST_TIMEOUT(msg = 'Request timed out') {
    return new DHTError(msg, 'REQUEST_TIMEOUT', DHTError.REQUEST_TIMEOUT)
  }

  static REQUEST_DESTROYED(msg = 'Request destroyed') {
    return new DHTError(msg, 'REQUEST_DESTROYED', DHTError.REQUEST_DESTROYED)
  }

  static IO_SUSPENDED(msg = 'I/O suspended') {
    return new DHTError(msg, 'IO_SUSPENDED', DHTError.IO_SUSPENDED)
  }
}
const MAX_HEALTH_WINDOW = 4
const OFFLINE_THRESHOLD = 2
const IDLE_THRESHOLD = 4
const DEGRADED_TIMEOUT_RATE_THRESHOLD = 0.5

module.exports = class NetworkHealth {
  constructor(dht) {
    this._dht = dht
    this._window = []
    this._head = -1
    this._offlineTicks = 0
    this._degradedTicks = 0
    this._healthyTicks = 0
    this.online = true
    this.degraded = false
  }

  get oldest() {
    return this._window[this._tail]
  }

  get previous() {
    return this._window[(this._head - 1 + MAX_HEALTH_WINDOW) % MAX_HEALTH_WINDOW]
  }

  get newest() {
    return this._window[this._head]
  }

  get responses() {
    if (!this.newest || !this.previous) return 0
    return this.newest.responses - this.previous.responses
  }

  get timeouts() {
    if (!this.newest || !this.previous) return 0
    return this.newest.timeouts - this.previous.timeouts
  }

  get timeoutsRate() {
    if (this.timeouts === 0) return 0
    return this.timeouts / (this.responses + this.timeouts)
  }

  get cold() {
    return this._window.length < MAX_HEALTH_WINDOW
  }

  get idle() {
    return this.responses + this.timeouts < IDLE_THRESHOLD
  }

  get stats() {
    return {
      online: this.online,
      degraded: this.degraded,
      cold: this.cold,
      idle: this.idle,
      responses: this.responses,
      timeouts: this.timeouts,
      timeoutsRate: this.timeoutsRate
    }
  }

  get _tail() {
    return (this._head + 1) % MAX_HEALTH_WINDOW
  }

  reset() {
    this._window = []
    this._head = -1
    this._offlineTicks = 0
    this._degradedTicks = 0
    this._healthyTicks = 0
    this.online = true
    this.degraded = false
    this._dht._online()
  }

  update() {
    // update counters before dropping oldest
    if (this.oldest?.degraded) this._degradedTicks--
    else if (this.oldest?.degraded === false) this._healthyTicks--

    // move window
    this._head = this._tail
    this._window[this._head] = {
      responses: this._dht.stats.requests.responses,
      timeouts: this._dht.stats.requests.timeouts
    }

    // skip state changes
    if (this.cold || this.idle) return

    // update newest
    this.newest.offline = this.responses === 0
    this.newest.degraded =
      !this.newest.offline && this.timeoutsRate > DEGRADED_TIMEOUT_RATE_THRESHOLD

    // update counters
    if (this.newest.offline) this._offlineTicks++
    else {
      this._offlineTicks = 0
      if (this.newest.degraded) this._degradedTicks++
      else this._healthyTicks++
    }

    // check online/offline
    if (!this.newest.offline) this.online = true
    else if (this._offlineTicks >= OFFLINE_THRESHOLD) this.online = false

    // check degraded
    if (!this.online || this._healthyTicks === MAX_HEALTH_WINDOW) this.degraded = false
    else if (this._degradedTicks === MAX_HEALTH_WINDOW) this.degraded = true

    // fire events
    if (this.online && !this.degraded) this._dht._online()
    else if (this.degraded) this._dht._degraded()
    else this._dht._offline()
  }
}
const FIFO = require('fast-fifo')
const sodium = require('sodium-universal')
const c = require('compact-encoding')
const b4a = require('b4a')
const AdaptiveTimeout = require('adaptive-timeout')
const peer = require('./peer')
const { INVALID_TOKEN, REQUEST_TIMEOUT, REQUEST_DESTROYED, IO_SUSPENDED } = require('./errors')

const VERSION = 0b11
const RESPONSE_ID = (0b0001 << 4) | VERSION
const REQUEST_ID = (0b0000 << 4) | VERSION
const EMPTY_ARRAY = []
const MAX_WINDOW = 80

module.exports = class IO {
  constructor(
    table,
    udx,
    {
      maxWindow = MAX_WINDOW,
      port = 0,
      host = '0.0.0.0',
      anyPort = true,
      firewalled = true,
      onrequest,
      onresponse = noop,
      ontimeout = noop,
      adaptiveTimeout
    } = {}
  ) {
    this.table = table
    this.udx = udx
    this.inflight = []
    this.clientSocket = null
    this.serverSocket = null
    this.firewalled = firewalled !== false
    this.ephemeral = true
    this.congestion = new CongestionWindow(maxWindow)
    this.networkInterfaces = udx.watchNetworkInterfaces()
    this.suspended = false

    this.stats = {
      requests: {
        active: 0,
        total: 0,
        responses: 0,
        timeouts: 0,
        retries: 0
      },
      commands: [
        { tx: 0, rx: 0 }, // tx = transmitted, rx = received
        { tx: 0, rx: 0 },
        { tx: 0, rx: 0 },
        { tx: 0, rx: 0 }
      ]
    }

    this.onrequest = onrequest
    this.onresponse = onresponse
    this.ontimeout = ontimeout

    this._pending = new FIFO()
    this._rotateSecrets = 10
    this._tid = (Math.random() * 65536) | 0
    this._secrets = null
    this._drainInterval = null
    this._destroying = null
    this._binding = null

    // port can be a number or a range [start, to]
    this.portRange = port.length ? port : port === 0 ? [0, 0] : [port, port + 5]

    this._host = host
    this._anyPort = anyPort !== false
    this._boundServerPort = 0
    this._boundClientPort = 0

    this._adt = adaptiveTimeout ? new AdaptiveTimeout(adaptiveTimeout) : null
  }

  static DEFAULT_MAX_WINDOW = MAX_WINDOW

  onmessage(socket, buffer, { host, port }) {
    if (buffer.byteLength < 2 || !(port > 0 && port < 65536) || this.suspended === true) return

    const from = { id: null, host, port }
    const state = { start: 1, end: buffer.byteLength, buffer }
    const expectedSocket = this.firewalled ? this.clientSocket : this.serverSocket
    const external = socket !== expectedSocket

    if (buffer[0] === REQUEST_ID) {
      const req = Request.decode(this, socket, from, state)
      if (req === null) return
      if (
        req.token !== null &&
        !b4a.equals(req.token, this.token(req.from, 1)) &&
        !b4a.equals(req.token, this.token(req.from, 0))
      ) {
        req.error(INVALID_TOKEN, { token: true })
        return
      }
      this.onrequest(req, external)
      return
    }

    if (buffer[0] === RESPONSE_ID) {
      const res = decodeReply(from, state)
      if (res === null) return

      for (let i = 0; i < this.inflight.length; i++) {
        const req = this.inflight[i]
        if (req.tid !== res.tid) continue

        res.rtt = Date.now() - req._timestamp

        if (this._adt && req.sent <= 2) {
          this._adt.put(`${req.to.host}:${req.to.port}`, res.rtt)
        }

        if (i === this.inflight.length - 1) this.inflight.pop()
        else this.inflight[i] = this.inflight.pop()

        if (req.session) req.session._detach(req)

        // TODO: Auto retry here if errors.INVALID_TOKEN is returned?

        if (req._timeout) {
          clearTimeout(req._timeout)
          req._timeout = null
        }

        this.congestion.recv()

        if (req.internal && req.command < this.stats.commands.length) {
          this.stats.commands[req.command].rx++
        }

        this.stats.requests.active--
        this.stats.requests.responses++

        this.onresponse(res, external)
        req.onresponse(res, req)
        break
      }
    }
  }

  token(addr, i) {
    if (this._secrets === null) {
      const buf = b4a.alloc(64)
      this._secrets = [buf.subarray(0, 32), buf.subarray(32, 64)]
      sodium.randombytes_buf(this._secrets[0])
      sodium.randombytes_buf(this._secrets[1])
    }

    const token = b4a.allocUnsafe(32)
    sodium.crypto_generichash(token, b4a.from(addr.host), this._secrets[i])
    return token
  }

  async destroy() {
    if (this._destroying) return this._destroying
    this._destroying = this._destroy()
    return this._destroying
  }

  async _destroy() {
    // simplifies timing to await the bind here also, although it might be unneeded
    await this.bind()
    await this._clear(false)
  }

  async _clear(suspended) {
    if (this._drainInterval) {
      clearInterval(this._drainInterval)
      this._drainInterval = null
    }

    while (this.inflight.length) {
      const req = this.inflight.pop()
      if (req._timeout) clearTimeout(req._timeout)
      req._timeout = null
      req.destroyed = true

      if (req.session) req.session._detach(req)

      this.congestion.recv()
      this.stats.requests.active--

      req.onerror(suspended ? IO_SUSPENDED() : REQUEST_DESTROYED(), req)
    }

    await Promise.allSettled([this.serverSocket.close(), this.clientSocket.close()])

    this.networkInterfaces.destroy()
  }

  async suspend() {
    this.suspended = true
    await this._clear(true)

    this.congestion.clear()

    if (this._drainInterval) {
      clearInterval(this._drainInterval)
      this._drainInterval = null
    }
  }

  async _rebind(binding) {
    if (binding) await binding
    if (this._destroying) return this._destroying
    await this._bindSockets()
    this.networkInterfaces = this.udx.watchNetworkInterfaces()
  }

  resume() {
    this.suspended = false
    const binding = this._binding
    this._binding = this._rebind(binding)
    return this._binding
  }

  bind() {
    if (this._binding) return this._binding
    this._binding = this._bindSockets()
    return this._binding
  }

  async _bindSockets() {
    const serverSocket = this.udx.createSocket()

    const candidatePorts = []

    // Retrying previous port always has precedence
    if (this._boundServerPort) candidatePorts.push(this._boundServerPort)

    for (let i = this.portRange[0]; i < this.portRange[1]; i++) candidatePorts.push(i)

    for (const port of candidatePorts) {
      if (serverSocket.bound) break

      try {
        serverSocket.bind(port, this._host)
      } catch (err) {
        if (!this._anyPort) {
          await serverSocket.close()
          throw err
        }
      }
    }

    if (!serverSocket.bound) {
      try {
        serverSocket.bind(0, this._host)
      } catch (err) {
        await serverSocket.close()
        throw err
      }
    }

    const clientSocket = this.udx.createSocket()

    try {
      clientSocket.bind(this._boundClientPort || 0, this._host)
    } catch {
      try {
        clientSocket.bind(0, this._host)
      } catch (err) {
        await serverSocket.close()
        await clientSocket.close()
        throw err
      }
    }

    this._boundServerPort = serverSocket.address().port
    this._boundClientPort = clientSocket.address().port

    this.clientSocket = clientSocket
    this.serverSocket = serverSocket

    this.serverSocket.on('message', this.onmessage.bind(this, this.serverSocket))
    this.clientSocket.on('message', this.onmessage.bind(this, this.clientSocket))

    if (this._drainInterval === null) {
      this._drainInterval = setInterval(this._drain.bind(this), 750)
      if (this._drainInterval.unref) this._drainInterval.unref()
    }

    for (const req of this.inflight) {
      if (!req.socket) req.socket = this.firewalled ? this.clientSocket : this.serverSocket
      req.sent = 0
      req.send(false)
    }
  }

  _drain() {
    if (this._secrets !== null && --this._rotateSecrets === 0) {
      this._rotateSecrets = 10
      const tmp = this._secrets[0]
      this._secrets[0] = this._secrets[1]
      this._secrets[1] = tmp
      sodium.crypto_generichash(tmp, tmp)
    }

    this.congestion.drain()

    while (!this.congestion.isFull()) {
      const p = this._pending.shift()
      if (p === undefined) return
      p._sendNow()
    }
  }

  createRequest(to, token, internal, command, target, value, session, ttl) {
    if (this._destroying !== null) return null

    if (this._tid === 65536) this._tid = 0

    const tid = this._tid++
    const socket = this.firewalled ? this.clientSocket : this.serverSocket

    const req = new Request(
      this,
      socket,
      tid,
      null,
      to,
      token,
      internal,
      command,
      target,
      value,
      session,
      ttl || 0
    )
    this.inflight.push(req)
    if (session) session._attach(req)

    if (internal && command < this.stats.commands.length) {
      this.stats.commands[command].tx++
    }

    this.stats.requests.active++
    this.stats.requests.total++

    return req
  }
}

class Request {
  constructor(io, socket, tid, from, to, token, internal, command, target, value, session, ttl) {
    this.socket = socket
    this.tid = tid
    this.from = from
    this.to = to
    this.token = token
    this.command = command
    this.target = target
    this.value = value
    this.internal = internal
    this.session = session
    this.ttl = ttl
    this.index = -1
    this.sent = 0
    this.retries = 3
    this.destroyed = false
    this.timeout = 0

    this.oncycle = noop
    this.onerror = noop
    this.onresponse = noop

    this._buffer = null
    this._io = io
    this._timeout = null
    this._timestamp = Date.now()
  }

  static decode(io, socket, from, state) {
    try {
      const flags = c.uint.decode(state)
      const tid = c.uint16.decode(state)
      const to = peer.ipv4.decode(state)
      const id = flags & 1 ? c.fixed32.decode(state) : null
      const token = flags & 2 ? c.fixed32.decode(state) : null
      const internal = (flags & 4) !== 0
      const command = c.uint.decode(state)
      const target = flags & 8 ? c.fixed32.decode(state) : null
      const value = flags & 16 ? c.buffer.decode(state) : null

      if (id !== null) from.id = validateId(id, from)

      return new Request(
        io,
        socket,
        tid,
        from,
        to,
        token,
        internal,
        command,
        target,
        value,
        null,
        0
      )
    } catch {
      return null
    }
  }

  reply(value, opts = {}) {
    const socket = opts.socket || this.socket
    const to = opts.to || this.from
    this._sendReply(0, value || null, opts.token !== false, opts.closerNodes !== false, to, socket)
  }

  error(code, opts = {}) {
    const socket = opts.socket || this.socket
    const to = opts.to || this.from
    this._sendReply(code, null, opts.token === true, opts.closerNodes !== false, to, socket)
  }

  relay(value, to, opts) {
    const socket = (opts && opts.socket) || this.socket
    const buffer = this._encodeRequest(null, value, to, socket)
    socket.trySend(buffer, to.port, to.host, this.ttl)
  }

  send(force = false) {
    if (this.destroyed) return

    if (this.socket === null) return
    if (this._buffer === null) {
      this._buffer = this._encodeRequest(this.token, this.value, this.to, this.socket)
    }

    if (!force && this._io.congestion.isFull()) {
      this._io._pending.push(this)
      return
    }

    this._sendNow()
  }

  sendReply(error, value, token, hasCloserNodes) {
    this._sendReply(error, value, token, hasCloserNodes, this.from, this.socket, null)
  }

  _sendNow() {
    if (this.destroyed) return
    this.sent++
    this._io.congestion.send()
    this.socket.trySend(this._buffer, this.to.port, this.to.host, this.ttl)
    if (this._timeout) clearTimeout(this._timeout)
    const value =
      this.timeout || this._io._adt?.get(`${this.to.host}:${this.to.port}`, this.sent) || 1000
    this._timeout = setTimeout(oncycle, value, this)
  }

  destroy(err) {
    if (this.destroyed) return
    this.destroyed = true

    if (this._timeout) {
      clearTimeout(this._timeout)
      this._timeout = null
    }

    const i = this._io.inflight.indexOf(this)
    if (i === -1) return

    if (i === this._io.inflight.length - 1) this._io.inflight.pop()
    else this._io.inflight[i] = this._io.inflight.pop()

    if (this.session) this.session._detach(this)

    this._io.stats.requests.active--
    this._io.congestion.recv()

    this.onerror(err || REQUEST_DESTROYED(), this)
  }

  _sendReply(error, value, token, hasCloserNodes, from, socket) {
    if (socket === null || this.destroyed) return

    const id = this._io.ephemeral === false && socket === this._io.serverSocket
    const closerNodes =
      this.target !== null && hasCloserNodes ? this._io.table.closest(this.target) : EMPTY_ARRAY
    const state = { start: 0, end: 1 + 1 + 6 + 2, buffer: null } // (type | version) + flags + to + tid

    if (id) state.end += 32
    if (token) state.end += 32
    if (closerNodes.length > 0) peer.ipv4Array.preencode(state, closerNodes)
    if (error > 0) c.uint.preencode(state, error)
    if (value) c.buffer.preencode(state, value)

    state.buffer = b4a.allocUnsafe(state.end)
    state.buffer[state.start++] = RESPONSE_ID
    state.buffer[state.start++] =
      (id ? 1 : 0) |
      (token ? 2 : 0) |
      (closerNodes.length > 0 ? 4 : 0) |
      (error > 0 ? 8 : 0) |
      (value ? 16 : 0)

    c.uint16.encode(state, this.tid)
    peer.ipv4.encode(state, from)

    if (id) c.fixed32.encode(state, this._io.table.id)
    if (token) c.fixed32.encode(state, this._io.token(from, 1))
    if (closerNodes.length > 0) peer.ipv4Array.encode(state, closerNodes)
    if (error > 0) c.uint.encode(state, error)
    if (value) c.buffer.encode(state, value)

    socket.trySend(state.buffer, from.port, from.host, this.ttl)
  }

  _encodeRequest(token, value, to, socket) {
    const id = this._io.ephemeral === false && socket === this._io.serverSocket
    const state = { start: 0, end: 1 + 1 + 6 + 2, buffer: null } // (type | version) + flags + to + tid

    if (id) state.end += 32
    if (token) state.end += 32

    c.uint.preencode(state, this.command)

    if (this.target) state.end += 32
    if (value) c.buffer.preencode(state, value)

    state.buffer = b4a.allocUnsafe(state.end)
    state.buffer[state.start++] = REQUEST_ID
    state.buffer[state.start++] =
      (id ? 1 : 0) |
      (token ? 2 : 0) |
      (this.internal ? 4 : 0) |
      (this.target ? 8 : 0) |
      (value ? 16 : 0)

    c.uint16.encode(state, this.tid)
    peer.ipv4.encode(state, to)

    if (id) c.fixed32.encode(state, this._io.table.id)
    if (token) c.fixed32.encode(state, token)

    c.uint.encode(state, this.command)

    if (this.target) c.fixed32.encode(state, this.target)
    if (value) c.buffer.encode(state, value)

    return state.buffer
  }
}

class CongestionWindow {
  constructor(maxWindow) {
    this._i = 0
    this._total = 0
    this._window = [0, 0, 0, 0]
    this._maxWindow = maxWindow
  }

  clear() {
    this._i = 0
    this._total = 0
    this._window = [0, 0, 0, 0]
  }

  isFull() {
    return this._total >= 2 * this._maxWindow || this._window[this._i] >= this._maxWindow
  }

  recv() {
    if (this._window[this._i] > 0) {
      this._window[this._i]--
      this._total--
    }
  }

  send() {
    this._total++
    this._window[this._i]++
  }

  drain() {
    this._i = (this._i + 1) & 3
    this._total -= this._window[this._i]
    this._window[this._i] = 0 // clear oldest
  }
}

function noop() {}

function oncycle(req) {
  req._timeout = null
  req.oncycle(req)
  if (req.sent > req.retries) {
    req._io.stats.requests.timeouts++
    req.destroy(REQUEST_TIMEOUT())
    req._io.ontimeout(req)
  } else {
    req._io.stats.requests.retries++
    req.send()
  }
}

function decodeReply(from, state) {
  try {
    const flags = c.uint.decode(state)
    const tid = c.uint16.decode(state)
    const to = peer.ipv4.decode(state)
    const id = flags & 1 ? c.fixed32.decode(state) : null
    const token = flags & 2 ? c.fixed32.decode(state) : null
    const closerNodes = flags & 4 ? peer.ipv4Array.decode(state) : null
    const error = flags & 8 ? c.uint.decode(state) : 0
    const value = flags & 16 ? c.buffer.decode(state) : null

    if (id !== null) from.id = validateId(id, from)

    return { tid, rtt: 0, from, to, token, closerNodes, error, value }
  } catch {
    return null
  }
}

function validateId(id, from) {
  const expected = peer.id(from.host, from.port)
  return b4a.equals(expected, id) ? expected : null
}
const sodium = require('sodium-universal')
const c = require('compact-encoding')
const b4a = require('b4a')

const ipv4 = {
  ...c.ipv4Address,
  decode(state) {
    const ip = c.ipv4Address.decode(state)
    return {
      id: null, // populated by the callee
      host: ip.host,
      port: ip.port
    }
  }
}

module.exports = { id, ipv4, ipv4Array: c.array(ipv4) }

function id(host, port, out = b4a.allocUnsafeSlow(32)) {
  const addr = out.subarray(0, 6)
  ipv4.encode({ start: 0, end: 6, buffer: addr }, { host, port })
  sodium.crypto_generichash(out, addr)
  return out
}
const { Readable, getStreamError } = require('streamx')
const b4a = require('b4a')
const peer = require('./peer')
const { DOWN_HINT } = require('./commands')

const DONE = []
const DOWN = []

module.exports = class Query extends Readable {
  constructor(dht, target, internal, command, value, opts = {}) {
    super()

    dht.stats.queries.total++
    dht.stats.queries.active++

    this.force = !!opts.force
    this.dht = dht
    this.k = this.dht.table.k
    this.target = target
    this.internal = internal
    this.command = command
    this.value = value
    this.errors = 0
    this.successes = 0
    this.concurrency = opts.concurrency || this.dht.concurrency
    this.inflight = 0
    this.map = opts.map || defaultMap
    this.retries =
      opts.retries === 0 ? 0 : opts.retries || (this.internal && command === DOWN_HINT ? 3 : 5)
    this.closestReplies = []

    this._slow = 0
    this._slowdown = false
    this._seen = new Map()
    this._pending = []
    this._fromTable = false
    this._commit = opts.commit === true ? autoCommit : opts.commit || null
    this._commiting = false
    this._session = opts.session || dht.session()
    this._autoDestroySession = !opts.session
    this._onlyClosestNodes = false

    this._onvisitbound = this._onvisit.bind(this)
    this._onerrorbound = this._onerror.bind(this)
    this._oncyclebound = this._oncycle.bind(this)

    const nodes = opts.nodes || opts.closestNodes
    const replies = opts.replies || opts.closestReplies

    // add them reverse as we pop below
    if (nodes) {
      for (let i = nodes.length - 1; i >= 0; i--) {
        const node = nodes[i]
        this._addPending(
          {
            id: node.id || peer.id(node.host, node.port),
            host: node.host,
            port: node.port
          },
          null
        )
      }
    } else if (replies) {
      for (let i = replies.length - 1; i >= 0; i--) {
        this._addPending(replies[i].from, null)
      }
    }

    if (opts.onlyClosestNodes) this._onlyClosestNodes = true
  }

  get closestNodes() {
    const nodes = new Array(this.closestReplies.length)

    for (let i = 0; i < nodes.length; i++) {
      nodes[i] = this.closestReplies[i].from
    }

    return nodes
  }

  finished() {
    return new Promise((resolve, reject) => {
      if (this.destroyed) {
        const error = getStreamError(this)
        if (error) reject(error)
        else resolve()
        return
      }

      const self = this
      let error = null

      this.resume()
      this.on('error', onerror)
      this.on('close', onclose)

      function onclose() {
        self.removeListener('error', onerror)
        self.removeListener('close', onclose)
        if (error) reject(error)
        else resolve()
      }

      function onerror(err) {
        error = err
      }
    })
  }

  _addFromTable() {
    if (this._pending.length >= this.k) return
    this._fromTable = true

    const closest = this.dht.table.closest(this.target, this.k - this._pending.length)

    for (const node of closest) {
      this._addPending({ id: node.id, host: node.host, port: node.port }, null)
    }
  }

  async _open(cb) {
    this._addFromTable()
    if (this._pending.length >= this.k) return cb(null)

    for await (const node of this.dht._resolveBootstrapNodes()) {
      this._addPending(node, null)
    }

    cb(null)
  }

  _isCloser(id) {
    return (
      this.closestReplies.length < this.k ||
      this._compare(id, this.closestReplies[this.closestReplies.length - 1].from.id) < 0
    )
  }

  _addPending(node, ref) {
    if (this._onlyClosestNodes) return false

    const addr = node.host + ':' + node.port
    const refs = this._seen.get(addr)
    const isCloser = this._isCloser(node.id)

    if (refs === DONE) {
      return isCloser
    }

    if (refs === DOWN) {
      if (ref) this._downHint(ref, node)
      return isCloser
    }

    if (refs) {
      if (ref !== null) refs.push(ref)
      return isCloser
    }

    if (!isCloser) {
      return false
    }

    this._seen.set(addr, ref === null ? [] : [ref])
    this._pending.push(node)

    return true
  }

  _read(cb) {
    this._readMore()
    cb(null)
  }

  _readMore() {
    if (this.destroying || this._commiting) return

    const concurrency = (this._slowdown ? 3 : this.concurrency) + this._slow

    while (this.inflight < concurrency && this._pending.length > 0) {
      const next = this._pending.pop()
      if (next && next.id && !this._isCloser(next.id)) continue
      this._visit(next)
    }

    // if reusing closest nodes, slow down after the first readMore tick to allow
    // the closest node a chance to reply before going broad to question more
    if (!this._fromTable && this.successes === 0 && this.errors === 0) {
      this._slowdown = true
    }

    if (this._pending.length > 0) return

    // if no inflight OR all the queries we are waiting on are marked as slow and we have a full result.
    if (
      this.inflight === 0 ||
      (this._slow === this.inflight && this.closestReplies.length >= this.k)
    ) {
      // if more than 3/4 failed and we only used cached nodes, try again from the routing table
      if (!this._fromTable && this.successes < this.k / 4) {
        this._addFromTable()
        this._readMore()
        return
      }

      this._flush()
    }
  }

  _flush() {
    if (this._commiting) return
    this._commiting = true

    if (this._commit === null) {
      this.push(null)
      return
    }

    const p = []
    for (const m of this.closestReplies) p.push(this._commit(m, this.dht, this))
    this._endAfterCommit(p)
  }

  _endAfterCommit(ps) {
    if (!ps.length) {
      this.destroy(new Error('Too few nodes responded'))
      return
    }

    const self = this

    let pending = ps.length
    let success = 0

    for (const p of ps) p.then(ondone, onerror)

    function ondone() {
      success++
      if (--pending === 0) self.push(null)
    }

    function onerror(err) {
      if (--pending > 0) return
      if (success) self.push(null)
      else self.destroy(err)
    }
  }

  _dec(req) {
    if (req.oncycle === noop) {
      this._slow--
    } else {
      req.oncycle = noop
    }
    this.inflight--
  }

  _onvisit(m, req) {
    this._dec(req)

    const addr = req.to.host + ':' + req.to.port
    this._seen.set(addr, DONE)

    if (this._commiting) return

    if (m.error === 0) this.successes++
    else this.errors++

    if (m.error === 0 && m.from.id !== null && this._isCloser(m.from.id)) this._pushClosest(m)

    if (m.closerNodes !== null) {
      for (const node of m.closerNodes) {
        node.id = peer.id(node.host, node.port)
        if (this.dht._filterNode !== null && !this.dht._filterNode(node)) continue
        if (b4a.equals(node.id, this.dht.table.id)) continue
        // TODO: we could continue here instead of breaking to ensure that one of the nodes in the closer list
        // is later marked as DOWN that we gossip that back
        if (!this._addPending(node, m.from)) break
      }
    }

    if (!this._fromTable && this.successes + this.errors >= this.concurrency) {
      this._slowdown = false
    }

    if (m.error !== 0) {
      this._readMore()
      return
    }

    const data = this.map(m)
    if (!data || this.push(data) !== false) {
      this._readMore()
    }
  }

  _onerror(err, req) {
    const addr = req.to.host + ':' + req.to.port
    const refs = this._seen.get(addr)

    if (err.code === 'REQUEST_TIMEOUT') {
      this._seen.set(addr, DOWN)
      for (const node of refs) this._downHint(node, req.to)
    }

    this._dec(req)
    this.errors++
    this._readMore()
  }

  _oncycle(req) {
    req.oncycle = noop
    this._slow++
    this._readMore()
  }

  _downHint(node, down) {
    // Check rate limit
    if (
      this.dht._downHintsRateLimit !== -1 &&
      this.dht._downHintsSentPerTick >= this.dht._downHintsRateLimit
    ) {
      return null
    }

    this.dht._downHintsSentPerTick++

    const state = { start: 0, end: 6, buffer: b4a.allocUnsafe(6) }
    peer.ipv4.encode(state, down)
    this.dht._request(node, false, true, DOWN_HINT, null, state.buffer, this._session, noop, noop)
  }

  _pushClosest(m) {
    this.closestReplies.push(m)
    for (let i = this.closestReplies.length - 2; i >= 0; i--) {
      const prev = this.closestReplies[i]
      const cmp = this._compare(prev.from.id, m.from.id)
      // if sorted, done!
      if (cmp < 0) break
      // if dup, splice it out (rare)
      if (cmp === 0) {
        this.closestReplies.splice(i + 1, 1)
        break
      }
      // swap and continue down
      this.closestReplies[i + 1] = prev
      this.closestReplies[i] = m
    }
    if (this.closestReplies.length > this.k) this.closestReplies.pop()
  }

  _compare(a, b) {
    for (let i = 0; i < a.length; i++) {
      if (a[i] === b[i]) continue
      const t = this.target[i]
      return (t ^ a[i]) - (t ^ b[i])
    }
    return 0
  }

  _visit(to) {
    this.inflight++

    const req = this.dht._request(
      to,
      this.force,
      this.internal,
      this.command,
      this.target,
      this.value,
      this._session,
      this._onvisitbound,
      this._onerrorbound
    )
    if (req === null) {
      this.destroy(new Error('Node was destroyed'))
      return
    }
    req.retries = this.retries
    req.oncycle = this._oncyclebound
    if (this.force) req.retries = 0
  }

  _destroy(cb) {
    this.dht.stats.queries.active--
    if (this._autoDestroySession) this._session.destroy()
    cb(null)
  }
}

function autoCommit(reply, dht, query) {
  if (!reply.token) return Promise.reject(new Error('No token received for closest node'))
  return dht.request(
    {
      token: reply.token,
      target: query.target,
      command: query.command,
      value: query.value
    },
    reply.from
  )
}

function defaultMap(m) {
  return m
}

function noop() {}
module.exports = class Session {
  constructor(dht) {
    this.dht = dht
    this.inflight = []
  }

  _attach(req) {
    req.index = this.inflight.push(req) - 1
  }

  _detach(req) {
    const i = req.index
    if (i === -1) return
    req.index = -1

    if (i === this.inflight.length - 1) this.inflight.pop()
    else {
      const req = (this.inflight[i] = this.inflight.pop())
      req.index = i
    }
  }

  query({ target, command, value }, opts = {}) {
    return this.dht.query({ target, command, value }, { ...opts, session: this })
  }

  request({ token, command, target, value }, { host, port }, opts = {}) {
    return this.dht.request(
      { token, command, target, value },
      { host, port },
      { ...opts, session: this }
    )
  }

  ping({ host, port }, opts = {}) {
    return this.dht.ping({ host, port }, { ...opts, session: this })
  }

  destroy(err) {
    while (this.inflight.length) {
      const req = this.inflight[0]
      // prevent destroyed requests from contributing to congestion counts
      this.dht.io.congestion.recv()
      req.destroy(err)
    }
  }
}
{
  "name": "dht-rpc",
  "version": "6.27.0",
  "description": "Make RPC calls over a Kademlia based DHT",
  "main": "index.js",
  "files": [
    "index.js",
    "lib/*.js"
  ],
  "imports": {
    "events": {
      "bare": "bare-events",
      "default": "events"
    }
  },
  "dependencies": {
    "adaptive-timeout": "^1.0.1",
    "b4a": "^1.6.1",
    "bare-events": "^2.2.0",
    "compact-encoding": "^3.0.0",
    "compact-encoding-net": "^1.2.0",
    "fast-fifo": "^1.1.0",
    "kademlia-routing-table": "^1.0.1",
    "nat-sampler": "^1.0.1",
    "sodium-universal": "^5.0.0",
    "streamx": "^2.13.2",
    "time-ordered-set": "^2.0.0",
    "udx-native": "^1.5.3"
  },
  "devDependencies": {
    "brittle": "^3.0.0",
    "lunte": "^1.3.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^2.0.0",
    "test-suspend": "^1.0.0"
  },
  "scripts": {
    "format": "prettier --write .",
    "test": "npm run test:node && npm run test:bare",
    "test:bare": "brittle-bare --coverage test.js",
    "lint": "prettier --check . && lunte",
    "test:node": "brittle-node --coverage test.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/holepunchto/dht-rpc.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/holepunchto/dht-rpc/issues"
  },
  "homepage": "https://github.com/holepunchto/dht-rpc#"
}
module.exports = require('bare-events')
{
  "name": "events-universal",
  "version": "1.0.1",
  "description": "Universal wrapper for the Node.js events module",
  "exports": {
    "./package": "./package.json",
    ".": {
      "bare": "./bare.js",
      "react-native": "./react-native.js",
      "default": "./default.js"
    }
  },
  "files": [
    "index.js",
    "default.js",
    "bare.js",
    "react-native.js"
  ],
  "scripts": {
    "test": "prettier . --check"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/events-universal.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/events-universal/issues"
  },
  "homepage": "https://github.com/holepunchto/events-universal#readme",
  "dependencies": {
    "bare-events": "^2.7.0"
  },
  "devDependencies": {
    "prettier": "^3.4.2",
    "prettier-config-holepunch": "^1.0.0"
  }
}
module.exports = class FixedFIFO {
  constructor (hwm) {
    if (!(hwm > 0) || ((hwm - 1) & hwm) !== 0) throw new Error('Max size for a FixedFIFO should be a power of two')
    this.buffer = new Array(hwm)
    this.mask = hwm - 1
    this.top = 0
    this.btm = 0
    this.next = null
  }

  clear () {
    this.top = this.btm = 0
    this.next = null
    this.buffer.fill(undefined)
  }

  push (data) {
    if (this.buffer[this.top] !== undefined) return false
    this.buffer[this.top] = data
    this.top = (this.top + 1) & this.mask
    return true
  }

  shift () {
    const last = this.buffer[this.btm]
    if (last === undefined) return undefined
    this.buffer[this.btm] = undefined
    this.btm = (this.btm + 1) & this.mask
    return last
  }

  peek () {
    return this.buffer[this.btm]
  }

  isEmpty () {
    return this.buffer[this.btm] === undefined
  }
}
const FixedFIFO = require('./fixed-size')

module.exports = class FastFIFO {
  constructor (hwm) {
    this.hwm = hwm || 16
    this.head = new FixedFIFO(this.hwm)
    this.tail = this.head
    this.length = 0
  }

  clear () {
    this.head = this.tail
    this.head.clear()
    this.length = 0
  }

  push (val) {
    this.length++
    if (!this.head.push(val)) {
      const prev = this.head
      this.head = prev.next = new FixedFIFO(2 * this.head.buffer.length)
      this.head.push(val)
    }
  }

  shift () {
    if (this.length !== 0) this.length--
    const val = this.tail.shift()
    if (val === undefined && this.tail.next) {
      const next = this.tail.next
      this.tail.next = null
      this.tail = next
      return this.tail.shift()
    }

    return val
  }

  peek () {
    const val = this.tail.peek()
    if (val === undefined && this.tail.next) return this.tail.next.peek()
    return val
  }

  isEmpty () {
    return this.length === 0
  }
}
{
  "name": "fast-fifo",
  "version": "1.3.2",
  "description": "A fast fifo implementation similar to the one powering nextTick in Node.js core",
  "main": "index.js",
  "files": [
    "./index.js",
    "./fixed-size.js"
  ],
  "dependencies": {},
  "devDependencies": {
    "standard": "^17.1.0",
    "brittle": "^3.3.2"
  },
  "scripts": {
    "test": "standard && brittle test.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/mafintosh/fast-fifo.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/mafintosh/fast-fifo/issues"
  },
  "homepage": "https://github.com/mafintosh/fast-fifo"
}
// Attestation — re-exported from `favela-identity`, which is where it lives now.
//
// ⛔ ONE COPY. This file used to hold the implementation, and its own header explained why the
// keet-identity-key `ProofEncoding` coupling "lives HERE so there is one place to fix if it moves".
// Other components need the same verifier — proving who someone is from a chain proof alone — and a
// second copy would put that coupling in two places. That is the failure
// `favela-markdown/lib/reach.js` names: "written three times… and the third one would eventually be
// written wrong".
//
// ⚠ Kept as a re-export rather than deleted. Eight modules import it by this path, and relocating an
// implementation is not a reason to touch eight call sites in the same change.
//
// ⚠ And the wording here is load-bearing: `test/boundary.test.js` refuses any source file in this
// package that names another network's stack, and the first draft of this comment named two. The
// daemon does not know what is downstream of it, including in its prose.
export { readAttested, readProof, signAttested } from 'favela-identity/lib/attestation.js'
export { default } from 'favela-identity/lib/attestation.js'
// Avatars — bytes in the overlay, an oid on the User.
//
// ⛔ NOT a record, and not a data: URI in anyone's frontmatter.
//
// A display name and a picture are properties of an ACCOUNT, not of something you published.
// `NAMING.md` §2 is explicit that a name must never appear in a record — a name is reclaimable, so
// a name baked into immutable bytes can later denote a different person. The same argument covers
// the picture: it changes, and rewriting every record on a change is exactly what a mutable
// registry row exists to avoid.
//
// So this follows pear-git-v3 exactly: `profilePicture` is a hyper-overlay CONTENT HASH stored as a
// plain string on the user record, and the bytes live in the overlay like any other content.
//
// ── Why the overlay and not a blob store ──────────────────────────────────────────────────────
//
// There is no blob store. The overlay already stores files by content hash and replicates them by
// hash — it is what every record's bytes already ride. An avatar is a file; nothing new is needed.
//
// ── The write order ───────────────────────────────────────────────────────────────────────────
//
// Hash first, then name the file by its hash, then register. The oid cannot be known before the
// bytes exist, so writing straight to the final name would mean guessing it.
//
// ⛔ PATHS, not bytes. An earlier version took base64 in and handed base64 back over the control
// socket, with a comment beside it saying the bytes travel over the overlay — true between PEERS
// and false for the local hop, which encoded a file to cross a unix socket to a process on the same
// machine that wrote it straight back to disk. `registerFile` takes a path; so does this.

import fs from '#node:fs'
import { join } from '#node:path'
import b4a from 'b4a'

// 512 KiB. An avatar rides the socket as base64 on the way in and is re-fetched by peers on the way
// out, so it is capped for both reasons. Stated in bytes of IMAGE, not of base64.
export const MAX_BYTES = 512 * 1024

// Typed from the MAGIC BYTES, never from a filename or a client-supplied content-type. An extension
// is a claim by whoever named the file; the first four bytes are the fact.
//
// ⛔ SVG is not here on purpose: it is a document that can carry script and fetch remote resources.
// png, jpeg, gif and webp cannot express either.
export function sniff (bytes) {
  if (!bytes || bytes.length < 12) return null
  const b = bytes
  if (b[0] === 0x89 && b[1] === 0x50 && b[2] === 0x4e && b[3] === 0x47) return 'image/png'
  if (b[0] === 0xff && b[1] === 0xd8 && b[2] === 0xff) return 'image/jpeg'
  if (b[0] === 0x47 && b[1] === 0x49 && b[2] === 0x46 && b[3] === 0x38) return 'image/gif'
  if (b4a.toString(b.subarray(0, 4)) === 'RIFF' && b4a.toString(b.subarray(8, 12)) === 'WEBP') return 'image/webp'
  return null
}

export class Avatars {
  constructor (storage, overlay) {
    this.dir = join(storage, 'avatars')
    this.overlay = overlay
  }

  _path (oid) { return join(this.dir, oid) }

  /**
   * Store an image that already exists on disk, returning `{ oid, mimeType, size }`.
   *
   * ⛔ Refuses anything that is not an inert raster image, by its bytes. The caller may be relaying
   * a browser upload, so nothing it says about the file is trusted.
   */
  async put (srcPath) {
    if (typeof srcPath !== 'string' || !srcPath) throw new Error('an avatar needs a path')
    let bytes
    try { bytes = fs.readFileSync(srcPath) } catch { throw new Error('no file at ' + srcPath) }
    if (!bytes.length) throw new Error('that file is empty')

    // ⚠ This cap is a POLICY, not a transport limit — an avatar is shown at thumbnail size and is
    // re-fetched by every peer that renders a byline, so a large one is waste rather than an error
    // in the plumbing. Files posted in a record have no cap; there is nothing to cap.
    if (bytes.length > MAX_BYTES) {
      throw new Error('that image is ' + Math.round(bytes.length / 1024) + ' KiB and the cap is ' +
        (MAX_BYTES / 1024) + ' KiB — scale it down first')
    }
    const mimeType = sniff(bytes)
    if (!mimeType) {
      throw new Error('that is not a png, jpeg, gif or webp — those are the four an avatar may be, ' +
        'and the check is on the bytes rather than the file name')
    }

    fs.mkdirSync(this.dir, { recursive: true })
    const h = await this.overlay.hashFile(srcPath)
    const oid = h && h.contentHash
    if (!oid) throw new Error('the overlay could not hash the image')
    // Named by its hash, then registered. Identical images collapse to one file, across every
    // account this node holds.
    const dest = this._path(oid)
    fs.copyFileSync(srcPath, dest)
    await this.overlay.registerFile('/avatars/' + oid, dest, { size: bytes.length })

    return { oid, mimeType, size: bytes.length }
  }

  /**
   * Make sure the image is on local disk and say where it is: `{ path, mimeType, size }`, or null.
   *
   * ⛔ Returns null rather than throwing when it cannot be had. An avatar that has not replicated
   * yet is a missing picture, not a broken page — the same distinction the record log makes between
   * absent and unresolved.
   */
  async get (oid) {
    if (typeof oid !== 'string' || !/^[0-9a-f]{8,128}$/i.test(oid)) return null
    const p = this._path(oid)

    const describe = (at = p) => {
      try {
        const bytes = fs.readFileSync(at)
        const mimeType = sniff(bytes)
        return mimeType ? { path: at, mimeType, size: bytes.length } : null
      } catch {
        return null
      }
    }

    const local = describe()
    if (local) return local

    try {
      fs.mkdirSync(this.dir, { recursive: true })
      const r = await this.overlay.fetchFile(oid, { destPath: p })
      if (!r) return null
      // ⛔ DESCRIBE WHERE IT ACTUALLY IS. On a LOCAL hit the overlay returns the path it already
      // holds the bytes at — which may be the OTHER store's directory, since Files and Avatars
      // fetch the same content hash from the same overlay and differ only in where they cache.
      // Re-reading our own expected path meant a file the overlay had was reported missing: on
      // ashatron, `file-get` for its own avatar answered {found:false} while the bytes sat in
      // avatars/<oid>, and every consumer downstream faithfully rendered no picture.
      return describe(r.destPath || p)
    } catch {
      return null
    }
  }
}

export default Avatars
// Browser identities — the web-UI half of auth.
//
// ── Why a browser is not a device ─────────────────────────────────────────────────────────────
//
// A device writes to the feed: it holds a writer core and a chain proof to the identity root. A
// browser cannot. It cannot run hypercore, cannot hold a writer core, and cannot even verify a
// chain — `keet-identity-key` runs on sodium-native, which is not a thing in a browser.
//
// So a browser is a CONTROLLER: it authenticates to a daemon and the daemon writes. Its key never
// enters the attestation chain, which is also why it needs no scope field — a chain proof has none
// to give (`{version, epoch, identity, chain, data?}` and nothing else), and attesting a browser
// key would hand localStorage the power to mint devices.
//
// ── One implementation of the wire format ─────────────────────────────────────────────────────
//
// The `anproto` package, which is what pear-git-v3 depends on. NOT one of the six-plus copies of
// the same protocol scattered through this monorepo — three of which still carry a greedy
// timestamp parse that eats the hash's first character whenever it happens to be a digit
// (~1 in 6 messages; measured 59/300 against hyper-identity's copy before it was pinned).
//
// The canonical package has always split on exactly 13 digits (`lib/an.js:119`), which is why the
// forge never saw it. Depending on the package is how we avoid becoming the seventh copy.
//
// The registry is `hyper-identity2` — the autobee binding — because this daemon runs autobee and
// must not carry autobase beside it.
//
// ── The gate ──────────────────────────────────────────────────────────────────────────────────
//
// `autoRegister: false`. An unknown key is refused, full stop. A browser gets in exactly one way:
// a ONE-TIME INVITE, generated by a human at the CLI or an already-authenticated web-UI.
//
// ⛔ The invite EXPIRES; the identity it creates does NOT. Those are different objects. An invite
// that never expired would be a standing hole; a browser that already proved itself stays until it
// is revoked. Only the token's HASH reaches the log, so holding the log never yields an invite, and
// single-use is enforced inside apply — two browsers racing one invite cannot both win.
//
// ── ⛔ TOFU: the FIRST browser claims the instance ─────────────────────────────────────────────
//
// With no browser registered, the first key to prove itself is accepted without an invite. That is
// pear-git-v3's shape — `envelope-ctx.js` creates a user for any verified envelope and gives the
// first one admin — and it exists so a fresh install is usable before it can mint an invite.
//
// ⛔ It closes permanently on the first claim. Every browser after that needs an invite; there is no
// window that reopens, and revoking the claimant does NOT reopen it either — a revoked identity is
// still a registered one, which is exactly why the listing keeps it.
//
// ⚠ The safety of this rests ENTIRELY on the surface binding localhost. Whoever reaches the page
// first owns the instance, so exposing it before claiming it hands it to whoever is on the network.
// pear-git-v3 carries the same property. It is a first-run convenience, not an authorisation model.
//
// ⚠ And it races: two browsers arriving together can both read "nobody registered". The window is
// one round trip on a loopback socket, and pgv3 has the same race, but it is real and not papered
// over here.

import { IdentityRegistry } from 'hyper-identity2'
import { an, Challenges } from 'anproto'

// The feed owner's user record. One identity at a time, so one user — every browser grant links to
// this one, and revoking a browser never touches the account.
const OWNER = 'owner'

export class Browsers {
  constructor (corestore, opts = {}) {
    // ⛔ ITS OWN NAMESPACE. Two Autobees on one Corestore derive the same local writer and DEADLOCK
    // — they do not error, they hang (measured 2026-09-06, favela-peer/test/replicate.test.js).
    // The feed is already namespaced; this must be too.
    this._store = corestore.namespace('identity')
    this.registry = new IdentityRegistry(this._store, {})
    // Keyed by the challenge string itself, single-use, TTL-swept. No ids to correlate.
    this.challenges = new Challenges(opts.challenges || {})
    this._owner = null
  }

  async ready () {
    await this.registry.ready()
    // Idempotent: the owner is created once and found thereafter.
    this._owner = await this.registry.getUser(OWNER).catch(() => null)
    if (!this._owner) {
      this._owner = await this.registry.createUser({ userId: OWNER, name: 'owner' })
    }
    return this
  }

  /** A fresh single-use challenge for the browser to sign. */
  challenge () {
    return { challenge: this.challenges.generate() }
  }

  /**
   * A one-time invite. This is the gate for an arbitrary browser.
   *
   * Returns the plaintext token, which is the only copy — the log holds its hash. Hand it to the
   * person at the browser out of band.
   */
  async invite (opts = {}) {
    const { token, expiresAt } = await this.registry.createLinkToken(OWNER, opts)
    return { token, expiresAt }
  }

  /**
   * Redeem an invite: prove the key, then attach it.
   *
   * ⛔ The signature is checked BEFORE the token is spent. Otherwise a wrong signature would burn
   * a single-use invite, which turns a typo into a denial of service.
   */
  async redeem ({ token, publicKey, signature, challenge }) {
    if (!token) throw new Error('redeem needs an invite token')
    await this._proveKey({ publicKey, signature, challenge })
    await this.registry.registerIdentity({
      identityId: publicKey, method: 'web-identity', publicKey, credentials: { publicKey }
    })
    await this.registry.consumeLinkToken(token, publicKey)
    return { publicKey }
  }

  /**
   * Authenticate an already-registered browser. Every request, not once per session.
   *
   * There is no bearer token to steal or expire: possession of the key is re-proven each time, and
   * the provider checks revocation on every call, so a revoke takes effect on the next request.
   */
  async authenticate ({ publicKey, signature, challenge }) {
    await this._proveKey({ publicKey, signature, challenge })

    const identity = await this.registry.getIdentityByPublicKey(publicKey)
    // ⛔ autoRegister is OFF by construction: an unknown key is refused here, and the ONLY way to
    // become known is to redeem a one-time invite. Proving you hold a key is not permission.
    if (!identity) throw new Error('unknown identity — redeem an invite first')
    if (await this.registry.isRevoked(identity.identityId)) throw new Error('identity has been revoked')

    return { identityId: identity.identityId, method: 'web-identity', credentials: { publicKey } }
  }

  /**
   * Is this browser known, and is it still allowed?
   *
   * ⛔ No signature. This is for a caller that has ALREADY verified possession once and now holds a
   * session — it re-checks standing on every request so a revoke lands immediately, without making
   * a no-JS page sign each navigation. Possession is proven at login; permission is checked always.
   */
  async status (publicKey) {
    const identity = await this.registry.getIdentityByPublicKey(publicKey)
    if (!identity) return { known: false, revoked: false }
    return { known: true, revoked: !!(await this.registry.isRevoked(identity.identityId)) }
  }

  /**
   * Has anyone claimed this instance yet?
   *
   * Reported so the login page can offer the claim rather than demand a token nobody can mint yet.
   * It reveals only whether a browser is registered — no key, no name.
   */
  async unclaimed () {
    return (await this.list()).length === 0
  }

  /**
   * Claim an unclaimed instance — TOFU. See the header.
   *
   * ⛔ Refused the moment ANY browser is registered, revoked ones included. Revocation must not
   * reopen the door, or "retire that browser" would silently mean "let the next stranger in".
   */
  async claim ({ publicKey, signature, challenge }) {
    await this._proveKey({ publicKey, signature, challenge })
    if (!(await this.unclaimed())) {
      throw new Error('this instance is already claimed — ask its owner for an invite')
    }
    await this.registry.registerIdentity({
      identityId: publicKey, method: 'web-identity', publicKey, credentials: { publicKey }
    })
    // ⛔ Registering an identity is not attaching it. `redeem` gets the link from
    // `consumeLinkToken`; a claim has no token, so it must link explicitly — otherwise the key
    // verifies, the instance still reads as unclaimed, and the next browser claims it too.
    await this.registry.linkIdentityToUser(publicKey, OWNER, { verified: true })
    return { publicKey, claimed: true }
  }

  // ⛔ The profile is NOT here. It was, and that was wrong: this registry is local to a node and
  // never replicates, so a name and picture kept here are invisible to everyone who reads the feed
  // — no name, no avatar, nothing to search. It lives in the feed as a `profile/self` record; see
  // peer.js. This class holds browser credentials, which are correctly local.

  /** Retire a browser. The only way out — there is no expiry. */
  async revoke (publicKey) {
    await this.registry.revokeIdentity(publicKey)
    return { revoked: publicKey }
  }

  /** Registered browsers, with their revocation state named rather than filtered away. */
  async list () {
    const result = await this.registry.getUserWithIdentities(OWNER).catch(() => null)
    const identities = (result && result.identities) || []
    const out = []
    for (const i of identities) {
      if (i.method !== 'web-identity') continue
      out.push({ publicKey: i.publicKey || i.identityId, revoked: !!(await this.registry.isRevoked(i.identityId)) })
    }
    return out
  }

  /**
   * Prove possession of the key against a live challenge.
   *
   * The package gives `open` (verify the signature, return the signed string) and `parse` (split it
   * into timestamp and hash on exactly 13 digits). Composing them here rather than reimplementing
   * either is the whole point of depending on the package.
   */
  async _proveKey ({ publicKey, signature, challenge }) {
    if (!publicKey || !signature || !challenge) {
      throw new Error('a browser must present { publicKey, signature, challenge }')
    }
    if (!this.challenges.has(challenge)) throw new Error('invalid or expired challenge')

    let opened
    try { opened = await an.open(signature) } catch { throw new Error('invalid signature') }
    if (!opened) throw new Error('invalid signature')

    // ⛔ The envelope EMBEDS the signer's public key, as its first 44 characters — that is what
    // `an.open` verifies against. Checking it against the CLAIMED key is what stops a valid
    // signature by one key being presented as another's.
    //
    // ⚠ Not `an.publicKey()`: that reads a 132-char KEYPAIR, not a signature, and refuses anything
    // else. Two different objects whose public halves are both 44 leading characters.
    if (signature.slice(0, 44) !== publicKey) {
      throw new Error('signature public key does not match the claimed key')
    }

    const { hash } = an.parse(opened)
    if (hash !== await an.hash(challenge)) throw new Error('signature does not cover this challenge')

    // ⛔ Consumed only after every check passes, so a bad signature cannot burn a live challenge.
    if (!this.challenges.consume(challenge)) throw new Error('challenge already used')
  }

  async close () {
    try { await this.registry.close() } catch {}
  }
}

export default Browsers
// The device credential on disk, and the ceremonies that produce one.
//
// ── What a device IS ──────────────────────────────────────────────────────────────────────────
//
// A real Ed25519 keypair with a chain proof to the identity root: `{ keyPair, proof }`. It CAN
// sign, which is what makes everything else work — an already-linked device attests the next one
// from its own keypair, with no mnemonic. That is exactly what pear-git-v3's DeviceLinker ships
// over blind-pairing (`{ deviceSecretKey, deviceProof, rootPubkey }`, never the root secret).
//
// ⛔ A hypercore WRITER key is not a device. It is manifest-derived, nobody holds its secret half,
// and it can never sign — measured: attesting one and then trying to attest FROM it is refused. So
// a writer core is BOUND to a device as attested data (`bindWriter`), and the roster records which
// device stands behind each core.
//
// ⇒ The vault is opened for exactly two things: minting the FIRST device of an identity, and
// recovering onto a machine that has no linked device and no peer to pair with. Never at boot, and
// never to add a device when another one is reachable.
//
// ⚠ An earlier version of this file concluded the opposite — that every device needs the vault —
// from a measurement taken against a writer core key rather than a device keypair. The primitive
// was never the limitation; the modelling was.

import fs from '#node:fs'
import { join } from '#node:path'
import b4a from 'b4a'
import crypto from 'hypercore-crypto'
import IdentityKey from 'keet-identity-key'
// ⛔ THEIRS, NOT OURS. argon2id + secretbox, and it is that only because the scheme it replaced was
// repeating-key XOR against a mnemonic drawn from a PUBLIC 2048-word list — recoverable WITHOUT the
// passphrase (hyper-social #47/#48). This monorepo already carries several copies of one signing
// format; a fourth vault crypto is how that class of bug returns. Import the one that was fixed.
import { CryptoManager } from 'hyperident/lib/crypto.js'

const FILE = 'device.json'
const FEED = 'feed.key'
const NAMESPACE = 'namespace.key'
const NAMESPACE_JSON = 'namespace.json'
const NAMESPACES_JSON = 'namespaces.json'
const ROOMS = 'rooms.json'

export function proofPath (storage) { return join(storage, FILE) }
export function feedKeyPath (storage) { return join(storage, FEED) }

/**
 * The feed this node writes to, if it was told one.
 *
 * ⛔ A node that founds its own feed never writes this — the key is derived when the log is created.
 * It exists for a device that JOINED an identity: pairing hands over the feed key along with the
 * credential, because being someone's device and writing to their feed are two different facts and
 * only the first is in the chain.
 */
export function loadFeedKey (storage) {
  try {
    const s = fs.readFileSync(feedKeyPath(storage), 'utf8').trim()
    return /^[0-9a-f]{64}$/.test(s) ? s : null
  } catch {
    return null
  }
}

/**
 * The namespace this node resolves handles in.
 *
 * ⛔ A FILE, not a constant. Shipping a hardcoded namespace would make us its authority; NAMING.md
 * §8 #2 is explicit that a default which cannot be changed is a root. Edit this file to join a
 * different one; delete it to start your own.
 */
export function loadNamespaceKey (storage) {
  try {
    const s = fs.readFileSync(join(storage, NAMESPACE), 'utf8').trim()
    return /^[0-9a-f]{64}$/.test(s) ? s : null
  } catch {
    return null
  }
}

export function saveNamespaceKey (storage, hex) {
  fs.writeFileSync(join(storage, NAMESPACE), String(hex).trim() + '\n')
}

/**
 * The namespace this node resolves handles in, AND the Corestore slot its log lives under.
 *
 * ⛔ The slot has to be recorded, not derived. Two Autobees sharing one Corestore namespace derive
 * the same local writer and DEADLOCK rather than erroring (`node.js`, measured 2026-09-06), so each
 * namespace gets its own — but the one this node CREATED could not be named after its key, because
 * the key does not exist until the log is opened. So it lives in the legacy slot `names` and joined
 * ones get `names:<key>`. Guessing that from the key alone is impossible; the file says which.
 *
 * ⚠ Seeds from the legacy `namespace.key`, which every deployed host holds, and never deletes it.
 */
export function loadNamespace (storage) {
  try {
    const raw = JSON.parse(fs.readFileSync(join(storage, NAMESPACE_JSON), 'utf8'))
    if (raw && /^[0-9a-f]{64}$/.test(raw.key) && typeof raw.slot === 'string' && raw.slot) return raw
  } catch {}
  const legacy = loadNamespaceKey(storage)
  // The legacy file names a log that was opened under `names`, and that is where its claims are.
  return legacy ? { key: legacy, slot: 'names' } : null
}

export function saveNamespace (storage, { key, slot }) {
  fs.writeFileSync(join(storage, NAMESPACE_JSON), JSON.stringify({ key, slot }, null, 2) + '\n')
}

/**
 * Every namespace this node has on disk — `[{ key, slot }]`, current one included.
 *
 * ⛔ AN INDEX, because the slots cannot be enumerated. A joined namespace lives under
 * `names:<key>` and the founded one under the legacy `names`, and nothing on disk lists them — so
 * `namespaceSettings` could truthfully promise "you can come back to it, handles and all" while
 * giving nobody a way to find out what "it" was. A 64-hex key you have to remember is not a door.
 *
 * ⚠ LOCAL state, exactly like `loadRooms`: which namespaces this machine holds is a fact about this
 * machine, not something derivable from anyone's log.
 */
export function loadNamespaces (storage) {
  let list = []
  try {
    const raw = JSON.parse(fs.readFileSync(join(storage, NAMESPACES_JSON), 'utf8'))
    if (Array.isArray(raw)) list = raw.filter((n) => n && /^[0-9a-f]{64}$/.test(n.key) && typeof n.slot === 'string' && n.slot)
  } catch {}
  // ⛔ The CURRENT one is always in the list, even if the index predates it or was never written.
  // An index that could omit the namespace you are standing in would offer you a switch to nowhere.
  const cur = loadNamespace(storage)
  if (cur && !list.some((n) => n.key === cur.key)) list = [{ key: cur.key, slot: cur.slot }, ...list]
  return list
}

/**
 * Which Corestore slot a namespace's log belongs in — pure, so the rule can be tested.
 *
 * ⛔ THE FOUNDED NAMESPACE IS THE WHOLE REASON THIS EXISTS. Its log lives in the legacy slot
 * `names`, which `namespaceSlot(key)` cannot produce because the key did not exist when the slot was
 * named. So leaving it and returning computed `names:<key>`, opened a SECOND log, and left every
 * handle claimed there resolving from a slot this node no longer used. Nothing was lost; it was
 * somewhere else, which is worse, because it presents as loss.
 *
 * Order matters: the namespace we are standing in knows its own slot, then the index, then derive.
 */
export function slotFor (key, { current = null, known = [] } = {}) {
  if (current && current.key === key && current.slot) return current.slot
  const recorded = (known || []).find((n) => n && n.key === key && n.slot)
  return recorded ? recorded.slot : namespaceSlot(key)
}

export function saveNamespaces (storage, list) {
  const seen = new Set()
  const clean = []
  for (const n of list || []) {
    if (!n || !/^[0-9a-f]{64}$/.test(n.key) || seen.has(n.key)) continue
    seen.add(n.key)
    clean.push({ key: n.key, slot: n.slot })
  }
  fs.writeFileSync(join(storage, NAMESPACES_JSON), JSON.stringify(clean, null, 2) + '\n')
}

/**
 * The rooms this node is in — `[{ key, slot }]`.
 *
 * ⚠ LOCAL state, not derived from anyone's log. A room key is a CAPABILITY: holding it is what lets
 * this node replicate the room at all, so the list of rooms it is in is a fact about this machine.
 */
export function loadRooms (storage) {
  try {
    const raw = JSON.parse(fs.readFileSync(join(storage, ROOMS), 'utf8'))
    return Array.isArray(raw) ? raw.filter((r) => r && /^[0-9a-f]{64}$/.test(r.key) && r.slot) : []
  } catch {
    return []
  }
}

export function saveRooms (storage, rooms) {
  fs.writeFileSync(join(storage, ROOMS), JSON.stringify(rooms, null, 2) + '\n')
}

/** The Corestore slot for a namespace we are JOINING rather than one we made. */
export const namespaceSlot = (keyHex) => 'names:' + String(keyHex).toLowerCase()

export function saveFeedKey (storage, hex) {
  fs.writeFileSync(feedKeyPath(storage), String(hex).trim() + '\n')
}

/** The stored proof, or null. Never throws — a missing credential is a state, not a fault. */
/** The stored credential `{ keyPair, proof }`, or null. Never throws. */
export function loadCredential (storage) {
  try {
    const raw = JSON.parse(fs.readFileSync(proofPath(storage), 'utf8'))
    if (!raw || !raw.proof || !raw.secretKey || !raw.publicKey) return null
    return {
      keyPair: { publicKey: b4a.from(raw.publicKey, 'hex'), secretKey: b4a.from(raw.secretKey, 'hex') },
      proof: raw.proof
    }
  } catch {
    return null
  }
}

// ⚠ This file holds a SECRET KEY. Mode 0600, and it is the one thing on disk whose loss means
// re-linking the device — not re-creating the identity, which lives only in the vault.
export function saveCredential (storage, cred) {
  const body = JSON.stringify({
    publicKey: b4a.toString(cred.keyPair.publicKey, 'hex'),
    secretKey: b4a.toString(cred.keyPair.secretKey, 'hex'),
    proof: cred.proof
  }, null, 2)
  fs.writeFileSync(proofPath(storage), body + '\n', { mode: 0o600 })
}

/**
 * Bind a hypercore writer key to this device — what an `add-device` op carries.
 *
 * The writer key travels as ATTESTED DATA inside the proof, so the result is good for that one
 * core and nothing else. Replaying it onto another core does not verify.
 */
export function bindWriter (cred, writerKeyHex) {
  const bound = IdentityKey.attestData(
    b4a.from(writerKeyHex, 'hex'), cred.keyPair, b4a.from(cred.proof, 'base64'))
  return b4a.toString(bound, 'base64')
}

/**
 * Attest a NEW device from this one. No mnemonic — the blind-pairing path.
 *
 * Returns a full credential for the target, whose SECRET KEY is generated here and must reach the
 * target over a channel that authenticated it (blind-pairing). Treat the return value as secret.
 */
export function mintForDevice (cred) {
  const keyPair = crypto.keyPair()
  const proof = IdentityKey.attestDevice(
    keyPair.publicKey, cred.keyPair, b4a.from(cred.proof, 'base64'))
  return { keyPair, proof: b4a.toString(proof, 'base64') }
}

/**
 * Mint a proof for `deviceKeyHex` from a hyperident vault file.
 *
 * ⚠ Reads the vault JSON directly rather than going through hyperident's IdentityManager, which
 * cannot load under Bare: `lib/file-ops.js` uses `process.` without importing `bare-process`
 * (`lib/debug-manager.js` too). One line each, in a repo we do not own here.
 *
 * ⛔ An ENCRYPTED vault now opens, and the crypto is HYPERIDENT'S — `CryptoManager` only, never
 * `IdentityManager`, for the Bare reason directly above. The previous version refused outright on the
 * grounds that "doing that badly is worse than not doing it", which was right while the alternative
 * was writing a fourth argon2id implementation in this monorepo. It is not right when the fixed one
 * can simply be imported, and it was a wall rather than a nuisance on a handset: no second machine to
 * link from, and no CLI to decrypt with.
 *
 * ⚠ Still a link-time ceremony a human is present for, not a boot path. The passphrase is passed in
 * by the caller that collected it; nothing here reads a prompt, and the mnemonic never leaves this
 * function.
 */
export async function mintFromVault (vaultPath, { passphrase = null } = {}) {
  let raw
  try {
    raw = JSON.parse(fs.readFileSync(vaultPath, 'utf8'))
  } catch (err) {
    throw new Error('no identity vault at ' + vaultPath + ' (' + err.message + ')')
  }

  if (!raw.mnemonic) throw new Error('the vault at ' + vaultPath + ' carries no mnemonic')

  let mnemonic = raw.mnemonic
  if (raw.encrypted) {
    // ⛔ NO `kdf` MEANS PRE-#47, AND THAT IS NOT A PASSPHRASE PROBLEM. Such a vault is XOR-"encrypted",
    // which protects nothing: the plaintext is lowercase words from a public list, ~150-200 bytes
    // against a 32-byte repeating key. Reporting "wrong passphrase" would send someone to try harder
    // at the wrong thing; the secret should be treated as EXPOSED and the identity re-minted.
    if (!raw.kdf) {
      throw new Error('the vault at ' + vaultPath + ' was written with the pre-#47 XOR scheme, which ' +
        'does not protect it. Recover the mnemonic with an older hyperident and re-create the vault. ' +
        'Treat the secret as exposed if the file ever left this machine.')
    }
    if (!passphrase) {
      throw new Error('the vault at ' + vaultPath + ' is encrypted — a passphrase is required to ' +
        'unlock it. (Linking from an ALREADY-LINKED device needs no passphrase and no mnemonic.)')
    }
    // ⚠ HONOUR THE COST THE VAULT WAS WRITTEN WITH. Using today's defaults would derive a different
    // key and surface as a wrong passphrase — a vault written on a phone at INTERACTIVE would become
    // unopenable the day the default is raised. The parameters are recorded per-vault for this.
    const params = {
      ...CryptoManager.kdfParams(),
      opslimit: raw.opslimit || CryptoManager.kdfParams().opslimit,
      memlimit: raw.memlimit || CryptoManager.kdfParams().memlimit
    }
    try {
      const box = Buffer.from(raw.mnemonic, 'hex')
      mnemonic = CryptoManager.decryptSecret(box, passphrase, Buffer.from(raw.salt, 'hex'), params).toString('utf8')
    } catch {
      // Deliberately not the underlying crypto error: secretbox failing and a mistyped passphrase are
      // the same event to the person at the keyboard, and the detail leaks nothing useful.
      throw new Error('the vault at ' + vaultPath + ' did not open — wrong passphrase')
    }
  }

  const idk = await IdentityKey.from({ mnemonic })
  // A FRESH device keypair, attested by the root. The vault's secret never leaves this function.
  const keyPair = crypto.keyPair()
  const proof = await IdentityKey.bootstrap({ identity: idk.identityKeyPair }, keyPair.publicKey)
  return { keyPair, proof: b4a.toString(proof, 'base64') }
}

export default { slotFor, loadNamespaces, saveNamespaces, loadRooms, saveRooms, proofPath, feedKeyPath, loadFeedKey, saveFeedKey, loadNamespaceKey, saveNamespaceKey, loadNamespace, saveNamespace, namespaceSlot, loadCredential, saveCredential, bindWriter, mintForDevice, mintFromVault }
// Feed — one identity's log, on autobee.
//
// The write path and the read path are NOT symmetric, and pretending otherwise is the usual way to
// misuse a multi-writer log. A write is an **op appended to your own writer core**; it becomes state
// only when the linearizer reaches it and `apply` runs. A read is against the linearized view. So
// `put()` returning does not mean `get()` will see it — `update()` does.
//
// Bytes never enter the log. `put` writes the record to the store first and appends only the
// pointer, so the core carries ~50 bytes per record instead of the record.
//
// ── Why autobee and not autobase ───────────────────────────────────────────────────────────────
//
// A feed's writers are one person's devices, so the writer set is usually TWO. Under autobase's
// majority (`floor(N/2)+1`) N=2 needs both indexers to ack every node: one device goes offline and
// `indexedLength` freezes while the view keeps growing, and you cannot self-heal by dropping to one.
// For a feed that is not an edge case, it is the normal posture. autobee replaces indexers and
// quorum with provable sort weights, which retires that class outright.
//
// ── The three autobee traps, all of which bite here ────────────────────────────────────────────
//
//  1. `node.key` is the writer key. autobase puts it on `node.from`. Attribution must never come
//     from the op body, so getting this wrong is silent misattribution rather than a crash.
//  2. The `view` inside apply is a WRITABLE hyperbee2 batch; `db.view` is a read-only snapshot.
//  3. hyperbee2 has no put/del and no key/valueEncoding — Buffers, and every mutation is a batch.

import Autobee from 'autobee'
import b4a from 'b4a'
import { applyOp, ROSTER, REVOKED, FOUNDER, KNOCKS, LISTING } from './feed.js'
import { RecordLog } from './log.js'

const hex = (k) => (typeof k === 'string' ? k : b4a.toString(k, 'hex'))
const enc = (v) => b4a.from(JSON.stringify(v), 'utf8')
const dec = (b) => (b == null ? null : JSON.parse(b4a.toString(b, 'utf8')))

// The { get, set } surface applyOp expects: reads from the bee, writes into one batch.
//
// A WriteBatch has tryPut/tryDelete/flush and NO get — measured, not assumed. So reads go to the
// bee, and a pending overlay carries writes made EARLIER IN THE SAME BATCH: `applyAddDevice` writes
// the roster entry and a later op in the same batch checks it via `isAdmitted`, which would
// otherwise miss. The overlay also lets the whole apply call flush ONCE rather than once per op.
function batchView (bee, batch) {
  const pending = new Map()
  return {
    pending,
    async get (key) {
      if (pending.has(key)) return pending.get(key)
      const node = await bee.get(b4a.from(key, 'utf8'))
      return node ? dec(node.value) : null
    },
    async set (key, value) {
      pending.set(key, value)
      batch.tryPut(b4a.from(key, 'utf8'), enc(value))
    }
  }
}

async function apply (nodes, view, host) {
  const batch = view.write()
  const v = batchView(view, batch)

  for (const node of nodes) {
    let op
    try { op = dec(node.value) } catch { continue } // a malformed block is a no-op, deterministically
    // ⛔ node.key, not node.from.key.
    const device = node.key ? hex(node.key) : null

    if (op && op.t === 'add-device' && typeof op.device === 'string') {
      const before = await v.get(ROSTER + '/' + op.device)
      await applyOp(v, op, { device })
      const after = await v.get(ROSTER + '/' + op.device)
      if (!before && after) {
        // ⛔ Ack ONLY here — after applyOp accepted the proof. `add-device` arrives optimistically
        // because an unadmitted device is not yet a writer and has nowhere else to put a block, and
        // an optimistic block that is acked STICKS IN THE LOG. So the ack has to be downstream of
        // the authorisation, not beside it: acking first would let anyone with a socket append.
        try { host.ackWriter(node.key) } catch {}
        // Writer admission is a HOST call and must happen with the roster entry, or an admitted
        // device would have no core to write into. Only apply may do either.
        try { host.addWriter(b4a.from(op.device, 'hex'), { isIndexer: true }) } catch {}
      }
      continue
    }

    // ⛔ Optimistic blocks must self-verify before being acked, or "any writer could append blocks
    // even when not added to the system" (autobee README). The verification here is deliberately
    // narrow: a KNOCK is acked, and nothing else ever is. An unadmitted device's put or add-device
    // is already refused by applyOp, but refusing it is not enough — acking it would let the block
    // itself stick in the log, which is the exploit the README warns about.
    if (op && op.t === 'knock') {
      try { host.ackWriter(node.key) } catch {}
    }

    await applyOp(v, op, { device })
  }

  await batch.flush()
}

export class Feed {
  constructor (store, key, opts = {}) {
    this.corestore = store
    // fast-forward: OFF unless a caller hands us a boot head — a configuration choice, not a
    // workaround for a defect.
    //
    // Fast-forward is on by default and PROBES for candidate oplog heads. Our joins are degenerate
    // for that: one serving peer, no stamped heads anywhere, so there are zero candidates and each
    // wakeup batch pays DEFAULT_OP_TIMEOUT (5s, lib/constants.js:5) looking for something that does
    // not exist. Measured at N=25 writers: 25s vs 0.2s. Turning it off is not fixing autobee, it is
    // declining to search when we know there is nothing to find.
    //
    // With a boot head it works and is fast — 201ms, and it does fast-forward.
    //
    // ⚠ A boot head is an OPLOG head — `{key, length}` of a writer's core — and a joiner cannot
    // derive one from a feed key. It has to be CONVEYED or REMEMBERED: from a registry listing, a
    // follow record, a knock, or our own record of a previous session. The seam is here for when we
    // carry one.
    //
    // ★ If we ever want DISCOVERY-based fast-forward instead, the handler that matters is
    // `mostRecentTrusted`, and it belongs on the peer that SERVES, not the one joining:
    // `lib/writers.js:112` — flushLocal passes its result into `w.flush(views, trusted)`, stamping
    // the vouched head into that peer's own oplog for joiners to read. Note also `trusted.js:19`:
    // supplying `mostRecentTrusted` flips the `isTrusted` default from permissive to deny, because
    // opting into the trust system means opting out of trusting everything. Both are coherent; they
    // are just not obvious from the outside.
    const bootHead = opts.bootHead || null
    this.base = new Autobee(store, key || null, {
      apply,
      optimistic: !!opts.optimistic, // the stranger doorbell, off by default
      fastForward: bootHead ? { boot: { head: bootHead } } : false,
      ...(opts.encryptionKey ? { encrypted: true, encryptionKey: opts.encryptionKey } : {})
    })
    this.store = opts.store || null // the RecordStore holding the bytes
    this._log = null
  }

  async ready () {
    await this.base.ready()
    // RecordLog over a READ-ONLY adapter: this log resolves and never writes, because a write here
    // has to be an append. Its three-state resolve() is the whole reason to reuse it.
    this._log = new RecordLog(this._readKv(), this.store)
    return this
  }

  get key () { return this.base.key }
  get writable () { return this.base.writable }
  get deviceKey () { return hex(this.base.local.key) }

  _readKv () {
    const bee = () => this.base.view
    return {
      set: async () => { throw new Error('Feed: the view is read-only — writes are appends') },
      del: async () => { throw new Error('Feed: the view is read-only — writes are appends') },
      get: async (key) => {
        const node = await bee().get(b4a.from(key, 'utf8'))
        return node ? dec(node.value) : null
      },
      list: async (prefix = '') => {
        const out = {}
        const range = prefix
          ? { gte: b4a.from(prefix, 'utf8'), lt: b4a.from(prefix.slice(0, -1) + String.fromCharCode(prefix.charCodeAt(prefix.length - 1) + 1), 'utf8') }
          : {}
        for await (const node of bee().createReadStream(range)) {
          out[b4a.toString(node.key, 'utf8')] = dec(node.value)
        }
        return out
      }
    }
  }

  // ── write side: appends. None of these are visible until update() ──────────────────────────────

  // Admit a device. The founding call is a device admitting ITSELF, which only takes effect while
  // the view has no founder — see feed.js. Everything after needs an already-admitted device.
  //
  // ⚠ autobee also exposes `host.genesis` for exactly this, which would let feed.js drop its FOUNDER
  // marker. Deliberately NOT taken here: this port must not change behaviour, and FOUNDER is what
  // the substrate-agnostic tests prove. One change at a time.
  /**
   * Admit this device by proving it belongs to the feed's owner.
   *
   * The proof must attest THIS feed's writer key — `deviceKey`, i.e. `base.local.key`.
   *
   * ⛔ That key is not something a caller chooses. Passing `keyPair` to the log sets its BOOT
   * MANIFEST, not the local writer core — measured: with a keyPair supplied, `base.local.key` was
   * still a corestore-derived key and did not match. So the credential cannot be minted ahead of
   * time; the feed is opened FIRST, its writer key read, and the proof minted for that key. Which
   * is also the honest claim to be making: "this writer core belongs to me".
   *
   * Self-admission: no other device has to be online or to vouch. Genesis is the same call — the
   * first one binds the proof's identity root into the feed permanently.
   *
   * ⛔ Appended OPTIMISTICALLY. A device not yet on the roster is not yet a writer and has nowhere
   * else to put a block; apply acks it only once the proof verifies.
   */
  async addDevice (proof) {
    if (!proof) {
      throw new Error('Feed.addDevice: a chain proof for ' + this.deviceKey + ' is required — ' +
        'admission is gated by attestation, and there is no unattested feed')
    }
    await this.base.append(enc({ t: 'add-device', device: this.deviceKey, proof }),
      { optimistic: true })
  }

  /**
   * Revoke a device. A tombstone, and it reaches everything that device attested.
   *
   * ⚠ You may revoke yourself, including the last device, which freezes the feed. Recoverable: a
   * device minted fresh from the vault descends from nothing revoked and admits itself normally.
   */
  async removeDevice (device) {
    await this.base.append(enc({ t: 'remove-device', device: hex(device) }))
  }

  // Bytes to the store, pointer to the log. If the append never lands the bytes are merely
  // unannounced; reversed, the pointer would name bytes nobody has.
  async put (collection, name, record) {
    const { oid, size } = await this.store.put(collection, name, record)
    await this.base.append(enc({ t: 'put', collection, name, $oid: oid, size }))
    return { oid, size }
  }

  // Ring someone's doorbell: append to THEIR feed, optimistically, naming MY feed. It grants
  // nothing and asks for nothing except to be seen.
  //
  // ⚠ Requires holding their base, which requires a transport. Untested end to end for that reason —
  // the authorisation rules it relies on are proven in knock.test.js, the wire is not.
  async knock (myFeedKey) {
    await this.base.append(enc({ t: 'knock', feed: hex(myFeedKey) }), { optimistic: true })
  }

  // Honour a knock by listing that feed. Bytes first, then the op — same order as `put`, and for the
  // same reason: a pointer naming bytes nobody has is worse than bytes nobody has announced.
  //
  // ⚠ apply refuses this unless an active knock exists for the feed, so listing something that never
  // asked is not merely discouraged, it does not land. Order matters: list, THEN dismiss.
  async listFeed (feedKey, record) {
    const feed = hex(feedKey)
    const { oid, size } = await this.store.put(LISTING, feed, record)
    await this.base.append(enc({ t: 'list-feed', feed, $oid: oid, size }))
    return { oid, size }
  }

  async delist (feedKey) {
    await this.base.append(enc({ t: 'delist', feed: hex(feedKey) }))
    try { await this.store.del(LISTING, hex(feedKey)) } catch {}
  }

  async listings () {
    return (await this.list(LISTING)).map((r) => ({ feed: r.name, record: r.record }))
  }

  async dismissKnock (feedKey) {
    await this.base.append(enc({ t: 'dismiss-knock', feed: hex(feedKey) }))
  }

  async knocks () {
    const map = await this._readKv().list(KNOCKS + '/')
    return Object.entries(map)
      .filter(([, v]) => v && v.$deleted !== true)
      .map(([k, v]) => ({ feed: k.slice(KNOCKS.length + 1), device: v.device }))
      .sort((a, b) => (a.feed < b.feed ? -1 : a.feed > b.feed ? 1 : 0))
  }

  async del (collection, name) {
    await this.base.append(enc({ t: 'del', collection, name }))
    try { await this.store.del(collection, name) } catch {}
  }

  // ── read side: the linearized view ────────────────────────────────────────────────────────────

  async update () { await this.base.update() }
  async resolve (collection, name) { return this._log.resolve(collection, name) }
  async get (collection, name) { return this._log.get(collection, name) }
  async list (collection = null) { return this._log.list(collection) }
  // Live pointers, WITHOUT fetching any bytes — see RecordLog.pointers. This is what lets the
  // mirror decide which records to go and get instead of getting all of them at once.
  async pointers (collection = null) { return this._log.pointers(collection) }
  async unresolved (collection = null) { return this._log.unresolved(collection) }

  // Admitted WRITER cores, excluding any whose device has been revoked.
  async devices () {
    return (await this.roster()).map((r) => r.writer).sort()
  }

  /**
   * The roster as { writer, device } pairs, revoked devices excluded.
   *
   * ⛔ Both keys, because they are different things and only one of them can be revoked. A listing
   * that showed only writer cores would name something `unlink` cannot take — you would be able to
   * see a device and not retire it.
   */
  async roster () {
    const rows = await this._readKv().list(ROSTER + '/')
    const dead = await this._readKv().list(REVOKED + '/')
    const out = []
    for (const [k, v] of Object.entries(rows)) {
      if (!v || !v.device) continue
      if (dead[REVOKED + '/' + v.device] != null) continue
      out.push({ writer: k.slice(ROSTER.length + 1), device: v.device })
    }
    return out.sort((a, b) => (a.writer < b.writer ? -1 : a.writer > b.writer ? 1 : 0))
  }

  /** Revoked device keys — named, because a retired device is a fact, not an absence. */
  async revokedDevices () {
    const dead = await this._readKv().list(REVOKED + '/')
    return Object.keys(dead).map((k) => k.slice(REVOKED.length + 1)).sort()
  }

  async founder () { return this._readKv().get(FOUNDER) }

  // ── replication ───────────────────────────────────────────────────────────────────────────────
  //
  // Replicate through the BASE. autobee owns which cores a peer needs — the writer set changes as
  // devices are admitted, so the base is the only thing that knows the full set at any moment.
  // `isInitiatorOrStream` is a boolean (make a stream) or an existing duplex, so a transport can
  // hand us a socket without this file knowing what a socket is.
  replicate (isInitiatorOrStream) { return this.base.replicate(isInitiatorOrStream) }

  get discoveryKey () { return this.base.discoveryKey }

  async close () { await this.base.close() }
}
// The feed's apply — the deterministic core of one identity's log.
//
// A feed is ONE identity. Devices are plural; the member is singular and is the feed itself, so the
// authorisation rule fits in a sentence: an op counts if the device that wrote it is in this feed's
// device roster. Nothing else grows the writer set. That is not a detail — it is the property that
// stops a popular feed from turning every person who interacts with it into a hypercore, and it is
// why interactions are written into the interactor's OWN feed instead of the author's.
//
// ── The two rules apply must obey ──────────────────────────────────────────────────────────────
//
// DETERMINISTIC. This runs inside autobase and is replayed on every boot and every reorg. No clock,
// no randomness, no external lookup, no I/O. Every peer must reach the same view from the same ops.
//
// TOTAL. It must never throw. A throw mid-replay wedges the base, and the op that caused it is
// already durable — so there is no version of "fix it and retry". A rejected op is a NO-OP, applied
// identically by everyone, and a malformed one is rejected the same way as an unauthorised one.
//
// Note what follows from ordering alone: a write from a device that has not been admitted YET is
// dropped, and admitting it later does not retroactively count that write. The linearization is the
// whole authority; there is no second pass.

import { readProof } from './attestation.js'

const SEP = '/'

// The replicated device roster. Reserved namespace: it lives in the same view as the records, so it
// converges with them, but no `put` can reach it — otherwise a rostered device could forge an
// admission by writing a record, which is admission without the admission op.
export const ROSTER = '__devices__'

// The doorbell. A stranger holds nothing of mine and I hold nothing of theirs, so neither
// replication nor a follow can carry a first contact; a knock is how one arrives.
//
// ⛔ A knock GRANTS NOTHING. It records that a feed key would like to be seen, and that is all. It
// is the one op accepted from an unadmitted device, it can only write its own entry here, and
// accepting it means *I* follow *them* — never that they gain write access to my feed. Write access
// to a feed belongs to its owner's devices and to nobody else; a doorbell is exactly where that
// would be easiest to lose.
//
// Reserved, so it converges with the records but can never be materialised as one.
export const KNOCKS = '__knocks__'

// A registry is a feed whose records are LISTINGS of other feeds. No new primitive — this is the
// collection those records live in, named so the rule below has something to attach to.
export const LISTING = 'listing'

// Genesis marker. Set once, by the founding admission, and never read for anything else.
//
// ⛔ autobee's `host.genesis` looks like it should replace this, and its README says to use it to
// bootstrap the first writer. It cannot be used as a per-node admission gate: `isGenesis()` reads
// the system bee's HEAD, which does not move until the batch flushes, so genesis stays true for
// every node of the batch that admitted the first writer. With `optimistic: true` two strangers'
// self-admissions landing in one batch would BOTH be admitted. Proven in test/genesis.test.js,
// which is kept as a regression test. This marker is visible to the very next op in the same batch
// because it goes through the apply-time pending overlay, which is exactly the difference.
//
// It replaces asking "is the roster empty?", which meant enumerating the view. That was fine
// against a Map and wrong against the real thing: the view is a Hyperbee, the scan is O(n) on every
// admission, and it is async — so the question could not be answered inside apply without either
// lying or scanning. A single key answers it in one get, and makes genesis explicit in the view
// rather than inferred from the absence of something.
export const FOUNDER = '__founder__'

// Revoked DEVICE keys.
//
// ⛔ Revocation targets the device, not the writer core. A chain is made of device keys — the only
// keys that can sign — so ancestry is only expressible over them, and a tombstone on a writer key
// could never reach what that device attested. One device may also hold several writer cores (one
// per feed it participates in); revoking the device should retire all of them at once.
//
// Reserved, so it converges with the records and can never be materialised as one.
export const REVOKED = '__revoked__'

export const isRosterKey = (key) => typeof key === 'string' && key.startsWith(ROSTER + SEP)

// A path segment: non-empty, no separator, not a traversal.
const isSeg = (s) => typeof s === 'string' && s.length > 0 && !s.includes(SEP) && s !== '.' && s !== '..'
// Reserved collections are control data and are never writable by an op.
const isReserved = (collection) => typeof collection === 'string' && collection.startsWith('__')
// A pointer is exactly { $oid, size } — see RecordLog. An op carries the pointer's fields inline,
// so this checks the fields rather than the object shape.
const isPointerOp = (op) => '$oid' in op && typeof op.size === 'number'

const rosterKey = (writer) => ROSTER + SEP + writer
const revokedKey = (device) => REVOKED + SEP + device

// ⛔ A revoked device is NOT admitted. Absence and revocation are different facts — absence may
// simply mean "not replicated to me yet" — so revocation is a tombstone and every admission check
// has to read through it rather than around it.
// A writer counts if it is on the roster AND the device it was admitted under is not revoked.
// Both halves matter: the roster says "this core belongs here", the revocation list says "that
// device no longer does", and a writer whose device was retired must stop counting immediately
// without anyone having to walk the roster to tombstone it.
const isAdmitted = async (view, writer) => {
  if (!isSeg(writer)) return false
  const rec = await view.get(rosterKey(writer))
  if (rec == null) return false
  return !(await isRevokedDevice(view, rec.device))
}

const isRevokedDevice = async (view, device) => {
  if (!isSeg(device)) return false
  return (await view.get(revokedKey(device))) != null
}

/**
 * applyOp(view, op, { device })
 *
 * `view` is the KV being built — the linearized state. `device` is the writer key autobase
 * attributes the op to; this function trusts that attribution and nothing else about the op.
 *
 * Returns nothing. A rejected op leaves the view untouched.
 *
 * Async because the real view is a Hyperbee. Every read is awaited; nothing else about the contract
 * changes, and determinism is unaffected — the awaits are I/O against state this same replay built.
 */
export async function applyOp (view, op, ctx) {
  if (!op || typeof op !== 'object') return
  const device = ctx && ctx.device
  if (!isSeg(device)) return

  switch (op.t) {
    case 'add-device': return applyAddDevice(view, op, device)
    case 'remove-device': return applyRemoveDevice(view, op, device)
    case 'put': return applyPut(view, op, device)
    case 'del': return applyDel(view, op, device)
    case 'knock': return applyKnock(view, op, device)
    case 'dismiss-knock': return applyDismissKnock(view, op, device)
    case 'list-feed': return applyListFeed(view, op, device)
    case 'delist': return applyDelist(view, op, device)
    default: // an unknown op is retained by the log and ignored by this version. A future op type
      // must be additive for exactly this reason: old peers consume and discard it, and upgrading
      // does not re-run history.
  }
}

// Admission — a device proves it belongs to this feed's owner, and admits ITSELF.
//
// ── The rule ──────────────────────────────────────────────────────────────────────────────────
//
// Genesis binds an identity ROOT into `__founder__`. After that, the only way onto the roster is a
// chain proof resolving to that root. There is no vouching, so no admitted device need be online,
// and the "two devices each believe they are first" ambiguity is gone: two devices under one root
// are both legitimately admitted, in whatever order linearizes.
//
// ⛔ There is no unattested feed. Genesis without a proof is refused, so there is no second
// authorisation rule to remember and no mode anyone is in by accident.
//
// ── Why this is allowed inside apply ──────────────────────────────────────────────────────────
//
// `readProof` is a pure function of the proof's bytes — no clock, no I/O, no lookup — and it never
// throws (`IdentityKey.verify` does; the wrapper is why). Determinism and totality both hold.
//
// ── The order of the checks is load-bearing ───────────────────────────────────────────────────
//
// Revocation is tested BEFORE the proof, because a removed device still holds a perfectly valid
// proof. If the proof were enough, self-service admission would make revocation decorative.
async function applyAddDevice (view, op, device) {
  const writer = op.device
  if (!isSeg(writer)) return

  // ⛔ The proof binds THIS writer key as attested data, so a proof is never a bearer token for
  // some other core. Verified in the same call that walks the chain.
  const proof = readProof(op.proof, writer)
  if (!proof) return

  // ⛔ Revocation is checked AFTER the proof only because the proof is what tells us WHICH device
  // is being claimed — but before any admission is written. A retired device still holds a
  // perfectly valid proof, so without this it would simply re-admit itself and revocation would be
  // decorative.
  if (await isRevokedDevice(view, proof.device)) return

  // ⛔ Transitive. A linked device can attest the next one from its own keypair — no mnemonic —
  // which is how a second machine is paired. So a compromised device has descendants, and every
  // one of them carries a chain that names it.
  for (const ancestor of proof.chain) {
    if (await isRevokedDevice(view, ancestor)) return
  }

  const founder = await view.get(FOUNDER)

  if (!founder) {
    // Founding is self-admission: the core writing the op is the core being admitted. The root its
    // device chains to becomes the feed's owner, once, permanently.
    if (writer !== device) return
    await view.set(FOUNDER, { device: writer, identityRoot: proof.root })
    await view.set(rosterKey(writer), { device: proof.device, admittedBy: device })
    return
  }

  // Someone else's identity, however valid their proof.
  if (proof.root !== founder.identityRoot) return

  await view.set(rosterKey(writer), { device: proof.device, admittedBy: device })
}

// Revocation. The owner's act, so it needs an admitted device — and it is a TOMBSTONE, because
// absence is ambiguous in a replicated log and "removed" has to be positive evidence.
//
// ⚠ A device may revoke itself, and the last device may revoke itself, freezing the feed. That is
// deliberate and it is recoverable: a device minted fresh from the vault descends from nothing
// revoked, so it admits itself normally. This is exactly why the vault is needed for RECOVERY and
// never at boot.
async function applyRemoveDevice (view, op, device) {
  if (!(await isAdmitted(view, device))) return
  if (!isSeg(op.device)) return
  await view.set(revokedKey(op.device), { by: device })
}

async function applyPut (view, op, device) {
  if (!(await isAdmitted(view, device))) return
  if (!isSeg(op.collection) || !isSeg(op.name)) return
  if (isReserved(op.collection)) return
  if (!isPointerOp(op)) return
  await view.set(op.collection + SEP + op.name, { $oid: op.$oid, size: op.size })
}

// A delete is a TOMBSTONE. Absence is ambiguous in a replicated log — "deleted" and "not replicated
// to me yet" look identical — so deletion has to be positive evidence.
async function applyDel (view, op, device) {
  if (!(await isAdmitted(view, device))) return
  if (!isSeg(op.collection) || !isSeg(op.name)) return
  if (isReserved(op.collection)) return
  await view.set(op.collection + SEP + op.name, { $deleted: true })
}

// A knock: record that `op.feed` wants to be seen, and who rang.
//
// Keyed by the FEED, not the device, so a flood from one device is one row rather than many — the
// bound is structural instead of a rate limit, which would have to live inside apply and would make
// the view non-deterministic.
//
// No admission check, deliberately: a stranger is the entire point. What bounds it is that this is
// the ONLY thing a stranger can do, and it writes one entry in a namespace that can never become a
// record.

async function applyKnock (view, op, device) {
  if (!isSeg(op.feed)) return
  await view.set(KNOCKS + SEP + op.feed, { device })
}

// Clearing a knock is the owner's act, so unlike the knock itself it requires admission. Answered
// or ignored, the entry goes; the knocker learns nothing either way, which is the point — a doorbell
// that reported "dismissed" back would be a channel a stranger could probe.
async function applyDismissKnock (view, op, device) {
  if (!(await isAdmitted(view, device))) return
  if (!isSeg(op.feed)) return
  await view.set(KNOCKS + SEP + op.feed, { $deleted: true })
}

// ★ Listing a feed requires TWO acts by TWO parties: an active knock from the feed being listed,
// and an admitted device of this registry choosing to honour it.
//
// That makes consent structural. A registry cannot list a feed that never asked, because the knock
// is written by the knocker's own device and apply can see whether one is present. Without this
// rule, "opt-in discoverability" would be a promise the registry operator makes rather than a
// property of the log — and hyper-social #61 §8 requires the two acts, subscribing and being
// listed, to stay separate.
//
// ⚠ What it does NOT give: a delisted feed is not retracted (the listing already replicated), and
// an unlisted feed is not private (a feed announces on a topic derived from its key). A registry is
// a phone book, not a gate.
async function applyListFeed (view, op, device) {
  if (!(await isAdmitted(view, device))) return
  if (!isSeg(op.feed)) return
  if (!isPointerOp(op)) return
  const knocked = await view.get(KNOCKS + SEP + op.feed)
  // A dismissed knock is a tombstone, not an absence — consent withdrawn before use is consent
  // refused, and it must not read as consent given.
  if (!knocked || knocked.$deleted === true) return
  await view.set(LISTING + SEP + op.feed, { $oid: op.$oid, size: op.size })
}

// Delisting is the registry's act, so it needs admission. A tombstone, like every other deletion
// here: absence is ambiguous in a replicated log and "delisted" has to be positive evidence.
async function applyDelist (view, op, device) {
  if (!(await isAdmitted(view, device))) return
  if (!isSeg(op.feed)) return
  await view.set(LISTING + SEP + op.feed, { $deleted: true })
}

export default { applyOp, isRosterKey, ROSTER, REVOKED, FOUNDER, KNOCKS, LISTING }
// Files — bytes in the overlay, an oid in the record.
//
// ⛔ There is NO blob store, and nothing new is being invented here. hyper-overlay already holds
// pointers to files on disk and replicates them by content hash; `avatars.js` has done exactly this
// since profiles shipped. This is that same three-step generalised past images:
//
//     hashFile(tmp) -> oid   ->   rename to <oid>   ->   registerFile('/blobs/' + oid, path)
//
// A record then names the oid — `![a cat](favela://blob/<oid>)` — and a peer that does not hold the
// bytes calls `fetchFile(oid)`. All of that is P2P; nothing about a file travels over HTTP.
//
// ⛔ No levels, no ladder, no manifest. SOCIAL-MODEL §6.6 specifies a mip-map pyramid, but that
// exists so a BRIDGE can pick a size that fits another network's ceiling. Nothing here bridges, so
// a pyramid would be complexity bought for a problem this stack does not have. If favela ever
// bridges media, §6.6 is waiting and this pointer-free design does not block it.
//
// ── Typing: what is a FACT and what is a GUESS ────────────────────────────────────────────────
//
// A record names an oid. There is no filename anywhere, so pgv3's `mimeFor(filename)` cannot
// transfer — extensions do not exist here. Types come from the bytes:
//
//   binary   a magic-byte match is a FACT. png, jpeg, gif, webp, mp4, webm, pdf.
//   text     "is this valid UTF-8 with no control bytes" is a FACT; "is this CSV" is a GUESS.
//
// ⚠ So text is accepted as text/plain on the fact, and upgraded to text/csv on the guess. The guess
// is safe to be wrong about: both are inert, and the only thing that would make a text file
// dangerous is serving it as html, which this never does.

import fs from '#node:fs'
import { join } from '#node:path'
import b4a from 'b4a'

// ⛔ There is NO size cap, because there is nothing to cap. hyper-overlay chunks with FastCDC and
// addresses by content hash — a large file is the case it is built for. An earlier draft of this
// file capped at 8 MiB and declared video unworkable, reasoning that a file rides the RPC as one
// base64 line. That was a limit I invented by copying the avatar path and then justified: the
// daemon and its surface share a machine by construction (it is a unix socket), and `registerFile`
// takes a PATH. Bytes never need to cross the socket at all.

const ascii = (b, i, s) => b4a.toString(b.subarray(i, i + s.length)) === s

/**
 * The type of these bytes, or null to refuse them.
 *
 * ⛔ SVG is refused, deliberately and consistently with `avatars.js`. An SVG is a document that can
 * carry script and fetch remote resources; png, jpeg, gif and webp cannot express either. pgv3
 * takes the other route — it serves SVG under `Content-Security-Policy: default-src 'none'`
 * (`lib/web/router.js:1028`) — and that is a reasonable position for a code forge showing a repo's
 * own files. It is the wrong one for arbitrary bytes a stranger put in a post.
 */
export function sniff (bytes) {
  if (!bytes || bytes.length < 12) return null
  const b = bytes

  // ── facts ───────────────────────────────────────────────────────────────────────────────────
  if (b[0] === 0x89 && ascii(b, 1, 'PNG')) return 'image/png'
  if (b[0] === 0xff && b[1] === 0xd8 && b[2] === 0xff) return 'image/jpeg'
  if (ascii(b, 0, 'GIF8')) return 'image/gif'
  if (ascii(b, 0, 'RIFF') && ascii(b, 8, 'WEBP')) return 'image/webp'
  if (ascii(b, 0, '%PDF-')) return 'application/pdf'
  // EBML — webm and mkv share it; only webm is claimed, since that is what a browser plays.
  if (b[0] === 0x1a && b[1] === 0x45 && b[2] === 0xdf && b[3] === 0xa3) return 'video/webm'
  // ISO-BMFF: the box length occupies 0..3 and the type follows, so `ftyp` sits at offset 4.
  if (ascii(b, 4, 'ftyp')) return 'video/mp4'

  // ── text: a fact, then a guess ──────────────────────────────────────────────────────────────
  if (!isInertText(b)) return null
  return looksDelimited(b) ? 'text/csv' : 'text/plain'
}

/**
 * Valid UTF-8, and no control bytes that would mean this is really binary.
 *
 * ⛔ Also refuses anything opening as markup. Serving it as text/plain is already inert and the
 * surface sets `nosniff`, so this is a third line rather than the only one — but an SVG is text,
 * and "we refuse SVG" should not depend on one header being remembered.
 */
export function isInertText (b) {
  if (!b || !b.length) return false
  let s
  try { s = b4a.toString(b, 'utf8') } catch { return false }
  // A lone replacement char means the decode failed on bytes that were not text.
  if (s.includes('�')) return false
  // Tab, newline and carriage return are the only control characters text needs.
  for (let i = 0; i < s.length; i++) {
    const c = s.charCodeAt(i)
    if (c < 0x20 && c !== 0x09 && c !== 0x0a && c !== 0x0d) return false
  }
  if (/^\s*(<\?xml|<svg|<!doctype|<html)/i.test(s.slice(0, 200))) return false
  return true
}

/** Does the first line look like delimited fields? A GUESS, and labelled as one. */
export function looksDelimited (b) {
  const first = b4a.toString(b.subarray(0, 4096), 'utf8').split('\n')[0]
  if (!first) return false
  const commas = (first.match(/,/g) || []).length
  const tabs = (first.match(/\t/g) || []).length
  return commas >= 1 || tabs >= 1
}

export class Files {
  constructor (storage, overlay) {
    this.dir = join(storage, 'blobs')
    this.overlay = overlay
  }

  _path (oid) { return join(this.dir, oid) }

  /**
   * Take a file that already exists on disk and put it in the overlay: `{ oid, mimeType, size }`.
   *
   * ⛔ A PATH, not bytes. The caller is on this machine — the control socket is a unix socket — and
   * `hashFile` wants a path anyway, so handing over bytes would mean encoding a file to cross a
   * socket to a process that then writes it straight back to disk.
   *
   * The source is COPIED, not moved: it belongs to the caller and may be a file they still need.
   * ⚠ It is read to sniff the type, which for a large file means reading it twice — once here and
   * once by `hashFile`. Fixing that means sniffing from a partial read; not done, and not pretended.
   */
  async put (srcPath) {
    if (typeof srcPath !== 'string' || !srcPath) throw new Error('a file needs a path')
    let head
    try { head = fs.readFileSync(srcPath) } catch { throw new Error('no file at ' + srcPath) }
    if (!head.length) throw new Error('that file is empty')

    const mimeType = sniff(head)
    if (!mimeType) {
      throw new Error('favela does not recognise those bytes as an image, a video, a PDF or text — ' +
        'the check is on the bytes rather than the file name, and SVG is refused on purpose')
    }

    fs.mkdirSync(this.dir, { recursive: true })
    const h = await this.overlay.hashFile(srcPath)
    const oid = h && h.contentHash
    if (!oid) throw new Error('the overlay could not hash the file')

    // Named by its hash. Identical files collapse to one across every record and every author this
    // node holds — the dedup the overlay gives us for free.
    const dest = this._path(oid)
    try {
      fs.copyFileSync(srcPath, dest)
    } catch (err) {
      throw new Error('could not store the file: ' + err.message)
    }
    await this.overlay.registerFile('/blobs/' + oid, dest, { size: head.length })

    return { oid, mimeType, size: head.length }
  }

  /**
   * Make sure the file is on local disk and say where it is: `{ path, mimeType, size }`, or null.
   *
   * ⛔ A PATH, not bytes — for the same reason `put` takes one. A surface serving this to a browser
   * streams the file; it does not receive a copy of it through a socket.
   *
   * ⛔ Returns null rather than throwing. A file that has not replicated yet is a missing picture,
   * not a broken page — the same distinction the record log makes between absent and unresolved.
   *
   * ⛔ The type is re-sniffed from the bytes every time, never remembered. These bytes may have
   * arrived from another peer, and what a stranger's record claims about them is not evidence.
   */
  async get (oid) {
    if (typeof oid !== 'string' || !/^[0-9a-f]{8,128}$/i.test(oid)) return null
    const p = this._path(oid)

    const describe = (at = p) => {
      try {
        const fd = fs.openSync(at, 'r')
        const head = b4a.alloc(4096)
        let n = 0
        try { n = fs.readSync(fd, head, 0, head.length, 0) } finally { fs.closeSync(fd) }
        const mimeType = sniff(head.subarray(0, n))
        if (!mimeType) return null
        return { path: at, mimeType, size: fs.statSync(at).size }
      } catch {
        return null
      }
    }

    const local = describe()
    if (local) return local

    try {
      fs.mkdirSync(this.dir, { recursive: true })
      const r = await this.overlay.fetchFile(oid, { destPath: p })
      if (!r) return null
      // ⛔ DESCRIBE WHERE IT ACTUALLY IS. On a LOCAL hit the overlay returns the path it already
      // holds the bytes at — which may be the OTHER store's directory, since Files and Avatars
      // fetch the same content hash from the same overlay and differ only in where they cache.
      // Re-reading our own expected path meant a file the overlay had was reported missing: on
      // ashatron, `file-get` for its own avatar answered {found:false} while the bytes sat in
      // avatars/<oid>, and every consumer downstream faithfully rendered no picture.
      return describe(r.destPath || p)
    } catch {
      return null
    }
  }
}

export default Files
// The graph — a PROJECTION that renders the substrate rather than the content.
//
// ⛔ Diagnostic, not decorative. EDGE-GRAPH.md §4: an edge passes four independent steps, each of
// which fails differently and all of which currently look identical from outside:
//
//   ① WRITTEN     the pointer is in the author's writer core
//   ② CONVERGED   it linearized into the view
//   ③ RESOLVED    the bytes are locally available
//   ④ PROJECTED   a bridge emitted it
//
// "A dashed edge at ② is a quorum problem; at ③ a byte-availability problem; at ④ a bridge problem.
// Today all three are 'the like didn't show up.'" Every one of those was a real hour of this
// project's life.
//
// ⛔ No storage, no replication, no new state — the same class of thing as a timeline. It is a pure
// function of what this node already holds.

import { Peer, POST, REPLY, REACTION, REPOST, FOLLOW } from './peer.js'

const KINDS = [[REACTION, 'reaction'], [REPOST, 'repost'], [REPLY, 'reply']]

/**
 * Nodes and edges for everything this peer can see.
 *
 * ⚠ Only what it HOLDS. A graph of the network would be a different and impossible thing; this is a
 * graph of one node's view, which is the only honest one and the only useful one for diagnosis.
 */
export async function graphOf (peer) {
  const nodes = new Map()
  const edges = []
  const mine = peer.feed.key.toString('hex')

  const actor = (id) => {
    if (!nodes.has(id)) nodes.set(id, { id, type: 'actor', state: 'resolved' })
  }
  // ⚠ `at` travels because the discovery view spends a spatial axis on TIME — a cluster forming
  // reads as a widening cone, which is not expressible in 2D without animation. It is the author's
  // claim, not a fact the log agrees on, exactly as `timeline()` documents.
  const record = (id, state, at) => {
    const prev = nodes.get(id)
    if (!prev) { nodes.set(id, { id, type: 'record', state, at: at || null }); return }
    // ⛔ LATER, BETTER information wins — the first sighting is not the authoritative one. A record
    // named by an edge is known only by id: no time, no state. The feed that actually HOLDS it is
    // walked afterwards whenever it sorts second, which for anything this node reacted to is
    // always, since its own feed is walked first. Skipping that second sighting left the post
    // undated at z=0 and marked 'unknown' — not a gap in the picture but a wrong position in it.
    if (prev.at == null && at) prev.at = at
    if (prev.state === 'unknown' && state !== 'unknown') prev.state = state
  }
  const rid = (t) => t.feed + '/' + t.collection + '/' + t.name

  /**
   * ⛔ AUTHORSHIP IS AN EDGE, not just a prefix on an id.
   *
   * It used to live only inside `<feed>/post/<name>`, which a force layout cannot see. So a post sat
   * wherever its REACTIONS dragged it — beside whoever liked it rather than whoever wrote it — and a
   * post nobody had reacted to sat nowhere: measured on tabertron 2026-09-07, 14 posts and 11 with
   * no edge of any kind. The picture said those posts had no author, which is the one thing about a
   * post that is never in doubt.
   *
   * ⚠ It is also the relation that makes a cluster mean anything. A community is people and what
   * they wrote; a graph that omits "wrote" is a graph of reactions wearing a community's name.
   *
   * ⛔ Only for a feed this node HOLDS. A like on a stranger's post names a record whose id reveals
   * its author's key — and asserting the edge from that would draw a relation this node cannot see.
   * `_readable()` is the boundary, and every call here is inside that walk.
   */
  const seenAuthorship = new Set()
  const authored = (author, id, at) => {
    if (seenAuthorship.has(id)) return
    seenAuthorship.add(id)
    edges.push({ kind: 'authored', from: author, to: id, value: null, at, state: 'converged' })
  }

  for (const [author, feed] of peer._readable()) {
    actor(author)

    // ③ RESOLVED vs ② CONVERGED. `unresolved()` is exactly "we hold the pointer and cannot read the
    // bytes" — the state that used to be indistinguishable from the record not existing.
    for (const row of await feed.unresolved()) {
      record(author + '/' + row.collection + '/' + row.name, 'converged', null)
      // ⚠ The pointer names the feed that wrote it whether or not the bytes ever arrived. Dropping
      // the edge here would strand exactly the records the view exists to draw attention to.
      authored(author, author + '/' + row.collection + '/' + row.name, null)
    }
    for (const row of await feed.list(POST)) {
      const fm = (row.record && row.record.frontmatter) || {}
      record(author + '/' + POST + '/' + row.name, 'resolved', fm.createdAt || null)
      authored(author, author + '/' + POST + '/' + row.name, fm.createdAt || null)
    }

    for (const [collection, kind] of KINDS) {
      for (const row of await feed.list(collection)) {
        const fm = (row.record && row.record.frontmatter) || {}
        const t = Peer.normalizeTarget(fm.target)
        if (!t) continue
        record(rid(t), 'unknown', null)
        edges.push({
          kind,
          from: author,
          to: rid(t),
          value: fm.value || null,
          at: fm.createdAt || null,
          // ⛔ The edge's own state, which is not the same as its subject's. An edge we hold is
          // written and converged BY DEFINITION — we are reading it out of the view. What we cannot
          // see from here is whether it reached anybody else, and the view must not imply it did.
          state: 'converged'
        })
      }
    }

    // A follow names an actor, not a record. ⛔ Two follows are two edges — the common bug is one
    // direction landing and the other not, and a double-headed arrow hides exactly that.
    for (const row of await feed.list(FOLLOW)) {
      actor(row.name)
      edges.push({
        kind: 'follow',
        from: author,
        to: row.name,
        value: null,
        at: (row.record && row.record.frontmatter && row.record.frontmatter.createdAt) || null,
        state: 'converged'
      })
    }
  }

  return {
    me: mine,
    nodes: [...nodes.values()],
    edges,
    // ⚠ UNKNOWN, not "no". No bridge is wired to this daemon, and a state we cannot observe must
    // not be drawn as one we can — the same lie as rendering `unheld` as `confirmed`.
    projection: 'unknown'
  }
}

export default { graphOf }
// RecordLog — the convergent half of a feed or a document.
//
// Names and POINTERS live here; bytes live in the RecordStore. The KV is injected — an autobase's
// linearized view in production, a Map in the tests — so this layer is provable without a swarm.
// The KV surface is the minimum an autobase view presents: set / get / del / list(prefix).
//
// ── The three states, kept apart on purpose ────────────────────────────────────────────────────
//
//   ABSENT      no entry. In a replicated log this ALSO means "not replicated to me yet", so it
//               can never be read as a deletion.
//   DELETED     a tombstone. Positive evidence. Absence is ambiguous; a tombstone is not.
//   UNRESOLVED  a pointer whose bytes we cannot obtain. The record exists and we cannot read it.
//
// Collapsing UNRESOLVED into ABSENT is how a lagging node concludes "deleted" and publishes a
// delete for a record that is alive. Collapsing ABSENT into DELETED is the same bug from the other
// side. `resolve()` is the only honest read, and `get()` is the convenience that flattens all three
// to null for callers that genuinely do not care.

import b4a from 'b4a'

const SEP = '/'
const byteOrder = (a, b) => b4a.compare(b4a.from(a), b4a.from(b))

export const OK = 'ok'
export const ABSENT = 'absent'
export const DELETED = 'deleted'
export const UNRESOLVED = 'unresolved'

const isTombstone = (v) => !!(v && typeof v === 'object' && v.$deleted === true)
// ⛔ A pointer is EXACTLY {$oid, size} and nothing else. Measured: a value carrying the record
// itself has no $oid, so a peer resolving it gets null — it reads only on the node that wrote it
// ("UNFETCHABLE BY EVERY OTHER NODE… that DID cannot be co-owned at all"). That shape is refused
// here rather than left as a rule to remember, because nothing fails visibly when it is broken.
const isPointer = (v) => !!(v && typeof v === 'object' && '$oid' in v && typeof v.size === 'number' &&
  Object.keys(v).length === 2)

// Split a KV key into { collection, name } on the FIRST separator. Reserved keys are never records:
// a single-segment key (a lease, a head) and any collection beginning with '__' (control data).
function splitKey (key) {
  const i = key.indexOf(SEP)
  if (i < 0) return null
  const collection = key.slice(0, i)
  if (collection.startsWith('__')) return null
  return { collection, name: key.slice(i + 1) }
}

function recordKey (collection, name) {
  if (typeof collection !== 'string' || !collection.length || collection.includes(SEP)) {
    throw new Error('RecordLog: collection must be a non-empty string without "' + SEP + '"')
  }
  if (typeof name !== 'string' || !name.length || name.includes(SEP)) {
    throw new Error('RecordLog: name must be a non-empty string without "' + SEP + '"')
  }
  return collection + SEP + name
}

export class RecordLog {
  constructor (kv, store) {
    if (!kv || typeof kv.set !== 'function' || typeof kv.list !== 'function') {
      throw new Error('RecordLog: a KV with async set/get/del/list is required')
    }
    if (!store || typeof store.put !== 'function' || typeof store.ensureLocal !== 'function') {
      throw new Error('RecordLog: a RecordStore is required for the bytes')
    }
    this.kv = kv
    this.store = store
  }

  // Bytes first, then the pointer. The order is a safety property: a pointer naming bytes that were
  // never written is unresolvable for everyone, while bytes with no pointer yet are merely not
  // announced. Fail towards the recoverable state.
  async put (collection, name, record) {
    const key = recordKey(collection, name)
    const { oid, size } = await this.store.put(collection, name, record)
    await this.kv.set(key, { $oid: oid, size })
    return { oid, size }
  }

  // Write a raw pointer — for a caller replicating someone else's entry. Refuses anything that is
  // not exactly {$oid,size}; see isPointer.
  async setPointer (collection, name, pointer) {
    const key = recordKey(collection, name)
    if (!isPointer(pointer)) {
      throw new Error('RecordLog: a log value must be exactly { $oid, size } — an inline record ' +
        'resolves only for its author and is invisible to every peer')
    }
    await this.kv.set(key, pointer)
  }

  // The honest read. { status, record, pointer }.
  async resolve (collection, name) {
    const key = recordKey(collection, name)
    const ptr = await this.kv.get(key)
    if (ptr == null) return { status: ABSENT, record: null, pointer: null }
    if (isTombstone(ptr)) return { status: DELETED, record: null, pointer: ptr }
    const record = await this.store.ensureLocal(collection, name, ptr.$oid)
    if (record == null) return { status: UNRESOLVED, record: null, pointer: ptr }
    return { status: OK, record, pointer: ptr }
  }

  // Flattens all three not-readable states to null. Use resolve() wherever the difference matters —
  // which is anywhere a decision is made, as opposed to anywhere something is rendered.
  async get (collection, name) {
    const r = await this.resolve(collection, name)
    return r.status === OK ? r.record : null
  }

  // Deletion is a TOMBSTONE, then the bytes. A tombstone converges symmetrically like any other
  // value, so a peer can tell "deleted" from "not yet replicated to me".
  async del (collection, name) {
    const key = recordKey(collection, name)
    await this.kv.set(key, { $deleted: true })
    try { await this.store.del(collection, name) } catch {}
  }

  // Live, readable records as { key, collection, name, record }, in byte order.
  async list (collection = null) {
    const rows = await this._scan(collection)
    return rows.filter((r) => r.status === OK).map(({ key, collection, name, record }) =>
      ({ key, collection, name, record }))
  }

  /**
   * Every live pointer, WITHOUT resolving any of them.
   *
   * ⛔ The one enumeration that does not fetch. `list()` and `unresolved()` both go through
   * `_scan`, which calls `resolve()` on every record — so both of them attempt a network fetch for
   * everything missing, all at once. That is right for a reader who wants the records, and wrong
   * for anything that needs to decide WHICH bytes to go and get: you cannot bound a fetch you have
   * already made by asking the question.
   *
   * Tombstones are omitted: a deleted record has no bytes to hold, and offering it here would send
   * a mirror looking for something that is positively known not to exist.
   */
  async pointers (collection = null) {
    const prefix = collection ? collection + SEP : ''
    const map = await this.kv.list(prefix)
    const out = []
    for (const key of Object.keys(map).filter((k) => splitKey(k) !== null).sort(byteOrder)) {
      const pointer = map[key]
      if (pointer == null || isTombstone(pointer)) continue
      const { collection: c, name } = splitKey(key)
      out.push({ key, collection: c, name, pointer })
    }
    return out
  }

  // Pointers we hold whose bytes we cannot obtain. Never merge this into list() — a record that is
  // silently missing from a listing is indistinguishable from one that was never written, and that
  // is the ambiguity this whole layer exists to refuse.
  async unresolved (collection = null) {
    const rows = await this._scan(collection)
    return rows.filter((r) => r.status === UNRESOLVED).map(({ key, collection, name, pointer }) =>
      ({ key, collection, name, pointer }))
  }

  async _scan (collection) {
    const prefix = collection ? collection + SEP : ''
    const map = await this.kv.list(prefix)
    const keys = Object.keys(map).filter((k) => splitKey(k) !== null).sort(byteOrder)
    // ⛔ Concurrent, and the ORDER is preserved by the map rather than by the await. Resolving in
    // series meant a feed whose bytes are unavailable paid the fetch timeout once PER RECORD — a
    // freshly peeked feed of fifty posts would have taken fifty timeouts to list. They are
    // independent reads; nothing about one depends on another.
    //
    // ⚠ Unbounded fan-out. Records already on disk cost nothing, so the width is the number of
    // MISSING ones — which for a fresh peek is all of them. Fine at the sizes this is built for and
    // it will want a limiter before it wants anything else.
    return Promise.all(keys.map(async (key) => {
      const { collection: c, name } = splitKey(key)
      return { key, collection: c, name, ...(await this.resolve(c, name)) }
    }))
  }
}
// Turning `@alice0` into a mention.
//
// ⛔ `markdown-strip.md` §3.4: "a bare @alice with no target is plain text with no facet — never a
// guess". That rule constrains the CODEC, which has no registry and genuinely cannot know who was
// meant. It does not forbid RESOLVING one where the answer is known — the namespace says which feed
// `alice0` names, and the feed's own founder says whether it agrees. Resolution is not a guess, and
// the codec still never does it.
//
// ⛔ This lives in the daemon rather than in a surface. The CLI writes posts, a phone will write
// posts, and a feature that exists in one client is not the feature.

/**
 * ⛔ A NAME or a handle. The first version required the trailing number and so matched `@alice0`
 * and never `@alice` — and nobody types the number. It exists so a name is never scarce; a person
 * types the name. Every mention anybody actually wrote was left as plain text.
 *
 * ⚠ `(^|[^\w@/])` keeps an EMAIL from matching: `alice@alice0.example` has a word character before
 * the @, and a URL path has a slash.
 */
const AT = /(^|[^\w@/])@([a-z][a-z0-9._-]{0,31})\b/gi

/**
 * Segments of `md` that are PROSE, with code left out.
 *
 * ⛔ A regex over the whole body rewrites inside code, and a code sample is exactly where an @ is
 * most likely to appear without meaning a person. Fenced blocks and inline spans are skipped whole.
 */
function prose (md) {
  const out = []
  let i = 0
  // Fences first, then inline spans inside what is left.
  const fence = /```[\s\S]*?```|~~~[\s\S]*?~~~/g
  let m
  while ((m = fence.exec(md)) !== null) {
    if (m.index > i) out.push([i, m.index])
    i = m.index + m[0].length
  }
  if (i < md.length) out.push([i, md.length])

  const final = []
  for (const [start, end] of out) {
    const chunk = md.slice(start, end)
    let j = 0
    const span = /`[^`\n]*`/g
    let s
    while ((s = span.exec(chunk)) !== null) {
      if (s.index > j) final.push([start + j, start + s.index])
      j = s.index + s[0].length
    }
    if (j < chunk.length) final.push([start + j, end])
  }
  return final
}

// Is this @ already inside a markdown link — either its label or its target?
// ⛔ Without this, saving a post twice nests the link inside itself.
function insideLink (md, at) {
  const before = md.lastIndexOf('[', at)
  if (before !== -1) {
    const close = md.indexOf(']', before)
    if (close !== -1 && close > at && md[close + 1] === '(') return true
  }
  const paren = md.lastIndexOf('(', at)
  if (paren !== -1 && md[paren - 1] === ']') {
    const end = md.indexOf(')', paren)
    if (end !== -1 && end > at) return true
  }
  return false
}

/**
 * Rewrite bare handles into mentions.
 *
 * `resolve(token)` returns `{ root, verdict }` or null. The token is a HANDLE (`alice0`) or a bare
 * NAME (`alice`); the caller decides which, because only it holds the namespace.
 *
 * ⚠ A bare name resolves only when exactly ONE confirmed claimant holds it. NAMING.md §3.2 — an
 * unresolvable name fails visibly rather than having a choice guessed for it — and picking "the
 * first alice" would be that guess wearing a helpful face. One candidate is not a guess.
 *
 * ⛔ CONFIRMED only. A handle claimed at somebody else's feed key is the rename attack, and turning
 * one into a link here would be that attack arriving through the composer. `unheld` is left alone
 * too: we cannot check a feed we do not hold, and linking unchecked is how a check becomes
 * decorative.
 */
export async function linkMentions (body, resolve) {
  const md = String(body == null ? '' : body)
  if (!md.includes('@')) return md

  const regions = prose(md)
  const inProse = (idx) => regions.some(([s, e]) => idx >= s && idx < e)

  // ⚠ One lookup per DISTINCT handle. A post naming somebody ten times asks once.
  const wanted = new Set()
  for (const m of md.matchAll(AT)) {
    const at = m.index + m[1].length
    if (!inProse(at) || insideLink(md, at)) continue
    wanted.add(m[2].toLowerCase())
  }
  if (!wanted.size) return md

  const found = new Map()
  for (const h of wanted) {
    let r = null
    try { r = await resolve(h) } catch {}
    if (r && r.verdict === 'confirmed' && /^[0-9a-f]{64}$/i.test(r.root || '')) found.set(h, r.root)
  }
  if (!found.size) return md

  let out = ''
  let last = 0
  for (const m of md.matchAll(AT)) {
    const at = m.index + m[1].length
    const root = found.get(m[2].toLowerCase())
    if (!root || !inProse(at) || insideLink(md, at)) continue
    out += md.slice(last, at) + '[@' + m[2] + '](favela://' + root + ')'
    last = at + 1 + m[2].length
  }
  return out + md.slice(last)
}

export default { linkMentions }
// MINT — create a new favela identity on the device that will use it, or restore an existing one.
//
// The counterpart of `pair-accept`. Pairing copies an identity onto a SECOND device and needs the
// first one online; this creates the FIRST one, and needs nobody. Without it the origin of every
// favela identity sat outside the product — `hyperident` on a machine with a shell — and a handset
// has neither, which is why the phone had no way to become anyone.
//
// ⛔ ANY NODE, NOT JUST THE PHONE. favela-android's lib/mint.js records the same rule for the atproto
// stack: "a desktop favela on a fresh install has exactly the same problem", so this belongs in the
// daemon's library where every device gets it, never in one shell.
//
// ── ⛔ THE MNEMONIC IS RETURNED, NEVER KEPT ──────────────────────────────────────────────────────
// The mnemonic is the recovery root: it re-derives the identity and therefore outranks every device
// credential. Minting is the one moment it exists here. So it is RETURNED for the caller to show
// once, and what lands on disk is the VAULT — encrypted, when a passphrase is given. Nothing else
// persists it. hyper-proto-peer/lib/mint.js states the same contract and its test asserts the config
// does not contain it; test/mint.test.js asserts the same here.
//
// ── RESTORE IS THE SAME FUNCTION ─────────────────────────────────────────────────────────────────
// Passing a `mnemonic` re-creates the vault for an identity that already exists. That is the path
// that survives losing every device — pairing cannot, because it needs one of them awake. It is the
// same code because it is the same operation: a vault is a mnemonic plus a cost, and the identity
// root is a pure function of the words.
//
// ── WHAT THIS DOES NOT DO ────────────────────────────────────────────────────────────────────────
// It does not claim a handle and it does not join a namespace. Creating an identity and taking a name
// are different decisions, and requiring both at once is what hyper-proto-peer's mint records as
// having made minting undecidable. `claim-name` comes later, and can fail without costing anything.
import fs from '#node:fs'
import { dirname } from '#node:path'
import IdentityKey from 'keet-identity-key'
import { CryptoManager } from 'hyperident/lib/crypto.js'
import { mintFromVault } from './device.js'

/**
 * Create (or restore) an identity vault, and mint this device's credential from it.
 *
 * @param {object} opts
 * @param {string}  opts.vaultPath      where the vault is written.
 * @param {string}  [opts.passphrase]   encrypts the vault. Omitted, it is written in the clear.
 * @param {string}  [opts.mnemonic]     restore an EXISTING identity. Omitted, a fresh one is generated.
 * @returns {{ mnemonic, credential: { publicKey, secretKey, proof } }} the mnemonic is for showing ONCE.
 */
export async function mintIdentity ({ vaultPath, passphrase = null, mnemonic = null } = {}) {
  if (!vaultPath) throw new Error('mint: a vault path is required')

  // ⛔ REFUSE RATHER THAN OVERWRITE. A device that already has a vault is not asking for a second one
  // — it arrived here by a double-tap or a routing mistake, and the cost of being wrong is asymmetric:
  // minting again is cheap, and overwriting a vault destroys the only copy of a recovery root. The
  // same asymmetry hyper-proto-peer's mint refuses on, and #48 is what it costs when a vault is lost.
  if (fs.existsSync(vaultPath)) {
    throw new Error('mint: a vault already exists at ' + vaultPath + ' — refusing to overwrite it. ' +
      'Move it aside first if you really mean to replace this identity.')
  }

  // ⛔ keet-identity-key's generator, NOT bip39-mnemonic's — hyperident and hyper-proto-peer both mint
  // with `IdentityKey.generateMnemonic()`, so a favela identity is the same shape as every other one
  // in the fleet and `IdentityKey.from()` is guaranteed to accept it.
  //
  // ⚠ VALIDATED whether generated or supplied, and validated by the REAL CONSUMER. A typo in a
  // restored phrase would otherwise derive a DIFFERENT identity rather than failing — a working node
  // that is nobody, discovered when nothing you owned turns out to be yours.
  const words = (mnemonic ? String(mnemonic) : IdentityKey.generateMnemonic()).trim().replace(/\s+/g, ' ')
  try {
    await IdentityKey.from({ mnemonic: words })
  } catch {
    throw new Error('mint: that is not a valid recovery phrase — check the words and their order')
  }

  const vault = passphrase ? sealed(words, passphrase) : { mnemonic: words }

  fs.mkdirSync(dirname(vaultPath), { recursive: true })
  // ⚠ Written via a temp file and renamed: a vault half-written by a process that died at the wrong
  // moment is indistinguishable from a corrupt one, and it holds the only copy of a recovery root.
  const tmp = vaultPath + '.tmp'
  fs.writeFileSync(tmp, JSON.stringify(vault, null, 2))
  fs.renameSync(tmp, vaultPath)

  const cred = await mintFromVault(vaultPath, { passphrase })
  return {
    mnemonic: words,
    credential: {
      publicKey: cred.keyPair.publicKey.toString('hex'),
      secretKey: cred.keyPair.secretKey.toString('hex'),
      proof: cred.proof
    }
  }
}

// The on-disk shape `mintFromVault` reads. The cost is recorded PER VAULT so raising the default
// later cannot make an existing vault unopenable — on a phone especially, where the parameters were
// chosen (INTERACTIVE, 64 MB) precisely because a 256 MB allocation during unlock is a real risk.
function sealed (words, passphrase) {
  const salt = CryptoManager.generateSalt()
  const params = CryptoManager.kdfParams()
  const box = CryptoManager.encryptSecret(Buffer.from(words, 'utf8'), passphrase, salt, params)
  return {
    encrypted: true,
    kdf: params.kdf,
    mnemonic: box.toString('hex'),
    salt: salt.toString('hex'),
    opslimit: params.opslimit,
    memlimit: params.memlimit
  }
}

export default { mintIdentity }
// mirror.js — hold the bytes of feeds you follow, before anyone asks to read them.
//
// ⛔ THE OUTAGE, measured 2026-09-08. tabertron held five pointers from ashatron's feed and could
// read none of them. ashatron is a laptop, it went offline, and it was the only machine holding the
// bytes. Nothing was broken: a record's bytes are fetched only when a reader asks for them, nobody
// had asked, so no second copy existed. The surface reported it honestly — "the log arrived, the
// bytes did not" — and all five resolved the moment ashatron came back.
//
// ★ SO THIS IS ONLY A TRIGGER. Two things already work and are deliberately NOT rebuilt here:
//
//   1. A fetch WRITES the record into that feed's working tree, so the copy is durable.
//   2. hyper-overlay RE-SEEDS on fetch — "register the verified blob locally so this node now
//      SERVES these chunks to other peers too" — so holding implies serving, automatically.
//
// Reading a record once already makes this node a mirror of it. Nothing reads unprompted, and that
// is the entire gap. hyper-overlay's `mirrorSpool` is NOT needed for us: it exists for "a node with
// no working tree of its own", and we have one per feed.
//
// ⛔ FOLLOWED FEEDS ONLY. `peer.timeline` already excludes peeked feeds because including them
// "would make reading a stranger's page indistinguishable from following them". Mirroring a peek
// would be that same collapse with disk attached — every profile glanced at becomes a permanent
// copy. Following is the decision to keep someone's bytes; peeking is not.
//
// Design: hyper-social/docs/HOLDING-AND-TELLING.md

import { embeds } from 'favela-markdown/lib/render.js'

const DEFAULT_CONCURRENCY = 4

/**
 * @param feeds        () => [{ key, feed }] — the FOLLOWED feeds, supplied by the caller so this
 *                     module never has to know how following is decided.
 * @param concurrency  files in flight. ⚠ Bounds FILES, not chunks: the layer below already holds an
 *                     RTT-scaled inflight window per peer, and re-implementing that would be worse
 *                     than what exists. What is unbounded is the file count, and a freshly followed
 *                     feed is all of them at once.
 */
export function createMirror ({ feeds, files = null, concurrency = DEFAULT_CONCURRENCY, debounceMs = 2000, log = () => {} }) {
  if (typeof feeds !== 'function') throw new Error('createMirror: feeds() is required')

  let stopped = false
  let running = false
  let timer = null
  let lastPassAt = 0
  // ⛔ Counted per TRIGGER so a dead hook is OBSERVABLE. 0.0.40's watcher never fired and nothing
  // on a running node could say so — the only evidence was how LONG a record took to appear, which
  // is inference. These counters are what turned that into a measurement.
  const fired = { append: 0, connection: 0, boot: 0, files: 0 }
  const bases = []
  const hooked = new Map()

  // Attach an 'append' listener to every writer core we have not already hooked. A hypercore emits
  // append when blocks land, INCLUDING replicated ones — the earliest honest signal someone else
  // wrote something. Idempotent, so calling it every pass is free after the first.
  function rehook (onAppend) {
    for (const base of bases) {
      let writers
      try { writers = base.writers } catch { continue }
      if (!writers) continue
      try {
        for (const w of writers) {
          const core = w && w.core
          if (!core || typeof core.on !== 'function') continue
          // ⛔ Keyed on the CORE OBJECT, not on core.id or core.key. Keying on an identifier meant
          // a core that had not yet exposed one was silently skipped — and "silently skipped" is
          // precisely the failure this whole trigger has already had three times.
          if (hooked.has(core)) continue
          core.on('append', onAppend)
          hooked.set(core, onAppend)
        }
      } catch { /* a writer set mid-change is not a failure of the pass */ }
    }
  }

  /**
   * ⛔ LEVEL 2 — the files a record NAMES.
   *
   * A record's own bytes are one oid, carried in the log. The pictures and files inside it are more
   * oids, carried in its markdown. Mirroring only the first leaves a post you can read whose image
   * will not load — the same failure as the outage this module exists for, one level down.
   * Following means holding what somebody published, and a picture is part of what they published.
   *
   * ⚠ Both levels are the SAME store. files.js: "There is NO blob store" — a record and an image
   * are both registerFile/fetchFile by content hash in hyper-overlay. There is one store here.
   *
   * `embeds()` is favela-markdown's real parser over the markdown tree, so this sees exactly what a
   * renderer would and never a regex's idea of it. A file we cannot fetch does NOT undo the record:
   * the record is held and readable, and the picture is missing — which is the honest state one
   * level down, and the next pass retries it.
   */
  async function mirrorFiles (record) {
    if (!files || typeof files.get !== 'function') return
    const body = record && record.body
    if (!body) return
    let refs
    try { refs = embeds(body) } catch { return }
    for (const e of refs) {
      if (stopped) return
      try {
        if (await files.get(e.oid)) fired.files++
      } catch { /* missing, not fatal — the record stands */ }
    }
  }

  async function mirrorFeed (key, feed) {
    // ⛔ `pointers()` and not `list()` or `unresolved()`. Both of those RESOLVE every record, which
    // means both of them fetch — unbounded, all at once. Enumerating without fetching is what lets
    // this pass be bounded at all.
    let rows
    try {
      // ⛔ REFRESH THE VIEW FIRST. `pointers()` reads the linearised view, and the view only
      // advances when apply runs. Replicated blocks can be sitting in the writer cores with the
      // view still behind them — so a sweep that skipped this would keep reporting "nothing new"
      // while the log had in fact grown. This is what makes the pass correct no matter what
      // triggered it.
      if (typeof feed.update === 'function') await feed.update()
      rows = await feed.pointers()
    } catch (err) {
      // A feed whose view is not ready yet is not a failure of the pass. It will be there next time.
      log({ msg: 'mirror: could not list feed', feed: key, error: err.message })
      return { fetched: 0, failed: 0 }
    }

    // Ask the STORE, not the log: `get` is a local read and answers without touching the network,
    // so a record already on disk costs nothing and is skipped before any slot is taken.
    const missing = []
    for (const r of rows) {
      if (stopped) break
      try {
        if ((await feed.store.get(r.collection, r.name)) == null) missing.push(r)
      } catch { missing.push(r) }
    }

    let fetched = 0
    let failed = 0
    let next = 0

    const worker = async () => {
      while (!stopped) {
        const i = next++
        if (i >= missing.length) return
        const r = missing[i]
        try {
          // ⛔ One unfetchable record must not end the pass. A peer that is away is not a peer that
          // is gone: the record stays unresolved, the surface says so, and the next pass retries.
          const got = await feed.store.ensureLocal(r.collection, r.name, r.pointer && r.pointer.$oid)
          if (got == null) failed++
          else { fetched++; await mirrorFiles(got) }
        } catch {
          failed++
        }
      }
    }

    await Promise.all(Array.from({ length: Math.min(concurrency, missing.length) }, worker))
    if (fetched || failed) log({ msg: 'mirror', feed: key, fetched, failed, missing: missing.length })
    return { fetched, failed }
  }

  return {
    /** One sweep over every followed feed. Safe to call concurrently — a second call is a no-op. */
    async pass () {
      if (stopped || running) return { fetched: 0, failed: 0 }
      running = true
      // Hook whatever writers exist NOW. On the first pass a remote feed usually has none; by the
      // second it does, and from then on every remote append triggers a sweep of its own.
      rehook(() => this.schedule('append'))
      let fetched = 0
      let failed = 0
      try {
        for (const { key, feed } of feeds()) {
          if (stopped) break
          const r = await mirrorFeed(key, feed)
          fetched += r.fetched
          failed += r.failed
        }
      } finally {
        running = false
        lastPassAt = Date.now()
      }
      return { fetched, failed }
    },

    /**
     * Sweep again shortly, coalescing a burst into one pass.
     *
     * ⛔ THE BOOT PASS ALONE IS NOT ENOUGH, and an end-to-end test on two machines is what proved
     * it. At boot a followed feed's log has usually NOT replicated yet — the swarm connection is
     * still forming — so the sweep runs, correctly finds nothing to do, and never runs again. The
     * unit tests missed this because a fake hands back its pointers instantly; nothing simulated a
     * log that arrives thirty seconds later. Records must be fetched when the log ADVANCES, which
     * is the only moment we know there is something new to fetch.
     */
    schedule (why = 'append') {
      if (stopped || timer) return
      fired[why] = (fired[why] || 0) + 1
      timer = setTimeout(() => {
        timer = null
        this.pass().catch(() => {})
      }, debounceMs)
      if (timer && timer.unref) timer.unref()
    },

    /**
     * Sweep whenever `base` advances. Autobee emits 'update' on every view change, which is far
     * more often than the log actually gains records — hence the debounce, and hence `schedule`
     * rather than `pass` directly.
     */
    watch (base) {
      if (!base) return () => {}
      bases.push(base)
      // ⛔ REGISTERED, NOT HOOKED — hooking happens in every pass. `base.writers` is an
      // ActiveWriters whose `active` map is EMPTY when a freshly-opened remote feed returns from
      // hold(): writers are only added once the system core has replicated. Hooking once here
      // therefore attached to NOTHING, silently, and that is the third time in this feature that
      // doing a thing once at open time ran before the data it needed existed. Re-hooking per pass
      // costs a Map lookup and cannot be early.
      return () => { bases.length = 0 }
    },

    /**
     * A peer connected — retry what we could not fetch before.
     *
     * ⛔ THIS IS THE EVENT THE TIMER WAS STANDING IN FOR. A record stays unresolved because nobody
     * holding its bytes is online. Their coming back is a swarm 'connection', not an absence, and
     * re-checking it on a clock was polling for data — the one thing the rule is absolute about.
     */
    onPeer () { this.schedule('connection') },

    /**
     * Which trigger is doing the work. `hooked` is the one that matters: 0.0.42 hooked ZERO cores
     * and looked identical to a working feature from the outside.
     */
    stats () { return { ...fired, hooked: hooked.size, bases: bases.length, lastPassAt } },

    stop () {
      stopped = true
      if (timer) { clearTimeout(timer); timer = null }
      for (const [c, fn] of hooked) { try { c.off('append', fn) } catch {} }
      hooked.clear()
      bases.length = 0
    }
  }
}

export default { createMirror }
// name.js — what a record is called.
//
// ⛔ THE DEFECT. A record's name was `String(Date.now())` (rpc.js) or a clock plus four hex
// characters (room.js). hyper-social #59 §2 named both faults: a millisecond clock COLLIDES, and the
// name came in as `a.name || …` — caller-supplied — so a collision was CHOOSABLE rather than merely
// improbable. Apply wrote it with an unconditional `set`, so a collision silently replaced a record,
// and in a room it silently reattributed one to whoever collided.
//
// ⛔ AND A NAME IS NOT A POSITION. #59 §2 proposed assigning it inside apply, the way keet assigns
// `index: p.count`. #61 §2b measured keet and found the reverse: `{thread, index}` is a view
// COORDINATE, and every cross-reference — parent, replyTo, and the edit target — resolves through
// `@keet/chat-by-message-id`, keyed by `oplog` (contract/chat.js:37-43, :89-101). A position is a
// function of the linearisation; the linearisation is re-derived on every boot and every reorg. So a
// permalink built from a position can change AFTER it has been handed out and embedded in bytes that
// #8 forbids rewriting. `oplog` is collision-free by construction, known at append time and stable
// under reorg — that is the property being reproduced here, without the coordination.
//
// So a name is: minted at write time (bytes go to a path before any append lands), carrying enough
// entropy that a collision is not a practical event, ordered by time because timelines and every
// bridge's rkey depend on it, and never supplied by a caller.
//
// ⚠ WHAT THIS IS NOT. This does not make a name an identity in keet's sense — provenance still lives
// in the log, and two records with the same bytes in two rooms are still two disclosures
// (SOCIAL-MODEL §9 #5). It removes the collision and the caller's ability to choose one. The
// address question — what the authority segment of `favela://` is — is #61 §12 #1 and stays open.

import crypto from 'hypercore-crypto'

// base32hex, lowercase (RFC 4648 §7 alphabet, downcased). Chosen because ASCII order matches value
// order — '0' < '9' < 'a' < 'v' — so a plain string sort is a chronological sort. That is a property
// the timeline, the record store's byte-ordered scan, and the bsky TID projection all lean on.
const ALPHABET = '0123456789abcdefghijklmnopqrstuv'
const TIME_LEN = 10 // 32^10 ms ≈ year 38,000. Fixed width, because sorting needs it.
const RAND_LEN = 16 // 80 bits

export const NAME_LEN = TIME_LEN + RAND_LEN
const NAME_RE = new RegExp('^[' + ALPHABET + ']{' + NAME_LEN + '}$')

function encode (value, width) {
  let n = BigInt(value)
  let out = ''
  for (let i = 0; i < width; i++) {
    out = ALPHABET[Number(n % 32n)] + out
    n /= 32n
  }
  return out
}

/**
 * Mint a name for a new record.
 *
 * ⛔ `at` and `bytes` are injectable FOR TESTS ONLY. Nothing in the daemon passes them, and no RPC
 * surface may ever expose them — a caller who can choose either can choose a collision, which is
 * precisely the defect this file exists to remove.
 */
export function recordName (at = Date.now(), bytes = null) {
  const r = bytes || crypto.randomBytes(10) // 80 bits
  let n = 0n
  for (let i = 0; i < 10; i++) n = (n << 8n) | BigInt(r[i])
  return encode(Math.floor(at), TIME_LEN) + encode(n, RAND_LEN)
}

/** True if `s` is one of ours. Deliberately strict: a legacy clock name is NOT this format. */
export function isRecordName (s) {
  return typeof s === 'string' && NAME_RE.test(s)
}

/**
 * The moment a name was minted, or null.
 *
 * ⛔ Returns null for a legacy clock name rather than parsing it. Records written before this change
 * are live on three machines and `record-store.js` treats a name as an opaque path segment, so the
 * two formats coexist and nothing needs migrating — but a caller must be told which it is holding
 * rather than handed a number that means something different.
 */
export function nameTime (s) {
  if (!isRecordName(s)) return null
  let n = 0n
  for (const ch of s.slice(0, TIME_LEN)) {
    const v = ALPHABET.indexOf(ch)
    if (v < 0) return null
    n = n * 32n + BigInt(v)
  }
  return Number(n)
}

export default { recordName, isRecordName, nameTime, NAME_LEN }
// A namespace — human names that resolve to feeds.
//
// ── What this is, in one line ─────────────────────────────────────────────────────────────────
//
// `NAMING.md`: **hashes are unique, names are not global, and there are global namespaces you can
// subscribe to.** A namespace is not an authority — it is a SUBSCRIPTION. You join it the way you
// join anything else here, and its names become resolvable *to you*.
//
// ⛔ Two people subscribed to different namespaces resolve the same string to different feeds, and
// that is correct rather than a conflict. Zooko's triangle is not being escaped: we give up global
// uniqueness, and the compensation is that a name is always qualified by a namespace you can point
// at.
//
// ── Why this is a separate log from the feed ──────────────────────────────────────────────────
//
// A feed's `profile/self` carries a SELF-LABEL: what a feed calls itself, claimed from nobody and
// unique to nobody. This is the other thing — a name CLAIMED in a shared log, unique within it,
// first-claim-wins by linearization. They coexist deliberately, and a client resolves in order:
//
//     handle (this)  →  self-label (profile record)  →  key (always shown)
//
// ── ⛔ The default is CONFIGURATION, never a root ─────────────────────────────────────────────
//
// Shipping a default namespace makes us a convenient starting point, never an authoritative one.
// `NAMING.md` §8 #2 names the hazard exactly: *a default that cannot be changed is a root.* So the
// key is an option, several namespaces coexist, and nobody has to agree.
//
// ── What we give up, stated ───────────────────────────────────────────────────────────────────
//
// ⚠ First-claim-wins plus reclaimable releases means a name CAN change hands. A client that has
// displayed a name must surface a reassignment — the same duty as a key-change warning in a
// messenger. Silently re-pointing a familiar name at a new person is the one failure this design
// can actually cause, so `resolve()` returns the claim's identity, not merely a feed key.

import Autobee from 'autobee'
import b4a from 'b4a'
import crypto from 'hypercore-crypto'
import { readAttested, signAttested } from './attestation.js'

const SEP = '/'
const CLAIM = 'name'
// Next discriminator per name. A COUNTER, not a scan: apply must assign the number deterministically
// and in O(1), and every peer replaying the same ops must reach the same assignment.
const COUNT = 'count'
// What a given identity already holds for a given name. Replay protection AND the rule that one
// person does not accumulate `alice0`, `alice1`, `alice2` — see `applyOps`.
const HELD = 'held'

// ── Who CARRIES the log ───────────────────────────────────────────────────────────────────────
//
// ⛔ Two different privileges, and conflating them turns a registry into an authority:
//
//   ADMIT   who carries the log — sort weight, ordering, writability. Granted.
//   CLAIM   who may take a name — ANYONE, for ever, by the forced number. Ungrantable.
//
// NAMING.md §4 says namespace governance is room governance, "who may claim = who may write". We
// take only half of that deliberately: this namespace stays open, and only WEIGHT is privileged.
//
// Why it is needed at all, MEASURED against autobee 2.1.1 (`probe-weight.mjs`, 2026-09-07):
// `host.ackWriter` records a writer's length so its optimistic block sticks and grants NOTHING —
// `maxWeight` unchanged, `isAdded` false, system record still null. `Writer.isIndexer` is
// `weight >= 2` and `weight` is the `maxWeight` capability only `host.addWriter` grants. So before
// this, every node that joined someone else's namespace was a weightless appender for ever and the
// founder carried the whole log alone.
//
// Reserved prefixes: control data, never reachable by a claim (names live under `name/`).
const ADMIN = '__admins__'
const WRITER = '__writers__'
const REVOKED = '__revoked__'
// A node asking to carry the log. The doorbell shape `feed.js` already uses: the one op a
// not-yet-writer may usefully send, and the thing an admit must point at.
const REQUEST = '__requests__'
// Invites. The log holds the HASH; the token is printed once and never stored.
//
// ⛔ An invite grants NOTHING — it cannot. Redeeming rides the joiner's own weightless block, and a
// grant is capped at the carrying block's weight. What the token does is authorise the INVITER'S
// node to answer a request unattended. The admit is still carried by an admin.
//
// ⚠ No expiry, revocation only. A deadline would need a clock inside apply, which is replayed on
// every peer — the same decision `browsers.js` reached.
const INVITE = '__invites__'
// A single marker, so "has this namespace been founded" is one get rather than a scan. The same
// reason `feed.js` keeps `__founder__` instead of asking whether the roster is empty.
const FOUNDED = '__founded__'

// ⛔ Ops carry a version. A claim that does not say `v: 2` is refused outright: there is no
// unattested claim and no mode anyone is in by accident, the same shape `feed.js` uses for
// admission.
//
// ⚠ This was justified as "affordable because the fleet's namespaces are empty — measured
// 2026-09-07 on all three hosts". That measurement was WRONG: ashatron was unreachable when it was
// taken, and it held `ashatron0`. The claim happened to be v:2 (made 22 minutes after attestation
// shipped) so nothing was lost, but the reasoning was luck rather than evidence.
//
// Once legacy claims DO exist, pgh's rule applies instead: legacy ops must keep applying or replay
// resurrects released names (`pgh/lib/name-registry.js`, OP_VERSION_SIGNED).
const OP_VERSION_SIGNED = 2
const KNOWN_OPS = new Set(['claim', 'release', 'found', 'admin', 'admit', 'revoke', 'request',
  'invite', 'uninvite'])
const enc = (v) => b4a.from(JSON.stringify(v), 'utf8')
const dec = (b) => (b == null ? null : JSON.parse(b4a.toString(b, 'utf8')))
const hex = (k) => (typeof k === 'string' ? k : b4a.toString(k, 'hex'))
const isKeyHex = (s) => typeof s === 'string' && /^[0-9a-f]{64}$/i.test(s)
// A single path segment: never empty, never a separator, never a traversal — the same guard
// `feed.js` uses, for the same reason (these become view keys).
const isSeg = (s) => typeof s === 'string' && s.length > 0 && !s.includes(SEP) && s !== '.' && s !== '..'

/**
 * Names are lower-cased and restricted to a small, unambiguous alphabet.
 *
 * ⛔ Normalised on the way IN and on the way OUT, so `Alice` and `alice` cannot be two claims. A
 * namespace whose names differ only by case or by a homoglyph is a namespace built for
 * impersonation.
 *
 * ⚠ Deliberately NOT unicode. Confusable-character handling is a real problem with a real
 * literature, and a half-done version of it is worse than an alphabet a human can verify by eye.
 */
export function normalize (name) {
  if (typeof name !== 'string') return null
  const n = name.trim().toLowerCase()
  // ⛔ A name may NOT END IN A DIGIT. The handle is the name with its number stuck straight on the
  // end — `alice` + `0` = `alice0` — so if a name could end in a digit, `alice12` would be both
  // "alice number 12" and "alice1 number 2" and nothing could tell them apart.
  //
  // That rule is what buys a separator-free handle. It costs names like `web3`, which is a real
  // cost and a deliberate one.
  if (!/^[a-z][a-z0-9._-]{0,30}[a-z._-]$/.test(n)) return null
  if (n.includes(SEP)) return null
  return n
}

const claimKey = (name, number) => CLAIM + SEP + name + SEP + number
const countKey = (name) => COUNT + SEP + name
const heldKey = (root, name) => HELD + SEP + root + SEP + name
const adminKey = (root) => ADMIN + SEP + root
const writerKey = (writer) => WRITER + SEP + writer
const revokedKey = (device) => REVOKED + SEP + device
const requestKey = (writer) => REQUEST + SEP + writer
const inviteKey = (hash) => INVITE + SEP + hash
const tokenHash = (token) => {
  try { return b4a.toString(crypto.data(b4a.from(String(token), 'hex')), 'hex') } catch { return null }
}

/**
 * The bytes an op's proof covers: every field EXCEPT the proof itself, keys sorted, JSON.
 *
 * ⛔ Sorted and total, so the signature commits to the WHOLE op — name, feed, namespace, anchor,
 * timestamp — rather than to a hand-picked subset. Swapping the feed key after signing produces
 * different bytes and the proof stops verifying. The same shape as pgh's `canonicalOpBytes`
 * (`pgh/lib/name-registry.js`), and it is why favela needs one primitive where keet needs two: keet
 * attests `encodeReservation({ username })`, which covers the username alone, so binding the core
 * key takes a SECOND proof (`keet-core/lib/core/invitations/index.js:114`).
 *
 * ⚠ Field ORDER cannot come from the object: two peers building the same op with keys inserted in
 * a different order would produce different bytes and disagree about a valid signature. Sorting is
 * what makes it canonical rather than merely deterministic-on-one-machine.
 */
export function canonicalOpBytes (op) {
  const rest = {}
  for (const k of Object.keys(op)) if (k !== 'proof') rest[k] = op[k]
  const ordered = {}
  for (const k of Object.keys(rest).sort()) ordered[k] = rest[k]
  return b4a.from(JSON.stringify(ordered), 'utf8')
}

/** Attach a device's proof over an op's canonical bytes. The inverse of what apply checks. */
function sign (op, cred) {
  return { ...op, proof: signAttested(canonicalOpBytes(op), cred) }
}

/**
 * Is this op authorised, and by whom?
 *
 * Returns the verified `{ root, device, chain }` or null. Pure, total, no clock — the properties
 * `attestation.js` exists to guarantee, and the reason this can run inside apply at all.
 *
 * ⛔ `nsHex` is checked because the proof alone does not name a namespace. Without it a claim signed
 * for one namespace lifts verbatim into any other, minting your name somewhere you never joined.
 */
function authorise (op, nsHex) {
  if (op.v !== OP_VERSION_SIGNED) return null
  if (!isKeyHex(op.root)) return null
  if (op.ns !== nsHex) return null
  return readAttested(op.proof, canonicalOpBytes(op), op.root)
}


/**
 * A handle is the name with its number on the end — `alice0`, `alice1`, `alice2`.
 *
 * ⛔ The NAME is not unique; the handle is. That is the point: a taken name never blocks anyone, so
 * there is nothing to squat and no race to win. It also makes changing your name ordinary — release
 * and claim again — rather than a transfer of something scarce.
 *
 * ⛔ There is no bare `alice`. The FIRST claimant is `alice0`, so nobody holds a name without a
 * number and no handle is privileged over another by being older.
 *
 * ⚠ Numbers are NEVER reused. A released `alice0` stays retired rather than passing to the next
 * claimant, because a recycled handle is precisely the "familiar name now denotes someone else"
 * failure NAMING.md warns about.
 */
export function parseHandle (handle) {
  if (typeof handle !== 'string') return null
  const h = handle.trim().toLowerCase()
  const m = /^(.*?)([0-9]+)$/.exec(h)
  if (!m) return null
  // No leading zeros: one handle, one spelling. `alice00` is not `alice0`.
  if (m[2].length > 1 && m[2][0] === '0') return null
  const name = normalize(m[1])
  if (!name) return null
  return { name, number: Number(m[2]) }
}

export const formatHandle = (name, number) => String(name) + String(number)

/**
 * The { get, set } surface apply wants, over ONE hyperbee2 write batch.
 *
 * ⛔ autobee's apply receives a hyperbee2, not a Hyperbee: raw Buffers, no key or value encoding,
 * `write()` rather than `batch()`, and the write handle has NO get. So reads go to the bee and a
 * pending overlay carries writes made earlier in the same batch — without it, two claims on one
 * name in a single batch would both see "free" and the second would overwrite the first.
 *
 * The same shape feed-base.js uses, for the same reasons.
 */
function batchView (bee, batch) {
  const pending = new Map()
  return {
    async get (key) {
      if (pending.has(key)) return pending.get(key)
      const node = await bee.get(b4a.from(key, 'utf8'))
      return node ? dec(node.value) : null
    },
    async set (key, value) {
      pending.set(key, value)
      batch.tryPut(b4a.from(key, 'utf8'), enc(value))
    },
    /**
     * Every entry under `prefix`, INCLUDING writes made earlier in this same batch.
     *
     * ⛔ The pending merge is the whole point. A scan straight against the bee cannot see a `set`
     * from an earlier op of the same batch, so a writer admitted and revoked in one batch would
     * survive the revocation. That is exactly the defect `hyper-identity2`'s BatchView was written
     * for (#63 §3.2), and a contract test would not have caught it there either.
     *
     * ⚠ Used ONLY over `__writers__`, whose size is the set of machines carrying the log — small
     * and bounded. Never over names: that scan is O(everyone) and would run inside apply.
     */
    async scan (prefix) {
      const out = new Map()
      const p = b4a.from(prefix, 'utf8')
      for await (const node of bee.createReadStream({ gte: p, lt: b4a.concat([p, b4a.from([0xff])]) })) {
        out.set(b4a.toString(node.key, 'utf8'), dec(node.value))
      }
      for (const [key, value] of pending) if (key.startsWith(prefix)) out.set(key, value)
      return [...out].map(([key, value]) => ({ key, value })).filter((e) => e.value != null)
    }
  }
}

/**
 * apply — deterministic and total, like every apply here.
 *
 * First-claim-wins is a property of the LINEARIZATION, not of a check-then-write: two peers racing
 * one name both append, the log picks an order, and every peer replays that same order. There is no
 * window in which both succeed.
 */
/**
 * Is this device retired, or does it descend from one that is?
 *
 * ⛔ Transitive, because a device can mint descendants from its own keypair with no mnemonic — so a
 * compromised device HAS descendants and revoking it has to reach them or it reaches nothing. The
 * proof carries the whole chain, which is why the ancestry is expressible here at all.
 */
async function isRetired (b, proof) {
  if (await b.get(revokedKey(proof.device))) return true
  for (const ancestor of proof.chain) {
    if (await b.get(revokedKey(ancestor))) return true
  }
  return false
}

/**
 * The signer, if this op is authorised AND its identity is an admin in good standing.
 *
 * ⛔ Revocation is tested BEFORE the grant is read. Self-service admission means a retired device
 * still holds a perfectly valid proof, so checking the admin list first would make revocation
 * decorative — the ordering `feed.js:167` already pays for.
 */
async function adminSigner (b, op, nsHex) {
  const proof = authorise(op, nsHex)
  if (!proof) return null
  if (await isRetired(b, proof)) return null
  if (!(await b.get(adminKey(proof.root)))) return null
  return proof
}

async function applyOps (batch, view, host, nsHex) {
  const write = view.write()
  const b = batchView(view, write)

  for (const node of batch) {
    let op
    try { op = dec(node.value) } catch { continue } // a malformed block is a no-op, deterministically
    if (!op || typeof op !== 'object') continue
    // ⛔ node.key, not node.from.key — autobee attributes on the node itself.
    const writer = node.key ? hex(node.key) : null
    if (!writer) continue

    // An open namespace takes writes from peers who were never admitted, so the optimistic block
    // has to be acked or it never sticks. Acked only for the ops that exist; anything else is
    // ignored AND unacked, so a stranger cannot park arbitrary blocks in the log.
    //
    // ⛔ `admit` MUST be in this set. The node sending one is by definition not yet a writer — that
    // is what it is asking to become — so without an ack its block never sticks and admission can
    // never happen at all. ⚠ An ack is not admission: it grants no weight and no writability
    // (measured — see the ADMIN block above).
    if (KNOWN_OPS.has(op.t)) {
      try { host.ackWriter(node.key) } catch {}
    }

    if (op.t === 'claim') {
      const name = normalize(op.name)
      if (!name || !isKeyHex(op.feed)) continue

      // ⛔ The claim must PROVE who is making it. Everything the op says is inside the signed bytes,
      // so a stranger cannot lift a valid claim and re-aim it at another feed, another name or
      // another namespace — any of those edits changes the bytes and the proof stops verifying.
      //
      // ⚠ What this CANNOT prove is that `root` owns `feed`. The namespace is a separate log with
      // no access to the feed's `__founder__`, so a stranger may still claim a name pointing at
      // someone else's feed — under THEIR OWN root, which is recorded here and is what makes the
      // lie detectable. That half is checked where the feed is actually held; the claim carries
      // `root` precisely so it can be.
      const proof = authorise(op, nsHex)
      if (!proof) continue

      // ⛔ One identity, one live claim per name. This is replay protection — re-appending a
      // captured claim op mints nothing — and it is also the rule that stops one person quietly
      // accumulating `alice0`, `alice1`, `alice2` and re-creating the scarcity the number exists
      // to abolish.
      const held = await b.get(heldKey(proof.root, name))
      if (held && held.live) continue

      // ⛔ APPLY assigns the number, from a counter it owns. A client that picked its own would be
      // racing every other client for the same one; here two peers claiming `alice` in the same
      // batch get 0 and 1, in whatever order the log settled, on every peer identically.
      // Starts at 0, so the first claimant is `alice0` — nobody gets a bare name.
      const stored = await b.get(countKey(name))
      const next = typeof stored === 'number' ? stored : 0
      await b.set(countKey(name), next + 1)
      await b.set(heldKey(proof.root, name), { number: next, live: true })
      await b.set(claimKey(name, next), {
        feed: op.feed.toLowerCase(),
        // ⛔ The ANCHOR is the identity ROOT, not the device and not the writer core. pgh shipped a
        // device key here and had to add `reanchor --all --force` to repair it; the writer key this
        // used to store is a step further down still — manifest-derived, no secret half, it can
        // never sign, so nothing could ever be proven about it later.
        root: proof.root,
        // The device that actually signed, kept for revocation: a chain is made of device keys, so
        // ancestry is only expressible over them.
        device: proof.device,
        writer,
        // ⚠ From the OP, never from a clock in here: apply is replayed per peer, and a timestamp
        // read here would make two peers write different bytes for the same op.
        at: typeof op.at === 'number' ? op.at : 0,
        released: false
      })
      continue
    }

    // ── who carries the log ─────────────────────────────────────────────────────────────────

    // Found the namespace: the creator's identity ROOT becomes the first admin.
    //
    // ⛔ ONE marker, checked first, so a second founding op changes nothing. The window this cannot
    // close is before the FIRST op ever lands — which is why `found()` runs at creation, before the
    // key has been handed to anyone. A namespace whose key you were given is already founded.
    //
    // ⚠ The op must reach the log NON-optimistically: "an optimistic writer cannot be genesis"
    // (autobee/lib/writers.js:529), so an optimistic founding op is never processed and the
    // namespace comes up with no admin and no way to add one.
    if (op.t === 'found') {
      if (await b.get(FOUNDED)) continue
      const proof = authorise(op, nsHex)
      if (!proof) continue
      await b.set(FOUNDED, { root: proof.root, at: typeof op.at === 'number' ? op.at : 0 })
      await b.set(adminKey(proof.root), { by: proof.root, device: proof.device, founding: true })
      continue
    }

    // Grant admin to another identity. An admin's act, and anchored to ROOTS on both sides: one
    // person's two devices are two writers, so a device-scoped list could never say "this person".
    if (op.t === 'admin') {
      if (!isKeyHex(op.grant)) continue
      const proof = await adminSigner(b, op, nsHex)
      if (!proof) continue
      await b.set(adminKey(op.grant.toLowerCase()), { by: proof.root, device: proof.device })
      continue
    }

    // Admit a writer core, so this node carries the log rather than appealing to it.
    //
    // ⛔ SELF-SERVICE, and the writer key is inside the SIGNED BYTES. `host.addWriter` wants an
    // autobee writer core key — manifest-derived, no secret half, it can never sign (#63 §2.2) — so
    // the node proves itself with its DEVICE credential and names its own core. Nothing is carried
    // back to the founder by hand, and swapping the key after signing breaks the proof.
    // Ask to carry the log. The one useful op a not-yet-writer can send, and it grants NOTHING —
    // it records that a writer core wants in, and whose device stands behind it.
    //
    // ⛔ A request must exist before an admit can name it, so admission takes TWO acts by TWO
    // parties: the joiner asks with its own device, an admin honours it. Exactly the rule
    // `applyListFeed` enforces with knocks, and for the same reason — consent becomes structural
    // rather than a promise the inviter makes.
    if (op.t === 'invite') {
      if (!isSeg(op.hash)) continue
      const proof = await adminSigner(b, op, nsHex)
      if (!proof) continue
      if (await b.get(inviteKey(op.hash))) continue // never re-open a spent one
      await b.set(inviteKey(op.hash), { by: proof.root, used: false })
      continue
    }

    if (op.t === 'uninvite') {
      if (!isSeg(op.hash)) continue
      const proof = await adminSigner(b, op, nsHex)
      if (!proof) continue
      const inv = await b.get(inviteKey(op.hash))
      if (!inv || inv.used) continue
      await b.set(inviteKey(op.hash), { ...inv, withdrawn: true, used: true, by: proof.root })
      continue
    }

    if (op.t === 'request') {
      if (!isKeyHex(op.writer)) continue
      const proof = authorise(op, nsHex)
      if (!proof) continue
      if (await isRetired(b, proof)) continue
      // ⛔ The token is spent INSIDE apply, so two nodes racing one invite cannot both win — the
      // linearization decides and there is no window. A bad token is not an error: anyone may ask,
      // the ask simply is not vouched for and no admin's node will answer it unattended.
      let invited = false
      if (op.token !== undefined) {
        const h = tokenHash(op.token)
        const inv = h && await b.get(inviteKey(h))
        if (inv && !inv.used) {
          await b.set(inviteKey(h), { ...inv, used: true, by: inv.by, redeemer: proof.root })
          invited = true
        }
      }
      await b.set(requestKey(op.writer.toLowerCase()), { root: proof.root, device: proof.device, invited })
      continue
    }

    // Admit a writer core, so it carries the log.
    //
    // ⛔⛔ THIS OP MUST BE CARRIED BY A WRITER THAT ALREADY HAS WEIGHT, and apply cannot check that.
    // `system.addWriter` computes `effective = coord && carrier < weight ? max(carrier, 0) : weight`
    // (autobee/lib/system.js:64) — `carrier` is the weight of the BLOCK carrying the grant. So an
    // admit ridden in on a weightless optimistic block is capped to 0: the grant appears to succeed,
    // every peer agrees on the view, and the writer silently gets nothing. MEASURED 2026-09-07
    // (`probe-optimistic.mjs`): self-admission gave maxWeight 0 on both peers; the same admit
    // carried by the founder gave 2.
    //
    // ⇒ Weight is only ever conferred DOWNWARD. A node cannot bootstrap its own, which is why
    // `admitWriter` refuses locally rather than appending something that would be quietly inert.
    if (op.t === 'admit') {
      if (!isKeyHex(op.writer)) continue
      const proof = await adminSigner(b, op, nsHex)
      if (!proof) continue
      const writer = op.writer.toLowerCase()
      const asked = await b.get(requestKey(writer))
      // An admin cannot conscript a machine that never asked, and cannot admit one whose device was
      // retired between asking and being answered.
      if (!asked || asked.removed) continue
      if (await b.get(revokedKey(asked.device))) continue
      // ⚠ Wrapped: apply must be TOTAL. A throw mid-replay wedges the base and the op that caused
      // it is already durable, so there is no version of "fix it and retry".
      try { host.addWriter(b4a.from(writer, 'hex'), { isIndexer: true }) } catch {}
      await b.set(writerKey(writer), { root: asked.root, device: asked.device, by: proof.root })
      continue
    }

    // Retire a device — a TOMBSTONE, because absence is ambiguous in a replicated log.
    //
    // ⛔ It reaches what the device already holds. A revocation that only refused FUTURE admits
    // would leave a compromised machine carrying the log for ever, which is revocation in name.
    if (op.t === 'revoke') {
      if (!isSeg(op.device)) continue
      const proof = await adminSigner(b, op, nsHex)
      if (!proof) continue
      const device = op.device.toLowerCase()
      await b.set(revokedKey(device), { by: proof.device, root: proof.root })
      // ⚠ A scan of the WRITER prefix only — the set of machines carrying the log, which is small
      // and bounded. Never a scan of names.
      for (const entry of await b.scan(WRITER + SEP)) {
        if (!entry.value || entry.value.device !== device) continue
        const w = entry.key.slice(WRITER.length + 1)
        try { host.removeWriter(b4a.from(w, 'hex')) } catch {}
        await b.set(entry.key, { ...entry.value, removed: true })
      }
      // ⚠ And its outstanding asks, or a retired device is re-admitted from a stale request the
      // moment any admin answers it.
      for (const entry of await b.scan(REQUEST + SEP)) {
        if (!entry.value || entry.value.device !== device) continue
        await b.set(entry.key, { ...entry.value, removed: true })
      }
      continue
    }

    if (op.t === 'release') {
      const h = parseHandle(op.handle)
      if (!h) continue
      const name = h.name
      const existing = await b.get(claimKey(name, h.number))
      if (!existing || existing.released) continue

      // ⛔ Only the IDENTITY that claimed it may release it — proven, not asserted. Anything less
      // would let a stranger free someone's name and take it.
      //
      // ★ This is what replaced the writer comparison that used to stand here, and it closes the
      // limitation written beside it: a name claimed on one machine can now be released from any
      // other device of the same person, because the anchor is the root and every device chains to
      // it. The old check could not express that — one identity's two devices are two writers.
      const proof = authorise(op, nsHex)
      if (!proof) continue
      if (existing.root !== proof.root) continue
      // A TOMBSTONE, and the name becomes reclaimable — NAMING.md says released names return to
      // the pool. That is also why a client must surface a reassignment.
      // ⚠ The number is retired, not freed — the counter never goes backwards.
      await b.set(claimKey(name, h.number), { ...existing, released: true, releasedAt: op.at || 0 })
      // ⛔ And the identity is free to claim this name again — which is what makes a rename ordinary
      // rather than a transfer. Without this the one-live-claim-per-name rule would turn every
      // release into a permanent ban on your own name.
      await b.set(heldKey(proof.root, name), { number: h.number, live: false })
      continue
    }
  }

  await write.flush()
}

export class Namespace {
  /**
   * @param store    a Corestore NAMESPACE of its own — two Autobees sharing one derive the same
   *                 local writer and deadlock rather than erroring.
   * @param key      the namespace to subscribe to; null creates one.
   */
  constructor (store, key = null, opts = {}) {
    this.base = new Autobee(store, key || null, {
      // ⛔ apply is closed over THIS namespace's own key, so an op has to name the namespace it was
      // signed for. `base.key` is fixed by `ready()` and identical on every peer replaying this
      // log, so reading it here costs no determinism.
      apply: (batch, view, host) => applyOps(batch, view, host, hex(this.base.key)),
      // ⛔ Open by construction. A namespace anyone can claim in needs writes from peers who were
      // never admitted, which is what optimistic append is for — the same mechanism the doorbell
      // uses. Governance of a CLOSED namespace is room roles (NAMING.md §4); this is the open one.
      optimistic: true,
      fastForward: false
    })
    this._label = opts.label || null
  }

  async ready () { await this.base.ready(); return this }
  get key () { return this.base.key }
  get discoveryKey () { return this.base.discoveryKey }
  async update () { await this.base.update() }
  replicate (isInitiatorOrStream) { return this.base.replicate(isInitiatorOrStream) }
  async close () { await this.base.close() }

  /**
   * Claim a name for a feed. The namespace assigns the number.
   *
   * Appended optimistically — a subscriber is not a writer. The handle is not known until the log
   * settles, so this returns nothing and the caller reads it back with `handleFor`.
   */
  async claim (name, feedKey, cred) {
    const op = this.claimOp(name, feedKey, cred)
    await this.base.append(enc(op), { optimistic: true })
    return op.name
  }

  /**
   * The signed op `claim` appends, without appending it.
   *
   * ⛔ Exposed so a test can drive the REAL op — tamper with one field, or replay it into another
   * namespace — rather than a hand-built lookalike. A test that reconstructs the op is testing its
   * own reconstruction; both of these tests passed for the wrong reason until they used this.
   */
  claimOp (name, feedKey, cred) {
    const n = normalize(name)
    if (!n) throw new Error('a name is 2-32 characters of a-z, 0-9, dot, dash or underscore, ' +
      'and may not END in a digit — the number goes there')
    return sign({
      t: 'claim',
      v: OP_VERSION_SIGNED,
      name: n,
      feed: hex(feedKey).toLowerCase(),
      ns: hex(this.base.key),
      root: this._root(cred),
      at: Date.now()
    }, cred)
  }

  /**
   * Give up a handle. Only the identity that claimed it may — from ANY of its devices.
   */
  async release (handle, cred) {
    await this.base.append(enc(this.releaseOp(handle, cred)), { optimistic: true })
  }

  /**
   * The signed op `release` appends, without appending it. Exposed for the same reason `claimOp`
   * is: a test that rebuilds the op is testing its own reconstruction, and a hostile op has to be
   * driven into the REAL base to prove apply refuses it.
   */
  releaseOp (handle, cred) {
    const h = parseHandle(handle)
    if (!h) throw new Error('a handle is a name with its number on the end, e.g. alice0')
    return sign({
      t: 'release',
      v: OP_VERSION_SIGNED,
      handle: formatHandle(h.name, h.number),
      ns: hex(this.base.key),
      root: this._root(cred),
      at: Date.now()
    }, cred)
  }

  // ── who carries the log ──────────────────────────────────────────────────────────────────────

  /**
   * Found this namespace: `cred`'s identity becomes its first admin.
   *
   * ⛔ Call it at CREATION, before the key has been shared. The op is refused once an admin exists,
   * so the only window is before the first op lands — and that window is only closed by nobody else
   * holding the key yet.
   *
   * ⛔ Appended NON-optimistically, deliberately: an optimistic writer cannot be genesis
   * (autobee/lib/writers.js:529), so an optimistic founding op is never processed at all. It throws
   * "Not writable" if this node is not the genesis writer, which is the correct loud failure — you
   * cannot found a namespace somebody else created.
   */
  async found (cred) {
    await this.base.append(enc(this.foundOp(cred)))
  }

  foundOp (cred) {
    return sign({
      t: 'found',
      v: OP_VERSION_SIGNED,
      ns: hex(this.base.key),
      root: this._root(cred),
      at: Date.now()
    }, cred)
  }

  /** Grant admin to another IDENTITY. An admin's act. */
  async grantAdmin (rootHex, cred) {
    if (!isKeyHex(rootHex)) throw new Error('an admin is an identity root — 32 bytes of hex')
    await this.base.append(enc(sign({
      t: 'admin',
      v: OP_VERSION_SIGNED,
      grant: String(rootHex).toLowerCase(),
      ns: hex(this.base.key),
      root: this._root(cred),
      at: Date.now()
    }, cred)), { optimistic: true })
  }

  /**
   * Ask to carry this namespace's log. Appended optimistically — a joiner is not a writer yet, and
   * asking is the one useful thing it can do. Grants nothing on its own.
   */
  async request (cred, token) {
    return this.requestWriter(hex(this.base.local.key), cred, token)
  }

  async requestWriter (writerHex, cred, token) {
    if (!isKeyHex(writerHex)) throw new Error('a writer is an autobee core key — 32 bytes of hex')
    const op = {
      t: 'request',
      v: OP_VERSION_SIGNED,
      writer: String(writerHex).toLowerCase(),
      ns: hex(this.base.key),
      root: this._root(cred),
      at: Date.now()
    }
    // ⚠ The token travels in the clear, and that is fine: it is spent by the very op that carries
    // it. Same as a browser redeem.
    if (token !== undefined && token !== null) op.token = String(token)
    await this.base.append(enc(sign(op, cred)), { optimistic: true })
  }

  /**
   * Answer a request: admit that writer core so it carries the log.
   *
   * ⛔ Refuses unless THIS node already carries weight. `system.addWriter` caps the grant at the
   * weight of the block carrying it, so an admit appended by a weightless writer is silently inert —
   * it applies, every peer agrees, and the target gets maxWeight 0. There is no error anywhere,
   * which is the worst kind of failure, so this is the loud one.
   */
  async admitWriter (writerHex, cred) {
    if (!this.base.isIndexer) {
      throw new Error('this node carries no weight in this namespace, so admitting anyone would ' +
        'grant them nothing at all — weight is only ever conferred downward. Ask an admin to ' +
        'admit them, or be admitted yourself first.')
    }
    await this.base.append(enc(this.admitOp(writerHex, cred)), { optimistic: true })
  }

  /** Outstanding asks — `{ writer, root, device, invited }`. What an inviter answers. */
  async requests () {
    return (await this._prefix(REQUEST))
      .filter((e) => !e.value.removed)
      .map((e) => ({ writer: e.key, root: e.value.root, device: e.value.device, invited: !!e.value.invited }))
  }

  /**
   * Mint a single-use invite. Returns `{ token, hash }` — the TOKEN is printed once and never
   * stored anywhere; only its hash reaches the log.
   */
  async invite (cred) {
    const token = b4a.toString(crypto.randomBytes(32), 'hex')
    const hash = tokenHash(token)
    await this.base.append(enc(sign({
      t: 'invite',
      v: OP_VERSION_SIGNED,
      hash,
      ns: hex(this.base.key),
      root: this._root(cred),
      at: Date.now()
    }, cred)), { optimistic: true })
    return { token, hash }
  }

  /** Withdraw an unspent invite. Revocation stands in for expiry — apply has no clock. */
  async withdrawInvite (hash, cred) {
    if (!isSeg(hash)) throw new Error('withdrawing needs an invite hash')
    await this.base.append(enc(sign({
      t: 'uninvite',
      v: OP_VERSION_SIGNED,
      hash: String(hash).toLowerCase(),
      ns: hex(this.base.key),
      root: this._root(cred),
      at: Date.now()
    }, cred)), { optimistic: true })
  }

  /** Invites minted here — `{ hash, used, withdrawn }`. The token is in none of them. */
  async invites () {
    return (await this._prefix(INVITE))
      .map((e) => ({ hash: e.key, used: !!e.value.used, withdrawn: !!e.value.withdrawn }))
  }

  /**
   * Answer every vouched-for ask that is not already a writer. Returns the writers admitted.
   *
   * ⛔ Only the INVITED ones. An uninvited ask still waits for a human, which is the entire point of
   * the token — otherwise this method would auto-admit any stranger who asked.
   */
  async answerInvited (cred) {
    if (!this.base.isIndexer) return []
    const done = new Set((await this.writers()).map((w) => w.writer))
    const out = []
    for (const r of await this.requests()) {
      if (!r.invited || done.has(r.writer)) continue
      try {
        await this.admitWriter(r.writer, cred)
        out.push(r.writer)
      } catch {}
    }
    if (out.length) await this.update()
    return out
  }

  admitOp (writerHex, cred) {
    if (!isKeyHex(writerHex)) throw new Error('a writer is an autobee core key — 32 bytes of hex')
    return sign({
      t: 'admit',
      v: OP_VERSION_SIGNED,
      writer: String(writerHex).toLowerCase(),
      ns: hex(this.base.key),
      root: this._root(cred),
      at: Date.now()
    }, cred)
  }

  /**
   * Retire a DEVICE, and everything it carries.
   *
   * ⛔ The device, never the writer core: a chain is made of device keys — the only keys that can
   * sign — so ancestry is only expressible over them, and one revoke retires every core that device
   * admitted.
   */
  async revokeDevice (deviceHex, cred) {
    if (!isSeg(deviceHex)) throw new Error('revoking needs a device key')
    await this.base.append(enc(sign({
      t: 'revoke',
      v: OP_VERSION_SIGNED,
      device: String(deviceHex).toLowerCase(),
      ns: hex(this.base.key),
      root: this._root(cred),
      at: Date.now()
    }, cred)), { optimistic: true })
  }

  /** The identity roots that carry this namespace. */
  async admins () {
    return (await this._prefix(ADMIN)).map((e) => e.key)
  }

  /** The writer cores admitted here. Removed ones are omitted — retired, not present. */
  async writers () {
    return (await this._prefix(WRITER))
      .filter((e) => !e.value.removed)
      .map((e) => ({ writer: e.key, root: e.value.root, device: e.value.device }))
  }

  /** Retired device keys. Kept visible: a revocation nobody can see is not evidence of anything. */
  async revokedDevices () {
    return (await this._prefix(REVOKED)).map((e) => e.key)
  }

  /** Has this namespace been founded, and by whom? */
  async founder () {
    const node = await this._view().get(b4a.from(FOUNDED, 'utf8'))
    return node ? dec(node.value) : null
  }

  async _prefix (prefix) {
    const out = []
    const start = prefix + SEP
    for await (const node of this._view().createReadStream({
      gte: b4a.from(start, 'utf8'),
      lt: b4a.from(start + '\xff', 'utf8')
    })) {
      const value = dec(node.value)
      if (value == null) continue
      out.push({ key: b4a.toString(node.key, 'utf8').slice(start.length), value })
    }
    return out.sort((a, b) => (a.key < b.key ? -1 : a.key > b.key ? 1 : 0))
  }

  /**
   * The identity root a credential resolves to.
   *
   * ⛔ Read back out of the credential's own proof rather than taken on trust from a caller, so the
   * `root` an op announces is always the one its proof will verify against. Refuses loudly: an
   * unlinked device has nothing to claim WITH, and a claim it could not sign would be dropped
   * silently by every peer's apply — a failure with no error, which is the worst kind.
   */
  _root (cred) {
    if (!cred) throw new Error('claiming a name needs this device\'s credential — run `favela link`')
    const read = readAttested(cred.proof, undefined)
    if (!read) throw new Error('this device\'s credential does not verify, so it cannot claim a name')
    return read.root
  }

  /**
   * Change a name: give up the old handle and take a new one.
   *
   * ⚠ Two ops, not one. The old handle is retired the moment the release lands, and the new number
   * is whatever the counter is at — so a rename is never a transfer, and there is a moment where
   * neither is current. That is honest for a log: an atomic rename would need a transaction the
   * substrate does not have.
   */
  async rename (oldHandle, newName, feedKey, cred) {
    await this.release(oldHandle, cred)
    return this.claim(newName, feedKey, cred)
  }

  /**
   * Resolve a handle to `{ handle, name, number, feed, at }`, or null.
   *
   * ⛔ Returns the whole claim rather than a bare feed key, so a caller can tell a REASSIGNMENT
   * from a steady handle. A client that has displayed a name owes the reader that difference.
   */
  async resolve (handle) {
    const h = parseHandle(handle)
    if (!h) return null
    const node = await this._view().get(b4a.from(claimKey(h.name, h.number), 'utf8'))
    const v = node && dec(node.value)
    if (!v || v.released) return null
    return {
      handle: formatHandle(h.name, h.number), name: h.name, number: h.number,
      feed: v.feed, at: v.at,
      // ⛔ The ANCHOR travels with every resolution. NAMING.md §3 resolves a name to an identity
      // root and only then to a room key — the caller needs the root to check this claim against
      // the feed's own `__founder__`, which is the half apply structurally cannot do.
      root: v.root, device: v.device
    }
  }

  /**
   * Everyone currently holding a given NAME — `alice` may be several people.
   *
   * ⛔ A bare name is deliberately not resolvable to one feed. NAMING.md §3.2: an unresolvable name
   * must fail visibly rather than have a namespace guessed for it, and picking "the first alice"
   * would be exactly that guess wearing a helpful face.
   */
  async candidates (name) {
    const n = normalize(name)
    if (!n) return []
    return (await this.list(n + SEP)).filter((c) => c.name === n)
  }

  /** The handle claimed for a feed, or null. */
  async handleFor (feedKey) {
    const feed = hex(feedKey).toLowerCase()
    for (const c of await this.list()) if (c.feed === feed) return c.handle
    return null
  }

  /** Live claims, in handle order. Released handles are omitted — they are retired, not present. */
  async list (prefix = '') {
    const out = []
    const start = CLAIM + SEP + (prefix || '')
    for await (const node of this._view().createReadStream({
      gte: b4a.from(start, 'utf8'),
      lt: b4a.from(CLAIM + SEP + (prefix || '') + '\xff', 'utf8')
    })) {
      const v = dec(node.value)
      if (!v || v.released) continue
      const rest = b4a.toString(node.key, 'utf8').slice(CLAIM.length + 1)
      const i = rest.lastIndexOf(SEP)
      if (i === -1) continue
      const name = rest.slice(0, i)
      const number = Number(rest.slice(i + 1))
      out.push({ handle: formatHandle(name, number), name, number, feed: v.feed, at: v.at, root: v.root })
    }
    return out.sort((a, b) => (a.handle < b.handle ? -1 : a.handle > b.handle ? 1 : 0))
  }

  /** Handles matching a query — what a search box calls. */
  async search (query, { limit = 25 } = {}) {
    const q = String(query == null ? '' : query).toLowerCase().trim()
    // Fail closed: an empty box is not a request for the whole namespace.
    if (!q) return []
    return (await this.list()).filter((c) => c.handle.includes(q) || c.name.includes(q)).slice(0, limit)
  }

  _view () { return this.base.view }
}

export default Namespace
// FavelaNode — one process, many feeds, ONE of everything underneath.
//
// The shape is not a convenience. Three separate lessons land on it:
//
//   ⛔ Two Autobees on one Corestore need SEPARATE NAMESPACES or the second derives the same local
//      writer as the first and the pair DEADLOCKS — it does not error, it hangs (measured
//      2026-09-06, replicate.test.js).
//   ★  keet carries many rooms on ONE corestore and ONE swarm, per-room autobase keyed by discovery
//      key. That topology is proven, not hypothetical.
//   ⚠  hyper-proto-peer learned the same thing the expensive way: one shared store + one swarm +
//      one overlay per DID, namespaced.
//
// So: one storage dir, one Corestore, one HyperOverlay, one swarm — and every feed is a namespace.

import Corestore from 'corestore'
import { HyperOverlay } from 'hyper-overlay'
import b4a from 'b4a'
import fs from '#node:fs'
import { join } from '#node:path'
import { Feed } from './feed-base.js'
import { RecordStore } from './record-store.js'
import { Peer } from './peer.js'
import { loadCredential, saveCredential, bindWriter, loadFeedKey, saveFeedKey } from './device.js'
import { Browsers } from './browsers.js'
import { Pairing } from './pairing.js'
import { Avatars } from './avatars.js'
import { Files } from './files.js'
import { Namespace } from './namespace.js'
import { loadNamespace, saveNamespace, namespaceSlot, slotFor, loadNamespaces, saveNamespaces, loadRooms, saveRooms } from './device.js'
import { Room } from './room.js'
import { readAttested } from './attestation.js'
import { createMirror } from './mirror.js'

const hex = (k) => (typeof k === 'string' ? k : b4a.toString(k, 'hex'))

/**
 * May this node adopt `incomingFeed` as its own?
 *
 * ⛔ Pairing happens AT INIT — a phone joining an identity it has never written under — so adopting
 * is the normal path and this guard is for the case that is not init.
 *
 * ⚠ It counts RECORDS, not founders. An unlinked node has an empty, unfounded feed (opening a log
 * is not founding it), so a "no founder" test would refuse every legitimate pairing; counting
 * records refuses exactly the case that would lose something.
 *
 * Pure, so it can be tested without a swarm.
 */
export function adoptionRefusal (recordCount, incomingFeed, ownFeed) {
  if (!incomingFeed || incomingFeed === ownFeed) return null // nothing to adopt
  if (recordCount > 0) {
    return 'this node has already written ' + recordCount + ' record(s) to its own feed, and ' +
      'joining another would abandon them. Pair a fresh node, or move those records first.'
  }
  return null
}

/**
 * Does the feed a claim points at AGREE that the claim's identity owns it?
 *
 * Pure, so it can be tested without a node, a store or a swarm — the same reason `adoptionRefusal`
 * above is pure. Everything that decides anything lives here; `resolveHandle` is only the lookup
 * that finds the two inputs.
 *
 *   confirmed    the feed's founder IS the claim's anchor
 *   MISMATCH     the feed is founded by someone else — the claim is a lie, refuse it
 *   unfounded    the feed is held but nobody founded it, so it names nobody yet
 */
export function handleVerdict (claim, founder) {
  if (!founder || !founder.identityRoot) return { ...claim, verdict: 'unfounded' }
  // ⛔ Compared as hex STRINGS, both lower-cased at their source — apply stores `proof.root` from
  // `attestation.js`, which returns hex, and `__founder__.identityRoot` comes from the same
  // function. A Buffer compared by identity here would pass nothing and reject everything.
  if (founder.identityRoot !== claim.root) {
    return { ...claim, verdict: 'MISMATCH', founder: founder.identityRoot }
  }
  return { ...claim, verdict: 'confirmed', founder: founder.identityRoot }
}

export class FavelaNode {
  constructor (storage, opts = {}) {
    if (typeof storage !== 'string') throw new Error('FavelaNode: a storage directory is required')
    this.storage = storage
    this.corestore = new Corestore(join(storage, 'store'))
    this.overlay = null
    this._injectedOverlay = opts.overlay || null
    this.swarm = opts.swarm || null
    this.transport = opts.transport || null
    this.peer = null
    this._feeds = new Map() // hex feed key -> Feed
    // Feeds being opened right now. ⛔ Keyed so a reader pressing twice, or a page polling, cannot
    // stack duplicate opens of the same feed.
    this._opening = new Map()
    // hex room key -> Room. ⛔ One Corestore namespace EACH: two Autobees sharing one derive the
    // same local writer and hang rather than erroring, and a node is expected to hold many rooms.
    this._rooms = new Map()
    this._optimistic = opts.optimistic !== false // the doorbell is on by default
    // Browser identities. Its own Autobee on its own Corestore namespace — see browsers.js.
    this.browsers = null
    this.avatars = null
    this.names = null
    this._invitedWatch = null
    this._answering = false
    // At most one pairing offer in flight. A second would mint a second credential and leave the
    // first dangling — an invite nobody can account for.
    this._pairing = null
    this._pairingState = null
  }

  /**
   * Rebuild the overlay's serve map from what this node already indexed.
   *
   * ⛔ hyper-overlay resolves an incoming fetch-by-oid through IN-MEMORY maps that only
   * `registerFile()` fills, and nothing rebuilds them at boot. Without this a restarted node serves
   * ONLY what it has written since it started — every other file sits on disk, indexed, with a
   * matching hash, and unreachable to peers. That cost 75+ silent hours on wj62; 0.3.9 added the
   * seed and the CONSUMER has to call it. This one never did.
   *
   * MEASURED 2026-09-07: tabertron held 9 pointers from ashatron and could read 0, stable, while
   * ashatron read its own posts fine off local disk — its daemon had been restarted minutes before.
   *
   * ⚠ Cheap by design: one index scan, no re-hashing and no file reads. `reindexOverlay()` is the
   * heavy repair for an index that is itself stale, and is deliberately not on the boot path.
   */
  async _seedOverlay () {
    if (!this.overlay || typeof this.overlay.seedFilePathsFromIndex !== 'function') return 0
    try {
      return await this.overlay.seedFilePathsFromIndex((p) => this._resolveOverlayPath(p))
    } catch {
      // A serve map that could not be rebuilt is a degraded node, not a dead one: it still reads,
      // writes and replicates logs. Refusing to boot over it would be worse.
      return 0
    }
  }

  /**
   * An overlay path back to a file under this node's storage, or null.
   *
   * ⛔ The index is DATA ON DISK. An entry carrying a traversing path must not be able to point the
   * serve map outside this storage root — that map is what gets handed to peers.
   */
  _resolveOverlayPath (p) {
    if (typeof p !== 'string' || !p.startsWith('/')) return null
    const abs = join(this.storage, p)
    const root = join(this.storage, '/')
    if (!abs.startsWith(root)) return null
    // ⚠ And it must EXIST. Entries written under the older overlay-path shape resolve to files that
    // were never there, and seeding the map with those would replace "I cannot serve this" with "I
    // will offer you a file I do not have" — a worse answer, and one the requester only discovers
    // by timing out. One stat per index entry; the scan is already reading them.
    try { return fs.existsSync(abs) ? abs : null } catch { return null }
  }

  // The records directory for one feed. Kept per-feed so a feed's bytes are never confused with
  // another's, while the OVERLAY is shared — dedup and serving are node-wide, ownership is not.
  _recordsDir (name) { return join(this.storage, 'records', name) }

  async ready () {
    fs.mkdirSync(this.storage, { recursive: true })
    this.overlay = this._injectedOverlay || new HyperOverlay(this.corestore.namespace('overlay'), {
      namespace: 'favela-records',
      destDir: join(this.storage, '.fetch')
    })
    await this.overlay.ready()
    await this._seedOverlay()

    // My own feed. Opened first and by a stable namespace so it survives a restart.
    //
    // ⛔ With a stored feed key this node JOINS that feed instead of founding one — it is somebody's
    // second device. Without one it founds its own, which is the first device of an identity. The
    // key is written only by pairing, so the two cases never have to be guessed at.
    const joining = loadFeedKey(this.storage)
    const mine = await this._open('self', joining ? b4a.from(joining, 'hex') : null)

    // ⛔ The daemon does NOT touch the vault at boot. It looks for a device credential — a proof
    // for this feed's writer key — and if there is none it starts anyway, unadmitted and not
    // writable, so that `favela link` has something to talk to. A daemon that crashed without a
    // credential would leave no way to give it one.
    // The credential is a DEVICE keypair; the writer core key is bound to it here, at boot, because
    // the writer key is only known once the feed is open.
    const cred = loadCredential(this.storage)
    if (cred) {
      await mine.addDevice(bindWriter(cred, mine.deviceKey))
      await mine.update()
    }
    // ⛔ Kept, because writing to the feed is not the only thing this device's identity is for: a
    // name CLAIM is signed by the same credential, in a different log. Null on an unlinked node,
    // and `claim` refuses loudly rather than appending something every peer would silently drop.
    this.cred = cred
    this.peer = await new Peer(mine).ready()
    this.browsers = await new Browsers(this.corestore).ready()
    this.avatars = new Avatars(this.storage, this.overlay)
    // Files embedded in records — the same overlay, a different directory and a wider type table.
    this.files = new Files(this.storage, this.overlay)

    // The namespace this node resolves handles in.
    //
    // ⛔ The key is CONFIGURATION, stored on disk and replaceable. NAMING.md §8 #2: a default that
    // cannot be changed IS a root. Generating one on first boot makes this node the origin of ITS
    // default and nobody else's — several namespaces coexist, and joining a different one is a
    // runtime act (`joinNamespace`) rather than a file edit and a restart.
    //
    // ⚠ So "global" here means "the one you happen to have joined". Two nodes that never exchange
    // this key resolve handles in different worlds, correctly.
    await this._openNames(loadNamespace(this.storage))
    // ⛔ Founded HERE, at creation, because the op is refused once an admin exists and the only
    // thing closing that race is nobody else holding the key yet. An unlinked node cannot sign, so
    // it founds nothing and `link()` does it instead — a daemon must still start without a
    // credential or `favela link` would have nothing to talk to.
    if (cred) await this._foundNames(cred)

    // Re-open the rooms this node is in, for the same reason as the feeds below: a restart that
    // forgot them would look exactly like having been removed from every one of them.
    for (const r of loadRooms(this.storage)) {
      try { await this._openRoom(r.key, r.slot) } catch {}
    }

    // ⛔ HOLD THE BYTES OF FEEDS WE FOLLOW, before anyone asks to read them.
    //
    // MEASURED 2026-09-08: this node held five pointers from ashatron and could read none of them.
    // ashatron is a laptop, it went offline, and it was the only machine with the bytes — because a
    // record's bytes are fetched only when a reader asks, and nobody had. All five resolved the
    // moment it came back.
    //
    // Following is the decision to KEEP someone's bytes; peeking is not, which is why this reads
    // `following()` rather than every readable feed. `peer.timeline` draws the same line for the
    // same reason. See hyper-social/docs/HOLDING-AND-TELLING.md.
    this._mirror = createMirror({
      // ⛔ LEVEL 2. A record's bytes are one oid in the log; the files it NAMES are more oids inside
      // its markdown, in the SAME overlay. Without this a mirrored post reads fine and its picture
      // will not load — the outage this exists for, one level down.
      files: this.files,
      feeds: () => {
        const out = []
        for (const [name, feed] of this.peer.feeds) {
          if (this.peer.peeked.has(name) || this.peer._blocked.has(name)) continue
          out.push({ key: name, feed })
        }
        return out
      },
      // ⚠ No logger on this class, and a mirror pass must not invent one. Failures surface where
      // they already do: a record whose bytes did not arrive stays UNRESOLVED and the surface says so.
    })
    // ⛔ CREATED BEFORE THE FEEDS ARE HELD, so `hold()` can attach a watcher to each as it opens.
    // Re-open the feeds we already follow, so a restart resumes rather than forgetting.
    for (const name of await this.peer.following()) {
      try { await this.hold(name) } catch {}
    }

    // ⚠ NOT awaited, and NOT sufficient on its own. A first sweep must not sit inside boot. And an
    // end-to-end run on two machines showed the boot pass alone finds nothing: at boot a followed
    // feed's log has usually not replicated yet, so the real work is done by the 'update' watcher
    // attached in hold().
    this._mirror.pass().catch(() => {})
    // ⛔ AND A FLOOR UNDER IT. 0.0.40's trigger subscribed to an autobee event that only fires on
    // an interrupt, so it never fired at all — and nothing said so. A slow local sweep cannot be
    // wrong in that way: it reads a bee and stats files, and touches the network only when a record
    // is genuinely missing.
    // ⛔ A PEER ARRIVING IS AN EVENT. A record is unresolved because nobody holding its bytes is
    // online; their return is a swarm 'connection', not an absence, so it must not be discovered by
    // a clock. This is the trigger the timer was standing in for.
    if (this.swarm && typeof this.swarm.on === 'function') {
      this._onMirrorPeer = () => this._mirror.onPeer()
      this.swarm.on('connection', this._onMirrorPeer)
    }
    return this
  }

  get key () { return this.peer.feed.key }

  /**
   * Announce everything `ready()` opened.
   *
   * ⛔ `ready()` builds the overlay, and the transport needs the overlay — so the transport does not
   * exist while ready() is opening things, and every `transport.join` inside it is a no-op. Three
   * separate things have now had to be re-joined by hand afterwards (the feeds, the namespace, and
   * rooms) and each was forgotten in turn, each time producing a log that replicates for nobody.
   *
   * One method, so the next thing ready() opens is announced by construction rather than by
   * somebody remembering.
   */
  async announceAll () {
    if (!this.transport) return 0
    let n = 0
    const join = async (base) => { if (base) { try { await this.transport.join(base); n++ } catch {} } }
    await join(this.peer && this.peer.feed)
    await join(this.names)
    for (const [, feed] of this._feeds) await join(feed)
    for (const [, room] of this._rooms) await join(room)
    return n
  }

  /**
   * Open a namespace into `this.names` and announce it.
   *
   * ⚠ `stored` is `{ key, slot }` or null. A null mints one, in the legacy `names` slot — the key
   * cannot name its own slot because it does not exist until the log is open.
   */
  async _openNames (stored) {
    this.names = await new Namespace(
      this.corestore.namespace(stored ? stored.slot : 'names'),
      stored ? b4a.from(stored.key, 'hex') : null
    ).ready()
    if (!stored) saveNamespace(this.storage, { key: hex(this.names.key), slot: 'names' })
    // ⛔ Announced, or a shared registry is three private ones. This is a no-op at boot — the
    // transport is built AFTER ready() so the overlay exists first — which is why `index.js` joins
    // it explicitly, exactly as it already does for the feeds.
    if (this.transport) { try { await this.transport.join(this.names) } catch {} }
    this._invitedWatch = null
    this._watchInvited()
  }

  /**
   * Answer vouched-for asks as they arrive, without a human.
   *
   * ⛔ Re-entrancy guarded: answering APPENDS, which fires `update` again. Without the flag the
   * first invited request would recurse until the stack gave out.
   */
  _watchInvited () {
    if (!this.names || this._invitedWatch) return
    this._invitedWatch = async () => {
      if (this._answering || !this.cred || !this.names) return
      this._answering = true
      try { await this.names.answerInvited(this.cred) } catch {} finally { this._answering = false }
    }
    this.names.base.on('update', this._invitedWatch)
  }

  /** Found this namespace if it is ours to found. Idempotent: apply refuses a second founding. */
  async _foundNames (cred) {
    if (!this.names || !cred) return
    await this.names.update()
    if (await this.names.founder()) return
    try { await this.names.found(cred) } catch { return }
    await this.names.update()
  }

  /**
   * Resolve handles in a different namespace from now on.
   *
   * ⛔ The one we leave stays on disk, in its own Corestore slot, and coming back finds it exactly
   * as it was. Joining somebody else's registry must never be a one-way door that destroys the one
   * you made.
   */
  async joinNamespace (keyHex) {
    if (typeof keyHex !== 'string' || !/^[0-9a-f]{64}$/i.test(keyHex)) {
      throw new Error('a namespace key is 32 bytes of hex — ' + String(keyHex).slice(0, 16))
    }
    const key = keyHex.toLowerCase()
    if (this.names && hex(this.names.key) === key) return { key, joined: false }

    const current = loadNamespace(this.storage)
    const known = loadNamespaces(this.storage)
    // ⚠ Back to one we already hold: reuse ITS slot. Opening the same log in a second slot would
    // give this node two writer cores in one namespace — not fatal, and permanently confusing.
    //
    // ⛔ AND THE INDEX IS CONSULTED, not just the current one. The namespace this node FOUNDED lives
    // in the legacy slot `names`, which `namespaceSlot(key)` cannot produce — so leaving it and
    // coming back later computed `names:<key>`, opened a SECOND log, and left every handle claimed
    // there resolving from a slot this node was no longer using. The claims were never lost; they
    // were simply somewhere else, which is worse because it looks like loss.
    const slot = slotFor(key, { current, known })

    if (this.names) {
      if (this.transport) { try { await this.transport.leave(this.names) } catch {} }
      await this.names.close()
      this.names = null
    }
    await this._openNames({ key, slot })
    saveNamespace(this.storage, { key, slot })
    // Remembered so it can be returned to by name rather than by a key someone has to have kept.
    saveNamespaces(this.storage, [...known.filter((n) => n.key !== key), { key, slot }])
    return { key, joined: true }
  }

  async _open (namespace, key, recordsName = namespace, bootHead = null) {
    const feed = new Feed(this.corestore.namespace(namespace), key, {
      // `base` is what makes an overlay path resolvable back to a file at boot — see
      // `_seedOverlay` and `RecordStore._overlayPath`.
      store: new RecordStore(this._recordsDir(recordsName), { overlay: this.overlay, base: this.storage }),
      optimistic: this._optimistic,
      bootHead // see Feed's constructor: without one, fast-forward is off rather than stalling
    })
    await feed.ready()
    // ⛔ Guarded, like every other transport call in this file. It was the one that was not, so a
    // swarm that refused a join threw AFTER the feed was open — leaving it draining, untracked and
    // never closed, which then blew up as "RocksDB session is closed" when the store shut down.
    //
    // ⚠ A feed with no join is still a usable feed. It simply does not replicate, which is a
    // degraded state and not a failure to open.
    if (this.transport) { try { await this.transport.join(feed) } catch {} }
    // ⛔ Repair what the boot seed could not: entries written under the older overlay-path shape
    // resolve to nothing and stay unservable for ever. ⚠ NOT awaited — it hashes every record file,
    // and making a feed open wait on that is the cold-start cost the overlay keeps off its own boot
    // path for the same reason.
    if (feed.store && typeof feed.store.reregisterLocal === 'function') {
      feed.store.reregisterLocal().catch(() => {})
    }
    return feed
  }

  // Hold someone else's feed: open it read-only under its own namespace and hand it to the Peer.
  /**
   * Resolve a handle all the way — name → identity root → feed — and say how far it got.
   *
   * ⛔ NAMING.md §3 resolves a name to an identity ROOT and only then to a room key, and the two
   * halves live in different places on purpose. `Namespace.resolve` proves WHO made the claim, from
   * the claim's own bytes: apply can do that with no state at all. What it cannot do is prove that
   * identity owns the feed the claim points at — the namespace is a separate log with no access to
   * a feed's `__founder__` — so a stranger may still aim a name at someone else's feed under their
   * OWN root. That lie is only detectable HERE, where the feed is actually held.
   *
   * Four verdicts, and the difference between the middle two is the whole point:
   *
   *   confirmed    the feed's founder IS the claim's anchor — the handle means what it says
   *   MISMATCH     the feed is founded by someone else. Refuse it. This is the attack.
   *   unheld       we do not hold the feed, so there is nothing to check against yet
   *   unfounded    the feed is held but has no founder — an empty feed names nobody
   *
   * ⚠ `unheld` deliberately does NOT go and fetch the feed. Holding someone's feed is a decision
   * with storage and replication behind it, and making a name lookup take it silently would turn
   * reading a byline into following a stranger. NAMING.md §3.2: fail visibly, never guess.
   */
  /**
   * A bare NAME to the one person who holds it, or null.
   *
   * ⛔ Only when there is exactly ONE confirmed claimant. NAMING.md §3.2: an unresolvable name must
   * fail visibly rather than have a namespace guessed for it, and "the first alice" is that guess
   * wearing a helpful face. Two alices means `@alice` names nobody, and it stays plain text.
   */
  async resolveName (name) {
    await this.names.update()
    let candidates = []
    try { candidates = await this.names.candidates(name) } catch { return null }
    const confirmed = []
    for (const c of candidates) {
      const judged = await this._judge(c)
      if (judged.verdict === 'confirmed') confirmed.push(judged)
    }
    return confirmed.length === 1 ? confirmed[0] : null
  }

  /** A handle OR a bare name — what a person actually types after an @. */
  async resolveMention (token) {
    return (await this.resolveHandle(token)) || this.resolveName(token)
  }

  async resolveHandle (handle) {
    await this.names.update()
    const claim = await this.names.resolve(handle)
    if (!claim) return null
    return this._judge(claim)
  }

  /**
   * Every handle in the namespace, each carrying its verdict. `query` narrows it to a search.
   *
   * ⛔ The verdict is attached HERE rather than by each caller, because every caller that forgets
   * it renders a stranger's claim as somebody's name. There is exactly one place that decides.
   */
  async handles (query = null) {
    await this.names.update()
    const claims = query ? await this.names.search(query) : await this.names.list()
    const out = []
    for (const c of claims) out.push(await this._judge(c))
    return out
  }

  /**
   * Confirmed handles only, as `feed -> handle`. What a byline may safely use.
   *
   * ⛔ A claim points at a feed; it does not prove the feed agrees. So an UNCONFIRMED claim must
   * never reach a byline — rendering one would let anyone rename anyone by claiming a name at their
   * feed key, which is the whole attack, arriving through the display layer instead of the log.
   *
   * ⚠ `unheld` is excluded too, not just MISMATCH. We cannot check a feed we do not hold, and
   * "unchecked" is not "fine" — that conflation is how a check becomes decorative.
   */
  async confirmedHandles () {
    const out = {}
    for (const c of await this.handles()) if (c.verdict === 'confirmed') out[c.feed] = c.handle
    return out
  }

  /**
   * Attach a verdict to one claim: does the feed it points at agree?
   *
   * ⚠ Does NOT fetch a feed it does not already hold. Holding someone's feed is a decision with
   * storage and replication behind it, and making a name lookup take it silently would turn reading
   * a byline into following a stranger. NAMING.md §3.2: fail visibly, never guess.
   */
  async _judge (claim) {
    const feed = this._feeds.get(claim.feed) ||
      (claim.feed === hex(this.key) ? this.peer.feed : null)
    if (!feed) return { ...claim, verdict: 'unheld' }
    return handleVerdict(claim, await feed.founder())
  }

  // ── rooms ─────────────────────────────────────────────────────────────────────────────────────

  async _openRoom (keyHex, slot) {
    const key = String(keyHex).toLowerCase()
    if (this._rooms.has(key)) return this._rooms.get(key)
    const room = await new Room(this.corestore.namespace(slot), b4a.from(key, 'hex')).ready()
    this._rooms.set(key, room)
    // ⛔ Announced, or it is a private log wearing the name of a shared one — the exact defect the
    // namespace had until today.
    if (this.transport) { try { await this.transport.join(room) } catch {} }
    return room
  }

  _persistRooms () {
    saveRooms(this.storage, [...this._rooms.keys()].map((key) => ({ key, slot: 'room:' + key })))
  }

  /** Make a room. This node's identity founds it, so it is the room's first admin. */
  async createRoom () {
    if (!this.cred) throw new Error('creating a room needs this device\'s credential — run `favela link`')
    // ⚠ The key is not known until the log is open, so the slot cannot be named after it. A fresh
    // slot per creation keeps each room's writer its own.
    const slot = 'room:new:' + Date.now().toString(16) + Math.random().toString(16).slice(2, 8)
    const room = await new Room(this.corestore.namespace(slot), null).ready()
    await room.found(this.cred)
    await room.update()
    const key = hex(room.key)
    this._rooms.set(key, room)
    if (this.transport) { try { await this.transport.join(room) } catch {} }
    saveRooms(this.storage, [...loadRooms(this.storage).filter((r) => r.key !== key), { key, slot }])
    return { key }
  }

  /**
   * Join a room by key.
   *
   * ⚠ Joined is not admitted. Holding the key lets this node replicate and read; becoming a MEMBER
   * is a decision somebody already inside makes, and it has to be, because a grant is capped at the
   * weight of the block carrying it.
   */
  async joinRoom (keyHex) {
    if (typeof keyHex !== 'string' || !/^[0-9a-f]{64}$/i.test(keyHex)) {
      throw new Error('a room key is 32 bytes of hex')
    }
    const key = keyHex.toLowerCase()
    if (this._rooms.has(key)) return { key, joined: false }
    await this._openRoom(key, 'room:' + key)
    saveRooms(this.storage, [...loadRooms(this.storage), { key, slot: 'room:' + key }])
    return { key, joined: true }
  }

  /** Stop replicating a room. ⚠ It does not delete one anybody else is in, and it cannot. */
  async leaveRoom (keyHex) {
    const key = String(keyHex).toLowerCase()
    const room = this._rooms.get(key)
    if (!room) return { key, left: false }
    if (this.transport) { try { await this.transport.leave(room) } catch {} }
    await room.close()
    this._rooms.delete(key)
    saveRooms(this.storage, loadRooms(this.storage).filter((r) => r.key !== key))
    return { key, left: true }
  }

  room (keyHex) { return this._rooms.get(String(keyHex).toLowerCase()) || null }

  /** This node's identity root, or null when it cannot sign. What a room asks about membership. */
  rootHex () {
    if (!this.cred) return null
    try { return readAttested(this.cred.proof, undefined).root } catch { return null }
  }

  async rooms () {
    const out = []
    for (const [key, room] of this._rooms) {
      await room.update()
      out.push({ key, config: await room.config(), members: (await room.members()).length })
    }
    return out
  }

  async hold (feedKey, bootHead = null) {
    const id = hex(feedKey)
    if (this._feeds.has(id)) return this._feeds.get(id)
    const feed = await this._open('feed:' + id, b4a.from(id, 'hex'), id, bootHead)
    this._feeds.set(id, feed)
    // Mirror this feed's bytes as its log advances — the trigger the boot pass cannot be.
    if (this._mirror) this._mirror.watch(feed.base)
    this.peer.feeds.set(id, feed)
    return feed
  }

  /**
   * Hold a feed to READ it — no follow record, and nothing written to theirs.
   *
   * ⛔ An explicit act, never a side effect of a lookup. `resolveHandle` deliberately refuses to
   * fetch a feed it does not hold, for exactly this reason: holding is a storage and replication
   * commitment, and taking it silently would turn reading a byline into following a stranger.
   *
   * ⚠ It does not survive a restart. `ready()` re-opens only what `following()` names, so a peek
   * lasts for the life of the daemon and then evaporates — which is the right default and costs no
   * new state. The BYTES stay on disk until something collects them; that is a separate question.
   */
  async peek (feedKey) {
    const id = hex(feedKey)
    if (this._feeds.has(id) || id === hex(this.key)) return { feed: id, status: 'held' }
    if (!this._opening.has(id)) {
      // ⛔ NOT awaited. Opening a feed we have never seen means an autobee on a remote key and a
      // swarm join, and that is a DHT round trip — inside the request it is indistinguishable from
      // a hung daemon. `pairOffer` learned the same thing and answers first too.
      //
      // ⚠ The failure path clears the entry. A peek that threw and stayed in this map would leave
      // the page saying "fetching…" for ever with no way to retry.
      const work = (async () => {
        const feed = await this.hold(feedKey)
        this.peer.peek(feedKey, feed)
      })().catch(() => {}).finally(() => this._opening.delete(id))
      this._opening.set(id, work)
    }
    return { feed: id, status: 'opening' }
  }

  /** The in-flight open for a feed, if there is one. Awaiting it is optional and never required. */
  opening (feedKey) { return this._opening.get(hex(feedKey)) || Promise.resolve() }

  /**
   * What this node has of a feed — synchronous, because a page asks it on every render.
   *
   * ⛔ Four states, not two. "Not held" and "still arriving" look identical to a reader and are
   * completely different facts: one means offer to look, the other means wait. Collapsing them is
   * how a page ends up claiming somebody posted nothing when their feed simply has not landed.
   */
  holding (feedKey) {
    const id = hex(feedKey)
    return {
      held: this._feeds.has(id) || id === hex(this.key),
      peeked: this.peer.peeked.has(id),
      following: this.peer._follows ? this.peer._follows.has(id) : false,
      opening: this._opening.has(id),
      blocked: this.peer._blocked ? this.peer._blocked.has(id) : false
    }
  }

  // Follow = record it AND hold it. The two are separable in Peer on purpose; here they travel
  // together, because a node that recorded a follow and did not open the feed would silently show
  // an empty timeline.
  async follow (feedKey) {
    const feed = await this.hold(feedKey)
    await this.peer.follow(feedKey, feed)
    await this.peer.update()
    return hex(feedKey)
  }

  async unfollow (feedKey) {
    const id = hex(feedKey)
    await this.peer.unfollow(id)
    const feed = this._feeds.get(id)
    if (feed) {
      if (this.transport) { try { await this.transport.leave(feed) } catch {} }
      this._feeds.delete(id)
      await feed.close()
    }
    await this.peer.update()
  }

  /**
   * Link this device: record the proof and admit ourselves.
   *
   * Idempotent — admission is a no-op once the roster holds this device, and re-admitting after a
   * revocation is refused by apply rather than here. The proof is stored first so a restart keeps
   * the link even if the append does not land.
   */
  async link (cred) {
    saveCredential(this.storage, cred)
    await this.peer.feed.addDevice(bindWriter(cred, this.peer.feed.deviceKey))
    await this.peer.update()
    // ⛔ Kept in step: a node that booted unlinked has an unfounded namespace, and this is the first
    // moment it can sign anything. Leaving it admin-less would mean nobody could ever be admitted
    // to carry it.
    this.cred = cred
    await this._foundNames(cred)
    return { device: this.peer.feed.deviceKey, devices: await this.peer.feed.devices() }
  }

  /**
   * Offer this device's credential to another machine.
   *
   * ⛔ Mints a NEW credential from ours — the vault is not opened and the root does not travel. The
   * invite is returned at once and the wait continues in the background, because a socket request
   * that blocked for five minutes would look like a hung daemon.
   */
  async pairOffer (opts = {}) {
    if (this._pairingState && this._pairingState.status === 'waiting') {
      throw new Error('an invite is already outstanding — `favela pair --cancel` first')
    }
    const cred = loadCredential(this.storage)
    if (!cred) throw new Error('this device is not linked, so it has nothing to pass on — run `favela link`')

    this._pairing = new Pairing()
    const { invite, joined } = await this._pairing.offer(cred, { ...opts, feedKey: hex(this.key) })
    this._pairingState = { status: 'waiting', invite }

    joined
      .then((r) => { this._pairingState = { status: 'linked', devicePublicKey: r.devicePublicKey } })
      .catch((err) => { this._pairingState = { status: 'failed', error: err.message } })
      // ⛔ close() DRAINS before destroying — see pairing.js. Closing eagerly here would cut the
      // frame that carries the credential and the other side would time out believing nothing
      // happened, while this side reported success.
      .finally(async () => { try { await this._pairing.close() } catch {}; this._pairing = null })

    return { invite }
  }

  pairStatus () { return this._pairingState || { status: 'idle' } }

  async pairCancel () {
    if (this._pairing) { try { await this._pairing.close() } catch {}; this._pairing = null }
    this._pairingState = { status: 'cancelled' }
    return this._pairingState
  }

  /**
   * Take an invite from another machine and link this one with what arrives.
   *
   * ⛔ Pairing happens AT INIT — a phone joining an identity it has never written under. So the
   * normal case is adopting the offered feed, and the guard exists for the case that is not init:
   * a node that has already founded its own feed and written to it would lose that log.
   *
   * The check is for RECORDS, not for a founder. An unlinked node has an empty unfounded feed
   * (opening one is not founding it), so requiring "no founder" would refuse every legitimate
   * pairing; requiring "nothing written" refuses exactly the case that would lose something.
   */
  async pairAccept (invite) {
    const p = new Pairing()
    try {
      const cred = await p.accept(invite)

      if (cred.feedKey && cred.feedKey !== hex(this.key)) {
        const refusal = adoptionRefusal((await this.peer.feed.list()).length, cred.feedKey, hex(this.key))
        if (refusal) throw new Error(refusal)
        saveFeedKey(this.storage, cred.feedKey)
        saveCredential(this.storage, cred)
        // ⚠ The feed is opened at boot, so adopting one means reopening. Reported rather than done
        // in place: swapping the log under a live Peer is the kind of half-applied state this
        // codebase refuses everywhere else.
        return { adopted: cred.feedKey, restart: true, label: cred.label }
      }

      // The same `link` the vault path uses. Nothing downstream is pairing-specific.
      return { ...(await this.link(cred)), label: cred.label }
    } finally {
      try { await p.close() } catch {}
    }
  }

  async close () {
    // Stop the mirror before anything it touches is torn down — a pass mid-fetch holding a
    // corestore session while the store closes throws into nobody's hands.
    if (this._mirror) this._mirror.stop()
    if (this._onMirrorPeer && this.swarm) { try { this.swarm.off('connection', this._onMirrorPeer) } catch {} }
    // ⚠ Settle in-flight peeks FIRST. A background open still holding a corestore session while
    // this tears the store down throws "RocksDB session is closed" into nobody's hands — the open
    // has no caller left to reject to. In production these settle promptly: `hold()` awaits a feed
    // open and a swarm join, and the join no longer waits on the DHT.
    if (this._opening.size) { try { await Promise.allSettled([...this._opening.values()]) } catch {} }
    if (this._pairing) { try { await this._pairing.close() } catch {}; this._pairing = null }
    if (this.names) { try { await this.names.close() } catch {} }
    if (this.browsers) { try { await this.browsers.close() } catch {} }
    if (this.transport) { try { await this.transport.destroy() } catch {} }
    for (const [, room] of this._rooms) { try { await room.close() } catch {} }
    for (const [, feed] of this._feeds) { try { await feed.close() } catch {} }
    if (this.peer) { try { await this.peer.feed.close() } catch {} }
    if (this.overlay && this.overlay.close) { try { await this.overlay.close() } catch {} }
    // ⛔ LAST, and it was missing. Closing every session above does not release the store's RocksDB
    // lock, so a node that closed cleanly still owned its storage directory: reopening it — a
    // restart, or a test that boots twice — failed with "File descriptor could not be locked".
    if (this.corestore) { try { await this.corestore.close() } catch {} }
  }
}
// Device pairing — moving a device credential to a second machine, over blind-pairing.
//
// ── What actually travels ─────────────────────────────────────────────────────────────────────
//
// A freshly minted device credential: `{ keyPair, proof }`, where the source's own device key
// attested the new one. NEVER the mnemonic.
//
//     v1 of this idea shipped the BIP39 mnemonic → a compromised target leaked the root
//     v3 ships a device-scoped keypair + chain → a compromised target has only its own device key
//
// That is pear-git-v3's `lib/device-linker.js` verbatim, and it is why linking a machine does not
// open the vault: `attestDevice(publicKey, parent, proof)` signs with the PARENT DEVICE's keypair.
//
// ── What the target does with it ──────────────────────────────────────────────────────────────
//
// Exactly what `favela link` does with a vault-minted credential: save it, open the feed, bind its
// own writer core to the device with `attestData`, and admit itself. Nothing about the receiving
// path is pairing-specific — the credential is the credential, however it arrived.
//
// ⚠ So the target needs NO pre-computation and no second round trip. pgv3 pre-computes the target's
// writer key (`Hypercore.key({version:1, signers:[{publicKey}]})` — measured, that is exactly what
// autobee derives when handed a keyPair) so it can ADD_WRITER before the target boots. We do not
// need that: our target binds its own writer key locally, because binding takes the device key, not
// the root.
//
// ── ⛔ The trap ───────────────────────────────────────────────────────────────────────────────
//
// `close()` MUST drain the swarm connections before destroying them. blind-pairing enqueues the
// confirm frame — the one carrying the bundle — AFTER the source's `onadd` handler returns, but the
// source's "peer connected" promise resolves inside that handler. Tear the stream down on the next
// line and the frame never transmits: the candidate waits out its whole timeout believing nothing
// happened, while the source reports success. One-sided, silent, and slow to diagnose. pgv3 hit
// this and wrote it down; the drain below is that fix.

import Hyperswarm from 'hyperswarm'
import BlindPairing from 'blind-pairing'
import crypto from 'hypercore-crypto'
import b4a from 'b4a'
import { mintForDevice } from './device.js'

const PAIRING_TIMEOUT_MS = 5 * 60 * 1000

export class Pairing {
  constructor (opts = {}) {
    this._externalSwarm = opts.swarm || null
    this._swarm = null
    this._pairing = null
    this._timeout = null
  }

  /**
   * Offer a device credential to whoever redeems the invite (source side).
   *
   * `cred` is THIS machine's credential; the new one is minted from it, so the mnemonic is never
   * touched and the root never leaves.
   *
   * Returns `{ invite, joined }` — `invite` is the hex string a human carries to the other machine,
   * `joined` resolves when a peer has taken it.
   */
  async offer (cred, opts = {}) {
    if (!cred || !cred.keyPair || !cred.proof) {
      throw new Error('offer requires this device\'s credential { keyPair, proof } — link this machine first')
    }

    const target = mintForDevice(cred)
    const bundle = b4a.from(JSON.stringify({
      publicKey: b4a.toString(target.keyPair.publicKey, 'hex'),
      secretKey: b4a.toString(target.keyPair.secretKey, 'hex'),
      proof: target.proof,
      // ⛔ The feed key travels too. Being someone's device and writing to their feed are different
      // facts: the chain proves the first, and only this carries the second. Without it the target
      // founds a feed of its own — same owner, different log, which is not what "my other phone"
      // means.
      feedKey: opts.feedKey || null,
      label: opts.label || 'linked-device'
    }), 'utf-8')

    const pairingKey = crypto.keyPair()
    const { invite, publicKey: invitePublicKey, discoveryKey, seed } = BlindPairing.createInvite(pairingKey.publicKey)
    const inviteKeyPair = crypto.keyPair(seed)

    this._swarm = this._externalSwarm || new Hyperswarm(opts.swarmOpts || {})
    this._pairing = new BlindPairing(this._swarm)

    const joined = new Promise((resolve, reject) => {
      this._timeout = setTimeout(() => reject(new Error('nobody took the invite')), opts.timeoutMs || PAIRING_TIMEOUT_MS)
      this._pairing.addMember({
        discoveryKey,
        onadd: (request) => {
          clearTimeout(this._timeout)
          this._timeout = null
          try {
            request.open(invitePublicKey)
            // The bundle rides the confirm as `additional.data`, signed with the invite keypair so
            // blind-pairing can verify it came from whoever minted the invite.
            request.confirm({
              key: pairingKey.publicKey,
              encryptionKey: null,
              additional: { data: bundle, signature: crypto.sign(bundle, inviteKeyPair.secretKey) }
            })
            resolve({ devicePublicKey: b4a.toString(target.keyPair.publicKey, 'hex') })
          } catch (err) {
            reject(err)
          }
        }
      })
    })

    return { invite: b4a.toString(invite, 'hex'), joined }
  }

  /**
   * Redeem an invite and come back with a device credential (target side).
   *
   * Returns `{ keyPair, proof }` in the same shape `loadCredential` produces, so the caller hands
   * it straight to `node.link()` — the receiving path is not pairing-specific.
   */
  async accept (inviteHex, opts = {}) {
    if (typeof inviteHex !== 'string' || !inviteHex) throw new Error('accept requires an invite')

    let inviteBuf
    try { inviteBuf = b4a.from(inviteHex.trim(), 'hex') } catch { throw new Error('malformed invite') }
    if (!inviteBuf.length) throw new Error('malformed invite')

    const hello = crypto.keyPair()
    this._swarm = this._externalSwarm || new Hyperswarm(opts.swarmOpts || {})
    this._pairing = new BlindPairing(this._swarm)

    const bundle = await new Promise((resolve, reject) => {
      this._timeout = setTimeout(() => reject(new Error('the other device did not answer')), opts.timeoutMs || PAIRING_TIMEOUT_MS)
      this._pairing.addCandidate({
        invite: inviteBuf,
        userData: hello.publicKey,
        onadd: (auth) => {
          clearTimeout(this._timeout)
          this._timeout = null
          try {
            if (!auth.data || auth.data.length === 0) return reject(new Error('the invite carried no credential'))
            resolve(JSON.parse(b4a.toString(auth.data, 'utf-8')))
          } catch (err) {
            reject(err)
          }
        }
      })
    })

    if (!bundle.publicKey || !bundle.secretKey || !bundle.proof) {
      throw new Error('the credential that arrived is incomplete')
    }

    return {
      keyPair: {
        publicKey: b4a.from(bundle.publicKey, 'hex'),
        secretKey: b4a.from(bundle.secretKey, 'hex')
      },
      proof: bundle.proof,
      feedKey: bundle.feedKey || null,
      label: bundle.label || null
    }
  }

  /**
   * ⛔ Drain BEFORE destroy. See the header — without this the confirm frame carrying the bundle
   * never leaves, and the failure is one-sided and silent.
   */
  async close () {
    if (this._timeout) { clearTimeout(this._timeout); this._timeout = null }
    if (this._swarm) {
      const conns = (this._externalSwarm || this._swarm).connections
      const flushes = []
      for (const conn of conns) if (typeof conn.flush === 'function') flushes.push(conn.flush())
      await Promise.all(flushes).catch(() => {})
    }
    if (this._pairing) { try { await this._pairing.close() } catch {}; this._pairing = null }
    if (this._swarm && !this._externalSwarm) { try { await this._swarm.destroy() } catch {} }
    this._swarm = null
  }
}

export default Pairing
// Peer — your feed, the feeds you hold, and the timeline you compute from them.
//
// ── Following is a record, not a subscription ──────────────────────────────────────────────────
//
// To follow someone you write a record in YOUR OWN feed naming their feed key. Nothing is written
// to theirs, and they need not be online, consent, or ever know. That is the same rule every
// interaction obeys here, and it is what keeps a feed's writer set at its owner's devices no matter
// how many people follow it.
//
// ── The timeline is a projection, and it is LOCAL ──────────────────────────────────────────────
//
// There is no relay and no aggregator. The timeline is a pure function of the feeds this node
// happens to hold, so two peers legitimately compute different timelines from the same follow list.
// A feed you follow but have not replicated contributes NOTHING — which is correct, and is why
// `missing()` exists: a gap you can name is a different thing from a gap that is silently empty.
//
// ⚠ No networking. Feeds are handed in. There is no transport seam yet, and adding one should not
// change anything in this file.

import b4a from 'b4a'
import crypto from 'hypercore-crypto'
// ⛔ The codec, not a regex. A mention is a markdown LINK with a `favela://` target, and deciding
// that from the source text means re-implementing link parsing — including the cases where a `@`
// inside code fences or a link label is not a mention at all.
import { strip } from 'favela-markdown/lib/strip.js'
import { NETWORKS } from 'favela-markdown/lib/reach.js'

// Collections. Bare names, deliberately: reverse-DNS is atproto's convention and this stack is not
// atproto's. A third party's collection would be namespaced; ours are not.
export const POST = 'post'
export const FOLLOW = 'follow'
export const LIKE = 'like'
// ⛔ REACTION is the primitive and `like` is a reserved VALUE of it — keet's shape, and the only
// one of the three networks that gets like for free rather than bolting reactions beside it
// (`keet-core/lib/core/contract/reactions.js`).
//
// ⛔ `like` is the string, never an emoji. A bridge must be able to name the one reaction both
// targets carry without guessing, and if the value were 👍 then "did they like it or react with a
// thumb" would be undecidable in both directions. The narrower target carries no other reaction, so
// every other value is a stated `loss[]` where that applies — which the BRIDGE states, not us.
export const REACTION = 'reaction'
// ⛔ A BODYLESS edge, per `interaction.md` §5.2: "if it has bytes it gets a file; if it does not,
// the log entry is the whole thing". Same class as like and follow, and deliberately NOT the same
// as reply, which is "the edge that is also a document". It is one of the five verbs the spec calls
// consensus-critical (§5.4) — both bridge targets carry it, under their own names, which is the
// bridges' business and not this file's.
//
// ⛔ And not the same as a QUOTE. `record-vocabulary.md` §3.5: "quotes are just links" — a quote is
// an ordinary post whose body references the quoted record, so it has a body and its own text. A
// repost adds nothing of its own, which is exactly what separates the two everywhere.
export const REPOST = 'repost'
// ⛔ A bodyless edge naming an ACTOR, like follow — `interaction.md` §5.2 and §5.4.
//
// ⛔ What it can and cannot do has to be said plainly, because the honest answer is weaker than
// people expect. A feed is public and replication is the READER's to perform, so a block cannot
// stop someone reading you, holding your feed, or writing about you. It is a decision about what
// YOU render, published as a signal — `SOCIAL-MODEL.md` §6.7's "signals, not enforcement", one
// level down. Anything stronger would be a promise this substrate cannot keep.
export const BLOCK = 'block'
export const LIKE_VALUE = 'like'
// A label, not a payload. Long enough for an emoji sequence with modifiers, short enough that the
// collection cannot become a message channel.
const MAX_VALUE = 64
export const REPLY = 'reply'

// The published profile. ONE record per feed, named by a constant — a feed is one identity, so
// there is nothing to enumerate.
//
// ⛔ Why this is a RECORD and not only a registry row. The registry is LOCAL to a node: it holds
// browser credentials and never replicates. So a profile kept only there is invisible to everyone
// who reads your feed — no name, no picture, nothing to search. Seeing who wrote something requires
// the profile to travel, and the feed is the only thing that travels.
//
// ⛔ What travels is a SELF-LABEL, and that is the whole justification. NAMING.md §2 forbids a name
// in a record because pgh's NameRegistry is first-claim-wins and a released name is RECLAIMABLE, so
// a claimed name in immutable bytes can later denote a different person. A self-label is claimed
// from nobody, unique to nobody, and reclaimable by nobody — it denotes only "what this feed's owner
// calls themselves". The resolvable handle is a different object and belongs in a HandleRegistry,
// beside this rather than instead of it.
//
// ⚠ Which is why the key is ALWAYS rendered next to it. A self-label is a claim, and two feeds may
// carry the same one.
export const PROFILE = 'profile'
// Which networks this identity publishes to. A PREFERENCE, in the feed, so it follows the identity
// to every device rather than living on whichever machine happened to set it.
export const REACH = 'reach'
// ⛔ The blob POINTER collection — record-vocabulary §3.5. A tiny record in the feed so every reader
// converges on which levels a blob has and the oid to fetch each by, while the bytes stay out of the
// log. Keyed by the BASE oid, which is what `favela://blob/<oid>` already names.
export const BLOB = 'blob'
export const SELF = 'self'

const hex = (k) => (typeof k === 'string' ? k : b4a.toString(k, 'hex'))

// Non-overlapping occurrences of `needle` in `haystack`. Both are already lower-cased by the caller;
// no normalisation happens here, deliberately — normalising would change what an author wrote for
// reasons invisible to them, which is the rule the strip already follows.
function countOf (haystack, needle) {
  let n = 0
  let i = haystack.indexOf(needle)
  while (i !== -1) {
    n++
    i = haystack.indexOf(needle, i + needle.length)
  }
  return n
}

export class Peer {
  // A feed key is 32 bytes of hex, which is a legal single path segment, so a follow record is named
  // by the key itself. No mapping table, and the name is a pure function of what it names.
  static nameFor (key) { return hex(key) }

  // A stable id for a target, used as the record NAME of a like.
  //
  // Deriving the name from the target is what makes liking idempotent: liking twice writes the same
  // key twice, which is one record, not two. keet reaches the same place from the other direction by
  // enforcing uniqueness of {id, sender, reaction} inside apply.
  //
  // It is a hash rather than the components joined, because a separator that cannot appear in a
  // collection or a name does not exist — and a joined string would silently collide for two
  // different targets. The components are stored in the record's frontmatter, so nothing needs to
  // read this backwards.
  /**
   * The record NAME of a reaction — derived from (target, value).
   *
   * ⛔ The value is IN the id, which is what lets one person hold 👍 and ❤️ on one target while
   * each stays idempotent. keet reaches the same place by enforcing uniqueness of
   * {message, sender, reaction} inside apply; deriving the key does it without a rule.
   */
  static reactionId (target, value) {
    const t = Peer.normalizeTarget(target)
    if (!t) throw new Error('Peer: a target needs { feed, collection, name }')
    return b4a.toString(crypto.data(
      b4a.from(JSON.stringify([t.feed, t.collection, t.name, String(value)]), 'utf8')), 'hex')
  }

  static normalizeValue (value) {
    const v = typeof value === 'string' ? value.trim() : ''
    if (!v || v.length > MAX_VALUE) return null
    // No control characters: a reaction is rendered next to a byline and must not carry newlines.
    return /[\u0000-\u001f\u007f]/.test(v) ? null : v
  }

  static targetId (target) {
    const t = Peer.normalizeTarget(target)
    if (!t) throw new Error('Peer: a target needs { feed, collection, name }')
    return b4a.toString(crypto.data(b4a.from(JSON.stringify([t.feed, t.collection, t.name]), 'utf8')), 'hex')
  }

  // ⛔ Components, never a URI string. The address form is open (hyper-social #61 §12 #1) and a URI
  // baked into a record's bytes is exactly what record-vocabulary §8 forbids rewriting. Components
  // serialise into whatever the scheme becomes; a URI does not un-serialise into components that
  // were never stored.
  static normalizeTarget (target) {
    if (!target || typeof target !== 'object') return null
    const feed = hex(target.feed)
    const { collection, name } = target
    if (typeof feed !== 'string' || !feed) return null
    if (typeof collection !== 'string' || !collection) return null
    if (typeof name !== 'string' || !name) return null
    return { feed, collection, name }
  }

  constructor (feed) {
    this.feed = feed
    this.feeds = new Map() // hex key -> Feed, the ones we actually hold
    // ⛔ Held to READ, not because I follow them. A peeked feed is readable — its page renders —
    // and is excluded from the timeline, because a stranger whose posts appear in your timeline is
    // someone you are following whether or not a record says so.
    this.peeked = new Set()
    // Feeds I have blocked. Cached like `_follows`, and consulted in ONE place — see `_readable`.
    this._blocked = new Set()
  }

  async ready () {
    await this.feed.ready?.()
    // What I follow, cached so `peek` can refuse to demote one. Rebuilt on `update()`, because the
    // follow set is a projection of my own log like everything else.
    this._follows = new Set(await this.following())
    this._blocked = await this.blocked()
    return this
  }

  // Record the follow, and hold the feed if one was supplied. The two are independent on purpose:
  // you can record a follow for a feed you cannot reach yet, and hold a feed you never followed.
  /**
   * Hold a feed to READ it, without following.
   *
   * Writes nothing — no record enters my feed, and nothing enters theirs. ⚠ Not invisible: holding
   * a feed means joining its discovery topic, so the peer can see that somebody arrived. It is
   * "not announced", not "anonymous".
   */
  /**
   * Stop rendering a feed. Writes nothing to theirs, and cannot stop them doing anything.
   *
   * ⛔ It names a FEED, not an identity root, because rendering is per-feed and follow — the
   * operation it undoes in practice — names a feed too. A root-scoped block would be the stronger
   * form and this is not it.
   */
  async block (key) {
    const name = Peer.nameFor(key)
    // ⛔ Blocking myself would delete me from my own timeline, which is nobody's intent and is
    // trivially reachable from a page that lists feeds including your own.
    if (name === hex(this.feed.key)) throw new Error('you cannot block yourself')
    await this.feed.put(BLOCK, name, { frontmatter: { createdAt: new Date().toISOString() }, body: '' })
    this._blocked.add(name)
    return name
  }

  // A tombstone, like every other undo here.
  async unblock (key) {
    const name = Peer.nameFor(key)
    await this.feed.del(BLOCK, name)
    this._blocked.delete(name)
  }

  /** Feeds I have blocked. */
  async blocked () {
    return new Set((await this.feed.list(BLOCK)).map((r) => r.name))
  }

  peek (key, feed) {
    const name = Peer.nameFor(key)
    if (feed) this.feeds.set(name, feed)
    // ⚠ Never demote a feed I already follow — that would silently drop them out of my timeline.
    if (!this._follows.has(name)) this.peeked.add(name)
    return name
  }

  async follow (key, feed = null) {
    const name = Peer.nameFor(key)
    // Wall time is fine HERE. The determinism rule binds `apply`, which is replayed on every boot
    // and reorg; this is the write path, which runs once. An epoch placeholder would have been
    // deterministic and false, and a record that lies to avoid a rule the rule does not impose is
    // the worse trade.
    await this.feed.put(FOLLOW, name, {
      frontmatter: { createdAt: new Date().toISOString() },
      body: ''
    })
    if (feed) this.feeds.set(name, feed)
    // A peek that stayed a peek after you followed would leave the timeline silently short.
    this.peeked.delete(name)
    this._follows.add(name)
    return name
  }

  // Tombstone, never a silent removal — absence is ambiguous, and a peer replicating my feed has to
  // be able to tell "unfollowed" from "not replicated to me yet".
  async unfollow (key) {
    const name = Peer.nameFor(key)
    await this.feed.del(FOLLOW, name)
    this.feeds.delete(name)
    this.peeked.delete(name)
    this._follows.delete(name)
  }

  // ── the profile ────────────────────────────────────────────────────────────
  //
  // An ordinary record: same put, same pointer, same tombstone, same replication as a post.

  /**
   * Publish this feed's profile. Only the fields given are touched — a settings form showing two
   * fields must not erase a third it never rendered.
   */
  /**
   * Which networks this identity publishes to, and under which identifier.
   *
   * ⛔ OFF BY DEFAULT, AND ABSENCE FAILS CLOSED. No record means `[]` — publish nowhere. Reading a
   * missing value as "everything" is the one mistake here that cannot be taken back, because the
   * result is words under someone's name on a network that does not forget.
   *
   * ⚠ This is a preference, NOT a bridge. The peer names networks — `favela-markdown/lib/reach.js`
   * defines them and this file already depends on it — and holds nothing that knows what any of
   * them are: no schema, no signer, no wire format. Whatever reads this preference and acts on it
   * is a separate component, which is what `test/boundary.test.js` exists to keep true.
   */
  async reach () {
    await this.feed.update()
    const rec = await this.feed.get(REACH, SELF)
    const fm = (rec && rec.frontmatter) || {}
    return {
      networks: Array.isArray(fm.networks) ? fm.networks : [],
      // identity.md §5.1 — the DESIGNATED credential. With none, a projection that needs one is
      // withheld rather than published under a guess.
      credential: fm.credential || null
    }
  }

  /**
   * The mip pointer for one blob of THIS feed, or null.
   *
   * ⛔ Null means "no pointer", which for a blob written before levels existed is the SINGLE-LEVEL
   * case and not an error — record-vocabulary §3.5 calls today's avatar exactly that. A caller must
   * treat absence as one level, never as a missing asset.
   */
  async blobPointer (oid) {
    return this.blobPointerOf(this.feed.key, oid)
  }

  /** One feed's pointer for a blob, or null. Reads any feed this node holds. */
  async blobPointerOf (feedKey, oid) {
    if (typeof oid !== 'string' || !oid) return null
    const id = hex(feedKey)
    for (const [author, feed] of this._readable()) {
      if (author !== id) continue
      const rec = await feed.get(BLOB, oid)
      const fm = (rec && rec.frontmatter) || null
      if (!fm || !Array.isArray(fm.levels)) return null
      return fm
    }
    return null
  }

  /**
   * Publish the pointer for a blob's pyramid.
   *
   * ⛔ THE POINTER, NOT A MANIFEST FILE. §3.5: one pointer, N oids, zero extra artifacts. It rides
   * the feed so a bridge holding this feed can choose a level without asking anyone.
   */
  async putBlobPointer (oid, pointer) {
    if (typeof oid !== 'string' || !oid) throw new Error('putBlobPointer: an oid is required')
    await this.feed.update()
    await this.feed.put(BLOB, oid, { frontmatter: pointer, body: '' })
    return pointer
  }

  /** Merges: a write naming only `networks` leaves the credential alone, and the reverse. */
  async setReach ({ networks, credential } = {}) {
    await this.feed.update()
    const current = await this.reach()
    const fm = { networks: current.networks, credential: current.credential }

    if (networks !== undefined) {
      if (!Array.isArray(networks)) throw new Error('setReach: networks must be an array')
      for (const n of networks) {
        // ⛔ Refused, not silently dropped. A name nothing can resolve to a bridge would sit in the
        // feed looking enabled, and the reader would have no way to tell it apart from one that works.
        if (n === 'favela') {
          throw new Error('setReach: favela is the substrate, not a destination — it cannot be switched on or off')
        }
        if (!NETWORKS.includes(n)) throw new Error('setReach: unknown network "' + String(n) + '"')
      }
      fm.networks = [...new Set(networks)]
    }
    if (credential !== undefined) {
      fm.credential = credential ? String(credential).slice(0, 256) : null
    }
    if (!fm.credential) delete fm.credential
    await this.feed.put(REACH, SELF, { frontmatter: fm, body: '' })
    return this.reach()
  }

  async setProfile ({ name, avatar, bio, banner } = {}) {
    await this.feed.update()
    const current = await this.feed.get(PROFILE, SELF)
    const fm = { ...((current && current.frontmatter) || {}) }

    if (name !== undefined) {
      const n = typeof name === 'string'
        ? name.replace(/[\n\r\t]/g, ' ').replace(/[\u0000-\u001f\u007f]/g, '').trim().replace(/\s+/g, ' ').slice(0, 64)
        : ''
      // A blank name is a removal, not an error. Absence asserts nothing; "" would assert that the
      // name is empty.
      if (n) fm.name = n
      else delete fm.name
    }
    if (avatar !== undefined) {
      if (avatar) fm.avatar = String(avatar)
      else delete fm.avatar
    }
    // ⛔ A SECOND PICTURE, not a variant of the first. AS2 has carried both `icon` and `image`
    // since forever and Mastodon renders both, so an identity that can only hold one is missing a
    // field the destination already had (hyper-social #67 §7.6). Same shape as the avatar — an oid,
    // bytes in the overlay — because nothing about a banner needs a second mechanism.
    if (banner !== undefined) {
      if (banner) fm.banner = String(banner)
      else delete fm.banner
    }
    // The record is mutable and overwritten in place, so createdAt keeps meaning creation.
    if (typeof fm.createdAt !== 'string') fm.createdAt = new Date().toISOString()

    /**
     * ⛔ The bio is the record's BODY, not another frontmatter field. It was designed that way and
     * this method has been carefully PRESERVING that body on every write while nothing ever wrote
     * it — so a profile could carry a name and a picture and never a sentence about who someone is.
     *
     * A body means it is markdown like every other record body, and it travels with the feed like
     * every other record. A frontmatter copy would be a second place for the same fact.
     */
    let body = (current && current.body) || ''
    if (bio !== undefined) {
      const b = typeof bio === 'string'
        // Control characters out, but newlines KEPT — it is markdown, and a bio with paragraphs is
        // an ordinary thing to write.
        ? bio.replace(/[\u0000-\u0009\u000b\u000c\u000e-\u001f\u007f]/g, '').trim().slice(0, 512)
        : ''
      // ⚠ Blank REMOVES it. Absence asserts nothing; an empty string asserts that the bio is empty.
      body = b
    }

    await this.feed.put(PROFILE, SELF, { frontmatter: fm, body })
    return { name: fm.name || null, avatar: fm.avatar || null, banner: fm.banner || null, bio: body.trim() || null }
  }

  /** This feed's own profile. */
  async profile () {
    return (await this.profileOf(this.feed.key)) || { name: null, avatar: null, banner: null, bio: null }
  }

  /**
   * One feed's profile, or null.
   *
   * Null covers all three unreadable states, which is right HERE and nowhere else in this file: the
   * fallback is the key, which is always shown and never wrong. An unreadable POST has no such
   * fallback, which is why `unresolved()` exists and this does not need one.
   */
  async profileOf (feedKey) {
    const id = hex(feedKey)
    for (const [author, feed] of this._readable()) {
      if (author !== id) continue
      const rec = await feed.get(PROFILE, SELF)
      if (!rec) return null
      const fm = rec.frontmatter || {}
      return { name: fm.name || null, avatar: fm.avatar || null, banner: fm.banner || null, bio: (rec.body || '').trim() || null }
    }
    return null
  }

  /**
   * Every readable feed's profile, keyed by feed.
   *
   * A feed with no profile is ABSENT from the map rather than present with empty fields — "has not
   * published one" and "published an empty one" are different facts.
   */
  async profiles () {
    const out = {}
    for (const [author, feed] of this._readable()) {
      const rec = await feed.get(PROFILE, SELF)
      if (!rec) continue
      const fm = rec.frontmatter || {}
      /**
       * ⛔ The identity ROOT travels with the profile. A mention names a root
       * (`markdown-strip.md` §3.4) and a bare `@name` is never a guess — so without a root on the
       * row there is no way to WRITE a mention from any interface, only to read one. That is what
       * shipped: mentions were findable, renderable, listable and unwritable.
       *
       * ⚠ It is a FEED fact rather than a profile one, and it rides here because this is the map
       * every byline already fetches. The alternative is a founder lookup per rendered author.
       */
      const founder = await feed.founder()
      out[author] = {
        name: fm.name || null,
        avatar: fm.avatar || null,
        banner: fm.banner || null,
        bio: (rec.body || '').trim() || null,
        root: (founder && founder.identityRoot) || null
      }
    }
    return out
  }

  /**
   * People whose PROFILE matches — searching who, not what.
   *
   * Kept separate from `search()` rather than folded in: a person and a post are different answers
   * to a query, and a UI that mixed them would have to guess which the reader meant.
   */
  async searchProfiles (query) {
    const q = String(query == null ? '' : query).toLowerCase()
    if (!q) return []
    const out = []
    for (const [author, profile] of Object.entries(await this.profiles())) {
      const name = (profile.name || '').toLowerCase()
      // The KEY is searchable too — pasting a feed key is how you find someone with no name yet.
      if (name.includes(q) || author.toLowerCase().startsWith(q)) out.push({ author, ...profile })
    }
    return out.sort((a, b) => (a.author < b.author ? -1 : 1))
  }

  // ── interactions ──────────────────────────────────────────────────────────────────────────────
  //
  // Every one of these writes to MY feed and names a target in someone's. Nothing is written to
  // theirs, so a feed's writer set never grows past its owner's devices however popular it gets.

  /** React to a target. `like` is the reserved value; anything else is an emoji or a short label. */
  async react (target, value, createdAt = new Date().toISOString()) {
    const t = Peer.normalizeTarget(target)
    if (!t) throw new Error('Peer.react: a target needs { feed, collection, name }')
    const v = Peer.normalizeValue(value)
    if (!v) throw new Error('Peer.react: a reaction value is 1-' + MAX_VALUE + ' printable characters')
    const name = Peer.reactionId(t, v)
    await this.feed.put(REACTION, name, { frontmatter: { createdAt, target: t, value: v }, body: '' })
    return name
  }

  async unreact (target, value) {
    const v = Peer.normalizeValue(value)
    if (!v) return
    await this.feed.del(REACTION, Peer.reactionId(target, v))
  }

  // `like` is a reaction whose value is the reserved one. Kept as its own method because it is the
  // one value both target networks can carry, so callers that mean exactly that should say so.
  async like (target, createdAt = new Date().toISOString()) {
    return this.react(target, LIKE_VALUE, createdAt)
  }

  async unlike (target) { return this.unreact(target, LIKE_VALUE) }

  // A reply IS a post that names a target — the body is what separates it from a like, which is why
  // the two need no different machinery.
  async reply (target, body, name, createdAt = new Date().toISOString()) {
    const t = Peer.normalizeTarget(target)
    if (!t) throw new Error('Peer.reply: a target needs { feed, collection, name }')
    if (typeof name !== 'string' || !name) throw new Error('Peer.reply: a reply needs its own name')
    await this.feed.put(REPLY, name, { frontmatter: { createdAt, target: t }, body: String(body || '') })
    return name
  }

  // Everything in `collection`, across readable feeds, whose frontmatter target matches.
  //
  // ⛔ There is no count(). You get the entries and may count what you can SEE — so no number exists
  // that could be mistaken for a global total, and none can be summed with a bridged one.
  async _pointingAt (collection, target) {
    const t = Peer.normalizeTarget(target)
    if (!t) return []
    const out = []
    for (const [author, feed] of this._readable()) {
      for (const row of await feed.list(collection)) {
        const rt = Peer.normalizeTarget(row.record.frontmatter && row.record.frontmatter.target)
        if (!rt) continue
        if (rt.feed === t.feed && rt.collection === t.collection && rt.name === t.name) {
          out.push({ author, ...row })
        }
      }
    }
    return out
  }

  /** Every reaction on a target, from feeds I can read — `{ author, value, ... }` per row. */
  async reactionsOn (target) {
    return (await this._pointingAt(REACTION, target)).map((row) => ({
      ...row, value: (row.record.frontmatter && row.record.frontmatter.value) || null
    }))
  }

  /** Repost a target — no body, nothing written to their feed. */
  async repost (target, createdAt = new Date().toISOString()) {
    const t = Peer.normalizeTarget(target)
    if (!t) throw new Error('Peer.repost: a target needs { feed, collection, name }')
    await this.feed.put(REPOST, Peer.targetId(t), { frontmatter: { createdAt, target: t }, body: '' })
    return Peer.targetId(t)
  }

  // A tombstone, like every other undo here: absence is ambiguous in a replicated log.
  async unrepost (target) {
    await this.feed.del(REPOST, Peer.targetId(target))
  }

  /** Who reposted a target, across the feeds I can read. No count — you may count what you SEE. */
  async repostsOf (target) { return this._pointingAt(REPOST, target) }

  /** The targets I have reposted, as a Set of targetId. My own feed, one listing for a page. */
  async myReposts () {
    return new Set((await this.feed.list(REPOST)).map((r) => r.name))
  }

  async likesOn (target) {
    return (await this.reactionsOn(target)).filter((r) => r.value === LIKE_VALUE)
  }

  /**
   * The targets I have liked — a Set of `targetId`.
   *
   * ⛔ MY OWN feed only. Every like lives in its author's feed, so reading this across readable
   * feeds would report someone else's like as mine and light the button up for everyone.
   *
   * ⚠ Cheap on purpose: a like is NAMED by its target, so this is one listing of one log I already
   * hold — never a scan of everyone's, which is what `likesOn` has to do and why it takes a target.
   * A tombstoned like is absent because `list()` filters to resolvable records.
   */
  async myLikes () {
    const out = new Set()
    for (const [id, values] of await this.myReactions()) {
      if (values.has(LIKE_VALUE)) out.add(id)
    }
    return out
  }

  /**
   * What I have reacted with, as `targetId -> Set(value)`.
   *
   * ⛔ MY OWN feed only, and one listing of it for a whole page. Reading this across readable feeds
   * would report someone else's reaction as mine.
   */
  async myReactions () {
    const out = new Map()
    for (const row of await this.feed.list(REACTION)) {
      const fm = row.record.frontmatter || {}
      const t = Peer.normalizeTarget(fm.target)
      const v = Peer.normalizeValue(fm.value)
      if (!t || !v) continue
      const id = Peer.targetId(t)
      if (!out.has(id)) out.set(id, new Set())
      out.get(id).add(v)
    }
    return out
  }

  /** Have I liked this target? The single-target form, for a page that renders one post. */
  async likedByMe (target) {
    const t = Peer.normalizeTarget(target)
    if (!t) return false
    const mine = (await this.myReactions()).get(Peer.targetId(t))
    return !!mine && mine.has(LIKE_VALUE)
  }

  // Oldest first — a thread reads forwards, where a timeline reads backwards.
  async repliesTo (target) {
    const rows = await this._pointingAt(REPLY, target)
    rows.sort((a, b) => {
      const ta = (a.record.frontmatter && a.record.frontmatter.createdAt) || ''
      const tb = (b.record.frontmatter && b.record.frontmatter.createdAt) || ''
      if (ta !== tb) return ta < tb ? -1 : 1
      const ka = a.author + '/' + a.key
      const kb = b.author + '/' + b.key
      return ka < kb ? -1 : ka > kb ? 1 : 0
    })
    return rows
  }

  // ── mentions ──────────────────────────────────────────────────────────────────────────────────
  //
  // ⚠ There is no mention EDGE. A mention lives in the body of a post (`markdown-strip.md` §3.4), so
  // finding them is a SCAN of what this node holds — the same class of work as `search`, and it will
  // want an index long before it wants anything else.

  /**
   * Posts naming `root`, across the feeds I can read.
   *
   * ⛔ The target is an identity ROOT. Not a feed key and not a network identifier: §3.4 was revised
   * precisely because a rotatable identifier in canonical bytes makes rotation a rewrite of every
   * record that ever named you.
   */
  async mentionsOf (root) {
    if (typeof root !== 'string' || !root) return []
    const uri = 'favela://' + root.toLowerCase()
    const out = []
    for (const [author, feed] of this._readable()) {
      for (const row of await feed.list(POST)) {
        const body = (row.record && row.record.body) || ''
        // Cheap reject before parsing: the target must appear verbatim for a facet to carry it.
        if (!body.toLowerCase().includes(uri)) continue
        let facets = []
        try { facets = strip(body).facets || [] } catch { continue }
        // ⛔ A facet, never a substring. A bare `@name` is plain text with no facet and must never
        // count — guessing who it meant is how a mention list starts naming people who were not.
        if (!facets.some((f) => f.type === 'mention' && String(f.value).toLowerCase() === uri)) continue
        out.push({ author, ...row })
      }
    }
    return out
  }

  /** Posts naming ME — resolved from my own feed's founder, which is the only root I can prove. */
  async myMentions () {
    const founder = await this.feed.founder()
    if (!founder || !founder.identityRoot) return []
    return this.mentionsOf(founder.identityRoot)
  }

  // ── notifications ─────────────────────────────────────────────────────────────────────────────
  //
  // ⛔ A PROJECTION, per `SOCIAL-MODEL.md` §3: "a pure function from records to a view — derived on
  // read, never stored, never replicated". Nothing is delivered, because nothing needs to be: every
  // interaction is written in its AUTHOR's feed, so if you hold their feed you already have it.
  //
  // That settles what hyper-social #62 was stuck on. #62 framed notification as DELIVERY and found
  // {stranger can notify, survives offline, bounded cost, Sybil-resistant, no relay} unsatisfiable.
  // It is — for the half this does not do. What is genuinely left is a stranger you hold nothing
  // of, which the DOORBELL answers and which is included below, and waking a sleeping device, which
  // is a separate job entirely.
  //
  // ⚠ No READ STATE, deliberately. The obvious cheap version is a "seen" timestamp, and ordering
  // here is by `createdAt` — which `timeline()` already documents as a CLAIM BY ITS AUTHOR, not a
  // fact the log agrees on. A seen-marker compared against attacker-controlled timestamps is
  // trivially defeated in both directions: post in the future to stay unread for ever, or in the
  // past to be missed. Read state needs a local-only store and an ordering nobody else supplies,
  // and shipping the broken version would be worse than not having it.
  //
  // ⚠ It is a SCAN of what this node holds, like `search` and `mentions`. It will want an index
  // long before it wants anything else.

  /**
   * Everything other people did that concerns me, newest first.
   *
   * `kind` is one of: reaction · repost · reply · mention · follow · knock.
   */
  async notifications ({ limit = 100 } = {}) {
    const mine = hex(this.feed.key)
    const founder = await this.feed.founder()
    const root = founder && founder.identityRoot ? founder.identityRoot : null
    const out = []

    const at = (row) => (row.record && row.record.frontmatter && row.record.frontmatter.createdAt) || ''
    const aimedAtMe = (row) => {
      const t = Peer.normalizeTarget(row.record && row.record.frontmatter && row.record.frontmatter.target)
      return t && t.feed === mine ? t : null
    }

    for (const [author, feed] of this._readable()) {
      // ⛔ Not my own doing. Liking your own post is ordinary; being told about it is not.
      if (author === mine) continue

      for (const collection of [REACTION, REPOST, REPLY]) {
        for (const row of await feed.list(collection)) {
          const target = aimedAtMe(row)
          if (!target) continue
          out.push({
            kind: collection === REACTION ? 'reaction' : collection === REPOST ? 'repost' : 'reply',
            author,
            at: at(row),
            target,
            value: (row.record.frontmatter && row.record.frontmatter.value) || null,
            name: row.name,
            body: (row.record && row.record.body) || ''
          })
        }
      }

      // A follow of ME is a record in THEIR feed named by MY feed key.
      for (const row of await feed.list(FOLLOW)) {
        if (row.name !== mine) continue
        out.push({ kind: 'follow', author, at: at(row), target: null, value: null, name: row.name, body: '' })
      }
    }

    // Mentions reuse the facet-based projection rather than re-deciding what a mention is.
    if (root) {
      for (const row of await this.mentionsOf(root)) {
        if (row.author === mine) continue
        out.push({
          kind: 'mention',
          author: row.author,
          at: (row.record.frontmatter && row.record.frontmatter.createdAt) || '',
          target: { feed: row.author, collection: row.collection, name: row.name },
          value: null,
          name: row.name,
          body: (row.record && row.record.body) || ''
        })
      }
    }

    // ⛔ The doorbell. Everything above needs their feed; this is what somebody you hold NOTHING of
    // can still do, and it is the whole answer to the stranger half of the problem.
    for (const k of await this.feed.knocks()) {
      out.push({ kind: 'knock', author: k.feed, at: '', target: null, value: null, name: k.feed, body: '' })
    }

    out.sort((a, b) => {
      if (a.at !== b.at) return a.at < b.at ? 1 : -1
      const ka = a.kind + '/' + a.author + '/' + a.name
      const kb = b.kind + '/' + b.author + '/' + b.name
      return ka < kb ? -1 : ka > kb ? 1 : 0
    })
    return limit === Infinity ? out : out.slice(0, limit)
  }

  // ── the registry ──────────────────────────────────────────────────────────────────────────────
  //
  // A registry is a FEED whose records are listings. Mine is just my own feed used that way; someone
  // else's is one I follow. There is no registry type, no registry server, and nothing central —
  // which is the only thing that keeps the one we happen to run replaceable rather than privileged.

  // Ask a registry to list me. This is a knock, which is to say: the doorbell already IS the consent
  // mechanism, and being listed needs no separate protocol.
  async askToBeListed (registryFeed, myFeedKey = this.feed.key) {
    await registryFeed.knock(myFeedKey)
  }

  // Honour a request. Listing then dismissing, in that order — apply refuses a listing whose knock
  // is already tombstoned.
  async listFeed (feedKey, record) {
    const r = await this.feed.listFeed(feedKey, record)
    await this.feed.dismissKnock(feedKey)
    return r
  }

  async delist (feedKey) { await this.feed.delist(feedKey) }

  // Every listing across the registries I hold, plus my own. Duplicates are kept: the same feed
  // listed by two registries is two endorsements, and collapsing them would throw away who vouched.
  async directory () {
    const out = []
    for (const [registry, feed] of this._readable()) {
      for (const l of await feed.listings()) out.push({ registry, ...l })
    }
    out.sort((a, b) => {
      const ka = a.registry + '/' + a.feed
      const kb = b.registry + '/' + b.feed
      return ka < kb ? -1 : ka > kb ? 1 : 0
    })
    return out
  }

  // ── the doorbell ──────────────────────────────────────────────────────────────────────────────

  async knocks () { return this.feed.knocks() }

  // ⛔ Accepting a knock means I FOLLOW THEM. It does not make them a writer on my feed, and there
  // is deliberately no method here that would. Write access to a feed belongs to its owner's devices
  // and to nobody else.
  async admitKnock (feedKey, feed = null) {
    const name = await this.follow(feedKey, feed)
    await this.feed.dismissKnock(feedKey)
    return name
  }

  async dismissKnock (feedKey) { await this.feed.dismissKnock(feedKey) }

  async following () {
    const rows = await this.feed.list(FOLLOW)
    return rows.map((r) => r.name).sort()
  }

  // Follows we cannot read: recorded, but no feed held. Named rather than silently absent, because
  // "nobody posted" and "I have not replicated them" look identical in a timeline and are not the
  // same fact.
  async missing () {
    const following = await this.following()
    return following.filter((name) => !this.feeds.has(name))
  }

  // Every feed we can read: our own, plus the held ones. Following is NOT required to be held, and
  // holding is not required to be followed.
  /**
   * Every feed I can read: mine, plus the held ones, MINUS the blocked.
   *
   * ⛔ Blocking is enforced HERE and nowhere else. Every projection in this file goes through this
   * method, so filtering the timeline and forgetting search is how a blocked person reappears in
   * the one view nobody thought about. One place decides.
   *
   * ⚠ A blocked feed is still HELD — still on disk, still replicating. Nothing here can
   * un-replicate what already arrived, and a block that claimed to delete their bytes would be a
   * promise this substrate cannot keep. It stops being rendered; it does not vanish.
   */
  _readable () {
    const out = [[hex(this.feed.key), this.feed]]
    for (const [name, feed] of this.feeds) {
      if (this._blocked.has(name)) continue
      out.push([name, feed])
    }
    return out
  }

  async update () {
    await this.feed.update()
    for (const [, feed] of this.feeds) await feed.update()
    this._follows = new Set(await this.following())
    this._blocked = await this.blocked()
    for (const name of this._follows) this.peeked.delete(name)
  }

  /**
   * The timeline: posts from every readable feed, newest first.
   *
   * ⚠ Ordered by `createdAt`, which is WALL TIME and therefore a claim by its author, not a fact the
   * log agrees on. Positions are per-feed counters and are not comparable across feeds, so there is
   * no cross-feed ordering the substrate can supply. A reader's timeline is a rendering; this is the
   * ordering readers expect, and it is stated rather than implied.
   *
   * Ties break on (author, name) so the same inputs always produce the same order — a timeline that
   * reshuffled on refresh would be indistinguishable from one that had new content.
   */
  async timeline ({ limit = Infinity } = {}) {
    const out = []
    // ⛔ Followed feeds and my own — NOT everything readable. A peeked feed is held so its page can
    // render; putting it here would make reading a stranger's page indistinguishable from
    // following them, which is the whole thing peek exists to avoid.
    for (const [author, feed] of this._readable()) {
      if (this.peeked.has(author)) continue
      for (const row of await feed.list(POST)) {
        out.push({
          key: author + '/' + row.key,
          author,
          collection: row.collection,
          name: row.name,
          record: row.record
        })
      }
    }
    out.sort((a, b) => {
      const ta = (a.record.frontmatter && a.record.frontmatter.createdAt) || ''
      const tb = (b.record.frontmatter && b.record.frontmatter.createdAt) || ''
      if (ta !== tb) return ta < tb ? 1 : -1 // newest first
      return a.key < b.key ? -1 : a.key > b.key ? 1 : 0
    })
    return limit === Infinity ? out : out.slice(0, limit)
  }

  /**
   * Every post by one author, newest first.
   *
   * ⛔ Its own read rather than a filter over a 200-row timeline, which is what the person page
   * did. Once the timeline stopped including peeked feeds, that filter could no longer render the
   * page of somebody you had only peeked — the exact case peek exists for.
   */
  async postsBy (feedKey) {
    const id = hex(feedKey)
    // ⚠ This one reads a feed DIRECTLY rather than through `_readable`, so it repeats the check.
    // A person page that still rendered someone you blocked would be the same bug by another door.
    if (this._blocked.has(id)) return []
    const feed = id === hex(this.feed.key) ? this.feed : this.feeds.get(id)
    if (!feed) return []
    const out = (await feed.list(POST)).map((row) => ({
      key: id + '/' + row.key, author: id, collection: row.collection, name: row.name, record: row.record
    }))
    out.sort((a, b) => {
      const ta = (a.record.frontmatter && a.record.frontmatter.createdAt) || ''
      const tb = (b.record.frontmatter && b.record.frontmatter.createdAt) || ''
      if (ta !== tb) return ta < tb ? 1 : -1
      return a.key < b.key ? -1 : a.key > b.key ? 1 : 0
    })
    return out
  }

  /**
   * Local full-text search over the records this node holds. No index, no crawler, no relay.
   *
   * It searches the markdown SOURCE rather than the stripped text. The source is the canonical
   * bytes, so a substring match over it finds strictly more and never less — "world" matches
   * "**world**", and text inside a table or a link target stays findable. Searching a lossy
   * projection would make findability depend on whatever that projection happened to drop.
   *
   * ⛔ Returns `{ results, unreadable }`. "No results" and "no results, and twelve records I hold
   * pointers for but cannot fetch" are different answers. A search box that renders them alike is
   * the silent-absence bug this codebase refuses everywhere else.
   *
   * No relevance ranking. Ordering is newest first with a deterministic tie-break, the same as the
   * timeline — a score nobody can explain is worse than an order everyone can predict.
   *
   * ⚠ It is a SCAN. Every held record is read on every query. That is honest for the sizes this is
   * built for and it will need an index long before it needs a ranker.
   */
  async search (query, { collection = null, limit = Infinity } = {}) {
    const q = String(query == null ? '' : query).toLowerCase()
    // Fail closed: an empty box is not a request for everything.
    if (!q) return { results: [], unreadable: 0 }

    const results = []
    let unreadable = 0

    for (const [author, feed] of this._readable()) {
      for (const row of await feed.list(collection)) {
        const body = (row.record && row.record.body) || ''
        const hits = countOf(body.toLowerCase(), q)
        if (hits > 0) results.push({ author, hits, ...row })
      }
      // Counted, not skipped. Note what this number is and is not: we cannot know whether an
      // unreadable record WOULD have matched, because we cannot read it. That is precisely why it is
      // reported as a separate figure rather than folded into the results — the honest statement is
      // "n records in scope could not be read", not "n matches were lost".
      unreadable += (await feed.unresolved(collection)).length
    }

    results.sort((a, b) => {
      const ta = (a.record.frontmatter && a.record.frontmatter.createdAt) || ''
      const tb = (b.record.frontmatter && b.record.frontmatter.createdAt) || ''
      if (ta !== tb) return ta < tb ? 1 : -1
      const ka = a.author + '/' + a.key
      const kb = b.author + '/' + b.key
      return ka < kb ? -1 : ka > kb ? 1 : 0
    })

    return { results: limit === Infinity ? results : results.slice(0, limit), unreadable }
  }

  // Records we hold a pointer for and cannot read, across every readable feed. Kept separate from
  // the timeline for the same reason `missing()` is: an unreadable record must never look like an
  // absent one.
  async unresolved () {
    const out = []
    for (const [author, feed] of this._readable()) {
      for (const row of await feed.unresolved()) out.push({ author, ...row })
    }
    return out
  }

  async close () {
    await this.feed.close()
    for (const [, feed] of this.feeds) await feed.close()
  }
}
// RecordStore — the bytes half of a feed or a document.
//
// One markdown file per record at records/<collection>/<name>.md. Overwrite in place on edit,
// unlink on delete, so on-disk size is O(current records) rather than O(operations ever). An
// optional hyper-overlay indexes and replicates the files by content hash (`oid`); without one this
// is a fully-functional local tree, which is what the tests use.
//
// This layer stores BYTES and nothing else. It has no log, no convergence, no notion of who wrote
// what and no tombstones — a delete here only drops bytes, because absence is ambiguous in a
// replicated system and the positive evidence belongs in the log above.
//
// `name` is deliberately not `rkey` and not `position`: the address question is open
// (hyper-social #61 §12 #1), and this layer must not prejudge it. Any single path segment works.

import b4a from 'b4a'
import fs from '#node:fs'
import { join, relative, dirname, isAbsolute } from '#node:path'
import { parse, serialize } from 'favela-markdown/lib/record.js'

const EXT = '.md'
// Byte order, so derivation over the record set is order-stable regardless of readdir order.
const byteOrder = (a, b) => b4a.compare(b4a.from(a), b4a.from(b))
// A collection or a name is one path segment. Reject anything that could escape records/.
const isSafeSeg = (s) => typeof s === 'string' && s.length > 0 && !s.includes('/') && s !== '.' && s !== '..'

export class RecordStore {
  constructor (root, opts = {}) {
    if (typeof root !== 'string') throw new Error('RecordStore: root dir path required')
    this._root = root
    this._recordsDir = join(root, 'records')
    // The storage root this store sits under. ⛔ The overlay path is DERIVED from the disk path
    // relative to it, rather than spelled out — the on-disk layout is `<base>/records/<feed>/
    // records/<collection>/<name>.md`, with `records` doubled, and any hand-written path template
    // is one refactor away from silently not resolving.
    this._base = opts.base || dirname(dirname(root))
    this._overlay = opts.overlay || null
    // ⛔ A byte fetch is bounded. `ensureLocal` is awaited once per record by `log.js`'s scan, so an
    // overlay call that never answers blocked every listing of a feed whose bytes nobody is
    // serving — `timeline`, `search` and `posts-by` alike. There is already a name for that state:
    // UNRESOLVED, "we hold the pointer and cannot read the bytes". A fetch that has not come back
    // IS that, and saying so is the honest answer rather than waiting for one.
    //
    // ⚠ Generous rather than tight. A slow peer is not an absent one, and the cost of being wrong
    // is a record that reads as unresolved for one pass and resolves on the next.
    this._fetchTimeoutMs = opts.fetchTimeoutMs == null ? 5000 : opts.fetchTimeoutMs
  }

  _pathFor (collection, name) {
    if (!isSafeSeg(collection) || !isSafeSeg(name)) {
      throw new Error(`RecordStore: unsafe record key "${collection}/${name}"`)
    }
    return join(this._recordsDir, collection, name + EXT)
  }

  /**
   * Where this record lives in the overlay's namespace.
   *
   * ⛔ It names the FEED. Every feed has its own RecordStore under `<storage>/records/<feed>/`, and
   * this used to be `/records/<collection>/<name>` with no feed in it — so two feeds holding
   * `post/p1` registered the SAME overlay path, the second overwriting the first's disk mapping.
   *
   * ⛔ And, worse, the path could not be resolved BACK to a file, which is what
   * `seedFilePathsFromIndex` needs at boot to rebuild the serve map. The invariant this establishes
   * is exactly that: `join(storage, overlayPath)` IS the file on disk, the same way `/blobs/<oid>`
   * and `/avatars/<oid>` already were.
   */
  _overlayPath (collection, name) { return '/' + relative(this._base, this._pathFor(collection, name)) }

  // Write the record and return { oid, size }. `oid` is the overlay content hash a peer fetches the
  // bytes by; null when there is no overlay. The serialized form is canonical
  // (serialize(parse(x)) === x is a law of the codec), so the same record always yields the same oid.
  async put (collection, name, record) {
    const bytes = b4a.from(serialize(record), 'utf8')
    const p = this._pathFor(collection, name)
    fs.mkdirSync(join(this._recordsDir, collection), { recursive: true })
    fs.writeFileSync(p, bytes)
    let oid = null
    if (this._overlay) {
      try {
        const r = await this._overlay.registerFile(this._overlayPath(collection, name), p, { size: bytes.length })
        oid = r ? r.contentHash : null
      } catch {}
    }
    return { oid, size: bytes.length }
  }

  // The stored bytes plus the parsed record. The bytes are what `put` wrote — never a re-encode.
  async getRaw (collection, name) {
    let bytes
    try { bytes = fs.readFileSync(this._pathFor(collection, name)) } catch (e) {
      if (e && e.code === 'ENOENT') return null
      throw e
    }
    return { bytes, record: parse(b4a.toString(bytes, 'utf8')) }
  }

  async get (collection, name) {
    const raw = await this.getRaw(collection, name)
    return raw ? raw.record : null
  }

  // The content hash the file on disk CURRENTLY has, or null. Reads only — safe against a live store.
  async localOid (collection, name) {
    if (!this._overlay || typeof this._overlay.hashFile !== 'function') return null
    let p
    try { p = this._pathFor(collection, name) } catch { return null }
    const r = await this._overlay.hashFile(p)
    return r ? { oid: r.contentHash, size: r.size } : null
  }

  // Resolve a record whose pointer we hold, fetching the bytes by oid if we do not have them.
  //
  // ⛔ A local hit is VERIFIED against `oid` before it is returned. Skipping that check is what let
  // two co-owners hold identical pointer sets, resolve them both happily, and publish different
  // content. A superseded copy is never returned: stalling loudly beats advancing with content
  // nobody agreed on.
  //
  // ⛔⛔ BUT A MISMATCH MUST FETCH, and for a long time it did not — it returned null right there,
  // and the fetch below was reachable only when there was no local file at all. Posts never hit it,
  // because a post is written under a fresh name and has nothing to be stale. The PROFILE is the
  // one record in the model that is MUTABLE and overwritten in place at the same (collection,
  // name) — so the moment its author edited it, every holder's copy went stale, the pointer named
  // bytes they would never go and get, and the record read as unresolved FOR EVER. Not "until the
  // next pass": nothing in the system would ever fetch it. That is why a profile change had never
  // once reached a follower, and why the symptom was a profile that went BLANK rather than one that
  // failed to update.
  //
  // "Do not return the stale copy" and "do not go and get the right one" are different rules, and
  // only the first was ever intended.
  async ensureLocal (collection, name, oid) {
    const local = await this.get(collection, name)
    if (local != null) {
      if (!oid || !this._overlay || typeof this._overlay.hashFile !== 'function') return local
      const h = await this.localOid(collection, name)
      // Only a POSITIVE mismatch is staleness. If the bytes cannot be hashed we cannot judge them,
      // and inventing a verdict would re-resolve every record on every pass.
      if (!h || h.oid === oid) return local
      // Stale — fall through to the fetch. The superseded bytes stay addressable by their own oid
      // in the overlay; what is replaced here is only this name's view of which version is current.
    }
    if (!this._overlay || !oid) return null
    const disk = this._pathFor(collection, name)
    try {
      fs.mkdirSync(join(this._recordsDir, collection), { recursive: true })
      // ⚠ Raced, not cancelled — the overlay has no cancel and the fetch may still land, which is
      // fine: the bytes end up on disk and the NEXT pass reads them locally for free.
      const r = await Promise.race([
        this._overlay.fetchFile(oid, { destPath: disk }),
        new Promise((resolve) => setTimeout(() => resolve(null), this._fetchTimeoutMs))
      ])
      if (!r) return null
    } catch { return null }

    // ⛔ VERIFY WHAT LANDED, because "a fetch returned" is not "the agreed bytes are on disk". Two
    // ways it can be false: a peer answers with content that does not hash to what was asked for,
    // and — the one that actually shipped, in files.js and avatars.js (0.0.46) — the fetch lands at
    // its own `destPath` while the caller re-reads its own path and quietly returns the OLD bytes.
    // Without this check, a stale record would refuse to resolve and then, one line later, resolve
    // to exactly the stale content it just refused.
    if (typeof this._overlay.hashFile === 'function') {
      const after = await this.localOid(collection, name)
      if (after && after.oid !== oid) return null
    }
    return this.get(collection, name)
  }

  /**
   * Re-register every record file this store holds on disk.
   *
   * ⛔ The repair the boot seed cannot do. The seed rebuilds the serve map from INDEX entries, and
   * an entry written under an older overlay-path shape resolves to a file that was never there — so
   * it is skipped, correctly, and stays unservable for ever. Nothing else rewrites it: `put()` only
   * registers what it writes, and these records were written long ago.
   *
   * ⚠ This HASHES each file, which the seed deliberately does not. That is the cold-start cost the
   * overlay's own `reindexOverlay` is kept off the boot path to avoid, so the caller runs this
   * without waiting on it.
   */
  async reregisterLocal () {
    if (!this._overlay || typeof this._overlay.registerFile !== 'function') return 0
    let n = 0
    let collections = []
    try { collections = fs.readdirSync(this._recordsDir) } catch { return 0 }
    for (const collection of collections) {
      let names = []
      try { names = fs.readdirSync(join(this._recordsDir, collection)) } catch { continue }
      for (const file of names) {
        if (!file.endsWith(EXT)) continue
        const name = file.slice(0, -EXT.length)
        try {
          const p = this._pathFor(collection, name)
          const size = fs.statSync(p).size
          await this._overlay.registerFile(this._overlayPath(collection, name), p, { size })
          n++
        } catch { /* one unreadable file must not stop the rest */ }
      }
    }
    return n
  }

  // Drop the BYTES. Not a deletion of the record — see the header.
  async del (collection, name) {
    try { fs.unlinkSync(this._pathFor(collection, name)) } catch (e) {
      if (!e || e.code !== 'ENOENT') throw e
    }
    if (this._overlay) { try { await this._overlay.unregisterFile(this._overlayPath(collection, name)) } catch {} }
    try { fs.rmdirSync(join(this._recordsDir, collection)) } catch {} // best-effort: drop an empty dir
  }

  // { name, record } for one collection, in byte order.
  async * list (collection) {
    const dir = join(this._recordsDir, collection)
    let names
    try { names = fs.readdirSync(dir) } catch (e) {
      if (e && e.code === 'ENOENT') return
      throw e
    }
    for (const n of names.filter((x) => x.endsWith(EXT)).sort(byteOrder)) {
      const bytes = fs.readFileSync(join(dir, n))
      yield { name: n.slice(0, -EXT.length), record: parse(b4a.toString(bytes, 'utf8')) }
    }
  }

  // Every record as { key, bytes, record } in byte order across collections.
  async all () {
    const out = []
    let collections
    try { collections = fs.readdirSync(this._recordsDir) } catch (e) {
      if (e && e.code === 'ENOENT') return out
      throw e
    }
    for (const collection of collections) {
      const dir = join(this._recordsDir, collection)
      let st
      try { st = fs.statSync(dir) } catch { continue }
      if (!st.isDirectory()) continue
      for (const n of fs.readdirSync(dir)) {
        if (!n.endsWith(EXT)) continue
        const bytes = fs.readFileSync(join(dir, n))
        out.push({
          key: `${collection}/${n.slice(0, -EXT.length)}`,
          bytes,
          record: parse(b4a.toString(bytes, 'utf8'))
        })
      }
    }
    out.sort((a, b) => byteOrder(a.key, b.key))
    return out
  }
}
// A room — many members, roles, and membership as the audience.
//
// ⛔ THE DISTINCTION FROM A FEED, which is the whole reason this file exists. `feed.js`'s roster is
// DEVICE-FLAT: one identity, writers are its devices, and the authorisation rule is "is this writer
// on the roster". #62 §3 named that as right for a feed and wrong for a room. A room has many
// MEMBERS and each member has devices, so there are two levels and every question has to be asked
// at the right one — a role belongs to a person, not to a laptop.
//
// ── The contract is keet's; the substrate is ours ─────────────────────────────────────────────
//
// #62 §3: "stop designing it and adopt keet's contract. One thing we design, one we reuse." The
// vocabulary is keet's — admin / moderator / peer / read-only, members, invitations, config with
// five built-in keys (COMMUNITY-MODERATION.md, SOCIAL-MODEL §6.7, ROOMS-ARE-THE-COMPUTER).
//
// The ENGINE is autobee rather than autobase, which is where we part company with keet. autobase's
// quorum is floor(N/2)+1 indexers: at N=2 one member going offline freezes `indexedLength` while
// the view keeps growing, and it cannot self-heal by dropping to one. A person's two devices make
// N=2 the ordinary posture rather than an edge case. Weights retire that class.
//
// ⛔ And weights bring their own rule, MEASURED while building namespace admins: `system.addWriter`
// caps a grant at the weight of the block CARRYING it, so nobody can admit themselves and the cap
// is SILENT. Admission is therefore always an act by somebody already inside — which is what a room
// wanted anyway.
//
// ── Visibility is a PLACE ─────────────────────────────────────────────────────────────────────
//
// SOCIAL-MODEL §4: you do not mark a post followers-only, you write it in a room whose members are
// your followers. That only holds if membership actually gates writing, which is what `applyOps`
// enforces — otherwise "visibility is a place" is a promise instead of a property.

import Autobee from 'autobee'
import b4a from 'b4a'
import { readAttested, signAttested } from './attestation.js'
import { recordName } from './name.js'

const SEP = '/'
const MEMBER = '__members__'
const DEVICE = '__devices__'
const FOUNDED = '__founded__'
const CONFIG = '__config__'
// A node asking to be let in. ⛔ The one op a not-yet-member can usefully send: an admin cannot
// admit a writer key it has no way to learn, and the joiner cannot grant itself weight, so without
// this the ceremony has a hole a person cannot cross.
const REQUEST = '__requests__'
const MSG = 'message'
// ⛔ A community is a ROOM WHOSE LISTINGS ARE ROOMS — convention, not a type.
// ROOMS-ARE-THE-COMPUTER: "the registry was a room with extra steps". So a community inherits
// members, roles, invites and chat instead of needing its own version of each.
const LISTING = 'listing'

// keet's roles. Ordered, because every check here is "at least this much".
export const READ_ONLY = 'read-only'
export const PEER = 'peer'
export const MODERATOR = 'moderator'
export const ADMIN = 'admin'
const RANK = { [READ_ONLY]: 0, [PEER]: 1, [MODERATOR]: 2, [ADMIN]: 3 }
const isRole = (r) => Object.prototype.hasOwnProperty.call(RANK, r)

// keet's five built-in config keys. ⛔ A closed set: anything else is a message by convention
// (ROOMS-ARE-THE-COMPUTER, "_hs:room-meta"), so config cannot quietly become a key-value store.
const CONFIG_KEYS = new Set(['title', 'description', 'avatar', 'supportedLanguages', 'blindPeers'])

const OP_VERSION = 1
const enc = (v) => b4a.from(JSON.stringify(v), 'utf8')
const dec = (b) => (b == null ? null : JSON.parse(b4a.toString(b, 'utf8')))
const hex = (k) => (typeof k === 'string' ? k : b4a.toString(k, 'hex'))
const isKeyHex = (s) => typeof s === 'string' && /^[0-9a-f]{64}$/i.test(s)
const isSeg = (s) => typeof s === 'string' && s.length > 0 && !s.includes(SEP) && s !== '.' && s !== '..'

const memberKey = (root) => MEMBER + SEP + root
const deviceKey = (device) => DEVICE + SEP + device
const msgKey = (name) => MSG + SEP + name
const requestKey = (writer) => REQUEST + SEP + writer
const listingKey = (room) => LISTING + SEP + room

/** The bytes an op's proof covers: every field except the proof, keys sorted. Same as namespace.js. */
export function canonicalOpBytes (op) {
  const rest = {}
  for (const k of Object.keys(op)) if (k !== 'proof') rest[k] = op[k]
  const ordered = {}
  for (const k of Object.keys(rest).sort()) ordered[k] = rest[k]
  return b4a.from(JSON.stringify(ordered), 'utf8')
}

const sign = (op, cred) => ({ ...op, proof: signAttested(canonicalOpBytes(op), cred) })

/**
 * Who signed this op, if it verifies and names this room.
 *
 * ⛔ `roomHex` is inside the signed bytes for the reason namespace.js learned: without it a signed
 * op lifts verbatim into another room, and an admin here becomes an admin there.
 */
function authorise (op, roomHex) {
  if (op.v !== OP_VERSION) return null
  if (!isKeyHex(op.root)) return null
  if (op.room !== roomHex) return null
  return readAttested(op.proof, canonicalOpBytes(op), op.root)
}

/** The { get, set, scan } surface apply wants, over one hyperbee2 batch. Same shape as elsewhere. */
function batchView (bee, batch) {
  const pending = new Map()
  return {
    async get (key) {
      if (pending.has(key)) return pending.get(key)
      const node = await bee.get(b4a.from(key, 'utf8'))
      return node ? dec(node.value) : null
    },
    async set (key, value) {
      pending.set(key, value)
      batch.tryPut(b4a.from(key, 'utf8'), enc(value))
    },
    async scan (prefix) {
      const out = new Map()
      const p = b4a.from(prefix, 'utf8')
      for await (const node of bee.createReadStream({ gte: p, lt: b4a.concat([p, b4a.from([0xff])]) })) {
        out.set(b4a.toString(node.key, 'utf8'), dec(node.value))
      }
      for (const [k, v] of pending) if (k.startsWith(prefix)) out.set(k, v)
      return [...out].map(([key, value]) => ({ key, value })).filter((e) => e.value != null)
    }
  }
}

/**
 * The role the SIGNER of an op acts with, or null.
 *
 * ⛔ By the SIGNED ROOT, never by the writer core that carried the block. Two reasons, and the
 * second is the one that bites:
 *
 *  1. A role belongs to a PERSON. The proof says which identity signed these exact bytes, so the
 *     answer does not change with which of their machines happened to append it.
 *  2. Authorising by writer core cannot tell two signers apart when one core carries both — which
 *     is every case where a node relays, and was every case in the first draft of the tests. A
 *     stranger's op looked like the room founder's because the founder's node appended it.
 *
 * ⚠ The DEVICE still matters, for revocation: a retired device's signature must stop counting even
 * though its member is still in the room. That is checked here, at the level the chain expresses.
 */
async function roleOfSigner (b, proof) {
  if (!proof) return null
  const d = await b.get(deviceKey(proof.device))
  if (d && d.removed) return null
  const m = await b.get(memberKey(proof.root))
  if (!m || m.removed) return null
  return m.role
}

const at = async (b, proof, role) => {
  const r = await roleOfSigner(b, proof)
  return r != null && RANK[r] >= RANK[role]
}

async function applyOps (batch, view, host, roomHex) {
  const write = view.write()
  const b = batchView(view, write)

  for (const node of batch) {
    let op
    try { op = dec(node.value) } catch { continue }
    if (!op || typeof op !== 'object') continue
    const writer = node.key ? hex(node.key) : null
    if (!writer) continue

    // A room takes writes from a member whose device this is; an unknown writer's block still has
    // to stick long enough to be judged, which is what the ack is for. It grants nothing.
    try { host.ackWriter(node.key) } catch {}

    // ── genesis ────────────────────────────────────────────────────────────────────────────────
    // ⛔ genesis is checked BEFORE the shared authorise below, because it is the one op whose
    // signer is not yet a member of anything.
    if (op.t === 'found') {
      if (await b.get(FOUNDED)) continue
      const proof = authorise(op, roomHex)
      if (!proof) continue
      await b.set(FOUNDED, { root: proof.root })
      await b.set(memberKey(proof.root), { root: proof.root, role: ADMIN })
      await b.set(deviceKey(proof.device), { root: proof.root, device: proof.device, writer })
      try { host.addWriter(node.key, { isIndexer: true }) } catch {}
      continue
    }

    // ── members ────────────────────────────────────────────────────────────────────────────────
    /**
     * ⛔ ONE authorise, above every handler that needs it. The first version declared this in the
     * middle of the switch and a handler above it read `signer` in the temporal dead zone — a
     * ReferenceError thrown INSIDE apply, which wedges the base rather than rejecting an op. apply
     * must be total; a `const` used before its declaration is a way to make it not.
     *
     * `found` is deliberately above this: it is the one op whose signer is not yet a member of
     * anything, so it does its own check.
     */
    const signer = authorise(op, roomHex)
    if (!signer) continue

    // ── asking to be let in ────────────────────────────────────────────────────────────────────
    //
    // Recorded, and it grants nothing: not membership, not weight, not the ability to write. It
    // says a writer core exists and whose it is, which is exactly what an admin needs to answer.
    if (op.t === 'request') {
      if (!isKeyHex(op.writer)) continue
      const bound = readAttested(op.bound, b4a.from(op.writer, 'hex'))
      if (!bound || bound.root !== signer.root) continue
      await b.set(requestKey(op.writer.toLowerCase()), { root: signer.root, device: signer.device })
      continue
    }


    if (op.t === 'add-member') {
      if (!isKeyHex(op.member) || !isRole(op.role)) continue
      // ⛔ Appointing is an ADMIN act. A moderator removes; only an admin decides who is in.
      if (!(await at(b, signer, ADMIN))) continue
      const existing = await b.get(memberKey(op.member))
      if (existing && !existing.removed) continue
      await b.set(memberKey(op.member), { root: op.member, role: op.role })
      continue
    }

    if (op.t === 'set-role') {
      if (!isKeyHex(op.member) || !isRole(op.role)) continue
      if (!(await at(b, signer, ADMIN))) continue
      const m = await b.get(memberKey(op.member))
      if (!m || m.removed) continue
      // ⛔ Never the last admin. A room with no admin can admit nobody ever again — nothing left
      // carries the weight — and that is unrecoverable rather than merely awkward.
      if (m.role === ADMIN && op.role !== ADMIN && !(await hasOtherAdmin(b, op.member))) continue
      await b.set(memberKey(op.member), { ...m, role: op.role })
      continue
    }

    if (op.t === 'remove-member') {
      if (!isKeyHex(op.member)) continue
      // A moderator may remove. SOCIAL-MODEL §6.7: moderation needs no new permission system.
      if (!(await at(b, signer, MODERATOR))) continue
      const m = await b.get(memberKey(op.member))
      if (!m || m.removed) continue
      if (m.role === ADMIN && !(await hasOtherAdmin(b, op.member))) continue
      await b.set(memberKey(op.member), { ...m, removed: true })
      // ⚠ Their devices go with them. A member-level decision repeated per device is a rule
      // somebody eventually applies incompletely.
      for (const e of await b.scan(DEVICE + SEP)) {
        if (!e.value || e.value.root !== op.member || e.value.removed) continue
        await b.set(e.key, { ...e.value, removed: true })
        if (e.value.writer) { try { host.removeWriter(b4a.from(e.value.writer, 'hex')) } catch {} }
      }
      continue
    }

    // ── devices ────────────────────────────────────────────────────────────────────────────────
    //
    // ⛔ Carried by somebody already inside, because a grant is capped at the carrying block's
    // weight and a joiner's block has none. The device proves WHOSE it is; the admin supplies the
    // weight. Both halves are needed and neither is sufficient.
    // Answer an ask: admit the writer that asked. ⛔ It must have ASKED — an admin conscripting a
    // machine that never offered would be admitting somebody TO a room rather than WITH them, and
    // the request is also the only place the writer key legitimately comes from.
    if (op.t === 'admit-request') {
      if (!isKeyHex(op.writer)) continue
      if (!(await at(b, signer, ADMIN))) continue
      const asked = await b.get(requestKey(op.writer.toLowerCase()))
      if (!asked || asked.removed) continue
      const m = await b.get(memberKey(asked.root))
      if (!m || m.removed) continue
      await b.set(deviceKey(asked.device), { root: asked.root, device: asked.device, writer: op.writer.toLowerCase() })
      await b.set(requestKey(op.writer.toLowerCase()), { ...asked, removed: true })
      try { host.addWriter(b4a.from(op.writer, 'hex'), { isIndexer: true }) } catch {}
      continue
    }

    if (op.t === 'admit-device') {
      if (!isKeyHex(op.writer)) continue
      if (!(await at(b, signer, ADMIN))) continue
      const proof = readAttested(op.bound, b4a.from(op.writer, 'hex'))
      if (!proof) continue
      const m = await b.get(memberKey(proof.root))
      if (!m || m.removed) continue
      await b.set(deviceKey(proof.device), { root: proof.root, device: proof.device, writer: op.writer.toLowerCase() })
      try { host.addWriter(b4a.from(op.writer, 'hex'), { isIndexer: true }) } catch {}
      continue
    }

    // ── listings ───────────────────────────────────────────────────────────────────────────────
    //
    // ⛔ A MODERATOR act. COMMUNITY-MODERATION: admins and moderators curate LISTINGS — and they
    // cannot moderate inside a listed room. Two rooms, two scopes, and the gap between them is
    // deliberate: "the community admin is a neighbourhood association; they decide which bars are
    // listed in the directory, they don't enter the bars."
    // ⛔ `listed`, NOT `room`. The envelope already uses `room` for THIS room's key, which is what
    // stops an op signed here from being replayed into another room — and a payload field of the
    // same name overwrote it, so every listing failed the anti-lift check before reaching this
    // handler and vanished with no error. The envelope owns that name.
    if (op.t === 'list-room') {
      if (!isKeyHex(op.listed)) continue
      if (!(await at(b, signer, MODERATOR))) continue
      await b.set(listingKey(op.listed.toLowerCase()), {
        room: op.listed.toLowerCase(),
        note: String(op.note || '').slice(0, 280),
        by: signer.root
      })
      continue
    }

    if (op.t === 'delist-room') {
      if (!isKeyHex(op.listed)) continue
      if (!(await at(b, signer, MODERATOR))) continue
      const l = await b.get(listingKey(op.listed.toLowerCase()))
      if (!l) continue
      // ⚠ A tombstone that stops PROPAGATION, not existence. The listed room is untouched and
      // anybody already holding its key still has it — a registry is a phone book, not a gate.
      await b.set(listingKey(op.listed.toLowerCase()), { ...l, removed: true })
      continue
    }

    // ── config ─────────────────────────────────────────────────────────────────────────────────
    if (op.t === 'config') {
      if (!CONFIG_KEYS.has(op.key)) continue
      if (!(await at(b, signer, MODERATOR))) continue
      const cur = (await b.get(CONFIG)) || {}
      await b.set(CONFIG, { ...cur, [op.key]: String(op.value == null ? '' : op.value).slice(0, 512) })
      continue
    }

    // ── messages ───────────────────────────────────────────────────────────────────────────────
    if (op.t === 'message') {
      if (!isSeg(op.name)) continue
      // ⛔ PEER and up. A read-only member is a member who cannot write, and if anyone could write
      // then membership would describe nothing and "visibility is a place" would be a promise
      // rather than a property.
      if (!(await at(b, signer, PEER))) continue
      // ⛔ A NAME ALREADY HELD BY SOMEBODY ELSE IS REFUSED. This was an unconditional `set`, and the
      // row carries `author`, so posting under a name another member had used replaced their words
      // AND took their byline — silently, with nothing in the view to show it. Not a race: an op is
      // `{t:'message', name, body}`, so any writer on this autobee can pick the name directly no
      // matter what the RPC surface allows. lib/name.js makes an accidental collision impractical;
      // only apply can make a deliberate one ineffective, because apply must never trust the op.
      //
      // Same author is still allowed through: that is a revision of your own record, and it is the
      // only edit path a room has today.
      const prior = await b.get(msgKey(op.name))
      if (prior && prior.author && prior.author !== signer.root) continue
      await b.set(msgKey(op.name), {
        author: signer.root, body: String(op.body || ''), at: typeof op.at === 'number' ? op.at : 0
      })
      continue
    }
  }

  await write.flush()
}

async function hasOtherAdmin (b, except) {
  for (const e of await b.scan(MEMBER + SEP)) {
    if (!e.value || e.value.removed || e.value.role !== ADMIN) continue
    if (e.value.root !== except) return true
  }
  return false
}

export class Room {
  constructor (store, key = null) {
    this.base = new Autobee(store, key || null, {
      apply: (batch, view, host) => applyOps(batch, view, host, hex(this.base.key)),
      // A joiner's block has to reach apply before anybody can judge it.
      optimistic: true,
      fastForward: false
    })
  }

  async ready () { await this.base.ready(); return this }
  get key () { return this.base.key }
  get discoveryKey () { return this.base.discoveryKey }
  async update () { await this.base.update() }
  replicate (s) { return this.base.replicate(s) }
  async close () { await this.base.close() }

  _root (cred) {
    if (!cred) throw new Error('this needs a device credential — run `favela link`')
    const read = readAttested(cred.proof, undefined)
    if (!read) throw new Error('this device\'s credential does not verify')
    return read.root
  }

  /**
   * ⛔ The envelope is written LAST, so a payload can never overwrite it. `room` and `root` are what
   * `authorise` checks — a payload field of the same name silently disarmed the anti-lift check and
   * every op of that kind vanished with no error anywhere.
   */
  _op (t, extra, cred) {
    return sign({ ...extra, t, v: OP_VERSION, room: hex(this.base.key), root: this._root(cred), at: Date.now() }, cred)
  }

  /**
   * Found the room. ⛔ Non-optimistic: an optimistic writer cannot be genesis
   * (autobee/lib/writers.js), so an optimistic founding op is never processed and the room comes up
   * with no admin and no way to add one.
   */
  async found (cred) {
    await this.base.append(enc(this._op('found', {}, cred)))
  }

  async addMember (root, role, cred) {
    await this.base.append(enc(this._op('add-member', { member: String(root).toLowerCase(), role }, cred)), { optimistic: true })
  }

  async setRole (root, role, cred) {
    await this.base.append(enc(this._op('set-role', { member: String(root).toLowerCase(), role }, cred)), { optimistic: true })
  }

  async removeMember (root, cred) {
    await this.base.append(enc(this._op('remove-member', { member: String(root).toLowerCase() }, cred)), { optimistic: true })
  }

  /**
   * Admit a device — `by` is a member already inside, and `cred` is the device being admitted.
   *
   * ⚠ Two credentials, because the two halves are different facts: the device proves WHOSE it is,
   * and the admin supplies the weight that makes the grant stick.
   */
  async admitDevice (cred, by, writerHex = null) {
    const w = writerHex || hex(this.base.local.key)
    const bound = signAttested(b4a.from(w, 'hex'), cred)
    await this.base.append(enc(this._op('admit-device', { writer: w.toLowerCase(), bound }, by)), { optimistic: true })
  }

  /** Ask to be let in. Appended optimistically — a joiner is not a writer yet. */
  async request (cred) { return this.requestWriter(hex(this.base.local.key), cred) }

  async requestWriter (writerHex, cred) {
    const bound = signAttested(b4a.from(writerHex, 'hex'), cred)
    await this.base.append(enc(this._op('request', { writer: String(writerHex).toLowerCase(), bound }, cred)), { optimistic: true })
  }

  /** Answer an ask. The caller must already be an admin — the weight comes from their block. */
  async admitRequest (writerHex, cred) {
    await this.base.append(enc(this._op('admit-request', { writer: String(writerHex).toLowerCase() }, cred)), { optimistic: true })
  }

  async requests () {
    return (await this._prefix(REQUEST)).filter((e) => !e.value.removed)
      .map((e) => ({ writer: e.key, root: e.value.root, device: e.value.device }))
  }

  /** List a room in this community. A moderator act. */
  async listRoom (roomKey, note, cred) {
    await this.base.append(enc(this._op('list-room', { listed: String(roomKey).toLowerCase(), note: String(note || '') }, cred)), { optimistic: true })
  }

  async delistRoom (roomKey, cred) {
    await this.base.append(enc(this._op('delist-room', { listed: String(roomKey).toLowerCase() }, cred)), { optimistic: true })
  }

  async listings () {
    return (await this._prefix(LISTING)).filter((e) => !e.value.removed)
      .map((e) => ({ room: e.key, note: e.value.note, by: e.value.by }))
  }

  async setConfig (key, value, cred) {
    await this.base.append(enc(this._op('config', { key, value }, cred)), { optimistic: true })
  }

  // ⛔ The default was `String(Date.now()) + 4 hex chars` — a THIRD naming scheme, different again
  // from the two in rpc.js, and one whose digit-subsequence collapsed 83% of names when
  // favela-bridge derived a bsky rkey from it. One generator now, in lib/name.js.
  //
  // `name` stays a parameter only so a test can force a collision and prove apply refuses it. No
  // caller in the daemon passes it.
  async post (body, cred, name = recordName()) {
    await this.base.append(enc(this._op('message', { name, body: String(body || '') }, cred)), { optimistic: true })
    return name
  }

  // ── reads ────────────────────────────────────────────────────────────────────────────────────

  async founder () { return this._get(FOUNDED) }
  async config () { return (await this._get(CONFIG)) || {} }

  async members () {
    return (await this._prefix(MEMBER)).filter((e) => !e.value.removed)
      .map((e) => ({ root: e.key, role: e.value.role }))
  }

  async roleOf (root) {
    const m = await this._get(memberKey(String(root).toLowerCase()))
    return m && !m.removed ? m.role : null
  }

  async devices (root) {
    return (await this._prefix(DEVICE))
      .filter((e) => !e.value.removed && (!root || e.value.root === String(root).toLowerCase()))
      .map((e) => ({ device: e.key, root: e.value.root, writer: e.value.writer || null }))
  }

  async messages () {
    return (await this._prefix(MSG)).map((e) => ({ name: e.key, ...e.value }))
      .sort((a, b) => (a.at - b.at) || (a.name < b.name ? -1 : 1))
  }

  async _get (key) {
    const node = await this.base.view.get(b4a.from(key, 'utf8'))
    return node ? dec(node.value) : null
  }

  async _prefix (prefix) {
    const out = []
    const start = prefix + SEP
    for await (const node of this.base.view.createReadStream({
      gte: b4a.from(start, 'utf8'), lt: b4a.from(start + '\xff', 'utf8')
    })) {
      const value = dec(node.value)
      if (value == null) continue
      out.push({ key: b4a.toString(node.key, 'utf8').slice(start.length), value })
    }
    return out.sort((a, b) => (a.key < b.key ? -1 : a.key > b.key ? 1 : 0))
  }
}

export default Room
// The daemon's RPC surface — newline-delimited JSON over a unix socket.
//
// Deliberately the smallest thing that works. hyper-svc is the ecosystem answer and brings
// transports, auth contexts and a documented half-open socket leak; none of that earns its place
// while the only client is a CLI on the same machine. The socket is owner-gated by the filesystem,
// which is the same trust boundary pgv3's UDS listener relies on.
//
// One frame per line: request `{id, cmd, args}` → response `{id, ok, value}` or `{id, ok:false, error}`.

import net from '#node:net'
import b4a from 'b4a'
import fs from '#node:fs'
import { join } from '#node:path'
import { Peer, LIKE_VALUE } from './peer.js'
import { linkMentions } from './mentions.js'
import { graphOf } from './graph.js'
import { recordName } from './name.js'
// ⛔ ONE ladder, owned by the record vocabulary that defines it (record-vocabulary §3.5).
import { makePointer, AUTHOR, selectLevel, planLevels, derivedBy } from 'favela-markdown/lib/levels.js'
import { available as ffmpegAvailable, probe, encodeLevel } from './transcode.js'

export function createServer (node, sockPath, { onError = () => {} } = {}) {
  // A stale socket from a crashed daemon would make bind fail with EADDRINUSE and look like
  // "already running". Removing it is safe ONLY because the caller has already established that no
  // daemon holds it — see index.js, which connects first and refuses to start if one answers.
  try { fs.unlinkSync(sockPath) } catch {}

  const server = net.createServer((socket) => {
    let buf = ''
    socket.on('error', onError)
    socket.on('data', async (chunk) => {
      buf += chunk.toString()
      let i
      while ((i = buf.indexOf('\n')) !== -1) {
        const line = buf.slice(0, i)
        buf = buf.slice(i + 1)
        if (!line.trim()) continue
        let req
        try { req = JSON.parse(line) } catch { continue }
        let res
        try {
          res = { id: req.id, ok: true, value: await dispatch(node, req.cmd, req.args || {}) }
        } catch (err) {
          res = { id: req.id, ok: false, error: (err && err.message) || String(err) }
        }
        try { socket.write(JSON.stringify(res) + '\n') } catch {}
      }
    })
  })
  server.listen(sockPath)
  return server
}

/**
 * Resolve bare `@handle` into real mentions before a body is written.
 *
 * ⛔ Applied on the WRITE path, for every client. A surface doing it would leave the CLI and any
 * other writer without it, and "you can @ someone in this one app" is not the feature.
 *
 * ⚠ It changes what the author typed, and that is the point — a bare @name is plain text for ever
 * otherwise. It is conservative about it: confirmed handles only, prose only, never inside code and
 * never inside an existing link.
 */
const mentioned = (node, body) =>
  linkMentions(String(body || ''), (token) => node.resolveMention(token))

// Every command is a thin pass-through. Business rules live in Peer and apply — a handler that
// enforced something would be a second place to look for the rule.
// ⛔ A write from an unadmitted device is DROPPED BY APPLY, deterministically and silently. The op
// appends, the CLI gets a name back, and the record never appears anywhere — which is exactly the
// "it returned, so it worked" confusion this codebase refuses at every other layer.
//
// So the socket refuses the write instead. The check is here rather than in Peer because Peer's job
// is to describe the feed, and apply's job is to be the authority; this is the door.
const MUTATIONS = new Set(['post', 'follow', 'unfollow', 'like', 'unlike', 'reply',
  'knock', 'admit', 'dismiss', 'unlink', 'react', 'unreact', 'repost', 'unrepost', 'block', 'unblock',
  // ⚠ The namespace ops that SIGN. `namespace-join` is not here: pointing this node at another
  // registry needs no credential, and a node that cannot yet sign must still be able to look.
  'namespace-request', 'namespace-admit', 'namespace-grant', 'namespace-revoke',
  'namespace-invite', 'namespace-uninvite',
  // Room ops all sign with this device's credential.
  'room-create', 'room-post', 'room-config', 'room-add', 'room-role', 'room-remove', 'room-admit',
  'room-request', 'room-list', 'room-delist', 'dm',
  // ⛔ Changing WHERE this identity publishes is an identity act, not a display preference.
  // `set-profile` is not here because a self-label asserts nothing; turning on a network puts words
  // under this identity somewhere that does not forget, so it requires an ATTESTED device — the
  // same gate as posting. A browser cannot do it without one, which is the whole point of the
  // browser being a controller rather than a device (see browsers.js).
  'set-reach'])

/**
 * The room, or a refusal that says which of the two possible things is wrong.
 *
 * ⚠ "not in that room" and "that room does not exist" look identical from outside and are fixed by
 * completely different things — one by a key, the other by nothing.
 */
// ⛔ UNDER THE NODE'S OWN STORAGE, never `~/.hyperident`. A phone has no home directory, and a
// daemon that guesses one is the same defect as a binary assumed to be on PATH. The node already
// knows where its state lives, so the vault goes beside it and travels with it.
const vaultPathFor = (node) => join(node.storage, 'identity', 'vault')

const b4aOf = (hex) => b4a.from(hex, 'hex')

function requireRoom (node, key) {
  const r = node.room(key)
  if (!r) throw new Error('this node is not in room ' + String(key).slice(0, 16) + '… — join it first')
  return r
}

async function requireLinked (peer) {
  const devices = await peer.feed.devices()
  if (devices.includes(peer.feed.deviceKey)) return
  throw new Error('this device is not admitted to its own feed, so every write would be silently ' +
    'dropped by apply. Run `favela link` to attest it.')
}

export async function dispatch (node, cmd, a) {
  const peer = node.peer
  if (MUTATIONS.has(cmd)) await requireLinked(peer)
  switch (cmd) {
    case 'whoami':
      return {
        feed: node.key.toString('hex'),
        device: peer.feed.deviceKey,
        writable: peer.feed.writable,
        // ⛔ Reported, because "not writable" and "not linked" look identical from outside and are
        // fixed by completely different things.
        linked: (await peer.feed.devices()).includes(peer.feed.deviceKey)
      }
    // Admit this device with a chain proof for its writer key. The CLI mints it; the daemon never
    // opens the vault itself.
    // ⛔ MINT — create this identity, or restore it from a recovery phrase. The counterpart of
    // `pair-accept`: pairing needs another device AWAKE, this needs nobody, and until it existed the
    // origin of every favela identity sat outside the product in a shell a handset does not have.
    //
    // ⛔ THE VAULT LIVES UNDER THE NODE'S STORAGE, not `~/.hyperident`. A phone has no home directory,
    // and defaulting to one is the same class of bug as assuming a binary is on PATH — the daemon
    // already knows where its own state goes, so it says.
    //
    // ⚠ THE MNEMONIC COMES BACK ONCE AND IS NEVER STORED IN THE CLEAR when a passphrase is given.
    // The caller shows it and forgets it; nothing here writes it anywhere but the sealed vault.
    // Re-asking is impossible by design, which is what makes "a stolen device yields a locked vault"
    // true rather than aspirational.
    case 'mint-identity': {
      const { mintIdentity } = await import('./mint.js')
      const r = await mintIdentity({
        vaultPath: vaultPathFor(node),
        passphrase: a.passphrase || null,
        mnemonic: a.mnemonic || null
      })
      // Linked immediately: a minted identity that is not attested to this device would leave the node
      // holding a vault it cannot write with, which reads as a half-finished ceremony with no way back.
      await node.link({
        keyPair: {
          publicKey: b4aOf(r.credential.publicKey),
          secretKey: b4aOf(r.credential.secretKey)
        },
        proof: r.credential.proof
      })
      return { mnemonic: r.mnemonic, device: r.credential.publicKey, restored: !!a.mnemonic }
    }

    case 'link': {
      if (!a.credential || !a.credential.publicKey || !a.credential.secretKey || !a.credential.proof) {
        throw new Error('link needs { credential: { publicKey, secretKey, proof } }')
      }
      const b4a = await import('b4a')
      return node.link({
        keyPair: {
          publicKey: b4a.default.from(a.credential.publicKey, 'hex'),
          secretKey: b4a.default.from(a.credential.secretKey, 'hex')
        },
        proof: a.credential.proof
      })
    }
    case 'devices': return {
      // { writer, device } pairs — `unlink` takes the DEVICE key, so the listing has to show it.
      roster: await peer.feed.roster(),
      revoked: await peer.feed.revokedDevices(),
      founder: await peer.feed.founder()
    }
    case 'unlink': { await peer.feed.removeDevice(a.device); await peer.update(); return { revoked: a.device } }
    case 'post': {
      // ⛔ MINTED HERE, NEVER TAKEN FROM THE CALLER. This was `a.name || String(Date.now())`:
      // a millisecond clock collides, and accepting `a.name` made a collision CHOOSABLE. See lib/name.js.
      const name = recordName()
      await peer.feed.put('post', name, {
        frontmatter: { createdAt: new Date().toISOString() },
        body: await mentioned(node, a.body)
      })
      await peer.update()
      return { name }
    }
    case 'timeline':
      await peer.update()
      return withLikes(peer, await peer.timeline({ limit: a.limit || 50 }))
    // One author's posts. ⛔ Not a filtered timeline: the timeline excludes peeked feeds, so
    // filtering it could never render the page of somebody you had only peeked.
    case 'posts-by': {
      await peer.update()
      return withLikes(peer, await peer.postsBy(a.feed))
    }
    // Do I hold this feed, and is it a follow or a peek? What a person page needs to know which
    // buttons to offer.
    case 'holding': return node.holding(String(a.feed || ''))
    case 'follow': return { feed: await node.follow(a.feed) }
    // ⛔ Read someone without following them. Not in MUTATIONS: it writes nothing anywhere, so a
    // device that cannot yet write to its own feed can still read a stranger's page.
    case 'peek': return node.peek(a.feed)
    // ⛔ Stop RENDERING a feed. It writes nothing to theirs and cannot stop them doing anything —
    // see peer.js. The surface must say that, or the button promises more than it does.
    case 'block': { await peer.block(a.feed); await peer.update(); return { blocked: a.feed } }
    case 'unblock': { await peer.unblock(a.feed); await peer.update(); return { unblocked: a.feed } }
    case 'blocked': return { blocked: [...await peer.blocked()] }
    case 'unfollow': { await node.unfollow(a.feed); return { feed: a.feed } }
    case 'following': return peer.following()
    case 'missing': return peer.missing()
    case 'like': { await peer.like(a.target); await peer.update(); return { ok: true } }
    case 'unlike': { await peer.unlike(a.target); await peer.update(); return { ok: true } }
    // ⛔ `like` stays its own command rather than becoming `react value=like`. It is the one value
    // both target networks can carry, so a caller that means exactly that should be able to say so.
    case 'react': { await peer.react(a.target, a.value); await peer.update(); return { ok: true } }
    case 'unreact': { await peer.unreact(a.target, a.value); await peer.update(); return { ok: true } }
    // Everyone's reactions on ONE target — a scan across held feeds, so it takes a target.
    case 'repost': { await peer.repost(a.target); await peer.update(); return { ok: true } }
    case 'unrepost': { await peer.unrepost(a.target); await peer.update(); return { ok: true } }
    // Posts naming me. ⚠ A scan of what this node holds — see peer.js. It takes no target because
    // the only root this node can prove is its own.
    // ⛔ A projection, not an inbox. Nothing was delivered to produce this — see peer.js.
    case 'notifications': {
      await peer.update()
      return { notifications: await peer.notifications({ limit: a.limit || 100 }) }
    }
    // ⛔ A projection of the SUBSTRATE. Its value is diagnostic: four states that currently look
    // identical from outside — see graph.js.
    case 'graph': { await peer.update(); return graphOf(peer) }
    case 'mentions': {
      await peer.update()
      return withLikes(peer, await peer.myMentions())
    }
    case 'reposts': {
      await peer.update()
      return { reposts: (await peer.repostsOf(a.target)).map((r) => ({ author: r.author })) }
    }
    case 'reactions': {
      await peer.update()
      const rows = await peer.reactionsOn(a.target)
      return { reactions: rows.map((r) => ({ author: r.author, value: r.value })) }
    }
    case 'likes': return (await peer.likesOn(a.target)).map((r) => view(r))
    // Have I liked it? Asked by a page that renders ONE post and has no timeline to stamp.
    case 'liked': return { liked: await peer.likedByMe(a.target) }
    case 'reply': {
      // ⛔ MINTED HERE, NEVER TAKEN FROM THE CALLER. This was `a.name || String(Date.now())`:
      // a millisecond clock collides, and accepting `a.name` made a collision CHOOSABLE. See lib/name.js.
      const name = recordName()
      await peer.reply(a.target, await mentioned(node, a.body), name)
      await peer.update()
      return { name }
    }
    case 'replies': return withLikes(peer, await peer.repliesTo(a.target))
    case 'search': {
      await peer.update()
      const r = await peer.search(a.query, { collection: a.collection || null, limit: a.limit || 25 })
      // People and posts are different answers to one query, so they are returned apart rather than
      // interleaved — a reader searching "stortron" may mean either, and the UI can show both.
      return { results: await withLikes(peer, r.results), unreadable: r.unreadable, people: await peer.searchProfiles(a.query) }
    }
    case 'knocks': return peer.knocks()
    case 'knock': { await peer.feed.knock(a.feed); return { ok: true } }
    case 'admit': { await peer.admitKnock(a.feed, await node.hold(a.feed)); await peer.update(); return { ok: true } }
    case 'dismiss': { await peer.dismissKnock(a.feed); await peer.update(); return { ok: true } }
    case 'directory': return peer.directory()

    // ── rooms ───────────────────────────────────────────────────────────────────────────────
    //
    // ⛔ A room is the unit of scope, audience, governance and history at once (SOCIAL-MODEL §3).
    // A feed is the degenerate case of one — which is why these are not "group feeds".
    case 'room-create': return node.createRoom()
    case 'room-join': return node.joinRoom(a.key)
    case 'room-leave': return node.leaveRoom(a.key)
    case 'rooms': return { rooms: await node.rooms() }
    case 'room': {
      const r = requireRoom(node, a.key)
      await r.update()
      return {
        key: a.key,
        config: await r.config(),
        members: await r.members(),
        // ⚠ Reported, because "I am in this room" and "I am a MEMBER of it" are different facts and
        // only the second lets you write. Holding the key gets you the first.
        role: await r.roleOf(node.rootHex()),
        requests: await r.requests(),
        listings: await r.listings(),
        messages: await r.messages()
      }
    }
    case 'room-post': { const r = requireRoom(node, a.key); const name = await r.post(a.body, node.cred); await r.update(); return { name } }
    case 'room-config': { const r = requireRoom(node, a.key); await r.setConfig(a.field, a.value, node.cred); await r.update(); return { ok: true } }
    case 'room-add': { const r = requireRoom(node, a.key); await r.addMember(a.member, a.role || 'peer', node.cred); await r.update(); return { ok: true } }
    case 'room-role': { const r = requireRoom(node, a.key); await r.setRole(a.member, a.role, node.cred); await r.update(); return { ok: true } }
    case 'room-remove': { const r = requireRoom(node, a.key); await r.removeMember(a.member, node.cred); await r.update(); return { ok: true } }
    // Admit a device into the room — carried by THIS node, which must already be an admin. The
    // weight cannot come from the joiner; see room.js.
    // ⛔ Ask to be let in. The joiner cannot grant itself weight and an admin cannot admit a
    // writer key it has no way to learn, so this is the only crossing a person can make alone.
    case 'room-request': { const r = requireRoom(node, a.key); await r.request(node.cred); await r.update(); return { asked: r.base.local.key.toString('hex') } }
    case 'room-requests': { const r = requireRoom(node, a.key); await r.update(); return { requests: await r.requests() } }
    // Answer one. Requires being an admin HERE, because the weight comes from this node's block.
    case 'room-admit': { const r = requireRoom(node, a.key); await r.admitRequest(a.writer, node.cred); await r.update(); return { admitted: a.writer } }
    // ⛔ A community is a ROOM WHOSE LISTINGS ARE ROOMS — not a type. "The registry was a room with
    // extra steps": it inherits members, roles, invites and chat instead of having its own.
    case 'room-list': { const r = requireRoom(node, a.key); await r.listRoom(a.room, a.note, node.cred); await r.update(); return { listed: a.room } }
    case 'room-delist': { const r = requireRoom(node, a.key); await r.delistRoom(a.room, node.cred); await r.update(); return { delisted: a.room } }
    // ⛔ A DM is a room with two members. SOCIAL-MODEL §4: a two-member room IS a direct message,
    // and there is deliberately no separate DM type — one primitive, used.
    case 'dm': {
      const { key } = await node.createRoom()
      const r = node.room(key)
      await r.addMember(a.member, 'peer', node.cred)
      await r.setConfig('title', 'DM', node.cred)
      await r.update()
      return { key, member: a.member }
    }

    // ── browser identities ──────────────────────────────────────────────────────────────────
    //
    // ⛔ These are reachable over the daemon's UDS, which is owner-gated by the filesystem. Minting
    // an invite from here is therefore already "a human at this machine", which is exactly the gate
    // a one-time invite is supposed to be.
    case 'invite': return node.browsers.invite(a.ttlMs !== undefined ? { ttlMs: a.ttlMs } : {})
    case 'browser-challenge': return node.browsers.challenge()
    case 'browser-redeem': return node.browsers.redeem(a)
    case 'browser-claim': return node.browsers.claim(a)
    case 'browser-unclaimed': return { unclaimed: await node.browsers.unclaimed() }

    // ── profile ─────────────────────────────────────────────────────────────────────────────
    // ⛔ The PROFILE lives in the feed, not in the local registry — a registry does not replicate,
    // so a profile kept only there is invisible to everyone who reads your feed.
    case 'profile': {
      await peer.update()
      return a.feed ? (await peer.profileOf(a.feed)) || { name: null, avatar: null } : peer.profile()
    }
    case 'profiles': {
      await peer.update()
      await node.names.update()
      const profiles = await peer.profiles()
      // ⛔ The HANDLE is attached here so a byline can prefer it over the self-label. Resolution
      // order is handle -> self-label -> key: one is claimed in a shared log, one is what a feed
      // says about itself, and the third is the only unforgeable thing on the row.
      //
      // ⛔ CONFIRMED claims only. A claim names a feed; it does not prove the feed agrees, and this
      // loop used to attach any claim that pointed at one — so claiming a name at someone else's
      // feed key renamed them in every byline on the page. That is the attack arriving through the
      // display layer rather than the log, and `confirmedHandles` is the one place it is refused.
      for (const [feed, handle] of Object.entries(await node.confirmedHandles())) {
        if (profiles[feed]) profiles[feed].handle = handle
        else profiles[feed] = { name: null, avatar: null, handle }
      }
      return profiles
    }
    case 'set-profile': { const r = await peer.setProfile(a); await peer.update(); return r }
    // Which networks this identity publishes to. ⚠ A preference, not a bridge — see peer.reach().
    case 'reach': return peer.reach()
    case 'set-reach': { const r = await peer.setReach(a); await peer.update(); return r }
    // Bytes cross the socket as base64 — it is a local UNIX socket carrying a small image, and a
    // second transport for one field would be more moving parts than the field is worth.
    // ── files ───────────────────────────────────────────────────────────────────────────────
    //
    // ⛔ PATHS cross this socket, never bytes. Both ends are on one machine — it is a unix socket —
    // and `registerFile` takes a path, so sending a file through here as base64 meant encoding it
    // to hand it to a process that wrote it straight back to disk. The bytes themselves move
    // between PEERS over hyper-overlay, chunked and content-addressed, and always did.
    case 'avatar-put': {
      const r = await node.avatars.put(a.path)
      // The oid goes into the PUBLISHED profile, so the picture travels with the feed.
      await peer.setProfile({ avatar: r.oid })
      await peer.update()
      return r
    }
    // ⛔ A SECOND PICTURE, stored by the SAME machinery. `avatars.put` types from magic bytes and
    // registers in the overlay — none of which is avatar-specific — so a banner needs a verb, not a
    // mechanism. It is a separate verb rather than `avatar-put {field}` because the two are
    // different acts and a mistyped field name would silently replace the wrong picture.
    case 'banner-put': {
      const r = await node.avatars.put(a.path)
      await peer.setProfile({ banner: r.oid })
      await peer.update()
      return r
    }
    case 'avatar-get': {
      const r = await node.avatars.get(a.oid)
      return r || { found: false }
    }
    // A file embedded in a record: `![alt](favela://blob/<oid>)`. No cap — the overlay chunks.
    case 'file-put': return node.files.put(a.path)
    /**
     * ⛔ THE PYRAMID, stored and pointed at in one act (SOCIAL-MODEL §6.6.1).
     *
     * The caller has already encoded the levels — that is authoring-time work and happens where the
     * encoder is: `<canvas>` in the browser for images, system ffmpeg for video. This stores each
     * whole file in the overlay exactly as a single blob is stored, then publishes ONE pointer
     * naming them. Bridges then SELECT a level rather than re-encoding per delivery.
     *
     * ⚠ The BASE oid is the key and is what a record references, so `![alt](favela://blob/<oid>)`
     * is unchanged and every reader that knows nothing of levels still resolves the author's own
     * bytes. Levels are additive; nothing already written moves.
     */
    case 'file-put-levels': {
      const input = Array.isArray(a.levels) ? a.levels : []
      if (!input.length) throw new Error('file-put-levels: at least one level is required')
      const stored = []
      for (const l of input) {
        if (!l || !l.path) throw new Error('file-put-levels: every level needs a path')
        const r = await node.files.put(l.path)
        stored.push({ oid: r.oid, w: Number(l.w) || 0, h: Number(l.h) || 0, size: r.size, by: l.by || AUTHOR })
      }
      // Largest first — makePointer sorts — so the base is level 0 and is the author's own bytes.
      // The type comes from the BASE level's bytes — files.put already sniffed it, and every level
      // of one asset is the same kind of thing.
      const baseInfo = await node.files.get(stored[0].oid)
      const pointer = makePointer({ mimeType: (baseInfo && baseInfo.mimeType) || '', size: stored[0].size, levels: stored })
      const base = pointer.levels[0]
      await peer.putBlobPointer(base.oid, pointer)
      await peer.update()
      return { oid: base.oid, pointer }
    }
    /**
     * ⛔ DERIVE the missing rungs of a video's pyramid, on a node that HAS an encoder.
     *
     * §6.6.4: the pyramid is allowed to be PARTIAL — a handset posts one level and a well-resourced
     * peer fills in the rest later. That is why this exists as a verb rather than happening on
     * write: the node that stored the bytes is not necessarily the node that can encode.
     *
     * ⛔ EVERY LEVEL IT MAKES IS `derived:`, NEVER the author's. Deriving is not authoring — a peer
     * adding a rung has produced a derived artifact and says so, or an author's bytes and somebody
     * else's re-encode become indistinguishable. §8's rule cost two of wj62's posts.
     *
     * ⚠ Says plainly when it cannot: a node without ffmpeg is an ordinary node, not a broken one.
     */
    case 'blob-derive': {
      const info = await node.files.get(a.oid)
      if (!info) return { ok: false, reason: 'no such blob' }
      if (!/^video\//.test(info.mimeType || '')) return { ok: false, reason: 'only video is derived here — images are encoded at authoring' }
      if (!await ffmpegAvailable()) return { ok: false, reason: 'no ffmpeg on this node — a level can be derived on one that has it' }

      const dims = await probe(info.path)
      if (!dims || !dims.height) return { ok: false, reason: 'could not probe the video' }

      const existing = await peer.blobPointer(a.oid)
      const have = new Set(((existing && existing.levels) || []).map((l) => l.h))
      const want = planLevels({ mimeType: info.mimeType, width: dims.width, height: dims.height })
        .filter((l) => !have.has(l.edge))
      if (!want.length) return { ok: true, added: 0, reason: 'every level this ladder defines already exists' }

      const me = b4a.toString(peer.feed.key, 'hex').slice(0, 8)
      const levels = ((existing && existing.levels) || [])
      if (!levels.length) levels.push({ oid: a.oid, w: dims.width, h: dims.height, size: info.size, by: AUTHOR })

      let added = 0
      for (const l of want) {
        const enc = await encodeLevel(info.path, l)
        if (!enc) continue
        // ⛔ A RUNG BIGGER THAN THE SOURCE IS NOT A RUNG. A fixed bitrate applied to already-small
        // or highly-compressible video produces a "smaller" level that costs more bytes and buys
        // nothing — measured on a synthetic 720p clip whose 480p rung came out larger. The browser
        // path guards the same way for the same reason.
        if (enc.size >= info.size) { try { fs.unlinkSync(enc.path) } catch {}; continue }
        const stored = await node.files.put(enc.path)
        try { fs.unlinkSync(enc.path) } catch {}
        levels.push({ oid: stored.oid, w: 0, h: l.edge, size: stored.size, by: derivedBy(me) })
        added++
      }
      if (!added) return { ok: false, reason: 'ffmpeg produced no usable level' }

      const pointer = makePointer({ mimeType: info.mimeType, size: info.size, levels })
      await peer.putBlobPointer(a.oid, pointer)
      await peer.update()
      return { ok: true, added, pointer }
    }
    /** The pointer for a blob — null when it has only the one level it was written with. */
    case 'blob-pointer': return (a.feed ? peer.blobPointerOf(a.feed, a.oid) : peer.blobPointer(a.oid)) || null
    /**
     * The level nearest a caller's ceiling, for a blob of any feed this node holds.
     *
     * ⛔ THE BRIDGE SUPPLIES THE CEILING; the choice is still the bridge's. Doing the arithmetic
     * here rather than in each bridge keeps ONE ladder — the alternative is the same table written
     * three times, which is how this monorepo got six copies of a signing format.
     *
     * ⚠ A blob with no pointer answers with its own single level, because that is what it has
     * (record-vocabulary §3.5: today's avatar is the single-level special case). Absence of a
     * pointer is never an error.
     */
    case 'blob-level': {
      const pointer = a.feed ? await peer.blobPointerOf(a.feed, a.oid) : await peer.blobPointer(a.oid)
      if (!pointer) {
        const info = await node.files.get(a.oid)
        if (!info) return { level: null, reason: 'no such blob' }
        const fits = (info.size || 0) <= (a.maxBytes || Infinity)
        return fits
          ? { level: { oid: a.oid, w: 0, h: 0, size: info.size, by: AUTHOR }, reason: 'single level' }
          : { level: null, reason: 'the only level is ' + info.size + ' bytes against a ceiling of ' + a.maxBytes }
      }
      return selectLevel(pointer, { maxBytes: a.maxBytes || Infinity, maxEdge: a.maxEdge || Infinity })
    }
    case 'file-get': {
      const r = await node.files.get(a.oid)
      return r || { found: false }
    }

    case 'browser-auth': return node.browsers.authenticate(a)
    case 'browsers': return { browsers: await node.browsers.list() }
    case 'browser-revoke': return node.browsers.revoke(a.publicKey)
    case 'browser-status': return node.browsers.status(a.publicKey)

    // ── names ───────────────────────────────────────────────────────────────────────────────
    //
    // ⛔ Every claim gets a number appended, always. There is no bare `alice` — the first claimant
    // is `alice0`, the next `alice1`. So a name is never scarce, nothing can be squatted, there is
    // no race to win, and no handle is privileged over another by being older.
    case 'namespace': {
      await node.names.update()
      const founder = await node.names.founder()
      return {
        key: node.names.key.toString('hex'),
        claims: (await node.names.list()).length,
        // ⛔ Reported, because "I resolve handles here" and "I carry this log" are different facts
        // and are fixed by completely different things. A node with no weight is not broken — it is
        // a reader — but nobody can tell from a key alone.
        founded: !!founder,
        founder: founder ? founder.root : null,
        admins: await node.names.admins(),
        writers: await node.names.writers(),
        requests: await node.names.requests(),
        carrying: node.names.base.isIndexer,
        writable: node.names.base.writable
      }
    }
    // Resolve handles in a different namespace from now on. The one we leave stays on disk.
    // ⛔ EVERY namespace this node holds, so "you can come back to it" is a door rather than a
    // promise. `namespaceSettings` has always said the one you leave stays on disk; without this
    // there was no way to learn what it was, and a 64-hex key someone has to have written down is
    // not a way back.
    case 'namespaces': {
      const { loadNamespaces } = await import('./device.js')
      const cur = node.names ? node.names.key.toString('hex') : null
      return {
        current: cur,
        namespaces: loadNamespaces(node.storage).map((n) => ({ ...n, current: n.key === cur }))
      }
    }
    // ⛔ FORGETS THE INDEX ENTRY, NEVER THE LOG. The slot stays on disk with every claim in it —
    // this removes it from the list, and joining by key again finds the same slot through the index
    // it was just dropped from... which it cannot. Stated plainly rather than discovered: forgetting
    // the CURRENT namespace is refused, and forgetting a founded one loses the only record of its
    // legacy slot, so that is refused too.
    case 'namespace-forget': {
      const { loadNamespaces, saveNamespaces } = await import('./device.js')
      const key = String(a.key || '').toLowerCase()
      const cur = node.names ? node.names.key.toString('hex') : null
      if (key === cur) throw new Error('that is the namespace this node is in — join another first')
      const known = loadNamespaces(node.storage)
      const entry = known.find((n) => n.key === key)
      if (!entry) throw new Error('this node does not hold that namespace')
      if (entry.slot === 'names') throw new Error('that is the namespace this node founded — forgetting it would lose the only record of where its claims live')
      saveNamespaces(node.storage, known.filter((n) => n.key !== key))
      return { forgotten: key, note: 'the log stays on disk; join by key to return to it' }
    }
    case 'namespace-join': return node.joinNamespace(a.key)
    // Ask to carry this namespace's log. Grants nothing; an admin has to answer.
    case 'namespace-request': { await node.names.request(node.cred, a.token); await node.names.update(); return { asked: node.names.base.local.key.toString('hex'), token: a.token ? 'redeemed' : null } }
    // ⛔ Minted over the daemon's UDS, which the filesystem already gates to the owner — so this is
    // already "a human at this machine", which is the gate the token stands in for elsewhere.
    case 'namespace-invite': return node.names.invite(node.cred)
    case 'namespace-uninvite': { await node.names.withdrawInvite(a.hash, node.cred); await node.names.update(); return { withdrawn: a.hash } }
    case 'namespace-invites': { await node.names.update(); return { invites: await node.names.invites() } }
    // ⛔ Answer an ask. Refuses loudly when this node carries no weight, because a grant riding a
    // weightless block is capped to zero SILENTLY (see namespace.js).
    case 'namespace-admit': { await node.names.admitWriter(a.writer, node.cred); await node.names.update(); return { admitted: a.writer } }
    case 'namespace-grant': { await node.names.grantAdmin(a.root, node.cred); await node.names.update(); return { admin: a.root } }
    case 'namespace-revoke': { await node.names.revokeDevice(a.device, node.cred); await node.names.update(); return { revoked: a.device } }
    case 'claim-name': {
      await node.names.claim(a.name, node.key, node.cred)
      await node.names.update()
      return { handle: await node.names.handleFor(node.key) }
    }
    case 'release-name': { await node.names.release(a.handle, node.cred); await node.names.update(); return { released: a.handle } }
    case 'rename': {
      await node.names.rename(a.handle, a.name, node.key, node.cred)
      await node.names.update()
      return { handle: await node.names.handleFor(node.key) }
    }
    case 'handles': return { handles: await node.handles() }
    // ⛔ Goes through the NODE, not the namespace: resolving a handle means checking the claim's
    // anchor against the feed's own founder, and only the node holds feeds. `names.resolve` alone
    // reports who CLAIMED a name, never whether the claim is true.
    case 'resolve': return (await node.resolveHandle(a.handle)) || { found: false }
    case 'search-names': return { handles: await node.handles(a.query) }

    // ── device pairing ──────────────────────────────────────────────────────────────────────
    case 'pair-offer': return node.pairOffer(a)
    case 'pair-status': return node.pairStatus()
    case 'pair-cancel': return node.pairCancel()
    case 'pair-accept': return node.pairAccept(a.invite)
    // ⚠ Reported, never folded into a listing: a record we hold a pointer for and cannot read is
    // not an absent record, and the CLI must be able to say which.
    case 'unresolved': return peer.unresolved()

    // ⛔ WHICH TRIGGER IS ACTUALLY FIRING. Built because three fixes in a row shipped a mirror
    // trigger that never fired, and nothing on a running node could say so — each time the only
    // evidence was how LONG a record took to appear, which is inference, not measurement. If
    // `append` and `connection` stay at zero while `stale` climbs, the events are dead. That is now
    // a fact you can read.
    case 'mirror-stats': return node._mirror ? node._mirror.stats() : { mirror: 'not running' }
    default:
      throw new Error('unknown command: ' + cmd)
  }
}

const view = (r, mine = null) => ({
  author: r.author,
  collection: r.collection,
  name: r.name,
  createdAt: r.record && r.record.frontmatter && r.record.frontmatter.createdAt,
  body: r.record && r.record.body,
  hits: r.hits,
  // ⛔ What *I* have already put on this row. Without it a surface renders the same button whether
  // or not I reacted, and pressing it twice looks the same as pressing it once. Computed from ONE
  // listing of my own feed for the whole page, never a lookup per row.
  //
  // ⚠ MINE only. Everyone else's reactions need a scan across held feeds, which is per-target work
  // (`reactions`) and belongs on a single-post view, not on every row of a timeline.
  ...(mine === null
    ? {}
    : (() => {
        const values = mine.get(Peer.targetId({ feed: r.author, collection: r.collection, name: r.name }))
        return { reactions: values ? [...values] : [], liked: !!values && values.has(LIKE_VALUE) }
      })())
})

// Stamp a page of rows with my own reactions AND reposts. One listing of each, for the page —
// never a lookup per row.
const withLikes = async (peer, rows) => {
  const mine = await peer.myReactions()
  const reposted = await peer.myReposts()
  return rows.map((r) => {
    const v = view(r, mine)
    v.reposted = reposted.has(Peer.targetId({ feed: r.author, collection: r.collection, name: r.name }))
    return v
  })
}
// transcode.js — deriving the video rungs of a mip pyramid with the SYSTEM ffmpeg.
//
// ⛔ A SYSTEM BINARY, not a native addon, and that is the measured choice rather than a default.
// SOCIAL-MODEL §6.6.5: hypernote already ships this exact shape (`lib/media/video-processor.js`
// spawns `ffmpeg`), and `bare-ffmpeg`'s android-arm64 prebuild is 29 MB against an APK that
// hyper-social #39 fought from 527 MB down to 117 MB. It is "a capability purchase, not a
// requirement" — buy it where the ladder is actually produced, which today is a server.
//
// ⛔ SO THE PYRAMID IS ALLOWED TO BE PARTIAL (§6.6.4). A phone posts one level; a peer that HAS an
// encoder derives the rest later. That is why every level records who made it: a level this file
// produces is `derived:`, never the author's, and §8's "never re-encode a record you did not author"
// is the rule it exists to keep enforceable.
//
// ⚠ Video is the case §6.6.5 says still needs a decision on handsets. This solves the server case
// only, and says so rather than implying the whole problem is closed.

import { spawn } from 'bare-subprocess'
import fs from '#node:fs'
import { join } from '#node:path'
import os from '#node:os'
import process from '#node:process'

// ⛔ INJECTABLE, like every other module in this fleet that touches the outside: a routing surface
// is testable with no port bound because the network is a parameter, and spawning is the same kind
// of edge. It also matters concretely — a test that really spawns ffmpeg passes and then holds the
// runner open for ever, so a suite containing one cannot be the suite people run.
//
// ⚠ The projection layer is deliberately not named here, even in prose: test/boundary.test.js scans
// this directory for its vocabulary, and it caught this comment. That is the test working.
export const systemRun = (cmd, args) => new Promise((resolve) => {
  let out = ''
  let err = ''
  let proc
  try {
    proc = spawn(cmd, args)
  } catch (e) {
    // ⛔ A MISSING BINARY IS AN ANSWER, not a crash. A node without ffmpeg simply cannot derive a
    // level, and the caller must be able to say that plainly.
    return resolve({ code: -1, out: '', err: 'cannot run ' + cmd + ': ' + ((e && e.message) || e) })
  }
  proc.stdout && proc.stdout.on('data', (c) => { out += c })
  proc.stderr && proc.stderr.on('data', (c) => { err += c })
  proc.on('error', (e) => resolve({ code: -1, out, err: (e && e.message) || String(e) }))
  // ⚠ `close`, not `exit`. exit fires when the child dies; close fires once its stdio has drained,
  // and resolving on the earlier one left the streams open — the suite passed and then hung for
  // five minutes with nothing to say it had finished.
  proc.on('close', (code) => {
    // ⛔ DESTROY THE PIPES. A closed child still leaves its stdio handles registered with the loop,
    // and bare will not exit while they are open — the suite passed and then hung, which reads
    // exactly like a failure and is not one.
    for (const st of [proc.stdout, proc.stderr, proc.stdin]) { try { st && st.destroy() } catch {} }
    resolve({ code, out, err })
  })
})

// ⛔ ENV-OVERRIDABLE, AND READ PER CALL. `rpc.js` invokes `available()`, `probe(path)` and
// `encodeLevel(path, level)` with no options, so every caller takes these defaults. On a server that
// is correct — the binary is on PATH. An APK has no PATH: its bundled binary sits at an absolute
// path the app only learns at runtime (the extracted native-lib dir), so without an override there
// is no way to name it and the node can never derive a rung however many binaries it ships.
//
// ⚠ Read INSIDE the function, not captured at import. The Android host sets these while booting the
// worklet, which happens after this module is imported — a value captured at module scope would be
// the default for ever, which is the "env before any import that reads it" trap favela-android's
// node.js already documents.
//
// ⚠ Absent or empty falls back to the previous literals, byte for byte. The three hosts running this
// daemon must see no change.
const ffmpegBin = () => process.env.FAVELA_FFMPEG || 'ffmpeg'
const ffprobeBin = () => process.env.FAVELA_FFPROBE || 'ffprobe'

export async function available ({ ffmpegPath = ffmpegBin(), ffprobePath = ffprobeBin(), run = systemRun } = {}) {
  const a = await run(ffmpegPath, ['-version'])
  const b = await run(ffprobePath, ['-version'])
  return a.code === 0 && b.code === 0
}

/** `{ width, height, duration }` or null. Never throws. */
export async function probe (path, { ffprobePath = ffprobeBin(), run = systemRun } = {}) {
  const r = await run(ffprobePath, [
    '-v', 'error', '-select_streams', 'v:0',
    '-show_entries', 'stream=width,height', '-show_entries', 'format=duration',
    '-of', 'json', path
  ])
  if (r.code !== 0) return null
  try {
    const j = JSON.parse(r.out)
    const s = (j.streams && j.streams[0]) || {}
    const width = Number(s.width) || 0
    const height = Number(s.height) || 0
    // ⛔ NO VIDEO STREAM IS NOT A ZERO-SIZED VIDEO. Returning {width:0,height:0} let an audio file or
    // a corrupt container look like a probe that succeeded, and planLevels would then answer "no
    // rungs" for the same reason it answers that for a tiny clip — two very different facts wearing
    // one shape.
    if (!width || !height) return null
    return { width, height, duration: Number(j.format && j.format.duration) || 0 }
  } catch { return null }
}

/**
 * Encode ONE rung. Resolves `{ path, width, height }` or null — never throws.
 *
 * ⚠ `-2` on the width keeps the aspect and rounds to an even number, which h264 requires; asking
 * for an odd width fails at encode time with a message about chroma that reads like a corrupt file.
 */
export async function encodeLevel (srcPath, level, { ffmpegPath = ffmpegBin(), dir = null, run = systemRun, stat = null } = {}) {
  const out = join(dir || os.tmpdir(), 'favela-level-' + level.name + '-' + Date.now() + '.mp4')
  const r = await run(ffmpegPath, [
    '-y', '-i', srcPath,
    '-vf', 'scale=-2:' + level.edge,
    '-c:v', 'libx264', '-profile:v', level.profile,
    '-b:v', level.bitrate, '-maxrate', level.maxrate, '-bufsize', level.maxrate,
    '-c:a', 'aac', '-b:a', '128k',
    // faststart so a player can begin before the whole file arrives — the same reason HLS exists.
    '-movflags', '+faststart',
    out
  ])
  if (r.code !== 0) { try { fs.unlinkSync(out) } catch {}; return null }
  let size = 0
  try { size = stat ? stat(out) : fs.statSync(out).size } catch { return null }
  if (!size) { try { fs.unlinkSync(out) } catch {}; return null }
  return { path: out, width: 0, height: level.edge, size }
}

export default { available, probe, encodeLevel, systemRun }
// Transport — how a feed meets its peers.
//
// Injected, exactly as the KV was. Nothing above this file knows what a socket is: `Feed.replicate`
// takes a boolean or a duplex, and a transport's whole job is to produce one and hand it over. That
// is what makes the alternatives — mDNS on a LAN (`hyperdht-mdns`), BLE (`ble-swarm`,
// `nearby-discovery`) — additions rather than rewrites, which matters because bootstrap is the
// weakest part of this design and the DHT is not the only answer to it.

import Protomux from 'protomux'

/**
 * SwarmTransport — Hyperswarm over the DHT.
 *
 * ⚠ NOT COVERED BY THE TEST SUITE. It needs a live DHT, and a test that needs one is a test that
 * fails for reasons unrelated to the code. What IS covered is everything it feeds: replication is
 * proven over an in-memory duplex pair in replicate.test.js, which is the same seam this hands a
 * socket to. This file is glue, and it is marked as such rather than counted as tested.
 */
export class SwarmTransport {
  // `swarm` is a Hyperswarm instance, injected so this file does not own its lifecycle either.
  constructor (swarm, opts = {}) {
    this.swarm = swarm
    // Off by default; the daemon turns it on with FAVELA_DEBUG. A transport that fails silently is
    // exactly what shipped here — a follow that records and replicates nothing, with no signal.
    this._log = opts.log || (() => {})
    // ⛔ The overlay must attach to EVERY connection or the log replicates and the bytes never do.
    // That failure is silent and looks like "nothing arrived": the follower converges the pointer
    // set, reports every record as UNRESOLVED, and shows an empty timeline. Measured 2026-09-06 —
    // two daemons, swarm connected, feeds replicating, zero bytes, because this line did not exist.
    this.overlay = opts.overlay || null
    this.feeds = new Map() // discovery key hex -> Feed
    this._onconnection = (socket) => {
      // Pass the SOCKET, never a boolean, and never pipe by hand.
      //
      // Two bugs lived here and neither showed up in a test, because the suite replicates over a
      // dedicated stream pair where a boolean is right:
      //   1. `replicate(false)` hardcoded BOTH ends as responder, so no handshake ever completed.
      //   2. Piping each feed separately onto one socket gives each its own stream over a shared
      //      transport. Hypercore multiplexes — every base attaches to the socket's existing muxer,
      //      which is what lets one connection carry all of them.
      // Symptom of either: a follow that records fine and replicates nothing, silently, forever.
      let n = 0
      for (const [, feed] of this.feeds) {
        try { feed.replicate(socket); n++ } catch (err) { this._log('replicate-failed', err.message) }
      }
      let overlay = false
      if (this.overlay) {
        try { this.overlay.attachProtocol(Protomux.from(socket)); overlay = true } catch (err) { this._log('overlay-attach-failed', { err: err.message }) }
      }
      this._log('connection', { feeds: n, overlay })
    }
    this.swarm.on('connection', this._onconnection)
  }

  // Announce and look for peers of this feed. The topic is the feed's discovery key — derived from
  // its public key, so it reveals which feed you want without revealing that you can write to it.
  async join (feed) {
    const dk = feed.discoveryKey
    const id = Buffer.from(dk).toString('hex')
    if (this.feeds.has(id)) return
    this.feeds.set(id, feed)
    const discovery = this.swarm.join(dk, { server: true, client: true })
    // Attach to connections we ALREADY have — a feed held after boot (a fresh follow) would
    // otherwise wait for a new connection that may never come.
    let existing = 0
    for (const socket of this.swarm.connections) { try { feed.replicate(socket); existing++ } catch {} }
    // ⛔ NOT awaited. `flushed()` waits for the DHT to acknowledge the announce, and its result is
    // never used — replication to peers we already have is set up on the line above, and new peers
    // arrive on the 'connection' handler whenever the announce lands. Awaiting it put a DHT round
    // trip inside every feed open: a socket request that hung, and a boot that paid it once per
    // followed feed, in series.
    discovery.flushed().then(
      () => this._log('join', { topic: id.slice(0, 12), existingConns: existing }),
      (err) => this._log('join-flush-failed', { topic: id.slice(0, 12), err: err.message })
    )
  }

  async leave (feed) {
    const id = Buffer.from(feed.discoveryKey).toString('hex')
    if (!this.feeds.delete(id)) return
    await this.swarm.leave(feed.discoveryKey)
    // ⚠ Leaving stops ANNOUNCING. It does not un-replicate what a peer already has, and it does not
    // close live connections. Same shape as delisting: propagation stops, nothing is retracted.
  }

  async destroy () {
    this.swarm.off('connection', this._onconnection)
    this.feeds.clear()
    await this.swarm.destroy()
  }
}
const { Pull, Push, HEADERBYTES, KEYBYTES, ABYTES } = require('sodium-secretstream')
const sodium = require('sodium-universal')
const crypto = require('hypercore-crypto')
const { Duplex, Writable, getStreamError } = require('streamx')
const b4a = require('b4a')
const Timeout = require('timeout-refresh')
const unslab = require('unslab')
const Bridge = require('./lib/bridge')
const Handshake = require('./lib/handshake')

const IDHEADERBYTES = HEADERBYTES + 32
const [NS_INITIATOR, NS_RESPONDER, NS_SEND] = crypto.namespace('hyperswarm/secret-stream', 3)
const MAX_ATOMIC_WRITE = 256 * 256 * 256 - 1

module.exports = class NoiseSecretStream extends Duplex {
  constructor(isInitiator, rawStream, opts = {}) {
    super({ mapWritable: toBuffer })

    if (typeof isInitiator !== 'boolean') {
      throw new Error('isInitiator should be a boolean')
    }

    this.noiseStream = this
    this.isInitiator = isInitiator
    this.rawStream = null

    this.publicKey = opts.publicKey || null
    this.remotePublicKey = opts.remotePublicKey || null
    this.handshakeHash = null
    this.connected = false
    this.keepAlive = opts.keepAlive || 0
    this.timeout = 0
    this.enableSend = opts.enableSend !== false

    // pointer for upstream to set data here if they want
    this.userData = null

    let openedDone = null
    this.opened = new Promise((resolve) => {
      openedDone = resolve
    })

    this.rawBytesWritten = 0
    this.rawBytesRead = 0

    // metadata used by 'hyperdht'
    this.relay = null
    this.puncher = null

    // unwrapped raw stream
    this._rawStream = null

    // handshake state
    this._handshake = null
    this._handshakePattern = opts.pattern || null
    this._handshakeDone = null

    // message parsing state
    this._state = 0
    this._len = 0
    this._tmp = 1
    this._message = null

    this._openedDone = openedDone
    this._startDone = null
    this._drainDone = null
    this._outgoingPlain = null
    this._outgoingWrapped = null
    this._utp = null
    this._setup = true
    this._ended = 2
    this._encrypt = null
    this._decrypt = null
    this._timeoutTimer = null
    this._keepAliveTimer = null
    this._sendState = null

    if (opts.autoStart !== false) this.start(rawStream, opts)

    // wiggle it to trigger open immediately (TODO add streamx option for this)
    this.resume()
    this.pause()
  }

  static keyPair(seed) {
    return Handshake.keyPair(seed)
  }

  static id(handshakeHash, isInitiator, id) {
    return streamId(handshakeHash, isInitiator, id)
  }

  setTimeout(ms) {
    if (!ms) ms = 0

    this._clearTimeout()
    this.timeout = ms

    if (!ms || this.rawStream === null) return

    this._timeoutTimer = Timeout.once(ms, destroyTimeout, this)
    this._timeoutTimer.unref()
  }

  setKeepAlive(ms) {
    if (!ms) ms = 0

    this._clearKeepAlive()

    this.keepAlive = ms

    if (!ms || this.rawStream === null) return

    this._keepAliveTimer = Timeout.on(ms, sendKeepAlive, this)
    this._keepAliveTimer.unref()
  }

  sendKeepAlive() {
    const empty = this.alloc(0)
    this.write(empty)
  }

  start(rawStream, opts = {}) {
    if (rawStream) {
      this.rawStream = rawStream
      this._rawStream = rawStream
      if (typeof this.rawStream.setContentSize === 'function') {
        this._utp = rawStream
      }
    } else {
      this.rawStream = new Bridge(this)
      this._rawStream = this.rawStream.reverse
    }

    this.rawStream.on('error', this._onrawerror.bind(this))
    this.rawStream.on('close', this._onrawclose.bind(this))

    this._startHandshake(opts.handshake, opts.keyPair || null)
    this._continueOpen(null)

    if (this.destroying) return

    if (opts.data) this._onrawdata(opts.data)
    if (opts.ended) this._onrawend()

    if (this.keepAlive > 0 && this._keepAliveTimer === null) {
      this.setKeepAlive(this.keepAlive)
    }

    if (this.timeout > 0 && this._timeoutTimer === null) {
      this.setTimeout(this.timeout)
    }
  }

  async flush() {
    if ((await this.opened) === false) return false
    if ((await Writable.drained(this)) === false) return false
    if (this.destroying) return false

    if (this.rawStream !== null && this.rawStream.flush) {
      return await this.rawStream.flush()
    }

    return true
  }

  _continueOpen(err) {
    if (err) this.destroy(err)
    if (this._startDone === null) return
    const done = this._startDone
    this._startDone = null
    this._open(done)
  }

  _onkeypairpromise(p) {
    const self = this
    const cont = this._continueOpen.bind(this)

    p.then(onkeypair, cont)

    function onkeypair(kp) {
      self._onkeypair(kp)
      cont(null)
    }
  }

  _onkeypair(keyPair) {
    const pattern = this._handshakePattern || 'XX'
    const remotePublicKey = this.remotePublicKey

    this._handshake = new Handshake(this.isInitiator, keyPair, remotePublicKey, pattern)
    this.publicKey = this._handshake.keyPair.publicKey
  }

  _startHandshake(handshake, keyPair) {
    if (handshake) {
      const { tx, rx, hash, publicKey, remotePublicKey } = handshake
      this._setupSecretStream(tx, rx, hash, publicKey, remotePublicKey)
      return
    }

    if (!keyPair) keyPair = Handshake.keyPair()

    if (typeof keyPair.then === 'function') {
      this._onkeypairpromise(keyPair)
    } else {
      this._onkeypair(keyPair)
    }
  }

  _onrawerror(err) {
    this.destroy(err)
  }

  _onrawclose() {
    if (this._ended !== 0) this.destroy()
  }

  _onrawdata(data) {
    let offset = 0

    if (this._timeoutTimer !== null) {
      this._timeoutTimer.refresh()
    }

    do {
      switch (this._state) {
        case 0: {
          while (this._tmp !== 0x1000000 && offset < data.byteLength) {
            const v = data[offset++]
            this._len += this._tmp * v
            this._tmp *= 256
          }

          if (this._tmp === 0x1000000) {
            this._tmp = 0
            this._state = 1
            const unprocessed = data.byteLength - offset
            if (unprocessed < this._len && this._utp !== null)
              this._utp.setContentSize(this._len - unprocessed)
          }

          break
        }

        case 1: {
          const missing = this._len - this._tmp
          const end = missing + offset

          if (this._message === null && end <= data.byteLength) {
            this._message = data.subarray(offset, end)
            offset += missing
            this._incoming()
            break
          }

          const unprocessed = data.byteLength - offset

          if (this._message === null) {
            this._message = b4a.allocUnsafe(this._len)
          }

          b4a.copy(data, this._message, this._tmp, offset)
          this._tmp += unprocessed

          if (end <= data.byteLength) {
            offset += missing
            this._incoming()
          } else {
            offset += unprocessed
          }

          break
        }
      }
    } while (offset < data.byteLength && !this.destroying)
  }

  _onrawend() {
    this._ended--
    this.push(null)
  }

  _onrawdrain() {
    const drain = this._drainDone
    if (drain === null) return
    this._drainDone = null
    drain()
  }

  _read(cb) {
    this.rawStream.resume()
    cb(null)
  }

  _incoming() {
    const message = this._message

    this._state = 0
    this._len = 0
    this._tmp = 1
    this._message = null

    if (this._setup === true) {
      if (this._handshake) {
        this._onhandshakert(this._handshake.recv(message))
      } else {
        if (message.byteLength !== IDHEADERBYTES) {
          this.destroy(new Error('Invalid header message received'))
          return
        }

        const remoteId = message.subarray(0, 32)
        const expectedId = streamId(this.handshakeHash, !this.isInitiator)
        const header = message.subarray(32)

        if (!b4a.equals(expectedId, remoteId)) {
          this.destroy(new Error('Invalid header received'))
          return
        }

        this._decrypt.init(header)
        this._setup = false // setup is now done
      }
      return
    }

    if (message.byteLength < ABYTES) {
      this.destroy(new Error('Invalid message received'))
      return
    }

    this.rawBytesRead += message.byteLength

    const plain = message.subarray(1, message.byteLength - ABYTES + 1)

    try {
      this._decrypt.next(message, plain)
    } catch (err) {
      this.destroy(err)
      return
    }

    // If keep alive is selective, eat the empty buffers (ie assume the other side has it enabled also)
    if (plain.byteLength === 0 && this.keepAlive !== 0) return

    if (this.push(plain) === false) {
      this.rawStream.pause()
    }
  }

  _onhandshakert(h) {
    if (this._handshakeDone === null) return

    if (h !== null) {
      if (h.data) this._rawStream.write(h.data)
      if (!h.tx) return
    }

    const done = this._handshakeDone
    const publicKey = this._handshake.keyPair.publicKey

    this._handshakeDone = null
    this._handshake = null

    if (h === null) return done(new Error('Noise handshake failed'))

    this._setupSecretStream(h.tx, h.rx, h.hash, publicKey, h.remotePublicKey)
    this._resolveOpened(true)
    done(null)
  }

  _setupSecretStream(tx, rx, handshakeHash, publicKey, remotePublicKey) {
    const buf = b4a.allocUnsafeSlow(3 + IDHEADERBYTES)
    writeUint24le(IDHEADERBYTES, buf)

    this._encrypt = new Push(unslab(tx.subarray(0, KEYBYTES)), undefined, buf.subarray(3 + 32))
    this._decrypt = new Pull(unslab(rx.subarray(0, KEYBYTES)))

    this.publicKey = publicKey
    this.remotePublicKey = remotePublicKey
    this.handshakeHash = handshakeHash

    const id = buf.subarray(3, 3 + 32)
    streamId(handshakeHash, this.isInitiator, id)

    // initialize secretbox state for unordered messages
    this._setupSecretSend(handshakeHash)

    this.emit('handshake')
    // if rawStream is a bridge, also emit it there
    if (this.rawStream !== this._rawStream) this.rawStream.emit('handshake')

    if (this.destroying) return

    this._rawStream.write(buf)
  }

  _setupSecretSend(handshakeHash) {
    this._sendState = b4a.allocUnsafeSlow(32 + 32 + 8 + 8)
    const encrypt = this._sendState.subarray(0, 32) // secrets
    const decrypt = this._sendState.subarray(32, 64)
    const counter = this._sendState.subarray(64, 72) // nonce
    const initial = this._sendState.subarray(72)

    const inputs = this.isInitiator
      ? [
          [NS_INITIATOR, NS_SEND],
          [NS_RESPONDER, NS_SEND]
        ]
      : [
          [NS_RESPONDER, NS_SEND],
          [NS_INITIATOR, NS_SEND]
        ]

    sodium.crypto_generichash_batch(encrypt, inputs[0], handshakeHash)
    sodium.crypto_generichash_batch(decrypt, inputs[1], handshakeHash)

    sodium.randombytes_buf(initial)
    counter.set(initial)
  }

  _open(cb) {
    // no autostart or no handshake yet
    if (this._rawStream === null || (this._handshake === null && this._encrypt === null)) {
      this._startDone = cb
      return
    }

    this._rawStream.on('data', this._onrawdata.bind(this))
    this._rawStream.on('end', this._onrawend.bind(this))
    this._rawStream.on('drain', this._onrawdrain.bind(this))

    if (this.enableSend) this._rawStream.on('message', this._onmessage.bind(this))

    if (this._encrypt !== null) {
      this._resolveOpened(true)
      return cb(null)
    }

    this._handshakeDone = cb

    if (this.isInitiator) this._onhandshakert(this._handshake.send())
  }

  _predestroy() {
    if (this.rawStream) {
      const error = getStreamError(this)
      this.rawStream.destroy(error)
    }

    if (this._startDone !== null) {
      const done = this._startDone
      this._startDone = null
      done(new Error('Stream destroyed'))
    }

    if (this._handshakeDone !== null) {
      const done = this._handshakeDone
      this._handshakeDone = null
      done(new Error('Stream destroyed'))
    }

    if (this._drainDone !== null) {
      const done = this._drainDone
      this._drainDone = null
      done(new Error('Stream destroyed'))
    }
  }

  _write(data, cb) {
    let wrapped = this._outgoingWrapped

    if (data !== this._outgoingPlain) {
      wrapped = b4a.allocUnsafe(data.byteLength + 3 + ABYTES)
      wrapped.set(data, 4)
    } else {
      this._outgoingWrapped = this._outgoingPlain = null
    }

    if (wrapped.byteLength - 3 > MAX_ATOMIC_WRITE) {
      return cb(
        new Error(
          'Message is too large for an atomic write. Max size is ' + MAX_ATOMIC_WRITE + ' bytes.'
        )
      )
    }
    this.rawBytesWritten += wrapped.byteLength

    writeUint24le(wrapped.byteLength - 3, wrapped)
    // offset 4 so we can do it in-place
    this._encrypt.next(wrapped.subarray(4, 4 + data.byteLength), wrapped.subarray(3))

    if (this._keepAliveTimer !== null) this._keepAliveTimer.refresh()

    if (this._rawStream.write(wrapped) === false) {
      this._drainDone = cb
    } else {
      cb(null)
    }
  }

  _final(cb) {
    this._clearKeepAlive()
    this._ended--
    this._rawStream.end()
    cb(null)
  }

  _resolveOpened(val) {
    if (this._openedDone === null) return
    const opened = this._openedDone
    this._openedDone = null
    opened(val)
    if (!val) return
    this.connected = true
    this.emit('connect')
  }

  _clearTimeout() {
    if (this._timeoutTimer === null) return
    this._timeoutTimer.destroy()
    this._timeoutTimer = null
    this.timeout = 0
  }

  _clearKeepAlive() {
    if (this._keepAliveTimer === null) return
    this._keepAliveTimer.destroy()
    this._keepAliveTimer = null
    this.keepAlive = 0
  }

  _destroy(cb) {
    this._clearKeepAlive()
    this._clearTimeout()
    this._resolveOpened(false)
    cb(null)
  }

  _boxMessage(buffer) {
    const MB = sodium.crypto_secretbox_MACBYTES // 16
    const NB = sodium.crypto_secretbox_NONCEBYTES // 24

    const counter = this._sendState.subarray(64, 72)
    sodium.sodium_increment(counter)
    if (b4a.equals(counter, this._sendState.subarray(72))) {
      this.destroy(new Error('udp send nonce exchausted'))
      return
    }

    const secret = this._sendState.subarray(0, 32)
    const envelope = b4a.allocUnsafe(8 + MB + buffer.byteLength)
    const nonce = envelope.subarray(0, NB)
    const ciphertext = envelope.subarray(8)

    b4a.fill(nonce, 0) // pad suffix
    nonce.set(counter)

    sodium.crypto_secretbox_easy(ciphertext, buffer, nonce, secret)
    return envelope
  }

  send(buffer) {
    if (!this._sendState) return
    if (!this.rawStream?.send) return // udx-stream expected

    const message = this._boxMessage(buffer)
    return this.rawStream.send(message)
  }

  trySend(buffer) {
    if (!this._sendState) return
    if (!this.rawStream?.trySend) return // udx-stream expected

    const message = this._boxMessage(buffer)
    this.rawStream.trySend(message)
  }

  _onmessage(buffer) {
    if (!this._sendState) return // messages before handshake are dropped

    const MB = sodium.crypto_secretbox_MACBYTES // 16
    const NB = sodium.crypto_secretbox_NONCEBYTES // 24

    if (buffer.byteLength < NB) return // Invalid message

    const nonce = b4a.allocUnsafe(NB)
    b4a.fill(nonce, 0)
    nonce.set(buffer.subarray(0, 8))

    const secret = this._sendState.subarray(32, 64)
    const ciphertext = buffer.subarray(8)
    const plain = buffer.subarray(8, buffer.byteLength - MB)

    if (ciphertext.byteLength < MB) return // invalid message

    const success = sodium.crypto_secretbox_open_easy(plain, ciphertext, nonce, secret)

    if (success) this.emit('message', plain)
  }

  alloc(len) {
    const buf = b4a.allocUnsafe(len + 3 + ABYTES)
    this._outgoingWrapped = buf
    this._outgoingPlain = buf.subarray(4, buf.byteLength - ABYTES + 1)
    return this._outgoingPlain
  }

  toJSON() {
    return {
      isInitiator: this.isInitiator,
      publicKey: this.publicKey && b4a.toString(this.publicKey, 'hex'),
      remotePublicKey: this.remotePublicKey && b4a.toString(this.remotePublicKey, 'hex'),
      connected: this.connected,
      destroying: this.destroying,
      destroyed: this.destroyed,
      rawStream: this.rawStream && this.rawStream.toJSON ? this.rawStream.toJSON() : null
    }
  }
}

function writeUint24le(n, buf) {
  buf[0] = n & 255
  buf[1] = (n >>> 8) & 255
  buf[2] = (n >>> 16) & 255
}

function streamId(handshakeHash, isInitiator, out = b4a.allocUnsafe(32)) {
  sodium.crypto_generichash(out, isInitiator ? NS_INITIATOR : NS_RESPONDER, handshakeHash)
  return out
}

function toBuffer(data) {
  return typeof data === 'string' ? b4a.from(data) : data
}

function destroyTimeout() {
  this.destroy(new Error('Stream timed out'))
}

function sendKeepAlive() {
  const empty = this.alloc(0)
  this.write(empty)
}
const { Duplex, Writable } = require('streamx')

class ReversePassThrough extends Duplex {
  constructor(s) {
    super()
    this._stream = s
    this._ondrain = null
  }

  _write(data, cb) {
    if (this._stream.push(data) === false) {
      this._stream._ondrain = cb
    } else {
      cb(null)
    }
  }

  _final(cb) {
    this._stream.push(null)
    cb(null)
  }

  _read(cb) {
    const ondrain = this._ondrain
    this._ondrain = null
    if (ondrain) ondrain()
    cb(null)
  }
}

module.exports = class Bridge extends Duplex {
  constructor(noiseStream) {
    super()

    this.noiseStream = noiseStream

    this._ondrain = null
    this.reverse = new ReversePassThrough(this)
  }

  get publicKey() {
    return this.noiseStream.publicKey
  }

  get remotePublicKey() {
    return this.noiseStream.remotePublicKey
  }

  get handshakeHash() {
    return this.noiseStream.handshakeHash
  }

  flush() {
    return Writable.drained(this)
  }

  _read(cb) {
    const ondrain = this._ondrain
    this._ondrain = null
    if (ondrain) ondrain()
    cb(null)
  }

  _write(data, cb) {
    if (this.reverse.push(data) === false) {
      this.reverse._ondrain = cb
    } else {
      cb(null)
    }
  }

  _final(cb) {
    this.reverse.push(null)
    cb(null)
  }
}
const sodium = require('sodium-universal')
const curve = require('noise-curve-ed')
const Noise = require('noise-handshake')
const b4a = require('b4a')

const EMPTY = b4a.alloc(0)

module.exports = class Handshake {
  constructor(isInitiator, keyPair, remotePublicKey, pattern) {
    this.isInitiator = isInitiator
    this.keyPair = keyPair
    this.noise = new Noise(pattern, isInitiator, keyPair, { curve })
    this.noise.initialise(EMPTY, remotePublicKey)
    this.destroyed = false
  }

  static keyPair(seed) {
    const publicKey = b4a.alloc(32)
    const secretKey = b4a.alloc(64)
    if (seed) sodium.crypto_sign_seed_keypair(publicKey, secretKey, seed)
    else sodium.crypto_sign_keypair(publicKey, secretKey)
    return { publicKey, secretKey }
  }

  recv(data) {
    try {
      this.noise.recv(data)
      if (this.noise.complete) return this._return(null)
      return this.send()
    } catch {
      this.destroy()
      return null
    }
  }

  // note that the data returned here is framed so we don't have to do an extra copy
  // when sending it...
  send() {
    try {
      const data = this.noise.send()
      const wrap = b4a.allocUnsafe(data.byteLength + 3)

      writeUint24le(data.byteLength, wrap)
      wrap.set(data, 3)

      return this._return(wrap)
    } catch {
      this.destroy()
      return null
    }
  }

  destroy() {
    if (this.destroyed) return
    this.destroyed = true
  }

  _return(data) {
    const tx = this.noise.complete ? b4a.toBuffer(this.noise.tx) : null
    const rx = this.noise.complete ? b4a.toBuffer(this.noise.rx) : null
    const hash = this.noise.complete ? b4a.toBuffer(this.noise.hash) : null
    const remotePublicKey = this.noise.complete ? b4a.toBuffer(this.noise.rs) : null

    return {
      data,
      remotePublicKey,
      hash,
      tx,
      rx
    }
  }
}

function writeUint24le(n, buf) {
  buf[0] = n & 255
  buf[1] = (n >>> 8) & 255
  buf[2] = (n >>> 16) & 255
}
{
  "name": "@hyperswarm/secret-stream",
  "version": "6.9.1",
  "description": "Secret stream backed by Noise and libsodium's secretstream",
  "main": "index.js",
  "files": [
    "index.js",
    "lib/**.js"
  ],
  "dependencies": {
    "b4a": "^1.1.0",
    "hypercore-crypto": "^3.3.1",
    "noise-curve-ed": "^2.0.1",
    "noise-handshake": "^4.0.0",
    "sodium-secretstream": "^1.1.0",
    "sodium-universal": "^5.0.0",
    "streamx": "^2.14.0",
    "timeout-refresh": "^2.0.0",
    "unslab": "^1.3.0"
  },
  "devDependencies": {
    "brittle": "^3.3.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^2.0.0",
    "udx-native": "^1.13.2"
  },
  "scripts": {
    "format": "prettier --write .",
    "lint": "prettier --check .",
    "test": "brittle test.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/holepunchto/hyperswarm-secret-stream.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/hyperswarm-secret-stream/issues"
  },
  "homepage": "https://github.com/holepunchto/hyperswarm-secret-stream"
}
const Cache = require('xache')

module.exports = class AdaptiveTimeout {
  constructor(opts = {}) {
    this._cache = new Cache({
      maxSize: opts.maxSize || 65536,
      maxAge: opts.maxAge || 10 * 60 * 1000 // 10 minutes
    })
    this._fallback = opts.fallback || AdaptiveTimeout.TimeoutExponential
    this._min = opts.min ?? 300
    this._max = opts.max || 4000
    this._jitter = opts.jitter ?? 256
  }

  // Default - aggressive ramp
  static TimeoutAggressive = [500, 750, 1000, 1500, 2000]

  // Linear - steady increase
  static TimeoutLinear = [500, 1000, 1500, 2000, 2500]

  // Exponential - slow start, rapid backoff
  static TimeoutExponential = [250, 500, 1000, 2000, 4000]

  // Gentle - conservative, patient
  static TimeoutGentle = [1000, 1250, 1500, 1750, 2000]

  // Fast - rapid fire retries
  static TimeoutFast = [200, 400, 600, 800, 1000]

  // U-shape - long, short, long
  static TimeoutUShape = [1500, 750, 500, 750, 1500]

  // Inverse U - short, long, short
  static TimeoutInverseU = [500, 1000, 1500, 1000, 500]

  // Sawtooth - alternating fast/slow
  static TimeoutSawtooth = [500, 1500, 500, 1500, 500]

  // Plateau - quick ramp then steady
  static TimeoutPlateau = [500, 1000, 2000, 2000, 2000]

  // Logarithmic - diminishing increases
  static TimeoutLogarithmic = [500, 1000, 1300, 1500, 1600]

  getValue(key) {
    return this._cache.get(key)
  }

  put(key, value) {
    let p = this._cache.get(key)

    if (!p) {
      p = { avg: value, variance: value >> 1 }
    } else {
      // blend 75% old + 25% new deviation
      p.variance += (Math.abs(p.avg - value) - p.variance) >> 2
      // blend 87.5% old + 12.5% new sample
      p.avg += (value - p.avg) >> 3
    }

    this._cache.set(key, p)

    return p
  }

  get(key, attempt = 1) {
    const p = this._cache.get(key)
    const jitter = (Math.random() * this._jitter) | 0

    if (p) {
      // known - use adaptive with linear backoff
      const base = p.avg + (p.variance << 1)
      const backoff = base * attempt
      return Math.min(Math.max(backoff + jitter, this._min), this._max)
    } else {
      // unknown - use aggressive fallback
      const base = this._fallback[Math.min(attempt - 1, this._fallback.length - 1)]
      return base + jitter
    }
  }

  has(key) {
    return this._cache.has(key)
  }

  delete(key) {
    return this._cache.delete(key)
  }

  clear() {
    this._cache.clear()
  }
}
{
  "name": "adaptive-timeout",
  "version": "1.0.1",
  "description": "adaptive-timeout",
  "main": "index.js",
  "exports": {
    "./package": "./package.json",
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    }
  },
  "files": [
    "package.json",
    "index.js",
    "index.d.ts"
  ],
  "devDependencies": {
    "brittle": "^3.19.0",
    "lunte": "^1.2.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^2.0.0"
  },
  "scripts": {
    "format": "prettier . --write",
    "test": "prettier . --check && lunte && brittle-bare test/index.js",
    "lint": "lunte"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/holepunchto/adaptive-timeout.git"
  },
  "author": "Holepunch Inc",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/adaptive-timeout/issues"
  },
  "homepage": "https://github.com/holepunchto/adaptive-timeout",
  "dependencies": {
    "xache": "^1.2.1"
  }
}
/**
 * anproto — wire-compatible implementation of evbogue's ANProto
 *
 * https://anproto.com — the Authenticated Non-networked protocol
 *
 * Exports:
 *   an         — the canonical 4 functions: gen, hash, sign, open (+ helpers)
 *   Challenges — replay-protection helper for HTTP login flows (not part of spec)
 */

export { an } from './lib/an.js'
export { Challenges } from './lib/challenges.js'
/**
 * an.js — wire-compatible implementation of evbogue's ANProto
 *
 * Spec: https://anproto.com  (canonical at github.com/evbogue/ANProto)
 *
 * Same wire format as the canonical TweetNaCl-based implementation, but
 * uses sodium-native (via hypercore-crypto) so we don't carry a second
 * Ed25519 implementation. Both libraries implement the same Ed25519
 * standard and produce byte-identical signatures for the same inputs.
 *
 * The four canonical functions:
 *
 *   gen()              → 132-char keypair string (44 pubkey b64 + 88 secret b64)
 *   hash(data)         → 44-char base64 SHA-256
 *   sign(hash, kp)     → pubkey(44) + base64(combined-mode-sign(timestamp + hash))
 *   open(message)      → "{timestamp}{hash}" string (throws if invalid)
 */

import crypto from 'hypercore-crypto'
import sodium from 'sodium-native'
import b4a from 'b4a'

const SIG_BYTES = 64 // Ed25519 detached signature size (crypto_sign_BYTES)

export const an = {}

/**
 * Generate a new Ed25519 keypair.
 * @returns {Promise<string>} 132-char string: base64(pubkey 32B) + base64(secret 64B)
 */
an.gen = async () => {
  const { publicKey, secretKey } = crypto.keyPair()
  return b4a.toString(publicKey, 'base64') + b4a.toString(secretKey, 'base64')
}

/**
 * SHA-256 hash data.
 * @param {string|Buffer} data
 * @returns {Promise<string>} 44-char base64 SHA-256 hash
 */
an.hash = async (data) => {
  const buf = typeof data === 'string' ? b4a.from(data, 'utf8') : data
  const out = b4a.alloc(32)
  sodium.crypto_hash_sha256(out, buf)
  return b4a.toString(out, 'base64')
}

/**
 * Sign a hash with a keypair, producing a wire-format ANProto signature.
 * The signed message is "{Date.now()}{hash}" — timestamp prefixed.
 *
 * Wire format: pubkey(44 b64) + base64(combined_mode_sign(message))
 * where combined_mode_sign(m) = signature(64B) || m
 *
 * @param {string} hash - 44-char base64 hash from an.hash()
 * @param {string} keypair - 132-char keypair string from an.gen()
 * @returns {Promise<string>} wire-format signature
 */
an.sign = async (hash, keypair) => {
  const ts = Date.now()
  const message = b4a.from(String(ts) + hash, 'utf8')
  const secretKey = b4a.from(keypair.substring(44), 'base64')
  const sig = crypto.sign(message, secretKey) // detached, 64 bytes
  // Combined-mode envelope: signature || message
  const combined = b4a.concat([sig, message])
  return keypair.substring(0, 44) + b4a.toString(combined, 'base64')
}

/**
 * Verify and open an ANProto signature, returning the embedded message.
 * The returned string is "{timestamp}{hash}" — caller can split at offset 13.
 *
 * @param {string} message - wire-format signature from an.sign()
 * @returns {Promise<string>} the original signed message ("{timestamp}{hash}")
 * @throws {Error} if the signature is invalid
 */
an.open = async (message) => {
  if (typeof message !== 'string' || message.length < 44) {
    throw new Error('Invalid ANProto message (too short)')
  }
  const publicKey = b4a.from(message.substring(0, 44), 'base64')
  const combined = b4a.from(message.substring(44), 'base64')
  if (publicKey.length !== 32) throw new Error('Invalid ANProto pubkey length')
  if (combined.length < SIG_BYTES) throw new Error('Invalid ANProto combined-mode envelope')

  const sig = combined.subarray(0, SIG_BYTES)
  const body = combined.subarray(SIG_BYTES)

  if (!crypto.verify(body, sig, publicKey)) {
    throw new Error('Invalid ANProto signature')
  }
  return b4a.toString(body, 'utf8')
}

// ── Convenience helpers (not part of canonical spec, but commonly needed) ──

/**
 * Extract the public key from a keypair string.
 * @param {string} keypair - 132-char keypair string
 * @returns {string} 44-char base64 public key
 */
an.publicKey = (keypair) => {
  if (typeof keypair !== 'string' || keypair.length !== 132) {
    throw new Error('Invalid keypair (expected 132 chars)')
  }
  return keypair.substring(0, 44)
}

/**
 * Parse an opened message into its timestamp and hash components.
 * Canonical format: 13-digit timestamp followed by 44-char base64 hash.
 * @param {string} opened - the string returned by an.open()
 * @returns {{ timestamp: number, hash: string }}
 */
an.parse = (opened) => {
  if (typeof opened !== 'string' || opened.length < 13) {
    throw new Error('Invalid opened message')
  }
  const tsMatch = opened.match(/^(\d{13})(.+)$/)
  if (!tsMatch) throw new Error('Invalid opened message format')
  return {
    timestamp: parseInt(tsMatch[1], 10),
    hash: tsMatch[2]
  }
}
/**
 * Challenges — in-memory replay protection helper for ANProto login flows
 *
 * Not part of canonical ANProto. A small helper for HTTP login flows where
 * the server issues a challenge string, the client signs hash(challenge),
 * and the server consumes the challenge to prevent replay.
 *
 * Usage:
 *   const challenges = new Challenges({ ttl: 5 * 60 * 1000 })
 *   const challenge = challenges.generate()      // server issues
 *   // ... browser signs hash(challenge), POSTs sig ...
 *   if (!challenges.consume(challenge)) reject() // server consumes (single-use)
 */

import crypto from 'hypercore-crypto'
import b4a from 'b4a'

const DEFAULT_TTL = 5 * 60 * 1000 // 5 minutes
const DEFAULT_CLEANUP_INTERVAL = 60 * 1000 // 1 minute

export class Challenges {
  constructor (opts = {}) {
    this.ttl = opts.ttl || DEFAULT_TTL
    this._active = new Map() // challenge string → expiresAt
    this._cleanupTimer = setInterval(() => this._cleanup(), opts.cleanupInterval || DEFAULT_CLEANUP_INTERVAL)
    if (this._cleanupTimer.unref) this._cleanupTimer.unref()
  }

  /**
   * Issue a new challenge string. The caller sends this to the client; the
   * client signs hash(challenge) and returns the signature.
   * @returns {string} 44-char base64 random challenge
   */
  generate () {
    const challenge = b4a.toString(crypto.randomBytes(32), 'base64')
    this._active.set(challenge, Date.now() + this.ttl)
    return challenge
  }

  /**
   * Consume a challenge. Returns true if the challenge was active and not
   * yet consumed; false otherwise. Single-use — calling consume() removes
   * the challenge from the active set.
   * @param {string} challenge
   * @returns {boolean}
   */
  consume (challenge) {
    const expiresAt = this._active.get(challenge)
    if (expiresAt === undefined) return false
    if (expiresAt < Date.now()) {
      this._active.delete(challenge)
      return false
    }
    this._active.delete(challenge)
    return true
  }

  /**
   * Check if a challenge is active without consuming it.
   * @param {string} challenge
   * @returns {boolean}
   */
  has (challenge) {
    const expiresAt = this._active.get(challenge)
    if (expiresAt === undefined) return false
    return expiresAt >= Date.now()
  }

  /**
   * Number of active (unexpired) challenges.
   */
  get size () {
    return this._active.size
  }

  _cleanup () {
    const now = Date.now()
    for (const [challenge, expiresAt] of this._active) {
      if (expiresAt < now) this._active.delete(challenge)
    }
  }

  /**
   * Stop the cleanup timer. Call when shutting down.
   */
  destroy () {
    if (this._cleanupTimer) {
      clearInterval(this._cleanupTimer)
      this._cleanupTimer = null
    }
    this._active.clear()
  }
}
function isBuffer(value) {
  return Buffer.isBuffer(value) || value instanceof Uint8Array
}

function isEncoding(encoding) {
  return Buffer.isEncoding(encoding)
}

function alloc(size, fill, encoding) {
  return Buffer.alloc(size, fill, encoding)
}

function allocUnsafe(size) {
  return Buffer.allocUnsafe(size)
}

function allocUnsafeSlow(size) {
  return Buffer.allocUnsafeSlow(size)
}

function byteLength(string, encoding) {
  return Buffer.byteLength(string, encoding)
}

function compare(a, b) {
  return Buffer.compare(a, b)
}

function concat(buffers, totalLength) {
  return Buffer.concat(buffers, totalLength)
}

function copy(source, target, targetStart, start, end) {
  return toBuffer(source).copy(target, targetStart, start, end)
}

function equals(a, b) {
  return toBuffer(a).equals(b)
}

function fill(buffer, value, offset, end, encoding) {
  return toBuffer(buffer).fill(value, offset, end, encoding)
}

function from(value, encodingOrOffset, length) {
  return Buffer.from(value, encodingOrOffset, length)
}

function includes(buffer, value, byteOffset, encoding) {
  return toBuffer(buffer).includes(value, byteOffset, encoding)
}

function indexOf(buffer, value, byfeOffset, encoding) {
  return toBuffer(buffer).indexOf(value, byfeOffset, encoding)
}

function lastIndexOf(buffer, value, byteOffset, encoding) {
  return toBuffer(buffer).lastIndexOf(value, byteOffset, encoding)
}

function swap16(buffer) {
  return toBuffer(buffer).swap16()
}

function swap32(buffer) {
  return toBuffer(buffer).swap32()
}

function swap64(buffer) {
  return toBuffer(buffer).swap64()
}

function toBuffer(buffer) {
  if (Buffer.isBuffer(buffer)) return buffer
  return Buffer.from(buffer.buffer, buffer.byteOffset, buffer.byteLength)
}

function toString(buffer, encoding, start, end) {
  return toBuffer(buffer).toString(encoding, start, end)
}

function write(buffer, string, offset, length, encoding) {
  return toBuffer(buffer).write(string, offset, length, encoding)
}

function readDoubleBE(buffer, offset) {
  return toBuffer(buffer).readDoubleBE(offset)
}

function readDoubleLE(buffer, offset) {
  return toBuffer(buffer).readDoubleLE(offset)
}

function readFloatBE(buffer, offset) {
  return toBuffer(buffer).readFloatBE(offset)
}

function readFloatLE(buffer, offset) {
  return toBuffer(buffer).readFloatLE(offset)
}

function readInt32BE(buffer, offset) {
  return toBuffer(buffer).readInt32BE(offset)
}

function readInt32LE(buffer, offset) {
  return toBuffer(buffer).readInt32LE(offset)
}

function readUInt32BE(buffer, offset) {
  return toBuffer(buffer).readUInt32BE(offset)
}

function readUInt32LE(buffer, offset) {
  return toBuffer(buffer).readUInt32LE(offset)
}

function writeDoubleBE(buffer, value, offset) {
  return toBuffer(buffer).writeDoubleBE(value, offset)
}

function writeDoubleLE(buffer, value, offset) {
  return toBuffer(buffer).writeDoubleLE(value, offset)
}

function writeFloatBE(buffer, value, offset) {
  return toBuffer(buffer).writeFloatBE(value, offset)
}

function writeFloatLE(buffer, value, offset) {
  return toBuffer(buffer).writeFloatLE(value, offset)
}

function writeInt32BE(buffer, value, offset) {
  return toBuffer(buffer).writeInt32BE(value, offset)
}

function writeInt32LE(buffer, value, offset) {
  return toBuffer(buffer).writeInt32LE(value, offset)
}

function writeUInt32BE(buffer, value, offset) {
  return toBuffer(buffer).writeUInt32BE(value, offset)
}

function writeUInt32LE(buffer, value, offset) {
  return toBuffer(buffer).writeUInt32LE(value, offset)
}

module.exports = {
  isBuffer,
  isEncoding,
  alloc,
  allocUnsafe,
  allocUnsafeSlow,
  byteLength,
  compare,
  concat,
  copy,
  equals,
  fill,
  from,
  includes,
  indexOf,
  lastIndexOf,
  swap16,
  swap32,
  swap64,
  toBuffer,
  toString,
  write,
  readDoubleBE,
  readDoubleLE,
  readFloatBE,
  readFloatLE,
  readInt32BE,
  readInt32LE,
  readUInt32BE,
  readUInt32LE,
  writeDoubleBE,
  writeDoubleLE,
  writeFloatBE,
  writeFloatLE,
  writeInt32BE,
  writeInt32LE,
  writeUInt32BE,
  writeUInt32LE
}
{
  "name": "b4a",
  "version": "1.8.0",
  "description": "Bridging the gap between buffers and typed arrays",
  "exports": {
    "./package": "./package.json",
    ".": {
      "react-native": "./react-native.js",
      "browser": "./browser.js",
      "default": "./index.js"
    }
  },
  "files": [
    "browser.js",
    "index.js",
    "react-native.js",
    "lib"
  ],
  "scripts": {
    "test": "npm run lint && npm run test:bare && npm run test:node",
    "test:bare": "bare test.mjs",
    "test:node": "node test.mjs",
    "lint": "prettier . --check"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/b4a.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/b4a/issues"
  },
  "homepage": "https://github.com/holepunchto/b4a#readme",
  "devDependencies": {
    "brittle": "^3.5.2",
    "nanobench": "^3.0.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^1.0.0"
  },
  "peerDependencies": {
    "react-native-b4a": "*"
  },
  "peerDependenciesMeta": {
    "react-native-b4a": {
      "optional": true
    }
  }
}
const ESC = '\x1b'
const CSI = ESC + '['
const SGR = (n) => CSI + n + 'm'

exports.constants = {
  ESC,
  CSI,
  SGR
}

// https://en.wikipedia.org/wiki/ANSI_escape_code#CSI_(Control_Sequence_Introducer)_sequences

exports.cursorHide = CSI + '?25l'
exports.cursorShow = CSI + '?25h'

exports.cursorUp = function cursorUp(n = 1) {
  return CSI + n + 'A'
}

exports.cursorDown = function cursorDown(n = 1) {
  return CSI + n + 'B'
}

exports.cursorForward = function cursorForward(n = 1) {
  return CSI + n + 'C'
}

exports.cursorBack = function cursorBack(n = 1) {
  return CSI + n + 'D'
}

exports.cursorNextLine = function cursorNextLine(n = 1) {
  return CSI + n + 'E'
}

exports.cursorPreviousLine = function cursorPreviousLine(n = 1) {
  return CSI + n + 'F'
}

exports.cursorPosition = function cursorPosition(column, row = 0) {
  if (row === 0) return CSI + (column + 1) + 'G'

  return CSI + (row + 1) + ';' + (column + 1) + 'H'
}

exports.eraseDisplayEnd = CSI + 'J'
exports.eraseDisplayStart = CSI + '1J'
exports.eraseDisplay = CSI + '2J'
exports.eraseLineEnd = CSI + 'K'
exports.eraseLineStart = CSI + '1K'
exports.eraseLine = CSI + '2K'

exports.scrollUp = function scrollUp(n = 1) {
  return CSI + n + 'S'
}

exports.scrollDown = function scrollDown(n = 1) {
  return CSI + n + 'T'
}

// https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_(Select_Graphic_Rendition)_parameters

exports.modifierReset = SGR(0)
exports.modifierBold = SGR(1)
exports.modifierDim = SGR(2)
exports.modifierItalic = SGR(3)
exports.modifierUnderline = SGR(4)
exports.modifierNormal = SGR(22)
exports.modifierNotItalic = SGR(23)
exports.modifierNotUnderline = SGR(24)

// https://en.wikipedia.org/wiki/ANSI_escape_code#Colors

exports.colorBlack = SGR(30)
exports.colorRed = SGR(31)
exports.colorGreen = SGR(32)
exports.colorYellow = SGR(33)
exports.colorBlue = SGR(34)
exports.colorMagenta = SGR(35)
exports.colorCyan = SGR(36)
exports.colorWhite = SGR(37)
exports.colorDefault = SGR(39)
exports.colorBrightBlack = SGR(90)
exports.colorBrightRed = SGR(91)
exports.colorBrightGreen = SGR(92)
exports.colorBrightYellow = SGR(93)
exports.colorBrightBlue = SGR(94)
exports.colorBrightMagenta = SGR(95)
exports.colorBrightCyan = SGR(96)
exports.colorBrightWhite = SGR(97)
{
  "name": "bare-ansi-escapes",
  "version": "2.2.3",
  "description": "Parse and produce ANSI escape sequences",
  "exports": {
    "./package": "./package.json",
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    },
    "./key-decoder": {
      "types": "./key-decoder.d.ts",
      "default": "./key-decoder.js"
    }
  },
  "files": [
    "index.js",
    "index.d.ts",
    "key-decoder.js",
    "key-decoder.d.ts"
  ],
  "scripts": {
    "test": "prettier . --check && bare test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-ansi-escapes.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-ansi-escapes/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-ansi-escapes#readme",
  "dependencies": {
    "bare-stream": "^2.6.5"
  },
  "devDependencies": {
    "bare-buffer": "^3.0.2",
    "brittle": "^3.3.2",
    "prettier": "^3.4.2",
    "prettier-config-standard": "^7.0.0"
  },
  "peerDependencies": {
    "bare-buffer": "*"
  },
  "peerDependenciesMeta": {
    "bare-buffer": {
      "optional": true
    }
  }
}
const inspect = require('bare-inspect')

class AssertionError extends Error {
  constructor(opts = {}) {
    let { message = null, actual, expected, operator } = opts

    if (message === null) {
      message = `${inspect(actual)} ${operator} ${inspect(expected)}`
    }

    super(message)

    this.actual = actual
    this.expected = expected
    this.operator = operator
  }

  get name() {
    return 'AssertionError'
  }

  get code() {
    'ASSERTION'
  }
}

function assertFail(opts, fn) {
  if (opts.message instanceof Error) throw opts.message

  const err = new AssertionError(opts)

  if (Error.captureStackTrace) Error.captureStackTrace(err, fn)

  throw err
}

module.exports = exports = function assert(actual, message) {
  if (actual) return

  assertFail({ message, actual, expected: true, operator: '==' }, assert)
}

exports.AssertionError = AssertionError

exports.fail = function fail(message) {
  if (message === undefined) message = 'Failed'

  assertFail({ message, operator: 'fail' }, fail)
}

exports.ok = function ok(actual, message) {
  if (actual) return

  assertFail({ message, actual, expected: true, operator: '==' }, ok)
}

exports.notOk = function ok(actual, message) {
  if (!actual) return

  assertFail({ message, actual, expected: false, operator: '==' }, ok)
}

exports.equal = function equal(actual, expected, message) {
  if (actual == expected || (actual !== actual && expected !== expected)) {
    return
  }

  assertFail({ message, actual, expected, operator: '==' }, equal)
}

exports.notEqual = function notEqual(actual, expected, message) {
  if (actual != expected && (actual === actual || expected === expected)) {
    return
  }

  assertFail({ message, actual, expected, operator: '!=' }, notEqual)
}

exports.strictEqual = function strictEqual(actual, expected, message) {
  if (Object.is(actual, expected)) return

  assertFail(
    { message, actual, expected, operator: 'strictEqual' },
    strictEqual
  )
}

exports.notStrictEqual = function notStrictEqual(actual, expected, message) {
  if (!Object.is(actual, expected)) return

  assertFail(
    { message, actual, expected, operator: 'notStrictEqual' },
    notStrictEqual
  )
}
{
  "name": "bare-assert",
  "version": "1.2.0",
  "description": "Assertion library for JavaScript",
  "exports": {
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    },
    "./package": "./package.json"
  },
  "files": [
    "index.js",
    "index.d.ts"
  ],
  "scripts": {
    "test": "prettier . --check && bare test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-assert.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-assert/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-assert#readme",
  "dependencies": {
    "bare-inspect": "^3.1.2"
  },
  "devDependencies": {
    "brittle": "^3.1.1",
    "prettier": "^3.4.2",
    "prettier-config-standard": "^7.0.0"
  }
}
module.exports = require.addon()
const ansiEscapes = require('bare-ansi-escapes')
const getType = require('bare-type')
const binding = require('./binding')

const PLAIN_KEY = /^[a-zA-Z_][a-zA-Z_0-9]*$/

const defaultDepth = 2
const defaultBreakLength = 80
const defaultMaxArrayLength = 40

module.exports = exports = function inspect(value, opts = {}) {
  const {
    colors = false,
    depth = defaultDepth,
    breakLength = defaultBreakLength,
    stylize = defaultStylize(colors)
  } = opts

  const references = new InspectRefMap()

  const tree = inspectValue(value, 0, {
    colors,
    depth,
    breakLength,
    stylize,
    references
  })

  return tree.toString()
}

exports.styles = {
  bigint: ansiEscapes.colorYellow,
  boolean: ansiEscapes.colorYellow,
  date: ansiEscapes.colorMagenta,
  module: ansiEscapes.modifierUnderline,
  name: ansiEscapes.modifierReset,
  null: ansiEscapes.modifierBold,
  number: ansiEscapes.colorYellow,
  regexp: ansiEscapes.colorRed,
  special: ansiEscapes.colorCyan,
  string: ansiEscapes.colorGreen,
  symbol: ansiEscapes.colorGreen,
  undefined: ansiEscapes.colorBrightBlack
}

const styles = exports.styles

function defaultStylize(colors) {
  return function stylize(value, style) {
    const color = colors && styles[style]

    if (color) return color + value + ansiEscapes.modifierReset

    return value
  }
}

class InspectRefMap {
  constructor() {
    this.refs = new WeakMap()
    this.ids = new WeakMap()
    this.nextId = 1
  }

  has(object) {
    return this.refs.has(object)
  }

  get(object) {
    return this.refs.get(object) || null
  }

  set(object, ref) {
    this.refs.set(object, ref)
  }

  id(object) {
    let id = this.ids.get(object)
    if (id) return id

    id = this.nextId++
    this.ids.set(object, id)

    return id
  }
}

class InspectNode {
  constructor(depth, length, opts) {
    const { breakLength = defaultBreakLength, breakAlways = false } = opts

    this.depth = depth
    this.length = length
    this.breakLength = breakLength
    this.breakAlways = breakAlways
  }

  pad(n, string) {
    return string.padStart(n, ' ')
  }

  indent(n, string) {
    return '  '.repeat(n) + string
  }
}

class InspectRef extends InspectNode {
  constructor(depth, opts) {
    super(depth, '[circular *]'.length, opts)

    this.refs = opts.references
    this.count = 0
    this.circular = false
    this.color = opts.colors && styles.special
  }

  get id() {
    return this.refs.id(this)
  }

  increment() {
    return ++this.count
  }

  decrement() {
    return --this.count
  }

  toString(opts = {}) {
    const { offset = 0, pad = 0, indent = 0 } = opts

    let value = this.pad(pad, '[circular *' + this.id + ']')

    if (this.color) value = this.color + value + ansiEscapes.modifierReset

    return offset ? value : this.indent(indent, value)
  }
}

class InspectLeaf extends InspectNode {
  constructor(value, color, depth, opts) {
    const length = value.length

    if (value.includes('\n')) {
      value = value.replaceAll('\n', '\n' + '  '.repeat(depth))

      opts = { ...opts, breakAlways: true }
    }

    super(depth, length, opts)

    this.value = value
    this.color = opts.colors && color
  }

  toString(opts = {}) {
    const { offset = 0, pad = 0, indent = 0 } = opts

    let value = this.pad(pad, this.value)

    if (this.color) value = this.color + value + ansiEscapes.modifierReset

    return offset ? value : this.indent(indent, value)
  }
}

class InspectPair extends InspectNode {
  constructor(delim, left, right, depth, opts) {
    const length = left.length + delim.length + right.length

    if (left.breakAlways || right.breakAlways) {
      opts = { ...opts, breakAlways: true }
    }

    super(depth, length, opts)

    this.delim = delim
    this.left = left
    this.right = right
  }

  toString(opts = {}) {
    const { indent = 0 } = opts

    return this.indent(
      indent,
      this.left +
        this.delim +
        this.right.toString({
          indent,
          offset: this.left.length + this.delim.length
        })
    )
  }
}

class InspectSuspension extends InspectNode {
  constructor(overflow, depth, opts) {
    const label = `... ${overflow} more`

    super(depth, label.length, opts)

    this.label = label
  }

  toString(opts = {}) {
    const { indent = 0 } = opts

    return this.indent(indent, this.label)
  }
}

class InspectSequence extends InspectNode {
  constructor(header, footer, delim, values, ref, depth, opts) {
    const { tabulate = false } = opts

    const length =
      (ref.circular ? '<ref *>'.length + 1 : 0) +
      header.length +
      values.reduce(
        (length, value, i) =>
          length + value.length + (i === 0 ? 0 : delim.length),
        0
      ) +
      footer.length

    if (values.some((value) => value.breakAlways)) {
      opts = { ...opts, breakAlways: true }
    }

    super(depth, length, opts)

    this.header = header
    this.footer = footer
    this.delim = delim
    this.values = values
    this.ref = ref
    this.tabulate = tabulate
  }

  toString(opts = {}) {
    const { offset = 0, indent = 0 } = opts

    const split =
      this.breakAlways ||
      (this.values.length &&
        (offset + this.length > this.breakLength ||
          indent * 2 + this.length > this.breakLength))

    let header = this.header

    if (this.ref.circular) {
      header = '<ref *' + this.ref.id + '> ' + header
    }

    if (this.values.length === 0) {
      header = header.trimEnd()
    }

    if (offset === 0) {
      header = this.indent(indent, header)
    }

    if (split) {
      header = header.trimEnd() + '\n'
    }

    let string = header

    let columns = 1
    let pad = 0

    if (this.tabulate) {
      const widest = this.values.reduce(
        (length, value) =>
          value.breakAlways ? length : Math.max(length, value.length),
        0
      )

      if (widest) {
        columns = Math.max(
          columns,
          Math.floor(
            (this.breakLength - indent * 2) / (widest + this.delim.length)
          )
        )

        if (columns > 1) pad = widest
      }
    }

    for (let i = 0, n = this.values.length, offset = 0; i < n; i++) {
      const value = this.values[i]

      if (split) {
        let part

        if (i % columns === 0 || value.breakAlways) {
          part = value.toString({ indent: indent + 1, pad })
        } else {
          part = value.toString({ pad })
        }

        string += part

        if (i < n - 1) {
          if (i % columns === columns - 1 || this.values[i + 1].breakAlways) {
            string += this.delim.trimEnd() + '\n'
          } else {
            string += this.delim
          }
        }
      } else {
        if (i > 0) string += this.delim

        string += value.toString({ offset })

        offset += value.length
      }
    }

    let footer = this.footer

    if (this.values.length === 0) {
      footer = footer.trimStart()
    }

    if (split) {
      string += '\n' + this.indent(indent, footer.trimStart())
    } else {
      string += footer
    }

    return string
  }
}

function inspectValue(value, depth, opts) {
  const type = getType(value)

  if (type.isUndefined()) return inspectUndefined(depth, opts)
  if (type.isNull()) return inspectNull(depth, opts)
  if (type.isBoolean()) return inspectBoolean(value, depth, opts)
  if (type.isNumber()) return inspectNumber(value, depth, opts)
  if (type.isBigInt()) return inspectBigInt(value, depth, opts)
  if (type.isString()) return inspectString(value, depth, opts)
  if (type.isSymbol()) return inspectSymbol(value, depth, opts)
  if (type.isObject()) return inspectObject(type, value, depth, opts)
  if (type.isFunction()) return inspectFunction(type, value, depth, opts)
  if (type.isExternal()) return inspectExternal(value, opts, opts)
}

function inspectUndefined(depth, opts) {
  return new InspectLeaf('undefined', styles.undefined, depth, opts)
}

function inspectNull(depth, opts) {
  return new InspectLeaf('null', styles.null, depth, opts)
}

function inspectBoolean(value, depth, opts) {
  return new InspectLeaf(value.toString(), styles.boolean, depth, opts)
}

function inspectNumber(value, depth, opts) {
  let string

  if (Object.is(value, -0)) {
    string = '-0'
  } else {
    string = value.toString(10)
  }

  return new InspectLeaf(string, styles.number, depth, opts)
}

function inspectBigInt(value, depth, opts) {
  return new InspectLeaf(value.toString(10) + 'n', styles.bigint, depth, opts)
}

const STRING_ESCAPES =
  /[\ud800-\udbff][\udc00-\udfff]|[\u0000-\u001f'\\\ud800-\udfff]/g

function inspectString(value, depth, opts) {
  // https://tc39.es/ecma262/multipage/structured-data.html#sec-quotejsonstring
  const string = value.replace(STRING_ESCAPES, (match) => {
    if (match.length === 2) return match

    switch (match) {
      case "'":
        return "\\'"
      case '\\':
        return '\\\\'
      case '\b':
        return '\\b'
      case '\t':
        return '\\t'
      case '\n':
        return '\\n'
      case '\f':
        return '\\f'
      case '\r':
        return '\\r'
      default:
        return '\\u' + match.charCodeAt(0).toString(16).padStart(4, '0')
    }
  })

  return new InspectLeaf("'" + string + "'", styles.string, depth, opts)
}

function inspectSymbol(value, depth, opts) {
  return new InspectLeaf(value.toString(), styles.symbol, depth, opts)
}

function inspectKey(value, depth, opts) {
  if (PLAIN_KEY.test(value)) {
    return new InspectLeaf(value, null, depth, opts)
  } else {
    return inspectValue(value, depth, opts)
  }
}

function inspectObject(type, object, depth, opts) {
  const refs = opts.references

  let ref = refs.get(object)
  if (ref === null) {
    ref = new InspectRef(depth, opts)
    refs.set(object, ref)
  } else if (ref.count) {
    ref.circular = true
    return ref
  }

  const maxDepth = typeof opts.depth === 'number' ? opts.depth : Infinity

  if (maxDepth < depth) {
    const constructor = object.constructor

    return new InspectLeaf(
      '[' +
        (constructor && constructor.name ? constructor.name : 'Object') +
        ']',
      styles.special,
      depth,
      opts
    )
  }

  const inspect =
    object[Symbol.for('bare.inspect')] ||
    object[Symbol.for('nodejs.util.inspect.custom')]

  if (typeof inspect === 'function') {
    const value = inspect.call(
      object,
      typeof opts.depth === 'number' ? opts.depth - depth : null,
      {
        colors: opts.colors,
        breakLength: opts.breakLength,
        stylize: opts.stylize
      },
      exports
    )

    if (typeof value === 'object' && value !== null) {
      refs.set(value, ref)
    }

    if (typeof value !== 'string') {
      return inspectValue(value, depth, opts)
    }

    return new InspectLeaf(value, null, depth, opts)
  }

  if (type.isArray()) return inspectArray(object, ref, depth, opts)
  if (type.isDate()) return inspectDate(object, ref, depth, opts)
  if (type.isRegExp()) return inspectRegExp(object, ref, depth, opts)
  if (type.isError()) return inspectError(object, ref, depth, opts)
  if (type.isPromise()) return inspectPromise(object, ref, depth, opts)
  if (type.isMap()) return inspectMap(object, ref, depth, opts)
  if (type.isSet()) return inspectSet(object, ref, depth, opts)
  if (type.isWeakMap()) return inspectWeakMap(object, ref, depth, opts)
  if (type.isWeakSet()) return inspectWeakSet(object, ref, depth, opts)
  if (type.isWeakRef()) return inspectWeakRef(object, ref, depth, opts)
  if (type.isArrayBuffer()) return inspectArrayBuffer(object, ref, depth, opts)
  if (type.isSharedArrayBuffer())
    return inspectSharedArrayBuffer(object, ref, depth, opts)
  if (type.isTypedArray()) return inspectTypedArray(object, ref, depth, opts)
  if (type.isDataView()) return inspectDataView(object, ref, depth, opts)

  ref.increment()

  const values = []

  for (const key in object) {
    if (key === 'constructor') continue

    values.push(
      new InspectPair(
        ': ',
        inspectKey(key, depth + 1, opts),
        inspectValue(object[key], depth + 1, opts),
        depth + 1,
        opts
      )
    )
  }

  ref.decrement()

  let header = '{ '

  const tag = object[Symbol.toStringTag]

  if (tag) header = '[' + tag + '] ' + header

  if (object.constructor) {
    const name = object.constructor.name

    if (name && name !== 'Object') {
      header = object.constructor.name + ' ' + header
    }
  }

  return new InspectSequence(header, ' }', ', ', values, ref, depth, opts)
}

function inspectArray(array, ref, depth, opts) {
  const { maxArrayLength = defaultMaxArrayLength } = opts

  ref.increment()

  const values = []

  let remaining = Math.max(maxArrayLength, 0)

  for (let i = 0, n = array.length; i < n; i++) {
    if (remaining-- === 0) {
      values.push(
        new InspectSuspension(array.length - values.length, depth + 1, {
          ...opts,
          breakAlways: true
        })
      )

      break
    }

    values.push(inspectValue(array[i], depth + 1, opts))
  }

  for (const key of binding.getOwnNonIndexPropertyNames(array)) {
    values.push(
      new InspectPair(
        ': ',
        inspectKey(key, depth + 1, opts),
        inspectValue(array[key], depth + 1, opts),
        depth + 1,
        { ...opts, breakAlways: remaining < 0 }
      )
    )
  }

  ref.decrement()

  let header = '[ '

  if (array.constructor.name !== 'Array') {
    header = array.constructor.name + '(' + array.length + ') ' + header
  }

  return new InspectSequence(header, ' ]', ', ', values, ref, depth, {
    ...opts,
    tabulate: true
  })
}

function inspectDate(date, ref, depth, opts) {
  return new InspectLeaf(date.toISOString(), styles.date, depth, opts)
}

function inspectRegExp(regExp, ref, depth, opts) {
  return new InspectLeaf(regExp.toString(), styles.regexp, depth, opts)
}

function inspectError(error, ref, depth, opts) {
  let header

  if ('stack' in error) {
    header = error.stack

    if (depth > 0) {
      header = header.replaceAll('\n', '\n' + '  '.repeat(depth))
    }
  } else {
    header = error.toString()
  }

  const builtins = ['cause']

  if (error.name === 'AggregateError') {
    builtins.push('errors')
  } else if (error.name === 'SuppressedError') {
    builtins.push('error', 'suppressed')
  }

  const values = []

  for (const key of builtins) {
    if (key in error === false) continue

    values.push(
      new InspectPair(
        ': ',
        new InspectLeaf('[' + key + ']', null, depth + 1, opts),
        inspectValue(error[key], depth + 1, opts),
        depth + 1,
        opts
      )
    )
  }

  for (const key in error) {
    if (key === 'constructor' || builtins.includes(key)) continue

    values.push(
      new InspectPair(
        ': ',
        inspectKey(key, depth + 1, opts),
        inspectValue(error[key], depth + 1, opts),
        depth + 1,
        opts
      )
    )
  }

  if (values.length === 0) return new InspectLeaf(header, null, depth, opts)

  return new InspectSequence(
    header + ' {',
    ' }',
    ', ',
    values,
    ref,
    depth,
    opts
  )
}

function inspectPromise(promise, ref, depth, opts) {
  ref.increment()

  const state = binding.getPromiseState(promise)

  const values = []

  switch (state) {
    case 0: // Pending
      values.push(new InspectLeaf('<pending>', styles.special, depth, opts))
      break

    case 1: // Fulfilled
      values.push(inspectValue(binding.getPromiseResult(promise), depth, opts))
      break

    case 2: // Rejected
      values.push(
        new InspectLeaf('<rejected>', styles.special, depth, opts),
        inspectValue(binding.getPromiseResult(promise), depth, opts)
      )
  }

  ref.decrement()

  const header = promise.constructor.name + ' { '

  return new InspectSequence(header, ' }', ' ', values, ref, depth, opts)
}

function inspectMap(map, ref, depth, opts) {
  const {
    maxArrayLength = defaultMaxArrayLength,
    maxMapLength = maxArrayLength
  } = opts

  ref.increment()

  const values = []

  let remaining = maxMapLength

  for (const entry of map) {
    if (remaining-- === 0) {
      values.push(
        new InspectSuspension(map.size - values.length, depth + 1, {
          ...opts,
          breakAlways: true
        })
      )
      break
    }

    values.push(
      new InspectPair(
        ' => ',
        inspectValue(entry[0], depth + 1, opts),
        inspectValue(entry[1], depth + 1, opts),
        depth + 1,
        opts
      )
    )
  }

  for (const key in map) {
    if (key === 'constructor') continue

    const value = inspectValue(map[key], depth + 1, opts)

    values.push(
      new InspectPair(
        ': ',
        inspectKey(key, depth + 1, opts),
        value,
        depth + 1,
        { ...opts, breakAlways: remaining < 0 }
      )
    )
  }

  ref.decrement()

  const header = map.constructor.name + '(' + map.size + ') { '

  return new InspectSequence(header, ' }', ', ', values, ref, depth, {
    ...opts,
    tabulate: true
  })
}

function inspectSet(set, ref, depth, opts) {
  const {
    maxArrayLength = defaultMaxArrayLength,
    maxSetLength = maxArrayLength
  } = opts

  ref.increment()

  const values = []

  let remaining = maxSetLength

  for (const entry of set) {
    if (remaining-- === 0) {
      values.push(
        new InspectSuspension(set.size - values.length, depth + 1, {
          ...opts,
          breakAlways: true
        })
      )
      break
    }

    values.push(inspectValue(entry, depth + 1, opts))
  }

  for (const key in set) {
    if (key === 'constructor') continue

    const value = inspectValue(set[key], depth + 1, opts)

    values.push(
      new InspectPair(
        ': ',
        inspectKey(key, depth + 1, opts),
        value,
        depth + 1,
        { ...opts, breakAlways: remaining < 0 }
      )
    )
  }

  ref.decrement()

  const header = set.constructor.name + '(' + set.size + ') { '

  return new InspectSequence(header, ' }', ', ', values, ref, depth, {
    ...opts,
    tabulate: true
  })
}

function inspectWeakMap(weakMap, ref, depth, opts) {
  const header = weakMap.constructor.name + ' { '

  return new InspectSequence(
    header,
    ' }',
    ' ',
    [new InspectLeaf('<items unknown>', styles.special, depth + 1, opts)],
    ref,
    depth,
    opts
  )
}

function inspectWeakSet(weakSet, ref, depth, opts) {
  const header = weakSet.constructor.name + ' { '

  return new InspectSequence(
    header,
    ' }',
    ' ',
    [new InspectLeaf('<items unknown>', styles.special, depth + 1, opts)],
    ref,
    depth,
    opts
  )
}

function inspectWeakRef(weakRef, ref, depth, opts) {
  const target = weakRef.deref()

  let value

  if (target === undefined) {
    value = new InspectLeaf('<cleared>', styles.special, depth + 1, opts)
  } else {
    value = inspectValue(target, depth + 1, opts)
  }

  const header = weakRef.constructor.name + ' { '

  return new InspectSequence(header, ' }', ' ', [value], ref, depth, opts)
}

function inspectArrayBuffer(arrayBuffer, ref, depth, opts) {
  ref.increment()

  const values = []

  for (const key of ['byteLength']) {
    values.push(
      new InspectPair(
        ': ',
        inspectKey(key, depth + 1, opts),
        inspectValue(arrayBuffer[key], depth + 1, opts),
        depth + 1,
        opts
      )
    )
  }

  ref.decrement()

  const header = arrayBuffer.constructor.name + ' { '

  return new InspectSequence(header, ' }', ', ', values, ref, depth, opts)
}

function inspectSharedArrayBuffer(sharedArrayBuffer, ref, depth, opts) {
  ref.increment()

  const values = []

  for (const key of ['byteLength']) {
    values.push(
      new InspectPair(
        ': ',
        inspectKey(key, depth + 1, opts),
        inspectValue(sharedArrayBuffer[key], depth + 1, opts),
        depth + 1,
        opts
      )
    )
  }

  ref.decrement()

  const header = sharedArrayBuffer.constructor.name + ' { '

  return new InspectSequence(header, ' }', ', ', values, ref, depth, opts)
}

function inspectTypedArray(typedArray, ref, depth, opts) {
  if (Buffer.isBuffer(typedArray)) {
    return inspectBuffer(typedArray, ref, depth, opts)
  }

  const {
    maxArrayLength = defaultMaxArrayLength,
    maxTypedArrayLength = maxArrayLength
  } = opts

  ref.increment()

  const values = []

  let remaining = Math.max(maxTypedArrayLength, 0)

  for (let i = 0, n = typedArray.length; i < n; i++) {
    if (remaining-- === 0) {
      values.push(
        new InspectSuspension(typedArray.length - values.length, depth + 1, {
          ...opts,
          breakAlways: true
        })
      )

      break
    }

    values.push(inspectValue(typedArray[i], depth + 1, opts))
  }

  for (const key of binding.getOwnNonIndexPropertyNames(typedArray)) {
    values.push(
      new InspectPair(
        ': ',
        inspectKey(key, depth + 1, opts),
        inspectValue(typedArray[key], depth + 1, opts),
        depth + 1,
        { ...opts, breakAlways: remaining < 0 }
      )
    )
  }

  ref.decrement()

  const header = typedArray.constructor.name + '(' + typedArray.length + ') [ '

  return new InspectSequence(header, ' ]', ', ', values, ref, depth, {
    ...opts,
    tabulate: true
  })
}

function inspectBuffer(buffer, ref, depth, opts) {
  const {
    maxArrayLength = defaultMaxArrayLength,
    maxBufferLength = maxArrayLength
  } = opts

  ref.increment()

  const values = []

  let remaining = Math.max(maxBufferLength, 0)

  for (let i = 0, n = buffer.byteLength; i < n; i++) {
    if (remaining-- === 0) {
      values.push(
        new InspectSuspension(buffer.length - values.length, depth + 1, {
          ...opts,
          breakAlways: true
        })
      )

      break
    }

    values.push(
      new InspectLeaf(
        buffer[i].toString(16).padStart(2, '0'),
        null,
        depth + 1,
        opts
      )
    )
  }

  for (const key of binding.getOwnNonIndexPropertyNames(buffer)) {
    values.push(
      new InspectPair(
        ': ',
        inspectKey(key, depth + 1, opts),
        inspectValue(buffer[key], depth + 1, opts),
        depth + 1,
        { ...opts, breakAlways: remaining < 0 }
      )
    )
  }

  ref.decrement()

  return new InspectSequence('<Buffer ', '>', ' ', values, ref, depth, {
    ...opts,
    tabulate: true
  })
}

function inspectDataView(dataView, ref, depth, opts) {
  ref.increment()

  const values = []

  for (const key of ['byteLength', 'byteOffset', 'buffer']) {
    values.push(
      new InspectPair(
        ': ',
        inspectKey(key, depth + 1, opts),
        inspectValue(dataView[key], depth + 1, opts),
        depth + 1,
        opts
      )
    )
  }

  for (const key of binding.getOwnNonIndexPropertyNames(dataView)) {
    values.push(
      new InspectPair(
        ': ',
        inspectKey(key, depth + 1, opts),
        inspectValue(dataView[key], depth + 1, opts),
        depth + 1,
        opts
      )
    )
  }

  ref.decrement()

  const header = dataView.constructor.name + ' { '

  return new InspectSequence(header, ' }', ', ', values, ref, depth, opts)
}

function inspectFunction(type, fn, depth, opts) {
  if (fn.toString().startsWith('class')) return inspectClass(fn, depth, opts)

  let tag = 'function'

  if (type.isGeneratorFunction()) tag = 'generator ' + tag
  if (type.isAsyncFunction()) tag = 'async ' + tag

  return new InspectLeaf(
    '[' + tag + ' ' + (fn.name ? fn.name : '(anonymous)') + ']',
    styles.special,
    depth,
    opts
  )
}

function inspectClass(ctor, depth, opts) {
  return new InspectLeaf(
    '[class ' + (ctor.name ? ctor.name : '(anonymous)') + ']',
    styles.special,
    depth,
    opts
  )
}

function inspectExternal(external, depth, opts) {
  return new InspectLeaf(
    '[external 0x' + binding.getExternal(external).toString(16) + ']',
    styles.special,
    depth,
    opts
  )
}
{
  "name": "bare-inspect",
  "version": "3.1.4",
  "description": "Inspect objects as strings for debugging",
  "exports": {
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    },
    "./package": "./package.json"
  },
  "files": [
    "index.js",
    "index.d.ts",
    "binding.c",
    "binding.js",
    "CMakeLists.txt",
    "prebuilds"
  ],
  "addon": true,
  "scripts": {
    "test": "prettier . --check && bare test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-inspect.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-inspect/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-inspect#readme",
  "engines": {
    "bare": ">=1.18.0"
  },
  "dependencies": {
    "bare-ansi-escapes": "^2.1.0",
    "bare-type": "^1.0.0"
  },
  "devDependencies": {
    "brittle": "^3.3.2",
    "cmake-bare": "^1.1.6",
    "prettier": "^3.4.1",
    "prettier-config-standard": "^7.0.0"
  }
}
module.exports = require.addon()
const binding = require('./binding')

const t = binding.constants

class Type {
  constructor(type) {
    this._type = type
  }

  isUndefined() {
    return this._type === t.UNDEFINED
  }

  isNull() {
    return this._type === t.NULL
  }

  isBoolean() {
    return this._type === t.BOOLEAN
  }

  isNumber() {
    return (this._type & 0xff) === t.NUMBER
  }

  isInt32() {
    return (this._type & (0xff | t.INT32)) === (t.NUMBER | t.INT32)
  }

  isUint32() {
    return (this._type & (0xff | t.UINT32)) === (t.NUMBER | t.UINT32)
  }

  isString() {
    return this._type === t.STRING
  }

  isSymbol() {
    return this._type === t.SYMBOL
  }

  isObject() {
    return (this._type & 0xff) === t.OBJECT
  }

  isArray() {
    return this._type === (t.OBJECT | t.ARRAY)
  }

  isArguments() {
    return this._type === (t.OBJECT | t.ARGUMENTS)
  }

  isDate() {
    return this._type === (t.OBJECT | t.DATE)
  }

  isRegExp() {
    return this._type === (t.OBJECT | t.REGEXP)
  }

  isError() {
    return this._type === (t.OBJECT | t.ERROR)
  }

  isPromise() {
    return this._type === (t.OBJECT | t.PROMISE)
  }

  isProxy() {
    return this._type === (t.OBJECT | t.PROXY)
  }

  isGenerator() {
    return this._type === (t.OBJECT | t.GENERATOR)
  }

  isMap() {
    return this._type === (t.OBJECT | t.MAP)
  }

  isSet() {
    return this._type === (t.OBJECT | t.SET)
  }

  isWeakMap() {
    return this._type === (t.OBJECT | t.WEAK_MAP)
  }

  isWeakSet() {
    return this._type === (t.OBJECT | t.WEAK_SET)
  }

  isWeakRef() {
    return this._type === (t.OBJECT | t.WEAK_REF)
  }

  isArrayBuffer() {
    return this._type === (t.OBJECT | t.ARRAYBUFFER)
  }

  isSharedArrayBuffer() {
    return this._type === (t.OBJECT | t.SHAREDARRAYBUFFER)
  }

  isTypedArray() {
    return (this._type & 0xffff) === (t.OBJECT | t.TYPEDARRAY)
  }

  isInt8Array() {
    return this._type === (t.OBJECT | t.TYPEDARRAY | t.INT8ARRAY)
  }

  isUint8Array() {
    return this._type === (t.OBJECT | t.TYPEDARRAY | t.UINT8ARRAY)
  }

  isUint8ClampedArray() {
    return this._type === (t.OBJECT | t.TYPEDARRAY | t.UINT8CLAMPEDARRAY)
  }

  isInt16Array() {
    return this._type === (t.OBJECT | t.TYPEDARRAY | t.INT16ARRAY)
  }

  isUint16Array() {
    return this._type === (t.OBJECT | t.TYPEDARRAY | t.UINT16ARRAY)
  }

  isInt32Array() {
    return this._type === (t.OBJECT | t.TYPEDARRAY | t.INT32ARRAY)
  }

  isUint32Array() {
    return this._type === (t.OBJECT | t.TYPEDARRAY | t.UINT32ARRAY)
  }

  isFloat16Array() {
    return this._type === (t.OBJECT | t.TYPEDARRAY | t.FLOAT16ARRAY)
  }

  isFloat32Array() {
    return this._type === (t.OBJECT | t.TYPEDARRAY | t.FLOAT32ARRAY)
  }

  isFloat64Array() {
    return this._type === (t.OBJECT | t.TYPEDARRAY | t.FLOAT64ARRAY)
  }

  isBigInt64Array() {
    return this._type === (t.OBJECT | t.TYPEDARRAY | t.BIGINT64ARRAY)
  }

  isBigUint64Array() {
    return this._type === (t.OBJECT | t.TYPEDARRAY | t.BIGUINT64ARRAY)
  }

  isDataView() {
    return this._type === (t.OBJECT | t.DATAVIEW)
  }

  isModuleNamespace() {
    return this._type === (t.OBJECT | t.MODULE_NAMESPACE)
  }

  isFunction() {
    return (this._type & 0xff) === t.FUNCTION
  }

  isAsyncFunction() {
    return (
      (this._type & (0xff | t.ASYNC_FUNCTION)) ===
      (t.FUNCTION | t.ASYNC_FUNCTION)
    )
  }

  isGeneratorFunction() {
    return (
      (this._type & (0xff | t.GENERATOR_FUNCTION)) ===
      (t.FUNCTION | t.GENERATOR_FUNCTION)
    )
  }

  isExternal() {
    return this._type === t.EXTERNAL
  }

  isBigInt() {
    return this._type === t.BIGINT
  }
}

module.exports = exports = function type(value) {
  switch (typeof value) {
    case 'undefined':
      return new Type(t.UNDEFINED)
    case 'boolean':
      return new Type(t.BOOLEAN)
    case 'number':
      return new Type(
        Number.isSafeInteger(value) ? binding.type(value) : t.NUMBER
      )
    case 'string':
      return new Type(t.STRING)
    case 'symbol':
      return new Type(t.SYMBOL)
    case 'object':
      return new Type(value === null ? t.NULL : binding.type(value))
    case 'function':
      return new Type(binding.type(value))
    case 'bigint':
      return new Type(t.BIGINT)
  }
}

exports.createTag = function createTag(...components) {
  const tag = new Uint32Array(4)
  for (let i = 0; i < 4; i++) tag[i] = components[i] || 0
  return tag
}

exports.addTag = function addTag(object, tag) {
  binding.addTag(object, tag)
}

exports.checkTag = function checkTag(object, tag) {
  return binding.checkTag(object, tag)
}
{
  "name": "bare-type",
  "version": "1.1.0",
  "description": "Cross-realm type predicates for Bare",
  "exports": {
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    },
    "./package": "./package.json"
  },
  "files": [
    "index.js",
    "index.d.ts",
    "binding.c",
    "binding.js",
    "CMakeLists.txt",
    "prebuilds"
  ],
  "addon": true,
  "scripts": {
    "test": "prettier . --check && bare test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-type.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-type/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-type#readme",
  "engines": {
    "bare": ">=1.2.0"
  },
  "devDependencies": {
    "brittle": "^3.4.0",
    "cmake-bare": "^1.1.6",
    "prettier": "^3.4.1",
    "prettier-config-standard": "^7.0.0"
  }
}
const LE = (exports.LE =
  new Uint8Array(new Uint16Array([0xff]).buffer)[0] === 0xff)

exports.BE = !LE
const b4a = require('b4a')

const { BE } = require('./endian')

exports.state = function (start = 0, end = 0, buffer = null) {
  return { start, end, buffer }
}

const raw = (exports.raw = require('./raw'))

const uint = (exports.uint = {
  preencode(state, n) {
    state.end += n <= 0xfc ? 1 : n <= 0xffff ? 3 : n <= 0xffffffff ? 5 : 9
  },
  encode(state, n) {
    if (n <= 0xfc) uint8.encode(state, n)
    else if (n <= 0xffff) {
      state.buffer[state.start++] = 0xfd
      uint16.encode(state, n)
    } else if (n <= 0xffffffff) {
      state.buffer[state.start++] = 0xfe
      uint32.encode(state, n)
    } else {
      state.buffer[state.start++] = 0xff
      uint64.encode(state, n)
    }
  },
  decode(state) {
    const a = uint8.decode(state)
    if (a <= 0xfc) return a
    if (a === 0xfd) return uint16.decode(state)
    if (a === 0xfe) return uint32.decode(state)
    return uint64.decode(state)
  }
})

const uint8 = (exports.uint8 = {
  preencode(state, n) {
    state.end += 1
  },
  encode(state, n) {
    validateUint(n)
    state.buffer[state.start++] = n
  },
  decode(state) {
    if (state.start >= state.end) throw new Error('Out of bounds')
    return state.buffer[state.start++]
  }
})

const uint16 = (exports.uint16 = {
  preencode(state, n) {
    state.end += 2
  },
  encode(state, n) {
    validateUint(n)
    state.buffer[state.start++] = n
    state.buffer[state.start++] = n >>> 8
  },
  decode(state) {
    if (state.end - state.start < 2) throw new Error('Out of bounds')
    return state.buffer[state.start++] + state.buffer[state.start++] * 0x100
  }
})

const uint24 = (exports.uint24 = {
  preencode(state, n) {
    state.end += 3
  },
  encode(state, n) {
    validateUint(n)
    state.buffer[state.start++] = n
    state.buffer[state.start++] = n >>> 8
    state.buffer[state.start++] = n >>> 16
  },
  decode(state) {
    if (state.end - state.start < 3) throw new Error('Out of bounds')
    return (
      state.buffer[state.start++] +
      state.buffer[state.start++] * 0x100 +
      state.buffer[state.start++] * 0x10000
    )
  }
})

const uint32 = (exports.uint32 = {
  preencode(state, n) {
    state.end += 4
  },
  encode(state, n) {
    validateUint(n)
    state.buffer[state.start++] = n
    state.buffer[state.start++] = n >>> 8
    state.buffer[state.start++] = n >>> 16
    state.buffer[state.start++] = n >>> 24
  },
  decode(state) {
    if (state.end - state.start < 4) throw new Error('Out of bounds')
    return (
      state.buffer[state.start++] +
      state.buffer[state.start++] * 0x100 +
      state.buffer[state.start++] * 0x10000 +
      state.buffer[state.start++] * 0x1000000
    )
  }
})

const uint40 = (exports.uint40 = {
  preencode(state, n) {
    state.end += 5
  },
  encode(state, n) {
    validateUint(n)
    const r = Math.floor(n / 0x100)
    uint8.encode(state, n)
    uint32.encode(state, r)
  },
  decode(state) {
    if (state.end - state.start < 5) throw new Error('Out of bounds')
    return uint8.decode(state) + 0x100 * uint32.decode(state)
  }
})

const uint48 = (exports.uint48 = {
  preencode(state, n) {
    state.end += 6
  },
  encode(state, n) {
    validateUint(n)
    const r = Math.floor(n / 0x10000)
    uint16.encode(state, n)
    uint32.encode(state, r)
  },
  decode(state) {
    if (state.end - state.start < 6) throw new Error('Out of bounds')
    return uint16.decode(state) + 0x10000 * uint32.decode(state)
  }
})

const uint56 = (exports.uint56 = {
  preencode(state, n) {
    state.end += 7
  },
  encode(state, n) {
    validateUint(n)
    const r = Math.floor(n / 0x1000000)
    uint24.encode(state, n)
    uint32.encode(state, r)
  },
  decode(state) {
    if (state.end - state.start < 7) throw new Error('Out of bounds')
    return uint24.decode(state) + 0x1000000 * uint32.decode(state)
  }
})

const uint64 = (exports.uint64 = {
  preencode(state, n) {
    state.end += 8
  },
  encode(state, n) {
    validateUint(n)
    const r = Math.floor(n / 0x100000000)
    uint32.encode(state, n)
    uint32.encode(state, r)
  },
  decode(state) {
    if (state.end - state.start < 8) throw new Error('Out of bounds')
    return uint32.decode(state) + 0x100000000 * uint32.decode(state)
  }
})

const int = (exports.int = zigZagInt(uint))
exports.int8 = zigZagInt(uint8)
exports.int16 = zigZagInt(uint16)
exports.int24 = zigZagInt(uint24)
exports.int32 = zigZagInt(uint32)
exports.int40 = zigZagInt(uint40)
exports.int48 = zigZagInt(uint48)
exports.int56 = zigZagInt(uint56)
exports.int64 = zigZagInt(uint64)

const biguint64 = (exports.biguint64 = {
  preencode(state, n) {
    state.end += 8
  },
  encode(state, n) {
    const view = new DataView(
      state.buffer.buffer,
      state.start + state.buffer.byteOffset,
      8
    )
    view.setBigUint64(0, n, true) // little endian
    state.start += 8
  },
  decode(state) {
    if (state.end - state.start < 8) throw new Error('Out of bounds')
    const view = new DataView(
      state.buffer.buffer,
      state.start + state.buffer.byteOffset,
      8
    )
    const n = view.getBigUint64(0, true) // little endian
    state.start += 8
    return n
  }
})

exports.bigint64 = zigZagBigInt(biguint64)

const biguint = (exports.biguint = {
  preencode(state, n) {
    let len = 0
    for (let m = n; m; m = m >> 64n) len++
    uint.preencode(state, len)
    state.end += 8 * len
  },
  encode(state, n) {
    let len = 0
    for (let m = n; m; m = m >> 64n) len++
    uint.encode(state, len)
    const view = new DataView(
      state.buffer.buffer,
      state.start + state.buffer.byteOffset,
      8 * len
    )
    for (let m = n, i = 0; m; m = m >> 64n, i += 8) {
      view.setBigUint64(i, BigInt.asUintN(64, m), true) // little endian
    }
    state.start += 8 * len
  },
  decode(state) {
    const len = uint.decode(state)
    if (state.end - state.start < 8 * len) throw new Error('Out of bounds')
    const view = new DataView(
      state.buffer.buffer,
      state.start + state.buffer.byteOffset,
      8 * len
    )
    let n = 0n
    for (let i = len - 1; i >= 0; i--)
      n = (n << 64n) + view.getBigUint64(i * 8, true) // little endian
    state.start += 8 * len
    return n
  }
})

exports.bigint = zigZagBigInt(biguint)

exports.lexint = require('./lexint')

exports.float32 = {
  preencode(state, n) {
    state.end += 4
  },
  encode(state, n) {
    const view = new DataView(
      state.buffer.buffer,
      state.start + state.buffer.byteOffset,
      4
    )
    view.setFloat32(0, n, true) // little endian
    state.start += 4
  },
  decode(state) {
    if (state.end - state.start < 4) throw new Error('Out of bounds')
    const view = new DataView(
      state.buffer.buffer,
      state.start + state.buffer.byteOffset,
      4
    )
    const float = view.getFloat32(0, true) // little endian
    state.start += 4
    return float
  }
}

exports.float64 = {
  preencode(state, n) {
    state.end += 8
  },
  encode(state, n) {
    const view = new DataView(
      state.buffer.buffer,
      state.start + state.buffer.byteOffset,
      8
    )
    view.setFloat64(0, n, true) // little endian
    state.start += 8
  },
  decode(state) {
    if (state.end - state.start < 8) throw new Error('Out of bounds')
    const view = new DataView(
      state.buffer.buffer,
      state.start + state.buffer.byteOffset,
      8
    )
    const float = view.getFloat64(0, true) // little endian
    state.start += 8
    return float
  }
}

const buffer = (exports.buffer = {
  preencode(state, b) {
    if (b) uint8array.preencode(state, b)
    else state.end++
  },
  encode(state, b) {
    if (b) uint8array.encode(state, b)
    else state.buffer[state.start++] = 0
  },
  decode(state) {
    const len = uint.decode(state)
    if (len === 0) return null
    if (state.end - state.start < len) throw new Error('Out of bounds')
    return state.buffer.subarray(state.start, (state.start += len))
  }
})

exports.binary = {
  ...buffer,
  preencode(state, b) {
    if (typeof b === 'string') utf8.preencode(state, b)
    else buffer.preencode(state, b)
  },
  encode(state, b) {
    if (typeof b === 'string') utf8.encode(state, b)
    else buffer.encode(state, b)
  }
}

exports.arraybuffer = {
  preencode(state, b) {
    uint.preencode(state, b.byteLength)
    state.end += b.byteLength
  },
  encode(state, b) {
    uint.encode(state, b.byteLength)

    const view = new Uint8Array(b)

    state.buffer.set(view, state.start)
    state.start += b.byteLength
  },
  decode(state) {
    const len = uint.decode(state)

    const b = new ArrayBuffer(len)
    const view = new Uint8Array(b)

    view.set(state.buffer.subarray(state.start, (state.start += len)))

    return b
  }
}

function typedarray(TypedArray, swap) {
  const n = TypedArray.BYTES_PER_ELEMENT

  return {
    preencode(state, b) {
      uint.preencode(state, b.length)
      state.end += b.byteLength
    },
    encode(state, b) {
      uint.encode(state, b.length)

      const view = new Uint8Array(b.buffer, b.byteOffset, b.byteLength)

      if (BE && swap) swap(view)

      state.buffer.set(view, state.start)
      state.start += b.byteLength
    },
    decode(state) {
      const len = uint.decode(state)

      let b = state.buffer.subarray(state.start, (state.start += len * n))
      if (b.byteLength !== len * n) throw new Error('Out of bounds')
      if (b.byteOffset % n !== 0) b = new Uint8Array(b)

      if (BE && swap) swap(b)

      return new TypedArray(b.buffer, b.byteOffset, b.byteLength / n)
    }
  }
}

const uint8array = (exports.uint8array = typedarray(Uint8Array))
exports.uint16array = typedarray(Uint16Array, b4a.swap16)
exports.uint32array = typedarray(Uint32Array, b4a.swap32)

exports.int8array = typedarray(Int8Array)
exports.int16array = typedarray(Int16Array, b4a.swap16)
exports.int32array = typedarray(Int32Array, b4a.swap32)

exports.biguint64array = typedarray(BigUint64Array, b4a.swap64)
exports.bigint64array = typedarray(BigInt64Array, b4a.swap64)

exports.float32array = typedarray(Float32Array, b4a.swap32)
exports.float64array = typedarray(Float64Array, b4a.swap64)

function string(encoding) {
  return {
    preencode(state, s) {
      const len = b4a.byteLength(s, encoding)
      uint.preencode(state, len)
      state.end += len
    },
    encode(state, s) {
      const len = b4a.byteLength(s, encoding)
      uint.encode(state, len)
      b4a.write(state.buffer, s, state.start, encoding)
      state.start += len
    },
    decode(state) {
      const len = uint.decode(state)
      if (state.end - state.start < len) throw new Error('Out of bounds')
      return b4a.toString(
        state.buffer,
        encoding,
        state.start,
        (state.start += len)
      )
    },
    fixed(n) {
      return {
        preencode(state) {
          state.end += n
        },
        encode(state, s) {
          b4a.write(state.buffer, s, state.start, n, encoding)
          state.start += n
        },
        decode(state) {
          if (state.end - state.start < n) throw new Error('Out of bounds')
          return b4a.toString(
            state.buffer,
            encoding,
            state.start,
            (state.start += n)
          )
        }
      }
    }
  }
}

const utf8 = (exports.string = exports.utf8 = string('utf-8'))
exports.ascii = string('ascii')
exports.hex = string('hex')
exports.base64 = string('base64')
exports.ucs2 = exports.utf16le = string('utf16le')

exports.bool = {
  preencode(state, b) {
    state.end++
  },
  encode(state, b) {
    state.buffer[state.start++] = b ? 1 : 0
  },
  decode(state) {
    if (state.start >= state.end) throw Error('Out of bounds')
    return state.buffer[state.start++] === 1
  }
}

const fixed = (exports.fixed = function fixed(n) {
  return {
    preencode(state, s) {
      if (s.byteLength !== n) throw new Error('Incorrect buffer size')
      state.end += n
    },
    encode(state, s) {
      state.buffer.set(s, state.start)
      state.start += n
    },
    decode(state) {
      if (state.end - state.start < n) throw new Error('Out of bounds')
      return state.buffer.subarray(state.start, (state.start += n))
    }
  }
})

exports.fixed32 = fixed(32)
exports.fixed64 = fixed(64)

exports.array = function array(enc) {
  return {
    preencode(state, list) {
      uint.preencode(state, list.length)
      for (let i = 0; i < list.length; i++) enc.preencode(state, list[i])
    },
    encode(state, list) {
      uint.encode(state, list.length)
      for (let i = 0; i < list.length; i++) enc.encode(state, list[i])
    },
    decode(state) {
      const len = uint.decode(state)
      if (len > 0x100000) throw new Error('Array is too big')
      const arr = new Array(len)
      for (let i = 0; i < len; i++) arr[i] = enc.decode(state)
      return arr
    }
  }
}

exports.frame = function frame(enc) {
  const dummy = exports.state()

  return {
    preencode(state, m) {
      const end = state.end
      enc.preencode(state, m)
      uint.preencode(state, state.end - end)
    },
    encode(state, m) {
      dummy.end = 0
      enc.preencode(dummy, m)
      uint.encode(state, dummy.end)
      enc.encode(state, m)
    },
    decode(state) {
      const end = state.end
      const len = uint.decode(state)
      state.end = state.start + len
      const m = enc.decode(state)
      state.start = state.end
      state.end = end
      return m
    }
  }
}

exports.date = {
  preencode(state, d) {
    int.preencode(state, d.getTime())
  },
  encode(state, d) {
    int.encode(state, d.getTime())
  },
  decode(state, d) {
    return new Date(int.decode(state))
  }
}

exports.json = {
  preencode(state, v) {
    utf8.preencode(state, JSON.stringify(v))
  },
  encode(state, v) {
    utf8.encode(state, JSON.stringify(v))
  },
  decode(state) {
    return JSON.parse(utf8.decode(state))
  }
}

exports.ndjson = {
  preencode(state, v) {
    utf8.preencode(state, JSON.stringify(v) + '\n')
  },
  encode(state, v) {
    utf8.encode(state, JSON.stringify(v) + '\n')
  },
  decode(state) {
    return JSON.parse(utf8.decode(state))
  }
}

// simple helper for when you want to just express nothing
exports.none = {
  preencode(state, n) {
    // do nothing
  },
  encode(state, n) {
    // do nothing
  },
  decode(state) {
    return null
  }
}

// "any" encoders here for helping just structure any object without schematising it

const anyArray = {
  preencode(state, arr) {
    uint.preencode(state, arr.length)
    for (let i = 0; i < arr.length; i++) {
      any.preencode(state, arr[i])
    }
  },
  encode(state, arr) {
    uint.encode(state, arr.length)
    for (let i = 0; i < arr.length; i++) {
      any.encode(state, arr[i])
    }
  },
  decode(state) {
    const arr = []
    let len = uint.decode(state)
    while (len-- > 0) {
      arr.push(any.decode(state))
    }
    return arr
  }
}

const anyObject = {
  preencode(state, o) {
    const keys = Object.keys(o)
    uint.preencode(state, keys.length)
    for (const key of keys) {
      utf8.preencode(state, key)
      any.preencode(state, o[key])
    }
  },
  encode(state, o) {
    const keys = Object.keys(o)
    uint.encode(state, keys.length)
    for (const key of keys) {
      utf8.encode(state, key)
      any.encode(state, o[key])
    }
  },
  decode(state) {
    let len = uint.decode(state)
    const o = {}
    while (len-- > 0) {
      const key = utf8.decode(state)
      o[key] = any.decode(state)
    }
    return o
  }
}

const anyTypes = [
  exports.none,
  exports.bool,
  exports.string,
  exports.buffer,
  exports.uint,
  exports.int,
  exports.float64,
  anyArray,
  anyObject,
  exports.date
]

const any = (exports.any = {
  preencode(state, o) {
    const t = getType(o)
    uint.preencode(state, t)
    anyTypes[t].preencode(state, o)
  },
  encode(state, o) {
    const t = getType(o)
    uint.encode(state, t)
    anyTypes[t].encode(state, o)
  },
  decode(state) {
    const t = uint.decode(state)
    if (t >= anyTypes.length) throw new Error('Unknown type: ' + t)
    return anyTypes[t].decode(state)
  }
})

const port = (exports.port = uint16)

const address = (host, family) => {
  return {
    preencode(state, m) {
      host.preencode(state, m.host)
      port.preencode(state, m.port)
    },
    encode(state, m) {
      host.encode(state, m.host)
      port.encode(state, m.port)
    },
    decode(state) {
      return {
        host: host.decode(state),
        family,
        port: port.decode(state)
      }
    }
  }
}

const ipv4 = (exports.ipv4 = {
  preencode(state) {
    state.end += 4
  },
  encode(state, string) {
    const start = state.start
    const end = start + 4

    let i = 0

    while (i < string.length) {
      let n = 0
      let c

      while (
        i < string.length &&
        (c = string.charCodeAt(i++)) !== /* . */ 0x2e
      ) {
        n = n * 10 + (c - /* 0 */ 0x30)
      }

      state.buffer[state.start++] = n
    }

    state.start = end
  },
  decode(state) {
    if (state.end - state.start < 4) throw new Error('Out of bounds')
    return (
      state.buffer[state.start++] +
      '.' +
      state.buffer[state.start++] +
      '.' +
      state.buffer[state.start++] +
      '.' +
      state.buffer[state.start++]
    )
  }
})

exports.ipv4Address = address(ipv4, 4)

const ipv6 = (exports.ipv6 = {
  preencode(state) {
    state.end += 16
  },
  encode(state, string) {
    const start = state.start
    const end = start + 16

    let i = 0
    let split = null

    while (i < string.length) {
      let n = 0
      let c

      while (
        i < string.length &&
        (c = string.charCodeAt(i++)) !== /* : */ 0x3a
      ) {
        if (c >= 0x30 && c <= 0x39) n = n * 0x10 + (c - /* 0 */ 0x30)
        else if (c >= 0x41 && c <= 0x46) n = n * 0x10 + (c - /* A */ 0x41 + 10)
        else if (c >= 0x61 && c <= 0x66) n = n * 0x10 + (c - /* a */ 0x61 + 10)
      }

      state.buffer[state.start++] = n >>> 8
      state.buffer[state.start++] = n

      if (i < string.length && string.charCodeAt(i) === /* : */ 0x3a) {
        i++
        split = state.start
      }
    }

    if (split !== null) {
      const offset = end - state.start
      state.buffer
        .copyWithin(split + offset, split)
        .fill(0, split, split + offset)
    }

    state.start = end
  },
  decode(state) {
    if (state.end - state.start < 16) throw new Error('Out of bounds')
    return (
      (
        state.buffer[state.start++] * 256 +
        state.buffer[state.start++]
      ).toString(16) +
      ':' +
      (
        state.buffer[state.start++] * 256 +
        state.buffer[state.start++]
      ).toString(16) +
      ':' +
      (
        state.buffer[state.start++] * 256 +
        state.buffer[state.start++]
      ).toString(16) +
      ':' +
      (
        state.buffer[state.start++] * 256 +
        state.buffer[state.start++]
      ).toString(16) +
      ':' +
      (
        state.buffer[state.start++] * 256 +
        state.buffer[state.start++]
      ).toString(16) +
      ':' +
      (
        state.buffer[state.start++] * 256 +
        state.buffer[state.start++]
      ).toString(16) +
      ':' +
      (
        state.buffer[state.start++] * 256 +
        state.buffer[state.start++]
      ).toString(16) +
      ':' +
      (
        state.buffer[state.start++] * 256 +
        state.buffer[state.start++]
      ).toString(16)
    )
  }
})

exports.ipv6Address = address(ipv6, 6)

const ip = (exports.ip = {
  preencode(state, string) {
    const family = string.includes(':') ? 6 : 4
    uint8.preencode(state, family)
    if (family === 4) ipv4.preencode(state)
    else ipv6.preencode(state)
  },
  encode(state, string) {
    const family = string.includes(':') ? 6 : 4
    uint8.encode(state, family)
    if (family === 4) ipv4.encode(state, string)
    else ipv6.encode(state, string)
  },
  decode(state) {
    const family = uint8.decode(state)
    if (family === 4) return ipv4.decode(state)
    else return ipv6.decode(state)
  }
})

exports.ipAddress = {
  preencode(state, m) {
    ip.preencode(state, m.host)
    port.preencode(state, m.port)
  },
  encode(state, m) {
    ip.encode(state, m.host)
    port.encode(state, m.port)
  },
  decode(state) {
    const family = uint8.decode(state)
    return {
      host: family === 4 ? ipv4.decode(state) : ipv6.decode(state),
      family,
      port: port.decode(state)
    }
  }
}

const record = (exports.record = function (keyEncoding, valueEncoding) {
  return {
    preencode(state, v) {
      const keys = Object.keys(v)
      uint.preencode(state, keys.length)
      for (const k of keys) {
        keyEncoding.preencode(state, k)
        valueEncoding.preencode(state, v[k])
      }
    },
    encode(state, v) {
      const keys = Object.keys(v)
      uint.encode(state, keys.length)
      for (const k of keys) {
        keyEncoding.encode(state, k)
        valueEncoding.encode(state, v[k])
      }
    },
    decode(state) {
      const out = Object.create(null)
      const keys = uint.decode(state)
      for (let i = 0; i < keys; i++) {
        out[keyEncoding.decode(state)] = valueEncoding.decode(state)
      }
      return out
    }
  }
})

exports.stringRecord = record(utf8, utf8)

function getType(o) {
  if (o === null || o === undefined) return 0
  if (typeof o === 'boolean') return 1
  if (typeof o === 'string') return 2
  if (b4a.isBuffer(o)) return 3
  if (typeof o === 'number') {
    if (Number.isInteger(o)) return o >= 0 ? 4 : 5
    return 6
  }
  if (Array.isArray(o)) return 7
  if (o instanceof Date) return 9
  if (typeof o === 'object') return 8

  throw new Error('Unsupported type for ' + o)
}

exports.from = function from(enc) {
  if (typeof enc === 'string') return fromNamed(enc)
  if (enc.preencode) return enc
  if (enc.encodingLength) return fromAbstractEncoder(enc)
  return fromCodec(enc)
}

function fromNamed(enc) {
  switch (enc) {
    case 'ascii':
      return raw.ascii
    case 'utf-8':
    case 'utf8':
      return raw.utf8
    case 'hex':
      return raw.hex
    case 'base64':
      return raw.base64
    case 'utf16-le':
    case 'utf16le':
    case 'ucs-2':
    case 'ucs2':
      return raw.ucs2
    case 'ndjson':
      return raw.ndjson
    case 'json':
      return raw.json
    case 'binary':
    default:
      return raw.binary
  }
}

function fromCodec(enc) {
  let tmpM = null
  let tmpBuf = null

  return {
    preencode(state, m) {
      tmpM = m
      tmpBuf = enc.encode(m)
      state.end += tmpBuf.byteLength
    },
    encode(state, m) {
      raw.encode(state, m === tmpM ? tmpBuf : enc.encode(m))
      tmpM = tmpBuf = null
    },
    decode(state) {
      return enc.decode(raw.decode(state))
    }
  }
}

function fromAbstractEncoder(enc) {
  return {
    preencode(state, m) {
      state.end += enc.encodingLength(m)
    },
    encode(state, m) {
      enc.encode(m, state.buffer, state.start)
      state.start += enc.encode.bytes
    },
    decode(state) {
      const m = enc.decode(state.buffer, state.start, state.end)
      state.start += enc.decode.bytes
      return m
    }
  }
}

exports.encode = function encode(enc, m) {
  const state = exports.state()
  enc.preencode(state, m)
  state.buffer = b4a.allocUnsafe(state.end)
  enc.encode(state, m)
  return state.buffer
}

exports.decode = function decode(enc, buffer) {
  return enc.decode(exports.state(0, buffer.byteLength, buffer))
}

function zigZagInt(enc) {
  return {
    preencode(state, n) {
      enc.preencode(state, zigZagEncodeInt(n))
    },
    encode(state, n) {
      enc.encode(state, zigZagEncodeInt(n))
    },
    decode(state) {
      return zigZagDecodeInt(enc.decode(state))
    }
  }
}

function zigZagDecodeInt(n) {
  return n === 0 ? n : (n & 1) === 0 ? n / 2 : -(n + 1) / 2
}

function zigZagEncodeInt(n) {
  // 0, -1, 1, -2, 2, ...
  return n < 0 ? 2 * -n - 1 : n === 0 ? 0 : 2 * n
}

function zigZagBigInt(enc) {
  return {
    preencode(state, n) {
      enc.preencode(state, zigZagEncodeBigInt(n))
    },
    encode(state, n) {
      enc.encode(state, zigZagEncodeBigInt(n))
    },
    decode(state) {
      return zigZagDecodeBigInt(enc.decode(state))
    }
  }
}

function zigZagDecodeBigInt(n) {
  return n === 0n ? n : (n & 1n) === 0n ? n / 2n : -(n + 1n) / 2n
}

function zigZagEncodeBigInt(n) {
  // 0, -1, 1, -2, 2, ...
  return n < 0n ? 2n * -n - 1n : n === 0n ? 0n : 2n * n
}

function validateUint(n) {
  if (n >= 0 === false /* Handles NaN as well */)
    throw new Error('uint must be positive')
}
module.exports = {
  preencode,
  encode,
  decode
}

function preencode(state, num) {
  if (num < 251) {
    state.end++
  } else if (num < 256) {
    state.end += 2
  } else if (num < 0x10000) {
    state.end += 3
  } else if (num < 0x1000000) {
    state.end += 4
  } else if (num < 0x100000000) {
    state.end += 5
  } else {
    state.end++
    const exp = Math.floor(Math.log(num) / Math.log(2)) - 32
    preencode(state, exp)
    state.end += 6
  }
}

function encode(state, num) {
  const max = 251
  const x = num - max

  if (num < max) {
    state.buffer[state.start++] = num
  } else if (num < 256) {
    state.buffer[state.start++] = max
    state.buffer[state.start++] = x
  } else if (num < 0x10000) {
    state.buffer[state.start++] = max + 1
    state.buffer[state.start++] = (x >> 8) & 0xff
    state.buffer[state.start++] = x & 0xff
  } else if (num < 0x1000000) {
    state.buffer[state.start++] = max + 2
    state.buffer[state.start++] = x >> 16
    state.buffer[state.start++] = (x >> 8) & 0xff
    state.buffer[state.start++] = x & 0xff
  } else if (num < 0x100000000) {
    state.buffer[state.start++] = max + 3
    state.buffer[state.start++] = x >> 24
    state.buffer[state.start++] = (x >> 16) & 0xff
    state.buffer[state.start++] = (x >> 8) & 0xff
    state.buffer[state.start++] = x & 0xff
  } else {
    // need to use Math here as bitwise ops are 32 bit
    const exp = Math.floor(Math.log(x) / Math.log(2)) - 32
    state.buffer[state.start++] = 0xff

    encode(state, exp)
    const rem = x / Math.pow(2, exp - 11)

    for (let i = 5; i >= 0; i--) {
      state.buffer[state.start++] = (rem / Math.pow(2, 8 * i)) & 0xff
    }
  }
}

function decode(state) {
  const max = 251

  if (state.end - state.start < 1) throw new Error('Out of bounds')

  const flag = state.buffer[state.start++]

  if (flag < max) return flag

  if (state.end - state.start < flag - max + 1) {
    throw new Error('Out of bounds.')
  }

  if (flag < 252) {
    return state.buffer[state.start++] + max
  }

  if (flag < 253) {
    return (
      (state.buffer[state.start++] << 8) + state.buffer[state.start++] + max
    )
  }

  if (flag < 254) {
    return (
      (state.buffer[state.start++] << 16) +
      (state.buffer[state.start++] << 8) +
      state.buffer[state.start++] +
      max
    )
  }

  // << 24 result may be interpreted as negative
  if (flag < 255) {
    return (
      state.buffer[state.start++] * 0x1000000 +
      (state.buffer[state.start++] << 16) +
      (state.buffer[state.start++] << 8) +
      state.buffer[state.start++] +
      max
    )
  }

  const exp = decode(state)

  if (state.end - state.start < 6) throw new Error('Out of bounds')

  let rem = 0
  for (let i = 5; i >= 0; i--) {
    rem += state.buffer[state.start++] * Math.pow(2, 8 * i)
  }

  return rem * Math.pow(2, exp - 11) + max
}
{
  "name": "compact-encoding",
  "version": "2.19.2",
  "description": "A series of compact encoding schemes for building small and fast parsers and serializers",
  "main": "index.js",
  "dependencies": {
    "b4a": "^1.3.0"
  },
  "devDependencies": {
    "brittle": "^3.0.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^1.0.0"
  },
  "scripts": {
    "format": "prettier . --write",
    "test": "prettier . --check && brittle test.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/holepunchto/compact-encoding.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/compact-encoding/issues"
  },
  "homepage": "https://github.com/holepunchto/compact-encoding"
}
const b4a = require('b4a')

const { BE } = require('./endian')

exports = module.exports = {
  preencode(state, b) {
    state.end += b.byteLength
  },
  encode(state, b) {
    state.buffer.set(b, state.start)
    state.start += b.byteLength
  },
  decode(state) {
    const b = state.buffer.subarray(state.start, state.end)
    state.start = state.end
    return b
  }
}

const buffer = (exports.buffer = {
  preencode(state, b) {
    if (b) uint8array.preencode(state, b)
    else state.end++
  },
  encode(state, b) {
    if (b) uint8array.encode(state, b)
    else state.buffer[state.start++] = 0
  },
  decode(state) {
    const b = state.buffer.subarray(state.start)
    if (b.byteLength === 0) return null
    state.start = state.end
    return b
  }
})

exports.binary = {
  ...buffer,
  preencode(state, b) {
    if (typeof b === 'string') utf8.preencode(state, b)
    else buffer.preencode(state, b)
  },
  encode(state, b) {
    if (typeof b === 'string') utf8.encode(state, b)
    else buffer.encode(state, b)
  }
}

exports.arraybuffer = {
  preencode(state, b) {
    state.end += b.byteLength
  },
  encode(state, b) {
    const view = new Uint8Array(b)

    state.buffer.set(view, state.start)
    state.start += b.byteLength
  },
  decode(state) {
    const b = new ArrayBuffer(state.end - state.start)
    const view = new Uint8Array(b)

    view.set(state.buffer.subarray(state.start))

    state.start = state.end

    return b
  }
}

function typedarray(TypedArray, swap) {
  const n = TypedArray.BYTES_PER_ELEMENT

  return {
    preencode(state, b) {
      state.end += b.byteLength
    },
    encode(state, b) {
      const view = new Uint8Array(b.buffer, b.byteOffset, b.byteLength)

      if (BE && swap) swap(view)

      state.buffer.set(view, state.start)
      state.start += b.byteLength
    },
    decode(state) {
      let b = state.buffer.subarray(state.start)
      if (b.byteOffset % n !== 0) b = new Uint8Array(b)

      if (BE && swap) swap(b)

      state.start = state.end

      return new TypedArray(b.buffer, b.byteOffset, b.byteLength / n)
    }
  }
}

const uint8array = (exports.uint8array = typedarray(Uint8Array))
exports.uint16array = typedarray(Uint16Array, b4a.swap16)
exports.uint32array = typedarray(Uint32Array, b4a.swap32)

exports.int8array = typedarray(Int8Array)
exports.int16array = typedarray(Int16Array, b4a.swap16)
exports.int32array = typedarray(Int32Array, b4a.swap32)

exports.biguint64array = typedarray(BigUint64Array, b4a.swap64)
exports.bigint64array = typedarray(BigInt64Array, b4a.swap64)

exports.float32array = typedarray(Float32Array, b4a.swap32)
exports.float64array = typedarray(Float64Array, b4a.swap64)

function string(encoding) {
  return {
    preencode(state, s) {
      state.end += b4a.byteLength(s, encoding)
    },
    encode(state, s) {
      state.start += b4a.write(state.buffer, s, state.start, encoding)
    },
    decode(state) {
      const s = b4a.toString(state.buffer, encoding, state.start)
      state.start = state.end
      return s
    }
  }
}

const utf8 = (exports.string = exports.utf8 = string('utf-8'))
exports.ascii = string('ascii')
exports.hex = string('hex')
exports.base64 = string('base64')
exports.ucs2 = exports.utf16le = string('utf16le')

exports.array = function array(enc) {
  return {
    preencode(state, list) {
      for (const value of list) enc.preencode(state, value)
    },
    encode(state, list) {
      for (const value of list) enc.encode(state, value)
    },
    decode(state) {
      const arr = []
      while (state.start < state.end) arr.push(enc.decode(state))
      return arr
    }
  }
}

exports.json = {
  preencode(state, v) {
    utf8.preencode(state, JSON.stringify(v))
  },
  encode(state, v) {
    utf8.encode(state, JSON.stringify(v))
  },
  decode(state) {
    return JSON.parse(utf8.decode(state))
  }
}

exports.ndjson = {
  preencode(state, v) {
    utf8.preencode(state, JSON.stringify(v) + '\n')
  },
  encode(state, v) {
    utf8.encode(state, JSON.stringify(v) + '\n')
  },
  decode(state) {
    return JSON.parse(utf8.decode(state))
  }
}
const sodium = require('sodium-universal')
const c = require('compact-encoding')
const b4a = require('b4a')

// https://en.wikipedia.org/wiki/Merkle_tree#Second_preimage_attack
const LEAF_TYPE = b4a.from([0])
const PARENT_TYPE = b4a.from([1])
const ROOT_TYPE = b4a.from([2])

const HYPERCORE = b4a.from('hypercore')

exports.keyPair = function (seed) {
  // key pairs might stay around for a while, so better not to use a default slab to avoid retaining it completely
  const slab = b4a.allocUnsafeSlow(sodium.crypto_sign_PUBLICKEYBYTES + sodium.crypto_sign_SECRETKEYBYTES)
  const publicKey = slab.subarray(0, sodium.crypto_sign_PUBLICKEYBYTES)
  const secretKey = slab.subarray(sodium.crypto_sign_PUBLICKEYBYTES)

  if (seed) sodium.crypto_sign_seed_keypair(publicKey, secretKey, seed)
  else sodium.crypto_sign_keypair(publicKey, secretKey)

  return {
    publicKey,
    secretKey
  }
}

exports.validateKeyPair = function (keyPair) {
  const pk = b4a.allocUnsafe(sodium.crypto_sign_PUBLICKEYBYTES)
  sodium.crypto_sign_ed25519_sk_to_pk(pk, keyPair.secretKey)
  return b4a.equals(pk, keyPair.publicKey)
}

exports.sign = function (message, secretKey) {
  // Dedicated slab for the signature, to avoid retaining unneeded mem and for security
  const signature = b4a.allocUnsafeSlow(sodium.crypto_sign_BYTES)
  sodium.crypto_sign_detached(signature, message, secretKey)
  return signature
}

exports.verify = function (message, signature, publicKey) {
  if (signature.byteLength !== sodium.crypto_sign_BYTES) return false
  if (publicKey.byteLength !== sodium.crypto_sign_PUBLICKEYBYTES) return false
  return sodium.crypto_sign_verify_detached(signature, message, publicKey)
}

exports.encrypt = function (message, publicKey) {
  const ciphertext = b4a.alloc(message.byteLength + sodium.crypto_box_SEALBYTES)
  sodium.crypto_box_seal(ciphertext, message, publicKey)
  return ciphertext
}

exports.decrypt = function (ciphertext, keyPair) {
  if (ciphertext.byteLength < sodium.crypto_box_SEALBYTES) return null

  const plaintext = b4a.alloc(ciphertext.byteLength - sodium.crypto_box_SEALBYTES)

  if (!sodium.crypto_box_seal_open(plaintext, ciphertext, keyPair.publicKey, keyPair.secretKey)) {
    return null
  }

  return plaintext
}

exports.encryptionKeyPair = function (seed) {
  const publicKey = b4a.alloc(sodium.crypto_box_PUBLICKEYBYTES)
  const secretKey = b4a.alloc(sodium.crypto_box_SECRETKEYBYTES)

  if (seed) {
    sodium.crypto_box_seed_keypair(publicKey, secretKey, seed)
  } else {
    sodium.crypto_box_keypair(publicKey, secretKey)
  }

  return {
    publicKey,
    secretKey
  }
}

exports.data = function (data) {
  const out = b4a.allocUnsafe(32)

  sodium.crypto_generichash_batch(out, [
    LEAF_TYPE,
    c.encode(c.uint64, data.byteLength),
    data
  ])

  return out
}

exports.parent = function (a, b) {
  if (a.index > b.index) {
    const tmp = a
    a = b
    b = tmp
  }

  const out = b4a.allocUnsafe(32)

  sodium.crypto_generichash_batch(out, [
    PARENT_TYPE,
    c.encode(c.uint64, a.size + b.size),
    a.hash,
    b.hash
  ])

  return out
}

exports.tree = function (roots, out) {
  const buffers = new Array(3 * roots.length + 1)
  let j = 0

  buffers[j++] = ROOT_TYPE

  for (let i = 0; i < roots.length; i++) {
    const r = roots[i]
    buffers[j++] = r.hash
    buffers[j++] = c.encode(c.uint64, r.index)
    buffers[j++] = c.encode(c.uint64, r.size)
  }

  if (!out) out = b4a.allocUnsafe(32)
  sodium.crypto_generichash_batch(out, buffers)
  return out
}

exports.hash = function (data, out) {
  if (!out) out = b4a.allocUnsafe(32)
  if (!Array.isArray(data)) data = [data]

  sodium.crypto_generichash_batch(out, data)

  return out
}

exports.randomBytes = function (n) {
  const buf = b4a.allocUnsafe(n)
  sodium.randombytes_buf(buf)
  return buf
}

exports.discoveryKey = function (key) {
  if (!key || key.byteLength !== 32) throw new Error('Must pass a 32 byte buffer')
  // Discovery keys might stay around for a while, so better not to use slab memory (for better gc)
  const digest = b4a.allocUnsafeSlow(32)
  sodium.crypto_generichash(digest, HYPERCORE, key)
  return digest
}

if (sodium.sodium_free) {
  exports.free = function (secureBuf) {
    if (secureBuf.secure) sodium.sodium_free(secureBuf)
  }
} else {
  exports.free = function () {}
}

exports.namespace = function (name, count) {
  const ids = typeof count === 'number' ? range(count) : count

  // Namespaces are long-lived, so better to use a dedicated slab
  const buf = b4a.allocUnsafeSlow(32 * ids.length)

  const list = new Array(ids.length)

  // ns is emhemeral, so default slab
  const ns = b4a.allocUnsafe(33)
  sodium.crypto_generichash(ns.subarray(0, 32), typeof name === 'string' ? b4a.from(name) : name)

  for (let i = 0; i < list.length; i++) {
    list[i] = buf.subarray(32 * i, 32 * i + 32)
    ns[32] = ids[i]
    sodium.crypto_generichash(list[i], ns)
  }

  return list
}

function range (count) {
  const arr = new Array(count)
  for (let i = 0; i < count; i++) arr[i] = i
  return arr
}
{
  "name": "hypercore-crypto",
  "version": "3.6.1",
  "description": "The crypto primitives used in hypercore, extracted into a separate module",
  "main": "index.js",
  "files": [
    "index.js"
  ],
  "dependencies": {
    "b4a": "^1.6.6",
    "compact-encoding": "^2.15.0",
    "sodium-universal": "^5.0.0"
  },
  "devDependencies": {
    "brittle": "^3.5.0",
    "standard": "^17.1.0"
  },
  "scripts": {
    "test": "standard && brittle test.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/mafintosh/hypercore-crypto.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/mafintosh/hypercore-crypto/issues"
  },
  "homepage": "https://github.com/mafintosh/hypercore-crypto"
}
module.exports = require.addon.bind(require)
{
  "name": "require-addon",
  "version": "1.2.0",
  "description": "Import native addons across JavaScript runtimes",
  "exports": {
    "./package": "./package.json",
    ".": {
      "bare": "./lib/bare.js",
      "node": "./lib/node.js",
      "default": "./lib/default.js"
    }
  },
  "imports": {
    "fs": {
      "bare": "bare-fs",
      "default": "fs"
    },
    "path": {
      "bare": "bare-path",
      "default": "path"
    },
    "url": {
      "bare": "bare-url",
      "default": "url"
    }
  },
  "files": [
    "lib"
  ],
  "scripts": {
    "test": "npm run lint && npm run test:bare && npm run test:node",
    "test:bare": "bare test.js",
    "test:node": "node test.js",
    "lint": "prettier --check .",
    "format": "prettier --write ."
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/require-addon.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/require-addon/issues"
  },
  "homepage": "https://github.com/holepunchto/require-addon#readme",
  "engines": {
    "bare": ">=1.10.0"
  },
  "dependencies": {
    "bare-addon-resolve": "^1.3.0"
  },
  "devDependencies": {
    "bare-bundle": "^1.8.1",
    "bare-bundle-evaluate": "^1.1.0",
    "bare-fs": "^4.0.0",
    "bare-path": "^3.0.0",
    "bare-url": "^2.1.0",
    "brittle": "^3.7.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^2.0.0"
  }
}
require.addon = require('require-addon')
module.exports = require.addon('.', __filename)
const assert = require('assert')
const binding = require('./binding')
const { isNode } = require('which-runtime')

const OPTIONAL = Buffer.from(new ArrayBuffer(0))

module.exports = exports = { ...binding }

// memory

exports.sodium_memzero = function (buf) {
  assert(ArrayBuffer.isView(buf), 'buf must be a typed array')

  binding.sodium_memzero(buf)
}

exports.sodium_mlock = function (buf) {
  assert(ArrayBuffer.isView(buf), 'buf must be a typed array')

  const res = binding.sodium_mlock(buf)

  if (res !== 0) throw new Error('memory lock failed')
}

exports.sodium_munlock = function (buf) {
  assert(ArrayBuffer.isView(buf), 'buf must be a typed array')

  const res = binding.sodium_munlock(buf)

  if (res !== 0) throw new Error('memory unlock failed')
}

exports.sodium_malloc = function (size) {
  assert(size >= 0, 'invalid size')

  const buf = Buffer.from(binding.sodium_malloc(size))
  buf.secure = true
  return buf
}

exports.sodium_free = function (buf) {
  if (!buf || !buf.secure) return

  binding.sodium_free(buf.buffer)
}

exports.sodium_mprotect_noaccess = function (buf) {
  const res = binding.sodium_mprotect_noaccess(buf.buffer)

  if (res !== 0) throw new Error('failed to lock buffer')
}

exports.sodium_mprotect_readonly = function (buf) {
  const res = binding.sodium_mprotect_readonly(buf.buffer)

  if (res !== 0) throw new Error('failed to unlock buffer')
}

exports.sodium_mprotect_readwrite = function (buf) {
  const res = binding.sodium_mprotect_readwrite(buf.buffer)

  if (res !== 0) throw new Error('failed to unlock buffer')
}

// crypto_randombytes

exports.randombytes_buf = function (buffer) {
  assert(ArrayBuffer.isView(buffer), 'buffer must be a typed array')

  binding.randombytes_buf(buffer.buffer, buffer.byteOffset, buffer.byteLength)
}

exports.randombytes_buf_deterministic = function (buffer, seed) {
  assert(ArrayBuffer.isView(buffer), 'buffer must be a typed array')
  assert(ArrayBuffer.isView(seed), 'seed must be a typed array')
  assert(
    seed.byteLength === binding.randombytes_SEEDBYTES,
    "seed must be 'randombytes_SEEDBYTES' bytes"
  )

  binding.randombytes_buf_deterministic(
    buffer.buffer,
    buffer.byteOffset,
    buffer.byteLength,

    seed.buffer,
    seed.byteOffset,
    seed.byteLength
  )
}

// sodium_helpers

exports.sodium_memcmp = function (a, b) {
  assert(ArrayBuffer.isView(a), 'a must be a typed array')
  assert(ArrayBuffer.isView(b), 'b must be a typed array')
  assert(a.byteLength === b.byteLength, 'buffers must be of same length')

  return binding.sodium_memcmp(a, b)
}

exports.sodium_add = function (a, b) {
  assert(ArrayBuffer.isView(a), 'a must be a typed array')
  assert(ArrayBuffer.isView(b), 'b must be a typed array')
  assert(a.byteLength === b.byteLength, 'buffers must be of same length')

  binding.sodium_add(a, b)
}

exports.sodium_sub = function (a, b) {
  assert(ArrayBuffer.isView(a), 'a must be a typed array')
  assert(ArrayBuffer.isView(b), 'b must be a typed array')
  assert(a.byteLength === b.byteLength, 'buffers must be of same length')

  binding.sodium_sub(a, b)
}

exports.sodium_compare = function (a, b) {
  assert(ArrayBuffer.isView(a), 'a must be a typed array')
  assert(ArrayBuffer.isView(b), 'b must be a typed array')
  assert(a.byteLength === b.byteLength, 'buffers must be of same length')

  return binding.sodium_compare(a, b)
}

exports.sodium_is_zero = function (buffer, length) {
  if (length === undefined) length = buffer.byteLength

  assert(ArrayBuffer.isView(buffer), 'buffer must be a typed array')
  assert(length >= 0 && length <= buffer.byteLength, 'invalid length')

  return binding.sodium_is_zero(buffer, length)
}

exports.sodium_pad = function (buffer, unpaddedBuflen, blockSize) {
  assert(ArrayBuffer.isView(buffer), 'buffer must be a typed array')
  assert(unpaddedBuflen <= buffer.byteLength, 'unpadded length cannot exceed buffer length')
  assert(blockSize <= buffer.byteLength, 'block size cannot exceed buffer length')
  assert(blockSize >= 1, 'block size must be at least 1 byte')
  assert(
    buffer.byteLength >= unpaddedBuflen + (blockSize - (unpaddedBuflen % blockSize)),
    'buf not long enough'
  )

  return binding.sodium_pad(buffer, unpaddedBuflen, blockSize)
}

exports.sodium_unpad = function (buffer, paddedBuflen, blockSize) {
  assert(ArrayBuffer.isView(buffer), 'buffer must be a typed array')
  assert(paddedBuflen <= buffer.byteLength, 'unpadded length cannot exceed buffer length')
  assert(blockSize <= buffer.byteLength, 'block size cannot exceed buffer length')
  assert(blockSize >= 1, 'block size must be at least 1 byte')

  return binding.sodium_unpad(buffer, paddedBuflen, blockSize)
}

// crypto_sign

exports.crypto_sign_keypair = function (pk, sk) {
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(
    pk.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
    "pk must be 'crypto_sign_PUBLICKEYBYTES' bytes"
  )
  assert(
    sk.byteLength === binding.crypto_sign_SECRETKEYBYTES,
    "sk must be 'crypto_sign_SECRETKEYBYTES' bytes"
  )

  const res = binding.crypto_sign_keypair(pk, sk)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_sign_seed_keypair = function (pk, sk, seed) {
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(ArrayBuffer.isView(seed), 'seed must be a typed array')
  assert(
    pk.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
    "pk must be 'crypto_sign_PUBLICKEYBYTES' bytes"
  )
  assert(
    sk.byteLength === binding.crypto_sign_SECRETKEYBYTES,
    "sk must be 'crypto_sign_SECRETKEYBYTES' bytes"
  )
  assert(
    seed.byteLength === binding.crypto_sign_SEEDBYTES,
    "seed must be 'crypto_sign_SEEDBYTES' bytes"
  )

  const res = binding.crypto_sign_seed_keypair(pk, sk, seed)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_sign = function (sm, m, sk) {
  assert(ArrayBuffer.isView(sm), 'sm must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(
    sm.byteLength === binding.crypto_sign_BYTES + m.byteLength,
    "sm must be 'm.byteLength + crypto_sign_BYTES' bytes"
  )
  assert(
    sk.byteLength === binding.crypto_sign_SECRETKEYBYTES,
    "sk must be 'crypto_sign_SECRETKEYBYTES' bytes"
  )

  const res = binding.crypto_sign(sm, m, sk)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_sign_open = function (m, sm, pk) {
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(sm), 'sm must be a typed array')
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(
    sm.byteLength >= binding.crypto_sign_BYTES,
    "sm must be at least 'crypto_sign_BYTES' bytes"
  )
  assert(
    m.byteLength === sm.byteLength - binding.crypto_sign_BYTES,
    "m must be 'sm.byteLength - crypto_sign_BYTES' bytes"
  )
  assert(
    pk.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
    "pk must be 'crypto_sign_PUBLICKEYBYTES' bytes"
  )

  const res = binding.crypto_sign_open(m, sm, pk)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_sign_open = function (m, sm, pk) {
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(sm), 'sm must be a typed array')
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(
    sm.byteLength >= binding.crypto_sign_BYTES,
    "sm must be at least 'crypto_sign_BYTES' bytes"
  )
  assert(
    m.byteLength === sm.byteLength - binding.crypto_sign_BYTES,
    "m must be 'sm.byteLength - crypto_sign_BYTES' bytes"
  )
  assert(
    pk.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
    "pk must be 'crypto_sign_PUBLICKEYBYTES' bytes"
  )

  return binding.crypto_sign_open(m, sm, pk)
}

exports.crypto_sign_detached = function (sig, m, sk) {
  assert(ArrayBuffer.isView(sig), 'sig must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(sig.byteLength === binding.crypto_sign_BYTES, "sig must be 'crypto_sign_BYTES' bytes")
  assert(
    sk.byteLength === binding.crypto_sign_SECRETKEYBYTES,
    "sk must be 'crypto_sign_SECRETKEYBYTES' bytes"
  )

  const res = binding.crypto_sign_detached(sig, m, sk)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_sign_verify_detached = function (sig, m, pk) {
  assert(ArrayBuffer.isView(sig), 'sig must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(
    sig.byteLength >= binding.crypto_sign_BYTES,
    "sig must be at least 'crypto_sign_BYTES' bytes"
  )
  assert(
    pk.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
    "pk must be 'crypto_sign_PUBLICKEYBYTES' bytes"
  )

  return binding.crypto_sign_verify_detached(
    sig.buffer,
    sig.byteOffset,
    sig.byteLength,

    m.buffer,
    m.byteOffset,
    m.byteLength,

    pk.buffer,
    pk.byteOffset,
    pk.byteLength
  )
}

exports.crypto_sign_ed25519_sk_to_pk = function (pk, sk) {
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(
    pk.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
    "pk must be 'crypto_sign_PUBLICKEYBYTES' bytes"
  )
  assert(
    sk.byteLength === binding.crypto_sign_SECRETKEYBYTES,
    "sk must be 'crypto_sign_SECRETKEYBYTES' bytes"
  )

  const res = binding.crypto_sign_ed25519_sk_to_pk(pk, sk)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_sign_ed25519_pk_to_curve25519 = function (x25519pk, ed25519pk) {
  assert(ArrayBuffer.isView(x25519pk), 'x25519pk must be a typed array')
  assert(ArrayBuffer.isView(ed25519pk), 'ed25519pk must be a typed array')
  assert(
    x25519pk.byteLength === binding.crypto_box_PUBLICKEYBYTES,
    "x25519pk must be 'crypto_box_PUBLICKEYBYTES' bytes"
  )
  assert(
    ed25519pk.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
    "ed25519pk must be 'crypto_sign_PUBLICKEYBYTES' bytes"
  )

  const res = binding.crypto_sign_ed25519_pk_to_curve25519(x25519pk, ed25519pk)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_sign_ed25519_sk_to_curve25519 = function (x25519sk, ed25519sk) {
  assert(ArrayBuffer.isView(x25519sk), 'x25519sk must be a typed array')
  assert(ArrayBuffer.isView(ed25519sk), 'ed25519sk must be a typed array')
  assert(
    x25519sk.byteLength === binding.crypto_box_SECRETKEYBYTES,
    "x25519sk must be 'crypto_box_SECRETKEYBYTES' bytes"
  )

  const edLen = ed25519sk.byteLength

  assert(
    edLen === binding.crypto_sign_SECRETKEYBYTES || edLen === binding.crypto_box_SECRETKEYBYTES,
    "ed25519sk must be 'crypto_sign_SECRETKEYBYTES' or 'crypto_sign_SECRETKEYBYTES - crypto_sign_PUBLICKEYBYTES' bytes"
  )

  const res = binding.crypto_sign_ed25519_sk_to_curve25519(x25519sk, ed25519sk)

  if (res !== 0) throw new Error('status: ' + res)
}

// crypto_box

exports.crypto_box_keypair = function (pk, sk) {
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(
    pk.byteLength === binding.crypto_box_PUBLICKEYBYTES,
    "pk must be 'crypto_box_PUBLICKEYBYTES' bytes"
  )
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(
    sk.byteLength === binding.crypto_box_SECRETKEYBYTES,
    "sk must be 'crypto_box_SECRETKEYBYTES' bytes"
  )

  const res = binding.crypto_box_keypair(pk, sk)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_box_seed_keypair = function (pk, sk, seed) {
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(
    pk.byteLength === binding.crypto_box_PUBLICKEYBYTES,
    "pk must be 'crypto_box_PUBLICKEYBYTES' bytes"
  )
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(
    sk.byteLength === binding.crypto_box_SECRETKEYBYTES,
    "sk must be 'crypto_box_SECRETKEYBYTES' bytes"
  )
  assert(ArrayBuffer.isView(seed), 'seed must be a typed array')
  assert(
    seed.byteLength === binding.crypto_box_SEEDBYTES,
    "seed must be 'crypto_box_SEEDBYTES' bytes"
  )

  const res = binding.crypto_box_seed_keypair(pk, sk, seed)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_box_easy = function (c, m, n, pk, sk) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(
    c.byteLength === m.byteLength + exports.crypto_box_MACBYTES,
    "c must be 'm.byteLength + crypto_box_MACBYTES' bytes"
  )
  assert(n.byteLength === exports.crypto_box_NONCEBYTES, "n must be 'crypto_box_NONCEBYTES' bytes")
  assert(
    pk.byteLength === exports.crypto_box_PUBLICKEYBYTES,
    "pk must be 'crypto_box_PUBLICKEYBYTES' bytes"
  )
  assert(
    sk.byteLength === exports.crypto_box_SECRETKEYBYTES,
    "sk must be 'crypto_box_SECRETKEYBYTES' bytes"
  )

  const res = binding.crypto_box_easy(c, m, n, pk, sk)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_box_detached = function (c, mac, m, n, pk, sk) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(mac), 'mac must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(mac.byteLength === exports.crypto_box_MACBYTES, "mac must be 'crypto_box_MACBYTES' bytes")
  assert(n.byteLength === exports.crypto_box_NONCEBYTES, "n must be 'crypto_box_NONCEBYTES' bytes")
  assert(
    pk.byteLength === exports.crypto_box_PUBLICKEYBYTES,
    "pk must be 'crypto_box_PUBLICKEYBYTES' bytes"
  )
  assert(
    sk.byteLength === exports.crypto_box_SECRETKEYBYTES,
    "sk must be 'crypto_box_SECRETKEYBYTES' bytes"
  )

  const res = binding.crypto_box_detached(c, mac, m, n, pk, sk)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_box_open_easy = function (m, c, n, pk, sk) {
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(
    c.byteLength >= exports.crypto_box_MACBYTES,
    "c must be at least 'crypto_box_MACBYTES' bytes"
  )
  assert(
    m.byteLength === c.byteLength - exports.crypto_box_MACBYTES,
    "m must be 'c.byteLength - crypto_box_MACBYTES' bytes"
  )
  assert(n.byteLength === exports.crypto_box_NONCEBYTES, "n must be 'crypto_box_NONCEBYTES' bytes")
  assert(
    pk.byteLength === exports.crypto_box_PUBLICKEYBYTES,
    "pk must be 'crypto_box_PUBLICKEYBYTES' bytes"
  )
  assert(
    sk.byteLength === exports.crypto_box_SECRETKEYBYTES,
    "sk must be 'crypto_box_SECRETKEYBYTES' bytes"
  )

  return binding.crypto_box_open_easy(m, c, n, pk, sk)
}

exports.crypto_box_open_detached = function (m, c, mac, n, pk, sk) {
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(mac), 'mac must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(m.byteLength === c.byteLength, "m must be 'c.byteLength' bytes")
  assert(mac.byteLength === exports.crypto_box_MACBYTES, "mac must be 'crypto_box_MACBYTES' bytes")
  assert(n.byteLength === exports.crypto_box_NONCEBYTES, "n must be 'crypto_box_NONCEBYTES' bytes")
  assert(
    pk.byteLength === exports.crypto_box_PUBLICKEYBYTES,
    "pk must be 'crypto_box_PUBLICKEYBYTES' bytes"
  )
  assert(
    sk.byteLength === exports.crypto_box_SECRETKEYBYTES,
    "sk must be 'crypto_box_SECRETKEYBYTES' bytes"
  )

  return binding.crypto_box_open_detached(m, c, mac, n, pk, sk)
}

exports.crypto_box_seal = function (c, m, pk) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(
    c.byteLength === m.byteLength + exports.crypto_box_SEALBYTES,
    "c must be 'm.byteLength + crypto_box_SEALBYTES' bytes"
  )
  assert(
    pk.byteLength === exports.crypto_box_PUBLICKEYBYTES,
    "pk must be 'crypto_box_PUBLICKEYBYTES' bytes"
  )

  const res = binding.crypto_box_seal(c, m, pk)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_box_seal_open = function (m, c, pk, sk) {
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(
    c.byteLength >= exports.crypto_box_SEALBYTES,
    "c must be at least 'crypto_box_SEALBYTES' bytes"
  )
  assert(
    m.byteLength === c.byteLength - exports.crypto_box_SEALBYTES,
    "m must be 'c.byteLength - crypto_box_SEALBYTES' bytes"
  )
  assert(
    pk.byteLength === exports.crypto_box_PUBLICKEYBYTES,
    "pk must be 'crypto_box_PUBLICKEYBYTES' bytes"
  )
  assert(
    sk.byteLength === exports.crypto_box_SECRETKEYBYTES,
    "sk must be 'crypto_box_SECRETKEYBYTES' bytes"
  )

  return binding.crypto_box_seal_open(
    m.buffer,
    m.byteOffset,
    m.byteLength,

    c.buffer,
    c.byteOffset,
    c.byteLength,

    pk.buffer,
    pk.byteOffset,
    pk.byteLength,

    sk.buffer,
    sk.byteOffset,
    sk.byteLength
  )
}

// crypto_secretbox

exports.crypto_secretbox_easy = function (c, m, n, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    c.byteLength === m.byteLength + binding.crypto_secretbox_MACBYTES,
    "c must be 'm.byteLength + crypto_secretbox_MACBYTES' bytes"
  )
  assert(
    n.byteLength === binding.crypto_secretbox_NONCEBYTES,
    "n must be 'crypto_secretbox_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_secretbox_KEYBYTES,
    "k must be 'crypto_secretbox_KEYBYTES' bytes"
  )

  const res = binding.crypto_secretbox_easy(c, m, n, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_secretbox_open_easy = function (m, c, n, k) {
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    c.byteLength >= binding.crypto_secretbox_MACBYTES,
    "c must be at least 'crypto_secretbox_MACBYTES' bytes"
  )
  assert(
    m.byteLength === c.byteLength - binding.crypto_secretbox_MACBYTES,
    "m must be 'c.byteLength - crypto_secretbox_MACBYTES' bytes"
  )
  assert(
    n.byteLength === binding.crypto_secretbox_NONCEBYTES,
    "n must be 'crypto_secretbox_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_secretbox_KEYBYTES,
    "k must be 'crypto_secretbox_KEYBYTES' bytes"
  )

  return binding.crypto_secretbox_open_easy(m, c, n, k)
}

exports.crypto_secretbox_detached = function (c, mac, m, n, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(mac), 'mac must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(
    mac.byteLength === binding.crypto_secretbox_MACBYTES,
    "mac must be 'crypto_secretbox_MACBYTES' bytes"
  )
  assert(
    n.byteLength === binding.crypto_secretbox_NONCEBYTES,
    "n must be 'crypto_secretbox_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_secretbox_KEYBYTES,
    "k must be 'crypto_secretbox_KEYBYTES' bytes"
  )

  const res = binding.crypto_secretbox_detached(c, mac, m, n, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_secretbox_open_detached = function (m, c, mac, n, k) {
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(mac), 'mac must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(m.byteLength === c.byteLength, "m must be 'c.byteLength' bytes")
  assert(
    mac.byteLength === binding.crypto_secretbox_MACBYTES,
    "mac must be 'crypto_secretbox_MACBYTES' bytes"
  )
  assert(
    n.byteLength === binding.crypto_secretbox_NONCEBYTES,
    "n must be 'crypto_secretbox_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_secretbox_KEYBYTES,
    "k must be 'crypto_secretbox_KEYBYTES' bytes"
  )

  return binding.crypto_secretbox_open_detached(m, c, mac, n, k)
}

// crypto_generichash

exports.crypto_generichash = function (output, input, key) {
  if (!key) key = OPTIONAL

  assert(ArrayBuffer.isView(output), 'output must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(
    output.byteLength >= binding.crypto_generichash_BYTES_MIN &&
      output.byteLength <= binding.crypto_generichash_BYTES_MAX,
    'output must be between crypto_generichash_BYTES_MIN and crypto_generichash_BYTES_MAX bytes'
  )

  if (key !== OPTIONAL) {
    assert(ArrayBuffer.isView(key), 'key must be a typed array')
    assert(
      key.byteLength >= binding.crypto_generichash_KEYBYTES_MIN &&
        key.byteLength <= binding.crypto_generichash_KEYBYTES_MAX,
      'key must be between crypto_generichash_KEYBYTES_MIN and crypto_generichash_KEYBYTES_MAX bytes'
    )
  }

  const res = binding.crypto_generichash(
    output.buffer,
    output.byteOffset,
    output.byteLength,

    input.buffer,
    input.byteOffset,
    input.byteLength,

    key.buffer,
    key.byteOffset,
    key.byteLength
  )

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_generichash_batch = function (output, batch, key) {
  assert(ArrayBuffer.isView(output), 'output must be a typed array')

  if (isNode || batch.length < 4) {
    const res = binding.crypto_generichash_batch(output, batch, !!key, key || OPTIONAL)
    if (res !== 0) throw new Error('status: ' + res)
  } else {
    const state = Buffer.alloc(binding.crypto_generichash_STATEBYTES)

    exports.crypto_generichash_init(state, key, output.byteLength)

    for (const buf of batch) {
      exports.crypto_generichash_update(state, buf)
    }

    exports.crypto_generichash_final(state, output)
  }
}

exports.crypto_generichash_keygen = function (key) {
  assert(ArrayBuffer.isView(key), 'key must be a typed array')
  assert(
    key.byteLength === binding.crypto_generichash_KEYBYTES,
    "key must be 'crypto_generichash_KEYBYTES' bytes"
  )

  const res = binding.crypto_generichash_keygen(key.buffer, key.byteOffset, key.byteLength)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_generichash_init = function (state, key, outputLength) {
  if (!key) key = OPTIONAL

  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_generichash_STATEBYTES,
    "state must be 'crypto_generichash_STATEBYTES' bytes"
  )

  const res = binding.crypto_generichash_init(
    state.buffer,
    state.byteOffset,
    state.byteLength,

    key.buffer,
    key.byteOffset,
    key.byteLength,

    outputLength
  )

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_generichash_update = function (state, input) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(
    state.byteLength === binding.crypto_generichash_STATEBYTES,
    "state must be 'crypto_generichash_STATEBYTES' bytes"
  )

  const res = binding.crypto_generichash_update(
    state.buffer,
    state.byteOffset,
    state.byteLength,

    input.buffer,
    input.byteOffset,
    input.byteLength
  )

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_generichash_final = function (state, output) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(ArrayBuffer.isView(output), 'output must be a typed array')
  assert(
    state.byteLength === binding.crypto_generichash_STATEBYTES,
    "state must be 'crypto_generichash_STATEBYTES' bytes"
  )

  const res = binding.crypto_generichash_final(
    state.buffer,
    state.byteOffset,
    state.byteLength,

    output.buffer,
    output.byteOffset,
    output.byteLength
  )

  if (res !== 0) throw new Error('status: ' + res)
}

// secretstream

exports.crypto_secretstream_xchacha20poly1305_keygen = function (k) {
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_secretstream_xchacha20poly1305_KEYBYTES,
    "k must be 'crypto_secretstream_xchacha20poly1305_KEYBYTES' bytes"
  )

  binding.crypto_secretstream_xchacha20poly1305_keygen(k.buffer, k.byteOffset, k.byteLength)
}

exports.crypto_secretstream_xchacha20poly1305_init_push = function (state, header, k) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(ArrayBuffer.isView(header), 'header must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    state.byteLength === binding.crypto_secretstream_xchacha20poly1305_STATEBYTES,
    "state must be 'crypto_secretstream_xchacha20poly1305_STATEBYTES' bytes"
  )
  assert(
    header.byteLength === binding.crypto_secretstream_xchacha20poly1305_HEADERBYTES,
    "header must be 'crypto_secretstream_xchacha20poly1305_HEADERBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_secretstream_xchacha20poly1305_KEYBYTES,
    "k must be 'crypto_secretstream_xchacha20poly1305_KEYBYTES' bytes"
  )

  const res = binding.crypto_secretstream_xchacha20poly1305_init_push(
    state.buffer,
    state.byteOffset,
    state.byteLength,

    header.buffer,
    header.byteOffset,
    header.byteLength,

    k.buffer,
    k.byteOffset,
    k.byteLength
  )

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_secretstream_xchacha20poly1305_init_pull = function (state, header, k) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(ArrayBuffer.isView(header), 'header must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    state.byteLength === binding.crypto_secretstream_xchacha20poly1305_STATEBYTES,
    "state must be 'crypto_secretstream_xchacha20poly1305_STATEBYTES' bytes"
  )
  assert(
    header.byteLength === binding.crypto_secretstream_xchacha20poly1305_HEADERBYTES,
    "header must be 'crypto_secretstream_xchacha20poly1305_HEADERBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_secretstream_xchacha20poly1305_KEYBYTES,
    "k must be 'crypto_secretstream_xchacha20poly1305_KEYBYTES' bytes"
  )

  const res = binding.crypto_secretstream_xchacha20poly1305_init_pull(
    state.buffer,
    state.byteOffset,
    state.byteLength,

    header.buffer,
    header.byteOffset,
    header.byteLength,

    k.buffer,
    k.byteOffset,
    k.byteLength
  )

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_secretstream_xchacha20poly1305_push = function (state, c, m, ad, tag) {
  if (!ad) ad = OPTIONAL

  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(
    state.byteLength === binding.crypto_secretstream_xchacha20poly1305_STATEBYTES,
    "state must be 'crypto_secretstream_xchacha20poly1305_STATEBYTES' bytes"
  )
  assert(
    c.byteLength === m.byteLength + binding.crypto_secretstream_xchacha20poly1305_ABYTES,
    "c must be 'm.byteLength + crypto_secretstream_xchacha20poly1305_ABYTES' bytes"
  )

  const res = binding.crypto_secretstream_xchacha20poly1305_push(
    state.buffer,
    state.byteOffset,
    state.byteLength,

    c.buffer,
    c.byteOffset,
    c.byteLength,

    m.buffer,
    m.byteOffset,
    m.byteLength,

    ad.buffer,
    ad.byteOffset,
    ad.byteLength,

    tag
  )

  if (res < 0) throw new Error('push failed')

  return res
}

exports.crypto_secretstream_xchacha20poly1305_pull = function (state, m, tag, c, ad) {
  if (!ad) ad = OPTIONAL

  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_secretstream_xchacha20poly1305_STATEBYTES,
    "state must be 'crypto_secretstream_xchacha20poly1305_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(tag), 'tag must be a typed array')
  assert(tag.byteLength === 1, 'tag must be 1 byte')

  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(
    c.byteLength >= binding.crypto_secretstream_xchacha20poly1305_ABYTES,
    "c must be at least 'crypto_secretstream_xchacha20poly1305_ABYTES' bytes"
  )
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(
    m.byteLength === c.byteLength - binding.crypto_secretstream_xchacha20poly1305_ABYTES,
    "m must be 'c.byteLength - crypto_secretstream_xchacha20poly1305_ABYTES' bytes"
  )

  const res = binding.crypto_secretstream_xchacha20poly1305_pull(
    state.buffer,
    state.byteOffset,
    state.byteLength,

    m.buffer,
    m.byteOffset,
    m.byteLength,

    tag.buffer,
    tag.byteOffset,
    tag.byteLength,

    c.buffer,
    c.byteOffset,
    c.byteLength,

    ad.buffer,
    ad.byteOffset,
    ad.byteLength
  )

  if (res < 0) throw new Error('pull failed')

  return res
}

exports.crypto_secretstream_xchacha20poly1305_rekey = function (state) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_secretstream_xchacha20poly1305_STATEBYTES,
    "state must be 'crypto_secretstream_xchacha20poly1305_STATEBYTES' bytes"
  )

  binding.crypto_secretstream_xchacha20poly1305_rekey(
    state.buffer,
    state.byteOffset,
    state.byteLength
  )
}

// crypto_stream

exports.crypto_stream = function (c, n, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    n.byteLength === binding.crypto_stream_NONCEBYTES,
    "n must be 'crypto_stream_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_KEYBYTES,
    "k must be 'crypto_stream_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream(c, n, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_xor = function (c, m, n, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(
    n.byteLength === binding.crypto_stream_NONCEBYTES,
    "n must be 'crypto_stream_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_KEYBYTES,
    "k must be 'crypto_stream_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_xor(
    c.buffer,
    c.byteOffset,
    c.byteLength,

    m.buffer,
    m.byteOffset,
    m.byteLength,

    n.buffer,
    n.byteOffset,
    n.byteLength,

    k.buffer,
    k.byteOffset,
    k.byteLength
  )

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_chacha20 = function (c, n, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    n.byteLength === binding.crypto_stream_chacha20_NONCEBYTES,
    "n must be 'crypto_stream_chacha20_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_chacha20_KEYBYTES,
    "k must be 'crypto_stream_chacha20_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_chacha20(c, n, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_chacha20_xor = function (c, m, n, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(
    n.byteLength === binding.crypto_stream_chacha20_NONCEBYTES,
    "n must be 'crypto_stream_chacha20_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_chacha20_KEYBYTES,
    "k must be 'crypto_stream_chacha20_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_chacha20_xor(c, m, n, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_chacha20_xor_ic = function (c, m, n, ic, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(
    n.byteLength === binding.crypto_stream_chacha20_NONCEBYTES,
    "n must be 'crypto_stream_chacha20_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_chacha20_KEYBYTES,
    "k must be 'crypto_stream_chacha20_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_chacha20_xor_ic(c, m, n, ic, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_chacha20_ietf = function (c, n, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    n.byteLength === binding.crypto_stream_chacha20_ietf_NONCEBYTES,
    "n must be 'crypto_stream_chacha20_ietf_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_chacha20_ietf_KEYBYTES,
    "k must be 'crypto_stream_chacha20_ietf_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_chacha20_ietf(c, n, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_chacha20_ietf_xor = function (c, m, n, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(
    n.byteLength === binding.crypto_stream_chacha20_ietf_NONCEBYTES,
    "n must be 'crypto_stream_chacha20_ietf_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_chacha20_ietf_KEYBYTES,
    "k must be 'crypto_stream_chacha20_ietf_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_chacha20_ietf_xor(c, m, n, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_chacha20_ietf_xor_ic = function (c, m, n, ic, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(
    n.byteLength === binding.crypto_stream_chacha20_ietf_NONCEBYTES,
    "n must be 'crypto_stream_chacha20_ietf_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_chacha20_ietf_KEYBYTES,
    "k must be 'crypto_stream_chacha20_ietf_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_chacha20_ietf_xor_ic(c, m, n, ic, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_xchacha20 = function (c, n, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    n.byteLength === binding.crypto_stream_xchacha20_NONCEBYTES,
    "n must be 'crypto_stream_xchacha20_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_xchacha20_KEYBYTES,
    "k must be 'crypto_stream_xchacha20_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_xchacha20(c, n, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_xchacha20_xor = function (c, m, n, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(
    n.byteLength === binding.crypto_stream_xchacha20_NONCEBYTES,
    "n must be 'crypto_stream_xchacha20_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_xchacha20_KEYBYTES,
    "k must be 'crypto_stream_xchacha20_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_xchacha20_xor(c, m, n, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_xchacha20_xor_ic = function (c, m, n, ic, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(
    n.byteLength === binding.crypto_stream_xchacha20_NONCEBYTES,
    "n must be 'crypto_stream_xchacha20_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_xchacha20_KEYBYTES,
    "k must be 'crypto_stream_xchacha20_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_xchacha20_xor_ic(c, m, n, ic, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_salsa20 = function (c, n, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    n.byteLength === binding.crypto_stream_salsa20_NONCEBYTES,
    "n must be 'crypto_stream_salsa20_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_salsa20_KEYBYTES,
    "k must be 'crypto_stream_salsa20_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_salsa20(c, n, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_salsa20_xor = function (c, m, n, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(
    n.byteLength === binding.crypto_stream_salsa20_NONCEBYTES,
    "n must be 'crypto_stream_salsa20_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_salsa20_KEYBYTES,
    "k must be 'crypto_stream_salsa20_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_salsa20_xor(c, m, n, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_salsa20_xor_ic = function (c, m, n, ic, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(
    n.byteLength === binding.crypto_stream_salsa20_NONCEBYTES,
    "n must be 'crypto_stream_salsa20_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_salsa20_KEYBYTES,
    "k must be 'crypto_stream_salsa20_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_salsa20_xor_ic(c, m, n, ic, k)

  if (res !== 0) throw new Error('status: ' + res)
}

// crypto_auth

exports.crypto_auth = function (out, input, k) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(out.byteLength === binding.crypto_auth_BYTES, "out must be 'crypto_auth_BYTES' bytes")
  assert(k.byteLength === binding.crypto_auth_KEYBYTES, "k must be 'crypto_auth_KEYBYTES' bytes")

  const res = binding.crypto_auth(out, input, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_auth_verify = function (h, input, k) {
  assert(ArrayBuffer.isView(h), 'h must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(h.byteLength === binding.crypto_auth_BYTES, "h must be 'crypto_auth_BYTES' bytes")
  assert(k.byteLength === binding.crypto_auth_KEYBYTES, "k must be 'crypto_auth_KEYBYTES' bytes")

  return binding.crypto_auth_verify(h, input, k)
}

// crypto_onetimeauth

exports.crypto_onetimeauth = function (out, input, k) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    out.byteLength === binding.crypto_onetimeauth_BYTES,
    "out must be 'crypto_onetimeauth_BYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_onetimeauth_KEYBYTES,
    "k must be 'crypto_onetimeauth_KEYBYTES' bytes"
  )

  const res = binding.crypto_onetimeauth(out, input, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_onetimeauth_init = function (state, k) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    state.byteLength === binding.crypto_onetimeauth_STATEBYTES,
    "state must be 'crypto_onetimeauth_STATEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_onetimeauth_KEYBYTES,
    "k must be 'crypto_onetimeauth_KEYBYTES' bytes"
  )

  const res = binding.crypto_onetimeauth_init(state, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_onetimeauth_update = function (state, input) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(
    state.byteLength === binding.crypto_onetimeauth_STATEBYTES,
    "state must be 'crypto_onetimeauth_STATEBYTES' bytes"
  )

  const res = binding.crypto_onetimeauth_update(state, input)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_onetimeauth_final = function (state, out) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(
    state.byteLength === binding.crypto_onetimeauth_STATEBYTES,
    "state must be 'crypto_onetimeauth_STATEBYTES' bytes"
  )
  assert(
    out.byteLength === binding.crypto_onetimeauth_BYTES,
    "out must be 'crypto_onetimeauth_BYTES' bytes"
  )

  const res = binding.crypto_onetimeauth_final(state, out)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_onetimeauth_verify = function (h, input, k) {
  assert(ArrayBuffer.isView(h), 'h must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    h.byteLength === binding.crypto_onetimeauth_BYTES,
    "h must be 'crypto_onetimeauth_BYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_onetimeauth_KEYBYTES,
    "k must be 'crypto_onetimeauth_KEYBYTES' bytes"
  )

  return binding.crypto_onetimeauth_verify(h, input, k)
}

// crypto_pwhash

exports.crypto_pwhash = function (out, passwd, salt, opslimit, memlimit, alg) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(ArrayBuffer.isView(salt), 'salt must be a typed array')
  assert(
    out.byteLength >= binding.crypto_pwhash_BYTES_MIN,
    "out must be at least 'crypto_pwhash_BYTES_MIN' bytes"
  )
  assert(
    out.byteLength <= binding.crypto_pwhash_BYTES_MAX,
    "out must be at most 'crypto_pwhash_BYTES_MAX' bytes"
  )
  assert(
    salt.byteLength === binding.crypto_pwhash_SALTBYTES,
    "salt must be 'crypto_pwhash_SALTBYTES' bytes"
  )
  assert(
    opslimit >= binding.crypto_pwhash_OPSLIMIT_MIN,
    "opslimit must be at least 'crypto_pwhash_OPSLIMIT_MIN'"
  )
  assert(
    opslimit <= binding.crypto_pwhash_OPSLIMIT_MAX,
    "opslimit must be at most 'crypto_pwhash_OPSLIMIT_MAX'"
  )
  assert(
    memlimit >= binding.crypto_pwhash_MEMLIMIT_MIN,
    "memlimit must be at least 'crypto_pwhash_MEMLIMIT_MIN'"
  )
  assert(
    memlimit <= binding.crypto_pwhash_MEMLIMIT_MAX,
    "memlimit must be at most 'crypto_pwhash_MEMLIMIT_MAX'"
  )
  assert(alg >= 1 && alg <= 2, 'alg must be either Argon2i 1.3 or Argon2id 1.3')

  const res = binding.crypto_pwhash(out, passwd, salt, opslimit, memlimit, alg)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_pwhash_async = function (
  out,
  passwd,
  salt,
  opslimit,
  memlimit,
  alg,
  callback = undefined
) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(ArrayBuffer.isView(salt), 'salt must be a typed array')
  assert(
    out.byteLength >= binding.crypto_pwhash_BYTES_MIN,
    "out must be at least 'crypto_pwhash_BYTES_MIN' bytes"
  )
  assert(
    out.byteLength <= binding.crypto_pwhash_BYTES_MAX,
    "out must be at most 'crypto_pwhash_BYTES_MAX' bytes"
  )
  assert(
    salt.byteLength === binding.crypto_pwhash_SALTBYTES,
    "salt must be 'crypto_pwhash_SALTBYTES' bytes"
  )
  assert(
    opslimit >= binding.crypto_pwhash_OPSLIMIT_MIN,
    "opslimit must be at least 'crypto_pwhash_OPSLIMIT_MIN'"
  )
  assert(
    opslimit <= binding.crypto_pwhash_OPSLIMIT_MAX,
    "opslimit must be at most 'crypto_pwhash_OPSLIMIT_MAX'"
  )
  assert(
    memlimit >= binding.crypto_pwhash_MEMLIMIT_MIN,
    "memlimit must be at least 'crypto_pwhash_MEMLIMIT_MIN'"
  )
  assert(
    memlimit <= binding.crypto_pwhash_MEMLIMIT_MAX,
    "memlimit must be at most 'crypto_pwhash_MEMLIMIT_MAX'"
  )
  assert(alg >= 1 && alg <= 2, 'alg must be either Argon2i 1.3 or Argon2id 1.3')

  const [done, promise] = checkStatus(callback)

  binding.crypto_pwhash_async(
    out.buffer,
    out.byteOffset,
    out.byteLength,

    passwd.buffer,
    passwd.byteOffset,
    passwd.byteLength,

    salt.buffer,
    salt.byteOffset,
    salt.byteLength,

    opslimit,
    memlimit,
    alg,

    done
  )

  return promise
}

exports.crypto_pwhash_str = function (out, passwd, opslimit, memlimit) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(
    out.byteLength === binding.crypto_pwhash_STRBYTES,
    "out must be 'crypto_pwhash_STRBYTES' bytes"
  )
  assert(typeof opslimit === 'number', 'opslimit must be a number')
  assert(
    opslimit >= binding.crypto_pwhash_OPSLIMIT_MIN,
    "opslimit must be at least 'crypto_pwhash_OPSLIMIT_MIN'"
  )
  assert(
    opslimit <= binding.crypto_pwhash_OPSLIMIT_MAX,
    "opslimit must be at most 'crypto_pwhash_OPSLIMIT_MAX'"
  )
  assert(typeof memlimit === 'number', 'memlimit must be a number')
  assert(
    memlimit >= binding.crypto_pwhash_MEMLIMIT_MIN,
    "memlimit must be at least 'crypto_pwhash_MEMLIMIT_MIN'"
  )
  assert(
    memlimit <= binding.crypto_pwhash_MEMLIMIT_MAX,
    "memlimit must be at most 'crypto_pwhash_MEMLIMIT_MAX'"
  )

  const res = binding.crypto_pwhash_str(out, passwd, opslimit, memlimit)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_pwhash_str_async = function (out, passwd, opslimit, memlimit, callback = undefined) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(
    out.byteLength === binding.crypto_pwhash_STRBYTES,
    "out must be 'crypto_pwhash_STRBYTES' bytes"
  )
  assert(passwd.byteLength > 0, 'passwd must not be empty')
  assert(typeof opslimit === 'number', 'opslimit must be a number')
  assert(
    opslimit >= binding.crypto_pwhash_OPSLIMIT_MIN,
    "opslimit must be at least 'crypto_pwhash_OPSLIMIT_MIN'"
  )
  assert(
    opslimit <= binding.crypto_pwhash_OPSLIMIT_MAX,
    "opslimit must be at most 'crypto_pwhash_OPSLIMIT_MAX'"
  )
  assert(typeof memlimit === 'number', 'memlimit must be a number')
  assert(
    memlimit >= binding.crypto_pwhash_MEMLIMIT_MIN,
    "memlimit must be at least 'crypto_pwhash_MEMLIMIT_MIN'"
  )
  assert(
    memlimit <= binding.crypto_pwhash_MEMLIMIT_MAX,
    "memlimit must be at most 'crypto_pwhash_MEMLIMIT_MAX'"
  )

  const [done, promise] = checkStatus(callback)

  binding.crypto_pwhash_str_async(
    out.buffer,
    out.byteOffset,
    out.byteLength,

    passwd.buffer,
    passwd.byteOffset,
    passwd.byteLength,

    opslimit,
    memlimit,

    done
  )

  return promise
}

exports.crypto_pwhash_str_verify = function (str, passwd) {
  assert(ArrayBuffer.isView(str), 'str must be a typed array')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(
    str.byteLength === binding.crypto_pwhash_STRBYTES,
    "str must be 'crypto_pwhash_STRBYTES' bytes"
  )

  return binding.crypto_pwhash_str_verify(str, passwd)
}

exports.crypto_pwhash_str_verify_async = function (str, passwd, callback = undefined) {
  assert(ArrayBuffer.isView(str), 'str must be a typed array')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(
    str.byteLength === binding.crypto_pwhash_STRBYTES,
    "str must be 'crypto_pwhash_STRBYTES' bytes"
  )
  assert(passwd.byteLength > 0, 'passwd must not be empty')

  const [done, promise] = checkStatus(callback, true)

  binding.crypto_pwhash_str_verify_async(
    str.buffer,
    str.byteOffset,
    str.byteLength,

    passwd.buffer,
    passwd.byteOffset,
    passwd.byteLength,

    done
  )

  return promise
}

exports.crypto_pwhash_str_needs_rehash = function (str, opslimit, memlimit) {
  assert(ArrayBuffer.isView(str), 'str must be a typed array')
  assert(
    str.byteLength === binding.crypto_pwhash_STRBYTES,
    "str must be 'crypto_pwhash_STRBYTES' bytes"
  )
  assert(
    opslimit >= binding.crypto_pwhash_OPSLIMIT_MIN,
    "opslimit must be at least 'crypto_pwhash_OPSLIMIT_MIN'"
  )
  assert(
    opslimit <= binding.crypto_pwhash_OPSLIMIT_MAX,
    "opslimit must be at most 'crypto_pwhash_OPSLIMIT_MAX'"
  )
  assert(
    memlimit >= binding.crypto_pwhash_MEMLIMIT_MIN,
    "memlimit must be at least 'crypto_pwhash_MEMLIMIT_MIN'"
  )
  assert(
    memlimit <= binding.crypto_pwhash_MEMLIMIT_MAX,
    "memlimit must be at most 'crypto_pwhash_MEMLIMIT_MAX'"
  )

  return binding.crypto_pwhash_str_needs_rehash(str, opslimit, memlimit)
}

exports.crypto_pwhash_scryptsalsa208sha256 = function (out, passwd, salt, opslimit, memlimit) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(ArrayBuffer.isView(salt), 'salt must be a typed array')
  assert(
    out.byteLength >= binding.crypto_pwhash_scryptsalsa208sha256_BYTES_MIN,
    "out must be at least 'crypto_pwhash_scryptsalsa208sha256_BYTES_MIN' bytes"
  )
  assert(
    out.byteLength <= binding.crypto_pwhash_scryptsalsa208sha256_BYTES_MAX,
    "out must be at most 'crypto_pwhash_scryptsalsa208sha256_BYTES_MAX' bytes"
  )
  assert(
    salt.byteLength === binding.crypto_pwhash_scryptsalsa208sha256_SALTBYTES,
    "salt must be 'crypto_pwhash_scryptsalsa208sha256_SALTBYTES' bytes"
  )
  assert(
    opslimit >= binding.crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MIN,
    "opslimit must be at least 'crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MIN'"
  )
  assert(
    opslimit <= binding.crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MAX,
    "opslimit must be at most 'crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MAX'"
  )
  assert(
    memlimit >= binding.crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MIN,
    "memlimit must be at least 'crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MIN'"
  )
  assert(
    memlimit <= binding.crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MAX,
    "memlimit must be at most 'crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MAX'"
  )

  const res = binding.crypto_pwhash_scryptsalsa208sha256(out, passwd, salt, opslimit, memlimit)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_pwhash_scryptsalsa208sha256_async = function (
  out,
  passwd,
  salt,
  opslimit,
  memlimit,
  callback = undefined
) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(ArrayBuffer.isView(salt), 'salt must be a typed array')
  assert(
    out.byteLength >= binding.crypto_pwhash_scryptsalsa208sha256_BYTES_MIN,
    "out must be at least 'crypto_pwhash_scryptsalsa208sha256_BYTES_MIN' bytes"
  )
  assert(
    out.byteLength <= binding.crypto_pwhash_scryptsalsa208sha256_BYTES_MAX,
    "out must be at most 'crypto_pwhash_scryptsalsa208sha256_BYTES_MAX' bytes"
  )
  assert(passwd.byteLength > 0, 'passwd must not be empty')
  assert(
    salt.byteLength === binding.crypto_pwhash_scryptsalsa208sha256_SALTBYTES,
    "salt must be 'crypto_pwhash_scryptsalsa208sha256_SALTBYTES' bytes"
  )
  assert(
    opslimit >= binding.crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MIN,
    "opslimit must be at least 'crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MIN'"
  )
  assert(
    opslimit <= binding.crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MAX,
    "opslimit must be at most 'crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MAX'"
  )
  assert(
    memlimit >= binding.crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MIN,
    "memlimit must be at least 'crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MIN'"
  )
  assert(
    memlimit <= binding.crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MAX,
    "memlimit must be at most 'crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MAX'"
  )

  const [done, promise] = checkStatus(callback)

  binding.crypto_pwhash_scryptsalsa208sha256_async(
    out.buffer,
    out.byteOffset,
    out.byteLength,

    passwd.buffer,
    passwd.byteOffset,
    passwd.byteLength,

    salt.buffer,
    salt.byteOffset,
    salt.byteLength,

    opslimit,
    memlimit,

    done
  )

  return promise
}

exports.crypto_pwhash_scryptsalsa208sha256_str_async = function (
  out,
  passwd,
  opslimit,
  memlimit,
  callback = undefined
) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(
    out.byteLength === binding.crypto_pwhash_scryptsalsa208sha256_STRBYTES,
    "out must be 'crypto_pwhash_scryptsalsa208sha256_STRBYTES' bytes"
  )
  assert(passwd.byteLength > 0, 'passwd must not be empty')
  assert(
    opslimit >= binding.crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MIN,
    "opslimit must be at least 'crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MIN'"
  )
  assert(
    opslimit <= binding.crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MAX,
    "opslimit must be at most 'crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MAX'"
  )
  assert(
    memlimit >= binding.crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MIN,
    "memlimit must be at least 'crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MIN'"
  )
  assert(
    memlimit <= binding.crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MAX,
    "memlimit must be at most 'crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MAX'"
  )

  const [done, promise] = checkStatus(callback)

  binding.crypto_pwhash_scryptsalsa208sha256_str_async(
    out.buffer,
    out.byteOffset,
    out.byteLength,

    passwd.buffer,
    passwd.byteOffset,
    passwd.byteLength,

    opslimit,
    memlimit,

    done
  )

  return promise
}

exports.crypto_pwhash_scryptsalsa208sha256_str = function (out, passwd, opslimit, memlimit) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(
    out.byteLength === binding.crypto_pwhash_scryptsalsa208sha256_STRBYTES,
    "out must be 'crypto_pwhash_scryptsalsa208sha256_STRBYTES' bytes"
  )
  assert(passwd.byteLength > 0, 'passwd must not be empty')
  assert(
    opslimit >= binding.crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MIN,
    "opslimit must be at least 'crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MIN'"
  )
  assert(
    opslimit <= binding.crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MAX,
    "opslimit must be at most 'crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MAX'"
  )
  assert(
    memlimit >= binding.crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MIN,
    "memlimit must be at least 'crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MIN'"
  )
  assert(
    memlimit <= binding.crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MAX,
    "memlimit must be at most 'crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MAX'"
  )

  const res = binding.crypto_pwhash_scryptsalsa208sha256_str(out, passwd, opslimit, memlimit)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_pwhash_scryptsalsa208sha256_str_verify_async = function (
  str,
  passwd,
  callback = undefined
) {
  assert(ArrayBuffer.isView(str), 'str must be a typed array')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(
    str.byteLength === binding.crypto_pwhash_scryptsalsa208sha256_STRBYTES,
    "str must be 'crypto_pwhash_scryptsalsa208sha256_STRBYTES' bytes"
  )
  assert(passwd.byteLength > 0, 'passwd must not be empty')

  const [done, promise] = checkStatus(callback, true)

  binding.crypto_pwhash_scryptsalsa208sha256_str_verify_async(
    str.buffer,
    str.byteOffset,
    str.byteLength,

    passwd.buffer,
    passwd.byteOffset,
    passwd.byteLength,

    done
  )

  return promise
}

exports.crypto_pwhash_scryptsalsa208sha256_str_verify = function (str, passwd) {
  assert(ArrayBuffer.isView(str), 'str must be a typed array')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(
    str.byteLength === binding.crypto_pwhash_scryptsalsa208sha256_STRBYTES,
    "str must be 'crypto_pwhash_scryptsalsa208sha256_STRBYTES' bytes"
  )
  assert(passwd.byteLength > 0, 'passwd must not be empty')

  return binding.crypto_pwhash_scryptsalsa208sha256_str_verify(str, passwd)
}

exports.crypto_pwhash_scryptsalsa208sha256_str_needs_rehash = function (str, opslimit, memlimit) {
  assert(ArrayBuffer.isView(str), 'str must be a typed array')
  assert(
    str.byteLength === binding.crypto_pwhash_scryptsalsa208sha256_STRBYTES,
    "str must be 'crypto_pwhash_scryptsalsa208sha256_STRBYTES' bytes"
  )
  assert(
    opslimit >= binding.crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MIN,
    "opslimit must be at least 'crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MIN'"
  )
  assert(
    opslimit <= binding.crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MAX,
    "opslimit must be at most 'crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MAX'"
  )
  assert(
    memlimit >= binding.crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MIN,
    "memlimit must be at least 'crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MIN'"
  )
  assert(
    memlimit <= binding.crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MAX,
    "memlimit must be at most 'crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MAX'"
  )

  return binding.crypto_pwhash_scryptsalsa208sha256_str_needs_rehash(str, opslimit, memlimit)
}

// crypto_kx

exports.crypto_kx_keypair = function (pk, sk) {
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(
    pk.byteLength === binding.crypto_kx_PUBLICKEYBYTES,
    "pk must be 'crypto_kx_PUBLICKEYBYTES' bytes"
  )
  assert(
    sk.byteLength === binding.crypto_kx_SECRETKEYBYTES,
    "sk must be 'crypto_kx_SECRETKEYBYTES' bytes"
  )

  const res = binding.crypto_kx_keypair(pk, sk)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_kx_seed_keypair = function (pk, sk, seed) {
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(ArrayBuffer.isView(seed), 'seed must be a typed array')
  assert(
    pk.byteLength === binding.crypto_kx_PUBLICKEYBYTES,
    "pk must be 'crypto_kx_PUBLICKEYBYTES' bytes"
  )
  assert(
    sk.byteLength === binding.crypto_kx_SECRETKEYBYTES,
    "sk must be 'crypto_kx_SECRETKEYBYTES' bytes"
  )
  assert(
    seed.byteLength === binding.crypto_kx_SEEDBYTES,
    "seed must be 'crypto_kx_SEEDBYTES' bytes"
  )

  const res = binding.crypto_kx_seed_keypair(pk, sk, seed)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_kx_client_session_keys = function (rx, tx, clientPk, clientSk, serverPk) {
  if (!rx) rx = undefined
  if (!tx) tx = undefined

  assert(rx || tx, 'at least one session key must be specified')

  if (rx) {
    assert(ArrayBuffer.isView(rx), 'rx must be a typed array')
    assert(
      rx.byteLength === binding.crypto_kx_SESSIONKEYBYTES,
      "rx must be 'crypto_kx_SESSIONKEYBYTES' bytes"
    )
  }

  if (tx) {
    assert(ArrayBuffer.isView(tx), 'tx must be a typed array')
    assert(
      tx.byteLength === binding.crypto_kx_SESSIONKEYBYTES,
      "tx must be 'crypto_kx_SESSIONKEYBYTES' bytes"
    )
  }

  assert(ArrayBuffer.isView(clientPk), 'clientPk must be a typed array')
  assert(ArrayBuffer.isView(clientSk), 'clientSk must be a typed array')
  assert(ArrayBuffer.isView(serverPk), 'serverPk must be a typed array')
  assert(
    clientPk.byteLength === binding.crypto_kx_PUBLICKEYBYTES,
    "clientPk must be 'crypto_kx_PUBLICKEYBYTES' bytes"
  )
  assert(
    clientSk.byteLength === binding.crypto_kx_SECRETKEYBYTES,
    "clientSk must be 'crypto_kx_SECRETKEYBYTES' bytes"
  )
  assert(
    serverPk.byteLength === binding.crypto_kx_PUBLICKEYBYTES,
    "serverPk must be 'crypto_kx_PUBLICKEYBYTES' bytes"
  )

  const res = binding.crypto_kx_client_session_keys(rx, tx, clientPk, clientSk, serverPk)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_kx_server_session_keys = function (rx, tx, serverPk, serverSk, clientPk) {
  if (!rx) rx = undefined
  if (!tx) tx = undefined

  assert(rx || tx, 'at least one session key must be specified')

  if (rx) {
    assert(ArrayBuffer.isView(rx), 'rx must be a typed array')
    assert(
      rx.byteLength === binding.crypto_kx_SESSIONKEYBYTES,
      "rx must be 'crypto_kx_SESSIONKEYBYTES' bytes"
    )
  }

  if (tx) {
    assert(ArrayBuffer.isView(tx), 'tx must be a typed array')
    assert(
      tx.byteLength === binding.crypto_kx_SESSIONKEYBYTES,
      "tx must be 'crypto_kx_SESSIONKEYBYTES' bytes"
    )
  }

  assert(ArrayBuffer.isView(serverPk), 'serverPk must be a typed array')
  assert(ArrayBuffer.isView(serverSk), 'serverSk must be a typed array')
  assert(ArrayBuffer.isView(clientPk), 'clientPk must be a typed array')
  assert(
    serverPk.byteLength === binding.crypto_kx_PUBLICKEYBYTES,
    "serverPk must be 'crypto_kx_PUBLICKEYBYTES' bytes"
  )
  assert(
    serverSk.byteLength === binding.crypto_kx_SECRETKEYBYTES,
    "serverSk must be 'crypto_kx_SECRETKEYBYTES' bytes"
  )
  assert(
    clientPk.byteLength === binding.crypto_kx_PUBLICKEYBYTES,
    "clientPk must be 'crypto_kx_PUBLICKEYBYTES' bytes"
  )

  const res = binding.crypto_kx_server_session_keys(rx, tx, serverPk, serverSk, clientPk)

  if (res !== 0) throw new Error('status: ' + res)
}

// crypto_scalarmult

exports.crypto_scalarmult_base = function (q, n) {
  assert(ArrayBuffer.isView(q), 'q must be a typed array')
  assert(
    q.byteLength === binding.crypto_scalarmult_BYTES,
    "q must be 'crypto_scalarmult_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.crypto_scalarmult_SCALARBYTES,
    "n must be 'crypto_scalarmult_SCALARBYTES' bytes"
  )

  const res = binding.crypto_scalarmult_base(q, n)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_scalarmult = function (q, n, p) {
  assert(ArrayBuffer.isView(q), 'q must be a typed array')
  assert(
    q.byteLength === binding.crypto_scalarmult_BYTES,
    "q must be 'crypto_scalarmult_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.crypto_scalarmult_SCALARBYTES,
    "n must be 'crypto_scalarmult_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(p), 'p must be a typed array')
  assert(
    p.byteLength === binding.crypto_scalarmult_BYTES,
    "p must be 'crypto_scalarmult_BYTES' bytes"
  )

  const res = binding.crypto_scalarmult(q, n, p)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_scalarmult_ed25519_base = function (q, n) {
  assert(ArrayBuffer.isView(q), 'q must be a typed array')
  assert(
    q.byteLength === binding.crypto_scalarmult_ed25519_BYTES,
    "q must be 'crypto_scalarmult_ed25519_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.crypto_scalarmult_ed25519_SCALARBYTES,
    "n must be 'crypto_scalarmult_ed25519_SCALARBYTES' bytes"
  )

  const res = binding.crypto_scalarmult_ed25519_base(q, n)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_scalarmult_ed25519 = function (q, n, p) {
  assert(ArrayBuffer.isView(q), 'q must be a typed array')
  assert(
    q.byteLength === binding.crypto_scalarmult_ed25519_BYTES,
    "q must be 'crypto_scalarmult_ed25519_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.crypto_scalarmult_ed25519_SCALARBYTES,
    "n must be 'crypto_scalarmult_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(p), 'p must be a typed array')
  assert(
    p.byteLength === binding.crypto_scalarmult_ed25519_BYTES,
    "p must be 'crypto_scalarmult_ed25519_BYTES' bytes"
  )

  const res = binding.crypto_scalarmult_ed25519(q, n, p)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_core_ed25519_is_valid_point = function (p) {
  assert(ArrayBuffer.isView(p), 'p must be a typed array')
  assert(
    p.byteLength === binding.crypto_core_ed25519_BYTES,
    "p must be 'crypto_core_ed25519_BYTES' bytes"
  )

  return binding.crypto_core_ed25519_is_valid_point(p)
}

exports.crypto_core_ed25519_from_uniform = function (p, r) {
  assert(ArrayBuffer.isView(p), 'p must be a typed array')
  assert(
    p.byteLength === binding.crypto_core_ed25519_BYTES,
    "p must be 'crypto_core_ed25519_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(r), 'r must be a typed array')
  assert(
    r.byteLength === binding.crypto_core_ed25519_UNIFORMBYTES,
    "r must be 'crypto_core_ed25519_UNIFORMBYTES' bytes"
  )

  const res = binding.crypto_core_ed25519_from_uniform(p, r)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_scalarmult_ed25519_base_noclamp = function (q, n) {
  assert(ArrayBuffer.isView(q), 'q must be a typed array')
  assert(
    q.byteLength === binding.crypto_scalarmult_ed25519_BYTES,
    "q must be 'crypto_scalarmult_ed25519_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.crypto_scalarmult_ed25519_SCALARBYTES,
    "n must be 'crypto_scalarmult_ed25519_SCALARBYTES' bytes"
  )

  const res = binding.crypto_scalarmult_ed25519_base_noclamp(q, n)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_scalarmult_ed25519_noclamp = function (q, n, p) {
  assert(ArrayBuffer.isView(q), 'q must be a typed array')
  assert(
    q.byteLength === binding.crypto_scalarmult_ed25519_BYTES,
    "q must be 'crypto_scalarmult_ed25519_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.crypto_scalarmult_ed25519_SCALARBYTES,
    "n must be 'crypto_scalarmult_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(p), 'p must be a typed array')
  assert(
    p.byteLength === binding.crypto_scalarmult_ed25519_BYTES,
    "p must be 'crypto_scalarmult_ed25519_BYTES' bytes"
  )

  const res = binding.crypto_scalarmult_ed25519_noclamp(q, n, p)

  if (res !== 0) throw new Error('status: ' + res)
}

// crypto_core

exports.crypto_core_ed25519_add = function (r, p, q) {
  assert(ArrayBuffer.isView(r), 'r must be a typed array')
  assert(
    r.byteLength === binding.crypto_core_ed25519_BYTES,
    "r must be 'crypto_core_ed25519_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(p), 'p must be a typed array')
  assert(
    p.byteLength === binding.crypto_core_ed25519_BYTES,
    "p must be 'crypto_core_ed25519_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(q), 'q must be a typed array')
  assert(
    q.byteLength === binding.crypto_core_ed25519_BYTES,
    "q must be 'crypto_core_ed25519_BYTES' bytes"
  )

  const res = binding.crypto_core_ed25519_add(r, p, q)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_core_ed25519_sub = function (r, p, q) {
  assert(ArrayBuffer.isView(r), 'r must be a typed array')
  assert(
    r.byteLength === binding.crypto_core_ed25519_BYTES,
    "r must be 'crypto_core_ed25519_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(p), 'p must be a typed array')
  assert(
    p.byteLength === binding.crypto_core_ed25519_BYTES,
    "p must be 'crypto_core_ed25519_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(q), 'q must be a typed array')
  assert(
    q.byteLength === binding.crypto_core_ed25519_BYTES,
    "q must be 'crypto_core_ed25519_BYTES' bytes"
  )

  const res = binding.crypto_core_ed25519_sub(r, p, q)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_core_ed25519_scalar_random = function (r) {
  assert(ArrayBuffer.isView(r), 'r must be a typed array')
  assert(
    r.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "r must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )

  binding.crypto_core_ed25519_scalar_random(r)
}

exports.crypto_core_ed25519_scalar_reduce = function (r, s) {
  assert(ArrayBuffer.isView(r), 'r must be a typed array')
  assert(
    r.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "r must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(s), 's must be a typed array')
  assert(
    s.byteLength === binding.crypto_core_ed25519_NONREDUCEDSCALARBYTES,
    "s must be 'crypto_core_ed25519_NONREDUCEDSCALARBYTES' bytes"
  )

  binding.crypto_core_ed25519_scalar_reduce(r, s)
}

exports.crypto_core_ed25519_scalar_invert = function (recip, s) {
  assert(ArrayBuffer.isView(recip), 'recip must be a typed array')
  assert(
    recip.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "recip must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(s), 's must be a typed array')
  assert(
    s.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "s must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )

  binding.crypto_core_ed25519_scalar_invert(recip, s)
}

exports.crypto_core_ed25519_scalar_negate = function (neg, s) {
  assert(ArrayBuffer.isView(neg), 'neg must be a typed array')
  assert(
    neg.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "neg must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(s), 's must be a typed array')
  assert(
    s.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "s must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )

  binding.crypto_core_ed25519_scalar_negate(neg, s)
}

exports.crypto_core_ed25519_scalar_complement = function (comp, s) {
  assert(ArrayBuffer.isView(comp), 'comp must be a typed array')
  assert(
    comp.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "comp must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(s), 's must be a typed array')
  assert(
    s.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "s must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )

  binding.crypto_core_ed25519_scalar_complement(comp, s)
}

exports.crypto_core_ed25519_scalar_add = function (z, x, y) {
  assert(ArrayBuffer.isView(z), 'z must be a typed array')
  assert(
    z.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "z must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(x), 'x must be a typed array')
  assert(
    x.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "x must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(y), 'y must be a typed array')
  assert(
    y.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "y must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )

  binding.crypto_core_ed25519_scalar_add(z, x, y)
}

exports.crypto_core_ed25519_scalar_sub = function (z, x, y) {
  assert(ArrayBuffer.isView(z), 'z must be a typed array')
  assert(
    z.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "z must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(x), 'x must be a typed array')
  assert(
    x.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "x must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(y), 'y must be a typed array')
  assert(
    y.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "y must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )

  binding.crypto_core_ed25519_scalar_sub(z, x, y)
}

// crypto_shorthash

exports.crypto_shorthash = function (out, input, k) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(
    out.byteLength === binding.crypto_shorthash_BYTES,
    "out must be 'crypto_shorthash_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_shorthash_KEYBYTES,
    "k must be 'crypto_shorthash_KEYBYTES' bytes"
  )

  const res = binding.crypto_shorthash(out, input, k)

  if (res !== 0) throw new Error('status: ' + res)
}

// crypto_kdf

exports.crypto_kdf_keygen = function (key) {
  assert(ArrayBuffer.isView(key), 'key must be a typed array')
  assert(key.byteLength === binding.crypto_kdf_KEYBYTES, "key must be 'crypto_kdf_KEYBYTES' bytes")

  binding.crypto_kdf_keygen(key)
}

exports.crypto_kdf_derive_from_key = function (subkey, subkeyId, ctx, key) {
  assert(ArrayBuffer.isView(subkey), 'subkey must be a typed array')
  assert(
    subkey.byteLength >= binding.crypto_kdf_BYTES_MIN,
    "subkey must be at least 'crypto_kdf_BYTES_MIN' bytes"
  )
  assert(
    subkey.byteLength <= binding.crypto_kdf_BYTES_MAX,
    "subkey must be at most 'crypto_kdf_BYTES_MAX' bytes"
  )
  assert(ArrayBuffer.isView(ctx), 'ctx must be a typed array')
  assert(
    ctx.byteLength === binding.crypto_kdf_CONTEXTBYTES,
    "ctx must be 'crypto_kdf_CONTEXTBYTES' bytes"
  )
  assert(ArrayBuffer.isView(key), 'key must be a typed array')
  assert(key.byteLength === binding.crypto_kdf_KEYBYTES, "key must be 'crypto_kdf_KEYBYTES' bytes")

  const res = binding.crypto_kdf_derive_from_key(subkey, subkeyId, ctx, key)

  if (res !== 0) throw new Error('status: ' + res)
}

// crypto_hash

exports.crypto_hash = function (out, input) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(out.byteLength === binding.crypto_hash_BYTES, "out must be 'crypto_hash_BYTES' bytes")

  const res = binding.crypto_hash(out, input)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_hash_sha256 = function (out, input) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(
    out.byteLength === binding.crypto_hash_sha256_BYTES,
    "out must be 'crypto_hash_sha256_BYTES' bytes"
  )

  const res = binding.crypto_hash_sha256(out, input)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_hash_sha256_init = function (state) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_hash_sha256_STATEBYTES,
    "state must be 'crypto_hash_sha256_STATEBYTES' bytes"
  )

  const res = binding.crypto_hash_sha256_init(state)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_hash_sha256_update = function (state, input) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(
    state.byteLength === binding.crypto_hash_sha256_STATEBYTES,
    "state must be 'crypto_hash_sha256_STATEBYTES' bytes"
  )

  const res = binding.crypto_hash_sha256_update(state, input)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_hash_sha256_final = function (state, out) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_hash_sha256_STATEBYTES,
    "state must be 'crypto_hash_sha256_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(
    out.byteLength === binding.crypto_hash_sha256_BYTES,
    "out must be 'crypto_hash_sha256_BYTES' bytes"
  )

  const res = binding.crypto_hash_sha256_final(state, out)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_hash_sha512 = function (out, input) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(
    out.byteLength === binding.crypto_hash_sha512_BYTES,
    "out must be 'crypto_hash_sha512_BYTES' bytes"
  )

  const res = binding.crypto_hash_sha512(out, input)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_hash_sha512_init = function (state) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_hash_sha512_STATEBYTES,
    "state must be 'crypto_hash_sha512_STATEBYTES' bytes"
  )

  const res = binding.crypto_hash_sha512_init(state)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_hash_sha512_update = function (state, input) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(
    state.byteLength === binding.crypto_hash_sha512_STATEBYTES,
    "state must be 'crypto_hash_sha512_STATEBYTES' bytes"
  )

  const res = binding.crypto_hash_sha512_update(state, input)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_hash_sha512_final = function (state, out) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_hash_sha512_STATEBYTES,
    "state must be 'crypto_hash_sha512_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(
    out.byteLength === binding.crypto_hash_sha512_BYTES,
    "out must be 'crypto_hash_sha512_BYTES' bytes"
  )

  const res = binding.crypto_hash_sha512_final(state, out)

  if (res !== 0) throw new Error('status: ' + res)
}

// crypto_aead

exports.crypto_aead_xchacha20poly1305_ietf_keygen = function (k) {
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_aead_xchacha20poly1305_ietf_KEYBYTES,
    "k must be 'crypto_aead_xchacha20poly1305_ietf_KEYBYTES' bytes"
  )

  binding.crypto_aead_xchacha20poly1305_ietf_keygen(k)
}

exports.crypto_aead_xchacha20poly1305_ietf_encrypt = function (c, m, ad, nsec, npub, k) {
  if (!ad) ad = undefined

  assert(nsec === null, 'nsec must always be set to null')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(
    c.byteLength === m.byteLength + binding.crypto_aead_xchacha20poly1305_ietf_ABYTES,
    "c must be 'm.byteLength + crypto_aead_xchacha20poly1305_ietf_ABYTES' bytes"
  )
  assert(c.byteLength <= 0xffffffff, 'c.byteLength must be a 32bit integer')
  assert(ArrayBuffer.isView(npub), 'npub must be a typed array')
  assert(
    npub.byteLength === binding.crypto_aead_xchacha20poly1305_ietf_NPUBBYTES,
    "npub must be 'crypto_aead_xchacha20poly1305_ietf_NPUBBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_aead_xchacha20poly1305_ietf_KEYBYTES,
    "k must be 'crypto_aead_xchacha20poly1305_ietf_KEYBYTES' bytes"
  )

  const res = binding.crypto_aead_xchacha20poly1305_ietf_encrypt(c, m, ad, npub, k)

  if (res < 0) throw new Error('could not encrypt data')

  return res
}

exports.crypto_aead_xchacha20poly1305_ietf_decrypt = function (m, nsec, c, ad, npub, k) {
  if (!ad) ad = undefined

  assert(nsec === null, 'nsec must always be set to null')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(
    m.byteLength === c.byteLength - binding.crypto_aead_xchacha20poly1305_ietf_ABYTES,
    "m must be 'c.byteLength - crypto_aead_xchacha20poly1305_ietf_ABYTES' bytes"
  )
  assert(m.byteLength <= 0xffffffff, 'm.byteLength must be a 32bit integer')
  assert(ArrayBuffer.isView(npub), 'npub must be a typed array')
  assert(
    npub.byteLength === binding.crypto_aead_xchacha20poly1305_ietf_NPUBBYTES,
    "npub must be 'crypto_aead_xchacha20poly1305_ietf_NPUBBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_aead_xchacha20poly1305_ietf_KEYBYTES,
    "k must be 'crypto_aead_xchacha20poly1305_ietf_KEYBYTES' bytes"
  )

  const res = binding.crypto_aead_xchacha20poly1305_ietf_decrypt(m, c, ad, npub, k)

  if (res < 0) throw new Error('could not verify data')

  return res
}

exports.crypto_aead_xchacha20poly1305_ietf_encrypt_detached = function (
  c,
  mac,
  m,
  ad,
  nsec,
  npub,
  k
) {
  if (!ad) ad = undefined

  assert(nsec === null, 'nsec must always be set to null')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(ArrayBuffer.isView(mac), 'mac must be a typed array')
  assert(
    mac.byteLength === binding.crypto_aead_xchacha20poly1305_ietf_ABYTES,
    "mac must be 'crypto_aead_xchacha20poly1305_ietf_ABYTES' bytes"
  )
  assert(ArrayBuffer.isView(npub), 'npub must be a typed array')
  assert(
    npub.byteLength === binding.crypto_aead_xchacha20poly1305_ietf_NPUBBYTES,
    "npub must be 'crypto_aead_xchacha20poly1305_ietf_NPUBBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_aead_xchacha20poly1305_ietf_KEYBYTES,
    "k must be 'crypto_aead_xchacha20poly1305_ietf_KEYBYTES' bytes"
  )

  const res = binding.crypto_aead_xchacha20poly1305_ietf_encrypt_detached(c, mac, m, ad, npub, k)

  if (res < 0) throw new Error('could not encrypt data')

  return res
}

exports.crypto_aead_xchacha20poly1305_ietf_decrypt_detached = function (
  m,
  nsec,
  c,
  mac,
  ad,
  npub,
  k
) {
  if (!ad) ad = undefined

  assert(nsec === null, 'nsec must always be set to null')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(m.byteLength === c.byteLength, "m must be 'c.byteLength' bytes")
  assert(ArrayBuffer.isView(mac), 'mac must be a typed array')
  assert(
    mac.byteLength === binding.crypto_aead_xchacha20poly1305_ietf_ABYTES,
    "mac must be 'crypto_aead_xchacha20poly1305_ietf_ABYTES' bytes"
  )
  assert(ArrayBuffer.isView(npub), 'npub must be a typed array')
  assert(
    npub.byteLength === binding.crypto_aead_xchacha20poly1305_ietf_NPUBBYTES,
    "npub must be 'crypto_aead_xchacha20poly1305_ietf_NPUBBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_aead_xchacha20poly1305_ietf_KEYBYTES,
    "k must be 'crypto_aead_xchacha20poly1305_ietf_KEYBYTES' bytes"
  )

  const res = binding.crypto_aead_xchacha20poly1305_ietf_decrypt_detached(m, c, mac, ad, npub, k)

  if (res !== 0) throw new Error('could not verify data')
}

exports.crypto_aead_chacha20poly1305_ietf_keygen = function (k) {
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_aead_chacha20poly1305_ietf_KEYBYTES,
    "k must be 'crypto_aead_chacha20poly1305_ietf_KEYBYTES' bytes"
  )

  binding.crypto_aead_chacha20poly1305_ietf_keygen(k)
}

exports.crypto_aead_chacha20poly1305_ietf_encrypt = function (c, m, ad, nsec, npub, k) {
  if (!ad) ad = undefined

  assert(nsec === null, 'nsec must always be set to null')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(
    c.byteLength === m.byteLength + binding.crypto_aead_chacha20poly1305_ietf_ABYTES,
    "c must be 'm.byteLength + crypto_aead_chacha20poly1305_ietf_ABYTES' bytes"
  )
  assert(c.byteLength <= 0xffffffff, 'c.byteLength must be a 32bit integer')
  assert(ArrayBuffer.isView(npub), 'npub must be a typed array')
  assert(
    npub.byteLength === binding.crypto_aead_chacha20poly1305_ietf_NPUBBYTES,
    "npub must be 'crypto_aead_chacha20poly1305_ietf_NPUBBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_aead_chacha20poly1305_ietf_KEYBYTES,
    "k must be 'crypto_aead_chacha20poly1305_ietf_KEYBYTES' bytes"
  )

  const res = binding.crypto_aead_chacha20poly1305_ietf_encrypt(c, m, ad, npub, k)

  if (res < 0) throw new Error('could not encrypt data')

  return res
}

exports.crypto_aead_chacha20poly1305_ietf_decrypt = function (m, nsec, c, ad, npub, k) {
  if (!ad) ad = undefined

  assert(nsec === null, 'nsec must always be set to null')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(
    m.byteLength === c.byteLength - binding.crypto_aead_chacha20poly1305_ietf_ABYTES,
    "m must be 'c.byteLength - crypto_aead_chacha20poly1305_ietf_ABYTES' bytes"
  )
  assert(m.byteLength <= 0xffffffff, 'm.byteLength must be a 32bit integer')
  assert(ArrayBuffer.isView(npub), 'npub must be a typed array')
  assert(
    npub.byteLength === binding.crypto_aead_chacha20poly1305_ietf_NPUBBYTES,
    "npub must be 'crypto_aead_chacha20poly1305_ietf_NPUBBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_aead_chacha20poly1305_ietf_KEYBYTES,
    "k must be 'crypto_aead_chacha20poly1305_ietf_KEYBYTES' bytes"
  )

  const res = binding.crypto_aead_chacha20poly1305_ietf_decrypt(m, c, ad, npub, k)

  if (res < 0) throw new Error('could not verify data')

  return res
}

exports.crypto_aead_chacha20poly1305_ietf_encrypt_detached = function (
  c,
  mac,
  m,
  ad,
  nsec,
  npub,
  k
) {
  if (!ad) ad = undefined

  assert(nsec === null, 'nsec must always be set to null')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(ArrayBuffer.isView(mac), 'mac must be a typed array')
  assert(
    mac.byteLength === binding.crypto_aead_chacha20poly1305_ietf_ABYTES,
    "mac must be 'crypto_aead_chacha20poly1305_ietf_ABYTES' bytes"
  )
  assert(ArrayBuffer.isView(npub), 'npub must be a typed array')
  assert(
    npub.byteLength === binding.crypto_aead_chacha20poly1305_ietf_NPUBBYTES,
    "npub must be 'crypto_aead_chacha20poly1305_ietf_NPUBBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_aead_chacha20poly1305_ietf_KEYBYTES,
    "k must be 'crypto_aead_chacha20poly1305_ietf_KEYBYTES' bytes"
  )

  const res = binding.crypto_aead_chacha20poly1305_ietf_encrypt_detached(c, mac, m, ad, npub, k)

  if (res < 0) throw new Error('could not encrypt data')

  return res
}

exports.crypto_aead_chacha20poly1305_ietf_decrypt_detached = function (
  m,
  nsec,
  c,
  mac,
  ad,
  npub,
  k
) {
  if (!ad) ad = undefined

  assert(nsec === null, 'nsec must always be set to null')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(m.byteLength === c.byteLength, "m must be 'c.byteLength' bytes")
  assert(ArrayBuffer.isView(mac), 'mac must be a typed array')
  assert(
    mac.byteLength === binding.crypto_aead_chacha20poly1305_ietf_ABYTES,
    "mac must be 'crypto_aead_chacha20poly1305_ietf_ABYTES' bytes"
  )
  assert(ArrayBuffer.isView(npub), 'npub must be a typed array')
  assert(
    npub.byteLength === binding.crypto_aead_chacha20poly1305_ietf_NPUBBYTES,
    "npub must be 'crypto_aead_chacha20poly1305_ietf_NPUBBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_aead_chacha20poly1305_ietf_KEYBYTES,
    "k must be 'crypto_aead_chacha20poly1305_ietf_KEYBYTES' bytes"
  )

  const res = binding.crypto_aead_chacha20poly1305_ietf_decrypt_detached(m, c, mac, ad, npub, k)

  if (res !== 0) throw new Error('could not verify data')
}

// crypto_stream

exports.crypto_stream_xor_wrap_init = function (state, n, k) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.sn_crypto_stream_xor_STATEBYTES,
    "state must be 'sn_crypto_stream_xor_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.crypto_stream_NONCEBYTES,
    "n must be 'crypto_stream_NONCEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_stream_KEYBYTES,
    "k must be 'crypto_stream_KEYBYTES' bytes"
  )

  binding.crypto_stream_xor_wrap_init(state, n, k)
}

exports.crypto_stream_xor_wrap_update = function (state, c, m) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.sn_crypto_stream_xor_STATEBYTES,
    "state must be 'sn_crypto_stream_xor_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")

  binding.crypto_stream_xor_wrap_update(state, c, m)
}

exports.crypto_stream_xor_wrap_final = function (state) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.sn_crypto_stream_xor_STATEBYTES,
    "state must be 'sn_crypto_stream_xor_STATEBYTES' bytes"
  )

  binding.crypto_stream_xor_wrap_final(state)
}

exports.crypto_stream_chacha20_xor_wrap_init = function (state, n, k) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_stream_chacha20_xor_STATEBYTES,
    "state must be 'crypto_stream_chacha20_xor_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.crypto_stream_chacha20_NONCEBYTES,
    "n must be 'crypto_stream_chacha20_NONCEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_stream_chacha20_KEYBYTES,
    "k must be 'crypto_stream_chacha20_KEYBYTES' bytes"
  )

  binding.crypto_stream_chacha20_xor_wrap_init(state, n, k)
}

exports.crypto_stream_chacha20_xor_wrap_update = function (state, c, m) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_stream_chacha20_xor_STATEBYTES,
    "state must be 'crypto_stream_chacha20_xor_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")

  binding.crypto_stream_chacha20_xor_wrap_update(state, c, m)
}

exports.crypto_stream_chacha20_xor_wrap_final = function (state) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_stream_chacha20_xor_STATEBYTES,
    "state must be 'crypto_stream_chacha20_xor_STATEBYTES' bytes"
  )

  binding.crypto_stream_chacha20_xor_wrap_final(state)
}

exports.crypto_stream_chacha20_ietf_xor_wrap_init = function (state, n, k) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_stream_chacha20_ietf_xor_STATEBYTES,
    "state must be 'crypto_stream_chacha20_ietf_xor_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.crypto_stream_chacha20_ietf_NONCEBYTES,
    "n must be 'crypto_stream_chacha20_ietf_NONCEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_stream_chacha20_ietf_KEYBYTES,
    "k must be 'crypto_stream_chacha20_ietf_KEYBYTES' bytes"
  )

  binding.crypto_stream_chacha20_ietf_xor_wrap_init(state, n, k)
}

exports.crypto_stream_chacha20_ietf_xor_wrap_update = function (state, c, m) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_stream_chacha20_ietf_xor_STATEBYTES,
    "state must be 'crypto_stream_chacha20_ietf_xor_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")

  binding.crypto_stream_chacha20_ietf_xor_wrap_update(state, c, m)
}

exports.crypto_stream_chacha20_ietf_xor_wrap_final = function (state) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_stream_chacha20_ietf_xor_STATEBYTES,
    "state must be 'crypto_stream_chacha20_ietf_xor_STATEBYTES' bytes"
  )

  binding.crypto_stream_chacha20_ietf_xor_wrap_final(state)
}

exports.crypto_stream_xchacha20_xor_wrap_init = function (state, n, k) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_stream_xchacha20_xor_STATEBYTES,
    "state must be 'crypto_stream_xchacha20_xor_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.crypto_stream_xchacha20_NONCEBYTES,
    "n must be 'crypto_stream_xchacha20_NONCEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_stream_xchacha20_KEYBYTES,
    "k must be 'crypto_stream_xchacha20_KEYBYTES' bytes"
  )

  binding.crypto_stream_xchacha20_xor_wrap_init(state, n, k)
}

exports.crypto_stream_xchacha20_xor_wrap_update = function (state, c, m) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_stream_xchacha20_xor_STATEBYTES,
    "state must be 'crypto_stream_xchacha20_xor_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")

  binding.crypto_stream_xchacha20_xor_wrap_update(state, c, m)
}

exports.crypto_stream_xchacha20_xor_wrap_final = function (state) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_stream_xchacha20_xor_STATEBYTES,
    "state must be 'crypto_stream_xchacha20_xor_STATEBYTES' bytes"
  )

  binding.crypto_stream_xchacha20_xor_wrap_final(state)
}

exports.crypto_stream_salsa20_xor_wrap_init = function (state, n, k) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_stream_salsa20_xor_STATEBYTES,
    "state must be 'crypto_stream_salsa20_xor_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.crypto_stream_salsa20_NONCEBYTES,
    "n must be 'crypto_stream_salsa20_NONCEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_stream_salsa20_KEYBYTES,
    "k must be 'crypto_stream_salsa20_KEYBYTES' bytes"
  )

  binding.crypto_stream_salsa20_xor_wrap_init(state, n, k)
}

exports.crypto_stream_salsa20_xor_wrap_update = function (state, c, m) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_stream_salsa20_xor_STATEBYTES,
    "state must be 'crypto_stream_salsa20_xor_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")

  binding.crypto_stream_salsa20_xor_wrap_update(state, c, m)
}

exports.crypto_stream_salsa20_xor_wrap_final = function (state) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_stream_salsa20_xor_STATEBYTES,
    "state must be 'crypto_stream_salsa20_xor_STATEBYTES' bytes"
  )

  binding.crypto_stream_salsa20_xor_wrap_final(state)
}

// experimental

exports.extension_tweak_ed25519_base = function (n, p, ns) {
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "n must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(p), 'p must be a typed array')
  assert(
    p.byteLength === binding.extension_tweak_ed25519_BYTES,
    "p must be 'extension_tweak_ed25519_BYTES' bytes"
  )

  binding.extension_tweak_ed25519_base(n, p, ns)
}

exports.extension_tweak_ed25519_sign_detached = function (sig, m, scalar, pk) {
  assert(ArrayBuffer.isView(sig), 'sig must be a typed array')
  assert(sig.byteLength === binding.crypto_sign_BYTES, "sig must be 'crypto_sign_BYTES' bytes")
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(scalar), 'scalar must be a typed array')
  assert(
    scalar.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "scalar must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )

  if (pk) {
    assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
    assert(
      pk.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
      "pk must be 'crypto_sign_PUBLICKEYBYTES' bytes"
    )
  }

  const res = binding.extension_tweak_ed25519_sign_detached(sig, m, scalar, pk)

  if (res !== 0) throw new Error('failed to compute signature')
}

exports.extension_tweak_ed25519_sk_to_scalar = function (n, sk) {
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "n must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(
    sk.byteLength === binding.crypto_sign_SECRETKEYBYTES,
    "sk must be 'crypto_sign_SECRETKEYBYTES' bytes"
  )

  binding.extension_tweak_ed25519_sk_to_scalar(n, sk)
}

exports.extension_tweak_ed25519_scalar = function (scalarOut, scalar, ns) {
  assert(ArrayBuffer.isView(scalarOut), 'scalarOut must be a typed array')
  assert(
    scalarOut.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "scalarOut must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(scalar), 'scalar must be a typed array')
  assert(
    scalar.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "scalar must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )

  binding.extension_tweak_ed25519_scalar(scalarOut, scalar, ns)
}

exports.extension_tweak_ed25519_pk = function (tpk, pk, ns) {
  assert(ArrayBuffer.isView(tpk), 'tpk must be a typed array')
  assert(
    tpk.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
    "tpk must be 'crypto_sign_PUBLICKEYBYTES' bytes"
  )
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(
    pk.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
    "pk must be 'crypto_sign_PUBLICKEYBYTES' bytes"
  )

  const res = binding.extension_tweak_ed25519_pk(tpk, pk, ns)

  if (res !== 0) throw new Error('failed to tweak public key')
}

exports.extension_tweak_ed25519_keypair = function (pk, scalarOut, scalarIn, ns) {
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(
    pk.byteLength === binding.extension_tweak_ed25519_BYTES,
    "pk must be 'extension_tweak_ed25519_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(scalarOut), 'scalarOut must be a typed array')
  assert(
    scalarOut.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "scalarOut must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(scalarIn), 'scalarIn must be a typed array')
  assert(
    scalarIn.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "scalarIn must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )

  binding.extension_tweak_ed25519_keypair(pk, scalarOut, scalarIn, ns)
}

exports.extension_tweak_ed25519_scalar_add = function (scalarOut, scalar, n) {
  assert(ArrayBuffer.isView(scalarOut), 'scalarOut must be a typed array')
  assert(
    scalarOut.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "scalarOut must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(scalar), 'scalar must be a typed array')
  assert(
    scalar.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "scalar must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "n must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )

  binding.extension_tweak_ed25519_scalar_add(scalarOut, scalar, n)
}

exports.extension_tweak_ed25519_pk_add = function (tpk, pk, p) {
  assert(ArrayBuffer.isView(tpk), 'tpk must be a typed array')
  assert(
    tpk.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
    "tpk must be 'crypto_sign_PUBLICKEYBYTES' bytes"
  )
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(
    pk.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
    "pk must be 'crypto_sign_PUBLICKEYBYTES' bytes"
  )
  assert(ArrayBuffer.isView(p), 'p must be a typed array')
  assert(
    p.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
    "p must be 'crypto_sign_PUBLICKEYBYTES' bytes"
  )

  const res = binding.extension_tweak_ed25519_pk_add(tpk, pk, p)

  if (res !== 0) throw new Error('failed to add tweak to public key')
}

exports.extension_tweak_ed25519_keypair_add = function (pk, scalarOut, scalarIn, tweak) {
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(
    pk.byteLength === binding.extension_tweak_ed25519_BYTES,
    "pk must be 'extension_tweak_ed25519_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(scalarOut), 'scalarOut must be a typed array')
  assert(
    scalarOut.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "scalarOut must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(scalarIn), 'scalarIn must be a typed array')
  assert(
    scalarIn.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "scalarIn must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(tweak), 'tweak must be a typed array')
  assert(
    tweak.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "tweak must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )

  const res = binding.extension_tweak_ed25519_keypair_add(pk, scalarOut, scalarIn, tweak)

  if (res !== 0) throw new Error('failed to add tweak to keypair')
}

exports.extension_pbkdf2_sha512_async = function (out, passwd, salt, iter, outlen, callback) {
  assert(
    iter >= binding.extension_pbkdf2_sha512_ITERATIONS_MIN,
    "iter must be at least 'extension_pbkdf2_sha512_ITERATIONS_MIN'"
  )
  assert(
    outlen <= binding.extension_pbkdf2_sha512_BYTES_MAX,
    "outlen must be at most 'extension_pbkdf2_sha512_BYTES_MAX'"
  )
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(out.byteLength >= outlen, "out must be at least 'outlen' bytes")
  assert(out.byteLength > 0, 'out must not be empty')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(passwd.byteLength > 0, 'passwd must not be empty')
  assert(ArrayBuffer.isView(salt), 'salt must be a typed array')
  assert(salt.byteLength > 0, 'salt must not be empty')

  const [done, promise] = checkStatus(callback)

  binding.extension_pbkdf2_sha512_async(
    out.buffer,
    out.byteOffset,
    out.byteLength,

    passwd.buffer,
    passwd.byteOffset,
    passwd.byteLength,

    salt.buffer,
    salt.byteOffset,
    salt.byteLength,

    iter,
    outlen,

    done
  )

  return promise
}

exports.extension_pbkdf2_sha512 = function (out, passwd, salt, iter, outlen) {
  assert(
    iter >= binding.extension_pbkdf2_sha512_ITERATIONS_MIN,
    "iter must be at least 'extension_pbkdf2_sha512_ITERATIONS_MIN'"
  )
  assert(
    outlen <= binding.extension_pbkdf2_sha512_BYTES_MAX,
    "outlen must be at most 'extension_pbkdf2_sha512_BYTES_MAX'"
  )
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(out.byteLength >= outlen, "out must be at least 'outlen' bytes")
  assert(out.byteLength > 0, 'out must not be empty')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(passwd.byteLength > 0, 'passwd must not be empty')
  assert(ArrayBuffer.isView(salt), 'salt must be a typed array')
  assert(salt.byteLength > 0, 'salt must not be empty')

  const res = binding.extension_pbkdf2_sha512(out, passwd, salt, iter, outlen)

  if (res !== 0) throw new Error('failed to add tweak to public key')
}

function checkStatus(callback, booleanResult = false) {
  let done, promise

  if (typeof callback === 'function') {
    done = function (status) {
      if (booleanResult) callback(null, status === 0)
      else if (status === 0) callback(null)
      else callback(new Error('status: ' + status))
    }
  } else {
    promise = new Promise(function (resolve, reject) {
      done = function (status) {
        if (booleanResult) resolve(status === 0)
        else if (status === 0) resolve()
        else reject(new Error('status: ' + status))
      }
    })
  }

  return [done, promise]
}
{
  "name": "sodium-native",
  "version": "5.1.0",
  "description": "Low level bindings for libsodium",
  "main": "index.js",
  "files": [
    "index.js",
    "binding.cc",
    "binding.js",
    "extensions",
    "prebuilds",
    "CMakeLists.txt"
  ],
  "imports": {
    "assert": {
      "bare": "bare-assert",
      "node": "assert"
    }
  },
  "addon": true,
  "dependencies": {
    "bare-assert": "^1.2.0",
    "require-addon": "^1.1.0",
    "which-runtime": "^1.2.1"
  },
  "devDependencies": {
    "bare-compat-napi": "^1.3.5",
    "brittle": "^3.16.2",
    "cmake-bare": "^1.6.1",
    "cmake-fetch": "^1.4.3",
    "cmake-napi": "^1.2.1",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^2.0.0"
  },
  "scripts": {
    "test": "prettier . --check && npm run test:node && npm run test:bare",
    "test:node": "node test/all.js",
    "test:bare": "bare test/all.js"
  },
  "standard": {
    "ignore": [
      "/test/fixtures/*.js"
    ]
  },
  "engines": {
    "bare": ">=1.16.0"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/sodium-native.git"
  },
  "contributors": [
    "Emil Bay <github@tixz.dk> (http://bayes.dk)",
    "Mathias Buus <mathiasbuus@gmail.com> (https://mafinto.sh)",
    "Christophe Diederichs <chm-diederichs@hyperdivision.dk>"
  ],
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/holepunchto/sodium-native/issues"
  },
  "homepage": "https://github.com/holepunchto/sodium-native"
}
module.exports = require('sodium-native')
{
  "name": "sodium-universal",
  "version": "5.0.1",
  "description": "Universal wrapper for sodium-javascript and sodium-native working in Node.js and the Browser",
  "main": "index.js",
  "dependencies": {
    "sodium-native": "^5.0.1"
  },
  "peerDependencies": {
    "sodium-javascript": "~0.8.0"
  },
  "peerDependenciesMeta": {
    "sodium-javascript": {
      "optional": true
    }
  },
  "scripts": {
    "prepublish": "./build-scripts/generate.js"
  },
  "browser": {
    "sodium-native": "sodium-javascript"
  },
  "browserify": {
    "transform": [
      "./build-scripts/transform.js"
    ]
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/sodium-universal.git"
  },
  "keywords": [
    "libsodium",
    "sodium",
    "sodium-native",
    "sodium-javascript",
    "browserify"
  ],
  "contributors": [
    "Emil Bay <github@tixz.dk> (http://bayes.dk)",
    "Mathias Buus <mathiasbuus@gmail.com> (https://mafinto.sh)",
    "Christophe Diederichs <chm-diederichs@hyperdivision.dk>"
  ],
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/holepunchto/sodium-universal/issues"
  },
  "homepage": "https://github.com/holepunchto/sodium-universal#readme"
}
const { runtime, platform, arch } = typeof Bare !== 'undefined'
  ? { runtime: 'bare', platform: global.Bare.platform, arch: global.Bare.arch }
  : typeof process !== 'undefined'
    ? { runtime: 'node', platform: global.process.platform, arch: global.process.arch }
    : typeof Window !== 'undefined'
      ? { runtime: 'browser', platform: 'unknown', arch: 'unknown' }
      : { runtime: 'unknown', platform: 'unknown', arch: 'unknown' }

exports.runtime = runtime
exports.platform = platform
exports.arch = arch
exports.isBare = runtime === 'bare'
exports.isBareKit = exports.isBare && typeof BareKit !== 'undefined'
exports.isPear = typeof Pear !== 'undefined'
exports.isNode = runtime === 'node'
exports.isBrowser = runtime === 'browser'
exports.isWindows = platform === 'win32'
exports.isLinux = platform === 'linux'
exports.isMac = platform === 'darwin'
exports.isIOS = platform === 'ios' || platform === 'ios-simulator'
exports.isAndroid = platform === 'android'
exports.isElectron = typeof process !== 'undefined' && !!global.process.versions?.electron
exports.isElectronRenderer = exports.isElectron && global.process.type === 'renderer'
exports.isElectronWorker = exports.isElectron && global.process.type === 'worker'
{
  "name": "which-runtime",
  "version": "1.3.2",
  "description": "Detect if you are in Bare or Node and which os etc",
  "main": "index.js",
  "files": [
    "index.js"
  ],
  "dependencies": {},
  "devDependencies": {
    "standard": "^17.0.0"
  },
  "scripts": {
    "test": "standard"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/holepunchto/which-runtime.git"
  },
  "author": "Holepunch Inc.",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/which-runtime/issues"
  },
  "homepage": "https://github.com/holepunchto/which-runtime"
}
{
  "name": "anproto",
  "version": "0.1.0",
  "description": "Wire-compatible implementation of evbogue's ANProto (Authenticated Non-networked protocol) — Ed25519 signed timestamp+hash messages",
  "type": "module",
  "main": "index.js",
  "scripts": {
    "test": "brittle test/*.test.js"
  },
  "dependencies": {
    "b4a": "^1.6.0",
    "hypercore-crypto": "^3.4.0",
    "sodium-native": "^5.0.0"
  },
  "devDependencies": {
    "brittle": "^3.0.0"
  }
}
const HypercoreEncryption = require('hypercore/lib/default-encryption.js')
const sodium = require('sodium-universal')
const crypto = require('hypercore-crypto')
const c = require('compact-encoding')
const b4a = require('b4a')

const ManifestData = {
  preencode(state, m) {
    c.uint.preencode(state, m.version)
    state.end++ // max flag is 2 so always one byte

    if (m.legacyBlocks) c.uint.preencode(state, m.legacyBlocks)
    if (m.namespace) c.fixed32.preencode(state, m.namespace)
  },
  encode(state, m) {
    const flags = (m.legacyBlocks ? 1 : 0) | (m.namespace ? 2 : 0)

    c.uint.encode(state, m.version)
    c.uint.encode(state, flags)

    if (m.legacyBlocks) c.uint.encode(state, m.legacyBlocks)
    if (m.namespace) c.fixed32.encode(state, m.namespace)
  },
  decode(state) {
    const r0 = c.uint.decode(state)
    const flags = c.uint.decode(state)

    return {
      version: r0,
      legacyBlocks: (flags & 1) !== 0 ? c.uint.decode(state) : 0,
      namespace: (flags & 2) !== 0 ? c.fixed32.decode(state) : null
    }
  }
}

const [, NS_VIEW_BLOCK_KEY, NS_HASH_KEY] = crypto.namespace('autobase', 4)

const [GENESIS_ENTROPY] = crypto.namespace('autobase/entropy', 2)

const nonce = b4a.alloc(sodium.crypto_stream_NONCEBYTES)
const hash = nonce.subarray(0, sodium.crypto_generichash_BYTES_MIN)

class AutobeeEncryption {
  static PADDING = 8

  constructor(auto) {
    this.auto = auto

    this.compat = null
    this.keys = null
    this.keysById = new Map()
  }

  get id() {
    return this.keys ? this.keys.id : 0
  }

  padding() {
    return AutobeeEncryption.PADDING
  }

  isCompat(ctx) {
    return ctx.manifest.version <= 1
  }

  load(keys) {
    if (this.keys === null) this.keys = keys
  }

  async update(ctx) {
    if (!this.keys) this.keys = await this.get(0, ctx)
  }

  async get(id, ctx) {
    if (this.keysById.has(id)) return this.keysById.get(id)

    const keys = await this.getKeys(id, ctx)
    this.keysById.set(id, keys)

    return keys
  }

  async getKeys(id, ctx) {
    if (!this.auto.encryptionKey) return null

    const entropy = GENESIS_ENTROPY
    const block = this.blockKey(entropy, ctx)
    const hash = crypto.hash([NS_HASH_KEY, block])

    return {
      id,
      block,
      hash
    }
  }

  blockKey(entropy, ctx) {
    return getBlockKey(this.auto.key, this.auto.encryptionKey, entropy, ctx.key)
  }

  async _ensureCompat(ctx) {
    if (!this.compat) this.compat = this.compatKeys(ctx)
  }

  compatKeys() {
    throw new Error('Compatibility method is not specified')
  }

  async encrypt(index, block, fork, ctx) {
    if (this.isCompat(ctx, index)) {
      this._ensureCompat(ctx)
      return HypercoreEncryption.encrypt(
        index,
        block,
        fork,
        this.compat.block,
        this.compat.blinding
      )
    }

    await this.update(ctx)

    encryptBlock(index, block, this.keys.id, this.keys.block, this.keys.hash)
  }

  async decrypt(index, block, ctx) {
    if (this.isCompat(ctx, index)) {
      this._ensureCompat(ctx)
      return HypercoreEncryption.decrypt(index, block, this.compat.block)
    }

    const padding = block.subarray(0, AutobeeEncryption.PADDING)
    block = block.subarray(AutobeeEncryption.PADDING)

    const type = padding[0]
    switch (type) {
      case 0:
        return block // unencrypted

      case 1:
        break

      default:
        throw new Error('Unrecognised encryption type')
    }

    const id = c.uint32.decode({ start: 4, end: 8, buffer: padding })

    const keys = await this.get(id, ctx)

    c.uint64.encode({ start: 0, end: 8, buffer: nonce }, index)
    nonce.set(padding, 8, 16)

    // Decrypt the block using the full nonce
    decrypt(block, nonce, keys.block)
  }

  static encryptAnchor(block, bootstrap, encryptionKey, namespace) {
    const entropy = GENESIS_ENTROPY

    const blockKey = getBlockKey(bootstrap, encryptionKey, entropy, namespace)
    const hashKey = crypto.hash([NS_HASH_KEY, block])

    encryptBlock(0, block, 0, blockKey, hashKey)
  }

  static async setSystemEncryption(bootstrap, encryptionKey, core, opts) {
    await core.ready()
    if (core.manifest.version === 1) {
      const key = getCompatBlockKey(bootstrap, encryptionKey, '_system')
      core.setEncryptionKey(key, { block: true })
      return
    }

    const info = {
      key: bootstrap,
      encryptionKey
    }

    return core.setEncryption(new ViewEncryption(info, 'system'))
  }

  static getSystemEncryption(bootstrap, encryptionKey) {
    return AutobeeEncryption.getViewEncryption(bootstrap, encryptionKey, '_system')
  }

  static getViewEncryption(bootstrap, encryptionKey, name) {
    const info = { key: bootstrap, encryptionKey }
    return new ViewEncryption(info, name)
  }

  static getWriterEncryption(bootstrap, encryptionKey) {
    return new WriterEncryption({ key: bootstrap, encryptionKey })
  }
}

class ViewEncryption extends AutobeeEncryption {
  constructor(auto, name) {
    super(auto)
    this.name = name
  }

  compatKeys() {
    const block = getCompatBlockKey(this.auto.key, this.auto.encryptionKey, this.name)
    return {
      block,
      blinding: crypto.hash(block)
    }
  }

  blockKey(entropy) {
    return getCompatBlockKey(this.auto.key, entropy, this.name)
  }
}

class WriterEncryption extends AutobeeEncryption {
  compatKeys(ctx) {
    return HypercoreEncryption.deriveKeys(this.auto.encryptionKey, ctx.key)
  }

  blockKey(entropy, ctx) {
    // used for anchors
    if (ctx.manifest.userData) {
      const userData = c.decode(ManifestData, ctx.manifest.userData)
      if (userData.namespace !== null) {
        const b = getBlockKey(this.auto.key, this.auto.encryptionKey, entropy, userData.namespace)
        return b
      }
    }

    return getBlockKey(this.auto.key, this.auto.encryptionKey, entropy, ctx.key)
  }
}

module.exports = {
  AutobeeEncryption,
  WriterEncryption,
  ViewEncryption
}

function encrypt(block, nonce, key) {
  sodium.crypto_stream_xor(block, block, nonce, key)
}

function decrypt(block, nonce, key) {
  return encrypt(block, nonce, key) // symmetric
}

function getBlockKey(bootstrap, encryptionKey, entropy, hypercoreKey) {
  return (
    encryptionKey &&
    crypto.hash([NS_VIEW_BLOCK_KEY, bootstrap, encryptionKey, entropy, hypercoreKey])
  )
}

function getCompatBlockKey(bootstrap, encryptionKey, name) {
  if (typeof name === 'string') return getCompatBlockKey(bootstrap, encryptionKey, b4a.from(name))
  return encryptionKey && crypto.hash([NS_VIEW_BLOCK_KEY, bootstrap, encryptionKey, name])
}

function blockhash(block, padding, hashKey) {
  sodium.crypto_generichash(hash, block, hashKey)
  padding.set(hash.subarray(0, 8)) // copy first 8 bytes of hash
  hash.fill(0) // clear nonce buffer
}

function encryptBlock(index, block, id, blockKey, hashKey) {
  const padding = block.subarray(0, AutobeeEncryption.PADDING)
  block = block.subarray(AutobeeEncryption.PADDING)

  blockhash(block, padding, hashKey)
  c.uint32.encode({ start: 4, end: 8, buffer: padding }, id)

  c.uint64.encode({ start: 0, end: 8, buffer: nonce }, index)

  padding[0] = 1 // version in plaintext

  nonce.set(padding, 8, 16)

  // The combination of index, key id, fork id and block hash is very likely
  // to be unique for a given Hypercore and therefore our nonce is suitable
  encrypt(block, nonce, blockKey)
}
const LE = (exports.LE =
  new Uint8Array(new Uint16Array([0xff]).buffer)[0] === 0xff)

exports.BE = !LE
const b4a = require('b4a')

const { BE } = require('./endian')

exports.state = function (start = 0, end = 0, buffer = null) {
  return { start, end, buffer }
}

const raw = (exports.raw = require('./raw'))

const uint = (exports.uint = {
  preencode(state, n) {
    state.end += n <= 0xfc ? 1 : n <= 0xffff ? 3 : n <= 0xffffffff ? 5 : 9
  },
  encode(state, n) {
    if (n <= 0xfc) uint8.encode(state, n)
    else if (n <= 0xffff) {
      state.buffer[state.start++] = 0xfd
      uint16.encode(state, n)
    } else if (n <= 0xffffffff) {
      state.buffer[state.start++] = 0xfe
      uint32.encode(state, n)
    } else {
      state.buffer[state.start++] = 0xff
      uint64.encode(state, n)
    }
  },
  decode(state) {
    const a = uint8.decode(state)
    if (a <= 0xfc) return a
    if (a === 0xfd) return uint16.decode(state)
    if (a === 0xfe) return uint32.decode(state)
    return uint64.decode(state)
  }
})

const uint8 = (exports.uint8 = {
  preencode(state, n) {
    state.end += 1
  },
  encode(state, n) {
    validateUint(n)
    state.buffer[state.start++] = n
  },
  decode(state) {
    if (state.start >= state.end) throw new Error('Out of bounds')
    return state.buffer[state.start++]
  }
})

const uint16 = (exports.uint16 = {
  preencode(state, n) {
    state.end += 2
  },
  encode(state, n) {
    validateUint(n)
    state.buffer[state.start++] = n
    state.buffer[state.start++] = n >>> 8
  },
  decode(state) {
    if (state.end - state.start < 2) throw new Error('Out of bounds')
    return state.buffer[state.start++] + state.buffer[state.start++] * 0x100
  }
})

const uint24 = (exports.uint24 = {
  preencode(state, n) {
    state.end += 3
  },
  encode(state, n) {
    validateUint(n)
    state.buffer[state.start++] = n
    state.buffer[state.start++] = n >>> 8
    state.buffer[state.start++] = n >>> 16
  },
  decode(state) {
    if (state.end - state.start < 3) throw new Error('Out of bounds')
    return (
      state.buffer[state.start++] +
      state.buffer[state.start++] * 0x100 +
      state.buffer[state.start++] * 0x10000
    )
  }
})

const uint32 = (exports.uint32 = {
  preencode(state, n) {
    state.end += 4
  },
  encode(state, n) {
    validateUint(n)
    state.buffer[state.start++] = n
    state.buffer[state.start++] = n >>> 8
    state.buffer[state.start++] = n >>> 16
    state.buffer[state.start++] = n >>> 24
  },
  decode(state) {
    if (state.end - state.start < 4) throw new Error('Out of bounds')
    return (
      state.buffer[state.start++] +
      state.buffer[state.start++] * 0x100 +
      state.buffer[state.start++] * 0x10000 +
      state.buffer[state.start++] * 0x1000000
    )
  }
})

const uint40 = (exports.uint40 = {
  preencode(state, n) {
    state.end += 5
  },
  encode(state, n) {
    validateUint(n)
    const r = Math.floor(n / 0x100)
    uint8.encode(state, n)
    uint32.encode(state, r)
  },
  decode(state) {
    if (state.end - state.start < 5) throw new Error('Out of bounds')
    return uint8.decode(state) + 0x100 * uint32.decode(state)
  }
})

const uint48 = (exports.uint48 = {
  preencode(state, n) {
    state.end += 6
  },
  encode(state, n) {
    validateUint(n)
    const r = Math.floor(n / 0x10000)
    uint16.encode(state, n)
    uint32.encode(state, r)
  },
  decode(state) {
    if (state.end - state.start < 6) throw new Error('Out of bounds')
    return uint16.decode(state) + 0x10000 * uint32.decode(state)
  }
})

const uint56 = (exports.uint56 = {
  preencode(state, n) {
    state.end += 7
  },
  encode(state, n) {
    validateUint(n)
    const r = Math.floor(n / 0x1000000)
    uint24.encode(state, n)
    uint32.encode(state, r)
  },
  decode(state) {
    if (state.end - state.start < 7) throw new Error('Out of bounds')
    return uint24.decode(state) + 0x1000000 * uint32.decode(state)
  }
})

const uint64 = (exports.uint64 = {
  preencode(state, n) {
    state.end += 8
  },
  encode(state, n) {
    validateUint(n)
    const r = Math.floor(n / 0x100000000)
    uint32.encode(state, n)
    uint32.encode(state, r)
  },
  decode(state) {
    if (state.end - state.start < 8) throw new Error('Out of bounds')
    return uint32.decode(state) + 0x100000000 * uint32.decode(state)
  }
})

const int = (exports.int = zigZagInt(uint))
exports.int8 = zigZagInt(uint8)
exports.int16 = zigZagInt(uint16)
exports.int24 = zigZagInt(uint24)
exports.int32 = zigZagInt(uint32)
exports.int40 = zigZagInt(uint40)
exports.int48 = zigZagInt(uint48)
exports.int56 = zigZagInt(uint56)
exports.int64 = zigZagInt(uint64)

const biguint64 = (exports.biguint64 = {
  preencode(state, n) {
    state.end += 8
  },
  encode(state, n) {
    const view = new DataView(
      state.buffer.buffer,
      state.start + state.buffer.byteOffset,
      8
    )
    view.setBigUint64(0, n, true) // little endian
    state.start += 8
  },
  decode(state) {
    if (state.end - state.start < 8) throw new Error('Out of bounds')
    const view = new DataView(
      state.buffer.buffer,
      state.start + state.buffer.byteOffset,
      8
    )
    const n = view.getBigUint64(0, true) // little endian
    state.start += 8
    return n
  }
})

exports.bigint64 = zigZagBigInt(biguint64)

const biguint = (exports.biguint = {
  preencode(state, n) {
    let len = 0
    for (let m = n; m; m = m >> 64n) len++
    uint.preencode(state, len)
    state.end += 8 * len
  },
  encode(state, n) {
    let len = 0
    for (let m = n; m; m = m >> 64n) len++
    uint.encode(state, len)
    const view = new DataView(
      state.buffer.buffer,
      state.start + state.buffer.byteOffset,
      8 * len
    )
    for (let m = n, i = 0; m; m = m >> 64n, i += 8) {
      view.setBigUint64(i, BigInt.asUintN(64, m), true) // little endian
    }
    state.start += 8 * len
  },
  decode(state) {
    const len = uint.decode(state)
    if (state.end - state.start < 8 * len) throw new Error('Out of bounds')
    const view = new DataView(
      state.buffer.buffer,
      state.start + state.buffer.byteOffset,
      8 * len
    )
    let n = 0n
    for (let i = len - 1; i >= 0; i--)
      n = (n << 64n) + view.getBigUint64(i * 8, true) // little endian
    state.start += 8 * len
    return n
  }
})

exports.bigint = zigZagBigInt(biguint)

exports.lexint = require('./lexint')

exports.float32 = {
  preencode(state, n) {
    state.end += 4
  },
  encode(state, n) {
    const view = new DataView(
      state.buffer.buffer,
      state.start + state.buffer.byteOffset,
      4
    )
    view.setFloat32(0, n, true) // little endian
    state.start += 4
  },
  decode(state) {
    if (state.end - state.start < 4) throw new Error('Out of bounds')
    const view = new DataView(
      state.buffer.buffer,
      state.start + state.buffer.byteOffset,
      4
    )
    const float = view.getFloat32(0, true) // little endian
    state.start += 4
    return float
  }
}

exports.float64 = {
  preencode(state, n) {
    state.end += 8
  },
  encode(state, n) {
    const view = new DataView(
      state.buffer.buffer,
      state.start + state.buffer.byteOffset,
      8
    )
    view.setFloat64(0, n, true) // little endian
    state.start += 8
  },
  decode(state) {
    if (state.end - state.start < 8) throw new Error('Out of bounds')
    const view = new DataView(
      state.buffer.buffer,
      state.start + state.buffer.byteOffset,
      8
    )
    const float = view.getFloat64(0, true) // little endian
    state.start += 8
    return float
  }
}

const buffer = (exports.buffer = {
  preencode(state, b) {
    if (b) uint8array.preencode(state, b)
    else state.end++
  },
  encode(state, b) {
    if (b) uint8array.encode(state, b)
    else state.buffer[state.start++] = 0
  },
  decode(state) {
    const len = uint.decode(state)
    if (len === 0) return null
    if (state.end - state.start < len) throw new Error('Out of bounds')
    return state.buffer.subarray(state.start, (state.start += len))
  }
})

exports.binary = {
  ...buffer,
  preencode(state, b) {
    if (typeof b === 'string') utf8.preencode(state, b)
    else buffer.preencode(state, b)
  },
  encode(state, b) {
    if (typeof b === 'string') utf8.encode(state, b)
    else buffer.encode(state, b)
  }
}

exports.arraybuffer = {
  preencode(state, b) {
    uint.preencode(state, b.byteLength)
    state.end += b.byteLength
  },
  encode(state, b) {
    uint.encode(state, b.byteLength)

    const view = new Uint8Array(b)

    state.buffer.set(view, state.start)
    state.start += b.byteLength
  },
  decode(state) {
    const len = uint.decode(state)

    const b = new ArrayBuffer(len)
    const view = new Uint8Array(b)

    view.set(state.buffer.subarray(state.start, (state.start += len)))

    return b
  }
}

function typedarray(TypedArray, swap) {
  const n = TypedArray.BYTES_PER_ELEMENT

  return {
    preencode(state, b) {
      uint.preencode(state, b.length)
      state.end += b.byteLength
    },
    encode(state, b) {
      uint.encode(state, b.length)

      const view = new Uint8Array(b.buffer, b.byteOffset, b.byteLength)

      if (BE && swap) swap(view)

      state.buffer.set(view, state.start)
      state.start += b.byteLength
    },
    decode(state) {
      const len = uint.decode(state)

      let b = state.buffer.subarray(state.start, (state.start += len * n))
      if (b.byteLength !== len * n) throw new Error('Out of bounds')
      if (b.byteOffset % n !== 0) b = new Uint8Array(b)

      if (BE && swap) swap(b)

      return new TypedArray(b.buffer, b.byteOffset, b.byteLength / n)
    }
  }
}

const uint8array = (exports.uint8array = typedarray(Uint8Array))
exports.uint16array = typedarray(Uint16Array, b4a.swap16)
exports.uint32array = typedarray(Uint32Array, b4a.swap32)

exports.int8array = typedarray(Int8Array)
exports.int16array = typedarray(Int16Array, b4a.swap16)
exports.int32array = typedarray(Int32Array, b4a.swap32)

exports.biguint64array = typedarray(BigUint64Array, b4a.swap64)
exports.bigint64array = typedarray(BigInt64Array, b4a.swap64)

exports.float32array = typedarray(Float32Array, b4a.swap32)
exports.float64array = typedarray(Float64Array, b4a.swap64)

function string(encoding) {
  return {
    preencode(state, s) {
      const len = b4a.byteLength(s, encoding)
      uint.preencode(state, len)
      state.end += len
    },
    encode(state, s) {
      const len = b4a.byteLength(s, encoding)
      uint.encode(state, len)
      b4a.write(state.buffer, s, state.start, encoding)
      state.start += len
    },
    decode(state) {
      const len = uint.decode(state)
      if (state.end - state.start < len) throw new Error('Out of bounds')
      return b4a.toString(
        state.buffer,
        encoding,
        state.start,
        (state.start += len)
      )
    },
    fixed(n) {
      return {
        preencode(state) {
          state.end += n
        },
        encode(state, s) {
          b4a.write(state.buffer, s, state.start, n, encoding)
          state.start += n
        },
        decode(state) {
          if (state.end - state.start < n) throw new Error('Out of bounds')
          return b4a.toString(
            state.buffer,
            encoding,
            state.start,
            (state.start += n)
          )
        }
      }
    }
  }
}

const utf8 = (exports.string = exports.utf8 = string('utf-8'))
exports.ascii = string('ascii')
exports.hex = string('hex')
exports.base64 = string('base64')
exports.ucs2 = exports.utf16le = string('utf16le')

exports.bool = {
  preencode(state, b) {
    state.end++
  },
  encode(state, b) {
    state.buffer[state.start++] = b ? 1 : 0
  },
  decode(state) {
    if (state.start >= state.end) throw Error('Out of bounds')
    return state.buffer[state.start++] === 1
  }
}

const fixed = (exports.fixed = function fixed(n) {
  return {
    preencode(state, s) {
      if (s.byteLength !== n) throw new Error('Incorrect buffer size')
      state.end += n
    },
    encode(state, s) {
      state.buffer.set(s, state.start)
      state.start += n
    },
    decode(state) {
      if (state.end - state.start < n) throw new Error('Out of bounds')
      return state.buffer.subarray(state.start, (state.start += n))
    }
  }
})

exports.fixed32 = fixed(32)
exports.fixed64 = fixed(64)

exports.array = function array(enc) {
  return {
    preencode(state, list) {
      uint.preencode(state, list.length)
      for (let i = 0; i < list.length; i++) enc.preencode(state, list[i])
    },
    encode(state, list) {
      uint.encode(state, list.length)
      for (let i = 0; i < list.length; i++) enc.encode(state, list[i])
    },
    decode(state) {
      const len = uint.decode(state)
      if (len > 0x100000) throw new Error('Array is too big')
      const arr = new Array(len)
      for (let i = 0; i < len; i++) arr[i] = enc.decode(state)
      return arr
    }
  }
}

exports.frame = function frame(enc) {
  const dummy = exports.state()

  return {
    preencode(state, m) {
      const end = state.end
      enc.preencode(state, m)
      uint.preencode(state, state.end - end)
    },
    encode(state, m) {
      dummy.end = 0
      enc.preencode(dummy, m)
      uint.encode(state, dummy.end)
      enc.encode(state, m)
    },
    decode(state) {
      const end = state.end
      const len = uint.decode(state)
      state.end = state.start + len
      const m = enc.decode(state)
      state.start = state.end
      state.end = end
      return m
    }
  }
}

exports.date = {
  preencode(state, d) {
    int.preencode(state, d.getTime())
  },
  encode(state, d) {
    int.encode(state, d.getTime())
  },
  decode(state, d) {
    return new Date(int.decode(state))
  }
}

exports.json = {
  preencode(state, v) {
    utf8.preencode(state, JSON.stringify(v))
  },
  encode(state, v) {
    utf8.encode(state, JSON.stringify(v))
  },
  decode(state) {
    return JSON.parse(utf8.decode(state))
  }
}

exports.ndjson = {
  preencode(state, v) {
    utf8.preencode(state, JSON.stringify(v) + '\n')
  },
  encode(state, v) {
    utf8.encode(state, JSON.stringify(v) + '\n')
  },
  decode(state) {
    return JSON.parse(utf8.decode(state))
  }
}

// simple helper for when you want to just express nothing
exports.none = {
  preencode(state, n) {
    // do nothing
  },
  encode(state, n) {
    // do nothing
  },
  decode(state) {
    return null
  }
}

// "any" encoders here for helping just structure any object without schematising it

const anyArray = {
  preencode(state, arr) {
    uint.preencode(state, arr.length)
    for (let i = 0; i < arr.length; i++) {
      any.preencode(state, arr[i])
    }
  },
  encode(state, arr) {
    uint.encode(state, arr.length)
    for (let i = 0; i < arr.length; i++) {
      any.encode(state, arr[i])
    }
  },
  decode(state) {
    const arr = []
    let len = uint.decode(state)
    while (len-- > 0) {
      arr.push(any.decode(state))
    }
    return arr
  }
}

const anyObject = {
  preencode(state, o) {
    const keys = Object.keys(o)
    uint.preencode(state, keys.length)
    for (const key of keys) {
      utf8.preencode(state, key)
      any.preencode(state, o[key])
    }
  },
  encode(state, o) {
    const keys = Object.keys(o)
    uint.encode(state, keys.length)
    for (const key of keys) {
      utf8.encode(state, key)
      any.encode(state, o[key])
    }
  },
  decode(state) {
    let len = uint.decode(state)
    const o = {}
    while (len-- > 0) {
      const key = utf8.decode(state)
      o[key] = any.decode(state)
    }
    return o
  }
}

const anyTypes = [
  exports.none,
  exports.bool,
  exports.string,
  exports.buffer,
  exports.uint,
  exports.int,
  exports.float64,
  anyArray,
  anyObject,
  exports.date
]

const any = (exports.any = {
  preencode(state, o) {
    const t = getType(o)
    uint.preencode(state, t)
    anyTypes[t].preencode(state, o)
  },
  encode(state, o) {
    const t = getType(o)
    uint.encode(state, t)
    anyTypes[t].encode(state, o)
  },
  decode(state) {
    const t = uint.decode(state)
    if (t >= anyTypes.length) throw new Error('Unknown type: ' + t)
    return anyTypes[t].decode(state)
  }
})

const port = (exports.port = uint16)

const address = (host, family) => {
  return {
    preencode(state, m) {
      host.preencode(state, m.host)
      port.preencode(state, m.port)
    },
    encode(state, m) {
      host.encode(state, m.host)
      port.encode(state, m.port)
    },
    decode(state) {
      return {
        host: host.decode(state),
        family,
        port: port.decode(state)
      }
    }
  }
}

const ipv4 = (exports.ipv4 = {
  preencode(state) {
    state.end += 4
  },
  encode(state, string) {
    const start = state.start
    const end = start + 4

    let i = 0

    while (i < string.length) {
      let n = 0
      let c

      while (
        i < string.length &&
        (c = string.charCodeAt(i++)) !== /* . */ 0x2e
      ) {
        n = n * 10 + (c - /* 0 */ 0x30)
      }

      state.buffer[state.start++] = n
    }

    state.start = end
  },
  decode(state) {
    if (state.end - state.start < 4) throw new Error('Out of bounds')
    return (
      state.buffer[state.start++] +
      '.' +
      state.buffer[state.start++] +
      '.' +
      state.buffer[state.start++] +
      '.' +
      state.buffer[state.start++]
    )
  }
})

exports.ipv4Address = address(ipv4, 4)

const ipv6 = (exports.ipv6 = {
  preencode(state) {
    state.end += 16
  },
  encode(state, string) {
    const start = state.start
    const end = start + 16

    let i = 0
    let split = null

    while (i < string.length) {
      let n = 0
      let c

      while (
        i < string.length &&
        (c = string.charCodeAt(i++)) !== /* : */ 0x3a
      ) {
        if (c >= 0x30 && c <= 0x39) n = n * 0x10 + (c - /* 0 */ 0x30)
        else if (c >= 0x41 && c <= 0x46) n = n * 0x10 + (c - /* A */ 0x41 + 10)
        else if (c >= 0x61 && c <= 0x66) n = n * 0x10 + (c - /* a */ 0x61 + 10)
      }

      state.buffer[state.start++] = n >>> 8
      state.buffer[state.start++] = n

      if (i < string.length && string.charCodeAt(i) === /* : */ 0x3a) {
        i++
        split = state.start
      }
    }

    if (split !== null) {
      const offset = end - state.start
      state.buffer
        .copyWithin(split + offset, split)
        .fill(0, split, split + offset)
    }

    state.start = end
  },
  decode(state) {
    if (state.end - state.start < 16) throw new Error('Out of bounds')
    return (
      (
        state.buffer[state.start++] * 256 +
        state.buffer[state.start++]
      ).toString(16) +
      ':' +
      (
        state.buffer[state.start++] * 256 +
        state.buffer[state.start++]
      ).toString(16) +
      ':' +
      (
        state.buffer[state.start++] * 256 +
        state.buffer[state.start++]
      ).toString(16) +
      ':' +
      (
        state.buffer[state.start++] * 256 +
        state.buffer[state.start++]
      ).toString(16) +
      ':' +
      (
        state.buffer[state.start++] * 256 +
        state.buffer[state.start++]
      ).toString(16) +
      ':' +
      (
        state.buffer[state.start++] * 256 +
        state.buffer[state.start++]
      ).toString(16) +
      ':' +
      (
        state.buffer[state.start++] * 256 +
        state.buffer[state.start++]
      ).toString(16) +
      ':' +
      (
        state.buffer[state.start++] * 256 +
        state.buffer[state.start++]
      ).toString(16)
    )
  }
})

exports.ipv6Address = address(ipv6, 6)

const ip = (exports.ip = {
  preencode(state, string) {
    const family = string.includes(':') ? 6 : 4
    uint8.preencode(state, family)
    if (family === 4) ipv4.preencode(state)
    else ipv6.preencode(state)
  },
  encode(state, string) {
    const family = string.includes(':') ? 6 : 4
    uint8.encode(state, family)
    if (family === 4) ipv4.encode(state, string)
    else ipv6.encode(state, string)
  },
  decode(state) {
    const family = uint8.decode(state)
    if (family === 4) return ipv4.decode(state)
    else return ipv6.decode(state)
  }
})

exports.ipAddress = {
  preencode(state, m) {
    ip.preencode(state, m.host)
    port.preencode(state, m.port)
  },
  encode(state, m) {
    ip.encode(state, m.host)
    port.encode(state, m.port)
  },
  decode(state) {
    const family = uint8.decode(state)
    return {
      host: family === 4 ? ipv4.decode(state) : ipv6.decode(state),
      family,
      port: port.decode(state)
    }
  }
}

const record = (exports.record = function (keyEncoding, valueEncoding) {
  return {
    preencode(state, v) {
      const keys = Object.keys(v)
      uint.preencode(state, keys.length)
      for (const k of keys) {
        keyEncoding.preencode(state, k)
        valueEncoding.preencode(state, v[k])
      }
    },
    encode(state, v) {
      const keys = Object.keys(v)
      uint.encode(state, keys.length)
      for (const k of keys) {
        keyEncoding.encode(state, k)
        valueEncoding.encode(state, v[k])
      }
    },
    decode(state) {
      const out = Object.create(null)
      const keys = uint.decode(state)
      for (let i = 0; i < keys; i++) {
        out[keyEncoding.decode(state)] = valueEncoding.decode(state)
      }
      return out
    }
  }
})

exports.stringRecord = record(utf8, utf8)

function getType(o) {
  if (o === null || o === undefined) return 0
  if (typeof o === 'boolean') return 1
  if (typeof o === 'string') return 2
  if (b4a.isBuffer(o)) return 3
  if (typeof o === 'number') {
    if (Number.isInteger(o)) return o >= 0 ? 4 : 5
    return 6
  }
  if (Array.isArray(o)) return 7
  if (o instanceof Date) return 9
  if (typeof o === 'object') return 8

  throw new Error('Unsupported type for ' + o)
}

exports.from = function from(enc) {
  if (typeof enc === 'string') return fromNamed(enc)
  if (enc.preencode) return enc
  if (enc.encodingLength) return fromAbstractEncoder(enc)
  return fromCodec(enc)
}

function fromNamed(enc) {
  switch (enc) {
    case 'ascii':
      return raw.ascii
    case 'utf-8':
    case 'utf8':
      return raw.utf8
    case 'hex':
      return raw.hex
    case 'base64':
      return raw.base64
    case 'utf16-le':
    case 'utf16le':
    case 'ucs-2':
    case 'ucs2':
      return raw.ucs2
    case 'ndjson':
      return raw.ndjson
    case 'json':
      return raw.json
    case 'binary':
    default:
      return raw.binary
  }
}

function fromCodec(enc) {
  let tmpM = null
  let tmpBuf = null

  return {
    preencode(state, m) {
      tmpM = m
      tmpBuf = enc.encode(m)
      state.end += tmpBuf.byteLength
    },
    encode(state, m) {
      raw.encode(state, m === tmpM ? tmpBuf : enc.encode(m))
      tmpM = tmpBuf = null
    },
    decode(state) {
      return enc.decode(raw.decode(state))
    }
  }
}

function fromAbstractEncoder(enc) {
  return {
    preencode(state, m) {
      state.end += enc.encodingLength(m)
    },
    encode(state, m) {
      enc.encode(m, state.buffer, state.start)
      state.start += enc.encode.bytes
    },
    decode(state) {
      const m = enc.decode(state.buffer, state.start, state.end)
      state.start += enc.decode.bytes
      return m
    }
  }
}

exports.encode = function encode(enc, m) {
  const state = exports.state()
  enc.preencode(state, m)
  state.buffer = b4a.allocUnsafe(state.end)
  enc.encode(state, m)
  return state.buffer
}

exports.decode = function decode(enc, buffer) {
  return enc.decode(exports.state(0, buffer.byteLength, buffer))
}

function zigZagInt(enc) {
  return {
    preencode(state, n) {
      enc.preencode(state, zigZagEncodeInt(n))
    },
    encode(state, n) {
      enc.encode(state, zigZagEncodeInt(n))
    },
    decode(state) {
      return zigZagDecodeInt(enc.decode(state))
    }
  }
}

function zigZagDecodeInt(n) {
  return n === 0 ? n : (n & 1) === 0 ? n / 2 : -(n + 1) / 2
}

function zigZagEncodeInt(n) {
  // 0, -1, 1, -2, 2, ...
  return n < 0 ? 2 * -n - 1 : n === 0 ? 0 : 2 * n
}

function zigZagBigInt(enc) {
  return {
    preencode(state, n) {
      enc.preencode(state, zigZagEncodeBigInt(n))
    },
    encode(state, n) {
      enc.encode(state, zigZagEncodeBigInt(n))
    },
    decode(state) {
      return zigZagDecodeBigInt(enc.decode(state))
    }
  }
}

function zigZagDecodeBigInt(n) {
  return n === 0n ? n : (n & 1n) === 0n ? n / 2n : -(n + 1n) / 2n
}

function zigZagEncodeBigInt(n) {
  // 0, -1, 1, -2, 2, ...
  return n < 0n ? 2n * -n - 1n : n === 0n ? 0n : 2n * n
}

function validateUint(n) {
  if (n >= 0 === false /* Handles NaN as well */)
    throw new Error('uint must be positive')
}
module.exports = {
  preencode,
  encode,
  decode
}

function preencode(state, num) {
  if (num < 251) {
    state.end++
  } else if (num < 256) {
    state.end += 2
  } else if (num < 0x10000) {
    state.end += 3
  } else if (num < 0x1000000) {
    state.end += 4
  } else if (num < 0x100000000) {
    state.end += 5
  } else {
    state.end++
    const exp = Math.floor(Math.log(num) / Math.log(2)) - 32
    preencode(state, exp)
    state.end += 6
  }
}

function encode(state, num) {
  const max = 251
  const x = num - max

  if (num < max) {
    state.buffer[state.start++] = num
  } else if (num < 256) {
    state.buffer[state.start++] = max
    state.buffer[state.start++] = x
  } else if (num < 0x10000) {
    state.buffer[state.start++] = max + 1
    state.buffer[state.start++] = (x >> 8) & 0xff
    state.buffer[state.start++] = x & 0xff
  } else if (num < 0x1000000) {
    state.buffer[state.start++] = max + 2
    state.buffer[state.start++] = x >> 16
    state.buffer[state.start++] = (x >> 8) & 0xff
    state.buffer[state.start++] = x & 0xff
  } else if (num < 0x100000000) {
    state.buffer[state.start++] = max + 3
    state.buffer[state.start++] = x >> 24
    state.buffer[state.start++] = (x >> 16) & 0xff
    state.buffer[state.start++] = (x >> 8) & 0xff
    state.buffer[state.start++] = x & 0xff
  } else {
    // need to use Math here as bitwise ops are 32 bit
    const exp = Math.floor(Math.log(x) / Math.log(2)) - 32
    state.buffer[state.start++] = 0xff

    encode(state, exp)
    const rem = x / Math.pow(2, exp - 11)

    for (let i = 5; i >= 0; i--) {
      state.buffer[state.start++] = (rem / Math.pow(2, 8 * i)) & 0xff
    }
  }
}

function decode(state) {
  const max = 251

  if (state.end - state.start < 1) throw new Error('Out of bounds')

  const flag = state.buffer[state.start++]

  if (flag < max) return flag

  if (state.end - state.start < flag - max + 1) {
    throw new Error('Out of bounds.')
  }

  if (flag < 252) {
    return state.buffer[state.start++] + max
  }

  if (flag < 253) {
    return (
      (state.buffer[state.start++] << 8) + state.buffer[state.start++] + max
    )
  }

  if (flag < 254) {
    return (
      (state.buffer[state.start++] << 16) +
      (state.buffer[state.start++] << 8) +
      state.buffer[state.start++] +
      max
    )
  }

  // << 24 result may be interpreted as negative
  if (flag < 255) {
    return (
      state.buffer[state.start++] * 0x1000000 +
      (state.buffer[state.start++] << 16) +
      (state.buffer[state.start++] << 8) +
      state.buffer[state.start++] +
      max
    )
  }

  const exp = decode(state)

  if (state.end - state.start < 6) throw new Error('Out of bounds')

  let rem = 0
  for (let i = 5; i >= 0; i--) {
    rem += state.buffer[state.start++] * Math.pow(2, 8 * i)
  }

  return rem * Math.pow(2, exp - 11) + max
}
{
  "name": "compact-encoding",
  "version": "2.19.2",
  "description": "A series of compact encoding schemes for building small and fast parsers and serializers",
  "main": "index.js",
  "dependencies": {
    "b4a": "^1.3.0"
  },
  "devDependencies": {
    "brittle": "^3.0.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^1.0.0"
  },
  "scripts": {
    "format": "prettier . --write",
    "test": "prettier . --check && brittle test.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/holepunchto/compact-encoding.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/compact-encoding/issues"
  },
  "homepage": "https://github.com/holepunchto/compact-encoding"
}
const b4a = require('b4a')

const { BE } = require('./endian')

exports = module.exports = {
  preencode(state, b) {
    state.end += b.byteLength
  },
  encode(state, b) {
    state.buffer.set(b, state.start)
    state.start += b.byteLength
  },
  decode(state) {
    const b = state.buffer.subarray(state.start, state.end)
    state.start = state.end
    return b
  }
}

const buffer = (exports.buffer = {
  preencode(state, b) {
    if (b) uint8array.preencode(state, b)
    else state.end++
  },
  encode(state, b) {
    if (b) uint8array.encode(state, b)
    else state.buffer[state.start++] = 0
  },
  decode(state) {
    const b = state.buffer.subarray(state.start)
    if (b.byteLength === 0) return null
    state.start = state.end
    return b
  }
})

exports.binary = {
  ...buffer,
  preencode(state, b) {
    if (typeof b === 'string') utf8.preencode(state, b)
    else buffer.preencode(state, b)
  },
  encode(state, b) {
    if (typeof b === 'string') utf8.encode(state, b)
    else buffer.encode(state, b)
  }
}

exports.arraybuffer = {
  preencode(state, b) {
    state.end += b.byteLength
  },
  encode(state, b) {
    const view = new Uint8Array(b)

    state.buffer.set(view, state.start)
    state.start += b.byteLength
  },
  decode(state) {
    const b = new ArrayBuffer(state.end - state.start)
    const view = new Uint8Array(b)

    view.set(state.buffer.subarray(state.start))

    state.start = state.end

    return b
  }
}

function typedarray(TypedArray, swap) {
  const n = TypedArray.BYTES_PER_ELEMENT

  return {
    preencode(state, b) {
      state.end += b.byteLength
    },
    encode(state, b) {
      const view = new Uint8Array(b.buffer, b.byteOffset, b.byteLength)

      if (BE && swap) swap(view)

      state.buffer.set(view, state.start)
      state.start += b.byteLength
    },
    decode(state) {
      let b = state.buffer.subarray(state.start)
      if (b.byteOffset % n !== 0) b = new Uint8Array(b)

      if (BE && swap) swap(b)

      state.start = state.end

      return new TypedArray(b.buffer, b.byteOffset, b.byteLength / n)
    }
  }
}

const uint8array = (exports.uint8array = typedarray(Uint8Array))
exports.uint16array = typedarray(Uint16Array, b4a.swap16)
exports.uint32array = typedarray(Uint32Array, b4a.swap32)

exports.int8array = typedarray(Int8Array)
exports.int16array = typedarray(Int16Array, b4a.swap16)
exports.int32array = typedarray(Int32Array, b4a.swap32)

exports.biguint64array = typedarray(BigUint64Array, b4a.swap64)
exports.bigint64array = typedarray(BigInt64Array, b4a.swap64)

exports.float32array = typedarray(Float32Array, b4a.swap32)
exports.float64array = typedarray(Float64Array, b4a.swap64)

function string(encoding) {
  return {
    preencode(state, s) {
      state.end += b4a.byteLength(s, encoding)
    },
    encode(state, s) {
      state.start += b4a.write(state.buffer, s, state.start, encoding)
    },
    decode(state) {
      const s = b4a.toString(state.buffer, encoding, state.start)
      state.start = state.end
      return s
    }
  }
}

const utf8 = (exports.string = exports.utf8 = string('utf-8'))
exports.ascii = string('ascii')
exports.hex = string('hex')
exports.base64 = string('base64')
exports.ucs2 = exports.utf16le = string('utf16le')

exports.array = function array(enc) {
  return {
    preencode(state, list) {
      for (const value of list) enc.preencode(state, value)
    },
    encode(state, list) {
      for (const value of list) enc.encode(state, value)
    },
    decode(state) {
      const arr = []
      while (state.start < state.end) arr.push(enc.decode(state))
      return arr
    }
  }
}

exports.json = {
  preencode(state, v) {
    utf8.preencode(state, JSON.stringify(v))
  },
  encode(state, v) {
    utf8.encode(state, JSON.stringify(v))
  },
  decode(state) {
    return JSON.parse(utf8.decode(state))
  }
}

exports.ndjson = {
  preencode(state, v) {
    utf8.preencode(state, JSON.stringify(v) + '\n')
  },
  encode(state, v) {
    utf8.encode(state, JSON.stringify(v) + '\n')
  },
  decode(state) {
    return JSON.parse(utf8.decode(state))
  }
}
{
  "name": "autobee-encryption",
  "version": "0.1.3",
  "description": "autobee-encryption",
  "main": "index.js",
  "exports": {
    "./package": "./package.json",
    ".": {
      "default": "./index.js"
    }
  },
  "files": [
    "package.json",
    "index.js"
  ],
  "devDependencies": {
    "brittle": "^3.19.0",
    "lunte": "^1.2.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^2.0.0"
  },
  "scripts": {
    "format": "prettier . --write",
    "test": "prettier . --check && lunte && brittle-bare test/index.js",
    "lint": "lunte"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/holepunchto/autobee-encryption.git"
  },
  "author": "Holepunch Inc",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/autobee-encryption/issues"
  },
  "homepage": "https://github.com/holepunchto/autobee-encryption",
  "dependencies": {
    "b4a": "^1.8.0",
    "compact-encoding": "^2.19.0",
    "hypercore": "^11.27.14",
    "hypercore-crypto": "^3.6.1",
    "sodium-universal": "^5.0.1"
  }
}
const ProtomuxWakeup = require('protomux-wakeup')
const CoreCoupler = require('core-coupler')
const ReadyResource = require('ready-resource')
const b4a = require('b4a')

class WakeupHandler {
  constructor(wakeup, discoveryKey) {
    this.wakeup = wakeup

    this.active = true
    this.discoveryKey = discoveryKey
  }

  onpeeractive(peer, session) {
    this.wakeup._bumpWakeupPeer(peer)
  }

  onlookup(req, peer, session) {
    const wakeup = this.wakeup._getWakeupWriters()
    if (wakeup.length === 0) return
    session.announce(peer, wakeup)
  }

  onannounce(wakeup, peer, session) {
    this.wakeup.hint(wakeup)
  }
}

module.exports = class AutobeeWakeup extends ReadyResource {
  constructor(auto, opts = {}) {
    super()

    this._auto = auto
    this._owner = !opts.wakeup

    this._protocol = opts.wakeup || new ProtomuxWakeup()
    this._session = null
    this._coupler = null
    this._hints = new Map()

    this._needsWakeupRequest = false
    this._needsWakeup = true
    this._needsWakeupHeads = true
    this._wakeupPeerBound = this._wakeupPeer.bind(this)
  }

  _close() {
    if (this._session) this._session.destroy()
    if (this._owner) this._protocol.destroy()
  }

  get hints() {
    return this._hints
  }

  hint(hints) {
    if (!Array.isArray(hints)) hints = [hints]
    for (const { key, length } of hints) {
      const hex = b4a.toString(key, 'hex')
      const prev = this._hints.get(hex)
      if (!prev || length === -1 || prev < length) this._hints.set(hex, length)
    }
    this._auto.bumpSoon()
  }

  flush() {
    const hints = new Map(this._hints)
    this._hints.clear()
    return hints
  }

  addStream(stream) {
    this._protocol.addStream(stream)
  }

  setCapability(cap, discoveryKey) {
    if (this._session) this._session.destroy()
    this._session = this._protocol.session(cap, new WakeupHandler(this, discoveryKey || null))

    // incase this session has active peers already, bump the coupler
    for (const peer of this._session.peers) {
      if (peer.active) this._bumpWakeupPeer(peer)
    }
  }

  _bumpWakeupPeer(peer) {
    if (this._coupler) this._coupler.update(peer.stream)
  }

  _wakeupPeer(stream, cores) {
    if (!this._session) return
    // Don't drop a freshly-coupled local writer that isn't pending at this
    // instant; the peer never learns it otherwise.
    const wakeup = cores && cores.length ? cores : this._getWakeupWriters()
    if (wakeup.length === 0) return
    this._session.announceByStream(stream, wakeup)
  }

  _getWakeupWriters() {
    const writers = []
    for (const [_, w] of this._auto.writers.active) {
      if (w.isPending) continue
      writers.push(w.core)
    }

    return writers
  }

  recouple() {
    if (this._coupler) this._coupler.destroy()
    const core = this._auto.bootstrap
    this._coupler = new CoreCoupler(core, this._wakeupPeerBound)
  }

  addCore(core) {
    if (!this._coupler) return false
    this._coupler.add(core)
    return true
  }

  removeCore(core) {
    if (!this._coupler) return false
    this._coupler.remove(core)
    return true
  }
}
{
  "name": "autobee-wakeup",
  "version": "1.2.1",
  "description": "autobee-wakeup",
  "main": "index.js",
  "exports": {
    "./package": "./package.json",
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    }
  },
  "files": [
    "package.json",
    "index.js",
    "index.d.ts"
  ],
  "devDependencies": {
    "brittle": "^3.19.0",
    "corestore": "^7.9.1",
    "lunte": "^1.2.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^2.0.0"
  },
  "scripts": {
    "format": "prettier . --write",
    "test": "brittle-bare test/index.js",
    "lint": "prettier . --check && lunte"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/autobee-wakeup.git"
  },
  "author": "Holepunch Inc",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/autobee-wakeup/issues"
  },
  "homepage": "https://github.com/holepunchto/autobee-wakeup",
  "dependencies": {
    "b4a": "^1.8.0",
    "core-coupler": "^2.0.0",
    "protomux-wakeup": "^2.9.0",
    "ready-resource": "^1.2.0"
  }
}
const c = require('compact-encoding')

const Checkout = {
  preencode(state, m) {
    c.fixed32.preencode(state, m.key)
    c.uint.preencode(state, m.length)
  },
  encode(state, m) {
    c.fixed32.encode(state, m.key)
    c.uint.encode(state, m.length)
  },
  decode(state) {
    return {
      key: c.fixed32.decode(state),
      length: c.uint.decode(state)
    }
  }
}

const Clock = c.array(Checkout)

const IndexCheckpoint = {
  preencode(state, m) {
    c.fixed64.preencode(state, m.signature)
    c.uint.preencode(state, m.length)
  },
  encode(state, m) {
    c.fixed64.encode(state, m.signature)
    c.uint.encode(state, m.length)
  },
  decode(state) {
    return {
      signature: c.fixed64.decode(state),
      length: c.uint.decode(state)
    }
  }
}

const BootRecordV0 = {
  preencode() {
    throw new Error('version 0 records cannot be encoded')
  },
  encode() {
    throw new Error('version 0 records cannot be encoded')
  },
  decode(state) {
    const indexed = Checkout.decode(state)
    const heads = Clock.decode(state)

    // one cause initial recover is not ff recovery
    return {
      version: 0,
      key: indexed.key,
      systemLength: indexed.length,
      indexersUpdated: false,
      fastForwarding: false,
      recoveries: 1,
      migrating: false,
      heads
    }
  }
}

const Checkpointer = {
  preencode(state, idx) {
    c.uint.preencode(state, idx.checkpointer)
    if (idx.checkpoint !== null) IndexCheckpoint.preencode(state, idx.checkpoint)
  },
  encode(state, idx) {
    c.uint.encode(state, idx.checkpointer)
    if (idx.checkpoint !== null) IndexCheckpoint.encode(state, idx.checkpoint)
  },
  decode(state) {
    const checkpointer = c.uint.decode(state)
    const checkpoint = checkpointer ? null : IndexCheckpoint.decode(state)

    return {
      checkpointer,
      checkpoint
    }
  }
}

const CheckpointerArray = c.array(Checkpointer)

const Indexer = {
  preencode(state, m) {
    c.uint.preencode(state, m.signature)
    c.fixed32.preencode(state, m.namespace)
    c.fixed32.preencode(state, m.publicKey)
  },
  encode(state, m) {
    c.uint.encode(state, m.signature)
    c.fixed32.encode(state, m.namespace)
    c.fixed32.encode(state, m.publicKey)
  },
  decode(state) {
    return {
      signature: c.uint.decode(state),
      namespace: c.fixed32.decode(state),
      publicKey: c.fixed32.decode(state)
    }
  }
}

const Indexers = c.array(Indexer)

const DigestV0 = {
  preencode(state, m) {
    c.uint.preencode(state, m.pointer)
    if (m.pointer === 0) {
      Indexers.preencode(state, m.indexers)
    }
  },
  encode(state, m) {
    c.uint.encode(state, m.pointer)
    if (m.pointer === 0) {
      Indexers.encode(state, m.indexers)
    }
  },
  decode(state) {
    const pointer = c.uint.decode(state)
    return {
      pointer,
      indexers: pointer === 0 ? Indexers.decode(state) : null
    }
  }
}

const Digest = {
  preencode(state, m) {
    c.uint.preencode(state, m.pointer)
    if (m.pointer === 0) {
      c.fixed32.preencode(state, m.key)
    }
  },
  encode(state, m) {
    c.uint.encode(state, m.pointer)
    if (m.pointer === 0) {
      c.fixed32.encode(state, m.key)
    }
  },
  decode(state) {
    const pointer = c.uint.decode(state)
    return {
      pointer,
      key: pointer === 0 ? c.fixed32.decode(state) : null
    }
  }
}

const Node = {
  preencode(state, m) {
    Clock.preencode(state, m.heads)
    c.uint.preencode(state, m.batch)
    c.optionalBuffer.preencode(state, m.value)
  },
  encode(state, m) {
    Clock.encode(state, m.heads)
    c.uint.encode(state, m.batch)
    c.optionalBuffer.encode(state, m.value)
  },
  decode(state, m) {
    return {
      heads: Clock.decode(state),
      batch: c.uint.decode(state),
      value: c.optionalBuffer.decode(state)
    }
  }
}

const Additional = {
  preencode(state, m) {
    c.uint.preencode(state, m.pointer)
    if (m.pointer === 0) {
      AdditionalData.preencode(state, m.data)
    }
  },
  encode(state, m) {
    c.uint.encode(state, m.pointer)
    if (m.pointer === 0) {
      AdditionalData.encode(state, m.data)
    }
  },
  decode(state) {
    const pointer = c.uint.decode(state)
    return {
      pointer,
      data: pointer === 0 ? AdditionalData.decode(state) : null
    }
  }
}

const AdditionalData = {
  preencode(state, m) {
    c.uint.preencode(state, 0)
  },
  encode(state, m) {
    c.uint.encode(state, 0) // empty for now, for the future
  },
  decode(state) {
    const flags = c.uint.decode(state)
    return {
      encryptionId: flags & 1 ? c.fixed32.decode(state) : null, // to help validate the encryption key used
      abi: flags & 2 ? c.uint.decode(state) : 0
    }
  }
}

const OplogMessageV1 = {
  preencode(state, m) {
    throw new Error('Encoding not supported')
  },
  encode(state, m) {
    throw new Error('Encoding not supported')
  },
  decode(state) {
    c.uint.decode(state) // version
    const maxSupportedVersion = c.uint.decode(state)

    const flags = c.uint.decode(state)

    const isCheckpointer = (flags & 1) !== 0

    const chk = isCheckpointer ? CheckpointerArray.decode(state) : null
    const digest = isCheckpointer ? Digest.decode(state) : null

    const node = Node.decode(state)

    return {
      version: 1,
      maxSupportedVersion,
      digest,
      checkpoint: chk ? { system: chk[0], encryption: null, user: chk.slice(1) } : null,
      optimistic: (flags & 2) !== 0,
      node
    }
  }
}

const OplogMessageV0 = {
  preencode(state, m) {
    throw new Error('Encoding not supported')
  },
  encode(state, m) {
    throw new Error('Encoding not supported')
  },
  decode(state) {
    c.uint.decode(state) // version
    const flags = c.uint.decode(state)

    const isCheckpointer = (flags & 1) !== 0

    if (isCheckpointer) DigestV0.decode(state)
    const chk = isCheckpointer ? CheckpointerArray.decode(state) : null
    const node = Node.decode(state)
    Additional.decode(state)
    const maxSupportedVersion = state.start < state.end ? c.uint.decode(state) : 0

    return {
      version: 0,
      maxSupportedVersion,
      digest: null,
      checkpoint: chk ? { system: chk[0], encryption: null, user: chk.slice(1) } : null,
      optimistic: null,
      node
    }
  }
}

const SystemWriterV0 = {
  preencode(state, m) {
    throw new Error('Encoding not supported')
  },
  encode(state, m) {
    throw new Error('Encoding not supported')
  },
  decode(state) {
    const flags = c.uint.decode(state)

    return {
      version: 0,
      isIndexer: (flags & 1) !== 0,
      isRemoved: (flags & 2) !== 0,
      length: c.uint.decode(state)
    }
  }
}

function infoLegacyMap(info) {
  return {
    version: info.version,
    timestamp: 0,
    flushes: 0,
    view: null,
    views: info.views,
    heads: info.heads,
    indexers: info.indexers
  }
}

function memberLegacyMap(m) {
  const weight = m.isIndexer ? 2 : 1

  return {
    version: 0,
    isRemoved: m.isRemoved,
    isOplog: false,
    isGenesis: false,
    weight,
    maxWeight: weight,
    length: m.length,
    timestamp: 0
  }
}

// a v3 node's witness is a signed attestation, which confers weight through a
// mechanism that no longer exists - it maps to null, so the node sorts at the
// standing its record already carries instead of failing to decode
function oplogLegacyMap(m) {
  return {
    version: m.version,
    timestamp: m.timestamp,
    links: m.links,
    batch: m.batch,
    views: m.views,
    trusted: m.trusted,
    witness: null,
    approvals: null,
    optimistic: m.optimistic,
    value: m.value
  }
}

module.exports = {
  BootRecordV0,
  OplogMessageV0,
  OplogMessageV1,
  SystemWriterV0,
  infoLegacyMap,
  memberLegacyMap,
  oplogLegacyMap
}
// This file is autogenerated by the hyperschema compiler
// Schema Version: 1
/* eslint-disable camelcase */
/* eslint-disable quotes */
/* eslint-disable space-before-function-paren */

const { c } = require('hyperschema/runtime')
const external0 = require('../../legacy.js')

const VERSION = 1

// eslint-disable-next-line no-unused-vars
let version = VERSION

// array of bools, packed as a bitfield
const boolArray = {
  preencode(state, m) {
    c.uint.preencode(state, m.length)
    state.end += Math.ceil(m.length / 8)
  },
  encode(state, m) {
    c.uint.encode(state, m.length)
    for (let i = 0; i < m.length; i += 8) {
      let byte = 0
      for (let j = 0; j < 8 && i + j < m.length; j++) {
        if (m[i + j]) byte |= 1 << j
      }
      state.buffer[state.start++] = byte
    }
  },
  decode(state) {
    const n = c.uint.decode(state)
    if (state.end - state.start < Math.ceil(n / 8)) throw new Error('Out of bounds')
    const m = new Array(n)
    for (let i = 0; i < n; i += 8) {
      const byte = state.buffer[state.start++]
      for (let j = 0; j < 8 && i + j < n; j++) {
        m[i + j] = (byte & (1 << j)) !== 0
      }
    }
    return m
  }
}

// @autobase-compat/checkout
const encoding0 = {
  preencode(state, m) {
    c.fixed32.preencode(state, m.key)
    c.uint.preencode(state, m.length)
  },
  encode(state, m) {
    c.fixed32.encode(state, m.key)
    c.uint.encode(state, m.length)
  },
  decode(state) {
    const r0 = c.fixed32.decode(state)
    const r1 = c.uint.decode(state)

    return {
      key: r0,
      length: r1
    }
  }
}

// @autobase-compat/clock
const encoding1 = c.array(encoding0)

// @autobase-compat/index-checkpoint
const encoding2 = {
  preencode(state, m) {
    c.fixed64.preencode(state, m.signature)
    c.uint.preencode(state, m.length)
  },
  encode(state, m) {
    c.fixed64.encode(state, m.signature)
    c.uint.encode(state, m.length)
  },
  decode(state) {
    const r0 = c.fixed64.decode(state)
    const r1 = c.uint.decode(state)

    return {
      signature: r0,
      length: r1
    }
  }
}

const encoding3 = external0.BootRecordV0

// @autobase-compat/boot-record-raw
const encoding4 = {
  preencode(state, m) {
    c.fixed32.preencode(state, m.key)
    c.uint.preencode(state, m.systemLength)
    state.end++ // max flag is 4 so always one byte

    if (m.recoveries) c.uint.preencode(state, m.recoveries)
  },
  encode(state, m) {
    const flags = (m.indexersUpdated ? 1 : 0) | (m.fastForwarding ? 2 : 0) | (m.recoveries ? 4 : 0)

    c.fixed32.encode(state, m.key)
    c.uint.encode(state, m.systemLength)
    c.uint.encode(state, flags)

    if (m.recoveries) c.uint.encode(state, m.recoveries)
  },
  decode(state) {
    const v = c.uint.decode(state)
    const r0 = c.fixed32.decode(state)
    const r1 = c.uint.decode(state)
    const flags = c.uint.decode(state)

    return {
      version: v,
      key: r0,
      systemLength: r1,
      indexersUpdated: (flags & 1) !== 0,
      fastForwarding: (flags & 2) !== 0,
      recoveries: (flags & 4) !== 0 ? c.uint.decode(state) : 0
    }
  }
}

// @autobase-compat/boot-record
const encoding5 = {
  preencode(state, m) {
    const v = m.version ?? 3
    c.uint.preencode(state, v)
    switch (v) {
      case 0:
        encoding3.preencode(state, m)
        break
      case 1:
      case 2:
      case 3:
        encoding4.preencode(state, m)
        break
      default:
        throw new Error('Unsupported version')
    }
  },
  encode(state, m) {
    const v = m.version ?? 3
    c.uint.encode(state, v)
    switch (v) {
      case 0:
        encoding3.encode(state, m)
        break
      case 1:
      case 2:
      case 3:
        encoding4.encode(state, m)
        break
      default:
        throw new Error('Unsupported version')
    }
  },
  decode(state) {
    const start = state.start
    const v = c.uint.decode(state)
    state.start = start
    switch (v) {
      case 0: {
        const decoded = encoding3.decode(state)
        return decoded
      }
      case 1:
      case 2:
      case 3: {
        const decoded = encoding4.decode(state)
        return decoded
      }
      default:
        throw new Error('Unsupported version')
    }
  }
}

// @autobase-compat/checkpointer
const encoding6 = {
  preencode(state, m) {
    state.end++ // flags are fixed size

    if (m.checkpointer) c.uint.preencode(state, m.checkpointer)
    if (m.checkpoint) encoding2.preencode(state, m.checkpoint)
  },
  encode(state, m) {
    const flags = (m.checkpointer ? 1 : 0) | (m.checkpoint ? 2 : 0)

    c.uint8.encode(state, flags)

    if (m.checkpointer) c.uint.encode(state, m.checkpointer)
    if (m.checkpoint) encoding2.encode(state, m.checkpoint)
  },
  decode(state) {
    const flags = c.uint8.decode(state)

    return {
      checkpointer: (flags & 1) !== 0 ? c.uint.decode(state) : 0,
      checkpoint: (flags & 2) !== 0 ? encoding2.decode(state) : null
    }
  }
}

// @autobase-compat/checkpoint.user
const encoding7_2 = c.array(encoding6)

// @autobase-compat/checkpoint
const encoding7 = {
  preencode(state, m) {
    state.end++ // max flag is 4 so always one byte

    if (m.system) encoding6.preencode(state, m.system)
    if (m.encryption) encoding6.preencode(state, m.encryption)
    if (m.user) encoding7_2.preencode(state, m.user)
  },
  encode(state, m) {
    const flags = (m.system ? 1 : 0) | (m.encryption ? 2 : 0) | (m.user ? 4 : 0)

    c.uint.encode(state, flags)

    if (m.system) encoding6.encode(state, m.system)
    if (m.encryption) encoding6.encode(state, m.encryption)
    if (m.user) encoding7_2.encode(state, m.user)
  },
  decode(state) {
    const flags = c.uint.decode(state)

    return {
      system: (flags & 1) !== 0 ? encoding6.decode(state) : null,
      encryption: (flags & 2) !== 0 ? encoding6.decode(state) : null,
      user: (flags & 4) !== 0 ? encoding7_2.decode(state) : null
    }
  }
}

// @autobase-compat/digest
const encoding8 = {
  preencode(state, m) {
    state.end++ // max flag is 2 so always one byte

    if (m.pointer) c.uint.preencode(state, m.pointer)
    if (m.key) c.fixed32.preencode(state, m.key)
  },
  encode(state, m) {
    const flags = (m.pointer ? 1 : 0) | (m.key ? 2 : 0)

    c.uint.encode(state, flags)

    if (m.pointer) c.uint.encode(state, m.pointer)
    if (m.key) c.fixed32.encode(state, m.key)
  },
  decode(state) {
    const flags = c.uint.decode(state)

    return {
      pointer: (flags & 1) !== 0 ? c.uint.decode(state) : 0,
      key: (flags & 2) !== 0 ? c.fixed32.decode(state) : null
    }
  }
}

// @autobase-compat/node
const encoding9 = {
  preencode(state, m) {
    encoding1.preencode(state, m.heads)
    c.uint.preencode(state, m.batch)
    c.optionalBuffer.preencode(state, m.value)
  },
  encode(state, m) {
    encoding1.encode(state, m.heads)
    c.uint.encode(state, m.batch)
    c.optionalBuffer.encode(state, m.value)
  },
  decode(state) {
    const r0 = encoding1.decode(state)
    const r1 = c.uint.decode(state)
    const r2 = c.optionalBuffer.decode(state)

    return {
      heads: r0,
      batch: r1,
      value: r2
    }
  }
}

// @autobase-compat/user-view-trace.blocks
const encoding10_1 = c.array(c.uint)

// @autobase-compat/user-view-trace
const encoding10 = {
  preencode(state, m) {
    c.uint.preencode(state, m.view)
    encoding10_1.preencode(state, m.blocks)
  },
  encode(state, m) {
    c.uint.encode(state, m.view)
    encoding10_1.encode(state, m.blocks)
  },
  decode(state) {
    const r0 = c.uint.decode(state)
    const r1 = encoding10_1.decode(state)

    return {
      view: r0,
      blocks: r1
    }
  }
}

// @autobase-compat/trace.system
const encoding11_0 = encoding10_1
// @autobase-compat/trace.encryption
const encoding11_1 = encoding10_1
// @autobase-compat/trace.user
const encoding11_2 = c.array(encoding10)

// @autobase-compat/trace
const encoding11 = {
  preencode(state, m) {
    encoding11_0.preencode(state, m.system)
    encoding11_1.preencode(state, m.encryption)
    encoding11_2.preencode(state, m.user)
  },
  encode(state, m) {
    encoding11_0.encode(state, m.system)
    encoding11_1.encode(state, m.encryption)
    encoding11_2.encode(state, m.user)
  },
  decode(state) {
    const r0 = encoding11_0.decode(state)
    const r1 = encoding11_1.decode(state)
    const r2 = encoding11_2.decode(state)

    return {
      system: r0,
      encryption: r1,
      user: r2
    }
  }
}

const encoding12 = external0.OplogMessageV0

const encoding13 = external0.OplogMessageV1

// @autobase-compat/oplog-message-v2.checkpoint
const encoding14_1 = c.frame(encoding7)
// @autobase-compat/oplog-message-v2.digest
const encoding14_2 = c.frame(encoding8)
// @autobase-compat/oplog-message-v2.trace
const encoding14_4 = c.frame(encoding11)

// @autobase-compat/oplog-message-v2
const encoding14 = {
  preencode(state, m) {
    encoding9.preencode(state, m.node)
    state.end++ // max flag is 8 so always one byte

    if (m.checkpoint) encoding14_1.preencode(state, m.checkpoint)
    if (m.digest) encoding14_2.preencode(state, m.digest)
    if (m.trace) encoding14_4.preencode(state, m.trace)
  },
  encode(state, m) {
    const flags =
      (m.checkpoint ? 1 : 0) | (m.digest ? 2 : 0) | (m.optimistic ? 4 : 0) | (m.trace ? 8 : 0)

    encoding9.encode(state, m.node)
    c.uint.encode(state, flags)

    if (m.checkpoint) encoding14_1.encode(state, m.checkpoint)
    if (m.digest) encoding14_2.encode(state, m.digest)
    if (m.trace) encoding14_4.encode(state, m.trace)
  },
  decode(state) {
    const v = c.uint.decode(state)
    const r0 = encoding9.decode(state)
    const flags = c.uint.decode(state)

    return {
      version: v,
      node: r0,
      checkpoint: (flags & 1) !== 0 ? encoding14_1.decode(state) : null,
      digest: (flags & 2) !== 0 ? encoding14_2.decode(state) : null,
      optimistic: (flags & 4) !== 0,
      trace: (flags & 8) !== 0 ? encoding14_4.decode(state) : null
    }
  }
}

// @autobase-compat/info-v1.pendingIndexers
const encoding15_1 = c.array(c.fixed32)

// @autobase-compat/info-v1
const encoding15 = {
  preencode(state, m) {
    c.uint.preencode(state, m.members)
    encoding15_1.preencode(state, m.pendingIndexers)
    encoding1.preencode(state, m.indexers)
    encoding1.preencode(state, m.heads)
    encoding1.preencode(state, m.views)
  },
  encode(state, m) {
    c.uint.encode(state, m.members)
    encoding15_1.encode(state, m.pendingIndexers)
    encoding1.encode(state, m.indexers)
    encoding1.encode(state, m.heads)
    encoding1.encode(state, m.views)
  },
  decode(state) {
    const v = c.uint.decode(state)
    const r0 = c.uint.decode(state)
    const r1 = encoding15_1.decode(state)
    const r2 = encoding1.decode(state)
    const r3 = encoding1.decode(state)
    const r4 = encoding1.decode(state)

    return {
      version: v,
      members: r0,
      pendingIndexers: r1,
      indexers: r2,
      heads: r3,
      views: r4
    }
  }
}

// @autobase-compat/info-v2.pendingIndexers
const encoding16_1 = encoding15_1

// @autobase-compat/info-v2
const encoding16 = {
  preencode(state, m) {
    c.uint.preencode(state, m.members)
    encoding16_1.preencode(state, m.pendingIndexers)
    encoding1.preencode(state, m.indexers)
    encoding1.preencode(state, m.heads)
    encoding1.preencode(state, m.views)
    c.uint.preencode(state, m.encryptionLength)
    state.end++ // max flag is 1 so always one byte

    if (m.entropy) c.fixed32.preencode(state, m.entropy)
  },
  encode(state, m) {
    const flags = m.entropy ? 1 : 0

    c.uint.encode(state, m.members)
    encoding16_1.encode(state, m.pendingIndexers)
    encoding1.encode(state, m.indexers)
    encoding1.encode(state, m.heads)
    encoding1.encode(state, m.views)
    c.uint.encode(state, m.encryptionLength)
    c.uint.encode(state, flags)

    if (m.entropy) c.fixed32.encode(state, m.entropy)
  },
  decode(state) {
    const v = c.uint.decode(state)
    const r0 = c.uint.decode(state)
    const r1 = encoding16_1.decode(state)
    const r2 = encoding1.decode(state)
    const r3 = encoding1.decode(state)
    const r4 = encoding1.decode(state)
    const r5 = c.uint.decode(state)
    const flags = c.uint.decode(state)

    return {
      version: v,
      members: r0,
      pendingIndexers: r1,
      indexers: r2,
      heads: r3,
      views: r4,
      encryptionLength: r5,
      entropy: (flags & 1) !== 0 ? c.fixed32.decode(state) : null
    }
  }
}

const encoding17 = external0.SystemWriterV0

// @autobee/system-info-v3.pending
const encoding18_5 = boolArray

// @autobee/system-info-v3
const encoding18 = {
  preencode(state, m) {
    c.uint.preencode(state, m.timestamp)
    c.uint.preencode(state, m.flushes)
    encoding22.preencode(state, m.view)
    encoding18_3.preencode(state, m.heads)
    state.end++ // max flag is 2 so always one byte

    if (m.indexers) encoding18_4.preencode(state, m.indexers)
    if (m.pending) encoding18_5.preencode(state, m.pending)
  },
  encode(state, m) {
    const flags = (m.indexers ? 1 : 0) | (m.pending ? 2 : 0)

    c.uint.encode(state, m.timestamp)
    c.uint.encode(state, m.flushes)
    encoding22.encode(state, m.view)
    encoding18_3.encode(state, m.heads)
    c.uint.encode(state, flags)

    if (m.indexers) encoding18_4.encode(state, m.indexers)
    if (m.pending) encoding18_5.encode(state, m.pending)
  },
  decode(state) {
    const v = c.uint.decode(state)
    const r0 = c.uint.decode(state)
    const r1 = c.uint.decode(state)
    const r2 = encoding22.decode(state)
    const r3 = encoding18_3.decode(state)
    const flags = c.uint.decode(state)

    return {
      version: v,
      timestamp: r0,
      flushes: r1,
      view: r2,
      heads: r3,
      indexers: (flags & 1) !== 0 ? encoding18_4.decode(state) : null,
      pending: (flags & 2) !== 0 ? encoding18_5.decode(state) : null
    }
  }
}

// @autobee/system-info
const encoding19 = {
  preencode(state, m) {
    const v = m.version ?? 3
    c.uint.preencode(state, v)
    switch (v) {
      case 0:
      case 1:
        encoding15.preencode(state, m)
        break
      case 2:
        encoding16.preencode(state, m)
        break
      case 3:
        encoding18.preencode(state, m)
        break
      default:
        throw new Error('Unsupported version')
    }
  },
  encode(state, m) {
    const v = m.version ?? 3
    c.uint.encode(state, v)
    switch (v) {
      case 0:
      case 1:
        encoding15.encode(state, m)
        break
      case 2:
        encoding16.encode(state, m)
        break
      case 3:
        encoding18.encode(state, m)
        break
      default:
        throw new Error('Unsupported version')
    }
  },
  decode(state) {
    const start = state.start
    const v = c.uint.decode(state)
    state.start = start
    switch (v) {
      case 0:
      case 1: {
        const decoded = encoding15.decode(state)
        const map = external0.infoLegacyMap
        return map(decoded)
      }
      case 2: {
        const decoded = encoding16.decode(state)
        return decoded
      }
      case 3: {
        const decoded = encoding18.decode(state)
        return decoded
      }
      default:
        throw new Error('Unsupported version')
    }
  }
}

// @autobee/system-writer-v4
const encoding20 = {
  preencode(state, m) {
    state.end++ // max flag is 16 so always one byte
    c.uint.preencode(state, m.weight)
    c.uint.preencode(state, m.maxWeight)
    c.uint.preencode(state, m.length)
    c.uint.preencode(state, m.clock)

    if (m.timestamp) c.int.preencode(state, m.timestamp)
  },
  encode(state, m) {
    const flags =
      (m.isRemoved ? 1 : 0) |
      (m.isOplog ? 2 : 0) |
      (m.isGenesis ? 4 : 0) |
      (m.isAnchor ? 8 : 0) |
      (m.timestamp ? 16 : 0)

    c.uint.encode(state, flags)
    c.uint.encode(state, m.weight)
    c.uint.encode(state, m.maxWeight)
    c.uint.encode(state, m.length)
    c.uint.encode(state, m.clock)

    if (m.timestamp) c.int.encode(state, m.timestamp)
  },
  decode(state) {
    const v = c.uint.decode(state)
    const flags = c.uint.decode(state)

    return {
      version: v,
      isRemoved: (flags & 1) !== 0,
      isOplog: (flags & 2) !== 0,
      weight: c.uint.decode(state),
      maxWeight: c.uint.decode(state),
      length: c.uint.decode(state),
      clock: c.uint.decode(state),
      isGenesis: (flags & 4) !== 0,
      isAnchor: (flags & 8) !== 0,
      timestamp: (flags & 16) !== 0 ? c.int.decode(state) : 0
    }
  }
}

// @autobee/system-writer
const encoding21 = {
  preencode(state, m) {
    const v = m.version ?? 5
    c.uint.preencode(state, v)
    switch (v) {
      case 0:
      case 1:
      case 2:
      case 3:
        encoding17.preencode(state, m)
        break
      case 4:
        encoding20.preencode(state, m)
        break
      case 5:
        encoding34.preencode(state, m)
        break
      default:
        throw new Error('Unsupported version')
    }
  },
  encode(state, m) {
    const v = m.version ?? 5
    c.uint.encode(state, v)
    switch (v) {
      case 0:
      case 1:
      case 2:
      case 3:
        encoding17.encode(state, m)
        break
      case 4:
        encoding20.encode(state, m)
        break
      case 5:
        encoding34.encode(state, m)
        break
      default:
        throw new Error('Unsupported version')
    }
  },
  decode(state) {
    const start = state.start
    const v = c.uint.decode(state)
    state.start = start
    switch (v) {
      case 0:
      case 1:
      case 2:
      case 3: {
        const decoded = encoding17.decode(state)
        const map = external0.memberLegacyMap
        return map(decoded)
      }
      case 4: {
        const decoded = encoding20.decode(state)
        return decoded
      }
      case 5: {
        const decoded = encoding34.decode(state)
        return decoded
      }
      default:
        throw new Error('Unsupported version')
    }
  }
}

// @autobee/link
const encoding22 = encoding0

// @autobee/external-batch
const encoding23 = {
  preencode(state, m) {
    c.fixed32.preencode(state, m.key)
    c.uint.preencode(state, m.start)
    c.uint.preencode(state, m.length)
  },
  encode(state, m) {
    c.fixed32.encode(state, m.key)
    c.uint.encode(state, m.start)
    c.uint.encode(state, m.length)
  },
  decode(state) {
    const r0 = c.fixed32.decode(state)
    const r1 = c.uint.decode(state)
    const r2 = c.uint.decode(state)

    return {
      key: r0,
      start: r1,
      length: r2
    }
  }
}

// @autobee/batch
const encoding24 = {
  preencode(state, m) {
    c.uint.preencode(state, m.start)
    c.uint.preencode(state, m.end)
  },
  encode(state, m) {
    c.uint.encode(state, m.start)
    c.uint.encode(state, m.end)
  },
  decode(state) {
    const r0 = c.uint.decode(state)
    const r1 = c.uint.decode(state)

    return {
      start: r0,
      end: r1
    }
  }
}

// @autobee/views
const encoding25 = {
  preencode(state, m) {
    c.uint.preencode(state, m.flushes)
    encoding23.preencode(state, m.system)
    state.end++ // flags are fixed size

    if (m.view) encoding23.preencode(state, m.view)
  },
  encode(state, m) {
    const flags = m.view ? 1 : 0

    c.uint.encode(state, m.flushes)
    encoding23.encode(state, m.system)
    c.uint8.encode(state, flags)

    if (m.view) encoding23.encode(state, m.view)
  },
  decode(state) {
    const r0 = c.uint.decode(state)
    const r1 = encoding23.decode(state)
    const flags = c.uint8.decode(state)

    return {
      flushes: r0,
      system: r1,
      view: (flags & 1) !== 0 ? encoding23.decode(state) : null
    }
  }
}

// @autobee/views (inline)
const encoding25_inline = {
  preencode(state, m) {
    c.uint.preencode(state, m.flushes)
    encoding23.preencode(state, m.system)

    if (m.view) encoding23.preencode(state, m.view)
  },
  encode(state, m) {
    c.uint.encode(state, m.flushes)
    encoding23.encode(state, m.system)

    if (m.view) encoding23.encode(state, m.view)
  },
  decode(state, inlining) {
    const r0 = c.uint.decode(state)
    const r1 = encoding23.decode(state)
    const flags = inlining

    return {
      flushes: r0,
      system: r1,
      view: (flags & 1) !== 0 ? encoding23.decode(state) : null
    }
  }
}

// @autobee/signed-backer
const encoding26 = {
  preencode(state, m) {
    c.fixed32.preencode(state, m.key)
    c.uint.preencode(state, m.length)
    c.fixed64.preencode(state, m.signature)
    c.buffer.preencode(state, m.manifest)
  },
  encode(state, m) {
    c.fixed32.encode(state, m.key)
    c.uint.encode(state, m.length)
    c.fixed64.encode(state, m.signature)
    c.buffer.encode(state, m.manifest)
  },
  decode(state) {
    const r0 = c.fixed32.decode(state)
    const r1 = c.uint.decode(state)
    const r2 = c.fixed64.decode(state)
    const r3 = c.buffer.decode(state)

    return {
      key: r0,
      length: r1,
      signature: r2,
      manifest: r3
    }
  }
}

// @autobee/witness.backer
const encoding27_1 = c.frame(encoding26)

// @autobee/witness
const encoding27 = {
  preencode(state, m) {
    c.uint.preencode(state, m.weight)
    encoding27_1.preencode(state, m.backer)
  },
  encode(state, m) {
    c.uint.encode(state, m.weight)
    encoding27_1.encode(state, m.backer)
  },
  decode(state) {
    const r0 = c.uint.decode(state)
    const r1 = encoding27_1.decode(state)

    return {
      weight: r0,
      backer: r1
    }
  }
}

// @autobee/attestation
const encoding28 = {
  preencode(state, m) {
    c.fixed32.preencode(state, m.key)
    c.uint.preencode(state, m.weight)
    c.fixed64.preencode(state, m.signature)
  },
  encode(state, m) {
    c.fixed32.encode(state, m.key)
    c.uint.encode(state, m.weight)
    c.fixed64.encode(state, m.signature)
  },
  decode(state) {
    const r0 = c.fixed32.decode(state)
    const r1 = c.uint.decode(state)
    const r2 = c.fixed64.decode(state)

    return {
      key: r0,
      weight: r1,
      signature: r2
    }
  }
}

// @autobee/oplog-message-v3.witness
const encoding29_6 = c.frame(encoding27)
// @autobee/oplog-message-v3.attestations
const encoding29_7 = c.array(c.frame(encoding28))

// @autobee/oplog-message-v3
const encoding29 = {
  preencode(state, m) {
    let flags =
      (m.batch ? 1 : 0) |
      (m.views ? 2 : 0) |
      (m.optimistic ? 8 : 0) |
      (m.value ? 16 : 0) |
      (m.witness ? 32 : 0) |
      (m.attestations ? 64 : 0) |
      (m.trusted ? 128 : 0)
    if (m.views) {
      flags |= m.views.view ? 4 : 0
    }

    c.uint.preencode(state, m.timestamp)
    encoding29_1.preencode(state, m.links)
    c.uint.preencode(state, flags)

    if (m.batch) encoding24.preencode(state, m.batch)
    if (m.views) encoding25_inline.preencode(state, m.views)
    if (m.value) c.buffer.preencode(state, m.value)
    if (m.witness) encoding29_6.preencode(state, m.witness)
    if (m.attestations) encoding29_7.preencode(state, m.attestations)
    if (m.trusted) encoding29_8.preencode(state, m.trusted)
  },
  encode(state, m) {
    let flags =
      (m.batch ? 1 : 0) |
      (m.views ? 2 : 0) |
      (m.optimistic ? 8 : 0) |
      (m.value ? 16 : 0) |
      (m.witness ? 32 : 0) |
      (m.attestations ? 64 : 0) |
      (m.trusted ? 128 : 0)
    if (m.views) {
      flags |= m.views.view ? 4 : 0
    }

    c.uint.encode(state, m.timestamp)
    encoding29_1.encode(state, m.links)
    c.uint.encode(state, flags)

    if (m.batch) encoding24.encode(state, m.batch)
    if (m.views) encoding25_inline.encode(state, m.views)
    if (m.value) c.buffer.encode(state, m.value)
    if (m.witness) encoding29_6.encode(state, m.witness)
    if (m.attestations) encoding29_7.encode(state, m.attestations)
    if (m.trusted) encoding29_8.encode(state, m.trusted)
  },
  decode(state) {
    const v = c.uint.decode(state)
    const r0 = c.uint.decode(state)
    const r1 = encoding29_1.decode(state)
    const flags = c.uint.decode(state)

    return {
      version: v,
      timestamp: r0,
      links: r1,
      batch: (flags & 1) !== 0 ? encoding24.decode(state) : null,
      views: (flags & 2) !== 0 ? encoding25_inline.decode(state, flags >>> 2) : null,
      optimistic: (flags & 8) !== 0,
      value: (flags & 16) !== 0 ? c.buffer.decode(state) : null,
      witness: (flags & 32) !== 0 ? encoding29_6.decode(state) : null,
      attestations: (flags & 64) !== 0 ? encoding29_7.decode(state) : null,
      trusted: (flags & 128) !== 0 ? encoding29_8.decode(state) : null
    }
  }
}

// @autobee/oplog
const encoding30 = {
  preencode(state, m) {
    const v = m.version ?? 4
    c.uint.preencode(state, v)
    switch (v) {
      case 0:
        encoding12.preencode(state, m)
        break
      case 1:
        encoding13.preencode(state, m)
        break
      case 2:
        encoding14.preencode(state, m)
        break
      case 3:
        encoding29.preencode(state, m)
        break
      case 4:
        encoding37.preencode(state, m)
        break
      default:
        throw new Error('Unsupported version')
    }
  },
  encode(state, m) {
    const v = m.version ?? 4
    c.uint.encode(state, v)
    switch (v) {
      case 0:
        encoding12.encode(state, m)
        break
      case 1:
        encoding13.encode(state, m)
        break
      case 2:
        encoding14.encode(state, m)
        break
      case 3:
        encoding29.encode(state, m)
        break
      case 4:
        encoding37.encode(state, m)
        break
      default:
        throw new Error('Unsupported version')
    }
  },
  decode(state) {
    const start = state.start
    const v = c.uint.decode(state)
    state.start = start
    switch (v) {
      case 0: {
        const decoded = encoding12.decode(state)
        return decoded
      }
      case 1: {
        const decoded = encoding13.decode(state)
        return decoded
      }
      case 2: {
        const decoded = encoding14.decode(state)
        return decoded
      }
      case 3: {
        const decoded = encoding29.decode(state)
        const map = external0.oplogLegacyMap
        return map(decoded)
      }
      case 4: {
        const decoded = encoding37.decode(state)
        return decoded
      }
      default:
        throw new Error('Unsupported version')
    }
  }
}

// @autobee/manifest-data
const encoding31 = {
  preencode(state, m) {
    c.uint.preencode(state, m.version)
    state.end++ // max flag is 2 so always one byte

    if (m.legacyBlocks) c.uint.preencode(state, m.legacyBlocks)
    if (m.namespace) c.fixed32.preencode(state, m.namespace)
  },
  encode(state, m) {
    const flags = (m.legacyBlocks ? 1 : 0) | (m.namespace ? 2 : 0)

    c.uint.encode(state, m.version)
    c.uint.encode(state, flags)

    if (m.legacyBlocks) c.uint.encode(state, m.legacyBlocks)
    if (m.namespace) c.fixed32.encode(state, m.namespace)
  },
  decode(state) {
    const r0 = c.uint.decode(state)
    const flags = c.uint.decode(state)

    return {
      version: r0,
      legacyBlocks: (flags & 1) !== 0 ? c.uint.decode(state) : 0,
      namespace: (flags & 2) !== 0 ? c.fixed32.decode(state) : null
    }
  }
}

// @autobee/trusted-head
const encoding32 = {
  preencode(state, m) {
    c.fixed32.preencode(state, m.key)
    c.uint.preencode(state, m.length)
    c.uint.preencode(state, m.flushes)
  },
  encode(state, m) {
    c.fixed32.encode(state, m.key)
    c.uint.encode(state, m.length)
    c.uint.encode(state, m.flushes)
  },
  decode(state) {
    const r0 = c.fixed32.decode(state)
    const r1 = c.uint.decode(state)
    const r2 = c.uint.decode(state)

    return {
      key: r0,
      length: r1,
      flushes: r2
    }
  }
}

// @autobee/migrated-head
const encoding33 = {
  preencode(state, m) {
    encoding22.preencode(state, m.system)
    state.end++ // flags are fixed size

    if (m.view) encoding22.preencode(state, m.view)
  },
  encode(state, m) {
    const flags = m.view ? 1 : 0

    encoding22.encode(state, m.system)
    c.uint8.encode(state, flags)

    if (m.view) encoding22.encode(state, m.view)
  },
  decode(state) {
    const r0 = encoding22.decode(state)
    const flags = c.uint8.decode(state)

    return {
      system: r0,
      view: (flags & 1) !== 0 ? encoding22.decode(state) : null
    }
  }
}

// @autobee/system-writer-v5
const encoding34 = {
  preencode(state, m) {
    state.end++ // max flag is 4 so always one byte
    c.uint.preencode(state, m.weight)
    c.uint.preencode(state, m.maxWeight)
    c.uint.preencode(state, m.length)
    c.uint.preencode(state, m.timestamp)
  },
  encode(state, m) {
    const flags = (m.isRemoved ? 1 : 0) | (m.isOplog ? 2 : 0) | (m.isGenesis ? 4 : 0)

    c.uint.encode(state, flags)
    c.uint.encode(state, m.weight)
    c.uint.encode(state, m.maxWeight)
    c.uint.encode(state, m.length)
    c.uint.encode(state, m.timestamp)
  },
  decode(state) {
    const v = c.uint.decode(state)
    const flags = c.uint.decode(state)

    return {
      version: v,
      isRemoved: (flags & 1) !== 0,
      isOplog: (flags & 2) !== 0,
      isGenesis: (flags & 4) !== 0,
      weight: c.uint.decode(state),
      maxWeight: c.uint.decode(state),
      length: c.uint.decode(state),
      timestamp: c.uint.decode(state)
    }
  }
}

// @autobee/grant-witness
const encoding35 = {
  preencode(state, m) {
    c.uint.preencode(state, m.weight)
    encoding22.preencode(state, m.link)
  },
  encode(state, m) {
    c.uint.encode(state, m.weight)
    encoding22.encode(state, m.link)
  },
  decode(state) {
    const r0 = c.uint.decode(state)
    const r1 = encoding22.decode(state)

    return {
      weight: r0,
      link: r1
    }
  }
}

// @autobee/approval
const encoding36 = {
  preencode(state, m) {
    c.fixed32.preencode(state, m.key)
    c.uint.preencode(state, m.weight)
  },
  encode(state, m) {
    c.fixed32.encode(state, m.key)
    c.uint.encode(state, m.weight)
  },
  decode(state) {
    const r0 = c.fixed32.decode(state)
    const r1 = c.uint.decode(state)

    return {
      key: r0,
      weight: r1
    }
  }
}

// @autobee/oplog-message-v4.approvals
const encoding37_6 = c.array(encoding36)

// @autobee/oplog-message-v4
const encoding37 = {
  preencode(state, m) {
    c.uint.preencode(state, m.timestamp)
    encoding37_1.preencode(state, m.links)
    state.end++ // max flag is 64 so always one byte

    if (m.batch) encoding24.preencode(state, m.batch)
    if (m.views) encoding25.preencode(state, m.views)
    if (m.trusted) encoding37_4.preencode(state, m.trusted)
    if (m.witness) encoding35.preencode(state, m.witness)
    if (m.approvals) encoding37_6.preencode(state, m.approvals)
    if (m.value) c.buffer.preencode(state, m.value)
  },
  encode(state, m) {
    const flags =
      (m.batch ? 1 : 0) |
      (m.views ? 2 : 0) |
      (m.trusted ? 4 : 0) |
      (m.witness ? 8 : 0) |
      (m.approvals ? 16 : 0) |
      (m.optimistic ? 32 : 0) |
      (m.value ? 64 : 0)

    c.uint.encode(state, m.timestamp)
    encoding37_1.encode(state, m.links)
    c.uint.encode(state, flags)

    if (m.batch) encoding24.encode(state, m.batch)
    if (m.views) encoding25.encode(state, m.views)
    if (m.trusted) encoding37_4.encode(state, m.trusted)
    if (m.witness) encoding35.encode(state, m.witness)
    if (m.approvals) encoding37_6.encode(state, m.approvals)
    if (m.value) c.buffer.encode(state, m.value)
  },
  decode(state) {
    const v = c.uint.decode(state)
    const r0 = c.uint.decode(state)
    const r1 = encoding37_1.decode(state)
    const flags = c.uint.decode(state)

    return {
      version: v,
      timestamp: r0,
      links: r1,
      batch: (flags & 1) !== 0 ? encoding24.decode(state) : null,
      views: (flags & 2) !== 0 ? encoding25.decode(state) : null,
      trusted: (flags & 4) !== 0 ? encoding37_4.decode(state) : null,
      witness: (flags & 8) !== 0 ? encoding35.decode(state) : null,
      approvals: (flags & 16) !== 0 ? encoding37_6.decode(state) : null,
      optimistic: (flags & 32) !== 0,
      value: (flags & 64) !== 0 ? c.buffer.decode(state) : null
    }
  }
}

// @autobee/system-info-v3.heads, deferred due to recusive use
const encoding18_3 = c.array(encoding22)
// @autobee/system-info-v3.indexers, deferred due to recusive use
const encoding18_4 = encoding18_3
// @autobee/oplog-message-v3.links, deferred due to recusive use
const encoding29_1 = encoding18_3
// @autobee/oplog-message-v3.trusted, deferred due to recusive use
const encoding29_8 = c.array(c.frame(encoding32))
// @autobee/oplog-message-v4.links, deferred due to recusive use
const encoding37_1 = encoding18_3
// @autobee/oplog-message-v4.trusted, deferred due to recusive use
const encoding37_4 = encoding18_3

function setVersion(v) {
  version = v
}

function encode(name, value, v = VERSION) {
  version = v
  return c.encode(getEncoding(name), value)
}

function decode(name, buffer, v = VERSION) {
  version = v
  return c.decode(getEncoding(name), buffer)
}

function getEnum(name) {
  switch (name) {
    default:
      throw new Error('Enum not found ' + name)
  }
}

function getEncoding(name) {
  switch (name) {
    case '@autobase-compat/checkout':
      return encoding0
    case '@autobase-compat/clock':
      return encoding1
    case '@autobase-compat/index-checkpoint':
      return encoding2
    case '@autobase-compat/boot-record-v0':
      return encoding3
    case '@autobase-compat/boot-record-raw':
      return encoding4
    case '@autobase-compat/boot-record':
      return encoding5
    case '@autobase-compat/checkpointer':
      return encoding6
    case '@autobase-compat/checkpoint':
      return encoding7
    case '@autobase-compat/digest':
      return encoding8
    case '@autobase-compat/node':
      return encoding9
    case '@autobase-compat/user-view-trace':
      return encoding10
    case '@autobase-compat/trace':
      return encoding11
    case '@autobase-compat/oplog-message-v0':
      return encoding12
    case '@autobase-compat/oplog-message-v1':
      return encoding13
    case '@autobase-compat/oplog-message-v2':
      return encoding14
    case '@autobase-compat/info-v1':
      return encoding15
    case '@autobase-compat/info-v2':
      return encoding16
    case '@autobase-compat/member':
      return encoding17
    case '@autobee/system-info-v3':
      return encoding18
    case '@autobee/system-info':
      return encoding19
    case '@autobee/system-writer-v4':
      return encoding20
    case '@autobee/system-writer':
      return encoding21
    case '@autobee/link':
      return encoding22
    case '@autobee/external-batch':
      return encoding23
    case '@autobee/batch':
      return encoding24
    case '@autobee/views':
      return encoding25
    case '@autobee/signed-backer':
      return encoding26
    case '@autobee/witness':
      return encoding27
    case '@autobee/attestation':
      return encoding28
    case '@autobee/oplog-message-v3':
      return encoding29
    case '@autobee/oplog':
      return encoding30
    case '@autobee/manifest-data':
      return encoding31
    case '@autobee/trusted-head':
      return encoding32
    case '@autobee/migrated-head':
      return encoding33
    case '@autobee/system-writer-v5':
      return encoding34
    case '@autobee/grant-witness':
      return encoding35
    case '@autobee/approval':
      return encoding36
    case '@autobee/oplog-message-v4':
      return encoding37
    default:
      throw new Error('Encoder not found ' + name)
  }
}

function getStruct(name, v = VERSION) {
  const enc = getEncoding(name)
  return {
    preencode(state, m) {
      version = v
      enc.preencode(state, m)
    },
    encode(state, m) {
      version = v
      enc.encode(state, m)
    },
    decode(state) {
      version = v
      return enc.decode(state)
    }
  }
}

const resolveStruct = getStruct // compat

module.exports = {
  resolveStruct,
  getStruct,
  getEnum,
  getEncoding,
  encode,
  decode,
  setVersion,
  version
}
const ReadyResource = require('ready-resource')
const ReadyGuard = require('ready-guard')
const b4a = require('b4a')
const safetyCatch = require('safety-catch')
const Hyperbee = require('hyperbee2')
const ID = require('hypercore-id-encoding')
const rrp = require('resolve-reject-promise')
const { AutobeeEncryption, WriterEncryption, ViewEncryption } = require('autobee-encryption')
const AutobeeWakeup = require('autobee-wakeup')
const Hypercore = require('hypercore')
const crypto = require('hypercore-crypto')
const c = require('compact-encoding')
const asserts = require('./lib/asserts.js')
const boot = require('./lib/boot.js')
const { resolveWeight, currentWeight } = require('./lib/witness.js')
const encoding = require('./lib/encoding.js')
const { LEGACY_OPLOG_VERSION } = require('./lib/constants.js')
const FastForward = require('./lib/fast-forward.js')
const System = require('./lib/system.js')
const ApplyCalls = require('./lib/apply-calls.js')
const topo = require('./lib/topo.js')
const { ActiveWriters } = require('./lib/writers.js')
const TrustedPeers = require('./lib/trusted.js')
const ApplyView = require('./lib/apply-view.js')
const UpdateChanges = require('./lib/updates.js')
const migrations = require('./lib/migrations.js')

const EMPTY_HEAD = { length: 0, key: null }
const INTERRUPT = new Error('Apply interrupted')

module.exports = class Autobee extends ReadyResource {
  constructor(store, key = null, handlers = {}) {
    super()

    if (isObject(key)) {
      handlers = key
      key = null
    }

    const {
      name = null,
      encrypted,
      encryptionKey,
      viewName = 'view',
      bootstrapWeight = 2
    } = handlers

    this.encrypted = encrypted === true || !!encryptionKey
    this.bootstrapWeight = bootstrapWeight

    this.getSystemEncryption = this._getEncryptionProvider.bind(this, '_system')
    this.getViewEncryption = this._getEncryptionProvider.bind(this, viewName)

    const beeStore = store.session()
    const bee = new Hyperbee(beeStore, {
      core: beeStore.get({ preload: this._getCorePreload(viewName) }),
      // defer one tick to ensure consistent state, then return state prom
      preload: async () => {
        await 1
        await this._bootGuard.ready()
      },
      getEncryptionProvider: this.getViewEncryption
    })

    this.store = store

    this.key = key ? ID.decode(key) : null
    this.discoveryKey = null
    this.id = null
    this.bootstrap = null
    this._handlers = handlers
    this.stats = { undos: 0, fastForwards: 0, drains: 0, applies: 0, appends: 0 }

    const systemStore = this.store.session()
    this.system = new System(systemStore, this.name, {
      core: systemStore.get({ preload: this._getCorePreload('system') }),
      encrypted: this.encrypted,
      getEncryptionProvider: this.getSystemEncryption
    })

    this.bee = bee.snapshot()
    this.view = ApplyView.open(this.bee, this)
    this.optimistic = handlers.optimistic !== false // TODO: should default to false instead

    this.name = name // for debugging

    this.local = null
    this.encryptionKey = null
    this.keyPair = null
    this.writers = null
    this.bumping = 0

    // fastForward: false disables all fast-forwards - both wakeup and boot
    const fastForward = handlers.fastForward === false ? null : handlers.fastForward || {}

    this._ffEnabled = fastForward !== null

    // oplog head to boot from: migrates or fast-forwards depending on its version
    this.bootFrom = (fastForward && fastForward.boot) || null

    // conservative (default on): only fast-forward onto a head someone can serve whole
    this._conservativeFF = !fastForward || fastForward.conservative !== false

    this.trusted = new TrustedPeers(handlers)

    this.ff = null
    this.fastForwarding = null
    this.fastForwardTo = null

    this._workingBee = bee
    this._workingView = new ApplyView(this._workingBee, this)

    this._localSystemStart = 0
    this._localSystemLength = 0
    this._localFlushes = 0
    this._localViewStart = 0
    this._localViewLength = 0

    this._appending = []
    this._draining = null
    this._updating = null
    this._bootWait = null

    this.legacyViews = handlers.legacyViews || []

    // the guards may be destroyed before anyone waits on them
    this._bootGuard = new ReadyGuard()
    this._bootGuard.ready().catch(safetyCatch)
    this._bootOnlineGuard = new ReadyGuard()
    this._bootOnlineGuard.ready().catch(safetyCatch)

    this._now = handlers.now || Date.now // overridable for clock-drift tests
    this._preapply = handlers.preapply || null
    this._preApplied = false
    this._hasApply = !!handlers.apply
    this._hasUpdate = !!handlers.update
    this._needsUpdate = false
    this._approvalCheck = true
    this._approvedPending = new Map()
    this._prefetchingApprovals = false
    this._updateLocalCore = null
    this._host = new ApplyCalls(this)
    this._notifyHandler = null

    this.interrupted = null
    this._interrupting = false
    this._onErrorBound = this._onError.bind(this)
    this._bumpSoonBound = this.bumpSoon.bind(this)
    this._onGroupUpdateBound = this._onGroupUpdate.bind(this)

    this.wakeupCapability = null
    this._wakeup = new AutobeeWakeup(this, handlers)
    this.previousDrain = 0

    this._catchupMigratedNodes = null
    this._migratedHead = null
    this._prebooting = null

    this.ready().catch(noop)
  }

  static GENESIS = EMPTY_HEAD

  static isAutobee(auto) {
    return auto instanceof Autobee
  }

  get isIndexer() {
    return this.writers.localWriter.isIndexer
  }

  get writable() {
    return this.writers.writable
  }

  // autobase compat
  get activeWriters() {
    return this.writers
  }

  get flushes() {
    return this.system.flushes
  }

  get busy() {
    return !!(this._draining || this._updating)
  }

  async _getCorePreload(name) {
    await 1
    const result = await this._prebooting
    return {
      name: 'autobee/' + result.local.id + '/' + name,
      encryption: name === 'system' ? this.getSystemEncryption() : this.getViewEncryption(),
      inflightRange: [256, 512]
    }
  }

  // network free: only a migration needs peers, so ready() awaits the full
  // state boot unless one is pending and defers it to the background if so -
  // anything needing the booted system must wait on the boot guard
  async _open() {
    this._prebooting = this._preBoot()

    let result = null

    try {
      result = await this._prebooting

      this.key = result.key
      this.bootstrap = result.bootstrap
      this.discoveryKey = result.bootstrap.core.discoveryKey
      this.id = result.bootstrap.core.id
      this.encryptionKey = result.encryptionKey
      this.previousDrain = result.previousDrain
      this.local = result.local

      if (this.encrypted && this.encryptionKey === null) {
        throw new Error('Encryption key is expected')
      }

      this.local.setEncryption(this._getEncryptionProvider())
      this.writers = new ActiveWriters(this)
    } catch (err) {
      // unblock the guard awaiters (bee preloads, bumps, wakeups, flushes)
      this._bootGuard.destroy(err)
      this._bootOnlineGuard.destroy(err)
      throw err
    }

    const booting = this._boot()

    // waits on the boot guard internally and destroys its own guard on failure
    this._bootOnline()

    // if migrating, we might need network io, if not lets wait on it - less surprises
    if (result.migration) booting.catch(this._onErrorBound)
    else await booting
  }

  _requestWakeup() {
    const session = this._wakeup._session
    if (session) session.broadcastLookup()
  }

  _registerWakeup() {
    if (!this.wakeupCapability) return
    this._wakeup.recouple()
    this._wakeup.setCapability(this.wakeupCapability.key, this.wakeupCapability.discoveryKey)
  }

  getExternalWriters() {
    const keys = []
    for (const w of this.writers.active.values()) {
      if (w === this.writers.localWriter) continue
      keys.push(w.core.key)
    }
    return keys
  }

  getWriterViews(key) {
    const id = b4a.toString(key, 'hex')
    const w = this.writers.active.get(id)
    if (!w) return []
    return w.views()
  }

  static getViewEncryption(bootstrap, encryptionKey, name) {
    return AutobeeEncryption.getViewEncryption(bootstrap, encryptionKey, name)
  }

  views() {
    const sys = this.system.bee.context.local
    const view = this._workingBee.context.local

    // todo: figure out why blind-peer doesn't mirror core
    // without adding it to mirror request here
    const head = this.system.bee.head()

    // signedLength for autobase compat
    return [
      { key: sys.key, length: sys.length, signedLength: sys.length },
      { key: view.key, length: view.length, signedLength: view.length },
      { key: head.key, length: head.length, signedLength: head.length }
    ]
  }

  async _close() {
    this._interrupting = true

    try {
      await ApplyView.close(this.view, this)
    } catch (err) {
      safetyCatch(err)
    }

    if (this.writers) await this.writers.close()
    await this.local.close()
    await this.system.close()
    await this._wakeup.close()
    if (this.bootstrap) await this.bootstrap.close()

    try {
      await this._workingView.close()
      await this.bee.close()
    } catch (err) {
      safetyCatch(err)
    }

    // don't rely on the store teardown to stop the notify watcher
    if (this._notifyHandler) this._notifyHandler.destroy()

    // rugpull the rest
    await this.store.close()

    if (this._bootWait !== null) this._bootWait.resolve()
    if (this._updating) await this._updating
    if (this._draining) await this._draining

    // let in-flight writer adds finish
    try {
      await this._bootOnlineGuard.ready()
    } catch {}
  }

  replicate(...args) {
    const stream = this.store.replicate(...args)
    this._wakeup.addStream(stream)
    return stream
  }

  async flush() {
    await this._bootOnlineGuard.ready()
  }

  hintWakeup(wakeup) {
    this._wakeup.hint(wakeup)
  }

  // the view an oplog head points at, opened and closed through the handlers
  openViewAt(oplog) {
    const v = oplog.op.views.view
    if (!v) return null

    return new ApplyView(this.bee.checkout({ key: v.key, length: v.start + v.length }), this)
  }

  openCore(key) {
    const encryption = this.encryptionKey ? new WriterEncryption(this) : null
    return this.store.get({ key, encryption })
  }

  _getEncryptionProvider(view) {
    if (!this.encrypted) return null
    if (view) return new ViewEncryption(this, view)
    return new WriterEncryption(this)
  }

  getMostRecentHead() {
    return topo.getMostRecentHead(this, this.system.bee.snapshot())
  }

  async _preBoot() {
    if (this._handlers.wait) await this._handlers.wait()

    await this.store.ready()

    if (this._handlers.encryptionKey) {
      this.encryptionKey = await this._handlers.encryptionKey
    }

    if (this._handlers.keyPair) {
      this.keyPair = await this._handlers.keyPair
    }

    if (this.bootFrom) {
      this.bootFrom = getBootOption(await this.bootFrom)
    }

    return boot(this.store, this.key, this.legacyViews, {
      encryptionKey: this.encryptionKey,
      keyPair: this.keyPair
    })
  }

  async _boot() {
    if (!this._bootGuard.enter()) return this._bootGuard.ready()

    try {
      await this._bootUnsafe()
    } catch (err) {
      this._bootGuard.destroy(err)
      throw err
    }

    this._bootGuard.exit()

    this.bumpSoon()

    return this._bootGuard.ready()
  }

  async _bootUnsafe() {
    const result = await this._prebooting

    if (this._handlers.wakeupCapability) {
      this.wakeupCapability = await this._handlers.wakeupCapability
    } else {
      this.wakeupCapability = { key: this.key, discoveryKey: this.discoveryKey }
    }

    this._registerWakeup()

    if (this.wakeupCapability) {
      if (this.bootstrap !== this.local && !this.store.storage.readOnly) {
        await this.bootstrap.setGroup(this.wakeupCapability.discoveryKey)
      }

      this._notifyHandler = this.store.notifyGroup(this.wakeupCapability.discoveryKey)
      this._notifyHandler.on('update', this._onGroupUpdateBound)
      await this._drainBootHints()
    }

    const system = result.system || EMPTY_HEAD

    await this.system.boot(system)

    const migrated = await this.local.getUserData('autobee/migrated-head')
    if (migrated) this._migratedHead = encoding.decodeMigratedHead(migrated)

    let view = this.system.view
    if (!view) view = (this._migratedHead && this._migratedHead.view) || EMPTY_HEAD

    // @todo migration
    if (result.migration) {
      if (this._handlers.migrate) {
        view =
          (await this._handlers.migrate(result.migration.views, result.migration.system)) ||
          EMPTY_HEAD
        this._catchupMigratedNodes = result.migration.catchup

        this._migratedHead = {
          system: result.migration.system,
          view: view.length ? view : (this._migratedHead && this._migratedHead.view) || null
        }

        await this._storeMigratedHead()
      }

      // ff boot invalidated by migration
      this.bootFrom = null

      // clear legacy data
      await this.bootstrap.setUserData('autobase/local', null)
      await this.local.setUserData('autobase/boot', null)
      await this.local.setUserData('autobase/encryption', null)

      for (const batch of result.migration.catchup) {
        const { key, length } = batch[batch.length - 1]
        this.writers.wakeup(key, length)
      }
    }

    // since we are doing this DURING the hyperbee2 boot we need to ready the core
    // be good if we had better plumbing for it
    await this._workingBee.core.ready()
    await this.bee.core.ready()

    this._workingBee.move(view)
    this.bee.move(view)

    await this.writers.updateLocalState()

    // roots are moved, so these resolve without re-entering the boot guard
    await this.bee.ready()
    await this._workingBee.ready()

    // baseline before the guard opens so an early drain can't flush against stale offsets
    this._localSystemStart = this.system.bee.context.local.length
    this._localViewStart = this._workingBee.context.local.length
    this._localFlushes = this.system.flushes
  }

  async _bootOnline() {
    if (!this._bootOnlineGuard.enter()) return

    try {
      if (!this._bootGuard.opened) await this._bootGuard.ready()

      for (const head of this.system.heads) {
        await this.writers.add(head.key)
      }

      if (!this.system.heads.length) {
        await this.writers.add(this.bootstrap.key)
      }

      await this._bump(true)
    } catch (err) {
      this._bootOnlineGuard.destroy(err)
      return
    }

    this._bootOnlineGuard.exit()
  }

  bumpSoon() {
    this._bump(false).catch(safetyCatch)
  }

  async _bump(force) {
    if (!force && !this._bootGuard.opened) await this._bootGuard.ready()

    // resolve before the bootAll gate: a parked boot-from retries on any bump,
    // and its drain is the one _bootOnline itself is waiting on
    if (this._bootWait !== null) this._bootWait.resolve()

    if (!force && !this._bootOnlineGuard.opened) await this._bootOnlineGuard.ready()

    this.bumping++

    if (!this._draining) {
      this._draining = this._drain().catch(this._onErrorBound)
    }

    return this._draining
  }

  update() {
    return this._bump(false)
  }

  async updated() {
    if (!this._bootGuard.opened) await this._bootGuard.ready()
    if (this._draining) return this._draining
    return Promise.resolve()
  }

  interrupt(reason) {
    asserts.assert(!!this._host.applying, 'Interrupt is only allowed in apply')
    this._interrupting = true
    if (reason) this.interrupted = reason
    throw INTERRUPT
  }

  getLastError() {
    return this._lastError
  }

  _onError(err) {
    if (this.closing) return

    this._lastError = err

    if (err === INTERRUPT) {
      this.emit('interrupt', this.interrupted)
      this.emit('update')
      return
    }

    this.close().catch(safetyCatch)

    // if no one is listening we should crash! we cannot rely on the EE here
    // as this is wrapped in a promise so instead of nextTick throw it
    if (ReadyResource.listenerCount(this, 'error') === 0) {
      crashSoon(err)
      return
    }

    this.emit('error', err)
  }

  // one-shot user gate: nothing applies until the host has resolved whatever
  // state apply depends on (e.g. legacy views recorded by a migration)
  async _runPreApply() {
    if (this._preapply === null || this._preApplied) return

    this._preApplied = true
    await this._preapply(this.view)
  }

  async _drain() {
    if (this._updating) await this._updating

    await this._runPreApply()

    this.stats.drains++

    if (this.bootFrom) {
      const { head = null, legacy = null, bootCondition = null, wait = false } = this.bootFrom

      this.bootFrom = null

      if (legacy) await this._bootFromSystem(legacy)
      else if (head) await this._bootFromHead(head, bootCondition, wait)
    }

    const changes = this._hasUpdate ? new UpdateChanges(this) : null
    if (changes) changes.track()

    // Anything expecting work to be done during bumpSoon should do it here
    while (!this._interrupting && this.bumping > 0) {
      if (this._interrupting) break

      // Ensure we catch updates during the drain (i.e. setLocal will bump)
      if (this._updateLocalCore !== null) {
        await this._rotateLocalWriter(this._updateLocalCore)
      }

      try {
        while (!this._interrupting) {
          await this._flushWakeup()
          if (this._interrupting) break

          if (this.fastForwardTo !== null) {
            await this._applyFastForward()
            break // revaluate conditions...
          }

          if (!(await this._bumpPendingWriters())) break
          this._needsUpdate = true
        }

        await this._flushLocal()

        if (!this._interrupting) await this.writers.refresh()
      } finally {
        if (this.bumping === 1) this.bumping = 0
        else this.bumping = 1
      }
    }

    this._draining = null
    if (this._interrupting) return

    const updating = rrp()
    this._updating = updating.promise

    try {
      if (this._needsUpdate) await this._update(changes)
      await this._storeBoot()
    } finally {
      this._updating = null
      updating.resolve()
    }
  }

  _onGroupUpdate({ key, length }) {
    this._wakeup.hint({ key, length })
    this.bumpSoon()
  }

  async _drainBootHints() {
    if (!this._notifyHandler) return

    const keys = []
    for await (const key of this._notifyHandler.updates({ since: this.previousDrain })) {
      keys.push(key)
    }
    if (!keys.length) return

    // read the lengths straight from storage in one batch instead of opening cores
    const discoveryKeys = keys.map((key) => crypto.discoveryKey(key))
    const infos = await this.store.storage.getInfos(discoveryKeys, {
      auth: false,
      head: true,
      hints: false
    })

    for (let i = 0; i < keys.length; i++) {
      const info = infos[i]
      const length = info && info.head ? info.head.length : 0
      this._wakeup.hint({ key: keys[i], length })
    }
  }

  // wakeup hints keep arriving asynchronously
  async _applyWakeupHints() {
    const hints = this._wakeup.flush()
    if (!hints.size) return []

    this.previousDrain = Date.now()
    const results = await this._filterHints(hints)

    for (const { key, length } of results) {
      // wakeup() itself no-ops the add for an already-active writer, but still
      // needs to run so it can hint() the announced length - skipping it here
      // for active writers left gc with no hint to protect them
      await this.writers.wakeup(key, length)
    }

    return results
  }

  async _filterHints(hints) {
    const results = []
    const promises = []
    for (const [hex, length] of hints) {
      const key = b4a.from(hex, 'hex')
      promises.push(this._filterTracked(key, length, results))
    }
    await Promise.all(promises)
    return results
  }

  async _filterTracked(key, length, results) {
    if (length) {
      const info = await this.system.get(key)
      // if we've seen this already, ignore
      if (info && info.length >= length) return
    }
    results.push({ key, length })
  }

  async _flushWakeup() {
    const hints = await this._applyWakeupHints()
    if (!hints.length) return

    if (!this._ffEnabled) return

    // a scheduled fast-forward is applied by the drain before we look again
    if (this.fastForwardTo !== null || this.fastForwarding !== null) return
    if (this._interrupting || this.closing || this.bootFrom) return

    try {
      const heads = await this._readCandidateHeads(hints, FastForward.DEFAULT_TIMEOUT)

      const ff = await FastForward.fromHeads(this, heads, {
        timeout: FastForward.DEFAULT_TIMEOUT
      })

      if (ff !== null) await this._runFastForward(ff)
    } catch (err) {
      safetyCatch(err)
    }
  }

  async _readCandidateHeads(hints, timeout) {
    const promises = []

    for (const { key, length } of hints) {
      if (length === 0) continue
      promises.push(this._resolveOplogHint(key, length, timeout ? { timeout } : null))
    }

    const ops = await Promise.all(promises)
    const heads = []

    for (const res of ops) {
      if (res === null) continue

      // the head we were woken on is a candidate in its own right
      heads.push({ key: res.key, length: res.length })

      if (!res.op.trusted) continue

      for (const trusted of res.op.trusted) {
        const head = this.trusted.read(trusted)
        if (head !== null) heads.push(head)
      }
    }

    return heads
  }

  async _resolveOplogHint(key, length, opts) {
    const core = this.openCore(key)

    try {
      await core.ready()
      const max = Math.max(length, core.length)

      const block = await this.readOplog(core, max, opts)
      if (max >= core.length) return block // best one

      return await this.readOplog(core, core.length, opts)
    } finally {
      await core.close()
    }
  }

  async _getOplog(key, length, opts = null) {
    const core = this.openCore(key)

    try {
      return await this.readOplog(core, length, opts)
    } finally {
      await core.close()
    }
  }

  // reads on a core the caller owns, so a walk does not churn a session per step
  async readOplog(core, length, opts = null) {
    await core.ready()

    const { conservative = false, timeout } = opts === null ? {} : opts

    const target = length >= 0 ? length : core.length
    if (target === 0) return null

    // conservative: only proceed when a connected peer can serve the head whole
    if (conservative && core.remoteContiguousLength < target) return null

    const buf = await core.get(target - 1, { timeout })
    if (buf === null) return null

    let op = encoding.decodeOplog(buf)

    // legacy nodes always inflate, but only indexers carry views - callers
    // that need system info must check op.views
    if (op.version < 3) {
      op = await migrations.inflateLegacyOplog(buf, core, target - 1, timeout)
    }

    return {
      key: core.key,
      length: target,
      op
    }
  }

  async _update(changes) {
    this._needsUpdate = false
    this.bee.update(this._workingBee.root)

    if (!changes) return

    changes.finalise()
    await this._handlers.update(this.view, changes)
  }

  async setLocal(key, { keyPair } = {}) {
    if (!this._bootGuard.opened) await this._bootGuard.ready()
    if (this.closing) throw new Error('Autobee closed')

    const manifest = keyPair
      ? { version: this.store.manifestVersion, signers: [{ publicKey: keyPair.publicKey }] }
      : null
    if (!key) key = Hypercore.key(manifest)
    // If the keys are the same, no need to rotate
    if (b4a.equals(key, this.local.key)) return

    const encryption = this.encryptionKey ? this._getEncryptionProvider() : null

    const local = this.store.get({
      key,
      manifest,
      active: false,
      exclusive: true,
      encryption
    })
    await local.ready()

    this._updateLocalCore = local

    let runs = 0
    while (!this._interrupting && this.appending && runs++ < 16) await this.update()
    this.bumpSoon()
  }

  async _rotateLocalWriter(newLocal) {
    asserts.assert(!this.appending, 'Cannot rotate a newLocal writer if an append is in progress')

    const oldLocal = this.local

    this.local = newLocal
    await this.writers.rotateLocalWriter(this.local)

    this._updateLocalCore = null

    this.local.setUserData('referrer', this.key)
    if (this.encryptionKey) {
      await this.local.setUserData('autobee/encryption', this.encryptionKey)
    }

    await this.bootstrap.setUserData('autobee/local', this.local.key)
    await oldLocal.close()

    // done, soft restart
    this.emit('rotate-local-writer')
  }

  async createAnchor(key, length) {
    let node = null
    for (let i = this._host.applying.length - 1; i >= 0; i--) {
      const n = this._host.applying[i]
      if (b4a.equals(n.key, key) && n.length === length) {
        node = n
        break
      }
    }

    if (!node) throw new Error('Anchor node is not in system')

    const legacy = node.version <= 2

    const info = await this.system.get(key, { unflushed: true })
    if (!info || info.length < length) throw new Error('Anchor node is not in system')

    const state = { start: 0, end: 40, buffer: b4a.alloc(40) }
    c.fixed32.encode(state, key)
    c.uint64.encode(state, length)

    const namespace = crypto.hash(state.buffer)
    const manifestData = c.encode(encoding.ManifestData, { version: 0, legacyBlocks: 0, namespace })

    const padding = this.encryptionKey ? AutobeeEncryption.PADDING : 0
    const links = [{ key, length }]

    const block = Autobee.encodeValue(null, {
      legacy,
      timestamp: 0,
      links,
      heads: links, // legacy compat
      padding
    })

    if (this.encryptionKey) {
      AutobeeEncryption.encryptAnchor(block, this.key, this.encryptionKey, namespace)
    }

    const root = { index: 0, size: block.byteLength, hash: crypto.data(block) }
    const hash = crypto.tree([root])
    const prologue = { hash, length: 1 }

    const core = createAnchorCore(this.store, prologue, manifestData)
    await core.ready()

    if (core.length === 0) {
      await core.append(block, { writable: true, maxLength: 1 })
    }

    await this.system.addAnchor(core.key)

    const anchor = { key: core.key, length: core.length }

    await core.close()

    return anchor
  }

  async _bumpMigratedWriters() {
    const opened = new Set()
    let updated = false

    for (const batch of this._catchupMigratedNodes) {
      await this._processBatch(batch)
      updated = true
      for (const node of batch) {
        if (node.from) opened.add(node.from)
      }
    }

    for (const core of opened) await core.close()
    return updated
  }

  async _prefetchApprovals() {
    if (this._prefetchingApprovals) return
    this._prefetchingApprovals = true

    try {
      const prefetch = []
      const standing = await this._standing()
      if (standing <= 0 || !this._pendingWork(standing)) return
      for await (const p of this._servableRequests(standing)) {
        prefetch.push(this.system.get(p.key).catch(safetyCatch))
        prefetch.push(this.system.grantHint(p.key).catch(safetyCatch))
      }
      await Promise.allSettled(prefetch)
    } finally {
      this._prefetchingApprovals = false
    }
  }

  async _standing() {
    const rec = await this.system.get(this.local.key)
    return currentWeight(rec)
  }

  // only our own tiers: a request whose lower tiers are already anchored is
  // somebody stronger's job, and must not keep every peer at this standing
  // rescanning (or offering) while it waits
  _pendingWork(standing) {
    const digest = this.system.promotions.digest
    const end = Math.min(standing, digest.length)
    for (let w = 1; w <= end; w++) {
      if (digest[w - 1]) return true
    }
    return false
  }

  // the requests we could serve and by how much - shared by the prefetch and
  // the collector so the two cannot drift on what counts as ours
  async *_servableRequests(standing) {
    for await (const p of this.system.listPendingPromotions()) {
      const amount = Math.min(standing, p.weight)
      if (amount <= 0) continue
      yield { key: p.key, amount }
    }
  }

  async _collectApprovals() {
    if (!this.system.promotions.changed && !this._approvalCheck) return null
    if (!this.writers.writable) return null

    const standing = await this._standing()
    if (standing <= 0) return null

    this.system.promotions.changed = false
    this._approvalCheck = false

    if (!this._pendingWork(standing)) return null

    const approvals = []
    const approving = []

    for await (const p of this._servableRequests(standing)) {
      approving.push(fetchApproval.call(this, p.key, p.amount, approvals))
    }

    if (!approving.length) return null

    try {
      await Promise.all(approving)
    } catch (err) {
      if (this._interrupting) return null
      throw err
    }

    if (!approvals.length) return null

    for (const a of approvals) {
      this._approvedPending.set(b4a.toString(a.key, 'hex'), a.weight)
    }

    return approvals

    async function fetchApproval(key, weight, approvals) {
      const hex = b4a.toString(key, 'hex')
      if ((this._approvedPending.get(hex) || 0) >= weight) return
      const [target, hint] = await Promise.all([this.system.get(key), this.system.grantHint(key)])
      if (!target || target.isRemoved) return
      if (hint && hint.weight >= weight) return
      approvals.push({ key, weight })
    }
  }

  async _bumpPendingWriters({ local = false } = {}) {
    if (!local && this._catchupMigratedNodes !== null) {
      const updated = await this._bumpMigratedWriters()
      this._catchupMigratedNodes = null
      if (updated) return true
    }

    // apply the best next node to keep the prefix stable
    const next = await this.writers.nextPendingNode({ local })
    if (next === null) return false

    const { writer: w, batch } = next

    if (w.isAdded || (w.isRemoved && w.hasReferrals())) {
      await this._processBatch(batch)
      w.notify(batch)
      return true
    }

    if (this.optimistic && !w.isRemoved && batch[0].optimistic) {
      if (!(await this._optimisticBatch(batch))) {
        w.removePending()
        return true
      }
      w.notify(batch)
      return true
    }

    return true
  }

  async _optimisticBatch(batch) {
    const rollbackSystem = this.system.bee.head()
    const rollbackView = this._workingBee.head()

    const t = await this.prepareBatch(batch)
    if (t.view) this._workingBee.move(t.view)

    for (const b of t.tip) {
      let failed = true
      try {
        if (await this.system.canApply(b[0].key, true)) {
          await this._applyBatch(b, true)
          failed = false
        }
      } catch {}

      // only check if batch was successful
      if (b !== batch) continue

      const w = failed ? null : await this.system.get(b[0].key)
      if (!w || w.length < b[0].length) {
        this._workingBee.move(rollbackView)
        this.system.bee.move(rollbackSystem)
        await this.system.reset()
        return false
      }
    }

    return true
  }

  async prepareBatch(batch) {
    const node = batch[0]
    // recomputed on every application, converges because prefixes converge
    node.weight = await resolveWeight(this, node)
    for (const n of batch) n.weight = node.weight

    // if (topo.isLinkingAll(node, this.system.heads)) {
    //   return { undo: null, view: null, tip: [batch] }
    // }

    const t = await topo.sort(this, batch)

    if (t.undo) {
      this.stats.undos++
      this.trusted.clear()
      t.view = await this.system.undo(t.undo)
      if (!t.view.length && this._migratedHead && this._migratedHead.view) {
        t.view = this._migratedHead.view
      }
    }

    return t
  }

  async applyBacklog(batches) {
    const queue = batches.slice()

    while (queue.length) {
      const batch = queue.shift()
      const t = await this.prepareBatch(batch)

      if (t.view) {
        this._workingBee.move(t.view)
        queue.unshift(...t.tip)
        continue
      }

      if (b4a.equals(batch[0].key, this.key) && batch[0].length === 1) {
        await this._host.addWriter(batch[0].key, { weight: this.bootstrapWeight })
      }

      await this._applyBatch(batch, batch[0].optimistic)

      this.writers.triggers.trigger(
        b4a.toString(batch[0].key, 'hex'),
        batch[batch.length - 1].length
      )
    }
  }

  async _processBatch(batch) {
    await this.applyBacklog([batch])
  }

  async _applyBatch(batch, optimistic) {
    const local = batch[0].core === this.local

    const userBatch = []
    for (const node of batch) {
      this.system.addNode(node)

      if (node.approvals) {
        for (const a of node.approvals) {
          await this.system.addWriter(a.key, {
            weight: a.weight,
            coord: { key: node.key, length: node.length },
            carrier: node.weight,
            anchor: true
          })
        }
      }

      // compat: autobase nodes may be null (legacy null decodes to 0-length buffer)
      if (node.value && node.value.length) userBatch.push(node)
    }

    if (this._hasApply && (await this.system.canApply(batch[0].key, optimistic))) {
      this.stats.applies++
      this._host.applying = batch
      await this._workingView.apply(userBatch)
      this._host.applying = null
    }

    const changed = await this.system.flush(batch, this._workingBee)

    if (this.system.promotions.changed) this._prefetchApprovals().catch(safetyCatch)

    if (local) {
      this._localSystemLength = this.system.bee.context.local.length - this._localSystemStart
      this._localViewLength = this._workingBee.context.local.length - this._localViewStart
      this._localFlushes = this.system.flushes
    }

    for (const { key, added } of changed) {
      if (added) await this.writers.add(key)
      else await this.writers.remove(key)
    }
  }

  _storeBoot() {
    const proms = []
    proms.push(
      this.local.setUserData(
        'autobee/previous-drain',
        encoding.encodePreviousDrain(this.previousDrain)
      )
    )

    const boot = this.system.bootRecord()
    if (boot) {
      proms.push(this.local.setUserData('autobee/head', encoding.encodeBootRecord(boot)))
    }

    return Promise.all(proms)
  }

  _storeMigratedHead() {
    const value = this._migratedHead ? encoding.encodeMigratedHead(this._migratedHead) : null
    return this.local.setUserData('autobee/migrated-head', value)
  }

  static decodeValue(buf, opts) {
    return encoding.decodeValue(buf, opts)
  }

  static encodeValue(value, opts) {
    return encoding.encodeValue(value, opts)
  }

  // gates on the state boot only, so hints can still feed a parked boot-from -
  // the trailing bump waits for bootAll like any other drain
  async wakeup({ key, length }) {
    if (!this._bootGuard.opened) await this._bootGuard.ready()
    await this.writers.wakeup(key, length)
    await this._bump(false)
  }

  async append(values, { optimistic = false } = {}) {
    if (this.closing) throw new Error('Autobee closed')

    this.stats.appends++

    if (!Array.isArray(values)) values = [values]

    if (!this._bootGuard.opened) await this._bootGuard.ready()

    if (!optimistic && this.writers.localWriter.isRemoved) {
      throw new Error('Not writable')
    }

    await this.local.ready()

    const links = this.system.getLinks(this.local.key)

    // never stamp before anything we link
    const t = Math.max(this._now(), this.system.timestamp)
    const batch = []

    const rec = await this.system.get(this.local.key)
    let witness = null
    if (rec) {
      const hint = await this.system.grantHint(this.local.key)
      if (hint && hint.weight > currentWeight(rec)) {
        witness = { weight: hint.weight, link: { key: hint.key, length: hint.length } }
      }
    }

    const approvals = optimistic ? null : await this._collectApprovals()

    for (let i = 0; i < values.length; i++) {
      const value = values[i]
      const buffer = typeof value === 'string' ? b4a.from(value) : value
      const lnk = i === 0 ? links : []
      const node = this.writers.appendLocal(
        buffer,
        t,
        { start: i, end: values.length - 1 - i },
        lnk,
        optimistic,
        i === 0 ? witness : null,
        i === 0 ? approvals : null
      )
      batch.push(node)
    }

    return this._bump(false)
  }

  async _flushLocal() {
    // pull any available local nodes in before flushing
    while (!this._interrupting && (await this._bumpPendingWriters({ local: true }))) {
      this._needsUpdate = true
    }

    await this.writers.flushLocal({
      flushes: this._localFlushes,
      system: {
        key: this.system.bee.context.local.key,
        start: this._localSystemStart,
        length: this._localSystemLength
      },
      view: {
        key: this._workingBee.context.local.key,
        start: this._localViewStart,
        length: this._localViewLength
      }
    })

    this._localSystemStart = this.system.bee.context.local.length
    this._localSystemLength = 0
    this._localFlushes = this.system.flushes
    this._localViewStart = this._workingBee.context.local.length
    this._localViewLength = 0
  }

  async moveTo(head) {
    if (!this._bootGuard.opened) await this._bootGuard.ready()
    if (this.closing) throw new Error('Autobee closed')

    const ff = await FastForward.fromHead(this, head, null, { force: true })
    if (ff === null) return null

    if (!(await this._runFastForward(ff))) return null

    return this.ff.promise
  }

  // legacy: ungated boot straight onto a system head
  async _bootFromSystem(system) {
    try {
      const ff = new FastForward(this, system, { timeout: FastForward.DEFAULT_TIMEOUT })
      return await this._runFastForward(ff)
    } catch (err) {
      safetyCatch(err)
      return false
    }
  }

  // the boot head seeds the view every trust decision here is made against,
  // since our own view is still empty
  async _bootFromHead(head, bootCondition, wait = false) {
    // just a head: one attempt, we do not wait around if it cannot be read
    if (bootCondition === null && !wait) {
      try {
        const ff = await FastForward.fromHead(this, head, null, {
          force: true,
          timeout: FastForward.DEFAULT_TIMEOUT
        })

        return ff !== null && (await this._runFastForward(ff))
      } catch (err) {
        safetyCatch(err)
        return false
      }
    }

    // park until the head can be read and, if a condition is set, something
    // acceptable turns up
    let opened = null

    try {
      while (!this._interrupting) {
        this._bootWait = rrp()

        try {
          if (bootCondition === null) {
            const ff = await FastForward.fromHead(this, head, null, {
              force: true,
              timeout: FastForward.DEFAULT_TIMEOUT
            })

            if (ff !== null && (await this._runFastForward(ff))) return true
          } else {
            if (opened === null) opened = await this._bootReference(head)

            if (opened !== null) {
              if (await this._bootAttempt(head, bootCondition, opened.view)) {
                return true
              }

              // the reference may be stale - resolve it afresh next attempt
              await opened.close()
              opened = null
            } else if (await this._bootLegacyHead(head)) {
              // boot legacy head through the migration pathway
              return true
            }
          }
        } catch (err) {
          safetyCatch(err)
        }

        if (this._interrupting) break

        await this._bootWait.promise
      }
    } finally {
      this._bootWait = null
      if (opened !== null) await opened.close()
    }

    return false
  }

  async _bootAttempt(head, bootCondition, reference) {
    // peek, so the hints are still there for the drain once we have booted
    const result = await this._filterHints(this._wakeup.hints)

    const candidates = await this._readCandidateHeads(result, FastForward.DEFAULT_TIMEOUT)

    const ff = await FastForward.fromHeads(this, [head, ...candidates], {
      force: true,
      timeout: FastForward.DEFAULT_TIMEOUT,
      condition: bootCondition,
      reference
    })

    return ff !== null && (await this._runFastForward(ff))
  }

  async _bootLegacyHead(head) {
    const oplog = await FastForward.flushHead(this, head, {
      timeout: FastForward.DEFAULT_TIMEOUT
    })
    if (oplog === null || oplog.op.version > LEGACY_OPLOG_VERSION) return false

    const ff = await FastForward.fromHead(this, head, null, {
      force: true,
      timeout: FastForward.DEFAULT_TIMEOUT
    })

    return ff !== null && (await this._runFastForward(ff))
  }

  async _bootReference(head) {
    const oplog = await FastForward.flushHead(this, head, {
      timeout: FastForward.DEFAULT_TIMEOUT
    })

    return oplog === null ? null : this.openViewAt(oplog)
  }

  async _runFastForward(ff) {
    if (this.fastForwardTo !== null || this.fastForwarding !== null) {
      await ff.close()
      return false
    }

    this.fastForwarding = ff

    const result = await ff.run()
    await ff.close()

    if (this.fastForwarding === ff) this.fastForwarding = null

    if (!result) return false

    this.fastForwardTo = result
    this.ff = rrp()

    this.bumpSoon()

    return true
  }

  async _applyFastForward() {
    const changes = this._hasUpdate ? new UpdateChanges(this) : null
    if (changes) changes.track()

    const { head, migrate } = this.fastForwardTo

    const from = this.system.bee.head()
    const to = head

    this.system.bee.move(head)
    await this.system.reset()

    // migrate is set when fast-forwarding from a legacy head
    if (migrate) {
      const view = (await this._handlers.migrate(migrate, head)) || EMPTY_HEAD

      this._migratedHead = {
        system: head,
        view: view.length ? view : (this._migratedHead && this._migratedHead.view) || null
      }

      await this._storeMigratedHead()
      this.bee.move(view)
      this._workingBee.move(view)
    } else {
      this.bee.move(this.system.view)
      this._workingBee.move(this.system.view)
    }

    this._approvalCheck = true
    this.fastForwardTo = null

    // process any wakeup while fast-forward itself was in flight
    await this._applyWakeupHints()
    await this.writers.refresh()

    // we moved, so ask our peers to tell us their heads again
    this._requestWakeup()

    await this._update(changes)
    await this._storeBoot()

    this.stats.fastForwards++
    this.emit('move-to', to, from)
    this.ff.resolve({ to, from })
  }

  replay() {
    return topo.replay(this)
  }
}

function isObject(o) {
  return typeof o === 'object' && o && !b4a.isBuffer(o)
}

function getBootOption(boot) {
  if (!boot) return null

  // oldest style, supported for now but will go away: a bare key is legacy
  if (boot.key) return { legacy: boot, head: null, bootCondition: null }

  asserts.assert(!(boot.head && boot.legacy), 'Boot from either a head or a legacy pointer')

  return boot
}

function noop() {}

function createAnchorCore(store, prologue, manifestData) {
  const manifest = {
    version: 2,
    hash: 'blake2b',
    prologue,
    allowPatch: false,
    quorum: 0,
    signers: [],
    userData: manifestData,
    linked: null
  }

  const core = store.get({
    manifest,
    active: false
  })

  return core
}

function crashSoon(err) {
  queueMicrotask(() => {
    throw err
  })
  throw err
}
const ID = require('hypercore-id-encoding')

class ApplyCalls {
  constructor(auto) {
    this.auto = auto
    this.applying = null
  }

  get id() {
    return this.auto.id
  }

  get key() {
    return this.auto.key
  }

  get discoveryKey() {
    return this.auto.discoveryKey
  }

  get name() {
    return this.auto.name
  }

  get clock() {
    return this.auto.system.flushes
  }

  get genesis() {
    return this.auto.system.isGenesis()
  }

  addWriter(key, { isIndexer = true, weight = isIndexer ? 2 : 1 } = {}) {
    if (typeof key === 'string') key = ID.decode(key)
    // no witness needed for genesis
    const isGenesis = !this.applying
    const coord = this.applying
      ? { key: this.applying[0].key, length: this.applying[0].length }
      : null
    const carrier = this.applying ? this.applying[0].weight : -1
    return this.auto.system.addWriter(key, { weight, isGenesis, coord, carrier })
  }

  ackWriter(key) {
    if (typeof key === 'string') key = ID.decode(key)
    return this.auto.system.ackWriter(key)
  }

  removeWriter(key) {
    if (typeof key === 'string') key = ID.decode(key)
    return this.auto.system.removeWriter(key)
  }

  preferFastForward() {
    // this.auto._preferFastForward()
  }

  interrupt(reason) {
    this.auto.interrupt(reason)
  }

  removeable(key) {
    return true
  }

  createAnchor(key, length) {
    return this.auto.createAnchor(key, length)
  }
}

module.exports = ApplyCalls
module.exports = class ApplyView {
  constructor(bee, auto) {
    this.bee = bee
    this.auto = auto
    this.view = ApplyView.open(bee, auto)
  }

  static open(bee, auto) {
    return auto._handlers.open ? auto._handlers.open(bee, auto) : bee
  }

  static close(view, auto) {
    if (!auto._handlers.close) return Promise.resolve()
    return auto._handlers.close(view)
  }

  apply(userBatch) {
    return this.auto._handlers.apply(userBatch, this.view, this.auto._host)
  }

  async close() {
    await ApplyView.close(this.view, this.auto)
    await this.bee.close()
  }
}
exports.heads = function (heads, node) {
  for (const h of this.heads) {
    if (h.key.equals(node.key)) {
      exports.bail('Same node included twice')
    }
  }
}

exports.systemFlush = function (w) {
  for (const op of w.ops) {
    if (op.applied && op.key[0] === 1) return
  }

  exports.bail('Invalid system batch')
}

exports.assert = function (cond, msg) {
  if (!cond) exports.bail(msg)
}

exports.bail = function (msg) {
  const err = new Error('ERR_ASSERTION: ' + msg)
  err.code = 'ERR_ASSERTION'
  throw err
}
const b4a = require('b4a')
const encoding = require('./encoding.js')
const { checkAutobaseMigration } = require('./migrations.js')

module.exports = async function boot(
  corestore,
  key,
  legacyViews,
  { encrypt, encryptionKey, keyPair, exclusive = true } = {}
) {
  const result = {
    key: null,
    local: null,
    bootstrap: null,
    encryptionKey: null,
    system: null,
    flushes: null,
    previousDrain: 0,
    migration: null
  }

  const manifest = keyPair
    ? { version: corestore.manifestVersion, signers: [{ publicKey: keyPair.publicKey }] }
    : null

  if (key) {
    result.key = key

    const bootstrap = corestore.get({ key })
    await bootstrap.ready()

    const localKey = await getLocalKey(bootstrap)

    if (keyPair) {
      result.local = corestore.get({
        keyPair,
        active: false,
        exclusive,
        manifest
      })
    } else {
      if (bootstrap.writable && !localKey) {
        result.local = bootstrap.session({
          active: false,
          exclusive
        })
      } else {
        const local = localKey
          ? corestore.get({
              key: localKey,
              active: false,
              exclusive
            })
          : corestore.get({
              name: 'local',
              active: false,
              exclusive
            })

        await local.ready()
        result.local = local
      }
    }

    result.bootstrap = bootstrap
  } else {
    result.local = keyPair
      ? corestore.get({
          keyPair,
          manifest,
          active: false,
          exclusive
        })
      : corestore.get({
          name: 'local',
          active: false,
          exclusive
        })
    await result.local.ready()

    const key = await result.local.getUserData('referrer')
    if (key) {
      result.key = key
      result.bootstrap = corestore.get({ key, active: false })
      await result.bootstrap.ready()
    } else {
      result.key = result.local.key
      result.bootstrap = result.local.session({ active: false })
      await result.bootstrap.setUserData('autobee/local', result.local.key)
    }
  }

  if (key || keyPair) {
    await result.bootstrap.setUserData('referrer', result.key)
    await result.bootstrap.setUserData('autobee/local', result.local.key)
    await result.local.setUserData('referrer', result.key)

    result.migration = await migrateWithFallback(
      corestore,
      result.local,
      result.bootstrap,
      legacyViews
    )

    if (result.migration) {
      await result.local.setUserData(
        'autobee/head',
        encoding.encodeBootRecord(result.migration.system)
      )
      await result.local.setUserData('autobee/encryption', result.migration.encryptionKey)
    }
  }

  const [systemHead, encryptionKeyBuffer, prevDrainBuffer] = await Promise.all([
    result.local.getUserData('autobee/head'),
    result.local.getUserData('autobee/encryption'),
    result.local.getUserData('autobee/previous-drain')
  ])

  if (result.system === null && systemHead) {
    result.system = encoding.decodeBootRecord(systemHead)
  }

  if (encryptionKeyBuffer) result.encryptionKey = encryptionKeyBuffer
  if (prevDrainBuffer) result.previousDrain = encoding.decodePreviousDrain(prevDrainBuffer)

  if (!result.encryptionKey && (encryptionKey || encrypt)) {
    if (!encryptionKey) {
      encryptionKey = (await corestore.createKeyPair('autobee/encryption')).secretKey.subarray(
        0,
        32
      )
    }

    await result.local.setUserData('autobee/encryption', encryptionKey)
    result.encryptionKey = encryptionKey
  }

  return result
}

// the bootstrap pointer names the rotated-to writer, whose legacy boot record
// can be unusable. the namespace local still carries the pre-rotation record,
// which points at the last system core that was actually signed
async function migrateWithFallback(corestore, local, bootstrap, legacyViews) {
  try {
    return await checkAutobaseMigration(corestore, local, bootstrap, legacyViews)
  } catch (err) {
    const prev = corestore.get({ name: 'local', active: false })
    await prev.ready()

    if (b4a.equals(prev.key, local.key)) throw err
    if (!(await prev.getUserData('autobase/boot'))) throw err

    return checkAutobaseMigration(corestore, prev, bootstrap, legacyViews)
  }
}

async function getLocalKey(bootstrap) {
  const [legacy, current] = await Promise.all([
    bootstrap.getUserData('autobase/local'),
    bootstrap.getUserData('autobee/local')
  ])

  if (current) return current
  return legacy
}
const LEGACY_OPLOG_VERSION = 2
const OPLOG_VERSION = 4
const LEGACY_AUTOBASE_VERSION = 2
const AUTOBEE_VERSION = 3
const DEFAULT_OP_TIMEOUT = 5000

module.exports = {
  LEGACY_OPLOG_VERSION,
  OPLOG_VERSION,
  LEGACY_AUTOBASE_VERSION,
  AUTOBEE_VERSION,
  DEFAULT_OP_TIMEOUT
}
const b4a = require('b4a')
const c = require('compact-encoding')
const crypto = require('hypercore-crypto')
const { AutobeeEncryption, WriterEncryption } = require('autobee-encryption')

const { getEncoding } = require('../encoding/spec/autobee')
const { LEGACY_OPLOG_VERSION, OPLOG_VERSION } = require('./constants')

const Oplog = getEncoding('@autobee/oplog')
const Link = getEncoding('@autobee/link')
const SystemInfo = getEncoding('@autobee/system-info')
const SystemWriter = getEncoding('@autobee/system-writer')
const ManifestData = getEncoding('@autobee/manifest-data')
const AutobaseBootRecord = getEncoding('@autobase-compat/boot-record')
const MigratedHead = getEncoding('@autobee/migrated-head')

exports.OPLOG_VERSION = OPLOG_VERSION

exports.Oplog = Oplog
exports.ManifestData = ManifestData

exports.encodeBootRecord = encodeBootRecord
exports.decodeBootRecord = decodeBootRecord

exports.encodeMigratedHead = encodeMigratedHead
exports.decodeMigratedHead = decodeMigratedHead

exports.encodePreviousDrain = encodePreviousDrain
exports.decodePreviousDrain = decodePreviousDrain

exports.decodeAutobaseBootRecord = decodeAutobaseBootRecord

exports.encodeSystemGrantKey = encodeSystemGrantKey
exports.encodeSystemGrantHint = encodeSystemGrantHint
exports.decodeSystemGrantHint = decodeSystemGrantHint
exports.encodeSystemPendingKey = encodeSystemPendingKey
exports.decodeSystemPendingKey = decodeSystemPendingKey
exports.encodeSystemPendingRange = encodeSystemPendingRange
exports.encodeSystemPending = encodeSystemPending
exports.decodeSystemPending = decodeSystemPending
exports.encodeSystemWriterKey = encodeSystemWriterKey
exports.encodeSystemWriter = encodeSystemWriter
exports.decodeSystemWriter = decodeSystemWriter

function encodeBootRecord(r) {
  return c.encode(Link, r)
}

function decodeBootRecord(value) {
  return c.decode(Link, value)
}

function encodeMigratedHead(m) {
  return c.encode(MigratedHead, m)
}

function decodeMigratedHead(value) {
  return c.decode(MigratedHead, value)
}

function encodePreviousDrain(d) {
  return c.encode(c.uint, d)
}

function decodePreviousDrain(value) {
  return c.decode(c.uint, value)
}

function decodeAutobaseBootRecord(value) {
  return c.decode(AutobaseBootRecord, value)
}

function encodeSystemWriterKey(key) {
  const buf = b4a.allocUnsafe(34)
  buf[0] = 1
  buf[1] = 0
  buf.set(key, 2)
  return buf
}

function encodeSystemPendingKey(key) {
  const buf = b4a.allocUnsafe(33)
  buf[0] = 5
  buf.set(key, 1)
  return buf
}

function decodeSystemPendingKey(buf) {
  return buf.subarray(1, 33)
}

function encodeSystemPendingRange() {
  return { gte: b4a.from([5]), lt: b4a.from([6]) }
}

function encodeSystemPending(entry) {
  return c.encode(c.uint, entry.weight)
}

function decodeSystemPending(value) {
  return { weight: c.decode(c.uint, value) }
}

function encodeSystemGrantKey(key) {
  const buf = b4a.allocUnsafe(33)
  buf[0] = 4
  buf.set(key, 1)
  return buf
}

function encodeSystemGrantHint(hint) {
  const state = { start: 0, end: 0, buffer: null }
  c.fixed32.preencode(state, hint.key)
  c.uint.preencode(state, hint.length)
  c.uint.preencode(state, hint.weight)
  state.buffer = b4a.alloc(state.end)
  c.fixed32.encode(state, hint.key)
  c.uint.encode(state, hint.length)
  c.uint.encode(state, hint.weight)
  return state.buffer
}

function decodeSystemGrantHint(value) {
  const state = { start: 0, end: value.byteLength, buffer: value }
  return {
    key: c.fixed32.decode(state),
    length: c.uint.decode(state),
    weight: c.uint.decode(state)
  }
}

function encodeSystemWriter(m) {
  return c.encode(SystemWriter, m)
}

function decodeSystemWriter(key, value) {
  return {
    // TODO: add dummy type to schema to preset key without extra alloc
    key: key.subarray(2),
    ...c.decode(SystemWriter, value)
  }
}

exports.encodeSystemInfo = encodeSystemInfo
exports.decodeSystemInfo = decodeSystemInfo

function encodeSystemInfo(m) {
  return c.encode(SystemInfo, m)
}

function decodeSystemInfo(buf) {
  return c.decode(SystemInfo, buf)
}

exports.encodeOplog = encodeOplog
exports.decodeOplog = decodeOplog
exports.decodeRawOplog = decodeRawOplog

function decodeRawOplog(buf) {
  return c.decode(Oplog, buf)
}

function decodeOplog(buf) {
  const m = c.decode(Oplog, buf)

  if (m.version <= LEGACY_OPLOG_VERSION) {
    return {
      version: m.version,
      timestamp: 0,
      links: m.node.heads,
      batch: { start: 0, end: m.node.batch - 1 },
      views: null,
      trusted: null,
      witness: null,
      approvals: null,
      optimistic: !!m.optimistic,
      value: m.node.value
    }
  }

  if (m.batch === null) m.batch = { start: 0, end: 0 }
  return m
}

function encodeOplog(m) {
  if (m.batch && m.batch.start === 0 && m.batch.end === 0) m.batch = null
  return c.encode(Oplog, m)
}

exports.encodeValue = encodeValue
exports.decodeValue = decodeValue

function createValue(value, opts = {}) {
  if (opts.legacy) {
    return {
      version: opts.version || LEGACY_OPLOG_VERSION,
      digest: null,
      checkpoint: null,
      optimistic: !!opts.optimistic,
      node: {
        heads: opts.heads || [],
        batch: 1,
        value
      }
    }
  }

  const {
    version = OPLOG_VERSION,
    optimistic = false,
    timestamp = Date.now(),
    links = [],
    views = null
  } = opts

  return {
    version,
    timestamp,
    links,
    batch: null,
    views,
    trusted: null,
    witness: null,
    approvals: null,
    optimistic,
    value
  }
}

function encodeValue(value, opts = {}) {
  const oplog = createValue(value, opts)

  const state = { start: 0, end: 0, buffer: null }

  Oplog.preencode(state, oplog)

  if (opts.padding) {
    state.start = opts.padding
    state.end += opts.padding
  }

  state.buffer = b4a.alloc(state.end)

  Oplog.encode(state, oplog)

  if (!opts.encrypted) return state.buffer

  if (!opts.optimistic) {
    throw new Error('Encoding an encrypted value is not supported')
  }

  const padding = b4a.alloc(16) // sodium.crypto_generichash_MINBYTES
  crypto.hash(state.buffer, padding)
  padding[0] = 0

  return b4a.concat([padding.subarray(0, AutobeeEncryption.PADDING), state.buffer])
}

async function decodeValue(buf, { encryptionKey, autobeeKey, key, manifest, index = 0 } = {}) {
  if (encryptionKey) {
    const w = new WriterEncryption({ encryptionKey, key: autobeeKey })

    await w.decrypt(index, buf, { key, manifest })
    buf = buf.subarray(AutobeeEncryption.PADDING)
  }

  return decodeOplog(buf).value
}
const safetyCatch = require('safety-catch')
const b4a = require('b4a')
const Hypercore = require('hypercore')

const System = require('./system.js')
const migrations = require('./migrations.js')
const { LEGACY_AUTOBASE_VERSION } = require('./constants.js')

const DEFAULT_OP_TIMEOUT = 5_000
const MIN_FF_GAP = 32

module.exports = class FastForward {
  constructor(auto, head, { timeout = DEFAULT_OP_TIMEOUT } = {}) {
    this.auto = auto

    this.activeRequests = []

    // 'reboot' is a storage namespace: renaming it would derive a different
    // scratch core on disk, so it stays as is
    this.system = new System(auto.store.namespace('reboot'), null, {
      getEncryptionProvider: this.auto.getSystemEncryption,
      encrypted: this.encrypted,
      activeRequests: this.activeRequests
    })

    // length -1 marks an uninitialised head: we know the key but not the length
    this.head = { key: head.key, length: head.length ?? 0 }
    this.timeout = timeout
    this.destroyed = false
    this.running = null
    this.failed = false
    this.cores = []
  }

  static DEFAULT_TIMEOUT = DEFAULT_OP_TIMEOUT

  static async fromHead(
    auto,
    head,
    trusted,
    { force = false, timeout = 0, condition = null, reference = null } = {}
  ) {
    const conservative = !force && auto._conservativeFF
    const timeoutOpts = timeout ? { timeout } : null

    const oplog = await FastForward.flushHead(auto, head, {
      conservative,
      ...timeoutOpts
    })
    if (!oplog) return null

    const verified = trusted ? await FastForward.flushHead(auto, trusted, timeoutOpts) : oplog
    if (!verified) return null

    // legacy nodes from non-indexers have no system info to fast-forward from
    if (!oplog.op.views || !verified.op.views) return null

    if (!force && verified.op.views.flushes - auto.system.flushes < MIN_FF_GAP) {
      return null
    }

    if (condition !== null && !(await FastForward.accepts(auto, verified, condition, reference))) {
      return null
    }

    return new FastForward(auto, batchToHead(verified.op.views.system))
  }

  static async fromHeads(
    auto,
    heads,
    { force = false, timeout = 0, condition = null, reference = null } = {}
  ) {
    if (reference === null) reference = auto._workingView.view

    const promises = []

    for (const head of heads) {
      if (head.length === 0) continue
      promises.push(FastForward.flushHead(auto, head, timeout ? { timeout } : null))
    }

    const ops = await Promise.all(promises)
    if (auto.fastForwarding || auto.fastForwardTo) return null

    const trust = await Promise.all(
      ops.map((res) => {
        return res === null ? false : auto.trusted.isTrusted(res.key, reference)
      })
    )
    if (auto.fastForwarding || auto.fastForwardTo) return null

    const candidates = []

    let bestTrusted = null
    let bestTrustedFlushes = -1

    for (let i = 0; i < ops.length; i++) {
      const res = ops[i]
      if (res === null || !res.op.views) continue
      if (!force && res.op.views.flushes - auto.system.flushes < MIN_FF_GAP) continue

      if (!trust[i]) {
        candidates.push(res)
        continue
      }

      if (res.op.views.flushes > bestTrustedFlushes) {
        bestTrustedFlushes = res.op.views.flushes
        bestTrusted = res
      }
    }

    if (bestTrusted !== null) {
      return FastForward.fromHead(auto, bestTrusted, null, {
        force,
        timeout,
        condition,
        reference
      })
    }

    candidates.sort((a, b) => b.op.views.flushes - a.op.views.flushes)

    for (const res of candidates) {
      const trusted = await FastForward.mostRecentTrusted(auto, res, reference)
      if (trusted === null) continue
      if (auto.fastForwarding || auto.fastForwardTo) return null

      const ff = await FastForward.fromHead(auto, res, trusted, {
        force,
        timeout,
        condition,
        reference
      })
      if (ff !== null) return ff
    }

    return null
  }

  static async flushHead(auto, head, opts) {
    const core = auto.openCore(head.key)

    try {
      const oplog = await auto.readOplog(core, head.length, opts)
      if (oplog === null) return null
      return oplog.op.views ? oplog : null
    } finally {
      await core.close()
    }
  }

  static async accepts(auto, oplog, condition, reference) {
    const opened = auto.openViewAt(oplog)
    if (opened === null) return false

    try {
      return !!(await condition(opened.view, reference))
    } finally {
      await opened.close()
    }
  }

  static async mostRecentTrusted(auto, head, reference) {
    const opened = auto.openViewAt(head)
    if (opened === null) return null

    try {
      return (await auto.trusted.mostRecentTrusted(opened.view, reference)) || null
    } finally {
      await opened.close()
    }
  }

  async run() {
    try {
      if (!this.running) this.running = this._run()

      return await this.running
    } catch (err) {
      safetyCatch(err)
      this.failed = true
      return null
    } finally {
      await this.close()
    }
  }

  async _run() {
    // an uninitialised head has an unknown length: wait for the core to catch up
    if (!this.head.length) {
      this.head.length = await this._resolveLength()
    }

    // zero-length boot is unsupported
    if (!this.head.length) return null

    await this.system.boot(this.head, { timeout: this.timeout })

    // a legacy system needs migrating rather than a plain fast-forward
    if (this.system.version <= LEGACY_AUTOBASE_VERSION) {
      const migrated = this.auto._migratedHead && this.auto._migratedHead.system
      if (
        migrated &&
        b4a.equals(migrated.key, this.head.key) &&
        migrated.length >= this.head.length
      ) {
        return null
      }

      const result = await this._migrate()
      if (result === null || result.autobee === null) return result

      // an indexer of the newest legacy generation already moved to autobee -
      // fast-forward straight onto its announced head instead of migrating,
      // falling back to the legacy migration if the head cannot be booted
      try {
        this.head = result.autobee
        await this.system.boot(this.head, { timeout: this.timeout })
        if (this.system.version <= LEGACY_AUTOBASE_VERSION) {
          throw new Error('Expected an autobee system')
        }
      } catch (err) {
        safetyCatch(err)
        this.head = result.head
        return { head: result.head, migrate: result.migrate }
      }

      // fall through to the plain fast-forward below
    }

    const promises = []

    // ensure local key is locally available always
    promises.push(this.system.get(this.auto.local.key, { timeout: this.timeout }))

    const view = this.auto.store.get({ key: this.system.view.key, active: true })
    this.cores.push(view)

    promises.push(
      view.get(this.system.view.length - 1, {
        timeout: this.timeout,
        activeRequests: this.activeRequests
      })
    )

    for (const head of this.system.heads) {
      promises.push(this.system.get(head.key, { timeout: this.timeout }))
    }

    await Promise.all(promises)
    if (this.destroyed) return null

    if (this.destroyed) return null

    return {
      head: this.head,
      migrate: null
    }
  }

  // Upgrade a legacy (pre-AUTOBEE_VERSION) system in place. The system already
  // booted above (coerced to the current struct), but the original v1/v2 struct
  // is needed to recover the indexers/views/entropy, so decode the raw block.
  //
  // A head may be arbitrarily old: an indexer rotation freezes the system core
  // and moves the indexers to a successor. The frozen core cannot reveal its
  // successor (the promotion only lands in its unsigned tail), but the
  // indexers' newest oplog digests stamp the newest system they know - chase
  // those so the migration lands on the newest generation instead of the head
  // we were given.
  async _migrate() {
    let head = { key: this.head.key, length: this.head.length }
    const seen = new Set()

    for (let hops = 0; hops < migrations.MAX_MIGRATE_HOPS; hops++) {
      seen.add(b4a.toString(head.key, 'hex'))

      const core = this.auto.store.get({ key: head.key, encryption: null })
      this.cores.push(core)
      await core.ready()

      // setup encryption
      await core.setEncryption(this.auto.getSystemEncryption())

      // successors are resolved by key only - wait for the replicated length
      if (!head.length) head.length = await migrations.coreLength(core, this.timeout)
      if (!head.length || this.destroyed) return null

      const info = await migrations.readLegacySystemInfo(core, head.length, {
        timeout: this.timeout,
        activeRequests: this.activeRequests
      })

      const { legacy, autobee } = await migrations.findNewestSystem(this, info)
      if (this.destroyed) return null

      if (legacy !== null && !seen.has(b4a.toString(legacy, 'hex'))) {
        head = { key: legacy, length: 0 }
        continue
      }

      this.head = head

      const views = await migrations.resolveLegacyViews(this, info)
      if (views === null || this.destroyed) return null

      return {
        head,
        migrate: views,
        autobee: autobee === null ? null : batchToHead(autobee.op.views.system)
      }
    }

    return null
  }

  async _resolveLength() {
    const core = this.auto.store.get({ key: this.head.key })
    this.cores.push(core)

    await core.ready()

    return migrations.coreLength(core, this.timeout)
  }

  // cancels every read in flight and fails any that come after
  clearRequests(err = null) {
    Hypercore.destroyRequests(this.activeRequests, err)
  }

  async close() {
    this.destroyed = true
    this.clearRequests()
    if (this.system) await this.system.close()
    for (const core of this.cores) await core.close()
  }
}

function batchToHead(b) {
  return {
    key: b.key,
    length: b.start + b.length
  }
}
const c = require('compact-encoding')
const b4a = require('b4a')
const crypto = require('hypercore-crypto')
const { AutobeeEncryption } = require('autobee-encryption')
const { decodeBlock } = require('hyperbee2/lib/encoding.js')

const encoding = require('./encoding.js')
const { assert, bail } = require('./asserts.js')
const { LEGACY_OPLOG_VERSION } = require('./constants.js')

const { getEncoding } = require('../encoding/spec/autobee')

const SystemInfoV1 = getEncoding('@autobase-compat/info-v1')
const SystemInfoV2 = getEncoding('@autobase-compat/info-v2')

const EMPTY = b4a.alloc(0)
const INDEX_VERSION = 1
const INFO = b4a.from([0x00, 0x00, 0x69, 0x6e, 0x66, 0x6f])
const [NS_SIGNER_NAMESPACE] = crypto.namespace('autobase', 1)

// indexer rotations chain legacy systems - how many generations we chase
const MAX_MIGRATE_HOPS = 64

module.exports = {
  MAX_MIGRATE_HOPS,
  checkAutobaseMigration,
  readLegacySystemInfo,
  findNewestSystem,
  resolveLegacyViews,
  inflateLegacyOplog,
  decodeLegacySystemInfo,
  deriveNamespace,
  coreLength
}

// boot-time migration of local legacy autobase storage
async function checkAutobaseMigration(store, local, bootstrap, legacyViews) {
  const bootRecord = await local.getUserData('autobase/boot')
  if (!bootRecord) return null

  const { key, systemLength } = encoding.decodeAutobaseBootRecord(bootRecord)
  const core = store.get({ key: key, encryption: null })
  await core.ready()

  // setup encryption
  const encryptionKey = await local.getUserData('autobase/encryption')
  await AutobeeEncryption.setSystemEncryption(bootstrap.key, encryptionKey, core)

  const length = await findLocalSystemLength(core, systemLength)
  const system = { key, length }

  const session = core.session({ name: 'batch' })
  await session.ready()

  let catchup

  try {
    catchup = await getCatchupHeads(session, length)
  } finally {
    await session.close()
  }

  const info = await readLegacySystemInfo(core, length, { wait: false })

  const nodes = await Promise.all(
    catchup.map((n) => getWriterBatch(store, n, bootstrap.key, encryptionKey))
  )

  const indexerManifests = await Promise.all(
    info.indexers.map((idx) => storeCoreManifest(store, idx.key))
  )
  const views = await Promise.all(info.views.map((v) => getPersistedView(store, v)))
  const entropy = legacyEntropy(info, indexerManifests)

  const getManifest = (key) => storeCoreManifest(store, key)

  const mappedViews = new Map()
  for (const name of legacyViews) {
    const v = await findViewByName(
      getManifest,
      bootstrap.key,
      encryptionKey,
      indexerManifests,
      views,
      entropy,
      name
    )

    mappedViews.set(name, v)
  }

  await core.close()

  return {
    encryptionKey,
    system,
    views: mappedViews,
    catchup: nodes
  }
}

// decode the legacy system info recorded in the system core's block at length
async function readLegacySystemInfo(core, length, opts = null) {
  const block = await core.get(length - 1, opts)
  assert(block !== null, 'Expected system block to exist locally')

  // Decode hyperbee block using hyperbee2 compat
  const node = decodeBlock(block)
  assert(node.keys.length > 0, 'bad system block')

  // Decode system info from hyperbee block using autobee compat
  return decodeLegacySystemInfo(node.keys[0].value)
}

// read each indexer's newest legacy oplog stamp - its digest/checkpoint mark
// the newest legacy system that indexer knows. take the majority vote. an
// indexer that already moved to autobee announces its live autobee state
// instead - keep the newest of those as a direct fast-forward candidate and
// bisect down to its buried legacy stamps so its vote still counts
async function findNewestSystem(ff, info) {
  const tally = new Map()

  let autobee = null

  for (const idx of info.indexers) {
    const core = ff.auto.openCore(idx.key)
    ff.cores.push(core)
    await core.ready()

    const length = await coreLength(core, ff.timeout)
    if (!length || ff.destroyed) continue

    let oplog = await flushHead(ff.auto, { key: idx.key, length }, { timeout: ff.timeout })
    if (oplog === null || !oplog.op.views) continue

    if (oplog.op.version > LEGACY_OPLOG_VERSION) {
      if (autobee === null || oplog.op.views.flushes > autobee.op.views.flushes) {
        autobee = oplog
      }

      const boundary = await findLegacyBoundary(ff, core, idx.length, oplog.length - 1)
      if (!boundary || ff.destroyed) continue

      oplog = await flushHead(ff.auto, { key: idx.key, length: boundary }, { timeout: ff.timeout })
      if (oplog === null || !oplog.op.views) continue
      if (oplog.op.version > LEGACY_OPLOG_VERSION) continue
    }

    const sys = oplog.op.views.system
    if (!sys || !sys.key) continue

    const hex = b4a.toString(sys.key, 'hex')
    const t = tally.get(hex) || { key: sys.key, votes: 0, length: sys.length }

    t.votes++
    if (sys.length > t.length) t.length = sys.length
    tally.set(hex, t)
  }

  const majority = (info.indexers.length >> 1) + 1

  let best = null
  for (const t of tally.values()) {
    if (t.votes >= majority && (best === null || t.length > best.length)) best = t
  }

  return { legacy: best === null ? null : best.key, autobee }
}

// map the legacy views recorded in the system info by name
async function resolveLegacyViews(ff, info) {
  const indexerManifests = await Promise.all(
    info.indexers.map((idx) => ffCoreManifest(ff, idx.key, idx.length))
  )
  if (ff.destroyed || indexerManifests.some((m) => m === null)) return null

  const entropy = legacyEntropy(info, indexerManifests)

  const getManifest = (key, length) => ffCoreManifest(ff, key, length)

  const views = new Map()

  await Promise.all(
    ff.auto.legacyViews.map(async (name) => {
      const v = await findViewByName(
        getManifest,
        ff.auto.key,
        ff.auto.encryptionKey,
        indexerManifests,
        info.views,
        entropy,
        name
      )

      if (v) views.set(name, v)
    })
  )

  if (ff.destroyed) return null

  return views
}

// legacy nodes always inflate, but only indexers carry the digest/checkpoint
// stamps that synthesise system info - callers must check op.views
async function inflateLegacyOplog(buf, core, seq, timeout) {
  const m = encoding.decodeRawOplog(buf)

  const op = {
    version: m.version,
    timestamp: 0,
    links: m.node.heads,
    batch: { start: 0, end: m.node.batch - 1 },
    views: null,
    optimistic: !!m.optimistic,
    value: m.node.value
  }

  if (m.digest === null || m.checkpoint === null || !m.checkpoint.system) return op

  const fetches = []

  fetches.push(m.digest.pointer ? core.get(seq - m.digest.pointer, { timeout }) : buf)
  fetches.push(
    m.checkpoint.system.checkpointer
      ? core.get(seq - m.checkpoint.system.checkpointer, { timeout })
      : buf
  )

  const [digestNode, checkpointNode] = await Promise.all(fetches)
  // no caller reads best-effort today, but stay total over a missing block
  if (digestNode === null || checkpointNode === null) return op

  const { digest } = encoding.decodeRawOplog(digestNode)
  const { checkpoint } = encoding.decodeRawOplog(checkpointNode)

  if (!checkpoint.system || !checkpoint.system.checkpoint) return op

  op.views = {
    system: {
      key: digest.key,
      start: 0,
      length: checkpoint.system.checkpoint.length
    },
    flushes: seq
  }

  return op
}

function decodeLegacySystemInfo(buffer) {
  const state = { start: 0, end: buffer.length, buffer }
  const version = c.uint.decode(state)

  state.start--

  switch (version) {
    case 1:
      return SystemInfoV1.decode(state)
    case 2:
      return SystemInfoV2.decode(state)
    default:
      bail('Expected legacy system info')
  }
}

function deriveNamespace(name, bootstrap, entropy, encryptionKey) {
  const encryptionId = crypto.hash(encryptionKey || EMPTY)
  const version = c.encode(c.uint, INDEX_VERSION)

  return crypto.hash([
    NS_SIGNER_NAMESPACE,
    version,
    bootstrap,
    encryptionId,
    entropy,
    b4a.from(name)
  ])
}

// the record's length can run past the signed core into the local batch
// session, and the signed core can carry verified-but-undownloaded blocks -
// scan back for the newest block the signed core actually has locally
async function findLocalSystemLength(core, systemLength) {
  let length = Math.min(core.length, systemLength)

  while (length > 0) {
    const seq = length - 1
    const node = await core.get(seq, { wait: false })

    if (node) {
      const blk = decodeBlock(node)
      const entry = blk.keys[0]
      if (b4a.equals(entry.key, INFO)) return length
    }

    length--
  }

  assert(false, 'Expected system block to exist locally')
}

function coreLength(core, timeout) {
  if (core.length) return core.length

  return new Promise((resolve) => {
    core.on('append', () => resolve(core.length))
    setTimeout(resolve, timeout, 0)
  })
}

// a writer's oplog is a legacy prefix followed by autobee-format nodes -
// bisect for the boundary. lo is a length known to be legacy (the legacy
// system's recorded length for this writer), hi a seq known to be autobee.
// returns the legacy prefix length
async function findLegacyBoundary(ff, core, lo, hi) {
  while (lo < hi) {
    const mid = (lo + hi) >>> 1

    const block = await core.get(mid, {
      timeout: ff.timeout,
      activeRequests: ff.activeRequests
    })

    if (encoding.decodeRawOplog(block).version > LEGACY_OPLOG_VERSION) hi = mid
    else lo = mid + 1
  }

  return lo
}

function legacyEntropy(info, indexerManifests) {
  return info.version > 1 && info.entropy ? info.entropy : indexerManifests[0].signers[0].namespace
}

async function findViewByName(
  getManifest,
  bootstrap,
  encryptionKey,
  indexerManifests,
  views,
  entropy,
  name
) {
  if (indexerManifests.length === 0) return null

  const namespace = deriveNamespace(name, bootstrap, entropy, encryptionKey)

  for (const v of views) {
    const manifest = await getManifest(v.key, v.length)
    if (!manifest) continue

    if (manifest.signers.length === 0) continue

    const signer = manifest.signers[0]

    if (b4a.equals(signer.namespace, namespace)) return v
  }

  return null
}

// request-tracked manifest read over replication
async function ffCoreManifest(ff, key, length) {
  const core = ff.auto.store.get(key)
  ff.cores.push(core)

  await core.ready()

  try {
    if (!core.manifest) {
      await core.get(length - 1, {
        timeout: ff.timeout,
        activeRequests: ff.activeRequests
      })
    }
  } catch {
    return null
  }

  return core.manifest
}

// local-storage manifest read
async function storeCoreManifest(store, key) {
  const core = store.get(key)
  await core.ready()

  const manifest = core.manifest
  await core.close()

  return manifest
}

async function getPersistedView(store, view) {
  const core = store.get(view.key)
  await core.ready()

  // core.length can run past what was downloaded - the system's recorded
  // length is the last one we know is local
  const length = Math.min(core.length, view.length)
  await core.close()

  return { key: view.key, length }
}

function getOplog(info, batch) {
  if (batch.length === 1) return batch[0].keys[0]

  const { heads } = encoding.decodeSystemInfo(info.value)

  for (const b of batch) {
    const entry = b.keys[0]
    const w = encoding.decodeSystemWriter(entry.key, entry.value)

    for (const { key } of heads) {
      if (b4a.equals(w.key, key)) return b.keys[0]
    }
  }

  throw new Error('Could not infer oplog node')
}

async function getCatchupHeads(session, from) {
  const seen = new Map()
  const nodes = []

  let batch = []

  // block 0 is the bee header, never a node
  for (let i = Math.max(from, 1); i < session.length; i++) {
    const node = await session.get(i, { wait: false })
    if (!node) throw new Error('Expect nodes to exist locally')

    const blk = decodeBlock(node)
    const entry = blk.keys[0]

    if (b4a.equals(entry.key, INFO)) {
      const oplog = getOplog(entry, batch)
      const { key, length } = encoding.decodeSystemWriter(oplog.key, oplog.value)

      batch = []

      if (!length) continue

      const id = b4a.toString(key, 'hex')
      const current = seen.get(id) || 0

      if (current >= length) continue

      seen.set(id, length)
      nodes.push({ key, length })
      continue
    }

    if (entry.key[0] === 0x01) batch.push(blk)
  }

  return nodes
}

async function getWriterBatch(store, head, key, encryptionKey, nodes = []) {
  const batch = []

  const core = store.get(head.key)
  await core.ready()

  if (encryptionKey) {
    await core.setEncryption(AutobeeEncryption.getWriterEncryption(key, encryptionKey))
  }

  let seq = head.length - 1
  const block = await core.get(seq--, { wait: false })
  if (!block) throw new Error('Expect writer node to exist locally')

  const node = encoding.decodeOplog(block)

  batch.unshift({ ...head, ...node, from: core })

  while (seq >= 0) {
    const block = await core.get(seq--, { wait: false })
    if (!block) break

    const node = encoding.decodeOplog(block)
    if (!node.batch.end) break

    batch.unshift({ key: head.key, length: seq + 2, from: core, ...node })
  }

  return batch
}

// lazy to avoid the require cycle - fast-forward imports this module
function flushHead(auto, head, opts) {
  const FastForward = require('./fast-forward.js')
  return FastForward.flushHead(auto, head, opts)
}
const b4a = require('b4a')
const encoding = require('./encoding.js')

// Owns the two promotion indexes: one hint per writer (the coordinate of the
// approval that anchored its capability - advisory, never verified against)
// and one pending entry per writer awaiting a stronger approval.
//
// An approval clears the entry as soon as it satisfies the request, so the
// index holds exactly the outstanding work. A partial approval leaves the
// entry alone: the request still wants more than the tier that answered could
// give.
//
// digest[w - 1] is true when some request needs an approver of standing w.
// The false is what makes a tier go quiet: once one tier-w peer has anchored
// w, the rest see their tier is finished instead of all offering in turn. A
// flag is enough because a satisfied request leaves the index entirely, so
// there is no "have I already seen this" watermark to keep.
module.exports = class Promotions {
  constructor(bee) {
    this.bee = bee
    this.digest = []
    this.changed = false
    this._pending = null
  }

  reset(info) {
    this.digest = info && info.pending ? info.pending : []
    this._pending = null
  }

  async hint(key, { activeRequests } = {}) {
    const node = await this.bee.get(encoding.encodeSystemGrantKey(key), { activeRequests })
    return node ? encoding.decodeSystemGrantHint(node.value) : null
  }

  async pending(key, { activeRequests } = {}) {
    if (this._pending !== null) {
      const entry = this._pending.get(b4a.toString(key, 'hex'))
      return entry ? entry.weight : 0
    }

    const node = await this.bee.get(encoding.encodeSystemPendingKey(key), { activeRequests })
    return node ? encoding.decodeSystemPending(node.value).weight : 0
  }

  // the level a writer has already anchored is exactly what its hint records,
  // so it is read from there rather than mirrored into the pending entry
  async anchored(key, { activeRequests } = {}) {
    const hint = await this.hint(key, { activeRequests })
    return hint ? hint.weight : 0
  }

  async *list({ activeRequests } = {}) {
    const pending = await this._load({ activeRequests })
    for (const [hex, entry] of pending) {
      yield { key: b4a.from(hex, 'hex'), weight: entry.weight }
    }
  }

  async updateHint(upd) {
    if (!upd.grant && !upd.isRemoved) return null

    const key = encoding.encodeSystemGrantKey(upd.key)
    const node = await this.bee.get(key)

    if (upd.isRemoved) return node ? { key, remove: true } : null

    const existing = node ? encoding.decodeSystemGrantHint(node.value) : null
    if (existing && existing.weight >= upd.grant.weight) return null

    return { key, value: encoding.encodeSystemGrantHint(upd.grant) }
  }

  // grouped and sequential on purpose: the entries are shared state, so
  // per-update parallelism would interleave at the awaits
  async flushPending(updates) {
    let relevant = false
    for (const upd of updates) {
      if (upd.pending !== -1 || upd.grant !== null || upd.isRemoved) {
        relevant = true
        break
      }
    }
    if (!relevant) return null

    const pending = await this._load()
    if (!pending.size && !updates.some((u) => u.pending !== -1)) return null

    const ops = []
    const anchoredBy = new Map()

    for (const upd of updates) {
      const hex = b4a.toString(upd.key, 'hex')
      const existing = pending.get(hex) || null

      if (upd.isRemoved) {
        if (existing) {
          pending.delete(hex)
          ops.push({ key: encoding.encodeSystemPendingKey(upd.key), remove: true })
        }
        continue
      }
      if (upd.pending === -1 && upd.grant === null) continue

      // the hint is still pre-write here, so this is the level already anchored
      // before whatever this update confers
      const prev = await this.anchored(upd.key)
      const anchored = upd.grant && upd.grant.weight > prev ? upd.grant.weight : prev
      anchoredBy.set(hex, anchored)

      const want = Math.max(existing ? existing.weight : 0, upd.pending)

      if (want > anchored) {
        if (existing && want === existing.weight) continue
        const entry = { weight: want }
        pending.set(hex, entry)
        ops.push({
          key: encoding.encodeSystemPendingKey(upd.key),
          value: encoding.encodeSystemPending(entry)
        })
        this.changed = true
      } else if (existing) {
        // satisfied - the approval covered everything the request asked for
        pending.delete(hex)
        ops.push({ key: encoding.encodeSystemPendingKey(upd.key), remove: true })
      }
    }

    // the flags depend on the anchored level, not just on which entries exist -
    // an approval that partially serves a request writes no entry at all, and
    // its tier still has to go quiet
    const open = new Set()
    for (const [hex, entry] of pending) {
      const anchored = anchoredBy.has(hex)
        ? anchoredBy.get(hex)
        : await this.anchored(b4a.from(hex, 'hex'))
      for (let w = anchored + 1; w <= entry.weight; w++) open.add(w)
    }

    let max = 0
    for (const w of open) if (w > max) max = w

    this.digest = []
    for (let w = 1; w <= max; w++) this.digest.push(open.has(w))

    return ops.length ? ops : null
  }

  async _load({ activeRequests } = {}) {
    if (this._pending !== null) return this._pending

    const pending = new Map()
    const range = encoding.encodeSystemPendingRange()
    for await (const data of this.bee.createReadStream({ ...range, activeRequests })) {
      pending.set(
        b4a.toString(encoding.decodeSystemPendingKey(data.key), 'hex'),
        encoding.decodeSystemPending(data.value)
      )
    }

    this._pending = pending
    return pending
  }
}
const Hyperbee = require('hyperbee2')
const Promotions = require('./promotions.js')
const b4a = require('b4a')

const { AUTOBEE_VERSION } = require('./constants')
const encoding = require('./encoding.js')
const topo = require('./topo.js')

const INFO_KEY = b4a.from([0])
const INFO_LEGACY_KEY = b4a.concat([b4a.from([0, 0]), b4a.from('info')])
const EMPTY_HEAD = { length: 0, key: null }
const EMPTY = b4a.from([])

class SystemSnapshot {
  constructor(bee) {
    this.bee = bee
  }

  async get(key, { timeout } = {}) {
    const node = await this.bee.get(encoding.encodeSystemWriterKey(key), { timeout })
    return node !== null ? encoding.decodeSystemWriter(node.key, node.value) : null
  }

  async has(link) {
    const node = await this.get(link.key)
    if (!node || node.length < link.length) return false
    return true
  }

  close() {
    return this.bee.close()
  }
}

module.exports = class Systembee {
  constructor(store, name, opts = {}) {
    this.name = name // for debuggin
    this.store = store
    this.bee = new Hyperbee(store, opts)
    this.view = null
    this.heads = []
    this.version = 0
    this.timestamp = 0
    this.flushes = 0
    this.updates = new Map()
    this.writers = new Map()
    this.encrypted = opts.encrypted === true
    this.promotions = new Promotions(this.bee)
  }

  async addWriter(
    key,
    { length = 0, weight = 1, isGenesis = false, coord = null, carrier = -1, anchor = false } = {}
  ) {
    const info = await this.get(key, { unflushed: true })
    if (length === 0) {
      length = info ? info.length : 0
    }

    const effective = coord && carrier < weight ? Math.max(carrier, 0) : weight
    const hint =
      anchor && coord ? { key: coord.key, length: coord.length, weight: effective } : null
    const pending = coord && !isGenesis ? weight : -1

    const current = info ? info.maxWeight : 0
    if (effective <= current) {
      // isAdded is false only when nothing has recorded the add yet - skipping
      // the update then leaves the writer undiscoverable, never announced
      const added = info && info.isAdded !== false
      if (added && !info.isRemoved && length <= info.length && !hint && pending === -1) return

      this.update(key, length, -1, -1, false, true, false, -1, hint, pending)
      return
    }

    const w = isGenesis ? effective : -1
    this.update(key, length, w, effective, isGenesis, true, false, -1, hint, pending)
  }

  async ackWriter(key, { length = 0 } = {}) {
    if (length === 0) {
      const info = await this.get(key, { unflushed: false })
      length = info ? info.length : 0
    }
    this.update(key, length, -1, -1, false, false, false, -1, null)
  }

  async removeWriter(key, { length = 0 } = {}) {
    if (length === 0) {
      const info = await this.get(key, { unflushed: true })
      length = info ? info.length : 0
    }
    this.update(key, length, -1, 0, false, false, true, -1, null)
  }

  addAnchor(key) {
    this.update(key, 0, -1, -1, false, true, false, -1, null)
  }

  isGenesis() {
    return this.bee.head() === null || this.bee.head().length === 0
  }

  isHead(key) {
    for (const head of this.heads) {
      if (b4a.equals(head.key, key)) return true
    }
    return false
  }

  async boot(view, { timeout } = {}) {
    await this.bee.ready()
    this.bee.move(view)
    await this.reset({ timeout })
  }

  async getInfo({ timeout } = {}) {
    const node = await this.bee.get(INFO_KEY, { timeout })
    if (node) return encoding.decodeSystemInfo(node.value)
    const legacy = await this.bee.get(INFO_LEGACY_KEY, { timeout })
    if (legacy) return encoding.decodeSystemInfo(legacy.value)
    return null
  }

  async reset({ timeout } = {}) {
    const info = await this.getInfo({ timeout })

    this.version = info ? info.version : 0
    this.view = info ? info.view : EMPTY_HEAD
    this.heads = info ? info.heads : []
    this.timestamp = info ? info.timestamp : 0
    this.flushes = info ? info.flushes : 0
    this.updates.clear()
    this.writers.clear()
    this.promotions.reset(info)

    if (this.version > AUTOBEE_VERSION) {
      throw new Error('Autobee signals newer version than locally supported')
    }
  }

  bootRecord() {
    const system = this.bee.head()
    return system.length ? system : null
  }

  async close() {
    await this.bee.close()
    await this.store.close()
  }

  async undo(head) {
    this.bee.move(head)

    await this.reset()

    return this.view || EMPTY_HEAD
  }

  getLinks(key) {
    const links = []
    for (const h of this.heads) {
      if (key && b4a.equals(h.key, key)) continue
      links.push(h)
    }
    return links
  }

  addNode(node) {
    for (let i = 0; i < this.heads.length; i++) {
      const h = this.heads[i]
      if (topo.isLinking(node, h)) {
        this.heads.splice(i--, 1)
      }
    }

    // Can enable if needed during debuggin
    // asserts.heads(heads, node)

    this.heads.push({ key: node.key, length: node.length })
    if (node.timestamp > this.timestamp) this.timestamp = node.timestamp // TODO: support smoothing

    // resolved sort weight, re-stamped on every reapplication - this is what
    // topo reads back out of the changes stream
    this.update(node.key, node.length, node.weight, -1, false, false, false, node.timestamp, null)
  }

  async canApply(key, optimistic) {
    const id = b4a.toString(key, 'hex')
    const w = this.writers.get(id)
    if (w) return w.added
    const info = await this.get(key)
    return info ? !info.isRemoved : !!optimistic
  }

  snapshot(head = null) {
    const bee =
      head === null
        ? this.bee.snapshot()
        : this.bee.checkout({ length: head.length, key: head.key })
    return new SystemSnapshot(bee)
  }

  async get(key, { unflushed = false, timeout, activeRequests } = {}) {
    const node = await this.bee.get(encoding.encodeSystemWriterKey(key), {
      timeout,
      activeRequests
    })
    const info = node !== null ? encoding.decodeSystemWriter(node.key, node.value) : null
    if (!unflushed) return info

    const upd = this.updates.get(b4a.toString(key, 'hex'))
    if (!upd) return info

    if (!info) {
      return {
        version: 4,
        key: upd.key,
        isRemoved: upd.isRemoved,
        isOplog: false,
        weight: upd.weight === -1 ? 0 : upd.weight,
        maxWeight: upd.maxWeight === -1 ? 0 : upd.maxWeight,
        isGenesis: upd.maxWeight !== -1 && !!upd.isGenesis,
        length: upd.length,
        clock: 0,
        isAdded: upd.isAdded
      }
    }

    if (upd.isAdded) info.isAdded = true
    if (upd.isRemoved) info.isRemoved = true
    if (upd.maxWeight !== -1) {
      info.isGenesis = upd.isGenesis
      info.maxWeight = upd.maxWeight
    }
    if (upd.weight !== -1) info.weight = upd.weight

    // length only ever advances
    if (upd.length > info.length) info.length = upd.length

    return info
  }

  async *list() {
    for await (const data of this.bee.createReadStream()) {
      if (data.key[0] === 1) {
        yield encoding.decodeSystemWriter(data.key, data.value)
      }
    }
  }

  async has(link) {
    // fast path
    for (let i = 0; i < this.heads.length; i++) {
      const h = this.heads[i]
      if (b4a.equals(h.key, link.key)) return h.length >= link.length
    }

    const node = await this.get(link.key)
    if (!node || node.length < link.length) return false
    return true
  }

  update(
    key,
    length,
    weight,
    maxWeight,
    isGenesis,
    isAdded,
    isRemoved,
    timestamp,
    grant,
    pending = -1
  ) {
    const id = b4a.toString(key, 'hex')

    if (isAdded) {
      this.writers.set(id, { key, added: true })
    } else if (isRemoved) {
      this.writers.set(id, { key, added: false })
    }

    let upd = this.updates.get(id)

    if (!upd) {
      upd = {
        key,
        length: 0,
        weight: -1,
        maxWeight: -1,
        isGenesis: false,
        isRemoved: false,
        isAdded: false,
        timestamp: 0,
        grant: null,
        pending: -1
      }
      this.updates.set(id, upd)
    }

    // resolved stamps are monotone per writer (resolve floors at prev)
    if (weight !== -1) upd.weight = upd.weight === -1 ? weight : Math.max(upd.weight, weight)
    if (maxWeight !== -1) {
      upd.maxWeight = maxWeight
      upd.isGenesis = isGenesis
    }
    if (isAdded) {
      upd.isAdded = true
      upd.isRemoved = false
    }
    if (isRemoved) {
      upd.isRemoved = true
      upd.isAdded = false
    }
    if (length > upd.length) {
      upd.length = length
    }

    if (timestamp > -1) {
      upd.timestamp = timestamp
    }
    if (pending > upd.pending) upd.pending = pending
    if (grant && (upd.grant === null || grant.weight > upd.grant.weight)) {
      upd.grant = grant
    }
  }

  async _updateWriter(upd, oplog) {
    const k = encoding.encodeSystemWriterKey(upd.key)

    const node = await this.bee.get(k)
    const v = node ? encoding.decodeSystemWriter(node.key, node.value) : null

    const prevWeight = v && v.weight !== undefined ? v.weight : 0
    const prevMaxWeight = v && v.maxWeight !== undefined ? v.maxWeight : 0
    const isOplog = b4a.equals(upd.key, oplog)

    const record = {
      version: 5,
      key: upd.key,
      isRemoved: upd.isRemoved ? true : upd.isAdded ? false : v ? v.isRemoved : false,
      isOplog,
      isGenesis: upd.maxWeight !== -1 ? !!upd.isGenesis : v ? v.isGenesis : false,
      weight: upd.weight !== -1 ? upd.weight : prevWeight,
      maxWeight: upd.maxWeight !== -1 ? upd.maxWeight : prevMaxWeight,
      length: Math.max(upd.length, v ? v.length : 0),
      timestamp: isOplog ? upd.timestamp : 0
    }

    return {
      key: k,
      value: encoding.encodeSystemWriter(record),
      add: null,
      remove: null,
      maxWeight: record.maxWeight
    }
  }

  async flush(batch, bee) {
    if (batch.length === 0) return []

    const oplog = batch[batch.length - 1].key

    const updates = [...this.updates.values()]
    const results = await Promise.all(updates.map((upd) => this._flushUpdate(upd, oplog)))
    const pendings = await this.promotions.flushPending(updates)

    const w = this.bee.write()

    for (const { writer } of results) {
      w.tryPut(writer.key, writer.value)
      if (writer.add) w.tryPut(writer.add, EMPTY)
      if (writer.remove) w.tryDelete(writer.remove)
    }

    for (const { grant } of results) {
      if (!grant) continue
      if (grant.remove) w.tryDelete(grant.key)
      else w.tryPut(grant.key, grant.value)
    }

    if (pendings) {
      for (const p of pendings) {
        if (p.remove) w.tryDelete(p.key)
        else w.tryPut(p.key, p.value)
      }
    }

    this.flushes++

    const info = {
      version: AUTOBEE_VERSION,
      view: bee.head(),
      heads: this.heads,
      timestamp: this.timestamp,
      flushes: this.flushes,
      indexers: null, // legacy
      pending: this.promotions.digest
    }

    w.tryPut(INFO_KEY, encoding.encodeSystemInfo(info))

    this.version = info.version
    this.view = info.view

    await w.flush()

    // Can enable if needed during debuggin
    // asserts.systemFlush(w)

    this.updates.clear()

    if (this.writers.size === 0) return []

    const changes = [...this.writers.values()]

    this.writers.clear()

    return changes
  }

  async _flushUpdate(upd, oplog) {
    const [writer, grant] = await Promise.all([
      this._updateWriter(upd, oplog),
      this.promotions.updateHint(upd)
    ])

    return { writer, grant }
  }

  pendingPromotion(key, opts) {
    return this.promotions.pending(key, opts)
  }

  listPendingPromotions(opts) {
    return this.promotions.list(opts)
  }

  grantHint(key, opts) {
    return this.promotions.hint(key, opts)
  }
}

function getInfo(data) {
  for (const { keys } of data.batch) {
    for (const k of keys) {
      const prefix = k.key[0]

      if (prefix === 0) {
        try {
          return encoding.decodeSystemInfo(k.value)
        } catch {
          return null
        }
      }
    }
  }

  return null
}
const b4a = require('b4a')
const encoding = require('./encoding.js')
const { createNode, finalizeNodeBatch } = require('./writers.js')
const asserts = require('./asserts.js')
const { LEGACY_OPLOG_VERSION } = require('./constants.js')

const EMPTY_HEAD = { length: 0, key: null }
const INFO_LEGACY_KEY = b4a.concat([b4a.from([0, 0]), b4a.from('info')])

const DEFAULT_OP_TIMEOUT = 5000

exports.sort = sort
exports.replay = replay
exports.getMostRecentHead = getMostRecentHead

exports.isLinking = isLinking
exports.isLinkingAll = isLinkingAll
exports.isLegacyNode = isLegacyNode

class Clock {
  constructor() {
    this.map = new Map()
  }

  add(link) {
    const hex = b4a.toString(link.key, 'hex')
    const p = this.map.get(hex)
    if (!p || p < link.length) this.map.set(hex, link.length)
  }

  has(link) {
    const hex = b4a.toString(link.key, 'hex')
    const p = this.map.get(hex)
    return p >= link.length
  }
}

async function getMostRecentHead(auto, system) {
  const ch = system.createChangesStream()
  for await (const data of ch) {
    const refs = getOplogCompat(data)
    if (!refs.length) continue

    const { oplog } = refs[0]
    return { key: oplog.key, length: oplog.length }
  }

  return null
}

// nodes we cannot resolve are returned as null rather than dropped, so callers
// can tell an incomplete replay from a short one
async function replay(auto) {
  const replay = []

  for await (const entry of oplogIterator(auto.system.bee)) {
    // the walk was cut short, nothing below this point is reachable
    if (entry === null) {
      replay.push(null)
      break
    }

    for (const ref of entry.refs) replay.push(ref)
  }

  // same reasoning as the iterator - never block on what we do not have
  const inflated = await inflate(auto, replay.reverse(), { wait: false })
  return inflated.flatMap((b) => (b === null ? [null] : b.batch))
}

async function sort(auto, batch) {
  const node = batch[0]
  const ch = auto.system.bee.createChangesStream()
  const reorder = []

  for await (const data of ch) {
    if (isLegacyNode(data)) break

    const ref = getOplog(data)
    const link = {
      key: ref.oplog.key,
      length: ref.oplog.length,
      // pinned resolved weight from this entry's own flush
      weight: ref.oplog.weight,
      timestamp: ref.oplog.timestamp
    }

    if (!isOrderedBefore(node, link)) break

    reorder.push(ref)
  }

  const inflated = await inflate(auto, reorder.reverse())

  // addSorted mutates inflated in place - capture original occupants first
  const undos = inflated.map((e) => e.undo)

  const { shared } = addSorted(inflated, { undo: null, batch, index: -1 })

  const tip = inflated.slice(shared).map((b) => b.batch)

  // rewind to the original occupant of the first changed position
  const undo = shared < undos.length ? undos[shared] : null

  return {
    undo,
    view: null,
    tip
  }
}

function addSorted(list, entry) {
  const node = entry.batch[0]
  const clock = new Clock()

  clock.add(node)
  for (const link of node.links) {
    clock.add(link)
  }

  if (node.witness) clock.add(node.witness.link)

  const tip = []
  const linked = []

  while (list.length) {
    // reached a stable sorting point
    if (cmp(node, list[list.length - 1].batch[0]) > 0) {
      break
    }

    const b = list.pop()
    const target = b.batch[0]

    // move past this node
    if (!clock.has(target)) {
      tip.push(b)
      continue
    }

    // keep entry identity (index/undo) for the shared-prefix scan below
    linked.push(b)

    // update the clock to catch linked nodes
    for (const link of target.links) {
      clock.add(link)
    }
  }

  while (linked.length) addSorted(list, linked.pop())
  list.push(entry)
  while (tip.length) list.push(tip.pop())

  let shared = 0
  while (shared < list.length) {
    if (list[shared].index === shared) {
      shared++
      continue
    }

    break
  }

  return { shared }
}

function isOrderedBefore(node, oplog) {
  return cmp(node, oplog) < 0
}

function cmp(a, b) {
  const w = a.weight - b.weight
  if (w) return w > 0 ? -1 : 1

  const t = a.timestamp - b.timestamp
  if (t) return t < 0 ? -1 : 1

  const c = b4a.compare(a.key, b.key)
  if (c) return c

  return a.length < b.length ? -1 : 1
}

async function inflate(auto, reorder, { wait = true, timeout = DEFAULT_OP_TIMEOUT } = {}) {
  const readOpts = wait ? { timeout } : { wait: false }

  const promises = []
  const sessions = new Map()

  for (let i = 0; i < reorder.length; i++) {
    const ref = reorder[i]

    // marker for a node we could not resolve, pass it through
    if (ref === null) {
      promises.push(null)
      continue
    }

    const id = b4a.toString(ref.oplog.key, 'hex')
    // only seeds the node - reapplication re-resolves from the witness
    const weight = ref.oplog.weight

    const local = auto.writers.localWriter
    if (local && local.id === id) {
      promises.push(getLocalBatch(ref.undo, local, ref.oplog.length, weight, i))
      continue
    }

    let core = sessions.get(id)
    if (!core) {
      core = auto.openCore(ref.oplog.key)
      sessions.set(id, core)
    }

    promises.push(getOplogBatch(ref.undo, core, ref.oplog.length, weight, i, readOpts))
  }

  try {
    return await Promise.all(promises)
  } finally {
    for (const core of sessions.values()) await core.close()
  }
}

async function getOplogBatch(undo, core, length, weight, index, readOpts = null) {
  const seq = length - 1

  const block = await core.get(seq, readOpts)
  if (block === null) return null

  const oplog = encoding.decodeOplog(block)
  const head = createNode(core, length, weight, oplog)
  const legacy = oplog.version <= LEGACY_OPLOG_VERSION

  // if this is legacy the start pointer is always 0, fix
  if (legacy) {
    for (let i = length - 1; i > 0; i--) {
      const block = await core.get(i - 1, readOpts)
      if (block === null) return null // wait: false in replay helper

      const oplog = encoding.decodeOplog(block)
      if (oplog.batch.end === 0) break
      head.batch.start++
    }
  }

  const batch = []
  const result = { undo, batch, index }

  const remaining = []
  const start = seq - head.batch.start
  const end = seq + head.batch.end // skip head, we have it

  for (let i = start; i < end; i++) {
    remaining.push(core.get(i, readOpts))
  }

  if (remaining.length) {
    const blocks = await Promise.all(remaining)
    for (let i = 0; i < blocks.length; i++) {
      // keep the hole so the gap in the batch stays visible
      if (blocks[i] === null) {
        batch.push(null)
        continue
      }

      const oplog = encoding.decodeOplog(blocks[i])
      batch.push(createNode(core, i + start + 1, weight, oplog))
    }
  }

  batch.push(head)
  finalizeNodeBatch(batch)

  return result
}

function getLocalBatch(undo, writer, length, weight, index) {
  if (writer.core.closed) throw new Error('Writer core is closed')

  if (length > writer.core.length) {
    if (!writer.pending) {
      throw new Error(
        `No local nodes: looking for ${length}, found ${writer.core.length}@${writer.core.id}`
      )
    }

    let i = 0
    while (i < writer.pending.length) {
      if (writer.pending[i].length === length) break
      i++
    }

    const head = writer.pending[i]
    const headBatch = head.batch || { start: 0, end: 0 }

    const start = i - headBatch.start
    const end = i + headBatch.end + 1 // include head

    const batch = writer.pending.slice(start, end)
    return { undo, batch, index }
  }

  return getOplogBatch(undo, writer.core, length, weight, index)
}

// tolerates legacy nodes instead of asserting
async function* oplogIterator(bee) {
  try {
    for await (const node of bee.createChangesStream({ wait: false })) {
      const legacy = isLegacyNode(node)
      const refs = getOplogCompat(node)
      if (refs.length) yield { node, refs, legacy }
    }
  } catch {
    yield null // signal data not available
  }
}

function getOplog(data) {
  for (const { keys } of data.batch) {
    if (!keys) continue

    for (const k of keys) {
      const prefix = k.key[0]

      if (prefix === 1) {
        // expect inlined for now
        const value = encoding.decodeSystemWriter(k.key, k.value)
        if (!value.isOplog) continue

        return { undo: data.tail || EMPTY_HEAD, oplog: value }
      }
    }
  }

  asserts.bail('Bad system node')
}

// legacy nodes carry no isOplog flag so we cannot tell which writer update is
// the oplog to fetch - yield every candidate until we have a better signal
function getOplogCompat(data) {
  const undo = data.tail || EMPTY_HEAD
  const oplogs = []
  const candidates = []

  for (const { keys } of data.batch) {
    for (const k of keys) {
      if (k.key[0] !== 1) continue

      let value = null
      try {
        value = encoding.decodeSystemWriter(k.key, k.value)
      } catch {
        continue
      }

      // writer was just added, nothing to fetch
      if (!value.length) continue

      if (value.isOplog) oplogs.push({ undo, oplog: value })
      else candidates.push({ undo, oplog: value })
    }
  }

  return oplogs.length ? oplogs : candidates
}

function isLinking(node, link) {
  if (node.length >= link.length && b4a.equals(node.key, link.key)) {
    return true
  }

  for (const l of node.links) {
    if (l.length >= link.length && b4a.equals(l.key, link.key)) {
      return true
    }
  }

  return false
}

function isLinkingAll(node, heads) {
  for (const h of heads) {
    if (!isLinking(node, h)) return false
  }

  return true
}

function isLegacyNode(data) {
  for (const { keys } of data.batch) {
    if (!keys) continue
    for (const k of keys) {
      if (b4a.equals(k.key, INFO_LEGACY_KEY)) return true
    }
  }
  return false
}
module.exports = class Triggers {
  constructor() {
    this.waiting = new Map()
  }

  add(id, length, writer) {
    let w = this.waiting.get(id)

    if (!w) {
      w = []
      this.waiting.set(id, w)
    }

    for (let i = 0; i < w.length; i++) {
      const entry = w[i]
      if (entry.writer !== writer) continue
      entry.length = length
      return entry
    }

    const entry = { index: w.length, id, writer, length }
    w.push(entry)
    return entry
  }

  remove(entry) {
    if (!entry.writer) return

    const w = this.waiting.get(entry.id)
    if (w) this._remove(w, entry)
  }

  has(id) {
    return this.waiting.get(id) !== undefined
  }

  get(id) {
    return this.waiting.get(id) || null
  }

  trigger(id, length) {
    const w = this.waiting.get(id)
    if (!w) return

    for (let i = w.length - 1; i >= 0; i--) {
      const entry = w[i]
      if (entry.length > length) continue

      const writer = entry.writer
      this._remove(w, entry)
      writer.bump()
    }
  }

  _remove(w, entry) {
    const head = w.pop()

    if (head !== entry) {
      w[entry.index] = head
      head.index = entry.index
    }

    entry.writer = null

    if (w.length === 0) {
      this.waiting.delete(entry.id)
    }
  }
}
const b4a = require('b4a')

class TrustedPeers {
  constructor(hooks) {
    this.trusted = new Map()
    this.pending = new Map()
    this.hooks = hooks
  }

  async isTrusted(key, reference) {
    const hex = b4a.toString(key, 'hex')
    if (this.trusted.get(hex)) return true

    let promise = this.pending.get(hex)
    if (promise) return promise

    promise = this.hooks.isTrusted
      ? this.hooks.isTrusted(key, reference)
      : Promise.resolve(!this.hooks.mostRecentTrusted)
    this.pending.set(hex, promise)

    const trusted = await promise
    if (trusted) this.trusted.set(hex, trusted)

    if (this.pending.get(hex) === promise) this.pending.delete(hex)
    return trusted
  }

  read(trusted) {
    return trusted || null
  }

  mostRecentTrusted(target, reference) {
    if (!this.hooks.mostRecentTrusted) return Promise.resolve(null)
    return this.hooks.mostRecentTrusted(target, reference)
  }

  clear() {
    this.pending.clear()
    this.trusted.clear()
  }
}

module.exports = TrustedPeers
module.exports = class UpdateChanges {
  constructor(auto) {
    this.auto = auto
    this.byName = new Map()
  }

  get(key) {
    return this.byName.get(key)
  }

  track() {
    this.tracking = {
      view: this.auto._workingBee,
      system: this.auto.system.bee
    }

    for (const k in this.tracking) {
      this.byName.set(k, {
        flushes: this.auto.system.flushes,
        from: this.tracking[k].head(),
        to: null,
        incremental: false
      })
    }
  }

  finalise() {
    // flush count at `to` - safe to read live, state is frozen for the hook
    const flushes = this.auto.system.flushes

    for (const k in this.tracking) {
      const tracked = this.byName.get(k)

      tracked.to = this.tracking[k].head()
      tracked.flushes = flushes
    }

    return this.byName
  }
}
const b4a = require('b4a')
const encoding = require('./encoding.js')

exports.currentWeight = currentWeight
exports.resolveWeight = resolveWeight
exports.verifyClaim = verifyClaim

function currentWeight(rec) {
  if (!rec) return 0
  if (rec.maxWeight > 0 && rec.isGenesis) return rec.maxWeight
  return rec.weight
}

function seedWeight(auto, node) {
  if (node.length === 1 && b4a.equals(node.key, auto.key)) return auto.bootstrapWeight
  return 0
}

async function resolveWeight(auto, node) {
  const rec = await auto.system.get(node.key)
  const prev = rec ? currentWeight(rec) : seedWeight(auto, node)

  const witness = node.witness
  if (!witness) return prev

  const { weight, link } = witness
  if (weight <= prev) return prev
  if (!(await verifyClaim(auto, node.key, weight, link))) return prev

  return weight
}

async function verifyClaim(auto, key, weight, link) {
  if (!link || !link.length) return false

  const anchor = await readOplogNode(auto, link.key, link.length)
  const entry = anchor.approvals ? anchor.approvals.find((a) => b4a.equals(a.key, key)) : null
  if (!entry || entry.weight < weight) return false

  return approverQualified(auto, link.key, entry.weight)
}

// the approval sits in the causal past of any citation of it, so the approver's
// record is lower-bounded by the standing it held when it approved - no need to
// walk its own witness chain back to genesis
async function approverQualified(auto, key, weight) {
  if (b4a.equals(key, auto.key)) return auto.bootstrapWeight >= weight

  const rec = await auto.system.get(key)
  return currentWeight(rec) >= weight
}

async function readOplogNode(auto, key, length) {
  const core = auto.openCore(key)

  try {
    await core.ready()
    const block = await core.get(length - 1)
    return encoding.decodeOplog(block)
  } finally {
    await core.close()
  }
}
const b4a = require('b4a')
const encoding = require('./encoding.js')
const Triggers = require('./triggers.js')
const { LEGACY_OPLOG_VERSION } = require('./constants.js')
const { resolveWeight } = require('./witness.js')
const { WriterEncryption } = require('autobee-encryption')

const WRITER_PREFETCH = 10

const BATCH_WRITER_SANITY = 1024
const BATCH_OPTIMISTIC_SANITY = 32

const MAX_FORWARD_TIME_DRIFT = 2 * 3600 * 1000

class ActiveWriters {
  constructor(auto) {
    const local = auto.local

    this.auto = auto
    this.active = new Map()
    this.triggers = new Triggers()
    this.localWriter = null
    this.writable = false
    this.pending = []

    this._pendingBuffer = new Map()
    this._adding = new Map()

    this.localWriter = new Writer(this, auto.system, true, local, b4a.toString(local.key, 'hex'))
    this.localWriter.attach()
  }

  get size() {
    return this.active.size
  }

  [Symbol.iterator]() {
    return this.active.values()
  }

  async close() {
    const closing = [this.localWriter.close()]
    for (const w of this.active.values()) closing.push(w.close())
    this.active.clear()
    this.pending.length = 0
    await Promise.allSettled(closing)
  }

  *external() {
    for (const w of this.active.values()) {
      if (w !== this.localWriter) yield w
    }
  }

  bump() {
    this.auto.bumpSoon()
  }

  async wakeup(key, length) {
    const id = b4a.toString(key, 'hex')
    let w = this.active.get(id)

    if (w === this.localWriter) return

    let added = false
    if (!w) {
      w = await this.add(key)
      if (!w) return
      added = true
    }

    const advanced = length > w.hintedLength

    // remember the announced length so gc doesn't drop a writer we haven't caught up on
    w.hint(length)

    // prefetch the oplog head for the fast-forward that runs right after us
    if (w && length > 0) w.core.get(length - 1).catch(noop)

    if (added || advanced) this.bump()
  }

  getLatestLocalOplog() {
    return this.localWriter.getLatest()
  }

  appendLocal(value, timestamp, batch, links, optimistic, witness, approvals = null) {
    return this.localWriter.append(value, timestamp, batch, links, optimistic, witness, approvals)
  }

  async rotateLocalWriter(core) {
    this.clearLocal()
    await this.localWriter.detachAndClose()

    this.localWriter = new Writer(this, this.auto.system, true, core, b4a.toString(core.key, 'hex'))
    this.localWriter.addPending()
    this.auto.emit('writer', this.localWriter)

    await this.updateLocalState()
  }

  clearLocal() {
    return this.localWriter.clear()
  }

  async flushLocal(views) {
    const w = this.localWriter

    // only consume the queue when a flush will actually write
    if (w.pending === null || w.processed === 0) return

    const trusted = await this.auto.trusted.mostRecentTrusted(this.auto._workingView.view, null)

    return w.flush(views, trusted)
  }

  async refresh() {
    // peeked batches were read against the pre-reboot system, drop them
    this._pendingBuffer.clear()

    if (this.localWriter && !this.localWriter.isClosed) {
      await this.updateLocalState()
    }

    for (const writer of this.active.values()) {
      if (writer.isFrozen && writer !== this.localWriter) await writer.detachAndClose()
      else await writer.reset()
    }
  }

  async updateLocalState() {
    await this.localWriter.update()
  }

  async nextPendingNode({ local = false } = {}) {
    for (const w of this.pending.slice()) {
      if (local && w !== this.localWriter) continue
      if (this._pendingBuffer.has(w) || w.isFrozen) continue

      const batch = await w.next()
      if (batch !== null) this._pendingBuffer.set(w, batch)
    }

    if (this._pendingBuffer.size === 0) return null

    let writer = null
    let best = null

    for (const [w, batch] of this._pendingBuffer) {
      if (local && w !== this.localWriter) continue
      const head = batch[0]
      // -1 marks a node next() has not resolved a sort weight for yet
      if (head.weight < 0) head.weight = await resolveWeight(this.auto, head)
      if (best === null || sortsBefore(head, best[0])) {
        writer = w
        best = batch
      }
    }

    if (writer === null) return null

    this._pendingBuffer.delete(writer)
    return { writer, batch: best }
  }

  _updateLocalState() {
    const w = this.localWriter

    const writable = w.isAdded && !w.isRemoved
    if (writable === this.writable) return

    this.writable = writable
    this.auto.emit(this.writable ? 'writable' : 'unwritable')
    this.auto.emit('update')
  }

  has(id) {
    return !!this.active.get(id)
  }

  add(key) {
    const id = b4a.toString(key, 'hex')

    let adding = this._adding.get(id)
    if (adding) return adding

    adding = this._add(key, id)
    this._adding.set(id, adding)

    return adding.finally(() => {
      this._adding.delete(id)
    })
  }

  // TODO: correct fix is writer as ReadyResource
  async _add(key, id) {
    const local = b4a.equals(key, this.auto.local.key)
    if (local) await this.updateLocalState()

    let w = this.active.get(id)
    if (w) return w

    // no new sessions once teardown started
    if (this.auto._interrupting) return null

    const encryption = this.auto.encryptionKey ? new WriterEncryption(this.auto) : null

    const core = this.auto.store.get({
      active: false,
      key,
      encryption,
      group: local || !this.auto.wakeupCapability ? null : this.auto.wakeupCapability.discoveryKey
    })

    await core.ready()
    await core.setUserData('referrer', this.auto.key)

    // teardown may have started underneath us
    if (this.auto._interrupting) {
      await core.close()
      return null
    }

    w = new Writer(this, this.auto.system, false, core, id)
    w.attach()

    return w
  }

  async remove(key) {
    if (b4a.equals(key, this.auto.local.key)) await this.updateLocalState()

    const id = b4a.toString(key, 'hex')
    const w = this.active.get(id)
    if (!w) return

    // nodes this writer appended before the removal may still sort before it,
    // and their ordering advances its record everywhere - closing the session
    // while we are behind strands us one node short forever. reset() closes it
    // once we have caught up
    if (w !== this.localWriter) {
      const length = await w.update()
      if (length < w.core.length || length < w.hintedLength) {
        w.addPending()
        return
      }
    }

    w.detach()
    w.decouple()
    if (w !== this.localWriter) await w.close()
  }
}

class Writer {
  constructor(writers, system, local, core, id) {
    this.writers = writers
    this.system = system

    this.core = core
    this.id = id
    this.index = 0
    this.weight = 0
    this.pending = null
    this.processed = 0
    this.appendLength = this.core.length + 1
    this.waiting = null
    this.download = null
    this.hintedLength = 0

    this.isPending = false
    this.isClosed = false
    this.isAttached = false
    this.isAdded = false
    this.isRemoved = false
    this.isCoupled = false
    this.isFrozen = false
    this.isHead = false

    this._onchangeBound = this._onchange.bind(this)

    if (!local) this.core.on('append', this._onchangeBound)
    if (!local) this.core.on('download', this._onchangeBound)
  }

  get isIndexer() {
    return this.weight >= 2
  }

  isSystemHead() {
    if (this.core.length === 0) return false

    if (this.system.heads.length === 0) {
      return b4a.equals(this.core.key, this.writers.auto.key)
    }

    return this.system.isHead(this.core.key)
  }

  couple() {
    if (this.isCoupled) return
    // if we don't have coupler yet we can't add
    if (this.writers.auto._wakeup.addCore(this.core)) {
      this.isCoupled = true
    }
  }

  decouple() {
    if (!this.isCoupled) return
    // if we don't have coupler yet we can't remove
    if (this.writers.auto._wakeup.removeCore(this.core)) {
      this.isCoupled = false
    }
  }

  hasReferrals() {
    const entries = this.writers.triggers.get(this.id)
    if (!entries) return false
    // TODO: should implement a system where we KNOW that a future writers IS not removed
    // to avoid accidental spam, for now kept simple as the algo still runs
    return true
  }

  addPending() {
    if (this.isPending) return
    this.isPending = true
    this.index = this.writers.pending.push(this) - 1
  }

  removePending() {
    if (!this.isPending) return
    this.isPending = false

    this.writers._pendingBuffer.delete(this)

    const head = this.writers.pending.pop()
    if (head !== this) {
      this.writers.pending[this.index] = head
      head.index = this.index
    }
  }

  // a peer announced this length for us, so we're not caught up until we've seen it
  hint(length) {
    if (length > this.hintedLength) {
      this.hintedLength = length
      this.core.setActive(true)
    }
  }

  _onchange() {
    this.hint(this.core.length)
    if (this.waiting === null) this.bump()
  }

  bump() {
    this.waiting = null
    if (this.isAttached) this.addPending() // TODO: remove the isAttach guard
    this.writers.bump()
  }

  attach() {
    if (this.isAttached) return
    this.isAttached = true
    this.writers.active.set(this.id, this)
    this.writers.auto.emit('writer', this)
    this.addPending()
  }

  detach() {
    if (!this.isAttached) return
    this.isAttached = false
    if (this.download) this.download.destroy()
    this.download = null
    this.writers.active.delete(this.id)
    this.removePending()
  }

  async reset() {
    this.removePending()

    if (this.waiting) {
      this.writers.triggers.remove(this.waiting)
      this.waiting = null
    }

    const length = await this.update()

    // don't drop any local nodes left in buffer, or work a peer told us about
    if (length < this.core.length || length < this.hintedLength || this.appending) {
      this.addPending()
    } else if (this !== this.writers.localWriter && !this.isHead) {
      await this.detachAndClose()
    }
  }

  close() {
    this.isClosed = true

    if (this.waiting) {
      this.writers.triggers.remove(this.waiting)
      this.waiting = null
    }

    this.removePending()

    return this.core.close()
  }

  detachAndClose() {
    this.detach()
    this.decouple()
    return this.close()
  }

  async views() {
    const latest = await this.getLatest()
    if (!latest || !latest.views) return []

    // signedLength for autobase compat
    const system = latest.views.system.start + latest.views.system.length
    const view = latest.views.view.start + latest.views.view.length

    return [
      { key: latest.views.system.key, length: system, signedLength: system },
      { key: latest.views.view.key, length: view, signedLength: view }
    ]
  }

  async getLatest() {
    if (this.core.length === 0) return null
    const block = await this.core.get(this.core.length - 1)
    const oplog = encoding.decodeOplog(block)
    return oplog
  }

  async update() {
    const info = await this.system.get(this.core.key)
    const length = info ? info.length : 0

    const isHead = this.isSystemHead()

    if (isHead) {
      this.isHead = true
      this.couple()
    } else {
      this.isHead = false
      this.decouple()
    }

    const active = length < this.core.length || length < this.hintedLength

    if (this.isHead || active) {
      this.core.setActive(true)
    } else {
      this.core.setActive(false)
    }

    if (!info) {
      this.isAdded = b4a.equals(this.core.key, this.writers.auto.key)
    } else {
      this.isAdded = true
      this.isRemoved = info.isRemoved
      // capability (isIndexer etc), NOT the resolved sort weight
      this.weight = info.maxWeight
    }

    if (this === this.writers.localWriter) {
      this.writers._updateLocalState()
    }

    return length
  }

  next() {
    if (this.isFrozen) return null
    return this._next()
  }

  async _next() {
    if (this.waiting !== null) return null

    const batch = []
    const genesis = this.system.isGenesis()

    let length = await this.update()
    let optimistic = false
    let timestamp = 0

    if (this.download) this.download.destroy()
    this.download = this.core.download({ start: length, end: length + WRITER_PREFETCH })

    while (length < this.core.length) {
      // @todo aim to prefetch when writer added, with read-ahead
      // download the block if it's not available locally
      // writer will be added back as pending automatically
      if (!(await this.core.has(length))) {
        this.removePending()
        return null
      }

      let oplog
      try {
        const block = await this.core.get(length++)
        oplog = encoding.decodeOplog(block)
      } catch {
        this.isFrozen = true
        this.removePending()
        return null
      }

      const node = createNode(this.core, length, -1, oplog)

      if (this.writers.writable && this === this.writers.localWriter) {
        node.optimistic = false
      }

      const b = oplog.batch

      // auto correct some batch invariants
      node.timestamp = timestamp = Math.max(node.timestamp, timestamp)
      node.optimistic = optimistic = optimistic || node.optimistic

      if (!this.isAdded && !node.optimistic && !genesis) {
        return null
      }

      // an optimistic writer cannot be genesis
      if (node.optimistic && genesis) {
        return null
      }

      if (node.optimistic && !this.isAdded && b.end >= BATCH_OPTIMISTIC_SANITY) {
        return null
      }

      if (node.timestamp - Date.now() > MAX_FORWARD_TIME_DRIFT) {
        return null
      }

      if (!node.optimistic && b.start === 0 && b.end > 1 && b.end < BATCH_WRITER_SANITY) {
        this.core.download({ start: length, end: length + oplog.batch.end })
      }

      if (!(await this._isLinked(node))) {
        this.removePending()
        return null
      }

      batch.push(node)
      if (b.end === 0) return finalizeNodeBatch(batch)

      // TODO: mark as dead
      if (batch.length >= BATCH_WRITER_SANITY) return null
    }

    // handle local writer
    if (this.core.writable && this.pending !== null) {
      let start = this.processed
      const end = this.pending.length

      while (start < end && this.pending[start].length <= length) {
        this.processed = ++start
      }

      if (start < end && !this.writers.writable) {
        const node = this.pending[start]
        if (!node.optimistic) {
          this.writers.clearLocal()
          throw new Error('Not writable')
        }
      }

      for (let i = start; i < end; i++) {
        const node = this.pending[i]

        // an optimistic writer cannot be genesis
        if (node.optimistic && genesis) break
        if (node.optimistic && !this.isAdded && this.pending.length >= BATCH_OPTIMISTIC_SANITY) {
          break
        }
        if (node.timestamp - Date.now() > MAX_FORWARD_TIME_DRIFT) break

        if (!(await this._isLinked(node))) break

        this.processed = i + 1

        if (node.batch.end === 0) break
      }

      if (this.processed === this.pending.length) {
        this.removePending()
      }

      if (start === this.processed) return null
      return this.pending.slice(start, this.processed)
    }

    return null
  }

  notify(batch) {
    this.writers.triggers.trigger(this.id, batch[batch.length - 1].length)
  }

  async _isLinked(node) {
    let links = node.links
    if (node.witness) {
      links = links.slice()
      links.push({ key: node.witness.link.key, length: node.witness.link.length })
    }

    const promises = new Array(links.length)

    for (let i = 0; i < links.length; i++) {
      const link = links[i]

      // legacy batch nodes link their own previous node, satisfied by oplog order
      if (link.length < node.length && b4a.equals(link.key, this.core.key)) {
        promises[i] = true
        continue
      }

      promises[i] = this.system.has(link)
    }

    const has = await Promise.all(promises)

    for (let i = 0; i < links.length; i++) {
      if (has[i]) continue
      const link = links[i]
      const id = b4a.toString(link.key, 'hex')

      this.waiting = this.writers.triggers.add(id, link.length, this)

      // check it didn't arrive underneath us
      if (await this.system.has(link)) {
        this.writers.triggers.remove(this.waiting)
        this.waiting = null
        continue
      }

      this.writers.wakeup(link.key, link.length).catch(noop)
      return false
    }

    return true
  }

  get appending() {
    return this.pending !== null
  }

  append(value, timestamp, batch, links, optimistic, witness, approvals = null) {
    if (this.pending === null) this.pending = []

    const oplog = {
      version: encoding.OPLOG_VERSION,
      timestamp,
      links,
      batch,
      views: null,
      trusted: null,
      witness: witness || null,
      approvals,
      optimistic: optimistic && !this.writers.writable,
      value
    }

    const node = createNode(this.core, this.appendLength++, -1, oplog)

    this.pending.push(node)
    this.addPending()

    return node
  }

  clear() {
    if (!this.pending) return

    if (this.processed === this.pending.length) {
      this.pending = null
    } else {
      this.pending = this.pending.slice(this.processed)
    }

    this.processed = 0
  }

  async flush(views, trusted) {
    if (this.pending === null || this.processed === 0) return

    const head = this.pending[this.processed - 1]
    head.views = views

    head.trusted = trusted ? [{ key: trusted.key, length: trusted.length }] : null
    this.writers.auto.emit('appending', this.pending)

    const buffers = []
    for (let i = 0; i < this.processed; i++) {
      buffers.push(encoding.encodeOplog(this.pending[i]))
    }

    try {
      await this.core.append(buffers)
    } finally {
      this.clear()
    }
  }
}

// mirrors topo.cmp - kept local because topo.js requires this module
function sortsBefore(a, b) {
  if (a.weight !== b.weight) return a.weight > b.weight
  if (a.timestamp !== b.timestamp) return a.timestamp < b.timestamp

  const c = b4a.compare(a.key, b.key)
  if (c !== 0) return c < 0

  return a.length < b.length
}

exports.ActiveWriters = ActiveWriters
exports.Writer = Writer
exports.createNode = createNode
exports.finalizeNodeBatch = finalizeNodeBatch

function createNode(core, length, weight, oplog) {
  return {
    core,
    from: core, // compat
    key: core.key,
    length,
    weight,
    version: oplog.version,
    timestamp: oplog.timestamp,
    links: oplog.links,
    batch: oplog.batch,
    views: oplog.views,
    trusted: oplog.trusted || [],
    witness: oplog.witness || null,
    approvals: oplog.approvals || null,
    optimistic: oplog.optimistic,
    value: oplog.value
  }
}

function finalizeNodeBatch(batch) {
  // replay batches keep unreachable blocks as null holes (topo.getOplogBatch)
  let first = null
  for (const node of batch) {
    if (node !== null) {
      first = node
      break
    }
  }
  if (first === null || first.version > LEGACY_OPLOG_VERSION) return batch

  for (let i = 0; i < batch.length; i++) {
    if (batch[i] !== null) batch[i].batch.start = i
  }

  return batch
}

function noop() {}
{
  "name": "autobee",
  "version": "2.1.1",
  "description": "Bee based autobase",
  "license": "Apache-2.0",
  "author": "Holepunch Inc.",
  "main": "index.js",
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/autobee.git"
  },
  "files": [
    "index.js",
    "lib/*.js",
    "encoding"
  ],
  "scripts": {
    "format": "prettier . --write",
    "lint": "lunte && prettier . --check",
    "test": "node test/all.js",
    "test:bare": "bare test/all.js",
    "test:encrypted": "bare test/all.js --encrypt-all",
    "test:generate": "brittle -r test/all.js test/*.js"
  },
  "dependencies": {
    "autobee-encryption": "^0.1.2",
    "autobee-wakeup": "^1.1.3",
    "b4a": "^1.8.0",
    "compact-encoding": "^3.3.0",
    "hyperbee2": "^2.10.3",
    "hypercore": "^11.33.2",
    "hypercore-id-encoding": "^1.3.0",
    "hyperschema": "^1.20.1",
    "ready-guard": "^1.0.0",
    "ready-resource": "^1.2.0",
    "resolve-reject-promise": "^1.1.0",
    "safety-catch": "^1.0.3"
  },
  "devDependencies": {
    "brittle": "^4.1.0",
    "corestore": "^7.12.2",
    "hypercore-crypto": "^3.6.1",
    "lunte": "^1.6.0",
    "prettier": "^3.8.1",
    "prettier-config-holepunch": "^2.0.0",
    "protomux-wakeup": "^2.9.0",
    "replication-simulator": "^3.0.0"
  }
}
function isBuffer(value) {
  return Buffer.isBuffer(value) || value instanceof Uint8Array
}

function isEncoding(encoding) {
  return Buffer.isEncoding(encoding)
}

function alloc(size, fill, encoding) {
  return Buffer.alloc(size, fill, encoding)
}

function allocUnsafe(size) {
  return Buffer.allocUnsafe(size)
}

function allocUnsafeSlow(size) {
  return Buffer.allocUnsafeSlow(size)
}

function byteLength(string, encoding) {
  return Buffer.byteLength(string, encoding)
}

function compare(a, b) {
  return Buffer.compare(a, b)
}

function concat(buffers, totalLength) {
  return Buffer.concat(buffers, totalLength)
}

function copy(source, target, targetStart, start, end) {
  return toBuffer(source).copy(target, targetStart, start, end)
}

function equals(a, b) {
  return toBuffer(a).equals(b)
}

function fill(buffer, value, offset, end, encoding) {
  return toBuffer(buffer).fill(value, offset, end, encoding)
}

function from(value, encodingOrOffset, length) {
  return Buffer.from(value, encodingOrOffset, length)
}

function includes(buffer, value, byteOffset, encoding) {
  return toBuffer(buffer).includes(value, byteOffset, encoding)
}

function indexOf(buffer, value, byfeOffset, encoding) {
  return toBuffer(buffer).indexOf(value, byfeOffset, encoding)
}

function lastIndexOf(buffer, value, byteOffset, encoding) {
  return toBuffer(buffer).lastIndexOf(value, byteOffset, encoding)
}

function swap16(buffer) {
  return toBuffer(buffer).swap16()
}

function swap32(buffer) {
  return toBuffer(buffer).swap32()
}

function swap64(buffer) {
  return toBuffer(buffer).swap64()
}

function toBuffer(buffer) {
  if (Buffer.isBuffer(buffer)) return buffer
  return Buffer.from(buffer.buffer, buffer.byteOffset, buffer.byteLength)
}

function toString(buffer, encoding, start, end) {
  return toBuffer(buffer).toString(encoding, start, end)
}

function write(buffer, string, offset, length, encoding) {
  return toBuffer(buffer).write(string, offset, length, encoding)
}

function readDoubleBE(buffer, offset) {
  return toBuffer(buffer).readDoubleBE(offset)
}

function readDoubleLE(buffer, offset) {
  return toBuffer(buffer).readDoubleLE(offset)
}

function readFloatBE(buffer, offset) {
  return toBuffer(buffer).readFloatBE(offset)
}

function readFloatLE(buffer, offset) {
  return toBuffer(buffer).readFloatLE(offset)
}

function readInt32BE(buffer, offset) {
  return toBuffer(buffer).readInt32BE(offset)
}

function readInt32LE(buffer, offset) {
  return toBuffer(buffer).readInt32LE(offset)
}

function readUInt32BE(buffer, offset) {
  return toBuffer(buffer).readUInt32BE(offset)
}

function readUInt32LE(buffer, offset) {
  return toBuffer(buffer).readUInt32LE(offset)
}

function writeDoubleBE(buffer, value, offset) {
  return toBuffer(buffer).writeDoubleBE(value, offset)
}

function writeDoubleLE(buffer, value, offset) {
  return toBuffer(buffer).writeDoubleLE(value, offset)
}

function writeFloatBE(buffer, value, offset) {
  return toBuffer(buffer).writeFloatBE(value, offset)
}

function writeFloatLE(buffer, value, offset) {
  return toBuffer(buffer).writeFloatLE(value, offset)
}

function writeInt32BE(buffer, value, offset) {
  return toBuffer(buffer).writeInt32BE(value, offset)
}

function writeInt32LE(buffer, value, offset) {
  return toBuffer(buffer).writeInt32LE(value, offset)
}

function writeUInt32BE(buffer, value, offset) {
  return toBuffer(buffer).writeUInt32BE(value, offset)
}

function writeUInt32LE(buffer, value, offset) {
  return toBuffer(buffer).writeUInt32LE(value, offset)
}

module.exports = {
  isBuffer,
  isEncoding,
  alloc,
  allocUnsafe,
  allocUnsafeSlow,
  byteLength,
  compare,
  concat,
  copy,
  equals,
  fill,
  from,
  includes,
  indexOf,
  lastIndexOf,
  swap16,
  swap32,
  swap64,
  toBuffer,
  toString,
  write,
  readDoubleBE,
  readDoubleLE,
  readFloatBE,
  readFloatLE,
  readInt32BE,
  readInt32LE,
  readUInt32BE,
  readUInt32LE,
  writeDoubleBE,
  writeDoubleLE,
  writeFloatBE,
  writeFloatLE,
  writeInt32BE,
  writeInt32LE,
  writeUInt32BE,
  writeUInt32LE
}
{
  "name": "b4a",
  "version": "1.8.1",
  "description": "Bridging the gap between buffers and typed arrays",
  "exports": {
    "./package": "./package.json",
    ".": {
      "react-native": "./react-native.js",
      "browser": "./browser.js",
      "default": "./index.js"
    }
  },
  "files": [
    "browser.js",
    "index.js",
    "react-native.js",
    "lib"
  ],
  "scripts": {
    "format": "prettier --write . && lunte --fix",
    "lint": "prettier --check . && lunte",
    "test": "npm run test:bare && npm run test:node",
    "test:bare": "brittle-bare --coverage test.mjs",
    "test:node": "brittle-node --coverage test.mjs"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/b4a.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/b4a/issues"
  },
  "homepage": "https://github.com/holepunchto/b4a#readme",
  "devDependencies": {
    "brittle": "^3.5.2",
    "lunte": "^1.7.0",
    "nanobench": "^3.0.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^2.0.0"
  },
  "peerDependencies": {
    "react-native-b4a": "*"
  },
  "peerDependenciesMeta": {
    "react-native-b4a": {
      "optional": true
    }
  }
}
module.exports = require.addon()
const binding = require('./binding')

module.exports = binding.abort
{
  "name": "bare-abort",
  "version": "2.0.13",
  "description": "Cause abnormal program termination and generate a crash report",
  "exports": {
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    },
    "./package": "./package.json"
  },
  "files": [
    "index.js",
    "index.d.ts",
    "binding.c",
    "binding.js",
    "CMakeLists.txt",
    "prebuilds"
  ],
  "addon": true,
  "scripts": {
    "test": "prettier . --check"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-abort.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-abort/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-abort#readme",
  "devDependencies": {
    "cmake-bare": "^1.1.7",
    "prettier": "^3.4.2",
    "prettier-config-standard": "^7.0.0"
  }
}
const ESC = '\x1b'
const CSI = ESC + '['
const SGR = (n) => CSI + n + 'm'

exports.constants = {
  ESC,
  CSI,
  SGR
}

// https://en.wikipedia.org/wiki/ANSI_escape_code#CSI_(Control_Sequence_Introducer)_sequences

exports.cursorHide = CSI + '?25l'
exports.cursorShow = CSI + '?25h'

exports.cursorUp = function cursorUp(n = 1) {
  return CSI + n + 'A'
}

exports.cursorDown = function cursorDown(n = 1) {
  return CSI + n + 'B'
}

exports.cursorForward = function cursorForward(n = 1) {
  return CSI + n + 'C'
}

exports.cursorBack = function cursorBack(n = 1) {
  return CSI + n + 'D'
}

exports.cursorNextLine = function cursorNextLine(n = 1) {
  return CSI + n + 'E'
}

exports.cursorPreviousLine = function cursorPreviousLine(n = 1) {
  return CSI + n + 'F'
}

exports.cursorPosition = function cursorPosition(column, row = 0) {
  if (row === 0) return CSI + (column + 1) + 'G'

  return CSI + (row + 1) + ';' + (column + 1) + 'H'
}

exports.eraseDisplayEnd = CSI + 'J'
exports.eraseDisplayStart = CSI + '1J'
exports.eraseDisplay = CSI + '2J'
exports.eraseLineEnd = CSI + 'K'
exports.eraseLineStart = CSI + '1K'
exports.eraseLine = CSI + '2K'

exports.scrollUp = function scrollUp(n = 1) {
  return CSI + n + 'S'
}

exports.scrollDown = function scrollDown(n = 1) {
  return CSI + n + 'T'
}

// https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_(Select_Graphic_Rendition)_parameters

exports.modifierReset = SGR(0)
exports.modifierBold = SGR(1)
exports.modifierDim = SGR(2)
exports.modifierItalic = SGR(3)
exports.modifierUnderline = SGR(4)
exports.modifierNormal = SGR(22)
exports.modifierNotItalic = SGR(23)
exports.modifierNotUnderline = SGR(24)

// https://en.wikipedia.org/wiki/ANSI_escape_code#Colors

exports.colorBlack = SGR(30)
exports.colorRed = SGR(31)
exports.colorGreen = SGR(32)
exports.colorYellow = SGR(33)
exports.colorBlue = SGR(34)
exports.colorMagenta = SGR(35)
exports.colorCyan = SGR(36)
exports.colorWhite = SGR(37)
exports.colorDefault = SGR(39)
exports.colorBrightBlack = SGR(90)
exports.colorBrightRed = SGR(91)
exports.colorBrightGreen = SGR(92)
exports.colorBrightYellow = SGR(93)
exports.colorBrightBlue = SGR(94)
exports.colorBrightMagenta = SGR(95)
exports.colorBrightCyan = SGR(96)
exports.colorBrightWhite = SGR(97)
{
  "name": "bare-ansi-escapes",
  "version": "2.2.3",
  "description": "Parse and produce ANSI escape sequences",
  "exports": {
    "./package": "./package.json",
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    },
    "./key-decoder": {
      "types": "./key-decoder.d.ts",
      "default": "./key-decoder.js"
    }
  },
  "files": [
    "index.js",
    "index.d.ts",
    "key-decoder.js",
    "key-decoder.d.ts"
  ],
  "scripts": {
    "test": "prettier . --check && bare test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-ansi-escapes.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-ansi-escapes/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-ansi-escapes#readme",
  "dependencies": {
    "bare-stream": "^2.6.5"
  },
  "devDependencies": {
    "bare-buffer": "^3.0.2",
    "brittle": "^3.3.2",
    "prettier": "^3.4.2",
    "prettier-config-standard": "^7.0.0"
  },
  "peerDependencies": {
    "bare-buffer": "*"
  },
  "peerDependenciesMeta": {
    "bare-buffer": {
      "optional": true
    }
  }
}
const inspect = require('bare-inspect')

class AssertionError extends Error {
  constructor(opts = {}) {
    let { message = null, actual, expected, operator } = opts

    if (message === null) {
      message = `${inspect(actual)} ${operator} ${inspect(expected)}`
    }

    super(message)

    this.actual = actual
    this.expected = expected
    this.operator = operator
  }

  get name() {
    return 'AssertionError'
  }

  get code() {
    'ASSERTION'
  }
}

function assertFail(opts, fn) {
  if (opts.message instanceof Error) throw opts.message

  const err = new AssertionError(opts)

  if (Error.captureStackTrace) Error.captureStackTrace(err, fn)

  throw err
}

module.exports = exports = function assert(actual, message) {
  if (actual) return

  assertFail({ message, actual, expected: true, operator: '==' }, assert)
}

exports.AssertionError = AssertionError

exports.fail = function fail(message) {
  if (message === undefined) message = 'Failed'

  assertFail({ message, operator: 'fail' }, fail)
}

exports.ok = function ok(actual, message) {
  if (actual) return

  assertFail({ message, actual, expected: true, operator: '==' }, ok)
}

exports.notOk = function ok(actual, message) {
  if (!actual) return

  assertFail({ message, actual, expected: false, operator: '==' }, ok)
}

exports.equal = function equal(actual, expected, message) {
  if (actual == expected || (actual !== actual && expected !== expected)) {
    return
  }

  assertFail({ message, actual, expected, operator: '==' }, equal)
}

exports.notEqual = function notEqual(actual, expected, message) {
  if (actual != expected && (actual === actual || expected === expected)) {
    return
  }

  assertFail({ message, actual, expected, operator: '!=' }, notEqual)
}

exports.strictEqual = function strictEqual(actual, expected, message) {
  if (Object.is(actual, expected)) return

  assertFail(
    { message, actual, expected, operator: 'strictEqual' },
    strictEqual
  )
}

exports.notStrictEqual = function notStrictEqual(actual, expected, message) {
  if (!Object.is(actual, expected)) return

  assertFail(
    { message, actual, expected, operator: 'notStrictEqual' },
    notStrictEqual
  )
}
{
  "name": "bare-assert",
  "version": "1.2.0",
  "description": "Assertion library for JavaScript",
  "exports": {
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    },
    "./package": "./package.json"
  },
  "files": [
    "index.js",
    "index.d.ts"
  ],
  "scripts": {
    "test": "prettier . --check && bare test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-assert.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-assert/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-assert#readme",
  "dependencies": {
    "bare-inspect": "^3.1.2"
  },
  "devDependencies": {
    "brittle": "^3.1.1",
    "prettier": "^3.4.2",
    "prettier-config-standard": "^7.0.0"
  }
}
module.exports = require.addon()
const constants = require('./lib/constants')
const ascii = require('./lib/ascii')
const base64 = require('./lib/base64')
const base64url = require('./lib/base64url')
const hex = require('./lib/hex')
const utf8 = require('./lib/utf8')
const utf16le = require('./lib/utf16le')
const latin1 = require('./lib/latin1')
const binding = require('./binding')
const checked = require('./lib/checked')

const kind = Symbol.for('bare.buffer.kind')

const SWAP_MIN_LENGTH = 128

let poolSize = 65536
let pool = null
let poolLength = 0
let poolOffset = 0

class Buffer extends Uint8Array {
  static get [kind]() {
    return 0 // Compatibility version
  }

  static get poolSize() {
    return poolSize
  }

  static set poolSize(value) {
    assertSize(value, 'Pool size')

    poolSize = value
  }

  constructor(arrayBuffer, offset, length, opts = {}) {
    if (typeof arrayBuffer === 'number') {
      opts = offset || {}

      const { uninitialized = false } = opts

      offset = 0
      length = arrayBuffer

      if (Number.isInteger(length) === false || length < 0 || length > constants.MAX_LENGTH) {
        throw new RangeError(
          `Buffer length must be an integer between 0 and ${constants.MAX_LENGTH}`
        )
      }

      arrayBuffer = uninitialized ? binding.allocUnsafe(length) : binding.alloc(length)
    } else {
      if (typeof offset === 'number') assertInteger(offset, 'Offset')
      if (typeof length === 'number') assertInteger(length, 'Length')

      if (length > constants.MAX_LENGTH) {
        throw new RangeError(`Buffer length must be at most ${constants.MAX_LENGTH}`)
      }

      if (typeof offset === 'object' && offset !== null) {
        opts = offset
        offset = 0
        length = arrayBuffer.byteLength
      } else if (typeof length === 'object' && length !== null) {
        opts = length
        length = arrayBuffer.byteLength - offset
      }
    }

    super(arrayBuffer, offset, length)
  }

  get [kind]() {
    return Buffer[kind]
  }

  copy(target, targetStart = 0, sourceStart = 0, sourceEnd = this.byteLength) {
    const source = this

    assertView(target, 'Target')

    assertSize(targetStart, 'Target start')
    assertSize(sourceStart, 'Source start')
    assertSize(sourceEnd, 'Source end')

    if (target.BYTES_PER_ELEMENT !== 1) {
      target = new Uint8Array(target.buffer, target.byteOffset, target.byteLength)
    }

    const sourceLength = source.byteLength
    const targetLength = target.byteLength

    if (sourceStart > sourceLength) {
      throw new RangeError(`Source start must be at most ${sourceLength}`)
    }

    if (targetStart >= targetLength) return 0

    if (sourceEnd > sourceLength) sourceEnd = sourceLength
    if (sourceEnd <= sourceStart) return 0

    const room = targetLength - targetStart

    if (sourceEnd - sourceStart > room) sourceEnd = sourceStart + room

    const length = sourceEnd - sourceStart

    if (source === target) {
      target.copyWithin(targetStart, sourceStart, sourceEnd)
    } else if (sourceStart === 0 && sourceEnd === sourceLength) {
      target.set(source, targetStart)
    } else {
      target.set(
        new Uint8Array(source.buffer, source.byteOffset + sourceStart, length),
        targetStart
      )
    }

    return length
  }

  equals(target) {
    const source = this

    if (source === target) return true

    assertView(target, 'Target')

    const sourceLength = source.byteLength
    const targetLength = target.byteLength

    if (sourceLength !== targetLength) return false

    return (
      checked(
        binding.compare(
          source.buffer,
          source.byteOffset,
          sourceLength,
          target.buffer,
          target.byteOffset,
          targetLength
        )
      ) === 0
    )
  }

  compare(
    target,
    targetStart = 0,
    targetEnd = target.byteLength,
    sourceStart = 0,
    sourceEnd = this.byteLength
  ) {
    const source = this

    if (source === target) return 0

    assertView(target, 'Target')

    const sourceLength = source.byteLength
    const targetLength = target.byteLength

    if (arguments.length === 1) {
      targetStart = 0
      targetEnd = targetLength
      sourceStart = 0
      sourceEnd = sourceLength
    } else {
      assertInteger(targetStart, 'Target start')
      assertInteger(targetEnd, 'Target end')
      assertInteger(sourceStart, 'Source start')
      assertInteger(sourceEnd, 'Source end')

      if (targetStart < 0) targetStart = 0
      if (targetStart > targetLength) targetStart = targetLength

      if (targetEnd < targetStart) targetEnd = targetStart
      if (targetEnd > targetLength) targetEnd = targetLength

      if (sourceStart < 0) sourceStart = 0
      if (sourceStart > sourceLength) sourceStart = sourceLength

      if (sourceEnd < sourceStart) sourceEnd = sourceStart
      if (sourceEnd > sourceLength) sourceEnd = sourceLength
    }

    return checked(
      binding.compare(
        source.buffer,
        source.byteOffset + sourceStart,
        sourceEnd - sourceStart,
        target.buffer,
        target.byteOffset + targetStart,
        targetEnd - targetStart
      )
    )
  }

  fill(value, offset = 0, end = this.byteLength, encoding = 'utf8') {
    if (typeof value === 'string') {
      if (typeof offset === 'string') {
        // fill(string, encoding)
        encoding = offset
        offset = 0
        end = this.byteLength
      } else if (typeof end === 'string') {
        // fill(string, offset, encoding)
        encoding = end
        end = this.byteLength
      }
    } else if (typeof value === 'number') {
      value = value & 0xff
    } else if (typeof value === 'boolean') {
      value = +value
    } else if (ArrayBuffer.isView(value)) {
      if (value.BYTES_PER_ELEMENT !== 1) {
        value = new Uint8Array(value.buffer, value.byteOffset, value.byteLength)
      }
    } else {
      throw new TypeError(
        `Fill value must be a number, a string or a view, received type ${typeof value}`
      )
    }

    assertInteger(offset, 'Offset')
    assertInteger(end, 'End')

    const byteLength = this.byteLength

    if (offset < 0) offset = 0
    if (offset >= byteLength) return this

    if (end <= offset) return this
    if (end > byteLength) end = byteLength

    if (typeof value === 'number') return super.fill(value, offset, end)

    if (typeof value === 'string') value = exports.from(value, encoding)

    const length = value.byteLength

    if (length === 0) return super.fill(0, offset, end)
    if (length === 1) return super.fill(value[0], offset, end)

    return fillPattern(this, value, length, offset, end)
  }

  includes(value, offset, encoding) {
    return this.indexOf(value, offset, encoding) !== -1
  }

  indexOf(value, offset = 0, encoding) {
    if (typeof offset === 'number') assertInteger(offset, 'Offset')

    if (typeof value === 'boolean') value = +value

    if (typeof value === 'number') {
      return super.indexOf(value & 0xff, offset)
    }

    return bidirectionalIndexOf(this, value, offset, encoding, true /* first */)
  }

  lastIndexOf(value, offset = this.byteLength - 1, encoding) {
    if (typeof offset === 'number') assertInteger(offset, 'Offset')

    if (typeof value === 'boolean') value = +value

    if (typeof value === 'number') {
      return super.lastIndexOf(value & 0xff, offset)
    }

    return bidirectionalIndexOf(this, value, offset, encoding, false /* last */)
  }

  swap16() {
    const length = this.byteLength

    if (length % 2 !== 0) {
      throw new RangeError('Buffer size must be a multiple of 16-bits')
    }

    if (length < SWAP_MIN_LENGTH) {
      for (let i = 0; i < length; i += 2) swap(this, i, i + 1)
    } else {
      checked(binding.swap16(this.buffer, this.byteOffset, length))
    }

    return this
  }

  swap32() {
    const length = this.byteLength

    if (length % 4 !== 0) {
      throw new RangeError('Buffer size must be a multiple of 32-bits')
    }

    if (length < SWAP_MIN_LENGTH) {
      for (let i = 0; i < length; i += 4) {
        swap(this, i, i + 3)
        swap(this, i + 1, i + 2)
      }
    } else {
      checked(binding.swap32(this.buffer, this.byteOffset, length))
    }

    return this
  }

  swap64() {
    const length = this.byteLength

    if (length % 8 !== 0) {
      throw new RangeError('Buffer size must be a multiple of 64-bits')
    }

    if (length < SWAP_MIN_LENGTH) {
      for (let i = 0; i < length; i += 8) {
        swap(this, i, i + 7)
        swap(this, i + 1, i + 6)
        swap(this, i + 2, i + 5)
        swap(this, i + 3, i + 4)
      }
    } else {
      checked(binding.swap64(this.buffer, this.byteOffset, length))
    }

    return this
  }

  toString(encoding = 'utf8', start = 0, end = this.byteLength) {
    // toString()
    if (arguments.length === 0) return utf8.toString(this)

    // toString(encoding)
    if (arguments.length === 1) return codecFor(encoding).toString(this)

    assertInteger(start, 'Start')
    assertInteger(end, 'End')

    const length = this.byteLength

    if (start < 0) start = 0
    if (start >= length) return ''

    if (end <= start) return ''
    if (end > length) end = length

    return codecFor(encoding).toString(this, start, end)
  }

  toJSON() {
    const length = this.byteLength
    const data = new Array(length)

    for (let i = 0; i < length; i++) data[i] = this[i]

    return data
  }

  write(string, offset = 0, length = this.byteLength - offset, encoding = 'utf8') {
    assertString(string, 'String')

    // write(string)
    if (arguments.length === 1) return utf8.write(this, string)

    if (typeof offset === 'string') {
      // write(string, encoding)
      encoding = offset
      offset = 0
      length = this.byteLength
    } else if (typeof length === 'string') {
      // write(string, offset, encoding)
      encoding = length
      length = this.byteLength - offset
    }

    assertInteger(offset, 'Offset')
    assertInteger(length, 'Length')

    length = Math.min(length, exports.byteLength(string, encoding))

    const byteLength = this.byteLength

    let start = offset
    if (start < 0) start = 0
    if (start >= byteLength) return 0

    let end = offset + length
    if (end <= start) return 0
    if (end > byteLength) end = byteLength

    return codecFor(encoding).write(this, string, start, end)
  }

  readBigInt64BE(offset = 0) {
    if (!Number.isInteger(offset)) offsetError(offset)
    return viewOf(this).getBigInt64(offset, false)
  }
  readBigInt64LE(offset = 0) {
    if (!Number.isInteger(offset)) offsetError(offset)
    return viewOf(this).getBigInt64(offset, true)
  }

  readBigUint64BE(offset = 0) {
    if (!Number.isInteger(offset)) offsetError(offset)
    return viewOf(this).getBigUint64(offset, false)
  }
  readBigUint64LE(offset = 0) {
    if (!Number.isInteger(offset)) offsetError(offset)
    return viewOf(this).getBigUint64(offset, true)
  }

  readDoubleBE(offset = 0) {
    if (!Number.isInteger(offset)) offsetError(offset)
    return viewOf(this).getFloat64(offset, false)
  }
  readDoubleLE(offset = 0) {
    if (!Number.isInteger(offset)) offsetError(offset)
    return viewOf(this).getFloat64(offset, true)
  }

  readFloatBE(offset = 0) {
    if (!Number.isInteger(offset)) offsetError(offset)
    return viewOf(this).getFloat32(offset, false)
  }
  readFloatLE(offset = 0) {
    if (!Number.isInteger(offset)) offsetError(offset)
    return viewOf(this).getFloat32(offset, true)
  }

  readInt8(offset = 0) {
    if (!Number.isInteger(offset)) offsetError(offset)
    const value = this[offset]
    if (value === undefined) boundsError()
    return (value << 24) >> 24
  }

  readInt16BE(offset = 0) {
    if (!Number.isInteger(offset)) offsetError(offset)
    const first = this[offset]
    const last = this[offset + 1]
    if (first === undefined || last === undefined) boundsError()
    return (((first << 8) | last) << 16) >> 16
  }
  readInt16LE(offset = 0) {
    if (!Number.isInteger(offset)) offsetError(offset)
    const first = this[offset]
    const last = this[offset + 1]
    if (first === undefined || last === undefined) boundsError()
    return (((last << 8) | first) << 16) >> 16
  }

  readInt32BE(offset = 0) {
    if (!Number.isInteger(offset)) offsetError(offset)
    const first = this[offset]
    const last = this[offset + 3]
    if (first === undefined || last === undefined) boundsError()
    return (first << 24) | (this[offset + 1] << 16) | (this[offset + 2] << 8) | last
  }
  readInt32LE(offset = 0) {
    if (!Number.isInteger(offset)) offsetError(offset)
    const first = this[offset]
    const last = this[offset + 3]
    if (first === undefined || last === undefined) boundsError()
    return first | (this[offset + 1] << 8) | (this[offset + 2] << 16) | (last << 24)
  }

  readIntBE(offset, byteLength) {
    if (!Number.isInteger(offset)) offsetError(offset)
    if (byteLength === 6) return readInt48BE(this, offset)
    if (byteLength === 5) return readInt40BE(this, offset)
    if (byteLength === 3) return readInt24BE(this, offset)
    if (byteLength === 4) return this.readInt32BE(offset)
    if (byteLength === 2) return this.readInt16BE(offset)
    if (byteLength === 1) return this.readInt8(offset)
    throw new RangeError(`Byte length must be between 1 and 6`)
  }

  readIntLE(offset, byteLength) {
    if (!Number.isInteger(offset)) offsetError(offset)
    if (byteLength === 6) return readInt48LE(this, offset)
    if (byteLength === 5) return readInt40LE(this, offset)
    if (byteLength === 3) return readInt24LE(this, offset)
    if (byteLength === 4) return this.readInt32LE(offset)
    if (byteLength === 2) return this.readInt16LE(offset)
    if (byteLength === 1) return this.readInt8(offset)
    throw new RangeError(`Byte length must be between 1 and 6`)
  }

  readUint8(offset = 0) {
    if (!Number.isInteger(offset)) offsetError(offset)
    const value = this[offset]
    if (value === undefined) boundsError()
    return value
  }

  readUint16BE(offset = 0) {
    if (!Number.isInteger(offset)) offsetError(offset)
    const first = this[offset]
    const last = this[offset + 1]
    if (first === undefined || last === undefined) boundsError()
    return (first << 8) | last
  }
  readUint16LE(offset = 0) {
    if (!Number.isInteger(offset)) offsetError(offset)
    const first = this[offset]
    const last = this[offset + 1]
    if (first === undefined || last === undefined) boundsError()
    return (last << 8) | first
  }

  readUint32BE(offset = 0) {
    if (!Number.isInteger(offset)) offsetError(offset)
    const first = this[offset]
    const last = this[offset + 3]
    if (first === undefined || last === undefined) boundsError()
    return first * 0x1000000 + ((this[offset + 1] << 16) | (this[offset + 2] << 8) | last)
  }
  readUint32LE(offset = 0) {
    if (!Number.isInteger(offset)) offsetError(offset)
    const first = this[offset]
    const last = this[offset + 3]
    if (first === undefined || last === undefined) boundsError()
    return last * 0x1000000 + ((this[offset + 2] << 16) | (this[offset + 1] << 8) | first)
  }

  readUintBE(offset, byteLength) {
    if (!Number.isInteger(offset)) offsetError(offset)
    if (byteLength === 6) return readUint48BE(this, offset)
    if (byteLength === 5) return readUint40BE(this, offset)
    if (byteLength === 3) return readUint24BE(this, offset)
    if (byteLength === 4) return this.readUint32BE(offset)
    if (byteLength === 2) return this.readUint16BE(offset)
    if (byteLength === 1) return this.readUint8(offset)
    throw new RangeError(`Byte length must be between 1 and 6`)
  }

  readUintLE(offset, byteLength) {
    if (!Number.isInteger(offset)) offsetError(offset)
    if (byteLength === 6) return readUint48LE(this, offset)
    if (byteLength === 5) return readUint40LE(this, offset)
    if (byteLength === 3) return readUint24LE(this, offset)
    if (byteLength === 4) return this.readUint32LE(offset)
    if (byteLength === 2) return this.readUint16LE(offset)
    if (byteLength === 1) return this.readUint8(offset)
    throw new RangeError(`Byte length must be between 1 and 6`)
  }

  writeBigInt64BE(value, offset = 0) {
    if (!Number.isInteger(offset)) offsetError(offset)
    viewOf(this).setBigInt64(offset, value, false)
    return offset + 8
  }
  writeBigInt64LE(value, offset = 0) {
    if (!Number.isInteger(offset)) offsetError(offset)
    viewOf(this).setBigInt64(offset, value, true)
    return offset + 8
  }

  writeBigUint64BE(value, offset = 0) {
    if (!Number.isInteger(offset)) offsetError(offset)
    viewOf(this).setBigUint64(offset, value, false)
    return offset + 8
  }
  writeBigUint64LE(value, offset = 0) {
    if (!Number.isInteger(offset)) offsetError(offset)
    viewOf(this).setBigUint64(offset, value, true)
    return offset + 8
  }

  writeDoubleBE(value, offset = 0) {
    if (!Number.isInteger(offset)) offsetError(offset)
    viewOf(this).setFloat64(offset, value, false)
    return offset + 8
  }
  writeDoubleLE(value, offset = 0) {
    if (!Number.isInteger(offset)) offsetError(offset)
    viewOf(this).setFloat64(offset, value, true)
    return offset + 8
  }

  writeFloatBE(value, offset = 0) {
    if (!Number.isInteger(offset)) offsetError(offset)
    viewOf(this).setFloat32(offset, value, false)
    return offset + 4
  }
  writeFloatLE(value, offset = 0) {
    if (!Number.isInteger(offset)) offsetError(offset)
    viewOf(this).setFloat32(offset, value, true)
    return offset + 4
  }

  writeInt8(value, offset = 0) {
    return this.writeUint8(value, offset)
  }

  writeInt16BE(value, offset = 0) {
    return this.writeUint16BE(value, offset)
  }
  writeInt16LE(value, offset = 0) {
    return this.writeUint16LE(value, offset)
  }

  writeInt32BE(value, offset = 0) {
    return this.writeUint32BE(value, offset)
  }
  writeInt32LE(value, offset = 0) {
    return this.writeUint32LE(value, offset)
  }

  writeIntBE(value, offset, byteLength) {
    if (!Number.isInteger(offset)) offsetError(offset)
    if (byteLength === 6) return writeInt48BE(this, value, offset)
    if (byteLength === 5) return writeInt40BE(this, value, offset)
    if (byteLength === 3) return writeInt24BE(this, value, offset)
    if (byteLength === 4) return this.writeInt32BE(value, offset)
    if (byteLength === 2) return this.writeInt16BE(value, offset)
    if (byteLength === 1) return this.writeInt8(value, offset)
    throw new RangeError(`Byte length must be between 1 and 6`)
  }

  writeIntLE(value, offset, byteLength) {
    if (!Number.isInteger(offset)) offsetError(offset)
    if (byteLength === 6) return writeInt48LE(this, value, offset)
    if (byteLength === 5) return writeInt40LE(this, value, offset)
    if (byteLength === 3) return writeInt24LE(this, value, offset)
    if (byteLength === 4) return this.writeInt32LE(value, offset)
    if (byteLength === 2) return this.writeInt16LE(value, offset)
    if (byteLength === 1) return this.writeInt8(value, offset)
    throw new RangeError(`Byte length must be between 1 and 6`)
  }

  writeUint8(value, offset = 0) {
    if (!Number.isInteger(offset)) offsetError(offset)
    if (this[offset] === undefined) boundsError()
    this[offset] = value
    return offset + 1
  }

  writeUint16BE(value, offset = 0) {
    if (!Number.isInteger(offset)) offsetError(offset)
    if (this[offset] === undefined || this[offset + 1] === undefined) boundsError()
    this[offset] = value >>> 8
    this[offset + 1] = value
    return offset + 2
  }
  writeUint16LE(value, offset = 0) {
    if (!Number.isInteger(offset)) offsetError(offset)
    if (this[offset] === undefined || this[offset + 1] === undefined) boundsError()
    this[offset] = value
    this[offset + 1] = value >>> 8
    return offset + 2
  }

  writeUint32LE(value, offset = 0) {
    if (!Number.isInteger(offset)) offsetError(offset)
    if (this[offset] === undefined || this[offset + 3] === undefined) boundsError()
    this[offset] = value
    this[offset + 1] = value >>> 8
    this[offset + 2] = value >>> 16
    this[offset + 3] = value >>> 24
    return offset + 4
  }
  writeUint32BE(value, offset = 0) {
    if (!Number.isInteger(offset)) offsetError(offset)
    if (this[offset] === undefined || this[offset + 3] === undefined) boundsError()
    this[offset] = value >>> 24
    this[offset + 1] = value >>> 16
    this[offset + 2] = value >>> 8
    this[offset + 3] = value
    return offset + 4
  }

  writeUintBE(value, offset, byteLength) {
    if (!Number.isInteger(offset)) offsetError(offset)
    if (byteLength === 6) return writeUint48BE(this, value, offset)
    if (byteLength === 5) return writeUint40BE(this, value, offset)
    if (byteLength === 3) return writeUint24BE(this, value, offset)
    if (byteLength === 4) return this.writeUint32BE(value, offset)
    if (byteLength === 2) return this.writeUint16BE(value, offset)
    if (byteLength === 1) return this.writeUint8(value, offset)
    throw new RangeError(`Byte length must be between 1 and 6`)
  }

  writeUintLE(value, offset, byteLength) {
    if (!Number.isInteger(offset)) offsetError(offset)
    if (byteLength === 6) return writeUint48LE(this, value, offset)
    if (byteLength === 5) return writeUint40LE(this, value, offset)
    if (byteLength === 3) return writeUint24LE(this, value, offset)
    if (byteLength === 4) return this.writeUint32LE(value, offset)
    if (byteLength === 2) return this.writeUint16LE(value, offset)
    if (byteLength === 1) return this.writeUint8(value, offset)
    throw new RangeError(`Byte length must be between 1 and 6`)
  }
}

for (const [alias, name] of [
  ['readBigUInt64BE', 'readBigUint64BE'],
  ['readBigUInt64LE', 'readBigUint64LE'],
  ['readUInt8', 'readUint8'],
  ['readUInt16BE', 'readUint16BE'],
  ['readUInt16LE', 'readUint16LE'],
  ['readUInt32BE', 'readUint32BE'],
  ['readUInt32LE', 'readUint32LE'],
  ['readUIntBE', 'readUintBE'],
  ['readUIntLE', 'readUintLE'],
  ['writeBigUInt64BE', 'writeBigUint64BE'],
  ['writeBigUInt64LE', 'writeBigUint64LE'],
  ['writeUInt8', 'writeUint8'],
  ['writeUInt16BE', 'writeUint16BE'],
  ['writeUInt16LE', 'writeUint16LE'],
  ['writeUInt32BE', 'writeUint32BE'],
  ['writeUInt32LE', 'writeUint32LE'],
  ['writeUIntBE', 'writeUintBE'],
  ['writeUIntLE', 'writeUintLE']
]) {
  Object.defineProperty(Buffer.prototype, alias, {
    value: Buffer.prototype[name],
    writable: true,
    configurable: true
  })
}

module.exports = exports = Buffer

exports.Buffer = Buffer // For Node.js compatibility

exports.constants = constants

const codecs = Object.create(null)

codecs.ascii = ascii
codecs.base64 = base64
codecs.base64url = base64url
codecs.hex = hex
codecs.utf8 = codecs['utf-8'] = utf8
codecs.utf16le = codecs.ucs2 = codecs['utf-16le'] = codecs['ucs-2'] = utf16le
codecs.latin1 = codecs.binary = latin1

function codecFor(encoding) {
  if (encoding === undefined) return utf8

  let codec = codecs[encoding]
  if (codec !== undefined) return codec

  if (typeof encoding !== 'string') {
    throw new TypeError(`Encoding must be a string, received type ${typeof encoding}`)
  }

  codec = codecs[encoding.toLowerCase()]
  if (codec !== undefined) return codec

  throw new Error(`Unknown encoding '${encoding}'`)
}

const views = new WeakMap()

function viewOf(buffer) {
  let view = views.get(buffer)
  if (view === undefined) {
    const store = buffer.buffer

    view = new DataView(store, buffer.byteOffset, buffer.byteLength)

    // A view of a store that can still change size covers the wrong range as
    // soon as it does, or falls out of bounds entirely, so it is only worth
    // remembering one of a store that cannot.
    if (store.resizable !== true && store.growable !== true) {
      views.set(buffer, view)
    }
  }
  return view
}

exports.isBuffer = function isBuffer(value) {
  if (value instanceof Buffer) return true

  return typeof value === 'object' && value !== null && value[kind] === Buffer[kind]
}

exports.isEncoding = function isEncoding(encoding) {
  try {
    codecFor(encoding)
    return true
  } catch {
    return false
  }
}

exports.isASCII = function isASCII(buffer) {
  return ascii.validate(buffer)
}

// for Node.js compatibily
exports.isAscii = exports.isASCII

exports.isUTF8 = function isUTF8(buffer) {
  return utf8.validate(buffer)
}

// for Node.js compatibility
exports.isUtf8 = exports.isUTF8

exports.alloc = function alloc(size, fill, encoding) {
  assertSize(size, 'Size')

  if (fill !== undefined && fill !== 0) {
    const buffer = new Buffer(size, { uninitialized: true })

    return buffer.fill(fill, 0, buffer.byteLength, encoding)
  }

  return new Buffer(size)
}

exports.allocUnsafe = function allocUnsafe(size) {
  assertSize(size, 'Size')

  return allocate(size)
}

exports.allocUnsafeSlow = function allocUnsafeSlow(size) {
  assertSize(size, 'Size')

  return new Buffer(size, { uninitialized: true })
}

function assertInteger(value, name) {
  if (typeof value !== 'number') {
    throw new TypeError(`${name} must be a number, received type ${typeof value}`)
  }

  if (Number.isInteger(value) === false) {
    throw new RangeError(`${name} must be an integer`)
  }
}

function assertString(value, name) {
  if (typeof value !== 'string') {
    throw new TypeError(`${name} must be a string, received type ${typeof value}`)
  }
}

function assertView(value, name) {
  if (ArrayBuffer.isView(value) === false) {
    throw new TypeError(`${name} must be a view, received type ${typeof value}`)
  }
}

function assertSize(size, name) {
  if (typeof size !== 'number') {
    throw new TypeError(`${name} must be a number, received type ${typeof size}`)
  }

  if (Number.isInteger(size) === false || size < 0 || size > constants.MAX_LENGTH) {
    throw new RangeError(`${name} must be an integer between 0 and ${constants.MAX_LENGTH}`)
  }
}

exports.byteLength = function byteLength(string, encoding) {
  if (typeof string === 'string') {
    return codecFor(encoding).byteLength(string)
  }

  return string.byteLength
}

exports.compare = function compare(a, b) {
  assertView(a, 'First buffer')
  assertView(b, 'Second buffer')

  return checked(
    binding.compare(a.buffer, a.byteOffset, a.byteLength, b.buffer, b.byteOffset, b.byteLength)
  )
}

exports.concat = function concat(buffers, length) {
  const n = buffers.length

  for (let i = 0; i < n; i++) {
    const buffer = buffers[i]

    if (ArrayBuffer.isView(buffer) === false || buffer.BYTES_PER_ELEMENT !== 1) {
      throw new TypeError(`buffers[${i}] must be a view of single bytes`)
    }
  }

  if (length === undefined) {
    length = 0
    for (let i = 0; i < n; i++) length += buffers[i].byteLength
  } else {
    assertSize(length, 'Length')
  }

  const result = allocate(length)

  let offset = 0

  for (let i = 0; i < n; i++) {
    const buffer = buffers[i]
    const buffered = buffer.byteLength

    if (offset + buffered > length) {
      const remaining = length - offset

      result.set(new Uint8Array(buffer.buffer, buffer.byteOffset, remaining), offset)

      offset = length
      break
    }

    result.set(buffer, offset)
    offset += buffered
  }

  // Only the bytes the inputs did not reach need initializing.
  if (offset < length) result.fill(0, offset, length)

  return result
}

exports.coerce = function coerce(buffer) {
  if (exports.isBuffer(buffer)) return buffer
  return new Buffer(buffer.buffer, buffer.byteOffset, buffer.byteLength)
}

exports.copyBytesFrom = function copyBytesFrom(view, offset = 0, length = view.length - offset) {
  assertSize(offset, 'Offset')
  assertSize(length, 'Length')

  if (offset + length > view.length) {
    throw new RangeError('View length is out of range')
  }

  if (offset !== 0 || length !== view.length) {
    view = view.subarray(offset, offset + length)
  }

  return new Buffer(view.buffer.slice(view.byteOffset, view.byteOffset + view.byteLength))
}

exports.from = function from(value, encodingOrOffset, length) {
  // from(string, encoding)
  if (typeof value === 'string') return fromString(value, encodingOrOffset)

  // from(array)
  if (Array.isArray(value)) return fromArray(value)

  // from(buffer)
  if (ArrayBuffer.isView(value)) return fromBuffer(value)

  // from(arrayBuffer[, offset[, length]])
  return fromArrayBuffer(value, encodingOrOffset, length)
}

function fromString(string, encoding) {
  const codec = codecFor(encoding)
  const length = codec.byteLength(string)
  const buffer = allocate(length)
  const written = codec.write(buffer, string, 0, length)

  // Some codecs only know an upper bound before encoding, and the remainder is
  // uninitialized rather than zeroed.
  if (written < length) return buffer.subarray(0, written)

  return buffer
}

function fromArray(array) {
  const buffer = allocate(array.length)
  buffer.set(array)
  return buffer
}

function fromBuffer(buffer) {
  // A view is copied element wise, each element truncated to a byte, so the
  // copy is sized by element count and not by byte length. A view that is not
  // array like, such as a DataView, reports no length and has nothing to copy.
  const length = typeof buffer.length === 'number' ? buffer.length : 0

  const copy = allocate(length)
  copy.set(buffer)
  return copy
}

function fromArrayBuffer(arrayBuffer, offset, length) {
  if (offset !== undefined) assertInteger(offset, 'Offset')
  if (length !== undefined) assertInteger(length, 'Length')

  return new Buffer(arrayBuffer, offset, length)
}

function bidirectionalIndexOf(buffer, value, offset, encoding, first) {
  if (typeof value !== 'string') assertView(value, 'Value')

  const length = buffer.byteLength

  if (length === 0) return -1

  if (typeof offset === 'string') {
    encoding = offset
    offset = 0
  } else if (offset === undefined) {
    offset = first ? 0 : length - 1
  } else if (offset < 0) {
    offset += length
  }

  if (offset >= length) {
    if (first) return -1
    else offset = length - 1
  } else if (offset < 0) {
    if (first) offset = 0
    else return -1
  }

  if (typeof value === 'string') value = exports.from(value, encoding)

  const needleLength = value.byteLength

  if (needleLength === 0) return -1

  const last = length - needleLength

  if (first) {
    if (offset > last) return -1
  } else {
    if (offset > last) offset = last
    if (offset < 0) return -1
  }

  // Confirming a match at the position the search starts from costs a handful
  // of comparisons, and keeps prefix checks off the native path entirely.
  let j = 0
  while (j < needleLength && buffer[offset + j] === value[j]) j++
  if (j === needleLength) return offset

  if (first) {
    if (offset === last) return -1

    return checked(
      binding.indexOf(
        buffer.buffer,
        buffer.byteOffset,
        length,
        value.buffer,
        value.byteOffset,
        needleLength,
        offset + 1
      )
    )
  }

  if (offset === 0) return -1

  return checked(
    binding.lastIndexOf(
      buffer.buffer,
      buffer.byteOffset,
      length,
      value.buffer,
      value.byteOffset,
      needleLength,
      offset - 1
    )
  )
}

// Writes the pattern once and then repeatedly doubles it in place, so the byte
// wise work is proportional to the pattern rather than to the range. Each block
// copied is a whole number of periods, which keeps the pattern aligned.
function fillPattern(buffer, value, length, offset, end) {
  const n = end - offset

  // Below this the copies cost more than they save.
  if (n < 64) {
    for (let i = 0; i < n; i++) buffer[offset + i] = value[i % length]
    return buffer
  }

  let filled = length < n ? length : n

  for (let i = 0; i < filled; i++) buffer[offset + i] = value[i]

  while (filled < n) {
    const copy = filled < n - filled ? filled : n - filled

    buffer.copyWithin(offset + filled, offset, offset + copy)

    filled += copy
  }

  return buffer
}

function allocate(size) {
  if (Number.isNaN(size) || size <= 0 || size >= poolSize / 2) {
    return new Buffer(size, { uninitialized: true })
  }

  if (pool === null || poolLength !== poolSize || poolLength - poolOffset < size) {
    pool = binding.allocUnsafe(poolSize)
    poolLength = poolSize
    poolOffset = 0
  }

  const buffer = new Buffer(pool, poolOffset, size)

  // Keep the next buffer 8 byte aligned so the wider accessors are not needlessly
  // reading across word boundaries.
  poolOffset = (poolOffset + size + 7) & ~7

  return buffer
}

function swap(buffer, n, m) {
  const i = buffer[n]
  buffer[n] = buffer[m]
  buffer[m] = i
}

exports.atob = function atob(data) {
  return Buffer.from(data, 'base64').toString('latin1')
}

exports.btoa = function btoa(data) {
  if (typeof data !== 'string') data = String(data)

  return Buffer.from(data, 'latin1').toString('base64')
}

exports.transcode = function transcode(buffer, from, to) {
  return Buffer.from(buffer.toString(from), to)
}

function boundsError() {
  throw new RangeError('Offset is outside the bounds of the buffer')
}

function offsetError(offset) {
  if (typeof offset !== 'number') {
    throw new TypeError(`Offset must be a number, received type ${typeof offset}`)
  }

  throw new RangeError('Offset must be an integer')
}

function readInt48BE(buffer, offset) {
  const value = readUint48BE(buffer, offset)
  return value >= 0x800000000000 ? value - 0x1000000000000 : value
}

function readInt48LE(buffer, offset) {
  const value = readUint48LE(buffer, offset)
  return value >= 0x800000000000 ? value - 0x1000000000000 : value
}

function readInt40BE(buffer, offset) {
  const value = readUint40BE(buffer, offset)
  return value >= 0x8000000000 ? value - 0x10000000000 : value
}

function readInt40LE(buffer, offset) {
  const value = readUint40LE(buffer, offset)
  return value >= 0x8000000000 ? value - 0x10000000000 : value
}

function readInt24BE(buffer, offset) {
  const value = readUint24BE(buffer, offset)
  return value & 0x800000 ? value - 0x1000000 : value
}

function readInt24LE(buffer, offset) {
  const value = readUint24LE(buffer, offset)
  return value & 0x800000 ? value - 0x1000000 : value
}

function readUint48BE(buffer, offset) {
  const first = buffer[offset]
  const last = buffer[offset + 5]
  if (first === undefined || last === undefined) boundsError()
  return (
    (first * 0x100 + buffer[offset + 1]) * 0x100000000 +
    ((buffer[offset + 2] << 24) >>> 0) +
    (buffer[offset + 3] << 16) +
    (buffer[offset + 4] << 8) +
    last
  )
}

function readUint48LE(buffer, offset) {
  const first = buffer[offset]
  const last = buffer[offset + 5]
  if (first === undefined || last === undefined) boundsError()
  return (
    (buffer[offset + 4] + last * 0x100) * 0x100000000 +
    ((buffer[offset + 3] << 24) >>> 0) +
    (buffer[offset + 2] << 16) +
    (buffer[offset + 1] << 8) +
    first
  )
}

function readUint40BE(buffer, offset) {
  const first = buffer[offset]
  const last = buffer[offset + 4]
  if (first === undefined || last === undefined) boundsError()
  return (
    first * 0x100000000 +
    ((buffer[offset + 1] << 24) >>> 0) +
    (buffer[offset + 2] << 16) +
    (buffer[offset + 3] << 8) +
    last
  )
}

function readUint40LE(buffer, offset) {
  const first = buffer[offset]
  const last = buffer[offset + 4]
  if (first === undefined || last === undefined) boundsError()
  return (
    last * 0x100000000 +
    ((buffer[offset + 3] << 24) >>> 0) +
    (buffer[offset + 2] << 16) +
    (buffer[offset + 1] << 8) +
    first
  )
}

function readUint24BE(buffer, offset) {
  const first = buffer[offset]
  const last = buffer[offset + 2]
  if (first === undefined || last === undefined) boundsError()
  return (first << 16) | (buffer[offset + 1] << 8) | last
}

function readUint24LE(buffer, offset) {
  const first = buffer[offset]
  const last = buffer[offset + 2]
  if (first === undefined || last === undefined) boundsError()
  return (last << 16) | (buffer[offset + 1] << 8) | first
}

function writeInt48BE(buffer, value, offset) {
  if (value < 0) value += 0x1000000000000
  return writeUint48BE(buffer, value, offset)
}

function writeInt48LE(buffer, value, offset) {
  if (value < 0) value += 0x1000000000000
  return writeUint48LE(buffer, value, offset)
}

function writeInt40BE(buffer, value, offset) {
  if (value < 0) value += 0x10000000000
  return writeUint40BE(buffer, value, offset)
}

function writeInt40LE(buffer, value, offset) {
  if (value < 0) value += 0x10000000000
  return writeUint40LE(buffer, value, offset)
}

function writeInt24BE(buffer, value, offset) {
  if (value < 0) value += 0x1000000
  return writeUint24BE(buffer, value, offset)
}

function writeInt24LE(buffer, value, offset) {
  if (value < 0) value += 0x1000000
  return writeUint24LE(buffer, value, offset)
}

function writeUint48BE(buffer, value, offset) {
  if (buffer[offset] === undefined || buffer[offset + 5] === undefined) boundsError()
  const hi = Math.floor(value / 0x100000000)
  const lo = value >>> 0
  buffer[offset] = hi >>> 8
  buffer[offset + 1] = hi
  buffer[offset + 2] = lo >>> 24
  buffer[offset + 3] = lo >>> 16
  buffer[offset + 4] = lo >>> 8
  buffer[offset + 5] = lo
  return offset + 6
}

function writeUint48LE(buffer, value, offset) {
  if (buffer[offset] === undefined || buffer[offset + 5] === undefined) boundsError()
  const hi = Math.floor(value / 0x100000000)
  const lo = value >>> 0
  buffer[offset] = lo
  buffer[offset + 1] = lo >>> 8
  buffer[offset + 2] = lo >>> 16
  buffer[offset + 3] = lo >>> 24
  buffer[offset + 4] = hi
  buffer[offset + 5] = hi >>> 8
  return offset + 6
}

function writeUint40BE(buffer, value, offset) {
  if (buffer[offset] === undefined || buffer[offset + 4] === undefined) boundsError()
  const lo = value >>> 0
  buffer[offset] = Math.floor(value / 0x100000000)
  buffer[offset + 1] = lo >>> 24
  buffer[offset + 2] = lo >>> 16
  buffer[offset + 3] = lo >>> 8
  buffer[offset + 4] = lo
  return offset + 5
}

function writeUint40LE(buffer, value, offset) {
  if (buffer[offset] === undefined || buffer[offset + 4] === undefined) boundsError()
  const lo = value >>> 0
  buffer[offset] = lo
  buffer[offset + 1] = lo >>> 8
  buffer[offset + 2] = lo >>> 16
  buffer[offset + 3] = lo >>> 24
  buffer[offset + 4] = Math.floor(value / 0x100000000)
  return offset + 5
}

function writeUint24BE(buffer, value, offset) {
  if (buffer[offset] === undefined || buffer[offset + 2] === undefined) boundsError()
  buffer[offset] = value >>> 16
  buffer[offset + 1] = value >>> 8
  buffer[offset + 2] = value
  return offset + 3
}

function writeUint24LE(buffer, value, offset) {
  if (buffer[offset] === undefined || buffer[offset + 2] === undefined) boundsError()
  buffer[offset] = value
  buffer[offset + 1] = value >>> 8
  buffer[offset + 2] = value >>> 16
  return offset + 3
}
const binding = require('../binding')
const checked = require('./checked')

exports.byteLength = function byteLength(string) {
  return string.length
}

exports.toString = function toString(buffer, start = 0, end = buffer.byteLength) {
  const offset = buffer.byteOffset + start
  const len = end - start

  // ASCII is Latin-1 with the high bit unset, so as long as no high bits are set
  // the native Latin-1 decoder produces the same string. Decoding through it
  // either way keeps the range checked in one place.
  const latin1 = binding.toStringLatin1(buffer.buffer, offset, len)

  if (exports.validate(buffer, offset, len)) return latin1

  let result = ''

  for (let i = 0, n = latin1.length; i < n; i++) {
    result += String.fromCharCode(latin1.charCodeAt(i) & 0x7f)
  }

  return result
}

// Encoding ASCII is equivalent to encoding Latin-1, both truncating each code
// unit to its low byte.
exports.write = function write(buffer, string, start = 0, end = buffer.byteLength) {
  return checked(binding.writeLatin1(buffer.buffer, buffer.byteOffset + start, end - start, string))
}

exports.validate = function validate(buffer, offset = buffer.byteOffset, len = buffer.byteLength) {
  return checked(binding.validateAscii(buffer.buffer, offset, len)) === 1
}
const binding = require('../binding')
const checked = require('./checked')

exports.byteLength = function byteLength(string) {
  let len = string.length

  if (string.charCodeAt(len - 1) === 0x3d) len--
  if (len > 1 && string.charCodeAt(len - 1) === 0x3d) len--

  return (len * 3) >>> 2
}

exports.toString = function toString(buffer, start = 0, end = buffer.byteLength) {
  return binding.toStringBase64(buffer.buffer, buffer.byteOffset + start, end - start)
}

exports.write = function write(buffer, string, start = 0, end = buffer.byteLength) {
  const written = checked(
    binding.writeBase64(buffer.buffer, buffer.byteOffset + start, end - start, string)
  )

  if (written < 0) throw new Error('Invalid input')

  return written
}
const binding = require('../binding')
const checked = require('./checked')

exports.byteLength = function byteLength(string) {
  let len = string.length

  if (string.charCodeAt(len - 1) === 0x3d) len--
  if (len > 1 && string.charCodeAt(len - 1) === 0x3d) len--

  return (len * 3) >>> 2
}

exports.toString = function toString(buffer, start = 0, end = buffer.byteLength) {
  return binding.toStringBase64URL(buffer.buffer, buffer.byteOffset + start, end - start)
}

exports.write = function write(buffer, string, start = 0, end = buffer.byteLength) {
  const written = checked(
    binding.writeBase64(buffer.buffer, buffer.byteOffset + start, end - start, string)
  )

  if (written < 0) throw new Error('Invalid input')

  return written
}
// A typed callback cannot raise, so the bindings report an argument they reject
// with a result no successful call can produce. There is one sentinel per error
// the untyped callback raises directly, so that the error does not change once
// V8 takes the fast path.
const OUT_OF_BOUNDS = -2147483648
const INVALID_BUFFER = -2147483647
const INVALID_STRING = -2147483646

module.exports = function checked(result) {
  if (result === OUT_OF_BOUNDS) {
    throw new RangeError('View is out of bounds of its backing store')
  }

  if (result === INVALID_BUFFER) {
    throw new TypeError('Buffer must be an array buffer')
  }

  if (result === INVALID_STRING) {
    throw new TypeError('String must be a string')
  }

  return result
}
const binding = require('../binding')

module.exports = binding.constants
const binding = require('../binding')
const checked = require('./checked')

exports.byteLength = function byteLength(string) {
  return string.length >>> 1
}

exports.toString = function toString(buffer, start = 0, end = buffer.byteLength) {
  return binding.toStringHex(buffer.buffer, buffer.byteOffset + start, end - start)
}

exports.write = function write(buffer, string, start = 0, end = buffer.byteLength) {
  const written = checked(
    binding.writeHex(buffer.buffer, buffer.byteOffset + start, end - start, string)
  )

  if (written < 0) throw new Error('Invalid input')

  return written
}
const binding = require('../binding')
const checked = require('./checked')

exports.byteLength = function byteLength(string) {
  return string.length
}

exports.toString = function toString(buffer, start = 0, end = buffer.byteLength) {
  return binding.toStringLatin1(buffer.buffer, buffer.byteOffset + start, end - start)
}

exports.write = function write(buffer, string, start = 0, end = buffer.byteLength) {
  return checked(binding.writeLatin1(buffer.buffer, buffer.byteOffset + start, end - start, string))
}
const binding = require('../binding')
const checked = require('./checked')

exports.byteLength = function byteLength(string) {
  return string.length * 2
}

exports.toString = function toString(buffer, start = 0, end = buffer.byteLength) {
  return binding.toStringUTF16LE(buffer.buffer, buffer.byteOffset + start, end - start)
}

exports.write = function write(buffer, string, start = 0, end = buffer.byteLength) {
  const written = checked(
    binding.writeUTF16LE(buffer.buffer, buffer.byteOffset + start, end - start, string)
  )

  if (written < 0) throw new Error('Out of memory')

  return written
}
const binding = require('../binding')
const checked = require('./checked')

exports.byteLength = function byteLength(string) {
  return checked(binding.byteLengthUTF8(string))
}

exports.toString = function toString(buffer, start = 0, end = buffer.byteLength) {
  return binding.toStringUTF8(buffer.buffer, buffer.byteOffset + start, end - start)
}

exports.write = function write(buffer, string, start = 0, end = buffer.byteLength) {
  return checked(binding.writeUTF8(buffer.buffer, buffer.byteOffset + start, end - start, string))
}

exports.validate = function validate(buffer) {
  return checked(binding.validateUTF8(buffer.buffer, buffer.byteOffset, buffer.byteLength)) === 1
}
{
  "name": "bare-buffer",
  "version": "3.7.1",
  "description": "Native buffers for JavaScript",
  "exports": {
    "./package": "./package.json",
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    },
    "./global": {
      "types": "./global.d.ts",
      "default": "./global.js"
    },
    "./constants": {
      "types": "./lib/constants.d.ts",
      "default": "./lib/constants.js"
    }
  },
  "files": [
    "index.js",
    "index.d.ts",
    "global.js",
    "global.d.ts",
    "binding.c",
    "binding.js",
    "CMakeLists.txt",
    "lib",
    "prebuilds"
  ],
  "addon": true,
  "scripts": {
    "format": "prettier --write . && lunte --fix",
    "lint": "prettier --check . && lunte",
    "test": "brittle-bare --coverage test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-buffer.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-buffer/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-buffer#readme",
  "engines": {
    "bare": ">=1.20.0"
  },
  "devDependencies": {
    "bare-realm": "^2.0.1",
    "brittle": "^3.1.1",
    "cmake-bare": "^1.1.6",
    "cmake-fetch": "^1.0.0",
    "lunte": "^1.8.0",
    "prettier": "^3.3.3",
    "prettier-config-holepunch": "^2.0.0"
  }
}
module.exports = require.addon()
const binding = require('./binding')
const constants = require('./lib/constants')
const errors = require('./lib/errors')
const DNSResolver = require('./lib/resolver')

exports.Resolver = DNSResolver

function onlookup(err, addresses) {
  const req = this

  if (err) return req.cb(err, null, 0)

  const { address, family } = addresses[0]

  return req.cb(null, address, family)
}

function onlookupall(err, addresses) {
  const req = this

  if (err) return req.cb(err, null)

  return req.cb(null, addresses)
}

exports.lookup = function lookup(hostname, opts = {}, cb) {
  if (typeof opts === 'function') {
    cb = opts
    opts = {}
  }

  let { family = 0, all = false } = opts

  if (typeof family === 'string') {
    switch (family) {
      case 'IPv4':
        family = 4
        break
      case 'IPv6':
        family = 6
        break
      default:
        family = 0
    }
  }

  const req = {
    cb,
    handle: null
  }

  req.handle = binding.lookup(hostname, family || 0, all, req, all ? onlookupall : onlookup)
}

exports.getServers = function getServers() {
  return DNSResolver.global.getServers()
}

exports.setServers = function setServers(servers) {
  return DNSResolver.global.setServers(servers)
}

exports.setLocalAddress = function setLocalAddress(ipv4, ipv6) {
  return DNSResolver.global.setLocalAddress(ipv4, ipv6)
}

exports.cancel = function cancel() {
  return DNSResolver.global.cancel()
}

exports.resolve = function resolve(hostname, rrtype, cb) {
  return DNSResolver.global.resolve(hostname, rrtype, cb)
}

exports.resolve4 = function resolve4(hostname, opts, cb) {
  return DNSResolver.global.resolve4(hostname, opts, cb)
}

exports.resolve6 = function resolve6(hostname, opts, cb) {
  return DNSResolver.global.resolve6(hostname, opts, cb)
}

exports.resolveAny = function resolveAny(hostname, cb) {
  return DNSResolver.global.resolveAny(hostname, cb)
}

exports.resolveCaa = function resolveCaa(hostname, cb) {
  return DNSResolver.global.resolveCaa(hostname, cb)
}

exports.resolveCname = function resolveCname(hostname, cb) {
  return DNSResolver.global.resolveCname(hostname, cb)
}

exports.resolveMx = function resolveMx(hostname, cb) {
  return DNSResolver.global.resolveMx(hostname, cb)
}

exports.resolveNaptr = function resolveNaptr(hostname, cb) {
  return DNSResolver.global.resolveNaptr(hostname, cb)
}

exports.resolveNs = function resolveNs(hostname, cb) {
  return DNSResolver.global.resolveNs(hostname, cb)
}

exports.resolvePtr = function resolvePtr(hostname, cb) {
  return DNSResolver.global.resolvePtr(hostname, cb)
}

exports.resolveSoa = function resolveSoa(hostname, cb) {
  return DNSResolver.global.resolveSoa(hostname, cb)
}

exports.resolveSrv = function resolveSrv(hostname, cb) {
  return DNSResolver.global.resolveSrv(hostname, cb)
}

exports.resolveTlsa = function resolveTlsa(hostname, cb) {
  return DNSResolver.global.resolveTlsa(hostname, cb)
}

exports.resolveTxt = function resolveTxt(hostname, cb) {
  return DNSResolver.global.resolveTxt(hostname, cb)
}

exports.reverse = function reverse(ip, cb) {
  return DNSResolver.global.reverse(ip, cb)
}

exports.constants = constants
exports.errors = errors
exports.promises = require('./lib/promises')
// Resource record types, as understood by the underlying resolver.
exports.type = {
  A: 1,
  NS: 2,
  CNAME: 5,
  SOA: 6,
  PTR: 12,
  HINFO: 13,
  MX: 15,
  TXT: 16,
  SIG: 24,
  AAAA: 28,
  SRV: 33,
  NAPTR: 35,
  OPT: 41,
  TLSA: 52,
  SVCB: 64,
  HTTPS: 65,
  ANY: 255,
  URI: 256,
  CAA: 257
}

// Resolver status codes, indexed to match the values reported by the binding.
exports.status = {
  SUCCESS: 0,
  ENODATA: 1,
  EFORMERR: 2,
  ESERVFAIL: 3,
  ENOTFOUND: 4,
  ENOTIMP: 5,
  EREFUSED: 6,
  EBADQUERY: 7,
  EBADNAME: 8,
  EBADFAMILY: 9,
  EBADRESP: 10,
  ECONNREFUSED: 11,
  ETIMEOUT: 12,
  EOF: 13,
  EFILE: 14,
  ENOMEM: 15,
  EDESTRUCTION: 16,
  EBADSTR: 17,
  EBADFLAGS: 18,
  ENONAME: 19,
  EBADHINTS: 20,
  ENOTINITIALIZED: 21,
  ELOADIPHLPAPI: 22,
  EADDRGETNETWORKPARAMS: 23,
  ECANCELLED: 24,
  ESERVICE: 25,
  ENOSERVER: 26
}
const constants = require('./constants')

// The status codes indexed by their value, for turning the status a query
// reports into the code its error carries.
const codes = []

for (const [name, value] of Object.entries(constants.status)) {
  codes[value] = name
}

module.exports = class DNSError extends Error {
  constructor(msg, fn = DNSError, code = fn.name) {
    super(`${code}: ${msg}`)

    this.code = code

    if (Error.captureStackTrace) Error.captureStackTrace(this, fn)
  }

  get name() {
    return 'DNSError'
  }

  static RESOLVER_DESTROYED(msg = 'Resolver is destroyed') {
    return new DNSError(msg, DNSError.RESOLVER_DESTROYED)
  }

  static UNKNOWN_RECORD_TYPE(msg = 'Unknown resource record type') {
    return new DNSError(msg, DNSError.UNKNOWN_RECORD_TYPE)
  }

  static INVALID_IP_ADDRESS(msg = 'Invalid IP address') {
    return new DNSError(msg, DNSError.INVALID_IP_ADDRESS)
  }

  static query(status, syscall, hostname = null) {
    const code = codes[status] || 'UNKNOWN'

    const err = new DNSError(
      `${syscall}` + (hostname === null ? '' : ` ${hostname}`),
      DNSError.query,
      code
    )

    err.syscall = syscall

    if (hostname !== null) err.hostname = hostname

    return err
  }
}
const constants = require('./constants')
const errors = require('./errors')
const DNSResolver = require('./resolver')

class DNSPromiseResolver {
  constructor(opts) {
    this._resolver = new DNSResolver(opts)
  }

  getServers() {
    return this._resolver.getServers()
  }

  setServers(servers) {
    return this._resolver.setServers(servers)
  }

  setLocalAddress(ipv4, ipv6) {
    return this._resolver.setLocalAddress(ipv4, ipv6)
  }

  cancel() {
    return this._resolver.cancel()
  }

  destroy() {
    return this._resolver.destroy()
  }

  resolve(hostname, rrtype) {
    return promisify(this._resolver, 'resolve', hostname, rrtype)
  }

  resolve4(hostname, opts) {
    return promisify(this._resolver, 'resolve4', hostname, opts)
  }

  resolve6(hostname, opts) {
    return promisify(this._resolver, 'resolve6', hostname, opts)
  }

  resolveAny(hostname) {
    return promisify(this._resolver, 'resolveAny', hostname)
  }

  resolveCaa(hostname) {
    return promisify(this._resolver, 'resolveCaa', hostname)
  }

  resolveCname(hostname) {
    return promisify(this._resolver, 'resolveCname', hostname)
  }

  resolveMx(hostname) {
    return promisify(this._resolver, 'resolveMx', hostname)
  }

  resolveNaptr(hostname) {
    return promisify(this._resolver, 'resolveNaptr', hostname)
  }

  resolveNs(hostname) {
    return promisify(this._resolver, 'resolveNs', hostname)
  }

  resolvePtr(hostname) {
    return promisify(this._resolver, 'resolvePtr', hostname)
  }

  resolveSoa(hostname) {
    return promisify(this._resolver, 'resolveSoa', hostname)
  }

  resolveSrv(hostname) {
    return promisify(this._resolver, 'resolveSrv', hostname)
  }

  resolveTlsa(hostname) {
    return promisify(this._resolver, 'resolveTlsa', hostname)
  }

  resolveTxt(hostname) {
    return promisify(this._resolver, 'resolveTxt', hostname)
  }

  reverse(ip) {
    return promisify(this._resolver, 'reverse', ip)
  }

  static global = new this()
}

function promisify(resolver, method, ...args) {
  return new Promise((resolve, reject) => {
    resolver[method](...args, (err, result) => {
      if (err) reject(err)
      else resolve(result)
    })
  })
}

exports.Resolver = DNSPromiseResolver

exports.lookup = function lookup(hostname, opts = {}) {
  const dns = require('../')

  return new Promise((resolve, reject) => {
    if (opts && opts.all) {
      dns.lookup(hostname, opts, (err, addresses) => {
        if (err) reject(err)
        else resolve(addresses)
      })
    } else {
      dns.lookup(hostname, opts, (err, address, family) => {
        if (err) reject(err)
        else resolve({ address, family })
      })
    }
  })
}

exports.getServers = function getServers() {
  return DNSPromiseResolver.global.getServers()
}

exports.setServers = function setServers(servers) {
  return DNSPromiseResolver.global.setServers(servers)
}

exports.setLocalAddress = function setLocalAddress(ipv4, ipv6) {
  return DNSPromiseResolver.global.setLocalAddress(ipv4, ipv6)
}

exports.cancel = function cancel() {
  return DNSPromiseResolver.global.cancel()
}

exports.resolve = function resolve(hostname, rrtype) {
  return DNSPromiseResolver.global.resolve(hostname, rrtype)
}

exports.resolve4 = function resolve4(hostname, opts) {
  return DNSPromiseResolver.global.resolve4(hostname, opts)
}

exports.resolve6 = function resolve6(hostname, opts) {
  return DNSPromiseResolver.global.resolve6(hostname, opts)
}

exports.resolveAny = function resolveAny(hostname) {
  return DNSPromiseResolver.global.resolveAny(hostname)
}

exports.resolveCaa = function resolveCaa(hostname) {
  return DNSPromiseResolver.global.resolveCaa(hostname)
}

exports.resolveCname = function resolveCname(hostname) {
  return DNSPromiseResolver.global.resolveCname(hostname)
}

exports.resolveMx = function resolveMx(hostname) {
  return DNSPromiseResolver.global.resolveMx(hostname)
}

exports.resolveNaptr = function resolveNaptr(hostname) {
  return DNSPromiseResolver.global.resolveNaptr(hostname)
}

exports.resolveNs = function resolveNs(hostname) {
  return DNSPromiseResolver.global.resolveNs(hostname)
}

exports.resolvePtr = function resolvePtr(hostname) {
  return DNSPromiseResolver.global.resolvePtr(hostname)
}

exports.resolveSoa = function resolveSoa(hostname) {
  return DNSPromiseResolver.global.resolveSoa(hostname)
}

exports.resolveSrv = function resolveSrv(hostname) {
  return DNSPromiseResolver.global.resolveSrv(hostname)
}

exports.resolveTlsa = function resolveTlsa(hostname) {
  return DNSPromiseResolver.global.resolveTlsa(hostname)
}

exports.resolveTxt = function resolveTxt(hostname) {
  return DNSPromiseResolver.global.resolveTxt(hostname)
}

exports.reverse = function reverse(ip) {
  return DNSPromiseResolver.global.reverse(ip)
}

exports.constants = constants
exports.errors = errors
// Shapes a parsed DNS record into the result each `resolve*()` method returns.
// The binding hands back every field a record type carries, named as the
// resolver names it.

function answers(record, type) {
  const result = []

  for (const rr of record.answers) {
    if (rr.type === type) result.push(rr)
  }

  return result
}

function string(value) {
  return value === null ? '' : Buffer.from(value).toString()
}

function buffer(value) {
  return value === null ? Buffer.alloc(0) : Buffer.from(value)
}

// Each shape returns `null` when the answer section holds no record of the
// requested type, which the caller reports as `ENODATA`.
function list(type, fn) {
  return (record) => {
    const rrs = answers(record, type)

    return rrs.length === 0 ? null : rrs.map(fn)
  }
}

const address = (rr) => rr.addr
const addressWithTTL = (rr) => ({ address: rr.addr, ttl: rr.ttl })

exports.a = (ttl) => list('A', ttl ? addressWithTTL : address)
exports.aaaa = (ttl) => list('AAAA', ttl ? addressWithTTL : address)

exports.cname = list('CNAME', (rr) => rr.cname)
exports.ns = list('NS', (rr) => rr.nsdname)
exports.ptr = list('PTR', (rr) => rr.dname)
exports.txt = list('TXT', (rr) => rr.data)

exports.mx = list('MX', (rr) => ({
  priority: rr.preference,
  exchange: rr.exchange
}))

exports.srv = list('SRV', (rr) => ({
  priority: rr.priority,
  weight: rr.weight,
  port: rr.port,
  name: rr.target
}))

exports.naptr = list('NAPTR', (rr) => ({
  flags: rr.flags,
  service: rr.services,
  regexp: rr.regexp,
  replacement: rr.replacement,
  order: rr.order,
  preference: rr.preference
}))

exports.caa = list('CAA', (rr) => ({
  critical: rr.critical,
  [rr.tag]: string(rr.value)
}))

exports.tlsa = list('TLSA', (rr) => ({
  certUsage: rr.cert_usage,
  selector: rr.selector,
  match: rr.match,
  data: buffer(rr.data)
}))

// Unlike every other type, a start of authority resolves to a single record
// rather than a list.
exports.soa = (record) => {
  const rrs = answers(record, 'SOA')

  return rrs.length === 0 ? null : soa(rrs[0])
}

function soa(rr) {
  return {
    nsname: rr.mname,
    hostmaster: rr.rname,
    serial: rr.serial,
    refresh: rr.refresh,
    retry: rr.retry,
    expire: rr.expire,
    minttl: rr.minimum
  }
}

// A wildcard query may come back with a mix of record types, each tagged with
// its type so that the caller can tell them apart.
exports.any = (record) => {
  const result = []

  for (const rr of record.answers) {
    switch (rr.type) {
      case 'A':
        result.push({ type: 'A', address: rr.addr, ttl: rr.ttl })
        break
      case 'AAAA':
        result.push({ type: 'AAAA', address: rr.addr, ttl: rr.ttl })
        break
      case 'CNAME':
        result.push({ type: 'CNAME', value: rr.cname })
        break
      case 'NS':
        result.push({ type: 'NS', value: rr.nsdname })
        break
      case 'PTR':
        result.push({ type: 'PTR', value: rr.dname })
        break
      case 'TXT':
        result.push({ type: 'TXT', entries: rr.data })
        break
      case 'MX':
        result.push({ type: 'MX', priority: rr.preference, exchange: rr.exchange })
        break
      case 'SRV':
        result.push({
          type: 'SRV',
          priority: rr.priority,
          weight: rr.weight,
          port: rr.port,
          name: rr.target
        })
        break
      case 'NAPTR':
        result.push({
          type: 'NAPTR',
          flags: rr.flags,
          service: rr.services,
          regexp: rr.regexp,
          replacement: rr.replacement,
          order: rr.order,
          preference: rr.preference
        })
        break
      case 'SOA':
        result.push({ type: 'SOA', ...soa(rr) })
        break
    }
  }

  return result
}
const binding = require('../binding')
const constants = require('./constants')
const errors = require('./errors')
const records = require('./records')

const { type, status } = constants

module.exports = class DNSResolver {
  constructor(opts = {}) {
    const { servers = null, localAddress = null } = opts

    this._handle = binding.initResolver()

    if (servers !== null) this.setServers(servers)

    if (localAddress !== null) {
      this.setLocalAddress(localAddress.ipv4, localAddress.ipv6)
    }
  }

  getServers() {
    if (this._handle === null) throw errors.RESOLVER_DESTROYED()

    const servers = binding.getServers(this._handle)

    return servers === '' ? [] : servers.split(',')
  }

  setServers(servers) {
    if (this._handle === null) throw errors.RESOLVER_DESTROYED()

    binding.setServers(this._handle, servers.join(','))
  }

  setLocalAddress(ipv4 = null, ipv6 = null) {
    if (this._handle === null) throw errors.RESOLVER_DESTROYED()

    binding.setLocalAddress(this._handle, ipv4, ipv6)
  }

  // Aborts every query outstanding on this resolver. Each pending callback is
  // invoked with `ECANCELLED`, releasing the loop.
  cancel() {
    if (this._handle === null) return

    binding.cancel(this._handle)
  }

  // Cancels every query outstanding on this resolver, so that none is left
  // without an answer, and releases the resources it holds.
  destroy() {
    if (this._handle === null) return

    binding.destroyResolver(this._handle)
    this._handle = null
  }

  resolve(hostname, rrtype = 'A', cb = noop) {
    if (typeof rrtype === 'function') {
      cb = rrtype
      rrtype = 'A'
    }

    switch (rrtype) {
      case 'A':
        return this.resolve4(hostname, cb)
      case 'AAAA':
        return this.resolve6(hostname, cb)
      case 'ANY':
        return this.resolveAny(hostname, cb)
      case 'CAA':
        return this.resolveCaa(hostname, cb)
      case 'CNAME':
        return this.resolveCname(hostname, cb)
      case 'MX':
        return this.resolveMx(hostname, cb)
      case 'NAPTR':
        return this.resolveNaptr(hostname, cb)
      case 'NS':
        return this.resolveNs(hostname, cb)
      case 'PTR':
        return this.resolvePtr(hostname, cb)
      case 'SOA':
        return this.resolveSoa(hostname, cb)
      case 'SRV':
        return this.resolveSrv(hostname, cb)
      case 'TLSA':
        return this.resolveTlsa(hostname, cb)
      case 'TXT':
        return this.resolveTxt(hostname, cb)
      default:
        throw errors.UNKNOWN_RECORD_TYPE(`Unknown resource record type '${rrtype}'`)
    }
  }

  resolve4(hostname, opts = {}, cb = noop) {
    if (typeof opts === 'function') {
      cb = opts
      opts = {}
    }

    const { ttl = false } = opts

    this._query(hostname, type.A, 'queryA', records.a(ttl), cb)
  }

  resolve6(hostname, opts = {}, cb = noop) {
    if (typeof opts === 'function') {
      cb = opts
      opts = {}
    }

    const { ttl = false } = opts

    this._query(hostname, type.AAAA, 'queryAaaa', records.aaaa(ttl), cb)
  }

  resolveAny(hostname, cb = noop) {
    this._query(hostname, type.ANY, 'queryAny', records.any, cb)
  }

  resolveCaa(hostname, cb = noop) {
    this._query(hostname, type.CAA, 'queryCaa', records.caa, cb)
  }

  resolveCname(hostname, cb = noop) {
    this._query(hostname, type.CNAME, 'queryCname', records.cname, cb)
  }

  resolveMx(hostname, cb = noop) {
    this._query(hostname, type.MX, 'queryMx', records.mx, cb)
  }

  resolveNaptr(hostname, cb = noop) {
    this._query(hostname, type.NAPTR, 'queryNaptr', records.naptr, cb)
  }

  resolveNs(hostname, cb = noop) {
    this._query(hostname, type.NS, 'queryNs', records.ns, cb)
  }

  resolvePtr(hostname, cb = noop) {
    this._query(hostname, type.PTR, 'queryPtr', records.ptr, cb)
  }

  resolveSoa(hostname, cb = noop) {
    this._query(hostname, type.SOA, 'querySoa', records.soa, cb)
  }

  resolveSrv(hostname, cb = noop) {
    this._query(hostname, type.SRV, 'querySrv', records.srv, cb)
  }

  resolveTlsa(hostname, cb = noop) {
    this._query(hostname, type.TLSA, 'queryTlsa', records.tlsa, cb)
  }

  resolveTxt(hostname, cb = noop) {
    this._query(hostname, type.TXT, 'queryTxt', records.txt, cb)
  }

  // Resolves a pointer record, which is a plain query against the reverse zone
  // for the address. Unlike Node.js, the hosts file is not consulted first, so
  // an address that is only named there does not resolve.
  reverse(ip, cb = noop) {
    let name

    try {
      name = binding.addrToPtr(ip)
    } catch {
      throw errors.INVALID_IP_ADDRESS(`Invalid IP address '${ip}'`)
    }

    this._query(name, type.PTR, 'getHostByAddr', records.ptr, cb, ip)
  }

  _query(name, rrtype, syscall, shape, cb, hostname = name) {
    if (this._handle === null) throw errors.RESOLVER_DESTROYED()

    const req = { hostname, syscall, shape, cb }

    binding.query(this._handle, name, rrtype, req, onquery)
  }

  static global = new this()
}

// The binding hands over an answer on a turn of the loop of its own, including
// one it served from its cache, so a callback never runs before the call that
// issued it.
function onquery(code, record) {
  const req = this

  if (code !== status.SUCCESS) {
    return req.cb(errors.query(code, req.syscall, req.hostname), null)
  }

  const result = req.shape(record)

  if (result === null) {
    return req.cb(errors.query(status.ENODATA, req.syscall, req.hostname), null)
  }

  req.cb(null, result)
}

function noop() {}
{
  "name": "bare-dns",
  "version": "2.2.0",
  "description": "Domain name resolution for JavaScript",
  "exports": {
    "./package": "./package.json",
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    },
    "./promises": {
      "types": "./lib/promises.d.ts",
      "default": "./lib/promises.js"
    },
    "./constants": {
      "types": "./lib/constants.d.ts",
      "default": "./lib/constants.js"
    },
    "./errors": {
      "types": "./lib/errors.d.ts",
      "default": "./lib/errors.js"
    }
  },
  "files": [
    "index.js",
    "index.d.ts",
    "binding.c",
    "binding.js",
    "CMakeLists.txt",
    "lib",
    "prebuilds"
  ],
  "addon": true,
  "scripts": {
    "format": "prettier --write . && lunte --fix",
    "lint": "prettier --check . && lunte",
    "test": "brittle-bare --coverage test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-dns.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-dns/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-dns",
  "engines": {
    "bare": ">=1.7.0"
  },
  "devDependencies": {
    "bare-buffer": "^3.0.2",
    "bare-dgram": "^1.0.1",
    "brittle": "^3.3.2",
    "cmake-bare": "^1.1.6",
    "cmake-fetch": "^1.3.0",
    "lunte": "^1.8.4",
    "prettier": "^3.4.1",
    "prettier-config-holepunch": "^2.0.0"
  },
  "peerDependencies": {
    "bare-buffer": "*"
  },
  "peerDependenciesMeta": {
    "bare-buffer": {
      "optional": true
    }
  }
}
const os = require('bare-os')

module.exports = new Proxy(Object.create(null), {
  ownKeys(target) {
    return os.getEnvKeys()
  },

  get(target, property) {
    if (typeof property !== 'string') return

    return os.getEnv(property)
  },

  has(target, property) {
    if (typeof property !== 'string') return false

    return os.hasEnv(property)
  },

  set(target, property, value) {
    if (typeof property !== 'string') return

    const type = typeof value

    if (type !== 'string' && type !== 'number' && type !== 'boolean') {
      throw new Error('Environment variable must be of type string, number, or boolean')
    }

    value = String(value)

    os.setEnv(property, value)

    return true
  },

  deleteProperty(target, property) {
    if (typeof property !== 'string') return

    os.unsetEnv(property)
  },

  getOwnPropertyDescriptor(target, property) {
    return {
      value: this.get(target, property),
      enumerable: true,
      configurable: true,
      writable: true
    }
  }
})
{
  "name": "bare-env",
  "version": "3.0.1",
  "description": "Environment variable support for JavaScript",
  "exports": {
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    },
    "./package": "./package.json"
  },
  "files": [
    "index.js",
    "index.d.ts"
  ],
  "scripts": {
    "format": "prettier --write . && lunte --fix",
    "lint": "prettier --check . && lunte",
    "test": "brittle-bare --coverage test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-env.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-env/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-env#readme",
  "dependencies": {
    "bare-os": "^3.0.1"
  },
  "devDependencies": {
    "brittle": "^3.2.1",
    "lunte": "^1.8.3",
    "prettier": "^3.4.2",
    "prettier-config-holepunch": "^2.0.0"
  }
}
const errors = require('./lib/errors')

class EventListener {
  constructor() {
    this.list = []
    this.count = 0
  }

  append(ctx, name, fn, once) {
    this.count++
    ctx.emit('newListener', name, fn) // Emit BEFORE adding
    this.list.push([fn, once, false])
  }

  prepend(ctx, name, fn, once) {
    this.count++
    ctx.emit('newListener', name, fn) // Emit BEFORE adding
    this.list.unshift([fn, once, false])
  }

  remove(ctx, name, fn) {
    for (let i = this.list.length - 1; i >= 0; i--) {
      if (this.list[i][0] === fn) return this.removeAt(ctx, name, i)
    }
  }

  removeAt(ctx, name, i) {
    const fn = this.list[i][0]

    this.list.splice(i, 1)

    if (this.count === 1) delete ctx._events[name]

    ctx.emit('removeListener', name, fn) // Emit AFTER removing

    this.count--
  }

  removeAll(ctx, name) {
    const list = [...this.list]
    this.list = []

    if (this.count === list.length) delete ctx._events[name]

    for (let i = list.length - 1; i >= 0; i--) {
      ctx.emit('removeListener', name, list[i][0]) // Emit AFTER removing
    }

    this.count -= list.length
  }

  emit(ctx, name, ...args) {
    const list = [...this.list]

    for (let i = 0, n = list.length; i < n; i++) {
      const l = list[i]

      if (l[1] === true) {
        // A reentrant emit may already have fired this listener, in which case
        // it must not be fired again.
        if (l[2] === true) continue

        l[2] = true

        // Remove this entry, rather than the first one with the same listener,
        // as the same listener may have been added more than once.
        const j = this.list.indexOf(l)

        if (j !== -1) this.removeAt(ctx, name, j)
      }

      Reflect.apply(l[0], ctx, args)
    }

    return list.length > 0
  }
}

function appendListener(ctx, name, fn, once) {
  if (ctx._events === undefined) ctx._events = Object.create(null)
  const e = ctx._events[name] || (ctx._events[name] = new EventListener())
  e.append(ctx, name, fn, once)
  return ctx
}

function prependListener(ctx, name, fn, once) {
  if (ctx._events === undefined) ctx._events = Object.create(null)
  const e = ctx._events[name] || (ctx._events[name] = new EventListener())
  e.prepend(ctx, name, fn, once)
  return ctx
}

function removeListener(ctx, name, fn) {
  if (ctx._events === undefined) return ctx
  const e = ctx._events[name]
  if (e !== undefined) e.remove(ctx, name, fn)
  return ctx
}

function throwUnhandledError(...args) {
  let err

  if (args.length > 0) err = args[0]

  if (err instanceof Error === false) {
    err = errors.UNHANDLED_ERROR(err)

    if (Error.captureStackTrace) Error.captureStackTrace(err, exports.prototype.emit)
  }

  queueMicrotask(() => {
    throw err
  })
}

module.exports = exports = class EventEmitter {
  constructor() {
    this._events = Object.create(null)
  }

  addListener(name, fn) {
    return appendListener(this, name, fn, false)
  }

  addOnceListener(name, fn) {
    return appendListener(this, name, fn, true)
  }

  prependListener(name, fn) {
    return prependListener(this, name, fn, false)
  }

  prependOnceListener(name, fn) {
    return prependListener(this, name, fn, true)
  }

  removeListener(name, fn) {
    return removeListener(this, name, fn)
  }

  on(name, fn) {
    return appendListener(this, name, fn, false)
  }

  once(name, fn) {
    return appendListener(this, name, fn, true)
  }

  off(name, fn) {
    return removeListener(this, name, fn)
  }

  emit(name, ...args) {
    if (name === 'error' && (this._events === undefined || this._events.error === undefined)) {
      throwUnhandledError(...args)
    }

    if (this._events === undefined) return false
    const e = this._events[name]
    return e === undefined ? false : e.emit(this, name, ...args)
  }

  listeners(name) {
    if (this._events === undefined) return []
    const e = this._events[name]
    return e === undefined ? [] : e.list.map((l) => l[0])
  }

  rawListeners(name) {
    if (this._events === undefined) return []
    const e = this._events[name]
    return e === undefined ? [] : e.list.map((l) => l[0])
  }

  eventNames() {
    if (this._events === undefined) return []
    return Reflect.ownKeys(this._events)
  }

  listenerCount(name) {
    if (this._events === undefined) return 0
    const e = this._events[name]
    return e === undefined ? 0 : e.list.length
  }

  getMaxListeners() {
    return EventEmitter.defaultMaxListeners
  }

  setMaxListeners(n) {
    return this
  }

  removeAllListeners(name) {
    if (this._events === undefined) return this

    if (arguments.length === 0) {
      for (const key of Reflect.ownKeys(this._events)) {
        if (key === 'removeListener') continue
        this.removeAllListeners(key)
      }
      this.removeAllListeners('removeListener')
    } else {
      const e = this._events[name]
      if (e !== undefined) e.removeAll(this, name)
    }
    return this
  }
}

exports.EventEmitter = exports

exports.errors = errors

exports.defaultMaxListeners = 10

exports.on = function on(emitter, name, opts = {}) {
  const { signal } = opts

  if (signal && signal.aborted) {
    throw errors.OPERATION_ABORTED(signal.reason)
  }

  let error = null
  let done = false

  const events = []
  const promises = []

  if (name !== 'error') emitter.on('error', onerror)

  if (signal) signal.addEventListener('abort', onabort)

  emitter.on(name, onevent)

  return {
    next() {
      if (events.length) {
        return Promise.resolve({ value: events.shift(), done: false })
      }

      if (error) {
        const err = error

        error = null

        return Promise.reject(err)
      }

      if (done) return onclose()

      return new Promise((resolve, reject) => promises.push({ resolve, reject }))
    },

    return() {
      return onclose()
    },

    throw(err) {
      return onerror(err)
    },

    [Symbol.asyncIterator]() {
      return this
    }
  }

  function onevent(...args) {
    if (promises.length) {
      promises.shift().resolve({ value: args, done: false })
    } else {
      events.push(args)
    }
  }

  function onerror(err) {
    if (promises.length) {
      promises.shift().reject(err)
    } else {
      error = err
    }

    return onclose()
  }

  function onabort() {
    return onerror(errors.OPERATION_ABORTED(signal.reason))
  }

  function onclose() {
    emitter.off(name, onevent)

    if (name !== 'error') emitter.off('error', onerror)

    if (signal) signal.removeEventListener('abort', onabort)

    done = true

    const result = { done: true }

    while (promises.length) promises.shift().resolve(result)

    return Promise.resolve(result)
  }
}

exports.once = function once(emitter, name, opts = {}) {
  const { signal } = opts

  if (signal && signal.aborted) {
    return Promise.reject(errors.OPERATION_ABORTED(signal.reason))
  }

  return new Promise((resolve, reject) => {
    if (name !== 'error') emitter.on('error', onerror)

    if (signal) signal.addEventListener('abort', onabort)

    emitter.once(name, onevent)

    function onevent(...args) {
      if (name !== 'error') emitter.off('error', onerror)

      if (signal) signal.removeEventListener('abort', onabort)

      resolve(args)
    }

    function onerror(err) {
      emitter.off(name, onevent)

      if (name !== 'error') emitter.off('error', onerror)

      if (signal) signal.removeEventListener('abort', onabort)

      reject(err)
    }

    function onabort() {
      onerror(errors.OPERATION_ABORTED(signal.reason))
    }
  })
}

exports.forward = function forward(from, to, names, opts = {}) {
  if (Array.isArray(names) === false) names = [names]

  const { emit = to.emit.bind(to) } = opts

  const listeners = names.map(
    (name) =>
      function onevent(...args) {
        emit(name, ...args)
      }
  )

  for (let i = 0, n = names.length; i < n; i++) {
    if (to.listenerCount(names[i]) > 0) from.on(names[i], listeners[i])
  }

  to.on('newListener', (name) => {
    const i = names.indexOf(name)

    if (i !== -1 && to.listenerCount(name) === 0) {
      from.on(name, listeners[i])
    }
  }).on('removeListener', (name) => {
    const i = names.indexOf(name)

    if (i !== -1 && to.listenerCount(name) === 0) {
      from.off(name, listeners[i])
    }
  })
}

exports.listenerCount = function listenerCount(emitter, name) {
  return emitter.listenerCount(name)
}

exports.getMaxListeners = function getMaxListeners(emitter) {
  if (typeof emitter.getMaxListeners === 'function') {
    return emitter.getMaxListeners()
  }

  return exports.defaultMaxListeners
}

exports.setMaxListeners = function setMaxListeners(n, ...emitters) {
  if (emitters.length === 0) exports.defaultMaxListeners = n
  else {
    for (const emitter of emitters) {
      if (typeof emitter.setMaxListeners === 'function') {
        emitter.setMaxListeners(n)
      }
    }
  }
}
module.exports = class EventEmitterError extends Error {
  constructor(msg, fn = EventEmitterError, opts = {}) {
    const { code = fn.name } = opts

    super(`${code}: ${msg}`, opts)

    this.code = code

    if (Error.captureStackTrace) Error.captureStackTrace(this, fn)
  }

  get name() {
    return 'EventEmitterError'
  }

  static OPERATION_ABORTED(cause, msg = 'Operation aborted') {
    return new EventEmitterError(msg, EventEmitterError.OPERATION_ABORTED, { cause })
  }

  static UNHANDLED_ERROR(cause, msg = 'Unhandled error') {
    return new EventEmitterError(msg, EventEmitterError.UNHANDLED_ERROR, { cause })
  }
}
{
  "name": "bare-events",
  "version": "2.9.2",
  "description": "Event emitters for JavaScript",
  "exports": {
    "./package": "./package.json",
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    },
    "./global": {
      "types": "./global.d.ts",
      "default": "./global.js"
    },
    "./web": {
      "types": "./web.d.ts",
      "default": "./web.js"
    },
    "./errors": "./lib/errors.js"
  },
  "files": [
    "index.js",
    "index.d.ts",
    "global.js",
    "global.d.ts",
    "web.js",
    "web.d.ts",
    "lib"
  ],
  "scripts": {
    "format": "prettier --write . && lunte --fix",
    "lint": "prettier --check . && lunte",
    "test": "npm run test:bare && npm run test:node",
    "test:bare": "brittle-bare --coverage test.js",
    "test:node": "brittle-node --coverage test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-events.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-events/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-events#readme",
  "devDependencies": {
    "bare-abort-controller": "^1.0.0",
    "brittle": "^3.3.2",
    "lunte": "^1.8.0",
    "prettier": "^3.4.2",
    "prettier-config-holepunch": "^2.0.0",
    "uncaughts": "^1.1.1"
  },
  "peerDependencies": {
    "bare-abort-controller": "*"
  },
  "peerDependenciesMeta": {
    "bare-abort-controller": {
      "optional": true
    }
  }
}
module.exports = require.addon()
const FIFO = require('fast-fifo')
const EventEmitter = require('bare-events')
const path = require('bare-path')
const { isURL, fileURLToPath } = require('bare-url')
const { Readable, Writable } = require('bare-stream')
const binding = require('./binding')
const constants = require('./lib/constants')
const FileError = require('./lib/errors')

const isWindows = Bare.platform === 'win32'

exports.constants = constants

class FileRequest {
  static borrow() {
    if (this._free.length > 0) return this._free.pop()
    return new FileRequest()
  }

  static return(req) {
    if (this._free.length < 32) this._free.push(req.reset())
    else req.destroy()
  }

  constructor() {
    this._reset()
    this._handle = binding.requestInit(this, this._onresult)
  }

  get handle() {
    return this._handle
  }

  retain(value) {
    this._retain = value // Tie the lifetime of `value` to the lifetime of `this`
  }

  reset() {
    if (this._handle === null) return this

    binding.requestReset(this._handle)

    this._reset()

    return this
  }

  destroy() {
    if (this._handle === null) return this

    binding.requestDestroy(this._handle)

    this._reset()
    this._handle = null

    return this
  }

  then(resolve, reject) {
    return this._promise.then(resolve, reject)
  }

  return() {
    if (this._handle === null) return this

    FileRequest.return(this)

    return this
  }

  _reset() {
    this._promise = new Promise((resolve, reject) => {
      this._resolve = resolve
      this._reject = reject
    })
    this._retain = null
  }

  _onresult(err, status) {
    if (err) this._reject(err)
    else this._resolve(status)
  }
}

FileRequest._free = []

function ok(result, cb) {
  if (typeof result === 'function') {
    cb = result
    result = undefined
  }

  if (cb) cb(null, result)
  else return result
}

function fail(err, cb) {
  if (cb) cb(err)
  else throw err
}

function done(err, result, cb) {
  if (typeof result === 'function') {
    cb = result
    result = undefined
  }

  if (err) fail(err, cb)
  else return ok(result, cb)
}

async function open(filepath, flags = 'r', mode = 0o666, cb) {
  if (typeof flags === 'function') {
    cb = flags
    flags = 'r'
    mode = 0o666
  } else if (typeof mode === 'function') {
    cb = mode
    mode = 0o666
  }

  if (typeof flags === 'string') flags = toFlags(flags)
  if (typeof mode === 'string') mode = toMode(mode)

  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  let fd
  let err = null
  try {
    binding.open(req.handle, filepath, flags, mode)

    fd = await req
  } catch (e) {
    err = new FileError(e.message, {
      operation: 'open',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }

  return done(err, fd, cb)
}

function openSync(filepath, flags = 'r', mode = 0o666) {
  if (typeof flags === 'string') flags = toFlags(flags)
  if (typeof mode === 'string') mode = toMode(mode)

  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  try {
    return binding.openSync(req.handle, filepath, flags, mode)
  } catch (e) {
    throw new FileError(e.message, {
      operation: 'open',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }
}

async function close(fd, cb) {
  const req = FileRequest.borrow()

  let err = null
  try {
    binding.close(req.handle, fd)

    await req
  } catch (e) {
    err = new FileError(e.message, { operation: 'close', code: e.code, fd })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function closeSync(fd) {
  const req = FileRequest.borrow()

  try {
    binding.closeSync(req.handle, fd)
  } catch (e) {
    throw new FileError(e.message, { operation: 'close', code: e.code, fd })
  } finally {
    req.return()
  }
}

async function access(filepath, mode = constants.F_OK, cb) {
  if (typeof mode === 'function') {
    cb = mode
    mode = constants.F_OK
  }

  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  let err = null
  try {
    binding.access(req.handle, filepath, mode)

    await req
  } catch (e) {
    err = new FileError(e.message, {
      operation: 'access',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function accessSync(filepath, mode = constants.F_OK) {
  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  try {
    binding.accessSync(req.handle, filepath, mode)
  } catch (e) {
    throw new FileError(e.message, {
      operation: 'access',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }
}

async function exists(filepath, cb) {
  let ok = true
  try {
    await access(filepath)
  } catch {
    ok = false
  }

  return done(null, ok, cb)
}

function existsSync(filepath) {
  try {
    accessSync(filepath)
  } catch {
    return false
  }

  return true
}

async function read(fd, buffer, offset = 0, len = buffer.byteLength - offset, pos = -1, cb) {
  if (typeof offset === 'function') {
    cb = offset
    offset = 0
    len = buffer.byteLength
    pos = -1
  } else if (typeof len === 'function') {
    cb = len
    len = buffer.byteLength - offset
    pos = -1
  } else if (typeof pos === 'function') {
    cb = pos
    pos = -1
  }

  if (typeof pos !== 'number') pos = -1

  const req = FileRequest.borrow()

  let bytes
  let err = null
  try {
    binding.read(req.handle, fd, buffer, offset, len, pos)

    req.retain(buffer)

    bytes = await req
  } catch (e) {
    err = new FileError(e.message, { operation: 'read', code: e.code, fd })
  } finally {
    req.return()
  }

  return done(err, bytes, cb)
}

function readSync(fd, buffer, offset = 0, len = buffer.byteLength - offset, pos = -1) {
  const req = FileRequest.borrow()

  try {
    return binding.readSync(req.handle, fd, buffer, offset, len, pos)
  } catch (e) {
    throw new FileError(e.message, { operation: 'read', code: e.code, fd })
  } finally {
    req.return()
  }
}

async function readv(fd, buffers, pos = -1, cb) {
  if (typeof pos === 'function') {
    cb = pos
    pos = -1
  }

  if (typeof pos !== 'number') pos = -1

  const req = FileRequest.borrow()

  let bytes
  let err = null
  try {
    binding.readv(req.handle, fd, buffers, pos)

    req.retain(buffers)

    bytes = await req
  } catch (e) {
    err = new FileError(e.message, { operation: 'readv', code: e.code, fd })
  } finally {
    req.return()
  }

  return done(err, bytes, cb)
}

function readvSync(fd, buffers, pos = -1) {
  if (typeof pos !== 'number') pos = -1

  const req = FileRequest.borrow()

  try {
    return binding.readvSync(req.handle, fd, buffers, pos)
  } catch (e) {
    throw new FileError(e.message, { operation: 'readv', code: e.code, fd })
  } finally {
    req.return()
  }
}

async function write(fd, data, offset, len, pos = -1, cb) {
  if (typeof data === 'string') {
    let encoding = len
    cb = pos
    pos = offset

    if (typeof pos === 'function') {
      cb = pos
      pos = -1
      encoding = 'utf8'
    } else if (typeof encoding === 'function') {
      cb = encoding
      encoding = 'utf8'
    }

    if (typeof pos === 'string') {
      encoding = pos
      pos = -1
    }

    data = Buffer.from(data, encoding)
    offset = 0
    len = data.byteLength
  } else if (typeof offset === 'function') {
    cb = offset
    offset = 0
    len = data.byteLength
    pos = -1
  } else if (typeof len === 'function') {
    cb = len
    len = data.byteLength - offset
    pos = -1
  } else if (typeof pos === 'function') {
    cb = pos
    pos = -1
  }

  if (typeof offset !== 'number') offset = 0
  if (typeof len !== 'number') len = data.byteLength - offset
  if (typeof pos !== 'number') pos = -1

  const req = FileRequest.borrow()

  let bytes
  let err = null
  try {
    binding.write(req.handle, fd, data, offset, len, pos)

    req.retain(data)

    bytes = await req
  } catch (e) {
    err = new FileError(e.message, { operation: 'write', code: e.code, fd })
  } finally {
    req.return()
  }

  return done(err, bytes, cb)
}

function writeSync(fd, data, offset, len, pos = -1) {
  if (typeof data === 'string') {
    let encoding = len
    pos = offset

    if (typeof pos === 'string') {
      encoding = pos
      pos = -1
    }

    data = Buffer.from(data, encoding)
    offset = 0
    len = data.byteLength
  }

  if (typeof offset !== 'number') offset = 0
  if (typeof len !== 'number') len = data.byteLength - offset
  if (typeof pos !== 'number') pos = -1

  const req = FileRequest.borrow()

  try {
    return binding.writeSync(req.handle, fd, data, offset, len, pos)
  } catch (e) {
    throw new FileError(e.message, { operation: 'write', code: e.code, fd })
  } finally {
    req.return()
  }
}

async function writev(fd, buffers, pos = -1, cb) {
  if (typeof pos === 'function') {
    cb = pos
    pos = -1
  }

  if (typeof pos !== 'number') pos = -1

  const req = FileRequest.borrow()

  let bytes
  let err = null
  try {
    binding.writev(req.handle, fd, buffers, pos)

    req.retain(buffers)

    bytes = await req
  } catch (e) {
    err = new FileError(e.message, { operation: 'writev', code: e.code, fd })
  } finally {
    req.return()
  }

  return done(err, bytes, cb)
}

function writevSync(fd, buffers, pos = -1) {
  if (typeof pos !== 'number') pos = -1

  const req = FileRequest.borrow()

  try {
    return binding.writevSync(req.handle, fd, buffers, pos)
  } catch (e) {
    throw new FileError(e.message, { operation: 'writev', code: e.code, fd })
  } finally {
    req.return()
  }
}

async function stat(filepath, cb) {
  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  let st
  let err = null
  try {
    binding.stat(req.handle, filepath)

    await req

    st = new Stats(...binding.requestResultStat(req.handle))
  } catch (e) {
    err = new FileError(e.message, { operation: 'stat', code: e.code, path: filepath })
  } finally {
    req.return()
  }

  return done(err, st, cb)
}

function statSync(filepath) {
  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  try {
    binding.statSync(req.handle, filepath)

    return new Stats(...binding.requestResultStat(req.handle))
  } catch (e) {
    throw new FileError(e.message, { operation: 'stat', code: e.code, path: filepath })
  } finally {
    req.return()
  }
}

async function lstat(filepath, cb) {
  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  let st
  let err = null
  try {
    binding.lstat(req.handle, filepath)

    await req

    st = new Stats(...binding.requestResultStat(req.handle))
  } catch (e) {
    err = new FileError(e.message, { operation: 'lstat', code: e.code, path: filepath })
  } finally {
    req.return()
  }

  return done(err, st, cb)
}

function lstatSync(filepath) {
  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  try {
    binding.lstatSync(req.handle, filepath)

    return new Stats(...binding.requestResultStat(req.handle))
  } catch (e) {
    throw new FileError(e.message, { operation: 'lstat', code: e.code, path: filepath })
  } finally {
    req.return()
  }
}

async function fstat(fd, cb) {
  const req = FileRequest.borrow()

  let st
  let err = null
  try {
    binding.fstat(req.handle, fd)

    await req

    st = new Stats(...binding.requestResultStat(req.handle))
  } catch (e) {
    err = new FileError(e.message, { operation: 'fstat', code: e.code, fd })
  } finally {
    req.return()
  }

  return done(err, st, cb)
}

function fstatSync(fd) {
  const req = FileRequest.borrow()

  try {
    binding.fstatSync(req.handle, fd)

    return new Stats(...binding.requestResultStat(req.handle))
  } catch (e) {
    throw new FileError(e.message, { operation: 'fstat', code: e.code, fd })
  } finally {
    req.return()
  }
}

async function statfs(filepath, cb) {
  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  let st
  let err = null
  try {
    binding.statfs(req.handle, filepath)

    await req

    st = new StatFs(...binding.requestResultStatfs(req.handle))
  } catch (e) {
    err = new FileError(e.message, { operation: 'statfs', code: e.code, path: filepath })
  } finally {
    req.return()
  }

  return done(err, st, cb)
}

function statfsSync(filepath) {
  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  try {
    binding.statfsSync(req.handle, filepath)

    return new StatFs(...binding.requestResultStatfs(req.handle))
  } catch (e) {
    throw new FileError(e.message, { operation: 'statfs', code: e.code, path: filepath })
  } finally {
    req.return()
  }
}

async function ftruncate(fd, len = 0, cb) {
  if (typeof len === 'function') {
    cb = len
    len = 0
  }

  if (typeof len !== 'number') len = 0

  const req = FileRequest.borrow()

  let err = null
  try {
    binding.ftruncate(req.handle, fd, len)

    await req
  } catch (e) {
    err = new FileError(e.message, { operation: 'ftruncate', code: e.code, fd })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function ftruncateSync(fd, len = 0) {
  if (typeof len !== 'number') len = 0

  const req = FileRequest.borrow()

  try {
    binding.ftruncateSync(req.handle, fd, len)
  } catch (e) {
    throw new FileError(e.message, { operation: 'ftruncate', code: e.code, fd })
  } finally {
    req.return()
  }
}

async function truncate(filepath, len, cb) {
  let fd = -1
  let err

  try {
    fd = await open(filepath, 'r+')

    await ftruncate(fd, len)
  } catch (e) {
    err = e
  } finally {
    if (fd !== -1) await close(fd)
  }

  return done(err, cb)
}

function truncateSync(filepath, len) {
  let fd = -1
  let err

  try {
    fd = openSync(filepath, 'r+')

    ftruncateSync(fd, len)
  } catch (e) {
    err = e
  } finally {
    if (fd !== -1) closeSync(fd)
  }
}

async function chmod(filepath, mode, cb) {
  if (typeof mode === 'string') mode = toMode(mode)

  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  let err = null
  try {
    binding.chmod(req.handle, filepath, mode)

    await req
  } catch (e) {
    err = new FileError(e.message, {
      operation: 'chmod',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function chmodSync(filepath, mode) {
  if (typeof mode === 'string') mode = toMode(mode)

  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  try {
    binding.chmodSync(req.handle, filepath, mode)
  } catch (e) {
    throw new FileError(e.message, {
      operation: 'chmod',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }
}

async function fchmod(fd, mode, cb) {
  if (typeof mode === 'string') mode = toMode(mode)

  const req = FileRequest.borrow()

  let err = null
  try {
    binding.fchmod(req.handle, fd, mode)

    await req
  } catch (e) {
    err = new FileError(e.message, { operation: 'fchmod', code: e.code, fd })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function fchmodSync(fd, mode) {
  if (typeof mode === 'string') mode = toMode(mode)

  const req = FileRequest.borrow()

  try {
    binding.fchmodSync(req.handle, fd, mode)
  } catch (e) {
    throw new FileError(e.message, { operation: 'fchmod', code: e.code, fd })
  } finally {
    req.return()
  }
}

async function chown(filepath, uid, gid, cb) {
  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  let err = null
  try {
    binding.chown(req.handle, filepath, uid, gid)

    await req
  } catch (e) {
    err = new FileError(e.message, { operation: 'chown', code: e.code, path: filepath })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function chownSync(filepath, uid, gid) {
  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  try {
    binding.chownSync(req.handle, filepath, uid, gid)
  } catch (e) {
    throw new FileError(e.message, { operation: 'chownSync', code: e.code, path: filepath })
  } finally {
    req.return()
  }
}

async function lchown(filepath, uid, gid, cb) {
  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  let err = null
  try {
    binding.lchown(req.handle, filepath, uid, gid)

    await req
  } catch (e) {
    err = new FileError(e.message, { operation: 'lchown', code: e.code, path: filepath })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function lchownSync(filepath, uid, gid) {
  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  try {
    binding.lchownSync(req.handle, filepath, uid, gid)
  } catch (e) {
    throw new FileError(e.message, { operation: 'lchownSync', code: e.code, path: filepath })
  } finally {
    req.return()
  }
}

async function fchown(fd, uid, gid, cb) {
  const req = FileRequest.borrow()

  let err = null
  try {
    binding.fchown(req.handle, fd, uid, gid)

    await req
  } catch (e) {
    err = new FileError(e.message, { operation: 'fchown', code: e.code, fd })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function fchownSync(fd, uid, gid) {
  const req = FileRequest.borrow()

  try {
    binding.fchownSync(req.handle, fd, uid, gid)
  } catch (e) {
    throw new FileError(e.message, { operation: 'fchownSync', code: e.code, fd })
  } finally {
    req.return()
  }
}

async function utimes(filepath, atime, mtime, cb) {
  if (typeof atime !== 'number') atime = atime.getTime() / 1000
  if (typeof mtime !== 'number') mtime = mtime.getTime() / 1000

  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  let err = null
  try {
    binding.utimes(req.handle, filepath, atime, mtime)

    await req
  } catch (e) {
    err = new FileError(e.message, { operation: 'utimes', code: e.code, path: filepath })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function utimesSync(filepath, atime, mtime) {
  if (typeof atime !== 'number') atime = atime.getTime() / 1000
  if (typeof mtime !== 'number') mtime = mtime.getTime() / 1000

  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  try {
    binding.utimesSync(req.handle, filepath, atime, mtime)
  } catch (e) {
    throw new FileError(e.message, { operation: 'utimes', code: e.code, path: filepath })
  } finally {
    req.return()
  }
}

async function lutimes(filepath, atime, mtime, cb) {
  if (typeof atime !== 'number') atime = atime.getTime() / 1000
  if (typeof mtime !== 'number') mtime = mtime.getTime() / 1000

  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  let err = null
  try {
    binding.lutimes(req.handle, filepath, atime, mtime)

    await req
  } catch (e) {
    err = new FileError(e.message, { operation: 'lutimes', code: e.code, path: filepath })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function lutimesSync(filepath, atime, mtime) {
  if (typeof atime !== 'number') atime = atime.getTime() / 1000
  if (typeof mtime !== 'number') mtime = mtime.getTime() / 1000

  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  try {
    binding.lutimesSync(req.handle, filepath, atime, mtime)
  } catch (e) {
    throw new FileError(e.message, { operation: 'lutimes', code: e.code, path: filepath })
  } finally {
    req.return()
  }
}

async function futimes(fd, atime, mtime, cb) {
  if (typeof atime !== 'number') atime = atime.getTime() / 1000
  if (typeof mtime !== 'number') mtime = mtime.getTime() / 1000

  const req = FileRequest.borrow()

  let err = null
  try {
    binding.futimes(req.handle, fd, atime, mtime)

    await req
  } catch (e) {
    err = new FileError(e.message, { operation: 'futimes', code: e.code, fd })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function futimesSync(fd, atime, mtime) {
  if (typeof atime !== 'number') atime = atime.getTime() / 1000
  if (typeof mtime !== 'number') mtime = mtime.getTime() / 1000

  const req = FileRequest.borrow()

  try {
    binding.futimesSync(req.handle, fd, atime, mtime)
  } catch (e) {
    throw new FileError(e.message, { operation: 'futimesSync', code: e.code, fd })
  } finally {
    req.return()
  }
}

async function link(src, dst, cb) {
  src = toNamespacedPath(src)
  dst = toNamespacedPath(dst)

  const req = FileRequest.borrow()

  let err = null
  try {
    binding.link(req.handle, src, dst)

    await req
  } catch (e) {
    err = new FileError(e.message, { operation: 'link', code: e.code, path: src, destination: dst })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function linkSync(src, dst) {
  src = toNamespacedPath(src)
  dst = toNamespacedPath(dst)

  const req = FileRequest.borrow()

  try {
    binding.linkSync(req.handle, src, dst)
  } catch (e) {
    throw new FileError(e.message, {
      operation: 'linkSync',
      code: e.code,
      path: src,
      destination: dst
    })
  } finally {
    req.return()
  }
}

async function mkdir(filepath, opts, cb) {
  if (typeof opts === 'function') {
    cb = opts
    opts = { mode: 0o777 }
  }

  if (typeof opts === 'number') opts = { mode: opts }
  else if (!opts) opts = {}

  const mode = typeof opts.mode === 'number' ? opts.mode : 0o777

  filepath = toNamespacedPath(filepath)

  if (opts.recursive) {
    let err = null
    try {
      try {
        await mkdir(filepath, { mode })
      } catch (err) {
        if (err.code !== 'ENOENT') {
          if (!(await stat(filepath)).isDirectory()) throw err
        } else {
          while (filepath.endsWith(path.sep)) filepath = filepath.slice(0, -1)
          const i = filepath.lastIndexOf(path.sep)
          if (i <= 0) throw err

          await mkdir(filepath.slice(0, i), { mode, recursive: true })

          try {
            await mkdir(filepath, { mode })
          } catch (err) {
            if (!(await stat(filepath)).isDirectory()) throw err
          }
        }
      }
    } catch (e) {
      err = e
    }

    return done(err, cb)
  }

  const req = FileRequest.borrow()

  let err = null
  try {
    binding.mkdir(req.handle, filepath, mode)

    await req
  } catch (e) {
    err = new FileError(e.message, {
      operation: 'mkdir',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function mkdirSync(filepath, opts) {
  if (typeof opts === 'number') opts = { mode: opts }
  else if (!opts) opts = {}

  const mode = typeof opts.mode === 'number' ? opts.mode : 0o777

  filepath = toNamespacedPath(filepath)

  if (opts.recursive) {
    try {
      mkdirSync(filepath, { mode })
    } catch (err) {
      if (err.code !== 'ENOENT') {
        if (!statSync(filepath).isDirectory()) throw err
      } else {
        while (filepath.endsWith(path.sep)) filepath = filepath.slice(0, -1)
        const i = filepath.lastIndexOf(path.sep)
        if (i <= 0) throw err

        mkdirSync(filepath.slice(0, i), { mode, recursive: true })

        try {
          mkdirSync(filepath, { mode })
        } catch (err) {
          if (!statSync(filepath).isDirectory()) throw err
        }
      }
    }

    return
  }

  const req = FileRequest.borrow()

  try {
    binding.mkdirSync(req.handle, filepath, mode)
  } catch (e) {
    throw new FileError(e.message, {
      operation: 'mkdir',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }
}

async function mkdtemp(prefix, cb) {
  prefix = toNamespacedPath(prefix)

  const req = FileRequest.borrow()

  let res
  let err = null
  try {
    binding.mkdtemp(req.handle, prefix + 'XXXXXX')

    await req

    res = binding.requestResultPath(req.handle)
  } catch (e) {
    err = new FileError(e.message, { operation: 'mkdtemp', code: e.code, path: prefix })
  } finally {
    req.return()
  }

  return done(err, res, cb)
}

function mkdtempSync(prefix) {
  prefix = toNamespacedPath(prefix)

  const req = FileRequest.borrow()

  try {
    binding.mkdtempSync(req.handle, prefix + 'XXXXXX')

    return binding.requestResultPath(req.handle)
  } catch (e) {
    throw new FileError(e.message, { operation: 'mkdtempSync', code: e.code, path: prefix })
  } finally {
    req.return()
  }
}

async function rmdir(filepath, cb) {
  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  let err = null
  try {
    binding.rmdir(req.handle, filepath)

    await req
  } catch (e) {
    err = new FileError(e.message, {
      operation: 'rmdir',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function rmdirSync(filepath) {
  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  try {
    binding.rmdirSync(req.handle, filepath)
  } catch (e) {
    throw new FileError(e.message, {
      operation: 'rmdir',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }
}

async function rm(filepath, opts, cb) {
  if (typeof opts === 'function') {
    cb = opts
    opts = {}
  }

  if (!opts) opts = {}

  filepath = toNamespacedPath(filepath)

  let err = null
  try {
    const st = await lstat(filepath)

    if (st.isDirectory()) {
      if (opts.recursive) {
        try {
          await rmdir(filepath)
        } catch (err) {
          if (err.code !== 'ENOTEMPTY') throw err

          const files = await readdir(filepath)

          for (const file of files) {
            await rm(filepath + path.sep + file, opts)
          }

          await rmdir(filepath)
        }
      } else {
        throw new FileError('is a directory', {
          operation: 'rm',
          code: 'EISDIR',
          path: filepath
        })
      }
    } else {
      await unlink(filepath)
    }
  } catch (e) {
    if (e.code !== 'ENOENT' || !opts.force) err = e
  }

  return done(err, cb)
}

function rmSync(filepath, opts) {
  if (!opts) opts = {}

  filepath = toNamespacedPath(filepath)

  try {
    const st = lstatSync(filepath)

    if (st.isDirectory()) {
      if (opts.recursive) {
        try {
          rmdirSync(filepath)
        } catch (err) {
          if (err.code !== 'ENOTEMPTY') throw err

          const files = readdirSync(filepath)

          for (const file of files) {
            rmSync(filepath + path.sep + file, opts)
          }

          rmdirSync(filepath)
        }
      } else {
        throw new FileError('is a directory', {
          operation: 'rm',
          code: 'EISDIR',
          path: filepath
        })
      }
    } else {
      unlinkSync(filepath)
    }
  } catch (err) {
    if (err.code !== 'ENOENT' || !opts.force) throw err
  }
}

async function unlink(filepath, cb) {
  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  let err = null
  try {
    binding.unlink(req.handle, filepath)

    await req
  } catch (e) {
    err = new FileError(e.message, {
      operation: 'unlink',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function unlinkSync(filepath) {
  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  try {
    binding.unlinkSync(req.handle, filepath)
  } catch (e) {
    throw new FileError(e.message, {
      operation: 'unlink',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }
}

async function rename(src, dst, cb) {
  src = toNamespacedPath(src)
  dst = toNamespacedPath(dst)

  const req = FileRequest.borrow()

  let err = null
  try {
    binding.rename(req.handle, src, dst)

    await req
  } catch (e) {
    err = new FileError(e.message, {
      operation: 'rename',
      code: e.code,
      path: src,
      destination: dst
    })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function renameSync(src, dst) {
  src = toNamespacedPath(src)
  dst = toNamespacedPath(dst)

  const req = FileRequest.borrow()

  try {
    binding.renameSync(req.handle, src, dst)
  } catch (e) {
    throw new FileError(e.message, {
      operation: 'rename',
      code: e.code,
      path: src,
      destination: dst
    })
  } finally {
    req.return()
  }
}

async function copyFile(src, dst, mode = 0, cb) {
  if (typeof mode === 'function') {
    cb = mode
    mode = 0
  }

  src = toNamespacedPath(src)
  dst = toNamespacedPath(dst)

  const req = FileRequest.borrow()

  let err = null
  try {
    binding.copyfile(req.handle, src, dst, mode)

    await req
  } catch (e) {
    err = new FileError(e.message, {
      operation: 'copyfile',
      code: e.code,
      path: src,
      destination: dst
    })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function copyFileSync(src, dst, mode = 0) {
  src = toNamespacedPath(src)
  dst = toNamespacedPath(dst)

  const req = FileRequest.borrow()

  try {
    binding.copyfileSync(req.handle, src, dst, mode)
  } catch (e) {
    throw new FileError(e.message, {
      operation: 'copyfile',
      code: e.code,
      path: src,
      destination: dst
    })
  } finally {
    req.return()
  }
}

async function cp(src, dst, opts, cb) {
  if (typeof opts === 'function') {
    cb = opts
    opts = {}
  }

  if (!opts) opts = {}

  src = toNamespacedPath(src)
  dst = toNamespacedPath(dst)

  let err = null
  try {
    const st = await lstat(src)

    if (st.isDirectory()) {
      if (opts.recursive !== true) {
        throw new FileError('is a directory', { operation: 'cp', code: 'EISDIR', path: src })
      }

      try {
        await lstat(dst)
      } catch (e) {
        if (e.code !== 'ENOENT') throw e

        await mkdir(dst, { mode: st.mode, recursive: true })
      }

      const dir = await opendir(src)
      for await (const { name } of dir) {
        await cp(path.join(src, name), path.join(dst, name), opts)
      }
    } else if (st.isFile()) {
      await copyFile(src, dst)
      await chmod(dst, st.mode)
    }
  } catch (e) {
    err = e
  }

  return done(err, cb)
}

function cpSync(src, dst, opts = {}) {
  src = toNamespacedPath(src)
  dst = toNamespacedPath(dst)

  const st = lstatSync(src)

  if (st.isDirectory()) {
    if (opts.recursive !== true) {
      throw new FileError('is a directory', { operation: 'cp', code: 'EISDIR', path: src })
    }

    try {
      lstatSync(dst)
    } catch (e) {
      if (e.code !== 'ENOENT') throw e

      mkdirSync(dst, { mode: st.mode, recursive: true })
    }

    const dir = opendirSync(src)
    for (const { name } of dir) {
      cpSync(path.join(src, name), path.join(dst, name), opts)
    }
  } else if (st.isFile()) {
    copyFileSync(src, dst)
    chmodSync(dst, st.mode)
  }
}

async function realpath(filepath, opts, cb) {
  if (typeof opts === 'function') {
    cb = opts
    opts = {}
  }

  if (typeof opts === 'string') opts = { encoding: opts }
  else if (!opts) opts = {}

  const { encoding = 'utf8' } = opts

  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  let res
  let err = null
  try {
    binding.realpath(req.handle, filepath)

    await req

    res = Buffer.from(binding.requestResultString(req.handle))

    if (encoding !== 'buffer') res = res.toString(encoding)
  } catch (e) {
    err = new FileError(e.message, {
      operation: 'realpath',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }

  return done(err, res, cb)
}

function realpathSync(filepath, opts) {
  if (typeof opts === 'string') opts = { encoding: opts }
  else if (!opts) opts = {}

  const { encoding = 'utf8' } = opts

  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  try {
    binding.realpathSync(req.handle, filepath)

    let res = Buffer.from(binding.requestResultString(req.handle))

    if (encoding !== 'buffer') res = res.toString(encoding)

    return res
  } catch (e) {
    throw new FileError(e.message, {
      operation: 'realpath',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }
}

async function readlink(filepath, opts, cb) {
  if (typeof opts === 'function') {
    cb = opts
    opts = {}
  }

  if (typeof opts === 'string') opts = { encoding: opts }
  else if (!opts) opts = {}

  const { encoding = 'utf8' } = opts

  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  let res
  let err = null
  try {
    binding.readlink(req.handle, filepath)

    await req

    res = Buffer.from(binding.requestResultString(req.handle))

    if (encoding !== 'buffer') res = res.toString(encoding)
  } catch (e) {
    err = new FileError(e.message, {
      operation: 'readlink',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }

  return done(err, res, cb)
}

function readlinkSync(filepath, opts) {
  if (typeof opts === 'string') opts = { encoding: opts }
  else if (!opts) opts = {}

  const { encoding = 'utf8' } = opts

  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  try {
    binding.readlinkSync(req.handle, filepath)

    let res = Buffer.from(binding.requestResultString(req.handle))

    if (encoding !== 'buffer') res = res.toString(encoding)

    return res
  } catch (e) {
    throw new FileError(e.message, {
      operation: 'readlink',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }
}

async function fsync(fd, cb) {
  const req = FileRequest.borrow()

  let err = null
  try {
    binding.fsync(req.handle, fd)

    await req
  } catch (e) {
    err = new FileError(e.message, { operation: 'fsync', code: e.code, fd })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function fsyncSync(fd) {
  const req = FileRequest.borrow()

  try {
    binding.fsyncSync(req.handle, fd)
  } catch (e) {
    throw new FileError(e.message, { operation: 'fsyncSync', code: e.code, fd })
  } finally {
    req.return()
  }
}

async function fdatasync(fd, cb) {
  const req = FileRequest.borrow()

  let err = null
  try {
    binding.fdatasync(req.handle, fd)

    await req
  } catch (e) {
    err = new FileError(e.message, { operation: 'fdatasync', code: e.code, fd })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function fdatasyncSync(fd) {
  const req = FileRequest.borrow()

  try {
    binding.fdatasyncSync(req.handle, fd)
  } catch (e) {
    throw new FileError(e.message, { operation: 'fdatasyncSync', code: e.code, fd })
  } finally {
    req.return()
  }
}

function normalizeSymlinkTarget(target, type, filepath) {
  if (isWindows) {
    if (type === constants.UV_FS_SYMLINK_JUNCTION) target = path.resolve(filepath, '..', target)

    if (path.isAbsolute(target)) return path.toNamespacedPath(target)

    return target.replace(/\//g, path.sep)
  }

  return target
}

async function symlink(target, filepath, type, cb) {
  if (typeof type === 'function') {
    cb = type
    type = null
  }

  filepath = toNamespacedPath(filepath)

  if (typeof type === 'string') {
    switch (type) {
      case 'dir':
        type = constants.UV_FS_SYMLINK_DIR
        break
      case 'junction':
        type = constants.UV_FS_SYMLINK_JUNCTION
        break
      case 'file':
      default:
        type = 0
        break
    }
  } else if (typeof type !== 'number') {
    if (isWindows) {
      target = path.resolve(filepath, '..', target)

      try {
        type = (await stat(target)).isDirectory()
          ? constants.UV_FS_SYMLINK_DIR
          : constants.UV_FS_SYMLINK_JUNCTION
      } catch {
        type = 0
      }
    } else {
      type = 0
    }
  }

  target = normalizeSymlinkTarget(target, type, filepath)

  const req = FileRequest.borrow()

  let err = null
  try {
    binding.symlink(req.handle, target, filepath, type)

    await req
  } catch (e) {
    err = new FileError(e.message, {
      operation: 'symlink',
      code: e.code,
      path: target,
      destination: filepath
    })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function symlinkSync(target, filepath, type) {
  filepath = toNamespacedPath(filepath)

  if (typeof type === 'string') {
    switch (type) {
      case 'dir':
        type = constants.UV_FS_SYMLINK_DIR
        break
      case 'junction':
        type = constants.UV_FS_SYMLINK_JUNCTION
        break
      case 'file':
      default:
        type = 0
        break
    }
  } else if (typeof type !== 'number') {
    if (isWindows) {
      target = path.resolve(filepath, '..', target)

      try {
        type = statSync(target).isDirectory()
          ? constants.UV_FS_SYMLINK_DIR
          : constants.UV_FS_SYMLINK_JUNCTION
      } catch {
        type = 0
      }
    } else {
      type = 0
    }
  }

  target = normalizeSymlinkTarget(target, type, filepath)

  const req = FileRequest.borrow()

  try {
    binding.symlinkSync(req.handle, target, filepath, type)
  } catch (e) {
    throw new FileError(e.message, {
      operation: 'symlink',
      code: e.code,
      path: target,
      destination: filepath
    })
  } finally {
    req.return()
  }
}

async function opendir(filepath, opts, cb) {
  if (typeof opts === 'function') {
    cb = opts
    opts = {}
  }

  if (typeof opts === 'string') opts = { encoding: opts }
  else if (!opts) opts = {}

  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  let dir
  let err = null
  try {
    binding.opendir(req.handle, filepath)

    await req

    dir = new Dir(filepath, binding.requestResultDir(req.handle), opts)
  } catch (e) {
    err = new FileError(e.message, {
      operation: 'opendir',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }

  return done(err, dir, cb)
}

function opendirSync(filepath, opts) {
  if (typeof opts === 'string') opts = { encoding: opts }
  else if (!opts) opts = {}

  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  try {
    binding.opendirSync(req.handle, filepath)

    return new Dir(filepath, binding.requestResultDir(req.handle), opts)
  } catch (e) {
    throw new FileError(e.message, {
      operation: 'opendir',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }
}

async function readdir(filepath, opts, cb) {
  if (typeof opts === 'function') {
    cb = opts
    opts = {}
  }

  if (typeof opts === 'string') opts = { encoding: opts }
  else if (!opts) opts = {}

  const { withFileTypes = false, recursive = false } = opts

  filepath = toNamespacedPath(filepath)

  const queue = [filepath]
  let result = []
  let err = null
  try {
    while (queue.length !== 0) {
      const dir = await opendir(queue.pop())

      for await (const entry of dir) {
        const entryPath = path.join(entry.parentPath, entry.name)

        if (withFileTypes) {
          result.push(entry)
        } else {
          result.push(path.relative(filepath, entryPath))
        }

        if (recursive && entry.isDirectory()) queue.push(entryPath)
      }
    }
  } catch (e) {
    result = []
    err = e
  }

  return done(err, result, cb)
}

function readdirSync(filepath, opts) {
  if (typeof opts === 'string') opts = { encoding: opts }
  else if (!opts) opts = {}

  const { withFileTypes = false, recursive = false } = opts

  filepath = toNamespacedPath(filepath)

  const queue = [filepath]
  const result = []

  while (queue.length !== 0) {
    const dir = opendirSync(queue.pop(), opts)

    for (const entry of dir) {
      const entryPath = path.join(entry.parentPath, entry.name)

      if (withFileTypes) {
        result.push(entry)
      } else {
        result.push(path.relative(filepath, entryPath))
      }

      if (recursive && entry.isDirectory()) queue.push(entryPath)
    }
  }

  return result
}

async function readFile(filepath, opts, cb) {
  if (typeof opts === 'function') {
    cb = opts
    opts = {}
  }

  if (typeof opts === 'string') opts = { encoding: opts }
  else if (!opts) opts = {}

  const { encoding = 'buffer' } = opts

  let fd = -1
  let buffer = null
  let err = null
  try {
    fd = await open(filepath, opts.flag || 'r')

    const st = await fstat(fd)

    let len = 0

    if (st.size === 0) {
      const buffers = []

      while (true) {
        buffer = Buffer.allocUnsafe(8192)
        const r = await read(fd, buffer)
        len += r
        if (r === 0) break
        buffers.push(buffer.subarray(0, r))
      }

      buffer = Buffer.concat(buffers)
    } else {
      buffer = Buffer.allocUnsafe(st.size)

      while (true) {
        const r = await read(fd, len ? buffer.subarray(len) : buffer)
        len += r
        if (r === 0 || len === buffer.byteLength) break
      }

      if (len !== buffer.byteLength) buffer = buffer.subarray(0, len)
    }

    if (encoding !== 'buffer') buffer = buffer.toString(encoding)
  } catch (e) {
    err = e
  } finally {
    if (fd !== -1) await close(fd)
  }

  return done(err, buffer, cb)
}

function readFileSync(filepath, opts) {
  if (typeof opts === 'string') opts = { encoding: opts }
  else if (!opts) opts = {}

  const { encoding = 'buffer' } = opts

  let fd = -1
  try {
    fd = openSync(filepath, opts.flag || 'r')

    const st = fstatSync(fd)

    let buffer
    let len = 0

    if (st.size === 0) {
      const buffers = []

      while (true) {
        buffer = Buffer.allocUnsafe(8192)
        const r = readSync(fd, buffer)
        len += r
        if (r === 0) break
        buffers.push(buffer.subarray(0, r))
      }

      buffer = Buffer.concat(buffers)
    } else {
      buffer = Buffer.allocUnsafe(st.size)

      while (true) {
        const r = readSync(fd, len ? buffer.subarray(len) : buffer)
        len += r
        if (r === 0 || len === buffer.byteLength) break
      }

      if (len !== buffer.byteLength) buffer = buffer.subarray(0, len)
    }

    if (encoding !== 'buffer') buffer = buffer.toString(encoding)

    return buffer
  } finally {
    if (fd !== -1) closeSync(fd)
  }
}

async function writeFile(filepath, data, opts, cb) {
  if (typeof opts === 'function') {
    cb = opts
    opts = {}
  }

  if (typeof opts === 'string') opts = { encoding: opts }
  else if (!opts) opts = {}

  if (typeof data === 'string') data = Buffer.from(data, opts.encoding)

  let fd = -1
  let len = 0
  let err = null
  try {
    fd = await open(filepath, opts.flag || 'w', opts.mode || 0o666)

    while (true) {
      len += await write(fd, len ? data.subarray(len) : data)
      if (len === data.byteLength) break
    }
  } catch (e) {
    err = e
  } finally {
    if (fd !== -1) await close(fd)
  }

  return done(err, len, cb)
}

function writeFileSync(filepath, data, opts) {
  if (typeof opts === 'string') opts = { encoding: opts }
  else if (!opts) opts = {}

  if (typeof data === 'string') data = Buffer.from(data, opts.encoding)

  let fd = -1
  try {
    fd = openSync(filepath, opts.flag || 'w', opts.mode || 0o666)

    let len = 0

    while (true) {
      len += writeSync(fd, len ? data.subarray(len) : data)
      if (len === data.byteLength) break
    }
  } finally {
    if (fd !== -1) closeSync(fd)
  }
}

function appendFile(filepath, data, opts, cb) {
  if (typeof opts === 'function') {
    cb = opts
    opts = {}
  }

  if (typeof opts === 'string') opts = { encoding: opts }
  else if (!opts) opts = {}

  if (!opts.flag) opts = { ...opts, flag: 'a' }

  return writeFile(filepath, data, opts, cb)
}

function appendFileSync(filepath, data, opts) {
  if (typeof opts === 'string') opts = { encoding: opts }
  else if (!opts) opts = {}

  if (!opts.flag) opts = { ...opts, flag: 'a' }

  return writeFileSync(filepath, data, opts)
}

function watch(filepath, opts, cb) {
  if (typeof opts === 'function') {
    cb = opts
    opts = {}
  }

  if (typeof opts === 'string') opts = { encoding: opts }
  else if (!opts) opts = {}

  filepath = toNamespacedPath(filepath)

  return new Watcher(filepath, opts, cb)
}

class Stats {
  constructor(
    dev,
    mode,
    nlink,
    uid,
    gid,
    rdev,
    blksize,
    ino,
    size,
    blocks,
    atimeMs,
    mtimeMs,
    ctimeMs,
    birthtimeMs
  ) {
    this.dev = dev
    this.mode = mode
    this.nlink = nlink
    this.uid = uid
    this.gid = gid
    this.rdev = rdev
    this.blksize = blksize
    this.ino = ino
    this.size = size
    this.blocks = blocks
    this.atimeMs = atimeMs
    this.mtimeMs = mtimeMs
    this.ctimeMs = ctimeMs
    this.birthtimeMs = birthtimeMs
    this.atime = new Date(atimeMs)
    this.mtime = new Date(mtimeMs)
    this.ctime = new Date(ctimeMs)
    this.birthtime = new Date(birthtimeMs)
  }

  isDirectory() {
    return (this.mode & constants.S_IFMT) === constants.S_IFDIR
  }

  isFile() {
    return (this.mode & constants.S_IFMT) === constants.S_IFREG
  }

  isBlockDevice() {
    return (this.mode & constants.S_IFMT) === constants.S_IFBLK
  }

  isCharacterDevice() {
    return (this.mode & constants.S_IFMT) === constants.S_IFCHR
  }

  isFIFO() {
    return (this.mode & constants.S_IFMT) === constants.S_IFIFO
  }

  isSymbolicLink() {
    return (this.mode & constants.S_IFMT) === constants.S_IFLNK
  }

  isSocket() {
    return (this.mode & constants.S_IFMT) === constants.S_IFSOCK
  }
}

class StatFs {
  constructor(type, bsize, blocks, bfree, bavail, files, ffree, frsize) {
    this.type = type
    this.bsize = bsize
    this.blocks = blocks
    this.bfree = bfree
    this.bavail = bavail
    this.files = files
    this.ffree = ffree
    this.frsize = frsize
  }
}

class Dir {
  constructor(path, handle, opts = {}) {
    const { encoding = 'utf8', bufferSize = 32 } = opts

    this.path = path

    this._encoding = encoding
    this._capacity = bufferSize
    this._buffer = new FIFO()
    this._ended = false
    this._handle = handle
  }

  async read(cb) {
    if (this._buffer.length) return ok(this._buffer.shift(), cb)
    if (this._ended) return ok(null, cb)

    const req = FileRequest.borrow()

    let entries
    let err = null
    try {
      req.retain(binding.readdir(req.handle, this._handle, this._capacity))

      await req

      entries = binding.requestResultDirents(req.handle)
    } catch (e) {
      err = new FileError(e.message, {
        operation: 'readdir',
        code: e.code,
        path: this.path
      })
    } finally {
      req.return()
    }

    if (err) return fail(err, cb)

    if (entries.length === 0) {
      this._ended = true

      return ok(null, cb)
    }

    for (const entry of entries) {
      let name = Buffer.from(entry.name)

      if (this._encoding !== 'buffer') name = name.toString(this._encoding)

      this._buffer.push(new Dirent(this.path, name, entry.type))
    }

    return ok(this._buffer.shift(), cb)
  }

  readSync() {
    if (this._buffer.length) return this._buffer.shift()
    if (this._ended) return null

    const req = FileRequest.borrow()

    let entries
    try {
      req.retain(binding.readdirSync(req.handle, this._handle, this._capacity))

      entries = binding.requestResultDirents(req.handle)
    } catch (e) {
      throw new FileError(e.message, {
        operation: 'readdir',
        code: e.code,
        path: this.path
      })
    } finally {
      req.return()
    }

    if (entries.length === 0) {
      this._ended = true

      return null
    }

    for (const entry of entries) {
      let name = Buffer.from(entry.name)

      if (this._encoding !== 'buffer') name = name.toString(this._encoding)

      this._buffer.push(new Dirent(this.path, name, entry.type))
    }

    return this._buffer.shift()
  }

  async close(cb) {
    const req = FileRequest.borrow()

    let err = null
    try {
      binding.closedir(req.handle, this._handle)

      await req
    } catch (e) {
      err = new FileError(e.message, {
        operation: 'closedir',
        code: e.code,
        path: this.path
      })
    } finally {
      req.return()
    }

    this._handle = null

    return done(err, cb)
  }

  closeSync() {
    const req = FileRequest.borrow()

    try {
      binding.closedirSync(req.handle, this._handle)
    } catch (e) {
      throw new FileError(e.message, {
        operation: 'closedir',
        code: e.code,
        path: this.path
      })
    } finally {
      req.return()
    }

    this._handle = null
  }

  [Symbol.dispose]() {
    this.closeSync()
  }

  async [Symbol.asyncDispose]() {
    await this.close()
  }

  *[Symbol.iterator]() {
    while (true) {
      const entry = this.readSync()
      if (entry === null) break
      yield entry
    }

    this.closeSync()
  }

  async *[Symbol.asyncIterator]() {
    while (true) {
      const entry = await this.read()
      if (entry === null) break
      yield entry
    }

    await this.close()
  }
}

class Dirent {
  constructor(parentPath, name, type) {
    this.parentPath = parentPath
    this.name = name
    this.type = type
  }

  isFile() {
    return this.type === constants.UV_DIRENT_FILE
  }

  isDirectory() {
    return this.type === constants.UV_DIRENT_DIR
  }

  isSymbolicLink() {
    return this.type === constants.UV_DIRENT_LINK
  }

  isFIFO() {
    return this.type === constants.UV_DIRENT_FIFO
  }

  isSocket() {
    return this.type === constants.UV_DIRENT_SOCKET
  }

  isCharacterDevice() {
    return this.type === constants.UV_DIRENT_CHAR
  }

  isBlockDevice() {
    return this.type === constants.UV_DIRENT_BLOCK
  }
}

class FileReadStream extends Readable {
  constructor(path, opts = {}) {
    const { eagerOpen = true } = opts

    super({ eagerOpen, ...opts })

    this.path = path
    this.fd = typeof opts.fd === 'number' ? opts.fd : -1
    this.flags = opts.flags || 'r'
    this.mode = opts.mode || 0o666

    this._offset = opts.start || 0
    this._missing = 0

    if (opts.length) {
      this._missing = opts.length
    } else if (typeof opts.end === 'number') {
      this._missing = opts.end - this._offset + 1
    } else {
      this._missing = -1
    }
  }

  async _open(cb) {
    let err

    if (this.fd === -1) {
      err = null
      try {
        this.fd = await open(this.path, this.flags, this.mode)
      } catch (e) {
        err = e
      }

      if (err) return cb(err)
    }

    let st
    err = null
    try {
      st = await fstat(this.fd)
    } catch (e) {
      err = e
    }

    if (err) return cb(err)

    this._isCharacterDevice = st.isCharacterDevice()

    if (this._isCharacterDevice) {
      this._missing = Infinity
    } else {
      if (this._missing === -1) this._missing = st.size

      if (st.size < this._offset) {
        this._offset = st.size
        this._missing = 0
      } else if (st.size < this._offset + this._missing) {
        this._missing = st.size - this._offset
      }
    }

    cb(null)
  }

  async _read(size) {
    if (this._missing <= 0) return this.push(null)

    const data = Buffer.allocUnsafe(Math.min(this._missing, size))

    let len
    let err = null
    try {
      len = await read(
        this.fd,
        data,
        0,
        data.byteLength,
        this._isCharacterDevice ? -1 : this._offset
      )
    } catch (e) {
      err = e
    }

    if (err) return this.destroy(err)

    if (len === 0) return this.push(null)

    if (this._missing < len) len = this._missing

    this._missing -= len
    this._offset += len

    this.push(data.subarray(0, len))
  }

  async _destroy(err, cb) {
    if (this.fd === -1) return cb(err)

    try {
      await close(this.fd)
    } catch (e) {
      err = err || e
    }

    cb(err)
  }
}

class FileWriteStream extends Writable {
  constructor(path, opts = {}) {
    const { eagerOpen = true } = opts

    super({ eagerOpen, ...opts })

    this.path = path
    this.fd = typeof opts.fd === 'number' ? opts.fd : -1
    this.flags = opts.flags || 'w'
    this.mode = opts.mode || 0o666
  }

  async _open(cb) {
    if (this.fd !== -1) return cb(null)

    let err = null
    try {
      this.fd = await open(this.path, this.flags, this.mode)
    } catch (e) {
      err = e
    }

    cb(err)
  }

  async _writev(batch, cb) {
    let err = null
    try {
      await writev(
        this.fd,
        batch.map(({ chunk }) => chunk)
      )
    } catch (e) {
      err = e
    }

    cb(err)
  }

  async _destroy(err, cb) {
    if (this.fd === -1) return cb(err)

    try {
      await close(this.fd)
    } catch (e) {
      err = err || e
    }

    cb(err)
  }
}

class Watcher extends EventEmitter {
  constructor(path, opts, onchange) {
    if (typeof opts === 'function') {
      onchange = opts
      opts = {}
    }

    if (!opts) opts = {}

    const { persistent = true, recursive = false, encoding = 'utf8' } = opts

    super()

    this._closed = false
    this._encoding = encoding
    this._handle = binding.watcherInit(path, recursive, this, this._onevent, this._onclose)

    if (!persistent) this.unref()

    if (onchange) this.on('change', onchange)
  }

  close() {
    if (this._closed) return
    this._closed = true

    binding.watcherClose(this._handle)
  }

  ref() {
    if (this._handle) binding.watcherRef(this._handle)
    return this
  }

  unref() {
    if (this._handle) binding.watcherUnref(this._handle)
    return this
  }

  [Symbol.asyncIterator]() {
    const buffer = []
    let done = false
    let error = null
    let next = null

    this.on('change', (eventType, filename) => {
      if (next) {
        next.resolve({ done: false, value: { eventType, filename } })
        next = null
      } else {
        buffer.push({ eventType, filename })
      }
    })
      .on('error', (err) => {
        done = true
        error = err

        if (next) {
          next.reject(error)
          next = null
        }
      })
      .on('close', () => {
        done = true

        if (next) {
          next.resolve({ done })
          next = null
        }
      })

    return {
      next: () =>
        new Promise((resolve, reject) => {
          if (error) return reject(error)

          if (buffer.length) return resolve({ done: false, value: buffer.shift() })

          if (done) return resolve({ done })

          next = { resolve, reject }
        })
    }
  }

  _onevent(err, events, filename) {
    if (err) {
      this.close()
      this.emit('error', err)
    } else {
      const path =
        this._encoding === 'buffer'
          ? Buffer.from(filename)
          : Buffer.from(filename).toString(this._encoding)

      if (events & binding.UV_RENAME) {
        this.emit('change', 'rename', path)
      }

      if (events & binding.UV_CHANGE) {
        this.emit('change', 'change', path)
      }
    }
  }

  _onclose() {
    this._handle = null

    this.emit('close')
  }
}

exports.access = access
exports.appendFile = appendFile
exports.chmod = chmod
exports.chown = chown
exports.close = close
exports.copyFile = copyFile
exports.cp = cp
exports.exists = exists
exports.fchmod = fchmod
exports.fchown = fchown
exports.fdatasync = fdatasync
exports.fstat = fstat
exports.fsync = fsync
exports.ftruncate = ftruncate
exports.futimes = futimes
exports.lchown = lchown
exports.lutimes = lutimes
exports.link = link
exports.lstat = lstat
exports.mkdir = mkdir
exports.mkdtemp = mkdtemp
exports.open = open
exports.opendir = opendir
exports.read = read
exports.readFile = readFile
exports.readdir = readdir
exports.readlink = readlink
exports.readv = readv
exports.realpath = realpath
exports.rename = rename
exports.rm = rm
exports.rmdir = rmdir
exports.stat = stat
exports.statfs = statfs
exports.symlink = symlink
exports.truncate = truncate
exports.unlink = unlink
exports.utimes = utimes
exports.watch = watch
exports.write = write
exports.writeFile = writeFile
exports.writev = writev

exports.accessSync = accessSync
exports.appendFileSync = appendFileSync
exports.chmodSync = chmodSync
exports.chownSync = chownSync
exports.closeSync = closeSync
exports.copyFileSync = copyFileSync
exports.cpSync = cpSync
exports.existsSync = existsSync
exports.fchmodSync = fchmodSync
exports.fchownSync = fchownSync
exports.fdatasyncSync = fdatasyncSync
exports.fstatSync = fstatSync
exports.fsyncSync = fsyncSync
exports.ftruncateSync = ftruncateSync
exports.futimesSync = futimesSync
exports.lchownSync = lchownSync
exports.lutimesSync = lutimesSync
exports.linkSync = linkSync
exports.lstatSync = lstatSync
exports.mkdirSync = mkdirSync
exports.mkdtempSync = mkdtempSync
exports.openSync = openSync
exports.opendirSync = opendirSync
exports.readFileSync = readFileSync
exports.readSync = readSync
exports.readdirSync = readdirSync
exports.readlinkSync = readlinkSync
exports.readvSync = readvSync
exports.realpathSync = realpathSync
exports.renameSync = renameSync
exports.rmSync = rmSync
exports.rmdirSync = rmdirSync
exports.statSync = statSync
exports.statfsSync = statfsSync
exports.symlinkSync = symlinkSync
exports.truncateSync = truncateSync
exports.unlinkSync = unlinkSync
exports.utimesSync = utimesSync
exports.writeFileSync = writeFileSync
exports.writeSync = writeSync
exports.writevSync = writevSync

exports.promises = require('./promises')

exports.Stats = Stats
exports.StatFs = StatFs
exports.Dir = Dir
exports.Dirent = Dirent
exports.Watcher = Watcher

exports.ReadStream = FileReadStream

exports.createReadStream = function createReadStream(path, opts) {
  return new FileReadStream(path, opts)
}

exports.WriteStream = FileWriteStream

exports.createWriteStream = function createWriteStream(path, opts) {
  return new FileWriteStream(path, opts)
}

function toNamespacedPath(filepath) {
  if (typeof filepath !== 'string') {
    if (isURL(filepath)) filepath = fileURLToPath(filepath)
    else filepath = filepath.toString()
  }

  return path.toNamespacedPath(filepath)
}

function toFlags(flags) {
  switch (flags) {
    case 'r':
      return constants.O_RDONLY
    case 'rs': // Fall through.
    case 'sr':
      return constants.O_RDONLY | constants.O_SYNC
    case 'r+':
      return constants.O_RDWR
    case 'rs+': // Fall through.
    case 'sr+':
      return constants.O_RDWR | constants.O_SYNC

    case 'w':
      return constants.O_TRUNC | constants.O_CREAT | constants.O_WRONLY
    case 'wx': // Fall through.
    case 'xw':
      return constants.O_TRUNC | constants.O_CREAT | constants.O_WRONLY | constants.O_EXCL

    case 'w+':
      return constants.O_TRUNC | constants.O_CREAT | constants.O_RDWR
    case 'wx+': // Fall through.
    case 'xw+':
      return constants.O_TRUNC | constants.O_CREAT | constants.O_RDWR | constants.O_EXCL

    case 'a':
      return constants.O_APPEND | constants.O_CREAT | constants.O_WRONLY
    case 'ax': // Fall through.
    case 'xa':
      return constants.O_APPEND | constants.O_CREAT | constants.O_WRONLY | constants.O_EXCL
    case 'as': // Fall through.
    case 'sa':
      return constants.O_APPEND | constants.O_CREAT | constants.O_WRONLY | constants.O_SYNC

    case 'a+':
      return constants.O_APPEND | constants.O_CREAT | constants.O_RDWR
    case 'ax+': // Fall through.
    case 'xa+':
      return constants.O_APPEND | constants.O_CREAT | constants.O_RDWR | constants.O_EXCL
    case 'as+': // Fall through.
    case 'sa+':
      return constants.O_APPEND | constants.O_CREAT | constants.O_RDWR | constants.O_SYNC
    default:
      return 0
  }
}

function toMode(mode) {
  return parseInt(mode, 8)
}
const binding = require('../binding')

module.exports = {
  O_RDWR: binding.O_RDWR,
  O_RDONLY: binding.O_RDONLY,
  O_WRONLY: binding.O_WRONLY,
  O_CREAT: binding.O_CREAT,
  O_TRUNC: binding.O_TRUNC,
  O_APPEND: binding.O_APPEND,

  F_OK: binding.F_OK || 0,
  R_OK: binding.R_OK || 0,
  W_OK: binding.W_OK || 0,
  X_OK: binding.X_OK || 0,

  S_IFMT: binding.S_IFMT,
  S_IFREG: binding.S_IFREG,
  S_IFDIR: binding.S_IFDIR,
  S_IFCHR: binding.S_IFCHR,
  S_IFLNK: binding.S_IFLNK,
  S_IFBLK: binding.S_IFBLK || 0,
  S_IFIFO: binding.S_IFIFO || 0,
  S_IFSOCK: binding.S_IFSOCK || 0,

  S_IRUSR: binding.S_IRUSR || 0,
  S_IWUSR: binding.S_IWUSR || 0,
  S_IXUSR: binding.S_IXUSR || 0,
  S_IRGRP: binding.S_IRGRP || 0,
  S_IWGRP: binding.S_IWGRP || 0,
  S_IXGRP: binding.S_IXGRP || 0,
  S_IROTH: binding.S_IROTH || 0,
  S_IWOTH: binding.S_IWOTH || 0,
  S_IXOTH: binding.S_IXOTH || 0,

  UV_DIRENT_UNKNOWN: binding.UV_DIRENT_UNKNOWN,
  UV_DIRENT_FILE: binding.UV_DIRENT_FILE,
  UV_DIRENT_DIR: binding.UV_DIRENT_DIR,
  UV_DIRENT_LINK: binding.UV_DIRENT_LINK,
  UV_DIRENT_FIFO: binding.UV_DIRENT_FIFO,
  UV_DIRENT_SOCKET: binding.UV_DIRENT_SOCKET,
  UV_DIRENT_CHAR: binding.UV_DIRENT_CHAR,
  UV_DIRENT_BLOCK: binding.UV_DIRENT_BLOCK,

  COPYFILE_EXCL: binding.UV_FS_COPYFILE_EXCL,
  COPYFILE_FICLONE: binding.UV_FS_COPYFILE_FICLONE,
  COPYFILE_FICLONE_FORCE: binding.UV_FS_COPYFILE_FICLONE_FORCE,
  UV_FS_SYMLINK_DIR: binding.UV_FS_SYMLINK_DIR,
  UV_FS_SYMLINK_JUNCTION: binding.UV_FS_SYMLINK_JUNCTION
}
const binding = require('../binding')

module.exports = class FileError extends Error {
  constructor(msg, opts = {}) {
    const { code, operation = null, path = null, destination = null, fd = -1 } = opts

    if (operation !== null) msg += describe(operation, opts)

    super(`${code}: ${msg}`)

    this.code = code

    if (operation !== null) this.operation = operation
    if (path !== null) this.path = path
    if (destination !== null) this.destination = destination
    if (fd !== -1) this.fd = fd
  }

  get name() {
    return 'FileError'
  }

  // For Node.js compatibility
  get errno() {
    return binding.errnos[this.code]
  }

  // For Node.js compatibility
  get syscall() {
    return this.operation
  }

  // For Node.js compatibility
  get dest() {
    return this.destination
  }
}

function describe(operation, opts) {
  const { path = null, destination = null, fd = -1 } = opts

  let result = `, ${operation}`

  if (path !== null) {
    result += ` ${JSON.stringify(path)}`

    if (destination !== null) {
      result += ` -> ${JSON.stringify(destination)}`
    }
  } else if (fd !== -1) {
    result += ` ${fd}`
  }

  return result
}
{
  "name": "bare-fs",
  "version": "4.8.1",
  "description": "Native file system operations for Bare",
  "exports": {
    "./package": "./package.json",
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    },
    "./promises": {
      "types": "./promises.d.ts",
      "default": "./promises.js"
    },
    "./constants": {
      "types": "./lib/constants.d.ts",
      "default": "./lib/constants.js"
    }
  },
  "files": [
    "index.js",
    "index.d.ts",
    "promises.js",
    "promises.d.ts",
    "binding.c",
    "binding.js",
    "CMakeLists.txt",
    "lib",
    "prebuilds"
  ],
  "addon": true,
  "scripts": {
    "format": "prettier --write . && lunte --fix",
    "lint": "prettier --check . && lunte",
    "test": "brittle-bare --coverage test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-fs.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-fs/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-fs#readme",
  "engines": {
    "bare": ">=1.28.0"
  },
  "dependencies": {
    "bare-events": "^2.5.4",
    "bare-path": "^3.0.0",
    "bare-stream": "^2.6.4",
    "bare-url": "^2.2.2",
    "fast-fifo": "^1.3.2"
  },
  "devDependencies": {
    "bare-buffer": "^3.0.2",
    "bare-crypto": "^1.11.2",
    "brittle": "^3.1.1",
    "cmake-bare": "^1.1.7",
    "lunte": "^1.6.0",
    "prettier": "^3.4.1",
    "prettier-config-holepunch": "^2.0.0"
  },
  "peerDependencies": {
    "bare-buffer": "*"
  },
  "peerDependenciesMeta": {
    "bare-buffer": {
      "optional": true
    }
  }
}
const EventEmitter = require('bare-events')
const fs = require('.')

class FileHandle extends EventEmitter {
  constructor(fd) {
    super()

    this.fd = fd
  }

  async close() {
    await fs.close(this.fd)

    this.fd = -1
    this.emit('close')
  }

  async read(buffer, ...args) {
    return {
      bytesRead: await fs.read(this.fd, buffer, ...args),
      buffer
    }
  }

  async readv(buffers, ...args) {
    return {
      bytesRead: await fs.readv(this.fd, buffers, ...args),
      buffers
    }
  }

  async write(buffer, ...args) {
    return {
      bytesWritten: await fs.write(this.fd, buffer, ...args),
      buffer
    }
  }

  async writev(buffers, ...args) {
    return {
      bytesWritten: await fs.writev(this.fd, buffers, ...args),
      buffers
    }
  }

  async stat() {
    return fs.fstat(this.fd)
  }

  async chmod(mode) {
    await fs.fchmod(this.fd, mode)
  }

  async chown(uid, gid) {
    await fs.fchown(this.fd, uid, gid)
  }

  async datasync() {
    return fs.fdatasync(this.fd)
  }

  async sync() {
    return fs.fsync(this.fd)
  }

  async truncate(len) {
    await fs.ftruncate(this.fd, len)
  }

  async utimes(atime, mtime) {
    await fs.futimes(this.fd, atime, mtime)
  }

  createReadStream(opts) {
    return fs.createReadStream(null, { ...opts, fd: this.fd })
  }

  createWriteStream(opts) {
    return fs.createWriteStream(null, { ...opts, fd: this.fd })
  }

  async [Symbol.asyncDispose]() {
    await this.close()
  }
}

exports.open = async function open(filepath, flags, mode) {
  return new FileHandle(await fs.open(filepath, flags, mode))
}

exports.access = fs.access
exports.appendFile = fs.appendFile
exports.chmod = fs.chmod
exports.chown = fs.chown
exports.constants = fs.constants
exports.copyFile = fs.copyFile
exports.cp = fs.cp
exports.lchown = fs.lchown
exports.lutimes = fs.lutimes
exports.link = fs.link
exports.lstat = fs.lstat
exports.mkdir = fs.mkdir
exports.mkdtemp = fs.mkdtemp
exports.opendir = fs.opendir
exports.readFile = fs.readFile
exports.readdir = fs.readdir
exports.readlink = fs.readlink
exports.realpath = fs.realpath
exports.rename = fs.rename
exports.rm = fs.rm
exports.rmdir = fs.rmdir
exports.stat = fs.stat
exports.statfs = fs.statfs
exports.truncate = fs.truncate
exports.symlink = fs.symlink
exports.unlink = fs.unlink
exports.utimes = fs.utimes
exports.watch = fs.watch
exports.writeFile = fs.writeFile
module.exports = require.addon()
const binding = require('./binding')

module.exports = exports = function hrtime(past) {
  let now = binding.hrtime()

  if (past) now -= BigInt(past[0]) * 1000000000n + BigInt(past[1])

  return [Number(now / 1000000000n), Number(now % 1000000000n)]
}

exports.bigint = function hrtime() {
  return binding.hrtime()
}
{
  "name": "bare-hrtime",
  "version": "2.1.2",
  "description": "High-resolution timers for JavaScript",
  "exports": {
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    },
    "./package": "./package.json"
  },
  "files": [
    "index.js",
    "index.d.ts",
    "binding.c",
    "binding.js",
    "CMakeLists.txt",
    "prebuilds"
  ],
  "addon": true,
  "scripts": {
    "format": "prettier --write . && lunte --fix",
    "lint": "prettier --check . && lunte",
    "test": "brittle-bare --coverage test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-hrtime.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-hrtime/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-hrtime#readme",
  "devDependencies": {
    "brittle": "^3.2.1",
    "cmake-bare": "^1.1.6",
    "lunte": "^1.8.4",
    "prettier": "^3.4.2",
    "prettier-config-holepunch": "^2.0.0"
  }
}
module.exports = require.addon()
const ansiEscapes = require('bare-ansi-escapes')
const getType = require('bare-type')
const binding = require('./binding')

const defaultDepth = 2
const defaultBreakLength = 80
const defaultMaxArrayLength = 40

module.exports = exports = function inspect(value, opts = {}) {
  const {
    colors = false,
    depth = defaultDepth,
    breakLength = defaultBreakLength,
    stylize = defaultStylize(colors)
  } = opts

  const references = new InspectRefMap()

  const tree = inspectValue(value, 0, {
    colors,
    depth,
    breakLength,
    stylize,
    references
  })

  return tree.toString()
}

exports.styles = {
  bigint: ansiEscapes.colorYellow,
  boolean: ansiEscapes.colorYellow,
  date: ansiEscapes.colorMagenta,
  module: ansiEscapes.modifierUnderline,
  name: ansiEscapes.modifierReset,
  null: ansiEscapes.modifierBold,
  number: ansiEscapes.colorYellow,
  regexp: ansiEscapes.colorRed,
  special: ansiEscapes.colorCyan,
  string: ansiEscapes.colorGreen,
  symbol: ansiEscapes.colorGreen,
  undefined: ansiEscapes.colorBrightBlack
}

const styles = exports.styles

function defaultStylize(colors) {
  return function stylize(value, style) {
    const color = colors && styles[style]

    if (color) return color + value + ansiEscapes.modifierReset

    return value
  }
}

class InspectRefMap {
  constructor() {
    this.refs = new WeakMap()
    this.ids = new WeakMap()
    this.nextId = 1
  }

  has(object) {
    return this.refs.has(object)
  }

  get(object) {
    return this.refs.get(object) || null
  }

  set(object, ref) {
    this.refs.set(object, ref)
  }

  id(object) {
    let id = this.ids.get(object)
    if (id) return id

    id = this.nextId++
    this.ids.set(object, id)

    return id
  }
}

class InspectNode {
  constructor(depth, length, opts) {
    const { breakLength = defaultBreakLength, breakAlways = false } = opts

    this.depth = depth
    this.length = length
    this.breakLength = breakLength
    this.breakAlways = breakAlways
  }

  pad(n, string) {
    return string.padStart(n, ' ')
  }

  indent(n, string) {
    return '  '.repeat(n) + string
  }
}

class InspectRef extends InspectNode {
  constructor(depth, opts) {
    super(depth, '[circular *]'.length, opts)

    this.refs = opts.references
    this.count = 0
    this.circular = false
    this.color = opts.colors && styles.special
  }

  get id() {
    return this.refs.id(this)
  }

  increment() {
    return ++this.count
  }

  decrement() {
    return --this.count
  }

  toString(opts = {}) {
    const { offset = 0, pad = 0, indent = 0 } = opts

    let value = this.pad(pad, '[circular *' + this.id + ']')

    if (this.color) value = this.color + value + ansiEscapes.modifierReset

    return offset ? value : this.indent(indent, value)
  }
}

class InspectLeaf extends InspectNode {
  constructor(value, color, depth, opts) {
    const length = value.length

    if (value.includes('\n')) {
      value = value.replaceAll('\n', '\n' + '  '.repeat(depth))

      opts = { ...opts, breakAlways: true }
    }

    super(depth, length, opts)

    this.value = value
    this.color = opts.colors && color
  }

  toString(opts = {}) {
    const { offset = 0, pad = 0, indent = 0 } = opts

    let value = this.pad(pad, this.value)

    if (this.color) value = this.color + value + ansiEscapes.modifierReset

    return offset ? value : this.indent(indent, value)
  }
}

class InspectPair extends InspectNode {
  constructor(delim, left, right, depth, opts) {
    const length = left.length + delim.length + right.length

    if (left.breakAlways || right.breakAlways) {
      opts = { ...opts, breakAlways: true }
    }

    super(depth, length, opts)

    this.delim = delim
    this.left = left
    this.right = right
  }

  toString(opts = {}) {
    const { indent = 0 } = opts

    return this.indent(
      indent,
      this.left +
        this.delim +
        this.right.toString({
          indent,
          offset: this.left.length + this.delim.length
        })
    )
  }
}

class InspectSuspension extends InspectNode {
  constructor(overflow, depth, opts) {
    const label = `... ${overflow} more`

    super(depth, label.length, opts)

    this.label = label
  }

  toString(opts = {}) {
    const { indent = 0 } = opts

    return this.indent(indent, this.label)
  }
}

class InspectSequence extends InspectNode {
  constructor(header, footer, delim, values, ref, depth, opts) {
    const { tabulate = false } = opts

    const length =
      (ref.circular ? '<ref *>'.length + 1 : 0) +
      header.length +
      values.reduce((length, value, i) => length + value.length + (i === 0 ? 0 : delim.length), 0) +
      footer.length

    if (values.some((value) => value.breakAlways)) {
      opts = { ...opts, breakAlways: true }
    }

    super(depth, length, opts)

    this.header = header
    this.footer = footer
    this.delim = delim
    this.values = values
    this.ref = ref
    this.tabulate = tabulate
  }

  toString(opts = {}) {
    const { offset = 0, indent = 0 } = opts

    const split =
      this.breakAlways ||
      (this.values.length &&
        (offset + this.length > this.breakLength || indent * 2 + this.length > this.breakLength))

    let header = this.header

    if (this.ref.circular) {
      header = '<ref *' + this.ref.id + '> ' + header
    }

    if (this.values.length === 0) {
      header = header.trimEnd()
    }

    if (offset === 0) {
      header = this.indent(indent, header)
    }

    if (split) {
      header = header.trimEnd() + '\n'
    }

    let string = header

    let columns = 1
    let pad = 0

    if (this.tabulate) {
      const widest = this.values.reduce(
        (length, value) => (value.breakAlways ? length : Math.max(length, value.length)),
        0
      )

      if (widest) {
        columns = Math.max(
          columns,
          Math.floor((this.breakLength - indent * 2) / (widest + this.delim.length))
        )

        if (columns > 1) pad = widest
      }
    }

    for (let i = 0, n = this.values.length, offset = 0; i < n; i++) {
      const value = this.values[i]

      if (split) {
        let part

        if (i % columns === 0 || value.breakAlways) {
          part = value.toString({ indent: indent + 1, pad })
        } else {
          part = value.toString({ pad })
        }

        string += part

        if (i < n - 1) {
          if (i % columns === columns - 1 || this.values[i + 1].breakAlways) {
            string += this.delim.trimEnd() + '\n'
          } else {
            string += this.delim
          }
        }
      } else {
        if (i > 0) string += this.delim

        string += value.toString({ offset })

        // TODO Lunte thinks this refers to `const { offset }...` further up
        offset += value.length // eslint-disable-line
      }
    }

    let footer = this.footer

    if (this.values.length === 0) {
      footer = footer.trimStart()
    }

    if (split) {
      string += '\n' + this.indent(indent, footer.trimStart())
    } else {
      string += footer
    }

    return string
  }
}

function inspectValue(value, depth, opts) {
  const type = getType(value)

  if (type.isUndefined()) return inspectUndefined(depth, opts)
  if (type.isNull()) return inspectNull(depth, opts)
  if (type.isBoolean()) return inspectBoolean(value, depth, opts)
  if (type.isNumber()) return inspectNumber(value, depth, opts)
  if (type.isBigInt()) return inspectBigInt(value, depth, opts)
  if (type.isString()) return inspectString(value, depth, opts)
  if (type.isSymbol()) return inspectSymbol(value, depth, opts)
  if (type.isObject()) return inspectObject(type, value, depth, opts)
  if (type.isFunction()) return inspectFunction(type, value, depth, opts)
  if (type.isExternal()) return inspectExternal(value, depth, opts)
}

function inspectUndefined(depth, opts) {
  return new InspectLeaf('undefined', styles.undefined, depth, opts)
}

function inspectNull(depth, opts) {
  return new InspectLeaf('null', styles.null, depth, opts)
}

function inspectBoolean(value, depth, opts) {
  return new InspectLeaf(value.toString(), styles.boolean, depth, opts)
}

function inspectNumber(value, depth, opts) {
  let string

  if (Object.is(value, -0)) {
    string = '-0'
  } else {
    string = value.toString(10)
  }

  return new InspectLeaf(string, styles.number, depth, opts)
}

function inspectBigInt(value, depth, opts) {
  return new InspectLeaf(value.toString(10) + 'n', styles.bigint, depth, opts)
}

const STRING_ESCAPES = /[\ud800-\udbff][\udc00-\udfff]|[\u0000-\u001f'\\\ud800-\udfff]/g

function inspectString(value, depth, opts) {
  // https://tc39.es/ecma262/multipage/structured-data.html#sec-quotejsonstring
  const string = value.replace(STRING_ESCAPES, (match) => {
    if (match.length === 2) return match

    switch (match) {
      case "'":
        return "\\'"
      case '\\':
        return '\\\\'
      case '\b':
        return '\\b'
      case '\t':
        return '\\t'
      case '\n':
        return '\\n'
      case '\f':
        return '\\f'
      case '\r':
        return '\\r'
      default:
        return '\\u' + match.charCodeAt(0).toString(16).padStart(4, '0')
    }
  })

  return new InspectLeaf("'" + string + "'", styles.string, depth, opts)
}

function inspectSymbol(value, depth, opts) {
  return new InspectLeaf(value.toString(), styles.symbol, depth, opts)
}

const PLAIN_KEY = /^[a-zA-Z_][a-zA-Z_0-9]*$/

function inspectKey(value, depth, opts) {
  if (typeof value === 'symbol') {
    return inspectValue(value, depth, opts)
  } else if (PLAIN_KEY.test(value)) {
    return new InspectLeaf(value, null, depth, opts)
  } else {
    return inspectValue(value, depth, opts)
  }
}

function inspectObject(type, object, depth, opts) {
  const refs = opts.references

  let ref = refs.get(object)
  if (ref === null) {
    ref = new InspectRef(depth, opts)
    refs.set(object, ref)
  } else if (ref.count) {
    ref.circular = true
    return ref
  }

  const maxDepth = typeof opts.depth === 'number' ? opts.depth : Infinity

  if (maxDepth < depth) {
    const name = nameOf(object)

    return new InspectLeaf('[' + (name || 'Object') + ']', styles.special, depth, opts)
  }

  const { value: inspect } = attempt(
    () => object[Symbol.for('bare.inspect')] || object[Symbol.for('nodejs.util.inspect.custom')]
  )

  if (typeof inspect === 'function' && isPrototype(object) === false) {
    const { value, threw, error } = attempt(() =>
      inspect.call(
        object,
        typeof opts.depth === 'number' ? opts.depth - depth : null,
        {
          colors: opts.colors,
          breakLength: opts.breakLength,
          stylize: opts.stylize
        },
        exports
      )
    )

    if (threw) {
      return new InspectLeaf(
        '<inspect threw ' + errorLabel(error) + '>',
        styles.special,
        depth,
        opts
      )
    }

    if (typeof value === 'object' && value !== null) {
      refs.set(value, ref)
    }

    if (typeof value !== 'string') {
      return inspectValue(value, depth, opts)
    }

    return new InspectLeaf(value, null, depth, opts)
  }

  if (type.isArray()) return inspectArray(object, ref, depth, opts)
  if (type.isDate()) return inspectDate(object, ref, depth, opts)
  if (type.isRegExp()) return inspectRegExp(object, ref, depth, opts)
  if (type.isError()) return inspectError(object, ref, depth, opts)
  if (type.isPromise()) return inspectPromise(object, ref, depth, opts)
  if (type.isMap()) return inspectMap(object, ref, depth, opts)
  if (type.isSet()) return inspectSet(object, ref, depth, opts)
  if (type.isWeakMap()) return inspectWeakMap(object, ref, depth, opts)
  if (type.isWeakSet()) return inspectWeakSet(object, ref, depth, opts)
  if (type.isWeakRef()) return inspectWeakRef(object, ref, depth, opts)
  if (type.isArrayBuffer()) return inspectArrayBuffer(object, ref, depth, opts)
  if (type.isSharedArrayBuffer()) return inspectSharedArrayBuffer(object, ref, depth, opts)
  if (type.isTypedArray()) return inspectTypedArray(object, ref, depth, opts)
  if (type.isDataView()) return inspectDataView(object, ref, depth, opts)

  ref.increment()

  const values = []

  for (const key of keysOf(object)) {
    if (key === 'constructor') continue

    values.push(
      new InspectPair(
        ': ',
        inspectKey(key, depth + 1, opts),
        inspectProperty(object, key, depth + 1, opts),
        depth + 1,
        opts
      )
    )
  }

  for (const key of symbolsOf(object)) {
    values.push(
      new InspectPair(
        ': ',
        inspectKey(key, depth + 1, opts),
        inspectProperty(object, key, depth + 1, opts),
        depth + 1,
        opts
      )
    )
  }

  ref.decrement()

  let header = '{ '

  const { value: tag } = attempt(() => object[Symbol.toStringTag])

  if (typeof tag === 'string' && tag) header = '[' + tag + '] ' + header

  const name = nameOf(object)

  if (name && name !== 'Object') header = name + ' ' + header

  return new InspectSequence(header, ' }', ', ', values, ref, depth, opts)
}

function inspectArray(array, ref, depth, opts) {
  const { maxArrayLength = defaultMaxArrayLength } = opts

  ref.increment()

  const values = []

  let remaining = Math.max(maxArrayLength, 0)

  for (let i = 0, n = array.length; i < n; i++) {
    if (remaining-- === 0) {
      values.push(
        new InspectSuspension(array.length - values.length, depth + 1, {
          ...opts,
          breakAlways: true
        })
      )

      break
    }

    values.push(inspectProperty(array, i, depth + 1, opts))
  }

  for (const key of nonIndexKeysOf(array)) {
    values.push(
      new InspectPair(
        ': ',
        inspectKey(key, depth + 1, opts),
        inspectProperty(array, key, depth + 1, opts),
        depth + 1,
        { ...opts, breakAlways: remaining < 0 }
      )
    )
  }

  ref.decrement()

  let header = '[ '

  const name = nameOf(array)

  if (name && name !== 'Array') header = name + '(' + array.length + ') ' + header

  return new InspectSequence(header, ' ]', ', ', values, ref, depth, {
    ...opts,
    tabulate: true
  })
}

function inspectProperty(object, key, depth, opts) {
  const descriptor = descriptorOf(object, key)

  if (descriptor === null) {
    return new InspectLeaf('<unreadable>', styles.special, depth, opts)
  }

  if (descriptor === undefined) return inspectUndefined(depth, opts)

  if ('value' in descriptor === false) {
    let label = '[getter/setter]'

    if (descriptor.get === undefined) label = '[setter]'
    else if (descriptor.set === undefined) label = '[getter]'

    return new InspectLeaf(label, styles.special, depth, opts)
  }

  return inspectValue(descriptor.value, depth, opts)
}

function inspectDate(date, ref, depth, opts) {
  const { value: time } = attempt(() => date.getTime())

  if (Number.isNaN(time)) {
    return new InspectLeaf('Invalid Date', styles.date, depth, opts)
  }

  const { value: string } = attempt(() => date.toISOString())

  return new InspectLeaf(string || '[Date]', styles.date, depth, opts)
}

function inspectRegExp(regExp, ref, depth, opts) {
  const { value: string } = attempt(() => regExp.toString())

  return new InspectLeaf(string || '[RegExp]', styles.regexp, depth, opts)
}

function inspectError(error, ref, depth, opts) {
  let header = attempt(() => error.stack).value

  if (typeof header === 'string') {
    // Not all engines put the `<name>: <message>` line at the top of `stack`,
    // so add it when it's missing.
    const title = attempt(() => error.toString()).value

    if (typeof title === 'string' && !header.startsWith(title)) {
      header = title + '\n' + header
    }

    if (depth > 0) {
      header = header.replaceAll('\n', '\n' + '  '.repeat(depth))
    }
  } else {
    header = attempt(() => error.toString()).value

    if (typeof header !== 'string') header = errorLabel(error)
  }

  const builtins = ['cause']

  const { value: name } = attempt(() => error.name)

  if (name === 'AggregateError') {
    builtins.push('errors')
  } else if (name === 'SuppressedError') {
    builtins.push('error', 'suppressed')
  }

  const values = []

  for (const key of builtins) {
    if (descriptorOf(error, key) === undefined) continue

    values.push(
      new InspectPair(
        ': ',
        new InspectLeaf('[' + key + ']', null, depth + 1, opts),
        inspectProperty(error, key, depth + 1, opts),
        depth + 1,
        opts
      )
    )
  }

  for (const key of keysOf(error)) {
    if (key === 'constructor' || builtins.includes(key)) continue

    values.push(
      new InspectPair(
        ': ',
        inspectKey(key, depth + 1, opts),
        inspectProperty(error, key, depth + 1, opts),
        depth + 1,
        opts
      )
    )
  }

  if (values.length === 0) return new InspectLeaf(header, null, depth, opts)

  return new InspectSequence(header + ' {', ' }', ', ', values, ref, depth, opts)
}

function inspectPromise(promise, ref, depth, opts) {
  ref.increment()

  const state = binding.getPromiseState(promise)

  const values = []

  switch (state) {
    case 0: // Pending
      values.push(new InspectLeaf('<pending>', styles.special, depth, opts))
      break

    case 1: // Fulfilled
      values.push(inspectValue(binding.getPromiseResult(promise), depth, opts))
      break

    case 2: // Rejected
      values.push(
        new InspectLeaf('<rejected>', styles.special, depth, opts),
        inspectValue(binding.getPromiseResult(promise), depth, opts)
      )
  }

  ref.decrement()

  const header = (nameOf(promise) || 'Promise') + ' { '

  return new InspectSequence(header, ' }', ' ', values, ref, depth, opts)
}

function inspectMap(map, ref, depth, opts) {
  const { maxArrayLength = defaultMaxArrayLength, maxMapLength = maxArrayLength } = opts

  ref.increment()

  const values = []

  let remaining = maxMapLength

  const { value: size } = attempt(() => map.size)

  attempt(() => {
    for (const entry of map) {
      if (remaining-- === 0) {
        values.push(
          new InspectSuspension(size - values.length, depth + 1, {
            ...opts,
            breakAlways: true
          })
        )
        break
      }

      values.push(
        new InspectPair(
          ' => ',
          inspectValue(entry[0], depth + 1, opts),
          inspectValue(entry[1], depth + 1, opts),
          depth + 1,
          opts
        )
      )
    }
  })

  for (const key of keysOf(map)) {
    if (key === 'constructor') continue

    const value = inspectProperty(map, key, depth + 1, opts)

    values.push(
      new InspectPair(': ', inspectKey(key, depth + 1, opts), value, depth + 1, {
        ...opts,
        breakAlways: remaining < 0
      })
    )
  }

  ref.decrement()

  const header = (nameOf(map) || 'Map') + '(' + size + ') { '

  return new InspectSequence(header, ' }', ', ', values, ref, depth, {
    ...opts,
    tabulate: true
  })
}

function inspectSet(set, ref, depth, opts) {
  const { maxArrayLength = defaultMaxArrayLength, maxSetLength = maxArrayLength } = opts

  ref.increment()

  const values = []

  let remaining = maxSetLength

  const { value: size } = attempt(() => set.size)

  attempt(() => {
    for (const entry of set) {
      if (remaining-- === 0) {
        values.push(
          new InspectSuspension(size - values.length, depth + 1, {
            ...opts,
            breakAlways: true
          })
        )
        break
      }

      values.push(inspectValue(entry, depth + 1, opts))
    }
  })

  for (const key of keysOf(set)) {
    if (key === 'constructor') continue

    const value = inspectProperty(set, key, depth + 1, opts)

    values.push(
      new InspectPair(': ', inspectKey(key, depth + 1, opts), value, depth + 1, {
        ...opts,
        breakAlways: remaining < 0
      })
    )
  }

  ref.decrement()

  const header = (nameOf(set) || 'Set') + '(' + size + ') { '

  return new InspectSequence(header, ' }', ', ', values, ref, depth, {
    ...opts,
    tabulate: true
  })
}

function inspectWeakMap(weakMap, ref, depth, opts) {
  const header = (nameOf(weakMap) || 'WeakMap') + ' { '

  return new InspectSequence(
    header,
    ' }',
    ' ',
    [new InspectLeaf('<items unknown>', styles.special, depth + 1, opts)],
    ref,
    depth,
    opts
  )
}

function inspectWeakSet(weakSet, ref, depth, opts) {
  const header = (nameOf(weakSet) || 'WeakSet') + ' { '

  return new InspectSequence(
    header,
    ' }',
    ' ',
    [new InspectLeaf('<items unknown>', styles.special, depth + 1, opts)],
    ref,
    depth,
    opts
  )
}

function inspectWeakRef(weakRef, ref, depth, opts) {
  const { value: target } = attempt(() => weakRef.deref())

  let value

  if (target === undefined) {
    value = new InspectLeaf('<cleared>', styles.special, depth + 1, opts)
  } else {
    value = inspectValue(target, depth + 1, opts)
  }

  const header = (nameOf(weakRef) || 'WeakRef') + ' { '

  return new InspectSequence(header, ' }', ' ', [value], ref, depth, opts)
}

function inspectArrayBuffer(arrayBuffer, ref, depth, opts) {
  ref.increment()

  const values = []

  for (const key of ['byteLength']) {
    values.push(
      new InspectPair(
        ': ',
        inspectKey(key, depth + 1, opts),
        inspectValue(arrayBuffer[key], depth + 1, opts),
        depth + 1,
        opts
      )
    )
  }

  ref.decrement()

  const header = (nameOf(arrayBuffer) || 'ArrayBuffer') + ' { '

  return new InspectSequence(header, ' }', ', ', values, ref, depth, opts)
}

function inspectSharedArrayBuffer(sharedArrayBuffer, ref, depth, opts) {
  ref.increment()

  const values = []

  for (const key of ['byteLength']) {
    values.push(
      new InspectPair(
        ': ',
        inspectKey(key, depth + 1, opts),
        inspectValue(sharedArrayBuffer[key], depth + 1, opts),
        depth + 1,
        opts
      )
    )
  }

  ref.decrement()

  const header = (nameOf(sharedArrayBuffer) || 'SharedArrayBuffer') + ' { '

  return new InspectSequence(header, ' }', ', ', values, ref, depth, opts)
}

function inspectTypedArray(typedArray, ref, depth, opts) {
  if (Buffer.isBuffer(typedArray)) {
    return inspectBuffer(typedArray, ref, depth, opts)
  }

  const { maxArrayLength = defaultMaxArrayLength, maxTypedArrayLength = maxArrayLength } = opts

  ref.increment()

  const values = []

  let remaining = Math.max(maxTypedArrayLength, 0)

  for (let i = 0, n = typedArray.length; i < n; i++) {
    if (remaining-- === 0) {
      values.push(
        new InspectSuspension(typedArray.length - values.length, depth + 1, {
          ...opts,
          breakAlways: true
        })
      )

      break
    }

    values.push(inspectValue(typedArray[i], depth + 1, opts))
  }

  for (const key of nonIndexKeysOf(typedArray)) {
    values.push(
      new InspectPair(
        ': ',
        inspectKey(key, depth + 1, opts),
        inspectProperty(typedArray, key, depth + 1, opts),
        depth + 1,
        { ...opts, breakAlways: remaining < 0 }
      )
    )
  }

  ref.decrement()

  const header = (nameOf(typedArray) || 'TypedArray') + '(' + typedArray.length + ') [ '

  return new InspectSequence(header, ' ]', ', ', values, ref, depth, {
    ...opts,
    tabulate: true
  })
}

function inspectBuffer(buffer, ref, depth, opts) {
  const { maxArrayLength = defaultMaxArrayLength, maxBufferLength = maxArrayLength } = opts

  ref.increment()

  const values = []

  let remaining = Math.max(maxBufferLength, 0)

  for (let i = 0, n = buffer.byteLength; i < n; i++) {
    if (remaining-- === 0) {
      values.push(
        new InspectSuspension(buffer.length - values.length, depth + 1, {
          ...opts,
          breakAlways: true
        })
      )

      break
    }

    values.push(new InspectLeaf(buffer[i].toString(16).padStart(2, '0'), null, depth + 1, opts))
  }

  for (const key of nonIndexKeysOf(buffer)) {
    values.push(
      new InspectPair(
        ': ',
        inspectKey(key, depth + 1, opts),
        inspectProperty(buffer, key, depth + 1, opts),
        depth + 1,
        { ...opts, breakAlways: remaining < 0 }
      )
    )
  }

  ref.decrement()

  return new InspectSequence('<Buffer ', '>', ' ', values, ref, depth, {
    ...opts,
    tabulate: true
  })
}

function inspectDataView(dataView, ref, depth, opts) {
  ref.increment()

  const values = []

  for (const key of ['byteLength', 'byteOffset', 'buffer']) {
    values.push(
      new InspectPair(
        ': ',
        inspectKey(key, depth + 1, opts),
        inspectValue(dataView[key], depth + 1, opts),
        depth + 1,
        opts
      )
    )
  }

  for (const key of nonIndexKeysOf(dataView)) {
    values.push(
      new InspectPair(
        ': ',
        inspectKey(key, depth + 1, opts),
        inspectProperty(dataView, key, depth + 1, opts),
        depth + 1,
        opts
      )
    )
  }

  ref.decrement()

  const header = (nameOf(dataView) || 'DataView') + ' { '

  return new InspectSequence(header, ' }', ', ', values, ref, depth, opts)
}

function inspectFunction(type, fn, depth, opts) {
  const { value: source } = attempt(() => fn.toString())

  if (typeof source === 'string' && source.startsWith('class')) {
    return inspectClass(fn, depth, opts)
  }

  let tag = 'function'

  if (type.isGeneratorFunction()) tag = 'generator ' + tag
  if (type.isAsyncFunction()) tag = 'async ' + tag

  const { value: name } = attempt(() => fn.name)

  return new InspectLeaf(
    '[' + tag + ' ' + (name ? name : '(anonymous)') + ']',
    styles.special,
    depth,
    opts
  )
}

function inspectClass(ctor, depth, opts) {
  const { value: name } = attempt(() => ctor.name)

  return new InspectLeaf(
    '[class ' + (name ? name : '(anonymous)') + ']',
    styles.special,
    depth,
    opts
  )
}

// Externals are labelled by an opaque, per-process token so that their address
// is never disclosed.
const externalTokens = new WeakMap()

let nextExternalToken = 1

function inspectExternal(external, depth, opts) {
  let token = externalTokens.get(external)

  if (token === undefined) {
    token = nextExternalToken++

    externalTokens.set(external, token)
  }

  return new InspectLeaf('[external #' + token + ']', styles.special, depth, opts)
}

// An inspection must not fail on the value it inspects, so everything read from
// that value is read through one of the helpers below. Values are read for
// their own sake, which is why an accessor is reported rather than called, but
// the labels an inspection puts around them are read as they are written.

function attempt(fn) {
  try {
    return { value: fn(), threw: false, error: null }
  } catch (err) {
    return { value: undefined, threw: true, error: err }
  }
}

// The name a value is labelled with, which is the name of its constructor.
function nameOf(object) {
  const { value: name } = attempt(() => object.constructor.name)

  return typeof name === 'string' ? name : ''
}

// A prototype inherits the custom inspection written for its instances, which
// must not be called on the prototype itself as it is not one of them.
function isPrototype(object) {
  const { value: prototype } = attempt(() => object.constructor.prototype)

  return prototype === object
}

function keysOf(object) {
  const keys = []

  // A partial list is the best that can be had from a value that stops
  // answering halfway through.
  attempt(() => {
    for (const key in object) keys.push(key)
  })

  return keys
}

function symbolsOf(object) {
  const symbols = []

  for (const symbol of attempt(() => Object.getOwnPropertySymbols(object)).value || []) {
    const { value: descriptor, threw } = attempt(() =>
      Object.getOwnPropertyDescriptor(object, symbol)
    )

    if (threw || descriptor === undefined || descriptor.enumerable) symbols.push(symbol)
  }

  return symbols
}

function nonIndexKeysOf(object) {
  return attempt(() => binding.getOwnNonIndexPropertyNames(object)).value || []
}

// Returns the descriptor, `undefined` if the property is absent, or `null` if
// the lookup itself could not be made, as with a proxy that throws.
function descriptorOf(object, key) {
  let target = object

  while (target !== null && target !== undefined) {
    const { value: descriptor, threw } = attempt(() => Object.getOwnPropertyDescriptor(target, key))

    if (threw) return null

    if (descriptor) return descriptor

    const { value: prototype, threw: unreadable } = attempt(() => Object.getPrototypeOf(target))

    if (unreadable) return null

    target = prototype
  }

  return undefined
}

function errorLabel(err) {
  if (err === null || typeof err !== 'object') return 'a non-error'

  const { value: message } = attempt(() => err.message)

  const name = nameOf(err) || 'Error'

  return typeof message === 'string' && message ? name + ': ' + message : name
}
{
  "name": "bare-inspect",
  "version": "3.1.10",
  "description": "Inspect objects as strings for debugging",
  "exports": {
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    },
    "./package": "./package.json"
  },
  "files": [
    "index.js",
    "index.d.ts",
    "binding.c",
    "binding.js",
    "CMakeLists.txt",
    "prebuilds"
  ],
  "addon": true,
  "scripts": {
    "format": "prettier --write . && lunte --fix",
    "lint": "prettier --check . && lunte",
    "test": "brittle-bare --coverage test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-inspect.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-inspect/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-inspect#readme",
  "engines": {
    "bare": ">=1.18.0"
  },
  "dependencies": {
    "bare-ansi-escapes": "^2.1.0",
    "bare-type": "^1.0.0"
  },
  "devDependencies": {
    "brittle": "^3.3.2",
    "cmake-bare": "^1.1.6",
    "lunte": "^1.8.4",
    "prettier": "^3.4.1",
    "prettier-config-holepunch": "^2.0.0"
  }
}
const EventEmitter = require('bare-events')
const { Duplex } = require('bare-stream')
const tcp = require('bare-tcp')
const pipe = require('bare-pipe')
const constants = require('./lib/constants')
const errors = require('./lib/errors')

const defaultReadBufferSize = 65536

exports.Socket = class NetSocket extends Duplex {
  constructor(opts = {}) {
    const {
      readBufferSize = defaultReadBufferSize,
      allowHalfOpen = false,
      eagerOpen = false
    } = opts

    super({ eagerOpen, allowHalfOpen })

    this._type = 0
    this._state = 0
    this._socket = null

    this._opts = { readBufferSize, allowHalfOpen, eagerOpen }

    this._pendingOpen = null
    this._pendingWrite = null
    this._pendingFinal = null
    this._pendingDestroy = null
  }

  get connecting() {
    return this._socket !== null && this._socket.connecting
  }

  get pending() {
    return this._socket === null || this._socket.pending
  }

  get timeout() {
    return this._socket === null ? undefined : this._socket.timeout
  }

  get readyState() {
    return this._socket === null ? 'opening' : this._socket.readyState
  }

  get localAddress() {
    return this._socket === null ? undefined : this._socket.localAddress
  }

  get localPort() {
    return this._socket === null ? undefined : this._socket.localPort
  }

  get localFamily() {
    return this._socket === null ? undefined : this._socket.localFamily
  }

  get remoteAddress() {
    return this._socket === null ? undefined : this._socket.remoteAddress
  }

  get remotePort() {
    return this._socket === null ? undefined : this._socket.remotePort
  }

  get remoteFamily() {
    return this._socket === null ? undefined : this._socket.remoteFamily
  }

  connect(...args) {
    let opts = {}
    let onconnect

    if (typeof args[0] === 'string') {
      // connect(path[, onconnect])
      opts.path = args[0]
      onconnect = args[1]
    } else if (typeof args[0] === 'number') {
      // connect(port[, host][, onconnect])
      opts.port = args[0]

      if (typeof args[1] === 'function') {
        onconnect = args[1]
      } else {
        opts.host = args[1]
        onconnect = args[2]
      }
    } else {
      // connect(opts[, onconnect])
      opts = args[0] || {}
      onconnect = args[1]
    }

    opts = { ...opts, ...this._opts }

    if (opts.path) {
      this._attach(constants.type.IPC, pipe.createConnection(opts))
    } else {
      this._attach(constants.type.TCP, tcp.createConnection(opts))
    }

    if (onconnect) this.once('connect', onconnect)

    return this
  }

  setKeepAlive(...args) {
    if (this._socket !== null) this._socket.setKeepAlive(...args)
    return this
  }

  setNoDelay(...args) {
    if (this._socket !== null) this._socket.setNoDelay(...args)
    return this
  }

  setTimeout(...args) {
    if (this._socket !== null) this._socket.setTimeout(...args)
    return this
  }

  ref() {
    this._state &= ~constants.state.UNREFED
    if (this._socket !== null) this._socket.ref()
    return this
  }

  unref() {
    this._state |= constants.state.UNREFED
    if (this._socket !== null) this._socket.unref()
    return this
  }

  _attach(type, socket) {
    this._type = type
    this._socket = socket

    this._socket
      .on('connect', this._onconnect.bind(this))
      .on('timeout', this._ontimeout.bind(this))
      .on('error', this._onerror.bind(this))
      .on('data', this._ondata.bind(this))
      .on('end', this._onend.bind(this))
      .on('finish', this._onfinish.bind(this))
      .on('drain', this._ondrain.bind(this))
      .on('close', this._onclose.bind(this))

    if (this._state & constants.state.UNREFED) this._socket.unref()

    this._continueOpen()

    return this
  }

  _open(cb) {
    if (this._socket !== null) return cb(null)
    this._pendingOpen = cb
  }

  _write(data, encoding, cb) {
    if (this._socket.write(data)) return cb(null)
    this._pendingWrite = cb
  }

  _final(cb) {
    this._socket.end()
    this._pendingFinal = cb
  }

  _destroy(err, cb) {
    if (this._socket === null || this._socket.destroyed) return cb(null)

    this._socket.destroy(err)
    this._pendingDestroy = cb
  }

  _onconnect() {
    this.emit('connect')
  }

  _ontimeout() {
    this.emit('timeout')
  }

  _onerror(err) {
    this.destroy(err)
  }

  _ondata(data) {
    this.push(data)
  }

  _onend() {
    this.push(null)
  }

  _onfinish() {
    this._continueFinal()
  }

  _ondrain() {
    this._continueWrite()
  }

  _onclose() {
    this._continueWrite()
    this._continueFinal()

    if (this._pendingDestroy) this._continueDestroy()
    else this.destroy()
  }

  _continueOpen() {
    if (this._pendingOpen === null) return
    const cb = this._pendingOpen
    this._pendingOpen = null
    cb(null)
  }

  _continueWrite() {
    if (this._pendingWrite === null) return
    const cb = this._pendingWrite
    this._pendingWrite = null
    cb(null)
  }

  _continueFinal() {
    if (this._pendingFinal === null) return
    const cb = this._pendingFinal
    this._pendingFinal = null
    cb(null)
  }

  _continueDestroy() {
    if (this._pendingDestroy === null) return
    const cb = this._pendingDestroy
    this._pendingDestroy = null
    cb(null)
  }
}

exports.Server = class NetServer extends EventEmitter {
  constructor(opts = {}, onconnection) {
    if (typeof opts === 'function') {
      onconnection = opts
      opts = {}
    }

    super()

    const {
      readBufferSize = defaultReadBufferSize,
      allowHalfOpen = false,
      pauseOnConnect = false
    } = opts

    this._type = 0
    this._state = 0
    this._server = null

    this._opts = { readBufferSize, allowHalfOpen, pauseOnConnect }

    if (onconnection) this.on('connection', onconnection)
  }

  get listening() {
    return (this._state & constants.state.BOUND) !== 0
  }

  address() {
    return this._server === null ? null : this._server.address()
  }

  listen(...args) {
    if (this._state & constants.state.BINDING || this._state & constants.state.BOUND) {
      throw errors.SERVER_ALREADY_LISTENING('Server is already listening')
    }

    let opts = {}
    let onlistening

    if (typeof args[0] === 'string') {
      // listen(path[, backlog][, onlistening])
      opts.path = args[0]

      if (typeof args[1] === 'function') {
        onlistening = args[1]
      } else {
        opts.backlog = args[1]
        onlistening = args[2]
      }
    } else {
      // listen([port[, host[, backlog]]][, onlistening])
      if (typeof args[0] === 'function') {
        onlistening = args[0]
      } else {
        opts.port = args[0]

        if (typeof args[1] === 'function') {
          onlistening = args[1]
        } else {
          opts.host = args[1]

          if (typeof args[2] === 'function') {
            onlistening = args[2]
          } else {
            opts.backlog = args[2]
            onlistening = args[3]
          }
        }
      }
    }

    opts = { ...opts, ...this._opts }

    this._state |= constants.state.BINDING

    if (opts.path) {
      this._attach(constants.type.IPC, pipe.createServer(opts))
    } else {
      this._attach(constants.type.TCP, tcp.createServer(opts))
    }

    this._server.listen(opts)

    if (onlistening) this.once('listening', onlistening)

    return this
  }

  close(onclose) {
    if (onclose) this.once('close', onclose)
    this._server.close()
    return this
  }

  ref() {
    this._state &= ~constants.state.UNREFED
    if (this._server !== null) this._server.ref()
    return this
  }

  unref() {
    this._state |= constants.state.UNREFED
    if (this._server !== null) this._server.unref()
    return this
  }

  _attach(type, server) {
    this._type = type
    this._server = server

    this._server
      .on('listening', this._onlistening.bind(this))
      .on('connection', this._onconnection.bind(this))
      .on('error', this._onerror.bind(this))
      .on('close', this._onclose.bind(this))

    if (this._state & constants.state.UNREFED) this._server.unref()

    return this
  }

  _onlistening() {
    this._state |= constants.state.BOUND
    this._state &= ~constants.state.BINDING

    this.emit('listening')
  }

  _onconnection(socket) {
    this.emit('connection', new exports.Socket(this._opts)._attach(this._type, socket))
  }

  _onerror(err) {
    this._state &= ~constants.state.BINDING

    this.emit('error', err)
  }

  _onclose() {
    this._state &= ~constants.state.BINDING
    this._state &= ~constants.state.BOUND

    this.emit('close')
  }
}

exports.constants = constants
exports.errors = errors

exports.isIP = tcp.isIP
exports.isIPv4 = tcp.isIPv4
exports.isIPv6 = tcp.isIPv6

exports.createConnection = function createConnection(...args) {
  let opts = {}
  let onconnect

  if (typeof args[0] === 'string') {
    // createConnection(path[, onconnect])
    opts.path = args[0]
    onconnect = args[1]
  } else if (typeof args[0] === 'number') {
    // createConnection(port[, host][, onconnect])
    opts.port = args[0]

    if (typeof args[1] === 'function') {
      onconnect = args[1]
    } else {
      opts.host = args[1]
      onconnect = args[2]
    }
  } else {
    // createConnection(opts[, onconnect])
    opts = args[0] || {}
    onconnect = args[1]
  }

  return new exports.Socket(opts).connect(opts, onconnect)
}

// For Node.js compatibility
exports.connect = exports.createConnection

exports.createServer = function createServer(opts, onconnection) {
  return new exports.Server(opts, onconnection)
}
module.exports = {
  type: {
    TCP: 1,
    IPC: 2
  },
  state: {
    UNREFED: 0x1,
    BINDING: 0x2,
    BOUND: 0x4
  }
}
module.exports = class NetError extends Error {
  constructor(msg, fn = NetError, code = fn.name) {
    super(`${code}: ${msg}`)

    this.code = code

    if (Error.captureStackTrace) Error.captureStackTrace(this, fn)
  }

  get name() {
    return 'NetError'
  }

  static SERVER_ALREADY_LISTENING(msg) {
    return new NetError(msg, NetError.SERVER_ALREADY_LISTENING)
  }
}
{
  "name": "bare-net",
  "version": "2.3.3",
  "description": "TCP and IPC servers and clients for JavaScript",
  "exports": {
    "./package": "./package.json",
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    },
    "./constants": {
      "types": "./lib/constants.d.ts",
      "default": "./lib/constants.js"
    },
    "./errors": {
      "types": "./lib/errors.d.ts",
      "default": "./lib/errors.js"
    }
  },
  "files": [
    "index.js",
    "index.d.ts",
    "lib"
  ],
  "scripts": {
    "format": "prettier --write . && lunte --fix",
    "lint": "prettier --check . && lunte",
    "test": "brittle-bare --coverage test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-net.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-net/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-net#readme",
  "dependencies": {
    "bare-events": "^2.2.2",
    "bare-pipe": "^4.0.0",
    "bare-stream": "^2.0.0",
    "bare-tcp": "^2.0.0"
  },
  "devDependencies": {
    "brittle": "^3.3.0",
    "lunte": "^1.8.0",
    "prettier": "^3.4.1",
    "prettier-config-holepunch": "^2.0.0"
  }
}
module.exports = require.addon()
const binding = require('./binding')
const errors = require('./lib/errors')
const constants = require('./lib/constants')

exports.constants = constants
exports.errors = errors

exports.EOL = binding.platform === 'win32' ? '\r\n' : '\n'

exports.devNull = binding.platform === 'win32' ? '\\\\.\\nul' : '/dev/null'

exports.platform = function platform() {
  return binding.platform
}

exports.arch = function arch() {
  return binding.arch
}

exports.type = function type() {
  return binding.type()
}

exports.version = function version() {
  return binding.version()
}

exports.release = function release() {
  return binding.release()
}

exports.machine = function machine() {
  return binding.machine()
}

exports.execPath = function execPath() {
  return binding.execPath()
}

exports.pid = function pid() {
  return binding.pid()
}

exports.ppid = function ppid() {
  return binding.ppid()
}

exports.cwd = function cwd() {
  return binding.cwd()
}

exports.chdir = function chdir(dir) {
  validateString(dir, 'Directory')

  binding.chdir(dir)
}

exports.tmpdir = function tmpdir() {
  return binding.tmpdir()
}

exports.homedir = function homedir() {
  return binding.homedir()
}

exports.hostname = function hostname() {
  return binding.hostname()
}

exports.userInfo = function userInfo(uid) {
  if (uid !== undefined) validateInteger(uid, 'User ID')

  return binding.userInfo(uid)
}

exports.groupInfo = function groupInfo(gid) {
  if (gid !== undefined) validateInteger(gid, 'Group ID')

  return binding.groupInfo(gid)
}

exports.networkInterfaces = function networkInterfaces() {
  const result = {}

  for (const entry of binding.networkInterfaces()) {
    const { name, ...properties } = entry

    if (result[name]) result[name].push(properties)
    else result[name] = [properties]
  }

  return result
}

exports.kill = function kill(pid, signal = constants.signals.SIGTERM) {
  validateInteger(pid, 'Process ID')

  if (typeof signal === 'string') {
    if (signal in constants.signals === false) {
      throw errors.UNKNOWN_SIGNAL('Unknown signal: ' + signal)
    }

    signal = constants.signals[signal]
  } else {
    validateInteger(signal, 'Signal')
  }

  binding.kill(pid, signal)
}

exports.endianness = function endianness() {
  return binding.isLittleEndian ? 'LE' : 'BE'
}

exports.availableParallelism = function availableParallelism() {
  return binding.availableParallelism()
}

exports.cpuUsage = function cpuUsage(previous) {
  const current = binding.cpuUsage()

  if (previous) {
    return {
      user: current.user - previous.user,
      system: current.system - previous.system
    }
  }

  return current
}

exports.threadCpuUsage = function threadCpuUsage(previous) {
  const current = binding.threadCpuUsage()

  if (previous) {
    return {
      user: current.user - previous.user,
      system: current.system - previous.system
    }
  }

  return current
}

exports.resourceUsage = function resourceUsage() {
  return binding.resourceUsage()
}

exports.memoryUsage = function memoryUsage() {
  return binding.memoryUsage()
}

exports.freemem = function freemem() {
  return binding.freemem()
}

exports.totalmem = function totalmem() {
  return binding.totalmem()
}

exports.availableMemory = function availableMemory() {
  return binding.availableMemory()
}

exports.constrainedMemory = function constrainedMemory() {
  return binding.constrainedMemory()
}

exports.uptime = function uptime() {
  return binding.uptime()
}

exports.loadavg = function loadavg() {
  return binding.loadavg()
}

exports.cpus = function cpus() {
  return binding.cpus()
}

exports.getProcessTitle = function getProcessTitle() {
  return binding.getProcessTitle()
}

exports.setProcessTitle = function setProcessTitle(title) {
  if (typeof title !== 'string') title = title.toString()

  if (title.length >= 256) {
    throw errors.TITLE_OVERFLOW('Process title is too long')
  }

  binding.setProcessTitle(title)
}

exports.getPriority = function getPriority(pid = 0) {
  validateInteger(pid, 'Process ID')

  return binding.getPriority(pid)
}

exports.setPriority = function setPriority(pid, priority) {
  if (priority === undefined) {
    priority = pid
    pid = 0
  }

  validateInteger(pid, 'Process ID')
  validateInteger(priority, 'Priority')

  binding.setPriority(pid, priority)
}

exports.getEnvKeys = function getEnvKeys() {
  return binding.getEnvKeys()
}

exports.getEnv = function getEnv(name) {
  validateString(name, 'Name')

  return binding.getEnv(name)
}

exports.hasEnv = function hasEnv(name) {
  validateString(name, 'Name')

  return binding.hasEnv(name)
}

exports.setEnv = function setEnv(name, value) {
  validateString(name, 'Name')
  validateString(value, 'Value')

  binding.setEnv(name, value)
}

exports.unsetEnv = function unsetEnv(name) {
  validateString(name, 'Name')

  binding.unsetEnv(name)
}

function validateString(value, name) {
  if (typeof value !== 'string') {
    throw new TypeError(`${name} must be a string. Received type ${typeof value} (${value})`)
  }
}

function validateInteger(value, name) {
  if (!Number.isInteger(value)) {
    throw new TypeError(`${name} must be an integer. Received type ${typeof value} (${value})`)
  }
}
const binding = require('../binding')

module.exports = {
  signals: binding.signals,
  errnos: binding.errnos,
  priority: binding.priority
}
module.exports = class OSError extends Error {
  constructor(msg, fn = OSError, code = fn.name) {
    super(`${code}: ${msg}`)
    this.code = code

    if (Error.captureStackTrace) {
      Error.captureStackTrace(this, fn)
    }
  }

  get name() {
    return 'OSError'
  }

  static UNKNOWN_SIGNAL(msg) {
    return new OSError(msg, OSError.UNKNOWN_SIGNAL)
  }

  static TITLE_OVERFLOW(msg) {
    return new OSError(msg, OSError.TITLE_OVERFLOW)
  }
}
{
  "name": "bare-os",
  "version": "3.9.3",
  "description": "Operating system utilities for Bare",
  "exports": {
    "./package": "./package.json",
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    },
    "./constants": {
      "types": "./lib/constants.d.ts",
      "default": "./lib/constants.js"
    },
    "./errors": {
      "types": "./lib/errors.d.ts",
      "default": "./lib/errors.js"
    }
  },
  "files": [
    "index.js",
    "index.d.ts",
    "binding.c",
    "binding.js",
    "CMakeLists.txt",
    "lib",
    "prebuilds"
  ],
  "addon": true,
  "scripts": {
    "format": "prettier --write . && lunte --fix",
    "lint": "prettier --check . && lunte",
    "test": "brittle-bare --coverage test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-os.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-os/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-os#readme",
  "engines": {
    "bare": ">=1.14.0"
  },
  "devDependencies": {
    "brittle": "^3.1.1",
    "cmake-bare": "^1.1.6",
    "lunte": "^1.6.0",
    "prettier": "^3.4.2",
    "prettier-config-holepunch": "^2.0.0"
  }
}
module.exports = require.addon()
// This export SHOULD NOT be shortened in any way as having the full
// `module.exports = require(...)` statement is crucial for synthesizing
// ESM exports.

if (Bare.platform === 'win32') {
  module.exports = require('./lib/win32')
} else {
  module.exports = require('./lib/posix')
}
module.exports = {
  CHAR_UPPERCASE_A: 0x41,
  CHAR_LOWERCASE_A: 0x61,
  CHAR_UPPERCASE_Z: 0x5a,
  CHAR_LOWERCASE_Z: 0x7a,
  CHAR_DOT: 0x2e,
  CHAR_FORWARD_SLASH: 0x2f,
  CHAR_BACKWARD_SLASH: 0x5c,
  CHAR_COLON: 0x3a,
  CHAR_QUESTION_MARK: 0x3f
}
const binding = require('../binding')

const { normalizeString, validateString } = require('./shared')
const { CHAR_DOT, CHAR_FORWARD_SLASH } = require('./constants')

function isPosixPathSeparator(code) {
  return code === CHAR_FORWARD_SLASH
}

exports.win32 = require('./win32')
exports.posix = exports

exports.sep = '/'
exports.delimiter = ':'

exports.resolve = function resolve(...args) {
  let resolvedPath = ''
  let resolvedAbsolute = false

  for (let i = args.length - 1; i >= -1 && !resolvedAbsolute; i--) {
    const path = i >= 0 ? args[i] : binding.cwd()

    if (i >= 0) validateString(path, `paths[${i}]`)

    if (path.length === 0) {
      continue
    }

    resolvedPath = `${path}/${resolvedPath}`
    resolvedAbsolute = path.charCodeAt(0) === CHAR_FORWARD_SLASH
  }

  resolvedPath = normalizeString(resolvedPath, !resolvedAbsolute, '/', isPosixPathSeparator)

  if (resolvedAbsolute) {
    return `/${resolvedPath}`
  }

  return resolvedPath.length > 0 ? resolvedPath : '.'
}

exports.normalize = function normalize(path) {
  validateString(path, 'path')

  if (path.length === 0) return '.'

  const isAbsolute = path.charCodeAt(0) === CHAR_FORWARD_SLASH
  const trailingSeparator = path.charCodeAt(path.length - 1) === CHAR_FORWARD_SLASH

  path = normalizeString(path, !isAbsolute, '/', isPosixPathSeparator)

  if (path.length === 0) {
    if (isAbsolute) return '/'
    return trailingSeparator ? './' : '.'
  }

  if (trailingSeparator) path += '/'

  return isAbsolute ? `/${path}` : path
}

exports.isAbsolute = function isAbsolute(path) {
  validateString(path, 'path')

  return path.length > 0 && path.charCodeAt(0) === CHAR_FORWARD_SLASH
}

exports.join = function join(...args) {
  if (args.length === 0) return '.'
  let joined
  for (let i = 0; i < args.length; ++i) {
    const arg = args[i]
    validateString(arg, 'path')
    if (arg.length > 0) {
      if (joined === undefined) joined = arg
      else joined += `/${arg}`
    }
  }
  if (joined === undefined) return '.'
  return exports.normalize(joined)
}

exports.relative = function relative(from, to) {
  validateString(from, 'from')
  validateString(to, 'to')

  if (from === to) return ''

  from = exports.resolve(from)
  to = exports.resolve(to)

  if (from === to) return ''

  const fromStart = 1
  const fromEnd = from.length
  const fromLen = fromEnd - fromStart
  const toStart = 1
  const toLen = to.length - toStart

  const length = fromLen < toLen ? fromLen : toLen
  let lastCommonSep = -1
  let i = 0
  for (; i < length; i++) {
    const fromCode = from.charCodeAt(fromStart + i)
    if (fromCode !== to.charCodeAt(toStart + i)) {
      break
    } else if (fromCode === CHAR_FORWARD_SLASH) {
      lastCommonSep = i
    }
  }
  if (i === length) {
    if (toLen > length) {
      if (to.charCodeAt(toStart + i) === CHAR_FORWARD_SLASH) {
        return to.substring(toStart + i + 1)
      }
      if (i === 0) {
        return to.substring(toStart + i)
      }
    } else if (fromLen > length) {
      if (from.charCodeAt(fromStart + i) === CHAR_FORWARD_SLASH) {
        lastCommonSep = i
      } else if (i === 0) {
        lastCommonSep = 0
      }
    }
  }

  let out = ''
  for (i = fromStart + lastCommonSep + 1; i <= fromEnd; ++i) {
    if (i === fromEnd || from.charCodeAt(i) === CHAR_FORWARD_SLASH) {
      out += out.length === 0 ? '..' : '/..'
    }
  }

  return `${out}${to.substring(toStart + lastCommonSep)}`
}

exports.toNamespacedPath = function toNamespacedPath(path) {
  return path
}

exports.dirname = function dirname(path) {
  validateString(path, 'path')

  if (path.length === 0) return '.'
  const hasRoot = path.charCodeAt(0) === CHAR_FORWARD_SLASH
  let end = -1
  let matchedSlash = true
  for (let i = path.length - 1; i >= 1; --i) {
    if (path.charCodeAt(i) === CHAR_FORWARD_SLASH) {
      if (!matchedSlash) {
        end = i
        break
      }
    } else {
      matchedSlash = false
    }
  }

  if (end === -1) return hasRoot ? '/' : '.'
  if (hasRoot && end === 1) return '//'
  return path.substring(0, end)
}

exports.basename = function basename(path, suffix) {
  if (suffix !== undefined) validateString(suffix, 'suffix')
  validateString(path, 'path')

  let start = 0
  let end = -1
  let matchedSlash = true

  if (suffix !== undefined && suffix.length > 0 && suffix.length <= path.length) {
    if (suffix === path) {
      return ''
    }
    let extIdx = suffix.length - 1
    let firstNonSlashEnd = -1
    for (let i = path.length - 1; i >= 0; --i) {
      const code = path.charCodeAt(i)
      if (code === CHAR_FORWARD_SLASH) {
        if (!matchedSlash) {
          start = i + 1
          break
        }
      } else {
        if (firstNonSlashEnd === -1) {
          matchedSlash = false
          firstNonSlashEnd = i + 1
        }
        if (extIdx >= 0) {
          if (code === suffix.charCodeAt(extIdx)) {
            if (--extIdx === -1) {
              end = i
            }
          } else {
            extIdx = -1
            end = firstNonSlashEnd
          }
        }
      }
    }

    if (start === end) end = firstNonSlashEnd
    else if (end === -1) end = path.length
    return path.substring(start, end)
  }

  for (let i = path.length - 1; i >= 0; --i) {
    if (path.charCodeAt(i) === CHAR_FORWARD_SLASH) {
      if (!matchedSlash) {
        start = i + 1
        break
      }
    } else if (end === -1) {
      matchedSlash = false
      end = i + 1
    }
  }

  if (end === -1) return ''
  return path.substring(start, end)
}

exports.extname = function extname(path) {
  validateString(path, 'path')

  let startDot = -1
  let startPart = 0
  let end = -1
  let matchedSlash = true
  let preDotState = 0
  for (let i = path.length - 1; i >= 0; --i) {
    const code = path.charCodeAt(i)
    if (code === CHAR_FORWARD_SLASH) {
      if (!matchedSlash) {
        startPart = i + 1
        break
      }
      continue
    }
    if (end === -1) {
      matchedSlash = false
      end = i + 1
    }
    if (code === CHAR_DOT) {
      if (startDot === -1) startDot = i
      else if (preDotState !== 1) preDotState = 1
    } else if (startDot !== -1) {
      preDotState = -1
    }
  }

  if (
    startDot === -1 ||
    end === -1 ||
    preDotState === 0 ||
    (preDotState === 1 && startDot === end - 1 && startDot === startPart + 1)
  ) {
    return ''
  }
  return path.substring(startDot, end)
}
const { CHAR_DOT, CHAR_FORWARD_SLASH } = require('./constants')

exports.validateString = function validateString(value, name) {
  if (typeof value !== 'string') {
    throw new TypeError(
      `The "${name}" argument must be of type string. Received type ${typeof value}`
    )
  }
}

exports.normalizeString = function normalizeString(
  path,
  allowAboveRoot,
  separator,
  isPathSeparator
) {
  let res = ''
  let lastSegmentLength = 0
  let lastSlash = -1
  let dots = 0
  let code = 0
  for (let i = 0; i <= path.length; ++i) {
    if (i < path.length) {
      code = path.charCodeAt(i)
    } else if (isPathSeparator(code)) {
      break
    } else {
      code = CHAR_FORWARD_SLASH
    }

    if (isPathSeparator(code)) {
      if (lastSlash === i - 1 || dots === 1);
      else if (dots === 2) {
        if (
          res.length < 2 ||
          lastSegmentLength !== 2 ||
          res.charCodeAt(res.length - 1) !== CHAR_DOT ||
          res.charCodeAt(res.length - 2) !== CHAR_DOT
        ) {
          if (res.length > 2) {
            const lastSlashIndex = res.lastIndexOf(separator)
            if (lastSlashIndex === -1) {
              res = ''
              lastSegmentLength = 0
            } else {
              res = res.substring(0, lastSlashIndex)
              lastSegmentLength = res.length - 1 - res.lastIndexOf(separator)
            }
            lastSlash = i
            dots = 0
            continue
          } else if (res.length !== 0) {
            res = ''
            lastSegmentLength = 0
            lastSlash = i
            dots = 0
            continue
          }
        }
        if (allowAboveRoot) {
          res += res.length > 0 ? `${separator}..` : '..'
          lastSegmentLength = 2
        }
      } else {
        if (res.length > 0) {
          res += `${separator}${path.substring(lastSlash + 1, i)}`
        } else {
          res = path.substring(lastSlash + 1, i)
        }
        lastSegmentLength = i - lastSlash - 1
      }
      lastSlash = i
      dots = 0
    } else if (code === CHAR_DOT && dots !== -1) {
      ++dots
    } else {
      dots = -1
    }
  }
  return res
}
const binding = require('../binding')

const { normalizeString, validateString } = require('./shared')
const {
  CHAR_UPPERCASE_A,
  CHAR_LOWERCASE_A,
  CHAR_UPPERCASE_Z,
  CHAR_LOWERCASE_Z,
  CHAR_DOT,
  CHAR_FORWARD_SLASH,
  CHAR_BACKWARD_SLASH,
  CHAR_COLON,
  CHAR_QUESTION_MARK
} = require('./constants')

function isWindowsPathSeparator(code) {
  return code === CHAR_FORWARD_SLASH || code === CHAR_BACKWARD_SLASH
}

function isWindowsDeviceRoot(code) {
  return (
    (code >= CHAR_UPPERCASE_A && code <= CHAR_UPPERCASE_Z) ||
    (code >= CHAR_LOWERCASE_A && code <= CHAR_LOWERCASE_Z)
  )
}

exports.posix = require('./posix')
exports.win32 = exports

exports.sep = '\\'
exports.delimiter = ';'

exports.resolve = function resolve(...args) {
  let resolvedDevice = ''
  let resolvedTail = ''
  let resolvedAbsolute = false

  for (let i = args.length - 1; i >= -1; i--) {
    let path
    if (i >= 0) {
      path = args[i]

      validateString(path, `paths[${i}]`)

      if (path.length === 0) continue
    } else if (resolvedDevice.length === 0) {
      path = binding.cwd()
    } else {
      path = binding.cwd(resolvedDevice)

      if (
        path === undefined ||
        (path.substring(0, 2).toLowerCase() !== resolvedDevice.toLowerCase() &&
          path.charCodeAt(2) === CHAR_BACKWARD_SLASH)
      ) {
        path = `${resolvedDevice}\\`
      }
    }

    const len = path.length
    let rootEnd = 0
    let device = ''
    let isAbsolute = false
    const code = path.charCodeAt(0)

    if (len === 1) {
      if (isWindowsPathSeparator(code)) {
        rootEnd = 1
        isAbsolute = true
      }
    } else if (isWindowsPathSeparator(code)) {
      isAbsolute = true

      if (isWindowsPathSeparator(path.charCodeAt(1))) {
        let j = 2
        let last = j
        while (j < len && !isWindowsPathSeparator(path.charCodeAt(j))) {
          j++
        }
        if (j < len && j !== last) {
          const firstPart = path.substring(last, j)
          last = j
          while (j < len && isWindowsPathSeparator(path.charCodeAt(j))) {
            j++
          }
          if (j < len && j !== last) {
            last = j
            while (j < len && !isWindowsPathSeparator(path.charCodeAt(j))) {
              j++
            }
            if (j === len || j !== last) {
              device = `\\\\${firstPart}\\${path.substring(last, j)}`
              rootEnd = j
            }
          }
        }
      } else {
        rootEnd = 1
      }
    } else if (isWindowsDeviceRoot(code) && path.charCodeAt(1) === CHAR_COLON) {
      device = path.substring(0, 2)
      rootEnd = 2
      if (len > 2 && isWindowsPathSeparator(path.charCodeAt(2))) {
        isAbsolute = true
        rootEnd = 3
      }
    }

    if (device.length > 0) {
      if (resolvedDevice.length > 0) {
        if (device.toLowerCase() !== resolvedDevice.toLowerCase()) {
          continue
        }
      } else {
        resolvedDevice = device
      }
    }

    if (resolvedAbsolute) {
      if (resolvedDevice.length > 0) {
        break
      }
    } else {
      resolvedTail = `${path.substring(rootEnd)}\\${resolvedTail}`
      resolvedAbsolute = isAbsolute
      if (isAbsolute && resolvedDevice.length > 0) {
        break
      }
    }
  }

  resolvedTail = normalizeString(resolvedTail, !resolvedAbsolute, '\\', isWindowsPathSeparator)

  return resolvedAbsolute
    ? `${resolvedDevice}\\${resolvedTail}`
    : `${resolvedDevice}${resolvedTail}` || '.'
}

exports.normalize = function normalize(path) {
  validateString(path, 'path')

  const len = path.length
  if (len === 0) return '.'
  let rootEnd = 0
  let device
  let isAbsolute = false
  const code = path.charCodeAt(0)

  if (len === 1) {
    return code === CHAR_FORWARD_SLASH ? '\\' : path
  }

  if (isWindowsPathSeparator(code)) {
    isAbsolute = true

    if (isWindowsPathSeparator(path.charCodeAt(1))) {
      let j = 2
      let last = j
      while (j < len && !isWindowsPathSeparator(path.charCodeAt(j))) {
        j++
      }
      if (j < len && j !== last) {
        const firstPart = path.substring(last, j)
        last = j
        while (j < len && isWindowsPathSeparator(path.charCodeAt(j))) {
          j++
        }
        if (j < len && j !== last) {
          last = j
          while (j < len && !isWindowsPathSeparator(path.charCodeAt(j))) {
            j++
          }
          if (j === len) {
            return `\\\\${firstPart}\\${path.substring(last)}\\`
          }
          if (j !== last) {
            device = `\\\\${firstPart}\\${path.substring(last, j)}`
            rootEnd = j
          }
        }
      }
    } else {
      rootEnd = 1
    }
  } else if (isWindowsDeviceRoot(code) && path.charCodeAt(1) === CHAR_COLON) {
    device = path.substring(0, 2)
    rootEnd = 2
    if (len > 2 && isWindowsPathSeparator(path.charCodeAt(2))) {
      isAbsolute = true
      rootEnd = 3
    }
  }

  let tail =
    rootEnd < len
      ? normalizeString(path.substring(rootEnd), !isAbsolute, '\\', isWindowsPathSeparator)
      : ''
  if (tail.length === 0 && !isAbsolute) {
    tail = '.'
  }
  if (tail.length > 0 && isWindowsPathSeparator(path.charCodeAt(len - 1))) {
    tail += '\\'
  }
  if (device === undefined) {
    return isAbsolute ? `\\${tail}` : tail
  }
  return isAbsolute ? `${device}\\${tail}` : `${device}${tail}`
}

exports.isAbsolute = function isAbsolute(path) {
  validateString(path, 'path')

  const len = path.length
  if (len === 0) return false

  const code = path.charCodeAt(0)

  return (
    isWindowsPathSeparator(code) ||
    (len > 2 &&
      isWindowsDeviceRoot(code) &&
      path.charCodeAt(1) === CHAR_COLON &&
      isWindowsPathSeparator(path.charCodeAt(2)))
  )
}

exports.join = function join(...args) {
  if (args.length === 0) return '.'

  let joined
  let firstPart
  for (let i = 0; i < args.length; ++i) {
    const arg = args[i]
    validateString(arg, 'path')
    if (arg.length > 0) {
      if (joined === undefined) joined = firstPart = arg
      else joined += `\\${arg}`
    }
  }

  if (joined === undefined) return '.'

  let needsReplace = true
  let slashCount = 0
  if (isWindowsPathSeparator(firstPart.charCodeAt(0))) {
    ++slashCount
    const firstLen = firstPart.length
    if (firstLen > 1 && isWindowsPathSeparator(firstPart.charCodeAt(1))) {
      ++slashCount
      if (firstLen > 2) {
        if (isWindowsPathSeparator(firstPart.charCodeAt(2))) {
          ++slashCount
        } else {
          needsReplace = false
        }
      }
    }
  }
  if (needsReplace) {
    while (slashCount < joined.length && isWindowsPathSeparator(joined.charCodeAt(slashCount))) {
      slashCount++
    }

    if (slashCount >= 2) {
      joined = `\\${joined.substring(slashCount)}`
    }
  }

  return exports.normalize(joined)
}

exports.relative = function relative(from, to) {
  validateString(from, 'from')
  validateString(to, 'to')

  if (from === to) return ''

  const fromOrig = exports.resolve(from)
  const toOrig = exports.resolve(to)

  if (fromOrig === toOrig) return ''

  from = fromOrig.toLowerCase()
  to = toOrig.toLowerCase()

  if (from === to) return ''

  let fromStart = 0
  while (fromStart < from.length && from.charCodeAt(fromStart) === CHAR_BACKWARD_SLASH) {
    fromStart++
  }
  let fromEnd = from.length
  while (fromEnd - 1 > fromStart && from.charCodeAt(fromEnd - 1) === CHAR_BACKWARD_SLASH) {
    fromEnd--
  }
  const fromLen = fromEnd - fromStart

  let toStart = 0
  while (toStart < to.length && to.charCodeAt(toStart) === CHAR_BACKWARD_SLASH) {
    toStart++
  }
  let toEnd = to.length
  while (toEnd - 1 > toStart && to.charCodeAt(toEnd - 1) === CHAR_BACKWARD_SLASH) {
    toEnd--
  }
  const toLen = toEnd - toStart

  const length = fromLen < toLen ? fromLen : toLen
  let lastCommonSep = -1
  let i = 0
  for (; i < length; i++) {
    const fromCode = from.charCodeAt(fromStart + i)
    if (fromCode !== to.charCodeAt(toStart + i)) {
      break
    } else if (fromCode === CHAR_BACKWARD_SLASH) {
      lastCommonSep = i
    }
  }

  if (i !== length) {
    if (lastCommonSep === -1) return toOrig
  } else {
    if (toLen > length) {
      if (to.charCodeAt(toStart + i) === CHAR_BACKWARD_SLASH) {
        return toOrig.substring(toStart + i + 1)
      }
      if (i === 2) {
        return toOrig.substring(toStart + i)
      }
    }
    if (fromLen > length) {
      if (from.charCodeAt(fromStart + i) === CHAR_BACKWARD_SLASH) {
        lastCommonSep = i
      } else if (i === 2) {
        lastCommonSep = 3
      }
    }
    if (lastCommonSep === -1) lastCommonSep = 0
  }

  let out = ''
  for (i = fromStart + lastCommonSep + 1; i <= fromEnd; ++i) {
    if (i === fromEnd || from.charCodeAt(i) === CHAR_BACKWARD_SLASH) {
      out += out.length === 0 ? '..' : '\\..'
    }
  }

  toStart += lastCommonSep

  if (out.length > 0) {
    return `${out}${toOrig.substring(toStart, toEnd)}`
  }
  if (toOrig.charCodeAt(toStart) === CHAR_BACKWARD_SLASH) {
    ++toStart
  }
  return toOrig.substring(toStart, toEnd)
}

exports.toNamespacedPath = function toNamespacedPath(path) {
  if (typeof path !== 'string' || path.length === 0) return path

  const resolvedPath = exports.resolve(path)

  if (resolvedPath.length <= 2) return path

  if (resolvedPath.charCodeAt(0) === CHAR_BACKWARD_SLASH) {
    if (resolvedPath.charCodeAt(1) === CHAR_BACKWARD_SLASH) {
      const code = resolvedPath.charCodeAt(2)
      if (code !== CHAR_QUESTION_MARK && code !== CHAR_DOT) {
        return `\\\\?\\UNC\\${resolvedPath.substring(2)}`
      }
    }
  } else if (
    isWindowsDeviceRoot(resolvedPath.charCodeAt(0)) &&
    resolvedPath.charCodeAt(1) === CHAR_COLON &&
    resolvedPath.charCodeAt(2) === CHAR_BACKWARD_SLASH
  ) {
    return `\\\\?\\${resolvedPath}`
  }

  return path
}

exports.dirname = function dirname(path) {
  validateString(path, 'path')

  const len = path.length
  if (len === 0) return '.'
  let rootEnd = -1
  let offset = 0
  const code = path.charCodeAt(0)

  if (len === 1) {
    return isWindowsPathSeparator(code) ? path : '.'
  }

  if (isWindowsPathSeparator(code)) {
    rootEnd = offset = 1

    if (isWindowsPathSeparator(path.charCodeAt(1))) {
      let j = 2
      let last = j
      while (j < len && !isWindowsPathSeparator(path.charCodeAt(j))) {
        j++
      }
      if (j < len && j !== last) {
        last = j
        while (j < len && isWindowsPathSeparator(path.charCodeAt(j))) {
          j++
        }
        if (j < len && j !== last) {
          last = j
          while (j < len && !isWindowsPathSeparator(path.charCodeAt(j))) {
            j++
          }
          if (j === len) {
            return path
          }
          if (j !== last) {
            rootEnd = offset = j + 1
          }
        }
      }
    }
  } else if (isWindowsDeviceRoot(code) && path.charCodeAt(1) === CHAR_COLON) {
    rootEnd = len > 2 && isWindowsPathSeparator(path.charCodeAt(2)) ? 3 : 2
    offset = rootEnd
  }

  let end = -1
  let matchedSlash = true
  for (let i = len - 1; i >= offset; --i) {
    if (isWindowsPathSeparator(path.charCodeAt(i))) {
      if (!matchedSlash) {
        end = i
        break
      }
    } else {
      matchedSlash = false
    }
  }

  if (end === -1) {
    if (rootEnd === -1) return '.'

    end = rootEnd
  }
  return path.substring(0, end)
}

exports.basename = function basename(path, suffix) {
  if (suffix !== undefined) validateString(suffix, 'suffix')
  validateString(path, 'path')

  let start = 0
  let end = -1
  let matchedSlash = true

  if (
    path.length >= 2 &&
    isWindowsDeviceRoot(path.charCodeAt(0)) &&
    path.charCodeAt(1) === CHAR_COLON
  ) {
    start = 2
  }

  if (suffix !== undefined && suffix.length > 0 && suffix.length <= path.length) {
    if (suffix === path) return ''
    let extIdx = suffix.length - 1
    let firstNonSlashEnd = -1
    for (let i = path.length - 1; i >= start; --i) {
      const code = path.charCodeAt(i)
      if (isWindowsPathSeparator(code)) {
        if (!matchedSlash) {
          start = i + 1
          break
        }
      } else {
        if (firstNonSlashEnd === -1) {
          matchedSlash = false
          firstNonSlashEnd = i + 1
        }
        if (extIdx >= 0) {
          if (code === suffix.charCodeAt(extIdx)) {
            if (--extIdx === -1) {
              end = i
            }
          } else {
            extIdx = -1
            end = firstNonSlashEnd
          }
        }
      }
    }

    if (start === end) end = firstNonSlashEnd
    else if (end === -1) end = path.length
    return path.substring(start, end)
  }
  for (let i = path.length - 1; i >= start; --i) {
    if (isWindowsPathSeparator(path.charCodeAt(i))) {
      if (!matchedSlash) {
        start = i + 1
        break
      }
    } else if (end === -1) {
      matchedSlash = false
      end = i + 1
    }
  }

  if (end === -1) return ''
  return path.substring(start, end)
}

exports.extname = function extname(path) {
  validateString(path, 'path')

  let start = 0
  let startDot = -1
  let startPart = 0
  let end = -1
  let matchedSlash = true
  let preDotState = 0

  if (
    path.length >= 2 &&
    path.charCodeAt(1) === CHAR_COLON &&
    isWindowsDeviceRoot(path.charCodeAt(0))
  ) {
    start = startPart = 2
  }

  for (let i = path.length - 1; i >= start; --i) {
    const code = path.charCodeAt(i)
    if (isWindowsPathSeparator(code)) {
      if (!matchedSlash) {
        startPart = i + 1
        break
      }
      continue
    }
    if (end === -1) {
      matchedSlash = false
      end = i + 1
    }
    if (code === CHAR_DOT) {
      if (startDot === -1) startDot = i
      else if (preDotState !== 1) preDotState = 1
    } else if (startDot !== -1) {
      preDotState = -1
    }
  }

  if (
    startDot === -1 ||
    end === -1 ||
    preDotState === 0 ||
    (preDotState === 1 && startDot === end - 1 && startDot === startPart + 1)
  ) {
    return ''
  }
  return path.substring(startDot, end)
}
{
  "name": "bare-path",
  "version": "3.1.2",
  "description": "Path manipulation library for JavaScript",
  "exports": {
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    },
    "./package": "./package.json",
    "./posix": "./lib/posix.js",
    "./win32": "./lib/win32.js"
  },
  "files": [
    "index.js",
    "index.d.ts",
    "binding.c",
    "binding.js",
    "CMakeLists.txt",
    "lib",
    "prebuilds",
    "NOTICE"
  ],
  "addon": true,
  "scripts": {
    "format": "prettier --write . && lunte --fix",
    "lint": "prettier --check . && lunte",
    "test": "brittle-bare --coverage test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-path.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-path/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-path#readme",
  "devDependencies": {
    "brittle": "^3.3.2",
    "cmake-bare": "^1.1.6",
    "lunte": "^1.8.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^2.0.0"
  }
}
module.exports = require.addon()
const EventEmitter = require('bare-events')
const { Duplex, isFinished, isReadable, isWritable } = require('bare-stream')
const binding = require('./binding')
const constants = require('./lib/constants')
const errors = require('./lib/errors')

const defaultReadBufferSize = 65536
const empty = Buffer.alloc(0)

const ipcHandle = Symbol.for('bare.ipc.handle')
const ipcAccept = Symbol.for('bare.ipc.accept')

module.exports = exports = class Pipe extends Duplex {
  constructor(path, opts = {}) {
    if (typeof path === 'object' && path !== null) {
      opts = path
      path = null
    }

    const {
      readBufferSize = defaultReadBufferSize,
      allowHalfOpen = true,
      eagerOpen = true,
      ipc = false
    } = opts

    validateInteger(readBufferSize, 'Read buffer size', 1, 0x7fffffff)

    super({ eagerOpen: !!eagerOpen })

    this._state = 0

    this._allowHalfOpen = !!allowHalfOpen
    this._ipc = !!ipc

    this._fd = -1
    this._path = null

    this._error = null

    this._pendingOpen = null
    this._pendingWrite = null
    this._pendingWriteBatch = null
    this._pendingWriteSegments = null
    this._pendingWriteIdx = 0
    this._pendingFinal = null
    this._pendingDestroy = null

    this._handleQueue = []
    this._handleQueueSize = 0

    this._buffer = Buffer.alloc(readBufferSize)

    this._handle = binding.init(
      this._buffer,
      this._ipc,
      this,
      noop,
      this._onconnect,
      this._onread,
      this._onwrite,
      this._onfinal,
      this._onhandle,
      this._onclose
    )

    if (typeof path === 'number') {
      this.open(path)
    } else if (typeof path === 'string') {
      this.connect(path)
    }
  }

  get connecting() {
    return (this._state & constants.state.CONNECTING) !== 0
  }

  get pending() {
    if (this._state & (constants.state.CLOSING | constants.state.CLOSED)) return true

    return (this._state & constants.state.CONNECTED) === 0
  }

  get readyState() {
    if (this._state & constants.state.CONNECTING) return 'opening'

    if (this._state & constants.state.CONNECTED) {
      const readable = (this._state & constants.state.READABLE) !== 0 && isReadable(this)
      const writable =
        (this._state & constants.state.WRITABLE) !== 0 && isWritable(this) && !isFinished(this)

      if (readable && writable) return 'open'
      if (readable) return 'readOnly'
      if (writable) return 'writeOnly'
    }

    return 'closed'
  }

  get [ipcHandle]() {
    return this._handle
  }

  connect(path, opts = {}, onconnect) {
    if ((this._state & constants.state.CLOSING) !== 0 || this._error !== null) {
      throw errors.PIPE_IS_CLOSED('Pipe is closed')
    }

    if (this._state & (constants.state.CONNECTING | constants.state.CONNECTED)) {
      throw errors.PIPE_ALREADY_CONNECTED('Pipe is already connected')
    }

    if (typeof opts === 'function') {
      onconnect = opts
      opts = {}
    }

    if (typeof path === 'object' && path !== null) {
      opts = path
      path = opts.path
    }

    validatePath(path)

    this._state |= constants.state.CONNECTING

    try {
      binding.connect(this._handle, path)

      this._path = path

      if (onconnect) this.once('connect', onconnect)
    } catch (err) {
      queueMicrotask(() => this._failConnect(err))
    }

    return this
  }

  open(fd, opts = {}, onconnect) {
    if ((this._state & constants.state.CLOSING) !== 0 || this._error !== null) {
      throw errors.PIPE_IS_CLOSED('Pipe is closed')
    }

    if (this._state & (constants.state.CONNECTING | constants.state.CONNECTED)) {
      throw errors.PIPE_ALREADY_CONNECTED('Pipe is already connected')
    }

    if (typeof opts === 'function') {
      onconnect = opts
      opts = {}
    }

    if (typeof fd === 'object' && fd !== null) {
      opts = fd
      fd = opts.fd
    }

    validateFd(fd)

    try {
      const status = binding.open(this._handle, fd)

      this._fd = fd

      this._state |= constants.state.CONNECTED

      if (status & binding.READABLE) {
        this._state |= constants.state.READABLE
      } else {
        this.push(null)
      }

      if (status & binding.WRITABLE) {
        this._state |= constants.state.WRITABLE
      } else {
        this.end()
      }

      if (onconnect) this.once('connect', onconnect)
    } catch (err) {
      this._error = err

      queueMicrotask(() => {
        if (this._pendingOpen) this._continueOpen(err)
        else this.destroy(err)
      })

      return this
    }

    this._continueOpen()

    queueMicrotask(() => {
      if (this._state & constants.state.CLOSING) return

      this.emit('connect')
    })

    return this
  }

  write(chunk, encoding, handle, cb) {
    if (typeof encoding === 'function') {
      cb = encoding
      encoding = undefined
      handle = null
    } else if (typeof encoding === 'object' && encoding !== null) {
      if (typeof handle === 'function') cb = handle
      handle = encoding
      encoding = undefined
    } else if (typeof handle === 'function') {
      cb = handle
      handle = null
    }

    if (handle) toIPCHandle(handle)

    if ((this._state & constants.state.CLOSING) === 0) {
      if (handle) this._handleQueueSize++

      this._handleQueue.push(handle || null)
    }

    if (encoding) return super.write(chunk, encoding, cb)

    return super.write(chunk, cb)
  }

  accept(target) {
    const handle = toIPCHandle(target)

    binding.accept(this._handle, handle)

    if (typeof target[ipcAccept] === 'function') target[ipcAccept]()

    return target
  }

  ref() {
    this._state &= ~constants.state.UNREFED

    if (this._state & constants.state.CLOSING) return this

    binding.ref(this._handle)

    return this
  }

  unref() {
    this._state |= constants.state.UNREFED

    if (this._state & constants.state.CLOSING) return this

    binding.unref(this._handle)

    return this
  }

  [ipcAccept]() {
    this._onaccept()
  }

  _open(cb) {
    if (this._state & constants.state.CONNECTED) return cb(null)

    this._pendingOpen = cb
  }

  _read() {
    if ((this._state & constants.state.READING) === 0) {
      this._state |= constants.state.READING

      binding.resume(this._handle)
    }
  }

  _writev(batch, cb) {
    this._pendingWrite = cb
    this._pendingWriteBatch = batch

    try {
      coerceBatch(batch)
    } catch (err) {
      return this._continueWrite(err)
    }

    if (this._handleQueueSize === 0) {
      this._handleQueue = []
      this._pendingWriteSegments = null

      try {
        binding.writev(
          this._handle,
          batch.map(({ chunk }) => chunk),
          null
        )
      } catch (err) {
        this._continueWrite(err)
      }

      return
    }

    const handles = this._handleQueue.splice(0, batch.length)

    // Fill any holes in the batch for messages that don't carry a handle. Each
    // message that does carry a handle decrements the handle queue size.
    for (let i = 0; i < batch.length; i++) {
      if (handles[i] === undefined) handles[i] = null
      else if (handles[i] !== null) this._handleQueueSize--
    }

    // A handle rides along with a single write, so the batch is split into runs
    // of handleless messages and one message per handle.
    const segments = []

    let i = 0
    while (i < batch.length) {
      if (handles[i] !== null) {
        segments.push({ chunks: [batch[i]], handle: handles[i] })
        i++
      } else {
        const start = i
        while (i < batch.length && handles[i] === null) i++
        segments.push({ chunks: batch.slice(start, i), handle: null })
      }
    }

    this._pendingWriteSegments = segments
    this._pendingWriteIdx = 0

    this._writeNextSegment()
  }

  _writeNextSegment() {
    const segment = this._pendingWriteSegments[this._pendingWriteIdx]

    const chunks = []
    for (let i = 0; i < segment.chunks.length; i++) {
      chunks.push(segment.chunks[i].chunk)
    }

    let sendHandle = null

    try {
      // The target was checked when the write was queued, but it may have been
      // invalidated since.
      if (segment.handle !== null) sendHandle = toIPCHandle(segment.handle)

      binding.writev(this._handle, chunks, sendHandle)
    } catch (err) {
      this._continueWrite(err)
    }
  }

  _final(cb) {
    const duplex = constants.state.READABLE | constants.state.WRITABLE

    if ((this._state & duplex) !== duplex) return cb(null)

    this._pendingFinal = cb

    try {
      binding.end(this._handle)
    } catch (err) {
      this._continueFinal(err.code === 'ENOTCONN' ? null : err)
    }
  }

  _predestroy() {
    if (this._state & constants.state.CLOSING) return
    this._state |= constants.state.CLOSING
    this._state &= ~constants.state.CONNECTING

    this._clearHandleQueue()

    binding.close(this._handle)
  }

  _destroy(err, cb) {
    if (this._state & constants.state.CLOSED) return cb(err)

    this._pendingDestroy = cb

    if (this._state & constants.state.CLOSING) return
    this._state |= constants.state.CLOSING
    this._state &= ~constants.state.CONNECTING

    this._clearHandleQueue()

    binding.close(this._handle)
  }

  _clearHandleQueue() {
    this._handleQueue = []
    this._handleQueueSize = 0
  }

  _continueOpen(err) {
    if (this._pendingOpen === null) return
    const cb = this._pendingOpen
    this._pendingOpen = null
    cb(err)
  }

  _continueWrite(err) {
    if (this._pendingWrite === null) return

    // A batch that carries handles is written one segment at a time, so it only
    // settles once the last segment has been written or one of them failed.
    if (this._pendingWriteSegments !== null) {
      this._pendingWriteIdx++

      if (err === null && this._pendingWriteIdx < this._pendingWriteSegments.length) {
        return this._writeNextSegment()
      }
    }

    const cb = this._pendingWrite
    this._pendingWrite = null
    this._pendingWriteBatch = null
    this._pendingWriteSegments = null
    this._pendingWriteIdx = 0
    cb(err)
  }

  _continueFinal(err) {
    if (this._pendingFinal === null) return
    const cb = this._pendingFinal
    this._pendingFinal = null
    cb(err)
  }

  _continueDestroy() {
    if (this._pendingDestroy === null) return
    const cb = this._pendingDestroy
    this._pendingDestroy = null
    cb(null)
  }

  _failConnect(err) {
    if (this._state & constants.state.CLOSING) return

    this._state &= ~constants.state.CONNECTING

    if (this._pendingOpen) this._continueOpen(err)
    else this.destroy(err)
  }

  _onconnect(err) {
    if (err) {
      this._failConnect(err)
      return
    }

    this._state |= constants.state.CONNECTED | constants.state.READABLE | constants.state.WRITABLE
    this._state &= ~constants.state.CONNECTING

    this._continueOpen()

    this.emit('connect')
  }

  _onaccept() {
    this._state |= constants.state.CONNECTED | constants.state.READABLE | constants.state.WRITABLE

    this._continueOpen()
  }

  _onread(err, read) {
    if (err) {
      this.destroy(err)
      return
    }

    if (read === 0) {
      this.push(null)
      if (this._allowHalfOpen === false) this.end()
      return
    }

    // Unpooled, as a read too small to be given a buffer of its own would
    // otherwise pin the whole pool it came from for as long as its consumer
    // holds on to it.
    const copy = Buffer.allocUnsafeSlow(read)
    copy.set(this._buffer.subarray(0, read))

    if (this.push(copy) === false && this.destroying === false) {
      this._state &= ~constants.state.READING

      binding.pause(this._handle)
    }
  }

  _onwrite(err) {
    this._continueWrite(err)
  }

  _onfinal() {
    this._continueFinal(null)
  }

  _onhandle(type) {
    this.emit('handle', type)
  }

  _onclose() {
    this._state |= constants.state.CLOSED

    this._continueOpen()
    this._continueDestroy()
  }

  _onspawn(readable, writable) {
    this._state |= constants.state.CONNECTED

    if (readable) {
      this._state |= constants.state.READABLE
    } else {
      this.push(null)
    }

    if (writable) {
      this._state |= constants.state.WRITABLE
    } else {
      this.end()
    }

    this._continueOpen()
  }
}

exports.Pipe = exports

exports.Server = class PipeServer extends EventEmitter {
  constructor(opts = {}, onconnection) {
    if (typeof opts === 'function') {
      onconnection = opts
      opts = {}
    }

    super()

    const {
      readBufferSize = defaultReadBufferSize,
      allowHalfOpen = true,
      pauseOnConnect = false,
      ipc = false
    } = opts

    validateInteger(readBufferSize, 'Read buffer size', 1, 0x7fffffff)

    this._state = 0

    this._readBufferSize = readBufferSize
    this._allowHalfOpen = !!allowHalfOpen
    this._pauseOnConnect = !!pauseOnConnect
    this._ipc = !!ipc

    this._path = null
    this._connections = new Set()

    this._error = null
    this._handle = null

    if (onconnection) this.on('connection', onconnection)
  }

  get listening() {
    return (this._state & constants.state.BOUND) !== 0
  }

  get closing() {
    return (this._state & constants.state.CLOSING) !== 0
  }

  address() {
    if ((this._state & constants.state.BOUND) === 0) return null

    return this._path
  }

  listen(path, backlog = 511, opts = {}, onlistening) {
    if (this._state & constants.state.CLOSING) {
      throw errors.SERVER_IS_CLOSED('Server is closed')
    }

    if (this._state & (constants.state.BINDING | constants.state.BOUND)) {
      throw errors.SERVER_ALREADY_LISTENING('Server is already listening')
    }

    if (typeof backlog === 'function') {
      onlistening = backlog
      backlog = 511
    } else if (typeof opts === 'function') {
      onlistening = opts
      opts = {}
    }

    if (typeof path === 'object' && path !== null) {
      opts = path
      path = opts.path
      backlog = defaultTo(opts.backlog, 511)
    }

    if (backlog === null || backlog === 0) backlog = 511

    validatePath(path)
    validateInteger(backlog, 'Backlog', 0, 0x7fffffff)

    this._state |= constants.state.BINDING
    this._state &= ~constants.state.CLOSED

    this._handle = binding.init(
      empty,
      this._ipc,
      this,
      this._onconnection,
      noop,
      noop,
      noop,
      noop,
      noop,
      this._onclose
    )

    if (this._state & constants.state.UNREFED) binding.unref(this._handle)

    try {
      binding.bind(this._handle, path, backlog)

      this._path = path

      this._state |= constants.state.BOUND
      this._state &= ~constants.state.BINDING

      if (onlistening) this.once('listening', onlistening)

      queueMicrotask(() => {
        if (this._state & constants.state.CLOSING) return

        this.emit('listening')
      })
    } catch (err) {
      this._error = err

      binding.close(this._handle)
    }

    return this
  }

  close(onclose) {
    if (this._state & constants.state.CLOSED) {
      if (onclose) queueMicrotask(onclose)

      return this
    }

    if (onclose) this.once('close', onclose)

    if (this._state & constants.state.CLOSING) return this
    this._state |= constants.state.CLOSING
    this._state &= ~(constants.state.BINDING | constants.state.BOUND)

    if (this._handle !== null) binding.close(this._handle)
    else this._closeMaybe()

    return this
  }

  ref() {
    this._state &= ~constants.state.UNREFED

    if (this._handle !== null) binding.ref(this._handle)

    return this
  }

  unref() {
    this._state |= constants.state.UNREFED

    if (this._handle !== null) binding.unref(this._handle)

    return this
  }

  _closeMaybe() {
    if ((this._state & constants.state.CLOSING) === 0) return
    if (this._state & constants.state.CLOSED) return
    if (this._handle !== null || this._connections.size > 0) return

    this._state |= constants.state.CLOSED
    this._state &= ~constants.state.CLOSING

    queueMicrotask(() => this.emit('close'))
  }

  _onconnection(err) {
    if (err) {
      this.emit('error', err)
      return
    }

    if (this._state & constants.state.CLOSING) return

    const pipe = new exports.Pipe({
      readBufferSize: this._readBufferSize,
      allowHalfOpen: this._allowHalfOpen,
      eagerOpen: !this._pauseOnConnect,
      ipc: this._ipc
    })

    try {
      binding.accept(this._handle, pipe._handle)

      pipe._path = this._path

      pipe._onaccept()

      this._connections.add(pipe)

      pipe.on('close', () => {
        this._connections.delete(pipe)
        this._closeMaybe()
      })
    } catch (err) {
      pipe.destroy()

      this.emit('error', err)
      return
    }

    this.emit('connection', pipe)
  }

  _onclose() {
    const err = this._error

    this._state &= ~(constants.state.BINDING | constants.state.BOUND)
    this._error = null
    this._handle = null
    this._path = null

    if (err) this.emit('error', err)

    this._closeMaybe()
  }
}

exports.constants = constants
exports.errors = errors

exports.createConnection = function createConnection(path, opts, onconnect) {
  if (typeof opts === 'function') {
    onconnect = opts
    opts = {}
  }

  if (typeof path === 'object' && path !== null) {
    opts = path
    path = opts.path
  }

  return new exports.Pipe(opts).connect(path, opts, onconnect)
}

exports.createServer = function createServer(opts, onconnection) {
  return new exports.Server(opts, onconnection)
}

exports.pipe = function pipe() {
  return binding.pipe()
}

function toIPCHandle(target) {
  const handle = target[ipcHandle]

  if (handle === undefined) {
    throw errors.INVALID_IPC_TARGET('Target does not implement the IPC handle protocol')
  }

  if (handle instanceof ArrayBuffer === false) {
    throw errors.INVALID_IPC_TARGET('Target does not provide a native IPC handle')
  }

  return handle
}

function validatePath(path) {
  if (typeof path !== 'string') {
    throw errors.INVALID_PATH(`Path must be a string, got ${typeof path}`)
  }

  validatePathLength(path)
}

function validatePathLength(path) {
  const length = Buffer.byteLength(path)

  if (length > constants.path.MAX_LENGTH) {
    throw errors.INVALID_PATH(
      `Path must be at most ${constants.path.MAX_LENGTH} bytes, got ${length}`
    )
  }
}

function validateFd(fd) {
  if (typeof fd !== 'number') {
    throw errors.INVALID_FD(`File descriptor must be a number, got ${typeof fd}`)
  }

  if (!Number.isInteger(fd) || fd < 0 || fd > 0x7fffffff) {
    throw errors.INVALID_FD(
      `File descriptor must be an integer between 0 and ${0x7fffffff}, got ${fd}`
    )
  }
}

function validateInteger(value, name, min, max) {
  if (typeof value !== 'number') {
    throw errors.INVALID_ARGUMENT(`${name} must be a number, got ${typeof value}`)
  }

  if (!Number.isInteger(value) || value < min || value > max) {
    throw errors.INVALID_ARGUMENT(
      `${name} must be an integer between ${min} and ${max}, got ${value}`
    )
  }
}

function coerceBatch(batch) {
  for (let i = 0; i < batch.length; i++) {
    const chunk = batch[i].chunk

    if (ArrayBuffer.isView(chunk) === false) {
      throw errors.INVALID_ARGUMENT(`Chunk must be a string or a view, got ${typeof chunk}`)
    }

    batch[i].chunk = Buffer.coerce(chunk)
  }
}

function defaultTo(value, fallback) {
  return value === undefined || value === null ? fallback : value
}

function noop() {}
const binding = require('../binding')

module.exports = {
  state: {
    CONNECTING: 0x1,
    CONNECTED: 0x2,
    BINDING: 0x4,
    BOUND: 0x8,
    READING: 0x10,
    CLOSING: 0x20,
    CLOSED: 0x40,
    UNREFED: 0x80,
    READABLE: 0x100,
    WRITABLE: 0x200
  },
  path: {
    MAX_LENGTH: binding.MAX_PATH_LENGTH
  },
  handle: {
    NAMED_PIPE: binding.UV_NAMED_PIPE,
    TCP: binding.UV_TCP,
    UDP: binding.UV_UDP
  }
}
module.exports = class PipeError extends Error {
  constructor(msg, fn = PipeError, code = fn.name) {
    super(`${code}: ${msg}`)
    this.code = code

    if (Error.captureStackTrace) {
      Error.captureStackTrace(this, fn)
    }
  }

  get name() {
    return 'PipeError'
  }

  static PIPE_ALREADY_CONNECTED(msg) {
    return new PipeError(msg, PipeError.PIPE_ALREADY_CONNECTED)
  }

  static PIPE_IS_CLOSED(msg) {
    return new PipeError(msg, PipeError.PIPE_IS_CLOSED)
  }

  static SERVER_ALREADY_LISTENING(msg) {
    return new PipeError(msg, PipeError.SERVER_ALREADY_LISTENING)
  }

  static SERVER_IS_CLOSED(msg) {
    return new PipeError(msg, PipeError.SERVER_IS_CLOSED)
  }

  static INVALID_ARGUMENT(msg) {
    return new PipeError(msg, PipeError.INVALID_ARGUMENT)
  }

  static INVALID_FD(msg) {
    return new PipeError(msg, PipeError.INVALID_FD)
  }

  static INVALID_IPC_TARGET(msg) {
    return new PipeError(msg, PipeError.INVALID_IPC_TARGET)
  }

  static INVALID_PATH(msg) {
    return new PipeError(msg, PipeError.INVALID_PATH)
  }
}
{
  "name": "bare-pipe",
  "version": "4.3.1",
  "description": "Native I/O pipes for JavaScript",
  "exports": {
    "./package": "./package.json",
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    },
    "./constants": {
      "types": "./lib/constants.d.ts",
      "default": "./lib/constants.js"
    },
    "./errors": {
      "types": "./lib/errors.d.ts",
      "default": "./lib/errors.js"
    }
  },
  "files": [
    "index.js",
    "index.d.ts",
    "binding.c",
    "binding.js",
    "CMakeLists.txt",
    "lib",
    "prebuilds"
  ],
  "addon": true,
  "scripts": {
    "format": "prettier --write . && lunte --fix",
    "lint": "prettier --check . && lunte",
    "test": "brittle-bare --coverage test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-pipe.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-pipe/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-pipe#readme",
  "engines": {
    "bare": ">=1.16.0"
  },
  "dependencies": {
    "bare-events": "^2.5.4",
    "bare-stream": "^2.6.4"
  },
  "devDependencies": {
    "bare-buffer": "^3.0.0",
    "bare-fs": "^4.0.0",
    "bare-path": "^3.0.0",
    "bare-tcp": "^2.5.4",
    "brittle": "^3.19.1",
    "cmake-bare": "^1.1.6",
    "lunte": "^1.8.0",
    "prettier": "^3.4.1",
    "prettier-config-holepunch": "^2.0.0"
  },
  "peerDependencies": {
    "bare-buffer": "*"
  },
  "peerDependenciesMeta": {
    "bare-buffer": {
      "optional": true
    }
  }
}
{
  "name": "bare-posix",
  "version": "1.0.1",
  "description": "POSIX-specific bindings for Bare",
  "exports": {
    ".": {
      "types": "./index.d.ts",
      "android": "./unsupported.js",
      "win32": "./unsupported.js",
      "default": "./index.js"
    },
    "./package": "./package.json"
  },
  "files": [
    "index.js",
    "index.d.ts",
    "unsupported.js",
    "binding.c",
    "binding.js",
    "CMakeLists.txt",
    "prebuilds"
  ],
  "addon": true,
  "scripts": {
    "format": "prettier --write .",
    "lint": "prettier --check . && lunte",
    "test": "brittle-bare --coverage test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-posix.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-posix/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-posix#readme",
  "devDependencies": {
    "brittle": "^3.19.1",
    "cmake-bare": "^1.7.7",
    "lunte": "^1.6.0",
    "prettier": "^3.8.2",
    "prettier-config-holepunch": "^2.0.0"
  }
}
exports.getgid = function getgid() {
  return -1
}

exports.setgid = function setgid() {
  throw new Error('Platform not supported')
}

exports.getegid = function getegid() {
  return -1
}

exports.setegid = function setgid() {
  throw new Error('Platform not supported')
}

exports.getuid = function getuid() {
  return -1
}

exports.setuid = function setgid() {
  throw new Error('Platform not supported')
}

exports.geteuid = function geteuid() {
  return -1
}

exports.seteuid = function setgid() {
  throw new Error('Platform not supported')
}

exports.getgroups = function getgroups() {
  return []
}

exports.getgrnam = function getgrnam() {
  return null
}

exports.getpwnam = function getpwnam() {
  return null
}
const abort = require('bare-abort')
const EventEmitter = require('bare-events')
const Signal = require('bare-signals')
const os = require('bare-os')
const env = require('bare-env')
const hrtime = require('bare-hrtime')
const stdio = require('bare-stdio')
const posix = require('bare-posix')

class Process extends EventEmitter {
  constructor() {
    super()

    this._startTime = hrtime.bigint()

    EventEmitter.forward(Bare, this, [
      'uncaughtException',
      'unhandledRejection',
      'beforeExit',
      'exit',
      'suspend',
      'wakeup',
      'idle',
      'resume'
    ])

    const signals = new Signal.Emitter()

    signals.unref()

    EventEmitter.forward(signals, this, Object.keys(os.constants.signals))
  }

  get stdin() {
    return stdio.in
  }

  get stdout() {
    return stdio.out
  }

  get stderr() {
    return stdio.err
  }

  get platform() {
    return os.platform()
  }

  get arch() {
    return os.arch()
  }

  get title() {
    return os.getProcessTitle()
  }

  set title(title) {
    os.setProcessTitle(title)
  }

  get pid() {
    return os.pid()
  }

  get ppid() {
    return os.ppid()
  }

  get argv() {
    return Bare.argv
  }

  get execArgv() {
    return []
  }

  get execPath() {
    return os.execPath()
  }

  get exitCode() {
    return Bare.exitCode
  }

  set exitCode(code) {
    Bare.exitCode = code
  }

  get version() {
    return Bare.version
  }

  get versions() {
    return Bare.versions
  }

  get env() {
    return env
  }

  get hrtime() {
    return hrtime
  }

  abort() {
    abort()
  }

  exit(code) {
    Bare.exit(code)
  }

  suspend() {
    Bare.suspend()
  }

  resume() {
    Bare.resume()
  }

  cwd() {
    return os.cwd()
  }

  chdir(directory) {
    os.chdir(directory)
  }

  kill(pid, signal) {
    os.kill(pid, signal)
  }

  uptime() {
    return Number(hrtime.bigint() - this._startTime) / 1e9
  }

  cpuUsage(previous) {
    return os.cpuUsage(previous)
  }

  threadCpuUsage(previous) {
    return os.threadCpuUsage(previous)
  }

  resourceUsage() {
    return os.resourceUsage()
  }

  availableMemory() {
    return os.availableMemory()
  }

  constrainedMemory() {
    return os.constrainedMemory()
  }

  memoryUsage() {
    return os.memoryUsage()
  }

  getgid() {
    return posix.getgid()
  }

  setgid(id) {
    return posix.setgid(id)
  }

  getegid() {
    return posix.getegid()
  }

  setegid(id) {
    return posix.setegid(id)
  }

  getuid() {
    return posix.getuid()
  }

  setuid(id) {
    return posix.setuid(id)
  }

  geteuid() {
    return posix.geteuid()
  }

  seteuid(id) {
    return posix.seteuid(id)
  }

  getgroups() {
    return posix.getgroups()
  }

  nextTick(cb, ...args) {
    queueMicrotask(cb.bind(null, ...args))
  }

  [Symbol.for('bare.inspect')]() {
    return {
      __proto__: { constructor: Process },

      platform: this.platform,
      arch: this.arch,
      title: this.title,
      pid: this.pid,
      ppid: this.ppid,
      argv: this.argv,
      execPath: this.execPath,
      exitCode: this.exitCode,
      version: this.version,
      versions: this.versions,
      env: this.env
    }
  }
}

module.exports = new Process()
{
  "name": "bare-process",
  "version": "4.5.1",
  "description": "Node.js-compatible process control for Bare",
  "exports": {
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    },
    "./global": {
      "types": "./global.d.ts",
      "default": "./global.js"
    },
    "./package": "./package.json"
  },
  "files": [
    "index.js",
    "index.d.ts",
    "global.js",
    "global.d.ts"
  ],
  "scripts": {
    "format": "prettier --write . && lunte --fix",
    "lint": "prettier --check . && lunte",
    "test": "brittle-bare --coverage test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-process.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-process/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-process#readme",
  "dependencies": {
    "bare-abort": "^2.0.13",
    "bare-env": "^3.0.0",
    "bare-events": "^2.3.1",
    "bare-hrtime": "^2.0.0",
    "bare-os": "^3.7.1",
    "bare-posix": "^1.0.1",
    "bare-signals": "^5.0.0",
    "bare-stdio": "^1.0.1"
  },
  "devDependencies": {
    "brittle": "^3.2.1",
    "lunte": "^1.6.0",
    "prettier": "^3.4.1",
    "prettier-config-holepunch": "^2.0.0"
  }
}
module.exports = require.addon()
const EventEmitter = require('bare-events')
const binding = require('./binding')
const errors = require('./lib/errors')

const signals = binding.signals

module.exports = exports = class Signal extends EventEmitter {
  constructor(signum) {
    super()

    if (typeof signum === 'string') {
      if (signum in signals === false) {
        throw errors.UNKNOWN_SIGNAL(`Unknown signal '${signum}'`)
      }

      signum = signals[signum]
    }

    this._signum = signum
    this._closing = null
    this._handle = binding.init(this, this._onsignal, this._onclose)
  }

  start() {
    if (this._closing) throw errors.SIGNAL_CLOSED('Signal is closed')
    binding.start(this._handle, this._signum)
    return this
  }

  stop() {
    if (this._closing) return this
    binding.stop(this._handle)
    return this
  }

  ref() {
    if (this._closing) return this
    binding.ref(this._handle)
    return this
  }

  unref() {
    if (this._closing) return this
    binding.unref(this._handle)
    return this
  }

  close() {
    if (this._closing) return this._closing
    this._closing = EventEmitter.once(this, 'close')

    binding.close(this._handle)

    return this._closing
  }

  _onsignal() {
    this.emit('signal', this._signum)
  }

  _onclose() {
    this._handle = null

    this.emit('close')
  }
}

exports.Emitter = require('./lib/emitter')

exports.constants = signals
exports.errors = errors
const EventEmitter = require('bare-events')
const Signal = require('..')

module.exports = class SignalEmitter extends EventEmitter {
  constructor() {
    super()

    this._signals = new Map()
    this._unrefed = false

    this.on('newListener', this._onnewlistener).on('removeListener', this._onremovelistener)
  }

  ref() {
    this._unrefed = false
    for (const signal of this._signals.values()) signal.ref()
    return this
  }

  unref() {
    this._unrefed = true
    for (const signal of this._signals.values()) signal.unref()
    return this
  }

  _onnewlistener(name) {
    if (name === 'newListener' || name === 'removeListener') return

    if (this.listenerCount(name) === 0) {
      const signal = new Signal(name)

      signal.on('signal', this._onsignal.bind(this, name)).start()

      if (this._unrefed) signal.unref()

      this._signals.set(name, signal)
    }
  }

  _onremovelistener(name) {
    if (name === 'newListener' || name === 'removeListener') return

    if (this.listenerCount(name) === 0) {
      const signal = this._signals.get(name)

      if (this._unrefed) signal.ref()

      signal.close()

      this._signals.delete(name)
    }
  }

  _onsignal(name) {
    this.emit(name, name, Signal.constants[name])
  }
}
module.exports = class SignalError extends Error {
  constructor(msg, fn = SignalError, code = fn.name) {
    super(`${code}: ${msg}`)
    this.code = code

    if (Error.captureStackTrace) {
      Error.captureStackTrace(this, fn)
    }
  }

  get name() {
    return 'SignalError'
  }

  static UNKNOWN_SIGNAL(msg) {
    return new SignalError(msg, SignalError.UNKNOWN_SIGNAL)
  }

  static SIGNAL_CLOSED(msg) {
    return new SignalError(msg, SignalError.SIGNAL_CLOSED)
  }
}
{
  "name": "bare-signals",
  "version": "5.0.0",
  "description": "Native signal handling for JavaScript",
  "exports": {
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    },
    "./package": "./package.json",
    "./errors": "./lib/errors.js",
    "./emitter": "./lib/emitter.js"
  },
  "files": [
    "index.js",
    "index.d.ts",
    "binding.c",
    "binding.js",
    "CMakeLists.txt",
    "lib",
    "prebuilds"
  ],
  "addon": true,
  "scripts": {
    "format": "prettier --write . && lunte --fix",
    "lint": "prettier --check . && lunte",
    "test": "brittle-bare --coverage test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-signals.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-signals/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-signals#readme",
  "engines": {
    "bare": ">=1.7.0"
  },
  "dependencies": {
    "bare-events": "^2.5.3"
  },
  "devDependencies": {
    "bare-os": "^3.3.1",
    "brittle": "^3.2.1",
    "cmake-bare": "^1.1.6",
    "lunte": "^1.8.3",
    "prettier": "^3.4.1",
    "prettier-config-holepunch": "^2.0.0"
  }
}
module.exports = require.addon()
const fs = require('bare-fs')
const tty = require('bare-tty')
const Pipe = require('bare-pipe')
const binding = require('./binding')

const { TTY, NAMED_PIPE } = binding

class IO {
  constructor() {
    this._in = null
    this._out = null
    this._err = null
  }

  get in() {
    if (this._in === null) {
      switch (binding.guessType(0)) {
        case TTY:
          this._in = new tty.ReadStream(0)
          break
        case NAMED_PIPE:
          this._in = new Pipe(0, { eagerOpen: false })
          break
        default:
          this._in = fs.createReadStream(null, { fd: 0, eagerOpen: false })
      }
    }

    return this._in
  }

  get out() {
    if (this._out === null) {
      switch (binding.guessType(1)) {
        case TTY:
          this._out = new tty.WriteStream(1)
          break
        case NAMED_PIPE:
          this._out = new Pipe(1, { eagerOpen: false })
          this._out.unref()
          break
        default:
          this._out = fs.createWriteStream(null, { fd: 1, eagerOpen: false })
      }
    }

    return this._out
  }

  get err() {
    if (this._err === null) {
      switch (binding.guessType(2)) {
        case TTY:
          this._err = new tty.WriteStream(2)
          break
        case NAMED_PIPE:
          this._err = new Pipe(2, { eagerOpen: false })
          this._err.unref()
          break
        default:
          this._err = fs.createWriteStream(null, { fd: 2, eagerOpen: false })
      }
    }

    return this._err
  }
}

module.exports = new IO()
{
  "name": "bare-stdio",
  "version": "1.0.3",
  "description": "Standard input/output streams for Bare",
  "exports": {
    "./package": "./package.json",
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    }
  },
  "files": [
    "index.js",
    "index.d.ts",
    "binding.c",
    "binding.js",
    "CMakeLists.txt",
    "prebuilds"
  ],
  "addon": true,
  "scripts": {
    "format": "prettier --write . && lunte --fix",
    "lint": "prettier --check . && lunte",
    "test": "brittle-bare --coverage test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-stdio.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-stdio/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-stdio#readme",
  "dependencies": {
    "bare-fs": "^4.5.2",
    "bare-pipe": "^4.1.5",
    "bare-tty": "^5.0.3"
  },
  "devDependencies": {
    "bare-os": "^3.6.2",
    "bare-path": "^3.0.0",
    "bare-subprocess": "^6.0.0",
    "brittle": "^3.19.1",
    "cmake-bare": "^1.7.7",
    "lunte": "^1.4.0",
    "prettier": "^3.8.1",
    "prettier-config-holepunch": "^2.0.0",
    "tt-native": "^1.1.1"
  }
}
const b4a = require('b4a')
const stream = require('streamx')
const { ReadableStream, WritableStream } = require('./web')

const defaultEncoding = 'utf8'

module.exports = exports = stream.Stream

exports.pipeline = stream.pipeline

exports.isStream = stream.isStream
exports.isEnding = stream.isEnding
exports.isEnded = stream.isEnded
exports.isFinishing = stream.isFinishing
exports.isFinished = stream.isFinished
exports.isDisturbed = stream.isDisturbed

exports.isErrored = function isErrored(stream) {
  return exports.getStreamError(stream) !== null
}

exports.isReadable = function isReadable(stream) {
  return stream.readable && !stream.destroying && !exports.isEnded(stream)
}

exports.isWritable = function isWritable(stream) {
  return stream.writable && !stream.destroying && !exports.isFinishing(stream)
}

exports.getStreamError = stream.getStreamError

exports.addAbortSignal = function addAbortSignal(signal, stream) {
  function onAbort() {
    stream.destroy(signal.reason)
  }

  if (signal.aborted) onAbort()
  else signal.addEventListener('abort', onAbort)

  return stream
}

exports.Stream = exports

exports.Readable = class Readable extends stream.Readable {
  constructor(opts = {}) {
    super({
      ...opts,
      byteLength: null,
      byteLengthReadable: null,
      map: null,
      mapReadable: null
    })

    if (this._construct) this._open = this._construct

    if (this._read !== stream.Readable.prototype._read) {
      this._read = read.bind(this, this._read)
    }

    if (this._destroy !== stream.Stream.prototype._destroy) {
      this._destroy = destroy.bind(this, this._destroy)
    }
  }

  get closed() {
    return !exports.isReadable(this)
  }

  get errored() {
    return stream.getStreamError(this)
  }

  push(chunk, encoding) {
    if (typeof chunk === 'string') {
      chunk = b4a.from(chunk, encoding || defaultEncoding)
    }

    return super.push(chunk)
  }

  unshift(chunk, encoding) {
    if (typeof chunk === 'string') {
      chunk = b4a.from(chunk, encoding || defaultEncoding)
    }

    super.unshift(chunk)
  }

  static fromWeb(readableStream, opts = {}) {
    const stream = readableStream._stream

    if (opts.encoding) stream.setEncoding(opts.encoding)
    if (opts.signal) exports.addAbortSignal(opts.signal, stream)

    return stream
  }

  static toWeb(readable, opts = {}) {
    return new ReadableStream(readable, opts.strategy)
  }

  async [Symbol.asyncDispose]() {
    if (!this.destroyed) this.destroy()

    await new Promise((resolve) => exports.finished(this, resolve))
  }
}

exports.Writable = class Writable extends stream.Writable {
  constructor(opts = {}) {
    super({
      ...opts,
      byteLength: null,
      byteLengthWritable,
      map: null,
      mapWritable: null
    })

    if (this._construct) this._open = this._construct

    if (this._write !== stream.Writable.prototype._write) {
      this._write = write.bind(this, this._write)
    }

    if (this._destroy !== stream.Stream.prototype._destroy) {
      this._destroy = destroy.bind(this, this._destroy)
    }
  }

  get closed() {
    return !exports.isWritable(this)
  }

  get errored() {
    return stream.getStreamError(this)
  }

  write(chunk, encoding, cb) {
    if (typeof encoding === 'function') {
      cb = encoding
      encoding = null
    }

    if (typeof chunk === 'string') {
      encoding = encoding || defaultEncoding
      chunk = b4a.from(chunk, encoding)
    } else {
      encoding = 'buffer'
    }

    const result = super.write({ chunk, encoding })

    if (cb) stream.Writable.drained(this).then(() => cb(null), cb)

    return result
  }

  end(chunk, encoding, cb) {
    if (typeof chunk === 'function') {
      cb = chunk
      chunk = null
    } else if (typeof encoding === 'function') {
      cb = encoding
      encoding = null
    }

    if (typeof chunk === 'string') {
      encoding = encoding || defaultEncoding
      chunk = b4a.from(chunk, encoding || defaultEncoding)
    } else {
      encoding = 'buffer'
    }

    const result =
      chunk !== undefined && chunk !== null ? super.end({ chunk, encoding }) : super.end()

    if (cb) this.once('finish', () => cb(null))

    return result
  }

  static fromWeb(writableStream, opts = {}) {
    const stream = writableStream._stream

    if (opts.signal) exports.addAbortSignal(opts.signal, stream)

    return stream
  }

  static toWeb(writable) {
    return new WritableStream(writable)
  }

  async [Symbol.asyncDispose]() {
    if (!this.destroyed) this.destroy()

    await new Promise((resolve) => exports.finished(this, resolve))
  }
}

exports.Duplex = class Duplex extends stream.Duplex {
  constructor(opts = {}) {
    super({
      ...opts,
      byteLength: null,
      byteLengthReadable: null,
      byteLengthWritable,
      map: null,
      mapReadable: null,
      mapWritable: null
    })

    if (this._construct) this._open = this._construct

    if (this._read !== stream.Readable.prototype._read) {
      this._read = read.bind(this, this._read)
    }

    if (this._write !== stream.Duplex.prototype._write) {
      this._write = write.bind(this, this._write)
    }

    if (this._destroy !== stream.Stream.prototype._destroy) {
      this._destroy = destroy.bind(this, this._destroy)
    }
  }

  push(chunk, encoding) {
    if (typeof chunk === 'string') {
      chunk = b4a.from(chunk, encoding || defaultEncoding)
    }

    return super.push(chunk)
  }

  unshift(chunk, encoding) {
    if (typeof chunk === 'string') {
      chunk = b4a.from(chunk, encoding || defaultEncoding)
    }

    super.unshift(chunk)
  }

  write(chunk, encoding, cb) {
    if (typeof encoding === 'function') {
      cb = encoding
      encoding = null
    }

    if (typeof chunk === 'string') {
      encoding = encoding || defaultEncoding
      chunk = b4a.from(chunk, encoding)
    } else {
      encoding = 'buffer'
    }

    const result = super.write({ chunk, encoding })

    if (cb) stream.Writable.drained(this).then(() => cb(null), cb)

    return result
  }

  end(chunk, encoding, cb) {
    if (typeof chunk === 'function') {
      cb = chunk
      chunk = null
    } else if (typeof encoding === 'function') {
      cb = encoding
      encoding = null
    }

    if (typeof chunk === 'string') {
      encoding = encoding || defaultEncoding
      chunk = b4a.from(chunk, encoding)
    } else {
      encoding = 'buffer'
    }

    const result =
      chunk !== undefined && chunk !== null ? super.end({ chunk, encoding }) : super.end()

    if (cb) this.once('finish', () => cb(null))

    return result
  }

  static fromWeb({ readable: readableStream, writable: writableStream }, opts) {
    const readable = exports.Readable.fromWeb(readableStream, opts)
    const writable = exports.Writable.fromWeb(writableStream, opts)

    const duplex = new exports.Duplex({
      write(data, encoding, cb) {
        writable.write(data, encoding, cb)
      }
    })

    readable
      .on('data', (data) => duplex.push(data))
      .on('end', () => duplex.push(null))
      .on('error', (err) => duplex.destroy(err))

    writable.on('finish', () => duplex.end()).on('error', (err) => duplex.destroy(err))

    return duplex
  }

  static toWeb(duplex) {
    const readableStream = exports.Readable.toWeb(duplex)
    const writableStream = exports.Writable.toWeb(duplex)

    return { readable: readableStream, writable: writableStream }
  }
}

class DuplexSide extends exports.Duplex {
  constructor(opts) {
    super(opts)

    this._otherSide = null
    this._cb = null
  }

  _read() {
    const cb = this._cb
    if (!cb) return

    this._cb = null
    cb()
  }

  _write(chunk, encoding, cb) {
    this._otherSide.push(chunk, encoding)
    this._otherSide._cb = cb
  }

  _final(cb) {
    this._otherSide.on('end', cb)
    this._otherSide.push(null)
  }
}

exports.duplexPair = function duplexPair(opts) {
  const sideA = new DuplexSide(opts)
  const sideB = new DuplexSide(opts)

  sideA._otherSide = sideB
  sideB._otherSide = sideA

  return [sideA, sideB]
}

exports.Transform = class Transform extends stream.Transform {
  constructor(opts = {}) {
    super({
      ...opts,
      byteLength: null,
      byteLengthReadable: null,
      byteLengthWritable,
      map: null,
      mapReadable: null,
      mapWritable: null
    })

    if (this._transform !== stream.Transform.prototype._transform) {
      this._transform = transform.bind(this, this._transform)
    } else {
      this._transform = passthrough
    }
  }

  push(chunk, encoding) {
    if (typeof chunk === 'string') {
      chunk = b4a.from(chunk, encoding || defaultEncoding)
    }

    return super.push(chunk)
  }

  unshift(chunk, encoding) {
    if (typeof chunk === 'string') {
      chunk = b4a.from(chunk, encoding || defaultEncoding)
    }

    super.unshift(chunk)
  }

  write(chunk, encoding, cb) {
    if (typeof encoding === 'function') {
      cb = encoding
      encoding = null
    }

    if (typeof chunk === 'string') {
      encoding = encoding || defaultEncoding
      chunk = b4a.from(chunk, encoding)
    } else {
      encoding = 'buffer'
    }

    const result = super.write({ chunk, encoding })

    if (cb) stream.Writable.drained(this).then(() => cb(null), cb)

    return result
  }

  end(chunk, encoding, cb) {
    if (typeof chunk === 'function') {
      cb = chunk
      chunk = null
    } else if (typeof encoding === 'function') {
      cb = encoding
      encoding = null
    }

    if (typeof chunk === 'string') {
      encoding = encoding || defaultEncoding
      chunk = b4a.from(chunk, encoding)
    } else {
      encoding = 'buffer'
    }

    const result =
      chunk !== undefined && chunk !== null ? super.end({ chunk, encoding }) : super.end()

    if (cb) this.once('finish', () => cb(null))

    return result
  }
}

exports.PassThrough = class PassThrough extends exports.Transform {}

exports.finished = function finished(stream, opts, cb) {
  if (typeof opts === 'function') {
    cb = opts
    opts = {}
  }

  if (!opts) opts = {}

  const { cleanup = false } = opts

  const done = () => {
    cb(exports.getStreamError(stream, { all: true }))

    if (cleanup) detach()
  }

  const detach = () => {
    stream.off('close', done)
    stream.off('error', noop)
  }

  if (stream.destroyed) {
    done()
  } else {
    stream.on('close', done)
    stream.on('error', noop)
  }

  return detach
}

function read(read, cb) {
  read.call(this, 65536)

  cb(null)
}

function write(write, data, cb) {
  write.call(this, data.chunk, data.encoding, cb)
}

function transform(transform, data, cb) {
  transform.call(this, data.chunk, data.encoding, cb)
}

function destroy(destroy, cb) {
  destroy.call(this, exports.getStreamError(this), cb)
}

function passthrough(data, cb) {
  cb(null, data.chunk)
}

function byteLengthWritable(data) {
  return data.chunk.byteLength
}

function noop() {}
{
  "name": "bare-stream",
  "version": "2.13.4",
  "description": "Streaming data for JavaScript",
  "exports": {
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    },
    "./package": "./package.json",
    "./promises": "./promises.js",
    "./web": {
      "types": "./web.d.ts",
      "default": "./web.js"
    },
    "./global": {
      "types": "./global.d.ts",
      "default": "./global.js"
    }
  },
  "files": [
    "index.js",
    "index.d.ts",
    "promises.js",
    "web.js",
    "web.d.ts",
    "global.js",
    "global.d.ts"
  ],
  "scripts": {
    "format": "prettier --write . && lunte --fix",
    "lint": "prettier --check . && lunte",
    "test": "brittle-bare --coverage test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-stream.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-stream/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-stream#readme",
  "dependencies": {
    "b4a": "^1.8.1",
    "streamx": "^2.25.0",
    "teex": "^1.0.1"
  },
  "devDependencies": {
    "bare-abort-controller": "^1.1.0",
    "bare-buffer": "^3.0.0",
    "bare-events": "^2.5.4",
    "brittle": "^3.5.2",
    "lunte": "^1.6.0",
    "prettier": "^3.3.3",
    "prettier-config-holepunch": "^2.0.0"
  },
  "peerDependencies": {
    "bare-abort-controller": "*",
    "bare-buffer": "*",
    "bare-events": "*"
  },
  "peerDependenciesMeta": {
    "bare-abort-controller": {
      "optional": true
    },
    "bare-buffer": {
      "optional": true
    },
    "bare-events": {
      "optional": true
    }
  }
}
const { Readable, Writable, Transform, getStreamError, isStreamx, isDisturbed } = require('streamx')
const tee = require('teex')

const readableKind = Symbol.for('bare.stream.readable.kind')
const writableKind = Symbol.for('bare.stream.writable.kind')
const transformKind = Symbol.for('bare.stream.transform.kind')

// https://streams.spec.whatwg.org/#readablestreamdefaultreader
exports.ReadableStreamDefaultReader = class ReadableStreamDefaultReader {
  constructor(stream) {
    this._stream = stream
    this._stream._stream.once('close', onclose).once('error', onerror)

    const closed = Promise.withResolvers()

    // Avoid unhandled exceptions
    closed.promise.catch(noop)

    this._closed = closed

    function onclose() {
      closed.resolve()
    }

    function onerror(err) {
      closed.reject(err)
    }
  }

  get closed() {
    return this._closed.promise
  }

  read() {
    const stream = this._stream._stream

    return new Promise((resolve, reject) => {
      const err = getStreamError(stream)

      if (err) return reject(err)

      if (stream.destroyed) {
        return resolve({ value: undefined, done: true })
      }

      const value = stream.read()

      if (value !== null) {
        return resolve({ value, done: false })
      }

      stream.once('readable', onreadable).once('close', onclose).once('error', onerror)

      function onreadable() {
        const value = stream.read()

        ondone(null, value === null ? { value: undefined, done: true } : { value, done: false })
      }

      function onclose() {
        ondone(null, { value: undefined, done: true })
      }

      function onerror(err) {
        ondone(err, null)
      }

      function ondone(err, value) {
        stream.off('readable', onreadable).off('close', onclose).off('error', onerror)

        if (err) reject(err)
        else resolve(value)
      }
    })
  }

  releaseLock() {
    this._closed.reject(new TypeError('Reader was released'))
    this._stream._releaseLock()
    this._stream = null
  }

  cancel(reason = new TypeError('Stream was cancelled')) {
    const stream = this._stream._stream

    if (stream.destroyed) return Promise.resolve()

    return new Promise((resolve) =>
      stream.once('close', resolve).once('error', noop).destroy(reason)
    )
  }
}

// https://streams.spec.whatwg.org/#readablestreamdefaultcontroller
exports.ReadableStreamDefaultController = class ReadableStreamDefaultController {
  constructor(stream) {
    this._stream = stream
  }

  get desiredSize() {
    const stream = this._stream._stream

    return stream._readableState.highWaterMark - stream._readableState.buffered
  }

  enqueue(data) {
    this._stream._stream.push(data)
  }

  close() {
    this._stream._stream.push(null)
  }

  error(err) {
    this._stream._stream.destroy(err)
  }
}

// https://streams.spec.whatwg.org/#readablestream
class ReadableStream {
  static get [readableKind]() {
    return 0 // Compatibility version
  }

  static from(iterable) {
    return new ReadableStream(Readable.from(iterable))
  }

  constructor(underlyingSource = {}, queuingStrategy) {
    if (isStreamx(underlyingSource)) {
      this._stream = underlyingSource
    } else {
      if (queuingStrategy === undefined) {
        queuingStrategy = new exports.CountQueuingStrategy()
      }

      const { start, pull, cancel } = underlyingSource
      const { highWaterMark = 1, size = defaultSize } = queuingStrategy

      this._stream = new Readable({ highWaterMark, byteLength: size })

      const controller = new exports.ReadableStreamDefaultController(this)

      try {
        let starting = Promise.resolve()

        if (start) starting = forwardError(start.call(this, controller), controller)

        if (pull) {
          this._stream._read = this._read.bind(this, starting, pull.bind(this, controller))
        }

        if (cancel) {
          this._stream.once('error', cancel.bind(this))
        }
      } catch (err) {
        controller.error(err)
      }
    }

    this._reader = null
  }

  get [readableKind]() {
    return ReadableStream[readableKind]
  }

  get locked() {
    return this._reader !== null
  }

  getReader() {
    if (this.locked) throw new TypeError('Stream is locked')

    this._reader = new exports.ReadableStreamDefaultReader(this)

    return this._reader
  }

  cancel(reason = new TypeError('Stream was cancelled')) {
    const stream = this._stream

    if (stream.destroyed) return Promise.resolve()

    if (this.locked) return Promise.reject(new TypeError('Stream is locked'))

    return new Promise((resolve) =>
      stream.once('close', resolve).once('error', noop).destroy(reason)
    )
  }

  tee() {
    const [a, b] = tee(this._stream)

    return [new ReadableStream(a), new ReadableStream(b)]
  }

  pipeTo(destination) {
    return new Promise((resolve, reject) =>
      this._stream.pipe(destination._stream, (err) => {
        err ? reject(err) : resolve()
      })
    )
  }

  [Symbol.asyncIterator]() {
    return this._stream[Symbol.asyncIterator]()
  }

  _releaseLock() {
    this._reader = null
  }

  async _read(starting, pull, cb) {
    await starting

    let err = null
    try {
      await pull()
    } catch (e) {
      err = e
    }
    cb(err)
  }
}

function defaultSize() {
  return 1
}

exports.ReadableStream = ReadableStream

// https://streams.spec.whatwg.org/#countqueuingstrategy
exports.CountQueuingStrategy = class CountQueuingStrategy {
  constructor(opts = {}) {
    const { highWaterMark = 1 } = opts

    this.highWaterMark = highWaterMark
  }

  size(chunk) {
    return 1
  }
}

// https://streams.spec.whatwg.org/#bytelengthqueuingstrategy
exports.ByteLengthQueuingStrategy = class ByteLengthQueuingStrategy {
  constructor(opts = {}) {
    const { highWaterMark = 16384 } = opts

    this.highWaterMark = highWaterMark
  }

  size(chunk) {
    return chunk.byteLength
  }
}

exports.isReadableStream = function isReadableStream(value) {
  if (value instanceof ReadableStream) return true

  return (
    typeof value === 'object' &&
    value !== null &&
    value[readableKind] === ReadableStream[readableKind]
  )
}

// https://streams.spec.whatwg.org/#readablestream-errored
exports.isReadableStreamErrored = function isReadableStreamErrored(stream) {
  return getStreamError(stream._stream) !== null
}

// https://streams.spec.whatwg.org/#is-readable-stream-disturbed
exports.isReadableStreamDisturbed = function isReadableStreamDisturbed(stream) {
  return isDisturbed(stream._stream)
}

// https://streams.spec.whatwg.org/#writablestreamdefaultwriter
exports.WritableStreamDefaultWriter = class WritableStreamDefaultWriter {
  constructor(stream) {
    this._stream = stream
    this._stream._stream.once('close', onclose).once('error', onerror)

    const closed = Promise.withResolvers()

    // Avoid unhandled exceptions
    closed.promise.catch(noop)

    this._closed = closed

    function onclose() {
      closed.resolve()
    }

    function onerror(err) {
      closed.reject(err)
    }
  }

  get desiredSize() {
    const stream = this._stream._stream

    return stream._writableState.highWaterMark - stream._writableState.buffered
  }

  get closed() {
    return this._closed.promise
  }

  get ready() {
    const stream = this._stream._stream

    if (getStreamError(stream)) return Promise.reject()

    return Writable.drained(stream).then()
  }

  async write(chunk) {
    const stream = this._stream._stream

    let err = getStreamError(stream)
    if (err) return Promise.reject(err)

    stream.write(chunk)

    await Writable.drained(stream)

    err = getStreamError(stream)
    if (err) return Promise.reject(err)
  }

  releaseLock() {
    this._closed.reject(new TypeError('Writer was released'))
    this._stream._releaseLock()
    this._stream = null
  }

  close() {
    const stream = this._stream._stream

    if (stream.destroyed) return Promise.resolve()

    return new Promise((resolve) => stream.once('close', resolve).end())
  }

  abort(reason = new TypeError('Stream was aborted')) {
    const stream = this._stream._stream

    if (stream.destroyed) return Promise.resolve()

    return new Promise((resolve) => stream.once('close', resolve).destroy(reason))
  }
}

// https://streams.spec.whatwg.org/#writablestreamdefaultcontroller
exports.WritableStreamDefaultController = class WritableStreamDefaultController {
  constructor(stream) {
    this._stream = stream
  }

  error(err) {
    this._stream._stream.destroy(err)
  }
}

// https://streams.spec.whatwg.org/#writablestream
class WritableStream {
  static get [writableKind]() {
    return 0 // Compatibility version
  }

  constructor(underlyingSink = {}, queuingStrategy = {}) {
    if (isStreamx(underlyingSink)) {
      this._stream = underlyingSink
    } else {
      if (queuingStrategy === undefined) {
        queuingStrategy = new exports.CountQueuingStrategy()
      }

      const { start, write, close, abort } = underlyingSink
      const { highWaterMark = 1, size = defaultSize } = queuingStrategy

      this._stream = new Writable({ highWaterMark, byteLength: size })

      const controller = new exports.WritableStreamDefaultController(this)

      this._controller = controller

      try {
        let starting = Promise.resolve()

        if (start) starting = forwardError(start.call(this, controller), controller)

        if (write) {
          this._stream._write = this._write.bind(this, starting, write.bind(this))
        }

        if (close) {
          this._stream._destroy = this._destroy.bind(this, close.call(this))
        }

        if (abort) {
          this._stream.once('error', abort.bind(this))
        }
      } catch (err) {
        controller.error(err)
      }
    }

    this._writer = null
  }

  get [writableKind]() {
    return WritableStream[writableKind]
  }

  get locked() {
    return this._writer !== null
  }

  getWriter() {
    if (this.locked) throw new TypeError('Stream is locked')

    this._writer = new exports.WritableStreamDefaultWriter(this)

    return this._writer
  }

  abort(reason = new TypeError('Stream was aborted')) {
    if (this._stream.destroyed) return Promise.resolve()

    if (this.locked) return Promise.reject(new TypeError('Stream is locked'))

    return new Promise((resolve) => this._stream.once('close', resolve).destroy(reason))
  }

  close() {
    if (this._stream.destroyed) return Promise.resolve()

    if (this.locked) return Promise.reject(new TypeError('Stream is locked'))

    return new Promise((resolve) => this._stream.once('close', resolve).end())
  }

  _releaseLock() {
    this._writer = null
  }

  async _write(starting, write, data, cb) {
    await starting

    let err = null
    try {
      await write(data, this._controller)
    } catch (e) {
      err = e
    }
    cb(err)
  }

  async _destroy(closing, cb) {
    let err = null
    try {
      await closing
    } catch (e) {
      err = e
    }
    cb(err)
  }
}

exports.WritableStream = WritableStream

exports.isWritableStream = function isWritableStream(value) {
  if (value instanceof WritableStream) return true

  return (
    typeof value === 'object' &&
    value !== null &&
    value[writableKind] === WritableStream[writableKind]
  )
}

// https://streams.spec.whatwg.org/#transformstreamdefaultcontroller
exports.TransformStreamDefaultController = class TransformStreamDefaultController {
  constructor(stream) {
    this._stream = stream
  }

  get desiredSize() {
    const stream = this._stream._stream

    return stream._readableState.highWaterMark - stream._readableState.buffered
  }

  enqueue(data) {
    this._stream._stream.push(data)
  }

  error(err) {
    this._stream._stream.destroy(err)
  }

  terminate() {
    const stream = this._stream._stream

    stream.push(null)
    stream.destroy(new TypeError('Stream has been terminated'))
  }
}

// https://streams.spec.whatwg.org/#transformstream
class TransformStream {
  static get [transformKind]() {
    return 0 // Compatibility version
  }

  constructor(transformer = {}, writableStrategy = {}, readableStrategy = {}) {
    if (isStreamx(transformer)) {
      this._stream = transformer
    } else {
      const { start, transform, flush } = transformer

      this._stream = new Transform({ ...writableStrategy, ...readableStrategy })

      const controller = new exports.TransformStreamDefaultController(this)

      this._controller = controller

      try {
        let starting = Promise.resolve()

        if (start) starting = forwardError(start.call(this, controller), controller)

        if (transform) {
          this._stream._transform = this._transform.bind(this, starting, transform.bind(this))
        }

        if (flush) {
          this._stream._flush = this._flush.bind(this, flush.call(this, this._controller))
        }
      } catch (err) {
        controller.error(err)
      }
    }

    this._writable = new WritableStream(this._stream)
    this._readable = new ReadableStream(this._stream)
  }

  get [transformKind]() {
    return TransformStream[transformKind]
  }

  get writable() {
    return this._writable
  }

  get readable() {
    return this._readable
  }

  async _transform(starting, transform, data, cb) {
    await starting

    let err = null
    try {
      await transform(data, this._controller)
    } catch (e) {
      err = e
    }
    cb(err)
  }

  async _flush(flush, cb) {
    let err = null
    try {
      await flush
    } catch (e) {
      err = e
    }
    cb(err)
  }
}

exports.TransformStream = TransformStream

exports.isTransformStream = function isTransformStream(value) {
  if (value instanceof TransformStream) return true

  return (
    typeof value === 'object' &&
    value !== null &&
    value[transformKind] === TransformStream[transformKind]
  )
}

async function forwardError(promise, controller) {
  try {
    await promise
  } catch (err) {
    controller.error(err)
  }
}

function noop() {}
module.exports = require.addon()
const getType = require('bare-type')
const { isURL } = require('bare-url')
const { isBuffer } = require('bare-buffer')
const c = require('compact-encoding')
const constants = require('./lib/constants')
const errors = require('./lib/errors')
const binding = require('./binding')

const t = constants.type

module.exports = exports = function structuredClone(value, opts = {}) {
  return exports.deserializeWithTransfer(
    exports.serializeWithTransfer(value, opts.transfer, opts.interfaces),
    opts.interfaces
  )
}

// https://html.spec.whatwg.org/multipage/structured-data.html#structuredserialize
exports.serialize = function serialize(value, forStorage = false, interfaces = []) {
  return serializeValue(value, forStorage, new InterfaceMap(interfaces), new ReferenceMap())
}

// https://html.spec.whatwg.org/multipage/structured-data.html#structuredserializewithtransfer
exports.serializeWithTransfer = function serializeWithTransfer(
  value,
  transferList = [],
  interfaces = []
) {
  return serializeValueWithTransfer(value, transferList, new InterfaceMap(interfaces))
}

// https://html.spec.whatwg.org/multipage/structured-data.html#structureddeserialize
exports.deserialize = function deserialize(serialized, interfaces = []) {
  return deserializeValue(serialized, new InterfaceMap(interfaces), new Map())
}

// https://html.spec.whatwg.org/multipage/structured-data.html#structureddeserializewithtransfer
exports.deserializeWithTransfer = function deserializeWithTransfer(serialized, interfaces = []) {
  return deserializeValueWithTransfer(serialized, new InterfaceMap(interfaces))
}

exports.constants = constants
exports.errors = errors

exports.symbols = {
  serialize: Symbol.for('bare.serialize'),
  deserialize: Symbol.for('bare.deserialize'),
  detach: Symbol.for('bare.detach'),
  attach: Symbol.for('bare.attach'),
  interface: Symbol.for('bare.interface')
}

exports.Serializable = class Serializable {
  [exports.symbols.serialize](forStorage) {}

  static [exports.symbols.deserialize](serialized) {}
}

exports.Transferable = class Transferable {
  constructor() {
    this.detached = false
  }

  [exports.symbols.detach]() {
    this.detached = true
  }

  static [exports.symbols.attach](serialized) {}
}

class InterfaceMap {
  constructor(interfaces) {
    this.ids = new WeakMap()
    this.keys = new Map()
    this.interfaces = new Map()

    let nextId = 1

    for (const constructor of interfaces) {
      const id = nextId++

      this.ids.set(constructor, id)

      const key = constructor[exports.symbols.interface]

      if (key !== undefined) this.keys.set(key, id)

      this.interfaces.set(id, constructor)
    }
  }

  id(constructor) {
    let id = this.ids.get(constructor)

    if (id) return id

    // The constructor was not registered by identity, but a distinct copy of
    // the same class might have been, for example when the value originates
    // from code that bundles its own copy of the interface. Fall back to the
    // interface key that the class declares for itself, a shared symbol such
    // as one from the global registry, so that any such copy with a matching
    // API resolves to the registered interface.
    const key = constructor[exports.symbols.interface]

    if (key !== undefined) {
      id = this.keys.get(key)

      if (id) {
        this.ids.set(constructor, id)

        return id
      }
    }

    throw errors.INVALID_INTERFACE(
      `Class '${constructor.name}' is not registered as a serializable or transferable interface`
    )
  }

  get(id) {
    const constructor = this.interfaces.get(id)

    if (!constructor) {
      throw errors.INVALID_INTERFACE(`Interface with ID '${id}' was not found`)
    }

    return constructor
  }
}

class ReferenceMap {
  constructor() {
    this.ids = new WeakMap()
    this.nextId = 1
  }

  id(object) {
    let id = this.ids.get(object)
    if (id) return id

    id = this.nextId++
    this.ids.set(object, id)

    return id
  }

  has(object) {
    return this.ids.has(object)
  }
}

function serializeValue(value, forStorage, interfaces, references) {
  const type = getType(value)

  if (type.isUndefined()) return { type: t.UNDEFINED }
  if (type.isNull()) return { type: t.NULL }
  if (type.isBoolean()) return { type: value ? t.TRUE : t.FALSE }
  if (type.isNumber()) return { type: t.NUMBER, value }
  if (type.isBigInt()) return { type: t.BIGINT, value }
  if (type.isString()) return serializeString(value)
  if (type.isSymbol()) return serializeSymbol(value)
  if (type.isObject()) {
    return serializeReferenceable(type, value, forStorage, interfaces, references)
  }
  if (type.isFunction()) return serializeFunction(value)
  if (type.isExternal()) return serializeExternal(value, forStorage, references)
}

function serializeString(value) {
  return { type: t.STRING, value }
}

function serializeSymbol(value) {
  throw errors.UNSERIALIZABLE_TYPE(`Symbol '${value.description}' cannot be serialized`)
}

function serializeFunction(value) {
  throw errors.UNSERIALIZABLE_TYPE(`Function '${value.name}' cannot be serialized`)
}

function serializeReferenceable(type, value, forStorage, interfaces, references) {
  if (references.has(value)) return serializeReference(value, references)

  if (isURL(value)) return serializeURL(value, references)
  if (isBuffer(value)) return serializeBuffer(value, forStorage, interfaces, references)

  if (type.isArray()) return serializeArray(value, forStorage, interfaces, references)
  if (type.isDate()) return serializeDate(value, references)
  if (type.isRegExp()) return serializeRegExp(value, references)
  if (type.isError()) return serializeError(value, forStorage, interfaces, references)
  if (type.isMap()) return serializeMap(value, forStorage, interfaces, references)
  if (type.isSet()) return serializeSet(value, forStorage, interfaces, references)
  if (type.isArrayBuffer()) return serializeArrayBuffer(value, references)
  if (type.isSharedArrayBuffer()) return serializeSharedArrayBuffer(value, forStorage, references)
  if (type.isTypedArray()) {
    return serializeTypedArray(type, value, forStorage, interfaces, references)
  }
  if (type.isDataView()) return serializeDataView(value, forStorage, interfaces, references)

  if (
    type.isPromise() ||
    type.isProxy() ||
    type.isWeakMap() ||
    type.isWeakSet() ||
    type.isWeakRef()
  ) {
    throw errors.UNSERIALIZABLE_TYPE(`${value.constructor.name} cannot be serialized`)
  }

  const serialize = value[Symbol.for('bare.serialize')]

  if (serialize) return serializeSerializable(value, serialize, forStorage, interfaces, references)

  return serializeObject(value, forStorage, interfaces, references)
}

function serializeReference(value, references) {
  return { type: t.REFERENCE, id: references.id(value) }
}

function serializeDate(value, references) {
  return { type: t.DATE, id: references.id(value), value: value.getTime() }
}

function serializeRegExp(value, references) {
  return {
    type: t.REGEXP,
    id: references.id(value),
    source: value.source,
    flags: value.flags
  }
}

function serializeError(value, forStorage, interfaces, references) {
  let name = 0

  switch (value.name) {
    case 'AggregateError':
      name = t.error.AGGREGATE
      break
    case 'EvalError':
      name = t.error.EVAL
      break
    case 'RangeError':
      name = t.error.RANGE
      break
    case 'ReferenceError':
      name = t.error.REFERENCE
      break
    case 'SyntaxError':
      name = t.error.SYNTAX
      break
    case 'TypeError':
      name = t.error.TYPE
      break
    case 'URIError':
      name = t.error.URI
      break
  }

  const serialized = {
    type: t.ERROR,
    id: references.id(value),
    name,
    message: value.message.toString(),
    stack: serializeValue(value.stack, forStorage, interfaces, references)
  }

  if ('cause' in value) {
    // Don't add unless defined
    serialized.cause = serializeValue(value.cause, forStorage, interfaces, references)
  }

  if (name === t.error.AGGREGATE) {
    serialized.errors = value.errors.map((err) =>
      serializeValue(err, forStorage, interfaces, references)
    )
  }

  return serialized
}

function serializeArrayBuffer(value, references) {
  if (value.detached) {
    throw errors.UNSERIALIZABLE_TYPE('Detached ArrayBuffer cannot be serialized')
  }

  const id = references.id(value)

  if (value.resizable) {
    return {
      type: t.RESIZABLEARRAYBUFFER,
      id,
      owned: false,
      data: value,
      maxByteLength: value.maxByteLength
    }
  }

  return {
    type: t.ARRAYBUFFER,
    id,
    owned: false,
    data: value
  }
}

function serializeSharedArrayBuffer(value, forStorage, references) {
  if (forStorage) {
    throw errors.UNSERIALIZABLE_TYPE('SharedArrayBuffer cannot be serialized to storage')
  }

  const id = references.id(value)

  const backingStore = binding.getSharedArrayBufferBackingStore(value)

  if (value.growable) {
    return {
      type: t.GROWABLESHAREDARRAYBUFFER,
      id,
      backingStore,
      maxByteLength: value.maxByteLength
    }
  }

  return {
    type: t.SHAREDARRAYBUFFER,
    id,
    backingStore
  }
}

function serializeTypedArray(type, value, forStorage, interfaces, references) {
  let view

  if (type.isUint8Array()) {
    view = t.typedarray.UINT8ARRAY
  } else if (type.isUint8ClampedArray()) {
    view = t.typedarray.UINT8CLAMPEDARRAY
  } else if (type.isInt8Array()) {
    view = t.typedarray.INT8ARRAY
  } else if (type.isUint16Array()) {
    view = t.typedarray.UINT16ARRAY
  } else if (type.isInt16Array()) {
    view = t.typedarray.INT16ARRAY
  } else if (type.isUint32Array()) {
    view = t.typedarray.UINT32ARRAY
  } else if (type.isInt32Array()) {
    view = t.typedarray.INT32ARRAY
  } else if (type.isBigUint64Array()) {
    view = t.typedarray.BIGUINT64ARRAY
  } else if (type.isBigInt64Array()) {
    view = t.typedarray.BIGINT64ARRAY
  } else if (type.isFloat16Array()) {
    view = t.typedarray.FLOAT16ARRAY
  } else if (type.isFloat32Array()) {
    view = t.typedarray.FLOAT32ARRAY
  } else if (type.isFloat64Array()) {
    view = t.typedarray.FLOAT64ARRAY
  }

  return {
    type: t.TYPEDARRAY,
    id: references.id(value),
    view,
    buffer: serializeValue(value.buffer, forStorage, interfaces, references),
    byteOffset: value.byteOffset,
    byteLength: value.byteLength,
    length: value.length
  }
}

function serializeDataView(value, forStorage, interfaces, references) {
  return {
    type: t.DATAVIEW,
    id: references.id(references),
    buffer: serializeValue(value.buffer, forStorage, interfaces, references),
    byteOffset: value.byteOffset,
    byteLength: value.byteLength
  }
}

function serializeMap(value, forStorage, interfaces, references) {
  const id = references.id(value)
  const data = []

  for (const entry of value) {
    const [key, value] = entry

    data.push({
      key: serializeValue(key, forStorage, interfaces, references),
      value: serializeValue(value, forStorage, interfaces, references)
    })
  }

  return { type: t.MAP, id, data }
}

function serializeSet(value, forStorage, interfaces, references) {
  const id = references.id(value)
  const data = []

  for (const entry of value) {
    data.push(serializeValue(entry, forStorage, interfaces, references))
  }

  return { type: t.SET, id, data }
}

function serializeArray(value, forStorage, interfaces, references) {
  const id = references.id(value)
  const properties = []

  for (const entry of Object.entries(value)) {
    const [key, value] = entry

    properties.push({
      key,
      value: serializeValue(value, forStorage, interfaces, references)
    })
  }

  return { type: t.ARRAY, id, length: value.length, properties }
}

function serializeObject(value, forStorage, interfaces, references) {
  const id = references.id(value)
  const properties = []

  for (const entry of Object.entries(value)) {
    const [key, value] = entry

    properties.push({
      key,
      value: serializeValue(value, forStorage, interfaces, references)
    })
  }

  return { type: t.OBJECT, id, properties }
}

function serializeURL(value, references) {
  return { type: t.URL, id: references.id(value), href: value.href }
}

function serializeBuffer(value, forStorage, interfaces, references) {
  if (value.detached) {
    throw errors.UNSERIALIZABLE_TYPE('Detached Buffer cannot be serialized')
  }

  return {
    type: t.BUFFER,
    id: references.id(value),
    buffer: serializeValue(value.buffer, forStorage, interfaces, references),
    byteOffset: value.byteOffset,
    byteLength: value.byteLength
  }
}

function serializeExternal(value, forStorage) {
  if (forStorage) {
    throw errors.UNSERIALIZABLE_TYPE('External pointer cannot be serialized to storage')
  }

  return {
    type: t.EXTERNAL,
    pointer: binding.getExternal(value)
  }
}

function serializeSerializable(value, serializer, forStorage, interfaces, references) {
  return {
    type: t.SERIALIZABLE,
    id: references.id(value),
    interface: interfaces.id(value.constructor),
    value: serializeValue(serializer.call(value, forStorage), forStorage, interfaces, references)
  }
}

function serializeValueWithTransfer(value, transferList, interfaces) {
  const references = new ReferenceMap()

  for (const transferable of transferList) {
    const type = getType(transferable)

    if (type.isArrayBuffer()) {
      if (transferable.detached) {
        throw errors.UNTRANSFERABLE_TYPE("Detached 'ArrayBuffer' cannot be transferred")
      }

      if (references.has(transferable)) {
        throw errors.ALREADY_TRANSFERRED("'ArrayBuffer' has already been transferred")
      }

      references.id(transferable)
    } else {
      const detach = transferable[exports.symbols.detach]

      if (detach) {
        if (transferable.detached) {
          throw errors.UNTRANSFERABLE_TYPE(
            `Detached '${transferable.constructor.name}' cannot be transferred`
          )
        }

        if (references.has(transferable)) {
          throw errors.ALREADY_TRANSFERRED(
            `'${transferable.constructor.name}' has already been transferred`
          )
        }

        references.id(transferable)
      } else {
        throw errors.UNTRANSFERABLE_TYPE('Value cannot be transferred')
      }
    }
  }

  const serialized = serializeValue(value, false, interfaces, references)

  const transfers = []

  for (const transferable of transferList) {
    const type = getType(transferable)

    if (type.isArrayBuffer()) {
      if (transferable.detached) {
        throw errors.UNTRANSFERABLE_TYPE('Detached ArrayBuffer cannot be transferred')
      }

      const backingStore = binding.getArrayBufferBackingStore(transferable)

      const id = references.id(transferable)

      let transfer

      if (value.resizable) {
        transfer = {
          type: t.RESIZABLEARRAYBUFFER,
          id,
          backingStore,
          maxByteLength: value.maxByteLength
        }
      } else {
        transfer = { type: t.ARRAYBUFFER, id, backingStore }
      }

      transfers.push(transfer)

      binding.detachArrayBuffer(transferable)
    } else {
      if (transferable.detached) {
        throw errors.UNTRANSFERABLE_TYPE(
          `Detached '${transferable.constructor.name}' cannot be transferred`
        )
      }

      const detach = transferable[exports.symbols.detach]

      const transfer = {
        type: t.TRANSFERABLE,
        id: references.id(transferable),
        interface: interfaces.id(transferable.constructor),
        value: serializeValue(detach.call(transferable), false, interfaces, references)
      }

      transfers.push(transfer)
    }
  }

  return { type: t.TRANSFER, transfers, value: serialized }
}

function deserializeValue(serialized, interfaces, references) {
  let value

  switch (serialized.type) {
    case t.UNDEFINED:
      return undefined
    case t.NULL:
      return null
    case t.TRUE:
      return true
    case t.FALSE:
      return false

    case t.NUMBER:
    case t.BIGINT:
    case t.STRING:
      return serialized.value

    case t.EXTERNAL:
      return binding.createExternal(serialized.pointer)

    case t.DATE:
      value = new Date(serialized.value)
      break

    case t.REGEXP:
      value = new RegExp(serialized.source, serialized.flags)
      break

    case t.ERROR: {
      const options = {}

      if ('cause' in serialized) {
        options.case = deserializeValue(serialized.cause, interfaces, references)
      }

      switch (serialized.name) {
        case t.error.AGGREGATE:
          value = new AggregateError(
            serialized.errors.map((err) => deserializeValue(err, interfaces, references)),
            serialized.message,
            options
          )
          break
        case t.error.EVAL:
          value = new EvalError(serialized.message, options)
          break
        case t.error.RANGE:
          value = new RangeError(serialized.message, options)
          break
        case t.error.REFERENCE:
          value = new ReferenceError(serialized.message, options)
          break
        case t.error.SYNTAX:
          value = new SyntaxError(serialized.message, options)
          break
        case t.error.TYPE:
          value = new TypeError(serialized.message, options)
          break
        default:
          value = new Error(serialized.message, options)
      }

      value.stack = deserializeValue(serialized.stack, interfaces, references)

      break
    }

    case t.ARRAYBUFFER:
      if (serialized.owned) value = serialized.data
      else {
        value = new ArrayBuffer(serialized.data.byteLength)

        Buffer.from(value).set(Buffer.from(serialized.data))
      }

      break

    case t.RESIZABLEARRAYBUFFER:
      if (serialized.owned) value = serialized.data
      else {
        value = new ArrayBuffer(serialized.data.byteLength, {
          maxByteLength: serialized.maxByteLength
        })

        Buffer.from(value).set(Buffer.from(serialized.data))
      }

      break

    case t.SHAREDARRAYBUFFER:
    case t.GROWABLESHAREDARRAYBUFFER:
      value = binding.createSharedArrayBuffer(serialized.backingStore)

      Buffer.from(serialized.backingStore).fill(0)

      break

    case t.TYPEDARRAY: {
      const buffer = deserializeValue(serialized.buffer, interfaces, references)

      switch (serialized.view) {
        case t.typedarray.UINT8ARRAY:
          value = new Uint8Array(buffer, serialized.byteOffset, serialized.length)
          break
        case t.typedarray.UINT8CLAMPEDARRAY:
          value = new Uint8ClampedArray(buffer, serialized.byteOffset, serialized.length)
          break
        case t.typedarray.INT8ARRAY:
          value = new Int8Array(buffer, serialized.byteOffset, serialized.length)
          break
        case t.typedarray.UINT16ARRAY:
          value = new Uint16Array(buffer, serialized.byteOffset, serialized.length)
          break
        case t.typedarray.INT16ARRAY:
          value = new Int16Array(buffer, serialized.byteOffset, serialized.length)
          break
        case t.typedarray.UINT32ARRAY:
          value = new Uint32Array(buffer, serialized.byteOffset, serialized.length)
          break
        case t.typedarray.INT32ARRAY:
          value = new Int32Array(buffer, serialized.byteOffset, serialized.length)
          break
        case t.typedarray.BIGUINT64ARRAY:
          value = new BigUint64Array(buffer, serialized.byteOffset, serialized.length)
          break
        case t.typedarray.BIGINT64ARRAY:
          value = new BigInt64Array(buffer, serialized.byteOffset, serialized.length)
          break
        case t.typedarray.FLOAT16ARRAY:
          value = new Float16Array(buffer, serialized.byteOffset, serialized.length)
          break
        case t.typedarray.FLOAT32ARRAY:
          value = new Float32Array(buffer, serialized.byteOffset, serialized.length)
          break
        case t.typedarray.FLOAT64ARRAY:
          value = new Float64Array(buffer, serialized.byteOffset, serialized.length)
          break
      }

      break
    }

    case t.DATAVIEW:
      value = new DataView(
        deserializeValue(serialized.buffer, interfaces, references),
        serialized.byteOffset,
        serialized.byteLength
      )
      break

    case t.MAP:
      value = new Map()
      break
    case t.SET:
      value = new Set()
      break
    case t.ARRAY:
      value = new Array(serialized.length)
      break
    case t.OBJECT:
      value = {}
      break

    case t.REFERENCE:
      if (references.has(serialized.id)) value = references.get(serialized.id)
      else {
        throw errors.INVALID_REFERENCE(`Object with ID '${serialized.id}' was not found`)
      }

      return value

    case t.URL:
      return new URL(serialized.href)

    case t.BUFFER:
      value = Buffer.from(
        deserializeValue(serialized.buffer, interfaces, references),
        serialized.byteOffset,
        serialized.byteLength
      )
      break

    case t.SERIALIZABLE: {
      const constructor = interfaces.get(serialized.interface)

      const deserialize = constructor[exports.symbols.deserialize]

      value = deserialize.call(
        constructor,
        deserializeValue(serialized.value, interfaces, references)
      )
      break
    }
  }

  references.set(serialized.id, value)

  switch (serialized.type) {
    case t.MAP:
      for (const entry of serialized.data) {
        value.set(
          deserializeValue(entry.key, interfaces, references),
          deserializeValue(entry.value, interfaces, references)
        )
      }
      break

    case t.SET:
      for (const entry of serialized.data) {
        value.add(deserializeValue(entry, interfaces, references))
      }
      break

    case t.ARRAY:
    case t.OBJECT:
      for (const entry of serialized.properties) {
        value[entry.key] = deserializeValue(entry.value, interfaces, references)
      }
      break
  }

  return value
}

function deserializeValueWithTransfer(serialized, interfaces) {
  const references = new Map()

  for (const transfer of serialized.transfers) {
    switch (transfer.type) {
      case t.ARRAYBUFFER:
      case t.RESIZABLEARRAYBUFFER:
        references.set(transfer.id, binding.createArrayBuffer(transfer.backingStore))
        break

      case t.TRANSFERABLE: {
        const constructor = interfaces.get(transfer.interface)

        const attach = constructor[exports.symbols.attach]

        references.set(
          transfer.id,
          attach.call(constructor, deserializeValue(transfer.value, interfaces, references))
        )
        break
      }
    }
  }

  return deserializeValue(serialized.value, interfaces, references)
}

const header = {
  preencode(state) {
    c.uint.preencode(state, constants.VERSION)
    c.uint.preencode(state, 0) // Flags
  },
  encode(state) {
    c.uint.encode(state, constants.VERSION)
    c.uint.encode(state, 0) // Flags
  },
  decode(state) {
    const version = c.uint.decode(state)

    if (version !== constants.VERSION) {
      throw errors.INVALID_VERSION(`Invalid ABI version '${version}'`)
    }

    c.uint.decode(state) // Flags
  }
}

const property = {
  preencode(state, m) {
    c.string.preencode(state, m.key)
    value.preencode(state, m.value)
  },
  encode(state, m) {
    c.string.encode(state, m.key)
    value.encode(state, m.value)
  },
  decode(state) {
    return {
      key: c.string.decode(state),
      value: value.decode(state)
    }
  }
}

const properties = c.array(property)

const entry = {
  preencode(state, m) {
    value.preencode(state, m.key)
    value.preencode(state, m.value)
  },
  encode(state, m) {
    value.encode(state, m.key)
    value.encode(state, m.value)
  },
  decode(state) {
    return {
      key: value.decode(state),
      value: value.decode(state)
    }
  }
}

const entries = c.array(entry)

const transfer = {
  preencode(state, m) {
    c.uint.preencode(state, m.type)
    c.uint.preencode(state, m.id)

    switch (m.type) {
      case t.ARRAYBUFFER:
        c.arraybuffer.preencode(state, m.backingStore)
        break
      case t.RESIZABLEARRAYBUFFER:
        c.arraybuffer.preencode(state, m.backingStore)
        c.uint.preencode(state, m.maxByteLength)
        break
      case t.TRANSFERABLE:
        c.uint.preencode(state, m.interface)
        value.preencode(state, m.value)
        break
    }
  },
  encode(state, m) {
    c.uint.encode(state, m.type)
    c.uint.encode(state, m.id)

    switch (m.type) {
      case t.ARRAYBUFFER:
        c.arraybuffer.encode(state, m.backingStore)
        break
      case t.RESIZABLEARRAYBUFFER:
        c.arraybuffer.encode(state, m.backingStore)
        c.uint.encode(state, m.maxByteLength)
        break
      case t.TRANSFERABLE:
        c.uint.encode(state, m.interface)
        value.encode(state, m.value)
        break
    }
  },
  decode(state) {
    const type = c.uint.decode(state)
    const id = c.uint.decode(state)

    switch (type) {
      case t.ARRAYBUFFER:
        return {
          type,
          id,
          backingStore: c.arraybuffer.decode(state)
        }
      case t.RESIZABLEARRAYBUFFER:
        return {
          type,
          id,
          backingStore: c.arraybuffer.decode(state),
          maxByteLength: c.uint.decode(state)
        }
      case t.TRANSFERABLE:
        return {
          type,
          id,
          interface: c.uint.decode(state),
          value: value.decode(state)
        }
    }
  }
}

const transfers = c.array(transfer)

const value = {
  preencode(state, m) {
    c.uint.preencode(state, m.type)

    switch (m.type) {
      case t.UNDEFINED:
      case t.NULL:
      case t.TRUE:
      case t.FALSE:
        return
      case t.NUMBER:
        return c.float64.preencode(state, m.value)
      case t.BIGINT:
        return c.bigint.preencode(state, m.value)
      case t.STRING:
        return c.string.preencode(state, m.value)
      case t.EXTERNAL:
        return c.arraybuffer.preencode(state, m.pointer)
      case t.TRANSFER:
        transfers.preencode(state, m.transfers)
        value.preencode(state, m.value)
        return
    }

    c.uint.preencode(state, m.type)

    switch (m.type) {
      case t.DATE:
        c.int.preencode(state, m.value)
        break
      case t.REGEXP:
        c.string.preencode(state, m.source)
        c.string.preencode(state, m.flags)
        break
      case t.ERROR:
        c.uint.preencode(state, 0) // Flags
        c.uint.preencode(state, m.name)
        c.string.preencode(state, m.message)
        value.preencode(state, m.stack)
        if ('cause' in m) value.preencode(state, m.cause)
        if (m.name === t.error.AGGREGATE) values.preencode(state, m.errors)
        break
      case t.ARRAYBUFFER:
        c.arraybuffer.preencode(state, m.data)
        break
      case t.RESIZABLEARRAYBUFFER:
        c.arraybuffer.preencode(state, m.data)
        c.uint.preencode(state, m.maxByteLength)
        break
      case t.SHAREDARRAYBUFFER:
        c.arraybuffer.preencode(state, m.backingStore)
        break
      case t.GROWABLESHAREDARRAYBUFFER:
        c.arraybuffer.preencode(state, m.backingStore)
        c.uint.preencode(state, m.maxByteLength)
        break
      case t.TYPEDARRAY:
        c.uint.preencode(state, m.view)
        value.preencode(state, m.buffer)
        c.uint.preencode(state, m.byteOffset)
        c.uint.preencode(state, m.byteLength)
        c.uint.preencode(state, m.length)
        break
      case t.DATAVIEW:
        value.preencode(state, m.buffer)
        c.uint.preencode(state, m.byteOffset)
        c.uint.preencode(state, m.byteLength)
        break
      case t.MAP:
        entries.preencode(state, m.data)
        break
      case t.SET:
        values.preencode(state, m.data)
        break
      case t.ARRAY:
        c.uint.preencode(state, m.length)
        properties.preencode(state, m.properties)
        break
      case t.OBJECT:
        properties.preencode(state, m.properties)
        break
      case t.REFERENCE:
        break
      case t.URL:
        c.string.preencode(state, m.href)
        break
      case t.BUFFER:
        value.preencode(state, m.buffer)
        c.uint.preencode(state, m.byteOffset)
        c.uint.preencode(state, m.byteLength)
        break
      case t.SERIALIZABLE:
        c.uint.preencode(state, m.interface)
        value.preencode(state, m.value)
        break
    }
  },
  encode(state, m) {
    c.uint.encode(state, m.type)

    switch (m.type) {
      case t.UNDEFINED:
      case t.NULL:
      case t.TRUE:
      case t.FALSE:
        return
      case t.NUMBER:
        return c.float64.encode(state, m.value)
      case t.BIGINT:
        return c.bigint.encode(state, m.value)
      case t.STRING:
        return c.string.encode(state, m.value)
      case t.EXTERNAL:
        return c.arraybuffer.encode(state, m.pointer)
      case t.TRANSFER:
        transfers.encode(state, m.transfers)
        value.encode(state, m.value)
        return
    }

    c.uint.encode(state, m.id)

    switch (m.type) {
      case t.DATE:
        c.int.encode(state, m.value)
        break
      case t.REGEXP:
        c.string.encode(state, m.source)
        c.string.encode(state, m.flags)
        break
      case t.ERROR:
        c.uint.encode(state, 'cause' in m ? 1 : 0) // Flags
        c.uint.encode(state, m.name)
        c.string.encode(state, m.message)
        value.encode(state, m.stack)
        if ('cause' in m) value.encode(state, m.cause)
        if (m.name === t.error.AGGREGATE) values.encode(state, m.errors)
        break
      case t.ARRAYBUFFER:
        c.arraybuffer.encode(state, m.data)
        break
      case t.RESIZABLEARRAYBUFFER:
        c.arraybuffer.encode(state, m.data)
        c.uint.encode(state, m.maxByteLength)
        break
      case t.SHAREDARRAYBUFFER:
        c.arraybuffer.encode(state, m.backingStore)
        break
      case t.GROWABLESHAREDARRAYBUFFER:
        c.arraybuffer.encode(state, m.backingStore)
        c.uint.encode(state, m.maxByteLength)
        break
      case t.TYPEDARRAY:
        c.uint.encode(state, m.view)
        value.encode(state, m.buffer)
        c.uint.encode(state, m.byteOffset)
        c.uint.encode(state, m.byteLength)
        c.uint.encode(state, m.length)
        break
      case t.DATAVIEW:
        value.encode(state, m.buffer)
        c.uint.encode(state, m.byteOffset)
        c.uint.encode(state, m.byteLength)
        break
      case t.MAP:
        entries.encode(state, m.data)
        break
      case t.SET:
        values.encode(state, m.data)
        break
      case t.ARRAY:
        c.uint.encode(state, m.length)
        properties.encode(state, m.properties)
        break
      case t.OBJECT:
        properties.encode(state, m.properties)
        break
      case t.REFERENCE:
        break
      case t.URL:
        c.string.encode(state, m.href)
        break
      case t.BUFFER:
        value.encode(state, m.buffer)
        c.uint.encode(state, m.byteOffset)
        c.uint.encode(state, m.byteLength)
        break
      case t.SERIALIZABLE:
        c.uint.encode(state, m.interface)
        value.encode(state, m.value)
        break
    }
  },
  decode(state) {
    const type = c.uint.decode(state)

    switch (type) {
      case t.UNDEFINED:
      case t.NULL:
      case t.TRUE:
      case t.FALSE:
        return {
          type
        }
      case t.NUMBER:
        return {
          type,
          value: c.float64.decode(state)
        }
      case t.BIGINT:
        return {
          type,
          value: c.bigint.decode(state)
        }
      case t.STRING:
        return {
          type,
          value: c.string.decode(state)
        }
      case t.EXTERNAL:
        return {
          type,
          pointer: c.arraybuffer.decode(state)
        }
      case t.TRANSFER:
        return {
          type,
          transfers: transfers.decode(state),
          value: value.decode(state)
        }
    }

    const id = c.uint.decode(state)

    switch (type) {
      case t.DATE:
        return {
          type,
          id,
          value: c.int.decode(state)
        }
      case t.REGEXP:
        return {
          type,
          id,
          source: c.string.decode(state),
          flags: c.string.decode(state)
        }
      case t.ERROR: {
        const flags = c.uint.decode(state)

        const hasCause = (flags & 1) !== 0

        const m = {
          type,
          id,
          name: c.uint.decode(state),
          message: c.string.decode(state),
          stack: value.decode(state)
        }

        if (hasCause) m.cause = value.decode(state)

        if (m.name === t.error.AGGREGATE) m.errors = values.decode(state)

        return m
      }
      case t.ARRAYBUFFER:
        return {
          type,
          id,
          owned: true,
          data: c.arraybuffer.decode(state)
        }
      case t.RESIZABLEARRAYBUFFER:
        return {
          type,
          id,
          owned: true,
          data: c.arraybuffer.decode(state),
          maxByteLength: c.uint.decode(state)
        }
      case t.SHAREDARRAYBUFFER:
        return {
          type,
          id,
          backingStore: c.arraybuffer.decode(state)
        }
      case t.GROWABLESHAREDARRAYBUFFER:
        return {
          type,
          id,
          backingStore: c.arraybuffer.decode(state),
          maxByteLength: c.uint.decode(state)
        }
      case t.TYPEDARRAY:
        return {
          type,
          id,
          view: c.uint.decode(state),
          buffer: value.decode(state),
          byteOffset: c.uint.decode(state),
          byteLength: c.uint.decode(state),
          length: c.uint.decode(state)
        }
      case t.DATAVIEW:
        return {
          type,
          id,
          buffer: value.decode(state),
          byteOffset: c.uint.decode(state),
          byteLength: c.uint.decode(state)
        }
      case t.MAP:
        return {
          type,
          id,
          data: entries.decode(state)
        }
      case t.SET:
        return {
          type,
          id,
          data: values.decode(state)
        }
      case t.ARRAY:
        return {
          type,
          id,
          length: c.uint.decode(state),
          properties: properties.decode(state)
        }
      case t.OBJECT:
        return {
          type,
          id,
          properties: properties.decode(state)
        }
      case t.REFERENCE:
        return {
          type,
          id
        }
      case t.URL:
        return {
          type,
          id,
          href: c.string.decode(state)
        }
      case t.BUFFER:
        return {
          type,
          id,
          buffer: value.decode(state),
          byteOffset: c.uint.decode(state),
          byteLength: c.uint.decode(state)
        }
      case t.SERIALIZABLE:
        return {
          type,
          id,
          interface: c.uint.decode(state),
          value: value.decode(state)
        }
    }
  }
}

const values = c.array(value)

exports.preencode = function preencode(state, m) {
  header.preencode(state)
  value.preencode(state, m)
}

exports.encode = function encode(state, m) {
  header.encode(state)
  value.encode(state, m)
}

exports.decode = function decode(state) {
  header.decode(state)
  return value.decode(state)
}
module.exports = {
  VERSION: 0,

  type: {
    // Primitive types
    UNDEFINED: 0,
    NULL: 1,
    TRUE: 2,
    FALSE: 3,
    NUMBER: 4,
    BIGINT: 5,
    STRING: 6,

    // Builtin objects
    DATE: 7,
    REGEXP: 8,
    ERROR: 9,

    // Builtin binary data objects
    ARRAYBUFFER: 10,
    RESIZABLEARRAYBUFFER: 11,
    SHAREDARRAYBUFFER: 12,
    GROWABLESHAREDARRAYBUFFER: 13,
    TYPEDARRAY: 14,
    DATAVIEW: 15,

    // Builtin composite objects
    MAP: 16,
    SET: 17,
    ARRAY: 18,
    OBJECT: 19,

    // Object references
    REFERENCE: 20,

    // Object transfers
    TRANSFER: 21,

    // Platform objects
    URL: 22,
    BUFFER: 23,
    EXTERNAL: 24,
    SERIALIZABLE: 25,
    TRANSFERABLE: 26,

    typedarray: {
      UINT8ARRAY: 1,
      UINT8CLAMPEDARRAY: 2,
      INT8ARRAY: 3,
      UINT16ARRAY: 4,
      INT16ARRAY: 5,
      UINT32ARRAY: 6,
      INT32ARRAY: 7,
      BIGUINT64ARRAY: 8,
      BIGINT64ARRAY: 9,
      FLOAT16ARRAY: 12,
      FLOAT32ARRAY: 10,
      FLOAT64ARRAY: 11
    },

    error: {
      AGGREGATE: 1,
      EVAL: 2,
      RANGE: 3,
      REFERENCE: 4,
      SYNTAX: 5,
      TYPE: 6,
      URI: 7
    }
  }
}
module.exports = class DataCloneError extends Error {
  constructor(msg, code, fn = DataCloneError) {
    super(`${code}: ${msg}`)
    this.code = code

    if (Error.captureStackTrace) {
      Error.captureStackTrace(this, fn)
    }
  }

  get name() {
    return 'DataCloneError'
  }

  static INVALID_VERSION(msg) {
    return new DataCloneError(msg, 'INVALID_VERSION', DataCloneError.INVALID_VERSION)
  }

  static UNSERIALIZABLE_TYPE(msg) {
    return new DataCloneError(msg, 'UNSERIALIZABLE_TYPE', DataCloneError.UNSERIALIZABLE_TYPE)
  }

  static UNTRANSFERABLE_TYPE(msg) {
    return new DataCloneError(msg, 'UNTRANSFERABLE_TYPE', DataCloneError.UNTRANSFERABLE_TYPE)
  }

  static ALREADY_TRANSFERRED(msg) {
    return new DataCloneError(msg, 'ALREADY_TRANSFERRED', DataCloneError.ALREADY_TRANSFERRED)
  }

  static INVALID_REFERENCE(msg) {
    return new DataCloneError(msg, 'INVALID_REFERENCE', DataCloneError.INVALID_REFERENCE)
  }

  static INVALID_INTERFACE(msg) {
    return new DataCloneError(msg, 'INVALID_INTERFACE', DataCloneError.INVALID_INTERFACE)
  }
}
{
  "name": "bare-structured-clone",
  "version": "1.6.0",
  "description": "Structured cloning algorithm for JavaScript",
  "exports": {
    "./package": "./package.json",
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    },
    "./global": {
      "types": "./global.d.ts",
      "default": "./global.js"
    },
    "./constants": {
      "types": "./lib/constants.d.ts",
      "default": "./lib/constants.js"
    },
    "./errors": {
      "types": "./lib/errors.d.ts",
      "default": "./lib/errors.js"
    }
  },
  "files": [
    "index.js",
    "index.d.ts",
    "global.js",
    "global.d.ts",
    "binding.c",
    "binding.js",
    "CMakeLists.txt",
    "lib",
    "prebuilds"
  ],
  "addon": true,
  "scripts": {
    "format": "prettier --write . && lunte --fix",
    "lint": "prettier --check . && lunte",
    "test": "brittle-bare --coverage test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-structured-clone.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-structured-clone/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-structured-clone#readme",
  "engines": {
    "bare": ">=1.2.0"
  },
  "dependencies": {
    "bare-buffer": "^3.6.0",
    "bare-type": "^1.1.0",
    "bare-url": "^2.4.0",
    "compact-encoding": "^3.0.1"
  },
  "devDependencies": {
    "brittle": "^3.1.1",
    "cmake-bare": "^1.1.6",
    "lunte": "^1.6.0",
    "prettier": "^3.3.3",
    "prettier-config-holepunch": "^2.0.0"
  }
}
module.exports = require.addon()
const EventEmitter = require('bare-events')
const Pipe = require('bare-pipe')
const os = require('bare-os')
const env = require('bare-env')
const { isURL, fileURLToPath } = require('bare-url')
const binding = require('./binding')
const Channel = require('./lib/channel')
const JSONFramer = require('./lib/framers/json')
const AdvancedFramer = require('./lib/framers/advanced')
const constants = require('./lib/constants')
const errors = require('./lib/errors')

exports.Subprocess = class Subprocess extends EventEmitter {
  constructor() {
    super()

    this.spawnfile = null
    this.spawnargs = []
    this.pid = null
    this.stdio = []
    this.exitCode = null
    this.signalCode = null
    this.killed = false
    this.channel = undefined

    this._closing = []
    this._handle = binding.init(this, this._onexit)
  }

  get connected() {
    return this.channel !== undefined
  }

  get stdin() {
    return this.stdio[0] || null
  }

  get stdout() {
    return this.stdio[1] || null
  }

  get stderr() {
    return this.stdio[2] || null
  }

  ref() {
    for (const pipe of this.stdio) {
      if (pipe && typeof pipe.ref === 'function') pipe.ref()
    }

    binding.ref(this._handle)
  }

  unref() {
    for (const pipe of this.stdio) {
      if (pipe && typeof pipe.unref === 'function') pipe.unref()
    }

    binding.unref(this._handle)
  }

  kill(signum = constants.SIGTERM) {
    if (typeof signum === 'string') {
      if (signum in constants === false) {
        throw errors.UNKNOWN_SIGNAL('Unknown signal: ' + signum)
      }

      signum = constants[signum]
    }

    binding.kill(this._handle, signum)

    this.killed = true
  }

  send(message, handle = null, cb) {
    if (typeof handle === 'function') {
      cb = handle
      handle = null
    }

    if (this.channel === undefined) {
      const err = errors.NO_IPC_CHANNEL('Subprocess has no IPC channel')
      if (cb) queueMicrotask(() => cb(err))
      return false
    }

    return this.channel.send(message, handle, cb)
  }

  disconnect() {
    if (this.channel === undefined) return
    this.channel.disconnect()
  }

  _flush() {
    for (const pipe of this.stdio) {
      if (pipe && typeof pipe.resume === 'function') pipe.resume()
    }
  }

  async _onexit(code, signal) {
    if (signal) {
      for (const [name, val] of Object.entries(os.constants.signals)) {
        if (signal === val) {
          this.signalCode = name
          break
        }
      }
    } else {
      this.exitCode = code
    }

    this.emit('exit', this.exitCode, this.signalCode)

    queueMicrotask(this._flush.bind(this)) // Defer to provide a last chance to consume

    await Promise.all(this._closing)

    this.emit('close', this.exitCode, this.signalCode)
  }
}

exports.ChildProcess = exports.Subprocess // For Node.js compatibility

exports.constants = constants
exports.errors = errors

exports.spawn = function spawn(file, args, opts) {
  if (Array.isArray(args)) {
    args = [...args]
  } else if (args === null) {
    args = []
  } else {
    opts = args
    args = []
  }

  if (!opts) opts = {}

  args = args.map(String)

  let {
    cwd = defaultCwd(),
    env = defaultEnv(),
    stdio = [],
    detached = false,
    shell = false,
    uid = -1,
    gid = -1,
    windowsHide = false,
    windowsVerbatimArguments = false,
    serialization = 'json'
  } = opts

  if (serialization !== 'json' && serialization !== 'advanced' && serialization !== 'binary') {
    throw errors.UNKNOWN_SERIALIZATION_MODE(`Unknown serialization mode '${serialization}'`)
  }

  file = toPath(file)
  cwd = toPath(cwd)

  if (shell) {
    const command = [file, ...args].join(' ')

    if (Bare.platform === 'win32') {
      if (typeof shell === 'string') {
        file = shell
      } else {
        file = env.comspec || 'cmd.exe'
      }

      if (/^(.*\\)?cmd(\.exe)?$/i.test(file)) {
        args = ['/d', '/s', '/c', `"${command}"`]
        windowsVerbatimArguments = true
      } else {
        args = ['-c', command]
      }
    } else {
      if (typeof shell === 'string') {
        file = shell
      } else if (Bare.platform === 'android') {
        file = '/system/bin/sh'
      } else {
        file = '/bin/sh'
      }

      args = ['-c', command]
    }
  }

  const pairs = []

  for (const [key, value] of Object.entries(env)) {
    pairs.push(`${key}=${value}`)
  }

  if (Array.isArray(stdio)) {
    stdio = [...stdio]
  } else if (typeof stdio === 'string') {
    stdio = [stdio, stdio, stdio]
  } else {
    stdio = []
  }

  const subprocess = new exports.Subprocess()

  subprocess.spawnfile = file
  subprocess.spawnargs = args

  let ipcSlot = -1

  for (let i = 0, n = Math.max(3, stdio.length); i < n; i++) {
    subprocess.stdio[i] = null

    let fd = stdio[i] || 'pipe'

    if (fd === 'inherit') fd = i < 3 ? i : 'ignore'

    if (fd === 'ignore') {
      stdio[i] = { flags: binding.UV_IGNORE }
    } else if (fd === 'pipe' || fd === 'overlapped') {
      const pipe = new Pipe()

      pipe._onspawn(i !== 0 /* Readable */, i === 0 || i > 2 /* Writable */)

      if (i > 0) {
        subprocess._closing.push(new Promise((resolve) => pipe.once('close', resolve)))
      }

      let flags = binding.UV_CREATE_PIPE | binding.UV_READABLE_PIPE | binding.UV_WRITABLE_PIPE

      if (fd === 'overlapped') flags |= binding.UV_NONBLOCK_PIPE

      stdio[i] = { flags, pipe: pipe._handle }

      subprocess.stdio[i] = pipe
    } else if (fd === 'ipc') {
      if (ipcSlot !== -1) {
        throw errors.IPC_CHANNEL_ALREADY_DEFINED('Only one IPC channel per subprocess')
      }

      ipcSlot = i

      const pipe = new Pipe({ ipc: true })

      pipe._onspawn(true, true)

      subprocess._closing.push(new Promise((resolve) => pipe.once('close', resolve)))

      const flags = binding.UV_CREATE_PIPE | binding.UV_READABLE_PIPE | binding.UV_WRITABLE_PIPE

      stdio[i] = { flags, pipe: pipe._handle }

      subprocess.stdio[i] = pipe
    } else {
      stdio[i] = { flags: binding.UV_INHERIT_FD, fd }
    }
  }

  if (ipcSlot !== -1) {
    pairs.push(`BARE_CHANNEL_FD=${ipcSlot}`, `BARE_CHANNEL_SERIALIZATION_MODE=${serialization}`)
  }

  subprocess.pid = binding.spawn(
    subprocess._handle,
    file,
    args,
    cwd,
    pairs,
    stdio,
    detached,
    uid,
    gid,
    windowsHide,
    windowsVerbatimArguments
  )

  if (ipcSlot !== -1 && serialization !== 'binary') {
    let framer

    if (serialization === 'json') {
      framer = new JSONFramer()
    } else if (serialization === 'advanced') {
      framer = new AdvancedFramer()
    }

    subprocess.channel = new Channel(subprocess, subprocess.stdio[ipcSlot], framer)

    subprocess.once('disconnect', () => {
      subprocess.channel = undefined
    })
  }

  return subprocess
}

exports.spawnSync = function spawn(file, args, opts) {
  if (Array.isArray(args)) {
    args = [...args]
  } else if (args === null) {
    args = []
  } else {
    opts = args
    args = []
  }

  if (!opts) opts = {}

  let {
    cwd = defaultCwd(),
    env = defaultEnv(),
    input = null,
    stdio = [],
    detached = false,
    shell = false,
    uid = -1,
    gid = -1,
    windowsHide = false,
    windowsVerbatimArguments = false,
    maxBuffer = 1024 * 1024
  } = opts

  file = toPath(file)
  cwd = toPath(cwd)

  if (shell) {
    const command = [file, ...args].join(' ')

    if (Bare.platform === 'win32') {
      if (typeof shell === 'string') {
        file = shell
      } else {
        file = env.comspec || 'cmd.exe'
      }

      if (/^(.*\\)?cmd(\.exe)?$/i.test(file)) {
        args = ['/d', '/s', '/c', `"${command}"`]
        windowsVerbatimArguments = true
      } else {
        args = ['-c', command]
      }
    } else {
      if (typeof shell === 'string') {
        file = shell
      } else if (Bare.platform === 'android') {
        file = '/system/bin/sh'
      } else {
        file = '/bin/sh'
      }

      args = ['-c', command]
    }
  }

  const pairs = []

  for (const [key, value] of Object.entries(env)) {
    pairs.push(`${key}=${value}`)
  }

  if (Array.isArray(stdio)) {
    stdio = [...stdio]
  } else if (typeof stdio === 'string') {
    stdio = [stdio, stdio, stdio]
  } else {
    stdio = []
  }

  const subprocess = new exports.Subprocess()

  if (input) {
    stdio[0] = {
      flags: binding.UV_CREATE_PIPE | binding.UV_READABLE_PIPE,
      buffer: input
    }

    subprocess.stdio[0] = null
  }

  for (let i = input ? 1 : 0, n = Math.max(3, stdio.length); i < n; i++) {
    subprocess.stdio[i] = null

    let fd = stdio[i] || 'pipe'

    if (fd === 'inherit') fd = i < 3 ? i : 'ignore'

    if (fd === 'ignore') {
      stdio[i] = { flags: binding.UV_IGNORE }
    } else if (fd === 'pipe') {
      const buffer = Buffer.alloc(maxBuffer)

      stdio[i] = {
        flags: binding.UV_CREATE_PIPE | binding.UV_WRITABLE_PIPE,
        buffer,
        written: 0
      }

      if (i > 0) subprocess.stdio[i] = buffer
    } else if (typeof fd === 'number') {
      stdio[i] = { flags: binding.UV_INHERIT_FD, fd }
    }
  }

  let error
  try {
    subprocess.pid = binding.spawnSync(
      subprocess._handle,
      file,
      args,
      cwd,
      pairs,
      stdio,
      detached,
      uid,
      gid,
      windowsHide,
      windowsVerbatimArguments
    )
  } catch (err) {
    error = err
  }

  if (error) return { status: null, signal: null, output: null, pid: 0, error }

  for (let i = 1, n = stdio.length; i < n; i++) {
    if (stdio[i].flags & binding.UV_WRITABLE_PIPE) {
      subprocess.stdio[i] = subprocess.stdio[i].subarray(0, stdio[i].written)
    }
  }

  return {
    status: subprocess.exitCode,
    signal: subprocess.signalCode,
    output: subprocess.stdio,
    pid: subprocess.pid,
    stdout: subprocess.stdout,
    stderr: subprocess.stderr
  }
}

function defaultCwd() {
  return os.cwd()
}

function defaultEnv() {
  return env
}

function toPath(filepath) {
  if (typeof filepath !== 'string') {
    if (isURL(filepath)) filepath = fileURLToPath(filepath)
    else filepath = filepath.toString()
  }

  return filepath
}
const Pipe = require('bare-pipe')
const { Socket } = require('bare-tcp')
const errors = require('./errors')

module.exports = class SubprocessChannel {
  constructor(subprocess, pipe, framer) {
    this._subprocess = subprocess
    this._pipe = pipe
    this._framer = framer
    this._connected = true
    this._pendingHandles = []

    this._onmessage = this._onmessage.bind(this)

    pipe
      .on('data', this._ondata.bind(this))
      .on('handle', this._onhandle.bind(this))
      .on('error', this._onerror.bind(this))
      .on('end', this._ondisconnect.bind(this))
      .on('close', this._ondisconnect.bind(this))
  }

  get connected() {
    return this._connected
  }

  send(message, handle = null, cb) {
    if (typeof handle === 'function') {
      cb = handle
      handle = null
    }

    if (this._connected === false) {
      const err = errors.CHANNEL_DISCONNECTED('Channel is disconnected')
      if (cb) queueMicrotask(() => cb(err))
      return false
    }

    const data = this._framer.encode({ message, hasHandle: handle !== null })

    if (handle !== null) return this._pipe.write(data, handle, cb)
    return this._pipe.write(data, cb)
  }

  disconnect() {
    if (this._connected === false) return
    this._pipe.end()
  }

  ref() {
    this._pipe.ref()
    return this
  }

  unref() {
    this._pipe.unref()
    return this
  }

  _onhandle(type) {
    let target

    if (type === Pipe.constants.handle.NAMED_PIPE) {
      target = new Pipe()
    } else if (type === Pipe.constants.handle.TCP) {
      target = new Socket()
    } else return

    this._pipe.accept(target)
    this._pendingHandles.push(target)
  }

  _ondata(chunk) {
    try {
      this._framer.push(chunk, this._onmessage)
    } catch (err) {
      this._subprocess.emit('error', err)
    }
  }

  _onerror(err) {
    this._subprocess.emit('error', err)
  }

  _onmessage(envelope) {
    const { message, hasHandle } = envelope
    const handle = hasHandle ? this._pendingHandles.shift() || null : null
    this._subprocess.emit('message', message, handle)
  }

  _ondisconnect() {
    if (this._connected === false) return
    this._connected = false

    for (const target of this._pendingHandles) target.destroy()
    this._pendingHandles = []

    this._subprocess.emit('disconnect')
  }
}
const os = require('bare-os')

module.exports = os.constants.signals
module.exports = class SubprocessError extends Error {
  constructor(msg, code, fn = SubprocessError, opts) {
    super(`${code}: ${msg}`, opts)
    this.code = code

    if (Error.captureStackTrace) {
      Error.captureStackTrace(this, fn)
    }
  }

  get name() {
    return 'SubprocessError'
  }

  static UNKNOWN_SIGNAL(msg) {
    return new SubprocessError(msg, 'UNKNOWN_SIGNAL', SubprocessError.UNKNOWN_SIGNAL)
  }

  static IPC_CHANNEL_ALREADY_DEFINED(msg) {
    return new SubprocessError(
      msg,
      'IPC_CHANNEL_ALREADY_DEFINED',
      SubprocessError.IPC_CHANNEL_ALREADY_DEFINED
    )
  }

  static NO_IPC_CHANNEL(msg) {
    return new SubprocessError(msg, 'NO_IPC_CHANNEL', SubprocessError.NO_IPC_CHANNEL)
  }

  static CHANNEL_DISCONNECTED(msg) {
    return new SubprocessError(msg, 'CHANNEL_DISCONNECTED', SubprocessError.CHANNEL_DISCONNECTED)
  }

  static INVALID_MESSAGE(msg, cause) {
    return new SubprocessError(msg, 'INVALID_MESSAGE', SubprocessError.INVALID_MESSAGE, {
      cause
    })
  }

  static MESSAGE_TOO_LARGE(msg) {
    return new SubprocessError(msg, 'MESSAGE_TOO_LARGE', SubprocessError.MESSAGE_TOO_LARGE)
  }

  static UNKNOWN_SERIALIZATION_MODE(msg) {
    return new SubprocessError(
      msg,
      'UNKNOWN_SERIALIZATION_MODE',
      SubprocessError.UNKNOWN_SERIALIZATION_MODE
    )
  }
}
const structuredClone = require('bare-structured-clone')
const errors = require('../errors')

const MAX_FRAME_SIZE = 0xffffffff

module.exports = class SubprocessAdvancedFramer {
  constructor() {
    this._buffer = []
    this._buffered = 0
    this._bodyLength = -1
  }

  encode(message) {
    const serialized = structuredClone.serialize(message)

    const state = { start: 0, end: 4, buffer: null }

    structuredClone.preencode(state, serialized)

    const bodyLength = state.end - 4

    if (bodyLength > MAX_FRAME_SIZE) {
      throw errors.MESSAGE_TOO_LARGE(
        `IPC message exceeds the maximum frame size of ${MAX_FRAME_SIZE} bytes`
      )
    }

    const frame = Buffer.alloc(state.end)
    state.buffer = frame

    frame.writeUInt32LE(bodyLength, 0)
    state.start += 4

    structuredClone.encode(state, serialized)

    return frame
  }

  push(chunk, onmessage) {
    this._buffer.push(chunk)
    this._buffered += chunk.byteLength

    this._consume(onmessage)
  }

  _consume(onmessage) {
    while (true) {
      if (this._bodyLength === -1) {
        if (this._buffered < 4) return

        const header = this._read(4)
        this._bodyLength = header.readUInt32LE(0)
      }

      if (this._buffered < this._bodyLength) return

      const body = this._read(this._bodyLength)
      this._bodyLength = -1

      let message
      try {
        const state = { start: 0, end: body.byteLength, buffer: body }

        message = structuredClone.deserialize(structuredClone.decode(state))
      } catch (err) {
        throw errors.INVALID_MESSAGE('Invalid IPC message', err)
      }

      onmessage(message)
    }
  }

  _read(n) {
    this._buffered -= n

    if (this._buffer[0].byteLength === n) {
      return this._buffer.shift()
    }

    if (this._buffer[0].byteLength > n) {
      const head = this._buffer[0]
      this._buffer[0] = head.subarray(n)

      return head.subarray(0, n)
    }

    const parts = []
    let remaining = n

    while (remaining > 0) {
      const head = this._buffer[0]

      if (head.byteLength <= remaining) {
        parts.push(head)
        remaining -= head.byteLength
        this._buffer.shift()
      } else {
        parts.push(head.subarray(0, remaining))
        this._buffer[0] = head.subarray(remaining)
        remaining = 0
      }
    }

    return Buffer.concat(parts)
  }
}
const errors = require('../errors')

const MAX_FRAME_SIZE = 0xffffffff

module.exports = class SubprocessJSONFramer {
  constructor() {
    this._buffer = []
    this._buffered = 0
  }

  encode(message) {
    return Buffer.from(JSON.stringify(message) + '\n')
  }

  push(chunk, onmessage) {
    this._buffer.push(chunk)
    this._buffered += chunk.byteLength

    if (chunk.indexOf(0x0a /* \n */) !== -1) {
      this._consume(onmessage)
    }

    if (this._buffered > MAX_FRAME_SIZE) {
      throw errors.MESSAGE_TOO_LARGE(
        `IPC message exceeds the maximum frame size of ${MAX_FRAME_SIZE} bytes`
      )
    }
  }

  _consume(onmessage) {
    while (true) {
      let bufferIdx = -1
      let byteIdx = -1
      let offset = 0

      for (let i = 0; i < this._buffer.length; i++) {
        const idx = this._buffer[i].indexOf(0x0a /* \n */)

        if (idx !== -1) {
          bufferIdx = i
          byteIdx = idx
          break
        }

        offset += this._buffer[i].byteLength
      }

      if (bufferIdx === -1) return

      const lineLength = offset + byteIdx

      if (lineLength > MAX_FRAME_SIZE) {
        throw errors.MESSAGE_TOO_LARGE(
          `IPC message exceeds the maximum frame size of ${MAX_FRAME_SIZE} bytes`
        )
      }

      let line

      if (bufferIdx === 0) {
        line = this._buffer[0].subarray(0, byteIdx).toString('utf8')
      } else {
        const parts = []
        for (let i = 0; i < bufferIdx; i++) parts.push(this._buffer[i])
        parts.push(this._buffer[bufferIdx].subarray(0, byteIdx))

        line = Buffer.concat(parts).toString('utf8')
      }

      this._buffered -= lineLength + 1

      const remainder = this._buffer[bufferIdx].subarray(byteIdx + 1)
      this._buffer.splice(0, bufferIdx + 1)
      if (remainder.byteLength > 0) this._buffer.unshift(remainder)

      let message
      try {
        message = JSON.parse(line)
      } catch (err) {
        throw errors.INVALID_MESSAGE('Invalid IPC message', err)
      }

      onmessage(message)
    }
  }
}
{
  "name": "bare-subprocess",
  "version": "6.1.0",
  "description": "Native process spawning for JavaScript",
  "exports": {
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    },
    "./package": "./package.json",
    "./constants": {
      "types": "./lib/constants.d.ts",
      "default": "./lib/constants.js"
    },
    "./errors": {
      "types": "./lib/errors.d.ts",
      "default": "./lib/errors.js"
    },
    "./parent": {
      "types": "./lib/parent.d.ts",
      "default": "./lib/parent.js"
    }
  },
  "files": [
    "index.js",
    "index.d.ts",
    "binding.c",
    "binding.js",
    "CMakeLists.txt",
    "lib",
    "prebuilds"
  ],
  "addon": true,
  "scripts": {
    "format": "prettier --write . && lunte --fix",
    "lint": "prettier --check . && lunte",
    "test": "brittle-bare --coverage test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-subprocess.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-subprocess/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-subprocess#readme",
  "engines": {
    "bare": ">=1.7.0"
  },
  "dependencies": {
    "bare-env": "^3.0.0",
    "bare-events": "^2.5.4",
    "bare-os": "^3.0.1",
    "bare-pipe": "^4.2.0",
    "bare-structured-clone": "^1.5.2",
    "bare-tcp": "^2.4.1",
    "bare-url": "^2.2.2"
  },
  "devDependencies": {
    "bare-abort": "^2.0.13",
    "bare-assert": "^1.0.2",
    "bare-buffer": "^3.0.2",
    "bare-fs": "^4.1.2",
    "bare-path": "^3.0.0",
    "brittle": "^3.2.1",
    "cmake-bare": "^1.1.7",
    "lunte": "^1.6.0",
    "prettier": "^3.4.1",
    "prettier-config-holepunch": "^2.0.0"
  },
  "peerDependencies": {
    "bare-buffer": "*"
  },
  "peerDependenciesMeta": {
    "bare-buffer": {
      "optional": true
    }
  }
}
module.exports = require.addon()
const EventEmitter = require('bare-events')
const { Duplex, isFinished, isReadable, isWritable } = require('bare-stream')
const dns = require('bare-dns')
const binding = require('./binding')
const constants = require('./lib/constants')
const errors = require('./lib/errors')
const ip = require('./lib/ip')

const defaultReadBufferSize = 65536
const empty = Buffer.alloc(0)

const ipcHandle = Symbol.for('bare.ipc.handle')
const ipcAccept = Symbol.for('bare.ipc.accept')

exports.Socket = class TCPSocket extends Duplex {
  constructor(opts = {}) {
    const { readBufferSize = defaultReadBufferSize, allowHalfOpen = true, eagerOpen = true } = opts

    validateInteger(readBufferSize, 'Read buffer size', 1, 0x7fffffff)

    super({ eagerOpen: !!eagerOpen })

    this._state = 0

    this._allowHalfOpen = !!allowHalfOpen

    this._keepAlive = false
    this._keepAliveInitialDelay = 0
    this._noDelay = false

    this._localAddress = null
    this._remoteAddress = null

    this._pendingOpen = null
    this._pendingWrite = null
    this._pendingWriteBatch = null
    this._pendingFinal = null
    this._pendingDestroy = null

    this._timer = null
    this._timeout = 0

    this._buffer = Buffer.alloc(readBufferSize)

    this._addresses = null
    this._errors = null

    this._handle = binding.init(
      this._buffer,
      this,
      noop,
      this._onconnect,
      this._onreset,
      this._onread,
      this._onwrite,
      this._onfinal,
      this._onclose
    )
  }

  get connecting() {
    return (this._state & constants.state.CONNECTING) !== 0
  }

  get pending() {
    if (this._state & (constants.state.CLOSING | constants.state.CLOSED)) return true

    return (this._state & constants.state.CONNECTED) === 0
  }

  get timeout() {
    return this._timeout || undefined // For Node.js compatibility
  }

  get readyState() {
    if (this._state & constants.state.CONNECTING) return 'opening'

    if (this._state & constants.state.CONNECTED) {
      const readable = isReadable(this)
      const writable = isWritable(this) && !isFinished(this)

      if (readable && writable) return 'open'
      if (readable) return 'readOnly'
      if (writable) return 'writeOnly'
    }

    return 'closed'
  }

  get keepAlive() {
    return this._keepAlive
  }

  get keepAliveInitialDelay() {
    return this._keepAliveInitialDelay
  }

  get noDelay() {
    return this._noDelay
  }

  get localAddress() {
    if (this._localAddress) return this._localAddress.address
  }

  get localFamily() {
    if (this._localAddress) return `IPv${this._localAddress.family}`
  }

  get localPort() {
    if (this._localAddress) return this._localAddress.port
  }

  get remoteAddress() {
    if (this._remoteAddress) return this._remoteAddress.address
  }

  get remoteFamily() {
    if (this._remoteAddress) return `IPv${this._remoteAddress.family}`
  }

  get remotePort() {
    if (this._remoteAddress) return this._remoteAddress.port
  }

  get [ipcHandle]() {
    return this._handle
  }

  address() {
    if (this._localAddress === null) return null

    const { address, family, port } = this._localAddress

    return { address, family: `IPv${family}`, port }
  }

  connect(port, host = 'localhost', opts = {}, onconnect) {
    if (this._state & constants.state.CLOSING) {
      throw errors.SOCKET_IS_CLOSED('Socket is closed')
    }

    if (this._state & (constants.state.CONNECTING | constants.state.CONNECTED)) {
      throw errors.SOCKET_ALREADY_CONNECTED('Socket is already connected')
    }

    if (typeof host === 'function') {
      onconnect = host
      host = 'localhost'
    } else if (typeof opts === 'function') {
      onconnect = opts
      opts = {}
    }

    if (typeof port === 'object' && port !== null) {
      opts = port
      port = defaultTo(opts.port, 0)
      host = opts.host || 'localhost'
    }

    if (!host) host = 'localhost'

    validateHost(host, 'Host')
    validatePort(port)

    const {
      lookup = dns.lookup,
      hints,
      family = 0,
      keepAlive = false,
      keepAliveInitialDelay = 0,
      noDelay = false,
      timeout
    } = opts

    if (keepAlive) this.setKeepAlive(true, keepAliveInitialDelay)
    if (noDelay) this.setNoDelay(true)
    if (timeout) this.setTimeout(timeout)

    this._state |= constants.state.CONNECTING

    if (ip.isIP(host) === 0) {
      lookup(host, { all: true, family, hints }, (err, addresses) => {
        if (this._state & constants.state.CLOSING) return

        if (!err) err = lookupError(addresses, host)

        if (err) {
          this._state &= ~constants.state.CONNECTING

          this.emit('lookup', err, null, 0, host)

          if (this._pendingOpen) this._continueOpen(err)
          else this.destroy(err)
          return
        }

        for (const { address, family } of addresses) {
          this.emit('lookup', null, address, family, host)
        }

        const [{ address }, ...rest] = addresses

        if (rest.length > 0) {
          this._addresses = rest.map(({ address }) => [port, address])

          this._errors = []
        }

        this._connect(port, address, onconnect)
      })

      return this
    }

    this._connect(port, host, onconnect)

    return this
  }

  _connect(port, host, onconnect) {
    this._state |= constants.state.CONNECTING

    try {
      binding.connect(this._handle, port, host, ip.isIP(host))

      if (onconnect) this.once('connect', onconnect)
    } catch (err) {
      queueMicrotask(() => this._failConnect(err))
    }
  }

  open(fd, opts = {}, onconnect) {
    if (this._state & constants.state.CLOSING) {
      throw errors.SOCKET_IS_CLOSED('Socket is closed')
    }

    if (this._state & (constants.state.CONNECTING | constants.state.CONNECTED)) {
      throw errors.SOCKET_ALREADY_CONNECTED('Socket is already connected')
    }

    if (typeof opts === 'function') {
      onconnect = opts
      opts = {}
    }

    if (typeof fd === 'object' && fd !== null) {
      opts = fd
      fd = opts.fd
    }

    validateFd(fd)

    const { keepAlive = false, keepAliveInitialDelay = 0, noDelay = false, timeout } = opts

    if (keepAlive) this.setKeepAlive(true, keepAliveInitialDelay)
    if (noDelay) this.setNoDelay(true)
    if (timeout) this.setTimeout(timeout)

    try {
      binding.open(this._handle, fd)

      this._updateAddresses()

      this._state |= constants.state.CONNECTED

      if (this._keepAlive) this.setKeepAlive(this._keepAlive, this._keepAliveInitialDelay)
      if (this._noDelay) this.setNoDelay(this._noDelay)

      if (onconnect) this.once('connect', onconnect)
    } catch (err) {
      queueMicrotask(() => {
        if (this._pendingOpen) this._continueOpen(err)
        else this.destroy(err)
      })

      return this
    }

    this._continueOpen()

    queueMicrotask(() => {
      if (this._state & constants.state.CLOSING) return

      this.emit('connect')
    })

    return this
  }

  setKeepAlive(enable = false, delay = 0) {
    if (typeof enable === 'number') {
      delay = enable
      enable = true
    }

    validateInteger(delay, 'Keep alive initial delay', 0, 0x7fffffff)

    enable = !!enable

    this._keepAlive = enable
    this._keepAliveInitialDelay = delay

    if (this._state & constants.state.CONNECTED) {
      binding.keepalive(this._handle, enable, Math.floor(delay / 1000))
    }

    return this
  }

  setNoDelay(enable = true) {
    enable = !!enable

    this._noDelay = enable

    if (this._state & constants.state.CONNECTED) {
      binding.nodelay(this._handle, enable)
    }

    return this
  }

  setTimeout(ms, ontimeout) {
    validateInteger(ms, 'Timeout', 0, 0x7fffffff)

    clearTimeout(this._timer)

    this._timer = null

    if (ms === 0) {
      if (ontimeout) this.removeListener('timeout', ontimeout)
    } else {
      if (ontimeout) this.once('timeout', ontimeout)

      this._timer = setTimeout(() => this.emit('timeout'), ms)
      this._timer.unref()
    }

    this._timeout = ms

    return this
  }

  ref() {
    this._state &= ~constants.state.UNREFED

    if (this._state & constants.state.CLOSING) return this

    binding.ref(this._handle)

    return this
  }

  unref() {
    this._state |= constants.state.UNREFED

    if (this._state & constants.state.CLOSING) return this

    binding.unref(this._handle)

    return this
  }

  [ipcAccept]() {
    this._onaccept()
  }

  _open(cb) {
    if (this._state & constants.state.CONNECTED) return cb(null)

    this._pendingOpen = cb
  }

  _read() {
    if ((this._state & constants.state.READING) === 0) {
      this._state |= constants.state.READING

      binding.resume(this._handle)
    }
  }

  _writev(batch, cb) {
    this._pendingWrite = cb
    this._pendingWriteBatch = batch

    try {
      coerceBatch(batch)

      binding.writev(
        this._handle,
        batch.map(({ chunk }) => chunk)
      )
    } catch (err) {
      this._continueWrite(err)
    }
  }

  _final(cb) {
    this._pendingFinal = cb

    try {
      binding.end(this._handle)
    } catch (err) {
      this._onfinal(err)
    }
  }

  _predestroy() {
    if (this._state & constants.state.CLOSING) return
    this._state |= constants.state.CLOSING
    this._state &= ~constants.state.CONNECTING

    binding.close(this._handle)
  }

  _destroy(err, cb) {
    if (this._state & constants.state.CLOSED) return cb(err)

    this._pendingDestroy = cb

    if (this._state & constants.state.CLOSING) return
    this._state |= constants.state.CLOSING
    this._state &= ~constants.state.CONNECTING

    binding.close(this._handle)
  }

  _continueOpen(err) {
    if (this._pendingOpen === null) return
    const cb = this._pendingOpen
    this._pendingOpen = null
    cb(err)
  }

  _continueWrite(err) {
    if (this._pendingWrite === null) return
    const cb = this._pendingWrite
    this._pendingWrite = null
    this._pendingWriteBatch = null
    cb(err)
  }

  _continueFinal(err) {
    if (this._pendingFinal === null) return
    const cb = this._pendingFinal
    this._pendingFinal = null
    cb(err)
  }

  _continueDestroy() {
    if (this._pendingDestroy === null) return
    const cb = this._pendingDestroy
    this._pendingDestroy = null
    cb(null)
  }

  _reset() {
    this._state &= constants.state.UNREFED

    this._localAddress = null
    this._remoteAddress = null

    binding.reset(this._handle)
  }

  _updateAddresses() {
    try {
      this._localAddress = binding.address(this._handle, true)
    } catch {
      this._localAddress = null
    }

    try {
      this._remoteAddress = binding.address(this._handle, false)
    } catch {
      this._remoteAddress = null
    }
  }

  _failConnect(err) {
    if (this._state & constants.state.CLOSING) return

    if (this._addresses !== null) {
      this._errors.push(err)

      if (this._addresses.length > 0) return this._reset()

      err = this._errors.length === 1 ? this._errors[0] : new AggregateError(this._errors)
    }

    this._state &= ~constants.state.CONNECTING

    if (this._pendingOpen) this._continueOpen(err)
    else this.destroy(err)
  }

  _onconnect(err) {
    if (err) {
      this._failConnect(err)
      return
    }

    this._updateAddresses()

    this._state |= constants.state.CONNECTED
    this._state &= ~constants.state.CONNECTING

    if (this._keepAlive) this.setKeepAlive(this._keepAlive, this._keepAliveInitialDelay)
    if (this._noDelay) this.setNoDelay(this._noDelay)

    this._continueOpen()

    this.emit('connect')
  }

  _onaccept() {
    this._updateAddresses()

    this._state |= constants.state.CONNECTED

    if (this._keepAlive) this.setKeepAlive(this._keepAlive, this._keepAliveInitialDelay)
    if (this._noDelay) this.setNoDelay(this._noDelay)

    this._continueOpen()
  }

  _onreset(err) {
    if (err) {
      this._errors.push(err)
      this.destroy(this._errors.length === 1 ? this._errors[0] : new AggregateError(this._errors))
      return
    }

    if (this._state & constants.state.UNREFED) binding.unref(this._handle)

    this._connect(...this._addresses.shift())
  }

  _onread(err, read) {
    if (this._timer) this._timer.refresh()

    if (err) {
      this.destroy(err)
      return
    }

    if (read === 0) {
      this.push(null)
      if (this._allowHalfOpen === false) this.end()
      return
    }

    // Unpooled, as a read too small to be given a buffer of its own would
    // otherwise pin the whole pool it came from for as long as its consumer
    // holds on to it.
    const copy = Buffer.allocUnsafeSlow(read)
    copy.set(this._buffer.subarray(0, read))

    if (this.push(copy) === false && this.destroying === false) {
      this._state &= ~constants.state.READING

      binding.pause(this._handle)
    }
  }

  _onwrite(err) {
    if (this._timer) this._timer.refresh()

    this._continueWrite(err)
  }

  _onfinal(err) {
    this._continueFinal(err === null || err.code === 'ENOTCONN' ? null : err)
  }

  _onclose() {
    this._state |= constants.state.CLOSED

    clearTimeout(this._timer)

    this._continueOpen()
    this._continueDestroy()
  }
}

exports.Server = class TCPServer extends EventEmitter {
  constructor(opts = {}, onconnection) {
    if (typeof opts === 'function') {
      onconnection = opts
      opts = {}
    }

    super()

    const {
      readBufferSize = defaultReadBufferSize,
      allowHalfOpen = true,
      keepAlive = false,
      keepAliveInitialDelay = 0,
      noDelay = false,
      pauseOnConnect = false,
      maxConnections = Infinity
    } = opts

    validateInteger(readBufferSize, 'Read buffer size', 1, 0x7fffffff)
    validateMaxConnections(maxConnections)

    this._state = 0

    this._readBufferSize = readBufferSize
    this._allowHalfOpen = !!allowHalfOpen
    this._keepAlive = !!keepAlive
    this._keepAliveInitialDelay = keepAliveInitialDelay
    this._noDelay = !!noDelay
    this._pauseOnConnect = !!pauseOnConnect
    this._maxConnections = maxConnections

    this._address = null
    this._connections = new Set()

    this._attempt = 0

    this._error = null
    this._handle = null

    if (onconnection) this.on('connection', onconnection)
  }

  get listening() {
    return (this._state & constants.state.BOUND) !== 0
  }

  get closing() {
    return (this._state & constants.state.CLOSING) !== 0
  }

  get connections() {
    return this._connections
  }

  get maxConnections() {
    return this._maxConnections
  }

  set maxConnections(value) {
    validateMaxConnections(value)

    this._maxConnections = value
  }

  address() {
    if ((this._state & constants.state.BOUND) === 0) return null

    const { address, family, port } = this._address

    return { address, family: `IPv${family}`, port }
  }

  listen(port = 0, host = 'localhost', backlog = 511, opts = {}, onlistening) {
    if (this._state & constants.state.CLOSING) {
      throw errors.SERVER_IS_CLOSED('Server is closed')
    }

    if (this._state & (constants.state.BINDING | constants.state.BOUND)) {
      throw errors.SERVER_ALREADY_LISTENING('Server is already listening')
    }

    if (typeof port === 'function') {
      onlistening = port
      port = 0
    } else if (typeof host === 'function') {
      onlistening = host
      host = 'localhost'
    } else if (typeof backlog === 'function') {
      onlistening = backlog
      backlog = 511
    } else if (typeof opts === 'function') {
      onlistening = opts
      opts = {}
    }

    if (typeof port === 'object' && port !== null) {
      opts = port
      port = defaultTo(opts.port, 0)
      host = opts.host || 'localhost'
      backlog = defaultTo(opts.backlog, 511)
    }

    if (!host) host = 'localhost'
    if (backlog === null || backlog === 0) backlog = 511

    validateHost(host, 'Host')
    validateInteger(backlog, 'Backlog', 0, 0x7fffffff)
    validatePort(port)

    this._state |= constants.state.BINDING
    this._state &= ~constants.state.CLOSED

    const attempt = ++this._attempt

    const { lookup = dns.lookup, hints, family = 0 } = opts

    const type = ip.isIP(host)

    if (type === 0) {
      lookup(host, { family, hints }, (err, address, family) => {
        if (attempt !== this._attempt) return

        this._state &= ~constants.state.BINDING

        if (!err) err = resolvedHostError(address)

        if (err) {
          this.emit('lookup', err, null, 0, host)

          return this.emit('error', err)
        }

        this.emit('lookup', null, address, family, host)

        this.listen(port, address, backlog, { ...opts, family }, onlistening)
      })

      return this
    }

    this._handle = binding.init(
      empty,
      this,
      this._onconnection,
      noop,
      noop,
      noop,
      noop,
      noop,
      this._onclose
    )

    if (this._state & constants.state.UNREFED) binding.unref(this._handle)

    try {
      binding.bind(this._handle, port, host, type, backlog)

      this._address = binding.address(this._handle, true)

      this._state |= constants.state.BOUND
      this._state &= ~constants.state.BINDING

      if (onlistening) this.once('listening', onlistening)

      queueMicrotask(() => {
        if (this._state & constants.state.CLOSING) return

        this.emit('listening')
      })
    } catch (err) {
      this._error = err

      binding.close(this._handle)
    }

    return this
  }

  close(onclose) {
    if (this._state & constants.state.CLOSED) {
      if (onclose) queueMicrotask(onclose)

      return this
    }

    if (onclose) this.once('close', onclose)

    if (this._state & constants.state.CLOSING) return this
    this._state |= constants.state.CLOSING
    this._state &= ~(constants.state.BINDING | constants.state.BOUND)

    this._attempt++

    if (this._handle !== null) binding.close(this._handle)
    else this._closeMaybe()

    return this
  }

  ref() {
    this._state &= ~constants.state.UNREFED

    if (this._handle !== null) binding.ref(this._handle)

    return this
  }

  unref() {
    this._state |= constants.state.UNREFED

    if (this._handle !== null) binding.unref(this._handle)

    return this
  }

  _closeMaybe() {
    if ((this._state & constants.state.CLOSING) === 0) return
    if (this._state & constants.state.CLOSED) return
    if (this._handle !== null || this._connections.size > 0) return

    this._state |= constants.state.CLOSED
    this._state &= ~constants.state.CLOSING

    queueMicrotask(() => this.emit('close'))
  }

  _onconnection(err) {
    if (err) {
      this.emit('error', err)
      return
    }

    if (this._state & constants.state.CLOSING) return

    const overLimit = this._maxConnections > 0 && this._connections.size >= this._maxConnections

    const socket = new exports.Socket({
      readBufferSize: overLimit ? 1 : this._readBufferSize,
      allowHalfOpen: this._allowHalfOpen,
      eagerOpen: !this._pauseOnConnect
    })

    let info = null
    try {
      binding.accept(this._handle, socket._handle)

      socket._onaccept()

      if (overLimit) {
        info = {
          localAddress: socket.localAddress,
          localPort: socket.localPort,
          localFamily: socket.localFamily,
          remoteAddress: socket.remoteAddress,
          remotePort: socket.remotePort,
          remoteFamily: socket.remoteFamily
        }

        socket.destroy()
      } else {
        this._connections.add(socket)

        if (this._keepAlive) socket.setKeepAlive(this._keepAlive, this._keepAliveInitialDelay)
        if (this._noDelay) socket.setNoDelay(this._noDelay)

        socket.on('close', () => {
          this._connections.delete(socket)
          this._closeMaybe()
        })
      }
    } catch (err) {
      socket.destroy()

      this.emit('error', err)
      return
    }

    if (info !== null) this.emit('drop', info)
    else this.emit('connection', socket)
  }

  _onclose() {
    const err = this._error

    this._state &= ~(constants.state.BINDING | constants.state.BOUND)
    this._error = null
    this._handle = null
    this._address = null

    if (err) this.emit('error', err)

    this._closeMaybe()
  }
}

exports.constants = constants
exports.errors = errors

exports.isIP = ip.isIP
exports.isIPv4 = ip.isIPv4
exports.isIPv6 = ip.isIPv6

exports.createConnection = function createConnection(port, host, opts, onconnect) {
  if (typeof host === 'function') {
    onconnect = host
    host = 'localhost'
  } else if (typeof opts === 'function') {
    onconnect = opts
    opts = {}
  }

  if (typeof port === 'object' && port !== null) {
    opts = port
    port = defaultTo(opts.port, 0)
    host = opts.host || 'localhost'
  }

  return new exports.Socket(opts).connect(port, host, opts, onconnect)
}

// For Node.js compatibility
exports.connect = exports.createConnection

exports.createServer = function createServer(opts, onconnection) {
  return new exports.Server(opts, onconnection)
}

exports.socketpair = function socketpair() {
  return binding.socketpair()
}

function resolvedHostError(address) {
  if (typeof address !== 'string') {
    return errors.INVALID_HOST(`Resolved address must be a string, got ${typeof address}`)
  }

  if (ip.isIP(address) === 0) {
    return errors.INVALID_HOST(`Resolved address must be an IP address, got "${address}"`)
  }

  const length = Buffer.byteLength(address)

  if (length > constants.address.MAX_LENGTH) {
    return errors.INVALID_HOST(
      `Resolved address must be at most ${constants.address.MAX_LENGTH} bytes, got ${length}`
    )
  }

  return null
}

function lookupError(addresses, host) {
  if (!Array.isArray(addresses)) {
    return errors.INVALID_HOST(`Resolved addresses must be an array, got ${typeof addresses}`)
  }

  if (addresses.length === 0) {
    const err = new Error(`No address found for host "${host}"`)
    err.code = 'ENOTFOUND'

    return err
  }

  for (const entry of addresses) {
    if (typeof entry !== 'object' || entry === null) {
      return errors.INVALID_HOST(`Resolved addresses must contain objects, got ${typeof entry}`)
    }

    const err = resolvedHostError(entry.address)

    if (err) return err
  }

  return null
}

function validateHost(host, name) {
  if (typeof host !== 'string') {
    throw errors.INVALID_HOST(`${name} must be a string, got ${typeof host}`)
  }

  if (ip.isIP(host) === 0) return

  validateAddressLength(host, name)
}

function validateAddressLength(address, name) {
  const length = Buffer.byteLength(address)

  if (length > constants.address.MAX_LENGTH) {
    throw errors.INVALID_HOST(
      `${name} must be at most ${constants.address.MAX_LENGTH} bytes, got ${length}`
    )
  }
}

function validatePort(port) {
  if (typeof port !== 'number') {
    throw errors.INVALID_PORT(`Port must be a number, got ${typeof port}`)
  }

  if (!Number.isInteger(port) || port < 0 || port > 0xffff) {
    throw errors.INVALID_PORT(`Port must be an integer between 0 and 65535, got ${port}`)
  }
}

function validateMaxConnections(value) {
  if (typeof value !== 'number') {
    throw errors.INVALID_ARGUMENT(`Max connections must be a number, got ${typeof value}`)
  }

  if (value === Infinity) return

  if (!Number.isInteger(value) || value < 0 || value > 0x7fffffff) {
    throw errors.INVALID_ARGUMENT(
      `Max connections must be a non-negative integer or Infinity, got ${value}`
    )
  }
}

function validateFd(fd) {
  if (typeof fd !== 'number') {
    throw errors.INVALID_FD(`File descriptor must be a number, got ${typeof fd}`)
  }

  if (!Number.isInteger(fd) || fd < 0 || fd > 0x7fffffff) {
    throw errors.INVALID_FD(
      `File descriptor must be an integer between 0 and ${0x7fffffff}, got ${fd}`
    )
  }
}

function validateInteger(value, name, min, max) {
  if (typeof value !== 'number') {
    throw errors.INVALID_ARGUMENT(`${name} must be a number, got ${typeof value}`)
  }

  if (!Number.isInteger(value) || value < min || value > max) {
    throw errors.INVALID_ARGUMENT(
      `${name} must be an integer between ${min} and ${max}, got ${value}`
    )
  }
}

function coerceBatch(batch) {
  for (let i = 0; i < batch.length; i++) {
    const chunk = batch[i].chunk

    if (ArrayBuffer.isView(chunk) === false) {
      throw errors.INVALID_ARGUMENT(`Chunk must be a string or a view, got ${typeof chunk}`)
    }

    batch[i].chunk = Buffer.coerce(chunk)
  }
}

function defaultTo(value, fallback) {
  return value === undefined || value === null ? fallback : value
}

function noop() {}
const binding = require('../binding')

module.exports = {
  state: {
    CONNECTING: 0x1,
    CONNECTED: 0x2,
    BINDING: 0x4,
    BOUND: 0x8,
    READING: 0x10,
    CLOSING: 0x20,
    CLOSED: 0x40,
    UNREFED: 0x80
  },
  address: {
    MAX_LENGTH: binding.MAX_ADDRESS_LENGTH
  }
}
module.exports = class TCPError extends Error {
  constructor(msg, fn = TCPError, code = fn.name) {
    super(`${code}: ${msg}`)
    this.code = code

    if (Error.captureStackTrace) {
      Error.captureStackTrace(this, fn)
    }
  }

  get name() {
    return 'TCPError'
  }

  static SOCKET_ALREADY_CONNECTED(msg) {
    return new TCPError(msg, TCPError.SOCKET_ALREADY_CONNECTED)
  }

  static SOCKET_IS_CLOSED(msg) {
    return new TCPError(msg, TCPError.SOCKET_IS_CLOSED)
  }

  static SERVER_ALREADY_LISTENING(msg) {
    return new TCPError(msg, TCPError.SERVER_ALREADY_LISTENING)
  }

  static SERVER_IS_CLOSED(msg) {
    return new TCPError(msg, TCPError.SERVER_IS_CLOSED)
  }

  static INVALID_ARGUMENT(msg) {
    return new TCPError(msg, TCPError.INVALID_ARGUMENT)
  }

  static INVALID_FD(msg) {
    return new TCPError(msg, TCPError.INVALID_FD)
  }

  static INVALID_HOST(msg = 'Unrecognizable host format') {
    return new TCPError(msg, TCPError.INVALID_HOST)
  }

  static INVALID_PORT(msg) {
    return new TCPError(msg, TCPError.INVALID_PORT)
  }
}
// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.

const v4Seg = '(?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])'
const v4Str = `(${v4Seg}[.]){3}${v4Seg}`
const IPv4Pattern = new RegExp(`^${v4Str}$`)

const v6Seg = '(?:[0-9a-fA-F]{1,4})'
const IPv6Pattern = new RegExp(
  '^(' +
    `(?:${v6Seg}:){7}(?:${v6Seg}|:)|` +
    `(?:${v6Seg}:){6}(?:${v4Str}|:${v6Seg}|:)|` +
    `(?:${v6Seg}:){5}(?::${v4Str}|(:${v6Seg}){1,2}|:)|` +
    `(?:${v6Seg}:){4}(?:(:${v6Seg}){0,1}:${v4Str}|(:${v6Seg}){1,3}|:)|` +
    `(?:${v6Seg}:){3}(?:(:${v6Seg}){0,2}:${v4Str}|(:${v6Seg}){1,4}|:)|` +
    `(?:${v6Seg}:){2}(?:(:${v6Seg}){0,3}:${v4Str}|(:${v6Seg}){1,5}|:)|` +
    `(?:${v6Seg}:){1}(?:(:${v6Seg}){0,4}:${v4Str}|(:${v6Seg}){1,6}|:)|` +
    `(?::((?::${v6Seg}){0,5}:${v4Str}|(?::${v6Seg}){1,7}|:))` +
    ')(%[0-9a-zA-Z-.:]{1,})?$'
)

const isIPv4 = (exports.isIPv4 = function isIPv4(host) {
  return IPv4Pattern.test(host)
})

const isIPv6 = (exports.isIPv6 = function isIPv6(host) {
  return IPv6Pattern.test(host)
})

exports.isIP = function isIP(host) {
  if (isIPv4(host)) return 4
  if (isIPv6(host)) return 6
  return 0
}
{
  "name": "bare-tcp",
  "version": "2.6.1",
  "description": "Native TCP sockets for JavaScript",
  "exports": {
    "./package": "./package.json",
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    },
    "./constants": {
      "types": "./lib/constants.d.ts",
      "default": "./lib/constants.js"
    },
    "./errors": {
      "types": "./lib/errors.d.ts",
      "default": "./lib/errors.js"
    }
  },
  "files": [
    "index.js",
    "index.d.ts",
    "binding.c",
    "binding.js",
    "CMakeLists.txt",
    "lib",
    "prebuilds"
  ],
  "addon": true,
  "scripts": {
    "format": "prettier --write . && lunte --fix",
    "lint": "prettier --check . && lunte",
    "test": "brittle-bare --coverage test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-tcp.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-tcp/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-tcp#readme",
  "engines": {
    "bare": ">=1.16.0"
  },
  "dependencies": {
    "bare-dns": "^2.0.4",
    "bare-events": "^2.5.4",
    "bare-stream": "^2.6.4"
  },
  "devDependencies": {
    "bare-os": "^3.9.3",
    "bare-pipe": "^4.2.3",
    "brittle": "^3.19.1",
    "cmake-bare": "^1.1.6",
    "lunte": "^1.8.0",
    "prettier": "^3.4.1",
    "prettier-config-holepunch": "^2.0.0"
  },
  "peerDependencies": {
    "bare-pipe": "*"
  },
  "peerDependenciesMeta": {
    "bare-pipe": {
      "optional": true
    }
  }
}
module.exports = require.addon()
const { Readable, Writable } = require('bare-stream')
const Signal = require('bare-signals')
const binding = require('./binding')
const constants = require('./lib/constants')
const errors = require('./lib/errors')

const defaultReadBufferSize = 65536
const empty = Buffer.alloc(0)
const modes = new Set([constants.mode.NORMAL, constants.mode.RAW, constants.mode.IO])

exports.ReadStream = class TTYReadStream extends Readable {
  constructor(fd, opts = {}) {
    super(opts)

    const { readBufferSize = defaultReadBufferSize } = opts

    validateFd(fd)
    validateInteger(readBufferSize, 'Read buffer size', 1, 0x7fffffff)

    this._fd = fd
    this._state = 0
    this._buffer = Buffer.alloc(readBufferSize)

    this._pendingDestroy = null

    this._handle = binding.init(fd, this._buffer, this, noop, this._onread, this._onclose, false)
  }

  get fd() {
    return this._fd
  }

  get isTTY() {
    return true
  }

  setMode(mode) {
    validateMode(mode)

    this._alive()

    binding.setMode(this._handle, mode)

    return this
  }

  setRawMode(enabled) {
    return this.setMode(enabled ? constants.mode.RAW : constants.mode.NORMAL)
  }

  _alive() {
    if (this._state & constants.state.CLOSING) {
      throw errors.STREAM_IS_CLOSED('Stream is closed')
    }
  }

  _read() {
    if ((this._state & constants.state.READING) === 0) {
      this._state |= constants.state.READING

      binding.resume(this._handle)
    }
  }

  _predestroy() {
    this._close()
  }

  _destroy(err, cb) {
    if (this._state & constants.state.CLOSED) return cb(err)

    this._pendingDestroy = cb

    this._close()
  }

  _close() {
    if (this._state & constants.state.CLOSING) return
    this._state |= constants.state.CLOSING

    binding.close(this._handle)
  }

  _continueDestroy() {
    if (this._pendingDestroy === null) return
    const cb = this._pendingDestroy
    this._pendingDestroy = null
    cb(null)
  }

  _onread(err, read) {
    if (err) {
      this.destroy(err)
      return
    }

    if (read === 0) {
      this.push(null)
      return
    }

    // Unpooled, as a read too small to be given a buffer of its own would
    // otherwise pin the whole pool it came from for as long as its consumer
    // holds on to it.
    const copy = Buffer.allocUnsafeSlow(read)
    copy.set(this._buffer.subarray(0, read))

    if (this.push(copy) === false && this.destroying === false) {
      this._state &= ~constants.state.READING

      binding.pause(this._handle)
    }
  }

  _onclose() {
    this._state |= constants.state.CLOSED

    this._continueDestroy()
  }
}

exports.WriteStream = class TTYWriteStream extends Writable {
  constructor(fd, opts = {}) {
    super(opts)

    validateFd(fd)

    this._fd = fd
    this._state = 0
    this._size = null

    this._pendingWrite = null
    this._pendingWriteBatch = null
    this._pendingDestroy = null

    this._handle = binding.init(fd, empty, this, this._onwrite, noop, this._onclose, true)

    try {
      this._refreshSize()
    } catch (err) {
      this._close()

      throw err
    }

    if (TTYWriteStream._streams.size === 0) TTYWriteStream._resize.start()

    TTYWriteStream._streams.add(this)
  }

  get fd() {
    return this._fd
  }

  get isTTY() {
    return true
  }

  get columns() {
    return this._size[0]
  }

  get rows() {
    return this._size[1]
  }

  getWindowSize() {
    this._alive()

    return [this._size[0], this._size[1]]
  }

  _refreshSize() {
    const size = binding.getWindowSize(this._handle)

    const changed = this._size === null || size[0] !== this._size[0] || size[1] !== this._size[1]

    this._size = size

    return changed
  }

  _alive() {
    if (this._state & constants.state.CLOSING) {
      throw errors.STREAM_IS_CLOSED('Stream is closed')
    }
  }

  _writev(batch, cb) {
    this._pendingWrite = cb
    this._pendingWriteBatch = batch

    try {
      coerceBatch(batch)

      binding.writev(
        this._handle,
        batch.map(({ chunk }) => chunk)
      )
    } catch (err) {
      this._continueWrite(err)
    }
  }

  _predestroy() {
    this._close()
  }

  _destroy(err, cb) {
    if (this._state & constants.state.CLOSED) return cb(err)

    this._pendingDestroy = cb

    this._close()
  }

  _close() {
    if (this._state & constants.state.CLOSING) return
    this._state |= constants.state.CLOSING

    binding.close(this._handle)

    TTYWriteStream._streams.delete(this)

    if (TTYWriteStream._streams.size === 0) TTYWriteStream._resize.stop()
  }

  _continueWrite(err) {
    if (this._pendingWrite === null) return
    const cb = this._pendingWrite
    this._pendingWrite = null
    this._pendingWriteBatch = null
    cb(err)
  }

  _continueDestroy() {
    if (this._pendingDestroy === null) return
    const cb = this._pendingDestroy
    this._pendingDestroy = null
    cb(null)
  }

  _onwrite(err) {
    this._continueWrite(err)
  }

  _onclose() {
    this._state |= constants.state.CLOSED

    this._continueDestroy()
  }

  _onresize() {
    let changed

    try {
      changed = this._refreshSize()
    } catch (err) {
      this.emit('error', err)
      return
    }

    if (changed) this.emit('resize')
  }

  static _streams = new Set()

  static _resize = new Signal('SIGWINCH')
}

exports.constants = constants

exports.errors = errors

exports.isTTY = function isTTY(fd) {
  return isValidFd(fd) && binding.isTTY(fd)
}

exports.isatty = exports.isTTY // For Node.js compatibility

exports.WriteStream._resize
  .on('signal', () => {
    for (const stream of exports.WriteStream._streams) {
      stream._onresize()
    }
  })
  .unref()

function isValidFd(fd) {
  return typeof fd === 'number' && Number.isInteger(fd) && fd >= 0 && fd <= 0x7fffffff
}

function validateFd(fd) {
  if (typeof fd !== 'number') {
    throw errors.INVALID_FD(`File descriptor must be a number, got ${typeof fd}`)
  }

  if (!isValidFd(fd)) {
    throw errors.INVALID_FD(
      `File descriptor must be an integer between 0 and ${0x7fffffff}, got ${fd}`
    )
  }
}

function validateMode(mode) {
  if (typeof mode !== 'number') {
    throw errors.INVALID_ARGUMENT(`Mode must be a number, got ${typeof mode}`)
  }

  if (!modes.has(mode)) {
    throw errors.INVALID_ARGUMENT(`Mode must be one of ${[...modes].join(', ')}, got ${mode}`)
  }
}

function validateInteger(value, name, min, max) {
  if (typeof value !== 'number') {
    throw errors.INVALID_ARGUMENT(`${name} must be a number, got ${typeof value}`)
  }

  if (!Number.isInteger(value) || value < min || value > max) {
    throw errors.INVALID_ARGUMENT(
      `${name} must be an integer between ${min} and ${max}, got ${value}`
    )
  }
}

function coerceBatch(batch) {
  for (let i = 0; i < batch.length; i++) {
    const chunk = batch[i].chunk

    if (ArrayBuffer.isView(chunk) === false) {
      throw errors.INVALID_ARGUMENT(`Chunk must be a string or a view, got ${typeof chunk}`)
    }

    batch[i].chunk = Buffer.coerce(chunk)
  }
}

function noop() {}
const binding = require('../binding')

module.exports = exports = {
  mode: {
    NORMAL: binding.MODE_NORMAL,
    RAW: binding.MODE_RAW,
    IO: binding.MODE_IO || 0
  },

  state: {
    READING: 0x1,
    CLOSING: 0x2,
    CLOSED: 0x4
  }
}

/** @deprecated Use `mode.NORMAL` */
exports.MODE_NORMAL = exports.mode.NORMAL
/** @deprecated Use `mode.RAW` */
exports.MODE_RAW = exports.mode.RAW
/** @deprecated Use `mode.IO` */
exports.MODE_IO = exports.mode.IO
module.exports = class TTYError extends Error {
  constructor(msg, fn = TTYError, code = fn.name) {
    super(`${code}: ${msg}`)
    this.code = code

    if (Error.captureStackTrace) {
      Error.captureStackTrace(this, fn)
    }
  }

  get name() {
    return 'TTYError'
  }

  static STREAM_IS_CLOSED(msg) {
    return new TTYError(msg, TTYError.STREAM_IS_CLOSED)
  }

  static INVALID_FD(msg) {
    return new TTYError(msg, TTYError.INVALID_FD)
  }

  static INVALID_ARGUMENT(msg) {
    return new TTYError(msg, TTYError.INVALID_ARGUMENT)
  }
}
{
  "name": "bare-tty",
  "version": "5.2.1",
  "description": "Native TTY streams for JavaScript",
  "exports": {
    "./package": "./package.json",
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    },
    "./constants": {
      "types": "./lib/constants.d.ts",
      "default": "./lib/constants.js"
    },
    "./errors": {
      "types": "./lib/errors.d.ts",
      "default": "./lib/errors.js"
    }
  },
  "files": [
    "index.js",
    "index.d.ts",
    "binding.c",
    "binding.js",
    "CMakeLists.txt",
    "lib",
    "prebuilds"
  ],
  "addon": true,
  "scripts": {
    "format": "prettier --write . && lunte --fix",
    "lint": "prettier --check . && lunte",
    "test": "brittle-bare --coverage test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-tty.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-tty/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-tty#readme",
  "engines": {
    "bare": ">=1.16.0"
  },
  "dependencies": {
    "bare-events": "^2.2.0",
    "bare-signals": "^5.0.0",
    "bare-stream": "^2.0.0"
  },
  "devDependencies": {
    "bare-subprocess": "^6.0.0",
    "brittle": "^3.2.1",
    "cmake-bare": "^1.1.6",
    "lunte": "^1.8.0",
    "prettier": "^3.4.1",
    "prettier-config-holepunch": "^2.0.0"
  }
}
module.exports = require.addon()
const binding = require('./binding')

const t = binding.constants

class Type {
  constructor(type) {
    this._type = type
  }

  isUndefined() {
    return this._type === t.UNDEFINED
  }

  isNull() {
    return this._type === t.NULL
  }

  isBoolean() {
    return this._type === t.BOOLEAN
  }

  isNumber() {
    return (this._type & 0xff) === t.NUMBER
  }

  isInt32() {
    return (this._type & (0xff | t.INT32)) === (t.NUMBER | t.INT32)
  }

  isUint32() {
    return (this._type & (0xff | t.UINT32)) === (t.NUMBER | t.UINT32)
  }

  isString() {
    return this._type === t.STRING
  }

  isSymbol() {
    return this._type === t.SYMBOL
  }

  isObject() {
    return (this._type & 0xff) === t.OBJECT
  }

  isArray() {
    return this._type === (t.OBJECT | t.ARRAY)
  }

  isArguments() {
    return this._type === (t.OBJECT | t.ARGUMENTS)
  }

  isDate() {
    return this._type === (t.OBJECT | t.DATE)
  }

  isRegExp() {
    return this._type === (t.OBJECT | t.REGEXP)
  }

  isError() {
    return this._type === (t.OBJECT | t.ERROR)
  }

  isPromise() {
    return this._type === (t.OBJECT | t.PROMISE)
  }

  isProxy() {
    return this._type === (t.OBJECT | t.PROXY)
  }

  isGenerator() {
    return this._type === (t.OBJECT | t.GENERATOR)
  }

  isMap() {
    return this._type === (t.OBJECT | t.MAP)
  }

  isSet() {
    return this._type === (t.OBJECT | t.SET)
  }

  isWeakMap() {
    return this._type === (t.OBJECT | t.WEAK_MAP)
  }

  isWeakSet() {
    return this._type === (t.OBJECT | t.WEAK_SET)
  }

  isWeakRef() {
    return this._type === (t.OBJECT | t.WEAK_REF)
  }

  isArrayBuffer() {
    return this._type === (t.OBJECT | t.ARRAYBUFFER)
  }

  isSharedArrayBuffer() {
    return this._type === (t.OBJECT | t.SHAREDARRAYBUFFER)
  }

  isTypedArray() {
    return (this._type & 0xffff) === (t.OBJECT | t.TYPEDARRAY)
  }

  isInt8Array() {
    return this._type === (t.OBJECT | t.TYPEDARRAY | t.INT8ARRAY)
  }

  isUint8Array() {
    return this._type === (t.OBJECT | t.TYPEDARRAY | t.UINT8ARRAY)
  }

  isUint8ClampedArray() {
    return this._type === (t.OBJECT | t.TYPEDARRAY | t.UINT8CLAMPEDARRAY)
  }

  isInt16Array() {
    return this._type === (t.OBJECT | t.TYPEDARRAY | t.INT16ARRAY)
  }

  isUint16Array() {
    return this._type === (t.OBJECT | t.TYPEDARRAY | t.UINT16ARRAY)
  }

  isInt32Array() {
    return this._type === (t.OBJECT | t.TYPEDARRAY | t.INT32ARRAY)
  }

  isUint32Array() {
    return this._type === (t.OBJECT | t.TYPEDARRAY | t.UINT32ARRAY)
  }

  isFloat16Array() {
    return this._type === (t.OBJECT | t.TYPEDARRAY | t.FLOAT16ARRAY)
  }

  isFloat32Array() {
    return this._type === (t.OBJECT | t.TYPEDARRAY | t.FLOAT32ARRAY)
  }

  isFloat64Array() {
    return this._type === (t.OBJECT | t.TYPEDARRAY | t.FLOAT64ARRAY)
  }

  isBigInt64Array() {
    return this._type === (t.OBJECT | t.TYPEDARRAY | t.BIGINT64ARRAY)
  }

  isBigUint64Array() {
    return this._type === (t.OBJECT | t.TYPEDARRAY | t.BIGUINT64ARRAY)
  }

  isDataView() {
    return this._type === (t.OBJECT | t.DATAVIEW)
  }

  isModuleNamespace() {
    return this._type === (t.OBJECT | t.MODULE_NAMESPACE)
  }

  isFunction() {
    return (this._type & 0xff) === t.FUNCTION
  }

  isAsyncFunction() {
    return (this._type & (0xff | t.ASYNC_FUNCTION)) === (t.FUNCTION | t.ASYNC_FUNCTION)
  }

  isGeneratorFunction() {
    return (this._type & (0xff | t.GENERATOR_FUNCTION)) === (t.FUNCTION | t.GENERATOR_FUNCTION)
  }

  isExternal() {
    return this._type === t.EXTERNAL
  }

  isBigInt() {
    return this._type === t.BIGINT
  }
}

// Classifies a number the way the engine does, but without crossing into the
// addon. A number is an `int32` or a `uint32` if it is an integer within the
// respective range, with negative zero being representable as neither.
function numberType(value) {
  if (!Number.isInteger(value)) return t.NUMBER

  // Negative zero passes both range checks below, so exclude it up front.
  if (value === 0 && 1 / value < 0) return t.NUMBER

  let type = t.NUMBER

  if (value >= -0x80000000 && value <= 0x7fffffff) type |= t.INT32
  if (value >= 0 && value <= 0xffffffff) type |= t.UINT32

  return type
}

module.exports = function type(value) {
  switch (typeof value) {
    case 'undefined':
      return new Type(t.UNDEFINED)
    case 'boolean':
      return new Type(t.BOOLEAN)
    case 'number':
      return new Type(numberType(value))
    case 'string':
      return new Type(t.STRING)
    case 'symbol':
      return new Type(t.SYMBOL)
    case 'object':
      return new Type(value === null ? t.NULL : binding.type(value))
    case 'function':
      return new Type(binding.type(value))
    case 'bigint':
      return new Type(t.BIGINT)
  }
}
{
  "name": "bare-type",
  "version": "1.1.1",
  "description": "Cross-realm type predicates for Bare",
  "exports": {
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    },
    "./package": "./package.json"
  },
  "files": [
    "index.js",
    "index.d.ts",
    "binding.c",
    "binding.js",
    "CMakeLists.txt",
    "prebuilds"
  ],
  "addon": true,
  "scripts": {
    "format": "prettier --write . && lunte --fix",
    "lint": "prettier --check . && lunte",
    "test": "brittle-bare --coverage test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-type.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-type/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-type#readme",
  "engines": {
    "bare": ">=1.2.0"
  },
  "devDependencies": {
    "brittle": "^3.4.0",
    "cmake-bare": "^1.1.6",
    "lunte": "^1.8.4",
    "prettier": "^3.4.1",
    "prettier-config-holepunch": "^2.0.0"
  }
}
module.exports = require.addon()
const path = require('bare-path')
const binding = require('./binding')
const errors = require('./lib/errors')
const URLSearchParams = require('./lib/url-search-params')

const kind = Symbol.for('bare.url.kind')

const isWindows = Bare.platform === 'win32'

// Scratch buffer that the binding writes the parsed component offsets into. A
// single shared buffer is reused across every parse.
//
// The offsets are copied out into fields on the URL immediately after a
// successful parse, so nothing observes the buffer across calls.
const components = new Uint32Array(8)

// The value used for a component that is not present in the URL.
const unset = 0xffffffff

// The schemes the parser treats specially. A URL cannot be switched between a
// special and a non-special scheme, and a backslash only terminates a host for
// the former.
const special = new Set(['ftp', 'file', 'http', 'https', 'ws', 'wss'])

// https://url.spec.whatwg.org/#scheme-start-state
const scheme = /^[a-z][a-z0-9+\-.]*$/

// ASCII tab and newline are removed from input before it is parsed rather than
// percent-encoded like the other C0 controls.
const whitespaceAll = /[\t\n\r]/g

// The characters that terminate a host, and so bound the value the host and
// hostname setters accept.
const hostEnd = /[/\\?#]/
const hostEndOpaque = /[/?#]/

// The delimiters that would let a setter's value escape the component it is
// spliced into. Everything else is left to the reparse, which applies the full
// percent-encode set for the component. As elsewhere in this package, each set
// needs two patterns because test() advances a global pattern's lastIndex.
//
// Credentials are not run through the parser and so keep their tabs and
// newlines, percent-encoded, rather than having them stripped.
const userinfoDelimiter = /[\t\n\r/\\?#@:]/
const userinfoDelimiterAll = /[\t\n\r/\\?#@:]/g

const pathDelimiter = /[?#]/
const pathDelimiterAll = /[?#]/g

// A leading or trailing run of C0 control or space in a value that ends up at
// either end of the href. The parser strips those, but only when parsing a URL
// as a whole, so a setter has to encode its own.
const edges = /^[\u0000-\u0020]+|[\u0000-\u0020]+$/g

const escapes = {
  '\t': '%09',
  '\n': '%0A',
  '\r': '%0D',
  '/': '%2F',
  '\\': '%5C',
  ':': '%3A',
  '?': '%3F',
  '@': '%40',
  '#': '%23'
}

// The characters that pathToFileURL() has to percent-encode itself. A backslash
// is a path separator on Windows and so is left alone there.
const reserved = isWindows ? /[%#?\n\r\t]/ : /[%#?\n\r\t\\]/

class URL {
  static get [kind]() {
    return 0 // Compatibility version
  }

  constructor(input, base, opts = {}) {
    if (arguments.length === 0) throw errors.INVALID_URL()

    input = String(input)

    if (base !== undefined) base = String(base)

    this._href = undefined
    this._schemeEnd = 0
    this._usernameEnd = 0
    this._hostStart = 0
    this._hostEnd = 0
    this._pathStart = 0
    this._queryStart = 0
    this._fragmentStart = 0
    this._params = null

    this._parse(input, base, opts.throw !== false)
  }

  get [kind]() {
    return URL[kind]
  }

  // https://url.spec.whatwg.org/#dom-url-href

  get href() {
    return this._href
  }

  set href(value) {
    // Unlike every other setter, the href setter reports a parse failure rather
    // than leaving the URL untouched.
    this._parse(String(value), null, true)

    if (this._params) this._params._parse(this.search)
  }

  // https://url.spec.whatwg.org/#dom-url-protocol

  get protocol() {
    return this._slice(0, this._schemeEnd) + ':'
  }

  set protocol(value) {
    value = strip(String(value))

    const end = value.indexOf(':')

    if (end !== -1) value = value.slice(0, end)

    value = value.toLowerCase()

    if (!scheme.test(value)) return

    const current = this._slice(0, this._schemeEnd)

    if (special.has(current) !== special.has(value)) return

    if (value === 'file' && (this.username || this.password || this.port)) {
      return
    }

    if (current === 'file' && this._hostStart === this._hostEnd) return

    this._update(this._replace(value, 0, this._schemeEnd))
  }

  // https://url.spec.whatwg.org/#dom-url-username

  get username() {
    return this._slice(this._schemeEnd + 3 /* :// */, this._usernameEnd)
  }

  set username(value) {
    if (cannotHaveCredentialsOrPort(this)) {
      return
    }

    value = encodeUserinfo(String(value))

    if (!hasCredentials(this)) value += '@'

    this._update(this._replace(value, this._schemeEnd + 3 /* :// */, this._usernameEnd))
  }

  // https://url.spec.whatwg.org/#dom-url-password

  get password() {
    return this._href.slice(this._usernameEnd + 1 /* : */, this._hostStart - 1 /* @ */)
  }

  set password(value) {
    if (cannotHaveCredentialsOrPort(this)) {
      return
    }

    value = ':' + encodeUserinfo(String(value))

    let end = this._hostStart - 1 /* @ */

    if (!hasCredentials(this)) {
      value += '@'
      end = this._usernameEnd
    }

    this._update(this._replace(value, this._usernameEnd, end))
  }

  // https://url.spec.whatwg.org/#dom-url-host

  get host() {
    return this._slice(this._hostStart, this._pathStart)
  }

  set host(value) {
    if (hasOpaquePath(this)) {
      return
    }

    const protocol = this._slice(0, this._schemeEnd)

    value = truncateHost(protocol, String(value))

    // An `@` would make the reparse read the value as credentials rather than
    // as a host, so it is rejected outright.
    if (value.includes('@')) return

    const separator = portSeparator(value)

    let end = this._hostEnd

    // A port in the value is parsed separately so that an invalid one leaves
    // the existing port in place rather than rejecting the host along with it.
    if (separator !== -1) {
      // A file URL cannot have a port, so a value carrying one is rejected
      // rather than split.
      if (protocol === 'file') return

      const port = parsePort(value.slice(separator + 1))

      value = value.slice(0, separator)

      if (port !== null) {
        value += port
        end = this._pathStart
      }
    }

    if (value === '' && cannotHaveEmptyHost(protocol)) return

    this._update(this._replace(value, this._hostStart, end))
  }

  // https://url.spec.whatwg.org/#dom-url-hostname

  get hostname() {
    return this._slice(this._hostStart, this._hostEnd)
  }

  set hostname(value) {
    if (hasOpaquePath(this)) {
      return
    }

    const protocol = this._slice(0, this._schemeEnd)

    value = truncateHost(protocol, String(value))

    // A port cannot be set through this setter, and a value that carries one is
    // rejected outright rather than truncated. An `@` would make the reparse
    // read the value as credentials rather than as a host.
    if (value.includes('@') || portSeparator(value) !== -1) return

    if (value === '' && cannotHaveEmptyHost(protocol)) return

    this._update(this._replace(value, this._hostStart, this._hostEnd))
  }

  // https://url.spec.whatwg.org/#dom-url-port

  get port() {
    return this._slice(this._hostEnd + 1 /* : */, this._pathStart)
  }

  set port(value) {
    if (cannotHaveCredentialsOrPort(this)) {
      return
    }

    value = strip(String(value))

    if (value !== '') {
      value = parsePort(value)

      if (value === null) return
    }

    this._update(this._replace(value, this._hostEnd, this._pathStart))
  }

  // https://url.spec.whatwg.org/#dom-url-pathname

  get pathname() {
    return this._slice(this._pathStart, this._queryStart - 1 /* ? */)
  }

  set pathname(value) {
    if (hasOpaquePath(this)) {
      return
    }

    value = encodePath(encodeEdges(String(value)))

    // An empty path is left alone, as only a special scheme is required to have
    // one and the reparse inserts it there.
    if (value !== '' && value[0] !== '/' && value[0] !== '\\') {
      value = '/' + value
    }

    this._update(this._replace(value, this._pathStart, this._queryStart - 1 /* ? */))
  }

  // https://url.spec.whatwg.org/#dom-url-search

  get search() {
    return this._slice(this._queryStart - 1 /* ? */, this._fragmentStart - 1 /* # */)
  }

  set search(value) {
    value = String(value)

    if (value !== '') {
      if (value[0] === '?') value = value.slice(1)

      value = '?' + encodeQuery(encodeEdges(value))
    }

    this._update(
      this._replace(value, this._queryStart - 1 /* ? */, this._fragmentStart - 1 /* # */)
    )
  }

  // https://url.spec.whatwg.org/#dom-url-searchparams

  get searchParams() {
    if (this._params === null) {
      this._params = new URLSearchParams(this.search, this)
    }

    return this._params
  }

  // https://url.spec.whatwg.org/#dom-url-hash

  get hash() {
    return this._slice(this._fragmentStart - 1 /* # */)
  }

  set hash(value) {
    value = String(value)

    // The fragment runs to the end of the URL, so nothing in it can escape into
    // another component and no delimiter needs encoding here.
    if (value !== '') {
      if (value[0] === '#') value = value.slice(1)

      value = '#' + encodeEdges(value)
    }

    this._update(this._replace(value, this._fragmentStart - 1 /* # */))
  }

  toString() {
    return this._href
  }

  toJSON() {
    return this._href
  }

  [Symbol.for('bare.inspect')]() {
    return {
      __proto__: { constructor: URL },

      href: this.href,
      protocol: this.protocol,
      username: this.username,
      password: this.password,
      host: this.host,
      hostname: this.hostname,
      port: this.port,
      pathname: this.pathname,
      search: this.search,
      searchParams: this.searchParams,
      hash: this.hash
    }
  }

  _slice(start, end = this._href.length) {
    return this._href.slice(start, end)
  }

  _replace(replacement, start, end = this._href.length) {
    return this._slice(0, start) + replacement + this._slice(end)
  }

  _parse(input, base, shouldThrow) {
    let href

    try {
      href = binding.parse(input, base || null, components, shouldThrow)
    } catch (err) {
      if (err instanceof TypeError || err.code !== undefined) throw err

      throw errors.INVALID_URL(`Invalid URL '${input}'`, input)
    }

    if (href === undefined) return

    this._href = href
    this._schemeEnd = components[0]
    this._usernameEnd = components[1]
    this._hostStart = components[2]
    this._hostEnd = components[3]
    this._pathStart = components[5]

    const queryStart = components[6]
    const fragmentStart = components[7]

    const end = href.length + 1

    this._queryStart = queryStart === unset ? end : queryStart
    this._fragmentStart = fragmentStart === unset ? end : fragmentStart
  }

  _update(input) {
    try {
      this._parse(input, null, true)
    } catch (err) {
      if (err instanceof TypeError) throw err

      return
    }

    if (this._params) this._params._parse(this.search)
  }
}

module.exports = exports = URL

// https://url.spec.whatwg.org/#url-opaque-path
function hasOpaquePath(url) {
  return url.pathname[0] !== '/'
}

// https://url.spec.whatwg.org/#cannot-have-a-username-password-port
function cannotHaveCredentialsOrPort(url) {
  return url.hostname === '' || url.protocol === 'file:'
}

// Whether the URL carries a userinfo section, and so an `@` before its host.
function hasCredentials(url) {
  return url._hostStart !== url._usernameEnd
}

// Almost no input contains any of these, so it is worth ruling all three out
// before rewriting anything.
function strip(value) {
  if (value.indexOf('\t') === -1 && value.indexOf('\n') === -1 && value.indexOf('\r') === -1) {
    return value
  }

  return value.replace(whitespaceAll, '')
}

// https://url.spec.whatwg.org/#host-state
//
// Host parsing stops at the first character that starts another component, so
// anything from there on is dropped rather than spliced into the host.
function truncateHost(protocol, value) {
  const end = value.search(special.has(protocol) ? hostEnd : hostEndOpaque)

  return end === -1 ? value : value.slice(0, end)
}

// Only a special scheme other than file has to have a host.
function cannotHaveEmptyHost(protocol) {
  return protocol !== 'file' && special.has(protocol)
}

// The index of the colon that separates a host from its port, disregarding the
// colons of an IPv6 address, or -1 if the host carries no port.
function portSeparator(host) {
  return host.indexOf(':', host[0] === '[' ? host.indexOf(']') : 0)
}

// https://url.spec.whatwg.org/#port-state
//
// Parsing stops at the first character that is not a digit, so a value with no
// leading digits, or one that overflows, leaves the port as it was.
function parsePort(value) {
  value = /^\d*/.exec(value)[0]

  if (value === '' || Number(value) > 65535) return null

  return ':' + value
}

function encodeUserinfo(value) {
  if (!userinfoDelimiter.test(value)) return value

  return value.replace(userinfoDelimiterAll, (match) => escapes[match])
}

function encodePath(value) {
  if (!pathDelimiter.test(value)) return value

  return value.replace(pathDelimiterAll, (match) => escapes[match])
}

function encodeQuery(value) {
  if (value.indexOf('#') === -1) return value

  return value.replaceAll('#', '%23')
}

// Percent-encodes a leading or trailing run of C0 control or space, which the
// parser would otherwise strip from a value that lands at either end of the
// href. Every component that can end a URL encodes them anyway, so this only
// brings the encoding forward.
function encodeEdges(value) {
  const len = value.length

  if (len === 0) return value
  if (value.charCodeAt(0) > 0x20 && value.charCodeAt(len - 1) > 0x20) return value

  return value.replace(edges, (match) => {
    let encoded = ''

    for (let i = 0, n = match.length; i < n; i++) {
      encoded += '%' + match.charCodeAt(i).toString(16).padStart(2, '0').toUpperCase()
    }

    return encoded
  })
}

exports.URL = URL
exports.URLSearchParams = URLSearchParams

exports.errors = errors

exports.isURL = function isURL(value) {
  if (value instanceof URL) return true

  return typeof value === 'object' && value !== null && value[kind] === URL[kind]
}

exports.isURLSearchParams = URLSearchParams.isURLSearchParams

// https://url.spec.whatwg.org/#dom-url-parse
exports.parse = function parse(input, base) {
  const url = new URL(input, base, { throw: false })
  return url._href ? url : null
}

// https://url.spec.whatwg.org/#dom-url-canparse
exports.canParse = function canParse(input, base) {
  return binding.canParse(String(input), base ? String(base) : null)
}

exports.fileURLToPath = function fileURLToPath(url) {
  if (typeof url === 'string') {
    url = new URL(url)
  }

  if (url.protocol !== 'file:') {
    throw errors.INVALID_URL_SCHEME('The URL must use the file: protocol')
  }

  if (!isWindows && url.hostname) {
    throw errors.INVALID_FILE_URL_HOST("The file: URL host must be 'localhost' or empty")
  }

  const encoded = url.pathname

  // Every check below looks for a percent encoded sequence, as does the decoding
  // that follows, so a path without any can skip all of them.
  const hasEncoded = encoded.includes('%')

  if (hasEncoded) {
    if (isWindows) {
      if (/%2f|%5c/i.test(encoded)) {
        throw errors.INVALID_FILE_URL_PATH(
          'The file: URL path must not include encoded \\ or / characters'
        )
      }
    } else if (/%2f/i.test(encoded)) {
      throw errors.INVALID_FILE_URL_PATH('The file: URL path must not include encoded / characters')
    }

    if (/%00/i.test(encoded)) {
      throw errors.INVALID_FILE_URL_PATH(
        'The file: URL path must not include encoded NUL characters'
      )
    }
  }

  const pathname = path.normalize(hasEncoded ? decodeURIComponent(encoded) : encoded)

  if (isWindows) {
    if (url.hostname) return '\\\\' + url.hostname + pathname

    const letter = pathname.charCodeAt(1) | 0x20

    if (letter < 0x61 /* a */ || letter > 0x7a /* z */ || pathname.charCodeAt(2) !== 0x3a /* : */) {
      throw errors.INVALID_FILE_URL_PATH('The file: URL path must be absolute')
    }

    return pathname.slice(1)
  }

  return pathname
}

exports.pathToFileURL = function pathToFileURL(pathname) {
  let resolved = path.resolve(pathname)

  if (pathname[pathname.length - 1] === '/') {
    resolved += '/'
  } else if (isWindows && pathname[pathname.length - 1] === '\\') {
    resolved += '\\'
  }

  // Paths hardly ever contain any of these, so one pass to rule them out is
  // cheaper than the six or seven replacements it stands in for.
  if (reserved.test(resolved)) {
    resolved = resolved
      .replaceAll('%', '%25') // Must be first
      .replaceAll('#', '%23')
      .replaceAll('?', '%3f')
      .replaceAll('\n', '%0a')
      .replaceAll('\r', '%0d')
      .replaceAll('\t', '%09')

    if (!isWindows) {
      resolved = resolved.replaceAll('\\', '%5c')
    }
  }

  return new URL('file:' + resolved)
}

exports.format = function format(parts) {
  const { protocol, auth, host, hostname, port, pathname, search, query, hash, slashes } = parts

  let result = ''

  if (typeof protocol === 'string') {
    result += protocol

    if (protocol[protocol.length - 1] !== ':') {
      result += ':'
    }

    if (slashes === true || /https?|ftp|gopher|file/.test(protocol)) {
      result += '//'
    }
  }

  if (typeof auth === 'string') {
    if (host || hostname) result += auth + '@'
  }

  if (typeof host === 'string') result += host
  else {
    result += hostname

    if (port) result += ':' + port
  }

  if (typeof pathname === 'string' && pathname !== '') {
    if (pathname[0] !== '/') result += '/'
    result += pathname
  }

  if (typeof search === 'string') {
    if (search[0] !== '?') result += '?'
    result += search
  } else if (typeof query === 'object' && query !== null) {
    result += '?' + new URLSearchParams(query)
  }

  if (typeof hash === 'string') {
    if (hash[0] !== '#') result += '#'
    result += hash
  }

  return result
}
module.exports = class URLError extends Error {
  constructor(msg, fn = URLError, code = fn.name) {
    super(`${code}: ${msg}`)

    this.code = code

    if (Error.captureStackTrace) Error.captureStackTrace(this, fn)
  }

  get name() {
    return 'URLError'
  }

  static INVALID_URL(msg, input) {
    const err = new URLError(msg, URLError.INVALID_URL)

    err.input = input

    return err
  }

  static INVALID_URL_SCHEME(msg = 'Invalid URL') {
    return new URLError(msg, URLError.INVALID_URL_SCHEME)
  }

  static INVALID_FILE_URL_HOST(msg = 'Invalid file: URL host') {
    return new URLError(msg, URLError.INVALID_FILE_URL_HOST)
  }

  static INVALID_FILE_URL_PATH(msg = 'Invalid file: URL path') {
    return new URLError(msg, URLError.INVALID_FILE_URL_PATH)
  }
}
const kind = Symbol.for('bare.url.search-params.kind')

// The URL each instance writes back to, if any. Kept module private so that the
// setter it drives cannot be pointed at an arbitrary object.
const urls = new WeakMap()

class URLSearchParams {
  static get [kind]() {
    return 0 // Compatibility version
  }

  // https://url.spec.whatwg.org/#dom-urlsearchparams-urlsearchparams
  constructor(init, url = null) {
    this._params = null

    if (url) urls.set(this, url)

    if (typeof init === 'string') {
      this._parse(init)
    } else {
      this._params = new Map()

      if (init) {
        for (const [name, value] of typeof init[Symbol.iterator] === 'function'
          ? init
          : Object.entries(init)) {
          this.append(name, value)
        }
      }
    }
  }

  get [kind]() {
    return URLSearchParams[kind]
  }

  // https://url.spec.whatwg.org/#dom-urlsearchparams-size
  get size() {
    let size = 0

    for (const values of this._params.values()) size += values.length

    return size
  }

  // https://url.spec.whatwg.org/#dom-urlsearchparams-append
  append(name, value = null) {
    if (value === null) return

    let list = this._params.get(name)

    if (list === undefined) {
      list = []
      this._params.set(name, list)
    }

    list.push(value)

    this._update()
  }

  // https://url.spec.whatwg.org/#dom-urlsearchparams-delete
  delete(name, value = null) {
    if (value === null) this._params.delete(name)
    else {
      let list = this._params.get(name)

      if (list === undefined) return

      list = list.filter((found) => found !== value)

      if (list.length === 0) this._params.delete(name)
      else this._params.set(name, list)
    }

    this._update()
  }

  // https://url.spec.whatwg.org/#dom-urlsearchparams-get
  get(name) {
    const list = this._params.get(name)

    if (list === undefined) return null

    return list[0]
  }

  // https://url.spec.whatwg.org/#dom-urlsearchparams-getall
  getAll(name) {
    const list = this._params.get(name)

    if (list === undefined) return []

    return Array.from(list)
  }

  // https://url.spec.whatwg.org/#dom-urlsearchparams-has
  has(name, value = null) {
    const list = this._params.get(name)

    if (list === undefined) return false

    if (value === null) return true

    return list.includes(value)
  }

  // https://url.spec.whatwg.org/#dom-urlsearchparams-set
  set(name, value = null) {
    if (value === null) this._params.delete(name)
    else this._params.set(name, [value])

    this._update()
  }

  toString() {
    return this._serialize()
  }

  toJSON() {
    return [...this]
  }

  *[Symbol.iterator]() {
    for (const [name, values] of this._params) {
      for (const value of values) yield [name, value]
    }
  }

  [Symbol.for('bare.inspect')]() {
    const object = {
      __proto__: { constructor: URLSearchParams }
    }

    for (const [name, values] of this._params) {
      if (values.length === 1) object[name] = values[0]
      else object[name] = values
    }

    return object
  }

  // https://url.spec.whatwg.org/#concept-urlsearchparams-update
  _update() {
    const url = urls.get(this)

    if (url === undefined) return

    url.search = this._serialize()
  }

  // https://url.spec.whatwg.org/#concept-urlencoded-parser
  _parse(input) {
    const params = new Map()

    this._params = params

    const len = input.length

    let start = input.charCodeAt(0) === 0x3f /* ? */ ? 1 : 0

    while (start < len) {
      let end = input.indexOf('&', start)
      if (end === -1) end = len

      if (end !== start) {
        let split = input.indexOf('=', start)
        if (split === -1 || split > end) split = end

        const name = decode(input.slice(start, split))
        const value = split === end ? '' : decode(input.slice(split + 1, end))

        const list = params.get(name)

        if (list === undefined) params.set(name, [value])
        else list.push(value)
      }

      start = end + 1
    }
  }

  // https://url.spec.whatwg.org/#concept-urlencoded-serializer
  _serialize() {
    let result = ''
    let separator = ''

    for (const [name, values] of this._params) {
      // The name is shared by all of its values, so it is only encoded once.
      const encoded = encode(String(name))

      for (const value of values) {
        result += separator + encoded + '=' + encode(String(value))
        separator = '&'
      }
    }

    return result
  }
}

module.exports = exports = URLSearchParams

exports.isURLSearchParams = function isURLSearchParams(value) {
  if (value instanceof URLSearchParams) return true

  return typeof value === 'object' && value !== null && value[kind] === URLSearchParams[kind]
}

// https://url.spec.whatwg.org/#concept-urlencoded-byte-serializer
//
// Everything except ASCII alphanumerics and `*-._` is percent-encoded, and a
// space becomes `+` rather than `%20`. Names and values usually consist entirely
// of characters that are left alone, so a component is tested as a whole before
// any of it is rewritten.
const unencoded = /^[\w*.-]*$/

// As above, but allowing spaces, which need nothing more than turning into `+`.
// Spaces are by far the most common reason for a component to need rewriting at
// all, so recognizing them here spares those components a trip through
// encodeURIComponent().
const spaced = /^[\w*. -]*$/

// The characters that encodeURIComponent() leaves alone but the urlencoded byte
// serializer does not. Two patterns are needed because a global regular
// expression cannot be shared between test() and replace(), as test() advances
// its lastIndex.
const extra = /[!'()~]/
const extraAll = /[!'()~]/g

const escapes = {
  '!': '%21',
  "'": '%27',
  '(': '%28',
  ')': '%29',
  '~': '%7E'
}

// A surrogate pair, or a surrogate that is not part of one and so encodes no
// scalar value. Pairs are matched first so that only the capture group can hold
// a lone surrogate; a lookbehind would say this more directly but isn't
// supported by every engine.
const lone = /[\ud800-\udbff][\udc00-\udfff]|([\ud800-\udbff]|[\udc00-\udfff])/g

function encode(component) {
  if (unencoded.test(component)) return component

  if (spaced.test(component)) return component.replaceAll(' ', '+')

  let encoded

  try {
    encoded = encodeURIComponent(component)
  } catch {
    // encodeURIComponent() rejects lone surrogates, whereas the UTF-8 encoder the
    // serializer is defined in terms of replaces them with U+FFFD.
    encoded = encodeURIComponent(
      component.replace(lone, (match, single) => (single === undefined ? match : '\ufffd'))
    )
  }

  if (encoded.includes('%20')) encoded = encoded.replaceAll('%20', '+')

  if (extra.test(encoded)) encoded = encoded.replace(extraAll, (match) => escapes[match])

  return encoded
}

// https://url.spec.whatwg.org/#percent-decode-after-encoding
function decode(component) {
  if (component.indexOf('+') !== -1) component = component.replaceAll('+', ' ')

  if (component.indexOf('%') === -1) return component

  return decodeURIComponent(component)
}
{
  "name": "bare-url",
  "version": "2.5.4",
  "description": "WHATWG URL implementation for JavaScript",
  "exports": {
    "./package": "./package.json",
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    },
    "./global": {
      "types": "./global.d.ts",
      "default": "./global.js"
    }
  },
  "files": [
    "index.js",
    "index.d.ts",
    "global.js",
    "global.d.ts",
    "binding.c",
    "binding.js",
    "CMakeLists.txt",
    "lib",
    "prebuilds"
  ],
  "addon": true,
  "scripts": {
    "format": "prettier --write . && lunte --fix",
    "lint": "prettier --check . && lunte",
    "test": "brittle-bare --coverage test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-url.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-url/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-url",
  "dependencies": {
    "bare-path": "^3.0.0"
  },
  "devDependencies": {
    "brittle": "^3.3.2",
    "cmake-bare": "^1.8.1",
    "cmake-fetch": "^1.0.0",
    "lunte": "^1.6.0",
    "prettier": "^3.3.3",
    "prettier-config-holepunch": "^2.0.0"
  }
}
const FACTOR = new Uint16Array(8)

function factor4096 (i, n) {
  while (n > 0) {
    const f = i & 4095
    FACTOR[--n] = f
    i = (i - f) / 4096
  }
  return FACTOR
}

module.exports = class BigSparseArray {
  constructor () {
    this.tiny = new TinyArray()
    this.maxLength = 4096
    this.factor = 1
  }

  set (index, val) {
    if (val !== undefined) {
      while (index >= this.maxLength) {
        this.maxLength *= 4096
        this.factor++
        if (!this.tiny.isEmptyish()) {
          const t = new TinyArray()
          t.set(0, this.tiny)
          this.tiny = t
        }
      }
    }

    const f = factor4096(index, this.factor)
    const last = this.factor - 1

    let tiny = this.tiny
    for (let i = 0; i < last; i++) {
      const next = tiny.get(f[i])
      if (next === undefined) {
        if (val === undefined) return
        tiny = tiny.set(f[i], new TinyArray())
      } else {
        tiny = next
      }
    }

    return tiny.set(f[last], val)
  }

  get (index) {
    if (index >= this.maxLength) return

    const f = factor4096(index, this.factor)
    const last = this.factor - 1

    let tiny = this.tiny
    for (let i = 0; i < last; i++) {
      tiny = tiny.get(f[i])
      if (tiny === undefined) return
    }

    return tiny.get(f[last])
  }
}

class TinyArray {
  constructor () {
    this.s = 0
    this.b = new Array(1)
    this.f = new Uint16Array(1)
  }

  isEmptyish () {
    return this.b.length === 1 && this.b[0] === undefined
  }

  get (i) {
    if (this.s === 12) return this.b[i]
    const f = i >>> this.s
    const r = i & (this.b.length - 1)
    return this.f[r] === f ? this.b[r] : undefined
  }

  set (i, v) {
    while (this.s !== 12) {
      const f = i >>> this.s
      const r = i & (this.b.length - 1)
      const o = this.b[r]

      if (o === undefined || f === this.f[r]) {
        this.b[r] = v
        this.f[r] = f
        return v
      }

      this.grow()
    }

    this.b[i] = v
    return v
  }

  grow () {
    const os = this.s
    const ob = this.b
    const of = this.f

    this.s += 4
    this.b = new Array(this.b.length << 4)
    this.f = this.s === 12 ? null : new Uint8Array(this.b.length)

    const m = this.b.length - 1

    for (let or = 0; or < ob.length; or++) {
      if (ob[or] === undefined) continue

      const i = of[or] << os | or
      const f = i >>> this.s
      const r = i & m

      this.b[r] = ob[or]
      if (this.s !== 12) this.f[r] = f
    }
  }
}
{
  "name": "big-sparse-array",
  "version": "1.0.3",
  "description": "A sparse array optimised for low memory whilst still being fast",
  "main": "index.js",
  "dependencies": {},
  "devDependencies": {
    "brittle": "^3.1.1",
    "standard": "^16.0.3"
  },
  "scripts": {
    "test": "standard && brittle test.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/mafintosh/big-sparse-array.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/mafintosh/big-sparse-array/issues"
  },
  "homepage": "https://github.com/mafintosh/big-sparse-array"
}
const sodium = require('sodium-universal')
const b4a = require('b4a')
const assert = require('nanoassert')
const { detectLanguage, loadWordlist } = require('./wordlist')

module.exports = {
  generateEntropy,
  generateMnemonic,
  normalizeMnemonic,
  validateMnemonic,
  mnemonicToEntropy,
  mnemonicToSeed,
  entropyToMnemonic
}

function generateMnemonic ({ entropy = generateEntropy(), language = 'english' } = {}) {
  const wordlist = loadWordlist(language)
  const extended = computeCheckSum(entropy)

  const words = []

  for (const index of uint11Reader(extended)) {
    words.push(wordlist[index])
  }

  const delimiter = language === 'japanese' ? '\u3000' : ' '

  return words.join(delimiter).trim()
}

function entropyToMnemonic (entropy, { language = 'english' } = {}) {
  return generateMnemonic({ entropy, language })
}

function mnemonicToEntropy (mnemonic) {
  const words = mnemonic.replace(/\u3000/g, ' ').trim().split(' ')
  const language = detectLanguage(words)

  if (!language) {
    throw new Error('Language not recognised')
  }

  if (words.length % 3 !== 0) {
    throw new Error('Invalid length')
  }

  const wordlist = loadWordlist(language)

  const indexes = []
  for (const word of words) {
    const index = wordlist.indexOf(word)
    if (index === -1) {
      throw new Error('Bad word')
    }

    indexes.push(index)
  }

  const bits = words.length * 11
  const len = (bits * 32 / 33) >> 3

  const extended = b4a.alloc(Math.ceil(bits / 8))
  const entropy = extended.subarray(0, len)

  uint11Writer(extended, indexes)

  if (!b4a.equals(extended, computeCheckSum(entropy))) {
    throw new Error('Invalid checksum')
  }

  return entropy
}

function normalizeMnemonic (mnemonic) {
  return mnemonic.trim().replace(/\u3000/, ' ').split(/\s+/).map(c => c.toLowerCase()).join(' ')
}

async function mnemonicToSeed (mnemonic, passphrase = '') {
  mnemonic = normalizeMnemonic(mnemonic)

  if (!validateMnemonic(mnemonic)) {
    throw new Error('Invalid mnemonic')
  }

  const input = b4a.from(mnemonic)
  const salt = b4a.from('mnemonic' + passphrase)

  const output = b4a.alloc(64)

  await sodium.extension_pbkdf2_sha512_async(
    output,
    input,
    salt,
    2048,
    64
  )

  return output
}

function validateMnemonic (mnemonic) {
  try {
    mnemonicToEntropy(mnemonic)
  } catch (e) {
    return false
  }
  return true
}

function sha256 (data, output = b4a.alloc(32)) {
  sodium.crypto_hash_sha256(output, data)
  return output
}

function computeCheckSum (seed) {
  assert((seed.byteLength & 4) === 0, 'seed must be a multiple of 4 bytes')

  const len = seed.byteLength
  const cklen = len >> 2 // cksum bits
  const total = len + Math.ceil(cklen / 8)

  const output = b4a.alloc(len + 32)
  output.set(seed)

  const entropy = output.subarray(0, len)
  const cksum = output.subarray(len)

  sha256(entropy, cksum)

  // only append cklen bits
  output[total - 1] &= (0xff ^ (0xff >> cklen))

  return output.subarray(0, total)
}

function generateEntropy (length = 32) {
  const seed = b4a.alloc(length)
  sodium.randombytes_buf(seed)

  return seed
}

function * uint11Reader (state) {
  yield * uintReader(state, 11)
}

function uint11Writer (buf, uints) {
  return uintWriter(buf, uints, 11)
}

function * uintReader (buffer, width) {
  const MASK = (2 << (width - 1)) - 1

  let pos = 0
  let value = 0

  while (true) {
    const offset = pos >> 3 // byte offset

    if (offset >= buffer.byteLength) {
      return value & MASK
    }

    const height = width - (pos % width)
    const leftover = (offset + 1) * 8 - pos

    value += shift(buffer[offset], height - leftover)

    pos += Math.min(height, leftover)
    if (pos % width) continue

    yield value & MASK

    value = 0
  }
}

function uintWriter (buffer, uints, width) {
  let pos = 0

  while (true) {
    const offset = pos >> 3 // byte offset

    const i = Math.floor(pos / width)
    if (i >= uints.length) break

    if (offset >= buffer.length) {
      throw new Error('Failed to encode uints')
    }

    const rem = 8 - pos % 8
    const height = (i + 1) * width - pos

    const value = shift(uints[i], rem - height)

    buffer[offset] += mask(value, rem)

    pos += Math.min(rem, height)
  }

  return buffer
}

// when n is positive, shift left n bits
// when n is negative, shift right -n bits
function shift (val, n) {
  if (n === 0) return val
  if (n > 0) return val << n

  return val >> (-1 * n)
}

function mask (val, bits) {
  if (bits < 32) return val & ((1 << bits) - 1)
  return val % (2 ** bits)
}
{
  "name": "bip39-mnemonic",
  "version": "2.5.0",
  "description": "BIP39 mnemonic for key derivation schemes",
  "main": "index.js",
  "scripts": {
    "generate-wordlist": "node scripts/generate-wordlist.js",
    "test": "standard && brittle test/*.js"
  },
  "author": "Holepunch Inc",
  "license": "Apache-2.0",
  "devDependencies": {
    "brittle": "^3.4.0",
    "standard": "^17.1.0"
  },
  "dependencies": {
    "b4a": "^1.6.6",
    "nanoassert": "^2.0.0",
    "sodium-universal": "^5.0.1"
  },
  "files": [
    "index.js",
    "wordlist"
  ],
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bip39.git"
  },
  "bugs": {
    "url": "https://github.com/holepunchto/bip39/issues"
  },
  "homepage": "https://github.com/holepunchto/bip39#readme"
}
["的","一","是","在","不","了","有","和","人","这","中","大","为","上","个","国","我","以","要","他","时","来","用","们","生","到","作","地","于","出","就","分","对","成","会","可","主","发","年","动","同","工","也","能","下","过","子","说","产","种","面","而","方","后","多","定","行","学","法","所","民","得","经","十","三","之","进","着","等","部","度","家","电","力","里","如","水","化","高","自","二","理","起","小","物","现","实","加","量","都","两","体","制","机","当","使","点","从","业","本","去","把","性","好","应","开","它","合","还","因","由","其","些","然","前","外","天","政","四","日","那","社","义","事","平","形","相","全","表","间","样","与","关","各","重","新","线","内","数","正","心","反","你","明","看","原","又","么","利","比","或","但","质","气","第","向","道","命","此","变","条","只","没","结","解","问","意","建","月","公","无","系","军","很","情","者","最","立","代","想","已","通","并","提","直","题","党","程","展","五","果","料","象","员","革","位","入","常","文","总","次","品","式","活","设","及","管","特","件","长","求","老","头","基","资","边","流","路","级","少","图","山","统","接","知","较","将","组","见","计","别","她","手","角","期","根","论","运","农","指","几","九","区","强","放","决","西","被","干","做","必","战","先","回","则","任","取","据","处","队","南","给","色","光","门","即","保","治","北","造","百","规","热","领","七","海","口","东","导","器","压","志","世","金","增","争","济","阶","油","思","术","极","交","受","联","什","认","六","共","权","收","证","改","清","美","再","采","转","更","单","风","切","打","白","教","速","花","带","安","场","身","车","例","真","务","具","万","每","目","至","达","走","积","示","议","声","报","斗","完","类","八","离","华","名","确","才","科","张","信","马","节","话","米","整","空","元","况","今","集","温","传","土","许","步","群","广","石","记","需","段","研","界","拉","林","律","叫","且","究","观","越","织","装","影","算","低","持","音","众","书","布","复","容","儿","须","际","商","非","验","连","断","深","难","近","矿","千","周","委","素","技","备","半","办","青","省","列","习","响","约","支","般","史","感","劳","便","团","往","酸","历","市","克","何","除","消","构","府","称","太","准","精","值","号","率","族","维","划","选","标","写","存","候","毛","亲","快","效","斯","院","查","江","型","眼","王","按","格","养","易","置","派","层","片","始","却","专","状","育","厂","京","识","适","属","圆","包","火","住","调","满","县","局","照","参","红","细","引","听","该","铁","价","严","首","底","液","官","德","随","病","苏","失","尔","死","讲","配","女","黄","推","显","谈","罪","神","艺","呢","席","含","企","望","密","批","营","项","防","举","球","英","氧","势","告","李","台","落","木","帮","轮","破","亚","师","围","注","远","字","材","排","供","河","态","封","另","施","减","树","溶","怎","止","案","言","士","均","武","固","叶","鱼","波","视","仅","费","紧","爱","左","章","早","朝","害","续","轻","服","试","食","充","兵","源","判","护","司","足","某","练","差","致","板","田","降","黑","犯","负","击","范","继","兴","似","余","坚","曲","输","修","故","城","夫","够","送","笔","船","占","右","财","吃","富","春","职","觉","汉","画","功","巴","跟","虽","杂","飞","检","吸","助","升","阳","互","初","创","抗","考","投","坏","策","古","径","换","未","跑","留","钢","曾","端","责","站","简","述","钱","副","尽","帝","射","草","冲","承","独","令","限","阿","宣","环","双","请","超","微","让","控","州","良","轴","找","否","纪","益","依","优","顶","础","载","倒","房","突","坐","粉","敌","略","客","袁","冷","胜","绝","析","块","剂","测","丝","协","诉","念","陈","仍","罗","盐","友","洋","错","苦","夜","刑","移","频","逐","靠","混","母","短","皮","终","聚","汽","村","云","哪","既","距","卫","停","烈","央","察","烧","迅","境","若","印","洲","刻","括","激","孔","搞","甚","室","待","核","校","散","侵","吧","甲","游","久","菜","味","旧","模","湖","货","损","预","阻","毫","普","稳","乙","妈","植","息","扩","银","语","挥","酒","守","拿","序","纸","医","缺","雨","吗","针","刘","啊","急","唱","误","训","愿","审","附","获","茶","鲜","粮","斤","孩","脱","硫","肥","善","龙","演","父","渐","血","欢","械","掌","歌","沙","刚","攻","谓","盾","讨","晚","粒","乱","燃","矛","乎","杀","药","宁","鲁","贵","钟","煤","读","班","伯","香","介","迫","句","丰","培","握","兰","担","弦","蛋","沉","假","穿","执","答","乐","谁","顺","烟","缩","征","脸","喜","松","脚","困","异","免","背","星","福","买","染","井","概","慢","怕","磁","倍","祖","皇","促","静","补","评","翻","肉","践","尼","衣","宽","扬","棉","希","伤","操","垂","秋","宜","氢","套","督","振","架","亮","末","宪","庆","编","牛","触","映","雷","销","诗","座","居","抓","裂","胞","呼","娘","景","威","绿","晶","厚","盟","衡","鸡","孙","延","危","胶","屋","乡","临","陆","顾","掉","呀","灯","岁","措","束","耐","剧","玉","赵","跳","哥","季","课","凯","胡","额","款","绍","卷","齐","伟","蒸","殖","永","宗","苗","川","炉","岩","弱","零","杨","奏","沿","露","杆","探","滑","镇","饭","浓","航","怀","赶","库","夺","伊","灵","税","途","灭","赛","归","召","鼓","播","盘","裁","险","康","唯","录","菌","纯","借","糖","盖","横","符","私","努","堂","域","枪","润","幅","哈","竟","熟","虫","泽","脑","壤","碳","欧","遍","侧","寨","敢","彻","虑","斜","薄","庭","纳","弹","饲","伸","折","麦","湿","暗","荷","瓦","塞","床","筑","恶","户","访","塔","奇","透","梁","刀","旋","迹","卡","氯","遇","份","毒","泥","退","洗","摆","灰","彩","卖","耗","夏","择","忙","铜","献","硬","予","繁","圈","雪","函","亦","抽","篇","阵","阴","丁","尺","追","堆","雄","迎","泛","爸","楼","避","谋","吨","野","猪","旗","累","偏","典","馆","索","秦","脂","潮","爷","豆","忽","托","惊","塑","遗","愈","朱","替","纤","粗","倾","尚","痛","楚","谢","奋","购","磨","君","池","旁","碎","骨","监","捕","弟","暴","割","贯","殊","释","词","亡","壁","顿","宝","午","尘","闻","揭","炮","残","冬","桥","妇","警","综","招","吴","付","浮","遭","徐","您","摇","谷","赞","箱","隔","订","男","吹","园","纷","唐","败","宋","玻","巨","耕","坦","荣","闭","湾","键","凡","驻","锅","救","恩","剥","凝","碱","齿","截","炼","麻","纺","禁","废","盛","版","缓","净","睛","昌","婚","涉","筒","嘴","插","岸","朗","庄","街","藏","姑","贸","腐","奴","啦","惯","乘","伙","恢","匀","纱","扎","辩","耳","彪","臣","亿","璃","抵","脉","秀","萨","俄","网","舞","店","喷","纵","寸","汗","挂","洪","贺","闪","柬","爆","烯","津","稻","墙","软","勇","像","滚","厘","蒙","芳","肯","坡","柱","荡","腿","仪","旅","尾","轧","冰","贡","登","黎","削","钻","勒","逃","障","氨","郭","峰","币","港","伏","轨","亩","毕","擦","莫","刺","浪","秘","援","株","健","售","股","岛","甘","泡","睡","童","铸","汤","阀","休","汇","舍","牧","绕","炸","哲","磷","绩","朋","淡","尖","启","陷","柴","呈","徒","颜","泪","稍","忘","泵","蓝","拖","洞","授","镜","辛","壮","锋","贫","虚","弯","摩","泰","幼","廷","尊","窗","纲","弄","隶","疑","氏","宫","姐","震","瑞","怪","尤","琴","循","描","膜","违","夹","腰","缘","珠","穷","森","枝","竹","沟","催","绳","忆","邦","剩","幸","浆","栏","拥","牙","贮","礼","滤","钠","纹","罢","拍","咱","喊","袖","埃","勤","罚","焦","潜","伍","墨","欲","缝","姓","刊","饱","仿","奖","铝","鬼","丽","跨","默","挖","链","扫","喝","袋","炭","污","幕","诸","弧","励","梅","奶","洁","灾","舟","鉴","苯","讼","抱","毁","懂","寒","智","埔","寄","届","跃","渡","挑","丹","艰","贝","碰","拔","爹","戴","码","梦","芽","熔","赤","渔","哭","敬","颗","奔","铅","仲","虎","稀","妹","乏","珍","申","桌","遵","允","隆","螺","仓","魏","锐","晓","氮","兼","隐","碍","赫","拨","忠","肃","缸","牵","抢","博","巧","壳","兄","杜","讯","诚","碧","祥","柯","页","巡","矩","悲","灌","龄","伦","票","寻","桂","铺","圣","恐","恰","郑","趣","抬","荒","腾","贴","柔","滴","猛","阔","辆","妻","填","撤","储","签","闹","扰","紫","砂","递","戏","吊","陶","伐","喂","疗","瓶","婆","抚","臂","摸","忍","虾","蜡","邻","胸","巩","挤","偶","弃","槽","劲","乳","邓","吉","仁","烂","砖","租","乌","舰","伴","瓜","浅","丙","暂","燥","橡","柳","迷","暖","牌","秧","胆","详","簧","踏","瓷","谱","呆","宾","糊","洛","辉","愤","竞","隙","怒","粘","乃","绪","肩","籍","敏","涂","熙","皆","侦","悬","掘","享","纠","醒","狂","锁","淀","恨","牲","霸","爬","赏","逆","玩","陵","祝","秒","浙","貌","役","彼","悉","鸭","趋","凤","晨","畜","辈","秩","卵","署","梯","炎","滩","棋","驱","筛","峡","冒","啥","寿","译","浸","泉","帽","迟","硅","疆","贷","漏","稿","冠","嫩","胁","芯","牢","叛","蚀","奥","鸣","岭","羊","凭","串","塘","绘","酵","融","盆","锡","庙","筹","冻","辅","摄","袭","筋","拒","僚","旱","钾","鸟","漆","沈","眉","疏","添","棒","穗","硝","韩","逼","扭","侨","凉","挺","碗","栽","炒","杯","患","馏","劝","豪","辽","勃","鸿","旦","吏","拜","狗","埋","辊","掩","饮","搬","骂","辞","勾","扣","估","蒋","绒","雾","丈","朵","姆","拟","宇","辑","陕","雕","偿","蓄","崇","剪","倡","厅","咬","驶","薯","刷","斥","番","赋","奉","佛","浇","漫","曼","扇","钙","桃","扶","仔","返","俗","亏","腔","鞋","棱","覆","框","悄","叔","撞","骗","勘","旺","沸","孤","吐","孟","渠","屈","疾","妙","惜","仰","狠","胀","谐","抛","霉","桑","岗","嘛","衰","盗","渗","脏","赖","涌","甜","曹","阅","肌","哩","厉","烃","纬","毅","昨","伪","症","煮","叹","钉","搭","茎","笼","酷","偷","弓","锥","恒","杰","坑","鼻","翼","纶","叙","狱","逮","罐","络","棚","抑","膨","蔬","寺","骤","穆","冶","枯","册","尸","凸","绅","坯","牺","焰","轰","欣","晋","瘦","御","锭","锦","丧","旬","锻","垄","搜","扑","邀","亭","酯","迈","舒","脆","酶","闲","忧","酚","顽","羽","涨","卸","仗","陪","辟","惩","杭","姚","肚","捉","飘","漂","昆","欺","吾","郎","烷","汁","呵","饰","萧","雅","邮","迁","燕","撒","姻","赴","宴","烦","债","帐","斑","铃","旨","醇","董","饼","雏","姿","拌","傅","腹","妥","揉","贤","拆","歪","葡","胺","丢","浩","徽","昂","垫","挡","览","贪","慰","缴","汪","慌","冯","诺","姜","谊","凶","劣","诬","耀","昏","躺","盈","骑","乔","溪","丛","卢","抹","闷","咨","刮","驾","缆","悟","摘","铒","掷","颇","幻","柄","惠","惨","佳","仇","腊","窝","涤","剑","瞧","堡","泼","葱","罩","霍","捞","胎","苍","滨","俩","捅","湘","砍","霞","邵","萄","疯","淮","遂","熊","粪","烘","宿","档","戈","驳","嫂","裕","徙","箭","捐","肠","撑","晒","辨","殿","莲","摊","搅","酱","屏","疫","哀","蔡","堵","沫","皱","畅","叠","阁","莱","敲","辖","钩","痕","坝","巷","饿","祸","丘","玄","溜","曰","逻","彭","尝","卿","妨","艇","吞","韦","怨","矮","歇"]["的","一","是","在","不","了","有","和","人","這","中","大","為","上","個","國","我","以","要","他","時","來","用","們","生","到","作","地","於","出","就","分","對","成","會","可","主","發","年","動","同","工","也","能","下","過","子","說","產","種","面","而","方","後","多","定","行","學","法","所","民","得","經","十","三","之","進","著","等","部","度","家","電","力","裡","如","水","化","高","自","二","理","起","小","物","現","實","加","量","都","兩","體","制","機","當","使","點","從","業","本","去","把","性","好","應","開","它","合","還","因","由","其","些","然","前","外","天","政","四","日","那","社","義","事","平","形","相","全","表","間","樣","與","關","各","重","新","線","內","數","正","心","反","你","明","看","原","又","麼","利","比","或","但","質","氣","第","向","道","命","此","變","條","只","沒","結","解","問","意","建","月","公","無","系","軍","很","情","者","最","立","代","想","已","通","並","提","直","題","黨","程","展","五","果","料","象","員","革","位","入","常","文","總","次","品","式","活","設","及","管","特","件","長","求","老","頭","基","資","邊","流","路","級","少","圖","山","統","接","知","較","將","組","見","計","別","她","手","角","期","根","論","運","農","指","幾","九","區","強","放","決","西","被","幹","做","必","戰","先","回","則","任","取","據","處","隊","南","給","色","光","門","即","保","治","北","造","百","規","熱","領","七","海","口","東","導","器","壓","志","世","金","增","爭","濟","階","油","思","術","極","交","受","聯","什","認","六","共","權","收","證","改","清","美","再","採","轉","更","單","風","切","打","白","教","速","花","帶","安","場","身","車","例","真","務","具","萬","每","目","至","達","走","積","示","議","聲","報","鬥","完","類","八","離","華","名","確","才","科","張","信","馬","節","話","米","整","空","元","況","今","集","溫","傳","土","許","步","群","廣","石","記","需","段","研","界","拉","林","律","叫","且","究","觀","越","織","裝","影","算","低","持","音","眾","書","布","复","容","兒","須","際","商","非","驗","連","斷","深","難","近","礦","千","週","委","素","技","備","半","辦","青","省","列","習","響","約","支","般","史","感","勞","便","團","往","酸","歷","市","克","何","除","消","構","府","稱","太","準","精","值","號","率","族","維","劃","選","標","寫","存","候","毛","親","快","效","斯","院","查","江","型","眼","王","按","格","養","易","置","派","層","片","始","卻","專","狀","育","廠","京","識","適","屬","圓","包","火","住","調","滿","縣","局","照","參","紅","細","引","聽","該","鐵","價","嚴","首","底","液","官","德","隨","病","蘇","失","爾","死","講","配","女","黃","推","顯","談","罪","神","藝","呢","席","含","企","望","密","批","營","項","防","舉","球","英","氧","勢","告","李","台","落","木","幫","輪","破","亞","師","圍","注","遠","字","材","排","供","河","態","封","另","施","減","樹","溶","怎","止","案","言","士","均","武","固","葉","魚","波","視","僅","費","緊","愛","左","章","早","朝","害","續","輕","服","試","食","充","兵","源","判","護","司","足","某","練","差","致","板","田","降","黑","犯","負","擊","范","繼","興","似","餘","堅","曲","輸","修","故","城","夫","夠","送","筆","船","佔","右","財","吃","富","春","職","覺","漢","畫","功","巴","跟","雖","雜","飛","檢","吸","助","昇","陽","互","初","創","抗","考","投","壞","策","古","徑","換","未","跑","留","鋼","曾","端","責","站","簡","述","錢","副","盡","帝","射","草","衝","承","獨","令","限","阿","宣","環","雙","請","超","微","讓","控","州","良","軸","找","否","紀","益","依","優","頂","礎","載","倒","房","突","坐","粉","敵","略","客","袁","冷","勝","絕","析","塊","劑","測","絲","協","訴","念","陳","仍","羅","鹽","友","洋","錯","苦","夜","刑","移","頻","逐","靠","混","母","短","皮","終","聚","汽","村","雲","哪","既","距","衛","停","烈","央","察","燒","迅","境","若","印","洲","刻","括","激","孔","搞","甚","室","待","核","校","散","侵","吧","甲","遊","久","菜","味","舊","模","湖","貨","損","預","阻","毫","普","穩","乙","媽","植","息","擴","銀","語","揮","酒","守","拿","序","紙","醫","缺","雨","嗎","針","劉","啊","急","唱","誤","訓","願","審","附","獲","茶","鮮","糧","斤","孩","脫","硫","肥","善","龍","演","父","漸","血","歡","械","掌","歌","沙","剛","攻","謂","盾","討","晚","粒","亂","燃","矛","乎","殺","藥","寧","魯","貴","鐘","煤","讀","班","伯","香","介","迫","句","豐","培","握","蘭","擔","弦","蛋","沉","假","穿","執","答","樂","誰","順","煙","縮","徵","臉","喜","松","腳","困","異","免","背","星","福","買","染","井","概","慢","怕","磁","倍","祖","皇","促","靜","補","評","翻","肉","踐","尼","衣","寬","揚","棉","希","傷","操","垂","秋","宜","氫","套","督","振","架","亮","末","憲","慶","編","牛","觸","映","雷","銷","詩","座","居","抓","裂","胞","呼","娘","景","威","綠","晶","厚","盟","衡","雞","孫","延","危","膠","屋","鄉","臨","陸","顧","掉","呀","燈","歲","措","束","耐","劇","玉","趙","跳","哥","季","課","凱","胡","額","款","紹","卷","齊","偉","蒸","殖","永","宗","苗","川","爐","岩","弱","零","楊","奏","沿","露","桿","探","滑","鎮","飯","濃","航","懷","趕","庫","奪","伊","靈","稅","途","滅","賽","歸","召","鼓","播","盤","裁","險","康","唯","錄","菌","純","借","糖","蓋","橫","符","私","努","堂","域","槍","潤","幅","哈","竟","熟","蟲","澤","腦","壤","碳","歐","遍","側","寨","敢","徹","慮","斜","薄","庭","納","彈","飼","伸","折","麥","濕","暗","荷","瓦","塞","床","築","惡","戶","訪","塔","奇","透","梁","刀","旋","跡","卡","氯","遇","份","毒","泥","退","洗","擺","灰","彩","賣","耗","夏","擇","忙","銅","獻","硬","予","繁","圈","雪","函","亦","抽","篇","陣","陰","丁","尺","追","堆","雄","迎","泛","爸","樓","避","謀","噸","野","豬","旗","累","偏","典","館","索","秦","脂","潮","爺","豆","忽","托","驚","塑","遺","愈","朱","替","纖","粗","傾","尚","痛","楚","謝","奮","購","磨","君","池","旁","碎","骨","監","捕","弟","暴","割","貫","殊","釋","詞","亡","壁","頓","寶","午","塵","聞","揭","炮","殘","冬","橋","婦","警","綜","招","吳","付","浮","遭","徐","您","搖","谷","贊","箱","隔","訂","男","吹","園","紛","唐","敗","宋","玻","巨","耕","坦","榮","閉","灣","鍵","凡","駐","鍋","救","恩","剝","凝","鹼","齒","截","煉","麻","紡","禁","廢","盛","版","緩","淨","睛","昌","婚","涉","筒","嘴","插","岸","朗","莊","街","藏","姑","貿","腐","奴","啦","慣","乘","夥","恢","勻","紗","扎","辯","耳","彪","臣","億","璃","抵","脈","秀","薩","俄","網","舞","店","噴","縱","寸","汗","掛","洪","賀","閃","柬","爆","烯","津","稻","牆","軟","勇","像","滾","厘","蒙","芳","肯","坡","柱","盪","腿","儀","旅","尾","軋","冰","貢","登","黎","削","鑽","勒","逃","障","氨","郭","峰","幣","港","伏","軌","畝","畢","擦","莫","刺","浪","秘","援","株","健","售","股","島","甘","泡","睡","童","鑄","湯","閥","休","匯","舍","牧","繞","炸","哲","磷","績","朋","淡","尖","啟","陷","柴","呈","徒","顏","淚","稍","忘","泵","藍","拖","洞","授","鏡","辛","壯","鋒","貧","虛","彎","摩","泰","幼","廷","尊","窗","綱","弄","隸","疑","氏","宮","姐","震","瑞","怪","尤","琴","循","描","膜","違","夾","腰","緣","珠","窮","森","枝","竹","溝","催","繩","憶","邦","剩","幸","漿","欄","擁","牙","貯","禮","濾","鈉","紋","罷","拍","咱","喊","袖","埃","勤","罰","焦","潛","伍","墨","欲","縫","姓","刊","飽","仿","獎","鋁","鬼","麗","跨","默","挖","鏈","掃","喝","袋","炭","污","幕","諸","弧","勵","梅","奶","潔","災","舟","鑑","苯","訟","抱","毀","懂","寒","智","埔","寄","屆","躍","渡","挑","丹","艱","貝","碰","拔","爹","戴","碼","夢","芽","熔","赤","漁","哭","敬","顆","奔","鉛","仲","虎","稀","妹","乏","珍","申","桌","遵","允","隆","螺","倉","魏","銳","曉","氮","兼","隱","礙","赫","撥","忠","肅","缸","牽","搶","博","巧","殼","兄","杜","訊","誠","碧","祥","柯","頁","巡","矩","悲","灌","齡","倫","票","尋","桂","鋪","聖","恐","恰","鄭","趣","抬","荒","騰","貼","柔","滴","猛","闊","輛","妻","填","撤","儲","簽","鬧","擾","紫","砂","遞","戲","吊","陶","伐","餵","療","瓶","婆","撫","臂","摸","忍","蝦","蠟","鄰","胸","鞏","擠","偶","棄","槽","勁","乳","鄧","吉","仁","爛","磚","租","烏","艦","伴","瓜","淺","丙","暫","燥","橡","柳","迷","暖","牌","秧","膽","詳","簧","踏","瓷","譜","呆","賓","糊","洛","輝","憤","競","隙","怒","粘","乃","緒","肩","籍","敏","塗","熙","皆","偵","懸","掘","享","糾","醒","狂","鎖","淀","恨","牲","霸","爬","賞","逆","玩","陵","祝","秒","浙","貌","役","彼","悉","鴨","趨","鳳","晨","畜","輩","秩","卵","署","梯","炎","灘","棋","驅","篩","峽","冒","啥","壽","譯","浸","泉","帽","遲","矽","疆","貸","漏","稿","冠","嫩","脅","芯","牢","叛","蝕","奧","鳴","嶺","羊","憑","串","塘","繪","酵","融","盆","錫","廟","籌","凍","輔","攝","襲","筋","拒","僚","旱","鉀","鳥","漆","沈","眉","疏","添","棒","穗","硝","韓","逼","扭","僑","涼","挺","碗","栽","炒","杯","患","餾","勸","豪","遼","勃","鴻","旦","吏","拜","狗","埋","輥","掩","飲","搬","罵","辭","勾","扣","估","蔣","絨","霧","丈","朵","姆","擬","宇","輯","陝","雕","償","蓄","崇","剪","倡","廳","咬","駛","薯","刷","斥","番","賦","奉","佛","澆","漫","曼","扇","鈣","桃","扶","仔","返","俗","虧","腔","鞋","棱","覆","框","悄","叔","撞","騙","勘","旺","沸","孤","吐","孟","渠","屈","疾","妙","惜","仰","狠","脹","諧","拋","黴","桑","崗","嘛","衰","盜","滲","臟","賴","湧","甜","曹","閱","肌","哩","厲","烴","緯","毅","昨","偽","症","煮","嘆","釘","搭","莖","籠","酷","偷","弓","錐","恆","傑","坑","鼻","翼","綸","敘","獄","逮","罐","絡","棚","抑","膨","蔬","寺","驟","穆","冶","枯","冊","屍","凸","紳","坯","犧","焰","轟","欣","晉","瘦","禦","錠","錦","喪","旬","鍛","壟","搜","撲","邀","亭","酯","邁","舒","脆","酶","閒","憂","酚","頑","羽","漲","卸","仗","陪","闢","懲","杭","姚","肚","捉","飄","漂","昆","欺","吾","郎","烷","汁","呵","飾","蕭","雅","郵","遷","燕","撒","姻","赴","宴","煩","債","帳","斑","鈴","旨","醇","董","餅","雛","姿","拌","傅","腹","妥","揉","賢","拆","歪","葡","胺","丟","浩","徽","昂","墊","擋","覽","貪","慰","繳","汪","慌","馮","諾","姜","誼","兇","劣","誣","耀","昏","躺","盈","騎","喬","溪","叢","盧","抹","悶","諮","刮","駕","纜","悟","摘","鉺","擲","頗","幻","柄","惠","慘","佳","仇","臘","窩","滌","劍","瞧","堡","潑","蔥","罩","霍","撈","胎","蒼","濱","倆","捅","湘","砍","霞","邵","萄","瘋","淮","遂","熊","糞","烘","宿","檔","戈","駁","嫂","裕","徙","箭","捐","腸","撐","曬","辨","殿","蓮","攤","攪","醬","屏","疫","哀","蔡","堵","沫","皺","暢","疊","閣","萊","敲","轄","鉤","痕","壩","巷","餓","禍","丘","玄","溜","曰","邏","彭","嘗","卿","妨","艇","吞","韋","怨","矮","歇"]["abdikace","abeceda","adresa","agrese","akce","aktovka","alej","alkohol","amputace","ananas","andulka","anekdota","anketa","antika","anulovat","archa","arogance","asfalt","asistent","aspirace","astma","astronom","atlas","atletika","atol","autobus","azyl","babka","bachor","bacil","baculka","badatel","bageta","bagr","bahno","bakterie","balada","baletka","balkon","balonek","balvan","balza","bambus","bankomat","barbar","baret","barman","baroko","barva","baterka","batoh","bavlna","bazalka","bazilika","bazuka","bedna","beran","beseda","bestie","beton","bezinka","bezmoc","beztak","bicykl","bidlo","biftek","bikiny","bilance","biograf","biolog","bitva","bizon","blahobyt","blatouch","blecha","bledule","blesk","blikat","blizna","blokovat","bloudit","blud","bobek","bobr","bodlina","bodnout","bohatost","bojkot","bojovat","bokorys","bolest","borec","borovice","bota","boubel","bouchat","bouda","boule","bourat","boxer","bradavka","brambora","branka","bratr","brepta","briketa","brko","brloh","bronz","broskev","brunetka","brusinka","brzda","brzy","bublina","bubnovat","buchta","buditel","budka","budova","bufet","bujarost","bukvice","buldok","bulva","bunda","bunkr","burza","butik","buvol","buzola","bydlet","bylina","bytovka","bzukot","capart","carevna","cedr","cedule","cejch","cejn","cela","celer","celkem","celnice","cenina","cennost","cenovka","centrum","cenzor","cestopis","cetka","chalupa","chapadlo","charita","chata","chechtat","chemie","chichot","chirurg","chlad","chleba","chlubit","chmel","chmura","chobot","chochol","chodba","cholera","chomout","chopit","choroba","chov","chrapot","chrlit","chrt","chrup","chtivost","chudina","chutnat","chvat","chvilka","chvost","chyba","chystat","chytit","cibule","cigareta","cihelna","cihla","cinkot","cirkus","cisterna","citace","citrus","cizinec","cizost","clona","cokoliv","couvat","ctitel","ctnost","cudnost","cuketa","cukr","cupot","cvaknout","cval","cvik","cvrkot","cyklista","daleko","dareba","datel","datum","dcera","debata","dechovka","decibel","deficit","deflace","dekl","dekret","demokrat","deprese","derby","deska","detektiv","dikobraz","diktovat","dioda","diplom","disk","displej","divadlo","divoch","dlaha","dlouho","dluhopis","dnes","dobro","dobytek","docent","dochutit","dodnes","dohled","dohoda","dohra","dojem","dojnice","doklad","dokola","doktor","dokument","dolar","doleva","dolina","doma","dominant","domluvit","domov","donutit","dopad","dopis","doplnit","doposud","doprovod","dopustit","dorazit","dorost","dort","dosah","doslov","dostatek","dosud","dosyta","dotaz","dotek","dotknout","doufat","doutnat","dovozce","dozadu","doznat","dozorce","drahota","drak","dramatik","dravec","draze","drdol","drobnost","drogerie","drozd","drsnost","drtit","drzost","duben","duchovno","dudek","duha","duhovka","dusit","dusno","dutost","dvojice","dvorec","dynamit","ekolog","ekonomie","elektron","elipsa","email","emise","emoce","empatie","epizoda","epocha","epopej","epos","esej","esence","eskorta","eskymo","etiketa","euforie","evoluce","exekuce","exkurze","expedice","exploze","export","extrakt","facka","fajfka","fakulta","fanatik","fantazie","farmacie","favorit","fazole","federace","fejeton","fenka","fialka","figurant","filozof","filtr","finance","finta","fixace","fjord","flanel","flirt","flotila","fond","fosfor","fotbal","fotka","foton","frakce","freska","fronta","fukar","funkce","fyzika","galeje","garant","genetika","geolog","gilotina","glazura","glejt","golem","golfista","gotika","graf","gramofon","granule","grep","gril","grog","groteska","guma","hadice","hadr","hala","halenka","hanba","hanopis","harfa","harpuna","havran","hebkost","hejkal","hejno","hejtman","hektar","helma","hematom","herec","herna","heslo","hezky","historik","hladovka","hlasivky","hlava","hledat","hlen","hlodavec","hloh","hloupost","hltat","hlubina","hluchota","hmat","hmota","hmyz","hnis","hnojivo","hnout","hoblina","hoboj","hoch","hodiny","hodlat","hodnota","hodovat","hojnost","hokej","holinka","holka","holub","homole","honitba","honorace","horal","horda","horizont","horko","horlivec","hormon","hornina","horoskop","horstvo","hospoda","hostina","hotovost","houba","houf","houpat","houska","hovor","hradba","hranice","hravost","hrazda","hrbolek","hrdina","hrdlo","hrdost","hrnek","hrobka","hromada","hrot","hrouda","hrozen","hrstka","hrubost","hryzat","hubenost","hubnout","hudba","hukot","humr","husita","hustota","hvozd","hybnost","hydrant","hygiena","hymna","hysterik","idylka","ihned","ikona","iluze","imunita","infekce","inflace","inkaso","inovace","inspekce","internet","invalida","investor","inzerce","ironie","jablko","jachta","jahoda","jakmile","jakost","jalovec","jantar","jarmark","jaro","jasan","jasno","jatka","javor","jazyk","jedinec","jedle","jednatel","jehlan","jekot","jelen","jelito","jemnost","jenom","jepice","jeseter","jevit","jezdec","jezero","jinak","jindy","jinoch","jiskra","jistota","jitrnice","jizva","jmenovat","jogurt","jurta","kabaret","kabel","kabinet","kachna","kadet","kadidlo","kahan","kajak","kajuta","kakao","kaktus","kalamita","kalhoty","kalibr","kalnost","kamera","kamkoliv","kamna","kanibal","kanoe","kantor","kapalina","kapela","kapitola","kapka","kaple","kapota","kapr","kapusta","kapybara","karamel","karotka","karton","kasa","katalog","katedra","kauce","kauza","kavalec","kazajka","kazeta","kazivost","kdekoliv","kdesi","kedluben","kemp","keramika","kino","klacek","kladivo","klam","klapot","klasika","klaun","klec","klenba","klepat","klesnout","klid","klima","klisna","klobouk","klokan","klopa","kloub","klubovna","klusat","kluzkost","kmen","kmitat","kmotr","kniha","knot","koalice","koberec","kobka","kobliha","kobyla","kocour","kohout","kojenec","kokos","koktejl","kolaps","koleda","kolize","kolo","komando","kometa","komik","komnata","komora","kompas","komunita","konat","koncept","kondice","konec","konfese","kongres","konina","konkurs","kontakt","konzerva","kopanec","kopie","kopnout","koprovka","korbel","korektor","kormidlo","koroptev","korpus","koruna","koryto","korzet","kosatec","kostka","kotel","kotleta","kotoul","koukat","koupelna","kousek","kouzlo","kovboj","koza","kozoroh","krabice","krach","krajina","kralovat","krasopis","kravata","kredit","krejcar","kresba","kreveta","kriket","kritik","krize","krkavec","krmelec","krmivo","krocan","krok","kronika","kropit","kroupa","krovka","krtek","kruhadlo","krupice","krutost","krvinka","krychle","krypta","krystal","kryt","kudlanka","kufr","kujnost","kukla","kulajda","kulich","kulka","kulomet","kultura","kuna","kupodivu","kurt","kurzor","kutil","kvalita","kvasinka","kvestor","kynolog","kyselina","kytara","kytice","kytka","kytovec","kyvadlo","labrador","lachtan","ladnost","laik","lakomec","lamela","lampa","lanovka","lasice","laso","lastura","latinka","lavina","lebka","leckdy","leden","lednice","ledovka","ledvina","legenda","legie","legrace","lehce","lehkost","lehnout","lektvar","lenochod","lentilka","lepenka","lepidlo","letadlo","letec","letmo","letokruh","levhart","levitace","levobok","libra","lichotka","lidojed","lidskost","lihovina","lijavec","lilek","limetka","linie","linka","linoleum","listopad","litina","litovat","lobista","lodivod","logika","logoped","lokalita","loket","lomcovat","lopata","lopuch","lord","losos","lotr","loudal","louh","louka","louskat","lovec","lstivost","lucerna","lucifer","lump","lusk","lustrace","lvice","lyra","lyrika","lysina","madam","madlo","magistr","mahagon","majetek","majitel","majorita","makak","makovice","makrela","malba","malina","malovat","malvice","maminka","mandle","manko","marnost","masakr","maskot","masopust","matice","matrika","maturita","mazanec","mazivo","mazlit","mazurka","mdloba","mechanik","meditace","medovina","melasa","meloun","mentolka","metla","metoda","metr","mezera","migrace","mihnout","mihule","mikina","mikrofon","milenec","milimetr","milost","mimika","mincovna","minibar","minomet","minulost","miska","mistr","mixovat","mladost","mlha","mlhovina","mlok","mlsat","mluvit","mnich","mnohem","mobil","mocnost","modelka","modlitba","mohyla","mokro","molekula","momentka","monarcha","monokl","monstrum","montovat","monzun","mosaz","moskyt","most","motivace","motorka","motyka","moucha","moudrost","mozaika","mozek","mozol","mramor","mravenec","mrkev","mrtvola","mrzet","mrzutost","mstitel","mudrc","muflon","mulat","mumie","munice","muset","mutace","muzeum","muzikant","myslivec","mzda","nabourat","nachytat","nadace","nadbytek","nadhoz","nadobro","nadpis","nahlas","nahnat","nahodile","nahradit","naivita","najednou","najisto","najmout","naklonit","nakonec","nakrmit","nalevo","namazat","namluvit","nanometr","naoko","naopak","naostro","napadat","napevno","naplnit","napnout","naposled","naprosto","narodit","naruby","narychlo","nasadit","nasekat","naslepo","nastat","natolik","navenek","navrch","navzdory","nazvat","nebe","nechat","necky","nedaleko","nedbat","neduh","negace","nehet","nehoda","nejen","nejprve","neklid","nelibost","nemilost","nemoc","neochota","neonka","nepokoj","nerost","nerv","nesmysl","nesoulad","netvor","neuron","nevina","nezvykle","nicota","nijak","nikam","nikdy","nikl","nikterak","nitro","nocleh","nohavice","nominace","nora","norek","nositel","nosnost","nouze","noviny","novota","nozdra","nuda","nudle","nuget","nutit","nutnost","nutrie","nymfa","obal","obarvit","obava","obdiv","obec","obehnat","obejmout","obezita","obhajoba","obilnice","objasnit","objekt","obklopit","oblast","oblek","obliba","obloha","obluda","obnos","obohatit","obojek","obout","obrazec","obrna","obruba","obrys","obsah","obsluha","obstarat","obuv","obvaz","obvinit","obvod","obvykle","obyvatel","obzor","ocas","ocel","ocenit","ochladit","ochota","ochrana","ocitnout","odboj","odbyt","odchod","odcizit","odebrat","odeslat","odevzdat","odezva","odhadce","odhodit","odjet","odjinud","odkaz","odkoupit","odliv","odluka","odmlka","odolnost","odpad","odpis","odplout","odpor","odpustit","odpykat","odrazka","odsoudit","odstup","odsun","odtok","odtud","odvaha","odveta","odvolat","odvracet","odznak","ofina","ofsajd","ohlas","ohnisko","ohrada","ohrozit","ohryzek","okap","okenice","oklika","okno","okouzlit","okovy","okrasa","okres","okrsek","okruh","okupant","okurka","okusit","olejnina","olizovat","omak","omeleta","omezit","omladina","omlouvat","omluva","omyl","onehdy","opakovat","opasek","operace","opice","opilost","opisovat","opora","opozice","opravdu","oproti","orbital","orchestr","orgie","orlice","orloj","ortel","osada","oschnout","osika","osivo","oslava","oslepit","oslnit","oslovit","osnova","osoba","osolit","ospalec","osten","ostraha","ostuda","ostych","osvojit","oteplit","otisk","otop","otrhat","otrlost","otrok","otruby","otvor","ovanout","ovar","oves","ovlivnit","ovoce","oxid","ozdoba","pachatel","pacient","padouch","pahorek","pakt","palanda","palec","palivo","paluba","pamflet","pamlsek","panenka","panika","panna","panovat","panstvo","pantofle","paprika","parketa","parodie","parta","paruka","paryba","paseka","pasivita","pastelka","patent","patrona","pavouk","pazneht","pazourek","pecka","pedagog","pejsek","peklo","peloton","penalta","pendrek","penze","periskop","pero","pestrost","petarda","petice","petrolej","pevnina","pexeso","pianista","piha","pijavice","pikle","piknik","pilina","pilnost","pilulka","pinzeta","pipeta","pisatel","pistole","pitevna","pivnice","pivovar","placenta","plakat","plamen","planeta","plastika","platit","plavidlo","plaz","plech","plemeno","plenta","ples","pletivo","plevel","plivat","plnit","plno","plocha","plodina","plomba","plout","pluk","plyn","pobavit","pobyt","pochod","pocit","poctivec","podat","podcenit","podepsat","podhled","podivit","podklad","podmanit","podnik","podoba","podpora","podraz","podstata","podvod","podzim","poezie","pohanka","pohnutka","pohovor","pohroma","pohyb","pointa","pojistka","pojmout","pokazit","pokles","pokoj","pokrok","pokuta","pokyn","poledne","polibek","polknout","poloha","polynom","pomalu","pominout","pomlka","pomoc","pomsta","pomyslet","ponechat","ponorka","ponurost","popadat","popel","popisek","poplach","poprosit","popsat","popud","poradce","porce","porod","porucha","poryv","posadit","posed","posila","poskok","poslanec","posoudit","pospolu","postava","posudek","posyp","potah","potkan","potlesk","potomek","potrava","potupa","potvora","poukaz","pouto","pouzdro","povaha","povidla","povlak","povoz","povrch","povstat","povyk","povzdech","pozdrav","pozemek","poznatek","pozor","pozvat","pracovat","prahory","praktika","prales","praotec","praporek","prase","pravda","princip","prkno","probudit","procento","prodej","profese","prohra","projekt","prolomit","promile","pronikat","propad","prorok","prosba","proton","proutek","provaz","prskavka","prsten","prudkost","prut","prvek","prvohory","psanec","psovod","pstruh","ptactvo","puberta","puch","pudl","pukavec","puklina","pukrle","pult","pumpa","punc","pupen","pusa","pusinka","pustina","putovat","putyka","pyramida","pysk","pytel","racek","rachot","radiace","radnice","radon","raft","ragby","raketa","rakovina","rameno","rampouch","rande","rarach","rarita","rasovna","rastr","ratolest","razance","razidlo","reagovat","reakce","recept","redaktor","referent","reflex","rejnok","reklama","rekord","rekrut","rektor","reputace","revize","revma","revolver","rezerva","riskovat","riziko","robotika","rodokmen","rohovka","rokle","rokoko","romaneto","ropovod","ropucha","rorejs","rosol","rostlina","rotmistr","rotoped","rotunda","roubenka","roucho","roup","roura","rovina","rovnice","rozbor","rozchod","rozdat","rozeznat","rozhodce","rozinka","rozjezd","rozkaz","rozloha","rozmar","rozpad","rozruch","rozsah","roztok","rozum","rozvod","rubrika","ruchadlo","rukavice","rukopis","ryba","rybolov","rychlost","rydlo","rypadlo","rytina","ryzost","sadista","sahat","sako","samec","samizdat","samota","sanitka","sardinka","sasanka","satelit","sazba","sazenice","sbor","schovat","sebranka","secese","sedadlo","sediment","sedlo","sehnat","sejmout","sekera","sekta","sekunda","sekvoje","semeno","seno","servis","sesadit","seshora","seskok","seslat","sestra","sesuv","sesypat","setba","setina","setkat","setnout","setrvat","sever","seznam","shoda","shrnout","sifon","silnice","sirka","sirotek","sirup","situace","skafandr","skalisko","skanzen","skaut","skeptik","skica","skladba","sklenice","sklo","skluz","skoba","skokan","skoro","skripta","skrz","skupina","skvost","skvrna","slabika","sladidlo","slanina","slast","slavnost","sledovat","slepec","sleva","slezina","slib","slina","sliznice","slon","sloupek","slovo","sluch","sluha","slunce","slupka","slza","smaragd","smetana","smilstvo","smlouva","smog","smrad","smrk","smrtka","smutek","smysl","snad","snaha","snob","sobota","socha","sodovka","sokol","sopka","sotva","souboj","soucit","soudce","souhlas","soulad","soumrak","souprava","soused","soutok","souviset","spalovna","spasitel","spis","splav","spodek","spojenec","spolu","sponzor","spornost","spousta","sprcha","spustit","sranda","sraz","srdce","srna","srnec","srovnat","srpen","srst","srub","stanice","starosta","statika","stavba","stehno","stezka","stodola","stolek","stopa","storno","stoupat","strach","stres","strhnout","strom","struna","studna","stupnice","stvol","styk","subjekt","subtropy","suchar","sudost","sukno","sundat","sunout","surikata","surovina","svah","svalstvo","svetr","svatba","svazek","svisle","svitek","svoboda","svodidlo","svorka","svrab","sykavka","sykot","synek","synovec","sypat","sypkost","syrovost","sysel","sytost","tabletka","tabule","tahoun","tajemno","tajfun","tajga","tajit","tajnost","taktika","tamhle","tampon","tancovat","tanec","tanker","tapeta","tavenina","tazatel","technika","tehdy","tekutina","telefon","temnota","tendence","tenista","tenor","teplota","tepna","teprve","terapie","termoska","textil","ticho","tiskopis","titulek","tkadlec","tkanina","tlapka","tleskat","tlukot","tlupa","tmel","toaleta","topinka","topol","torzo","touha","toulec","tradice","traktor","tramp","trasa","traverza","trefit","trest","trezor","trhavina","trhlina","trochu","trojice","troska","trouba","trpce","trpitel","trpkost","trubec","truchlit","truhlice","trus","trvat","tudy","tuhnout","tuhost","tundra","turista","turnaj","tuzemsko","tvaroh","tvorba","tvrdost","tvrz","tygr","tykev","ubohost","uboze","ubrat","ubrousek","ubrus","ubytovna","ucho","uctivost","udivit","uhradit","ujednat","ujistit","ujmout","ukazatel","uklidnit","uklonit","ukotvit","ukrojit","ulice","ulita","ulovit","umyvadlo","unavit","uniforma","uniknout","upadnout","uplatnit","uplynout","upoutat","upravit","uran","urazit","usednout","usilovat","usmrtit","usnadnit","usnout","usoudit","ustlat","ustrnout","utahovat","utkat","utlumit","utonout","utopenec","utrousit","uvalit","uvolnit","uvozovka","uzdravit","uzel","uzenina","uzlina","uznat","vagon","valcha","valoun","vana","vandal","vanilka","varan","varhany","varovat","vcelku","vchod","vdova","vedro","vegetace","vejce","velbloud","veletrh","velitel","velmoc","velryba","venkov","veranda","verze","veselka","veskrze","vesnice","vespodu","vesta","veterina","veverka","vibrace","vichr","videohra","vidina","vidle","vila","vinice","viset","vitalita","vize","vizitka","vjezd","vklad","vkus","vlajka","vlak","vlasec","vlevo","vlhkost","vliv","vlnovka","vloupat","vnucovat","vnuk","voda","vodivost","vodoznak","vodstvo","vojensky","vojna","vojsko","volant","volba","volit","volno","voskovka","vozidlo","vozovna","vpravo","vrabec","vracet","vrah","vrata","vrba","vrcholek","vrhat","vrstva","vrtule","vsadit","vstoupit","vstup","vtip","vybavit","vybrat","vychovat","vydat","vydra","vyfotit","vyhledat","vyhnout","vyhodit","vyhradit","vyhubit","vyjasnit","vyjet","vyjmout","vyklopit","vykonat","vylekat","vymazat","vymezit","vymizet","vymyslet","vynechat","vynikat","vynutit","vypadat","vyplatit","vypravit","vypustit","vyrazit","vyrovnat","vyrvat","vyslovit","vysoko","vystavit","vysunout","vysypat","vytasit","vytesat","vytratit","vyvinout","vyvolat","vyvrhel","vyzdobit","vyznat","vzadu","vzbudit","vzchopit","vzdor","vzduch","vzdychat","vzestup","vzhledem","vzkaz","vzlykat","vznik","vzorek","vzpoura","vztah","vztek","xylofon","zabrat","zabydlet","zachovat","zadarmo","zadusit","zafoukat","zahltit","zahodit","zahrada","zahynout","zajatec","zajet","zajistit","zaklepat","zakoupit","zalepit","zamezit","zamotat","zamyslet","zanechat","zanikat","zaplatit","zapojit","zapsat","zarazit","zastavit","zasunout","zatajit","zatemnit","zatknout","zaujmout","zavalit","zavelet","zavinit","zavolat","zavrtat","zazvonit","zbavit","zbrusu","zbudovat","zbytek","zdaleka","zdarma","zdatnost","zdivo","zdobit","zdroj","zdvih","zdymadlo","zelenina","zeman","zemina","zeptat","zezadu","zezdola","zhatit","zhltnout","zhluboka","zhotovit","zhruba","zima","zimnice","zjemnit","zklamat","zkoumat","zkratka","zkumavka","zlato","zlehka","zloba","zlom","zlost","zlozvyk","zmapovat","zmar","zmatek","zmije","zmizet","zmocnit","zmodrat","zmrzlina","zmutovat","znak","znalost","znamenat","znovu","zobrazit","zotavit","zoubek","zoufale","zplodit","zpomalit","zprava","zprostit","zprudka","zprvu","zrada","zranit","zrcadlo","zrnitost","zrno","zrovna","zrychlit","zrzavost","zticha","ztratit","zubovina","zubr","zvednout","zvenku","zvesela","zvon","zvrat","zvukovod","zvyk"]["abandon","ability","able","about","above","absent","absorb","abstract","absurd","abuse","access","accident","account","accuse","achieve","acid","acoustic","acquire","across","act","action","actor","actress","actual","adapt","add","addict","address","adjust","admit","adult","advance","advice","aerobic","affair","afford","afraid","again","age","agent","agree","ahead","aim","air","airport","aisle","alarm","album","alcohol","alert","alien","all","alley","allow","almost","alone","alpha","already","also","alter","always","amateur","amazing","among","amount","amused","analyst","anchor","ancient","anger","angle","angry","animal","ankle","announce","annual","another","answer","antenna","antique","anxiety","any","apart","apology","appear","apple","approve","april","arch","arctic","area","arena","argue","arm","armed","armor","army","around","arrange","arrest","arrive","arrow","art","artefact","artist","artwork","ask","aspect","assault","asset","assist","assume","asthma","athlete","atom","attack","attend","attitude","attract","auction","audit","august","aunt","author","auto","autumn","average","avocado","avoid","awake","aware","away","awesome","awful","awkward","axis","baby","bachelor","bacon","badge","bag","balance","balcony","ball","bamboo","banana","banner","bar","barely","bargain","barrel","base","basic","basket","battle","beach","bean","beauty","because","become","beef","before","begin","behave","behind","believe","below","belt","bench","benefit","best","betray","better","between","beyond","bicycle","bid","bike","bind","biology","bird","birth","bitter","black","blade","blame","blanket","blast","bleak","bless","blind","blood","blossom","blouse","blue","blur","blush","board","boat","body","boil","bomb","bone","bonus","book","boost","border","boring","borrow","boss","bottom","bounce","box","boy","bracket","brain","brand","brass","brave","bread","breeze","brick","bridge","brief","bright","bring","brisk","broccoli","broken","bronze","broom","brother","brown","brush","bubble","buddy","budget","buffalo","build","bulb","bulk","bullet","bundle","bunker","burden","burger","burst","bus","business","busy","butter","buyer","buzz","cabbage","cabin","cable","cactus","cage","cake","call","calm","camera","camp","can","canal","cancel","candy","cannon","canoe","canvas","canyon","capable","capital","captain","car","carbon","card","cargo","carpet","carry","cart","case","cash","casino","castle","casual","cat","catalog","catch","category","cattle","caught","cause","caution","cave","ceiling","celery","cement","census","century","cereal","certain","chair","chalk","champion","change","chaos","chapter","charge","chase","chat","cheap","check","cheese","chef","cherry","chest","chicken","chief","child","chimney","choice","choose","chronic","chuckle","chunk","churn","cigar","cinnamon","circle","citizen","city","civil","claim","clap","clarify","claw","clay","clean","clerk","clever","click","client","cliff","climb","clinic","clip","clock","clog","close","cloth","cloud","clown","club","clump","cluster","clutch","coach","coast","coconut","code","coffee","coil","coin","collect","color","column","combine","come","comfort","comic","common","company","concert","conduct","confirm","congress","connect","consider","control","convince","cook","cool","copper","copy","coral","core","corn","correct","cost","cotton","couch","country","couple","course","cousin","cover","coyote","crack","cradle","craft","cram","crane","crash","crater","crawl","crazy","cream","credit","creek","crew","cricket","crime","crisp","critic","crop","cross","crouch","crowd","crucial","cruel","cruise","crumble","crunch","crush","cry","crystal","cube","culture","cup","cupboard","curious","current","curtain","curve","cushion","custom","cute","cycle","dad","damage","damp","dance","danger","daring","dash","daughter","dawn","day","deal","debate","debris","decade","december","decide","decline","decorate","decrease","deer","defense","define","defy","degree","delay","deliver","demand","demise","denial","dentist","deny","depart","depend","deposit","depth","deputy","derive","describe","desert","design","desk","despair","destroy","detail","detect","develop","device","devote","diagram","dial","diamond","diary","dice","diesel","diet","differ","digital","dignity","dilemma","dinner","dinosaur","direct","dirt","disagree","discover","disease","dish","dismiss","disorder","display","distance","divert","divide","divorce","dizzy","doctor","document","dog","doll","dolphin","domain","donate","donkey","donor","door","dose","double","dove","draft","dragon","drama","drastic","draw","dream","dress","drift","drill","drink","drip","drive","drop","drum","dry","duck","dumb","dune","during","dust","dutch","duty","dwarf","dynamic","eager","eagle","early","earn","earth","easily","east","easy","echo","ecology","economy","edge","edit","educate","effort","egg","eight","either","elbow","elder","electric","elegant","element","elephant","elevator","elite","else","embark","embody","embrace","emerge","emotion","employ","empower","empty","enable","enact","end","endless","endorse","enemy","energy","enforce","engage","engine","enhance","enjoy","enlist","enough","enrich","enroll","ensure","enter","entire","entry","envelope","episode","equal","equip","era","erase","erode","erosion","error","erupt","escape","essay","essence","estate","eternal","ethics","evidence","evil","evoke","evolve","exact","example","excess","exchange","excite","exclude","excuse","execute","exercise","exhaust","exhibit","exile","exist","exit","exotic","expand","expect","expire","explain","expose","express","extend","extra","eye","eyebrow","fabric","face","faculty","fade","faint","faith","fall","false","fame","family","famous","fan","fancy","fantasy","farm","fashion","fat","fatal","father","fatigue","fault","favorite","feature","february","federal","fee","feed","feel","female","fence","festival","fetch","fever","few","fiber","fiction","field","figure","file","film","filter","final","find","fine","finger","finish","fire","firm","first","fiscal","fish","fit","fitness","fix","flag","flame","flash","flat","flavor","flee","flight","flip","float","flock","floor","flower","fluid","flush","fly","foam","focus","fog","foil","fold","follow","food","foot","force","forest","forget","fork","fortune","forum","forward","fossil","foster","found","fox","fragile","frame","frequent","fresh","friend","fringe","frog","front","frost","frown","frozen","fruit","fuel","fun","funny","furnace","fury","future","gadget","gain","galaxy","gallery","game","gap","garage","garbage","garden","garlic","garment","gas","gasp","gate","gather","gauge","gaze","general","genius","genre","gentle","genuine","gesture","ghost","giant","gift","giggle","ginger","giraffe","girl","give","glad","glance","glare","glass","glide","glimpse","globe","gloom","glory","glove","glow","glue","goat","goddess","gold","good","goose","gorilla","gospel","gossip","govern","gown","grab","grace","grain","grant","grape","grass","gravity","great","green","grid","grief","grit","grocery","group","grow","grunt","guard","guess","guide","guilt","guitar","gun","gym","habit","hair","half","hammer","hamster","hand","happy","harbor","hard","harsh","harvest","hat","have","hawk","hazard","head","health","heart","heavy","hedgehog","height","hello","helmet","help","hen","hero","hidden","high","hill","hint","hip","hire","history","hobby","hockey","hold","hole","holiday","hollow","home","honey","hood","hope","horn","horror","horse","hospital","host","hotel","hour","hover","hub","huge","human","humble","humor","hundred","hungry","hunt","hurdle","hurry","hurt","husband","hybrid","ice","icon","idea","identify","idle","ignore","ill","illegal","illness","image","imitate","immense","immune","impact","impose","improve","impulse","inch","include","income","increase","index","indicate","indoor","industry","infant","inflict","inform","inhale","inherit","initial","inject","injury","inmate","inner","innocent","input","inquiry","insane","insect","inside","inspire","install","intact","interest","into","invest","invite","involve","iron","island","isolate","issue","item","ivory","jacket","jaguar","jar","jazz","jealous","jeans","jelly","jewel","job","join","joke","journey","joy","judge","juice","jump","jungle","junior","junk","just","kangaroo","keen","keep","ketchup","key","kick","kid","kidney","kind","kingdom","kiss","kit","kitchen","kite","kitten","kiwi","knee","knife","knock","know","lab","label","labor","ladder","lady","lake","lamp","language","laptop","large","later","latin","laugh","laundry","lava","law","lawn","lawsuit","layer","lazy","leader","leaf","learn","leave","lecture","left","leg","legal","legend","leisure","lemon","lend","length","lens","leopard","lesson","letter","level","liar","liberty","library","license","life","lift","light","like","limb","limit","link","lion","liquid","list","little","live","lizard","load","loan","lobster","local","lock","logic","lonely","long","loop","lottery","loud","lounge","love","loyal","lucky","luggage","lumber","lunar","lunch","luxury","lyrics","machine","mad","magic","magnet","maid","mail","main","major","make","mammal","man","manage","mandate","mango","mansion","manual","maple","marble","march","margin","marine","market","marriage","mask","mass","master","match","material","math","matrix","matter","maximum","maze","meadow","mean","measure","meat","mechanic","medal","media","melody","melt","member","memory","mention","menu","mercy","merge","merit","merry","mesh","message","metal","method","middle","midnight","milk","million","mimic","mind","minimum","minor","minute","miracle","mirror","misery","miss","mistake","mix","mixed","mixture","mobile","model","modify","mom","moment","monitor","monkey","monster","month","moon","moral","more","morning","mosquito","mother","motion","motor","mountain","mouse","move","movie","much","muffin","mule","multiply","muscle","museum","mushroom","music","must","mutual","myself","mystery","myth","naive","name","napkin","narrow","nasty","nation","nature","near","neck","need","negative","neglect","neither","nephew","nerve","nest","net","network","neutral","never","news","next","nice","night","noble","noise","nominee","noodle","normal","north","nose","notable","note","nothing","notice","novel","now","nuclear","number","nurse","nut","oak","obey","object","oblige","obscure","observe","obtain","obvious","occur","ocean","october","odor","off","offer","office","often","oil","okay","old","olive","olympic","omit","once","one","onion","online","only","open","opera","opinion","oppose","option","orange","orbit","orchard","order","ordinary","organ","orient","original","orphan","ostrich","other","outdoor","outer","output","outside","oval","oven","over","own","owner","oxygen","oyster","ozone","pact","paddle","page","pair","palace","palm","panda","panel","panic","panther","paper","parade","parent","park","parrot","party","pass","patch","path","patient","patrol","pattern","pause","pave","payment","peace","peanut","pear","peasant","pelican","pen","penalty","pencil","people","pepper","perfect","permit","person","pet","phone","photo","phrase","physical","piano","picnic","picture","piece","pig","pigeon","pill","pilot","pink","pioneer","pipe","pistol","pitch","pizza","place","planet","plastic","plate","play","please","pledge","pluck","plug","plunge","poem","poet","point","polar","pole","police","pond","pony","pool","popular","portion","position","possible","post","potato","pottery","poverty","powder","power","practice","praise","predict","prefer","prepare","present","pretty","prevent","price","pride","primary","print","priority","prison","private","prize","problem","process","produce","profit","program","project","promote","proof","property","prosper","protect","proud","provide","public","pudding","pull","pulp","pulse","pumpkin","punch","pupil","puppy","purchase","purity","purpose","purse","push","put","puzzle","pyramid","quality","quantum","quarter","question","quick","quit","quiz","quote","rabbit","raccoon","race","rack","radar","radio","rail","rain","raise","rally","ramp","ranch","random","range","rapid","rare","rate","rather","raven","raw","razor","ready","real","reason","rebel","rebuild","recall","receive","recipe","record","recycle","reduce","reflect","reform","refuse","region","regret","regular","reject","relax","release","relief","rely","remain","remember","remind","remove","render","renew","rent","reopen","repair","repeat","replace","report","require","rescue","resemble","resist","resource","response","result","retire","retreat","return","reunion","reveal","review","reward","rhythm","rib","ribbon","rice","rich","ride","ridge","rifle","right","rigid","ring","riot","ripple","risk","ritual","rival","river","road","roast","robot","robust","rocket","romance","roof","rookie","room","rose","rotate","rough","round","route","royal","rubber","rude","rug","rule","run","runway","rural","sad","saddle","sadness","safe","sail","salad","salmon","salon","salt","salute","same","sample","sand","satisfy","satoshi","sauce","sausage","save","say","scale","scan","scare","scatter","scene","scheme","school","science","scissors","scorpion","scout","scrap","screen","script","scrub","sea","search","season","seat","second","secret","section","security","seed","seek","segment","select","sell","seminar","senior","sense","sentence","series","service","session","settle","setup","seven","shadow","shaft","shallow","share","shed","shell","sheriff","shield","shift","shine","ship","shiver","shock","shoe","shoot","shop","short","shoulder","shove","shrimp","shrug","shuffle","shy","sibling","sick","side","siege","sight","sign","silent","silk","silly","silver","similar","simple","since","sing","siren","sister","situate","six","size","skate","sketch","ski","skill","skin","skirt","skull","slab","slam","sleep","slender","slice","slide","slight","slim","slogan","slot","slow","slush","small","smart","smile","smoke","smooth","snack","snake","snap","sniff","snow","soap","soccer","social","sock","soda","soft","solar","soldier","solid","solution","solve","someone","song","soon","sorry","sort","soul","sound","soup","source","south","space","spare","spatial","spawn","speak","special","speed","spell","spend","sphere","spice","spider","spike","spin","spirit","split","spoil","sponsor","spoon","sport","spot","spray","spread","spring","spy","square","squeeze","squirrel","stable","stadium","staff","stage","stairs","stamp","stand","start","state","stay","steak","steel","stem","step","stereo","stick","still","sting","stock","stomach","stone","stool","story","stove","strategy","street","strike","strong","struggle","student","stuff","stumble","style","subject","submit","subway","success","such","sudden","suffer","sugar","suggest","suit","summer","sun","sunny","sunset","super","supply","supreme","sure","surface","surge","surprise","surround","survey","suspect","sustain","swallow","swamp","swap","swarm","swear","sweet","swift","swim","swing","switch","sword","symbol","symptom","syrup","system","table","tackle","tag","tail","talent","talk","tank","tape","target","task","taste","tattoo","taxi","teach","team","tell","ten","tenant","tennis","tent","term","test","text","thank","that","theme","then","theory","there","they","thing","this","thought","three","thrive","throw","thumb","thunder","ticket","tide","tiger","tilt","timber","time","tiny","tip","tired","tissue","title","toast","tobacco","today","toddler","toe","together","toilet","token","tomato","tomorrow","tone","tongue","tonight","tool","tooth","top","topic","topple","torch","tornado","tortoise","toss","total","tourist","toward","tower","town","toy","track","trade","traffic","tragic","train","transfer","trap","trash","travel","tray","treat","tree","trend","trial","tribe","trick","trigger","trim","trip","trophy","trouble","truck","true","truly","trumpet","trust","truth","try","tube","tuition","tumble","tuna","tunnel","turkey","turn","turtle","twelve","twenty","twice","twin","twist","two","type","typical","ugly","umbrella","unable","unaware","uncle","uncover","under","undo","unfair","unfold","unhappy","uniform","unique","unit","universe","unknown","unlock","until","unusual","unveil","update","upgrade","uphold","upon","upper","upset","urban","urge","usage","use","used","useful","useless","usual","utility","vacant","vacuum","vague","valid","valley","valve","van","vanish","vapor","various","vast","vault","vehicle","velvet","vendor","venture","venue","verb","verify","version","very","vessel","veteran","viable","vibrant","vicious","victory","video","view","village","vintage","violin","virtual","virus","visa","visit","visual","vital","vivid","vocal","voice","void","volcano","volume","vote","voyage","wage","wagon","wait","walk","wall","walnut","want","warfare","warm","warrior","wash","wasp","waste","water","wave","way","wealth","weapon","wear","weasel","weather","web","wedding","weekend","weird","welcome","west","wet","whale","what","wheat","wheel","when","where","whip","whisper","wide","width","wife","wild","will","win","window","wine","wing","wink","winner","winter","wire","wisdom","wise","wish","witness","wolf","woman","wonder","wood","wool","word","work","world","worry","worth","wrap","wreck","wrestle","wrist","write","wrong","yard","year","yellow","you","young","youth","zebra","zero","zone","zoo"]["abaisser","abandon","abdiquer","abeille","abolir","aborder","aboutir","aboyer","abrasif","abreuver","abriter","abroger","abrupt","absence","absolu","absurde","abusif","abyssal","académie","acajou","acarien","accabler","accepter","acclamer","accolade","accroche","accuser","acerbe","achat","acheter","aciduler","acier","acompte","acquérir","acronyme","acteur","actif","actuel","adepte","adéquat","adhésif","adjectif","adjuger","admettre","admirer","adopter","adorer","adoucir","adresse","adroit","adulte","adverbe","aérer","aéronef","affaire","affecter","affiche","affreux","affubler","agacer","agencer","agile","agiter","agrafer","agréable","agrume","aider","aiguille","ailier","aimable","aisance","ajouter","ajuster","alarmer","alchimie","alerte","algèbre","algue","aliéner","aliment","alléger","alliage","allouer","allumer","alourdir","alpaga","altesse","alvéole","amateur","ambigu","ambre","aménager","amertume","amidon","amiral","amorcer","amour","amovible","amphibie","ampleur","amusant","analyse","anaphore","anarchie","anatomie","ancien","anéantir","angle","angoisse","anguleux","animal","annexer","annonce","annuel","anodin","anomalie","anonyme","anormal","antenne","antidote","anxieux","apaiser","apéritif","aplanir","apologie","appareil","appeler","apporter","appuyer","aquarium","aqueduc","arbitre","arbuste","ardeur","ardoise","argent","arlequin","armature","armement","armoire","armure","arpenter","arracher","arriver","arroser","arsenic","artériel","article","aspect","asphalte","aspirer","assaut","asservir","assiette","associer","assurer","asticot","astre","astuce","atelier","atome","atrium","atroce","attaque","attentif","attirer","attraper","aubaine","auberge","audace","audible","augurer","aurore","automne","autruche","avaler","avancer","avarice","avenir","averse","aveugle","aviateur","avide","avion","aviser","avoine","avouer","avril","axial","axiome","badge","bafouer","bagage","baguette","baignade","balancer","balcon","baleine","balisage","bambin","bancaire","bandage","banlieue","bannière","banquier","barbier","baril","baron","barque","barrage","bassin","bastion","bataille","bateau","batterie","baudrier","bavarder","belette","bélier","belote","bénéfice","berceau","berger","berline","bermuda","besace","besogne","bétail","beurre","biberon","bicycle","bidule","bijou","bilan","bilingue","billard","binaire","biologie","biopsie","biotype","biscuit","bison","bistouri","bitume","bizarre","blafard","blague","blanchir","blessant","blinder","blond","bloquer","blouson","bobard","bobine","boire","boiser","bolide","bonbon","bondir","bonheur","bonifier","bonus","bordure","borne","botte","boucle","boueux","bougie","boulon","bouquin","bourse","boussole","boutique","boxeur","branche","brasier","brave","brebis","brèche","breuvage","bricoler","brigade","brillant","brioche","brique","brochure","broder","bronzer","brousse","broyeur","brume","brusque","brutal","bruyant","buffle","buisson","bulletin","bureau","burin","bustier","butiner","butoir","buvable","buvette","cabanon","cabine","cachette","cadeau","cadre","caféine","caillou","caisson","calculer","calepin","calibre","calmer","calomnie","calvaire","camarade","caméra","camion","campagne","canal","caneton","canon","cantine","canular","capable","caporal","caprice","capsule","capter","capuche","carabine","carbone","caresser","caribou","carnage","carotte","carreau","carton","cascade","casier","casque","cassure","causer","caution","cavalier","caverne","caviar","cédille","ceinture","céleste","cellule","cendrier","censurer","central","cercle","cérébral","cerise","cerner","cerveau","cesser","chagrin","chaise","chaleur","chambre","chance","chapitre","charbon","chasseur","chaton","chausson","chavirer","chemise","chenille","chéquier","chercher","cheval","chien","chiffre","chignon","chimère","chiot","chlorure","chocolat","choisir","chose","chouette","chrome","chute","cigare","cigogne","cimenter","cinéma","cintrer","circuler","cirer","cirque","citerne","citoyen","citron","civil","clairon","clameur","claquer","classe","clavier","client","cligner","climat","clivage","cloche","clonage","cloporte","cobalt","cobra","cocasse","cocotier","coder","codifier","coffre","cogner","cohésion","coiffer","coincer","colère","colibri","colline","colmater","colonel","combat","comédie","commande","compact","concert","conduire","confier","congeler","connoter","consonne","contact","convexe","copain","copie","corail","corbeau","cordage","corniche","corpus","correct","cortège","cosmique","costume","coton","coude","coupure","courage","couteau","couvrir","coyote","crabe","crainte","cravate","crayon","créature","créditer","crémeux","creuser","crevette","cribler","crier","cristal","critère","croire","croquer","crotale","crucial","cruel","crypter","cubique","cueillir","cuillère","cuisine","cuivre","culminer","cultiver","cumuler","cupide","curatif","curseur","cyanure","cycle","cylindre","cynique","daigner","damier","danger","danseur","dauphin","débattre","débiter","déborder","débrider","débutant","décaler","décembre","déchirer","décider","déclarer","décorer","décrire","décupler","dédale","déductif","déesse","défensif","défiler","défrayer","dégager","dégivrer","déglutir","dégrafer","déjeuner","délice","déloger","demander","demeurer","démolir","dénicher","dénouer","dentelle","dénuder","départ","dépenser","déphaser","déplacer","déposer","déranger","dérober","désastre","descente","désert","désigner","désobéir","dessiner","destrier","détacher","détester","détourer","détresse","devancer","devenir","deviner","devoir","diable","dialogue","diamant","dicter","différer","digérer","digital","digne","diluer","dimanche","diminuer","dioxyde","directif","diriger","discuter","disposer","dissiper","distance","divertir","diviser","docile","docteur","dogme","doigt","domaine","domicile","dompter","donateur","donjon","donner","dopamine","dortoir","dorure","dosage","doseur","dossier","dotation","douanier","double","douceur","douter","doyen","dragon","draper","dresser","dribbler","droiture","duperie","duplexe","durable","durcir","dynastie","éblouir","écarter","écharpe","échelle","éclairer","éclipse","éclore","écluse","école","économie","écorce","écouter","écraser","écrémer","écrivain","écrou","écume","écureuil","édifier","éduquer","effacer","effectif","effigie","effort","effrayer","effusion","égaliser","égarer","éjecter","élaborer","élargir","électron","élégant","éléphant","élève","éligible","élitisme","éloge","élucider","éluder","emballer","embellir","embryon","émeraude","émission","emmener","émotion","émouvoir","empereur","employer","emporter","emprise","émulsion","encadrer","enchère","enclave","encoche","endiguer","endosser","endroit","enduire","énergie","enfance","enfermer","enfouir","engager","engin","englober","énigme","enjamber","enjeu","enlever","ennemi","ennuyeux","enrichir","enrobage","enseigne","entasser","entendre","entier","entourer","entraver","énumérer","envahir","enviable","envoyer","enzyme","éolien","épaissir","épargne","épatant","épaule","épicerie","épidémie","épier","épilogue","épine","épisode","épitaphe","époque","épreuve","éprouver","épuisant","équerre","équipe","ériger","érosion","erreur","éruption","escalier","espadon","espèce","espiègle","espoir","esprit","esquiver","essayer","essence","essieu","essorer","estime","estomac","estrade","étagère","étaler","étanche","étatique","éteindre","étendoir","éternel","éthanol","éthique","ethnie","étirer","étoffer","étoile","étonnant","étourdir","étrange","étroit","étude","euphorie","évaluer","évasion","éventail","évidence","éviter","évolutif","évoquer","exact","exagérer","exaucer","exceller","excitant","exclusif","excuse","exécuter","exemple","exercer","exhaler","exhorter","exigence","exiler","exister","exotique","expédier","explorer","exposer","exprimer","exquis","extensif","extraire","exulter","fable","fabuleux","facette","facile","facture","faiblir","falaise","fameux","famille","farceur","farfelu","farine","farouche","fasciner","fatal","fatigue","faucon","fautif","faveur","favori","fébrile","féconder","fédérer","félin","femme","fémur","fendoir","féodal","fermer","féroce","ferveur","festival","feuille","feutre","février","fiasco","ficeler","fictif","fidèle","figure","filature","filetage","filière","filleul","filmer","filou","filtrer","financer","finir","fiole","firme","fissure","fixer","flairer","flamme","flasque","flatteur","fléau","flèche","fleur","flexion","flocon","flore","fluctuer","fluide","fluvial","folie","fonderie","fongible","fontaine","forcer","forgeron","formuler","fortune","fossile","foudre","fougère","fouiller","foulure","fourmi","fragile","fraise","franchir","frapper","frayeur","frégate","freiner","frelon","frémir","frénésie","frère","friable","friction","frisson","frivole","froid","fromage","frontal","frotter","fruit","fugitif","fuite","fureur","furieux","furtif","fusion","futur","gagner","galaxie","galerie","gambader","garantir","gardien","garnir","garrigue","gazelle","gazon","géant","gélatine","gélule","gendarme","général","génie","genou","gentil","géologie","géomètre","géranium","germe","gestuel","geyser","gibier","gicler","girafe","givre","glace","glaive","glisser","globe","gloire","glorieux","golfeur","gomme","gonfler","gorge","gorille","goudron","gouffre","goulot","goupille","gourmand","goutte","graduel","graffiti","graine","grand","grappin","gratuit","gravir","grenat","griffure","griller","grimper","grogner","gronder","grotte","groupe","gruger","grutier","gruyère","guépard","guerrier","guide","guimauve","guitare","gustatif","gymnaste","gyrostat","habitude","hachoir","halte","hameau","hangar","hanneton","haricot","harmonie","harpon","hasard","hélium","hématome","herbe","hérisson","hermine","héron","hésiter","heureux","hiberner","hibou","hilarant","histoire","hiver","homard","hommage","homogène","honneur","honorer","honteux","horde","horizon","horloge","hormone","horrible","houleux","housse","hublot","huileux","humain","humble","humide","humour","hurler","hydromel","hygiène","hymne","hypnose","idylle","ignorer","iguane","illicite","illusion","image","imbiber","imiter","immense","immobile","immuable","impact","impérial","implorer","imposer","imprimer","imputer","incarner","incendie","incident","incliner","incolore","indexer","indice","inductif","inédit","ineptie","inexact","infini","infliger","informer","infusion","ingérer","inhaler","inhiber","injecter","injure","innocent","inoculer","inonder","inscrire","insecte","insigne","insolite","inspirer","instinct","insulter","intact","intense","intime","intrigue","intuitif","inutile","invasion","inventer","inviter","invoquer","ironique","irradier","irréel","irriter","isoler","ivoire","ivresse","jaguar","jaillir","jambe","janvier","jardin","jauger","jaune","javelot","jetable","jeton","jeudi","jeunesse","joindre","joncher","jongler","joueur","jouissif","journal","jovial","joyau","joyeux","jubiler","jugement","junior","jupon","juriste","justice","juteux","juvénile","kayak","kimono","kiosque","label","labial","labourer","lacérer","lactose","lagune","laine","laisser","laitier","lambeau","lamelle","lampe","lanceur","langage","lanterne","lapin","largeur","larme","laurier","lavabo","lavoir","lecture","légal","léger","légume","lessive","lettre","levier","lexique","lézard","liasse","libérer","libre","licence","licorne","liège","lièvre","ligature","ligoter","ligue","limer","limite","limonade","limpide","linéaire","lingot","lionceau","liquide","lisière","lister","lithium","litige","littoral","livreur","logique","lointain","loisir","lombric","loterie","louer","lourd","loutre","louve","loyal","lubie","lucide","lucratif","lueur","lugubre","luisant","lumière","lunaire","lundi","luron","lutter","luxueux","machine","magasin","magenta","magique","maigre","maillon","maintien","mairie","maison","majorer","malaxer","maléfice","malheur","malice","mallette","mammouth","mandater","maniable","manquant","manteau","manuel","marathon","marbre","marchand","mardi","maritime","marqueur","marron","marteler","mascotte","massif","matériel","matière","matraque","maudire","maussade","mauve","maximal","méchant","méconnu","médaille","médecin","méditer","méduse","meilleur","mélange","mélodie","membre","mémoire","menacer","mener","menhir","mensonge","mentor","mercredi","mérite","merle","messager","mesure","métal","météore","méthode","métier","meuble","miauler","microbe","miette","mignon","migrer","milieu","million","mimique","mince","minéral","minimal","minorer","minute","miracle","miroiter","missile","mixte","mobile","moderne","moelleux","mondial","moniteur","monnaie","monotone","monstre","montagne","monument","moqueur","morceau","morsure","mortier","moteur","motif","mouche","moufle","moulin","mousson","mouton","mouvant","multiple","munition","muraille","murène","murmure","muscle","muséum","musicien","mutation","muter","mutuel","myriade","myrtille","mystère","mythique","nageur","nappe","narquois","narrer","natation","nation","nature","naufrage","nautique","navire","nébuleux","nectar","néfaste","négation","négliger","négocier","neige","nerveux","nettoyer","neurone","neutron","neveu","niche","nickel","nitrate","niveau","noble","nocif","nocturne","noirceur","noisette","nomade","nombreux","nommer","normatif","notable","notifier","notoire","nourrir","nouveau","novateur","novembre","novice","nuage","nuancer","nuire","nuisible","numéro","nuptial","nuque","nutritif","obéir","objectif","obliger","obscur","observer","obstacle","obtenir","obturer","occasion","occuper","océan","octobre","octroyer","octupler","oculaire","odeur","odorant","offenser","officier","offrir","ogive","oiseau","oisillon","olfactif","olivier","ombrage","omettre","onctueux","onduler","onéreux","onirique","opale","opaque","opérer","opinion","opportun","opprimer","opter","optique","orageux","orange","orbite","ordonner","oreille","organe","orgueil","orifice","ornement","orque","ortie","osciller","osmose","ossature","otarie","ouragan","ourson","outil","outrager","ouvrage","ovation","oxyde","oxygène","ozone","paisible","palace","palmarès","palourde","palper","panache","panda","pangolin","paniquer","panneau","panorama","pantalon","papaye","papier","papoter","papyrus","paradoxe","parcelle","paresse","parfumer","parler","parole","parrain","parsemer","partager","parure","parvenir","passion","pastèque","paternel","patience","patron","pavillon","pavoiser","payer","paysage","peigne","peintre","pelage","pélican","pelle","pelouse","peluche","pendule","pénétrer","pénible","pensif","pénurie","pépite","péplum","perdrix","perforer","période","permuter","perplexe","persil","perte","peser","pétale","petit","pétrir","peuple","pharaon","phobie","phoque","photon","phrase","physique","piano","pictural","pièce","pierre","pieuvre","pilote","pinceau","pipette","piquer","pirogue","piscine","piston","pivoter","pixel","pizza","placard","plafond","plaisir","planer","plaque","plastron","plateau","pleurer","plexus","pliage","plomb","plonger","pluie","plumage","pochette","poésie","poète","pointe","poirier","poisson","poivre","polaire","policier","pollen","polygone","pommade","pompier","ponctuel","pondérer","poney","portique","position","posséder","posture","potager","poteau","potion","pouce","poulain","poumon","pourpre","poussin","pouvoir","prairie","pratique","précieux","prédire","préfixe","prélude","prénom","présence","prétexte","prévoir","primitif","prince","prison","priver","problème","procéder","prodige","profond","progrès","proie","projeter","prologue","promener","propre","prospère","protéger","prouesse","proverbe","prudence","pruneau","psychose","public","puceron","puiser","pulpe","pulsar","punaise","punitif","pupitre","purifier","puzzle","pyramide","quasar","querelle","question","quiétude","quitter","quotient","racine","raconter","radieux","ragondin","raideur","raisin","ralentir","rallonge","ramasser","rapide","rasage","ratisser","ravager","ravin","rayonner","réactif","réagir","réaliser","réanimer","recevoir","réciter","réclamer","récolter","recruter","reculer","recycler","rédiger","redouter","refaire","réflexe","réformer","refrain","refuge","régalien","région","réglage","régulier","réitérer","rejeter","rejouer","relatif","relever","relief","remarque","remède","remise","remonter","remplir","remuer","renard","renfort","renifler","renoncer","rentrer","renvoi","replier","reporter","reprise","reptile","requin","réserve","résineux","résoudre","respect","rester","résultat","rétablir","retenir","réticule","retomber","retracer","réunion","réussir","revanche","revivre","révolte","révulsif","richesse","rideau","rieur","rigide","rigoler","rincer","riposter","risible","risque","rituel","rival","rivière","rocheux","romance","rompre","ronce","rondin","roseau","rosier","rotatif","rotor","rotule","rouge","rouille","rouleau","routine","royaume","ruban","rubis","ruche","ruelle","rugueux","ruiner","ruisseau","ruser","rustique","rythme","sabler","saboter","sabre","sacoche","safari","sagesse","saisir","salade","salive","salon","saluer","samedi","sanction","sanglier","sarcasme","sardine","saturer","saugrenu","saumon","sauter","sauvage","savant","savonner","scalpel","scandale","scélérat","scénario","sceptre","schéma","science","scinder","score","scrutin","sculpter","séance","sécable","sécher","secouer","sécréter","sédatif","séduire","seigneur","séjour","sélectif","semaine","sembler","semence","séminal","sénateur","sensible","sentence","séparer","séquence","serein","sergent","sérieux","serrure","sérum","service","sésame","sévir","sevrage","sextuple","sidéral","siècle","siéger","siffler","sigle","signal","silence","silicium","simple","sincère","sinistre","siphon","sirop","sismique","situer","skier","social","socle","sodium","soigneux","soldat","soleil","solitude","soluble","sombre","sommeil","somnoler","sonde","songeur","sonnette","sonore","sorcier","sortir","sosie","sottise","soucieux","soudure","souffle","soulever","soupape","source","soutirer","souvenir","spacieux","spatial","spécial","sphère","spiral","stable","station","sternum","stimulus","stipuler","strict","studieux","stupeur","styliste","sublime","substrat","subtil","subvenir","succès","sucre","suffixe","suggérer","suiveur","sulfate","superbe","supplier","surface","suricate","surmener","surprise","sursaut","survie","suspect","syllabe","symbole","symétrie","synapse","syntaxe","système","tabac","tablier","tactile","tailler","talent","talisman","talonner","tambour","tamiser","tangible","tapis","taquiner","tarder","tarif","tartine","tasse","tatami","tatouage","taupe","taureau","taxer","témoin","temporel","tenaille","tendre","teneur","tenir","tension","terminer","terne","terrible","tétine","texte","thème","théorie","thérapie","thorax","tibia","tiède","timide","tirelire","tiroir","tissu","titane","titre","tituber","toboggan","tolérant","tomate","tonique","tonneau","toponyme","torche","tordre","tornade","torpille","torrent","torse","tortue","totem","toucher","tournage","tousser","toxine","traction","trafic","tragique","trahir","train","trancher","travail","trèfle","tremper","trésor","treuil","triage","tribunal","tricoter","trilogie","triomphe","tripler","triturer","trivial","trombone","tronc","tropical","troupeau","tuile","tulipe","tumulte","tunnel","turbine","tuteur","tutoyer","tuyau","tympan","typhon","typique","tyran","ubuesque","ultime","ultrason","unanime","unifier","union","unique","unitaire","univers","uranium","urbain","urticant","usage","usine","usuel","usure","utile","utopie","vacarme","vaccin","vagabond","vague","vaillant","vaincre","vaisseau","valable","valise","vallon","valve","vampire","vanille","vapeur","varier","vaseux","vassal","vaste","vecteur","vedette","végétal","véhicule","veinard","véloce","vendredi","vénérer","venger","venimeux","ventouse","verdure","vérin","vernir","verrou","verser","vertu","veston","vétéran","vétuste","vexant","vexer","viaduc","viande","victoire","vidange","vidéo","vignette","vigueur","vilain","village","vinaigre","violon","vipère","virement","virtuose","virus","visage","viseur","vision","visqueux","visuel","vital","vitesse","viticole","vitrine","vivace","vivipare","vocation","voguer","voile","voisin","voiture","volaille","volcan","voltiger","volume","vorace","vortex","voter","vouloir","voyage","voyelle","wagon","xénon","yacht","zèbre","zénith","zeste","zoologie"]// note all JSON files in this folder are generated with npm run generate-wordlist
const lookup = require('./lookup.json')

module.exports = {
  loadWordlist,
  detectLanguage
}

function loadWordlist (language) {
  switch (language) {
    case 'chinese_simplified': return require('./chinese_simplified.json')
    case 'chinese_traditional': return require('./chinese_traditional.json')
    case 'czech': return require('./czech.json')
    case 'english': return require('./english.json')
    case 'french': return require('./french.json')
    case 'italian': return require('./italian.json')
    case 'japanese': return require('./japanese.json')
    case 'korean': return require('./korean.json')
    case 'portuguese': return require('./portuguese.json')
    case 'russian': return require('./russian.json')
    case 'spanish': return require('./spanish.json')
    case 'turkish': return require('./turkish.json')
  }

  throw new Error('Unknown language: ' + language)
}

function detectLanguage (words) {
  let candidate = null

  for (const word of words) {
    const languages = lookup[word]
    if (!languages) return null

    if (languages.length === 1) return languages[0]

    if (!candidate) {
      candidate = languages
      continue
    }

    const intersect = candidate.filter(l => languages.includes(l))
    if (intersect.length === 1) return intersect[0]
  }

  return candidate[0]
}
["abaco","abbaglio","abbinato","abete","abisso","abolire","abrasivo","abrogato","accadere","accenno","accusato","acetone","achille","acido","acqua","acre","acrilico","acrobata","acuto","adagio","addebito","addome","adeguato","aderire","adipe","adottare","adulare","affabile","affetto","affisso","affranto","aforisma","afoso","africano","agave","agente","agevole","aggancio","agire","agitare","agonismo","agricolo","agrumeto","aguzzo","alabarda","alato","albatro","alberato","albo","albume","alce","alcolico","alettone","alfa","algebra","aliante","alibi","alimento","allagato","allegro","allievo","allodola","allusivo","almeno","alogeno","alpaca","alpestre","altalena","alterno","alticcio","altrove","alunno","alveolo","alzare","amalgama","amanita","amarena","ambito","ambrato","ameba","america","ametista","amico","ammasso","ammenda","ammirare","ammonito","amore","ampio","ampliare","amuleto","anacardo","anagrafe","analista","anarchia","anatra","anca","ancella","ancora","andare","andrea","anello","angelo","angolare","angusto","anima","annegare","annidato","anno","annuncio","anonimo","anticipo","anzi","apatico","apertura","apode","apparire","appetito","appoggio","approdo","appunto","aprile","arabica","arachide","aragosta","araldica","arancio","aratura","arazzo","arbitro","archivio","ardito","arenile","argento","argine","arguto","aria","armonia","arnese","arredato","arringa","arrosto","arsenico","arso","artefice","arzillo","asciutto","ascolto","asepsi","asettico","asfalto","asino","asola","aspirato","aspro","assaggio","asse","assoluto","assurdo","asta","astenuto","astice","astratto","atavico","ateismo","atomico","atono","attesa","attivare","attorno","attrito","attuale","ausilio","austria","autista","autonomo","autunno","avanzato","avere","avvenire","avviso","avvolgere","azione","azoto","azzimo","azzurro","babele","baccano","bacino","baco","badessa","badilata","bagnato","baita","balcone","baldo","balena","ballata","balzano","bambino","bandire","baraonda","barbaro","barca","baritono","barlume","barocco","basilico","basso","batosta","battuto","baule","bava","bavosa","becco","beffa","belgio","belva","benda","benevole","benigno","benzina","bere","berlina","beta","bibita","bici","bidone","bifido","biga","bilancia","bimbo","binocolo","biologo","bipede","bipolare","birbante","birra","biscotto","bisesto","bisnonno","bisonte","bisturi","bizzarro","blando","blatta","bollito","bonifico","bordo","bosco","botanico","bottino","bozzolo","braccio","bradipo","brama","branca","bravura","bretella","brevetto","brezza","briglia","brillante","brindare","broccolo","brodo","bronzina","brullo","bruno","bubbone","buca","budino","buffone","buio","bulbo","buono","burlone","burrasca","bussola","busta","cadetto","caduco","calamaro","calcolo","calesse","calibro","calmo","caloria","cambusa","camerata","camicia","cammino","camola","campale","canapa","candela","cane","canino","canotto","cantina","capace","capello","capitolo","capogiro","cappero","capra","capsula","carapace","carcassa","cardo","carisma","carovana","carretto","cartolina","casaccio","cascata","caserma","caso","cassone","castello","casuale","catasta","catena","catrame","cauto","cavillo","cedibile","cedrata","cefalo","celebre","cellulare","cena","cenone","centesimo","ceramica","cercare","certo","cerume","cervello","cesoia","cespo","ceto","chela","chiaro","chicca","chiedere","chimera","china","chirurgo","chitarra","ciao","ciclismo","cifrare","cigno","cilindro","ciottolo","circa","cirrosi","citrico","cittadino","ciuffo","civetta","civile","classico","clinica","cloro","cocco","codardo","codice","coerente","cognome","collare","colmato","colore","colposo","coltivato","colza","coma","cometa","commando","comodo","computer","comune","conciso","condurre","conferma","congelare","coniuge","connesso","conoscere","consumo","continuo","convegno","coperto","copione","coppia","copricapo","corazza","cordata","coricato","cornice","corolla","corpo","corredo","corsia","cortese","cosmico","costante","cottura","covato","cratere","cravatta","creato","credere","cremoso","crescita","creta","criceto","crinale","crisi","critico","croce","cronaca","crostata","cruciale","crusca","cucire","cuculo","cugino","cullato","cupola","curatore","cursore","curvo","cuscino","custode","dado","daino","dalmata","damerino","daniela","dannoso","danzare","datato","davanti","davvero","debutto","decennio","deciso","declino","decollo","decreto","dedicato","definito","deforme","degno","delegare","delfino","delirio","delta","demenza","denotato","dentro","deposito","derapata","derivare","deroga","descritto","deserto","desiderio","desumere","detersivo","devoto","diametro","dicembre","diedro","difeso","diffuso","digerire","digitale","diluvio","dinamico","dinnanzi","dipinto","diploma","dipolo","diradare","dire","dirotto","dirupo","disagio","discreto","disfare","disgelo","disposto","distanza","disumano","dito","divano","divelto","dividere","divorato","doblone","docente","doganale","dogma","dolce","domato","domenica","dominare","dondolo","dono","dormire","dote","dottore","dovuto","dozzina","drago","druido","dubbio","dubitare","ducale","duna","duomo","duplice","duraturo","ebano","eccesso","ecco","eclissi","economia","edera","edicola","edile","editoria","educare","egemonia","egli","egoismo","egregio","elaborato","elargire","elegante","elencato","eletto","elevare","elfico","elica","elmo","elsa","eluso","emanato","emblema","emesso","emiro","emotivo","emozione","empirico","emulo","endemico","enduro","energia","enfasi","enoteca","entrare","enzima","epatite","epilogo","episodio","epocale","eppure","equatore","erario","erba","erboso","erede","eremita","erigere","ermetico","eroe","erosivo","errante","esagono","esame","esanime","esaudire","esca","esempio","esercito","esibito","esigente","esistere","esito","esofago","esortato","esoso","espanso","espresso","essenza","esso","esteso","estimare","estonia","estroso","esultare","etilico","etnico","etrusco","etto","euclideo","europa","evaso","evidenza","evitato","evoluto","evviva","fabbrica","faccenda","fachiro","falco","famiglia","fanale","fanfara","fango","fantasma","fare","farfalla","farinoso","farmaco","fascia","fastoso","fasullo","faticare","fato","favoloso","febbre","fecola","fede","fegato","felpa","feltro","femmina","fendere","fenomeno","fermento","ferro","fertile","fessura","festivo","fetta","feudo","fiaba","fiducia","fifa","figurato","filo","finanza","finestra","finire","fiore","fiscale","fisico","fiume","flacone","flamenco","flebo","flemma","florido","fluente","fluoro","fobico","focaccia","focoso","foderato","foglio","folata","folclore","folgore","fondente","fonetico","fonia","fontana","forbito","forchetta","foresta","formica","fornaio","foro","fortezza","forzare","fosfato","fosso","fracasso","frana","frassino","fratello","freccetta","frenata","fresco","frigo","frollino","fronde","frugale","frutta","fucilata","fucsia","fuggente","fulmine","fulvo","fumante","fumetto","fumoso","fune","funzione","fuoco","furbo","furgone","furore","fuso","futile","gabbiano","gaffe","galateo","gallina","galoppo","gambero","gamma","garanzia","garbo","garofano","garzone","gasdotto","gasolio","gastrico","gatto","gaudio","gazebo","gazzella","geco","gelatina","gelso","gemello","gemmato","gene","genitore","gennaio","genotipo","gergo","ghepardo","ghiaccio","ghisa","giallo","gilda","ginepro","giocare","gioiello","giorno","giove","girato","girone","gittata","giudizio","giurato","giusto","globulo","glutine","gnomo","gobba","golf","gomito","gommone","gonfio","gonna","governo","gracile","grado","grafico","grammo","grande","grattare","gravoso","grazia","greca","gregge","grifone","grigio","grinza","grotta","gruppo","guadagno","guaio","guanto","guardare","gufo","guidare","ibernato","icona","identico","idillio","idolo","idra","idrico","idrogeno","igiene","ignaro","ignorato","ilare","illeso","illogico","illudere","imballo","imbevuto","imbocco","imbuto","immane","immerso","immolato","impacco","impeto","impiego","importo","impronta","inalare","inarcare","inattivo","incanto","incendio","inchino","incisivo","incluso","incontro","incrocio","incubo","indagine","india","indole","inedito","infatti","infilare","inflitto","ingaggio","ingegno","inglese","ingordo","ingrosso","innesco","inodore","inoltrare","inondato","insano","insetto","insieme","insonnia","insulina","intasato","intero","intonaco","intuito","inumidire","invalido","invece","invito","iperbole","ipnotico","ipotesi","ippica","iride","irlanda","ironico","irrigato","irrorare","isolato","isotopo","isterico","istituto","istrice","italia","iterare","labbro","labirinto","lacca","lacerato","lacrima","lacuna","laddove","lago","lampo","lancetta","lanterna","lardoso","larga","laringe","lastra","latenza","latino","lattuga","lavagna","lavoro","legale","leggero","lembo","lentezza","lenza","leone","lepre","lesivo","lessato","lesto","letterale","leva","levigato","libero","lido","lievito","lilla","limatura","limitare","limpido","lineare","lingua","liquido","lira","lirica","lisca","lite","litigio","livrea","locanda","lode","logica","lombare","londra","longevo","loquace","lorenzo","loto","lotteria","luce","lucidato","lumaca","luminoso","lungo","lupo","luppolo","lusinga","lusso","lutto","macabro","macchina","macero","macinato","madama","magico","maglia","magnete","magro","maiolica","malafede","malgrado","malinteso","malsano","malto","malumore","mana","mancia","mandorla","mangiare","manifesto","mannaro","manovra","mansarda","mantide","manubrio","mappa","maratona","marcire","maretta","marmo","marsupio","maschera","massaia","mastino","materasso","matricola","mattone","maturo","mazurca","meandro","meccanico","mecenate","medesimo","meditare","mega","melassa","melis","melodia","meninge","meno","mensola","mercurio","merenda","merlo","meschino","mese","messere","mestolo","metallo","metodo","mettere","miagolare","mica","micelio","michele","microbo","midollo","miele","migliore","milano","milite","mimosa","minerale","mini","minore","mirino","mirtillo","miscela","missiva","misto","misurare","mitezza","mitigare","mitra","mittente","mnemonico","modello","modifica","modulo","mogano","mogio","mole","molosso","monastero","monco","mondina","monetario","monile","monotono","monsone","montato","monviso","mora","mordere","morsicato","mostro","motivato","motosega","motto","movenza","movimento","mozzo","mucca","mucosa","muffa","mughetto","mugnaio","mulatto","mulinello","multiplo","mummia","munto","muovere","murale","musa","muscolo","musica","mutevole","muto","nababbo","nafta","nanometro","narciso","narice","narrato","nascere","nastrare","naturale","nautica","naviglio","nebulosa","necrosi","negativo","negozio","nemmeno","neofita","neretto","nervo","nessuno","nettuno","neutrale","neve","nevrotico","nicchia","ninfa","nitido","nobile","nocivo","nodo","nome","nomina","nordico","normale","norvegese","nostrano","notare","notizia","notturno","novella","nucleo","nulla","numero","nuovo","nutrire","nuvola","nuziale","oasi","obbedire","obbligo","obelisco","oblio","obolo","obsoleto","occasione","occhio","occidente","occorrere","occultare","ocra","oculato","odierno","odorare","offerta","offrire","offuscato","oggetto","oggi","ognuno","olandese","olfatto","oliato","oliva","ologramma","oltre","omaggio","ombelico","ombra","omega","omissione","ondoso","onere","onice","onnivoro","onorevole","onta","operato","opinione","opposto","oracolo","orafo","ordine","orecchino","orefice","orfano","organico","origine","orizzonte","orma","ormeggio","ornativo","orologio","orrendo","orribile","ortensia","ortica","orzata","orzo","osare","oscurare","osmosi","ospedale","ospite","ossa","ossidare","ostacolo","oste","otite","otre","ottagono","ottimo","ottobre","ovale","ovest","ovino","oviparo","ovocito","ovunque","ovviare","ozio","pacchetto","pace","pacifico","padella","padrone","paese","paga","pagina","palazzina","palesare","pallido","palo","palude","pandoro","pannello","paolo","paonazzo","paprica","parabola","parcella","parere","pargolo","pari","parlato","parola","partire","parvenza","parziale","passivo","pasticca","patacca","patologia","pattume","pavone","peccato","pedalare","pedonale","peggio","peloso","penare","pendice","penisola","pennuto","penombra","pensare","pentola","pepe","pepita","perbene","percorso","perdonato","perforare","pergamena","periodo","permesso","perno","perplesso","persuaso","pertugio","pervaso","pesatore","pesista","peso","pestifero","petalo","pettine","petulante","pezzo","piacere","pianta","piattino","piccino","picozza","piega","pietra","piffero","pigiama","pigolio","pigro","pila","pilifero","pillola","pilota","pimpante","pineta","pinna","pinolo","pioggia","piombo","piramide","piretico","pirite","pirolisi","pitone","pizzico","placebo","planare","plasma","platano","plenario","pochezza","poderoso","podismo","poesia","poggiare","polenta","poligono","pollice","polmonite","polpetta","polso","poltrona","polvere","pomice","pomodoro","ponte","popoloso","porfido","poroso","porpora","porre","portata","posa","positivo","possesso","postulato","potassio","potere","pranzo","prassi","pratica","precluso","predica","prefisso","pregiato","prelievo","premere","prenotare","preparato","presenza","pretesto","prevalso","prima","principe","privato","problema","procura","produrre","profumo","progetto","prolunga","promessa","pronome","proposta","proroga","proteso","prova","prudente","prugna","prurito","psiche","pubblico","pudica","pugilato","pugno","pulce","pulito","pulsante","puntare","pupazzo","pupilla","puro","quadro","qualcosa","quasi","querela","quota","raccolto","raddoppio","radicale","radunato","raffica","ragazzo","ragione","ragno","ramarro","ramingo","ramo","randagio","rantolare","rapato","rapina","rappreso","rasatura","raschiato","rasente","rassegna","rastrello","rata","ravveduto","reale","recepire","recinto","recluta","recondito","recupero","reddito","redimere","regalato","registro","regola","regresso","relazione","remare","remoto","renna","replica","reprimere","reputare","resa","residente","responso","restauro","rete","retina","retorica","rettifica","revocato","riassunto","ribadire","ribelle","ribrezzo","ricarica","ricco","ricevere","riciclato","ricordo","ricreduto","ridicolo","ridurre","rifasare","riflesso","riforma","rifugio","rigare","rigettato","righello","rilassato","rilevato","rimanere","rimbalzo","rimedio","rimorchio","rinascita","rincaro","rinforzo","rinnovo","rinomato","rinsavito","rintocco","rinuncia","rinvenire","riparato","ripetuto","ripieno","riportare","ripresa","ripulire","risata","rischio","riserva","risibile","riso","rispetto","ristoro","risultato","risvolto","ritardo","ritegno","ritmico","ritrovo","riunione","riva","riverso","rivincita","rivolto","rizoma","roba","robotico","robusto","roccia","roco","rodaggio","rodere","roditore","rogito","rollio","romantico","rompere","ronzio","rosolare","rospo","rotante","rotondo","rotula","rovescio","rubizzo","rubrica","ruga","rullino","rumine","rumoroso","ruolo","rupe","russare","rustico","sabato","sabbiare","sabotato","sagoma","salasso","saldatura","salgemma","salivare","salmone","salone","saltare","saluto","salvo","sapere","sapido","saporito","saraceno","sarcasmo","sarto","sassoso","satellite","satira","satollo","saturno","savana","savio","saziato","sbadiglio","sbalzo","sbancato","sbarra","sbattere","sbavare","sbendare","sbirciare","sbloccato","sbocciato","sbrinare","sbruffone","sbuffare","scabroso","scadenza","scala","scambiare","scandalo","scapola","scarso","scatenare","scavato","scelto","scenico","scettro","scheda","schiena","sciarpa","scienza","scindere","scippo","sciroppo","scivolo","sclerare","scodella","scolpito","scomparto","sconforto","scoprire","scorta","scossone","scozzese","scriba","scrollare","scrutinio","scuderia","scultore","scuola","scuro","scusare","sdebitare","sdoganare","seccatura","secondo","sedano","seggiola","segnalato","segregato","seguito","selciato","selettivo","sella","selvaggio","semaforo","sembrare","seme","seminato","sempre","senso","sentire","sepolto","sequenza","serata","serbato","sereno","serio","serpente","serraglio","servire","sestina","setola","settimana","sfacelo","sfaldare","sfamato","sfarzoso","sfaticato","sfera","sfida","sfilato","sfinge","sfocato","sfoderare","sfogo","sfoltire","sforzato","sfratto","sfruttato","sfuggito","sfumare","sfuso","sgabello","sgarbato","sgonfiare","sgorbio","sgrassato","sguardo","sibilo","siccome","sierra","sigla","signore","silenzio","sillaba","simbolo","simpatico","simulato","sinfonia","singolo","sinistro","sino","sintesi","sinusoide","sipario","sisma","sistole","situato","slitta","slogatura","sloveno","smarrito","smemorato","smentito","smeraldo","smilzo","smontare","smottato","smussato","snellire","snervato","snodo","sobbalzo","sobrio","soccorso","sociale","sodale","soffitto","sogno","soldato","solenne","solido","sollazzo","solo","solubile","solvente","somatico","somma","sonda","sonetto","sonnifero","sopire","soppeso","sopra","sorgere","sorpasso","sorriso","sorso","sorteggio","sorvolato","sospiro","sosta","sottile","spada","spalla","spargere","spatola","spavento","spazzola","specie","spedire","spegnere","spelatura","speranza","spessore","spettrale","spezzato","spia","spigoloso","spillato","spinoso","spirale","splendido","sportivo","sposo","spranga","sprecare","spronato","spruzzo","spuntino","squillo","sradicare","srotolato","stabile","stacco","staffa","stagnare","stampato","stantio","starnuto","stasera","statuto","stelo","steppa","sterzo","stiletto","stima","stirpe","stivale","stizzoso","stonato","storico","strappo","stregato","stridulo","strozzare","strutto","stuccare","stufo","stupendo","subentro","succoso","sudore","suggerito","sugo","sultano","suonare","superbo","supporto","surgelato","surrogato","sussurro","sutura","svagare","svedese","sveglio","svelare","svenuto","svezia","sviluppo","svista","svizzera","svolta","svuotare","tabacco","tabulato","tacciare","taciturno","tale","talismano","tampone","tannino","tara","tardivo","targato","tariffa","tarpare","tartaruga","tasto","tattico","taverna","tavolata","tazza","teca","tecnico","telefono","temerario","tempo","temuto","tendone","tenero","tensione","tentacolo","teorema","terme","terrazzo","terzetto","tesi","tesserato","testato","tetro","tettoia","tifare","tigella","timbro","tinto","tipico","tipografo","tiraggio","tiro","titanio","titolo","titubante","tizio","tizzone","toccare","tollerare","tolto","tombola","tomo","tonfo","tonsilla","topazio","topologia","toppa","torba","tornare","torrone","tortora","toscano","tossire","tostatura","totano","trabocco","trachea","trafila","tragedia","tralcio","tramonto","transito","trapano","trarre","trasloco","trattato","trave","treccia","tremolio","trespolo","tributo","tricheco","trifoglio","trillo","trincea","trio","tristezza","triturato","trivella","tromba","trono","troppo","trottola","trovare","truccato","tubatura","tuffato","tulipano","tumulto","tunisia","turbare","turchino","tuta","tutela","ubicato","uccello","uccisore","udire","uditivo","uffa","ufficio","uguale","ulisse","ultimato","umano","umile","umorismo","uncinetto","ungere","ungherese","unicorno","unificato","unisono","unitario","unte","uovo","upupa","uragano","urgenza","urlo","usanza","usato","uscito","usignolo","usuraio","utensile","utilizzo","utopia","vacante","vaccinato","vagabondo","vagliato","valanga","valgo","valico","valletta","valoroso","valutare","valvola","vampata","vangare","vanitoso","vano","vantaggio","vanvera","vapore","varano","varcato","variante","vasca","vedetta","vedova","veduto","vegetale","veicolo","velcro","velina","velluto","veloce","venato","vendemmia","vento","verace","verbale","vergogna","verifica","vero","verruca","verticale","vescica","vessillo","vestale","veterano","vetrina","vetusto","viandante","vibrante","vicenda","vichingo","vicinanza","vidimare","vigilia","vigneto","vigore","vile","villano","vimini","vincitore","viola","vipera","virgola","virologo","virulento","viscoso","visione","vispo","vissuto","visura","vita","vitello","vittima","vivanda","vivido","viziare","voce","voga","volatile","volere","volpe","voragine","vulcano","zampogna","zanna","zappato","zattera","zavorra","zefiro","zelante","zelo","zenzero","zerbino","zibetto","zinco","zircone","zitto","zolla","zotico","zucchero","zufolo","zulu","zuppa"]["あいこくしん","あいさつ","あいだ","あおぞら","あかちゃん","あきる","あけがた","あける","あこがれる","あさい","あさひ","あしあと","あじわう","あずかる","あずき","あそぶ","あたえる","あたためる","あたりまえ","あたる","あつい","あつかう","あっしゅく","あつまり","あつめる","あてな","あてはまる","あひる","あぶら","あぶる","あふれる","あまい","あまど","あまやかす","あまり","あみもの","あめりか","あやまる","あゆむ","あらいぐま","あらし","あらすじ","あらためる","あらゆる","あらわす","ありがとう","あわせる","あわてる","あんい","あんがい","あんこ","あんぜん","あんてい","あんない","あんまり","いいだす","いおん","いがい","いがく","いきおい","いきなり","いきもの","いきる","いくじ","いくぶん","いけばな","いけん","いこう","いこく","いこつ","いさましい","いさん","いしき","いじゅう","いじょう","いじわる","いずみ","いずれ","いせい","いせえび","いせかい","いせき","いぜん","いそうろう","いそがしい","いだい","いだく","いたずら","いたみ","いたりあ","いちおう","いちじ","いちど","いちば","いちぶ","いちりゅう","いつか","いっしゅん","いっせい","いっそう","いったん","いっち","いってい","いっぽう","いてざ","いてん","いどう","いとこ","いない","いなか","いねむり","いのち","いのる","いはつ","いばる","いはん","いびき","いひん","いふく","いへん","いほう","いみん","いもうと","いもたれ","いもり","いやがる","いやす","いよかん","いよく","いらい","いらすと","いりぐち","いりょう","いれい","いれもの","いれる","いろえんぴつ","いわい","いわう","いわかん","いわば","いわゆる","いんげんまめ","いんさつ","いんしょう","いんよう","うえき","うえる","うおざ","うがい","うかぶ","うかべる","うきわ","うくらいな","うくれれ","うけたまわる","うけつけ","うけとる","うけもつ","うける","うごかす","うごく","うこん","うさぎ","うしなう","うしろがみ","うすい","うすぎ","うすぐらい","うすめる","うせつ","うちあわせ","うちがわ","うちき","うちゅう","うっかり","うつくしい","うったえる","うつる","うどん","うなぎ","うなじ","うなずく","うなる","うねる","うのう","うぶげ","うぶごえ","うまれる","うめる","うもう","うやまう","うよく","うらがえす","うらぐち","うらない","うりあげ","うりきれ","うるさい","うれしい","うれゆき","うれる","うろこ","うわき","うわさ","うんこう","うんちん","うんてん","うんどう","えいえん","えいが","えいきょう","えいご","えいせい","えいぶん","えいよう","えいわ","えおり","えがお","えがく","えきたい","えくせる","えしゃく","えすて","えつらん","えのぐ","えほうまき","えほん","えまき","えもじ","えもの","えらい","えらぶ","えりあ","えんえん","えんかい","えんぎ","えんげき","えんしゅう","えんぜつ","えんそく","えんちょう","えんとつ","おいかける","おいこす","おいしい","おいつく","おうえん","おうさま","おうじ","おうせつ","おうたい","おうふく","おうべい","おうよう","おえる","おおい","おおう","おおどおり","おおや","おおよそ","おかえり","おかず","おがむ","おかわり","おぎなう","おきる","おくさま","おくじょう","おくりがな","おくる","おくれる","おこす","おこなう","おこる","おさえる","おさない","おさめる","おしいれ","おしえる","おじぎ","おじさん","おしゃれ","おそらく","おそわる","おたがい","おたく","おだやか","おちつく","おっと","おつり","おでかけ","おとしもの","おとなしい","おどり","おどろかす","おばさん","おまいり","おめでとう","おもいで","おもう","おもたい","おもちゃ","おやつ","おやゆび","およぼす","おらんだ","おろす","おんがく","おんけい","おんしゃ","おんせん","おんだん","おんちゅう","おんどけい","かあつ","かいが","がいき","がいけん","がいこう","かいさつ","かいしゃ","かいすいよく","かいぜん","かいぞうど","かいつう","かいてん","かいとう","かいふく","がいへき","かいほう","かいよう","がいらい","かいわ","かえる","かおり","かかえる","かがく","かがし","かがみ","かくご","かくとく","かざる","がぞう","かたい","かたち","がちょう","がっきゅう","がっこう","がっさん","がっしょう","かなざわし","かのう","がはく","かぶか","かほう","かほご","かまう","かまぼこ","かめれおん","かゆい","かようび","からい","かるい","かろう","かわく","かわら","がんか","かんけい","かんこう","かんしゃ","かんそう","かんたん","かんち","がんばる","きあい","きあつ","きいろ","ぎいん","きうい","きうん","きえる","きおう","きおく","きおち","きおん","きかい","きかく","きかんしゃ","ききて","きくばり","きくらげ","きけんせい","きこう","きこえる","きこく","きさい","きさく","きさま","きさらぎ","ぎじかがく","ぎしき","ぎじたいけん","ぎじにってい","ぎじゅつしゃ","きすう","きせい","きせき","きせつ","きそう","きぞく","きぞん","きたえる","きちょう","きつえん","ぎっちり","きつつき","きつね","きてい","きどう","きどく","きない","きなが","きなこ","きぬごし","きねん","きのう","きのした","きはく","きびしい","きひん","きふく","きぶん","きぼう","きほん","きまる","きみつ","きむずかしい","きめる","きもだめし","きもち","きもの","きゃく","きやく","ぎゅうにく","きよう","きょうりゅう","きらい","きらく","きりん","きれい","きれつ","きろく","ぎろん","きわめる","ぎんいろ","きんかくじ","きんじょ","きんようび","ぐあい","くいず","くうかん","くうき","くうぐん","くうこう","ぐうせい","くうそう","ぐうたら","くうふく","くうぼ","くかん","くきょう","くげん","ぐこう","くさい","くさき","くさばな","くさる","くしゃみ","くしょう","くすのき","くすりゆび","くせげ","くせん","ぐたいてき","くださる","くたびれる","くちこみ","くちさき","くつした","ぐっすり","くつろぐ","くとうてん","くどく","くなん","くねくね","くのう","くふう","くみあわせ","くみたてる","くめる","くやくしょ","くらす","くらべる","くるま","くれる","くろう","くわしい","ぐんかん","ぐんしょく","ぐんたい","ぐんて","けあな","けいかく","けいけん","けいこ","けいさつ","げいじゅつ","けいたい","げいのうじん","けいれき","けいろ","けおとす","けおりもの","げきか","げきげん","げきだん","げきちん","げきとつ","げきは","げきやく","げこう","げこくじょう","げざい","けさき","げざん","けしき","けしごむ","けしょう","げすと","けたば","けちゃっぷ","けちらす","けつあつ","けつい","けつえき","けっこん","けつじょ","けっせき","けってい","けつまつ","げつようび","げつれい","けつろん","げどく","けとばす","けとる","けなげ","けなす","けなみ","けぬき","げねつ","けねん","けはい","げひん","けぶかい","げぼく","けまり","けみかる","けむし","けむり","けもの","けらい","けろけろ","けわしい","けんい","けんえつ","けんお","けんか","げんき","けんげん","けんこう","けんさく","けんしゅう","けんすう","げんそう","けんちく","けんてい","けんとう","けんない","けんにん","げんぶつ","けんま","けんみん","けんめい","けんらん","けんり","こあくま","こいぬ","こいびと","ごうい","こうえん","こうおん","こうかん","ごうきゅう","ごうけい","こうこう","こうさい","こうじ","こうすい","ごうせい","こうそく","こうたい","こうちゃ","こうつう","こうてい","こうどう","こうない","こうはい","ごうほう","ごうまん","こうもく","こうりつ","こえる","こおり","ごかい","ごがつ","ごかん","こくご","こくさい","こくとう","こくない","こくはく","こぐま","こけい","こける","ここのか","こころ","こさめ","こしつ","こすう","こせい","こせき","こぜん","こそだて","こたい","こたえる","こたつ","こちょう","こっか","こつこつ","こつばん","こつぶ","こてい","こてん","ことがら","ことし","ことば","ことり","こなごな","こねこね","このまま","このみ","このよ","ごはん","こひつじ","こふう","こふん","こぼれる","ごまあぶら","こまかい","ごますり","こまつな","こまる","こむぎこ","こもじ","こもち","こもの","こもん","こやく","こやま","こゆう","こゆび","こよい","こよう","こりる","これくしょん","ころっけ","こわもて","こわれる","こんいん","こんかい","こんき","こんしゅう","こんすい","こんだて","こんとん","こんなん","こんびに","こんぽん","こんまけ","こんや","こんれい","こんわく","ざいえき","さいかい","さいきん","ざいげん","ざいこ","さいしょ","さいせい","ざいたく","ざいちゅう","さいてき","ざいりょう","さうな","さかいし","さがす","さかな","さかみち","さがる","さぎょう","さくし","さくひん","さくら","さこく","さこつ","さずかる","ざせき","さたん","さつえい","ざつおん","ざっか","ざつがく","さっきょく","ざっし","さつじん","ざっそう","さつたば","さつまいも","さてい","さといも","さとう","さとおや","さとし","さとる","さのう","さばく","さびしい","さべつ","さほう","さほど","さます","さみしい","さみだれ","さむけ","さめる","さやえんどう","さゆう","さよう","さよく","さらだ","ざるそば","さわやか","さわる","さんいん","さんか","さんきゃく","さんこう","さんさい","ざんしょ","さんすう","さんせい","さんそ","さんち","さんま","さんみ","さんらん","しあい","しあげ","しあさって","しあわせ","しいく","しいん","しうち","しえい","しおけ","しかい","しかく","じかん","しごと","しすう","じだい","したうけ","したぎ","したて","したみ","しちょう","しちりん","しっかり","しつじ","しつもん","してい","してき","してつ","じてん","じどう","しなぎれ","しなもの","しなん","しねま","しねん","しのぐ","しのぶ","しはい","しばかり","しはつ","しはらい","しはん","しひょう","しふく","じぶん","しへい","しほう","しほん","しまう","しまる","しみん","しむける","じむしょ","しめい","しめる","しもん","しゃいん","しゃうん","しゃおん","じゃがいも","しやくしょ","しゃくほう","しゃけん","しゃこ","しゃざい","しゃしん","しゃせん","しゃそう","しゃたい","しゃちょう","しゃっきん","じゃま","しゃりん","しゃれい","じゆう","じゅうしょ","しゅくはく","じゅしん","しゅっせき","しゅみ","しゅらば","じゅんばん","しょうかい","しょくたく","しょっけん","しょどう","しょもつ","しらせる","しらべる","しんか","しんこう","じんじゃ","しんせいじ","しんちく","しんりん","すあげ","すあし","すあな","ずあん","すいえい","すいか","すいとう","ずいぶん","すいようび","すうがく","すうじつ","すうせん","すおどり","すきま","すくう","すくない","すける","すごい","すこし","ずさん","すずしい","すすむ","すすめる","すっかり","ずっしり","ずっと","すてき","すてる","すねる","すのこ","すはだ","すばらしい","ずひょう","ずぶぬれ","すぶり","すふれ","すべて","すべる","ずほう","すぼん","すまい","すめし","すもう","すやき","すらすら","するめ","すれちがう","すろっと","すわる","すんぜん","すんぽう","せあぶら","せいかつ","せいげん","せいじ","せいよう","せおう","せかいかん","せきにん","せきむ","せきゆ","せきらんうん","せけん","せこう","せすじ","せたい","せたけ","せっかく","せっきゃく","ぜっく","せっけん","せっこつ","せっさたくま","せつぞく","せつだん","せつでん","せっぱん","せつび","せつぶん","せつめい","せつりつ","せなか","せのび","せはば","せびろ","せぼね","せまい","せまる","せめる","せもたれ","せりふ","ぜんあく","せんい","せんえい","せんか","せんきょ","せんく","せんげん","ぜんご","せんさい","せんしゅ","せんすい","せんせい","せんぞ","せんたく","せんちょう","せんてい","せんとう","せんぬき","せんねん","せんぱい","ぜんぶ","ぜんぽう","せんむ","せんめんじょ","せんもん","せんやく","せんゆう","せんよう","ぜんら","ぜんりゃく","せんれい","せんろ","そあく","そいとげる","そいね","そうがんきょう","そうき","そうご","そうしん","そうだん","そうなん","そうび","そうめん","そうり","そえもの","そえん","そがい","そげき","そこう","そこそこ","そざい","そしな","そせい","そせん","そそぐ","そだてる","そつう","そつえん","そっかん","そつぎょう","そっけつ","そっこう","そっせん","そっと","そとがわ","そとづら","そなえる","そなた","そふぼ","そぼく","そぼろ","そまつ","そまる","そむく","そむりえ","そめる","そもそも","そよかぜ","そらまめ","そろう","そんかい","そんけい","そんざい","そんしつ","そんぞく","そんちょう","ぞんび","ぞんぶん","そんみん","たあい","たいいん","たいうん","たいえき","たいおう","だいがく","たいき","たいぐう","たいけん","たいこ","たいざい","だいじょうぶ","だいすき","たいせつ","たいそう","だいたい","たいちょう","たいてい","だいどころ","たいない","たいねつ","たいのう","たいはん","だいひょう","たいふう","たいへん","たいほ","たいまつばな","たいみんぐ","たいむ","たいめん","たいやき","たいよう","たいら","たいりょく","たいる","たいわん","たうえ","たえる","たおす","たおる","たおれる","たかい","たかね","たきび","たくさん","たこく","たこやき","たさい","たしざん","だじゃれ","たすける","たずさわる","たそがれ","たたかう","たたく","ただしい","たたみ","たちばな","だっかい","だっきゃく","だっこ","だっしゅつ","だったい","たてる","たとえる","たなばた","たにん","たぬき","たのしみ","たはつ","たぶん","たべる","たぼう","たまご","たまる","だむる","ためいき","ためす","ためる","たもつ","たやすい","たよる","たらす","たりきほんがん","たりょう","たりる","たると","たれる","たれんと","たろっと","たわむれる","だんあつ","たんい","たんおん","たんか","たんき","たんけん","たんご","たんさん","たんじょうび","だんせい","たんそく","たんたい","だんち","たんてい","たんとう","だんな","たんにん","だんねつ","たんのう","たんぴん","だんぼう","たんまつ","たんめい","だんれつ","だんろ","だんわ","ちあい","ちあん","ちいき","ちいさい","ちえん","ちかい","ちから","ちきゅう","ちきん","ちけいず","ちけん","ちこく","ちさい","ちしき","ちしりょう","ちせい","ちそう","ちたい","ちたん","ちちおや","ちつじょ","ちてき","ちてん","ちぬき","ちぬり","ちのう","ちひょう","ちへいせん","ちほう","ちまた","ちみつ","ちみどろ","ちめいど","ちゃんこなべ","ちゅうい","ちゆりょく","ちょうし","ちょさくけん","ちらし","ちらみ","ちりがみ","ちりょう","ちるど","ちわわ","ちんたい","ちんもく","ついか","ついたち","つうか","つうじょう","つうはん","つうわ","つかう","つかれる","つくね","つくる","つけね","つける","つごう","つたえる","つづく","つつじ","つつむ","つとめる","つながる","つなみ","つねづね","つのる","つぶす","つまらない","つまる","つみき","つめたい","つもり","つもる","つよい","つるぼ","つるみく","つわもの","つわり","てあし","てあて","てあみ","ていおん","ていか","ていき","ていけい","ていこく","ていさつ","ていし","ていせい","ていたい","ていど","ていねい","ていひょう","ていへん","ていぼう","てうち","ておくれ","てきとう","てくび","でこぼこ","てさぎょう","てさげ","てすり","てそう","てちがい","てちょう","てつがく","てつづき","でっぱ","てつぼう","てつや","でぬかえ","てぬき","てぬぐい","てのひら","てはい","てぶくろ","てふだ","てほどき","てほん","てまえ","てまきずし","てみじか","てみやげ","てらす","てれび","てわけ","てわたし","でんあつ","てんいん","てんかい","てんき","てんぐ","てんけん","てんごく","てんさい","てんし","てんすう","でんち","てんてき","てんとう","てんない","てんぷら","てんぼうだい","てんめつ","てんらんかい","でんりょく","でんわ","どあい","といれ","どうかん","とうきゅう","どうぐ","とうし","とうむぎ","とおい","とおか","とおく","とおす","とおる","とかい","とかす","ときおり","ときどき","とくい","とくしゅう","とくてん","とくに","とくべつ","とけい","とける","とこや","とさか","としょかん","とそう","とたん","とちゅう","とっきゅう","とっくん","とつぜん","とつにゅう","とどける","ととのえる","とない","となえる","となり","とのさま","とばす","どぶがわ","とほう","とまる","とめる","ともだち","ともる","どようび","とらえる","とんかつ","どんぶり","ないかく","ないこう","ないしょ","ないす","ないせん","ないそう","なおす","ながい","なくす","なげる","なこうど","なさけ","なたでここ","なっとう","なつやすみ","ななおし","なにごと","なにもの","なにわ","なのか","なふだ","なまいき","なまえ","なまみ","なみだ","なめらか","なめる","なやむ","ならう","ならび","ならぶ","なれる","なわとび","なわばり","にあう","にいがた","にうけ","におい","にかい","にがて","にきび","にくしみ","にくまん","にげる","にさんかたんそ","にしき","にせもの","にちじょう","にちようび","にっか","にっき","にっけい","にっこう","にっさん","にっしょく","にっすう","にっせき","にってい","になう","にほん","にまめ","にもつ","にやり","にゅういん","にりんしゃ","にわとり","にんい","にんか","にんき","にんげん","にんしき","にんずう","にんそう","にんたい","にんち","にんてい","にんにく","にんぷ","にんまり","にんむ","にんめい","にんよう","ぬいくぎ","ぬかす","ぬぐいとる","ぬぐう","ぬくもり","ぬすむ","ぬまえび","ぬめり","ぬらす","ぬんちゃく","ねあげ","ねいき","ねいる","ねいろ","ねぐせ","ねくたい","ねくら","ねこぜ","ねこむ","ねさげ","ねすごす","ねそべる","ねだん","ねつい","ねっしん","ねつぞう","ねったいぎょ","ねぶそく","ねふだ","ねぼう","ねほりはほり","ねまき","ねまわし","ねみみ","ねむい","ねむたい","ねもと","ねらう","ねわざ","ねんいり","ねんおし","ねんかん","ねんきん","ねんぐ","ねんざ","ねんし","ねんちゃく","ねんど","ねんぴ","ねんぶつ","ねんまつ","ねんりょう","ねんれい","のいず","のおづま","のがす","のきなみ","のこぎり","のこす","のこる","のせる","のぞく","のぞむ","のたまう","のちほど","のっく","のばす","のはら","のべる","のぼる","のみもの","のやま","のらいぬ","のらねこ","のりもの","のりゆき","のれん","のんき","ばあい","はあく","ばあさん","ばいか","ばいく","はいけん","はいご","はいしん","はいすい","はいせん","はいそう","はいち","ばいばい","はいれつ","はえる","はおる","はかい","ばかり","はかる","はくしゅ","はけん","はこぶ","はさみ","はさん","はしご","ばしょ","はしる","はせる","ぱそこん","はそん","はたん","はちみつ","はつおん","はっかく","はづき","はっきり","はっくつ","はっけん","はっこう","はっさん","はっしん","はったつ","はっちゅう","はってん","はっぴょう","はっぽう","はなす","はなび","はにかむ","はぶらし","はみがき","はむかう","はめつ","はやい","はやし","はらう","はろうぃん","はわい","はんい","はんえい","はんおん","はんかく","はんきょう","ばんぐみ","はんこ","はんしゃ","はんすう","はんだん","ぱんち","ぱんつ","はんてい","はんとし","はんのう","はんぱ","はんぶん","はんぺん","はんぼうき","はんめい","はんらん","はんろん","ひいき","ひうん","ひえる","ひかく","ひかり","ひかる","ひかん","ひくい","ひけつ","ひこうき","ひこく","ひさい","ひさしぶり","ひさん","びじゅつかん","ひしょ","ひそか","ひそむ","ひたむき","ひだり","ひたる","ひつぎ","ひっこし","ひっし","ひつじゅひん","ひっす","ひつぜん","ぴったり","ぴっちり","ひつよう","ひてい","ひとごみ","ひなまつり","ひなん","ひねる","ひはん","ひびく","ひひょう","ひほう","ひまわり","ひまん","ひみつ","ひめい","ひめじし","ひやけ","ひやす","ひよう","びょうき","ひらがな","ひらく","ひりつ","ひりょう","ひるま","ひるやすみ","ひれい","ひろい","ひろう","ひろき","ひろゆき","ひんかく","ひんけつ","ひんこん","ひんしゅ","ひんそう","ぴんち","ひんぱん","びんぼう","ふあん","ふいうち","ふうけい","ふうせん","ぷうたろう","ふうとう","ふうふ","ふえる","ふおん","ふかい","ふきん","ふくざつ","ふくぶくろ","ふこう","ふさい","ふしぎ","ふじみ","ふすま","ふせい","ふせぐ","ふそく","ぶたにく","ふたん","ふちょう","ふつう","ふつか","ふっかつ","ふっき","ふっこく","ぶどう","ふとる","ふとん","ふのう","ふはい","ふひょう","ふへん","ふまん","ふみん","ふめつ","ふめん","ふよう","ふりこ","ふりる","ふるい","ふんいき","ぶんがく","ぶんぐ","ふんしつ","ぶんせき","ふんそう","ぶんぽう","へいあん","へいおん","へいがい","へいき","へいげん","へいこう","へいさ","へいしゃ","へいせつ","へいそ","へいたく","へいてん","へいねつ","へいわ","へきが","へこむ","べにいろ","べにしょうが","へらす","へんかん","べんきょう","べんごし","へんさい","へんたい","べんり","ほあん","ほいく","ぼうぎょ","ほうこく","ほうそう","ほうほう","ほうもん","ほうりつ","ほえる","ほおん","ほかん","ほきょう","ぼきん","ほくろ","ほけつ","ほけん","ほこう","ほこる","ほしい","ほしつ","ほしゅ","ほしょう","ほせい","ほそい","ほそく","ほたて","ほたる","ぽちぶくろ","ほっきょく","ほっさ","ほったん","ほとんど","ほめる","ほんい","ほんき","ほんけ","ほんしつ","ほんやく","まいにち","まかい","まかせる","まがる","まける","まこと","まさつ","まじめ","ますく","まぜる","まつり","まとめ","まなぶ","まぬけ","まねく","まほう","まもる","まゆげ","まよう","まろやか","まわす","まわり","まわる","まんが","まんきつ","まんぞく","まんなか","みいら","みうち","みえる","みがく","みかた","みかん","みけん","みこん","みじかい","みすい","みすえる","みせる","みっか","みつかる","みつける","みてい","みとめる","みなと","みなみかさい","みねらる","みのう","みのがす","みほん","みもと","みやげ","みらい","みりょく","みわく","みんか","みんぞく","むいか","むえき","むえん","むかい","むかう","むかえ","むかし","むぎちゃ","むける","むげん","むさぼる","むしあつい","むしば","むじゅん","むしろ","むすう","むすこ","むすぶ","むすめ","むせる","むせん","むちゅう","むなしい","むのう","むやみ","むよう","むらさき","むりょう","むろん","めいあん","めいうん","めいえん","めいかく","めいきょく","めいさい","めいし","めいそう","めいぶつ","めいれい","めいわく","めぐまれる","めざす","めした","めずらしい","めだつ","めまい","めやす","めんきょ","めんせき","めんどう","もうしあげる","もうどうけん","もえる","もくし","もくてき","もくようび","もちろん","もどる","もらう","もんく","もんだい","やおや","やける","やさい","やさしい","やすい","やすたろう","やすみ","やせる","やそう","やたい","やちん","やっと","やっぱり","やぶる","やめる","ややこしい","やよい","やわらかい","ゆうき","ゆうびんきょく","ゆうべ","ゆうめい","ゆけつ","ゆしゅつ","ゆせん","ゆそう","ゆたか","ゆちゃく","ゆでる","ゆにゅう","ゆびわ","ゆらい","ゆれる","ようい","ようか","ようきゅう","ようじ","ようす","ようちえん","よかぜ","よかん","よきん","よくせい","よくぼう","よけい","よごれる","よさん","よしゅう","よそう","よそく","よっか","よてい","よどがわく","よねつ","よやく","よゆう","よろこぶ","よろしい","らいう","らくがき","らくご","らくさつ","らくだ","らしんばん","らせん","らぞく","らたい","らっか","られつ","りえき","りかい","りきさく","りきせつ","りくぐん","りくつ","りけん","りこう","りせい","りそう","りそく","りてん","りねん","りゆう","りゅうがく","りよう","りょうり","りょかん","りょくちゃ","りょこう","りりく","りれき","りろん","りんご","るいけい","るいさい","るいじ","るいせき","るすばん","るりがわら","れいかん","れいぎ","れいせい","れいぞうこ","れいとう","れいぼう","れきし","れきだい","れんあい","れんけい","れんこん","れんさい","れんしゅう","れんぞく","れんらく","ろうか","ろうご","ろうじん","ろうそく","ろくが","ろこつ","ろじうら","ろしゅつ","ろせん","ろてん","ろめん","ろれつ","ろんぎ","ろんぱ","ろんぶん","ろんり","わかす","わかめ","わかやま","わかれる","わしつ","わじまし","わすれもの","わらう","われる"]["가격","가끔","가난","가능","가득","가르침","가뭄","가방","가상","가슴","가운데","가을","가이드","가입","가장","가정","가족","가죽","각오","각자","간격","간부","간섭","간장","간접","간판","갈등","갈비","갈색","갈증","감각","감기","감소","감수성","감자","감정","갑자기","강남","강당","강도","강력히","강변","강북","강사","강수량","강아지","강원도","강의","강제","강조","같이","개구리","개나리","개방","개별","개선","개성","개인","객관적","거실","거액","거울","거짓","거품","걱정","건강","건물","건설","건조","건축","걸음","검사","검토","게시판","게임","겨울","견해","결과","결국","결론","결석","결승","결심","결정","결혼","경계","경고","경기","경력","경복궁","경비","경상도","경영","경우","경쟁","경제","경주","경찰","경치","경향","경험","계곡","계단","계란","계산","계속","계약","계절","계층","계획","고객","고구려","고궁","고급","고등학생","고무신","고민","고양이","고장","고전","고집","고춧가루","고통","고향","곡식","골목","골짜기","골프","공간","공개","공격","공군","공급","공기","공동","공무원","공부","공사","공식","공업","공연","공원","공장","공짜","공책","공통","공포","공항","공휴일","과목","과일","과장","과정","과학","관객","관계","관광","관념","관람","관련","관리","관습","관심","관점","관찰","광경","광고","광장","광주","괴로움","굉장히","교과서","교문","교복","교실","교양","교육","교장","교직","교통","교환","교훈","구경","구름","구멍","구별","구분","구석","구성","구속","구역","구입","구청","구체적","국가","국기","국내","국립","국물","국민","국수","국어","국왕","국적","국제","국회","군대","군사","군인","궁극적","권리","권위","권투","귀국","귀신","규정","규칙","균형","그날","그냥","그늘","그러나","그룹","그릇","그림","그제서야","그토록","극복","극히","근거","근교","근래","근로","근무","근본","근원","근육","근처","글씨","글자","금강산","금고","금년","금메달","금액","금연","금요일","금지","긍정적","기간","기관","기념","기능","기독교","기둥","기록","기름","기법","기본","기분","기쁨","기숙사","기술","기억","기업","기온","기운","기원","기적","기준","기침","기혼","기획","긴급","긴장","길이","김밥","김치","김포공항","깍두기","깜빡","깨달음","깨소금","껍질","꼭대기","꽃잎","나들이","나란히","나머지","나물","나침반","나흘","낙엽","난방","날개","날씨","날짜","남녀","남대문","남매","남산","남자","남편","남학생","낭비","낱말","내년","내용","내일","냄비","냄새","냇물","냉동","냉면","냉방","냉장고","넥타이","넷째","노동","노란색","노력","노인","녹음","녹차","녹화","논리","논문","논쟁","놀이","농구","농담","농민","농부","농업","농장","농촌","높이","눈동자","눈물","눈썹","뉴욕","느낌","늑대","능동적","능력","다방","다양성","다음","다이어트","다행","단계","단골","단독","단맛","단순","단어","단위","단점","단체","단추","단편","단풍","달걀","달러","달력","달리","닭고기","담당","담배","담요","담임","답변","답장","당근","당분간","당연히","당장","대규모","대낮","대단히","대답","대도시","대략","대량","대륙","대문","대부분","대신","대응","대장","대전","대접","대중","대책","대출","대충","대통령","대학","대한민국","대합실","대형","덩어리","데이트","도대체","도덕","도둑","도망","도서관","도심","도움","도입","도자기","도저히","도전","도중","도착","독감","독립","독서","독일","독창적","동화책","뒷모습","뒷산","딸아이","마누라","마늘","마당","마라톤","마련","마무리","마사지","마약","마요네즈","마을","마음","마이크","마중","마지막","마찬가지","마찰","마흔","막걸리","막내","막상","만남","만두","만세","만약","만일","만점","만족","만화","많이","말기","말씀","말투","맘대로","망원경","매년","매달","매력","매번","매스컴","매일","매장","맥주","먹이","먼저","먼지","멀리","메일","며느리","며칠","면담","멸치","명단","명령","명예","명의","명절","명칭","명함","모금","모니터","모델","모든","모범","모습","모양","모임","모조리","모집","모퉁이","목걸이","목록","목사","목소리","목숨","목적","목표","몰래","몸매","몸무게","몸살","몸속","몸짓","몸통","몹시","무관심","무궁화","무더위","무덤","무릎","무슨","무엇","무역","무용","무조건","무지개","무척","문구","문득","문법","문서","문제","문학","문화","물가","물건","물결","물고기","물론","물리학","물음","물질","물체","미국","미디어","미사일","미술","미역","미용실","미움","미인","미팅","미혼","민간","민족","민주","믿음","밀가루","밀리미터","밑바닥","바가지","바구니","바나나","바늘","바닥","바닷가","바람","바이러스","바탕","박물관","박사","박수","반대","반드시","반말","반발","반성","반응","반장","반죽","반지","반찬","받침","발가락","발걸음","발견","발달","발레","발목","발바닥","발생","발음","발자국","발전","발톱","발표","밤하늘","밥그릇","밥맛","밥상","밥솥","방금","방면","방문","방바닥","방법","방송","방식","방안","방울","방지","방학","방해","방향","배경","배꼽","배달","배드민턴","백두산","백색","백성","백인","백제","백화점","버릇","버섯","버튼","번개","번역","번지","번호","벌금","벌레","벌써","범위","범인","범죄","법률","법원","법적","법칙","베이징","벨트","변경","변동","변명","변신","변호사","변화","별도","별명","별일","병실","병아리","병원","보관","보너스","보라색","보람","보름","보상","보안","보자기","보장","보전","보존","보통","보편적","보험","복도","복사","복숭아","복습","볶음","본격적","본래","본부","본사","본성","본인","본질","볼펜","봉사","봉지","봉투","부근","부끄러움","부담","부동산","부문","부분","부산","부상","부엌","부인","부작용","부장","부정","부족","부지런히","부친","부탁","부품","부회장","북부","북한","분노","분량","분리","분명","분석","분야","분위기","분필","분홍색","불고기","불과","불교","불꽃","불만","불법","불빛","불안","불이익","불행","브랜드","비극","비난","비닐","비둘기","비디오","비로소","비만","비명","비밀","비바람","비빔밥","비상","비용","비율","비중","비타민","비판","빌딩","빗물","빗방울","빗줄기","빛깔","빨간색","빨래","빨리","사건","사계절","사나이","사냥","사람","사랑","사립","사모님","사물","사방","사상","사생활","사설","사슴","사실","사업","사용","사월","사장","사전","사진","사촌","사춘기","사탕","사투리","사흘","산길","산부인과","산업","산책","살림","살인","살짝","삼계탕","삼국","삼십","삼월","삼촌","상관","상금","상대","상류","상반기","상상","상식","상업","상인","상자","상점","상처","상추","상태","상표","상품","상황","새벽","색깔","색연필","생각","생명","생물","생방송","생산","생선","생신","생일","생활","서랍","서른","서명","서민","서비스","서양","서울","서적","서점","서쪽","서클","석사","석유","선거","선물","선배","선생","선수","선원","선장","선전","선택","선풍기","설거지","설날","설렁탕","설명","설문","설사","설악산","설치","설탕","섭씨","성공","성당","성명","성별","성인","성장","성적","성질","성함","세금","세미나","세상","세월","세종대왕","세탁","센터","센티미터","셋째","소규모","소극적","소금","소나기","소년","소득","소망","소문","소설","소속","소아과","소용","소원","소음","소중히","소지품","소질","소풍","소형","속담","속도","속옷","손가락","손길","손녀","손님","손등","손목","손뼉","손실","손질","손톱","손해","솔직히","솜씨","송아지","송이","송편","쇠고기","쇼핑","수건","수년","수단","수돗물","수동적","수면","수명","수박","수상","수석","수술","수시로","수업","수염","수영","수입","수준","수집","수출","수컷","수필","수학","수험생","수화기","숙녀","숙소","숙제","순간","순서","순수","순식간","순위","숟가락","술병","술집","숫자","스님","스물","스스로","스승","스웨터","스위치","스케이트","스튜디오","스트레스","스포츠","슬쩍","슬픔","습관","습기","승객","승리","승부","승용차","승진","시각","시간","시골","시금치","시나리오","시댁","시리즈","시멘트","시민","시부모","시선","시설","시스템","시아버지","시어머니","시월","시인","시일","시작","시장","시절","시점","시중","시즌","시집","시청","시합","시험","식구","식기","식당","식량","식료품","식물","식빵","식사","식생활","식초","식탁","식품","신고","신규","신념","신문","신발","신비","신사","신세","신용","신제품","신청","신체","신화","실감","실내","실력","실례","실망","실수","실습","실시","실장","실정","실질적","실천","실체","실컷","실태","실패","실험","실현","심리","심부름","심사","심장","심정","심판","쌍둥이","씨름","씨앗","아가씨","아나운서","아드님","아들","아쉬움","아스팔트","아시아","아울러","아저씨","아줌마","아직","아침","아파트","아프리카","아픔","아홉","아흔","악기","악몽","악수","안개","안경","안과","안내","안녕","안동","안방","안부","안주","알루미늄","알코올","암시","암컷","압력","앞날","앞문","애인","애정","액수","앨범","야간","야단","야옹","약간","약국","약속","약수","약점","약품","약혼녀","양념","양력","양말","양배추","양주","양파","어둠","어려움","어른","어젯밤","어쨌든","어쩌다가","어쩐지","언니","언덕","언론","언어","얼굴","얼른","얼음","얼핏","엄마","업무","업종","업체","엉덩이","엉망","엉터리","엊그제","에너지","에어컨","엔진","여건","여고생","여관","여군","여권","여대생","여덟","여동생","여든","여론","여름","여섯","여성","여왕","여인","여전히","여직원","여학생","여행","역사","역시","역할","연결","연구","연극","연기","연락","연설","연세","연속","연습","연애","연예인","연인","연장","연주","연출","연필","연합","연휴","열기","열매","열쇠","열심히","열정","열차","열흘","염려","엽서","영국","영남","영상","영양","영역","영웅","영원히","영하","영향","영혼","영화","옆구리","옆방","옆집","예감","예금","예방","예산","예상","예선","예술","예습","예식장","예약","예전","예절","예정","예컨대","옛날","오늘","오락","오랫동안","오렌지","오로지","오른발","오븐","오십","오염","오월","오전","오직","오징어","오페라","오피스텔","오히려","옥상","옥수수","온갖","온라인","온몸","온종일","온통","올가을","올림픽","올해","옷차림","와이셔츠","와인","완성","완전","왕비","왕자","왜냐하면","왠지","외갓집","외국","외로움","외삼촌","외출","외침","외할머니","왼발","왼손","왼쪽","요금","요일","요즘","요청","용기","용서","용어","우산","우선","우승","우연히","우정","우체국","우편","운동","운명","운반","운전","운행","울산","울음","움직임","웃어른","웃음","워낙","원고","원래","원서","원숭이","원인","원장","원피스","월급","월드컵","월세","월요일","웨이터","위반","위법","위성","위원","위험","위협","윗사람","유난히","유럽","유명","유물","유산","유적","유치원","유학","유행","유형","육군","육상","육십","육체","은행","음력","음료","음반","음성","음식","음악","음주","의견","의논","의문","의복","의식","의심","의외로","의욕","의원","의학","이것","이곳","이념","이놈","이달","이대로","이동","이렇게","이력서","이론적","이름","이민","이발소","이별","이불","이빨","이상","이성","이슬","이야기","이용","이웃","이월","이윽고","이익","이전","이중","이튿날","이틀","이혼","인간","인격","인공","인구","인근","인기","인도","인류","인물","인생","인쇄","인연","인원","인재","인종","인천","인체","인터넷","인하","인형","일곱","일기","일단","일대","일등","일반","일본","일부","일상","일생","일손","일요일","일월","일정","일종","일주일","일찍","일체","일치","일행","일회용","임금","임무","입대","입력","입맛","입사","입술","입시","입원","입장","입학","자가용","자격","자극","자동","자랑","자부심","자식","자신","자연","자원","자율","자전거","자정","자존심","자판","작가","작년","작성","작업","작용","작은딸","작품","잔디","잔뜩","잔치","잘못","잠깐","잠수함","잠시","잠옷","잠자리","잡지","장관","장군","장기간","장래","장례","장르","장마","장면","장모","장미","장비","장사","장소","장식","장애인","장인","장점","장차","장학금","재능","재빨리","재산","재생","재작년","재정","재채기","재판","재학","재활용","저것","저고리","저곳","저녁","저런","저렇게","저번","저울","저절로","저축","적극","적당히","적성","적용","적응","전개","전공","전기","전달","전라도","전망","전문","전반","전부","전세","전시","전용","전자","전쟁","전주","전철","전체","전통","전혀","전후","절대","절망","절반","절약","절차","점검","점수","점심","점원","점점","점차","접근","접시","접촉","젓가락","정거장","정도","정류장","정리","정말","정면","정문","정반대","정보","정부","정비","정상","정성","정오","정원","정장","정지","정치","정확히","제공","제과점","제대로","제목","제발","제법","제삿날","제안","제일","제작","제주도","제출","제품","제한","조각","조건","조금","조깅","조명","조미료","조상","조선","조용히","조절","조정","조직","존댓말","존재","졸업","졸음","종교","종로","종류","종소리","종업원","종종","종합","좌석","죄인","주관적","주름","주말","주머니","주먹","주문","주민","주방","주변","주식","주인","주일","주장","주전자","주택","준비","줄거리","줄기","줄무늬","중간","중계방송","중국","중년","중단","중독","중반","중부","중세","중소기업","중순","중앙","중요","중학교","즉석","즉시","즐거움","증가","증거","증권","증상","증세","지각","지갑","지경","지극히","지금","지급","지능","지름길","지리산","지방","지붕","지식","지역","지우개","지원","지적","지점","지진","지출","직선","직업","직원","직장","진급","진동","진로","진료","진리","진짜","진찰","진출","진통","진행","질문","질병","질서","짐작","집단","집안","집중","짜증","찌꺼기","차남","차라리","차량","차림","차별","차선","차츰","착각","찬물","찬성","참가","참기름","참새","참석","참여","참외","참조","찻잔","창가","창고","창구","창문","창밖","창작","창조","채널","채점","책가방","책방","책상","책임","챔피언","처벌","처음","천국","천둥","천장","천재","천천히","철도","철저히","철학","첫날","첫째","청년","청바지","청소","청춘","체계","체력","체온","체육","체중","체험","초등학생","초반","초밥","초상화","초순","초여름","초원","초저녁","초점","초청","초콜릿","촛불","총각","총리","총장","촬영","최근","최상","최선","최신","최악","최종","추석","추억","추진","추천","추측","축구","축소","축제","축하","출근","출발","출산","출신","출연","출입","출장","출판","충격","충고","충돌","충분히","충청도","취업","취직","취향","치약","친구","친척","칠십","칠월","칠판","침대","침묵","침실","칫솔","칭찬","카메라","카운터","칼국수","캐릭터","캠퍼스","캠페인","커튼","컨디션","컬러","컴퓨터","코끼리","코미디","콘서트","콜라","콤플렉스","콩나물","쾌감","쿠데타","크림","큰길","큰딸","큰소리","큰아들","큰어머니","큰일","큰절","클래식","클럽","킬로","타입","타자기","탁구","탁자","탄생","태권도","태양","태풍","택시","탤런트","터널","터미널","테니스","테스트","테이블","텔레비전","토론","토마토","토요일","통계","통과","통로","통신","통역","통일","통장","통제","통증","통합","통화","퇴근","퇴원","퇴직금","튀김","트럭","특급","특별","특성","특수","특징","특히","튼튼히","티셔츠","파란색","파일","파출소","판결","판단","판매","판사","팔십","팔월","팝송","패션","팩스","팩시밀리","팬티","퍼센트","페인트","편견","편의","편지","편히","평가","평균","평생","평소","평양","평일","평화","포스터","포인트","포장","포함","표면","표정","표준","표현","품목","품질","풍경","풍속","풍습","프랑스","프린터","플라스틱","피곤","피망","피아노","필름","필수","필요","필자","필통","핑계","하느님","하늘","하드웨어","하룻밤","하반기","하숙집","하순","하여튼","하지만","하천","하품","하필","학과","학교","학급","학기","학년","학력","학번","학부모","학비","학생","학술","학습","학용품","학원","학위","학자","학점","한계","한글","한꺼번에","한낮","한눈","한동안","한때","한라산","한마디","한문","한번","한복","한식","한여름","한쪽","할머니","할아버지","할인","함께","함부로","합격","합리적","항공","항구","항상","항의","해결","해군","해답","해당","해물","해석","해설","해수욕장","해안","핵심","핸드백","햄버거","햇볕","햇살","행동","행복","행사","행운","행위","향기","향상","향수","허락","허용","헬기","현관","현금","현대","현상","현실","현장","현재","현지","혈액","협력","형부","형사","형수","형식","형제","형태","형편","혜택","호기심","호남","호랑이","호박","호텔","호흡","혹시","홀로","홈페이지","홍보","홍수","홍차","화면","화분","화살","화요일","화장","화학","확보","확인","확장","확정","환갑","환경","환영","환율","환자","활기","활동","활발히","활용","활짝","회견","회관","회복","회색","회원","회장","회전","횟수","횡단보도","효율적","후반","후춧가루","훈련","훨씬","휴식","휴일","흉내","흐름","흑백","흑인","흔적","흔히","흥미","흥분","희곡","희망","희생","흰색","힘껏"]{"的":["chinese_simplified","chinese_traditional"],"一":["chinese_simplified","chinese_traditional"],"是":["chinese_simplified","chinese_traditional"],"在":["chinese_simplified","chinese_traditional"],"不":["chinese_simplified","chinese_traditional"],"了":["chinese_simplified","chinese_traditional"],"有":["chinese_simplified","chinese_traditional"],"和":["chinese_simplified","chinese_traditional"],"人":["chinese_simplified","chinese_traditional"],"这":["chinese_simplified"],"中":["chinese_simplified","chinese_traditional"],"大":["chinese_simplified","chinese_traditional"],"为":["chinese_simplified"],"上":["chinese_simplified","chinese_traditional"],"个":["chinese_simplified"],"国":["chinese_simplified"],"我":["chinese_simplified","chinese_traditional"],"以":["chinese_simplified","chinese_traditional"],"要":["chinese_simplified","chinese_traditional"],"他":["chinese_simplified","chinese_traditional"],"时":["chinese_simplified"],"来":["chinese_simplified"],"用":["chinese_simplified","chinese_traditional"],"们":["chinese_simplified"],"生":["chinese_simplified","chinese_traditional"],"到":["chinese_simplified","chinese_traditional"],"作":["chinese_simplified","chinese_traditional"],"地":["chinese_simplified","chinese_traditional"],"于":["chinese_simplified"],"出":["chinese_simplified","chinese_traditional"],"就":["chinese_simplified","chinese_traditional"],"分":["chinese_simplified","chinese_traditional"],"对":["chinese_simplified"],"成":["chinese_simplified","chinese_traditional"],"会":["chinese_simplified"],"可":["chinese_simplified","chinese_traditional"],"主":["chinese_simplified","chinese_traditional"],"发":["chinese_simplified"],"年":["chinese_simplified","chinese_traditional"],"动":["chinese_simplified"],"同":["chinese_simplified","chinese_traditional"],"工":["chinese_simplified","chinese_traditional"],"也":["chinese_simplified","chinese_traditional"],"能":["chinese_simplified","chinese_traditional"],"下":["chinese_simplified","chinese_traditional"],"过":["chinese_simplified"],"子":["chinese_simplified","chinese_traditional"],"说":["chinese_simplified"],"产":["chinese_simplified"],"种":["chinese_simplified"],"面":["chinese_simplified","chinese_traditional"],"而":["chinese_simplified","chinese_traditional"],"方":["chinese_simplified","chinese_traditional"],"后":["chinese_simplified"],"多":["chinese_simplified","chinese_traditional"],"定":["chinese_simplified","chinese_traditional"],"行":["chinese_simplified","chinese_traditional"],"学":["chinese_simplified"],"法":["chinese_simplified","chinese_traditional"],"所":["chinese_simplified","chinese_traditional"],"民":["chinese_simplified","chinese_traditional"],"得":["chinese_simplified","chinese_traditional"],"经":["chinese_simplified"],"十":["chinese_simplified","chinese_traditional"],"三":["chinese_simplified","chinese_traditional"],"之":["chinese_simplified","chinese_traditional"],"进":["chinese_simplified"],"着":["chinese_simplified"],"等":["chinese_simplified","chinese_traditional"],"部":["chinese_simplified","chinese_traditional"],"度":["chinese_simplified","chinese_traditional"],"家":["chinese_simplified","chinese_traditional"],"电":["chinese_simplified"],"力":["chinese_simplified","chinese_traditional"],"里":["chinese_simplified"],"如":["chinese_simplified","chinese_traditional"],"水":["chinese_simplified","chinese_traditional"],"化":["chinese_simplified","chinese_traditional"],"高":["chinese_simplified","chinese_traditional"],"自":["chinese_simplified","chinese_traditional"],"二":["chinese_simplified","chinese_traditional"],"理":["chinese_simplified","chinese_traditional"],"起":["chinese_simplified","chinese_traditional"],"小":["chinese_simplified","chinese_traditional"],"物":["chinese_simplified","chinese_traditional"],"现":["chinese_simplified"],"实":["chinese_simplified"],"加":["chinese_simplified","chinese_traditional"],"量":["chinese_simplified","chinese_traditional"],"都":["chinese_simplified","chinese_traditional"],"两":["chinese_simplified"],"体":["chinese_simplified"],"制":["chinese_simplified","chinese_traditional"],"机":["chinese_simplified"],"当":["chinese_simplified"],"使":["chinese_simplified","chinese_traditional"],"点":["chinese_simplified"],"从":["chinese_simplified"],"业":["chinese_simplified"],"本":["chinese_simplified","chinese_traditional"],"去":["chinese_simplified","chinese_traditional"],"把":["chinese_simplified","chinese_traditional"],"性":["chinese_simplified","chinese_traditional"],"好":["chinese_simplified","chinese_traditional"],"应":["chinese_simplified"],"开":["chinese_simplified"],"它":["chinese_simplified","chinese_traditional"],"合":["chinese_simplified","chinese_traditional"],"还":["chinese_simplified"],"因":["chinese_simplified","chinese_traditional"],"由":["chinese_simplified","chinese_traditional"],"其":["chinese_simplified","chinese_traditional"],"些":["chinese_simplified","chinese_traditional"],"然":["chinese_simplified","chinese_traditional"],"前":["chinese_simplified","chinese_traditional"],"外":["chinese_simplified","chinese_traditional"],"天":["chinese_simplified","chinese_traditional"],"政":["chinese_simplified","chinese_traditional"],"四":["chinese_simplified","chinese_traditional"],"日":["chinese_simplified","chinese_traditional"],"那":["chinese_simplified","chinese_traditional"],"社":["chinese_simplified","chinese_traditional"],"义":["chinese_simplified"],"事":["chinese_simplified","chinese_traditional"],"平":["chinese_simplified","chinese_traditional"],"形":["chinese_simplified","chinese_traditional"],"相":["chinese_simplified","chinese_traditional"],"全":["chinese_simplified","chinese_traditional"],"表":["chinese_simplified","chinese_traditional"],"间":["chinese_simplified"],"样":["chinese_simplified"],"与":["chinese_simplified"],"关":["chinese_simplified"],"各":["chinese_simplified","chinese_traditional"],"重":["chinese_simplified","chinese_traditional"],"新":["chinese_simplified","chinese_traditional"],"线":["chinese_simplified"],"内":["chinese_simplified"],"数":["chinese_simplified"],"正":["chinese_simplified","chinese_traditional"],"心":["chinese_simplified","chinese_traditional"],"反":["chinese_simplified","chinese_traditional"],"你":["chinese_simplified","chinese_traditional"],"明":["chinese_simplified","chinese_traditional"],"看":["chinese_simplified","chinese_traditional"],"原":["chinese_simplified","chinese_traditional"],"又":["chinese_simplified","chinese_traditional"],"么":["chinese_simplified"],"利":["chinese_simplified","chinese_traditional"],"比":["chinese_simplified","chinese_traditional"],"或":["chinese_simplified","chinese_traditional"],"但":["chinese_simplified","chinese_traditional"],"质":["chinese_simplified"],"气":["chinese_simplified"],"第":["chinese_simplified","chinese_traditional"],"向":["chinese_simplified","chinese_traditional"],"道":["chinese_simplified","chinese_traditional"],"命":["chinese_simplified","chinese_traditional"],"此":["chinese_simplified","chinese_traditional"],"变":["chinese_simplified"],"条":["chinese_simplified"],"只":["chinese_simplified","chinese_traditional"],"没":["chinese_simplified"],"结":["chinese_simplified"],"解":["chinese_simplified","chinese_traditional"],"问":["chinese_simplified"],"意":["chinese_simplified","chinese_traditional"],"建":["chinese_simplified","chinese_traditional"],"月":["chinese_simplified","chinese_traditional"],"公":["chinese_simplified","chinese_traditional"],"无":["chinese_simplified"],"系":["chinese_simplified","chinese_traditional"],"军":["chinese_simplified"],"很":["chinese_simplified","chinese_traditional"],"情":["chinese_simplified","chinese_traditional"],"者":["chinese_simplified","chinese_traditional"],"最":["chinese_simplified","chinese_traditional"],"立":["chinese_simplified","chinese_traditional"],"代":["chinese_simplified","chinese_traditional"],"想":["chinese_simplified","chinese_traditional"],"已":["chinese_simplified","chinese_traditional"],"通":["chinese_simplified","chinese_traditional"],"并":["chinese_simplified"],"提":["chinese_simplified","chinese_traditional"],"直":["chinese_simplified","chinese_traditional"],"题":["chinese_simplified"],"党":["chinese_simplified"],"程":["chinese_simplified","chinese_traditional"],"展":["chinese_simplified","chinese_traditional"],"五":["chinese_simplified","chinese_traditional"],"果":["chinese_simplified","chinese_traditional"],"料":["chinese_simplified","chinese_traditional"],"象":["chinese_simplified","chinese_traditional"],"员":["chinese_simplified"],"革":["chinese_simplified","chinese_traditional"],"位":["chinese_simplified","chinese_traditional"],"入":["chinese_simplified","chinese_traditional"],"常":["chinese_simplified","chinese_traditional"],"文":["chinese_simplified","chinese_traditional"],"总":["chinese_simplified"],"次":["chinese_simplified","chinese_traditional"],"品":["chinese_simplified","chinese_traditional"],"式":["chinese_simplified","chinese_traditional"],"活":["chinese_simplified","chinese_traditional"],"设":["chinese_simplified"],"及":["chinese_simplified","chinese_traditional"],"管":["chinese_simplified","chinese_traditional"],"特":["chinese_simplified","chinese_traditional"],"件":["chinese_simplified","chinese_traditional"],"长":["chinese_simplified"],"求":["chinese_simplified","chinese_traditional"],"老":["chinese_simplified","chinese_traditional"],"头":["chinese_simplified"],"基":["chinese_simplified","chinese_traditional"],"资":["chinese_simplified"],"边":["chinese_simplified"],"流":["chinese_simplified","chinese_traditional"],"路":["chinese_simplified","chinese_traditional"],"级":["chinese_simplified"],"少":["chinese_simplified","chinese_traditional"],"图":["chinese_simplified"],"山":["chinese_simplified","chinese_traditional"],"统":["chinese_simplified"],"接":["chinese_simplified","chinese_traditional"],"知":["chinese_simplified","chinese_traditional"],"较":["chinese_simplified"],"将":["chinese_simplified"],"组":["chinese_simplified"],"见":["chinese_simplified"],"计":["chinese_simplified"],"别":["chinese_simplified"],"她":["chinese_simplified","chinese_traditional"],"手":["chinese_simplified","chinese_traditional"],"角":["chinese_simplified","chinese_traditional"],"期":["chinese_simplified","chinese_traditional"],"根":["chinese_simplified","chinese_traditional"],"论":["chinese_simplified"],"运":["chinese_simplified"],"农":["chinese_simplified"],"指":["chinese_simplified","chinese_traditional"],"几":["chinese_simplified"],"九":["chinese_simplified","chinese_traditional"],"区":["chinese_simplified"],"强":["chinese_simplified"],"放":["chinese_simplified","chinese_traditional"],"决":["chinese_simplified"],"西":["chinese_simplified","chinese_traditional"],"被":["chinese_simplified","chinese_traditional"],"干":["chinese_simplified"],"做":["chinese_simplified","chinese_traditional"],"必":["chinese_simplified","chinese_traditional"],"战":["chinese_simplified"],"先":["chinese_simplified","chinese_traditional"],"回":["chinese_simplified","chinese_traditional"],"则":["chinese_simplified"],"任":["chinese_simplified","chinese_traditional"],"取":["chinese_simplified","chinese_traditional"],"据":["chinese_simplified"],"处":["chinese_simplified"],"队":["chinese_simplified"],"南":["chinese_simplified","chinese_traditional"],"给":["chinese_simplified"],"色":["chinese_simplified","chinese_traditional"],"光":["chinese_simplified","chinese_traditional"],"门":["chinese_simplified"],"即":["chinese_simplified","chinese_traditional"],"保":["chinese_simplified","chinese_traditional"],"治":["chinese_simplified","chinese_traditional"],"北":["chinese_simplified","chinese_traditional"],"造":["chinese_simplified","chinese_traditional"],"百":["chinese_simplified","chinese_traditional"],"规":["chinese_simplified"],"热":["chinese_simplified"],"领":["chinese_simplified"],"七":["chinese_simplified","chinese_traditional"],"海":["chinese_simplified","chinese_traditional"],"口":["chinese_simplified","chinese_traditional"],"东":["chinese_simplified"],"导":["chinese_simplified"],"器":["chinese_simplified","chinese_traditional"],"压":["chinese_simplified"],"志":["chinese_simplified","chinese_traditional"],"世":["chinese_simplified","chinese_traditional"],"金":["chinese_simplified","chinese_traditional"],"增":["chinese_simplified","chinese_traditional"],"争":["chinese_simplified"],"济":["chinese_simplified"],"阶":["chinese_simplified"],"油":["chinese_simplified","chinese_traditional"],"思":["chinese_simplified","chinese_traditional"],"术":["chinese_simplified"],"极":["chinese_simplified"],"交":["chinese_simplified","chinese_traditional"],"受":["chinese_simplified","chinese_traditional"],"联":["chinese_simplified"],"什":["chinese_simplified","chinese_traditional"],"认":["chinese_simplified"],"六":["chinese_simplified","chinese_traditional"],"共":["chinese_simplified","chinese_traditional"],"权":["chinese_simplified"],"收":["chinese_simplified","chinese_traditional"],"证":["chinese_simplified"],"改":["chinese_simplified","chinese_traditional"],"清":["chinese_simplified","chinese_traditional"],"美":["chinese_simplified","chinese_traditional"],"再":["chinese_simplified","chinese_traditional"],"采":["chinese_simplified"],"转":["chinese_simplified"],"更":["chinese_simplified","chinese_traditional"],"单":["chinese_simplified"],"风":["chinese_simplified"],"切":["chinese_simplified","chinese_traditional"],"打":["chinese_simplified","chinese_traditional"],"白":["chinese_simplified","chinese_traditional"],"教":["chinese_simplified","chinese_traditional"],"速":["chinese_simplified","chinese_traditional"],"花":["chinese_simplified","chinese_traditional"],"带":["chinese_simplified"],"安":["chinese_simplified","chinese_traditional"],"场":["chinese_simplified"],"身":["chinese_simplified","chinese_traditional"],"车":["chinese_simplified"],"例":["chinese_simplified","chinese_traditional"],"真":["chinese_simplified","chinese_traditional"],"务":["chinese_simplified"],"具":["chinese_simplified","chinese_traditional"],"万":["chinese_simplified"],"每":["chinese_simplified","chinese_traditional"],"目":["chinese_simplified","chinese_traditional"],"至":["chinese_simplified","chinese_traditional"],"达":["chinese_simplified"],"走":["chinese_simplified","chinese_traditional"],"积":["chinese_simplified"],"示":["chinese_simplified","chinese_traditional"],"议":["chinese_simplified"],"声":["chinese_simplified"],"报":["chinese_simplified"],"斗":["chinese_simplified"],"完":["chinese_simplified","chinese_traditional"],"类":["chinese_simplified"],"八":["chinese_simplified","chinese_traditional"],"离":["chinese_simplified"],"华":["chinese_simplified"],"名":["chinese_simplified","chinese_traditional"],"确":["chinese_simplified"],"才":["chinese_simplified","chinese_traditional"],"科":["chinese_simplified","chinese_traditional"],"张":["chinese_simplified"],"信":["chinese_simplified","chinese_traditional"],"马":["chinese_simplified"],"节":["chinese_simplified"],"话":["chinese_simplified"],"米":["chinese_simplified","chinese_traditional"],"整":["chinese_simplified","chinese_traditional"],"空":["chinese_simplified","chinese_traditional"],"元":["chinese_simplified","chinese_traditional"],"况":["chinese_simplified"],"今":["chinese_simplified","chinese_traditional"],"集":["chinese_simplified","chinese_traditional"],"温":["chinese_simplified"],"传":["chinese_simplified"],"土":["chinese_simplified","chinese_traditional"],"许":["chinese_simplified"],"步":["chinese_simplified","chinese_traditional"],"群":["chinese_simplified","chinese_traditional"],"广":["chinese_simplified"],"石":["chinese_simplified","chinese_traditional"],"记":["chinese_simplified"],"需":["chinese_simplified","chinese_traditional"],"段":["chinese_simplified","chinese_traditional"],"研":["chinese_simplified","chinese_traditional"],"界":["chinese_simplified","chinese_traditional"],"拉":["chinese_simplified","chinese_traditional"],"林":["chinese_simplified","chinese_traditional"],"律":["chinese_simplified","chinese_traditional"],"叫":["chinese_simplified","chinese_traditional"],"且":["chinese_simplified","chinese_traditional"],"究":["chinese_simplified","chinese_traditional"],"观":["chinese_simplified"],"越":["chinese_simplified","chinese_traditional"],"织":["chinese_simplified"],"装":["chinese_simplified"],"影":["chinese_simplified","chinese_traditional"],"算":["chinese_simplified","chinese_traditional"],"低":["chinese_simplified","chinese_traditional"],"持":["chinese_simplified","chinese_traditional"],"音":["chinese_simplified","chinese_traditional"],"众":["chinese_simplified"],"书":["chinese_simplified"],"布":["chinese_simplified","chinese_traditional"],"复":["chinese_simplified","chinese_traditional"],"容":["chinese_simplified","chinese_traditional"],"儿":["chinese_simplified"],"须":["chinese_simplified"],"际":["chinese_simplified"],"商":["chinese_simplified","chinese_traditional"],"非":["chinese_simplified","chinese_traditional"],"验":["chinese_simplified"],"连":["chinese_simplified"],"断":["chinese_simplified"],"深":["chinese_simplified","chinese_traditional"],"难":["chinese_simplified"],"近":["chinese_simplified","chinese_traditional"],"矿":["chinese_simplified"],"千":["chinese_simplified","chinese_traditional"],"周":["chinese_simplified"],"委":["chinese_simplified","chinese_traditional"],"素":["chinese_simplified","chinese_traditional"],"技":["chinese_simplified","chinese_traditional"],"备":["chinese_simplified"],"半":["chinese_simplified","chinese_traditional"],"办":["chinese_simplified"],"青":["chinese_simplified","chinese_traditional"],"省":["chinese_simplified","chinese_traditional"],"列":["chinese_simplified","chinese_traditional"],"习":["chinese_simplified"],"响":["chinese_simplified"],"约":["chinese_simplified"],"支":["chinese_simplified","chinese_traditional"],"般":["chinese_simplified","chinese_traditional"],"史":["chinese_simplified","chinese_traditional"],"感":["chinese_simplified","chinese_traditional"],"劳":["chinese_simplified"],"便":["chinese_simplified","chinese_traditional"],"团":["chinese_simplified"],"往":["chinese_simplified","chinese_traditional"],"酸":["chinese_simplified","chinese_traditional"],"历":["chinese_simplified"],"市":["chinese_simplified","chinese_traditional"],"克":["chinese_simplified","chinese_traditional"],"何":["chinese_simplified","chinese_traditional"],"除":["chinese_simplified","chinese_traditional"],"消":["chinese_simplified","chinese_traditional"],"构":["chinese_simplified"],"府":["chinese_simplified","chinese_traditional"],"称":["chinese_simplified"],"太":["chinese_simplified","chinese_traditional"],"准":["chinese_simplified"],"精":["chinese_simplified","chinese_traditional"],"值":["chinese_simplified","chinese_traditional"],"号":["chinese_simplified"],"率":["chinese_simplified","chinese_traditional"],"族":["chinese_simplified","chinese_traditional"],"维":["chinese_simplified"],"划":["chinese_simplified"],"选":["chinese_simplified"],"标":["chinese_simplified"],"写":["chinese_simplified"],"存":["chinese_simplified","chinese_traditional"],"候":["chinese_simplified","chinese_traditional"],"毛":["chinese_simplified","chinese_traditional"],"亲":["chinese_simplified"],"快":["chinese_simplified","chinese_traditional"],"效":["chinese_simplified","chinese_traditional"],"斯":["chinese_simplified","chinese_traditional"],"院":["chinese_simplified","chinese_traditional"],"查":["chinese_simplified","chinese_traditional"],"江":["chinese_simplified","chinese_traditional"],"型":["chinese_simplified","chinese_traditional"],"眼":["chinese_simplified","chinese_traditional"],"王":["chinese_simplified","chinese_traditional"],"按":["chinese_simplified","chinese_traditional"],"格":["chinese_simplified","chinese_traditional"],"养":["chinese_simplified"],"易":["chinese_simplified","chinese_traditional"],"置":["chinese_simplified","chinese_traditional"],"派":["chinese_simplified","chinese_traditional"],"层":["chinese_simplified"],"片":["chinese_simplified","chinese_traditional"],"始":["chinese_simplified","chinese_traditional"],"却":["chinese_simplified"],"专":["chinese_simplified"],"状":["chinese_simplified"],"育":["chinese_simplified","chinese_traditional"],"厂":["chinese_simplified"],"京":["chinese_simplified","chinese_traditional"],"识":["chinese_simplified"],"适":["chinese_simplified"],"属":["chinese_simplified"],"圆":["chinese_simplified"],"包":["chinese_simplified","chinese_traditional"],"火":["chinese_simplified","chinese_traditional"],"住":["chinese_simplified","chinese_traditional"],"调":["chinese_simplified"],"满":["chinese_simplified"],"县":["chinese_simplified"],"局":["chinese_simplified","chinese_traditional"],"照":["chinese_simplified","chinese_traditional"],"参":["chinese_simplified"],"红":["chinese_simplified"],"细":["chinese_simplified"],"引":["chinese_simplified","chinese_traditional"],"听":["chinese_simplified"],"该":["chinese_simplified"],"铁":["chinese_simplified"],"价":["chinese_simplified"],"严":["chinese_simplified"],"首":["chinese_simplified","chinese_traditional"],"底":["chinese_simplified","chinese_traditional"],"液":["chinese_simplified","chinese_traditional"],"官":["chinese_simplified","chinese_traditional"],"德":["chinese_simplified","chinese_traditional"],"随":["chinese_simplified"],"病":["chinese_simplified","chinese_traditional"],"苏":["chinese_simplified"],"失":["chinese_simplified","chinese_traditional"],"尔":["chinese_simplified"],"死":["chinese_simplified","chinese_traditional"],"讲":["chinese_simplified"],"配":["chinese_simplified","chinese_traditional"],"女":["chinese_simplified","chinese_traditional"],"黄":["chinese_simplified"],"推":["chinese_simplified","chinese_traditional"],"显":["chinese_simplified"],"谈":["chinese_simplified"],"罪":["chinese_simplified","chinese_traditional"],"神":["chinese_simplified","chinese_traditional"],"艺":["chinese_simplified"],"呢":["chinese_simplified","chinese_traditional"],"席":["chinese_simplified","chinese_traditional"],"含":["chinese_simplified","chinese_traditional"],"企":["chinese_simplified","chinese_traditional"],"望":["chinese_simplified","chinese_traditional"],"密":["chinese_simplified","chinese_traditional"],"批":["chinese_simplified","chinese_traditional"],"营":["chinese_simplified"],"项":["chinese_simplified"],"防":["chinese_simplified","chinese_traditional"],"举":["chinese_simplified"],"球":["chinese_simplified","chinese_traditional"],"英":["chinese_simplified","chinese_traditional"],"氧":["chinese_simplified","chinese_traditional"],"势":["chinese_simplified"],"告":["chinese_simplified","chinese_traditional"],"李":["chinese_simplified","chinese_traditional"],"台":["chinese_simplified","chinese_traditional"],"落":["chinese_simplified","chinese_traditional"],"木":["chinese_simplified","chinese_traditional"],"帮":["chinese_simplified"],"轮":["chinese_simplified"],"破":["chinese_simplified","chinese_traditional"],"亚":["chinese_simplified"],"师":["chinese_simplified"],"围":["chinese_simplified"],"注":["chinese_simplified","chinese_traditional"],"远":["chinese_simplified"],"字":["chinese_simplified","chinese_traditional"],"材":["chinese_simplified","chinese_traditional"],"排":["chinese_simplified","chinese_traditional"],"供":["chinese_simplified","chinese_traditional"],"河":["chinese_simplified","chinese_traditional"],"态":["chinese_simplified"],"封":["chinese_simplified","chinese_traditional"],"另":["chinese_simplified","chinese_traditional"],"施":["chinese_simplified","chinese_traditional"],"减":["chinese_simplified"],"树":["chinese_simplified"],"溶":["chinese_simplified","chinese_traditional"],"怎":["chinese_simplified","chinese_traditional"],"止":["chinese_simplified","chinese_traditional"],"案":["chinese_simplified","chinese_traditional"],"言":["chinese_simplified","chinese_traditional"],"士":["chinese_simplified","chinese_traditional"],"均":["chinese_simplified","chinese_traditional"],"武":["chinese_simplified","chinese_traditional"],"固":["chinese_simplified","chinese_traditional"],"叶":["chinese_simplified"],"鱼":["chinese_simplified"],"波":["chinese_simplified","chinese_traditional"],"视":["chinese_simplified"],"仅":["chinese_simplified"],"费":["chinese_simplified"],"紧":["chinese_simplified"],"爱":["chinese_simplified"],"左":["chinese_simplified","chinese_traditional"],"章":["chinese_simplified","chinese_traditional"],"早":["chinese_simplified","chinese_traditional"],"朝":["chinese_simplified","chinese_traditional"],"害":["chinese_simplified","chinese_traditional"],"续":["chinese_simplified"],"轻":["chinese_simplified"],"服":["chinese_simplified","chinese_traditional"],"试":["chinese_simplified"],"食":["chinese_simplified","chinese_traditional"],"充":["chinese_simplified","chinese_traditional"],"兵":["chinese_simplified","chinese_traditional"],"源":["chinese_simplified","chinese_traditional"],"判":["chinese_simplified","chinese_traditional"],"护":["chinese_simplified"],"司":["chinese_simplified","chinese_traditional"],"足":["chinese_simplified","chinese_traditional"],"某":["chinese_simplified","chinese_traditional"],"练":["chinese_simplified"],"差":["chinese_simplified","chinese_traditional"],"致":["chinese_simplified","chinese_traditional"],"板":["chinese_simplified","chinese_traditional"],"田":["chinese_simplified","chinese_traditional"],"降":["chinese_simplified","chinese_traditional"],"黑":["chinese_simplified","chinese_traditional"],"犯":["chinese_simplified","chinese_traditional"],"负":["chinese_simplified"],"击":["chinese_simplified"],"范":["chinese_simplified","chinese_traditional"],"继":["chinese_simplified"],"兴":["chinese_simplified"],"似":["chinese_simplified","chinese_traditional"],"余":["chinese_simplified"],"坚":["chinese_simplified"],"曲":["chinese_simplified","chinese_traditional"],"输":["chinese_simplified"],"修":["chinese_simplified","chinese_traditional"],"故":["chinese_simplified","chinese_traditional"],"城":["chinese_simplified","chinese_traditional"],"夫":["chinese_simplified","chinese_traditional"],"够":["chinese_simplified"],"送":["chinese_simplified","chinese_traditional"],"笔":["chinese_simplified"],"船":["chinese_simplified","chinese_traditional"],"占":["chinese_simplified"],"右":["chinese_simplified","chinese_traditional"],"财":["chinese_simplified"],"吃":["chinese_simplified","chinese_traditional"],"富":["chinese_simplified","chinese_traditional"],"春":["chinese_simplified","chinese_traditional"],"职":["chinese_simplified"],"觉":["chinese_simplified"],"汉":["chinese_simplified"],"画":["chinese_simplified"],"功":["chinese_simplified","chinese_traditional"],"巴":["chinese_simplified","chinese_traditional"],"跟":["chinese_simplified","chinese_traditional"],"虽":["chinese_simplified"],"杂":["chinese_simplified"],"飞":["chinese_simplified"],"检":["chinese_simplified"],"吸":["chinese_simplified","chinese_traditional"],"助":["chinese_simplified","chinese_traditional"],"升":["chinese_simplified"],"阳":["chinese_simplified"],"互":["chinese_simplified","chinese_traditional"],"初":["chinese_simplified","chinese_traditional"],"创":["chinese_simplified"],"抗":["chinese_simplified","chinese_traditional"],"考":["chinese_simplified","chinese_traditional"],"投":["chinese_simplified","chinese_traditional"],"坏":["chinese_simplified"],"策":["chinese_simplified","chinese_traditional"],"古":["chinese_simplified","chinese_traditional"],"径":["chinese_simplified"],"换":["chinese_simplified"],"未":["chinese_simplified","chinese_traditional"],"跑":["chinese_simplified","chinese_traditional"],"留":["chinese_simplified","chinese_traditional"],"钢":["chinese_simplified"],"曾":["chinese_simplified","chinese_traditional"],"端":["chinese_simplified","chinese_traditional"],"责":["chinese_simplified"],"站":["chinese_simplified","chinese_traditional"],"简":["chinese_simplified"],"述":["chinese_simplified","chinese_traditional"],"钱":["chinese_simplified"],"副":["chinese_simplified","chinese_traditional"],"尽":["chinese_simplified"],"帝":["chinese_simplified","chinese_traditional"],"射":["chinese_simplified","chinese_traditional"],"草":["chinese_simplified","chinese_traditional"],"冲":["chinese_simplified"],"承":["chinese_simplified","chinese_traditional"],"独":["chinese_simplified"],"令":["chinese_simplified","chinese_traditional"],"限":["chinese_simplified","chinese_traditional"],"阿":["chinese_simplified","chinese_traditional"],"宣":["chinese_simplified","chinese_traditional"],"环":["chinese_simplified"],"双":["chinese_simplified"],"请":["chinese_simplified"],"超":["chinese_simplified","chinese_traditional"],"微":["chinese_simplified","chinese_traditional"],"让":["chinese_simplified"],"控":["chinese_simplified","chinese_traditional"],"州":["chinese_simplified","chinese_traditional"],"良":["chinese_simplified","chinese_traditional"],"轴":["chinese_simplified"],"找":["chinese_simplified","chinese_traditional"],"否":["chinese_simplified","chinese_traditional"],"纪":["chinese_simplified"],"益":["chinese_simplified","chinese_traditional"],"依":["chinese_simplified","chinese_traditional"],"优":["chinese_simplified"],"顶":["chinese_simplified"],"础":["chinese_simplified"],"载":["chinese_simplified"],"倒":["chinese_simplified","chinese_traditional"],"房":["chinese_simplified","chinese_traditional"],"突":["chinese_simplified","chinese_traditional"],"坐":["chinese_simplified","chinese_traditional"],"粉":["chinese_simplified","chinese_traditional"],"敌":["chinese_simplified"],"略":["chinese_simplified","chinese_traditional"],"客":["chinese_simplified","chinese_traditional"],"袁":["chinese_simplified","chinese_traditional"],"冷":["chinese_simplified","chinese_traditional"],"胜":["chinese_simplified"],"绝":["chinese_simplified"],"析":["chinese_simplified","chinese_traditional"],"块":["chinese_simplified"],"剂":["chinese_simplified"],"测":["chinese_simplified"],"丝":["chinese_simplified"],"协":["chinese_simplified"],"诉":["chinese_simplified"],"念":["chinese_simplified","chinese_traditional"],"陈":["chinese_simplified"],"仍":["chinese_simplified","chinese_traditional"],"罗":["chinese_simplified"],"盐":["chinese_simplified"],"友":["chinese_simplified","chinese_traditional"],"洋":["chinese_simplified","chinese_traditional"],"错":["chinese_simplified"],"苦":["chinese_simplified","chinese_traditional"],"夜":["chinese_simplified","chinese_traditional"],"刑":["chinese_simplified","chinese_traditional"],"移":["chinese_simplified","chinese_traditional"],"频":["chinese_simplified"],"逐":["chinese_simplified","chinese_traditional"],"靠":["chinese_simplified","chinese_traditional"],"混":["chinese_simplified","chinese_traditional"],"母":["chinese_simplified","chinese_traditional"],"短":["chinese_simplified","chinese_traditional"],"皮":["chinese_simplified","chinese_traditional"],"终":["chinese_simplified"],"聚":["chinese_simplified","chinese_traditional"],"汽":["chinese_simplified","chinese_traditional"],"村":["chinese_simplified","chinese_traditional"],"云":["chinese_simplified"],"哪":["chinese_simplified","chinese_traditional"],"既":["chinese_simplified","chinese_traditional"],"距":["chinese_simplified","chinese_traditional"],"卫":["chinese_simplified"],"停":["chinese_simplified","chinese_traditional"],"烈":["chinese_simplified","chinese_traditional"],"央":["chinese_simplified","chinese_traditional"],"察":["chinese_simplified","chinese_traditional"],"烧":["chinese_simplified"],"迅":["chinese_simplified","chinese_traditional"],"境":["chinese_simplified","chinese_traditional"],"若":["chinese_simplified","chinese_traditional"],"印":["chinese_simplified","chinese_traditional"],"洲":["chinese_simplified","chinese_traditional"],"刻":["chinese_simplified","chinese_traditional"],"括":["chinese_simplified","chinese_traditional"],"激":["chinese_simplified","chinese_traditional"],"孔":["chinese_simplified","chinese_traditional"],"搞":["chinese_simplified","chinese_traditional"],"甚":["chinese_simplified","chinese_traditional"],"室":["chinese_simplified","chinese_traditional"],"待":["chinese_simplified","chinese_traditional"],"核":["chinese_simplified","chinese_traditional"],"校":["chinese_simplified","chinese_traditional"],"散":["chinese_simplified","chinese_traditional"],"侵":["chinese_simplified","chinese_traditional"],"吧":["chinese_simplified","chinese_traditional"],"甲":["chinese_simplified","chinese_traditional"],"游":["chinese_simplified"],"久":["chinese_simplified","chinese_traditional"],"菜":["chinese_simplified","chinese_traditional"],"味":["chinese_simplified","chinese_traditional"],"旧":["chinese_simplified"],"模":["chinese_simplified","chinese_traditional"],"湖":["chinese_simplified","chinese_traditional"],"货":["chinese_simplified"],"损":["chinese_simplified"],"预":["chinese_simplified"],"阻":["chinese_simplified","chinese_traditional"],"毫":["chinese_simplified","chinese_traditional"],"普":["chinese_simplified","chinese_traditional"],"稳":["chinese_simplified"],"乙":["chinese_simplified","chinese_traditional"],"妈":["chinese_simplified"],"植":["chinese_simplified","chinese_traditional"],"息":["chinese_simplified","chinese_traditional"],"扩":["chinese_simplified"],"银":["chinese_simplified"],"语":["chinese_simplified"],"挥":["chinese_simplified"],"酒":["chinese_simplified","chinese_traditional"],"守":["chinese_simplified","chinese_traditional"],"拿":["chinese_simplified","chinese_traditional"],"序":["chinese_simplified","chinese_traditional"],"纸":["chinese_simplified"],"医":["chinese_simplified"],"缺":["chinese_simplified","chinese_traditional"],"雨":["chinese_simplified","chinese_traditional"],"吗":["chinese_simplified"],"针":["chinese_simplified"],"刘":["chinese_simplified"],"啊":["chinese_simplified","chinese_traditional"],"急":["chinese_simplified","chinese_traditional"],"唱":["chinese_simplified","chinese_traditional"],"误":["chinese_simplified"],"训":["chinese_simplified"],"愿":["chinese_simplified"],"审":["chinese_simplified"],"附":["chinese_simplified","chinese_traditional"],"获":["chinese_simplified"],"茶":["chinese_simplified","chinese_traditional"],"鲜":["chinese_simplified"],"粮":["chinese_simplified"],"斤":["chinese_simplified","chinese_traditional"],"孩":["chinese_simplified","chinese_traditional"],"脱":["chinese_simplified"],"硫":["chinese_simplified","chinese_traditional"],"肥":["chinese_simplified","chinese_traditional"],"善":["chinese_simplified","chinese_traditional"],"龙":["chinese_simplified"],"演":["chinese_simplified","chinese_traditional"],"父":["chinese_simplified","chinese_traditional"],"渐":["chinese_simplified"],"血":["chinese_simplified","chinese_traditional"],"欢":["chinese_simplified"],"械":["chinese_simplified","chinese_traditional"],"掌":["chinese_simplified","chinese_traditional"],"歌":["chinese_simplified","chinese_traditional"],"沙":["chinese_simplified","chinese_traditional"],"刚":["chinese_simplified"],"攻":["chinese_simplified","chinese_traditional"],"谓":["chinese_simplified"],"盾":["chinese_simplified","chinese_traditional"],"讨":["chinese_simplified"],"晚":["chinese_simplified","chinese_traditional"],"粒":["chinese_simplified","chinese_traditional"],"乱":["chinese_simplified"],"燃":["chinese_simplified","chinese_traditional"],"矛":["chinese_simplified","chinese_traditional"],"乎":["chinese_simplified","chinese_traditional"],"杀":["chinese_simplified"],"药":["chinese_simplified"],"宁":["chinese_simplified"],"鲁":["chinese_simplified"],"贵":["chinese_simplified"],"钟":["chinese_simplified"],"煤":["chinese_simplified","chinese_traditional"],"读":["chinese_simplified"],"班":["chinese_simplified","chinese_traditional"],"伯":["chinese_simplified","chinese_traditional"],"香":["chinese_simplified","chinese_traditional"],"介":["chinese_simplified","chinese_traditional"],"迫":["chinese_simplified","chinese_traditional"],"句":["chinese_simplified","chinese_traditional"],"丰":["chinese_simplified"],"培":["chinese_simplified","chinese_traditional"],"握":["chinese_simplified","chinese_traditional"],"兰":["chinese_simplified"],"担":["chinese_simplified"],"弦":["chinese_simplified","chinese_traditional"],"蛋":["chinese_simplified","chinese_traditional"],"沉":["chinese_simplified","chinese_traditional"],"假":["chinese_simplified","chinese_traditional"],"穿":["chinese_simplified","chinese_traditional"],"执":["chinese_simplified"],"答":["chinese_simplified","chinese_traditional"],"乐":["chinese_simplified"],"谁":["chinese_simplified"],"顺":["chinese_simplified"],"烟":["chinese_simplified"],"缩":["chinese_simplified"],"征":["chinese_simplified"],"脸":["chinese_simplified"],"喜":["chinese_simplified","chinese_traditional"],"松":["chinese_simplified","chinese_traditional"],"脚":["chinese_simplified"],"困":["chinese_simplified","chinese_traditional"],"异":["chinese_simplified"],"免":["chinese_simplified","chinese_traditional"],"背":["chinese_simplified","chinese_traditional"],"星":["chinese_simplified","chinese_traditional"],"福":["chinese_simplified","chinese_traditional"],"买":["chinese_simplified"],"染":["chinese_simplified","chinese_traditional"],"井":["chinese_simplified","chinese_traditional"],"概":["chinese_simplified","chinese_traditional"],"慢":["chinese_simplified","chinese_traditional"],"怕":["chinese_simplified","chinese_traditional"],"磁":["chinese_simplified","chinese_traditional"],"倍":["chinese_simplified","chinese_traditional"],"祖":["chinese_simplified","chinese_traditional"],"皇":["chinese_simplified","chinese_traditional"],"促":["chinese_simplified","chinese_traditional"],"静":["chinese_simplified"],"补":["chinese_simplified"],"评":["chinese_simplified"],"翻":["chinese_simplified","chinese_traditional"],"肉":["chinese_simplified","chinese_traditional"],"践":["chinese_simplified"],"尼":["chinese_simplified","chinese_traditional"],"衣":["chinese_simplified","chinese_traditional"],"宽":["chinese_simplified"],"扬":["chinese_simplified"],"棉":["chinese_simplified","chinese_traditional"],"希":["chinese_simplified","chinese_traditional"],"伤":["chinese_simplified"],"操":["chinese_simplified","chinese_traditional"],"垂":["chinese_simplified","chinese_traditional"],"秋":["chinese_simplified","chinese_traditional"],"宜":["chinese_simplified","chinese_traditional"],"氢":["chinese_simplified"],"套":["chinese_simplified","chinese_traditional"],"督":["chinese_simplified","chinese_traditional"],"振":["chinese_simplified","chinese_traditional"],"架":["chinese_simplified","chinese_traditional"],"亮":["chinese_simplified","chinese_traditional"],"末":["chinese_simplified","chinese_traditional"],"宪":["chinese_simplified"],"庆":["chinese_simplified"],"编":["chinese_simplified"],"牛":["chinese_simplified","chinese_traditional"],"触":["chinese_simplified"],"映":["chinese_simplified","chinese_traditional"],"雷":["chinese_simplified","chinese_traditional"],"销":["chinese_simplified"],"诗":["chinese_simplified"],"座":["chinese_simplified","chinese_traditional"],"居":["chinese_simplified","chinese_traditional"],"抓":["chinese_simplified","chinese_traditional"],"裂":["chinese_simplified","chinese_traditional"],"胞":["chinese_simplified","chinese_traditional"],"呼":["chinese_simplified","chinese_traditional"],"娘":["chinese_simplified","chinese_traditional"],"景":["chinese_simplified","chinese_traditional"],"威":["chinese_simplified","chinese_traditional"],"绿":["chinese_simplified"],"晶":["chinese_simplified","chinese_traditional"],"厚":["chinese_simplified","chinese_traditional"],"盟":["chinese_simplified","chinese_traditional"],"衡":["chinese_simplified","chinese_traditional"],"鸡":["chinese_simplified"],"孙":["chinese_simplified"],"延":["chinese_simplified","chinese_traditional"],"危":["chinese_simplified","chinese_traditional"],"胶":["chinese_simplified"],"屋":["chinese_simplified","chinese_traditional"],"乡":["chinese_simplified"],"临":["chinese_simplified"],"陆":["chinese_simplified"],"顾":["chinese_simplified"],"掉":["chinese_simplified","chinese_traditional"],"呀":["chinese_simplified","chinese_traditional"],"灯":["chinese_simplified"],"岁":["chinese_simplified"],"措":["chinese_simplified","chinese_traditional"],"束":["chinese_simplified","chinese_traditional"],"耐":["chinese_simplified","chinese_traditional"],"剧":["chinese_simplified"],"玉":["chinese_simplified","chinese_traditional"],"赵":["chinese_simplified"],"跳":["chinese_simplified","chinese_traditional"],"哥":["chinese_simplified","chinese_traditional"],"季":["chinese_simplified","chinese_traditional"],"课":["chinese_simplified"],"凯":["chinese_simplified"],"胡":["chinese_simplified","chinese_traditional"],"额":["chinese_simplified"],"款":["chinese_simplified","chinese_traditional"],"绍":["chinese_simplified"],"卷":["chinese_simplified","chinese_traditional"],"齐":["chinese_simplified"],"伟":["chinese_simplified"],"蒸":["chinese_simplified","chinese_traditional"],"殖":["chinese_simplified","chinese_traditional"],"永":["chinese_simplified","chinese_traditional"],"宗":["chinese_simplified","chinese_traditional"],"苗":["chinese_simplified","chinese_traditional"],"川":["chinese_simplified","chinese_traditional"],"炉":["chinese_simplified"],"岩":["chinese_simplified","chinese_traditional"],"弱":["chinese_simplified","chinese_traditional"],"零":["chinese_simplified","chinese_traditional"],"杨":["chinese_simplified"],"奏":["chinese_simplified","chinese_traditional"],"沿":["chinese_simplified","chinese_traditional"],"露":["chinese_simplified","chinese_traditional"],"杆":["chinese_simplified"],"探":["chinese_simplified","chinese_traditional"],"滑":["chinese_simplified","chinese_traditional"],"镇":["chinese_simplified"],"饭":["chinese_simplified"],"浓":["chinese_simplified"],"航":["chinese_simplified","chinese_traditional"],"怀":["chinese_simplified"],"赶":["chinese_simplified"],"库":["chinese_simplified"],"夺":["chinese_simplified"],"伊":["chinese_simplified","chinese_traditional"],"灵":["chinese_simplified"],"税":["chinese_simplified"],"途":["chinese_simplified","chinese_traditional"],"灭":["chinese_simplified"],"赛":["chinese_simplified"],"归":["chinese_simplified"],"召":["chinese_simplified","chinese_traditional"],"鼓":["chinese_simplified","chinese_traditional"],"播":["chinese_simplified","chinese_traditional"],"盘":["chinese_simplified"],"裁":["chinese_simplified","chinese_traditional"],"险":["chinese_simplified"],"康":["chinese_simplified","chinese_traditional"],"唯":["chinese_simplified","chinese_traditional"],"录":["chinese_simplified"],"菌":["chinese_simplified","chinese_traditional"],"纯":["chinese_simplified"],"借":["chinese_simplified","chinese_traditional"],"糖":["chinese_simplified","chinese_traditional"],"盖":["chinese_simplified"],"横":["chinese_simplified"],"符":["chinese_simplified","chinese_traditional"],"私":["chinese_simplified","chinese_traditional"],"努":["chinese_simplified","chinese_traditional"],"堂":["chinese_simplified","chinese_traditional"],"域":["chinese_simplified","chinese_traditional"],"枪":["chinese_simplified"],"润":["chinese_simplified"],"幅":["chinese_simplified","chinese_traditional"],"哈":["chinese_simplified","chinese_traditional"],"竟":["chinese_simplified","chinese_traditional"],"熟":["chinese_simplified","chinese_traditional"],"虫":["chinese_simplified"],"泽":["chinese_simplified"],"脑":["chinese_simplified"],"壤":["chinese_simplified","chinese_traditional"],"碳":["chinese_simplified","chinese_traditional"],"欧":["chinese_simplified"],"遍":["chinese_simplified","chinese_traditional"],"侧":["chinese_simplified"],"寨":["chinese_simplified","chinese_traditional"],"敢":["chinese_simplified","chinese_traditional"],"彻":["chinese_simplified"],"虑":["chinese_simplified"],"斜":["chinese_simplified","chinese_traditional"],"薄":["chinese_simplified","chinese_traditional"],"庭":["chinese_simplified","chinese_traditional"],"纳":["chinese_simplified"],"弹":["chinese_simplified"],"饲":["chinese_simplified"],"伸":["chinese_simplified","chinese_traditional"],"折":["chinese_simplified","chinese_traditional"],"麦":["chinese_simplified"],"湿":["chinese_simplified"],"暗":["chinese_simplified","chinese_traditional"],"荷":["chinese_simplified","chinese_traditional"],"瓦":["chinese_simplified","chinese_traditional"],"塞":["chinese_simplified","chinese_traditional"],"床":["chinese_simplified","chinese_traditional"],"筑":["chinese_simplified"],"恶":["chinese_simplified"],"户":["chinese_simplified"],"访":["chinese_simplified"],"塔":["chinese_simplified","chinese_traditional"],"奇":["chinese_simplified","chinese_traditional"],"透":["chinese_simplified","chinese_traditional"],"梁":["chinese_simplified","chinese_traditional"],"刀":["chinese_simplified","chinese_traditional"],"旋":["chinese_simplified","chinese_traditional"],"迹":["chinese_simplified"],"卡":["chinese_simplified","chinese_traditional"],"氯":["chinese_simplified","chinese_traditional"],"遇":["chinese_simplified","chinese_traditional"],"份":["chinese_simplified","chinese_traditional"],"毒":["chinese_simplified","chinese_traditional"],"泥":["chinese_simplified","chinese_traditional"],"退":["chinese_simplified","chinese_traditional"],"洗":["chinese_simplified","chinese_traditional"],"摆":["chinese_simplified"],"灰":["chinese_simplified","chinese_traditional"],"彩":["chinese_simplified","chinese_traditional"],"卖":["chinese_simplified"],"耗":["chinese_simplified","chinese_traditional"],"夏":["chinese_simplified","chinese_traditional"],"择":["chinese_simplified"],"忙":["chinese_simplified","chinese_traditional"],"铜":["chinese_simplified"],"献":["chinese_simplified"],"硬":["chinese_simplified","chinese_traditional"],"予":["chinese_simplified","chinese_traditional"],"繁":["chinese_simplified","chinese_traditional"],"圈":["chinese_simplified","chinese_traditional"],"雪":["chinese_simplified","chinese_traditional"],"函":["chinese_simplified","chinese_traditional"],"亦":["chinese_simplified","chinese_traditional"],"抽":["chinese_simplified","chinese_traditional"],"篇":["chinese_simplified","chinese_traditional"],"阵":["chinese_simplified"],"阴":["chinese_simplified"],"丁":["chinese_simplified","chinese_traditional"],"尺":["chinese_simplified","chinese_traditional"],"追":["chinese_simplified","chinese_traditional"],"堆":["chinese_simplified","chinese_traditional"],"雄":["chinese_simplified","chinese_traditional"],"迎":["chinese_simplified","chinese_traditional"],"泛":["chinese_simplified","chinese_traditional"],"爸":["chinese_simplified","chinese_traditional"],"楼":["chinese_simplified"],"避":["chinese_simplified","chinese_traditional"],"谋":["chinese_simplified"],"吨":["chinese_simplified"],"野":["chinese_simplified","chinese_traditional"],"猪":["chinese_simplified"],"旗":["chinese_simplified","chinese_traditional"],"累":["chinese_simplified","chinese_traditional"],"偏":["chinese_simplified","chinese_traditional"],"典":["chinese_simplified","chinese_traditional"],"馆":["chinese_simplified"],"索":["chinese_simplified","chinese_traditional"],"秦":["chinese_simplified","chinese_traditional"],"脂":["chinese_simplified","chinese_traditional"],"潮":["chinese_simplified","chinese_traditional"],"爷":["chinese_simplified"],"豆":["chinese_simplified","chinese_traditional"],"忽":["chinese_simplified","chinese_traditional"],"托":["chinese_simplified","chinese_traditional"],"惊":["chinese_simplified"],"塑":["chinese_simplified","chinese_traditional"],"遗":["chinese_simplified"],"愈":["chinese_simplified","chinese_traditional"],"朱":["chinese_simplified","chinese_traditional"],"替":["chinese_simplified","chinese_traditional"],"纤":["chinese_simplified"],"粗":["chinese_simplified","chinese_traditional"],"倾":["chinese_simplified"],"尚":["chinese_simplified","chinese_traditional"],"痛":["chinese_simplified","chinese_traditional"],"楚":["chinese_simplified","chinese_traditional"],"谢":["chinese_simplified"],"奋":["chinese_simplified"],"购":["chinese_simplified"],"磨":["chinese_simplified","chinese_traditional"],"君":["chinese_simplified","chinese_traditional"],"池":["chinese_simplified","chinese_traditional"],"旁":["chinese_simplified","chinese_traditional"],"碎":["chinese_simplified","chinese_traditional"],"骨":["chinese_simplified","chinese_traditional"],"监":["chinese_simplified"],"捕":["chinese_simplified","chinese_traditional"],"弟":["chinese_simplified","chinese_traditional"],"暴":["chinese_simplified","chinese_traditional"],"割":["chinese_simplified","chinese_traditional"],"贯":["chinese_simplified"],"殊":["chinese_simplified","chinese_traditional"],"释":["chinese_simplified"],"词":["chinese_simplified"],"亡":["chinese_simplified","chinese_traditional"],"壁":["chinese_simplified","chinese_traditional"],"顿":["chinese_simplified"],"宝":["chinese_simplified"],"午":["chinese_simplified","chinese_traditional"],"尘":["chinese_simplified"],"闻":["chinese_simplified"],"揭":["chinese_simplified","chinese_traditional"],"炮":["chinese_simplified","chinese_traditional"],"残":["chinese_simplified"],"冬":["chinese_simplified","chinese_traditional"],"桥":["chinese_simplified"],"妇":["chinese_simplified"],"警":["chinese_simplified","chinese_traditional"],"综":["chinese_simplified"],"招":["chinese_simplified","chinese_traditional"],"吴":["chinese_simplified"],"付":["chinese_simplified","chinese_traditional"],"浮":["chinese_simplified","chinese_traditional"],"遭":["chinese_simplified","chinese_traditional"],"徐":["chinese_simplified","chinese_traditional"],"您":["chinese_simplified","chinese_traditional"],"摇":["chinese_simplified"],"谷":["chinese_simplified","chinese_traditional"],"赞":["chinese_simplified"],"箱":["chinese_simplified","chinese_traditional"],"隔":["chinese_simplified","chinese_traditional"],"订":["chinese_simplified"],"男":["chinese_simplified","chinese_traditional"],"吹":["chinese_simplified","chinese_traditional"],"园":["chinese_simplified"],"纷":["chinese_simplified"],"唐":["chinese_simplified","chinese_traditional"],"败":["chinese_simplified"],"宋":["chinese_simplified","chinese_traditional"],"玻":["chinese_simplified","chinese_traditional"],"巨":["chinese_simplified","chinese_traditional"],"耕":["chinese_simplified","chinese_traditional"],"坦":["chinese_simplified","chinese_traditional"],"荣":["chinese_simplified"],"闭":["chinese_simplified"],"湾":["chinese_simplified"],"键":["chinese_simplified"],"凡":["chinese_simplified","chinese_traditional"],"驻":["chinese_simplified"],"锅":["chinese_simplified"],"救":["chinese_simplified","chinese_traditional"],"恩":["chinese_simplified","chinese_traditional"],"剥":["chinese_simplified"],"凝":["chinese_simplified","chinese_traditional"],"碱":["chinese_simplified"],"齿":["chinese_simplified"],"截":["chinese_simplified","chinese_traditional"],"炼":["chinese_simplified"],"麻":["chinese_simplified","chinese_traditional"],"纺":["chinese_simplified"],"禁":["chinese_simplified","chinese_traditional"],"废":["chinese_simplified"],"盛":["chinese_simplified","chinese_traditional"],"版":["chinese_simplified","chinese_traditional"],"缓":["chinese_simplified"],"净":["chinese_simplified"],"睛":["chinese_simplified","chinese_traditional"],"昌":["chinese_simplified","chinese_traditional"],"婚":["chinese_simplified","chinese_traditional"],"涉":["chinese_simplified","chinese_traditional"],"筒":["chinese_simplified","chinese_traditional"],"嘴":["chinese_simplified","chinese_traditional"],"插":["chinese_simplified","chinese_traditional"],"岸":["chinese_simplified","chinese_traditional"],"朗":["chinese_simplified","chinese_traditional"],"庄":["chinese_simplified"],"街":["chinese_simplified","chinese_traditional"],"藏":["chinese_simplified","chinese_traditional"],"姑":["chinese_simplified","chinese_traditional"],"贸":["chinese_simplified"],"腐":["chinese_simplified","chinese_traditional"],"奴":["chinese_simplified","chinese_traditional"],"啦":["chinese_simplified","chinese_traditional"],"惯":["chinese_simplified"],"乘":["chinese_simplified","chinese_traditional"],"伙":["chinese_simplified"],"恢":["chinese_simplified","chinese_traditional"],"匀":["chinese_simplified"],"纱":["chinese_simplified"],"扎":["chinese_simplified","chinese_traditional"],"辩":["chinese_simplified"],"耳":["chinese_simplified","chinese_traditional"],"彪":["chinese_simplified","chinese_traditional"],"臣":["chinese_simplified","chinese_traditional"],"亿":["chinese_simplified"],"璃":["chinese_simplified","chinese_traditional"],"抵":["chinese_simplified","chinese_traditional"],"脉":["chinese_simplified"],"秀":["chinese_simplified","chinese_traditional"],"萨":["chinese_simplified"],"俄":["chinese_simplified","chinese_traditional"],"网":["chinese_simplified"],"舞":["chinese_simplified","chinese_traditional"],"店":["chinese_simplified","chinese_traditional"],"喷":["chinese_simplified"],"纵":["chinese_simplified"],"寸":["chinese_simplified","chinese_traditional"],"汗":["chinese_simplified","chinese_traditional"],"挂":["chinese_simplified"],"洪":["chinese_simplified","chinese_traditional"],"贺":["chinese_simplified"],"闪":["chinese_simplified"],"柬":["chinese_simplified","chinese_traditional"],"爆":["chinese_simplified","chinese_traditional"],"烯":["chinese_simplified","chinese_traditional"],"津":["chinese_simplified","chinese_traditional"],"稻":["chinese_simplified","chinese_traditional"],"墙":["chinese_simplified"],"软":["chinese_simplified"],"勇":["chinese_simplified","chinese_traditional"],"像":["chinese_simplified","chinese_traditional"],"滚":["chinese_simplified"],"厘":["chinese_simplified","chinese_traditional"],"蒙":["chinese_simplified","chinese_traditional"],"芳":["chinese_simplified","chinese_traditional"],"肯":["chinese_simplified","chinese_traditional"],"坡":["chinese_simplified","chinese_traditional"],"柱":["chinese_simplified","chinese_traditional"],"荡":["chinese_simplified"],"腿":["chinese_simplified","chinese_traditional"],"仪":["chinese_simplified"],"旅":["chinese_simplified","chinese_traditional"],"尾":["chinese_simplified","chinese_traditional"],"轧":["chinese_simplified"],"冰":["chinese_simplified","chinese_traditional"],"贡":["chinese_simplified"],"登":["chinese_simplified","chinese_traditional"],"黎":["chinese_simplified","chinese_traditional"],"削":["chinese_simplified","chinese_traditional"],"钻":["chinese_simplified"],"勒":["chinese_simplified","chinese_traditional"],"逃":["chinese_simplified","chinese_traditional"],"障":["chinese_simplified","chinese_traditional"],"氨":["chinese_simplified","chinese_traditional"],"郭":["chinese_simplified","chinese_traditional"],"峰":["chinese_simplified","chinese_traditional"],"币":["chinese_simplified"],"港":["chinese_simplified","chinese_traditional"],"伏":["chinese_simplified","chinese_traditional"],"轨":["chinese_simplified"],"亩":["chinese_simplified"],"毕":["chinese_simplified"],"擦":["chinese_simplified","chinese_traditional"],"莫":["chinese_simplified","chinese_traditional"],"刺":["chinese_simplified","chinese_traditional"],"浪":["chinese_simplified","chinese_traditional"],"秘":["chinese_simplified","chinese_traditional"],"援":["chinese_simplified","chinese_traditional"],"株":["chinese_simplified","chinese_traditional"],"健":["chinese_simplified","chinese_traditional"],"售":["chinese_simplified","chinese_traditional"],"股":["chinese_simplified","chinese_traditional"],"岛":["chinese_simplified"],"甘":["chinese_simplified","chinese_traditional"],"泡":["chinese_simplified","chinese_traditional"],"睡":["chinese_simplified","chinese_traditional"],"童":["chinese_simplified","chinese_traditional"],"铸":["chinese_simplified"],"汤":["chinese_simplified"],"阀":["chinese_simplified"],"休":["chinese_simplified","chinese_traditional"],"汇":["chinese_simplified"],"舍":["chinese_simplified","chinese_traditional"],"牧":["chinese_simplified","chinese_traditional"],"绕":["chinese_simplified"],"炸":["chinese_simplified","chinese_traditional"],"哲":["chinese_simplified","chinese_traditional"],"磷":["chinese_simplified","chinese_traditional"],"绩":["chinese_simplified"],"朋":["chinese_simplified","chinese_traditional"],"淡":["chinese_simplified","chinese_traditional"],"尖":["chinese_simplified","chinese_traditional"],"启":["chinese_simplified"],"陷":["chinese_simplified","chinese_traditional"],"柴":["chinese_simplified","chinese_traditional"],"呈":["chinese_simplified","chinese_traditional"],"徒":["chinese_simplified","chinese_traditional"],"颜":["chinese_simplified"],"泪":["chinese_simplified"],"稍":["chinese_simplified","chinese_traditional"],"忘":["chinese_simplified","chinese_traditional"],"泵":["chinese_simplified","chinese_traditional"],"蓝":["chinese_simplified"],"拖":["chinese_simplified","chinese_traditional"],"洞":["chinese_simplified","chinese_traditional"],"授":["chinese_simplified","chinese_traditional"],"镜":["chinese_simplified"],"辛":["chinese_simplified","chinese_traditional"],"壮":["chinese_simplified"],"锋":["chinese_simplified"],"贫":["chinese_simplified"],"虚":["chinese_simplified"],"弯":["chinese_simplified"],"摩":["chinese_simplified","chinese_traditional"],"泰":["chinese_simplified","chinese_traditional"],"幼":["chinese_simplified","chinese_traditional"],"廷":["chinese_simplified","chinese_traditional"],"尊":["chinese_simplified","chinese_traditional"],"窗":["chinese_simplified","chinese_traditional"],"纲":["chinese_simplified"],"弄":["chinese_simplified","chinese_traditional"],"隶":["chinese_simplified"],"疑":["chinese_simplified","chinese_traditional"],"氏":["chinese_simplified","chinese_traditional"],"宫":["chinese_simplified"],"姐":["chinese_simplified","chinese_traditional"],"震":["chinese_simplified","chinese_traditional"],"瑞":["chinese_simplified","chinese_traditional"],"怪":["chinese_simplified","chinese_traditional"],"尤":["chinese_simplified","chinese_traditional"],"琴":["chinese_simplified","chinese_traditional"],"循":["chinese_simplified","chinese_traditional"],"描":["chinese_simplified","chinese_traditional"],"膜":["chinese_simplified","chinese_traditional"],"违":["chinese_simplified"],"夹":["chinese_simplified"],"腰":["chinese_simplified","chinese_traditional"],"缘":["chinese_simplified"],"珠":["chinese_simplified","chinese_traditional"],"穷":["chinese_simplified"],"森":["chinese_simplified","chinese_traditional"],"枝":["chinese_simplified","chinese_traditional"],"竹":["chinese_simplified","chinese_traditional"],"沟":["chinese_simplified"],"催":["chinese_simplified","chinese_traditional"],"绳":["chinese_simplified"],"忆":["chinese_simplified"],"邦":["chinese_simplified","chinese_traditional"],"剩":["chinese_simplified","chinese_traditional"],"幸":["chinese_simplified","chinese_traditional"],"浆":["chinese_simplified"],"栏":["chinese_simplified"],"拥":["chinese_simplified"],"牙":["chinese_simplified","chinese_traditional"],"贮":["chinese_simplified"],"礼":["chinese_simplified"],"滤":["chinese_simplified"],"钠":["chinese_simplified"],"纹":["chinese_simplified"],"罢":["chinese_simplified"],"拍":["chinese_simplified","chinese_traditional"],"咱":["chinese_simplified","chinese_traditional"],"喊":["chinese_simplified","chinese_traditional"],"袖":["chinese_simplified","chinese_traditional"],"埃":["chinese_simplified","chinese_traditional"],"勤":["chinese_simplified","chinese_traditional"],"罚":["chinese_simplified"],"焦":["chinese_simplified","chinese_traditional"],"潜":["chinese_simplified"],"伍":["chinese_simplified","chinese_traditional"],"墨":["chinese_simplified","chinese_traditional"],"欲":["chinese_simplified","chinese_traditional"],"缝":["chinese_simplified"],"姓":["chinese_simplified","chinese_traditional"],"刊":["chinese_simplified","chinese_traditional"],"饱":["chinese_simplified"],"仿":["chinese_simplified","chinese_traditional"],"奖":["chinese_simplified"],"铝":["chinese_simplified"],"鬼":["chinese_simplified","chinese_traditional"],"丽":["chinese_simplified"],"跨":["chinese_simplified","chinese_traditional"],"默":["chinese_simplified","chinese_traditional"],"挖":["chinese_simplified","chinese_traditional"],"链":["chinese_simplified"],"扫":["chinese_simplified"],"喝":["chinese_simplified","chinese_traditional"],"袋":["chinese_simplified","chinese_traditional"],"炭":["chinese_simplified","chinese_traditional"],"污":["chinese_simplified","chinese_traditional"],"幕":["chinese_simplified","chinese_traditional"],"诸":["chinese_simplified"],"弧":["chinese_simplified","chinese_traditional"],"励":["chinese_simplified"],"梅":["chinese_simplified","chinese_traditional"],"奶":["chinese_simplified","chinese_traditional"],"洁":["chinese_simplified"],"灾":["chinese_simplified"],"舟":["chinese_simplified","chinese_traditional"],"鉴":["chinese_simplified"],"苯":["chinese_simplified","chinese_traditional"],"讼":["chinese_simplified"],"抱":["chinese_simplified","chinese_traditional"],"毁":["chinese_simplified"],"懂":["chinese_simplified","chinese_traditional"],"寒":["chinese_simplified","chinese_traditional"],"智":["chinese_simplified","chinese_traditional"],"埔":["chinese_simplified","chinese_traditional"],"寄":["chinese_simplified","chinese_traditional"],"届":["chinese_simplified"],"跃":["chinese_simplified"],"渡":["chinese_simplified","chinese_traditional"],"挑":["chinese_simplified","chinese_traditional"],"丹":["chinese_simplified","chinese_traditional"],"艰":["chinese_simplified"],"贝":["chinese_simplified"],"碰":["chinese_simplified","chinese_traditional"],"拔":["chinese_simplified","chinese_traditional"],"爹":["chinese_simplified","chinese_traditional"],"戴":["chinese_simplified","chinese_traditional"],"码":["chinese_simplified"],"梦":["chinese_simplified"],"芽":["chinese_simplified","chinese_traditional"],"熔":["chinese_simplified","chinese_traditional"],"赤":["chinese_simplified","chinese_traditional"],"渔":["chinese_simplified"],"哭":["chinese_simplified","chinese_traditional"],"敬":["chinese_simplified","chinese_traditional"],"颗":["chinese_simplified"],"奔":["chinese_simplified","chinese_traditional"],"铅":["chinese_simplified"],"仲":["chinese_simplified","chinese_traditional"],"虎":["chinese_simplified","chinese_traditional"],"稀":["chinese_simplified","chinese_traditional"],"妹":["chinese_simplified","chinese_traditional"],"乏":["chinese_simplified","chinese_traditional"],"珍":["chinese_simplified","chinese_traditional"],"申":["chinese_simplified","chinese_traditional"],"桌":["chinese_simplified","chinese_traditional"],"遵":["chinese_simplified","chinese_traditional"],"允":["chinese_simplified","chinese_traditional"],"隆":["chinese_simplified","chinese_traditional"],"螺":["chinese_simplified","chinese_traditional"],"仓":["chinese_simplified"],"魏":["chinese_simplified","chinese_traditional"],"锐":["chinese_simplified"],"晓":["chinese_simplified"],"氮":["chinese_simplified","chinese_traditional"],"兼":["chinese_simplified","chinese_traditional"],"隐":["chinese_simplified"],"碍":["chinese_simplified"],"赫":["chinese_simplified","chinese_traditional"],"拨":["chinese_simplified"],"忠":["chinese_simplified","chinese_traditional"],"肃":["chinese_simplified"],"缸":["chinese_simplified","chinese_traditional"],"牵":["chinese_simplified"],"抢":["chinese_simplified"],"博":["chinese_simplified","chinese_traditional"],"巧":["chinese_simplified","chinese_traditional"],"壳":["chinese_simplified"],"兄":["chinese_simplified","chinese_traditional"],"杜":["chinese_simplified","chinese_traditional"],"讯":["chinese_simplified"],"诚":["chinese_simplified"],"碧":["chinese_simplified","chinese_traditional"],"祥":["chinese_simplified","chinese_traditional"],"柯":["chinese_simplified","chinese_traditional"],"页":["chinese_simplified"],"巡":["chinese_simplified","chinese_traditional"],"矩":["chinese_simplified","chinese_traditional"],"悲":["chinese_simplified","chinese_traditional"],"灌":["chinese_simplified","chinese_traditional"],"龄":["chinese_simplified"],"伦":["chinese_simplified"],"票":["chinese_simplified","chinese_traditional"],"寻":["chinese_simplified"],"桂":["chinese_simplified","chinese_traditional"],"铺":["chinese_simplified"],"圣":["chinese_simplified"],"恐":["chinese_simplified","chinese_traditional"],"恰":["chinese_simplified","chinese_traditional"],"郑":["chinese_simplified"],"趣":["chinese_simplified","chinese_traditional"],"抬":["chinese_simplified","chinese_traditional"],"荒":["chinese_simplified","chinese_traditional"],"腾":["chinese_simplified"],"贴":["chinese_simplified"],"柔":["chinese_simplified","chinese_traditional"],"滴":["chinese_simplified","chinese_traditional"],"猛":["chinese_simplified","chinese_traditional"],"阔":["chinese_simplified"],"辆":["chinese_simplified"],"妻":["chinese_simplified","chinese_traditional"],"填":["chinese_simplified","chinese_traditional"],"撤":["chinese_simplified","chinese_traditional"],"储":["chinese_simplified"],"签":["chinese_simplified"],"闹":["chinese_simplified"],"扰":["chinese_simplified"],"紫":["chinese_simplified","chinese_traditional"],"砂":["chinese_simplified","chinese_traditional"],"递":["chinese_simplified"],"戏":["chinese_simplified"],"吊":["chinese_simplified","chinese_traditional"],"陶":["chinese_simplified","chinese_traditional"],"伐":["chinese_simplified","chinese_traditional"],"喂":["chinese_simplified"],"疗":["chinese_simplified"],"瓶":["chinese_simplified","chinese_traditional"],"婆":["chinese_simplified","chinese_traditional"],"抚":["chinese_simplified"],"臂":["chinese_simplified","chinese_traditional"],"摸":["chinese_simplified","chinese_traditional"],"忍":["chinese_simplified","chinese_traditional"],"虾":["chinese_simplified"],"蜡":["chinese_simplified"],"邻":["chinese_simplified"],"胸":["chinese_simplified","chinese_traditional"],"巩":["chinese_simplified"],"挤":["chinese_simplified"],"偶":["chinese_simplified","chinese_traditional"],"弃":["chinese_simplified"],"槽":["chinese_simplified","chinese_traditional"],"劲":["chinese_simplified"],"乳":["chinese_simplified","chinese_traditional"],"邓":["chinese_simplified"],"吉":["chinese_simplified","chinese_traditional"],"仁":["chinese_simplified","chinese_traditional"],"烂":["chinese_simplified"],"砖":["chinese_simplified"],"租":["chinese_simplified","chinese_traditional"],"乌":["chinese_simplified"],"舰":["chinese_simplified"],"伴":["chinese_simplified","chinese_traditional"],"瓜":["chinese_simplified","chinese_traditional"],"浅":["chinese_simplified"],"丙":["chinese_simplified","chinese_traditional"],"暂":["chinese_simplified"],"燥":["chinese_simplified","chinese_traditional"],"橡":["chinese_simplified","chinese_traditional"],"柳":["chinese_simplified","chinese_traditional"],"迷":["chinese_simplified","chinese_traditional"],"暖":["chinese_simplified","chinese_traditional"],"牌":["chinese_simplified","chinese_traditional"],"秧":["chinese_simplified","chinese_traditional"],"胆":["chinese_simplified"],"详":["chinese_simplified"],"簧":["chinese_simplified","chinese_traditional"],"踏":["chinese_simplified","chinese_traditional"],"瓷":["chinese_simplified","chinese_traditional"],"谱":["chinese_simplified"],"呆":["chinese_simplified","chinese_traditional"],"宾":["chinese_simplified"],"糊":["chinese_simplified","chinese_traditional"],"洛":["chinese_simplified","chinese_traditional"],"辉":["chinese_simplified"],"愤":["chinese_simplified"],"竞":["chinese_simplified"],"隙":["chinese_simplified","chinese_traditional"],"怒":["chinese_simplified","chinese_traditional"],"粘":["chinese_simplified","chinese_traditional"],"乃":["chinese_simplified","chinese_traditional"],"绪":["chinese_simplified"],"肩":["chinese_simplified","chinese_traditional"],"籍":["chinese_simplified","chinese_traditional"],"敏":["chinese_simplified","chinese_traditional"],"涂":["chinese_simplified"],"熙":["chinese_simplified","chinese_traditional"],"皆":["chinese_simplified","chinese_traditional"],"侦":["chinese_simplified"],"悬":["chinese_simplified"],"掘":["chinese_simplified","chinese_traditional"],"享":["chinese_simplified","chinese_traditional"],"纠":["chinese_simplified"],"醒":["chinese_simplified","chinese_traditional"],"狂":["chinese_simplified","chinese_traditional"],"锁":["chinese_simplified"],"淀":["chinese_simplified","chinese_traditional"],"恨":["chinese_simplified","chinese_traditional"],"牲":["chinese_simplified","chinese_traditional"],"霸":["chinese_simplified","chinese_traditional"],"爬":["chinese_simplified","chinese_traditional"],"赏":["chinese_simplified"],"逆":["chinese_simplified","chinese_traditional"],"玩":["chinese_simplified","chinese_traditional"],"陵":["chinese_simplified","chinese_traditional"],"祝":["chinese_simplified","chinese_traditional"],"秒":["chinese_simplified","chinese_traditional"],"浙":["chinese_simplified","chinese_traditional"],"貌":["chinese_simplified","chinese_traditional"],"役":["chinese_simplified","chinese_traditional"],"彼":["chinese_simplified","chinese_traditional"],"悉":["chinese_simplified","chinese_traditional"],"鸭":["chinese_simplified"],"趋":["chinese_simplified"],"凤":["chinese_simplified"],"晨":["chinese_simplified","chinese_traditional"],"畜":["chinese_simplified","chinese_traditional"],"辈":["chinese_simplified"],"秩":["chinese_simplified","chinese_traditional"],"卵":["chinese_simplified","chinese_traditional"],"署":["chinese_simplified","chinese_traditional"],"梯":["chinese_simplified","chinese_traditional"],"炎":["chinese_simplified","chinese_traditional"],"滩":["chinese_simplified"],"棋":["chinese_simplified","chinese_traditional"],"驱":["chinese_simplified"],"筛":["chinese_simplified"],"峡":["chinese_simplified"],"冒":["chinese_simplified","chinese_traditional"],"啥":["chinese_simplified","chinese_traditional"],"寿":["chinese_simplified"],"译":["chinese_simplified"],"浸":["chinese_simplified","chinese_traditional"],"泉":["chinese_simplified","chinese_traditional"],"帽":["chinese_simplified","chinese_traditional"],"迟":["chinese_simplified"],"硅":["chinese_simplified"],"疆":["chinese_simplified","chinese_traditional"],"贷":["chinese_simplified"],"漏":["chinese_simplified","chinese_traditional"],"稿":["chinese_simplified","chinese_traditional"],"冠":["chinese_simplified","chinese_traditional"],"嫩":["chinese_simplified","chinese_traditional"],"胁":["chinese_simplified"],"芯":["chinese_simplified","chinese_traditional"],"牢":["chinese_simplified","chinese_traditional"],"叛":["chinese_simplified","chinese_traditional"],"蚀":["chinese_simplified"],"奥":["chinese_simplified"],"鸣":["chinese_simplified"],"岭":["chinese_simplified"],"羊":["chinese_simplified","chinese_traditional"],"凭":["chinese_simplified"],"串":["chinese_simplified","chinese_traditional"],"塘":["chinese_simplified","chinese_traditional"],"绘":["chinese_simplified"],"酵":["chinese_simplified","chinese_traditional"],"融":["chinese_simplified","chinese_traditional"],"盆":["chinese_simplified","chinese_traditional"],"锡":["chinese_simplified"],"庙":["chinese_simplified"],"筹":["chinese_simplified"],"冻":["chinese_simplified"],"辅":["chinese_simplified"],"摄":["chinese_simplified"],"袭":["chinese_simplified"],"筋":["chinese_simplified","chinese_traditional"],"拒":["chinese_simplified","chinese_traditional"],"僚":["chinese_simplified","chinese_traditional"],"旱":["chinese_simplified","chinese_traditional"],"钾":["chinese_simplified"],"鸟":["chinese_simplified"],"漆":["chinese_simplified","chinese_traditional"],"沈":["chinese_simplified","chinese_traditional"],"眉":["chinese_simplified","chinese_traditional"],"疏":["chinese_simplified","chinese_traditional"],"添":["chinese_simplified","chinese_traditional"],"棒":["chinese_simplified","chinese_traditional"],"穗":["chinese_simplified","chinese_traditional"],"硝":["chinese_simplified","chinese_traditional"],"韩":["chinese_simplified"],"逼":["chinese_simplified","chinese_traditional"],"扭":["chinese_simplified","chinese_traditional"],"侨":["chinese_simplified"],"凉":["chinese_simplified"],"挺":["chinese_simplified","chinese_traditional"],"碗":["chinese_simplified","chinese_traditional"],"栽":["chinese_simplified","chinese_traditional"],"炒":["chinese_simplified","chinese_traditional"],"杯":["chinese_simplified","chinese_traditional"],"患":["chinese_simplified","chinese_traditional"],"馏":["chinese_simplified"],"劝":["chinese_simplified"],"豪":["chinese_simplified","chinese_traditional"],"辽":["chinese_simplified"],"勃":["chinese_simplified","chinese_traditional"],"鸿":["chinese_simplified"],"旦":["chinese_simplified","chinese_traditional"],"吏":["chinese_simplified","chinese_traditional"],"拜":["chinese_simplified","chinese_traditional"],"狗":["chinese_simplified","chinese_traditional"],"埋":["chinese_simplified","chinese_traditional"],"辊":["chinese_simplified"],"掩":["chinese_simplified","chinese_traditional"],"饮":["chinese_simplified"],"搬":["chinese_simplified","chinese_traditional"],"骂":["chinese_simplified"],"辞":["chinese_simplified"],"勾":["chinese_simplified","chinese_traditional"],"扣":["chinese_simplified","chinese_traditional"],"估":["chinese_simplified","chinese_traditional"],"蒋":["chinese_simplified"],"绒":["chinese_simplified"],"雾":["chinese_simplified"],"丈":["chinese_simplified","chinese_traditional"],"朵":["chinese_simplified","chinese_traditional"],"姆":["chinese_simplified","chinese_traditional"],"拟":["chinese_simplified"],"宇":["chinese_simplified","chinese_traditional"],"辑":["chinese_simplified"],"陕":["chinese_simplified"],"雕":["chinese_simplified","chinese_traditional"],"偿":["chinese_simplified"],"蓄":["chinese_simplified","chinese_traditional"],"崇":["chinese_simplified","chinese_traditional"],"剪":["chinese_simplified","chinese_traditional"],"倡":["chinese_simplified","chinese_traditional"],"厅":["chinese_simplified"],"咬":["chinese_simplified","chinese_traditional"],"驶":["chinese_simplified"],"薯":["chinese_simplified","chinese_traditional"],"刷":["chinese_simplified","chinese_traditional"],"斥":["chinese_simplified","chinese_traditional"],"番":["chinese_simplified","chinese_traditional"],"赋":["chinese_simplified"],"奉":["chinese_simplified","chinese_traditional"],"佛":["chinese_simplified","chinese_traditional"],"浇":["chinese_simplified"],"漫":["chinese_simplified","chinese_traditional"],"曼":["chinese_simplified","chinese_traditional"],"扇":["chinese_simplified","chinese_traditional"],"钙":["chinese_simplified"],"桃":["chinese_simplified","chinese_traditional"],"扶":["chinese_simplified","chinese_traditional"],"仔":["chinese_simplified","chinese_traditional"],"返":["chinese_simplified","chinese_traditional"],"俗":["chinese_simplified","chinese_traditional"],"亏":["chinese_simplified"],"腔":["chinese_simplified","chinese_traditional"],"鞋":["chinese_simplified","chinese_traditional"],"棱":["chinese_simplified","chinese_traditional"],"覆":["chinese_simplified","chinese_traditional"],"框":["chinese_simplified","chinese_traditional"],"悄":["chinese_simplified","chinese_traditional"],"叔":["chinese_simplified","chinese_traditional"],"撞":["chinese_simplified","chinese_traditional"],"骗":["chinese_simplified"],"勘":["chinese_simplified","chinese_traditional"],"旺":["chinese_simplified","chinese_traditional"],"沸":["chinese_simplified","chinese_traditional"],"孤":["chinese_simplified","chinese_traditional"],"吐":["chinese_simplified","chinese_traditional"],"孟":["chinese_simplified","chinese_traditional"],"渠":["chinese_simplified","chinese_traditional"],"屈":["chinese_simplified","chinese_traditional"],"疾":["chinese_simplified","chinese_traditional"],"妙":["chinese_simplified","chinese_traditional"],"惜":["chinese_simplified","chinese_traditional"],"仰":["chinese_simplified","chinese_traditional"],"狠":["chinese_simplified","chinese_traditional"],"胀":["chinese_simplified"],"谐":["chinese_simplified"],"抛":["chinese_simplified"],"霉":["chinese_simplified"],"桑":["chinese_simplified","chinese_traditional"],"岗":["chinese_simplified"],"嘛":["chinese_simplified","chinese_traditional"],"衰":["chinese_simplified","chinese_traditional"],"盗":["chinese_simplified"],"渗":["chinese_simplified"],"脏":["chinese_simplified"],"赖":["chinese_simplified"],"涌":["chinese_simplified"],"甜":["chinese_simplified","chinese_traditional"],"曹":["chinese_simplified","chinese_traditional"],"阅":["chinese_simplified"],"肌":["chinese_simplified","chinese_traditional"],"哩":["chinese_simplified","chinese_traditional"],"厉":["chinese_simplified"],"烃":["chinese_simplified"],"纬":["chinese_simplified"],"毅":["chinese_simplified","chinese_traditional"],"昨":["chinese_simplified","chinese_traditional"],"伪":["chinese_simplified"],"症":["chinese_simplified","chinese_traditional"],"煮":["chinese_simplified","chinese_traditional"],"叹":["chinese_simplified"],"钉":["chinese_simplified"],"搭":["chinese_simplified","chinese_traditional"],"茎":["chinese_simplified"],"笼":["chinese_simplified"],"酷":["chinese_simplified","chinese_traditional"],"偷":["chinese_simplified","chinese_traditional"],"弓":["chinese_simplified","chinese_traditional"],"锥":["chinese_simplified"],"恒":["chinese_simplified"],"杰":["chinese_simplified"],"坑":["chinese_simplified","chinese_traditional"],"鼻":["chinese_simplified","chinese_traditional"],"翼":["chinese_simplified","chinese_traditional"],"纶":["chinese_simplified"],"叙":["chinese_simplified"],"狱":["chinese_simplified"],"逮":["chinese_simplified","chinese_traditional"],"罐":["chinese_simplified","chinese_traditional"],"络":["chinese_simplified"],"棚":["chinese_simplified","chinese_traditional"],"抑":["chinese_simplified","chinese_traditional"],"膨":["chinese_simplified","chinese_traditional"],"蔬":["chinese_simplified","chinese_traditional"],"寺":["chinese_simplified","chinese_traditional"],"骤":["chinese_simplified"],"穆":["chinese_simplified","chinese_traditional"],"冶":["chinese_simplified","chinese_traditional"],"枯":["chinese_simplified","chinese_traditional"],"册":["chinese_simplified"],"尸":["chinese_simplified"],"凸":["chinese_simplified","chinese_traditional"],"绅":["chinese_simplified"],"坯":["chinese_simplified","chinese_traditional"],"牺":["chinese_simplified"],"焰":["chinese_simplified","chinese_traditional"],"轰":["chinese_simplified"],"欣":["chinese_simplified","chinese_traditional"],"晋":["chinese_simplified"],"瘦":["chinese_simplified","chinese_traditional"],"御":["chinese_simplified"],"锭":["chinese_simplified"],"锦":["chinese_simplified"],"丧":["chinese_simplified"],"旬":["chinese_simplified","chinese_traditional"],"锻":["chinese_simplified"],"垄":["chinese_simplified"],"搜":["chinese_simplified","chinese_traditional"],"扑":["chinese_simplified"],"邀":["chinese_simplified","chinese_traditional"],"亭":["chinese_simplified","chinese_traditional"],"酯":["chinese_simplified","chinese_traditional"],"迈":["chinese_simplified"],"舒":["chinese_simplified","chinese_traditional"],"脆":["chinese_simplified","chinese_traditional"],"酶":["chinese_simplified","chinese_traditional"],"闲":["chinese_simplified"],"忧":["chinese_simplified"],"酚":["chinese_simplified","chinese_traditional"],"顽":["chinese_simplified"],"羽":["chinese_simplified","chinese_traditional"],"涨":["chinese_simplified"],"卸":["chinese_simplified","chinese_traditional"],"仗":["chinese_simplified","chinese_traditional"],"陪":["chinese_simplified","chinese_traditional"],"辟":["chinese_simplified"],"惩":["chinese_simplified"],"杭":["chinese_simplified","chinese_traditional"],"姚":["chinese_simplified","chinese_traditional"],"肚":["chinese_simplified","chinese_traditional"],"捉":["chinese_simplified","chinese_traditional"],"飘":["chinese_simplified"],"漂":["chinese_simplified","chinese_traditional"],"昆":["chinese_simplified","chinese_traditional"],"欺":["chinese_simplified","chinese_traditional"],"吾":["chinese_simplified","chinese_traditional"],"郎":["chinese_simplified","chinese_traditional"],"烷":["chinese_simplified","chinese_traditional"],"汁":["chinese_simplified","chinese_traditional"],"呵":["chinese_simplified","chinese_traditional"],"饰":["chinese_simplified"],"萧":["chinese_simplified"],"雅":["chinese_simplified","chinese_traditional"],"邮":["chinese_simplified"],"迁":["chinese_simplified"],"燕":["chinese_simplified","chinese_traditional"],"撒":["chinese_simplified","chinese_traditional"],"姻":["chinese_simplified","chinese_traditional"],"赴":["chinese_simplified","chinese_traditional"],"宴":["chinese_simplified","chinese_traditional"],"烦":["chinese_simplified"],"债":["chinese_simplified"],"帐":["chinese_simplified"],"斑":["chinese_simplified","chinese_traditional"],"铃":["chinese_simplified"],"旨":["chinese_simplified","chinese_traditional"],"醇":["chinese_simplified","chinese_traditional"],"董":["chinese_simplified","chinese_traditional"],"饼":["chinese_simplified"],"雏":["chinese_simplified"],"姿":["chinese_simplified","chinese_traditional"],"拌":["chinese_simplified","chinese_traditional"],"傅":["chinese_simplified","chinese_traditional"],"腹":["chinese_simplified","chinese_traditional"],"妥":["chinese_simplified","chinese_traditional"],"揉":["chinese_simplified","chinese_traditional"],"贤":["chinese_simplified"],"拆":["chinese_simplified","chinese_traditional"],"歪":["chinese_simplified","chinese_traditional"],"葡":["chinese_simplified","chinese_traditional"],"胺":["chinese_simplified","chinese_traditional"],"丢":["chinese_simplified"],"浩":["chinese_simplified","chinese_traditional"],"徽":["chinese_simplified","chinese_traditional"],"昂":["chinese_simplified","chinese_traditional"],"垫":["chinese_simplified"],"挡":["chinese_simplified"],"览":["chinese_simplified"],"贪":["chinese_simplified"],"慰":["chinese_simplified","chinese_traditional"],"缴":["chinese_simplified"],"汪":["chinese_simplified","chinese_traditional"],"慌":["chinese_simplified","chinese_traditional"],"冯":["chinese_simplified"],"诺":["chinese_simplified"],"姜":["chinese_simplified","chinese_traditional"],"谊":["chinese_simplified"],"凶":["chinese_simplified"],"劣":["chinese_simplified","chinese_traditional"],"诬":["chinese_simplified"],"耀":["chinese_simplified","chinese_traditional"],"昏":["chinese_simplified","chinese_traditional"],"躺":["chinese_simplified","chinese_traditional"],"盈":["chinese_simplified","chinese_traditional"],"骑":["chinese_simplified"],"乔":["chinese_simplified"],"溪":["chinese_simplified","chinese_traditional"],"丛":["chinese_simplified"],"卢":["chinese_simplified"],"抹":["chinese_simplified","chinese_traditional"],"闷":["chinese_simplified"],"咨":["chinese_simplified"],"刮":["chinese_simplified","chinese_traditional"],"驾":["chinese_simplified"],"缆":["chinese_simplified"],"悟":["chinese_simplified","chinese_traditional"],"摘":["chinese_simplified","chinese_traditional"],"铒":["chinese_simplified"],"掷":["chinese_simplified"],"颇":["chinese_simplified"],"幻":["chinese_simplified","chinese_traditional"],"柄":["chinese_simplified","chinese_traditional"],"惠":["chinese_simplified","chinese_traditional"],"惨":["chinese_simplified"],"佳":["chinese_simplified","chinese_traditional"],"仇":["chinese_simplified","chinese_traditional"],"腊":["chinese_simplified"],"窝":["chinese_simplified"],"涤":["chinese_simplified"],"剑":["chinese_simplified"],"瞧":["chinese_simplified","chinese_traditional"],"堡":["chinese_simplified","chinese_traditional"],"泼":["chinese_simplified"],"葱":["chinese_simplified"],"罩":["chinese_simplified","chinese_traditional"],"霍":["chinese_simplified","chinese_traditional"],"捞":["chinese_simplified"],"胎":["chinese_simplified","chinese_traditional"],"苍":["chinese_simplified"],"滨":["chinese_simplified"],"俩":["chinese_simplified"],"捅":["chinese_simplified","chinese_traditional"],"湘":["chinese_simplified","chinese_traditional"],"砍":["chinese_simplified","chinese_traditional"],"霞":["chinese_simplified","chinese_traditional"],"邵":["chinese_simplified","chinese_traditional"],"萄":["chinese_simplified","chinese_traditional"],"疯":["chinese_simplified"],"淮":["chinese_simplified","chinese_traditional"],"遂":["chinese_simplified","chinese_traditional"],"熊":["chinese_simplified","chinese_traditional"],"粪":["chinese_simplified"],"烘":["chinese_simplified","chinese_traditional"],"宿":["chinese_simplified","chinese_traditional"],"档":["chinese_simplified"],"戈":["chinese_simplified","chinese_traditional"],"驳":["chinese_simplified"],"嫂":["chinese_simplified","chinese_traditional"],"裕":["chinese_simplified","chinese_traditional"],"徙":["chinese_simplified","chinese_traditional"],"箭":["chinese_simplified","chinese_traditional"],"捐":["chinese_simplified","chinese_traditional"],"肠":["chinese_simplified"],"撑":["chinese_simplified"],"晒":["chinese_simplified"],"辨":["chinese_simplified","chinese_traditional"],"殿":["chinese_simplified","chinese_traditional"],"莲":["chinese_simplified"],"摊":["chinese_simplified"],"搅":["chinese_simplified"],"酱":["chinese_simplified"],"屏":["chinese_simplified","chinese_traditional"],"疫":["chinese_simplified","chinese_traditional"],"哀":["chinese_simplified","chinese_traditional"],"蔡":["chinese_simplified","chinese_traditional"],"堵":["chinese_simplified","chinese_traditional"],"沫":["chinese_simplified","chinese_traditional"],"皱":["chinese_simplified"],"畅":["chinese_simplified"],"叠":["chinese_simplified"],"阁":["chinese_simplified"],"莱":["chinese_simplified"],"敲":["chinese_simplified","chinese_traditional"],"辖":["chinese_simplified"],"钩":["chinese_simplified"],"痕":["chinese_simplified","chinese_traditional"],"坝":["chinese_simplified"],"巷":["chinese_simplified","chinese_traditional"],"饿":["chinese_simplified"],"祸":["chinese_simplified"],"丘":["chinese_simplified","chinese_traditional"],"玄":["chinese_simplified","chinese_traditional"],"溜":["chinese_simplified","chinese_traditional"],"曰":["chinese_simplified","chinese_traditional"],"逻":["chinese_simplified"],"彭":["chinese_simplified","chinese_traditional"],"尝":["chinese_simplified"],"卿":["chinese_simplified","chinese_traditional"],"妨":["chinese_simplified","chinese_traditional"],"艇":["chinese_simplified","chinese_traditional"],"吞":["chinese_simplified","chinese_traditional"],"韦":["chinese_simplified"],"怨":["chinese_simplified","chinese_traditional"],"矮":["chinese_simplified","chinese_traditional"],"歇":["chinese_simplified","chinese_traditional"],"這":["chinese_traditional"],"為":["chinese_traditional"],"個":["chinese_traditional"],"國":["chinese_traditional"],"時":["chinese_traditional"],"來":["chinese_traditional"],"們":["chinese_traditional"],"於":["chinese_traditional"],"對":["chinese_traditional"],"會":["chinese_traditional"],"發":["chinese_traditional"],"動":["chinese_traditional"],"過":["chinese_traditional"],"說":["chinese_traditional"],"產":["chinese_traditional"],"種":["chinese_traditional"],"後":["chinese_traditional"],"學":["chinese_traditional"],"經":["chinese_traditional"],"進":["chinese_traditional"],"著":["chinese_traditional"],"電":["chinese_traditional"],"裡":["chinese_traditional"],"現":["chinese_traditional"],"實":["chinese_traditional"],"兩":["chinese_traditional"],"體":["chinese_traditional"],"機":["chinese_traditional"],"當":["chinese_traditional"],"點":["chinese_traditional"],"從":["chinese_traditional"],"業":["chinese_traditional"],"應":["chinese_traditional"],"開":["chinese_traditional"],"還":["chinese_traditional"],"義":["chinese_traditional"],"間":["chinese_traditional"],"樣":["chinese_traditional"],"與":["chinese_traditional"],"關":["chinese_traditional"],"線":["chinese_traditional"],"內":["chinese_traditional"],"數":["chinese_traditional"],"麼":["chinese_traditional"],"質":["chinese_traditional"],"氣":["chinese_traditional"],"變":["chinese_traditional"],"條":["chinese_traditional"],"沒":["chinese_traditional"],"結":["chinese_traditional"],"問":["chinese_traditional"],"無":["chinese_traditional"],"軍":["chinese_traditional"],"並":["chinese_traditional"],"題":["chinese_traditional"],"黨":["chinese_traditional"],"員":["chinese_traditional"],"總":["chinese_traditional"],"設":["chinese_traditional"],"長":["chinese_traditional"],"頭":["chinese_traditional"],"資":["chinese_traditional"],"邊":["chinese_traditional"],"級":["chinese_traditional"],"圖":["chinese_traditional"],"統":["chinese_traditional"],"較":["chinese_traditional"],"將":["chinese_traditional"],"組":["chinese_traditional"],"見":["chinese_traditional"],"計":["chinese_traditional"],"別":["chinese_traditional"],"論":["chinese_traditional"],"運":["chinese_traditional"],"農":["chinese_traditional"],"幾":["chinese_traditional"],"區":["chinese_traditional"],"強":["chinese_traditional"],"決":["chinese_traditional"],"幹":["chinese_traditional"],"戰":["chinese_traditional"],"則":["chinese_traditional"],"據":["chinese_traditional"],"處":["chinese_traditional"],"隊":["chinese_traditional"],"給":["chinese_traditional"],"門":["chinese_traditional"],"規":["chinese_traditional"],"熱":["chinese_traditional"],"領":["chinese_traditional"],"東":["chinese_traditional"],"導":["chinese_traditional"],"壓":["chinese_traditional"],"爭":["chinese_traditional"],"濟":["chinese_traditional"],"階":["chinese_traditional"],"術":["chinese_traditional"],"極":["chinese_traditional"],"聯":["chinese_traditional"],"認":["chinese_traditional"],"權":["chinese_traditional"],"證":["chinese_traditional"],"採":["chinese_traditional"],"轉":["chinese_traditional"],"單":["chinese_traditional"],"風":["chinese_traditional"],"帶":["chinese_traditional"],"場":["chinese_traditional"],"車":["chinese_traditional"],"務":["chinese_traditional"],"萬":["chinese_traditional"],"達":["chinese_traditional"],"積":["chinese_traditional"],"議":["chinese_traditional"],"聲":["chinese_traditional"],"報":["chinese_traditional"],"鬥":["chinese_traditional"],"類":["chinese_traditional"],"離":["chinese_traditional"],"華":["chinese_traditional"],"確":["chinese_traditional"],"張":["chinese_traditional"],"馬":["chinese_traditional"],"節":["chinese_traditional"],"話":["chinese_traditional"],"況":["chinese_traditional"],"溫":["chinese_traditional"],"傳":["chinese_traditional"],"許":["chinese_traditional"],"廣":["chinese_traditional"],"記":["chinese_traditional"],"觀":["chinese_traditional"],"織":["chinese_traditional"],"裝":["chinese_traditional"],"眾":["chinese_traditional"],"書":["chinese_traditional"],"兒":["chinese_traditional"],"須":["chinese_traditional"],"際":["chinese_traditional"],"驗":["chinese_traditional"],"連":["chinese_traditional"],"斷":["chinese_traditional"],"難":["chinese_traditional"],"礦":["chinese_traditional"],"週":["chinese_traditional"],"備":["chinese_traditional"],"辦":["chinese_traditional"],"習":["chinese_traditional"],"響":["chinese_traditional"],"約":["chinese_traditional"],"勞":["chinese_traditional"],"團":["chinese_traditional"],"歷":["chinese_traditional"],"構":["chinese_traditional"],"稱":["chinese_traditional"],"準":["chinese_traditional"],"號":["chinese_traditional"],"維":["chinese_traditional"],"劃":["chinese_traditional"],"選":["chinese_traditional"],"標":["chinese_traditional"],"寫":["chinese_traditional"],"親":["chinese_traditional"],"養":["chinese_traditional"],"層":["chinese_traditional"],"卻":["chinese_traditional"],"專":["chinese_traditional"],"狀":["chinese_traditional"],"廠":["chinese_traditional"],"識":["chinese_traditional"],"適":["chinese_traditional"],"屬":["chinese_traditional"],"圓":["chinese_traditional"],"調":["chinese_traditional"],"滿":["chinese_traditional"],"縣":["chinese_traditional"],"參":["chinese_traditional"],"紅":["chinese_traditional"],"細":["chinese_traditional"],"聽":["chinese_traditional"],"該":["chinese_traditional"],"鐵":["chinese_traditional"],"價":["chinese_traditional"],"嚴":["chinese_traditional"],"隨":["chinese_traditional"],"蘇":["chinese_traditional"],"爾":["chinese_traditional"],"講":["chinese_traditional"],"黃":["chinese_traditional"],"顯":["chinese_traditional"],"談":["chinese_traditional"],"藝":["chinese_traditional"],"營":["chinese_traditional"],"項":["chinese_traditional"],"舉":["chinese_traditional"],"勢":["chinese_traditional"],"幫":["chinese_traditional"],"輪":["chinese_traditional"],"亞":["chinese_traditional"],"師":["chinese_traditional"],"圍":["chinese_traditional"],"遠":["chinese_traditional"],"態":["chinese_traditional"],"減":["chinese_traditional"],"樹":["chinese_traditional"],"葉":["chinese_traditional"],"魚":["chinese_traditional"],"視":["chinese_traditional"],"僅":["chinese_traditional"],"費":["chinese_traditional"],"緊":["chinese_traditional"],"愛":["chinese_traditional"],"續":["chinese_traditional"],"輕":["chinese_traditional"],"試":["chinese_traditional"],"護":["chinese_traditional"],"練":["chinese_traditional"],"負":["chinese_traditional"],"擊":["chinese_traditional"],"繼":["chinese_traditional"],"興":["chinese_traditional"],"餘":["chinese_traditional"],"堅":["chinese_traditional"],"輸":["chinese_traditional"],"夠":["chinese_traditional"],"筆":["chinese_traditional"],"佔":["chinese_traditional"],"財":["chinese_traditional"],"職":["chinese_traditional"],"覺":["chinese_traditional"],"漢":["chinese_traditional"],"畫":["chinese_traditional"],"雖":["chinese_traditional"],"雜":["chinese_traditional"],"飛":["chinese_traditional"],"檢":["chinese_traditional"],"昇":["chinese_traditional"],"陽":["chinese_traditional"],"創":["chinese_traditional"],"壞":["chinese_traditional"],"徑":["chinese_traditional"],"換":["chinese_traditional"],"鋼":["chinese_traditional"],"責":["chinese_traditional"],"簡":["chinese_traditional"],"錢":["chinese_traditional"],"盡":["chinese_traditional"],"衝":["chinese_traditional"],"獨":["chinese_traditional"],"環":["chinese_traditional"],"雙":["chinese_traditional"],"請":["chinese_traditional"],"讓":["chinese_traditional"],"軸":["chinese_traditional"],"紀":["chinese_traditional"],"優":["chinese_traditional"],"頂":["chinese_traditional"],"礎":["chinese_traditional"],"載":["chinese_traditional"],"敵":["chinese_traditional"],"勝":["chinese_traditional"],"絕":["chinese_traditional"],"塊":["chinese_traditional"],"劑":["chinese_traditional"],"測":["chinese_traditional"],"絲":["chinese_traditional"],"協":["chinese_traditional"],"訴":["chinese_traditional"],"陳":["chinese_traditional"],"羅":["chinese_traditional"],"鹽":["chinese_traditional"],"錯":["chinese_traditional"],"頻":["chinese_traditional"],"終":["chinese_traditional"],"雲":["chinese_traditional"],"衛":["chinese_traditional"],"燒":["chinese_traditional"],"遊":["chinese_traditional"],"舊":["chinese_traditional"],"貨":["chinese_traditional"],"損":["chinese_traditional"],"預":["chinese_traditional"],"穩":["chinese_traditional"],"媽":["chinese_traditional"],"擴":["chinese_traditional"],"銀":["chinese_traditional"],"語":["chinese_traditional"],"揮":["chinese_traditional"],"紙":["chinese_traditional"],"醫":["chinese_traditional"],"嗎":["chinese_traditional"],"針":["chinese_traditional"],"劉":["chinese_traditional"],"誤":["chinese_traditional"],"訓":["chinese_traditional"],"願":["chinese_traditional"],"審":["chinese_traditional"],"獲":["chinese_traditional"],"鮮":["chinese_traditional"],"糧":["chinese_traditional"],"脫":["chinese_traditional"],"龍":["chinese_traditional"],"漸":["chinese_traditional"],"歡":["chinese_traditional"],"剛":["chinese_traditional"],"謂":["chinese_traditional"],"討":["chinese_traditional"],"亂":["chinese_traditional"],"殺":["chinese_traditional"],"藥":["chinese_traditional"],"寧":["chinese_traditional"],"魯":["chinese_traditional"],"貴":["chinese_traditional"],"鐘":["chinese_traditional"],"讀":["chinese_traditional"],"豐":["chinese_traditional"],"蘭":["chinese_traditional"],"擔":["chinese_traditional"],"執":["chinese_traditional"],"樂":["chinese_traditional"],"誰":["chinese_traditional"],"順":["chinese_traditional"],"煙":["chinese_traditional"],"縮":["chinese_traditional"],"徵":["chinese_traditional"],"臉":["chinese_traditional"],"腳":["chinese_traditional"],"異":["chinese_traditional"],"買":["chinese_traditional"],"靜":["chinese_traditional"],"補":["chinese_traditional"],"評":["chinese_traditional"],"踐":["chinese_traditional"],"寬":["chinese_traditional"],"揚":["chinese_traditional"],"傷":["chinese_traditional"],"氫":["chinese_traditional"],"憲":["chinese_traditional"],"慶":["chinese_traditional"],"編":["chinese_traditional"],"觸":["chinese_traditional"],"銷":["chinese_traditional"],"詩":["chinese_traditional"],"綠":["chinese_traditional"],"雞":["chinese_traditional"],"孫":["chinese_traditional"],"膠":["chinese_traditional"],"鄉":["chinese_traditional"],"臨":["chinese_traditional"],"陸":["chinese_traditional"],"顧":["chinese_traditional"],"燈":["chinese_traditional"],"歲":["chinese_traditional"],"劇":["chinese_traditional"],"趙":["chinese_traditional"],"課":["chinese_traditional"],"凱":["chinese_traditional"],"額":["chinese_traditional"],"紹":["chinese_traditional"],"齊":["chinese_traditional"],"偉":["chinese_traditional"],"爐":["chinese_traditional"],"楊":["chinese_traditional"],"桿":["chinese_traditional"],"鎮":["chinese_traditional"],"飯":["chinese_traditional"],"濃":["chinese_traditional"],"懷":["chinese_traditional"],"趕":["chinese_traditional"],"庫":["chinese_traditional"],"奪":["chinese_traditional"],"靈":["chinese_traditional"],"稅":["chinese_traditional"],"滅":["chinese_traditional"],"賽":["chinese_traditional"],"歸":["chinese_traditional"],"盤":["chinese_traditional"],"險":["chinese_traditional"],"錄":["chinese_traditional"],"純":["chinese_traditional"],"蓋":["chinese_traditional"],"橫":["chinese_traditional"],"槍":["chinese_traditional"],"潤":["chinese_traditional"],"蟲":["chinese_traditional"],"澤":["chinese_traditional"],"腦":["chinese_traditional"],"歐":["chinese_traditional"],"側":["chinese_traditional"],"徹":["chinese_traditional"],"慮":["chinese_traditional"],"納":["chinese_traditional"],"彈":["chinese_traditional"],"飼":["chinese_traditional"],"麥":["chinese_traditional"],"濕":["chinese_traditional"],"築":["chinese_traditional"],"惡":["chinese_traditional"],"戶":["chinese_traditional"],"訪":["chinese_traditional"],"跡":["chinese_traditional"],"擺":["chinese_traditional"],"賣":["chinese_traditional"],"擇":["chinese_traditional"],"銅":["chinese_traditional"],"獻":["chinese_traditional"],"陣":["chinese_traditional"],"陰":["chinese_traditional"],"樓":["chinese_traditional"],"謀":["chinese_traditional"],"噸":["chinese_traditional"],"豬":["chinese_traditional"],"館":["chinese_traditional"],"爺":["chinese_traditional"],"驚":["chinese_traditional"],"遺":["chinese_traditional"],"纖":["chinese_traditional"],"傾":["chinese_traditional"],"謝":["chinese_traditional"],"奮":["chinese_traditional"],"購":["chinese_traditional"],"監":["chinese_traditional"],"貫":["chinese_traditional"],"釋":["chinese_traditional"],"詞":["chinese_traditional"],"頓":["chinese_traditional"],"寶":["chinese_traditional"],"塵":["chinese_traditional"],"聞":["chinese_traditional"],"殘":["chinese_traditional"],"橋":["chinese_traditional"],"婦":["chinese_traditional"],"綜":["chinese_traditional"],"吳":["chinese_traditional"],"搖":["chinese_traditional"],"贊":["chinese_traditional"],"訂":["chinese_traditional"],"園":["chinese_traditional"],"紛":["chinese_traditional"],"敗":["chinese_traditional"],"榮":["chinese_traditional"],"閉":["chinese_traditional"],"灣":["chinese_traditional"],"鍵":["chinese_traditional"],"駐":["chinese_traditional"],"鍋":["chinese_traditional"],"剝":["chinese_traditional"],"鹼":["chinese_traditional"],"齒":["chinese_traditional"],"煉":["chinese_traditional"],"紡":["chinese_traditional"],"廢":["chinese_traditional"],"緩":["chinese_traditional"],"淨":["chinese_traditional"],"莊":["chinese_traditional"],"貿":["chinese_traditional"],"慣":["chinese_traditional"],"夥":["chinese_traditional"],"勻":["chinese_traditional"],"紗":["chinese_traditional"],"辯":["chinese_traditional"],"億":["chinese_traditional"],"脈":["chinese_traditional"],"薩":["chinese_traditional"],"網":["chinese_traditional"],"噴":["chinese_traditional"],"縱":["chinese_traditional"],"掛":["chinese_traditional"],"賀":["chinese_traditional"],"閃":["chinese_traditional"],"牆":["chinese_traditional"],"軟":["chinese_traditional"],"滾":["chinese_traditional"],"盪":["chinese_traditional"],"儀":["chinese_traditional"],"軋":["chinese_traditional"],"貢":["chinese_traditional"],"鑽":["chinese_traditional"],"幣":["chinese_traditional"],"軌":["chinese_traditional"],"畝":["chinese_traditional"],"畢":["chinese_traditional"],"島":["chinese_traditional"],"鑄":["chinese_traditional"],"湯":["chinese_traditional"],"閥":["chinese_traditional"],"匯":["chinese_traditional"],"繞":["chinese_traditional"],"績":["chinese_traditional"],"啟":["chinese_traditional"],"顏":["chinese_traditional"],"淚":["chinese_traditional"],"藍":["chinese_traditional"],"鏡":["chinese_traditional"],"壯":["chinese_traditional"],"鋒":["chinese_traditional"],"貧":["chinese_traditional"],"虛":["chinese_traditional"],"彎":["chinese_traditional"],"綱":["chinese_traditional"],"隸":["chinese_traditional"],"宮":["chinese_traditional"],"違":["chinese_traditional"],"夾":["chinese_traditional"],"緣":["chinese_traditional"],"窮":["chinese_traditional"],"溝":["chinese_traditional"],"繩":["chinese_traditional"],"憶":["chinese_traditional"],"漿":["chinese_traditional"],"欄":["chinese_traditional"],"擁":["chinese_traditional"],"貯":["chinese_traditional"],"禮":["chinese_traditional"],"濾":["chinese_traditional"],"鈉":["chinese_traditional"],"紋":["chinese_traditional"],"罷":["chinese_traditional"],"罰":["chinese_traditional"],"潛":["chinese_traditional"],"縫":["chinese_traditional"],"飽":["chinese_traditional"],"獎":["chinese_traditional"],"鋁":["chinese_traditional"],"麗":["chinese_traditional"],"鏈":["chinese_traditional"],"掃":["chinese_traditional"],"諸":["chinese_traditional"],"勵":["chinese_traditional"],"潔":["chinese_traditional"],"災":["chinese_traditional"],"鑑":["chinese_traditional"],"訟":["chinese_traditional"],"毀":["chinese_traditional"],"屆":["chinese_traditional"],"躍":["chinese_traditional"],"艱":["chinese_traditional"],"貝":["chinese_traditional"],"碼":["chinese_traditional"],"夢":["chinese_traditional"],"漁":["chinese_traditional"],"顆":["chinese_traditional"],"鉛":["chinese_traditional"],"倉":["chinese_traditional"],"銳":["chinese_traditional"],"曉":["chinese_traditional"],"隱":["chinese_traditional"],"礙":["chinese_traditional"],"撥":["chinese_traditional"],"肅":["chinese_traditional"],"牽":["chinese_traditional"],"搶":["chinese_traditional"],"殼":["chinese_traditional"],"訊":["chinese_traditional"],"誠":["chinese_traditional"],"頁":["chinese_traditional"],"齡":["chinese_traditional"],"倫":["chinese_traditional"],"尋":["chinese_traditional"],"鋪":["chinese_traditional"],"聖":["chinese_traditional"],"鄭":["chinese_traditional"],"騰":["chinese_traditional"],"貼":["chinese_traditional"],"闊":["chinese_traditional"],"輛":["chinese_traditional"],"儲":["chinese_traditional"],"簽":["chinese_traditional"],"鬧":["chinese_traditional"],"擾":["chinese_traditional"],"遞":["chinese_traditional"],"戲":["chinese_traditional"],"餵":["chinese_traditional"],"療":["chinese_traditional"],"撫":["chinese_traditional"],"蝦":["chinese_traditional"],"蠟":["chinese_traditional"],"鄰":["chinese_traditional"],"鞏":["chinese_traditional"],"擠":["chinese_traditional"],"棄":["chinese_traditional"],"勁":["chinese_traditional"],"鄧":["chinese_traditional"],"爛":["chinese_traditional"],"磚":["chinese_traditional"],"烏":["chinese_traditional"],"艦":["chinese_traditional"],"淺":["chinese_traditional"],"暫":["chinese_traditional"],"膽":["chinese_traditional"],"詳":["chinese_traditional"],"譜":["chinese_traditional"],"賓":["chinese_traditional"],"輝":["chinese_traditional"],"憤":["chinese_traditional"],"競":["chinese_traditional"],"緒":["chinese_traditional"],"塗":["chinese_traditional"],"偵":["chinese_traditional"],"懸":["chinese_traditional"],"糾":["chinese_traditional"],"鎖":["chinese_traditional"],"賞":["chinese_traditional"],"鴨":["chinese_traditional"],"趨":["chinese_traditional"],"鳳":["chinese_traditional"],"輩":["chinese_traditional"],"灘":["chinese_traditional"],"驅":["chinese_traditional"],"篩":["chinese_traditional"],"峽":["chinese_traditional"],"壽":["chinese_traditional"],"譯":["chinese_traditional"],"遲":["chinese_traditional"],"矽":["chinese_traditional"],"貸":["chinese_traditional"],"脅":["chinese_traditional"],"蝕":["chinese_traditional"],"奧":["chinese_traditional"],"鳴":["chinese_traditional"],"嶺":["chinese_traditional"],"憑":["chinese_traditional"],"繪":["chinese_traditional"],"錫":["chinese_traditional"],"廟":["chinese_traditional"],"籌":["chinese_traditional"],"凍":["chinese_traditional"],"輔":["chinese_traditional"],"攝":["chinese_traditional"],"襲":["chinese_traditional"],"鉀":["chinese_traditional"],"鳥":["chinese_traditional"],"韓":["chinese_traditional"],"僑":["chinese_traditional"],"涼":["chinese_traditional"],"餾":["chinese_traditional"],"勸":["chinese_traditional"],"遼":["chinese_traditional"],"鴻":["chinese_traditional"],"輥":["chinese_traditional"],"飲":["chinese_traditional"],"罵":["chinese_traditional"],"辭":["chinese_traditional"],"蔣":["chinese_traditional"],"絨":["chinese_traditional"],"霧":["chinese_traditional"],"擬":["chinese_traditional"],"輯":["chinese_traditional"],"陝":["chinese_traditional"],"償":["chinese_traditional"],"廳":["chinese_traditional"],"駛":["chinese_traditional"],"賦":["chinese_traditional"],"澆":["chinese_traditional"],"鈣":["chinese_traditional"],"虧":["chinese_traditional"],"騙":["chinese_traditional"],"脹":["chinese_traditional"],"諧":["chinese_traditional"],"拋":["chinese_traditional"],"黴":["chinese_traditional"],"崗":["chinese_traditional"],"盜":["chinese_traditional"],"滲":["chinese_traditional"],"臟":["chinese_traditional"],"賴":["chinese_traditional"],"湧":["chinese_traditional"],"閱":["chinese_traditional"],"厲":["chinese_traditional"],"烴":["chinese_traditional"],"緯":["chinese_traditional"],"偽":["chinese_traditional"],"嘆":["chinese_traditional"],"釘":["chinese_traditional"],"莖":["chinese_traditional"],"籠":["chinese_traditional"],"錐":["chinese_traditional"],"恆":["chinese_traditional"],"傑":["chinese_traditional"],"綸":["chinese_traditional"],"敘":["chinese_traditional"],"獄":["chinese_traditional"],"絡":["chinese_traditional"],"驟":["chinese_traditional"],"冊":["chinese_traditional"],"屍":["chinese_traditional"],"紳":["chinese_traditional"],"犧":["chinese_traditional"],"轟":["chinese_traditional"],"晉":["chinese_traditional"],"禦":["chinese_traditional"],"錠":["chinese_traditional"],"錦":["chinese_traditional"],"喪":["chinese_traditional"],"鍛":["chinese_traditional"],"壟":["chinese_traditional"],"撲":["chinese_traditional"],"邁":["chinese_traditional"],"閒":["chinese_traditional"],"憂":["chinese_traditional"],"頑":["chinese_traditional"],"漲":["chinese_traditional"],"闢":["chinese_traditional"],"懲":["chinese_traditional"],"飄":["chinese_traditional"],"飾":["chinese_traditional"],"蕭":["chinese_traditional"],"郵":["chinese_traditional"],"遷":["chinese_traditional"],"煩":["chinese_traditional"],"債":["chinese_traditional"],"帳":["chinese_traditional"],"鈴":["chinese_traditional"],"餅":["chinese_traditional"],"雛":["chinese_traditional"],"賢":["chinese_traditional"],"丟":["chinese_traditional"],"墊":["chinese_traditional"],"擋":["chinese_traditional"],"覽":["chinese_traditional"],"貪":["chinese_traditional"],"繳":["chinese_traditional"],"馮":["chinese_traditional"],"諾":["chinese_traditional"],"誼":["chinese_traditional"],"兇":["chinese_traditional"],"誣":["chinese_traditional"],"騎":["chinese_traditional"],"喬":["chinese_traditional"],"叢":["chinese_traditional"],"盧":["chinese_traditional"],"悶":["chinese_traditional"],"諮":["chinese_traditional"],"駕":["chinese_traditional"],"纜":["chinese_traditional"],"鉺":["chinese_traditional"],"擲":["chinese_traditional"],"頗":["chinese_traditional"],"慘":["chinese_traditional"],"臘":["chinese_traditional"],"窩":["chinese_traditional"],"滌":["chinese_traditional"],"劍":["chinese_traditional"],"潑":["chinese_traditional"],"蔥":["chinese_traditional"],"撈":["chinese_traditional"],"蒼":["chinese_traditional"],"濱":["chinese_traditional"],"倆":["chinese_traditional"],"瘋":["chinese_traditional"],"糞":["chinese_traditional"],"檔":["chinese_traditional"],"駁":["chinese_traditional"],"腸":["chinese_traditional"],"撐":["chinese_traditional"],"曬":["chinese_traditional"],"蓮":["chinese_traditional"],"攤":["chinese_traditional"],"攪":["chinese_traditional"],"醬":["chinese_traditional"],"皺":["chinese_traditional"],"暢":["chinese_traditional"],"疊":["chinese_traditional"],"閣":["chinese_traditional"],"萊":["chinese_traditional"],"轄":["chinese_traditional"],"鉤":["chinese_traditional"],"壩":["chinese_traditional"],"餓":["chinese_traditional"],"禍":["chinese_traditional"],"邏":["chinese_traditional"],"嘗":["chinese_traditional"],"韋":["chinese_traditional"],"abdikace":["czech"],"abeceda":["czech"],"adresa":["czech"],"agrese":["czech"],"akce":["czech"],"aktovka":["czech"],"alej":["czech"],"alkohol":["czech"],"amputace":["czech"],"ananas":["czech"],"andulka":["czech"],"anekdota":["czech"],"anketa":["czech"],"antika":["czech"],"anulovat":["czech"],"archa":["czech"],"arogance":["czech"],"asfalt":["czech","turkish"],"asistent":["czech"],"aspirace":["czech"],"astma":["czech"],"astronom":["czech"],"atlas":["czech"],"atletika":["czech"],"atol":["czech"],"autobus":["czech"],"azyl":["czech"],"babka":["czech"],"bachor":["czech"],"bacil":["czech"],"baculka":["czech"],"badatel":["czech"],"bageta":["czech"],"bagr":["czech"],"bahno":["czech"],"bakterie":["czech"],"balada":["czech"],"baletka":["czech"],"balkon":["czech"],"balonek":["czech"],"balvan":["czech"],"balza":["czech"],"bambus":["czech"],"bankomat":["czech"],"barbar":["czech"],"baret":["czech"],"barman":["czech"],"baroko":["czech"],"barva":["czech"],"baterka":["czech"],"batoh":["czech"],"bavlna":["czech"],"bazalka":["czech"],"bazilika":["czech"],"bazuka":["czech"],"bedna":["czech"],"beran":["czech"],"beseda":["czech"],"bestie":["czech"],"beton":["czech","turkish"],"bezinka":["czech"],"bezmoc":["czech"],"beztak":["czech"],"bicykl":["czech"],"bidlo":["czech"],"biftek":["czech","turkish"],"bikiny":["czech"],"bilance":["czech"],"biograf":["czech"],"biolog":["czech"],"bitva":["czech"],"bizon":["czech"],"blahobyt":["czech"],"blatouch":["czech"],"blecha":["czech"],"bledule":["czech"],"blesk":["czech"],"blikat":["czech"],"blizna":["czech"],"blokovat":["czech"],"bloudit":["czech"],"blud":["czech"],"bobek":["czech"],"bobr":["czech"],"bodlina":["czech"],"bodnout":["czech"],"bohatost":["czech"],"bojkot":["czech"],"bojovat":["czech"],"bokorys":["czech"],"bolest":["czech"],"borec":["czech"],"borovice":["czech"],"bota":["czech"],"boubel":["czech"],"bouchat":["czech"],"bouda":["czech"],"boule":["czech"],"bourat":["czech"],"boxer":["czech"],"bradavka":["czech"],"brambora":["czech"],"branka":["czech"],"bratr":["czech"],"brepta":["czech"],"briketa":["czech"],"brko":["czech"],"brloh":["czech"],"bronz":["czech"],"broskev":["czech"],"brunetka":["czech"],"brusinka":["czech"],"brzda":["czech"],"brzy":["czech"],"bublina":["czech"],"bubnovat":["czech"],"buchta":["czech"],"buditel":["czech"],"budka":["czech"],"budova":["czech"],"bufet":["czech"],"bujarost":["czech"],"bukvice":["czech"],"buldok":["czech"],"bulva":["czech"],"bunda":["czech"],"bunkr":["czech"],"burza":["czech"],"butik":["czech","turkish"],"buvol":["czech"],"buzola":["czech"],"bydlet":["czech"],"bylina":["czech"],"bytovka":["czech"],"bzukot":["czech"],"capart":["czech"],"carevna":["czech"],"cedr":["czech"],"cedule":["czech"],"cejch":["czech"],"cejn":["czech"],"cela":["czech"],"celer":["czech"],"celkem":["czech"],"celnice":["czech"],"cenina":["czech"],"cennost":["czech"],"cenovka":["czech"],"centrum":["czech"],"cenzor":["czech"],"cestopis":["czech"],"cetka":["czech"],"chalupa":["czech"],"chapadlo":["czech"],"charita":["czech"],"chata":["czech"],"chechtat":["czech"],"chemie":["czech"],"chichot":["czech"],"chirurg":["czech"],"chlad":["czech"],"chleba":["czech"],"chlubit":["czech"],"chmel":["czech"],"chmura":["czech"],"chobot":["czech"],"chochol":["czech"],"chodba":["czech"],"cholera":["czech"],"chomout":["czech"],"chopit":["czech"],"choroba":["czech"],"chov":["czech"],"chrapot":["czech"],"chrlit":["czech"],"chrt":["czech"],"chrup":["czech"],"chtivost":["czech"],"chudina":["czech"],"chutnat":["czech"],"chvat":["czech"],"chvilka":["czech"],"chvost":["czech"],"chyba":["czech"],"chystat":["czech"],"chytit":["czech"],"cibule":["czech"],"cigareta":["czech"],"cihelna":["czech"],"cihla":["czech"],"cinkot":["czech"],"cirkus":["czech"],"cisterna":["czech"],"citace":["czech"],"citrus":["czech"],"cizinec":["czech"],"cizost":["czech"],"clona":["czech"],"cokoliv":["czech"],"couvat":["czech"],"ctitel":["czech"],"ctnost":["czech"],"cudnost":["czech"],"cuketa":["czech"],"cukr":["czech"],"cupot":["czech"],"cvaknout":["czech"],"cval":["czech"],"cvik":["czech"],"cvrkot":["czech"],"cyklista":["czech"],"daleko":["czech"],"dareba":["czech"],"datel":["czech"],"datum":["czech"],"dcera":["czech"],"debata":["czech"],"dechovka":["czech"],"decibel":["czech"],"deficit":["czech"],"deflace":["czech"],"dekl":["czech"],"dekret":["czech"],"demokrat":["czech"],"deprese":["czech"],"derby":["czech"],"deska":["czech"],"detektiv":["czech"],"dikobraz":["czech"],"diktovat":["czech"],"dioda":["czech"],"diplom":["czech"],"disk":["czech","turkish"],"displej":["czech"],"divadlo":["czech"],"divoch":["czech"],"dlaha":["czech"],"dlouho":["czech"],"dluhopis":["czech"],"dnes":["czech"],"dobro":["czech"],"dobytek":["czech"],"docent":["czech"],"dochutit":["czech"],"dodnes":["czech"],"dohled":["czech"],"dohoda":["czech"],"dohra":["czech"],"dojem":["czech"],"dojnice":["czech"],"doklad":["czech"],"dokola":["czech"],"doktor":["czech","turkish"],"dokument":["czech"],"dolar":["czech"],"doleva":["czech"],"dolina":["czech"],"doma":["czech"],"dominant":["czech"],"domluvit":["czech"],"domov":["czech"],"donutit":["czech"],"dopad":["czech"],"dopis":["czech"],"doplnit":["czech"],"doposud":["czech"],"doprovod":["czech"],"dopustit":["czech"],"dorazit":["czech"],"dorost":["czech"],"dort":["czech"],"dosah":["czech"],"doslov":["czech"],"dostatek":["czech"],"dosud":["czech"],"dosyta":["czech"],"dotaz":["czech"],"dotek":["czech"],"dotknout":["czech"],"doufat":["czech"],"doutnat":["czech"],"dovozce":["czech"],"dozadu":["czech"],"doznat":["czech"],"dozorce":["czech"],"drahota":["czech"],"drak":["czech"],"dramatik":["czech"],"dravec":["czech"],"draze":["czech"],"drdol":["czech"],"drobnost":["czech"],"drogerie":["czech"],"drozd":["czech"],"drsnost":["czech"],"drtit":["czech"],"drzost":["czech"],"duben":["czech"],"duchovno":["czech"],"dudek":["czech"],"duha":["czech"],"duhovka":["czech"],"dusit":["czech"],"dusno":["czech"],"dutost":["czech"],"dvojice":["czech"],"dvorec":["czech"],"dynamit":["czech"],"ekolog":["czech"],"ekonomie":["czech"],"elektron":["czech"],"elipsa":["czech"],"email":["czech"],"emise":["czech"],"emoce":["czech"],"empatie":["czech"],"epizoda":["czech"],"epocha":["czech"],"epopej":["czech"],"epos":["czech"],"esej":["czech"],"esence":["czech"],"eskorta":["czech"],"eskymo":["czech"],"etiketa":["czech"],"euforie":["czech"],"evoluce":["czech"],"exekuce":["czech"],"exkurze":["czech"],"expedice":["czech"],"exploze":["czech"],"export":["czech"],"extrakt":["czech"],"facka":["czech"],"fajfka":["czech"],"fakulta":["czech"],"fanatik":["czech","turkish"],"fantazie":["czech"],"farmacie":["czech"],"favorit":["czech"],"fazole":["czech"],"federace":["czech"],"fejeton":["czech"],"fenka":["czech"],"fialka":["czech"],"figurant":["czech"],"filozof":["czech"],"filtr":["czech"],"finance":["czech"],"finta":["czech"],"fixace":["czech"],"fjord":["czech"],"flanel":["czech"],"flirt":["czech"],"flotila":["czech"],"fond":["czech"],"fosfor":["czech"],"fotbal":["czech"],"fotka":["czech"],"foton":["czech"],"frakce":["czech"],"freska":["czech"],"fronta":["czech"],"fukar":["czech"],"funkce":["czech"],"fyzika":["czech"],"galeje":["czech"],"garant":["czech"],"genetika":["czech"],"geolog":["czech"],"gilotina":["czech"],"glazura":["czech"],"glejt":["czech"],"golem":["czech"],"golfista":["czech"],"gotika":["czech"],"graf":["czech"],"gramofon":["czech"],"granule":["czech"],"grep":["czech"],"gril":["czech"],"grog":["czech"],"groteska":["czech"],"guma":["czech"],"hadice":["czech"],"hadr":["czech"],"hala":["czech"],"halenka":["czech"],"hanba":["czech"],"hanopis":["czech"],"harfa":["czech"],"harpuna":["czech"],"havran":["czech"],"hebkost":["czech"],"hejkal":["czech"],"hejno":["czech"],"hejtman":["czech"],"hektar":["czech","turkish"],"helma":["czech"],"hematom":["czech"],"herec":["czech"],"herna":["czech"],"heslo":["czech"],"hezky":["czech"],"historik":["czech"],"hladovka":["czech"],"hlasivky":["czech"],"hlava":["czech"],"hledat":["czech"],"hlen":["czech"],"hlodavec":["czech"],"hloh":["czech"],"hloupost":["czech"],"hltat":["czech"],"hlubina":["czech"],"hluchota":["czech"],"hmat":["czech"],"hmota":["czech"],"hmyz":["czech"],"hnis":["czech"],"hnojivo":["czech"],"hnout":["czech"],"hoblina":["czech"],"hoboj":["czech"],"hoch":["czech"],"hodiny":["czech"],"hodlat":["czech"],"hodnota":["czech"],"hodovat":["czech"],"hojnost":["czech"],"hokej":["czech"],"holinka":["czech"],"holka":["czech"],"holub":["czech"],"homole":["czech"],"honitba":["czech"],"honorace":["czech"],"horal":["czech"],"horda":["czech"],"horizont":["czech"],"horko":["czech"],"horlivec":["czech"],"hormon":["czech","turkish"],"hornina":["czech"],"horoskop":["czech"],"horstvo":["czech"],"hospoda":["czech"],"hostina":["czech"],"hotovost":["czech"],"houba":["czech"],"houf":["czech"],"houpat":["czech"],"houska":["czech"],"hovor":["czech"],"hradba":["czech"],"hranice":["czech"],"hravost":["czech"],"hrazda":["czech"],"hrbolek":["czech"],"hrdina":["czech"],"hrdlo":["czech"],"hrdost":["czech"],"hrnek":["czech"],"hrobka":["czech"],"hromada":["czech"],"hrot":["czech"],"hrouda":["czech"],"hrozen":["czech"],"hrstka":["czech"],"hrubost":["czech"],"hryzat":["czech"],"hubenost":["czech"],"hubnout":["czech"],"hudba":["czech"],"hukot":["czech"],"humr":["czech"],"husita":["czech"],"hustota":["czech"],"hvozd":["czech"],"hybnost":["czech"],"hydrant":["czech"],"hygiena":["czech"],"hymna":["czech"],"hysterik":["czech"],"idylka":["czech"],"ihned":["czech"],"ikona":["czech"],"iluze":["czech"],"imunita":["czech"],"infekce":["czech"],"inflace":["czech"],"inkaso":["czech"],"inovace":["czech"],"inspekce":["czech"],"internet":["czech"],"invalida":["czech"],"investor":["czech"],"inzerce":["czech"],"ironie":["czech"],"jablko":["czech"],"jachta":["czech"],"jahoda":["czech"],"jakmile":["czech"],"jakost":["czech"],"jalovec":["czech"],"jantar":["czech"],"jarmark":["czech"],"jaro":["czech"],"jasan":["czech"],"jasno":["czech"],"jatka":["czech"],"javor":["czech"],"jazyk":["czech"],"jedinec":["czech"],"jedle":["czech"],"jednatel":["czech"],"jehlan":["czech"],"jekot":["czech"],"jelen":["czech"],"jelito":["czech"],"jemnost":["czech"],"jenom":["czech"],"jepice":["czech"],"jeseter":["czech"],"jevit":["czech"],"jezdec":["czech"],"jezero":["czech"],"jinak":["czech"],"jindy":["czech"],"jinoch":["czech"],"jiskra":["czech"],"jistota":["czech"],"jitrnice":["czech"],"jizva":["czech"],"jmenovat":["czech"],"jogurt":["czech"],"jurta":["czech"],"kabaret":["czech"],"kabel":["czech"],"kabinet":["czech"],"kachna":["czech"],"kadet":["czech"],"kadidlo":["czech"],"kahan":["czech"],"kajak":["czech"],"kajuta":["czech"],"kakao":["czech"],"kaktus":["czech"],"kalamita":["czech"],"kalhoty":["czech"],"kalibr":["czech"],"kalnost":["czech"],"kamera":["czech"],"kamkoliv":["czech"],"kamna":["czech"],"kanibal":["czech"],"kanoe":["czech"],"kantor":["czech"],"kapalina":["czech"],"kapela":["czech"],"kapitola":["czech"],"kapka":["czech"],"kaple":["czech"],"kapota":["czech"],"kapr":["czech"],"kapusta":["czech"],"kapybara":["czech"],"karamel":["czech"],"karotka":["czech"],"karton":["czech"],"kasa":["czech"],"katalog":["czech"],"katedra":["czech"],"kauce":["czech"],"kauza":["czech"],"kavalec":["czech"],"kazajka":["czech"],"kazeta":["czech"],"kazivost":["czech"],"kdekoliv":["czech"],"kdesi":["czech"],"kedluben":["czech"],"kemp":["czech"],"keramika":["czech"],"kino":["czech"],"klacek":["czech"],"kladivo":["czech"],"klam":["czech"],"klapot":["czech"],"klasika":["czech"],"klaun":["czech"],"klec":["czech"],"klenba":["czech"],"klepat":["czech"],"klesnout":["czech"],"klid":["czech"],"klima":["czech"],"klisna":["czech"],"klobouk":["czech"],"klokan":["czech"],"klopa":["czech"],"kloub":["czech"],"klubovna":["czech"],"klusat":["czech"],"kluzkost":["czech"],"kmen":["czech"],"kmitat":["czech"],"kmotr":["czech"],"kniha":["czech"],"knot":["czech"],"koalice":["czech"],"koberec":["czech"],"kobka":["czech"],"kobliha":["czech"],"kobyla":["czech"],"kocour":["czech"],"kohout":["czech"],"kojenec":["czech"],"kokos":["czech"],"koktejl":["czech"],"kolaps":["czech"],"koleda":["czech"],"kolize":["czech"],"kolo":["czech"],"komando":["czech"],"kometa":["czech"],"komik":["czech"],"komnata":["czech"],"komora":["czech"],"kompas":["czech"],"komunita":["czech"],"konat":["czech"],"koncept":["czech"],"kondice":["czech"],"konec":["czech"],"konfese":["czech"],"kongres":["czech"],"konina":["czech"],"konkurs":["czech"],"kontakt":["czech"],"konzerva":["czech"],"kopanec":["czech"],"kopie":["czech"],"kopnout":["czech"],"koprovka":["czech"],"korbel":["czech"],"korektor":["czech"],"kormidlo":["czech"],"koroptev":["czech"],"korpus":["czech"],"koruna":["czech"],"koryto":["czech"],"korzet":["czech"],"kosatec":["czech"],"kostka":["czech"],"kotel":["czech"],"kotleta":["czech"],"kotoul":["czech"],"koukat":["czech"],"koupelna":["czech"],"kousek":["czech"],"kouzlo":["czech"],"kovboj":["czech"],"koza":["czech"],"kozoroh":["czech"],"krabice":["czech"],"krach":["czech"],"krajina":["czech"],"kralovat":["czech"],"krasopis":["czech"],"kravata":["czech"],"kredit":["czech"],"krejcar":["czech"],"kresba":["czech"],"kreveta":["czech"],"kriket":["czech"],"kritik":["czech"],"krize":["czech"],"krkavec":["czech"],"krmelec":["czech"],"krmivo":["czech"],"krocan":["czech"],"krok":["czech"],"kronika":["czech"],"kropit":["czech"],"kroupa":["czech"],"krovka":["czech"],"krtek":["czech"],"kruhadlo":["czech"],"krupice":["czech"],"krutost":["czech"],"krvinka":["czech"],"krychle":["czech"],"krypta":["czech"],"krystal":["czech"],"kryt":["czech"],"kudlanka":["czech"],"kufr":["czech"],"kujnost":["czech"],"kukla":["czech"],"kulajda":["czech"],"kulich":["czech"],"kulka":["czech"],"kulomet":["czech"],"kultura":["czech"],"kuna":["czech"],"kupodivu":["czech"],"kurt":["czech"],"kurzor":["czech"],"kutil":["czech"],"kvalita":["czech"],"kvasinka":["czech"],"kvestor":["czech"],"kynolog":["czech"],"kyselina":["czech"],"kytara":["czech"],"kytice":["czech"],"kytka":["czech"],"kytovec":["czech"],"kyvadlo":["czech"],"labrador":["czech"],"lachtan":["czech"],"ladnost":["czech"],"laik":["czech"],"lakomec":["czech"],"lamela":["czech"],"lampa":["czech"],"lanovka":["czech"],"lasice":["czech"],"laso":["czech"],"lastura":["czech"],"latinka":["czech"],"lavina":["czech"],"lebka":["czech"],"leckdy":["czech"],"leden":["czech"],"lednice":["czech"],"ledovka":["czech"],"ledvina":["czech"],"legenda":["czech"],"legie":["czech"],"legrace":["czech"],"lehce":["czech"],"lehkost":["czech"],"lehnout":["czech"],"lektvar":["czech"],"lenochod":["czech"],"lentilka":["czech"],"lepenka":["czech"],"lepidlo":["czech"],"letadlo":["czech"],"letec":["czech"],"letmo":["czech"],"letokruh":["czech"],"levhart":["czech"],"levitace":["czech"],"levobok":["czech"],"libra":["czech"],"lichotka":["czech"],"lidojed":["czech"],"lidskost":["czech"],"lihovina":["czech"],"lijavec":["czech"],"lilek":["czech"],"limetka":["czech"],"linie":["czech"],"linka":["czech"],"linoleum":["czech"],"listopad":["czech"],"litina":["czech"],"litovat":["czech"],"lobista":["czech"],"lodivod":["czech"],"logika":["czech"],"logoped":["czech"],"lokalita":["czech"],"loket":["czech"],"lomcovat":["czech"],"lopata":["czech"],"lopuch":["czech"],"lord":["czech"],"losos":["czech"],"lotr":["czech"],"loudal":["czech"],"louh":["czech"],"louka":["czech"],"louskat":["czech"],"lovec":["czech"],"lstivost":["czech"],"lucerna":["czech"],"lucifer":["czech"],"lump":["czech"],"lusk":["czech"],"lustrace":["czech"],"lvice":["czech"],"lyra":["czech"],"lyrika":["czech"],"lysina":["czech"],"madam":["czech"],"madlo":["czech"],"magistr":["czech"],"mahagon":["czech"],"majetek":["czech"],"majitel":["czech"],"majorita":["czech"],"makak":["czech"],"makovice":["czech"],"makrela":["czech"],"malba":["czech"],"malina":["czech"],"malovat":["czech"],"malvice":["czech"],"maminka":["czech"],"mandle":["czech"],"manko":["czech"],"marnost":["czech"],"masakr":["czech"],"maskot":["czech"],"masopust":["czech"],"matice":["czech"],"matrika":["czech"],"maturita":["czech"],"mazanec":["czech"],"mazivo":["czech"],"mazlit":["czech"],"mazurka":["czech"],"mdloba":["czech"],"mechanik":["czech"],"meditace":["czech"],"medovina":["czech"],"melasa":["czech"],"meloun":["czech"],"mentolka":["czech"],"metla":["czech"],"metoda":["czech"],"metr":["czech"],"mezera":["czech"],"migrace":["czech"],"mihnout":["czech"],"mihule":["czech"],"mikina":["czech"],"mikrofon":["czech","turkish"],"milenec":["czech"],"milimetr":["czech"],"milost":["czech"],"mimika":["czech"],"mincovna":["czech"],"minibar":["czech"],"minomet":["czech"],"minulost":["czech"],"miska":["czech"],"mistr":["czech"],"mixovat":["czech"],"mladost":["czech"],"mlha":["czech"],"mlhovina":["czech"],"mlok":["czech"],"mlsat":["czech"],"mluvit":["czech"],"mnich":["czech"],"mnohem":["czech"],"mobil":["czech"],"mocnost":["czech"],"modelka":["czech"],"modlitba":["czech"],"mohyla":["czech"],"mokro":["czech"],"molekula":["czech"],"momentka":["czech"],"monarcha":["czech"],"monokl":["czech"],"monstrum":["czech"],"montovat":["czech"],"monzun":["czech"],"mosaz":["czech"],"moskyt":["czech"],"most":["czech"],"motivace":["czech"],"motorka":["czech"],"motyka":["czech"],"moucha":["czech"],"moudrost":["czech"],"mozaika":["czech"],"mozek":["czech"],"mozol":["czech"],"mramor":["czech"],"mravenec":["czech"],"mrkev":["czech"],"mrtvola":["czech"],"mrzet":["czech"],"mrzutost":["czech"],"mstitel":["czech"],"mudrc":["czech"],"muflon":["czech"],"mulat":["czech"],"mumie":["czech"],"munice":["czech"],"muset":["czech"],"mutace":["czech"],"muzeum":["czech"],"muzikant":["czech"],"myslivec":["czech"],"mzda":["czech"],"nabourat":["czech"],"nachytat":["czech"],"nadace":["czech"],"nadbytek":["czech"],"nadhoz":["czech"],"nadobro":["czech"],"nadpis":["czech"],"nahlas":["czech"],"nahnat":["czech"],"nahodile":["czech"],"nahradit":["czech"],"naivita":["czech"],"najednou":["czech"],"najisto":["czech"],"najmout":["czech"],"naklonit":["czech"],"nakonec":["czech"],"nakrmit":["czech"],"nalevo":["czech"],"namazat":["czech"],"namluvit":["czech"],"nanometr":["czech"],"naoko":["czech"],"naopak":["czech"],"naostro":["czech"],"napadat":["czech"],"napevno":["czech"],"naplnit":["czech"],"napnout":["czech"],"naposled":["czech"],"naprosto":["czech"],"narodit":["czech"],"naruby":["czech"],"narychlo":["czech"],"nasadit":["czech"],"nasekat":["czech"],"naslepo":["czech"],"nastat":["czech"],"natolik":["czech"],"navenek":["czech"],"navrch":["czech"],"navzdory":["czech"],"nazvat":["czech"],"nebe":["czech"],"nechat":["czech"],"necky":["czech"],"nedaleko":["czech"],"nedbat":["czech"],"neduh":["czech"],"negace":["czech"],"nehet":["czech"],"nehoda":["czech"],"nejen":["czech"],"nejprve":["czech"],"neklid":["czech"],"nelibost":["czech"],"nemilost":["czech"],"nemoc":["czech"],"neochota":["czech"],"neonka":["czech"],"nepokoj":["czech"],"nerost":["czech"],"nerv":["czech"],"nesmysl":["czech"],"nesoulad":["czech"],"netvor":["czech"],"neuron":["czech"],"nevina":["czech"],"nezvykle":["czech"],"nicota":["czech"],"nijak":["czech"],"nikam":["czech"],"nikdy":["czech"],"nikl":["czech"],"nikterak":["czech"],"nitro":["czech"],"nocleh":["czech"],"nohavice":["czech"],"nominace":["czech"],"nora":["czech"],"norek":["czech"],"nositel":["czech"],"nosnost":["czech"],"nouze":["czech"],"noviny":["czech"],"novota":["czech"],"nozdra":["czech"],"nuda":["czech"],"nudle":["czech"],"nuget":["czech"],"nutit":["czech"],"nutnost":["czech"],"nutrie":["czech"],"nymfa":["czech"],"obal":["czech"],"obarvit":["czech"],"obava":["czech"],"obdiv":["czech"],"obec":["czech"],"obehnat":["czech"],"obejmout":["czech"],"obezita":["czech"],"obhajoba":["czech"],"obilnice":["czech"],"objasnit":["czech"],"objekt":["czech"],"obklopit":["czech"],"oblast":["czech"],"oblek":["czech"],"obliba":["czech"],"obloha":["czech"],"obluda":["czech"],"obnos":["czech"],"obohatit":["czech"],"obojek":["czech"],"obout":["czech"],"obrazec":["czech"],"obrna":["czech"],"obruba":["czech"],"obrys":["czech"],"obsah":["czech"],"obsluha":["czech"],"obstarat":["czech"],"obuv":["czech"],"obvaz":["czech"],"obvinit":["czech"],"obvod":["czech"],"obvykle":["czech"],"obyvatel":["czech"],"obzor":["czech"],"ocas":["czech"],"ocel":["czech"],"ocenit":["czech"],"ochladit":["czech"],"ochota":["czech"],"ochrana":["czech"],"ocitnout":["czech"],"odboj":["czech"],"odbyt":["czech"],"odchod":["czech"],"odcizit":["czech"],"odebrat":["czech"],"odeslat":["czech"],"odevzdat":["czech"],"odezva":["czech"],"odhadce":["czech"],"odhodit":["czech"],"odjet":["czech"],"odjinud":["czech"],"odkaz":["czech"],"odkoupit":["czech"],"odliv":["czech"],"odluka":["czech"],"odmlka":["czech"],"odolnost":["czech"],"odpad":["czech"],"odpis":["czech"],"odplout":["czech"],"odpor":["czech"],"odpustit":["czech"],"odpykat":["czech"],"odrazka":["czech"],"odsoudit":["czech"],"odstup":["czech"],"odsun":["czech"],"odtok":["czech"],"odtud":["czech"],"odvaha":["czech"],"odveta":["czech"],"odvolat":["czech"],"odvracet":["czech"],"odznak":["czech"],"ofina":["czech"],"ofsajd":["czech"],"ohlas":["czech"],"ohnisko":["czech"],"ohrada":["czech"],"ohrozit":["czech"],"ohryzek":["czech"],"okap":["czech"],"okenice":["czech"],"oklika":["czech"],"okno":["czech"],"okouzlit":["czech"],"okovy":["czech"],"okrasa":["czech"],"okres":["czech"],"okrsek":["czech"],"okruh":["czech"],"okupant":["czech"],"okurka":["czech"],"okusit":["czech"],"olejnina":["czech"],"olizovat":["czech"],"omak":["czech"],"omeleta":["czech"],"omezit":["czech"],"omladina":["czech"],"omlouvat":["czech"],"omluva":["czech"],"omyl":["czech"],"onehdy":["czech"],"opakovat":["czech"],"opasek":["czech"],"operace":["czech"],"opice":["czech"],"opilost":["czech"],"opisovat":["czech"],"opora":["czech"],"opozice":["czech"],"opravdu":["czech"],"oproti":["czech"],"orbital":["czech"],"orchestr":["czech"],"orgie":["czech"],"orlice":["czech"],"orloj":["czech"],"ortel":["czech"],"osada":["czech"],"oschnout":["czech"],"osika":["czech"],"osivo":["czech"],"oslava":["czech"],"oslepit":["czech"],"oslnit":["czech"],"oslovit":["czech"],"osnova":["czech"],"osoba":["czech"],"osolit":["czech"],"ospalec":["czech"],"osten":["czech"],"ostraha":["czech"],"ostuda":["czech"],"ostych":["czech"],"osvojit":["czech"],"oteplit":["czech"],"otisk":["czech"],"otop":["czech"],"otrhat":["czech"],"otrlost":["czech"],"otrok":["czech"],"otruby":["czech"],"otvor":["czech"],"ovanout":["czech"],"ovar":["czech"],"oves":["czech"],"ovlivnit":["czech"],"ovoce":["czech"],"oxid":["czech"],"ozdoba":["czech"],"pachatel":["czech"],"pacient":["czech"],"padouch":["czech"],"pahorek":["czech"],"pakt":["czech"],"palanda":["czech"],"palec":["czech"],"palivo":["czech"],"paluba":["czech"],"pamflet":["czech"],"pamlsek":["czech"],"panenka":["czech"],"panika":["czech"],"panna":["czech"],"panovat":["czech"],"panstvo":["czech"],"pantofle":["czech"],"paprika":["czech"],"parketa":["czech"],"parodie":["czech"],"parta":["czech"],"paruka":["czech"],"paryba":["czech"],"paseka":["czech"],"pasivita":["czech"],"pastelka":["czech"],"patent":["czech"],"patrona":["czech"],"pavouk":["czech"],"pazneht":["czech"],"pazourek":["czech"],"pecka":["czech"],"pedagog":["czech"],"pejsek":["czech"],"peklo":["czech"],"peloton":["czech"],"penalta":["czech"],"pendrek":["czech"],"penze":["czech"],"periskop":["czech"],"pero":["czech"],"pestrost":["czech"],"petarda":["czech"],"petice":["czech"],"petrolej":["czech"],"pevnina":["czech"],"pexeso":["czech"],"pianista":["czech"],"piha":["czech"],"pijavice":["czech"],"pikle":["czech"],"piknik":["czech","turkish"],"pilina":["czech"],"pilnost":["czech"],"pilulka":["czech"],"pinzeta":["czech"],"pipeta":["czech"],"pisatel":["czech"],"pistole":["czech"],"pitevna":["czech"],"pivnice":["czech"],"pivovar":["czech"],"placenta":["czech"],"plakat":["czech"],"plamen":["czech"],"planeta":["czech"],"plastika":["czech"],"platit":["czech"],"plavidlo":["czech"],"plaz":["czech"],"plech":["czech"],"plemeno":["czech"],"plenta":["czech"],"ples":["czech"],"pletivo":["czech"],"plevel":["czech"],"plivat":["czech"],"plnit":["czech"],"plno":["czech"],"plocha":["czech"],"plodina":["czech"],"plomba":["czech"],"plout":["czech"],"pluk":["czech"],"plyn":["czech"],"pobavit":["czech"],"pobyt":["czech"],"pochod":["czech"],"pocit":["czech"],"poctivec":["czech"],"podat":["czech"],"podcenit":["czech"],"podepsat":["czech"],"podhled":["czech"],"podivit":["czech"],"podklad":["czech"],"podmanit":["czech"],"podnik":["czech"],"podoba":["czech"],"podpora":["czech"],"podraz":["czech"],"podstata":["czech"],"podvod":["czech"],"podzim":["czech"],"poezie":["czech"],"pohanka":["czech"],"pohnutka":["czech"],"pohovor":["czech"],"pohroma":["czech"],"pohyb":["czech"],"pointa":["czech"],"pojistka":["czech"],"pojmout":["czech"],"pokazit":["czech"],"pokles":["czech"],"pokoj":["czech"],"pokrok":["czech"],"pokuta":["czech"],"pokyn":["czech"],"poledne":["czech"],"polibek":["czech"],"polknout":["czech"],"poloha":["czech"],"polynom":["czech"],"pomalu":["czech"],"pominout":["czech"],"pomlka":["czech"],"pomoc":["czech"],"pomsta":["czech"],"pomyslet":["czech"],"ponechat":["czech"],"ponorka":["czech"],"ponurost":["czech"],"popadat":["czech"],"popel":["czech"],"popisek":["czech"],"poplach":["czech"],"poprosit":["czech"],"popsat":["czech"],"popud":["czech"],"poradce":["czech"],"porce":["czech"],"porod":["czech"],"porucha":["czech"],"poryv":["czech"],"posadit":["czech"],"posed":["czech"],"posila":["czech"],"poskok":["czech"],"poslanec":["czech"],"posoudit":["czech"],"pospolu":["czech"],"postava":["czech"],"posudek":["czech"],"posyp":["czech"],"potah":["czech"],"potkan":["czech"],"potlesk":["czech"],"potomek":["czech"],"potrava":["czech"],"potupa":["czech"],"potvora":["czech"],"poukaz":["czech"],"pouto":["czech"],"pouzdro":["czech"],"povaha":["czech"],"povidla":["czech"],"povlak":["czech"],"povoz":["czech"],"povrch":["czech"],"povstat":["czech"],"povyk":["czech"],"povzdech":["czech"],"pozdrav":["czech"],"pozemek":["czech"],"poznatek":["czech"],"pozor":["czech"],"pozvat":["czech"],"pracovat":["czech"],"prahory":["czech"],"praktika":["czech"],"prales":["czech"],"praotec":["czech"],"praporek":["czech"],"prase":["czech"],"pravda":["czech"],"princip":["czech"],"prkno":["czech"],"probudit":["czech"],"procento":["czech"],"prodej":["czech"],"profese":["czech"],"prohra":["czech"],"projekt":["czech"],"prolomit":["czech"],"promile":["czech"],"pronikat":["czech"],"propad":["czech"],"prorok":["czech"],"prosba":["czech"],"proton":["czech"],"proutek":["czech"],"provaz":["czech"],"prskavka":["czech"],"prsten":["czech"],"prudkost":["czech"],"prut":["czech"],"prvek":["czech"],"prvohory":["czech"],"psanec":["czech"],"psovod":["czech"],"pstruh":["czech"],"ptactvo":["czech"],"puberta":["czech"],"puch":["czech"],"pudl":["czech"],"pukavec":["czech"],"puklina":["czech"],"pukrle":["czech"],"pult":["czech"],"pumpa":["czech"],"punc":["czech"],"pupen":["czech"],"pusa":["czech"],"pusinka":["czech"],"pustina":["czech"],"putovat":["czech"],"putyka":["czech"],"pyramida":["czech"],"pysk":["czech"],"pytel":["czech"],"racek":["czech"],"rachot":["czech"],"radiace":["czech"],"radnice":["czech"],"radon":["czech"],"raft":["czech"],"ragby":["czech"],"raketa":["czech"],"rakovina":["czech"],"rameno":["czech"],"rampouch":["czech"],"rande":["czech"],"rarach":["czech"],"rarita":["czech"],"rasovna":["czech"],"rastr":["czech"],"ratolest":["czech"],"razance":["czech"],"razidlo":["czech"],"reagovat":["czech"],"reakce":["czech"],"recept":["czech"],"redaktor":["czech"],"referent":["czech"],"reflex":["czech"],"rejnok":["czech"],"reklama":["czech"],"rekord":["czech"],"rekrut":["czech"],"rektor":["czech"],"reputace":["czech"],"revize":["czech","turkish"],"revma":["czech"],"revolver":["czech"],"rezerva":["czech"],"riskovat":["czech"],"riziko":["czech"],"robotika":["czech"],"rodokmen":["czech"],"rohovka":["czech"],"rokle":["czech"],"rokoko":["czech"],"romaneto":["czech"],"ropovod":["czech"],"ropucha":["czech"],"rorejs":["czech"],"rosol":["czech"],"rostlina":["czech"],"rotmistr":["czech"],"rotoped":["czech"],"rotunda":["czech"],"roubenka":["czech"],"roucho":["czech"],"roup":["czech"],"roura":["czech"],"rovina":["czech"],"rovnice":["czech"],"rozbor":["czech"],"rozchod":["czech"],"rozdat":["czech"],"rozeznat":["czech"],"rozhodce":["czech"],"rozinka":["czech"],"rozjezd":["czech"],"rozkaz":["czech"],"rozloha":["czech"],"rozmar":["czech"],"rozpad":["czech"],"rozruch":["czech"],"rozsah":["czech"],"roztok":["czech"],"rozum":["czech"],"rozvod":["czech"],"rubrika":["czech"],"ruchadlo":["czech"],"rukavice":["czech"],"rukopis":["czech"],"ryba":["czech"],"rybolov":["czech"],"rychlost":["czech"],"rydlo":["czech"],"rypadlo":["czech"],"rytina":["czech"],"ryzost":["czech"],"sadista":["czech"],"sahat":["czech"],"sako":["czech"],"samec":["czech"],"samizdat":["czech"],"samota":["czech"],"sanitka":["czech"],"sardinka":["czech"],"sasanka":["czech"],"satelit":["czech"],"sazba":["czech"],"sazenice":["czech"],"sbor":["czech"],"schovat":["czech"],"sebranka":["czech"],"secese":["czech"],"sedadlo":["czech"],"sediment":["czech"],"sedlo":["czech"],"sehnat":["czech"],"sejmout":["czech"],"sekera":["czech"],"sekta":["czech"],"sekunda":["czech"],"sekvoje":["czech"],"semeno":["czech"],"seno":["czech"],"servis":["czech","turkish"],"sesadit":["czech"],"seshora":["czech"],"seskok":["czech"],"seslat":["czech"],"sestra":["czech"],"sesuv":["czech"],"sesypat":["czech"],"setba":["czech"],"setina":["czech"],"setkat":["czech"],"setnout":["czech"],"setrvat":["czech"],"sever":["czech"],"seznam":["czech"],"shoda":["czech"],"shrnout":["czech"],"sifon":["czech"],"silnice":["czech"],"sirka":["czech"],"sirotek":["czech"],"sirup":["czech"],"situace":["czech"],"skafandr":["czech"],"skalisko":["czech"],"skanzen":["czech"],"skaut":["czech"],"skeptik":["czech"],"skica":["czech"],"skladba":["czech"],"sklenice":["czech"],"sklo":["czech"],"skluz":["czech"],"skoba":["czech"],"skokan":["czech"],"skoro":["czech"],"skripta":["czech"],"skrz":["czech"],"skupina":["czech"],"skvost":["czech"],"skvrna":["czech"],"slabika":["czech"],"sladidlo":["czech"],"slanina":["czech"],"slast":["czech"],"slavnost":["czech"],"sledovat":["czech"],"slepec":["czech"],"sleva":["czech"],"slezina":["czech"],"slib":["czech"],"slina":["czech"],"sliznice":["czech"],"slon":["czech"],"sloupek":["czech"],"slovo":["czech"],"sluch":["czech"],"sluha":["czech"],"slunce":["czech"],"slupka":["czech"],"slza":["czech"],"smaragd":["czech"],"smetana":["czech"],"smilstvo":["czech"],"smlouva":["czech"],"smog":["czech"],"smrad":["czech"],"smrk":["czech"],"smrtka":["czech"],"smutek":["czech"],"smysl":["czech"],"snad":["czech"],"snaha":["czech"],"snob":["czech"],"sobota":["czech"],"socha":["czech"],"sodovka":["czech"],"sokol":["czech"],"sopka":["czech"],"sotva":["czech"],"souboj":["czech"],"soucit":["czech"],"soudce":["czech"],"souhlas":["czech"],"soulad":["czech"],"soumrak":["czech"],"souprava":["czech"],"soused":["czech"],"soutok":["czech"],"souviset":["czech"],"spalovna":["czech"],"spasitel":["czech"],"spis":["czech"],"splav":["czech"],"spodek":["czech"],"spojenec":["czech"],"spolu":["czech"],"sponzor":["czech"],"spornost":["czech"],"spousta":["czech"],"sprcha":["czech"],"spustit":["czech"],"sranda":["czech"],"sraz":["czech"],"srdce":["czech"],"srna":["czech"],"srnec":["czech"],"srovnat":["czech"],"srpen":["czech"],"srst":["czech"],"srub":["czech"],"stanice":["czech"],"starosta":["czech"],"statika":["czech"],"stavba":["czech"],"stehno":["czech"],"stezka":["czech"],"stodola":["czech"],"stolek":["czech"],"stopa":["czech"],"storno":["czech"],"stoupat":["czech"],"strach":["czech"],"stres":["czech"],"strhnout":["czech"],"strom":["czech"],"struna":["czech"],"studna":["czech"],"stupnice":["czech"],"stvol":["czech"],"styk":["czech"],"subjekt":["czech"],"subtropy":["czech"],"suchar":["czech"],"sudost":["czech"],"sukno":["czech"],"sundat":["czech"],"sunout":["czech"],"surikata":["czech"],"surovina":["czech"],"svah":["czech"],"svalstvo":["czech"],"svetr":["czech"],"svatba":["czech"],"svazek":["czech"],"svisle":["czech"],"svitek":["czech"],"svoboda":["czech"],"svodidlo":["czech"],"svorka":["czech"],"svrab":["czech"],"sykavka":["czech"],"sykot":["czech"],"synek":["czech"],"synovec":["czech"],"sypat":["czech"],"sypkost":["czech"],"syrovost":["czech"],"sysel":["czech"],"sytost":["czech"],"tabletka":["czech"],"tabule":["czech"],"tahoun":["czech"],"tajemno":["czech"],"tajfun":["czech"],"tajga":["czech"],"tajit":["czech"],"tajnost":["czech"],"taktika":["czech"],"tamhle":["czech"],"tampon":["czech","turkish"],"tancovat":["czech"],"tanec":["czech"],"tanker":["czech"],"tapeta":["czech"],"tavenina":["czech"],"tazatel":["czech"],"technika":["czech"],"tehdy":["czech"],"tekutina":["czech"],"telefon":["czech"],"temnota":["czech"],"tendence":["czech"],"tenista":["czech"],"tenor":["czech"],"teplota":["czech"],"tepna":["czech"],"teprve":["czech"],"terapie":["czech"],"termoska":["czech"],"textil":["czech"],"ticho":["czech"],"tiskopis":["czech"],"titulek":["czech"],"tkadlec":["czech"],"tkanina":["czech"],"tlapka":["czech"],"tleskat":["czech"],"tlukot":["czech"],"tlupa":["czech"],"tmel":["czech"],"toaleta":["czech"],"topinka":["czech"],"topol":["czech"],"torzo":["czech"],"touha":["czech"],"toulec":["czech"],"tradice":["czech"],"traktor":["czech"],"tramp":["czech"],"trasa":["czech"],"traverza":["czech"],"trefit":["czech"],"trest":["czech"],"trezor":["czech"],"trhavina":["czech"],"trhlina":["czech"],"trochu":["czech"],"trojice":["czech"],"troska":["czech"],"trouba":["czech"],"trpce":["czech"],"trpitel":["czech"],"trpkost":["czech"],"trubec":["czech"],"truchlit":["czech"],"truhlice":["czech"],"trus":["czech"],"trvat":["czech"],"tudy":["czech"],"tuhnout":["czech"],"tuhost":["czech"],"tundra":["czech"],"turista":["czech"],"turnaj":["czech"],"tuzemsko":["czech"],"tvaroh":["czech"],"tvorba":["czech"],"tvrdost":["czech"],"tvrz":["czech"],"tygr":["czech"],"tykev":["czech"],"ubohost":["czech"],"uboze":["czech"],"ubrat":["czech"],"ubrousek":["czech"],"ubrus":["czech"],"ubytovna":["czech"],"ucho":["czech"],"uctivost":["czech"],"udivit":["czech"],"uhradit":["czech"],"ujednat":["czech"],"ujistit":["czech"],"ujmout":["czech"],"ukazatel":["czech"],"uklidnit":["czech"],"uklonit":["czech"],"ukotvit":["czech"],"ukrojit":["czech"],"ulice":["czech"],"ulita":["czech"],"ulovit":["czech"],"umyvadlo":["czech"],"unavit":["czech"],"uniforma":["czech"],"uniknout":["czech"],"upadnout":["czech"],"uplatnit":["czech"],"uplynout":["czech"],"upoutat":["czech"],"upravit":["czech"],"uran":["czech"],"urazit":["czech"],"usednout":["czech"],"usilovat":["czech"],"usmrtit":["czech"],"usnadnit":["czech"],"usnout":["czech"],"usoudit":["czech"],"ustlat":["czech"],"ustrnout":["czech"],"utahovat":["czech"],"utkat":["czech"],"utlumit":["czech"],"utonout":["czech"],"utopenec":["czech"],"utrousit":["czech"],"uvalit":["czech"],"uvolnit":["czech"],"uvozovka":["czech"],"uzdravit":["czech"],"uzel":["czech"],"uzenina":["czech"],"uzlina":["czech"],"uznat":["czech"],"vagon":["czech","turkish"],"valcha":["czech"],"valoun":["czech"],"vana":["czech"],"vandal":["czech"],"vanilka":["czech"],"varan":["czech"],"varhany":["czech"],"varovat":["czech"],"vcelku":["czech"],"vchod":["czech"],"vdova":["czech"],"vedro":["czech"],"vegetace":["czech"],"vejce":["czech"],"velbloud":["czech"],"veletrh":["czech"],"velitel":["czech"],"velmoc":["czech"],"velryba":["czech"],"venkov":["czech"],"veranda":["czech"],"verze":["czech"],"veselka":["czech"],"veskrze":["czech"],"vesnice":["czech"],"vespodu":["czech"],"vesta":["czech"],"veterina":["czech"],"veverka":["czech"],"vibrace":["czech"],"vichr":["czech"],"videohra":["czech"],"vidina":["czech"],"vidle":["czech"],"vila":["czech"],"vinice":["czech"],"viset":["czech"],"vitalita":["czech"],"vize":["czech"],"vizitka":["czech"],"vjezd":["czech"],"vklad":["czech"],"vkus":["czech"],"vlajka":["czech"],"vlak":["czech"],"vlasec":["czech"],"vlevo":["czech"],"vlhkost":["czech"],"vliv":["czech"],"vlnovka":["czech"],"vloupat":["czech"],"vnucovat":["czech"],"vnuk":["czech"],"voda":["czech"],"vodivost":["czech"],"vodoznak":["czech"],"vodstvo":["czech"],"vojensky":["czech"],"vojna":["czech"],"vojsko":["czech"],"volant":["czech"],"volba":["czech"],"volit":["czech"],"volno":["czech"],"voskovka":["czech"],"vozidlo":["czech"],"vozovna":["czech"],"vpravo":["czech"],"vrabec":["czech"],"vracet":["czech"],"vrah":["czech"],"vrata":["czech"],"vrba":["czech"],"vrcholek":["czech"],"vrhat":["czech"],"vrstva":["czech"],"vrtule":["czech"],"vsadit":["czech"],"vstoupit":["czech"],"vstup":["czech"],"vtip":["czech"],"vybavit":["czech"],"vybrat":["czech"],"vychovat":["czech"],"vydat":["czech"],"vydra":["czech"],"vyfotit":["czech"],"vyhledat":["czech"],"vyhnout":["czech"],"vyhodit":["czech"],"vyhradit":["czech"],"vyhubit":["czech"],"vyjasnit":["czech"],"vyjet":["czech"],"vyjmout":["czech"],"vyklopit":["czech"],"vykonat":["czech"],"vylekat":["czech"],"vymazat":["czech"],"vymezit":["czech"],"vymizet":["czech"],"vymyslet":["czech"],"vynechat":["czech"],"vynikat":["czech"],"vynutit":["czech"],"vypadat":["czech"],"vyplatit":["czech"],"vypravit":["czech"],"vypustit":["czech"],"vyrazit":["czech"],"vyrovnat":["czech"],"vyrvat":["czech"],"vyslovit":["czech"],"vysoko":["czech"],"vystavit":["czech"],"vysunout":["czech"],"vysypat":["czech"],"vytasit":["czech"],"vytesat":["czech"],"vytratit":["czech"],"vyvinout":["czech"],"vyvolat":["czech"],"vyvrhel":["czech"],"vyzdobit":["czech"],"vyznat":["czech"],"vzadu":["czech"],"vzbudit":["czech"],"vzchopit":["czech"],"vzdor":["czech"],"vzduch":["czech"],"vzdychat":["czech"],"vzestup":["czech"],"vzhledem":["czech"],"vzkaz":["czech"],"vzlykat":["czech"],"vznik":["czech"],"vzorek":["czech"],"vzpoura":["czech"],"vztah":["czech"],"vztek":["czech"],"xylofon":["czech"],"zabrat":["czech"],"zabydlet":["czech"],"zachovat":["czech"],"zadarmo":["czech"],"zadusit":["czech"],"zafoukat":["czech"],"zahltit":["czech"],"zahodit":["czech"],"zahrada":["czech"],"zahynout":["czech"],"zajatec":["czech"],"zajet":["czech"],"zajistit":["czech"],"zaklepat":["czech"],"zakoupit":["czech"],"zalepit":["czech"],"zamezit":["czech"],"zamotat":["czech"],"zamyslet":["czech"],"zanechat":["czech"],"zanikat":["czech"],"zaplatit":["czech"],"zapojit":["czech"],"zapsat":["czech"],"zarazit":["czech"],"zastavit":["czech"],"zasunout":["czech"],"zatajit":["czech"],"zatemnit":["czech"],"zatknout":["czech"],"zaujmout":["czech"],"zavalit":["czech"],"zavelet":["czech"],"zavinit":["czech"],"zavolat":["czech"],"zavrtat":["czech"],"zazvonit":["czech"],"zbavit":["czech"],"zbrusu":["czech"],"zbudovat":["czech"],"zbytek":["czech"],"zdaleka":["czech"],"zdarma":["czech"],"zdatnost":["czech"],"zdivo":["czech"],"zdobit":["czech"],"zdroj":["czech"],"zdvih":["czech"],"zdymadlo":["czech"],"zelenina":["czech"],"zeman":["czech"],"zemina":["czech"],"zeptat":["czech"],"zezadu":["czech"],"zezdola":["czech"],"zhatit":["czech"],"zhltnout":["czech"],"zhluboka":["czech"],"zhotovit":["czech"],"zhruba":["czech"],"zima":["czech"],"zimnice":["czech"],"zjemnit":["czech"],"zklamat":["czech"],"zkoumat":["czech"],"zkratka":["czech"],"zkumavka":["czech"],"zlato":["czech"],"zlehka":["czech"],"zloba":["czech"],"zlom":["czech"],"zlost":["czech"],"zlozvyk":["czech"],"zmapovat":["czech"],"zmar":["czech"],"zmatek":["czech"],"zmije":["czech"],"zmizet":["czech"],"zmocnit":["czech"],"zmodrat":["czech"],"zmrzlina":["czech"],"zmutovat":["czech"],"znak":["czech"],"znalost":["czech"],"znamenat":["czech"],"znovu":["czech"],"zobrazit":["czech"],"zotavit":["czech"],"zoubek":["czech"],"zoufale":["czech"],"zplodit":["czech"],"zpomalit":["czech"],"zprava":["czech"],"zprostit":["czech"],"zprudka":["czech"],"zprvu":["czech"],"zrada":["czech"],"zranit":["czech"],"zrcadlo":["czech"],"zrnitost":["czech"],"zrno":["czech"],"zrovna":["czech"],"zrychlit":["czech"],"zrzavost":["czech"],"zticha":["czech"],"ztratit":["czech"],"zubovina":["czech"],"zubr":["czech"],"zvednout":["czech"],"zvenku":["czech"],"zvesela":["czech"],"zvon":["czech"],"zvrat":["czech"],"zvukovod":["czech"],"zvyk":["czech"],"abandon":["english","french"],"ability":["english"],"able":["english"],"about":["english"],"above":["english"],"absent":["english"],"absorb":["english"],"abstract":["english"],"absurd":["english"],"abuse":["english"],"access":["english"],"accident":["english"],"account":["english"],"accuse":["english"],"achieve":["english"],"acid":["english"],"acoustic":["english"],"acquire":["english"],"across":["english"],"act":["english"],"action":["english"],"actor":["english"],"actress":["english"],"actual":["english"],"adapt":["english"],"add":["english"],"addict":["english"],"address":["english"],"adjust":["english"],"admit":["english"],"adult":["english"],"advance":["english"],"advice":["english"],"aerobic":["english"],"affair":["english"],"afford":["english"],"afraid":["english"],"again":["english"],"age":["english"],"agent":["english"],"agree":["english"],"ahead":["english"],"aim":["english"],"air":["english"],"airport":["english"],"aisle":["english"],"alarm":["english"],"album":["english"],"alcohol":["english"],"alert":["english"],"alien":["english"],"all":["english"],"alley":["english"],"allow":["english"],"almost":["english"],"alone":["english"],"alpha":["english"],"already":["english"],"also":["english"],"alter":["english"],"always":["english"],"amateur":["english","french"],"amazing":["english"],"among":["english"],"amount":["english"],"amused":["english"],"analyst":["english"],"anchor":["english"],"ancient":["english"],"anger":["english"],"angle":["english","french"],"angry":["english"],"animal":["english","french"],"ankle":["english"],"announce":["english"],"annual":["english"],"another":["english"],"answer":["english"],"antenna":["english"],"antique":["english"],"anxiety":["english"],"any":["english"],"apart":["english"],"apology":["english"],"appear":["english"],"apple":["english"],"approve":["english"],"april":["english"],"arch":["english"],"arctic":["english"],"area":["english"],"arena":["english","turkish"],"argue":["english"],"arm":["english"],"armed":["english"],"armor":["english"],"army":["english"],"around":["english"],"arrange":["english"],"arrest":["english"],"arrive":["english"],"arrow":["english"],"art":["english"],"artefact":["english"],"artist":["english","turkish"],"artwork":["english"],"ask":["english"],"aspect":["english","french"],"assault":["english"],"asset":["english"],"assist":["english"],"assume":["english"],"asthma":["english"],"athlete":["english"],"atom":["english","turkish"],"attack":["english"],"attend":["english"],"attitude":["english"],"attract":["english"],"auction":["english"],"audit":["english"],"august":["english"],"aunt":["english"],"author":["english"],"auto":["english"],"autumn":["english"],"average":["english"],"avocado":["english"],"avoid":["english"],"awake":["english"],"aware":["english"],"away":["english"],"awesome":["english"],"awful":["english"],"awkward":["english"],"axis":["english"],"baby":["english"],"bachelor":["english"],"bacon":["english"],"badge":["english","french"],"bag":["english"],"balance":["english"],"balcony":["english"],"ball":["english"],"bamboo":["english"],"banana":["english"],"banner":["english"],"bar":["english"],"barely":["english"],"bargain":["english"],"barrel":["english"],"base":["english"],"basic":["english"],"basket":["english"],"battle":["english"],"beach":["english"],"bean":["english"],"beauty":["english"],"because":["english"],"become":["english"],"beef":["english"],"before":["english"],"begin":["english"],"behave":["english"],"behind":["english"],"believe":["english"],"below":["english"],"belt":["english"],"bench":["english"],"benefit":["english"],"best":["english"],"betray":["english"],"better":["english"],"between":["english"],"beyond":["english"],"bicycle":["english","french"],"bid":["english"],"bike":["english"],"bind":["english"],"biology":["english"],"bird":["english"],"birth":["english"],"bitter":["english"],"black":["english"],"blade":["english"],"blame":["english"],"blanket":["english"],"blast":["english"],"bleak":["english"],"bless":["english"],"blind":["english"],"blood":["english"],"blossom":["english"],"blouse":["english"],"blue":["english"],"blur":["english"],"blush":["english"],"board":["english"],"boat":["english"],"body":["english"],"boil":["english"],"bomb":["english"],"bone":["english"],"bonus":["english","french"],"book":["english"],"boost":["english"],"border":["english"],"boring":["english"],"borrow":["english"],"boss":["english"],"bottom":["english"],"bounce":["english"],"box":["english"],"boy":["english"],"bracket":["english"],"brain":["english"],"brand":["english"],"brass":["english"],"brave":["english","french"],"bread":["english"],"breeze":["english"],"brick":["english"],"bridge":["english"],"brief":["english"],"bright":["english"],"bring":["english"],"brisk":["english"],"broccoli":["english"],"broken":["english"],"bronze":["english"],"broom":["english"],"brother":["english"],"brown":["english"],"brush":["english"],"bubble":["english"],"buddy":["english"],"budget":["english"],"buffalo":["english"],"build":["english"],"bulb":["english"],"bulk":["english"],"bullet":["english"],"bundle":["english"],"bunker":["english"],"burden":["english"],"burger":["english"],"burst":["english"],"bus":["english"],"business":["english"],"busy":["english"],"butter":["english"],"buyer":["english"],"buzz":["english"],"cabbage":["english"],"cabin":["english"],"cable":["english"],"cactus":["english"],"cage":["english"],"cake":["english"],"call":["english"],"calm":["english"],"camera":["english"],"camp":["english"],"can":["english"],"canal":["english","french"],"cancel":["english"],"candy":["english"],"cannon":["english"],"canoe":["english"],"canvas":["english"],"canyon":["english"],"capable":["english","french"],"capital":["english"],"captain":["english"],"car":["english"],"carbon":["english"],"card":["english"],"cargo":["english"],"carpet":["english"],"carry":["english"],"cart":["english"],"case":["english"],"cash":["english"],"casino":["english"],"castle":["english"],"casual":["english"],"cat":["english"],"catalog":["english"],"catch":["english"],"category":["english"],"cattle":["english"],"caught":["english"],"cause":["english"],"caution":["english","french"],"cave":["english"],"ceiling":["english"],"celery":["english"],"cement":["english"],"census":["english"],"century":["english"],"cereal":["english"],"certain":["english"],"chair":["english"],"chalk":["english"],"champion":["english"],"change":["english"],"chaos":["english"],"chapter":["english"],"charge":["english"],"chase":["english"],"chat":["english"],"cheap":["english"],"check":["english"],"cheese":["english"],"chef":["english"],"cherry":["english"],"chest":["english"],"chicken":["english"],"chief":["english"],"child":["english"],"chimney":["english"],"choice":["english"],"choose":["english"],"chronic":["english"],"chuckle":["english"],"chunk":["english"],"churn":["english"],"cigar":["english"],"cinnamon":["english"],"circle":["english"],"citizen":["english"],"city":["english"],"civil":["english","french"],"claim":["english"],"clap":["english"],"clarify":["english"],"claw":["english"],"clay":["english"],"clean":["english"],"clerk":["english"],"clever":["english"],"click":["english"],"client":["english","french"],"cliff":["english"],"climb":["english"],"clinic":["english"],"clip":["english"],"clock":["english"],"clog":["english"],"close":["english"],"cloth":["english"],"cloud":["english"],"clown":["english"],"club":["english"],"clump":["english"],"cluster":["english"],"clutch":["english"],"coach":["english"],"coast":["english"],"coconut":["english"],"code":["english"],"coffee":["english"],"coil":["english"],"coin":["english"],"collect":["english"],"color":["english"],"column":["english"],"combine":["english"],"come":["english"],"comfort":["english"],"comic":["english"],"common":["english"],"company":["english"],"concert":["english","french"],"conduct":["english"],"confirm":["english"],"congress":["english"],"connect":["english"],"consider":["english"],"control":["english"],"convince":["english"],"cook":["english"],"cool":["english"],"copper":["english"],"copy":["english"],"coral":["english"],"core":["english"],"corn":["english"],"correct":["english","french"],"cost":["english"],"cotton":["english"],"couch":["english"],"country":["english"],"couple":["english"],"course":["english"],"cousin":["english"],"cover":["english"],"coyote":["english","french"],"crack":["english"],"cradle":["english"],"craft":["english"],"cram":["english"],"crane":["english"],"crash":["english"],"crater":["english"],"crawl":["english"],"crazy":["english"],"cream":["english"],"credit":["english"],"creek":["english"],"crew":["english"],"cricket":["english"],"crime":["english"],"crisp":["english"],"critic":["english"],"crop":["english"],"cross":["english"],"crouch":["english"],"crowd":["english"],"crucial":["english","french"],"cruel":["english","french"],"cruise":["english"],"crumble":["english"],"crunch":["english"],"crush":["english"],"cry":["english"],"crystal":["english"],"cube":["english"],"culture":["english"],"cup":["english"],"cupboard":["english"],"curious":["english"],"current":["english"],"curtain":["english"],"curve":["english"],"cushion":["english"],"custom":["english"],"cute":["english"],"cycle":["english","french"],"dad":["english"],"damage":["english"],"damp":["english"],"dance":["english"],"danger":["english","french"],"daring":["english"],"dash":["english"],"daughter":["english"],"dawn":["english"],"day":["english"],"deal":["english"],"debate":["english"],"debris":["english"],"decade":["english"],"december":["english"],"decide":["english"],"decline":["english"],"decorate":["english"],"decrease":["english"],"deer":["english"],"defense":["english"],"define":["english"],"defy":["english"],"degree":["english"],"delay":["english"],"deliver":["english"],"demand":["english"],"demise":["english"],"denial":["english"],"dentist":["english"],"deny":["english"],"depart":["english"],"depend":["english"],"deposit":["english"],"depth":["english"],"deputy":["english"],"derive":["english"],"describe":["english"],"desert":["english"],"design":["english"],"desk":["english"],"despair":["english"],"destroy":["english"],"detail":["english"],"detect":["english"],"develop":["english"],"device":["english"],"devote":["english"],"diagram":["english"],"dial":["english"],"diamond":["english"],"diary":["english"],"dice":["english"],"diesel":["english"],"diet":["english"],"differ":["english"],"digital":["english","french"],"dignity":["english"],"dilemma":["english"],"dinner":["english"],"dinosaur":["english"],"direct":["english"],"dirt":["english"],"disagree":["english"],"discover":["english"],"disease":["english"],"dish":["english"],"dismiss":["english"],"disorder":["english"],"display":["english"],"distance":["english","french"],"divert":["english"],"divide":["english"],"divorce":["english"],"dizzy":["english"],"doctor":["english"],"document":["english"],"dog":["english"],"doll":["english"],"dolphin":["english"],"domain":["english"],"donate":["english"],"donkey":["english"],"donor":["english"],"door":["english"],"dose":["english"],"double":["english","french"],"dove":["english"],"draft":["english"],"dragon":["english","french"],"drama":["english"],"drastic":["english"],"draw":["english"],"dream":["english"],"dress":["english"],"drift":["english"],"drill":["english"],"drink":["english"],"drip":["english"],"drive":["english"],"drop":["english"],"drum":["english"],"dry":["english"],"duck":["english"],"dumb":["english"],"dune":["english"],"during":["english"],"dust":["english"],"dutch":["english"],"duty":["english"],"dwarf":["english"],"dynamic":["english"],"eager":["english"],"eagle":["english"],"early":["english"],"earn":["english"],"earth":["english"],"easily":["english"],"east":["english"],"easy":["english"],"echo":["english"],"ecology":["english"],"economy":["english"],"edge":["english"],"edit":["english"],"educate":["english"],"effort":["english","french"],"egg":["english"],"eight":["english"],"either":["english"],"elbow":["english"],"elder":["english"],"electric":["english"],"elegant":["english"],"element":["english"],"elephant":["english"],"elevator":["english"],"elite":["english"],"else":["english"],"embark":["english"],"embody":["english"],"embrace":["english"],"emerge":["english"],"emotion":["english"],"employ":["english"],"empower":["english"],"empty":["english"],"enable":["english"],"enact":["english"],"end":["english"],"endless":["english"],"endorse":["english"],"enemy":["english"],"energy":["english"],"enforce":["english"],"engage":["english"],"engine":["english"],"enhance":["english"],"enjoy":["english"],"enlist":["english"],"enough":["english"],"enrich":["english"],"enroll":["english"],"ensure":["english"],"enter":["english"],"entire":["english"],"entry":["english"],"envelope":["english"],"episode":["english"],"equal":["english"],"equip":["english"],"era":["english"],"erase":["english"],"erode":["english"],"erosion":["english"],"error":["english"],"erupt":["english"],"escape":["english"],"essay":["english"],"essence":["english","french"],"estate":["english"],"eternal":["english"],"ethics":["english"],"evidence":["english"],"evil":["english"],"evoke":["english"],"evolve":["english"],"exact":["english","french"],"example":["english"],"excess":["english"],"exchange":["english"],"excite":["english"],"exclude":["english"],"excuse":["english","french"],"execute":["english"],"exercise":["english"],"exhaust":["english"],"exhibit":["english"],"exile":["english"],"exist":["english"],"exit":["english"],"exotic":["english"],"expand":["english"],"expect":["english"],"expire":["english"],"explain":["english"],"expose":["english"],"express":["english"],"extend":["english"],"extra":["english"],"eye":["english"],"eyebrow":["english"],"fabric":["english"],"face":["english"],"faculty":["english"],"fade":["english"],"faint":["english"],"faith":["english"],"fall":["english"],"false":["english"],"fame":["english"],"family":["english"],"famous":["english"],"fan":["english"],"fancy":["english"],"fantasy":["english"],"farm":["english"],"fashion":["english"],"fat":["english"],"fatal":["english","french"],"father":["english"],"fatigue":["english","french"],"fault":["english"],"favorite":["english"],"feature":["english"],"february":["english"],"federal":["english","turkish"],"fee":["english"],"feed":["english"],"feel":["english"],"female":["english"],"fence":["english"],"festival":["english","french","turkish"],"fetch":["english"],"fever":["english"],"few":["english"],"fiber":["english"],"fiction":["english"],"field":["english"],"figure":["english","french"],"file":["english"],"film":["english"],"filter":["english"],"final":["english"],"find":["english"],"fine":["english"],"finger":["english"],"finish":["english"],"fire":["english"],"firm":["english"],"first":["english"],"fiscal":["english"],"fish":["english"],"fit":["english"],"fitness":["english"],"fix":["english"],"flag":["english"],"flame":["english"],"flash":["english"],"flat":["english"],"flavor":["english"],"flee":["english"],"flight":["english"],"flip":["english"],"float":["english"],"flock":["english"],"floor":["english"],"flower":["english"],"fluid":["english"],"flush":["english"],"fly":["english"],"foam":["english"],"focus":["english"],"fog":["english"],"foil":["english"],"fold":["english"],"follow":["english"],"food":["english"],"foot":["english"],"force":["english"],"forest":["english"],"forget":["english"],"fork":["english"],"fortune":["english","french"],"forum":["english"],"forward":["english"],"fossil":["english"],"foster":["english"],"found":["english"],"fox":["english"],"fragile":["english","french"],"frame":["english"],"frequent":["english"],"fresh":["english"],"friend":["english"],"fringe":["english"],"frog":["english"],"front":["english"],"frost":["english"],"frown":["english"],"frozen":["english"],"fruit":["english","french"],"fuel":["english"],"fun":["english"],"funny":["english"],"furnace":["english"],"fury":["english"],"future":["english"],"gadget":["english"],"gain":["english"],"galaxy":["english"],"gallery":["english"],"game":["english"],"gap":["english"],"garage":["english"],"garbage":["english"],"garden":["english"],"garlic":["english"],"garment":["english"],"gas":["english"],"gasp":["english"],"gate":["english"],"gather":["english"],"gauge":["english"],"gaze":["english"],"general":["english"],"genius":["english"],"genre":["english"],"gentle":["english"],"genuine":["english"],"gesture":["english"],"ghost":["english"],"giant":["english"],"gift":["english"],"giggle":["english"],"ginger":["english"],"giraffe":["english"],"girl":["english"],"give":["english"],"glad":["english"],"glance":["english"],"glare":["english"],"glass":["english"],"glide":["english"],"glimpse":["english"],"globe":["english","french"],"gloom":["english"],"glory":["english"],"glove":["english"],"glow":["english"],"glue":["english"],"goat":["english"],"goddess":["english"],"gold":["english"],"good":["english"],"goose":["english"],"gorilla":["english"],"gospel":["english"],"gossip":["english"],"govern":["english"],"gown":["english"],"grab":["english"],"grace":["english"],"grain":["english"],"grant":["english"],"grape":["english"],"grass":["english"],"gravity":["english"],"great":["english"],"green":["english"],"grid":["english"],"grief":["english"],"grit":["english"],"grocery":["english"],"group":["english"],"grow":["english"],"grunt":["english"],"guard":["english"],"guess":["english"],"guide":["english","french"],"guilt":["english"],"guitar":["english"],"gun":["english"],"gym":["english"],"habit":["english"],"hair":["english"],"half":["english"],"hammer":["english"],"hamster":["english"],"hand":["english"],"happy":["english"],"harbor":["english"],"hard":["english"],"harsh":["english"],"harvest":["english"],"hat":["english"],"have":["english"],"hawk":["english"],"hazard":["english"],"head":["english"],"health":["english"],"heart":["english"],"heavy":["english"],"hedgehog":["english"],"height":["english"],"hello":["english"],"helmet":["english"],"help":["english"],"hen":["english"],"hero":["english"],"hidden":["english"],"high":["english"],"hill":["english"],"hint":["english"],"hip":["english"],"hire":["english"],"history":["english"],"hobby":["english"],"hockey":["english"],"hold":["english"],"hole":["english"],"holiday":["english"],"hollow":["english"],"home":["english"],"honey":["english"],"hood":["english"],"hope":["english"],"horn":["english"],"horror":["english"],"horse":["english"],"hospital":["english"],"host":["english"],"hotel":["english"],"hour":["english"],"hover":["english"],"hub":["english"],"huge":["english"],"human":["english"],"humble":["english","french"],"humor":["english"],"hundred":["english"],"hungry":["english"],"hunt":["english"],"hurdle":["english"],"hurry":["english"],"hurt":["english"],"husband":["english"],"hybrid":["english"],"ice":["english"],"icon":["english"],"idea":["english"],"identify":["english"],"idle":["english"],"ignore":["english"],"ill":["english"],"illegal":["english"],"illness":["english"],"image":["english","french"],"imitate":["english"],"immense":["english","french"],"immune":["english"],"impact":["english","french"],"impose":["english"],"improve":["english"],"impulse":["english"],"inch":["english"],"include":["english"],"income":["english"],"increase":["english"],"index":["english"],"indicate":["english"],"indoor":["english"],"industry":["english"],"infant":["english"],"inflict":["english"],"inform":["english"],"inhale":["english"],"inherit":["english"],"initial":["english"],"inject":["english"],"injury":["english"],"inmate":["english"],"inner":["english"],"innocent":["english","french"],"input":["english"],"inquiry":["english"],"insane":["english"],"insect":["english"],"inside":["english"],"inspire":["english"],"install":["english"],"intact":["english","french"],"interest":["english"],"into":["english"],"invest":["english"],"invite":["english"],"involve":["english"],"iron":["english"],"island":["english"],"isolate":["english"],"issue":["english"],"item":["english"],"ivory":["english"],"jacket":["english"],"jaguar":["english","french"],"jar":["english"],"jazz":["english"],"jealous":["english"],"jeans":["english"],"jelly":["english"],"jewel":["english"],"job":["english"],"join":["english"],"joke":["english"],"journey":["english"],"joy":["english"],"judge":["english"],"juice":["english"],"jump":["english"],"jungle":["english"],"junior":["english","french"],"junk":["english"],"just":["english"],"kangaroo":["english"],"keen":["english"],"keep":["english"],"ketchup":["english"],"key":["english"],"kick":["english"],"kid":["english"],"kidney":["english"],"kind":["english"],"kingdom":["english"],"kiss":["english"],"kit":["english"],"kitchen":["english"],"kite":["english"],"kitten":["english"],"kiwi":["english"],"knee":["english"],"knife":["english"],"knock":["english"],"know":["english"],"lab":["english"],"label":["english","french"],"labor":["english"],"ladder":["english"],"lady":["english"],"lake":["english"],"lamp":["english"],"language":["english"],"laptop":["english"],"large":["english"],"later":["english"],"latin":["english"],"laugh":["english"],"laundry":["english"],"lava":["english"],"law":["english"],"lawn":["english"],"lawsuit":["english"],"layer":["english"],"lazy":["english"],"leader":["english"],"leaf":["english"],"learn":["english"],"leave":["english"],"lecture":["english","french"],"left":["english"],"leg":["english"],"legal":["english"],"legend":["english"],"leisure":["english"],"lemon":["english"],"lend":["english"],"length":["english"],"lens":["english"],"leopard":["english"],"lesson":["english"],"letter":["english"],"level":["english"],"liar":["english"],"liberty":["english"],"library":["english"],"license":["english"],"life":["english"],"lift":["english"],"light":["english"],"like":["english"],"limb":["english"],"limit":["english"],"link":["english"],"lion":["english"],"liquid":["english"],"list":["english"],"little":["english"],"live":["english"],"lizard":["english"],"load":["english"],"loan":["english"],"lobster":["english"],"local":["english"],"lock":["english"],"logic":["english"],"lonely":["english"],"long":["english"],"loop":["english"],"lottery":["english"],"loud":["english"],"lounge":["english"],"love":["english"],"loyal":["english","french"],"lucky":["english"],"luggage":["english"],"lumber":["english"],"lunar":["english"],"lunch":["english"],"luxury":["english"],"lyrics":["english"],"machine":["english","french"],"mad":["english"],"magic":["english"],"magnet":["english"],"maid":["english"],"mail":["english"],"main":["english"],"major":["english"],"make":["english"],"mammal":["english"],"man":["english"],"manage":["english"],"mandate":["english"],"mango":["english"],"mansion":["english"],"manual":["english"],"maple":["english"],"marble":["english"],"march":["english"],"margin":["english"],"marine":["english"],"market":["english","turkish"],"marriage":["english"],"mask":["english"],"mass":["english"],"master":["english"],"match":["english"],"material":["english"],"math":["english"],"matrix":["english"],"matter":["english"],"maximum":["english"],"maze":["english"],"meadow":["english"],"mean":["english"],"measure":["english"],"meat":["english"],"mechanic":["english"],"medal":["english"],"media":["english"],"melody":["english"],"melt":["english"],"member":["english"],"memory":["english"],"mention":["english"],"menu":["english"],"mercy":["english"],"merge":["english"],"merit":["english"],"merry":["english"],"mesh":["english"],"message":["english"],"metal":["english"],"method":["english"],"middle":["english"],"midnight":["english"],"milk":["english"],"million":["english","french"],"mimic":["english"],"mind":["english"],"minimum":["english"],"minor":["english"],"minute":["english","french"],"miracle":["english","french"],"mirror":["english"],"misery":["english"],"miss":["english"],"mistake":["english"],"mix":["english"],"mixed":["english"],"mixture":["english"],"mobile":["english","french"],"model":["english","turkish"],"modify":["english"],"mom":["english"],"moment":["english"],"monitor":["english"],"monkey":["english"],"monster":["english"],"month":["english"],"moon":["english"],"moral":["english"],"more":["english"],"morning":["english"],"mosquito":["english"],"mother":["english"],"motion":["english"],"motor":["english","turkish"],"mountain":["english"],"mouse":["english"],"move":["english"],"movie":["english"],"much":["english"],"muffin":["english"],"mule":["english"],"multiply":["english"],"muscle":["english","french"],"museum":["english"],"mushroom":["english"],"music":["english"],"must":["english"],"mutual":["english"],"myself":["english"],"mystery":["english"],"myth":["english"],"naive":["english"],"name":["english"],"napkin":["english"],"narrow":["english"],"nasty":["english"],"nation":["english","french"],"nature":["english","french"],"near":["english"],"neck":["english"],"need":["english"],"negative":["english"],"neglect":["english"],"neither":["english"],"nephew":["english"],"nerve":["english"],"nest":["english"],"net":["english"],"network":["english"],"neutral":["english"],"never":["english"],"news":["english"],"next":["english"],"nice":["english"],"night":["english"],"noble":["english","french"],"noise":["english"],"nominee":["english"],"noodle":["english"],"normal":["english","turkish"],"north":["english"],"nose":["english"],"notable":["english","french"],"note":["english"],"nothing":["english"],"notice":["english"],"novel":["english"],"now":["english"],"nuclear":["english"],"number":["english"],"nurse":["english"],"nut":["english"],"oak":["english"],"obey":["english"],"object":["english"],"oblige":["english"],"obscure":["english"],"observe":["english"],"obtain":["english"],"obvious":["english"],"occur":["english"],"ocean":["english"],"october":["english"],"odor":["english"],"off":["english"],"offer":["english"],"office":["english"],"often":["english"],"oil":["english"],"okay":["english"],"old":["english"],"olive":["english"],"olympic":["english"],"omit":["english"],"once":["english"],"one":["english"],"onion":["english"],"online":["english"],"only":["english"],"open":["english"],"opera":["english","turkish"],"opinion":["english","french"],"oppose":["english"],"option":["english"],"orange":["english","french"],"orbit":["english"],"orchard":["english"],"order":["english"],"ordinary":["english"],"organ":["english"],"orient":["english"],"original":["english"],"orphan":["english"],"ostrich":["english"],"other":["english"],"outdoor":["english"],"outer":["english"],"output":["english"],"outside":["english"],"oval":["english"],"oven":["english"],"over":["english"],"own":["english"],"owner":["english"],"oxygen":["english"],"oyster":["english"],"ozone":["english","french"],"pact":["english"],"paddle":["english"],"page":["english"],"pair":["english"],"palace":["english","french"],"palm":["english"],"panda":["english","french","turkish"],"panel":["english","turkish"],"panic":["english"],"panther":["english"],"paper":["english"],"parade":["english"],"parent":["english"],"park":["english"],"parrot":["english"],"party":["english"],"pass":["english"],"patch":["english"],"path":["english"],"patient":["english"],"patrol":["english"],"pattern":["english"],"pause":["english"],"pave":["english"],"payment":["english"],"peace":["english"],"peanut":["english"],"pear":["english"],"peasant":["english"],"pelican":["english"],"pen":["english"],"penalty":["english"],"pencil":["english"],"people":["english"],"pepper":["english"],"perfect":["english"],"permit":["english"],"person":["english"],"pet":["english"],"phone":["english"],"photo":["english"],"phrase":["english","french"],"physical":["english"],"piano":["english","french"],"picnic":["english"],"picture":["english"],"piece":["english"],"pig":["english"],"pigeon":["english"],"pill":["english"],"pilot":["english","turkish"],"pink":["english"],"pioneer":["english"],"pipe":["english"],"pistol":["english"],"pitch":["english"],"pizza":["english","french"],"place":["english"],"planet":["english"],"plastic":["english"],"plate":["english"],"play":["english"],"please":["english"],"pledge":["english"],"pluck":["english"],"plug":["english"],"plunge":["english"],"poem":["english"],"poet":["english"],"point":["english"],"polar":["english"],"pole":["english"],"police":["english"],"pond":["english"],"pony":["english"],"pool":["english"],"popular":["english"],"portion":["english"],"position":["english","french"],"possible":["english"],"post":["english"],"potato":["english"],"pottery":["english"],"poverty":["english"],"powder":["english"],"power":["english"],"practice":["english"],"praise":["english"],"predict":["english"],"prefer":["english"],"prepare":["english"],"present":["english"],"pretty":["english"],"prevent":["english"],"price":["english"],"pride":["english"],"primary":["english"],"print":["english"],"priority":["english"],"prison":["english","french"],"private":["english"],"prize":["english"],"problem":["english","turkish"],"process":["english"],"produce":["english"],"profit":["english"],"program":["english","turkish"],"project":["english"],"promote":["english"],"proof":["english"],"property":["english"],"prosper":["english"],"protect":["english"],"proud":["english"],"provide":["english"],"public":["english","french"],"pudding":["english"],"pull":["english"],"pulp":["english"],"pulse":["english"],"pumpkin":["english"],"punch":["english"],"pupil":["english"],"puppy":["english"],"purchase":["english"],"purity":["english"],"purpose":["english"],"purse":["english"],"push":["english"],"put":["english"],"puzzle":["english","french"],"pyramid":["english"],"quality":["english"],"quantum":["english"],"quarter":["english"],"question":["english","french"],"quick":["english"],"quit":["english"],"quiz":["english"],"quote":["english"],"rabbit":["english"],"raccoon":["english"],"race":["english"],"rack":["english"],"radar":["english","turkish"],"radio":["english"],"rail":["english"],"rain":["english"],"raise":["english"],"rally":["english"],"ramp":["english"],"ranch":["english"],"random":["english"],"range":["english"],"rapid":["english"],"rare":["english"],"rate":["english"],"rather":["english"],"raven":["english"],"raw":["english"],"razor":["english"],"ready":["english"],"real":["english"],"reason":["english"],"rebel":["english"],"rebuild":["english"],"recall":["english"],"receive":["english"],"recipe":["english"],"record":["english"],"recycle":["english"],"reduce":["english"],"reflect":["english"],"reform":["english","turkish"],"refuse":["english"],"region":["english"],"regret":["english"],"regular":["english"],"reject":["english"],"relax":["english"],"release":["english"],"relief":["english","french"],"rely":["english"],"remain":["english"],"remember":["english"],"remind":["english"],"remove":["english"],"render":["english"],"renew":["english"],"rent":["english"],"reopen":["english"],"repair":["english"],"repeat":["english"],"replace":["english"],"report":["english"],"require":["english"],"rescue":["english"],"resemble":["english"],"resist":["english"],"resource":["english"],"response":["english"],"result":["english"],"retire":["english"],"retreat":["english"],"return":["english"],"reunion":["english"],"reveal":["english"],"review":["english"],"reward":["english"],"rhythm":["english"],"rib":["english"],"ribbon":["english"],"rice":["english"],"rich":["english"],"ride":["english"],"ridge":["english"],"rifle":["english"],"right":["english"],"rigid":["english"],"ring":["english"],"riot":["english"],"ripple":["english"],"risk":["english"],"ritual":["english"],"rival":["english","french"],"river":["english"],"road":["english"],"roast":["english"],"robot":["english","turkish"],"robust":["english"],"rocket":["english"],"romance":["english","french"],"roof":["english"],"rookie":["english"],"room":["english"],"rose":["english"],"rotate":["english"],"rough":["english"],"round":["english"],"route":["english"],"royal":["english"],"rubber":["english"],"rude":["english"],"rug":["english"],"rule":["english"],"run":["english"],"runway":["english"],"rural":["english"],"sad":["english"],"saddle":["english"],"sadness":["english"],"safe":["english"],"sail":["english"],"salad":["english"],"salmon":["english"],"salon":["english","french","turkish"],"salt":["english"],"salute":["english"],"same":["english"],"sample":["english"],"sand":["english"],"satisfy":["english"],"satoshi":["english"],"sauce":["english"],"sausage":["english"],"save":["english"],"say":["english"],"scale":["english"],"scan":["english"],"scare":["english"],"scatter":["english"],"scene":["english"],"scheme":["english"],"school":["english"],"science":["english","french"],"scissors":["english"],"scorpion":["english"],"scout":["english"],"scrap":["english"],"screen":["english"],"script":["english"],"scrub":["english"],"sea":["english"],"search":["english"],"season":["english"],"seat":["english"],"second":["english"],"secret":["english"],"section":["english"],"security":["english"],"seed":["english"],"seek":["english"],"segment":["english"],"select":["english"],"sell":["english"],"seminar":["english"],"senior":["english"],"sense":["english"],"sentence":["english","french"],"series":["english"],"service":["english","french"],"session":["english"],"settle":["english"],"setup":["english"],"seven":["english"],"shadow":["english"],"shaft":["english"],"shallow":["english"],"share":["english"],"shed":["english"],"shell":["english"],"sheriff":["english"],"shield":["english"],"shift":["english"],"shine":["english"],"ship":["english"],"shiver":["english"],"shock":["english"],"shoe":["english"],"shoot":["english"],"shop":["english"],"short":["english"],"shoulder":["english"],"shove":["english"],"shrimp":["english"],"shrug":["english"],"shuffle":["english"],"shy":["english"],"sibling":["english"],"sick":["english"],"side":["english"],"siege":["english"],"sight":["english"],"sign":["english"],"silent":["english"],"silk":["english"],"silly":["english"],"silver":["english"],"similar":["english"],"simple":["english","french"],"since":["english"],"sing":["english"],"siren":["english"],"sister":["english"],"situate":["english"],"six":["english"],"size":["english"],"skate":["english"],"sketch":["english"],"ski":["english"],"skill":["english"],"skin":["english"],"skirt":["english"],"skull":["english"],"slab":["english"],"slam":["english"],"sleep":["english"],"slender":["english"],"slice":["english"],"slide":["english"],"slight":["english"],"slim":["english"],"slogan":["english","turkish"],"slot":["english"],"slow":["english"],"slush":["english"],"small":["english"],"smart":["english"],"smile":["english"],"smoke":["english"],"smooth":["english"],"snack":["english"],"snake":["english"],"snap":["english"],"sniff":["english"],"snow":["english"],"soap":["english"],"soccer":["english"],"social":["english","french"],"sock":["english"],"soda":["english"],"soft":["english"],"solar":["english"],"soldier":["english"],"solid":["english"],"solution":["english"],"solve":["english"],"someone":["english"],"song":["english"],"soon":["english"],"sorry":["english"],"sort":["english"],"soul":["english"],"sound":["english"],"soup":["english"],"source":["english","french"],"south":["english"],"space":["english"],"spare":["english"],"spatial":["english","french"],"spawn":["english"],"speak":["english"],"special":["english"],"speed":["english"],"spell":["english"],"spend":["english"],"sphere":["english"],"spice":["english"],"spider":["english"],"spike":["english"],"spin":["english"],"spirit":["english"],"split":["english"],"spoil":["english"],"sponsor":["english","turkish"],"spoon":["english"],"sport":["english"],"spot":["english"],"spray":["english"],"spread":["english"],"spring":["english"],"spy":["english"],"square":["english"],"squeeze":["english"],"squirrel":["english"],"stable":["english","french"],"stadium":["english"],"staff":["english"],"stage":["english"],"stairs":["english"],"stamp":["english"],"stand":["english"],"start":["english"],"state":["english"],"stay":["english"],"steak":["english"],"steel":["english"],"stem":["english"],"step":["english"],"stereo":["english"],"stick":["english"],"still":["english"],"sting":["english"],"stock":["english"],"stomach":["english"],"stone":["english"],"stool":["english"],"story":["english"],"stove":["english"],"strategy":["english"],"street":["english"],"strike":["english"],"strong":["english"],"struggle":["english"],"student":["english"],"stuff":["english"],"stumble":["english"],"style":["english"],"subject":["english"],"submit":["english"],"subway":["english"],"success":["english"],"such":["english"],"sudden":["english"],"suffer":["english"],"sugar":["english"],"suggest":["english"],"suit":["english"],"summer":["english"],"sun":["english"],"sunny":["english"],"sunset":["english"],"super":["english"],"supply":["english"],"supreme":["english"],"sure":["english"],"surface":["english","french"],"surge":["english"],"surprise":["english","french"],"surround":["english"],"survey":["english"],"suspect":["english","french"],"sustain":["english"],"swallow":["english"],"swamp":["english"],"swap":["english"],"swarm":["english"],"swear":["english"],"sweet":["english"],"swift":["english"],"swim":["english"],"swing":["english"],"switch":["english"],"sword":["english"],"symbol":["english"],"symptom":["english"],"syrup":["english"],"system":["english"],"table":["english"],"tackle":["english"],"tag":["english"],"tail":["english"],"talent":["english","french"],"talk":["english"],"tank":["english"],"tape":["english"],"target":["english"],"task":["english"],"taste":["english"],"tattoo":["english"],"taxi":["english"],"teach":["english"],"team":["english"],"tell":["english"],"ten":["english"],"tenant":["english"],"tennis":["english"],"tent":["english"],"term":["english"],"test":["english"],"text":["english"],"thank":["english"],"that":["english"],"theme":["english"],"then":["english"],"theory":["english"],"there":["english"],"they":["english"],"thing":["english"],"this":["english"],"thought":["english"],"three":["english"],"thrive":["english"],"throw":["english"],"thumb":["english"],"thunder":["english"],"ticket":["english"],"tide":["english"],"tiger":["english"],"tilt":["english"],"timber":["english"],"time":["english"],"tiny":["english"],"tip":["english"],"tired":["english"],"tissue":["english"],"title":["english"],"toast":["english"],"tobacco":["english"],"today":["english"],"toddler":["english"],"toe":["english"],"together":["english"],"toilet":["english"],"token":["english"],"tomato":["english"],"tomorrow":["english"],"tone":["english"],"tongue":["english"],"tonight":["english"],"tool":["english"],"tooth":["english"],"top":["english"],"topic":["english"],"topple":["english"],"torch":["english"],"tornado":["english"],"tortoise":["english"],"toss":["english"],"total":["english"],"tourist":["english"],"toward":["english"],"tower":["english"],"town":["english"],"toy":["english"],"track":["english"],"trade":["english"],"traffic":["english"],"tragic":["english"],"train":["english","french"],"transfer":["english","turkish"],"trap":["english"],"trash":["english"],"travel":["english"],"tray":["english"],"treat":["english"],"tree":["english"],"trend":["english"],"trial":["english"],"tribe":["english"],"trick":["english"],"trigger":["english"],"trim":["english"],"trip":["english"],"trophy":["english"],"trouble":["english"],"truck":["english"],"true":["english"],"truly":["english"],"trumpet":["english"],"trust":["english"],"truth":["english"],"try":["english"],"tube":["english"],"tuition":["english"],"tumble":["english"],"tuna":["english"],"tunnel":["english","french"],"turkey":["english"],"turn":["english"],"turtle":["english"],"twelve":["english"],"twenty":["english"],"twice":["english"],"twin":["english"],"twist":["english"],"two":["english"],"type":["english"],"typical":["english"],"ugly":["english"],"umbrella":["english"],"unable":["english"],"unaware":["english"],"uncle":["english"],"uncover":["english"],"under":["english"],"undo":["english"],"unfair":["english"],"unfold":["english"],"unhappy":["english"],"uniform":["english"],"unique":["english","french"],"unit":["english"],"universe":["english"],"unknown":["english"],"unlock":["english"],"until":["english"],"unusual":["english"],"unveil":["english"],"update":["english"],"upgrade":["english"],"uphold":["english"],"upon":["english"],"upper":["english"],"upset":["english"],"urban":["english"],"urge":["english"],"usage":["english","french"],"use":["english"],"used":["english"],"useful":["english"],"useless":["english"],"usual":["english"],"utility":["english"],"vacant":["english"],"vacuum":["english"],"vague":["english","french"],"valid":["english"],"valley":["english"],"valve":["english","french"],"van":["english"],"vanish":["english"],"vapor":["english"],"various":["english"],"vast":["english"],"vault":["english"],"vehicle":["english"],"velvet":["english"],"vendor":["english"],"venture":["english"],"venue":["english"],"verb":["english"],"verify":["english"],"version":["english"],"very":["english"],"vessel":["english"],"veteran":["english"],"viable":["english"],"vibrant":["english"],"vicious":["english"],"victory":["english"],"video":["english"],"view":["english"],"village":["english","french"],"vintage":["english"],"violin":["english"],"virtual":["english"],"virus":["english","french"],"visa":["english"],"visit":["english"],"visual":["english"],"vital":["english","french"],"vivid":["english"],"vocal":["english"],"voice":["english"],"void":["english"],"volcano":["english"],"volume":["english","french"],"vote":["english"],"voyage":["english","french"],"wage":["english"],"wagon":["english","french"],"wait":["english"],"walk":["english"],"wall":["english"],"walnut":["english"],"want":["english"],"warfare":["english"],"warm":["english"],"warrior":["english"],"wash":["english"],"wasp":["english"],"waste":["english"],"water":["english"],"wave":["english"],"way":["english"],"wealth":["english"],"weapon":["english"],"wear":["english"],"weasel":["english"],"weather":["english"],"web":["english"],"wedding":["english"],"weekend":["english"],"weird":["english"],"welcome":["english"],"west":["english"],"wet":["english"],"whale":["english"],"what":["english"],"wheat":["english"],"wheel":["english"],"when":["english"],"where":["english"],"whip":["english"],"whisper":["english"],"wide":["english"],"width":["english"],"wife":["english"],"wild":["english"],"will":["english"],"win":["english"],"window":["english"],"wine":["english"],"wing":["english"],"wink":["english"],"winner":["english"],"winter":["english"],"wire":["english"],"wisdom":["english"],"wise":["english"],"wish":["english"],"witness":["english"],"wolf":["english"],"woman":["english"],"wonder":["english"],"wood":["english"],"wool":["english"],"word":["english"],"work":["english"],"world":["english"],"worry":["english"],"worth":["english"],"wrap":["english"],"wreck":["english"],"wrestle":["english"],"wrist":["english"],"write":["english"],"wrong":["english"],"yard":["english"],"year":["english"],"yellow":["english"],"you":["english"],"young":["english"],"youth":["english"],"zebra":["english"],"zero":["english"],"zone":["english"],"zoo":["english"],"abaisser":["french"],"abdiquer":["french"],"abeille":["french"],"abolir":["french"],"aborder":["french"],"aboutir":["french"],"aboyer":["french"],"abrasif":["french"],"abreuver":["french"],"abriter":["french"],"abroger":["french"],"abrupt":["french"],"absence":["french"],"absolu":["french"],"absurde":["french"],"abusif":["french"],"abyssal":["french"],"académie":["french"],"acajou":["french"],"acarien":["french"],"accabler":["french"],"accepter":["french"],"acclamer":["french"],"accolade":["french"],"accroche":["french"],"accuser":["french"],"acerbe":["french"],"achat":["french"],"acheter":["french"],"aciduler":["french"],"acier":["french"],"acompte":["french"],"acquérir":["french"],"acronyme":["french"],"acteur":["french"],"actif":["french"],"actuel":["french"],"adepte":["french"],"adéquat":["french"],"adhésif":["french"],"adjectif":["french"],"adjuger":["french"],"admettre":["french"],"admirer":["french"],"adopter":["french"],"adorer":["french"],"adoucir":["french"],"adresse":["french"],"adroit":["french"],"adulte":["french"],"adverbe":["french"],"aérer":["french"],"aéronef":["french"],"affaire":["french"],"affecter":["french"],"affiche":["french"],"affreux":["french"],"affubler":["french"],"agacer":["french"],"agencer":["french"],"agile":["french"],"agiter":["french"],"agrafer":["french"],"agréable":["french"],"agrume":["french"],"aider":["french"],"aiguille":["french"],"ailier":["french"],"aimable":["french"],"aisance":["french"],"ajouter":["french"],"ajuster":["french"],"alarmer":["french"],"alchimie":["french"],"alerte":["french"],"algèbre":["french"],"algue":["french"],"aliéner":["french"],"aliment":["french"],"alléger":["french"],"alliage":["french"],"allouer":["french"],"allumer":["french"],"alourdir":["french"],"alpaga":["french"],"altesse":["french"],"alvéole":["french"],"ambigu":["french"],"ambre":["french"],"aménager":["french"],"amertume":["french"],"amidon":["french"],"amiral":["french"],"amorcer":["french"],"amour":["french"],"amovible":["french"],"amphibie":["french"],"ampleur":["french"],"amusant":["french"],"analyse":["french"],"anaphore":["french"],"anarchie":["french"],"anatomie":["french"],"ancien":["french"],"anéantir":["french"],"angoisse":["french"],"anguleux":["french"],"annexer":["french"],"annonce":["french"],"annuel":["french"],"anodin":["french"],"anomalie":["french"],"anonyme":["french"],"anormal":["french"],"antenne":["french"],"antidote":["french"],"anxieux":["french"],"apaiser":["french"],"apéritif":["french"],"aplanir":["french"],"apologie":["french"],"appareil":["french"],"appeler":["french"],"apporter":["french"],"appuyer":["french"],"aquarium":["french"],"aqueduc":["french"],"arbitre":["french"],"arbuste":["french"],"ardeur":["french"],"ardoise":["french"],"argent":["french"],"arlequin":["french"],"armature":["french"],"armement":["french"],"armoire":["french"],"armure":["french"],"arpenter":["french"],"arracher":["french"],"arriver":["french"],"arroser":["french"],"arsenic":["french"],"artériel":["french"],"article":["french"],"asphalte":["french"],"aspirer":["french"],"assaut":["french"],"asservir":["french"],"assiette":["french"],"associer":["french"],"assurer":["french"],"asticot":["french"],"astre":["french"],"astuce":["french"],"atelier":["french"],"atome":["french"],"atrium":["french"],"atroce":["french"],"attaque":["french"],"attentif":["french"],"attirer":["french"],"attraper":["french"],"aubaine":["french"],"auberge":["french"],"audace":["french"],"audible":["french"],"augurer":["french"],"aurore":["french"],"automne":["french"],"autruche":["french"],"avaler":["french"],"avancer":["french"],"avarice":["french"],"avenir":["french"],"averse":["french"],"aveugle":["french"],"aviateur":["french"],"avide":["french"],"avion":["french"],"aviser":["french"],"avoine":["french"],"avouer":["french"],"avril":["french"],"axial":["french"],"axiome":["french"],"bafouer":["french"],"bagage":["french"],"baguette":["french"],"baignade":["french"],"balancer":["french"],"balcon":["french"],"baleine":["french"],"balisage":["french"],"bambin":["french"],"bancaire":["french"],"bandage":["french"],"banlieue":["french"],"bannière":["french"],"banquier":["french"],"barbier":["french"],"baril":["french"],"baron":["french"],"barque":["french"],"barrage":["french"],"bassin":["french"],"bastion":["french"],"bataille":["french"],"bateau":["french"],"batterie":["french"],"baudrier":["french"],"bavarder":["french"],"belette":["french"],"bélier":["french"],"belote":["french"],"bénéfice":["french"],"berceau":["french"],"berger":["french"],"berline":["french"],"bermuda":["french","turkish"],"besace":["french"],"besogne":["french"],"bétail":["french"],"beurre":["french"],"biberon":["french"],"bidule":["french"],"bijou":["french"],"bilan":["french"],"bilingue":["french"],"billard":["french"],"binaire":["french"],"biologie":["french"],"biopsie":["french"],"biotype":["french"],"biscuit":["french"],"bison":["french"],"bistouri":["french"],"bitume":["french"],"bizarre":["french"],"blafard":["french"],"blague":["french"],"blanchir":["french"],"blessant":["french"],"blinder":["french"],"blond":["french"],"bloquer":["french"],"blouson":["french"],"bobard":["french"],"bobine":["french"],"boire":["french"],"boiser":["french"],"bolide":["french"],"bonbon":["french"],"bondir":["french"],"bonheur":["french"],"bonifier":["french"],"bordure":["french"],"borne":["french"],"botte":["french"],"boucle":["french"],"boueux":["french"],"bougie":["french"],"boulon":["french"],"bouquin":["french"],"bourse":["french"],"boussole":["french"],"boutique":["french"],"boxeur":["french"],"branche":["french"],"brasier":["french"],"brebis":["french"],"brèche":["french"],"breuvage":["french"],"bricoler":["french"],"brigade":["french"],"brillant":["french"],"brioche":["french"],"brique":["french"],"brochure":["french"],"broder":["french"],"bronzer":["french"],"brousse":["french"],"broyeur":["french"],"brume":["french"],"brusque":["french"],"brutal":["french"],"bruyant":["french"],"buffle":["french"],"buisson":["french"],"bulletin":["french"],"bureau":["french"],"burin":["french"],"bustier":["french"],"butiner":["french"],"butoir":["french"],"buvable":["french"],"buvette":["french"],"cabanon":["french"],"cabine":["french"],"cachette":["french"],"cadeau":["french"],"cadre":["french"],"caféine":["french"],"caillou":["french"],"caisson":["french"],"calculer":["french"],"calepin":["french"],"calibre":["french"],"calmer":["french"],"calomnie":["french"],"calvaire":["french"],"camarade":["french"],"caméra":["french"],"camion":["french"],"campagne":["french"],"caneton":["french"],"canon":["french"],"cantine":["french"],"canular":["french"],"caporal":["french"],"caprice":["french"],"capsule":["french"],"capter":["french"],"capuche":["french"],"carabine":["french"],"carbone":["french"],"caresser":["french"],"caribou":["french"],"carnage":["french"],"carotte":["french"],"carreau":["french"],"carton":["french"],"cascade":["french"],"casier":["french"],"casque":["french"],"cassure":["french"],"causer":["french"],"cavalier":["french"],"caverne":["french"],"caviar":["french"],"cédille":["french"],"ceinture":["french"],"céleste":["french"],"cellule":["french"],"cendrier":["french"],"censurer":["french"],"central":["french"],"cercle":["french"],"cérébral":["french"],"cerise":["french"],"cerner":["french"],"cerveau":["french"],"cesser":["french"],"chagrin":["french"],"chaise":["french"],"chaleur":["french"],"chambre":["french"],"chance":["french"],"chapitre":["french"],"charbon":["french"],"chasseur":["french"],"chaton":["french"],"chausson":["french"],"chavirer":["french"],"chemise":["french"],"chenille":["french"],"chéquier":["french"],"chercher":["french"],"cheval":["french"],"chien":["french"],"chiffre":["french"],"chignon":["french"],"chimère":["french"],"chiot":["french"],"chlorure":["french"],"chocolat":["french"],"choisir":["french"],"chose":["french"],"chouette":["french"],"chrome":["french"],"chute":["french"],"cigare":["french"],"cigogne":["french"],"cimenter":["french"],"cinéma":["french"],"cintrer":["french"],"circuler":["french"],"cirer":["french"],"cirque":["french"],"citerne":["french"],"citoyen":["french"],"citron":["french"],"clairon":["french"],"clameur":["french"],"claquer":["french"],"classe":["french"],"clavier":["french"],"cligner":["french"],"climat":["french"],"clivage":["french"],"cloche":["french"],"clonage":["french"],"cloporte":["french"],"cobalt":["french"],"cobra":["french"],"cocasse":["french"],"cocotier":["french"],"coder":["french"],"codifier":["french"],"coffre":["french"],"cogner":["french"],"cohésion":["french"],"coiffer":["french"],"coincer":["french"],"colère":["french"],"colibri":["french"],"colline":["french"],"colmater":["french"],"colonel":["french"],"combat":["french"],"comédie":["french"],"commande":["french"],"compact":["french"],"conduire":["french"],"confier":["french"],"congeler":["french"],"connoter":["french"],"consonne":["french"],"contact":["french"],"convexe":["french"],"copain":["french"],"copie":["french"],"corail":["french"],"corbeau":["french"],"cordage":["french"],"corniche":["french"],"corpus":["french"],"cortège":["french"],"cosmique":["french"],"costume":["french"],"coton":["french"],"coude":["french"],"coupure":["french"],"courage":["french"],"couteau":["french"],"couvrir":["french"],"crabe":["french"],"crainte":["french"],"cravate":["french"],"crayon":["french"],"créature":["french"],"créditer":["french"],"crémeux":["french"],"creuser":["french"],"crevette":["french"],"cribler":["french"],"crier":["french"],"cristal":["french"],"critère":["french"],"croire":["french"],"croquer":["french"],"crotale":["french"],"crypter":["french"],"cubique":["french"],"cueillir":["french"],"cuillère":["french"],"cuisine":["french"],"cuivre":["french"],"culminer":["french"],"cultiver":["french"],"cumuler":["french"],"cupide":["french"],"curatif":["french"],"curseur":["french"],"cyanure":["french"],"cylindre":["french"],"cynique":["french"],"daigner":["french"],"damier":["french"],"danseur":["french"],"dauphin":["french"],"débattre":["french"],"débiter":["french"],"déborder":["french"],"débrider":["french"],"débutant":["french"],"décaler":["french"],"décembre":["french"],"déchirer":["french"],"décider":["french"],"déclarer":["french"],"décorer":["french"],"décrire":["french"],"décupler":["french"],"dédale":["french"],"déductif":["french"],"déesse":["french"],"défensif":["french"],"défiler":["french"],"défrayer":["french"],"dégager":["french"],"dégivrer":["french"],"déglutir":["french"],"dégrafer":["french"],"déjeuner":["french"],"délice":["french"],"déloger":["french"],"demander":["french"],"demeurer":["french"],"démolir":["french"],"dénicher":["french"],"dénouer":["french"],"dentelle":["french"],"dénuder":["french"],"départ":["french"],"dépenser":["french"],"déphaser":["french"],"déplacer":["french"],"déposer":["french"],"déranger":["french"],"dérober":["french"],"désastre":["french"],"descente":["french"],"désert":["french"],"désigner":["french"],"désobéir":["french"],"dessiner":["french"],"destrier":["french"],"détacher":["french"],"détester":["french"],"détourer":["french"],"détresse":["french"],"devancer":["french"],"devenir":["french"],"deviner":["french"],"devoir":["french"],"diable":["french"],"dialogue":["french"],"diamant":["french"],"dicter":["french"],"différer":["french"],"digérer":["french"],"digne":["french"],"diluer":["french"],"dimanche":["french"],"diminuer":["french"],"dioxyde":["french"],"directif":["french"],"diriger":["french"],"discuter":["french"],"disposer":["french"],"dissiper":["french"],"divertir":["french"],"diviser":["french"],"docile":["french"],"docteur":["french"],"dogme":["french"],"doigt":["french"],"domaine":["french"],"domicile":["french"],"dompter":["french"],"donateur":["french"],"donjon":["french"],"donner":["french"],"dopamine":["french"],"dortoir":["french"],"dorure":["french"],"dosage":["french"],"doseur":["french"],"dossier":["french"],"dotation":["french"],"douanier":["french"],"douceur":["french"],"douter":["french"],"doyen":["french"],"draper":["french"],"dresser":["french"],"dribbler":["french"],"droiture":["french"],"duperie":["french"],"duplexe":["french"],"durable":["french"],"durcir":["french"],"dynastie":["french"],"éblouir":["french"],"écarter":["french"],"écharpe":["french"],"échelle":["french"],"éclairer":["french"],"éclipse":["french"],"éclore":["french"],"écluse":["french"],"école":["french"],"économie":["french"],"écorce":["french"],"écouter":["french"],"écraser":["french"],"écrémer":["french"],"écrivain":["french"],"écrou":["french"],"écume":["french"],"écureuil":["french"],"édifier":["french"],"éduquer":["french"],"effacer":["french"],"effectif":["french"],"effigie":["french"],"effrayer":["french"],"effusion":["french"],"égaliser":["french"],"égarer":["french"],"éjecter":["french"],"élaborer":["french"],"élargir":["french"],"électron":["french"],"élégant":["french"],"éléphant":["french"],"élève":["french"],"éligible":["french"],"élitisme":["french"],"éloge":["french"],"élucider":["french"],"éluder":["french"],"emballer":["french"],"embellir":["french"],"embryon":["french"],"émeraude":["french"],"émission":["french"],"emmener":["french"],"émotion":["french"],"émouvoir":["french"],"empereur":["french"],"employer":["french"],"emporter":["french"],"emprise":["french"],"émulsion":["french"],"encadrer":["french"],"enchère":["french"],"enclave":["french"],"encoche":["french"],"endiguer":["french"],"endosser":["french"],"endroit":["french"],"enduire":["french"],"énergie":["french"],"enfance":["french"],"enfermer":["french"],"enfouir":["french"],"engager":["french"],"engin":["french","turkish"],"englober":["french"],"énigme":["french"],"enjamber":["french"],"enjeu":["french"],"enlever":["french"],"ennemi":["french"],"ennuyeux":["french"],"enrichir":["french"],"enrobage":["french"],"enseigne":["french"],"entasser":["french"],"entendre":["french"],"entier":["french"],"entourer":["french"],"entraver":["french"],"énumérer":["french"],"envahir":["french"],"enviable":["french"],"envoyer":["french"],"enzyme":["french"],"éolien":["french"],"épaissir":["french"],"épargne":["french"],"épatant":["french"],"épaule":["french"],"épicerie":["french"],"épidémie":["french"],"épier":["french"],"épilogue":["french"],"épine":["french"],"épisode":["french"],"épitaphe":["french"],"époque":["french"],"épreuve":["french"],"éprouver":["french"],"épuisant":["french"],"équerre":["french"],"équipe":["french"],"ériger":["french"],"érosion":["french"],"erreur":["french"],"éruption":["french"],"escalier":["french"],"espadon":["french"],"espèce":["french"],"espiègle":["french"],"espoir":["french"],"esprit":["french"],"esquiver":["french"],"essayer":["french"],"essieu":["french"],"essorer":["french"],"estime":["french"],"estomac":["french"],"estrade":["french"],"étagère":["french"],"étaler":["french"],"étanche":["french"],"étatique":["french"],"éteindre":["french"],"étendoir":["french"],"éternel":["french"],"éthanol":["french"],"éthique":["french"],"ethnie":["french"],"étirer":["french"],"étoffer":["french"],"étoile":["french"],"étonnant":["french"],"étourdir":["french"],"étrange":["french"],"étroit":["french"],"étude":["french"],"euphorie":["french"],"évaluer":["french"],"évasion":["french"],"éventail":["french"],"évidence":["french"],"éviter":["french"],"évolutif":["french"],"évoquer":["french"],"exagérer":["french"],"exaucer":["french"],"exceller":["french"],"excitant":["french"],"exclusif":["french"],"exécuter":["french"],"exemple":["french"],"exercer":["french"],"exhaler":["french"],"exhorter":["french"],"exigence":["french"],"exiler":["french"],"exister":["french"],"exotique":["french"],"expédier":["french"],"explorer":["french"],"exposer":["french"],"exprimer":["french"],"exquis":["french"],"extensif":["french"],"extraire":["french"],"exulter":["french"],"fable":["french"],"fabuleux":["french"],"facette":["french"],"facile":["french"],"facture":["french"],"faiblir":["french"],"falaise":["french"],"fameux":["french"],"famille":["french"],"farceur":["french"],"farfelu":["french"],"farine":["french"],"farouche":["french"],"fasciner":["french"],"faucon":["french"],"fautif":["french"],"faveur":["french"],"favori":["french","turkish"],"fébrile":["french"],"féconder":["french"],"fédérer":["french"],"félin":["french"],"femme":["french"],"fémur":["french"],"fendoir":["french"],"féodal":["french"],"fermer":["french"],"féroce":["french"],"ferveur":["french"],"feuille":["french"],"feutre":["french"],"février":["french"],"fiasco":["french"],"ficeler":["french"],"fictif":["french"],"fidèle":["french"],"filature":["french"],"filetage":["french"],"filière":["french"],"filleul":["french"],"filmer":["french"],"filou":["french"],"filtrer":["french"],"financer":["french"],"finir":["french"],"fiole":["french"],"firme":["french"],"fissure":["french"],"fixer":["french"],"flairer":["french"],"flamme":["french"],"flasque":["french"],"flatteur":["french"],"fléau":["french"],"flèche":["french"],"fleur":["french"],"flexion":["french"],"flocon":["french"],"flore":["french"],"fluctuer":["french"],"fluide":["french"],"fluvial":["french"],"folie":["french"],"fonderie":["french"],"fongible":["french"],"fontaine":["french"],"forcer":["french"],"forgeron":["french"],"formuler":["french"],"fossile":["french"],"foudre":["french"],"fougère":["french"],"fouiller":["french"],"foulure":["french"],"fourmi":["french"],"fraise":["french"],"franchir":["french"],"frapper":["french"],"frayeur":["french"],"frégate":["french"],"freiner":["french"],"frelon":["french"],"frémir":["french"],"frénésie":["french"],"frère":["french"],"friable":["french"],"friction":["french"],"frisson":["french"],"frivole":["french"],"froid":["french"],"fromage":["french"],"frontal":["french"],"frotter":["french"],"fugitif":["french"],"fuite":["french"],"fureur":["french"],"furieux":["french"],"furtif":["french"],"fusion":["french"],"futur":["french"],"gagner":["french"],"galaxie":["french"],"galerie":["french"],"gambader":["french"],"garantir":["french"],"gardien":["french"],"garnir":["french"],"garrigue":["french"],"gazelle":["french"],"gazon":["french"],"géant":["french"],"gélatine":["french"],"gélule":["french"],"gendarme":["french"],"général":["french"],"génie":["french"],"genou":["french"],"gentil":["french"],"géologie":["french"],"géomètre":["french"],"géranium":["french"],"germe":["french"],"gestuel":["french"],"geyser":["french"],"gibier":["french"],"gicler":["french"],"girafe":["french"],"givre":["french"],"glace":["french"],"glaive":["french"],"glisser":["french"],"gloire":["french"],"glorieux":["french"],"golfeur":["french"],"gomme":["french"],"gonfler":["french"],"gorge":["french"],"gorille":["french"],"goudron":["french"],"gouffre":["french"],"goulot":["french"],"goupille":["french"],"gourmand":["french"],"goutte":["french"],"graduel":["french"],"graffiti":["french"],"graine":["french"],"grand":["french"],"grappin":["french"],"gratuit":["french"],"gravir":["french"],"grenat":["french"],"griffure":["french"],"griller":["french"],"grimper":["french"],"grogner":["french"],"gronder":["french"],"grotte":["french"],"groupe":["french"],"gruger":["french"],"grutier":["french"],"gruyère":["french"],"guépard":["french"],"guerrier":["french"],"guimauve":["french"],"guitare":["french"],"gustatif":["french"],"gymnaste":["french"],"gyrostat":["french"],"habitude":["french"],"hachoir":["french"],"halte":["french"],"hameau":["french"],"hangar":["french","turkish"],"hanneton":["french"],"haricot":["french"],"harmonie":["french"],"harpon":["french"],"hasard":["french"],"hélium":["french"],"hématome":["french"],"herbe":["french"],"hérisson":["french"],"hermine":["french"],"héron":["french"],"hésiter":["french"],"heureux":["french"],"hiberner":["french"],"hibou":["french"],"hilarant":["french"],"histoire":["french"],"hiver":["french"],"homard":["french"],"hommage":["french"],"homogène":["french"],"honneur":["french"],"honorer":["french"],"honteux":["french"],"horde":["french"],"horizon":["french"],"horloge":["french"],"hormone":["french"],"horrible":["french"],"houleux":["french"],"housse":["french"],"hublot":["french"],"huileux":["french"],"humain":["french"],"humide":["french"],"humour":["french"],"hurler":["french"],"hydromel":["french"],"hygiène":["french"],"hymne":["french"],"hypnose":["french"],"idylle":["french"],"ignorer":["french"],"iguane":["french"],"illicite":["french"],"illusion":["french"],"imbiber":["french"],"imiter":["french"],"immobile":["french"],"immuable":["french"],"impérial":["french"],"implorer":["french"],"imposer":["french"],"imprimer":["french"],"imputer":["french"],"incarner":["french"],"incendie":["french"],"incident":["french"],"incliner":["french"],"incolore":["french"],"indexer":["french"],"indice":["french"],"inductif":["french"],"inédit":["french"],"ineptie":["french"],"inexact":["french"],"infini":["french"],"infliger":["french"],"informer":["french"],"infusion":["french"],"ingérer":["french"],"inhaler":["french"],"inhiber":["french"],"injecter":["french"],"injure":["french"],"inoculer":["french"],"inonder":["french"],"inscrire":["french"],"insecte":["french"],"insigne":["french"],"insolite":["french"],"inspirer":["french"],"instinct":["french"],"insulter":["french"],"intense":["french"],"intime":["french"],"intrigue":["french"],"intuitif":["french"],"inutile":["french"],"invasion":["french"],"inventer":["french"],"inviter":["french"],"invoquer":["french"],"ironique":["french"],"irradier":["french"],"irréel":["french"],"irriter":["french"],"isoler":["french"],"ivoire":["french"],"ivresse":["french"],"jaillir":["french"],"jambe":["french"],"janvier":["french"],"jardin":["french"],"jauger":["french"],"jaune":["french"],"javelot":["french"],"jetable":["french"],"jeton":["french"],"jeudi":["french"],"jeunesse":["french"],"joindre":["french"],"joncher":["french"],"jongler":["french"],"joueur":["french"],"jouissif":["french"],"journal":["french"],"jovial":["french"],"joyau":["french"],"joyeux":["french"],"jubiler":["french"],"jugement":["french"],"jupon":["french"],"juriste":["french"],"justice":["french"],"juteux":["french"],"juvénile":["french"],"kayak":["french"],"kimono":["french"],"kiosque":["french"],"labial":["french"],"labourer":["french"],"lacérer":["french"],"lactose":["french"],"lagune":["french"],"laine":["french"],"laisser":["french"],"laitier":["french"],"lambeau":["french"],"lamelle":["french"],"lampe":["french"],"lanceur":["french"],"langage":["french"],"lanterne":["french"],"lapin":["french"],"largeur":["french"],"larme":["french"],"laurier":["french"],"lavabo":["french"],"lavoir":["french"],"légal":["french"],"léger":["french"],"légume":["french"],"lessive":["french"],"lettre":["french"],"levier":["french"],"lexique":["french"],"lézard":["french"],"liasse":["french"],"libérer":["french"],"libre":["french"],"licence":["french"],"licorne":["french"],"liège":["french"],"lièvre":["french"],"ligature":["french"],"ligoter":["french"],"ligue":["french"],"limer":["french"],"limite":["french"],"limonade":["french"],"limpide":["french"],"linéaire":["french"],"lingot":["french"],"lionceau":["french"],"liquide":["french"],"lisière":["french"],"lister":["french"],"lithium":["french"],"litige":["french"],"littoral":["french"],"livreur":["french"],"logique":["french"],"lointain":["french"],"loisir":["french"],"lombric":["french"],"loterie":["french"],"louer":["french"],"lourd":["french"],"loutre":["french"],"louve":["french"],"lubie":["french"],"lucide":["french"],"lucratif":["french"],"lueur":["french"],"lugubre":["french"],"luisant":["french"],"lumière":["french"],"lunaire":["french"],"lundi":["french"],"luron":["french"],"lutter":["french"],"luxueux":["french"],"magasin":["french"],"magenta":["french"],"magique":["french"],"maigre":["french"],"maillon":["french"],"maintien":["french"],"mairie":["french"],"maison":["french"],"majorer":["french"],"malaxer":["french"],"maléfice":["french"],"malheur":["french"],"malice":["french"],"mallette":["french"],"mammouth":["french"],"mandater":["french"],"maniable":["french"],"manquant":["french"],"manteau":["french"],"manuel":["french"],"marathon":["french"],"marbre":["french"],"marchand":["french"],"mardi":["french"],"maritime":["french"],"marqueur":["french"],"marron":["french"],"marteler":["french"],"mascotte":["french"],"massif":["french"],"matériel":["french"],"matière":["french"],"matraque":["french"],"maudire":["french"],"maussade":["french"],"mauve":["french"],"maximal":["french"],"méchant":["french"],"méconnu":["french"],"médaille":["french"],"médecin":["french"],"méditer":["french"],"méduse":["french"],"meilleur":["french"],"mélange":["french"],"mélodie":["french"],"membre":["french"],"mémoire":["french"],"menacer":["french"],"mener":["french"],"menhir":["french"],"mensonge":["french"],"mentor":["french"],"mercredi":["french"],"mérite":["french"],"merle":["french"],"messager":["french"],"mesure":["french"],"métal":["french"],"météore":["french"],"méthode":["french"],"métier":["french"],"meuble":["french"],"miauler":["french"],"microbe":["french"],"miette":["french"],"mignon":["french"],"migrer":["french"],"milieu":["french"],"mimique":["french"],"mince":["french"],"minéral":["french"],"minimal":["french"],"minorer":["french"],"miroiter":["french"],"missile":["french"],"mixte":["french"],"moderne":["french"],"moelleux":["french"],"mondial":["french"],"moniteur":["french"],"monnaie":["french"],"monotone":["french"],"monstre":["french"],"montagne":["french"],"monument":["french"],"moqueur":["french"],"morceau":["french"],"morsure":["french"],"mortier":["french"],"moteur":["french"],"motif":["french"],"mouche":["french"],"moufle":["french"],"moulin":["french"],"mousson":["french"],"mouton":["french"],"mouvant":["french"],"multiple":["french"],"munition":["french"],"muraille":["french"],"murène":["french"],"murmure":["french"],"muséum":["french"],"musicien":["french"],"mutation":["french"],"muter":["french"],"mutuel":["french"],"myriade":["french"],"myrtille":["french"],"mystère":["french"],"mythique":["french"],"nageur":["french"],"nappe":["french"],"narquois":["french"],"narrer":["french"],"natation":["french"],"naufrage":["french"],"nautique":["french"],"navire":["french"],"nébuleux":["french"],"nectar":["french"],"néfaste":["french"],"négation":["french"],"négliger":["french"],"négocier":["french"],"neige":["french"],"nerveux":["french"],"nettoyer":["french"],"neurone":["french"],"neutron":["french"],"neveu":["french"],"niche":["french"],"nickel":["french"],"nitrate":["french"],"niveau":["french"],"nocif":["french"],"nocturne":["french"],"noirceur":["french"],"noisette":["french"],"nomade":["french"],"nombreux":["french"],"nommer":["french"],"normatif":["french"],"notifier":["french"],"notoire":["french"],"nourrir":["french"],"nouveau":["french"],"novateur":["french"],"novembre":["french"],"novice":["french"],"nuage":["french"],"nuancer":["french"],"nuire":["french"],"nuisible":["french"],"numéro":["french"],"nuptial":["french"],"nuque":["french"],"nutritif":["french"],"obéir":["french"],"objectif":["french"],"obliger":["french"],"obscur":["french"],"observer":["french"],"obstacle":["french"],"obtenir":["french"],"obturer":["french"],"occasion":["french"],"occuper":["french"],"océan":["french"],"octobre":["french"],"octroyer":["french"],"octupler":["french"],"oculaire":["french"],"odeur":["french"],"odorant":["french"],"offenser":["french"],"officier":["french"],"offrir":["french"],"ogive":["french"],"oiseau":["french"],"oisillon":["french"],"olfactif":["french"],"olivier":["french"],"ombrage":["french"],"omettre":["french"],"onctueux":["french"],"onduler":["french"],"onéreux":["french"],"onirique":["french"],"opale":["french"],"opaque":["french"],"opérer":["french"],"opportun":["french"],"opprimer":["french"],"opter":["french"],"optique":["french"],"orageux":["french"],"orbite":["french"],"ordonner":["french"],"oreille":["french"],"organe":["french"],"orgueil":["french"],"orifice":["french"],"ornement":["french"],"orque":["french"],"ortie":["french"],"osciller":["french"],"osmose":["french"],"ossature":["french"],"otarie":["french"],"ouragan":["french"],"ourson":["french"],"outil":["french"],"outrager":["french"],"ouvrage":["french"],"ovation":["french"],"oxyde":["french"],"oxygène":["french"],"paisible":["french"],"palmarès":["french"],"palourde":["french"],"palper":["french"],"panache":["french"],"pangolin":["french"],"paniquer":["french"],"panneau":["french"],"panorama":["french"],"pantalon":["french"],"papaye":["french"],"papier":["french"],"papoter":["french"],"papyrus":["french"],"paradoxe":["french"],"parcelle":["french"],"paresse":["french"],"parfumer":["french"],"parler":["french"],"parole":["french"],"parrain":["french"],"parsemer":["french"],"partager":["french"],"parure":["french"],"parvenir":["french"],"passion":["french"],"pastèque":["french"],"paternel":["french"],"patience":["french"],"patron":["french","turkish"],"pavillon":["french"],"pavoiser":["french"],"payer":["french"],"paysage":["french"],"peigne":["french"],"peintre":["french"],"pelage":["french"],"pélican":["french"],"pelle":["french"],"pelouse":["french"],"peluche":["french"],"pendule":["french"],"pénétrer":["french"],"pénible":["french"],"pensif":["french"],"pénurie":["french"],"pépite":["french"],"péplum":["french"],"perdrix":["french"],"perforer":["french"],"période":["french"],"permuter":["french"],"perplexe":["french"],"persil":["french"],"perte":["french"],"peser":["french"],"pétale":["french"],"petit":["french"],"pétrir":["french"],"peuple":["french"],"pharaon":["french"],"phobie":["french"],"phoque":["french"],"photon":["french"],"physique":["french"],"pictural":["french"],"pièce":["french"],"pierre":["french"],"pieuvre":["french"],"pilote":["french"],"pinceau":["french"],"pipette":["french"],"piquer":["french"],"pirogue":["french"],"piscine":["french"],"piston":["french"],"pivoter":["french"],"pixel":["french"],"placard":["french"],"plafond":["french"],"plaisir":["french"],"planer":["french"],"plaque":["french"],"plastron":["french"],"plateau":["french"],"pleurer":["french"],"plexus":["french"],"pliage":["french"],"plomb":["french"],"plonger":["french"],"pluie":["french"],"plumage":["french"],"pochette":["french"],"poésie":["french"],"poète":["french"],"pointe":["french"],"poirier":["french"],"poisson":["french"],"poivre":["french"],"polaire":["french"],"policier":["french"],"pollen":["french"],"polygone":["french"],"pommade":["french"],"pompier":["french"],"ponctuel":["french"],"pondérer":["french"],"poney":["french"],"portique":["french"],"posséder":["french"],"posture":["french"],"potager":["french"],"poteau":["french"],"potion":["french"],"pouce":["french"],"poulain":["french"],"poumon":["french"],"pourpre":["french"],"poussin":["french"],"pouvoir":["french"],"prairie":["french"],"pratique":["french"],"précieux":["french"],"prédire":["french"],"préfixe":["french"],"prélude":["french"],"prénom":["french"],"présence":["french"],"prétexte":["french"],"prévoir":["french"],"primitif":["french"],"prince":["french"],"priver":["french"],"problème":["french"],"procéder":["french"],"prodige":["french"],"profond":["french"],"progrès":["french"],"proie":["french"],"projeter":["french"],"prologue":["french"],"promener":["french"],"propre":["french"],"prospère":["french"],"protéger":["french"],"prouesse":["french"],"proverbe":["french"],"prudence":["french"],"pruneau":["french"],"psychose":["french"],"puceron":["french"],"puiser":["french"],"pulpe":["french"],"pulsar":["french"],"punaise":["french"],"punitif":["french"],"pupitre":["french"],"purifier":["french"],"pyramide":["french"],"quasar":["french"],"querelle":["french"],"quiétude":["french"],"quitter":["french"],"quotient":["french"],"racine":["french"],"raconter":["french"],"radieux":["french"],"ragondin":["french"],"raideur":["french"],"raisin":["french"],"ralentir":["french"],"rallonge":["french"],"ramasser":["french"],"rapide":["french"],"rasage":["french"],"ratisser":["french"],"ravager":["french"],"ravin":["french"],"rayonner":["french"],"réactif":["french"],"réagir":["french"],"réaliser":["french"],"réanimer":["french"],"recevoir":["french"],"réciter":["french"],"réclamer":["french"],"récolter":["french"],"recruter":["french"],"reculer":["french"],"recycler":["french"],"rédiger":["french"],"redouter":["french"],"refaire":["french"],"réflexe":["french"],"réformer":["french"],"refrain":["french"],"refuge":["french"],"régalien":["french"],"région":["french"],"réglage":["french"],"régulier":["french"],"réitérer":["french"],"rejeter":["french"],"rejouer":["french"],"relatif":["french"],"relever":["french"],"remarque":["french"],"remède":["french"],"remise":["french"],"remonter":["french"],"remplir":["french"],"remuer":["french"],"renard":["french"],"renfort":["french"],"renifler":["french"],"renoncer":["french"],"rentrer":["french"],"renvoi":["french"],"replier":["french"],"reporter":["french"],"reprise":["french"],"reptile":["french"],"requin":["french"],"réserve":["french"],"résineux":["french"],"résoudre":["french"],"respect":["french"],"rester":["french"],"résultat":["french"],"rétablir":["french"],"retenir":["french"],"réticule":["french"],"retomber":["french"],"retracer":["french"],"réunion":["french"],"réussir":["french"],"revanche":["french"],"revivre":["french"],"révolte":["french"],"révulsif":["french"],"richesse":["french"],"rideau":["french"],"rieur":["french"],"rigide":["french"],"rigoler":["french"],"rincer":["french"],"riposter":["french"],"risible":["french"],"risque":["french"],"rituel":["french"],"rivière":["french"],"rocheux":["french"],"rompre":["french"],"ronce":["french"],"rondin":["french"],"roseau":["french"],"rosier":["french"],"rotatif":["french"],"rotor":["french"],"rotule":["french"],"rouge":["french"],"rouille":["french"],"rouleau":["french"],"routine":["french"],"royaume":["french"],"ruban":["french"],"rubis":["french"],"ruche":["french"],"ruelle":["french"],"rugueux":["french"],"ruiner":["french"],"ruisseau":["french"],"ruser":["french"],"rustique":["french"],"rythme":["french"],"sabler":["french"],"saboter":["french"],"sabre":["french"],"sacoche":["french"],"safari":["french"],"sagesse":["french"],"saisir":["french"],"salade":["french"],"salive":["french"],"saluer":["french"],"samedi":["french"],"sanction":["french"],"sanglier":["french"],"sarcasme":["french"],"sardine":["french"],"saturer":["french"],"saugrenu":["french"],"saumon":["french"],"sauter":["french"],"sauvage":["french"],"savant":["french"],"savonner":["french"],"scalpel":["french"],"scandale":["french"],"scélérat":["french"],"scénario":["french"],"sceptre":["french"],"schéma":["french"],"scinder":["french"],"score":["french"],"scrutin":["french"],"sculpter":["french"],"séance":["french"],"sécable":["french"],"sécher":["french"],"secouer":["french"],"sécréter":["french"],"sédatif":["french"],"séduire":["french"],"seigneur":["french"],"séjour":["french"],"sélectif":["french"],"semaine":["french"],"sembler":["french"],"semence":["french"],"séminal":["french"],"sénateur":["french"],"sensible":["french"],"séparer":["french"],"séquence":["french"],"serein":["french"],"sergent":["french"],"sérieux":["french"],"serrure":["french"],"sérum":["french"],"sésame":["french"],"sévir":["french"],"sevrage":["french"],"sextuple":["french"],"sidéral":["french"],"siècle":["french"],"siéger":["french"],"siffler":["french"],"sigle":["french"],"signal":["french"],"silence":["french"],"silicium":["french"],"sincère":["french"],"sinistre":["french"],"siphon":["french"],"sirop":["french"],"sismique":["french"],"situer":["french"],"skier":["french"],"socle":["french"],"sodium":["french"],"soigneux":["french"],"soldat":["french"],"soleil":["french"],"solitude":["french"],"soluble":["french"],"sombre":["french"],"sommeil":["french"],"somnoler":["french"],"sonde":["french"],"songeur":["french"],"sonnette":["french"],"sonore":["french"],"sorcier":["french"],"sortir":["french"],"sosie":["french"],"sottise":["french"],"soucieux":["french"],"soudure":["french"],"souffle":["french"],"soulever":["french"],"soupape":["french"],"soutirer":["french"],"souvenir":["french"],"spacieux":["french"],"spécial":["french"],"sphère":["french"],"spiral":["french","turkish"],"station":["french"],"sternum":["french"],"stimulus":["french"],"stipuler":["french"],"strict":["french"],"studieux":["french"],"stupeur":["french"],"styliste":["french"],"sublime":["french"],"substrat":["french"],"subtil":["french"],"subvenir":["french"],"succès":["french"],"sucre":["french"],"suffixe":["french"],"suggérer":["french"],"suiveur":["french"],"sulfate":["french"],"superbe":["french"],"supplier":["french"],"suricate":["french"],"surmener":["french"],"sursaut":["french"],"survie":["french"],"syllabe":["french"],"symbole":["french"],"symétrie":["french"],"synapse":["french"],"syntaxe":["french"],"système":["french"],"tabac":["french"],"tablier":["french"],"tactile":["french"],"tailler":["french"],"talisman":["french"],"talonner":["french"],"tambour":["french"],"tamiser":["french"],"tangible":["french"],"tapis":["french"],"taquiner":["french"],"tarder":["french"],"tarif":["french"],"tartine":["french"],"tasse":["french"],"tatami":["french"],"tatouage":["french"],"taupe":["french"],"taureau":["french"],"taxer":["french"],"témoin":["french"],"temporel":["french"],"tenaille":["french"],"tendre":["french"],"teneur":["french"],"tenir":["french"],"tension":["french"],"terminer":["french"],"terne":["french"],"terrible":["french"],"tétine":["french"],"texte":["french"],"thème":["french"],"théorie":["french"],"thérapie":["french"],"thorax":["french"],"tibia":["french"],"tiède":["french"],"timide":["french"],"tirelire":["french"],"tiroir":["french"],"tissu":["french"],"titane":["french"],"titre":["french"],"tituber":["french"],"toboggan":["french"],"tolérant":["french"],"tomate":["french"],"tonique":["french"],"tonneau":["french"],"toponyme":["french"],"torche":["french"],"tordre":["french"],"tornade":["french"],"torpille":["french"],"torrent":["french"],"torse":["french"],"tortue":["french"],"totem":["french"],"toucher":["french"],"tournage":["french"],"tousser":["french"],"toxine":["french"],"traction":["french"],"trafic":["french"],"tragique":["french"],"trahir":["french"],"trancher":["french"],"travail":["french"],"trèfle":["french"],"tremper":["french"],"trésor":["french"],"treuil":["french"],"triage":["french"],"tribunal":["french"],"tricoter":["french"],"trilogie":["french"],"triomphe":["french"],"tripler":["french"],"triturer":["french"],"trivial":["french"],"trombone":["french"],"tronc":["french"],"tropical":["french"],"troupeau":["french"],"tuile":["french"],"tulipe":["french"],"tumulte":["french"],"turbine":["french"],"tuteur":["french"],"tutoyer":["french"],"tuyau":["french"],"tympan":["french"],"typhon":["french"],"typique":["french"],"tyran":["french"],"ubuesque":["french"],"ultime":["french"],"ultrason":["french"],"unanime":["french"],"unifier":["french"],"union":["french"],"unitaire":["french"],"univers":["french"],"uranium":["french"],"urbain":["french"],"urticant":["french"],"usine":["french"],"usuel":["french"],"usure":["french"],"utile":["french"],"utopie":["french"],"vacarme":["french"],"vaccin":["french"],"vagabond":["french"],"vaillant":["french"],"vaincre":["french"],"vaisseau":["french"],"valable":["french"],"valise":["french"],"vallon":["french"],"vampire":["french"],"vanille":["french"],"vapeur":["french"],"varier":["french"],"vaseux":["french"],"vassal":["french"],"vaste":["french"],"vecteur":["french"],"vedette":["french"],"végétal":["french"],"véhicule":["french"],"veinard":["french"],"véloce":["french"],"vendredi":["french"],"vénérer":["french"],"venger":["french"],"venimeux":["french"],"ventouse":["french"],"verdure":["french"],"vérin":["french"],"vernir":["french"],"verrou":["french"],"verser":["french"],"vertu":["french"],"veston":["french"],"vétéran":["french"],"vétuste":["french"],"vexant":["french"],"vexer":["french"],"viaduc":["french"],"viande":["french"],"victoire":["french"],"vidange":["french"],"vidéo":["french"],"vignette":["french"],"vigueur":["french"],"vilain":["french"],"vinaigre":["french"],"violon":["french"],"vipère":["french"],"virement":["french"],"virtuose":["french"],"visage":["french"],"viseur":["french"],"vision":["french"],"visqueux":["french"],"visuel":["french"],"vitesse":["french"],"viticole":["french"],"vitrine":["french"],"vivace":["french"],"vivipare":["french"],"vocation":["french"],"voguer":["french"],"voile":["french"],"voisin":["french"],"voiture":["french"],"volaille":["french"],"volcan":["french"],"voltiger":["french"],"vorace":["french"],"vortex":["french"],"voter":["french"],"vouloir":["french"],"voyelle":["french"],"xénon":["french"],"yacht":["french"],"zèbre":["french"],"zénith":["french"],"zeste":["french"],"zoologie":["french"],"abaco":["italian"],"abbaglio":["italian"],"abbinato":["italian"],"abete":["italian"],"abisso":["italian"],"abolire":["italian"],"abrasivo":["italian"],"abrogato":["italian"],"accadere":["italian"],"accenno":["italian"],"accusato":["italian"],"acetone":["italian"],"achille":["italian"],"acido":["italian"],"acqua":["italian"],"acre":["italian"],"acrilico":["italian"],"acrobata":["italian"],"acuto":["italian"],"adagio":["italian"],"addebito":["italian"],"addome":["italian"],"adeguato":["italian"],"aderire":["italian"],"adipe":["italian"],"adottare":["italian"],"adulare":["italian"],"affabile":["italian"],"affetto":["italian"],"affisso":["italian"],"affranto":["italian"],"aforisma":["italian"],"afoso":["italian"],"africano":["italian"],"agave":["italian"],"agente":["italian"],"agevole":["italian"],"aggancio":["italian"],"agire":["italian"],"agitare":["italian"],"agonismo":["italian"],"agricolo":["italian"],"agrumeto":["italian"],"aguzzo":["italian"],"alabarda":["italian"],"alato":["italian"],"albatro":["italian"],"alberato":["italian"],"albo":["italian"],"albume":["italian"],"alce":["italian"],"alcolico":["italian"],"alettone":["italian"],"alfa":["italian"],"algebra":["italian"],"aliante":["italian"],"alibi":["italian"],"alimento":["italian"],"allagato":["italian"],"allegro":["italian"],"allievo":["italian"],"allodola":["italian"],"allusivo":["italian"],"almeno":["italian"],"alogeno":["italian"],"alpaca":["italian"],"alpestre":["italian"],"altalena":["italian"],"alterno":["italian"],"alticcio":["italian"],"altrove":["italian"],"alunno":["italian"],"alveolo":["italian"],"alzare":["italian"],"amalgama":["italian"],"amanita":["italian"],"amarena":["italian"],"ambito":["italian"],"ambrato":["italian"],"ameba":["italian"],"america":["italian"],"ametista":["italian"],"amico":["italian"],"ammasso":["italian"],"ammenda":["italian"],"ammirare":["italian"],"ammonito":["italian"],"amore":["italian"],"ampio":["italian"],"ampliare":["italian"],"amuleto":["italian"],"anacardo":["italian"],"anagrafe":["italian"],"analista":["italian"],"anarchia":["italian"],"anatra":["italian"],"anca":["italian"],"ancella":["italian"],"ancora":["italian"],"andare":["italian"],"andrea":["italian"],"anello":["italian"],"angelo":["italian"],"angolare":["italian"],"angusto":["italian"],"anima":["italian"],"annegare":["italian"],"annidato":["italian"],"anno":["italian"],"annuncio":["italian"],"anonimo":["italian"],"anticipo":["italian"],"anzi":["italian"],"apatico":["italian"],"apertura":["italian"],"apode":["italian"],"apparire":["italian"],"appetito":["italian"],"appoggio":["italian"],"approdo":["italian"],"appunto":["italian"],"aprile":["italian"],"arabica":["italian"],"arachide":["italian"],"aragosta":["italian"],"araldica":["italian"],"arancio":["italian"],"aratura":["italian"],"arazzo":["italian"],"arbitro":["italian"],"archivio":["italian"],"ardito":["italian"],"arenile":["italian"],"argento":["italian"],"argine":["italian"],"arguto":["italian"],"aria":["italian"],"armonia":["italian"],"arnese":["italian"],"arredato":["italian"],"arringa":["italian"],"arrosto":["italian"],"arsenico":["italian"],"arso":["italian"],"artefice":["italian"],"arzillo":["italian"],"asciutto":["italian"],"ascolto":["italian"],"asepsi":["italian"],"asettico":["italian"],"asfalto":["italian"],"asino":["italian"],"asola":["italian"],"aspirato":["italian"],"aspro":["italian"],"assaggio":["italian"],"asse":["italian"],"assoluto":["italian"],"assurdo":["italian"],"asta":["italian"],"astenuto":["italian"],"astice":["italian"],"astratto":["italian"],"atavico":["italian"],"ateismo":["italian"],"atomico":["italian"],"atono":["italian"],"attesa":["italian"],"attivare":["italian"],"attorno":["italian"],"attrito":["italian"],"attuale":["italian"],"ausilio":["italian"],"austria":["italian"],"autista":["italian"],"autonomo":["italian"],"autunno":["italian"],"avanzato":["italian"],"avere":["italian"],"avvenire":["italian"],"avviso":["italian"],"avvolgere":["italian"],"azione":["italian"],"azoto":["italian"],"azzimo":["italian"],"azzurro":["italian"],"babele":["italian"],"baccano":["italian"],"bacino":["italian"],"baco":["italian"],"badessa":["italian"],"badilata":["italian"],"bagnato":["italian"],"baita":["italian"],"balcone":["italian"],"baldo":["italian"],"balena":["italian"],"ballata":["italian"],"balzano":["italian"],"bambino":["italian"],"bandire":["italian"],"baraonda":["italian"],"barbaro":["italian"],"barca":["italian"],"baritono":["italian"],"barlume":["italian"],"barocco":["italian"],"basilico":["italian"],"basso":["italian"],"batosta":["italian"],"battuto":["italian"],"baule":["italian"],"bava":["italian"],"bavosa":["italian"],"becco":["italian"],"beffa":["italian"],"belgio":["italian"],"belva":["italian"],"benda":["italian"],"benevole":["italian"],"benigno":["italian"],"benzina":["italian"],"bere":["italian"],"berlina":["italian"],"beta":["italian"],"bibita":["italian"],"bici":["italian"],"bidone":["italian"],"bifido":["italian"],"biga":["italian"],"bilancia":["italian"],"bimbo":["italian"],"binocolo":["italian"],"biologo":["italian"],"bipede":["italian"],"bipolare":["italian"],"birbante":["italian"],"birra":["italian"],"biscotto":["italian"],"bisesto":["italian"],"bisnonno":["italian"],"bisonte":["italian"],"bisturi":["italian"],"bizzarro":["italian"],"blando":["italian"],"blatta":["italian"],"bollito":["italian"],"bonifico":["italian"],"bordo":["italian"],"bosco":["italian"],"botanico":["italian"],"bottino":["italian"],"bozzolo":["italian"],"braccio":["italian"],"bradipo":["italian"],"brama":["italian"],"branca":["italian"],"bravura":["italian"],"bretella":["italian"],"brevetto":["italian"],"brezza":["italian"],"briglia":["italian"],"brillante":["italian"],"brindare":["italian"],"broccolo":["italian"],"brodo":["italian"],"bronzina":["italian"],"brullo":["italian"],"bruno":["italian"],"bubbone":["italian"],"buca":["italian"],"budino":["italian"],"buffone":["italian"],"buio":["italian"],"bulbo":["italian"],"buono":["italian"],"burlone":["italian"],"burrasca":["italian"],"bussola":["italian"],"busta":["italian"],"cadetto":["italian"],"caduco":["italian"],"calamaro":["italian"],"calcolo":["italian"],"calesse":["italian"],"calibro":["italian"],"calmo":["italian"],"caloria":["italian"],"cambusa":["italian"],"camerata":["italian"],"camicia":["italian"],"cammino":["italian"],"camola":["italian"],"campale":["italian"],"canapa":["italian"],"candela":["italian"],"cane":["italian"],"canino":["italian"],"canotto":["italian"],"cantina":["italian"],"capace":["italian"],"capello":["italian"],"capitolo":["italian"],"capogiro":["italian"],"cappero":["italian"],"capra":["italian"],"capsula":["italian"],"carapace":["italian"],"carcassa":["italian"],"cardo":["italian"],"carisma":["italian"],"carovana":["italian"],"carretto":["italian"],"cartolina":["italian"],"casaccio":["italian"],"cascata":["italian"],"caserma":["italian"],"caso":["italian"],"cassone":["italian"],"castello":["italian"],"casuale":["italian"],"catasta":["italian"],"catena":["italian"],"catrame":["italian"],"cauto":["italian"],"cavillo":["italian"],"cedibile":["italian"],"cedrata":["italian"],"cefalo":["italian"],"celebre":["italian"],"cellulare":["italian"],"cena":["italian"],"cenone":["italian"],"centesimo":["italian"],"ceramica":["italian"],"cercare":["italian"],"certo":["italian"],"cerume":["italian"],"cervello":["italian"],"cesoia":["italian"],"cespo":["italian"],"ceto":["italian"],"chela":["italian"],"chiaro":["italian"],"chicca":["italian"],"chiedere":["italian"],"chimera":["italian"],"china":["italian"],"chirurgo":["italian"],"chitarra":["italian"],"ciao":["italian"],"ciclismo":["italian"],"cifrare":["italian"],"cigno":["italian"],"cilindro":["italian"],"ciottolo":["italian"],"circa":["italian"],"cirrosi":["italian"],"citrico":["italian"],"cittadino":["italian"],"ciuffo":["italian"],"civetta":["italian"],"civile":["italian"],"classico":["italian"],"clinica":["italian"],"cloro":["italian"],"cocco":["italian"],"codardo":["italian"],"codice":["italian"],"coerente":["italian"],"cognome":["italian"],"collare":["italian"],"colmato":["italian"],"colore":["italian"],"colposo":["italian"],"coltivato":["italian"],"colza":["italian"],"coma":["italian"],"cometa":["italian"],"commando":["italian"],"comodo":["italian"],"computer":["italian"],"comune":["italian"],"conciso":["italian"],"condurre":["italian"],"conferma":["italian"],"congelare":["italian"],"coniuge":["italian"],"connesso":["italian"],"conoscere":["italian"],"consumo":["italian"],"continuo":["italian"],"convegno":["italian"],"coperto":["italian"],"copione":["italian"],"coppia":["italian"],"copricapo":["italian"],"corazza":["italian"],"cordata":["italian"],"coricato":["italian"],"cornice":["italian"],"corolla":["italian"],"corpo":["italian"],"corredo":["italian"],"corsia":["italian"],"cortese":["italian"],"cosmico":["italian"],"costante":["italian"],"cottura":["italian"],"covato":["italian"],"cratere":["italian"],"cravatta":["italian"],"creato":["italian"],"credere":["italian"],"cremoso":["italian"],"crescita":["italian"],"creta":["italian"],"criceto":["italian"],"crinale":["italian"],"crisi":["italian"],"critico":["italian"],"croce":["italian"],"cronaca":["italian"],"crostata":["italian"],"cruciale":["italian"],"crusca":["italian"],"cucire":["italian"],"cuculo":["italian"],"cugino":["italian"],"cullato":["italian"],"cupola":["italian"],"curatore":["italian"],"cursore":["italian"],"curvo":["italian"],"cuscino":["italian"],"custode":["italian"],"dado":["italian"],"daino":["italian"],"dalmata":["italian"],"damerino":["italian"],"daniela":["italian"],"dannoso":["italian"],"danzare":["italian"],"datato":["italian"],"davanti":["italian"],"davvero":["italian"],"debutto":["italian"],"decennio":["italian"],"deciso":["italian"],"declino":["italian"],"decollo":["italian"],"decreto":["italian"],"dedicato":["italian"],"definito":["italian"],"deforme":["italian"],"degno":["italian"],"delegare":["italian"],"delfino":["italian"],"delirio":["italian"],"delta":["italian"],"demenza":["italian"],"denotato":["italian"],"dentro":["italian"],"deposito":["italian"],"derapata":["italian"],"derivare":["italian"],"deroga":["italian"],"descritto":["italian"],"deserto":["italian"],"desiderio":["italian"],"desumere":["italian"],"detersivo":["italian"],"devoto":["italian"],"diametro":["italian"],"dicembre":["italian"],"diedro":["italian"],"difeso":["italian"],"diffuso":["italian"],"digerire":["italian"],"digitale":["italian"],"diluvio":["italian"],"dinamico":["italian"],"dinnanzi":["italian"],"dipinto":["italian"],"diploma":["italian"],"dipolo":["italian"],"diradare":["italian"],"dire":["italian"],"dirotto":["italian"],"dirupo":["italian"],"disagio":["italian"],"discreto":["italian"],"disfare":["italian"],"disgelo":["italian"],"disposto":["italian"],"distanza":["italian"],"disumano":["italian"],"dito":["italian"],"divano":["italian"],"divelto":["italian"],"dividere":["italian"],"divorato":["italian"],"doblone":["italian"],"docente":["italian"],"doganale":["italian"],"dogma":["italian"],"dolce":["italian"],"domato":["italian"],"domenica":["italian"],"dominare":["italian"],"dondolo":["italian"],"dono":["italian"],"dormire":["italian"],"dote":["italian"],"dottore":["italian"],"dovuto":["italian"],"dozzina":["italian"],"drago":["italian"],"druido":["italian"],"dubbio":["italian"],"dubitare":["italian"],"ducale":["italian"],"duna":["italian"],"duomo":["italian"],"duplice":["italian"],"duraturo":["italian"],"ebano":["italian"],"eccesso":["italian"],"ecco":["italian"],"eclissi":["italian"],"economia":["italian"],"edera":["italian"],"edicola":["italian"],"edile":["italian"],"editoria":["italian"],"educare":["italian"],"egemonia":["italian"],"egli":["italian"],"egoismo":["italian"],"egregio":["italian"],"elaborato":["italian"],"elargire":["italian"],"elegante":["italian"],"elencato":["italian"],"eletto":["italian"],"elevare":["italian"],"elfico":["italian"],"elica":["italian"],"elmo":["italian"],"elsa":["italian"],"eluso":["italian"],"emanato":["italian"],"emblema":["italian"],"emesso":["italian"],"emiro":["italian"],"emotivo":["italian"],"emozione":["italian"],"empirico":["italian"],"emulo":["italian"],"endemico":["italian"],"enduro":["italian"],"energia":["italian"],"enfasi":["italian"],"enoteca":["italian"],"entrare":["italian"],"enzima":["italian"],"epatite":["italian"],"epilogo":["italian"],"episodio":["italian"],"epocale":["italian"],"eppure":["italian"],"equatore":["italian"],"erario":["italian"],"erba":["italian"],"erboso":["italian"],"erede":["italian"],"eremita":["italian"],"erigere":["italian"],"ermetico":["italian"],"eroe":["italian"],"erosivo":["italian"],"errante":["italian"],"esagono":["italian"],"esame":["italian"],"esanime":["italian"],"esaudire":["italian"],"esca":["italian"],"esempio":["italian"],"esercito":["italian"],"esibito":["italian"],"esigente":["italian"],"esistere":["italian"],"esito":["italian"],"esofago":["italian"],"esortato":["italian"],"esoso":["italian"],"espanso":["italian"],"espresso":["italian"],"essenza":["italian"],"esso":["italian"],"esteso":["italian"],"estimare":["italian"],"estonia":["italian"],"estroso":["italian"],"esultare":["italian"],"etilico":["italian"],"etnico":["italian"],"etrusco":["italian"],"etto":["italian"],"euclideo":["italian"],"europa":["italian"],"evaso":["italian"],"evidenza":["italian"],"evitato":["italian"],"evoluto":["italian"],"evviva":["italian"],"fabbrica":["italian"],"faccenda":["italian"],"fachiro":["italian"],"falco":["italian"],"famiglia":["italian"],"fanale":["italian"],"fanfara":["italian"],"fango":["italian"],"fantasma":["italian"],"fare":["italian"],"farfalla":["italian"],"farinoso":["italian"],"farmaco":["italian"],"fascia":["italian"],"fastoso":["italian"],"fasullo":["italian"],"faticare":["italian"],"fato":["italian"],"favoloso":["italian"],"febbre":["italian"],"fecola":["italian"],"fede":["italian"],"fegato":["italian"],"felpa":["italian"],"feltro":["italian"],"femmina":["italian"],"fendere":["italian"],"fenomeno":["italian"],"fermento":["italian"],"ferro":["italian"],"fertile":["italian"],"fessura":["italian"],"festivo":["italian"],"fetta":["italian"],"feudo":["italian"],"fiaba":["italian"],"fiducia":["italian"],"fifa":["italian"],"figurato":["italian"],"filo":["italian","turkish"],"finanza":["italian"],"finestra":["italian"],"finire":["italian"],"fiore":["italian"],"fiscale":["italian"],"fisico":["italian"],"fiume":["italian"],"flacone":["italian"],"flamenco":["italian"],"flebo":["italian"],"flemma":["italian"],"florido":["italian"],"fluente":["italian"],"fluoro":["italian"],"fobico":["italian"],"focaccia":["italian"],"focoso":["italian"],"foderato":["italian"],"foglio":["italian"],"folata":["italian"],"folclore":["italian"],"folgore":["italian"],"fondente":["italian"],"fonetico":["italian"],"fonia":["italian"],"fontana":["italian"],"forbito":["italian"],"forchetta":["italian"],"foresta":["italian"],"formica":["italian"],"fornaio":["italian"],"foro":["italian"],"fortezza":["italian"],"forzare":["italian"],"fosfato":["italian"],"fosso":["italian"],"fracasso":["italian"],"frana":["italian"],"frassino":["italian"],"fratello":["italian"],"freccetta":["italian"],"frenata":["italian"],"fresco":["italian"],"frigo":["italian"],"frollino":["italian"],"fronde":["italian"],"frugale":["italian"],"frutta":["italian"],"fucilata":["italian"],"fucsia":["italian"],"fuggente":["italian"],"fulmine":["italian"],"fulvo":["italian"],"fumante":["italian"],"fumetto":["italian"],"fumoso":["italian"],"fune":["italian"],"funzione":["italian"],"fuoco":["italian"],"furbo":["italian"],"furgone":["italian"],"furore":["italian"],"fuso":["italian"],"futile":["italian"],"gabbiano":["italian"],"gaffe":["italian"],"galateo":["italian"],"gallina":["italian"],"galoppo":["italian"],"gambero":["italian"],"gamma":["italian"],"garanzia":["italian"],"garbo":["italian"],"garofano":["italian"],"garzone":["italian"],"gasdotto":["italian"],"gasolio":["italian"],"gastrico":["italian"],"gatto":["italian"],"gaudio":["italian"],"gazebo":["italian"],"gazzella":["italian"],"geco":["italian"],"gelatina":["italian"],"gelso":["italian"],"gemello":["italian"],"gemmato":["italian"],"gene":["italian"],"genitore":["italian"],"gennaio":["italian"],"genotipo":["italian"],"gergo":["italian"],"ghepardo":["italian"],"ghiaccio":["italian"],"ghisa":["italian"],"giallo":["italian"],"gilda":["italian"],"ginepro":["italian"],"giocare":["italian"],"gioiello":["italian"],"giorno":["italian"],"giove":["italian"],"girato":["italian"],"girone":["italian"],"gittata":["italian"],"giudizio":["italian"],"giurato":["italian"],"giusto":["italian"],"globulo":["italian"],"glutine":["italian"],"gnomo":["italian"],"gobba":["italian"],"golf":["italian"],"gomito":["italian"],"gommone":["italian"],"gonfio":["italian"],"gonna":["italian"],"governo":["italian"],"gracile":["italian"],"grado":["italian"],"grafico":["italian"],"grammo":["italian"],"grande":["italian"],"grattare":["italian"],"gravoso":["italian"],"grazia":["italian"],"greca":["italian"],"gregge":["italian"],"grifone":["italian"],"grigio":["italian"],"grinza":["italian"],"grotta":["italian"],"gruppo":["italian"],"guadagno":["italian"],"guaio":["italian"],"guanto":["italian"],"guardare":["italian"],"gufo":["italian"],"guidare":["italian"],"ibernato":["italian"],"icona":["italian"],"identico":["italian"],"idillio":["italian"],"idolo":["italian"],"idra":["italian"],"idrico":["italian"],"idrogeno":["italian"],"igiene":["italian"],"ignaro":["italian"],"ignorato":["italian"],"ilare":["italian"],"illeso":["italian"],"illogico":["italian"],"illudere":["italian"],"imballo":["italian"],"imbevuto":["italian"],"imbocco":["italian"],"imbuto":["italian"],"immane":["italian"],"immerso":["italian"],"immolato":["italian"],"impacco":["italian"],"impeto":["italian"],"impiego":["italian"],"importo":["italian"],"impronta":["italian"],"inalare":["italian"],"inarcare":["italian"],"inattivo":["italian"],"incanto":["italian"],"incendio":["italian"],"inchino":["italian"],"incisivo":["italian"],"incluso":["italian"],"incontro":["italian"],"incrocio":["italian"],"incubo":["italian"],"indagine":["italian"],"india":["italian"],"indole":["italian"],"inedito":["italian"],"infatti":["italian"],"infilare":["italian"],"inflitto":["italian"],"ingaggio":["italian"],"ingegno":["italian"],"inglese":["italian"],"ingordo":["italian"],"ingrosso":["italian"],"innesco":["italian"],"inodore":["italian"],"inoltrare":["italian"],"inondato":["italian"],"insano":["italian"],"insetto":["italian"],"insieme":["italian"],"insonnia":["italian"],"insulina":["italian"],"intasato":["italian"],"intero":["italian"],"intonaco":["italian"],"intuito":["italian"],"inumidire":["italian"],"invalido":["italian"],"invece":["italian"],"invito":["italian"],"iperbole":["italian"],"ipnotico":["italian"],"ipotesi":["italian"],"ippica":["italian"],"iride":["italian"],"irlanda":["italian"],"ironico":["italian"],"irrigato":["italian"],"irrorare":["italian"],"isolato":["italian"],"isotopo":["italian"],"isterico":["italian"],"istituto":["italian"],"istrice":["italian"],"italia":["italian"],"iterare":["italian"],"labbro":["italian"],"labirinto":["italian"],"lacca":["italian"],"lacerato":["italian"],"lacrima":["italian"],"lacuna":["italian"],"laddove":["italian"],"lago":["italian"],"lampo":["italian"],"lancetta":["italian"],"lanterna":["italian"],"lardoso":["italian"],"larga":["italian"],"laringe":["italian"],"lastra":["italian"],"latenza":["italian"],"latino":["italian"],"lattuga":["italian"],"lavagna":["italian"],"lavoro":["italian"],"legale":["italian"],"leggero":["italian"],"lembo":["italian"],"lentezza":["italian"],"lenza":["italian"],"leone":["italian"],"lepre":["italian"],"lesivo":["italian"],"lessato":["italian"],"lesto":["italian"],"letterale":["italian"],"leva":["italian"],"levigato":["italian"],"libero":["italian"],"lido":["italian"],"lievito":["italian"],"lilla":["italian"],"limatura":["italian"],"limitare":["italian"],"limpido":["italian"],"lineare":["italian"],"lingua":["italian"],"liquido":["italian"],"lira":["italian"],"lirica":["italian"],"lisca":["italian"],"lite":["italian"],"litigio":["italian"],"livrea":["italian"],"locanda":["italian"],"lode":["italian"],"logica":["italian"],"lombare":["italian"],"londra":["italian"],"longevo":["italian"],"loquace":["italian"],"lorenzo":["italian"],"loto":["italian"],"lotteria":["italian"],"luce":["italian"],"lucidato":["italian"],"lumaca":["italian"],"luminoso":["italian"],"lungo":["italian"],"lupo":["italian"],"luppolo":["italian"],"lusinga":["italian"],"lusso":["italian"],"lutto":["italian"],"macabro":["italian"],"macchina":["italian"],"macero":["italian"],"macinato":["italian"],"madama":["italian"],"magico":["italian"],"maglia":["italian"],"magnete":["italian"],"magro":["italian"],"maiolica":["italian"],"malafede":["italian"],"malgrado":["italian"],"malinteso":["italian"],"malsano":["italian"],"malto":["italian"],"malumore":["italian"],"mana":["italian"],"mancia":["italian"],"mandorla":["italian"],"mangiare":["italian"],"manifesto":["italian"],"mannaro":["italian"],"manovra":["italian"],"mansarda":["italian"],"mantide":["italian"],"manubrio":["italian"],"mappa":["italian"],"maratona":["italian"],"marcire":["italian"],"maretta":["italian"],"marmo":["italian"],"marsupio":["italian"],"maschera":["italian"],"massaia":["italian"],"mastino":["italian"],"materasso":["italian"],"matricola":["italian"],"mattone":["italian"],"maturo":["italian"],"mazurca":["italian"],"meandro":["italian"],"meccanico":["italian"],"mecenate":["italian"],"medesimo":["italian"],"meditare":["italian"],"mega":["italian"],"melassa":["italian"],"melis":["italian"],"melodia":["italian"],"meninge":["italian"],"meno":["italian"],"mensola":["italian"],"mercurio":["italian"],"merenda":["italian"],"merlo":["italian"],"meschino":["italian"],"mese":["italian"],"messere":["italian"],"mestolo":["italian"],"metallo":["italian"],"metodo":["italian"],"mettere":["italian"],"miagolare":["italian"],"mica":["italian"],"micelio":["italian"],"michele":["italian"],"microbo":["italian"],"midollo":["italian"],"miele":["italian"],"migliore":["italian"],"milano":["italian"],"milite":["italian"],"mimosa":["italian"],"minerale":["italian"],"mini":["italian"],"minore":["italian"],"mirino":["italian"],"mirtillo":["italian"],"miscela":["italian"],"missiva":["italian"],"misto":["italian"],"misurare":["italian"],"mitezza":["italian"],"mitigare":["italian"],"mitra":["italian"],"mittente":["italian"],"mnemonico":["italian"],"modello":["italian"],"modifica":["italian"],"modulo":["italian"],"mogano":["italian"],"mogio":["italian"],"mole":["italian"],"molosso":["italian"],"monastero":["italian"],"monco":["italian"],"mondina":["italian"],"monetario":["italian"],"monile":["italian"],"monotono":["italian"],"monsone":["italian"],"montato":["italian"],"monviso":["italian"],"mora":["italian"],"mordere":["italian"],"morsicato":["italian"],"mostro":["italian"],"motivato":["italian"],"motosega":["italian"],"motto":["italian"],"movenza":["italian"],"movimento":["italian"],"mozzo":["italian"],"mucca":["italian"],"mucosa":["italian"],"muffa":["italian"],"mughetto":["italian"],"mugnaio":["italian"],"mulatto":["italian"],"mulinello":["italian"],"multiplo":["italian"],"mummia":["italian"],"munto":["italian"],"muovere":["italian"],"murale":["italian"],"musa":["italian"],"muscolo":["italian"],"musica":["italian"],"mutevole":["italian"],"muto":["italian"],"nababbo":["italian"],"nafta":["italian"],"nanometro":["italian"],"narciso":["italian"],"narice":["italian"],"narrato":["italian"],"nascere":["italian"],"nastrare":["italian"],"naturale":["italian"],"nautica":["italian"],"naviglio":["italian"],"nebulosa":["italian"],"necrosi":["italian"],"negativo":["italian"],"negozio":["italian"],"nemmeno":["italian"],"neofita":["italian"],"neretto":["italian"],"nervo":["italian"],"nessuno":["italian"],"nettuno":["italian"],"neutrale":["italian"],"neve":["italian"],"nevrotico":["italian"],"nicchia":["italian"],"ninfa":["italian"],"nitido":["italian"],"nobile":["italian"],"nocivo":["italian"],"nodo":["italian"],"nome":["italian"],"nomina":["italian"],"nordico":["italian"],"normale":["italian"],"norvegese":["italian"],"nostrano":["italian"],"notare":["italian"],"notizia":["italian"],"notturno":["italian"],"novella":["italian"],"nucleo":["italian"],"nulla":["italian"],"numero":["italian"],"nuovo":["italian"],"nutrire":["italian"],"nuvola":["italian"],"nuziale":["italian"],"oasi":["italian"],"obbedire":["italian"],"obbligo":["italian"],"obelisco":["italian"],"oblio":["italian"],"obolo":["italian"],"obsoleto":["italian"],"occasione":["italian"],"occhio":["italian"],"occidente":["italian"],"occorrere":["italian"],"occultare":["italian"],"ocra":["italian"],"oculato":["italian"],"odierno":["italian"],"odorare":["italian"],"offerta":["italian"],"offrire":["italian"],"offuscato":["italian"],"oggetto":["italian"],"oggi":["italian"],"ognuno":["italian"],"olandese":["italian"],"olfatto":["italian"],"oliato":["italian"],"oliva":["italian"],"ologramma":["italian"],"oltre":["italian"],"omaggio":["italian"],"ombelico":["italian"],"ombra":["italian"],"omega":["italian"],"omissione":["italian"],"ondoso":["italian"],"onere":["italian"],"onice":["italian"],"onnivoro":["italian"],"onorevole":["italian"],"onta":["italian"],"operato":["italian"],"opinione":["italian"],"opposto":["italian"],"oracolo":["italian"],"orafo":["italian"],"ordine":["italian"],"orecchino":["italian"],"orefice":["italian"],"orfano":["italian"],"organico":["italian"],"origine":["italian"],"orizzonte":["italian"],"orma":["italian"],"ormeggio":["italian"],"ornativo":["italian"],"orologio":["italian"],"orrendo":["italian"],"orribile":["italian"],"ortensia":["italian"],"ortica":["italian"],"orzata":["italian"],"orzo":["italian"],"osare":["italian"],"oscurare":["italian"],"osmosi":["italian"],"ospedale":["italian"],"ospite":["italian"],"ossa":["italian"],"ossidare":["italian"],"ostacolo":["italian"],"oste":["italian"],"otite":["italian"],"otre":["italian"],"ottagono":["italian"],"ottimo":["italian"],"ottobre":["italian"],"ovale":["italian"],"ovest":["italian"],"ovino":["italian"],"oviparo":["italian"],"ovocito":["italian"],"ovunque":["italian"],"ovviare":["italian"],"ozio":["italian"],"pacchetto":["italian"],"pace":["italian"],"pacifico":["italian"],"padella":["italian"],"padrone":["italian"],"paese":["italian"],"paga":["italian"],"pagina":["italian"],"palazzina":["italian"],"palesare":["italian"],"pallido":["italian"],"palo":["italian"],"palude":["italian"],"pandoro":["italian"],"pannello":["italian"],"paolo":["italian"],"paonazzo":["italian"],"paprica":["italian"],"parabola":["italian"],"parcella":["italian"],"parere":["italian"],"pargolo":["italian"],"pari":["italian"],"parlato":["italian"],"parola":["italian"],"partire":["italian"],"parvenza":["italian"],"parziale":["italian"],"passivo":["italian"],"pasticca":["italian"],"patacca":["italian"],"patologia":["italian"],"pattume":["italian"],"pavone":["italian"],"peccato":["italian"],"pedalare":["italian"],"pedonale":["italian"],"peggio":["italian"],"peloso":["italian"],"penare":["italian"],"pendice":["italian"],"penisola":["italian"],"pennuto":["italian"],"penombra":["italian"],"pensare":["italian"],"pentola":["italian"],"pepe":["italian"],"pepita":["italian"],"perbene":["italian"],"percorso":["italian"],"perdonato":["italian"],"perforare":["italian"],"pergamena":["italian"],"periodo":["italian"],"permesso":["italian"],"perno":["italian"],"perplesso":["italian"],"persuaso":["italian"],"pertugio":["italian"],"pervaso":["italian"],"pesatore":["italian"],"pesista":["italian"],"peso":["italian"],"pestifero":["italian"],"petalo":["italian"],"pettine":["italian"],"petulante":["italian"],"pezzo":["italian"],"piacere":["italian"],"pianta":["italian"],"piattino":["italian"],"piccino":["italian"],"picozza":["italian"],"piega":["italian"],"pietra":["italian"],"piffero":["italian"],"pigiama":["italian"],"pigolio":["italian"],"pigro":["italian"],"pila":["italian"],"pilifero":["italian"],"pillola":["italian"],"pilota":["italian"],"pimpante":["italian"],"pineta":["italian"],"pinna":["italian"],"pinolo":["italian"],"pioggia":["italian"],"piombo":["italian"],"piramide":["italian"],"piretico":["italian"],"pirite":["italian"],"pirolisi":["italian"],"pitone":["italian"],"pizzico":["italian"],"placebo":["italian"],"planare":["italian"],"plasma":["italian"],"platano":["italian"],"plenario":["italian"],"pochezza":["italian"],"poderoso":["italian"],"podismo":["italian"],"poesia":["italian"],"poggiare":["italian"],"polenta":["italian"],"poligono":["italian"],"pollice":["italian"],"polmonite":["italian"],"polpetta":["italian"],"polso":["italian"],"poltrona":["italian"],"polvere":["italian"],"pomice":["italian"],"pomodoro":["italian"],"ponte":["italian"],"popoloso":["italian"],"porfido":["italian"],"poroso":["italian"],"porpora":["italian"],"porre":["italian"],"portata":["italian"],"posa":["italian","turkish"],"positivo":["italian"],"possesso":["italian"],"postulato":["italian"],"potassio":["italian"],"potere":["italian"],"pranzo":["italian"],"prassi":["italian"],"pratica":["italian"],"precluso":["italian"],"predica":["italian"],"prefisso":["italian"],"pregiato":["italian"],"prelievo":["italian"],"premere":["italian"],"prenotare":["italian"],"preparato":["italian"],"presenza":["italian"],"pretesto":["italian"],"prevalso":["italian"],"prima":["italian"],"principe":["italian"],"privato":["italian"],"problema":["italian"],"procura":["italian"],"produrre":["italian"],"profumo":["italian"],"progetto":["italian"],"prolunga":["italian"],"promessa":["italian"],"pronome":["italian"],"proposta":["italian"],"proroga":["italian"],"proteso":["italian"],"prova":["italian","turkish"],"prudente":["italian"],"prugna":["italian"],"prurito":["italian"],"psiche":["italian"],"pubblico":["italian"],"pudica":["italian"],"pugilato":["italian"],"pugno":["italian"],"pulce":["italian"],"pulito":["italian"],"pulsante":["italian"],"puntare":["italian"],"pupazzo":["italian"],"pupilla":["italian"],"puro":["italian"],"quadro":["italian"],"qualcosa":["italian"],"quasi":["italian"],"querela":["italian"],"quota":["italian"],"raccolto":["italian"],"raddoppio":["italian"],"radicale":["italian"],"radunato":["italian"],"raffica":["italian"],"ragazzo":["italian"],"ragione":["italian"],"ragno":["italian"],"ramarro":["italian"],"ramingo":["italian"],"ramo":["italian"],"randagio":["italian"],"rantolare":["italian"],"rapato":["italian"],"rapina":["italian"],"rappreso":["italian"],"rasatura":["italian"],"raschiato":["italian"],"rasente":["italian"],"rassegna":["italian"],"rastrello":["italian"],"rata":["italian"],"ravveduto":["italian"],"reale":["italian"],"recepire":["italian"],"recinto":["italian"],"recluta":["italian"],"recondito":["italian"],"recupero":["italian"],"reddito":["italian"],"redimere":["italian"],"regalato":["italian"],"registro":["italian"],"regola":["italian"],"regresso":["italian"],"relazione":["italian"],"remare":["italian"],"remoto":["italian"],"renna":["italian"],"replica":["italian"],"reprimere":["italian"],"reputare":["italian"],"resa":["italian"],"residente":["italian"],"responso":["italian"],"restauro":["italian"],"rete":["italian"],"retina":["italian"],"retorica":["italian"],"rettifica":["italian"],"revocato":["italian"],"riassunto":["italian"],"ribadire":["italian"],"ribelle":["italian"],"ribrezzo":["italian"],"ricarica":["italian"],"ricco":["italian"],"ricevere":["italian"],"riciclato":["italian"],"ricordo":["italian"],"ricreduto":["italian"],"ridicolo":["italian"],"ridurre":["italian"],"rifasare":["italian"],"riflesso":["italian"],"riforma":["italian"],"rifugio":["italian"],"rigare":["italian"],"rigettato":["italian"],"righello":["italian"],"rilassato":["italian"],"rilevato":["italian"],"rimanere":["italian"],"rimbalzo":["italian"],"rimedio":["italian"],"rimorchio":["italian"],"rinascita":["italian"],"rincaro":["italian"],"rinforzo":["italian"],"rinnovo":["italian"],"rinomato":["italian"],"rinsavito":["italian"],"rintocco":["italian"],"rinuncia":["italian"],"rinvenire":["italian"],"riparato":["italian"],"ripetuto":["italian"],"ripieno":["italian"],"riportare":["italian"],"ripresa":["italian"],"ripulire":["italian"],"risata":["italian"],"rischio":["italian"],"riserva":["italian"],"risibile":["italian"],"riso":["italian"],"rispetto":["italian"],"ristoro":["italian"],"risultato":["italian"],"risvolto":["italian"],"ritardo":["italian"],"ritegno":["italian"],"ritmico":["italian"],"ritrovo":["italian"],"riunione":["italian"],"riva":["italian"],"riverso":["italian"],"rivincita":["italian"],"rivolto":["italian"],"rizoma":["italian"],"roba":["italian"],"robotico":["italian"],"robusto":["italian"],"roccia":["italian"],"roco":["italian"],"rodaggio":["italian"],"rodere":["italian"],"roditore":["italian"],"rogito":["italian"],"rollio":["italian"],"romantico":["italian"],"rompere":["italian"],"ronzio":["italian"],"rosolare":["italian"],"rospo":["italian"],"rotante":["italian"],"rotondo":["italian"],"rotula":["italian"],"rovescio":["italian"],"rubizzo":["italian"],"rubrica":["italian"],"ruga":["italian"],"rullino":["italian"],"rumine":["italian"],"rumoroso":["italian"],"ruolo":["italian"],"rupe":["italian"],"russare":["italian"],"rustico":["italian"],"sabato":["italian"],"sabbiare":["italian"],"sabotato":["italian"],"sagoma":["italian"],"salasso":["italian"],"saldatura":["italian"],"salgemma":["italian"],"salivare":["italian"],"salmone":["italian"],"salone":["italian"],"saltare":["italian"],"saluto":["italian"],"salvo":["italian"],"sapere":["italian"],"sapido":["italian"],"saporito":["italian"],"saraceno":["italian"],"sarcasmo":["italian"],"sarto":["italian"],"sassoso":["italian"],"satellite":["italian"],"satira":["italian"],"satollo":["italian"],"saturno":["italian"],"savana":["italian"],"savio":["italian"],"saziato":["italian"],"sbadiglio":["italian"],"sbalzo":["italian"],"sbancato":["italian"],"sbarra":["italian"],"sbattere":["italian"],"sbavare":["italian"],"sbendare":["italian"],"sbirciare":["italian"],"sbloccato":["italian"],"sbocciato":["italian"],"sbrinare":["italian"],"sbruffone":["italian"],"sbuffare":["italian"],"scabroso":["italian"],"scadenza":["italian"],"scala":["italian"],"scambiare":["italian"],"scandalo":["italian"],"scapola":["italian"],"scarso":["italian"],"scatenare":["italian"],"scavato":["italian"],"scelto":["italian"],"scenico":["italian"],"scettro":["italian"],"scheda":["italian"],"schiena":["italian"],"sciarpa":["italian"],"scienza":["italian"],"scindere":["italian"],"scippo":["italian"],"sciroppo":["italian"],"scivolo":["italian"],"sclerare":["italian"],"scodella":["italian"],"scolpito":["italian"],"scomparto":["italian"],"sconforto":["italian"],"scoprire":["italian"],"scorta":["italian"],"scossone":["italian"],"scozzese":["italian"],"scriba":["italian"],"scrollare":["italian"],"scrutinio":["italian"],"scuderia":["italian"],"scultore":["italian"],"scuola":["italian"],"scuro":["italian"],"scusare":["italian"],"sdebitare":["italian"],"sdoganare":["italian"],"seccatura":["italian"],"secondo":["italian"],"sedano":["italian"],"seggiola":["italian"],"segnalato":["italian"],"segregato":["italian"],"seguito":["italian"],"selciato":["italian"],"selettivo":["italian"],"sella":["italian"],"selvaggio":["italian"],"semaforo":["italian"],"sembrare":["italian"],"seme":["italian"],"seminato":["italian"],"sempre":["italian"],"senso":["italian"],"sentire":["italian"],"sepolto":["italian"],"sequenza":["italian"],"serata":["italian"],"serbato":["italian"],"sereno":["italian"],"serio":["italian"],"serpente":["italian"],"serraglio":["italian"],"servire":["italian"],"sestina":["italian"],"setola":["italian"],"settimana":["italian"],"sfacelo":["italian"],"sfaldare":["italian"],"sfamato":["italian"],"sfarzoso":["italian"],"sfaticato":["italian"],"sfera":["italian"],"sfida":["italian"],"sfilato":["italian"],"sfinge":["italian"],"sfocato":["italian"],"sfoderare":["italian"],"sfogo":["italian"],"sfoltire":["italian"],"sforzato":["italian"],"sfratto":["italian"],"sfruttato":["italian"],"sfuggito":["italian"],"sfumare":["italian"],"sfuso":["italian"],"sgabello":["italian"],"sgarbato":["italian"],"sgonfiare":["italian"],"sgorbio":["italian"],"sgrassato":["italian"],"sguardo":["italian"],"sibilo":["italian"],"siccome":["italian"],"sierra":["italian"],"sigla":["italian"],"signore":["italian"],"silenzio":["italian"],"sillaba":["italian"],"simbolo":["italian"],"simpatico":["italian"],"simulato":["italian"],"sinfonia":["italian"],"singolo":["italian"],"sinistro":["italian"],"sino":["italian"],"sintesi":["italian"],"sinusoide":["italian"],"sipario":["italian"],"sisma":["italian"],"sistole":["italian"],"situato":["italian"],"slitta":["italian"],"slogatura":["italian"],"sloveno":["italian"],"smarrito":["italian"],"smemorato":["italian"],"smentito":["italian"],"smeraldo":["italian"],"smilzo":["italian"],"smontare":["italian"],"smottato":["italian"],"smussato":["italian"],"snellire":["italian"],"snervato":["italian"],"snodo":["italian"],"sobbalzo":["italian"],"sobrio":["italian"],"soccorso":["italian"],"sociale":["italian"],"sodale":["italian"],"soffitto":["italian"],"sogno":["italian"],"soldato":["italian"],"solenne":["italian"],"solido":["italian"],"sollazzo":["italian"],"solo":["italian"],"solubile":["italian"],"solvente":["italian"],"somatico":["italian"],"somma":["italian"],"sonda":["italian"],"sonetto":["italian"],"sonnifero":["italian"],"sopire":["italian"],"soppeso":["italian"],"sopra":["italian"],"sorgere":["italian"],"sorpasso":["italian"],"sorriso":["italian"],"sorso":["italian"],"sorteggio":["italian"],"sorvolato":["italian"],"sospiro":["italian"],"sosta":["italian"],"sottile":["italian"],"spada":["italian"],"spalla":["italian"],"spargere":["italian"],"spatola":["italian"],"spavento":["italian"],"spazzola":["italian"],"specie":["italian"],"spedire":["italian"],"spegnere":["italian"],"spelatura":["italian"],"speranza":["italian"],"spessore":["italian"],"spettrale":["italian"],"spezzato":["italian"],"spia":["italian"],"spigoloso":["italian"],"spillato":["italian"],"spinoso":["italian"],"spirale":["italian"],"splendido":["italian"],"sportivo":["italian"],"sposo":["italian"],"spranga":["italian"],"sprecare":["italian"],"spronato":["italian"],"spruzzo":["italian"],"spuntino":["italian"],"squillo":["italian"],"sradicare":["italian"],"srotolato":["italian"],"stabile":["italian"],"stacco":["italian"],"staffa":["italian"],"stagnare":["italian"],"stampato":["italian"],"stantio":["italian"],"starnuto":["italian"],"stasera":["italian"],"statuto":["italian"],"stelo":["italian"],"steppa":["italian"],"sterzo":["italian"],"stiletto":["italian"],"stima":["italian"],"stirpe":["italian"],"stivale":["italian"],"stizzoso":["italian"],"stonato":["italian"],"storico":["italian"],"strappo":["italian"],"stregato":["italian"],"stridulo":["italian"],"strozzare":["italian"],"strutto":["italian"],"stuccare":["italian"],"stufo":["italian"],"stupendo":["italian"],"subentro":["italian"],"succoso":["italian"],"sudore":["italian"],"suggerito":["italian"],"sugo":["italian"],"sultano":["italian"],"suonare":["italian"],"superbo":["italian"],"supporto":["italian"],"surgelato":["italian"],"surrogato":["italian"],"sussurro":["italian"],"sutura":["italian"],"svagare":["italian"],"svedese":["italian"],"sveglio":["italian"],"svelare":["italian"],"svenuto":["italian"],"svezia":["italian"],"sviluppo":["italian"],"svista":["italian"],"svizzera":["italian"],"svolta":["italian"],"svuotare":["italian"],"tabacco":["italian"],"tabulato":["italian"],"tacciare":["italian"],"taciturno":["italian"],"tale":["italian"],"talismano":["italian"],"tampone":["italian"],"tannino":["italian"],"tara":["italian"],"tardivo":["italian"],"targato":["italian"],"tariffa":["italian"],"tarpare":["italian"],"tartaruga":["italian"],"tasto":["italian"],"tattico":["italian"],"taverna":["italian"],"tavolata":["italian"],"tazza":["italian"],"teca":["italian"],"tecnico":["italian"],"telefono":["italian"],"temerario":["italian"],"tempo":["italian","turkish"],"temuto":["italian"],"tendone":["italian"],"tenero":["italian"],"tensione":["italian"],"tentacolo":["italian"],"teorema":["italian"],"terme":["italian"],"terrazzo":["italian"],"terzetto":["italian"],"tesi":["italian"],"tesserato":["italian"],"testato":["italian"],"tetro":["italian"],"tettoia":["italian"],"tifare":["italian"],"tigella":["italian"],"timbro":["italian"],"tinto":["italian"],"tipico":["italian"],"tipografo":["italian"],"tiraggio":["italian"],"tiro":["italian"],"titanio":["italian"],"titolo":["italian"],"titubante":["italian"],"tizio":["italian"],"tizzone":["italian"],"toccare":["italian"],"tollerare":["italian"],"tolto":["italian"],"tombola":["italian"],"tomo":["italian"],"tonfo":["italian"],"tonsilla":["italian"],"topazio":["italian"],"topologia":["italian"],"toppa":["italian"],"torba":["italian"],"tornare":["italian"],"torrone":["italian"],"tortora":["italian"],"toscano":["italian"],"tossire":["italian"],"tostatura":["italian"],"totano":["italian"],"trabocco":["italian"],"trachea":["italian"],"trafila":["italian"],"tragedia":["italian"],"tralcio":["italian"],"tramonto":["italian"],"transito":["italian"],"trapano":["italian"],"trarre":["italian"],"trasloco":["italian"],"trattato":["italian"],"trave":["italian"],"treccia":["italian"],"tremolio":["italian"],"trespolo":["italian"],"tributo":["italian"],"tricheco":["italian"],"trifoglio":["italian"],"trillo":["italian"],"trincea":["italian"],"trio":["italian"],"tristezza":["italian"],"triturato":["italian"],"trivella":["italian"],"tromba":["italian"],"trono":["italian"],"troppo":["italian"],"trottola":["italian"],"trovare":["italian"],"truccato":["italian"],"tubatura":["italian"],"tuffato":["italian"],"tulipano":["italian"],"tumulto":["italian"],"tunisia":["italian"],"turbare":["italian"],"turchino":["italian"],"tuta":["italian"],"tutela":["italian"],"ubicato":["italian"],"uccello":["italian"],"uccisore":["italian"],"udire":["italian"],"uditivo":["italian"],"uffa":["italian"],"ufficio":["italian"],"uguale":["italian"],"ulisse":["italian"],"ultimato":["italian"],"umano":["italian"],"umile":["italian"],"umorismo":["italian"],"uncinetto":["italian"],"ungere":["italian"],"ungherese":["italian"],"unicorno":["italian"],"unificato":["italian"],"unisono":["italian"],"unitario":["italian"],"unte":["italian"],"uovo":["italian"],"upupa":["italian"],"uragano":["italian"],"urgenza":["italian"],"urlo":["italian"],"usanza":["italian"],"usato":["italian"],"uscito":["italian"],"usignolo":["italian"],"usuraio":["italian"],"utensile":["italian"],"utilizzo":["italian"],"utopia":["italian"],"vacante":["italian"],"vaccinato":["italian"],"vagabondo":["italian"],"vagliato":["italian"],"valanga":["italian"],"valgo":["italian"],"valico":["italian"],"valletta":["italian"],"valoroso":["italian"],"valutare":["italian"],"valvola":["italian"],"vampata":["italian"],"vangare":["italian"],"vanitoso":["italian"],"vano":["italian"],"vantaggio":["italian"],"vanvera":["italian"],"vapore":["italian"],"varano":["italian"],"varcato":["italian"],"variante":["italian"],"vasca":["italian"],"vedetta":["italian"],"vedova":["italian"],"veduto":["italian"],"vegetale":["italian"],"veicolo":["italian"],"velcro":["italian"],"velina":["italian"],"velluto":["italian"],"veloce":["italian"],"venato":["italian"],"vendemmia":["italian"],"vento":["italian"],"verace":["italian"],"verbale":["italian"],"vergogna":["italian"],"verifica":["italian"],"vero":["italian"],"verruca":["italian"],"verticale":["italian"],"vescica":["italian"],"vessillo":["italian"],"vestale":["italian"],"veterano":["italian"],"vetrina":["italian"],"vetusto":["italian"],"viandante":["italian"],"vibrante":["italian"],"vicenda":["italian"],"vichingo":["italian"],"vicinanza":["italian"],"vidimare":["italian"],"vigilia":["italian"],"vigneto":["italian"],"vigore":["italian"],"vile":["italian"],"villano":["italian"],"vimini":["italian"],"vincitore":["italian"],"viola":["italian"],"vipera":["italian"],"virgola":["italian"],"virologo":["italian"],"virulento":["italian"],"viscoso":["italian"],"visione":["italian"],"vispo":["italian"],"vissuto":["italian"],"visura":["italian"],"vita":["italian"],"vitello":["italian"],"vittima":["italian"],"vivanda":["italian"],"vivido":["italian"],"viziare":["italian"],"voce":["italian"],"voga":["italian"],"volatile":["italian"],"volere":["italian"],"volpe":["italian"],"voragine":["italian"],"vulcano":["italian"],"zampogna":["italian"],"zanna":["italian"],"zappato":["italian"],"zattera":["italian"],"zavorra":["italian"],"zefiro":["italian"],"zelante":["italian"],"zelo":["italian"],"zenzero":["italian"],"zerbino":["italian"],"zibetto":["italian"],"zinco":["italian"],"zircone":["italian"],"zitto":["italian"],"zolla":["italian"],"zotico":["italian"],"zucchero":["italian"],"zufolo":["italian"],"zulu":["italian"],"zuppa":["italian"],"あいこくしん":["japanese"],"あいさつ":["japanese"],"あいだ":["japanese"],"あおぞら":["japanese"],"あかちゃん":["japanese"],"あきる":["japanese"],"あけがた":["japanese"],"あける":["japanese"],"あこがれる":["japanese"],"あさい":["japanese"],"あさひ":["japanese"],"あしあと":["japanese"],"あじわう":["japanese"],"あずかる":["japanese"],"あずき":["japanese"],"あそぶ":["japanese"],"あたえる":["japanese"],"あたためる":["japanese"],"あたりまえ":["japanese"],"あたる":["japanese"],"あつい":["japanese"],"あつかう":["japanese"],"あっしゅく":["japanese"],"あつまり":["japanese"],"あつめる":["japanese"],"あてな":["japanese"],"あてはまる":["japanese"],"あひる":["japanese"],"あぶら":["japanese"],"あぶる":["japanese"],"あふれる":["japanese"],"あまい":["japanese"],"あまど":["japanese"],"あまやかす":["japanese"],"あまり":["japanese"],"あみもの":["japanese"],"あめりか":["japanese"],"あやまる":["japanese"],"あゆむ":["japanese"],"あらいぐま":["japanese"],"あらし":["japanese"],"あらすじ":["japanese"],"あらためる":["japanese"],"あらゆる":["japanese"],"あらわす":["japanese"],"ありがとう":["japanese"],"あわせる":["japanese"],"あわてる":["japanese"],"あんい":["japanese"],"あんがい":["japanese"],"あんこ":["japanese"],"あんぜん":["japanese"],"あんてい":["japanese"],"あんない":["japanese"],"あんまり":["japanese"],"いいだす":["japanese"],"いおん":["japanese"],"いがい":["japanese"],"いがく":["japanese"],"いきおい":["japanese"],"いきなり":["japanese"],"いきもの":["japanese"],"いきる":["japanese"],"いくじ":["japanese"],"いくぶん":["japanese"],"いけばな":["japanese"],"いけん":["japanese"],"いこう":["japanese"],"いこく":["japanese"],"いこつ":["japanese"],"いさましい":["japanese"],"いさん":["japanese"],"いしき":["japanese"],"いじゅう":["japanese"],"いじょう":["japanese"],"いじわる":["japanese"],"いずみ":["japanese"],"いずれ":["japanese"],"いせい":["japanese"],"いせえび":["japanese"],"いせかい":["japanese"],"いせき":["japanese"],"いぜん":["japanese"],"いそうろう":["japanese"],"いそがしい":["japanese"],"いだい":["japanese"],"いだく":["japanese"],"いたずら":["japanese"],"いたみ":["japanese"],"いたりあ":["japanese"],"いちおう":["japanese"],"いちじ":["japanese"],"いちど":["japanese"],"いちば":["japanese"],"いちぶ":["japanese"],"いちりゅう":["japanese"],"いつか":["japanese"],"いっしゅん":["japanese"],"いっせい":["japanese"],"いっそう":["japanese"],"いったん":["japanese"],"いっち":["japanese"],"いってい":["japanese"],"いっぽう":["japanese"],"いてざ":["japanese"],"いてん":["japanese"],"いどう":["japanese"],"いとこ":["japanese"],"いない":["japanese"],"いなか":["japanese"],"いねむり":["japanese"],"いのち":["japanese"],"いのる":["japanese"],"いはつ":["japanese"],"いばる":["japanese"],"いはん":["japanese"],"いびき":["japanese"],"いひん":["japanese"],"いふく":["japanese"],"いへん":["japanese"],"いほう":["japanese"],"いみん":["japanese"],"いもうと":["japanese"],"いもたれ":["japanese"],"いもり":["japanese"],"いやがる":["japanese"],"いやす":["japanese"],"いよかん":["japanese"],"いよく":["japanese"],"いらい":["japanese"],"いらすと":["japanese"],"いりぐち":["japanese"],"いりょう":["japanese"],"いれい":["japanese"],"いれもの":["japanese"],"いれる":["japanese"],"いろえんぴつ":["japanese"],"いわい":["japanese"],"いわう":["japanese"],"いわかん":["japanese"],"いわば":["japanese"],"いわゆる":["japanese"],"いんげんまめ":["japanese"],"いんさつ":["japanese"],"いんしょう":["japanese"],"いんよう":["japanese"],"うえき":["japanese"],"うえる":["japanese"],"うおざ":["japanese"],"うがい":["japanese"],"うかぶ":["japanese"],"うかべる":["japanese"],"うきわ":["japanese"],"うくらいな":["japanese"],"うくれれ":["japanese"],"うけたまわる":["japanese"],"うけつけ":["japanese"],"うけとる":["japanese"],"うけもつ":["japanese"],"うける":["japanese"],"うごかす":["japanese"],"うごく":["japanese"],"うこん":["japanese"],"うさぎ":["japanese"],"うしなう":["japanese"],"うしろがみ":["japanese"],"うすい":["japanese"],"うすぎ":["japanese"],"うすぐらい":["japanese"],"うすめる":["japanese"],"うせつ":["japanese"],"うちあわせ":["japanese"],"うちがわ":["japanese"],"うちき":["japanese"],"うちゅう":["japanese"],"うっかり":["japanese"],"うつくしい":["japanese"],"うったえる":["japanese"],"うつる":["japanese"],"うどん":["japanese"],"うなぎ":["japanese"],"うなじ":["japanese"],"うなずく":["japanese"],"うなる":["japanese"],"うねる":["japanese"],"うのう":["japanese"],"うぶげ":["japanese"],"うぶごえ":["japanese"],"うまれる":["japanese"],"うめる":["japanese"],"うもう":["japanese"],"うやまう":["japanese"],"うよく":["japanese"],"うらがえす":["japanese"],"うらぐち":["japanese"],"うらない":["japanese"],"うりあげ":["japanese"],"うりきれ":["japanese"],"うるさい":["japanese"],"うれしい":["japanese"],"うれゆき":["japanese"],"うれる":["japanese"],"うろこ":["japanese"],"うわき":["japanese"],"うわさ":["japanese"],"うんこう":["japanese"],"うんちん":["japanese"],"うんてん":["japanese"],"うんどう":["japanese"],"えいえん":["japanese"],"えいが":["japanese"],"えいきょう":["japanese"],"えいご":["japanese"],"えいせい":["japanese"],"えいぶん":["japanese"],"えいよう":["japanese"],"えいわ":["japanese"],"えおり":["japanese"],"えがお":["japanese"],"えがく":["japanese"],"えきたい":["japanese"],"えくせる":["japanese"],"えしゃく":["japanese"],"えすて":["japanese"],"えつらん":["japanese"],"えのぐ":["japanese"],"えほうまき":["japanese"],"えほん":["japanese"],"えまき":["japanese"],"えもじ":["japanese"],"えもの":["japanese"],"えらい":["japanese"],"えらぶ":["japanese"],"えりあ":["japanese"],"えんえん":["japanese"],"えんかい":["japanese"],"えんぎ":["japanese"],"えんげき":["japanese"],"えんしゅう":["japanese"],"えんぜつ":["japanese"],"えんそく":["japanese"],"えんちょう":["japanese"],"えんとつ":["japanese"],"おいかける":["japanese"],"おいこす":["japanese"],"おいしい":["japanese"],"おいつく":["japanese"],"おうえん":["japanese"],"おうさま":["japanese"],"おうじ":["japanese"],"おうせつ":["japanese"],"おうたい":["japanese"],"おうふく":["japanese"],"おうべい":["japanese"],"おうよう":["japanese"],"おえる":["japanese"],"おおい":["japanese"],"おおう":["japanese"],"おおどおり":["japanese"],"おおや":["japanese"],"おおよそ":["japanese"],"おかえり":["japanese"],"おかず":["japanese"],"おがむ":["japanese"],"おかわり":["japanese"],"おぎなう":["japanese"],"おきる":["japanese"],"おくさま":["japanese"],"おくじょう":["japanese"],"おくりがな":["japanese"],"おくる":["japanese"],"おくれる":["japanese"],"おこす":["japanese"],"おこなう":["japanese"],"おこる":["japanese"],"おさえる":["japanese"],"おさない":["japanese"],"おさめる":["japanese"],"おしいれ":["japanese"],"おしえる":["japanese"],"おじぎ":["japanese"],"おじさん":["japanese"],"おしゃれ":["japanese"],"おそらく":["japanese"],"おそわる":["japanese"],"おたがい":["japanese"],"おたく":["japanese"],"おだやか":["japanese"],"おちつく":["japanese"],"おっと":["japanese"],"おつり":["japanese"],"おでかけ":["japanese"],"おとしもの":["japanese"],"おとなしい":["japanese"],"おどり":["japanese"],"おどろかす":["japanese"],"おばさん":["japanese"],"おまいり":["japanese"],"おめでとう":["japanese"],"おもいで":["japanese"],"おもう":["japanese"],"おもたい":["japanese"],"おもちゃ":["japanese"],"おやつ":["japanese"],"おやゆび":["japanese"],"およぼす":["japanese"],"おらんだ":["japanese"],"おろす":["japanese"],"おんがく":["japanese"],"おんけい":["japanese"],"おんしゃ":["japanese"],"おんせん":["japanese"],"おんだん":["japanese"],"おんちゅう":["japanese"],"おんどけい":["japanese"],"かあつ":["japanese"],"かいが":["japanese"],"がいき":["japanese"],"がいけん":["japanese"],"がいこう":["japanese"],"かいさつ":["japanese"],"かいしゃ":["japanese"],"かいすいよく":["japanese"],"かいぜん":["japanese"],"かいぞうど":["japanese"],"かいつう":["japanese"],"かいてん":["japanese"],"かいとう":["japanese"],"かいふく":["japanese"],"がいへき":["japanese"],"かいほう":["japanese"],"かいよう":["japanese"],"がいらい":["japanese"],"かいわ":["japanese"],"かえる":["japanese"],"かおり":["japanese"],"かかえる":["japanese"],"かがく":["japanese"],"かがし":["japanese"],"かがみ":["japanese"],"かくご":["japanese"],"かくとく":["japanese"],"かざる":["japanese"],"がぞう":["japanese"],"かたい":["japanese"],"かたち":["japanese"],"がちょう":["japanese"],"がっきゅう":["japanese"],"がっこう":["japanese"],"がっさん":["japanese"],"がっしょう":["japanese"],"かなざわし":["japanese"],"かのう":["japanese"],"がはく":["japanese"],"かぶか":["japanese"],"かほう":["japanese"],"かほご":["japanese"],"かまう":["japanese"],"かまぼこ":["japanese"],"かめれおん":["japanese"],"かゆい":["japanese"],"かようび":["japanese"],"からい":["japanese"],"かるい":["japanese"],"かろう":["japanese"],"かわく":["japanese"],"かわら":["japanese"],"がんか":["japanese"],"かんけい":["japanese"],"かんこう":["japanese"],"かんしゃ":["japanese"],"かんそう":["japanese"],"かんたん":["japanese"],"かんち":["japanese"],"がんばる":["japanese"],"きあい":["japanese"],"きあつ":["japanese"],"きいろ":["japanese"],"ぎいん":["japanese"],"きうい":["japanese"],"きうん":["japanese"],"きえる":["japanese"],"きおう":["japanese"],"きおく":["japanese"],"きおち":["japanese"],"きおん":["japanese"],"きかい":["japanese"],"きかく":["japanese"],"きかんしゃ":["japanese"],"ききて":["japanese"],"きくばり":["japanese"],"きくらげ":["japanese"],"きけんせい":["japanese"],"きこう":["japanese"],"きこえる":["japanese"],"きこく":["japanese"],"きさい":["japanese"],"きさく":["japanese"],"きさま":["japanese"],"きさらぎ":["japanese"],"ぎじかがく":["japanese"],"ぎしき":["japanese"],"ぎじたいけん":["japanese"],"ぎじにってい":["japanese"],"ぎじゅつしゃ":["japanese"],"きすう":["japanese"],"きせい":["japanese"],"きせき":["japanese"],"きせつ":["japanese"],"きそう":["japanese"],"きぞく":["japanese"],"きぞん":["japanese"],"きたえる":["japanese"],"きちょう":["japanese"],"きつえん":["japanese"],"ぎっちり":["japanese"],"きつつき":["japanese"],"きつね":["japanese"],"きてい":["japanese"],"きどう":["japanese"],"きどく":["japanese"],"きない":["japanese"],"きなが":["japanese"],"きなこ":["japanese"],"きぬごし":["japanese"],"きねん":["japanese"],"きのう":["japanese"],"きのした":["japanese"],"きはく":["japanese"],"きびしい":["japanese"],"きひん":["japanese"],"きふく":["japanese"],"きぶん":["japanese"],"きぼう":["japanese"],"きほん":["japanese"],"きまる":["japanese"],"きみつ":["japanese"],"きむずかしい":["japanese"],"きめる":["japanese"],"きもだめし":["japanese"],"きもち":["japanese"],"きもの":["japanese"],"きゃく":["japanese"],"きやく":["japanese"],"ぎゅうにく":["japanese"],"きよう":["japanese"],"きょうりゅう":["japanese"],"きらい":["japanese"],"きらく":["japanese"],"きりん":["japanese"],"きれい":["japanese"],"きれつ":["japanese"],"きろく":["japanese"],"ぎろん":["japanese"],"きわめる":["japanese"],"ぎんいろ":["japanese"],"きんかくじ":["japanese"],"きんじょ":["japanese"],"きんようび":["japanese"],"ぐあい":["japanese"],"くいず":["japanese"],"くうかん":["japanese"],"くうき":["japanese"],"くうぐん":["japanese"],"くうこう":["japanese"],"ぐうせい":["japanese"],"くうそう":["japanese"],"ぐうたら":["japanese"],"くうふく":["japanese"],"くうぼ":["japanese"],"くかん":["japanese"],"くきょう":["japanese"],"くげん":["japanese"],"ぐこう":["japanese"],"くさい":["japanese"],"くさき":["japanese"],"くさばな":["japanese"],"くさる":["japanese"],"くしゃみ":["japanese"],"くしょう":["japanese"],"くすのき":["japanese"],"くすりゆび":["japanese"],"くせげ":["japanese"],"くせん":["japanese"],"ぐたいてき":["japanese"],"くださる":["japanese"],"くたびれる":["japanese"],"くちこみ":["japanese"],"くちさき":["japanese"],"くつした":["japanese"],"ぐっすり":["japanese"],"くつろぐ":["japanese"],"くとうてん":["japanese"],"くどく":["japanese"],"くなん":["japanese"],"くねくね":["japanese"],"くのう":["japanese"],"くふう":["japanese"],"くみあわせ":["japanese"],"くみたてる":["japanese"],"くめる":["japanese"],"くやくしょ":["japanese"],"くらす":["japanese"],"くらべる":["japanese"],"くるま":["japanese"],"くれる":["japanese"],"くろう":["japanese"],"くわしい":["japanese"],"ぐんかん":["japanese"],"ぐんしょく":["japanese"],"ぐんたい":["japanese"],"ぐんて":["japanese"],"けあな":["japanese"],"けいかく":["japanese"],"けいけん":["japanese"],"けいこ":["japanese"],"けいさつ":["japanese"],"げいじゅつ":["japanese"],"けいたい":["japanese"],"げいのうじん":["japanese"],"けいれき":["japanese"],"けいろ":["japanese"],"けおとす":["japanese"],"けおりもの":["japanese"],"げきか":["japanese"],"げきげん":["japanese"],"げきだん":["japanese"],"げきちん":["japanese"],"げきとつ":["japanese"],"げきは":["japanese"],"げきやく":["japanese"],"げこう":["japanese"],"げこくじょう":["japanese"],"げざい":["japanese"],"けさき":["japanese"],"げざん":["japanese"],"けしき":["japanese"],"けしごむ":["japanese"],"けしょう":["japanese"],"げすと":["japanese"],"けたば":["japanese"],"けちゃっぷ":["japanese"],"けちらす":["japanese"],"けつあつ":["japanese"],"けつい":["japanese"],"けつえき":["japanese"],"けっこん":["japanese"],"けつじょ":["japanese"],"けっせき":["japanese"],"けってい":["japanese"],"けつまつ":["japanese"],"げつようび":["japanese"],"げつれい":["japanese"],"けつろん":["japanese"],"げどく":["japanese"],"けとばす":["japanese"],"けとる":["japanese"],"けなげ":["japanese"],"けなす":["japanese"],"けなみ":["japanese"],"けぬき":["japanese"],"げねつ":["japanese"],"けねん":["japanese"],"けはい":["japanese"],"げひん":["japanese"],"けぶかい":["japanese"],"げぼく":["japanese"],"けまり":["japanese"],"けみかる":["japanese"],"けむし":["japanese"],"けむり":["japanese"],"けもの":["japanese"],"けらい":["japanese"],"けろけろ":["japanese"],"けわしい":["japanese"],"けんい":["japanese"],"けんえつ":["japanese"],"けんお":["japanese"],"けんか":["japanese"],"げんき":["japanese"],"けんげん":["japanese"],"けんこう":["japanese"],"けんさく":["japanese"],"けんしゅう":["japanese"],"けんすう":["japanese"],"げんそう":["japanese"],"けんちく":["japanese"],"けんてい":["japanese"],"けんとう":["japanese"],"けんない":["japanese"],"けんにん":["japanese"],"げんぶつ":["japanese"],"けんま":["japanese"],"けんみん":["japanese"],"けんめい":["japanese"],"けんらん":["japanese"],"けんり":["japanese"],"こあくま":["japanese"],"こいぬ":["japanese"],"こいびと":["japanese"],"ごうい":["japanese"],"こうえん":["japanese"],"こうおん":["japanese"],"こうかん":["japanese"],"ごうきゅう":["japanese"],"ごうけい":["japanese"],"こうこう":["japanese"],"こうさい":["japanese"],"こうじ":["japanese"],"こうすい":["japanese"],"ごうせい":["japanese"],"こうそく":["japanese"],"こうたい":["japanese"],"こうちゃ":["japanese"],"こうつう":["japanese"],"こうてい":["japanese"],"こうどう":["japanese"],"こうない":["japanese"],"こうはい":["japanese"],"ごうほう":["japanese"],"ごうまん":["japanese"],"こうもく":["japanese"],"こうりつ":["japanese"],"こえる":["japanese"],"こおり":["japanese"],"ごかい":["japanese"],"ごがつ":["japanese"],"ごかん":["japanese"],"こくご":["japanese"],"こくさい":["japanese"],"こくとう":["japanese"],"こくない":["japanese"],"こくはく":["japanese"],"こぐま":["japanese"],"こけい":["japanese"],"こける":["japanese"],"ここのか":["japanese"],"こころ":["japanese"],"こさめ":["japanese"],"こしつ":["japanese"],"こすう":["japanese"],"こせい":["japanese"],"こせき":["japanese"],"こぜん":["japanese"],"こそだて":["japanese"],"こたい":["japanese"],"こたえる":["japanese"],"こたつ":["japanese"],"こちょう":["japanese"],"こっか":["japanese"],"こつこつ":["japanese"],"こつばん":["japanese"],"こつぶ":["japanese"],"こてい":["japanese"],"こてん":["japanese"],"ことがら":["japanese"],"ことし":["japanese"],"ことば":["japanese"],"ことり":["japanese"],"こなごな":["japanese"],"こねこね":["japanese"],"このまま":["japanese"],"このみ":["japanese"],"このよ":["japanese"],"ごはん":["japanese"],"こひつじ":["japanese"],"こふう":["japanese"],"こふん":["japanese"],"こぼれる":["japanese"],"ごまあぶら":["japanese"],"こまかい":["japanese"],"ごますり":["japanese"],"こまつな":["japanese"],"こまる":["japanese"],"こむぎこ":["japanese"],"こもじ":["japanese"],"こもち":["japanese"],"こもの":["japanese"],"こもん":["japanese"],"こやく":["japanese"],"こやま":["japanese"],"こゆう":["japanese"],"こゆび":["japanese"],"こよい":["japanese"],"こよう":["japanese"],"こりる":["japanese"],"これくしょん":["japanese"],"ころっけ":["japanese"],"こわもて":["japanese"],"こわれる":["japanese"],"こんいん":["japanese"],"こんかい":["japanese"],"こんき":["japanese"],"こんしゅう":["japanese"],"こんすい":["japanese"],"こんだて":["japanese"],"こんとん":["japanese"],"こんなん":["japanese"],"こんびに":["japanese"],"こんぽん":["japanese"],"こんまけ":["japanese"],"こんや":["japanese"],"こんれい":["japanese"],"こんわく":["japanese"],"ざいえき":["japanese"],"さいかい":["japanese"],"さいきん":["japanese"],"ざいげん":["japanese"],"ざいこ":["japanese"],"さいしょ":["japanese"],"さいせい":["japanese"],"ざいたく":["japanese"],"ざいちゅう":["japanese"],"さいてき":["japanese"],"ざいりょう":["japanese"],"さうな":["japanese"],"さかいし":["japanese"],"さがす":["japanese"],"さかな":["japanese"],"さかみち":["japanese"],"さがる":["japanese"],"さぎょう":["japanese"],"さくし":["japanese"],"さくひん":["japanese"],"さくら":["japanese"],"さこく":["japanese"],"さこつ":["japanese"],"さずかる":["japanese"],"ざせき":["japanese"],"さたん":["japanese"],"さつえい":["japanese"],"ざつおん":["japanese"],"ざっか":["japanese"],"ざつがく":["japanese"],"さっきょく":["japanese"],"ざっし":["japanese"],"さつじん":["japanese"],"ざっそう":["japanese"],"さつたば":["japanese"],"さつまいも":["japanese"],"さてい":["japanese"],"さといも":["japanese"],"さとう":["japanese"],"さとおや":["japanese"],"さとし":["japanese"],"さとる":["japanese"],"さのう":["japanese"],"さばく":["japanese"],"さびしい":["japanese"],"さべつ":["japanese"],"さほう":["japanese"],"さほど":["japanese"],"さます":["japanese"],"さみしい":["japanese"],"さみだれ":["japanese"],"さむけ":["japanese"],"さめる":["japanese"],"さやえんどう":["japanese"],"さゆう":["japanese"],"さよう":["japanese"],"さよく":["japanese"],"さらだ":["japanese"],"ざるそば":["japanese"],"さわやか":["japanese"],"さわる":["japanese"],"さんいん":["japanese"],"さんか":["japanese"],"さんきゃく":["japanese"],"さんこう":["japanese"],"さんさい":["japanese"],"ざんしょ":["japanese"],"さんすう":["japanese"],"さんせい":["japanese"],"さんそ":["japanese"],"さんち":["japanese"],"さんま":["japanese"],"さんみ":["japanese"],"さんらん":["japanese"],"しあい":["japanese"],"しあげ":["japanese"],"しあさって":["japanese"],"しあわせ":["japanese"],"しいく":["japanese"],"しいん":["japanese"],"しうち":["japanese"],"しえい":["japanese"],"しおけ":["japanese"],"しかい":["japanese"],"しかく":["japanese"],"じかん":["japanese"],"しごと":["japanese"],"しすう":["japanese"],"じだい":["japanese"],"したうけ":["japanese"],"したぎ":["japanese"],"したて":["japanese"],"したみ":["japanese"],"しちょう":["japanese"],"しちりん":["japanese"],"しっかり":["japanese"],"しつじ":["japanese"],"しつもん":["japanese"],"してい":["japanese"],"してき":["japanese"],"してつ":["japanese"],"じてん":["japanese"],"じどう":["japanese"],"しなぎれ":["japanese"],"しなもの":["japanese"],"しなん":["japanese"],"しねま":["japanese"],"しねん":["japanese"],"しのぐ":["japanese"],"しのぶ":["japanese"],"しはい":["japanese"],"しばかり":["japanese"],"しはつ":["japanese"],"しはらい":["japanese"],"しはん":["japanese"],"しひょう":["japanese"],"しふく":["japanese"],"じぶん":["japanese"],"しへい":["japanese"],"しほう":["japanese"],"しほん":["japanese"],"しまう":["japanese"],"しまる":["japanese"],"しみん":["japanese"],"しむける":["japanese"],"じむしょ":["japanese"],"しめい":["japanese"],"しめる":["japanese"],"しもん":["japanese"],"しゃいん":["japanese"],"しゃうん":["japanese"],"しゃおん":["japanese"],"じゃがいも":["japanese"],"しやくしょ":["japanese"],"しゃくほう":["japanese"],"しゃけん":["japanese"],"しゃこ":["japanese"],"しゃざい":["japanese"],"しゃしん":["japanese"],"しゃせん":["japanese"],"しゃそう":["japanese"],"しゃたい":["japanese"],"しゃちょう":["japanese"],"しゃっきん":["japanese"],"じゃま":["japanese"],"しゃりん":["japanese"],"しゃれい":["japanese"],"じゆう":["japanese"],"じゅうしょ":["japanese"],"しゅくはく":["japanese"],"じゅしん":["japanese"],"しゅっせき":["japanese"],"しゅみ":["japanese"],"しゅらば":["japanese"],"じゅんばん":["japanese"],"しょうかい":["japanese"],"しょくたく":["japanese"],"しょっけん":["japanese"],"しょどう":["japanese"],"しょもつ":["japanese"],"しらせる":["japanese"],"しらべる":["japanese"],"しんか":["japanese"],"しんこう":["japanese"],"じんじゃ":["japanese"],"しんせいじ":["japanese"],"しんちく":["japanese"],"しんりん":["japanese"],"すあげ":["japanese"],"すあし":["japanese"],"すあな":["japanese"],"ずあん":["japanese"],"すいえい":["japanese"],"すいか":["japanese"],"すいとう":["japanese"],"ずいぶん":["japanese"],"すいようび":["japanese"],"すうがく":["japanese"],"すうじつ":["japanese"],"すうせん":["japanese"],"すおどり":["japanese"],"すきま":["japanese"],"すくう":["japanese"],"すくない":["japanese"],"すける":["japanese"],"すごい":["japanese"],"すこし":["japanese"],"ずさん":["japanese"],"すずしい":["japanese"],"すすむ":["japanese"],"すすめる":["japanese"],"すっかり":["japanese"],"ずっしり":["japanese"],"ずっと":["japanese"],"すてき":["japanese"],"すてる":["japanese"],"すねる":["japanese"],"すのこ":["japanese"],"すはだ":["japanese"],"すばらしい":["japanese"],"ずひょう":["japanese"],"ずぶぬれ":["japanese"],"すぶり":["japanese"],"すふれ":["japanese"],"すべて":["japanese"],"すべる":["japanese"],"ずほう":["japanese"],"すぼん":["japanese"],"すまい":["japanese"],"すめし":["japanese"],"すもう":["japanese"],"すやき":["japanese"],"すらすら":["japanese"],"するめ":["japanese"],"すれちがう":["japanese"],"すろっと":["japanese"],"すわる":["japanese"],"すんぜん":["japanese"],"すんぽう":["japanese"],"せあぶら":["japanese"],"せいかつ":["japanese"],"せいげん":["japanese"],"せいじ":["japanese"],"せいよう":["japanese"],"せおう":["japanese"],"せかいかん":["japanese"],"せきにん":["japanese"],"せきむ":["japanese"],"せきゆ":["japanese"],"せきらんうん":["japanese"],"せけん":["japanese"],"せこう":["japanese"],"せすじ":["japanese"],"せたい":["japanese"],"せたけ":["japanese"],"せっかく":["japanese"],"せっきゃく":["japanese"],"ぜっく":["japanese"],"せっけん":["japanese"],"せっこつ":["japanese"],"せっさたくま":["japanese"],"せつぞく":["japanese"],"せつだん":["japanese"],"せつでん":["japanese"],"せっぱん":["japanese"],"せつび":["japanese"],"せつぶん":["japanese"],"せつめい":["japanese"],"せつりつ":["japanese"],"せなか":["japanese"],"せのび":["japanese"],"せはば":["japanese"],"せびろ":["japanese"],"せぼね":["japanese"],"せまい":["japanese"],"せまる":["japanese"],"せめる":["japanese"],"せもたれ":["japanese"],"せりふ":["japanese"],"ぜんあく":["japanese"],"せんい":["japanese"],"せんえい":["japanese"],"せんか":["japanese"],"せんきょ":["japanese"],"せんく":["japanese"],"せんげん":["japanese"],"ぜんご":["japanese"],"せんさい":["japanese"],"せんしゅ":["japanese"],"せんすい":["japanese"],"せんせい":["japanese"],"せんぞ":["japanese"],"せんたく":["japanese"],"せんちょう":["japanese"],"せんてい":["japanese"],"せんとう":["japanese"],"せんぬき":["japanese"],"せんねん":["japanese"],"せんぱい":["japanese"],"ぜんぶ":["japanese"],"ぜんぽう":["japanese"],"せんむ":["japanese"],"せんめんじょ":["japanese"],"せんもん":["japanese"],"せんやく":["japanese"],"せんゆう":["japanese"],"せんよう":["japanese"],"ぜんら":["japanese"],"ぜんりゃく":["japanese"],"せんれい":["japanese"],"せんろ":["japanese"],"そあく":["japanese"],"そいとげる":["japanese"],"そいね":["japanese"],"そうがんきょう":["japanese"],"そうき":["japanese"],"そうご":["japanese"],"そうしん":["japanese"],"そうだん":["japanese"],"そうなん":["japanese"],"そうび":["japanese"],"そうめん":["japanese"],"そうり":["japanese"],"そえもの":["japanese"],"そえん":["japanese"],"そがい":["japanese"],"そげき":["japanese"],"そこう":["japanese"],"そこそこ":["japanese"],"そざい":["japanese"],"そしな":["japanese"],"そせい":["japanese"],"そせん":["japanese"],"そそぐ":["japanese"],"そだてる":["japanese"],"そつう":["japanese"],"そつえん":["japanese"],"そっかん":["japanese"],"そつぎょう":["japanese"],"そっけつ":["japanese"],"そっこう":["japanese"],"そっせん":["japanese"],"そっと":["japanese"],"そとがわ":["japanese"],"そとづら":["japanese"],"そなえる":["japanese"],"そなた":["japanese"],"そふぼ":["japanese"],"そぼく":["japanese"],"そぼろ":["japanese"],"そまつ":["japanese"],"そまる":["japanese"],"そむく":["japanese"],"そむりえ":["japanese"],"そめる":["japanese"],"そもそも":["japanese"],"そよかぜ":["japanese"],"そらまめ":["japanese"],"そろう":["japanese"],"そんかい":["japanese"],"そんけい":["japanese"],"そんざい":["japanese"],"そんしつ":["japanese"],"そんぞく":["japanese"],"そんちょう":["japanese"],"ぞんび":["japanese"],"ぞんぶん":["japanese"],"そんみん":["japanese"],"たあい":["japanese"],"たいいん":["japanese"],"たいうん":["japanese"],"たいえき":["japanese"],"たいおう":["japanese"],"だいがく":["japanese"],"たいき":["japanese"],"たいぐう":["japanese"],"たいけん":["japanese"],"たいこ":["japanese"],"たいざい":["japanese"],"だいじょうぶ":["japanese"],"だいすき":["japanese"],"たいせつ":["japanese"],"たいそう":["japanese"],"だいたい":["japanese"],"たいちょう":["japanese"],"たいてい":["japanese"],"だいどころ":["japanese"],"たいない":["japanese"],"たいねつ":["japanese"],"たいのう":["japanese"],"たいはん":["japanese"],"だいひょう":["japanese"],"たいふう":["japanese"],"たいへん":["japanese"],"たいほ":["japanese"],"たいまつばな":["japanese"],"たいみんぐ":["japanese"],"たいむ":["japanese"],"たいめん":["japanese"],"たいやき":["japanese"],"たいよう":["japanese"],"たいら":["japanese"],"たいりょく":["japanese"],"たいる":["japanese"],"たいわん":["japanese"],"たうえ":["japanese"],"たえる":["japanese"],"たおす":["japanese"],"たおる":["japanese"],"たおれる":["japanese"],"たかい":["japanese"],"たかね":["japanese"],"たきび":["japanese"],"たくさん":["japanese"],"たこく":["japanese"],"たこやき":["japanese"],"たさい":["japanese"],"たしざん":["japanese"],"だじゃれ":["japanese"],"たすける":["japanese"],"たずさわる":["japanese"],"たそがれ":["japanese"],"たたかう":["japanese"],"たたく":["japanese"],"ただしい":["japanese"],"たたみ":["japanese"],"たちばな":["japanese"],"だっかい":["japanese"],"だっきゃく":["japanese"],"だっこ":["japanese"],"だっしゅつ":["japanese"],"だったい":["japanese"],"たてる":["japanese"],"たとえる":["japanese"],"たなばた":["japanese"],"たにん":["japanese"],"たぬき":["japanese"],"たのしみ":["japanese"],"たはつ":["japanese"],"たぶん":["japanese"],"たべる":["japanese"],"たぼう":["japanese"],"たまご":["japanese"],"たまる":["japanese"],"だむる":["japanese"],"ためいき":["japanese"],"ためす":["japanese"],"ためる":["japanese"],"たもつ":["japanese"],"たやすい":["japanese"],"たよる":["japanese"],"たらす":["japanese"],"たりきほんがん":["japanese"],"たりょう":["japanese"],"たりる":["japanese"],"たると":["japanese"],"たれる":["japanese"],"たれんと":["japanese"],"たろっと":["japanese"],"たわむれる":["japanese"],"だんあつ":["japanese"],"たんい":["japanese"],"たんおん":["japanese"],"たんか":["japanese"],"たんき":["japanese"],"たんけん":["japanese"],"たんご":["japanese"],"たんさん":["japanese"],"たんじょうび":["japanese"],"だんせい":["japanese"],"たんそく":["japanese"],"たんたい":["japanese"],"だんち":["japanese"],"たんてい":["japanese"],"たんとう":["japanese"],"だんな":["japanese"],"たんにん":["japanese"],"だんねつ":["japanese"],"たんのう":["japanese"],"たんぴん":["japanese"],"だんぼう":["japanese"],"たんまつ":["japanese"],"たんめい":["japanese"],"だんれつ":["japanese"],"だんろ":["japanese"],"だんわ":["japanese"],"ちあい":["japanese"],"ちあん":["japanese"],"ちいき":["japanese"],"ちいさい":["japanese"],"ちえん":["japanese"],"ちかい":["japanese"],"ちから":["japanese"],"ちきゅう":["japanese"],"ちきん":["japanese"],"ちけいず":["japanese"],"ちけん":["japanese"],"ちこく":["japanese"],"ちさい":["japanese"],"ちしき":["japanese"],"ちしりょう":["japanese"],"ちせい":["japanese"],"ちそう":["japanese"],"ちたい":["japanese"],"ちたん":["japanese"],"ちちおや":["japanese"],"ちつじょ":["japanese"],"ちてき":["japanese"],"ちてん":["japanese"],"ちぬき":["japanese"],"ちぬり":["japanese"],"ちのう":["japanese"],"ちひょう":["japanese"],"ちへいせん":["japanese"],"ちほう":["japanese"],"ちまた":["japanese"],"ちみつ":["japanese"],"ちみどろ":["japanese"],"ちめいど":["japanese"],"ちゃんこなべ":["japanese"],"ちゅうい":["japanese"],"ちゆりょく":["japanese"],"ちょうし":["japanese"],"ちょさくけん":["japanese"],"ちらし":["japanese"],"ちらみ":["japanese"],"ちりがみ":["japanese"],"ちりょう":["japanese"],"ちるど":["japanese"],"ちわわ":["japanese"],"ちんたい":["japanese"],"ちんもく":["japanese"],"ついか":["japanese"],"ついたち":["japanese"],"つうか":["japanese"],"つうじょう":["japanese"],"つうはん":["japanese"],"つうわ":["japanese"],"つかう":["japanese"],"つかれる":["japanese"],"つくね":["japanese"],"つくる":["japanese"],"つけね":["japanese"],"つける":["japanese"],"つごう":["japanese"],"つたえる":["japanese"],"つづく":["japanese"],"つつじ":["japanese"],"つつむ":["japanese"],"つとめる":["japanese"],"つながる":["japanese"],"つなみ":["japanese"],"つねづね":["japanese"],"つのる":["japanese"],"つぶす":["japanese"],"つまらない":["japanese"],"つまる":["japanese"],"つみき":["japanese"],"つめたい":["japanese"],"つもり":["japanese"],"つもる":["japanese"],"つよい":["japanese"],"つるぼ":["japanese"],"つるみく":["japanese"],"つわもの":["japanese"],"つわり":["japanese"],"てあし":["japanese"],"てあて":["japanese"],"てあみ":["japanese"],"ていおん":["japanese"],"ていか":["japanese"],"ていき":["japanese"],"ていけい":["japanese"],"ていこく":["japanese"],"ていさつ":["japanese"],"ていし":["japanese"],"ていせい":["japanese"],"ていたい":["japanese"],"ていど":["japanese"],"ていねい":["japanese"],"ていひょう":["japanese"],"ていへん":["japanese"],"ていぼう":["japanese"],"てうち":["japanese"],"ておくれ":["japanese"],"てきとう":["japanese"],"てくび":["japanese"],"でこぼこ":["japanese"],"てさぎょう":["japanese"],"てさげ":["japanese"],"てすり":["japanese"],"てそう":["japanese"],"てちがい":["japanese"],"てちょう":["japanese"],"てつがく":["japanese"],"てつづき":["japanese"],"でっぱ":["japanese"],"てつぼう":["japanese"],"てつや":["japanese"],"でぬかえ":["japanese"],"てぬき":["japanese"],"てぬぐい":["japanese"],"てのひら":["japanese"],"てはい":["japanese"],"てぶくろ":["japanese"],"てふだ":["japanese"],"てほどき":["japanese"],"てほん":["japanese"],"てまえ":["japanese"],"てまきずし":["japanese"],"てみじか":["japanese"],"てみやげ":["japanese"],"てらす":["japanese"],"てれび":["japanese"],"てわけ":["japanese"],"てわたし":["japanese"],"でんあつ":["japanese"],"てんいん":["japanese"],"てんかい":["japanese"],"てんき":["japanese"],"てんぐ":["japanese"],"てんけん":["japanese"],"てんごく":["japanese"],"てんさい":["japanese"],"てんし":["japanese"],"てんすう":["japanese"],"でんち":["japanese"],"てんてき":["japanese"],"てんとう":["japanese"],"てんない":["japanese"],"てんぷら":["japanese"],"てんぼうだい":["japanese"],"てんめつ":["japanese"],"てんらんかい":["japanese"],"でんりょく":["japanese"],"でんわ":["japanese"],"どあい":["japanese"],"といれ":["japanese"],"どうかん":["japanese"],"とうきゅう":["japanese"],"どうぐ":["japanese"],"とうし":["japanese"],"とうむぎ":["japanese"],"とおい":["japanese"],"とおか":["japanese"],"とおく":["japanese"],"とおす":["japanese"],"とおる":["japanese"],"とかい":["japanese"],"とかす":["japanese"],"ときおり":["japanese"],"ときどき":["japanese"],"とくい":["japanese"],"とくしゅう":["japanese"],"とくてん":["japanese"],"とくに":["japanese"],"とくべつ":["japanese"],"とけい":["japanese"],"とける":["japanese"],"とこや":["japanese"],"とさか":["japanese"],"としょかん":["japanese"],"とそう":["japanese"],"とたん":["japanese"],"とちゅう":["japanese"],"とっきゅう":["japanese"],"とっくん":["japanese"],"とつぜん":["japanese"],"とつにゅう":["japanese"],"とどける":["japanese"],"ととのえる":["japanese"],"とない":["japanese"],"となえる":["japanese"],"となり":["japanese"],"とのさま":["japanese"],"とばす":["japanese"],"どぶがわ":["japanese"],"とほう":["japanese"],"とまる":["japanese"],"とめる":["japanese"],"ともだち":["japanese"],"ともる":["japanese"],"どようび":["japanese"],"とらえる":["japanese"],"とんかつ":["japanese"],"どんぶり":["japanese"],"ないかく":["japanese"],"ないこう":["japanese"],"ないしょ":["japanese"],"ないす":["japanese"],"ないせん":["japanese"],"ないそう":["japanese"],"なおす":["japanese"],"ながい":["japanese"],"なくす":["japanese"],"なげる":["japanese"],"なこうど":["japanese"],"なさけ":["japanese"],"なたでここ":["japanese"],"なっとう":["japanese"],"なつやすみ":["japanese"],"ななおし":["japanese"],"なにごと":["japanese"],"なにもの":["japanese"],"なにわ":["japanese"],"なのか":["japanese"],"なふだ":["japanese"],"なまいき":["japanese"],"なまえ":["japanese"],"なまみ":["japanese"],"なみだ":["japanese"],"なめらか":["japanese"],"なめる":["japanese"],"なやむ":["japanese"],"ならう":["japanese"],"ならび":["japanese"],"ならぶ":["japanese"],"なれる":["japanese"],"なわとび":["japanese"],"なわばり":["japanese"],"にあう":["japanese"],"にいがた":["japanese"],"にうけ":["japanese"],"におい":["japanese"],"にかい":["japanese"],"にがて":["japanese"],"にきび":["japanese"],"にくしみ":["japanese"],"にくまん":["japanese"],"にげる":["japanese"],"にさんかたんそ":["japanese"],"にしき":["japanese"],"にせもの":["japanese"],"にちじょう":["japanese"],"にちようび":["japanese"],"にっか":["japanese"],"にっき":["japanese"],"にっけい":["japanese"],"にっこう":["japanese"],"にっさん":["japanese"],"にっしょく":["japanese"],"にっすう":["japanese"],"にっせき":["japanese"],"にってい":["japanese"],"になう":["japanese"],"にほん":["japanese"],"にまめ":["japanese"],"にもつ":["japanese"],"にやり":["japanese"],"にゅういん":["japanese"],"にりんしゃ":["japanese"],"にわとり":["japanese"],"にんい":["japanese"],"にんか":["japanese"],"にんき":["japanese"],"にんげん":["japanese"],"にんしき":["japanese"],"にんずう":["japanese"],"にんそう":["japanese"],"にんたい":["japanese"],"にんち":["japanese"],"にんてい":["japanese"],"にんにく":["japanese"],"にんぷ":["japanese"],"にんまり":["japanese"],"にんむ":["japanese"],"にんめい":["japanese"],"にんよう":["japanese"],"ぬいくぎ":["japanese"],"ぬかす":["japanese"],"ぬぐいとる":["japanese"],"ぬぐう":["japanese"],"ぬくもり":["japanese"],"ぬすむ":["japanese"],"ぬまえび":["japanese"],"ぬめり":["japanese"],"ぬらす":["japanese"],"ぬんちゃく":["japanese"],"ねあげ":["japanese"],"ねいき":["japanese"],"ねいる":["japanese"],"ねいろ":["japanese"],"ねぐせ":["japanese"],"ねくたい":["japanese"],"ねくら":["japanese"],"ねこぜ":["japanese"],"ねこむ":["japanese"],"ねさげ":["japanese"],"ねすごす":["japanese"],"ねそべる":["japanese"],"ねだん":["japanese"],"ねつい":["japanese"],"ねっしん":["japanese"],"ねつぞう":["japanese"],"ねったいぎょ":["japanese"],"ねぶそく":["japanese"],"ねふだ":["japanese"],"ねぼう":["japanese"],"ねほりはほり":["japanese"],"ねまき":["japanese"],"ねまわし":["japanese"],"ねみみ":["japanese"],"ねむい":["japanese"],"ねむたい":["japanese"],"ねもと":["japanese"],"ねらう":["japanese"],"ねわざ":["japanese"],"ねんいり":["japanese"],"ねんおし":["japanese"],"ねんかん":["japanese"],"ねんきん":["japanese"],"ねんぐ":["japanese"],"ねんざ":["japanese"],"ねんし":["japanese"],"ねんちゃく":["japanese"],"ねんど":["japanese"],"ねんぴ":["japanese"],"ねんぶつ":["japanese"],"ねんまつ":["japanese"],"ねんりょう":["japanese"],"ねんれい":["japanese"],"のいず":["japanese"],"のおづま":["japanese"],"のがす":["japanese"],"のきなみ":["japanese"],"のこぎり":["japanese"],"のこす":["japanese"],"のこる":["japanese"],"のせる":["japanese"],"のぞく":["japanese"],"のぞむ":["japanese"],"のたまう":["japanese"],"のちほど":["japanese"],"のっく":["japanese"],"のばす":["japanese"],"のはら":["japanese"],"のべる":["japanese"],"のぼる":["japanese"],"のみもの":["japanese"],"のやま":["japanese"],"のらいぬ":["japanese"],"のらねこ":["japanese"],"のりもの":["japanese"],"のりゆき":["japanese"],"のれん":["japanese"],"のんき":["japanese"],"ばあい":["japanese"],"はあく":["japanese"],"ばあさん":["japanese"],"ばいか":["japanese"],"ばいく":["japanese"],"はいけん":["japanese"],"はいご":["japanese"],"はいしん":["japanese"],"はいすい":["japanese"],"はいせん":["japanese"],"はいそう":["japanese"],"はいち":["japanese"],"ばいばい":["japanese"],"はいれつ":["japanese"],"はえる":["japanese"],"はおる":["japanese"],"はかい":["japanese"],"ばかり":["japanese"],"はかる":["japanese"],"はくしゅ":["japanese"],"はけん":["japanese"],"はこぶ":["japanese"],"はさみ":["japanese"],"はさん":["japanese"],"はしご":["japanese"],"ばしょ":["japanese"],"はしる":["japanese"],"はせる":["japanese"],"ぱそこん":["japanese"],"はそん":["japanese"],"はたん":["japanese"],"はちみつ":["japanese"],"はつおん":["japanese"],"はっかく":["japanese"],"はづき":["japanese"],"はっきり":["japanese"],"はっくつ":["japanese"],"はっけん":["japanese"],"はっこう":["japanese"],"はっさん":["japanese"],"はっしん":["japanese"],"はったつ":["japanese"],"はっちゅう":["japanese"],"はってん":["japanese"],"はっぴょう":["japanese"],"はっぽう":["japanese"],"はなす":["japanese"],"はなび":["japanese"],"はにかむ":["japanese"],"はぶらし":["japanese"],"はみがき":["japanese"],"はむかう":["japanese"],"はめつ":["japanese"],"はやい":["japanese"],"はやし":["japanese"],"はらう":["japanese"],"はろうぃん":["japanese"],"はわい":["japanese"],"はんい":["japanese"],"はんえい":["japanese"],"はんおん":["japanese"],"はんかく":["japanese"],"はんきょう":["japanese"],"ばんぐみ":["japanese"],"はんこ":["japanese"],"はんしゃ":["japanese"],"はんすう":["japanese"],"はんだん":["japanese"],"ぱんち":["japanese"],"ぱんつ":["japanese"],"はんてい":["japanese"],"はんとし":["japanese"],"はんのう":["japanese"],"はんぱ":["japanese"],"はんぶん":["japanese"],"はんぺん":["japanese"],"はんぼうき":["japanese"],"はんめい":["japanese"],"はんらん":["japanese"],"はんろん":["japanese"],"ひいき":["japanese"],"ひうん":["japanese"],"ひえる":["japanese"],"ひかく":["japanese"],"ひかり":["japanese"],"ひかる":["japanese"],"ひかん":["japanese"],"ひくい":["japanese"],"ひけつ":["japanese"],"ひこうき":["japanese"],"ひこく":["japanese"],"ひさい":["japanese"],"ひさしぶり":["japanese"],"ひさん":["japanese"],"びじゅつかん":["japanese"],"ひしょ":["japanese"],"ひそか":["japanese"],"ひそむ":["japanese"],"ひたむき":["japanese"],"ひだり":["japanese"],"ひたる":["japanese"],"ひつぎ":["japanese"],"ひっこし":["japanese"],"ひっし":["japanese"],"ひつじゅひん":["japanese"],"ひっす":["japanese"],"ひつぜん":["japanese"],"ぴったり":["japanese"],"ぴっちり":["japanese"],"ひつよう":["japanese"],"ひてい":["japanese"],"ひとごみ":["japanese"],"ひなまつり":["japanese"],"ひなん":["japanese"],"ひねる":["japanese"],"ひはん":["japanese"],"ひびく":["japanese"],"ひひょう":["japanese"],"ひほう":["japanese"],"ひまわり":["japanese"],"ひまん":["japanese"],"ひみつ":["japanese"],"ひめい":["japanese"],"ひめじし":["japanese"],"ひやけ":["japanese"],"ひやす":["japanese"],"ひよう":["japanese"],"びょうき":["japanese"],"ひらがな":["japanese"],"ひらく":["japanese"],"ひりつ":["japanese"],"ひりょう":["japanese"],"ひるま":["japanese"],"ひるやすみ":["japanese"],"ひれい":["japanese"],"ひろい":["japanese"],"ひろう":["japanese"],"ひろき":["japanese"],"ひろゆき":["japanese"],"ひんかく":["japanese"],"ひんけつ":["japanese"],"ひんこん":["japanese"],"ひんしゅ":["japanese"],"ひんそう":["japanese"],"ぴんち":["japanese"],"ひんぱん":["japanese"],"びんぼう":["japanese"],"ふあん":["japanese"],"ふいうち":["japanese"],"ふうけい":["japanese"],"ふうせん":["japanese"],"ぷうたろう":["japanese"],"ふうとう":["japanese"],"ふうふ":["japanese"],"ふえる":["japanese"],"ふおん":["japanese"],"ふかい":["japanese"],"ふきん":["japanese"],"ふくざつ":["japanese"],"ふくぶくろ":["japanese"],"ふこう":["japanese"],"ふさい":["japanese"],"ふしぎ":["japanese"],"ふじみ":["japanese"],"ふすま":["japanese"],"ふせい":["japanese"],"ふせぐ":["japanese"],"ふそく":["japanese"],"ぶたにく":["japanese"],"ふたん":["japanese"],"ふちょう":["japanese"],"ふつう":["japanese"],"ふつか":["japanese"],"ふっかつ":["japanese"],"ふっき":["japanese"],"ふっこく":["japanese"],"ぶどう":["japanese"],"ふとる":["japanese"],"ふとん":["japanese"],"ふのう":["japanese"],"ふはい":["japanese"],"ふひょう":["japanese"],"ふへん":["japanese"],"ふまん":["japanese"],"ふみん":["japanese"],"ふめつ":["japanese"],"ふめん":["japanese"],"ふよう":["japanese"],"ふりこ":["japanese"],"ふりる":["japanese"],"ふるい":["japanese"],"ふんいき":["japanese"],"ぶんがく":["japanese"],"ぶんぐ":["japanese"],"ふんしつ":["japanese"],"ぶんせき":["japanese"],"ふんそう":["japanese"],"ぶんぽう":["japanese"],"へいあん":["japanese"],"へいおん":["japanese"],"へいがい":["japanese"],"へいき":["japanese"],"へいげん":["japanese"],"へいこう":["japanese"],"へいさ":["japanese"],"へいしゃ":["japanese"],"へいせつ":["japanese"],"へいそ":["japanese"],"へいたく":["japanese"],"へいてん":["japanese"],"へいねつ":["japanese"],"へいわ":["japanese"],"へきが":["japanese"],"へこむ":["japanese"],"べにいろ":["japanese"],"べにしょうが":["japanese"],"へらす":["japanese"],"へんかん":["japanese"],"べんきょう":["japanese"],"べんごし":["japanese"],"へんさい":["japanese"],"へんたい":["japanese"],"べんり":["japanese"],"ほあん":["japanese"],"ほいく":["japanese"],"ぼうぎょ":["japanese"],"ほうこく":["japanese"],"ほうそう":["japanese"],"ほうほう":["japanese"],"ほうもん":["japanese"],"ほうりつ":["japanese"],"ほえる":["japanese"],"ほおん":["japanese"],"ほかん":["japanese"],"ほきょう":["japanese"],"ぼきん":["japanese"],"ほくろ":["japanese"],"ほけつ":["japanese"],"ほけん":["japanese"],"ほこう":["japanese"],"ほこる":["japanese"],"ほしい":["japanese"],"ほしつ":["japanese"],"ほしゅ":["japanese"],"ほしょう":["japanese"],"ほせい":["japanese"],"ほそい":["japanese"],"ほそく":["japanese"],"ほたて":["japanese"],"ほたる":["japanese"],"ぽちぶくろ":["japanese"],"ほっきょく":["japanese"],"ほっさ":["japanese"],"ほったん":["japanese"],"ほとんど":["japanese"],"ほめる":["japanese"],"ほんい":["japanese"],"ほんき":["japanese"],"ほんけ":["japanese"],"ほんしつ":["japanese"],"ほんやく":["japanese"],"まいにち":["japanese"],"まかい":["japanese"],"まかせる":["japanese"],"まがる":["japanese"],"まける":["japanese"],"まこと":["japanese"],"まさつ":["japanese"],"まじめ":["japanese"],"ますく":["japanese"],"まぜる":["japanese"],"まつり":["japanese"],"まとめ":["japanese"],"まなぶ":["japanese"],"まぬけ":["japanese"],"まねく":["japanese"],"まほう":["japanese"],"まもる":["japanese"],"まゆげ":["japanese"],"まよう":["japanese"],"まろやか":["japanese"],"まわす":["japanese"],"まわり":["japanese"],"まわる":["japanese"],"まんが":["japanese"],"まんきつ":["japanese"],"まんぞく":["japanese"],"まんなか":["japanese"],"みいら":["japanese"],"みうち":["japanese"],"みえる":["japanese"],"みがく":["japanese"],"みかた":["japanese"],"みかん":["japanese"],"みけん":["japanese"],"みこん":["japanese"],"みじかい":["japanese"],"みすい":["japanese"],"みすえる":["japanese"],"みせる":["japanese"],"みっか":["japanese"],"みつかる":["japanese"],"みつける":["japanese"],"みてい":["japanese"],"みとめる":["japanese"],"みなと":["japanese"],"みなみかさい":["japanese"],"みねらる":["japanese"],"みのう":["japanese"],"みのがす":["japanese"],"みほん":["japanese"],"みもと":["japanese"],"みやげ":["japanese"],"みらい":["japanese"],"みりょく":["japanese"],"みわく":["japanese"],"みんか":["japanese"],"みんぞく":["japanese"],"むいか":["japanese"],"むえき":["japanese"],"むえん":["japanese"],"むかい":["japanese"],"むかう":["japanese"],"むかえ":["japanese"],"むかし":["japanese"],"むぎちゃ":["japanese"],"むける":["japanese"],"むげん":["japanese"],"むさぼる":["japanese"],"むしあつい":["japanese"],"むしば":["japanese"],"むじゅん":["japanese"],"むしろ":["japanese"],"むすう":["japanese"],"むすこ":["japanese"],"むすぶ":["japanese"],"むすめ":["japanese"],"むせる":["japanese"],"むせん":["japanese"],"むちゅう":["japanese"],"むなしい":["japanese"],"むのう":["japanese"],"むやみ":["japanese"],"むよう":["japanese"],"むらさき":["japanese"],"むりょう":["japanese"],"むろん":["japanese"],"めいあん":["japanese"],"めいうん":["japanese"],"めいえん":["japanese"],"めいかく":["japanese"],"めいきょく":["japanese"],"めいさい":["japanese"],"めいし":["japanese"],"めいそう":["japanese"],"めいぶつ":["japanese"],"めいれい":["japanese"],"めいわく":["japanese"],"めぐまれる":["japanese"],"めざす":["japanese"],"めした":["japanese"],"めずらしい":["japanese"],"めだつ":["japanese"],"めまい":["japanese"],"めやす":["japanese"],"めんきょ":["japanese"],"めんせき":["japanese"],"めんどう":["japanese"],"もうしあげる":["japanese"],"もうどうけん":["japanese"],"もえる":["japanese"],"もくし":["japanese"],"もくてき":["japanese"],"もくようび":["japanese"],"もちろん":["japanese"],"もどる":["japanese"],"もらう":["japanese"],"もんく":["japanese"],"もんだい":["japanese"],"やおや":["japanese"],"やける":["japanese"],"やさい":["japanese"],"やさしい":["japanese"],"やすい":["japanese"],"やすたろう":["japanese"],"やすみ":["japanese"],"やせる":["japanese"],"やそう":["japanese"],"やたい":["japanese"],"やちん":["japanese"],"やっと":["japanese"],"やっぱり":["japanese"],"やぶる":["japanese"],"やめる":["japanese"],"ややこしい":["japanese"],"やよい":["japanese"],"やわらかい":["japanese"],"ゆうき":["japanese"],"ゆうびんきょく":["japanese"],"ゆうべ":["japanese"],"ゆうめい":["japanese"],"ゆけつ":["japanese"],"ゆしゅつ":["japanese"],"ゆせん":["japanese"],"ゆそう":["japanese"],"ゆたか":["japanese"],"ゆちゃく":["japanese"],"ゆでる":["japanese"],"ゆにゅう":["japanese"],"ゆびわ":["japanese"],"ゆらい":["japanese"],"ゆれる":["japanese"],"ようい":["japanese"],"ようか":["japanese"],"ようきゅう":["japanese"],"ようじ":["japanese"],"ようす":["japanese"],"ようちえん":["japanese"],"よかぜ":["japanese"],"よかん":["japanese"],"よきん":["japanese"],"よくせい":["japanese"],"よくぼう":["japanese"],"よけい":["japanese"],"よごれる":["japanese"],"よさん":["japanese"],"よしゅう":["japanese"],"よそう":["japanese"],"よそく":["japanese"],"よっか":["japanese"],"よてい":["japanese"],"よどがわく":["japanese"],"よねつ":["japanese"],"よやく":["japanese"],"よゆう":["japanese"],"よろこぶ":["japanese"],"よろしい":["japanese"],"らいう":["japanese"],"らくがき":["japanese"],"らくご":["japanese"],"らくさつ":["japanese"],"らくだ":["japanese"],"らしんばん":["japanese"],"らせん":["japanese"],"らぞく":["japanese"],"らたい":["japanese"],"らっか":["japanese"],"られつ":["japanese"],"りえき":["japanese"],"りかい":["japanese"],"りきさく":["japanese"],"りきせつ":["japanese"],"りくぐん":["japanese"],"りくつ":["japanese"],"りけん":["japanese"],"りこう":["japanese"],"りせい":["japanese"],"りそう":["japanese"],"りそく":["japanese"],"りてん":["japanese"],"りねん":["japanese"],"りゆう":["japanese"],"りゅうがく":["japanese"],"りよう":["japanese"],"りょうり":["japanese"],"りょかん":["japanese"],"りょくちゃ":["japanese"],"りょこう":["japanese"],"りりく":["japanese"],"りれき":["japanese"],"りろん":["japanese"],"りんご":["japanese"],"るいけい":["japanese"],"るいさい":["japanese"],"るいじ":["japanese"],"るいせき":["japanese"],"るすばん":["japanese"],"るりがわら":["japanese"],"れいかん":["japanese"],"れいぎ":["japanese"],"れいせい":["japanese"],"れいぞうこ":["japanese"],"れいとう":["japanese"],"れいぼう":["japanese"],"れきし":["japanese"],"れきだい":["japanese"],"れんあい":["japanese"],"れんけい":["japanese"],"れんこん":["japanese"],"れんさい":["japanese"],"れんしゅう":["japanese"],"れんぞく":["japanese"],"れんらく":["japanese"],"ろうか":["japanese"],"ろうご":["japanese"],"ろうじん":["japanese"],"ろうそく":["japanese"],"ろくが":["japanese"],"ろこつ":["japanese"],"ろじうら":["japanese"],"ろしゅつ":["japanese"],"ろせん":["japanese"],"ろてん":["japanese"],"ろめん":["japanese"],"ろれつ":["japanese"],"ろんぎ":["japanese"],"ろんぱ":["japanese"],"ろんぶん":["japanese"],"ろんり":["japanese"],"わかす":["japanese"],"わかめ":["japanese"],"わかやま":["japanese"],"わかれる":["japanese"],"わしつ":["japanese"],"わじまし":["japanese"],"わすれもの":["japanese"],"わらう":["japanese"],"われる":["japanese"],"가격":["korean"],"가끔":["korean"],"가난":["korean"],"가능":["korean"],"가득":["korean"],"가르침":["korean"],"가뭄":["korean"],"가방":["korean"],"가상":["korean"],"가슴":["korean"],"가운데":["korean"],"가을":["korean"],"가이드":["korean"],"가입":["korean"],"가장":["korean"],"가정":["korean"],"가족":["korean"],"가죽":["korean"],"각오":["korean"],"각자":["korean"],"간격":["korean"],"간부":["korean"],"간섭":["korean"],"간장":["korean"],"간접":["korean"],"간판":["korean"],"갈등":["korean"],"갈비":["korean"],"갈색":["korean"],"갈증":["korean"],"감각":["korean"],"감기":["korean"],"감소":["korean"],"감수성":["korean"],"감자":["korean"],"감정":["korean"],"갑자기":["korean"],"강남":["korean"],"강당":["korean"],"강도":["korean"],"강력히":["korean"],"강변":["korean"],"강북":["korean"],"강사":["korean"],"강수량":["korean"],"강아지":["korean"],"강원도":["korean"],"강의":["korean"],"강제":["korean"],"강조":["korean"],"같이":["korean"],"개구리":["korean"],"개나리":["korean"],"개방":["korean"],"개별":["korean"],"개선":["korean"],"개성":["korean"],"개인":["korean"],"객관적":["korean"],"거실":["korean"],"거액":["korean"],"거울":["korean"],"거짓":["korean"],"거품":["korean"],"걱정":["korean"],"건강":["korean"],"건물":["korean"],"건설":["korean"],"건조":["korean"],"건축":["korean"],"걸음":["korean"],"검사":["korean"],"검토":["korean"],"게시판":["korean"],"게임":["korean"],"겨울":["korean"],"견해":["korean"],"결과":["korean"],"결국":["korean"],"결론":["korean"],"결석":["korean"],"결승":["korean"],"결심":["korean"],"결정":["korean"],"결혼":["korean"],"경계":["korean"],"경고":["korean"],"경기":["korean"],"경력":["korean"],"경복궁":["korean"],"경비":["korean"],"경상도":["korean"],"경영":["korean"],"경우":["korean"],"경쟁":["korean"],"경제":["korean"],"경주":["korean"],"경찰":["korean"],"경치":["korean"],"경향":["korean"],"경험":["korean"],"계곡":["korean"],"계단":["korean"],"계란":["korean"],"계산":["korean"],"계속":["korean"],"계약":["korean"],"계절":["korean"],"계층":["korean"],"계획":["korean"],"고객":["korean"],"고구려":["korean"],"고궁":["korean"],"고급":["korean"],"고등학생":["korean"],"고무신":["korean"],"고민":["korean"],"고양이":["korean"],"고장":["korean"],"고전":["korean"],"고집":["korean"],"고춧가루":["korean"],"고통":["korean"],"고향":["korean"],"곡식":["korean"],"골목":["korean"],"골짜기":["korean"],"골프":["korean"],"공간":["korean"],"공개":["korean"],"공격":["korean"],"공군":["korean"],"공급":["korean"],"공기":["korean"],"공동":["korean"],"공무원":["korean"],"공부":["korean"],"공사":["korean"],"공식":["korean"],"공업":["korean"],"공연":["korean"],"공원":["korean"],"공장":["korean"],"공짜":["korean"],"공책":["korean"],"공통":["korean"],"공포":["korean"],"공항":["korean"],"공휴일":["korean"],"과목":["korean"],"과일":["korean"],"과장":["korean"],"과정":["korean"],"과학":["korean"],"관객":["korean"],"관계":["korean"],"관광":["korean"],"관념":["korean"],"관람":["korean"],"관련":["korean"],"관리":["korean"],"관습":["korean"],"관심":["korean"],"관점":["korean"],"관찰":["korean"],"광경":["korean"],"광고":["korean"],"광장":["korean"],"광주":["korean"],"괴로움":["korean"],"굉장히":["korean"],"교과서":["korean"],"교문":["korean"],"교복":["korean"],"교실":["korean"],"교양":["korean"],"교육":["korean"],"교장":["korean"],"교직":["korean"],"교통":["korean"],"교환":["korean"],"교훈":["korean"],"구경":["korean"],"구름":["korean"],"구멍":["korean"],"구별":["korean"],"구분":["korean"],"구석":["korean"],"구성":["korean"],"구속":["korean"],"구역":["korean"],"구입":["korean"],"구청":["korean"],"구체적":["korean"],"국가":["korean"],"국기":["korean"],"국내":["korean"],"국립":["korean"],"국물":["korean"],"국민":["korean"],"국수":["korean"],"국어":["korean"],"국왕":["korean"],"국적":["korean"],"국제":["korean"],"국회":["korean"],"군대":["korean"],"군사":["korean"],"군인":["korean"],"궁극적":["korean"],"권리":["korean"],"권위":["korean"],"권투":["korean"],"귀국":["korean"],"귀신":["korean"],"규정":["korean"],"규칙":["korean"],"균형":["korean"],"그날":["korean"],"그냥":["korean"],"그늘":["korean"],"그러나":["korean"],"그룹":["korean"],"그릇":["korean"],"그림":["korean"],"그제서야":["korean"],"그토록":["korean"],"극복":["korean"],"극히":["korean"],"근거":["korean"],"근교":["korean"],"근래":["korean"],"근로":["korean"],"근무":["korean"],"근본":["korean"],"근원":["korean"],"근육":["korean"],"근처":["korean"],"글씨":["korean"],"글자":["korean"],"금강산":["korean"],"금고":["korean"],"금년":["korean"],"금메달":["korean"],"금액":["korean"],"금연":["korean"],"금요일":["korean"],"금지":["korean"],"긍정적":["korean"],"기간":["korean"],"기관":["korean"],"기념":["korean"],"기능":["korean"],"기독교":["korean"],"기둥":["korean"],"기록":["korean"],"기름":["korean"],"기법":["korean"],"기본":["korean"],"기분":["korean"],"기쁨":["korean"],"기숙사":["korean"],"기술":["korean"],"기억":["korean"],"기업":["korean"],"기온":["korean"],"기운":["korean"],"기원":["korean"],"기적":["korean"],"기준":["korean"],"기침":["korean"],"기혼":["korean"],"기획":["korean"],"긴급":["korean"],"긴장":["korean"],"길이":["korean"],"김밥":["korean"],"김치":["korean"],"김포공항":["korean"],"깍두기":["korean"],"깜빡":["korean"],"깨달음":["korean"],"깨소금":["korean"],"껍질":["korean"],"꼭대기":["korean"],"꽃잎":["korean"],"나들이":["korean"],"나란히":["korean"],"나머지":["korean"],"나물":["korean"],"나침반":["korean"],"나흘":["korean"],"낙엽":["korean"],"난방":["korean"],"날개":["korean"],"날씨":["korean"],"날짜":["korean"],"남녀":["korean"],"남대문":["korean"],"남매":["korean"],"남산":["korean"],"남자":["korean"],"남편":["korean"],"남학생":["korean"],"낭비":["korean"],"낱말":["korean"],"내년":["korean"],"내용":["korean"],"내일":["korean"],"냄비":["korean"],"냄새":["korean"],"냇물":["korean"],"냉동":["korean"],"냉면":["korean"],"냉방":["korean"],"냉장고":["korean"],"넥타이":["korean"],"넷째":["korean"],"노동":["korean"],"노란색":["korean"],"노력":["korean"],"노인":["korean"],"녹음":["korean"],"녹차":["korean"],"녹화":["korean"],"논리":["korean"],"논문":["korean"],"논쟁":["korean"],"놀이":["korean"],"농구":["korean"],"농담":["korean"],"농민":["korean"],"농부":["korean"],"농업":["korean"],"농장":["korean"],"농촌":["korean"],"높이":["korean"],"눈동자":["korean"],"눈물":["korean"],"눈썹":["korean"],"뉴욕":["korean"],"느낌":["korean"],"늑대":["korean"],"능동적":["korean"],"능력":["korean"],"다방":["korean"],"다양성":["korean"],"다음":["korean"],"다이어트":["korean"],"다행":["korean"],"단계":["korean"],"단골":["korean"],"단독":["korean"],"단맛":["korean"],"단순":["korean"],"단어":["korean"],"단위":["korean"],"단점":["korean"],"단체":["korean"],"단추":["korean"],"단편":["korean"],"단풍":["korean"],"달걀":["korean"],"달러":["korean"],"달력":["korean"],"달리":["korean"],"닭고기":["korean"],"담당":["korean"],"담배":["korean"],"담요":["korean"],"담임":["korean"],"답변":["korean"],"답장":["korean"],"당근":["korean"],"당분간":["korean"],"당연히":["korean"],"당장":["korean"],"대규모":["korean"],"대낮":["korean"],"대단히":["korean"],"대답":["korean"],"대도시":["korean"],"대략":["korean"],"대량":["korean"],"대륙":["korean"],"대문":["korean"],"대부분":["korean"],"대신":["korean"],"대응":["korean"],"대장":["korean"],"대전":["korean"],"대접":["korean"],"대중":["korean"],"대책":["korean"],"대출":["korean"],"대충":["korean"],"대통령":["korean"],"대학":["korean"],"대한민국":["korean"],"대합실":["korean"],"대형":["korean"],"덩어리":["korean"],"데이트":["korean"],"도대체":["korean"],"도덕":["korean"],"도둑":["korean"],"도망":["korean"],"도서관":["korean"],"도심":["korean"],"도움":["korean"],"도입":["korean"],"도자기":["korean"],"도저히":["korean"],"도전":["korean"],"도중":["korean"],"도착":["korean"],"독감":["korean"],"독립":["korean"],"독서":["korean"],"독일":["korean"],"독창적":["korean"],"동화책":["korean"],"뒷모습":["korean"],"뒷산":["korean"],"딸아이":["korean"],"마누라":["korean"],"마늘":["korean"],"마당":["korean"],"마라톤":["korean"],"마련":["korean"],"마무리":["korean"],"마사지":["korean"],"마약":["korean"],"마요네즈":["korean"],"마을":["korean"],"마음":["korean"],"마이크":["korean"],"마중":["korean"],"마지막":["korean"],"마찬가지":["korean"],"마찰":["korean"],"마흔":["korean"],"막걸리":["korean"],"막내":["korean"],"막상":["korean"],"만남":["korean"],"만두":["korean"],"만세":["korean"],"만약":["korean"],"만일":["korean"],"만점":["korean"],"만족":["korean"],"만화":["korean"],"많이":["korean"],"말기":["korean"],"말씀":["korean"],"말투":["korean"],"맘대로":["korean"],"망원경":["korean"],"매년":["korean"],"매달":["korean"],"매력":["korean"],"매번":["korean"],"매스컴":["korean"],"매일":["korean"],"매장":["korean"],"맥주":["korean"],"먹이":["korean"],"먼저":["korean"],"먼지":["korean"],"멀리":["korean"],"메일":["korean"],"며느리":["korean"],"며칠":["korean"],"면담":["korean"],"멸치":["korean"],"명단":["korean"],"명령":["korean"],"명예":["korean"],"명의":["korean"],"명절":["korean"],"명칭":["korean"],"명함":["korean"],"모금":["korean"],"모니터":["korean"],"모델":["korean"],"모든":["korean"],"모범":["korean"],"모습":["korean"],"모양":["korean"],"모임":["korean"],"모조리":["korean"],"모집":["korean"],"모퉁이":["korean"],"목걸이":["korean"],"목록":["korean"],"목사":["korean"],"목소리":["korean"],"목숨":["korean"],"목적":["korean"],"목표":["korean"],"몰래":["korean"],"몸매":["korean"],"몸무게":["korean"],"몸살":["korean"],"몸속":["korean"],"몸짓":["korean"],"몸통":["korean"],"몹시":["korean"],"무관심":["korean"],"무궁화":["korean"],"무더위":["korean"],"무덤":["korean"],"무릎":["korean"],"무슨":["korean"],"무엇":["korean"],"무역":["korean"],"무용":["korean"],"무조건":["korean"],"무지개":["korean"],"무척":["korean"],"문구":["korean"],"문득":["korean"],"문법":["korean"],"문서":["korean"],"문제":["korean"],"문학":["korean"],"문화":["korean"],"물가":["korean"],"물건":["korean"],"물결":["korean"],"물고기":["korean"],"물론":["korean"],"물리학":["korean"],"물음":["korean"],"물질":["korean"],"물체":["korean"],"미국":["korean"],"미디어":["korean"],"미사일":["korean"],"미술":["korean"],"미역":["korean"],"미용실":["korean"],"미움":["korean"],"미인":["korean"],"미팅":["korean"],"미혼":["korean"],"민간":["korean"],"민족":["korean"],"민주":["korean"],"믿음":["korean"],"밀가루":["korean"],"밀리미터":["korean"],"밑바닥":["korean"],"바가지":["korean"],"바구니":["korean"],"바나나":["korean"],"바늘":["korean"],"바닥":["korean"],"바닷가":["korean"],"바람":["korean"],"바이러스":["korean"],"바탕":["korean"],"박물관":["korean"],"박사":["korean"],"박수":["korean"],"반대":["korean"],"반드시":["korean"],"반말":["korean"],"반발":["korean"],"반성":["korean"],"반응":["korean"],"반장":["korean"],"반죽":["korean"],"반지":["korean"],"반찬":["korean"],"받침":["korean"],"발가락":["korean"],"발걸음":["korean"],"발견":["korean"],"발달":["korean"],"발레":["korean"],"발목":["korean"],"발바닥":["korean"],"발생":["korean"],"발음":["korean"],"발자국":["korean"],"발전":["korean"],"발톱":["korean"],"발표":["korean"],"밤하늘":["korean"],"밥그릇":["korean"],"밥맛":["korean"],"밥상":["korean"],"밥솥":["korean"],"방금":["korean"],"방면":["korean"],"방문":["korean"],"방바닥":["korean"],"방법":["korean"],"방송":["korean"],"방식":["korean"],"방안":["korean"],"방울":["korean"],"방지":["korean"],"방학":["korean"],"방해":["korean"],"방향":["korean"],"배경":["korean"],"배꼽":["korean"],"배달":["korean"],"배드민턴":["korean"],"백두산":["korean"],"백색":["korean"],"백성":["korean"],"백인":["korean"],"백제":["korean"],"백화점":["korean"],"버릇":["korean"],"버섯":["korean"],"버튼":["korean"],"번개":["korean"],"번역":["korean"],"번지":["korean"],"번호":["korean"],"벌금":["korean"],"벌레":["korean"],"벌써":["korean"],"범위":["korean"],"범인":["korean"],"범죄":["korean"],"법률":["korean"],"법원":["korean"],"법적":["korean"],"법칙":["korean"],"베이징":["korean"],"벨트":["korean"],"변경":["korean"],"변동":["korean"],"변명":["korean"],"변신":["korean"],"변호사":["korean"],"변화":["korean"],"별도":["korean"],"별명":["korean"],"별일":["korean"],"병실":["korean"],"병아리":["korean"],"병원":["korean"],"보관":["korean"],"보너스":["korean"],"보라색":["korean"],"보람":["korean"],"보름":["korean"],"보상":["korean"],"보안":["korean"],"보자기":["korean"],"보장":["korean"],"보전":["korean"],"보존":["korean"],"보통":["korean"],"보편적":["korean"],"보험":["korean"],"복도":["korean"],"복사":["korean"],"복숭아":["korean"],"복습":["korean"],"볶음":["korean"],"본격적":["korean"],"본래":["korean"],"본부":["korean"],"본사":["korean"],"본성":["korean"],"본인":["korean"],"본질":["korean"],"볼펜":["korean"],"봉사":["korean"],"봉지":["korean"],"봉투":["korean"],"부근":["korean"],"부끄러움":["korean"],"부담":["korean"],"부동산":["korean"],"부문":["korean"],"부분":["korean"],"부산":["korean"],"부상":["korean"],"부엌":["korean"],"부인":["korean"],"부작용":["korean"],"부장":["korean"],"부정":["korean"],"부족":["korean"],"부지런히":["korean"],"부친":["korean"],"부탁":["korean"],"부품":["korean"],"부회장":["korean"],"북부":["korean"],"북한":["korean"],"분노":["korean"],"분량":["korean"],"분리":["korean"],"분명":["korean"],"분석":["korean"],"분야":["korean"],"분위기":["korean"],"분필":["korean"],"분홍색":["korean"],"불고기":["korean"],"불과":["korean"],"불교":["korean"],"불꽃":["korean"],"불만":["korean"],"불법":["korean"],"불빛":["korean"],"불안":["korean"],"불이익":["korean"],"불행":["korean"],"브랜드":["korean"],"비극":["korean"],"비난":["korean"],"비닐":["korean"],"비둘기":["korean"],"비디오":["korean"],"비로소":["korean"],"비만":["korean"],"비명":["korean"],"비밀":["korean"],"비바람":["korean"],"비빔밥":["korean"],"비상":["korean"],"비용":["korean"],"비율":["korean"],"비중":["korean"],"비타민":["korean"],"비판":["korean"],"빌딩":["korean"],"빗물":["korean"],"빗방울":["korean"],"빗줄기":["korean"],"빛깔":["korean"],"빨간색":["korean"],"빨래":["korean"],"빨리":["korean"],"사건":["korean"],"사계절":["korean"],"사나이":["korean"],"사냥":["korean"],"사람":["korean"],"사랑":["korean"],"사립":["korean"],"사모님":["korean"],"사물":["korean"],"사방":["korean"],"사상":["korean"],"사생활":["korean"],"사설":["korean"],"사슴":["korean"],"사실":["korean"],"사업":["korean"],"사용":["korean"],"사월":["korean"],"사장":["korean"],"사전":["korean"],"사진":["korean"],"사촌":["korean"],"사춘기":["korean"],"사탕":["korean"],"사투리":["korean"],"사흘":["korean"],"산길":["korean"],"산부인과":["korean"],"산업":["korean"],"산책":["korean"],"살림":["korean"],"살인":["korean"],"살짝":["korean"],"삼계탕":["korean"],"삼국":["korean"],"삼십":["korean"],"삼월":["korean"],"삼촌":["korean"],"상관":["korean"],"상금":["korean"],"상대":["korean"],"상류":["korean"],"상반기":["korean"],"상상":["korean"],"상식":["korean"],"상업":["korean"],"상인":["korean"],"상자":["korean"],"상점":["korean"],"상처":["korean"],"상추":["korean"],"상태":["korean"],"상표":["korean"],"상품":["korean"],"상황":["korean"],"새벽":["korean"],"색깔":["korean"],"색연필":["korean"],"생각":["korean"],"생명":["korean"],"생물":["korean"],"생방송":["korean"],"생산":["korean"],"생선":["korean"],"생신":["korean"],"생일":["korean"],"생활":["korean"],"서랍":["korean"],"서른":["korean"],"서명":["korean"],"서민":["korean"],"서비스":["korean"],"서양":["korean"],"서울":["korean"],"서적":["korean"],"서점":["korean"],"서쪽":["korean"],"서클":["korean"],"석사":["korean"],"석유":["korean"],"선거":["korean"],"선물":["korean"],"선배":["korean"],"선생":["korean"],"선수":["korean"],"선원":["korean"],"선장":["korean"],"선전":["korean"],"선택":["korean"],"선풍기":["korean"],"설거지":["korean"],"설날":["korean"],"설렁탕":["korean"],"설명":["korean"],"설문":["korean"],"설사":["korean"],"설악산":["korean"],"설치":["korean"],"설탕":["korean"],"섭씨":["korean"],"성공":["korean"],"성당":["korean"],"성명":["korean"],"성별":["korean"],"성인":["korean"],"성장":["korean"],"성적":["korean"],"성질":["korean"],"성함":["korean"],"세금":["korean"],"세미나":["korean"],"세상":["korean"],"세월":["korean"],"세종대왕":["korean"],"세탁":["korean"],"센터":["korean"],"센티미터":["korean"],"셋째":["korean"],"소규모":["korean"],"소극적":["korean"],"소금":["korean"],"소나기":["korean"],"소년":["korean"],"소득":["korean"],"소망":["korean"],"소문":["korean"],"소설":["korean"],"소속":["korean"],"소아과":["korean"],"소용":["korean"],"소원":["korean"],"소음":["korean"],"소중히":["korean"],"소지품":["korean"],"소질":["korean"],"소풍":["korean"],"소형":["korean"],"속담":["korean"],"속도":["korean"],"속옷":["korean"],"손가락":["korean"],"손길":["korean"],"손녀":["korean"],"손님":["korean"],"손등":["korean"],"손목":["korean"],"손뼉":["korean"],"손실":["korean"],"손질":["korean"],"손톱":["korean"],"손해":["korean"],"솔직히":["korean"],"솜씨":["korean"],"송아지":["korean"],"송이":["korean"],"송편":["korean"],"쇠고기":["korean"],"쇼핑":["korean"],"수건":["korean"],"수년":["korean"],"수단":["korean"],"수돗물":["korean"],"수동적":["korean"],"수면":["korean"],"수명":["korean"],"수박":["korean"],"수상":["korean"],"수석":["korean"],"수술":["korean"],"수시로":["korean"],"수업":["korean"],"수염":["korean"],"수영":["korean"],"수입":["korean"],"수준":["korean"],"수집":["korean"],"수출":["korean"],"수컷":["korean"],"수필":["korean"],"수학":["korean"],"수험생":["korean"],"수화기":["korean"],"숙녀":["korean"],"숙소":["korean"],"숙제":["korean"],"순간":["korean"],"순서":["korean"],"순수":["korean"],"순식간":["korean"],"순위":["korean"],"숟가락":["korean"],"술병":["korean"],"술집":["korean"],"숫자":["korean"],"스님":["korean"],"스물":["korean"],"스스로":["korean"],"스승":["korean"],"스웨터":["korean"],"스위치":["korean"],"스케이트":["korean"],"스튜디오":["korean"],"스트레스":["korean"],"스포츠":["korean"],"슬쩍":["korean"],"슬픔":["korean"],"습관":["korean"],"습기":["korean"],"승객":["korean"],"승리":["korean"],"승부":["korean"],"승용차":["korean"],"승진":["korean"],"시각":["korean"],"시간":["korean"],"시골":["korean"],"시금치":["korean"],"시나리오":["korean"],"시댁":["korean"],"시리즈":["korean"],"시멘트":["korean"],"시민":["korean"],"시부모":["korean"],"시선":["korean"],"시설":["korean"],"시스템":["korean"],"시아버지":["korean"],"시어머니":["korean"],"시월":["korean"],"시인":["korean"],"시일":["korean"],"시작":["korean"],"시장":["korean"],"시절":["korean"],"시점":["korean"],"시중":["korean"],"시즌":["korean"],"시집":["korean"],"시청":["korean"],"시합":["korean"],"시험":["korean"],"식구":["korean"],"식기":["korean"],"식당":["korean"],"식량":["korean"],"식료품":["korean"],"식물":["korean"],"식빵":["korean"],"식사":["korean"],"식생활":["korean"],"식초":["korean"],"식탁":["korean"],"식품":["korean"],"신고":["korean"],"신규":["korean"],"신념":["korean"],"신문":["korean"],"신발":["korean"],"신비":["korean"],"신사":["korean"],"신세":["korean"],"신용":["korean"],"신제품":["korean"],"신청":["korean"],"신체":["korean"],"신화":["korean"],"실감":["korean"],"실내":["korean"],"실력":["korean"],"실례":["korean"],"실망":["korean"],"실수":["korean"],"실습":["korean"],"실시":["korean"],"실장":["korean"],"실정":["korean"],"실질적":["korean"],"실천":["korean"],"실체":["korean"],"실컷":["korean"],"실태":["korean"],"실패":["korean"],"실험":["korean"],"실현":["korean"],"심리":["korean"],"심부름":["korean"],"심사":["korean"],"심장":["korean"],"심정":["korean"],"심판":["korean"],"쌍둥이":["korean"],"씨름":["korean"],"씨앗":["korean"],"아가씨":["korean"],"아나운서":["korean"],"아드님":["korean"],"아들":["korean"],"아쉬움":["korean"],"아스팔트":["korean"],"아시아":["korean"],"아울러":["korean"],"아저씨":["korean"],"아줌마":["korean"],"아직":["korean"],"아침":["korean"],"아파트":["korean"],"아프리카":["korean"],"아픔":["korean"],"아홉":["korean"],"아흔":["korean"],"악기":["korean"],"악몽":["korean"],"악수":["korean"],"안개":["korean"],"안경":["korean"],"안과":["korean"],"안내":["korean"],"안녕":["korean"],"안동":["korean"],"안방":["korean"],"안부":["korean"],"안주":["korean"],"알루미늄":["korean"],"알코올":["korean"],"암시":["korean"],"암컷":["korean"],"압력":["korean"],"앞날":["korean"],"앞문":["korean"],"애인":["korean"],"애정":["korean"],"액수":["korean"],"앨범":["korean"],"야간":["korean"],"야단":["korean"],"야옹":["korean"],"약간":["korean"],"약국":["korean"],"약속":["korean"],"약수":["korean"],"약점":["korean"],"약품":["korean"],"약혼녀":["korean"],"양념":["korean"],"양력":["korean"],"양말":["korean"],"양배추":["korean"],"양주":["korean"],"양파":["korean"],"어둠":["korean"],"어려움":["korean"],"어른":["korean"],"어젯밤":["korean"],"어쨌든":["korean"],"어쩌다가":["korean"],"어쩐지":["korean"],"언니":["korean"],"언덕":["korean"],"언론":["korean"],"언어":["korean"],"얼굴":["korean"],"얼른":["korean"],"얼음":["korean"],"얼핏":["korean"],"엄마":["korean"],"업무":["korean"],"업종":["korean"],"업체":["korean"],"엉덩이":["korean"],"엉망":["korean"],"엉터리":["korean"],"엊그제":["korean"],"에너지":["korean"],"에어컨":["korean"],"엔진":["korean"],"여건":["korean"],"여고생":["korean"],"여관":["korean"],"여군":["korean"],"여권":["korean"],"여대생":["korean"],"여덟":["korean"],"여동생":["korean"],"여든":["korean"],"여론":["korean"],"여름":["korean"],"여섯":["korean"],"여성":["korean"],"여왕":["korean"],"여인":["korean"],"여전히":["korean"],"여직원":["korean"],"여학생":["korean"],"여행":["korean"],"역사":["korean"],"역시":["korean"],"역할":["korean"],"연결":["korean"],"연구":["korean"],"연극":["korean"],"연기":["korean"],"연락":["korean"],"연설":["korean"],"연세":["korean"],"연속":["korean"],"연습":["korean"],"연애":["korean"],"연예인":["korean"],"연인":["korean"],"연장":["korean"],"연주":["korean"],"연출":["korean"],"연필":["korean"],"연합":["korean"],"연휴":["korean"],"열기":["korean"],"열매":["korean"],"열쇠":["korean"],"열심히":["korean"],"열정":["korean"],"열차":["korean"],"열흘":["korean"],"염려":["korean"],"엽서":["korean"],"영국":["korean"],"영남":["korean"],"영상":["korean"],"영양":["korean"],"영역":["korean"],"영웅":["korean"],"영원히":["korean"],"영하":["korean"],"영향":["korean"],"영혼":["korean"],"영화":["korean"],"옆구리":["korean"],"옆방":["korean"],"옆집":["korean"],"예감":["korean"],"예금":["korean"],"예방":["korean"],"예산":["korean"],"예상":["korean"],"예선":["korean"],"예술":["korean"],"예습":["korean"],"예식장":["korean"],"예약":["korean"],"예전":["korean"],"예절":["korean"],"예정":["korean"],"예컨대":["korean"],"옛날":["korean"],"오늘":["korean"],"오락":["korean"],"오랫동안":["korean"],"오렌지":["korean"],"오로지":["korean"],"오른발":["korean"],"오븐":["korean"],"오십":["korean"],"오염":["korean"],"오월":["korean"],"오전":["korean"],"오직":["korean"],"오징어":["korean"],"오페라":["korean"],"오피스텔":["korean"],"오히려":["korean"],"옥상":["korean"],"옥수수":["korean"],"온갖":["korean"],"온라인":["korean"],"온몸":["korean"],"온종일":["korean"],"온통":["korean"],"올가을":["korean"],"올림픽":["korean"],"올해":["korean"],"옷차림":["korean"],"와이셔츠":["korean"],"와인":["korean"],"완성":["korean"],"완전":["korean"],"왕비":["korean"],"왕자":["korean"],"왜냐하면":["korean"],"왠지":["korean"],"외갓집":["korean"],"외국":["korean"],"외로움":["korean"],"외삼촌":["korean"],"외출":["korean"],"외침":["korean"],"외할머니":["korean"],"왼발":["korean"],"왼손":["korean"],"왼쪽":["korean"],"요금":["korean"],"요일":["korean"],"요즘":["korean"],"요청":["korean"],"용기":["korean"],"용서":["korean"],"용어":["korean"],"우산":["korean"],"우선":["korean"],"우승":["korean"],"우연히":["korean"],"우정":["korean"],"우체국":["korean"],"우편":["korean"],"운동":["korean"],"운명":["korean"],"운반":["korean"],"운전":["korean"],"운행":["korean"],"울산":["korean"],"울음":["korean"],"움직임":["korean"],"웃어른":["korean"],"웃음":["korean"],"워낙":["korean"],"원고":["korean"],"원래":["korean"],"원서":["korean"],"원숭이":["korean"],"원인":["korean"],"원장":["korean"],"원피스":["korean"],"월급":["korean"],"월드컵":["korean"],"월세":["korean"],"월요일":["korean"],"웨이터":["korean"],"위반":["korean"],"위법":["korean"],"위성":["korean"],"위원":["korean"],"위험":["korean"],"위협":["korean"],"윗사람":["korean"],"유난히":["korean"],"유럽":["korean"],"유명":["korean"],"유물":["korean"],"유산":["korean"],"유적":["korean"],"유치원":["korean"],"유학":["korean"],"유행":["korean"],"유형":["korean"],"육군":["korean"],"육상":["korean"],"육십":["korean"],"육체":["korean"],"은행":["korean"],"음력":["korean"],"음료":["korean"],"음반":["korean"],"음성":["korean"],"음식":["korean"],"음악":["korean"],"음주":["korean"],"의견":["korean"],"의논":["korean"],"의문":["korean"],"의복":["korean"],"의식":["korean"],"의심":["korean"],"의외로":["korean"],"의욕":["korean"],"의원":["korean"],"의학":["korean"],"이것":["korean"],"이곳":["korean"],"이념":["korean"],"이놈":["korean"],"이달":["korean"],"이대로":["korean"],"이동":["korean"],"이렇게":["korean"],"이력서":["korean"],"이론적":["korean"],"이름":["korean"],"이민":["korean"],"이발소":["korean"],"이별":["korean"],"이불":["korean"],"이빨":["korean"],"이상":["korean"],"이성":["korean"],"이슬":["korean"],"이야기":["korean"],"이용":["korean"],"이웃":["korean"],"이월":["korean"],"이윽고":["korean"],"이익":["korean"],"이전":["korean"],"이중":["korean"],"이튿날":["korean"],"이틀":["korean"],"이혼":["korean"],"인간":["korean"],"인격":["korean"],"인공":["korean"],"인구":["korean"],"인근":["korean"],"인기":["korean"],"인도":["korean"],"인류":["korean"],"인물":["korean"],"인생":["korean"],"인쇄":["korean"],"인연":["korean"],"인원":["korean"],"인재":["korean"],"인종":["korean"],"인천":["korean"],"인체":["korean"],"인터넷":["korean"],"인하":["korean"],"인형":["korean"],"일곱":["korean"],"일기":["korean"],"일단":["korean"],"일대":["korean"],"일등":["korean"],"일반":["korean"],"일본":["korean"],"일부":["korean"],"일상":["korean"],"일생":["korean"],"일손":["korean"],"일요일":["korean"],"일월":["korean"],"일정":["korean"],"일종":["korean"],"일주일":["korean"],"일찍":["korean"],"일체":["korean"],"일치":["korean"],"일행":["korean"],"일회용":["korean"],"임금":["korean"],"임무":["korean"],"입대":["korean"],"입력":["korean"],"입맛":["korean"],"입사":["korean"],"입술":["korean"],"입시":["korean"],"입원":["korean"],"입장":["korean"],"입학":["korean"],"자가용":["korean"],"자격":["korean"],"자극":["korean"],"자동":["korean"],"자랑":["korean"],"자부심":["korean"],"자식":["korean"],"자신":["korean"],"자연":["korean"],"자원":["korean"],"자율":["korean"],"자전거":["korean"],"자정":["korean"],"자존심":["korean"],"자판":["korean"],"작가":["korean"],"작년":["korean"],"작성":["korean"],"작업":["korean"],"작용":["korean"],"작은딸":["korean"],"작품":["korean"],"잔디":["korean"],"잔뜩":["korean"],"잔치":["korean"],"잘못":["korean"],"잠깐":["korean"],"잠수함":["korean"],"잠시":["korean"],"잠옷":["korean"],"잠자리":["korean"],"잡지":["korean"],"장관":["korean"],"장군":["korean"],"장기간":["korean"],"장래":["korean"],"장례":["korean"],"장르":["korean"],"장마":["korean"],"장면":["korean"],"장모":["korean"],"장미":["korean"],"장비":["korean"],"장사":["korean"],"장소":["korean"],"장식":["korean"],"장애인":["korean"],"장인":["korean"],"장점":["korean"],"장차":["korean"],"장학금":["korean"],"재능":["korean"],"재빨리":["korean"],"재산":["korean"],"재생":["korean"],"재작년":["korean"],"재정":["korean"],"재채기":["korean"],"재판":["korean"],"재학":["korean"],"재활용":["korean"],"저것":["korean"],"저고리":["korean"],"저곳":["korean"],"저녁":["korean"],"저런":["korean"],"저렇게":["korean"],"저번":["korean"],"저울":["korean"],"저절로":["korean"],"저축":["korean"],"적극":["korean"],"적당히":["korean"],"적성":["korean"],"적용":["korean"],"적응":["korean"],"전개":["korean"],"전공":["korean"],"전기":["korean"],"전달":["korean"],"전라도":["korean"],"전망":["korean"],"전문":["korean"],"전반":["korean"],"전부":["korean"],"전세":["korean"],"전시":["korean"],"전용":["korean"],"전자":["korean"],"전쟁":["korean"],"전주":["korean"],"전철":["korean"],"전체":["korean"],"전통":["korean"],"전혀":["korean"],"전후":["korean"],"절대":["korean"],"절망":["korean"],"절반":["korean"],"절약":["korean"],"절차":["korean"],"점검":["korean"],"점수":["korean"],"점심":["korean"],"점원":["korean"],"점점":["korean"],"점차":["korean"],"접근":["korean"],"접시":["korean"],"접촉":["korean"],"젓가락":["korean"],"정거장":["korean"],"정도":["korean"],"정류장":["korean"],"정리":["korean"],"정말":["korean"],"정면":["korean"],"정문":["korean"],"정반대":["korean"],"정보":["korean"],"정부":["korean"],"정비":["korean"],"정상":["korean"],"정성":["korean"],"정오":["korean"],"정원":["korean"],"정장":["korean"],"정지":["korean"],"정치":["korean"],"정확히":["korean"],"제공":["korean"],"제과점":["korean"],"제대로":["korean"],"제목":["korean"],"제발":["korean"],"제법":["korean"],"제삿날":["korean"],"제안":["korean"],"제일":["korean"],"제작":["korean"],"제주도":["korean"],"제출":["korean"],"제품":["korean"],"제한":["korean"],"조각":["korean"],"조건":["korean"],"조금":["korean"],"조깅":["korean"],"조명":["korean"],"조미료":["korean"],"조상":["korean"],"조선":["korean"],"조용히":["korean"],"조절":["korean"],"조정":["korean"],"조직":["korean"],"존댓말":["korean"],"존재":["korean"],"졸업":["korean"],"졸음":["korean"],"종교":["korean"],"종로":["korean"],"종류":["korean"],"종소리":["korean"],"종업원":["korean"],"종종":["korean"],"종합":["korean"],"좌석":["korean"],"죄인":["korean"],"주관적":["korean"],"주름":["korean"],"주말":["korean"],"주머니":["korean"],"주먹":["korean"],"주문":["korean"],"주민":["korean"],"주방":["korean"],"주변":["korean"],"주식":["korean"],"주인":["korean"],"주일":["korean"],"주장":["korean"],"주전자":["korean"],"주택":["korean"],"준비":["korean"],"줄거리":["korean"],"줄기":["korean"],"줄무늬":["korean"],"중간":["korean"],"중계방송":["korean"],"중국":["korean"],"중년":["korean"],"중단":["korean"],"중독":["korean"],"중반":["korean"],"중부":["korean"],"중세":["korean"],"중소기업":["korean"],"중순":["korean"],"중앙":["korean"],"중요":["korean"],"중학교":["korean"],"즉석":["korean"],"즉시":["korean"],"즐거움":["korean"],"증가":["korean"],"증거":["korean"],"증권":["korean"],"증상":["korean"],"증세":["korean"],"지각":["korean"],"지갑":["korean"],"지경":["korean"],"지극히":["korean"],"지금":["korean"],"지급":["korean"],"지능":["korean"],"지름길":["korean"],"지리산":["korean"],"지방":["korean"],"지붕":["korean"],"지식":["korean"],"지역":["korean"],"지우개":["korean"],"지원":["korean"],"지적":["korean"],"지점":["korean"],"지진":["korean"],"지출":["korean"],"직선":["korean"],"직업":["korean"],"직원":["korean"],"직장":["korean"],"진급":["korean"],"진동":["korean"],"진로":["korean"],"진료":["korean"],"진리":["korean"],"진짜":["korean"],"진찰":["korean"],"진출":["korean"],"진통":["korean"],"진행":["korean"],"질문":["korean"],"질병":["korean"],"질서":["korean"],"짐작":["korean"],"집단":["korean"],"집안":["korean"],"집중":["korean"],"짜증":["korean"],"찌꺼기":["korean"],"차남":["korean"],"차라리":["korean"],"차량":["korean"],"차림":["korean"],"차별":["korean"],"차선":["korean"],"차츰":["korean"],"착각":["korean"],"찬물":["korean"],"찬성":["korean"],"참가":["korean"],"참기름":["korean"],"참새":["korean"],"참석":["korean"],"참여":["korean"],"참외":["korean"],"참조":["korean"],"찻잔":["korean"],"창가":["korean"],"창고":["korean"],"창구":["korean"],"창문":["korean"],"창밖":["korean"],"창작":["korean"],"창조":["korean"],"채널":["korean"],"채점":["korean"],"책가방":["korean"],"책방":["korean"],"책상":["korean"],"책임":["korean"],"챔피언":["korean"],"처벌":["korean"],"처음":["korean"],"천국":["korean"],"천둥":["korean"],"천장":["korean"],"천재":["korean"],"천천히":["korean"],"철도":["korean"],"철저히":["korean"],"철학":["korean"],"첫날":["korean"],"첫째":["korean"],"청년":["korean"],"청바지":["korean"],"청소":["korean"],"청춘":["korean"],"체계":["korean"],"체력":["korean"],"체온":["korean"],"체육":["korean"],"체중":["korean"],"체험":["korean"],"초등학생":["korean"],"초반":["korean"],"초밥":["korean"],"초상화":["korean"],"초순":["korean"],"초여름":["korean"],"초원":["korean"],"초저녁":["korean"],"초점":["korean"],"초청":["korean"],"초콜릿":["korean"],"촛불":["korean"],"총각":["korean"],"총리":["korean"],"총장":["korean"],"촬영":["korean"],"최근":["korean"],"최상":["korean"],"최선":["korean"],"최신":["korean"],"최악":["korean"],"최종":["korean"],"추석":["korean"],"추억":["korean"],"추진":["korean"],"추천":["korean"],"추측":["korean"],"축구":["korean"],"축소":["korean"],"축제":["korean"],"축하":["korean"],"출근":["korean"],"출발":["korean"],"출산":["korean"],"출신":["korean"],"출연":["korean"],"출입":["korean"],"출장":["korean"],"출판":["korean"],"충격":["korean"],"충고":["korean"],"충돌":["korean"],"충분히":["korean"],"충청도":["korean"],"취업":["korean"],"취직":["korean"],"취향":["korean"],"치약":["korean"],"친구":["korean"],"친척":["korean"],"칠십":["korean"],"칠월":["korean"],"칠판":["korean"],"침대":["korean"],"침묵":["korean"],"침실":["korean"],"칫솔":["korean"],"칭찬":["korean"],"카메라":["korean"],"카운터":["korean"],"칼국수":["korean"],"캐릭터":["korean"],"캠퍼스":["korean"],"캠페인":["korean"],"커튼":["korean"],"컨디션":["korean"],"컬러":["korean"],"컴퓨터":["korean"],"코끼리":["korean"],"코미디":["korean"],"콘서트":["korean"],"콜라":["korean"],"콤플렉스":["korean"],"콩나물":["korean"],"쾌감":["korean"],"쿠데타":["korean"],"크림":["korean"],"큰길":["korean"],"큰딸":["korean"],"큰소리":["korean"],"큰아들":["korean"],"큰어머니":["korean"],"큰일":["korean"],"큰절":["korean"],"클래식":["korean"],"클럽":["korean"],"킬로":["korean"],"타입":["korean"],"타자기":["korean"],"탁구":["korean"],"탁자":["korean"],"탄생":["korean"],"태권도":["korean"],"태양":["korean"],"태풍":["korean"],"택시":["korean"],"탤런트":["korean"],"터널":["korean"],"터미널":["korean"],"테니스":["korean"],"테스트":["korean"],"테이블":["korean"],"텔레비전":["korean"],"토론":["korean"],"토마토":["korean"],"토요일":["korean"],"통계":["korean"],"통과":["korean"],"통로":["korean"],"통신":["korean"],"통역":["korean"],"통일":["korean"],"통장":["korean"],"통제":["korean"],"통증":["korean"],"통합":["korean"],"통화":["korean"],"퇴근":["korean"],"퇴원":["korean"],"퇴직금":["korean"],"튀김":["korean"],"트럭":["korean"],"특급":["korean"],"특별":["korean"],"특성":["korean"],"특수":["korean"],"특징":["korean"],"특히":["korean"],"튼튼히":["korean"],"티셔츠":["korean"],"파란색":["korean"],"파일":["korean"],"파출소":["korean"],"판결":["korean"],"판단":["korean"],"판매":["korean"],"판사":["korean"],"팔십":["korean"],"팔월":["korean"],"팝송":["korean"],"패션":["korean"],"팩스":["korean"],"팩시밀리":["korean"],"팬티":["korean"],"퍼센트":["korean"],"페인트":["korean"],"편견":["korean"],"편의":["korean"],"편지":["korean"],"편히":["korean"],"평가":["korean"],"평균":["korean"],"평생":["korean"],"평소":["korean"],"평양":["korean"],"평일":["korean"],"평화":["korean"],"포스터":["korean"],"포인트":["korean"],"포장":["korean"],"포함":["korean"],"표면":["korean"],"표정":["korean"],"표준":["korean"],"표현":["korean"],"품목":["korean"],"품질":["korean"],"풍경":["korean"],"풍속":["korean"],"풍습":["korean"],"프랑스":["korean"],"프린터":["korean"],"플라스틱":["korean"],"피곤":["korean"],"피망":["korean"],"피아노":["korean"],"필름":["korean"],"필수":["korean"],"필요":["korean"],"필자":["korean"],"필통":["korean"],"핑계":["korean"],"하느님":["korean"],"하늘":["korean"],"하드웨어":["korean"],"하룻밤":["korean"],"하반기":["korean"],"하숙집":["korean"],"하순":["korean"],"하여튼":["korean"],"하지만":["korean"],"하천":["korean"],"하품":["korean"],"하필":["korean"],"학과":["korean"],"학교":["korean"],"학급":["korean"],"학기":["korean"],"학년":["korean"],"학력":["korean"],"학번":["korean"],"학부모":["korean"],"학비":["korean"],"학생":["korean"],"학술":["korean"],"학습":["korean"],"학용품":["korean"],"학원":["korean"],"학위":["korean"],"학자":["korean"],"학점":["korean"],"한계":["korean"],"한글":["korean"],"한꺼번에":["korean"],"한낮":["korean"],"한눈":["korean"],"한동안":["korean"],"한때":["korean"],"한라산":["korean"],"한마디":["korean"],"한문":["korean"],"한번":["korean"],"한복":["korean"],"한식":["korean"],"한여름":["korean"],"한쪽":["korean"],"할머니":["korean"],"할아버지":["korean"],"할인":["korean"],"함께":["korean"],"함부로":["korean"],"합격":["korean"],"합리적":["korean"],"항공":["korean"],"항구":["korean"],"항상":["korean"],"항의":["korean"],"해결":["korean"],"해군":["korean"],"해답":["korean"],"해당":["korean"],"해물":["korean"],"해석":["korean"],"해설":["korean"],"해수욕장":["korean"],"해안":["korean"],"핵심":["korean"],"핸드백":["korean"],"햄버거":["korean"],"햇볕":["korean"],"햇살":["korean"],"행동":["korean"],"행복":["korean"],"행사":["korean"],"행운":["korean"],"행위":["korean"],"향기":["korean"],"향상":["korean"],"향수":["korean"],"허락":["korean"],"허용":["korean"],"헬기":["korean"],"현관":["korean"],"현금":["korean"],"현대":["korean"],"현상":["korean"],"현실":["korean"],"현장":["korean"],"현재":["korean"],"현지":["korean"],"혈액":["korean"],"협력":["korean"],"형부":["korean"],"형사":["korean"],"형수":["korean"],"형식":["korean"],"형제":["korean"],"형태":["korean"],"형편":["korean"],"혜택":["korean"],"호기심":["korean"],"호남":["korean"],"호랑이":["korean"],"호박":["korean"],"호텔":["korean"],"호흡":["korean"],"혹시":["korean"],"홀로":["korean"],"홈페이지":["korean"],"홍보":["korean"],"홍수":["korean"],"홍차":["korean"],"화면":["korean"],"화분":["korean"],"화살":["korean"],"화요일":["korean"],"화장":["korean"],"화학":["korean"],"확보":["korean"],"확인":["korean"],"확장":["korean"],"확정":["korean"],"환갑":["korean"],"환경":["korean"],"환영":["korean"],"환율":["korean"],"환자":["korean"],"활기":["korean"],"활동":["korean"],"활발히":["korean"],"활용":["korean"],"활짝":["korean"],"회견":["korean"],"회관":["korean"],"회복":["korean"],"회색":["korean"],"회원":["korean"],"회장":["korean"],"회전":["korean"],"횟수":["korean"],"횡단보도":["korean"],"효율적":["korean"],"후반":["korean"],"후춧가루":["korean"],"훈련":["korean"],"훨씬":["korean"],"휴식":["korean"],"휴일":["korean"],"흉내":["korean"],"흐름":["korean"],"흑백":["korean"],"흑인":["korean"],"흔적":["korean"],"흔히":["korean"],"흥미":["korean"],"흥분":["korean"],"희곡":["korean"],"희망":["korean"],"희생":["korean"],"흰색":["korean"],"힘껏":["korean"],"abacate":["portuguese"],"abaixo":["portuguese"],"abalar":["portuguese"],"abater":["portuguese"],"abduzir":["portuguese"],"abelha":["portuguese"],"aberto":["portuguese"],"abismo":["portuguese"],"abotoar":["portuguese"],"abranger":["portuguese"],"abreviar":["portuguese"],"abrigar":["portuguese"],"abrupto":["portuguese"],"absinto":["portuguese"],"absoluto":["portuguese"],"absurdo":["portuguese"],"abutre":["portuguese"],"acabado":["portuguese"],"acalmar":["portuguese"],"acampar":["portuguese"],"acanhar":["portuguese"],"acaso":["portuguese"],"aceitar":["portuguese"],"acelerar":["portuguese"],"acenar":["portuguese"],"acervo":["portuguese"],"acessar":["portuguese"],"acetona":["portuguese"],"achatar":["portuguese"],"acidez":["portuguese"],"acima":["portuguese"],"acionado":["portuguese"],"acirrar":["portuguese"],"aclamar":["portuguese"],"aclive":["portuguese"],"acolhida":["portuguese"],"acomodar":["portuguese"],"acoplar":["portuguese"],"acordar":["portuguese"],"acumular":["portuguese"],"acusador":["portuguese"],"adaptar":["portuguese"],"adega":["portuguese"],"adentro":["portuguese"],"adepto":["portuguese"],"adequar":["portuguese"],"aderente":["portuguese"],"adesivo":["portuguese"],"adeus":["portuguese"],"adiante":["portuguese"],"aditivo":["portuguese"],"adjetivo":["portuguese"],"adjunto":["portuguese"],"admirar":["portuguese"],"adorar":["portuguese"],"adquirir":["portuguese"],"adubo":["portuguese"],"adverso":["portuguese"],"advogado":["portuguese"],"aeronave":["portuguese"],"afastar":["portuguese"],"aferir":["portuguese"],"afetivo":["portuguese"],"afinador":["portuguese"],"afivelar":["portuguese"],"aflito":["portuguese"],"afluente":["portuguese"],"afrontar":["portuguese"],"agachar":["portuguese"],"agarrar":["portuguese"],"agasalho":["portuguese"],"agenciar":["portuguese"],"agilizar":["portuguese"],"agiota":["portuguese"],"agitado":["portuguese"],"agora":["portuguese"],"agradar":["portuguese"],"agreste":["portuguese"],"agrupar":["portuguese"],"aguardar":["portuguese"],"agulha":["portuguese"],"ajoelhar":["portuguese"],"ajudar":["portuguese"],"ajustar":["portuguese"],"alameda":["portuguese"],"alarme":["portuguese"],"alastrar":["portuguese"],"alavanca":["portuguese"],"albergue":["portuguese"],"albino":["portuguese"],"alcatra":["portuguese"],"aldeia":["portuguese"],"alecrim":["portuguese"],"alegria":["portuguese"],"alertar":["portuguese"],"alface":["portuguese"],"alfinete":["portuguese"],"algum":["portuguese"],"alheio":["portuguese"],"aliar":["portuguese"],"alicate":["portuguese"],"alienar":["portuguese"],"alinhar":["portuguese"],"aliviar":["portuguese"],"almofada":["portuguese"],"alocar":["portuguese"],"alpiste":["portuguese"],"alterar":["portuguese"],"altitude":["portuguese"],"alucinar":["portuguese"],"alugar":["portuguese"],"aluno":["portuguese"],"alusivo":["portuguese"],"alvo":["portuguese"],"amaciar":["portuguese"],"amador":["portuguese"],"amarelo":["portuguese"],"amassar":["portuguese"],"ambas":["portuguese"],"ambiente":["portuguese"],"ameixa":["portuguese"],"amenizar":["portuguese"],"amido":["portuguese"],"amistoso":["portuguese"],"amizade":["portuguese"],"amolador":["portuguese"],"amontoar":["portuguese"],"amoroso":["portuguese"],"amostra":["portuguese"],"amparar":["portuguese"],"ampliar":["portuguese"],"ampola":["portuguese"],"anagrama":["portuguese"],"analisar":["portuguese"],"anarquia":["portuguese"],"anatomia":["portuguese"],"andaime":["portuguese"],"anel":["portuguese"],"anexo":["portuguese"],"angular":["portuguese"],"animar":["portuguese"],"anjo":["portuguese"],"anomalia":["portuguese"],"anotado":["portuguese"],"ansioso":["portuguese"],"anterior":["portuguese"],"anuidade":["portuguese"],"anunciar":["portuguese"],"anzol":["portuguese"],"apagador":["portuguese"],"apalpar":["portuguese"],"apanhado":["portuguese"],"apego":["portuguese"],"apelido":["portuguese"],"apertada":["portuguese"],"apesar":["portuguese"],"apetite":["portuguese"],"apito":["portuguese"],"aplauso":["portuguese"],"aplicada":["portuguese"],"apoio":["portuguese"],"apontar":["portuguese"],"aposta":["portuguese"],"aprendiz":["portuguese"],"aprovar":["portuguese"],"aquecer":["portuguese"],"arame":["portuguese"],"aranha":["portuguese"],"arara":["portuguese"],"arcada":["portuguese"],"ardente":["portuguese"],"areia":["portuguese"],"arejar":["portuguese"],"arenito":["portuguese"],"aresta":["portuguese"],"argiloso":["portuguese"],"argola":["portuguese"],"arma":["portuguese"],"arquivo":["portuguese"],"arraial":["portuguese"],"arrebate":["portuguese"],"arriscar":["portuguese"],"arroba":["portuguese"],"arrumar":["portuguese"],"arsenal":["portuguese"],"arterial":["portuguese"],"artigo":["portuguese"],"arvoredo":["portuguese"],"asfaltar":["portuguese"],"asilado":["portuguese"],"aspirar":["portuguese"],"assador":["portuguese"],"assinar":["portuguese"],"assoalho":["portuguese"],"assunto":["portuguese"],"astral":["portuguese"],"atacado":["portuguese"],"atadura":["portuguese"],"atalho":["portuguese"],"atarefar":["portuguese"],"atear":["portuguese"],"atender":["portuguese"],"aterro":["portuguese"],"ateu":["portuguese"],"atingir":["portuguese"],"atirador":["portuguese"],"ativo":["portuguese"],"atoleiro":["portuguese"],"atracar":["portuguese"],"atrevido":["portuguese"],"atriz":["portuguese"],"atual":["portuguese"],"atum":["portuguese"],"auditor":["portuguese"],"aumentar":["portuguese"],"aura":["portuguese"],"aurora":["portuguese"],"autismo":["portuguese"],"autoria":["portuguese"],"autuar":["portuguese"],"avaliar":["portuguese"],"avante":["portuguese"],"avaria":["portuguese"],"avental":["portuguese"],"avesso":["portuguese"],"aviador":["portuguese"],"avisar":["portuguese"],"avulso":["portuguese"],"axila":["portuguese"],"azarar":["portuguese"],"azedo":["portuguese"],"azeite":["portuguese"],"azulejo":["portuguese"],"babar":["portuguese"],"babosa":["portuguese"],"bacalhau":["portuguese"],"bacharel":["portuguese"],"bacia":["portuguese"],"bagagem":["portuguese"],"baiano":["portuguese"],"bailar":["portuguese"],"baioneta":["portuguese"],"bairro":["portuguese"],"baixista":["portuguese"],"bajular":["portuguese"],"baleia":["portuguese"],"baliza":["portuguese"],"balsa":["portuguese"],"banal":["portuguese"],"bandeira":["portuguese"],"banho":["portuguese"],"banir":["portuguese"],"banquete":["portuguese"],"barato":["portuguese"],"barbado":["portuguese"],"baronesa":["portuguese"],"barraca":["portuguese"],"barulho":["portuguese"],"baseado":["portuguese"],"bastante":["portuguese"],"batata":["portuguese"],"batedor":["portuguese"],"batida":["portuguese"],"batom":["portuguese"],"batucar":["portuguese"],"baunilha":["portuguese"],"beber":["portuguese"],"beijo":["portuguese"],"beirada":["portuguese"],"beisebol":["portuguese"],"beldade":["portuguese"],"beleza":["portuguese"],"belga":["portuguese"],"beliscar":["portuguese"],"bendito":["portuguese"],"bengala":["portuguese"],"benzer":["portuguese","turkish"],"berimbau":["portuguese"],"berlinda":["portuguese"],"berro":["portuguese"],"besouro":["portuguese"],"bexiga":["portuguese"],"bezerro":["portuguese"],"bico":["portuguese"],"bicudo":["portuguese"],"bienal":["portuguese"],"bifocal":["portuguese"],"bifurcar":["portuguese"],"bigorna":["portuguese"],"bilhete":["portuguese"],"bimestre":["portuguese"],"bimotor":["portuguese"],"biologia":["portuguese"],"biombo":["portuguese"],"biosfera":["portuguese"],"bipolar":["portuguese"],"birrento":["portuguese"],"biscoito":["portuguese"],"bisneto":["portuguese"],"bispo":["portuguese"],"bissexto":["portuguese"],"bitola":["portuguese"],"bizarro":["portuguese"],"blindado":["portuguese"],"bloco":["portuguese"],"bloquear":["portuguese"],"boato":["portuguese"],"bobagem":["portuguese"],"bocado":["portuguese"],"bocejo":["portuguese"],"bochecha":["portuguese"],"boicotar":["portuguese"],"bolada":["portuguese"],"boletim":["portuguese"],"bolha":["portuguese"],"bolo":["portuguese"],"bombeiro":["portuguese"],"bonde":["portuguese"],"boneco":["portuguese"],"bonita":["portuguese"],"borbulha":["portuguese"],"borda":["portuguese"],"boreal":["portuguese"],"borracha":["portuguese"],"bovino":["portuguese"],"boxeador":["portuguese"],"branco":["portuguese"],"brasa":["portuguese"],"braveza":["portuguese"],"breu":["portuguese"],"briga":["portuguese"],"brilho":["portuguese"],"brincar":["portuguese"],"broa":["portuguese"],"brochura":["portuguese"],"bronzear":["portuguese"],"broto":["portuguese"],"bruxo":["portuguese"],"bucha":["portuguese"],"budismo":["portuguese"],"bufar":["portuguese"],"bule":["portuguese"],"buraco":["portuguese"],"busca":["portuguese"],"busto":["portuguese"],"buzina":["portuguese"],"cabana":["portuguese"],"cabelo":["portuguese"],"cabide":["portuguese"],"cabo":["portuguese"],"cabrito":["portuguese"],"cacau":["portuguese"],"cacetada":["portuguese"],"cachorro":["portuguese"],"cacique":["portuguese"],"cadastro":["portuguese"],"cadeado":["portuguese"],"cafezal":["portuguese"],"caiaque":["portuguese"],"caipira":["portuguese"],"caixote":["portuguese"],"cajado":["portuguese"],"caju":["portuguese"],"calafrio":["portuguese"],"calcular":["portuguese"],"caldeira":["portuguese"],"calibrar":["portuguese"],"calmante":["portuguese"],"calota":["portuguese"],"camada":["portuguese"],"cambista":["portuguese"],"camisa":["portuguese"],"camomila":["portuguese"],"campanha":["portuguese"],"camuflar":["portuguese"],"canavial":["portuguese"],"cancelar":["portuguese"],"caneta":["portuguese"],"canguru":["portuguese"],"canhoto":["portuguese"],"canivete":["portuguese"],"canoa":["portuguese"],"cansado":["portuguese"],"cantar":["portuguese"],"canudo":["portuguese"],"capacho":["portuguese"],"capela":["portuguese"],"capinar":["portuguese"],"capotar":["portuguese"],"capricho":["portuguese"],"captador":["portuguese"],"capuz":["portuguese"],"caracol":["portuguese"],"carbono":["portuguese"],"cardeal":["portuguese"],"careca":["portuguese"],"carimbar":["portuguese"],"carneiro":["portuguese"],"carpete":["portuguese"],"carreira":["portuguese"],"cartaz":["portuguese"],"carvalho":["portuguese"],"casaco":["portuguese"],"casca":["portuguese"],"casebre":["portuguese"],"castelo":["portuguese"],"casulo":["portuguese"],"catarata":["portuguese"],"cativar":["portuguese"],"caule":["portuguese"],"causador":["portuguese"],"cautelar":["portuguese"],"cavalo":["portuguese"],"caverna":["portuguese"],"cebola":["portuguese"],"cedilha":["portuguese"],"cegonha":["portuguese"],"celebrar":["portuguese"],"celular":["portuguese"],"cenoura":["portuguese"],"censo":["portuguese"],"centeio":["portuguese"],"cercar":["portuguese"],"cerrado":["portuguese"],"certeiro":["portuguese"],"cerveja":["portuguese"],"cetim":["portuguese"],"cevada":["portuguese"],"chacota":["portuguese"],"chaleira":["portuguese"],"chamado":["portuguese"],"chapada":["portuguese"],"charme":["portuguese"],"chatice":["portuguese"],"chave":["portuguese"],"chefe":["portuguese"],"chegada":["portuguese"],"cheiro":["portuguese"],"cheque":["portuguese"],"chicote":["portuguese"],"chifre":["portuguese"],"chinelo":["portuguese"],"chocalho":["portuguese"],"chover":["portuguese"],"chumbo":["portuguese"],"chutar":["portuguese"],"chuva":["portuguese"],"cicatriz":["portuguese"],"ciclone":["portuguese"],"cidade":["portuguese"],"cidreira":["portuguese"],"ciente":["portuguese"],"cigana":["portuguese"],"cimento":["portuguese"],"cinto":["portuguese"],"cinza":["portuguese"],"ciranda":["portuguese"],"circuito":["portuguese"],"cirurgia":["portuguese"],"citar":["portuguese"],"clareza":["portuguese"],"clero":["portuguese"],"clicar":["portuguese"],"clone":["portuguese"],"clube":["portuguese"],"coado":["portuguese"],"coagir":["portuguese"],"cobaia":["portuguese"],"cobertor":["portuguese"],"cobrar":["portuguese"],"cocada":["portuguese"],"coelho":["portuguese"],"coentro":["portuguese"],"coeso":["portuguese"],"cogumelo":["portuguese"],"coibir":["portuguese"],"coifa":["portuguese"],"coiote":["portuguese"],"colar":["portuguese"],"coleira":["portuguese"],"colher":["portuguese"],"colidir":["portuguese"],"colmeia":["portuguese"],"colono":["portuguese"],"coluna":["portuguese"],"comando":["portuguese"],"combinar":["portuguese"],"comentar":["portuguese"],"comitiva":["portuguese"],"comover":["portuguese"],"complexo":["portuguese"],"comum":["portuguese"],"concha":["portuguese"],"condor":["portuguese"],"conectar":["portuguese"],"confuso":["portuguese"],"congelar":["portuguese"],"conhecer":["portuguese"],"conjugar":["portuguese"],"consumir":["portuguese"],"contrato":["portuguese"],"convite":["portuguese"],"cooperar":["portuguese"],"copeiro":["portuguese"],"copiador":["portuguese"],"copo":["portuguese"],"coquetel":["portuguese"],"coragem":["portuguese"],"cordial":["portuguese"],"corneta":["portuguese"],"coronha":["portuguese"],"corporal":["portuguese"],"correio":["portuguese"],"cortejo":["portuguese"],"coruja":["portuguese"],"corvo":["portuguese"],"cosseno":["portuguese"],"costela":["portuguese"],"cotonete":["portuguese"],"couro":["portuguese"],"couve":["portuguese"],"covil":["portuguese"],"cozinha":["portuguese"],"cratera":["portuguese"],"cravo":["portuguese"],"creche":["portuguese"],"credor":["portuguese"],"creme":["portuguese"],"crer":["portuguese"],"crespo":["portuguese"],"criada":["portuguese"],"criminal":["portuguese"],"crioulo":["portuguese"],"crise":["portuguese"],"criticar":["portuguese"],"crosta":["portuguese"],"crua":["portuguese"],"cruzeiro":["portuguese"],"cubano":["portuguese"],"cueca":["portuguese"],"cuidado":["portuguese"],"cujo":["portuguese"],"culatra":["portuguese"],"culminar":["portuguese"],"culpar":["portuguese"],"cultura":["portuguese"],"cumprir":["portuguese"],"cunhado":["portuguese"],"cupido":["portuguese"],"curativo":["portuguese"],"curral":["portuguese"],"cursar":["portuguese"],"curto":["portuguese"],"cuspir":["portuguese"],"custear":["portuguese"],"cutelo":["portuguese"],"damasco":["portuguese"],"datar":["portuguese"],"debater":["portuguese"],"debitar":["portuguese"],"deboche":["portuguese"],"debulhar":["portuguese"],"decalque":["portuguese"],"decimal":["portuguese"],"declive":["portuguese"],"decote":["portuguese"],"decretar":["portuguese"],"dedal":["portuguese"],"dedicado":["portuguese"],"deduzir":["portuguese"],"defesa":["portuguese"],"defumar":["portuguese"],"degelo":["portuguese"],"degrau":["portuguese"],"degustar":["portuguese"],"deitado":["portuguese"],"deixar":["portuguese"],"delator":["portuguese"],"delegado":["portuguese"],"delinear":["portuguese"],"delonga":["portuguese"],"demanda":["portuguese"],"demitir":["portuguese"],"demolido":["portuguese"],"dentista":["portuguese"],"depenado":["portuguese"],"depilar":["portuguese"],"depois":["portuguese"],"depressa":["portuguese"],"depurar":["portuguese"],"deriva":["portuguese"],"derramar":["portuguese"],"desafio":["portuguese"],"desbotar":["portuguese"],"descanso":["portuguese"],"desenho":["portuguese"],"desfiado":["portuguese"],"desgaste":["portuguese"],"desigual":["portuguese"],"deslize":["portuguese"],"desmamar":["portuguese"],"desova":["portuguese"],"despesa":["portuguese"],"destaque":["portuguese"],"desviar":["portuguese"],"detalhar":["portuguese"],"detentor":["portuguese"],"detonar":["portuguese"],"detrito":["portuguese"],"deusa":["portuguese"],"dever":["portuguese"],"devido":["portuguese"],"devotado":["portuguese"],"dezena":["portuguese"],"diagrama":["portuguese"],"dialeto":["portuguese"],"didata":["portuguese"],"difuso":["portuguese"],"digitar":["portuguese"],"dilatado":["portuguese"],"diluente":["portuguese"],"diminuir":["portuguese"],"dinastia":["portuguese"],"dinheiro":["portuguese"],"diocese":["portuguese"],"direto":["portuguese"],"discreta":["portuguese"],"disfarce":["portuguese"],"disparo":["portuguese"],"disquete":["portuguese"],"dissipar":["portuguese"],"distante":["portuguese"],"ditador":["portuguese"],"diurno":["portuguese"],"diverso":["portuguese"],"divisor":["portuguese"],"divulgar":["portuguese"],"dizer":["portuguese"],"dobrador":["portuguese"],"dolorido":["portuguese"],"domador":["portuguese"],"dominado":["portuguese"],"donativo":["portuguese"],"donzela":["portuguese"],"dormente":["portuguese"],"dorsal":["portuguese"],"dosagem":["portuguese"],"dourado":["portuguese"],"doutor":["portuguese"],"drenagem":["portuguese"],"drible":["portuguese"],"drogaria":["portuguese"],"duelar":["portuguese"],"duende":["portuguese"],"dueto":["portuguese"],"duplo":["portuguese"],"duquesa":["portuguese"],"durante":["portuguese"],"duvidoso":["portuguese"],"eclodir":["portuguese"],"ecoar":["portuguese"],"ecologia":["portuguese"],"edificar":["portuguese"],"edital":["portuguese"],"educado":["portuguese"],"efeito":["portuguese"],"efetivar":["portuguese"],"ejetar":["portuguese"],"elaborar":["portuguese"],"eleger":["portuguese"],"eleitor":["portuguese"],"elenco":["portuguese"],"elevador":["portuguese"],"eliminar":["portuguese"],"elogiar":["portuguese"],"embargo":["portuguese"],"embolado":["portuguese"],"embrulho":["portuguese"],"embutido":["portuguese"],"emenda":["portuguese"],"emergir":["portuguese"],"emissor":["portuguese"],"empatia":["portuguese"],"empenho":["portuguese"],"empinado":["portuguese"],"empolgar":["portuguese"],"emprego":["portuguese"],"empurrar":["portuguese"],"emulador":["portuguese"],"encaixe":["portuguese"],"encenado":["portuguese"],"enchente":["portuguese"],"encontro":["portuguese"],"endeusar":["portuguese"],"endossar":["portuguese"],"enfaixar":["portuguese"],"enfeite":["portuguese"],"enfim":["portuguese"],"engajado":["portuguese"],"engenho":["portuguese"],"englobar":["portuguese"],"engomado":["portuguese"],"engraxar":["portuguese"],"enguia":["portuguese"],"enjoar":["portuguese"],"enlatar":["portuguese"],"enquanto":["portuguese"],"enraizar":["portuguese"],"enrolado":["portuguese"],"enrugar":["portuguese"],"ensaio":["portuguese"],"enseada":["portuguese"],"ensino":["portuguese"],"ensopado":["portuguese"],"entanto":["portuguese"],"enteado":["portuguese"],"entidade":["portuguese"],"entortar":["portuguese"],"entrada":["portuguese"],"entulho":["portuguese"],"envergar":["portuguese"],"enviado":["portuguese"],"envolver":["portuguese"],"enxame":["portuguese"],"enxerto":["portuguese"],"enxofre":["portuguese"],"enxuto":["portuguese"],"epiderme":["portuguese"],"equipar":["portuguese"],"ereto":["portuguese"],"erguido":["portuguese"],"errata":["portuguese"],"erva":["portuguese"],"ervilha":["portuguese"],"esbanjar":["portuguese"],"esbelto":["portuguese"],"escama":["portuguese"],"escola":["portuguese"],"escrita":["portuguese"],"escuta":["portuguese"],"esfinge":["portuguese"],"esfolar":["portuguese"],"esfregar":["portuguese"],"esfumado":["portuguese"],"esgrima":["portuguese"],"esmalte":["portuguese"],"espanto":["portuguese"],"espelho":["portuguese"],"espiga":["portuguese"],"esponja":["portuguese"],"espreita":["portuguese"],"espumar":["portuguese"],"esquerda":["portuguese"],"estaca":["portuguese"],"esteira":["portuguese"],"esticar":["portuguese"],"estofado":["portuguese"],"estrela":["portuguese"],"estudo":["portuguese"],"esvaziar":["portuguese"],"etanol":["portuguese"],"etiqueta":["portuguese"],"euforia":["portuguese"],"europeu":["portuguese"],"evacuar":["portuguese"],"evaporar":["portuguese"],"evasivo":["portuguese"],"eventual":["portuguese"],"evidente":["portuguese"],"evoluir":["portuguese"],"exagero":["portuguese"],"exalar":["portuguese"],"examinar":["portuguese"],"exato":["portuguese"],"exausto":["portuguese"],"excesso":["portuguese"],"excitar":["portuguese"],"exclamar":["portuguese"],"executar":["portuguese"],"exemplo":["portuguese"],"exibir":["portuguese"],"exigente":["portuguese"],"exonerar":["portuguese"],"expandir":["portuguese"],"expelir":["portuguese"],"expirar":["portuguese"],"explanar":["portuguese"],"exposto":["portuguese"],"expresso":["portuguese"],"expulsar":["portuguese"],"externo":["portuguese"],"extinto":["portuguese"],"extrato":["portuguese"],"fabricar":["portuguese"],"fabuloso":["portuguese"],"faceta":["portuguese"],"facial":["portuguese"],"fada":["portuguese"],"fadiga":["portuguese"],"faixa":["portuguese"],"falar":["portuguese"],"falta":["portuguese"],"familiar":["portuguese"],"fandango":["portuguese"],"fanfarra":["portuguese"],"fantoche":["portuguese"],"fardado":["portuguese"],"farelo":["portuguese"],"farinha":["portuguese"],"farofa":["portuguese"],"farpa":["portuguese"],"fartura":["portuguese"],"fatia":["portuguese"],"fator":["portuguese"],"favorita":["portuguese"],"faxina":["portuguese"],"fazenda":["portuguese"],"fechado":["portuguese"],"feijoada":["portuguese"],"feirante":["portuguese"],"felino":["portuguese"],"feminino":["portuguese"],"fenda":["portuguese"],"feno":["portuguese"],"fera":["portuguese"],"feriado":["portuguese"],"ferrugem":["portuguese"],"ferver":["portuguese"],"festejar":["portuguese"],"fetal":["portuguese"],"feudal":["portuguese"],"fiapo":["portuguese"],"fibrose":["portuguese"],"ficar":["portuguese"],"ficheiro":["portuguese"],"figurado":["portuguese"],"fileira":["portuguese"],"filho":["portuguese"],"filme":["portuguese"],"filtrar":["portuguese"],"firmeza":["portuguese"],"fisgada":["portuguese"],"fissura":["portuguese"],"fita":["portuguese"],"fivela":["portuguese"],"fixador":["portuguese"],"fixo":["portuguese"],"flacidez":["portuguese"],"flamingo":["portuguese"],"flanela":["portuguese"],"flechada":["portuguese"],"flora":["portuguese"],"flutuar":["portuguese"],"fluxo":["portuguese"],"focal":["portuguese"],"focinho":["portuguese"],"fofocar":["portuguese"],"fogo":["portuguese"],"foguete":["portuguese"],"foice":["portuguese"],"folgado":["portuguese"],"folheto":["portuguese"],"forjar":["portuguese"],"formiga":["portuguese"],"forno":["portuguese"],"forte":["portuguese"],"fosco":["portuguese"],"fossa":["portuguese"],"fragata":["portuguese"],"fralda":["portuguese"],"frango":["portuguese"],"frasco":["portuguese"],"fraterno":["portuguese"],"freira":["portuguese"],"frente":["portuguese"],"fretar":["portuguese"],"frieza":["portuguese"],"friso":["portuguese"],"fritura":["portuguese"],"fronha":["portuguese"],"frustrar":["portuguese"],"fruteira":["portuguese"],"fugir":["portuguese"],"fulano":["portuguese"],"fuligem":["portuguese"],"fundar":["portuguese"],"fungo":["portuguese"],"funil":["portuguese"],"furador":["portuguese"],"furioso":["portuguese"],"futebol":["portuguese"],"gabarito":["portuguese"],"gabinete":["portuguese"],"gado":["portuguese"],"gaiato":["portuguese"],"gaiola":["portuguese"],"gaivota":["portuguese"],"galega":["portuguese"],"galho":["portuguese"],"galinha":["portuguese"],"galocha":["portuguese"],"ganhar":["portuguese"],"garagem":["portuguese"],"garfo":["portuguese"],"gargalo":["portuguese"],"garimpo":["portuguese"],"garoupa":["portuguese"],"garrafa":["portuguese"],"gasoduto":["portuguese"],"gasto":["portuguese"],"gata":["portuguese"],"gatilho":["portuguese"],"gaveta":["portuguese"],"gazela":["portuguese"],"gelado":["portuguese"],"geleia":["portuguese"],"gelo":["portuguese"],"gemada":["portuguese"],"gemer":["portuguese"],"gemido":["portuguese"],"generoso":["portuguese"],"gengiva":["portuguese"],"genial":["portuguese"],"genoma":["portuguese"],"genro":["portuguese"],"geologia":["portuguese"],"gerador":["portuguese"],"germinar":["portuguese"],"gesso":["portuguese"],"gestor":["portuguese"],"ginasta":["portuguese"],"gincana":["portuguese"],"gingado":["portuguese"],"girafa":["portuguese"],"girino":["portuguese"],"glacial":["portuguese"],"glicose":["portuguese"],"global":["portuguese"],"glorioso":["portuguese"],"goela":["portuguese"],"goiaba":["portuguese"],"golfe":["portuguese"],"golpear":["portuguese"],"gordura":["portuguese"],"gorjeta":["portuguese"],"gorro":["portuguese"],"gostoso":["portuguese"],"goteira":["portuguese"],"governar":["portuguese"],"gracejo":["portuguese"],"gradual":["portuguese"],"grafite":["portuguese"],"gralha":["portuguese"],"grampo":["portuguese"],"granada":["portuguese"],"gratuito":["portuguese"],"graveto":["portuguese"],"graxa":["portuguese"],"grego":["portuguese"],"grelhar":["portuguese"],"greve":["portuguese"],"grilo":["portuguese"],"grisalho":["portuguese"],"gritaria":["portuguese"],"grosso":["portuguese"],"grotesco":["portuguese"],"grudado":["portuguese"],"grunhido":["portuguese"],"gruta":["portuguese"],"guache":["portuguese"],"guarani":["portuguese"],"guaxinim":["portuguese"],"guerrear":["portuguese"],"guiar":["portuguese"],"guincho":["portuguese"],"guisado":["portuguese"],"gula":["portuguese"],"guloso":["portuguese"],"guru":["portuguese"],"habitar":["portuguese"],"harmonia":["portuguese"],"haste":["portuguese"],"haver":["portuguese"],"hectare":["portuguese"],"herdar":["portuguese"],"heresia":["portuguese"],"hesitar":["portuguese"],"hiato":["portuguese"],"hibernar":["portuguese"],"hidratar":["portuguese"],"hiena":["portuguese"],"hino":["portuguese"],"hipismo":["portuguese"],"hipnose":["portuguese"],"hipoteca":["portuguese"],"hoje":["portuguese"],"holofote":["portuguese"],"homem":["portuguese"],"honesto":["portuguese"],"honrado":["portuguese"],"hormonal":["portuguese"],"hospedar":["portuguese"],"humorado":["portuguese"],"iate":["portuguese"],"ideia":["portuguese"],"idoso":["portuguese"],"ignorado":["portuguese"],"igreja":["portuguese"],"iguana":["portuguese"],"ileso":["portuguese"],"ilha":["portuguese"],"iludido":["portuguese"],"iluminar":["portuguese"],"ilustrar":["portuguese"],"imagem":["portuguese"],"imediato":["portuguese"],"imenso":["portuguese"],"imersivo":["portuguese"],"iminente":["portuguese"],"imitador":["portuguese"],"imortal":["portuguese"],"impacto":["portuguese"],"impedir":["portuguese"],"implante":["portuguese"],"impor":["portuguese"],"imprensa":["portuguese"],"impune":["portuguese"],"imunizar":["portuguese"],"inalador":["portuguese"],"inapto":["portuguese"],"inativo":["portuguese"],"incenso":["portuguese"],"inchar":["portuguese"],"incidir":["portuguese"],"incluir":["portuguese"],"incolor":["portuguese"],"indeciso":["portuguese"],"indireto":["portuguese"],"indutor":["portuguese"],"ineficaz":["portuguese"],"inerente":["portuguese"],"infantil":["portuguese"],"infestar":["portuguese"],"infinito":["portuguese"],"inflamar":["portuguese"],"informal":["portuguese"],"infrator":["portuguese"],"ingerir":["portuguese"],"inibido":["portuguese"],"inicial":["portuguese"],"inimigo":["portuguese"],"injetar":["portuguese"],"inocente":["portuguese"],"inodoro":["portuguese"],"inovador":["portuguese"],"inox":["portuguese"],"inquieto":["portuguese"],"inscrito":["portuguese"],"inseto":["portuguese"],"insistir":["portuguese"],"inspetor":["portuguese"],"instalar":["portuguese"],"insulto":["portuguese"],"intacto":["portuguese"],"integral":["portuguese"],"intimar":["portuguese"],"intocado":["portuguese"],"intriga":["portuguese"],"invasor":["portuguese"],"inverno":["portuguese"],"invicto":["portuguese"],"invocar":["portuguese"],"iogurte":["portuguese"],"iraniano":["portuguese"],"ironizar":["portuguese"],"irreal":["portuguese"],"irritado":["portuguese"],"isca":["portuguese"],"isento":["portuguese"],"isolado":["portuguese"],"isqueiro":["portuguese"],"italiano":["portuguese"],"janeiro":["portuguese"],"jangada":["portuguese"],"janta":["portuguese"],"jararaca":["portuguese"],"jardim":["portuguese"],"jarro":["portuguese"],"jasmim":["portuguese"],"jato":["portuguese"],"javali":["portuguese"],"jazida":["portuguese"],"jejum":["portuguese"],"joaninha":["portuguese"],"joelhada":["portuguese"],"jogador":["portuguese"],"joia":["portuguese"],"jornal":["portuguese"],"jorrar":["portuguese"],"jovem":["portuguese"],"juba":["portuguese"],"judeu":["portuguese"],"judoca":["portuguese"],"juiz":["portuguese"],"julgador":["portuguese"],"julho":["portuguese"],"jurado":["portuguese"],"jurista":["portuguese"],"juro":["portuguese"],"justa":["portuguese"],"labareda":["portuguese"],"laboral":["portuguese"],"lacre":["portuguese"],"lactante":["portuguese"],"ladrilho":["portuguese"],"lagarta":["portuguese"],"lagoa":["portuguese"],"laje":["portuguese"],"lamber":["portuguese"],"lamentar":["portuguese"],"laminar":["portuguese"],"lampejo":["portuguese"],"lanche":["portuguese"],"lapidar":["portuguese"],"lapso":["portuguese"],"laranja":["portuguese"],"lareira":["portuguese"],"largura":["portuguese"],"lasanha":["portuguese"],"lastro":["portuguese"],"lateral":["portuguese"],"latido":["portuguese"],"lavanda":["portuguese"],"lavoura":["portuguese"],"lavrador":["portuguese"],"laxante":["portuguese"],"lazer":["portuguese"],"lealdade":["portuguese"],"lebre":["portuguese"],"legado":["portuguese"],"legendar":["portuguese"],"legista":["portuguese"],"leigo":["portuguese"],"leiloar":["portuguese"],"leitura":["portuguese"],"lembrete":["portuguese"],"leme":["portuguese"],"lenhador":["portuguese"],"lentilha":["portuguese"],"leoa":["portuguese"],"lesma":["portuguese"],"leste":["portuguese"],"letivo":["portuguese"],"letreiro":["portuguese"],"levar":["portuguese"],"leveza":["portuguese"],"levitar":["portuguese"],"liberal":["portuguese"],"libido":["portuguese"],"liderar":["portuguese"],"ligar":["portuguese"],"ligeiro":["portuguese"],"limitar":["portuguese"],"limoeiro":["portuguese"],"limpador":["portuguese"],"linda":["portuguese"],"linear":["portuguese"],"linhagem":["portuguese"],"liquidez":["portuguese"],"listagem":["portuguese"],"lisura":["portuguese"],"litoral":["portuguese"],"livro":["portuguese"],"lixa":["portuguese"],"lixeira":["portuguese"],"locador":["portuguese"],"locutor":["portuguese"],"lojista":["portuguese"],"lombo":["portuguese"],"lona":["portuguese"],"longe":["portuguese"],"lontra":["portuguese"],"lorde":["portuguese"],"lotado":["portuguese"],"loteria":["portuguese"],"loucura":["portuguese"],"lousa":["portuguese"],"louvar":["portuguese"],"luar":["portuguese"],"lucidez":["portuguese"],"lucro":["portuguese"],"luneta":["portuguese"],"lustre":["portuguese"],"lutador":["portuguese"],"luva":["portuguese"],"macaco":["portuguese"],"macete":["portuguese"],"machado":["portuguese"],"macio":["portuguese"],"madeira":["portuguese"],"madrinha":["portuguese"],"magnata":["portuguese"],"magreza":["portuguese"],"maior":["portuguese"],"mais":["portuguese"],"malandro":["portuguese"],"malha":["portuguese"],"malote":["portuguese"],"maluco":["portuguese"],"mamilo":["portuguese"],"mamoeiro":["portuguese"],"mamute":["portuguese"],"manada":["portuguese"],"mancha":["portuguese"],"mandato":["portuguese"],"manequim":["portuguese"],"manhoso":["portuguese"],"manivela":["portuguese"],"manobrar":["portuguese"],"mansa":["portuguese"],"manter":["portuguese"],"manusear":["portuguese"],"mapeado":["portuguese"],"maquinar":["portuguese"],"marcador":["portuguese"],"maresia":["portuguese"],"marfim":["portuguese"],"margem":["portuguese"],"marinho":["portuguese"],"marmita":["portuguese"],"maroto":["portuguese"],"marquise":["portuguese"],"marreco":["portuguese"],"martelo":["portuguese"],"marujo":["portuguese"],"mascote":["portuguese"],"masmorra":["portuguese"],"massagem":["portuguese"],"mastigar":["portuguese"],"matagal":["portuguese"],"materno":["portuguese"],"matinal":["portuguese"],"matutar":["portuguese"],"maxilar":["portuguese"],"medalha":["portuguese"],"medida":["portuguese"],"medusa":["portuguese"],"megafone":["portuguese"],"meiga":["portuguese"],"melancia":["portuguese"],"melhor":["portuguese"],"membro":["portuguese"],"memorial":["portuguese"],"menino":["portuguese"],"menos":["portuguese"],"mensagem":["portuguese"],"mental":["portuguese"],"merecer":["portuguese"],"mergulho":["portuguese"],"mesada":["portuguese"],"mesclar":["portuguese"],"mesmo":["portuguese"],"mesquita":["portuguese"],"mestre":["portuguese"],"metade":["portuguese"],"meteoro":["portuguese"],"metragem":["portuguese"],"mexer":["portuguese"],"mexicano":["portuguese"],"micro":["portuguese"],"migalha":["portuguese"],"migrar":["portuguese"],"milagre":["portuguese"],"milenar":["portuguese"],"milhar":["portuguese"],"mimado":["portuguese"],"minerar":["portuguese"],"minhoca":["portuguese"],"ministro":["portuguese"],"minoria":["portuguese"],"miolo":["portuguese"],"mirante":["portuguese"],"mirtilo":["portuguese"],"misturar":["portuguese"],"mocidade":["portuguese"],"moderno":["portuguese"],"modular":["portuguese"],"moeda":["portuguese"],"moer":["portuguese"],"moinho":["portuguese"],"moita":["portuguese"],"moldura":["portuguese"],"moleza":["portuguese"],"molho":["portuguese"],"molinete":["portuguese"],"molusco":["portuguese"],"montanha":["portuguese"],"moqueca":["portuguese"],"morango":["portuguese"],"morcego":["portuguese"],"mordomo":["portuguese"],"morena":["portuguese"],"mosaico":["portuguese"],"mosquete":["portuguese"],"mostarda":["portuguese"],"motel":["portuguese"],"motim":["portuguese"],"moto":["portuguese"],"motriz":["portuguese"],"muda":["portuguese"],"muito":["portuguese"],"mulata":["portuguese"],"mulher":["portuguese"],"multar":["portuguese"],"mundial":["portuguese"],"munido":["portuguese"],"muralha":["portuguese"],"murcho":["portuguese"],"muscular":["portuguese"],"museu":["portuguese"],"musical":["portuguese"],"nacional":["portuguese"],"nadador":["portuguese"],"naja":["portuguese"],"namoro":["portuguese"],"narina":["portuguese"],"narrado":["portuguese"],"nascer":["portuguese"],"nativa":["portuguese"],"natureza":["portuguese"],"navalha":["portuguese"],"navegar":["portuguese"],"navio":["portuguese"],"neblina":["portuguese"],"nebuloso":["portuguese"],"negativa":["portuguese"],"negociar":["portuguese"],"negrito":["portuguese"],"nervoso":["portuguese"],"neta":["portuguese"],"neural":["portuguese"],"nevasca":["portuguese"],"nevoeiro":["portuguese"],"ninar":["portuguese"],"ninho":["portuguese"],"nitidez":["portuguese"],"nivelar":["portuguese"],"nobreza":["portuguese"],"noite":["portuguese"],"noiva":["portuguese"],"nomear":["portuguese"],"nominal":["portuguese"],"nordeste":["portuguese"],"nortear":["portuguese"],"notar":["portuguese"],"noticiar":["portuguese"],"noturno":["portuguese"],"novelo":["portuguese"],"novilho":["portuguese"],"novo":["portuguese"],"nublado":["portuguese"],"nudez":["portuguese"],"numeral":["portuguese"],"nupcial":["portuguese"],"nutrir":["portuguese"],"nuvem":["portuguese"],"obcecado":["portuguese"],"obedecer":["portuguese"],"objetivo":["portuguese"],"obrigado":["portuguese"],"obscuro":["portuguese"],"obstetra":["portuguese"],"obter":["portuguese"],"obturar":["portuguese"],"ocidente":["portuguese"],"ocioso":["portuguese"],"ocorrer":["portuguese"],"oculista":["portuguese"],"ocupado":["portuguese"],"ofegante":["portuguese"],"ofensiva":["portuguese"],"oferenda":["portuguese"],"oficina":["portuguese"],"ofuscado":["portuguese"],"ogiva":["portuguese"],"olaria":["portuguese"],"oleoso":["portuguese"],"olhar":["portuguese"],"oliveira":["portuguese"],"ombro":["portuguese"],"omelete":["portuguese"],"omisso":["portuguese"],"omitir":["portuguese"],"ondulado":["portuguese"],"oneroso":["portuguese"],"ontem":["portuguese"],"opcional":["portuguese"],"operador":["portuguese"],"oponente":["portuguese"],"oportuno":["portuguese"],"oposto":["portuguese"],"orar":["portuguese"],"orbitar":["portuguese"],"ordem":["portuguese"],"ordinal":["portuguese"],"orfanato":["portuguese"],"orgasmo":["portuguese"],"orgulho":["portuguese"],"oriental":["portuguese"],"origem":["portuguese"],"oriundo":["portuguese"],"orla":["portuguese"],"ortodoxo":["portuguese"],"orvalho":["portuguese"],"oscilar":["portuguese"],"ossada":["portuguese"],"osso":["portuguese"],"ostentar":["portuguese"],"otimismo":["portuguese"],"ousadia":["portuguese"],"outono":["portuguese"],"outubro":["portuguese"],"ouvido":["portuguese"],"ovelha":["portuguese"],"ovular":["portuguese"],"oxidar":["portuguese"],"oxigenar":["portuguese"],"pacato":["portuguese"],"paciente":["portuguese"],"pacote":["portuguese"],"pactuar":["portuguese"],"padaria":["portuguese"],"padrinho":["portuguese"],"pagar":["portuguese"],"pagode":["portuguese"],"painel":["portuguese"],"pairar":["portuguese"],"paisagem":["portuguese"],"palavra":["portuguese"],"palestra":["portuguese"],"palheta":["portuguese"],"palito":["portuguese"],"palmada":["portuguese"],"palpitar":["portuguese"],"pancada":["portuguese"],"panela":["portuguese"],"panfleto":["portuguese"],"panqueca":["portuguese"],"pantanal":["portuguese"],"papagaio":["portuguese"],"papelada":["portuguese"],"papiro":["portuguese"],"parafina":["portuguese"],"parcial":["portuguese"],"pardal":["portuguese"],"parede":["portuguese"],"partida":["portuguese"],"pasmo":["portuguese"],"passado":["portuguese"],"pastel":["portuguese"],"patamar":["portuguese"],"patente":["portuguese"],"patinar":["portuguese"],"patrono":["portuguese"],"paulada":["portuguese"],"pausar":["portuguese"],"peculiar":["portuguese"],"pedalar":["portuguese"],"pedestre":["portuguese"],"pediatra":["portuguese"],"pedra":["portuguese"],"pegada":["portuguese"],"peitoral":["portuguese"],"peixe":["portuguese"],"pele":["portuguese"],"pelicano":["portuguese"],"penca":["portuguese"],"pendurar":["portuguese"],"peneira":["portuguese"],"penhasco":["portuguese"],"pensador":["portuguese"],"pente":["portuguese"],"perceber":["portuguese"],"perfeito":["portuguese"],"pergunta":["portuguese"],"perito":["portuguese"],"permitir":["portuguese"],"perna":["portuguese"],"perplexo":["portuguese"],"persiana":["portuguese"],"pertence":["portuguese"],"peruca":["portuguese"],"pescado":["portuguese"],"pesquisa":["portuguese"],"pessoa":["portuguese"],"petiscar":["portuguese"],"piada":["portuguese"],"picado":["portuguese"],"piedade":["portuguese"],"pigmento":["portuguese"],"pilastra":["portuguese"],"pilhado":["portuguese"],"pilotar":["portuguese"],"pimenta":["portuguese"],"pincel":["portuguese"],"pinguim":["portuguese"],"pinha":["portuguese"],"pinote":["portuguese"],"pintar":["portuguese"],"pioneiro":["portuguese"],"pipoca":["portuguese"],"piquete":["portuguese"],"piranha":["portuguese"],"pires":["portuguese"],"pirueta":["portuguese"],"piscar":["portuguese"],"pistola":["portuguese"],"pitanga":["portuguese"],"pivete":["portuguese"],"planta":["portuguese"],"plaqueta":["portuguese"],"platina":["portuguese"],"plebeu":["portuguese"],"plumagem":["portuguese"],"pluvial":["portuguese"],"pneu":["portuguese"],"poda":["portuguese"],"poeira":["portuguese"],"poetisa":["portuguese"],"polegada":["portuguese"],"policiar":["portuguese"],"poluente":["portuguese"],"polvilho":["portuguese"],"pomar":["portuguese"],"pomba":["portuguese"],"ponderar":["portuguese"],"pontaria":["portuguese"],"populoso":["portuguese"],"porta":["portuguese"],"possuir":["portuguese"],"postal":["portuguese"],"pote":["portuguese"],"poupar":["portuguese"],"pouso":["portuguese"],"povoar":["portuguese"],"praia":["portuguese"],"prancha":["portuguese"],"prato":["portuguese"],"praxe":["portuguese"],"prece":["portuguese"],"predador":["portuguese"],"prefeito":["portuguese"],"premiar":["portuguese"],"prensar":["portuguese"],"preparar":["portuguese"],"presilha":["portuguese"],"pretexto":["portuguese"],"prevenir":["portuguese"],"prezar":["portuguese"],"primata":["portuguese"],"princesa":["portuguese"],"prisma":["portuguese"],"privado":["portuguese"],"processo":["portuguese"],"produto":["portuguese"],"profeta":["portuguese"],"proibido":["portuguese"],"projeto":["portuguese"],"prometer":["portuguese"],"propagar":["portuguese"],"prosa":["portuguese"],"protetor":["portuguese"],"provador":["portuguese"],"publicar":["portuguese"],"pudim":["portuguese"],"pular":["portuguese"],"pulmonar":["portuguese"],"pulseira":["portuguese"],"punhal":["portuguese"],"punir":["portuguese"],"pupilo":["portuguese"],"pureza":["portuguese"],"puxador":["portuguese"],"quadra":["portuguese"],"quantia":["portuguese"],"quarto":["portuguese"],"quase":["portuguese"],"quebrar":["portuguese"],"queda":["portuguese"],"queijo":["portuguese"],"quente":["portuguese"],"querido":["portuguese"],"quimono":["portuguese"],"quina":["portuguese"],"quiosque":["portuguese"],"rabanada":["portuguese"],"rabisco":["portuguese"],"rachar":["portuguese"],"racionar":["portuguese"],"radial":["portuguese"],"raiar":["portuguese"],"rainha":["portuguese"],"raio":["portuguese"],"raiva":["portuguese"],"rajada":["portuguese"],"ralado":["portuguese"],"ramal":["portuguese"],"ranger":["portuguese"],"ranhura":["portuguese"],"rapadura":["portuguese"],"rapel":["portuguese"],"rapidez":["portuguese"],"raposa":["portuguese"],"raquete":["portuguese"],"raridade":["portuguese"],"rasante":["portuguese"],"rascunho":["portuguese"],"rasgar":["portuguese"],"raspador":["portuguese"],"rasteira":["portuguese"],"rasurar":["portuguese"],"ratazana":["portuguese"],"ratoeira":["portuguese"],"realeza":["portuguese"],"reanimar":["portuguese"],"reaver":["portuguese"],"rebaixar":["portuguese"],"rebelde":["portuguese"],"rebolar":["portuguese"],"recado":["portuguese"],"recente":["portuguese"],"recheio":["portuguese"],"recibo":["portuguese"],"recordar":["portuguese"],"recrutar":["portuguese"],"recuar":["portuguese"],"rede":["portuguese"],"redimir":["portuguese"],"redonda":["portuguese"],"reduzida":["portuguese"],"reenvio":["portuguese"],"refinar":["portuguese"],"refletir":["portuguese"],"refogar":["portuguese"],"refresco":["portuguese"],"refugiar":["portuguese"],"regalia":["portuguese"],"regime":["portuguese"],"regra":["portuguese"],"reinado":["portuguese"],"reitor":["portuguese"],"rejeitar":["portuguese"],"relativo":["portuguese"],"remador":["portuguese"],"remendo":["portuguese"],"remorso":["portuguese"],"renovado":["portuguese"],"reparo":["portuguese"],"repelir":["portuguese"],"repleto":["portuguese"],"repolho":["portuguese"],"represa":["portuguese"],"repudiar":["portuguese"],"requerer":["portuguese"],"resenha":["portuguese"],"resfriar":["portuguese"],"resgatar":["portuguese"],"residir":["portuguese"],"resolver":["portuguese"],"respeito":["portuguese"],"ressaca":["portuguese"],"restante":["portuguese"],"resumir":["portuguese"],"retalho":["portuguese"],"reter":["portuguese"],"retirar":["portuguese"],"retomada":["portuguese"],"retratar":["portuguese"],"revelar":["portuguese"],"revisor":["portuguese"],"revolta":["portuguese"],"riacho":["portuguese"],"rica":["portuguese"],"rigidez":["portuguese"],"rigoroso":["portuguese"],"rimar":["portuguese"],"ringue":["portuguese"],"risada":["portuguese"],"risco":["portuguese"],"risonho":["portuguese"],"robalo":["portuguese"],"rochedo":["portuguese"],"rodada":["portuguese"],"rodeio":["portuguese"],"rodovia":["portuguese"],"roedor":["portuguese"],"roleta":["portuguese"],"romano":["portuguese"],"roncar":["portuguese"],"rosado":["portuguese"],"roseira":["portuguese"],"rosto":["portuguese"],"rota":["portuguese","turkish"],"roteiro":["portuguese"],"rotina":["portuguese"],"rotular":["portuguese"],"rouco":["portuguese"],"roupa":["portuguese"],"roxo":["portuguese"],"rubro":["portuguese"],"rugido":["portuguese"],"rugoso":["portuguese"],"ruivo":["portuguese"],"rumo":["portuguese"],"rupestre":["portuguese"],"russo":["portuguese"],"sabor":["portuguese"],"saciar":["portuguese"],"sacola":["portuguese"],"sacudir":["portuguese"],"sadio":["portuguese"],"safira":["portuguese"],"saga":["portuguese"],"sagrada":["portuguese"],"saibro":["portuguese"],"salada":["portuguese"],"saleiro":["portuguese"],"salgado":["portuguese"],"saliva":["portuguese"],"salpicar":["portuguese"],"salsicha":["portuguese"],"saltar":["portuguese"],"salvador":["portuguese"],"sambar":["portuguese"],"samurai":["portuguese"],"sanar":["portuguese"],"sanfona":["portuguese"],"sangue":["portuguese"],"sanidade":["portuguese"],"sapato":["portuguese"],"sarda":["portuguese"],"sargento":["portuguese"],"sarjeta":["portuguese"],"saturar":["portuguese"],"saudade":["portuguese"],"saxofone":["portuguese"],"sazonal":["portuguese"],"secar":["portuguese"],"secular":["portuguese"],"seda":["portuguese","turkish"],"sedento":["portuguese"],"sediado":["portuguese"],"sedoso":["portuguese"],"sedutor":["portuguese"],"segmento":["portuguese"],"segredo":["portuguese"],"segundo":["portuguese"],"seiva":["portuguese"],"seleto":["portuguese"],"selvagem":["portuguese"],"semanal":["portuguese"],"semente":["portuguese"],"senador":["portuguese"],"senhor":["portuguese"],"sensual":["portuguese"],"sentado":["portuguese"],"separado":["portuguese"],"sereia":["portuguese"],"seringa":["portuguese"],"serra":["portuguese"],"servo":["portuguese"],"setembro":["portuguese"],"setor":["portuguese"],"sigilo":["portuguese"],"silhueta":["portuguese"],"silicone":["portuguese"],"simetria":["portuguese"],"simpatia":["portuguese"],"simular":["portuguese"],"sinal":["portuguese"],"sincero":["portuguese"],"singular":["portuguese"],"sinopse":["portuguese"],"sintonia":["portuguese"],"sirene":["portuguese"],"siri":["portuguese"],"situado":["portuguese"],"soberano":["portuguese"],"sobra":["portuguese"],"socorro":["portuguese"],"sogro":["portuguese"],"soja":["portuguese"],"solda":["portuguese"],"soletrar":["portuguese"],"solteiro":["portuguese"],"sombrio":["portuguese"],"sonata":["portuguese"],"sondar":["portuguese"],"sonegar":["portuguese"],"sonhador":["portuguese"],"sono":["portuguese"],"soprano":["portuguese"],"soquete":["portuguese"],"sorrir":["portuguese"],"sorteio":["portuguese"],"sossego":["portuguese"],"sotaque":["portuguese"],"soterrar":["portuguese"],"sovado":["portuguese"],"sozinho":["portuguese"],"suavizar":["portuguese"],"subida":["portuguese"],"submerso":["portuguese"],"subsolo":["portuguese"],"subtrair":["portuguese"],"sucata":["portuguese"],"sucesso":["portuguese"],"suco":["portuguese"],"sudeste":["portuguese"],"sufixo":["portuguese"],"sugador":["portuguese"],"sugerir":["portuguese"],"sujeito":["portuguese"],"sulfato":["portuguese"],"sumir":["portuguese"],"suor":["portuguese"],"superior":["portuguese"],"suplicar":["portuguese"],"suposto":["portuguese"],"suprimir":["portuguese"],"surdina":["portuguese"],"surfista":["portuguese"],"surpresa":["portuguese"],"surreal":["portuguese"],"surtir":["portuguese"],"suspiro":["portuguese"],"sustento":["portuguese"],"tabela":["portuguese","turkish"],"tablete":["portuguese"],"tabuada":["portuguese"],"tacho":["portuguese"],"tagarela":["portuguese"],"talher":["portuguese"],"talo":["portuguese"],"talvez":["portuguese"],"tamanho":["portuguese"],"tamborim":["portuguese"],"tampa":["portuguese"],"tangente":["portuguese"],"tanto":["portuguese"],"tapar":["portuguese"],"tapioca":["portuguese"],"tardio":["portuguese"],"tarefa":["portuguese"],"tarja":["portuguese"],"tarraxa":["portuguese"],"tatuagem":["portuguese"],"taurino":["portuguese"],"taxativo":["portuguese"],"taxista":["portuguese"],"teatral":["portuguese"],"tecer":["portuguese"],"tecido":["portuguese"],"teclado":["portuguese"],"tedioso":["portuguese"],"teia":["portuguese"],"teimar":["portuguese"],"telefone":["portuguese"],"telhado":["portuguese"],"tempero":["portuguese"],"tenente":["portuguese"],"tensor":["portuguese"],"tentar":["portuguese"],"termal":["portuguese"],"terno":["portuguese"],"terreno":["portuguese"],"tese":["portuguese"],"tesoura":["portuguese"],"testado":["portuguese"],"teto":["portuguese"],"textura":["portuguese"],"texugo":["portuguese"],"tiara":["portuguese"],"tigela":["portuguese"],"tijolo":["portuguese"],"timbrar":["portuguese"],"timidez":["portuguese"],"tingido":["portuguese"],"tinteiro":["portuguese"],"tiragem":["portuguese"],"titular":["portuguese"],"toalha":["portuguese"],"tocha":["portuguese"],"tolerar":["portuguese"],"tolice":["portuguese"],"tomada":["portuguese"],"tomilho":["portuguese"],"tonel":["portuguese"],"tontura":["portuguese"],"topete":["portuguese"],"tora":["portuguese"],"torcido":["portuguese"],"torneio":["portuguese"],"torque":["portuguese"],"torrada":["portuguese"],"torto":["portuguese"],"tostar":["portuguese"],"touca":["portuguese"],"toupeira":["portuguese"],"toxina":["portuguese"],"trabalho":["portuguese"],"tracejar":["portuguese"],"tradutor":["portuguese"],"trafegar":["portuguese"],"trajeto":["portuguese"],"trama":["portuguese"],"trancar":["portuguese"],"trapo":["portuguese"],"traseiro":["portuguese"],"tratador":["portuguese"],"travar":["portuguese"],"treino":["portuguese"],"tremer":["portuguese"],"trepidar":["portuguese"],"trevo":["portuguese"],"triagem":["portuguese"],"tribo":["portuguese"],"triciclo":["portuguese"],"tridente":["portuguese"],"trilogia":["portuguese"],"trindade":["portuguese"],"triplo":["portuguese"],"triturar":["portuguese"],"triunfal":["portuguese"],"trocar":["portuguese"],"trombeta":["portuguese"],"trova":["portuguese"],"trunfo":["portuguese"],"truque":["portuguese"],"tubular":["portuguese"],"tucano":["portuguese"],"tudo":["portuguese"],"tulipa":["portuguese"],"tupi":["portuguese"],"turbo":["portuguese"],"turma":["portuguese"],"turquesa":["portuguese"],"tutelar":["portuguese"],"tutorial":["portuguese"],"uivar":["portuguese"],"umbigo":["portuguese"],"unha":["portuguese"],"unidade":["portuguese"],"uniforme":["portuguese"],"urologia":["portuguese"],"urso":["portuguese"],"urtiga":["portuguese"],"urubu":["portuguese"],"usado":["portuguese"],"usina":["portuguese"],"usufruir":["portuguese"],"vacina":["portuguese"],"vadiar":["portuguese"],"vagaroso":["portuguese"],"vaidoso":["portuguese"],"vala":["portuguese"],"valente":["portuguese"],"validade":["portuguese"],"valores":["portuguese"],"vantagem":["portuguese"],"vaqueiro":["portuguese"],"varanda":["portuguese"],"vareta":["portuguese"],"varrer":["portuguese"],"vascular":["portuguese"],"vasilha":["portuguese"],"vassoura":["portuguese"],"vazar":["portuguese"],"vazio":["portuguese"],"veado":["portuguese"],"vedar":["portuguese"],"vegetar":["portuguese"],"veicular":["portuguese"],"veleiro":["portuguese"],"velhice":["portuguese"],"veludo":["portuguese"],"vencedor":["portuguese"],"vendaval":["portuguese"],"venerar":["portuguese"],"ventre":["portuguese"],"verbal":["portuguese"],"verdade":["portuguese"],"vereador":["portuguese"],"vergonha":["portuguese"],"vermelho":["portuguese"],"verniz":["portuguese"],"versar":["portuguese"],"vertente":["portuguese"],"vespa":["portuguese"],"vestido":["portuguese"],"vetorial":["portuguese"],"viaduto":["portuguese"],"viagem":["portuguese"],"viajar":["portuguese"],"viatura":["portuguese"],"vibrador":["portuguese"],"videira":["portuguese"],"vidraria":["portuguese"],"viela":["portuguese"],"viga":["portuguese"],"vigente":["portuguese"],"vigiar":["portuguese"],"vigorar":["portuguese"],"vilarejo":["portuguese"],"vinco":["portuguese"],"vinheta":["portuguese"],"vinil":["portuguese"],"violeta":["portuguese"],"virada":["portuguese"],"virtude":["portuguese"],"visitar":["portuguese"],"visto":["portuguese"],"vitral":["portuguese"],"viveiro":["portuguese"],"vizinho":["portuguese"],"voador":["portuguese"],"voar":["portuguese"],"vogal":["portuguese"],"volante":["portuguese"],"voleibol":["portuguese"],"voltagem":["portuguese"],"volumoso":["portuguese"],"vontade":["portuguese"],"vulto":["portuguese"],"vuvuzela":["portuguese"],"xadrez":["portuguese"],"xarope":["portuguese"],"xeque":["portuguese"],"xeretar":["portuguese"],"xerife":["portuguese"],"xingar":["portuguese"],"zangado":["portuguese"],"zarpar":["portuguese"],"zebu":["portuguese"],"zelador":["portuguese"],"zombar":["portuguese"],"zoologia":["portuguese"],"zumbido":["portuguese"],"абзац":["russian"],"абонент":["russian"],"абсурд":["russian"],"авангард":["russian"],"авария":["russian"],"август":["russian"],"авиация":["russian"],"автор":["russian"],"агент":["russian"],"агитация":["russian"],"агрегат":["russian"],"адвокат":["russian"],"адмирал":["russian"],"адрес":["russian"],"азарт":["russian"],"азот":["russian"],"академия":["russian"],"аквариум":["russian"],"аксиома":["russian"],"акула":["russian"],"акцент":["russian"],"акция":["russian"],"аллея":["russian"],"алмаз":["russian"],"алтарь":["russian"],"альбом":["russian"],"альянс":["russian"],"амбиция":["russian"],"анализ":["russian"],"анекдот":["russian"],"анкета":["russian"],"ансамбль":["russian"],"антенна":["russian"],"апельсин":["russian"],"аппарат":["russian"],"аппетит":["russian"],"апрель":["russian"],"аптека":["russian"],"арбуз":["russian"],"аргумент":["russian"],"аренда":["russian"],"арест":["russian"],"армия":["russian"],"аромат":["russian"],"арсенал":["russian"],"артерия":["russian"],"артист":["russian"],"архив":["russian"],"аспирант":["russian"],"асфальт":["russian"],"атака":["russian"],"атомный":["russian"],"атрибут":["russian"],"аукцион":["russian"],"афиша":["russian"],"аэропорт":["russian"],"бабочка":["russian"],"бабушка":["russian"],"багаж":["russian"],"база":["russian"],"бактерия":["russian"],"баланс":["russian"],"балерина":["russian"],"балкон":["russian"],"бандит":["russian"],"банк":["russian"],"барабан":["russian"],"барон":["russian"],"барышня":["russian"],"барьер":["russian"],"бассейн":["russian"],"батарея":["russian"],"башмак":["russian"],"башня":["russian"],"бедный":["russian"],"беженец":["russian"],"бездна":["russian"],"белка":["russian"],"белый":["russian"],"бензин":["russian"],"берег":["russian"],"беседа":["russian"],"бешеный":["russian"],"билет":["russian"],"бинокль":["russian"],"биржа":["russian"],"битва":["russian"],"благо":["russian"],"блеск":["russian"],"близкий":["russian"],"блин":["russian"],"блок":["russian"],"блюдо":["russian"],"богатый":["russian"],"бодрый":["russian"],"боец":["russian"],"бокал":["russian"],"боковой":["russian"],"бокс":["russian"],"более":["russian"],"болото":["russian"],"болтать":["russian"],"большой":["russian"],"бомба":["russian"],"борт":["russian"],"борьба":["russian"],"босой":["russian"],"ботинок":["russian"],"бояться":["russian"],"брак":["russian"],"брать":["russian"],"бревно":["russian"],"бред":["russian"],"бригада":["russian"],"бродяга":["russian"],"броня":["russian"],"бросить":["russian"],"брызги":["russian"],"брюки":["russian"],"брюхо":["russian"],"бугор":["russian"],"будка":["russian"],"будни":["russian"],"будущее":["russian"],"буква":["russian"],"букет":["russian"],"бульвар":["russian"],"бумага":["russian"],"бунт":["russian"],"бурный":["russian"],"буря":["russian"],"бутылка":["russian"],"бухта":["russian"],"бывший":["russian"],"быстро":["russian"],"бытовой":["russian"],"быть":["russian"],"бюджет":["russian"],"бюро":["russian"],"бюст":["russian"],"вагон":["russian"],"важный":["russian"],"вакцина":["russian"],"валенок":["russian"],"вальс":["russian"],"валюта":["russian"],"ванная":["russian"],"варенье":["russian"],"вариант":["russian"],"вблизи":["russian"],"вверх":["russian"],"вводить":["russian"],"вдали":["russian"],"вдвое":["russian"],"вдова":["russian"],"вдоль":["russian"],"вдруг":["russian"],"ведро":["russian"],"ведущий":["russian"],"ведьма":["russian"],"вежливо":["russian"],"везде":["russian"],"веко":["russian"],"вексель":["russian"],"велеть":["russian"],"великий":["russian"],"венец":["russian"],"веник":["russian"],"веранда":["russian"],"верблюд":["russian"],"верить":["russian"],"верный":["russian"],"версия":["russian"],"вертеть":["russian"],"верхний":["russian"],"вершина":["russian"],"весело":["russian"],"весна":["russian"],"весомый":["russian"],"вести":["russian"],"весь":["russian"],"ветеран":["russian"],"ветхий":["russian"],"вечер":["russian"],"вечно":["russian"],"вешалка":["russian"],"вещество":["russian"],"взамен":["russian"],"взгляд":["russian"],"вздох":["russian"],"взнос":["russian"],"взойти":["russian"],"взор":["russian"],"взрыв":["russian"],"взять":["russian"],"видеть":["russian"],"видимо":["russian"],"визг":["russian"],"визит":["russian"],"вилка":["russian"],"вина":["russian"],"вирус":["russian"],"висок":["russian"],"витамин":["russian"],"витрина":["russian"],"вихрь":["russian"],"вишня":["russian"],"вкус":["russian"],"влага":["russian"],"владелец":["russian"],"власть":["russian"],"влево":["russian"],"влияние":["russian"],"вложить":["russian"],"вместе":["russian"],"внешний":["russian"],"вникать":["russian"],"внимание":["russian"],"вновь":["russian"],"внук":["russian"],"внутри":["russian"],"внучка":["russian"],"внушать":["russian"],"вовлечь":["russian"],"вовремя":["russian"],"вовсю":["russian"],"вода":["russian"],"водород":["russian"],"водяной":["russian"],"воевать":["russian"],"возврат":["russian"],"возглас":["russian"],"воздух":["russian"],"возить":["russian"],"возле":["russian"],"возня":["russian"],"возраст":["russian"],"война":["russian"],"войско":["russian"],"вокзал":["russian"],"волос":["russian"],"волчий":["russian"],"вольный":["russian"],"воля":["russian"],"вообще":["russian"],"вопль":["russian"],"вопрос":["russian"],"ворота":["russian"],"восемь":["russian"],"восток":["russian"],"вплоть":["russian"],"вполне":["russian"],"вправе":["russian"],"впредь":["russian"],"впрочем":["russian"],"врач":["russian"],"вредный":["russian"],"время":["russian"],"вручить":["russian"],"всадник":["russian"],"всегда":["russian"],"вскоре":["russian"],"вскрыть":["russian"],"всплеск":["russian"],"вспышка":["russian"],"встреча":["russian"],"всюду":["russian"],"всякий":["russian"],"второй":["russian"],"вход":["russian"],"вчера":["russian"],"выбор":["russian"],"вывод":["russian"],"выгнать":["russian"],"выдать":["russian"],"выехать":["russian"],"вызов":["russian"],"выйти":["russian"],"выкуп":["russian"],"вылезти":["russian"],"вымыть":["russian"],"выпасть":["russian"],"выпить":["russian"],"выплата":["russian"],"выпуск":["russian"],"вырасти":["russian"],"выручка":["russian"],"выслать":["russian"],"высокий":["russian"],"выставка":["russian"],"вышка":["russian"],"вязать":["russian"],"вялый":["russian"],"газета":["russian"],"газовый":["russian"],"галерея":["russian"],"галстук":["russian"],"гамма":["russian"],"гарантия":["russian"],"гармония":["russian"],"гарнизон":["russian"],"гастроли":["russian"],"гвардия":["russian"],"гвоздь":["russian"],"гектар":["russian"],"генерал":["russian"],"гений":["russian"],"геном":["russian"],"геолог":["russian"],"герб":["russian"],"герой":["russian"],"гибкий":["russian"],"гигант":["russian"],"гимн":["russian"],"гипотеза":["russian"],"гитара":["russian"],"главный":["russian"],"глагол":["russian"],"гладить":["russian"],"глаз":["russian"],"глина":["russian"],"глоток":["russian"],"глубокий":["russian"],"глупый":["russian"],"глухой":["russian"],"глыба":["russian"],"глядеть":["russian"],"гнев":["russian"],"гнездо":["russian"],"гнилой":["russian"],"годовой":["russian"],"голова":["russian"],"голубой":["russian"],"голый":["russian"],"гонорар":["russian"],"гордость":["russian"],"горизонт":["russian"],"горло":["russian"],"горный":["russian"],"город":["russian"],"горшок":["russian"],"горький":["russian"],"горючее":["russian"],"горячий":["russian"],"готовый":["russian"],"градус":["russian"],"грамм":["russian"],"граница":["russian"],"граф":["russian"],"гребень":["russian"],"гриб":["russian"],"гримаса":["russian"],"грозить":["russian"],"грохот":["russian"],"грош":["russian"],"грубый":["russian"],"грудь":["russian"],"груз":["russian"],"грунт":["russian"],"группа":["russian"],"груша":["russian"],"грязный":["russian"],"губа":["russian"],"гудок":["russian"],"гулкий":["russian"],"гулять":["russian"],"гусеница":["russian"],"густо":["russian"],"гусь":["russian"],"давление":["russian"],"давно":["russian"],"даже":["russian"],"дальний":["russian"],"данный":["russian"],"дарить":["russian"],"датчик":["russian"],"дать":["russian"],"дача":["russian"],"двадцать":["russian"],"дважды":["russian"],"дверь":["russian"],"двигать":["russian"],"движение":["russian"],"двойной":["russian"],"двор":["russian"],"дебют":["russian"],"девятый":["russian"],"дежурный":["russian"],"действие":["russian"],"декабрь":["russian"],"деление":["russian"],"дело":["russian"],"дельфин":["russian"],"день":["russian"],"дерево":["russian"],"держать":["russian"],"дерзкий":["russian"],"десять":["russian"],"деталь":["russian"],"детский":["russian"],"дефект":["russian"],"дефицит":["russian"],"деятель":["russian"],"джаз":["russian"],"джинсы":["russian"],"джунгли":["russian"],"диагноз":["russian"],"диалог":["russian"],"диапазон":["russian"],"диван":["russian"],"дивизия":["russian"],"дивный":["russian"],"диета":["russian"],"дизайн":["russian"],"дикарь":["russian"],"дилер":["russian"],"динамика":["russian"],"диплом":["russian"],"директор":["russian"],"дитя":["russian"],"длинный":["russian"],"дневник":["russian"],"добрый":["russian"],"добыча":["russian"],"доверие":["russian"],"догадка":["russian"],"догнать":["russian"],"дождь":["russian"],"доклад":["russian"],"доктор":["russian"],"документ":["russian"],"долго":["russian"],"должен":["russian"],"долина":["russian"],"донос":["russian"],"дорога":["russian"],"досада":["russian"],"доска":["russian"],"достать":["russian"],"досуг":["russian"],"доход":["russian"],"доцент":["russian"],"дощатый":["russian"],"драка":["russian"],"древний":["russian"],"дремать":["russian"],"дробный":["russian"],"дрова":["russian"],"дрожать":["russian"],"другой":["russian"],"дружба":["russian"],"дубовый":["russian"],"дуга":["russian"],"думать":["russian"],"дурной":["russian"],"духи":["russian"],"душный":["russian"],"дуэль":["russian"],"дуэт":["russian"],"дыра":["russian"],"дыхание":["russian"],"дюжина":["russian"],"дядя":["russian"],"едва":["russian"],"единый":["russian"],"ерунда":["russian"],"если":["russian"],"ехать":["russian"],"жадный":["russian"],"жажда":["russian"],"жалеть":["russian"],"жалоба":["russian"],"жанр":["russian"],"жареный":["russian"],"жаркий":["russian"],"жгучий":["russian"],"жевать":["russian"],"желание":["russian"],"желудок":["russian"],"жена":["russian"],"женщина":["russian"],"жертва":["russian"],"жест":["russian"],"жидкость":["russian"],"житель":["russian"],"жить":["russian"],"жрец":["russian"],"жулик":["russian"],"журнал":["russian"],"жуткий":["russian"],"забрать":["russian"],"забыть":["russian"],"завести":["russian"],"завод":["russian"],"завтра":["russian"],"загадка":["russian"],"загнать":["russian"],"заговор":["russian"],"задача":["russian"],"задеть":["russian"],"задний":["russian"],"задолго":["russian"],"заехать":["russian"],"заказ":["russian"],"закон":["russian"],"закрыть":["russian"],"закуска":["russian"],"залезть":["russian"],"залить":["russian"],"залп":["russian"],"замок":["russian"],"замуж":["russian"],"замысел":["russian"],"занавес":["russian"],"заново":["russian"],"занять":["russian"],"заодно":["russian"],"запись":["russian"],"запрос":["russian"],"запуск":["russian"],"запястье":["russian"],"заранее":["russian"],"заросль":["russian"],"зарплата":["russian"],"заря":["russian"],"засада":["russian"],"заслуга":["russian"],"заснуть":["russian"],"застать":["russian"],"затвор":["russian"],"затеять":["russian"],"затрата":["russian"],"затылок":["russian"],"захват":["russian"],"зачем":["russian"],"защита":["russian"],"заявить":["russian"],"заяц":["russian"],"звезда":["russian"],"звено":["russian"],"звонить":["russian"],"здесь":["russian"],"зелень":["russian"],"земля":["russian"],"зеркало":["russian"],"зерно":["russian"],"зима":["russian"],"злой":["russian"],"змея":["russian"],"знамя":["russian"],"знание":["russian"],"значит":["russian"],"золотой":["russian"],"зона":["russian"],"зонтик":["russian"],"зоопарк":["russian"],"зрачок":["russian"],"зрение":["russian"],"зритель":["russian"],"зубной":["russian"],"зубр":["russian"],"игла":["russian"],"идеал":["russian"],"идеолог":["russian"],"идея":["russian"],"идол":["russian"],"идти":["russian"],"изба":["russian"],"избить":["russian"],"избрать":["russian"],"избыток":["russian"],"извлечь":["russian"],"извне":["russian"],"изгиб":["russian"],"изгнать":["russian"],"издание":["russian"],"изделие":["russian"],"изнутри":["russian"],"изобилие":["russian"],"изоляция":["russian"],"изредка":["russian"],"изрядно":["russian"],"изучение":["russian"],"изъять":["russian"],"изящный":["russian"],"икона":["russian"],"икра":["russian"],"иллюзия":["russian"],"именно":["russian"],"иметь":["russian"],"имидж":["russian"],"империя":["russian"],"импульс":["russian"],"иначе":["russian"],"инвалид":["russian"],"индекс":["russian"],"индивид":["russian"],"инерция":["russian"],"инженер":["russian"],"иногда":["russian"],"иной":["russian"],"институт":["russian"],"интерес":["russian"],"интрига":["russian"],"интуиция":["russian"],"инфаркт":["russian"],"инцидент":["russian"],"ирония":["russian"],"искать":["russian"],"испуг":["russian"],"история":["russian"],"итог":["russian"],"июнь":["russian"],"кабель":["russian"],"кабинет":["russian"],"каблук":["russian"],"кавалер":["russian"],"кадр":["russian"],"каждый":["russian"],"кажется":["russian"],"казино":["russian"],"калитка":["russian"],"камень":["russian"],"камин":["russian"],"канал":["russian"],"кандидат":["russian"],"каникулы":["russian"],"канон":["russian"],"капитан":["russian"],"капля":["russian"],"капот":["russian"],"капуста":["russian"],"карандаш":["russian"],"карета":["russian"],"каркас":["russian"],"карман":["russian"],"картина":["russian"],"карьера":["russian"],"каска":["russian"],"кассета":["russian"],"кастрюля":["russian"],"каталог":["russian"],"катер":["russian"],"каток":["russian"],"катушка":["russian"],"кафедра":["russian"],"качество":["russian"],"каша":["russian"],"кашлять":["russian"],"каюта":["russian"],"квадрат":["russian"],"квартира":["russian"],"квота":["russian"],"кепка":["russian"],"кивнуть":["russian"],"километр":["russian"],"кино":["russian"],"киоск":["russian"],"кипяток":["russian"],"кирпич":["russian"],"кислота":["russian"],"кисть":["russian"],"клавиша":["russian"],"клапан":["russian"],"класс":["russian"],"клей":["russian"],"клетка":["russian"],"клиент":["russian"],"климат":["russian"],"клиника":["russian"],"кличка":["russian"],"клоун":["russian"],"клочок":["russian"],"клуб":["russian"],"клумба":["russian"],"ключ":["russian"],"книга":["russian"],"кнопка":["russian"],"кнут":["russian"],"княгиня":["russian"],"князь":["russian"],"кобура":["russian"],"когда":["russian"],"кодекс":["russian"],"кожа":["russian"],"коктейль":["russian"],"колено":["russian"],"коллега":["russian"],"колонна":["russian"],"колпак":["russian"],"кольцо":["russian"],"колючий":["russian"],"коляска":["russian"],"команда":["russian"],"комедия":["russian"],"комиссия":["russian"],"коммуна":["russian"],"комната":["russian"],"комок":["russian"],"компания":["russian"],"комфорт":["russian"],"конвейер":["russian"],"конгресс":["russian"],"конечно":["russian"],"конкурс":["russian"],"контроль":["russian"],"концерт":["russian"],"конь":["russian"],"конюшня":["russian"],"копать":["russian"],"копейка":["russian"],"копыто":["russian"],"корабль":["russian"],"корень":["russian"],"корзина":["russian"],"коридор":["russian"],"кормить":["russian"],"корпус":["russian"],"космос":["russian"],"костюм":["russian"],"косяк":["russian"],"котел":["russian"],"котлета":["russian"],"который":["russian"],"коттедж":["russian"],"кофе":["russian"],"кофта":["russian"],"кошка":["russian"],"кража":["russian"],"край":["russian"],"красный":["russian"],"краткий":["russian"],"кредит":["russian"],"крем":["russian"],"крепкий":["russian"],"кресло":["russian"],"кривой":["russian"],"кризис":["russian"],"кристалл":["russian"],"критерий":["russian"],"кричать":["russian"],"кровь":["russian"],"крокодил":["russian"],"кролик":["russian"],"кроме":["russian"],"крона":["russian"],"круг":["russian"],"кружка":["russian"],"крупный":["russian"],"крутой":["russian"],"крушение":["russian"],"крыло":["russian"],"крыша":["russian"],"крючок":["russian"],"кстати":["russian"],"кубик":["russian"],"куда":["russian"],"кузов":["russian"],"кукла":["russian"],"кулак":["russian"],"кулиса":["russian"],"культура":["russian"],"кумир":["russian"],"купе":["russian"],"купить":["russian"],"купол":["russian"],"купюра":["russian"],"курица":["russian"],"курорт":["russian"],"курс":["russian"],"куртка":["russian"],"кусок":["russian"],"куст":["russian"],"кухня":["russian"],"кушать":["russian"],"лабиринт":["russian"],"лавка":["russian"],"лагерь":["russian"],"ладно":["russian"],"ладонь":["russian"],"лапа":["russian"],"лауреат":["russian"],"лгать":["russian"],"лебедь":["russian"],"левый":["russian"],"легенда":["russian"],"легкий":["russian"],"ледяной":["russian"],"лежать":["russian"],"лезвие":["russian"],"лезть":["russian"],"лекция":["russian"],"ленивый":["russian"],"лента":["russian"],"лепесток":["russian"],"лесной":["russian"],"лестница":["russian"],"лететь":["russian"],"лето":["russian"],"лечить":["russian"],"лига":["russian"],"лидер":["russian"],"лиловый":["russian"],"лимон":["russian"],"линия":["russian"],"липкий":["russian"],"лист":["russian"],"литр":["russian"],"лихой":["russian"],"лицо":["russian"],"лишить":["russian"],"лишний":["russian"],"ловить":["russian"],"логика":["russian"],"лодка":["russian"],"ложь":["russian"],"лозунг":["russian"],"локоть":["russian"],"лопата":["russian"],"лошадь":["russian"],"лукавый":["russian"],"луна":["russian"],"лучший":["russian"],"лысый":["russian"],"льгота":["russian"],"любить":["russian"],"любой":["russian"],"людской":["russian"],"люстра":["russian"],"лютый":["russian"],"лягушка":["russian"],"магазин":["russian"],"магия":["russian"],"майор":["russian"],"майский":["russian"],"максимум":["russian"],"макушка":["russian"],"мало":["russian"],"мальчик":["russian"],"мама":["russian"],"манера":["russian"],"марка":["russian"],"март":["russian"],"маршрут":["russian"],"масса":["russian"],"мастер":["russian"],"масштаб":["russian"],"материал":["russian"],"матч":["russian"],"махать":["russian"],"машина":["russian"],"маяк":["russian"],"мебель":["russian"],"медаль":["russian"],"медведь":["russian"],"медицина":["russian"],"медь":["russian"],"между":["russian"],"мелкий":["russian"],"мелочь":["russian"],"мемуары":["russian"],"меньше":["russian"],"меню":["russian"],"менять":["russian"],"мера":["russian"],"мерцать":["russian"],"место":["russian"],"месяц":["russian"],"металл":["russian"],"метод":["russian"],"метр":["russian"],"механизм":["russian"],"меховой":["russian"],"мечтать":["russian"],"мешать":["russian"],"мешок":["russian"],"миграция":["russian"],"микрофон":["russian"],"милиция":["russian"],"миллион":["russian"],"милость":["russian"],"миля":["russian"],"мимо":["russian"],"минерал":["russian"],"министр":["russian"],"минута":["russian"],"мирный":["russian"],"миска":["russian"],"миссия":["russian"],"митинг":["russian"],"мишень":["russian"],"младший":["russian"],"мнение":["russian"],"мнимый":["russian"],"много":["russian"],"могучий":["russian"],"модель":["russian"],"может":["russian"],"мозг":["russian"],"мокрый":["russian"],"молекула":["russian"],"молния":["russian"],"молодой":["russian"],"молчать":["russian"],"момент":["russian"],"монета":["russian"],"монитор":["russian"],"монолог":["russian"],"монстр":["russian"],"монтаж":["russian"],"мораль":["russian"],"море":["russian"],"морковь":["russian"],"мороз":["russian"],"морщина":["russian"],"мостовая":["russian"],"мотать":["russian"],"мотив":["russian"],"мотор":["russian"],"мохнатый":["russian"],"мрамор":["russian"],"мрачный":["russian"],"мстить":["russian"],"мудрый":["russian"],"мужество":["russian"],"мужчина":["russian"],"музей":["russian"],"музыка":["russian"],"мундир":["russian"],"муравей":["russian"],"мусор":["russian"],"муха":["russian"],"мчаться":["russian"],"мысль":["russian"],"мыться":["russian"],"мышца":["russian"],"мышь":["russian"],"мюзикл":["russian"],"мягкий":["russian"],"мясо":["russian"],"набор":["russian"],"навык":["russian"],"наглый":["russian"],"нагрузка":["russian"],"надежда":["russian"],"надзор":["russian"],"надо":["russian"],"наедине":["russian"],"назад":["russian"],"название":["russian"],"назло":["russian"],"наивный":["russian"],"найти":["russian"],"наконец":["russian"],"налево":["russian"],"наличие":["russian"],"налог":["russian"],"намерен":["russian"],"нанести":["russian"],"напасть":["russian"],"например":["russian"],"народ":["russian"],"наследие":["russian"],"натура":["russian"],"наука":["russian"],"наутро":["russian"],"начать":["russian"],"небо":["russian"],"неважно":["russian"],"невеста":["russian"],"негодяй":["russian"],"недавно":["russian"],"неделя":["russian"],"недолго":["russian"],"недра":["russian"],"недуг":["russian"],"нежный":["russian"],"незачем":["russian"],"некто":["russian"],"нелепый":["russian"],"неловко":["russian"],"нельзя":["russian"],"немало":["russian"],"немой":["russian"],"неплохо":["russian"],"нервный":["russian"],"нередко":["russian"],"нестись":["russian"],"неудача":["russian"],"неужели":["russian"],"нефть":["russian"],"неясный":["russian"],"нигде":["russian"],"низкий":["russian"],"никакой":["russian"],"никогда":["russian"],"никуда":["russian"],"ничто":["russian"],"ничуть":["russian"],"ниша":["russian"],"нищий":["russian"],"новость":["russian"],"новый":["russian"],"нога":["russian"],"ноготь":["russian"],"ножницы":["russian"],"ноздря":["russian"],"номер":["russian"],"носить":["russian"],"носок":["russian"],"ночь":["russian"],"ноябрь":["russian"],"нрав":["russian"],"нуль":["russian"],"нынче":["russian"],"нырять":["russian"],"нюанс":["russian"],"няня":["russian"],"обаяние":["russian"],"обед":["russian"],"обезьяна":["russian"],"обещать":["russian"],"обжечь":["russian"],"обзор":["russian"],"обилие":["russian"],"обитать":["russian"],"область":["russian"],"облик":["russian"],"обложка":["russian"],"обмен":["russian"],"обморок":["russian"],"обожать":["russian"],"обои":["russian"],"оболочка":["russian"],"оборона":["russian"],"обочина":["russian"],"образ":["russian"],"обрести":["russian"],"обрыв":["russian"],"обувь":["russian"],"обучение":["russian"],"обход":["russian"],"общество":["russian"],"общий":["russian"],"объект":["russian"],"обыск":["russian"],"обычно":["russian"],"обязать":["russian"],"овощи":["russian"],"овраг":["russian"],"овца":["russian"],"оговорка":["russian"],"ограда":["russian"],"огурец":["russian"],"одежда":["russian"],"одеяло":["russian"],"один":["russian"],"однако":["russian"],"одолеть":["russian"],"ожидать":["russian"],"озеро":["russian"],"океан":["russian"],"окно":["russian"],"около":["russian"],"окоп":["russian"],"окраина":["russian"],"октябрь":["russian"],"опасный":["russian"],"опека":["russian"],"операция":["russian"],"описание":["russian"],"оплата":["russian"],"опора":["russian"],"оппонент":["russian"],"оптимизм":["russian"],"оптовый":["russian"],"опухоль":["russian"],"опыт":["russian"],"оратор":["russian"],"орбита":["russian"],"орган":["russian"],"орден":["russian"],"орел":["russian"],"оригинал":["russian"],"ориентир":["russian"],"оркестр":["russian"],"оружие":["russian"],"осенний":["russian"],"осколок":["russian"],"осмотр":["russian"],"остров":["russian"],"отбор":["russian"],"отбыть":["russian"],"отвлечь":["russian"],"отдать":["russian"],"отдел":["russian"],"отдых":["russian"],"отель":["russian"],"отец":["russian"],"отзыв":["russian"],"отказ":["russian"],"отклик":["russian"],"открыть":["russian"],"откуда":["russian"],"отличие":["russian"],"отныне":["russian"],"отойти":["russian"],"отпуск":["russian"],"отрасль":["russian"],"отросток":["russian"],"отрывок":["russian"],"отряд":["russian"],"отсек":["russian"],"отставка":["russian"],"отсюда":["russian"],"оттенок":["russian"],"оттого":["russian"],"отчего":["russian"],"отъезд":["russian"],"офис":["russian"],"офицер":["russian"],"охота":["russian"],"охрана":["russian"],"оценка":["russian"],"очаг":["russian"],"очень":["russian"],"очередь":["russian"],"очищать":["russian"],"ошибка":["russian"],"ощущение":["russian"],"павильон":["russian"],"падать":["russian"],"пазуха":["russian"],"пакет":["russian"],"палата":["russian"],"палец":["russian"],"палуба":["russian"],"пальто":["russian"],"память":["russian"],"панель":["russian"],"паника":["russian"],"пара":["russian"],"парень":["russian"],"пароход":["russian"],"партия":["russian"],"парус":["russian"],"паспорт":["russian"],"пассажир":["russian"],"пастух":["russian"],"патент":["russian"],"патрон":["russian"],"пауза":["russian"],"паук":["russian"],"паутина":["russian"],"пафос":["russian"],"пахнуть":["russian"],"пациент":["russian"],"пачка":["russian"],"певец":["russian"],"педагог":["russian"],"пейзаж":["russian"],"пенсия":["russian"],"пепел":["russian"],"первый":["russian"],"перед":["russian"],"период":["russian"],"перо":["russian"],"перрон":["russian"],"персонаж":["russian"],"перчатка":["russian"],"песня":["russian"],"песок":["russian"],"петля":["russian"],"петрушка":["russian"],"петух":["russian"],"пехота":["russian"],"печать":["russian"],"печень":["russian"],"пешком":["russian"],"пещера":["russian"],"пианист":["russian"],"пиджак":["russian"],"пилот":["russian"],"пионер":["russian"],"пирамида":["russian"],"пирожок":["russian"],"письмо":["russian"],"пища":["russian"],"плавание":["russian"],"плакать":["russian"],"пламя":["russian"],"план":["russian"],"пласт":["russian"],"платить":["russian"],"пленный":["russian"],"плечо":["russian"],"плита":["russian"],"плод":["russian"],"плоский":["russian"],"плотный":["russian"],"плохой":["russian"],"площадь":["russian"],"плыть":["russian"],"плюс":["russian"],"пляж":["russian"],"плясать":["russian"],"победа":["russian"],"повар":["russian"],"повод":["russian"],"повсюду":["russian"],"повязка":["russian"],"погода":["russian"],"погреб":["russian"],"подбор":["russian"],"подвиг":["russian"],"подделка":["russian"],"поджать":["russian"],"поднос":["russian"],"подпись":["russian"],"подруга":["russian"],"подход":["russian"],"подчас":["russian"],"подъезд":["russian"],"поединок":["russian"],"поезд":["russian"],"поесть":["russian"],"поехать":["russian"],"пожалуй":["russian"],"пожилой":["russian"],"позади":["russian"],"позвать":["russian"],"поздний":["russian"],"позиция":["russian"],"позор":["russian"],"поиск":["russian"],"поймать":["russian"],"пойти":["russian"],"поклон":["russian"],"покой":["russian"],"покрыть":["russian"],"полдень":["russian"],"полезный":["russian"],"ползти":["russian"],"полк":["russian"],"полный":["russian"],"половина":["russian"],"полтора":["russian"],"польза":["russian"],"поляна":["russian"],"помидор":["russian"],"помнить":["russian"],"помощь":["russian"],"попасть":["russian"],"поперек":["russian"],"поплыть":["russian"],"пополам":["russian"],"поправка":["russian"],"попугай":["russian"],"попытка":["russian"],"порог":["russian"],"портрет":["russian"],"порция":["russian"],"порыв":["russian"],"порядок":["russian"],"после":["russian"],"посол":["russian"],"посреди":["russian"],"постель":["russian"],"посуда":["russian"],"потом":["russian"],"похвала":["russian"],"похожий":["russian"],"поцелуй":["russian"],"почва":["russian"],"почему":["russian"],"пошлина":["russian"],"поэма":["russian"],"поэтому":["russian"],"право":["russian"],"праздник":["russian"],"практика":["russian"],"прах":["russian"],"преграда":["russian"],"предмет":["russian"],"прежде":["russian"],"прелесть":["russian"],"премия":["russian"],"препарат":["russian"],"пресса":["russian"],"прибыть":["russian"],"прижать":["russian"],"прийти":["russian"],"приказ":["russian"],"прилавок":["russian"],"пример":["russian"],"принять":["russian"],"природа":["russian"],"притом":["russian"],"прихожая":["russian"],"прицел":["russian"],"причина":["russian"],"приют":["russian"],"прогноз":["russian"],"продукт":["russian"],"проект":["russian"],"прожить":["russian"],"прокат":["russian"],"промысел":["russian"],"пропуск":["russian"],"просто":["russian"],"против":["russian"],"профиль":["russian"],"процесс":["russian"],"прочий":["russian"],"прошлый":["russian"],"прощать":["russian"],"пружина":["russian"],"прут":["russian"],"прыжок":["russian"],"прямой":["russian"],"птица":["russian"],"публика":["russian"],"пугать":["russian"],"пуговица":["russian"],"пузырь":["russian"],"пульт":["russian"],"пуля":["russian"],"пункт":["russian"],"пускать":["russian"],"пустой":["russian"],"путь":["russian"],"пухлый":["russian"],"пучок":["russian"],"пушистый":["russian"],"пушка":["russian"],"пчела":["russian"],"пшеница":["russian"],"пылать":["russian"],"пыль":["russian"],"пышный":["russian"],"пьеса":["russian"],"пятка":["russian"],"пятно":["russian"],"пятый":["russian"],"пятьсот":["russian"],"работа":["russian"],"равнина":["russian"],"ради":["russian"],"радость":["russian"],"радуга":["russian"],"разбить":["russian"],"развитие":["russian"],"разговор":["russian"],"раздел":["russian"],"различие":["russian"],"размер":["russian"],"разный":["russian"],"разрыв":["russian"],"разум":["russian"],"район":["russian"],"ракета":["russian"],"раковина":["russian"],"рамка":["russian"],"рано":["russian"],"рапорт":["russian"],"распад":["russian"],"рассказ":["russian"],"расти":["russian"],"расход":["russian"],"расцвет":["russian"],"рация":["russian"],"рвануть":["russian"],"рваться":["russian"],"реакция":["russian"],"ребро":["russian"],"реветь":["russian"],"редактор":["russian"],"редкий":["russian"],"реестр":["russian"],"режим":["russian"],"резать":["russian"],"резерв":["russian"],"резина":["russian"],"резко":["russian"],"резной":["russian"],"рейс":["russian"],"реклама":["russian"],"рекорд":["russian"],"религия":["russian"],"рельс":["russian"],"ремень":["russian"],"ремонт":["russian"],"реплика":["russian"],"репортаж":["russian"],"ресница":["russian"],"ресторан":["russian"],"реформа":["russian"],"рецепт":["russian"],"речь":["russian"],"решение":["russian"],"ржавый":["russian"],"риск":["russian"],"рисунок":["russian"],"ритуал":["russian"],"рифма":["russian"],"робко":["russian"],"робот":["russian"],"ровесник":["russian"],"ровно":["russian"],"родной":["russian"],"рождение":["russian"],"роза":["russian"],"розовый":["russian"],"розыск":["russian"],"роль":["russian"],"роман":["russian"],"роскошь":["russian"],"роспись":["russian"],"рост":["russian"],"рубашка":["russian"],"рубеж":["russian"],"рубить":["russian"],"рубрика":["russian"],"рудник":["russian"],"рука":["russian"],"рукопись":["russian"],"румяный":["russian"],"русло":["russian"],"рухнуть":["russian"],"ручей":["russian"],"ручной":["russian"],"рыба":["russian"],"рыжий":["russian"],"рынок":["russian"],"рыхлый":["russian"],"рыцарь":["russian"],"рычаг":["russian"],"рюкзак":["russian"],"рядом":["russian"],"садовый":["russian"],"сажать":["russian"],"салон":["russian"],"салфетка":["russian"],"салют":["russian"],"самец":["russian"],"самовар":["russian"],"самый":["russian"],"сани":["russian"],"санкция":["russian"],"сапог":["russian"],"сарай":["russian"],"сатира":["russian"],"сахар":["russian"],"сбить":["russian"],"сбоку":["russian"],"сборная":["russian"],"сбыт":["russian"],"свадьба":["russian"],"свалка":["russian"],"сварить":["russian"],"свежий":["russian"],"сверху":["russian"],"свет":["russian"],"свеча":["russian"],"свинья":["russian"],"свист":["russian"],"свитер":["russian"],"свобода":["russian"],"сводка":["russian"],"свой":["russian"],"свыше":["russian"],"связь":["russian"],"сдаться":["russian"],"сделать":["russian"],"сегмент":["russian"],"сегодня":["russian"],"седло":["russian"],"седой":["russian"],"седьмой":["russian"],"сезон":["russian"],"сейф":["russian"],"сейчас":["russian"],"секрет":["russian"],"сектор":["russian"],"секунда":["russian"],"семинар":["russian"],"семья":["russian"],"сенатор":["russian"],"сено":["russian"],"сенсация":["russian"],"сентябрь":["russian"],"сервис":["russian"],"сердце":["russian"],"середина":["russian"],"сержант":["russian"],"серия":["russian"],"серый":["russian"],"сессия":["russian"],"сесть":["russian"],"сетевой":["russian"],"сжатый":["russian"],"сжечь":["russian"],"сзади":["russian"],"сигнал":["russian"],"сиденье":["russian"],"сила":["russian"],"силуэт":["russian"],"сильный":["russian"],"символ":["russian"],"симпатия":["russian"],"симфония":["russian"],"синий":["russian"],"синтез":["russian"],"синяк":["russian"],"сирень":["russian"],"система":["russian"],"ситуация":["russian"],"сияние":["russian"],"сказать":["russian"],"скала":["russian"],"скамейка":["russian"],"скандал":["russian"],"скатерть":["russian"],"скачок":["russian"],"скважина":["russian"],"сквер":["russian"],"сквозь":["russian"],"скелет":["russian"],"скидка":["russian"],"склад":["russian"],"сколько":["russian"],"скорый":["russian"],"скосить":["russian"],"скот":["russian"],"скрипка":["russian"],"скудный":["russian"],"скука":["russian"],"слабый":["russian"],"слава":["russian"],"сладкий":["russian"],"слегка":["russian"],"след":["russian"],"слеза":["russian"],"слепой":["russian"],"слесарь":["russian"],"слишком":["russian"],"слово":["russian"],"слог":["russian"],"сложный":["russian"],"сломать":["russian"],"служба":["russian"],"слух":["russian"],"случай":["russian"],"слышать":["russian"],"слюна":["russian"],"смежный":["russian"],"смелый":["russian"],"сменить":["russian"],"смесь":["russian"],"сметана":["russian"],"смех":["russian"],"смола":["russian"],"смуглый":["russian"],"смутный":["russian"],"смущать":["russian"],"смысл":["russian"],"снайпер":["russian"],"снаряд":["russian"],"сначала":["russian"],"снег":["russian"],"снизу":["russian"],"сниться":["russian"],"сно��а":["russian"],"снять":["russian"],"собака":["russian"],"соблазн":["russian"],"собрание":["russian"],"событие":["russian"],"совесть":["russian"],"совсем":["russian"],"согласие":["russian"],"создать":["russian"],"сознание":["russian"],"созреть":["russian"],"сойтись":["russian"],"сокол":["russian"],"солдат":["russian"],"соленый":["russian"],"солнце":["russian"],"солома":["russian"],"сомнение":["russian"],"сонный":["russian"],"соперник":["russian"],"соратник":["russian"],"сорвать":["russian"],"сосед":["russian"],"сосиска":["russian"],"состав":["russian"],"сотня":["russian"],"соус":["russian"],"союз":["russian"],"спад":["russian"],"спальня":["russian"],"спасти":["russian"],"спектр":["russian"],"сперва":["russian"],"спешить":["russian"],"спина":["russian"],"спирт":["russian"],"список":["russian"],"спичка":["russian"],"сплав":["russian"],"спонсор":["russian"],"спор":["russian"],"способ":["russian"],"справка":["russian"],"спустя":["russian"],"спутник":["russian"],"сразу":["russian"],"средство":["russian"],"срок":["russian"],"срыв":["russian"],"ссора":["russian"],"ссылка":["russian"],"ставить":["russian"],"стадия":["russian"],"стакан":["russian"],"станция":["russian"],"старый":["russian"],"стая":["russian"],"стебель":["russian"],"стекло":["russian"],"стена":["russian"],"степень":["russian"],"стереть":["russian"],"стиль":["russian"],"стимул":["russian"],"стирать":["russian"],"стихи":["russian"],"стоить":["russian"],"стойка":["russian"],"стол":["russian"],"стонать":["russian"],"стопа":["russian"],"сторона":["russian"],"стоянка":["russian"],"страна":["russian"],"стричь":["russian"],"строгий":["russian"],"струя":["russian"],"студент":["russian"],"стук":["russian"],"ступня":["russian"],"стыдно":["russian"],"суббота":["russian"],"субъект":["russian"],"сувенир":["russian"],"сугроб":["russian"],"сугубо":["russian"],"судить":["russian"],"судно":["russian"],"судьба":["russian"],"суета":["russian"],"суметь":["russian"],"сумма":["russian"],"сумрак":["russian"],"сундук":["russian"],"супруг":["russian"],"суровый":["russian"],"сутки":["russian"],"сухой":["russian"],"суша":["russian"],"существо":["russian"],"сфера":["russian"],"схема":["russian"],"схожий":["russian"],"сценарий":["russian"],"счастье":["russian"],"считать":["russian"],"съезд":["russian"],"сыграть":["russian"],"сырой":["russian"],"сытый":["russian"],"сыщик":["russian"],"сюда":["russian"],"сюжет":["russian"],"сюрприз":["russian"],"тайна":["russian"],"также":["russian"],"такой":["russian"],"такси":["russian"],"тактика":["russian"],"талия":["russian"],"таможня":["russian"],"танец":["russian"],"таракан":["russian"],"тарелка":["russian"],"тариф":["russian"],"тащить":["russian"],"таять":["russian"],"тварь":["russian"],"театр":["russian"],"тезис":["russian"],"текст":["russian"],"текущий":["russian"],"телефон":["russian"],"тема":["russian"],"темнота":["russian"],"теневой":["russian"],"теннис":["russian"],"теория":["russian"],"теперь":["russian"],"тепло":["russian"],"терапия":["russian"],"терзать":["russian"],"термин":["russian"],"терпеть":["russian"],"терраса":["russian"],"терять":["russian"],"тесный":["russian"],"тетрадь":["russian"],"техника":["russian"],"течение":["russian"],"тигр":["russian"],"типовой":["russian"],"тираж":["russian"],"титул":["russian"],"тихий":["russian"],"ткань":["russian"],"товарищ":["russian"],"тоже":["russian"],"толпа":["russian"],"толстый":["russian"],"толчок":["russian"],"толщина":["russian"],"только":["russian"],"тонкий":["russian"],"тонна":["russian"],"топить":["russian"],"топор":["russian"],"торговля":["russian"],"тормоз":["russian"],"торчать":["russian"],"тотчас":["russian"],"точка":["russian"],"точно":["russian"],"тощий":["russian"],"трава":["russian"],"традиция":["russian"],"трактор":["russian"],"трамвай":["russian"],"траншея":["russian"],"трасса":["russian"],"тревога":["russian"],"трезвый":["russian"],"тренер":["russian"],"трепет":["russian"],"треск":["russian"],"третий":["russian"],"трещина":["russian"],"трибуна":["russian"],"тридцать":["russian"],"триста":["russian"],"триумф":["russian"],"трогать":["russian"],"тройка":["russian"],"тронуть":["russian"],"тропа":["russian"],"тротуар":["russian"],"трубка":["russian"],"труд":["russian"],"трюк":["russian"],"тряпка":["russian"],"туго":["russian"],"туловище":["russian"],"туман":["russian"],"тумбочка":["russian"],"тундра":["russian"],"тупик":["russian"],"турист":["russian"],"турнир":["russian"],"тусклый":["russian"],"туфля":["russian"],"туча":["russian"],"тысяча":["russian"],"тяга":["russian"],"тяжело":["russian"],"убежать":["russian"],"убогий":["russian"],"уборка":["russian"],"уважение":["russian"],"увезти":["russian"],"уволить":["russian"],"угадать":["russian"],"угол":["russian"],"угощать":["russian"],"угроза":["russian"],"угрюмый":["russian"],"удар":["russian"],"удачный":["russian"],"уделять":["russian"],"удивить":["russian"],"удобный":["russian"],"удочка":["russian"],"уезжать":["russian"],"ужин":["russian"],"узел":["russian"],"узкий":["russian"],"уйти":["russian"],"указание":["russian"],"уклон":["russian"],"украсть":["russian"],"укусить":["russian"],"улетать":["russian"],"улица":["russian"],"улыбка":["russian"],"умело":["russian"],"умение":["russian"],"умный":["russian"],"умолять":["russian"],"унести":["russian"],"унижать":["russian"],"унылый":["russian"],"упаковка":["russian"],"упасть":["russian"],"упорно":["russian"],"упрек":["russian"],"урна":["russian"],"уровень":["russian"],"урожай":["russian"],"уронить":["russian"],"усадьба":["russian"],"усатый":["russian"],"усвоить":["russian"],"усилие":["russian"],"условие":["russian"],"услуга":["russian"],"усмешка":["russian"],"успеть":["russian"],"устав":["russian"],"устоять":["russian"],"утечка":["russian"],"утешать":["russian"],"утро":["russian"],"уцелеть":["russian"],"участие":["russian"],"ученик":["russian"],"учесть":["russian"],"ущелье":["russian"],"ущерб":["russian"],"уютный":["russian"],"фабрика":["russian"],"фаворит":["russian"],"факел":["russian"],"факт":["russian"],"фамилия":["russian"],"фантазия":["russian"],"фасад":["russian"],"февраль":["russian"],"феномен":["russian"],"фермер":["russian"],"фигура":["russian"],"физика":["russian"],"филиал":["russian"],"философ":["russian"],"фильм":["russian"],"финал":["russian"],"флаг":["russian"],"флот":["russian"],"фойе":["russian"],"фокус":["russian"],"фонарь":["russian"],"фонд":["russian"],"фонтан":["russian"],"форма":["russian"],"форум":["russian"],"фото":["russian"],"фрагмент":["russian"],"фраза":["russian"],"фракция":["russian"],"фронт":["russian"],"фрукт":["russian"],"функция":["russian"],"фуражка":["russian"],"футбол":["russian"],"футляр":["russian"],"халат":["russian"],"хаос":["russian"],"характер":["russian"],"хата":["russian"],"хвалить":["russian"],"хватать":["russian"],"хвойный":["russian"],"хвост":["russian"],"химия":["russian"],"хирург":["russian"],"хитрый":["russian"],"хищник":["russian"],"хлеб":["russian"],"хлынуть":["russian"],"хмурый":["russian"],"ходить":["russian"],"хозяин":["russian"],"хоккей":["russian"],"холм":["russian"],"холст":["russian"],"хорошо":["russian"],"хотеть":["russian"],"храбрый":["russian"],"храм":["russian"],"хранить":["russian"],"хребет":["russian"],"хрен":["russian"],"хрипло":["russian"],"хроника":["russian"],"хрупкий":["russian"],"художник":["russian"],"худший":["russian"],"хулиган":["russian"],"хутор":["russian"],"царь":["russian"],"цветок":["russian"],"целевой":["russian"],"целиком":["russian"],"целое":["russian"],"цель":["russian"],"цензура":["russian"],"ценить":["russian"],"центр":["russian"],"цепной":["russian"],"цикл":["russian"],"цилиндр":["russian"],"цирк":["russian"],"цитата":["russian"],"цифра":["russian"],"чайник":["russian"],"часы":["russian"],"чашка":["russian"],"человек":["russian"],"челюсть":["russian"],"чемодан":["russian"],"чемпион":["russian"],"чепуха":["russian"],"червь":["russian"],"чердак":["russian"],"через":["russian"],"чернила":["russian"],"черта":["russian"],"чеснок":["russian"],"честно":["russian"],"четверть":["russian"],"четыре":["russian"],"число":["russian"],"чистый":["russian"],"читатель":["russian"],"чтение":["russian"],"чтобы":["russian"],"чувство":["russian"],"чудак":["russian"],"чудный":["russian"],"чудо":["russian"],"чужой":["russian"],"чулок":["russian"],"чума":["russian"],"чушь":["russian"],"чуять":["russian"],"шагать":["russian"],"шанс":["russian"],"шапка":["russian"],"шарик":["russian"],"шарф":["russian"],"шахматы":["russian"],"шашлык":["russian"],"шедевр":["russian"],"шептать":["russian"],"шерсть":["russian"],"шестой":["russian"],"шинель":["russian"],"ширина":["russian"],"шишка":["russian"],"шкаф":["russian"],"школа":["russian"],"шкура":["russian"],"шланг":["russian"],"шлем":["russian"],"шнур":["russian"],"шоколад":["russian"],"шорох":["russian"],"шоссе":["russian"],"шпион":["russian"],"шприц":["russian"],"штаб":["russian"],"штамм":["russian"],"штаны":["russian"],"штатный":["russian"],"штора":["russian"],"штраф":["russian"],"штурм":["russian"],"штык":["russian"],"шумно":["russian"],"шуршать":["russian"],"шутить":["russian"],"шутка":["russian"],"щедрый":["russian"],"щека":["russian"],"щенок":["russian"],"экзамен":["russian"],"экипаж":["russian"],"экономия":["russian"],"экран":["russian"],"эксперт":["russian"],"элемент":["russian"],"элитный":["russian"],"эмоция":["russian"],"энергия":["russian"],"эпизод":["russian"],"эпоха":["russian"],"эскиз":["russian"],"эстрада":["russian"],"этап":["russian"],"этика":["russian"],"этот":["russian"],"эфир":["russian"],"эффект":["russian"],"эшелон":["russian"],"юбилей":["russian"],"юбка":["russian"],"южный":["russian"],"юмор":["russian"],"юность":["russian"],"юрист":["russian"],"юстиция":["russian"],"яблоко":["russian"],"явление":["russian"],"ягода":["russian"],"ядро":["russian"],"язык":["russian"],"яйцо":["russian"],"якобы":["russian"],"якорь":["russian"],"январь":["russian"],"яркий":["russian"],"ярмарка":["russian"],"ярость":["russian"],"ясный":["russian"],"яхта":["russian"],"ячейка":["russian"],"ящик":["russian"],"ábaco":["spanish"],"abdomen":["spanish"],"abeja":["spanish"],"abierto":["spanish"],"abogado":["spanish"],"abono":["spanish"],"aborto":["spanish"],"abrazo":["spanish"],"abrir":["spanish"],"abuelo":["spanish"],"abuso":["spanish"],"acabar":["spanish"],"academia":["spanish"],"acceso":["spanish"],"acción":["spanish"],"aceite":["spanish"],"acelga":["spanish"],"acento":["spanish"],"aceptar":["spanish"],"ácido":["spanish"],"aclarar":["spanish"],"acné":["spanish"],"acoger":["spanish"],"acoso":["spanish"],"activo":["spanish"],"acto":["spanish"],"actriz":["spanish"],"actuar":["spanish"],"acudir":["spanish"],"acuerdo":["spanish"],"acusar":["spanish"],"adicto":["spanish"],"admitir":["spanish"],"adoptar":["spanish"],"adorno":["spanish"],"aduana":["spanish"],"adulto":["spanish"],"aéreo":["spanish"],"afectar":["spanish"],"afición":["spanish"],"afinar":["spanish"],"afirmar":["spanish"],"ágil":["spanish"],"agitar":["spanish"],"agonía":["spanish"],"agosto":["spanish"],"agotar":["spanish"],"agregar":["spanish"],"agrio":["spanish"],"agua":["spanish"],"agudo":["spanish"],"águila":["spanish"],"aguja":["spanish"],"ahogo":["spanish"],"ahorro":["spanish"],"aire":["spanish"],"aislar":["spanish"],"ajedrez":["spanish"],"ajeno":["spanish"],"ajuste":["spanish"],"alacrán":["spanish"],"alambre":["spanish"],"alarma":["spanish"],"alba":["spanish"],"álbum":["spanish"],"alcalde":["spanish"],"aldea":["spanish"],"alegre":["spanish"],"alejar":["spanish"],"alerta":["spanish"],"aleta":["spanish"],"alfiler":["spanish"],"alga":["spanish"],"algodón":["spanish"],"aliado":["spanish"],"aliento":["spanish"],"alivio":["spanish"],"alma":["spanish"],"almeja":["spanish"],"almíbar":["spanish"],"altar":["spanish"],"alteza":["spanish"],"altivo":["spanish"],"alto":["spanish"],"altura":["spanish"],"alumno":["spanish"],"alzar":["spanish"],"amable":["spanish"],"amante":["spanish"],"amapola":["spanish"],"amargo":["spanish"],"amasar":["spanish"],"ámbar":["spanish"],"ámbito":["spanish"],"ameno":["spanish"],"amigo":["spanish","turkish"],"amistad":["spanish"],"amor":["spanish"],"amparo":["spanish"],"amplio":["spanish"],"ancho":["spanish"],"anciano":["spanish"],"ancla":["spanish"],"andar":["spanish"],"andén":["spanish"],"anemia":["spanish"],"ángulo":["spanish"],"anillo":["spanish"],"ánimo":["spanish"],"anís":["spanish"],"anotar":["spanish"],"antena":["spanish"],"antiguo":["spanish"],"antojo":["spanish"],"anual":["spanish"],"anular":["spanish"],"anuncio":["spanish"],"añadir":["spanish"],"añejo":["spanish"],"año":["spanish"],"apagar":["spanish"],"aparato":["spanish"],"apetito":["spanish"],"apio":["spanish"],"aplicar":["spanish"],"apodo":["spanish"],"aporte":["spanish"],"apoyo":["spanish"],"aprender":["spanish"],"aprobar":["spanish"],"apuesta":["spanish"],"apuro":["spanish"],"arado":["spanish"],"araña":["spanish"],"arar":["spanish"],"árbitro":["spanish"],"árbol":["spanish"],"arbusto":["spanish"],"archivo":["spanish"],"arco":["spanish"],"arder":["spanish"],"ardilla":["spanish"],"arduo":["spanish"],"área":["spanish"],"árido":["spanish"],"aries":["spanish"],"armonía":["spanish"],"arnés":["spanish"],"aroma":["spanish","turkish"],"arpa":["spanish"],"arpón":["spanish"],"arreglo":["spanish"],"arroz":["spanish"],"arruga":["spanish"],"arte":["spanish"],"artista":["spanish"],"asa":["spanish"],"asado":["spanish"],"asalto":["spanish"],"ascenso":["spanish"],"asegurar":["spanish"],"aseo":["spanish"],"asesor":["spanish"],"asiento":["spanish"],"asilo":["spanish"],"asistir":["spanish"],"asno":["spanish"],"asombro":["spanish"],"áspero":["spanish"],"astilla":["spanish"],"astro":["spanish"],"astuto":["spanish"],"asumir":["spanish"],"asunto":["spanish"],"atajo":["spanish"],"ataque":["spanish"],"atar":["spanish"],"atento":["spanish"],"ateo":["spanish"],"ático":["spanish"],"atleta":["spanish"],"átomo":["spanish"],"atraer":["spanish"],"atroz":["spanish"],"atún":["spanish"],"audaz":["spanish"],"audio":["spanish"],"auge":["spanish"],"aula":["spanish"],"aumento":["spanish"],"ausente":["spanish"],"autor":["spanish"],"aval":["spanish"],"avance":["spanish"],"avaro":["spanish"],"ave":["spanish"],"avellana":["spanish"],"avena":["spanish"],"avestruz":["spanish"],"avión":["spanish"],"aviso":["spanish"],"ayer":["spanish"],"ayuda":["spanish"],"ayuno":["spanish"],"azafrán":["spanish"],"azar":["spanish"],"azote":["spanish"],"azúcar":["spanish"],"azufre":["spanish"],"azul":["spanish"],"baba":["spanish"],"babor":["spanish"],"bache":["spanish"],"bahía":["spanish"],"baile":["spanish"],"bajar":["spanish"],"balanza":["spanish"],"balcón":["spanish"],"balde":["spanish"],"bambú":["spanish"],"banco":["spanish"],"banda":["spanish"],"baño":["spanish"],"barba":["spanish"],"barco":["spanish"],"barniz":["spanish"],"barro":["spanish"],"báscula":["spanish"],"bastón":["spanish"],"basura":["spanish"],"batalla":["spanish"],"batería":["spanish"],"batir":["spanish"],"batuta":["spanish"],"baúl":["spanish"],"bazar":["spanish"],"bebé":["spanish"],"bebida":["spanish"],"bello":["spanish"],"besar":["spanish"],"beso":["spanish"],"bestia":["spanish"],"bicho":["spanish"],"bien":["spanish"],"bingo":["spanish"],"blanco":["spanish"],"bloque":["spanish"],"blusa":["spanish"],"boa":["spanish"],"bobina":["spanish"],"bobo":["spanish"],"boca":["spanish"],"bocina":["spanish"],"boda":["spanish"],"bodega":["spanish"],"boina":["spanish"],"bola":["spanish"],"bolero":["spanish","turkish"],"bolsa":["spanish"],"bomba":["spanish"],"bondad":["spanish"],"bonito":["spanish"],"bono":["spanish"],"bonsái":["spanish"],"borde":["spanish"],"borrar":["spanish"],"bosque":["spanish"],"bote":["spanish"],"botín":["spanish"],"bóveda":["spanish"],"bozal":["spanish"],"bravo":["spanish"],"brazo":["spanish"],"brecha":["spanish"],"breve":["spanish"],"brillo":["spanish"],"brinco":["spanish"],"brisa":["spanish"],"broca":["spanish"],"broma":["spanish"],"bronce":["spanish"],"brote":["spanish"],"bruja":["spanish"],"brusco":["spanish"],"bruto":["spanish"],"buceo":["spanish"],"bucle":["spanish"],"bueno":["spanish"],"buey":["spanish"],"bufanda":["spanish"],"bufón":["spanish"],"búho":["spanish"],"buitre":["spanish"],"bulto":["spanish"],"burbuja":["spanish"],"burla":["spanish"],"burro":["spanish"],"buscar":["spanish"],"butaca":["spanish"],"buzón":["spanish"],"caballo":["spanish"],"cabeza":["spanish"],"cabina":["spanish"],"cabra":["spanish"],"cacao":["spanish"],"cadáver":["spanish"],"cadena":["spanish"],"caer":["spanish"],"café":["spanish"],"caída":["spanish"],"caimán":["spanish"],"caja":["spanish"],"cajón":["spanish"],"cal":["spanish"],"calamar":["spanish"],"calcio":["spanish"],"caldo":["spanish"],"calidad":["spanish"],"calle":["spanish"],"calma":["spanish"],"calor":["spanish"],"calvo":["spanish"],"cama":["spanish"],"cambio":["spanish"],"camello":["spanish"],"camino":["spanish"],"campo":["spanish"],"cáncer":["spanish"],"candil":["spanish"],"canela":["spanish"],"canguro":["spanish"],"canica":["spanish"],"canto":["spanish"],"caña":["spanish"],"cañón":["spanish"],"caoba":["spanish"],"caos":["spanish"],"capaz":["spanish"],"capitán":["spanish"],"capote":["spanish"],"captar":["spanish"],"capucha":["spanish"],"cara":["spanish"],"carbón":["spanish"],"cárcel":["spanish"],"careta":["spanish"],"carga":["spanish"],"cariño":["spanish"],"carne":["spanish"],"carpeta":["spanish"],"carro":["spanish"],"carta":["spanish"],"casa":["spanish"],"casco":["spanish"],"casero":["spanish"],"caspa":["spanish"],"castor":["spanish"],"catorce":["spanish"],"catre":["spanish"],"caudal":["spanish"],"causa":["spanish"],"cazo":["spanish"],"cebolla":["spanish"],"ceder":["spanish"],"cedro":["spanish"],"celda":["spanish"],"célebre":["spanish"],"celoso":["spanish"],"célula":["spanish"],"cemento":["spanish"],"ceniza":["spanish"],"centro":["spanish"],"cerca":["spanish"],"cerdo":["spanish"],"cereza":["spanish"],"cero":["spanish"],"cerrar":["spanish"],"certeza":["spanish"],"césped":["spanish"],"cetro":["spanish"],"chacal":["spanish"],"chaleco":["spanish"],"champú":["spanish"],"chancla":["spanish"],"chapa":["spanish"],"charla":["spanish"],"chico":["spanish"],"chiste":["spanish"],"chivo":["spanish"],"choque":["spanish"],"choza":["spanish"],"chuleta":["spanish"],"chupar":["spanish"],"ciclón":["spanish"],"ciego":["spanish"],"cielo":["spanish"],"cien":["spanish"],"cierto":["spanish"],"cifra":["spanish"],"cigarro":["spanish"],"cima":["spanish"],"cinco":["spanish"],"cine":["spanish"],"cinta":["spanish"],"ciprés":["spanish"],"circo":["spanish"],"ciruela":["spanish"],"cisne":["spanish"],"cita":["spanish"],"ciudad":["spanish"],"clamor":["spanish"],"clan":["spanish"],"claro":["spanish"],"clase":["spanish"],"clave":["spanish"],"cliente":["spanish"],"clima":["spanish"],"clínica":["spanish"],"cobre":["spanish"],"cocción":["spanish"],"cochino":["spanish"],"cocina":["spanish"],"coco":["spanish"],"código":["spanish"],"codo":["spanish"],"cofre":["spanish"],"coger":["spanish"],"cohete":["spanish"],"cojín":["spanish"],"cojo":["spanish"],"cola":["spanish"],"colcha":["spanish"],"colegio":["spanish"],"colgar":["spanish"],"colina":["spanish"],"collar":["spanish"],"colmo":["spanish"],"columna":["spanish"],"combate":["spanish"],"comer":["spanish"],"comida":["spanish"],"cómodo":["spanish"],"compra":["spanish"],"conde":["spanish"],"conejo":["spanish"],"conga":["spanish"],"conocer":["spanish"],"consejo":["spanish"],"contar":["spanish"],"copa":["spanish"],"copia":["spanish"],"corazón":["spanish"],"corbata":["spanish"],"corcho":["spanish"],"cordón":["spanish"],"corona":["spanish"],"correr":["spanish"],"coser":["spanish"],"cosmos":["spanish"],"costa":["spanish"],"cráneo":["spanish"],"cráter":["spanish"],"crear":["spanish"],"crecer":["spanish"],"creído":["spanish"],"crema":["spanish"],"cría":["spanish"],"crimen":["spanish"],"cripta":["spanish"],"crisis":["spanish"],"cromo":["spanish"],"crónica":["spanish"],"croqueta":["spanish"],"crudo":["spanish"],"cruz":["spanish"],"cuadro":["spanish"],"cuarto":["spanish"],"cuatro":["spanish"],"cubo":["spanish"],"cubrir":["spanish"],"cuchara":["spanish"],"cuello":["spanish"],"cuento":["spanish"],"cuerda":["spanish"],"cuesta":["spanish"],"cueva":["spanish"],"cuidar":["spanish"],"culebra":["spanish"],"culpa":["spanish"],"culto":["spanish"],"cumbre":["spanish"],"cumplir":["spanish"],"cuna":["spanish"],"cuneta":["spanish"],"cuota":["spanish"],"cupón":["spanish"],"cúpula":["spanish"],"curar":["spanish"],"curioso":["spanish"],"curso":["spanish"],"curva":["spanish"],"cutis":["spanish"],"dama":["spanish"],"danza":["spanish"],"dar":["spanish"],"dardo":["spanish"],"dátil":["spanish"],"deber":["spanish"],"débil":["spanish"],"década":["spanish"],"decir":["spanish"],"dedo":["spanish"],"defensa":["spanish"],"definir":["spanish"],"dejar":["spanish"],"delfín":["spanish"],"delgado":["spanish"],"delito":["spanish"],"demora":["spanish"],"denso":["spanish"],"dental":["spanish"],"deporte":["spanish"],"derecho":["spanish"],"derrota":["spanish"],"desayuno":["spanish"],"deseo":["spanish"],"desfile":["spanish"],"desnudo":["spanish"],"destino":["spanish"],"desvío":["spanish"],"detalle":["spanish"],"detener":["spanish"],"deuda":["spanish"],"día":["spanish"],"diablo":["spanish"],"diadema":["spanish"],"diamante":["spanish"],"diana":["spanish"],"diario":["spanish"],"dibujo":["spanish"],"dictar":["spanish"],"diente":["spanish"],"dieta":["spanish"],"diez":["spanish"],"difícil":["spanish"],"digno":["spanish"],"dilema":["spanish"],"diluir":["spanish"],"dinero":["spanish"],"directo":["spanish"],"dirigir":["spanish"],"disco":["spanish"],"diseño":["spanish"],"disfraz":["spanish"],"diva":["spanish"],"divino":["spanish"],"doble":["spanish"],"doce":["spanish"],"dolor":["spanish"],"domingo":["spanish"],"don":["spanish"],"donar":["spanish"],"dorado":["spanish"],"dormir":["spanish"],"dorso":["spanish"],"dos":["spanish"],"dosis":["spanish"],"dragón":["spanish"],"droga":["spanish"],"ducha":["spanish"],"duda":["spanish"],"duelo":["spanish"],"dueño":["spanish"],"dulce":["spanish"],"dúo":["spanish"],"duque":["spanish"],"durar":["spanish"],"dureza":["spanish"],"duro":["spanish"],"ébano":["spanish"],"ebrio":["spanish"],"echar":["spanish"],"eco":["spanish"],"ecuador":["spanish"],"edad":["spanish"],"edición":["spanish"],"edificio":["spanish"],"editor":["spanish"],"educar":["spanish"],"efecto":["spanish"],"eficaz":["spanish"],"eje":["spanish"],"ejemplo":["spanish"],"elefante":["spanish"],"elegir":["spanish"],"elemento":["spanish"],"elevar":["spanish"],"elipse":["spanish"],"élite":["spanish"],"elixir":["spanish"],"elogio":["spanish"],"eludir":["spanish"],"embudo":["spanish"],"emitir":["spanish"],"emoción":["spanish"],"empate":["spanish"],"empeño":["spanish"],"empleo":["spanish"],"empresa":["spanish"],"enano":["spanish"],"encargo":["spanish"],"enchufe":["spanish"],"encía":["spanish"],"enemigo":["spanish"],"enero":["spanish"],"enfado":["spanish"],"enfermo":["spanish"],"engaño":["spanish"],"enigma":["spanish"],"enlace":["spanish"],"enorme":["spanish"],"enredo":["spanish"],"ensayo":["spanish"],"enseñar":["spanish"],"entero":["spanish"],"entrar":["spanish"],"envase":["spanish"],"envío":["spanish"],"época":["spanish"],"equipo":["spanish"],"erizo":["spanish"],"escala":["spanish"],"escena":["spanish"],"escolar":["spanish"],"escribir":["spanish"],"escudo":["spanish"],"esencia":["spanish"],"esfera":["spanish"],"esfuerzo":["spanish"],"espada":["spanish"],"espejo":["spanish"],"espía":["spanish"],"esposa":["spanish"],"espuma":["spanish"],"esquí":["spanish"],"estar":["spanish"],"este":["spanish"],"estilo":["spanish"],"estufa":["spanish"],"etapa":["spanish"],"eterno":["spanish"],"ética":["spanish"],"etnia":["spanish"],"evadir":["spanish"],"evaluar":["spanish"],"evento":["spanish"],"evitar":["spanish"],"exacto":["spanish"],"examen":["spanish"],"exceso":["spanish"],"excusa":["spanish"],"exento":["spanish"],"exigir":["spanish"],"exilio":["spanish"],"existir":["spanish"],"éxito":["spanish"],"experto":["spanish"],"explicar":["spanish"],"exponer":["spanish"],"extremo":["spanish"],"fábrica":["spanish"],"fábula":["spanish"],"fachada":["spanish"],"fácil":["spanish"],"factor":["spanish"],"faena":["spanish"],"faja":["spanish"],"falda":["spanish"],"fallo":["spanish"],"falso":["spanish"],"faltar":["spanish"],"fama":["spanish"],"familia":["spanish"],"famoso":["spanish"],"faraón":["spanish"],"farmacia":["spanish"],"farol":["spanish"],"farsa":["spanish"],"fase":["spanish"],"fatiga":["spanish"],"fauna":["spanish","turkish"],"favor":["spanish"],"fax":["spanish"],"febrero":["spanish"],"fecha":["spanish"],"feliz":["spanish"],"feo":["spanish"],"feria":["spanish"],"feroz":["spanish"],"fértil":["spanish"],"fervor":["spanish"],"festín":["spanish"],"fiable":["spanish"],"fianza":["spanish"],"fiar":["spanish"],"fibra":["spanish"],"ficción":["spanish"],"ficha":["spanish"],"fideo":["spanish"],"fiebre":["spanish"],"fiel":["spanish"],"fiera":["spanish"],"fiesta":["spanish"],"figura":["spanish"],"fijar":["spanish"],"fijo":["spanish"],"fila":["spanish"],"filete":["spanish"],"filial":["spanish"],"filtro":["spanish"],"fin":["spanish"],"finca":["spanish"],"fingir":["spanish"],"finito":["spanish"],"firma":["spanish"],"flaco":["spanish"],"flauta":["spanish"],"flecha":["spanish"],"flor":["spanish"],"flota":["spanish"],"fluir":["spanish"],"flujo":["spanish"],"flúor":["spanish"],"fobia":["spanish"],"foca":["spanish"],"fogata":["spanish"],"fogón":["spanish"],"folio":["spanish"],"folleto":["spanish"],"fondo":["spanish"],"forma":["spanish"],"forro":["spanish"],"fortuna":["spanish"],"forzar":["spanish"],"fosa":["spanish"],"foto":["spanish"],"fracaso":["spanish"],"frágil":["spanish"],"franja":["spanish"],"frase":["spanish"],"fraude":["spanish"],"freír":["spanish"],"freno":["spanish"],"fresa":["spanish"],"frío":["spanish"],"frito":["spanish"],"fruta":["spanish"],"fuego":["spanish"],"fuente":["spanish"],"fuerza":["spanish"],"fuga":["spanish"],"fumar":["spanish"],"función":["spanish"],"funda":["spanish"],"furgón":["spanish"],"furia":["spanish"],"fusil":["spanish"],"fútbol":["spanish"],"futuro":["spanish"],"gacela":["spanish"],"gafas":["spanish"],"gaita":["spanish"],"gajo":["spanish"],"gala":["spanish"],"galería":["spanish"],"gallo":["spanish"],"gamba":["spanish"],"ganar":["spanish"],"gancho":["spanish"],"ganga":["spanish"],"ganso":["spanish"],"garaje":["spanish"],"garza":["spanish"],"gasolina":["spanish"],"gastar":["spanish"],"gato":["spanish"],"gavilán":["spanish"],"gemelo":["spanish"],"gemir":["spanish"],"gen":["spanish"],"género":["spanish"],"genio":["spanish"],"gente":["spanish"],"geranio":["spanish"],"gerente":["spanish"],"germen":["spanish"],"gesto":["spanish"],"gigante":["spanish"],"gimnasio":["spanish"],"girar":["spanish"],"giro":["spanish"],"glaciar":["spanish"],"globo":["spanish"],"gloria":["spanish"],"gol":["spanish"],"golfo":["spanish"],"goloso":["spanish"],"golpe":["spanish"],"goma":["spanish"],"gordo":["spanish"],"gorila":["spanish"],"gorra":["spanish"],"gota":["spanish"],"goteo":["spanish"],"gozar":["spanish"],"grada":["spanish"],"gráfico":["spanish"],"grano":["spanish"],"grasa":["spanish"],"gratis":["spanish"],"grave":["spanish"],"grieta":["spanish"],"grillo":["spanish"],"gripe":["spanish"],"gris":["spanish"],"grito":["spanish"],"grosor":["spanish"],"grúa":["spanish"],"grueso":["spanish"],"grumo":["spanish"],"grupo":["spanish"],"guante":["spanish"],"guapo":["spanish"],"guardia":["spanish"],"guerra":["spanish"],"guía":["spanish"],"guiño":["spanish"],"guion":["spanish"],"guiso":["spanish"],"guitarra":["spanish"],"gusano":["spanish"],"gustar":["spanish"],"haber":["spanish","turkish"],"hábil":["spanish"],"hablar":["spanish"],"hacer":["spanish"],"hacha":["spanish"],"hada":["spanish"],"hallar":["spanish"],"hamaca":["spanish"],"harina":["spanish"],"haz":["spanish"],"hazaña":["spanish"],"hebilla":["spanish"],"hebra":["spanish"],"hecho":["spanish"],"helado":["spanish"],"helio":["spanish"],"hembra":["spanish"],"herir":["spanish"],"hermano":["spanish"],"héroe":["spanish"],"hervir":["spanish"],"hielo":["spanish"],"hierro":["spanish"],"hígado":["spanish"],"higiene":["spanish"],"hijo":["spanish"],"himno":["spanish"],"historia":["spanish"],"hocico":["spanish"],"hogar":["spanish"],"hoguera":["spanish"],"hoja":["spanish"],"hombre":["spanish"],"hongo":["spanish"],"honor":["spanish"],"honra":["spanish"],"hora":["spanish"],"hormiga":["spanish"],"horno":["spanish"],"hostil":["spanish"],"hoyo":["spanish"],"hueco":["spanish"],"huelga":["spanish"],"huerta":["spanish"],"hueso":["spanish"],"huevo":["spanish"],"huida":["spanish"],"huir":["spanish"],"humano":["spanish"],"húmedo":["spanish"],"humilde":["spanish"],"humo":["spanish"],"hundir":["spanish"],"huracán":["spanish"],"hurto":["spanish"],"icono":["spanish"],"ideal":["spanish"],"idioma":["spanish"],"ídolo":["spanish"],"iglesia":["spanish"],"iglú":["spanish"],"igual":["spanish"],"ilegal":["spanish"],"ilusión":["spanish"],"imagen":["spanish"],"imán":["spanish"],"imitar":["spanish"],"impar":["spanish"],"imperio":["spanish"],"imponer":["spanish"],"impulso":["spanish"],"incapaz":["spanish"],"índice":["spanish"],"inerte":["spanish"],"infiel":["spanish"],"informe":["spanish"],"ingenio":["spanish"],"inicio":["spanish"],"inmenso":["spanish"],"inmune":["spanish"],"innato":["spanish"],"insecto":["spanish"],"instante":["spanish"],"interés":["spanish"],"íntimo":["spanish"],"intuir":["spanish"],"inútil":["spanish"],"invierno":["spanish"],"ira":["spanish"],"iris":["spanish"],"ironía":["spanish"],"isla":["spanish"],"islote":["spanish"],"jabalí":["spanish"],"jabón":["spanish"],"jamón":["spanish"],"jarabe":["spanish"],"jardín":["spanish"],"jarra":["spanish"],"jaula":["spanish"],"jazmín":["spanish"],"jefe":["spanish"],"jeringa":["spanish"],"jinete":["spanish"],"jornada":["spanish"],"joroba":["spanish"],"joven":["spanish"],"joya":["spanish"],"juerga":["spanish"],"jueves":["spanish"],"juez":["spanish"],"jugador":["spanish"],"jugo":["spanish"],"juguete":["spanish"],"juicio":["spanish"],"junco":["spanish"],"jungla":["spanish"],"junio":["spanish"],"juntar":["spanish"],"júpiter":["spanish"],"jurar":["spanish"],"justo":["spanish"],"juvenil":["spanish"],"juzgar":["spanish"],"kilo":["spanish"],"koala":["spanish"],"labio":["spanish"],"lacio":["spanish"],"lacra":["spanish"],"lado":["spanish"],"ladrón":["spanish"],"lagarto":["spanish"],"lágrima":["spanish"],"laguna":["spanish"],"laico":["spanish"],"lamer":["spanish"],"lámina":["spanish"],"lámpara":["spanish"],"lana":["spanish"],"lancha":["spanish"],"langosta":["spanish"],"lanza":["spanish"],"lápiz":["spanish"],"largo":["spanish"],"larva":["spanish"],"lástima":["spanish"],"lata":["spanish"],"látex":["spanish"],"latir":["spanish"],"laurel":["spanish"],"lavar":["spanish"],"lazo":["spanish"],"leal":["spanish"],"lección":["spanish"],"leche":["spanish"],"lector":["spanish"],"leer":["spanish"],"legión":["spanish"],"legumbre":["spanish"],"lejano":["spanish"],"lengua":["spanish"],"lento":["spanish"],"leña":["spanish"],"león":["spanish"],"leopardo":["spanish"],"lesión":["spanish"],"letal":["spanish"],"letra":["spanish"],"leve":["spanish"],"leyenda":["spanish"],"libertad":["spanish"],"libro":["spanish"],"licor":["spanish"],"líder":["spanish"],"lidiar":["spanish"],"lienzo":["spanish"],"liga":["spanish"],"ligero":["spanish"],"lima":["spanish"],"límite":["spanish"],"limón":["spanish"],"limpio":["spanish"],"lince":["spanish"],"lindo":["spanish"],"línea":["spanish"],"lingote":["spanish"],"lino":["spanish"],"linterna":["spanish"],"líquido":["spanish"],"liso":["spanish"],"lista":["spanish"],"litera":["spanish"],"litio":["spanish"],"litro":["spanish"],"llaga":["spanish"],"llama":["spanish"],"llanto":["spanish"],"llave":["spanish"],"llegar":["spanish"],"llenar":["spanish"],"llevar":["spanish"],"llorar":["spanish"],"llover":["spanish"],"lluvia":["spanish"],"lobo":["spanish"],"loción":["spanish"],"loco":["spanish"],"locura":["spanish"],"lógica":["spanish"],"logro":["spanish"],"lombriz":["spanish"],"lomo":["spanish"],"lonja":["spanish"],"lote":["spanish"],"lucha":["spanish"],"lucir":["spanish"],"lugar":["spanish"],"lujo":["spanish"],"luna":["spanish"],"lunes":["spanish"],"lupa":["spanish"],"lustro":["spanish"],"luto":["spanish"],"luz":["spanish"],"maceta":["spanish"],"macho":["spanish"],"madera":["spanish"],"madre":["spanish"],"maduro":["spanish"],"maestro":["spanish"],"mafia":["spanish"],"magia":["spanish"],"mago":["spanish"],"maíz":["spanish"],"maldad":["spanish"],"maleta":["spanish"],"malla":["spanish"],"malo":["spanish"],"mamá":["spanish"],"mambo":["spanish"],"mamut":["spanish"],"manco":["spanish"],"mando":["spanish"],"manejar":["spanish"],"manga":["spanish"],"maniquí":["spanish"],"manjar":["spanish"],"mano":["spanish"],"manso":["spanish"],"manta":["spanish"],"mañana":["spanish"],"mapa":["spanish"],"máquina":["spanish"],"mar":["spanish"],"marco":["spanish"],"marea":["spanish"],"marfil":["spanish"],"margen":["spanish"],"marido":["spanish"],"mármol":["spanish"],"marrón":["spanish"],"martes":["spanish"],"marzo":["spanish"],"masa":["spanish"],"máscara":["spanish"],"masivo":["spanish"],"matar":["spanish"],"materia":["spanish"],"matiz":["spanish"],"matriz":["spanish"],"máximo":["spanish"],"mayor":["spanish"],"mazorca":["spanish"],"mecha":["spanish"],"medalla":["spanish"],"medio":["spanish"],"médula":["spanish"],"mejilla":["spanish"],"mejor":["spanish"],"melena":["spanish"],"melón":["spanish"],"memoria":["spanish"],"menor":["spanish"],"mensaje":["spanish"],"mente":["spanish"],"menú":["spanish"],"mercado":["spanish"],"merengue":["spanish"],"mérito":["spanish"],"mes":["spanish"],"mesón":["spanish"],"meta":["spanish"],"meter":["spanish"],"método":["spanish"],"metro":["spanish"],"mezcla":["spanish"],"miedo":["spanish"],"miel":["spanish"],"miembro":["spanish"],"miga":["spanish"],"mil":["spanish"],"milagro":["spanish"],"militar":["spanish"],"millón":["spanish"],"mimo":["spanish"],"mina":["spanish"],"minero":["spanish"],"mínimo":["spanish"],"minuto":["spanish"],"miope":["spanish"],"mirar":["spanish"],"misa":["spanish"],"miseria":["spanish"],"misil":["spanish"],"mismo":["spanish"],"mitad":["spanish"],"mito":["spanish"],"mochila":["spanish"],"moción":["spanish"],"moda":["spanish"],"modelo":["spanish"],"moho":["spanish"],"mojar":["spanish"],"molde":["spanish"],"moler":["spanish"],"molino":["spanish"],"momento":["spanish"],"momia":["spanish"],"monarca":["spanish"],"moneda":["spanish"],"monja":["spanish"],"monto":["spanish"],"moño":["spanish"],"morada":["spanish"],"morder":["spanish"],"moreno":["spanish"],"morir":["spanish"],"morro":["spanish"],"morsa":["spanish"],"mortal":["spanish"],"mosca":["spanish"],"mostrar":["spanish"],"motivo":["spanish"],"mover":["spanish"],"móvil":["spanish"],"mozo":["spanish"],"mucho":["spanish"],"mudar":["spanish"],"mueble":["spanish"],"muela":["spanish"],"muerte":["spanish"],"muestra":["spanish"],"mugre":["spanish"],"mujer":["spanish"],"mula":["spanish"],"muleta":["spanish"],"multa":["spanish"],"mundo":["spanish"],"muñeca":["spanish"],"mural":["spanish"],"muro":["spanish"],"músculo":["spanish"],"museo":["spanish"],"musgo":["spanish"],"música":["spanish"],"muslo":["spanish"],"nácar":["spanish"],"nación":["spanish"],"nadar":["spanish"],"naipe":["spanish"],"naranja":["spanish"],"nariz":["spanish"],"narrar":["spanish"],"nasal":["spanish"],"natal":["spanish"],"nativo":["spanish"],"natural":["spanish"],"náusea":["spanish"],"naval":["spanish"],"nave":["spanish"],"navidad":["spanish"],"necio":["spanish"],"néctar":["spanish"],"negar":["spanish"],"negocio":["spanish"],"negro":["spanish"],"neón":["spanish"],"nervio":["spanish"],"neto":["spanish"],"neutro":["spanish"],"nevar":["spanish"],"nevera":["spanish"],"nicho":["spanish"],"nido":["spanish"],"niebla":["spanish"],"nieto":["spanish"],"niñez":["spanish"],"niño":["spanish"],"nítido":["spanish"],"nivel":["spanish"],"nobleza":["spanish"],"noche":["spanish"],"nómina":["spanish"],"noria":["spanish"],"norma":["spanish"],"norte":["spanish"],"nota":["spanish"],"noticia":["spanish"],"novato":["spanish"],"novela":["spanish"],"novio":["spanish"],"nube":["spanish"],"nuca":["spanish"],"núcleo":["spanish"],"nudillo":["spanish"],"nudo":["spanish"],"nuera":["spanish"],"nueve":["spanish"],"nuez":["spanish"],"nulo":["spanish"],"número":["spanish"],"nutria":["spanish"],"oasis":["spanish"],"obeso":["spanish"],"obispo":["spanish"],"objeto":["spanish"],"obra":["spanish"],"obrero":["spanish"],"observar":["spanish"],"obtener":["spanish"],"obvio":["spanish"],"oca":["spanish"],"ocaso":["spanish"],"océano":["spanish"],"ochenta":["spanish"],"ocho":["spanish"],"ocio":["spanish"],"ocre":["spanish"],"octavo":["spanish"],"octubre":["spanish"],"oculto":["spanish"],"ocupar":["spanish"],"ocurrir":["spanish"],"odiar":["spanish"],"odio":["spanish"],"odisea":["spanish"],"oeste":["spanish"],"ofensa":["spanish"],"oferta":["spanish"],"oficio":["spanish"],"ofrecer":["spanish"],"ogro":["spanish"],"oído":["spanish"],"oír":["spanish"],"ojo":["spanish"],"ola":["spanish"],"oleada":["spanish"],"olfato":["spanish"],"olivo":["spanish"],"olla":["spanish"],"olmo":["spanish"],"olor":["spanish"],"olvido":["spanish"],"ombligo":["spanish"],"onda":["spanish"],"onza":["spanish"],"opaco":["spanish"],"opción":["spanish"],"ópera":["spanish"],"opinar":["spanish"],"oponer":["spanish"],"optar":["spanish"],"óptica":["spanish"],"opuesto":["spanish"],"oración":["spanish"],"orador":["spanish"],"oral":["spanish","turkish"],"órbita":["spanish"],"orca":["spanish"],"orden":["spanish"],"oreja":["spanish"],"órgano":["spanish"],"orgía":["spanish"],"orgullo":["spanish"],"oriente":["spanish"],"origen":["spanish"],"orilla":["spanish"],"oro":["spanish"],"orquesta":["spanish"],"oruga":["spanish"],"osadía":["spanish"],"oscuro":["spanish"],"osezno":["spanish"],"oso":["spanish"],"ostra":["spanish"],"otoño":["spanish"],"otro":["spanish"],"oveja":["spanish"],"óvulo":["spanish"],"óxido":["spanish"],"oxígeno":["spanish"],"oyente":["spanish"],"ozono":["spanish"],"pacto":["spanish"],"padre":["spanish"],"paella":["spanish"],"página":["spanish"],"pago":["spanish"],"país":["spanish"],"pájaro":["spanish"],"palabra":["spanish"],"palco":["spanish"],"paleta":["spanish"],"pálido":["spanish"],"palma":["spanish"],"paloma":["spanish"],"palpar":["spanish"],"pan":["spanish"],"panal":["spanish"],"pánico":["spanish"],"pantera":["spanish"],"pañuelo":["spanish"],"papá":["spanish"],"papel":["spanish"],"papilla":["spanish"],"paquete":["spanish"],"parar":["spanish"],"parcela":["spanish"],"pared":["spanish"],"parir":["spanish"],"paro":["spanish"],"párpado":["spanish"],"parque":["spanish"],"párrafo":["spanish"],"parte":["spanish"],"pasar":["spanish"],"paseo":["spanish"],"pasión":["spanish"],"paso":["spanish"],"pasta":["spanish"],"pata":["spanish"],"patio":["spanish"],"patria":["spanish"],"pausa":["spanish"],"pauta":["spanish"],"pavo":["spanish"],"payaso":["spanish"],"peatón":["spanish"],"pecado":["spanish"],"pecera":["spanish"],"pecho":["spanish"],"pedal":["spanish","turkish"],"pedir":["spanish"],"pegar":["spanish"],"peine":["spanish"],"pelar":["spanish"],"peldaño":["spanish"],"pelea":["spanish"],"peligro":["spanish"],"pellejo":["spanish"],"pelo":["spanish"],"peluca":["spanish"],"pena":["spanish","turkish"],"pensar":["spanish"],"peñón":["spanish"],"peón":["spanish"],"peor":["spanish"],"pepino":["spanish"],"pequeño":["spanish"],"pera":["spanish"],"percha":["spanish"],"perder":["spanish"],"pereza":["spanish"],"perfil":["spanish"],"perico":["spanish"],"perla":["spanish"],"permiso":["spanish"],"perro":["spanish"],"persona":["spanish"],"pesa":["spanish"],"pesca":["spanish"],"pésimo":["spanish"],"pestaña":["spanish"],"pétalo":["spanish"],"petróleo":["spanish"],"pez":["spanish"],"pezuña":["spanish"],"picar":["spanish"],"pichón":["spanish"],"pie":["spanish"],"piedra":["spanish"],"pierna":["spanish"],"pieza":["spanish"],"pijama":["spanish"],"pilar":["spanish"],"piloto":["spanish"],"pimienta":["spanish"],"pino":["spanish"],"pintor":["spanish"],"pinza":["spanish"],"piña":["spanish"],"piojo":["spanish"],"pipa":["spanish"],"pirata":["spanish"],"pisar":["spanish"],"piscina":["spanish"],"piso":["spanish"],"pista":["spanish"],"pitón":["spanish"],"pizca":["spanish"],"placa":["spanish"],"plan":["spanish"],"plata":["spanish"],"playa":["spanish"],"plaza":["spanish"],"pleito":["spanish"],"pleno":["spanish"],"plomo":["spanish"],"pluma":["spanish"],"plural":["spanish"],"pobre":["spanish"],"poco":["spanish"],"poder":["spanish"],"podio":["spanish"],"poema":["spanish"],"poesía":["spanish"],"poeta":["spanish"],"polen":["spanish","turkish"],"policía":["spanish"],"pollo":["spanish"],"polvo":["spanish"],"pomada":["spanish"],"pomelo":["spanish"],"pomo":["spanish"],"pompa":["spanish","turkish"],"poner":["spanish"],"porción":["spanish"],"portal":["spanish"],"posada":["spanish"],"poseer":["spanish"],"posible":["spanish"],"poste":["spanish"],"potencia":["spanish"],"potro":["spanish"],"pozo":["spanish"],"prado":["spanish"],"precoz":["spanish"],"pregunta":["spanish"],"premio":["spanish"],"prensa":["spanish"],"preso":["spanish"],"previo":["spanish"],"primo":["spanish"],"príncipe":["spanish"],"prisión":["spanish"],"privar":["spanish"],"proa":["spanish"],"probar":["spanish"],"proceso":["spanish"],"producto":["spanish"],"proeza":["spanish"],"profesor":["spanish"],"programa":["spanish"],"prole":["spanish"],"promesa":["spanish"],"pronto":["spanish"],"propio":["spanish"],"próximo":["spanish"],"prueba":["spanish"],"público":["spanish"],"puchero":["spanish"],"pudor":["spanish"],"pueblo":["spanish"],"puerta":["spanish"],"puesto":["spanish"],"pulga":["spanish"],"pulir":["spanish"],"pulmón":["spanish"],"pulpo":["spanish"],"pulso":["spanish"],"puma":["spanish"],"punto":["spanish"],"puñal":["spanish"],"puño":["spanish"],"pupa":["spanish"],"pupila":["spanish"],"puré":["spanish"],"quedar":["spanish"],"queja":["spanish"],"quemar":["spanish"],"querer":["spanish"],"queso":["spanish"],"quieto":["spanish"],"química":["spanish"],"quince":["spanish"],"quitar":["spanish"],"rábano":["spanish"],"rabia":["spanish"],"rabo":["spanish"],"ración":["spanish"],"radical":["spanish"],"raíz":["spanish"],"rama":["spanish"],"rampa":["spanish","turkish"],"rancho":["spanish"],"rango":["spanish"],"rapaz":["spanish"],"rápido":["spanish"],"rapto":["spanish"],"rasgo":["spanish"],"raspa":["spanish"],"rato":["spanish"],"rayo":["spanish"],"raza":["spanish"],"razón":["spanish"],"reacción":["spanish"],"realidad":["spanish"],"rebaño":["spanish"],"rebote":["spanish"],"recaer":["spanish"],"receta":["spanish"],"rechazo":["spanish"],"recoger":["spanish"],"recreo":["spanish"],"recto":["spanish"],"recurso":["spanish"],"red":["spanish"],"redondo":["spanish"],"reducir":["spanish"],"reflejo":["spanish"],"reforma":["spanish"],"refrán":["spanish"],"refugio":["spanish"],"regalo":["spanish"],"regir":["spanish"],"regla":["spanish"],"regreso":["spanish"],"rehén":["spanish"],"reino":["spanish"],"reír":["spanish"],"reja":["spanish"],"relato":["spanish"],"relevo":["spanish"],"relieve":["spanish"],"relleno":["spanish"],"reloj":["spanish"],"remar":["spanish"],"remedio":["spanish"],"remo":["spanish"],"rencor":["spanish"],"rendir":["spanish"],"renta":["spanish"],"reparto":["spanish"],"repetir":["spanish"],"reposo":["spanish"],"reptil":["spanish"],"res":["spanish"],"rescate":["spanish"],"resina":["spanish"],"respeto":["spanish"],"resto":["spanish"],"resumen":["spanish"],"retiro":["spanish"],"retorno":["spanish"],"retrato":["spanish"],"reunir":["spanish"],"revés":["spanish"],"revista":["spanish"],"rey":["spanish"],"rezar":["spanish"],"rico":["spanish"],"riego":["spanish"],"rienda":["spanish"],"riesgo":["spanish"],"rifa":["spanish"],"rígido":["spanish"],"rigor":["spanish"],"rincón":["spanish"],"riñón":["spanish"],"río":["spanish"],"riqueza":["spanish"],"risa":["spanish"],"ritmo":["spanish"],"rito":["spanish"],"rizo":["spanish"],"roble":["spanish"],"roce":["spanish"],"rociar":["spanish"],"rodar":["spanish"],"rodeo":["spanish"],"rodilla":["spanish"],"roer":["spanish"],"rojizo":["spanish"],"rojo":["spanish"],"romero":["spanish"],"romper":["spanish"],"ron":["spanish"],"ronco":["spanish"],"ronda":["spanish"],"ropa":["spanish"],"ropero":["spanish"],"rosa":["spanish"],"rosca":["spanish"],"rostro":["spanish"],"rotar":["spanish"],"rubí":["spanish"],"rubor":["spanish"],"rudo":["spanish"],"rueda":["spanish"],"rugir":["spanish"],"ruido":["spanish"],"ruina":["spanish"],"ruleta":["spanish"],"rulo":["spanish","turkish"],"rumbo":["spanish"],"rumor":["spanish"],"ruptura":["spanish"],"ruta":["spanish"],"rutina":["spanish"],"sábado":["spanish"],"saber":["spanish"],"sabio":["spanish"],"sable":["spanish"],"sacar":["spanish"],"sagaz":["spanish"],"sagrado":["spanish"],"sala":["spanish"],"saldo":["spanish"],"salero":["spanish"],"salir":["spanish"],"salmón":["spanish"],"salón":["spanish"],"salsa":["spanish"],"salto":["spanish"],"salud":["spanish"],"salvar":["spanish"],"samba":["spanish"],"sanción":["spanish"],"sandía":["spanish"],"sanear":["spanish"],"sangre":["spanish"],"sanidad":["spanish"],"sano":["spanish"],"santo":["spanish"],"sapo":["spanish"],"saque":["spanish"],"sardina":["spanish"],"sartén":["spanish"],"sastre":["spanish"],"satán":["spanish"],"sauna":["spanish"],"saxofón":["spanish"],"sección":["spanish"],"seco":["spanish"],"secreto":["spanish"],"secta":["spanish"],"sed":["spanish"],"seguir":["spanish"],"seis":["spanish"],"sello":["spanish"],"selva":["spanish"],"semana":["spanish"],"semilla":["spanish"],"senda":["spanish"],"sensor":["spanish"],"señal":["spanish"],"señor":["spanish"],"separar":["spanish"],"sepia":["spanish"],"sequía":["spanish"],"ser":["spanish"],"serie":["spanish"],"sermón":["spanish"],"servir":["spanish"],"sesenta":["spanish"],"sesión":["spanish"],"seta":["spanish"],"setenta":["spanish"],"severo":["spanish"],"sexo":["spanish"],"sexto":["spanish"],"sidra":["spanish"],"siesta":["spanish"],"siete":["spanish"],"siglo":["spanish"],"signo":["spanish"],"sílaba":["spanish"],"silbar":["spanish"],"silencio":["spanish"],"silla":["spanish"],"símbolo":["spanish"],"simio":["spanish"],"sirena":["spanish"],"sistema":["spanish"],"sitio":["spanish"],"situar":["spanish"],"sobre":["spanish"],"socio":["spanish"],"sodio":["spanish"],"sol":["spanish"],"solapa":["spanish"],"soldado":["spanish"],"soledad":["spanish"],"sólido":["spanish"],"soltar":["spanish"],"solución":["spanish"],"sombra":["spanish"],"sondeo":["spanish"],"sonido":["spanish"],"sonoro":["spanish"],"sonrisa":["spanish"],"sopa":["spanish"],"soplar":["spanish"],"soporte":["spanish"],"sordo":["spanish"],"sorpresa":["spanish"],"sorteo":["spanish"],"sostén":["spanish"],"sótano":["spanish"],"suave":["spanish"],"subir":["spanish"],"suceso":["spanish"],"sudor":["spanish"],"suegra":["spanish"],"suelo":["spanish"],"sueño":["spanish"],"suerte":["spanish"],"sufrir":["spanish"],"sujeto":["spanish"],"sultán":["spanish"],"sumar":["spanish"],"superar":["spanish"],"suplir":["spanish"],"suponer":["spanish"],"supremo":["spanish"],"sur":["spanish"],"surco":["spanish"],"sureño":["spanish"],"surgir":["spanish"],"susto":["spanish"],"sutil":["spanish"],"tabaco":["spanish"],"tabique":["spanish"],"tabla":["spanish"],"tabú":["spanish"],"taco":["spanish"],"tacto":["spanish"],"tajo":["spanish"],"talar":["spanish"],"talco":["spanish"],"talento":["spanish"],"talla":["spanish"],"talón":["spanish"],"tamaño":["spanish"],"tambor":["spanish"],"tango":["spanish"],"tanque":["spanish"],"tapa":["spanish"],"tapete":["spanish"],"tapia":["spanish"],"tapón":["spanish"],"taquilla":["spanish"],"tarde":["spanish"],"tarea":["spanish"],"tarifa":["spanish"],"tarjeta":["spanish"],"tarot":["spanish"],"tarro":["spanish"],"tarta":["spanish"],"tatuaje":["spanish"],"tauro":["spanish"],"taza":["spanish"],"tazón":["spanish"],"teatro":["spanish"],"techo":["spanish"],"tecla":["spanish"],"técnica":["spanish"],"tejado":["spanish"],"tejer":["spanish"],"tejido":["spanish"],"tela":["spanish"],"teléfono":["spanish"],"tema":["spanish"],"temor":["spanish"],"templo":["spanish"],"tenaz":["spanish"],"tender":["spanish"],"tener":["spanish"],"tenis":["spanish"],"tenso":["spanish"],"teoría":["spanish"],"terapia":["spanish"],"terco":["spanish"],"término":["spanish"],"ternura":["spanish"],"terror":["spanish"],"tesis":["spanish"],"tesoro":["spanish"],"testigo":["spanish"],"tetera":["spanish"],"texto":["spanish"],"tez":["spanish"],"tibio":["spanish"],"tiburón":["spanish"],"tiempo":["spanish"],"tienda":["spanish"],"tierra":["spanish"],"tieso":["spanish"],"tigre":["spanish"],"tijera":["spanish"],"tilde":["spanish"],"timbre":["spanish"],"tímido":["spanish"],"timo":["spanish"],"tinta":["spanish"],"tío":["spanish"],"típico":["spanish"],"tipo":["spanish"],"tira":["spanish"],"tirón":["spanish"],"titán":["spanish"],"títere":["spanish"],"título":["spanish"],"tiza":["spanish"],"toalla":["spanish"],"tobillo":["spanish"],"tocar":["spanish"],"tocino":["spanish"],"todo":["spanish"],"toga":["spanish"],"toldo":["spanish"],"tomar":["spanish","turkish"],"tono":["spanish"],"tonto":["spanish"],"topar":["spanish"],"tope":["spanish"],"toque":["spanish"],"tórax":["spanish"],"torero":["spanish"],"tormenta":["spanish"],"torneo":["spanish"],"toro":["spanish"],"torpedo":["spanish"],"torre":["spanish"],"torso":["spanish"],"tortuga":["spanish"],"tos":["spanish"],"tosco":["spanish"],"toser":["spanish"],"tóxico":["spanish"],"trabajo":["spanish"],"tractor":["spanish"],"traer":["spanish"],"tráfico":["spanish"],"trago":["spanish"],"traje":["spanish"],"tramo":["spanish"],"trance":["spanish"],"trato":["spanish"],"trauma":["spanish"],"trazar":["spanish"],"trébol":["spanish"],"tregua":["spanish"],"treinta":["spanish"],"tren":["spanish"],"trepar":["spanish"],"tres":["spanish"],"tribu":["spanish"],"trigo":["spanish"],"tripa":["spanish"],"triste":["spanish"],"triunfo":["spanish"],"trofeo":["spanish"],"trompa":["spanish"],"tronco":["spanish"],"tropa":["spanish"],"trote":["spanish"],"trozo":["spanish"],"truco":["spanish"],"trueno":["spanish"],"trufa":["spanish"],"tubería":["spanish"],"tubo":["spanish"],"tuerto":["spanish"],"tumba":["spanish"],"tumor":["spanish"],"túnel":["spanish"],"túnica":["spanish"],"turbina":["spanish"],"turismo":["spanish"],"turno":["spanish"],"tutor":["spanish"],"ubicar":["spanish"],"úlcera":["spanish"],"umbral":["spanish"],"unidad":["spanish"],"unir":["spanish"],"universo":["spanish"],"uno":["spanish"],"untar":["spanish"],"uña":["spanish"],"urbano":["spanish"],"urbe":["spanish"],"urgente":["spanish"],"urna":["spanish"],"usar":["spanish"],"usuario":["spanish"],"útil":["spanish"],"utopía":["spanish"],"uva":["spanish"],"vaca":["spanish"],"vacío":["spanish"],"vacuna":["spanish"],"vagar":["spanish"],"vago":["spanish"],"vaina":["spanish"],"vajilla":["spanish"],"vale":["spanish"],"válido":["spanish"],"valle":["spanish"],"valor":["spanish"],"válvula":["spanish"],"vampiro":["spanish"],"vara":["spanish"],"variar":["spanish"],"varón":["spanish"],"vaso":["spanish"],"vecino":["spanish"],"vector":["spanish"],"vehículo":["spanish"],"veinte":["spanish"],"vejez":["spanish"],"vela":["spanish"],"velero":["spanish"],"veloz":["spanish"],"vena":["spanish"],"vencer":["spanish"],"venda":["spanish"],"veneno":["spanish"],"vengar":["spanish"],"venir":["spanish"],"venta":["spanish"],"venus":["spanish"],"ver":["spanish"],"verano":["spanish"],"verbo":["spanish"],"verde":["spanish"],"vereda":["spanish"],"verja":["spanish"],"verso":["spanish"],"verter":["spanish"],"vía":["spanish"],"viaje":["spanish"],"vibrar":["spanish"],"vicio":["spanish"],"víctima":["spanish"],"vida":["spanish"],"vídeo":["spanish"],"vidrio":["spanish"],"viejo":["spanish"],"viernes":["spanish"],"vigor":["spanish"],"vil":["spanish"],"villa":["spanish"],"vinagre":["spanish"],"vino":["spanish"],"viñedo":["spanish"],"violín":["spanish"],"viral":["spanish"],"virgo":["spanish"],"virtud":["spanish"],"visor":["spanish"],"víspera":["spanish"],"vista":["spanish"],"vitamina":["spanish"],"viudo":["spanish"],"vivaz":["spanish"],"vivero":["spanish"],"vivir":["spanish"],"vivo":["spanish"],"volcán":["spanish"],"volumen":["spanish"],"volver":["spanish"],"voraz":["spanish"],"votar":["spanish"],"voto":["spanish"],"voz":["spanish"],"vuelo":["spanish"],"vulgar":["spanish"],"yacer":["spanish"],"yate":["spanish"],"yegua":["spanish"],"yema":["spanish"],"yerno":["spanish"],"yeso":["spanish"],"yodo":["spanish"],"yoga":["spanish"],"yogur":["spanish"],"zafiro":["spanish"],"zanja":["spanish"],"zapato":["spanish"],"zarza":["spanish"],"zona":["spanish"],"zorro":["spanish"],"zumo":["spanish"],"zurdo":["spanish"],"abajur":["turkish"],"abaküs":["turkish"],"abartı":["turkish"],"abdal":["turkish"],"abdest":["turkish"],"abiye":["turkish"],"abluka":["turkish"],"abone":["turkish"],"absorbe":["turkish"],"absürt":["turkish"],"acayip":["turkish"],"acele":["turkish"],"acemi":["turkish"],"açıkgöz":["turkish"],"adalet":["turkish"],"adam":["turkish"],"adezyon":["turkish"],"adisyon":["turkish"],"adliye":["turkish"],"adres":["turkish"],"afacan":["turkish"],"afili":["turkish"],"afiş":["turkish"],"afiyet":["turkish"],"aforizm":["turkish"],"afra":["turkish"],"ağaç":["turkish"],"ağır":["turkish"],"ahbap":["turkish"],"ahkam":["turkish"],"ahlak":["turkish"],"ahtapot":["turkish"],"aidat":["turkish"],"aile":["turkish"],"ajan":["turkish"],"akademi":["turkish"],"akarsu":["turkish"],"akbaş":["turkish"],"akciğer":["turkish"],"akdeniz":["turkish"],"akıbet":["turkish"],"akıl":["turkish"],"akıntı":["turkish"],"akide":["turkish"],"akrep":["turkish"],"akrobasi":["turkish"],"aksiyon":["turkish"],"akşam":["turkish"],"aktif":["turkish"],"aktör":["turkish"],"aktris":["turkish"],"akustik":["turkish"],"alaca":["turkish"],"albüm":["turkish"],"alçak":["turkish"],"aldanma":["turkish"],"aleni":["turkish"],"alet":["turkish"],"alfabe":["turkish"],"algılama":["turkish"],"alıngan":["turkish"],"alkış":["turkish"],"alkol":["turkish"],"alpay":["turkish"],"alperen":["turkish"],"altın":["turkish"],"altüst":["turkish"],"altyapı":["turkish"],"alyuvar":["turkish"],"amade":["turkish"],"amatör":["turkish"],"amazon":["turkish"],"ambalaj":["turkish"],"amblem":["turkish"],"ambulans":["turkish"],"amca":["turkish"],"amel":["turkish"],"amir":["turkish"],"amiyane":["turkish"],"amorti":["turkish"],"ampul":["turkish"],"anadolu":["turkish"],"anahtar":["turkish"],"anakonda":["turkish"],"anaokul":["turkish"],"anapara":["turkish"],"anarşi":["turkish"],"anatomi":["turkish"],"anayasa":["turkish"],"anekdot":["turkish"],"anestezi":["turkish"],"angaje":["turkish"],"anka":["turkish"],"anket":["turkish"],"anlamlı":["turkish"],"anne":["turkish"],"anomali":["turkish"],"anonim":["turkish"],"anten":["turkish"],"antlaşma":["turkish"],"apse":["turkish"],"araba":["turkish"],"aracı":["turkish"],"araf":["turkish"],"arbede":["turkish"],"arda":["turkish"],"arefe":["turkish"],"argo":["turkish"],"argüman":["turkish"],"arkadaş":["turkish"],"armoni":["turkish"],"arsa":["turkish"],"arsız":["turkish"],"artı":["turkish"],"aruz":["turkish"],"asansör":["turkish"],"asayiş":["turkish"],"asgari":["turkish"],"asil":["turkish"],"asker":["turkish"],"askı":["turkish"],"aslan":["turkish"],"asosyal":["turkish"],"astsubay":["turkish"],"asya":["turkish"],"aşçı":["turkish"],"aşırı":["turkish"],"aşure":["turkish"],"atabey":["turkish"],"ataman":["turkish"],"ateş":["turkish"],"atmaca":["turkish"],"atmosfer":["turkish"],"atölye":["turkish"],"avcı":["turkish"],"avdet":["turkish"],"avize":["turkish"],"avlu":["turkish"],"avokado":["turkish"],"avrupa":["turkish"],"avukat":["turkish"],"ayaz":["turkish"],"ayçiçeği":["turkish"],"aydın":["turkish"],"aygıt":["turkish"],"ayna":["turkish"],"ayran":["turkish"],"ayrıntı":["turkish"],"azim":["turkish"],"baca":["turkish"],"bagaj":["turkish"],"bağlantı":["turkish"],"bahadır":["turkish"],"bahçe":["turkish"],"baki":["turkish"],"bakkal":["turkish"],"baklava":["turkish"],"bakteri":["turkish"],"balçık":["turkish"],"balina":["turkish"],"balo":["turkish"],"balta":["turkish"],"bant":["turkish"],"banyo":["turkish"],"bardak":["turkish"],"barış":["turkish"],"başbuğ":["turkish"],"başıboş":["turkish"],"başkan":["turkish"],"başlık":["turkish"],"bavul":["turkish"],"bayındır":["turkish"],"baykuş":["turkish"],"bazlama":["turkish"],"bedel":["turkish"],"begüm":["turkish"],"bekçi":["turkish"],"bekle":["turkish"],"belge":["turkish"],"belki":["turkish"],"bencil":["turkish"],"benek":["turkish"],"bengi":["turkish"],"berjer":["turkish"],"berk":["turkish"],"berrak":["turkish"],"beşik":["turkish"],"beyin":["turkish"],"beyoğlu":["turkish"],"bıçak":["turkish"],"biberiye":["turkish"],"bidon":["turkish"],"bihaber":["turkish"],"bikini":["turkish"],"bilezik":["turkish"],"bilinç":["turkish"],"bilye":["turkish"],"bina":["turkish"],"binbaşı":["turkish"],"binyıl":["turkish"],"bisiklet":["turkish"],"bisküvi":["turkish"],"bitki":["turkish"],"bizzat":["turkish"],"bodrum":["turkish"],"boğaz":["turkish"],"bohça":["turkish"],"boncuk":["turkish"],"bonfile":["turkish"],"borsa":["turkish"],"boru":["turkish"],"bostan":["turkish"],"boşboğaz":["turkish"],"botanik":["turkish"],"boya":["turkish"],"boykot":["turkish"],"boynuz":["turkish"],"bozgun":["turkish"],"bozkır":["turkish"],"bölüm":["turkish"],"börek":["turkish"],"buçuk":["turkish"],"bugün":["turkish"],"buğday":["turkish"],"buhar":["turkish"],"buhran":["turkish"],"bulvar":["turkish"],"buram":["turkish"],"burçak":["turkish"],"burs":["turkish"],"burun":["turkish"],"buzdağı":["turkish"],"buzkıran":["turkish"],"bücür":["turkish"],"büfe":["turkish"],"bülten":["turkish"],"bütçe":["turkish"],"bütün":["turkish"],"büyük":["turkish"],"cacık":["turkish"],"cadı":["turkish"],"cahil":["turkish"],"cambaz":["turkish"],"canhıraş":["turkish"],"casus":["turkish"],"cazibe":["turkish"],"cehalet":["turkish"],"cehennem":["turkish"],"ceket":["turkish"],"cemre":["turkish"],"cenin":["turkish"],"cennet":["turkish"],"cepken":["turkish"],"cerrah":["turkish"],"cesur":["turkish"],"cetvel":["turkish"],"cevher":["turkish"],"ceylan":["turkish"],"cılız":["turkish"],"cıva":["turkish"],"cilt":["turkish"],"cisim":["turkish"],"ciyak":["turkish"],"coğrafya":["turkish"],"cömert":["turkish"],"cumba":["turkish"],"cüzdan":["turkish"],"çabucak":["turkish"],"çadır":["turkish"],"çağdaş":["turkish"],"çağlayan":["turkish"],"çağrı":["turkish"],"çakmak":["turkish"],"çalışkan":["turkish"],"çamaşır":["turkish"],"çapa":["turkish"],"çaput":["turkish"],"çarık":["turkish"],"çarpan":["turkish"],"çarşaf":["turkish"],"çayhane":["turkish"],"çekirdek":["turkish"],"çelebi":["turkish"],"çember":["turkish"],"çenet":["turkish"],"çengel":["turkish"],"çerçeve":["turkish"],"çerez":["turkish"],"çeşit":["turkish"],"çeşme":["turkish"],"çete":["turkish"],"çevre":["turkish"],"çeyiz":["turkish"],"çeyrek":["turkish"],"çığır":["turkish"],"çılgın":["turkish"],"çıngırak":["turkish"],"çift":["turkish"],"çiğdem":["turkish"],"çikolata":["turkish"],"çilek":["turkish"],"çimen":["turkish"],"çivi":["turkish"],"çoban":["turkish"],"çocuk":["turkish"],"çokgen":["turkish"],"çomak":["turkish"],"çorba":["turkish"],"çözelti":["turkish"],"çubuk":["turkish"],"çukur":["turkish"],"çuval":["turkish"],"çürük":["turkish"],"dağbaşı":["turkish"],"dağılım":["turkish"],"daktilo":["turkish"],"daldırış":["turkish"],"dalga":["turkish"],"dalkavuk":["turkish"],"damak":["turkish"],"damıtma":["turkish"],"damla":["turkish"],"dana":["turkish"],"dandik":["turkish"],"danışman":["turkish"],"daniska":["turkish"],"dantel":["turkish"],"dargeçit":["turkish"],"darphane":["turkish"],"davet":["turkish"],"dayı":["turkish"],"defter":["turkish"],"değer":["turkish"],"değirmen":["turkish"],"dehşet":["turkish"],"delgeç":["turkish"],"demir":["turkish"],"deneyim":["turkish"],"denge":["turkish"],"depo":["turkish"],"deprem":["turkish"],"derdest":["turkish"],"dere":["turkish"],"derhal":["turkish"],"derman":["turkish"],"dernek":["turkish"],"derviş":["turkish"],"desen":["turkish"],"destan":["turkish"],"dışarı":["turkish"],"dışbükey":["turkish"],"dijital":["turkish"],"dikbaşlı":["turkish"],"dilekçe":["turkish"],"dimağ":["turkish"],"dinamik":["turkish"],"dindar":["turkish"],"dinleme":["turkish"],"dinozor":["turkish"],"dipçik":["turkish"],"dipnot":["turkish"],"direniş":["turkish"],"dirsek":["turkish"],"disiplin":["turkish"],"divriği":["turkish"],"dizüstü":["turkish"],"dobra":["turkish"],"dodurga":["turkish"],"doğalgaz":["turkish"],"doküman":["turkish"],"dolap":["turkish"],"donanım":["turkish"],"dondurma":["turkish"],"donör":["turkish"],"doruk":["turkish"],"dosdoğru":["turkish"],"dost":["turkish"],"dosya":["turkish"],"dozer":["turkish"],"döküm":["turkish"],"dönence":["turkish"],"dörtyol":["turkish"],"dövme":["turkish"],"dram":["turkish"],"dublaj":["turkish"],"durum":["turkish"],"duvak":["turkish"],"duyarga":["turkish"],"duyma":["turkish"],"duyuru":["turkish"],"düğme":["turkish"],"düğüm":["turkish"],"dükkan":["turkish"],"dünür":["turkish"],"düpedüz":["turkish"],"dürbün":["turkish"],"düşünür":["turkish"],"düzayak":["turkish"],"düzeltme":["turkish"],"ebeveyn":["turkish"],"ebru":["turkish"],"ecel":["turkish"],"ecnebi":["turkish"],"ecza":["turkish"],"edat":["turkish"],"edilgen":["turkish"],"efendi":["turkish"],"efor":["turkish"],"efsane":["turkish"],"egemen":["turkish"],"egzersiz":["turkish"],"eğrelti":["turkish"],"ekarte":["turkish"],"ekip":["turkish"],"eklem":["turkish"],"ekmek":["turkish"],"ekol":["turkish"],"ekonomi":["turkish"],"ekose":["turkish"],"ekran":["turkish"],"ekvator":["turkish"],"elaman":["turkish"],"elastik":["turkish"],"elbet":["turkish"],"elbise":["turkish"],"elçi":["turkish"],"eldiven":["turkish"],"elebaşı":["turkish"],"eleştiri":["turkish"],"elma":["turkish"],"eloğlu":["turkish"],"elveda":["turkish"],"emare":["turkish"],"emekçi":["turkish"],"emisyon":["turkish"],"emniyet":["turkish"],"empati":["turkish"],"emsal":["turkish"],"emzik":["turkish"],"endüstri":["turkish"],"enerji":["turkish"],"engebe":["turkish"],"enişte":["turkish"],"enkaz":["turkish"],"entari":["turkish"],"entegre":["turkish"],"entrika":["turkish"],"enzim":["turkish"],"erdem":["turkish"],"ergen":["turkish"],"erguvan":["turkish"],"erkek":["turkish"],"erozyon":["turkish"],"ertesi":["turkish"],"erzak":["turkish"],"esaret":["turkish"],"esenlik":["turkish"],"eser":["turkish"],"eski":["turkish"],"esnek":["turkish"],"eşarp":["turkish"],"eşofman":["turkish"],"eşraf":["turkish"],"eşya":["turkish"],"eşzaman":["turkish"],"etik":["turkish"],"etken":["turkish"],"etkinlik":["turkish"],"etüt":["turkish"],"evet":["turkish"],"evire":["turkish"],"evrak":["turkish"],"evrim":["turkish"],"eyalet":["turkish"],"eyvah":["turkish"],"ezber":["turkish"],"fabrika":["turkish"],"fanus":["turkish"],"fason":["turkish"],"fasulye":["turkish"],"fatih":["turkish"],"fatura":["turkish"],"fayans":["turkish"],"fayton":["turkish"],"fazıl":["turkish"],"fazilet":["turkish"],"felsefe":["turkish"],"fener":["turkish"],"feribot":["turkish"],"fersah":["turkish"],"fesih":["turkish"],"feveran":["turkish"],"feza":["turkish"],"fıçı":["turkish"],"fıldır":["turkish"],"fındık":["turkish"],"fırça":["turkish"],"fırsat":["turkish"],"fırtına":["turkish"],"fıtık":["turkish"],"fidan":["turkish"],"fidye":["turkish"],"figür":["turkish"],"fihrist":["turkish"],"fikir":["turkish"],"fildişi":["turkish"],"filtre":["turkish"],"fincan":["turkish"],"firuze":["turkish"],"fitil":["turkish"],"fiyaka":["turkish"],"fizik":["turkish"],"flaş":["turkish"],"flüt":["turkish"],"fosil":["turkish"],"fren":["turkish"],"fukara":["turkish"],"futbol":["turkish"],"garabet":["turkish"],"gariban":["turkish"],"garnitür":["turkish"],"gazi":["turkish"],"gece":["turkish"],"gedik":["turkish"],"gelenek":["turkish"],"gelin":["turkish"],"gemi":["turkish"],"genç":["turkish"],"geniş":["turkish"],"geometri":["turkish"],"gerçek":["turkish"],"gevrek":["turkish"],"gezegen":["turkish"],"gezgin":["turkish"],"geziyolu":["turkish"],"gıcık":["turkish"],"gıda":["turkish"],"gıybet":["turkish"],"girdap":["turkish"],"girişim":["turkish"],"gitar":["turkish"],"giyecek":["turkish"],"giysi":["turkish"],"gizem":["turkish"],"gofret":["turkish"],"goril":["turkish"],"göbek":["turkish"],"göçebe":["turkish"],"göğüs":["turkish"],"gökdelen":["turkish"],"gökmen":["turkish"],"gökyüzü":["turkish"],"gölge":["turkish"],"gömlek":["turkish"],"gönül":["turkish"],"görenek":["turkish"],"görkemli":["turkish"],"görsel":["turkish"],"gösteri":["turkish"],"gövde":["turkish"],"gözaltı":["turkish"],"gözcü":["turkish"],"gözdağı":["turkish"],"gözleme":["turkish"],"gözyaşı":["turkish"],"grup":["turkish"],"gurbet":["turkish"],"gusül":["turkish"],"gübre":["turkish"],"güfte":["turkish"],"gümüş":["turkish"],"günaydın":["turkish"],"güncel":["turkish"],"gündüz":["turkish"],"güneş":["turkish"],"günyüzü":["turkish"],"gürbüz":["turkish"],"güvercin":["turkish"],"güzel":["turkish"],"hacamat":["turkish"],"hacim":["turkish"],"hademe":["turkish"],"hafız":["turkish"],"hafriyat":["turkish"],"hafta":["turkish"],"hakan":["turkish"],"hakem":["turkish"],"hakikat":["turkish"],"haksever":["turkish"],"halı":["turkish"],"hançer":["turkish"],"hane":["turkish"],"hapis":["turkish"],"hapşırık":["turkish"],"harf":["turkish"],"haseki":["turkish"],"hasret":["turkish"],"hatun":["turkish"],"havuç":["turkish"],"haylaz":["turkish"],"haysiyet":["turkish"],"hayvan":["turkish"],"hedef":["turkish"],"hemen":["turkish"],"hemfikir":["turkish"],"hendek":["turkish"],"hepsi":["turkish"],"hergele":["turkish"],"herhangi":["turkish"],"hesap":["turkish"],"heyecan":["turkish"],"heykel":["turkish"],"hezimet":["turkish"],"hıçkırık":["turkish"],"hızölçer":["turkish"],"hicviye":["turkish"],"hikaye":["turkish"],"hikmet":["turkish"],"hile":["turkish"],"hisse":["turkish"],"hobi":["turkish"],"hoca":["turkish"],"horlama":["turkish"],"hoşbeş":["turkish"],"hoşgörü":["turkish"],"hoyrat":["turkish"],"hörgüç":["turkish"],"höyük":["turkish"],"hudut":["turkish"],"hukuk":["turkish"],"hunhar":["turkish"],"hurda":["turkish"],"huysuz":["turkish"],"huzur":["turkish"],"hücum":["turkish"],"hükümet":["turkish"],"hünkar":["turkish"],"hüviyet":["turkish"],"ırmak":["turkish"],"ısıölçer":["turkish"],"ısıtıcı":["turkish"],"ıspanak":["turkish"],"ısrar":["turkish"],"ışıldak":["turkish"],"ızdırap":["turkish"],"ızgara":["turkish"],"ibadet":["turkish"],"icat":["turkish"],"içbükey":["turkish"],"içecek":["turkish"],"içgüdü":["turkish"],"içsel":["turkish"],"idman":["turkish"],"iftihar":["turkish"],"iğne":["turkish"],"ihanet":["turkish"],"ihbar":["turkish"],"ihdas":["turkish"],"ihmal":["turkish"],"ihracat":["turkish"],"ihsan":["turkish"],"ikilem":["turkish"],"ikindi":["turkish"],"ikircik":["turkish"],"iklim":["turkish"],"iksir":["turkish"],"iktibas":["turkish"],"ilaç":["turkish"],"ilçe":["turkish"],"ileri":["turkish"],"iletişim":["turkish"],"ilgi":["turkish"],"ilhak":["turkish"],"ilkbahar":["turkish"],"ilkokul":["turkish"],"ilmek":["turkish"],"imkan":["turkish"],"imleç":["turkish"],"imsak":["turkish"],"imtihan":["turkish"],"imza":["turkish"],"ince":["turkish"],"inkar":["turkish"],"inşa":["turkish"],"ipek":["turkish"],"ipucu":["turkish"],"irade":["turkish"],"irfan":["turkish"],"irmik":["turkish"],"isabet":["turkish"],"iskele":["turkish"],"israf":["turkish"],"isyan":["turkish"],"işçi":["turkish"],"işgal":["turkish"],"işgüzar":["turkish"],"işlem":["turkish"],"itibar":["turkish"],"itiraf":["turkish"],"ivedi":["turkish"],"ivme":["turkish"],"iyileşme":["turkish"],"iyimser":["turkish"],"izbandut":["turkish"],"izci":["turkish"],"izdiham":["turkish"],"izin":["turkish"],"jakoben":["turkish"],"jandarma":["turkish"],"jargon":["turkish"],"kabadayı":["turkish"],"kablo":["turkish"],"kabus":["turkish"],"kaçamak":["turkish"],"kadeh":["turkish"],"kadın":["turkish"],"kadraj":["turkish"],"kafa":["turkish"],"kafkas":["turkish"],"kağıt":["turkish"],"kağnı":["turkish"],"kahkaha":["turkish"],"kahraman":["turkish"],"kahvaltı":["turkish"],"kakül":["turkish"],"kaldırım":["turkish"],"kale":["turkish"],"kalibre":["turkish"],"kalkan":["turkish"],"kalpak":["turkish"],"kamış":["turkish"],"kamyon":["turkish"],"kanat":["turkish"],"kandaş":["turkish"],"kanepe":["turkish"],"kanser":["turkish"],"kanun":["turkish"],"kaos":["turkish"],"kapı":["turkish"],"kaplıca":["turkish"],"kaptan":["turkish"],"karanlık":["turkish"],"kardeş":["turkish"],"karga":["turkish"],"karınca":["turkish"],"karmaşa":["turkish"],"karşıt":["turkish"],"kasırga":["turkish"],"kask":["turkish"],"kasvet":["turkish"],"katkı":["turkish"],"katman":["turkish"],"kavram":["turkish"],"kaygan":["turkish"],"kaynakça":["turkish"],"kayyum":["turkish"],"kedi":["turkish"],"kehanet":["turkish"],"kekik":["turkish"],"kelebek":["turkish"],"kenar":["turkish"],"kerkenez":["turkish"],"kerpiç":["turkish"],"kesirli":["turkish"],"kesmece":["turkish"],"kestane":["turkish"],"keşkek":["turkish"],"ketçap":["turkish"],"keyfiyet":["turkish"],"kıble":["turkish"],"kıdemli":["turkish"],"kılavuz":["turkish"],"kılçık":["turkish"],"kılıf":["turkish"],"kıraç":["turkish"],"kırmızı":["turkish"],"kırsal":["turkish"],"kısayol":["turkish"],"kısım":["turkish"],"kıskanç":["turkish"],"kısmet":["turkish"],"kışla":["turkish"],"kıvanç":["turkish"],"kıvılcım":["turkish"],"kıvrık":["turkish"],"kıyafet":["turkish"],"kıymetli":["turkish"],"kızak":["turkish"],"kızılcık":["turkish"],"kibar":["turkish"],"kinaye":["turkish"],"kira":["turkish"],"kiremit":["turkish"],"kirli":["turkish"],"kirpik":["turkish"],"kişisel":["turkish"],"kitap":["turkish"],"koçbaşı":["turkish"],"kodaman":["turkish"],"koğuş":["turkish"],"kokteyl":["turkish"],"kolaycı":["turkish"],"kolbastı":["turkish"],"kolonya":["turkish"],"koltuk":["turkish"],"kolye":["turkish"],"kombine":["turkish"],"komedyen":["turkish"],"komiser":["turkish"],"komposto":["turkish"],"komşu":["turkish"],"komuta":["turkish"],"konak":["turkish"],"konfor":["turkish"],"koni":["turkish"],"konsül":["turkish"],"kopya":["turkish"],"korkusuz":["turkish"],"korna":["turkish"],"korse":["turkish"],"korunak":["turkish"],"korvet":["turkish"],"kostüm":["turkish"],"koşul":["turkish"],"koyu":["turkish"],"kozmik":["turkish"],"köfte":["turkish"],"kökensel":["turkish"],"köprücük":["turkish"],"köpük":["turkish"],"kördüğüm":["turkish"],"körfez":["turkish"],"köstebek":["turkish"],"köşegen":["turkish"],"kötü":["turkish"],"kravat":["turkish"],"kriter":["turkish"],"kuantum":["turkish"],"kudurma":["turkish"],"kuluçka":["turkish"],"kulübe":["turkish"],"kumanya":["turkish"],"kumbara":["turkish"],"kumlu":["turkish"],"kumpir":["turkish"],"kumral":["turkish"],"kundura":["turkish"],"kupa":["turkish"],"kupkuru":["turkish"],"kuramsal":["turkish"],"kurbağa":["turkish"],"kurdele":["turkish"],"kurgu":["turkish"],"kurmay":["turkish"],"kurşun":["turkish"],"kurtuluş":["turkish"],"kurultay":["turkish"],"kurye":["turkish"],"kusursuz":["turkish"],"kuşak":["turkish"],"kuşbaşı":["turkish"],"kuşkulu":["turkish"],"kutlama":["turkish"],"kutsal":["turkish"],"kutup":["turkish"],"kuver":["turkish"],"kuyruk":["turkish"],"kuzey":["turkish"],"kuzgun":["turkish"],"küçük":["turkish"],"külçe":["turkish"],"külfet":["turkish"],"külliye":["turkish"],"kültürel":["turkish"],"kümes":["turkish"],"künefe":["turkish"],"küresel":["turkish"],"kütle":["turkish"],"lahana":["turkish"],"lahmacun":["turkish"],"lamba":["turkish"],"lansman":["turkish"],"lavaş":["turkish"],"layık":["turkish"],"leğen":["turkish"],"levent":["turkish"],"leziz":["turkish"],"lezzet":["turkish"],"lider":["turkish"],"likide":["turkish"],"liman":["turkish"],"liste":["turkish"],"litre":["turkish"],"liyakat":["turkish"],"lodos":["turkish"],"lokanta":["turkish"],"lokman":["turkish"],"lokum":["turkish"],"lunapark":["turkish"],"lütfen":["turkish"],"lüzum":["turkish"],"nokta":["turkish"],"mabet":["turkish"],"macera":["turkish"],"macun":["turkish"],"madalya":["turkish"],"madde":["turkish"],"madem":["turkish"],"mağara":["turkish"],"mağdur":["turkish"],"mağfiret":["turkish"],"mağlup":["turkish"],"mahalle":["turkish"],"mahcup":["turkish"],"mahir":["turkish"],"mahkeme":["turkish"],"mahlas":["turkish"],"mahrum":["turkish"],"mahsul":["turkish"],"makas":["turkish"],"makbuz":["turkish"],"makine":["turkish"],"makro":["turkish"],"maksat":["turkish"],"makul":["turkish"],"maliye":["turkish"],"manav":["turkish"],"mangal":["turkish"],"manidar":["turkish"],"manken":["turkish"],"mantık":["turkish"],"manzara":["turkish"],"mareşal":["turkish"],"margarin":["turkish"],"marifet":["turkish"],"marmelat":["turkish"],"masaüstü":["turkish"],"masmavi":["turkish"],"masraf":["turkish"],"masum":["turkish"],"matah":["turkish"],"materyal":["turkish"],"matrak":["turkish"],"maval":["turkish"],"mavra":["turkish"],"maydanoz":["turkish"],"mayhoş":["turkish"],"maytap":["turkish"],"mazbata":["turkish"],"mazeret":["turkish"],"mazlum":["turkish"],"mazot":["turkish"],"mazur":["turkish"],"meblağ":["turkish"],"mebus":["turkish"],"mecaz":["turkish"],"mecbur":["turkish"],"meclis":["turkish"],"mecmua":["turkish"],"mecnun":["turkish"],"meçhul":["turkish"],"medeni":["turkish"],"mehtap":["turkish"],"mekanik":["turkish"],"melodi":["turkish"],"meltem":["turkish"],"memur":["turkish"],"mendil":["turkish"],"menekşe":["turkish"],"menteşe":["turkish"],"meraklı":["turkish"],"mercek":["turkish"],"merdiven":["turkish"],"merhaba":["turkish"],"merinos":["turkish"],"merkez":["turkish"],"mermi":["turkish"],"mert":["turkish"],"mesafe":["turkish"],"mesele":["turkish"],"mesken":["turkish"],"meslek":["turkish"],"meşale":["turkish"],"meşgul":["turkish"],"meşhur":["turkish"],"metafor":["turkish"],"metin":["turkish"],"metre":["turkish"],"mevcut":["turkish"],"mevkidaş":["turkish"],"meydan":["turkish"],"meyil":["turkish"],"meyve":["turkish"],"meziyet":["turkish"],"mezun":["turkish"],"mıknatıs":["turkish"],"mısra":["turkish"],"mızıka":["turkish"],"miğfer":["turkish"],"mihrak":["turkish"],"miktar":["turkish"],"milat":["turkish"],"milli":["turkish"],"mimar":["turkish"],"minare":["turkish"],"mineral":["turkish"],"minik":["turkish"],"minyon":["turkish"],"mirliva":["turkish"],"misafir":["turkish"],"miskin":["turkish"],"miting":["turkish"],"miyop":["turkish"],"mizah":["turkish"],"mobilya":["turkish"],"monitör":["turkish"],"morötesi":["turkish"],"motive":["turkish"],"mozaik":["turkish"],"muavin":["turkish"],"mucize":["turkish"],"muhafız":["turkish"],"muhteşem":["turkish"],"mukayese":["turkish"],"mumya":["turkish"],"musluk":["turkish"],"muşamba":["turkish"],"mutabık":["turkish"],"mutfak":["turkish"],"mutlu":["turkish"],"muzaffer":["turkish"],"muzdarip":["turkish"],"mübarek":["turkish"],"mücadele":["turkish"],"müdür":["turkish"],"müfredat":["turkish"],"müftü":["turkish"],"mühendis":["turkish"],"mühim":["turkish"],"mühlet":["turkish"],"mükemmel":["turkish"],"mülk":["turkish"],"mümkün":["turkish"],"mümtaz":["turkish"],"müsrif":["turkish"],"müstesna":["turkish"],"müşahit":["turkish"],"müşteri":["turkish"],"mütercim":["turkish"],"müthiş":["turkish"],"müze":["turkish"],"müzik":["turkish"],"nabız":["turkish"],"nadas":["turkish"],"nadir":["turkish"],"nahoş":["turkish"],"nakarat":["turkish"],"nakış":["turkish"],"nalbur":["turkish"],"namlu":["turkish"],"namus":["turkish"],"nankör":["turkish"],"nargile":["turkish"],"narkoz":["turkish"],"nasıl":["turkish"],"nasip":["turkish"],"naylon":["turkish"],"nazar":["turkish"],"nazım":["turkish"],"nazik":["turkish"],"neden":["turkish"],"nefes":["turkish"],"negatif":["turkish"],"neon":["turkish"],"neptün":["turkish"],"nerede":["turkish"],"nesil":["turkish"],"nesnel":["turkish"],"neşeli":["turkish"],"netice":["turkish"],"nevresim":["turkish"],"neyse":["turkish"],"neyzen":["turkish"],"nezaket":["turkish"],"nezih":["turkish"],"nezle":["turkish"],"nicel":["turkish"],"nilüfer":["turkish"],"nimet":["turkish"],"nisan":["turkish"],"nispet":["turkish"],"nitekim":["turkish"],"nizam":["turkish"],"nohut":["turkish"],"noksan":["turkish"],"nostalji":["turkish"],"noter":["turkish"],"nöbet":["turkish"],"numara":["turkish"],"numune":["turkish"],"nutuk":["turkish"],"nüfus":["turkish"],"obabaşı":["turkish"],"obez":["turkish"],"obje":["turkish"],"ocak":["turkish"],"odun":["turkish"],"ofansif":["turkish"],"ofis":["turkish"],"oğlak":["turkish"],"oğuz":["turkish"],"okçu":["turkish"],"oklava":["turkish"],"oksijen":["turkish"],"okul":["turkish"],"okumuş":["turkish"],"okutman":["turkish"],"okuyucu":["turkish"],"okyanus":["turkish"],"olağan":["turkish"],"olanak":["turkish"],"olası":["turkish"],"olay":["turkish"],"olgun":["turkish"],"olimpik":["turkish"],"olumlu":["turkish"],"omlet":["turkish"],"omurga":["turkish"],"onarım":["turkish"],"onursal":["turkish"],"optik":["turkish"],"orantı":["turkish"],"ordu":["turkish"],"organik":["turkish"],"orijin":["turkish"],"orkide":["turkish"],"orman":["turkish"],"orta":["turkish"],"oruç":["turkish"],"otağ":["turkish"],"otantik":["turkish"],"otel":["turkish"],"otoban":["turkish"],"otogar":["turkish"],"otomobil":["turkish"],"otonom":["turkish"],"otopark":["turkish"],"otorite":["turkish"],"otoyol":["turkish"],"oturum":["turkish"],"oyuk":["turkish"],"oyuncak":["turkish"],"ozan":["turkish"],"ödeme":["turkish"],"ödenek":["turkish"],"ödev":["turkish"],"ödül":["turkish"],"ödünç":["turkish"],"öfke":["turkish"],"öğlen":["turkish"],"öğrenci":["turkish"],"öğün":["turkish"],"öğütücü":["turkish"],"öksürük":["turkish"],"ölçme":["turkish"],"ölçü":["turkish"],"ölümsüz":["turkish"],"ömür":["turkish"],"önayak":["turkish"],"öncü":["turkish"],"önder":["turkish"],"önem":["turkish"],"önerge":["turkish"],"öngörü":["turkish"],"önlük":["turkish"],"önsezi":["turkish"],"öpücük":["turkish"],"ördek":["turkish"],"örgü":["turkish"],"örtbas":["turkish"],"örtme":["turkish"],"örtü":["turkish"],"örümcek":["turkish"],"örüntü":["turkish"],"öteberi":["turkish"],"öteki":["turkish"],"övünç":["turkish"],"öykü":["turkish"],"öyleyse":["turkish"],"özçekim":["turkish"],"özdeyiş":["turkish"],"özel":["turkish"],"özenti":["turkish"],"özerk":["turkish"],"özgürlük":["turkish"],"özlem":["turkish"],"özlü":["turkish"],"özne":["turkish"],"özsever":["turkish"],"özümseme":["turkish"],"özür":["turkish"],"özveri":["turkish"],"pabuç":["turkish"],"padişah":["turkish"],"palamut":["turkish"],"palmiye":["turkish"],"palto":["turkish"],"palyaço":["turkish"],"pamuk":["turkish"],"panayır":["turkish"],"pancar":["turkish"],"panik":["turkish"],"panjur":["turkish"],"pankart":["turkish"],"pano":["turkish"],"pansuman":["turkish"],"pantolon":["turkish"],"panzehir":["turkish"],"papatya":["turkish"],"papyon":["turkish"],"paraşüt":["turkish"],"parça":["turkish"],"pardösü":["turkish"],"parfüm":["turkish"],"parıltı":["turkish"],"parkur":["turkish"],"parmak":["turkish"],"parodi":["turkish"],"parsel":["turkish"],"partner":["turkish"],"pasaport":["turkish"],"pasif":["turkish"],"paskalya":["turkish"],"pastırma":["turkish"],"paşa":["turkish"],"patates":["turkish"],"paten":["turkish"],"patika":["turkish"],"patlıcan":["turkish"],"patolog":["turkish"],"payanda":["turkish"],"paydaş":["turkish"],"payidar":["turkish"],"paylaşma":["turkish"],"paytak":["turkish"],"peçete":["turkish"],"peder":["turkish"],"pehlivan":["turkish"],"pekala":["turkish"],"pekmez":["turkish"],"pelerin":["turkish"],"pelikan":["turkish"],"pelüş":["turkish"],"pembe":["turkish"],"pencere":["turkish"],"pense":["turkish"],"perçin":["turkish"],"perde":["turkish"],"pergel":["turkish"],"perişan":["turkish"],"peron":["turkish"],"personel":["turkish"],"perşembe":["turkish"],"peruk":["turkish"],"pervane":["turkish"],"pespaye":["turkish"],"pestil":["turkish"],"peşin":["turkish"],"petek":["turkish"],"petrol":["turkish"],"petunya":["turkish"],"peynir":["turkish"],"peyzaj":["turkish"],"pınar":["turkish"],"pırasa":["turkish"],"pırlanta":["turkish"],"pide":["turkish"],"pikap":["turkish"],"pilav":["turkish"],"piliç":["turkish"],"pipet":["turkish"],"pipo":["turkish"],"piramit":["turkish"],"pirinç":["turkish"],"pirzola":["turkish"],"pist":["turkish"],"pişik":["turkish"],"pişman":["turkish"],"piyasa":["turkish"],"piyes":["turkish"],"plaj":["turkish"],"plaket":["turkish"],"planlama":["turkish"],"platform":["turkish"],"plazma":["turkish"],"podyum":["turkish"],"poğaça":["turkish"],"polat":["turkish"],"politika":["turkish"],"popüler":["turkish"],"porselen":["turkish"],"portakal":["turkish"],"poster":["turkish"],"poşet":["turkish"],"poyraz":["turkish"],"pozitif":["turkish"],"pranga":["turkish"],"pratik":["turkish"],"prenses":["turkish"],"prim":["turkish"],"profil":["turkish"],"proje":["turkish"],"protokol":["turkish"],"puan":["turkish"],"pudra":["turkish"],"pusula":["turkish"],"püre":["turkish"],"pürüz":["turkish"],"püstül":["turkish"],"püsür":["turkish"],"racon":["turkish"],"radikal":["turkish"],"radyo":["turkish"],"rafadan":["turkish"],"rafine":["turkish"],"rağbet":["turkish"],"rahat":["turkish"],"rahle":["turkish"],"rakam":["turkish"],"raket":["turkish"],"rakip":["turkish"],"rakun":["turkish"],"ralli":["turkish"],"randevu":["turkish"],"ranza":["turkish"],"rapor":["turkish"],"rastgele":["turkish"],"rasyonel":["turkish"],"razı":["turkish"],"realite":["turkish"],"reçine":["turkish"],"refah":["turkish"],"referans":["turkish"],"refik":["turkish"],"rehber":["turkish"],"rehin":["turkish"],"reis":["turkish"],"rekabet":["turkish"],"reklam":["turkish"],"rekor":["turkish"],"rektör":["turkish"],"renk":["turkish"],"resim":["turkish"],"resmen":["turkish"],"restoran":["turkish"],"retorik":["turkish"],"revaç":["turkish"],"reyon":["turkish"],"rezalet":["turkish"],"rezerv":["turkish"],"rezil":["turkish"],"rıhtım":["turkish"],"rıza":["turkish"],"ritim":["turkish"],"ritüel":["turkish"],"rivayet":["turkish"],"roman":["turkish"],"rozet":["turkish"],"röportaj":["turkish"],"rötar":["turkish"],"ruble":["turkish"],"ruhban":["turkish"],"ruhsat":["turkish"],"rulet":["turkish"],"runik":["turkish"],"rutin":["turkish"],"rutubet":["turkish"],"rüşvet":["turkish"],"rütbe":["turkish"],"rüya":["turkish"],"rüzgar":["turkish"],"sabah":["turkish"],"sabıka":["turkish"],"sabit":["turkish"],"sabun":["turkish"],"saçma":["turkish"],"sade":["turkish"],"sadık":["turkish"],"safahat":["turkish"],"safdil":["turkish"],"safkan":["turkish"],"sağanak":["turkish"],"sağduyu":["turkish"],"sağlam":["turkish"],"saha":["turkish"],"sahiden":["turkish"],"sahne":["turkish"],"sakal":["turkish"],"sakız":["turkish"],"sakin":["turkish"],"saklama":["turkish"],"saksağan":["turkish"],"salamura":["turkish"],"salça":["turkish"],"salgı":["turkish"],"salınım":["turkish"],"salkım":["turkish"],"saltanat":["turkish"],"sanatçı":["turkish"],"sancak":["turkish"],"sandalye":["turkish"],"saniye":["turkish"],"saplantı":["turkish"],"sapsız":["turkish"],"saray":["turkish"],"sarışın":["turkish"],"sarkık":["turkish"],"sarmaşık":["turkish"],"satır":["turkish"],"savaşım":["turkish"],"savunma":["turkish"],"saydam":["turkish"],"sayfa":["turkish"],"saygın":["turkish"],"sayısal":["turkish"],"sebep":["turkish"],"seçenek":["turkish"],"seçim":["turkish"],"seçkin":["turkish"],"seçmen":["turkish"],"sedir":["turkish"],"sedye":["turkish"],"sefer":["turkish"],"sehpa":["turkish"],"sekizgen":["turkish"],"selektör":["turkish"],"selvi":["turkish"],"semavi":["turkish"],"sembol":["turkish"],"seminer":["turkish"],"senaryo":["turkish"],"sendika":["turkish"],"senkron":["turkish"],"sensör":["turkish"],"sentez":["turkish"],"sepet":["turkish"],"seramik":["turkish"],"serbest":["turkish"],"serdar":["turkish"],"seremoni":["turkish"],"sergi":["turkish"],"serhat":["turkish"],"serin":["turkish"],"sermaye":["turkish"],"serpuş":["turkish"],"sersem":["turkish"],"serüven":["turkish"],"sesli":["turkish"],"sesteş":["turkish"],"sevap":["turkish"],"seviye":["turkish"],"seyahat":["turkish"],"seyirci":["turkish"],"sezon":["turkish"],"sıcak":["turkish"],"sıfat":["turkish"],"sıhhi":["turkish"],"sınanma":["turkish"],"sınır":["turkish"],"sıradan":["turkish"],"sırdaş":["turkish"],"sırma":["turkish"],"sırtüstü":["turkish"],"sızgıt":["turkish"],"siftah":["turkish"],"sigorta":["turkish"],"sihirbaz":["turkish"],"silah":["turkish"],"silecek":["turkish"],"silindir":["turkish"],"simetri":["turkish"],"simge":["turkish"],"simit":["turkish"],"sincap":["turkish"],"sindirim":["turkish"],"sinema":["turkish"],"sinirli":["turkish"],"sipariş":["turkish"],"sirke":["turkish"],"siroz":["turkish"],"sistem":["turkish"],"sivilce":["turkish"],"siyasi":["turkish"],"soba":["turkish"],"sofra":["turkish"],"soğuk":["turkish"],"sohbet":["turkish"],"sokak":["turkish"],"solfej":["turkish"],"solunum":["turkish"],"somut":["turkish"],"sonbahar":["turkish"],"sonraki":["turkish"],"sonsuz":["turkish"],"sorunsuz":["turkish"],"sosyete":["turkish"],"soyağacı":["turkish"],"soydaş":["turkish"],"soygun":["turkish"],"soytarı":["turkish"],"söğüş":["turkish"],"sömürge":["turkish"],"sönük":["turkish"],"söylem":["turkish"],"sözcük":["turkish"],"sözde":["turkish"],"spatula":["turkish"],"spektrum":["turkish"],"spiker":["turkish"],"sporcu":["turkish"],"sprey":["turkish"],"stabil":["turkish"],"statü":["turkish"],"stok":["turkish"],"stopaj":["turkish"],"strateji":["turkish"],"subay":["turkish"],"sucuk":["turkish"],"suçüstü":["turkish"],"suhulet":["turkish"],"sulama":["turkish"],"sungur":["turkish"],"sunucu":["turkish"],"surat":["turkish"],"susam":["turkish"],"suskun":["turkish"],"sükse":["turkish"],"sükut":["turkish"],"sülale":["turkish"],"sünger":["turkish"],"süpürge":["turkish"],"sürahi":["turkish"],"süreç":["turkish"],"sürgün":["turkish"],"sürüm":["turkish"],"süsleme":["turkish"],"sütanne":["turkish"],"sütlaç":["turkish"],"sütun":["turkish"],"süvari":["turkish"],"şahane":["turkish"],"şahbaz":["turkish"],"şahit":["turkish"],"şahsiyet":["turkish"],"şakıma":["turkish"],"şaklaban":["turkish"],"şakrak":["turkish"],"şamar":["turkish"],"şampiyon":["turkish"],"şanslı":["turkish"],"şantiye":["turkish"],"şapka":["turkish"],"şarkıcı":["turkish"],"şartname":["turkish"],"şaşırma":["turkish"],"şaşkın":["turkish"],"şatafat":["turkish"],"şayet":["turkish"],"şebeke":["turkish"],"şefkat":["turkish"],"şeftali":["turkish"],"şehir":["turkish"],"şehvet":["turkish"],"şeker":["turkish"],"şekil":["turkish"],"şelale":["turkish"],"şema":["turkish"],"şemsiye":["turkish"],"şerbet":["turkish"],"şeref":["turkish"],"şerit":["turkish"],"şımarık":["turkish"],"şıpıdık":["turkish"],"şifre":["turkish"],"şimdi":["turkish"],"şimşek":["turkish"],"şipşak":["turkish"],"şirin":["turkish"],"şişe":["turkish"],"şişirme":["turkish"],"şofben":["turkish"],"şöhret":["turkish"],"şölen":["turkish"],"şüphe":["turkish"],"tabaka":["turkish"],"tabure":["turkish"],"tadilat":["turkish"],"taharet":["turkish"],"tahıl":["turkish"],"tahkim":["turkish"],"tahlil":["turkish"],"tahmin":["turkish"],"tahrifat":["turkish"],"tahsilat":["turkish"],"tahta":["turkish"],"taklit":["turkish"],"takoz":["turkish"],"taksici":["turkish"],"taktik":["turkish"],"takvim":["turkish"],"talebe":["turkish"],"talip":["turkish"],"tamamen":["turkish"],"tamirci":["turkish"],"tamtakır":["turkish"],"tandır":["turkish"],"tanecik":["turkish"],"tanıtım":["turkish"],"tanrı":["turkish"],"tansiyon":["turkish"],"tapan":["turkish"],"tapınak":["turkish"],"taptaze":["turkish"],"tapu":["turkish"],"tarafgir":["turkish"],"tarhana":["turkish"],"tarım":["turkish"],"tarih":["turkish"],"tarla":["turkish"],"tartak":["turkish"],"tarumar":["turkish"],"tasarım":["turkish"],"tasdik":["turkish"],"taslak":["turkish"],"tastamam":["turkish"],"taşeron":["turkish"],"taşınmaz":["turkish"],"taşra":["turkish"],"tatava":["turkish"],"tatbikat":["turkish"],"tatil":["turkish"],"tatlı":["turkish"],"tavsiye":["turkish"],"tavşan":["turkish"],"tavuk":["turkish"],"taze":["turkish"],"taziye":["turkish"],"tazminat":["turkish"],"tebeşir":["turkish"],"tebrik":["turkish"],"tecrübe":["turkish"],"teçhizat":["turkish"],"tedarik":["turkish"],"tedbir":["turkish"],"teftiş":["turkish"],"teğet":["turkish"],"teğmen":["turkish"],"tehdit":["turkish"],"tehlike":["turkish"],"tekdüze":["turkish"],"tekerlek":["turkish"],"tekme":["turkish"],"teknik":["turkish"],"tekrar":["turkish"],"telef":["turkish"],"telsiz":["turkish"],"telve":["turkish"],"temas":["turkish"],"tembel":["turkish"],"temiz":["turkish"],"temkin":["turkish"],"temsilci":["turkish"],"tendon":["turkish"],"teneke":["turkish"],"tenha":["turkish"],"tenkit":["turkish"],"tepegöz":["turkish"],"tepki":["turkish"],"terazi":["turkish"],"terbiye":["turkish"],"tercih":["turkish"],"tereyağı":["turkish"],"terfi":["turkish"],"terim":["turkish"],"terminal":["turkish"],"tersane":["turkish"],"tertip":["turkish"],"tesadüf":["turkish"],"tescil":["turkish"],"tesir":["turkish"],"teslimat":["turkish"],"tespit":["turkish"],"testere":["turkish"],"teşekkür":["turkish"],"teşhir":["turkish"],"teşrif":["turkish"],"teşvik":["turkish"],"teyze":["turkish"],"tezahür":["turkish"],"tezgah":["turkish"],"tıbbi":["turkish"],"tıkaç":["turkish"],"tıkışık":["turkish"],"tıknaz":["turkish"],"tılsım":["turkish"],"tıpkı":["turkish"],"tıraş":["turkish"],"tırışka":["turkish"],"tırmanış":["turkish"],"tırnak":["turkish"],"tırpan":["turkish"],"tıslama":["turkish"],"ticaret":["turkish"],"tilki":["turkish"],"tiryaki":["turkish"],"titreşim":["turkish"],"tohum":["turkish"],"tokat":["turkish"],"tolere":["turkish"],"tombak":["turkish"],"tomurcuk":["turkish"],"topaç":["turkish"],"toplum":["turkish"],"toprak":["turkish"],"toptan":["turkish"],"toraman":["turkish"],"torpido":["turkish"],"tortu":["turkish"],"tosbağa":["turkish"],"toynak":["turkish"],"tören":["turkish"],"trafik":["turkish"],"trajedi":["turkish"],"tramvay":["turkish"],"tribün":["turkish"],"triko":["turkish"],"tugay":["turkish"],"tuğla":["turkish"],"tuğrul":["turkish"],"tuhaf":["turkish"],"tulumba":["turkish"],"tunç":["turkish"],"turan":["turkish"],"turkuaz":["turkish"],"turnusol":["turkish"],"turşu":["turkish"],"turuncu":["turkish"],"tutanak":["turkish"],"tutkal":["turkish"],"tutsak":["turkish"],"tutum":["turkish"],"tuyuğ":["turkish"],"tuzlu":["turkish"],"tüccar":["turkish"],"tüfek":["turkish"],"tükenmez":["turkish"],"tülbent":["turkish"],"tümleç":["turkish"],"tünel":["turkish"],"türbin":["turkish"],"türev":["turkish"],"türk":["turkish"],"tüzük":["turkish"],"ucube":["turkish"],"ucuz":["turkish"],"uçak":["turkish"],"uçurtma":["turkish"],"ufuk":["turkish"],"uğrak":["turkish"],"uğur":["turkish"],"ukala":["turkish"],"ulaşım":["turkish"],"ulema":["turkish"],"ulus":["turkish"],"ulvi":["turkish"],"umursama":["turkish"],"umut":["turkish"],"unutkan":["turkish"],"uslu":["turkish"],"ustabaşı":["turkish"],"ustura":["turkish"],"usul":["turkish"],"utangaç":["turkish"],"uyanık":["turkish"],"uyarı":["turkish"],"uydu":["turkish"],"uygar":["turkish"],"uygulama":["turkish"],"uykusuz":["turkish"],"uysal":["turkish"],"uyuşma":["turkish"],"uzantı":["turkish"],"uzay":["turkish"],"uzgören":["turkish"],"uzlaşma":["turkish"],"uzman":["turkish"],"uzun":["turkish"],"ücra":["turkish"],"ücret":["turkish"],"üçbudak":["turkish"],"üçgen":["turkish"],"üçkağıt":["turkish"],"üçleme":["turkish"],"üfürük":["turkish"],"ülke":["turkish"],"ümit":["turkish"],"üniforma":["turkish"],"ünite":["turkish"],"ünlem":["turkish"],"üretken":["turkish"],"ürün":["turkish"],"üslup":["turkish"],"üstel":["turkish"],"üstün":["turkish"],"üşengeç":["turkish"],"üşüme":["turkish"],"ütopya":["turkish"],"üvey":["turkish"],"üzengi":["turkish"],"üzgün":["turkish"],"üzüm":["turkish"],"vaka":["turkish"],"vakfiye":["turkish"],"vakıf":["turkish"],"vakit":["turkish"],"vakum":["turkish"],"vapur":["turkish"],"varil":["turkish"],"varlık":["turkish"],"varsayım":["turkish"],"varyemez":["turkish"],"vasıta":["turkish"],"vasiyet":["turkish"],"vatandaş":["turkish"],"vazife":["turkish"],"vazo":["turkish"],"veciz":["turkish"],"vefa":["turkish"],"vehim":["turkish"],"veliaht":["turkish"],"veresiye":["turkish"],"verimli":["turkish"],"verkaç":["turkish"],"vernik":["turkish"],"vertigo":["turkish"],"vesait":["turkish"],"vesika":["turkish"],"vestiyer":["turkish"],"veznedar":["turkish"],"vicdan":["turkish"],"vilayet":["turkish"],"virane":["turkish"],"virgül":["turkish"],"vişne":["turkish"],"vites":["turkish"],"vokal":["turkish"],"volkan":["turkish"],"vurma":["turkish"],"vurucu":["turkish"],"vücut":["turkish"],"yabancı":["turkish"],"yabgu":["turkish"],"yağış":["turkish"],"yağlı":["turkish"],"yağmur":["turkish"],"yakamoz":["turkish"],"yakın":["turkish"],"yaklaşık":["turkish"],"yalçın":["turkish"],"yalıtım":["turkish"],"yaman":["turkish"],"yanardağ":["turkish"],"yangın":["turkish"],"yanıt":["turkish"],"yankı":["turkish"],"yanlış":["turkish"],"yansıma":["turkish"],"yapay":["turkish"],"yapboz":["turkish"],"yapımcı":["turkish"],"yaprak":["turkish"],"yaratık":["turkish"],"yarbay":["turkish"],"yardım":["turkish"],"yargıç":["turkish"],"yarıçap":["turkish"],"yasemin":["turkish"],"yastık":["turkish"],"yaşam":["turkish"],"yatak":["turkish"],"yatırım":["turkish"],"yavru":["turkish"],"yaygara":["turkish"],"yayıncı":["turkish"],"yayla":["turkish"],"yazılım":["turkish"],"yekpare":["turkish"],"yekvücut":["turkish"],"yelkovan":["turkish"],"yelpaze":["turkish"],"yemek":["turkish"],"yemiş":["turkish"],"yengeç":["turkish"],"yeniçeri":["turkish"],"yeraltı":["turkish"],"yerküre":["turkish"],"yerleşke":["turkish"],"yeryüzü":["turkish"],"yeşil":["turkish"],"yetenek":["turkish"],"yetkili":["turkish"],"yığınak":["turkish"],"yıkama":["turkish"],"yılbaşı":["turkish"],"yıldırım":["turkish"],"yılkı":["turkish"],"yılmaz":["turkish"],"yırtıcı":["turkish"],"yiğit":["turkish"],"yoğurt":["turkish"],"yokuş":["turkish"],"yolcu":["turkish"],"yoldaş":["turkish"],"yolgeçen":["turkish"],"yolkesen":["turkish"],"yolüstü":["turkish"],"yordam":["turkish"],"yorgan":["turkish"],"yorumcu":["turkish"],"yosun":["turkish"],"yöndeş":["turkish"],"yönetim":["turkish"],"yönlü":["turkish"],"yöntem":["turkish"],"yöresel":["turkish"],"yörünge":["turkish"],"yufka":["turkish"],"yukarı":["turkish"],"yumruk":["turkish"],"yumurta":["turkish"],"yuvarlak":["turkish"],"yücelme":["turkish"],"yükçeker":["turkish"],"yüklem":["turkish"],"yüksek":["turkish"],"yürek":["turkish"],"yürütme":["turkish"],"yüzde":["turkish"],"yüzeysel":["turkish"],"yüzgeç":["turkish"],"yüzüstü":["turkish"],"yüzyıl":["turkish"],"zabıta":["turkish"],"zafer":["turkish"],"zahmet":["turkish"],"zambak":["turkish"],"zaptiye":["turkish"],"zarafet":["turkish"],"zaruret":["turkish"],"zeka":["turkish"],"zekice":["turkish"],"zemberek":["turkish"],"zemin":["turkish"],"zencefil":["turkish"],"zeplin":["turkish"],"zeytin":["turkish"],"zıbın":["turkish"],"zılgıt":["turkish"],"zımbırtı":["turkish"],"zımpara":["turkish"],"zıpkın":["turkish"],"zigon":["turkish"],"zihinsel":["turkish"],"zihniyet":["turkish"],"zincir":["turkish"],"zindan":["turkish"],"zirzop":["turkish"],"ziyaret":["turkish"],"ziynet":["turkish"],"zoraki":["turkish"],"zorlu":["turkish"],"zorunlu":["turkish"],"züğürt":["turkish"],"zümre":["turkish"]}["abacate","abaixo","abalar","abater","abduzir","abelha","aberto","abismo","abotoar","abranger","abreviar","abrigar","abrupto","absinto","absoluto","absurdo","abutre","acabado","acalmar","acampar","acanhar","acaso","aceitar","acelerar","acenar","acervo","acessar","acetona","achatar","acidez","acima","acionado","acirrar","aclamar","aclive","acolhida","acomodar","acoplar","acordar","acumular","acusador","adaptar","adega","adentro","adepto","adequar","aderente","adesivo","adeus","adiante","aditivo","adjetivo","adjunto","admirar","adorar","adquirir","adubo","adverso","advogado","aeronave","afastar","aferir","afetivo","afinador","afivelar","aflito","afluente","afrontar","agachar","agarrar","agasalho","agenciar","agilizar","agiota","agitado","agora","agradar","agreste","agrupar","aguardar","agulha","ajoelhar","ajudar","ajustar","alameda","alarme","alastrar","alavanca","albergue","albino","alcatra","aldeia","alecrim","alegria","alertar","alface","alfinete","algum","alheio","aliar","alicate","alienar","alinhar","aliviar","almofada","alocar","alpiste","alterar","altitude","alucinar","alugar","aluno","alusivo","alvo","amaciar","amador","amarelo","amassar","ambas","ambiente","ameixa","amenizar","amido","amistoso","amizade","amolador","amontoar","amoroso","amostra","amparar","ampliar","ampola","anagrama","analisar","anarquia","anatomia","andaime","anel","anexo","angular","animar","anjo","anomalia","anotado","ansioso","anterior","anuidade","anunciar","anzol","apagador","apalpar","apanhado","apego","apelido","apertada","apesar","apetite","apito","aplauso","aplicada","apoio","apontar","aposta","aprendiz","aprovar","aquecer","arame","aranha","arara","arcada","ardente","areia","arejar","arenito","aresta","argiloso","argola","arma","arquivo","arraial","arrebate","arriscar","arroba","arrumar","arsenal","arterial","artigo","arvoredo","asfaltar","asilado","aspirar","assador","assinar","assoalho","assunto","astral","atacado","atadura","atalho","atarefar","atear","atender","aterro","ateu","atingir","atirador","ativo","atoleiro","atracar","atrevido","atriz","atual","atum","auditor","aumentar","aura","aurora","autismo","autoria","autuar","avaliar","avante","avaria","avental","avesso","aviador","avisar","avulso","axila","azarar","azedo","azeite","azulejo","babar","babosa","bacalhau","bacharel","bacia","bagagem","baiano","bailar","baioneta","bairro","baixista","bajular","baleia","baliza","balsa","banal","bandeira","banho","banir","banquete","barato","barbado","baronesa","barraca","barulho","baseado","bastante","batata","batedor","batida","batom","batucar","baunilha","beber","beijo","beirada","beisebol","beldade","beleza","belga","beliscar","bendito","bengala","benzer","berimbau","berlinda","berro","besouro","bexiga","bezerro","bico","bicudo","bienal","bifocal","bifurcar","bigorna","bilhete","bimestre","bimotor","biologia","biombo","biosfera","bipolar","birrento","biscoito","bisneto","bispo","bissexto","bitola","bizarro","blindado","bloco","bloquear","boato","bobagem","bocado","bocejo","bochecha","boicotar","bolada","boletim","bolha","bolo","bombeiro","bonde","boneco","bonita","borbulha","borda","boreal","borracha","bovino","boxeador","branco","brasa","braveza","breu","briga","brilho","brincar","broa","brochura","bronzear","broto","bruxo","bucha","budismo","bufar","bule","buraco","busca","busto","buzina","cabana","cabelo","cabide","cabo","cabrito","cacau","cacetada","cachorro","cacique","cadastro","cadeado","cafezal","caiaque","caipira","caixote","cajado","caju","calafrio","calcular","caldeira","calibrar","calmante","calota","camada","cambista","camisa","camomila","campanha","camuflar","canavial","cancelar","caneta","canguru","canhoto","canivete","canoa","cansado","cantar","canudo","capacho","capela","capinar","capotar","capricho","captador","capuz","caracol","carbono","cardeal","careca","carimbar","carneiro","carpete","carreira","cartaz","carvalho","casaco","casca","casebre","castelo","casulo","catarata","cativar","caule","causador","cautelar","cavalo","caverna","cebola","cedilha","cegonha","celebrar","celular","cenoura","censo","centeio","cercar","cerrado","certeiro","cerveja","cetim","cevada","chacota","chaleira","chamado","chapada","charme","chatice","chave","chefe","chegada","cheiro","cheque","chicote","chifre","chinelo","chocalho","chover","chumbo","chutar","chuva","cicatriz","ciclone","cidade","cidreira","ciente","cigana","cimento","cinto","cinza","ciranda","circuito","cirurgia","citar","clareza","clero","clicar","clone","clube","coado","coagir","cobaia","cobertor","cobrar","cocada","coelho","coentro","coeso","cogumelo","coibir","coifa","coiote","colar","coleira","colher","colidir","colmeia","colono","coluna","comando","combinar","comentar","comitiva","comover","complexo","comum","concha","condor","conectar","confuso","congelar","conhecer","conjugar","consumir","contrato","convite","cooperar","copeiro","copiador","copo","coquetel","coragem","cordial","corneta","coronha","corporal","correio","cortejo","coruja","corvo","cosseno","costela","cotonete","couro","couve","covil","cozinha","cratera","cravo","creche","credor","creme","crer","crespo","criada","criminal","crioulo","crise","criticar","crosta","crua","cruzeiro","cubano","cueca","cuidado","cujo","culatra","culminar","culpar","cultura","cumprir","cunhado","cupido","curativo","curral","cursar","curto","cuspir","custear","cutelo","damasco","datar","debater","debitar","deboche","debulhar","decalque","decimal","declive","decote","decretar","dedal","dedicado","deduzir","defesa","defumar","degelo","degrau","degustar","deitado","deixar","delator","delegado","delinear","delonga","demanda","demitir","demolido","dentista","depenado","depilar","depois","depressa","depurar","deriva","derramar","desafio","desbotar","descanso","desenho","desfiado","desgaste","desigual","deslize","desmamar","desova","despesa","destaque","desviar","detalhar","detentor","detonar","detrito","deusa","dever","devido","devotado","dezena","diagrama","dialeto","didata","difuso","digitar","dilatado","diluente","diminuir","dinastia","dinheiro","diocese","direto","discreta","disfarce","disparo","disquete","dissipar","distante","ditador","diurno","diverso","divisor","divulgar","dizer","dobrador","dolorido","domador","dominado","donativo","donzela","dormente","dorsal","dosagem","dourado","doutor","drenagem","drible","drogaria","duelar","duende","dueto","duplo","duquesa","durante","duvidoso","eclodir","ecoar","ecologia","edificar","edital","educado","efeito","efetivar","ejetar","elaborar","eleger","eleitor","elenco","elevador","eliminar","elogiar","embargo","embolado","embrulho","embutido","emenda","emergir","emissor","empatia","empenho","empinado","empolgar","emprego","empurrar","emulador","encaixe","encenado","enchente","encontro","endeusar","endossar","enfaixar","enfeite","enfim","engajado","engenho","englobar","engomado","engraxar","enguia","enjoar","enlatar","enquanto","enraizar","enrolado","enrugar","ensaio","enseada","ensino","ensopado","entanto","enteado","entidade","entortar","entrada","entulho","envergar","enviado","envolver","enxame","enxerto","enxofre","enxuto","epiderme","equipar","ereto","erguido","errata","erva","ervilha","esbanjar","esbelto","escama","escola","escrita","escuta","esfinge","esfolar","esfregar","esfumado","esgrima","esmalte","espanto","espelho","espiga","esponja","espreita","espumar","esquerda","estaca","esteira","esticar","estofado","estrela","estudo","esvaziar","etanol","etiqueta","euforia","europeu","evacuar","evaporar","evasivo","eventual","evidente","evoluir","exagero","exalar","examinar","exato","exausto","excesso","excitar","exclamar","executar","exemplo","exibir","exigente","exonerar","expandir","expelir","expirar","explanar","exposto","expresso","expulsar","externo","extinto","extrato","fabricar","fabuloso","faceta","facial","fada","fadiga","faixa","falar","falta","familiar","fandango","fanfarra","fantoche","fardado","farelo","farinha","farofa","farpa","fartura","fatia","fator","favorita","faxina","fazenda","fechado","feijoada","feirante","felino","feminino","fenda","feno","fera","feriado","ferrugem","ferver","festejar","fetal","feudal","fiapo","fibrose","ficar","ficheiro","figurado","fileira","filho","filme","filtrar","firmeza","fisgada","fissura","fita","fivela","fixador","fixo","flacidez","flamingo","flanela","flechada","flora","flutuar","fluxo","focal","focinho","fofocar","fogo","foguete","foice","folgado","folheto","forjar","formiga","forno","forte","fosco","fossa","fragata","fralda","frango","frasco","fraterno","freira","frente","fretar","frieza","friso","fritura","fronha","frustrar","fruteira","fugir","fulano","fuligem","fundar","fungo","funil","furador","furioso","futebol","gabarito","gabinete","gado","gaiato","gaiola","gaivota","galega","galho","galinha","galocha","ganhar","garagem","garfo","gargalo","garimpo","garoupa","garrafa","gasoduto","gasto","gata","gatilho","gaveta","gazela","gelado","geleia","gelo","gemada","gemer","gemido","generoso","gengiva","genial","genoma","genro","geologia","gerador","germinar","gesso","gestor","ginasta","gincana","gingado","girafa","girino","glacial","glicose","global","glorioso","goela","goiaba","golfe","golpear","gordura","gorjeta","gorro","gostoso","goteira","governar","gracejo","gradual","grafite","gralha","grampo","granada","gratuito","graveto","graxa","grego","grelhar","greve","grilo","grisalho","gritaria","grosso","grotesco","grudado","grunhido","gruta","guache","guarani","guaxinim","guerrear","guiar","guincho","guisado","gula","guloso","guru","habitar","harmonia","haste","haver","hectare","herdar","heresia","hesitar","hiato","hibernar","hidratar","hiena","hino","hipismo","hipnose","hipoteca","hoje","holofote","homem","honesto","honrado","hormonal","hospedar","humorado","iate","ideia","idoso","ignorado","igreja","iguana","ileso","ilha","iludido","iluminar","ilustrar","imagem","imediato","imenso","imersivo","iminente","imitador","imortal","impacto","impedir","implante","impor","imprensa","impune","imunizar","inalador","inapto","inativo","incenso","inchar","incidir","incluir","incolor","indeciso","indireto","indutor","ineficaz","inerente","infantil","infestar","infinito","inflamar","informal","infrator","ingerir","inibido","inicial","inimigo","injetar","inocente","inodoro","inovador","inox","inquieto","inscrito","inseto","insistir","inspetor","instalar","insulto","intacto","integral","intimar","intocado","intriga","invasor","inverno","invicto","invocar","iogurte","iraniano","ironizar","irreal","irritado","isca","isento","isolado","isqueiro","italiano","janeiro","jangada","janta","jararaca","jardim","jarro","jasmim","jato","javali","jazida","jejum","joaninha","joelhada","jogador","joia","jornal","jorrar","jovem","juba","judeu","judoca","juiz","julgador","julho","jurado","jurista","juro","justa","labareda","laboral","lacre","lactante","ladrilho","lagarta","lagoa","laje","lamber","lamentar","laminar","lampejo","lanche","lapidar","lapso","laranja","lareira","largura","lasanha","lastro","lateral","latido","lavanda","lavoura","lavrador","laxante","lazer","lealdade","lebre","legado","legendar","legista","leigo","leiloar","leitura","lembrete","leme","lenhador","lentilha","leoa","lesma","leste","letivo","letreiro","levar","leveza","levitar","liberal","libido","liderar","ligar","ligeiro","limitar","limoeiro","limpador","linda","linear","linhagem","liquidez","listagem","lisura","litoral","livro","lixa","lixeira","locador","locutor","lojista","lombo","lona","longe","lontra","lorde","lotado","loteria","loucura","lousa","louvar","luar","lucidez","lucro","luneta","lustre","lutador","luva","macaco","macete","machado","macio","madeira","madrinha","magnata","magreza","maior","mais","malandro","malha","malote","maluco","mamilo","mamoeiro","mamute","manada","mancha","mandato","manequim","manhoso","manivela","manobrar","mansa","manter","manusear","mapeado","maquinar","marcador","maresia","marfim","margem","marinho","marmita","maroto","marquise","marreco","martelo","marujo","mascote","masmorra","massagem","mastigar","matagal","materno","matinal","matutar","maxilar","medalha","medida","medusa","megafone","meiga","melancia","melhor","membro","memorial","menino","menos","mensagem","mental","merecer","mergulho","mesada","mesclar","mesmo","mesquita","mestre","metade","meteoro","metragem","mexer","mexicano","micro","migalha","migrar","milagre","milenar","milhar","mimado","minerar","minhoca","ministro","minoria","miolo","mirante","mirtilo","misturar","mocidade","moderno","modular","moeda","moer","moinho","moita","moldura","moleza","molho","molinete","molusco","montanha","moqueca","morango","morcego","mordomo","morena","mosaico","mosquete","mostarda","motel","motim","moto","motriz","muda","muito","mulata","mulher","multar","mundial","munido","muralha","murcho","muscular","museu","musical","nacional","nadador","naja","namoro","narina","narrado","nascer","nativa","natureza","navalha","navegar","navio","neblina","nebuloso","negativa","negociar","negrito","nervoso","neta","neural","nevasca","nevoeiro","ninar","ninho","nitidez","nivelar","nobreza","noite","noiva","nomear","nominal","nordeste","nortear","notar","noticiar","noturno","novelo","novilho","novo","nublado","nudez","numeral","nupcial","nutrir","nuvem","obcecado","obedecer","objetivo","obrigado","obscuro","obstetra","obter","obturar","ocidente","ocioso","ocorrer","oculista","ocupado","ofegante","ofensiva","oferenda","oficina","ofuscado","ogiva","olaria","oleoso","olhar","oliveira","ombro","omelete","omisso","omitir","ondulado","oneroso","ontem","opcional","operador","oponente","oportuno","oposto","orar","orbitar","ordem","ordinal","orfanato","orgasmo","orgulho","oriental","origem","oriundo","orla","ortodoxo","orvalho","oscilar","ossada","osso","ostentar","otimismo","ousadia","outono","outubro","ouvido","ovelha","ovular","oxidar","oxigenar","pacato","paciente","pacote","pactuar","padaria","padrinho","pagar","pagode","painel","pairar","paisagem","palavra","palestra","palheta","palito","palmada","palpitar","pancada","panela","panfleto","panqueca","pantanal","papagaio","papelada","papiro","parafina","parcial","pardal","parede","partida","pasmo","passado","pastel","patamar","patente","patinar","patrono","paulada","pausar","peculiar","pedalar","pedestre","pediatra","pedra","pegada","peitoral","peixe","pele","pelicano","penca","pendurar","peneira","penhasco","pensador","pente","perceber","perfeito","pergunta","perito","permitir","perna","perplexo","persiana","pertence","peruca","pescado","pesquisa","pessoa","petiscar","piada","picado","piedade","pigmento","pilastra","pilhado","pilotar","pimenta","pincel","pinguim","pinha","pinote","pintar","pioneiro","pipoca","piquete","piranha","pires","pirueta","piscar","pistola","pitanga","pivete","planta","plaqueta","platina","plebeu","plumagem","pluvial","pneu","poda","poeira","poetisa","polegada","policiar","poluente","polvilho","pomar","pomba","ponderar","pontaria","populoso","porta","possuir","postal","pote","poupar","pouso","povoar","praia","prancha","prato","praxe","prece","predador","prefeito","premiar","prensar","preparar","presilha","pretexto","prevenir","prezar","primata","princesa","prisma","privado","processo","produto","profeta","proibido","projeto","prometer","propagar","prosa","protetor","provador","publicar","pudim","pular","pulmonar","pulseira","punhal","punir","pupilo","pureza","puxador","quadra","quantia","quarto","quase","quebrar","queda","queijo","quente","querido","quimono","quina","quiosque","rabanada","rabisco","rachar","racionar","radial","raiar","rainha","raio","raiva","rajada","ralado","ramal","ranger","ranhura","rapadura","rapel","rapidez","raposa","raquete","raridade","rasante","rascunho","rasgar","raspador","rasteira","rasurar","ratazana","ratoeira","realeza","reanimar","reaver","rebaixar","rebelde","rebolar","recado","recente","recheio","recibo","recordar","recrutar","recuar","rede","redimir","redonda","reduzida","reenvio","refinar","refletir","refogar","refresco","refugiar","regalia","regime","regra","reinado","reitor","rejeitar","relativo","remador","remendo","remorso","renovado","reparo","repelir","repleto","repolho","represa","repudiar","requerer","resenha","resfriar","resgatar","residir","resolver","respeito","ressaca","restante","resumir","retalho","reter","retirar","retomada","retratar","revelar","revisor","revolta","riacho","rica","rigidez","rigoroso","rimar","ringue","risada","risco","risonho","robalo","rochedo","rodada","rodeio","rodovia","roedor","roleta","romano","roncar","rosado","roseira","rosto","rota","roteiro","rotina","rotular","rouco","roupa","roxo","rubro","rugido","rugoso","ruivo","rumo","rupestre","russo","sabor","saciar","sacola","sacudir","sadio","safira","saga","sagrada","saibro","salada","saleiro","salgado","saliva","salpicar","salsicha","saltar","salvador","sambar","samurai","sanar","sanfona","sangue","sanidade","sapato","sarda","sargento","sarjeta","saturar","saudade","saxofone","sazonal","secar","secular","seda","sedento","sediado","sedoso","sedutor","segmento","segredo","segundo","seiva","seleto","selvagem","semanal","semente","senador","senhor","sensual","sentado","separado","sereia","seringa","serra","servo","setembro","setor","sigilo","silhueta","silicone","simetria","simpatia","simular","sinal","sincero","singular","sinopse","sintonia","sirene","siri","situado","soberano","sobra","socorro","sogro","soja","solda","soletrar","solteiro","sombrio","sonata","sondar","sonegar","sonhador","sono","soprano","soquete","sorrir","sorteio","sossego","sotaque","soterrar","sovado","sozinho","suavizar","subida","submerso","subsolo","subtrair","sucata","sucesso","suco","sudeste","sufixo","sugador","sugerir","sujeito","sulfato","sumir","suor","superior","suplicar","suposto","suprimir","surdina","surfista","surpresa","surreal","surtir","suspiro","sustento","tabela","tablete","tabuada","tacho","tagarela","talher","talo","talvez","tamanho","tamborim","tampa","tangente","tanto","tapar","tapioca","tardio","tarefa","tarja","tarraxa","tatuagem","taurino","taxativo","taxista","teatral","tecer","tecido","teclado","tedioso","teia","teimar","telefone","telhado","tempero","tenente","tensor","tentar","termal","terno","terreno","tese","tesoura","testado","teto","textura","texugo","tiara","tigela","tijolo","timbrar","timidez","tingido","tinteiro","tiragem","titular","toalha","tocha","tolerar","tolice","tomada","tomilho","tonel","tontura","topete","tora","torcido","torneio","torque","torrada","torto","tostar","touca","toupeira","toxina","trabalho","tracejar","tradutor","trafegar","trajeto","trama","trancar","trapo","traseiro","tratador","travar","treino","tremer","trepidar","trevo","triagem","tribo","triciclo","tridente","trilogia","trindade","triplo","triturar","triunfal","trocar","trombeta","trova","trunfo","truque","tubular","tucano","tudo","tulipa","tupi","turbo","turma","turquesa","tutelar","tutorial","uivar","umbigo","unha","unidade","uniforme","urologia","urso","urtiga","urubu","usado","usina","usufruir","vacina","vadiar","vagaroso","vaidoso","vala","valente","validade","valores","vantagem","vaqueiro","varanda","vareta","varrer","vascular","vasilha","vassoura","vazar","vazio","veado","vedar","vegetar","veicular","veleiro","velhice","veludo","vencedor","vendaval","venerar","ventre","verbal","verdade","vereador","vergonha","vermelho","verniz","versar","vertente","vespa","vestido","vetorial","viaduto","viagem","viajar","viatura","vibrador","videira","vidraria","viela","viga","vigente","vigiar","vigorar","vilarejo","vinco","vinheta","vinil","violeta","virada","virtude","visitar","visto","vitral","viveiro","vizinho","voador","voar","vogal","volante","voleibol","voltagem","volumoso","vontade","vulto","vuvuzela","xadrez","xarope","xeque","xeretar","xerife","xingar","zangado","zarpar","zebu","zelador","zombar","zoologia","zumbido"]["абзац","абонент","абсурд","авангард","авария","август","авиация","автор","агент","агитация","агрегат","адвокат","адмирал","адрес","азарт","азот","академия","аквариум","аксиома","акула","акцент","акция","аллея","алмаз","алтарь","альбом","альянс","амбиция","анализ","анекдот","анкета","ансамбль","антенна","апельсин","аппарат","аппетит","апрель","аптека","арбуз","аргумент","аренда","арест","армия","аромат","арсенал","артерия","артист","архив","аспирант","асфальт","атака","атомный","атрибут","аукцион","афиша","аэропорт","бабочка","бабушка","багаж","база","бактерия","баланс","балерина","балкон","бандит","банк","барабан","барон","барышня","барьер","бассейн","батарея","башмак","башня","бедный","беженец","бездна","белка","белый","бензин","берег","беседа","бешеный","билет","бинокль","биржа","битва","благо","блеск","близкий","блин","блок","блюдо","богатый","бодрый","боец","бокал","боковой","бокс","более","болото","болтать","большой","бомба","борт","борьба","босой","ботинок","бояться","брак","брать","бревно","бред","бригада","бродяга","броня","бросить","брызги","брюки","брюхо","бугор","будка","будни","будущее","буква","букет","бульвар","бумага","бунт","бурный","буря","бутылка","бухта","бывший","быстро","бытовой","быть","бюджет","бюро","бюст","вагон","важный","вакцина","валенок","вальс","валюта","ванная","варенье","вариант","вблизи","вверх","вводить","вдали","вдвое","вдова","вдоль","вдруг","ведро","ведущий","ведьма","вежливо","везде","веко","вексель","велеть","великий","венец","веник","веранда","верблюд","верить","верный","версия","вертеть","верхний","вершина","весело","весна","весомый","вести","весь","ветеран","ветхий","вечер","вечно","вешалка","вещество","взамен","взгляд","вздох","взнос","взойти","взор","взрыв","взять","видеть","видимо","визг","визит","вилка","вина","вирус","висок","витамин","витрина","вихрь","вишня","вкус","влага","владелец","власть","влево","влияние","вложить","вместе","внешний","вникать","внимание","вновь","внук","внутри","внучка","внушать","вовлечь","вовремя","вовсю","вода","водород","водяной","воевать","возврат","возглас","воздух","возить","возле","возня","возраст","война","войско","вокзал","волос","волчий","вольный","воля","вообще","вопль","вопрос","ворота","восемь","восток","вплоть","вполне","вправе","впредь","впрочем","врач","вредный","время","вручить","всадник","всегда","вскоре","вскрыть","всплеск","вспышка","встреча","всюду","всякий","второй","вход","вчера","выбор","вывод","выгнать","выдать","выехать","вызов","выйти","выкуп","вылезти","вымыть","выпасть","выпить","выплата","выпуск","вырасти","выручка","выслать","высокий","выставка","вышка","вязать","вялый","газета","газовый","галерея","галстук","гамма","гарантия","гармония","гарнизон","гастроли","гвардия","гвоздь","гектар","генерал","гений","геном","геолог","герб","герой","гибкий","гигант","гимн","гипотеза","гитара","главный","глагол","гладить","глаз","глина","глоток","глубокий","глупый","глухой","глыба","глядеть","гнев","гнездо","гнилой","годовой","голова","голубой","голый","гонорар","гордость","горизонт","горло","горный","город","горшок","горький","горючее","горячий","готовый","градус","грамм","граница","граф","гребень","гриб","гримаса","грозить","грохот","грош","грубый","грудь","груз","грунт","группа","груша","грязный","губа","гудок","гулкий","гулять","гусеница","густо","гусь","давление","давно","даже","дальний","данный","дарить","датчик","дать","дача","двадцать","дважды","дверь","двигать","движение","двойной","двор","дебют","девятый","дежурный","действие","декабрь","деление","дело","дельфин","день","дерево","держать","дерзкий","десять","деталь","детский","дефект","дефицит","деятель","джаз","джинсы","джунгли","диагноз","диалог","диапазон","диван","дивизия","дивный","диета","дизайн","дикарь","дилер","динамика","диплом","директор","дитя","длинный","дневник","добрый","добыча","доверие","догадка","догнать","дождь","доклад","доктор","документ","долго","должен","долина","донос","дорога","досада","доска","достать","досуг","доход","доцент","дощатый","драка","древний","дремать","дробный","дрова","дрожать","другой","дружба","дубовый","дуга","думать","дурной","духи","душный","дуэль","дуэт","дыра","дыхание","дюжина","дядя","едва","единый","ерунда","если","ехать","жадный","жажда","жалеть","жалоба","жанр","жареный","жаркий","жгучий","жевать","желание","желудок","жена","женщина","жертва","жест","жидкость","житель","жить","жрец","жулик","журнал","жуткий","забрать","забыть","завести","завод","завтра","загадка","загнать","заговор","задача","задеть","задний","задолго","заехать","заказ","закон","закрыть","закуска","залезть","залить","залп","замок","замуж","замысел","занавес","заново","занять","заодно","запись","запрос","запуск","запястье","заранее","заросль","зарплата","заря","засада","заслуга","заснуть","застать","затвор","затеять","затрата","затылок","захват","зачем","защита","заявить","заяц","звезда","звено","звонить","здесь","зелень","земля","зеркало","зерно","зима","злой","змея","знамя","знание","значит","золотой","зона","зонтик","зоопарк","зрачок","зрение","зритель","зубной","зубр","игла","идеал","идеолог","идея","идол","идти","изба","избить","избрать","избыток","извлечь","извне","изгиб","изгнать","издание","изделие","изнутри","изобилие","изоляция","изредка","изрядно","изучение","изъять","изящный","икона","икра","иллюзия","именно","иметь","имидж","империя","импульс","иначе","инвалид","индекс","индивид","инерция","инженер","иногда","иной","институт","интерес","интрига","интуиция","инфаркт","инцидент","ирония","искать","испуг","история","итог","июнь","кабель","кабинет","каблук","кавалер","кадр","каждый","кажется","казино","калитка","камень","камин","канал","кандидат","каникулы","канон","капитан","капля","капот","капуста","карандаш","карета","каркас","карман","картина","карьера","каска","кассета","кастрюля","каталог","катер","каток","катушка","кафедра","качество","каша","кашлять","каюта","квадрат","квартира","квота","кепка","кивнуть","километр","кино","киоск","кипяток","кирпич","кислота","кисть","клавиша","клапан","класс","клей","клетка","клиент","климат","клиника","кличка","клоун","клочок","клуб","клумба","ключ","книга","кнопка","кнут","княгиня","князь","кобура","когда","кодекс","кожа","коктейль","колено","коллега","колонна","колпак","кольцо","колючий","коляска","команда","комедия","комиссия","коммуна","комната","комок","компания","комфорт","конвейер","конгресс","конечно","конкурс","контроль","концерт","конь","конюшня","копать","копейка","копыто","корабль","корень","корзина","коридор","кормить","корпус","космос","костюм","косяк","котел","котлета","который","коттедж","кофе","кофта","кошка","кража","край","красный","краткий","кредит","крем","крепкий","кресло","кривой","кризис","кристалл","критерий","кричать","кровь","крокодил","кролик","кроме","крона","круг","кружка","крупный","крутой","крушение","крыло","крыша","крючок","кстати","кубик","куда","кузов","кукла","кулак","кулиса","культура","кумир","купе","купить","купол","купюра","курица","курорт","курс","куртка","кусок","куст","кухня","кушать","лабиринт","лавка","лагерь","ладно","ладонь","лапа","лауреат","лгать","лебедь","левый","легенда","легкий","ледяной","лежать","лезвие","лезть","лекция","ленивый","лента","лепесток","лесной","лестница","лететь","лето","лечить","лига","лидер","лиловый","лимон","линия","липкий","лист","литр","лихой","лицо","лишить","лишний","ловить","логика","лодка","ложь","лозунг","локоть","лопата","лошадь","лукавый","луна","лучший","лысый","льгота","любить","любой","людской","люстра","лютый","лягушка","магазин","магия","майор","майский","максимум","макушка","мало","мальчик","мама","манера","марка","март","маршрут","масса","мастер","масштаб","материал","матч","махать","машина","маяк","мебель","медаль","медведь","медицина","медь","между","мелкий","мелочь","мемуары","меньше","меню","менять","мера","мерцать","место","месяц","металл","метод","метр","механизм","меховой","мечтать","мешать","мешок","миграция","микрофон","милиция","миллион","милость","миля","мимо","минерал","министр","минута","мирный","миска","миссия","митинг","мишень","младший","мнение","мнимый","много","могучий","модель","может","мозг","мокрый","молекула","молния","молодой","молчать","момент","монета","монитор","монолог","монстр","монтаж","мораль","море","морковь","мороз","морщина","мостовая","мотать","мотив","мотор","мохнатый","мрамор","мрачный","мстить","мудрый","мужество","мужчина","музей","музыка","мундир","муравей","мусор","муха","мчаться","мысль","мыться","мышца","мышь","мюзикл","мягкий","мясо","набор","навык","наглый","нагрузка","надежда","надзор","надо","наедине","назад","название","назло","наивный","найти","наконец","налево","наличие","налог","намерен","нанести","напасть","например","народ","наследие","натура","наука","наутро","начать","небо","неважно","невеста","негодяй","недавно","неделя","недолго","недра","недуг","нежный","незачем","некто","нелепый","неловко","нельзя","немало","немой","неплохо","нервный","нередко","нестись","неудача","неужели","нефть","неясный","нигде","низкий","никакой","никогда","никуда","ничто","ничуть","ниша","нищий","новость","новый","нога","ноготь","ножницы","ноздря","номер","носить","носок","ночь","ноябрь","нрав","нуль","нынче","нырять","нюанс","няня","обаяние","обед","обезьяна","обещать","обжечь","обзор","обилие","обитать","область","облик","обложка","обмен","обморок","обожать","обои","оболочка","оборона","обочина","образ","обрести","обрыв","обувь","обучение","обход","общество","общий","объект","обыск","обычно","обязать","овощи","овраг","овца","оговорка","ограда","огурец","одежда","одеяло","один","однако","одолеть","ожидать","озеро","океан","окно","около","окоп","окраина","октябрь","опасный","опека","операция","описание","оплата","опора","оппонент","оптимизм","оптовый","опухоль","опыт","оратор","орбита","орган","орден","орел","оригинал","ориентир","оркестр","оружие","осенний","осколок","осмотр","остров","отбор","отбыть","отвлечь","отдать","отдел","отдых","отель","отец","отзыв","отказ","отклик","открыть","откуда","отличие","отныне","отойти","отпуск","отрасль","отросток","отрывок","отряд","отсек","отставка","отсюда","оттенок","оттого","отчего","отъезд","офис","офицер","охота","охрана","оценка","очаг","очень","очередь","очищать","ошибка","ощущение","павильон","падать","пазуха","пакет","палата","палец","палуба","пальто","память","панель","паника","пара","парень","пароход","партия","парус","паспорт","пассажир","пастух","патент","патрон","пауза","паук","паутина","пафос","пахнуть","пациент","пачка","певец","педагог","пейзаж","пенсия","пепел","первый","перед","период","перо","перрон","персонаж","перчатка","песня","песок","петля","петрушка","петух","пехота","печать","печень","пешком","пещера","пианист","пиджак","пилот","пионер","пирамида","пирожок","письмо","пища","плавание","плакать","пламя","план","пласт","платить","пленный","плечо","плита","плод","плоский","плотный","плохой","площадь","плыть","плюс","пляж","плясать","победа","повар","повод","повсюду","повязка","погода","погреб","подбор","подвиг","подделка","поджать","поднос","подпись","подруга","подход","подчас","подъезд","поединок","поезд","поесть","поехать","пожалуй","пожилой","позади","позвать","поздний","позиция","позор","поиск","поймать","пойти","поклон","покой","покрыть","полдень","полезный","ползти","полк","полный","половина","полтора","польза","поляна","помидор","помнить","помощь","попасть","поперек","поплыть","пополам","поправка","попугай","попытка","порог","портрет","порция","порыв","порядок","после","посол","посреди","постель","посуда","потом","похвала","похожий","поцелуй","почва","почему","пошлина","поэма","поэтому","право","праздник","практика","прах","преграда","предмет","прежде","прелесть","премия","препарат","пресса","прибыть","прижать","прийти","приказ","прилавок","пример","принять","природа","притом","прихожая","прицел","причина","приют","прогноз","продукт","проект","прожить","прокат","промысел","пропуск","просто","против","профиль","процесс","прочий","прошлый","прощать","пружина","прут","прыжок","прямой","птица","публика","пугать","пуговица","пузырь","пульт","пуля","пункт","пускать","пустой","путь","пухлый","пучок","пушистый","пушка","пчела","пшеница","пылать","пыль","пышный","пьеса","пятка","пятно","пятый","пятьсот","работа","равнина","ради","радость","радуга","разбить","развитие","разговор","раздел","различие","размер","разный","разрыв","разум","район","ракета","раковина","рамка","рано","рапорт","распад","рассказ","расти","расход","расцвет","рация","рвануть","рваться","реакция","ребро","реветь","редактор","редкий","реестр","режим","резать","резерв","резина","резко","резной","рейс","реклама","рекорд","религия","рельс","ремень","ремонт","реплика","репортаж","ресница","ресторан","реформа","рецепт","речь","решение","ржавый","риск","рисунок","ритуал","рифма","робко","робот","ровесник","ровно","родной","рождение","роза","розовый","розыск","роль","роман","роскошь","роспись","рост","рубашка","рубеж","рубить","рубрика","рудник","рука","рукопись","румяный","русло","рухнуть","ручей","ручной","рыба","рыжий","рынок","рыхлый","рыцарь","рычаг","рюкзак","рядом","садовый","сажать","салон","салфетка","салют","самец","самовар","самый","сани","санкция","сапог","сарай","сатира","сахар","сбить","сбоку","сборная","сбыт","свадьба","свалка","сварить","свежий","сверху","свет","свеча","свинья","свист","свитер","свобода","сводка","свой","свыше","связь","сдаться","сделать","сегмент","сегодня","седло","седой","седьмой","сезон","сейф","сейчас","секрет","сектор","секунда","семинар","семья","сенатор","сено","сенсация","сентябрь","сервис","сердце","середина","сержант","серия","серый","сессия","сесть","сетевой","сжатый","сжечь","сзади","сигнал","сиденье","сила","силуэт","сильный","символ","симпатия","симфония","синий","синтез","синяк","сирень","система","ситуация","сияние","сказать","скала","скамейка","скандал","скатерть","скачок","скважина","сквер","сквозь","скелет","скидка","склад","сколько","скорый","скосить","скот","скрипка","скудный","скука","слабый","слава","сладкий","слегка","след","слеза","слепой","слесарь","слишком","слово","слог","сложный","сломать","служба","слух","случай","слышать","слюна","смежный","смелый","сменить","смесь","сметана","смех","смола","смуглый","смутный","смущать","смысл","снайпер","снаряд","сначала","снег","снизу","сниться","сно��а","снять","собака","соблазн","собрание","событие","совесть","совсем","согласие","создать","сознание","созреть","сойтись","сокол","солдат","соленый","солнце","солома","сомнение","сонный","соперник","соратник","сорвать","сосед","сосиска","состав","сотня","соус","союз","спад","спальня","спасти","спектр","сперва","спешить","спина","спирт","список","спичка","сплав","спонсор","спор","способ","справка","спустя","спутник","сразу","средство","срок","срыв","ссора","ссылка","ставить","стадия","стакан","станция","старый","стая","стебель","стекло","стена","степень","стереть","стиль","стимул","стирать","стихи","стоить","стойка","стол","стонать","стопа","сторона","стоянка","страна","стричь","строгий","струя","студент","стук","ступня","стыдно","суббота","субъект","сувенир","сугроб","сугубо","судить","судно","судьба","суета","суметь","сумма","сумрак","сундук","супруг","суровый","сутки","сухой","суша","существо","сфера","схема","схожий","сценарий","счастье","считать","съезд","сыграть","сырой","сытый","сыщик","сюда","сюжет","сюрприз","тайна","также","такой","такси","тактика","талия","таможня","танец","таракан","тарелка","тариф","тащить","таять","тварь","театр","тезис","текст","текущий","телефон","тема","темнота","теневой","теннис","теория","теперь","тепло","терапия","терзать","термин","терпеть","терраса","терять","тесный","тетрадь","техника","течение","тигр","типовой","тираж","титул","тихий","ткань","товарищ","тоже","толпа","толстый","толчок","толщина","только","тонкий","тонна","топить","топор","торговля","тормоз","торчать","тотчас","точка","точно","тощий","трава","традиция","трактор","трамвай","траншея","трасса","тревога","трезвый","тренер","трепет","треск","третий","трещина","трибуна","тридцать","триста","триумф","трогать","тройка","тронуть","тропа","тротуар","трубка","труд","трюк","тряпка","туго","туловище","туман","тумбочка","тундра","тупик","турист","турнир","тусклый","туфля","туча","тысяча","тяга","тяжело","убежать","убогий","уборка","уважение","увезти","уволить","угадать","угол","угощать","угроза","угрюмый","удар","удачный","уделять","удивить","удобный","удочка","уезжать","ужин","узел","узкий","уйти","указание","уклон","украсть","укусить","улетать","улица","улыбка","умело","умение","умный","умолять","унести","унижать","унылый","упаковка","упасть","упорно","упрек","урна","уровень","урожай","уронить","усадьба","усатый","усвоить","усилие","условие","услуга","усмешка","успеть","устав","устоять","утечка","утешать","утро","уцелеть","участие","ученик","учесть","ущелье","ущерб","уютный","фабрика","фаворит","факел","факт","фамилия","фантазия","фасад","февраль","феномен","фермер","фигура","физика","филиал","философ","фильм","финал","флаг","флот","фойе","фокус","фонарь","фонд","фонтан","форма","форум","фото","фрагмент","фраза","фракция","фронт","фрукт","функция","фуражка","футбол","футляр","халат","хаос","характер","хата","хвалить","хватать","хвойный","хвост","химия","хирург","хитрый","хищник","хлеб","хлынуть","хмурый","ходить","хозяин","хоккей","холм","холст","хорошо","хотеть","храбрый","храм","хранить","хребет","хрен","хрипло","хроника","хрупкий","художник","худший","хулиган","хутор","царь","цветок","целевой","целиком","целое","цель","цензура","ценить","центр","цепной","цикл","цилиндр","цирк","цитата","цифра","чайник","часы","чашка","человек","челюсть","чемодан","чемпион","чепуха","червь","чердак","через","чернила","черта","чеснок","честно","четверть","четыре","число","чистый","читатель","чтение","чтобы","чувство","чудак","чудный","чудо","чужой","чулок","чума","чушь","чуять","шагать","шанс","шапка","шарик","шарф","шахматы","шашлык","шедевр","шептать","шерсть","шестой","шинель","ширина","шишка","шкаф","школа","шкура","шланг","шлем","шнур","шоколад","шорох","шоссе","шпион","шприц","штаб","штамм","штаны","штатный","штора","штраф","штурм","штык","шумно","шуршать","шутить","шутка","щедрый","щека","щенок","экзамен","экипаж","экономия","экран","эксперт","элемент","элитный","эмоция","энергия","эпизод","эпоха","эскиз","эстрада","этап","этика","этот","эфир","эффект","эшелон","юбилей","юбка","южный","юмор","юность","юрист","юстиция","яблоко","явление","ягода","ядро","язык","яйцо","якобы","якорь","январь","яркий","ярмарка","ярость","ясный","яхта","ячейка","ящик"]["ábaco","abdomen","abeja","abierto","abogado","abono","aborto","abrazo","abrir","abuelo","abuso","acabar","academia","acceso","acción","aceite","acelga","acento","aceptar","ácido","aclarar","acné","acoger","acoso","activo","acto","actriz","actuar","acudir","acuerdo","acusar","adicto","admitir","adoptar","adorno","aduana","adulto","aéreo","afectar","afición","afinar","afirmar","ágil","agitar","agonía","agosto","agotar","agregar","agrio","agua","agudo","águila","aguja","ahogo","ahorro","aire","aislar","ajedrez","ajeno","ajuste","alacrán","alambre","alarma","alba","álbum","alcalde","aldea","alegre","alejar","alerta","aleta","alfiler","alga","algodón","aliado","aliento","alivio","alma","almeja","almíbar","altar","alteza","altivo","alto","altura","alumno","alzar","amable","amante","amapola","amargo","amasar","ámbar","ámbito","ameno","amigo","amistad","amor","amparo","amplio","ancho","anciano","ancla","andar","andén","anemia","ángulo","anillo","ánimo","anís","anotar","antena","antiguo","antojo","anual","anular","anuncio","añadir","añejo","año","apagar","aparato","apetito","apio","aplicar","apodo","aporte","apoyo","aprender","aprobar","apuesta","apuro","arado","araña","arar","árbitro","árbol","arbusto","archivo","arco","arder","ardilla","arduo","área","árido","aries","armonía","arnés","aroma","arpa","arpón","arreglo","arroz","arruga","arte","artista","asa","asado","asalto","ascenso","asegurar","aseo","asesor","asiento","asilo","asistir","asno","asombro","áspero","astilla","astro","astuto","asumir","asunto","atajo","ataque","atar","atento","ateo","ático","atleta","átomo","atraer","atroz","atún","audaz","audio","auge","aula","aumento","ausente","autor","aval","avance","avaro","ave","avellana","avena","avestruz","avión","aviso","ayer","ayuda","ayuno","azafrán","azar","azote","azúcar","azufre","azul","baba","babor","bache","bahía","baile","bajar","balanza","balcón","balde","bambú","banco","banda","baño","barba","barco","barniz","barro","báscula","bastón","basura","batalla","batería","batir","batuta","baúl","bazar","bebé","bebida","bello","besar","beso","bestia","bicho","bien","bingo","blanco","bloque","blusa","boa","bobina","bobo","boca","bocina","boda","bodega","boina","bola","bolero","bolsa","bomba","bondad","bonito","bono","bonsái","borde","borrar","bosque","bote","botín","bóveda","bozal","bravo","brazo","brecha","breve","brillo","brinco","brisa","broca","broma","bronce","brote","bruja","brusco","bruto","buceo","bucle","bueno","buey","bufanda","bufón","búho","buitre","bulto","burbuja","burla","burro","buscar","butaca","buzón","caballo","cabeza","cabina","cabra","cacao","cadáver","cadena","caer","café","caída","caimán","caja","cajón","cal","calamar","calcio","caldo","calidad","calle","calma","calor","calvo","cama","cambio","camello","camino","campo","cáncer","candil","canela","canguro","canica","canto","caña","cañón","caoba","caos","capaz","capitán","capote","captar","capucha","cara","carbón","cárcel","careta","carga","cariño","carne","carpeta","carro","carta","casa","casco","casero","caspa","castor","catorce","catre","caudal","causa","cazo","cebolla","ceder","cedro","celda","célebre","celoso","célula","cemento","ceniza","centro","cerca","cerdo","cereza","cero","cerrar","certeza","césped","cetro","chacal","chaleco","champú","chancla","chapa","charla","chico","chiste","chivo","choque","choza","chuleta","chupar","ciclón","ciego","cielo","cien","cierto","cifra","cigarro","cima","cinco","cine","cinta","ciprés","circo","ciruela","cisne","cita","ciudad","clamor","clan","claro","clase","clave","cliente","clima","clínica","cobre","cocción","cochino","cocina","coco","código","codo","cofre","coger","cohete","cojín","cojo","cola","colcha","colegio","colgar","colina","collar","colmo","columna","combate","comer","comida","cómodo","compra","conde","conejo","conga","conocer","consejo","contar","copa","copia","corazón","corbata","corcho","cordón","corona","correr","coser","cosmos","costa","cráneo","cráter","crear","crecer","creído","crema","cría","crimen","cripta","crisis","cromo","crónica","croqueta","crudo","cruz","cuadro","cuarto","cuatro","cubo","cubrir","cuchara","cuello","cuento","cuerda","cuesta","cueva","cuidar","culebra","culpa","culto","cumbre","cumplir","cuna","cuneta","cuota","cupón","cúpula","curar","curioso","curso","curva","cutis","dama","danza","dar","dardo","dátil","deber","débil","década","decir","dedo","defensa","definir","dejar","delfín","delgado","delito","demora","denso","dental","deporte","derecho","derrota","desayuno","deseo","desfile","desnudo","destino","desvío","detalle","detener","deuda","día","diablo","diadema","diamante","diana","diario","dibujo","dictar","diente","dieta","diez","difícil","digno","dilema","diluir","dinero","directo","dirigir","disco","diseño","disfraz","diva","divino","doble","doce","dolor","domingo","don","donar","dorado","dormir","dorso","dos","dosis","dragón","droga","ducha","duda","duelo","dueño","dulce","dúo","duque","durar","dureza","duro","ébano","ebrio","echar","eco","ecuador","edad","edición","edificio","editor","educar","efecto","eficaz","eje","ejemplo","elefante","elegir","elemento","elevar","elipse","élite","elixir","elogio","eludir","embudo","emitir","emoción","empate","empeño","empleo","empresa","enano","encargo","enchufe","encía","enemigo","enero","enfado","enfermo","engaño","enigma","enlace","enorme","enredo","ensayo","enseñar","entero","entrar","envase","envío","época","equipo","erizo","escala","escena","escolar","escribir","escudo","esencia","esfera","esfuerzo","espada","espejo","espía","esposa","espuma","esquí","estar","este","estilo","estufa","etapa","eterno","ética","etnia","evadir","evaluar","evento","evitar","exacto","examen","exceso","excusa","exento","exigir","exilio","existir","éxito","experto","explicar","exponer","extremo","fábrica","fábula","fachada","fácil","factor","faena","faja","falda","fallo","falso","faltar","fama","familia","famoso","faraón","farmacia","farol","farsa","fase","fatiga","fauna","favor","fax","febrero","fecha","feliz","feo","feria","feroz","fértil","fervor","festín","fiable","fianza","fiar","fibra","ficción","ficha","fideo","fiebre","fiel","fiera","fiesta","figura","fijar","fijo","fila","filete","filial","filtro","fin","finca","fingir","finito","firma","flaco","flauta","flecha","flor","flota","fluir","flujo","flúor","fobia","foca","fogata","fogón","folio","folleto","fondo","forma","forro","fortuna","forzar","fosa","foto","fracaso","frágil","franja","frase","fraude","freír","freno","fresa","frío","frito","fruta","fuego","fuente","fuerza","fuga","fumar","función","funda","furgón","furia","fusil","fútbol","futuro","gacela","gafas","gaita","gajo","gala","galería","gallo","gamba","ganar","gancho","ganga","ganso","garaje","garza","gasolina","gastar","gato","gavilán","gemelo","gemir","gen","género","genio","gente","geranio","gerente","germen","gesto","gigante","gimnasio","girar","giro","glaciar","globo","gloria","gol","golfo","goloso","golpe","goma","gordo","gorila","gorra","gota","goteo","gozar","grada","gráfico","grano","grasa","gratis","grave","grieta","grillo","gripe","gris","grito","grosor","grúa","grueso","grumo","grupo","guante","guapo","guardia","guerra","guía","guiño","guion","guiso","guitarra","gusano","gustar","haber","hábil","hablar","hacer","hacha","hada","hallar","hamaca","harina","haz","hazaña","hebilla","hebra","hecho","helado","helio","hembra","herir","hermano","héroe","hervir","hielo","hierro","hígado","higiene","hijo","himno","historia","hocico","hogar","hoguera","hoja","hombre","hongo","honor","honra","hora","hormiga","horno","hostil","hoyo","hueco","huelga","huerta","hueso","huevo","huida","huir","humano","húmedo","humilde","humo","hundir","huracán","hurto","icono","ideal","idioma","ídolo","iglesia","iglú","igual","ilegal","ilusión","imagen","imán","imitar","impar","imperio","imponer","impulso","incapaz","índice","inerte","infiel","informe","ingenio","inicio","inmenso","inmune","innato","insecto","instante","interés","íntimo","intuir","inútil","invierno","ira","iris","ironía","isla","islote","jabalí","jabón","jamón","jarabe","jardín","jarra","jaula","jazmín","jefe","jeringa","jinete","jornada","joroba","joven","joya","juerga","jueves","juez","jugador","jugo","juguete","juicio","junco","jungla","junio","juntar","júpiter","jurar","justo","juvenil","juzgar","kilo","koala","labio","lacio","lacra","lado","ladrón","lagarto","lágrima","laguna","laico","lamer","lámina","lámpara","lana","lancha","langosta","lanza","lápiz","largo","larva","lástima","lata","látex","latir","laurel","lavar","lazo","leal","lección","leche","lector","leer","legión","legumbre","lejano","lengua","lento","leña","león","leopardo","lesión","letal","letra","leve","leyenda","libertad","libro","licor","líder","lidiar","lienzo","liga","ligero","lima","límite","limón","limpio","lince","lindo","línea","lingote","lino","linterna","líquido","liso","lista","litera","litio","litro","llaga","llama","llanto","llave","llegar","llenar","llevar","llorar","llover","lluvia","lobo","loción","loco","locura","lógica","logro","lombriz","lomo","lonja","lote","lucha","lucir","lugar","lujo","luna","lunes","lupa","lustro","luto","luz","maceta","macho","madera","madre","maduro","maestro","mafia","magia","mago","maíz","maldad","maleta","malla","malo","mamá","mambo","mamut","manco","mando","manejar","manga","maniquí","manjar","mano","manso","manta","mañana","mapa","máquina","mar","marco","marea","marfil","margen","marido","mármol","marrón","martes","marzo","masa","máscara","masivo","matar","materia","matiz","matriz","máximo","mayor","mazorca","mecha","medalla","medio","médula","mejilla","mejor","melena","melón","memoria","menor","mensaje","mente","menú","mercado","merengue","mérito","mes","mesón","meta","meter","método","metro","mezcla","miedo","miel","miembro","miga","mil","milagro","militar","millón","mimo","mina","minero","mínimo","minuto","miope","mirar","misa","miseria","misil","mismo","mitad","mito","mochila","moción","moda","modelo","moho","mojar","molde","moler","molino","momento","momia","monarca","moneda","monja","monto","moño","morada","morder","moreno","morir","morro","morsa","mortal","mosca","mostrar","motivo","mover","móvil","mozo","mucho","mudar","mueble","muela","muerte","muestra","mugre","mujer","mula","muleta","multa","mundo","muñeca","mural","muro","músculo","museo","musgo","música","muslo","nácar","nación","nadar","naipe","naranja","nariz","narrar","nasal","natal","nativo","natural","náusea","naval","nave","navidad","necio","néctar","negar","negocio","negro","neón","nervio","neto","neutro","nevar","nevera","nicho","nido","niebla","nieto","niñez","niño","nítido","nivel","nobleza","noche","nómina","noria","norma","norte","nota","noticia","novato","novela","novio","nube","nuca","núcleo","nudillo","nudo","nuera","nueve","nuez","nulo","número","nutria","oasis","obeso","obispo","objeto","obra","obrero","observar","obtener","obvio","oca","ocaso","océano","ochenta","ocho","ocio","ocre","octavo","octubre","oculto","ocupar","ocurrir","odiar","odio","odisea","oeste","ofensa","oferta","oficio","ofrecer","ogro","oído","oír","ojo","ola","oleada","olfato","olivo","olla","olmo","olor","olvido","ombligo","onda","onza","opaco","opción","ópera","opinar","oponer","optar","óptica","opuesto","oración","orador","oral","órbita","orca","orden","oreja","órgano","orgía","orgullo","oriente","origen","orilla","oro","orquesta","oruga","osadía","oscuro","osezno","oso","ostra","otoño","otro","oveja","óvulo","óxido","oxígeno","oyente","ozono","pacto","padre","paella","página","pago","país","pájaro","palabra","palco","paleta","pálido","palma","paloma","palpar","pan","panal","pánico","pantera","pañuelo","papá","papel","papilla","paquete","parar","parcela","pared","parir","paro","párpado","parque","párrafo","parte","pasar","paseo","pasión","paso","pasta","pata","patio","patria","pausa","pauta","pavo","payaso","peatón","pecado","pecera","pecho","pedal","pedir","pegar","peine","pelar","peldaño","pelea","peligro","pellejo","pelo","peluca","pena","pensar","peñón","peón","peor","pepino","pequeño","pera","percha","perder","pereza","perfil","perico","perla","permiso","perro","persona","pesa","pesca","pésimo","pestaña","pétalo","petróleo","pez","pezuña","picar","pichón","pie","piedra","pierna","pieza","pijama","pilar","piloto","pimienta","pino","pintor","pinza","piña","piojo","pipa","pirata","pisar","piscina","piso","pista","pitón","pizca","placa","plan","plata","playa","plaza","pleito","pleno","plomo","pluma","plural","pobre","poco","poder","podio","poema","poesía","poeta","polen","policía","pollo","polvo","pomada","pomelo","pomo","pompa","poner","porción","portal","posada","poseer","posible","poste","potencia","potro","pozo","prado","precoz","pregunta","premio","prensa","preso","previo","primo","príncipe","prisión","privar","proa","probar","proceso","producto","proeza","profesor","programa","prole","promesa","pronto","propio","próximo","prueba","público","puchero","pudor","pueblo","puerta","puesto","pulga","pulir","pulmón","pulpo","pulso","puma","punto","puñal","puño","pupa","pupila","puré","quedar","queja","quemar","querer","queso","quieto","química","quince","quitar","rábano","rabia","rabo","ración","radical","raíz","rama","rampa","rancho","rango","rapaz","rápido","rapto","rasgo","raspa","rato","rayo","raza","razón","reacción","realidad","rebaño","rebote","recaer","receta","rechazo","recoger","recreo","recto","recurso","red","redondo","reducir","reflejo","reforma","refrán","refugio","regalo","regir","regla","regreso","rehén","reino","reír","reja","relato","relevo","relieve","relleno","reloj","remar","remedio","remo","rencor","rendir","renta","reparto","repetir","reposo","reptil","res","rescate","resina","respeto","resto","resumen","retiro","retorno","retrato","reunir","revés","revista","rey","rezar","rico","riego","rienda","riesgo","rifa","rígido","rigor","rincón","riñón","río","riqueza","risa","ritmo","rito","rizo","roble","roce","rociar","rodar","rodeo","rodilla","roer","rojizo","rojo","romero","romper","ron","ronco","ronda","ropa","ropero","rosa","rosca","rostro","rotar","rubí","rubor","rudo","rueda","rugir","ruido","ruina","ruleta","rulo","rumbo","rumor","ruptura","ruta","rutina","sábado","saber","sabio","sable","sacar","sagaz","sagrado","sala","saldo","salero","salir","salmón","salón","salsa","salto","salud","salvar","samba","sanción","sandía","sanear","sangre","sanidad","sano","santo","sapo","saque","sardina","sartén","sastre","satán","sauna","saxofón","sección","seco","secreto","secta","sed","seguir","seis","sello","selva","semana","semilla","senda","sensor","señal","señor","separar","sepia","sequía","ser","serie","sermón","servir","sesenta","sesión","seta","setenta","severo","sexo","sexto","sidra","siesta","siete","siglo","signo","sílaba","silbar","silencio","silla","símbolo","simio","sirena","sistema","sitio","situar","sobre","socio","sodio","sol","solapa","soldado","soledad","sólido","soltar","solución","sombra","sondeo","sonido","sonoro","sonrisa","sopa","soplar","soporte","sordo","sorpresa","sorteo","sostén","sótano","suave","subir","suceso","sudor","suegra","suelo","sueño","suerte","sufrir","sujeto","sultán","sumar","superar","suplir","suponer","supremo","sur","surco","sureño","surgir","susto","sutil","tabaco","tabique","tabla","tabú","taco","tacto","tajo","talar","talco","talento","talla","talón","tamaño","tambor","tango","tanque","tapa","tapete","tapia","tapón","taquilla","tarde","tarea","tarifa","tarjeta","tarot","tarro","tarta","tatuaje","tauro","taza","tazón","teatro","techo","tecla","técnica","tejado","tejer","tejido","tela","teléfono","tema","temor","templo","tenaz","tender","tener","tenis","tenso","teoría","terapia","terco","término","ternura","terror","tesis","tesoro","testigo","tetera","texto","tez","tibio","tiburón","tiempo","tienda","tierra","tieso","tigre","tijera","tilde","timbre","tímido","timo","tinta","tío","típico","tipo","tira","tirón","titán","títere","título","tiza","toalla","tobillo","tocar","tocino","todo","toga","toldo","tomar","tono","tonto","topar","tope","toque","tórax","torero","tormenta","torneo","toro","torpedo","torre","torso","tortuga","tos","tosco","toser","tóxico","trabajo","tractor","traer","tráfico","trago","traje","tramo","trance","trato","trauma","trazar","trébol","tregua","treinta","tren","trepar","tres","tribu","trigo","tripa","triste","triunfo","trofeo","trompa","tronco","tropa","trote","trozo","truco","trueno","trufa","tubería","tubo","tuerto","tumba","tumor","túnel","túnica","turbina","turismo","turno","tutor","ubicar","úlcera","umbral","unidad","unir","universo","uno","untar","uña","urbano","urbe","urgente","urna","usar","usuario","útil","utopía","uva","vaca","vacío","vacuna","vagar","vago","vaina","vajilla","vale","válido","valle","valor","válvula","vampiro","vara","variar","varón","vaso","vecino","vector","vehículo","veinte","vejez","vela","velero","veloz","vena","vencer","venda","veneno","vengar","venir","venta","venus","ver","verano","verbo","verde","vereda","verja","verso","verter","vía","viaje","vibrar","vicio","víctima","vida","vídeo","vidrio","viejo","viernes","vigor","vil","villa","vinagre","vino","viñedo","violín","viral","virgo","virtud","visor","víspera","vista","vitamina","viudo","vivaz","vivero","vivir","vivo","volcán","volumen","volver","voraz","votar","voto","voz","vuelo","vulgar","yacer","yate","yegua","yema","yerno","yeso","yodo","yoga","yogur","zafiro","zanja","zapato","zarza","zona","zorro","zumo","zurdo"]["abajur","abaküs","abartı","abdal","abdest","abiye","abluka","abone","absorbe","absürt","acayip","acele","acemi","açıkgöz","adalet","adam","adezyon","adisyon","adliye","adres","afacan","afili","afiş","afiyet","aforizm","afra","ağaç","ağır","ahbap","ahkam","ahlak","ahtapot","aidat","aile","ajan","akademi","akarsu","akbaş","akciğer","akdeniz","akıbet","akıl","akıntı","akide","akrep","akrobasi","aksiyon","akşam","aktif","aktör","aktris","akustik","alaca","albüm","alçak","aldanma","aleni","alet","alfabe","algılama","alıngan","alkış","alkol","alpay","alperen","altın","altüst","altyapı","alyuvar","amade","amatör","amazon","ambalaj","amblem","ambulans","amca","amel","amigo","amir","amiyane","amorti","ampul","anadolu","anahtar","anakonda","anaokul","anapara","anarşi","anatomi","anayasa","anekdot","anestezi","angaje","anka","anket","anlamlı","anne","anomali","anonim","anten","antlaşma","apse","araba","aracı","araf","arbede","arda","arefe","arena","argo","argüman","arkadaş","armoni","aroma","arsa","arsız","artı","artist","aruz","asansör","asayiş","asfalt","asgari","asil","asker","askı","aslan","asosyal","astsubay","asya","aşçı","aşırı","aşure","atabey","ataman","ateş","atmaca","atmosfer","atom","atölye","avcı","avdet","avize","avlu","avokado","avrupa","avukat","ayaz","ayçiçeği","aydın","aygıt","ayna","ayran","ayrıntı","azim","baca","bagaj","bağlantı","bahadır","bahçe","baki","bakkal","baklava","bakteri","balçık","balina","balo","balta","bant","banyo","bardak","barış","başbuğ","başıboş","başkan","başlık","bavul","bayındır","baykuş","bazlama","bedel","begüm","bekçi","bekle","belge","belki","bencil","benek","bengi","benzer","berjer","berk","bermuda","berrak","beşik","beton","beyin","beyoğlu","bıçak","biberiye","bidon","biftek","bihaber","bikini","bilezik","bilinç","bilye","bina","binbaşı","binyıl","bisiklet","bisküvi","bitki","bizzat","bodrum","boğaz","bohça","bolero","boncuk","bonfile","borsa","boru","bostan","boşboğaz","botanik","boya","boykot","boynuz","bozgun","bozkır","bölüm","börek","buçuk","bugün","buğday","buhar","buhran","bulvar","buram","burçak","burs","burun","butik","buzdağı","buzkıran","bücür","büfe","bülten","bütçe","bütün","büyük","cacık","cadı","cahil","cambaz","canhıraş","casus","cazibe","cehalet","cehennem","ceket","cemre","cenin","cennet","cepken","cerrah","cesur","cetvel","cevher","ceylan","cılız","cıva","cilt","cisim","ciyak","coğrafya","cömert","cumba","cüzdan","çabucak","çadır","çağdaş","çağlayan","çağrı","çakmak","çalışkan","çamaşır","çapa","çaput","çarık","çarpan","çarşaf","çayhane","çekirdek","çelebi","çember","çenet","çengel","çerçeve","çerez","çeşit","çeşme","çete","çevre","çeyiz","çeyrek","çığır","çılgın","çıngırak","çift","çiğdem","çikolata","çilek","çimen","çivi","çoban","çocuk","çokgen","çomak","çorba","çözelti","çubuk","çukur","çuval","çürük","dağbaşı","dağılım","daktilo","daldırış","dalga","dalkavuk","damak","damıtma","damla","dana","dandik","danışman","daniska","dantel","dargeçit","darphane","davet","dayı","defter","değer","değirmen","dehşet","delgeç","demir","deneyim","denge","depo","deprem","derdest","dere","derhal","derman","dernek","derviş","desen","destan","dışarı","dışbükey","dijital","dikbaşlı","dilekçe","dimağ","dinamik","dindar","dinleme","dinozor","dipçik","dipnot","direniş","dirsek","disiplin","disk","divriği","dizüstü","dobra","dodurga","doğalgaz","doktor","doküman","dolap","donanım","dondurma","donör","doruk","dosdoğru","dost","dosya","dozer","döküm","dönence","dörtyol","dövme","dram","dublaj","durum","duvak","duyarga","duyma","duyuru","düğme","düğüm","dükkan","dünür","düpedüz","dürbün","düşünür","düzayak","düzeltme","ebeveyn","ebru","ecel","ecnebi","ecza","edat","edilgen","efendi","efor","efsane","egemen","egzersiz","eğrelti","ekarte","ekip","eklem","ekmek","ekol","ekonomi","ekose","ekran","ekvator","elaman","elastik","elbet","elbise","elçi","eldiven","elebaşı","eleştiri","elma","eloğlu","elveda","emare","emekçi","emisyon","emniyet","empati","emsal","emzik","endüstri","enerji","engebe","engin","enişte","enkaz","entari","entegre","entrika","enzim","erdem","ergen","erguvan","erkek","erozyon","ertesi","erzak","esaret","esenlik","eser","eski","esnek","eşarp","eşofman","eşraf","eşya","eşzaman","etik","etken","etkinlik","etüt","evet","evire","evrak","evrim","eyalet","eyvah","ezber","fabrika","fanatik","fanus","fason","fasulye","fatih","fatura","fauna","favori","fayans","fayton","fazıl","fazilet","federal","felsefe","fener","feribot","fersah","fesih","festival","feveran","feza","fıçı","fıldır","fındık","fırça","fırsat","fırtına","fıtık","fidan","fidye","figür","fihrist","fikir","fildişi","filo","filtre","fincan","firuze","fitil","fiyaka","fizik","flaş","flüt","fosil","fren","fukara","futbol","garabet","gariban","garnitür","gazi","gece","gedik","gelenek","gelin","gemi","genç","geniş","geometri","gerçek","gevrek","gezegen","gezgin","geziyolu","gıcık","gıda","gıybet","girdap","girişim","gitar","giyecek","giysi","gizem","gofret","goril","göbek","göçebe","göğüs","gökdelen","gökmen","gökyüzü","gölge","gömlek","gönül","görenek","görkemli","görsel","gösteri","gövde","gözaltı","gözcü","gözdağı","gözleme","gözyaşı","grup","gurbet","gusül","gübre","güfte","gümüş","günaydın","güncel","gündüz","güneş","günyüzü","gürbüz","güvercin","güzel","haber","hacamat","hacim","hademe","hafız","hafriyat","hafta","hakan","hakem","hakikat","haksever","halı","hançer","hane","hangar","hapis","hapşırık","harf","haseki","hasret","hatun","havuç","haylaz","haysiyet","hayvan","hedef","hektar","hemen","hemfikir","hendek","hepsi","hergele","herhangi","hesap","heyecan","heykel","hezimet","hıçkırık","hızölçer","hicviye","hikaye","hikmet","hile","hisse","hobi","hoca","horlama","hormon","hoşbeş","hoşgörü","hoyrat","hörgüç","höyük","hudut","hukuk","hunhar","hurda","huysuz","huzur","hücum","hükümet","hünkar","hüviyet","ırmak","ısıölçer","ısıtıcı","ıspanak","ısrar","ışıldak","ızdırap","ızgara","ibadet","icat","içbükey","içecek","içgüdü","içsel","idman","iftihar","iğne","ihanet","ihbar","ihdas","ihmal","ihracat","ihsan","ikilem","ikindi","ikircik","iklim","iksir","iktibas","ilaç","ilçe","ileri","iletişim","ilgi","ilhak","ilkbahar","ilkokul","ilmek","imkan","imleç","imsak","imtihan","imza","ince","inkar","inşa","ipek","ipucu","irade","irfan","irmik","isabet","iskele","israf","isyan","işçi","işgal","işgüzar","işlem","itibar","itiraf","ivedi","ivme","iyileşme","iyimser","izbandut","izci","izdiham","izin","jakoben","jandarma","jargon","kabadayı","kablo","kabus","kaçamak","kadeh","kadın","kadraj","kafa","kafkas","kağıt","kağnı","kahkaha","kahraman","kahvaltı","kakül","kaldırım","kale","kalibre","kalkan","kalpak","kamış","kamyon","kanat","kandaş","kanepe","kanser","kanun","kaos","kapı","kaplıca","kaptan","karanlık","kardeş","karga","karınca","karmaşa","karşıt","kasırga","kask","kasvet","katkı","katman","kavram","kaygan","kaynakça","kayyum","kedi","kehanet","kekik","kelebek","kenar","kerkenez","kerpiç","kesirli","kesmece","kestane","keşkek","ketçap","keyfiyet","kıble","kıdemli","kılavuz","kılçık","kılıf","kıraç","kırmızı","kırsal","kısayol","kısım","kıskanç","kısmet","kışla","kıvanç","kıvılcım","kıvrık","kıyafet","kıymetli","kızak","kızılcık","kibar","kinaye","kira","kiremit","kirli","kirpik","kişisel","kitap","koçbaşı","kodaman","koğuş","kokteyl","kolaycı","kolbastı","kolonya","koltuk","kolye","kombine","komedyen","komiser","komposto","komşu","komuta","konak","konfor","koni","konsül","kopya","korkusuz","korna","korse","korunak","korvet","kostüm","koşul","koyu","kozmik","köfte","kökensel","köprücük","köpük","kördüğüm","körfez","köstebek","köşegen","kötü","kravat","kriter","kuantum","kudurma","kuluçka","kulübe","kumanya","kumbara","kumlu","kumpir","kumral","kundura","kupa","kupkuru","kuramsal","kurbağa","kurdele","kurgu","kurmay","kurşun","kurtuluş","kurultay","kurye","kusursuz","kuşak","kuşbaşı","kuşkulu","kutlama","kutsal","kutup","kuver","kuyruk","kuzey","kuzgun","küçük","külçe","külfet","külliye","kültürel","kümes","künefe","küresel","kütle","lahana","lahmacun","lamba","lansman","lavaş","layık","leğen","levent","leziz","lezzet","lider","likide","liman","liste","litre","liyakat","lodos","lokanta","lokman","lokum","lunapark","lütfen","lüzum","nokta","mabet","macera","macun","madalya","madde","madem","mağara","mağdur","mağfiret","mağlup","mahalle","mahcup","mahir","mahkeme","mahlas","mahrum","mahsul","makas","makbuz","makine","makro","maksat","makul","maliye","manav","mangal","manidar","manken","mantık","manzara","mareşal","margarin","marifet","market","marmelat","masaüstü","masmavi","masraf","masum","matah","materyal","matrak","maval","mavra","maydanoz","mayhoş","maytap","mazbata","mazeret","mazlum","mazot","mazur","meblağ","mebus","mecaz","mecbur","meclis","mecmua","mecnun","meçhul","medeni","mehtap","mekanik","melodi","meltem","memur","mendil","menekşe","menteşe","meraklı","mercek","merdiven","merhaba","merinos","merkez","mermi","mert","mesafe","mesele","mesken","meslek","meşale","meşgul","meşhur","metafor","metin","metre","mevcut","mevkidaş","meydan","meyil","meyve","meziyet","mezun","mıknatıs","mısra","mızıka","miğfer","mihrak","mikrofon","miktar","milat","milli","mimar","minare","mineral","minik","minyon","mirliva","misafir","miskin","miting","miyop","mizah","mobilya","model","monitör","morötesi","motive","motor","mozaik","muavin","mucize","muhafız","muhteşem","mukayese","mumya","musluk","muşamba","mutabık","mutfak","mutlu","muzaffer","muzdarip","mübarek","mücadele","müdür","müfredat","müftü","mühendis","mühim","mühlet","mükemmel","mülk","mümkün","mümtaz","müsrif","müstesna","müşahit","müşteri","mütercim","müthiş","müze","müzik","nabız","nadas","nadir","nahoş","nakarat","nakış","nalbur","namlu","namus","nankör","nargile","narkoz","nasıl","nasip","naylon","nazar","nazım","nazik","neden","nefes","negatif","neon","neptün","nerede","nesil","nesnel","neşeli","netice","nevresim","neyse","neyzen","nezaket","nezih","nezle","nicel","nilüfer","nimet","nisan","nispet","nitekim","nizam","nohut","noksan","normal","nostalji","noter","nöbet","numara","numune","nutuk","nüfus","obabaşı","obez","obje","ocak","odun","ofansif","ofis","oğlak","oğuz","okçu","oklava","oksijen","okul","okumuş","okutman","okuyucu","okyanus","olağan","olanak","olası","olay","olgun","olimpik","olumlu","omlet","omurga","onarım","onursal","opera","optik","oral","orantı","ordu","organik","orijin","orkide","orman","orta","oruç","otağ","otantik","otel","otoban","otogar","otomobil","otonom","otopark","otorite","otoyol","oturum","oyuk","oyuncak","ozan","ödeme","ödenek","ödev","ödül","ödünç","öfke","öğlen","öğrenci","öğün","öğütücü","öksürük","ölçme","ölçü","ölümsüz","ömür","önayak","öncü","önder","önem","önerge","öngörü","önlük","önsezi","öpücük","ördek","örgü","örtbas","örtme","örtü","örümcek","örüntü","öteberi","öteki","övünç","öykü","öyleyse","özçekim","özdeyiş","özel","özenti","özerk","özgürlük","özlem","özlü","özne","özsever","özümseme","özür","özveri","pabuç","padişah","palamut","palmiye","palto","palyaço","pamuk","panayır","pancar","panda","panel","panik","panjur","pankart","pano","pansuman","pantolon","panzehir","papatya","papyon","paraşüt","parça","pardösü","parfüm","parıltı","parkur","parmak","parodi","parsel","partner","pasaport","pasif","paskalya","pastırma","paşa","patates","paten","patika","patlıcan","patolog","patron","payanda","paydaş","payidar","paylaşma","paytak","peçete","pedal","peder","pehlivan","pekala","pekmez","pelerin","pelikan","pelüş","pembe","pena","pencere","pense","perçin","perde","pergel","perişan","peron","personel","perşembe","peruk","pervane","pespaye","pestil","peşin","petek","petrol","petunya","peynir","peyzaj","pınar","pırasa","pırlanta","pide","pikap","piknik","pilav","piliç","pilot","pipet","pipo","piramit","pirinç","pirzola","pist","pişik","pişman","piyasa","piyes","plaj","plaket","planlama","platform","plazma","podyum","poğaça","polat","polen","politika","pompa","popüler","porselen","portakal","posa","poster","poşet","poyraz","pozitif","pranga","pratik","prenses","prim","problem","profil","program","proje","protokol","prova","puan","pudra","pusula","püre","pürüz","püstül","püsür","racon","radar","radikal","radyo","rafadan","rafine","rağbet","rahat","rahle","rakam","raket","rakip","rakun","ralli","rampa","randevu","ranza","rapor","rastgele","rasyonel","razı","realite","reçine","refah","referans","refik","reform","rehber","rehin","reis","rekabet","reklam","rekor","rektör","renk","resim","resmen","restoran","retorik","revaç","revize","reyon","rezalet","rezerv","rezil","rıhtım","rıza","ritim","ritüel","rivayet","robot","roman","rota","rozet","röportaj","rötar","ruble","ruhban","ruhsat","rulet","rulo","runik","rutin","rutubet","rüşvet","rütbe","rüya","rüzgar","sabah","sabıka","sabit","sabun","saçma","sade","sadık","safahat","safdil","safkan","sağanak","sağduyu","sağlam","saha","sahiden","sahne","sakal","sakız","sakin","saklama","saksağan","salamura","salça","salgı","salınım","salkım","salon","saltanat","sanatçı","sancak","sandalye","saniye","saplantı","sapsız","saray","sarışın","sarkık","sarmaşık","satır","savaşım","savunma","saydam","sayfa","saygın","sayısal","sebep","seçenek","seçim","seçkin","seçmen","seda","sedir","sedye","sefer","sehpa","sekizgen","selektör","selvi","semavi","sembol","seminer","senaryo","sendika","senkron","sensör","sentez","sepet","seramik","serbest","serdar","seremoni","sergi","serhat","serin","sermaye","serpuş","sersem","serüven","servis","sesli","sesteş","sevap","seviye","seyahat","seyirci","sezon","sıcak","sıfat","sıhhi","sınanma","sınır","sıradan","sırdaş","sırma","sırtüstü","sızgıt","siftah","sigorta","sihirbaz","silah","silecek","silindir","simetri","simge","simit","sincap","sindirim","sinema","sinirli","sipariş","sirke","siroz","sistem","sivilce","siyasi","slogan","soba","sofra","soğuk","sohbet","sokak","solfej","solunum","somut","sonbahar","sonraki","sonsuz","sorunsuz","sosyete","soyağacı","soydaş","soygun","soytarı","söğüş","sömürge","sönük","söylem","sözcük","sözde","spatula","spektrum","spiker","spiral","sponsor","sporcu","sprey","stabil","statü","stok","stopaj","strateji","subay","sucuk","suçüstü","suhulet","sulama","sungur","sunucu","surat","susam","suskun","sükse","sükut","sülale","sünger","süpürge","sürahi","süreç","sürgün","sürüm","süsleme","sütanne","sütlaç","sütun","süvari","şahane","şahbaz","şahit","şahsiyet","şakıma","şaklaban","şakrak","şamar","şampiyon","şanslı","şantiye","şapka","şarkıcı","şartname","şaşırma","şaşkın","şatafat","şayet","şebeke","şefkat","şeftali","şehir","şehvet","şeker","şekil","şelale","şema","şemsiye","şerbet","şeref","şerit","şımarık","şıpıdık","şifre","şimdi","şimşek","şipşak","şirin","şişe","şişirme","şofben","şöhret","şölen","şüphe","tabaka","tabela","tabure","tadilat","taharet","tahıl","tahkim","tahlil","tahmin","tahrifat","tahsilat","tahta","taklit","takoz","taksici","taktik","takvim","talebe","talip","tamamen","tamirci","tampon","tamtakır","tandır","tanecik","tanıtım","tanrı","tansiyon","tapan","tapınak","taptaze","tapu","tarafgir","tarhana","tarım","tarih","tarla","tartak","tarumar","tasarım","tasdik","taslak","tastamam","taşeron","taşınmaz","taşra","tatava","tatbikat","tatil","tatlı","tavsiye","tavşan","tavuk","taze","taziye","tazminat","tebeşir","tebrik","tecrübe","teçhizat","tedarik","tedbir","teftiş","teğet","teğmen","tehdit","tehlike","tekdüze","tekerlek","tekme","teknik","tekrar","telef","telsiz","telve","temas","tembel","temiz","temkin","tempo","temsilci","tendon","teneke","tenha","tenkit","tepegöz","tepki","terazi","terbiye","tercih","tereyağı","terfi","terim","terminal","tersane","tertip","tesadüf","tescil","tesir","teslimat","tespit","testere","teşekkür","teşhir","teşrif","teşvik","teyze","tezahür","tezgah","tıbbi","tıkaç","tıkışık","tıknaz","tılsım","tıpkı","tıraş","tırışka","tırmanış","tırnak","tırpan","tıslama","ticaret","tilki","tiryaki","titreşim","tohum","tokat","tolere","tomar","tombak","tomurcuk","topaç","toplum","toprak","toptan","toraman","torpido","tortu","tosbağa","toynak","tören","trafik","trajedi","tramvay","transfer","tribün","triko","tugay","tuğla","tuğrul","tuhaf","tulumba","tunç","turan","turkuaz","turnusol","turşu","turuncu","tutanak","tutkal","tutsak","tutum","tuyuğ","tuzlu","tüccar","tüfek","tükenmez","tülbent","tümleç","tünel","türbin","türev","türk","tüzük","ucube","ucuz","uçak","uçurtma","ufuk","uğrak","uğur","ukala","ulaşım","ulema","ulus","ulvi","umursama","umut","unutkan","uslu","ustabaşı","ustura","usul","utangaç","uyanık","uyarı","uydu","uygar","uygulama","uykusuz","uysal","uyuşma","uzantı","uzay","uzgören","uzlaşma","uzman","uzun","ücra","ücret","üçbudak","üçgen","üçkağıt","üçleme","üfürük","ülke","ümit","üniforma","ünite","ünlem","üretken","ürün","üslup","üstel","üstün","üşengeç","üşüme","ütopya","üvey","üzengi","üzgün","üzüm","vagon","vaka","vakfiye","vakıf","vakit","vakum","vapur","varil","varlık","varsayım","varyemez","vasıta","vasiyet","vatandaş","vazife","vazo","veciz","vefa","vehim","veliaht","veresiye","verimli","verkaç","vernik","vertigo","vesait","vesika","vestiyer","veznedar","vicdan","vilayet","virane","virgül","vişne","vites","vokal","volkan","vurma","vurucu","vücut","yabancı","yabgu","yağış","yağlı","yağmur","yakamoz","yakın","yaklaşık","yalçın","yalıtım","yaman","yanardağ","yangın","yanıt","yankı","yanlış","yansıma","yapay","yapboz","yapımcı","yaprak","yaratık","yarbay","yardım","yargıç","yarıçap","yasemin","yastık","yaşam","yatak","yatırım","yavru","yaygara","yayıncı","yayla","yazılım","yekpare","yekvücut","yelkovan","yelpaze","yemek","yemiş","yengeç","yeniçeri","yeraltı","yerküre","yerleşke","yeryüzü","yeşil","yetenek","yetkili","yığınak","yıkama","yılbaşı","yıldırım","yılkı","yılmaz","yırtıcı","yiğit","yoğurt","yokuş","yolcu","yoldaş","yolgeçen","yolkesen","yolüstü","yordam","yorgan","yorumcu","yosun","yöndeş","yönetim","yönlü","yöntem","yöresel","yörünge","yufka","yukarı","yumruk","yumurta","yuvarlak","yücelme","yükçeker","yüklem","yüksek","yürek","yürütme","yüzde","yüzeysel","yüzgeç","yüzüstü","yüzyıl","zabıta","zafer","zahmet","zambak","zaptiye","zarafet","zaruret","zeka","zekice","zemberek","zemin","zencefil","zeplin","zeytin","zıbın","zılgıt","zımbırtı","zımpara","zıpkın","zigon","zihinsel","zihniyet","zincir","zindan","zirzop","ziyaret","ziynet","zoraki","zorlu","zorunlu","züğürt","zümre"]const b4a = require('b4a')

function byteLength (size) {
  return Math.ceil(size / 8)
}

function get (buffer, bit) {
  const n = buffer.BYTES_PER_ELEMENT * 8

  const offset = bit & (n - 1)
  const i = (bit - offset) / n

  return (buffer[i] & (1 << offset)) !== 0
}

function set (buffer, bit, value = true) {
  const n = buffer.BYTES_PER_ELEMENT * 8

  const offset = bit & (n - 1)
  const i = (bit - offset) / n
  const mask = 1 << offset

  if (value) {
    if ((buffer[i] & mask) !== 0) return false
  } else {
    if ((buffer[i] & mask) === 0) return false
  }

  buffer[i] ^= mask
  return true
}

function setRange (buffer, start, end, value = true) {
  const n = buffer.BYTES_PER_ELEMENT * 8

  let remaining = end - start
  let offset = start & (n - 1)
  let i = (start - offset) / n

  let changed = false

  while (remaining > 0) {
    const mask = (2 ** Math.min(remaining, n - offset) - 1) << offset

    if (value) {
      if ((buffer[i] & mask) !== mask) {
        buffer[i] |= mask
        changed = true
      }
    } else {
      if ((buffer[i] & mask) !== 0) {
        buffer[i] &= ~mask
        changed = true
      }
    }

    remaining -= n - offset
    offset = 0
    i++
  }

  return changed
}

function fill (buffer, value, start = 0, end = buffer.byteLength * 8) {
  const n = buffer.BYTES_PER_ELEMENT * 8
  let i, j

  {
    const offset = start & (n - 1)
    i = (start - offset) / n

    if (offset !== 0) {
      const mask = (2 ** Math.min(n - offset, end - start) - 1) << offset

      if (value) buffer[i] |= mask
      else buffer[i] &= ~mask

      i++
    }
  }

  {
    const offset = end & (n - 1)
    j = (end - offset) / n

    if (offset !== 0 && j >= i) {
      const mask = (2 ** offset) - 1

      if (value) buffer[j] |= mask
      else buffer[j] &= ~mask
    }
  }

  return buffer.fill(value ? (2 ** n) - 1 : 0, i, j)
}

function toggle (buffer, bit) {
  const n = buffer.BYTES_PER_ELEMENT * 8

  const offset = bit & (n - 1)
  const i = (bit - offset) / n
  const mask = 1 << offset

  buffer[i] ^= mask
  return (buffer[i] & mask) !== 0
}

function remove (buffer, bit) {
  return set(buffer, bit, false)
}

function removeRange (buffer, start, end) {
  return setRange(buffer, start, end, false)
}

function indexOf (buffer, value, position = 0) {
  for (let i = position, n = buffer.byteLength * 8; i < n; i++) {
    if (get(buffer, i) === value) return i
  }

  return -1
}

function lastIndexOf (buffer, value, position = buffer.byteLength * 8 - 1) {
  for (let i = position; i >= 0; i--) {
    if (get(buffer, i) === value) return i
  }

  return -1
}

function of (...bits) {
  return from(bits)
}

function from (bits) {
  const buffer = b4a.alloc(byteLength(bits.length))
  for (let i = 0; i < bits.length; i++) set(buffer, i, bits[i])
  return buffer
}

function * iterator (buffer) {
  for (let i = 0, n = buffer.byteLength * 8; i < n; i++) yield get(buffer, i)
}

module.exports = {
  byteLength,
  get,
  set,
  setRange,
  fill,
  toggle,
  remove,
  removeRange,
  indexOf,
  lastIndexOf,
  of,
  from,
  iterator
}
{
  "name": "bits-to-bytes",
  "version": "1.3.0",
  "description": "Functions for doing bit manipulation of typed arrays",
  "main": "index.js",
  "files": [
    "index.js"
  ],
  "scripts": {
    "test": "standard && brittle test.mjs"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bits-to-bytes.git"
  },
  "author": "Kasper Isager Dalsgarð <kasper@funktionel.co>",
  "license": "ISC",
  "bugs": {
    "url": "https://github.com/holepunchto/bits-to-bytes/issues"
  },
  "homepage": "https://github.com/holepunchto/bits-to-bytes#readme",
  "dependencies": {
    "b4a": "^1.5.0"
  },
  "devDependencies": {
    "brittle": "^2.3.1",
    "standard": "^17.0.0"
  }
}
const { EventEmitter } = require('events')
const sodium = require('sodium-universal')
const crypto = require('hypercore-crypto')
const b4a = require('b4a')
const c = require('compact-encoding')

const {
  Invite,
  ResponsePayload,
  InviteRequest,
  InviteResponse,
  InviteData,
  InviteReceipt,
  PersistedRequest,
  AuthData
} = require('./lib/messages')

const {
  PAIRING_REJECTED,
  INVITE_USED,
  INVITE_EXPIRED
} = require('./lib/errors')

const [
  NS_SIGNATURE,
  NS_TOKEN,
  NS_INVITE_ID,
  NS_REQUEST_ID,
  NS_SESSION,
  NS_SESSION_KEY,
  NS_ENCRYPT,
  NS_NONCE
] = crypto.namespace('blind-pairing', 8)

class CandidateRequest extends EventEmitter {
  constructor(invite, userData, opts = {}) {
    super()

    if (b4a.isBuffer(invite)) {
      invite = c.decode(Invite, invite)
    }

    this.discoveryKey = invite.discoveryKey
    this.seed = invite.seed

    this.keyPair = crypto.keyPair(this.seed)
    this.inviteId = deriveInviteId(this.keyPair.publicKey)
    this.userData = userData

    this.token = deriveToken(this.keyPair.publicKey, userData)
    this.session = opts.session || createSessionToken(this.token)
    this.id = deriveRequestId(this.session)

    this.payload = createAuth(this.userData, this.keyPair, this.session)

    this._encoded = null

    // set in reply
    this.auth = null
  }

  static from(buf) {
    const info = c.decode(PersistedRequest, buf)
    const { seed, discoveryKey, userData } = info
    const request = new CandidateRequest({ discoveryKey, seed }, userData)

    // clear completed request
    if (info.key) {
      request.key = info.key
      request.token = null
      request.payload = null
    }

    return request
  }

  handleResponse(payload) {
    if (b4a.isBuffer(payload)) {
      payload = this._decodeResponse(payload)
    }

    try {
      this._openResponse(payload)
    } catch (err) {
      this.emit('rejected', err)
      return null
    }

    this._onAccept()

    return this.auth
  }

  _openResponse(payload) {
    try {
      const response = openReply(
        payload,
        this.payload.session,
        this.keyPair.publicKey
      )
      this.response = c.decode(ResponsePayload, response)
    } catch (e) {
      throw new Error('Could not decrypt reply.')
    }

    const { status, key, encryptionKey, additional } = this.response

    if (status !== 0) {
      switch (status) {
        case 1:
          throw PAIRING_REJECTED()

        case 2:
          throw INVITE_USED()

        case 3:
          throw INVITE_EXPIRED()
      }
    }

    if (b4a.compare(crypto.discoveryKey(key), this.discoveryKey)) {
      throw new Error('Invite response does not match discoveryKey')
    }

    if (
      additional &&
      !crypto.verify(
        additional.data,
        additional.signature,
        this.keyPair.publicKey
      )
    ) {
      throw new Error('Additional data failed verification')
    }

    this.auth = {
      key,
      encryptionKey,
      data: additional ? additional.data : null
    }
  }

  _onAccept() {
    this.emit('accepted', this.auth)
    this.destroy()
  }

  _decodeResponse(buf) {
    try {
      const { payload } = c.decode(InviteResponse, buf)
      return payload
    } catch {
      throw new Error('Could not decode response.')
    }
  }

  destroy() {
    this.token = null
    this.payload = null

    this.emit('destroyed')
  }

  encode() {
    if (!this._encoded) this._encoded = c.encode(InviteRequest, this)
    return this._encoded
  }
}

class MemberRequest {
  constructor(inviteId, requestData) {
    this.inviteId = inviteId
    this.requestData = requestData

    this._opened = false
    this._confirmed = false
    this._denied = false

    // set by transport
    this.discoveryKey = null

    // set in open
    this.publicKey = null
    this.userData = null
    this.session = null
    this.id = null
    this.receipt = null

    // set in confirm/respond
    this._payload = null
    this.response = null
  }

  static from(req) {
    if (b4a.isBuffer(req)) {
      return MemberRequest.from(c.decode(InviteRequest, req))
    }

    return new MemberRequest(req.inviteId, req.payload)
  }

  confirm({ key, encryptionKey, additional }) {
    if (this._confirmed || this._denied || !this._opened) return
    this._confirmed = true

    const payload = c.encode(ResponsePayload, {
      status: 0,
      key,
      encryptionKey,
      additional
    })
    this._payload = createReply(payload, this.session, this.publicKey)

    this._respond()
  }

  deny({ status = 1 } = {}) {
    if (this._confirmed || this._denied) return
    this._denied = true

    if (!status) return

    const payload = c.encode(ResponsePayload, {
      status,
      key: null,
      encryptionKey: null,
      additional: null
    })
    this._payload = createReply(payload, this.session, this.publicKey)

    this._respond()
  }

  respond() {
    return {
      id: this.id,
      payload: this._payload
    }
  }

  _respond() {
    this.response = c.encode(InviteResponse, this.respond())
  }

  open(publicKey) {
    if (this._opened && b4a.equals(this.publicKey, publicKey))
      return this.userData

    try {
      this.receipt = openAuth(this.requestData, publicKey)
      const { userData, session } = c.decode(InviteReceipt, this.receipt)

      this.userData = userData
      this.session = session
      this.token = deriveToken(publicKey, userData)
      this.id = deriveRequestId(this.session)
    } catch (e) {
      throw new Error('Failed to open invite with provided key')
    }

    this.publicKey = publicKey
    this._opened = true

    return this.userData
  }
}

module.exports.CandidateRequest = CandidateRequest
module.exports.MemberRequest = MemberRequest
module.exports.createInvite = createInvite
module.exports.decodeInvite = decodeInvite
module.exports.verifyReceipt = verifyReceipt
module.exports.createReceipt = createReceipt
module.exports.Invite = Invite

function createReceipt(invite, userData) {
  const req = new CandidateRequest(invite, userData) // yolo, refactor
  const receipt = openAuth(req.payload, req.keyPair.publicKey)
  return { id: deriveInviteId(req.keyPair.publicKey), receipt }
}

function verifyReceipt(receipt, publicKey) {
  if (b4a.isBuffer(receipt)) {
    receipt = c.decode(InviteReceipt, receipt)
  }

  const { session, signature, userData } = receipt
  const signData = c.encode(AuthData, { userData, session })

  if (!verifySignature(signData, signature, publicKey)) return null

  return userData
}

function deriveInviteId(publicKey) {
  return crypto.hash([NS_INVITE_ID, publicKey])
}

function deriveKey(publicKey) {
  const out = b4a.allocUnsafe(
    sodium.crypto_aead_xchacha20poly1305_ietf_KEYBYTES
  )
  return crypto.hash([NS_ENCRYPT, publicKey], out)
}

function deriveNonce(publicKey, sessionToken) {
  const out = b4a.allocUnsafe(
    sodium.crypto_aead_xchacha20poly1305_ietf_NPUBBYTES
  )
  return crypto.hash([NS_NONCE, publicKey, sessionToken], out)
}

function deriveToken(publicKey, userData) {
  return crypto.hash([NS_TOKEN, publicKey, userData])
}

function createSessionToken(token) {
  return crypto.hash([NS_SESSION, token])
}

function deriveRequestId(sessionToken) {
  return crypto.hash([NS_REQUEST_ID, sessionToken])
}

function createInvite(key, opts = {}) {
  const {
    discoveryKey = crypto.discoveryKey(key),
    expires = 0,
    seed = crypto.randomBytes(32),
    sensitive = false,
    data,
    testInvitation = false,
    additionalNodes
  } = opts

  const keyPair = crypto.keyPair(seed)
  const additional = data
    ? {
        data,
        signature: crypto.sign(data, keyPair.secretKey)
      }
    : null

  return {
    id: deriveInviteId(keyPair.publicKey),
    invite: c.encode(Invite, {
      seed,
      discoveryKey,
      expires,
      sensitive,
      testInvitation,
      additionalNodes
    }),
    seed,
    publicKey: keyPair.publicKey,
    additional,
    discoveryKey,
    expires,
    sensitive,
    testInvitation
  }
}

function decodeInvite(invite) {
  const data = c.decode(Invite, invite)
  return {
    id: deriveInviteId(crypto.keyPair(data.seed).publicKey),
    ...data
  }
}

function encrypt(data, nonce, secretKey) {
  const output = b4a.allocUnsafe(
    data.byteLength + sodium.crypto_aead_xchacha20poly1305_ietf_ABYTES
  )
  sodium.crypto_aead_xchacha20poly1305_ietf_encrypt(
    output,
    data,
    nonce,
    null,
    nonce,
    secretKey
  )
  return output
}

function decrypt(data, nonce, secretKey) {
  const output = b4a.allocUnsafe(
    data.byteLength - sodium.crypto_aead_xchacha20poly1305_ietf_ABYTES
  )
  sodium.crypto_aead_xchacha20poly1305_ietf_decrypt(
    output,
    null,
    data,
    nonce,
    nonce,
    secretKey
  )
  return output
}

function createAuth(userData, invitationKeyPair, session) {
  const secret = deriveKey(invitationKeyPair.publicKey)

  const nonce = deriveNonce(invitationKeyPair.publicKey, session)
  const signData = c.encode(AuthData, { userData, session })
  const signature = createSignature(signData, invitationKeyPair.secretKey)

  const inviteData = c.encode(InviteData, { userData, signature })
  const data = encrypt(inviteData, nonce, secret)

  return {
    session,
    data
  }
}

function openAuth(payload, invitationKey) {
  const secret = deriveKey(invitationKey)

  const { session, data } = payload

  const nonce = deriveNonce(invitationKey, session)

  let plaintext
  try {
    plaintext = decrypt(data, nonce, secret)
  } catch {
    // todo stronger check
    throw new Error('Decryption failed.')
  }

  const { userData, signature } = c.decode(InviteData, plaintext)
  const receipt = { session, signature, userData }

  if (!verifyReceipt(receipt, invitationKey)) {
    throw new Error('Invalid reply')
  }

  return c.encode(InviteReceipt, { session, signature, userData })
}

function createReply(payload, sessionToken, invitationKey) {
  const sessionKey = crypto.hash([NS_SESSION_KEY, invitationKey, sessionToken])
  const secret = deriveKey(sessionKey)
  const nonce = deriveNonce(sessionKey, sessionToken)

  return encrypt(payload, nonce, secret)
}

function openReply(data, sessionToken, invitationKey) {
  const sessionKey = crypto.hash([NS_SESSION_KEY, invitationKey, sessionToken])
  const secret = deriveKey(sessionKey)
  const nonce = deriveNonce(sessionKey, sessionToken)

  return decrypt(data, nonce, secret)
}

function createSignature(data, secretKey) {
  const signature = b4a.allocUnsafe(sodium.crypto_sign_BYTES)
  const namespaced = b4a.allocUnsafe(32 + data.byteLength)

  namespaced.set(NS_SIGNATURE, 0)
  namespaced.set(data, 32)

  sodium.crypto_sign_detached(signature, namespaced, secretKey)

  return signature
}

function verifySignature(data, signature, publicKey) {
  const namespaced = b4a.allocUnsafe(32 + data.byteLength)

  namespaced.set(NS_SIGNATURE, 0)
  namespaced.set(data, 32)

  return sodium.crypto_sign_verify_detached(signature, namespaced, publicKey)
}
module.exports = class PairingError extends Error {
  constructor(msg, code, fn = PairingError) {
    super(`${code}: ${msg}`)
    this.code = code

    if (Error.captureStackTrace) {
      Error.captureStackTrace(this, fn)
    }
  }

  get name() {
    return 'PairingError'
  }

  static PAIRING_REJECTED(msg = 'Pairing was rejected') {
    return new PairingError(
      msg,
      'PAIRING_REJECTED',
      PairingError.PAIRING_REJECTED
    )
  }

  static INVITE_USED(msg = 'Invite has been used') {
    return new PairingError(msg, 'INVITE_USED', PairingError.INVITE_USED)
  }

  static INVITE_EXPIRED(msg = 'Invite has expireds') {
    return new PairingError(msg, 'INVITE_EXPIRED', PairingError.INVITE_EXPIRED)
  }
}
const { isBogon } = require('bogon')
const c = require('compact-encoding')

const DHTNodes = c.array(c.ipv4Address)

const Invite = {
  preencode(state, i) {
    state.end++ // version
    state.end++ // flags
    c.fixed32.preencode(state, i.seed)
    if (i.discoveryKey) c.fixed32.preencode(state, i.discoveryKey)
    if (i.expires) c.uint32.preencode(state, Math.floor(i.expires / 1000)) // store as secs
    if (i.additionalNodes && Array.isArray(i.additionalNodes))
      DHTNodes.preencode(
        state,
        i.additionalNodes.filter((node) => !isBogon(node.host))
      )
  },
  encode(state, i) {
    c.uint.encode(state, 1) // version
    c.uint.encode(
      state,
      (i.discoveryKey ? 1 : 0) |
        (i.expires ? 2 : 0) |
        (i.sensitive ? 4 : 0) |
        (i.testInvitation ? 8 : 0) |
        (i.additionalNodes && Array.isArray(i.additionalNodes) ? 16 : 0)
    )
    c.fixed32.encode(state, i.seed)
    if (i.discoveryKey) c.fixed32.encode(state, i.discoveryKey)
    if (i.expires) c.uint32.encode(state, Math.floor(i.expires / 1000))
    if (i.additionalNodes && Array.isArray(i.additionalNodes))
      DHTNodes.encode(
        state,
        i.additionalNodes.filter((node) => !isBogon(node.host))
      )
  },
  decode(state) {
    const version = c.uint.decode(state)
    if (version !== 1) {
      throw new Error('Unknown invite version')
    }

    const flags = c.uint.decode(state)

    return {
      seed: c.fixed32.decode(state),
      discoveryKey: flags & 1 ? c.fixed32.decode(state) : null,
      expires: flags & 2 ? c.uint32.decode(state) * 1000 : 0,
      sensitive: (flags & 4) !== 0,
      testInvitation: (flags & 8) !== 0,
      additionalNodes: flags & 16 ? DHTNodes.decode(state) : null
    }
  }
}

const RequestPayload = {
  preencode(state, p) {
    c.optionalBuffer.preencode(state, p.session)
    c.optionalBuffer.preencode(state, p.data)
  },
  encode(state, p) {
    c.optionalBuffer.encode(state, p.session)
    c.optionalBuffer.encode(state, p.data)
  },
  decode(state) {
    return {
      session: c.optionalBuffer.decode(state),
      data: c.optionalBuffer.decode(state)
    }
  }
}

const ResponseStatus = c.uint

const AdditionalData = {
  preencode(state, m) {
    c.optionalBuffer.preencode(state, m.data)
    c.fixed64.preencode(state, m.signature)
  },
  encode(state, m) {
    c.optionalBuffer.encode(state, m.data)
    c.fixed64.encode(state, m.signature)
  },
  decode(state) {
    return {
      data: c.optionalBuffer.decode(state),
      signature: c.fixed64.decode(state)
    }
  }
}

const ResponsePayload = {
  preencode(state, p) {
    ResponseStatus.preencode(state, p.status)
    if (p.status !== 0) return

    let flags = 0
    if (p.encryptionKey) flags |= 1
    if (p.additional) flags |= 2
    c.uint.preencode(state, flags) // flags

    c.fixed32.preencode(state, p.key)
    if (p.encryptionKey) c.fixed32.preencode(state, p.encryptionKey)
    if (p.additional) AdditionalData.preencode(state, p.additional)
  },
  encode(state, p) {
    ResponseStatus.encode(state, p.status)
    if (p.status !== 0) return

    let flags = 0
    if (p.encryptionKey) flags |= 1
    if (p.additional) flags |= 2
    c.uint.encode(state, flags)

    c.fixed32.encode(state, p.key)
    if (p.encryptionKey) c.fixed32.encode(state, p.encryptionKey)
    if (p.additional) AdditionalData.encode(state, p.additional)
  },
  decode(state) {
    const status = ResponseStatus.decode(state)

    if (status !== 0) {
      return {
        status,
        key: null,
        encryptionKey: null,
        additional: null
      }
    }

    const flags = c.uint.decode(state)
    const key = c.fixed32.decode(state)
    const encryptionKey = (flags & 1) !== 0 ? c.fixed32.decode(state) : null
    const additional = (flags & 2) !== 0 ? AdditionalData.decode(state) : null

    return {
      status: 0,
      key,
      encryptionKey,
      additional
    }
  }
}

const InviteRequest = {
  preencode(state, i) {
    c.fixed32.preencode(state, i.inviteId)
    RequestPayload.preencode(state, i.payload)
  },
  encode(state, i) {
    c.fixed32.encode(state, i.inviteId)
    RequestPayload.encode(state, i.payload)
  },
  decode(state) {
    return {
      inviteId: c.fixed32.decode(state),
      payload: RequestPayload.decode(state)
    }
  }
}

const InviteResponse = {
  preencode(state, i) {
    c.fixed32.preencode(state, i.id)
    c.optionalBuffer.preencode(state, i.payload)
  },
  encode(state, i) {
    c.fixed32.encode(state, i.id)
    c.optionalBuffer.encode(state, i.payload)
  },
  decode(state) {
    return {
      id: c.fixed32.decode(state),
      payload: c.optionalBuffer.decode(state)
    }
  }
}

const InviteData = {
  preencode(state, i) {
    c.fixed64.preencode(state, i.signature)
    c.optionalBuffer.preencode(state, i.userData)
  },
  encode(state, i) {
    c.fixed64.encode(state, i.signature)
    c.optionalBuffer.encode(state, i.userData)
  },
  decode(state) {
    return {
      signature: c.fixed64.decode(state),
      userData: c.optionalBuffer.decode(state)
    }
  }
}

const InviteReceipt = {
  preencode(state, i) {
    c.fixed32.preencode(state, i.session)
    c.fixed64.preencode(state, i.signature)
    c.optionalBuffer.preencode(state, i.userData)
  },
  encode(state, i) {
    c.fixed32.encode(state, i.session)
    c.fixed64.encode(state, i.signature)
    c.optionalBuffer.encode(state, i.userData)
  },
  decode(state) {
    return {
      session: c.fixed32.decode(state),
      signature: c.fixed64.decode(state),
      userData: c.optionalBuffer.decode(state)
    }
  }
}

const AuthData = {
  preencode(state, i) {
    c.optionalBuffer.preencode(state, i.session)
    c.optionalBuffer.preencode(state, i.userData)
  },
  encode(state, i) {
    c.optionalBuffer.encode(state, i.session)
    c.optionalBuffer.encode(state, i.userData)
  },
  decode(state) {
    return {
      session: c.optionalBuffer.decode(state),
      userData: c.optionalBuffer.decode(state)
    }
  }
}

module.exports = {
  Invite,
  ResponsePayload,
  InviteRequest,
  InviteResponse,
  InviteData,
  InviteReceipt,
  AuthData
}
{
  "name": "blind-pairing-core",
  "version": "2.10.1",
  "description": "Core blind pairing module",
  "main": "index.js",
  "files": [
    "index.js",
    "lib/**.js"
  ],
  "scripts": {
    "format": "prettier . --write",
    "test": "prettier . --check && brittle test/*.js"
  },
  "imports": {
    "events": {
      "bare": "bare-events",
      "default": "events"
    }
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/blind-pairing-core.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/blind-pairing-core/issues"
  },
  "homepage": "https://github.com/holepunchto/blind-pairing-core#readme",
  "dependencies": {
    "b4a": "^1.6.4",
    "bare-events": "^2.5.0",
    "bogon": "^1.1.0",
    "compact-encoding": "^3.0.0",
    "hypercore-crypto": "^3.4.0",
    "sodium-universal": "^5.0.1",
    "tiny-buffer-map": "^1.1.1"
  },
  "devDependencies": {
    "brittle": "^3.2.1",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^1.0.0"
  }
}
const crypto = require('hypercore-crypto')
const b4a = require('b4a')
const safetyCatch = require('safety-catch')
const ReadyResource = require('ready-resource')
const Xache = require('xache')
const { MemberRequest, CandidateRequest, createInvite, decodeInvite, verifyReceipt, Invite } = require('blind-pairing-core')
const Protomux = require('protomux')
const c = require('compact-encoding')
const debounce = require('debounceify')
const isOptions = require('is-options')

const [NS_EPHEMERAL, NS_REPLY, NS_DISCOVERY] = crypto.namespace('blind-pairing/dht', 3)

const DEFAULT_POLL = 7 * 60 * 1000
const PEER_INTERVAL = 1000

class TimeoutPromise {
  constructor (ms) {
    this.ms = ms
    this.resolve = null
    this.timeout = null
    this.destroyed = false
    this.suspended = false

    this._resolveBound = this._resolve.bind(this)
    this._ontimerBound = this._ontimer.bind(this)
  }

  wait () {
    if (this.destroyed) return Promise.resolve()
    if (this.resolve) this._resolve()
    return new Promise(this._ontimerBound)
  }

  suspend () {
    this.suspended = true
    if (this.timeout !== null) clearTimeout(this.timeout)
    this.timeout = null
  }

  resume () {
    this.suspended = false
    if (this.resolve) this._resolve()
  }

  destroy () {
    this.destroyed = true
    if (this.resolve) this._resolve()
  }

  _ontimer (resolve) {
    this.resolve = resolve
    if (!this.suspended) this.timeout = setTimeout(this._resolveBound, this.ms)
  }

  _resolve () {
    if (this.timeout !== null) clearTimeout(this.timeout)

    const resolve = this.resolve
    this.timeout = null
    this.resolve = null

    resolve()
  }
}

class BlindPairing extends ReadyResource {
  constructor (swarm, { poll = DEFAULT_POLL, onincoming = noop } = {}) {
    super()

    this.swarm = swarm
    this.poll = poll
    this.active = new Map()
    this.suspended = false

    this._onincoming = onincoming
    this._onconnectionBound = this._onconnection.bind(this)
    this._refreshBound = this.refresh.bind(this)
    this._refreshing = null

    this.swarm.on('connection', this._onconnectionBound)
    this.swarm.dht.on('network-change', this._refreshBound)
  }

  static Invite = Invite

  static createInvite (key, opts) {
    return createInvite(key, opts)
  }

  static decodeInvite (invite) {
    return decodeInvite(invite)
  }

  static verifyReceipt (receipt, publicKey) {
    return verifyReceipt(receipt, publicKey)
  }

  static createRequest (invite, userData) {
    return new CandidateRequest(invite, userData)
  }

  async suspend () {
    if (this.suspended) return
    this.suspended = true

    const all = []

    for (const ref of this.active.values()) {
      if (ref.candidate) all.push(ref.candidate._suspend())
      if (ref.member) all.push(ref.member._suspend())
    }

    await Promise.allSettled(all)
  }

  resume () {
    if (!this.suspended) return
    this.suspended = false
    this.refresh().catch(safetyCatch) // no need to wait for the refreshes
  }

  async refresh () {
    if (this._refreshing) {
      await this._refreshing
      return
    }

    if (this.closing || this.suspended) return

    const r = this._refreshing = this._refresh()

    try {
      await r
    } finally {
      if (r === this._refreshing) this._refreshing = null
    }
  }

  async _refresh () {
    if (this.closing || this.suspended) return

    const all = []

    for (const ref of this.active.values()) {
      if (ref.candidate) all.push(ref.candidate.refresh())
      if (ref.member) all.push(ref.member.refresh())
    }

    await Promise.allSettled(all)
  }

  addMember (opts) {
    return new Member(this, opts)
  }

  addCandidate (request, opts) {
    if (isOptions(request)) return this.addCandidate(null, request)
    if (!request) request = new CandidateRequest(opts.invite, opts.userData)
    return new Candidate(this, request, opts)
  }

  async _close () {
    this.swarm.removeListener('connection', this._onconnectionBound)
    this.swarm.dht.removeListener('network-change', this._refreshBound)

    const all = []

    for (const conn of this.swarm.connections) {
      const mux = getMuxer(conn)
      mux.unpair({ protocol: 'blind-pairing' })
      for (const ref of this.active.values()) mux.unpair({ protocol: 'blind-pairing', id: ref.discoveryKey })
    }

    for (const ref of this.active.values()) {
      if (ref.member) all.push(ref.member.close())
      if (ref.candidate) all.push(ref.candidate.close())
      if (ref.discovery) all.push(ref.discovery.destroy())
    }

    await Promise.allSettled(all)
  }

  _randomPoll () {
    return randomInterval(this.poll)
  }

  _add (discoveryKey) {
    const id = b4a.toString(discoveryKey, 'hex')
    const t = this.active.get(id)
    if (t) return t

    const fresh = {
      id,
      discoveryKey,
      member: null,
      candidate: null,
      channels: new Set(),
      alwaysServer: false,
      alwaysClient: false,
      discovery: null
    }

    this.active.set(id, fresh)
    return fresh
  }

  _swarm (ref) {
    const server = ref.alwaysServer || !!ref.member
    const client = ref.alwaysClient || !!ref.candidate

    if (ref.discovery && ref.discovery.isServer === server && ref.discovery.isClient === client) {
      return
    }

    if (ref.discovery) ref.discovery.destroy().catch(safetyCatch)

    // just a sanity check, not needed but doesnt hurt
    if (!server && !client) return

    ref.discovery = this.swarm.join(ref.discoveryKey, { server, client })
    this._attachToSwarm(ref)
  }

  _attachToSwarm (ref) {
    for (const conn of this.swarm.connections) {
      const mux = getMuxer(conn)
      this._attachToMuxer(mux, ref.discoveryKey, ref)
    }
  }

  _gc (ref) {
    if (ref.member || ref.candidate) {
      if (ref.discovery) this._swarm(ref) // in case it needs updating...
      return false
    }
    this.active.delete(ref.id)
    for (const ch of ref.channels) ch.close()
    for (const conn of this.swarm.connections) {
      const mux = getMuxer(conn)
      mux.unpair({ protocol: 'blind-pairing', id: ref.discoveryKey })
    }
    if (ref.discovery) ref.discovery.destroy().catch(safetyCatch)
    return true
  }

  _onconnection (conn) {
    const mux = getMuxer(conn)

    mux.pair({ protocol: 'blind-pairing' }, this._onincoming)

    for (const ref of this.active.values()) {
      this._attachToMuxer(mux, ref.discoveryKey, ref)
    }
  }

  _attachToMuxer (mux, discoveryKey, ref) {
    if (!ref) ref = this._add(discoveryKey)

    const ch = mux.createChannel({
      protocol: 'blind-pairing',
      id: discoveryKey,
      messages: [
        { encoding: c.buffer, onmessage: (req) => this._onpairingrequest(ch, ref, req) },
        { encoding: c.buffer, onmessage: (res) => this._onpairingresponse(ch, ref, res) }
      ],
      onclose: () => {
        ref.channels.delete(ch)
        if (ref.candidate) ref.candidate.visited.delete(ch)
      }
    })

    if (ch === null) return

    ch.open()
    mux.pair({ protocol: 'blind-pairing', id: discoveryKey }, () => this._attachToMuxer(mux, discoveryKey, null))
    ref.channels.add(ch)

    if (ref.candidate) ref.candidate.broadcast()
  }

  async _onpairingrequest (ch, ref, req) {
    if (!ref.member) return

    const request = await ref.member._addRequest(req)
    if (!request) return

    ch.messages[1].send(request.response)
  }

  async _onpairingresponse (ch, ref, res) {
    if (!ref.candidate) return

    await ref.candidate._addResponse(res, false)
  }
}

class Member extends ReadyResource {
  constructor (blind, { announce = true, discoveryKey, onadd = noop } = {}) {
    super()

    if (!discoveryKey) {
      throw new Error('Must provide discoveryKey')
    }

    const ref = blind._add(discoveryKey)

    if (ref.member) {
      throw new Error('Active member already exist')
    }

    ref.member = this

    this._pendingRequests = new Map()

    this.blind = blind
    this.dht = blind.swarm.dht
    this.discoveryKey = discoveryKey
    this.pairingDiscoveryKey = deriveDiscoveryKey(discoveryKey)
    this.timeout = new TimeoutPromise(blind._randomPoll())
    this.pairing = null
    this.skip = new Xache({ maxSize: 512 })
    this.ref = ref
    this.onadd = onadd

    this._activeQuery = null
    this._activePoll = null
    this._closestNodes = null
    this._autoAnnounce = announce

    this.ready()
  }

  announce () {
    if (this.pairing) return this.pairing

    this.blind._swarm(this.ref)
    this.pairing = this._run()
    this.pairing.catch(safetyCatch)

    return this.pairing
  }

  async flushed () {
    if (!this.ref.discovery) return
    return this.ref.discovery.flushed()
  }

  _open () {
    if (this._autoAnnounce) this.announce()
    else this.blind._attachToSwarm(this.ref)
  }

  _suspend () {
    this.timeout.suspend()
    return this._abort()
  }

  async _abort () {
    if (this._activeQuery) this._activeQuery.destroy()
    while (this._activePoll !== null) await this._activePoll
  }

  async refresh () {
    await this._abort()
    this.timeout.resume()
  }

  async _close () {
    this.ref.member = null
    this.blind._gc(this.ref)
    this.timeout.destroy()
    await this._abort()

    try {
      await this.pairing
    } catch {
      // ignore errors since we teardown
    }
  }

  async _run () {
    while (!this.closing) {
      this._activePoll = this._poll()
      await this._activePoll
      this._activePoll = null
      await this.timeout.wait()
    }
  }

  async _poll () {
    const visited = new Set()
    let alwaysClient = false

    if (this._activeQuery) this._activeQuery.destroy()

    const query = this._activeQuery = this.dht.lookup(this.pairingDiscoveryKey, { closestNodes: this._closestNodes })

    try {
      for await (const data of this._activeQuery) {
        if (this.closing || this.blind.suspended) return

        for (const peer of data.peers) {
          const id = b4a.toString(peer.publicKey, 'hex')

          if (visited.has(id) || this.skip.get(id)) continue
          visited.add(id)

          try {
            if (await this._add(peer.publicKey, id)) alwaysClient = true
          } catch (err) {
            safetyCatch(err)
          }

          if (this.closing || this.blind.suspended) return

          if (alwaysClient && !this.ref.alwaysClient) {
            this.ref.alwaysClient = true
            this.blind._swarm(this.ref)
          }
        }
      }
    } catch {
      // do nothing
    } finally {
      const nodes = this._activeQuery.closestNodes
      if (this._activeQuery === query) this._activeQuery = null
      if (nodes && nodes.length > 0) this._closestNodes = nodes
    }

    if (alwaysClient) this._revertClientAfterFlush() // safe to do in bg
  }

  async _revertClientAfterFlush () {
    try {
      await this.blind.swarm.flush()
    } catch {
      return
    }
    if (this.closing || this.blind.suspended) return

    this.ref.alwaysClient = false
    this.blind._swarm(this.ref)
  }

  async _addRequest (value) {
    let request = null
    try {
      request = MemberRequest.from(value)
    } catch {
      return null
    }

    request.discoveryKey = this.discoveryKey

    const session = b4a.toString(request.requestData.session, 'hex')

    if (!this._pendingRequests.has(session)) {
      this._pendingRequests.set(session, {
        request,
        promise: this.onadd(request)
      })
    }

    // laod existing request if it exists
    const pending = this._pendingRequests.get(session)

    try {
      await pending.promise
    } catch (e) {
      safetyCatch(e)
      return null
    }

    this._pendingRequests.delete(session)

    if (!pending.request.response) return null

    return pending.request
  }

  async _add (publicKey, id) {
    const node = await this.dht.mutableGet(publicKey, { latest: false })
    if (!node) return false

    this.skip.set(id, true)

    const request = await this._addRequest(node.value)
    if (!request) return false

    const replyKeyPair = deriveReplyKeyPair(request.token)
    await this.dht.mutablePut(replyKeyPair, request.response)

    return true
  }
}

class Candidate extends ReadyResource {
  constructor (blind, request, { discoveryKey = request.discoveryKey, onadd = noop } = {}) {
    super()

    const ref = blind._add(discoveryKey)
    if (ref.candidate) {
      throw new Error('Active candidate already exist')
    }

    ref.candidate = this

    this.blind = blind
    this.discoveryKey = discoveryKey
    this.pairingDiscoveryKey = deriveDiscoveryKey(discoveryKey)
    this.dht = blind.swarm.dht
    this.request = request
    this.token = request.token
    this.timeout = new TimeoutPromise(blind._randomPoll())
    this.announced = false
    this.gcing = null
    this.ref = ref
    this.paired = null
    this.pairing = null
    this.onadd = onadd

    this.signal = null
    this.visited = new Set()
    this.broadcast = debounce(this._broadcast.bind(this))

    this._activePoll = null

    this.ready()
  }

  _open () {
    this.blind._swarm(this.ref)
    this.pairing = this._run()
    this.broadcast()
  }

  _suspend () {
    this.timeout.suspend()
    // no good way to suspend the mut gets atm unfortunately so we just rely on the polls timing out
  }

  async refresh () {
    while (this._activePoll !== null) await this._activePoll
    this.announced = false
    this.timeout.resume()
  }

  async _close () {
    this.ref.candidate = null
    this.blind._gc(this.ref)
    this.timeout.destroy()
    this.visited.clear()
    await this.pairing
    // gc never throws
    if (this.gcing) await this.gcing
  }

  async _addResponse (value, gc) {
    if (this.paired) return

    const paired = this.request.handleResponse(value)
    if (!paired) return

    this.paired = paired
    if (this.signal) this.signal.destroy()

    if ((gc || this.announced) && !this.gcing) this.gcing = this._gc() // gc in the background
    await this.onadd(paired)
    this.timeout.destroy()
  }

  async _run () {
    while (!this._done()) {
      this._activePoll = this._poll()
      await this._activePoll
      this._activePoll = null
      if (this._done()) break
      await this.timeout.wait()
    }

    this.close().catch(safetyCatch)
    return this.paired
  }

  _done () {
    return !!(this.closing || this.paired)
  }

  async _announce () {
    const eph = deriveEphemeralKeyPair(this.token)

    await this.dht.mutablePut(eph, this.request.encode())
    if (this._done()) return

    await this.dht.announce(this.pairingDiscoveryKey, eph).finished()
    if (this._done()) return

    if (!this.paired) {
      this.ref.alwaysServer = true
      this.blind._swarm(this.ref)
    }

    this.emit('announce')
  }

  async _gc () {
    const eph = deriveEphemeralKeyPair(this.token)

    try {
      await this.dht.unannounce(this.pairingDiscoveryKey, eph)
    } catch (err) {
      safetyCatch(err) // just gc, whatevs
    }
  }

  _sendRequest (ch) {
    ch.messages[0].send(this.request.encode())
    this.visited.add(ch)
  }

  async _broadcast () {
    for (const channel of this.closestPeers()) {
      this.signal = new TimeoutPromise(randomInterval(PEER_INTERVAL))
      if (channel) this._sendRequest(channel)

      await this.signal.wait() // resolves on destroy

      if (this.paired || this.suspended) break
    }
  }

  * closestPeers () {
    while (!this.paired) {
      const closest = Infinity
      let channel = null

      for (const ch of this.ref.channels) {
        if (this.visited.has(ch)) continue

        const { rtt } = ch._mux.stream.rawStream
        if (rtt < closest) channel = ch
      }

      if (!channel) return

      yield channel
    }
  }

  async _poll () {
    try {
      const value = await this._getReply()
      if (this._done() || this.blind.suspended) return

      if (value) {
        await this._addResponse(value, true)
        if (this._done() || this.blind.suspended) return
      }

      if (!this.announced) {
        this.announced = true
        await this._announce()
      }
    } catch {
      // can run in bg, should never crash it
    }
  }

  async _getReply () {
    const { publicKey } = deriveReplyKeyPair(this.token)
    const node = await this.dht.mutableGet(publicKey, { latest: false })
    if (!node) return null
    return node.value
  }
}

module.exports = BlindPairing

function noop () {}

function deriveReplyKeyPair (token) {
  return crypto.keyPair(crypto.hash([NS_REPLY, token]))
}

function deriveEphemeralKeyPair (token) {
  return crypto.keyPair(crypto.hash([NS_EPHEMERAL, token]))
}

function deriveDiscoveryKey (discoveryKey) {
  return crypto.hash([NS_DISCOVERY, discoveryKey])
}

function getMuxer (stream) {
  if (stream.userData) return stream.userData
  const protocol = Protomux.from(stream)
  stream.setKeepAlive(5000)
  stream.userData = protocol
  return protocol
}

function randomInterval (n) {
  return n + (n * 0.5 * Math.random()) | 0
}
{
  "name": "blind-pairing",
  "version": "2.3.1",
  "description": "Blind pairing using Hyperswarm",
  "main": "index.js",
  "files": [
    "index.js"
  ],
  "dependencies": {
    "blind-pairing-core": "^2.0.0",
    "b4a": "^1.6.4",
    "debounceify": "^1.1.0",
    "hypercore-crypto": "^3.4.0",
    "is-options": "^1.0.2",
    "ready-resource": "^1.0.0",
    "safety-catch": "^1.0.2",
    "xache": "^1.2.0"
  },
  "devDependencies": {
    "@hyperswarm/testnet": "^3.1.4",
    "brittle": "^3.3.2",
    "hyperdht": "^6.8.9",
    "hyperswarm": "^4.7.3",
    "minimist": "^1.2.8",
    "standard": "^17.1.0",
    "z32": "^1.0.1"
  },
  "scripts": {
    "test": "standard && brittle test/*.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/holepunchto/blind-pairing.git"
  },
  "author": "Holepunch Inc",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/blind-pairing/issues"
  },
  "homepage": "https://github.com/holepunchto/blind-pairing"
}
const EventEmitter = require('events')
const Protomux = require('protomux')
const { Readable } = require('streamx')
const sodium = require('sodium-universal')
const b4a = require('b4a')
const c = require('compact-encoding')
const bitfield = require('compact-encoding-bitfield')
const bits = require('bits-to-bytes')
const errors = require('./lib/errors')

exports.Server = class BlindRelayServer extends EventEmitter {
  constructor(opts = {}) {
    super()

    const { createStream } = opts

    this._createStream = createStream
    this._pairing = new Map()
    this._activePairingRefs = new Map()
    this._sessions = new Set()
    this.stats = createStats()
  }

  get sessions() {
    return this._sessions[Symbol.iterator]()
  }

  accept(stream, opts) {
    const session = new BlindRelaySession(this, stream, opts)

    this._sessions.add(session)
    this.stats.sessions.accepted++

    return session
  }

  async close() {
    const ending = []

    for (const session of this._sessions) {
      ending.push(session.end())
    }

    await Promise.all(ending)

    this._pairing.clear()
  }

  _trackActiveLink(keyString) {
    const refs = this._activePairingRefs.get(keyString) || 0

    if (refs === 0) this.stats.pairings.active++

    this._activePairingRefs.set(keyString, refs + 1)
  }

  _untrackActiveLink(keyString) {
    const refs = this._activePairingRefs.get(keyString)

    if (refs === undefined) return

    if (refs === 1) {
      this._activePairingRefs.delete(keyString)
      this.stats.pairings.active--
    } else {
      this._activePairingRefs.set(keyString, refs - 1)
    }
  }
}

class BlindRelaySession extends EventEmitter {
  constructor(server, stream, opts = {}) {
    super()

    const { id, handshake, handshakeEncoding } = opts

    this._server = server
    this._mux = Protomux.from(stream)

    this._channel = this._mux.createChannel({
      protocol: 'blind-relay',
      id,
      handshake: handshake ? handshakeEncoding || c.raw : null,
      onopen: this._onopen.bind(this),
      onclose: this._onclose.bind(this),
      ondestroy: this._ondestroy.bind(this)
    })

    this._pair = this._channel.addMessage({
      encoding: m.pair,
      onmessage: this._onpair.bind(this)
    })

    this._unpair = this._channel.addMessage({
      encoding: m.unpair,
      onmessage: this._onunpair.bind(this)
    })

    this._ending = null
    this._destroyed = false
    this._error = null
    this._pairing = new Set()
    this._links = new Map()

    this._onerror = (err) => {
      this._server.stats.streams.errors++
      this.emit('error', err)
    }

    this._channel.open(handshake)
  }

  get closed() {
    return this._channel.closed
  }

  get mux() {
    return this._mux
  }

  get stream() {
    return this._mux.stream
  }

  _onopen() {
    this._server.stats.sessions.opened++
    this.emit('open')
  }

  _onclose() {
    this._ending = Promise.resolve()

    const err = this._error || errors.CHANNEL_CLOSED()
    this._server.stats.pairings.cancelled += this._pairing.size

    for (const token of this._pairing) {
      if (this._server._pairing.delete(token.toString('hex'))) {
        this._server.stats.pairings.pending--
      }
    }

    for (const link of this._links.values()) {
      link.destroy(err)
    }

    this._pairing.clear()
    this._links.clear()

    this._server._sessions.delete(this)
    this._server.stats.sessions.closed++

    this.emit('close')
  }

  _ondestroy() {
    this._destroyed = true
    this.emit('destroy')
  }

  _onpair({ isInitiator, token, id: remoteId }) {
    const keyString = token.toString('hex')

    let pair = this._server._pairing.get(keyString)

    if (pair === undefined) {
      pair = new BlindRelayPair(token)
      this._server._pairing.set(keyString, pair)
      this._server.stats.pairings.pending++
    } else if (pair.links[+isInitiator]) return

    this._pairing.add(keyString)
    this._server.stats.pairings.requested++

    pair.links[+isInitiator] = new BlindRelayLink(this, isInitiator, remoteId)

    if (!pair.paired) return

    this._server._pairing.delete(keyString)
    this._server.stats.pairings.pending--
    this._server.stats.pairings.matched++

    // 1st pass: Create the raw streams needed for each end of the link.
    for (const link of pair.links) {
      link.createStream()
    }

    // 2nd pass: Connect the raw streams and set up handlers.
    for (const link of pair.links) {
      link.remote = pair.remote(link.isInitiator)
      link.activate(keyString)
    }

    // 3rd pass: Let either end of the link know the streams were set up.
    for (const { isInitiator, session, remoteId, stream } of pair.links) {
      session._pair.send({
        isInitiator,
        token,
        id: stream.id,
        seq: 0
      })

      session._endMaybe()

      session.emit('pair', isInitiator, token, stream, remoteId)
    }
  }

  _onunpair({ token }) {
    const keyString = token.toString('hex')

    const pair = this._server._pairing.get(keyString)

    if (pair) {
      for (const link of pair.links) {
        if (link) link.session._pairing.delete(keyString)
      }

      this._server._pairing.delete(keyString)
      this._server.stats.pairings.pending--
      this._server.stats.pairings.cancelled++
      return
    }

    const link = this._links.get(keyString)

    if (link) {
      link.destroyPair(errors.PAIRING_CANCELLED())
      this._server.stats.pairings.cancelled++
    }
  }

  cork() {
    this._channel.cork()
  }

  uncork() {
    this._channel.uncork()
  }

  async end() {
    if (this._ending) return this._ending

    this._ending = EventEmitter.once(this, 'close')
    this._endMaybe()

    return this._ending
  }

  _endMaybe() {
    if (this._ending && this._pairing.size === 0) {
      this._channel.close()
    }
  }

  destroy(err) {
    if (this._destroyed) return
    this._destroyed = true

    this._error = err || errors.CHANNEL_DESTROYED()
    this._channel.close()
  }
}

class BlindRelayPair {
  constructor(token) {
    this.token = token
    this.links = [null, null]
  }

  get paired() {
    return this.links[0] !== null && this.links[1] !== null
  }

  remote(isInitiator) {
    return this.links[isInitiator ? 0 : 1]
  }
}

class BlindRelayLink {
  constructor(session, isInitiator, remoteId) {
    this.session = session
    this.isInitiator = isInitiator
    this.remoteId = remoteId
    this.stream = null
    this.remote = null
    this.keyString = null
    this._onclose = null
  }

  createStream() {
    if (this.stream) return

    this.stream = this.session._server._createStream({
      firewall: this._onfirewall.bind(this)
    })
  }

  activate(keyString) {
    const stream = this.stream

    this.keyString = keyString
    this._onclose = () => {
      if (this.stream !== stream) return

      this.stream = null
      unlink(this)
      this.session._links.delete(keyString)
      this.session._server._untrackActiveLink(keyString)
      this.session._server.stats.streams.closed++
    }

    stream.on('error', this.session._onerror).on('close', this._onclose).relayTo(this.remote.stream)

    this.session._pairing.delete(keyString)
    this.session._links.set(keyString, this)
    this.session._server._trackActiveLink(keyString)
    this.session._server.stats.streams.opened++
  }

  destroy(err) {
    const stream = this.stream

    if (!stream) return

    this.stream = null

    stream.off('close', this._onclose)
    this._onclose = null

    stream.off('error', this.session._onerror)
    unlink(this)
    this.session._links.delete(this.keyString)
    this.session._server._untrackActiveLink(this.keyString)
    this.session._server.stats.streams.closed++
    stream.on('error', noop).destroy(err)
  }

  destroyPair(err) {
    const remote = this.remote

    this.destroy(err)
    if (remote) remote.destroy(err)
  }

  _onfirewall(socket, port, host) {
    this.stream.connect(socket, this.remoteId, port, host)

    return false
  }
}

exports.Client = class BlindRelayClient extends EventEmitter {
  static _clients = new WeakMap()

  static from(stream, opts) {
    let client = this._clients.get(stream)
    if (client) return client
    client = new this(stream, opts)
    this._clients.set(stream, client)
    return client
  }

  constructor(stream, opts = {}) {
    super()

    const { id, handshake, handshakeEncoding } = opts

    this._mux = Protomux.from(stream)

    this._channel = this._mux.createChannel({
      protocol: 'blind-relay',
      id,
      handshake: handshake ? handshakeEncoding || c.raw : null,
      onopen: this._onopen.bind(this),
      onclose: this._onclose.bind(this),
      ondestroy: this._ondestroy.bind(this)
    })

    this._pair = this._channel.addMessage({
      encoding: m.pair,
      onmessage: this._onpair.bind(this)
    })

    this._unpair = this._channel.addMessage({
      encoding: m.unpair
    })

    this._ending = false
    this._destroyed = false
    this._error = null
    this._requests = new Map()

    this._channel.open(handshake)
  }

  get closed() {
    return this._channel.closed
  }

  get mux() {
    return this._mux
  }

  get stream() {
    return this._mux.stream
  }

  get requests() {
    return this._requests.values()
  }

  _onopen() {
    this.emit('open')
  }

  _onclose() {
    this._ending = Promise.resolve()

    const err = this._error || errors.CHANNEL_CLOSED()

    for (const request of this._requests.values()) {
      request.destroy(err)
    }

    this._requests.clear()

    this.constructor._clients.delete(this.stream)

    this.emit('close')
  }

  _ondestroy() {
    this._destroyed = true
    this.emit('destroy')
  }

  _onpair({ isInitiator, token, id: remoteId }) {
    const request = this._requests.get(token.toString('hex'))

    if (request === undefined || request.isInitiator !== isInitiator) return

    request.push(remoteId)
    request.push(null)

    this.emit('pair', request.isInitiator, request.token, request.stream, remoteId)
  }

  pair(isInitiator, token, stream) {
    if (this._destroyed) throw errors.CHANNEL_DESTROYED()

    const keyString = token.toString('hex')

    if (this._requests.has(keyString)) throw errors.ALREADY_PAIRING()

    const request = new BlindRelayRequest(this, isInitiator, token, stream)

    this._requests.set(keyString, request)

    return request
  }

  unpair(token) {
    if (this._destroyed) throw errors.CHANNEL_DESTROYED()

    const request = this._requests.get(token.toString('hex'))

    if (request) request.destroy(errors.PAIRING_CANCELLED())

    this._unpair.send({ token })
  }

  cork() {
    this._channel.cork()
  }

  uncork() {
    this._channel.uncork()
  }

  async end() {
    if (this._ending) return this._ending

    this._ending = EventEmitter.once(this, 'close')
    this._endMaybe()

    return this._ending
  }

  _endMaybe() {
    if (this._ending && this._requests.size === 0) {
      this._channel.close()
    }
  }

  destroy(err) {
    if (this._destroyed) return
    this._destroyed = true

    this._error = err || errors.CHANNEL_DESTROYED()
    this._channel.close()
  }
}

class BlindRelayRequest extends Readable {
  constructor(client, isInitiator, token, stream) {
    super()

    this.client = client
    this.isInitiator = isInitiator
    this.token = token
    this.stream = stream
  }

  _open(cb) {
    if (this.client._destroyed) return cb(errors.CHANNEL_DESTROYED())

    this.client._pair.send({
      isInitiator: this.isInitiator,
      token: this.token,
      id: this.stream.id,
      seq: 0
    })

    cb(null)
  }

  _destroy(cb) {
    this.client._requests.delete(this.token.toString('hex'))

    cb(null)

    this.client._endMaybe()
  }
}

exports.token = function token(buf = b4a.allocUnsafe(32)) {
  sodium.randombytes_buf(buf)
  return buf
}

function noop() {}

function unlink(link) {
  const remote = link.remote

  link.remote = null

  if (remote && remote.remote === link) {
    remote.remote = null
  }
}

function createStats() {
  const stats = {
    sessions: {
      accepted: 0,
      opened: 0,
      closed: 0,
      get active() {
        return this.accepted - this.closed
      }
    },
    pairings: {
      requested: 0,
      matched: 0,
      cancelled: 0,
      pending: 0,
      active: 0
    },
    streams: {
      opened: 0,
      closed: 0,
      errors: 0,
      get active() {
        return this.opened - this.closed
      }
    }
  }

  return stats
}

const m = (exports.messages = {})

const flags = bitfield(7)

m.pair = {
  preencode(state, m) {
    flags.preencode(state)
    c.fixed32.preencode(state, m.token)
    c.uint.preencode(state, m.id)
    c.uint.preencode(state, m.seq)
  },
  encode(state, m) {
    flags.encode(state, bits.of(m.isInitiator))
    c.fixed32.encode(state, m.token)
    c.uint.encode(state, m.id)
    c.uint.encode(state, m.seq)
  },
  decode(state) {
    const [isInitiator] = bits.iterator(flags.decode(state))

    return {
      isInitiator,
      token: c.fixed32.decode(state),
      id: c.uint.decode(state),
      seq: c.uint.decode(state)
    }
  }
}

m.unpair = {
  preencode(state, m) {
    flags.preencode(state)
    c.fixed32.preencode(state, m.token)
  },
  encode(state, m) {
    flags.encode(state, bits.of())
    c.fixed32.encode(state, m.token)
  },
  decode(state) {
    flags.decode(state)

    return {
      token: c.fixed32.decode(state)
    }
  }
}
module.exports = class BlindRelayError extends Error {
  constructor(msg, code, fn = BlindRelayError) {
    super(`${code}: ${msg}`)
    this.code = code

    if (Error.captureStackTrace) {
      Error.captureStackTrace(this, fn)
    }
  }

  get name() {
    return 'BlindRelayError'
  }

  static DUPLICATE_CHANNEL(msg = 'Duplicate channel') {
    return new BlindRelayError(msg, 'DUPLICATE_CHANNEL', BlindRelayError.DUPLICATE_CHANNEL)
  }

  static CHANNEL_CLOSED(msg = 'Channel closed') {
    return new BlindRelayError(msg, 'CHANNEL_CLOSED', BlindRelayError.CHANNEL_CLOSED)
  }

  static CHANNEL_DESTROYED(msg = 'Channel destroyed') {
    return new BlindRelayError(msg, 'CHANNEL_DESTROYED', BlindRelayError.CHANNEL_DESTROYED)
  }

  static ALREADY_PAIRING(msg = 'Already pairing') {
    return new BlindRelayError(msg, 'ALREADY_PAIRING', BlindRelayError.ALREADY_PAIRING)
  }

  static PAIRING_CANCELLED(msg = 'Pairing cancelled') {
    return new BlindRelayError(msg, 'PAIRING_CANCELLED', BlindRelayError.PAIRING_CANCELLED)
  }
}
{
  "name": "blind-relay",
  "version": "1.6.1",
  "description": "Blind relay for UDX over Protomux channels",
  "main": "index.js",
  "files": [
    "index.js",
    "lib"
  ],
  "scripts": {
    "format": "prettier --write .",
    "lint": "prettier --check . && lunte",
    "test": "npm run test:bare",
    "test:bare": "brittle-bare --coverage test.mjs"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/blind-relay.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/blind-relay/issues"
  },
  "homepage": "https://github.com/holepunchto/blind-relay#readme",
  "imports": {
    "events": {
      "bare": "bare-events",
      "default": "events"
    }
  },
  "dependencies": {
    "b4a": "^1.6.4",
    "bare-events": "^2.2.0",
    "bits-to-bytes": "^1.3.0",
    "compact-encoding": "^3.0.0",
    "compact-encoding-bitfield": "^1.0.0",
    "protomux": "^3.5.1",
    "sodium-universal": "^5.0.0",
    "streamx": "^2.15.1"
  },
  "devDependencies": {
    "brittle": "^3.2.1",
    "hyperdht": "^6.6.1",
    "lunte": "^1.0.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^2.0.0",
    "udx-native": "^1.6.1"
  }
}
// https://ipinfo.io/bogon

const b4a = require('b4a')
const c = require('compact-encoding')

module.exports = exports = function isBogon (ip) {
  return isBogonIP(ensureBuffer(ip))
}

exports.isBogon = exports

exports.isPrivate = function isPrivate (ip) {
  return isPrivateIP(ensureBuffer(ip))
}

exports.isReserved = function isReserved (ip) {
  return isReservedIP(ensureBuffer(ip))
}

function isBogonIP (ip) {
  return isPrivateIP(ip) || isReservedIP(ip)
}

function isPrivateIP (ip) {
  return ip.byteLength === 4 ? isPrivateIPv4(ip) : false // IPv6 has no private IPs
}

function isPrivateIPv4 (ip) {
  return (
    // 10.0.0.0/8  Private-use networks
    (ip[0] === 10) ||
    // 100.64.0.0/10 Carrier-grade NAT
    (ip[0] === 100 && ip[1] >= 64 && ip[1] <= 127) ||
    // 127.0.0.0/8 Loopback + Name collision occurrence (127.0.53.53)
    (ip[0] === 127) ||
    // 169.254.0.0/16  Link local
    (ip[0] === 169 && ip[1] === 254) ||
    // 172.16.0.0/12 Private-use networks
    (ip[0] === 172 && ip[1] >= 16 && ip[1] <= 31) ||
    // 192.168.0.0/16  Private-use networks
    (ip[0] === 192 && ip[1] === 168)
  )
}

function isReservedIP (ip) {
  return ip.byteLength === 4 ? isReservedIPv4(ip) : isReservedIPv6(ip)
}

function isReservedIPv4 (ip) {
  return (
    // 0.0.0.0/8 "This" network
    (ip[0] === 0) ||
    // 192.0.0.0/24  IETF protocol assignments
    (ip[0] === 192 && ip[1] === 0 && ip[2] === 0) ||
    // 192.0.2.0/24  TEST-NET-1
    (ip[0] === 192 && ip[1] === 0 && ip[2] === 2) ||
    // 198.18.0.0/15 Network interconnect device benchmark testing
    (ip[0] === 198 && ip[1] >= 18 && ip[1] <= 19) ||
    // 198.51.100.0/24 TEST-NET-2
    (ip[0] === 198 && ip[1] === 51 && ip[2] === 100) ||
    // 203.0.113.0/24  TEST-NET-3
    (ip[0] === 203 && ip[1] === 0 && ip[2] === 113) ||
    // 224.0.0.0/4 Multicast
    (ip[0] >= 224 && ip[0] <= 239) ||
    // 240.0.0.0/4 Reserved for future use
    (ip[0] >= 240) ||
    // 255.255.255.255/32
    (ip[0] === 255 && ip[1] === 255 && ip[2] === 255 && ip[3] === 255)
  )
}

function isReservedIPv6 (ip) {
  return (
    // ::/128 Node-scope unicast unspecified address
    // ::1/128 Node-scope unicast loopback address
    (
      ip[0] === 0 && ip[1] === 0 && ip[2] === 0 && ip[3] === 0 && ip[4] === 0 &&
      ip[5] === 0 && ip[6] === 0 && ip[7] === 0 && ip[8] === 0 && ip[9] === 0 &&
      ip[10] === 0 && ip[11] === 0 && ip[12] === 0 && ip[13] === 0 && ip[14] === 0 &&
      ip[15] <= 1
    ) ||
    // ::ffff:0:0/96 IPv4-mapped addresses
    // ::/96 IPv4-compatible addresses
    (
      ip[0] === 0 && ip[1] === 0 && ip[2] === 0 && ip[3] === 0 && ip[4] === 0 &&
      ip[5] === 0 && ip[6] === 0 && ip[7] === 0 && ip[8] === 0 && ip[9] === 0 &&
      (ip[10] === 0 || ip[10] === 0xff) &&
      (ip[11] === 0 || ip[11] === 0xff)
    ) ||
    // 100::/64 Remotely triggered black hole addresses
    (ip[0] === 0x01 && ip[1] === 0 && ip[2] === 0 && ip[3] === 0 && ip[4] === 0 && ip[5] === 0 && ip[6] === 0 && ip[7] === 0) ||
    // 2001:10::/28 Overlay routable cryptographic hash identifiers (ORCHID)
    (ip[0] === 0x20 && ip[1] === 0x01 && ip[2] === 0 && ip[3] >= 0x10 && ip[3] <= 0x1f) ||
    // 2001:20::/28 Overlay routable cryptographic hash identifiers version 2 (ORCHIDv2)
    (ip[0] === 0x20 && ip[1] === 0x01 && ip[2] === 0 && ip[3] >= 0x20 && ip[3] <= 0x2f) ||
    // 2001:db8::/32 Documentation prefix
    (ip[0] === 0x20 && ip[1] === 0x01 && ip[2] === 0x0d && ip[3] === 0xb8) ||
    // fc00::/7 Unique local addresses (ULA)
    (ip[0] >= 0xfc && ip[0] <= 0xfd) ||
    // fe80::/10 Link-local unicast
    (ip[0] === 0xfe && ip[1] >= 0x80 && ip[1] <= 0xbf) ||
    // ff00::/8 Multicast
    (ip[0] === 0xff)
  )
}

const state = c.state(0, 0, b4a.allocUnsafe(1 /* family */ + 16))

function ensureBuffer (ip) {
  if (b4a.isBuffer(ip)) return ip

  c.ip.preencode(state, ip)
  c.ip.encode(state, ip)

  const buffer = state.buffer.subarray(1 /* family */, state.end)

  state.start = 0
  state.end = 0

  return buffer
}
{
  "name": "bogon",
  "version": "1.3.0",
  "description": "Check if an IP is a bogon",
  "main": "index.js",
  "scripts": {
    "test": "standard && brittle test.mjs"
  },
  "dependencies": {
    "compact-encoding": "^3.0.0",
    "compact-encoding-net": "^1.2.0"
  },
  "devDependencies": {
    "brittle": "^3.0.4",
    "nanobench": "^2.1.1",
    "standard": "^17.0.0"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/mafintosh/bogon.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/mafintosh/bogon/issues"
  },
  "homepage": "https://github.com/mafintosh/bogon"
}
const c = require('compact-encoding')

module.exports = function bitfield(length) {
  if (length > 64) {
    throw new RangeError('Bitfield cannot be larger than 64 bits')
  }

  let byteLength
  if (length < 8) byteLength = 1
  else if (length <= 16) byteLength = 2
  else if (length <= 32) byteLength = 4
  else byteLength = 8

  return {
    preencode(state) {
      state.end++ // Length byte, used for data when byteLength === 1

      if (byteLength === 1);
      else if (byteLength === 2) c.uint16.preencode(state)
      else if (byteLength === 4) c.uint32.preencode(state)
      else c.uint64.preencode(state)
    },

    encode(state, b) {
      if (byteLength === 1);
      else if (byteLength === 2) c.uint8.encode(state, 0xfd)
      else if (byteLength === 4) c.uint8.encode(state, 0xfe)
      else c.uint8.encode(state, 0xff)

      if (typeof b === 'number') {
        if (byteLength === 1) c.uint8.encode(state, b)
        else if (byteLength === 2) c.uint16.encode(state, b)
        else if (byteLength === 4) c.uint32.encode(state, b)
        else c.uint64.encode(state, b)
      } else {
        state.buffer.set(b, state.start)

        if (b.byteLength < byteLength) {
          // Zero-fill the rest of the byte length.
          state.buffer.fill(
            0,
            state.start + b.byteLength,
            state.start + byteLength
          )
        }

        state.start += byteLength
      }
    },

    decode(state) {
      const byte = state.buffer[state.start]

      let byteLength
      if (byte <= 0xfc) byteLength = 1
      else if (byte === 0xfd) byteLength = 2
      else if (byte === 0xfe) byteLength = 4
      else byteLength = 8

      if (byteLength > 1) state.start++ // Skip the length byte

      if (state.end - state.start < byteLength) throw new Error('Out of bounds')

      const b = state.buffer.subarray(state.start, (state.start += byteLength))

      return length <= 8 ? b.subarray(0, 1) : b
    }
  }
}
{
  "name": "compact-encoding-bitfield",
  "version": "1.1.0",
  "description": "Compact codec for bitfields",
  "main": "index.js",
  "files": [
    "index.js"
  ],
  "scripts": {
    "format": "prettier --write .",
    "lint": "prettier --check . && lunte",
    "test": "npm run test:node && npm run test:bare",
    "test:node": "brittle-node --coverage test.mjs",
    "test:bare": "brittle-bare --coverage test.mjs"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/compact-encoding/compact-encoding-bitfield.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/compact-encoding/compact-encoding-bitfield/issues"
  },
  "homepage": "https://github.com/compact-encoding/compact-encoding-bitfield#readme",
  "imports": {
    "util": {
      "bare": "bare-utils",
      "default": "util"
    }
  },
  "dependencies": {
    "compact-encoding": "^3.0.0"
  },
  "devDependencies": {
    "bare-utils": "^1.5.1",
    "brittle": "^3.1.0",
    "lunte": "^1.3.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^1.0.0"
  }
}
const LE = (exports.LE = new Uint8Array(new Uint16Array([0xff]).buffer)[0] === 0xff)

exports.BE = !LE
const b4a = require('b4a')

const { BE } = require('./endian')

// Zig-zag doubles the magnitude of a value before it is written as a uint, so
// an int only reaches half as far as a uint of the same width does.
const MAX_SAFE_INT = 2 ** 52 - 1
const MIN_SAFE_INT = -(2 ** 52)

exports.state = function (start = 0, end = 0, buffer = null) {
  return { start, end, buffer }
}

const raw = (exports.raw = require('./raw'))

const uint = (exports.uint = {
  preencode(state, n) {
    state.end += n <= 0xfc ? 1 : n <= 0xffff ? 3 : n <= 0xffffffff ? 5 : 9
  },
  encode(state, n) {
    if (n <= 0xfc) uint8.encode(state, n)
    else if (n <= 0xffff) {
      state.buffer[state.start++] = 0xfd
      uint16.encode(state, n)
    } else if (n <= 0xffffffff) {
      state.buffer[state.start++] = 0xfe
      uint32.encode(state, n)
    } else {
      state.buffer[state.start++] = 0xff
      uint64.encode(state, n)
    }
  },
  decode(state) {
    const a = uint8.decode(state)
    if (a <= 0xfc) return a
    if (a === 0xfd) return uint16.decode(state)
    if (a === 0xfe) return uint32.decode(state)
    return uint64.decode(state)
  }
})

const uint8 = (exports.uint8 = {
  preencode(state, n) {
    state.end += 1
  },
  encode(state, n) {
    validateUint(n)
    state.buffer[state.start++] = n
  },
  decode(state) {
    if (state.start >= state.end) throw new Error('Out of bounds')
    return state.buffer[state.start++]
  }
})

const uint16 = (exports.uint16 = {
  preencode(state, n) {
    state.end += 2
  },
  encode(state, n) {
    validateUint(n)
    state.buffer[state.start++] = n
    state.buffer[state.start++] = n >>> 8
  },
  decode(state) {
    if (state.end - state.start < 2) throw new Error('Out of bounds')
    return state.buffer[state.start++] + state.buffer[state.start++] * 0x100
  }
})

const uint24 = (exports.uint24 = {
  preencode(state, n) {
    state.end += 3
  },
  encode(state, n) {
    validateUint(n)
    state.buffer[state.start++] = n
    state.buffer[state.start++] = n >>> 8
    state.buffer[state.start++] = n >>> 16
  },
  decode(state) {
    if (state.end - state.start < 3) throw new Error('Out of bounds')
    return (
      state.buffer[state.start++] +
      state.buffer[state.start++] * 0x100 +
      state.buffer[state.start++] * 0x10000
    )
  }
})

const uint32 = (exports.uint32 = {
  preencode(state, n) {
    state.end += 4
  },
  encode(state, n) {
    validateUint(n)
    state.buffer[state.start++] = n
    state.buffer[state.start++] = n >>> 8
    state.buffer[state.start++] = n >>> 16
    state.buffer[state.start++] = n >>> 24
  },
  decode(state) {
    if (state.end - state.start < 4) throw new Error('Out of bounds')
    return (
      state.buffer[state.start++] +
      state.buffer[state.start++] * 0x100 +
      state.buffer[state.start++] * 0x10000 +
      state.buffer[state.start++] * 0x1000000
    )
  }
})

const uint32be = (exports.uint32be = {
  preencode(state, n) {
    state.end += 4
  },
  encode(state, n) {
    validateUint(n)
    state.buffer[state.start++] = n >>> 24
    state.buffer[state.start++] = n >>> 16
    state.buffer[state.start++] = n >>> 8
    state.buffer[state.start++] = n
  },
  decode(state) {
    if (state.end - state.start < 4) throw new Error('Out of bounds')
    return (
      state.buffer[state.start++] * 0x1000000 +
      state.buffer[state.start++] * 0x10000 +
      state.buffer[state.start++] * 0x100 +
      state.buffer[state.start++]
    )
  }
})

const uint40 = (exports.uint40 = {
  preencode(state, n) {
    state.end += 5
  },
  encode(state, n) {
    validateUint(n)
    const r = Math.floor(n / 0x100)
    uint8.encode(state, n)
    uint32.encode(state, r)
  },
  decode(state) {
    if (state.end - state.start < 5) throw new Error('Out of bounds')
    return uint8.decode(state) + 0x100 * uint32.decode(state)
  }
})

const uint48 = (exports.uint48 = {
  preencode(state, n) {
    state.end += 6
  },
  encode(state, n) {
    validateUint(n)
    const r = Math.floor(n / 0x10000)
    uint16.encode(state, n)
    uint32.encode(state, r)
  },
  decode(state) {
    if (state.end - state.start < 6) throw new Error('Out of bounds')
    return uint16.decode(state) + 0x10000 * uint32.decode(state)
  }
})

const uint56 = (exports.uint56 = {
  preencode(state, n) {
    state.end += 7
  },
  encode(state, n) {
    validateUint(n)
    const r = Math.floor(n / 0x1000000)
    uint24.encode(state, n)
    uint32.encode(state, r)
  },
  decode(state) {
    if (state.end - state.start < 7) throw new Error('Out of bounds')
    return validateSafeUint(uint24.decode(state) + 0x1000000 * uint32.decode(state))
  }
})

const uint64 = (exports.uint64 = {
  preencode(state, n) {
    state.end += 8
  },
  encode(state, n) {
    validateUint(n)
    const r = Math.floor(n / 0x100000000)
    uint32.encode(state, n)
    uint32.encode(state, r)
  },
  decode(state) {
    if (state.end - state.start < 8) throw new Error('Out of bounds')
    return validateSafeUint(uint32.decode(state) + 0x100000000 * uint32.decode(state))
  }
})

exports.uint64be = {
  preencode(state, n) {
    state.end += 8
  },
  encode(state, n) {
    validateUint(n)
    const r = Math.floor(n / 0x100000000)
    uint32be.encode(state, r)
    uint32be.encode(state, n)
  },
  decode(state) {
    if (state.end - state.start < 8) throw new Error('Out of bounds')
    return validateSafeUint(0x100000000 * uint32be.decode(state) + uint32be.decode(state))
  }
}

const int = (exports.int = zigZagInt(uint))
exports.int8 = zigZagInt(uint8)
exports.int16 = zigZagInt(uint16)
exports.int24 = zigZagInt(uint24)
exports.int32 = zigZagInt(uint32)
exports.int40 = zigZagInt(uint40)
exports.int48 = zigZagInt(uint48)
exports.int56 = zigZagInt(uint56)
exports.int64 = zigZagInt(uint64)

const biguint64 = (exports.biguint64 = {
  preencode(state, n) {
    state.end += 8
  },
  encode(state, n) {
    const view = new DataView(state.buffer.buffer, state.start + state.buffer.byteOffset, 8)
    view.setBigUint64(0, n, true) // little endian
    state.start += 8
  },
  decode(state) {
    if (state.end - state.start < 8) throw new Error('Out of bounds')
    const view = new DataView(state.buffer.buffer, state.start + state.buffer.byteOffset, 8)
    const n = view.getBigUint64(0, true) // little endian
    state.start += 8
    return n
  }
})

exports.bigint64 = zigZagBigInt(biguint64)

const biguint = (exports.biguint = {
  preencode(state, n) {
    let len = 0
    for (let m = n; m; m = m >> 64n) len++
    uint.preencode(state, len)
    state.end += 8 * len
  },
  encode(state, n) {
    let len = 0
    for (let m = n; m; m = m >> 64n) len++
    uint.encode(state, len)
    const view = new DataView(state.buffer.buffer, state.start + state.buffer.byteOffset, 8 * len)
    for (let m = n, i = 0; m; m = m >> 64n, i += 8) {
      view.setBigUint64(i, BigInt.asUintN(64, m), true) // little endian
    }
    state.start += 8 * len
  },
  decode(state) {
    const len = uint.decode(state)
    if (state.end - state.start < 8 * len) throw new Error('Out of bounds')
    const view = new DataView(state.buffer.buffer, state.start + state.buffer.byteOffset, 8 * len)
    let n = 0n
    for (let i = len - 1; i >= 0; i--) n = (n << 64n) + view.getBigUint64(i * 8, true) // little endian
    state.start += 8 * len
    return n
  }
})

exports.bigint = zigZagBigInt(biguint)

exports.lexint = require('./lexint')

exports.float32 = {
  preencode(state, n) {
    state.end += 4
  },
  encode(state, n) {
    const view = new DataView(state.buffer.buffer, state.start + state.buffer.byteOffset, 4)
    view.setFloat32(0, n, true) // little endian
    state.start += 4
  },
  decode(state) {
    if (state.end - state.start < 4) throw new Error('Out of bounds')
    const view = new DataView(state.buffer.buffer, state.start + state.buffer.byteOffset, 4)
    const float = view.getFloat32(0, true) // little endian
    state.start += 4
    return float
  }
}

exports.float64 = {
  preencode(state, n) {
    state.end += 8
  },
  encode(state, n) {
    const view = new DataView(state.buffer.buffer, state.start + state.buffer.byteOffset, 8)
    view.setFloat64(0, n, true) // little endian
    state.start += 8
  },
  decode(state) {
    if (state.end - state.start < 8) throw new Error('Out of bounds')
    const view = new DataView(state.buffer.buffer, state.start + state.buffer.byteOffset, 8)
    const float = view.getFloat64(0, true) // little endian
    state.start += 8
    return float
  }
}

const buffer = (exports.buffer = {
  preencode(state, b) {
    uint8array.preencode(state, b)
  },
  encode(state, b) {
    uint8array.encode(state, b)
  },
  decode(state) {
    const len = uint.decode(state)
    if (state.end - state.start < len) throw new Error('Out of bounds')
    return state.buffer.subarray(state.start, (state.start += len))
  }
})

exports.optionalBuffer = {
  preencode(state, b) {
    if (b) uint8array.preencode(state, b)
    else state.end++
  },
  encode(state, b) {
    if (b) uint8array.encode(state, b)
    else state.buffer[state.start++] = 0
  },
  decode(state) {
    const len = uint.decode(state)
    if (len === 0) return null
    if (state.end - state.start < len) throw new Error('Out of bounds')
    return state.buffer.subarray(state.start, (state.start += len))
  }
}

exports.binary = {
  ...buffer,
  preencode(state, b) {
    if (typeof b === 'string') utf8.preencode(state, b)
    else buffer.preencode(state, b)
  },
  encode(state, b) {
    if (typeof b === 'string') utf8.encode(state, b)
    else buffer.encode(state, b)
  }
}

exports.arraybuffer = {
  preencode(state, b) {
    uint.preencode(state, b.byteLength)
    state.end += b.byteLength
  },
  encode(state, b) {
    uint.encode(state, b.byteLength)

    const view = new Uint8Array(b)

    state.buffer.set(view, state.start)
    state.start += b.byteLength
  },
  decode(state) {
    const len = uint.decode(state)

    const b = new ArrayBuffer(len)
    const view = new Uint8Array(b)

    view.set(state.buffer.subarray(state.start, (state.start += len)))

    return b
  }
}

exports.bitarray = {
  preencode(state, m) {
    uint.preencode(state, m.length)
    state.end += Math.ceil(m.length / 8)
  },
  encode(state, m) {
    uint.encode(state, m.length)
    for (let i = 0; i < m.length; i += 8) {
      let byte = 0
      for (let j = 0; j < 8 && i + j < m.length; j++) {
        if (m[i + j]) byte |= 1 << j
      }
      state.buffer[state.start++] = byte
    }
  },
  decode(state) {
    const n = uint.decode(state)
    if (state.end - state.start < Math.ceil(n / 8)) throw new Error('Out of bounds')
    const m = new Array(n)
    for (let i = 0; i < n; i += 8) {
      const byte = state.buffer[state.start++]
      for (let j = 0; j < 8 && i + j < n; j++) {
        m[i + j] = (byte & (1 << j)) !== 0
      }
    }
    return m
  }
}

function typedarray(TypedArray, swap) {
  const n = TypedArray.BYTES_PER_ELEMENT

  return {
    preencode(state, b) {
      uint.preencode(state, b.length)
      state.end += b.byteLength
    },
    encode(state, b) {
      uint.encode(state, b.length)

      const view = new Uint8Array(b.buffer, b.byteOffset, b.byteLength)

      if (BE && swap) swap(view)

      state.buffer.set(view, state.start)
      state.start += b.byteLength
    },
    decode(state) {
      const len = uint.decode(state)

      let b = state.buffer.subarray(state.start, (state.start += len * n))
      if (b.byteLength !== len * n) throw new Error('Out of bounds')
      if (b.byteOffset % n !== 0) b = new Uint8Array(b)

      if (BE && swap) swap(b)

      return new TypedArray(b.buffer, b.byteOffset, b.byteLength / n)
    }
  }
}

const uint8array = (exports.uint8array = typedarray(Uint8Array))
exports.uint16array = typedarray(Uint16Array, b4a.swap16)
exports.uint32array = typedarray(Uint32Array, b4a.swap32)

exports.int8array = typedarray(Int8Array)
exports.int16array = typedarray(Int16Array, b4a.swap16)
exports.int32array = typedarray(Int32Array, b4a.swap32)

exports.biguint64array = typedarray(BigUint64Array, b4a.swap64)
exports.bigint64array = typedarray(BigInt64Array, b4a.swap64)

exports.float32array = typedarray(Float32Array, b4a.swap32)
exports.float64array = typedarray(Float64Array, b4a.swap64)

function string(encoding) {
  return {
    preencode(state, s) {
      const len = b4a.byteLength(s, encoding)
      uint.preencode(state, len)
      state.end += len
    },
    encode(state, s) {
      const len = b4a.byteLength(s, encoding)
      uint.encode(state, len)
      b4a.write(state.buffer, s, state.start, encoding)
      state.start += len
    },
    decode(state) {
      const len = uint.decode(state)
      if (state.end - state.start < len) throw new Error('Out of bounds')
      return b4a.toString(state.buffer, encoding, state.start, (state.start += len))
    },
    fixed(n) {
      return {
        preencode(state) {
          state.end += n
        },
        encode(state, s) {
          b4a.write(state.buffer, s, state.start, n, encoding)
          state.start += n
        },
        decode(state) {
          if (state.end - state.start < n) throw new Error('Out of bounds')
          return b4a.toString(state.buffer, encoding, state.start, (state.start += n))
        }
      }
    }
  }
}

const utf8 = (exports.string = exports.utf8 = string('utf-8'))
exports.ascii = string('ascii')
exports.hex = string('hex')
exports.base64 = string('base64')
exports.ucs2 = exports.utf16le = string('utf16le')

exports.bool = {
  preencode(state, b) {
    state.end++
  },
  encode(state, b) {
    state.buffer[state.start++] = b ? 1 : 0
  },
  decode(state) {
    if (state.start >= state.end) throw Error('Out of bounds')
    return state.buffer[state.start++] === 1
  }
}

const fixed = (exports.fixed = function fixed(n) {
  return {
    preencode(state, s) {
      if (s.byteLength !== n) throw new Error('Incorrect buffer size')
      state.end += n
    },
    encode(state, s) {
      state.buffer.set(s, state.start)
      state.start += n
    },
    decode(state) {
      if (state.end - state.start < n) throw new Error('Out of bounds')
      return state.buffer.subarray(state.start, (state.start += n))
    }
  }
})

exports.fixed32 = fixed(32)
exports.fixed64 = fixed(64)

exports.array = function array(enc) {
  return {
    preencode(state, list) {
      uint.preencode(state, list.length)
      for (let i = 0; i < list.length; i++) enc.preencode(state, list[i])
    },
    encode(state, list) {
      uint.encode(state, list.length)
      for (let i = 0; i < list.length; i++) enc.encode(state, list[i])
    },
    decode(state) {
      const len = uint.decode(state)
      if (len > 0x100000) throw new Error('Array is too big')
      const arr = new Array(len)
      for (let i = 0; i < len; i++) arr[i] = enc.decode(state)
      return arr
    }
  }
}

exports.frame = function frame(enc) {
  const dummy = exports.state()

  return {
    preencode(state, m) {
      const end = state.end
      enc.preencode(state, m)
      uint.preencode(state, state.end - end)
    },
    encode(state, m) {
      dummy.end = 0
      enc.preencode(dummy, m)
      uint.encode(state, dummy.end)
      enc.encode(state, m)
    },
    decode(state) {
      const end = state.end
      const len = uint.decode(state)
      state.end = state.start + len
      const m = enc.decode(state)
      state.start = state.end
      state.end = end
      return m
    }
  }
}

exports.date = {
  preencode(state, d) {
    int.preencode(state, d.getTime())
  },
  encode(state, d) {
    int.encode(state, d.getTime())
  },
  decode(state, d) {
    return new Date(int.decode(state))
  }
}

exports.json = {
  preencode(state, v) {
    utf8.preencode(state, JSON.stringify(v))
  },
  encode(state, v) {
    utf8.encode(state, JSON.stringify(v))
  },
  decode(state) {
    return JSON.parse(utf8.decode(state))
  }
}

exports.ndjson = {
  preencode(state, v) {
    utf8.preencode(state, JSON.stringify(v) + '\n')
  },
  encode(state, v) {
    utf8.encode(state, JSON.stringify(v) + '\n')
  },
  decode(state) {
    return JSON.parse(utf8.decode(state))
  }
}

// simple helper for when you want to just express nothing
exports.none = {
  preencode(state, n) {
    // do nothing
  },
  encode(state, n) {
    // do nothing
  },
  decode(state) {
    return null
  }
}

// "any" encoders here for helping just structure any object without schematising it

const anyArray = {
  preencode(state, arr) {
    uint.preencode(state, arr.length)
    for (let i = 0; i < arr.length; i++) {
      any.preencode(state, arr[i])
    }
  },
  encode(state, arr) {
    uint.encode(state, arr.length)
    for (let i = 0; i < arr.length; i++) {
      any.encode(state, arr[i])
    }
  },
  decode(state) {
    const arr = []
    let len = uint.decode(state)
    while (len-- > 0) {
      arr.push(any.decode(state))
    }
    return arr
  }
}

const anyObject = {
  preencode(state, o) {
    const keys = Object.keys(o)
    uint.preencode(state, keys.length)
    for (const key of keys) {
      utf8.preencode(state, key)
      any.preencode(state, o[key])
    }
  },
  encode(state, o) {
    const keys = Object.keys(o)
    uint.encode(state, keys.length)
    for (const key of keys) {
      utf8.encode(state, key)
      any.encode(state, o[key])
    }
  },
  decode(state) {
    let len = uint.decode(state)
    const o = {}
    while (len-- > 0) {
      const key = utf8.decode(state)
      o[key] = any.decode(state)
    }
    return o
  }
}

const anyTypes = [
  exports.none,
  exports.bool,
  exports.string,
  exports.buffer,
  exports.uint,
  exports.int,
  exports.float64,
  anyArray,
  anyObject,
  exports.date
]

const any = (exports.any = {
  preencode(state, o) {
    const t = getType(o)
    uint.preencode(state, t)
    anyTypes[t].preencode(state, o)
  },
  encode(state, o) {
    const t = getType(o)
    uint.encode(state, t)
    anyTypes[t].encode(state, o)
  },
  decode(state) {
    const t = uint.decode(state)
    if (t >= anyTypes.length) throw new Error('Unknown type: ' + t)
    return anyTypes[t].decode(state)
  }
})

const port = (exports.port = uint16)

const address = (host, family) => {
  return {
    preencode(state, m) {
      host.preencode(state, m.host)
      port.preencode(state, m.port)
    },
    encode(state, m) {
      host.encode(state, m.host)
      port.encode(state, m.port)
    },
    decode(state) {
      return {
        host: host.decode(state),
        family,
        port: port.decode(state)
      }
    }
  }
}

const ipv4 = (exports.ipv4 = {
  preencode(state) {
    state.end += 4
  },
  encode(state, string) {
    const start = state.start
    const end = start + 4

    let i = 0

    while (i < string.length) {
      let n = 0
      let c

      while (i < string.length && (c = string.charCodeAt(i++)) !== /* . */ 0x2e) {
        n = n * 10 + (c - /* 0 */ 0x30)
      }

      state.buffer[state.start++] = n
    }

    state.start = end
  },
  decode(state) {
    if (state.end - state.start < 4) throw new Error('Out of bounds')
    return (
      state.buffer[state.start++] +
      '.' +
      state.buffer[state.start++] +
      '.' +
      state.buffer[state.start++] +
      '.' +
      state.buffer[state.start++]
    )
  }
})

exports.ipv4Address = address(ipv4, 4)

const ipv6 = (exports.ipv6 = {
  preencode(state) {
    state.end += 16
  },
  encode(state, string) {
    const start = state.start
    const end = start + 16

    let i = 0
    let split = null

    while (i < string.length) {
      let n = 0
      let c

      while (i < string.length && (c = string.charCodeAt(i++)) !== /* : */ 0x3a) {
        if (c >= 0x30 && c <= 0x39) n = n * 0x10 + (c - /* 0 */ 0x30)
        else if (c >= 0x41 && c <= 0x46) n = n * 0x10 + (c - /* A */ 0x41 + 10)
        else if (c >= 0x61 && c <= 0x66) n = n * 0x10 + (c - /* a */ 0x61 + 10)
      }

      state.buffer[state.start++] = n >>> 8
      state.buffer[state.start++] = n

      if (i < string.length && string.charCodeAt(i) === /* : */ 0x3a) {
        i++
        split = state.start
      }
    }

    if (split !== null) {
      const offset = end - state.start
      state.buffer.copyWithin(split + offset, split).fill(0, split, split + offset)
    }

    state.start = end
  },
  decode(state) {
    if (state.end - state.start < 16) throw new Error('Out of bounds')
    return (
      (state.buffer[state.start++] * 256 + state.buffer[state.start++]).toString(16) +
      ':' +
      (state.buffer[state.start++] * 256 + state.buffer[state.start++]).toString(16) +
      ':' +
      (state.buffer[state.start++] * 256 + state.buffer[state.start++]).toString(16) +
      ':' +
      (state.buffer[state.start++] * 256 + state.buffer[state.start++]).toString(16) +
      ':' +
      (state.buffer[state.start++] * 256 + state.buffer[state.start++]).toString(16) +
      ':' +
      (state.buffer[state.start++] * 256 + state.buffer[state.start++]).toString(16) +
      ':' +
      (state.buffer[state.start++] * 256 + state.buffer[state.start++]).toString(16) +
      ':' +
      (state.buffer[state.start++] * 256 + state.buffer[state.start++]).toString(16)
    )
  }
})

exports.ipv6Address = address(ipv6, 6)

const ip = (exports.ip = {
  preencode(state, string) {
    const family = string.includes(':') ? 6 : 4
    uint8.preencode(state, family)
    if (family === 4) ipv4.preencode(state)
    else ipv6.preencode(state)
  },
  encode(state, string) {
    const family = string.includes(':') ? 6 : 4
    uint8.encode(state, family)
    if (family === 4) ipv4.encode(state, string)
    else ipv6.encode(state, string)
  },
  decode(state) {
    const family = uint8.decode(state)
    if (family === 4) return ipv4.decode(state)
    else return ipv6.decode(state)
  }
})

exports.ipAddress = {
  preencode(state, m) {
    ip.preencode(state, m.host)
    port.preencode(state, m.port)
  },
  encode(state, m) {
    ip.encode(state, m.host)
    port.encode(state, m.port)
  },
  decode(state) {
    const family = uint8.decode(state)
    return {
      host: family === 4 ? ipv4.decode(state) : ipv6.decode(state),
      family,
      port: port.decode(state)
    }
  }
}

const record = (exports.record = function (keyEncoding, valueEncoding) {
  return {
    preencode(state, v) {
      const keys = Object.keys(v)
      uint.preencode(state, keys.length)
      for (const k of keys) {
        keyEncoding.preencode(state, k)
        valueEncoding.preencode(state, v[k])
      }
    },
    encode(state, v) {
      const keys = Object.keys(v)
      uint.encode(state, keys.length)
      for (const k of keys) {
        keyEncoding.encode(state, k)
        valueEncoding.encode(state, v[k])
      }
    },
    decode(state) {
      const out = Object.create(null)
      const keys = uint.decode(state)
      for (let i = 0; i < keys; i++) {
        out[keyEncoding.decode(state)] = valueEncoding.decode(state)
      }
      return out
    }
  }
})

exports.stringRecord = record(utf8, utf8)

function getType(o) {
  if (o === null || o === undefined) return 0
  if (typeof o === 'boolean') return 1
  if (typeof o === 'string') return 2
  if (b4a.isBuffer(o)) return 3
  if (typeof o === 'number') {
    if (Number.isInteger(o)) return o >= 0 ? 4 : 5
    return 6
  }
  if (Array.isArray(o)) return 7
  if (o instanceof Date) return 9
  if (typeof o === 'object') return 8

  throw new Error('Unsupported type for ' + o)
}

exports.from = function from(enc) {
  if (typeof enc === 'string') return fromNamed(enc)
  if (enc.preencode) return enc
  if (enc.encodingLength) return fromAbstractEncoder(enc)
  return fromCodec(enc)
}

function fromNamed(enc) {
  switch (enc) {
    case 'ascii':
      return raw.ascii
    case 'utf-8':
    case 'utf8':
      return raw.utf8
    case 'hex':
      return raw.hex
    case 'base64':
      return raw.base64
    case 'utf16-le':
    case 'utf16le':
    case 'ucs-2':
    case 'ucs2':
      return raw.ucs2
    case 'ndjson':
      return raw.ndjson
    case 'json':
      return raw.json
    case 'binary':
    default:
      return raw.binary
  }
}

function fromCodec(enc) {
  let tmpM = null
  let tmpBuf = null

  return {
    preencode(state, m) {
      tmpM = m
      tmpBuf = enc.encode(m)
      state.end += tmpBuf.byteLength
    },
    encode(state, m) {
      raw.encode(state, m === tmpM ? tmpBuf : enc.encode(m))
      tmpM = tmpBuf = null
    },
    decode(state) {
      return enc.decode(raw.decode(state))
    }
  }
}

function fromAbstractEncoder(enc) {
  return {
    preencode(state, m) {
      state.end += enc.encodingLength(m)
    },
    encode(state, m) {
      enc.encode(m, state.buffer, state.start)
      state.start += enc.encode.bytes
    },
    decode(state) {
      const m = enc.decode(state.buffer, state.start, state.end)
      state.start += enc.decode.bytes
      return m
    }
  }
}

exports.encode = function encode(enc, m) {
  const state = exports.state()
  enc.preencode(state, m)
  state.buffer = b4a.allocUnsafe(state.end)
  enc.encode(state, m)
  return state.buffer
}

exports.decode = function decode(enc, buffer) {
  return enc.decode(exports.state(0, buffer.byteLength, buffer))
}

function zigZagInt(enc) {
  return {
    preencode(state, n) {
      enc.preencode(state, zigZagEncodeInt(n))
    },
    encode(state, n) {
      enc.encode(state, zigZagEncodeInt(n))
    },
    decode(state) {
      return zigZagDecodeInt(enc.decode(state))
    }
  }
}

function zigZagDecodeInt(n) {
  return n === 0 ? n : (n & 1) === 0 ? n / 2 : -(n + 1) / 2
}

function zigZagEncodeInt(n) {
  validateInt(n)
  // 0, -1, 1, -2, 2, ...
  return n < 0 ? 2 * -n - 1 : n === 0 ? 0 : 2 * n
}

function zigZagBigInt(enc) {
  return {
    preencode(state, n) {
      enc.preencode(state, zigZagEncodeBigInt(n))
    },
    encode(state, n) {
      enc.encode(state, zigZagEncodeBigInt(n))
    },
    decode(state) {
      return zigZagDecodeBigInt(enc.decode(state))
    }
  }
}

function zigZagDecodeBigInt(n) {
  return n === 0n ? n : (n & 1n) === 0n ? n / 2n : -(n + 1n) / 2n
}

function zigZagEncodeBigInt(n) {
  // 0, -1, 1, -2, 2, ...
  return n < 0n ? 2n * -n - 1n : n === 0n ? 0n : 2n * n
}

function validateSafeUint(n) {
  if (n <= Number.MAX_SAFE_INTEGER) return n // Handles NaN as well

  throw outsideUintRange()
}

function validateUint(n) {
  if (n >= 0 && n <= Number.MAX_SAFE_INTEGER) return n // Handles NaN as well

  throw outsideUintRange()
}

function validateInt(n) {
  if (n >= MIN_SAFE_INT && n <= MAX_SAFE_INT) return n // Handles NaN as well

  throw outsideIntRange()
}

// The validations above sit on the hottest paths in the library and are small
// enough to be inlined, which building a message inline would put a stop to.
// Kept out here, the message costs nothing until it is actually thrown.

function outsideUintRange() {
  return new Error(`uint must be between 0 and ${Number.MAX_SAFE_INTEGER}, use biguint`)
}

function outsideIntRange() {
  return new Error(`int must be between ${MIN_SAFE_INT} and ${MAX_SAFE_INT}, use bigint`)
}
module.exports = {
  preencode,
  encode,
  decode
}

function preencode(state, num) {
  if (num < 251) {
    state.end++
  } else if (num < 256) {
    state.end += 2
  } else if (num < 0x10000) {
    state.end += 3
  } else if (num < 0x1000000) {
    state.end += 4
  } else if (num < 0x100000000) {
    state.end += 5
  } else {
    state.end++
    const exp = Math.floor(Math.log(num) / Math.log(2)) - 32
    preencode(state, exp)
    state.end += 6
  }
}

function encode(state, num) {
  const max = 251
  const x = num - max

  if (num < max) {
    state.buffer[state.start++] = num
  } else if (num < 256) {
    state.buffer[state.start++] = max
    state.buffer[state.start++] = x
  } else if (num < 0x10000) {
    state.buffer[state.start++] = max + 1
    state.buffer[state.start++] = (x >> 8) & 0xff
    state.buffer[state.start++] = x & 0xff
  } else if (num < 0x1000000) {
    state.buffer[state.start++] = max + 2
    state.buffer[state.start++] = x >> 16
    state.buffer[state.start++] = (x >> 8) & 0xff
    state.buffer[state.start++] = x & 0xff
  } else if (num < 0x100000000) {
    state.buffer[state.start++] = max + 3
    state.buffer[state.start++] = x >> 24
    state.buffer[state.start++] = (x >> 16) & 0xff
    state.buffer[state.start++] = (x >> 8) & 0xff
    state.buffer[state.start++] = x & 0xff
  } else {
    // need to use Math here as bitwise ops are 32 bit
    const exp = Math.floor(Math.log(x) / Math.log(2)) - 32
    state.buffer[state.start++] = 0xff

    encode(state, exp)
    const rem = x / Math.pow(2, exp - 11)

    for (let i = 5; i >= 0; i--) {
      state.buffer[state.start++] = (rem / Math.pow(2, 8 * i)) & 0xff
    }
  }
}

function decode(state) {
  const max = 251

  if (state.end - state.start < 1) throw new Error('Out of bounds')

  const flag = state.buffer[state.start++]

  if (flag < max) return flag

  if (state.end - state.start < flag - max + 1) {
    throw new Error('Out of bounds.')
  }

  if (flag < 252) {
    return state.buffer[state.start++] + max
  }

  if (flag < 253) {
    return (state.buffer[state.start++] << 8) + state.buffer[state.start++] + max
  }

  if (flag < 254) {
    return (
      (state.buffer[state.start++] << 16) +
      (state.buffer[state.start++] << 8) +
      state.buffer[state.start++] +
      max
    )
  }

  // << 24 result may be interpreted as negative
  if (flag < 255) {
    return (
      state.buffer[state.start++] * 0x1000000 +
      (state.buffer[state.start++] << 16) +
      (state.buffer[state.start++] << 8) +
      state.buffer[state.start++] +
      max
    )
  }

  const exp = decode(state)

  if (state.end - state.start < 6) throw new Error('Out of bounds')

  let rem = 0
  for (let i = 5; i >= 0; i--) {
    rem += state.buffer[state.start++] * Math.pow(2, 8 * i)
  }

  return rem * Math.pow(2, exp - 11) + max
}
{
  "name": "compact-encoding",
  "version": "3.4.0",
  "description": "A series of compact encoding schemes for building small and fast parsers and serializers",
  "main": "index.js",
  "types": "index.d.ts",
  "files": [
    "endian.js",
    "endian.d.ts",
    "index.js",
    "index.d.ts",
    "lexint.js",
    "lexint.d.ts",
    "raw.js",
    "raw.d.ts"
  ],
  "dependencies": {
    "b4a": "^1.3.0"
  },
  "devDependencies": {
    "brittle": "^4.0.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^2.0.0"
  },
  "scripts": {
    "format": "prettier . --write",
    "test": "prettier . --check && brittle-node test.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/holepunchto/compact-encoding.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/compact-encoding/issues"
  },
  "homepage": "https://github.com/holepunchto/compact-encoding"
}
const b4a = require('b4a')

const { BE } = require('./endian')

exports = module.exports = {
  preencode(state, b) {
    state.end += b.byteLength
  },
  encode(state, b) {
    state.buffer.set(b, state.start)
    state.start += b.byteLength
  },
  decode(state) {
    const b = state.buffer.subarray(state.start, state.end)
    state.start = state.end
    return b
  }
}

const buffer = (exports.buffer = {
  preencode(state, b) {
    uint8array.preencode(state, b)
  },
  encode(state, b) {
    uint8array.encode(state, b)
  },
  decode(state) {
    const b = state.buffer.subarray(state.start)
    state.start = state.end
    return b
  }
})

exports.binary = {
  ...buffer,
  preencode(state, b) {
    if (typeof b === 'string') utf8.preencode(state, b)
    else buffer.preencode(state, b)
  },
  encode(state, b) {
    if (typeof b === 'string') utf8.encode(state, b)
    else buffer.encode(state, b)
  }
}

exports.arraybuffer = {
  preencode(state, b) {
    state.end += b.byteLength
  },
  encode(state, b) {
    const view = new Uint8Array(b)

    state.buffer.set(view, state.start)
    state.start += b.byteLength
  },
  decode(state) {
    const b = new ArrayBuffer(state.end - state.start)
    const view = new Uint8Array(b)

    view.set(state.buffer.subarray(state.start))

    state.start = state.end

    return b
  }
}

function typedarray(TypedArray, swap) {
  const n = TypedArray.BYTES_PER_ELEMENT

  return {
    preencode(state, b) {
      state.end += b.byteLength
    },
    encode(state, b) {
      const view = new Uint8Array(b.buffer, b.byteOffset, b.byteLength)

      if (BE && swap) swap(view)

      state.buffer.set(view, state.start)
      state.start += b.byteLength
    },
    decode(state) {
      let b = state.buffer.subarray(state.start)
      if (b.byteOffset % n !== 0) b = new Uint8Array(b)

      if (BE && swap) swap(b)

      state.start = state.end

      return new TypedArray(b.buffer, b.byteOffset, b.byteLength / n)
    }
  }
}

const uint8array = (exports.uint8array = typedarray(Uint8Array))
exports.uint16array = typedarray(Uint16Array, b4a.swap16)
exports.uint32array = typedarray(Uint32Array, b4a.swap32)

exports.int8array = typedarray(Int8Array)
exports.int16array = typedarray(Int16Array, b4a.swap16)
exports.int32array = typedarray(Int32Array, b4a.swap32)

exports.biguint64array = typedarray(BigUint64Array, b4a.swap64)
exports.bigint64array = typedarray(BigInt64Array, b4a.swap64)

exports.float32array = typedarray(Float32Array, b4a.swap32)
exports.float64array = typedarray(Float64Array, b4a.swap64)

function string(encoding) {
  return {
    preencode(state, s) {
      state.end += b4a.byteLength(s, encoding)
    },
    encode(state, s) {
      state.start += b4a.write(state.buffer, s, state.start, encoding)
    },
    decode(state) {
      const s = b4a.toString(state.buffer, encoding, state.start)
      state.start = state.end
      return s
    }
  }
}

const utf8 = (exports.string = exports.utf8 = string('utf-8'))
exports.ascii = string('ascii')
exports.hex = string('hex')
exports.base64 = string('base64')
exports.ucs2 = exports.utf16le = string('utf16le')

exports.array = function array(enc) {
  return {
    preencode(state, list) {
      for (const value of list) enc.preencode(state, value)
    },
    encode(state, list) {
      for (const value of list) enc.encode(state, value)
    },
    decode(state) {
      const arr = []
      while (state.start < state.end) arr.push(enc.decode(state))
      return arr
    }
  }
}

exports.json = {
  preencode(state, v) {
    utf8.preencode(state, JSON.stringify(v))
  },
  encode(state, v) {
    utf8.encode(state, JSON.stringify(v))
  },
  decode(state) {
    return JSON.parse(utf8.decode(state))
  }
}

exports.ndjson = {
  preencode(state, v) {
    utf8.preencode(state, JSON.stringify(v) + '\n')
  },
  encode(state, v) {
    utf8.encode(state, JSON.stringify(v) + '\n')
  },
  decode(state) {
    return JSON.parse(utf8.decode(state))
  }
}
const safetyCatch = require('safety-catch')

module.exports = class CoreCoupler {
  constructor (target, wakeup) {
    this.target = target
    this.wakeup = wakeup
    this.coupled = new Set()

    this._onpeeraddBound = this._onpeeradd.bind(this)
    this.target.on('peer-add', this._onpeeraddBound)
  }

  add (core) {
    const added = this.coupled.size
    this.coupled.add(core)
    if (added !== this.coupled.size) this._couple(core)
  }

  remove (core) {
    this.coupled.delete(core)
  }

  destroy () {
    this.target.off('peer-add', this._onpeeraddBound)
  }

  async update (stream) {
    const muxer = stream.userData
    if (!muxer) return

    try {
      if (!(await this._hasMuxer(this.target, muxer))) return

      let wakeup = null

      for (const core of this.coupled) {
        if (await this._hasMuxer(core, muxer)) continue
        if (wakeup === null) wakeup = []
        wakeup.push(core)
      }

      if (wakeup !== null) {
        this.wakeup(stream, wakeup)
      }
    } catch (err) {
      safetyCatch(err)
    }
  }

  async _couple (core) {
    try {
      let wakeup = null

      for (const peer of this.target.peers) {
        if (await this._hasPeer(core, peer)) continue
        if (wakeup === null) wakeup = []
        wakeup.push(peer)
      }

      if (wakeup !== null && this.coupled.has(core)) {
        for (const peer of wakeup) this.wakeup(peer.stream, [core])
      }
    } catch (err) {
      safetyCatch(err)
    }
  }

  async _onpeeradd (peer) {
    try {
      let wakeup = null

      for (const core of this.coupled) {
        if (await this._hasPeer(core, peer)) continue
        if (wakeup === null) wakeup = []
        wakeup.push(core)
      }

      if (wakeup !== null) {
        this.wakeup(peer.stream, wakeup)
      }
    } catch (err) {
      safetyCatch(err)
    }
  }

  _hasMuxer (core, muxer) {
    const ch = muxer.getLastChannel({ protocol: 'hypercore', id: core.discoveryKey })
    if (ch) return ch.fullyOpened()

    const cha = muxer.getLastChannel({ protocol: 'hypercore/alpha', id: core.discoveryKey })
    if (cha) return cha.fullyOpened()

    return Promise.resolve(false)
  }

  _hasPeer (core, peer) {
    return this._hasMuxer(core, peer.protomux)
  }
}
{
  "name": "core-coupler",
  "version": "2.0.0",
  "description": "Couple the peers of cores",
  "main": "index.js",
  "scripts": {
    "test": "standard"
  },
  "dependencies": {
    "safety-catch": "^1.0.2"
  },
  "devDependencies": {
    "corestore": "^6.18.2",
    "standard": "^17.1.0"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/holepunchto/core-coupler.git"
  },
  "author": "Holepunch Inc",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/core-coupler/issues"
  },
  "homepage": "https://github.com/holepunchto/core-coupler"
}
const b4a = require('b4a')
const Hypercore = require('hypercore')
const ReadyResource = require('ready-resource')
const sodium = require('sodium-universal')
const crypto = require('hypercore-crypto')
const ID = require('hypercore-id-encoding')
const { isAndroid } = require('which-runtime')
const { STORAGE_EMPTY, ASSERTION } = require('hypercore-errors')

const auditStore = require('./lib/audit.js')
const GroupNotifyHandle = require('./lib/notify.js')

const [NS] = crypto.namespace('corestore', 1)
const DEFAULT_NAMESPACE = b4a.alloc(32) // This is meant to be 32 0-bytes

class StreamTracker {
  constructor() {
    this.records = []
  }

  add(stream, isExternal) {
    const record = { index: 0, stream, isExternal }
    record.index = this.records.push(record) - 1
    return record
  }

  remove(record) {
    const popped = this.records.pop()
    if (popped === record) return
    this.records[(popped.index = record.index)] = popped
  }

  attachAll(core) {
    for (let i = 0; i < this.records.length; i++) {
      const record = this.records[i]
      const muxer = record.stream.noiseStream.userData
      if (!core.replicator.attached(muxer)) core.replicator.attachTo(muxer)
    }
  }

  destroy() {
    // reverse is safer cause we delete mb
    for (let i = this.records.length - 1; i >= 0; i--) {
      const record = this.records[i]
      if (!record.isExternal) record.stream.destroy()
    }
  }
}

class SessionTracker {
  constructor() {
    this.map = new Map()
  }

  get size() {
    return this.map.size
  }

  get(id) {
    const existing = this.map.get(id)
    if (existing !== undefined) return existing
    const fresh = []
    this.map.set(id, fresh)
    return fresh
  }

  gc(id) {
    this.map.delete(id)
  }

  list(id) {
    return id ? this.map.get(id) || [] : [...this]
  }

  *[Symbol.iterator]() {
    for (const sessions of this.map.values()) {
      yield* sessions[Symbol.iterator]()
    }
  }
}

class CoreTracker {
  constructor() {
    this.map = new Map()
    this.watching = []

    this._gcing = new Set()
    this._gcInterval = null
    this._gcCycleBound = this._gcCycle.bind(this)
  }

  get size() {
    return this.map.size
  }

  watch(store) {
    if (store.watchIndex !== -1) return
    store.watchIndex = this.watching.push(store) - 1
  }

  unwatch(store) {
    if (store.watchIndex === -1) return
    const head = this.watching.pop()
    if (head !== store) this.watching[(head.watchIndex = store.watchIndex)] = head
    store.watchIndex = -1
  }

  resume(id) {
    const core = this.map.get(id)

    if (!core) return null

    // signal back that we have a closing one stored
    if (core.closing) return core

    if (core.gc) {
      this._gcing.delete(core)
      if (this._gcing.size === 0) this._stopGC()
      core.gc = 0
    }

    return core
  }

  opened(id) {
    const core = this.map.get(id)
    return !!(core && core.opened && !core.closing)
  }

  get(id) {
    // we allow you do call this from the outside, so support normal buffers also
    if (b4a.isBuffer(id)) id = b4a.toString(id, 'hex')
    const core = this.map.get(id)
    if (!core || core.closing) return null
    return core
  }

  set(id, core) {
    this.map.set(id, core)
    if (this.watching.length > 0) this._emit(core)
  }

  _emit(core) {
    for (let i = this.watching.length - 1; i >= 0; i--) {
      const store = this.watching[i]
      for (const fn of store.watchers) fn(core)
    }
  }

  _gc(core) {
    const id = toHex(core.discoveryKey)
    if (this.map.get(id) === core) this.map.delete(id)
  }

  _gcCycle() {
    for (const core of this._gcing) {
      if (++core.gc < 4) continue
      const gc = this._gc.bind(this, core)
      core.close().then(gc, gc)
      this._gcing.delete(core)
    }

    if (this._gcing.size === 0) this._stopGC()
  }

  gc(core) {
    core.gc = 1 // first strike
    this._gcing.add(core)
    if (this._gcing.size === 1) this._startGC()
  }

  _stopGC() {
    clearInterval(this._gcInterval)
    this._gcInterval = null
  }

  _startGC() {
    if (this._gcInterval) return
    this._gcInterval = setInterval(this._gcCycleBound, 2000)
    if (this._gcInterval.unref) this._gcInterval.unref()
  }

  close() {
    this._stopGC()
    this._gcing.clear()

    const all = []
    for (const core of this.map.values()) {
      core.onidle = noop // no reentry
      all.push(core.close())
    }
    this.map.clear()

    return Promise.all(all)
  }

  *[Symbol.iterator]() {
    for (const core of this.map.values()) {
      if (!core.closing) yield core
    }
  }
}

class FindingPeers {
  constructor() {
    this.count = 0
    this.pending = []
    this.destroyed = false
  }

  add(core) {
    if (this.count === 0) return
    this.pending.push(core.findingPeers())
  }

  inc(sessions) {
    if (this.destroyed || ++this.count !== 1) return

    for (const core of sessions) {
      this.pending.push(core.findingPeers())
    }
  }

  dec(sessions) {
    if (this.destroyed || --this.count !== 0) return
    while (this.pending.length > 0) this.pending.pop()()
  }

  destroy() {
    this.destroyed = true
    while (this.pending.length > 0) this.pending.pop()()
  }
}

class Corestore extends ReadyResource {
  constructor(storage, opts = {}) {
    super()

    this.root = opts.root || null
    this.storage = this.root
      ? this.root.storage
      : Hypercore.defaultStorage(storage, {
          id: opts.id,
          allowBackup: opts.allowBackup,
          readOnly: opts.readOnly,
          wait: opts.wait,
          treeCache: opts.treeCache
        })
    this.streamTracker = this.root ? this.root.streamTracker : new StreamTracker()
    this.cores = this.root ? this.root.cores : new CoreTracker()
    this.sessions = new SessionTracker()
    this.corestores = this.root ? this.root.corestores : new Set()
    this.readOnly = opts.writable === false || !!opts.readOnly
    this.globalCache = this.root ? this.root.globalCache : opts.globalCache || null
    this.primaryKey = this.root ? this.root.primaryKey : opts.primaryKey || null
    this.ns = opts.namespace || DEFAULT_NAMESPACE
    this.manifestVersion = opts.manifestVersion || 1
    this.shouldSuspend = isAndroid ? !!opts.suspend : opts.suspend !== false
    this.active = opts.active !== false

    this.watchers = null
    this.watchIndex = -1

    this._groupNotifiers = this.root ? this.root._groupNotifiers : new Map() // group notifications
    this._findingPeers = null // here for legacy
    this._ongcBound = this._ongc.bind(this)
    this._onGroupActiveBound = this._onGroupActive.bind(this)

    if (opts.primaryKey && !this.root && !opts.unsafe) {
      throw ASSERTION(
        'Passing the primary key is unsafe unless you know what you are doing. Set unsafe: true to acknowledge that'
      )
    }

    this.ready().catch(noop)
  }

  watch(fn) {
    if (this.watchers === null) {
      this.watchers = new Set()
      this.cores.watch(this)
    }

    this.watchers.add(fn)
  }

  unwatch(fn) {
    if (this.watchers === null) return

    this.watchers.delete(fn)

    if (this.watchers.size === 0) {
      this.watchers = null
      this.cores.unwatch(this)
    }
  }

  _onGroupActive(topic, update) {
    this.emit('group-active', topic, update)

    const topicHex = b4a.toString(topic, 'hex')
    const handles = this._groupNotifiers.get(topicHex)
    if (!handles) return
    for (const handle of handles) handle.emit('update', update)
  }

  notifyGroup(topic) {
    const topicHex = b4a.toString(topic, 'hex')
    let handles = this._groupNotifiers.get(topicHex)

    if (!handles) {
      handles = []
      this._groupNotifiers.set(topicHex, handles)
    }

    const handle = new GroupNotifyHandle(this, topic)
    const length = handles.push(handle)
    handle.index = length - 1

    return handle
  }

  _removeGroupNotify(handle) {
    if (handle.index === -1) return

    const topicHex = b4a.toString(handle._topic, 'hex')
    const handles = this._groupNotifiers.get(topicHex)
    if (!handles) return

    const popped = handles.pop()
    if (popped !== handle) handles[(popped.index = handle.index)] = popped
    if (handles.length === 0) this._groupNotifiers.delete(topicHex)
    handle.index = -1
  }

  findingPeers() {
    if (this._findingPeers === null) this._findingPeers = new FindingPeers()
    this._findingPeers.inc(this.sessions)
    let done = false
    return () => {
      if (done || !this._findingPeers) return
      done = true
      this._findingPeers.dec(this.sessions)
    }
  }

  audit(opts = {}) {
    return auditStore(this, opts)
  }

  async suspend({ log = noop } = {}) {
    if (!this.shouldSuspend) return
    await log('Suspending db...')
    await this.storage.suspend({ log })
    await log('Suspending db completed')
  }

  resume() {
    return this.storage.resume()
  }

  session(opts) {
    this._maybeClosed()
    const root = this.root || this
    return new Corestore(null, {
      manifestVersion: this.manifestVersion,
      ...opts,
      root
    })
  }

  namespace(name, opts) {
    return this.session({
      ...opts,
      namespace: generateNamespace(this.ns, name)
    })
  }

  list(namespace) {
    return this.storage.createDiscoveryKeyStream(namespace)
  }

  getAuth(discoveryKey) {
    return this.storage.getAuth(discoveryKey)
  }

  _ongc(session) {
    if (session.sessions.length === 0) this.sessions.gc(session.id)
  }

  async _getOrSetSeed() {
    const seed = await this.storage.getSeed()
    if (seed !== null) return seed
    return await this.storage.setSeed(this.primaryKey || crypto.randomBytes(32))
  }

  async _open() {
    if (this.root !== null) {
      this.corestores.add(this)
      if (this.root.opened === false) await this.root.ready()
      this.primaryKey = this.root.primaryKey
      return
    }

    const primaryKey = await this._getOrSetSeed()

    if (this.primaryKey === null) {
      this.primaryKey = primaryKey
      return
    }

    if (!b4a.equals(primaryKey, this.primaryKey)) {
      throw new Error('Another corestore is stored here')
    }
  }

  async _close() {
    const closing = []
    const hanging = [...this.sessions]
    for (const sess of hanging) closing.push(sess.close())

    if (this.watchers !== null) {
      this.cores.unwatch(this)
      this.watchers = null
    }

    if (this._findingPeers !== null) {
      this._findingPeers.destroy()
      this._findingPeers = null
    }

    if (this.root !== null) {
      await Promise.all(closing)
      this.corestores.delete(this)
      return
    }

    for (const store of this.corestores) {
      closing.push(store.close())
    }

    await Promise.all(closing)

    await this.cores.close()
    await this.storage.close()
  }

  async _attachMaybe(muxer, discoveryKey) {
    if (this.opened === false) await this.ready()
    if (
      !this.cores.opened(toHex(discoveryKey)) &&
      !(await this.storage.hasCore(discoveryKey, { ifMigrated: true }))
    ) {
      return
    }
    if (this.closing) return

    const core = this._openCore(discoveryKey, { createIfMissing: false })

    if (!core) return
    if (!core.opened) await core.ready()

    if (!core.replicator.attached(muxer)) {
      core.replicator.attachTo(muxer)
    }

    core.checkIfIdle()
  }

  _shouldReplicate(core, muxer) {
    return (
      core.replicator.downloading && !core.replicator.attached(muxer) && core.opened && this.active
    )
  }

  replicate(isInitiator, opts) {
    this._maybeClosed()

    const isExternal = isStream(isInitiator)
    const stream = Hypercore.createProtocolStream(isInitiator, {
      ...opts,
      ondiscoverykey: (discoveryKey) => {
        if (this.closing) return
        const muxer = stream.noiseStream.userData
        return this._attachMaybe(muxer, discoveryKey)
      }
    })

    if (this.cores.size > 0) {
      const muxer = stream.noiseStream.userData
      const uncork = muxer.uncork.bind(muxer)
      muxer.cork()

      for (const core of this.cores) {
        if (!this._shouldReplicate(core, muxer)) {
          continue
        }
        core.replicator.attachTo(muxer)
      }

      stream.noiseStream.opened.then(uncork)
    }

    const record = this.streamTracker.add(stream, isExternal)
    stream.once('close', () => this.streamTracker.remove(record))
    return stream
  }

  _maybeClosed() {
    if (this.closing || (this.root !== null && this.root.closing)) {
      throw new Error('Corestore is closed')
    }
  }

  async staticify(core, opts) {
    if (!this.opened) await this.ready()
    if (!core.opened) await core.ready()

    const rx = core.state.storage.read()

    const headPromise = rx.getHead()
    const authPromise = rx.getAuth()

    rx.tryFlush()

    const [head, auth] = await Promise.all([headPromise, authPromise])
    if (!head || head.length === 0) throw new Error('Core must have data')

    const prologue = {
      length: head.length,
      hash: head.rootHash
    }

    const manifest = {
      version: 1,
      hash: auth.manifest.hash,
      quorum: 0,
      signers: [],
      prologue
    }

    const c = {
      key: null,
      discoveryKey: null,
      manifest,
      core: core.state.storage.core
    }

    c.key = Hypercore.key(c.manifest)
    c.discoveryKey = Hypercore.discoveryKey(c.key)

    await this.storage.createCore(c)

    const staticCore = this.get({ ...opts, key: c.key })
    await staticCore.ready()
    return staticCore
  }

  get(opts) {
    this._maybeClosed()

    if (b4a.isBuffer(opts) || typeof opts === 'string') opts = { key: opts }
    if (!opts) opts = {}

    const conf = {
      preload: null,
      sessions: null,
      ongc: null,
      core: null,
      active: opts.active !== false,
      encryption: opts.encryption || null,
      encryptionKey: opts.encryptionKey || null, // back compat, should remove
      isBlockKey: !!opts.isBlockKey, // back compat, should remove
      valueEncoding: opts.valueEncoding || null,
      exclusive: !!opts.exclusive,
      manifest: opts.manifest || null,
      keyPair: opts.keyPair || null,
      onwait: opts.onwait || null,
      pushOnly: opts.pushOnly === true,
      wait: opts.wait !== false,
      timeout: opts.timeout || 0,
      draft: !!opts.draft,
      writable: opts.writable === undefined && this.readOnly ? false : opts.writable,
      group: opts.group || null
    }

    // name requires us to rt to storage + ready, so needs preload
    // same goes if user has defined async preload obvs
    if (opts.name || opts.preload) {
      conf.preload = this._preload(opts)
      return this._makeSession(conf)
    }

    if (opts.discoveryKey && !opts.key && !opts.manifest) {
      conf.preload = this._preloadCheckIfExists(opts)
      return this._makeSession(conf)
    }

    // if not not we can sync create it, which just is easier for the
    // upstream user in terms of guarantees (key is there etc etc)
    const core = this._openCore(null, opts)

    conf.core = core
    conf.sessions = this.sessions.get(core.id)
    conf.ongc = this._ongcBound

    return this._makeSession(conf)
  }

  _makeSession(conf) {
    const session = new Hypercore(null, null, conf)

    if (this._findingPeers !== null) this._findingPeers.add(session)
    return session
  }

  async createKeyPair(name, ns = this.ns) {
    if (this.opened === false) await this.ready()
    return createKeyPair(this.primaryKey, ns, name)
  }

  async _preloadCheckIfExists(opts) {
    const has = await this.storage.hasCore(opts.discoveryKey)
    if (!has) throw STORAGE_EMPTY('No Hypercore is stored here')
    return this._preload(opts)
  }

  async _preload(opts) {
    if (opts.preload) opts = { ...opts, ...(await opts.preload) }
    if (this.opened === false) await this.ready()

    const discoveryKey = opts.name
      ? await this.storage.getAlias({ name: opts.name, namespace: this.ns })
      : null
    this._maybeClosed()

    const core = this._openCore(discoveryKey, opts)

    return {
      core,
      sessions: this.sessions.get(core.id),
      ongc: this._ongcBound,
      manifest: opts.manifest || null,
      encryption: opts.encryption || null,
      encryptionKey: opts.encryptionKey || null, // back compat, should remove
      isBlockKey: !!opts.isBlockKey // back compat, should remove
    }
  }

  _auth(discoveryKey, opts) {
    const result = {
      keyPair: null,
      key: null,
      discoveryKey,
      manifest: null,
      group: null
    }

    if (opts.name) {
      result.keyPair = createKeyPair(this.primaryKey, this.ns, opts.name)
    } else if (opts.keyPair) {
      result.keyPair = opts.keyPair
    }

    if (opts.manifest) {
      result.manifest = opts.manifest
    } else if (result.keyPair && !result.discoveryKey) {
      result.manifest = {
        version: this.manifestVersion,
        signers: [{ publicKey: result.keyPair.publicKey }]
      }
    }

    if (opts.group) {
      result.group = opts.group
    }

    if (opts.key) result.key = ID.decode(opts.key)
    else if (result.manifest) result.key = Hypercore.key(result.manifest)

    if (result.discoveryKey) return result

    if (opts.discoveryKey) result.discoveryKey = ID.decode(opts.discoveryKey)
    else if (result.key) result.discoveryKey = crypto.discoveryKey(result.key)
    else throw new Error('Could not derive discovery from input')

    return result
  }

  _openCore(discoveryKey, opts) {
    const auth = this._auth(discoveryKey, opts)

    const id = toHex(auth.discoveryKey)
    const existing = this.cores.resume(id)
    if (existing && !existing.closing) return existing

    const core = Hypercore.createCore(this.storage, {
      preopen: existing && existing.opened ? existing.closing : null, // always wait for the prev one to close first in any case...
      eagerUpgrade: true,
      notDownloadingLinger: opts.notDownloadingLinger,
      allowFork: opts.allowFork !== false,
      allowPush: !!opts.allowPush,
      inflightRange: opts.inflightRange,
      compat: false, // no compat for now :)
      force: opts.force,
      createIfMissing: opts.createIfMissing,
      discoveryKey: auth.discoveryKey,
      overwrite: opts.overwrite,
      key: auth.key,
      keyPair: auth.keyPair,
      legacy: opts.legacy,
      manifest: auth.manifest,
      group: auth.group,
      globalCache: opts.globalCache || this.globalCache || null,
      alias: opts.name ? { name: opts.name, namespace: this.ns } : null
    })

    core.onidle = () => {
      this.cores.gc(core)
    }

    core.ongroupupdate = this._onGroupActiveBound

    core.replicator.ondownloading = () => {
      if (this.active) this.streamTracker.attachAll(core)
    }

    this.cores.set(id, core)
    return core
  }
}

module.exports = Corestore

function isStream(s) {
  return typeof s === 'object' && s && typeof s.pipe === 'function'
}

function generateNamespace(namespace, name) {
  if (!b4a.isBuffer(name)) name = b4a.from(name)
  const out = b4a.allocUnsafeSlow(32)
  sodium.crypto_generichash_batch(out, [namespace, name])
  return out
}

function deriveSeed(primaryKey, namespace, name) {
  if (!b4a.isBuffer(name)) name = b4a.from(name)
  const out = b4a.alloc(32)
  sodium.crypto_generichash_batch(out, [NS, namespace, name], primaryKey)
  return out
}

function createKeyPair(primaryKey, namespace, name) {
  const seed = deriveSeed(primaryKey, namespace, name)
  const buf = b4a.alloc(sodium.crypto_sign_PUBLICKEYBYTES + sodium.crypto_sign_SECRETKEYBYTES)
  const keyPair = {
    publicKey: buf.subarray(0, sodium.crypto_sign_PUBLICKEYBYTES),
    secretKey: buf.subarray(sodium.crypto_sign_PUBLICKEYBYTES)
  }
  sodium.crypto_sign_seed_keypair(keyPair.publicKey, keyPair.secretKey, seed)
  return keyPair
}

function noop() {}

function toHex(discoveryKey) {
  return b4a.toString(discoveryKey, 'hex')
}
module.exports = async function* audit(store, { dryRun = false } = {}) {
  for await (const { discoveryKey, core } of store.storage.createCoreStream()) {
    if (core.version < 1) continue // not migrated, ignore

    const c = store.get({ discoveryKey, active: false })
    await c.ready()

    yield { discoveryKey, key: c.key, audit: await c.core.audit({ dryRun }) }

    try {
      await c.close()
    } catch {
      // ignore if failed, we are auditing...
    }
  }
}
const { EventEmitter } = require('events')
const { Readable } = require('streamx')

class GroupNotifyHandle extends EventEmitter {
  constructor(store, topic) {
    super()
    this._store = store
    this._topic = topic
    this.index = -1
  }

  updates(opts) {
    return Readable.deferred(async () => {
      const groupId = await this._store.storage.getGroup(this._topic).catch(noop)
      if (groupId === null) return null
      return this._store.storage.createGroupUpdateStream(groupId, opts)
    })
  }

  destroy() {
    this._store._removeGroupNotify(this)
  }
}

module.exports = GroupNotifyHandle

function noop() {}
{
  "name": "corestore",
  "version": "7.12.3",
  "description": "A Hypercore factory that simplifies managing collections of cores.",
  "main": "index.js",
  "files": [
    "index.js",
    "lib/*"
  ],
  "imports": {
    "events": {
      "bare": "bare-events",
      "default": "events"
    }
  },
  "dependencies": {
    "b4a": "^1.6.7",
    "bare-events": "^2.8.3",
    "hypercore": "^11.35.4",
    "hypercore-crypto": "^3.4.2",
    "hypercore-errors": "^1.4.0",
    "hypercore-id-encoding": "^1.3.0",
    "ready-resource": "^1.1.1",
    "sodium-universal": "^5.0.1",
    "streamx": "^2.26.0",
    "which-runtime": "^1.2.1"
  },
  "devDependencies": {
    "brittle": "^3.7.0",
    "lunte": "^1.3.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^2.0.0",
    "rache": "^1.0.0"
  },
  "scripts": {
    "format": "prettier --write .",
    "test": "npm run test:node && npm run test:bare",
    "test:bare": "brittle-bare --coverage test/all.js",
    "lint": "prettier --check . && lunte",
    "test:node": "brittle-node --coverage test/all.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/holepunchto/corestore.git"
  },
  "author": "Holepunch Inc",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/holepunchto/corestore/issues"
  },
  "homepage": "https://github.com/holepunchto/corestore"
}
module.exports = function debounce (worker, context = null) {
  debounced.running = null
  return debounced

  async function debounced () {
    if (debounced.running !== null) {
      try {
        await debounced.running
      } catch (_) {
        // ignore - do not fail on old errors
      }
    }

    // another "thread" beat us to it, just piggy pack on that one
    if (debounced.running !== null) return debounced.running

    debounced.running = worker.call(context)

    try {
      return await debounced.running
    } finally {
      debounced.running = null
    }
  }
}
{
  "name": "debounceify",
  "version": "1.1.0",
  "description": "Tiny async debouncer",
  "main": "index.js",
  "dependencies": {},
  "devDependencies": {
    "standard": "^16.0.3",
    "tape": "^5.1.1"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/mafintosh/debounceify.git"
  },
  "scripts": {
    "test": "standard && tape test.js"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/mafintosh/debounceify/issues"
  },
  "homepage": "https://github.com/mafintosh/debounceify"
}
const fs = require('fs')
const path = require('path')
const fsx = require('fs-native-extensions')
const b4a = require('b4a')
const ReadyResource = require('ready-resource')
const FDLock = require('fd-lock')

const PLATFORM = global.Bare ? global.Bare.platform : global.process.platform
const IS_WIN = PLATFORM === 'win32'
const IS_LINUX = PLATFORM === 'linux'
const MODIFIED_SLACK = 5000
const EMPTY = b4a.alloc(0)
const ATTR = IS_LINUX ? 'user.device-file' : 'device-file'

const nl = IS_WIN ? '\r\n' : '\n'

class DeviceFile extends ReadyResource {
  constructor(filename, { create = true, wait = false, lock = wait, data = {} } = {}) {
    super()

    this.filename = filename
    this.data = data
    this.lock = null

    this._create = create
    this._wait = wait
    this._lock = lock
  }

  async _open() {
    if (await verifyDeviceFile(this)) return
    if (!this._create) throwDeviceFileError('No device file present', false)
    await writeDeviceFile(this)
  }

  static async validate(filename, data) {
    const file = new DeviceFile(filename, { create: false, wait: false, data })
    try {
      await verifyDeviceFile(file)
    } catch (err) {
      if (err.fatal && err.code === 'DEVICE_FILE') return false
      throw err
    }
    return true
  }

  transfer() {
    return this.lock ? this.lock.transfer() : -1
  }

  async _close() {
    if (this.lock) await this.lock.close()
  }

  async suspend() {
    if (!this.opened) await this.ready()
    if (this.lock) await this.lock.suspend()
  }

  async resume() {
    if (!this.opened) await this.ready()
    if (this.lock) await this.lock.resume()
  }
}

module.exports = DeviceFile

async function writeDeviceFile(device) {
  let s = ''

  for (const [key, value] of Object.entries(device.data)) {
    if (value === null) continue
    s += key + '=' + value + nl
  }

  await fs.promises.mkdir(path.dirname(device.filename), { recursive: true })

  const fd = await open(device.filename, 'w')

  device.lock = device._lock ? new FDLock(fd, { wait: device._wait }) : null

  if (device.lock) {
    try {
      await device.lock.ready()
    } catch (err) {
      await device.lock.close()
      throw err
    }
  }

  const st = await fstat(fd)

  const created = Date.now()

  s += 'device/platform=' + PLATFORM + nl
  s += 'device/inode=' + st.ino + nl
  s += 'device/created=' + created + nl

  if (await setAttr(fd, ATTR, b4a.from('original'))) {
    s += 'device/attribute=original' + nl
  }

  await write(fd, b4a.from(s))

  if (!device.lock) await close(fd)
}

async function verifyDeviceFile(device) {
  let fd = 0

  try {
    fd = await open(device.filename, 'r+')
  } catch (e) {
    fd = 0
  }

  if (fd === 0) return false

  device.lock = device._lock ? new FDLock(fd, { wait: device._wait }) : null

  if (device.lock) {
    try {
      await device.lock.ready()
    } catch (err) {
      await device.lock.close()
      throw err
    }
  }

  const buf = await read(fd)
  const result = {}

  const s = b4a.toString(buf).trim().split('\n')

  let inode = 0
  let created = 0
  let attr = ''
  let platform = ''

  for (const ln of s) {
    const i = ln.indexOf('=')
    if (i === -1) continue

    const k = ln.slice(0, i).trim()
    const v = ln.slice(i + 1).trim()

    switch (k) {
      case 'device/platform':
        platform = v
        break
      case 'device/inode':
        inode = Number(v)
        break
      case 'device/created':
        created = Number(v)
        break
      case 'device/attribute':
        attr = v
        break
      default:
        result[k] = v
        break
    }
  }

  for (const [k, v] of Object.entries(device.data)) {
    if (v === null) continue
    if (result[k] === undefined) continue // allow upserts
    if (result[k] !== '' + v) {
      await teardown()
      throwDeviceFileError(`Invalid device file, ${k} has changed. Was ${result[k]}, is ${v}`, true)
    }
  }

  const st = await fstat(fd)
  const at = await getAttr(fd, ATTR)

  const sameAttr = b4a.toString(at || EMPTY) === attr
  const modified = Math.max(st.mtime.getTime(), st.birthtime.getTime())

  if (platform && platform !== PLATFORM) {
    await teardown()
    throwDeviceFileError('Invalid device file, was made on different platform', true)
  }

  if (!sameAttr) {
    await teardown()
    throwDeviceFileError('Invalid device file, was moved unsafely', true)
  }

  if (st.ino !== inode || (created && Math.abs(modified - created) >= MODIFIED_SLACK)) {
    await teardown()
    throwDeviceFileError('Invalid device file, was modified', true)
  }

  if (!device.lock) await close(fd)

  device.data = result
  return true

  async function teardown() {
    if (device.lock) await device.lock.close()
    else await close(fd)
  }
}

async function getAttr(fd, name) {
  try {
    return await fsx.getAttr(fd, name)
  } catch {
    return null
  }
}

async function setAttr(fd, name, value) {
  try {
    await fsx.setAttr(fd, name, value)
    return true
  } catch {
    return false
  }
}

function fstat(fd) {
  return new Promise((resolve, reject) => {
    fs.fstat(fd, (err, st) => {
      if (err) reject(err)
      resolve(st)
    })
  })
}

function close(fd) {
  return new Promise((resolve, reject) => {
    fs.close(fd, (err, st) => {
      if (err) reject(err)
      resolve(st)
    })
  })
}

function write(fd, buf) {
  return new Promise((resolve, reject) => {
    let offset = 0

    onwrite(null, 0)

    function onwrite(err, wrote) {
      if (err) return reject(err)
      if (offset === buf.byteLength) return resolve()
      offset += wrote
      fs.write(fd, buf, offset, buf.byteLength - offset, offset, onwrite)
    }
  })
}

function read(fd) {
  const buf = b4a.allocUnsafe(4096)

  return new Promise((resolve, reject) => {
    let offset = 0

    fs.read(fd, buf, 0, buf.byteLength, 0, onread)

    function onread(err, read) {
      if (err) return reject(err)
      if (read === 0) return resolve(buf.subarray(0, offset))
      offset += read
      fs.read(fd, buf, offset, buf.byteLength - offset, offset, onread)
    }
  })
}

function open(filename, flags) {
  return new Promise((resolve, reject) => {
    fs.open(filename, flags, (err, fd) => {
      if (err) reject(err)
      resolve(fd)
    })
  })
}

function throwDeviceFileError(message, fatal) {
  const err = new Error(message)
  err.code = 'DEVICE_FILE'
  err.fatal = fatal
  throw err
}
{
  "name": "device-file",
  "version": "2.3.1",
  "description": "Device only file",
  "main": "index.js",
  "files": [
    "index.js"
  ],
  "dependencies": {
    "b4a": "^1.6.7",
    "bare-fs": "^4.0.1",
    "bare-path": "^3.0.0",
    "fd-lock": "^2.1.0",
    "fs-native-extensions": "^1.4.0",
    "ready-resource": "^1.2.0"
  },
  "devDependencies": {
    "brittle": "^3.13.1",
    "lunte": "^1.3.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^2.0.0"
  },
  "scripts": {
    "format": "prettier --write .",
    "lint": "prettier --check . && lunte",
    "test": "npm run test:node && npm run test:bare",
    "test:node": "brittle-node --coverage test.js",
    "test:bare": "brittle-bare --coverage test.js"
  },
  "imports": {
    "fs": {
      "bare": "bare-fs",
      "default": "fs"
    },
    "path": {
      "bare": "bare-path",
      "default": "path"
    }
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/holepunchto/device-file.git"
  },
  "author": "Holepunch Inc",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/device-file/issues"
  },
  "homepage": "https://github.com/holepunchto/device-file"
}
const { EventEmitter } = require('events')
const Table = require('kademlia-routing-table')
const TOS = require('time-ordered-set')
const UDX = require('udx-native')
const sodium = require('sodium-universal')
const c = require('compact-encoding')
const NatSampler = require('nat-sampler')
const b4a = require('b4a')
const NetworkHealth = require('./lib/health')
const IO = require('./lib/io')
const Query = require('./lib/query')
const Session = require('./lib/session')
const peer = require('./lib/peer')
const { UNKNOWN_COMMAND, INVALID_TOKEN } = require('./lib/errors')
const { PING, PING_NAT, FIND_NODE, DOWN_HINT, DELAYED_PING } = require('./lib/commands')

const TMP = b4a.allocUnsafe(32)
const TICK_INTERVAL = 5000
const SLEEPING_INTERVAL = 3 * TICK_INTERVAL
const STABLE_TICKS = 240 // if nothing major bad happens in ~20mins we can consider this node stable (if nat is friendly)
const MORE_STABLE_TICKS = 3 * STABLE_TICKS
const REFRESH_TICKS = 60 // refresh every ~5min when idle
const RECENT_NODE = 12 // we've heard from a node less than 1min ago
const OLD_NODE = 360 // if an node has been around more than 30 min we consider it old

const DEFAULTS = {
  concurrency: 10,
  maxWindow: IO.DEFAULT_MAX_WINDOW,
  maxPingDelay: 10_000
}

class DHT extends EventEmitter {
  constructor(opts = {}) {
    super()

    this.bootstrapNodes = opts.bootstrap === false ? [] : (opts.bootstrap || []).map(parseNode)
    this.table = new Table(randomBytes(32))
    this.nodes = new TOS()
    this.udx = opts.udx || new UDX()
    this.io = new IO(this.table, this.udx, {
      ...opts,
      onrequest: this._onrequest.bind(this),
      onresponse: this._onresponse.bind(this),
      ontimeout: this._ontimeout.bind(this)
    })
    this.health = new NetworkHealth(this)

    this.concurrency = opts.concurrency || DEFAULTS.concurrency
    this.maxPingDelay = opts.maxPingDelay || DEFAULTS.maxPingDelay
    this.bootstrapped = false
    this.ephemeral = true
    this.firewalled = this.io.firewalled
    this.adaptive = typeof opts.ephemeral !== 'boolean' && opts.adaptive !== false
    this.destroyed = false
    this.suspended = false
    this.online = true
    this.degraded = false
    this.stats = {
      queries: { active: 0, total: 0 },
      requests: this.io.stats.requests,
      commands: {
        ping: this.io.stats.commands[PING],
        pingNat: this.io.stats.commands[PING_NAT],
        findNode: this.io.stats.commands[FIND_NODE],
        downHint: this.io.stats.commands[DOWN_HINT]
      }
    }

    this._nat = new NatSampler()
    this._quickFirewall = opts.quickFirewall !== false
    this._forcePersistent = opts.ephemeral === false
    this._repinging = 0
    this._checks = 0
    this._tick = randomOffset(100) // make sure to random offset all the network ticks
    this._refreshTicks = randomOffset(REFRESH_TICKS)
    this._stableTicks = this.adaptive ? STABLE_TICKS : 0
    this._tickInterval = setInterval(this._ontick.bind(this), TICK_INTERVAL)
    this._lastTick = Date.now()
    this._lastHost = null
    this._filterNode = opts.filterNode || opts.addNode || null // opts.addNode is deprecating, use opts.filterNode instead
    this._onrow = (row) => row.on('full', (node) => this._onfullrow(node, row))
    this._nonePersistentSamples = []
    this._bootstrapping = this._bootstrap()
    this._bootstrapping.catch(noop)
    this._sendDownHints = opts.sendDownHints !== false
    this._downHintsRateLimit =
      opts.downHintsRateLimit !== undefined ? opts.downHintsRateLimit : 10 * 5
    this._downHintsSentPerTick = 0
    this._pendingTimers = new Set()

    this.table.on('row', this._onrow)

    this.io.networkInterfaces.on('change', (interfaces) => this._onnetworkchange(interfaces))

    if (opts.nodes) {
      for (let i = opts.nodes.length - 1; i >= 0; i--) {
        this.addNode(opts.nodes[i])
      }
    }
  }

  static DEFAULTS = DEFAULTS

  static bootstrapper(port, host, opts) {
    if (!port) throw new Error('Port is required')
    if (!host) throw new Error('Host is required')
    if (host === '0.0.0.0' || host === '::') throw new Error('Invalid host')
    if (!UDX.isIPv4(host)) throw new Error('Host must be a IPv4 address')

    const dht = new this({
      port,
      ephemeral: false,
      firewalled: false,
      anyPort: false,
      bootstrap: [],
      ...opts
    })
    dht._nat.add(host, port)
    return dht
  }

  get id() {
    return this.ephemeral ? null : this.table.id
  }

  get host() {
    return this._nat.host
  }

  get port() {
    return this._nat.port
  }

  get randomized() {
    return this._nat.host !== null && this._nat.port === 0
  }

  get socket() {
    return this.firewalled ? this.io.clientSocket : this.io.serverSocket
  }

  get config() {
    return {
      concurrency: this.concurrency,
      maxWindow: this.io.congestion._maxWindow,
      randomPunchInterval: this._randomPunchInterval,
      connectionKeepAlive: this.connectionKeepAlive,
      sendDownHints: this._sendDownHints,
      downHintsRateLimit: this._downHintsRateLimit
    }
  }

  onmessage(socket, buf, rinfo) {
    if (buf.byteLength > 1) this.io.onmessage(socket, buf, rinfo)
  }

  bind() {
    return this.io.bind()
  }

  async suspend({ log = noop } = {}) {
    log('Suspending waiting for io bind...')
    await this.io.bind()
    log('Done, continuing')
    if (this.suspended || this.destroyed) return
    this.suspended = true
    clearInterval(this._tickInterval)
    log('Done, suspending io')
    await this.io.suspend({ log })
    log('Done, dht suspended')
    this.emit('suspend')
  }

  async resume({ log = noop } = {}) {
    if (!this.suspended || this.destroyed) return
    this.suspended = false
    this._tickInterval = setInterval(this._ontick.bind(this), TICK_INTERVAL)
    this._onwakeup()
    log('Resuming io')
    await this.io.resume()
    log('Done, dht resumed')
    this.io.networkInterfaces.on('change', (interfaces) => this._onnetworkchange(interfaces))
    this.refresh()
    this.emit('resume')
  }

  address() {
    const socket = this.socket
    return socket ? socket.address() : null
  }

  localAddress() {
    if (!this.io.serverSocket) return null

    return {
      host: localIP(this.udx),
      port: this.io.serverSocket.address().port
    }
  }

  remoteAddress() {
    if (!this.host) return null
    if (!this.port) return null
    if (this.firewalled) return null
    if (!this.io.serverSocket) return null

    const port = this.io.serverSocket.address().port
    if (port !== this.port) return null

    return {
      host: this.host,
      port
    }
  }

  addNode({ host, port }) {
    this._addNode({
      id: peer.id(host, port),
      port,
      host,
      token: null,
      to: null,
      sampled: 0,
      added: this._tick,
      pinged: 0,
      seen: 0,
      downHints: 0,
      prev: null,
      next: null
    })
  }

  toArray(opts) {
    const limit = opts && opts.limit
    if (limit === 0) return []
    return this.nodes.toArray({ limit, reverse: true }).map(({ host, port }) => ({ host, port }))
  }

  async fullyBootstrapped() {
    return this._bootstrapping
  }

  ready() {
    // Deprecating, use fullyBootstrapped instead (removed on next major)
    return this.fullyBootstrapped()
  }

  findNode(target, opts) {
    if (this.destroyed) throw new Error('Node destroyed')
    this._refreshTicks = REFRESH_TICKS
    return new Query(this, target, true, FIND_NODE, null, opts)
  }

  query({ target, command, value }, opts) {
    if (this.destroyed) throw new Error('Node destroyed')
    this._refreshTicks = REFRESH_TICKS
    return new Query(this, target, false, command, value || null, opts)
  }

  ping({ host, port }, opts) {
    let value = null

    if (opts && opts.size && opts.size > 0) value = b4a.alloc(opts.size)

    const req = this.io.createRequest(
      { id: null, host, port },
      null,
      true,
      PING,
      null,
      value,
      (opts && opts.session) || null,
      opts && opts.ttl
    )
    return this._requestToPromise(req, opts)
  }

  delayedPing({ host, port }, delayMs, opts) {
    if (delayMs > this.maxPingDelay) {
      throw new Error(`Delay exceeds max delay: ${this.maxPingDelay}ms`)
    }

    const value = b4a.allocUnsafe(4)
    c.uint32.encode({ start: 0, end: 4, buffer: value }, delayMs)

    const req = this.io.createRequest(
      { id: null, host, port },
      null,
      true,
      DELAYED_PING,
      null,
      value,
      (opts && opts.session) || null,
      opts && opts.ttl
    )
    // add 1 second to the timeout to account for network overhead
    req.timeout = delayMs + 1_000
    return this._requestToPromise(req, opts)
  }

  async rttStats() {
    const stats = {
      successes: 0,
      errors: 0,
      responses: {
        avgRtt: 0,
        errors: 0,
        avgCloserNodes: 0
      },
      closestReplies: {
        avgRtt: 0,
        errors: 0,
        avgCloserNodes: 0
      }
    }

    if (this.nodes.latest) {
      const q = this.findNode(this.nodes.latest.id)

      let responseCount = 0
      let closestCount = 0

      for await (const msg of q) {
        stats.responses.avgRtt += msg.rtt
        stats.responses.errors += msg.error
        stats.responses.avgCloserNodes += msg.closerNodes?.length || 0
        responseCount++
      }
      stats.responses.avgRtt /= responseCount
      stats.responses.avgCloserNodes /= responseCount

      for await (const msg of q.closestReplies) {
        stats.closestReplies.avgRtt += msg.rtt
        stats.closestReplies.errors += msg.error
        stats.closestReplies.avgCloserNodes += msg.closerNodes?.length || 0
        closestCount++
      }
      stats.closestReplies.avgRtt /= closestCount
      stats.closestReplies.avgCloserNodes /= closestCount

      stats.successes = q.successes
      stats.errors = q.errors
    }

    return stats
  }

  request({ token = null, command, target = null, value = null }, { host, port }, opts) {
    const req = this.io.createRequest(
      { id: null, host, port },
      token,
      false,
      command,
      target,
      value,
      (opts && opts.session) || null,
      opts && opts.ttl
    )
    return this._requestToPromise(req, opts)
  }

  session() {
    return new Session(this)
  }

  _requestToPromise(req, opts) {
    if (req === null) return Promise.reject(new Error('Node destroyed'))

    if (opts && opts.socket) req.socket = opts.socket
    if (opts && opts.retry === false) req.retries = 0

    return new Promise((resolve, reject) => {
      req.onresponse = resolve
      req.onerror = reject
      req.send()
    })
  }

  async _bootstrap() {
    const self = this

    await Promise.resolve() // wait a tick, so apis can be used from the outside
    await this.io.bind()

    this.emit('listening')

    // TODO: some papers describe more advanced ways of bootstrapping - we should prob look into that

    let first = this.firewalled && this._quickFirewall && !this._forcePersistent
    let testNat = false

    const onlyFirewall = !this._forcePersistent

    for (let i = 0; i < 2; i++) {
      await this._backgroundQuery(this.table.id).on('data', ondata).finished()

      if (this.bootstrapped || (!testNat && !this._forcePersistent)) break
      if (!(await this._updateNetworkState(onlyFirewall))) break
    }

    if (this.bootstrapped) return
    this.bootstrapped = true

    this.emit('ready')

    function ondata(data) {
      // Simple QUICK nat heuristic.
      // If we get ONE positive nat ping before the bootstrap query finishes
      // then we always to a nat test, no matter if we are adaptive...
      // This should be expanded in the future to try more than one node etc, not always hit the first etc
      // If this fails, then nbd, as the onstable hook will pick it up later.

      if (!first) return
      first = false

      const value = b4a.allocUnsafe(2)
      c.uint16.encode({ start: 0, end: 2, buffer: value }, self.io.serverSocket.address().port)

      self._request(
        data.from,
        false,
        true,
        PING_NAT,
        null,
        value,
        null,
        () => {
          testNat = true
        },
        noop
      )
    }
  }

  refresh() {
    const node = this.table.random()
    this._backgroundQuery(node ? node.id : this.table.id).on('error', noop)
  }

  async destroy() {
    const emitClose = !this.destroyed
    this.destroyed = true
    clearInterval(this._tickInterval)
    for (const timer of this._pendingTimers) {
      clearTimeout(timer)
    }
    await this.io.destroy()
    if (emitClose) this.emit('close')
  }

  _request(to, force, internal, command, target, value, session, onresponse, onerror) {
    if (internal && !this._sendDownHints && command === DOWN_HINT) return null
    const req = this.io.createRequest(to, null, internal, command, target, value, session)
    if (req === null) return null

    req.onresponse = onresponse
    req.onerror = onerror
    req.send(force)

    return req
  }

  _natAdd(host, port) {
    const prevHost = this._nat.host
    const prevPort = this._nat.port

    this._nat.add(host, port)

    if (prevHost === this._nat.host && prevPort === this._nat.port) return

    this.emit('nat-update', this._nat.host, this._nat.port)
  }

  // we don't check that this is a bootstrap node but we limit the sample size to very few nodes, so fine
  _sampleBootstrapMaybe(from, to) {
    if (this._nonePersistentSamples.length >= Math.max(1, this.bootstrapNodes.length)) return
    const id = from.host + ':' + from.port
    if (this._nonePersistentSamples.indexOf(id) > -1) return
    this._nonePersistentSamples.push(id)
    this._natAdd(to.host, to.port)
  }

  _addNodeFromNetwork(sample, from, to) {
    if (this._filterNode !== null && !this._filterNode(from)) {
      return
    }

    if (from.id === null) {
      this._sampleBootstrapMaybe(from, to)
      return
    }

    const oldNode = this.table.get(from.id)

    // refresh it, if we've seen this before
    if (oldNode) {
      if (sample && (oldNode.sampled === 0 || this._tick - oldNode.sampled >= OLD_NODE)) {
        oldNode.to = to
        oldNode.sampled = this._tick
        this._natAdd(to.host, to.port)
      }

      oldNode.pinged = oldNode.seen = this._tick
      this.nodes.add(oldNode)
      return
    }

    this._addNode({
      id: from.id,
      port: from.port,
      host: from.host,
      to,
      sampled: 0,
      added: this._tick,
      pinged: this._tick, // last time we interacted with them
      seen: this._tick, // last time we heard from them
      downHints: 0,
      prev: null,
      next: null
    })
  }

  _addNode(node) {
    if (this.nodes.has(node) || b4a.equals(node.id, this.table.id)) return

    node.added = node.pinged = node.seen = this._tick

    if (!this.table.add(node)) return
    this.nodes.add(node)

    if (node.to && node.sampled === 0) {
      node.sampled = this._tick
      this._natAdd(node.to.host, node.to.port)
    }

    this.emit('add-node', node)
  }

  _removeStaleNode(node, lastSeen) {
    if (node.seen <= lastSeen) this._removeNode(node)
  }

  _removeNode(node) {
    if (!this.nodes.has(node)) return

    this.table.remove(node.id)
    this.nodes.remove(node)

    this.emit('remove-node', node)
  }

  _onwakeup() {
    this._tick += 2 * OLD_NODE // bump the tick enough that everything appears old.
    this._tick += 8 - (this._tick & 7) - 2 // triggers a series of pings in two ticks
    this._stableTicks = MORE_STABLE_TICKS
    this._refreshTicks = 1 // triggers a refresh next tick (allow network time to wake up also)
    this._lastHost = null // clear network cache check
    this.health.reset()

    if (this.adaptive) {
      // TODO: re-enable this as soon as we find out why this is over triggering in some edge cases
      // this.firewalled = true
      // this.io.firewalled = true

      if (!this.ephemeral) {
        this.ephemeral = true
        this.io.ephemeral = true
        this.emit('ephemeral')
      }
    }

    this.emit('wakeup')
  }

  _onfullrow(newNode, row) {
    if (!this.bootstrapped || this._repinging >= 3) return

    let oldest = null
    for (const node of row.nodes) {
      if (node.pinged === this._tick) continue
      if (
        oldest === null ||
        oldest.pinged > node.pinged ||
        (oldest.pinged === node.pinged && oldest.added > node.added)
      ) {
        oldest = node
      }
    }

    if (oldest === null) return
    if (this._tick - oldest.pinged < RECENT_NODE && this._tick - oldest.added > OLD_NODE) return

    this._repingAndSwap(newNode, oldest)
  }

  _onnetworkchange(interfaces) {
    this.emit('network-change', interfaces)
    this.emit('network-update')
  }

  _repingAndSwap(newNode, oldNode) {
    const self = this
    const lastSeen = oldNode.seen

    oldNode.pinged = this._tick

    this._repinging++
    this._request(
      { id: null, host: oldNode.host, port: oldNode.port },
      false,
      true,
      PING,
      null,
      null,
      null,
      onsuccess,
      onswap
    )

    function onsuccess(m) {
      if (oldNode.seen <= lastSeen) return onswap()
      self._repinging--
    }

    function onswap(e) {
      self._repinging--
      self._removeNode(oldNode)
      self._addNode(newNode)
    }
  }

  _onrequest(req, external) {
    if (req.from.id !== null) {
      this._addNodeFromNetwork(!external, req.from, req.to)
    }

    if (req.internal) {
      switch (req.command) {
        // standard keep alive call
        case PING: {
          req.sendReply(0, null, false, false)
          return
        }
        case DELAYED_PING: {
          this._ondelayedping(req)
          return
        }
        // check if the other side can receive a message to their other socket
        case PING_NAT: {
          if (req.value === null || req.value.byteLength < 2) return
          const port = c.uint16.decode({ start: 0, end: 2, buffer: req.value })
          if (port === 0) return
          req.from.port = port
          req.sendReply(0, null, false, false)
          return
        }
        // empty dht reply back
        case FIND_NODE: {
          if (!req.target) return
          req.sendReply(0, null, false, true)
          return
        }
        // "this is node you sent me is down" - let's try to ping it
        case DOWN_HINT: {
          if (req.value === null || req.value.byteLength < 6) return
          if (this._checks < 10) {
            sodium.crypto_generichash(TMP, req.value.subarray(0, 6))
            const node = this.table.get(TMP)
            if (node && (node.pinged < this._tick || node.downHints === 0)) {
              node.downHints++
              this._check(node)
            }
          }
          req.sendReply(0, null, false, false)
          return
        }
      }

      req.sendReply(UNKNOWN_COMMAND, null, false, req.target !== null)
      return
    }

    // ask the user to handle it or reply back with a bad command
    if (this.onrequest(req) === false) {
      req.sendReply(UNKNOWN_COMMAND, null, false, req.target !== null)
    }
  }

  onrequest(req) {
    return this.emit('request', req)
  }

  _ondelayedping(req) {
    if (req.value === null || req.value.byteLength < 4) return
    const delayMs = c.uint32.decode({ start: 0, end: 4, buffer: req.value })
    if (delayMs > this.maxPingDelay) return
    const timer = setTimeout(() => {
      if (this.destroyed) return
      this._pendingTimers.delete(timer)
      req.sendReply(0, null, false, false)
    }, delayMs)
    this._pendingTimers.add(timer)
  }

  _onresponse(res, external) {
    this._addNodeFromNetwork(!external, res.from, res.to)
  }

  _ontimeout(req) {
    if (!req.to.id) return
    const node = this.table.get(req.to.id)
    if (node) this._removeNode(node)
  }

  _pingSome() {
    let cnt = this.io.inflight.length > 2 ? 3 : 5
    let oldest = this.nodes.oldest

    // tiny dht, pinged the bootstrap again
    if (!oldest) {
      this.refresh()
      return
    }

    // we've recently pinged the oldest one, so only trigger a couple of repings
    if (this._tick - oldest.pinged < RECENT_NODE) {
      cnt = 2
    }

    while (cnt--) {
      if (!oldest || this._tick === oldest.pinged) continue
      this._check(oldest)
      oldest = oldest.next
    }
  }

  _check(node) {
    node.pinged = this._tick

    const lastSeen = node.seen
    const onresponse = () => {
      this._checks--
      this._removeStaleNode(node, lastSeen)
    }
    const onerror = () => {
      this._checks--
      this._removeNode(node)
    }

    this._checks++
    this._request(
      { id: null, host: node.host, port: node.port },
      false,
      true,
      PING,
      null,
      null,
      null,
      onresponse,
      onerror
    )
  }

  _ontick() {
    const time = Date.now()

    if (time - this._lastTick > SLEEPING_INTERVAL && this.suspended === false) {
      this._onwakeup()
    } else {
      this._tick++
    }

    this._lastTick = time

    if (!this.bootstrapped || this.suspended) return

    if (this.adaptive && this.ephemeral && --this._stableTicks <= 0) {
      if (this._lastHost === this._nat.host) {
        // do not recheck the same network...
        this._stableTicks = MORE_STABLE_TICKS
      } else {
        this._updateNetworkState() // the promise returned here never fails so just ignore it
      }
    }

    if ((this._tick & 7) === 0) {
      this._pingSome()
    }

    if (
      ((this._tick & 63) === 0 && this.nodes.length < this.table.k) ||
      --this._refreshTicks <= 0
    ) {
      this.refresh()
    }

    this._downHintsSentPerTick = 0
    this.health.update()
  }

  async _updateNetworkState(onlyFirewall = false) {
    if (!this.ephemeral) return false
    if (onlyFirewall && !this.firewalled) return false

    const { host, port } = this._nat

    if (!onlyFirewall) {
      // remember what host we checked and reset the counter
      this._stableTicks = MORE_STABLE_TICKS
      this._lastHost = host
    }

    // check if we have a consistent host and port
    if (host === null || port === 0) {
      return false
    }

    const natSampler = this.firewalled ? new NatSampler() : this._nat

    // ask remote nodes to ping us on our server socket to see if we have the port open
    const firewalled = this.firewalled && (await this._checkIfFirewalled(natSampler))
    if (firewalled) return false

    this.firewalled = this.io.firewalled = false

    // incase it's called in parallel for some reason, or if our nat status somehow changed
    if (!this.ephemeral || host !== this._nat.host || port !== this._nat.port) return false
    // if the firewall probe returned a different host / non consistent port, bail as well
    if (natSampler.host !== host || natSampler.port === 0) return false

    const id = peer.id(natSampler.host, natSampler.port)

    if (!onlyFirewall) {
      this.ephemeral = this.io.ephemeral = false
    }

    if (natSampler !== this._nat) {
      const prevHost = this._nat.host
      const prevPort = this._nat.port

      this._nonePersistentSamples = []
      this._nat = natSampler

      if (prevHost !== this._nat.host || prevPort !== this._nat.port) {
        this.emit('nat-update', this._nat.host, this._nat.port)
      }
    }

    // TODO: we should make this a bit more defensive in terms of using more
    // resources to make sure that the new routing table contains as many alive nodes
    // as possible, vs blindly copying them over...

    // all good! copy over the old routing table to the new one
    if (!b4a.equals(this.table.id, id)) {
      const nodes = this.table.toArray()

      this.table = this.io.table = new Table(id)

      for (const node of nodes) {
        if (b4a.equals(node.id, id)) continue
        if (!this.table.add(node)) this.nodes.remove(node)
      }

      this.table.on('row', this._onrow)

      // we need to rebootstrap/refresh since we updated our id
      if (this.bootstrapped) this.refresh()
    }

    if (!this.ephemeral) {
      this.emit('persistent')
    }

    return true
  }

  async *_resolveBootstrapNodes() {
    for (let { host, port } of this.bootstrapNodes) {
      let doLookup = false

      if (host.indexOf('@') === -1) {
        doLookup = true
      } else {
        const [suggestedIP, fallbackHost] = host.split('@')
        try {
          await this.ping({ host: suggestedIP, port })
          host = suggestedIP
        } catch {
          host = fallbackHost
          doLookup = true
        }
      }

      if (doLookup) {
        try {
          host = UDX.isIPv4(host) ? host : (await this.udx.lookup(host, { family: 4 })).host
        } catch {
          continue
        }
      }

      yield {
        id: peer.id(host, port),
        host,
        port
      }
    }
  }

  async _addBootstrapNodes(nodes) {
    for await (const node of this._resolveBootstrapNodes()) {
      nodes.push(node)
    }
  }

  async _checkIfFirewalled(natSampler = new NatSampler()) {
    const nodes = []
    for (let node = this.nodes.latest; node && nodes.length < 5; node = node.prev) {
      nodes.push(node)
    }

    if (nodes.length < 5) await this._addBootstrapNodes(nodes)
    // if no nodes are available, including bootstrappers - bail
    if (nodes.length === 0) return true

    const hosts = new Set()
    const value = b4a.allocUnsafe(2)

    c.uint16.encode({ start: 0, end: 2, buffer: value }, this.io.serverSocket.address().port)

    // double check they actually came on the server socket...
    this.io.serverSocket.on('message', onmessage)

    const pongs = await requestAll(this, true, PING_NAT, value, nodes)

    let count = 0
    for (const res of pongs) {
      if (hosts.has(res.from.host)) {
        count++
        natSampler.add(res.to.host, res.to.port)
      }
    }

    this.io.serverSocket.removeListener('message', onmessage)

    // if we got no or very few replies, consider it a fluke
    if (count < (nodes.length >= 5 ? 3 : 1)) return true

    // check that the server socket has the same ip as the client socket
    if (natSampler.host === null || this._nat.host !== natSampler.host) return true

    // check that the local port of the server socket is the same as the remote port
    // TODO: we might want a flag to opt out of this heuristic for specific remapped port servers
    if (natSampler.port === 0 || natSampler.port !== this.io.serverSocket.address().port) {
      return true
    }

    return false

    function onmessage(_, { host }) {
      hosts.add(host)
    }
  }

  _backgroundQuery(target) {
    this._refreshTicks = REFRESH_TICKS

    const backgroundCon = Math.min(this.concurrency, Math.max(2, (this.concurrency / 8) | 0))
    const q = new Query(this, target, true, FIND_NODE, null, {
      concurrency: backgroundCon
    })

    q.on('data', () => {
      // yield to other traffic
      q.concurrency = this.io.inflight.length < 3 ? this.concurrency : backgroundCon
    })

    return q
  }

  // called by health
  _online() {
    if (this.online && !this.degraded) return
    this.online = true
    this.degraded = false
    this.emit('network-update')
  }

  // called by health
  _degraded() {
    if (this.degraded) return
    this.online = true
    this.degraded = true
    this.emit('network-update')
  }

  // called by health
  _offline() {
    if (!this.online) return
    this.online = false
    this.degraded = false
    this.emit('network-update')
  }
}

DHT.OK = 0
DHT.ERROR_UNKNOWN_COMMAND = UNKNOWN_COMMAND
DHT.ERROR_INVALID_TOKEN = INVALID_TOKEN

module.exports = DHT

function localIP(udx, family = 4) {
  let host = null

  for (const n of udx.networkInterfaces()) {
    if (n.family !== family || n.internal) continue

    // mac really likes en0, mb a better way but this shouldnt be bad anywhere so return now
    if (n.name === 'en0') return n.host

    // otherwise pick the first non internal host (let the loop continue in case we see en0)
    if (host === null) host = n.host
  }

  return host || (family === 4 ? '127.0.0.1' : '::1')
}

function parseNode(s) {
  if (typeof s === 'object') return s
  if (typeof s === 'number') return { host: '127.0.0.1', port: s }
  const [host, port] = s.split(':')
  if (!port) throw new Error('Bootstrap node format is host:port')

  return {
    host,
    port: Number(port)
  }
}

function randomBytes(n) {
  const b = b4a.alloc(n)
  sodium.randombytes_buf(b)
  return b
}

function randomOffset(n) {
  return n - ((Math.random() * 0.5 * n) | 0)
}

function requestAll(dht, internal, command, value, nodes) {
  let missing = nodes.length
  const replies = []

  return new Promise((resolve) => {
    for (const node of nodes) {
      const req = dht._request(
        node,
        false,
        internal,
        command,
        null,
        value,
        null,
        onsuccess,
        onerror
      )
      if (!req) return resolve(replies)
    }

    function onsuccess(res) {
      replies.push(res)
      if (--missing === 0) resolve(replies)
    }

    function onerror() {
      if (--missing === 0) resolve(replies)
    }
  })
}

function noop() {}
exports.PING = 0
exports.PING_NAT = 1
exports.FIND_NODE = 2
exports.DOWN_HINT = 3
exports.DELAYED_PING = 4
module.exports = class DHTError extends Error {
  constructor(msg, code, fn = DHTError) {
    super(`${code}: ${msg}`)
    this.code = code

    if (Error.captureStackTrace) {
      Error.captureStackTrace(this, fn)
    }
  }

  get name() {
    return 'DHTError'
  }

  static UNKNOWN_COMMAND = 1
  static INVALID_TOKEN = 2

  static REQUEST_TIMEOUT(msg = 'Request timed out') {
    return new DHTError(msg, 'REQUEST_TIMEOUT', DHTError.REQUEST_TIMEOUT)
  }

  static REQUEST_DESTROYED(msg = 'Request destroyed') {
    return new DHTError(msg, 'REQUEST_DESTROYED', DHTError.REQUEST_DESTROYED)
  }

  static IO_SUSPENDED(msg = 'I/O suspended') {
    return new DHTError(msg, 'IO_SUSPENDED', DHTError.IO_SUSPENDED)
  }
}
const MAX_HEALTH_WINDOW = 4
const OFFLINE_THRESHOLD = 2
const IDLE_THRESHOLD = 4
const DEGRADED_TIMEOUT_RATE_THRESHOLD = 0.5

module.exports = class NetworkHealth {
  constructor(dht) {
    this._dht = dht
    this._window = []
    this._head = -1
    this._offlineTicks = 0
    this._degradedTicks = 0
    this._healthyTicks = 0
    this.online = true
    this.degraded = false
  }

  get oldest() {
    return this._window[this._tail]
  }

  get previous() {
    return this._window[(this._head - 1 + MAX_HEALTH_WINDOW) % MAX_HEALTH_WINDOW]
  }

  get newest() {
    return this._window[this._head]
  }

  get responses() {
    if (!this.newest || !this.previous) return 0
    return this.newest.responses - this.previous.responses
  }

  get timeouts() {
    if (!this.newest || !this.previous) return 0
    return this.newest.timeouts - this.previous.timeouts
  }

  get timeoutsRate() {
    if (this.timeouts === 0) return 0
    return this.timeouts / (this.responses + this.timeouts)
  }

  get cold() {
    return this._window.length < MAX_HEALTH_WINDOW
  }

  get idle() {
    return this.responses + this.timeouts < IDLE_THRESHOLD
  }

  get stats() {
    return {
      online: this.online,
      degraded: this.degraded,
      cold: this.cold,
      idle: this.idle,
      responses: this.responses,
      timeouts: this.timeouts,
      timeoutsRate: this.timeoutsRate
    }
  }

  get _tail() {
    return (this._head + 1) % MAX_HEALTH_WINDOW
  }

  reset() {
    this._window = []
    this._head = -1
    this._offlineTicks = 0
    this._degradedTicks = 0
    this._healthyTicks = 0
    this.online = true
    this.degraded = false
    this._dht._online()
  }

  update() {
    // update counters before dropping oldest
    if (this.oldest?.degraded) this._degradedTicks--
    else if (this.oldest?.degraded === false) this._healthyTicks--

    // move window
    this._head = this._tail
    this._window[this._head] = {
      responses: this._dht.stats.requests.responses,
      timeouts: this._dht.stats.requests.timeouts
    }

    // skip state changes
    if (this.cold || this.idle) return

    // update newest
    this.newest.offline = this.responses === 0
    this.newest.degraded =
      !this.newest.offline && this.timeoutsRate > DEGRADED_TIMEOUT_RATE_THRESHOLD

    // update counters
    if (this.newest.offline) this._offlineTicks++
    else {
      this._offlineTicks = 0
      if (this.newest.degraded) this._degradedTicks++
      else this._healthyTicks++
    }

    // check online/offline
    if (!this.newest.offline) this.online = true
    else if (this._offlineTicks >= OFFLINE_THRESHOLD) this.online = false

    // check degraded
    if (!this.online || this._healthyTicks === MAX_HEALTH_WINDOW) this.degraded = false
    else if (this._degradedTicks === MAX_HEALTH_WINDOW) this.degraded = true

    // fire events
    if (this.online && !this.degraded) this._dht._online()
    else if (this.degraded) this._dht._degraded()
    else this._dht._offline()
  }
}
const FIFO = require('fast-fifo')
const sodium = require('sodium-universal')
const c = require('compact-encoding')
const b4a = require('b4a')
const AdaptiveTimeout = require('adaptive-timeout')
const peer = require('./peer')
const { INVALID_TOKEN, REQUEST_TIMEOUT, REQUEST_DESTROYED, IO_SUSPENDED } = require('./errors')

const VERSION = 0b11
const RESPONSE_ID = (0b0001 << 4) | VERSION
const REQUEST_ID = (0b0000 << 4) | VERSION
const EMPTY_ARRAY = []
const MAX_WINDOW = 80

module.exports = class IO {
  constructor(
    table,
    udx,
    {
      maxWindow = MAX_WINDOW,
      port = 0,
      host = '0.0.0.0',
      anyPort = true,
      firewalled = true,
      onrequest,
      onresponse = noop,
      ontimeout = noop,
      adaptiveTimeout
    } = {}
  ) {
    this.table = table
    this.udx = udx
    this.inflight = []
    this.clientSocket = null
    this.serverSocket = null
    this.firewalled = firewalled !== false
    this.ephemeral = true
    this.congestion = new CongestionWindow(maxWindow)
    this.networkInterfaces = udx.watchNetworkInterfaces()
    this.suspended = false

    this.stats = {
      requests: {
        active: 0,
        total: 0,
        responses: 0,
        timeouts: 0,
        retries: 0
      },
      commands: [
        { tx: 0, rx: 0 }, // tx = transmitted, rx = received
        { tx: 0, rx: 0 },
        { tx: 0, rx: 0 },
        { tx: 0, rx: 0 }
      ]
    }

    this.onrequest = onrequest
    this.onresponse = onresponse
    this.ontimeout = ontimeout

    this._pending = new FIFO()
    this._rotateSecrets = 10
    this._tid = (Math.random() * 65536) | 0
    this._secrets = null
    this._drainInterval = null
    this._destroying = null
    this._binding = null

    // port can be a number or a range [start, to]
    this.portRange = port.length ? port : port === 0 ? [0, 0] : [port, port + 5]

    this._host = host
    this._anyPort = anyPort !== false
    this._boundServerPort = 0
    this._boundClientPort = 0

    this._adt = adaptiveTimeout ? new AdaptiveTimeout(adaptiveTimeout) : null
  }

  static DEFAULT_MAX_WINDOW = MAX_WINDOW

  onmessage(socket, buffer, { host, port }) {
    if (buffer.byteLength < 2 || !(port > 0 && port < 65536) || this.suspended === true) return

    const from = { id: null, host, port }
    const state = { start: 1, end: buffer.byteLength, buffer }
    const expectedSocket = this.firewalled ? this.clientSocket : this.serverSocket
    const external = socket !== expectedSocket

    if (buffer[0] === REQUEST_ID) {
      const req = Request.decode(this, socket, from, state)
      if (req === null) return
      if (
        req.token !== null &&
        !b4a.equals(req.token, this.token(req.from, 1)) &&
        !b4a.equals(req.token, this.token(req.from, 0))
      ) {
        req.error(INVALID_TOKEN, { token: true })
        return
      }
      this.onrequest(req, external)
      return
    }

    if (buffer[0] === RESPONSE_ID) {
      const res = decodeReply(from, state)
      if (res === null) return

      for (let i = 0; i < this.inflight.length; i++) {
        const req = this.inflight[i]
        if (req.tid !== res.tid) continue

        res.rtt = Date.now() - req._timestamp

        if (this._adt && req.sent <= 2) {
          this._adt.put(`${req.to.host}:${req.to.port}`, res.rtt)
        }

        if (i === this.inflight.length - 1) this.inflight.pop()
        else this.inflight[i] = this.inflight.pop()

        if (req.session) req.session._detach(req)

        // TODO: Auto retry here if errors.INVALID_TOKEN is returned?

        if (req._timeout) {
          clearTimeout(req._timeout)
          req._timeout = null
        }

        this.congestion.recv()

        if (req.internal && req.command < this.stats.commands.length) {
          this.stats.commands[req.command].rx++
        }

        this.stats.requests.active--
        this.stats.requests.responses++

        this.onresponse(res, external)
        req.onresponse(res, req)
        break
      }
    }
  }

  token(addr, i) {
    if (this._secrets === null) {
      const buf = b4a.alloc(64)
      this._secrets = [buf.subarray(0, 32), buf.subarray(32, 64)]
      sodium.randombytes_buf(this._secrets[0])
      sodium.randombytes_buf(this._secrets[1])
    }

    const token = b4a.allocUnsafe(32)
    sodium.crypto_generichash(token, b4a.from(addr.host), this._secrets[i])
    return token
  }

  async destroy() {
    if (this._destroying) return this._destroying
    this._destroying = this._destroy()
    return this._destroying
  }

  async _destroy() {
    // simplifies timing to await the bind here also, although it might be unneeded
    await this.bind()
    await this._clear(false)
  }

  async _clear(suspended) {
    if (this._drainInterval) {
      clearInterval(this._drainInterval)
      this._drainInterval = null
    }

    while (this.inflight.length) {
      const req = this.inflight.pop()
      if (req._timeout) clearTimeout(req._timeout)
      req._timeout = null
      req.destroyed = true

      if (req.session) req.session._detach(req)

      this.congestion.recv()
      this.stats.requests.active--

      req.onerror(suspended ? IO_SUSPENDED() : REQUEST_DESTROYED(), req)
    }

    await Promise.allSettled([this.serverSocket.close(), this.clientSocket.close()])

    this.networkInterfaces.destroy()
  }

  async suspend() {
    this.suspended = true
    await this._clear(true)

    this.congestion.clear()

    if (this._drainInterval) {
      clearInterval(this._drainInterval)
      this._drainInterval = null
    }
  }

  async _rebind(binding) {
    if (binding) await binding
    if (this._destroying) return this._destroying
    await this._bindSockets()
    this.networkInterfaces = this.udx.watchNetworkInterfaces()
  }

  resume() {
    this.suspended = false
    const binding = this._binding
    this._binding = this._rebind(binding)
    return this._binding
  }

  bind() {
    if (this._binding) return this._binding
    this._binding = this._bindSockets()
    return this._binding
  }

  async _bindSockets() {
    const serverSocket = this.udx.createSocket()

    const candidatePorts = []

    // Retrying previous port always has precedence
    if (this._boundServerPort) candidatePorts.push(this._boundServerPort)

    for (let i = this.portRange[0]; i < this.portRange[1]; i++) candidatePorts.push(i)

    for (const port of candidatePorts) {
      if (serverSocket.bound) break

      try {
        serverSocket.bind(port, this._host)
      } catch (err) {
        if (!this._anyPort) {
          await serverSocket.close()
          throw err
        }
      }
    }

    if (!serverSocket.bound) {
      try {
        serverSocket.bind(0, this._host)
      } catch (err) {
        await serverSocket.close()
        throw err
      }
    }

    const clientSocket = this.udx.createSocket()

    try {
      clientSocket.bind(this._boundClientPort || 0, this._host)
    } catch {
      try {
        clientSocket.bind(0, this._host)
      } catch (err) {
        await serverSocket.close()
        await clientSocket.close()
        throw err
      }
    }

    this._boundServerPort = serverSocket.address().port
    this._boundClientPort = clientSocket.address().port

    this.clientSocket = clientSocket
    this.serverSocket = serverSocket

    this.serverSocket.on('message', this.onmessage.bind(this, this.serverSocket))
    this.clientSocket.on('message', this.onmessage.bind(this, this.clientSocket))

    if (this._drainInterval === null) {
      this._drainInterval = setInterval(this._drain.bind(this), 750)
      if (this._drainInterval.unref) this._drainInterval.unref()
    }

    for (const req of this.inflight) {
      if (!req.socket) req.socket = this.firewalled ? this.clientSocket : this.serverSocket
      req.sent = 0
      req.send(false)
    }
  }

  _drain() {
    if (this._secrets !== null && --this._rotateSecrets === 0) {
      this._rotateSecrets = 10
      const tmp = this._secrets[0]
      this._secrets[0] = this._secrets[1]
      this._secrets[1] = tmp
      sodium.crypto_generichash(tmp, tmp)
    }

    this.congestion.drain()

    while (!this.congestion.isFull()) {
      const p = this._pending.shift()
      if (p === undefined) return
      p._sendNow()
    }
  }

  createRequest(to, token, internal, command, target, value, session, ttl) {
    if (this._destroying !== null) return null

    if (this._tid === 65536) this._tid = 0

    const tid = this._tid++
    const socket = this.firewalled ? this.clientSocket : this.serverSocket

    const req = new Request(
      this,
      socket,
      tid,
      null,
      to,
      token,
      internal,
      command,
      target,
      value,
      session,
      ttl || 0
    )
    this.inflight.push(req)
    if (session) session._attach(req)

    if (internal && command < this.stats.commands.length) {
      this.stats.commands[command].tx++
    }

    this.stats.requests.active++
    this.stats.requests.total++

    return req
  }
}

class Request {
  constructor(io, socket, tid, from, to, token, internal, command, target, value, session, ttl) {
    this.socket = socket
    this.tid = tid
    this.from = from
    this.to = to
    this.token = token
    this.command = command
    this.target = target
    this.value = value
    this.internal = internal
    this.session = session
    this.ttl = ttl
    this.index = -1
    this.sent = 0
    this.retries = 3
    this.destroyed = false
    this.timeout = 0

    this.oncycle = noop
    this.onerror = noop
    this.onresponse = noop

    this._buffer = null
    this._io = io
    this._timeout = null
    this._timestamp = Date.now()
  }

  static decode(io, socket, from, state) {
    try {
      const flags = c.uint.decode(state)
      const tid = c.uint16.decode(state)
      const to = peer.ipv4.decode(state)
      const id = flags & 1 ? c.fixed32.decode(state) : null
      const token = flags & 2 ? c.fixed32.decode(state) : null
      const internal = (flags & 4) !== 0
      const command = c.uint.decode(state)
      const target = flags & 8 ? c.fixed32.decode(state) : null
      const value = flags & 16 ? c.buffer.decode(state) : null

      if (id !== null) from.id = validateId(id, from)

      return new Request(
        io,
        socket,
        tid,
        from,
        to,
        token,
        internal,
        command,
        target,
        value,
        null,
        0
      )
    } catch {
      return null
    }
  }

  reply(value, opts = {}) {
    const socket = opts.socket || this.socket
    const to = opts.to || this.from
    this._sendReply(0, value || null, opts.token !== false, opts.closerNodes !== false, to, socket)
  }

  error(code, opts = {}) {
    const socket = opts.socket || this.socket
    const to = opts.to || this.from
    this._sendReply(code, null, opts.token === true, opts.closerNodes !== false, to, socket)
  }

  relay(value, to, opts) {
    const socket = (opts && opts.socket) || this.socket
    const buffer = this._encodeRequest(null, value, to, socket)
    socket.trySend(buffer, to.port, to.host, this.ttl)
  }

  send(force = false) {
    if (this.destroyed) return

    if (this.socket === null) return
    if (this._buffer === null) {
      this._buffer = this._encodeRequest(this.token, this.value, this.to, this.socket)
    }

    if (!force && this._io.congestion.isFull()) {
      this._io._pending.push(this)
      return
    }

    this._sendNow()
  }

  sendReply(error, value, token, hasCloserNodes) {
    this._sendReply(error, value, token, hasCloserNodes, this.from, this.socket, null)
  }

  _sendNow() {
    if (this.destroyed) return
    this.sent++
    this._io.congestion.send()
    this.socket.trySend(this._buffer, this.to.port, this.to.host, this.ttl)
    if (this._timeout) clearTimeout(this._timeout)
    const value =
      this.timeout || this._io._adt?.get(`${this.to.host}:${this.to.port}`, this.sent) || 1000
    this._timeout = setTimeout(oncycle, value, this)
  }

  destroy(err) {
    if (this.destroyed) return
    this.destroyed = true

    if (this._timeout) {
      clearTimeout(this._timeout)
      this._timeout = null
    }

    const i = this._io.inflight.indexOf(this)
    if (i === -1) return

    if (i === this._io.inflight.length - 1) this._io.inflight.pop()
    else this._io.inflight[i] = this._io.inflight.pop()

    if (this.session) this.session._detach(this)

    this._io.stats.requests.active--
    this._io.congestion.recv()

    this.onerror(err || REQUEST_DESTROYED(), this)
  }

  _sendReply(error, value, token, hasCloserNodes, from, socket) {
    if (socket === null || this.destroyed) return

    const id = this._io.ephemeral === false && socket === this._io.serverSocket
    const closerNodes =
      this.target !== null && hasCloserNodes ? this._io.table.closest(this.target) : EMPTY_ARRAY
    const state = { start: 0, end: 1 + 1 + 6 + 2, buffer: null } // (type | version) + flags + to + tid

    if (id) state.end += 32
    if (token) state.end += 32
    if (closerNodes.length > 0) peer.ipv4Array.preencode(state, closerNodes)
    if (error > 0) c.uint.preencode(state, error)
    if (value) c.buffer.preencode(state, value)

    state.buffer = b4a.allocUnsafe(state.end)
    state.buffer[state.start++] = RESPONSE_ID
    state.buffer[state.start++] =
      (id ? 1 : 0) |
      (token ? 2 : 0) |
      (closerNodes.length > 0 ? 4 : 0) |
      (error > 0 ? 8 : 0) |
      (value ? 16 : 0)

    c.uint16.encode(state, this.tid)
    peer.ipv4.encode(state, from)

    if (id) c.fixed32.encode(state, this._io.table.id)
    if (token) c.fixed32.encode(state, this._io.token(from, 1))
    if (closerNodes.length > 0) peer.ipv4Array.encode(state, closerNodes)
    if (error > 0) c.uint.encode(state, error)
    if (value) c.buffer.encode(state, value)

    socket.trySend(state.buffer, from.port, from.host, this.ttl)
  }

  _encodeRequest(token, value, to, socket) {
    const id = this._io.ephemeral === false && socket === this._io.serverSocket
    const state = { start: 0, end: 1 + 1 + 6 + 2, buffer: null } // (type | version) + flags + to + tid

    if (id) state.end += 32
    if (token) state.end += 32

    c.uint.preencode(state, this.command)

    if (this.target) state.end += 32
    if (value) c.buffer.preencode(state, value)

    state.buffer = b4a.allocUnsafe(state.end)
    state.buffer[state.start++] = REQUEST_ID
    state.buffer[state.start++] =
      (id ? 1 : 0) |
      (token ? 2 : 0) |
      (this.internal ? 4 : 0) |
      (this.target ? 8 : 0) |
      (value ? 16 : 0)

    c.uint16.encode(state, this.tid)
    peer.ipv4.encode(state, to)

    if (id) c.fixed32.encode(state, this._io.table.id)
    if (token) c.fixed32.encode(state, token)

    c.uint.encode(state, this.command)

    if (this.target) c.fixed32.encode(state, this.target)
    if (value) c.buffer.encode(state, value)

    return state.buffer
  }
}

class CongestionWindow {
  constructor(maxWindow) {
    this._i = 0
    this._total = 0
    this._window = [0, 0, 0, 0]
    this._maxWindow = maxWindow
  }

  clear() {
    this._i = 0
    this._total = 0
    this._window = [0, 0, 0, 0]
  }

  isFull() {
    return this._total >= 2 * this._maxWindow || this._window[this._i] >= this._maxWindow
  }

  recv() {
    if (this._window[this._i] > 0) {
      this._window[this._i]--
      this._total--
    }
  }

  send() {
    this._total++
    this._window[this._i]++
  }

  drain() {
    this._i = (this._i + 1) & 3
    this._total -= this._window[this._i]
    this._window[this._i] = 0 // clear oldest
  }
}

function noop() {}

function oncycle(req) {
  req._timeout = null
  req.oncycle(req)
  if (req.sent > req.retries) {
    req._io.stats.requests.timeouts++
    req.destroy(REQUEST_TIMEOUT())
    req._io.ontimeout(req)
  } else {
    req._io.stats.requests.retries++
    req.send()
  }
}

function decodeReply(from, state) {
  try {
    const flags = c.uint.decode(state)
    const tid = c.uint16.decode(state)
    const to = peer.ipv4.decode(state)
    const id = flags & 1 ? c.fixed32.decode(state) : null
    const token = flags & 2 ? c.fixed32.decode(state) : null
    const closerNodes = flags & 4 ? peer.ipv4Array.decode(state) : null
    const error = flags & 8 ? c.uint.decode(state) : 0
    const value = flags & 16 ? c.buffer.decode(state) : null

    if (id !== null) from.id = validateId(id, from)

    return { tid, rtt: 0, from, to, token, closerNodes, error, value }
  } catch {
    return null
  }
}

function validateId(id, from) {
  const expected = peer.id(from.host, from.port)
  return b4a.equals(expected, id) ? expected : null
}
const sodium = require('sodium-universal')
const c = require('compact-encoding')
const b4a = require('b4a')

const ipv4 = {
  ...c.ipv4Address,
  decode(state) {
    const ip = c.ipv4Address.decode(state)
    return {
      id: null, // populated by the callee
      host: ip.host,
      port: ip.port
    }
  }
}

module.exports = { id, ipv4, ipv4Array: c.array(ipv4) }

function id(host, port, out = b4a.allocUnsafeSlow(32)) {
  const addr = out.subarray(0, 6)
  ipv4.encode({ start: 0, end: 6, buffer: addr }, { host, port })
  sodium.crypto_generichash(out, addr)
  return out
}
const { Readable, getStreamError } = require('streamx')
const b4a = require('b4a')
const peer = require('./peer')
const { DOWN_HINT } = require('./commands')

const DONE = []
const DOWN = []

module.exports = class Query extends Readable {
  constructor(dht, target, internal, command, value, opts = {}) {
    super()

    dht.stats.queries.total++
    dht.stats.queries.active++

    this.force = !!opts.force
    this.dht = dht
    this.k = this.dht.table.k
    this.target = target
    this.internal = internal
    this.command = command
    this.value = value
    this.errors = 0
    this.successes = 0
    this.concurrency = opts.concurrency || this.dht.concurrency
    this.inflight = 0
    this.map = opts.map || defaultMap
    this.retries =
      opts.retries === 0 ? 0 : opts.retries || (this.internal && command === DOWN_HINT ? 3 : 5)
    this.closestReplies = []

    this._slow = 0
    this._slowdown = false
    this._seen = new Map()
    this._pending = []
    this._fromTable = false
    this._commit = opts.commit === true ? autoCommit : opts.commit || null
    this._commiting = false
    this._session = opts.session || dht.session()
    this._autoDestroySession = !opts.session
    this._onlyClosestNodes = false

    this._onvisitbound = this._onvisit.bind(this)
    this._onerrorbound = this._onerror.bind(this)
    this._oncyclebound = this._oncycle.bind(this)

    const nodes = opts.nodes || opts.closestNodes
    const replies = opts.replies || opts.closestReplies

    // add them reverse as we pop below
    if (nodes) {
      for (let i = nodes.length - 1; i >= 0; i--) {
        const node = nodes[i]
        this._addPending(
          {
            id: node.id || peer.id(node.host, node.port),
            host: node.host,
            port: node.port
          },
          null
        )
      }
    } else if (replies) {
      for (let i = replies.length - 1; i >= 0; i--) {
        this._addPending(replies[i].from, null)
      }
    }

    if (opts.onlyClosestNodes) this._onlyClosestNodes = true
  }

  get closestNodes() {
    const nodes = new Array(this.closestReplies.length)

    for (let i = 0; i < nodes.length; i++) {
      nodes[i] = this.closestReplies[i].from
    }

    return nodes
  }

  finished() {
    return new Promise((resolve, reject) => {
      if (this.destroyed) {
        const error = getStreamError(this)
        if (error) reject(error)
        else resolve()
        return
      }

      const self = this
      let error = null

      this.resume()
      this.on('error', onerror)
      this.on('close', onclose)

      function onclose() {
        self.removeListener('error', onerror)
        self.removeListener('close', onclose)
        if (error) reject(error)
        else resolve()
      }

      function onerror(err) {
        error = err
      }
    })
  }

  _addFromTable() {
    if (this._pending.length >= this.k) return
    this._fromTable = true

    const closest = this.dht.table.closest(this.target, this.k - this._pending.length)

    for (const node of closest) {
      this._addPending({ id: node.id, host: node.host, port: node.port }, null)
    }
  }

  async _open(cb) {
    this._addFromTable()
    if (this._pending.length >= this.k) return cb(null)

    for await (const node of this.dht._resolveBootstrapNodes()) {
      this._addPending(node, null)
    }

    cb(null)
  }

  _isCloser(id) {
    return (
      this.closestReplies.length < this.k ||
      this._compare(id, this.closestReplies[this.closestReplies.length - 1].from.id) < 0
    )
  }

  _addPending(node, ref) {
    if (this._onlyClosestNodes) return false

    const addr = node.host + ':' + node.port
    const refs = this._seen.get(addr)
    const isCloser = this._isCloser(node.id)

    if (refs === DONE) {
      return isCloser
    }

    if (refs === DOWN) {
      if (ref) this._downHint(ref, node)
      return isCloser
    }

    if (refs) {
      if (ref !== null) refs.push(ref)
      return isCloser
    }

    if (!isCloser) {
      return false
    }

    this._seen.set(addr, ref === null ? [] : [ref])
    this._pending.push(node)

    return true
  }

  _read(cb) {
    this._readMore()
    cb(null)
  }

  _readMore() {
    if (this.destroying || this._commiting) return

    const concurrency = (this._slowdown ? 3 : this.concurrency) + this._slow

    while (this.inflight < concurrency && this._pending.length > 0) {
      const next = this._pending.pop()
      if (next && next.id && !this._isCloser(next.id)) continue
      this._visit(next)
    }

    // if reusing closest nodes, slow down after the first readMore tick to allow
    // the closest node a chance to reply before going broad to question more
    if (!this._fromTable && this.successes === 0 && this.errors === 0) {
      this._slowdown = true
    }

    if (this._pending.length > 0) return

    // if no inflight OR all the queries we are waiting on are marked as slow and we have a full result.
    if (
      this.inflight === 0 ||
      (this._slow === this.inflight && this.closestReplies.length >= this.k)
    ) {
      // if more than 3/4 failed and we only used cached nodes, try again from the routing table
      if (!this._fromTable && this.successes < this.k / 4) {
        this._addFromTable()
        this._readMore()
        return
      }

      this._flush()
    }
  }

  _flush() {
    if (this._commiting) return
    this._commiting = true

    if (this._commit === null) {
      this.push(null)
      return
    }

    const p = []
    for (const m of this.closestReplies) p.push(this._commit(m, this.dht, this))
    this._endAfterCommit(p)
  }

  _endAfterCommit(ps) {
    if (!ps.length) {
      this.destroy(new Error('Too few nodes responded'))
      return
    }

    const self = this

    let pending = ps.length
    let success = 0

    for (const p of ps) p.then(ondone, onerror)

    function ondone() {
      success++
      if (--pending === 0) self.push(null)
    }

    function onerror(err) {
      if (--pending > 0) return
      if (success) self.push(null)
      else self.destroy(err)
    }
  }

  _dec(req) {
    if (req.oncycle === noop) {
      this._slow--
    } else {
      req.oncycle = noop
    }
    this.inflight--
  }

  _onvisit(m, req) {
    this._dec(req)

    const addr = req.to.host + ':' + req.to.port
    this._seen.set(addr, DONE)

    if (this._commiting) return

    if (m.error === 0) this.successes++
    else this.errors++

    if (m.error === 0 && m.from.id !== null && this._isCloser(m.from.id)) this._pushClosest(m)

    if (m.closerNodes !== null) {
      for (const node of m.closerNodes) {
        node.id = peer.id(node.host, node.port)
        if (this.dht._filterNode !== null && !this.dht._filterNode(node)) continue
        if (b4a.equals(node.id, this.dht.table.id)) continue
        // TODO: we could continue here instead of breaking to ensure that one of the nodes in the closer list
        // is later marked as DOWN that we gossip that back
        if (!this._addPending(node, m.from)) break
      }
    }

    if (!this._fromTable && this.successes + this.errors >= this.concurrency) {
      this._slowdown = false
    }

    if (m.error !== 0) {
      this._readMore()
      return
    }

    const data = this.map(m)
    if (!data || this.push(data) !== false) {
      this._readMore()
    }
  }

  _onerror(err, req) {
    const addr = req.to.host + ':' + req.to.port
    const refs = this._seen.get(addr)

    if (err.code === 'REQUEST_TIMEOUT') {
      this._seen.set(addr, DOWN)
      for (const node of refs) this._downHint(node, req.to)
    }

    this._dec(req)
    this.errors++
    this._readMore()
  }

  _oncycle(req) {
    req.oncycle = noop
    this._slow++
    this._readMore()
  }

  _downHint(node, down) {
    // Check rate limit
    if (
      this.dht._downHintsRateLimit !== -1 &&
      this.dht._downHintsSentPerTick >= this.dht._downHintsRateLimit
    ) {
      return null
    }

    this.dht._downHintsSentPerTick++

    const state = { start: 0, end: 6, buffer: b4a.allocUnsafe(6) }
    peer.ipv4.encode(state, down)
    this.dht._request(node, false, true, DOWN_HINT, null, state.buffer, this._session, noop, noop)
  }

  _pushClosest(m) {
    this.closestReplies.push(m)
    for (let i = this.closestReplies.length - 2; i >= 0; i--) {
      const prev = this.closestReplies[i]
      const cmp = this._compare(prev.from.id, m.from.id)
      // if sorted, done!
      if (cmp < 0) break
      // if dup, splice it out (rare)
      if (cmp === 0) {
        this.closestReplies.splice(i + 1, 1)
        break
      }
      // swap and continue down
      this.closestReplies[i + 1] = prev
      this.closestReplies[i] = m
    }
    if (this.closestReplies.length > this.k) this.closestReplies.pop()
  }

  _compare(a, b) {
    for (let i = 0; i < a.length; i++) {
      if (a[i] === b[i]) continue
      const t = this.target[i]
      return (t ^ a[i]) - (t ^ b[i])
    }
    return 0
  }

  _visit(to) {
    this.inflight++

    const req = this.dht._request(
      to,
      this.force,
      this.internal,
      this.command,
      this.target,
      this.value,
      this._session,
      this._onvisitbound,
      this._onerrorbound
    )
    if (req === null) {
      this.destroy(new Error('Node was destroyed'))
      return
    }
    req.retries = this.retries
    req.oncycle = this._oncyclebound
    if (this.force) req.retries = 0
  }

  _destroy(cb) {
    this.dht.stats.queries.active--
    if (this._autoDestroySession) this._session.destroy()
    cb(null)
  }
}

function autoCommit(reply, dht, query) {
  if (!reply.token) return Promise.reject(new Error('No token received for closest node'))
  return dht.request(
    {
      token: reply.token,
      target: query.target,
      command: query.command,
      value: query.value
    },
    reply.from
  )
}

function defaultMap(m) {
  return m
}

function noop() {}
module.exports = class Session {
  constructor(dht) {
    this.dht = dht
    this.inflight = []
  }

  _attach(req) {
    req.index = this.inflight.push(req) - 1
  }

  _detach(req) {
    const i = req.index
    if (i === -1) return
    req.index = -1

    if (i === this.inflight.length - 1) this.inflight.pop()
    else {
      const req = (this.inflight[i] = this.inflight.pop())
      req.index = i
    }
  }

  query({ target, command, value }, opts = {}) {
    return this.dht.query({ target, command, value }, { ...opts, session: this })
  }

  request({ token, command, target, value }, { host, port }, opts = {}) {
    return this.dht.request(
      { token, command, target, value },
      { host, port },
      { ...opts, session: this }
    )
  }

  ping({ host, port }, opts = {}) {
    return this.dht.ping({ host, port }, { ...opts, session: this })
  }

  destroy(err) {
    while (this.inflight.length) {
      const req = this.inflight[0]
      // prevent destroyed requests from contributing to congestion counts
      this.dht.io.congestion.recv()
      req.destroy(err)
    }
  }
}
{
  "name": "dht-rpc",
  "version": "6.27.0",
  "description": "Make RPC calls over a Kademlia based DHT",
  "main": "index.js",
  "files": [
    "index.js",
    "lib/*.js"
  ],
  "imports": {
    "events": {
      "bare": "bare-events",
      "default": "events"
    }
  },
  "dependencies": {
    "adaptive-timeout": "^1.0.1",
    "b4a": "^1.6.1",
    "bare-events": "^2.2.0",
    "compact-encoding": "^3.0.0",
    "compact-encoding-net": "^1.2.0",
    "fast-fifo": "^1.1.0",
    "kademlia-routing-table": "^1.0.1",
    "nat-sampler": "^1.0.1",
    "sodium-universal": "^5.0.0",
    "streamx": "^2.13.2",
    "time-ordered-set": "^2.0.0",
    "udx-native": "^1.5.3"
  },
  "devDependencies": {
    "brittle": "^3.0.0",
    "lunte": "^1.3.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^2.0.0",
    "test-suspend": "^1.0.0"
  },
  "scripts": {
    "format": "prettier --write .",
    "test": "npm run test:node && npm run test:bare",
    "test:bare": "brittle-bare --coverage test.js",
    "lint": "prettier --check . && lunte",
    "test:node": "brittle-node --coverage test.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/holepunchto/dht-rpc.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/holepunchto/dht-rpc/issues"
  },
  "homepage": "https://github.com/holepunchto/dht-rpc#"
}
module.exports = require('bare-events')
{
  "name": "events-universal",
  "version": "1.0.1",
  "description": "Universal wrapper for the Node.js events module",
  "exports": {
    "./package": "./package.json",
    ".": {
      "bare": "./bare.js",
      "react-native": "./react-native.js",
      "default": "./default.js"
    }
  },
  "files": [
    "index.js",
    "default.js",
    "bare.js",
    "react-native.js"
  ],
  "scripts": {
    "test": "prettier . --check"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/events-universal.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/events-universal/issues"
  },
  "homepage": "https://github.com/holepunchto/events-universal#readme",
  "dependencies": {
    "bare-events": "^2.7.0"
  },
  "devDependencies": {
    "prettier": "^3.4.2",
    "prettier-config-holepunch": "^1.0.0"
  }
}
module.exports = class FixedFIFO {
  constructor (hwm) {
    if (!(hwm > 0) || ((hwm - 1) & hwm) !== 0) throw new Error('Max size for a FixedFIFO should be a power of two')
    this.buffer = new Array(hwm)
    this.mask = hwm - 1
    this.top = 0
    this.btm = 0
    this.next = null
  }

  clear () {
    this.top = this.btm = 0
    this.next = null
    this.buffer.fill(undefined)
  }

  push (data) {
    if (this.buffer[this.top] !== undefined) return false
    this.buffer[this.top] = data
    this.top = (this.top + 1) & this.mask
    return true
  }

  shift () {
    const last = this.buffer[this.btm]
    if (last === undefined) return undefined
    this.buffer[this.btm] = undefined
    this.btm = (this.btm + 1) & this.mask
    return last
  }

  peek () {
    return this.buffer[this.btm]
  }

  isEmpty () {
    return this.buffer[this.btm] === undefined
  }
}
const FixedFIFO = require('./fixed-size')

module.exports = class FastFIFO {
  constructor (hwm) {
    this.hwm = hwm || 16
    this.head = new FixedFIFO(this.hwm)
    this.tail = this.head
    this.length = 0
  }

  clear () {
    this.head = this.tail
    this.head.clear()
    this.length = 0
  }

  push (val) {
    this.length++
    if (!this.head.push(val)) {
      const prev = this.head
      this.head = prev.next = new FixedFIFO(2 * this.head.buffer.length)
      this.head.push(val)
    }
  }

  shift () {
    if (this.length !== 0) this.length--
    const val = this.tail.shift()
    if (val === undefined && this.tail.next) {
      const next = this.tail.next
      this.tail.next = null
      this.tail = next
      return this.tail.shift()
    }

    return val
  }

  peek () {
    const val = this.tail.peek()
    if (val === undefined && this.tail.next) return this.tail.next.peek()
    return val
  }

  isEmpty () {
    return this.length === 0
  }
}
{
  "name": "fast-fifo",
  "version": "1.3.2",
  "description": "A fast fifo implementation similar to the one powering nextTick in Node.js core",
  "main": "index.js",
  "files": [
    "./index.js",
    "./fixed-size.js"
  ],
  "dependencies": {},
  "devDependencies": {
    "standard": "^17.1.0",
    "brittle": "^3.3.2"
  },
  "scripts": {
    "test": "standard && brittle test.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/mafintosh/fast-fifo.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/mafintosh/fast-fifo/issues"
  },
  "homepage": "https://github.com/mafintosh/fast-fifo"
}
// Attestation — the one place that knows keet-identity-key.
//
// A device proves it belongs to an identity by carrying a CHAIN PROOF: a signed path from the
// identity root down to the device's own public key. Verifying one is a pure function of the proof's
// bytes — no disk, no network, no clock — which is the property that lets it run inside `apply`.
//
// ── Why this file exists rather than calling IdentityKey directly ─────────────────────────────
//
//  1. ⛔ `IdentityKey.verify` THROWS on malformed input. Measured, not assumed: an empty buffer and
//     a truncated one both raise "Out of bounds", and random bytes raise "uint is greater than the
//     maximum safe integer". apply must be TOTAL — a throw mid-replay wedges the base and the op
//     that caused it is already durable, so there is no version of "fix it and retry". Every call
//     is wrapped here, once, instead of at each site.
//
//  2. It returns the CHAIN, which `verify` does not. `verify` gives back
//     `{ receipt, identityPublicKey, devicePublicKey }` and drops the ancestry — but the ancestry is
//     exactly what revocation needs, because `attestDevice(publicKey, parent, proof)` lets any
//     device mint further devices using only its own keypair and its own proof. No mnemonic. So a
//     compromised device can have descendants, and revoking it has to reach them.
//     ⚠ That means decoding `ProofEncoding` from the package's `lib/`, which is internal. It is
//     reachable (keet-identity-key declares no `exports` map), and 3.1↔3.2 are wire-identical, but
//     it is a coupling and it lives HERE so there is one place to fix if it moves.
//
//  3. Everything comes back as hex STRINGS. The view stores strings, apply compares strings, and a
//     Buffer that compared by identity instead of by value would be a silent authorisation bug.

import IdentityKey from 'keet-identity-key'
import { ProofEncoding } from 'keet-identity-key/lib/encoding'
import c from 'compact-encoding'
import b4a from 'b4a'

const hex = (k) => (typeof k === 'string' ? k : b4a.toString(k, 'hex'))
const isKeyHex = (s) => typeof s === 'string' && /^[0-9a-f]{64}$/i.test(s)

/**
 * readAttested(proof, attestedData, expectedRootHex) -> { root, device, chain } | null
 *
 *   root    hex identity public key the chain resolves to — the ANCHOR
 *   device  hex DEVICE key this proof attests — a real Ed25519 keypair, one that can sign
 *   chain   hex device keys from the root's first child down to `device`. The ANCESTRY.
 *
 * The general form: verify that some identity attested `attestedData`, optionally pinning WHICH
 * identity. One `verify` call settles both halves — it walks root → … → device checking each
 * link's signature, then checks `proof.data.signature` against the LAST device in that chain. So a
 * non-null return means exactly: *this root attested this device, and that device signed these
 * bytes.* Neither half is trusted separately, and there is no second primitive to keep in step.
 *
 * ⛔ `expectedRootHex` is pinned INSIDE verify (`opts.expectedIdentity`) rather than string-compared
 * afterwards. keet does the same at `keet-core/lib/core/invitations/index.js:122`. A comparison the
 * caller has to remember to write is a comparison someone eventually forgets.
 *
 * ⚠ `attestedData` is not optional for a proof that carries `data`: `verify` on one, called without
 * it, throws inside blake2b ("input must be a typed array") rather than returning null. Guarded
 * here, because the caller is apply and apply may not throw for any reason at all.
 *
 * TOTAL: returns null for anything it cannot verify, and never throws. `proof` may be a Buffer or
 * base64 — a daemon stores it as text and hands it back over a socket, so both arrive in practice.
 */
export function readAttested (proof, attestedData, expectedRootHex) {
  if (!proof) return null

  let buf
  try {
    buf = typeof proof === 'string' ? b4a.from(proof, 'base64') : proof
  } catch {
    return null
  }
  if (!buf || buf.length === 0) return null

  if (attestedData !== undefined && (!attestedData || attestedData.length === 0)) return null

  const opts = {}
  if (expectedRootHex !== undefined) {
    if (!isKeyHex(expectedRootHex)) return null
    opts.expectedIdentity = b4a.from(expectedRootHex, 'hex')
  }

  // Two independent throwers: verify() on malformed bytes, and decode() on bytes that verify
  // happened to accept. Both are inside the same guard.
  try {
    const verified = attestedData === undefined
      ? IdentityKey.verify(buf, null, opts)
      : IdentityKey.verify(buf, attestedData, opts)
    if (!verified) return null

    const decoded = c.decode(ProofEncoding, buf)
    if (!decoded || !Array.isArray(decoded.chain)) return null

    return {
      root: hex(verified.identityPublicKey),
      device: hex(verified.devicePublicKey),
      chain: decoded.chain.map((link) => hex(link.publicKey))
    }
  } catch {
    return null
  }
}

/**
 * readProof(proof, writerKeyHex) -> { root, device, chain } | null
 *
 * The admission case: `writerKeyHex` is the hypercore writer key being admitted, verified as
 * ATTESTED DATA. That indirection is the whole design:
 *
 *   - A writer core key is MANIFEST-derived. Nobody holds its secret half, so it can never sign,
 *     so a chain naming it can never be extended — measured: attesting one and then trying to
 *     attest from it is REFUSED.
 *   - A device key is an ordinary keypair. It CAN sign, so an already-linked device can attest the
 *     next one with no mnemonic — which is exactly what pear-git-v3's DeviceLinker does over
 *     blind-pairing, shipping { deviceSecretKey, deviceProof } and never the root.
 *
 * So the device key carries the identity, and the writer key is bound to it as data.
 */
export function readProof (proof, writerKeyHex) {
  if (writerKeyHex === undefined) return readAttested(proof, undefined)
  if (typeof writerKeyHex !== 'string' || writerKeyHex.length === 0) return null

  let attested
  try { attested = b4a.from(writerKeyHex, 'hex') } catch { return null }
  if (attested.length === 0) return null

  return readAttested(proof, attested)
}

/**
 * Sign arbitrary bytes as this device, carrying the chain that proves who it is.
 *
 * The inverse of `readAttested`. `attestData` re-encodes the credential's proof with a `data`
 * signature over `hash(bytes)` made by the device's own keypair — so the result is a self-contained
 * "identity R, via device D, signed these bytes", verifiable with no lookup and no state.
 *
 * ⚠ It OVERWRITES `proof.data`, so one credential proof yields one signature per call and each call
 * must start from the stored credential rather than from a previous result. Decoding from the
 * stored base64 each time is what keeps that true.
 */
export function signAttested (bytes, cred) {
  if (!cred || !cred.keyPair || !cred.proof) throw new Error('signing needs a device credential { keyPair, proof }')
  const proof = typeof cred.proof === 'string' ? b4a.from(cred.proof, 'base64') : cred.proof
  return b4a.toString(IdentityKey.attestData(bytes, cred.keyPair, proof), 'base64')
}

export default { readProof, readAttested, signAttested }
function isBuffer(value) {
  return Buffer.isBuffer(value) || value instanceof Uint8Array
}

function isEncoding(encoding) {
  return Buffer.isEncoding(encoding)
}

function alloc(size, fill, encoding) {
  return Buffer.alloc(size, fill, encoding)
}

function allocUnsafe(size) {
  return Buffer.allocUnsafe(size)
}

function allocUnsafeSlow(size) {
  return Buffer.allocUnsafeSlow(size)
}

function byteLength(string, encoding) {
  return Buffer.byteLength(string, encoding)
}

function compare(a, b) {
  return Buffer.compare(a, b)
}

function concat(buffers, totalLength) {
  return Buffer.concat(buffers, totalLength)
}

function copy(source, target, targetStart, start, end) {
  return toBuffer(source).copy(target, targetStart, start, end)
}

function equals(a, b) {
  return toBuffer(a).equals(b)
}

function fill(buffer, value, offset, end, encoding) {
  return toBuffer(buffer).fill(value, offset, end, encoding)
}

function from(value, encodingOrOffset, length) {
  return Buffer.from(value, encodingOrOffset, length)
}

function includes(buffer, value, byteOffset, encoding) {
  return toBuffer(buffer).includes(value, byteOffset, encoding)
}

function indexOf(buffer, value, byfeOffset, encoding) {
  return toBuffer(buffer).indexOf(value, byfeOffset, encoding)
}

function lastIndexOf(buffer, value, byteOffset, encoding) {
  return toBuffer(buffer).lastIndexOf(value, byteOffset, encoding)
}

function swap16(buffer) {
  return toBuffer(buffer).swap16()
}

function swap32(buffer) {
  return toBuffer(buffer).swap32()
}

function swap64(buffer) {
  return toBuffer(buffer).swap64()
}

function toBuffer(buffer) {
  if (Buffer.isBuffer(buffer)) return buffer
  return Buffer.from(buffer.buffer, buffer.byteOffset, buffer.byteLength)
}

function toString(buffer, encoding, start, end) {
  return toBuffer(buffer).toString(encoding, start, end)
}

function write(buffer, string, offset, length, encoding) {
  return toBuffer(buffer).write(string, offset, length, encoding)
}

function readDoubleBE(buffer, offset) {
  return toBuffer(buffer).readDoubleBE(offset)
}

function readDoubleLE(buffer, offset) {
  return toBuffer(buffer).readDoubleLE(offset)
}

function readFloatBE(buffer, offset) {
  return toBuffer(buffer).readFloatBE(offset)
}

function readFloatLE(buffer, offset) {
  return toBuffer(buffer).readFloatLE(offset)
}

function readInt32BE(buffer, offset) {
  return toBuffer(buffer).readInt32BE(offset)
}

function readInt32LE(buffer, offset) {
  return toBuffer(buffer).readInt32LE(offset)
}

function readUInt32BE(buffer, offset) {
  return toBuffer(buffer).readUInt32BE(offset)
}

function readUInt32LE(buffer, offset) {
  return toBuffer(buffer).readUInt32LE(offset)
}

function writeDoubleBE(buffer, value, offset) {
  return toBuffer(buffer).writeDoubleBE(value, offset)
}

function writeDoubleLE(buffer, value, offset) {
  return toBuffer(buffer).writeDoubleLE(value, offset)
}

function writeFloatBE(buffer, value, offset) {
  return toBuffer(buffer).writeFloatBE(value, offset)
}

function writeFloatLE(buffer, value, offset) {
  return toBuffer(buffer).writeFloatLE(value, offset)
}

function writeInt32BE(buffer, value, offset) {
  return toBuffer(buffer).writeInt32BE(value, offset)
}

function writeInt32LE(buffer, value, offset) {
  return toBuffer(buffer).writeInt32LE(value, offset)
}

function writeUInt32BE(buffer, value, offset) {
  return toBuffer(buffer).writeUInt32BE(value, offset)
}

function writeUInt32LE(buffer, value, offset) {
  return toBuffer(buffer).writeUInt32LE(value, offset)
}

module.exports = {
  isBuffer,
  isEncoding,
  alloc,
  allocUnsafe,
  allocUnsafeSlow,
  byteLength,
  compare,
  concat,
  copy,
  equals,
  fill,
  from,
  includes,
  indexOf,
  lastIndexOf,
  swap16,
  swap32,
  swap64,
  toBuffer,
  toString,
  write,
  readDoubleBE,
  readDoubleLE,
  readFloatBE,
  readFloatLE,
  readInt32BE,
  readInt32LE,
  readUInt32BE,
  readUInt32LE,
  writeDoubleBE,
  writeDoubleLE,
  writeFloatBE,
  writeFloatLE,
  writeInt32BE,
  writeInt32LE,
  writeUInt32BE,
  writeUInt32LE
}
{
  "name": "b4a",
  "version": "1.8.1",
  "description": "Bridging the gap between buffers and typed arrays",
  "exports": {
    "./package": "./package.json",
    ".": {
      "react-native": "./react-native.js",
      "browser": "./browser.js",
      "default": "./index.js"
    }
  },
  "files": [
    "browser.js",
    "index.js",
    "react-native.js",
    "lib"
  ],
  "scripts": {
    "format": "prettier --write . && lunte --fix",
    "lint": "prettier --check . && lunte",
    "test": "npm run test:bare && npm run test:node",
    "test:bare": "brittle-bare --coverage test.mjs",
    "test:node": "brittle-node --coverage test.mjs"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/b4a.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/b4a/issues"
  },
  "homepage": "https://github.com/holepunchto/b4a#readme",
  "devDependencies": {
    "brittle": "^3.5.2",
    "lunte": "^1.7.0",
    "nanobench": "^3.0.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^2.0.0"
  },
  "peerDependencies": {
    "react-native-b4a": "*"
  },
  "peerDependenciesMeta": {
    "react-native-b4a": {
      "optional": true
    }
  }
}
const LE = (exports.LE = new Uint8Array(new Uint16Array([0xff]).buffer)[0] === 0xff)

exports.BE = !LE
const b4a = require('b4a')

const { BE } = require('./endian')

// Zig-zag doubles the magnitude of a value before it is written as a uint, so
// an int only reaches half as far as a uint of the same width does.
const MAX_SAFE_INT = 2 ** 52 - 1
const MIN_SAFE_INT = -(2 ** 52)

exports.state = function (start = 0, end = 0, buffer = null) {
  return { start, end, buffer }
}

const raw = (exports.raw = require('./raw'))

const uint = (exports.uint = {
  preencode(state, n) {
    state.end += n <= 0xfc ? 1 : n <= 0xffff ? 3 : n <= 0xffffffff ? 5 : 9
  },
  encode(state, n) {
    if (n <= 0xfc) uint8.encode(state, n)
    else if (n <= 0xffff) {
      state.buffer[state.start++] = 0xfd
      uint16.encode(state, n)
    } else if (n <= 0xffffffff) {
      state.buffer[state.start++] = 0xfe
      uint32.encode(state, n)
    } else {
      state.buffer[state.start++] = 0xff
      uint64.encode(state, n)
    }
  },
  decode(state) {
    const a = uint8.decode(state)
    if (a <= 0xfc) return a
    if (a === 0xfd) return uint16.decode(state)
    if (a === 0xfe) return uint32.decode(state)
    return uint64.decode(state)
  }
})

const uint8 = (exports.uint8 = {
  preencode(state, n) {
    state.end += 1
  },
  encode(state, n) {
    validateUint(n)
    state.buffer[state.start++] = n
  },
  decode(state) {
    if (state.start >= state.end) throw new Error('Out of bounds')
    return state.buffer[state.start++]
  }
})

const uint16 = (exports.uint16 = {
  preencode(state, n) {
    state.end += 2
  },
  encode(state, n) {
    validateUint(n)
    state.buffer[state.start++] = n
    state.buffer[state.start++] = n >>> 8
  },
  decode(state) {
    if (state.end - state.start < 2) throw new Error('Out of bounds')
    return state.buffer[state.start++] + state.buffer[state.start++] * 0x100
  }
})

const uint24 = (exports.uint24 = {
  preencode(state, n) {
    state.end += 3
  },
  encode(state, n) {
    validateUint(n)
    state.buffer[state.start++] = n
    state.buffer[state.start++] = n >>> 8
    state.buffer[state.start++] = n >>> 16
  },
  decode(state) {
    if (state.end - state.start < 3) throw new Error('Out of bounds')
    return (
      state.buffer[state.start++] +
      state.buffer[state.start++] * 0x100 +
      state.buffer[state.start++] * 0x10000
    )
  }
})

const uint32 = (exports.uint32 = {
  preencode(state, n) {
    state.end += 4
  },
  encode(state, n) {
    validateUint(n)
    state.buffer[state.start++] = n
    state.buffer[state.start++] = n >>> 8
    state.buffer[state.start++] = n >>> 16
    state.buffer[state.start++] = n >>> 24
  },
  decode(state) {
    if (state.end - state.start < 4) throw new Error('Out of bounds')
    return (
      state.buffer[state.start++] +
      state.buffer[state.start++] * 0x100 +
      state.buffer[state.start++] * 0x10000 +
      state.buffer[state.start++] * 0x1000000
    )
  }
})

const uint32be = (exports.uint32be = {
  preencode(state, n) {
    state.end += 4
  },
  encode(state, n) {
    validateUint(n)
    state.buffer[state.start++] = n >>> 24
    state.buffer[state.start++] = n >>> 16
    state.buffer[state.start++] = n >>> 8
    state.buffer[state.start++] = n
  },
  decode(state) {
    if (state.end - state.start < 4) throw new Error('Out of bounds')
    return (
      state.buffer[state.start++] * 0x1000000 +
      state.buffer[state.start++] * 0x10000 +
      state.buffer[state.start++] * 0x100 +
      state.buffer[state.start++]
    )
  }
})

const uint40 = (exports.uint40 = {
  preencode(state, n) {
    state.end += 5
  },
  encode(state, n) {
    validateUint(n)
    const r = Math.floor(n / 0x100)
    uint8.encode(state, n)
    uint32.encode(state, r)
  },
  decode(state) {
    if (state.end - state.start < 5) throw new Error('Out of bounds')
    return uint8.decode(state) + 0x100 * uint32.decode(state)
  }
})

const uint48 = (exports.uint48 = {
  preencode(state, n) {
    state.end += 6
  },
  encode(state, n) {
    validateUint(n)
    const r = Math.floor(n / 0x10000)
    uint16.encode(state, n)
    uint32.encode(state, r)
  },
  decode(state) {
    if (state.end - state.start < 6) throw new Error('Out of bounds')
    return uint16.decode(state) + 0x10000 * uint32.decode(state)
  }
})

const uint56 = (exports.uint56 = {
  preencode(state, n) {
    state.end += 7
  },
  encode(state, n) {
    validateUint(n)
    const r = Math.floor(n / 0x1000000)
    uint24.encode(state, n)
    uint32.encode(state, r)
  },
  decode(state) {
    if (state.end - state.start < 7) throw new Error('Out of bounds')
    return validateSafeUint(uint24.decode(state) + 0x1000000 * uint32.decode(state))
  }
})

const uint64 = (exports.uint64 = {
  preencode(state, n) {
    state.end += 8
  },
  encode(state, n) {
    validateUint(n)
    const r = Math.floor(n / 0x100000000)
    uint32.encode(state, n)
    uint32.encode(state, r)
  },
  decode(state) {
    if (state.end - state.start < 8) throw new Error('Out of bounds')
    return validateSafeUint(uint32.decode(state) + 0x100000000 * uint32.decode(state))
  }
})

exports.uint64be = {
  preencode(state, n) {
    state.end += 8
  },
  encode(state, n) {
    validateUint(n)
    const r = Math.floor(n / 0x100000000)
    uint32be.encode(state, r)
    uint32be.encode(state, n)
  },
  decode(state) {
    if (state.end - state.start < 8) throw new Error('Out of bounds')
    return validateSafeUint(0x100000000 * uint32be.decode(state) + uint32be.decode(state))
  }
}

const int = (exports.int = zigZagInt(uint))
exports.int8 = zigZagInt(uint8)
exports.int16 = zigZagInt(uint16)
exports.int24 = zigZagInt(uint24)
exports.int32 = zigZagInt(uint32)
exports.int40 = zigZagInt(uint40)
exports.int48 = zigZagInt(uint48)
exports.int56 = zigZagInt(uint56)
exports.int64 = zigZagInt(uint64)

const biguint64 = (exports.biguint64 = {
  preencode(state, n) {
    state.end += 8
  },
  encode(state, n) {
    const view = new DataView(state.buffer.buffer, state.start + state.buffer.byteOffset, 8)
    view.setBigUint64(0, n, true) // little endian
    state.start += 8
  },
  decode(state) {
    if (state.end - state.start < 8) throw new Error('Out of bounds')
    const view = new DataView(state.buffer.buffer, state.start + state.buffer.byteOffset, 8)
    const n = view.getBigUint64(0, true) // little endian
    state.start += 8
    return n
  }
})

exports.bigint64 = zigZagBigInt(biguint64)

const biguint = (exports.biguint = {
  preencode(state, n) {
    let len = 0
    for (let m = n; m; m = m >> 64n) len++
    uint.preencode(state, len)
    state.end += 8 * len
  },
  encode(state, n) {
    let len = 0
    for (let m = n; m; m = m >> 64n) len++
    uint.encode(state, len)
    const view = new DataView(state.buffer.buffer, state.start + state.buffer.byteOffset, 8 * len)
    for (let m = n, i = 0; m; m = m >> 64n, i += 8) {
      view.setBigUint64(i, BigInt.asUintN(64, m), true) // little endian
    }
    state.start += 8 * len
  },
  decode(state) {
    const len = uint.decode(state)
    if (state.end - state.start < 8 * len) throw new Error('Out of bounds')
    const view = new DataView(state.buffer.buffer, state.start + state.buffer.byteOffset, 8 * len)
    let n = 0n
    for (let i = len - 1; i >= 0; i--) n = (n << 64n) + view.getBigUint64(i * 8, true) // little endian
    state.start += 8 * len
    return n
  }
})

exports.bigint = zigZagBigInt(biguint)

exports.lexint = require('./lexint')

exports.float32 = {
  preencode(state, n) {
    state.end += 4
  },
  encode(state, n) {
    const view = new DataView(state.buffer.buffer, state.start + state.buffer.byteOffset, 4)
    view.setFloat32(0, n, true) // little endian
    state.start += 4
  },
  decode(state) {
    if (state.end - state.start < 4) throw new Error('Out of bounds')
    const view = new DataView(state.buffer.buffer, state.start + state.buffer.byteOffset, 4)
    const float = view.getFloat32(0, true) // little endian
    state.start += 4
    return float
  }
}

exports.float64 = {
  preencode(state, n) {
    state.end += 8
  },
  encode(state, n) {
    const view = new DataView(state.buffer.buffer, state.start + state.buffer.byteOffset, 8)
    view.setFloat64(0, n, true) // little endian
    state.start += 8
  },
  decode(state) {
    if (state.end - state.start < 8) throw new Error('Out of bounds')
    const view = new DataView(state.buffer.buffer, state.start + state.buffer.byteOffset, 8)
    const float = view.getFloat64(0, true) // little endian
    state.start += 8
    return float
  }
}

const buffer = (exports.buffer = {
  preencode(state, b) {
    uint8array.preencode(state, b)
  },
  encode(state, b) {
    uint8array.encode(state, b)
  },
  decode(state) {
    const len = uint.decode(state)
    if (state.end - state.start < len) throw new Error('Out of bounds')
    return state.buffer.subarray(state.start, (state.start += len))
  }
})

exports.optionalBuffer = {
  preencode(state, b) {
    if (b) uint8array.preencode(state, b)
    else state.end++
  },
  encode(state, b) {
    if (b) uint8array.encode(state, b)
    else state.buffer[state.start++] = 0
  },
  decode(state) {
    const len = uint.decode(state)
    if (len === 0) return null
    if (state.end - state.start < len) throw new Error('Out of bounds')
    return state.buffer.subarray(state.start, (state.start += len))
  }
}

exports.binary = {
  ...buffer,
  preencode(state, b) {
    if (typeof b === 'string') utf8.preencode(state, b)
    else buffer.preencode(state, b)
  },
  encode(state, b) {
    if (typeof b === 'string') utf8.encode(state, b)
    else buffer.encode(state, b)
  }
}

exports.arraybuffer = {
  preencode(state, b) {
    uint.preencode(state, b.byteLength)
    state.end += b.byteLength
  },
  encode(state, b) {
    uint.encode(state, b.byteLength)

    const view = new Uint8Array(b)

    state.buffer.set(view, state.start)
    state.start += b.byteLength
  },
  decode(state) {
    const len = uint.decode(state)

    const b = new ArrayBuffer(len)
    const view = new Uint8Array(b)

    view.set(state.buffer.subarray(state.start, (state.start += len)))

    return b
  }
}

exports.bitarray = {
  preencode(state, m) {
    uint.preencode(state, m.length)
    state.end += Math.ceil(m.length / 8)
  },
  encode(state, m) {
    uint.encode(state, m.length)
    for (let i = 0; i < m.length; i += 8) {
      let byte = 0
      for (let j = 0; j < 8 && i + j < m.length; j++) {
        if (m[i + j]) byte |= 1 << j
      }
      state.buffer[state.start++] = byte
    }
  },
  decode(state) {
    const n = uint.decode(state)
    if (state.end - state.start < Math.ceil(n / 8)) throw new Error('Out of bounds')
    const m = new Array(n)
    for (let i = 0; i < n; i += 8) {
      const byte = state.buffer[state.start++]
      for (let j = 0; j < 8 && i + j < n; j++) {
        m[i + j] = (byte & (1 << j)) !== 0
      }
    }
    return m
  }
}

function typedarray(TypedArray, swap) {
  const n = TypedArray.BYTES_PER_ELEMENT

  return {
    preencode(state, b) {
      uint.preencode(state, b.length)
      state.end += b.byteLength
    },
    encode(state, b) {
      uint.encode(state, b.length)

      const view = new Uint8Array(b.buffer, b.byteOffset, b.byteLength)

      if (BE && swap) swap(view)

      state.buffer.set(view, state.start)
      state.start += b.byteLength
    },
    decode(state) {
      const len = uint.decode(state)

      let b = state.buffer.subarray(state.start, (state.start += len * n))
      if (b.byteLength !== len * n) throw new Error('Out of bounds')
      if (b.byteOffset % n !== 0) b = new Uint8Array(b)

      if (BE && swap) swap(b)

      return new TypedArray(b.buffer, b.byteOffset, b.byteLength / n)
    }
  }
}

const uint8array = (exports.uint8array = typedarray(Uint8Array))
exports.uint16array = typedarray(Uint16Array, b4a.swap16)
exports.uint32array = typedarray(Uint32Array, b4a.swap32)

exports.int8array = typedarray(Int8Array)
exports.int16array = typedarray(Int16Array, b4a.swap16)
exports.int32array = typedarray(Int32Array, b4a.swap32)

exports.biguint64array = typedarray(BigUint64Array, b4a.swap64)
exports.bigint64array = typedarray(BigInt64Array, b4a.swap64)

exports.float32array = typedarray(Float32Array, b4a.swap32)
exports.float64array = typedarray(Float64Array, b4a.swap64)

function string(encoding) {
  return {
    preencode(state, s) {
      const len = b4a.byteLength(s, encoding)
      uint.preencode(state, len)
      state.end += len
    },
    encode(state, s) {
      const len = b4a.byteLength(s, encoding)
      uint.encode(state, len)
      b4a.write(state.buffer, s, state.start, encoding)
      state.start += len
    },
    decode(state) {
      const len = uint.decode(state)
      if (state.end - state.start < len) throw new Error('Out of bounds')
      return b4a.toString(state.buffer, encoding, state.start, (state.start += len))
    },
    fixed(n) {
      return {
        preencode(state) {
          state.end += n
        },
        encode(state, s) {
          b4a.write(state.buffer, s, state.start, n, encoding)
          state.start += n
        },
        decode(state) {
          if (state.end - state.start < n) throw new Error('Out of bounds')
          return b4a.toString(state.buffer, encoding, state.start, (state.start += n))
        }
      }
    }
  }
}

const utf8 = (exports.string = exports.utf8 = string('utf-8'))
exports.ascii = string('ascii')
exports.hex = string('hex')
exports.base64 = string('base64')
exports.ucs2 = exports.utf16le = string('utf16le')

exports.bool = {
  preencode(state, b) {
    state.end++
  },
  encode(state, b) {
    state.buffer[state.start++] = b ? 1 : 0
  },
  decode(state) {
    if (state.start >= state.end) throw Error('Out of bounds')
    return state.buffer[state.start++] === 1
  }
}

const fixed = (exports.fixed = function fixed(n) {
  return {
    preencode(state, s) {
      if (s.byteLength !== n) throw new Error('Incorrect buffer size')
      state.end += n
    },
    encode(state, s) {
      state.buffer.set(s, state.start)
      state.start += n
    },
    decode(state) {
      if (state.end - state.start < n) throw new Error('Out of bounds')
      return state.buffer.subarray(state.start, (state.start += n))
    }
  }
})

exports.fixed32 = fixed(32)
exports.fixed64 = fixed(64)

exports.array = function array(enc) {
  return {
    preencode(state, list) {
      uint.preencode(state, list.length)
      for (let i = 0; i < list.length; i++) enc.preencode(state, list[i])
    },
    encode(state, list) {
      uint.encode(state, list.length)
      for (let i = 0; i < list.length; i++) enc.encode(state, list[i])
    },
    decode(state) {
      const len = uint.decode(state)
      if (len > 0x100000) throw new Error('Array is too big')
      const arr = new Array(len)
      for (let i = 0; i < len; i++) arr[i] = enc.decode(state)
      return arr
    }
  }
}

exports.frame = function frame(enc) {
  const dummy = exports.state()

  return {
    preencode(state, m) {
      const end = state.end
      enc.preencode(state, m)
      uint.preencode(state, state.end - end)
    },
    encode(state, m) {
      dummy.end = 0
      enc.preencode(dummy, m)
      uint.encode(state, dummy.end)
      enc.encode(state, m)
    },
    decode(state) {
      const end = state.end
      const len = uint.decode(state)
      state.end = state.start + len
      const m = enc.decode(state)
      state.start = state.end
      state.end = end
      return m
    }
  }
}

exports.date = {
  preencode(state, d) {
    int.preencode(state, d.getTime())
  },
  encode(state, d) {
    int.encode(state, d.getTime())
  },
  decode(state, d) {
    return new Date(int.decode(state))
  }
}

exports.json = {
  preencode(state, v) {
    utf8.preencode(state, JSON.stringify(v))
  },
  encode(state, v) {
    utf8.encode(state, JSON.stringify(v))
  },
  decode(state) {
    return JSON.parse(utf8.decode(state))
  }
}

exports.ndjson = {
  preencode(state, v) {
    utf8.preencode(state, JSON.stringify(v) + '\n')
  },
  encode(state, v) {
    utf8.encode(state, JSON.stringify(v) + '\n')
  },
  decode(state) {
    return JSON.parse(utf8.decode(state))
  }
}

// simple helper for when you want to just express nothing
exports.none = {
  preencode(state, n) {
    // do nothing
  },
  encode(state, n) {
    // do nothing
  },
  decode(state) {
    return null
  }
}

// "any" encoders here for helping just structure any object without schematising it

const anyArray = {
  preencode(state, arr) {
    uint.preencode(state, arr.length)
    for (let i = 0; i < arr.length; i++) {
      any.preencode(state, arr[i])
    }
  },
  encode(state, arr) {
    uint.encode(state, arr.length)
    for (let i = 0; i < arr.length; i++) {
      any.encode(state, arr[i])
    }
  },
  decode(state) {
    const arr = []
    let len = uint.decode(state)
    while (len-- > 0) {
      arr.push(any.decode(state))
    }
    return arr
  }
}

const anyObject = {
  preencode(state, o) {
    const keys = Object.keys(o)
    uint.preencode(state, keys.length)
    for (const key of keys) {
      utf8.preencode(state, key)
      any.preencode(state, o[key])
    }
  },
  encode(state, o) {
    const keys = Object.keys(o)
    uint.encode(state, keys.length)
    for (const key of keys) {
      utf8.encode(state, key)
      any.encode(state, o[key])
    }
  },
  decode(state) {
    let len = uint.decode(state)
    const o = {}
    while (len-- > 0) {
      const key = utf8.decode(state)
      o[key] = any.decode(state)
    }
    return o
  }
}

const anyTypes = [
  exports.none,
  exports.bool,
  exports.string,
  exports.buffer,
  exports.uint,
  exports.int,
  exports.float64,
  anyArray,
  anyObject,
  exports.date
]

const any = (exports.any = {
  preencode(state, o) {
    const t = getType(o)
    uint.preencode(state, t)
    anyTypes[t].preencode(state, o)
  },
  encode(state, o) {
    const t = getType(o)
    uint.encode(state, t)
    anyTypes[t].encode(state, o)
  },
  decode(state) {
    const t = uint.decode(state)
    if (t >= anyTypes.length) throw new Error('Unknown type: ' + t)
    return anyTypes[t].decode(state)
  }
})

const port = (exports.port = uint16)

const address = (host, family) => {
  return {
    preencode(state, m) {
      host.preencode(state, m.host)
      port.preencode(state, m.port)
    },
    encode(state, m) {
      host.encode(state, m.host)
      port.encode(state, m.port)
    },
    decode(state) {
      return {
        host: host.decode(state),
        family,
        port: port.decode(state)
      }
    }
  }
}

const ipv4 = (exports.ipv4 = {
  preencode(state) {
    state.end += 4
  },
  encode(state, string) {
    const start = state.start
    const end = start + 4

    let i = 0

    while (i < string.length) {
      let n = 0
      let c

      while (i < string.length && (c = string.charCodeAt(i++)) !== /* . */ 0x2e) {
        n = n * 10 + (c - /* 0 */ 0x30)
      }

      state.buffer[state.start++] = n
    }

    state.start = end
  },
  decode(state) {
    if (state.end - state.start < 4) throw new Error('Out of bounds')
    return (
      state.buffer[state.start++] +
      '.' +
      state.buffer[state.start++] +
      '.' +
      state.buffer[state.start++] +
      '.' +
      state.buffer[state.start++]
    )
  }
})

exports.ipv4Address = address(ipv4, 4)

const ipv6 = (exports.ipv6 = {
  preencode(state) {
    state.end += 16
  },
  encode(state, string) {
    const start = state.start
    const end = start + 16

    let i = 0
    let split = null

    while (i < string.length) {
      let n = 0
      let c

      while (i < string.length && (c = string.charCodeAt(i++)) !== /* : */ 0x3a) {
        if (c >= 0x30 && c <= 0x39) n = n * 0x10 + (c - /* 0 */ 0x30)
        else if (c >= 0x41 && c <= 0x46) n = n * 0x10 + (c - /* A */ 0x41 + 10)
        else if (c >= 0x61 && c <= 0x66) n = n * 0x10 + (c - /* a */ 0x61 + 10)
      }

      state.buffer[state.start++] = n >>> 8
      state.buffer[state.start++] = n

      if (i < string.length && string.charCodeAt(i) === /* : */ 0x3a) {
        i++
        split = state.start
      }
    }

    if (split !== null) {
      const offset = end - state.start
      state.buffer.copyWithin(split + offset, split).fill(0, split, split + offset)
    }

    state.start = end
  },
  decode(state) {
    if (state.end - state.start < 16) throw new Error('Out of bounds')
    return (
      (state.buffer[state.start++] * 256 + state.buffer[state.start++]).toString(16) +
      ':' +
      (state.buffer[state.start++] * 256 + state.buffer[state.start++]).toString(16) +
      ':' +
      (state.buffer[state.start++] * 256 + state.buffer[state.start++]).toString(16) +
      ':' +
      (state.buffer[state.start++] * 256 + state.buffer[state.start++]).toString(16) +
      ':' +
      (state.buffer[state.start++] * 256 + state.buffer[state.start++]).toString(16) +
      ':' +
      (state.buffer[state.start++] * 256 + state.buffer[state.start++]).toString(16) +
      ':' +
      (state.buffer[state.start++] * 256 + state.buffer[state.start++]).toString(16) +
      ':' +
      (state.buffer[state.start++] * 256 + state.buffer[state.start++]).toString(16)
    )
  }
})

exports.ipv6Address = address(ipv6, 6)

const ip = (exports.ip = {
  preencode(state, string) {
    const family = string.includes(':') ? 6 : 4
    uint8.preencode(state, family)
    if (family === 4) ipv4.preencode(state)
    else ipv6.preencode(state)
  },
  encode(state, string) {
    const family = string.includes(':') ? 6 : 4
    uint8.encode(state, family)
    if (family === 4) ipv4.encode(state, string)
    else ipv6.encode(state, string)
  },
  decode(state) {
    const family = uint8.decode(state)
    if (family === 4) return ipv4.decode(state)
    else return ipv6.decode(state)
  }
})

exports.ipAddress = {
  preencode(state, m) {
    ip.preencode(state, m.host)
    port.preencode(state, m.port)
  },
  encode(state, m) {
    ip.encode(state, m.host)
    port.encode(state, m.port)
  },
  decode(state) {
    const family = uint8.decode(state)
    return {
      host: family === 4 ? ipv4.decode(state) : ipv6.decode(state),
      family,
      port: port.decode(state)
    }
  }
}

const record = (exports.record = function (keyEncoding, valueEncoding) {
  return {
    preencode(state, v) {
      const keys = Object.keys(v)
      uint.preencode(state, keys.length)
      for (const k of keys) {
        keyEncoding.preencode(state, k)
        valueEncoding.preencode(state, v[k])
      }
    },
    encode(state, v) {
      const keys = Object.keys(v)
      uint.encode(state, keys.length)
      for (const k of keys) {
        keyEncoding.encode(state, k)
        valueEncoding.encode(state, v[k])
      }
    },
    decode(state) {
      const out = Object.create(null)
      const keys = uint.decode(state)
      for (let i = 0; i < keys; i++) {
        out[keyEncoding.decode(state)] = valueEncoding.decode(state)
      }
      return out
    }
  }
})

exports.stringRecord = record(utf8, utf8)

function getType(o) {
  if (o === null || o === undefined) return 0
  if (typeof o === 'boolean') return 1
  if (typeof o === 'string') return 2
  if (b4a.isBuffer(o)) return 3
  if (typeof o === 'number') {
    if (Number.isInteger(o)) return o >= 0 ? 4 : 5
    return 6
  }
  if (Array.isArray(o)) return 7
  if (o instanceof Date) return 9
  if (typeof o === 'object') return 8

  throw new Error('Unsupported type for ' + o)
}

exports.from = function from(enc) {
  if (typeof enc === 'string') return fromNamed(enc)
  if (enc.preencode) return enc
  if (enc.encodingLength) return fromAbstractEncoder(enc)
  return fromCodec(enc)
}

function fromNamed(enc) {
  switch (enc) {
    case 'ascii':
      return raw.ascii
    case 'utf-8':
    case 'utf8':
      return raw.utf8
    case 'hex':
      return raw.hex
    case 'base64':
      return raw.base64
    case 'utf16-le':
    case 'utf16le':
    case 'ucs-2':
    case 'ucs2':
      return raw.ucs2
    case 'ndjson':
      return raw.ndjson
    case 'json':
      return raw.json
    case 'binary':
    default:
      return raw.binary
  }
}

function fromCodec(enc) {
  let tmpM = null
  let tmpBuf = null

  return {
    preencode(state, m) {
      tmpM = m
      tmpBuf = enc.encode(m)
      state.end += tmpBuf.byteLength
    },
    encode(state, m) {
      raw.encode(state, m === tmpM ? tmpBuf : enc.encode(m))
      tmpM = tmpBuf = null
    },
    decode(state) {
      return enc.decode(raw.decode(state))
    }
  }
}

function fromAbstractEncoder(enc) {
  return {
    preencode(state, m) {
      state.end += enc.encodingLength(m)
    },
    encode(state, m) {
      enc.encode(m, state.buffer, state.start)
      state.start += enc.encode.bytes
    },
    decode(state) {
      const m = enc.decode(state.buffer, state.start, state.end)
      state.start += enc.decode.bytes
      return m
    }
  }
}

exports.encode = function encode(enc, m) {
  const state = exports.state()
  enc.preencode(state, m)
  state.buffer = b4a.allocUnsafe(state.end)
  enc.encode(state, m)
  return state.buffer
}

exports.decode = function decode(enc, buffer) {
  return enc.decode(exports.state(0, buffer.byteLength, buffer))
}

function zigZagInt(enc) {
  return {
    preencode(state, n) {
      enc.preencode(state, zigZagEncodeInt(n))
    },
    encode(state, n) {
      enc.encode(state, zigZagEncodeInt(n))
    },
    decode(state) {
      return zigZagDecodeInt(enc.decode(state))
    }
  }
}

function zigZagDecodeInt(n) {
  return n === 0 ? n : (n & 1) === 0 ? n / 2 : -(n + 1) / 2
}

function zigZagEncodeInt(n) {
  validateInt(n)
  // 0, -1, 1, -2, 2, ...
  return n < 0 ? 2 * -n - 1 : n === 0 ? 0 : 2 * n
}

function zigZagBigInt(enc) {
  return {
    preencode(state, n) {
      enc.preencode(state, zigZagEncodeBigInt(n))
    },
    encode(state, n) {
      enc.encode(state, zigZagEncodeBigInt(n))
    },
    decode(state) {
      return zigZagDecodeBigInt(enc.decode(state))
    }
  }
}

function zigZagDecodeBigInt(n) {
  return n === 0n ? n : (n & 1n) === 0n ? n / 2n : -(n + 1n) / 2n
}

function zigZagEncodeBigInt(n) {
  // 0, -1, 1, -2, 2, ...
  return n < 0n ? 2n * -n - 1n : n === 0n ? 0n : 2n * n
}

function validateSafeUint(n) {
  if (n <= Number.MAX_SAFE_INTEGER) return n // Handles NaN as well

  throw outsideUintRange()
}

function validateUint(n) {
  if (n >= 0 && n <= Number.MAX_SAFE_INTEGER) return n // Handles NaN as well

  throw outsideUintRange()
}

function validateInt(n) {
  if (n >= MIN_SAFE_INT && n <= MAX_SAFE_INT) return n // Handles NaN as well

  throw outsideIntRange()
}

// The validations above sit on the hottest paths in the library and are small
// enough to be inlined, which building a message inline would put a stop to.
// Kept out here, the message costs nothing until it is actually thrown.

function outsideUintRange() {
  return new Error(`uint must be between 0 and ${Number.MAX_SAFE_INTEGER}, use biguint`)
}

function outsideIntRange() {
  return new Error(`int must be between ${MIN_SAFE_INT} and ${MAX_SAFE_INT}, use bigint`)
}
module.exports = {
  preencode,
  encode,
  decode
}

function preencode(state, num) {
  if (num < 251) {
    state.end++
  } else if (num < 256) {
    state.end += 2
  } else if (num < 0x10000) {
    state.end += 3
  } else if (num < 0x1000000) {
    state.end += 4
  } else if (num < 0x100000000) {
    state.end += 5
  } else {
    state.end++
    const exp = Math.floor(Math.log(num) / Math.log(2)) - 32
    preencode(state, exp)
    state.end += 6
  }
}

function encode(state, num) {
  const max = 251
  const x = num - max

  if (num < max) {
    state.buffer[state.start++] = num
  } else if (num < 256) {
    state.buffer[state.start++] = max
    state.buffer[state.start++] = x
  } else if (num < 0x10000) {
    state.buffer[state.start++] = max + 1
    state.buffer[state.start++] = (x >> 8) & 0xff
    state.buffer[state.start++] = x & 0xff
  } else if (num < 0x1000000) {
    state.buffer[state.start++] = max + 2
    state.buffer[state.start++] = x >> 16
    state.buffer[state.start++] = (x >> 8) & 0xff
    state.buffer[state.start++] = x & 0xff
  } else if (num < 0x100000000) {
    state.buffer[state.start++] = max + 3
    state.buffer[state.start++] = x >> 24
    state.buffer[state.start++] = (x >> 16) & 0xff
    state.buffer[state.start++] = (x >> 8) & 0xff
    state.buffer[state.start++] = x & 0xff
  } else {
    // need to use Math here as bitwise ops are 32 bit
    const exp = Math.floor(Math.log(x) / Math.log(2)) - 32
    state.buffer[state.start++] = 0xff

    encode(state, exp)
    const rem = x / Math.pow(2, exp - 11)

    for (let i = 5; i >= 0; i--) {
      state.buffer[state.start++] = (rem / Math.pow(2, 8 * i)) & 0xff
    }
  }
}

function decode(state) {
  const max = 251

  if (state.end - state.start < 1) throw new Error('Out of bounds')

  const flag = state.buffer[state.start++]

  if (flag < max) return flag

  if (state.end - state.start < flag - max + 1) {
    throw new Error('Out of bounds.')
  }

  if (flag < 252) {
    return state.buffer[state.start++] + max
  }

  if (flag < 253) {
    return (state.buffer[state.start++] << 8) + state.buffer[state.start++] + max
  }

  if (flag < 254) {
    return (
      (state.buffer[state.start++] << 16) +
      (state.buffer[state.start++] << 8) +
      state.buffer[state.start++] +
      max
    )
  }

  // << 24 result may be interpreted as negative
  if (flag < 255) {
    return (
      state.buffer[state.start++] * 0x1000000 +
      (state.buffer[state.start++] << 16) +
      (state.buffer[state.start++] << 8) +
      state.buffer[state.start++] +
      max
    )
  }

  const exp = decode(state)

  if (state.end - state.start < 6) throw new Error('Out of bounds')

  let rem = 0
  for (let i = 5; i >= 0; i--) {
    rem += state.buffer[state.start++] * Math.pow(2, 8 * i)
  }

  return rem * Math.pow(2, exp - 11) + max
}
{
  "name": "compact-encoding",
  "version": "3.4.0",
  "description": "A series of compact encoding schemes for building small and fast parsers and serializers",
  "main": "index.js",
  "types": "index.d.ts",
  "files": [
    "endian.js",
    "endian.d.ts",
    "index.js",
    "index.d.ts",
    "lexint.js",
    "lexint.d.ts",
    "raw.js",
    "raw.d.ts"
  ],
  "dependencies": {
    "b4a": "^1.3.0"
  },
  "devDependencies": {
    "brittle": "^4.0.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^2.0.0"
  },
  "scripts": {
    "format": "prettier . --write",
    "test": "prettier . --check && brittle-node test.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/holepunchto/compact-encoding.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/compact-encoding/issues"
  },
  "homepage": "https://github.com/holepunchto/compact-encoding"
}
const b4a = require('b4a')

const { BE } = require('./endian')

exports = module.exports = {
  preencode(state, b) {
    state.end += b.byteLength
  },
  encode(state, b) {
    state.buffer.set(b, state.start)
    state.start += b.byteLength
  },
  decode(state) {
    const b = state.buffer.subarray(state.start, state.end)
    state.start = state.end
    return b
  }
}

const buffer = (exports.buffer = {
  preencode(state, b) {
    uint8array.preencode(state, b)
  },
  encode(state, b) {
    uint8array.encode(state, b)
  },
  decode(state) {
    const b = state.buffer.subarray(state.start)
    state.start = state.end
    return b
  }
})

exports.binary = {
  ...buffer,
  preencode(state, b) {
    if (typeof b === 'string') utf8.preencode(state, b)
    else buffer.preencode(state, b)
  },
  encode(state, b) {
    if (typeof b === 'string') utf8.encode(state, b)
    else buffer.encode(state, b)
  }
}

exports.arraybuffer = {
  preencode(state, b) {
    state.end += b.byteLength
  },
  encode(state, b) {
    const view = new Uint8Array(b)

    state.buffer.set(view, state.start)
    state.start += b.byteLength
  },
  decode(state) {
    const b = new ArrayBuffer(state.end - state.start)
    const view = new Uint8Array(b)

    view.set(state.buffer.subarray(state.start))

    state.start = state.end

    return b
  }
}

function typedarray(TypedArray, swap) {
  const n = TypedArray.BYTES_PER_ELEMENT

  return {
    preencode(state, b) {
      state.end += b.byteLength
    },
    encode(state, b) {
      const view = new Uint8Array(b.buffer, b.byteOffset, b.byteLength)

      if (BE && swap) swap(view)

      state.buffer.set(view, state.start)
      state.start += b.byteLength
    },
    decode(state) {
      let b = state.buffer.subarray(state.start)
      if (b.byteOffset % n !== 0) b = new Uint8Array(b)

      if (BE && swap) swap(b)

      state.start = state.end

      return new TypedArray(b.buffer, b.byteOffset, b.byteLength / n)
    }
  }
}

const uint8array = (exports.uint8array = typedarray(Uint8Array))
exports.uint16array = typedarray(Uint16Array, b4a.swap16)
exports.uint32array = typedarray(Uint32Array, b4a.swap32)

exports.int8array = typedarray(Int8Array)
exports.int16array = typedarray(Int16Array, b4a.swap16)
exports.int32array = typedarray(Int32Array, b4a.swap32)

exports.biguint64array = typedarray(BigUint64Array, b4a.swap64)
exports.bigint64array = typedarray(BigInt64Array, b4a.swap64)

exports.float32array = typedarray(Float32Array, b4a.swap32)
exports.float64array = typedarray(Float64Array, b4a.swap64)

function string(encoding) {
  return {
    preencode(state, s) {
      state.end += b4a.byteLength(s, encoding)
    },
    encode(state, s) {
      state.start += b4a.write(state.buffer, s, state.start, encoding)
    },
    decode(state) {
      const s = b4a.toString(state.buffer, encoding, state.start)
      state.start = state.end
      return s
    }
  }
}

const utf8 = (exports.string = exports.utf8 = string('utf-8'))
exports.ascii = string('ascii')
exports.hex = string('hex')
exports.base64 = string('base64')
exports.ucs2 = exports.utf16le = string('utf16le')

exports.array = function array(enc) {
  return {
    preencode(state, list) {
      for (const value of list) enc.preencode(state, value)
    },
    encode(state, list) {
      for (const value of list) enc.encode(state, value)
    },
    decode(state) {
      const arr = []
      while (state.start < state.end) arr.push(enc.decode(state))
      return arr
    }
  }
}

exports.json = {
  preencode(state, v) {
    utf8.preencode(state, JSON.stringify(v))
  },
  encode(state, v) {
    utf8.encode(state, JSON.stringify(v))
  },
  decode(state) {
    return JSON.parse(utf8.decode(state))
  }
}

exports.ndjson = {
  preencode(state, v) {
    utf8.preencode(state, JSON.stringify(v) + '\n')
  },
  encode(state, v) {
    utf8.encode(state, JSON.stringify(v) + '\n')
  },
  decode(state) {
    return JSON.parse(utf8.decode(state))
  }
}
const c = require('compact-encoding')
const b4a = require('b4a')

const KeyChain = require('./lib/keychain')
const { sign, verify, hash } = require('./lib/crypto')

const {
  ProofEncoding,
  AttestedData,
  AttestedDevice,
  ReceiptEncoding
} = require('./lib/encoding')

const PROOF_VERSION = 1
const KEET_TYPE = 5338

const NS_PROFILE_DISC_ENC = hash(b4a.from('profile discovery'))

module.exports = class IdentityKey {
  constructor (keyChain) {
    this.keyChain = keyChain

    this.identityKeyPair = this.keyChain.get(identityKeyPath(0))
    this.profileDiscoveryKeyPair = this.keyChain.get(discoveryCorePath(0))
  }

  static generateMnemonic () {
    return KeyChain.generateMnemonic()
  }

  static deriveSeed (mnemonic) {
    return KeyChain.deriveSeed(mnemonic)
  }

  get identityPublicKey () {
    return this.identityKeyPair.publicKey
  }

  get profileDiscoveryPublicKey () {
    return this.profileDiscoveryKeyPair.publicKey
  }

  getProfileDiscoveryEncryptionKey () {
    return this.keyChain.getSymmetricKey(encryptionKeyPath(NS_PROFILE_DISC_ENC))
  }

  getEncryptionKey (profileKey) {
    return this.keyChain.getSymmetricKey(encryptionKeyPath(profileKey))
  }

  bootstrap (device) {
    return IdentityKey.bootstrap({ identity: this.identityKeyPair }, device)
  }

  clear () {
    this.keyChain.clear()
    this.identityKeyPair.secretKey.fill(0)
    this.profileDiscoveryKeyPair.secretKey.fill(0)
  }

  static async bootstrap ({ identity, seed, mnemonic }, device) {
    if (!identity) {
      const identityPath = identityKeyPath(0) // accountIndex unused for now
      identity = await KeyChain.from({ seed, mnemonic }, identityPath)
    }

    const proof = {
      version: PROOF_VERSION,
      epoch: Date.now(),
      identity: identity.publicKey,
      chain: []
    }

    return IdentityKey.attestDevice(device, identity, proof)
  }

  static attestDevice (publicKey, parent, proof) {
    if (!proof) return IdentityKey.bootstrap({ identity: parent }, publicKey)

    if (b4a.isBuffer(proof)) {
      proof = c.decode(ProofEncoding, proof)
    }

    if (proof.version === 0) {
      throw new Error('Version 0 proofs are not supported')
    }

    const signable = c.encode(AttestedDevice, {
      epoch: proof.epoch,
      identity: proof.identity,
      device: publicKey
    })

    const signature = sign(signable, parent)

    proof.chain.push({ publicKey, signature })

    return c.encode(ProofEncoding, proof)
  }

  static attestData (attestedData, keyPair, proof) {
    if (attestedData === null) throw new Error('Data is not attestable')

    if (b4a.isBuffer(proof)) {
      proof = c.decode(ProofEncoding, proof)
    }

    if (proof && proof.version === 0) {
      throw new Error('Version 0 proofs are not supported')
    }

    if (!proof) {
      proof = {
        version: PROOF_VERSION,
        epoch: Date.now(),
        identity: keyPair.publicKey,
        chain: []
      }
    }

    const signable = c.encode(AttestedData, {
      epoch: proof.epoch,
      identity: proof.identity,
      data: hash(attestedData)
    })

    proof.data = { signature: sign(signable, keyPair) }

    return c.encode(ProofEncoding, proof)
  }

  static async from ({ seed, mnemonic }) {
    const keyChain = await KeyChain.from({ seed, mnemonic })

    return new IdentityKey(keyChain)
  }

  static verify (proof, attestedData, opts = {}) {
    if (b4a.isBuffer(proof)) {
      proof = c.decode(ProofEncoding, proof)
    }

    if (!validateProof(proof, attestedData, opts)) {
      return null
    }

    const { epoch, identity, chain } = proof

    const candidate = getLastKey(chain) || identity

    if (opts.expectedDevice) {
      if (!b4a.equals(candidate, opts.expectedDevice)) return null
    }

    let parent = identity

    // verify chain
    for (let i = 0; i < chain.length; i++) {
      const { publicKey, signature } = chain[i]

      const signable = c.encode(AttestedDevice, {
        epoch,
        identity,
        device: publicKey
      })

      if (!verify(signable, signature, parent)) {
        return null
      }

      parent = publicKey
    }

    if (proof.data) {
      const signable = c.encode(AttestedData, {
        epoch,
        identity,
        data: hash(attestedData)
      })

      if (!verify(signable, proof.data.signature, parent)) {
        return null
      }
    }

    const receipt = c.encode(ReceiptEncoding, { epoch })

    return {
      receipt,
      identityPublicKey: identity,
      devicePublicKey: candidate
    }
  }
}

function validateProof (proof, attestedData, opts = {}) {
  // version 0 is ignored
  if (proof.version === 0) return false

  // validate version
  if (proof.version > PROOF_VERSION) return false

  // verify epoch
  if (opts.receipt) {
    const { epoch } = c.decode(ReceiptEncoding, opts.receipt)
    if (proof.epoch < epoch) return false
  }

  // verify identity
  if (opts.expectedIdentity) {
    if (!b4a.equals(proof.identity, opts.expectedIdentity)) return false
  }

  return validateAttestedData(proof.data, attestedData)
}

function validateAttestedData (data, attestedData) {
  if (!data && attestedData) return false
  return true
}

function getLastKey (chain) {
  if (!chain.length) return null

  const last = chain[chain.length - 1]
  return last.publicKey
}

// slip48 derivations:
// https://github.com/satoshilabs/slips/blob/master/slip-0048.md

function keyPath (accountIndex, index) {
  // https://github.com/bitcoin/bips/blob/master/bip-0043.mediawiki
  const purpose = 48 // SLIP-48 wallet
  const role = 0 // owner

  return [purpose, KEET_TYPE, role, accountIndex, index]
}

// identityKey -> m/48'/keet'/0'/0'

function identityKeyPath (accountIndex) {
  return keyPath(accountIndex, 0)
}

// identityKey -> m/48'/keet'/0'/1'

function discoveryCorePath (accountIndex) {
  return keyPath(accountIndex, 1)
}

// slip21 derivations
// https://github.com/satoshilabs/slips/blob/master/slip-0021.md

function symmetricPath (...path) {
  const purpose = 'SLIP-0021' // SLIP-21 wallet
  const namespace = 'keet-identity-key' // keet

  return [purpose, namespace, ...path]
}

// encryptionKey  -> m/SLIP-10/keet-identity-key/account/"encryption key"

function encryptionKeyPath (profileKey) {
  return symmetricPath(b4a.toString(profileKey, 'hex'), 'encryption key')
}
const sodium = require('sodium-universal')
const b4a = require('b4a')

module.exports = {
  sign,
  verify,
  hash: blake2b
}

function sign (signable, keyPair) {
  const signature = b4a.alloc(sodium.crypto_sign_BYTES)
  sodium.crypto_sign_detached(signature, signable, keyPair.secretKey)

  return signature
}

function verify (signable, signature, publicKey) {
  return sodium.crypto_sign_verify_detached(signature, signable, publicKey)
}

function blake2b (data, output = b4a.alloc(32)) {
  sodium.crypto_generichash(output, data)
  return output
}
const c = require('compact-encoding')

const ATTESTED_DEVICE = 0
const ATTESTED_DATA = 1

const Epoch = {
  preencode (state, e) {
    c.uint64.preencode(state, Math.floor(e / 1000))
  },
  encode (state, e) {
    c.uint64.encode(state, Math.floor(e / 1000))
  },
  decode (state) {
    return c.uint64.decode(state) * 1000
  }
}

const AttestedDevice = {
  preencode (state, a) {
    c.uint8.preencode(state, ATTESTED_DEVICE)
    Epoch.preencode(state, a.epoch)
    c.fixed32.preencode(state, a.identity)
    c.fixed32.preencode(state, a.device)
  },
  encode (state, a) {
    c.uint8.encode(state, ATTESTED_DEVICE)
    Epoch.encode(state, a.epoch)
    c.fixed32.encode(state, a.identity)
    c.fixed32.encode(state, a.device)
  },
  decode (state) {
    throw new Error('Signed data should only be encoded')
  }
}

const AttestedData = {
  preencode (state, a) {
    c.uint8.preencode(state, ATTESTED_DATA)
    Epoch.preencode(state, a.epoch)
    c.fixed32.preencode(state, a.identity)
    c.fixed32.preencode(state, a.data)
  },
  encode (state, a) {
    c.uint8.encode(state, ATTESTED_DATA)
    Epoch.encode(state, a.epoch)
    c.fixed32.encode(state, a.identity)
    c.fixed32.encode(state, a.data)
  },
  decode (state) {
    throw new Error('Signed data should only be encoded')
  }
}

const DeviceAttestation = {
  preencode (state, a) {
    c.fixed32.preencode(state, a.publicKey)
    c.fixed64.preencode(state, a.signature)
  },
  encode (state, a) {
    c.fixed32.encode(state, a.publicKey)
    c.fixed64.encode(state, a.signature)
  },
  decode (state) {
    return {
      publicKey: c.fixed32.decode(state),
      signature: c.fixed64.decode(state)
    }
  }
}

const DeviceAttestationArray = c.array(DeviceAttestation)

const DataAttestation = {
  preencode (state, a) {
    c.fixed64.preencode(state, a.signature)
  },
  encode (state, a) {
    c.fixed64.encode(state, a.signature)
  },
  decode (state) {
    return {
      signature: c.fixed64.decode(state)
    }
  }
}

const ProofEncoding = {
  preencode (state, proof) {
    c.uint.preencode(state, proof.version)
    Epoch.preencode(state, proof.epoch)
    c.fixed32.preencode(state, proof.identity)
    DeviceAttestationArray.preencode(state, proof.chain)

    let flags = 0
    if (proof.data) flags |= 1
    c.uint.preencode(state, flags)

    if (proof.data) DataAttestation.preencode(state, proof.data)
  },
  encode (state, proof) {
    c.uint.encode(state, proof.version)
    Epoch.encode(state, proof.epoch)
    c.fixed32.encode(state, proof.identity)
    DeviceAttestationArray.encode(state, proof.chain)

    let flags = 0
    if (proof.data) flags |= 1
    c.uint.encode(state, flags)

    if (proof.data) DataAttestation.encode(state, proof.data)
  },
  decode (state) {
    const version = c.uint.decode(state)

    // ignore v0 proofs
    if (version === 0) {
      return {
        version: 0,
        epoch: 0,
        identity: null,
        chain: [],
        data: null
      }
    }

    const epoch = Epoch.decode(state)
    const identity = c.fixed32.decode(state)
    const chain = DeviceAttestationArray.decode(state)

    const flags = c.uint.decode(state)
    const data = flags & 1 ? DataAttestation.decode(state) : null

    return {
      version,
      epoch,
      identity,
      chain,
      data
    }
  }
}

const ReceiptEncoding = {
  preencode (state, receipt) {
    Epoch.preencode(state, receipt.epoch)
  },
  encode (state, receipt) {
    Epoch.encode(state, receipt.epoch)
  },
  decode (state) {
    return {
      epoch: Epoch.decode(state)
    }
  }
}

module.exports = {
  ATTESTED_DEVICE,
  ATTESTED_DATA,
  AttestedDevice,
  AttestedData,
  ProofEncoding,
  ReceiptEncoding
}
const sodium = require('sodium-universal')
const c = require('compact-encoding')
const assert = require('nanoassert')
const b4a = require('b4a')
const { HMAC } = require('sodium-hmac')
const bip39 = require('bip39-mnemonic')

const { sign } = require('./crypto')

const MASTER_CHAIN_CODE = b4a.from('ed25519 seed')
const MASTER_SYMMETRIC_CODE = b4a.from('Symmetric key seed')

const HARDENED_OFFSET = 0x80000000

class KeyChain {
  constructor (seed) {
    this.seed = seed

    this._buffer = b4a.alloc(64)

    this.chainKey = this._buffer.subarray(0, 32)
    this.chainCode = this._buffer.subarray(32)

    this.publicKey = b4a.alloc(sodium.crypto_sign_PUBLICKEYBYTES)
    this.secretKey = b4a.alloc(sodium.crypto_sign_SECRETKEYBYTES)

    this._initialise()
  }

  static generateMnemonic () {
    return bip39.generateMnemonic()
  }

  static deriveSeed (mnemonic) {
    return bip39.mnemonicToSeed(mnemonic)
  }

  static async from ({ mnemonic, seed }, path) {
    assert(mnemonic || seed, 'No mnemonic or seed was passed.')

    if (!seed) seed = await KeyChain.deriveSeed(mnemonic)

    const key = new KeyChain(seed)

    if (!path) return key

    return key.get(path)
  }

  getSymmetricKey (path) {
    const buffer = b4a.alloc(64)

    const chainKey = buffer.subarray(0, 32)
    const key = buffer.subarray(32)

    HMAC.sha512(this.seed, MASTER_SYMMETRIC_CODE, buffer)

    for (const step of path) {
      const label = b4a.alloc(step.length + 1)
      b4a.write(label, step, 1, 'utf8')

      HMAC.sha512(label, chainKey, buffer)
    }

    return key
  }

  get isKeychain () {
    return true
  }

  _initialise () {
    HMAC.sha512(this.seed, MASTER_CHAIN_CODE, this._buffer)
    sodium.crypto_sign_seed_keypair(this.publicKey, this.secretKey, this.chainKey)
  }

  clear () {
    this.secretKey.fill(0)
    this.chainKey.fill(0)
    this.chainCode.fill(0)
  }

  get (path) {
    return this.derive(path)
  }

  sign (signable) {
    return sign(signable, this)
  }

  derive (path) {
    const buffer = b4a.from(this._buffer)

    const chainKey = buffer.subarray(0, 32)
    const chainCode = buffer.subarray(32)

    for (const step of path) {
      const index = ensureHardened(step) // hardened indices are >= 2^31
      HMAC.sha512(encodeDerivationData(chainKey, index), chainCode, buffer)
    }

    return createKeyPair(chainKey)
  }
}

module.exports = KeyChain

function createKeyPair (seed) {
  if (!seed) throw new Error('No seed provided')

  const publicKey = b4a.alloc(sodium.crypto_sign_PUBLICKEYBYTES)
  const secretKey = b4a.alloc(sodium.crypto_sign_SECRETKEYBYTES)

  sodium.crypto_sign_seed_keypair(publicKey, secretKey, seed)

  return {
    publicKey,
    secretKey
  }
}

function encodeDerivationData (key, step) {
  const state = { buffer: b4a.alloc(37), start: 0, end: 37 }

  c.uint8.encode(state, 0)
  c.fixed32.encode(state, key)
  state.buffer.writeUInt32BE(step, state.start)

  state.buffer[33] |= 0x80

  return state.buffer
}

function ensureHardened (n) {
  if (n >= HARDENED_OFFSET) return n
  return n + HARDENED_OFFSET
}
{
  "name": "keet-identity-key",
  "version": "3.2.0",
  "description": "Hierarchical Deterministic Keys for Keet Identities",
  "main": "index.js",
  "scripts": {
    "test": "standard && brittle test/*.js"
  },
  "author": "Holepunch Inc.",
  "license": "Apache-2.0",
  "devDependencies": {
    "brittle": "^3.3.2",
    "hypercore-crypto": "^3.4.0",
    "standard": "^17.1.0"
  },
  "dependencies": {
    "b4a": "^1.6.4",
    "bip39-mnemonic": "^2.2.0",
    "compact-encoding": "^3.0.0",
    "nanoassert": "^2.0.0",
    "sodium-hmac": "^2.0.1",
    "sodium-universal": "^5.0.1"
  },
  "files": [
    "lib/*"
  ],
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/keet-identity-key.git"
  },
  "bugs": {
    "url": "https://github.com/holepunchto/keet-identity-key/issues"
  },
  "homepage": "https://github.com/holepunchto/keet-identity-key#readme"
}
{
  "name": "favela-identity",
  "version": "0.0.2",
  "description": "favela's identity primitives. Attestation today; the account layer (a User is durable, a DID is one credential, a bridge is a resource) as it lands.",
  "type": "module",
  "license": "MIT",
  "main": "index.js",
  "exports": {
    ".": "./index.js",
    "./lib/attestation.js": "./lib/attestation.js",
    "./lib/peer-client.js": "./lib/peer-client.js"
  },
  "scripts": {
    "test": "brittle-bare test/*.test.js"
  },
  "dependencies": {
    "b4a": "^1.6.0",
    "keet-identity-key": "3.2.0",
    "compact-encoding": "^3.3.0",
    "bare-fs": "^4.0.0",
    "bare-path": "^3.0.0",
    "hypercore-crypto": "^3.4.0",
    "bare-net": "^1.3.0"
  },
  "devDependencies": {
    "brittle": "^3.5.0"
  },
  "imports": {
    "#node:fs": {
      "bare": "bare-fs",
      "default": "fs"
    },
    "#node:path": {
      "bare": "bare-path",
      "default": "path"
    }
  }
}
// The pinned dialect, in ONE place.
//
// markdown-strip.md §2.1 pins CommonMark 0.31.2 plus exactly six extensions, and the set is CLOSED:
// syntax outside it is literal text, not markup. Both `strip()` (→ text + facets, for bridges) and
// `render()` (→ HTML, for our own surfaces) must parse identically — if they drift, the same post
// says two different things depending on who is looking, which is the whole failure the pinned
// dialect exists to prevent. So the config and the lexical rules live here and are imported, never
// copied.

import { gfmStrikethrough } from 'micromark-extension-gfm-strikethrough'
import { gfmStrikethroughFromMarkdown } from 'mdast-util-gfm-strikethrough'
import { gfmTable } from 'micromark-extension-gfm-table'
import { gfmTableFromMarkdown } from 'mdast-util-gfm-table'
import { gfmFootnote } from 'micromark-extension-gfm-footnote'
import { gfmFootnoteFromMarkdown } from 'mdast-util-gfm-footnote'
import { directive } from 'micromark-extension-directive'
import { directiveFromMarkdown } from 'mdast-util-directive'
import { parseUri } from './uri.js'

export const MICROMARK = {
  // ⛔ singleTilde:false — micromark defaults it TRUE, which would consume `~x~` as strikethrough.
  extensions: [gfmStrikethrough({ singleTilde: false }), gfmTable(), gfmFootnote(), directive()],
  mdastExtensions: [
    gfmStrikethroughFromMarkdown(),
    gfmTableFromMarkdown(),
    gfmFootnoteFromMarkdown(),
    directiveFromMarkdown()
  ]
}

// §3.3 NORMATIVE — favela-web's shipped URL_RE verbatim. Changing it changes records.
export const URL_RE = /\bhttps?:\/\/[^\s<]+[^\s<.,:;!?)\]}'"]/g
export const WIKILINK_RE = /\[\[([^\][|]+)(?:\|([^\][]*))?\]\]/g
export const HASHTAG_RE = /(^|\s)(#([A-Za-z0-9_À-￿]{1,64}))/g

// §3.2 — only http/https may carry a link. Everything else renders as text.
export const isHttp = (uri) => /^https?:\/\//i.test(String(uri || ''))
// §3.4 — a mention names a USER: `favela://<identity-root>`; `did:` is the legacy form.
//
// ⛔ NARROWED. This was bare `/^(favela:\/\/|did:)/`, so it matched EVERY favela URI — including
// `favela://blob/<oid>` (worked around below by telling every caller to test for a blob first) and
// `favela://<root>/<collection>/<name>`, which was NOT worked around because the record form was
// never specified. favela-surface emits one for a quote (router.js:291), so a quote was classified
// as a mention of a user whose identity was actually a feed key. See lib/uri.js.
//
// A mention is now the USER form specifically: a favela URI with no path, or a `did:`.
export const isMention = (uri) => {
  const s = String(uri || '')
  if (/^did:/i.test(s)) return true
  const u = parseUri(s)
  return !!u && u.kind === 'user'
}

// record-vocabulary.md §3.5 — an embedded file: `favela://blob/<oid>`, where `<oid>` is the
// hyper-overlay content hash. A record never contains media; it names a file the overlay holds.
//
// ⛔ This form ALSO matches `isMention`, which is `^favela://`. Every caller must test for a blob
// FIRST — otherwise an embed renders as a mention span and the picture silently disappears. That
// ordering is not a style choice, it is the difference between an image and nothing.
export const BLOB_RE = /^favela:\/\/blob\/([0-9a-f]{8,128})$/i
export const isBlob = (uri) => BLOB_RE.test(String(uri || ''))
export const blobOid = (uri) => {
  const m = BLOB_RE.exec(String(uri || ''))
  return m ? m[1].toLowerCase() : null
}
// levels.js — the mip pyramid. Pure: no I/O, no clock, no throw.
//
// ⛔ IT LIVES HERE BECAUSE IT IS RECORD VOCABULARY. `spec/record-vocabulary.md` §3.5 defines the
// pointer; the peer writes one, and BOTH bridges read one to choose a level. A copy per consumer is
// how this monorepo ended up with six copies of one signing format, three of which ate the first
// character of a hash — and a ladder that drifts between the writer and the reader is worse than
// that, because each side stays internally consistent while disagreeing about what a level means.
//
// ⛔ THE PYRAMID IS THE OBJECT (SOCIAL-MODEL §6.6.1). A texture in a game is not a file plus a set
// of related smaller files; it is one asset containing a pyramid, and the consumer picks a level by
// how much of the screen it covers. Media works the same way here. There is no `variant-of` edge
// because the levels were never separate objects — the relation is the structure of the asset, not
// something asserted and then maintained.
//
// ⛔ AND IT SIDESTEPS THE OVERLAY'S LIMIT RATHER THAN FIGHTING IT. hyper-overlay addresses a file by
// a whole-file hash, so a prefix cannot be authenticated and sparse fetch of ONE blob is impossible.
// A pyramid needs no prefix: every level is a whole file with its own oid, which is exactly what the
// overlay already does well. Fetch the pointer, choose a level, fetch that oid. Sparseness BY LEVEL.
//
// ⛔ BRIDGES SELECT, THEY DO NOT DOWNSAMPLE. Encoding happens once, at authoring, deterministically —
// like generating mips at asset build time — instead of per delivery on every bridge. `selectLevel`
// is that choice, and it reports which level it picked so the loss is never silent.
//
// ⚠ Cost is the mip cost and it is bounded: 1 + ¼ + 1/16 + … = 4/3 of the base. Known in advance,
// unlike an open-ended set of variants.

// ── the ladders ───────────────────────────────────────────────────────────────────────────────
//
// ⚠ Levels are chosen against REAL NETWORK CEILINGS, not powers of two (§6.6.1, "where the analogy
// breaks" #1). bsky takes 2,000,000 bytes for a post image and 1,000,000 for a profile picture;
// Mastodon resizes on receipt and is not the binding constraint. The edges below bracket those.
export const IMAGE_LEVELS = [
  { name: 'full', edge: 2000, targetBytes: 2000000 },
  { name: 'large', edge: 1280, targetBytes: 1000000 },
  { name: 'small', edge: 640, targetBytes: 300000 },
  { name: 'thumb', edge: 320, targetBytes: 80000 }
]

// The bitrate ladder IS a mip chain for video — hypernote's HLS_LEVELS, which the spec names as the
// one we already ship. Same shape, same never-upscale filter.
export const VIDEO_LEVELS = [
  { name: '1080p', edge: 1080, bitrate: '5000k', maxrate: '6000k', profile: 'high' },
  { name: '720p', edge: 720, bitrate: '2500k', maxrate: '3000k', profile: 'main' },
  { name: '480p', edge: 480, bitrate: '1000k', maxrate: '1200k', profile: 'baseline' }
]

export const laddersFor = (mimeType) =>
  /^video\//.test(String(mimeType || '')) ? VIDEO_LEVELS
    : /^image\//.test(String(mimeType || '')) ? IMAGE_LEVELS
      : []

/**
 * Which levels should be GENERATED for a source asset.
 *
 * ⛔ NEVER UPSCALE (§6.6.1 #2). A level is a floor, not a guarantee: an asset smaller than the
 * lowest level has exactly one level, and the pointer must not imply levels it lacks. hypernote
 * already encodes this by filtering `l.height <= srcHeight`; this is the same filter, stated once.
 *
 * The source itself is always level 0 — the author's own bytes, unaltered.
 */
export function planLevels ({ mimeType = '', width = 0, height = 0 } = {}) {
  const ladder = laddersFor(mimeType)
  if (!ladder.length) return []
  const longest = Math.max(Number(width) || 0, Number(height) || 0)
  // For video the ladder is keyed on HEIGHT, which is how every encoder ladder is expressed.
  const source = /^video\//.test(mimeType) ? (Number(height) || 0) : longest
  if (!source) return []
  return ladder.filter((l) => l.edge < source)
}

/**
 * Scale a source to a level's edge, preserving aspect. Whole pixels, never zero.
 */
export function scaleTo ({ width, height }, edge) {
  const w = Number(width) || 0
  const h = Number(height) || 0
  const longest = Math.max(w, h)
  if (!longest || edge >= longest) return { width: w, height: h }
  const s = edge / longest
  return { width: Math.max(1, Math.round(w * s)), height: Math.max(1, Math.round(h * s)) }
}

// ── the pointer ───────────────────────────────────────────────────────────────────────────────
//
// ⛔ THERE IS NO MANIFEST FILE (record-vocabulary §3.5). The pointer carries the levels — one
// pointer, N oids, zero extra artifacts. A manifest file would be a third artifact for a job the
// pointer already does, and the only per-asset duplicate in a design whose whole point is that
// identical bytes are stored once however many records reference them.
//
//     { mimeType, size, levels: [ { oid, w, h, size, by }, … ] }
//
// ⛔ `by` IS LOAD-BEARING, not a nicety (§6.6.4 #2/#3). A pyramid is allowed to be PARTIAL, and a
// level may be derived later by a bridge or a well-resourced peer. Deriving is not authoring: a peer
// adding a level has produced a derived artifact and must say so, or an author's bytes and somebody
// else's re-encode become indistinguishable. §8's "never re-encode a record you did not author"
// already cost two of wj62's posts.
export const AUTHOR = 'author'
export const derivedBy = (who) => 'derived:' + String(who || 'unknown').slice(0, 32)

export function makePointer ({ mimeType, size, levels = [] }) {
  return {
    mimeType: String(mimeType || 'application/octet-stream'),
    size: Number(size) || 0,
    // Largest first, so "the biggest that fits" is a scan from the front and the base is level 0.
    levels: [...levels].sort((a, b) => (b.w * b.h) - (a.w * a.h)).map((l) => ({
      oid: l.oid, w: Number(l.w) || 0, h: Number(l.h) || 0, size: Number(l.size) || 0, by: l.by || AUTHOR
    }))
  }
}

/**
 * The level nearest a destination's ceiling. `{ level, reason }` — level is null when none fits.
 *
 * ⛔ A CLEAR ABSENCE, NEVER A 404 (§6.6.4 #1). "This asset has no level under your ceiling" and "the
 * fetch failed" are different facts and a consumer must be able to tell them apart, so this returns
 * a reason rather than nothing.
 *
 * ⚠ It reports what it picked even on success, because §6.6.1 makes that the honesty requirement:
 * a bridge that quietly serves a smaller picture has altered what the author published.
 */
export function selectLevel (pointer, { maxBytes = Infinity, maxEdge = Infinity } = {}) {
  const levels = (pointer && Array.isArray(pointer.levels)) ? pointer.levels : []
  if (!levels.length) return { level: null, reason: 'this asset has no levels' }
  for (const l of levels) {
    const edge = Math.max(l.w || 0, l.h || 0)
    if ((l.size || 0) <= maxBytes && (!edge || edge <= maxEdge)) {
      const isBase = l === levels[0]
      return {
        level: l,
        reason: isBase ? 'full size' : 'level ' + edge + 'px (' + l.size + ' bytes) chosen for a ceiling of ' + maxBytes
      }
    }
  }
  const smallest = levels[levels.length - 1]
  return {
    level: null,
    reason: 'no level fits — the smallest is ' + (smallest.size || 0) + ' bytes against a ceiling of ' + maxBytes
  }
}

export default { IMAGE_LEVELS, VIDEO_LEVELS, planLevels, scaleTo, makePointer, selectLevel, AUTHOR, derivedBy }
// maxReach — the lattice. library.md §6.1, record-vocabulary.md §5.2, interaction.md §5.2
//
// ⛔ THIS IS THE ONE PLACE A DEFECT IS A PRIVACY BUG rather than a rendering bug (library §6.1).
// Written here once, this check would otherwise be written three times — favela-web,
// hyper-proto-peer, ap-bridge — and the third one would eventually be written wrong.
//
// Everything is deny-by-default. Unknown room type, unknown verb, missing argument, hostile input:
// the answer is {favela}, which is not a restriction but a fact — "every post is local; the
// canonical record always exists on the P2P side; that is what authoring one means"
// (record-vocabulary §5.4). Local is the substrate, not a scope you opt into.
//
// SIGNATURE CHANGED 2026-09-05. library.md §6.1 signs this `maxReach(visibility)`, from when
// visibility was a frontmatter field. Visibility is now a PLACE — the room you wrote in
// (SOCIAL-MODEL §4) — so the inputs are the room's kind and the record's verb. This library has no
// storage and cannot inspect a room, so the CALLER supplies the kind. That is honest about the
// library boundary (§1: no storage, no network) rather than pretending to knowledge it lacks.

export const NETWORKS = ['favela', 'bsky', 'fediverse']

// What each kind of room can be expressed as, on each network.
//
//   public    → everywhere. AS2 Public; an atproto repo is world-readable by construction.
//   followers → the fediverse ONLY. AP has addressing, so a followers room maps onto the followers
//               collection. ⛔ atproto has NO addressing at all (record-vocabulary §5.2), so a
//               projection it cannot restrict is a leak, and the only safe answer is to withhold.
//   members   → favela only. AS2 *can* address actors individually, but an arbitrary room's
//   dm          membership is not expressible as an audience a receiving server will honour, and
//               approximating it is how a private post reaches a stranger. Fails closed.
const ROOM_REACH = {
  public: ['favela', 'bsky', 'fediverse'],
  followers: ['favela', 'fediverse'],
  members: ['favela'],
  dm: ['favela']
}

// What each verb can be expressed as. interaction.md §5.4 (the signed set) and §5.5 (native).
// ⛔ A verb absent from this table reaches {favela} and nothing else — that IS the extension
// mechanism (interaction §5.2): a native-only verb cannot fork a signed projection, because it
// never reaches a shared derivation. Adding a verb here is a spec change, not a plugin point.
const VERB_REACH = {
  post: ['favela', 'bsky', 'fediverse'],
  reply: ['favela', 'bsky', 'fediverse'],
  like: ['favela', 'bsky', 'fediverse'],
  repost: ['favela', 'bsky', 'fediverse'],
  follow: ['favela', 'bsky', 'fediverse'],
  // AS2 `Block` exists but Mastodon does not federate it, so it projects with a loss entry rather
  // than being withheld — the receiving server simply does nothing with it.
  block: ['favela', 'bsky', 'fediverse'],
  // AS2 `Flag` is a REPORT to the origin's moderators, a different act from a shared signal, so
  // conflating them would misrepresent the moderator (SOCIAL-MODEL §6.7). atproto has a real
  // equivalent — a signed com.atproto.label, which we already emit.
  flag: ['favela', 'bsky'],
  // Internal: how a node knows two names, or two byte-sets, denote one thing. No projection
  // anywhere, by design (interaction §5.5, record-vocabulary §3.5).
  alias: ['favela'],
  'variant-of': ['favela']
}

const LOCAL = () => new Set(['favela'])

/**
 * maxReach({ roomType, verb }) -> Set<network>
 *
 * The MEET of what the room can express and what the verb can express. TOTAL: never throws, and
 * always contains 'favela'.
 */
export function maxReach (opts) {
  const roomType = opts && typeof opts.roomType === 'string' ? opts.roomType : null
  const verb = opts && typeof opts.verb === 'string' ? opts.verb : null

  // Exact match only. 'PUBLIC' and 'public ' are NOT 'public': normalising input here would mean a
  // typo could widen an audience, which is precisely the class of bug §6.1 exists to prevent.
  const room = (roomType && Object.prototype.hasOwnProperty.call(ROOM_REACH, roomType))
    ? ROOM_REACH[roomType]
    : null
  const verbs = (verb && Object.prototype.hasOwnProperty.call(VERB_REACH, verb))
    ? VERB_REACH[verb]
    : null

  if (!room || !verbs) return LOCAL()

  const out = new Set()
  for (const n of room) if (verbs.includes(n)) out.add(n)
  out.add('favela') // §5.4 — local is the substrate; there is no way to opt out of it
  return out
}

export default { maxReach, NETWORKS }
// parse / serialize — the canonical favela record.
//
// A record is ONE FILE: a JSON object inside a `---` fence, a blank line, then the markdown body
// (design.md §5). JSON-in-a-fence was chosen over YAML because `audience: no` parsing as `false` is
// a privacy bug, and over a sidecar because one durable human-readable artifact is the property
// worth keeping.
//
// THE ROUND-TRIP LAW IS WHY THIS FILE IS CAREFUL. library.md §10.4: `migrateInline` leans on
// DAG-CBOR being canonical so a rewrite is byte-identical, and ASSERTS it — skipping any key whose
// re-encoded oid differs rather than republishing changed content. Markdown gets no such guarantee
// for free, so `serialize(parse(x)) === x` has to be engineered: key order, indentation, the blank
// line, the trailing newline. Each is pinned below and each is a vector in test/record.test.js.
//
// The law holds "for canonical input" (§7). serialize() emits ONE canonical form, so
// serialize(parse(x)) NORMALISES anything else. That is the useful direction — a record that has
// been through the codec once is stable forever after, which is exactly what migrateInline needs.

// §3.2 — the pinned field order. Unknown keys follow, sorted, so an extension can be added without
// reordering (and therefore without changing the bytes of) any record that predates it: design.md
// §1.1's extensibility property, enforced here rather than remembered.
export const CANONICAL_KEYS = ['createdAt', 'langs', 'reply', 'reach']

const FENCE = '---'
const INDENT = 2 // matches design.md §5's example; pinned because it is part of the bytes

// Order an object's keys canonically: pinned ones first in their fixed order, then the rest sorted.
function canonicalise (obj) {
  const out = {}
  for (const k of CANONICAL_KEYS) if (k in obj) out[k] = obj[k]
  for (const k of Object.keys(obj).sort()) if (!(k in out)) out[k] = obj[k]
  return out
}

/**
 * parse(md) -> { frontmatter, body, publishable, errors }
 *
 * TOTAL — never throws (§2). Anything unparseable degrades rather than failing.
 */
export function parse (md) {
  const src = String(md == null ? '' : md).replace(/\r\n?/g, '\n') // §5.1, on the input
  const errors = []

  // The frontmatter is delimited by the FIRST fence pair only. A `---` later in the body is a
  // thematic break and must not truncate the record — otherwise a post about markdown loses half
  // of itself, silently.
  if (!src.startsWith(FENCE + '\n')) {
    if (src.trim() !== '') errors.push({ code: 'no-frontmatter', message: 'no leading --- fence' })
    return { frontmatter: {}, body: src, publishable: false, errors }
  }

  const close = src.indexOf('\n' + FENCE + '\n', FENCE.length)
  const closeAtEnd = src.endsWith('\n' + FENCE) ? src.length - FENCE.length - 1 : -1
  const end = close >= 0 ? close : closeAtEnd
  if (end < 0) {
    errors.push({ code: 'unterminated-frontmatter', message: 'no closing --- fence' })
    return { frontmatter: {}, body: src, publishable: false, errors }
  }

  const raw = src.slice(FENCE.length + 1, end)
  const after = close >= 0 ? src.slice(end + FENCE.length + 2) : ''
  // Exactly one blank line separates the fence from the body in canonical form; tolerate its
  // absence on the way IN (serialize puts it back) so a hand-written file still reads.
  const body = after.startsWith('\n') ? after.slice(1) : after

  let frontmatter = {}
  try {
    const v = JSON.parse(raw)
    if (v && typeof v === 'object' && !Array.isArray(v)) frontmatter = v
    else errors.push({ code: 'frontmatter-not-object', message: 'frontmatter must be a JSON object' })
  } catch (err) {
    errors.push({ code: 'frontmatter-invalid-json', message: err.message })
  }

  // §6.2 ⛔ — a record whose frontmatter does not parse is readable, renderable and NOT publishable.
  // It degrades to local-only, NEVER to Public. This is the one place a defect is a privacy bug, so
  // it fails closed: no frontmatter means nothing asserts anything, including who may see it.
  const publishable = errors.length === 0 && typeof frontmatter.createdAt === 'string'
  if (errors.length === 0 && !publishable) {
    errors.push({ code: 'no-created-at', message: 'createdAt is required to publish' })
  }

  return { frontmatter, body, publishable, errors }
}

/**
 * serialize({ frontmatter, body }) -> md
 *
 * Emits the ONE canonical form. Every byte below is pinned by a test.
 */
export function serialize (record) {
  const fm = canonicalise((record && record.frontmatter) || {})
  let body = String((record && record.body) || '')

  // Exactly one trailing newline. Pinned because "no trailing newline" and "two" are both stable
  // states a text editor will produce, and either would make the law fail for reasons no author
  // could see.
  body = body.replace(/\n+$/, '') + '\n'

  return FENCE + '\n' + JSON.stringify(fm, null, INDENT) + '\n' + FENCE + '\n\n' + body
}

export default { parse, serialize, CANONICAL_KEYS }
// render(markdown) -> HTML — the NATIVE projection.
//
// `strip()` is for bridges: it throws formatting away to fit networks that have none. This is the
// opposite, and it is what our own surfaces show. Both parse through `dialect.js` so they can never
// disagree about what the markup means.
//
// ⛔ This is the one function in the package where a defect is an XSS rather than a rendering bug,
// so it is fail-closed in the same way `reach.js` is for privacy:
//   - every text node and every attribute value is escaped;
//   - only http/https may carry an `href` or `src` — anything else renders as its label, or is
//     dropped, and NEVER becomes a link;
//   - raw HTML in the source is emitted as escaped text, never as markup.
//
// ★ Note the spoiler asymmetry, which is the thesis in one node type: `:::spoiler` is REVEALED by
// strip (atproto has no such concept) and CONCEALED here. Same record, two projections, and only
// one of them is lossy — which is why `library.md` §5 asks the composer to say so.

import { fromMarkdown } from 'mdast-util-from-markdown'
import { MICROMARK, WIKILINK_RE, HASHTAG_RE, isHttp, isMention, blobOid } from './dialect.js'

const ESC = { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }
const esc = (s) => String(s == null ? '' : s).replace(/[&<>"']/g, (c) => ESC[c])

// Text runs get the two LEXICAL rules the dialect owns (§3.6, §3.7) — wiki-links and hashtags are
// not structural, so micromark never sees them and they are applied here, after escaping, so a page
// name or a tag can never inject markup.
function inlineText (raw) {
  let out = esc(raw)
  out = out.replace(WIKILINK_RE, (_m, page, label) =>
    '<span class="wikilink" data-page="' + esc(page) + '">' + esc(label !== undefined ? label : page) + '</span>')
  out = out.replace(HASHTAG_RE, (_m, lead, whole, tag) =>
    lead + '<span class="hashtag" data-tag="' + esc(tag) + '">' + esc(whole) + '</span>')
  return out
}

const children = (node, o) => (node.children || []).map((c) => toHtml(c, o)).join('')

/**
 * The images in a paragraph that contains NOTHING ELSE, or null.
 *
 * ⛔ "Nothing else" is strict: any text with content, any link, any video, and this is prose with a
 * picture in it rather than a row of pictures. Grouping those would silently restructure someone's
 * writing, which is a much worse failure than not grouping.
 *
 * Whitespace between images does not count — a composer appending embeds produces exactly that.
 */
function imageRun (node, o) {
  if (!node || node.type !== 'paragraph') return null
  const out = []
  for (const c of node.children || []) {
    if (c.type === 'image') {
      const b = blob(o, c.url || '')
      // ⛔ Only resolved IMAGES group. A video has controls and its own aspect; an unresolved embed
      // renders as nothing and would leave a hole in the grid.
      if (!b || !b.mimeType.startsWith('image/')) return null
      out.push(c)
    } else if (c.type === 'text' && !String(c.value || '').trim()) {
      continue
    } else {
      return null
    }
  }
  return out.length ? out : null
}

/**
 * Walk the top level, folding RUNS of image-only paragraphs into one lighttable.
 *
 * ⛔ A run, not a paragraph: the composer appends one embed per attachment separated by blank lines,
 * so three attachments are three paragraphs and grouping per-paragraph would do nothing at all —
 * which is exactly what it did before this.
 *
 * ⚠ A single image is left alone. One picture is not a grid, and wrapping it in one would change
 * how every existing post renders for no gain.
 */
function topLevel (node, o) {
  const kids = node.children || []
  const out = []
  let i = 0
  while (i < kids.length) {
    const run = []
    let j = i
    for (;;) {
      const imgs = imageRun(kids[j], o)
      if (!imgs) break
      run.push(...imgs)
      j++
    }
    if (run.length > 1) {
      out.push('<div class="lighttable">' + run.map((n) => image(n, o)).join('') + '</div>')
      i = j
    } else {
      out.push(toHtml(kids[i], o))
      i++
    }
  }
  return out.join('')
}

/**
 * An embedded file: `favela://blob/<oid>` (record-vocabulary.md §3.5).
 *
 * ⛔ The library has NO OPINION about the URL scheme. `resolveBlob(oid)` belongs to the surface —
 * an HTTP surface returns a loopback path, a native one returns whatever it can display — and this
 * only escapes what comes back. Baking `http` in here would make a pure library assume a browser,
 * and the surfaces are not all browsers. The bytes themselves never move over HTTP: they arrive
 * P2P through hyper-overlay, and the surface is only saying where it can read them from.
 *
 * ⛔ It is NOT run through `isHttp`. That allowlist guards AUTHOR-supplied URLs, which are the
 * untrusted input it exists for; a resolver is the surface's own code and no author can influence
 * what it returns.
 *
 * Absent resolver, unknown oid, or a type this element cannot show ⇒ nothing, the same fail-closed
 * posture the rest of this file has.
 */
function blob (o, url) {
  const oid = blobOid(url)
  if (!oid) return null
  const r = o && typeof o.resolveBlob === 'function' ? o.resolveBlob(oid) : null
  if (!r || typeof r.src !== 'string' || r.src === '') return null
  return { src: r.src, mimeType: typeof r.mimeType === 'string' ? r.mimeType : '' }
}

function link (node, o) {
  const url = node.url || ''
  // ⛔ BLOB FIRST. `favela://blob/<oid>` also matches `isMention` (which is bare `^favela://`), so
  // testing mentions first turns every embed into a mention span and the file silently vanishes.
  const b = blob(o, url)
  if (b) {
    // A PDF is shown inline because a browser already knows how; anything else is a link. Either
    // way the bytes came P2P and the surface's resolver said where to read them.
    if (b.mimeType === 'application/pdf') {
      return '<embed class="doc" type="application/pdf" src="' + esc(b.src) + '">'
    }
    return '<a class="file" href="' + esc(b.src) + '">' + children(node, o) + '</a>'
  }
  // A mention is OURS to resolve, so it never becomes an href — a browser cannot follow favela://
  // and a surface should not be tempted to try. The target rides in a data attribute.
  if (isMention(url)) {
    return '<span class="mention" data-target="' + esc(url) + '">' + children(node) + '</span>'
  }
  // §3.2 — anything not http/https degrades to its label with NO link. That covers javascript:,
  // data:, vbscript: and every scheme nobody has invented yet, because it is an allowlist.
  if (!isHttp(url)) return children(node)
  return '<a href="' + esc(url) + '" rel="noopener noreferrer nofollow">' + children(node) + '</a>'
}

function image (node, o) {
  const alt = esc(node.alt || '')
  const b = blob(o, node.url || '')
  if (b) {
    if (b.mimeType.startsWith('image/')) {
      // ⛔ A PREVIEW, wrapped in a link to the file itself. An embed rendered at its natural size
      // takes over a timeline — a 4000px photo is not a post, it is a wall — but a preview that
      // cannot be opened has thrown the picture away. The surface constrains `.embed` and the
      // anchor gives the full file back, with no script involved.
      return '<a class="lightbox" href="' + esc(b.src) + '">' +
        '<img class="embed" loading="lazy" src="' + esc(b.src) + '" alt="' + alt + '">' +
        '</a>'
    }
    // ⛔ `![]()` on a video is deliberate, not a mistake: §3.5 has the embed pipeline consuming
    // images in source order, and a video is the same authoring gesture with different bytes.
    if (b.mimeType.startsWith('video/')) {
      return '<video class="embed" controls preload="metadata" src="' + esc(b.src) + '"></video>'
    }
    // A file that is neither is not an image, whatever the author wrote. Dropped, not guessed.
    return ''
  }
  if (!isHttp(node.url || '')) return '' // dropped entirely — an <img> with no usable src is noise
  return '<img src="' + esc(node.url) + '" alt="' + alt + '">'
}

// :::spoiler / :::cw <text> / :::details — the pinned set (library.md §5).
function directive (node, o) {
  const name = node.name || ''
  const inner = children(node, o)
  if (name === 'spoiler') return '<details class="spoiler"><summary>spoiler</summary>' + inner + '</details>'
  if (name === 'cw') {
    const label = (node.attributes && node.attributes.label) || textOf(node.children && node.children[0]) || 'content warning'
    return '<details class="cw"><summary>' + esc(label) + '</summary>' + inner + '</details>'
  }
  if (name === 'details') return '<details>' + inner + '</details>'
  // An unknown directive contributes its CONTENTS, matching strip §4.8 — the dialect is closed, so
  // an unrecognised name is not markup and must not vanish.
  return inner
}

const textOf = (n) => (n && n.type === 'paragraph' ? (n.children || []).map((c) => c.value || '').join('') : '')

function toHtml (node, o) {
  if (!node || typeof node !== 'object') return ''
  switch (node.type) {
    case 'root': return topLevel(node, o)
    case 'paragraph': {
      // ⛔ A paragraph that renders to nothing IS nothing. `image()` drops an unresolvable embed
      // deliberately — an <img> with no usable src is noise — but the paragraph around it used to
      // render anyway, so a post whose only content was a picture produced `<p></p>` and every
      // consumer showed a blank line. Measured against a live feed 2026-09-07.
      const inner = children(node, o)
      return inner.trim() ? '<p>' + inner + '</p>' : ''
    }
    case 'text': return inlineText(node.value)
    case 'strong': return '<strong>' + children(node, o) + '</strong>'
    case 'emphasis': return '<em>' + children(node, o) + '</em>'
    case 'delete': return '<del>' + children(node, o) + '</del>'
    case 'inlineCode': return '<code>' + esc(node.value) + '</code>'
    case 'break': return '<br>'
    case 'thematicBreak': return '<hr>'
    case 'link': return link(node, o)
    case 'image': return image(node, o)
    case 'heading': {
      const d = Math.min(Math.max(node.depth || 1, 1), 6)
      return '<h' + d + '>' + children(node, o) + '</h' + d + '>'
    }
    case 'blockquote': return '<blockquote>' + children(node, o) + '</blockquote>'
    case 'list': return node.ordered ? '<ol>' + children(node, o) + '</ol>' : '<ul>' + children(node, o) + '</ul>'
    case 'listItem': {
      const inner = (node.children || []).map((c) =>
        c.type === 'paragraph' ? children(c, o) : toHtml(c, o)).join('')
      return '<li>' + inner + '</li>'
    }
    case 'code':
      return '<pre><code' + (node.lang ? ' class="language-' + esc(node.lang) + '"' : '') + '>' +
        esc(node.value) + '</code></pre>'
    case 'table': return '<table>' + children(node, o) + '</table>'
    case 'tableRow': {
      const head = node.__head === true
      const cells = (node.children || []).map((c) =>
        head ? '<th>' + children(c, o) + '</th>' : '<td>' + children(c, o) + '</td>').join('')
      return '<tr>' + cells + '</tr>'
    }
    case 'tableCell': return children(node, o)
    case 'footnoteReference': return '<sup class="fnref">' + esc(node.identifier) + '</sup>'
    case 'footnoteDefinition': return '<div class="fn" id="fn-' + esc(node.identifier) + '">' + children(node, o) + '</div>'
    case 'containerDirective':
    case 'leafDirective':
    case 'textDirective': return directive(node, o)
    // ⛔ Raw HTML is emitted as TEXT. The dialect does not include HTML, and rendering it would hand
    // markup authorship to whoever wrote the record.
    case 'html': return esc(node.value)
    default: return children(node, o)
  }
}

/**
 * render(source, { resolveBlob }) -> html
 *
 * `resolveBlob(oid) -> { src, mimeType } | null` is how a surface says where a `favela://blob/<oid>`
 * can be read from and what it is. Omit it and every embed renders as nothing, which is correct for
 * a caller that cannot serve files.
 *
 * TOTAL — never throws. Anything unparseable degrades to escaped text, the same posture `strip` has,
 * because a surface that throws on one bad record shows nothing at all.
 */
export function render (source, opts = {}) {
  const src = String(source == null ? '' : source).replace(/\r\n?/g, '\n')
  let tree
  try {
    tree = fromMarkdown(src, MICROMARK)
  } catch {
    return '<p>' + esc(src) + '</p>'
  }
  // mdast marks the header row only by position, so tag it before the walk.
  for (const node of tree.children || []) {
    if (node.type === 'table' && node.children && node.children[0]) node.children[0].__head = true
  }
  try { return toHtml(tree, opts) } catch { return '<p>' + esc(src) + '</p>' }
}

export default render

/**
 * embeds(source) -> [{ oid, alt, kind }] — every `favela://blob/<oid>` a record references.
 *
 * ⛔ Uses the SAME parse `render` does, not a regular expression. A regex would match a blob URI
 * inside a code fence or an escaped bracket and report a reference the renderer will never make —
 * and the two disagreeing about what a record references is how a surface fetches the wrong files.
 *
 * `kind` is the authoring gesture: `image` for `![…]()`, `link` for `[…]()`. Source order, because
 * §3.5 has the embed pipeline consuming them in it.
 *
 * TOTAL — never throws; an unparseable record has no embeds.
 */
const labelOf = (n) => (n.children || []).map((c) => c.value || labelOf(c)).join('')

export function embeds (source) {
  const src = String(source == null ? '' : source).replace(/\r\n?/g, '\n')
  let tree
  try { tree = fromMarkdown(src, MICROMARK) } catch { return [] }

  const out = []
  const walk = (node) => {
    if (!node || typeof node !== 'object') return
    if (node.type === 'image' || node.type === 'link') {
      const oid = blobOid(node.url || '')
      if (oid) {
        out.push({
          oid,
          // ⛔ Not `textOf`, which only walks a paragraph — a link's label is its own children.
          alt: node.type === 'image' ? (node.alt || '') : labelOf(node),
          kind: node.type === 'image' ? 'image' : 'link'
        })
      }
    }
    for (const c of node.children || []) walk(c)
  }
  try { walk(tree) } catch { return out }
  return out
}
// strip(source) -> { text, facets }  —  spec/markdown-strip.md
//
// The strip's output is what bsky sees and what a CID commits to, so every rule here cites the
// spec section it implements and nothing is left to "whatever the parser does" (§1). The conformance
// vectors are the oracle; this file exists to reproduce them byte-for-byte.
//
// Structure, and why:
//
//   1. CRLF normalisation happens on the INPUT, before parsing (§5.1).
//   2. Each block renders into its OWN buffer so an empty block can be omitted along with its
//      separator (§4.1) — you cannot decide that after concatenating.
//   3. Facets are recorded as CHARACTER ranges while building, then mapped through normalisation,
//      then converted to UTF-8 BYTE offsets last (§6.4: offsets are computed against the FINAL
//      text, never an intermediate). Doing it in this order is what makes the multibyte vectors
//      pass; computing byte offsets early and patching them later is the defect §8 exists to catch.
//   4. Normalisation is expressed as deletions with an index map, so a facet's range can be
//      carried across it exactly rather than re-derived by searching the text.
//
// Extensions: micromark supplies the four that change block/inline STRUCTURE (GFM strikethrough,
// GFM tables, footnotes, directives). Wiki-links and hashtags are handled here because the spec
// gives them as lexical rules over text (§3.6, §3.7) — a normative regex in the hashtag case —
// so there is no structural ambiguity to get wrong.

import { isUserUri } from './uri.js'
import { fromMarkdown } from 'mdast-util-from-markdown'
import { gfmStrikethrough } from 'micromark-extension-gfm-strikethrough'
import { gfmStrikethroughFromMarkdown } from 'mdast-util-gfm-strikethrough'
import { gfmTable } from 'micromark-extension-gfm-table'
import { gfmTableFromMarkdown } from 'mdast-util-gfm-table'
import { gfmFootnote } from 'micromark-extension-gfm-footnote'
import { gfmFootnoteFromMarkdown } from 'mdast-util-gfm-footnote'
import { directive } from 'micromark-extension-directive'
import { directiveFromMarkdown } from 'mdast-util-directive'

const MICROMARK = {
  // ⛔ singleTilde:false — micromark defaults it TRUE, which would consume `~x~` as strikethrough.
  // §2.1 pins the extension as `~~x~~`, and the dialect is CLOSED: syntax outside it is text, not
  // markup. Caught by the `single-tilde-is-literal` vector, which exists because of that rule.
  extensions: [gfmStrikethrough({ singleTilde: false }), gfmTable(), gfmFootnote(), directive()],
  mdastExtensions: [
    gfmStrikethroughFromMarkdown(),
    gfmTableFromMarkdown(),
    gfmFootnoteFromMarkdown(),
    directiveFromMarkdown()
  ]
}

// §3.3 — NORMATIVE. This is favela-web's shipped URL_RE verbatim; changing it changes records.
// The trailing class is what keeps "see https://x.com." from swallowing the full stop.
const URL_RE = /\bhttps?:\/\/[^\s<]+[^\s<.,:;!?)\]}'"]/g

// §3.6 — [[Page]] and [[Page|label]].
const WIKILINK_RE = /\[\[([^\][|]+)(?:\|([^\][]*))?\]\]/g

// §3.7 — '#' at start-of-text or after whitespace, then 1-64 of [A-Za-z0-9_] plus any non-ASCII
// letter. A '#' inside a word, or one followed only by digits, is literal text.
const HASHTAG_RE = /(^|\s)(#([A-Za-z0-9_À-￿]{1,64}))/g

// §3.2 — only http/https may carry a link facet. Mention schemes are handled separately (§3.4);
// anything else emits its label as plain text with no facet.
const isHttp = (uri) => /^https?:\/\//i.test(String(uri || ''))

// §3.4 — a mention names a USER. `favela://<identity-root>` is the canonical form; `did:` is the
// LEGACY form kept because §8 is forward-only and nothing is rewritten. strip() does NOT resolve
// either: it is a codec with no registry access, and resolution to a bsky DID belongs at
// PROJECTION time (identity.md §5). So the facet carries the target exactly as written.
// ⛔ NARROWED, and this is the copy that matters most: strip() produces the FACETS a bridge sends.
// The old test was bare `^(favela://|did:)`, so a record URI — `favela://<root>/<collection>/<name>`,
// which favela-surface emits for a quote — became a MENTION facet, and every bridge then tried to
// resolve a record address as if it were a person. A mention is the USER form: no path, or a `did:`.
//
// ⚠ This was a second, divergent copy of dialect.js's predicate. Two definitions of one normative
// rule is how they drift; it delegates now.
const isMention = (uri) => {
  const s = String(uri || '')
  return /^did:/i.test(s) || isUserUri(s)
}

// §6 — the NATIVE facet feature: { type, value }, the same compact form the vectors are written in.
//
// This used to be app.bsky.richtext.facet shape ($type / features[] / index{}), which put atproto's
// data format in the core library's OUTPUT — the inherited-shape problem one level below the one
// design.md §1 caught, and flagged as such by markdown-strip.md §3.4. It is gone (MVP.md §4
// decision 3): a mention's target is a favela:// URI, and it travelled in a field named `did`.
// project(rec, 'bsky') is the one place that knows atproto's names; nothing else may.
const linkFeature = (uri) => ({ type: 'link', value: uri })
const mentionFeature = (target) => ({ type: 'mention', value: target })
const tagFeature = (tag) => ({ type: 'tag', value: tag })

// A block's rendered text plus the facets found inside it, at character offsets relative to
// this block. Kept per-block so §4.1 can drop an empty block and its separator together.
class Buf {
  constructor () {
    this.text = ''
    this.marks = [] // { start, end, feature } — character offsets into this.text
  }

  get length () { return this.text.length }

  add (s) { this.text += s }

  mark (start, end, feature) {
    if (end > start) this.marks.push({ start, end, feature })
  }

  // Append another buffer's text and marks, shifting the marks by the current length.
  append (other) {
    const base = this.text.length
    this.text += other.text
    for (const m of other.marks) {
      this.marks.push({ start: m.start + base, end: m.end + base, feature: m.feature })
    }
  }
}

// ── inline ────────────────────────────────────────────────────────────────────

// §3.6 / §3.7 / §3.3 — lexical passes over a plain text run, in one left-to-right sweep so the
// three can never produce overlapping facets (§6.2 would otherwise have to arbitrate).
function addTextRun (buf, raw) {
  // §3.8 — a SOFT line break arrives as "\n" inside a text node and becomes a single space.
  // A HARD break is a separate 'break' node (below) and stays a newline, so the distinction is
  // made here by node type rather than by trying to remember which newlines were which after
  // the text is assembled. One character in, one character out, so no offset shifts.
  const soft = raw.replace(/\n/g, ' ')

  // Wiki-links next: they rewrite the text, and §3.6 emits no facet, so doing them before the
  // facet-bearing scans keeps every offset below correct without any later adjustment.
  const s = soft.replace(WIKILINK_RE, (_m, page, label) => (label !== undefined ? label : page))

  const found = []
  for (const m of s.matchAll(URL_RE)) {
    found.push({ start: m.index, end: m.index + m[0].length, feature: linkFeature(m[0]) })
  }
  for (const m of s.matchAll(HASHTAG_RE)) {
    // m[1] is the leading boundary (start-of-text or the whitespace); the facet covers '#tag'
    // INCLUDING the hash (§3.7: "the hash is kept in the text"), while the value drops it.
    const start = m.index + m[1].length
    found.push({ start, end: start + m[2].length, feature: tagFeature(m[3]) })
  }
  found.sort((a, b) => a.start - b.start || b.end - a.end)

  const base = buf.length
  let taken = -1
  for (const f of found) {
    if (f.start < taken) continue // overlapping match: the earlier/longer one already won (§6.2)
    buf.mark(base + f.start, base + f.end, f.feature)
    taken = f.end
  }
  buf.add(s)
}

function inline (nodes, buf) {
  for (const node of nodes || []) {
    switch (node.type) {
      case 'text':
        addTextRun(buf, node.value)
        break

      // §3.1 — markers removed, inner text kept verbatim, NO facet (atproto has no bold).
      case 'emphasis':
      case 'strong':
      case 'delete':
        inline(node.children, buf)
        break

      // §3.1 — a code span is text, not markup. Its value is literal, so no lexical scan:
      // a URL inside backticks was written as code and must not become a link.
      case 'inlineCode':
        buf.add(node.value)
        break

      case 'link': {
        const uri = node.url
        const start = buf.length
        const inner = new Buf()
        inline(node.children, inner)

        if (isMention(uri)) {
          // §3.4 — a mention target names the user, not a network identifier. The label is kept.
          buf.append(inner)
          buf.mark(start, buf.length, mentionFeature(uri))
        } else if (isHttp(uri)) {
          if (inner.length === 0) {
            // §3.2 — an empty label emits the URL as text, with a facet over it.
            buf.add(uri)
            buf.mark(start, buf.length, linkFeature(uri))
          } else {
            buf.append(inner)
            buf.mark(start, buf.length, linkFeature(uri))
          }
        } else {
          // §3.2 ⛔ — non-http (javascript:, etc.) emits the label as PLAIN TEXT, no facet.
          // inner.marks are dropped deliberately: a refused link must not smuggle one out.
          buf.add(inner.text)
        }
        break
      }

      // §3.5 — images produce no text and no facet. The embed pipeline consumes them separately.
      case 'image':
      case 'imageReference':
        break

      // §3.9 — a footnote reference is removed entirely.
      case 'footnoteReference':
        break

      // §3.8 — a hard break is one newline. (A soft break arrives as "\n" inside a text node
      // and is turned into a space by the block-level collapse below.)
      case 'break':
        buf.add('\n')
        break

      // §3.10 — raw HTML is emitted as its literal source text, unparsed, with no facet.
      case 'html':
        buf.add(node.value)
        break

      case 'linkReference':
        // An unresolved reference has no target, so there is nothing to point a facet at:
        // it degrades to its label, like a wiki-link (§3.6).
        inline(node.children, buf)
        break

      default:
        if (node.children) inline(node.children, buf)
        else if (typeof node.value === 'string') buf.add(node.value)
    }
  }
}

function paragraphText (children) {
  const buf = new Buf()
  inline(children, buf)
  return buf
}

// ── blocks ────────────────────────────────────────────────────────────────────

function listBlock (node, depth) {
  const buf = new Buf()
  const indent = '  '.repeat(depth) // §4.5 — nested lists indent two spaces per level
  let n = typeof node.start === 'number' ? node.start : 1
  let first = true

  for (const item of node.children || []) {
    if (item.type !== 'listItem') continue
    // §4.5 — ordered items use the RENDERED sequence, not the literal source numbers.
    const marker = node.ordered ? n + '. ' : '• '
    if (node.ordered) n++

    const inner = new Buf()
    let firstChild = true
    for (const child of item.children || []) {
      const sub = child.type === 'list' ? listBlock(child, depth + 1) : blockBuf(child, depth)
      if (sub.length === 0) continue
      if (!firstChild) inner.add('\n')
      inner.append(sub)
      firstChild = false
    }
    if (inner.length === 0) continue

    if (!first) buf.add('\n')
    buf.add(indent + marker)
    buf.append(inner)
    first = false
  }
  return buf
}

function tableBlock (node) {
  const buf = new Buf()
  let firstRow = true
  for (const row of node.children || []) {
    if (row.type !== 'tableRow') continue
    if (!firstRow) buf.add('\n')
    let firstCell = true
    for (const cell of row.children || []) {
      if (!firstCell) buf.add(' | ') // §4.6 — cells joined with ' | ', outer pipes dropped
      buf.append(paragraphText(cell.children))
      firstCell = false
    }
    firstRow = false
  }
  return buf
}

// Render one block node into its own buffer. Returns an empty buffer for anything §4 omits.
function blockBuf (node, depth = 0) {
  switch (node.type) {
    case 'paragraph':
      return paragraphText(node.children)

    // §4.2 — ATX and setext both reduce to the heading text; no extra spacing, no case change.
    case 'heading':
      return paragraphText(node.children)

    // §4.3 — contents verbatim: no fence, no info string, no re-indent. Not scanned for
    // hashtags or URLs, for the same reason as an inline code span.
    case 'code': {
      const buf = new Buf()
      buf.add(String(node.value || '').replace(/\n+$/, ''))
      return buf
    }

    // §4.4 — the marker is removed and nested quotes flatten. No '> ' prefix is invented.
    case 'blockquote': {
      const buf = new Buf()
      let first = true
      for (const child of node.children || []) {
        const sub = blockBuf(child, depth)
        if (sub.length === 0) continue
        if (!first) buf.add('\n\n')
        buf.append(sub)
        first = false
      }
      return buf
    }

    case 'list':
      return listBlock(node, depth)

    case 'table':
      return tableBlock(node)

    // §4.7 — a thematic break is omitted entirely, including its separator.
    case 'thematicBreak':
      return new Buf()

    // §3.9 — the definition block is dropped along with its reference.
    case 'footnoteDefinition':
      return new Buf()

    // §4.8 — a directive contributes its CONTENTS; the fences and the name are removed.
    case 'containerDirective':
    case 'leafDirective':
    case 'textDirective': {
      const buf = new Buf()
      let first = true
      for (const child of node.children || []) {
        const sub = blockBuf(child, depth)
        if (sub.length === 0) continue
        if (!first) buf.add('\n\n')
        buf.append(sub)
        first = false
      }
      return buf
    }

    // §3.10 — block HTML is its literal source text.
    case 'html': {
      const buf = new Buf()
      buf.add(String(node.value || ''))
      return buf
    }

    default: {
      if (node.children) {
        const buf = new Buf()
        let first = true
        for (const child of node.children) {
          const sub = blockBuf(child, depth)
          if (sub.length === 0) continue
          if (!first) buf.add('\n\n')
          buf.append(sub)
          first = false
        }
        return buf
      }
      const buf = new Buf()
      if (typeof node.value === 'string') buf.add(node.value)
      return buf
    }
  }
}

// ── normalisation (§5) ────────────────────────────────────────────────────────

// Every §5 rule is a DELETION, so normalising is a filter that also yields old->new index map.
// Carrying marks across the map is exact; re-finding them in the output would not be.
function normalise (text) {
  const keep = []
  const map = new Array(text.length + 1).fill(-1)

  // §5.2 trailing whitespace per line, §5.3 3+ newlines -> 2, §5.4 outer trim.
  const lines = []
  let lineStart = 0
  for (let i = 0; i <= text.length; i++) {
    if (i === text.length || text[i] === '\n') {
      lines.push([lineStart, i])
      lineStart = i + 1
    }
  }

  let newlineRun = 0
  let emitted = false
  for (let li = 0; li < lines.length; li++) {
    const [s, e] = lines[li]
    let end = e
    while (end > s && (text[end - 1] === ' ' || text[end - 1] === '\t')) end-- // §5.2

    const blank = end === s
    if (blank) { newlineRun++; continue }

    if (emitted) {
      // §5.3 — a run of blank lines collapses so that at most two newlines survive.
      keep.push(-1)
      if (newlineRun > 0) keep.push(-1)
    }
    newlineRun = 0
    for (let i = s; i < end; i++) keep.push(i)
    emitted = true
  }

  let out = ''
  for (const idx of keep) {
    if (idx === -1) { out += '\n'; continue }
    map[idx] = out.length
    out += text[idx]
  }
  map[text.length] = out.length

  // ⛔ TWO maps, because a deleted character means OPPOSITE things at each end of a range.
  //
  // Only KEPT characters were mapped above; every character §5 deleted — the newlines a block
  // separator is emitted from, trailing whitespace, collapsed blank lines — still has an index a
  // facet range may land on. A facet ending exactly at a block boundary has its `end` on such a
  // character. Leaving it unmapped DROPPED the facet; mapping it forward to the next kept character
  // made the facet SWALLOW the separator (byteEnd 8 where 6 was correct). Both were wrong, in
  // opposite directions, which is the tell that one map cannot serve both ends.
  //
  //   startMap  a deleted start moves FORWARD to the next kept character — the range begins at
  //             real content rather than in a gap.
  //   endMap    a deleted end stays BEHIND, just past the last kept character before it — the
  //             range ends where its content ended, not where the next one begins.
  //
  // Caught by `facet-at-end-of-block`: no vector had a facet at the end of a non-final block, and
  // the fuzzer only generates single-block input. The CLI found it on its first real file.
  const startMap = new Array(text.length + 1)
  const endMap = new Array(text.length + 1)

  startMap[text.length] = out.length
  for (let i = text.length - 1; i >= 0; i--) {
    startMap[i] = map[i] >= 0 ? map[i] : startMap[i + 1]
  }
  endMap[0] = 0
  for (let i = 1; i <= text.length; i++) {
    endMap[i] = map[i - 1] >= 0 ? map[i - 1] + 1 : endMap[i - 1]
  }

  // §5.4 — leading whitespace of the first kept line is not removed by the per-line pass above
  // (only trailing is), so trim the assembled result and shift both maps to match.
  const lead = out.length - out.replace(/^\s+/, '').length
  if (lead > 0) {
    out = out.slice(lead)
    for (let i = 0; i < startMap.length; i++) {
      startMap[i] = Math.max(0, startMap[i] - lead)
      endMap[i] = Math.max(0, endMap[i] - lead)
    }
  }
  const trimmed = out.replace(/\s+$/, '')
  if (trimmed.length !== out.length) {
    for (let i = 0; i < startMap.length; i++) {
      if (startMap[i] > trimmed.length) startMap[i] = trimmed.length
      if (endMap[i] > trimmed.length) endMap[i] = trimmed.length
    }
    out = trimmed
  }

  return { text: out, startMap, endMap }
}

const byteLen = (s) => {
  let n = 0
  for (let i = 0; i < s.length; i++) {
    const c = s.codePointAt(i)
    if (c > 0xffff) i++
    n += c < 0x80 ? 1 : c < 0x800 ? 2 : c < 0x10000 ? 3 : 4
  }
  return n
}

// ── the strip ─────────────────────────────────────────────────────────────────

export function strip (source) {
  // §2 — total: never throws. Anything unparseable degrades to its literal text.
  let src
  try {
    src = String(source == null ? '' : source).replace(/\r\n?/g, '\n') // §5.1, on the INPUT
  } catch {
    return { text: '', facets: [] }
  }

  let tree
  try {
    tree = fromMarkdown(src, MICROMARK)
  } catch {
    return { text: src, facets: [] }
  }

  // §4.1 — blocks joined with exactly "\n\n"; a block producing empty text is omitted along
  // with its separator, which is why each renders into its own buffer first.
  const doc = new Buf()
  let first = true
  try {
    for (const node of tree.children || []) {
      const b = blockBuf(node, 0)
      if (b.length === 0) continue
      if (!first) doc.add('\n\n')
      doc.append(b)
      first = false
    }
  } catch {
    return { text: '', facets: [] }
  }

  const { text, startMap, endMap } = normalise(doc.text)

  // §6.4 — offsets are computed against the FINAL text, and only now converted to bytes.
  const facets = []
  for (const m of doc.marks) {
    const s = startMap[m.start]
    const e = endMap[m.end]
    if (s === undefined || e === undefined || s < 0 || e < 0 || e <= s) continue // §6.3
    // §6.5 — exactly one feature per facet. Flat, so a multi-feature facet is not expressible
    // rather than being forbidden by a rule someone has to remember.
    facets.push({
      byteStart: byteLen(text.slice(0, s)),
      byteEnd: byteLen(text.slice(0, e)),
      type: m.feature.type,
      value: m.feature.value
    })
  }

  // §6.1 sorted by byteStart; §6.2 on a tie the longer wins, then mention > link > tag.
  const rank = (f) => (f.type === 'mention' ? 0 : f.type === 'link' ? 1 : 2)
  facets.sort((a, b) =>
    a.byteStart - b.byteStart ||
    b.byteEnd - a.byteEnd ||
    rank(a) - rank(b))

  // §6.2 — no overlaps: the winner of the sort above keeps its range, later overlaps are dropped.
  const out = []
  let last = -1
  for (const f of facets) {
    if (f.byteStart < last) continue
    out.push(f)
    last = f.byteEnd
  }
  return { text, facets: out }
}

export default strip
// uri.js — the `favela://` scheme.
//
// ⛔ WHY THIS FILE EXISTS. `isMention` was `/^(favela:\/\/|did:)/` — it matched ANY favela URI. The
// blob form was noticed and worked around by ORDERING: dialect.js:43 says "every caller must test
// for a blob FIRST — otherwise an embed renders as a mention span and the picture silently
// disappears". The RECORD form was never specified, so nobody worked around it, and
// `favela://<x>/<collection>/<name>` — which favela-surface already emits for a quote
// (router.js:291) — was classified as a mention of a user. A quote rendered as a mention of nobody.
//
// A scheme with three forms needs a parse, not a chain of predicates in the right order.
//
// ── The authority (hyper-social #61 §12 #1) ───────────────────────────────────────────────────
//
// The authority was TWO things at once. A mention carries an IDENTITY ROOT (dialect.js:37,
// favela-peer/lib/mentions.js:116). The quote path carries a FEED KEY. Both are 64 hex characters,
// so no reader could tell which it had.
//
// ★ IT IS THE IDENTITY ROOT. One space, everywhere:
//
//   1. The mention form already uses it, and those records are written. Changing that would move
//      every existing mention.
//   2. `NAMING.md` §3 — a name resolves to an identity ROOT, and only then to a room key.
//   3. hyper-social #64 §1 — handle claims anchor to the root. pgh shipped the device-key version
//      first and needed `pgh names reanchor --all --force` to repair it; favela stored something
//      worse still (a manifest-derived writer key, which can never sign).
//   4. #61 §2a — deriving an address from a rotatable locator is the exact failure `favela://`
//      exists to prevent. `roomKey = hash(NS('room-key') || did)` broke because a DID can rotate.
//
// A FEED KEY IS A LOCATOR, not an identity: it says where bytes are, and #61 §12 #3 leaves oplog
// stability under `setLocal` writer rotation unverified. The root says who. Same split as a record's
// name versus its oplog — identity is not the same question as where to fetch.
//
// Resolution root → feed already exists: `profiles()` returns feed → root for feeds you hold, and a
// handle claim binds root → feed and REPLICATES, which is the namespace layer's whole job.
//
// ⚠ PERMISSIVE WHEN READING, STRICT WHEN WRITING. `parseUri` does not police the authority's shape,
// because it must read bytes already written — including a conformance vector that mentions
// `favela://abc123`. The 64-hex requirement is enforced in `recordUri`, where we mint.

const PREFIX = 'favela://'
const HEX64 = /^[0-9a-f]{64}$/i
const OID = /^[0-9a-f]{8,128}$/i
// One path segment, matching favela-peer's `isSafeSeg`: no separator, not a dot entry.
const isSeg = (s) => typeof s === 'string' && s.length > 0 && !s.includes('/') && s !== '.' && s !== '..'

/**
 * Parse a favela URI into exactly one of three kinds, or null.
 *
 * ⛔ BY SHAPE, never by call order:
 *   favela://blob/<oid>                      → blob   (authority is the literal "blob")
 *   favela://<root>                          → user   (no path)
 *   favela://<root>/<collection>/<name>      → record (two path segments)
 *
 * Anything else is null. A URI we cannot classify must not be guessed at — rendering an
 * unrecognised form as the nearest familiar one is how the picture disappeared.
 */
export function parseUri (uri) {
  const s = String(uri == null ? '' : uri)
  if (!s.toLowerCase().startsWith(PREFIX)) return null

  const rest = s.slice(PREFIX.length)
  if (!rest) return null
  const parts = rest.split('/')

  if (parts[0].toLowerCase() === 'blob') {
    if (parts.length !== 2 || !OID.test(parts[1])) return null
    return { kind: 'blob', root: null, collection: null, name: null, oid: parts[1].toLowerCase() }
  }

  if (parts.length === 1) {
    return { kind: 'user', root: parts[0], collection: null, name: null, oid: null }
  }

  if (parts.length === 3) {
    if (!isSeg(parts[1]) || !isSeg(parts[2])) return null
    return { kind: 'record', root: parts[0], collection: parts[1], name: parts[2], oid: null }
  }

  return null
}

export const isUserUri = (uri) => { const u = parseUri(uri); return !!u && u.kind === 'user' }
export const isRecordUri = (uri) => { const u = parseUri(uri); return !!u && u.kind === 'record' }
export const isBlobUri = (uri) => { const u = parseUri(uri); return !!u && u.kind === 'blob' }

export const userUri = (root) => PREFIX + String(root)
export const blobUri = (oid) => PREFIX + 'blob/' + String(oid).toLowerCase()

/**
 * Mint a record URI.
 *
 * ⛔ STRICT ABOUT THE AUTHORITY, deliberately. This is the door a feed key would otherwise walk
 * through — favela-surface's quote path was putting one here — and once a URI is written into a
 * record's bytes it cannot be corrected, because SOCIAL-MODEL §8 forbids rewriting a record you did
 * not author. Refusing at mint time is the only place this can be enforced.
 */
export function recordUri ({ root, collection, name }) {
  if (!HEX64.test(String(root || ''))) {
    throw new Error('recordUri: the authority must be a 64-hex identity root, not a feed key')
  }
  if (!isSeg(collection) || !isSeg(name)) {
    throw new Error('recordUri: collection and name must each be one path segment')
  }
  return PREFIX + String(root).toLowerCase() + '/' + collection + '/' + name
}

export default { parseUri, isUserUri, isRecordUri, isBlobUri, userUri, recordUri, blobUri }
/**
 * Count how often a character (or substring) is used in a string.
 *
 * @param {string} value
 *   Value to search in.
 * @param {string} character
 *   Character (or substring) to look for.
 * @return {number}
 *   Number of times `character` occurred in `value`.
 */
export function ccount(value, character) {
  const source = String(value)

  if (typeof character !== 'string') {
    throw new TypeError('Expected character')
  }

  let count = 0
  let index = source.indexOf(character)

  while (index !== -1) {
    count++
    index = source.indexOf(character, index + character.length)
  }

  return count
}
{
  "name": "ccount",
  "version": "2.0.1",
  "description": "Count how often a character (or substring) is used in a string",
  "license": "MIT",
  "keywords": [
    "character",
    "count",
    "char"
  ],
  "repository": "wooorm/ccount",
  "bugs": "https://github.com/wooorm/ccount/issues",
  "funding": {
    "type": "github",
    "url": "https://github.com/sponsors/wooorm"
  },
  "author": "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)",
  "contributors": [
    "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)"
  ],
  "sideEffects": false,
  "type": "module",
  "main": "index.js",
  "types": "index.d.ts",
  "files": [
    "index.d.ts",
    "index.js"
  ],
  "devDependencies": {
    "@types/tape": "^4.0.0",
    "c8": "^7.0.0",
    "prettier": "^2.0.0",
    "remark-cli": "^10.0.0",
    "remark-preset-wooorm": "^9.0.0",
    "rimraf": "^3.0.0",
    "tape": "^5.0.0",
    "type-coverage": "^2.0.0",
    "typescript": "^4.0.0",
    "xo": "^0.46.0"
  },
  "scripts": {
    "prepublishOnly": "npm run build && npm run format",
    "build": "rimraf \"*.d.ts\" && tsc && type-coverage",
    "format": "remark . -qfo && prettier . -w --loglevel warn && xo --fix",
    "test-api": "node --conditions development test.js",
    "test-coverage": "c8 --check-coverage --branches 100 --functions 100 --lines 100 --statements 100 --reporter lcov npm run test-api",
    "test": "npm run build && npm run format && npm run test-coverage"
  },
  "prettier": {
    "tabWidth": 2,
    "useTabs": false,
    "singleQuote": true,
    "bracketSpacing": false,
    "semi": false,
    "trailingComma": "none"
  },
  "xo": {
    "prettier": true
  },
  "remarkConfig": {
    "plugins": [
      "preset-wooorm"
    ]
  },
  "typeCoverage": {
    "atLeast": 100,
    "detail": true,
    "strict": true,
    "ignoreCatch": true
  }
}
/**
 * Map of named character references from HTML 4.
 *
 * @type {Record<string, string>}
 */
export const characterEntitiesHtml4 = {
  nbsp: ' ',
  iexcl: '¡',
  cent: '¢',
  pound: '£',
  curren: '¤',
  yen: '¥',
  brvbar: '¦',
  sect: '§',
  uml: '¨',
  copy: '©',
  ordf: 'ª',
  laquo: '«',
  not: '¬',
  shy: '­',
  reg: '®',
  macr: '¯',
  deg: '°',
  plusmn: '±',
  sup2: '²',
  sup3: '³',
  acute: '´',
  micro: 'µ',
  para: '¶',
  middot: '·',
  cedil: '¸',
  sup1: '¹',
  ordm: 'º',
  raquo: '»',
  frac14: '¼',
  frac12: '½',
  frac34: '¾',
  iquest: '¿',
  Agrave: 'À',
  Aacute: 'Á',
  Acirc: 'Â',
  Atilde: 'Ã',
  Auml: 'Ä',
  Aring: 'Å',
  AElig: 'Æ',
  Ccedil: 'Ç',
  Egrave: 'È',
  Eacute: 'É',
  Ecirc: 'Ê',
  Euml: 'Ë',
  Igrave: 'Ì',
  Iacute: 'Í',
  Icirc: 'Î',
  Iuml: 'Ï',
  ETH: 'Ð',
  Ntilde: 'Ñ',
  Ograve: 'Ò',
  Oacute: 'Ó',
  Ocirc: 'Ô',
  Otilde: 'Õ',
  Ouml: 'Ö',
  times: '×',
  Oslash: 'Ø',
  Ugrave: 'Ù',
  Uacute: 'Ú',
  Ucirc: 'Û',
  Uuml: 'Ü',
  Yacute: 'Ý',
  THORN: 'Þ',
  szlig: 'ß',
  agrave: 'à',
  aacute: 'á',
  acirc: 'â',
  atilde: 'ã',
  auml: 'ä',
  aring: 'å',
  aelig: 'æ',
  ccedil: 'ç',
  egrave: 'è',
  eacute: 'é',
  ecirc: 'ê',
  euml: 'ë',
  igrave: 'ì',
  iacute: 'í',
  icirc: 'î',
  iuml: 'ï',
  eth: 'ð',
  ntilde: 'ñ',
  ograve: 'ò',
  oacute: 'ó',
  ocirc: 'ô',
  otilde: 'õ',
  ouml: 'ö',
  divide: '÷',
  oslash: 'ø',
  ugrave: 'ù',
  uacute: 'ú',
  ucirc: 'û',
  uuml: 'ü',
  yacute: 'ý',
  thorn: 'þ',
  yuml: 'ÿ',
  fnof: 'ƒ',
  Alpha: 'Α',
  Beta: 'Β',
  Gamma: 'Γ',
  Delta: 'Δ',
  Epsilon: 'Ε',
  Zeta: 'Ζ',
  Eta: 'Η',
  Theta: 'Θ',
  Iota: 'Ι',
  Kappa: 'Κ',
  Lambda: 'Λ',
  Mu: 'Μ',
  Nu: 'Ν',
  Xi: 'Ξ',
  Omicron: 'Ο',
  Pi: 'Π',
  Rho: 'Ρ',
  Sigma: 'Σ',
  Tau: 'Τ',
  Upsilon: 'Υ',
  Phi: 'Φ',
  Chi: 'Χ',
  Psi: 'Ψ',
  Omega: 'Ω',
  alpha: 'α',
  beta: 'β',
  gamma: 'γ',
  delta: 'δ',
  epsilon: 'ε',
  zeta: 'ζ',
  eta: 'η',
  theta: 'θ',
  iota: 'ι',
  kappa: 'κ',
  lambda: 'λ',
  mu: 'μ',
  nu: 'ν',
  xi: 'ξ',
  omicron: 'ο',
  pi: 'π',
  rho: 'ρ',
  sigmaf: 'ς',
  sigma: 'σ',
  tau: 'τ',
  upsilon: 'υ',
  phi: 'φ',
  chi: 'χ',
  psi: 'ψ',
  omega: 'ω',
  thetasym: 'ϑ',
  upsih: 'ϒ',
  piv: 'ϖ',
  bull: '•',
  hellip: '…',
  prime: '′',
  Prime: '″',
  oline: '‾',
  frasl: '⁄',
  weierp: '℘',
  image: 'ℑ',
  real: 'ℜ',
  trade: '™',
  alefsym: 'ℵ',
  larr: '←',
  uarr: '↑',
  rarr: '→',
  darr: '↓',
  harr: '↔',
  crarr: '↵',
  lArr: '⇐',
  uArr: '⇑',
  rArr: '⇒',
  dArr: '⇓',
  hArr: '⇔',
  forall: '∀',
  part: '∂',
  exist: '∃',
  empty: '∅',
  nabla: '∇',
  isin: '∈',
  notin: '∉',
  ni: '∋',
  prod: '∏',
  sum: '∑',
  minus: '−',
  lowast: '∗',
  radic: '√',
  prop: '∝',
  infin: '∞',
  ang: '∠',
  and: '∧',
  or: '∨',
  cap: '∩',
  cup: '∪',
  int: '∫',
  there4: '∴',
  sim: '∼',
  cong: '≅',
  asymp: '≈',
  ne: '≠',
  equiv: '≡',
  le: '≤',
  ge: '≥',
  sub: '⊂',
  sup: '⊃',
  nsub: '⊄',
  sube: '⊆',
  supe: '⊇',
  oplus: '⊕',
  otimes: '⊗',
  perp: '⊥',
  sdot: '⋅',
  lceil: '⌈',
  rceil: '⌉',
  lfloor: '⌊',
  rfloor: '⌋',
  lang: '〈',
  rang: '〉',
  loz: '◊',
  spades: '♠',
  clubs: '♣',
  hearts: '♥',
  diams: '♦',
  quot: '"',
  amp: '&',
  lt: '<',
  gt: '>',
  OElig: 'Œ',
  oelig: 'œ',
  Scaron: 'Š',
  scaron: 'š',
  Yuml: 'Ÿ',
  circ: 'ˆ',
  tilde: '˜',
  ensp: ' ',
  emsp: ' ',
  thinsp: ' ',
  zwnj: '‌',
  zwj: '‍',
  lrm: '‎',
  rlm: '‏',
  ndash: '–',
  mdash: '—',
  lsquo: '‘',
  rsquo: '’',
  sbquo: '‚',
  ldquo: '“',
  rdquo: '”',
  bdquo: '„',
  dagger: '†',
  Dagger: '‡',
  permil: '‰',
  lsaquo: '‹',
  rsaquo: '›',
  euro: '€'
}
{
  "name": "character-entities-html4",
  "version": "2.1.0",
  "description": "Map of named character references from HTML 4",
  "license": "MIT",
  "keywords": [
    "html",
    "html4",
    "entity",
    "entities",
    "character",
    "reference",
    "name",
    "replacement"
  ],
  "repository": "wooorm/character-entities-html4",
  "bugs": "https://github.com/wooorm/character-entities-html4/issues",
  "funding": {
    "type": "github",
    "url": "https://github.com/sponsors/wooorm"
  },
  "author": "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)",
  "contributors": [
    "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)"
  ],
  "sideEffects": false,
  "type": "module",
  "main": "index.js",
  "types": "index.d.ts",
  "files": [
    "index.d.ts",
    "index.js"
  ],
  "devDependencies": {
    "@types/tape": "^4.0.0",
    "bail": "^2.0.0",
    "c8": "^7.0.0",
    "concat-stream": "^2.0.0",
    "prettier": "^2.0.0",
    "remark-cli": "^10.0.0",
    "remark-preset-wooorm": "^9.0.0",
    "rimraf": "^3.0.0",
    "tape": "^5.0.0",
    "type-coverage": "^2.0.0",
    "typescript": "^4.0.0",
    "xo": "^0.46.0"
  },
  "scripts": {
    "prepublishOnly": "npm run build && npm run format",
    "generate": "node build",
    "build": "rimraf \"*.d.ts\" && tsc && type-coverage",
    "format": "remark . -qfo && prettier . -w --loglevel warn && xo --fix",
    "test-api": "node --conditions development test.js",
    "test-coverage": "c8 --check-coverage --branches 100 --functions 100 --lines 100 --statements 100 --reporter lcov npm run test-api",
    "test": "npm run generate && npm run build && npm run format && npm run test-coverage"
  },
  "prettier": {
    "tabWidth": 2,
    "useTabs": false,
    "singleQuote": true,
    "bracketSpacing": false,
    "semi": false,
    "trailingComma": "none"
  },
  "xo": {
    "prettier": true
  },
  "remarkConfig": {
    "plugins": [
      "preset-wooorm"
    ]
  },
  "typeCoverage": {
    "atLeast": 100,
    "detail": true,
    "strict": true,
    "ignoreCatch": true
  }
}
/**
 * List of legacy HTML named character references that don’t need a trailing semicolon.
 *
 * @type {Array<string>}
 */
export const characterEntitiesLegacy = [
  'AElig',
  'AMP',
  'Aacute',
  'Acirc',
  'Agrave',
  'Aring',
  'Atilde',
  'Auml',
  'COPY',
  'Ccedil',
  'ETH',
  'Eacute',
  'Ecirc',
  'Egrave',
  'Euml',
  'GT',
  'Iacute',
  'Icirc',
  'Igrave',
  'Iuml',
  'LT',
  'Ntilde',
  'Oacute',
  'Ocirc',
  'Ograve',
  'Oslash',
  'Otilde',
  'Ouml',
  'QUOT',
  'REG',
  'THORN',
  'Uacute',
  'Ucirc',
  'Ugrave',
  'Uuml',
  'Yacute',
  'aacute',
  'acirc',
  'acute',
  'aelig',
  'agrave',
  'amp',
  'aring',
  'atilde',
  'auml',
  'brvbar',
  'ccedil',
  'cedil',
  'cent',
  'copy',
  'curren',
  'deg',
  'divide',
  'eacute',
  'ecirc',
  'egrave',
  'eth',
  'euml',
  'frac12',
  'frac14',
  'frac34',
  'gt',
  'iacute',
  'icirc',
  'iexcl',
  'igrave',
  'iquest',
  'iuml',
  'laquo',
  'lt',
  'macr',
  'micro',
  'middot',
  'nbsp',
  'not',
  'ntilde',
  'oacute',
  'ocirc',
  'ograve',
  'ordf',
  'ordm',
  'oslash',
  'otilde',
  'ouml',
  'para',
  'plusmn',
  'pound',
  'quot',
  'raquo',
  'reg',
  'sect',
  'shy',
  'sup1',
  'sup2',
  'sup3',
  'szlig',
  'thorn',
  'times',
  'uacute',
  'ucirc',
  'ugrave',
  'uml',
  'uuml',
  'yacute',
  'yen',
  'yuml'
]
{
  "name": "character-entities-legacy",
  "version": "3.0.0",
  "description": "List of legacy HTML named character references that don’t need a trailing semicolon",
  "license": "MIT",
  "keywords": [
    "html",
    "entity",
    "entities",
    "character",
    "reference",
    "name"
  ],
  "repository": "wooorm/character-entities-legacy",
  "bugs": "https://github.com/wooorm/character-entities-legacy/issues",
  "funding": {
    "type": "github",
    "url": "https://github.com/sponsors/wooorm"
  },
  "author": "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)",
  "contributors": [
    "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)"
  ],
  "sideEffects": false,
  "type": "module",
  "main": "index.js",
  "types": "index.d.ts",
  "files": [
    "index.d.ts",
    "index.js"
  ],
  "devDependencies": {
    "@types/tape": "^4.0.0",
    "bail": "^2.0.0",
    "c8": "^7.0.0",
    "concat-stream": "^2.0.0",
    "prettier": "^2.0.0",
    "remark-cli": "^10.0.0",
    "remark-preset-wooorm": "^9.0.0",
    "rimraf": "^3.0.0",
    "tape": "^5.0.0",
    "type-coverage": "^2.0.0",
    "typescript": "^4.0.0",
    "xo": "^0.45.0"
  },
  "scripts": {
    "generate": "node build",
    "prepublishOnly": "npm run build && npm run format",
    "build": "rimraf \"*.d.ts\" && tsc && type-coverage",
    "format": "remark . -qfo && prettier . -w --loglevel warn && xo --fix",
    "test-api": "node --conditions development test.js",
    "test-coverage": "c8 --check-coverage --branches 100 --functions 100 --lines 100 --statements 100 --reporter lcov npm run test-api",
    "test": "npm run generate && npm run build && npm run format && npm run test-coverage"
  },
  "prettier": {
    "tabWidth": 2,
    "useTabs": false,
    "singleQuote": true,
    "bracketSpacing": false,
    "semi": false,
    "trailingComma": "none"
  },
  "xo": {
    "prettier": true
  },
  "remarkConfig": {
    "plugins": [
      "preset-wooorm"
    ]
  },
  "typeCoverage": {
    "atLeast": 100,
    "detail": true,
    "strict": true,
    "ignoreCatch": true
  }
}
/**
 * Map of named character references.
 *
 * @type {Record<string, string>}
 */
export const characterEntities = {
  AElig: 'Æ',
  AMP: '&',
  Aacute: 'Á',
  Abreve: 'Ă',
  Acirc: 'Â',
  Acy: 'А',
  Afr: '𝔄',
  Agrave: 'À',
  Alpha: 'Α',
  Amacr: 'Ā',
  And: '⩓',
  Aogon: 'Ą',
  Aopf: '𝔸',
  ApplyFunction: '⁡',
  Aring: 'Å',
  Ascr: '𝒜',
  Assign: '≔',
  Atilde: 'Ã',
  Auml: 'Ä',
  Backslash: '∖',
  Barv: '⫧',
  Barwed: '⌆',
  Bcy: 'Б',
  Because: '∵',
  Bernoullis: 'ℬ',
  Beta: 'Β',
  Bfr: '𝔅',
  Bopf: '𝔹',
  Breve: '˘',
  Bscr: 'ℬ',
  Bumpeq: '≎',
  CHcy: 'Ч',
  COPY: '©',
  Cacute: 'Ć',
  Cap: '⋒',
  CapitalDifferentialD: 'ⅅ',
  Cayleys: 'ℭ',
  Ccaron: 'Č',
  Ccedil: 'Ç',
  Ccirc: 'Ĉ',
  Cconint: '∰',
  Cdot: 'Ċ',
  Cedilla: '¸',
  CenterDot: '·',
  Cfr: 'ℭ',
  Chi: 'Χ',
  CircleDot: '⊙',
  CircleMinus: '⊖',
  CirclePlus: '⊕',
  CircleTimes: '⊗',
  ClockwiseContourIntegral: '∲',
  CloseCurlyDoubleQuote: '”',
  CloseCurlyQuote: '’',
  Colon: '∷',
  Colone: '⩴',
  Congruent: '≡',
  Conint: '∯',
  ContourIntegral: '∮',
  Copf: 'ℂ',
  Coproduct: '∐',
  CounterClockwiseContourIntegral: '∳',
  Cross: '⨯',
  Cscr: '𝒞',
  Cup: '⋓',
  CupCap: '≍',
  DD: 'ⅅ',
  DDotrahd: '⤑',
  DJcy: 'Ђ',
  DScy: 'Ѕ',
  DZcy: 'Џ',
  Dagger: '‡',
  Darr: '↡',
  Dashv: '⫤',
  Dcaron: 'Ď',
  Dcy: 'Д',
  Del: '∇',
  Delta: 'Δ',
  Dfr: '𝔇',
  DiacriticalAcute: '´',
  DiacriticalDot: '˙',
  DiacriticalDoubleAcute: '˝',
  DiacriticalGrave: '`',
  DiacriticalTilde: '˜',
  Diamond: '⋄',
  DifferentialD: 'ⅆ',
  Dopf: '𝔻',
  Dot: '¨',
  DotDot: '⃜',
  DotEqual: '≐',
  DoubleContourIntegral: '∯',
  DoubleDot: '¨',
  DoubleDownArrow: '⇓',
  DoubleLeftArrow: '⇐',
  DoubleLeftRightArrow: '⇔',
  DoubleLeftTee: '⫤',
  DoubleLongLeftArrow: '⟸',
  DoubleLongLeftRightArrow: '⟺',
  DoubleLongRightArrow: '⟹',
  DoubleRightArrow: '⇒',
  DoubleRightTee: '⊨',
  DoubleUpArrow: '⇑',
  DoubleUpDownArrow: '⇕',
  DoubleVerticalBar: '∥',
  DownArrow: '↓',
  DownArrowBar: '⤓',
  DownArrowUpArrow: '⇵',
  DownBreve: '̑',
  DownLeftRightVector: '⥐',
  DownLeftTeeVector: '⥞',
  DownLeftVector: '↽',
  DownLeftVectorBar: '⥖',
  DownRightTeeVector: '⥟',
  DownRightVector: '⇁',
  DownRightVectorBar: '⥗',
  DownTee: '⊤',
  DownTeeArrow: '↧',
  Downarrow: '⇓',
  Dscr: '𝒟',
  Dstrok: 'Đ',
  ENG: 'Ŋ',
  ETH: 'Ð',
  Eacute: 'É',
  Ecaron: 'Ě',
  Ecirc: 'Ê',
  Ecy: 'Э',
  Edot: 'Ė',
  Efr: '𝔈',
  Egrave: 'È',
  Element: '∈',
  Emacr: 'Ē',
  EmptySmallSquare: '◻',
  EmptyVerySmallSquare: '▫',
  Eogon: 'Ę',
  Eopf: '𝔼',
  Epsilon: 'Ε',
  Equal: '⩵',
  EqualTilde: '≂',
  Equilibrium: '⇌',
  Escr: 'ℰ',
  Esim: '⩳',
  Eta: 'Η',
  Euml: 'Ë',
  Exists: '∃',
  ExponentialE: 'ⅇ',
  Fcy: 'Ф',
  Ffr: '𝔉',
  FilledSmallSquare: '◼',
  FilledVerySmallSquare: '▪',
  Fopf: '𝔽',
  ForAll: '∀',
  Fouriertrf: 'ℱ',
  Fscr: 'ℱ',
  GJcy: 'Ѓ',
  GT: '>',
  Gamma: 'Γ',
  Gammad: 'Ϝ',
  Gbreve: 'Ğ',
  Gcedil: 'Ģ',
  Gcirc: 'Ĝ',
  Gcy: 'Г',
  Gdot: 'Ġ',
  Gfr: '𝔊',
  Gg: '⋙',
  Gopf: '𝔾',
  GreaterEqual: '≥',
  GreaterEqualLess: '⋛',
  GreaterFullEqual: '≧',
  GreaterGreater: '⪢',
  GreaterLess: '≷',
  GreaterSlantEqual: '⩾',
  GreaterTilde: '≳',
  Gscr: '𝒢',
  Gt: '≫',
  HARDcy: 'Ъ',
  Hacek: 'ˇ',
  Hat: '^',
  Hcirc: 'Ĥ',
  Hfr: 'ℌ',
  HilbertSpace: 'ℋ',
  Hopf: 'ℍ',
  HorizontalLine: '─',
  Hscr: 'ℋ',
  Hstrok: 'Ħ',
  HumpDownHump: '≎',
  HumpEqual: '≏',
  IEcy: 'Е',
  IJlig: 'Ĳ',
  IOcy: 'Ё',
  Iacute: 'Í',
  Icirc: 'Î',
  Icy: 'И',
  Idot: 'İ',
  Ifr: 'ℑ',
  Igrave: 'Ì',
  Im: 'ℑ',
  Imacr: 'Ī',
  ImaginaryI: 'ⅈ',
  Implies: '⇒',
  Int: '∬',
  Integral: '∫',
  Intersection: '⋂',
  InvisibleComma: '⁣',
  InvisibleTimes: '⁢',
  Iogon: 'Į',
  Iopf: '𝕀',
  Iota: 'Ι',
  Iscr: 'ℐ',
  Itilde: 'Ĩ',
  Iukcy: 'І',
  Iuml: 'Ï',
  Jcirc: 'Ĵ',
  Jcy: 'Й',
  Jfr: '𝔍',
  Jopf: '𝕁',
  Jscr: '𝒥',
  Jsercy: 'Ј',
  Jukcy: 'Є',
  KHcy: 'Х',
  KJcy: 'Ќ',
  Kappa: 'Κ',
  Kcedil: 'Ķ',
  Kcy: 'К',
  Kfr: '𝔎',
  Kopf: '𝕂',
  Kscr: '𝒦',
  LJcy: 'Љ',
  LT: '<',
  Lacute: 'Ĺ',
  Lambda: 'Λ',
  Lang: '⟪',
  Laplacetrf: 'ℒ',
  Larr: '↞',
  Lcaron: 'Ľ',
  Lcedil: 'Ļ',
  Lcy: 'Л',
  LeftAngleBracket: '⟨',
  LeftArrow: '←',
  LeftArrowBar: '⇤',
  LeftArrowRightArrow: '⇆',
  LeftCeiling: '⌈',
  LeftDoubleBracket: '⟦',
  LeftDownTeeVector: '⥡',
  LeftDownVector: '⇃',
  LeftDownVectorBar: '⥙',
  LeftFloor: '⌊',
  LeftRightArrow: '↔',
  LeftRightVector: '⥎',
  LeftTee: '⊣',
  LeftTeeArrow: '↤',
  LeftTeeVector: '⥚',
  LeftTriangle: '⊲',
  LeftTriangleBar: '⧏',
  LeftTriangleEqual: '⊴',
  LeftUpDownVector: '⥑',
  LeftUpTeeVector: '⥠',
  LeftUpVector: '↿',
  LeftUpVectorBar: '⥘',
  LeftVector: '↼',
  LeftVectorBar: '⥒',
  Leftarrow: '⇐',
  Leftrightarrow: '⇔',
  LessEqualGreater: '⋚',
  LessFullEqual: '≦',
  LessGreater: '≶',
  LessLess: '⪡',
  LessSlantEqual: '⩽',
  LessTilde: '≲',
  Lfr: '𝔏',
  Ll: '⋘',
  Lleftarrow: '⇚',
  Lmidot: 'Ŀ',
  LongLeftArrow: '⟵',
  LongLeftRightArrow: '⟷',
  LongRightArrow: '⟶',
  Longleftarrow: '⟸',
  Longleftrightarrow: '⟺',
  Longrightarrow: '⟹',
  Lopf: '𝕃',
  LowerLeftArrow: '↙',
  LowerRightArrow: '↘',
  Lscr: 'ℒ',
  Lsh: '↰',
  Lstrok: 'Ł',
  Lt: '≪',
  Map: '⤅',
  Mcy: 'М',
  MediumSpace: ' ',
  Mellintrf: 'ℳ',
  Mfr: '𝔐',
  MinusPlus: '∓',
  Mopf: '𝕄',
  Mscr: 'ℳ',
  Mu: 'Μ',
  NJcy: 'Њ',
  Nacute: 'Ń',
  Ncaron: 'Ň',
  Ncedil: 'Ņ',
  Ncy: 'Н',
  NegativeMediumSpace: '​',
  NegativeThickSpace: '​',
  NegativeThinSpace: '​',
  NegativeVeryThinSpace: '​',
  NestedGreaterGreater: '≫',
  NestedLessLess: '≪',
  NewLine: '\n',
  Nfr: '𝔑',
  NoBreak: '⁠',
  NonBreakingSpace: ' ',
  Nopf: 'ℕ',
  Not: '⫬',
  NotCongruent: '≢',
  NotCupCap: '≭',
  NotDoubleVerticalBar: '∦',
  NotElement: '∉',
  NotEqual: '≠',
  NotEqualTilde: '≂̸',
  NotExists: '∄',
  NotGreater: '≯',
  NotGreaterEqual: '≱',
  NotGreaterFullEqual: '≧̸',
  NotGreaterGreater: '≫̸',
  NotGreaterLess: '≹',
  NotGreaterSlantEqual: '⩾̸',
  NotGreaterTilde: '≵',
  NotHumpDownHump: '≎̸',
  NotHumpEqual: '≏̸',
  NotLeftTriangle: '⋪',
  NotLeftTriangleBar: '⧏̸',
  NotLeftTriangleEqual: '⋬',
  NotLess: '≮',
  NotLessEqual: '≰',
  NotLessGreater: '≸',
  NotLessLess: '≪̸',
  NotLessSlantEqual: '⩽̸',
  NotLessTilde: '≴',
  NotNestedGreaterGreater: '⪢̸',
  NotNestedLessLess: '⪡̸',
  NotPrecedes: '⊀',
  NotPrecedesEqual: '⪯̸',
  NotPrecedesSlantEqual: '⋠',
  NotReverseElement: '∌',
  NotRightTriangle: '⋫',
  NotRightTriangleBar: '⧐̸',
  NotRightTriangleEqual: '⋭',
  NotSquareSubset: '⊏̸',
  NotSquareSubsetEqual: '⋢',
  NotSquareSuperset: '⊐̸',
  NotSquareSupersetEqual: '⋣',
  NotSubset: '⊂⃒',
  NotSubsetEqual: '⊈',
  NotSucceeds: '⊁',
  NotSucceedsEqual: '⪰̸',
  NotSucceedsSlantEqual: '⋡',
  NotSucceedsTilde: '≿̸',
  NotSuperset: '⊃⃒',
  NotSupersetEqual: '⊉',
  NotTilde: '≁',
  NotTildeEqual: '≄',
  NotTildeFullEqual: '≇',
  NotTildeTilde: '≉',
  NotVerticalBar: '∤',
  Nscr: '𝒩',
  Ntilde: 'Ñ',
  Nu: 'Ν',
  OElig: 'Œ',
  Oacute: 'Ó',
  Ocirc: 'Ô',
  Ocy: 'О',
  Odblac: 'Ő',
  Ofr: '𝔒',
  Ograve: 'Ò',
  Omacr: 'Ō',
  Omega: 'Ω',
  Omicron: 'Ο',
  Oopf: '𝕆',
  OpenCurlyDoubleQuote: '“',
  OpenCurlyQuote: '‘',
  Or: '⩔',
  Oscr: '𝒪',
  Oslash: 'Ø',
  Otilde: 'Õ',
  Otimes: '⨷',
  Ouml: 'Ö',
  OverBar: '‾',
  OverBrace: '⏞',
  OverBracket: '⎴',
  OverParenthesis: '⏜',
  PartialD: '∂',
  Pcy: 'П',
  Pfr: '𝔓',
  Phi: 'Φ',
  Pi: 'Π',
  PlusMinus: '±',
  Poincareplane: 'ℌ',
  Popf: 'ℙ',
  Pr: '⪻',
  Precedes: '≺',
  PrecedesEqual: '⪯',
  PrecedesSlantEqual: '≼',
  PrecedesTilde: '≾',
  Prime: '″',
  Product: '∏',
  Proportion: '∷',
  Proportional: '∝',
  Pscr: '𝒫',
  Psi: 'Ψ',
  QUOT: '"',
  Qfr: '𝔔',
  Qopf: 'ℚ',
  Qscr: '𝒬',
  RBarr: '⤐',
  REG: '®',
  Racute: 'Ŕ',
  Rang: '⟫',
  Rarr: '↠',
  Rarrtl: '⤖',
  Rcaron: 'Ř',
  Rcedil: 'Ŗ',
  Rcy: 'Р',
  Re: 'ℜ',
  ReverseElement: '∋',
  ReverseEquilibrium: '⇋',
  ReverseUpEquilibrium: '⥯',
  Rfr: 'ℜ',
  Rho: 'Ρ',
  RightAngleBracket: '⟩',
  RightArrow: '→',
  RightArrowBar: '⇥',
  RightArrowLeftArrow: '⇄',
  RightCeiling: '⌉',
  RightDoubleBracket: '⟧',
  RightDownTeeVector: '⥝',
  RightDownVector: '⇂',
  RightDownVectorBar: '⥕',
  RightFloor: '⌋',
  RightTee: '⊢',
  RightTeeArrow: '↦',
  RightTeeVector: '⥛',
  RightTriangle: '⊳',
  RightTriangleBar: '⧐',
  RightTriangleEqual: '⊵',
  RightUpDownVector: '⥏',
  RightUpTeeVector: '⥜',
  RightUpVector: '↾',
  RightUpVectorBar: '⥔',
  RightVector: '⇀',
  RightVectorBar: '⥓',
  Rightarrow: '⇒',
  Ropf: 'ℝ',
  RoundImplies: '⥰',
  Rrightarrow: '⇛',
  Rscr: 'ℛ',
  Rsh: '↱',
  RuleDelayed: '⧴',
  SHCHcy: 'Щ',
  SHcy: 'Ш',
  SOFTcy: 'Ь',
  Sacute: 'Ś',
  Sc: '⪼',
  Scaron: 'Š',
  Scedil: 'Ş',
  Scirc: 'Ŝ',
  Scy: 'С',
  Sfr: '𝔖',
  ShortDownArrow: '↓',
  ShortLeftArrow: '←',
  ShortRightArrow: '→',
  ShortUpArrow: '↑',
  Sigma: 'Σ',
  SmallCircle: '∘',
  Sopf: '𝕊',
  Sqrt: '√',
  Square: '□',
  SquareIntersection: '⊓',
  SquareSubset: '⊏',
  SquareSubsetEqual: '⊑',
  SquareSuperset: '⊐',
  SquareSupersetEqual: '⊒',
  SquareUnion: '⊔',
  Sscr: '𝒮',
  Star: '⋆',
  Sub: '⋐',
  Subset: '⋐',
  SubsetEqual: '⊆',
  Succeeds: '≻',
  SucceedsEqual: '⪰',
  SucceedsSlantEqual: '≽',
  SucceedsTilde: '≿',
  SuchThat: '∋',
  Sum: '∑',
  Sup: '⋑',
  Superset: '⊃',
  SupersetEqual: '⊇',
  Supset: '⋑',
  THORN: 'Þ',
  TRADE: '™',
  TSHcy: 'Ћ',
  TScy: 'Ц',
  Tab: '\t',
  Tau: 'Τ',
  Tcaron: 'Ť',
  Tcedil: 'Ţ',
  Tcy: 'Т',
  Tfr: '𝔗',
  Therefore: '∴',
  Theta: 'Θ',
  ThickSpace: '  ',
  ThinSpace: ' ',
  Tilde: '∼',
  TildeEqual: '≃',
  TildeFullEqual: '≅',
  TildeTilde: '≈',
  Topf: '𝕋',
  TripleDot: '⃛',
  Tscr: '𝒯',
  Tstrok: 'Ŧ',
  Uacute: 'Ú',
  Uarr: '↟',
  Uarrocir: '⥉',
  Ubrcy: 'Ў',
  Ubreve: 'Ŭ',
  Ucirc: 'Û',
  Ucy: 'У',
  Udblac: 'Ű',
  Ufr: '𝔘',
  Ugrave: 'Ù',
  Umacr: 'Ū',
  UnderBar: '_',
  UnderBrace: '⏟',
  UnderBracket: '⎵',
  UnderParenthesis: '⏝',
  Union: '⋃',
  UnionPlus: '⊎',
  Uogon: 'Ų',
  Uopf: '𝕌',
  UpArrow: '↑',
  UpArrowBar: '⤒',
  UpArrowDownArrow: '⇅',
  UpDownArrow: '↕',
  UpEquilibrium: '⥮',
  UpTee: '⊥',
  UpTeeArrow: '↥',
  Uparrow: '⇑',
  Updownarrow: '⇕',
  UpperLeftArrow: '↖',
  UpperRightArrow: '↗',
  Upsi: 'ϒ',
  Upsilon: 'Υ',
  Uring: 'Ů',
  Uscr: '𝒰',
  Utilde: 'Ũ',
  Uuml: 'Ü',
  VDash: '⊫',
  Vbar: '⫫',
  Vcy: 'В',
  Vdash: '⊩',
  Vdashl: '⫦',
  Vee: '⋁',
  Verbar: '‖',
  Vert: '‖',
  VerticalBar: '∣',
  VerticalLine: '|',
  VerticalSeparator: '❘',
  VerticalTilde: '≀',
  VeryThinSpace: ' ',
  Vfr: '𝔙',
  Vopf: '𝕍',
  Vscr: '𝒱',
  Vvdash: '⊪',
  Wcirc: 'Ŵ',
  Wedge: '⋀',
  Wfr: '𝔚',
  Wopf: '𝕎',
  Wscr: '𝒲',
  Xfr: '𝔛',
  Xi: 'Ξ',
  Xopf: '𝕏',
  Xscr: '𝒳',
  YAcy: 'Я',
  YIcy: 'Ї',
  YUcy: 'Ю',
  Yacute: 'Ý',
  Ycirc: 'Ŷ',
  Ycy: 'Ы',
  Yfr: '𝔜',
  Yopf: '𝕐',
  Yscr: '𝒴',
  Yuml: 'Ÿ',
  ZHcy: 'Ж',
  Zacute: 'Ź',
  Zcaron: 'Ž',
  Zcy: 'З',
  Zdot: 'Ż',
  ZeroWidthSpace: '​',
  Zeta: 'Ζ',
  Zfr: 'ℨ',
  Zopf: 'ℤ',
  Zscr: '𝒵',
  aacute: 'á',
  abreve: 'ă',
  ac: '∾',
  acE: '∾̳',
  acd: '∿',
  acirc: 'â',
  acute: '´',
  acy: 'а',
  aelig: 'æ',
  af: '⁡',
  afr: '𝔞',
  agrave: 'à',
  alefsym: 'ℵ',
  aleph: 'ℵ',
  alpha: 'α',
  amacr: 'ā',
  amalg: '⨿',
  amp: '&',
  and: '∧',
  andand: '⩕',
  andd: '⩜',
  andslope: '⩘',
  andv: '⩚',
  ang: '∠',
  ange: '⦤',
  angle: '∠',
  angmsd: '∡',
  angmsdaa: '⦨',
  angmsdab: '⦩',
  angmsdac: '⦪',
  angmsdad: '⦫',
  angmsdae: '⦬',
  angmsdaf: '⦭',
  angmsdag: '⦮',
  angmsdah: '⦯',
  angrt: '∟',
  angrtvb: '⊾',
  angrtvbd: '⦝',
  angsph: '∢',
  angst: 'Å',
  angzarr: '⍼',
  aogon: 'ą',
  aopf: '𝕒',
  ap: '≈',
  apE: '⩰',
  apacir: '⩯',
  ape: '≊',
  apid: '≋',
  apos: "'",
  approx: '≈',
  approxeq: '≊',
  aring: 'å',
  ascr: '𝒶',
  ast: '*',
  asymp: '≈',
  asympeq: '≍',
  atilde: 'ã',
  auml: 'ä',
  awconint: '∳',
  awint: '⨑',
  bNot: '⫭',
  backcong: '≌',
  backepsilon: '϶',
  backprime: '‵',
  backsim: '∽',
  backsimeq: '⋍',
  barvee: '⊽',
  barwed: '⌅',
  barwedge: '⌅',
  bbrk: '⎵',
  bbrktbrk: '⎶',
  bcong: '≌',
  bcy: 'б',
  bdquo: '„',
  becaus: '∵',
  because: '∵',
  bemptyv: '⦰',
  bepsi: '϶',
  bernou: 'ℬ',
  beta: 'β',
  beth: 'ℶ',
  between: '≬',
  bfr: '𝔟',
  bigcap: '⋂',
  bigcirc: '◯',
  bigcup: '⋃',
  bigodot: '⨀',
  bigoplus: '⨁',
  bigotimes: '⨂',
  bigsqcup: '⨆',
  bigstar: '★',
  bigtriangledown: '▽',
  bigtriangleup: '△',
  biguplus: '⨄',
  bigvee: '⋁',
  bigwedge: '⋀',
  bkarow: '⤍',
  blacklozenge: '⧫',
  blacksquare: '▪',
  blacktriangle: '▴',
  blacktriangledown: '▾',
  blacktriangleleft: '◂',
  blacktriangleright: '▸',
  blank: '␣',
  blk12: '▒',
  blk14: '░',
  blk34: '▓',
  block: '█',
  bne: '=⃥',
  bnequiv: '≡⃥',
  bnot: '⌐',
  bopf: '𝕓',
  bot: '⊥',
  bottom: '⊥',
  bowtie: '⋈',
  boxDL: '╗',
  boxDR: '╔',
  boxDl: '╖',
  boxDr: '╓',
  boxH: '═',
  boxHD: '╦',
  boxHU: '╩',
  boxHd: '╤',
  boxHu: '╧',
  boxUL: '╝',
  boxUR: '╚',
  boxUl: '╜',
  boxUr: '╙',
  boxV: '║',
  boxVH: '╬',
  boxVL: '╣',
  boxVR: '╠',
  boxVh: '╫',
  boxVl: '╢',
  boxVr: '╟',
  boxbox: '⧉',
  boxdL: '╕',
  boxdR: '╒',
  boxdl: '┐',
  boxdr: '┌',
  boxh: '─',
  boxhD: '╥',
  boxhU: '╨',
  boxhd: '┬',
  boxhu: '┴',
  boxminus: '⊟',
  boxplus: '⊞',
  boxtimes: '⊠',
  boxuL: '╛',
  boxuR: '╘',
  boxul: '┘',
  boxur: '└',
  boxv: '│',
  boxvH: '╪',
  boxvL: '╡',
  boxvR: '╞',
  boxvh: '┼',
  boxvl: '┤',
  boxvr: '├',
  bprime: '‵',
  breve: '˘',
  brvbar: '¦',
  bscr: '𝒷',
  bsemi: '⁏',
  bsim: '∽',
  bsime: '⋍',
  bsol: '\\',
  bsolb: '⧅',
  bsolhsub: '⟈',
  bull: '•',
  bullet: '•',
  bump: '≎',
  bumpE: '⪮',
  bumpe: '≏',
  bumpeq: '≏',
  cacute: 'ć',
  cap: '∩',
  capand: '⩄',
  capbrcup: '⩉',
  capcap: '⩋',
  capcup: '⩇',
  capdot: '⩀',
  caps: '∩︀',
  caret: '⁁',
  caron: 'ˇ',
  ccaps: '⩍',
  ccaron: 'č',
  ccedil: 'ç',
  ccirc: 'ĉ',
  ccups: '⩌',
  ccupssm: '⩐',
  cdot: 'ċ',
  cedil: '¸',
  cemptyv: '⦲',
  cent: '¢',
  centerdot: '·',
  cfr: '𝔠',
  chcy: 'ч',
  check: '✓',
  checkmark: '✓',
  chi: 'χ',
  cir: '○',
  cirE: '⧃',
  circ: 'ˆ',
  circeq: '≗',
  circlearrowleft: '↺',
  circlearrowright: '↻',
  circledR: '®',
  circledS: 'Ⓢ',
  circledast: '⊛',
  circledcirc: '⊚',
  circleddash: '⊝',
  cire: '≗',
  cirfnint: '⨐',
  cirmid: '⫯',
  cirscir: '⧂',
  clubs: '♣',
  clubsuit: '♣',
  colon: ':',
  colone: '≔',
  coloneq: '≔',
  comma: ',',
  commat: '@',
  comp: '∁',
  compfn: '∘',
  complement: '∁',
  complexes: 'ℂ',
  cong: '≅',
  congdot: '⩭',
  conint: '∮',
  copf: '𝕔',
  coprod: '∐',
  copy: '©',
  copysr: '℗',
  crarr: '↵',
  cross: '✗',
  cscr: '𝒸',
  csub: '⫏',
  csube: '⫑',
  csup: '⫐',
  csupe: '⫒',
  ctdot: '⋯',
  cudarrl: '⤸',
  cudarrr: '⤵',
  cuepr: '⋞',
  cuesc: '⋟',
  cularr: '↶',
  cularrp: '⤽',
  cup: '∪',
  cupbrcap: '⩈',
  cupcap: '⩆',
  cupcup: '⩊',
  cupdot: '⊍',
  cupor: '⩅',
  cups: '∪︀',
  curarr: '↷',
  curarrm: '⤼',
  curlyeqprec: '⋞',
  curlyeqsucc: '⋟',
  curlyvee: '⋎',
  curlywedge: '⋏',
  curren: '¤',
  curvearrowleft: '↶',
  curvearrowright: '↷',
  cuvee: '⋎',
  cuwed: '⋏',
  cwconint: '∲',
  cwint: '∱',
  cylcty: '⌭',
  dArr: '⇓',
  dHar: '⥥',
  dagger: '†',
  daleth: 'ℸ',
  darr: '↓',
  dash: '‐',
  dashv: '⊣',
  dbkarow: '⤏',
  dblac: '˝',
  dcaron: 'ď',
  dcy: 'д',
  dd: 'ⅆ',
  ddagger: '‡',
  ddarr: '⇊',
  ddotseq: '⩷',
  deg: '°',
  delta: 'δ',
  demptyv: '⦱',
  dfisht: '⥿',
  dfr: '𝔡',
  dharl: '⇃',
  dharr: '⇂',
  diam: '⋄',
  diamond: '⋄',
  diamondsuit: '♦',
  diams: '♦',
  die: '¨',
  digamma: 'ϝ',
  disin: '⋲',
  div: '÷',
  divide: '÷',
  divideontimes: '⋇',
  divonx: '⋇',
  djcy: 'ђ',
  dlcorn: '⌞',
  dlcrop: '⌍',
  dollar: '$',
  dopf: '𝕕',
  dot: '˙',
  doteq: '≐',
  doteqdot: '≑',
  dotminus: '∸',
  dotplus: '∔',
  dotsquare: '⊡',
  doublebarwedge: '⌆',
  downarrow: '↓',
  downdownarrows: '⇊',
  downharpoonleft: '⇃',
  downharpoonright: '⇂',
  drbkarow: '⤐',
  drcorn: '⌟',
  drcrop: '⌌',
  dscr: '𝒹',
  dscy: 'ѕ',
  dsol: '⧶',
  dstrok: 'đ',
  dtdot: '⋱',
  dtri: '▿',
  dtrif: '▾',
  duarr: '⇵',
  duhar: '⥯',
  dwangle: '⦦',
  dzcy: 'џ',
  dzigrarr: '⟿',
  eDDot: '⩷',
  eDot: '≑',
  eacute: 'é',
  easter: '⩮',
  ecaron: 'ě',
  ecir: '≖',
  ecirc: 'ê',
  ecolon: '≕',
  ecy: 'э',
  edot: 'ė',
  ee: 'ⅇ',
  efDot: '≒',
  efr: '𝔢',
  eg: '⪚',
  egrave: 'è',
  egs: '⪖',
  egsdot: '⪘',
  el: '⪙',
  elinters: '⏧',
  ell: 'ℓ',
  els: '⪕',
  elsdot: '⪗',
  emacr: 'ē',
  empty: '∅',
  emptyset: '∅',
  emptyv: '∅',
  emsp13: ' ',
  emsp14: ' ',
  emsp: ' ',
  eng: 'ŋ',
  ensp: ' ',
  eogon: 'ę',
  eopf: '𝕖',
  epar: '⋕',
  eparsl: '⧣',
  eplus: '⩱',
  epsi: 'ε',
  epsilon: 'ε',
  epsiv: 'ϵ',
  eqcirc: '≖',
  eqcolon: '≕',
  eqsim: '≂',
  eqslantgtr: '⪖',
  eqslantless: '⪕',
  equals: '=',
  equest: '≟',
  equiv: '≡',
  equivDD: '⩸',
  eqvparsl: '⧥',
  erDot: '≓',
  erarr: '⥱',
  escr: 'ℯ',
  esdot: '≐',
  esim: '≂',
  eta: 'η',
  eth: 'ð',
  euml: 'ë',
  euro: '€',
  excl: '!',
  exist: '∃',
  expectation: 'ℰ',
  exponentiale: 'ⅇ',
  fallingdotseq: '≒',
  fcy: 'ф',
  female: '♀',
  ffilig: 'ﬃ',
  fflig: 'ﬀ',
  ffllig: 'ﬄ',
  ffr: '𝔣',
  filig: 'ﬁ',
  fjlig: 'fj',
  flat: '♭',
  fllig: 'ﬂ',
  fltns: '▱',
  fnof: 'ƒ',
  fopf: '𝕗',
  forall: '∀',
  fork: '⋔',
  forkv: '⫙',
  fpartint: '⨍',
  frac12: '½',
  frac13: '⅓',
  frac14: '¼',
  frac15: '⅕',
  frac16: '⅙',
  frac18: '⅛',
  frac23: '⅔',
  frac25: '⅖',
  frac34: '¾',
  frac35: '⅗',
  frac38: '⅜',
  frac45: '⅘',
  frac56: '⅚',
  frac58: '⅝',
  frac78: '⅞',
  frasl: '⁄',
  frown: '⌢',
  fscr: '𝒻',
  gE: '≧',
  gEl: '⪌',
  gacute: 'ǵ',
  gamma: 'γ',
  gammad: 'ϝ',
  gap: '⪆',
  gbreve: 'ğ',
  gcirc: 'ĝ',
  gcy: 'г',
  gdot: 'ġ',
  ge: '≥',
  gel: '⋛',
  geq: '≥',
  geqq: '≧',
  geqslant: '⩾',
  ges: '⩾',
  gescc: '⪩',
  gesdot: '⪀',
  gesdoto: '⪂',
  gesdotol: '⪄',
  gesl: '⋛︀',
  gesles: '⪔',
  gfr: '𝔤',
  gg: '≫',
  ggg: '⋙',
  gimel: 'ℷ',
  gjcy: 'ѓ',
  gl: '≷',
  glE: '⪒',
  gla: '⪥',
  glj: '⪤',
  gnE: '≩',
  gnap: '⪊',
  gnapprox: '⪊',
  gne: '⪈',
  gneq: '⪈',
  gneqq: '≩',
  gnsim: '⋧',
  gopf: '𝕘',
  grave: '`',
  gscr: 'ℊ',
  gsim: '≳',
  gsime: '⪎',
  gsiml: '⪐',
  gt: '>',
  gtcc: '⪧',
  gtcir: '⩺',
  gtdot: '⋗',
  gtlPar: '⦕',
  gtquest: '⩼',
  gtrapprox: '⪆',
  gtrarr: '⥸',
  gtrdot: '⋗',
  gtreqless: '⋛',
  gtreqqless: '⪌',
  gtrless: '≷',
  gtrsim: '≳',
  gvertneqq: '≩︀',
  gvnE: '≩︀',
  hArr: '⇔',
  hairsp: ' ',
  half: '½',
  hamilt: 'ℋ',
  hardcy: 'ъ',
  harr: '↔',
  harrcir: '⥈',
  harrw: '↭',
  hbar: 'ℏ',
  hcirc: 'ĥ',
  hearts: '♥',
  heartsuit: '♥',
  hellip: '…',
  hercon: '⊹',
  hfr: '𝔥',
  hksearow: '⤥',
  hkswarow: '⤦',
  hoarr: '⇿',
  homtht: '∻',
  hookleftarrow: '↩',
  hookrightarrow: '↪',
  hopf: '𝕙',
  horbar: '―',
  hscr: '𝒽',
  hslash: 'ℏ',
  hstrok: 'ħ',
  hybull: '⁃',
  hyphen: '‐',
  iacute: 'í',
  ic: '⁣',
  icirc: 'î',
  icy: 'и',
  iecy: 'е',
  iexcl: '¡',
  iff: '⇔',
  ifr: '𝔦',
  igrave: 'ì',
  ii: 'ⅈ',
  iiiint: '⨌',
  iiint: '∭',
  iinfin: '⧜',
  iiota: '℩',
  ijlig: 'ĳ',
  imacr: 'ī',
  image: 'ℑ',
  imagline: 'ℐ',
  imagpart: 'ℑ',
  imath: 'ı',
  imof: '⊷',
  imped: 'Ƶ',
  in: '∈',
  incare: '℅',
  infin: '∞',
  infintie: '⧝',
  inodot: 'ı',
  int: '∫',
  intcal: '⊺',
  integers: 'ℤ',
  intercal: '⊺',
  intlarhk: '⨗',
  intprod: '⨼',
  iocy: 'ё',
  iogon: 'į',
  iopf: '𝕚',
  iota: 'ι',
  iprod: '⨼',
  iquest: '¿',
  iscr: '𝒾',
  isin: '∈',
  isinE: '⋹',
  isindot: '⋵',
  isins: '⋴',
  isinsv: '⋳',
  isinv: '∈',
  it: '⁢',
  itilde: 'ĩ',
  iukcy: 'і',
  iuml: 'ï',
  jcirc: 'ĵ',
  jcy: 'й',
  jfr: '𝔧',
  jmath: 'ȷ',
  jopf: '𝕛',
  jscr: '𝒿',
  jsercy: 'ј',
  jukcy: 'є',
  kappa: 'κ',
  kappav: 'ϰ',
  kcedil: 'ķ',
  kcy: 'к',
  kfr: '𝔨',
  kgreen: 'ĸ',
  khcy: 'х',
  kjcy: 'ќ',
  kopf: '𝕜',
  kscr: '𝓀',
  lAarr: '⇚',
  lArr: '⇐',
  lAtail: '⤛',
  lBarr: '⤎',
  lE: '≦',
  lEg: '⪋',
  lHar: '⥢',
  lacute: 'ĺ',
  laemptyv: '⦴',
  lagran: 'ℒ',
  lambda: 'λ',
  lang: '⟨',
  langd: '⦑',
  langle: '⟨',
  lap: '⪅',
  laquo: '«',
  larr: '←',
  larrb: '⇤',
  larrbfs: '⤟',
  larrfs: '⤝',
  larrhk: '↩',
  larrlp: '↫',
  larrpl: '⤹',
  larrsim: '⥳',
  larrtl: '↢',
  lat: '⪫',
  latail: '⤙',
  late: '⪭',
  lates: '⪭︀',
  lbarr: '⤌',
  lbbrk: '❲',
  lbrace: '{',
  lbrack: '[',
  lbrke: '⦋',
  lbrksld: '⦏',
  lbrkslu: '⦍',
  lcaron: 'ľ',
  lcedil: 'ļ',
  lceil: '⌈',
  lcub: '{',
  lcy: 'л',
  ldca: '⤶',
  ldquo: '“',
  ldquor: '„',
  ldrdhar: '⥧',
  ldrushar: '⥋',
  ldsh: '↲',
  le: '≤',
  leftarrow: '←',
  leftarrowtail: '↢',
  leftharpoondown: '↽',
  leftharpoonup: '↼',
  leftleftarrows: '⇇',
  leftrightarrow: '↔',
  leftrightarrows: '⇆',
  leftrightharpoons: '⇋',
  leftrightsquigarrow: '↭',
  leftthreetimes: '⋋',
  leg: '⋚',
  leq: '≤',
  leqq: '≦',
  leqslant: '⩽',
  les: '⩽',
  lescc: '⪨',
  lesdot: '⩿',
  lesdoto: '⪁',
  lesdotor: '⪃',
  lesg: '⋚︀',
  lesges: '⪓',
  lessapprox: '⪅',
  lessdot: '⋖',
  lesseqgtr: '⋚',
  lesseqqgtr: '⪋',
  lessgtr: '≶',
  lesssim: '≲',
  lfisht: '⥼',
  lfloor: '⌊',
  lfr: '𝔩',
  lg: '≶',
  lgE: '⪑',
  lhard: '↽',
  lharu: '↼',
  lharul: '⥪',
  lhblk: '▄',
  ljcy: 'љ',
  ll: '≪',
  llarr: '⇇',
  llcorner: '⌞',
  llhard: '⥫',
  lltri: '◺',
  lmidot: 'ŀ',
  lmoust: '⎰',
  lmoustache: '⎰',
  lnE: '≨',
  lnap: '⪉',
  lnapprox: '⪉',
  lne: '⪇',
  lneq: '⪇',
  lneqq: '≨',
  lnsim: '⋦',
  loang: '⟬',
  loarr: '⇽',
  lobrk: '⟦',
  longleftarrow: '⟵',
  longleftrightarrow: '⟷',
  longmapsto: '⟼',
  longrightarrow: '⟶',
  looparrowleft: '↫',
  looparrowright: '↬',
  lopar: '⦅',
  lopf: '𝕝',
  loplus: '⨭',
  lotimes: '⨴',
  lowast: '∗',
  lowbar: '_',
  loz: '◊',
  lozenge: '◊',
  lozf: '⧫',
  lpar: '(',
  lparlt: '⦓',
  lrarr: '⇆',
  lrcorner: '⌟',
  lrhar: '⇋',
  lrhard: '⥭',
  lrm: '‎',
  lrtri: '⊿',
  lsaquo: '‹',
  lscr: '𝓁',
  lsh: '↰',
  lsim: '≲',
  lsime: '⪍',
  lsimg: '⪏',
  lsqb: '[',
  lsquo: '‘',
  lsquor: '‚',
  lstrok: 'ł',
  lt: '<',
  ltcc: '⪦',
  ltcir: '⩹',
  ltdot: '⋖',
  lthree: '⋋',
  ltimes: '⋉',
  ltlarr: '⥶',
  ltquest: '⩻',
  ltrPar: '⦖',
  ltri: '◃',
  ltrie: '⊴',
  ltrif: '◂',
  lurdshar: '⥊',
  luruhar: '⥦',
  lvertneqq: '≨︀',
  lvnE: '≨︀',
  mDDot: '∺',
  macr: '¯',
  male: '♂',
  malt: '✠',
  maltese: '✠',
  map: '↦',
  mapsto: '↦',
  mapstodown: '↧',
  mapstoleft: '↤',
  mapstoup: '↥',
  marker: '▮',
  mcomma: '⨩',
  mcy: 'м',
  mdash: '—',
  measuredangle: '∡',
  mfr: '𝔪',
  mho: '℧',
  micro: 'µ',
  mid: '∣',
  midast: '*',
  midcir: '⫰',
  middot: '·',
  minus: '−',
  minusb: '⊟',
  minusd: '∸',
  minusdu: '⨪',
  mlcp: '⫛',
  mldr: '…',
  mnplus: '∓',
  models: '⊧',
  mopf: '𝕞',
  mp: '∓',
  mscr: '𝓂',
  mstpos: '∾',
  mu: 'μ',
  multimap: '⊸',
  mumap: '⊸',
  nGg: '⋙̸',
  nGt: '≫⃒',
  nGtv: '≫̸',
  nLeftarrow: '⇍',
  nLeftrightarrow: '⇎',
  nLl: '⋘̸',
  nLt: '≪⃒',
  nLtv: '≪̸',
  nRightarrow: '⇏',
  nVDash: '⊯',
  nVdash: '⊮',
  nabla: '∇',
  nacute: 'ń',
  nang: '∠⃒',
  nap: '≉',
  napE: '⩰̸',
  napid: '≋̸',
  napos: 'ŉ',
  napprox: '≉',
  natur: '♮',
  natural: '♮',
  naturals: 'ℕ',
  nbsp: ' ',
  nbump: '≎̸',
  nbumpe: '≏̸',
  ncap: '⩃',
  ncaron: 'ň',
  ncedil: 'ņ',
  ncong: '≇',
  ncongdot: '⩭̸',
  ncup: '⩂',
  ncy: 'н',
  ndash: '–',
  ne: '≠',
  neArr: '⇗',
  nearhk: '⤤',
  nearr: '↗',
  nearrow: '↗',
  nedot: '≐̸',
  nequiv: '≢',
  nesear: '⤨',
  nesim: '≂̸',
  nexist: '∄',
  nexists: '∄',
  nfr: '𝔫',
  ngE: '≧̸',
  nge: '≱',
  ngeq: '≱',
  ngeqq: '≧̸',
  ngeqslant: '⩾̸',
  nges: '⩾̸',
  ngsim: '≵',
  ngt: '≯',
  ngtr: '≯',
  nhArr: '⇎',
  nharr: '↮',
  nhpar: '⫲',
  ni: '∋',
  nis: '⋼',
  nisd: '⋺',
  niv: '∋',
  njcy: 'њ',
  nlArr: '⇍',
  nlE: '≦̸',
  nlarr: '↚',
  nldr: '‥',
  nle: '≰',
  nleftarrow: '↚',
  nleftrightarrow: '↮',
  nleq: '≰',
  nleqq: '≦̸',
  nleqslant: '⩽̸',
  nles: '⩽̸',
  nless: '≮',
  nlsim: '≴',
  nlt: '≮',
  nltri: '⋪',
  nltrie: '⋬',
  nmid: '∤',
  nopf: '𝕟',
  not: '¬',
  notin: '∉',
  notinE: '⋹̸',
  notindot: '⋵̸',
  notinva: '∉',
  notinvb: '⋷',
  notinvc: '⋶',
  notni: '∌',
  notniva: '∌',
  notnivb: '⋾',
  notnivc: '⋽',
  npar: '∦',
  nparallel: '∦',
  nparsl: '⫽⃥',
  npart: '∂̸',
  npolint: '⨔',
  npr: '⊀',
  nprcue: '⋠',
  npre: '⪯̸',
  nprec: '⊀',
  npreceq: '⪯̸',
  nrArr: '⇏',
  nrarr: '↛',
  nrarrc: '⤳̸',
  nrarrw: '↝̸',
  nrightarrow: '↛',
  nrtri: '⋫',
  nrtrie: '⋭',
  nsc: '⊁',
  nsccue: '⋡',
  nsce: '⪰̸',
  nscr: '𝓃',
  nshortmid: '∤',
  nshortparallel: '∦',
  nsim: '≁',
  nsime: '≄',
  nsimeq: '≄',
  nsmid: '∤',
  nspar: '∦',
  nsqsube: '⋢',
  nsqsupe: '⋣',
  nsub: '⊄',
  nsubE: '⫅̸',
  nsube: '⊈',
  nsubset: '⊂⃒',
  nsubseteq: '⊈',
  nsubseteqq: '⫅̸',
  nsucc: '⊁',
  nsucceq: '⪰̸',
  nsup: '⊅',
  nsupE: '⫆̸',
  nsupe: '⊉',
  nsupset: '⊃⃒',
  nsupseteq: '⊉',
  nsupseteqq: '⫆̸',
  ntgl: '≹',
  ntilde: 'ñ',
  ntlg: '≸',
  ntriangleleft: '⋪',
  ntrianglelefteq: '⋬',
  ntriangleright: '⋫',
  ntrianglerighteq: '⋭',
  nu: 'ν',
  num: '#',
  numero: '№',
  numsp: ' ',
  nvDash: '⊭',
  nvHarr: '⤄',
  nvap: '≍⃒',
  nvdash: '⊬',
  nvge: '≥⃒',
  nvgt: '>⃒',
  nvinfin: '⧞',
  nvlArr: '⤂',
  nvle: '≤⃒',
  nvlt: '<⃒',
  nvltrie: '⊴⃒',
  nvrArr: '⤃',
  nvrtrie: '⊵⃒',
  nvsim: '∼⃒',
  nwArr: '⇖',
  nwarhk: '⤣',
  nwarr: '↖',
  nwarrow: '↖',
  nwnear: '⤧',
  oS: 'Ⓢ',
  oacute: 'ó',
  oast: '⊛',
  ocir: '⊚',
  ocirc: 'ô',
  ocy: 'о',
  odash: '⊝',
  odblac: 'ő',
  odiv: '⨸',
  odot: '⊙',
  odsold: '⦼',
  oelig: 'œ',
  ofcir: '⦿',
  ofr: '𝔬',
  ogon: '˛',
  ograve: 'ò',
  ogt: '⧁',
  ohbar: '⦵',
  ohm: 'Ω',
  oint: '∮',
  olarr: '↺',
  olcir: '⦾',
  olcross: '⦻',
  oline: '‾',
  olt: '⧀',
  omacr: 'ō',
  omega: 'ω',
  omicron: 'ο',
  omid: '⦶',
  ominus: '⊖',
  oopf: '𝕠',
  opar: '⦷',
  operp: '⦹',
  oplus: '⊕',
  or: '∨',
  orarr: '↻',
  ord: '⩝',
  order: 'ℴ',
  orderof: 'ℴ',
  ordf: 'ª',
  ordm: 'º',
  origof: '⊶',
  oror: '⩖',
  orslope: '⩗',
  orv: '⩛',
  oscr: 'ℴ',
  oslash: 'ø',
  osol: '⊘',
  otilde: 'õ',
  otimes: '⊗',
  otimesas: '⨶',
  ouml: 'ö',
  ovbar: '⌽',
  par: '∥',
  para: '¶',
  parallel: '∥',
  parsim: '⫳',
  parsl: '⫽',
  part: '∂',
  pcy: 'п',
  percnt: '%',
  period: '.',
  permil: '‰',
  perp: '⊥',
  pertenk: '‱',
  pfr: '𝔭',
  phi: 'φ',
  phiv: 'ϕ',
  phmmat: 'ℳ',
  phone: '☎',
  pi: 'π',
  pitchfork: '⋔',
  piv: 'ϖ',
  planck: 'ℏ',
  planckh: 'ℎ',
  plankv: 'ℏ',
  plus: '+',
  plusacir: '⨣',
  plusb: '⊞',
  pluscir: '⨢',
  plusdo: '∔',
  plusdu: '⨥',
  pluse: '⩲',
  plusmn: '±',
  plussim: '⨦',
  plustwo: '⨧',
  pm: '±',
  pointint: '⨕',
  popf: '𝕡',
  pound: '£',
  pr: '≺',
  prE: '⪳',
  prap: '⪷',
  prcue: '≼',
  pre: '⪯',
  prec: '≺',
  precapprox: '⪷',
  preccurlyeq: '≼',
  preceq: '⪯',
  precnapprox: '⪹',
  precneqq: '⪵',
  precnsim: '⋨',
  precsim: '≾',
  prime: '′',
  primes: 'ℙ',
  prnE: '⪵',
  prnap: '⪹',
  prnsim: '⋨',
  prod: '∏',
  profalar: '⌮',
  profline: '⌒',
  profsurf: '⌓',
  prop: '∝',
  propto: '∝',
  prsim: '≾',
  prurel: '⊰',
  pscr: '𝓅',
  psi: 'ψ',
  puncsp: ' ',
  qfr: '𝔮',
  qint: '⨌',
  qopf: '𝕢',
  qprime: '⁗',
  qscr: '𝓆',
  quaternions: 'ℍ',
  quatint: '⨖',
  quest: '?',
  questeq: '≟',
  quot: '"',
  rAarr: '⇛',
  rArr: '⇒',
  rAtail: '⤜',
  rBarr: '⤏',
  rHar: '⥤',
  race: '∽̱',
  racute: 'ŕ',
  radic: '√',
  raemptyv: '⦳',
  rang: '⟩',
  rangd: '⦒',
  range: '⦥',
  rangle: '⟩',
  raquo: '»',
  rarr: '→',
  rarrap: '⥵',
  rarrb: '⇥',
  rarrbfs: '⤠',
  rarrc: '⤳',
  rarrfs: '⤞',
  rarrhk: '↪',
  rarrlp: '↬',
  rarrpl: '⥅',
  rarrsim: '⥴',
  rarrtl: '↣',
  rarrw: '↝',
  ratail: '⤚',
  ratio: '∶',
  rationals: 'ℚ',
  rbarr: '⤍',
  rbbrk: '❳',
  rbrace: '}',
  rbrack: ']',
  rbrke: '⦌',
  rbrksld: '⦎',
  rbrkslu: '⦐',
  rcaron: 'ř',
  rcedil: 'ŗ',
  rceil: '⌉',
  rcub: '}',
  rcy: 'р',
  rdca: '⤷',
  rdldhar: '⥩',
  rdquo: '”',
  rdquor: '”',
  rdsh: '↳',
  real: 'ℜ',
  realine: 'ℛ',
  realpart: 'ℜ',
  reals: 'ℝ',
  rect: '▭',
  reg: '®',
  rfisht: '⥽',
  rfloor: '⌋',
  rfr: '𝔯',
  rhard: '⇁',
  rharu: '⇀',
  rharul: '⥬',
  rho: 'ρ',
  rhov: 'ϱ',
  rightarrow: '→',
  rightarrowtail: '↣',
  rightharpoondown: '⇁',
  rightharpoonup: '⇀',
  rightleftarrows: '⇄',
  rightleftharpoons: '⇌',
  rightrightarrows: '⇉',
  rightsquigarrow: '↝',
  rightthreetimes: '⋌',
  ring: '˚',
  risingdotseq: '≓',
  rlarr: '⇄',
  rlhar: '⇌',
  rlm: '‏',
  rmoust: '⎱',
  rmoustache: '⎱',
  rnmid: '⫮',
  roang: '⟭',
  roarr: '⇾',
  robrk: '⟧',
  ropar: '⦆',
  ropf: '𝕣',
  roplus: '⨮',
  rotimes: '⨵',
  rpar: ')',
  rpargt: '⦔',
  rppolint: '⨒',
  rrarr: '⇉',
  rsaquo: '›',
  rscr: '𝓇',
  rsh: '↱',
  rsqb: ']',
  rsquo: '’',
  rsquor: '’',
  rthree: '⋌',
  rtimes: '⋊',
  rtri: '▹',
  rtrie: '⊵',
  rtrif: '▸',
  rtriltri: '⧎',
  ruluhar: '⥨',
  rx: '℞',
  sacute: 'ś',
  sbquo: '‚',
  sc: '≻',
  scE: '⪴',
  scap: '⪸',
  scaron: 'š',
  sccue: '≽',
  sce: '⪰',
  scedil: 'ş',
  scirc: 'ŝ',
  scnE: '⪶',
  scnap: '⪺',
  scnsim: '⋩',
  scpolint: '⨓',
  scsim: '≿',
  scy: 'с',
  sdot: '⋅',
  sdotb: '⊡',
  sdote: '⩦',
  seArr: '⇘',
  searhk: '⤥',
  searr: '↘',
  searrow: '↘',
  sect: '§',
  semi: ';',
  seswar: '⤩',
  setminus: '∖',
  setmn: '∖',
  sext: '✶',
  sfr: '𝔰',
  sfrown: '⌢',
  sharp: '♯',
  shchcy: 'щ',
  shcy: 'ш',
  shortmid: '∣',
  shortparallel: '∥',
  shy: '­',
  sigma: 'σ',
  sigmaf: 'ς',
  sigmav: 'ς',
  sim: '∼',
  simdot: '⩪',
  sime: '≃',
  simeq: '≃',
  simg: '⪞',
  simgE: '⪠',
  siml: '⪝',
  simlE: '⪟',
  simne: '≆',
  simplus: '⨤',
  simrarr: '⥲',
  slarr: '←',
  smallsetminus: '∖',
  smashp: '⨳',
  smeparsl: '⧤',
  smid: '∣',
  smile: '⌣',
  smt: '⪪',
  smte: '⪬',
  smtes: '⪬︀',
  softcy: 'ь',
  sol: '/',
  solb: '⧄',
  solbar: '⌿',
  sopf: '𝕤',
  spades: '♠',
  spadesuit: '♠',
  spar: '∥',
  sqcap: '⊓',
  sqcaps: '⊓︀',
  sqcup: '⊔',
  sqcups: '⊔︀',
  sqsub: '⊏',
  sqsube: '⊑',
  sqsubset: '⊏',
  sqsubseteq: '⊑',
  sqsup: '⊐',
  sqsupe: '⊒',
  sqsupset: '⊐',
  sqsupseteq: '⊒',
  squ: '□',
  square: '□',
  squarf: '▪',
  squf: '▪',
  srarr: '→',
  sscr: '𝓈',
  ssetmn: '∖',
  ssmile: '⌣',
  sstarf: '⋆',
  star: '☆',
  starf: '★',
  straightepsilon: 'ϵ',
  straightphi: 'ϕ',
  strns: '¯',
  sub: '⊂',
  subE: '⫅',
  subdot: '⪽',
  sube: '⊆',
  subedot: '⫃',
  submult: '⫁',
  subnE: '⫋',
  subne: '⊊',
  subplus: '⪿',
  subrarr: '⥹',
  subset: '⊂',
  subseteq: '⊆',
  subseteqq: '⫅',
  subsetneq: '⊊',
  subsetneqq: '⫋',
  subsim: '⫇',
  subsub: '⫕',
  subsup: '⫓',
  succ: '≻',
  succapprox: '⪸',
  succcurlyeq: '≽',
  succeq: '⪰',
  succnapprox: '⪺',
  succneqq: '⪶',
  succnsim: '⋩',
  succsim: '≿',
  sum: '∑',
  sung: '♪',
  sup1: '¹',
  sup2: '²',
  sup3: '³',
  sup: '⊃',
  supE: '⫆',
  supdot: '⪾',
  supdsub: '⫘',
  supe: '⊇',
  supedot: '⫄',
  suphsol: '⟉',
  suphsub: '⫗',
  suplarr: '⥻',
  supmult: '⫂',
  supnE: '⫌',
  supne: '⊋',
  supplus: '⫀',
  supset: '⊃',
  supseteq: '⊇',
  supseteqq: '⫆',
  supsetneq: '⊋',
  supsetneqq: '⫌',
  supsim: '⫈',
  supsub: '⫔',
  supsup: '⫖',
  swArr: '⇙',
  swarhk: '⤦',
  swarr: '↙',
  swarrow: '↙',
  swnwar: '⤪',
  szlig: 'ß',
  target: '⌖',
  tau: 'τ',
  tbrk: '⎴',
  tcaron: 'ť',
  tcedil: 'ţ',
  tcy: 'т',
  tdot: '⃛',
  telrec: '⌕',
  tfr: '𝔱',
  there4: '∴',
  therefore: '∴',
  theta: 'θ',
  thetasym: 'ϑ',
  thetav: 'ϑ',
  thickapprox: '≈',
  thicksim: '∼',
  thinsp: ' ',
  thkap: '≈',
  thksim: '∼',
  thorn: 'þ',
  tilde: '˜',
  times: '×',
  timesb: '⊠',
  timesbar: '⨱',
  timesd: '⨰',
  tint: '∭',
  toea: '⤨',
  top: '⊤',
  topbot: '⌶',
  topcir: '⫱',
  topf: '𝕥',
  topfork: '⫚',
  tosa: '⤩',
  tprime: '‴',
  trade: '™',
  triangle: '▵',
  triangledown: '▿',
  triangleleft: '◃',
  trianglelefteq: '⊴',
  triangleq: '≜',
  triangleright: '▹',
  trianglerighteq: '⊵',
  tridot: '◬',
  trie: '≜',
  triminus: '⨺',
  triplus: '⨹',
  trisb: '⧍',
  tritime: '⨻',
  trpezium: '⏢',
  tscr: '𝓉',
  tscy: 'ц',
  tshcy: 'ћ',
  tstrok: 'ŧ',
  twixt: '≬',
  twoheadleftarrow: '↞',
  twoheadrightarrow: '↠',
  uArr: '⇑',
  uHar: '⥣',
  uacute: 'ú',
  uarr: '↑',
  ubrcy: 'ў',
  ubreve: 'ŭ',
  ucirc: 'û',
  ucy: 'у',
  udarr: '⇅',
  udblac: 'ű',
  udhar: '⥮',
  ufisht: '⥾',
  ufr: '𝔲',
  ugrave: 'ù',
  uharl: '↿',
  uharr: '↾',
  uhblk: '▀',
  ulcorn: '⌜',
  ulcorner: '⌜',
  ulcrop: '⌏',
  ultri: '◸',
  umacr: 'ū',
  uml: '¨',
  uogon: 'ų',
  uopf: '𝕦',
  uparrow: '↑',
  updownarrow: '↕',
  upharpoonleft: '↿',
  upharpoonright: '↾',
  uplus: '⊎',
  upsi: 'υ',
  upsih: 'ϒ',
  upsilon: 'υ',
  upuparrows: '⇈',
  urcorn: '⌝',
  urcorner: '⌝',
  urcrop: '⌎',
  uring: 'ů',
  urtri: '◹',
  uscr: '𝓊',
  utdot: '⋰',
  utilde: 'ũ',
  utri: '▵',
  utrif: '▴',
  uuarr: '⇈',
  uuml: 'ü',
  uwangle: '⦧',
  vArr: '⇕',
  vBar: '⫨',
  vBarv: '⫩',
  vDash: '⊨',
  vangrt: '⦜',
  varepsilon: 'ϵ',
  varkappa: 'ϰ',
  varnothing: '∅',
  varphi: 'ϕ',
  varpi: 'ϖ',
  varpropto: '∝',
  varr: '↕',
  varrho: 'ϱ',
  varsigma: 'ς',
  varsubsetneq: '⊊︀',
  varsubsetneqq: '⫋︀',
  varsupsetneq: '⊋︀',
  varsupsetneqq: '⫌︀',
  vartheta: 'ϑ',
  vartriangleleft: '⊲',
  vartriangleright: '⊳',
  vcy: 'в',
  vdash: '⊢',
  vee: '∨',
  veebar: '⊻',
  veeeq: '≚',
  vellip: '⋮',
  verbar: '|',
  vert: '|',
  vfr: '𝔳',
  vltri: '⊲',
  vnsub: '⊂⃒',
  vnsup: '⊃⃒',
  vopf: '𝕧',
  vprop: '∝',
  vrtri: '⊳',
  vscr: '𝓋',
  vsubnE: '⫋︀',
  vsubne: '⊊︀',
  vsupnE: '⫌︀',
  vsupne: '⊋︀',
  vzigzag: '⦚',
  wcirc: 'ŵ',
  wedbar: '⩟',
  wedge: '∧',
  wedgeq: '≙',
  weierp: '℘',
  wfr: '𝔴',
  wopf: '𝕨',
  wp: '℘',
  wr: '≀',
  wreath: '≀',
  wscr: '𝓌',
  xcap: '⋂',
  xcirc: '◯',
  xcup: '⋃',
  xdtri: '▽',
  xfr: '𝔵',
  xhArr: '⟺',
  xharr: '⟷',
  xi: 'ξ',
  xlArr: '⟸',
  xlarr: '⟵',
  xmap: '⟼',
  xnis: '⋻',
  xodot: '⨀',
  xopf: '𝕩',
  xoplus: '⨁',
  xotime: '⨂',
  xrArr: '⟹',
  xrarr: '⟶',
  xscr: '𝓍',
  xsqcup: '⨆',
  xuplus: '⨄',
  xutri: '△',
  xvee: '⋁',
  xwedge: '⋀',
  yacute: 'ý',
  yacy: 'я',
  ycirc: 'ŷ',
  ycy: 'ы',
  yen: '¥',
  yfr: '𝔶',
  yicy: 'ї',
  yopf: '𝕪',
  yscr: '𝓎',
  yucy: 'ю',
  yuml: 'ÿ',
  zacute: 'ź',
  zcaron: 'ž',
  zcy: 'з',
  zdot: 'ż',
  zeetrf: 'ℨ',
  zeta: 'ζ',
  zfr: '𝔷',
  zhcy: 'ж',
  zigrarr: '⇝',
  zopf: '𝕫',
  zscr: '𝓏',
  zwj: '‍',
  zwnj: '‌'
}
{
  "name": "character-entities",
  "version": "2.0.2",
  "description": "Map of named character references",
  "license": "MIT",
  "keywords": [
    "html",
    "entity",
    "entities",
    "character",
    "reference",
    "name",
    "replacement"
  ],
  "repository": "wooorm/character-entities",
  "bugs": "https://github.com/wooorm/character-entities/issues",
  "funding": {
    "type": "github",
    "url": "https://github.com/sponsors/wooorm"
  },
  "author": "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)",
  "contributors": [
    "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)"
  ],
  "sideEffects": false,
  "type": "module",
  "main": "index.js",
  "types": "index.d.ts",
  "files": [
    "index.d.ts",
    "index.js"
  ],
  "devDependencies": {
    "@types/tape": "^4.0.0",
    "bail": "^2.0.0",
    "c8": "^7.0.0",
    "concat-stream": "^2.0.0",
    "prettier": "^2.0.0",
    "remark-cli": "^10.0.0",
    "remark-preset-wooorm": "^9.0.0",
    "rimraf": "^3.0.0",
    "tape": "^5.0.0",
    "type-coverage": "^2.0.0",
    "typescript": "^4.0.0",
    "xo": "^0.50.0"
  },
  "scripts": {
    "generate": "node build",
    "prepublishOnly": "npm run build && npm run format",
    "build": "rimraf \"*.d.ts\" && tsc && type-coverage",
    "format": "remark . -qfo && prettier . -w --loglevel warn && xo --fix",
    "test-api": "node --conditions development test.js",
    "test-coverage": "c8 --check-coverage --branches 100 --functions 100 --lines 100 --statements 100 --reporter lcov npm run test-api",
    "test": "npm run generate && npm run build && npm run format && npm run test-coverage"
  },
  "prettier": {
    "tabWidth": 2,
    "useTabs": false,
    "singleQuote": true,
    "bracketSpacing": false,
    "semi": false,
    "trailingComma": "none"
  },
  "xo": {
    "prettier": true
  },
  "remarkConfig": {
    "plugins": [
      "preset-wooorm"
    ]
  },
  "typeCoverage": {
    "atLeast": 100,
    "detail": true,
    "strict": true,
    "ignoreCatch": true
  }
}
/**
 * Map of invalid numeric character references to their replacements, according to HTML.
 *
 * @type {Record<number, string>}
 */
export const characterReferenceInvalid = {
  0: '�',
  128: '€',
  130: '‚',
  131: 'ƒ',
  132: '„',
  133: '…',
  134: '†',
  135: '‡',
  136: 'ˆ',
  137: '‰',
  138: 'Š',
  139: '‹',
  140: 'Œ',
  142: 'Ž',
  145: '‘',
  146: '’',
  147: '“',
  148: '”',
  149: '•',
  150: '–',
  151: '—',
  152: '˜',
  153: '™',
  154: 'š',
  155: '›',
  156: 'œ',
  158: 'ž',
  159: 'Ÿ'
}
{
  "name": "character-reference-invalid",
  "version": "2.0.1",
  "description": "Map of invalid numeric character references to their replacements, according to HTML",
  "license": "MIT",
  "keywords": [
    "html",
    "entity",
    "numeric",
    "character",
    "reference",
    "replacement",
    "invalid",
    "name"
  ],
  "repository": "wooorm/character-reference-invalid",
  "bugs": "https://github.com/wooorm/character-reference-invalid/issues",
  "funding": {
    "type": "github",
    "url": "https://github.com/sponsors/wooorm"
  },
  "author": "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)",
  "contributors": [
    "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)"
  ],
  "sideEffects": false,
  "type": "module",
  "main": "index.js",
  "types": "index.d.ts",
  "files": [
    "index.d.ts",
    "index.js"
  ],
  "devDependencies": {
    "@types/tape": "^4.0.0",
    "bail": "^2.0.0",
    "c8": "^7.0.0",
    "concat-stream": "^2.0.0",
    "hast-util-select": "^5.0.0",
    "hast-util-to-string": "^2.0.0",
    "prettier": "^2.0.0",
    "rehype-parse": "^8.0.0",
    "remark-cli": "^10.0.0",
    "remark-preset-wooorm": "^9.0.0",
    "rimraf": "^3.0.0",
    "tape": "^5.0.0",
    "type-coverage": "^2.0.0",
    "typescript": "^4.0.0",
    "unified": "^10.0.0",
    "xo": "^0.45.0"
  },
  "scripts": {
    "prepublishOnly": "npm run build && npm run format",
    "generate": "node build",
    "build": "rimraf \"*.d.ts\" && tsc && type-coverage",
    "format": "remark . -qfo && prettier . -w --loglevel warn && xo --fix",
    "test-api": "node --conditions development test.js",
    "test-coverage": "c8 --check-coverage --branches 100 --functions 100 --lines 100 --statements 100 --reporter lcov npm run test-api",
    "test": "npm run generate && npm run build && npm run format && npm run test-coverage"
  },
  "prettier": {
    "tabWidth": 2,
    "useTabs": false,
    "singleQuote": true,
    "bracketSpacing": false,
    "semi": false,
    "trailingComma": "none"
  },
  "xo": {
    "prettier": true
  },
  "remarkConfig": {
    "plugins": [
      "preset-wooorm"
    ]
  },
  "typeCoverage": {
    "atLeast": 100,
    "detail": true,
    "strict": true,
    "ignoreCatch": true
  }
}
import {characterEntities} from 'character-entities'

// To do: next major: use `Object.hasOwn`.
const own = {}.hasOwnProperty

/**
 * Decode a single character reference (without the `&` or `;`).
 * You probably only need this when you’re building parsers yourself that follow
 * different rules compared to HTML.
 * This is optimized to be tiny in browsers.
 *
 * @param {string} value
 *   `notin` (named), `#123` (deci), `#x123` (hexa).
 * @returns {string|false}
 *   Decoded reference.
 */
export function decodeNamedCharacterReference(value) {
  return own.call(characterEntities, value) ? characterEntities[value] : false
}
{
  "author": "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)",
  "bugs": "https://github.com/wooorm/decode-named-character-reference/issues",
  "contributors": [
    "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)"
  ],
  "dependencies": {
    "character-entities": "^2.0.0"
  },
  "description": "Decode named character references",
  "devDependencies": {
    "@types/node": "^25.0.0",
    "c8": "^10.0.0",
    "prettier": "^3.0.0",
    "remark-cli": "^12.0.0",
    "remark-preset-wooorm": "^11.0.0",
    "type-coverage": "^2.0.0",
    "typescript": "^5.0.0",
    "xo": "^1.0.0"
  },
  "exports": {
    "convex": "./index.js",
    "deno": "./index.js",
    "edge-light": "./index.js",
    "react-native": "./index.js",
    "worker": "./index.js",
    "workerd": "./index.js",
    "browser": "./index.dom.js",
    "default": "./index.js"
  },
  "files": [
    "index.d.ts.map",
    "index.d.ts",
    "index.dom.d.ts.map",
    "index.dom.d.ts",
    "index.dom.js",
    "index.js"
  ],
  "funding": {
    "type": "github",
    "url": "https://github.com/sponsors/wooorm"
  },
  "keywords": [
    "character",
    "decode",
    "named",
    "references"
  ],
  "license": "MIT",
  "main#": "to do: next major: remove field",
  "main": "index.js",
  "name": "decode-named-character-reference",
  "prettier": {
    "bracketSpacing": false,
    "semi": false,
    "singleQuote": true,
    "tabWidth": 2,
    "trailingComma": "none",
    "useTabs": false
  },
  "remarkConfig": {
    "plugins": [
      "remark-preset-wooorm"
    ]
  },
  "repository": "wooorm/decode-named-character-reference",
  "scripts": {
    "build": "tsc --build --clean && tsc --build && type-coverage",
    "format": "remark --frail --output --quiet -- . && prettier --log-level warn --write -- . && xo --fix",
    "test-api": "node --conditions development test.js",
    "test-coverage": "c8 --100 --reporter lcov -- npm run test-api",
    "test": "npm run build && npm run format && npm run test-coverage"
  },
  "sideEffects": false,
  "typeCoverage": {
    "atLeast": 100,
    "ignoreCatch": true
  },
  "types#": "to do: next major: remove field",
  "types": "index.d.ts",
  "type": "module",
  "version": "1.3.0",
  "xo": {
    "prettier": true,
    "rules": {
      "unicorn/prefer-code-point": "off"
    },
    "space": true
  }
}
export function deprecate(fn) {
  return fn
}

export function equal() {}

export function ok() {}

export function unreachable() {}
{
  "name": "devlop",
  "version": "1.1.0",
  "description": "Do things in development and nothing otherwise",
  "license": "MIT",
  "keywords": [
    "assert",
    "deprecate",
    "develop",
    "development"
  ],
  "repository": "wooorm/devlop",
  "bugs": "https://github.com/wooorm/devlop/issues",
  "funding": {
    "type": "github",
    "url": "https://github.com/sponsors/wooorm"
  },
  "author": "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)",
  "contributors": [
    "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)"
  ],
  "sideEffects": false,
  "type": "module",
  "exports": {
    "types": "./lib/development.d.ts",
    "development": "./lib/development.js",
    "default": "./lib/default.js"
  },
  "files": [
    "lib/"
  ],
  "dependencies": {
    "dequal": "^2.0.0"
  },
  "devDependencies": {
    "@rollup/plugin-node-resolve": "^15.1.0",
    "@rollup/plugin-terser": "^0.4.3",
    "@types/node": "^20.0.0",
    "c8": "^8.0.0",
    "esbuild": "^0.18.0",
    "prettier": "^2.0.0",
    "remark-cli": "^11.0.0",
    "remark-preset-wooorm": "^9.0.0",
    "type-coverage": "^2.0.0",
    "typescript": "^5.0.0",
    "xo": "^0.54.0"
  },
  "scripts": {
    "prepack": "npm run build && npm run format",
    "build": "tsc --build --clean && tsc --build && type-coverage",
    "format": "remark . -qfo && prettier . -w --loglevel warn && xo --fix",
    "test-api-development": "node --conditions development test-development.js",
    "test-api-default": "node test-default.js",
    "test-api": "npm run test-api-development && npm run test-api-default",
    "test-coverage": "c8 --100 --reporter lcov npm run test-api",
    "test": "npm run build && npm run format && npm run test-coverage"
  },
  "prettier": {
    "bracketSpacing": false,
    "semi": false,
    "singleQuote": true,
    "tabWidth": 2,
    "trailingComma": "none",
    "useTabs": false
  },
  "remarkConfig": {
    "plugins": [
      "remark-preset-wooorm"
    ]
  },
  "typeCoverage": {
    "atLeast": 100,
    "detail": true,
    "ignoreCatch": true,
    "strict": true
  },
  "xo": {
    "prettier": true
  }
}
/**
 * Check if the given character code, or the character code at the first
 * character, is alphabetical.
 *
 * @param {string|number} character
 * @returns {boolean} Whether `character` is alphabetical.
 */
export function isAlphabetical(character) {
  const code =
    typeof character === 'string' ? character.charCodeAt(0) : character

  return (
    (code >= 97 && code <= 122) /* a-z */ ||
    (code >= 65 && code <= 90) /* A-Z */
  )
}
{
  "name": "is-alphabetical",
  "version": "2.0.1",
  "description": "Check if a character is alphabetical",
  "license": "MIT",
  "keywords": [
    "string",
    "character",
    "char",
    "code",
    "alphabetical"
  ],
  "repository": "wooorm/is-alphabetical",
  "bugs": "https://github.com/wooorm/is-alphabetical/issues",
  "funding": {
    "type": "github",
    "url": "https://github.com/sponsors/wooorm"
  },
  "author": "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)",
  "contributors": [
    "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)"
  ],
  "sideEffects": false,
  "type": "module",
  "main": "index.js",
  "types": "index.d.ts",
  "files": [
    "index.d.ts",
    "index.js"
  ],
  "devDependencies": {
    "@types/tape": "^4.0.0",
    "c8": "^7.0.0",
    "prettier": "^2.0.0",
    "remark-cli": "^10.0.0",
    "remark-preset-wooorm": "^9.0.0",
    "rimraf": "^3.0.0",
    "tape": "^5.0.0",
    "type-coverage": "^2.0.0",
    "typescript": "^4.0.0",
    "xo": "^0.46.0"
  },
  "scripts": {
    "prepublishOnly": "npm run build && npm run format",
    "build": "rimraf \"*.d.ts\" && tsc && type-coverage",
    "format": "remark . -qfo && prettier . -w --loglevel warn && xo --fix",
    "test-api": "node --conditions development test.js",
    "test-coverage": "c8 --check-coverage --branches 100 --functions 100 --lines 100 --statements 100 --reporter lcov npm run test-api",
    "test": "npm run build && npm run format && npm run test-coverage"
  },
  "prettier": {
    "tabWidth": 2,
    "useTabs": false,
    "singleQuote": true,
    "bracketSpacing": false,
    "semi": false,
    "trailingComma": "none"
  },
  "xo": {
    "prettier": true
  },
  "remarkConfig": {
    "plugins": [
      "preset-wooorm"
    ]
  },
  "typeCoverage": {
    "atLeast": 100,
    "detail": true,
    "strict": true,
    "ignoreCatch": true
  }
}
import {isAlphabetical} from 'is-alphabetical'
import {isDecimal} from 'is-decimal'

/**
 * Check if the given character code, or the character code at the first
 * character, is alphanumerical.
 *
 * @param {string|number} character
 * @returns {boolean} Whether `character` is alphanumerical.
 */
export function isAlphanumerical(character) {
  return isAlphabetical(character) || isDecimal(character)
}
{
  "name": "is-alphanumerical",
  "version": "2.0.1",
  "description": "Check if a character is alphanumerical",
  "license": "MIT",
  "keywords": [
    "string",
    "character",
    "char",
    "code",
    "alphabetical",
    "numerical",
    "alphanumerical"
  ],
  "repository": "wooorm/is-alphanumerical",
  "bugs": "https://github.com/wooorm/is-alphanumerical/issues",
  "funding": {
    "type": "github",
    "url": "https://github.com/sponsors/wooorm"
  },
  "author": "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)",
  "contributors": [
    "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)"
  ],
  "sideEffects": false,
  "type": "module",
  "main": "index.js",
  "types": "index.d.ts",
  "files": [
    "index.d.ts",
    "index.js"
  ],
  "dependencies": {
    "is-alphabetical": "^2.0.0",
    "is-decimal": "^2.0.0"
  },
  "devDependencies": {
    "@types/tape": "^4.0.0",
    "c8": "^7.0.0",
    "prettier": "^2.0.0",
    "remark-cli": "^10.0.0",
    "remark-preset-wooorm": "^9.0.0",
    "rimraf": "^3.0.0",
    "tape": "^5.0.0",
    "type-coverage": "^2.0.0",
    "typescript": "^4.0.0",
    "xo": "^0.46.0"
  },
  "scripts": {
    "prepublishOnly": "npm run build && npm run format",
    "build": "rimraf \"*.d.ts\" && tsc && type-coverage",
    "format": "remark . -qfo && prettier . -w --loglevel warn && xo --fix",
    "test-api": "node --conditions development test.js",
    "test-coverage": "c8 --check-coverage --branches 100 --functions 100 --lines 100 --statements 100 --reporter lcov npm run test-api",
    "test": "npm run build && npm run format && npm run test-coverage"
  },
  "prettier": {
    "tabWidth": 2,
    "useTabs": false,
    "singleQuote": true,
    "bracketSpacing": false,
    "semi": false,
    "trailingComma": "none"
  },
  "xo": {
    "prettier": true
  },
  "remarkConfig": {
    "plugins": [
      "preset-wooorm"
    ]
  },
  "typeCoverage": {
    "atLeast": 100,
    "detail": true,
    "strict": true,
    "ignoreCatch": true
  }
}
/**
 * Check if the given character code, or the character code at the first
 * character, is decimal.
 *
 * @param {string|number} character
 * @returns {boolean} Whether `character` is a decimal
 */
export function isDecimal(character) {
  const code =
    typeof character === 'string' ? character.charCodeAt(0) : character

  return code >= 48 && code <= 57 /* 0-9 */
}
{
  "name": "is-decimal",
  "version": "2.0.1",
  "description": "Check if a character is decimal",
  "license": "MIT",
  "keywords": [
    "string",
    "character",
    "char",
    "code",
    "decimal"
  ],
  "repository": "wooorm/is-decimal",
  "bugs": "https://github.com/wooorm/is-decimal/issues",
  "funding": {
    "type": "github",
    "url": "https://github.com/sponsors/wooorm"
  },
  "author": "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)",
  "contributors": [
    "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)"
  ],
  "sideEffects": false,
  "type": "module",
  "main": "index.js",
  "types": "index.d.ts",
  "files": [
    "index.d.ts",
    "index.js"
  ],
  "devDependencies": {
    "@types/tape": "^4.0.0",
    "c8": "^7.0.0",
    "prettier": "^2.0.0",
    "remark-cli": "^10.0.0",
    "remark-preset-wooorm": "^9.0.0",
    "rimraf": "^3.0.0",
    "tape": "^5.0.0",
    "type-coverage": "^2.0.0",
    "typescript": "^4.0.0",
    "xo": "^0.46.0"
  },
  "scripts": {
    "prepublishOnly": "npm run build && npm run format",
    "build": "rimraf \"*.d.ts\" && tsc && type-coverage",
    "format": "remark . -qfo && prettier . -w --loglevel warn && xo --fix",
    "test-api": "node --conditions development test.js",
    "test-coverage": "c8 --check-coverage --branches 100 --functions 100 --lines 100 --statements 100 --reporter lcov npm run test-api",
    "test": "npm run build && npm run format && npm run test-coverage"
  },
  "prettier": {
    "tabWidth": 2,
    "useTabs": false,
    "singleQuote": true,
    "bracketSpacing": false,
    "semi": false,
    "trailingComma": "none"
  },
  "xo": {
    "prettier": true
  },
  "remarkConfig": {
    "plugins": [
      "preset-wooorm"
    ]
  },
  "typeCoverage": {
    "atLeast": 100,
    "detail": true,
    "strict": true,
    "ignoreCatch": true
  }
}
/**
 * Check if the given character code, or the character code at the first
 * character, is hexadecimal.
 *
 * @param {string|number} character
 * @returns {boolean} Whether `character` is hexadecimal
 */
export function isHexadecimal(character) {
  const code =
    typeof character === 'string' ? character.charCodeAt(0) : character

  return (
    (code >= 97 /* a */ && code <= 102) /* z */ ||
    (code >= 65 /* A */ && code <= 70) /* Z */ ||
    (code >= 48 /* A */ && code <= 57) /* Z */
  )
}
{
  "name": "is-hexadecimal",
  "version": "2.0.1",
  "description": "Check if a character is hexadecimal",
  "license": "MIT",
  "keywords": [
    "string",
    "character",
    "char",
    "code",
    "hexadecimal"
  ],
  "repository": "wooorm/is-hexadecimal",
  "bugs": "https://github.com/wooorm/is-hexadecimal/issues",
  "funding": {
    "type": "github",
    "url": "https://github.com/sponsors/wooorm"
  },
  "author": "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)",
  "contributors": [
    "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)"
  ],
  "sideEffects": false,
  "type": "module",
  "main": "index.js",
  "types": "index.d.ts",
  "files": [
    "index.d.ts",
    "index.js"
  ],
  "devDependencies": {
    "@types/tape": "^4.0.0",
    "c8": "^7.0.0",
    "prettier": "^2.0.0",
    "remark-cli": "^10.0.0",
    "remark-preset-wooorm": "^9.0.0",
    "rimraf": "^3.0.0",
    "tape": "^5.0.0",
    "type-coverage": "^2.0.0",
    "typescript": "^4.0.0",
    "xo": "^0.46.0"
  },
  "scripts": {
    "prepublishOnly": "npm run build && npm run format",
    "build": "rimraf \"*.d.ts\" && tsc && type-coverage",
    "format": "remark . -qfo && prettier . -w --loglevel warn && xo --fix",
    "test-api": "node --conditions development test.js",
    "test-coverage": "c8 --check-coverage --branches 100 --functions 100 --lines 100 --statements 100 --reporter lcov npm run test-api",
    "test": "npm run build && npm run format && npm run test-coverage"
  },
  "prettier": {
    "tabWidth": 2,
    "useTabs": false,
    "singleQuote": true,
    "bracketSpacing": false,
    "semi": false,
    "trailingComma": "none"
  },
  "xo": {
    "prettier": true
  },
  "remarkConfig": {
    "plugins": [
      "preset-wooorm"
    ]
  },
  "typeCoverage": {
    "atLeast": 100,
    "detail": true,
    "strict": true,
    "ignoreCatch": true
  }
}
/**
 * Get the count of the longest repeating streak of `substring` in `value`.
 *
 * @param {string} value
 *   Content to search in.
 * @param {string} substring
 *   Substring to look for, typically one character.
 * @returns {number}
 *   Count of most frequent adjacent `substring`s in `value`.
 */
export function longestStreak(value, substring) {
  const source = String(value)
  let index = source.indexOf(substring)
  let expected = index
  let count = 0
  let max = 0

  if (typeof substring !== 'string') {
    throw new TypeError('Expected substring')
  }

  while (index !== -1) {
    if (index === expected) {
      if (++count > max) {
        max = count
      }
    } else {
      count = 1
    }

    expected = index + substring.length
    index = source.indexOf(substring, expected)
  }

  return max
}
{
  "name": "longest-streak",
  "version": "3.1.0",
  "description": "Count the longest repeating streak of a substring",
  "license": "MIT",
  "keywords": [
    "count",
    "length",
    "longest",
    "repeating",
    "streak",
    "substring",
    "character"
  ],
  "repository": "wooorm/longest-streak",
  "bugs": "https://github.com/wooorm/longest-streak/issues",
  "funding": {
    "type": "github",
    "url": "https://github.com/sponsors/wooorm"
  },
  "author": "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)",
  "contributors": [
    "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)"
  ],
  "sideEffects": false,
  "type": "module",
  "main": "index.js",
  "types": "index.d.ts",
  "files": [
    "index.d.ts",
    "index.js"
  ],
  "devDependencies": {
    "@types/node": "^18.0.0",
    "c8": "^7.0.0",
    "prettier": "^2.0.0",
    "remark-cli": "^11.0.0",
    "remark-preset-wooorm": "^9.0.0",
    "type-coverage": "^2.0.0",
    "typescript": "^4.0.0",
    "xo": "^0.52.0"
  },
  "scripts": {
    "prepack": "npm run build && npm run format",
    "build": "tsc --build --clean && tsc --build && type-coverage",
    "format": "remark . -qfo && prettier . -w --loglevel warn && xo --fix",
    "test-api": "node --conditions development test.js",
    "test-coverage": "c8 --check-coverage --100 --reporter lcov npm run test-api",
    "test": "npm run build && npm run format && npm run test-coverage"
  },
  "prettier": {
    "tabWidth": 2,
    "useTabs": false,
    "singleQuote": true,
    "bracketSpacing": false,
    "semi": false,
    "trailingComma": "none"
  },
  "xo": {
    "prettier": true
  },
  "remarkConfig": {
    "plugins": [
      "preset-wooorm"
    ]
  },
  "typeCoverage": {
    "atLeast": 100,
    "detail": true,
    "strict": true,
    "ignoreCatch": true
  }
}
// To do: next major: remove.
/**
 * @typedef {Options} MarkdownTableOptions
 *   Configuration.
 */

/**
 * @typedef Options
 *   Configuration.
 * @property {boolean | null | undefined} [alignDelimiters=true]
 *   Whether to align the delimiters (default: `true`);
 *   they are aligned by default:
 *
 *   ```markdown
 *   | Alpha | B     |
 *   | ----- | ----- |
 *   | C     | Delta |
 *   ```
 *
 *   Pass `false` to make them staggered:
 *
 *   ```markdown
 *   | Alpha | B |
 *   | - | - |
 *   | C | Delta |
 *   ```
 * @property {ReadonlyArray<string | null | undefined> | string | null | undefined} [align]
 *   How to align columns (default: `''`);
 *   one style for all columns or styles for their respective columns;
 *   each style is either `'l'` (left), `'r'` (right), or `'c'` (center);
 *   other values are treated as `''`, which doesn’t place the colon in the
 *   alignment row but does align left;
 *   *only the lowercased first character is used, so `Right` is fine.*
 * @property {boolean | null | undefined} [delimiterEnd=true]
 *   Whether to end each row with the delimiter (default: `true`).
 *
 *   > 👉 **Note**: please don’t use this: it could create fragile structures
 *   > that aren’t understandable to some markdown parsers.
 *
 *   When `true`, there are ending delimiters:
 *
 *   ```markdown
 *   | Alpha | B     |
 *   | ----- | ----- |
 *   | C     | Delta |
 *   ```
 *
 *   When `false`, there are no ending delimiters:
 *
 *   ```markdown
 *   | Alpha | B
 *   | ----- | -----
 *   | C     | Delta
 *   ```
 * @property {boolean | null | undefined} [delimiterStart=true]
 *   Whether to begin each row with the delimiter (default: `true`).
 *
 *   > 👉 **Note**: please don’t use this: it could create fragile structures
 *   > that aren’t understandable to some markdown parsers.
 *
 *   When `true`, there are starting delimiters:
 *
 *   ```markdown
 *   | Alpha | B     |
 *   | ----- | ----- |
 *   | C     | Delta |
 *   ```
 *
 *   When `false`, there are no starting delimiters:
 *
 *   ```markdown
 *   Alpha | B     |
 *   ----- | ----- |
 *   C     | Delta |
 *   ```
 * @property {boolean | null | undefined} [padding=true]
 *   Whether to add a space of padding between delimiters and cells
 *   (default: `true`).
 *
 *   When `true`, there is padding:
 *
 *   ```markdown
 *   | Alpha | B     |
 *   | ----- | ----- |
 *   | C     | Delta |
 *   ```
 *
 *   When `false`, there is no padding:
 *
 *   ```markdown
 *   |Alpha|B    |
 *   |-----|-----|
 *   |C    |Delta|
 *   ```
 * @property {((value: string) => number) | null | undefined} [stringLength]
 *   Function to detect the length of table cell content (optional);
 *   this is used when aligning the delimiters (`|`) between table cells;
 *   full-width characters and emoji mess up delimiter alignment when viewing
 *   the markdown source;
 *   to fix this, you can pass this function,
 *   which receives the cell content and returns its “visible” size;
 *   note that what is and isn’t visible depends on where the text is displayed.
 *
 *   Without such a function, the following:
 *
 *   ```js
 *   markdownTable([
 *     ['Alpha', 'Bravo'],
 *     ['中文', 'Charlie'],
 *     ['👩‍❤️‍👩', 'Delta']
 *   ])
 *   ```
 *
 *   Yields:
 *
 *   ```markdown
 *   | Alpha | Bravo |
 *   | - | - |
 *   | 中文 | Charlie |
 *   | 👩‍❤️‍👩 | Delta |
 *   ```
 *
 *   With [`string-width`](https://github.com/sindresorhus/string-width):
 *
 *   ```js
 *   import stringWidth from 'string-width'
 *
 *   markdownTable(
 *     [
 *       ['Alpha', 'Bravo'],
 *       ['中文', 'Charlie'],
 *       ['👩‍❤️‍👩', 'Delta']
 *     ],
 *     {stringLength: stringWidth}
 *   )
 *   ```
 *
 *   Yields:
 *
 *   ```markdown
 *   | Alpha | Bravo   |
 *   | ----- | ------- |
 *   | 中文  | Charlie |
 *   | 👩‍❤️‍👩    | Delta   |
 *   ```
 */

/**
 * @param {string} value
 *   Cell value.
 * @returns {number}
 *   Cell size.
 */
function defaultStringLength(value) {
  return value.length
}

/**
 * Generate a markdown
 * ([GFM](https://docs.github.com/en/github/writing-on-github/working-with-advanced-formatting/organizing-information-with-tables))
 * table.
 *
 * @param {ReadonlyArray<ReadonlyArray<string | null | undefined>>} table
 *   Table data (matrix of strings).
 * @param {Readonly<Options> | null | undefined} [options]
 *   Configuration (optional).
 * @returns {string}
 *   Result.
 */
export function markdownTable(table, options) {
  const settings = options || {}
  // To do: next major: change to spread.
  const align = (settings.align || []).concat()
  const stringLength = settings.stringLength || defaultStringLength
  /** @type {Array<number>} Character codes as symbols for alignment per column. */
  const alignments = []
  /** @type {Array<Array<string>>} Cells per row. */
  const cellMatrix = []
  /** @type {Array<Array<number>>} Sizes of each cell per row. */
  const sizeMatrix = []
  /** @type {Array<number>} */
  const longestCellByColumn = []
  let mostCellsPerRow = 0
  let rowIndex = -1

  // This is a superfluous loop if we don’t align delimiters, but otherwise we’d
  // do superfluous work when aligning, so optimize for aligning.
  while (++rowIndex < table.length) {
    /** @type {Array<string>} */
    const row = []
    /** @type {Array<number>} */
    const sizes = []
    let columnIndex = -1

    if (table[rowIndex].length > mostCellsPerRow) {
      mostCellsPerRow = table[rowIndex].length
    }

    while (++columnIndex < table[rowIndex].length) {
      const cell = serialize(table[rowIndex][columnIndex])

      if (settings.alignDelimiters !== false) {
        const size = stringLength(cell)
        sizes[columnIndex] = size

        if (
          longestCellByColumn[columnIndex] === undefined ||
          size > longestCellByColumn[columnIndex]
        ) {
          longestCellByColumn[columnIndex] = size
        }
      }

      row.push(cell)
    }

    cellMatrix[rowIndex] = row
    sizeMatrix[rowIndex] = sizes
  }

  // Figure out which alignments to use.
  let columnIndex = -1

  if (typeof align === 'object' && 'length' in align) {
    while (++columnIndex < mostCellsPerRow) {
      alignments[columnIndex] = toAlignment(align[columnIndex])
    }
  } else {
    const code = toAlignment(align)

    while (++columnIndex < mostCellsPerRow) {
      alignments[columnIndex] = code
    }
  }

  // Inject the alignment row.
  columnIndex = -1
  /** @type {Array<string>} */
  const row = []
  /** @type {Array<number>} */
  const sizes = []

  while (++columnIndex < mostCellsPerRow) {
    const code = alignments[columnIndex]
    let before = ''
    let after = ''

    if (code === 99 /* `c` */) {
      before = ':'
      after = ':'
    } else if (code === 108 /* `l` */) {
      before = ':'
    } else if (code === 114 /* `r` */) {
      after = ':'
    }

    // There *must* be at least one hyphen-minus in each alignment cell.
    let size =
      settings.alignDelimiters === false
        ? 1
        : Math.max(
            1,
            longestCellByColumn[columnIndex] - before.length - after.length
          )

    const cell = before + '-'.repeat(size) + after

    if (settings.alignDelimiters !== false) {
      size = before.length + size + after.length

      if (size > longestCellByColumn[columnIndex]) {
        longestCellByColumn[columnIndex] = size
      }

      sizes[columnIndex] = size
    }

    row[columnIndex] = cell
  }

  // Inject the alignment row.
  cellMatrix.splice(1, 0, row)
  sizeMatrix.splice(1, 0, sizes)

  rowIndex = -1
  /** @type {Array<string>} */
  const lines = []

  while (++rowIndex < cellMatrix.length) {
    const row = cellMatrix[rowIndex]
    const sizes = sizeMatrix[rowIndex]
    columnIndex = -1
    /** @type {Array<string>} */
    const line = []

    while (++columnIndex < mostCellsPerRow) {
      const cell = row[columnIndex] || ''
      let before = ''
      let after = ''

      if (settings.alignDelimiters !== false) {
        const size =
          longestCellByColumn[columnIndex] - (sizes[columnIndex] || 0)
        const code = alignments[columnIndex]

        if (code === 114 /* `r` */) {
          before = ' '.repeat(size)
        } else if (code === 99 /* `c` */) {
          if (size % 2) {
            before = ' '.repeat(size / 2 + 0.5)
            after = ' '.repeat(size / 2 - 0.5)
          } else {
            before = ' '.repeat(size / 2)
            after = before
          }
        } else {
          after = ' '.repeat(size)
        }
      }

      if (settings.delimiterStart !== false && !columnIndex) {
        line.push('|')
      }

      if (
        settings.padding !== false &&
        // Don’t add the opening space if we’re not aligning and the cell is
        // empty: there will be a closing space.
        !(settings.alignDelimiters === false && cell === '') &&
        (settings.delimiterStart !== false || columnIndex)
      ) {
        line.push(' ')
      }

      if (settings.alignDelimiters !== false) {
        line.push(before)
      }

      line.push(cell)

      if (settings.alignDelimiters !== false) {
        line.push(after)
      }

      if (settings.padding !== false) {
        line.push(' ')
      }

      if (
        settings.delimiterEnd !== false ||
        columnIndex !== mostCellsPerRow - 1
      ) {
        line.push('|')
      }
    }

    lines.push(
      settings.delimiterEnd === false
        ? line.join('').replace(/ +$/, '')
        : line.join('')
    )
  }

  return lines.join('\n')
}

/**
 * @param {string | null | undefined} [value]
 *   Value to serialize.
 * @returns {string}
 *   Result.
 */
function serialize(value) {
  return value === null || value === undefined ? '' : String(value)
}

/**
 * @param {string | null | undefined} value
 *   Value.
 * @returns {number}
 *   Alignment.
 */
function toAlignment(value) {
  const code = typeof value === 'string' ? value.codePointAt(0) : 0

  return code === 67 /* `C` */ || code === 99 /* `c` */
    ? 99 /* `c` */
    : code === 76 /* `L` */ || code === 108 /* `l` */
      ? 108 /* `l` */
      : code === 82 /* `R` */ || code === 114 /* `r` */
        ? 114 /* `r` */
        : 0
}
{
  "author": "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)",
  "bugs": "https://github.com/wooorm/markdown-table/issues",
  "contributors": [
    "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)"
  ],
  "description": "Generate a markdown (GFM) table",
  "devDependencies": {
    "@types/node": "^22.0.0",
    "c8": "^10.0.0",
    "chalk": "^5.0.0",
    "prettier": "^3.0.0",
    "remark-cli": "^12.0.0",
    "remark-preset-wooorm": "^10.0.0",
    "strip-ansi": "^7.0.0",
    "type-coverage": "^2.0.0",
    "typescript": "^5.0.0",
    "xo": "^0.59.0"
  },
  "files": [
    "index.d.ts.map",
    "index.d.ts",
    "index.js"
  ],
  "funding": {
    "type": "github",
    "url": "https://github.com/sponsors/wooorm"
  },
  "keywords": [
    "align",
    "markdown",
    "rows",
    "table",
    "tabular",
    "text"
  ],
  "license": "MIT",
  "main": "index.js",
  "name": "markdown-table",
  "prettier": {
    "bracketSpacing": false,
    "semi": false,
    "singleQuote": true,
    "tabWidth": 2,
    "trailingComma": "none",
    "useTabs": false
  },
  "remarkConfig": {
    "plugins": [
      "remark-preset-wooorm"
    ]
  },
  "repository": "wooorm/markdown-table",
  "scripts": {
    "build": "tsc --build --clean && tsc --build && type-coverage",
    "format": "remark --frail --output --quiet -- . && prettier --log-level warn --write -- . && xo --fix",
    "test-api": "node --conditions development test.js",
    "test-coverage": "c8 --100 --check-coverage --reporter lcov -- npm run test-api",
    "test": "npm run build && npm run format && npm run test-coverage"
  },
  "sideEffects": false,
  "typeCoverage": {
    "atLeast": 100,
    "detail": true,
    "strict": true
  },
  "types": "index.d.ts",
  "type": "module",
  "version": "3.0.4",
  "xo": {
    "prettier": true,
    "rules": {
      "complexity": "off",
      "unicorn/prefer-switch": "off"
    }
  }
}
// Note: types exposed from `index.d.ts`.
export {directiveFromMarkdown, directiveToMarkdown} from './lib/index.js'
/**
 * @import {Directives, LeafDirective, TextDirective, ToMarkdownOptions} from 'mdast-util-directive'
 * @import {
 *   CompileContext,
 *   Extension as FromMarkdownExtension,
 *   Handle as FromMarkdownHandle,
 *   Token
 * } from 'mdast-util-from-markdown'
 * @import {
 *   ConstructName,
 *   Handle as ToMarkdownHandle,
 *   Options as ToMarkdownExtension,
 *   State
 * } from 'mdast-util-to-markdown'
 * @import {Nodes, Paragraph} from 'mdast'
 */

import {ccount} from 'ccount'
import {ok as assert} from 'devlop'
import {parseEntities} from 'parse-entities'
import {stringifyEntitiesLight} from 'stringify-entities'
import {visitParents} from 'unist-util-visit-parents'

const own = {}.hasOwnProperty

/** @type {Readonly<ToMarkdownOptions>} */
const emptyOptions = {}

const shortcut = /^[^\t\n\r "#'.<=>`}]+$/
const unquoted = /^[^\t\n\r "'<=>`}]+$/

/**
 * Create an extension for `mdast-util-from-markdown` to enable directives in
 * markdown.
 *
 * @returns {FromMarkdownExtension}
 *   Extension for `mdast-util-from-markdown` to enable directives.
 */
export function directiveFromMarkdown() {
  return {
    canContainEols: ['textDirective'],
    enter: {
      directiveContainer: enterContainer,
      directiveContainerAttributes: enterAttributes,
      directiveContainerLabel: enterContainerLabel,

      directiveLeaf: enterLeaf,
      directiveLeafAttributes: enterAttributes,

      directiveText: enterText,
      directiveTextAttributes: enterAttributes
    },
    exit: {
      directiveContainer: exit,
      directiveContainerAttributeClassValue: exitAttributeClassValue,
      directiveContainerAttributeIdValue: exitAttributeIdValue,
      directiveContainerAttributeName: exitAttributeName,
      directiveContainerAttributeValue: exitAttributeValue,
      directiveContainerAttributes: exitAttributes,
      directiveContainerLabel: exitContainerLabel,
      directiveContainerName: exitName,

      directiveLeaf: exit,
      directiveLeafAttributeClassValue: exitAttributeClassValue,
      directiveLeafAttributeIdValue: exitAttributeIdValue,
      directiveLeafAttributeName: exitAttributeName,
      directiveLeafAttributeValue: exitAttributeValue,
      directiveLeafAttributes: exitAttributes,
      directiveLeafName: exitName,

      directiveText: exit,
      directiveTextAttributeClassValue: exitAttributeClassValue,
      directiveTextAttributeIdValue: exitAttributeIdValue,
      directiveTextAttributeName: exitAttributeName,
      directiveTextAttributeValue: exitAttributeValue,
      directiveTextAttributes: exitAttributes,
      directiveTextName: exitName
    }
  }
}

/**
 * Create an extension for `mdast-util-to-markdown` to enable directives in
 * markdown.
 *
 * @param {Readonly<ToMarkdownOptions> | null | undefined} [options]
 *   Configuration (optional).
 * @returns {ToMarkdownExtension}
 *   Extension for `mdast-util-to-markdown` to enable directives.
 */
export function directiveToMarkdown(options) {
  const settings = options || emptyOptions

  if (
    settings.quote !== '"' &&
    settings.quote !== "'" &&
    settings.quote !== null &&
    settings.quote !== undefined
  ) {
    throw new Error(
      'Invalid quote `' + settings.quote + '`, expected `\'` or `"`'
    )
  }

  handleDirective.peek = peekDirective

  return {
    handlers: {
      containerDirective: handleDirective,
      leafDirective: handleDirective,
      textDirective: handleDirective
    },
    unsafe: [
      {
        character: '\r',
        inConstruct: ['leafDirectiveLabel', 'containerDirectiveLabel']
      },
      {
        character: '\n',
        inConstruct: ['leafDirectiveLabel', 'containerDirectiveLabel']
      },
      {
        before: '[^:]',
        character: ':',
        after: '[A-Za-z]',
        inConstruct: ['phrasing']
      },
      {atBreak: true, character: ':', after: ':'}
    ]
  }

  /**
   * @type {ToMarkdownHandle}
   * @param {Directives} node
   */
  function handleDirective(node, _, state, info) {
    const tracker = state.createTracker(info)
    const sequence = fence(node)
    const exit = state.enter(node.type)
    let value = tracker.move(sequence + (node.name || ''))
    /** @type {LeafDirective | Paragraph | TextDirective | undefined} */
    let label

    if (node.type === 'containerDirective') {
      const head = (node.children || [])[0]
      label = inlineDirectiveLabel(head) ? head : undefined
    } else {
      label = node
    }

    if (label && label.children && label.children.length > 0) {
      const exit = state.enter('label')
      /** @type {ConstructName} */
      const labelType = `${node.type}Label`
      const subexit = state.enter(labelType)
      value += tracker.move('[')
      value += tracker.move(
        state.containerPhrasing(label, {
          ...tracker.current(),
          before: value,
          after: ']'
        })
      )
      value += tracker.move(']')
      subexit()
      exit()
    }

    value += tracker.move(attributes(node, state))

    if (node.type === 'containerDirective') {
      const head = (node.children || [])[0]
      let shallow = node

      if (inlineDirectiveLabel(head)) {
        shallow = Object.assign({}, node, {children: node.children.slice(1)})
      }

      if (shallow && shallow.children && shallow.children.length > 0) {
        value += tracker.move('\n')
        value += tracker.move(state.containerFlow(shallow, tracker.current()))
      }

      value += tracker.move('\n' + sequence)
    }

    exit()
    return value
  }

  /**
   * @param {Directives} node
   * @param {State} state
   * @returns {string}
   */
  function attributes(node, state) {
    const attributes = node.attributes || {}
    /** @type {Array<string>} */
    const values = []
    /** @type {string | undefined} */
    let classesFull
    /** @type {string | undefined} */
    let classes
    /** @type {string | undefined} */
    let id
    /** @type {string} */
    let key

    for (key in attributes) {
      if (
        own.call(attributes, key) &&
        attributes[key] !== undefined &&
        attributes[key] !== null
      ) {
        const value = String(attributes[key])

        // To do: next major:
        // Do not reorder `id` and `class` attributes when they do not turn into
        // shortcuts.
        // Additionally, join shortcuts: `#a .b.c d="e"` -> `#a.b.c d="e"`
        if (key === 'id') {
          id =
            settings.preferShortcut !== false && shortcut.test(value)
              ? '#' + value
              : quoted('id', value, node, state)
        } else if (key === 'class') {
          const list = value.split(/[\t\n\r ]+/g)
          /** @type {Array<string>} */
          const classesFullList = []
          /** @type {Array<string>} */
          const classesList = []
          let index = -1

          while (++index < list.length) {
            ;(settings.preferShortcut !== false && shortcut.test(list[index])
              ? classesList
              : classesFullList
            ).push(list[index])
          }

          classesFull =
            classesFullList.length > 0
              ? quoted('class', classesFullList.join(' '), node, state)
              : ''
          classes = classesList.length > 0 ? '.' + classesList.join('.') : ''
        } else {
          values.push(quoted(key, value, node, state))
        }
      }
    }

    if (classesFull) {
      values.unshift(classesFull)
    }

    if (classes) {
      values.unshift(classes)
    }

    if (id) {
      values.unshift(id)
    }

    return values.length > 0 ? '{' + values.join(' ') + '}' : ''
  }

  /**
   * @param {string} key
   * @param {string} value
   * @param {Directives} node
   * @param {State} state
   * @returns {string}
   */
  function quoted(key, value, node, state) {
    if (settings.collapseEmptyAttributes !== false && !value) return key

    if (settings.preferUnquoted && unquoted.test(value)) {
      return key + '=' + value
    }

    // If the alternative is less common than `quote`, switch.
    const preferred = settings.quote || state.options.quote || '"'
    const alternative = preferred === '"' ? "'" : '"'
    // If the alternative is less common than `quote`, switch.
    const appliedQuote =
      settings.quoteSmart &&
      ccount(value, preferred) > ccount(value, alternative)
        ? alternative
        : preferred
    const subset =
      node.type === 'textDirective'
        ? [appliedQuote]
        : [appliedQuote, '\n', '\r']

    return (
      key +
      '=' +
      appliedQuote +
      stringifyEntitiesLight(value, {subset}) +
      appliedQuote
    )
  }
}

/**
 * @this {CompileContext}
 * @type {FromMarkdownHandle}
 */
function enterContainer(token) {
  enter.call(this, 'containerDirective', token)
}

/**
 * @this {CompileContext}
 * @type {FromMarkdownHandle}
 */
function enterLeaf(token) {
  enter.call(this, 'leafDirective', token)
}

/**
 * @this {CompileContext}
 * @type {FromMarkdownHandle}
 */
function enterText(token) {
  enter.call(this, 'textDirective', token)
}

/**
 * @this {CompileContext}
 * @param {Directives['type']} type
 * @param {Token} token
 */
function enter(type, token) {
  this.enter({type, name: '', attributes: {}, children: []}, token)
}

/**
 * @this {CompileContext}
 * @param {Token} token
 */
function exitName(token) {
  const node = this.stack[this.stack.length - 1]
  assert(
    node.type === 'containerDirective' ||
      node.type === 'leafDirective' ||
      node.type === 'textDirective'
  )
  node.name = this.sliceSerialize(token)
}

/**
 * @this {CompileContext}
 * @type {FromMarkdownHandle}
 */
function enterContainerLabel(token) {
  this.enter(
    {type: 'paragraph', data: {directiveLabel: true}, children: []},
    token
  )
}

/**
 * @this {CompileContext}
 * @type {FromMarkdownHandle}
 */
function exitContainerLabel(token) {
  this.exit(token)
}

/**
 * @this {CompileContext}
 * @type {FromMarkdownHandle}
 */
function enterAttributes() {
  this.data.directiveAttributes = []
  this.buffer() // Capture EOLs
}

/**
 * @this {CompileContext}
 * @type {FromMarkdownHandle}
 */
function exitAttributeIdValue(token) {
  const list = this.data.directiveAttributes
  assert(list, 'expected `directiveAttributes`')
  list.push([
    'id',
    parseEntities(this.sliceSerialize(token), {attribute: true})
  ])
}

/**
 * @this {CompileContext}
 * @type {FromMarkdownHandle}
 */
function exitAttributeClassValue(token) {
  const list = this.data.directiveAttributes
  assert(list, 'expected `directiveAttributes`')
  list.push([
    'class',
    parseEntities(this.sliceSerialize(token), {attribute: true})
  ])
}

/**
 * @this {CompileContext}
 * @type {FromMarkdownHandle}
 */
function exitAttributeValue(token) {
  const list = this.data.directiveAttributes
  assert(list, 'expected `directiveAttributes`')
  list[list.length - 1][1] = parseEntities(this.sliceSerialize(token), {
    attribute: true
  })
}

/**
 * @this {CompileContext}
 * @type {FromMarkdownHandle}
 */
function exitAttributeName(token) {
  const list = this.data.directiveAttributes
  assert(list, 'expected `directiveAttributes`')

  // Attribute names in CommonMark are significantly limited, so character
  // references can’t exist.
  list.push([this.sliceSerialize(token), ''])
}

/**
 * @this {CompileContext}
 * @type {FromMarkdownHandle}
 */
function exitAttributes() {
  const list = this.data.directiveAttributes
  assert(list, 'expected `directiveAttributes`')
  /** @type {Record<string, string>} */
  const cleaned = {}
  let index = -1

  while (++index < list.length) {
    const attribute = list[index]

    if (attribute[0] === 'class' && cleaned.class) {
      cleaned.class += ' ' + attribute[1]
    } else {
      cleaned[attribute[0]] = attribute[1]
    }
  }

  this.data.directiveAttributes = undefined
  this.resume() // Drop EOLs
  const node = this.stack[this.stack.length - 1]
  assert(
    node.type === 'containerDirective' ||
      node.type === 'leafDirective' ||
      node.type === 'textDirective'
  )
  node.attributes = cleaned
}

/**
 * @this {CompileContext}
 * @type {FromMarkdownHandle}
 */
function exit(token) {
  this.exit(token)
}

/** @type {ToMarkdownHandle} */
function peekDirective() {
  return ':'
}

/**
 * @param {Nodes} node
 * @returns {node is Paragraph & {data: {directiveLabel: true}}}
 */
function inlineDirectiveLabel(node) {
  return Boolean(
    node && node.type === 'paragraph' && node.data && node.data.directiveLabel
  )
}

/**
 * @param {Directives} node
 * @returns {string}
 */
function fence(node) {
  let size = 0

  if (node.type === 'containerDirective') {
    visitParents(node, function (node, parents) {
      if (node.type === 'containerDirective') {
        let index = parents.length
        let nesting = 0

        while (index--) {
          if (parents[index].type === 'containerDirective') {
            nesting++
          }
        }

        if (nesting > size) size = nesting
      }
    })
    size += 3
  } else if (node.type === 'leafDirective') {
    size = 2
  } else {
    size = 1
  }

  return ':'.repeat(size)
}
{
  "author": "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)",
  "bugs": "https://github.com/syntax-tree/mdast-util-directive/issues",
  "contributors": [
    "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)"
  ],
  "dependencies": {
    "@types/mdast": "^4.0.0",
    "@types/unist": "^3.0.0",
    "ccount": "^2.0.0",
    "devlop": "^1.0.0",
    "mdast-util-from-markdown": "^2.0.0",
    "mdast-util-to-markdown": "^2.0.0",
    "parse-entities": "^4.0.0",
    "stringify-entities": "^4.0.0",
    "unist-util-visit-parents": "^6.0.0"
  },
  "description": "mdast extension to parse and serialize generic directives (`:cite[smith04]`)",
  "devDependencies": {
    "@types/node": "^22.0.0",
    "c8": "^10.0.0",
    "micromark-extension-directive": "^3.0.0",
    "prettier": "^3.0.0",
    "remark-cli": "^12.0.0",
    "remark-preset-wooorm": "^10.0.0",
    "type-coverage": "^2.0.0",
    "typescript": "^5.0.0",
    "unist-util-remove-position": "^5.0.0",
    "xo": "^0.60.0"
  },
  "exports": "./index.js",
  "files": [
    "index.d.ts",
    "index.js",
    "lib/"
  ],
  "funding": {
    "type": "opencollective",
    "url": "https://opencollective.com/unified"
  },
  "keywords": [
    "container",
    "directive",
    "extension",
    "generic",
    "markdown",
    "markup",
    "mdast-util",
    "mdast",
    "unist",
    "utility",
    "util"
  ],
  "license": "MIT",
  "name": "mdast-util-directive",
  "prettier": {
    "bracketSpacing": false,
    "semi": false,
    "singleQuote": true,
    "tabWidth": 2,
    "trailingComma": "none",
    "useTabs": false
  },
  "remarkConfig": {
    "plugins": [
      "remark-preset-wooorm"
    ]
  },
  "repository": "syntax-tree/mdast-util-directive",
  "scripts": {
    "build": "tsc --build --clean && tsc --build && type-coverage",
    "format": "remark --frail --output --quiet -- . && prettier --log-level warn --write -- . && xo --fix",
    "test-api-dev": "node --conditions development test.js",
    "test-api-prod": "node --conditions production test.js",
    "test-api": "npm run test-api-dev && npm run test-api-prod",
    "test-coverage": "c8 --100 --reporter lcov -- npm run test-api",
    "test": "npm run build && npm run format && npm run test-coverage"
  },
  "sideEffects": false,
  "typeCoverage": {
    "atLeast": 100,
    "strict": true
  },
  "type": "module",
  "version": "3.1.0",
  "xo": {
    "overrides": [
      {
        "files": [
          "**/*.d.ts"
        ],
        "rules": {
          "@typescript-eslint/array-type": [
            "error",
            {
              "default": "generic"
            }
          ],
          "@typescript-eslint/ban-types": [
            "error",
            {
              "extendDefaults": true
            }
          ],
          "@typescript-eslint/consistent-type-definitions": [
            "error",
            "interface"
          ]
        }
      }
    ],
    "prettier": true,
    "rules": {
      "unicorn/prefer-at": "off"
    }
  }
}
// Note: types exported from `index.d.ts`.
export { fromMarkdown } from './lib/index.js';/**
 * @import {
 *   Break,
 *   Blockquote,
 *   Code,
 *   Definition,
 *   Emphasis,
 *   Heading,
 *   Html,
 *   Image,
 *   InlineCode,
 *   Link,
 *   ListItem,
 *   List,
 *   Nodes,
 *   Paragraph,
 *   PhrasingContent,
 *   ReferenceType,
 *   Root,
 *   Strong,
 *   Text,
 *   ThematicBreak
 * } from 'mdast'
 * @import {
 *   Encoding,
 *   Event,
 *   Token,
 *   Value
 * } from 'micromark-util-types'
 * @import {Point} from 'unist'
 * @import {
 *   CompileContext,
 *   CompileData,
 *   Config,
 *   Extension,
 *   Handle,
 *   OnEnterError,
 *   Options
 * } from './types.js'
 */

import { toString } from 'mdast-util-to-string';
import { parse, postprocess, preprocess } from 'micromark';
import { decodeNumericCharacterReference } from 'micromark-util-decode-numeric-character-reference';
import { decodeString } from 'micromark-util-decode-string';
import { normalizeIdentifier } from 'micromark-util-normalize-identifier';
import { decodeNamedCharacterReference } from 'decode-named-character-reference';
import { stringifyPosition } from 'unist-util-stringify-position';
const own = {}.hasOwnProperty;

/**
 * Turn markdown into a syntax tree.
 *
 * @overload
 * @param {Value} value
 * @param {Encoding | null | undefined} [encoding]
 * @param {Options | null | undefined} [options]
 * @returns {Root}
 *
 * @overload
 * @param {Value} value
 * @param {Options | null | undefined} [options]
 * @returns {Root}
 *
 * @param {Value} value
 *   Markdown to parse.
 * @param {Encoding | Options | null | undefined} [encoding]
 *   Character encoding for when `value` is `Buffer`.
 * @param {Options | null | undefined} [options]
 *   Configuration.
 * @returns {Root}
 *   mdast tree.
 */
export function fromMarkdown(value, encoding, options) {
  if (encoding && typeof encoding === 'object') {
    options = encoding;
    encoding = undefined;
  }
  return compiler(options)(postprocess(parse(options).document().write(preprocess()(value, encoding, true))));
}

/**
 * Note this compiler only understand complete buffering, not streaming.
 *
 * @param {Options | null | undefined} [options]
 */
function compiler(options) {
  /** @type {Config} */
  const config = {
    transforms: [],
    canContainEols: ['emphasis', 'fragment', 'heading', 'paragraph', 'strong'],
    enter: {
      autolink: opener(link),
      autolinkProtocol: onenterdata,
      autolinkEmail: onenterdata,
      atxHeading: opener(heading),
      blockQuote: opener(blockQuote),
      characterEscape: onenterdata,
      characterReference: onenterdata,
      codeFenced: opener(codeFlow),
      codeFencedFenceInfo: buffer,
      codeFencedFenceMeta: buffer,
      codeIndented: opener(codeFlow, buffer),
      codeText: opener(codeText, buffer),
      codeTextData: onenterdata,
      data: onenterdata,
      codeFlowValue: onenterdata,
      definition: opener(definition),
      definitionDestinationString: buffer,
      definitionLabelString: buffer,
      definitionTitleString: buffer,
      emphasis: opener(emphasis),
      hardBreakEscape: opener(hardBreak),
      hardBreakTrailing: opener(hardBreak),
      htmlFlow: opener(html, buffer),
      htmlFlowData: onenterdata,
      htmlText: opener(html, buffer),
      htmlTextData: onenterdata,
      image: opener(image),
      label: buffer,
      link: opener(link),
      listItem: opener(listItem),
      listItemValue: onenterlistitemvalue,
      listOrdered: opener(list, onenterlistordered),
      listUnordered: opener(list),
      paragraph: opener(paragraph),
      reference: onenterreference,
      referenceString: buffer,
      resourceDestinationString: buffer,
      resourceTitleString: buffer,
      setextHeading: opener(heading),
      strong: opener(strong),
      thematicBreak: opener(thematicBreak)
    },
    exit: {
      atxHeading: closer(),
      atxHeadingSequence: onexitatxheadingsequence,
      autolink: closer(),
      autolinkEmail: onexitautolinkemail,
      autolinkProtocol: onexitautolinkprotocol,
      blockQuote: closer(),
      characterEscapeValue: onexitdata,
      characterReferenceMarkerHexadecimal: onexitcharacterreferencemarker,
      characterReferenceMarkerNumeric: onexitcharacterreferencemarker,
      characterReferenceValue: onexitcharacterreferencevalue,
      characterReference: onexitcharacterreference,
      codeFenced: closer(onexitcodefenced),
      codeFencedFence: onexitcodefencedfence,
      codeFencedFenceInfo: onexitcodefencedfenceinfo,
      codeFencedFenceMeta: onexitcodefencedfencemeta,
      codeFlowValue: onexitdata,
      codeIndented: closer(onexitcodeindented),
      codeText: closer(onexitcodetext),
      codeTextData: onexitdata,
      data: onexitdata,
      definition: closer(),
      definitionDestinationString: onexitdefinitiondestinationstring,
      definitionLabelString: onexitdefinitionlabelstring,
      definitionTitleString: onexitdefinitiontitlestring,
      emphasis: closer(),
      hardBreakEscape: closer(onexithardbreak),
      hardBreakTrailing: closer(onexithardbreak),
      htmlFlow: closer(onexithtmlflow),
      htmlFlowData: onexitdata,
      htmlText: closer(onexithtmltext),
      htmlTextData: onexitdata,
      image: closer(onexitimage),
      label: onexitlabel,
      labelText: onexitlabeltext,
      lineEnding: onexitlineending,
      link: closer(onexitlink),
      listItem: closer(),
      listOrdered: closer(),
      listUnordered: closer(),
      paragraph: closer(),
      referenceString: onexitreferencestring,
      resourceDestinationString: onexitresourcedestinationstring,
      resourceTitleString: onexitresourcetitlestring,
      resource: onexitresource,
      setextHeading: closer(onexitsetextheading),
      setextHeadingLineSequence: onexitsetextheadinglinesequence,
      setextHeadingText: onexitsetextheadingtext,
      strong: closer(),
      thematicBreak: closer()
    }
  };
  configure(config, (options || {}).mdastExtensions || []);

  /** @type {CompileData} */
  const data = {};
  return compile;

  /**
   * Turn micromark events into an mdast tree.
   *
   * @param {Array<Event>} events
   *   Events.
   * @returns {Root}
   *   mdast tree.
   */
  function compile(events) {
    /** @type {Root} */
    let tree = {
      type: 'root',
      children: []
    };
    /** @type {Omit<CompileContext, 'sliceSerialize'>} */
    const context = {
      stack: [tree],
      tokenStack: [],
      config,
      enter,
      exit,
      buffer,
      resume,
      data
    };
    /** @type {Array<number>} */
    const listStack = [];
    let index = -1;
    while (++index < events.length) {
      // We preprocess lists to add `listItem` tokens, and to infer whether
      // items the list itself are spread out.
      if (events[index][1].type === "listOrdered" || events[index][1].type === "listUnordered") {
        if (events[index][0] === 'enter') {
          listStack.push(index);
        } else {
          const tail = listStack.pop();
          index = prepareList(events, tail, index);
        }
      }
    }
    index = -1;
    while (++index < events.length) {
      const handler = config[events[index][0]];
      if (own.call(handler, events[index][1].type)) {
        handler[events[index][1].type].call(Object.assign({
          sliceSerialize: events[index][2].sliceSerialize
        }, context), events[index][1]);
      }
    }

    // Handle tokens still being open.
    if (context.tokenStack.length > 0) {
      const tail = context.tokenStack[context.tokenStack.length - 1];
      const handler = tail[1] || defaultOnError;
      handler.call(context, undefined, tail[0]);
    }

    // Figure out `root` position.
    tree.position = {
      start: point(events.length > 0 ? events[0][1].start : {
        line: 1,
        column: 1,
        offset: 0
      }),
      end: point(events.length > 0 ? events[events.length - 2][1].end : {
        line: 1,
        column: 1,
        offset: 0
      })
    };

    // Call transforms.
    index = -1;
    while (++index < config.transforms.length) {
      tree = config.transforms[index](tree) || tree;
    }
    return tree;
  }

  /**
   * @param {Array<Event>} events
   * @param {number} start
   * @param {number} length
   * @returns {number}
   */
  function prepareList(events, start, length) {
    let index = start - 1;
    let containerBalance = -1;
    let listSpread = false;
    /** @type {Token | undefined} */
    let listItem;
    /** @type {number | undefined} */
    let lineIndex;
    /** @type {number | undefined} */
    let firstBlankLineIndex;
    /** @type {boolean | undefined} */
    let atMarker;
    while (++index <= length) {
      const event = events[index];
      switch (event[1].type) {
        case "listUnordered":
        case "listOrdered":
        case "blockQuote":
          {
            if (event[0] === 'enter') {
              containerBalance++;
            } else {
              containerBalance--;
            }
            atMarker = undefined;
            break;
          }
        case "lineEndingBlank":
          {
            if (event[0] === 'enter') {
              if (listItem && !atMarker && !containerBalance && !firstBlankLineIndex) {
                firstBlankLineIndex = index;
              }
              atMarker = undefined;
            }
            break;
          }
        case "linePrefix":
        case "listItemValue":
        case "listItemMarker":
        case "listItemPrefix":
        case "listItemPrefixWhitespace":
          {
            // Empty.

            break;
          }
        default:
          {
            atMarker = undefined;
          }
      }
      if (!containerBalance && event[0] === 'enter' && event[1].type === "listItemPrefix" || containerBalance === -1 && event[0] === 'exit' && (event[1].type === "listUnordered" || event[1].type === "listOrdered")) {
        if (listItem) {
          let tailIndex = index;
          lineIndex = undefined;
          while (tailIndex--) {
            const tailEvent = events[tailIndex];
            if (tailEvent[1].type === "lineEnding" || tailEvent[1].type === "lineEndingBlank") {
              if (tailEvent[0] === 'exit') continue;
              if (lineIndex) {
                events[lineIndex][1].type = "lineEndingBlank";
                listSpread = true;
              }
              tailEvent[1].type = "lineEnding";
              lineIndex = tailIndex;
            } else if (tailEvent[1].type === "linePrefix" || tailEvent[1].type === "blockQuotePrefix" || tailEvent[1].type === "blockQuotePrefixWhitespace" || tailEvent[1].type === "blockQuoteMarker" || tailEvent[1].type === "listItemIndent") {
              // Empty
            } else {
              break;
            }
          }
          if (firstBlankLineIndex && (!lineIndex || firstBlankLineIndex < lineIndex)) {
            listItem._spread = true;
          }

          // Fix position.
          listItem.end = Object.assign({}, lineIndex ? events[lineIndex][1].start : event[1].end);
          events.splice(lineIndex || index, 0, ['exit', listItem, event[2]]);
          index++;
          length++;
        }

        // Create a new list item.
        if (event[1].type === "listItemPrefix") {
          /** @type {Token} */
          const item = {
            type: 'listItem',
            _spread: false,
            start: Object.assign({}, event[1].start),
            // @ts-expect-error: we’ll add `end` in a second.
            end: undefined
          };
          listItem = item;
          events.splice(index, 0, ['enter', item, event[2]]);
          index++;
          length++;
          firstBlankLineIndex = undefined;
          atMarker = true;
        }
      }
    }
    events[start][1]._spread = listSpread;
    return length;
  }

  /**
   * Create an opener handle.
   *
   * @param {(token: Token) => Nodes} create
   *   Create a node.
   * @param {Handle | undefined} [and]
   *   Optional function to also run.
   * @returns {Handle}
   *   Handle.
   */
  function opener(create, and) {
    return open;

    /**
     * @this {CompileContext}
     * @param {Token} token
     * @returns {undefined}
     */
    function open(token) {
      enter.call(this, create(token), token);
      if (and) and.call(this, token);
    }
  }

  /**
   * @type {CompileContext['buffer']}
   */
  function buffer() {
    this.stack.push({
      type: 'fragment',
      children: []
    });
  }

  /**
   * @type {CompileContext['enter']}
   */
  function enter(node, token, errorHandler) {
    const parent = this.stack[this.stack.length - 1];
    /** @type {Array<Nodes>} */
    const siblings = parent.children;
    siblings.push(node);
    this.stack.push(node);
    this.tokenStack.push([token, errorHandler || undefined]);
    node.position = {
      start: point(token.start),
      // @ts-expect-error: `end` will be patched later.
      end: undefined
    };
  }

  /**
   * Create a closer handle.
   *
   * @param {Handle | undefined} [and]
   *   Optional function to also run.
   * @returns {Handle}
   *   Handle.
   */
  function closer(and) {
    return close;

    /**
     * @this {CompileContext}
     * @param {Token} token
     * @returns {undefined}
     */
    function close(token) {
      if (and) and.call(this, token);
      exit.call(this, token);
    }
  }

  /**
   * @type {CompileContext['exit']}
   */
  function exit(token, onExitError) {
    const node = this.stack.pop();
    const open = this.tokenStack.pop();
    if (!open) {
      throw new Error('Cannot close `' + token.type + '` (' + stringifyPosition({
        start: token.start,
        end: token.end
      }) + '): it’s not open');
    } else if (open[0].type !== token.type) {
      if (onExitError) {
        onExitError.call(this, token, open[0]);
      } else {
        const handler = open[1] || defaultOnError;
        handler.call(this, token, open[0]);
      }
    }
    node.position.end = point(token.end);
  }

  /**
   * @type {CompileContext['resume']}
   */
  function resume() {
    return toString(this.stack.pop());
  }

  //
  // Handlers.
  //

  /**
   * @this {CompileContext}
   * @type {Handle}
   */
  function onenterlistordered() {
    this.data.expectingFirstListItemValue = true;
  }

  /**
   * @this {CompileContext}
   * @type {Handle}
   */
  function onenterlistitemvalue(token) {
    if (this.data.expectingFirstListItemValue) {
      const ancestor = this.stack[this.stack.length - 2];
      ancestor.start = Number.parseInt(this.sliceSerialize(token), 10);
      this.data.expectingFirstListItemValue = undefined;
    }
  }

  /**
   * @this {CompileContext}
   * @type {Handle}
   */
  function onexitcodefencedfenceinfo() {
    const data = this.resume();
    const node = this.stack[this.stack.length - 1];
    node.lang = data;
  }

  /**
   * @this {CompileContext}
   * @type {Handle}
   */
  function onexitcodefencedfencemeta() {
    const data = this.resume();
    const node = this.stack[this.stack.length - 1];
    node.meta = data;
  }

  /**
   * @this {CompileContext}
   * @type {Handle}
   */
  function onexitcodefencedfence() {
    // Exit if this is the closing fence.
    if (this.data.flowCodeInside) return;
    this.buffer();
    this.data.flowCodeInside = true;
  }

  /**
   * @this {CompileContext}
   * @type {Handle}
   */
  function onexitcodefenced() {
    const data = this.resume();
    const node = this.stack[this.stack.length - 1];
    node.value = data.replace(/^(\r?\n|\r)|(\r?\n|\r)$/g, '');
    this.data.flowCodeInside = undefined;
  }

  /**
   * @this {CompileContext}
   * @type {Handle}
   */
  function onexitcodeindented() {
    const data = this.resume();
    const node = this.stack[this.stack.length - 1];
    node.value = data.replace(/(\r?\n|\r)$/g, '');
  }

  /**
   * @this {CompileContext}
   * @type {Handle}
   */
  function onexitdefinitionlabelstring(token) {
    const label = this.resume();
    const node = this.stack[this.stack.length - 1];
    node.label = label;
    node.identifier = normalizeIdentifier(this.sliceSerialize(token)).toLowerCase();
  }

  /**
   * @this {CompileContext}
   * @type {Handle}
   */
  function onexitdefinitiontitlestring() {
    const data = this.resume();
    const node = this.stack[this.stack.length - 1];
    node.title = data;
  }

  /**
   * @this {CompileContext}
   * @type {Handle}
   */
  function onexitdefinitiondestinationstring() {
    const data = this.resume();
    const node = this.stack[this.stack.length - 1];
    node.url = data;
  }

  /**
   * @this {CompileContext}
   * @type {Handle}
   */
  function onexitatxheadingsequence(token) {
    const node = this.stack[this.stack.length - 1];
    if (!node.depth) {
      const depth = this.sliceSerialize(token).length;
      node.depth = depth;
    }
  }

  /**
   * @this {CompileContext}
   * @type {Handle}
   */
  function onexitsetextheadingtext() {
    this.data.setextHeadingSlurpLineEnding = true;
  }

  /**
   * @this {CompileContext}
   * @type {Handle}
   */
  function onexitsetextheadinglinesequence(token) {
    const node = this.stack[this.stack.length - 1];
    node.depth = this.sliceSerialize(token).codePointAt(0) === 61 ? 1 : 2;
  }

  /**
   * @this {CompileContext}
   * @type {Handle}
   */
  function onexitsetextheading() {
    this.data.setextHeadingSlurpLineEnding = undefined;
  }

  /**
   * @this {CompileContext}
   * @type {Handle}
   */

  function onenterdata(token) {
    const node = this.stack[this.stack.length - 1];
    /** @type {Array<Nodes>} */
    const siblings = node.children;
    let tail = siblings[siblings.length - 1];
    if (!tail || tail.type !== 'text') {
      // Add a new text node.
      tail = text();
      tail.position = {
        start: point(token.start),
        // @ts-expect-error: we’ll add `end` later.
        end: undefined
      };
      siblings.push(tail);
    }
    this.stack.push(tail);
  }

  /**
   * @this {CompileContext}
   * @type {Handle}
   */

  function onexitdata(token) {
    const tail = this.stack.pop();
    tail.value += this.sliceSerialize(token);
    tail.position.end = point(token.end);
  }

  /**
   * @this {CompileContext}
   * @type {Handle}
   */

  function onexitlineending(token) {
    const context = this.stack[this.stack.length - 1];
    // If we’re at a hard break, include the line ending in there.
    if (this.data.atHardBreak) {
      const tail = context.children[context.children.length - 1];
      tail.position.end = point(token.end);
      this.data.atHardBreak = undefined;
      return;
    }
    if (!this.data.setextHeadingSlurpLineEnding && config.canContainEols.includes(context.type)) {
      onenterdata.call(this, token);
      onexitdata.call(this, token);
    }
  }

  /**
   * @this {CompileContext}
   * @type {Handle}
   */

  function onexithardbreak() {
    this.data.atHardBreak = true;
  }

  /**
   * @this {CompileContext}
   * @type {Handle}
   */

  function onexithtmlflow() {
    const data = this.resume();
    const node = this.stack[this.stack.length - 1];
    node.value = data;
  }

  /**
   * @this {CompileContext}
   * @type {Handle}
   */

  function onexithtmltext() {
    const data = this.resume();
    const node = this.stack[this.stack.length - 1];
    node.value = data;
  }

  /**
   * @this {CompileContext}
   * @type {Handle}
   */

  function onexitcodetext() {
    const data = this.resume();
    const node = this.stack[this.stack.length - 1];
    node.value = data;
  }

  /**
   * @this {CompileContext}
   * @type {Handle}
   */

  function onexitlink() {
    const node = this.stack[this.stack.length - 1];
    // Note: there are also `identifier` and `label` fields on this link node!
    // These are used / cleaned here.

    // To do: clean.
    if (this.data.inReference) {
      /** @type {ReferenceType} */
      const referenceType = this.data.referenceType || 'shortcut';
      node.type += 'Reference';
      // @ts-expect-error: mutate.
      node.referenceType = referenceType;
      // @ts-expect-error: mutate.
      delete node.url;
      delete node.title;
    } else {
      // @ts-expect-error: mutate.
      delete node.identifier;
      // @ts-expect-error: mutate.
      delete node.label;
    }
    this.data.referenceType = undefined;
  }

  /**
   * @this {CompileContext}
   * @type {Handle}
   */

  function onexitimage() {
    const node = this.stack[this.stack.length - 1];
    // Note: there are also `identifier` and `label` fields on this link node!
    // These are used / cleaned here.

    // To do: clean.
    if (this.data.inReference) {
      /** @type {ReferenceType} */
      const referenceType = this.data.referenceType || 'shortcut';
      node.type += 'Reference';
      // @ts-expect-error: mutate.
      node.referenceType = referenceType;
      // @ts-expect-error: mutate.
      delete node.url;
      delete node.title;
    } else {
      // @ts-expect-error: mutate.
      delete node.identifier;
      // @ts-expect-error: mutate.
      delete node.label;
    }
    this.data.referenceType = undefined;
  }

  /**
   * @this {CompileContext}
   * @type {Handle}
   */

  function onexitlabeltext(token) {
    const string = this.sliceSerialize(token);
    const ancestor = this.stack[this.stack.length - 2];
    // @ts-expect-error: stash this on the node, as it might become a reference
    // later.
    ancestor.label = decodeString(string);
    // @ts-expect-error: same as above.
    ancestor.identifier = normalizeIdentifier(string).toLowerCase();
  }

  /**
   * @this {CompileContext}
   * @type {Handle}
   */

  function onexitlabel() {
    const fragment = this.stack[this.stack.length - 1];
    const value = this.resume();
    const node = this.stack[this.stack.length - 1];
    // Assume a reference.
    this.data.inReference = true;
    if (node.type === 'link') {
      /** @type {Array<PhrasingContent>} */
      const children = fragment.children;
      node.children = children;
    } else {
      node.alt = value;
    }
  }

  /**
   * @this {CompileContext}
   * @type {Handle}
   */

  function onexitresourcedestinationstring() {
    const data = this.resume();
    const node = this.stack[this.stack.length - 1];
    node.url = data;
  }

  /**
   * @this {CompileContext}
   * @type {Handle}
   */

  function onexitresourcetitlestring() {
    const data = this.resume();
    const node = this.stack[this.stack.length - 1];
    node.title = data;
  }

  /**
   * @this {CompileContext}
   * @type {Handle}
   */

  function onexitresource() {
    this.data.inReference = undefined;
  }

  /**
   * @this {CompileContext}
   * @type {Handle}
   */

  function onenterreference() {
    this.data.referenceType = 'collapsed';
  }

  /**
   * @this {CompileContext}
   * @type {Handle}
   */

  function onexitreferencestring(token) {
    const label = this.resume();
    const node = this.stack[this.stack.length - 1];
    // @ts-expect-error: stash this on the node, as it might become a reference
    // later.
    node.label = label;
    // @ts-expect-error: same as above.
    node.identifier = normalizeIdentifier(this.sliceSerialize(token)).toLowerCase();
    this.data.referenceType = 'full';
  }

  /**
   * @this {CompileContext}
   * @type {Handle}
   */

  function onexitcharacterreferencemarker(token) {
    this.data.characterReferenceType = token.type;
  }

  /**
   * @this {CompileContext}
   * @type {Handle}
   */
  function onexitcharacterreferencevalue(token) {
    const data = this.sliceSerialize(token);
    const type = this.data.characterReferenceType;
    /** @type {string} */
    let value;
    if (type) {
      value = decodeNumericCharacterReference(data, type === "characterReferenceMarkerNumeric" ? 10 : 16);
      this.data.characterReferenceType = undefined;
    } else {
      const result = decodeNamedCharacterReference(data);
      value = result;
    }
    const tail = this.stack[this.stack.length - 1];
    tail.value += value;
  }

  /**
   * @this {CompileContext}
   * @type {Handle}
   */
  function onexitcharacterreference(token) {
    const tail = this.stack.pop();
    tail.position.end = point(token.end);
  }

  /**
   * @this {CompileContext}
   * @type {Handle}
   */
  function onexitautolinkprotocol(token) {
    onexitdata.call(this, token);
    const node = this.stack[this.stack.length - 1];
    node.url = this.sliceSerialize(token);
  }

  /**
   * @this {CompileContext}
   * @type {Handle}
   */
  function onexitautolinkemail(token) {
    onexitdata.call(this, token);
    const node = this.stack[this.stack.length - 1];
    node.url = 'mailto:' + this.sliceSerialize(token);
  }

  //
  // Creaters.
  //

  /** @returns {Blockquote} */
  function blockQuote() {
    return {
      type: 'blockquote',
      children: []
    };
  }

  /** @returns {Code} */
  function codeFlow() {
    return {
      type: 'code',
      lang: null,
      meta: null,
      value: ''
    };
  }

  /** @returns {InlineCode} */
  function codeText() {
    return {
      type: 'inlineCode',
      value: ''
    };
  }

  /** @returns {Definition} */
  function definition() {
    return {
      type: 'definition',
      identifier: '',
      label: null,
      title: null,
      url: ''
    };
  }

  /** @returns {Emphasis} */
  function emphasis() {
    return {
      type: 'emphasis',
      children: []
    };
  }

  /** @returns {Heading} */
  function heading() {
    return {
      type: 'heading',
      // @ts-expect-error `depth` will be set later.
      depth: 0,
      children: []
    };
  }

  /** @returns {Break} */
  function hardBreak() {
    return {
      type: 'break'
    };
  }

  /** @returns {Html} */
  function html() {
    return {
      type: 'html',
      value: ''
    };
  }

  /** @returns {Image} */
  function image() {
    return {
      type: 'image',
      title: null,
      url: '',
      alt: null
    };
  }

  /** @returns {Link} */
  function link() {
    return {
      type: 'link',
      title: null,
      url: '',
      children: []
    };
  }

  /**
   * @param {Token} token
   * @returns {List}
   */
  function list(token) {
    return {
      type: 'list',
      ordered: token.type === 'listOrdered',
      start: null,
      spread: token._spread,
      children: []
    };
  }

  /**
   * @param {Token} token
   * @returns {ListItem}
   */
  function listItem(token) {
    return {
      type: 'listItem',
      spread: token._spread,
      checked: null,
      children: []
    };
  }

  /** @returns {Paragraph} */
  function paragraph() {
    return {
      type: 'paragraph',
      children: []
    };
  }

  /** @returns {Strong} */
  function strong() {
    return {
      type: 'strong',
      children: []
    };
  }

  /** @returns {Text} */
  function text() {
    return {
      type: 'text',
      value: ''
    };
  }

  /** @returns {ThematicBreak} */
  function thematicBreak() {
    return {
      type: 'thematicBreak'
    };
  }
}

/**
 * Copy a point-like value.
 *
 * @param {Point} d
 *   Point-like value.
 * @returns {Point}
 *   unist point.
 */
function point(d) {
  return {
    line: d.line,
    column: d.column,
    offset: d.offset
  };
}

/**
 * @param {Config} combined
 * @param {Array<Array<Extension> | Extension>} extensions
 * @returns {undefined}
 */
function configure(combined, extensions) {
  let index = -1;
  while (++index < extensions.length) {
    const value = extensions[index];
    if (Array.isArray(value)) {
      configure(combined, value);
    } else {
      extension(combined, value);
    }
  }
}

/**
 * @param {Config} combined
 * @param {Extension} extension
 * @returns {undefined}
 */
function extension(combined, extension) {
  /** @type {keyof Extension} */
  let key;
  for (key in extension) {
    if (own.call(extension, key)) {
      switch (key) {
        case 'canContainEols':
          {
            const right = extension[key];
            if (right) {
              combined[key].push(...right);
            }
            break;
          }
        case 'transforms':
          {
            const right = extension[key];
            if (right) {
              combined[key].push(...right);
            }
            break;
          }
        case 'enter':
        case 'exit':
          {
            const right = extension[key];
            if (right) {
              Object.assign(combined[key], right);
            }
            break;
          }
        // No default
      }
    }
  }
}

/** @type {OnEnterError} */
function defaultOnError(left, right) {
  if (left) {
    throw new Error('Cannot close `' + left.type + '` (' + stringifyPosition({
      start: left.start,
      end: left.end
    }) + '): a different token (`' + right.type + '`, ' + stringifyPosition({
      start: right.start,
      end: right.end
    }) + ') is open');
  } else {
    throw new Error('Cannot close document, a token (`' + right.type + '`, ' + stringifyPosition({
      start: right.start,
      end: right.end
    }) + ') is still open');
  }
}{
  "author": "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)",
  "bugs": "https://github.com/syntax-tree/mdast-util-from-markdown/issues",
  "contributors": [
    "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)"
  ],
  "dependencies": {
    "@types/mdast": "^4.0.0",
    "@types/unist": "^3.0.0",
    "decode-named-character-reference": "^1.0.0",
    "devlop": "^1.0.0",
    "mdast-util-to-string": "^4.0.0",
    "micromark": "^4.0.0",
    "micromark-util-decode-numeric-character-reference": "^2.0.0",
    "micromark-util-decode-string": "^2.0.0",
    "micromark-util-normalize-identifier": "^2.0.0",
    "micromark-util-symbol": "^2.0.0",
    "micromark-util-types": "^2.0.0",
    "unist-util-stringify-position": "^4.0.0"
  },
  "description": "mdast utility to parse markdown",
  "devDependencies": {
    "@types/node": "^25.0.0",
    "c8": "^10.0.0",
    "commonmark.json": "^0.31.0",
    "esbuild": "^0.27.0",
    "gzip-size-cli": "^5.0.0",
    "hast-util-from-html": "^2.0.0",
    "hast-util-to-html": "^9.0.0",
    "mdast-util-to-hast": "^13.0.0",
    "micromark-build": "^2.0.0",
    "prettier": "^3.0.0",
    "remark-cli": "^12.0.0",
    "remark-preset-wooorm": "^11.0.0",
    "terser": "^5.0.0",
    "type-coverage": "^2.0.0",
    "typescript": "^5.0.0",
    "xo": "^1.0.0"
  },
  "exports": {
    "development": "./dev/index.js",
    "default": "./index.js"
  },
  "files": [
    "dev/",
    "lib/",
    "index.d.ts",
    "index.js"
  ],
  "funding": {
    "type": "opencollective",
    "url": "https://opencollective.com/unified"
  },
  "keywords": [
    "ast",
    "markdown",
    "markup",
    "mdast-util",
    "mdast",
    "parse",
    "syntax",
    "tree",
    "unist",
    "utility",
    "util"
  ],
  "license": "MIT",
  "name": "mdast-util-from-markdown",
  "prettier": {
    "bracketSpacing": false,
    "semi": false,
    "singleQuote": true,
    "tabWidth": 2,
    "trailingComma": "none",
    "useTabs": false
  },
  "remarkConfig": {
    "plugins": [
      "remark-preset-wooorm"
    ]
  },
  "repository": "syntax-tree/mdast-util-from-markdown",
  "scripts": {
    "build": "tsc --build --clean && tsc --build && type-coverage && micromark-build && esbuild . --bundle --minify | terser | gzip-size --raw",
    "format": "remark --frail --quiet --output -- . && prettier --log-level warn --write -- . && xo --fix",
    "test-api-dev": "node --conditions development test/index.js",
    "test-api-prod": "node --conditions production test/index.js",
    "test-api": "npm run test-api-dev && npm run test-api-prod",
    "test-coverage": "c8 --100 --reporter lcov -- npm run test-api",
    "test": "npm run build && npm run format && npm run test-coverage"
  },
  "sideEffects": false,
  "typeCoverage": {
    "atLeast": 100,
    "strict": true
  },
  "type": "module",
  "version": "2.0.3"
}
// Note: extra types exported from `index.d.ts`.
export {gfmFootnoteFromMarkdown, gfmFootnoteToMarkdown} from './lib/index.js'
/**
 * @import {
 *   CompileContext,
 *   Extension as FromMarkdownExtension,
 *   Handle as FromMarkdownHandle
 * } from 'mdast-util-from-markdown'
 * @import {ToMarkdownOptions} from 'mdast-util-gfm-footnote'
 * @import {
 *   Handle as ToMarkdownHandle,
 *   Map,
 *   Options as ToMarkdownExtension
 * } from 'mdast-util-to-markdown'
 * @import {FootnoteDefinition, FootnoteReference} from 'mdast'
 */

import {ok as assert} from 'devlop'
import {normalizeIdentifier} from 'micromark-util-normalize-identifier'

footnoteReference.peek = footnoteReferencePeek

/**
 * @this {CompileContext}
 * @type {FromMarkdownHandle}
 */
function enterFootnoteCallString() {
  this.buffer()
}

/**
 * @this {CompileContext}
 * @type {FromMarkdownHandle}
 */
function enterFootnoteCall(token) {
  this.enter({type: 'footnoteReference', identifier: '', label: ''}, token)
}

/**
 * @this {CompileContext}
 * @type {FromMarkdownHandle}
 */
function enterFootnoteDefinitionLabelString() {
  this.buffer()
}

/**
 * @this {CompileContext}
 * @type {FromMarkdownHandle}
 */
function enterFootnoteDefinition(token) {
  this.enter(
    {type: 'footnoteDefinition', identifier: '', label: '', children: []},
    token
  )
}

/**
 * @this {CompileContext}
 * @type {FromMarkdownHandle}
 */
function exitFootnoteCallString(token) {
  const label = this.resume()
  const node = this.stack[this.stack.length - 1]
  assert(node.type === 'footnoteReference')
  node.identifier = normalizeIdentifier(
    this.sliceSerialize(token)
  ).toLowerCase()
  node.label = label
}

/**
 * @this {CompileContext}
 * @type {FromMarkdownHandle}
 */
function exitFootnoteCall(token) {
  this.exit(token)
}

/**
 * @this {CompileContext}
 * @type {FromMarkdownHandle}
 */
function exitFootnoteDefinitionLabelString(token) {
  const label = this.resume()
  const node = this.stack[this.stack.length - 1]
  assert(node.type === 'footnoteDefinition')
  node.identifier = normalizeIdentifier(
    this.sliceSerialize(token)
  ).toLowerCase()
  node.label = label
}

/**
 * @this {CompileContext}
 * @type {FromMarkdownHandle}
 */
function exitFootnoteDefinition(token) {
  this.exit(token)
}

/** @type {ToMarkdownHandle} */
function footnoteReferencePeek() {
  return '['
}

/**
 * @type {ToMarkdownHandle}
 * @param {FootnoteReference} node
 */
function footnoteReference(node, _, state, info) {
  const tracker = state.createTracker(info)
  let value = tracker.move('[^')
  const exit = state.enter('footnoteReference')
  const subexit = state.enter('reference')
  value += tracker.move(
    state.safe(state.associationId(node), {after: ']', before: value})
  )
  subexit()
  exit()
  value += tracker.move(']')
  return value
}

/**
 * Create an extension for `mdast-util-from-markdown` to enable GFM footnotes
 * in markdown.
 *
 * @returns {FromMarkdownExtension}
 *   Extension for `mdast-util-from-markdown`.
 */
export function gfmFootnoteFromMarkdown() {
  return {
    enter: {
      gfmFootnoteCallString: enterFootnoteCallString,
      gfmFootnoteCall: enterFootnoteCall,
      gfmFootnoteDefinitionLabelString: enterFootnoteDefinitionLabelString,
      gfmFootnoteDefinition: enterFootnoteDefinition
    },
    exit: {
      gfmFootnoteCallString: exitFootnoteCallString,
      gfmFootnoteCall: exitFootnoteCall,
      gfmFootnoteDefinitionLabelString: exitFootnoteDefinitionLabelString,
      gfmFootnoteDefinition: exitFootnoteDefinition
    }
  }
}

/**
 * Create an extension for `mdast-util-to-markdown` to enable GFM footnotes
 * in markdown.
 *
 * @param {ToMarkdownOptions | null | undefined} [options]
 *   Configuration (optional).
 * @returns {ToMarkdownExtension}
 *   Extension for `mdast-util-to-markdown`.
 */
export function gfmFootnoteToMarkdown(options) {
  // To do: next major: change default.
  let firstLineBlank = false

  if (options && options.firstLineBlank) {
    firstLineBlank = true
  }

  return {
    handlers: {footnoteDefinition, footnoteReference},
    // This is on by default already.
    unsafe: [{character: '[', inConstruct: ['label', 'phrasing', 'reference']}]
  }

  /**
   * @type {ToMarkdownHandle}
   * @param {FootnoteDefinition} node
   */
  function footnoteDefinition(node, _, state, info) {
    const tracker = state.createTracker(info)
    let value = tracker.move('[^')
    const exit = state.enter('footnoteDefinition')
    const subexit = state.enter('label')
    value += tracker.move(
      state.safe(state.associationId(node), {before: value, after: ']'})
    )
    subexit()

    value += tracker.move(']:')

    if (node.children && node.children.length > 0) {
      tracker.shift(4)

      value += tracker.move(
        (firstLineBlank ? '\n' : ' ') +
          state.indentLines(
            state.containerFlow(node, tracker.current()),
            firstLineBlank ? mapAll : mapExceptFirst
          )
      )
    }

    exit()

    return value
  }
}

/** @type {Map} */
function mapExceptFirst(line, index, blank) {
  return index === 0 ? line : mapAll(line, index, blank)
}

/** @type {Map} */
function mapAll(line, index, blank) {
  return (blank ? '' : '    ') + line
}
{
  "author": "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)",
  "bugs": "https://github.com/syntax-tree/mdast-util-gfm-footnote/issues",
  "contributors": [
    "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)"
  ],
  "dependencies": {
    "@types/mdast": "^4.0.0",
    "devlop": "^1.1.0",
    "mdast-util-from-markdown": "^2.0.0",
    "mdast-util-to-markdown": "^2.0.0",
    "micromark-util-normalize-identifier": "^2.0.0"
  },
  "description": "mdast extension to parse and serialize GFM footnotes",
  "devDependencies": {
    "@types/node": "^22.0.0",
    "c8": "^10.0.0",
    "micromark-extension-gfm-footnote": "^2.0.0",
    "prettier": "^3.0.0",
    "remark-cli": "^12.0.0",
    "remark-preset-wooorm": "^11.0.0",
    "type-coverage": "^2.0.0",
    "typescript": "^5.0.0",
    "xo": "^0.60.0"
  },
  "exports": "./index.js",
  "files": [
    "index.d.ts",
    "index.js",
    "lib/"
  ],
  "funding": {
    "type": "opencollective",
    "url": "https://opencollective.com/unified"
  },
  "keywords": [
    "footnote",
    "gfm",
    "markdown",
    "markup",
    "mdast-util",
    "mdast",
    "note",
    "unist",
    "utility",
    "util"
  ],
  "license": "MIT",
  "name": "mdast-util-gfm-footnote",
  "prettier": {
    "bracketSpacing": false,
    "semi": false,
    "singleQuote": true,
    "tabWidth": 2,
    "trailingComma": "none",
    "useTabs": false
  },
  "remarkConfig": {
    "plugins": [
      "remark-preset-wooorm"
    ]
  },
  "repository": "syntax-tree/mdast-util-gfm-footnote",
  "scripts": {
    "build": "tsc --build --clean && tsc --build && type-coverage",
    "format": "remark --frail --output --quiet -- . && prettier --log-level warn --write -- . && xo --fix",
    "test-api-dev": "node --conditions development test.js",
    "test-api-prod": "node --conditions production test.js",
    "test-api": "npm run test-api-dev && npm run test-api-prod",
    "test-coverage": "c8 --100 --reporter lcov npm run test-api",
    "test": "npm run build && npm run format && npm run test-coverage"
  },
  "sideEffects": false,
  "typeCoverage": {
    "atLeast": 100,
    "strict": true
  },
  "type": "module",
  "version": "2.1.0",
  "xo": {
    "overrides": [
      {
        "files": [
          "**/*.d.ts"
        ],
        "rules": {
          "@typescript-eslint/array-type": [
            "error",
            {
              "default": "generic"
            }
          ],
          "@typescript-eslint/ban-types": [
            "error",
            {
              "extendDefaults": true
            }
          ],
          "@typescript-eslint/consistent-type-definitions": [
            "error",
            "interface"
          ]
        }
      }
    ],
    "prettier": true,
    "rules": {
      "unicorn/prefer-at": "off"
    }
  }
}
// Note: extra types in `index.d.ts`.
export {
  gfmStrikethroughFromMarkdown,
  gfmStrikethroughToMarkdown
} from './lib/index.js'
/**
 * @typedef {import('mdast').Delete} Delete
 *
 * @typedef {import('mdast-util-from-markdown').CompileContext} CompileContext
 * @typedef {import('mdast-util-from-markdown').Extension} FromMarkdownExtension
 * @typedef {import('mdast-util-from-markdown').Handle} FromMarkdownHandle
 *
 * @typedef {import('mdast-util-to-markdown').ConstructName} ConstructName
 * @typedef {import('mdast-util-to-markdown').Handle} ToMarkdownHandle
 * @typedef {import('mdast-util-to-markdown').Options} ToMarkdownExtension
 */

/**
 * List of constructs that occur in phrasing (paragraphs, headings), but cannot
 * contain strikethrough.
 * So they sort of cancel each other out.
 * Note: could use a better name.
 *
 * Note: keep in sync with: <https://github.com/syntax-tree/mdast-util-to-markdown/blob/8ce8dbf/lib/unsafe.js#L14>
 *
 * @type {Array<ConstructName>}
 */
const constructsWithoutStrikethrough = [
  'autolink',
  'destinationLiteral',
  'destinationRaw',
  'reference',
  'titleQuote',
  'titleApostrophe'
]

handleDelete.peek = peekDelete

/**
 * Create an extension for `mdast-util-from-markdown` to enable GFM
 * strikethrough in markdown.
 *
 * @returns {FromMarkdownExtension}
 *   Extension for `mdast-util-from-markdown` to enable GFM strikethrough.
 */
export function gfmStrikethroughFromMarkdown() {
  return {
    canContainEols: ['delete'],
    enter: {strikethrough: enterStrikethrough},
    exit: {strikethrough: exitStrikethrough}
  }
}

/**
 * Create an extension for `mdast-util-to-markdown` to enable GFM
 * strikethrough in markdown.
 *
 * @returns {ToMarkdownExtension}
 *   Extension for `mdast-util-to-markdown` to enable GFM strikethrough.
 */
export function gfmStrikethroughToMarkdown() {
  return {
    unsafe: [
      {
        character: '~',
        inConstruct: 'phrasing',
        notInConstruct: constructsWithoutStrikethrough
      }
    ],
    handlers: {delete: handleDelete}
  }
}

/**
 * @this {CompileContext}
 * @type {FromMarkdownHandle}
 */
function enterStrikethrough(token) {
  this.enter({type: 'delete', children: []}, token)
}

/**
 * @this {CompileContext}
 * @type {FromMarkdownHandle}
 */
function exitStrikethrough(token) {
  this.exit(token)
}

/**
 * @type {ToMarkdownHandle}
 * @param {Delete} node
 */
function handleDelete(node, _, state, info) {
  const tracker = state.createTracker(info)
  const exit = state.enter('strikethrough')
  let value = tracker.move('~~')
  value += state.containerPhrasing(node, {
    ...tracker.current(),
    before: value,
    after: '~'
  })
  value += tracker.move('~~')
  exit()
  return value
}

/** @type {ToMarkdownHandle} */
function peekDelete() {
  return '~'
}
{
  "name": "mdast-util-gfm-strikethrough",
  "version": "2.0.0",
  "description": "mdast extension to parse and serialize GFM strikethrough",
  "license": "MIT",
  "keywords": [
    "unist",
    "mdast",
    "mdast-util",
    "util",
    "utility",
    "markdown",
    "markup",
    "strikethrough",
    "strike",
    "through",
    "del",
    "delete",
    "deletion",
    "gfm"
  ],
  "repository": "syntax-tree/mdast-util-gfm-strikethrough",
  "bugs": "https://github.com/syntax-tree/mdast-util-gfm-strikethrough/issues",
  "funding": {
    "type": "opencollective",
    "url": "https://opencollective.com/unified"
  },
  "author": "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)",
  "contributors": [
    "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)"
  ],
  "sideEffects": false,
  "type": "module",
  "exports": "./index.js",
  "files": [
    "lib/",
    "index.d.ts",
    "index.js"
  ],
  "dependencies": {
    "@types/mdast": "^4.0.0",
    "mdast-util-from-markdown": "^2.0.0",
    "mdast-util-to-markdown": "^2.0.0"
  },
  "devDependencies": {
    "@types/node": "^20.0.0",
    "c8": "^8.0.0",
    "micromark-extension-gfm-strikethrough": "^2.0.0",
    "prettier": "^2.0.0",
    "remark-cli": "^11.0.0",
    "remark-preset-wooorm": "^9.0.0",
    "type-coverage": "^2.0.0",
    "typescript": "^5.0.0",
    "unist-util-remove-position": "^5.0.0",
    "xo": "^0.54.0"
  },
  "scripts": {
    "prepack": "npm run build && npm run format",
    "build": "tsc --build --clean && tsc --build && type-coverage",
    "format": "remark . -qfo && prettier . -w --loglevel warn && xo --fix",
    "test-api-prod": "node --conditions production test.js",
    "test-api-dev": "node --conditions development test.js",
    "test-api": "npm run test-api-dev && npm run test-api-prod",
    "test-coverage": "c8 --100 --reporter lcov npm run test-api",
    "test": "npm run build && npm run format && npm run test-coverage"
  },
  "prettier": {
    "bracketSpacing": false,
    "semi": false,
    "singleQuote": true,
    "tabWidth": 2,
    "trailingComma": "none",
    "useTabs": false
  },
  "remarkConfig": {
    "plugins": [
      "remark-preset-wooorm"
    ]
  },
  "typeCoverage": {
    "atLeast": 100,
    "detail": true,
    "ignoreCatch": true,
    "strict": true
  },
  "xo": {
    "overrides": [
      {
        "files": [
          "**/*.ts"
        ],
        "rules": {
          "@typescript-eslint/consistent-type-definitions": "off"
        }
      }
    ],
    "prettier": true
  }
}
// Note: types exposed from `index.d.ts`.
export {gfmTableFromMarkdown, gfmTableToMarkdown} from './lib/index.js'
/**
 * @typedef {import('mdast').InlineCode} InlineCode
 * @typedef {import('mdast').Table} Table
 * @typedef {import('mdast').TableCell} TableCell
 * @typedef {import('mdast').TableRow} TableRow
 *
 * @typedef {import('markdown-table').Options} MarkdownTableOptions
 *
 * @typedef {import('mdast-util-from-markdown').CompileContext} CompileContext
 * @typedef {import('mdast-util-from-markdown').Extension} FromMarkdownExtension
 * @typedef {import('mdast-util-from-markdown').Handle} FromMarkdownHandle
 *
 * @typedef {import('mdast-util-to-markdown').Options} ToMarkdownExtension
 * @typedef {import('mdast-util-to-markdown').Handle} ToMarkdownHandle
 * @typedef {import('mdast-util-to-markdown').State} State
 * @typedef {import('mdast-util-to-markdown').Info} Info
 */

/**
 * @typedef Options
 *   Configuration.
 * @property {boolean | null | undefined} [tableCellPadding=true]
 *   Whether to add a space of padding between delimiters and cells (default:
 *   `true`).
 * @property {boolean | null | undefined} [tablePipeAlign=true]
 *   Whether to align the delimiters (default: `true`).
 * @property {MarkdownTableOptions['stringLength'] | null | undefined} [stringLength]
 *   Function to detect the length of table cell content, used when aligning
 *   the delimiters between cells (optional).
 */

import {ok as assert} from 'devlop'
import {markdownTable} from 'markdown-table'
import {defaultHandlers} from 'mdast-util-to-markdown'

/**
 * Create an extension for `mdast-util-from-markdown` to enable GFM tables in
 * markdown.
 *
 * @returns {FromMarkdownExtension}
 *   Extension for `mdast-util-from-markdown` to enable GFM tables.
 */
export function gfmTableFromMarkdown() {
  return {
    enter: {
      table: enterTable,
      tableData: enterCell,
      tableHeader: enterCell,
      tableRow: enterRow
    },
    exit: {
      codeText: exitCodeText,
      table: exitTable,
      tableData: exit,
      tableHeader: exit,
      tableRow: exit
    }
  }
}

/**
 * @this {CompileContext}
 * @type {FromMarkdownHandle}
 */
function enterTable(token) {
  const align = token._align
  assert(align, 'expected `_align` on table')
  this.enter(
    {
      type: 'table',
      align: align.map(function (d) {
        return d === 'none' ? null : d
      }),
      children: []
    },
    token
  )
  this.data.inTable = true
}

/**
 * @this {CompileContext}
 * @type {FromMarkdownHandle}
 */
function exitTable(token) {
  this.exit(token)
  this.data.inTable = undefined
}

/**
 * @this {CompileContext}
 * @type {FromMarkdownHandle}
 */
function enterRow(token) {
  this.enter({type: 'tableRow', children: []}, token)
}

/**
 * @this {CompileContext}
 * @type {FromMarkdownHandle}
 */
function exit(token) {
  this.exit(token)
}

/**
 * @this {CompileContext}
 * @type {FromMarkdownHandle}
 */
function enterCell(token) {
  this.enter({type: 'tableCell', children: []}, token)
}

// Overwrite the default code text data handler to unescape escaped pipes when
// they are in tables.
/**
 * @this {CompileContext}
 * @type {FromMarkdownHandle}
 */
function exitCodeText(token) {
  let value = this.resume()

  if (this.data.inTable) {
    value = value.replace(/\\([\\|])/g, replace)
  }

  const node = this.stack[this.stack.length - 1]
  assert(node.type === 'inlineCode')
  node.value = value
  this.exit(token)
}

/**
 * @param {string} $0
 * @param {string} $1
 * @returns {string}
 */
function replace($0, $1) {
  // Pipes work, backslashes don’t (but can’t escape pipes).
  return $1 === '|' ? $1 : $0
}

/**
 * Create an extension for `mdast-util-to-markdown` to enable GFM tables in
 * markdown.
 *
 * @param {Options | null | undefined} [options]
 *   Configuration.
 * @returns {ToMarkdownExtension}
 *   Extension for `mdast-util-to-markdown` to enable GFM tables.
 */
export function gfmTableToMarkdown(options) {
  const settings = options || {}
  const padding = settings.tableCellPadding
  const alignDelimiters = settings.tablePipeAlign
  const stringLength = settings.stringLength
  const around = padding ? ' ' : '|'

  return {
    unsafe: [
      {character: '\r', inConstruct: 'tableCell'},
      {character: '\n', inConstruct: 'tableCell'},
      // A pipe, when followed by a tab or space (padding), or a dash or colon
      // (unpadded delimiter row), could result in a table.
      {atBreak: true, character: '|', after: '[\t :-]'},
      // A pipe in a cell must be encoded.
      {character: '|', inConstruct: 'tableCell'},
      // A colon must be followed by a dash, in which case it could start a
      // delimiter row.
      {atBreak: true, character: ':', after: '-'},
      // A delimiter row can also start with a dash, when followed by more
      // dashes, a colon, or a pipe.
      // This is a stricter version than the built in check for lists, thematic
      // breaks, and setex heading underlines though:
      // <https://github.com/syntax-tree/mdast-util-to-markdown/blob/51a2038/lib/unsafe.js#L57>
      {atBreak: true, character: '-', after: '[:|-]'}
    ],
    handlers: {
      inlineCode: inlineCodeWithTable,
      table: handleTable,
      tableCell: handleTableCell,
      tableRow: handleTableRow
    }
  }

  /**
   * @type {ToMarkdownHandle}
   * @param {Table} node
   */
  function handleTable(node, _, state, info) {
    return serializeData(handleTableAsData(node, state, info), node.align)
  }

  /**
   * This function isn’t really used normally, because we handle rows at the
   * table level.
   * But, if someone passes in a table row, this ensures we make somewhat sense.
   *
   * @type {ToMarkdownHandle}
   * @param {TableRow} node
   */
  function handleTableRow(node, _, state, info) {
    const row = handleTableRowAsData(node, state, info)
    const value = serializeData([row])
    // `markdown-table` will always add an align row
    return value.slice(0, value.indexOf('\n'))
  }

  /**
   * @type {ToMarkdownHandle}
   * @param {TableCell} node
   */
  function handleTableCell(node, _, state, info) {
    const exit = state.enter('tableCell')
    const subexit = state.enter('phrasing')
    const value = state.containerPhrasing(node, {
      ...info,
      before: around,
      after: around
    })
    subexit()
    exit()
    return value
  }

  /**
   * @param {Array<Array<string>>} matrix
   * @param {Array<string | null | undefined> | null | undefined} [align]
   */
  function serializeData(matrix, align) {
    return markdownTable(matrix, {
      align,
      // @ts-expect-error: `markdown-table` types should support `null`.
      alignDelimiters,
      // @ts-expect-error: `markdown-table` types should support `null`.
      padding,
      // @ts-expect-error: `markdown-table` types should support `null`.
      stringLength
    })
  }

  /**
   * @param {Table} node
   * @param {State} state
   * @param {Info} info
   */
  function handleTableAsData(node, state, info) {
    const children = node.children
    let index = -1
    /** @type {Array<Array<string>>} */
    const result = []
    const subexit = state.enter('table')

    while (++index < children.length) {
      result[index] = handleTableRowAsData(children[index], state, info)
    }

    subexit()

    return result
  }

  /**
   * @param {TableRow} node
   * @param {State} state
   * @param {Info} info
   */
  function handleTableRowAsData(node, state, info) {
    const children = node.children
    let index = -1
    /** @type {Array<string>} */
    const result = []
    const subexit = state.enter('tableRow')

    while (++index < children.length) {
      // Note: the positional info as used here is incorrect.
      // Making it correct would be impossible due to aligning cells?
      // And it would need copy/pasting `markdown-table` into this project.
      result[index] = handleTableCell(children[index], node, state, info)
    }

    subexit()

    return result
  }

  /**
   * @type {ToMarkdownHandle}
   * @param {InlineCode} node
   */
  function inlineCodeWithTable(node, parent, state) {
    let value = defaultHandlers.inlineCode(node, parent, state)

    if (state.stack.includes('tableCell')) {
      value = value.replace(/\|/g, '\\$&')
    }

    return value
  }
}
{
  "name": "mdast-util-gfm-table",
  "version": "2.0.0",
  "description": "mdast extension to parse and serialize GFM tables",
  "license": "MIT",
  "keywords": [
    "unist",
    "mdast",
    "mdast-util",
    "util",
    "utility",
    "markdown",
    "markup",
    "table",
    "row",
    "column",
    "cell",
    "tabular",
    "gfm"
  ],
  "repository": "syntax-tree/mdast-util-gfm-table",
  "bugs": "https://github.com/syntax-tree/mdast-util-gfm-table/issues",
  "funding": {
    "type": "opencollective",
    "url": "https://opencollective.com/unified"
  },
  "author": "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)",
  "contributors": [
    "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)"
  ],
  "sideEffects": false,
  "type": "module",
  "exports": "./index.js",
  "files": [
    "lib/",
    "index.d.ts",
    "index.js"
  ],
  "dependencies": {
    "@types/mdast": "^4.0.0",
    "devlop": "^1.0.0",
    "markdown-table": "^3.0.0",
    "mdast-util-from-markdown": "^2.0.0",
    "mdast-util-to-markdown": "^2.0.0"
  },
  "devDependencies": {
    "@types/node": "^20.0.0",
    "c8": "^8.0.0",
    "micromark-extension-gfm-table": "^2.0.0",
    "prettier": "^2.0.0",
    "remark-cli": "^11.0.0",
    "remark-preset-wooorm": "^9.0.0",
    "string-width": "^6.0.0",
    "type-coverage": "^2.0.0",
    "typescript": "^5.0.0",
    "unist-util-remove-position": "^5.0.0",
    "xo": "^0.54.0"
  },
  "scripts": {
    "prepack": "npm run build && npm run format",
    "build": "tsc --build --clean && tsc --build && type-coverage",
    "format": "remark . -qfo && prettier . -w --loglevel warn && xo --fix",
    "test-api-prod": "node --conditions production test.js",
    "test-api-dev": "node --conditions development test.js",
    "test-api": "npm run test-api-dev && npm run test-api-prod",
    "test-coverage": "c8 --100 --reporter lcov npm run test-api",
    "test": "npm run build && npm run format && npm run test-coverage"
  },
  "prettier": {
    "bracketSpacing": false,
    "semi": false,
    "singleQuote": true,
    "tabWidth": 2,
    "trailingComma": "none",
    "useTabs": false
  },
  "remarkConfig": {
    "plugins": [
      "remark-preset-wooorm"
    ]
  },
  "typeCoverage": {
    "atLeast": 100,
    "detail": true,
    "ignoreCatch": true,
    "strict": true
  },
  "xo": {
    "overrides": [
      {
        "files": [
          "**/*.ts"
        ],
        "rules": {
          "@typescript-eslint/consistent-type-definitions": "off"
        }
      }
    ],
    "prettier": true
  }
}
export {phrasing} from './lib/index.js'
/**
 * @typedef {import('mdast').Html} Html
 * @typedef {import('mdast').PhrasingContent} PhrasingContent
 */

import {convert} from 'unist-util-is'

/**
 * Check if the given value is *phrasing content*.
 *
 * > 👉 **Note**: Excludes `html`, which can be both phrasing or flow.
 *
 * @param node
 *   Thing to check, typically `Node`.
 * @returns
 *   Whether `value` is phrasing content.
 */

export const phrasing =
  /** @type {(node?: unknown) => node is Exclude<PhrasingContent, Html>} */
  (
    convert([
      'break',
      'delete',
      'emphasis',
      // To do: next major: removed since footnotes were added to GFM.
      'footnote',
      'footnoteReference',
      'image',
      'imageReference',
      'inlineCode',
      // Enabled by `mdast-util-math`:
      'inlineMath',
      'link',
      'linkReference',
      // Enabled by `mdast-util-mdx`:
      'mdxJsxTextElement',
      // Enabled by `mdast-util-mdx`:
      'mdxTextExpression',
      'strong',
      'text',
      // Enabled by `mdast-util-directive`:
      'textDirective'
    ])
  )
{
  "name": "mdast-util-phrasing",
  "version": "4.1.0",
  "description": "mdast utility to check if a node is phrasing content",
  "license": "MIT",
  "keywords": [
    "unist",
    "mdast",
    "mdast=util",
    "util",
    "utility",
    "markdown",
    "phrasing"
  ],
  "repository": "syntax-tree/mdast-util-phrasing",
  "bugs": "https://github.com/syntax-tree/mdast-util-phrasing/issues",
  "funding": {
    "type": "opencollective",
    "url": "https://opencollective.com/unified"
  },
  "author": "Victor Felder <victor@draft.li> (https://draft.li)",
  "contributors": [
    "Victor Felder <victor@draft.li> (https://draft.li)",
    "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)"
  ],
  "sideEffects": false,
  "type": "module",
  "exports": "./index.js",
  "files": [
    "lib/",
    "index.d.ts",
    "index.js"
  ],
  "dependencies": {
    "@types/mdast": "^4.0.0",
    "unist-util-is": "^6.0.0"
  },
  "devDependencies": {
    "@types/node": "^20.0.0",
    "c8": "^9.0.0",
    "prettier": "^3.0.0",
    "remark-cli": "^11.0.0",
    "remark-preset-wooorm": "^9.0.0",
    "type-coverage": "^2.0.0",
    "typescript": "^5.0.0",
    "xo": "^0.56.0"
  },
  "scripts": {
    "prepack": "npm run build && npm run format",
    "build": "tsc --build --clean && tsc --build && type-coverage",
    "format": "remark . -qfo && prettier . -w --log-level warn && xo --fix",
    "test-api": "node --conditions development test.js",
    "test-coverage": "c8 --100 --reporter lcov npm run test-api",
    "test": "npm run build && npm run format && npm run test-coverage"
  },
  "prettier": {
    "bracketSpacing": false,
    "semi": false,
    "singleQuote": true,
    "tabWidth": 2,
    "trailingComma": "none",
    "useTabs": false
  },
  "remarkConfig": {
    "plugins": [
      "remark-preset-wooorm"
    ]
  },
  "typeCoverage": {
    "atLeast": 100,
    "detail": true,
    "ignoreCatch": true,
    "strict": true
  },
  "xo": {
    "prettier": true
  }
}
// Note: extra types exposed from `index.d.ts`.
export {toMarkdown} from './lib/index.js'
export {handle as defaultHandlers} from './lib/handle/index.js'
/**
 * @import {Options, State} from './types.js'
 */

const own = {}.hasOwnProperty

/**
 * @param {State} base
 * @param {Options} extension
 * @returns {State}
 */
export function configure(base, extension) {
  let index = -1
  /** @type {keyof Options} */
  let key

  // First do subextensions.
  if (extension.extensions) {
    while (++index < extension.extensions.length) {
      configure(base, extension.extensions[index])
    }
  }

  for (key in extension) {
    if (own.call(extension, key)) {
      switch (key) {
        case 'extensions': {
          // Empty.
          break
        }

        /* c8 ignore next 4 */
        case 'unsafe': {
          list(base[key], extension[key])
          break
        }

        case 'join': {
          list(base[key], extension[key])
          break
        }

        case 'handlers': {
          map(base[key], extension[key])
          break
        }

        default: {
          // @ts-expect-error: matches.
          base.options[key] = extension[key]
        }
      }
    }
  }

  return base
}

/**
 * @template T
 * @param {Array<T>} left
 * @param {Array<T> | null | undefined} right
 */
function list(left, right) {
  if (right) {
    left.push(...right)
  }
}

/**
 * @template T
 * @param {Record<string, T>} left
 * @param {Record<string, T> | null | undefined} right
 */
function map(left, right) {
  if (right) {
    Object.assign(left, right)
  }
}
/**
 * @import {Blockquote, Parents} from 'mdast'
 * @import {Info, Map, State} from 'mdast-util-to-markdown'
 */

/**
 * @param {Blockquote} node
 * @param {Parents | undefined} _
 * @param {State} state
 * @param {Info} info
 * @returns {string}
 */
export function blockquote(node, _, state, info) {
  const exit = state.enter('blockquote')
  const tracker = state.createTracker(info)
  tracker.move('> ')
  tracker.shift(2)
  const value = state.indentLines(
    state.containerFlow(node, tracker.current()),
    map
  )
  exit()
  return value
}

/** @type {Map} */
function map(line, _, blank) {
  return '>' + (blank ? '' : ' ') + line
}
/**
 * @import {Break, Parents} from 'mdast'
 * @import {Info, State} from 'mdast-util-to-markdown'
 */

import {patternInScope} from '../util/pattern-in-scope.js'

/**
 * @param {Break} _
 * @param {Parents | undefined} _1
 * @param {State} state
 * @param {Info} info
 * @returns {string}
 */
export function hardBreak(_, _1, state, info) {
  let index = -1

  while (++index < state.unsafe.length) {
    // If we can’t put eols in this construct (setext headings, tables), use a
    // space instead.
    if (
      state.unsafe[index].character === '\n' &&
      patternInScope(state.stack, state.unsafe[index])
    ) {
      return /[ \t]/.test(info.before) ? '' : ' '
    }
  }

  return '\\\n'
}
/**
 * @import {Info, Map, State} from 'mdast-util-to-markdown'
 * @import {Code, Parents} from 'mdast'
 */

import {longestStreak} from 'longest-streak'
import {formatCodeAsIndented} from '../util/format-code-as-indented.js'
import {checkFence} from '../util/check-fence.js'

/**
 * @param {Code} node
 * @param {Parents | undefined} _
 * @param {State} state
 * @param {Info} info
 * @returns {string}
 */
export function code(node, _, state, info) {
  const marker = checkFence(state)
  const raw = node.value || ''
  const suffix = marker === '`' ? 'GraveAccent' : 'Tilde'

  if (formatCodeAsIndented(node, state)) {
    const exit = state.enter('codeIndented')
    const value = state.indentLines(raw, map)
    exit()
    return value
  }

  const tracker = state.createTracker(info)
  const sequence = marker.repeat(Math.max(longestStreak(raw, marker) + 1, 3))
  const exit = state.enter('codeFenced')
  let value = tracker.move(sequence)

  if (node.lang) {
    const subexit = state.enter(`codeFencedLang${suffix}`)
    value += tracker.move(
      state.safe(node.lang, {
        before: value,
        after: ' ',
        encode: ['`'],
        ...tracker.current()
      })
    )
    subexit()
  }

  if (node.lang && node.meta) {
    const subexit = state.enter(`codeFencedMeta${suffix}`)
    value += tracker.move(' ')
    value += tracker.move(
      state.safe(node.meta, {
        before: value,
        after: '\n',
        encode: ['`'],
        ...tracker.current()
      })
    )
    subexit()
  }

  value += tracker.move('\n')

  if (raw) {
    value += tracker.move(raw + '\n')
  }

  value += tracker.move(sequence)
  exit()
  return value
}

/** @type {Map} */
function map(line, _, blank) {
  return (blank ? '' : '    ') + line
}
/**
 * @import {Info, State} from 'mdast-util-to-markdown'
 * @import {Definition, Parents} from 'mdast'
 */

import {checkQuote} from '../util/check-quote.js'

/**
 * @param {Definition} node
 * @param {Parents | undefined} _
 * @param {State} state
 * @param {Info} info
 * @returns {string}
 */
export function definition(node, _, state, info) {
  const quote = checkQuote(state)
  const suffix = quote === '"' ? 'Quote' : 'Apostrophe'
  const exit = state.enter('definition')
  let subexit = state.enter('label')
  const tracker = state.createTracker(info)
  let value = tracker.move('[')
  value += tracker.move(
    state.safe(state.associationId(node), {
      before: value,
      after: ']',
      ...tracker.current()
    })
  )
  value += tracker.move(']: ')

  subexit()

  if (
    // If there’s no url, or…
    !node.url ||
    // If there are control characters or whitespace.
    /[\0- \u007F]/.test(node.url)
  ) {
    subexit = state.enter('destinationLiteral')
    value += tracker.move('<')
    value += tracker.move(
      state.safe(node.url, {before: value, after: '>', ...tracker.current()})
    )
    value += tracker.move('>')
  } else {
    // No whitespace, raw is prettier.
    subexit = state.enter('destinationRaw')
    value += tracker.move(
      state.safe(node.url, {
        before: value,
        after: node.title ? ' ' : '\n',
        ...tracker.current()
      })
    )
  }

  subexit()

  if (node.title) {
    subexit = state.enter(`title${suffix}`)
    value += tracker.move(' ' + quote)
    value += tracker.move(
      state.safe(node.title, {
        before: value,
        after: quote,
        ...tracker.current()
      })
    )
    value += tracker.move(quote)
    subexit()
  }

  exit()

  return value
}
/**
 * @import {Info, State} from 'mdast-util-to-markdown'
 * @import {Emphasis, Parents} from 'mdast'
 */

import {checkEmphasis} from '../util/check-emphasis.js'
import {encodeCharacterReference} from '../util/encode-character-reference.js'
import {encodeInfo} from '../util/encode-info.js'

emphasis.peek = emphasisPeek

/**
 * @param {Emphasis} node
 * @param {Parents | undefined} _
 * @param {State} state
 * @param {Info} info
 * @returns {string}
 */
export function emphasis(node, _, state, info) {
  const marker = checkEmphasis(state)
  const exit = state.enter('emphasis')
  const tracker = state.createTracker(info)
  const before = tracker.move(marker)

  let between = tracker.move(
    state.containerPhrasing(node, {
      after: marker,
      before,
      ...tracker.current()
    })
  )
  const betweenHead = between.charCodeAt(0)
  const open = encodeInfo(
    info.before.charCodeAt(info.before.length - 1),
    betweenHead,
    marker
  )

  if (open.inside) {
    between = encodeCharacterReference(betweenHead) + between.slice(1)
  }

  const betweenTail = between.charCodeAt(between.length - 1)
  const close = encodeInfo(info.after.charCodeAt(0), betweenTail, marker)

  if (close.inside) {
    between = between.slice(0, -1) + encodeCharacterReference(betweenTail)
  }

  const after = tracker.move(marker)

  exit()

  state.attentionEncodeSurroundingInfo = {
    after: close.outside,
    before: open.outside
  }
  return before + between + after
}

/**
 * @param {Emphasis} _
 * @param {Parents | undefined} _1
 * @param {State} state
 * @returns {string}
 */
function emphasisPeek(_, _1, state) {
  return state.options.emphasis || '*'
}
/**
 * @import {Info, State} from 'mdast-util-to-markdown'
 * @import {Heading, Parents} from 'mdast'
 */

import {encodeCharacterReference} from '../util/encode-character-reference.js'
import {formatHeadingAsSetext} from '../util/format-heading-as-setext.js'

/**
 * @param {Heading} node
 * @param {Parents | undefined} _
 * @param {State} state
 * @param {Info} info
 * @returns {string}
 */
export function heading(node, _, state, info) {
  const rank = Math.max(Math.min(6, node.depth || 1), 1)
  const tracker = state.createTracker(info)

  if (formatHeadingAsSetext(node, state)) {
    const exit = state.enter('headingSetext')
    const subexit = state.enter('phrasing')
    const value = state.containerPhrasing(node, {
      ...tracker.current(),
      before: '\n',
      after: '\n'
    })
    subexit()
    exit()

    return (
      value +
      '\n' +
      (rank === 1 ? '=' : '-').repeat(
        // The whole size…
        value.length -
          // Minus the position of the character after the last EOL (or
          // 0 if there is none)…
          (Math.max(value.lastIndexOf('\r'), value.lastIndexOf('\n')) + 1)
      )
    )
  }

  const sequence = '#'.repeat(rank)
  const exit = state.enter('headingAtx')
  const subexit = state.enter('phrasing')

  // Note: for proper tracking, we should reset the output positions when there
  // is no content returned, because then the space is not output.
  // Practically, in that case, there is no content, so it doesn’t matter that
  // we’ve tracked one too many characters.
  tracker.move(sequence + ' ')

  let value = state.containerPhrasing(node, {
    before: '# ',
    after: '\n',
    ...tracker.current()
  })

  if (/^[\t ]/.test(value)) {
    // To do: what effect has the character reference on tracking?
    value = encodeCharacterReference(value.charCodeAt(0)) + value.slice(1)
  }

  value = value ? sequence + ' ' + value : sequence

  if (state.options.closeAtx) {
    value += ' ' + sequence
  }

  subexit()
  exit()

  return value
}
/**
 * @import {Html} from 'mdast'
 */

html.peek = htmlPeek

/**
 * @param {Html} node
 * @returns {string}
 */
export function html(node) {
  return node.value || ''
}

/**
 * @returns {string}
 */
function htmlPeek() {
  return '<'
}
/**
 * @import {Info, State} from 'mdast-util-to-markdown'
 * @import {ImageReference, Parents} from 'mdast'
 */

imageReference.peek = imageReferencePeek

/**
 * @param {ImageReference} node
 * @param {Parents | undefined} _
 * @param {State} state
 * @param {Info} info
 * @returns {string}
 */
export function imageReference(node, _, state, info) {
  const type = node.referenceType
  const exit = state.enter('imageReference')
  let subexit = state.enter('label')
  const tracker = state.createTracker(info)
  let value = tracker.move('![')
  const alt = state.safe(node.alt, {
    before: value,
    after: ']',
    ...tracker.current()
  })
  value += tracker.move(alt + '][')

  subexit()
  // Hide the fact that we’re in phrasing, because escapes don’t work.
  const stack = state.stack
  state.stack = []
  subexit = state.enter('reference')
  // Note: for proper tracking, we should reset the output positions when we end
  // up making a `shortcut` reference, because then there is no brace output.
  // Practically, in that case, there is no content, so it doesn’t matter that
  // we’ve tracked one too many characters.
  const reference = state.safe(state.associationId(node), {
    before: value,
    after: ']',
    ...tracker.current()
  })
  subexit()
  state.stack = stack
  exit()

  if (type === 'full' || !alt || alt !== reference) {
    value += tracker.move(reference + ']')
  } else if (type === 'shortcut') {
    // Remove the unwanted `[`.
    value = value.slice(0, -1)
  } else {
    value += tracker.move(']')
  }

  return value
}

/**
 * @returns {string}
 */
function imageReferencePeek() {
  return '!'
}
/**
 * @import {Info, State} from 'mdast-util-to-markdown'
 * @import {Image, Parents} from 'mdast'
 */

import {checkQuote} from '../util/check-quote.js'

image.peek = imagePeek

/**
 * @param {Image} node
 * @param {Parents | undefined} _
 * @param {State} state
 * @param {Info} info
 * @returns {string}
 */
export function image(node, _, state, info) {
  const quote = checkQuote(state)
  const suffix = quote === '"' ? 'Quote' : 'Apostrophe'
  const exit = state.enter('image')
  let subexit = state.enter('label')
  const tracker = state.createTracker(info)
  let value = tracker.move('![')
  value += tracker.move(
    state.safe(node.alt, {before: value, after: ']', ...tracker.current()})
  )
  value += tracker.move('](')

  subexit()

  if (
    // If there’s no url but there is a title…
    (!node.url && node.title) ||
    // If there are control characters or whitespace.
    /[\0- \u007F]/.test(node.url)
  ) {
    subexit = state.enter('destinationLiteral')
    value += tracker.move('<')
    value += tracker.move(
      state.safe(node.url, {before: value, after: '>', ...tracker.current()})
    )
    value += tracker.move('>')
  } else {
    // No whitespace, raw is prettier.
    subexit = state.enter('destinationRaw')
    value += tracker.move(
      state.safe(node.url, {
        before: value,
        after: node.title ? ' ' : ')',
        ...tracker.current()
      })
    )
  }

  subexit()

  if (node.title) {
    subexit = state.enter(`title${suffix}`)
    value += tracker.move(' ' + quote)
    value += tracker.move(
      state.safe(node.title, {
        before: value,
        after: quote,
        ...tracker.current()
      })
    )
    value += tracker.move(quote)
    subexit()
  }

  value += tracker.move(')')
  exit()

  return value
}

/**
 * @returns {string}
 */
function imagePeek() {
  return '!'
}
import {blockquote} from './blockquote.js'
import {hardBreak} from './break.js'
import {code} from './code.js'
import {definition} from './definition.js'
import {emphasis} from './emphasis.js'
import {heading} from './heading.js'
import {html} from './html.js'
import {image} from './image.js'
import {imageReference} from './image-reference.js'
import {inlineCode} from './inline-code.js'
import {link} from './link.js'
import {linkReference} from './link-reference.js'
import {list} from './list.js'
import {listItem} from './list-item.js'
import {paragraph} from './paragraph.js'
import {root} from './root.js'
import {strong} from './strong.js'
import {text} from './text.js'
import {thematicBreak} from './thematic-break.js'

/**
 * Default (CommonMark) handlers.
 */
export const handle = {
  blockquote,
  break: hardBreak,
  code,
  definition,
  emphasis,
  hardBreak,
  heading,
  html,
  image,
  imageReference,
  inlineCode,
  link,
  linkReference,
  list,
  listItem,
  paragraph,
  root,
  strong,
  text,
  thematicBreak
}
/**
 * @import {State} from 'mdast-util-to-markdown'
 * @import {InlineCode, Parents} from 'mdast'
 */

inlineCode.peek = inlineCodePeek

/**
 * @param {InlineCode} node
 * @param {Parents | undefined} _
 * @param {State} state
 * @returns {string}
 */
export function inlineCode(node, _, state) {
  let value = node.value || ''
  let sequence = '`'
  let index = -1

  // If there is a single grave accent on its own in the code, use a fence of
  // two.
  // If there are two in a row, use one.
  while (new RegExp('(^|[^`])' + sequence + '([^`]|$)').test(value)) {
    sequence += '`'
  }

  // If this is not just spaces or eols (tabs don’t count), and either the
  // first or last character are a space, eol, or tick, then pad with spaces.
  if (
    /[^ \r\n]/.test(value) &&
    ((/^[ \r\n]/.test(value) && /[ \r\n]$/.test(value)) || /^`|`$/.test(value))
  ) {
    value = ' ' + value + ' '
  }

  // We have a potential problem: certain characters after eols could result in
  // blocks being seen.
  // For example, if someone injected the string `'\n# b'`, then that would
  // result in an ATX heading.
  // We can’t escape characters in `inlineCode`, but because eols are
  // transformed to spaces when going from markdown to HTML anyway, we can swap
  // them out.
  while (++index < state.unsafe.length) {
    const pattern = state.unsafe[index]
    const expression = state.compilePattern(pattern)
    /** @type {RegExpExecArray | null} */
    let match

    // Only look for `atBreak`s.
    // Btw: note that `atBreak` patterns will always start the regex at LF or
    // CR.
    if (!pattern.atBreak) continue

    while ((match = expression.exec(value))) {
      let position = match.index

      // Support CRLF (patterns only look for one of the characters).
      if (
        value.charCodeAt(position) === 10 /* `\n` */ &&
        value.charCodeAt(position - 1) === 13 /* `\r` */
      ) {
        position--
      }

      value = value.slice(0, position) + ' ' + value.slice(match.index + 1)
    }
  }

  return sequence + value + sequence
}

/**
 * @returns {string}
 */
function inlineCodePeek() {
  return '`'
}
/**
 * @import {Info, State} from 'mdast-util-to-markdown'
 * @import {LinkReference, Parents} from 'mdast'
 */

linkReference.peek = linkReferencePeek

/**
 * @param {LinkReference} node
 * @param {Parents | undefined} _
 * @param {State} state
 * @param {Info} info
 * @returns {string}
 */
export function linkReference(node, _, state, info) {
  const type = node.referenceType
  const exit = state.enter('linkReference')
  let subexit = state.enter('label')
  const tracker = state.createTracker(info)
  let value = tracker.move('[')
  const text = state.containerPhrasing(node, {
    before: value,
    after: ']',
    ...tracker.current()
  })
  value += tracker.move(text + '][')

  subexit()
  // Hide the fact that we’re in phrasing, because escapes don’t work.
  const stack = state.stack
  state.stack = []
  subexit = state.enter('reference')
  // Note: for proper tracking, we should reset the output positions when we end
  // up making a `shortcut` reference, because then there is no brace output.
  // Practically, in that case, there is no content, so it doesn’t matter that
  // we’ve tracked one too many characters.
  const reference = state.safe(state.associationId(node), {
    before: value,
    after: ']',
    ...tracker.current()
  })
  subexit()
  state.stack = stack
  exit()

  if (type === 'full' || !text || text !== reference) {
    value += tracker.move(reference + ']')
  } else if (type === 'shortcut') {
    // Remove the unwanted `[`.
    value = value.slice(0, -1)
  } else {
    value += tracker.move(']')
  }

  return value
}

/**
 * @returns {string}
 */
function linkReferencePeek() {
  return '['
}
/**
 * @import {Info, State} from 'mdast-util-to-markdown'
 * @import {Link, Parents} from 'mdast'
 * @import {Exit} from '../types.js'
 */

import {checkQuote} from '../util/check-quote.js'
import {formatLinkAsAutolink} from '../util/format-link-as-autolink.js'

link.peek = linkPeek

/**
 * @param {Link} node
 * @param {Parents | undefined} _
 * @param {State} state
 * @param {Info} info
 * @returns {string}
 */
export function link(node, _, state, info) {
  const quote = checkQuote(state)
  const suffix = quote === '"' ? 'Quote' : 'Apostrophe'
  const tracker = state.createTracker(info)
  /** @type {Exit} */
  let exit
  /** @type {Exit} */
  let subexit

  if (formatLinkAsAutolink(node, state)) {
    // Hide the fact that we’re in phrasing, because escapes don’t work.
    const stack = state.stack
    state.stack = []
    exit = state.enter('autolink')
    let value = tracker.move('<')
    value += tracker.move(
      state.containerPhrasing(node, {
        before: value,
        after: '>',
        ...tracker.current()
      })
    )
    value += tracker.move('>')
    exit()
    state.stack = stack
    return value
  }

  exit = state.enter('link')
  subexit = state.enter('label')
  let value = tracker.move('[')
  value += tracker.move(
    state.containerPhrasing(node, {
      before: value,
      after: '](',
      ...tracker.current()
    })
  )
  value += tracker.move('](')
  subexit()

  if (
    // If there’s no url but there is a title…
    (!node.url && node.title) ||
    // If there are control characters or whitespace.
    /[\0- \u007F]/.test(node.url)
  ) {
    subexit = state.enter('destinationLiteral')
    value += tracker.move('<')
    value += tracker.move(
      state.safe(node.url, {before: value, after: '>', ...tracker.current()})
    )
    value += tracker.move('>')
  } else {
    // No whitespace, raw is prettier.
    subexit = state.enter('destinationRaw')
    value += tracker.move(
      state.safe(node.url, {
        before: value,
        after: node.title ? ' ' : ')',
        ...tracker.current()
      })
    )
  }

  subexit()

  if (node.title) {
    subexit = state.enter(`title${suffix}`)
    value += tracker.move(' ' + quote)
    value += tracker.move(
      state.safe(node.title, {
        before: value,
        after: quote,
        ...tracker.current()
      })
    )
    value += tracker.move(quote)
    subexit()
  }

  value += tracker.move(')')

  exit()
  return value
}

/**
 * @param {Link} node
 * @param {Parents | undefined} _
 * @param {State} state
 * @returns {string}
 */
function linkPeek(node, _, state) {
  return formatLinkAsAutolink(node, state) ? '<' : '['
}
/**
 * @import {Info, Map, State} from 'mdast-util-to-markdown'
 * @import {ListItem, Parents} from 'mdast'
 */

import {checkBullet} from '../util/check-bullet.js'
import {checkListItemIndent} from '../util/check-list-item-indent.js'

/**
 * @param {ListItem} node
 * @param {Parents | undefined} parent
 * @param {State} state
 * @param {Info} info
 * @returns {string}
 */
export function listItem(node, parent, state, info) {
  const listItemIndent = checkListItemIndent(state)
  let bullet = state.bulletCurrent || checkBullet(state)

  // Add the marker value for ordered lists.
  if (parent && parent.type === 'list' && parent.ordered) {
    bullet =
      (typeof parent.start === 'number' && parent.start > -1
        ? parent.start
        : 1) +
      (state.options.incrementListMarker === false
        ? 0
        : parent.children.indexOf(node)) +
      bullet
  }

  let size = bullet.length + 1

  if (
    listItemIndent === 'tab' ||
    (listItemIndent === 'mixed' &&
      ((parent && parent.type === 'list' && parent.spread) || node.spread))
  ) {
    size = Math.ceil(size / 4) * 4
  }

  const tracker = state.createTracker(info)
  tracker.move(bullet + ' '.repeat(size - bullet.length))
  tracker.shift(size)
  const exit = state.enter('listItem')
  const value = state.indentLines(
    state.containerFlow(node, tracker.current()),
    map
  )
  exit()

  return value

  /** @type {Map} */
  function map(line, index, blank) {
    if (index) {
      return (blank ? '' : ' '.repeat(size)) + line
    }

    return (blank ? bullet : bullet + ' '.repeat(size - bullet.length)) + line
  }
}
/**
 * @import {Info, State} from 'mdast-util-to-markdown'
 * @import {List, Parents} from 'mdast'
 */

import {checkBullet} from '../util/check-bullet.js'
import {checkBulletOther} from '../util/check-bullet-other.js'
import {checkBulletOrdered} from '../util/check-bullet-ordered.js'
import {checkRule} from '../util/check-rule.js'

/**
 * @param {List} node
 * @param {Parents | undefined} parent
 * @param {State} state
 * @param {Info} info
 * @returns {string}
 */
export function list(node, parent, state, info) {
  const exit = state.enter('list')
  const bulletCurrent = state.bulletCurrent
  /** @type {string} */
  let bullet = node.ordered ? checkBulletOrdered(state) : checkBullet(state)
  /** @type {string} */
  const bulletOther = node.ordered
    ? bullet === '.'
      ? ')'
      : '.'
    : checkBulletOther(state)
  let useDifferentMarker =
    parent && state.bulletLastUsed ? bullet === state.bulletLastUsed : false

  if (!node.ordered) {
    const firstListItem = node.children ? node.children[0] : undefined

    // If there’s an empty first list item directly in two list items,
    // we have to use a different bullet:
    //
    // ```markdown
    // * - *
    // ```
    //
    // …because otherwise it would become one big thematic break.
    if (
      // Bullet could be used as a thematic break marker:
      (bullet === '*' || bullet === '-') &&
      // Empty first list item:
      firstListItem &&
      (!firstListItem.children || !firstListItem.children[0]) &&
      // Directly in two other list items:
      state.stack[state.stack.length - 1] === 'list' &&
      state.stack[state.stack.length - 2] === 'listItem' &&
      state.stack[state.stack.length - 3] === 'list' &&
      state.stack[state.stack.length - 4] === 'listItem' &&
      // That are each the first child.
      state.indexStack[state.indexStack.length - 1] === 0 &&
      state.indexStack[state.indexStack.length - 2] === 0 &&
      state.indexStack[state.indexStack.length - 3] === 0
    ) {
      useDifferentMarker = true
    }

    // If there’s a thematic break at the start of the first list item,
    // we have to use a different bullet:
    //
    // ```markdown
    // * ---
    // ```
    //
    // …because otherwise it would become one big thematic break.
    if (checkRule(state) === bullet && firstListItem) {
      let index = -1

      while (++index < node.children.length) {
        const item = node.children[index]

        if (
          item &&
          item.type === 'listItem' &&
          item.children &&
          item.children[0] &&
          item.children[0].type === 'thematicBreak'
        ) {
          useDifferentMarker = true
          break
        }
      }
    }
  }

  if (useDifferentMarker) {
    bullet = bulletOther
  }

  state.bulletCurrent = bullet
  const value = state.containerFlow(node, info)
  state.bulletLastUsed = bullet
  state.bulletCurrent = bulletCurrent
  exit()
  return value
}
/**
 * @import {Info, State} from 'mdast-util-to-markdown'
 * @import {Paragraph, Parents} from 'mdast'
 */

/**
 * @param {Paragraph} node
 * @param {Parents | undefined} _
 * @param {State} state
 * @param {Info} info
 * @returns {string}
 */
export function paragraph(node, _, state, info) {
  const exit = state.enter('paragraph')
  const subexit = state.enter('phrasing')
  const value = state.containerPhrasing(node, info)
  subexit()
  exit()
  return value
}
/**
 * @import {Info, State} from 'mdast-util-to-markdown'
 * @import {Parents, Root} from 'mdast'
 */

import {phrasing} from 'mdast-util-phrasing'

/**
 * @param {Root} node
 * @param {Parents | undefined} _
 * @param {State} state
 * @param {Info} info
 * @returns {string}
 */
export function root(node, _, state, info) {
  // Note: `html` nodes are ambiguous.
  const hasPhrasing = node.children.some(function (d) {
    return phrasing(d)
  })

  const container = hasPhrasing ? state.containerPhrasing : state.containerFlow
  return container.call(state, node, info)
}
/**
 * @import {Info, State} from 'mdast-util-to-markdown'
 * @import {Parents, Strong} from 'mdast'
 */

import {checkStrong} from '../util/check-strong.js'
import {encodeCharacterReference} from '../util/encode-character-reference.js'
import {encodeInfo} from '../util/encode-info.js'

strong.peek = strongPeek

/**
 * @param {Strong} node
 * @param {Parents | undefined} _
 * @param {State} state
 * @param {Info} info
 * @returns {string}
 */
export function strong(node, _, state, info) {
  const marker = checkStrong(state)
  const exit = state.enter('strong')
  const tracker = state.createTracker(info)
  const before = tracker.move(marker + marker)

  let between = tracker.move(
    state.containerPhrasing(node, {
      after: marker,
      before,
      ...tracker.current()
    })
  )
  const betweenHead = between.charCodeAt(0)
  const open = encodeInfo(
    info.before.charCodeAt(info.before.length - 1),
    betweenHead,
    marker
  )

  if (open.inside) {
    between = encodeCharacterReference(betweenHead) + between.slice(1)
  }

  const betweenTail = between.charCodeAt(between.length - 1)
  const close = encodeInfo(info.after.charCodeAt(0), betweenTail, marker)

  if (close.inside) {
    between = between.slice(0, -1) + encodeCharacterReference(betweenTail)
  }

  const after = tracker.move(marker + marker)

  exit()

  state.attentionEncodeSurroundingInfo = {
    after: close.outside,
    before: open.outside
  }
  return before + between + after
}

/**
 * @param {Strong} _
 * @param {Parents | undefined} _1
 * @param {State} state
 * @returns {string}
 */
function strongPeek(_, _1, state) {
  return state.options.strong || '*'
}
/**
 * @import {Info, State} from 'mdast-util-to-markdown'
 * @import {Parents, Text} from 'mdast'
 */

/**
 * @param {Text} node
 * @param {Parents | undefined} _
 * @param {State} state
 * @param {Info} info
 * @returns {string}
 */
export function text(node, _, state, info) {
  return state.safe(node.value, info)
}
/**
 * @import {State} from 'mdast-util-to-markdown'
 * @import {Parents, ThematicBreak} from 'mdast'
 */

import {checkRuleRepetition} from '../util/check-rule-repetition.js'
import {checkRule} from '../util/check-rule.js'

/**
 * @param {ThematicBreak} _
 * @param {Parents | undefined} _1
 * @param {State} state
 * @returns {string}
 */
export function thematicBreak(_, _1, state) {
  const value = (
    checkRule(state) + (state.options.ruleSpaces ? ' ' : '')
  ).repeat(checkRuleRepetition(state))

  return state.options.ruleSpaces ? value.slice(0, -1) : value
}
/**
 * @import {Info, Join, Options, SafeConfig, State} from 'mdast-util-to-markdown'
 * @import {Nodes} from 'mdast'
 * @import {Enter, FlowParents, PhrasingParents, TrackFields} from './types.js'
 */

import {zwitch} from 'zwitch'
import {configure} from './configure.js'
import {handle as handlers} from './handle/index.js'
import {join} from './join.js'
import {unsafe} from './unsafe.js'
import {association} from './util/association.js'
import {compilePattern} from './util/compile-pattern.js'
import {containerPhrasing} from './util/container-phrasing.js'
import {containerFlow} from './util/container-flow.js'
import {indentLines} from './util/indent-lines.js'
import {safe} from './util/safe.js'
import {track} from './util/track.js'

/**
 * Turn an mdast syntax tree into markdown.
 *
 * @param {Nodes} tree
 *   Tree to serialize.
 * @param {Options | null | undefined} [options]
 *   Configuration (optional).
 * @returns {string}
 *   Serialized markdown representing `tree`.
 */
export function toMarkdown(tree, options) {
  const settings = options || {}
  /** @type {State} */
  const state = {
    associationId: association,
    containerPhrasing: containerPhrasingBound,
    containerFlow: containerFlowBound,
    createTracker: track,
    compilePattern,
    enter,
    // @ts-expect-error: GFM / frontmatter are typed in `mdast` but not defined
    // here.
    handlers: {...handlers},
    // @ts-expect-error: add `handle` in a second.
    handle: undefined,
    indentLines,
    indexStack: [],
    join: [...join],
    options: {},
    safe: safeBound,
    stack: [],
    unsafe: [...unsafe]
  }

  configure(state, settings)

  if (state.options.tightDefinitions) {
    state.join.push(joinDefinition)
  }

  state.handle = zwitch('type', {
    invalid,
    unknown,
    handlers: state.handlers
  })

  let result = state.handle(tree, undefined, state, {
    before: '\n',
    after: '\n',
    now: {line: 1, column: 1},
    lineShift: 0
  })

  if (
    result &&
    result.charCodeAt(result.length - 1) !== 10 &&
    result.charCodeAt(result.length - 1) !== 13
  ) {
    result += '\n'
  }

  return result

  /** @type {Enter} */
  function enter(name) {
    state.stack.push(name)
    return exit

    /**
     * @returns {undefined}
     */
    function exit() {
      state.stack.pop()
    }
  }
}

/**
 * @param {unknown} value
 * @returns {never}
 */
function invalid(value) {
  throw new Error('Cannot handle value `' + value + '`, expected node')
}

/**
 * @param {unknown} value
 * @returns {never}
 */
function unknown(value) {
  // Always a node.
  const node = /** @type {Nodes} */ (value)
  throw new Error('Cannot handle unknown node `' + node.type + '`')
}

/** @type {Join} */
function joinDefinition(left, right) {
  // No blank line between adjacent definitions.
  if (left.type === 'definition' && left.type === right.type) {
    return 0
  }
}

/**
 * Serialize the children of a parent that contains phrasing children.
 *
 * These children will be joined flush together.
 *
 * @this {State}
 *   Info passed around about the current state.
 * @param {PhrasingParents} parent
 *   Parent of flow nodes.
 * @param {Info} info
 *   Info on where we are in the document we are generating.
 * @returns {string}
 *   Serialized children, joined together.
 */
function containerPhrasingBound(parent, info) {
  return containerPhrasing(parent, this, info)
}

/**
 * Serialize the children of a parent that contains flow children.
 *
 * These children will typically be joined by blank lines.
 * What they are joined by exactly is defined by `Join` functions.
 *
 * @this {State}
 *   Info passed around about the current state.
 * @param {FlowParents} parent
 *   Parent of flow nodes.
 * @param {TrackFields} info
 *   Info on where we are in the document we are generating.
 * @returns {string}
 *   Serialized children, joined by (blank) lines.
 */
function containerFlowBound(parent, info) {
  return containerFlow(parent, this, info)
}

/**
 * Make a string safe for embedding in markdown constructs.
 *
 * In markdown, almost all punctuation characters can, in certain cases,
 * result in something.
 * Whether they do is highly subjective to where they happen and in what
 * they happen.
 *
 * To solve this, `mdast-util-to-markdown` tracks:
 *
 * * Characters before and after something;
 * * What “constructs” we are in.
 *
 * This information is then used by this function to escape or encode
 * special characters.
 *
 * @this {State}
 *   Info passed around about the current state.
 * @param {string | null | undefined} value
 *   Raw value to make safe.
 * @param {SafeConfig} config
 *   Configuration.
 * @returns {string}
 *   Serialized markdown safe for embedding.
 */
function safeBound(value, config) {
  return safe(this, value, config)
}
/**
 * @import {Join} from 'mdast-util-to-markdown'
 */

import {formatCodeAsIndented} from './util/format-code-as-indented.js'
import {formatHeadingAsSetext} from './util/format-heading-as-setext.js'

/** @type {Array<Join>} */
export const join = [joinDefaults]

/** @type {Join} */
function joinDefaults(left, right, parent, state) {
  // Indented code after list or another indented code.
  if (
    right.type === 'code' &&
    formatCodeAsIndented(right, state) &&
    (left.type === 'list' ||
      (left.type === right.type && formatCodeAsIndented(left, state)))
  ) {
    return false
  }

  // Join children of a list or an item.
  // In which case, `parent` has a `spread` field.
  if ('spread' in parent && typeof parent.spread === 'boolean') {
    if (
      left.type === 'paragraph' &&
      // Two paragraphs.
      (left.type === right.type ||
        right.type === 'definition' ||
        // Paragraph followed by a setext heading.
        (right.type === 'heading' && formatHeadingAsSetext(right, state)))
    ) {
      return
    }

    return parent.spread ? 1 : 0
  }
}
/**
 * @import {ConstructName, Unsafe} from 'mdast-util-to-markdown'
 */

/**
 * List of constructs that occur in phrasing (paragraphs, headings), but cannot
 * contain things like attention (emphasis, strong), images, or links.
 * So they sort of cancel each other out.
 * Note: could use a better name.
 *
 * @type {Array<ConstructName>}
 */
const fullPhrasingSpans = [
  'autolink',
  'destinationLiteral',
  'destinationRaw',
  'reference',
  'titleQuote',
  'titleApostrophe'
]

/** @type {Array<Unsafe>} */
export const unsafe = [
  {character: '\t', after: '[\\r\\n]', inConstruct: 'phrasing'},
  {character: '\t', before: '[\\r\\n]', inConstruct: 'phrasing'},
  {
    character: '\t',
    inConstruct: ['codeFencedLangGraveAccent', 'codeFencedLangTilde']
  },
  {
    character: '\r',
    inConstruct: [
      'codeFencedLangGraveAccent',
      'codeFencedLangTilde',
      'codeFencedMetaGraveAccent',
      'codeFencedMetaTilde',
      'destinationLiteral',
      'headingAtx'
    ]
  },
  {
    character: '\n',
    inConstruct: [
      'codeFencedLangGraveAccent',
      'codeFencedLangTilde',
      'codeFencedMetaGraveAccent',
      'codeFencedMetaTilde',
      'destinationLiteral',
      'headingAtx'
    ]
  },
  {character: ' ', after: '[\\r\\n]', inConstruct: 'phrasing'},
  {character: ' ', before: '[\\r\\n]', inConstruct: 'phrasing'},
  {
    character: ' ',
    inConstruct: ['codeFencedLangGraveAccent', 'codeFencedLangTilde']
  },
  // An exclamation mark can start an image, if it is followed by a link or
  // a link reference.
  {
    character: '!',
    after: '\\[',
    inConstruct: 'phrasing',
    notInConstruct: fullPhrasingSpans
  },
  // A quote can break out of a title.
  {character: '"', inConstruct: 'titleQuote'},
  // A number sign could start an ATX heading if it starts a line.
  {atBreak: true, character: '#'},
  {character: '#', inConstruct: 'headingAtx', after: '(?:[\r\n]|$)'},
  // Dollar sign and percentage are not used in markdown.
  // An ampersand could start a character reference.
  {character: '&', after: '[#A-Za-z]', inConstruct: 'phrasing'},
  // An apostrophe can break out of a title.
  {character: "'", inConstruct: 'titleApostrophe'},
  // A left paren could break out of a destination raw.
  {character: '(', inConstruct: 'destinationRaw'},
  // A left paren followed by `]` could make something into a link or image.
  {
    before: '\\]',
    character: '(',
    inConstruct: 'phrasing',
    notInConstruct: fullPhrasingSpans
  },
  // A right paren could start a list item or break out of a destination
  // raw.
  {atBreak: true, before: '\\d+', character: ')'},
  {character: ')', inConstruct: 'destinationRaw'},
  // An asterisk can start thematic breaks, list items, emphasis, strong.
  {atBreak: true, character: '*', after: '(?:[ \t\r\n*])'},
  {character: '*', inConstruct: 'phrasing', notInConstruct: fullPhrasingSpans},
  // A plus sign could start a list item.
  {atBreak: true, character: '+', after: '(?:[ \t\r\n])'},
  // A dash can start thematic breaks, list items, and setext heading
  // underlines.
  {atBreak: true, character: '-', after: '(?:[ \t\r\n-])'},
  // A dot could start a list item.
  {atBreak: true, before: '\\d+', character: '.', after: '(?:[ \t\r\n]|$)'},
  // Slash, colon, and semicolon are not used in markdown for constructs.
  // A less than can start html (flow or text) or an autolink.
  // HTML could start with an exclamation mark (declaration, cdata, comment),
  // slash (closing tag), question mark (instruction), or a letter (tag).
  // An autolink also starts with a letter.
  // Finally, it could break out of a destination literal.
  {atBreak: true, character: '<', after: '[!/?A-Za-z]'},
  {
    character: '<',
    after: '[!/?A-Za-z]',
    inConstruct: 'phrasing',
    notInConstruct: fullPhrasingSpans
  },
  {character: '<', inConstruct: 'destinationLiteral'},
  // An equals to can start setext heading underlines.
  {atBreak: true, character: '='},
  // A greater than can start block quotes and it can break out of a
  // destination literal.
  {atBreak: true, character: '>'},
  {character: '>', inConstruct: 'destinationLiteral'},
  // Question mark and at sign are not used in markdown for constructs.
  // A left bracket can start definitions, references, labels,
  {atBreak: true, character: '['},
  {character: '[', inConstruct: 'phrasing', notInConstruct: fullPhrasingSpans},
  {character: '[', inConstruct: ['label', 'reference']},
  // A backslash can start an escape (when followed by punctuation) or a
  // hard break (when followed by an eol).
  // Note: typical escapes are handled in `safe`!
  {character: '\\', after: '[\\r\\n]', inConstruct: 'phrasing'},
  // A right bracket can exit labels.
  {character: ']', inConstruct: ['label', 'reference']},
  // Caret is not used in markdown for constructs.
  // An underscore can start emphasis, strong, or a thematic break.
  {atBreak: true, character: '_'},
  {character: '_', inConstruct: 'phrasing', notInConstruct: fullPhrasingSpans},
  // A grave accent can start code (fenced or text), or it can break out of
  // a grave accent code fence.
  {atBreak: true, character: '`'},
  {
    character: '`',
    inConstruct: ['codeFencedLangGraveAccent', 'codeFencedMetaGraveAccent']
  },
  {character: '`', inConstruct: 'phrasing', notInConstruct: fullPhrasingSpans},
  // Left brace, vertical bar, right brace are not used in markdown for
  // constructs.
  // A tilde can start code (fenced).
  {atBreak: true, character: '~'}
]
/**
 * @import {AssociationId} from '../types.js'
 */

import {decodeString} from 'micromark-util-decode-string'

/**
 * Get an identifier from an association to match it to others.
 *
 * Associations are nodes that match to something else through an ID:
 * <https://github.com/syntax-tree/mdast#association>.
 *
 * The `label` of an association is the string value: character escapes and
 * references work, and casing is intact.
 * The `identifier` is used to match one association to another:
 * controversially, character escapes and references don’t work in this
 * matching: `&copy;` does not match `©`, and `\+` does not match `+`.
 *
 * But casing is ignored (and whitespace) is trimmed and collapsed: ` A\nb`
 * matches `a b`.
 * So, we do prefer the label when figuring out how we’re going to serialize:
 * it has whitespace, casing, and we can ignore most useless character
 * escapes and all character references.
 *
 * @type {AssociationId}
 */
export function association(node) {
  if (node.label || !node.identifier) {
    return node.label || ''
  }

  return decodeString(node.identifier)
}
/**
 * @import {Options, State} from 'mdast-util-to-markdown'
 */

/**
 * @param {State} state
 * @returns {Exclude<Options['bulletOrdered'], null | undefined>}
 */
export function checkBulletOrdered(state) {
  const marker = state.options.bulletOrdered || '.'

  if (marker !== '.' && marker !== ')') {
    throw new Error(
      'Cannot serialize items with `' +
        marker +
        '` for `options.bulletOrdered`, expected `.` or `)`'
    )
  }

  return marker
}
/**
 * @import {Options, State} from 'mdast-util-to-markdown'
 */

import {checkBullet} from './check-bullet.js'

/**
 * @param {State} state
 * @returns {Exclude<Options['bullet'], null | undefined>}
 */
export function checkBulletOther(state) {
  const bullet = checkBullet(state)
  const bulletOther = state.options.bulletOther

  if (!bulletOther) {
    return bullet === '*' ? '-' : '*'
  }

  if (bulletOther !== '*' && bulletOther !== '+' && bulletOther !== '-') {
    throw new Error(
      'Cannot serialize items with `' +
        bulletOther +
        '` for `options.bulletOther`, expected `*`, `+`, or `-`'
    )
  }

  if (bulletOther === bullet) {
    throw new Error(
      'Expected `bullet` (`' +
        bullet +
        '`) and `bulletOther` (`' +
        bulletOther +
        '`) to be different'
    )
  }

  return bulletOther
}
/**
 * @import {Options, State} from 'mdast-util-to-markdown'
 */

/**
 * @param {State} state
 * @returns {Exclude<Options['bullet'], null | undefined>}
 */
export function checkBullet(state) {
  const marker = state.options.bullet || '*'

  if (marker !== '*' && marker !== '+' && marker !== '-') {
    throw new Error(
      'Cannot serialize items with `' +
        marker +
        '` for `options.bullet`, expected `*`, `+`, or `-`'
    )
  }

  return marker
}
/**
 * @import {Options, State} from 'mdast-util-to-markdown'
 */

/**
 * @param {State} state
 * @returns {Exclude<Options['emphasis'], null | undefined>}
 */
export function checkEmphasis(state) {
  const marker = state.options.emphasis || '*'

  if (marker !== '*' && marker !== '_') {
    throw new Error(
      'Cannot serialize emphasis with `' +
        marker +
        '` for `options.emphasis`, expected `*`, or `_`'
    )
  }

  return marker
}
/**
 * @import {Options, State} from 'mdast-util-to-markdown'
 */

/**
 * @param {State} state
 * @returns {Exclude<Options['fence'], null | undefined>}
 */
export function checkFence(state) {
  const marker = state.options.fence || '`'

  if (marker !== '`' && marker !== '~') {
    throw new Error(
      'Cannot serialize code with `' +
        marker +
        '` for `options.fence`, expected `` ` `` or `~`'
    )
  }

  return marker
}
/**
 * @import {Options, State} from 'mdast-util-to-markdown'
 */

/**
 * @param {State} state
 * @returns {Exclude<Options['listItemIndent'], null | undefined>}
 */
export function checkListItemIndent(state) {
  const style = state.options.listItemIndent || 'one'

  if (style !== 'tab' && style !== 'one' && style !== 'mixed') {
    throw new Error(
      'Cannot serialize items with `' +
        style +
        '` for `options.listItemIndent`, expected `tab`, `one`, or `mixed`'
    )
  }

  return style
}
/**
 * @import {Options, State} from 'mdast-util-to-markdown'
 */

/**
 * @param {State} state
 * @returns {Exclude<Options['quote'], null | undefined>}
 */
export function checkQuote(state) {
  const marker = state.options.quote || '"'

  if (marker !== '"' && marker !== "'") {
    throw new Error(
      'Cannot serialize title with `' +
        marker +
        '` for `options.quote`, expected `"`, or `\'`'
    )
  }

  return marker
}
/**
 * @import {Options, State} from 'mdast-util-to-markdown'
 */

/**
 * @param {State} state
 * @returns {Exclude<Options['ruleRepetition'], null | undefined>}
 */
export function checkRuleRepetition(state) {
  const repetition = state.options.ruleRepetition || 3

  if (repetition < 3) {
    throw new Error(
      'Cannot serialize rules with repetition `' +
        repetition +
        '` for `options.ruleRepetition`, expected `3` or more'
    )
  }

  return repetition
}
/**
 * @import {Options, State} from 'mdast-util-to-markdown'
 */

/**
 * @param {State} state
 * @returns {Exclude<Options['rule'], null | undefined>}
 */
export function checkRule(state) {
  const marker = state.options.rule || '*'

  if (marker !== '*' && marker !== '-' && marker !== '_') {
    throw new Error(
      'Cannot serialize rules with `' +
        marker +
        '` for `options.rule`, expected `*`, `-`, or `_`'
    )
  }

  return marker
}
/**
 * @import {Options, State} from 'mdast-util-to-markdown'
 */

/**
 * @param {State} state
 * @returns {Exclude<Options['strong'], null | undefined>}
 */
export function checkStrong(state) {
  const marker = state.options.strong || '*'

  if (marker !== '*' && marker !== '_') {
    throw new Error(
      'Cannot serialize strong with `' +
        marker +
        '` for `options.strong`, expected `*`, or `_`'
    )
  }

  return marker
}
/**
 * @import {CompilePattern} from '../types.js'
 */

/**
 * @type {CompilePattern}
 */
export function compilePattern(pattern) {
  if (!pattern._compiled) {
    const before =
      (pattern.atBreak ? '[\\r\\n][\\t ]*' : '') +
      (pattern.before ? '(?:' + pattern.before + ')' : '')

    pattern._compiled = new RegExp(
      (before ? '(' + before + ')' : '') +
        (/[|\\{}()[\]^$+*?.-]/.test(pattern.character) ? '\\' : '') +
        pattern.character +
        (pattern.after ? '(?:' + pattern.after + ')' : ''),
      'g'
    )
  }

  return pattern._compiled
}
/**
 * @import {State} from 'mdast-util-to-markdown'
 * @import {FlowChildren, FlowParents, TrackFields} from '../types.js'
 */

/**
 * @param {FlowParents} parent
 *   Parent of flow nodes.
 * @param {State} state
 *   Info passed around about the current state.
 * @param {TrackFields} info
 *   Info on where we are in the document we are generating.
 * @returns {string}
 *   Serialized children, joined by (blank) lines.
 */
export function containerFlow(parent, state, info) {
  const indexStack = state.indexStack
  const children = parent.children || []
  const tracker = state.createTracker(info)
  /** @type {Array<string>} */
  const results = []
  let index = -1

  indexStack.push(-1)

  while (++index < children.length) {
    const child = children[index]

    indexStack[indexStack.length - 1] = index

    results.push(
      tracker.move(
        state.handle(child, parent, state, {
          before: '\n',
          after: '\n',
          ...tracker.current()
        })
      )
    )

    if (child.type !== 'list') {
      state.bulletLastUsed = undefined
    }

    if (index < children.length - 1) {
      results.push(
        tracker.move(between(child, children[index + 1], parent, state))
      )
    }
  }

  indexStack.pop()

  return results.join('')
}

/**
 * @param {FlowChildren} left
 * @param {FlowChildren} right
 * @param {FlowParents} parent
 * @param {State} state
 * @returns {string}
 */
function between(left, right, parent, state) {
  let index = state.join.length

  while (index--) {
    const result = state.join[index](left, right, parent, state)

    if (result === true || result === 1) {
      break
    }

    if (typeof result === 'number') {
      return '\n'.repeat(1 + result)
    }

    if (result === false) {
      return '\n\n<!---->\n\n'
    }
  }

  return '\n\n'
}
/**
 * @import {Handle, Info, State} from 'mdast-util-to-markdown'
 * @import {PhrasingParents} from '../types.js'
 */

import {encodeCharacterReference} from './encode-character-reference.js'

/**
 * Serialize the children of a parent that contains phrasing children.
 *
 * These children will be joined flush together.
 *
 * @param {PhrasingParents} parent
 *   Parent of flow nodes.
 * @param {State} state
 *   Info passed around about the current state.
 * @param {Info} info
 *   Info on where we are in the document we are generating.
 * @returns {string}
 *   Serialized children, joined together.
 */
export function containerPhrasing(parent, state, info) {
  const indexStack = state.indexStack
  const children = parent.children || []
  /** @type {Array<string>} */
  const results = []
  let index = -1
  let before = info.before
  /** @type {string | undefined} */
  let encodeAfter

  indexStack.push(-1)
  let tracker = state.createTracker(info)

  while (++index < children.length) {
    const child = children[index]
    /** @type {string} */
    let after

    indexStack[indexStack.length - 1] = index

    if (index + 1 < children.length) {
      /** @type {Handle} */
      // @ts-expect-error: hush, it’s actually a `zwitch`.
      let handle = state.handle.handlers[children[index + 1].type]
      /** @type {Handle} */
      // @ts-expect-error: hush, it’s actually a `zwitch`.
      if (handle && handle.peek) handle = handle.peek
      after = handle
        ? handle(children[index + 1], parent, state, {
            before: '',
            after: '',
            ...tracker.current()
          }).charAt(0)
        : ''
    } else {
      after = info.after
    }

    // In some cases, html (text) can be found in phrasing right after an eol.
    // When we’d serialize that, in most cases that would be seen as html
    // (flow).
    // As we can’t escape or so to prevent it from happening, we take a somewhat
    // reasonable approach: replace that eol with a space.
    // See: <https://github.com/syntax-tree/mdast-util-to-markdown/issues/15>
    if (
      results.length > 0 &&
      (before === '\r' || before === '\n') &&
      child.type === 'html'
    ) {
      results[results.length - 1] = results[results.length - 1].replace(
        /(\r?\n|\r)$/,
        ' '
      )
      before = ' '

      // To do: does this work to reset tracker?
      tracker = state.createTracker(info)
      tracker.move(results.join(''))
    }

    let value = state.handle(child, parent, state, {
      ...tracker.current(),
      after,
      before
    })

    // If we had to encode the first character after the previous node and it’s
    // still the same character,
    // encode it.
    if (encodeAfter && encodeAfter === value.slice(0, 1)) {
      value =
        encodeCharacterReference(encodeAfter.charCodeAt(0)) + value.slice(1)
    }

    const encodingInfo = state.attentionEncodeSurroundingInfo
    state.attentionEncodeSurroundingInfo = undefined
    encodeAfter = undefined

    // If we have to encode the first character before the current node and
    // it’s still the same character,
    // encode it.
    if (encodingInfo) {
      if (
        results.length > 0 &&
        encodingInfo.before &&
        before === results[results.length - 1].slice(-1)
      ) {
        results[results.length - 1] =
          results[results.length - 1].slice(0, -1) +
          encodeCharacterReference(before.charCodeAt(0))
      }

      if (encodingInfo.after) encodeAfter = after
    }

    tracker.move(value)
    results.push(value)
    before = value.slice(-1)
  }

  indexStack.pop()

  return results.join('')
}
/**
 * Encode a code point as a character reference.
 *
 * @param {number} code
 *   Code point to encode.
 * @returns {string}
 *   Encoded character reference.
 */
export function encodeCharacterReference(code) {
  return '&#x' + code.toString(16).toUpperCase() + ';'
}
/**
 * @import {EncodeSides} from '../types.js'
 */

import {classifyCharacter} from 'micromark-util-classify-character'

/**
 * Check whether to encode (as a character reference) the characters
 * surrounding an attention run.
 *
 * Which characters are around an attention run influence whether it works or
 * not.
 *
 * See <https://github.com/orgs/syntax-tree/discussions/60> for more info.
 * See this markdown in a particular renderer to see what works:
 *
 * ```markdown
 * |                         | A (letter inside) | B (punctuation inside) | C (whitespace inside) | D (nothing inside) |
 * | ----------------------- | ----------------- | ---------------------- | --------------------- | ------------------ |
 * | 1 (letter outside)      | x*y*z             | x*.*z                  | x* *z                 | x**z               |
 * | 2 (punctuation outside) | .*y*.             | .*.*.                  | .* *.                 | .**.               |
 * | 3 (whitespace outside)  | x *y* z           | x *.* z                | x * * z               | x ** z             |
 * | 4 (nothing outside)     | *x*               | *.*                    | * *                   | **                 |
 * ```
 *
 * @param {number} outside
 *   Code point on the outer side of the run.
 * @param {number} inside
 *   Code point on the inner side of the run.
 * @param {'*' | '_'} marker
 *   Marker of the run.
 *   Underscores are handled more strictly (they form less often) than
 *   asterisks.
 * @returns {EncodeSides}
 *   Whether to encode characters.
 */
// Important: punctuation must never be encoded.
// Punctuation is solely used by markdown constructs.
// And by encoding itself.
// Encoding them will break constructs or double encode things.
export function encodeInfo(outside, inside, marker) {
  const outsideKind = classifyCharacter(outside)
  const insideKind = classifyCharacter(inside)

  // Letter outside:
  if (outsideKind === undefined) {
    return insideKind === undefined
      ? // Letter inside:
        // we have to encode *both* letters for `_` as it is looser.
        // it already forms for `*` (and GFMs `~`).
        marker === '_'
        ? {inside: true, outside: true}
        : {inside: false, outside: false}
      : insideKind === 1
        ? // Whitespace inside: encode both (letter, whitespace).
          {inside: true, outside: true}
        : // Punctuation inside: encode outer (letter)
          {inside: false, outside: true}
  }

  // Whitespace outside:
  if (outsideKind === 1) {
    return insideKind === undefined
      ? // Letter inside: already forms.
        {inside: false, outside: false}
      : insideKind === 1
        ? // Whitespace inside: encode both (whitespace).
          {inside: true, outside: true}
        : // Punctuation inside: already forms.
          {inside: false, outside: false}
  }

  // Punctuation outside:
  return insideKind === undefined
    ? // Letter inside: already forms.
      {inside: false, outside: false}
    : insideKind === 1
      ? // Whitespace inside: encode inner (whitespace).
        {inside: true, outside: false}
      : // Punctuation inside: already forms.
        {inside: false, outside: false}
}
/**
 * @import {State} from 'mdast-util-to-markdown'
 * @import {Code} from 'mdast'
 */

/**
 * @param {Code} node
 * @param {State} state
 * @returns {boolean}
 */
export function formatCodeAsIndented(node, state) {
  return Boolean(
    state.options.fences === false &&
      node.value &&
      // If there’s no info…
      !node.lang &&
      // And there’s a non-whitespace character…
      /[^ \r\n]/.test(node.value) &&
      // And the value doesn’t start or end in a blank…
      !/^[\t ]*(?:[\r\n]|$)|(?:^|[\r\n])[\t ]*$/.test(node.value)
  )
}
/**
 * @import {State} from 'mdast-util-to-markdown'
 * @import {Heading} from 'mdast'
 */

import {EXIT, visit} from 'unist-util-visit'
import {toString} from 'mdast-util-to-string'

/**
 * @param {Heading} node
 * @param {State} state
 * @returns {boolean}
 */
export function formatHeadingAsSetext(node, state) {
  let literalWithBreak = false

  // Look for literals with a line break.
  // Note that this also
  visit(node, function (node) {
    if (
      ('value' in node && /\r?\n|\r/.test(node.value)) ||
      node.type === 'break'
    ) {
      literalWithBreak = true
      return EXIT
    }
  })

  return Boolean(
    (!node.depth || node.depth < 3) &&
      toString(node) &&
      (state.options.setext || literalWithBreak)
  )
}
/**
 * @import {State} from 'mdast-util-to-markdown'
 * @import {Link} from 'mdast'
 */

import {toString} from 'mdast-util-to-string'

/**
 * @param {Link} node
 * @param {State} state
 * @returns {boolean}
 */
export function formatLinkAsAutolink(node, state) {
  const raw = toString(node)

  return Boolean(
    !state.options.resourceLink &&
      // If there’s a url…
      node.url &&
      // And there’s a no title…
      !node.title &&
      // And the content of `node` is a single text node…
      node.children &&
      node.children.length === 1 &&
      node.children[0].type === 'text' &&
      // And if the url is the same as the content…
      (raw === node.url || 'mailto:' + raw === node.url) &&
      // And that starts w/ a protocol…
      /^[a-z][a-z+.-]+:/i.test(node.url) &&
      // And that doesn’t contain ASCII control codes (character escapes and
      // references don’t work), space, or angle brackets…
      !/[\0- <>\u007F]/.test(node.url)
  )
}
/**
 * @import {IndentLines} from '../types.js'
 */

const eol = /\r?\n|\r/g

/**
 * @type {IndentLines}
 */
export function indentLines(value, map) {
  /** @type {Array<string>} */
  const result = []
  let start = 0
  let line = 0
  /** @type {RegExpExecArray | null} */
  let match

  while ((match = eol.exec(value))) {
    one(value.slice(start, match.index))
    result.push(match[0])
    start = match.index + match[0].length
    line++
  }

  one(value.slice(start))

  return result.join('')

  /**
   * @param {string} value
   */
  function one(value) {
    result.push(map(value, line, !value))
  }
}
/**
 * @import {ConstructName, Unsafe} from 'mdast-util-to-markdown'
 */

/**
 * @param {Array<ConstructName>} stack
 * @param {Unsafe} pattern
 * @returns {boolean}
 */
export function patternInScope(stack, pattern) {
  return (
    listInScope(stack, pattern.inConstruct, true) &&
    !listInScope(stack, pattern.notInConstruct, false)
  )
}

/**
 * @param {Array<ConstructName>} stack
 * @param {Unsafe['inConstruct']} list
 * @param {boolean} none
 * @returns {boolean}
 */
function listInScope(stack, list, none) {
  if (typeof list === 'string') {
    list = [list]
  }

  if (!list || list.length === 0) {
    return none
  }

  let index = -1

  while (++index < list.length) {
    if (stack.includes(list[index])) {
      return true
    }
  }

  return false
}
/**
 * @import {SafeConfig, State} from 'mdast-util-to-markdown'
 */

import {encodeCharacterReference} from './encode-character-reference.js'
import {patternInScope} from './pattern-in-scope.js'

/**
 * Make a string safe for embedding in markdown constructs.
 *
 * In markdown, almost all punctuation characters can, in certain cases,
 * result in something.
 * Whether they do is highly subjective to where they happen and in what
 * they happen.
 *
 * To solve this, `mdast-util-to-markdown` tracks:
 *
 * * Characters before and after something;
 * * What “constructs” we are in.
 *
 * This information is then used by this function to escape or encode
 * special characters.
 *
 * @param {State} state
 *   Info passed around about the current state.
 * @param {string | null | undefined} input
 *   Raw value to make safe.
 * @param {SafeConfig} config
 *   Configuration.
 * @returns {string}
 *   Serialized markdown safe for embedding.
 */
export function safe(state, input, config) {
  const value = (config.before || '') + (input || '') + (config.after || '')
  /** @type {Array<number>} */
  const positions = []
  /** @type {Array<string>} */
  const result = []
  /** @type {Record<number, {before: boolean, after: boolean}>} */
  const infos = {}
  let index = -1

  while (++index < state.unsafe.length) {
    const pattern = state.unsafe[index]

    if (!patternInScope(state.stack, pattern)) {
      continue
    }

    const expression = state.compilePattern(pattern)
    /** @type {RegExpExecArray | null} */
    let match

    while ((match = expression.exec(value))) {
      const before = 'before' in pattern || Boolean(pattern.atBreak)
      const after = 'after' in pattern
      const position = match.index + (before ? match[1].length : 0)

      if (positions.includes(position)) {
        if (infos[position].before && !before) {
          infos[position].before = false
        }

        if (infos[position].after && !after) {
          infos[position].after = false
        }
      } else {
        positions.push(position)
        infos[position] = {before, after}
      }
    }
  }

  positions.sort(numerical)

  let start = config.before ? config.before.length : 0
  const end = value.length - (config.after ? config.after.length : 0)
  index = -1

  while (++index < positions.length) {
    const position = positions[index]

    // Character before or after matched:
    if (position < start || position >= end) {
      continue
    }

    // If this character is supposed to be escaped because it has a condition on
    // the next character, and the next character is definitly being escaped,
    // then skip this escape.
    if (
      (position + 1 < end &&
        positions[index + 1] === position + 1 &&
        infos[position].after &&
        !infos[position + 1].before &&
        !infos[position + 1].after) ||
      (positions[index - 1] === position - 1 &&
        infos[position].before &&
        !infos[position - 1].before &&
        !infos[position - 1].after)
    ) {
      continue
    }

    if (start !== position) {
      // If we have to use a character reference, an ampersand would be more
      // correct, but as backslashes only care about punctuation, either will
      // do the trick
      result.push(escapeBackslashes(value.slice(start, position), '\\'))
    }

    start = position

    if (
      /[!-/:-@[-`{-~]/.test(value.charAt(position)) &&
      (!config.encode || !config.encode.includes(value.charAt(position)))
    ) {
      // Character escape.
      result.push('\\')
    } else {
      // Character reference.
      result.push(encodeCharacterReference(value.charCodeAt(position)))
      start++
    }
  }

  result.push(escapeBackslashes(value.slice(start, end), config.after))

  return result.join('')
}

/**
 * @param {number} a
 * @param {number} b
 * @returns {number}
 */
function numerical(a, b) {
  return a - b
}

/**
 * @param {string} value
 * @param {string} after
 * @returns {string}
 */
function escapeBackslashes(value, after) {
  const expression = /\\(?=[!-/:-@[-`{-~])/g
  /** @type {Array<number>} */
  const positions = []
  /** @type {Array<string>} */
  const results = []
  const whole = value + after
  let index = -1
  let start = 0
  /** @type {RegExpExecArray | null} */
  let match

  while ((match = expression.exec(whole))) {
    positions.push(match.index)
  }

  while (++index < positions.length) {
    if (start !== positions[index]) {
      results.push(value.slice(start, positions[index]))
    }

    results.push('\\')
    start = positions[index]
  }

  results.push(value.slice(start))

  return results.join('')
}
/**
 * @import {CreateTracker, TrackCurrent, TrackMove, TrackShift} from '../types.js'
 */

/**
 * Track positional info in the output.
 *
 * @type {CreateTracker}
 */
export function track(config) {
  // Defaults are used to prevent crashes when older utilities somehow activate
  // this code.
  /* c8 ignore next 5 */
  const options = config || {}
  const now = options.now || {}
  let lineShift = options.lineShift || 0
  let line = now.line || 1
  let column = now.column || 1

  return {move, current, shift}

  /**
   * Get the current tracked info.
   *
   * @type {TrackCurrent}
   */
  function current() {
    return {now: {line, column}, lineShift}
  }

  /**
   * Define an increased line shift (the typical indent for lines).
   *
   * @type {TrackShift}
   */
  function shift(value) {
    lineShift += value
  }

  /**
   * Move past some generated markdown.
   *
   * @type {TrackMove}
   */
  function move(input) {
    // eslint-disable-next-line unicorn/prefer-default-parameters
    const value = input || ''
    const chunks = value.split(/\r?\n|\r/g)
    const tail = chunks[chunks.length - 1]
    line += chunks.length - 1
    column =
      chunks.length === 1 ? column + tail.length : 1 + tail.length + lineShift
    return value
  }
}
{
  "author": "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)",
  "bugs": "https://github.com/syntax-tree/mdast-util-to-markdown/issues",
  "contributors": [
    "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)"
  ],
  "dependencies": {
    "@types/mdast": "^4.0.0",
    "@types/unist": "^3.0.0",
    "longest-streak": "^3.0.0",
    "mdast-util-phrasing": "^4.0.0",
    "mdast-util-to-string": "^4.0.0",
    "micromark-util-classify-character": "^2.0.0",
    "micromark-util-decode-string": "^2.0.0",
    "unist-util-visit": "^5.0.0",
    "zwitch": "^2.0.0"
  },
  "description": "mdast utility to serialize markdown",
  "devDependencies": {
    "@types/node": "^22.0.0",
    "c8": "^10.0.0",
    "mdast-util-from-markdown": "^2.0.0",
    "prettier": "^3.0.0",
    "remark-cli": "^12.0.0",
    "remark-preset-wooorm": "^10.0.0",
    "type-coverage": "^2.0.0",
    "typescript": "^5.0.0",
    "unist-util-remove-position": "^5.0.0",
    "xo": "^0.59.0"
  },
  "exports": "./index.js",
  "files": [
    "lib/",
    "index.d.ts",
    "index.js"
  ],
  "funding": {
    "type": "opencollective",
    "url": "https://opencollective.com/unified"
  },
  "keywords": [
    "ast",
    "compile",
    "markdown",
    "markup",
    "mdast-util",
    "mdast",
    "serialize",
    "stringify",
    "syntax",
    "tree",
    "unist",
    "utility",
    "util"
  ],
  "license": "MIT",
  "name": "mdast-util-to-markdown",
  "prettier": {
    "bracketSpacing": false,
    "semi": false,
    "singleQuote": true,
    "tabWidth": 2,
    "trailingComma": "none",
    "useTabs": false
  },
  "remarkConfig": {
    "plugins": [
      "remark-preset-wooorm"
    ]
  },
  "repository": "syntax-tree/mdast-util-to-markdown",
  "version": "2.1.2",
  "scripts": {
    "prepack": "npm run build && npm run format",
    "build": "tsc --build --clean && tsc --build && type-coverage",
    "format": "remark . -qfo && prettier . -w --log-level warn && xo --fix",
    "test-api": "node --conditions development test/index.js",
    "test-coverage": "c8 --100 --reporter lcov npm run test-api",
    "test": "npm run build && npm run format && npm run test-coverage"
  },
  "sideEffects": false,
  "typeCoverage": {
    "atLeast": 100,
    "ignoreFiles": [
      "lib/types.d.ts"
    ],
    "strict": true
  },
  "type": "module",
  "xo": {
    "overrides": [
      {
        "files": [
          "**/*.d.ts"
        ],
        "rules": {
          "@typescript-eslint/array-type": [
            "error",
            {
              "default": "generic"
            }
          ],
          "@typescript-eslint/ban-types": [
            "error",
            {
              "extendDefaults": true
            }
          ],
          "@typescript-eslint/consistent-type-definitions": [
            "error",
            "interface"
          ]
        }
      },
      {
        "files": [
          "test/**/*.js"
        ],
        "rules": {
          "max-depth": "off",
          "no-await-in-loop": "off"
        }
      }
    ],
    "prettier": true,
    "rules": {
      "complexity": "off",
      "unicorn/prefer-at": "off",
      "unicorn/prefer-code-point": "off"
    }
  }
}
/**
 * @typedef {import('./lib/index.js').Options} Options
 */

export {toString} from './lib/index.js'
/**
 * @typedef {import('mdast').Nodes} Nodes
 *
 * @typedef Options
 *   Configuration (optional).
 * @property {boolean | null | undefined} [includeImageAlt=true]
 *   Whether to use `alt` for `image`s (default: `true`).
 * @property {boolean | null | undefined} [includeHtml=true]
 *   Whether to use `value` of HTML (default: `true`).
 */

/** @type {Options} */
const emptyOptions = {}

/**
 * Get the text content of a node or list of nodes.
 *
 * Prefers the node’s plain-text fields, otherwise serializes its children,
 * and if the given value is an array, serialize the nodes in it.
 *
 * @param {unknown} [value]
 *   Thing to serialize, typically `Node`.
 * @param {Options | null | undefined} [options]
 *   Configuration (optional).
 * @returns {string}
 *   Serialized `value`.
 */
export function toString(value, options) {
  const settings = options || emptyOptions
  const includeImageAlt =
    typeof settings.includeImageAlt === 'boolean'
      ? settings.includeImageAlt
      : true
  const includeHtml =
    typeof settings.includeHtml === 'boolean' ? settings.includeHtml : true

  return one(value, includeImageAlt, includeHtml)
}

/**
 * One node or several nodes.
 *
 * @param {unknown} value
 *   Thing to serialize.
 * @param {boolean} includeImageAlt
 *   Include image `alt`s.
 * @param {boolean} includeHtml
 *   Include HTML.
 * @returns {string}
 *   Serialized node.
 */
function one(value, includeImageAlt, includeHtml) {
  if (node(value)) {
    if ('value' in value) {
      return value.type === 'html' && !includeHtml ? '' : value.value
    }

    if (includeImageAlt && 'alt' in value && value.alt) {
      return value.alt
    }

    if ('children' in value) {
      return all(value.children, includeImageAlt, includeHtml)
    }
  }

  if (Array.isArray(value)) {
    return all(value, includeImageAlt, includeHtml)
  }

  return ''
}

/**
 * Serialize a list of nodes.
 *
 * @param {Array<unknown>} values
 *   Thing to serialize.
 * @param {boolean} includeImageAlt
 *   Include image `alt`s.
 * @param {boolean} includeHtml
 *   Include HTML.
 * @returns {string}
 *   Serialized nodes.
 */
function all(values, includeImageAlt, includeHtml) {
  /** @type {Array<string>} */
  const result = []
  let index = -1

  while (++index < values.length) {
    result[index] = one(values[index], includeImageAlt, includeHtml)
  }

  return result.join('')
}

/**
 * Check if `value` looks like a node.
 *
 * @param {unknown} value
 *   Thing.
 * @returns {value is Nodes}
 *   Whether `value` is a node.
 */
function node(value) {
  return Boolean(value && typeof value === 'object')
}
{
  "name": "mdast-util-to-string",
  "version": "4.0.0",
  "description": "mdast utility to get the plain text content of a node",
  "license": "MIT",
  "keywords": [
    "unist",
    "mdast",
    "mdast-util",
    "util",
    "utility",
    "markdown",
    "node",
    "string",
    "serialize"
  ],
  "repository": "syntax-tree/mdast-util-to-string",
  "bugs": "https://github.com/syntax-tree/mdast-util-to-string/issues",
  "funding": {
    "type": "opencollective",
    "url": "https://opencollective.com/unified"
  },
  "author": "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)",
  "contributors": [
    "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)"
  ],
  "sideEffects": false,
  "type": "module",
  "exports": "./index.js",
  "files": [
    "lib/",
    "index.d.ts",
    "index.js"
  ],
  "dependencies": {
    "@types/mdast": "^4.0.0"
  },
  "devDependencies": {
    "@types/node": "^20.0.0",
    "c8": "^8.0.0",
    "prettier": "^2.0.0",
    "remark-cli": "^11.0.0",
    "remark-preset-wooorm": "^9.0.0",
    "type-coverage": "^2.0.0",
    "typescript": "^5.0.0",
    "xo": "^0.54.0"
  },
  "scripts": {
    "prepack": "npm run build && npm run format",
    "build": "tsc --build --clean && tsc --build && type-coverage",
    "format": "remark . -qfo && prettier . -w --loglevel warn && xo --fix",
    "test-api": "node --conditions development test.js",
    "test-coverage": "c8 --100 --reporter lcov npm run test-api",
    "test": "npm run build && npm run format && npm run test-coverage"
  },
  "prettier": {
    "bracketSpacing": false,
    "semi": false,
    "singleQuote": true,
    "tabWidth": 2,
    "trailingComma": "none",
    "useTabs": false
  },
  "remarkConfig": {
    "plugins": [
      "remark-preset-wooorm"
    ]
  },
  "typeCoverage": {
    "atLeast": 100,
    "detail": true,
    "ignoreCatch": true,
    "strict": true
  },
  "xo": {
    "prettier": true
  }
}
export { attention } from './lib/attention.js';
export { autolink } from './lib/autolink.js';
export { blankLine } from './lib/blank-line.js';
export { blockQuote } from './lib/block-quote.js';
export { characterEscape } from './lib/character-escape.js';
export { characterReference } from './lib/character-reference.js';
export { codeFenced } from './lib/code-fenced.js';
export { codeIndented } from './lib/code-indented.js';
export { codeText } from './lib/code-text.js';
export { content } from './lib/content.js';
export { definition } from './lib/definition.js';
export { hardBreakEscape } from './lib/hard-break-escape.js';
export { headingAtx } from './lib/heading-atx.js';
export { htmlFlow } from './lib/html-flow.js';
export { htmlText } from './lib/html-text.js';
export { labelEnd } from './lib/label-end.js';
export { labelStartImage } from './lib/label-start-image.js';
export { labelStartLink } from './lib/label-start-link.js';
export { lineEnding } from './lib/line-ending.js';
export { list } from './lib/list.js';
export { setextUnderline } from './lib/setext-underline.js';
export { thematicBreak } from './lib/thematic-break.js';/**
 * @import {
 *   Code,
 *   Construct,
 *   Event,
 *   Point,
 *   Resolver,
 *   State,
 *   TokenizeContext,
 *   Tokenizer,
 *   Token
 * } from 'micromark-util-types'
 */

import { push, splice } from 'micromark-util-chunked';
import { classifyCharacter } from 'micromark-util-classify-character';
import { resolveAll } from 'micromark-util-resolve-all';
/** @type {Construct} */
export const attention = {
  name: 'attention',
  resolveAll: resolveAllAttention,
  tokenize: tokenizeAttention
};

/**
 * Take all events and resolve attention to emphasis or strong.
 *
 * @type {Resolver}
 */
// eslint-disable-next-line complexity
function resolveAllAttention(events, context) {
  let index = -1;
  /** @type {number} */
  let open;
  /** @type {Token} */
  let group;
  /** @type {Token} */
  let text;
  /** @type {Token} */
  let openingSequence;
  /** @type {Token} */
  let closingSequence;
  /** @type {number} */
  let use;
  /** @type {Array<Event>} */
  let nextEvents;
  /** @type {number} */
  let offset;

  // Walk through all events.
  //
  // Note: performance of this is fine on an mb of normal markdown, but it’s
  // a bottleneck for malicious stuff.
  while (++index < events.length) {
    // Find a token that can close.
    if (events[index][0] === 'enter' && events[index][1].type === 'attentionSequence' && events[index][1]._close) {
      open = index;

      // Now walk back to find an opener.
      while (open--) {
        // Find a token that can open the closer.
        if (events[open][0] === 'exit' && events[open][1].type === 'attentionSequence' && events[open][1]._open &&
        // If the markers are the same:
        context.sliceSerialize(events[open][1]).charCodeAt(0) === context.sliceSerialize(events[index][1]).charCodeAt(0)) {
          // If the opening can close or the closing can open,
          // and the close size *is not* a multiple of three,
          // but the sum of the opening and closing size *is* multiple of three,
          // then don’t match.
          if ((events[open][1]._close || events[index][1]._open) && (events[index][1].end.offset - events[index][1].start.offset) % 3 && !((events[open][1].end.offset - events[open][1].start.offset + events[index][1].end.offset - events[index][1].start.offset) % 3)) {
            continue;
          }

          // Number of markers to use from the sequence.
          use = events[open][1].end.offset - events[open][1].start.offset > 1 && events[index][1].end.offset - events[index][1].start.offset > 1 ? 2 : 1;
          const start = {
            ...events[open][1].end
          };
          const end = {
            ...events[index][1].start
          };
          movePoint(start, -use);
          movePoint(end, use);
          openingSequence = {
            type: use > 1 ? "strongSequence" : "emphasisSequence",
            start,
            end: {
              ...events[open][1].end
            }
          };
          closingSequence = {
            type: use > 1 ? "strongSequence" : "emphasisSequence",
            start: {
              ...events[index][1].start
            },
            end
          };
          text = {
            type: use > 1 ? "strongText" : "emphasisText",
            start: {
              ...events[open][1].end
            },
            end: {
              ...events[index][1].start
            }
          };
          group = {
            type: use > 1 ? "strong" : "emphasis",
            start: {
              ...openingSequence.start
            },
            end: {
              ...closingSequence.end
            }
          };
          events[open][1].end = {
            ...openingSequence.start
          };
          events[index][1].start = {
            ...closingSequence.end
          };
          nextEvents = [];

          // If there are more markers in the opening, add them before.
          if (events[open][1].end.offset - events[open][1].start.offset) {
            nextEvents = push(nextEvents, [['enter', events[open][1], context], ['exit', events[open][1], context]]);
          }

          // Opening.
          nextEvents = push(nextEvents, [['enter', group, context], ['enter', openingSequence, context], ['exit', openingSequence, context], ['enter', text, context]]);

          // Always populated by defaults.

          // Between.
          nextEvents = push(nextEvents, resolveAll(context.parser.constructs.insideSpan.null, events.slice(open + 1, index), context));

          // Closing.
          nextEvents = push(nextEvents, [['exit', text, context], ['enter', closingSequence, context], ['exit', closingSequence, context], ['exit', group, context]]);

          // If there are more markers in the closing, add them after.
          if (events[index][1].end.offset - events[index][1].start.offset) {
            offset = 2;
            nextEvents = push(nextEvents, [['enter', events[index][1], context], ['exit', events[index][1], context]]);
          } else {
            offset = 0;
          }
          splice(events, open - 1, index - open + 3, nextEvents);
          index = open + nextEvents.length - offset - 2;
          break;
        }
      }
    }
  }

  // Remove remaining sequences.
  index = -1;
  while (++index < events.length) {
    if (events[index][1].type === 'attentionSequence') {
      events[index][1].type = 'data';
    }
  }
  return events;
}

/**
 * @this {TokenizeContext}
 *   Context.
 * @type {Tokenizer}
 */
function tokenizeAttention(effects, ok) {
  const attentionMarkers = this.parser.constructs.attentionMarkers.null;
  const previous = this.previous;
  const before = classifyCharacter(previous);

  /** @type {NonNullable<Code>} */
  let marker;
  return start;

  /**
   * Before a sequence.
   *
   * ```markdown
   * > | **
   *     ^
   * ```
   *
   * @type {State}
   */
  function start(code) {
    marker = code;
    effects.enter('attentionSequence');
    return inside(code);
  }

  /**
   * In a sequence.
   *
   * ```markdown
   * > | **
   *     ^^
   * ```
   *
   * @type {State}
   */
  function inside(code) {
    if (code === marker) {
      effects.consume(code);
      return inside;
    }
    const token = effects.exit('attentionSequence');

    // To do: next major: move this to resolver, just like `markdown-rs`.
    const after = classifyCharacter(code);

    // Always populated by defaults.

    const open = !after || after === 2 && before || attentionMarkers.includes(code);
    const close = !before || before === 2 && after || attentionMarkers.includes(previous);
    token._open = Boolean(marker === 42 ? open : open && (before || !close));
    token._close = Boolean(marker === 42 ? close : close && (after || !open));
    return ok(code);
  }
}

/**
 * Move a point a bit.
 *
 * Note: `move` only works inside lines! It’s not possible to move past other
 * chunks (replacement characters, tabs, or line endings).
 *
 * @param {Point} point
 *   Point.
 * @param {number} offset
 *   Amount to move.
 * @returns {undefined}
 *   Nothing.
 */
function movePoint(point, offset) {
  point.column += offset;
  point.offset += offset;
  point._bufferIndex += offset;
}/**
 * @import {
 *   Construct,
 *   State,
 *   TokenizeContext,
 *   Tokenizer
 * } from 'micromark-util-types'
 */

import { asciiAlphanumeric, asciiAlpha, asciiAtext, asciiControl } from 'micromark-util-character';
/** @type {Construct} */
export const autolink = {
  name: 'autolink',
  tokenize: tokenizeAutolink
};

/**
 * @this {TokenizeContext}
 *   Context.
 * @type {Tokenizer}
 */
function tokenizeAutolink(effects, ok, nok) {
  let size = 0;
  return start;

  /**
   * Start of an autolink.
   *
   * ```markdown
   * > | a<https://example.com>b
   *      ^
   * > | a<user@example.com>b
   *      ^
   * ```
   *
   * @type {State}
   */
  function start(code) {
    effects.enter("autolink");
    effects.enter("autolinkMarker");
    effects.consume(code);
    effects.exit("autolinkMarker");
    effects.enter("autolinkProtocol");
    return open;
  }

  /**
   * After `<`, at protocol or atext.
   *
   * ```markdown
   * > | a<https://example.com>b
   *       ^
   * > | a<user@example.com>b
   *       ^
   * ```
   *
   * @type {State}
   */
  function open(code) {
    if (asciiAlpha(code)) {
      effects.consume(code);
      return schemeOrEmailAtext;
    }
    if (code === 64) {
      return nok(code);
    }
    return emailAtext(code);
  }

  /**
   * At second byte of protocol or atext.
   *
   * ```markdown
   * > | a<https://example.com>b
   *        ^
   * > | a<user@example.com>b
   *        ^
   * ```
   *
   * @type {State}
   */
  function schemeOrEmailAtext(code) {
    // ASCII alphanumeric and `+`, `-`, and `.`.
    if (code === 43 || code === 45 || code === 46 || asciiAlphanumeric(code)) {
      // Count the previous alphabetical from `open` too.
      size = 1;
      return schemeInsideOrEmailAtext(code);
    }
    return emailAtext(code);
  }

  /**
   * In ambiguous protocol or atext.
   *
   * ```markdown
   * > | a<https://example.com>b
   *        ^
   * > | a<user@example.com>b
   *        ^
   * ```
   *
   * @type {State}
   */
  function schemeInsideOrEmailAtext(code) {
    if (code === 58) {
      effects.consume(code);
      size = 0;
      return urlInside;
    }

    // ASCII alphanumeric and `+`, `-`, and `.`.
    if ((code === 43 || code === 45 || code === 46 || asciiAlphanumeric(code)) && size++ < 32) {
      effects.consume(code);
      return schemeInsideOrEmailAtext;
    }
    size = 0;
    return emailAtext(code);
  }

  /**
   * After protocol, in URL.
   *
   * ```markdown
   * > | a<https://example.com>b
   *             ^
   * ```
   *
   * @type {State}
   */
  function urlInside(code) {
    if (code === 62) {
      effects.exit("autolinkProtocol");
      effects.enter("autolinkMarker");
      effects.consume(code);
      effects.exit("autolinkMarker");
      effects.exit("autolink");
      return ok;
    }

    // ASCII control, space, or `<`.
    if (code === null || code === 32 || code === 60 || asciiControl(code)) {
      return nok(code);
    }
    effects.consume(code);
    return urlInside;
  }

  /**
   * In email atext.
   *
   * ```markdown
   * > | a<user.name@example.com>b
   *              ^
   * ```
   *
   * @type {State}
   */
  function emailAtext(code) {
    if (code === 64) {
      effects.consume(code);
      return emailAtSignOrDot;
    }
    if (asciiAtext(code)) {
      effects.consume(code);
      return emailAtext;
    }
    return nok(code);
  }

  /**
   * In label, after at-sign or dot.
   *
   * ```markdown
   * > | a<user.name@example.com>b
   *                 ^       ^
   * ```
   *
   * @type {State}
   */
  function emailAtSignOrDot(code) {
    return asciiAlphanumeric(code) ? emailLabel(code) : nok(code);
  }

  /**
   * In label, where `.` and `>` are allowed.
   *
   * ```markdown
   * > | a<user.name@example.com>b
   *                   ^
   * ```
   *
   * @type {State}
   */
  function emailLabel(code) {
    if (code === 46) {
      effects.consume(code);
      size = 0;
      return emailAtSignOrDot;
    }
    if (code === 62) {
      // Exit, then change the token type.
      effects.exit("autolinkProtocol").type = "autolinkEmail";
      effects.enter("autolinkMarker");
      effects.consume(code);
      effects.exit("autolinkMarker");
      effects.exit("autolink");
      return ok;
    }
    return emailValue(code);
  }

  /**
   * In label, where `.` and `>` are *not* allowed.
   *
   * Though, this is also used in `emailLabel` to parse other values.
   *
   * ```markdown
   * > | a<user.name@ex-ample.com>b
   *                    ^
   * ```
   *
   * @type {State}
   */
  function emailValue(code) {
    // ASCII alphanumeric or `-`.
    if ((code === 45 || asciiAlphanumeric(code)) && size++ < 63) {
      const next = code === 45 ? emailValue : emailLabel;
      effects.consume(code);
      return next;
    }
    return nok(code);
  }
}/**
 * @import {
 *   Construct,
 *   State,
 *   TokenizeContext,
 *   Tokenizer
 * } from 'micromark-util-types'
 */

import { factorySpace } from 'micromark-factory-space';
import { markdownLineEnding, markdownSpace } from 'micromark-util-character';
/** @type {Construct} */
export const blankLine = {
  partial: true,
  tokenize: tokenizeBlankLine
};

/**
 * @this {TokenizeContext}
 *   Context.
 * @type {Tokenizer}
 */
function tokenizeBlankLine(effects, ok, nok) {
  return start;

  /**
   * Start of blank line.
   *
   * > 👉 **Note**: `␠` represents a space character.
   *
   * ```markdown
   * > | ␠␠␊
   *     ^
   * > | ␊
   *     ^
   * ```
   *
   * @type {State}
   */
  function start(code) {
    return markdownSpace(code) ? factorySpace(effects, after, "linePrefix")(code) : after(code);
  }

  /**
   * At eof/eol, after optional whitespace.
   *
   * > 👉 **Note**: `␠` represents a space character.
   *
   * ```markdown
   * > | ␠␠␊
   *       ^
   * > | ␊
   *     ^
   * ```
   *
   * @type {State}
   */
  function after(code) {
    return code === null || markdownLineEnding(code) ? ok(code) : nok(code);
  }
}/**
 * @import {
 *   Construct,
 *   Exiter,
 *   State,
 *   TokenizeContext,
 *   Tokenizer
 * } from 'micromark-util-types'
 */

import { factorySpace } from 'micromark-factory-space';
import { markdownSpace } from 'micromark-util-character';
/** @type {Construct} */
export const blockQuote = {
  continuation: {
    tokenize: tokenizeBlockQuoteContinuation
  },
  exit,
  name: 'blockQuote',
  tokenize: tokenizeBlockQuoteStart
};

/**
 * @this {TokenizeContext}
 *   Context.
 * @type {Tokenizer}
 */
function tokenizeBlockQuoteStart(effects, ok, nok) {
  const self = this;
  return start;

  /**
   * Start of block quote.
   *
   * ```markdown
   * > | > a
   *     ^
   * ```
   *
   * @type {State}
   */
  function start(code) {
    if (code === 62) {
      const state = self.containerState;
      if (!state.open) {
        effects.enter("blockQuote", {
          _container: true
        });
        state.open = true;
      }
      effects.enter("blockQuotePrefix");
      effects.enter("blockQuoteMarker");
      effects.consume(code);
      effects.exit("blockQuoteMarker");
      return after;
    }
    return nok(code);
  }

  /**
   * After `>`, before optional whitespace.
   *
   * ```markdown
   * > | > a
   *      ^
   * ```
   *
   * @type {State}
   */
  function after(code) {
    if (markdownSpace(code)) {
      effects.enter("blockQuotePrefixWhitespace");
      effects.consume(code);
      effects.exit("blockQuotePrefixWhitespace");
      effects.exit("blockQuotePrefix");
      return ok;
    }
    effects.exit("blockQuotePrefix");
    return ok(code);
  }
}

/**
 * Start of block quote continuation.
 *
 * ```markdown
 *   | > a
 * > | > b
 *     ^
 * ```
 *
 * @this {TokenizeContext}
 *   Context.
 * @type {Tokenizer}
 */
function tokenizeBlockQuoteContinuation(effects, ok, nok) {
  const self = this;
  return contStart;

  /**
   * Start of block quote continuation.
   *
   * Also used to parse the first block quote opening.
   *
   * ```markdown
   *   | > a
   * > | > b
   *     ^
   * ```
   *
   * @type {State}
   */
  function contStart(code) {
    if (markdownSpace(code)) {
      // Always populated by defaults.

      return factorySpace(effects, contBefore, "linePrefix", self.parser.constructs.disable.null.includes('codeIndented') ? undefined : 4)(code);
    }
    return contBefore(code);
  }

  /**
   * At `>`, after optional whitespace.
   *
   * Also used to parse the first block quote opening.
   *
   * ```markdown
   *   | > a
   * > | > b
   *     ^
   * ```
   *
   * @type {State}
   */
  function contBefore(code) {
    return effects.attempt(blockQuote, ok, nok)(code);
  }
}

/** @type {Exiter} */
function exit(effects) {
  effects.exit("blockQuote");
}/**
 * @import {
 *   Construct,
 *   State,
 *   TokenizeContext,
 *   Tokenizer
 * } from 'micromark-util-types'
 */

import { asciiPunctuation } from 'micromark-util-character';
/** @type {Construct} */
export const characterEscape = {
  name: 'characterEscape',
  tokenize: tokenizeCharacterEscape
};

/**
 * @this {TokenizeContext}
 *   Context.
 * @type {Tokenizer}
 */
function tokenizeCharacterEscape(effects, ok, nok) {
  return start;

  /**
   * Start of character escape.
   *
   * ```markdown
   * > | a\*b
   *      ^
   * ```
   *
   * @type {State}
   */
  function start(code) {
    effects.enter("characterEscape");
    effects.enter("escapeMarker");
    effects.consume(code);
    effects.exit("escapeMarker");
    return inside;
  }

  /**
   * After `\`, at punctuation.
   *
   * ```markdown
   * > | a\*b
   *       ^
   * ```
   *
   * @type {State}
   */
  function inside(code) {
    // ASCII punctuation.
    if (asciiPunctuation(code)) {
      effects.enter("characterEscapeValue");
      effects.consume(code);
      effects.exit("characterEscapeValue");
      effects.exit("characterEscape");
      return ok;
    }
    return nok(code);
  }
}/**
 * @import {
 *   Code,
 *   Construct,
 *   State,
 *   TokenizeContext,
 *   Tokenizer
 * } from 'micromark-util-types'
 */

import { decodeNamedCharacterReference } from 'decode-named-character-reference';
import { asciiAlphanumeric, asciiDigit, asciiHexDigit } from 'micromark-util-character';
/** @type {Construct} */
export const characterReference = {
  name: 'characterReference',
  tokenize: tokenizeCharacterReference
};

/**
 * @this {TokenizeContext}
 *   Context.
 * @type {Tokenizer}
 */
function tokenizeCharacterReference(effects, ok, nok) {
  const self = this;
  let size = 0;
  /** @type {number} */
  let max;
  /** @type {(code: Code) => boolean} */
  let test;
  return start;

  /**
   * Start of character reference.
   *
   * ```markdown
   * > | a&amp;b
   *      ^
   * > | a&#123;b
   *      ^
   * > | a&#x9;b
   *      ^
   * ```
   *
   * @type {State}
   */
  function start(code) {
    effects.enter("characterReference");
    effects.enter("characterReferenceMarker");
    effects.consume(code);
    effects.exit("characterReferenceMarker");
    return open;
  }

  /**
   * After `&`, at `#` for numeric references or alphanumeric for named
   * references.
   *
   * ```markdown
   * > | a&amp;b
   *       ^
   * > | a&#123;b
   *       ^
   * > | a&#x9;b
   *       ^
   * ```
   *
   * @type {State}
   */
  function open(code) {
    if (code === 35) {
      effects.enter("characterReferenceMarkerNumeric");
      effects.consume(code);
      effects.exit("characterReferenceMarkerNumeric");
      return numeric;
    }
    effects.enter("characterReferenceValue");
    max = 31;
    test = asciiAlphanumeric;
    return value(code);
  }

  /**
   * After `#`, at `x` for hexadecimals or digit for decimals.
   *
   * ```markdown
   * > | a&#123;b
   *        ^
   * > | a&#x9;b
   *        ^
   * ```
   *
   * @type {State}
   */
  function numeric(code) {
    if (code === 88 || code === 120) {
      effects.enter("characterReferenceMarkerHexadecimal");
      effects.consume(code);
      effects.exit("characterReferenceMarkerHexadecimal");
      effects.enter("characterReferenceValue");
      max = 6;
      test = asciiHexDigit;
      return value;
    }
    effects.enter("characterReferenceValue");
    max = 7;
    test = asciiDigit;
    return value(code);
  }

  /**
   * After markers (`&#x`, `&#`, or `&`), in value, before `;`.
   *
   * The character reference kind defines what and how many characters are
   * allowed.
   *
   * ```markdown
   * > | a&amp;b
   *       ^^^
   * > | a&#123;b
   *        ^^^
   * > | a&#x9;b
   *         ^
   * ```
   *
   * @type {State}
   */
  function value(code) {
    if (code === 59 && size) {
      const token = effects.exit("characterReferenceValue");
      if (test === asciiAlphanumeric && !decodeNamedCharacterReference(self.sliceSerialize(token))) {
        return nok(code);
      }

      // To do: `markdown-rs` uses a different name:
      // `CharacterReferenceMarkerSemi`.
      effects.enter("characterReferenceMarker");
      effects.consume(code);
      effects.exit("characterReferenceMarker");
      effects.exit("characterReference");
      return ok;
    }
    if (test(code) && size++ < max) {
      effects.consume(code);
      return value;
    }
    return nok(code);
  }
}/**
 * @import {
 *   Code,
 *   Construct,
 *   State,
 *   TokenizeContext,
 *   Tokenizer
 * } from 'micromark-util-types'
 */

import { factorySpace } from 'micromark-factory-space';
import { markdownLineEnding, markdownSpace } from 'micromark-util-character';
/** @type {Construct} */
const nonLazyContinuation = {
  partial: true,
  tokenize: tokenizeNonLazyContinuation
};

/** @type {Construct} */
export const codeFenced = {
  concrete: true,
  name: 'codeFenced',
  tokenize: tokenizeCodeFenced
};

/**
 * @this {TokenizeContext}
 *   Context.
 * @type {Tokenizer}
 */
function tokenizeCodeFenced(effects, ok, nok) {
  const self = this;
  /** @type {Construct} */
  const closeStart = {
    partial: true,
    tokenize: tokenizeCloseStart
  };
  let initialPrefix = 0;
  let sizeOpen = 0;
  /** @type {NonNullable<Code>} */
  let marker;
  return start;

  /**
   * Start of code.
   *
   * ```markdown
   * > | ~~~js
   *     ^
   *   | alert(1)
   *   | ~~~
   * ```
   *
   * @type {State}
   */
  function start(code) {
    // To do: parse whitespace like `markdown-rs`.
    return beforeSequenceOpen(code);
  }

  /**
   * In opening fence, after prefix, at sequence.
   *
   * ```markdown
   * > | ~~~js
   *     ^
   *   | alert(1)
   *   | ~~~
   * ```
   *
   * @type {State}
   */
  function beforeSequenceOpen(code) {
    const tail = self.events[self.events.length - 1];
    initialPrefix = tail && tail[1].type === "linePrefix" ? tail[2].sliceSerialize(tail[1], true).length : 0;
    marker = code;
    effects.enter("codeFenced");
    effects.enter("codeFencedFence");
    effects.enter("codeFencedFenceSequence");
    return sequenceOpen(code);
  }

  /**
   * In opening fence sequence.
   *
   * ```markdown
   * > | ~~~js
   *      ^
   *   | alert(1)
   *   | ~~~
   * ```
   *
   * @type {State}
   */
  function sequenceOpen(code) {
    if (code === marker) {
      sizeOpen++;
      effects.consume(code);
      return sequenceOpen;
    }
    if (sizeOpen < 3) {
      return nok(code);
    }
    effects.exit("codeFencedFenceSequence");
    return markdownSpace(code) ? factorySpace(effects, infoBefore, "whitespace")(code) : infoBefore(code);
  }

  /**
   * In opening fence, after the sequence (and optional whitespace), before info.
   *
   * ```markdown
   * > | ~~~js
   *        ^
   *   | alert(1)
   *   | ~~~
   * ```
   *
   * @type {State}
   */
  function infoBefore(code) {
    if (code === null || markdownLineEnding(code)) {
      effects.exit("codeFencedFence");
      return self.interrupt ? ok(code) : effects.check(nonLazyContinuation, atNonLazyBreak, after)(code);
    }
    effects.enter("codeFencedFenceInfo");
    effects.enter("chunkString", {
      contentType: "string"
    });
    return info(code);
  }

  /**
   * In info.
   *
   * ```markdown
   * > | ~~~js
   *        ^
   *   | alert(1)
   *   | ~~~
   * ```
   *
   * @type {State}
   */
  function info(code) {
    if (code === null || markdownLineEnding(code)) {
      effects.exit("chunkString");
      effects.exit("codeFencedFenceInfo");
      return infoBefore(code);
    }
    if (markdownSpace(code)) {
      effects.exit("chunkString");
      effects.exit("codeFencedFenceInfo");
      return factorySpace(effects, metaBefore, "whitespace")(code);
    }
    if (code === 96 && code === marker) {
      return nok(code);
    }
    effects.consume(code);
    return info;
  }

  /**
   * In opening fence, after info and whitespace, before meta.
   *
   * ```markdown
   * > | ~~~js eval
   *           ^
   *   | alert(1)
   *   | ~~~
   * ```
   *
   * @type {State}
   */
  function metaBefore(code) {
    if (code === null || markdownLineEnding(code)) {
      return infoBefore(code);
    }
    effects.enter("codeFencedFenceMeta");
    effects.enter("chunkString", {
      contentType: "string"
    });
    return meta(code);
  }

  /**
   * In meta.
   *
   * ```markdown
   * > | ~~~js eval
   *           ^
   *   | alert(1)
   *   | ~~~
   * ```
   *
   * @type {State}
   */
  function meta(code) {
    if (code === null || markdownLineEnding(code)) {
      effects.exit("chunkString");
      effects.exit("codeFencedFenceMeta");
      return infoBefore(code);
    }
    if (code === 96 && code === marker) {
      return nok(code);
    }
    effects.consume(code);
    return meta;
  }

  /**
   * At eol/eof in code, before a non-lazy closing fence or content.
   *
   * ```markdown
   * > | ~~~js
   *          ^
   * > | alert(1)
   *             ^
   *   | ~~~
   * ```
   *
   * @type {State}
   */
  function atNonLazyBreak(code) {
    return effects.attempt(closeStart, after, contentBefore)(code);
  }

  /**
   * Before code content, not a closing fence, at eol.
   *
   * ```markdown
   *   | ~~~js
   * > | alert(1)
   *             ^
   *   | ~~~
   * ```
   *
   * @type {State}
   */
  function contentBefore(code) {
    effects.enter("lineEnding");
    effects.consume(code);
    effects.exit("lineEnding");
    return contentStart;
  }

  /**
   * Before code content, not a closing fence.
   *
   * ```markdown
   *   | ~~~js
   * > | alert(1)
   *     ^
   *   | ~~~
   * ```
   *
   * @type {State}
   */
  function contentStart(code) {
    return initialPrefix > 0 && markdownSpace(code) ? factorySpace(effects, beforeContentChunk, "linePrefix", initialPrefix + 1)(code) : beforeContentChunk(code);
  }

  /**
   * Before code content, after optional prefix.
   *
   * ```markdown
   *   | ~~~js
   * > | alert(1)
   *     ^
   *   | ~~~
   * ```
   *
   * @type {State}
   */
  function beforeContentChunk(code) {
    if (code === null || markdownLineEnding(code)) {
      return effects.check(nonLazyContinuation, atNonLazyBreak, after)(code);
    }
    effects.enter("codeFlowValue");
    return contentChunk(code);
  }

  /**
   * In code content.
   *
   * ```markdown
   *   | ~~~js
   * > | alert(1)
   *     ^^^^^^^^
   *   | ~~~
   * ```
   *
   * @type {State}
   */
  function contentChunk(code) {
    if (code === null || markdownLineEnding(code)) {
      effects.exit("codeFlowValue");
      return beforeContentChunk(code);
    }
    effects.consume(code);
    return contentChunk;
  }

  /**
   * After code.
   *
   * ```markdown
   *   | ~~~js
   *   | alert(1)
   * > | ~~~
   *        ^
   * ```
   *
   * @type {State}
   */
  function after(code) {
    effects.exit("codeFenced");
    return ok(code);
  }

  /**
   * @this {TokenizeContext}
   *   Context.
   * @type {Tokenizer}
   */
  function tokenizeCloseStart(effects, ok, nok) {
    let size = 0;
    return startBefore;

    /**
     *
     *
     * @type {State}
     */
    function startBefore(code) {
      effects.enter("lineEnding");
      effects.consume(code);
      effects.exit("lineEnding");
      return start;
    }

    /**
     * Before closing fence, at optional whitespace.
     *
     * ```markdown
     *   | ~~~js
     *   | alert(1)
     * > | ~~~
     *     ^
     * ```
     *
     * @type {State}
     */
    function start(code) {
      // Always populated by defaults.

      // To do: `enter` here or in next state?
      effects.enter("codeFencedFence");
      return markdownSpace(code) ? factorySpace(effects, beforeSequenceClose, "linePrefix", self.parser.constructs.disable.null.includes('codeIndented') ? undefined : 4)(code) : beforeSequenceClose(code);
    }

    /**
     * In closing fence, after optional whitespace, at sequence.
     *
     * ```markdown
     *   | ~~~js
     *   | alert(1)
     * > | ~~~
     *     ^
     * ```
     *
     * @type {State}
     */
    function beforeSequenceClose(code) {
      if (code === marker) {
        effects.enter("codeFencedFenceSequence");
        return sequenceClose(code);
      }
      return nok(code);
    }

    /**
     * In closing fence sequence.
     *
     * ```markdown
     *   | ~~~js
     *   | alert(1)
     * > | ~~~
     *     ^
     * ```
     *
     * @type {State}
     */
    function sequenceClose(code) {
      if (code === marker) {
        size++;
        effects.consume(code);
        return sequenceClose;
      }
      if (size >= sizeOpen) {
        effects.exit("codeFencedFenceSequence");
        return markdownSpace(code) ? factorySpace(effects, sequenceCloseAfter, "whitespace")(code) : sequenceCloseAfter(code);
      }
      return nok(code);
    }

    /**
     * After closing fence sequence, after optional whitespace.
     *
     * ```markdown
     *   | ~~~js
     *   | alert(1)
     * > | ~~~
     *        ^
     * ```
     *
     * @type {State}
     */
    function sequenceCloseAfter(code) {
      if (code === null || markdownLineEnding(code)) {
        effects.exit("codeFencedFence");
        return ok(code);
      }
      return nok(code);
    }
  }
}

/**
 * @this {TokenizeContext}
 *   Context.
 * @type {Tokenizer}
 */
function tokenizeNonLazyContinuation(effects, ok, nok) {
  const self = this;
  return start;

  /**
   *
   *
   * @type {State}
   */
  function start(code) {
    if (code === null) {
      return nok(code);
    }
    effects.enter("lineEnding");
    effects.consume(code);
    effects.exit("lineEnding");
    return lineStart;
  }

  /**
   *
   *
   * @type {State}
   */
  function lineStart(code) {
    return self.parser.lazy[self.now().line] ? nok(code) : ok(code);
  }
}/**
 * @import {
 *   Construct,
 *   State,
 *   TokenizeContext,
 *   Tokenizer
 * } from 'micromark-util-types'
 */

import { factorySpace } from 'micromark-factory-space';
import { markdownLineEnding, markdownSpace } from 'micromark-util-character';
/** @type {Construct} */
export const codeIndented = {
  name: 'codeIndented',
  tokenize: tokenizeCodeIndented
};

/** @type {Construct} */
const furtherStart = {
  partial: true,
  tokenize: tokenizeFurtherStart
};

/**
 * @this {TokenizeContext}
 *   Context.
 * @type {Tokenizer}
 */
function tokenizeCodeIndented(effects, ok, nok) {
  const self = this;
  return start;

  /**
   * Start of code (indented).
   *
   * > **Parsing note**: it is not needed to check if this first line is a
   * > filled line (that it has a non-whitespace character), because blank lines
   * > are parsed already, so we never run into that.
   *
   * ```markdown
   * > |     aaa
   *     ^
   * ```
   *
   * @type {State}
   */
  function start(code) {
    // To do: manually check if interrupting like `markdown-rs`.

    effects.enter("codeIndented");
    // To do: use an improved `space_or_tab` function like `markdown-rs`,
    // so that we can drop the next state.
    return factorySpace(effects, afterPrefix, "linePrefix", 4 + 1)(code);
  }

  /**
   * At start, after 1 or 4 spaces.
   *
   * ```markdown
   * > |     aaa
   *         ^
   * ```
   *
   * @type {State}
   */
  function afterPrefix(code) {
    const tail = self.events[self.events.length - 1];
    return tail && tail[1].type === "linePrefix" && tail[2].sliceSerialize(tail[1], true).length >= 4 ? atBreak(code) : nok(code);
  }

  /**
   * At a break.
   *
   * ```markdown
   * > |     aaa
   *         ^  ^
   * ```
   *
   * @type {State}
   */
  function atBreak(code) {
    if (code === null) {
      return after(code);
    }
    if (markdownLineEnding(code)) {
      return effects.attempt(furtherStart, atBreak, after)(code);
    }
    effects.enter("codeFlowValue");
    return inside(code);
  }

  /**
   * In code content.
   *
   * ```markdown
   * > |     aaa
   *         ^^^^
   * ```
   *
   * @type {State}
   */
  function inside(code) {
    if (code === null || markdownLineEnding(code)) {
      effects.exit("codeFlowValue");
      return atBreak(code);
    }
    effects.consume(code);
    return inside;
  }

  /** @type {State} */
  function after(code) {
    effects.exit("codeIndented");
    // To do: allow interrupting like `markdown-rs`.
    // Feel free to interrupt.
    // tokenizer.interrupt = false
    return ok(code);
  }
}

/**
 * @this {TokenizeContext}
 *   Context.
 * @type {Tokenizer}
 */
function tokenizeFurtherStart(effects, ok, nok) {
  const self = this;
  return furtherStart;

  /**
   * At eol, trying to parse another indent.
   *
   * ```markdown
   * > |     aaa
   *            ^
   *   |     bbb
   * ```
   *
   * @type {State}
   */
  function furtherStart(code) {
    // To do: improve `lazy` / `pierce` handling.
    // If this is a lazy line, it can’t be code.
    if (self.parser.lazy[self.now().line]) {
      return nok(code);
    }
    if (markdownLineEnding(code)) {
      effects.enter("lineEnding");
      effects.consume(code);
      effects.exit("lineEnding");
      return furtherStart;
    }

    // To do: the code here in `micromark-js` is a bit different from
    // `markdown-rs` because there it can attempt spaces.
    // We can’t yet.
    //
    // To do: use an improved `space_or_tab` function like `markdown-rs`,
    // so that we can drop the next state.
    return factorySpace(effects, afterPrefix, "linePrefix", 4 + 1)(code);
  }

  /**
   * At start, after 1 or 4 spaces.
   *
   * ```markdown
   * > |     aaa
   *         ^
   * ```
   *
   * @type {State}
   */
  function afterPrefix(code) {
    const tail = self.events[self.events.length - 1];
    return tail && tail[1].type === "linePrefix" && tail[2].sliceSerialize(tail[1], true).length >= 4 ? ok(code) : markdownLineEnding(code) ? furtherStart(code) : nok(code);
  }
}/**
 * @import {
 *   Construct,
 *   Previous,
 *   Resolver,
 *   State,
 *   TokenizeContext,
 *   Tokenizer,
 *   Token
 * } from 'micromark-util-types'
 */

import { markdownLineEnding } from 'micromark-util-character';
/** @type {Construct} */
export const codeText = {
  name: 'codeText',
  previous,
  resolve: resolveCodeText,
  tokenize: tokenizeCodeText
};

// To do: next major: don’t resolve, like `markdown-rs`.
/** @type {Resolver} */
function resolveCodeText(events) {
  let tailExitIndex = events.length - 4;
  let headEnterIndex = 3;
  /** @type {number} */
  let index;
  /** @type {number | undefined} */
  let enter;

  // If we start and end with an EOL or a space.
  if ((events[headEnterIndex][1].type === "lineEnding" || events[headEnterIndex][1].type === 'space') && (events[tailExitIndex][1].type === "lineEnding" || events[tailExitIndex][1].type === 'space')) {
    index = headEnterIndex;

    // And we have data.
    while (++index < tailExitIndex) {
      if (events[index][1].type === "codeTextData") {
        // Then we have padding.
        events[headEnterIndex][1].type = "codeTextPadding";
        events[tailExitIndex][1].type = "codeTextPadding";
        headEnterIndex += 2;
        tailExitIndex -= 2;
        break;
      }
    }
  }

  // Merge adjacent spaces and data.
  index = headEnterIndex - 1;
  tailExitIndex++;
  while (++index <= tailExitIndex) {
    if (enter === undefined) {
      if (index !== tailExitIndex && events[index][1].type !== "lineEnding") {
        enter = index;
      }
    } else if (index === tailExitIndex || events[index][1].type === "lineEnding") {
      events[enter][1].type = "codeTextData";
      if (index !== enter + 2) {
        events[enter][1].end = events[index - 1][1].end;
        events.splice(enter + 2, index - enter - 2);
        tailExitIndex -= index - enter - 2;
        index = enter + 2;
      }
      enter = undefined;
    }
  }
  return events;
}

/**
 * @this {TokenizeContext}
 *   Context.
 * @type {Previous}
 */
function previous(code) {
  // If there is a previous code, there will always be a tail.
  return code !== 96 || this.events[this.events.length - 1][1].type === "characterEscape";
}

/**
 * @this {TokenizeContext}
 *   Context.
 * @type {Tokenizer}
 */
function tokenizeCodeText(effects, ok, nok) {
  const self = this;
  let sizeOpen = 0;
  /** @type {number} */
  let size;
  /** @type {Token} */
  let token;
  return start;

  /**
   * Start of code (text).
   *
   * ```markdown
   * > | `a`
   *     ^
   * > | \`a`
   *      ^
   * ```
   *
   * @type {State}
   */
  function start(code) {
    effects.enter("codeText");
    effects.enter("codeTextSequence");
    return sequenceOpen(code);
  }

  /**
   * In opening sequence.
   *
   * ```markdown
   * > | `a`
   *     ^
   * ```
   *
   * @type {State}
   */
  function sequenceOpen(code) {
    if (code === 96) {
      effects.consume(code);
      sizeOpen++;
      return sequenceOpen;
    }
    effects.exit("codeTextSequence");
    return between(code);
  }

  /**
   * Between something and something else.
   *
   * ```markdown
   * > | `a`
   *      ^^
   * ```
   *
   * @type {State}
   */
  function between(code) {
    // EOF.
    if (code === null) {
      return nok(code);
    }

    // To do: next major: don’t do spaces in resolve, but when compiling,
    // like `markdown-rs`.
    // Tabs don’t work, and virtual spaces don’t make sense.
    if (code === 32) {
      effects.enter('space');
      effects.consume(code);
      effects.exit('space');
      return between;
    }

    // Closing fence? Could also be data.
    if (code === 96) {
      token = effects.enter("codeTextSequence");
      size = 0;
      return sequenceClose(code);
    }
    if (markdownLineEnding(code)) {
      effects.enter("lineEnding");
      effects.consume(code);
      effects.exit("lineEnding");
      return between;
    }

    // Data.
    effects.enter("codeTextData");
    return data(code);
  }

  /**
   * In data.
   *
   * ```markdown
   * > | `a`
   *      ^
   * ```
   *
   * @type {State}
   */
  function data(code) {
    if (code === null || code === 32 || code === 96 || markdownLineEnding(code)) {
      effects.exit("codeTextData");
      return between(code);
    }
    effects.consume(code);
    return data;
  }

  /**
   * In closing sequence.
   *
   * ```markdown
   * > | `a`
   *       ^
   * ```
   *
   * @type {State}
   */
  function sequenceClose(code) {
    // More.
    if (code === 96) {
      effects.consume(code);
      size++;
      return sequenceClose;
    }

    // Done!
    if (size === sizeOpen) {
      effects.exit("codeTextSequence");
      effects.exit("codeText");
      return ok(code);
    }

    // More or less accents: mark as data.
    token.type = "codeTextData";
    return data(code);
  }
}/**
 * @import {
 *   Construct,
 *   Resolver,
 *   State,
 *   TokenizeContext,
 *   Tokenizer,
 *   Token
 * } from 'micromark-util-types'
 */

import { factorySpace } from 'micromark-factory-space';
import { markdownLineEnding } from 'micromark-util-character';
import { subtokenize } from 'micromark-util-subtokenize';
/**
 * No name because it must not be turned off.
 * @type {Construct}
 */
export const content = {
  resolve: resolveContent,
  tokenize: tokenizeContent
};

/** @type {Construct} */
const continuationConstruct = {
  partial: true,
  tokenize: tokenizeContinuation
};

/**
 * Content is transparent: it’s parsed right now. That way, definitions are also
 * parsed right now: before text in paragraphs (specifically, media) are parsed.
 *
 * @type {Resolver}
 */
function resolveContent(events) {
  subtokenize(events);
  return events;
}

/**
 * @this {TokenizeContext}
 *   Context.
 * @type {Tokenizer}
 */
function tokenizeContent(effects, ok) {
  /** @type {Token | undefined} */
  let previous;
  return chunkStart;

  /**
   * Before a content chunk.
   *
   * ```markdown
   * > | abc
   *     ^
   * ```
   *
   * @type {State}
   */
  function chunkStart(code) {
    effects.enter("content");
    previous = effects.enter("chunkContent", {
      contentType: "content"
    });
    return chunkInside(code);
  }

  /**
   * In a content chunk.
   *
   * ```markdown
   * > | abc
   *     ^^^
   * ```
   *
   * @type {State}
   */
  function chunkInside(code) {
    if (code === null) {
      return contentEnd(code);
    }

    // To do: in `markdown-rs`, each line is parsed on its own, and everything
    // is stitched together resolving.
    if (markdownLineEnding(code)) {
      return effects.check(continuationConstruct, contentContinue, contentEnd)(code);
    }

    // Data.
    effects.consume(code);
    return chunkInside;
  }

  /**
   *
   *
   * @type {State}
   */
  function contentEnd(code) {
    effects.exit("chunkContent");
    effects.exit("content");
    return ok(code);
  }

  /**
   *
   *
   * @type {State}
   */
  function contentContinue(code) {
    effects.consume(code);
    effects.exit("chunkContent");
    previous.next = effects.enter("chunkContent", {
      contentType: "content",
      previous
    });
    previous = previous.next;
    return chunkInside;
  }
}

/**
 * @this {TokenizeContext}
 *   Context.
 * @type {Tokenizer}
 */
function tokenizeContinuation(effects, ok, nok) {
  const self = this;
  return startLookahead;

  /**
   *
   *
   * @type {State}
   */
  function startLookahead(code) {
    effects.exit("chunkContent");
    effects.enter("lineEnding");
    effects.consume(code);
    effects.exit("lineEnding");
    return factorySpace(effects, prefixed, "linePrefix");
  }

  /**
   *
   *
   * @type {State}
   */
  function prefixed(code) {
    if (code === null || markdownLineEnding(code)) {
      return nok(code);
    }

    // Always populated by defaults.

    const tail = self.events[self.events.length - 1];
    if (!self.parser.constructs.disable.null.includes('codeIndented') && tail && tail[1].type === "linePrefix" && tail[2].sliceSerialize(tail[1], true).length >= 4) {
      return ok(code);
    }
    return effects.interrupt(self.parser.constructs.flow, nok, ok)(code);
  }
}/**
 * @import {
 *   Construct,
 *   State,
 *   TokenizeContext,
 *   Tokenizer
 * } from 'micromark-util-types'
 */

import { factoryDestination } from 'micromark-factory-destination';
import { factoryLabel } from 'micromark-factory-label';
import { factorySpace } from 'micromark-factory-space';
import { factoryTitle } from 'micromark-factory-title';
import { factoryWhitespace } from 'micromark-factory-whitespace';
import { markdownLineEndingOrSpace, markdownLineEnding, markdownSpace } from 'micromark-util-character';
import { normalizeIdentifier } from 'micromark-util-normalize-identifier';
/** @type {Construct} */
export const definition = {
  name: 'definition',
  tokenize: tokenizeDefinition
};

/** @type {Construct} */
const titleBefore = {
  partial: true,
  tokenize: tokenizeTitleBefore
};

/**
 * @this {TokenizeContext}
 *   Context.
 * @type {Tokenizer}
 */
function tokenizeDefinition(effects, ok, nok) {
  const self = this;
  /** @type {string} */
  let identifier;
  return start;

  /**
   * At start of a definition.
   *
   * ```markdown
   * > | [a]: b "c"
   *     ^
   * ```
   *
   * @type {State}
   */
  function start(code) {
    // Do not interrupt paragraphs (but do follow definitions).
    // To do: do `interrupt` the way `markdown-rs` does.
    // To do: parse whitespace the way `markdown-rs` does.
    effects.enter("definition");
    return before(code);
  }

  /**
   * After optional whitespace, at `[`.
   *
   * ```markdown
   * > | [a]: b "c"
   *     ^
   * ```
   *
   * @type {State}
   */
  function before(code) {
    // To do: parse whitespace the way `markdown-rs` does.

    return factoryLabel.call(self, effects, labelAfter,
    // Note: we don’t need to reset the way `markdown-rs` does.
    nok, "definitionLabel", "definitionLabelMarker", "definitionLabelString")(code);
  }

  /**
   * After label.
   *
   * ```markdown
   * > | [a]: b "c"
   *        ^
   * ```
   *
   * @type {State}
   */
  function labelAfter(code) {
    identifier = normalizeIdentifier(self.sliceSerialize(self.events[self.events.length - 1][1]).slice(1, -1));
    if (code === 58) {
      effects.enter("definitionMarker");
      effects.consume(code);
      effects.exit("definitionMarker");
      return markerAfter;
    }
    return nok(code);
  }

  /**
   * After marker.
   *
   * ```markdown
   * > | [a]: b "c"
   *         ^
   * ```
   *
   * @type {State}
   */
  function markerAfter(code) {
    // Note: whitespace is optional.
    return markdownLineEndingOrSpace(code) ? factoryWhitespace(effects, destinationBefore)(code) : destinationBefore(code);
  }

  /**
   * Before destination.
   *
   * ```markdown
   * > | [a]: b "c"
   *          ^
   * ```
   *
   * @type {State}
   */
  function destinationBefore(code) {
    return factoryDestination(effects, destinationAfter,
    // Note: we don’t need to reset the way `markdown-rs` does.
    nok, "definitionDestination", "definitionDestinationLiteral", "definitionDestinationLiteralMarker", "definitionDestinationRaw", "definitionDestinationString")(code);
  }

  /**
   * After destination.
   *
   * ```markdown
   * > | [a]: b "c"
   *           ^
   * ```
   *
   * @type {State}
   */
  function destinationAfter(code) {
    return effects.attempt(titleBefore, after, after)(code);
  }

  /**
   * After definition.
   *
   * ```markdown
   * > | [a]: b
   *           ^
   * > | [a]: b "c"
   *               ^
   * ```
   *
   * @type {State}
   */
  function after(code) {
    return markdownSpace(code) ? factorySpace(effects, afterWhitespace, "whitespace")(code) : afterWhitespace(code);
  }

  /**
   * After definition, after optional whitespace.
   *
   * ```markdown
   * > | [a]: b
   *           ^
   * > | [a]: b "c"
   *               ^
   * ```
   *
   * @type {State}
   */
  function afterWhitespace(code) {
    if (code === null || markdownLineEnding(code)) {
      effects.exit("definition");

      // Note: we don’t care about uniqueness.
      // It’s likely that that doesn’t happen very frequently.
      // It is more likely that it wastes precious time.
      self.parser.defined.push(identifier);

      // To do: `markdown-rs` interrupt.
      // // You’d be interrupting.
      // tokenizer.interrupt = true
      return ok(code);
    }
    return nok(code);
  }
}

/**
 * @this {TokenizeContext}
 *   Context.
 * @type {Tokenizer}
 */
function tokenizeTitleBefore(effects, ok, nok) {
  return titleBefore;

  /**
   * After destination, at whitespace.
   *
   * ```markdown
   * > | [a]: b
   *           ^
   * > | [a]: b "c"
   *           ^
   * ```
   *
   * @type {State}
   */
  function titleBefore(code) {
    return markdownLineEndingOrSpace(code) ? factoryWhitespace(effects, beforeMarker)(code) : nok(code);
  }

  /**
   * At title.
   *
   * ```markdown
   *   | [a]: b
   * > | "c"
   *     ^
   * ```
   *
   * @type {State}
   */
  function beforeMarker(code) {
    return factoryTitle(effects, titleAfter, nok, "definitionTitle", "definitionTitleMarker", "definitionTitleString")(code);
  }

  /**
   * After title.
   *
   * ```markdown
   * > | [a]: b "c"
   *               ^
   * ```
   *
   * @type {State}
   */
  function titleAfter(code) {
    return markdownSpace(code) ? factorySpace(effects, titleAfterOptionalWhitespace, "whitespace")(code) : titleAfterOptionalWhitespace(code);
  }

  /**
   * After title, after optional whitespace.
   *
   * ```markdown
   * > | [a]: b "c"
   *               ^
   * ```
   *
   * @type {State}
   */
  function titleAfterOptionalWhitespace(code) {
    return code === null || markdownLineEnding(code) ? ok(code) : nok(code);
  }
}/**
 * @import {
 *   Construct,
 *   State,
 *   TokenizeContext,
 *   Tokenizer
 * } from 'micromark-util-types'
 */

import { markdownLineEnding } from 'micromark-util-character';
/** @type {Construct} */
export const hardBreakEscape = {
  name: 'hardBreakEscape',
  tokenize: tokenizeHardBreakEscape
};

/**
 * @this {TokenizeContext}
 *   Context.
 * @type {Tokenizer}
 */
function tokenizeHardBreakEscape(effects, ok, nok) {
  return start;

  /**
   * Start of a hard break (escape).
   *
   * ```markdown
   * > | a\
   *      ^
   *   | b
   * ```
   *
   * @type {State}
   */
  function start(code) {
    effects.enter("hardBreakEscape");
    effects.consume(code);
    return after;
  }

  /**
   * After `\`, at eol.
   *
   * ```markdown
   * > | a\
   *       ^
   *   | b
   * ```
   *
   *  @type {State}
   */
  function after(code) {
    if (markdownLineEnding(code)) {
      effects.exit("hardBreakEscape");
      return ok(code);
    }
    return nok(code);
  }
}/**
 * @import {
 *   Construct,
 *   Resolver,
 *   State,
 *   TokenizeContext,
 *   Tokenizer,
 *   Token
 * } from 'micromark-util-types'
 */

import { factorySpace } from 'micromark-factory-space';
import { markdownLineEndingOrSpace, markdownLineEnding, markdownSpace } from 'micromark-util-character';
import { splice } from 'micromark-util-chunked';
/** @type {Construct} */
export const headingAtx = {
  name: 'headingAtx',
  resolve: resolveHeadingAtx,
  tokenize: tokenizeHeadingAtx
};

/** @type {Resolver} */
function resolveHeadingAtx(events, context) {
  let contentEnd = events.length - 2;
  let contentStart = 3;
  /** @type {Token} */
  let content;
  /** @type {Token} */
  let text;

  // Prefix whitespace, part of the opening.
  if (events[contentStart][1].type === "whitespace") {
    contentStart += 2;
  }

  // Suffix whitespace, part of the closing.
  if (contentEnd - 2 > contentStart && events[contentEnd][1].type === "whitespace") {
    contentEnd -= 2;
  }
  if (events[contentEnd][1].type === "atxHeadingSequence" && (contentStart === contentEnd - 1 || contentEnd - 4 > contentStart && events[contentEnd - 2][1].type === "whitespace")) {
    contentEnd -= contentStart + 1 === contentEnd ? 2 : 4;
  }
  if (contentEnd > contentStart) {
    content = {
      type: "atxHeadingText",
      start: events[contentStart][1].start,
      end: events[contentEnd][1].end
    };
    text = {
      type: "chunkText",
      start: events[contentStart][1].start,
      end: events[contentEnd][1].end,
      contentType: "text"
    };
    splice(events, contentStart, contentEnd - contentStart + 1, [['enter', content, context], ['enter', text, context], ['exit', text, context], ['exit', content, context]]);
  }
  return events;
}

/**
 * @this {TokenizeContext}
 *   Context.
 * @type {Tokenizer}
 */
function tokenizeHeadingAtx(effects, ok, nok) {
  let size = 0;
  return start;

  /**
   * Start of a heading (atx).
   *
   * ```markdown
   * > | ## aa
   *     ^
   * ```
   *
   * @type {State}
   */
  function start(code) {
    // To do: parse indent like `markdown-rs`.
    effects.enter("atxHeading");
    return before(code);
  }

  /**
   * After optional whitespace, at `#`.
   *
   * ```markdown
   * > | ## aa
   *     ^
   * ```
   *
   * @type {State}
   */
  function before(code) {
    effects.enter("atxHeadingSequence");
    return sequenceOpen(code);
  }

  /**
   * In opening sequence.
   *
   * ```markdown
   * > | ## aa
   *     ^
   * ```
   *
   * @type {State}
   */
  function sequenceOpen(code) {
    if (code === 35 && size++ < 6) {
      effects.consume(code);
      return sequenceOpen;
    }

    // Always at least one `#`.
    if (code === null || markdownLineEndingOrSpace(code)) {
      effects.exit("atxHeadingSequence");
      return atBreak(code);
    }
    return nok(code);
  }

  /**
   * After something, before something else.
   *
   * ```markdown
   * > | ## aa
   *       ^
   * ```
   *
   * @type {State}
   */
  function atBreak(code) {
    if (code === 35) {
      effects.enter("atxHeadingSequence");
      return sequenceFurther(code);
    }
    if (code === null || markdownLineEnding(code)) {
      effects.exit("atxHeading");
      // To do: interrupt like `markdown-rs`.
      // // Feel free to interrupt.
      // tokenizer.interrupt = false
      return ok(code);
    }
    if (markdownSpace(code)) {
      return factorySpace(effects, atBreak, "whitespace")(code);
    }

    // To do: generate `data` tokens, add the `text` token later.
    // Needs edit map, see: `markdown.rs`.
    effects.enter("atxHeadingText");
    return data(code);
  }

  /**
   * In further sequence (after whitespace).
   *
   * Could be normal “visible” hashes in the heading or a final sequence.
   *
   * ```markdown
   * > | ## aa ##
   *           ^
   * ```
   *
   * @type {State}
   */
  function sequenceFurther(code) {
    if (code === 35) {
      effects.consume(code);
      return sequenceFurther;
    }
    effects.exit("atxHeadingSequence");
    return atBreak(code);
  }

  /**
   * In text.
   *
   * ```markdown
   * > | ## aa
   *        ^
   * ```
   *
   * @type {State}
   */
  function data(code) {
    if (code === null || code === 35 || markdownLineEndingOrSpace(code)) {
      effects.exit("atxHeadingText");
      return atBreak(code);
    }
    effects.consume(code);
    return data;
  }
}/**
 * @import {
 *   Code,
 *   Construct,
 *   Resolver,
 *   State,
 *   TokenizeContext,
 *   Tokenizer
 * } from 'micromark-util-types'
 */

import { asciiAlphanumeric, asciiAlpha, markdownLineEndingOrSpace, markdownLineEnding, markdownSpace } from 'micromark-util-character';
import { htmlBlockNames, htmlRawNames } from 'micromark-util-html-tag-name';
import { blankLine } from './blank-line.js';

/** @type {Construct} */
export const htmlFlow = {
  concrete: true,
  name: 'htmlFlow',
  resolveTo: resolveToHtmlFlow,
  tokenize: tokenizeHtmlFlow
};

/** @type {Construct} */
const blankLineBefore = {
  partial: true,
  tokenize: tokenizeBlankLineBefore
};
const nonLazyContinuationStart = {
  partial: true,
  tokenize: tokenizeNonLazyContinuationStart
};

/** @type {Resolver} */
function resolveToHtmlFlow(events) {
  let index = events.length;
  while (index--) {
    if (events[index][0] === 'enter' && events[index][1].type === "htmlFlow") {
      break;
    }
  }
  if (index > 1 && events[index - 2][1].type === "linePrefix") {
    // Add the prefix start to the HTML token.
    events[index][1].start = events[index - 2][1].start;
    // Add the prefix start to the HTML line token.
    events[index + 1][1].start = events[index - 2][1].start;
    // Remove the line prefix.
    events.splice(index - 2, 2);
  }
  return events;
}

/**
 * @this {TokenizeContext}
 *   Context.
 * @type {Tokenizer}
 */
function tokenizeHtmlFlow(effects, ok, nok) {
  const self = this;
  /** @type {number} */
  let marker;
  /** @type {boolean} */
  let closingTag;
  /** @type {string} */
  let buffer;
  /** @type {number} */
  let index;
  /** @type {Code} */
  let markerB;
  return start;

  /**
   * Start of HTML (flow).
   *
   * ```markdown
   * > | <x />
   *     ^
   * ```
   *
   * @type {State}
   */
  function start(code) {
    // To do: parse indent like `markdown-rs`.
    return before(code);
  }

  /**
   * At `<`, after optional whitespace.
   *
   * ```markdown
   * > | <x />
   *     ^
   * ```
   *
   * @type {State}
   */
  function before(code) {
    effects.enter("htmlFlow");
    effects.enter("htmlFlowData");
    effects.consume(code);
    return open;
  }

  /**
   * After `<`, at tag name or other stuff.
   *
   * ```markdown
   * > | <x />
   *      ^
   * > | <!doctype>
   *      ^
   * > | <!--xxx-->
   *      ^
   * ```
   *
   * @type {State}
   */
  function open(code) {
    if (code === 33) {
      effects.consume(code);
      return declarationOpen;
    }
    if (code === 47) {
      effects.consume(code);
      closingTag = true;
      return tagCloseStart;
    }
    if (code === 63) {
      effects.consume(code);
      marker = 3;
      // To do:
      // tokenizer.concrete = true
      // To do: use `markdown-rs` style interrupt.
      // While we’re in an instruction instead of a declaration, we’re on a `?`
      // right now, so we do need to search for `>`, similar to declarations.
      return self.interrupt ? ok : continuationDeclarationInside;
    }

    // ASCII alphabetical.
    if (asciiAlpha(code)) {
      // Always the case.
      effects.consume(code);
      buffer = String.fromCharCode(code);
      return tagName;
    }
    return nok(code);
  }

  /**
   * After `<!`, at declaration, comment, or CDATA.
   *
   * ```markdown
   * > | <!doctype>
   *       ^
   * > | <!--xxx-->
   *       ^
   * > | <![CDATA[>&<]]>
   *       ^
   * ```
   *
   * @type {State}
   */
  function declarationOpen(code) {
    if (code === 45) {
      effects.consume(code);
      marker = 2;
      return commentOpenInside;
    }
    if (code === 91) {
      effects.consume(code);
      marker = 5;
      index = 0;
      return cdataOpenInside;
    }

    // ASCII alphabetical.
    if (asciiAlpha(code)) {
      effects.consume(code);
      marker = 4;
      // // Do not form containers.
      // tokenizer.concrete = true
      return self.interrupt ? ok : continuationDeclarationInside;
    }
    return nok(code);
  }

  /**
   * After `<!-`, inside a comment, at another `-`.
   *
   * ```markdown
   * > | <!--xxx-->
   *        ^
   * ```
   *
   * @type {State}
   */
  function commentOpenInside(code) {
    if (code === 45) {
      effects.consume(code);
      // // Do not form containers.
      // tokenizer.concrete = true
      return self.interrupt ? ok : continuationDeclarationInside;
    }
    return nok(code);
  }

  /**
   * After `<![`, inside CDATA, expecting `CDATA[`.
   *
   * ```markdown
   * > | <![CDATA[>&<]]>
   *        ^^^^^^
   * ```
   *
   * @type {State}
   */
  function cdataOpenInside(code) {
    const value = "CDATA[";
    if (code === value.charCodeAt(index++)) {
      effects.consume(code);
      if (index === value.length) {
        // // Do not form containers.
        // tokenizer.concrete = true
        return self.interrupt ? ok : continuation;
      }
      return cdataOpenInside;
    }
    return nok(code);
  }

  /**
   * After `</`, in closing tag, at tag name.
   *
   * ```markdown
   * > | </x>
   *       ^
   * ```
   *
   * @type {State}
   */
  function tagCloseStart(code) {
    if (asciiAlpha(code)) {
      // Always the case.
      effects.consume(code);
      buffer = String.fromCharCode(code);
      return tagName;
    }
    return nok(code);
  }

  /**
   * In tag name.
   *
   * ```markdown
   * > | <ab>
   *      ^^
   * > | </ab>
   *       ^^
   * ```
   *
   * @type {State}
   */
  function tagName(code) {
    if (code === null || code === 47 || code === 62 || markdownLineEndingOrSpace(code)) {
      const slash = code === 47;
      const name = buffer.toLowerCase();
      if (!slash && !closingTag && htmlRawNames.includes(name)) {
        marker = 1;
        // // Do not form containers.
        // tokenizer.concrete = true
        return self.interrupt ? ok(code) : continuation(code);
      }
      if (htmlBlockNames.includes(buffer.toLowerCase())) {
        marker = 6;
        if (slash) {
          effects.consume(code);
          return basicSelfClosing;
        }

        // // Do not form containers.
        // tokenizer.concrete = true
        return self.interrupt ? ok(code) : continuation(code);
      }
      marker = 7;
      // Do not support complete HTML when interrupting.
      return self.interrupt && !self.parser.lazy[self.now().line] ? nok(code) : closingTag ? completeClosingTagAfter(code) : completeAttributeNameBefore(code);
    }

    // ASCII alphanumerical and `-`.
    if (code === 45 || asciiAlphanumeric(code)) {
      effects.consume(code);
      buffer += String.fromCharCode(code);
      return tagName;
    }
    return nok(code);
  }

  /**
   * After closing slash of a basic tag name.
   *
   * ```markdown
   * > | <div/>
   *          ^
   * ```
   *
   * @type {State}
   */
  function basicSelfClosing(code) {
    if (code === 62) {
      effects.consume(code);
      // // Do not form containers.
      // tokenizer.concrete = true
      return self.interrupt ? ok : continuation;
    }
    return nok(code);
  }

  /**
   * After closing slash of a complete tag name.
   *
   * ```markdown
   * > | <x/>
   *        ^
   * ```
   *
   * @type {State}
   */
  function completeClosingTagAfter(code) {
    if (markdownSpace(code)) {
      effects.consume(code);
      return completeClosingTagAfter;
    }
    return completeEnd(code);
  }

  /**
   * At an attribute name.
   *
   * At first, this state is used after a complete tag name, after whitespace,
   * where it expects optional attributes or the end of the tag.
   * It is also reused after attributes, when expecting more optional
   * attributes.
   *
   * ```markdown
   * > | <a />
   *        ^
   * > | <a :b>
   *        ^
   * > | <a _b>
   *        ^
   * > | <a b>
   *        ^
   * > | <a >
   *        ^
   * ```
   *
   * @type {State}
   */
  function completeAttributeNameBefore(code) {
    if (code === 47) {
      effects.consume(code);
      return completeEnd;
    }

    // ASCII alphanumerical and `:` and `_`.
    if (code === 58 || code === 95 || asciiAlpha(code)) {
      effects.consume(code);
      return completeAttributeName;
    }
    if (markdownSpace(code)) {
      effects.consume(code);
      return completeAttributeNameBefore;
    }
    return completeEnd(code);
  }

  /**
   * In attribute name.
   *
   * ```markdown
   * > | <a :b>
   *         ^
   * > | <a _b>
   *         ^
   * > | <a b>
   *         ^
   * ```
   *
   * @type {State}
   */
  function completeAttributeName(code) {
    // ASCII alphanumerical and `-`, `.`, `:`, and `_`.
    if (code === 45 || code === 46 || code === 58 || code === 95 || asciiAlphanumeric(code)) {
      effects.consume(code);
      return completeAttributeName;
    }
    return completeAttributeNameAfter(code);
  }

  /**
   * After attribute name, at an optional initializer, the end of the tag, or
   * whitespace.
   *
   * ```markdown
   * > | <a b>
   *         ^
   * > | <a b=c>
   *         ^
   * ```
   *
   * @type {State}
   */
  function completeAttributeNameAfter(code) {
    if (code === 61) {
      effects.consume(code);
      return completeAttributeValueBefore;
    }
    if (markdownSpace(code)) {
      effects.consume(code);
      return completeAttributeNameAfter;
    }
    return completeAttributeNameBefore(code);
  }

  /**
   * Before unquoted, double quoted, or single quoted attribute value, allowing
   * whitespace.
   *
   * ```markdown
   * > | <a b=c>
   *          ^
   * > | <a b="c">
   *          ^
   * ```
   *
   * @type {State}
   */
  function completeAttributeValueBefore(code) {
    if (code === null || code === 60 || code === 61 || code === 62 || code === 96) {
      return nok(code);
    }
    if (code === 34 || code === 39) {
      effects.consume(code);
      markerB = code;
      return completeAttributeValueQuoted;
    }
    if (markdownSpace(code)) {
      effects.consume(code);
      return completeAttributeValueBefore;
    }
    return completeAttributeValueUnquoted(code);
  }

  /**
   * In double or single quoted attribute value.
   *
   * ```markdown
   * > | <a b="c">
   *           ^
   * > | <a b='c'>
   *           ^
   * ```
   *
   * @type {State}
   */
  function completeAttributeValueQuoted(code) {
    if (code === markerB) {
      effects.consume(code);
      markerB = null;
      return completeAttributeValueQuotedAfter;
    }
    if (code === null || markdownLineEnding(code)) {
      return nok(code);
    }
    effects.consume(code);
    return completeAttributeValueQuoted;
  }

  /**
   * In unquoted attribute value.
   *
   * ```markdown
   * > | <a b=c>
   *          ^
   * ```
   *
   * @type {State}
   */
  function completeAttributeValueUnquoted(code) {
    if (code === null || code === 34 || code === 39 || code === 47 || code === 60 || code === 61 || code === 62 || code === 96 || markdownLineEndingOrSpace(code)) {
      return completeAttributeNameAfter(code);
    }
    effects.consume(code);
    return completeAttributeValueUnquoted;
  }

  /**
   * After double or single quoted attribute value, before whitespace or the
   * end of the tag.
   *
   * ```markdown
   * > | <a b="c">
   *            ^
   * ```
   *
   * @type {State}
   */
  function completeAttributeValueQuotedAfter(code) {
    if (code === 47 || code === 62 || markdownSpace(code)) {
      return completeAttributeNameBefore(code);
    }
    return nok(code);
  }

  /**
   * In certain circumstances of a complete tag where only an `>` is allowed.
   *
   * ```markdown
   * > | <a b="c">
   *             ^
   * ```
   *
   * @type {State}
   */
  function completeEnd(code) {
    if (code === 62) {
      effects.consume(code);
      return completeAfter;
    }
    return nok(code);
  }

  /**
   * After `>` in a complete tag.
   *
   * ```markdown
   * > | <x>
   *        ^
   * ```
   *
   * @type {State}
   */
  function completeAfter(code) {
    if (code === null || markdownLineEnding(code)) {
      // // Do not form containers.
      // tokenizer.concrete = true
      return continuation(code);
    }
    if (markdownSpace(code)) {
      effects.consume(code);
      return completeAfter;
    }
    return nok(code);
  }

  /**
   * In continuation of any HTML kind.
   *
   * ```markdown
   * > | <!--xxx-->
   *          ^
   * ```
   *
   * @type {State}
   */
  function continuation(code) {
    if (code === 45 && marker === 2) {
      effects.consume(code);
      return continuationCommentInside;
    }
    if (code === 60 && marker === 1) {
      effects.consume(code);
      return continuationRawTagOpen;
    }
    if (code === 62 && marker === 4) {
      effects.consume(code);
      return continuationClose;
    }
    if (code === 63 && marker === 3) {
      effects.consume(code);
      return continuationDeclarationInside;
    }
    if (code === 93 && marker === 5) {
      effects.consume(code);
      return continuationCdataInside;
    }
    if (markdownLineEnding(code) && (marker === 6 || marker === 7)) {
      effects.exit("htmlFlowData");
      return effects.check(blankLineBefore, continuationAfter, continuationStart)(code);
    }
    if (code === null || markdownLineEnding(code)) {
      effects.exit("htmlFlowData");
      return continuationStart(code);
    }
    effects.consume(code);
    return continuation;
  }

  /**
   * In continuation, at eol.
   *
   * ```markdown
   * > | <x>
   *        ^
   *   | asd
   * ```
   *
   * @type {State}
   */
  function continuationStart(code) {
    return effects.check(nonLazyContinuationStart, continuationStartNonLazy, continuationAfter)(code);
  }

  /**
   * In continuation, at eol, before non-lazy content.
   *
   * ```markdown
   * > | <x>
   *        ^
   *   | asd
   * ```
   *
   * @type {State}
   */
  function continuationStartNonLazy(code) {
    effects.enter("lineEnding");
    effects.consume(code);
    effects.exit("lineEnding");
    return continuationBefore;
  }

  /**
   * In continuation, before non-lazy content.
   *
   * ```markdown
   *   | <x>
   * > | asd
   *     ^
   * ```
   *
   * @type {State}
   */
  function continuationBefore(code) {
    if (code === null || markdownLineEnding(code)) {
      return continuationStart(code);
    }
    effects.enter("htmlFlowData");
    return continuation(code);
  }

  /**
   * In comment continuation, after one `-`, expecting another.
   *
   * ```markdown
   * > | <!--xxx-->
   *             ^
   * ```
   *
   * @type {State}
   */
  function continuationCommentInside(code) {
    if (code === 45) {
      effects.consume(code);
      return continuationDeclarationInside;
    }
    return continuation(code);
  }

  /**
   * In raw continuation, after `<`, at `/`.
   *
   * ```markdown
   * > | <script>console.log(1)</script>
   *                            ^
   * ```
   *
   * @type {State}
   */
  function continuationRawTagOpen(code) {
    if (code === 47) {
      effects.consume(code);
      buffer = '';
      return continuationRawEndTag;
    }
    return continuation(code);
  }

  /**
   * In raw continuation, after `</`, in a raw tag name.
   *
   * ```markdown
   * > | <script>console.log(1)</script>
   *                             ^^^^^^
   * ```
   *
   * @type {State}
   */
  function continuationRawEndTag(code) {
    if (code === 62) {
      const name = buffer.toLowerCase();
      if (htmlRawNames.includes(name)) {
        effects.consume(code);
        return continuationClose;
      }
      return continuation(code);
    }
    if (asciiAlpha(code) && buffer.length < 8) {
      // Always the case.
      effects.consume(code);
      buffer += String.fromCharCode(code);
      return continuationRawEndTag;
    }
    return continuation(code);
  }

  /**
   * In cdata continuation, after `]`, expecting `]>`.
   *
   * ```markdown
   * > | <![CDATA[>&<]]>
   *                  ^
   * ```
   *
   * @type {State}
   */
  function continuationCdataInside(code) {
    if (code === 93) {
      effects.consume(code);
      return continuationDeclarationInside;
    }
    return continuation(code);
  }

  /**
   * In declaration or instruction continuation, at `>`.
   *
   * ```markdown
   * > | <!-->
   *         ^
   * > | <?>
   *       ^
   * > | <!q>
   *        ^
   * > | <!--ab-->
   *             ^
   * > | <![CDATA[>&<]]>
   *                   ^
   * ```
   *
   * @type {State}
   */
  function continuationDeclarationInside(code) {
    if (code === 62) {
      effects.consume(code);
      return continuationClose;
    }

    // More dashes.
    if (code === 45 && marker === 2) {
      effects.consume(code);
      return continuationDeclarationInside;
    }
    return continuation(code);
  }

  /**
   * In closed continuation: everything we get until the eol/eof is part of it.
   *
   * ```markdown
   * > | <!doctype>
   *               ^
   * ```
   *
   * @type {State}
   */
  function continuationClose(code) {
    if (code === null || markdownLineEnding(code)) {
      effects.exit("htmlFlowData");
      return continuationAfter(code);
    }
    effects.consume(code);
    return continuationClose;
  }

  /**
   * Done.
   *
   * ```markdown
   * > | <!doctype>
   *               ^
   * ```
   *
   * @type {State}
   */
  function continuationAfter(code) {
    effects.exit("htmlFlow");
    // // Feel free to interrupt.
    // tokenizer.interrupt = false
    // // No longer concrete.
    // tokenizer.concrete = false
    return ok(code);
  }
}

/**
 * @this {TokenizeContext}
 *   Context.
 * @type {Tokenizer}
 */
function tokenizeNonLazyContinuationStart(effects, ok, nok) {
  const self = this;
  return start;

  /**
   * At eol, before continuation.
   *
   * ```markdown
   * > | * ```js
   *            ^
   *   | b
   * ```
   *
   * @type {State}
   */
  function start(code) {
    if (markdownLineEnding(code)) {
      effects.enter("lineEnding");
      effects.consume(code);
      effects.exit("lineEnding");
      return after;
    }
    return nok(code);
  }

  /**
   * A continuation.
   *
   * ```markdown
   *   | * ```js
   * > | b
   *     ^
   * ```
   *
   * @type {State}
   */
  function after(code) {
    return self.parser.lazy[self.now().line] ? nok(code) : ok(code);
  }
}

/**
 * @this {TokenizeContext}
 *   Context.
 * @type {Tokenizer}
 */
function tokenizeBlankLineBefore(effects, ok, nok) {
  return start;

  /**
   * Before eol, expecting blank line.
   *
   * ```markdown
   * > | <div>
   *          ^
   *   |
   * ```
   *
   * @type {State}
   */
  function start(code) {
    effects.enter("lineEnding");
    effects.consume(code);
    effects.exit("lineEnding");
    return effects.attempt(blankLine, ok, nok);
  }
}/**
 * @import {
 *   Code,
 *   Construct,
 *   State,
 *   TokenizeContext,
 *   Tokenizer
 * } from 'micromark-util-types'
 */

import { factorySpace } from 'micromark-factory-space';
import { asciiAlphanumeric, asciiAlpha, markdownLineEndingOrSpace, markdownLineEnding, markdownSpace } from 'micromark-util-character';
/** @type {Construct} */
export const htmlText = {
  name: 'htmlText',
  tokenize: tokenizeHtmlText
};

/**
 * @this {TokenizeContext}
 *   Context.
 * @type {Tokenizer}
 */
function tokenizeHtmlText(effects, ok, nok) {
  const self = this;
  /** @type {NonNullable<Code> | undefined} */
  let marker;
  /** @type {number} */
  let index;
  /** @type {State} */
  let returnState;
  return start;

  /**
   * Start of HTML (text).
   *
   * ```markdown
   * > | a <b> c
   *       ^
   * ```
   *
   * @type {State}
   */
  function start(code) {
    effects.enter("htmlText");
    effects.enter("htmlTextData");
    effects.consume(code);
    return open;
  }

  /**
   * After `<`, at tag name or other stuff.
   *
   * ```markdown
   * > | a <b> c
   *        ^
   * > | a <!doctype> c
   *        ^
   * > | a <!--b--> c
   *        ^
   * ```
   *
   * @type {State}
   */
  function open(code) {
    if (code === 33) {
      effects.consume(code);
      return declarationOpen;
    }
    if (code === 47) {
      effects.consume(code);
      return tagCloseStart;
    }
    if (code === 63) {
      effects.consume(code);
      return instruction;
    }

    // ASCII alphabetical.
    if (asciiAlpha(code)) {
      effects.consume(code);
      return tagOpen;
    }
    return nok(code);
  }

  /**
   * After `<!`, at declaration, comment, or CDATA.
   *
   * ```markdown
   * > | a <!doctype> c
   *         ^
   * > | a <!--b--> c
   *         ^
   * > | a <![CDATA[>&<]]> c
   *         ^
   * ```
   *
   * @type {State}
   */
  function declarationOpen(code) {
    if (code === 45) {
      effects.consume(code);
      return commentOpenInside;
    }
    if (code === 91) {
      effects.consume(code);
      index = 0;
      return cdataOpenInside;
    }
    if (asciiAlpha(code)) {
      effects.consume(code);
      return declaration;
    }
    return nok(code);
  }

  /**
   * In a comment, after `<!-`, at another `-`.
   *
   * ```markdown
   * > | a <!--b--> c
   *          ^
   * ```
   *
   * @type {State}
   */
  function commentOpenInside(code) {
    if (code === 45) {
      effects.consume(code);
      return commentEnd;
    }
    return nok(code);
  }

  /**
   * In comment.
   *
   * ```markdown
   * > | a <!--b--> c
   *           ^
   * ```
   *
   * @type {State}
   */
  function comment(code) {
    if (code === null) {
      return nok(code);
    }
    if (code === 45) {
      effects.consume(code);
      return commentClose;
    }
    if (markdownLineEnding(code)) {
      returnState = comment;
      return lineEndingBefore(code);
    }
    effects.consume(code);
    return comment;
  }

  /**
   * In comment, after `-`.
   *
   * ```markdown
   * > | a <!--b--> c
   *             ^
   * ```
   *
   * @type {State}
   */
  function commentClose(code) {
    if (code === 45) {
      effects.consume(code);
      return commentEnd;
    }
    return comment(code);
  }

  /**
   * In comment, after `--`.
   *
   * ```markdown
   * > | a <!--b--> c
   *              ^
   * ```
   *
   * @type {State}
   */
  function commentEnd(code) {
    return code === 62 ? end(code) : code === 45 ? commentClose(code) : comment(code);
  }

  /**
   * After `<![`, in CDATA, expecting `CDATA[`.
   *
   * ```markdown
   * > | a <![CDATA[>&<]]> b
   *          ^^^^^^
   * ```
   *
   * @type {State}
   */
  function cdataOpenInside(code) {
    const value = "CDATA[";
    if (code === value.charCodeAt(index++)) {
      effects.consume(code);
      return index === value.length ? cdata : cdataOpenInside;
    }
    return nok(code);
  }

  /**
   * In CDATA.
   *
   * ```markdown
   * > | a <![CDATA[>&<]]> b
   *                ^^^
   * ```
   *
   * @type {State}
   */
  function cdata(code) {
    if (code === null) {
      return nok(code);
    }
    if (code === 93) {
      effects.consume(code);
      return cdataClose;
    }
    if (markdownLineEnding(code)) {
      returnState = cdata;
      return lineEndingBefore(code);
    }
    effects.consume(code);
    return cdata;
  }

  /**
   * In CDATA, after `]`, at another `]`.
   *
   * ```markdown
   * > | a <![CDATA[>&<]]> b
   *                    ^
   * ```
   *
   * @type {State}
   */
  function cdataClose(code) {
    if (code === 93) {
      effects.consume(code);
      return cdataEnd;
    }
    return cdata(code);
  }

  /**
   * In CDATA, after `]]`, at `>`.
   *
   * ```markdown
   * > | a <![CDATA[>&<]]> b
   *                     ^
   * ```
   *
   * @type {State}
   */
  function cdataEnd(code) {
    if (code === 62) {
      return end(code);
    }
    if (code === 93) {
      effects.consume(code);
      return cdataEnd;
    }
    return cdata(code);
  }

  /**
   * In declaration.
   *
   * ```markdown
   * > | a <!b> c
   *          ^
   * ```
   *
   * @type {State}
   */
  function declaration(code) {
    if (code === null || code === 62) {
      return end(code);
    }
    if (markdownLineEnding(code)) {
      returnState = declaration;
      return lineEndingBefore(code);
    }
    effects.consume(code);
    return declaration;
  }

  /**
   * In instruction.
   *
   * ```markdown
   * > | a <?b?> c
   *         ^
   * ```
   *
   * @type {State}
   */
  function instruction(code) {
    if (code === null) {
      return nok(code);
    }
    if (code === 63) {
      effects.consume(code);
      return instructionClose;
    }
    if (markdownLineEnding(code)) {
      returnState = instruction;
      return lineEndingBefore(code);
    }
    effects.consume(code);
    return instruction;
  }

  /**
   * In instruction, after `?`, at `>`.
   *
   * ```markdown
   * > | a <?b?> c
   *           ^
   * ```
   *
   * @type {State}
   */
  function instructionClose(code) {
    return code === 62 ? end(code) : instruction(code);
  }

  /**
   * After `</`, in closing tag, at tag name.
   *
   * ```markdown
   * > | a </b> c
   *         ^
   * ```
   *
   * @type {State}
   */
  function tagCloseStart(code) {
    // ASCII alphabetical.
    if (asciiAlpha(code)) {
      effects.consume(code);
      return tagClose;
    }
    return nok(code);
  }

  /**
   * After `</x`, in a tag name.
   *
   * ```markdown
   * > | a </b> c
   *          ^
   * ```
   *
   * @type {State}
   */
  function tagClose(code) {
    // ASCII alphanumerical and `-`.
    if (code === 45 || asciiAlphanumeric(code)) {
      effects.consume(code);
      return tagClose;
    }
    return tagCloseBetween(code);
  }

  /**
   * In closing tag, after tag name.
   *
   * ```markdown
   * > | a </b> c
   *          ^
   * ```
   *
   * @type {State}
   */
  function tagCloseBetween(code) {
    if (markdownLineEnding(code)) {
      returnState = tagCloseBetween;
      return lineEndingBefore(code);
    }
    if (markdownSpace(code)) {
      effects.consume(code);
      return tagCloseBetween;
    }
    return end(code);
  }

  /**
   * After `<x`, in opening tag name.
   *
   * ```markdown
   * > | a <b> c
   *         ^
   * ```
   *
   * @type {State}
   */
  function tagOpen(code) {
    // ASCII alphanumerical and `-`.
    if (code === 45 || asciiAlphanumeric(code)) {
      effects.consume(code);
      return tagOpen;
    }
    if (code === 47 || code === 62 || markdownLineEndingOrSpace(code)) {
      return tagOpenBetween(code);
    }
    return nok(code);
  }

  /**
   * In opening tag, after tag name.
   *
   * ```markdown
   * > | a <b> c
   *         ^
   * ```
   *
   * @type {State}
   */
  function tagOpenBetween(code) {
    if (code === 47) {
      effects.consume(code);
      return end;
    }

    // ASCII alphabetical and `:` and `_`.
    if (code === 58 || code === 95 || asciiAlpha(code)) {
      effects.consume(code);
      return tagOpenAttributeName;
    }
    if (markdownLineEnding(code)) {
      returnState = tagOpenBetween;
      return lineEndingBefore(code);
    }
    if (markdownSpace(code)) {
      effects.consume(code);
      return tagOpenBetween;
    }
    return end(code);
  }

  /**
   * In attribute name.
   *
   * ```markdown
   * > | a <b c> d
   *          ^
   * ```
   *
   * @type {State}
   */
  function tagOpenAttributeName(code) {
    // ASCII alphabetical and `-`, `.`, `:`, and `_`.
    if (code === 45 || code === 46 || code === 58 || code === 95 || asciiAlphanumeric(code)) {
      effects.consume(code);
      return tagOpenAttributeName;
    }
    return tagOpenAttributeNameAfter(code);
  }

  /**
   * After attribute name, before initializer, the end of the tag, or
   * whitespace.
   *
   * ```markdown
   * > | a <b c> d
   *           ^
   * ```
   *
   * @type {State}
   */
  function tagOpenAttributeNameAfter(code) {
    if (code === 61) {
      effects.consume(code);
      return tagOpenAttributeValueBefore;
    }
    if (markdownLineEnding(code)) {
      returnState = tagOpenAttributeNameAfter;
      return lineEndingBefore(code);
    }
    if (markdownSpace(code)) {
      effects.consume(code);
      return tagOpenAttributeNameAfter;
    }
    return tagOpenBetween(code);
  }

  /**
   * Before unquoted, double quoted, or single quoted attribute value, allowing
   * whitespace.
   *
   * ```markdown
   * > | a <b c=d> e
   *            ^
   * ```
   *
   * @type {State}
   */
  function tagOpenAttributeValueBefore(code) {
    if (code === null || code === 60 || code === 61 || code === 62 || code === 96) {
      return nok(code);
    }
    if (code === 34 || code === 39) {
      effects.consume(code);
      marker = code;
      return tagOpenAttributeValueQuoted;
    }
    if (markdownLineEnding(code)) {
      returnState = tagOpenAttributeValueBefore;
      return lineEndingBefore(code);
    }
    if (markdownSpace(code)) {
      effects.consume(code);
      return tagOpenAttributeValueBefore;
    }
    effects.consume(code);
    return tagOpenAttributeValueUnquoted;
  }

  /**
   * In double or single quoted attribute value.
   *
   * ```markdown
   * > | a <b c="d"> e
   *             ^
   * ```
   *
   * @type {State}
   */
  function tagOpenAttributeValueQuoted(code) {
    if (code === marker) {
      effects.consume(code);
      marker = undefined;
      return tagOpenAttributeValueQuotedAfter;
    }
    if (code === null) {
      return nok(code);
    }
    if (markdownLineEnding(code)) {
      returnState = tagOpenAttributeValueQuoted;
      return lineEndingBefore(code);
    }
    effects.consume(code);
    return tagOpenAttributeValueQuoted;
  }

  /**
   * In unquoted attribute value.
   *
   * ```markdown
   * > | a <b c=d> e
   *            ^
   * ```
   *
   * @type {State}
   */
  function tagOpenAttributeValueUnquoted(code) {
    if (code === null || code === 34 || code === 39 || code === 60 || code === 61 || code === 96) {
      return nok(code);
    }
    if (code === 47 || code === 62 || markdownLineEndingOrSpace(code)) {
      return tagOpenBetween(code);
    }
    effects.consume(code);
    return tagOpenAttributeValueUnquoted;
  }

  /**
   * After double or single quoted attribute value, before whitespace or the end
   * of the tag.
   *
   * ```markdown
   * > | a <b c="d"> e
   *               ^
   * ```
   *
   * @type {State}
   */
  function tagOpenAttributeValueQuotedAfter(code) {
    if (code === 47 || code === 62 || markdownLineEndingOrSpace(code)) {
      return tagOpenBetween(code);
    }
    return nok(code);
  }

  /**
   * In certain circumstances of a tag where only an `>` is allowed.
   *
   * ```markdown
   * > | a <b c="d"> e
   *               ^
   * ```
   *
   * @type {State}
   */
  function end(code) {
    if (code === 62) {
      effects.consume(code);
      effects.exit("htmlTextData");
      effects.exit("htmlText");
      return ok;
    }
    return nok(code);
  }

  /**
   * At eol.
   *
   * > 👉 **Note**: we can’t have blank lines in text, so no need to worry about
   * > empty tokens.
   *
   * ```markdown
   * > | a <!--a
   *            ^
   *   | b-->
   * ```
   *
   * @type {State}
   */
  function lineEndingBefore(code) {
    effects.exit("htmlTextData");
    effects.enter("lineEnding");
    effects.consume(code);
    effects.exit("lineEnding");
    return lineEndingAfter;
  }

  /**
   * After eol, at optional whitespace.
   *
   * > 👉 **Note**: we can’t have blank lines in text, so no need to worry about
   * > empty tokens.
   *
   * ```markdown
   *   | a <!--a
   * > | b-->
   *     ^
   * ```
   *
   * @type {State}
   */
  function lineEndingAfter(code) {
    // Always populated by defaults.

    return markdownSpace(code) ? factorySpace(effects, lineEndingAfterPrefix, "linePrefix", self.parser.constructs.disable.null.includes('codeIndented') ? undefined : 4)(code) : lineEndingAfterPrefix(code);
  }

  /**
   * After eol, after optional whitespace.
   *
   * > 👉 **Note**: we can’t have blank lines in text, so no need to worry about
   * > empty tokens.
   *
   * ```markdown
   *   | a <!--a
   * > | b-->
   *     ^
   * ```
   *
   * @type {State}
   */
  function lineEndingAfterPrefix(code) {
    effects.enter("htmlTextData");
    return returnState(code);
  }
}/**
 * @import {
 *   Construct,
 *   Event,
 *   Resolver,
 *   State,
 *   TokenizeContext,
 *   Tokenizer,
 *   Token
 * } from 'micromark-util-types'
 */

import { factoryDestination } from 'micromark-factory-destination';
import { factoryLabel } from 'micromark-factory-label';
import { factoryTitle } from 'micromark-factory-title';
import { factoryWhitespace } from 'micromark-factory-whitespace';
import { markdownLineEndingOrSpace } from 'micromark-util-character';
import { push, splice } from 'micromark-util-chunked';
import { normalizeIdentifier } from 'micromark-util-normalize-identifier';
import { resolveAll } from 'micromark-util-resolve-all';
/** @type {Construct} */
export const labelEnd = {
  name: 'labelEnd',
  resolveAll: resolveAllLabelEnd,
  resolveTo: resolveToLabelEnd,
  tokenize: tokenizeLabelEnd
};

/** @type {Construct} */
const resourceConstruct = {
  tokenize: tokenizeResource
};
/** @type {Construct} */
const referenceFullConstruct = {
  tokenize: tokenizeReferenceFull
};
/** @type {Construct} */
const referenceCollapsedConstruct = {
  tokenize: tokenizeReferenceCollapsed
};

/** @type {Resolver} */
function resolveAllLabelEnd(events) {
  let index = -1;
  /** @type {Array<Event>} */
  const newEvents = [];
  while (++index < events.length) {
    const token = events[index][1];
    newEvents.push(events[index]);
    if (token.type === "labelImage" || token.type === "labelLink" || token.type === "labelEnd") {
      // Remove the marker.
      const offset = token.type === "labelImage" ? 4 : 2;
      token.type = "data";
      index += offset;
    }
  }

  // If the events are equal, we don't have to copy newEvents to events
  if (events.length !== newEvents.length) {
    splice(events, 0, events.length, newEvents);
  }
  return events;
}

/** @type {Resolver} */
function resolveToLabelEnd(events, context) {
  let index = events.length;
  let offset = 0;
  /** @type {Token} */
  let token;
  /** @type {number | undefined} */
  let open;
  /** @type {number | undefined} */
  let close;
  /** @type {Array<Event>} */
  let media;

  // Find an opening.
  while (index--) {
    token = events[index][1];
    if (open) {
      // If we see another link, or inactive link label, we’ve been here before.
      if (token.type === "link" || token.type === "labelLink" && token._inactive) {
        break;
      }

      // Mark other link openings as inactive, as we can’t have links in
      // links.
      if (events[index][0] === 'enter' && token.type === "labelLink") {
        token._inactive = true;
      }
    } else if (close) {
      if (events[index][0] === 'enter' && (token.type === "labelImage" || token.type === "labelLink") && !token._balanced) {
        open = index;
        if (token.type !== "labelLink") {
          offset = 2;
          break;
        }
      }
    } else if (token.type === "labelEnd") {
      close = index;
    }
  }
  const group = {
    type: events[open][1].type === "labelLink" ? "link" : "image",
    start: {
      ...events[open][1].start
    },
    end: {
      ...events[events.length - 1][1].end
    }
  };
  const label = {
    type: "label",
    start: {
      ...events[open][1].start
    },
    end: {
      ...events[close][1].end
    }
  };
  const text = {
    type: "labelText",
    start: {
      ...events[open + offset + 2][1].end
    },
    end: {
      ...events[close - 2][1].start
    }
  };
  media = [['enter', group, context], ['enter', label, context]];

  // Opening marker.
  media = push(media, events.slice(open + 1, open + offset + 3));

  // Text open.
  media = push(media, [['enter', text, context]]);

  // Always populated by defaults.

  // Between.
  media = push(media, resolveAll(context.parser.constructs.insideSpan.null, events.slice(open + offset + 4, close - 3), context));

  // Text close, marker close, label close.
  media = push(media, [['exit', text, context], events[close - 2], events[close - 1], ['exit', label, context]]);

  // Reference, resource, or so.
  media = push(media, events.slice(close + 1));

  // Media close.
  media = push(media, [['exit', group, context]]);
  splice(events, open, events.length, media);
  return events;
}

/**
 * @this {TokenizeContext}
 *   Context.
 * @type {Tokenizer}
 */
function tokenizeLabelEnd(effects, ok, nok) {
  const self = this;
  let index = self.events.length;
  /** @type {Token} */
  let labelStart;
  /** @type {boolean} */
  let defined;

  // Find an opening.
  while (index--) {
    if ((self.events[index][1].type === "labelImage" || self.events[index][1].type === "labelLink") && !self.events[index][1]._balanced) {
      labelStart = self.events[index][1];
      break;
    }
  }
  return start;

  /**
   * Start of label end.
   *
   * ```markdown
   * > | [a](b) c
   *       ^
   * > | [a][b] c
   *       ^
   * > | [a][] b
   *       ^
   * > | [a] b
   * ```
   *
   * @type {State}
   */
  function start(code) {
    // If there is not an okay opening.
    if (!labelStart) {
      return nok(code);
    }

    // If the corresponding label (link) start is marked as inactive,
    // it means we’d be wrapping a link, like this:
    //
    // ```markdown
    // > | a [b [c](d) e](f) g.
    //                  ^
    // ```
    //
    // We can’t have that, so it’s just balanced brackets.
    if (labelStart._inactive) {
      return labelEndNok(code);
    }
    defined = self.parser.defined.includes(normalizeIdentifier(self.sliceSerialize({
      start: labelStart.end,
      end: self.now()
    })));
    effects.enter("labelEnd");
    effects.enter("labelMarker");
    effects.consume(code);
    effects.exit("labelMarker");
    effects.exit("labelEnd");
    return after;
  }

  /**
   * After `]`.
   *
   * ```markdown
   * > | [a](b) c
   *       ^
   * > | [a][b] c
   *       ^
   * > | [a][] b
   *       ^
   * > | [a] b
   *       ^
   * ```
   *
   * @type {State}
   */
  function after(code) {
    // Note: `markdown-rs` also parses GFM footnotes here, which for us is in
    // an extension.

    // Resource (`[asd](fgh)`)?
    if (code === 40) {
      return effects.attempt(resourceConstruct, labelEndOk, defined ? labelEndOk : labelEndNok)(code);
    }

    // Full (`[asd][fgh]`) or collapsed (`[asd][]`) reference?
    if (code === 91) {
      return effects.attempt(referenceFullConstruct, labelEndOk, defined ? referenceNotFull : labelEndNok)(code);
    }

    // Shortcut (`[asd]`) reference?
    return defined ? labelEndOk(code) : labelEndNok(code);
  }

  /**
   * After `]`, at `[`, but not at a full reference.
   *
   * > 👉 **Note**: we only get here if the label is defined.
   *
   * ```markdown
   * > | [a][] b
   *        ^
   * > | [a] b
   *        ^
   * ```
   *
   * @type {State}
   */
  function referenceNotFull(code) {
    return effects.attempt(referenceCollapsedConstruct, labelEndOk, labelEndNok)(code);
  }

  /**
   * Done, we found something.
   *
   * ```markdown
   * > | [a](b) c
   *           ^
   * > | [a][b] c
   *           ^
   * > | [a][] b
   *          ^
   * > | [a] b
   *        ^
   * ```
   *
   * @type {State}
   */
  function labelEndOk(code) {
    // Note: `markdown-rs` does a bunch of stuff here.
    return ok(code);
  }

  /**
   * Done, it’s nothing.
   *
   * There was an okay opening, but we didn’t match anything.
   *
   * ```markdown
   * > | [a](b c
   *        ^
   * > | [a][b c
   *        ^
   * > | [a] b
   *        ^
   * ```
   *
   * @type {State}
   */
  function labelEndNok(code) {
    labelStart._balanced = true;
    return nok(code);
  }
}

/**
 * @this {TokenizeContext}
 *   Context.
 * @type {Tokenizer}
 */
function tokenizeResource(effects, ok, nok) {
  return resourceStart;

  /**
   * At a resource.
   *
   * ```markdown
   * > | [a](b) c
   *        ^
   * ```
   *
   * @type {State}
   */
  function resourceStart(code) {
    effects.enter("resource");
    effects.enter("resourceMarker");
    effects.consume(code);
    effects.exit("resourceMarker");
    return resourceBefore;
  }

  /**
   * In resource, after `(`, at optional whitespace.
   *
   * ```markdown
   * > | [a](b) c
   *         ^
   * ```
   *
   * @type {State}
   */
  function resourceBefore(code) {
    return markdownLineEndingOrSpace(code) ? factoryWhitespace(effects, resourceOpen)(code) : resourceOpen(code);
  }

  /**
   * In resource, after optional whitespace, at `)` or a destination.
   *
   * ```markdown
   * > | [a](b) c
   *         ^
   * ```
   *
   * @type {State}
   */
  function resourceOpen(code) {
    if (code === 41) {
      return resourceEnd(code);
    }
    return factoryDestination(effects, resourceDestinationAfter, resourceDestinationMissing, "resourceDestination", "resourceDestinationLiteral", "resourceDestinationLiteralMarker", "resourceDestinationRaw", "resourceDestinationString", 32)(code);
  }

  /**
   * In resource, after destination, at optional whitespace.
   *
   * ```markdown
   * > | [a](b) c
   *          ^
   * ```
   *
   * @type {State}
   */
  function resourceDestinationAfter(code) {
    return markdownLineEndingOrSpace(code) ? factoryWhitespace(effects, resourceBetween)(code) : resourceEnd(code);
  }

  /**
   * At invalid destination.
   *
   * ```markdown
   * > | [a](<<) b
   *         ^
   * ```
   *
   * @type {State}
   */
  function resourceDestinationMissing(code) {
    return nok(code);
  }

  /**
   * In resource, after destination and whitespace, at `(` or title.
   *
   * ```markdown
   * > | [a](b ) c
   *           ^
   * ```
   *
   * @type {State}
   */
  function resourceBetween(code) {
    if (code === 34 || code === 39 || code === 40) {
      return factoryTitle(effects, resourceTitleAfter, nok, "resourceTitle", "resourceTitleMarker", "resourceTitleString")(code);
    }
    return resourceEnd(code);
  }

  /**
   * In resource, after title, at optional whitespace.
   *
   * ```markdown
   * > | [a](b "c") d
   *              ^
   * ```
   *
   * @type {State}
   */
  function resourceTitleAfter(code) {
    return markdownLineEndingOrSpace(code) ? factoryWhitespace(effects, resourceEnd)(code) : resourceEnd(code);
  }

  /**
   * In resource, at `)`.
   *
   * ```markdown
   * > | [a](b) d
   *          ^
   * ```
   *
   * @type {State}
   */
  function resourceEnd(code) {
    if (code === 41) {
      effects.enter("resourceMarker");
      effects.consume(code);
      effects.exit("resourceMarker");
      effects.exit("resource");
      return ok;
    }
    return nok(code);
  }
}

/**
 * @this {TokenizeContext}
 *   Context.
 * @type {Tokenizer}
 */
function tokenizeReferenceFull(effects, ok, nok) {
  const self = this;
  return referenceFull;

  /**
   * In a reference (full), at the `[`.
   *
   * ```markdown
   * > | [a][b] d
   *        ^
   * ```
   *
   * @type {State}
   */
  function referenceFull(code) {
    return factoryLabel.call(self, effects, referenceFullAfter, referenceFullMissing, "reference", "referenceMarker", "referenceString")(code);
  }

  /**
   * In a reference (full), after `]`.
   *
   * ```markdown
   * > | [a][b] d
   *          ^
   * ```
   *
   * @type {State}
   */
  function referenceFullAfter(code) {
    return self.parser.defined.includes(normalizeIdentifier(self.sliceSerialize(self.events[self.events.length - 1][1]).slice(1, -1))) ? ok(code) : nok(code);
  }

  /**
   * In reference (full) that was missing.
   *
   * ```markdown
   * > | [a][b d
   *        ^
   * ```
   *
   * @type {State}
   */
  function referenceFullMissing(code) {
    return nok(code);
  }
}

/**
 * @this {TokenizeContext}
 *   Context.
 * @type {Tokenizer}
 */
function tokenizeReferenceCollapsed(effects, ok, nok) {
  return referenceCollapsedStart;

  /**
   * In reference (collapsed), at `[`.
   *
   * > 👉 **Note**: we only get here if the label is defined.
   *
   * ```markdown
   * > | [a][] d
   *        ^
   * ```
   *
   * @type {State}
   */
  function referenceCollapsedStart(code) {
    // We only attempt a collapsed label if there’s a `[`.

    effects.enter("reference");
    effects.enter("referenceMarker");
    effects.consume(code);
    effects.exit("referenceMarker");
    return referenceCollapsedOpen;
  }

  /**
   * In reference (collapsed), at `]`.
   *
   * > 👉 **Note**: we only get here if the label is defined.
   *
   * ```markdown
   * > | [a][] d
   *         ^
   * ```
   *
   *  @type {State}
   */
  function referenceCollapsedOpen(code) {
    if (code === 93) {
      effects.enter("referenceMarker");
      effects.consume(code);
      effects.exit("referenceMarker");
      effects.exit("reference");
      return ok;
    }
    return nok(code);
  }
}/**
 * @import {
 *   Construct,
 *   State,
 *   TokenizeContext,
 *   Tokenizer
 * } from 'micromark-util-types'
 */

import { labelEnd } from './label-end.js';

/** @type {Construct} */
export const labelStartImage = {
  name: 'labelStartImage',
  resolveAll: labelEnd.resolveAll,
  tokenize: tokenizeLabelStartImage
};

/**
 * @this {TokenizeContext}
 *   Context.
 * @type {Tokenizer}
 */
function tokenizeLabelStartImage(effects, ok, nok) {
  const self = this;
  return start;

  /**
   * Start of label (image) start.
   *
   * ```markdown
   * > | a ![b] c
   *       ^
   * ```
   *
   * @type {State}
   */
  function start(code) {
    effects.enter("labelImage");
    effects.enter("labelImageMarker");
    effects.consume(code);
    effects.exit("labelImageMarker");
    return open;
  }

  /**
   * After `!`, at `[`.
   *
   * ```markdown
   * > | a ![b] c
   *        ^
   * ```
   *
   * @type {State}
   */
  function open(code) {
    if (code === 91) {
      effects.enter("labelMarker");
      effects.consume(code);
      effects.exit("labelMarker");
      effects.exit("labelImage");
      return after;
    }
    return nok(code);
  }

  /**
   * After `![`.
   *
   * ```markdown
   * > | a ![b] c
   *         ^
   * ```
   *
   * This is needed in because, when GFM footnotes are enabled, images never
   * form when started with a `^`.
   * Instead, links form:
   *
   * ```markdown
   * ![^a](b)
   *
   * ![^a][b]
   *
   * [b]: c
   * ```
   *
   * ```html
   * <p>!<a href=\"b\">^a</a></p>
   * <p>!<a href=\"c\">^a</a></p>
   * ```
   *
   * @type {State}
   */
  function after(code) {
    // To do: use a new field to do this, this is still needed for
    // `micromark-extension-gfm-footnote`, but the `label-start-link`
    // behavior isn’t.
    // Hidden footnotes hook.
    /* c8 ignore next 3 */
    return code === 94 && '_hiddenFootnoteSupport' in self.parser.constructs ? nok(code) : ok(code);
  }
}/**
 * @import {
 *   Construct,
 *   State,
 *   TokenizeContext,
 *   Tokenizer
 * } from 'micromark-util-types'
 */

import { labelEnd } from './label-end.js';

/** @type {Construct} */
export const labelStartLink = {
  name: 'labelStartLink',
  resolveAll: labelEnd.resolveAll,
  tokenize: tokenizeLabelStartLink
};

/**
 * @this {TokenizeContext}
 *   Context.
 * @type {Tokenizer}
 */
function tokenizeLabelStartLink(effects, ok, nok) {
  const self = this;
  return start;

  /**
   * Start of label (link) start.
   *
   * ```markdown
   * > | a [b] c
   *       ^
   * ```
   *
   * @type {State}
   */
  function start(code) {
    effects.enter("labelLink");
    effects.enter("labelMarker");
    effects.consume(code);
    effects.exit("labelMarker");
    effects.exit("labelLink");
    return after;
  }

  /** @type {State} */
  function after(code) {
    // To do: this isn’t needed in `micromark-extension-gfm-footnote`,
    // remove.
    // Hidden footnotes hook.
    /* c8 ignore next 3 */
    return code === 94 && '_hiddenFootnoteSupport' in self.parser.constructs ? nok(code) : ok(code);
  }
}/**
 * @import {
 *   Construct,
 *   State,
 *   TokenizeContext,
 *   Tokenizer
 * } from 'micromark-util-types'
 */

import { factorySpace } from 'micromark-factory-space';
import { markdownLineEnding } from 'micromark-util-character';
/** @type {Construct} */
export const lineEnding = {
  name: 'lineEnding',
  tokenize: tokenizeLineEnding
};

/**
 * @this {TokenizeContext}
 *   Context.
 * @type {Tokenizer}
 */
function tokenizeLineEnding(effects, ok) {
  return start;

  /** @type {State} */
  function start(code) {
    effects.enter("lineEnding");
    effects.consume(code);
    effects.exit("lineEnding");
    return factorySpace(effects, ok, "linePrefix");
  }
}/**
 * @import {
 *   Code,
 *   Construct,
 *   Exiter,
 *   State,
 *   TokenizeContext,
 *   Tokenizer
 * } from 'micromark-util-types'
 */

import { factorySpace } from 'micromark-factory-space';
import { asciiDigit, markdownSpace } from 'micromark-util-character';
import { blankLine } from './blank-line.js';
import { thematicBreak } from './thematic-break.js';

/** @type {Construct} */
export const list = {
  continuation: {
    tokenize: tokenizeListContinuation
  },
  exit: tokenizeListEnd,
  name: 'list',
  tokenize: tokenizeListStart
};

/** @type {Construct} */
const listItemPrefixWhitespaceConstruct = {
  partial: true,
  tokenize: tokenizeListItemPrefixWhitespace
};

/** @type {Construct} */
const indentConstruct = {
  partial: true,
  tokenize: tokenizeIndent
};

// To do: `markdown-rs` parses list items on their own and later stitches them
// together.

/**
 * @this {TokenizeContext}
 *   Context.
 * @type {Tokenizer}
 */
function tokenizeListStart(effects, ok, nok) {
  const self = this;
  const tail = self.events[self.events.length - 1];
  let initialSize = tail && tail[1].type === "linePrefix" ? tail[2].sliceSerialize(tail[1], true).length : 0;
  let size = 0;
  return start;

  /** @type {State} */
  function start(code) {
    const kind = self.containerState.type || (code === 42 || code === 43 || code === 45 ? "listUnordered" : "listOrdered");
    if (kind === "listUnordered" ? !self.containerState.marker || code === self.containerState.marker : asciiDigit(code)) {
      if (!self.containerState.type) {
        self.containerState.type = kind;
        effects.enter(kind, {
          _container: true
        });
      }
      if (kind === "listUnordered") {
        effects.enter("listItemPrefix");
        return code === 42 || code === 45 ? effects.check(thematicBreak, nok, atMarker)(code) : atMarker(code);
      }
      if (!self.interrupt || code === 49) {
        effects.enter("listItemPrefix");
        effects.enter("listItemValue");
        return inside(code);
      }
    }
    return nok(code);
  }

  /** @type {State} */
  function inside(code) {
    if (asciiDigit(code) && ++size < 10) {
      effects.consume(code);
      return inside;
    }
    if ((!self.interrupt || size < 2) && (self.containerState.marker ? code === self.containerState.marker : code === 41 || code === 46)) {
      effects.exit("listItemValue");
      return atMarker(code);
    }
    return nok(code);
  }

  /**
   * @type {State}
   **/
  function atMarker(code) {
    effects.enter("listItemMarker");
    effects.consume(code);
    effects.exit("listItemMarker");
    self.containerState.marker = self.containerState.marker || code;
    return effects.check(blankLine,
    // Can’t be empty when interrupting.
    self.interrupt ? nok : onBlank, effects.attempt(listItemPrefixWhitespaceConstruct, endOfPrefix, otherPrefix));
  }

  /** @type {State} */
  function onBlank(code) {
    self.containerState.initialBlankLine = true;
    initialSize++;
    return endOfPrefix(code);
  }

  /** @type {State} */
  function otherPrefix(code) {
    if (markdownSpace(code)) {
      effects.enter("listItemPrefixWhitespace");
      effects.consume(code);
      effects.exit("listItemPrefixWhitespace");
      return endOfPrefix;
    }
    return nok(code);
  }

  /** @type {State} */
  function endOfPrefix(code) {
    self.containerState.size = initialSize + self.sliceSerialize(effects.exit("listItemPrefix"), true).length;
    return ok(code);
  }
}

/**
 * @this {TokenizeContext}
 *   Context.
 * @type {Tokenizer}
 */
function tokenizeListContinuation(effects, ok, nok) {
  const self = this;
  self.containerState._closeFlow = undefined;
  return effects.check(blankLine, onBlank, notBlank);

  /** @type {State} */
  function onBlank(code) {
    self.containerState.furtherBlankLines = self.containerState.furtherBlankLines || self.containerState.initialBlankLine;

    // We have a blank line.
    // Still, try to consume at most the items size.
    return factorySpace(effects, ok, "listItemIndent", self.containerState.size + 1)(code);
  }

  /** @type {State} */
  function notBlank(code) {
    if (self.containerState.furtherBlankLines || !markdownSpace(code)) {
      self.containerState.furtherBlankLines = undefined;
      self.containerState.initialBlankLine = undefined;
      return notInCurrentItem(code);
    }
    self.containerState.furtherBlankLines = undefined;
    self.containerState.initialBlankLine = undefined;
    return effects.attempt(indentConstruct, ok, notInCurrentItem)(code);
  }

  /** @type {State} */
  function notInCurrentItem(code) {
    // While we do continue, we signal that the flow should be closed.
    self.containerState._closeFlow = true;
    // As we’re closing flow, we’re no longer interrupting.
    self.interrupt = undefined;
    // Always populated by defaults.

    return factorySpace(effects, effects.attempt(list, ok, nok), "linePrefix", self.parser.constructs.disable.null.includes('codeIndented') ? undefined : 4)(code);
  }
}

/**
 * @this {TokenizeContext}
 *   Context.
 * @type {Tokenizer}
 */
function tokenizeIndent(effects, ok, nok) {
  const self = this;
  return factorySpace(effects, afterPrefix, "listItemIndent", self.containerState.size + 1);

  /** @type {State} */
  function afterPrefix(code) {
    const tail = self.events[self.events.length - 1];
    return tail && tail[1].type === "listItemIndent" && tail[2].sliceSerialize(tail[1], true).length === self.containerState.size ? ok(code) : nok(code);
  }
}

/**
 * @this {TokenizeContext}
 *   Context.
 * @type {Exiter}
 */
function tokenizeListEnd(effects) {
  effects.exit(this.containerState.type);
}

/**
 * @this {TokenizeContext}
 *   Context.
 * @type {Tokenizer}
 */
function tokenizeListItemPrefixWhitespace(effects, ok, nok) {
  const self = this;

  // Always populated by defaults.

  return factorySpace(effects, afterPrefix, "listItemPrefixWhitespace", self.parser.constructs.disable.null.includes('codeIndented') ? undefined : 4 + 1);

  /** @type {State} */
  function afterPrefix(code) {
    const tail = self.events[self.events.length - 1];
    return !markdownSpace(code) && tail && tail[1].type === "listItemPrefixWhitespace" ? ok(code) : nok(code);
  }
}/**
 * @import {
 *   Code,
 *   Construct,
 *   Resolver,
 *   State,
 *   TokenizeContext,
 *   Tokenizer
 * } from 'micromark-util-types'
 */

import { factorySpace } from 'micromark-factory-space';
import { markdownLineEnding, markdownSpace } from 'micromark-util-character';
/** @type {Construct} */
export const setextUnderline = {
  name: 'setextUnderline',
  resolveTo: resolveToSetextUnderline,
  tokenize: tokenizeSetextUnderline
};

/** @type {Resolver} */
function resolveToSetextUnderline(events, context) {
  // To do: resolve like `markdown-rs`.
  let index = events.length;
  /** @type {number | undefined} */
  let content;
  /** @type {number | undefined} */
  let text;
  /** @type {number | undefined} */
  let definition;

  // Find the opening of the content.
  // It’ll always exist: we don’t tokenize if it isn’t there.
  while (index--) {
    if (events[index][0] === 'enter') {
      if (events[index][1].type === "content") {
        content = index;
        break;
      }
      if (events[index][1].type === "paragraph") {
        text = index;
      }
    }
    // Exit
    else {
      if (events[index][1].type === "content") {
        // Remove the content end (if needed we’ll add it later)
        events.splice(index, 1);
      }
      if (!definition && events[index][1].type === "definition") {
        definition = index;
      }
    }
  }
  const heading = {
    type: "setextHeading",
    start: {
      ...events[content][1].start
    },
    end: {
      ...events[events.length - 1][1].end
    }
  };

  // Change the paragraph to setext heading text.
  events[text][1].type = "setextHeadingText";

  // If we have definitions in the content, we’ll keep on having content,
  // but we need move it.
  if (definition) {
    events.splice(text, 0, ['enter', heading, context]);
    events.splice(definition + 1, 0, ['exit', events[content][1], context]);
    events[content][1].end = {
      ...events[definition][1].end
    };
  } else {
    events[content][1] = heading;
  }

  // Add the heading exit at the end.
  events.push(['exit', heading, context]);
  return events;
}

/**
 * @this {TokenizeContext}
 *   Context.
 * @type {Tokenizer}
 */
function tokenizeSetextUnderline(effects, ok, nok) {
  const self = this;
  /** @type {NonNullable<Code>} */
  let marker;
  return start;

  /**
   * At start of heading (setext) underline.
   *
   * ```markdown
   *   | aa
   * > | ==
   *     ^
   * ```
   *
   * @type {State}
   */
  function start(code) {
    let index = self.events.length;
    /** @type {boolean | undefined} */
    let paragraph;
    // Find an opening.
    while (index--) {
      // Skip enter/exit of line ending, line prefix, and content.
      // We can now either have a definition or a paragraph.
      if (self.events[index][1].type !== "lineEnding" && self.events[index][1].type !== "linePrefix" && self.events[index][1].type !== "content") {
        paragraph = self.events[index][1].type === "paragraph";
        break;
      }
    }

    // To do: handle lazy/pierce like `markdown-rs`.
    // To do: parse indent like `markdown-rs`.
    if (!self.parser.lazy[self.now().line] && (self.interrupt || paragraph)) {
      effects.enter("setextHeadingLine");
      marker = code;
      return before(code);
    }
    return nok(code);
  }

  /**
   * After optional whitespace, at `-` or `=`.
   *
   * ```markdown
   *   | aa
   * > | ==
   *     ^
   * ```
   *
   * @type {State}
   */
  function before(code) {
    effects.enter("setextHeadingLineSequence");
    return inside(code);
  }

  /**
   * In sequence.
   *
   * ```markdown
   *   | aa
   * > | ==
   *     ^
   * ```
   *
   * @type {State}
   */
  function inside(code) {
    if (code === marker) {
      effects.consume(code);
      return inside;
    }
    effects.exit("setextHeadingLineSequence");
    return markdownSpace(code) ? factorySpace(effects, after, "lineSuffix")(code) : after(code);
  }

  /**
   * After sequence, after optional whitespace.
   *
   * ```markdown
   *   | aa
   * > | ==
   *       ^
   * ```
   *
   * @type {State}
   */
  function after(code) {
    if (code === null || markdownLineEnding(code)) {
      effects.exit("setextHeadingLine");
      return ok(code);
    }
    return nok(code);
  }
}/**
 * @import {
 *   Code,
 *   Construct,
 *   State,
 *   TokenizeContext,
 *   Tokenizer
 * } from 'micromark-util-types'
 */

import { factorySpace } from 'micromark-factory-space';
import { markdownLineEnding, markdownSpace } from 'micromark-util-character';
/** @type {Construct} */
export const thematicBreak = {
  name: 'thematicBreak',
  tokenize: tokenizeThematicBreak
};

/**
 * @this {TokenizeContext}
 *   Context.
 * @type {Tokenizer}
 */
function tokenizeThematicBreak(effects, ok, nok) {
  let size = 0;
  /** @type {NonNullable<Code>} */
  let marker;
  return start;

  /**
   * Start of thematic break.
   *
   * ```markdown
   * > | ***
   *     ^
   * ```
   *
   * @type {State}
   */
  function start(code) {
    effects.enter("thematicBreak");
    // To do: parse indent like `markdown-rs`.
    return before(code);
  }

  /**
   * After optional whitespace, at marker.
   *
   * ```markdown
   * > | ***
   *     ^
   * ```
   *
   * @type {State}
   */
  function before(code) {
    marker = code;
    return atBreak(code);
  }

  /**
   * After something, before something else.
   *
   * ```markdown
   * > | ***
   *     ^
   * ```
   *
   * @type {State}
   */
  function atBreak(code) {
    if (code === marker) {
      effects.enter("thematicBreakSequence");
      return sequence(code);
    }
    if (size >= 3 && (code === null || markdownLineEnding(code))) {
      effects.exit("thematicBreak");
      return ok(code);
    }
    return nok(code);
  }

  /**
   * In sequence.
   *
   * ```markdown
   * > | ***
   *     ^
   * ```
   *
   * @type {State}
   */
  function sequence(code) {
    if (code === marker) {
      effects.consume(code);
      size++;
      return sequence;
    }
    effects.exit("thematicBreakSequence");
    return markdownSpace(code) ? factorySpace(effects, atBreak, "whitespace")(code) : atBreak(code);
  }
}{
  "name": "micromark-core-commonmark",
  "version": "2.0.3",
  "description": "The CommonMark markdown constructs",
  "license": "MIT",
  "keywords": [
    "micromark",
    "core",
    "commonmark"
  ],
  "repository": "https://github.com/micromark/micromark/tree/main/packages/micromark-core-commonmark",
  "bugs": "https://github.com/micromark/micromark/issues",
  "funding": [
    {
      "type": "GitHub Sponsors",
      "url": "https://github.com/sponsors/unifiedjs"
    },
    {
      "type": "OpenCollective",
      "url": "https://opencollective.com/unified"
    }
  ],
  "author": "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)",
  "contributors": [
    "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)"
  ],
  "sideEffects": false,
  "type": "module",
  "files": [
    "dev/",
    "lib/",
    "index.d.ts.map",
    "index.d.ts",
    "index.js"
  ],
  "exports": {
    "development": "./dev/index.js",
    "default": "./index.js"
  },
  "dependencies": {
    "decode-named-character-reference": "^1.0.0",
    "devlop": "^1.0.0",
    "micromark-factory-destination": "^2.0.0",
    "micromark-factory-label": "^2.0.0",
    "micromark-factory-space": "^2.0.0",
    "micromark-factory-title": "^2.0.0",
    "micromark-factory-whitespace": "^2.0.0",
    "micromark-util-character": "^2.0.0",
    "micromark-util-chunked": "^2.0.0",
    "micromark-util-classify-character": "^2.0.0",
    "micromark-util-html-tag-name": "^2.0.0",
    "micromark-util-normalize-identifier": "^2.0.0",
    "micromark-util-resolve-all": "^2.0.0",
    "micromark-util-subtokenize": "^2.0.0",
    "micromark-util-symbol": "^2.0.0",
    "micromark-util-types": "^2.0.0"
  },
  "scripts": {
    "build": "micromark-build"
  },
  "xo": {
    "envs": [
      "shared-node-browser"
    ],
    "prettier": true,
    "rules": {
      "logical-assignment-operators": "off",
      "max-depth": "off",
      "unicorn/no-this-assignment": "off",
      "unicorn/prefer-at": "off",
      "unicorn/prefer-code-point": "off"
    }
  }
}
// Note: more types exported from `index.d.ts`.
export { directive } from './lib/syntax.js';
export { directiveHtml } from './lib/html.js';/**
 * @import {Construct, State, Token, TokenizeContext, Tokenizer} from 'micromark-util-types'
 */

import { factorySpace } from 'micromark-factory-space';
import { markdownLineEnding } from 'micromark-util-character';
import { factoryAttributes } from './factory-attributes.js';
import { factoryLabel } from './factory-label.js';
import { factoryName } from './factory-name.js';

/** @type {Construct} */
export const directiveContainer = {
  tokenize: tokenizeDirectiveContainer,
  concrete: true
};
const label = {
  tokenize: tokenizeLabel,
  partial: true
};
const attributes = {
  tokenize: tokenizeAttributes,
  partial: true
};
const nonLazyLine = {
  tokenize: tokenizeNonLazyLine,
  partial: true
};

/**
 * @this {TokenizeContext}
 * @type {Tokenizer}
 */
function tokenizeDirectiveContainer(effects, ok, nok) {
  const self = this;
  const tail = self.events[self.events.length - 1];
  const initialSize = tail && tail[1].type === "linePrefix" ? tail[2].sliceSerialize(tail[1], true).length : 0;
  let sizeOpen = 0;
  /** @type {Token} */
  let previous;
  return start;

  /** @type {State} */
  function start(code) {
    effects.enter('directiveContainer');
    effects.enter('directiveContainerFence');
    effects.enter('directiveContainerSequence');
    return sequenceOpen(code);
  }

  /** @type {State} */
  function sequenceOpen(code) {
    if (code === 58) {
      effects.consume(code);
      sizeOpen++;
      return sequenceOpen;
    }
    if (sizeOpen < 3) {
      return nok(code);
    }
    effects.exit('directiveContainerSequence');
    return factoryName.call(self, effects, afterName, nok, 'directiveContainerName')(code);
  }

  /** @type {State} */
  function afterName(code) {
    return code === 91 ? effects.attempt(label, afterLabel, afterLabel)(code) : afterLabel(code);
  }

  /** @type {State} */
  function afterLabel(code) {
    return code === 123 ? effects.attempt(attributes, afterAttributes, afterAttributes)(code) : afterAttributes(code);
  }

  /** @type {State} */
  function afterAttributes(code) {
    return factorySpace(effects, openAfter, "whitespace")(code);
  }

  /** @type {State} */
  function openAfter(code) {
    effects.exit('directiveContainerFence');
    if (code === null) {
      return after(code);
    }
    if (markdownLineEnding(code)) {
      if (self.interrupt) {
        return ok(code);
      }
      return effects.attempt(nonLazyLine, contentStart, after)(code);
    }
    return nok(code);
  }

  /** @type {State} */
  function contentStart(code) {
    if (code === null) {
      return after(code);
    }
    if (markdownLineEnding(code)) {
      return effects.check(nonLazyLine, emptyContentNonLazyLineAfter, after)(code);
    }
    effects.enter('directiveContainerContent');
    return lineStart(code);
  }

  /** @type {State} */
  function lineStart(code) {
    return effects.attempt({
      tokenize: tokenizeClosingFence,
      partial: true
    }, afterContent, initialSize ? factorySpace(effects, chunkStart, "linePrefix", initialSize + 1) : chunkStart)(code);
  }

  /** @type {State} */
  function chunkStart(code) {
    if (code === null) {
      return afterContent(code);
    }
    if (markdownLineEnding(code)) {
      return effects.check(nonLazyLine, chunkNonLazyStart, afterContent)(code);
    }
    return chunkNonLazyStart(code);
  }

  /** @type {State} */
  function contentContinue(code) {
    if (code === null) {
      const t = effects.exit("chunkDocument");
      self.parser.lazy[t.start.line] = false;
      return afterContent(code);
    }
    if (markdownLineEnding(code)) {
      return effects.check(nonLazyLine, nonLazyLineAfter, lineAfter)(code);
    }
    effects.consume(code);
    return contentContinue;
  }

  /** @type {State} */
  function chunkNonLazyStart(code) {
    const token = effects.enter("chunkDocument", {
      contentType: "document",
      previous
    });
    if (previous) previous.next = token;
    previous = token;
    return contentContinue(code);
  }

  /** @type {State} */
  function emptyContentNonLazyLineAfter(code) {
    effects.enter('directiveContainerContent');
    return lineStart(code);
  }

  /** @type {State} */
  function nonLazyLineAfter(code) {
    effects.consume(code);
    const t = effects.exit("chunkDocument");
    self.parser.lazy[t.start.line] = false;
    return lineStart;
  }

  /** @type {State} */
  function lineAfter(code) {
    const t = effects.exit("chunkDocument");
    self.parser.lazy[t.start.line] = false;
    return afterContent(code);
  }

  /** @type {State} */
  function afterContent(code) {
    effects.exit('directiveContainerContent');
    return after(code);
  }

  /** @type {State} */
  function after(code) {
    effects.exit('directiveContainer');
    return ok(code);
  }

  /**
   * @this {TokenizeContext}
   * @type {Tokenizer}
   */
  function tokenizeClosingFence(effects, ok, nok) {
    let size = 0;
    return factorySpace(effects, closingPrefixAfter, "linePrefix", self.parser.constructs.disable.null.includes('codeIndented') ? undefined : 4);

    /** @type {State} */
    function closingPrefixAfter(code) {
      effects.enter('directiveContainerFence');
      effects.enter('directiveContainerSequence');
      return closingSequence(code);
    }

    /** @type {State} */
    function closingSequence(code) {
      if (code === 58) {
        effects.consume(code);
        size++;
        return closingSequence;
      }
      if (size < sizeOpen) return nok(code);
      effects.exit('directiveContainerSequence');
      return factorySpace(effects, closingSequenceEnd, "whitespace")(code);
    }

    /** @type {State} */
    function closingSequenceEnd(code) {
      if (code === null || markdownLineEnding(code)) {
        effects.exit('directiveContainerFence');
        return ok(code);
      }
      return nok(code);
    }
  }
}

/**
 * @this {TokenizeContext}
 * @type {Tokenizer}
 */
function tokenizeLabel(effects, ok, nok) {
  // Always a `[`
  return factoryLabel(effects, ok, nok, 'directiveContainerLabel', 'directiveContainerLabelMarker', 'directiveContainerLabelString', true);
}

/**
 * @this {TokenizeContext}
 * @type {Tokenizer}
 */
function tokenizeAttributes(effects, ok, nok) {
  // Always a `{`
  return factoryAttributes(effects, ok, nok, 'directiveContainerAttributes', 'directiveContainerAttributesMarker', 'directiveContainerAttribute', 'directiveContainerAttributeId', 'directiveContainerAttributeClass', 'directiveContainerAttributeName', 'directiveContainerAttributeInitializerMarker', 'directiveContainerAttributeValueLiteral', 'directiveContainerAttributeValue', 'directiveContainerAttributeValueMarker', 'directiveContainerAttributeValueData', true);
}

/**
 * @this {TokenizeContext}
 * @type {Tokenizer}
 */
function tokenizeNonLazyLine(effects, ok, nok) {
  const self = this;
  return start;

  /** @type {State} */
  function start(code) {
    effects.enter("lineEnding");
    effects.consume(code);
    effects.exit("lineEnding");
    return lineStart;
  }

  /** @type {State} */
  function lineStart(code) {
    return self.parser.lazy[self.now().line] ? nok(code) : ok(code);
  }
}/**
 * @import {Construct, State, TokenizeContext, Tokenizer} from 'micromark-util-types'
 */

import { factorySpace } from 'micromark-factory-space';
import { markdownLineEnding } from 'micromark-util-character';
import { factoryAttributes } from './factory-attributes.js';
import { factoryLabel } from './factory-label.js';
import { factoryName } from './factory-name.js';

/** @type {Construct} */
export const directiveLeaf = {
  tokenize: tokenizeDirectiveLeaf
};
const label = {
  tokenize: tokenizeLabel,
  partial: true
};
const attributes = {
  tokenize: tokenizeAttributes,
  partial: true
};

/**
 * @this {TokenizeContext}
 * @type {Tokenizer}
 */
function tokenizeDirectiveLeaf(effects, ok, nok) {
  const self = this;
  return start;

  /** @type {State} */
  function start(code) {
    effects.enter('directiveLeaf');
    effects.enter('directiveLeafSequence');
    effects.consume(code);
    return inStart;
  }

  /** @type {State} */
  function inStart(code) {
    if (code === 58) {
      effects.consume(code);
      effects.exit('directiveLeafSequence');
      return factoryName.call(self, effects, afterName, nok, 'directiveLeafName');
    }
    return nok(code);
  }

  /** @type {State} */
  function afterName(code) {
    return code === 91 ? effects.attempt(label, afterLabel, afterLabel)(code) : afterLabel(code);
  }

  /** @type {State} */
  function afterLabel(code) {
    return code === 123 ? effects.attempt(attributes, afterAttributes, afterAttributes)(code) : afterAttributes(code);
  }

  /** @type {State} */
  function afterAttributes(code) {
    return factorySpace(effects, end, "whitespace")(code);
  }

  /** @type {State} */
  function end(code) {
    if (code === null || markdownLineEnding(code)) {
      effects.exit('directiveLeaf');
      return ok(code);
    }
    return nok(code);
  }
}

/**
 * @this {TokenizeContext}
 * @type {Tokenizer}
 */
function tokenizeLabel(effects, ok, nok) {
  // Always a `[`
  return factoryLabel(effects, ok, nok, 'directiveLeafLabel', 'directiveLeafLabelMarker', 'directiveLeafLabelString', true);
}

/**
 * @this {TokenizeContext}
 * @type {Tokenizer}
 */
function tokenizeAttributes(effects, ok, nok) {
  // Always a `{`
  return factoryAttributes(effects, ok, nok, 'directiveLeafAttributes', 'directiveLeafAttributesMarker', 'directiveLeafAttribute', 'directiveLeafAttributeId', 'directiveLeafAttributeClass', 'directiveLeafAttributeName', 'directiveLeafAttributeInitializerMarker', 'directiveLeafAttributeValueLiteral', 'directiveLeafAttributeValue', 'directiveLeafAttributeValueMarker', 'directiveLeafAttributeValueData', true);
}/**
 * @import {Construct, Previous, State, TokenizeContext, Tokenizer} from 'micromark-util-types'
 */

import { factoryAttributes } from './factory-attributes.js';
import { factoryLabel } from './factory-label.js';
import { factoryName } from './factory-name.js';

/** @type {Construct} */
export const directiveText = {
  tokenize: tokenizeDirectiveText,
  previous
};
const label = {
  tokenize: tokenizeLabel,
  partial: true
};
const attributes = {
  tokenize: tokenizeAttributes,
  partial: true
};

/**
 * @this {TokenizeContext}
 * @type {Previous}
 */
function previous(code) {
  // If there is a previous code, there will always be a tail.
  return code !== 58 || this.events[this.events.length - 1][1].type === "characterEscape";
}

/**
 * @this {TokenizeContext}
 * @type {Tokenizer}
 */
function tokenizeDirectiveText(effects, ok, nok) {
  const self = this;
  return start;

  /** @type {State} */
  function start(code) {
    effects.enter('directiveText');
    effects.enter('directiveTextMarker');
    effects.consume(code);
    effects.exit('directiveTextMarker');
    return factoryName.call(self, effects, afterName, nok, 'directiveTextName');
  }

  /** @type {State} */
  function afterName(code) {
    return code === 58 ? nok(code) : code === 91 ? effects.attempt(label, afterLabel, afterLabel)(code) : afterLabel(code);
  }

  /** @type {State} */
  function afterLabel(code) {
    return code === 123 ? effects.attempt(attributes, afterAttributes, afterAttributes)(code) : afterAttributes(code);
  }

  /** @type {State} */
  function afterAttributes(code) {
    effects.exit('directiveText');
    return ok(code);
  }
}

/**
 * @this {TokenizeContext}
 * @type {Tokenizer}
 */
function tokenizeLabel(effects, ok, nok) {
  // Always a `[`
  return factoryLabel(effects, ok, nok, 'directiveTextLabel', 'directiveTextLabelMarker', 'directiveTextLabelString');
}

/**
 * @this {TokenizeContext}
 * @type {Tokenizer}
 */
function tokenizeAttributes(effects, ok, nok) {
  // Always a `{`
  return factoryAttributes(effects, ok, nok, 'directiveTextAttributes', 'directiveTextAttributesMarker', 'directiveTextAttribute', 'directiveTextAttributeId', 'directiveTextAttributeClass', 'directiveTextAttributeName', 'directiveTextAttributeInitializerMarker', 'directiveTextAttributeValueLiteral', 'directiveTextAttributeValue', 'directiveTextAttributeValueMarker', 'directiveTextAttributeValueData');
}/**
 * @import {Code, Effects, State, TokenType} from 'micromark-util-types'
 */

import { factorySpace } from 'micromark-factory-space';
import { factoryWhitespace } from 'micromark-factory-whitespace';
import { markdownLineEnding, markdownLineEndingOrSpace, markdownSpace, unicodePunctuation, unicodeWhitespace } from 'micromark-util-character';
/**
 * @param {Effects} effects
 * @param {State} ok
 * @param {State} nok
 * @param {TokenType} attributesType
 * @param {TokenType} attributesMarkerType
 * @param {TokenType} attributeType
 * @param {TokenType} attributeIdType
 * @param {TokenType} attributeClassType
 * @param {TokenType} attributeNameType
 * @param {TokenType} attributeInitializerType
 * @param {TokenType} attributeValueLiteralType
 * @param {TokenType} attributeValueType
 * @param {TokenType} attributeValueMarker
 * @param {TokenType} attributeValueData
 * @param {boolean | undefined} [disallowEol=false]
 */
export function factoryAttributes(effects, ok, nok, attributesType, attributesMarkerType, attributeType, attributeIdType, attributeClassType, attributeNameType, attributeInitializerType, attributeValueLiteralType, attributeValueType, attributeValueMarker, attributeValueData, disallowEol) {
  /** @type {TokenType} */
  let type;
  /** @type {Code | undefined} */
  let marker;
  return start;

  /** @type {State} */
  function start(code) {
    effects.enter(attributesType);
    effects.enter(attributesMarkerType);
    effects.consume(code);
    effects.exit(attributesMarkerType);
    return between;
  }

  /** @type {State} */
  function between(code) {
    if (code === 35) {
      type = attributeIdType;
      return shortcutStart(code);
    }
    if (code === 46) {
      type = attributeClassType;
      return shortcutStart(code);
    }
    if (disallowEol && markdownSpace(code)) {
      return factorySpace(effects, between, "whitespace")(code);
    }
    if (!disallowEol && markdownLineEndingOrSpace(code)) {
      return factoryWhitespace(effects, between)(code);
    }
    if (code === null || markdownLineEnding(code) || unicodeWhitespace(code) || unicodePunctuation(code) && code !== 45 && code !== 95) {
      return end(code);
    }
    effects.enter(attributeType);
    effects.enter(attributeNameType);
    effects.consume(code);
    return name;
  }

  /** @type {State} */
  function shortcutStart(code) {
    // Assume it’s registered.
    const markerType = /** @type {TokenType} */type + 'Marker';
    effects.enter(attributeType);
    effects.enter(type);
    effects.enter(markerType);
    effects.consume(code);
    effects.exit(markerType);
    return shortcutStartAfter;
  }

  /** @type {State} */
  function shortcutStartAfter(code) {
    if (code === null || code === 34 || code === 35 || code === 39 || code === 46 || code === 60 || code === 61 || code === 62 || code === 96 || code === 125 || markdownLineEndingOrSpace(code)) {
      return nok(code);
    }

    // Assume it’s registered.
    const valueType = /** @type {TokenType} */type + 'Value';
    effects.enter(valueType);
    effects.consume(code);
    return shortcut;
  }

  /** @type {State} */
  function shortcut(code) {
    if (code === null || code === 34 || code === 39 || code === 60 || code === 61 || code === 62 || code === 96) {
      return nok(code);
    }
    if (code === 35 || code === 46 || code === 125 || markdownLineEndingOrSpace(code)) {
      // Assume it’s registered.
      const valueType = /** @type {TokenType} */type + 'Value';
      effects.exit(valueType);
      effects.exit(type);
      effects.exit(attributeType);
      return between(code);
    }
    effects.consume(code);
    return shortcut;
  }

  /** @type {State} */
  function name(code) {
    if (code === null || markdownLineEnding(code) || unicodeWhitespace(code) || unicodePunctuation(code) && code !== 45 && code !== 46 && code !== 58 && code !== 95) {
      effects.exit(attributeNameType);
      if (disallowEol && markdownSpace(code)) {
        return factorySpace(effects, nameAfter, "whitespace")(code);
      }
      if (!disallowEol && markdownLineEndingOrSpace(code)) {
        return factoryWhitespace(effects, nameAfter)(code);
      }
      return nameAfter(code);
    }
    effects.consume(code);
    return name;
  }

  /** @type {State} */
  function nameAfter(code) {
    if (code === 61) {
      effects.enter(attributeInitializerType);
      effects.consume(code);
      effects.exit(attributeInitializerType);
      return valueBefore;
    }

    // Attribute w/o value.
    effects.exit(attributeType);
    return between(code);
  }

  /** @type {State} */
  function valueBefore(code) {
    if (code === null || code === 60 || code === 61 || code === 62 || code === 96 || code === 125 || disallowEol && markdownLineEnding(code)) {
      return nok(code);
    }
    if (code === 34 || code === 39) {
      effects.enter(attributeValueLiteralType);
      effects.enter(attributeValueMarker);
      effects.consume(code);
      effects.exit(attributeValueMarker);
      marker = code;
      return valueQuotedStart;
    }
    if (disallowEol && markdownSpace(code)) {
      return factorySpace(effects, valueBefore, "whitespace")(code);
    }
    if (!disallowEol && markdownLineEndingOrSpace(code)) {
      return factoryWhitespace(effects, valueBefore)(code);
    }
    effects.enter(attributeValueType);
    effects.enter(attributeValueData);
    effects.consume(code);
    marker = undefined;
    return valueUnquoted;
  }

  /** @type {State} */
  function valueUnquoted(code) {
    if (code === null || code === 34 || code === 39 || code === 60 || code === 61 || code === 62 || code === 96) {
      return nok(code);
    }
    if (code === 125 || markdownLineEndingOrSpace(code)) {
      effects.exit(attributeValueData);
      effects.exit(attributeValueType);
      effects.exit(attributeType);
      return between(code);
    }
    effects.consume(code);
    return valueUnquoted;
  }

  /** @type {State} */
  function valueQuotedStart(code) {
    if (code === marker) {
      effects.enter(attributeValueMarker);
      effects.consume(code);
      effects.exit(attributeValueMarker);
      effects.exit(attributeValueLiteralType);
      effects.exit(attributeType);
      return valueQuotedAfter;
    }
    effects.enter(attributeValueType);
    return valueQuotedBetween(code);
  }

  /** @type {State} */
  function valueQuotedBetween(code) {
    if (code === marker) {
      effects.exit(attributeValueType);
      return valueQuotedStart(code);
    }
    if (code === null) {
      return nok(code);
    }

    // Note: blank lines can’t exist in content.
    if (markdownLineEnding(code)) {
      return disallowEol ? nok(code) : factoryWhitespace(effects, valueQuotedBetween)(code);
    }
    effects.enter(attributeValueData);
    effects.consume(code);
    return valueQuoted;
  }

  /** @type {State} */
  function valueQuoted(code) {
    if (code === marker || code === null || markdownLineEnding(code)) {
      effects.exit(attributeValueData);
      return valueQuotedBetween(code);
    }
    effects.consume(code);
    return valueQuoted;
  }

  /** @type {State} */
  function valueQuotedAfter(code) {
    return code === 125 || markdownLineEndingOrSpace(code) ? between(code) : end(code);
  }

  /** @type {State} */
  function end(code) {
    if (code === 125) {
      effects.enter(attributesMarkerType);
      effects.consume(code);
      effects.exit(attributesMarkerType);
      effects.exit(attributesType);
      return ok;
    }
    return nok(code);
  }
}/**
 * @import {Code, Effects, State, Token, TokenType} from 'micromark-util-types'
 */

import { markdownLineEnding } from 'micromark-util-character';
// This is a fork of:
// <https://github.com/micromark/micromark/tree/main/packages/micromark-factory-label>
// to allow empty labels, balanced brackets (such as for nested directives),
// text instead of strings, and optionally disallows EOLs.

/**
 * @param {Effects} effects
 * @param {State} ok
 * @param {State} nok
 * @param {TokenType} type
 * @param {TokenType} markerType
 * @param {TokenType} stringType
 * @param {boolean | undefined} [disallowEol=false]
 */
export function factoryLabel(effects, ok, nok, type, markerType, stringType, disallowEol) {
  let size = 0;
  let balance = 0;
  /** @type {Token | undefined} */
  let previous;
  return start;

  /** @type {State} */
  function start(code) {
    effects.enter(type);
    effects.enter(markerType);
    effects.consume(code);
    effects.exit(markerType);
    return afterStart;
  }

  /** @type {State} */
  function afterStart(code) {
    if (code === 93) {
      effects.enter(markerType);
      effects.consume(code);
      effects.exit(markerType);
      effects.exit(type);
      return ok;
    }
    effects.enter(stringType);
    return lineStart(code);
  }

  /** @type {State} */
  function lineStart(code) {
    if (code === 93 && !balance) {
      return atClosingBrace(code);
    }
    const token = effects.enter("chunkText", {
      _contentTypeTextTrailing: true,
      contentType: "text",
      previous
    });
    if (previous) previous.next = token;
    previous = token;
    return data(code);
  }

  /** @type {State} */
  function data(code) {
    if (code === null || size > 999) {
      return nok(code);
    }
    if (code === 91 && ++balance > 32) {
      return nok(code);
    }
    if (code === 93 && !balance--) {
      effects.exit("chunkText");
      return atClosingBrace(code);
    }
    if (markdownLineEnding(code)) {
      if (disallowEol) {
        return nok(code);
      }
      effects.consume(code);
      effects.exit("chunkText");
      return lineStart;
    }
    effects.consume(code);
    return code === 92 ? dataEscape : data;
  }

  /** @type {State} */
  function dataEscape(code) {
    if (code === 91 || code === 92 || code === 93) {
      effects.consume(code);
      size++;
      return data;
    }
    return data(code);
  }

  /** @type {State} */
  function atClosingBrace(code) {
    effects.exit(stringType);
    effects.enter(markerType);
    effects.consume(code);
    effects.exit(markerType);
    effects.exit(type);
    return ok;
  }
}/**
 * @import {Code, Effects, State, TokenizeContext, TokenType} from 'micromark-util-types'
 */

import { markdownLineEnding, unicodePunctuation, unicodeWhitespace } from 'micromark-util-character';
/**
 * @this {TokenizeContext}
 * @param {Effects} effects
 * @param {State} ok
 * @param {State} nok
 * @param {TokenType} type
 */
export function factoryName(effects, ok, nok, type) {
  const self = this;
  return start;

  /** @type {State} */
  function start(code) {
    if (code === null || markdownLineEnding(code) || unicodePunctuation(code) || unicodeWhitespace(code)) {
      return nok(code);
    }
    effects.enter(type);
    effects.consume(code);
    return name;
  }

  /** @type {State} */
  function name(code) {
    if (code === null || markdownLineEnding(code) || unicodeWhitespace(code) || unicodePunctuation(code) && code !== 45 && code !== 95) {
      effects.exit(type);
      return self.previous === 45 || self.previous === 95 ? nok(code) : ok(code);
    }
    effects.consume(code);
    return name;
  }
}/**
 * @import {Directive, HtmlOptions} from 'micromark-extension-directive'
 * @import {CompileContext, Handle as MicromarkHandle, HtmlExtension} from 'micromark-util-types'
 */

import { parseEntities } from 'parse-entities';
const own = {}.hasOwnProperty;

/**
 * Create an extension for `micromark` to support directives when serializing
 * to HTML.
 *
 * @param {HtmlOptions | null | undefined} [options={}]
 *   Configuration (default: `{}`).
 * @returns {HtmlExtension}
 *   Extension for `micromark` that can be passed in `htmlExtensions`, to
 *   support directives when serializing to HTML.
 */
export function directiveHtml(options) {
  const options_ = options || {};
  return {
    enter: {
      directiveContainer() {
        enter.call(this, 'containerDirective');
      },
      directiveContainerAttributes: enterAttributes,
      directiveContainerLabel: enterLabel,
      directiveContainerContent() {
        this.buffer();
      },
      directiveLeaf() {
        enter.call(this, 'leafDirective');
      },
      directiveLeafAttributes: enterAttributes,
      directiveLeafLabel: enterLabel,
      directiveText() {
        enter.call(this, 'textDirective');
      },
      directiveTextAttributes: enterAttributes,
      directiveTextLabel: enterLabel
    },
    exit: {
      directiveContainer: exit,
      directiveContainerAttributeClassValue: exitAttributeClassValue,
      directiveContainerAttributeIdValue: exitAttributeIdValue,
      directiveContainerAttributeName: exitAttributeName,
      directiveContainerAttributeValue: exitAttributeValue,
      directiveContainerAttributes: exitAttributes,
      directiveContainerContent: exitContainerContent,
      directiveContainerFence: exitContainerFence,
      directiveContainerLabel: exitLabel,
      directiveContainerName: exitName,
      directiveLeaf: exit,
      directiveLeafAttributeClassValue: exitAttributeClassValue,
      directiveLeafAttributeIdValue: exitAttributeIdValue,
      directiveLeafAttributeName: exitAttributeName,
      directiveLeafAttributeValue: exitAttributeValue,
      directiveLeafAttributes: exitAttributes,
      directiveLeafLabel: exitLabel,
      directiveLeafName: exitName,
      directiveText: exit,
      directiveTextAttributeClassValue: exitAttributeClassValue,
      directiveTextAttributeIdValue: exitAttributeIdValue,
      directiveTextAttributeName: exitAttributeName,
      directiveTextAttributeValue: exitAttributeValue,
      directiveTextAttributes: exitAttributes,
      directiveTextLabel: exitLabel,
      directiveTextName: exitName
    }
  };

  /**
   * @this {CompileContext}
   * @param {Directive['type']} type
   */
  function enter(type) {
    let stack = this.getData('directiveStack');
    if (!stack) this.setData('directiveStack', stack = []);
    stack.push({
      type,
      name: ''
    });
  }

  /**
   * @this {CompileContext}
   * @type {MicromarkHandle}
   */
  function exitName(token) {
    const stack = this.getData('directiveStack');
    stack[stack.length - 1].name = this.sliceSerialize(token);
  }

  /**
   * @this {CompileContext}
   * @type {MicromarkHandle}
   */
  function enterLabel() {
    this.buffer();
  }

  /**
   * @this {CompileContext}
   * @type {MicromarkHandle}
   */
  function exitLabel() {
    const data = this.resume();
    const stack = this.getData('directiveStack');
    stack[stack.length - 1].label = data;
  }

  /**
   * @this {CompileContext}
   * @type {MicromarkHandle}
   */
  function enterAttributes() {
    this.buffer();
    this.setData('directiveAttributes', []);
  }

  /**
   * @this {CompileContext}
   * @type {MicromarkHandle}
   */
  function exitAttributeIdValue(token) {
    const attributes = this.getData('directiveAttributes');
    attributes.push(['id', parseEntities(this.sliceSerialize(token), {
      attribute: true
    })]);
  }

  /**
   * @this {CompileContext}
   * @type {MicromarkHandle}
   */
  function exitAttributeClassValue(token) {
    const attributes = this.getData('directiveAttributes');
    attributes.push(['class', parseEntities(this.sliceSerialize(token), {
      attribute: true
    })]);
  }

  /**
   * @this {CompileContext}
   * @type {MicromarkHandle}
   */
  function exitAttributeName(token) {
    // Attribute names in CommonMark are significantly limited, so character
    // references can’t exist.
    const attributes = this.getData('directiveAttributes');
    attributes.push([this.sliceSerialize(token), '']);
  }

  /**
   * @this {CompileContext}
   * @type {MicromarkHandle}
   */
  function exitAttributeValue(token) {
    const attributes = this.getData('directiveAttributes');
    attributes[attributes.length - 1][1] = parseEntities(this.sliceSerialize(token), {
      attribute: true
    });
  }

  /**
   * @this {CompileContext}
   * @type {MicromarkHandle}
   */
  function exitAttributes() {
    const stack = this.getData('directiveStack');
    const attributes = this.getData('directiveAttributes');
    /** @type {Record<string, string>} */
    const cleaned = {};
    let index = -1;
    while (++index < attributes.length) {
      const attribute = attributes[index];
      if (attribute[0] === 'class' && cleaned.class) {
        cleaned.class += ' ' + attribute[1];
      } else {
        cleaned[attribute[0]] = attribute[1];
      }
    }
    this.resume();
    this.setData('directiveAttributes');
    stack[stack.length - 1].attributes = cleaned;
  }

  /**
   * @this {CompileContext}
   * @type {MicromarkHandle}
   */
  function exitContainerContent() {
    const data = this.resume();
    const stack = this.getData('directiveStack');
    stack[stack.length - 1].content = data;
  }

  /**
   * @this {CompileContext}
   * @type {MicromarkHandle}
   */
  function exitContainerFence() {
    const stack = this.getData('directiveStack');
    const directive = stack[stack.length - 1];
    if (!directive._fenceCount) directive._fenceCount = 0;
    directive._fenceCount++;
    if (directive._fenceCount === 1) this.setData('slurpOneLineEnding', true);
  }

  /**
   * @this {CompileContext}
   * @type {MicromarkHandle}
   */
  function exit() {
    const stack = this.getData('directiveStack');
    const directive = stack.pop();
    /** @type {boolean | undefined} */
    let found;
    /** @type {boolean | undefined} */
    let result;
    if (own.call(options_, directive.name)) {
      result = options_[directive.name].call(this, directive);
      found = result !== false;
    }
    if (!found && own.call(options_, '*')) {
      result = options_['*'].call(this, directive);
      found = result !== false;
    }
    if (!found && directive.type !== 'textDirective') {
      this.setData('slurpOneLineEnding', true);
    }
  }
}/**
 * @import {Extension} from 'micromark-util-types'
 */

import { directiveContainer } from './directive-container.js';
import { directiveLeaf } from './directive-leaf.js';
import { directiveText } from './directive-text.js';

/**
 * Create an extension for `micromark` to enable directive syntax.
 *
 * @returns {Extension}
 *   Extension for `micromark` that can be passed in `extensions`, to
 *   enable directive syntax.
 */
export function directive() {
  return {
    text: {
      [58]: directiveText
    },
    flow: {
      [58]: [directiveContainer, directiveLeaf]
    }
  };
}{
  "author": "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)",
  "bugs": "https://github.com/micromark/micromark-extension-directive/issues",
  "contributors": [
    "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)"
  ],
  "dependencies": {
    "devlop": "^1.0.0",
    "micromark-factory-space": "^2.0.0",
    "micromark-factory-whitespace": "^2.0.0",
    "micromark-util-character": "^2.0.0",
    "micromark-util-symbol": "^2.0.0",
    "micromark-util-types": "^2.0.0",
    "parse-entities": "^4.0.0"
  },
  "description": "micromark extension to support generic directives (`:cite[smith04]`)",
  "devDependencies": {
    "@types/node": "^22.0.0",
    "c8": "^10.0.0",
    "html-void-elements": "^3.0.0",
    "micromark": "^4.0.0",
    "micromark-build": "^2.0.0",
    "prettier": "^3.0.0",
    "remark-cli": "^12.0.0",
    "remark-preset-wooorm": "^11.0.0",
    "type-coverage": "^2.0.0",
    "typescript": "^5.0.0",
    "xo": "^0.60.0"
  },
  "exports": {
    "development": "./dev/index.js",
    "default": "./index.js"
  },
  "files": [
    "dev/",
    "index.d.ts",
    "index.js",
    "lib/"
  ],
  "funding": {
    "type": "opencollective",
    "url": "https://opencollective.com/unified"
  },
  "keywords": [
    "container",
    "directive",
    "extension",
    "generic",
    "markdown",
    "micromark-extension",
    "micromark",
    "unified"
  ],
  "license": "MIT",
  "name": "micromark-extension-directive",
  "prettier": {
    "bracketSpacing": false,
    "semi": false,
    "singleQuote": true,
    "tabWidth": 2,
    "trailingComma": "none",
    "useTabs": false
  },
  "remarkConfig": {
    "plugins": [
      "remark-preset-wooorm"
    ]
  },
  "repository": "micromark/micromark-extension-directive",
  "scripts": {
    "build": "tsc --build --clean && tsc --build && type-coverage && micromark-build",
    "format": "remark --frail --output --quiet -- . && prettier --log-level warn --write -- . && xo --fix",
    "test-api-prod": "node --conditions production test/index.js",
    "test-api-dev": "node --conditions development test/index.js",
    "test-api": "npm run test-api-dev && npm run test-api-prod",
    "test-coverage": "c8 --100 --reporter lcov -- npm run test-api",
    "test": "npm run build && npm run format && npm run test-coverage"
  },
  "sideEffects": false,
  "typeCoverage": {
    "atLeast": 100,
    "strict": true
  },
  "type": "module",
  "version": "4.0.0",
  "xo": {
    "overrides": [
      {
        "files": [
          "**/*.d.ts"
        ],
        "rules": {
          "@typescript-eslint/array-type": [
            "error",
            {
              "default": "generic"
            }
          ],
          "@typescript-eslint/ban-types": [
            "error",
            {
              "extendDefaults": true
            }
          ],
          "@typescript-eslint/consistent-indexed-object-style": [
            "error",
            "index-signature"
          ],
          "@typescript-eslint/consistent-type-definitions": [
            "error",
            "interface"
          ]
        }
      }
    ],
    "prettier": true,
    "rules": {
      "logical-assignment-operators": "off",
      "max-params": "off",
      "unicorn/no-this-assignment": "off",
      "unicorn/prefer-at": "off"
    }
  }
}
// Note: types are exported from `dev/index.d.ts`.
export { gfmFootnote } from './lib/syntax.js';
export { gfmFootnoteHtml, defaultBackLabel } from './lib/html.js';/**
 * @import {HtmlOptions as Options} from 'micromark-extension-gfm-footnote'
 * @import {HtmlExtension} from 'micromark-util-types'
 */

import { normalizeIdentifier } from 'micromark-util-normalize-identifier';
import { sanitizeUri } from 'micromark-util-sanitize-uri';
const own = {}.hasOwnProperty;

/** @type {Options} */
const emptyOptions = {};

/**
 * Generate the default label that GitHub uses on backreferences.
 *
 * @param {number} referenceIndex
 *   Index of the definition in the order that they are first referenced,
 *   0-indexed.
 * @param {number} rereferenceIndex
 *   Index of calls to the same definition, 0-indexed.
 * @returns {string}
 *   Default label.
 */
export function defaultBackLabel(referenceIndex, rereferenceIndex) {
  return 'Back to reference ' + (referenceIndex + 1) + (rereferenceIndex > 1 ? '-' + rereferenceIndex : '');
}

/**
 * Create an extension for `micromark` to support GFM footnotes when
 * serializing to HTML.
 *
 * @param {Options | null | undefined} [options={}]
 *   Configuration (optional).
 * @returns {HtmlExtension}
 *   Extension for `micromark` that can be passed in `htmlExtensions` to
 *   support GFM footnotes when serializing to HTML.
 */
export function gfmFootnoteHtml(options) {
  const config = options || emptyOptions;
  const label = config.label || 'Footnotes';
  const labelTagName = config.labelTagName || 'h2';
  const labelAttributes = config.labelAttributes === null || config.labelAttributes === undefined ? 'class="sr-only"' : config.labelAttributes;
  const backLabel = config.backLabel || defaultBackLabel;
  const clobberPrefix = config.clobberPrefix === null || config.clobberPrefix === undefined ? 'user-content-' : config.clobberPrefix;
  return {
    enter: {
      gfmFootnoteDefinition() {
        const stack = this.getData('tightStack');
        stack.push(false);
      },
      gfmFootnoteDefinitionLabelString() {
        this.buffer();
      },
      gfmFootnoteCallString() {
        this.buffer();
      }
    },
    exit: {
      gfmFootnoteDefinition() {
        let definitions = this.getData('gfmFootnoteDefinitions');
        const footnoteStack = this.getData('gfmFootnoteDefinitionStack');
        const tightStack = this.getData('tightStack');
        const current = footnoteStack.pop();
        const value = this.resume();
        if (!definitions) {
          this.setData('gfmFootnoteDefinitions', definitions = {});
        }
        if (!own.call(definitions, current)) definitions[current] = value;
        tightStack.pop();
        this.setData('slurpOneLineEnding', true);
        // “Hack” to prevent a line ending from showing up if we’re in a definition in
        // an empty list item.
        this.setData('lastWasTag');
      },
      gfmFootnoteDefinitionLabelString(token) {
        let footnoteStack = this.getData('gfmFootnoteDefinitionStack');
        if (!footnoteStack) {
          this.setData('gfmFootnoteDefinitionStack', footnoteStack = []);
        }
        footnoteStack.push(normalizeIdentifier(this.sliceSerialize(token)));
        this.resume(); // Drop the label.
        this.buffer(); // Get ready for a value.
      },
      gfmFootnoteCallString(token) {
        let calls = this.getData('gfmFootnoteCallOrder');
        let counts = this.getData('gfmFootnoteCallCounts');
        const id = normalizeIdentifier(this.sliceSerialize(token));
        /** @type {number} */
        let counter;
        this.resume();
        if (!calls) this.setData('gfmFootnoteCallOrder', calls = []);
        if (!counts) this.setData('gfmFootnoteCallCounts', counts = {});
        const index = calls.indexOf(id);
        const safeId = sanitizeUri(id.toLowerCase());
        if (index === -1) {
          calls.push(id);
          counts[id] = 1;
          counter = calls.length;
        } else {
          counts[id]++;
          counter = index + 1;
        }
        const reuseCounter = counts[id];
        this.tag('<sup><a href="#' + clobberPrefix + 'fn-' + safeId + '" id="' + clobberPrefix + 'fnref-' + safeId + (reuseCounter > 1 ? '-' + reuseCounter : '') + '" data-footnote-ref="" aria-describedby="footnote-label">' + String(counter) + '</a></sup>');
      },
      null() {
        const calls = this.getData('gfmFootnoteCallOrder') || [];
        const counts = this.getData('gfmFootnoteCallCounts') || {};
        const definitions = this.getData('gfmFootnoteDefinitions') || {};
        let index = -1;
        if (calls.length > 0) {
          this.lineEndingIfNeeded();
          this.tag('<section data-footnotes="" class="footnotes"><' + labelTagName + ' id="footnote-label"' + (labelAttributes ? ' ' + labelAttributes : '') + '>');
          this.raw(this.encode(label));
          this.tag('</' + labelTagName + '>');
          this.lineEndingIfNeeded();
          this.tag('<ol>');
        }
        while (++index < calls.length) {
          // Called definitions are always defined.
          const id = calls[index];
          const safeId = sanitizeUri(id.toLowerCase());
          let referenceIndex = 0;
          /** @type {Array<string>} */
          const references = [];
          while (++referenceIndex <= counts[id]) {
            references.push('<a href="#' + clobberPrefix + 'fnref-' + safeId + (referenceIndex > 1 ? '-' + referenceIndex : '') + '" data-footnote-backref="" aria-label="' + this.encode(typeof backLabel === 'string' ? backLabel : backLabel(index, referenceIndex)) + '" class="data-footnote-backref">↩' + (referenceIndex > 1 ? '<sup>' + referenceIndex + '</sup>' : '') + '</a>');
          }
          const reference = references.join(' ');
          let injected = false;
          this.lineEndingIfNeeded();
          this.tag('<li id="' + clobberPrefix + 'fn-' + safeId + '">');
          this.lineEndingIfNeeded();
          this.tag(definitions[id].replace(/<\/p>(?:\r?\n|\r)?$/, function ($0) {
            injected = true;
            return ' ' + reference + $0;
          }));
          if (!injected) {
            this.lineEndingIfNeeded();
            this.tag(reference);
          }
          this.lineEndingIfNeeded();
          this.tag('</li>');
        }
        if (calls.length > 0) {
          this.lineEndingIfNeeded();
          this.tag('</ol>');
          this.lineEndingIfNeeded();
          this.tag('</section>');
        }
      }
    }
  };
}/**
 * @import {Event, Exiter, Extension, Resolver, State, Token, TokenizeContext, Tokenizer} from 'micromark-util-types'
 */

import { blankLine } from 'micromark-core-commonmark';
import { factorySpace } from 'micromark-factory-space';
import { markdownLineEndingOrSpace } from 'micromark-util-character';
import { normalizeIdentifier } from 'micromark-util-normalize-identifier';
const indent = {
  tokenize: tokenizeIndent,
  partial: true
};

// To do: micromark should support a `_hiddenGfmFootnoteSupport`, which only
// affects label start (image).
// That will let us drop `tokenizePotentialGfmFootnote*`.
// It currently has a `_hiddenFootnoteSupport`, which affects that and more.
// That can be removed when `micromark-extension-footnote` is archived.

/**
 * Create an extension for `micromark` to enable GFM footnote syntax.
 *
 * @returns {Extension}
 *   Extension for `micromark` that can be passed in `extensions` to
 *   enable GFM footnote syntax.
 */
export function gfmFootnote() {
  /** @type {Extension} */
  return {
    document: {
      [91]: {
        name: 'gfmFootnoteDefinition',
        tokenize: tokenizeDefinitionStart,
        continuation: {
          tokenize: tokenizeDefinitionContinuation
        },
        exit: gfmFootnoteDefinitionEnd
      }
    },
    text: {
      [91]: {
        name: 'gfmFootnoteCall',
        tokenize: tokenizeGfmFootnoteCall
      },
      [93]: {
        name: 'gfmPotentialFootnoteCall',
        add: 'after',
        tokenize: tokenizePotentialGfmFootnoteCall,
        resolveTo: resolveToPotentialGfmFootnoteCall
      }
    }
  };
}

// To do: remove after micromark update.
/**
 * @this {TokenizeContext}
 * @type {Tokenizer}
 */
function tokenizePotentialGfmFootnoteCall(effects, ok, nok) {
  const self = this;
  let index = self.events.length;
  const defined = self.parser.gfmFootnotes || (self.parser.gfmFootnotes = []);
  /** @type {Token} */
  let labelStart;

  // Find an opening.
  while (index--) {
    const token = self.events[index][1];
    if (token.type === "labelImage") {
      labelStart = token;
      break;
    }

    // Exit if we’ve walked far enough.
    if (token.type === 'gfmFootnoteCall' || token.type === "labelLink" || token.type === "label" || token.type === "image" || token.type === "link") {
      break;
    }
  }
  return start;

  /**
   * @type {State}
   */
  function start(code) {
    if (!labelStart || !labelStart._balanced) {
      return nok(code);
    }
    const id = normalizeIdentifier(self.sliceSerialize({
      start: labelStart.end,
      end: self.now()
    }));
    if (id.codePointAt(0) !== 94 || !defined.includes(id.slice(1))) {
      return nok(code);
    }
    effects.enter('gfmFootnoteCallLabelMarker');
    effects.consume(code);
    effects.exit('gfmFootnoteCallLabelMarker');
    return ok(code);
  }
}

// To do: remove after micromark update.
/** @type {Resolver} */
function resolveToPotentialGfmFootnoteCall(events, context) {
  let index = events.length;
  /** @type {Token | undefined} */
  let labelStart;

  // Find an opening.
  while (index--) {
    if (events[index][1].type === "labelImage" && events[index][0] === 'enter') {
      labelStart = events[index][1];
      break;
    }
  }
  // Change the `labelImageMarker` to a `data`.
  events[index + 1][1].type = "data";
  events[index + 3][1].type = 'gfmFootnoteCallLabelMarker';

  // The whole (without `!`):
  /** @type {Token} */
  const call = {
    type: 'gfmFootnoteCall',
    start: Object.assign({}, events[index + 3][1].start),
    end: Object.assign({}, events[events.length - 1][1].end)
  };
  // The `^` marker
  /** @type {Token} */
  const marker = {
    type: 'gfmFootnoteCallMarker',
    start: Object.assign({}, events[index + 3][1].end),
    end: Object.assign({}, events[index + 3][1].end)
  };
  // Increment the end 1 character.
  marker.end.column++;
  marker.end.offset++;
  marker.end._bufferIndex++;
  /** @type {Token} */
  const string = {
    type: 'gfmFootnoteCallString',
    start: Object.assign({}, marker.end),
    end: Object.assign({}, events[events.length - 1][1].start)
  };
  /** @type {Token} */
  const chunk = {
    type: "chunkString",
    contentType: 'string',
    start: Object.assign({}, string.start),
    end: Object.assign({}, string.end)
  };

  /** @type {Array<Event>} */
  const replacement = [
  // Take the `labelImageMarker` (now `data`, the `!`)
  events[index + 1], events[index + 2], ['enter', call, context],
  // The `[`
  events[index + 3], events[index + 4],
  // The `^`.
  ['enter', marker, context], ['exit', marker, context],
  // Everything in between.
  ['enter', string, context], ['enter', chunk, context], ['exit', chunk, context], ['exit', string, context],
  // The ending (`]`, properly parsed and labelled).
  events[events.length - 2], events[events.length - 1], ['exit', call, context]];
  events.splice(index, events.length - index + 1, ...replacement);
  return events;
}

/**
 * @this {TokenizeContext}
 * @type {Tokenizer}
 */
function tokenizeGfmFootnoteCall(effects, ok, nok) {
  const self = this;
  const defined = self.parser.gfmFootnotes || (self.parser.gfmFootnotes = []);
  let size = 0;
  /** @type {boolean} */
  let data;

  // Note: the implementation of `markdown-rs` is different, because it houses
  // core *and* extensions in one project.
  // Therefore, it can include footnote logic inside `label-end`.
  // We can’t do that, but luckily, we can parse footnotes in a simpler way than
  // needed for labels.
  return start;

  /**
   * Start of footnote label.
   *
   * ```markdown
   * > | a [^b] c
   *       ^
   * ```
   *
   * @type {State}
   */
  function start(code) {
    effects.enter('gfmFootnoteCall');
    effects.enter('gfmFootnoteCallLabelMarker');
    effects.consume(code);
    effects.exit('gfmFootnoteCallLabelMarker');
    return callStart;
  }

  /**
   * After `[`, at `^`.
   *
   * ```markdown
   * > | a [^b] c
   *        ^
   * ```
   *
   * @type {State}
   */
  function callStart(code) {
    if (code !== 94) return nok(code);
    effects.enter('gfmFootnoteCallMarker');
    effects.consume(code);
    effects.exit('gfmFootnoteCallMarker');
    effects.enter('gfmFootnoteCallString');
    effects.enter('chunkString').contentType = 'string';
    return callData;
  }

  /**
   * In label.
   *
   * ```markdown
   * > | a [^b] c
   *         ^
   * ```
   *
   * @type {State}
   */
  function callData(code) {
    if (
    // Too long.
    size > 999 ||
    // Closing brace with nothing.
    code === 93 && !data ||
    // Space or tab is not supported by GFM for some reason.
    // `\n` and `[` not being supported makes sense.
    code === null || code === 91 || markdownLineEndingOrSpace(code)) {
      return nok(code);
    }
    if (code === 93) {
      effects.exit('chunkString');
      const token = effects.exit('gfmFootnoteCallString');
      if (!defined.includes(normalizeIdentifier(self.sliceSerialize(token)))) {
        return nok(code);
      }
      effects.enter('gfmFootnoteCallLabelMarker');
      effects.consume(code);
      effects.exit('gfmFootnoteCallLabelMarker');
      effects.exit('gfmFootnoteCall');
      return ok;
    }
    if (!markdownLineEndingOrSpace(code)) {
      data = true;
    }
    size++;
    effects.consume(code);
    return code === 92 ? callEscape : callData;
  }

  /**
   * On character after escape.
   *
   * ```markdown
   * > | a [^b\c] d
   *           ^
   * ```
   *
   * @type {State}
   */
  function callEscape(code) {
    if (code === 91 || code === 92 || code === 93) {
      effects.consume(code);
      size++;
      return callData;
    }
    return callData(code);
  }
}

/**
 * @this {TokenizeContext}
 * @type {Tokenizer}
 */
function tokenizeDefinitionStart(effects, ok, nok) {
  const self = this;
  const defined = self.parser.gfmFootnotes || (self.parser.gfmFootnotes = []);
  /** @type {string} */
  let identifier;
  let size = 0;
  /** @type {boolean | undefined} */
  let data;
  return start;

  /**
   * Start of GFM footnote definition.
   *
   * ```markdown
   * > | [^a]: b
   *     ^
   * ```
   *
   * @type {State}
   */
  function start(code) {
    effects.enter('gfmFootnoteDefinition')._container = true;
    effects.enter('gfmFootnoteDefinitionLabel');
    effects.enter('gfmFootnoteDefinitionLabelMarker');
    effects.consume(code);
    effects.exit('gfmFootnoteDefinitionLabelMarker');
    return labelAtMarker;
  }

  /**
   * In label, at caret.
   *
   * ```markdown
   * > | [^a]: b
   *      ^
   * ```
   *
   * @type {State}
   */
  function labelAtMarker(code) {
    if (code === 94) {
      effects.enter('gfmFootnoteDefinitionMarker');
      effects.consume(code);
      effects.exit('gfmFootnoteDefinitionMarker');
      effects.enter('gfmFootnoteDefinitionLabelString');
      effects.enter('chunkString').contentType = 'string';
      return labelInside;
    }
    return nok(code);
  }

  /**
   * In label.
   *
   * > 👉 **Note**: `cmark-gfm` prevents whitespace from occurring in footnote
   * > definition labels.
   *
   * ```markdown
   * > | [^a]: b
   *       ^
   * ```
   *
   * @type {State}
   */
  function labelInside(code) {
    if (
    // Too long.
    size > 999 ||
    // Closing brace with nothing.
    code === 93 && !data ||
    // Space or tab is not supported by GFM for some reason.
    // `\n` and `[` not being supported makes sense.
    code === null || code === 91 || markdownLineEndingOrSpace(code)) {
      return nok(code);
    }
    if (code === 93) {
      effects.exit('chunkString');
      const token = effects.exit('gfmFootnoteDefinitionLabelString');
      identifier = normalizeIdentifier(self.sliceSerialize(token));
      effects.enter('gfmFootnoteDefinitionLabelMarker');
      effects.consume(code);
      effects.exit('gfmFootnoteDefinitionLabelMarker');
      effects.exit('gfmFootnoteDefinitionLabel');
      return labelAfter;
    }
    if (!markdownLineEndingOrSpace(code)) {
      data = true;
    }
    size++;
    effects.consume(code);
    return code === 92 ? labelEscape : labelInside;
  }

  /**
   * After `\`, at a special character.
   *
   * > 👉 **Note**: `cmark-gfm` currently does not support escaped brackets:
   * > <https://github.com/github/cmark-gfm/issues/240>
   *
   * ```markdown
   * > | [^a\*b]: c
   *         ^
   * ```
   *
   * @type {State}
   */
  function labelEscape(code) {
    if (code === 91 || code === 92 || code === 93) {
      effects.consume(code);
      size++;
      return labelInside;
    }
    return labelInside(code);
  }

  /**
   * After definition label.
   *
   * ```markdown
   * > | [^a]: b
   *         ^
   * ```
   *
   * @type {State}
   */
  function labelAfter(code) {
    if (code === 58) {
      effects.enter('definitionMarker');
      effects.consume(code);
      effects.exit('definitionMarker');
      if (!defined.includes(identifier)) {
        defined.push(identifier);
      }

      // Any whitespace after the marker is eaten, forming indented code
      // is not possible.
      // No space is also fine, just like a block quote marker.
      return factorySpace(effects, whitespaceAfter, 'gfmFootnoteDefinitionWhitespace');
    }
    return nok(code);
  }

  /**
   * After definition prefix.
   *
   * ```markdown
   * > | [^a]: b
   *           ^
   * ```
   *
   * @type {State}
   */
  function whitespaceAfter(code) {
    // `markdown-rs` has a wrapping token for the prefix that is closed here.
    return ok(code);
  }
}

/**
 * @this {TokenizeContext}
 * @type {Tokenizer}
 */
function tokenizeDefinitionContinuation(effects, ok, nok) {
  /// Start of footnote definition continuation.
  ///
  /// ```markdown
  ///   | [^a]: b
  /// > |     c
  ///     ^
  /// ```
  //
  // Either a blank line, which is okay, or an indented thing.
  return effects.check(blankLine, ok, effects.attempt(indent, ok, nok));
}

/** @type {Exiter} */
function gfmFootnoteDefinitionEnd(effects) {
  effects.exit('gfmFootnoteDefinition');
}

/**
 * @this {TokenizeContext}
 * @type {Tokenizer}
 */
function tokenizeIndent(effects, ok, nok) {
  const self = this;
  return factorySpace(effects, afterPrefix, 'gfmFootnoteDefinitionIndent', 4 + 1);

  /**
   * @type {State}
   */
  function afterPrefix(code) {
    const tail = self.events[self.events.length - 1];
    return tail && tail[1].type === 'gfmFootnoteDefinitionIndent' && tail[2].sliceSerialize(tail[1], true).length === 4 ? ok(code) : nok(code);
  }
}{
  "name": "micromark-extension-gfm-footnote",
  "version": "2.1.0",
  "description": "micromark extension to support GFM footnotes",
  "license": "MIT",
  "keywords": [
    "micromark",
    "micromark-extension",
    "gfm",
    "footnote",
    "note",
    "definition",
    "markdown",
    "unified"
  ],
  "repository": "micromark/micromark-extension-gfm-footnote",
  "bugs": "https://github.com/micromark/micromark-extension-gfm-footnote/issues",
  "funding": {
    "type": "opencollective",
    "url": "https://opencollective.com/unified"
  },
  "author": "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)",
  "contributors": [
    "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)"
  ],
  "sideEffects": false,
  "type": "module",
  "files": [
    "dev/",
    "lib/",
    "index.d.ts",
    "index.js"
  ],
  "exports": {
    "development": "./dev/index.js",
    "default": "./index.js"
  },
  "dependencies": {
    "devlop": "^1.0.0",
    "micromark-core-commonmark": "^2.0.0",
    "micromark-factory-space": "^2.0.0",
    "micromark-util-character": "^2.0.0",
    "micromark-util-normalize-identifier": "^2.0.0",
    "micromark-util-sanitize-uri": "^2.0.0",
    "micromark-util-symbol": "^2.0.0",
    "micromark-util-types": "^2.0.0"
  },
  "devDependencies": {
    "@types/node": "^20.0.0",
    "c8": "^10.0.0",
    "create-gfm-fixtures": "^1.0.0",
    "micromark": "^4.0.0",
    "micromark-build": "^2.0.0",
    "prettier": "^3.0.0",
    "remark-cli": "^12.0.0",
    "remark-preset-wooorm": "^10.0.0",
    "type-coverage": "^2.0.0",
    "typescript": "^5.0.0",
    "xo": "^0.58.0"
  },
  "scripts": {
    "prepack": "npm run build && npm run format",
    "build": "tsc --build --clean && tsc --build && type-coverage && micromark-build",
    "format": "remark . -qfo && prettier . -w --log-level warn && xo --fix",
    "test-api-prod": "node --conditions production test/index.js",
    "test-api-dev": "node --conditions development test/index.js",
    "test-api": "npm run test-api-dev && npm run test-api-prod",
    "test-coverage": "c8 --100 --reporter lcov npm run test-api",
    "test": "npm run build && npm run format && npm run test-coverage"
  },
  "prettier": {
    "bracketSpacing": false,
    "semi": false,
    "singleQuote": true,
    "tabWidth": 2,
    "trailingComma": "none",
    "useTabs": false
  },
  "remarkConfig": {
    "plugins": [
      "remark-preset-wooorm"
    ]
  },
  "typeCoverage": {
    "atLeast": 100,
    "detail": true,
    "ignoreCatch": true,
    "strict": true
  },
  "xo": {
    "prettier": true,
    "rules": {
      "logical-assignment-operators": "off",
      "unicorn/no-this-assignment": "off",
      "unicorn/prefer-at": "off",
      "unicorn/prefer-string-replace-all": "off"
    },
    "overrides": [
      {
        "files": [
          "**/*.d.ts"
        ],
        "rules": {
          "@typescript-eslint/array-type": [
            "error",
            {
              "default": "generic"
            }
          ],
          "@typescript-eslint/ban-types": [
            "error",
            {
              "extendDefaults": true
            }
          ],
          "@typescript-eslint/consistent-type-definitions": [
            "error",
            "interface"
          ]
        }
      },
      {
        "files": [
          "test/**/*.js"
        ],
        "rules": {
          "no-await-in-loop": 0
        }
      }
    ]
  }
}
// Note: more types exposed from `index.d.ts`.
export { gfmStrikethroughHtml } from './lib/html.js';
export { gfmStrikethrough } from './lib/syntax.js';/**
 * @import {HtmlExtension} from 'micromark-util-types'
 */

/**
 * Create an HTML extension for `micromark` to support GFM strikethrough when
 * serializing to HTML.
 *
 * @returns {HtmlExtension}
 *   Extension for `micromark` that can be passed in `htmlExtensions`, to
 *   support GFM strikethrough when serializing to HTML.
 */
export function gfmStrikethroughHtml() {
  return {
    enter: {
      strikethrough() {
        this.tag('<del>');
      }
    },
    exit: {
      strikethrough() {
        this.tag('</del>');
      }
    }
  };
}/**
 * @import {Options} from 'micromark-extension-gfm-strikethrough'
 * @import {Event, Extension, Resolver, State, Token, TokenizeContext, Tokenizer} from 'micromark-util-types'
 */

import { splice } from 'micromark-util-chunked';
import { classifyCharacter } from 'micromark-util-classify-character';
import { resolveAll } from 'micromark-util-resolve-all';
/**
 * Create an extension for `micromark` to enable GFM strikethrough syntax.
 *
 * @param {Options | null | undefined} [options={}]
 *   Configuration.
 * @returns {Extension}
 *   Extension for `micromark` that can be passed in `extensions`, to
 *   enable GFM strikethrough syntax.
 */
export function gfmStrikethrough(options) {
  const options_ = options || {};
  let single = options_.singleTilde;
  const tokenizer = {
    name: 'strikethrough',
    tokenize: tokenizeStrikethrough,
    resolveAll: resolveAllStrikethrough
  };
  if (single === null || single === undefined) {
    single = true;
  }
  return {
    text: {
      [126]: tokenizer
    },
    insideSpan: {
      null: [tokenizer]
    },
    attentionMarkers: {
      null: [126]
    }
  };

  /**
   * Take events and resolve strikethrough.
   *
   * @type {Resolver}
   */
  function resolveAllStrikethrough(events, context) {
    let index = -1;

    // Walk through all events.
    while (++index < events.length) {
      // Find a token that can close.
      if (events[index][0] === 'enter' && events[index][1].type === 'strikethroughSequenceTemporary' && events[index][1]._close) {
        let open = index;

        // Now walk back to find an opener.
        while (open--) {
          // Find a token that can open the closer.
          if (events[open][0] === 'exit' && events[open][1].type === 'strikethroughSequenceTemporary' && events[open][1]._open &&
          // If the sizes are the same:
          events[index][1].end.offset - events[index][1].start.offset === events[open][1].end.offset - events[open][1].start.offset) {
            events[index][1].type = 'strikethroughSequence';
            events[open][1].type = 'strikethroughSequence';

            /** @type {Token} */
            const strikethrough = {
              type: 'strikethrough',
              start: Object.assign({}, events[open][1].start),
              end: Object.assign({}, events[index][1].end)
            };

            /** @type {Token} */
            const text = {
              type: 'strikethroughText',
              start: Object.assign({}, events[open][1].end),
              end: Object.assign({}, events[index][1].start)
            };

            // Opening.
            /** @type {Array<Event>} */
            const nextEvents = [['enter', strikethrough, context], ['enter', events[open][1], context], ['exit', events[open][1], context], ['enter', text, context]];
            const insideSpan = context.parser.constructs.insideSpan.null;
            if (insideSpan) {
              // Between.
              splice(nextEvents, nextEvents.length, 0, resolveAll(insideSpan, events.slice(open + 1, index), context));
            }

            // Closing.
            splice(nextEvents, nextEvents.length, 0, [['exit', text, context], ['enter', events[index][1], context], ['exit', events[index][1], context], ['exit', strikethrough, context]]);
            splice(events, open - 1, index - open + 3, nextEvents);
            index = open + nextEvents.length - 2;
            break;
          }
        }
      }
    }
    index = -1;
    while (++index < events.length) {
      if (events[index][1].type === 'strikethroughSequenceTemporary') {
        events[index][1].type = "data";
      }
    }
    return events;
  }

  /**
   * @this {TokenizeContext}
   * @type {Tokenizer}
   */
  function tokenizeStrikethrough(effects, ok, nok) {
    const previous = this.previous;
    const events = this.events;
    let size = 0;
    return start;

    /** @type {State} */
    function start(code) {
      if (previous === 126 && events[events.length - 1][1].type !== "characterEscape") {
        return nok(code);
      }
      effects.enter('strikethroughSequenceTemporary');
      return more(code);
    }

    /** @type {State} */
    function more(code) {
      const before = classifyCharacter(previous);
      if (code === 126) {
        // If this is the third marker, exit.
        if (size > 1) return nok(code);
        effects.consume(code);
        size++;
        return more;
      }
      if (size < 2 && !single) return nok(code);
      const token = effects.exit('strikethroughSequenceTemporary');
      const after = classifyCharacter(code);
      token._open = !after || after === 2 && Boolean(before);
      token._close = !before || before === 2 && Boolean(after);
      return ok(code);
    }
  }
}{
  "name": "micromark-extension-gfm-strikethrough",
  "version": "2.1.0",
  "description": "micromark extension to support GFM strikethrough",
  "license": "MIT",
  "keywords": [
    "micromark",
    "micromark-extension",
    "strikethrough",
    "strike",
    "through",
    "del",
    "delete",
    "deletion",
    "gfm",
    "markdown",
    "unified"
  ],
  "repository": "micromark/micromark-extension-gfm-strikethrough",
  "bugs": "https://github.com/micromark/micromark-extension-gfm-strikethrough/issues",
  "funding": {
    "type": "opencollective",
    "url": "https://opencollective.com/unified"
  },
  "author": "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)",
  "contributors": [
    "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)"
  ],
  "sideEffects": false,
  "type": "module",
  "files": [
    "dev/",
    "lib/",
    "index.d.ts",
    "index.js"
  ],
  "exports": {
    "development": "./dev/index.js",
    "default": "./index.js"
  },
  "dependencies": {
    "devlop": "^1.0.0",
    "micromark-util-chunked": "^2.0.0",
    "micromark-util-classify-character": "^2.0.0",
    "micromark-util-resolve-all": "^2.0.0",
    "micromark-util-symbol": "^2.0.0",
    "micromark-util-types": "^2.0.0"
  },
  "devDependencies": {
    "@types/node": "^20.0.0",
    "c8": "^10.0.0",
    "create-gfm-fixtures": "^1.0.0",
    "micromark": "^4.0.0",
    "micromark-build": "^2.0.0",
    "prettier": "^3.0.0",
    "remark-cli": "^12.0.0",
    "remark-preset-wooorm": "^10.0.0",
    "tape": "^5.0.0",
    "type-coverage": "^2.0.0",
    "typescript": "^5.0.0",
    "xo": "^0.58.0"
  },
  "scripts": {
    "prepack": "npm run build && npm run format",
    "build": "tsc --build --clean && tsc --build && type-coverage && micromark-build",
    "format": "remark . -qfo && prettier . -w --log-level warn && xo --fix",
    "test-api-prod": "node --conditions production test/index.js",
    "test-api-dev": "node --conditions development test/index.js",
    "test-api": "npm run test-api-dev && npm run test-api-prod",
    "test-coverage": "c8 --100 --reporter lcov npm run test-api",
    "test": "npm run build && npm run format && npm run test-coverage"
  },
  "prettier": {
    "bracketSpacing": false,
    "semi": false,
    "singleQuote": true,
    "tabWidth": 2,
    "trailingComma": "none",
    "useTabs": false
  },
  "remarkConfig": {
    "plugins": [
      "remark-preset-wooorm"
    ]
  },
  "typeCoverage": {
    "atLeast": 100,
    "detail": true,
    "ignoreCatch": true,
    "strict": true
  },
  "xo": {
    "prettier": true,
    "rules": {
      "logical-assignment-operators": "off",
      "max-depth": "off",
      "unicorn/prefer-at": "off"
    },
    "overrides": [
      {
        "files": "**/*.ts",
        "rules": {
          "@typescript-eslint/array-type": [
            "error",
            {
              "default": "generic"
            }
          ],
          "@typescript-eslint/ban-types": [
            "error",
            {
              "extendDefaults": true
            }
          ],
          "@typescript-eslint/consistent-type-definitions": [
            "error",
            "interface"
          ]
        }
      },
      {
        "files": "test/**/*.js",
        "rules": {
          "no-await-in-loop": 0
        }
      }
    ]
  }
}
export { gfmTableHtml } from './lib/html.js';
export { gfmTable } from './lib/syntax.js';/**
 * @import {Event} from 'micromark-util-types'
 */

// Port of `edit_map.rs` from `markdown-rs`.
// This should move to `markdown-js` later.

// Deal with several changes in events, batching them together.
//
// Preferably, changes should be kept to a minimum.
// Sometimes, it’s needed to change the list of events, because parsing can be
// messy, and it helps to expose a cleaner interface of events to the compiler
// and other users.
// It can also help to merge many adjacent similar events.
// And, in other cases, it’s needed to parse subcontent: pass some events
// through another tokenizer and inject the result.

/**
 * @typedef {[number, number, Array<Event>]} Change
 * @typedef {[number, number, number]} Jump
 */

/**
 * Tracks a bunch of edits.
 */
export class EditMap {
  /**
   * Create a new edit map.
   */
  constructor() {
    /**
     * Record of changes.
     *
     * @type {Array<Change>}
     */
    this.map = [];
  }

  /**
   * Create an edit: a remove and/or add at a certain place.
   *
   * @param {number} index
   * @param {number} remove
   * @param {Array<Event>} add
   * @returns {undefined}
   */
  add(index, remove, add) {
    addImplementation(this, index, remove, add);
  }

  // To do: add this when moving to `micromark`.
  // /**
  //  * Create an edit: but insert `add` before existing additions.
  //  *
  //  * @param {number} index
  //  * @param {number} remove
  //  * @param {Array<Event>} add
  //  * @returns {undefined}
  //  */
  // addBefore(index, remove, add) {
  //   addImplementation(this, index, remove, add, true)
  // }

  /**
   * Done, change the events.
   *
   * @param {Array<Event>} events
   * @returns {undefined}
   */
  consume(events) {
    this.map.sort(function (a, b) {
      return a[0] - b[0];
    });

    /* c8 ignore next 3 -- `resolve` is never called without tables, so without edits. */
    if (this.map.length === 0) {
      return;
    }

    // To do: if links are added in events, like they are in `markdown-rs`,
    // this is needed.
    // // Calculate jumps: where items in the current list move to.
    // /** @type {Array<Jump>} */
    // const jumps = []
    // let index = 0
    // let addAcc = 0
    // let removeAcc = 0
    // while (index < this.map.length) {
    //   const [at, remove, add] = this.map[index]
    //   removeAcc += remove
    //   addAcc += add.length
    //   jumps.push([at, removeAcc, addAcc])
    //   index += 1
    // }
    //
    // . shiftLinks(events, jumps)

    let index = this.map.length;
    /** @type {Array<Array<Event>>} */
    const vecs = [];
    while (index > 0) {
      index -= 1;
      vecs.push(events.slice(this.map[index][0] + this.map[index][1]), this.map[index][2]);

      // Truncate rest.
      events.length = this.map[index][0];
    }
    vecs.push(events.slice());
    events.length = 0;
    let slice = vecs.pop();
    while (slice) {
      for (const element of slice) {
        events.push(element);
      }
      slice = vecs.pop();
    }

    // Truncate everything.
    this.map.length = 0;
  }
}

/**
 * Create an edit.
 *
 * @param {EditMap} editMap
 * @param {number} at
 * @param {number} remove
 * @param {Array<Event>} add
 * @returns {undefined}
 */
function addImplementation(editMap, at, remove, add) {
  let index = 0;

  /* c8 ignore next 3 -- `resolve` is never called without tables, so without edits. */
  if (remove === 0 && add.length === 0) {
    return;
  }
  while (index < editMap.map.length) {
    if (editMap.map[index][0] === at) {
      editMap.map[index][1] += remove;

      // To do: before not used by tables, use when moving to micromark.
      // if (before) {
      //   add.push(...editMap.map[index][2])
      //   editMap.map[index][2] = add
      // } else {
      editMap.map[index][2].push(...add);
      // }

      return;
    }
    index += 1;
  }
  editMap.map.push([at, remove, add]);
}

// /**
//  * Shift `previous` and `next` links according to `jumps`.
//  *
//  * This fixes links in case there are events removed or added between them.
//  *
//  * @param {Array<Event>} events
//  * @param {Array<Jump>} jumps
//  */
// function shiftLinks(events, jumps) {
//   let jumpIndex = 0
//   let index = 0
//   let add = 0
//   let rm = 0

//   while (index < events.length) {
//     const rmCurr = rm

//     while (jumpIndex < jumps.length && jumps[jumpIndex][0] <= index) {
//       add = jumps[jumpIndex][2]
//       rm = jumps[jumpIndex][1]
//       jumpIndex += 1
//     }

//     // Ignore items that will be removed.
//     if (rm > rmCurr) {
//       index += rm - rmCurr
//     } else {
//       // ?
//       // if let Some(link) = &events[index].link {
//       //     if let Some(next) = link.next {
//       //         events[next].link.as_mut().unwrap().previous = Some(index + add - rm);
//       //         while jumpIndex < jumps.len() && jumps[jumpIndex].0 <= next {
//       //             add = jumps[jumpIndex].2;
//       //             rm = jumps[jumpIndex].1;
//       //             jumpIndex += 1;
//       //         }
//       //         events[index].link.as_mut().unwrap().next = Some(next + add - rm);
//       //         index = next;
//       //         continue;
//       //     }
//       // }
//       index += 1
//     }
//   }
// }/**
 * @import {HtmlExtension} from 'micromark-util-types'
 */

const alignment = {
  none: '',
  left: ' align="left"',
  right: ' align="right"',
  center: ' align="center"'
};

// To do: micromark@5: use `infer` here, when all events are exposed.

/**
 * Create an HTML extension for `micromark` to support GitHub tables when
 * serializing to HTML.
 *
 * @returns {HtmlExtension}
 *   Extension for `micromark` that can be passed in `htmlExtensions` to
 *   support GitHub tables when serializing to HTML.
 */
export function gfmTableHtml() {
  return {
    enter: {
      table(token) {
        const tableAlign = token._align;
        this.lineEndingIfNeeded();
        this.tag('<table>');
        this.setData('tableAlign', tableAlign);
      },
      tableBody() {
        this.tag('<tbody>');
      },
      tableData() {
        const tableAlign = this.getData('tableAlign');
        const tableColumn = this.getData('tableColumn');
        const align = alignment[tableAlign[tableColumn]];
        if (align === undefined) {
          // Capture results to ignore them.
          this.buffer();
        } else {
          this.lineEndingIfNeeded();
          this.tag('<td' + align + '>');
        }
      },
      tableHead() {
        this.lineEndingIfNeeded();
        this.tag('<thead>');
      },
      tableHeader() {
        const tableAlign = this.getData('tableAlign');
        const tableColumn = this.getData('tableColumn');
        const align = alignment[tableAlign[tableColumn]];
        this.lineEndingIfNeeded();
        this.tag('<th' + align + '>');
      },
      tableRow() {
        this.setData('tableColumn', 0);
        this.lineEndingIfNeeded();
        this.tag('<tr>');
      }
    },
    exit: {
      // Overwrite the default code text data handler to unescape escaped pipes when
      // they are in tables.
      codeTextData(token) {
        let value = this.sliceSerialize(token);
        if (this.getData('tableAlign')) {
          value = value.replace(/\\([\\|])/g, replace);
        }
        this.raw(this.encode(value));
      },
      table() {
        this.setData('tableAlign');
        // Note: we don’t set `slurpAllLineEndings` anymore, in delimiter rows,
        // but we do need to reset it to match a funky newline GH generates for
        // list items combined with tables.
        this.setData('slurpAllLineEndings');
        this.lineEndingIfNeeded();
        this.tag('</table>');
      },
      tableBody() {
        this.lineEndingIfNeeded();
        this.tag('</tbody>');
      },
      tableData() {
        const tableAlign = this.getData('tableAlign');
        const tableColumn = this.getData('tableColumn');
        if (tableColumn in tableAlign) {
          this.tag('</td>');
          this.setData('tableColumn', tableColumn + 1);
        } else {
          // Stop capturing.
          this.resume();
        }
      },
      tableHead() {
        this.lineEndingIfNeeded();
        this.tag('</thead>');
      },
      tableHeader() {
        const tableColumn = this.getData('tableColumn');
        this.tag('</th>');
        this.setData('tableColumn', tableColumn + 1);
      },
      tableRow() {
        const tableAlign = this.getData('tableAlign');
        let tableColumn = this.getData('tableColumn');
        while (tableColumn < tableAlign.length) {
          this.lineEndingIfNeeded();
          this.tag('<td' + alignment[tableAlign[tableColumn]] + '></td>');
          tableColumn++;
        }
        this.setData('tableColumn', tableColumn);
        this.lineEndingIfNeeded();
        this.tag('</tr>');
      }
    }
  };
}

/**
 * @param {string} $0
 * @param {string} $1
 * @returns {string}
 */
function replace($0, $1) {
  // Pipes work, backslashes don’t (but can’t escape pipes).
  return $1 === '|' ? $1 : $0;
}/**
 * @import {Event} from 'micromark-util-types'
 */

/**
 * @typedef {'center' | 'left' | 'none' | 'right'} Align
 */

/**
 * Figure out the alignment of a GFM table.
 *
 * @param {Readonly<Array<Event>>} events
 *   List of events.
 * @param {number} index
 *   Table enter event.
 * @returns {Array<Align>}
 *   List of aligns.
 */
export function gfmTableAlign(events, index) {
  let inDelimiterRow = false;
  /** @type {Array<Align>} */
  const align = [];
  while (index < events.length) {
    const event = events[index];
    if (inDelimiterRow) {
      if (event[0] === 'enter') {
        // Start of alignment value: set a new column.
        // To do: `markdown-rs` uses `tableDelimiterCellValue`.
        if (event[1].type === 'tableContent') {
          align.push(events[index + 1][1].type === 'tableDelimiterMarker' ? 'left' : 'none');
        }
      }
      // Exits:
      // End of alignment value: change the column.
      // To do: `markdown-rs` uses `tableDelimiterCellValue`.
      else if (event[1].type === 'tableContent') {
        if (events[index - 1][1].type === 'tableDelimiterMarker') {
          const alignIndex = align.length - 1;
          align[alignIndex] = align[alignIndex] === 'left' ? 'center' : 'right';
        }
      }
      // Done!
      else if (event[1].type === 'tableDelimiterRow') {
        break;
      }
    } else if (event[0] === 'enter' && event[1].type === 'tableDelimiterRow') {
      inDelimiterRow = true;
    }
    index += 1;
  }
  return align;
}/**
 * @import {Event, Extension, Point, Resolver, State, Token, TokenizeContext, Tokenizer} from 'micromark-util-types'
 */

/**
 * @typedef {[number, number, number, number]} Range
 *   Cell info.
 *
 * @typedef {0 | 1 | 2 | 3} RowKind
 *   Where we are: `1` for head row, `2` for delimiter row, `3` for body row.
 */

import { factorySpace } from 'micromark-factory-space';
import { markdownLineEnding, markdownLineEndingOrSpace, markdownSpace } from 'micromark-util-character';
import { EditMap } from './edit-map.js';
import { gfmTableAlign } from './infer.js';

/**
 * Create an HTML extension for `micromark` to support GitHub tables syntax.
 *
 * @returns {Extension}
 *   Extension for `micromark` that can be passed in `extensions` to enable GFM
 *   table syntax.
 */
export function gfmTable() {
  return {
    flow: {
      null: {
        name: 'table',
        tokenize: tokenizeTable,
        resolveAll: resolveTable
      }
    }
  };
}

/**
 * @this {TokenizeContext}
 * @type {Tokenizer}
 */
function tokenizeTable(effects, ok, nok) {
  const self = this;
  let size = 0;
  let sizeB = 0;
  /** @type {boolean | undefined} */
  let seen;
  return start;

  /**
   * Start of a GFM table.
   *
   * If there is a valid table row or table head before, then we try to parse
   * another row.
   * Otherwise, we try to parse a head.
   *
   * ```markdown
   * > | | a |
   *     ^
   *   | | - |
   * > | | b |
   *     ^
   * ```
   * @type {State}
   */
  function start(code) {
    let index = self.events.length - 1;
    while (index > -1) {
      const type = self.events[index][1].type;
      if (type === "lineEnding" ||
      // Note: markdown-rs uses `whitespace` instead of `linePrefix`
      type === "linePrefix") index--;else break;
    }
    const tail = index > -1 ? self.events[index][1].type : null;
    const next = tail === 'tableHead' || tail === 'tableRow' ? bodyRowStart : headRowBefore;

    // Don’t allow lazy body rows.
    if (next === bodyRowStart && self.parser.lazy[self.now().line]) {
      return nok(code);
    }
    return next(code);
  }

  /**
   * Before table head row.
   *
   * ```markdown
   * > | | a |
   *     ^
   *   | | - |
   *   | | b |
   * ```
   *
   * @type {State}
   */
  function headRowBefore(code) {
    effects.enter('tableHead');
    effects.enter('tableRow');
    return headRowStart(code);
  }

  /**
   * Before table head row, after whitespace.
   *
   * ```markdown
   * > | | a |
   *     ^
   *   | | - |
   *   | | b |
   * ```
   *
   * @type {State}
   */
  function headRowStart(code) {
    if (code === 124) {
      return headRowBreak(code);
    }

    // To do: micromark-js should let us parse our own whitespace in extensions,
    // like `markdown-rs`:
    //
    // ```js
    // // 4+ spaces.
    // if (markdownSpace(code)) {
    //   return nok(code)
    // }
    // ```

    seen = true;
    // Count the first character, that isn’t a pipe, double.
    sizeB += 1;
    return headRowBreak(code);
  }

  /**
   * At break in table head row.
   *
   * ```markdown
   * > | | a |
   *     ^
   *       ^
   *         ^
   *   | | - |
   *   | | b |
   * ```
   *
   * @type {State}
   */
  function headRowBreak(code) {
    if (code === null) {
      // Note: in `markdown-rs`, we need to reset, in `micromark-js` we don‘t.
      return nok(code);
    }
    if (markdownLineEnding(code)) {
      // If anything other than one pipe (ignoring whitespace) was used, it’s fine.
      if (sizeB > 1) {
        sizeB = 0;
        // To do: check if this works.
        // Feel free to interrupt:
        self.interrupt = true;
        effects.exit('tableRow');
        effects.enter("lineEnding");
        effects.consume(code);
        effects.exit("lineEnding");
        return headDelimiterStart;
      }

      // Note: in `markdown-rs`, we need to reset, in `micromark-js` we don‘t.
      return nok(code);
    }
    if (markdownSpace(code)) {
      // To do: check if this is fine.
      // effects.attempt(State::Next(StateName::GfmTableHeadRowBreak), State::Nok)
      // State::Retry(space_or_tab(tokenizer))
      return factorySpace(effects, headRowBreak, "whitespace")(code);
    }
    sizeB += 1;
    if (seen) {
      seen = false;
      // Header cell count.
      size += 1;
    }
    if (code === 124) {
      effects.enter('tableCellDivider');
      effects.consume(code);
      effects.exit('tableCellDivider');
      // Whether a delimiter was seen.
      seen = true;
      return headRowBreak;
    }

    // Anything else is cell data.
    effects.enter("data");
    return headRowData(code);
  }

  /**
   * In table head row data.
   *
   * ```markdown
   * > | | a |
   *       ^
   *   | | - |
   *   | | b |
   * ```
   *
   * @type {State}
   */
  function headRowData(code) {
    if (code === null || code === 124 || markdownLineEndingOrSpace(code)) {
      effects.exit("data");
      return headRowBreak(code);
    }
    effects.consume(code);
    return code === 92 ? headRowEscape : headRowData;
  }

  /**
   * In table head row escape.
   *
   * ```markdown
   * > | | a\-b |
   *         ^
   *   | | ---- |
   *   | | c    |
   * ```
   *
   * @type {State}
   */
  function headRowEscape(code) {
    if (code === 92 || code === 124) {
      effects.consume(code);
      return headRowData;
    }
    return headRowData(code);
  }

  /**
   * Before delimiter row.
   *
   * ```markdown
   *   | | a |
   * > | | - |
   *     ^
   *   | | b |
   * ```
   *
   * @type {State}
   */
  function headDelimiterStart(code) {
    // Reset `interrupt`.
    self.interrupt = false;

    // Note: in `markdown-rs`, we need to handle piercing here too.
    if (self.parser.lazy[self.now().line]) {
      return nok(code);
    }
    effects.enter('tableDelimiterRow');
    // Track if we’ve seen a `:` or `|`.
    seen = false;
    if (markdownSpace(code)) {
      return factorySpace(effects, headDelimiterBefore, "linePrefix", self.parser.constructs.disable.null.includes('codeIndented') ? undefined : 4)(code);
    }
    return headDelimiterBefore(code);
  }

  /**
   * Before delimiter row, after optional whitespace.
   *
   * Reused when a `|` is found later, to parse another cell.
   *
   * ```markdown
   *   | | a |
   * > | | - |
   *     ^
   *   | | b |
   * ```
   *
   * @type {State}
   */
  function headDelimiterBefore(code) {
    if (code === 45 || code === 58) {
      return headDelimiterValueBefore(code);
    }
    if (code === 124) {
      seen = true;
      // If we start with a pipe, we open a cell marker.
      effects.enter('tableCellDivider');
      effects.consume(code);
      effects.exit('tableCellDivider');
      return headDelimiterCellBefore;
    }

    // More whitespace / empty row not allowed at start.
    return headDelimiterNok(code);
  }

  /**
   * After `|`, before delimiter cell.
   *
   * ```markdown
   *   | | a |
   * > | | - |
   *      ^
   * ```
   *
   * @type {State}
   */
  function headDelimiterCellBefore(code) {
    if (markdownSpace(code)) {
      return factorySpace(effects, headDelimiterValueBefore, "whitespace")(code);
    }
    return headDelimiterValueBefore(code);
  }

  /**
   * Before delimiter cell value.
   *
   * ```markdown
   *   | | a |
   * > | | - |
   *       ^
   * ```
   *
   * @type {State}
   */
  function headDelimiterValueBefore(code) {
    // Align: left.
    if (code === 58) {
      sizeB += 1;
      seen = true;
      effects.enter('tableDelimiterMarker');
      effects.consume(code);
      effects.exit('tableDelimiterMarker');
      return headDelimiterLeftAlignmentAfter;
    }

    // Align: none.
    if (code === 45) {
      sizeB += 1;
      // To do: seems weird that this *isn’t* left aligned, but that state is used?
      return headDelimiterLeftAlignmentAfter(code);
    }
    if (code === null || markdownLineEnding(code)) {
      return headDelimiterCellAfter(code);
    }
    return headDelimiterNok(code);
  }

  /**
   * After delimiter cell left alignment marker.
   *
   * ```markdown
   *   | | a  |
   * > | | :- |
   *        ^
   * ```
   *
   * @type {State}
   */
  function headDelimiterLeftAlignmentAfter(code) {
    if (code === 45) {
      effects.enter('tableDelimiterFiller');
      return headDelimiterFiller(code);
    }

    // Anything else is not ok after the left-align colon.
    return headDelimiterNok(code);
  }

  /**
   * In delimiter cell filler.
   *
   * ```markdown
   *   | | a |
   * > | | - |
   *       ^
   * ```
   *
   * @type {State}
   */
  function headDelimiterFiller(code) {
    if (code === 45) {
      effects.consume(code);
      return headDelimiterFiller;
    }

    // Align is `center` if it was `left`, `right` otherwise.
    if (code === 58) {
      seen = true;
      effects.exit('tableDelimiterFiller');
      effects.enter('tableDelimiterMarker');
      effects.consume(code);
      effects.exit('tableDelimiterMarker');
      return headDelimiterRightAlignmentAfter;
    }
    effects.exit('tableDelimiterFiller');
    return headDelimiterRightAlignmentAfter(code);
  }

  /**
   * After delimiter cell right alignment marker.
   *
   * ```markdown
   *   | |  a |
   * > | | -: |
   *         ^
   * ```
   *
   * @type {State}
   */
  function headDelimiterRightAlignmentAfter(code) {
    if (markdownSpace(code)) {
      return factorySpace(effects, headDelimiterCellAfter, "whitespace")(code);
    }
    return headDelimiterCellAfter(code);
  }

  /**
   * After delimiter cell.
   *
   * ```markdown
   *   | |  a |
   * > | | -: |
   *          ^
   * ```
   *
   * @type {State}
   */
  function headDelimiterCellAfter(code) {
    if (code === 124) {
      return headDelimiterBefore(code);
    }
    if (code === null || markdownLineEnding(code)) {
      // Exit when:
      // * there was no `:` or `|` at all (it’s a thematic break or setext
      //   underline instead)
      // * the header cell count is not the delimiter cell count
      if (!seen || size !== sizeB) {
        return headDelimiterNok(code);
      }

      // Note: in markdown-rs`, a reset is needed here.
      effects.exit('tableDelimiterRow');
      effects.exit('tableHead');
      // To do: in `markdown-rs`, resolvers need to be registered manually.
      // effects.register_resolver(ResolveName::GfmTable)
      return ok(code);
    }
    return headDelimiterNok(code);
  }

  /**
   * In delimiter row, at a disallowed byte.
   *
   * ```markdown
   *   | | a |
   * > | | x |
   *       ^
   * ```
   *
   * @type {State}
   */
  function headDelimiterNok(code) {
    // Note: in `markdown-rs`, we need to reset, in `micromark-js` we don‘t.
    return nok(code);
  }

  /**
   * Before table body row.
   *
   * ```markdown
   *   | | a |
   *   | | - |
   * > | | b |
   *     ^
   * ```
   *
   * @type {State}
   */
  function bodyRowStart(code) {
    // Note: in `markdown-rs` we need to manually take care of a prefix,
    // but in `micromark-js` that is done for us, so if we’re here, we’re
    // never at whitespace.
    effects.enter('tableRow');
    return bodyRowBreak(code);
  }

  /**
   * At break in table body row.
   *
   * ```markdown
   *   | | a |
   *   | | - |
   * > | | b |
   *     ^
   *       ^
   *         ^
   * ```
   *
   * @type {State}
   */
  function bodyRowBreak(code) {
    if (code === 124) {
      effects.enter('tableCellDivider');
      effects.consume(code);
      effects.exit('tableCellDivider');
      return bodyRowBreak;
    }
    if (code === null || markdownLineEnding(code)) {
      effects.exit('tableRow');
      return ok(code);
    }
    if (markdownSpace(code)) {
      return factorySpace(effects, bodyRowBreak, "whitespace")(code);
    }

    // Anything else is cell content.
    effects.enter("data");
    return bodyRowData(code);
  }

  /**
   * In table body row data.
   *
   * ```markdown
   *   | | a |
   *   | | - |
   * > | | b |
   *       ^
   * ```
   *
   * @type {State}
   */
  function bodyRowData(code) {
    if (code === null || code === 124 || markdownLineEndingOrSpace(code)) {
      effects.exit("data");
      return bodyRowBreak(code);
    }
    effects.consume(code);
    return code === 92 ? bodyRowEscape : bodyRowData;
  }

  /**
   * In table body row escape.
   *
   * ```markdown
   *   | | a    |
   *   | | ---- |
   * > | | b\-c |
   *         ^
   * ```
   *
   * @type {State}
   */
  function bodyRowEscape(code) {
    if (code === 92 || code === 124) {
      effects.consume(code);
      return bodyRowData;
    }
    return bodyRowData(code);
  }
}

/** @type {Resolver} */

function resolveTable(events, context) {
  let index = -1;
  let inFirstCellAwaitingPipe = true;
  /** @type {RowKind} */
  let rowKind = 0;
  /** @type {Range} */
  let lastCell = [0, 0, 0, 0];
  /** @type {Range} */
  let cell = [0, 0, 0, 0];
  let afterHeadAwaitingFirstBodyRow = false;
  let lastTableEnd = 0;
  /** @type {Token | undefined} */
  let currentTable;
  /** @type {Token | undefined} */
  let currentBody;
  /** @type {Token | undefined} */
  let currentCell;
  const map = new EditMap();
  while (++index < events.length) {
    const event = events[index];
    const token = event[1];
    if (event[0] === 'enter') {
      // Start of head.
      if (token.type === 'tableHead') {
        afterHeadAwaitingFirstBodyRow = false;

        // Inject previous (body end and) table end.
        if (lastTableEnd !== 0) {
          flushTableEnd(map, context, lastTableEnd, currentTable, currentBody);
          currentBody = undefined;
          lastTableEnd = 0;
        }

        // Inject table start.
        currentTable = {
          type: 'table',
          start: Object.assign({}, token.start),
          // Note: correct end is set later.
          end: Object.assign({}, token.end)
        };
        map.add(index, 0, [['enter', currentTable, context]]);
      } else if (token.type === 'tableRow' || token.type === 'tableDelimiterRow') {
        inFirstCellAwaitingPipe = true;
        currentCell = undefined;
        lastCell = [0, 0, 0, 0];
        cell = [0, index + 1, 0, 0];

        // Inject table body start.
        if (afterHeadAwaitingFirstBodyRow) {
          afterHeadAwaitingFirstBodyRow = false;
          currentBody = {
            type: 'tableBody',
            start: Object.assign({}, token.start),
            // Note: correct end is set later.
            end: Object.assign({}, token.end)
          };
          map.add(index, 0, [['enter', currentBody, context]]);
        }
        rowKind = token.type === 'tableDelimiterRow' ? 2 : currentBody ? 3 : 1;
      }
      // Cell data.
      else if (rowKind && (token.type === "data" || token.type === 'tableDelimiterMarker' || token.type === 'tableDelimiterFiller')) {
        inFirstCellAwaitingPipe = false;

        // First value in cell.
        if (cell[2] === 0) {
          if (lastCell[1] !== 0) {
            cell[0] = cell[1];
            currentCell = flushCell(map, context, lastCell, rowKind, undefined, currentCell);
            lastCell = [0, 0, 0, 0];
          }
          cell[2] = index;
        }
      } else if (token.type === 'tableCellDivider') {
        if (inFirstCellAwaitingPipe) {
          inFirstCellAwaitingPipe = false;
        } else {
          if (lastCell[1] !== 0) {
            cell[0] = cell[1];
            currentCell = flushCell(map, context, lastCell, rowKind, undefined, currentCell);
          }
          lastCell = cell;
          cell = [lastCell[1], index, 0, 0];
        }
      }
    }
    // Exit events.
    else if (token.type === 'tableHead') {
      afterHeadAwaitingFirstBodyRow = true;
      lastTableEnd = index;
    } else if (token.type === 'tableRow' || token.type === 'tableDelimiterRow') {
      lastTableEnd = index;
      if (lastCell[1] !== 0) {
        cell[0] = cell[1];
        currentCell = flushCell(map, context, lastCell, rowKind, index, currentCell);
      } else if (cell[1] !== 0) {
        currentCell = flushCell(map, context, cell, rowKind, index, currentCell);
      }
      rowKind = 0;
    } else if (rowKind && (token.type === "data" || token.type === 'tableDelimiterMarker' || token.type === 'tableDelimiterFiller')) {
      cell[3] = index;
    }
  }
  if (lastTableEnd !== 0) {
    flushTableEnd(map, context, lastTableEnd, currentTable, currentBody);
  }
  map.consume(context.events);

  // To do: move this into `html`, when events are exposed there.
  // That’s what `markdown-rs` does.
  // That needs updates to `mdast-util-gfm-table`.
  index = -1;
  while (++index < context.events.length) {
    const event = context.events[index];
    if (event[0] === 'enter' && event[1].type === 'table') {
      event[1]._align = gfmTableAlign(context.events, index);
    }
  }
  return events;
}

/**
 * Generate a cell.
 *
 * @param {EditMap} map
 * @param {Readonly<TokenizeContext>} context
 * @param {Readonly<Range>} range
 * @param {RowKind} rowKind
 * @param {number | undefined} rowEnd
 * @param {Token | undefined} previousCell
 * @returns {Token | undefined}
 */
// eslint-disable-next-line max-params
function flushCell(map, context, range, rowKind, rowEnd, previousCell) {
  // `markdown-rs` uses:
  // rowKind === 2 ? 'tableDelimiterCell' : 'tableCell'
  const groupName = rowKind === 1 ? 'tableHeader' : rowKind === 2 ? 'tableDelimiter' : 'tableData';
  // `markdown-rs` uses:
  // rowKind === 2 ? 'tableDelimiterCellValue' : 'tableCellText'
  const valueName = 'tableContent';

  // Insert an exit for the previous cell, if there is one.
  //
  // ```markdown
  // > | | aa | bb | cc |
  //          ^-- exit
  //           ^^^^-- this cell
  // ```
  if (range[0] !== 0) {
    previousCell.end = Object.assign({}, getPoint(context.events, range[0]));
    map.add(range[0], 0, [['exit', previousCell, context]]);
  }

  // Insert enter of this cell.
  //
  // ```markdown
  // > | | aa | bb | cc |
  //           ^-- enter
  //           ^^^^-- this cell
  // ```
  const now = getPoint(context.events, range[1]);
  previousCell = {
    type: groupName,
    start: Object.assign({}, now),
    // Note: correct end is set later.
    end: Object.assign({}, now)
  };
  map.add(range[1], 0, [['enter', previousCell, context]]);

  // Insert text start at first data start and end at last data end, and
  // remove events between.
  //
  // ```markdown
  // > | | aa | bb | cc |
  //            ^-- enter
  //             ^-- exit
  //           ^^^^-- this cell
  // ```
  if (range[2] !== 0) {
    const relatedStart = getPoint(context.events, range[2]);
    const relatedEnd = getPoint(context.events, range[3]);
    /** @type {Token} */
    const valueToken = {
      type: valueName,
      start: Object.assign({}, relatedStart),
      end: Object.assign({}, relatedEnd)
    };
    map.add(range[2], 0, [['enter', valueToken, context]]);
    if (rowKind !== 2) {
      // Fix positional info on remaining events
      const start = context.events[range[2]];
      const end = context.events[range[3]];
      start[1].end = Object.assign({}, end[1].end);
      start[1].type = "chunkText";
      start[1].contentType = "text";

      // Remove if needed.
      if (range[3] > range[2] + 1) {
        const a = range[2] + 1;
        const b = range[3] - range[2] - 1;
        map.add(a, b, []);
      }
    }
    map.add(range[3] + 1, 0, [['exit', valueToken, context]]);
  }

  // Insert an exit for the last cell, if at the row end.
  //
  // ```markdown
  // > | | aa | bb | cc |
  //                    ^-- exit
  //               ^^^^^^-- this cell (the last one contains two “between” parts)
  // ```
  if (rowEnd !== undefined) {
    previousCell.end = Object.assign({}, getPoint(context.events, rowEnd));
    map.add(rowEnd, 0, [['exit', previousCell, context]]);
    previousCell = undefined;
  }
  return previousCell;
}

/**
 * Generate table end (and table body end).
 *
 * @param {Readonly<EditMap>} map
 * @param {Readonly<TokenizeContext>} context
 * @param {number} index
 * @param {Token} table
 * @param {Token | undefined} tableBody
 */
// eslint-disable-next-line max-params
function flushTableEnd(map, context, index, table, tableBody) {
  /** @type {Array<Event>} */
  const exits = [];
  const related = getPoint(context.events, index);
  if (tableBody) {
    tableBody.end = Object.assign({}, related);
    exits.push(['exit', tableBody, context]);
  }
  table.end = Object.assign({}, related);
  exits.push(['exit', table, context]);
  map.add(index + 1, 0, exits);
}

/**
 * @param {Readonly<Array<Event>>} events
 * @param {number} index
 * @returns {Readonly<Point>}
 */
function getPoint(events, index) {
  const event = events[index];
  const side = event[0] === 'enter' ? 'start' : 'end';
  return event[1][side];
}{
  "author": "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)",
  "bugs": "https://github.com/micromark/micromark-extension-gfm-table/issues",
  "contributors": [
    "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)"
  ],
  "dependencies": {
    "devlop": "^1.0.0",
    "micromark-factory-space": "^2.0.0",
    "micromark-util-character": "^2.0.0",
    "micromark-util-symbol": "^2.0.0",
    "micromark-util-types": "^2.0.0"
  },
  "description": "micromark extension to support GFM tables",
  "devDependencies": {
    "@types/node": "^22.0.0",
    "c8": "^10.0.0",
    "create-gfm-fixtures": "^2.0.0",
    "micromark": "^4.0.0",
    "micromark-build": "^2.0.0",
    "prettier": "^3.0.0",
    "remark-cli": "^12.0.0",
    "remark-preset-wooorm": "^10.0.0",
    "type-coverage": "^2.0.0",
    "typescript": "^5.0.0",
    "xo": "^0.60.0"
  },
  "exports": {
    "development": "./dev/index.js",
    "default": "./index.js"
  },
  "files": [
    "dev/",
    "index.d.ts",
    "index.js",
    "lib/"
  ],
  "funding": {
    "type": "opencollective",
    "url": "https://opencollective.com/unified"
  },
  "keywords": [
    "cell",
    "column",
    "gfm",
    "markdown",
    "micromark-extension",
    "micromark",
    "row",
    "table",
    "tabular",
    "unified"
  ],
  "license": "MIT",
  "name": "micromark-extension-gfm-table",
  "prettier": {
    "bracketSpacing": false,
    "semi": false,
    "singleQuote": true,
    "tabWidth": 2,
    "trailingComma": "none",
    "useTabs": false
  },
  "remarkConfig": {
    "plugins": [
      "remark-preset-wooorm"
    ]
  },
  "repository": "micromark/micromark-extension-gfm-table",
  "scripts": {
    "build": "tsc --build --clean && tsc --build && type-coverage && micromark-build",
    "format": "remark --frail --quiet --output -- . && prettier --log-level warn --write -- . && xo --fix",
    "test-api-dev": "node --conditions development test/index.js",
    "test-api-prod": "node --conditions production test/index.js",
    "test-api": "npm run test-api-dev && npm run test-api-prod",
    "test-coverage": "c8 --100 --reporter lcov -- npm run test-api",
    "test": "npm run build && npm run format && npm run test-coverage"
  },
  "sideEffects": false,
  "typeCoverage": {
    "atLeast": 100,
    "strict": true
  },
  "type": "module",
  "version": "2.1.1",
  "xo": {
    "overrides": [
      {
        "files": [
          "**/*.d.ts"
        ],
        "rules": {
          "@typescript-eslint/array-type": [
            "error",
            {
              "default": "generic"
            }
          ],
          "@typescript-eslint/ban-types": [
            "error",
            {
              "extendDefaults": true
            }
          ],
          "@typescript-eslint/consistent-type-definitions": [
            "error",
            "interface"
          ]
        }
      },
      {
        "files": [
          "test/**/*.js"
        ],
        "rules": {
          "no-await-in-loop": "off"
        }
      }
    ],
    "prettier": true,
    "rules": {
      "complexity": "off",
      "max-depth": "off",
      "unicorn/no-this-assignment": "off",
      "unicorn/prefer-string-replace-all": "off"
    }
  }
}
/**
 * @import {Effects, State, TokenType} from 'micromark-util-types'
 */

import { asciiControl, markdownLineEndingOrSpace, markdownLineEnding } from 'micromark-util-character';
/**
 * Parse destinations.
 *
 * ###### Examples
 *
 * ```markdown
 * <a>
 * <a\>b>
 * <a b>
 * <a)>
 * a
 * a\)b
 * a(b)c
 * a(b)
 * ```
 *
 * @param {Effects} effects
 *   Context.
 * @param {State} ok
 *   State switched to when successful.
 * @param {State} nok
 *   State switched to when unsuccessful.
 * @param {TokenType} type
 *   Type for whole (`<a>` or `b`).
 * @param {TokenType} literalType
 *   Type when enclosed (`<a>`).
 * @param {TokenType} literalMarkerType
 *   Type for enclosing (`<` and `>`).
 * @param {TokenType} rawType
 *   Type when not enclosed (`b`).
 * @param {TokenType} stringType
 *   Type for the value (`a` or `b`).
 * @param {number | undefined} [max=Infinity]
 *   Depth of nested parens (inclusive).
 * @returns {State}
 *   Start state.
 */
export function factoryDestination(effects, ok, nok, type, literalType, literalMarkerType, rawType, stringType, max) {
  const limit = max || Number.POSITIVE_INFINITY;
  let balance = 0;
  return start;

  /**
   * Start of destination.
   *
   * ```markdown
   * > | <aa>
   *     ^
   * > | aa
   *     ^
   * ```
   *
   * @type {State}
   */
  function start(code) {
    if (code === 60) {
      effects.enter(type);
      effects.enter(literalType);
      effects.enter(literalMarkerType);
      effects.consume(code);
      effects.exit(literalMarkerType);
      return enclosedBefore;
    }

    // ASCII control, space, closing paren.
    if (code === null || code === 32 || code === 41 || asciiControl(code)) {
      return nok(code);
    }
    effects.enter(type);
    effects.enter(rawType);
    effects.enter(stringType);
    effects.enter("chunkString", {
      contentType: "string"
    });
    return raw(code);
  }

  /**
   * After `<`, at an enclosed destination.
   *
   * ```markdown
   * > | <aa>
   *      ^
   * ```
   *
   * @type {State}
   */
  function enclosedBefore(code) {
    if (code === 62) {
      effects.enter(literalMarkerType);
      effects.consume(code);
      effects.exit(literalMarkerType);
      effects.exit(literalType);
      effects.exit(type);
      return ok;
    }
    effects.enter(stringType);
    effects.enter("chunkString", {
      contentType: "string"
    });
    return enclosed(code);
  }

  /**
   * In enclosed destination.
   *
   * ```markdown
   * > | <aa>
   *      ^
   * ```
   *
   * @type {State}
   */
  function enclosed(code) {
    if (code === 62) {
      effects.exit("chunkString");
      effects.exit(stringType);
      return enclosedBefore(code);
    }
    if (code === null || code === 60 || markdownLineEnding(code)) {
      return nok(code);
    }
    effects.consume(code);
    return code === 92 ? enclosedEscape : enclosed;
  }

  /**
   * After `\`, at a special character.
   *
   * ```markdown
   * > | <a\*a>
   *        ^
   * ```
   *
   * @type {State}
   */
  function enclosedEscape(code) {
    if (code === 60 || code === 62 || code === 92) {
      effects.consume(code);
      return enclosed;
    }
    return enclosed(code);
  }

  /**
   * In raw destination.
   *
   * ```markdown
   * > | aa
   *     ^
   * ```
   *
   * @type {State}
   */
  function raw(code) {
    if (!balance && (code === null || code === 41 || markdownLineEndingOrSpace(code))) {
      effects.exit("chunkString");
      effects.exit(stringType);
      effects.exit(rawType);
      effects.exit(type);
      return ok(code);
    }
    if (balance < limit && code === 40) {
      effects.consume(code);
      balance++;
      return raw;
    }
    if (code === 41) {
      effects.consume(code);
      balance--;
      return raw;
    }

    // ASCII control (but *not* `\0`) and space and `(`.
    // Note: in `markdown-rs`, `\0` exists in codes, in `micromark-js` it
    // doesn’t.
    if (code === null || code === 32 || code === 40 || asciiControl(code)) {
      return nok(code);
    }
    effects.consume(code);
    return code === 92 ? rawEscape : raw;
  }

  /**
   * After `\`, at special character.
   *
   * ```markdown
   * > | a\*a
   *       ^
   * ```
   *
   * @type {State}
   */
  function rawEscape(code) {
    if (code === 40 || code === 41 || code === 92) {
      effects.consume(code);
      return raw;
    }
    return raw(code);
  }
}{
  "name": "micromark-factory-destination",
  "version": "2.0.1",
  "description": "micromark factory to parse destinations (found in resources, definitions)",
  "license": "MIT",
  "keywords": [
    "micromark",
    "factory",
    "destination"
  ],
  "repository": "https://github.com/micromark/micromark/tree/main/packages/micromark-factory-destination",
  "bugs": "https://github.com/micromark/micromark/issues",
  "funding": [
    {
      "type": "GitHub Sponsors",
      "url": "https://github.com/sponsors/unifiedjs"
    },
    {
      "type": "OpenCollective",
      "url": "https://opencollective.com/unified"
    }
  ],
  "author": "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)",
  "contributors": [
    "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)"
  ],
  "sideEffects": false,
  "type": "module",
  "files": [
    "dev/",
    "index.d.ts.map",
    "index.d.ts",
    "index.js"
  ],
  "exports": {
    "development": "./dev/index.js",
    "default": "./index.js"
  },
  "dependencies": {
    "micromark-util-character": "^2.0.0",
    "micromark-util-symbol": "^2.0.0",
    "micromark-util-types": "^2.0.0"
  },
  "scripts": {
    "build": "micromark-build"
  },
  "xo": {
    "envs": [
      "shared-node-browser"
    ],
    "prettier": true,
    "rules": {
      "max-params": "off",
      "unicorn/prefer-code-point": "off"
    }
  }
}
/**
 * @import {
 *   Effects,
 *   State,
 *   TokenizeContext,
 *   TokenType
 * } from 'micromark-util-types'
 */

import { markdownLineEnding, markdownSpace } from 'micromark-util-character';
/**
 * Parse labels.
 *
 * > 👉 **Note**: labels in markdown are capped at 999 characters in the string.
 *
 * ###### Examples
 *
 * ```markdown
 * [a]
 * [a
 * b]
 * [a\]b]
 * ```
 *
 * @this {TokenizeContext}
 *   Tokenize context.
 * @param {Effects} effects
 *   Context.
 * @param {State} ok
 *   State switched to when successful.
 * @param {State} nok
 *   State switched to when unsuccessful.
 * @param {TokenType} type
 *   Type of the whole label (`[a]`).
 * @param {TokenType} markerType
 *   Type for the markers (`[` and `]`).
 * @param {TokenType} stringType
 *   Type for the identifier (`a`).
 * @returns {State}
 *   Start state.
 */
export function factoryLabel(effects, ok, nok, type, markerType, stringType) {
  const self = this;
  let size = 0;
  /** @type {boolean} */
  let seen;
  return start;

  /**
   * Start of label.
   *
   * ```markdown
   * > | [a]
   *     ^
   * ```
   *
   * @type {State}
   */
  function start(code) {
    effects.enter(type);
    effects.enter(markerType);
    effects.consume(code);
    effects.exit(markerType);
    effects.enter(stringType);
    return atBreak;
  }

  /**
   * In label, at something, before something else.
   *
   * ```markdown
   * > | [a]
   *      ^
   * ```
   *
   * @type {State}
   */
  function atBreak(code) {
    if (size > 999 || code === null || code === 91 || code === 93 && !seen ||
    // To do: remove in the future once we’ve switched from
    // `micromark-extension-footnote` to `micromark-extension-gfm-footnote`,
    // which doesn’t need this.
    // Hidden footnotes hook.
    /* c8 ignore next 3 */
    code === 94 && !size && '_hiddenFootnoteSupport' in self.parser.constructs) {
      return nok(code);
    }
    if (code === 93) {
      effects.exit(stringType);
      effects.enter(markerType);
      effects.consume(code);
      effects.exit(markerType);
      effects.exit(type);
      return ok;
    }

    // To do: indent? Link chunks and EOLs together?
    if (markdownLineEnding(code)) {
      effects.enter("lineEnding");
      effects.consume(code);
      effects.exit("lineEnding");
      return atBreak;
    }
    effects.enter("chunkString", {
      contentType: "string"
    });
    return labelInside(code);
  }

  /**
   * In label, in text.
   *
   * ```markdown
   * > | [a]
   *      ^
   * ```
   *
   * @type {State}
   */
  function labelInside(code) {
    if (code === null || code === 91 || code === 93 || markdownLineEnding(code) || size++ > 999) {
      effects.exit("chunkString");
      return atBreak(code);
    }
    effects.consume(code);
    if (!seen) seen = !markdownSpace(code);
    return code === 92 ? labelEscape : labelInside;
  }

  /**
   * After `\`, at a special character.
   *
   * ```markdown
   * > | [a\*a]
   *        ^
   * ```
   *
   * @type {State}
   */
  function labelEscape(code) {
    if (code === 91 || code === 92 || code === 93) {
      effects.consume(code);
      size++;
      return labelInside;
    }
    return labelInside(code);
  }
}{
  "name": "micromark-factory-label",
  "version": "2.0.1",
  "description": "micromark factory to parse labels (found in media, definitions)",
  "license": "MIT",
  "keywords": [
    "micromark",
    "factory",
    "label"
  ],
  "repository": "https://github.com/micromark/micromark/tree/main/packages/micromark-factory-label",
  "bugs": "https://github.com/micromark/micromark/issues",
  "funding": [
    {
      "type": "GitHub Sponsors",
      "url": "https://github.com/sponsors/unifiedjs"
    },
    {
      "type": "OpenCollective",
      "url": "https://opencollective.com/unified"
    }
  ],
  "author": "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)",
  "contributors": [
    "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)"
  ],
  "sideEffects": false,
  "type": "module",
  "files": [
    "dev/",
    "index.d.ts.map",
    "index.d.ts",
    "index.js"
  ],
  "exports": {
    "development": "./dev/index.js",
    "default": "./index.js"
  },
  "dependencies": {
    "devlop": "^1.0.0",
    "micromark-util-character": "^2.0.0",
    "micromark-util-symbol": "^2.0.0",
    "micromark-util-types": "^2.0.0"
  },
  "scripts": {
    "build": "micromark-build"
  },
  "xo": {
    "envs": [
      "shared-node-browser"
    ],
    "prettier": true,
    "rules": {
      "logical-assignment-operators": "off",
      "max-params": "off",
      "unicorn/no-this-assignment": "off",
      "unicorn/prefer-code-point": "off"
    }
  }
}
/**
 * @import {Effects, State, TokenType} from 'micromark-util-types'
 */

import { markdownSpace } from 'micromark-util-character';

// To do: implement `spaceOrTab`, `spaceOrTabMinMax`, `spaceOrTabWithOptions`.

/**
 * Parse spaces and tabs.
 *
 * There is no `nok` parameter:
 *
 * *   spaces in markdown are often optional, in which case this factory can be
 *     used and `ok` will be switched to whether spaces were found or not
 * *   one line ending or space can be detected with `markdownSpace(code)` right
 *     before using `factorySpace`
 *
 * ###### Examples
 *
 * Where `␉` represents a tab (plus how much it expands) and `␠` represents a
 * single space.
 *
 * ```markdown
 * ␉
 * ␠␠␠␠
 * ␉␠
 * ```
 *
 * @param {Effects} effects
 *   Context.
 * @param {State} ok
 *   State switched to when successful.
 * @param {TokenType} type
 *   Type (`' \t'`).
 * @param {number | undefined} [max=Infinity]
 *   Max (exclusive).
 * @returns {State}
 *   Start state.
 */
export function factorySpace(effects, ok, type, max) {
  const limit = max ? max - 1 : Number.POSITIVE_INFINITY;
  let size = 0;
  return start;

  /** @type {State} */
  function start(code) {
    if (markdownSpace(code)) {
      effects.enter(type);
      return prefix(code);
    }
    return ok(code);
  }

  /** @type {State} */
  function prefix(code) {
    if (markdownSpace(code) && size++ < limit) {
      effects.consume(code);
      return prefix;
    }
    effects.exit(type);
    return ok(code);
  }
}{
  "name": "micromark-factory-space",
  "version": "2.0.1",
  "description": "micromark factory to parse markdown space (found in lots of places)",
  "license": "MIT",
  "keywords": [
    "micromark",
    "factory",
    "space"
  ],
  "repository": "https://github.com/micromark/micromark/tree/main/packages/micromark-factory-space",
  "bugs": "https://github.com/micromark/micromark/issues",
  "funding": [
    {
      "type": "GitHub Sponsors",
      "url": "https://github.com/sponsors/unifiedjs"
    },
    {
      "type": "OpenCollective",
      "url": "https://opencollective.com/unified"
    }
  ],
  "author": "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)",
  "contributors": [
    "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)"
  ],
  "sideEffects": false,
  "type": "module",
  "files": [
    "dev/",
    "index.d.ts.map",
    "index.d.ts",
    "index.js"
  ],
  "exports": {
    "development": "./dev/index.js",
    "default": "./index.js"
  },
  "dependencies": {
    "micromark-util-character": "^2.0.0",
    "micromark-util-types": "^2.0.0"
  },
  "scripts": {
    "build": "micromark-build"
  },
  "xo": {
    "envs": [
      "shared-node-browser"
    ],
    "prettier": true,
    "rules": {
      "unicorn/prefer-code-point": "off"
    }
  }
}
/**
 * @import {
 *   Code,
 *   Effects,
 *   State,
 *   TokenType
 * } from 'micromark-util-types'
 */

import { factorySpace } from 'micromark-factory-space';
import { markdownLineEnding } from 'micromark-util-character';
/**
 * Parse titles.
 *
 * ###### Examples
 *
 * ```markdown
 * "a"
 * 'b'
 * (c)
 * "a
 * b"
 * 'a
 *     b'
 * (a\)b)
 * ```
 *
 * @param {Effects} effects
 *   Context.
 * @param {State} ok
 *   State switched to when successful.
 * @param {State} nok
 *   State switched to when unsuccessful.
 * @param {TokenType} type
 *   Type of the whole title (`"a"`, `'b'`, `(c)`).
 * @param {TokenType} markerType
 *   Type for the markers (`"`, `'`, `(`, and `)`).
 * @param {TokenType} stringType
 *   Type for the value (`a`).
 * @returns {State}
 *   Start state.
 */
export function factoryTitle(effects, ok, nok, type, markerType, stringType) {
  /** @type {NonNullable<Code>} */
  let marker;
  return start;

  /**
   * Start of title.
   *
   * ```markdown
   * > | "a"
   *     ^
   * ```
   *
   * @type {State}
   */
  function start(code) {
    if (code === 34 || code === 39 || code === 40) {
      effects.enter(type);
      effects.enter(markerType);
      effects.consume(code);
      effects.exit(markerType);
      marker = code === 40 ? 41 : code;
      return begin;
    }
    return nok(code);
  }

  /**
   * After opening marker.
   *
   * This is also used at the closing marker.
   *
   * ```markdown
   * > | "a"
   *      ^
   * ```
   *
   * @type {State}
   */
  function begin(code) {
    if (code === marker) {
      effects.enter(markerType);
      effects.consume(code);
      effects.exit(markerType);
      effects.exit(type);
      return ok;
    }
    effects.enter(stringType);
    return atBreak(code);
  }

  /**
   * At something, before something else.
   *
   * ```markdown
   * > | "a"
   *      ^
   * ```
   *
   * @type {State}
   */
  function atBreak(code) {
    if (code === marker) {
      effects.exit(stringType);
      return begin(marker);
    }
    if (code === null) {
      return nok(code);
    }

    // Note: blank lines can’t exist in content.
    if (markdownLineEnding(code)) {
      // To do: use `space_or_tab_eol_with_options`, connect.
      effects.enter("lineEnding");
      effects.consume(code);
      effects.exit("lineEnding");
      return factorySpace(effects, atBreak, "linePrefix");
    }
    effects.enter("chunkString", {
      contentType: "string"
    });
    return inside(code);
  }

  /**
   *
   *
   * @type {State}
   */
  function inside(code) {
    if (code === marker || code === null || markdownLineEnding(code)) {
      effects.exit("chunkString");
      return atBreak(code);
    }
    effects.consume(code);
    return code === 92 ? escape : inside;
  }

  /**
   * After `\`, at a special character.
   *
   * ```markdown
   * > | "a\*b"
   *      ^
   * ```
   *
   * @type {State}
   */
  function escape(code) {
    if (code === marker || code === 92) {
      effects.consume(code);
      return inside;
    }
    return inside(code);
  }
}{
  "name": "micromark-factory-title",
  "version": "2.0.1",
  "description": "micromark factory to parse markdown titles (found in resources, definitions)",
  "license": "MIT",
  "keywords": [
    "micromark",
    "factory",
    "title"
  ],
  "repository": "https://github.com/micromark/micromark/tree/main/packages/micromark-factory-title",
  "bugs": "https://github.com/micromark/micromark/issues",
  "funding": [
    {
      "type": "GitHub Sponsors",
      "url": "https://github.com/sponsors/unifiedjs"
    },
    {
      "type": "OpenCollective",
      "url": "https://opencollective.com/unified"
    }
  ],
  "author": "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)",
  "contributors": [
    "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)"
  ],
  "sideEffects": false,
  "type": "module",
  "files": [
    "dev/",
    "index.d.ts.map",
    "index.d.ts",
    "index.js"
  ],
  "exports": {
    "development": "./dev/index.js",
    "default": "./index.js"
  },
  "dependencies": {
    "micromark-factory-space": "^2.0.0",
    "micromark-util-character": "^2.0.0",
    "micromark-util-symbol": "^2.0.0",
    "micromark-util-types": "^2.0.0"
  },
  "scripts": {
    "build": "micromark-build"
  },
  "xo": {
    "envs": [
      "shared-node-browser"
    ],
    "prettier": true,
    "rules": {
      "max-params": "off",
      "unicorn/prefer-code-point": "off"
    }
  }
}
/**
 * @import {Effects, State} from 'micromark-util-types'
 */

import { factorySpace } from 'micromark-factory-space';
import { markdownLineEnding, markdownSpace } from 'micromark-util-character';
/**
 * Parse spaces and tabs.
 *
 * There is no `nok` parameter:
 *
 * *   line endings or spaces in markdown are often optional, in which case this
 *     factory can be used and `ok` will be switched to whether spaces were found
 *     or not
 * *   one line ending or space can be detected with
 *     `markdownLineEndingOrSpace(code)` right before using `factoryWhitespace`
 *
 * @param {Effects} effects
 *   Context.
 * @param {State} ok
 *   State switched to when successful.
 * @returns {State}
 *   Start state.
 */
export function factoryWhitespace(effects, ok) {
  /** @type {boolean} */
  let seen;
  return start;

  /** @type {State} */
  function start(code) {
    if (markdownLineEnding(code)) {
      effects.enter("lineEnding");
      effects.consume(code);
      effects.exit("lineEnding");
      seen = true;
      return start;
    }
    if (markdownSpace(code)) {
      return factorySpace(effects, start, seen ? "linePrefix" : "lineSuffix")(code);
    }
    return ok(code);
  }
}{
  "name": "micromark-factory-whitespace",
  "version": "2.0.1",
  "description": "micromark factory to parse markdown whitespace (found in lots of places)",
  "license": "MIT",
  "keywords": [
    "micromark",
    "factory",
    "whitespace"
  ],
  "repository": "https://github.com/micromark/micromark/tree/main/packages/micromark-factory-whitespace",
  "bugs": "https://github.com/micromark/micromark/issues",
  "funding": [
    {
      "type": "GitHub Sponsors",
      "url": "https://github.com/sponsors/unifiedjs"
    },
    {
      "type": "OpenCollective",
      "url": "https://opencollective.com/unified"
    }
  ],
  "author": "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)",
  "contributors": [
    "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)"
  ],
  "sideEffects": false,
  "type": "module",
  "files": [
    "dev/",
    "index.d.ts.map",
    "index.d.ts",
    "index.js"
  ],
  "exports": {
    "development": "./dev/index.js",
    "default": "./index.js"
  },
  "dependencies": {
    "micromark-factory-space": "^2.0.0",
    "micromark-util-character": "^2.0.0",
    "micromark-util-symbol": "^2.0.0",
    "micromark-util-types": "^2.0.0"
  },
  "scripts": {
    "build": "micromark-build"
  },
  "xo": {
    "envs": [
      "shared-node-browser"
    ],
    "prettier": true,
    "rules": {
      "unicorn/prefer-code-point": "off"
    }
  }
}
/**
 * @import {Code} from 'micromark-util-types'
 */

/**
 * Check whether the character code represents an ASCII alpha (`a` through `z`,
 * case insensitive).
 *
 * An **ASCII alpha** is an ASCII upper alpha or ASCII lower alpha.
 *
 * An **ASCII upper alpha** is a character in the inclusive range U+0041 (`A`)
 * to U+005A (`Z`).
 *
 * An **ASCII lower alpha** is a character in the inclusive range U+0061 (`a`)
 * to U+007A (`z`).
 *
 * @param code
 *   Code.
 * @returns {boolean}
 *   Whether it matches.
 */
export const asciiAlpha = regexCheck(/[A-Za-z]/);

/**
 * Check whether the character code represents an ASCII alphanumeric (`a`
 * through `z`, case insensitive, or `0` through `9`).
 *
 * An **ASCII alphanumeric** is an ASCII digit (see `asciiDigit`) or ASCII alpha
 * (see `asciiAlpha`).
 *
 * @param code
 *   Code.
 * @returns {boolean}
 *   Whether it matches.
 */
export const asciiAlphanumeric = regexCheck(/[\dA-Za-z]/);

/**
 * Check whether the character code represents an ASCII atext.
 *
 * atext is an ASCII alphanumeric (see `asciiAlphanumeric`), or a character in
 * the inclusive ranges U+0023 NUMBER SIGN (`#`) to U+0027 APOSTROPHE (`'`),
 * U+002A ASTERISK (`*`), U+002B PLUS SIGN (`+`), U+002D DASH (`-`), U+002F
 * SLASH (`/`), U+003D EQUALS TO (`=`), U+003F QUESTION MARK (`?`), U+005E
 * CARET (`^`) to U+0060 GRAVE ACCENT (`` ` ``), or U+007B LEFT CURLY BRACE
 * (`{`) to U+007E TILDE (`~`).
 *
 * See:
 * **\[RFC5322]**:
 * [Internet Message Format](https://tools.ietf.org/html/rfc5322).
 * P. Resnick.
 * IETF.
 *
 * @param code
 *   Code.
 * @returns {boolean}
 *   Whether it matches.
 */
export const asciiAtext = regexCheck(/[#-'*+\--9=?A-Z^-~]/);

/**
 * Check whether a character code is an ASCII control character.
 *
 * An **ASCII control** is a character in the inclusive range U+0000 NULL (NUL)
 * to U+001F (US), or U+007F (DEL).
 *
 * @param {Code} code
 *   Code.
 * @returns {boolean}
 *   Whether it matches.
 */
export function asciiControl(code) {
  return (
    // Special whitespace codes (which have negative values), C0 and Control
    // character DEL
    code !== null && (code < 32 || code === 127)
  );
}

/**
 * Check whether the character code represents an ASCII digit (`0` through `9`).
 *
 * An **ASCII digit** is a character in the inclusive range U+0030 (`0`) to
 * U+0039 (`9`).
 *
 * @param code
 *   Code.
 * @returns {boolean}
 *   Whether it matches.
 */
export const asciiDigit = regexCheck(/\d/);

/**
 * Check whether the character code represents an ASCII hex digit (`a` through
 * `f`, case insensitive, or `0` through `9`).
 *
 * An **ASCII hex digit** is an ASCII digit (see `asciiDigit`), ASCII upper hex
 * digit, or an ASCII lower hex digit.
 *
 * An **ASCII upper hex digit** is a character in the inclusive range U+0041
 * (`A`) to U+0046 (`F`).
 *
 * An **ASCII lower hex digit** is a character in the inclusive range U+0061
 * (`a`) to U+0066 (`f`).
 *
 * @param code
 *   Code.
 * @returns {boolean}
 *   Whether it matches.
 */
export const asciiHexDigit = regexCheck(/[\dA-Fa-f]/);

/**
 * Check whether the character code represents ASCII punctuation.
 *
 * An **ASCII punctuation** is a character in the inclusive ranges U+0021
 * EXCLAMATION MARK (`!`) to U+002F SLASH (`/`), U+003A COLON (`:`) to U+0040 AT
 * SIGN (`@`), U+005B LEFT SQUARE BRACKET (`[`) to U+0060 GRAVE ACCENT
 * (`` ` ``), or U+007B LEFT CURLY BRACE (`{`) to U+007E TILDE (`~`).
 *
 * @param code
 *   Code.
 * @returns {boolean}
 *   Whether it matches.
 */
export const asciiPunctuation = regexCheck(/[!-/:-@[-`{-~]/);

/**
 * Check whether a character code is a markdown line ending.
 *
 * A **markdown line ending** is the virtual characters M-0003 CARRIAGE RETURN
 * LINE FEED (CRLF), M-0004 LINE FEED (LF) and M-0005 CARRIAGE RETURN (CR).
 *
 * In micromark, the actual character U+000A LINE FEED (LF) and U+000D CARRIAGE
 * RETURN (CR) are replaced by these virtual characters depending on whether
 * they occurred together.
 *
 * @param {Code} code
 *   Code.
 * @returns {boolean}
 *   Whether it matches.
 */
export function markdownLineEnding(code) {
  return code !== null && code < -2;
}

/**
 * Check whether a character code is a markdown line ending (see
 * `markdownLineEnding`) or markdown space (see `markdownSpace`).
 *
 * @param {Code} code
 *   Code.
 * @returns {boolean}
 *   Whether it matches.
 */
export function markdownLineEndingOrSpace(code) {
  return code !== null && (code < 0 || code === 32);
}

/**
 * Check whether a character code is a markdown space.
 *
 * A **markdown space** is the concrete character U+0020 SPACE (SP) and the
 * virtual characters M-0001 VIRTUAL SPACE (VS) and M-0002 HORIZONTAL TAB (HT).
 *
 * In micromark, the actual character U+0009 CHARACTER TABULATION (HT) is
 * replaced by one M-0002 HORIZONTAL TAB (HT) and between 0 and 3 M-0001 VIRTUAL
 * SPACE (VS) characters, depending on the column at which the tab occurred.
 *
 * @param {Code} code
 *   Code.
 * @returns {boolean}
 *   Whether it matches.
 */
export function markdownSpace(code) {
  return code === -2 || code === -1 || code === 32;
}

// Size note: removing ASCII from the regex and using `asciiPunctuation` here
// In fact adds to the bundle size.
/**
 * Check whether the character code represents Unicode punctuation.
 *
 * A **Unicode punctuation** is a character in the Unicode `Pc` (Punctuation,
 * Connector), `Pd` (Punctuation, Dash), `Pe` (Punctuation, Close), `Pf`
 * (Punctuation, Final quote), `Pi` (Punctuation, Initial quote), `Po`
 * (Punctuation, Other), or `Ps` (Punctuation, Open) categories, or an ASCII
 * punctuation (see `asciiPunctuation`).
 *
 * See:
 * **\[UNICODE]**:
 * [The Unicode Standard](https://www.unicode.org/versions/).
 * Unicode Consortium.
 *
 * @param code
 *   Code.
 * @returns
 *   Whether it matches.
 */
export const unicodePunctuation = regexCheck(/\p{P}|\p{S}/u);

/**
 * Check whether the character code represents Unicode whitespace.
 *
 * Note that this does handle micromark specific markdown whitespace characters.
 * See `markdownLineEndingOrSpace` to check that.
 *
 * A **Unicode whitespace** is a character in the Unicode `Zs` (Separator,
 * Space) category, or U+0009 CHARACTER TABULATION (HT), U+000A LINE FEED (LF),
 * U+000C (FF), or U+000D CARRIAGE RETURN (CR) (**\[UNICODE]**).
 *
 * See:
 * **\[UNICODE]**:
 * [The Unicode Standard](https://www.unicode.org/versions/).
 * Unicode Consortium.
 *
 * @param code
 *   Code.
 * @returns
 *   Whether it matches.
 */
export const unicodeWhitespace = regexCheck(/\s/);

/**
 * Create a code check from a regex.
 *
 * @param {RegExp} regex
 *   Expression.
 * @returns {(code: Code) => boolean}
 *   Check.
 */
function regexCheck(regex) {
  return check;

  /**
   * Check whether a code matches the bound regex.
   *
   * @param {Code} code
   *   Character code.
   * @returns {boolean}
   *   Whether the character code matches the bound regex.
   */
  function check(code) {
    return code !== null && code > -1 && regex.test(String.fromCharCode(code));
  }
}{
  "name": "micromark-util-character",
  "version": "2.1.1",
  "description": "micromark utility to handle character codes",
  "license": "MIT",
  "keywords": [
    "micromark",
    "util",
    "utility",
    "character"
  ],
  "repository": "https://github.com/micromark/micromark/tree/main/packages/micromark-util-character",
  "bugs": "https://github.com/micromark/micromark/issues",
  "funding": [
    {
      "type": "GitHub Sponsors",
      "url": "https://github.com/sponsors/unifiedjs"
    },
    {
      "type": "OpenCollective",
      "url": "https://opencollective.com/unified"
    }
  ],
  "author": "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)",
  "contributors": [
    "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)"
  ],
  "sideEffects": false,
  "type": "module",
  "files": [
    "dev/",
    "lib/",
    "index.d.ts.map",
    "index.d.ts",
    "index.js"
  ],
  "exports": {
    "development": "./dev/index.js",
    "default": "./index.js"
  },
  "dependencies": {
    "micromark-util-symbol": "^2.0.0",
    "micromark-util-types": "^2.0.0"
  },
  "scripts": {
    "build": "micromark-build"
  },
  "xo": {
    "envs": [
      "shared-node-browser"
    ],
    "prettier": true,
    "rules": {
      "unicorn/prefer-code-point": "off"
    }
  }
}
/**
 * Like `Array#splice`, but smarter for giant arrays.
 *
 * `Array#splice` takes all items to be inserted as individual argument which
 * causes a stack overflow in V8 when trying to insert 100k items for instance.
 *
 * Otherwise, this does not return the removed items, and takes `items` as an
 * array instead of rest parameters.
 *
 * @template {unknown} T
 *   Item type.
 * @param {Array<T>} list
 *   List to operate on.
 * @param {number} start
 *   Index to remove/insert at (can be negative).
 * @param {number} remove
 *   Number of items to remove.
 * @param {Array<T>} items
 *   Items to inject into `list`.
 * @returns {undefined}
 *   Nothing.
 */
export function splice(list, start, remove, items) {
  const end = list.length;
  let chunkStart = 0;
  /** @type {Array<unknown>} */
  let parameters;

  // Make start between zero and `end` (included).
  if (start < 0) {
    start = -start > end ? 0 : end + start;
  } else {
    start = start > end ? end : start;
  }
  remove = remove > 0 ? remove : 0;

  // No need to chunk the items if there’s only a couple (10k) items.
  if (items.length < 10000) {
    parameters = Array.from(items);
    parameters.unshift(start, remove);
    // @ts-expect-error Hush, it’s fine.
    list.splice(...parameters);
  } else {
    // Delete `remove` items starting from `start`
    if (remove) list.splice(start, remove);

    // Insert the items in chunks to not cause stack overflows.
    while (chunkStart < items.length) {
      parameters = items.slice(chunkStart, chunkStart + 10000);
      parameters.unshift(start, 0);
      // @ts-expect-error Hush, it’s fine.
      list.splice(...parameters);
      chunkStart += 10000;
      start += 10000;
    }
  }
}

/**
 * Append `items` (an array) at the end of `list` (another array).
 * When `list` was empty, returns `items` instead.
 *
 * This prevents a potentially expensive operation when `list` is empty,
 * and adds items in batches to prevent V8 from hanging.
 *
 * @template {unknown} T
 *   Item type.
 * @param {Array<T>} list
 *   List to operate on.
 * @param {Array<T>} items
 *   Items to add to `list`.
 * @returns {Array<T>}
 *   Either `list` or `items`.
 */
export function push(list, items) {
  if (list.length > 0) {
    splice(list, list.length, 0, items);
    return list;
  }
  return items;
}{
  "name": "micromark-util-chunked",
  "version": "2.0.1",
  "description": "micromark utility to splice and push with giant arrays",
  "license": "MIT",
  "keywords": [
    "micromark",
    "util",
    "utility",
    "chunk",
    "splice",
    "push"
  ],
  "repository": "https://github.com/micromark/micromark/tree/main/packages/micromark-util-chunked",
  "bugs": "https://github.com/micromark/micromark/issues",
  "funding": [
    {
      "type": "GitHub Sponsors",
      "url": "https://github.com/sponsors/unifiedjs"
    },
    {
      "type": "OpenCollective",
      "url": "https://opencollective.com/unified"
    }
  ],
  "author": "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)",
  "contributors": [
    "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)"
  ],
  "sideEffects": false,
  "type": "module",
  "files": [
    "dev/",
    "index.d.ts.map",
    "index.d.ts",
    "index.js"
  ],
  "exports": {
    "development": "./dev/index.js",
    "default": "./index.js"
  },
  "dependencies": {
    "micromark-util-symbol": "^2.0.0"
  },
  "scripts": {
    "build": "micromark-build"
  },
  "xo": {
    "envs": [
      "shared-node-browser"
    ],
    "prettier": true,
    "rules": {
      "unicorn/prefer-code-point": "off"
    }
  }
}
/**
 * @import {Code} from 'micromark-util-types'
 */

import { markdownLineEndingOrSpace, unicodePunctuation, unicodeWhitespace } from 'micromark-util-character';
/**
 * Classify whether a code represents whitespace, punctuation, or something
 * else.
 *
 * Used for attention (emphasis, strong), whose sequences can open or close
 * based on the class of surrounding characters.
 *
 * > 👉 **Note**: eof (`null`) is seen as whitespace.
 *
 * @param {Code} code
 *   Code.
 * @returns {typeof constants.characterGroupWhitespace | typeof constants.characterGroupPunctuation | undefined}
 *   Group.
 */
export function classifyCharacter(code) {
  if (code === null || markdownLineEndingOrSpace(code) || unicodeWhitespace(code)) {
    return 1;
  }
  if (unicodePunctuation(code)) {
    return 2;
  }
}{
  "name": "micromark-util-classify-character",
  "version": "2.0.1",
  "description": "micromark utility to classify whether a character is whitespace or punctuation",
  "license": "MIT",
  "keywords": [
    "micromark",
    "util",
    "utility",
    "attention",
    "classify",
    "character"
  ],
  "repository": "https://github.com/micromark/micromark/tree/main/packages/micromark-util-classify-character",
  "bugs": "https://github.com/micromark/micromark/issues",
  "funding": [
    {
      "type": "GitHub Sponsors",
      "url": "https://github.com/sponsors/unifiedjs"
    },
    {
      "type": "OpenCollective",
      "url": "https://opencollective.com/unified"
    }
  ],
  "author": "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)",
  "contributors": [
    "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)"
  ],
  "sideEffects": false,
  "type": "module",
  "files": [
    "dev/",
    "index.d.ts.map",
    "index.d.ts",
    "index.js"
  ],
  "exports": {
    "development": "./dev/index.js",
    "default": "./index.js"
  },
  "dependencies": {
    "micromark-util-character": "^2.0.0",
    "micromark-util-symbol": "^2.0.0",
    "micromark-util-types": "^2.0.0"
  },
  "scripts": {
    "build": "micromark-build"
  },
  "xo": {
    "envs": [
      "shared-node-browser"
    ],
    "prettier": true,
    "rules": {
      "unicorn/prefer-code-point": "off"
    }
  }
}
/**
 * @import {
 *   Extension,
 *   Handles,
 *   HtmlExtension,
 *   NormalizedExtension
 * } from 'micromark-util-types'
 */

import {splice} from 'micromark-util-chunked'

const hasOwnProperty = {}.hasOwnProperty

/**
 * Combine multiple syntax extensions into one.
 *
 * @param {ReadonlyArray<Extension>} extensions
 *   List of syntax extensions.
 * @returns {NormalizedExtension}
 *   A single combined extension.
 */
export function combineExtensions(extensions) {
  /** @type {NormalizedExtension} */
  const all = {}
  let index = -1

  while (++index < extensions.length) {
    syntaxExtension(all, extensions[index])
  }

  return all
}

/**
 * Merge `extension` into `all`.
 *
 * @param {NormalizedExtension} all
 *   Extension to merge into.
 * @param {Extension} extension
 *   Extension to merge.
 * @returns {undefined}
 *   Nothing.
 */
function syntaxExtension(all, extension) {
  /** @type {keyof Extension} */
  let hook

  for (hook in extension) {
    const maybe = hasOwnProperty.call(all, hook) ? all[hook] : undefined
    /** @type {Record<string, unknown>} */
    const left = maybe || (all[hook] = {})
    /** @type {Record<string, unknown> | undefined} */
    const right = extension[hook]
    /** @type {string} */
    let code

    if (right) {
      for (code in right) {
        if (!hasOwnProperty.call(left, code)) left[code] = []
        const value = right[code]
        constructs(
          // @ts-expect-error Looks like a list.
          left[code],
          Array.isArray(value) ? value : value ? [value] : []
        )
      }
    }
  }
}

/**
 * Merge `list` into `existing` (both lists of constructs).
 * Mutates `existing`.
 *
 * @param {Array<unknown>} existing
 *   List of constructs to merge into.
 * @param {Array<unknown>} list
 *   List of constructs to merge.
 * @returns {undefined}
 *   Nothing.
 */
function constructs(existing, list) {
  let index = -1
  /** @type {Array<unknown>} */
  const before = []

  while (++index < list.length) {
    // @ts-expect-error Looks like an object.
    ;(list[index].add === 'after' ? existing : before).push(list[index])
  }

  splice(existing, 0, 0, before)
}

/**
 * Combine multiple HTML extensions into one.
 *
 * @param {ReadonlyArray<HtmlExtension>} htmlExtensions
 *   List of HTML extensions.
 * @returns {HtmlExtension}
 *   Single combined HTML extension.
 */
export function combineHtmlExtensions(htmlExtensions) {
  /** @type {HtmlExtension} */
  const handlers = {}
  let index = -1

  while (++index < htmlExtensions.length) {
    htmlExtension(handlers, htmlExtensions[index])
  }

  return handlers
}

/**
 * Merge `extension` into `all`.
 *
 * @param {HtmlExtension} all
 *   Extension to merge into.
 * @param {HtmlExtension} extension
 *   Extension to merge.
 * @returns {undefined}
 *   Nothing.
 */
function htmlExtension(all, extension) {
  /** @type {keyof HtmlExtension} */
  let hook

  for (hook in extension) {
    const maybe = hasOwnProperty.call(all, hook) ? all[hook] : undefined
    const left = maybe || (all[hook] = {})
    const right = extension[hook]
    /** @type {keyof Handles} */
    let type

    if (right) {
      for (type in right) {
        // @ts-expect-error assume document vs regular handler are managed correctly.
        left[type] = right[type]
      }
    }
  }
}
{
  "name": "micromark-util-combine-extensions",
  "version": "2.0.1",
  "description": "micromark utility to combine syntax or html extensions",
  "license": "MIT",
  "keywords": [
    "micromark",
    "util",
    "utility",
    "extension",
    "combine",
    "merge"
  ],
  "repository": "https://github.com/micromark/micromark/tree/main/packages/micromark-util-combine-extensions",
  "bugs": "https://github.com/micromark/micromark/issues",
  "funding": [
    {
      "type": "GitHub Sponsors",
      "url": "https://github.com/sponsors/unifiedjs"
    },
    {
      "type": "OpenCollective",
      "url": "https://opencollective.com/unified"
    }
  ],
  "author": "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)",
  "contributors": [
    "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)"
  ],
  "sideEffects": false,
  "type": "module",
  "files": [
    "index.d.ts.map",
    "index.d.ts",
    "index.js"
  ],
  "exports": "./index.js",
  "dependencies": {
    "micromark-util-chunked": "^2.0.0",
    "micromark-util-types": "^2.0.0"
  },
  "xo": {
    "envs": [
      "shared-node-browser"
    ],
    "prettier": true,
    "rules": {
      "guard-for-in": "off",
      "unicorn/prefer-code-point": "off"
    }
  }
}
/**
 * Turn the number (in string form as either hexa- or plain decimal) coming from
 * a numeric character reference into a character.
 *
 * Sort of like `String.fromCodePoint(Number.parseInt(value, base))`, but makes
 * non-characters and control characters safe.
 *
 * @param {string} value
 *   Value to decode.
 * @param {number} base
 *   Numeric base.
 * @returns {string}
 *   Character.
 */
export function decodeNumericCharacterReference(value, base) {
  const code = Number.parseInt(value, base);
  if (
  // C0 except for HT, LF, FF, CR, space.
  code < 9 || code === 11 || code > 13 && code < 32 ||
  // Control character (DEL) of C0, and C1 controls.
  code > 126 && code < 160 ||
  // Lone high surrogates and low surrogates.
  code > 55_295 && code < 57_344 ||
  // Noncharacters.
  code > 64_975 && code < 65_008 || /* eslint-disable no-bitwise */
  (code & 65_535) === 65_535 || (code & 65_535) === 65_534 || /* eslint-enable no-bitwise */
  // Out of range
  code > 1_114_111) {
    return "\uFFFD";
  }
  return String.fromCodePoint(code);
}{
  "name": "micromark-util-decode-numeric-character-reference",
  "version": "2.0.2",
  "description": "micromark utility to decode numeric character references",
  "license": "MIT",
  "keywords": [
    "micromark",
    "util",
    "utility",
    "decode",
    "numeric",
    "number",
    "character",
    "reference"
  ],
  "repository": "https://github.com/micromark/micromark/tree/main/packages/micromark-util-decode-numeric-character-reference",
  "bugs": "https://github.com/micromark/micromark/issues",
  "funding": [
    {
      "type": "GitHub Sponsors",
      "url": "https://github.com/sponsors/unifiedjs"
    },
    {
      "type": "OpenCollective",
      "url": "https://opencollective.com/unified"
    }
  ],
  "author": "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)",
  "contributors": [
    "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)"
  ],
  "sideEffects": false,
  "type": "module",
  "files": [
    "dev/",
    "index.d.ts.map",
    "index.d.ts",
    "index.js"
  ],
  "exports": {
    "development": "./dev/index.js",
    "default": "./index.js"
  },
  "dependencies": {
    "micromark-util-symbol": "^2.0.0"
  },
  "scripts": {
    "build": "micromark-build"
  },
  "xo": {
    "envs": [
      "shared-node-browser"
    ],
    "prettier": true,
    "rules": {
      "unicorn/prefer-code-point": "off"
    }
  }
}
import { decodeNamedCharacterReference } from 'decode-named-character-reference';
import { decodeNumericCharacterReference } from 'micromark-util-decode-numeric-character-reference';
const characterEscapeOrReference = /\\([!-/:-@[-`{-~])|&(#(?:\d{1,7}|x[\da-f]{1,6})|[\da-z]{1,31});/gi;

/**
 * Decode markdown strings (which occur in places such as fenced code info
 * strings, destinations, labels, and titles).
 *
 * The “string” content type allows character escapes and -references.
 * This decodes those.
 *
 * @param {string} value
 *   Value to decode.
 * @returns {string}
 *   Decoded value.
 */
export function decodeString(value) {
  return value.replace(characterEscapeOrReference, decode);
}

/**
 * @param {string} $0
 *   Match.
 * @param {string} $1
 *   Character escape.
 * @param {string} $2
 *   Character reference.
 * @returns {string}
 *   Decoded value
 */
function decode($0, $1, $2) {
  if ($1) {
    // Escape.
    return $1;
  }

  // Reference.
  const head = $2.charCodeAt(0);
  if (head === 35) {
    const head = $2.charCodeAt(1);
    const hex = head === 120 || head === 88;
    return decodeNumericCharacterReference($2.slice(hex ? 2 : 1), hex ? 16 : 10);
  }
  return decodeNamedCharacterReference($2) || $0;
}{
  "name": "micromark-util-decode-string",
  "version": "2.0.1",
  "description": "micromark utility to decode markdown strings",
  "license": "MIT",
  "keywords": [
    "micromark",
    "util",
    "utility",
    "decode",
    "character",
    "reference",
    "escape",
    "string"
  ],
  "repository": "https://github.com/micromark/micromark/tree/main/packages/micromark-util-decode-string",
  "bugs": "https://github.com/micromark/micromark/issues",
  "funding": [
    {
      "type": "GitHub Sponsors",
      "url": "https://github.com/sponsors/unifiedjs"
    },
    {
      "type": "OpenCollective",
      "url": "https://opencollective.com/unified"
    }
  ],
  "author": "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)",
  "contributors": [
    "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)"
  ],
  "sideEffects": false,
  "type": "module",
  "files": [
    "dev/",
    "index.d.ts.map",
    "index.d.ts",
    "index.js"
  ],
  "exports": {
    "development": "./dev/index.js",
    "default": "./index.js"
  },
  "dependencies": {
    "micromark-util-character": "^2.0.0",
    "micromark-util-decode-numeric-character-reference": "^2.0.0",
    "micromark-util-symbol": "^2.0.0",
    "decode-named-character-reference": "^1.0.0"
  },
  "scripts": {
    "build": "micromark-build"
  },
  "xo": {
    "envs": [
      "shared-node-browser"
    ],
    "prettier": true,
    "rules": {
      "unicorn/prefer-code-point": "off",
      "unicorn/prefer-string-replace-all": "off"
    }
  }
}
const characterReferences = {'"': 'quot', '&': 'amp', '<': 'lt', '>': 'gt'}

/**
 * Encode only the dangerous HTML characters.
 *
 * This ensures that certain characters which have special meaning in HTML are
 * dealt with.
 * Technically, we can skip `>` and `"` in many cases, but CM includes them.
 *
 * @param {string} value
 *   Value to encode.
 * @returns {string}
 *   Encoded value.
 */
export function encode(value) {
  return value.replace(/["&<>]/g, replace)

  /**
   * @param {string} value
   *   Value to replace.
   * @returns {string}
   *   Encoded value.
   */
  function replace(value) {
    return (
      '&' +
      characterReferences[
        /** @type {keyof typeof characterReferences} */ (value)
      ] +
      ';'
    )
  }
}
{
  "name": "micromark-util-encode",
  "version": "2.0.1",
  "description": "micromark utility to encode dangerous html characters",
  "license": "MIT",
  "keywords": [
    "micromark",
    "util",
    "utility",
    "html",
    "encode"
  ],
  "repository": "https://github.com/micromark/micromark/tree/main/packages/micromark-util-encode",
  "bugs": "https://github.com/micromark/micromark/issues",
  "funding": [
    {
      "type": "GitHub Sponsors",
      "url": "https://github.com/sponsors/unifiedjs"
    },
    {
      "type": "OpenCollective",
      "url": "https://opencollective.com/unified"
    }
  ],
  "author": "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)",
  "contributors": [
    "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)"
  ],
  "sideEffects": false,
  "type": "module",
  "files": [
    "index.d.ts.map",
    "index.d.ts",
    "index.js"
  ],
  "exports": "./index.js",
  "xo": {
    "envs": [
      "shared-node-browser"
    ],
    "prettier": true,
    "rules": {
      "unicorn/prefer-string-replace-all": "off",
      "unicorn/prefer-code-point": "off"
    }
  }
}
/**
 * List of lowercase HTML “block” tag names.
 *
 * The list, when parsing HTML (flow), results in more relaxed rules (condition
 * 6).
 * Because they are known blocks, the HTML-like syntax doesn’t have to be
 * strictly parsed.
 * For tag names not in this list, a more strict algorithm (condition 7) is used
 * to detect whether the HTML-like syntax is seen as HTML (flow) or not.
 *
 * This is copied from:
 * <https://spec.commonmark.org/0.30/#html-blocks>.
 *
 * > 👉 **Note**: `search` was added in `CommonMark@0.31`.
 */
export const htmlBlockNames = [
  'address',
  'article',
  'aside',
  'base',
  'basefont',
  'blockquote',
  'body',
  'caption',
  'center',
  'col',
  'colgroup',
  'dd',
  'details',
  'dialog',
  'dir',
  'div',
  'dl',
  'dt',
  'fieldset',
  'figcaption',
  'figure',
  'footer',
  'form',
  'frame',
  'frameset',
  'h1',
  'h2',
  'h3',
  'h4',
  'h5',
  'h6',
  'head',
  'header',
  'hr',
  'html',
  'iframe',
  'legend',
  'li',
  'link',
  'main',
  'menu',
  'menuitem',
  'nav',
  'noframes',
  'ol',
  'optgroup',
  'option',
  'p',
  'param',
  'search',
  'section',
  'summary',
  'table',
  'tbody',
  'td',
  'tfoot',
  'th',
  'thead',
  'title',
  'tr',
  'track',
  'ul'
]

/**
 * List of lowercase HTML “raw” tag names.
 *
 * The list, when parsing HTML (flow), results in HTML that can include lines
 * without exiting, until a closing tag also in this list is found (condition
 * 1).
 *
 * This module is copied from:
 * <https://spec.commonmark.org/0.30/#html-blocks>.
 *
 * > 👉 **Note**: `textarea` was added in `CommonMark@0.30`.
 */
export const htmlRawNames = ['pre', 'script', 'style', 'textarea']
{
  "name": "micromark-util-html-tag-name",
  "version": "2.0.1",
  "description": "micromark utility with list of html tag names",
  "license": "MIT",
  "keywords": [
    "micromark",
    "util",
    "utility",
    "html",
    "tag",
    "name"
  ],
  "repository": "https://github.com/micromark/micromark/tree/main/packages/micromark-util-html-tag-name",
  "bugs": "https://github.com/micromark/micromark/issues",
  "funding": [
    {
      "type": "GitHub Sponsors",
      "url": "https://github.com/sponsors/unifiedjs"
    },
    {
      "type": "OpenCollective",
      "url": "https://opencollective.com/unified"
    }
  ],
  "author": "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)",
  "contributors": [
    "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)"
  ],
  "sideEffects": false,
  "type": "module",
  "files": [
    "index.d.ts.map",
    "index.d.ts",
    "index.js"
  ],
  "exports": "./index.js",
  "xo": {
    "envs": [
      "shared-node-browser"
    ],
    "prettier": true,
    "rules": {
      "unicorn/prefer-code-point": "off"
    }
  }
}
/**
 * Normalize an identifier (as found in references, definitions).
 *
 * Collapses markdown whitespace, trim, and then lower- and uppercase.
 *
 * Some characters are considered “uppercase”, such as U+03F4 (`ϴ`), but if their
 * lowercase counterpart (U+03B8 (`θ`)) is uppercased will result in a different
 * uppercase character (U+0398 (`Θ`)).
 * So, to get a canonical form, we perform both lower- and uppercase.
 *
 * Using uppercase last makes sure keys will never interact with default
 * prototypal values (such as `constructor`): nothing in the prototype of
 * `Object` is uppercase.
 *
 * @param {string} value
 *   Identifier to normalize.
 * @returns {string}
 *   Normalized identifier.
 */
export function normalizeIdentifier(value) {
  return value
  // Collapse markdown whitespace.
  .replace(/[\t\n\r ]+/g, " ")
  // Trim.
  .replace(/^ | $/g, '')
  // Some characters are considered “uppercase”, but if their lowercase
  // counterpart is uppercased will result in a different uppercase
  // character.
  // Hence, to get that form, we perform both lower- and uppercase.
  // Upper case makes sure keys will not interact with default prototypal
  // methods: no method is uppercase.
  .toLowerCase().toUpperCase();
}{
  "name": "micromark-util-normalize-identifier",
  "version": "2.0.1",
  "description": "micromark utility normalize identifiers (as found in references, definitions)",
  "license": "MIT",
  "keywords": [
    "micromark",
    "util",
    "utility",
    "normalize",
    "id",
    "identifier"
  ],
  "repository": "https://github.com/micromark/micromark/tree/main/packages/micromark-util-normalize-identifier",
  "bugs": "https://github.com/micromark/micromark/issues",
  "funding": [
    {
      "type": "GitHub Sponsors",
      "url": "https://github.com/sponsors/unifiedjs"
    },
    {
      "type": "OpenCollective",
      "url": "https://opencollective.com/unified"
    }
  ],
  "author": "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)",
  "contributors": [
    "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)"
  ],
  "sideEffects": false,
  "type": "module",
  "files": [
    "dev/",
    "index.d.ts.map",
    "index.d.ts",
    "index.js"
  ],
  "exports": {
    "development": "./dev/index.js",
    "default": "./index.js"
  },
  "dependencies": {
    "micromark-util-symbol": "^2.0.0"
  },
  "scripts": {
    "build": "micromark-build"
  },
  "xo": {
    "envs": [
      "shared-node-browser"
    ],
    "prettier": true,
    "rules": {
      "unicorn/prefer-code-point": "off",
      "unicorn/prefer-string-replace-all": "off"
    }
  }
}
/**
 * @import {Event, Resolver, TokenizeContext} from 'micromark-util-types'
 */

/**
 * Call all `resolveAll`s.
 *
 * @param {ReadonlyArray<{resolveAll?: Resolver | undefined}>} constructs
 *   List of constructs, optionally with `resolveAll`s.
 * @param {Array<Event>} events
 *   List of events.
 * @param {TokenizeContext} context
 *   Context used by `tokenize`.
 * @returns {Array<Event>}
 *   Changed events.
 */
export function resolveAll(constructs, events, context) {
  /** @type {Array<Resolver>} */
  const called = []
  let index = -1

  while (++index < constructs.length) {
    const resolve = constructs[index].resolveAll

    if (resolve && !called.includes(resolve)) {
      events = resolve(events, context)
      called.push(resolve)
    }
  }

  return events
}
{
  "name": "micromark-util-resolve-all",
  "version": "2.0.1",
  "description": "micromark utility to resolve subtokens",
  "license": "MIT",
  "keywords": [
    "micromark",
    "util",
    "utility",
    "resolve"
  ],
  "repository": "https://github.com/micromark/micromark/tree/main/packages/micromark-util-resolve-all",
  "bugs": "https://github.com/micromark/micromark/issues",
  "funding": [
    {
      "type": "GitHub Sponsors",
      "url": "https://github.com/sponsors/unifiedjs"
    },
    {
      "type": "OpenCollective",
      "url": "https://opencollective.com/unified"
    }
  ],
  "author": "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)",
  "contributors": [
    "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)"
  ],
  "sideEffects": false,
  "type": "module",
  "files": [
    "index.d.ts.map",
    "index.d.ts",
    "index.js"
  ],
  "exports": "./index.js",
  "dependencies": {
    "micromark-util-types": "^2.0.0"
  },
  "xo": {
    "envs": [
      "shared-node-browser"
    ],
    "prettier": true,
    "rules": {
      "unicorn/prefer-code-point": "off"
    }
  }
}
import { asciiAlphanumeric } from 'micromark-util-character';
import { encode } from 'micromark-util-encode';
/**
 * Make a value safe for injection as a URL.
 *
 * This encodes unsafe characters with percent-encoding and skips already
 * encoded sequences (see `normalizeUri`).
 * Further unsafe characters are encoded as character references (see
 * `micromark-util-encode`).
 *
 * A regex of allowed protocols can be given, in which case the URL is
 * sanitized.
 * For example, `/^(https?|ircs?|mailto|xmpp)$/i` can be used for `a[href]`, or
 * `/^https?$/i` for `img[src]` (this is what `github.com` allows).
 * If the URL includes an unknown protocol (one not matched by `protocol`, such
 * as a dangerous example, `javascript:`), the value is ignored.
 *
 * @param {string | null | undefined} url
 *   URI to sanitize.
 * @param {RegExp | null | undefined} [protocol]
 *   Allowed protocols.
 * @returns {string}
 *   Sanitized URI.
 */
export function sanitizeUri(url, protocol) {
  const value = encode(normalizeUri(url || ''));
  if (!protocol) {
    return value;
  }
  const colon = value.indexOf(':');
  const questionMark = value.indexOf('?');
  const numberSign = value.indexOf('#');
  const slash = value.indexOf('/');
  if (
  // If there is no protocol, it’s relative.
  colon < 0 ||
  // If the first colon is after a `?`, `#`, or `/`, it’s not a protocol.
  slash > -1 && colon > slash || questionMark > -1 && colon > questionMark || numberSign > -1 && colon > numberSign ||
  // It is a protocol, it should be allowed.
  protocol.test(value.slice(0, colon))) {
    return value;
  }
  return '';
}

/**
 * Normalize a URL.
 *
 * Encode unsafe characters with percent-encoding, skipping already encoded
 * sequences.
 *
 * @param {string} value
 *   URI to normalize.
 * @returns {string}
 *   Normalized URI.
 */
export function normalizeUri(value) {
  /** @type {Array<string>} */
  const result = [];
  let index = -1;
  let start = 0;
  let skip = 0;
  while (++index < value.length) {
    const code = value.charCodeAt(index);
    /** @type {string} */
    let replace = '';

    // A correct percent encoded value.
    if (code === 37 && asciiAlphanumeric(value.charCodeAt(index + 1)) && asciiAlphanumeric(value.charCodeAt(index + 2))) {
      skip = 2;
    }
    // ASCII.
    else if (code < 128) {
      if (!/[!#$&-;=?-Z_a-z~]/.test(String.fromCharCode(code))) {
        replace = String.fromCharCode(code);
      }
    }
    // Astral.
    else if (code > 55_295 && code < 57_344) {
      const next = value.charCodeAt(index + 1);

      // A correct surrogate pair.
      if (code < 56_320 && next > 56_319 && next < 57_344) {
        replace = String.fromCharCode(code, next);
        skip = 1;
      }
      // Lone surrogate.
      else {
        replace = "\uFFFD";
      }
    }
    // Unicode.
    else {
      replace = String.fromCharCode(code);
    }
    if (replace) {
      result.push(value.slice(start, index), encodeURIComponent(replace));
      start = index + skip + 1;
      replace = '';
    }
    if (skip) {
      index += skip;
      skip = 0;
    }
  }
  return result.join('') + value.slice(start);
}{
  "name": "micromark-util-sanitize-uri",
  "version": "2.0.1",
  "description": "micromark utility to sanitize urls",
  "license": "MIT",
  "keywords": [
    "micromark",
    "util",
    "utility",
    "sanitize",
    "clear",
    "url"
  ],
  "repository": "https://github.com/micromark/micromark/tree/main/packages/micromark-util-sanitize-uri",
  "bugs": "https://github.com/micromark/micromark/issues",
  "funding": [
    {
      "type": "GitHub Sponsors",
      "url": "https://github.com/sponsors/unifiedjs"
    },
    {
      "type": "OpenCollective",
      "url": "https://opencollective.com/unified"
    }
  ],
  "author": "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)",
  "contributors": [
    "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)"
  ],
  "sideEffects": false,
  "type": "module",
  "files": [
    "dev/",
    "index.d.ts.map",
    "index.d.ts",
    "index.js"
  ],
  "exports": {
    "development": "./dev/index.js",
    "default": "./index.js"
  },
  "dependencies": {
    "micromark-util-character": "^2.0.0",
    "micromark-util-encode": "^2.0.0",
    "micromark-util-symbol": "^2.0.0"
  },
  "scripts": {
    "build": "micromark-build"
  },
  "xo": {
    "envs": [
      "shared-node-browser"
    ],
    "prettier": true,
    "rules": {
      "unicorn/prefer-code-point": "off"
    }
  }
}
/**
 * @import {Chunk, Event, Token} from 'micromark-util-types'
 */

import { splice } from 'micromark-util-chunked';
import { SpliceBuffer } from './lib/splice-buffer.js';

// Hidden API exposed for testing.
export { SpliceBuffer } from './lib/splice-buffer.js';

/**
 * Tokenize subcontent.
 *
 * @param {Array<Event>} eventsArray
 *   List of events.
 * @returns {boolean}
 *   Whether subtokens were found.
 */
// eslint-disable-next-line complexity
export function subtokenize(eventsArray) {
  /** @type {Record<string, number>} */
  const jumps = {};
  let index = -1;
  /** @type {Event} */
  let event;
  /** @type {number | undefined} */
  let lineIndex;
  /** @type {number} */
  let otherIndex;
  /** @type {Event} */
  let otherEvent;
  /** @type {Array<Event>} */
  let parameters;
  /** @type {Array<Event>} */
  let subevents;
  /** @type {boolean | undefined} */
  let more;
  const events = new SpliceBuffer(eventsArray);
  while (++index < events.length) {
    while (index in jumps) {
      index = jumps[index];
    }
    event = events.get(index);

    // Add a hook for the GFM tasklist extension, which needs to know if text
    // is in the first content of a list item.
    if (index && event[1].type === "chunkFlow" && events.get(index - 1)[1].type === "listItemPrefix") {
      subevents = event[1]._tokenizer.events;
      otherIndex = 0;
      if (otherIndex < subevents.length && subevents[otherIndex][1].type === "lineEndingBlank") {
        otherIndex += 2;
      }
      if (otherIndex < subevents.length && subevents[otherIndex][1].type === "content") {
        while (++otherIndex < subevents.length) {
          if (subevents[otherIndex][1].type === "content") {
            break;
          }
          if (subevents[otherIndex][1].type === "chunkText") {
            subevents[otherIndex][1]._isInFirstContentOfListItem = true;
            otherIndex++;
          }
        }
      }
    }

    // Enter.
    if (event[0] === 'enter') {
      if (event[1].contentType) {
        Object.assign(jumps, subcontent(events, index));
        index = jumps[index];
        more = true;
      }
    }
    // Exit.
    else if (event[1]._container) {
      otherIndex = index;
      lineIndex = undefined;
      while (otherIndex--) {
        otherEvent = events.get(otherIndex);
        if (otherEvent[1].type === "lineEnding" || otherEvent[1].type === "lineEndingBlank") {
          if (otherEvent[0] === 'enter') {
            if (lineIndex) {
              events.get(lineIndex)[1].type = "lineEndingBlank";
            }
            otherEvent[1].type = "lineEnding";
            lineIndex = otherIndex;
          }
        } else if (otherEvent[1].type === "linePrefix" || otherEvent[1].type === "listItemIndent") {
          // Move past.
        } else {
          break;
        }
      }
      if (lineIndex) {
        // Fix position.
        event[1].end = {
          ...events.get(lineIndex)[1].start
        };

        // Switch container exit w/ line endings.
        parameters = events.slice(lineIndex, index);
        parameters.unshift(event);
        events.splice(lineIndex, index - lineIndex + 1, parameters);
      }
    }
  }

  // The changes to the `events` buffer must be copied back into the eventsArray
  splice(eventsArray, 0, Number.POSITIVE_INFINITY, events.slice(0));
  return !more;
}

/**
 * Tokenize embedded tokens.
 *
 * @param {SpliceBuffer<Event>} events
 *   Events.
 * @param {number} eventIndex
 *   Index.
 * @returns {Record<string, number>}
 *   Gaps.
 */
function subcontent(events, eventIndex) {
  const token = events.get(eventIndex)[1];
  const context = events.get(eventIndex)[2];
  let startPosition = eventIndex - 1;
  /** @type {Array<number>} */
  const startPositions = [];
  let tokenizer = token._tokenizer;
  if (!tokenizer) {
    tokenizer = context.parser[token.contentType](token.start);
    if (token._contentTypeTextTrailing) {
      tokenizer._contentTypeTextTrailing = true;
    }
  }
  const childEvents = tokenizer.events;
  /** @type {Array<[number, number]>} */
  const jumps = [];
  /** @type {Record<string, number>} */
  const gaps = {};
  /** @type {Array<Chunk>} */
  let stream;
  /** @type {Token | undefined} */
  let previous;
  let index = -1;
  /** @type {Token | undefined} */
  let current = token;
  let adjust = 0;
  let start = 0;
  const breaks = [start];

  // Loop forward through the linked tokens to pass them in order to the
  // subtokenizer.
  while (current) {
    // Find the position of the event for this token.
    while (events.get(++startPosition)[1] !== current) {
      // Empty.
    }
    startPositions.push(startPosition);
    if (!current._tokenizer) {
      stream = context.sliceStream(current);
      if (!current.next) {
        stream.push(null);
      }
      if (previous) {
        tokenizer.defineSkip(current.start);
      }
      if (current._isInFirstContentOfListItem) {
        tokenizer._gfmTasklistFirstContentOfListItem = true;
      }
      tokenizer.write(stream);
      if (current._isInFirstContentOfListItem) {
        tokenizer._gfmTasklistFirstContentOfListItem = undefined;
      }
    }

    // Unravel the next token.
    previous = current;
    current = current.next;
  }

  // Now, loop back through all events (and linked tokens), to figure out which
  // parts belong where.
  current = token;
  while (++index < childEvents.length) {
    if (
    // Find a void token that includes a break.
    childEvents[index][0] === 'exit' && childEvents[index - 1][0] === 'enter' && childEvents[index][1].type === childEvents[index - 1][1].type && childEvents[index][1].start.line !== childEvents[index][1].end.line) {
      start = index + 1;
      breaks.push(start);
      // Help GC.
      current._tokenizer = undefined;
      current.previous = undefined;
      current = current.next;
    }
  }

  // Help GC.
  tokenizer.events = [];

  // If there’s one more token (which is the cases for lines that end in an
  // EOF), that’s perfect: the last point we found starts it.
  // If there isn’t then make sure any remaining content is added to it.
  if (current) {
    // Help GC.
    current._tokenizer = undefined;
    current.previous = undefined;
  } else {
    breaks.pop();
  }

  // Now splice the events from the subtokenizer into the current events,
  // moving back to front so that splice indices aren’t affected.
  index = breaks.length;
  while (index--) {
    const slice = childEvents.slice(breaks[index], breaks[index + 1]);
    const start = startPositions.pop();
    jumps.push([start, start + slice.length - 1]);
    events.splice(start, 2, slice);
  }
  jumps.reverse();
  index = -1;
  while (++index < jumps.length) {
    gaps[adjust + jumps[index][0]] = adjust + jumps[index][1];
    adjust += jumps[index][1] - jumps[index][0] - 1;
  }
  return gaps;
}/**
 * Some of the internal operations of micromark do lots of editing
 * operations on very large arrays. This runs into problems with two
 * properties of most circa-2020 JavaScript interpreters:
 *
 *  - Array-length modifications at the high end of an array (push/pop) are
 *    expected to be common and are implemented in (amortized) time
 *    proportional to the number of elements added or removed, whereas
 *    other operations (shift/unshift and splice) are much less efficient.
 *  - Function arguments are passed on the stack, so adding tens of thousands
 *    of elements to an array with `arr.push(...newElements)` will frequently
 *    cause stack overflows. (see <https://stackoverflow.com/questions/22123769/rangeerror-maximum-call-stack-size-exceeded-why>)
 *
 * SpliceBuffers are an implementation of gap buffers, which are a
 * generalization of the "queue made of two stacks" idea. The splice buffer
 * maintains a cursor, and moving the cursor has cost proportional to the
 * distance the cursor moves, but inserting, deleting, or splicing in
 * new information at the cursor is as efficient as the push/pop operation.
 * This allows for an efficient sequence of splices (or pushes, pops, shifts,
 * or unshifts) as long such edits happen at the same part of the array or
 * generally sweep through the array from the beginning to the end.
 *
 * The interface for splice buffers also supports large numbers of inputs by
 * passing a single array argument rather passing multiple arguments on the
 * function call stack.
 *
 * @template T
 *   Item type.
 */
export class SpliceBuffer {
  /**
   * @param {ReadonlyArray<T> | null | undefined} [initial]
   *   Initial items (optional).
   * @returns
   *   Splice buffer.
   */
  constructor(initial) {
    /** @type {Array<T>} */
    this.left = initial ? [...initial] : [];
    /** @type {Array<T>} */
    this.right = [];
  }

  /**
   * Array access;
   * does not move the cursor.
   *
   * @param {number} index
   *   Index.
   * @return {T}
   *   Item.
   */
  get(index) {
    if (index < 0 || index >= this.left.length + this.right.length) {
      throw new RangeError('Cannot access index `' + index + '` in a splice buffer of size `' + (this.left.length + this.right.length) + '`');
    }
    if (index < this.left.length) return this.left[index];
    return this.right[this.right.length - index + this.left.length - 1];
  }

  /**
   * The length of the splice buffer, one greater than the largest index in the
   * array.
   */
  get length() {
    return this.left.length + this.right.length;
  }

  /**
   * Remove and return `list[0]`;
   * moves the cursor to `0`.
   *
   * @returns {T | undefined}
   *   Item, optional.
   */
  shift() {
    this.setCursor(0);
    return this.right.pop();
  }

  /**
   * Slice the buffer to get an array;
   * does not move the cursor.
   *
   * @param {number} start
   *   Start.
   * @param {number | null | undefined} [end]
   *   End (optional).
   * @returns {Array<T>}
   *   Array of items.
   */
  slice(start, end) {
    /** @type {number} */
    const stop = end === null || end === undefined ? Number.POSITIVE_INFINITY : end;
    if (stop < this.left.length) {
      return this.left.slice(start, stop);
    }
    if (start > this.left.length) {
      return this.right.slice(this.right.length - stop + this.left.length, this.right.length - start + this.left.length).reverse();
    }
    return this.left.slice(start).concat(this.right.slice(this.right.length - stop + this.left.length).reverse());
  }

  /**
   * Mimics the behavior of Array.prototype.splice() except for the change of
   * interface necessary to avoid segfaults when patching in very large arrays.
   *
   * This operation moves cursor is moved to `start` and results in the cursor
   * placed after any inserted items.
   *
   * @param {number} start
   *   Start;
   *   zero-based index at which to start changing the array;
   *   negative numbers count backwards from the end of the array and values
   *   that are out-of bounds are clamped to the appropriate end of the array.
   * @param {number | null | undefined} [deleteCount=0]
   *   Delete count (default: `0`);
   *   maximum number of elements to delete, starting from start.
   * @param {Array<T> | null | undefined} [items=[]]
   *   Items to include in place of the deleted items (default: `[]`).
   * @return {Array<T>}
   *   Any removed items.
   */
  splice(start, deleteCount, items) {
    /** @type {number} */
    const count = deleteCount || 0;
    this.setCursor(Math.trunc(start));
    const removed = this.right.splice(this.right.length - count, Number.POSITIVE_INFINITY);
    if (items) chunkedPush(this.left, items);
    return removed.reverse();
  }

  /**
   * Remove and return the highest-numbered item in the array, so
   * `list[list.length - 1]`;
   * Moves the cursor to `length`.
   *
   * @returns {T | undefined}
   *   Item, optional.
   */
  pop() {
    this.setCursor(Number.POSITIVE_INFINITY);
    return this.left.pop();
  }

  /**
   * Inserts a single item to the high-numbered side of the array;
   * moves the cursor to `length`.
   *
   * @param {T} item
   *   Item.
   * @returns {undefined}
   *   Nothing.
   */
  push(item) {
    this.setCursor(Number.POSITIVE_INFINITY);
    this.left.push(item);
  }

  /**
   * Inserts many items to the high-numbered side of the array.
   * Moves the cursor to `length`.
   *
   * @param {Array<T>} items
   *   Items.
   * @returns {undefined}
   *   Nothing.
   */
  pushMany(items) {
    this.setCursor(Number.POSITIVE_INFINITY);
    chunkedPush(this.left, items);
  }

  /**
   * Inserts a single item to the low-numbered side of the array;
   * Moves the cursor to `0`.
   *
   * @param {T} item
   *   Item.
   * @returns {undefined}
   *   Nothing.
   */
  unshift(item) {
    this.setCursor(0);
    this.right.push(item);
  }

  /**
   * Inserts many items to the low-numbered side of the array;
   * moves the cursor to `0`.
   *
   * @param {Array<T>} items
   *   Items.
   * @returns {undefined}
   *   Nothing.
   */
  unshiftMany(items) {
    this.setCursor(0);
    chunkedPush(this.right, items.reverse());
  }

  /**
   * Move the cursor to a specific position in the array. Requires
   * time proportional to the distance moved.
   *
   * If `n < 0`, the cursor will end up at the beginning.
   * If `n > length`, the cursor will end up at the end.
   *
   * @param {number} n
   *   Position.
   * @return {undefined}
   *   Nothing.
   */
  setCursor(n) {
    if (n === this.left.length || n > this.left.length && this.right.length === 0 || n < 0 && this.left.length === 0) return;
    if (n < this.left.length) {
      // Move cursor to the this.left
      const removed = this.left.splice(n, Number.POSITIVE_INFINITY);
      chunkedPush(this.right, removed.reverse());
    } else {
      // Move cursor to the this.right
      const removed = this.right.splice(this.left.length + this.right.length - n, Number.POSITIVE_INFINITY);
      chunkedPush(this.left, removed.reverse());
    }
  }
}

/**
 * Avoid stack overflow by pushing items onto the stack in segments
 *
 * @template T
 *   Item type.
 * @param {Array<T>} list
 *   List to inject into.
 * @param {ReadonlyArray<T>} right
 *   Items to inject.
 * @return {undefined}
 *   Nothing.
 */
function chunkedPush(list, right) {
  /** @type {number} */
  let chunkStart = 0;
  if (right.length < 10000) {
    list.push(...right);
  } else {
    while (chunkStart < right.length) {
      list.push(...right.slice(chunkStart, chunkStart + 10000));
      chunkStart += 10000;
    }
  }
}{
  "name": "micromark-util-subtokenize",
  "version": "2.1.0",
  "description": "micromark utility to tokenize subtokens",
  "license": "MIT",
  "keywords": [
    "micromark",
    "util",
    "utility",
    "tokenize"
  ],
  "repository": "https://github.com/micromark/micromark/tree/main/packages/micromark-util-subtokenize",
  "bugs": "https://github.com/micromark/micromark/issues",
  "funding": [
    {
      "type": "GitHub Sponsors",
      "url": "https://github.com/sponsors/unifiedjs"
    },
    {
      "type": "OpenCollective",
      "url": "https://opencollective.com/unified"
    }
  ],
  "author": "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)",
  "contributors": [
    "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)"
  ],
  "sideEffects": false,
  "type": "module",
  "files": [
    "dev/",
    "lib/",
    "index.d.ts.map",
    "index.d.ts",
    "index.js"
  ],
  "exports": {
    "development": "./dev/index.js",
    "default": "./index.js"
  },
  "dependencies": {
    "devlop": "^1.0.0",
    "micromark-util-chunked": "^2.0.0",
    "micromark-util-symbol": "^2.0.0",
    "micromark-util-types": "^2.0.0"
  },
  "scripts": {
    "build": "micromark-build"
  },
  "xo": {
    "envs": [
      "shared-node-browser"
    ],
    "prettier": true,
    "rules": {
      "max-depth": "off",
      "unicorn/prefer-code-point": "off"
    }
  }
}
/**
 * @import {Encoding, Value} from 'micromark-util-types'
 */

/**
 * @typedef {import('micromark-util-types').Options} Options
 */

import { compile } from './lib/compile.js';
import { parse } from './lib/parse.js';
import { postprocess } from './lib/postprocess.js';
import { preprocess } from './lib/preprocess.js';
export { compile } from './lib/compile.js';
export { parse } from './lib/parse.js';
export { postprocess } from './lib/postprocess.js';
export { preprocess } from './lib/preprocess.js';

/**
 * Compile markdown to HTML.
 *
 * > Note: which encodings are supported depends on the engine.
 * > For info on Node.js, see:
 * > <https://nodejs.org/api/util.html#whatwg-supported-encodings>.
 *
 * @overload
 * @param {Value} value
 *   Markdown to parse (`string` or `Uint8Array`).
 * @param {Encoding | null | undefined} encoding
 *   Character encoding to understand `value` as when it’s a `Uint8Array`
 *   (`string`, default: `'utf8'`).
 * @param {Options | null | undefined} [options]
 *   Configuration.
 * @returns {string}
 *   Compiled HTML.
 *
 * @overload
 * @param {Value} value
 *   Markdown to parse (`string` or `Uint8Array`).
 * @param {Options | null | undefined} [options]
 *   Configuration.
 * @returns {string}
 *   Compiled HTML.
 *
 * @param {Value} value
 *   Markdown to parse (`string` or `Uint8Array`).
 * @param {Encoding | Options | null | undefined} [encoding]
 *   Character encoding to understand `value` as when it’s a `Uint8Array`
 *   (`string`, default: `'utf8'`).
 * @param {Options | null | undefined} [options]
 *   Configuration.
 * @returns {string}
 *   Compiled HTML.
 */
export function micromark(value, encoding, options) {
  if (typeof encoding !== 'string') {
    options = encoding;
    encoding = undefined;
  }
  return compile(options)(postprocess(parse(options).document().write(preprocess()(value, encoding, true))));
}/**
 * While micromark is a lexer/tokenizer, the common case of going from markdown
 * to html is currently built in as this module, even though the parts can be
 * used separately to build ASTs, CSTs, or many other output formats.
 *
 * Having an HTML compiler built in is useful because it allows us to check for
 * compliancy to CommonMark, the de facto norm of markdown, specified in roughly
 * 600 input/output cases.
 *
 * This module has an interface that accepts lists of events instead of the
 * whole at once, however, because markdown can’t be truly streaming, we buffer
 * events before processing and outputting the final result.
 */

/**
 * @import {
 *   CompileContext,
 *   CompileData,
 *   CompileOptions,
 *   Compile,
 *   Definition,
 *   Event,
 *   Handle,
 *   HtmlExtension,
 *   LineEnding,
 *   NormalizedHtmlExtension,
 *   Token
 * } from 'micromark-util-types'
 */

/**
 * @typedef Media
 * @property {boolean | undefined} [image]
 * @property {string | undefined} [labelId]
 * @property {string | undefined} [label]
 * @property {string | undefined} [referenceId]
 * @property {string | undefined} [destination]
 * @property {string | undefined} [title]
 */

import { decodeNamedCharacterReference } from 'decode-named-character-reference';
import { push } from 'micromark-util-chunked';
import { combineHtmlExtensions } from 'micromark-util-combine-extensions';
import { decodeNumericCharacterReference } from 'micromark-util-decode-numeric-character-reference';
import { encode as _encode } from 'micromark-util-encode';
import { normalizeIdentifier } from 'micromark-util-normalize-identifier';
import { sanitizeUri } from 'micromark-util-sanitize-uri';
const hasOwnProperty = {}.hasOwnProperty;

/**
 * These two are allowlists of safe protocols for full URLs in respectively the
 * `href` (on `<a>`) and `src` (on `<img>`) attributes.
 * They are based on what is allowed on GitHub,
 * <https://github.com/syntax-tree/hast-util-sanitize/blob/9275b21/lib/github.json#L31>
 */
const protocolHref = /^(https?|ircs?|mailto|xmpp)$/i;
const protocolSource = /^https?$/i;

/**
 * @param {CompileOptions | null | undefined} [options]
 * @returns {Compile}
 */
export function compile(options) {
  const settings = options || {};

  /**
   * Tags is needed because according to markdown, links and emphasis and
   * whatnot can exist in images, however, as HTML doesn’t allow content in
   * images, the tags are ignored in the `alt` attribute, but the content
   * remains.
   *
   * @type {boolean | undefined}
   */
  let tags = true;

  /**
   * An object to track identifiers to media (URLs and titles) defined with
   * definitions.
   *
   * @type {Record<string, Definition>}
   */
  const definitions = {};

  /**
   * A lot of the handlers need to capture some of the output data, modify it
   * somehow, and then deal with it.
   * We do that by tracking a stack of buffers, that can be opened (with
   * `buffer`) and closed (with `resume`) to access them.
   *
   * @type {Array<Array<string>>}
   */
  const buffers = [[]];

  /**
   * As we can have links in images and the other way around, where the deepest
   * ones are closed first, we need to track which one we’re in.
   *
   * @type {Array<Media>}
   */
  const mediaStack = [];

  /**
   * Same as `mediaStack` for tightness, which is specific to lists.
   * We need to track if we’re currently in a tight or loose container.
   *
   * @type {Array<boolean>}
   */
  const tightStack = [];

  /** @type {HtmlExtension} */
  const defaultHandlers = {
    enter: {
      blockQuote: onenterblockquote,
      codeFenced: onentercodefenced,
      codeFencedFenceInfo: buffer,
      codeFencedFenceMeta: buffer,
      codeIndented: onentercodeindented,
      codeText: onentercodetext,
      content: onentercontent,
      definition: onenterdefinition,
      definitionDestinationString: onenterdefinitiondestinationstring,
      definitionLabelString: buffer,
      definitionTitleString: buffer,
      emphasis: onenteremphasis,
      htmlFlow: onenterhtmlflow,
      htmlText: onenterhtml,
      image: onenterimage,
      label: buffer,
      link: onenterlink,
      listItemMarker: onenterlistitemmarker,
      listItemValue: onenterlistitemvalue,
      listOrdered: onenterlistordered,
      listUnordered: onenterlistunordered,
      paragraph: onenterparagraph,
      reference: buffer,
      resource: onenterresource,
      resourceDestinationString: onenterresourcedestinationstring,
      resourceTitleString: buffer,
      setextHeading: onentersetextheading,
      strong: onenterstrong
    },
    exit: {
      atxHeading: onexitatxheading,
      atxHeadingSequence: onexitatxheadingsequence,
      autolinkEmail: onexitautolinkemail,
      autolinkProtocol: onexitautolinkprotocol,
      blockQuote: onexitblockquote,
      characterEscapeValue: onexitdata,
      characterReferenceMarkerHexadecimal: onexitcharacterreferencemarker,
      characterReferenceMarkerNumeric: onexitcharacterreferencemarker,
      characterReferenceValue: onexitcharacterreferencevalue,
      codeFenced: onexitflowcode,
      codeFencedFence: onexitcodefencedfence,
      codeFencedFenceInfo: onexitcodefencedfenceinfo,
      codeFencedFenceMeta: onresumedrop,
      codeFlowValue: onexitcodeflowvalue,
      codeIndented: onexitflowcode,
      codeText: onexitcodetext,
      codeTextData: onexitdata,
      data: onexitdata,
      definition: onexitdefinition,
      definitionDestinationString: onexitdefinitiondestinationstring,
      definitionLabelString: onexitdefinitionlabelstring,
      definitionTitleString: onexitdefinitiontitlestring,
      emphasis: onexitemphasis,
      hardBreakEscape: onexithardbreak,
      hardBreakTrailing: onexithardbreak,
      htmlFlow: onexithtml,
      htmlFlowData: onexitdata,
      htmlText: onexithtml,
      htmlTextData: onexitdata,
      image: onexitmedia,
      label: onexitlabel,
      labelText: onexitlabeltext,
      lineEnding: onexitlineending,
      link: onexitmedia,
      listOrdered: onexitlistordered,
      listUnordered: onexitlistunordered,
      paragraph: onexitparagraph,
      reference: onresumedrop,
      referenceString: onexitreferencestring,
      resource: onresumedrop,
      resourceDestinationString: onexitresourcedestinationstring,
      resourceTitleString: onexitresourcetitlestring,
      setextHeading: onexitsetextheading,
      setextHeadingLineSequence: onexitsetextheadinglinesequence,
      setextHeadingText: onexitsetextheadingtext,
      strong: onexitstrong,
      thematicBreak: onexitthematicbreak
    }
  };

  /**
   * Combine the HTML extensions with the default handlers.
   * An HTML extension is an object whose fields are either `enter` or `exit`
   * (reflecting whether a token is entered or exited).
   * The values at such objects are names of tokens mapping to handlers.
   * Handlers are called, respectively when a token is opener or closed, with
   * that token, and a context as `this`.
   */
  const handlers = /** @type {NormalizedHtmlExtension} */
  combineHtmlExtensions([defaultHandlers, ...(settings.htmlExtensions || [])]);

  /**
   * Handlers do often need to keep track of some state.
   * That state is provided here as a key-value store (an object).
   *
   * @type {CompileData}
   */
  const data = {
    definitions,
    tightStack
  };

  /**
   * The context for handlers references a couple of useful functions.
   * In handlers from extensions, those can be accessed at `this`.
   * For the handlers here, they can be accessed directly.
   *
   * @type {Omit<CompileContext, 'sliceSerialize'>}
   */
  const context = {
    buffer,
    encode,
    getData,
    lineEndingIfNeeded,
    options: settings,
    raw,
    resume,
    setData,
    tag
  };

  /**
   * Generally, micromark copies line endings (`'\r'`, `'\n'`, `'\r\n'`) in the
   * markdown document over to the compiled HTML.
   * In some cases, such as `> a`, CommonMark requires that extra line endings
   * are added: `<blockquote>\n<p>a</p>\n</blockquote>`.
   * This variable hold the default line ending when given (or `undefined`),
   * and in the latter case will be updated to the first found line ending if
   * there is one.
   */
  let lineEndingStyle = settings.defaultLineEnding;

  // Return the function that handles a slice of events.
  return compile;

  /**
   * Deal w/ a slice of events.
   * Return either the empty string if there’s nothing of note to return, or the
   * result when done.
   *
   * @param {ReadonlyArray<Event>} events
   * @returns {string}
   */
  function compile(events) {
    let index = -1;
    let start = 0;
    /** @type {Array<number>} */
    const listStack = [];
    // As definitions can come after references, we need to figure out the media
    // (urls and titles) defined by them before handling the references.
    // So, we do sort of what HTML does: put metadata at the start (in head), and
    // then put content after (`body`).
    /** @type {Array<Event>} */
    let head = [];
    /** @type {Array<Event>} */
    let body = [];
    while (++index < events.length) {
      // Figure out the line ending style used in the document.
      if (!lineEndingStyle && (events[index][1].type === "lineEnding" || events[index][1].type === "lineEndingBlank")) {
        lineEndingStyle = /** @type {LineEnding} */
        events[index][2].sliceSerialize(events[index][1]);
      }

      // Preprocess lists to infer whether the list is loose or not.
      if (events[index][1].type === "listOrdered" || events[index][1].type === "listUnordered") {
        if (events[index][0] === 'enter') {
          listStack.push(index);
        } else {
          prepareList(events.slice(listStack.pop(), index));
        }
      }

      // Move definitions to the front.
      if (events[index][1].type === "definition") {
        if (events[index][0] === 'enter') {
          body = push(body, events.slice(start, index));
          start = index;
        } else {
          head = push(head, events.slice(start, index + 1));
          start = index + 1;
        }
      }
    }
    head = push(head, body);
    head = push(head, events.slice(start));
    index = -1;
    const result = head;

    // Handle the start of the document, if defined.
    if (handlers.enter.null) {
      handlers.enter.null.call(context);
    }

    // Handle all events.
    while (++index < events.length) {
      const handles = handlers[result[index][0]];
      const kind = result[index][1].type;
      const handle = handles[kind];
      if (hasOwnProperty.call(handles, kind) && handle) {
        handle.call({
          sliceSerialize: result[index][2].sliceSerialize,
          ...context
        }, result[index][1]);
      }
    }

    // Handle the end of the document, if defined.
    if (handlers.exit.null) {
      handlers.exit.null.call(context);
    }
    return buffers[0].join('');
  }

  /**
   * Figure out whether lists are loose or not.
   *
   * @param {ReadonlyArray<Event>} slice
   * @returns {undefined}
   */
  function prepareList(slice) {
    const length = slice.length;
    let index = 0; // Skip open.
    let containerBalance = 0;
    let loose = false;
    /** @type {boolean | undefined} */
    let atMarker;
    while (++index < length) {
      const event = slice[index];
      if (event[1]._container) {
        atMarker = undefined;
        if (event[0] === 'enter') {
          containerBalance++;
        } else {
          containerBalance--;
        }
      } else switch (event[1].type) {
        case "listItemPrefix":
          {
            if (event[0] === 'exit') {
              atMarker = true;
            }
            break;
          }
        case "linePrefix":
          {
            // Ignore

            break;
          }
        case "lineEndingBlank":
          {
            if (event[0] === 'enter' && !containerBalance) {
              if (atMarker) {
                atMarker = undefined;
              } else {
                loose = true;
              }
            }
            break;
          }
        default:
          {
            atMarker = undefined;
          }
      }
    }
    slice[0][1]._loose = loose;
  }

  /**
   * @type {CompileContext['setData']}
   */
  function setData(key, value) {
    // @ts-expect-error: assume `value` is omitted (`undefined` is passed) only
    // if allowed.
    data[key] = value;
  }

  /**
   * @type {CompileContext['getData']}
   */
  function getData(key) {
    return data[key];
  }

  /** @type {CompileContext['buffer']} */
  function buffer() {
    buffers.push([]);
  }

  /** @type {CompileContext['resume']} */
  function resume() {
    const buf = buffers.pop();
    return buf.join('');
  }

  /** @type {CompileContext['tag']} */
  function tag(value) {
    if (!tags) return;
    setData('lastWasTag', true);
    buffers[buffers.length - 1].push(value);
  }

  /** @type {CompileContext['raw']} */
  function raw(value) {
    setData('lastWasTag');
    buffers[buffers.length - 1].push(value);
  }

  /**
   * Output an extra line ending.
   *
   * @returns {undefined}
   */
  function lineEnding() {
    raw(lineEndingStyle || '\n');
  }

  /** @type {CompileContext['lineEndingIfNeeded']} */
  function lineEndingIfNeeded() {
    const buffer = buffers[buffers.length - 1];
    const slice = buffer[buffer.length - 1];
    const previous = slice ? slice.charCodeAt(slice.length - 1) : null;
    if (previous === 10 || previous === 13 || previous === null) {
      return;
    }
    lineEnding();
  }

  /** @type {CompileContext['encode']} */
  function encode(value) {
    return getData('ignoreEncode') ? value : _encode(value);
  }

  //
  // Handlers.
  //

  /**
   * @returns {undefined}
   */
  function onresumedrop() {
    resume();
  }

  /**
   * @this {CompileContext}
   * @type {Handle}
   */
  function onenterlistordered(token) {
    tightStack.push(!token._loose);
    lineEndingIfNeeded();
    tag('<ol');
    setData('expectFirstItem', true);
  }

  /**
   * @this {CompileContext}
   * @type {Handle}
   */
  function onenterlistunordered(token) {
    tightStack.push(!token._loose);
    lineEndingIfNeeded();
    tag('<ul');
    setData('expectFirstItem', true);
  }

  /**
   * @this {CompileContext}
   * @type {Handle}
   */
  function onenterlistitemvalue(token) {
    if (getData('expectFirstItem')) {
      const value = Number.parseInt(this.sliceSerialize(token), 10);
      if (value !== 1) {
        tag(' start="' + encode(String(value)) + '"');
      }
    }
  }

  /**
   * @returns {undefined}
   */
  function onenterlistitemmarker() {
    if (getData('expectFirstItem')) {
      tag('>');
    } else {
      onexitlistitem();
    }
    lineEndingIfNeeded();
    tag('<li>');
    setData('expectFirstItem');
    // “Hack” to prevent a line ending from showing up if the item is empty.
    setData('lastWasTag');
  }

  /**
   * @returns {undefined}
   */
  function onexitlistordered() {
    onexitlistitem();
    tightStack.pop();
    lineEnding();
    tag('</ol>');
  }

  /**
   * @returns {undefined}
   */
  function onexitlistunordered() {
    onexitlistitem();
    tightStack.pop();
    lineEnding();
    tag('</ul>');
  }

  /**
   * @returns {undefined}
   */
  function onexitlistitem() {
    if (getData('lastWasTag') && !getData('slurpAllLineEndings')) {
      lineEndingIfNeeded();
    }
    tag('</li>');
    setData('slurpAllLineEndings');
  }

  /**
   * @this {CompileContext}
   * @type {Handle}
   */
  function onenterblockquote() {
    tightStack.push(false);
    lineEndingIfNeeded();
    tag('<blockquote>');
  }

  /**
   * @this {CompileContext}
   * @type {Handle}
   */
  function onexitblockquote() {
    tightStack.pop();
    lineEndingIfNeeded();
    tag('</blockquote>');
    setData('slurpAllLineEndings');
  }

  /**
   * @this {CompileContext}
   * @type {Handle}
   */
  function onenterparagraph() {
    if (!tightStack[tightStack.length - 1]) {
      lineEndingIfNeeded();
      tag('<p>');
    }
    setData('slurpAllLineEndings');
  }

  /**
   * @this {CompileContext}
   * @type {Handle}
   */
  function onexitparagraph() {
    if (tightStack[tightStack.length - 1]) {
      setData('slurpAllLineEndings', true);
    } else {
      tag('</p>');
    }
  }

  /**
   * @this {CompileContext}
   * @type {Handle}
   */
  function onentercodefenced() {
    lineEndingIfNeeded();
    tag('<pre><code');
    setData('fencesCount', 0);
  }

  /**
   * @this {CompileContext}
   * @type {Handle}
   */
  function onexitcodefencedfenceinfo() {
    const value = resume();
    tag(' class="language-' + value + '"');
  }

  /**
   * @this {CompileContext}
   * @type {Handle}
   */
  function onexitcodefencedfence() {
    const count = getData('fencesCount') || 0;
    if (!count) {
      tag('>');
      setData('slurpOneLineEnding', true);
    }
    setData('fencesCount', count + 1);
  }

  /**
   * @this {CompileContext}
   * @type {Handle}
   */
  function onentercodeindented() {
    lineEndingIfNeeded();
    tag('<pre><code>');
  }

  /**
   * @this {CompileContext}
   * @type {Handle}
   */
  function onexitflowcode() {
    const count = getData('fencesCount');

    // One special case is if we are inside a container, and the fenced code was
    // not closed (meaning it runs to the end).
    // In that case, the following line ending, is considered *outside* the
    // fenced code and block quote by micromark, but CM wants to treat that
    // ending as part of the code.
    if (count !== undefined && count < 2 && data.tightStack.length > 0 && !getData('lastWasTag')) {
      lineEnding();
    }

    // But in most cases, it’s simpler: when we’ve seen some data, emit an extra
    // line ending when needed.
    if (getData('flowCodeSeenData')) {
      lineEndingIfNeeded();
    }
    tag('</code></pre>');
    if (count !== undefined && count < 2) lineEndingIfNeeded();
    setData('flowCodeSeenData');
    setData('fencesCount');
    setData('slurpOneLineEnding');
  }

  /**
   * @this {CompileContext}
   * @type {Handle}
   */
  function onenterimage() {
    mediaStack.push({
      image: true
    });
    tags = undefined; // Disallow tags.
  }

  /**
   * @this {CompileContext}
   * @type {Handle}
   */
  function onenterlink() {
    mediaStack.push({});
  }

  /**
   * @this {CompileContext}
   * @type {Handle}
   */
  function onexitlabeltext(token) {
    mediaStack[mediaStack.length - 1].labelId = this.sliceSerialize(token);
  }

  /**
   * @this {CompileContext}
   * @type {Handle}
   */
  function onexitlabel() {
    mediaStack[mediaStack.length - 1].label = resume();
  }

  /**
   * @this {CompileContext}
   * @type {Handle}
   */
  function onexitreferencestring(token) {
    mediaStack[mediaStack.length - 1].referenceId = this.sliceSerialize(token);
  }

  /**
   * @this {CompileContext}
   * @type {Handle}
   */
  function onenterresource() {
    buffer(); // We can have line endings in the resource, ignore them.
    mediaStack[mediaStack.length - 1].destination = '';
  }

  /**
   * @this {CompileContext}
   * @type {Handle}
   */
  function onenterresourcedestinationstring() {
    buffer();
    // Ignore encoding the result, as we’ll first percent encode the url and
    // encode manually after.
    setData('ignoreEncode', true);
  }

  /**
   * @this {CompileContext}
   * @type {Handle}
   */
  function onexitresourcedestinationstring() {
    mediaStack[mediaStack.length - 1].destination = resume();
    setData('ignoreEncode');
  }

  /**
   * @this {CompileContext}
   * @type {Handle}
   */
  function onexitresourcetitlestring() {
    mediaStack[mediaStack.length - 1].title = resume();
  }

  /**
   * @this {CompileContext}
   * @type {Handle}
   */
  function onexitmedia() {
    let index = mediaStack.length - 1; // Skip current.
    const media = mediaStack[index];
    const id = media.referenceId || media.labelId;
    const context = media.destination === undefined ? definitions[normalizeIdentifier(id)] : media;
    tags = true;
    while (index--) {
      if (mediaStack[index].image) {
        tags = undefined;
        break;
      }
    }
    if (media.image) {
      tag('<img src="' + sanitizeUri(context.destination, settings.allowDangerousProtocol ? undefined : protocolSource) + '" alt="');
      raw(media.label);
      tag('"');
    } else {
      tag('<a href="' + sanitizeUri(context.destination, settings.allowDangerousProtocol ? undefined : protocolHref) + '"');
    }
    tag(context.title ? ' title="' + context.title + '"' : '');
    if (media.image) {
      tag(' />');
    } else {
      tag('>');
      raw(media.label);
      tag('</a>');
    }
    mediaStack.pop();
  }

  /**
   * @this {CompileContext}
   * @type {Handle}
   */
  function onenterdefinition() {
    buffer();
    mediaStack.push({});
  }

  /**
   * @this {CompileContext}
   * @type {Handle}
   */
  function onexitdefinitionlabelstring(token) {
    // Discard label, use the source content instead.
    resume();
    mediaStack[mediaStack.length - 1].labelId = this.sliceSerialize(token);
  }

  /**
   * @this {CompileContext}
   * @type {Handle}
   */
  function onenterdefinitiondestinationstring() {
    buffer();
    setData('ignoreEncode', true);
  }

  /**
   * @this {CompileContext}
   * @type {Handle}
   */
  function onexitdefinitiondestinationstring() {
    mediaStack[mediaStack.length - 1].destination = resume();
    setData('ignoreEncode');
  }

  /**
   * @this {CompileContext}
   * @type {Handle}
   */
  function onexitdefinitiontitlestring() {
    mediaStack[mediaStack.length - 1].title = resume();
  }

  /**
   * @this {CompileContext}
   * @type {Handle}
   */
  function onexitdefinition() {
    const media = mediaStack[mediaStack.length - 1];
    const id = normalizeIdentifier(media.labelId);
    resume();
    if (!hasOwnProperty.call(definitions, id)) {
      definitions[id] = mediaStack[mediaStack.length - 1];
    }
    mediaStack.pop();
  }

  /**
   * @this {CompileContext}
   * @type {Handle}
   */
  function onentercontent() {
    setData('slurpAllLineEndings', true);
  }

  /**
   * @this {CompileContext}
   * @type {Handle}
   */
  function onexitatxheadingsequence(token) {
    // Exit for further sequences.
    if (getData('headingRank')) return;
    setData('headingRank', this.sliceSerialize(token).length);
    lineEndingIfNeeded();
    tag('<h' + getData('headingRank') + '>');
  }

  /**
   * @this {CompileContext}
   * @type {Handle}
   */
  function onentersetextheading() {
    buffer();
    setData('slurpAllLineEndings');
  }

  /**
   * @this {CompileContext}
   * @type {Handle}
   */
  function onexitsetextheadingtext() {
    setData('slurpAllLineEndings', true);
  }

  /**
   * @this {CompileContext}
   * @type {Handle}
   */
  function onexitatxheading() {
    tag('</h' + getData('headingRank') + '>');
    setData('headingRank');
  }

  /**
   * @this {CompileContext}
   * @type {Handle}
   */
  function onexitsetextheadinglinesequence(token) {
    setData('headingRank', this.sliceSerialize(token).charCodeAt(0) === 61 ? 1 : 2);
  }

  /**
   * @this {CompileContext}
   * @type {Handle}
   */
  function onexitsetextheading() {
    const value = resume();
    lineEndingIfNeeded();
    tag('<h' + getData('headingRank') + '>');
    raw(value);
    tag('</h' + getData('headingRank') + '>');
    setData('slurpAllLineEndings');
    setData('headingRank');
  }

  /**
   * @this {CompileContext}
   * @type {Handle}
   */
  function onexitdata(token) {
    raw(encode(this.sliceSerialize(token)));
  }

  /**
   * @this {CompileContext}
   * @type {Handle}
   */
  function onexitlineending(token) {
    if (getData('slurpAllLineEndings')) {
      return;
    }
    if (getData('slurpOneLineEnding')) {
      setData('slurpOneLineEnding');
      return;
    }
    if (getData('inCodeText')) {
      raw(' ');
      return;
    }
    raw(encode(this.sliceSerialize(token)));
  }

  /**
   * @this {CompileContext}
   * @type {Handle}
   */
  function onexitcodeflowvalue(token) {
    raw(encode(this.sliceSerialize(token)));
    setData('flowCodeSeenData', true);
  }

  /**
   * @this {CompileContext}
   * @type {Handle}
   */
  function onexithardbreak() {
    tag('<br />');
  }

  /**
   * @returns {undefined}
   */
  function onenterhtmlflow() {
    lineEndingIfNeeded();
    onenterhtml();
  }

  /**
   * @returns {undefined}
   */
  function onexithtml() {
    setData('ignoreEncode');
  }

  /**
   * @returns {undefined}
   */
  function onenterhtml() {
    if (settings.allowDangerousHtml) {
      setData('ignoreEncode', true);
    }
  }

  /**
   * @returns {undefined}
   */
  function onenteremphasis() {
    tag('<em>');
  }

  /**
   * @returns {undefined}
   */
  function onenterstrong() {
    tag('<strong>');
  }

  /**
   * @returns {undefined}
   */
  function onentercodetext() {
    setData('inCodeText', true);
    tag('<code>');
  }

  /**
   * @returns {undefined}
   */
  function onexitcodetext() {
    setData('inCodeText');
    tag('</code>');
  }

  /**
   * @returns {undefined}
   */
  function onexitemphasis() {
    tag('</em>');
  }

  /**
   * @returns {undefined}
   */
  function onexitstrong() {
    tag('</strong>');
  }

  /**
   * @returns {undefined}
   */
  function onexitthematicbreak() {
    lineEndingIfNeeded();
    tag('<hr />');
  }

  /**
   * @this {CompileContext}
   * @param {Token} token
   * @returns {undefined}
   */
  function onexitcharacterreferencemarker(token) {
    setData('characterReferenceType', token.type);
  }

  /**
   * @this {CompileContext}
   * @type {Handle}
   */
  function onexitcharacterreferencevalue(token) {
    const value = this.sliceSerialize(token);
    const decoded = getData('characterReferenceType') ? decodeNumericCharacterReference(value, getData('characterReferenceType') === "characterReferenceMarkerNumeric" ? 10 : 16) : decodeNamedCharacterReference(value);

    // `decodeNamedCharacterReference` can return `false` for invalid named
    // character references,
    // but everything we’ve tokenized is valid.
    raw(encode(/** @type {string} */decoded));
    setData('characterReferenceType');
  }

  /**
   * @this {CompileContext}
   * @type {Handle}
   */
  function onexitautolinkprotocol(token) {
    const uri = this.sliceSerialize(token);
    tag('<a href="' + sanitizeUri(uri, settings.allowDangerousProtocol ? undefined : protocolHref) + '">');
    raw(encode(uri));
    tag('</a>');
  }

  /**
   * @this {CompileContext}
   * @type {Handle}
   */
  function onexitautolinkemail(token) {
    const uri = this.sliceSerialize(token);
    tag('<a href="' + sanitizeUri('mailto:' + uri) + '">');
    raw(encode(uri));
    tag('</a>');
  }
}/**
 * @import {Extension} from 'micromark-util-types'
 */

import { attention, autolink, blockQuote, characterEscape, characterReference, codeFenced, codeIndented, codeText, definition, hardBreakEscape, headingAtx, htmlFlow, htmlText, labelEnd, labelStartImage, labelStartLink, lineEnding, list, setextUnderline, thematicBreak } from 'micromark-core-commonmark';
import { resolver as resolveText } from './initialize/text.js';

/** @satisfies {Extension['document']} */
export const document = {
  [42]: list,
  [43]: list,
  [45]: list,
  [48]: list,
  [49]: list,
  [50]: list,
  [51]: list,
  [52]: list,
  [53]: list,
  [54]: list,
  [55]: list,
  [56]: list,
  [57]: list,
  [62]: blockQuote
};

/** @satisfies {Extension['contentInitial']} */
export const contentInitial = {
  [91]: definition
};

/** @satisfies {Extension['flowInitial']} */
export const flowInitial = {
  [-2]: codeIndented,
  [-1]: codeIndented,
  [32]: codeIndented
};

/** @satisfies {Extension['flow']} */
export const flow = {
  [35]: headingAtx,
  [42]: thematicBreak,
  [45]: [setextUnderline, thematicBreak],
  [60]: htmlFlow,
  [61]: setextUnderline,
  [95]: thematicBreak,
  [96]: codeFenced,
  [126]: codeFenced
};

/** @satisfies {Extension['string']} */
export const string = {
  [38]: characterReference,
  [92]: characterEscape
};

/** @satisfies {Extension['text']} */
export const text = {
  [-5]: lineEnding,
  [-4]: lineEnding,
  [-3]: lineEnding,
  [33]: labelStartImage,
  [38]: characterReference,
  [42]: attention,
  [60]: [autolink, htmlText],
  [91]: labelStartLink,
  [92]: [hardBreakEscape, characterEscape],
  [93]: labelEnd,
  [95]: attention,
  [96]: codeText
};

/** @satisfies {Extension['insideSpan']} */
export const insideSpan = {
  null: [attention, resolveText]
};

/** @satisfies {Extension['attentionMarkers']} */
export const attentionMarkers = {
  null: [42, 95]
};

/** @satisfies {Extension['disable']} */
export const disable = {
  null: []
};/**
 * @import {
 *   Chunk,
 *   Code,
 *   ConstructRecord,
 *   Construct,
 *   Effects,
 *   InitialConstruct,
 *   ParseContext,
 *   Point,
 *   State,
 *   TokenizeContext,
 *   Token
 * } from 'micromark-util-types'
 */

/**
 * @callback Restore
 *   Restore the state.
 * @returns {undefined}
 *   Nothing.
 *
 * @typedef Info
 *   Info.
 * @property {Restore} restore
 *   Restore.
 * @property {number} from
 *   From.
 *
 * @callback ReturnHandle
 *   Handle a successful run.
 * @param {Construct} construct
 *   Construct.
 * @param {Info} info
 *   Info.
 * @returns {undefined}
 *   Nothing.
 */

import { markdownLineEnding } from 'micromark-util-character';
import { push, splice } from 'micromark-util-chunked';
import { resolveAll } from 'micromark-util-resolve-all';
/**
 * Create a tokenizer.
 * Tokenizers deal with one type of data (e.g., containers, flow, text).
 * The parser is the object dealing with it all.
 * `initialize` works like other constructs, except that only its `tokenize`
 * function is used, in which case it doesn’t receive an `ok` or `nok`.
 * `from` can be given to set the point before the first character, although
 * when further lines are indented, they must be set with `defineSkip`.
 *
 * @param {ParseContext} parser
 *   Parser.
 * @param {InitialConstruct} initialize
 *   Construct.
 * @param {Omit<Point, '_bufferIndex' | '_index'> | undefined} [from]
 *   Point (optional).
 * @returns {TokenizeContext}
 *   Context.
 */
export function createTokenizer(parser, initialize, from) {
  /** @type {Point} */
  let point = {
    _bufferIndex: -1,
    _index: 0,
    line: from && from.line || 1,
    column: from && from.column || 1,
    offset: from && from.offset || 0
  };
  /** @type {Record<string, number>} */
  const columnStart = {};
  /** @type {Array<Construct>} */
  const resolveAllConstructs = [];
  /** @type {Array<Chunk>} */
  let chunks = [];
  /** @type {Array<Token>} */
  let stack = [];
  /** @type {boolean | undefined} */
  let consumed = true;

  /**
   * Tools used for tokenizing.
   *
   * @type {Effects}
   */
  const effects = {
    attempt: constructFactory(onsuccessfulconstruct),
    check: constructFactory(onsuccessfulcheck),
    consume,
    enter,
    exit,
    interrupt: constructFactory(onsuccessfulcheck, {
      interrupt: true
    })
  };

  /**
   * State and tools for resolving and serializing.
   *
   * @type {TokenizeContext}
   */
  const context = {
    code: null,
    containerState: {},
    defineSkip,
    events: [],
    now,
    parser,
    previous: null,
    sliceSerialize,
    sliceStream,
    write
  };

  /**
   * The state function.
   *
   * @type {State | undefined}
   */
  let state = initialize.tokenize.call(context, effects);

  /**
   * Track which character we expect to be consumed, to catch bugs.
   *
   * @type {Code}
   */
  let expectedCode;
  if (initialize.resolveAll) {
    resolveAllConstructs.push(initialize);
  }
  return context;

  /** @type {TokenizeContext['write']} */
  function write(slice) {
    chunks = push(chunks, slice);
    main();

    // Exit if we’re not done, resolve might change stuff.
    if (chunks[chunks.length - 1] !== null) {
      return [];
    }
    addResult(initialize, 0);

    // Otherwise, resolve, and exit.
    context.events = resolveAll(resolveAllConstructs, context.events, context);
    return context.events;
  }

  //
  // Tools.
  //

  /** @type {TokenizeContext['sliceSerialize']} */
  function sliceSerialize(token, expandTabs) {
    return serializeChunks(sliceStream(token), expandTabs);
  }

  /** @type {TokenizeContext['sliceStream']} */
  function sliceStream(token) {
    return sliceChunks(chunks, token);
  }

  /** @type {TokenizeContext['now']} */
  function now() {
    // This is a hot path, so we clone manually instead of `Object.assign({}, point)`
    const {
      _bufferIndex,
      _index,
      line,
      column,
      offset
    } = point;
    return {
      _bufferIndex,
      _index,
      line,
      column,
      offset
    };
  }

  /** @type {TokenizeContext['defineSkip']} */
  function defineSkip(value) {
    columnStart[value.line] = value.column;
    accountForPotentialSkip();
  }

  //
  // State management.
  //

  /**
   * Main loop (note that `_index` and `_bufferIndex` in `point` are modified by
   * `consume`).
   * Here is where we walk through the chunks, which either include strings of
   * several characters, or numerical character codes.
   * The reason to do this in a loop instead of a call is so the stack can
   * drain.
   *
   * @returns {undefined}
   *   Nothing.
   */
  function main() {
    /** @type {number} */
    let chunkIndex;
    while (point._index < chunks.length) {
      const chunk = chunks[point._index];

      // If we’re in a buffer chunk, loop through it.
      if (typeof chunk === 'string') {
        chunkIndex = point._index;
        if (point._bufferIndex < 0) {
          point._bufferIndex = 0;
        }
        while (point._index === chunkIndex && point._bufferIndex < chunk.length) {
          go(chunk.charCodeAt(point._bufferIndex));
        }
      } else {
        go(chunk);
      }
    }
  }

  /**
   * Deal with one code.
   *
   * @param {Code} code
   *   Code.
   * @returns {undefined}
   *   Nothing.
   */
  function go(code) {
    consumed = undefined;
    expectedCode = code;
    state = state(code);
  }

  /** @type {Effects['consume']} */
  function consume(code) {
    if (markdownLineEnding(code)) {
      point.line++;
      point.column = 1;
      point.offset += code === -3 ? 2 : 1;
      accountForPotentialSkip();
    } else if (code !== -1) {
      point.column++;
      point.offset++;
    }

    // Not in a string chunk.
    if (point._bufferIndex < 0) {
      point._index++;
    } else {
      point._bufferIndex++;

      // At end of string chunk.
      if (point._bufferIndex ===
      // Points w/ non-negative `_bufferIndex` reference
      // strings.
      /** @type {string} */
      chunks[point._index].length) {
        point._bufferIndex = -1;
        point._index++;
      }
    }

    // Expose the previous character.
    context.previous = code;

    // Mark as consumed.
    consumed = true;
  }

  /** @type {Effects['enter']} */
  function enter(type, fields) {
    /** @type {Token} */
    // @ts-expect-error Patch instead of assign required fields to help GC.
    const token = fields || {};
    token.type = type;
    token.start = now();
    context.events.push(['enter', token, context]);
    stack.push(token);
    return token;
  }

  /** @type {Effects['exit']} */
  function exit(type) {
    const token = stack.pop();
    token.end = now();
    context.events.push(['exit', token, context]);
    return token;
  }

  /**
   * Use results.
   *
   * @type {ReturnHandle}
   */
  function onsuccessfulconstruct(construct, info) {
    addResult(construct, info.from);
  }

  /**
   * Discard results.
   *
   * @type {ReturnHandle}
   */
  function onsuccessfulcheck(_, info) {
    info.restore();
  }

  /**
   * Factory to attempt/check/interrupt.
   *
   * @param {ReturnHandle} onreturn
   *   Callback.
   * @param {{interrupt?: boolean | undefined} | undefined} [fields]
   *   Fields.
   */
  function constructFactory(onreturn, fields) {
    return hook;

    /**
     * Handle either an object mapping codes to constructs, a list of
     * constructs, or a single construct.
     *
     * @param {Array<Construct> | ConstructRecord | Construct} constructs
     *   Constructs.
     * @param {State} returnState
     *   State.
     * @param {State | undefined} [bogusState]
     *   State.
     * @returns {State}
     *   State.
     */
    function hook(constructs, returnState, bogusState) {
      /** @type {ReadonlyArray<Construct>} */
      let listOfConstructs;
      /** @type {number} */
      let constructIndex;
      /** @type {Construct} */
      let currentConstruct;
      /** @type {Info} */
      let info;
      return Array.isArray(constructs) ? /* c8 ignore next 1 */
      handleListOfConstructs(constructs) : 'tokenize' in constructs ?
      // Looks like a construct.
      handleListOfConstructs([(/** @type {Construct} */constructs)]) : handleMapOfConstructs(constructs);

      /**
       * Handle a list of construct.
       *
       * @param {ConstructRecord} map
       *   Constructs.
       * @returns {State}
       *   State.
       */
      function handleMapOfConstructs(map) {
        return start;

        /** @type {State} */
        function start(code) {
          const left = code !== null && map[code];
          const all = code !== null && map.null;
          const list = [
          // To do: add more extension tests.
          /* c8 ignore next 2 */
          ...(Array.isArray(left) ? left : left ? [left] : []), ...(Array.isArray(all) ? all : all ? [all] : [])];
          return handleListOfConstructs(list)(code);
        }
      }

      /**
       * Handle a list of construct.
       *
       * @param {ReadonlyArray<Construct>} list
       *   Constructs.
       * @returns {State}
       *   State.
       */
      function handleListOfConstructs(list) {
        listOfConstructs = list;
        constructIndex = 0;
        if (list.length === 0) {
          return bogusState;
        }
        return handleConstruct(list[constructIndex]);
      }

      /**
       * Handle a single construct.
       *
       * @param {Construct} construct
       *   Construct.
       * @returns {State}
       *   State.
       */
      function handleConstruct(construct) {
        return start;

        /** @type {State} */
        function start(code) {
          // To do: not needed to store if there is no bogus state, probably?
          // Currently doesn’t work because `inspect` in document does a check
          // w/o a bogus, which doesn’t make sense. But it does seem to help perf
          // by not storing.
          info = store();
          currentConstruct = construct;
          if (!construct.partial) {
            context.currentConstruct = construct;
          }

          // Always populated by defaults.

          if (construct.name && context.parser.constructs.disable.null.includes(construct.name)) {
            return nok(code);
          }
          return construct.tokenize.call(
          // If we do have fields, create an object w/ `context` as its
          // prototype.
          // This allows a “live binding”, which is needed for `interrupt`.
          fields ? Object.assign(Object.create(context), fields) : context, effects, ok, nok)(code);
        }
      }

      /** @type {State} */
      function ok(code) {
        consumed = true;
        onreturn(currentConstruct, info);
        return returnState;
      }

      /** @type {State} */
      function nok(code) {
        consumed = true;
        info.restore();
        if (++constructIndex < listOfConstructs.length) {
          return handleConstruct(listOfConstructs[constructIndex]);
        }
        return bogusState;
      }
    }
  }

  /**
   * @param {Construct} construct
   *   Construct.
   * @param {number} from
   *   From.
   * @returns {undefined}
   *   Nothing.
   */
  function addResult(construct, from) {
    if (construct.resolveAll && !resolveAllConstructs.includes(construct)) {
      resolveAllConstructs.push(construct);
    }
    if (construct.resolve) {
      splice(context.events, from, context.events.length - from, construct.resolve(context.events.slice(from), context));
    }
    if (construct.resolveTo) {
      context.events = construct.resolveTo(context.events, context);
    }
  }

  /**
   * Store state.
   *
   * @returns {Info}
   *   Info.
   */
  function store() {
    const startPoint = now();
    const startPrevious = context.previous;
    const startCurrentConstruct = context.currentConstruct;
    const startEventsIndex = context.events.length;
    const startStack = Array.from(stack);
    return {
      from: startEventsIndex,
      restore
    };

    /**
     * Restore state.
     *
     * @returns {undefined}
     *   Nothing.
     */
    function restore() {
      point = startPoint;
      context.previous = startPrevious;
      context.currentConstruct = startCurrentConstruct;
      context.events.length = startEventsIndex;
      stack = startStack;
      accountForPotentialSkip();
    }
  }

  /**
   * Move the current point a bit forward in the line when it’s on a column
   * skip.
   *
   * @returns {undefined}
   *   Nothing.
   */
  function accountForPotentialSkip() {
    if (point.line in columnStart && point.column < 2) {
      point.column = columnStart[point.line];
      point.offset += columnStart[point.line] - 1;
    }
  }
}

/**
 * Get the chunks from a slice of chunks in the range of a token.
 *
 * @param {ReadonlyArray<Chunk>} chunks
 *   Chunks.
 * @param {Pick<Token, 'end' | 'start'>} token
 *   Token.
 * @returns {Array<Chunk>}
 *   Chunks.
 */
function sliceChunks(chunks, token) {
  const startIndex = token.start._index;
  const startBufferIndex = token.start._bufferIndex;
  const endIndex = token.end._index;
  const endBufferIndex = token.end._bufferIndex;
  /** @type {Array<Chunk>} */
  let view;
  if (startIndex === endIndex) {
    // @ts-expect-error `_bufferIndex` is used on string chunks.
    view = [chunks[startIndex].slice(startBufferIndex, endBufferIndex)];
  } else {
    view = chunks.slice(startIndex, endIndex);
    if (startBufferIndex > -1) {
      const head = view[0];
      if (typeof head === 'string') {
        view[0] = head.slice(startBufferIndex);
        /* c8 ignore next 4 -- used to be used, no longer */
      } else {
        view.shift();
      }
    }
    if (endBufferIndex > 0) {
      // @ts-expect-error `_bufferIndex` is used on string chunks.
      view.push(chunks[endIndex].slice(0, endBufferIndex));
    }
  }
  return view;
}

/**
 * Get the string value of a slice of chunks.
 *
 * @param {ReadonlyArray<Chunk>} chunks
 *   Chunks.
 * @param {boolean | undefined} [expandTabs=false]
 *   Whether to expand tabs (default: `false`).
 * @returns {string}
 *   Result.
 */
function serializeChunks(chunks, expandTabs) {
  let index = -1;
  /** @type {Array<string>} */
  const result = [];
  /** @type {boolean | undefined} */
  let atTab;
  while (++index < chunks.length) {
    const chunk = chunks[index];
    /** @type {string} */
    let value;
    if (typeof chunk === 'string') {
      value = chunk;
    } else switch (chunk) {
      case -5:
        {
          value = "\r";
          break;
        }
      case -4:
        {
          value = "\n";
          break;
        }
      case -3:
        {
          value = "\r" + "\n";
          break;
        }
      case -2:
        {
          value = expandTabs ? " " : "\t";
          break;
        }
      case -1:
        {
          if (!expandTabs && atTab) continue;
          value = " ";
          break;
        }
      default:
        {
          // Currently only replacement character.
          value = String.fromCharCode(chunk);
        }
    }
    atTab = chunk === -2;
    result.push(value);
  }
  return result.join('');
}/**
 * @import {
 *   InitialConstruct,
 *   Initializer,
 *   State,
 *   TokenizeContext,
 *   Token
 * } from 'micromark-util-types'
 */

import { factorySpace } from 'micromark-factory-space';
import { markdownLineEnding } from 'micromark-util-character';
/** @type {InitialConstruct} */
export const content = {
  tokenize: initializeContent
};

/**
 * @this {TokenizeContext}
 *   Context.
 * @type {Initializer}
 *   Content.
 */
function initializeContent(effects) {
  const contentStart = effects.attempt(this.parser.constructs.contentInitial, afterContentStartConstruct, paragraphInitial);
  /** @type {Token} */
  let previous;
  return contentStart;

  /** @type {State} */
  function afterContentStartConstruct(code) {
    if (code === null) {
      effects.consume(code);
      return;
    }
    effects.enter("lineEnding");
    effects.consume(code);
    effects.exit("lineEnding");
    return factorySpace(effects, contentStart, "linePrefix");
  }

  /** @type {State} */
  function paragraphInitial(code) {
    effects.enter("paragraph");
    return lineStart(code);
  }

  /** @type {State} */
  function lineStart(code) {
    const token = effects.enter("chunkText", {
      contentType: "text",
      previous
    });
    if (previous) {
      previous.next = token;
    }
    previous = token;
    return data(code);
  }

  /** @type {State} */
  function data(code) {
    if (code === null) {
      effects.exit("chunkText");
      effects.exit("paragraph");
      effects.consume(code);
      return;
    }
    if (markdownLineEnding(code)) {
      effects.consume(code);
      effects.exit("chunkText");
      return lineStart;
    }

    // Data.
    effects.consume(code);
    return data;
  }
}/**
 * @import {
 *   Construct,
 *   ContainerState,
 *   InitialConstruct,
 *   Initializer,
 *   Point,
 *   State,
 *   TokenizeContext,
 *   Tokenizer,
 *   Token
 * } from 'micromark-util-types'
 */

/**
 * @typedef {[Construct, ContainerState]} StackItem
 *   Construct and its state.
 */

import { factorySpace } from 'micromark-factory-space';
import { markdownLineEnding } from 'micromark-util-character';
import { splice } from 'micromark-util-chunked';
/** @type {InitialConstruct} */
export const document = {
  tokenize: initializeDocument
};

/** @type {Construct} */
const containerConstruct = {
  tokenize: tokenizeContainer
};

/**
 * @this {TokenizeContext}
 *   Self.
 * @type {Initializer}
 *   Initializer.
 */
function initializeDocument(effects) {
  const self = this;
  /** @type {Array<StackItem>} */
  const stack = [];
  let continued = 0;
  /** @type {TokenizeContext | undefined} */
  let childFlow;
  /** @type {Token | undefined} */
  let childToken;
  /** @type {number} */
  let lineStartOffset;
  return start;

  /** @type {State} */
  function start(code) {
    // First we iterate through the open blocks, starting with the root
    // document, and descending through last children down to the last open
    // block.
    // Each block imposes a condition that the line must satisfy if the block is
    // to remain open.
    // For example, a block quote requires a `>` character.
    // A paragraph requires a non-blank line.
    // In this phase we may match all or just some of the open blocks.
    // But we cannot close unmatched blocks yet, because we may have a lazy
    // continuation line.
    if (continued < stack.length) {
      const item = stack[continued];
      self.containerState = item[1];
      return effects.attempt(item[0].continuation, documentContinue, checkNewContainers)(code);
    }

    // Done.
    return checkNewContainers(code);
  }

  /** @type {State} */
  function documentContinue(code) {
    continued++;

    // Note: this field is called `_closeFlow` but it also closes containers.
    // Perhaps a good idea to rename it but it’s already used in the wild by
    // extensions.
    if (self.containerState._closeFlow) {
      self.containerState._closeFlow = undefined;
      if (childFlow) {
        closeFlow();
      }

      // Note: this algorithm for moving events around is similar to the
      // algorithm when dealing with lazy lines in `writeToChild`.
      const indexBeforeExits = self.events.length;
      let indexBeforeFlow = indexBeforeExits;
      /** @type {Point | undefined} */
      let point;

      // Find the flow chunk.
      while (indexBeforeFlow--) {
        if (self.events[indexBeforeFlow][0] === 'exit' && self.events[indexBeforeFlow][1].type === "chunkFlow") {
          point = self.events[indexBeforeFlow][1].end;
          break;
        }
      }
      exitContainers(continued);

      // Fix positions.
      let index = indexBeforeExits;
      while (index < self.events.length) {
        self.events[index][1].end = {
          ...point
        };
        index++;
      }

      // Inject the exits earlier (they’re still also at the end).
      splice(self.events, indexBeforeFlow + 1, 0, self.events.slice(indexBeforeExits));

      // Discard the duplicate exits.
      self.events.length = index;
      return checkNewContainers(code);
    }
    return start(code);
  }

  /** @type {State} */
  function checkNewContainers(code) {
    // Next, after consuming the continuation markers for existing blocks, we
    // look for new block starts (e.g. `>` for a block quote).
    // If we encounter a new block start, we close any blocks unmatched in
    // step 1 before creating the new block as a child of the last matched
    // block.
    if (continued === stack.length) {
      // No need to `check` whether there’s a container, of `exitContainers`
      // would be moot.
      // We can instead immediately `attempt` to parse one.
      if (!childFlow) {
        return documentContinued(code);
      }

      // If we have concrete content, such as block HTML or fenced code,
      // we can’t have containers “pierce” into them, so we can immediately
      // start.
      if (childFlow.currentConstruct && childFlow.currentConstruct.concrete) {
        return flowStart(code);
      }

      // If we do have flow, it could still be a blank line,
      // but we’d be interrupting it w/ a new container if there’s a current
      // construct.
      // To do: next major: remove `_gfmTableDynamicInterruptHack` (no longer
      // needed in micromark-extension-gfm-table@1.0.6).
      self.interrupt = Boolean(childFlow.currentConstruct && !childFlow._gfmTableDynamicInterruptHack);
    }

    // Check if there is a new container.
    self.containerState = {};
    return effects.check(containerConstruct, thereIsANewContainer, thereIsNoNewContainer)(code);
  }

  /** @type {State} */
  function thereIsANewContainer(code) {
    if (childFlow) closeFlow();
    exitContainers(continued);
    return documentContinued(code);
  }

  /** @type {State} */
  function thereIsNoNewContainer(code) {
    self.parser.lazy[self.now().line] = continued !== stack.length;
    lineStartOffset = self.now().offset;
    return flowStart(code);
  }

  /** @type {State} */
  function documentContinued(code) {
    // Try new containers.
    self.containerState = {};
    return effects.attempt(containerConstruct, containerContinue, flowStart)(code);
  }

  /** @type {State} */
  function containerContinue(code) {
    continued++;
    stack.push([self.currentConstruct, self.containerState]);
    // Try another.
    return documentContinued(code);
  }

  /** @type {State} */
  function flowStart(code) {
    if (code === null) {
      if (childFlow) closeFlow();
      exitContainers(0);
      effects.consume(code);
      return;
    }
    childFlow = childFlow || self.parser.flow(self.now());
    effects.enter("chunkFlow", {
      _tokenizer: childFlow,
      contentType: "flow",
      previous: childToken
    });
    return flowContinue(code);
  }

  /** @type {State} */
  function flowContinue(code) {
    if (code === null) {
      writeToChild(effects.exit("chunkFlow"), true);
      exitContainers(0);
      effects.consume(code);
      return;
    }
    if (markdownLineEnding(code)) {
      effects.consume(code);
      writeToChild(effects.exit("chunkFlow"));
      // Get ready for the next line.
      continued = 0;
      self.interrupt = undefined;
      return start;
    }
    effects.consume(code);
    return flowContinue;
  }

  /**
   * @param {Token} token
   *   Token.
   * @param {boolean | undefined} [endOfFile]
   *   Whether the token is at the end of the file (default: `false`).
   * @returns {undefined}
   *   Nothing.
   */
  function writeToChild(token, endOfFile) {
    const stream = self.sliceStream(token);
    if (endOfFile) stream.push(null);
    token.previous = childToken;
    if (childToken) childToken.next = token;
    childToken = token;
    childFlow.defineSkip(token.start);
    childFlow.write(stream);

    // Alright, so we just added a lazy line:
    //
    // ```markdown
    // > a
    // b.
    //
    // Or:
    //
    // > ~~~c
    // d
    //
    // Or:
    //
    // > | e |
    // f
    // ```
    //
    // The construct in the second example (fenced code) does not accept lazy
    // lines, so it marked itself as done at the end of its first line, and
    // then the content construct parses `d`.
    // Most constructs in markdown match on the first line: if the first line
    // forms a construct, a non-lazy line can’t “unmake” it.
    //
    // The construct in the third example is potentially a GFM table, and
    // those are *weird*.
    // It *could* be a table, from the first line, if the following line
    // matches a condition.
    // In this case, that second line is lazy, which “unmakes” the first line
    // and turns the whole into one content block.
    //
    // We’ve now parsed the non-lazy and the lazy line, and can figure out
    // whether the lazy line started a new flow block.
    // If it did, we exit the current containers between the two flow blocks.
    if (self.parser.lazy[token.start.line]) {
      let index = childFlow.events.length;
      while (index--) {
        if (
        // The token starts before the line ending…
        childFlow.events[index][1].start.offset < lineStartOffset && (
        // …and either is not ended yet…
        !childFlow.events[index][1].end ||
        // …or ends after it.
        childFlow.events[index][1].end.offset > lineStartOffset)) {
          // Exit: there’s still something open, which means it’s a lazy line
          // part of something.
          return;
        }
      }

      // Note: this algorithm for moving events around is similar to the
      // algorithm when closing flow in `documentContinue`.
      const indexBeforeExits = self.events.length;
      let indexBeforeFlow = indexBeforeExits;
      /** @type {boolean | undefined} */
      let seen;
      /** @type {Point | undefined} */
      let point;

      // Find the previous chunk (the one before the lazy line).
      while (indexBeforeFlow--) {
        if (self.events[indexBeforeFlow][0] === 'exit' && self.events[indexBeforeFlow][1].type === "chunkFlow") {
          if (seen) {
            point = self.events[indexBeforeFlow][1].end;
            break;
          }
          seen = true;
        }
      }
      exitContainers(continued);

      // Fix positions.
      index = indexBeforeExits;
      while (index < self.events.length) {
        self.events[index][1].end = {
          ...point
        };
        index++;
      }

      // Inject the exits earlier (they’re still also at the end).
      splice(self.events, indexBeforeFlow + 1, 0, self.events.slice(indexBeforeExits));

      // Discard the duplicate exits.
      self.events.length = index;
    }
  }

  /**
   * @param {number} size
   *   Size.
   * @returns {undefined}
   *   Nothing.
   */
  function exitContainers(size) {
    let index = stack.length;

    // Exit open containers.
    while (index-- > size) {
      const entry = stack[index];
      self.containerState = entry[1];
      entry[0].exit.call(self, effects);
    }
    stack.length = size;
  }
  function closeFlow() {
    childFlow.write([null]);
    childToken = undefined;
    childFlow = undefined;
    self.containerState._closeFlow = undefined;
  }
}

/**
 * @this {TokenizeContext}
 *   Context.
 * @type {Tokenizer}
 *   Tokenizer.
 */
function tokenizeContainer(effects, ok, nok) {
  // Always populated by defaults.

  return factorySpace(effects, effects.attempt(this.parser.constructs.document, ok, nok), "linePrefix", this.parser.constructs.disable.null.includes('codeIndented') ? undefined : 4);
}/**
 * @import {
 *   InitialConstruct,
 *   Initializer,
 *   State,
 *   TokenizeContext
 * } from 'micromark-util-types'
 */

import { blankLine, content } from 'micromark-core-commonmark';
import { factorySpace } from 'micromark-factory-space';
import { markdownLineEnding } from 'micromark-util-character';
/** @type {InitialConstruct} */
export const flow = {
  tokenize: initializeFlow
};

/**
 * @this {TokenizeContext}
 *   Self.
 * @type {Initializer}
 *   Initializer.
 */
function initializeFlow(effects) {
  const self = this;
  const initial = effects.attempt(
  // Try to parse a blank line.
  blankLine, atBlankEnding,
  // Try to parse initial flow (essentially, only code).
  effects.attempt(this.parser.constructs.flowInitial, afterConstruct, factorySpace(effects, effects.attempt(this.parser.constructs.flow, afterConstruct, effects.attempt(content, afterConstruct)), "linePrefix")));
  return initial;

  /** @type {State} */
  function atBlankEnding(code) {
    if (code === null) {
      effects.consume(code);
      return;
    }
    effects.enter("lineEndingBlank");
    effects.consume(code);
    effects.exit("lineEndingBlank");
    self.currentConstruct = undefined;
    return initial;
  }

  /** @type {State} */
  function afterConstruct(code) {
    if (code === null) {
      effects.consume(code);
      return;
    }
    effects.enter("lineEnding");
    effects.consume(code);
    effects.exit("lineEnding");
    self.currentConstruct = undefined;
    return initial;
  }
}/**
 * @import {
 *   Code,
 *   InitialConstruct,
 *   Initializer,
 *   Resolver,
 *   State,
 *   TokenizeContext
 * } from 'micromark-util-types'
 */

export const resolver = {
  resolveAll: createResolver()
};
export const string = initializeFactory('string');
export const text = initializeFactory('text');

/**
 * @param {'string' | 'text'} field
 *   Field.
 * @returns {InitialConstruct}
 *   Construct.
 */
function initializeFactory(field) {
  return {
    resolveAll: createResolver(field === 'text' ? resolveAllLineSuffixes : undefined),
    tokenize: initializeText
  };

  /**
   * @this {TokenizeContext}
   *   Context.
   * @type {Initializer}
   */
  function initializeText(effects) {
    const self = this;
    const constructs = this.parser.constructs[field];
    const text = effects.attempt(constructs, start, notText);
    return start;

    /** @type {State} */
    function start(code) {
      return atBreak(code) ? text(code) : notText(code);
    }

    /** @type {State} */
    function notText(code) {
      if (code === null) {
        effects.consume(code);
        return;
      }
      effects.enter("data");
      effects.consume(code);
      return data;
    }

    /** @type {State} */
    function data(code) {
      if (atBreak(code)) {
        effects.exit("data");
        return text(code);
      }

      // Data.
      effects.consume(code);
      return data;
    }

    /**
     * @param {Code} code
     *   Code.
     * @returns {boolean}
     *   Whether the code is a break.
     */
    function atBreak(code) {
      if (code === null) {
        return true;
      }
      const list = constructs[code];
      let index = -1;
      if (list) {
        // Always populated by defaults.

        while (++index < list.length) {
          const item = list[index];
          if (!item.previous || item.previous.call(self, self.previous)) {
            return true;
          }
        }
      }
      return false;
    }
  }
}

/**
 * @param {Resolver | undefined} [extraResolver]
 *   Resolver.
 * @returns {Resolver}
 *   Resolver.
 */
function createResolver(extraResolver) {
  return resolveAllText;

  /** @type {Resolver} */
  function resolveAllText(events, context) {
    let index = -1;
    /** @type {number | undefined} */
    let enter;

    // A rather boring computation (to merge adjacent `data` events) which
    // improves mm performance by 29%.
    while (++index <= events.length) {
      if (enter === undefined) {
        if (events[index] && events[index][1].type === "data") {
          enter = index;
          index++;
        }
      } else if (!events[index] || events[index][1].type !== "data") {
        // Don’t do anything if there is one data token.
        if (index !== enter + 2) {
          events[enter][1].end = events[index - 1][1].end;
          events.splice(enter + 2, index - enter - 2);
          index = enter + 2;
        }
        enter = undefined;
      }
    }
    return extraResolver ? extraResolver(events, context) : events;
  }
}

/**
 * A rather ugly set of instructions which again looks at chunks in the input
 * stream.
 * The reason to do this here is that it is *much* faster to parse in reverse.
 * And that we can’t hook into `null` to split the line suffix before an EOF.
 * To do: figure out if we can make this into a clean utility, or even in core.
 * As it will be useful for GFMs literal autolink extension (and maybe even
 * tables?)
 *
 * @type {Resolver}
 */
function resolveAllLineSuffixes(events, context) {
  let eventIndex = 0; // Skip first.

  while (++eventIndex <= events.length) {
    if ((eventIndex === events.length || events[eventIndex][1].type === "lineEnding") && events[eventIndex - 1][1].type === "data") {
      const data = events[eventIndex - 1][1];
      const chunks = context.sliceStream(data);
      let index = chunks.length;
      let bufferIndex = -1;
      let size = 0;
      /** @type {boolean | undefined} */
      let tabs;
      while (index--) {
        const chunk = chunks[index];
        if (typeof chunk === 'string') {
          bufferIndex = chunk.length;
          while (chunk.charCodeAt(bufferIndex - 1) === 32) {
            size++;
            bufferIndex--;
          }
          if (bufferIndex) break;
          bufferIndex = -1;
        }
        // Number
        else if (chunk === -2) {
          tabs = true;
          size++;
        } else if (chunk === -1) {
          // Empty
        } else {
          // Replacement character, exit.
          index++;
          break;
        }
      }

      // Allow final trailing whitespace.
      if (context._contentTypeTextTrailing && eventIndex === events.length) {
        size = 0;
      }
      if (size) {
        const token = {
          type: eventIndex === events.length || tabs || size < 2 ? "lineSuffix" : "hardBreakTrailing",
          start: {
            _bufferIndex: index ? bufferIndex : data.start._bufferIndex + bufferIndex,
            _index: data.start._index + index,
            line: data.end.line,
            column: data.end.column - size,
            offset: data.end.offset - size
          },
          end: {
            ...data.end
          }
        };
        data.end = {
          ...token.start
        };
        if (data.start.offset === data.end.offset) {
          Object.assign(data, token);
        } else {
          events.splice(eventIndex, 0, ['enter', token, context], ['exit', token, context]);
          eventIndex += 2;
        }
      }
      eventIndex++;
    }
  }
  return events;
}/**
 * @import {
 *   Create,
 *   FullNormalizedExtension,
 *   InitialConstruct,
 *   ParseContext,
 *   ParseOptions
 * } from 'micromark-util-types'
 */

import { combineExtensions } from 'micromark-util-combine-extensions';
import { content } from './initialize/content.js';
import { document } from './initialize/document.js';
import { flow } from './initialize/flow.js';
import { string, text } from './initialize/text.js';
import * as defaultConstructs from './constructs.js';
import { createTokenizer } from './create-tokenizer.js';

/**
 * @param {ParseOptions | null | undefined} [options]
 *   Configuration (optional).
 * @returns {ParseContext}
 *   Parser.
 */
export function parse(options) {
  const settings = options || {};
  const constructs = /** @type {FullNormalizedExtension} */
  combineExtensions([defaultConstructs, ...(settings.extensions || [])]);

  /** @type {ParseContext} */
  const parser = {
    constructs,
    content: create(content),
    defined: [],
    document: create(document),
    flow: create(flow),
    lazy: {},
    string: create(string),
    text: create(text)
  };
  return parser;

  /**
   * @param {InitialConstruct} initial
   *   Construct to start with.
   * @returns {Create}
   *   Create a tokenizer.
   */
  function create(initial) {
    return creator;
    /** @type {Create} */
    function creator(from) {
      return createTokenizer(parser, initial, from);
    }
  }
}/**
 * @import {Event} from 'micromark-util-types'
 */

import { subtokenize } from 'micromark-util-subtokenize';

/**
 * @param {Array<Event>} events
 *   Events.
 * @returns {Array<Event>}
 *   Events.
 */
export function postprocess(events) {
  while (!subtokenize(events)) {
    // Empty
  }
  return events;
}/**
 * @import {Chunk, Code, Encoding, Value} from 'micromark-util-types'
 */

/**
 * @callback Preprocessor
 *   Preprocess a value.
 * @param {Value} value
 *   Value.
 * @param {Encoding | null | undefined} [encoding]
 *   Encoding when `value` is a typed array (optional).
 * @param {boolean | null | undefined} [end=false]
 *   Whether this is the last chunk (default: `false`).
 * @returns {Array<Chunk>}
 *   Chunks.
 */

const search = /[\0\t\n\r]/g;

/**
 * @returns {Preprocessor}
 *   Preprocess a value.
 */
export function preprocess() {
  let column = 1;
  let buffer = '';
  /** @type {boolean | undefined} */
  let start = true;
  /** @type {boolean | undefined} */
  let atCarriageReturn;
  return preprocessor;

  /** @type {Preprocessor} */
  // eslint-disable-next-line complexity
  function preprocessor(value, encoding, end) {
    /** @type {Array<Chunk>} */
    const chunks = [];
    /** @type {RegExpMatchArray | null} */
    let match;
    /** @type {number} */
    let next;
    /** @type {number} */
    let startPosition;
    /** @type {number} */
    let endPosition;
    /** @type {Code} */
    let code;
    value = buffer + (typeof value === 'string' ? value.toString() : new TextDecoder(encoding || undefined).decode(value));
    startPosition = 0;
    buffer = '';
    if (start) {
      // To do: `markdown-rs` actually parses BOMs (byte order mark).
      if (value.charCodeAt(0) === 65279) {
        startPosition++;
      }
      start = undefined;
    }
    while (startPosition < value.length) {
      search.lastIndex = startPosition;
      match = search.exec(value);
      endPosition = match && match.index !== undefined ? match.index : value.length;
      code = value.charCodeAt(endPosition);
      if (!match) {
        buffer = value.slice(startPosition);
        break;
      }
      if (code === 10 && startPosition === endPosition && atCarriageReturn) {
        chunks.push(-3);
        atCarriageReturn = undefined;
      } else {
        if (atCarriageReturn) {
          chunks.push(-5);
          atCarriageReturn = undefined;
        }
        if (startPosition < endPosition) {
          chunks.push(value.slice(startPosition, endPosition));
          column += endPosition - startPosition;
        }
        switch (code) {
          case 0:
            {
              chunks.push(65533);
              column++;
              break;
            }
          case 9:
            {
              next = Math.ceil(column / 4) * 4;
              chunks.push(-2);
              while (column++ < next) chunks.push(-1);
              break;
            }
          case 10:
            {
              chunks.push(-4);
              column = 1;
              break;
            }
          default:
            {
              atCarriageReturn = true;
              column = 1;
            }
        }
      }
      startPosition = endPosition + 1;
    }
    if (end) {
      if (atCarriageReturn) chunks.push(-5);
      if (buffer) chunks.push(buffer);
      chunks.push(null);
    }
    return chunks;
  }
}{
  "name": "micromark",
  "version": "4.0.2",
  "description": "small commonmark compliant markdown parser with positional info and concrete tokens",
  "license": "MIT",
  "keywords": [
    "commonmark",
    "compiler",
    "gfm",
    "html",
    "lexer",
    "markdown",
    "markup",
    "md",
    "unified",
    "parse",
    "parser",
    "plugin",
    "process",
    "remark",
    "render",
    "renderer",
    "token",
    "tokenizer"
  ],
  "repository": "https://github.com/micromark/micromark/tree/main/packages/micromark",
  "bugs": "https://github.com/micromark/micromark/issues",
  "funding": [
    {
      "type": "GitHub Sponsors",
      "url": "https://github.com/sponsors/unifiedjs"
    },
    {
      "type": "OpenCollective",
      "url": "https://opencollective.com/unified"
    }
  ],
  "author": "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)",
  "contributors": [
    "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)"
  ],
  "sideEffects": false,
  "type": "module",
  "files": [
    "dev/",
    "lib/",
    "index.d.ts.map",
    "index.d.ts",
    "index.js",
    "stream.d.ts.map",
    "stream.d.ts",
    "stream.js"
  ],
  "exports": {
    ".": {
      "development": "./dev/index.js",
      "default": "./index.js"
    },
    "./stream": {
      "development": "./dev/stream.js",
      "default": "./stream.js"
    }
  },
  "dependencies": {
    "@types/debug": "^4.0.0",
    "debug": "^4.0.0",
    "decode-named-character-reference": "^1.0.0",
    "devlop": "^1.0.0",
    "micromark-core-commonmark": "^2.0.0",
    "micromark-factory-space": "^2.0.0",
    "micromark-util-character": "^2.0.0",
    "micromark-util-chunked": "^2.0.0",
    "micromark-util-combine-extensions": "^2.0.0",
    "micromark-util-decode-numeric-character-reference": "^2.0.0",
    "micromark-util-encode": "^2.0.0",
    "micromark-util-normalize-identifier": "^2.0.0",
    "micromark-util-resolve-all": "^2.0.0",
    "micromark-util-sanitize-uri": "^2.0.0",
    "micromark-util-subtokenize": "^2.0.0",
    "micromark-util-symbol": "^2.0.0",
    "micromark-util-types": "^2.0.0"
  },
  "scripts": {
    "build": "micromark-build"
  },
  "xo": {
    "envs": [
      "shared-node-browser"
    ],
    "prettier": true,
    "rules": {
      "logical-assignment-operators": "off",
      "max-depth": "off",
      "unicorn/no-this-assignment": "off",
      "unicorn/prefer-at": "off",
      "unicorn/prefer-code-point": "off",
      "unicorn/prefer-event-target": "off"
    }
  }
}
// Note: more types exposed from `index.d.ts`.
// To do: refactor to include type parameters in JS.
export {parseEntities} from './lib/index.js'
/**
 * @import {Point} from 'unist'
 * @import {Options} from '../index.js'
 */

import {characterEntitiesLegacy} from 'character-entities-legacy'
import {characterReferenceInvalid} from 'character-reference-invalid'
import {isDecimal} from 'is-decimal'
import {isHexadecimal} from 'is-hexadecimal'
import {isAlphanumerical} from 'is-alphanumerical'
import {decodeNamedCharacterReference} from 'decode-named-character-reference'

// Warning messages.
const messages = [
  '',
  /* 1: Non terminated (named) */
  'Named character references must be terminated by a semicolon',
  /* 2: Non terminated (numeric) */
  'Numeric character references must be terminated by a semicolon',
  /* 3: Empty (named) */
  'Named character references cannot be empty',
  /* 4: Empty (numeric) */
  'Numeric character references cannot be empty',
  /* 5: Unknown (named) */
  'Named character references must be known',
  /* 6: Disallowed (numeric) */
  'Numeric character references cannot be disallowed',
  /* 7: Prohibited (numeric) */
  'Numeric character references cannot be outside the permissible Unicode range'
]

/**
 * Parse HTML character references.
 *
 * @param {string} value
 * @param {Readonly<Options> | null | undefined} [options]
 */
export function parseEntities(value, options) {
  const settings = options || {}
  const additional =
    typeof settings.additional === 'string'
      ? settings.additional.charCodeAt(0)
      : settings.additional
  /** @type {Array<string>} */
  const result = []
  let index = 0
  let lines = -1
  let queue = ''
  /** @type {Point | undefined} */
  let point
  /** @type {Array<number>|undefined} */
  let indent

  if (settings.position) {
    if ('start' in settings.position || 'indent' in settings.position) {
      // @ts-expect-error: points don’t have indent.
      indent = settings.position.indent
      // @ts-expect-error: points don’t have indent.
      point = settings.position.start
    } else {
      point = settings.position
    }
  }

  let line = (point ? point.line : 0) || 1
  let column = (point ? point.column : 0) || 1

  // Cache the current point.
  let previous = now()
  /** @type {number|undefined} */
  let character

  // Ensure the algorithm walks over the first character (inclusive).
  index--

  while (++index <= value.length) {
    // If the previous character was a newline.
    if (character === 10 /* `\n` */) {
      column = (indent ? indent[lines] : 0) || 1
    }

    character = value.charCodeAt(index)

    if (character === 38 /* `&` */) {
      const following = value.charCodeAt(index + 1)

      // The behavior depends on the identity of the next character.
      if (
        following === 9 /* `\t` */ ||
        following === 10 /* `\n` */ ||
        following === 12 /* `\f` */ ||
        following === 32 /* ` ` */ ||
        following === 38 /* `&` */ ||
        following === 60 /* `<` */ ||
        Number.isNaN(following) ||
        (additional && following === additional)
      ) {
        // Not a character reference.
        // No characters are consumed, and nothing is returned.
        // This is not an error, either.
        queue += String.fromCharCode(character)
        column++
        continue
      }

      const start = index + 1
      let begin = start
      let end = start
      /** @type {string} */
      let type

      if (following === 35 /* `#` */) {
        // Numerical reference.
        end = ++begin

        // The behavior further depends on the next character.
        const following = value.charCodeAt(end)

        if (following === 88 /* `X` */ || following === 120 /* `x` */) {
          // ASCII hexadecimal digits.
          type = 'hexadecimal'
          end = ++begin
        } else {
          // ASCII decimal digits.
          type = 'decimal'
        }
      } else {
        // Named reference.
        type = 'named'
      }

      let characterReferenceCharacters = ''
      let characterReference = ''
      let characters = ''
      // Each type of character reference accepts different characters.
      // This test is used to detect whether a reference has ended (as the semicolon
      // is not strictly needed).
      const test =
        type === 'named'
          ? isAlphanumerical
          : type === 'decimal'
            ? isDecimal
            : isHexadecimal

      end--

      while (++end <= value.length) {
        const following = value.charCodeAt(end)

        if (!test(following)) {
          break
        }

        characters += String.fromCharCode(following)

        // Check if we can match a legacy named reference.
        // If so, we cache that as the last viable named reference.
        // This ensures we do not need to walk backwards later.
        if (type === 'named' && characterEntitiesLegacy.includes(characters)) {
          characterReferenceCharacters = characters
          // @ts-expect-error: always able to decode.
          characterReference = decodeNamedCharacterReference(characters)
        }
      }

      let terminated = value.charCodeAt(end) === 59 /* `;` */

      if (terminated) {
        end++

        const namedReference =
          type === 'named' ? decodeNamedCharacterReference(characters) : false

        if (namedReference) {
          characterReferenceCharacters = characters
          characterReference = namedReference
        }
      }

      let diff = 1 + end - start
      let reference = ''

      if (!terminated && settings.nonTerminated === false) {
        // Empty.
      } else if (!characters) {
        // An empty (possible) reference is valid, unless it’s numeric (thus an
        // ampersand followed by an octothorp).
        if (type !== 'named') {
          warning(4 /* Empty (numeric) */, diff)
        }
      } else if (type === 'named') {
        // An ampersand followed by anything unknown, and not terminated, is
        // invalid.
        if (terminated && !characterReference) {
          warning(5 /* Unknown (named) */, 1)
        } else {
          // If there’s something after an named reference which is not known,
          // cap the reference.
          if (characterReferenceCharacters !== characters) {
            end = begin + characterReferenceCharacters.length
            diff = 1 + end - begin
            terminated = false
          }

          // If the reference is not terminated, warn.
          if (!terminated) {
            const reason = characterReferenceCharacters
              ? 1 /* Non terminated (named) */
              : 3 /* Empty (named) */

            if (settings.attribute) {
              const following = value.charCodeAt(end)

              if (following === 61 /* `=` */) {
                warning(reason, diff)
                characterReference = ''
              } else if (isAlphanumerical(following)) {
                characterReference = ''
              } else {
                warning(reason, diff)
              }
            } else {
              warning(reason, diff)
            }
          }
        }

        reference = characterReference
      } else {
        if (!terminated) {
          // All nonterminated numeric references are not rendered, and emit a
          // warning.
          warning(2 /* Non terminated (numeric) */, diff)
        }

        // When terminated and numerical, parse as either hexadecimal or
        // decimal.
        let referenceCode = Number.parseInt(
          characters,
          type === 'hexadecimal' ? 16 : 10
        )

        // Emit a warning when the parsed number is prohibited, and replace with
        // replacement character.
        if (prohibited(referenceCode)) {
          warning(7 /* Prohibited (numeric) */, diff)
          reference = String.fromCharCode(65533 /* `�` */)
        } else if (referenceCode in characterReferenceInvalid) {
          // Emit a warning when the parsed number is disallowed, and replace by
          // an alternative.
          warning(6 /* Disallowed (numeric) */, diff)
          reference = characterReferenceInvalid[referenceCode]
        } else {
          // Parse the number.
          let output = ''

          // Emit a warning when the parsed number should not be used.
          if (disallowed(referenceCode)) {
            warning(6 /* Disallowed (numeric) */, diff)
          }

          // Serialize the number.
          if (referenceCode > 0xffff) {
            referenceCode -= 0x10000
            output += String.fromCharCode(
              (referenceCode >>> (10 & 0x3ff)) | 0xd800
            )
            referenceCode = 0xdc00 | (referenceCode & 0x3ff)
          }

          reference = output + String.fromCharCode(referenceCode)
        }
      }

      // Found it!
      // First eat the queued characters as normal text, then eat a reference.
      if (reference) {
        flush()

        previous = now()
        index = end - 1
        column += end - start + 1
        result.push(reference)
        const next = now()
        next.offset++

        if (settings.reference) {
          settings.reference.call(
            settings.referenceContext || undefined,
            reference,
            {start: previous, end: next},
            value.slice(start - 1, end)
          )
        }

        previous = next
      } else {
        // If we could not find a reference, queue the checked characters (as
        // normal characters), and move the pointer to their end.
        // This is possible because we can be certain neither newlines nor
        // ampersands are included.
        characters = value.slice(start - 1, end)
        queue += characters
        column += characters.length
        index = end - 1
      }
    } else {
      // Handle anything other than an ampersand, including newlines and EOF.
      if (character === 10 /* `\n` */) {
        line++
        lines++
        column = 0
      }

      if (Number.isNaN(character)) {
        flush()
      } else {
        queue += String.fromCharCode(character)
        column++
      }
    }
  }

  // Return the reduced nodes.
  return result.join('')

  // Get current position.
  function now() {
    return {
      line,
      column,
      offset: index + ((point ? point.offset : 0) || 0)
    }
  }

  /**
   * Handle the warning.
   *
   * @param {1|2|3|4|5|6|7} code
   * @param {number} offset
   */
  function warning(code, offset) {
    /** @type {ReturnType<now>} */
    let position

    if (settings.warning) {
      position = now()
      position.column += offset
      position.offset += offset

      settings.warning.call(
        settings.warningContext || undefined,
        messages[code],
        position,
        code
      )
    }
  }

  /**
   * Flush `queue` (normal text).
   * Macro invoked before each reference and at the end of `value`.
   * Does nothing when `queue` is empty.
   */
  function flush() {
    if (queue) {
      result.push(queue)

      if (settings.text) {
        settings.text.call(settings.textContext || undefined, queue, {
          start: previous,
          end: now()
        })
      }

      queue = ''
    }
  }
}

/**
 * Check if `character` is outside the permissible unicode range.
 *
 * @param {number} code
 * @returns {boolean}
 */
function prohibited(code) {
  return (code >= 0xd800 && code <= 0xdfff) || code > 0x10ffff
}

/**
 * Check if `character` is disallowed.
 *
 * @param {number} code
 * @returns {boolean}
 */
function disallowed(code) {
  return (
    (code >= 0x0001 && code <= 0x0008) ||
    code === 0x000b ||
    (code >= 0x000d && code <= 0x001f) ||
    (code >= 0x007f && code <= 0x009f) ||
    (code >= 0xfdd0 && code <= 0xfdef) ||
    (code & 0xffff) === 0xffff ||
    (code & 0xffff) === 0xfffe
  )
}
{
  "name": "parse-entities",
  "version": "4.0.2",
  "description": "Parse HTML character references",
  "license": "MIT",
  "keywords": [
    "parse",
    "html",
    "character",
    "reference",
    "entity",
    "entities"
  ],
  "repository": "wooorm/parse-entities",
  "bugs": "https://github.com/wooorm/parse-entities/issues",
  "funding": {
    "type": "github",
    "url": "https://github.com/sponsors/wooorm"
  },
  "author": "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)",
  "contributors": [
    "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)"
  ],
  "sideEffects": false,
  "type": "module",
  "main": "index.js",
  "types": "index.d.ts",
  "files": [
    "lib/",
    "index.d.ts",
    "index.js"
  ],
  "dependencies": {
    "@types/unist": "^2.0.0",
    "character-entities-legacy": "^3.0.0",
    "character-reference-invalid": "^2.0.0",
    "decode-named-character-reference": "^1.0.0",
    "is-alphanumerical": "^2.0.0",
    "is-decimal": "^2.0.0",
    "is-hexadecimal": "^2.0.0"
  },
  "devDependencies": {
    "@types/node": "^22.0.0",
    "c8": "^10.0.0",
    "prettier": "^3.0.0",
    "remark-cli": "^12.0.0",
    "remark-preset-wooorm": "^10.0.0",
    "type-coverage": "^2.0.0",
    "typescript": "^5.0.0",
    "xo": "^0.60.0"
  },
  "scripts": {
    "prepack": "npm run build && npm run format",
    "build": "tsc --build --clean && tsc --build && type-coverage",
    "format": "remark . -qfo && prettier . -w --log-level warn && xo --fix",
    "test-api": "node --conditions development test.js",
    "test-coverage": "c8 --check-coverage --100 --reporter lcov npm run test-api",
    "test": "npm run build && npm run format && npm run test-coverage"
  },
  "prettier": {
    "tabWidth": 2,
    "useTabs": false,
    "singleQuote": true,
    "bracketSpacing": false,
    "semi": false,
    "trailingComma": "none"
  },
  "xo": {
    "prettier": true,
    "rules": {
      "@typescript-eslint/consistent-type-definitions": "off",
      "@typescript-eslint/ban-types": "off",
      "complexity": "off",
      "max-depth": "off",
      "no-bitwise": "off",
      "unicorn/numeric-separators-style": "off",
      "unicorn/prefer-code-point": "off"
    }
  },
  "remarkConfig": {
    "plugins": [
      "remark-preset-wooorm"
    ]
  },
  "typeCoverage": {
    "atLeast": 100,
    "detail": true,
    "strict": true,
    "ignoreCatch": true
  }
}
/**
 * @typedef {import('./lib/index.js').LightOptions} LightOptions
 * @typedef {import('./lib/index.js').Options} Options
 */

export * from './lib/index.js'
/**
 * List of legacy (that don’t need a trailing `;`) named references which could,
 * depending on what follows them, turn into a different meaning
 *
 * @type {Array<string>}
 */
export const dangerous = [
  'cent',
  'copy',
  'divide',
  'gt',
  'lt',
  'not',
  'para',
  'times'
]
/**
 * @typedef CoreOptions
 * @property {ReadonlyArray<string>} [subset=[]]
 *   Whether to only escape the given subset of characters.
 * @property {boolean} [escapeOnly=false]
 *   Whether to only escape possibly dangerous characters.
 *   Those characters are `"`, `&`, `'`, `<`, `>`, and `` ` ``.
 *
 * @typedef FormatOptions
 * @property {(code: number, next: number, options: CoreWithFormatOptions) => string} format
 *   Format strategy.
 *
 * @typedef {CoreOptions & FormatOptions & import('./util/format-smart.js').FormatSmartOptions} CoreWithFormatOptions
 */

const defaultSubsetRegex = /["&'<>`]/g
const surrogatePairsRegex = /[\uD800-\uDBFF][\uDC00-\uDFFF]/g
const controlCharactersRegex =
  // eslint-disable-next-line no-control-regex, unicorn/no-hex-escape
  /[\x01-\t\v\f\x0E-\x1F\x7F\x81\x8D\x8F\x90\x9D\xA0-\uFFFF]/g
const regexEscapeRegex = /[|\\{}()[\]^$+*?.]/g

/** @type {WeakMap<ReadonlyArray<string>, RegExp>} */
const subsetToRegexCache = new WeakMap()

/**
 * Encode certain characters in `value`.
 *
 * @param {string} value
 * @param {CoreWithFormatOptions} options
 * @returns {string}
 */
export function core(value, options) {
  value = value.replace(
    options.subset
      ? charactersToExpressionCached(options.subset)
      : defaultSubsetRegex,
    basic
  )

  if (options.subset || options.escapeOnly) {
    return value
  }

  return (
    value
      // Surrogate pairs.
      .replace(surrogatePairsRegex, surrogate)
      // BMP control characters (C0 except for LF, CR, SP; DEL; and some more
      // non-ASCII ones).
      .replace(controlCharactersRegex, basic)
  )

  /**
   * @param {string} pair
   * @param {number} index
   * @param {string} all
   */
  function surrogate(pair, index, all) {
    return options.format(
      (pair.charCodeAt(0) - 0xd800) * 0x400 +
        pair.charCodeAt(1) -
        0xdc00 +
        0x10000,
      all.charCodeAt(index + 2),
      options
    )
  }

  /**
   * @param {string} character
   * @param {number} index
   * @param {string} all
   */
  function basic(character, index, all) {
    return options.format(
      character.charCodeAt(0),
      all.charCodeAt(index + 1),
      options
    )
  }
}

/**
 * A wrapper function that caches the result of `charactersToExpression` with a WeakMap.
 * This can improve performance when tooling calls `charactersToExpression` repeatedly
 * with the same subset.
 *
 * @param {ReadonlyArray<string>} subset
 * @returns {RegExp}
 */
function charactersToExpressionCached(subset) {
  let cached = subsetToRegexCache.get(subset)

  if (!cached) {
    cached = charactersToExpression(subset)
    subsetToRegexCache.set(subset, cached)
  }

  return cached
}

/**
 * @param {ReadonlyArray<string>} subset
 * @returns {RegExp}
 */
function charactersToExpression(subset) {
  /** @type {Array<string>} */
  const groups = []
  let index = -1

  while (++index < subset.length) {
    groups.push(subset[index].replace(regexEscapeRegex, '\\$&'))
  }

  return new RegExp('(?:' + groups.join('|') + ')', 'g')
}
/**
 * @typedef {import('./core.js').CoreOptions & import('./util/format-smart.js').FormatSmartOptions} Options
 * @typedef {import('./core.js').CoreOptions} LightOptions
 */

import {core} from './core.js'
import {formatSmart} from './util/format-smart.js'
import {formatBasic} from './util/format-basic.js'

/**
 * Encode special characters in `value`.
 *
 * @param {string} value
 *   Value to encode.
 * @param {Options} [options]
 *   Configuration.
 * @returns {string}
 *   Encoded value.
 */
export function stringifyEntities(value, options) {
  return core(value, Object.assign({format: formatSmart}, options))
}

/**
 * Encode special characters in `value` as hexadecimals.
 *
 * @param {string} value
 *   Value to encode.
 * @param {LightOptions} [options]
 *   Configuration.
 * @returns {string}
 *   Encoded value.
 */
export function stringifyEntitiesLight(value, options) {
  return core(value, Object.assign({format: formatBasic}, options))
}
/**
 * The smallest way to encode a character.
 *
 * @param {number} code
 * @returns {string}
 */
export function formatBasic(code) {
  return '&#x' + code.toString(16).toUpperCase() + ';'
}
/**
 * @typedef FormatSmartOptions
 * @property {boolean} [useNamedReferences=false]
 *   Prefer named character references (`&amp;`) where possible.
 * @property {boolean} [useShortestReferences=false]
 *   Prefer the shortest possible reference, if that results in less bytes.
 *   **Note**: `useNamedReferences` can be omitted when using `useShortestReferences`.
 * @property {boolean} [omitOptionalSemicolons=false]
 *   Whether to omit semicolons when possible.
 *   **Note**: This creates what HTML calls “parse errors” but is otherwise still valid HTML — don’t use this except when building a minifier.
 *   Omitting semicolons is possible for certain named and numeric references in some cases.
 * @property {boolean} [attribute=false]
 *   Create character references which don’t fail in attributes.
 *   **Note**: `attribute` only applies when operating dangerously with
 *   `omitOptionalSemicolons: true`.
 */

import {toHexadecimal} from './to-hexadecimal.js'
import {toDecimal} from './to-decimal.js'
import {toNamed} from './to-named.js'

/**
 * Configurable ways to encode a character yielding pretty or small results.
 *
 * @param {number} code
 * @param {number} next
 * @param {FormatSmartOptions} options
 * @returns {string}
 */
export function formatSmart(code, next, options) {
  let numeric = toHexadecimal(code, next, options.omitOptionalSemicolons)
  /** @type {string|undefined} */
  let named

  if (options.useNamedReferences || options.useShortestReferences) {
    named = toNamed(
      code,
      next,
      options.omitOptionalSemicolons,
      options.attribute
    )
  }

  // Use the shortest numeric reference when requested.
  // A simple algorithm would use decimal for all code points under 100, as
  // those are shorter than hexadecimal:
  //
  // * `&#99;` vs `&#x63;` (decimal shorter)
  // * `&#100;` vs `&#x64;` (equal)
  //
  // However, because we take `next` into consideration when `omit` is used,
  // And it would be possible that decimals are shorter on bigger values as
  // well if `next` is hexadecimal but not decimal, we instead compare both.
  if (
    (options.useShortestReferences || !named) &&
    options.useShortestReferences
  ) {
    const decimal = toDecimal(code, next, options.omitOptionalSemicolons)

    if (decimal.length < numeric.length) {
      numeric = decimal
    }
  }

  return named &&
    (!options.useShortestReferences || named.length < numeric.length)
    ? named
    : numeric
}
const decimalRegex = /\d/

/**
 * Configurable ways to encode characters as decimal references.
 *
 * @param {number} code
 * @param {number} next
 * @param {boolean|undefined} omit
 * @returns {string}
 */
export function toDecimal(code, next, omit) {
  const value = '&#' + String(code)
  return omit && next && !decimalRegex.test(String.fromCharCode(next))
    ? value
    : value + ';'
}
const hexadecimalRegex = /[\dA-Fa-f]/

/**
 * Configurable ways to encode characters as hexadecimal references.
 *
 * @param {number} code
 * @param {number} next
 * @param {boolean|undefined} omit
 * @returns {string}
 */
export function toHexadecimal(code, next, omit) {
  const value = '&#x' + code.toString(16).toUpperCase()
  return omit && next && !hexadecimalRegex.test(String.fromCharCode(next))
    ? value
    : value + ';'
}
import {characterEntitiesLegacy} from 'character-entities-legacy'
import {characterEntitiesHtml4} from 'character-entities-html4'
import {dangerous} from '../constant/dangerous.js'

const own = {}.hasOwnProperty

/**
 * `characterEntitiesHtml4` but inverted.
 *
 * @type {Record<string, string>}
 */
const characters = {}

/** @type {string} */
let key

for (key in characterEntitiesHtml4) {
  if (own.call(characterEntitiesHtml4, key)) {
    characters[characterEntitiesHtml4[key]] = key
  }
}

const notAlphanumericRegex = /[^\dA-Za-z]/

/**
 * Configurable ways to encode characters as named references.
 *
 * @param {number} code
 * @param {number} next
 * @param {boolean|undefined} omit
 * @param {boolean|undefined} attribute
 * @returns {string}
 */
export function toNamed(code, next, omit, attribute) {
  const character = String.fromCharCode(code)

  if (own.call(characters, character)) {
    const name = characters[character]
    const value = '&' + name

    if (
      omit &&
      characterEntitiesLegacy.includes(name) &&
      !dangerous.includes(name) &&
      (!attribute ||
        (next &&
          next !== 61 /* `=` */ &&
          notAlphanumericRegex.test(String.fromCharCode(next))))
    ) {
      return value
    }

    return value + ';'
  }

  return ''
}
{
  "name": "stringify-entities",
  "version": "4.0.4",
  "description": "Serialize (encode) HTML character references",
  "license": "MIT",
  "keywords": [
    "stringify",
    "encode",
    "escape",
    "html",
    "character",
    "reference",
    "entity",
    "entities"
  ],
  "repository": "wooorm/stringify-entities",
  "bugs": "https://github.com/wooorm/stringify-entities/issues",
  "funding": {
    "type": "github",
    "url": "https://github.com/sponsors/wooorm"
  },
  "author": "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)",
  "contributors": [
    "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)"
  ],
  "sideEffects": false,
  "type": "module",
  "main": "index.js",
  "types": "index.d.ts",
  "files": [
    "lib/",
    "index.d.ts",
    "index.js"
  ],
  "dependencies": {
    "character-entities-html4": "^2.0.0",
    "character-entities-legacy": "^3.0.0"
  },
  "devDependencies": {
    "@types/node": "^20.0.0",
    "c8": "^9.0.0",
    "character-entities": "^2.0.0",
    "prettier": "^3.0.0",
    "remark-cli": "^11.0.0",
    "remark-preset-wooorm": "^9.0.0",
    "type-coverage": "^2.0.0",
    "typescript": "^5.0.0",
    "xo": "^0.58.0"
  },
  "scripts": {
    "prepack": "npm run build && npm run format",
    "generate": "node --conditions development build.js",
    "build": "tsc --build --clean && tsc --build && type-coverage",
    "format": "remark . -qfo && prettier . -w --log-level warn && xo --fix",
    "test-api": "node --conditions development test.js",
    "test-coverage": "c8 --check-coverage --100 --reporter lcov npm run test-api",
    "test": "npm run generate && npm run build && npm run format && npm run test-coverage"
  },
  "prettier": {
    "tabWidth": 2,
    "useTabs": false,
    "singleQuote": true,
    "bracketSpacing": false,
    "semi": false,
    "trailingComma": "none"
  },
  "xo": {
    "prettier": true,
    "rules": {
      "unicorn/prefer-code-point": "off",
      "unicorn/prefer-string-replace-all": "off",
      "unicorn/numeric-separators-style": "off"
    }
  },
  "remarkConfig": {
    "plugins": [
      "preset-wooorm"
    ]
  },
  "typeCoverage": {
    "atLeast": 100,
    "detail": true,
    "strict": true,
    "ignoreCatch": true
  }
}
/**
 * @typedef {import('./lib/index.js').Check} Check
 * @typedef {import('./lib/index.js').Test} Test
 * @typedef {import('./lib/index.js').TestFunction} TestFunction
 */

export {is, convert} from './lib/index.js'
/**
 * @import {Node, Parent} from 'unist'
 */

/**
 * @template Fn
 * @template Fallback
 * @typedef {Fn extends (value: any) => value is infer Thing ? Thing : Fallback} Predicate
 */

/**
 * @callback Check
 *   Check that an arbitrary value is a node.
 * @param {unknown} this
 *   The given context.
 * @param {unknown} [node]
 *   Anything (typically a node).
 * @param {number | null | undefined} [index]
 *   The node’s position in its parent.
 * @param {Parent | null | undefined} [parent]
 *   The node’s parent.
 * @returns {boolean}
 *   Whether this is a node and passes a test.
 *
 * @typedef {Record<string, unknown> | Node} Props
 *   Object to check for equivalence.
 *
 *   Note: `Node` is included as it is common but is not indexable.
 *
 * @typedef {Array<Props | TestFunction | string> | ReadonlyArray<Props | TestFunction | string> | Props | TestFunction | string | null | undefined} Test
 *   Check for an arbitrary node.
 *
 * @callback TestFunction
 *   Check if a node passes a test.
 * @param {unknown} this
 *   The given context.
 * @param {Node} node
 *   A node.
 * @param {number | undefined} [index]
 *   The node’s position in its parent.
 * @param {Parent | undefined} [parent]
 *   The node’s parent.
 * @returns {boolean | undefined | void}
 *   Whether this node passes the test.
 *
 *   Note: `void` is included until TS sees no return as `undefined`.
 */

/**
 * Check if `node` is a `Node` and whether it passes the given test.
 *
 * @param {unknown} node
 *   Thing to check, typically `Node`.
 * @param {Test} test
 *   A check for a specific node.
 * @param {number | null | undefined} index
 *   The node’s position in its parent.
 * @param {Parent | null | undefined} parent
 *   The node’s parent.
 * @param {unknown} context
 *   Context object (`this`) to pass to `test` functions.
 * @returns {boolean}
 *   Whether `node` is a node and passes a test.
 */
export const is =
  // Note: overloads in JSDoc can’t yet use different `@template`s.
  /**
   * @type {(
   *   (<Condition extends ReadonlyArray<string>>(node: unknown, test: Condition, index?: number | null | undefined, parent?: Parent | null | undefined, context?: unknown) => node is Node & {type: Condition[number]}) &
   *   (<Condition extends Array<string>>(node: unknown, test: Condition, index?: number | null | undefined, parent?: Parent | null | undefined, context?: unknown) => node is Node & {type: Condition[number]}) &
   *   (<Condition extends string>(node: unknown, test: Condition, index?: number | null | undefined, parent?: Parent | null | undefined, context?: unknown) => node is Node & {type: Condition}) &
   *   (<Condition extends Props>(node: unknown, test: Condition, index?: number | null | undefined, parent?: Parent | null | undefined, context?: unknown) => node is Node & Condition) &
   *   (<Condition extends TestFunction>(node: unknown, test: Condition, index?: number | null | undefined, parent?: Parent | null | undefined, context?: unknown) => node is Node & Predicate<Condition, Node>) &
   *   ((node?: null | undefined) => false) &
   *   ((node: unknown, test?: null | undefined, index?: number | null | undefined, parent?: Parent | null | undefined, context?: unknown) => node is Node) &
   *   ((node: unknown, test?: Test, index?: number | null | undefined, parent?: Parent | null | undefined, context?: unknown) => boolean)
   * )}
   */
  (
    /**
     * @param {unknown} [node]
     * @param {Test} [test]
     * @param {number | null | undefined} [index]
     * @param {Parent | null | undefined} [parent]
     * @param {unknown} [context]
     * @returns {boolean}
     */
    // eslint-disable-next-line max-params
    function (node, test, index, parent, context) {
      const check = convert(test)

      if (
        index !== undefined &&
        index !== null &&
        (typeof index !== 'number' ||
          index < 0 ||
          index === Number.POSITIVE_INFINITY)
      ) {
        throw new Error('Expected positive finite index')
      }

      if (
        parent !== undefined &&
        parent !== null &&
        (!is(parent) || !parent.children)
      ) {
        throw new Error('Expected parent node')
      }

      if (
        (parent === undefined || parent === null) !==
        (index === undefined || index === null)
      ) {
        throw new Error('Expected both parent and index')
      }

      return looksLikeANode(node)
        ? check.call(context, node, index, parent)
        : false
    }
  )

/**
 * Generate an assertion from a test.
 *
 * Useful if you’re going to test many nodes, for example when creating a
 * utility where something else passes a compatible test.
 *
 * The created function is a bit faster because it expects valid input only:
 * a `node`, `index`, and `parent`.
 *
 * @param {Test} test
 *   *   when nullish, checks if `node` is a `Node`.
 *   *   when `string`, works like passing `(node) => node.type === test`.
 *   *   when `function` checks if function passed the node is true.
 *   *   when `object`, checks that all keys in test are in node, and that they have (strictly) equal values.
 *   *   when `array`, checks if any one of the subtests pass.
 * @returns {Check}
 *   An assertion.
 */
export const convert =
  // Note: overloads in JSDoc can’t yet use different `@template`s.
  /**
   * @type {(
   *   (<Condition extends string>(test: Condition) => (node: unknown, index?: number | null | undefined, parent?: Parent | null | undefined, context?: unknown) => node is Node & {type: Condition}) &
   *   (<Condition extends Props>(test: Condition) => (node: unknown, index?: number | null | undefined, parent?: Parent | null | undefined, context?: unknown) => node is Node & Condition) &
   *   (<Condition extends TestFunction>(test: Condition) => (node: unknown, index?: number | null | undefined, parent?: Parent | null | undefined, context?: unknown) => node is Node & Predicate<Condition, Node>) &
   *   ((test?: null | undefined) => (node?: unknown, index?: number | null | undefined, parent?: Parent | null | undefined, context?: unknown) => node is Node) &
   *   ((test?: Test) => Check)
   * )}
   */
  (
    /**
     * @param {Test} [test]
     * @returns {Check}
     */
    function (test) {
      if (test === null || test === undefined) {
        return ok
      }

      if (typeof test === 'function') {
        return castFactory(test)
      }

      if (typeof test === 'object') {
        return Array.isArray(test)
          ? anyFactory(test)
          : // Cast because `ReadonlyArray` goes into the above but `isArray`
            // narrows to `Array`.
            propertiesFactory(/** @type {Props} */ (test))
      }

      if (typeof test === 'string') {
        return typeFactory(test)
      }

      throw new Error('Expected function, string, or object as test')
    }
  )

/**
 * @param {Array<Props | TestFunction | string>} tests
 * @returns {Check}
 */
function anyFactory(tests) {
  /** @type {Array<Check>} */
  const checks = []
  let index = -1

  while (++index < tests.length) {
    checks[index] = convert(tests[index])
  }

  return castFactory(any)

  /**
   * @this {unknown}
   * @type {TestFunction}
   */
  function any(...parameters) {
    let index = -1

    while (++index < checks.length) {
      if (checks[index].apply(this, parameters)) return true
    }

    return false
  }
}

/**
 * Turn an object into a test for a node with a certain fields.
 *
 * @param {Props} check
 * @returns {Check}
 */
function propertiesFactory(check) {
  const checkAsRecord = /** @type {Record<string, unknown>} */ (check)

  return castFactory(all)

  /**
   * @param {Node} node
   * @returns {boolean}
   */
  function all(node) {
    const nodeAsRecord = /** @type {Record<string, unknown>} */ (
      /** @type {unknown} */ (node)
    )

    /** @type {string} */
    let key

    for (key in check) {
      if (nodeAsRecord[key] !== checkAsRecord[key]) return false
    }

    return true
  }
}

/**
 * Turn a string into a test for a node with a certain type.
 *
 * @param {string} check
 * @returns {Check}
 */
function typeFactory(check) {
  return castFactory(type)

  /**
   * @param {Node} node
   */
  function type(node) {
    return node && node.type === check
  }
}

/**
 * Turn a custom test into a test for a node that passes that test.
 *
 * @param {TestFunction} testFunction
 * @returns {Check}
 */
function castFactory(testFunction) {
  return check

  /**
   * @this {unknown}
   * @type {Check}
   */
  function check(value, index, parent) {
    return Boolean(
      looksLikeANode(value) &&
        testFunction.call(
          this,
          value,
          typeof index === 'number' ? index : undefined,
          parent || undefined
        )
    )
  }
}

function ok() {
  return true
}

/**
 * @param {unknown} value
 * @returns {value is Node}
 */
function looksLikeANode(value) {
  return value !== null && typeof value === 'object' && 'type' in value
}
{
  "name": "unist-util-is",
  "version": "6.0.1",
  "description": "unist utility to check if a node passes a test",
  "license": "MIT",
  "keywords": [
    "unist",
    "unist-util",
    "util",
    "utility",
    "tree",
    "node",
    "is",
    "equal",
    "check",
    "test",
    "type"
  ],
  "repository": "syntax-tree/unist-util-is",
  "bugs": "https://github.com/syntax-tree/unist-util-is/issues",
  "funding": {
    "type": "opencollective",
    "url": "https://opencollective.com/unified"
  },
  "author": "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)",
  "contributors": [
    "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)",
    "Christian Murphy <christian.murphy.42@gmail.com>",
    "Lucas Brandstaetter <lucas@brandstaetter.tech> (https://github.com/Roang-zero1)"
  ],
  "sideEffects": false,
  "type": "module",
  "exports": "./index.js",
  "files": [
    "lib/",
    "index.d.ts.map",
    "index.d.ts",
    "index.js"
  ],
  "dependencies": {
    "@types/unist": "^3.0.0"
  },
  "devDependencies": {
    "@types/mdast": "^4.0.0",
    "@types/node": "^24.0.0",
    "c8": "^10.0.0",
    "prettier": "^3.0.0",
    "remark-cli": "^12.0.0",
    "remark-preset-wooorm": "^11.0.0",
    "tsd": "^0.33.0",
    "type-coverage": "^2.0.0",
    "typescript": "^5.0.0",
    "xo": "^0.58.0"
  },
  "scripts": {
    "prepack": "npm run build && npm run format",
    "build": "tsc --build --clean && tsc --build && tsd && type-coverage",
    "format": "remark . -qfo && prettier . -w --log-level warn && xo --fix",
    "test-api": "node --conditions development test/index.js",
    "test-coverage": "c8 --100 --reporter lcov npm run test-api",
    "test": "npm run build && npm run format && npm run test-coverage"
  },
  "prettier": {
    "bracketSpacing": false,
    "semi": false,
    "singleQuote": true,
    "tabWidth": 2,
    "trailingComma": "none",
    "useTabs": false
  },
  "remarkConfig": {
    "plugins": [
      "remark-preset-wooorm"
    ]
  },
  "typeCoverage": {
    "atLeast": 100,
    "detail": true,
    "#": "needed `any`s",
    "ignoreFiles": [
      "lib/index.d.ts"
    ],
    "ignoreCatch": true,
    "strict": true
  },
  "xo": {
    "overrides": [
      {
        "files": [
          "**/*.ts"
        ],
        "rules": {
          "@typescript-eslint/consistent-type-definitions": "off",
          "@typescript-eslint/no-unnecessary-type-arguments": "off",
          "@typescript-eslint/no-unsafe-argument": "off",
          "@typescript-eslint/no-unsafe-assignment": "off",
          "import/no-extraneous-dependencies": "off"
        }
      }
    ],
    "prettier": true
  }
}
export {stringifyPosition} from './lib/index.js'
/**
 * @typedef {import('unist').Node} Node
 * @typedef {import('unist').Point} Point
 * @typedef {import('unist').Position} Position
 */

/**
 * @typedef NodeLike
 * @property {string} type
 * @property {PositionLike | null | undefined} [position]
 *
 * @typedef PointLike
 * @property {number | null | undefined} [line]
 * @property {number | null | undefined} [column]
 * @property {number | null | undefined} [offset]
 *
 * @typedef PositionLike
 * @property {PointLike | null | undefined} [start]
 * @property {PointLike | null | undefined} [end]
 */

/**
 * Serialize the positional info of a point, position (start and end points),
 * or node.
 *
 * @param {Node | NodeLike | Point | PointLike | Position | PositionLike | null | undefined} [value]
 *   Node, position, or point.
 * @returns {string}
 *   Pretty printed positional info of a node (`string`).
 *
 *   In the format of a range `ls:cs-le:ce` (when given `node` or `position`)
 *   or a point `l:c` (when given `point`), where `l` stands for line, `c` for
 *   column, `s` for `start`, and `e` for end.
 *   An empty string (`''`) is returned if the given value is neither `node`,
 *   `position`, nor `point`.
 */
export function stringifyPosition(value) {
  // Nothing.
  if (!value || typeof value !== 'object') {
    return ''
  }

  // Node.
  if ('position' in value || 'type' in value) {
    return position(value.position)
  }

  // Position.
  if ('start' in value || 'end' in value) {
    return position(value)
  }

  // Point.
  if ('line' in value || 'column' in value) {
    return point(value)
  }

  // ?
  return ''
}

/**
 * @param {Point | PointLike | null | undefined} point
 * @returns {string}
 */
function point(point) {
  return index(point && point.line) + ':' + index(point && point.column)
}

/**
 * @param {Position | PositionLike | null | undefined} pos
 * @returns {string}
 */
function position(pos) {
  return point(pos && pos.start) + '-' + point(pos && pos.end)
}

/**
 * @param {number | null | undefined} value
 * @returns {number}
 */
function index(value) {
  return value && typeof value === 'number' ? value : 1
}
{
  "name": "unist-util-stringify-position",
  "version": "4.0.0",
  "description": "unist utility to serialize a node, position, or point as a human readable location",
  "license": "MIT",
  "keywords": [
    "unist",
    "unist-util",
    "util",
    "utility",
    "position",
    "location",
    "point",
    "node",
    "stringify",
    "tostring"
  ],
  "repository": "syntax-tree/unist-util-stringify-position",
  "bugs": "https://github.com/syntax-tree/unist-util-stringify-position/issues",
  "funding": {
    "type": "opencollective",
    "url": "https://opencollective.com/unified"
  },
  "author": "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)",
  "contributors": [
    "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)"
  ],
  "sideEffects": false,
  "type": "module",
  "exports": "./index.js",
  "files": [
    "lib/",
    "index.d.ts",
    "index.js"
  ],
  "dependencies": {
    "@types/unist": "^3.0.0"
  },
  "devDependencies": {
    "@types/mdast": "^4.0.0",
    "@types/node": "^20.0.0",
    "c8": "^8.0.0",
    "prettier": "^2.0.0",
    "remark-cli": "^11.0.0",
    "remark-preset-wooorm": "^9.0.0",
    "type-coverage": "^2.0.0",
    "typescript": "^5.0.0",
    "xo": "^0.54.0"
  },
  "scripts": {
    "prepack": "npm run build && npm run format",
    "build": "tsc --build --clean && tsc --build && type-coverage",
    "format": "remark . -qfo && prettier . -w --loglevel warn && xo --fix",
    "test-api": "node --conditions development test.js",
    "test-coverage": "c8 --100 --reporter lcov npm run test-api",
    "test": "npm run build && npm run format && npm run test-coverage"
  },
  "prettier": {
    "bracketSpacing": false,
    "semi": false,
    "singleQuote": true,
    "tabWidth": 2,
    "trailingComma": "none",
    "useTabs": false
  },
  "remarkConfig": {
    "plugins": [
      "remark-preset-wooorm"
    ]
  },
  "typeCoverage": {
    "atLeast": 100,
    "detail": true,
    "ignoreCatch": true,
    "strict": true
  },
  "xo": {
    "prettier": true
  }
}
// Note: types exported from `index.d.ts`
export {CONTINUE, EXIT, SKIP, visitParents} from './lib/index.js'
/**
 * @param {string} d
 * @returns {string}
 */
export function color(d) {
  return '\u001B[33m' + d + '\u001B[39m'
}
/**
 * @import {Node as UnistNode, Parent as UnistParent} from 'unist'
 */

/**
 * @typedef {Exclude<import('unist-util-is').Test, undefined> | undefined} Test
 *   Test from `unist-util-is`.
 *
 *   Note: we have remove and add `undefined`, because otherwise when generating
 *   automatic `.d.ts` files, TS tries to flatten paths from a local perspective,
 *   which doesn’t work when publishing on npm.
 */

/**
 * @typedef {(
 *   Fn extends (value: any) => value is infer Thing
 *   ? Thing
 *   : Fallback
 * )} Predicate
 *   Get the value of a type guard `Fn`.
 * @template Fn
 *   Value; typically function that is a type guard (such as `(x): x is Y`).
 * @template Fallback
 *   Value to yield if `Fn` is not a type guard.
 */

/**
 * @typedef {(
 *   Check extends null | undefined // No test.
 *   ? Value
 *   : Value extends {type: Check} // String (type) test.
 *   ? Value
 *   : Value extends Check // Partial test.
 *   ? Value
 *   : Check extends Function // Function test.
 *   ? Predicate<Check, Value> extends Value
 *     ? Predicate<Check, Value>
 *     : never
 *   : never // Some other test?
 * )} MatchesOne
 *   Check whether a node matches a primitive check in the type system.
 * @template Value
 *   Value; typically unist `Node`.
 * @template Check
 *   Value; typically `unist-util-is`-compatible test, but not arrays.
 */

/**
 * @typedef {(
 *   Check extends ReadonlyArray<infer T>
 *   ? MatchesOne<Value, T>
 *   : Check extends Array<infer T>
 *   ? MatchesOne<Value, T>
 *   : MatchesOne<Value, Check>
 * )} Matches
 *   Check whether a node matches a check in the type system.
 * @template Value
 *   Value; typically unist `Node`.
 * @template Check
 *   Value; typically `unist-util-is`-compatible test.
 */

/**
 * @typedef {0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10} Uint
 *   Number; capped reasonably.
 */

/**
 * @typedef {I extends 0 ? 1 : I extends 1 ? 2 : I extends 2 ? 3 : I extends 3 ? 4 : I extends 4 ? 5 : I extends 5 ? 6 : I extends 6 ? 7 : I extends 7 ? 8 : I extends 8 ? 9 : 10} Increment
 *   Increment a number in the type system.
 * @template {Uint} [I=0]
 *   Index.
 */

/**
 * @typedef {(
 *   Node extends UnistParent
 *   ? Node extends {children: Array<infer Children>}
 *     ? Child extends Children ? Node : never
 *     : never
 *   : never
 * )} InternalParent
 *   Collect nodes that can be parents of `Child`.
 * @template {UnistNode} Node
 *   All node types in a tree.
 * @template {UnistNode} Child
 *   Node to search for.
 */

/**
 * @typedef {InternalParent<InclusiveDescendant<Tree>, Child>} Parent
 *   Collect nodes in `Tree` that can be parents of `Child`.
 * @template {UnistNode} Tree
 *   All node types in a tree.
 * @template {UnistNode} Child
 *   Node to search for.
 */

/**
 * @typedef {(
 *   Depth extends Max
 *   ? never
 *   :
 *     | InternalParent<Node, Child>
 *     | InternalAncestor<Node, InternalParent<Node, Child>, Max, Increment<Depth>>
 * )} InternalAncestor
 *   Collect nodes in `Tree` that can be ancestors of `Child`.
 * @template {UnistNode} Node
 *   All node types in a tree.
 * @template {UnistNode} Child
 *   Node to search for.
 * @template {Uint} [Max=10]
 *   Max; searches up to this depth.
 * @template {Uint} [Depth=0]
 *   Current depth.
 */

/**
 * @typedef {InternalAncestor<InclusiveDescendant<Tree>, Child>} Ancestor
 *   Collect nodes in `Tree` that can be ancestors of `Child`.
 * @template {UnistNode} Tree
 *   All node types in a tree.
 * @template {UnistNode} Child
 *   Node to search for.
 */

/**
 * @typedef {(
 *   Tree extends UnistParent
 *     ? Depth extends Max
 *       ? Tree
 *       : Tree | InclusiveDescendant<Tree['children'][number], Max, Increment<Depth>>
 *     : Tree
 * )} InclusiveDescendant
 *   Collect all (inclusive) descendants of `Tree`.
 *
 *   > 👉 **Note**: for performance reasons, this seems to be the fastest way to
 *   > recurse without actually running into an infinite loop, which the
 *   > previous version did.
 *   >
 *   > Practically, a max of `2` is typically enough assuming a `Root` is
 *   > passed, but it doesn’t improve performance.
 *   > It gets higher with `List > ListItem > Table > TableRow > TableCell`.
 *   > Using up to `10` doesn’t hurt or help either.
 * @template {UnistNode} Tree
 *   Tree type.
 * @template {Uint} [Max=10]
 *   Max; searches up to this depth.
 * @template {Uint} [Depth=0]
 *   Current depth.
 */

/**
 * @typedef {'skip' | boolean} Action
 *   Union of the action types.
 *
 * @typedef {number} Index
 *   Move to the sibling at `index` next (after node itself is completely
 *   traversed).
 *
 *   Useful if mutating the tree, such as removing the node the visitor is
 *   currently on, or any of its previous siblings.
 *   Results less than 0 or greater than or equal to `children.length` stop
 *   traversing the parent.
 *
 * @typedef {[(Action | null | undefined | void)?, (Index | null | undefined)?]} ActionTuple
 *   List with one or two values, the first an action, the second an index.
 *
 * @typedef {Action | ActionTuple | Index | null | undefined | void} VisitorResult
 *   Any value that can be returned from a visitor.
 */

/**
 * @callback Visitor
 *   Handle a node (matching `test`, if given).
 *
 *   Visitors are free to transform `node`.
 *   They can also transform the parent of node (the last of `ancestors`).
 *
 *   Replacing `node` itself, if `SKIP` is not returned, still causes its
 *   descendants to be walked (which is a bug).
 *
 *   When adding or removing previous siblings of `node` (or next siblings, in
 *   case of reverse), the `Visitor` should return a new `Index` to specify the
 *   sibling to traverse after `node` is traversed.
 *   Adding or removing next siblings of `node` (or previous siblings, in case
 *   of reverse) is handled as expected without needing to return a new `Index`.
 *
 *   Removing the children property of an ancestor still results in them being
 *   traversed.
 * @param {Visited} node
 *   Found node.
 * @param {Array<VisitedParents>} ancestors
 *   Ancestors of `node`.
 * @returns {VisitorResult}
 *   What to do next.
 *
 *   An `Index` is treated as a tuple of `[CONTINUE, Index]`.
 *   An `Action` is treated as a tuple of `[Action]`.
 *
 *   Passing a tuple back only makes sense if the `Action` is `SKIP`.
 *   When the `Action` is `EXIT`, that action can be returned.
 *   When the `Action` is `CONTINUE`, `Index` can be returned.
 * @template {UnistNode} [Visited=UnistNode]
 *   Visited node type.
 * @template {UnistParent} [VisitedParents=UnistParent]
 *   Ancestor type.
 */

/**
 * @typedef {Visitor<Matches<InclusiveDescendant<Tree>, Check>, Ancestor<Tree, Matches<InclusiveDescendant<Tree>, Check>>>} BuildVisitor
 *   Build a typed `Visitor` function from a tree and a test.
 *
 *   It will infer which values are passed as `node` and which as `parents`.
 * @template {UnistNode} [Tree=UnistNode]
 *   Tree type.
 * @template {Test} [Check=Test]
 *   Test type.
 */

import {convert} from 'unist-util-is'
import {color} from 'unist-util-visit-parents/do-not-use-color'

/** @type {Readonly<ActionTuple>} */
const empty = []

/**
 * Continue traversing as normal.
 */
export const CONTINUE = true

/**
 * Stop traversing immediately.
 */
export const EXIT = false

/**
 * Do not traverse this node’s children.
 */
export const SKIP = 'skip'

/**
 * Visit nodes, with ancestral information.
 *
 * This algorithm performs *depth-first* *tree traversal* in *preorder*
 * (**NLR**) or if `reverse` is given, in *reverse preorder* (**NRL**).
 *
 * You can choose for which nodes `visitor` is called by passing a `test`.
 * For complex tests, you should test yourself in `visitor`, as it will be
 * faster and will have improved type information.
 *
 * Walking the tree is an intensive task.
 * Make use of the return values of the visitor when possible.
 * Instead of walking a tree multiple times, walk it once, use `unist-util-is`
 * to check if a node matches, and then perform different operations.
 *
 * You can change the tree.
 * See `Visitor` for more info.
 *
 * @overload
 * @param {Tree} tree
 * @param {Check} check
 * @param {BuildVisitor<Tree, Check>} visitor
 * @param {boolean | null | undefined} [reverse]
 * @returns {undefined}
 *
 * @overload
 * @param {Tree} tree
 * @param {BuildVisitor<Tree>} visitor
 * @param {boolean | null | undefined} [reverse]
 * @returns {undefined}
 *
 * @param {UnistNode} tree
 *   Tree to traverse.
 * @param {Visitor | Test} test
 *   `unist-util-is`-compatible test
 * @param {Visitor | boolean | null | undefined} [visitor]
 *   Handle each node.
 * @param {boolean | null | undefined} [reverse]
 *   Traverse in reverse preorder (NRL) instead of the default preorder (NLR).
 * @returns {undefined}
 *   Nothing.
 *
 * @template {UnistNode} Tree
 *   Node type.
 * @template {Test} Check
 *   `unist-util-is`-compatible test.
 */
export function visitParents(tree, test, visitor, reverse) {
  /** @type {Test} */
  let check

  if (typeof test === 'function' && typeof visitor !== 'function') {
    reverse = visitor
    // @ts-expect-error no visitor given, so `visitor` is test.
    visitor = test
  } else {
    // @ts-expect-error visitor given, so `test` isn’t a visitor.
    check = test
  }

  const is = convert(check)
  const step = reverse ? -1 : 1

  factory(tree, undefined, [])()

  /**
   * @param {UnistNode} node
   * @param {number | undefined} index
   * @param {Array<UnistParent>} parents
   */
  function factory(node, index, parents) {
    const value = /** @type {Record<string, unknown>} */ (
      node && typeof node === 'object' ? node : {}
    )

    if (typeof value.type === 'string') {
      const name =
        // `hast`
        typeof value.tagName === 'string'
          ? value.tagName
          : // `xast`
            typeof value.name === 'string'
            ? value.name
            : undefined

      Object.defineProperty(visit, 'name', {
        value:
          'node (' + color(node.type + (name ? '<' + name + '>' : '')) + ')'
      })
    }

    return visit

    function visit() {
      /** @type {Readonly<ActionTuple>} */
      let result = empty
      /** @type {Readonly<ActionTuple>} */
      let subresult
      /** @type {number} */
      let offset
      /** @type {Array<UnistParent>} */
      let grandparents

      if (!test || is(node, index, parents[parents.length - 1] || undefined)) {
        // @ts-expect-error: `visitor` is now a visitor.
        result = toResult(visitor(node, parents))

        if (result[0] === EXIT) {
          return result
        }
      }

      if ('children' in node && node.children) {
        const nodeAsParent = /** @type {UnistParent} */ (node)

        if (nodeAsParent.children && result[0] !== SKIP) {
          offset = (reverse ? nodeAsParent.children.length : -1) + step
          grandparents = parents.concat(nodeAsParent)

          while (offset > -1 && offset < nodeAsParent.children.length) {
            const child = nodeAsParent.children[offset]

            subresult = factory(child, offset, grandparents)()

            if (subresult[0] === EXIT) {
              return subresult
            }

            offset =
              typeof subresult[1] === 'number' ? subresult[1] : offset + step
          }
        }
      }

      return result
    }
  }
}

/**
 * Turn a return value into a clean result.
 *
 * @param {VisitorResult} value
 *   Valid return values from visitors.
 * @returns {Readonly<ActionTuple>}
 *   Clean result.
 */
function toResult(value) {
  if (Array.isArray(value)) {
    return value
  }

  if (typeof value === 'number') {
    return [CONTINUE, value]
  }

  return value === null || value === undefined ? empty : [value]
}
{
  "name": "unist-util-visit-parents",
  "version": "6.0.2",
  "description": "unist utility to recursively walk over nodes, with ancestral information",
  "license": "MIT",
  "keywords": [
    "unist",
    "unist-util",
    "util",
    "utility",
    "tree",
    "ast",
    "visit",
    "traverse",
    "walk",
    "check",
    "parent",
    "parents"
  ],
  "repository": "syntax-tree/unist-util-visit-parents",
  "bugs": "https://github.com/syntax-tree/unist-util-visit-parents/issues",
  "funding": {
    "type": "opencollective",
    "url": "https://opencollective.com/unified"
  },
  "author": "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)",
  "contributors": [
    "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)"
  ],
  "sideEffects": false,
  "type": "module",
  "exports": {
    ".": "./index.js",
    "./do-not-use-color": {
      "node": "./lib/color.node.js",
      "default": "./lib/color.js"
    }
  },
  "types": "index.d.ts",
  "files": [
    "lib/",
    "index.d.ts.map",
    "index.d.ts",
    "index.js"
  ],
  "dependencies": {
    "@types/unist": "^3.0.0",
    "unist-util-is": "^6.0.0"
  },
  "devDependencies": {
    "@types/hast": "^3.0.0",
    "@types/mdast": "^4.0.0",
    "@types/node": "^24.0.0",
    "@types/xast": "^2.0.0",
    "c8": "^10.0.0",
    "mdast-util-from-markdown": "^2.0.0",
    "mdast-util-gfm": "^3.0.0",
    "micromark-extension-gfm": "^3.0.0",
    "prettier": "^3.0.0",
    "remark-cli": "^12.0.0",
    "remark-preset-wooorm": "^10.0.0",
    "strip-ansi": "^7.0.0",
    "tsd": "^0.33.0",
    "type-coverage": "^2.0.0",
    "typescript": "^5.0.0",
    "xo": "^0.58.0"
  },
  "scripts": {
    "prepack": "npm run build && npm run format",
    "build": "tsc --build --clean && tsc --build && tsd && type-coverage",
    "format": "remark . -qfo && prettier . -w --log-level warn && xo --fix",
    "test-api": "node --conditions development test.js",
    "test-coverage": "c8 --100 --reporter lcov npm run test-api",
    "test": "npm run build && npm run format && npm run test-coverage"
  },
  "prettier": {
    "bracketSpacing": false,
    "semi": false,
    "singleQuote": true,
    "tabWidth": 2,
    "trailingComma": "none",
    "useTabs": false
  },
  "remarkConfig": {
    "plugins": [
      "remark-preset-wooorm"
    ]
  },
  "typeCoverage": {
    "atLeast": 100,
    "detail": true,
    "#": "needed `any`s",
    "ignoreFiles": [
      "lib/index.d.ts"
    ],
    "ignoreCatch": true,
    "strict": true
  },
  "xo": {
    "prettier": true,
    "rules": {
      "unicorn/prefer-at": "off",
      "unicorn/prefer-string-replace-all": "off"
    }
  }
}
// Note: types exported from `index.d.ts`
export {CONTINUE, EXIT, SKIP, visit} from './lib/index.js'
/**
 * @import {Node as UnistNode, Parent as UnistParent} from 'unist'
 * @import {VisitorResult} from 'unist-util-visit-parents'
 */

/**
 * @typedef {Exclude<import('unist-util-is').Test, undefined> | undefined} Test
 *   Test from `unist-util-is`.
 *
 *   Note: we have remove and add `undefined`, because otherwise when generating
 *   automatic `.d.ts` files, TS tries to flatten paths from a local perspective,
 *   which doesn’t work when publishing on npm.
 */

// To do: use types from `unist-util-visit-parents` when it’s released.

/**
 * @typedef {(
 *   Fn extends (value: any) => value is infer Thing
 *   ? Thing
 *   : Fallback
 * )} Predicate
 *   Get the value of a type guard `Fn`.
 * @template Fn
 *   Value; typically function that is a type guard (such as `(x): x is Y`).
 * @template Fallback
 *   Value to yield if `Fn` is not a type guard.
 */

/**
 * @typedef {(
 *   Check extends null | undefined // No test.
 *   ? Value
 *   : Value extends {type: Check} // String (type) test.
 *   ? Value
 *   : Value extends Check // Partial test.
 *   ? Value
 *   : Check extends Function // Function test.
 *   ? Predicate<Check, Value> extends Value
 *     ? Predicate<Check, Value>
 *     : never
 *   : never // Some other test?
 * )} MatchesOne
 *   Check whether a node matches a primitive check in the type system.
 * @template Value
 *   Value; typically unist `Node`.
 * @template Check
 *   Value; typically `unist-util-is`-compatible test, but not arrays.
 */

/**
 * @typedef {(
 *   Check extends ReadonlyArray<any>
 *   ? MatchesOne<Value, Check[number]>
 *   : MatchesOne<Value, Check>
 * )} Matches
 *   Check whether a node matches a check in the type system.
 * @template Value
 *   Value; typically unist `Node`.
 * @template Check
 *   Value; typically `unist-util-is`-compatible test.
 */

/**
 * @typedef {0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10} Uint
 *   Number; capped reasonably.
 */

/**
 * @typedef {I extends 0 ? 1 : I extends 1 ? 2 : I extends 2 ? 3 : I extends 3 ? 4 : I extends 4 ? 5 : I extends 5 ? 6 : I extends 6 ? 7 : I extends 7 ? 8 : I extends 8 ? 9 : 10} Increment
 *   Increment a number in the type system.
 * @template {Uint} [I=0]
 *   Index.
 */

/**
 * @typedef {(
 *   Node extends UnistParent
 *   ? Node extends {children: Array<infer Children>}
 *     ? Child extends Children ? Node : never
 *     : never
 *   : never
 * )} InternalParent
 *   Collect nodes that can be parents of `Child`.
 * @template {UnistNode} Node
 *   All node types in a tree.
 * @template {UnistNode} Child
 *   Node to search for.
 */

/**
 * @typedef {InternalParent<InclusiveDescendant<Tree>, Child>} Parent
 *   Collect nodes in `Tree` that can be parents of `Child`.
 * @template {UnistNode} Tree
 *   All node types in a tree.
 * @template {UnistNode} Child
 *   Node to search for.
 */

/**
 * @typedef {(
 *   Depth extends Max
 *   ? never
 *   :
 *     | InternalParent<Node, Child>
 *     | InternalAncestor<Node, InternalParent<Node, Child>, Max, Increment<Depth>>
 * )} InternalAncestor
 *   Collect nodes in `Tree` that can be ancestors of `Child`.
 * @template {UnistNode} Node
 *   All node types in a tree.
 * @template {UnistNode} Child
 *   Node to search for.
 * @template {Uint} [Max=10]
 *   Max; searches up to this depth.
 * @template {Uint} [Depth=0]
 *   Current depth.
 */

/**
 * @typedef {(
 *   Tree extends UnistParent
 *     ? Depth extends Max
 *       ? Tree
 *       : Tree | InclusiveDescendant<Tree['children'][number], Max, Increment<Depth>>
 *     : Tree
 * )} InclusiveDescendant
 *   Collect all (inclusive) descendants of `Tree`.
 *
 *   > 👉 **Note**: for performance reasons, this seems to be the fastest way to
 *   > recurse without actually running into an infinite loop, which the
 *   > previous version did.
 *   >
 *   > Practically, a max of `2` is typically enough assuming a `Root` is
 *   > passed, but it doesn’t improve performance.
 *   > It gets higher with `List > ListItem > Table > TableRow > TableCell`.
 *   > Using up to `10` doesn’t hurt or help either.
 * @template {UnistNode} Tree
 *   Tree type.
 * @template {Uint} [Max=10]
 *   Max; searches up to this depth.
 * @template {Uint} [Depth=0]
 *   Current depth.
 */

/**
 * @callback Visitor
 *   Handle a node (matching `test`, if given).
 *
 *   Visitors are free to transform `node`.
 *   They can also transform `parent`.
 *
 *   Replacing `node` itself, if `SKIP` is not returned, still causes its
 *   descendants to be walked (which is a bug).
 *
 *   When adding or removing previous siblings of `node` (or next siblings, in
 *   case of reverse), the `Visitor` should return a new `Index` to specify the
 *   sibling to traverse after `node` is traversed.
 *   Adding or removing next siblings of `node` (or previous siblings, in case
 *   of reverse) is handled as expected without needing to return a new `Index`.
 *
 *   Removing the children property of `parent` still results in them being
 *   traversed.
 * @param {Visited} node
 *   Found node.
 * @param {Visited extends UnistNode ? number | undefined : never} index
 *   Index of `node` in `parent`.
 * @param {Ancestor extends UnistParent ? Ancestor | undefined : never} parent
 *   Parent of `node`.
 * @returns {VisitorResult}
 *   What to do next.
 *
 *   An `Index` is treated as a tuple of `[CONTINUE, Index]`.
 *   An `Action` is treated as a tuple of `[Action]`.
 *
 *   Passing a tuple back only makes sense if the `Action` is `SKIP`.
 *   When the `Action` is `EXIT`, that action can be returned.
 *   When the `Action` is `CONTINUE`, `Index` can be returned.
 * @template {UnistNode} [Visited=UnistNode]
 *   Visited node type.
 * @template {UnistParent} [Ancestor=UnistParent]
 *   Ancestor type.
 */

/**
 * @typedef {Visitor<Visited, Parent<Ancestor, Visited>>} BuildVisitorFromMatch
 *   Build a typed `Visitor` function from a node and all possible parents.
 *
 *   It will infer which values are passed as `node` and which as `parent`.
 * @template {UnistNode} Visited
 *   Node type.
 * @template {UnistParent} Ancestor
 *   Parent type.
 */

/**
 * @typedef {(
 *   BuildVisitorFromMatch<
 *     Matches<Descendant, Check>,
 *     Extract<Descendant, UnistParent>
 *   >
 * )} BuildVisitorFromDescendants
 *   Build a typed `Visitor` function from a list of descendants and a test.
 *
 *   It will infer which values are passed as `node` and which as `parent`.
 * @template {UnistNode} Descendant
 *   Node type.
 * @template {Test} Check
 *   Test type.
 */

/**
 * @typedef {(
 *   BuildVisitorFromDescendants<
 *     InclusiveDescendant<Tree>,
 *     Check
 *   >
 * )} BuildVisitor
 *   Build a typed `Visitor` function from a tree and a test.
 *
 *   It will infer which values are passed as `node` and which as `parent`.
 * @template {UnistNode} [Tree=UnistNode]
 *   Node type.
 * @template {Test} [Check=Test]
 *   Test type.
 */

import {visitParents} from 'unist-util-visit-parents'

export {CONTINUE, EXIT, SKIP} from 'unist-util-visit-parents'

/**
 * Visit nodes.
 *
 * This algorithm performs *depth-first* *tree traversal* in *preorder*
 * (**NLR**) or if `reverse` is given, in *reverse preorder* (**NRL**).
 *
 * You can choose for which nodes `visitor` is called by passing a `test`.
 * For complex tests, you should test yourself in `visitor`, as it will be
 * faster and will have improved type information.
 *
 * Walking the tree is an intensive task.
 * Make use of the return values of the visitor when possible.
 * Instead of walking a tree multiple times, walk it once, use `unist-util-is`
 * to check if a node matches, and then perform different operations.
 *
 * You can change the tree.
 * See `Visitor` for more info.
 *
 * @overload
 * @param {Tree} tree
 * @param {Check} check
 * @param {BuildVisitor<Tree, Check>} visitor
 * @param {boolean | null | undefined} [reverse]
 * @returns {undefined}
 *
 * @overload
 * @param {Tree} tree
 * @param {BuildVisitor<Tree>} visitor
 * @param {boolean | null | undefined} [reverse]
 * @returns {undefined}
 *
 * @param {UnistNode} tree
 *   Tree to traverse.
 * @param {Visitor | Test} testOrVisitor
 *   `unist-util-is`-compatible test (optional, omit to pass a visitor).
 * @param {Visitor | boolean | null | undefined} [visitorOrReverse]
 *   Handle each node (when test is omitted, pass `reverse`).
 * @param {boolean | null | undefined} [maybeReverse=false]
 *   Traverse in reverse preorder (NRL) instead of the default preorder (NLR).
 * @returns {undefined}
 *   Nothing.
 *
 * @template {UnistNode} Tree
 *   Node type.
 * @template {Test} Check
 *   `unist-util-is`-compatible test.
 */
export function visit(tree, testOrVisitor, visitorOrReverse, maybeReverse) {
  /** @type {boolean | null | undefined} */
  let reverse
  /** @type {Test} */
  let test
  /** @type {Visitor} */
  let visitor

  if (
    typeof testOrVisitor === 'function' &&
    typeof visitorOrReverse !== 'function'
  ) {
    test = undefined
    visitor = testOrVisitor
    reverse = visitorOrReverse
  } else {
    // @ts-expect-error: assume the overload with test was given.
    test = testOrVisitor
    // @ts-expect-error: assume the overload with test was given.
    visitor = visitorOrReverse
    reverse = maybeReverse
  }

  visitParents(tree, test, overload, reverse)

  /**
   * @param {UnistNode} node
   * @param {Array<UnistParent>} parents
   */
  function overload(node, parents) {
    const parent = parents[parents.length - 1]
    const index = parent ? parent.children.indexOf(node) : undefined
    return visitor(node, index, parent)
  }
}
{
  "name": "unist-util-visit",
  "version": "5.1.0",
  "description": "unist utility to visit nodes",
  "license": "MIT",
  "keywords": [
    "unist",
    "unist-util",
    "util",
    "utility",
    "remark",
    "retext",
    "rehype",
    "mdast",
    "hast",
    "xast",
    "nlcst",
    "natural",
    "language",
    "markdown",
    "html",
    "xml",
    "tree",
    "ast",
    "node",
    "visit",
    "walk"
  ],
  "repository": "syntax-tree/unist-util-visit",
  "bugs": "https://github.com/syntax-tree/unist-util-visit/issues",
  "funding": {
    "type": "opencollective",
    "url": "https://opencollective.com/unified"
  },
  "author": "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)",
  "contributors": [
    "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)",
    "Eugene Sharygin <eush77@gmail.com>",
    "Richard Gibson <richard.gibson@gmail.com>"
  ],
  "sideEffects": false,
  "type": "module",
  "exports": "./index.js",
  "files": [
    "lib/",
    "index.d.ts.map",
    "index.d.ts",
    "index.js"
  ],
  "dependencies": {
    "@types/unist": "^3.0.0",
    "unist-util-is": "^6.0.0",
    "unist-util-visit-parents": "^6.0.0"
  },
  "devDependencies": {
    "@types/mdast": "^4.0.0",
    "@types/node": "^25.0.0",
    "c8": "^10.0.0",
    "mdast-util-from-markdown": "^2.0.0",
    "mdast-util-gfm": "^3.0.0",
    "micromark-extension-gfm": "^3.0.0",
    "prettier": "^3.0.0",
    "remark-cli": "^12.0.0",
    "remark-preset-wooorm": "^11.0.0",
    "tsd": "^0.33.0",
    "type-coverage": "^2.0.0",
    "typescript": "^5.0.0",
    "xo": "^1.0.0"
  },
  "scripts": {
    "prepack": "npm run build && npm run format",
    "build": "tsc --build --clean && tsc --build && tsd && type-coverage",
    "format": "remark . -qfo && prettier . -w --log-level warn && xo --fix",
    "test-api": "node --conditions development test.js",
    "test-coverage": "c8 --100 --reporter lcov npm run test-api",
    "test": "npm run build && npm run format && npm run test-coverage"
  },
  "prettier": {
    "bracketSpacing": false,
    "semi": false,
    "singleQuote": true,
    "tabWidth": 2,
    "trailingComma": "none",
    "useTabs": false
  },
  "remarkConfig": {
    "plugins": [
      "remark-preset-wooorm"
    ]
  },
  "typeCoverage": {
    "atLeast": 100,
    "detail": true,
    "#": "needed `any`s",
    "ignoreFiles": [
      "lib/index.d.ts"
    ],
    "ignoreCatch": true,
    "strict": true
  },
  "xo": {
    "space": true,
    "prettier": true,
    "rules": {
      "unicorn/prefer-at": "off"
    }
  }
}
/**
 * @callback Handler
 *   Handle a value, with a certain ID field set to a certain value.
 *   The ID field is passed to `zwitch`, and it’s value is this function’s
 *   place on the `handlers` record.
 * @param {...any} parameters
 *   Arbitrary parameters passed to the zwitch.
 *   The first will be an object with a certain ID field set to a certain value.
 * @returns {any}
 *   Anything!
 */

/**
 * @callback UnknownHandler
 *   Handle values that do have a certain ID field, but it’s set to a value
 *   that is not listed in the `handlers` record.
 * @param {unknown} value
 *   An object with a certain ID field set to an unknown value.
 * @param {...any} rest
 *   Arbitrary parameters passed to the zwitch.
 * @returns {any}
 *   Anything!
 */

/**
 * @callback InvalidHandler
 *   Handle values that do not have a certain ID field.
 * @param {unknown} value
 *   Any unknown value.
 * @param {...any} rest
 *   Arbitrary parameters passed to the zwitch.
 * @returns {void|null|undefined|never}
 *   This should crash or return nothing.
 */

/**
 * @template {InvalidHandler} [Invalid=InvalidHandler]
 * @template {UnknownHandler} [Unknown=UnknownHandler]
 * @template {Record<string, Handler>} [Handlers=Record<string, Handler>]
 * @typedef Options
 *   Configuration (required).
 * @property {Invalid} [invalid]
 *   Handler to use for invalid values.
 * @property {Unknown} [unknown]
 *   Handler to use for unknown values.
 * @property {Handlers} [handlers]
 *   Handlers to use.
 */

const own = {}.hasOwnProperty

/**
 * Handle values based on a field.
 *
 * @template {InvalidHandler} [Invalid=InvalidHandler]
 * @template {UnknownHandler} [Unknown=UnknownHandler]
 * @template {Record<string, Handler>} [Handlers=Record<string, Handler>]
 * @param {string} key
 *   Field to switch on.
 * @param {Options<Invalid, Unknown, Handlers>} [options]
 *   Configuration (required).
 * @returns {{unknown: Unknown, invalid: Invalid, handlers: Handlers, (...parameters: Parameters<Handlers[keyof Handlers]>): ReturnType<Handlers[keyof Handlers]>, (...parameters: Parameters<Unknown>): ReturnType<Unknown>}}
 */
export function zwitch(key, options) {
  const settings = options || {}

  /**
   * Handle one value.
   *
   * Based on the bound `key`, a respective handler will be called.
   * If `value` is not an object, or doesn’t have a `key` property, the special
   * “invalid” handler will be called.
   * If `value` has an unknown `key`, the special “unknown” handler will be
   * called.
   *
   * All arguments, and the context object, are passed through to the handler,
   * and it’s result is returned.
   *
   * @this {unknown}
   *   Any context object.
   * @param {unknown} [value]
   *   Any value.
   * @param {...unknown} parameters
   *   Arbitrary parameters passed to the zwitch.
   * @property {Handler} invalid
   *   Handle for values that do not have a certain ID field.
   * @property {Handler} unknown
   *   Handle values that do have a certain ID field, but it’s set to a value
   *   that is not listed in the `handlers` record.
   * @property {Handlers} handlers
   *   Record of handlers.
   * @returns {unknown}
   *   Anything.
   */
  function one(value, ...parameters) {
    /** @type {Handler|undefined} */
    let fn = one.invalid
    const handlers = one.handlers

    if (value && own.call(value, key)) {
      // @ts-expect-error Indexable.
      const id = String(value[key])
      // @ts-expect-error Indexable.
      fn = own.call(handlers, id) ? handlers[id] : one.unknown
    }

    if (fn) {
      return fn.call(this, value, ...parameters)
    }
  }

  one.handlers = settings.handlers || {}
  one.invalid = settings.invalid
  one.unknown = settings.unknown

  // @ts-expect-error: matches!
  return one
}
{
  "name": "zwitch",
  "version": "2.0.4",
  "description": "Handle values based on a property",
  "license": "MIT",
  "keywords": [
    "handle",
    "switch",
    "property"
  ],
  "repository": "wooorm/zwitch",
  "bugs": "https://github.com/wooorm/zwitch/issues",
  "funding": {
    "type": "github",
    "url": "https://github.com/sponsors/wooorm"
  },
  "author": "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)",
  "contributors": [
    "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)"
  ],
  "sideEffects": false,
  "type": "module",
  "main": "index.js",
  "types": "index.d.ts",
  "files": [
    "index.d.ts",
    "index.js"
  ],
  "devDependencies": {
    "@types/node": "^18.0.0",
    "c8": "^7.0.0",
    "prettier": "^2.0.0",
    "remark-cli": "^11.0.0",
    "remark-preset-wooorm": "^9.0.0",
    "tsd": "^0.24.0",
    "type-coverage": "^2.0.0",
    "typescript": "^4.0.0",
    "xo": "^0.52.0"
  },
  "scripts": {
    "prepack": "npm run build && npm run format",
    "build": "tsc --build --clean && tsc --build && tsd && type-coverage",
    "format": "remark . -qfo && prettier . -w --loglevel warn && xo --fix",
    "test-api": "node --conditions development test.js",
    "test-coverage": "c8 --check-coverage --100 --reporter lcov npm run test-api",
    "test": "npm run build && npm run format && npm run test-coverage"
  },
  "prettier": {
    "tabWidth": 2,
    "useTabs": false,
    "singleQuote": true,
    "bracketSpacing": false,
    "semi": false,
    "trailingComma": "none"
  },
  "xo": {
    "prettier": true
  },
  "remarkConfig": {
    "plugins": [
      "preset-wooorm"
    ]
  },
  "typeCoverage": {
    "atLeast": 100,
    "detail": true,
    "strict": true,
    "ignoreFiles": [
      "index.d.ts"
    ]
  }
}
{
  "name": "favela-markdown",
  "version": "0.2.0",
  "description": "The P2P-native markdown record layer for favela. A post is a markdown document; atproto and ActivityPub are projections derived from it on read.",
  "type": "module",
  "license": "MIT",
  "dependencies": {
    "mdast-util-directive": "^3.1.0",
    "mdast-util-from-markdown": "^2.0.3",
    "mdast-util-gfm-footnote": "^2.1.0",
    "mdast-util-gfm-strikethrough": "^2.0.0",
    "mdast-util-gfm-table": "^2.0.0",
    "micromark-extension-directive": "^4.0.0",
    "micromark-extension-gfm-footnote": "^2.1.0",
    "micromark-extension-gfm-strikethrough": "^2.1.0",
    "micromark-extension-gfm-table": "^2.1.1",
    "bare-fs": "^4.8.1",
    "bare-path": "^3.1.2",
    "bare-os": "^3.9.3"
  },
  "scripts": {
    "test": "brittle-bare test/*.test.js"
  },
  "bin": {
    "favela-markdown": "bin/favela-markdown.js"
  },
  "imports": {
    "#node:fs": {
      "bare": "bare-fs",
      "default": "fs"
    },
    "#node:path": {
      "bare": "bare-path",
      "default": "path"
    },
    "#node:os": {
      "bare": "bare-os",
      "default": "os"
    }
  }
}
const fs = require('fs')
const fsx = require('fs-native-extensions')
const onexit = require('resource-on-exit')
const ReadyResource = require('ready-resource')

module.exports = class FDLock extends ReadyResource {
  constructor(fd, opts = {}) {
    const { wait = false } = opts

    super()

    this._fd = fd
    this._wait = wait
    this._locked = false

    onexit.add(this, closeSync)
  }

  get locked() {
    return this._locked
  }

  async _open() {
    try {
      await this._resume()
    } catch (err) {
      onexit.remove(this)
      await close(this)
      throw err
    }
  }

  async _close() {
    onexit.remove(this)
    await close(this)
    this._locked = false
  }

  transfer() {
    const fd = this._fd
    if (fd === -1) throw new Error('Lock has already been transferred')
    this._fd = -1
    onexit.remove(this)
    return fd
  }

  async suspend() {
    if (!this.opened) await this.ready()
    return this._suspend()
  }

  async _suspend() {
    if (this._fd === -1 || this._locked === false) return
    fsx.unlock(this._fd)
    this._locked = false
  }

  async resume() {
    if (!this.opened) await this.ready()
    return this._resume()
  }

  async _resume() {
    if (this._fd === -1 || this._locked === true) return
    if (this._wait) await fsx.waitForLock(this._fd)
    else if (!fsx.tryLock(this._fd)) {
      throw new Error('File descriptor could not be locked')
    }
    this._locked = true
  }
}

function close(lock) {
  const fd = lock._fd
  if (fd === -1) return
  lock._fd = -1
  return new Promise((resolve) => fs.close(fd, () => resolve()))
}

function closeSync(lock) {
  const fd = lock._fd
  if (fd === -1) return
  lock._fd = -1
  lock._locked = false
  try {
    fs.closeSync(fd)
  } catch {}
}
{
  "name": "fd-lock",
  "version": "2.2.0",
  "description": "Stateful file descriptor locks for JavaScript",
  "exports": {
    "./package": "./package.json",
    ".": "./index.js"
  },
  "imports": {
    "fs": {
      "bare": "bare-fs",
      "default": "fs"
    }
  },
  "files": [
    "index.js"
  ],
  "scripts": {
    "test": "npm run lint && npm run test:bare && npm run test:node",
    "test:bare": "bare test.js",
    "test:node": "node test.js",
    "lint": "prettier --check .",
    "format": "prettier --write ."
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/fd-lock.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/fd-lock/issues"
  },
  "homepage": "https://github.com/holepunchto/fd-lock#readme",
  "dependencies": {
    "bare-fs": "^4.5.0",
    "fs-native-extensions": "^1.4.4",
    "ready-resource": "^1.2.0",
    "resource-on-exit": "^1.0.0"
  },
  "devDependencies": {
    "brittle": "^3.3.2",
    "prettier": "^3.4.2",
    "prettier-config-holepunch": "^2.0.0"
  }
}
exports.fullRoots = function (index, result) {
  if (index & 1) throw new Error('You can only look up roots for depth(0) blocks')
  if (!result) result = []

  index /= 2

  let offset = 0
  let factor = 1

  while (true) {
    if (!index) return result
    while (factor * 2 <= index) factor *= 2
    result.push(offset + factor - 1)
    offset = offset + 2 * factor
    index -= factor
    factor = 1
  }
}

exports.futureRoots = function (index, result) {
  if (index & 1) throw new Error('You can only look up future roots for depth(0) blocks')
  if (!result) result = []

  let factor = 1

  // make first root
  while (factor * 2 <= index) factor *= 2

  // full factor of 2 - done
  if (factor * 2 - 2 === index) return result

  let pos = factor / 2 - 1

  // while its not a full tree
  while ((pos + factor / 2 - 1) !== index) {
    pos += factor

    // read too far, to to left child
    while ((pos + factor / 2 - 1) > index) {
      factor /= 2
      pos -= factor / 2
    }

    // the "gap" is a future root
    result.push(pos - factor / 2)
  }

  return result
}

exports.patch = function (from, to) {
  if (from === 0 || from >= to) return []

  const roots = exports.fullRoots(from)
  const target = exports.fullRoots(to)

  // first find the first root that is different

  let i = 0
  for (; i < target.length; i++) {
    if (i >= roots.length || roots[i] !== target[i]) break
  }

  const patch = []

  if (i < roots.length) {
    // now we need to grow the newest root until it hits the diff one
    let prev = roots.length - 1

    const ite = exports.iterator(roots[prev--])

    while (ite.index !== target[i]) {
      ite.sibling()

      if (prev >= 0 && ite.index === roots[prev]) {
        prev--
      } else {
        patch.push(ite.index)
      }

      patch.push(ite.parent())
    }

    i++ // patched to next root, so inc
  }

  // include the rest

  for (; i < target.length; i++) patch.push(target[i])

  return patch
}

exports.depth = function (index) {
  let depth = 0

  index += 1
  while (!(index & 1)) {
    depth++
    index = rightShift(index)
  }

  return depth
}

exports.sibling = function (index, depth) {
  if (!depth) depth = exports.depth(index)
  const offset = exports.offset(index, depth)

  return exports.index(depth, offset & 1 ? offset - 1 : offset + 1)
}

exports.parent = function (index, depth) {
  if (!depth) depth = exports.depth(index)
  const offset = exports.offset(index, depth)

  return exports.index(depth + 1, rightShift(offset))
}

exports.leftChild = function (index, depth) {
  if (!(index & 1)) return -1
  if (!depth) depth = exports.depth(index)
  return exports.index(depth - 1, exports.offset(index, depth) * 2)
}

exports.rightChild = function (index, depth) {
  if (!(index & 1)) return -1
  if (!depth) depth = exports.depth(index)
  return exports.index(depth - 1, 1 + (exports.offset(index, depth) * 2))
}

exports.children = function (index, depth) {
  if (!(index & 1)) return null

  if (!depth) depth = exports.depth(index)
  const offset = exports.offset(index, depth) * 2

  return [
    exports.index(depth - 1, offset),
    exports.index(depth - 1, offset + 1)
  ]
}

exports.leftSpan = function (index, depth) {
  if (!(index & 1)) return index
  if (!depth) depth = exports.depth(index)
  return exports.offset(index, depth) * twoPow(depth + 1)
}

exports.rightSpan = function (index, depth) {
  if (!(index & 1)) return index
  if (!depth) depth = exports.depth(index)
  return (exports.offset(index, depth) + 1) * twoPow(depth + 1) - 2
}

exports.nextLeaf = function (index) {
  let factor = 1
  let r = index

  while ((r & 1) === 1) {
    r = (r - 1) / 2
    factor *= 2
  }

  return index + factor + 1
}

exports.count = function (index, depth) {
  if (!(index & 1)) return 1
  if (!depth) depth = exports.depth(index)
  return twoPow(depth + 1) - 1
}

exports.countLeaves = function (index) {
  return (exports.count(index) + 1) / 2
}

exports.spans = function (index, depth) {
  if (!(index & 1)) return [index, index]
  if (!depth) depth = exports.depth(index)

  const offset = exports.offset(index, depth)
  const width = twoPow(depth + 1)

  return [offset * width, (offset + 1) * width - 2]
}

exports.index = function (depth, offset) {
  return (1 + 2 * offset) * twoPow(depth) - 1
}

exports.offset = function (index, depth) {
  if (!(index & 1)) return index / 2
  if (!depth) depth = exports.depth(index)

  return ((index + 1) / twoPow(depth) - 1) / 2
}

exports.iterator = function (index) {
  const ite = new Iterator()
  ite.seek(index || 0)
  return ite
}

function twoPow (n) {
  return n < 31 ? 1 << n : ((1 << 30) * (1 << (n - 30)))
}

function rightShift (n) {
  return (n - (n & 1)) / 2
}

function Iterator () {
  this.index = 0
  this.offset = 0
  this.factor = 0
}

Iterator.prototype.seek = function (index) {
  this.index = index
  if (this.index & 1) {
    this.offset = exports.offset(index)
    this.factor = twoPow(exports.depth(index) + 1)
  } else {
    this.offset = index / 2
    this.factor = 2
  }
}

Iterator.prototype.isLeft = function () {
  return (this.offset & 1) === 0
}

Iterator.prototype.isRight = function () {
  return (this.offset & 1) === 1
}

Iterator.prototype.isRoot = function (length) {
  const currentLength = 1 + (this.index + this.factor / 2 - 1) / 2
  if (length < currentLength) return false

  const factor = this.factor * 2
  const index = (this.offset & 1)
    ? this.index - this.factor / 2
    : this.index + this.factor / 2

  const parentLength = 1 + (index + factor / 2 - 1) / 2
  return parentLength > length
}

Iterator.prototype.contains = function (index) {
  return index > this.index
    ? index < (this.index + this.factor / 2)
    : index < this.index
      ? index > (this.index - this.factor / 2)
      : true
}

Iterator.prototype.prev = function () {
  if (!this.offset) return this.index
  this.offset--
  this.index -= this.factor
  return this.index
}

Iterator.prototype.next = function () {
  this.offset++
  this.index += this.factor
  return this.index
}

Iterator.prototype.count = function () {
  if (!(this.index & 1)) return 1
  return this.factor - 1
}

Iterator.prototype.countLeaves = function () {
  return (this.count() + 1) / 2
}

Iterator.prototype.sibling = function () {
  return this.isLeft() ? this.next() : this.prev()
}

Iterator.prototype.parent = function () {
  if (this.offset & 1) {
    this.index -= this.factor / 2
    this.offset = (this.offset - 1) / 2
  } else {
    this.index += this.factor / 2
    this.offset /= 2
  }
  this.factor *= 2
  return this.index
}

Iterator.prototype.leftSpan = function () {
  this.index = this.index - this.factor / 2 + 1
  this.offset = this.index / 2
  this.factor = 2
  return this.index
}

Iterator.prototype.peekLeftSpan = function () {
  return this.index - this.factor / 2 + 1
}

Iterator.prototype.rightSpan = function () {
  this.index = this.index + this.factor / 2 - 1
  this.offset = this.index / 2
  this.factor = 2
  return this.index
}

Iterator.prototype.peekRightSpan = function () {
  return this.index + this.factor / 2 - 1
}

Iterator.prototype.leftChild = function () {
  if (this.factor === 2) return this.index
  this.factor /= 2
  this.index -= this.factor / 2
  this.offset *= 2
  return this.index
}

Iterator.prototype.rightChild = function () {
  if (this.factor === 2) return this.index
  this.factor /= 2
  this.index += this.factor / 2
  this.offset = 2 * this.offset + 1
  return this.index
}

Iterator.prototype.nextTree = function () {
  this.index = this.index + this.factor / 2 + 1
  this.offset = this.index / 2
  this.factor = 2
  return this.index
}

Iterator.prototype.prevTree = function () {
  if (!this.offset) {
    this.index = 0
    this.factor = 2
  } else {
    this.index = this.index - this.factor / 2 - 1
    this.offset = this.index / 2
    this.factor = 2
  }
  return this.index
}

Iterator.prototype.fullRoot = function (index) {
  if (index <= this.index || (this.index & 1) > 0) return false
  while (index > this.index + this.factor + this.factor / 2) {
    this.index += this.factor / 2
    this.factor *= 2
    this.offset /= 2
  }
  return true
}
{
  "name": "flat-tree",
  "version": "1.13.0",
  "description": "A series of functions to map a binary tree to a list",
  "main": "index.js",
  "files": [
    "index.js"
  ],
  "dependencies": {},
  "devDependencies": {
    "brittle": "^3.3.2",
    "standard": "^17.1.0"
  },
  "scripts": {
    "test": "standard && brittle test.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/mafintosh/flat-tree.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/mafintosh/flat-tree/issues"
  },
  "homepage": "https://github.com/mafintosh/flat-tree"
}
require.addon = require('require-addon')

module.exports = require.addon('.', __filename)
const { isWindows } = require('which-runtime')
const binding = require('./binding')

function onwork(err, result) {
  if (err) this.reject(err)
  else this.resolve(result)
}

exports.tryLock = function tryLock(fd, offset = 0, length = 0, opts = {}) {
  if (typeof offset === 'object') {
    opts = offset
    offset = 0
  }

  if (typeof length === 'object') {
    opts = length
    length = 0
  }

  if (typeof opts !== 'object' || opts === null) {
    opts = {}
  }

  try {
    binding.tryLock(fd, offset, length, opts.shared !== true)
  } catch (err) {
    if (err.code === 'EAGAIN') return false
    throw err
  }

  return true
}

exports.waitForLock = function waitForLock(fd, offset = 0, length = 0, opts = {}) {
  if (typeof offset === 'object') {
    opts = offset
    offset = 0
  }

  if (typeof length === 'object') {
    opts = length
    length = 0
  }

  if (typeof opts !== 'object' || opts === null) {
    opts = {}
  }

  const req = {
    handle: null,
    resolve: null,
    reject: null
  }

  const promise = new Promise((resolve, reject) => {
    req.resolve = resolve
    req.reject = reject
  })

  try {
    req.handle = binding.waitForLock(fd, offset, length, opts.shared !== true, req, onwork)
  } catch (err) {
    return Promise.reject(err)
  }

  return promise
}

exports.waitForLockSync = function waitForLockSync(fd, offset = 0, length = 0, opts = {}) {
  if (typeof offset === 'object') {
    opts = offset
    offset = 0
  }

  if (typeof length === 'object') {
    opts = length
    length = 0
  }

  if (typeof opts !== 'object' || opts === null) {
    opts = {}
  }

  binding.waitForLockSync(fd, offset, length, opts.shared !== true)
}

exports.tryDowngradeLock = function tryDowngradeLock(fd, offset = 0, length = 0) {
  try {
    binding.tryDowngradeLock(fd, offset, length)
  } catch (err) {
    if (err.code === 'EAGAIN') return false
    throw err
  }

  return true
}

exports.waitForDowngradeLock = function downgradeLock(fd, offset = 0, length = 0) {
  const req = {
    handle: null,
    resolve: null,
    reject: null
  }

  const promise = new Promise((resolve, reject) => {
    req.resolve = resolve
    req.reject = reject
  })

  try {
    req.handle = binding.waitForDowngradeLock(fd, offset, length, req, onwork)
  } catch (err) {
    return Promise.reject(err)
  }

  return promise
}

exports.waitForDowngradeLockSync = function waitForDowngradeLockSync(fd, offset = 0, length = 0) {
  binding.waitForDowngradeLockSync(fd, offset, length)
}

exports.tryUpgradeLock = function tryUpgradeLock(fd, offset = 0, length = 0) {
  try {
    binding.tryUpgradeLock(fd, offset, length)
  } catch (err) {
    if (err.code === 'EAGAIN') return false
    throw err
  }

  return true
}

exports.waitForUpgradeLock = function upgradeLock(fd, offset = 0, length = 0) {
  const req = {
    handle: null,
    resolve: null,
    reject: null
  }

  const promise = new Promise((resolve, reject) => {
    req.resolve = resolve
    req.reject = reject
  })

  try {
    req.handle = binding.waitForUpgradeLock(fd, offset, length, req, onwork)
  } catch (err) {
    return Promise.reject(err)
  }

  return promise
}

exports.waitForUpgradeLockSync = function waitForUpgradeLockSync(fd, offset = 0, length = 0) {
  binding.waitForUpgradeLockSync(fd, offset, length)
}

exports.unlock = function unlock(fd, offset = 0, length = 0) {
  binding.unlock(fd, offset, length)
}

exports.trim = function trim(fd, offset, length) {
  const req = {
    handle: null,
    resolve: null,
    reject: null
  }

  const promise = new Promise((resolve, reject) => {
    req.resolve = resolve
    req.reject = reject
  })

  try {
    req.handle = binding.trim(fd, offset, length, req, onwork)
  } catch (err) {
    return Promise.reject(err)
  }

  return promise
}

exports.trimSync = function trimSync(fd, offset, length) {
  binding.trimSync(fd, offset, length)
}

exports.sparse = function sparse(fd) {
  // Short circuit on everything but Windows
  if (!isWindows) return Promise.resolve()

  const req = {
    handle: null,
    resolve: null,
    reject: null
  }

  const promise = new Promise((resolve, reject) => {
    req.resolve = resolve
    req.reject = reject
  })

  try {
    req.handle = binding.sparse(fd, req, onwork)
  } catch (err) {
    return Promise.reject(err)
  }

  return promise
}

exports.sparseSync = function sparseSync(fd) {
  // Short circuit on everything but Windows
  if (!isWindows) return

  binding.sparseSync(fd)
}

exports.swap = function swap(from, to) {
  const req = {
    handle: null,
    resolve: null,
    reject: null
  }

  const promise = new Promise((resolve, reject) => {
    req.resolve = resolve
    req.reject = reject
  })

  try {
    req.handle = binding.swap(from, to, req, onwork)
  } catch (err) {
    return Promise.reject(err)
  }

  return promise
}

exports.swapSync = function swapSync(from, to) {
  binding.swapSync(from, to)
}

exports.getAttr = function getAttr(fd, name) {
  const req = {
    handle: null,
    resolve: null,
    reject: null
  }

  const promise = new Promise((resolve, reject) => {
    req.resolve = resolve
    req.reject = reject
  })

  try {
    req.handle = binding.getAttr(fd, name, req, onwork)
  } catch (err) {
    return Promise.reject(err)
  }

  return promise.then((buffer) => (buffer === null ? null : Buffer.from(buffer)))
}

exports.getAttrSync = function getAttrSync(fd, name) {
  const buffer = binding.getAttrSync(fd, name)
  return buffer === null ? null : Buffer.from(buffer)
}

exports.setAttr = function setAttr(fd, name, value, encoding) {
  if (typeof value === 'string') value = Buffer.from(value, encoding)

  const req = {
    value,
    handle: null,
    resolve: null,
    reject: null
  }

  const promise = new Promise((resolve, reject) => {
    req.resolve = resolve
    req.reject = reject
  })

  try {
    req.handle = binding.setAttr(
      fd,
      name,
      value.buffer,
      value.byteOffset,
      value.byteLength,
      req,
      onwork
    )
  } catch (err) {
    return Promise.reject(err)
  }

  return promise
}

exports.setAttrSync = function setAttrSync(fd, name, value, encoding) {
  if (typeof value === 'string') value = Buffer.from(value, encoding)

  binding.setAttrSync(fd, name, value.buffer, value.byteOffset, value.byteLength)
}

exports.removeAttr = function removeAttr(fd, name) {
  const req = {
    handle: null,
    resolve: null,
    reject: null
  }

  const promise = new Promise((resolve, reject) => {
    req.resolve = resolve
    req.reject = reject
  })

  try {
    req.handle = binding.removeAttr(fd, name, req, onwork)
  } catch (err) {
    return Promise.reject(err)
  }

  return promise
}

exports.removeAttrSync = function removeAttrSync(fd, name) {
  binding.removeAttrSync(fd, name)
}

exports.listAttrs = function listAttrs(fd) {
  const req = {
    handle: null,
    resolve: null,
    reject: null
  }

  const promise = new Promise((resolve, reject) => {
    req.resolve = resolve
    req.reject = reject
  })

  try {
    req.handle = binding.listAttrs(fd, req, onwork)
  } catch (err) {
    return Promise.reject(err)
  }

  return promise
}

exports.listAttrsSync = function listAttrsSync(fd) {
  return binding.listAttrsSync(fd)
}
{
  "name": "fs-native-extensions",
  "version": "1.5.1",
  "description": "Native file system extensions for advanced file operations",
  "main": "index.js",
  "files": [
    "index.js",
    "macros.h",
    "binding.c",
    "binding.js",
    "CMakeLists.txt",
    "include",
    "src",
    "prebuilds"
  ],
  "imports": {
    "child_process": {
      "bare": "bare-subprocess",
      "default": "child_process"
    },
    "fs": {
      "bare": "bare-fs",
      "default": "fs"
    },
    "fs/*": {
      "bare": "bare-fs/*",
      "default": "fs/*"
    },
    "path": {
      "bare": "bare-path",
      "default": "path"
    }
  },
  "addon": true,
  "scripts": {
    "format": "prettier --write .",
    "lint": "prettier --check . && lunte",
    "test": "npm run test:node && npm run test:bare",
    "test:bare": "brittle-bare --coverage test.js",
    "test:node": "brittle-node --coverage test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/fs-native-extensions.git"
  },
  "author": "Kasper Isager Dalsgarð <kasper@funktionel.co>",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/fs-native-extensions/issues"
  },
  "homepage": "https://github.com/holepunchto/fs-native-extensions#readme",
  "dependencies": {
    "require-addon": "^1.1.0",
    "which-runtime": "^1.2.0"
  },
  "devDependencies": {
    "bare-compat-napi": "^1.3.8",
    "bare-fs": "^4.4.4",
    "bare-path": "^3.0.0",
    "bare-subprocess": "^4.0.5",
    "brittle": "^3.1.1",
    "cmake-bare": "^1.1.10",
    "cmake-fetch": "^1.4.7",
    "cmake-napi": "^1.0.5",
    "cmake-npm": "^1.1.0",
    "lunte": "^1.6.0",
    "minimist": "^1.2.6",
    "prettier": "^3.5.3",
    "prettier-config-holepunch": "^2.0.0",
    "test-tmp": "^1.2.1"
  }
}
// hyper-identity2 — the identity layer on autobee.
//
// Same registry, same ops, same apply as `hyper-identity`; all of that lives in
// `hyper-identity-core` and is shared, not copied. What differs is the engine, and the engine is
// the PACKAGE — pick the one you want rather than passing a flag that could be wrong.
//
// It depends on autobee and NOT on autobase, which is the whole point: a consumer already running
// autobee (favela-peer) can use the identity layer without carrying a second multi-writer engine.

export { IdentityRegistry } from './lib/registry.js'
export { Hyperbee2View, BatchView } from './lib/view-shim.js'
export { adaptHost } from './lib/adapt-host.js'
export { OpTypes, encodeOp, decodeOp, validateOp, createApplyFunction } from 'hyper-identity-core'
// The autobee spelling of the core's writer-set contract.
//
// autobee already speaks `{ isIndexer }`, so this looks like it could be omitted. ⛔ It could not.
//
//     addWriter(key, { isIndexer = true, weight = isIndexer ? 2 : 1 } = {})
//                                 ^^^^ autobee 2.1.0 lib/apply-calls.js:33
//
// The default is TRUE. So the difference between passing the flag and forgetting it is the
// difference between a plain writer and an indexer — and forgetting it is what a "no translation
// needed, just pass through" binding does the first time the core adds a call site that omits it.
//
// Pinning it here means the absent case is decided in the file that knows autobee's defaults,
// rather than inherited from them silently.

export function adaptHost (host) {
  if (!host || typeof host.addWriter !== 'function') return host
  return {
    ...host,
    addWriter: (key, opts = {}) => host.addWriter(key, { isIndexer: !!opts.isIndexer }),
    removeWriter: (key) => host.removeWriter(key)
  }
}

export default adaptHost
// IdentityRegistry — the AUTOBEE binding of hyper-identity-core's registry.
//
// Sibling of `hyper-identity`, which is the autobase binding. Same class, same ops, same apply,
// same reads — the engine is a package, not an option, so nothing here can silently be the other
// one.

import Autobee from 'autobee'
import { BaseIdentityRegistry } from 'hyper-identity-core/registry'
import { Hyperbee2View } from './view-shim.js'
import { adaptHost } from './adapt-host.js'

export class IdentityRegistry extends BaseIdentityRegistry {
  static engine = 'autobee'

  _adaptHost () { return adaptHost }

  // ⛔ autobee attributes an op to `node.key`; autobase to `node.from.key`. Without this the core's
  // contract check reads an undefined `node.from` and skips — a permission gate that stops gating
  // with no error.
  _writerKeyOf (node) { return node && node.key ? node.key : null }

  async _createBase ({ apply, rootKey }) {
    // ⛔ `fastForward` is NOT the same option here. autobase takes `{ key }` — the log's key.
    // autobee takes a BOOT HEAD, `{ boot: { head: { key, length } } }` naming a writer's core at a
    // length, which a joiner cannot derive from a log key: it has to be conveyed or remembered.
    //
    // So `fastForwardTo` has no autobee meaning, and quietly ignoring it would be the same class of
    // bug as the addWriter flag — an option accepted and dropped.
    if (this._fastForwardTo) {
      throw new Error(
        'hyper-identity2: fastForwardTo is an autobase option and has no autobee equivalent — ' +
        'autobee fast-forward needs an oplog BOOT HEAD ({ key, length } of a writer core), not a ' +
        'log key. Omit it, or convey a head and pass fastForward directly.')
    }

    return new Autobee(this._store, rootKey, {
      apply,
      // autobee's open() receives a hyperbee2 INSTANCE, where autobase's receives a store. That
      // one-line difference is why the view shim exists.
      open: (bee) => new Hyperbee2View(bee),
      keyPair: this._keyPair || undefined,
      optimistic: true,
      // ⚠ OFF, deliberately, and not a workaround. Unconfigured, autobee PROBES for candidate
      // oplog heads and pays DEFAULT_OP_TIMEOUT (5s, lib/constants.js:5) per wakeup batch looking
      // for something that does not exist when there are no stamped heads. Measured at 25 writers:
      // 25s versus 0.2s. Declining to search is not the same as disabling a feature that works.
      fastForward: false
    })
  }
}

export default IdentityRegistry
// The view shim — hyperbee2 wearing hyperbee's face.
//
// hyper-identity-core's registry and apply were written against a Hyperbee configured
// `keyEncoding: 'utf-8', valueEncoding: 'json'`. hyperbee2 has NEITHER, and no `put`/`del` at all:
// keys and values are raw Buffers and every mutation goes through a `write()` batch
// (`tryPut`/`tryDelete` → `await flush()`). Measured against hyperbee2 2.13.2.
//
// So this file is the entire cost of the port. 65 call sites in the core depend on the Hyperbee
// shape — 31 puts and 13 deletes in apply, 14 gets and 7 range scans in the registry — and every
// one of them stays unchanged because this presents that shape.
//
// ── ⛔ THE PART THAT IS NOT A CODEC ────────────────────────────────────────────────────────────
//
// apply calls `createReadStream` ON THE BATCH, at three sites. One of them is `hasAnyUser`, which
// decides whether a new user is promoted to admin:
//
//     const isFirstUser = !(await hasAnyUser(b, prefix))
//
// Against a Hyperbee that is trivially correct — a `put` is visible to a read stream immediately.
// Against a batch it is not: writes sit unflushed. If two CREATE_USER ops land in ONE apply batch
// and the scan cannot see the first, the second is ALSO promoted, and the log now has two admins.
// No error, no warning; a permissions bug produced by a storage detail.
//
// That is why `BatchView` merges its pending writes into every scan rather than keeping a plain
// map of them. A pending map is enough for read-your-own-writes by KEY (favela-peer's feed-base.js
// gets away with exactly that) and is NOT enough the moment apply iterates a RANGE.

import b4a from 'b4a'

const encKey = (k) => (typeof k === 'string' ? b4a.from(k, 'utf8') : k)
const decKey = (k) => (typeof k === 'string' ? k : b4a.toString(k, 'utf8'))
const encVal = (v) => b4a.from(JSON.stringify(v), 'utf8')
const decVal = (v) => {
  if (v == null) return null
  // A value that will not parse is data we did not write. Returning null loses it; throwing inside
  // apply wedges the log (apply must be TOTAL). Neither is good, so it surfaces as null and the
  // caller treats it as absent — the same thing a missing key does.
  try { return JSON.parse(b4a.toString(v, 'utf8')) } catch { return null }
}

// Range bounds are encoded with the SAME function as keys, so `user:\xff` bounds `user:…` exactly
// as it did under utf-8 keyEncoding. Comparison is by BYTES, never by JS string order — those
// diverge above the BMP, and a username is user-supplied text.
function inRange (key, range) {
  if (range.gt !== undefined && b4a.compare(key, encKey(range.gt)) <= 0) return false
  if (range.gte !== undefined && b4a.compare(key, encKey(range.gte)) < 0) return false
  if (range.lt !== undefined && b4a.compare(key, encKey(range.lt)) >= 0) return false
  if (range.lte !== undefined && b4a.compare(key, encKey(range.lte)) > 0) return false
  return true
}

const streamOpts = (range) => {
  const o = {}
  for (const b of ['gt', 'gte', 'lt', 'lte']) if (range[b] !== undefined) o[b] = encKey(range[b])
  return o
}

/**
 * The view the core sees. `get` + `createReadStream` for the registry's reads, and `batch()` for
 * apply — which calls `view.batch({ update: false })`, then `flush()` or `close()` on what it gets.
 */
export class Hyperbee2View {
  constructor (bee) { this.bee = bee }

  // apply's write handle. The option is accepted and ignored: hyperbee2 has no auto-update on a
  // write batch, so `{ update: false }` is already its only behaviour.
  batch (opts = {}) { return new BatchView(this.bee) }

  async get (key) {
    const node = await this.bee.get(encKey(key))
    if (!node) return null
    return { key: decKey(node.key), value: decVal(node.value) }
  }

  async * createReadStream (range = {}) {
    let n = 0
    // `limit` is applied here rather than passed down, because it is not part of hyperbee2's
    // documented range options and a silently-ignored limit reads as "scan found everything".
    for await (const node of this.bee.createReadStream(streamOpts(range))) {
      yield { key: decKey(node.key), value: decVal(node.value) }
      if (range.limit !== undefined && ++n >= range.limit) return
    }
  }
}

/**
 * A writable view backed by ONE hyperbee2 batch, with pending writes visible to reads.
 *
 * One batch per apply CALL, not per mutation. The autobee spike's shim flushed a batch per write
 * and was never benchmarked; apply performs up to 44 mutations in a call, so that is 44 commits
 * where a Hyperbee did none.
 */
export class BatchView {
  constructor (bee) {
    this.bee = bee
    this.batch = bee.write()
    // key (utf-8 string) -> value, or DELETED. Ordered by insertion; scans sort what they need.
    this.pending = new Map()
  }

  async get (key) {
    const k = decKey(key)
    if (this.pending.has(k)) {
      const v = this.pending.get(k)
      return v === BatchView.DELETED ? null : { key: k, value: v }
    }
    const node = await this.bee.get(encKey(key))
    if (!node) return null
    return { key: decKey(node.key), value: decVal(node.value) }
  }

  async put (key, value) {
    const k = decKey(key)
    this.pending.set(k, value)
    this.batch.tryPut(encKey(key), encVal(value))
  }

  async del (key) {
    const k = decKey(key)
    this.pending.set(k, BatchView.DELETED)
    this.batch.tryDelete(encKey(key))
  }

  /**
   * A range scan that sees this batch's own unflushed writes.
   *
   * ⛔ Read the header. Without the merge, `hasAnyUser` misses a user created earlier in the same
   * batch and every user in that batch is promoted to admin.
   *
   * Merges two ordered sources by byte order: the committed stream, and the pending writes that
   * fall in range. Pending wins on a tie — it is newer — and a pending deletion removes the key
   * from the result rather than yielding a tombstone.
   */
  async * createReadStream (range = {}) {
    const pend = []
    for (const [k, v] of this.pending) {
      const ek = encKey(k)
      if (inRange(ek, range)) pend.push({ ek, key: k, value: v })
    }
    pend.sort((a, b) => b4a.compare(a.ek, b.ek))

    let i = 0
    let n = 0
    const take = (key, value) => {
      if (range.limit !== undefined && n >= range.limit) return 'stop'
      n++
      return { key, value }
    }

    for await (const node of this.bee.createReadStream(streamOpts(range))) {
      const ek = node.key
      // Emit every pending key that sorts before this one.
      while (i < pend.length && b4a.compare(pend[i].ek, ek) < 0) {
        const p = pend[i++]
        if (p.value === BatchView.DELETED) continue
        const out = take(p.key, p.value)
        if (out === 'stop') return
        yield out
      }
      // Same key: the pending write supersedes the committed one.
      if (i < pend.length && b4a.compare(pend[i].ek, ek) === 0) {
        const p = pend[i++]
        if (p.value === BatchView.DELETED) continue
        const out = take(p.key, p.value)
        if (out === 'stop') return
        yield out
        continue
      }
      const out = take(decKey(ek), decVal(node.value))
      if (out === 'stop') return
      yield out
    }

    // Whatever pending keys sort after everything committed.
    while (i < pend.length) {
      const p = pend[i++]
      if (p.value === BatchView.DELETED) continue
      const out = take(p.key, p.value)
      if (out === 'stop') return
      yield out
    }
  }

  async flush () { await this.batch.flush() }

  // apply calls this when a batch throws, to drop the uncommitted writes rather than half-apply
  // them. hyperbee2's write batch has its own close(); the pending map goes with it.
  async close () {
    this.pending.clear()
    if (typeof this.batch.close === 'function') await this.batch.close()
  }
}

// A sentinel, not `null` — `null` is a legal stored value and would read as "deleted".
BatchView.DELETED = Symbol('deleted')

export default { Hyperbee2View, BatchView }
const { Pull, Push, HEADERBYTES, KEYBYTES, ABYTES } = require('sodium-secretstream')
const sodium = require('sodium-universal')
const crypto = require('hypercore-crypto')
const { Duplex, Writable, getStreamError } = require('streamx')
const b4a = require('b4a')
const Timeout = require('timeout-refresh')
const unslab = require('unslab')
const Bridge = require('./lib/bridge')
const Handshake = require('./lib/handshake')

const IDHEADERBYTES = HEADERBYTES + 32
const [NS_INITIATOR, NS_RESPONDER, NS_SEND] = crypto.namespace('hyperswarm/secret-stream', 3)
const MAX_ATOMIC_WRITE = 256 * 256 * 256 - 1

module.exports = class NoiseSecretStream extends Duplex {
  constructor(isInitiator, rawStream, opts = {}) {
    super({ mapWritable: toBuffer })

    if (typeof isInitiator !== 'boolean') {
      throw new Error('isInitiator should be a boolean')
    }

    this.noiseStream = this
    this.isInitiator = isInitiator
    this.rawStream = null

    this.publicKey = opts.publicKey || null
    this.remotePublicKey = opts.remotePublicKey || null
    this.handshakeHash = null
    this.connected = false
    this.keepAlive = opts.keepAlive || 0
    this.timeout = 0
    this.enableSend = opts.enableSend !== false

    // pointer for upstream to set data here if they want
    this.userData = null

    let openedDone = null
    this.opened = new Promise((resolve) => {
      openedDone = resolve
    })

    this.rawBytesWritten = 0
    this.rawBytesRead = 0

    // metadata used by 'hyperdht'
    this.relay = null
    this.puncher = null

    // unwrapped raw stream
    this._rawStream = null

    // handshake state
    this._handshake = null
    this._handshakePattern = opts.pattern || null
    this._handshakeDone = null

    // message parsing state
    this._state = 0
    this._len = 0
    this._tmp = 1
    this._message = null

    this._openedDone = openedDone
    this._startDone = null
    this._drainDone = null
    this._outgoingPlain = null
    this._outgoingWrapped = null
    this._utp = null
    this._setup = true
    this._ended = 2
    this._encrypt = null
    this._decrypt = null
    this._timeoutTimer = null
    this._keepAliveTimer = null
    this._sendState = null

    if (opts.autoStart !== false) this.start(rawStream, opts)

    // wiggle it to trigger open immediately (TODO add streamx option for this)
    this.resume()
    this.pause()
  }

  static keyPair(seed) {
    return Handshake.keyPair(seed)
  }

  static id(handshakeHash, isInitiator, id) {
    return streamId(handshakeHash, isInitiator, id)
  }

  setTimeout(ms) {
    if (!ms) ms = 0

    this._clearTimeout()
    this.timeout = ms

    if (!ms || this.rawStream === null) return

    this._timeoutTimer = Timeout.once(ms, destroyTimeout, this)
    this._timeoutTimer.unref()
  }

  setKeepAlive(ms) {
    if (!ms) ms = 0

    this._clearKeepAlive()

    this.keepAlive = ms

    if (!ms || this.rawStream === null) return

    this._keepAliveTimer = Timeout.on(ms, sendKeepAlive, this)
    this._keepAliveTimer.unref()
  }

  sendKeepAlive() {
    const empty = this.alloc(0)
    this.write(empty)
  }

  start(rawStream, opts = {}) {
    if (rawStream) {
      this.rawStream = rawStream
      this._rawStream = rawStream
      if (typeof this.rawStream.setContentSize === 'function') {
        this._utp = rawStream
      }
    } else {
      this.rawStream = new Bridge(this)
      this._rawStream = this.rawStream.reverse
    }

    this.rawStream.on('error', this._onrawerror.bind(this))
    this.rawStream.on('close', this._onrawclose.bind(this))

    this._startHandshake(opts.handshake, opts.keyPair || null)
    this._continueOpen(null)

    if (this.destroying) return

    if (opts.data) this._onrawdata(opts.data)
    if (opts.ended) this._onrawend()

    if (this.keepAlive > 0 && this._keepAliveTimer === null) {
      this.setKeepAlive(this.keepAlive)
    }

    if (this.timeout > 0 && this._timeoutTimer === null) {
      this.setTimeout(this.timeout)
    }
  }

  async flush() {
    if ((await this.opened) === false) return false
    if ((await Writable.drained(this)) === false) return false
    if (this.destroying) return false

    if (this.rawStream !== null && this.rawStream.flush) {
      return await this.rawStream.flush()
    }

    return true
  }

  _continueOpen(err) {
    if (err) this.destroy(err)
    if (this._startDone === null) return
    const done = this._startDone
    this._startDone = null
    this._open(done)
  }

  _onkeypairpromise(p) {
    const self = this
    const cont = this._continueOpen.bind(this)

    p.then(onkeypair, cont)

    function onkeypair(kp) {
      self._onkeypair(kp)
      cont(null)
    }
  }

  _onkeypair(keyPair) {
    const pattern = this._handshakePattern || 'XX'
    const remotePublicKey = this.remotePublicKey

    this._handshake = new Handshake(this.isInitiator, keyPair, remotePublicKey, pattern)
    this.publicKey = this._handshake.keyPair.publicKey
  }

  _startHandshake(handshake, keyPair) {
    if (handshake) {
      const { tx, rx, hash, publicKey, remotePublicKey } = handshake
      this._setupSecretStream(tx, rx, hash, publicKey, remotePublicKey)
      return
    }

    if (!keyPair) keyPair = Handshake.keyPair()

    if (typeof keyPair.then === 'function') {
      this._onkeypairpromise(keyPair)
    } else {
      this._onkeypair(keyPair)
    }
  }

  _onrawerror(err) {
    this.destroy(err)
  }

  _onrawclose() {
    if (this._ended !== 0) this.destroy()
  }

  _onrawdata(data) {
    let offset = 0

    if (this._timeoutTimer !== null) {
      this._timeoutTimer.refresh()
    }

    do {
      switch (this._state) {
        case 0: {
          while (this._tmp !== 0x1000000 && offset < data.byteLength) {
            const v = data[offset++]
            this._len += this._tmp * v
            this._tmp *= 256
          }

          if (this._tmp === 0x1000000) {
            this._tmp = 0
            this._state = 1
            const unprocessed = data.byteLength - offset
            if (unprocessed < this._len && this._utp !== null)
              this._utp.setContentSize(this._len - unprocessed)
          }

          break
        }

        case 1: {
          const missing = this._len - this._tmp
          const end = missing + offset

          if (this._message === null && end <= data.byteLength) {
            this._message = data.subarray(offset, end)
            offset += missing
            this._incoming()
            break
          }

          const unprocessed = data.byteLength - offset

          if (this._message === null) {
            this._message = b4a.allocUnsafe(this._len)
          }

          b4a.copy(data, this._message, this._tmp, offset)
          this._tmp += unprocessed

          if (end <= data.byteLength) {
            offset += missing
            this._incoming()
          } else {
            offset += unprocessed
          }

          break
        }
      }
    } while (offset < data.byteLength && !this.destroying)
  }

  _onrawend() {
    this._ended--
    this.push(null)
  }

  _onrawdrain() {
    const drain = this._drainDone
    if (drain === null) return
    this._drainDone = null
    drain()
  }

  _read(cb) {
    this.rawStream.resume()
    cb(null)
  }

  _incoming() {
    const message = this._message

    this._state = 0
    this._len = 0
    this._tmp = 1
    this._message = null

    if (this._setup === true) {
      if (this._handshake) {
        this._onhandshakert(this._handshake.recv(message))
      } else {
        if (message.byteLength !== IDHEADERBYTES) {
          this.destroy(new Error('Invalid header message received'))
          return
        }

        const remoteId = message.subarray(0, 32)
        const expectedId = streamId(this.handshakeHash, !this.isInitiator)
        const header = message.subarray(32)

        if (!b4a.equals(expectedId, remoteId)) {
          this.destroy(new Error('Invalid header received'))
          return
        }

        this._decrypt.init(header)
        this._setup = false // setup is now done
      }
      return
    }

    if (message.byteLength < ABYTES) {
      this.destroy(new Error('Invalid message received'))
      return
    }

    this.rawBytesRead += message.byteLength

    const plain = message.subarray(1, message.byteLength - ABYTES + 1)

    try {
      this._decrypt.next(message, plain)
    } catch (err) {
      this.destroy(err)
      return
    }

    // If keep alive is selective, eat the empty buffers (ie assume the other side has it enabled also)
    if (plain.byteLength === 0 && this.keepAlive !== 0) return

    if (this.push(plain) === false) {
      this.rawStream.pause()
    }
  }

  _onhandshakert(h) {
    if (this._handshakeDone === null) return

    if (h !== null) {
      if (h.data) this._rawStream.write(h.data)
      if (!h.tx) return
    }

    const done = this._handshakeDone
    const publicKey = this._handshake.keyPair.publicKey

    this._handshakeDone = null
    this._handshake = null

    if (h === null) return done(new Error('Noise handshake failed'))

    this._setupSecretStream(h.tx, h.rx, h.hash, publicKey, h.remotePublicKey)
    this._resolveOpened(true)
    done(null)
  }

  _setupSecretStream(tx, rx, handshakeHash, publicKey, remotePublicKey) {
    const buf = b4a.allocUnsafeSlow(3 + IDHEADERBYTES)
    writeUint24le(IDHEADERBYTES, buf)

    this._encrypt = new Push(unslab(tx.subarray(0, KEYBYTES)), undefined, buf.subarray(3 + 32))
    this._decrypt = new Pull(unslab(rx.subarray(0, KEYBYTES)))

    this.publicKey = publicKey
    this.remotePublicKey = remotePublicKey
    this.handshakeHash = handshakeHash

    const id = buf.subarray(3, 3 + 32)
    streamId(handshakeHash, this.isInitiator, id)

    // initialize secretbox state for unordered messages
    this._setupSecretSend(handshakeHash)

    this.emit('handshake')
    // if rawStream is a bridge, also emit it there
    if (this.rawStream !== this._rawStream) this.rawStream.emit('handshake')

    if (this.destroying) return

    this._rawStream.write(buf)
  }

  _setupSecretSend(handshakeHash) {
    this._sendState = b4a.allocUnsafeSlow(32 + 32 + 8 + 8)
    const encrypt = this._sendState.subarray(0, 32) // secrets
    const decrypt = this._sendState.subarray(32, 64)
    const counter = this._sendState.subarray(64, 72) // nonce
    const initial = this._sendState.subarray(72)

    const inputs = this.isInitiator
      ? [
          [NS_INITIATOR, NS_SEND],
          [NS_RESPONDER, NS_SEND]
        ]
      : [
          [NS_RESPONDER, NS_SEND],
          [NS_INITIATOR, NS_SEND]
        ]

    sodium.crypto_generichash_batch(encrypt, inputs[0], handshakeHash)
    sodium.crypto_generichash_batch(decrypt, inputs[1], handshakeHash)

    sodium.randombytes_buf(initial)
    counter.set(initial)
  }

  _open(cb) {
    // no autostart or no handshake yet
    if (this._rawStream === null || (this._handshake === null && this._encrypt === null)) {
      this._startDone = cb
      return
    }

    this._rawStream.on('data', this._onrawdata.bind(this))
    this._rawStream.on('end', this._onrawend.bind(this))
    this._rawStream.on('drain', this._onrawdrain.bind(this))

    if (this.enableSend) this._rawStream.on('message', this._onmessage.bind(this))

    if (this._encrypt !== null) {
      this._resolveOpened(true)
      return cb(null)
    }

    this._handshakeDone = cb

    if (this.isInitiator) this._onhandshakert(this._handshake.send())
  }

  _predestroy() {
    if (this.rawStream) {
      const error = getStreamError(this)
      this.rawStream.destroy(error)
    }

    if (this._startDone !== null) {
      const done = this._startDone
      this._startDone = null
      done(new Error('Stream destroyed'))
    }

    if (this._handshakeDone !== null) {
      const done = this._handshakeDone
      this._handshakeDone = null
      done(new Error('Stream destroyed'))
    }

    if (this._drainDone !== null) {
      const done = this._drainDone
      this._drainDone = null
      done(new Error('Stream destroyed'))
    }
  }

  _write(data, cb) {
    let wrapped = this._outgoingWrapped

    if (data !== this._outgoingPlain) {
      wrapped = b4a.allocUnsafe(data.byteLength + 3 + ABYTES)
      wrapped.set(data, 4)
    } else {
      this._outgoingWrapped = this._outgoingPlain = null
    }

    if (wrapped.byteLength - 3 > MAX_ATOMIC_WRITE) {
      return cb(
        new Error(
          'Message is too large for an atomic write. Max size is ' + MAX_ATOMIC_WRITE + ' bytes.'
        )
      )
    }
    this.rawBytesWritten += wrapped.byteLength

    writeUint24le(wrapped.byteLength - 3, wrapped)
    // offset 4 so we can do it in-place
    this._encrypt.next(wrapped.subarray(4, 4 + data.byteLength), wrapped.subarray(3))

    if (this._keepAliveTimer !== null) this._keepAliveTimer.refresh()

    if (this._rawStream.write(wrapped) === false) {
      this._drainDone = cb
    } else {
      cb(null)
    }
  }

  _final(cb) {
    this._clearKeepAlive()
    this._ended--
    this._rawStream.end()
    cb(null)
  }

  _resolveOpened(val) {
    if (this._openedDone === null) return
    const opened = this._openedDone
    this._openedDone = null
    opened(val)
    if (!val) return
    this.connected = true
    this.emit('connect')
  }

  _clearTimeout() {
    if (this._timeoutTimer === null) return
    this._timeoutTimer.destroy()
    this._timeoutTimer = null
    this.timeout = 0
  }

  _clearKeepAlive() {
    if (this._keepAliveTimer === null) return
    this._keepAliveTimer.destroy()
    this._keepAliveTimer = null
    this.keepAlive = 0
  }

  _destroy(cb) {
    this._clearKeepAlive()
    this._clearTimeout()
    this._resolveOpened(false)
    cb(null)
  }

  _boxMessage(buffer) {
    const MB = sodium.crypto_secretbox_MACBYTES // 16
    const NB = sodium.crypto_secretbox_NONCEBYTES // 24

    const counter = this._sendState.subarray(64, 72)
    sodium.sodium_increment(counter)
    if (b4a.equals(counter, this._sendState.subarray(72))) {
      this.destroy(new Error('udp send nonce exchausted'))
      return
    }

    const secret = this._sendState.subarray(0, 32)
    const envelope = b4a.allocUnsafe(8 + MB + buffer.byteLength)
    const nonce = envelope.subarray(0, NB)
    const ciphertext = envelope.subarray(8)

    b4a.fill(nonce, 0) // pad suffix
    nonce.set(counter)

    sodium.crypto_secretbox_easy(ciphertext, buffer, nonce, secret)
    return envelope
  }

  send(buffer) {
    if (!this._sendState) return
    if (!this.rawStream?.send) return // udx-stream expected

    const message = this._boxMessage(buffer)
    return this.rawStream.send(message)
  }

  trySend(buffer) {
    if (!this._sendState) return
    if (!this.rawStream?.trySend) return // udx-stream expected

    const message = this._boxMessage(buffer)
    this.rawStream.trySend(message)
  }

  _onmessage(buffer) {
    if (!this._sendState) return // messages before handshake are dropped

    const MB = sodium.crypto_secretbox_MACBYTES // 16
    const NB = sodium.crypto_secretbox_NONCEBYTES // 24

    if (buffer.byteLength < NB) return // Invalid message

    const nonce = b4a.allocUnsafe(NB)
    b4a.fill(nonce, 0)
    nonce.set(buffer.subarray(0, 8))

    const secret = this._sendState.subarray(32, 64)
    const ciphertext = buffer.subarray(8)
    const plain = buffer.subarray(8, buffer.byteLength - MB)

    if (ciphertext.byteLength < MB) return // invalid message

    const success = sodium.crypto_secretbox_open_easy(plain, ciphertext, nonce, secret)

    if (success) this.emit('message', plain)
  }

  alloc(len) {
    const buf = b4a.allocUnsafe(len + 3 + ABYTES)
    this._outgoingWrapped = buf
    this._outgoingPlain = buf.subarray(4, buf.byteLength - ABYTES + 1)
    return this._outgoingPlain
  }

  toJSON() {
    return {
      isInitiator: this.isInitiator,
      publicKey: this.publicKey && b4a.toString(this.publicKey, 'hex'),
      remotePublicKey: this.remotePublicKey && b4a.toString(this.remotePublicKey, 'hex'),
      connected: this.connected,
      destroying: this.destroying,
      destroyed: this.destroyed,
      rawStream: this.rawStream && this.rawStream.toJSON ? this.rawStream.toJSON() : null
    }
  }
}

function writeUint24le(n, buf) {
  buf[0] = n & 255
  buf[1] = (n >>> 8) & 255
  buf[2] = (n >>> 16) & 255
}

function streamId(handshakeHash, isInitiator, out = b4a.allocUnsafe(32)) {
  sodium.crypto_generichash(out, isInitiator ? NS_INITIATOR : NS_RESPONDER, handshakeHash)
  return out
}

function toBuffer(data) {
  return typeof data === 'string' ? b4a.from(data) : data
}

function destroyTimeout() {
  this.destroy(new Error('Stream timed out'))
}

function sendKeepAlive() {
  const empty = this.alloc(0)
  this.write(empty)
}
const { Duplex, Writable } = require('streamx')

class ReversePassThrough extends Duplex {
  constructor(s) {
    super()
    this._stream = s
    this._ondrain = null
  }

  _write(data, cb) {
    if (this._stream.push(data) === false) {
      this._stream._ondrain = cb
    } else {
      cb(null)
    }
  }

  _final(cb) {
    this._stream.push(null)
    cb(null)
  }

  _read(cb) {
    const ondrain = this._ondrain
    this._ondrain = null
    if (ondrain) ondrain()
    cb(null)
  }
}

module.exports = class Bridge extends Duplex {
  constructor(noiseStream) {
    super()

    this.noiseStream = noiseStream

    this._ondrain = null
    this.reverse = new ReversePassThrough(this)
  }

  get publicKey() {
    return this.noiseStream.publicKey
  }

  get remotePublicKey() {
    return this.noiseStream.remotePublicKey
  }

  get handshakeHash() {
    return this.noiseStream.handshakeHash
  }

  flush() {
    return Writable.drained(this)
  }

  _read(cb) {
    const ondrain = this._ondrain
    this._ondrain = null
    if (ondrain) ondrain()
    cb(null)
  }

  _write(data, cb) {
    if (this.reverse.push(data) === false) {
      this.reverse._ondrain = cb
    } else {
      cb(null)
    }
  }

  _final(cb) {
    this.reverse.push(null)
    cb(null)
  }
}
const sodium = require('sodium-universal')
const curve = require('noise-curve-ed')
const Noise = require('noise-handshake')
const b4a = require('b4a')

const EMPTY = b4a.alloc(0)

module.exports = class Handshake {
  constructor(isInitiator, keyPair, remotePublicKey, pattern) {
    this.isInitiator = isInitiator
    this.keyPair = keyPair
    this.noise = new Noise(pattern, isInitiator, keyPair, { curve })
    this.noise.initialise(EMPTY, remotePublicKey)
    this.destroyed = false
  }

  static keyPair(seed) {
    const publicKey = b4a.alloc(32)
    const secretKey = b4a.alloc(64)
    if (seed) sodium.crypto_sign_seed_keypair(publicKey, secretKey, seed)
    else sodium.crypto_sign_keypair(publicKey, secretKey)
    return { publicKey, secretKey }
  }

  recv(data) {
    try {
      this.noise.recv(data)
      if (this.noise.complete) return this._return(null)
      return this.send()
    } catch {
      this.destroy()
      return null
    }
  }

  // note that the data returned here is framed so we don't have to do an extra copy
  // when sending it...
  send() {
    try {
      const data = this.noise.send()
      const wrap = b4a.allocUnsafe(data.byteLength + 3)

      writeUint24le(data.byteLength, wrap)
      wrap.set(data, 3)

      return this._return(wrap)
    } catch {
      this.destroy()
      return null
    }
  }

  destroy() {
    if (this.destroyed) return
    this.destroyed = true
  }

  _return(data) {
    const tx = this.noise.complete ? b4a.toBuffer(this.noise.tx) : null
    const rx = this.noise.complete ? b4a.toBuffer(this.noise.rx) : null
    const hash = this.noise.complete ? b4a.toBuffer(this.noise.hash) : null
    const remotePublicKey = this.noise.complete ? b4a.toBuffer(this.noise.rs) : null

    return {
      data,
      remotePublicKey,
      hash,
      tx,
      rx
    }
  }
}

function writeUint24le(n, buf) {
  buf[0] = n & 255
  buf[1] = (n >>> 8) & 255
  buf[2] = (n >>> 16) & 255
}
{
  "name": "@hyperswarm/secret-stream",
  "version": "6.9.1",
  "description": "Secret stream backed by Noise and libsodium's secretstream",
  "main": "index.js",
  "files": [
    "index.js",
    "lib/**.js"
  ],
  "dependencies": {
    "b4a": "^1.1.0",
    "hypercore-crypto": "^3.3.1",
    "noise-curve-ed": "^2.0.1",
    "noise-handshake": "^4.0.0",
    "sodium-secretstream": "^1.1.0",
    "sodium-universal": "^5.0.0",
    "streamx": "^2.14.0",
    "timeout-refresh": "^2.0.0",
    "unslab": "^1.3.0"
  },
  "devDependencies": {
    "brittle": "^3.3.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^2.0.0",
    "udx-native": "^1.13.2"
  },
  "scripts": {
    "format": "prettier --write .",
    "lint": "prettier --check .",
    "test": "brittle test.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/holepunchto/hyperswarm-secret-stream.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/hyperswarm-secret-stream/issues"
  },
  "homepage": "https://github.com/holepunchto/hyperswarm-secret-stream"
}
const HypercoreEncryption = require('hypercore/lib/default-encryption.js')
const sodium = require('sodium-universal')
const crypto = require('hypercore-crypto')
const c = require('compact-encoding')
const b4a = require('b4a')

const ManifestData = {
  preencode(state, m) {
    c.uint.preencode(state, m.version)
    state.end++ // max flag is 2 so always one byte

    if (m.legacyBlocks) c.uint.preencode(state, m.legacyBlocks)
    if (m.namespace) c.fixed32.preencode(state, m.namespace)
  },
  encode(state, m) {
    const flags = (m.legacyBlocks ? 1 : 0) | (m.namespace ? 2 : 0)

    c.uint.encode(state, m.version)
    c.uint.encode(state, flags)

    if (m.legacyBlocks) c.uint.encode(state, m.legacyBlocks)
    if (m.namespace) c.fixed32.encode(state, m.namespace)
  },
  decode(state) {
    const r0 = c.uint.decode(state)
    const flags = c.uint.decode(state)

    return {
      version: r0,
      legacyBlocks: (flags & 1) !== 0 ? c.uint.decode(state) : 0,
      namespace: (flags & 2) !== 0 ? c.fixed32.decode(state) : null
    }
  }
}

const [, NS_VIEW_BLOCK_KEY, NS_HASH_KEY] = crypto.namespace('autobase', 4)

const [GENESIS_ENTROPY] = crypto.namespace('autobase/entropy', 2)

const nonce = b4a.alloc(sodium.crypto_stream_NONCEBYTES)
const hash = nonce.subarray(0, sodium.crypto_generichash_BYTES_MIN)

class AutobeeEncryption {
  static PADDING = 8

  constructor(auto) {
    this.auto = auto

    this.compat = null
    this.keys = null
    this.keysById = new Map()
  }

  get id() {
    return this.keys ? this.keys.id : 0
  }

  padding() {
    return AutobeeEncryption.PADDING
  }

  isCompat(ctx) {
    return ctx.manifest.version <= 1
  }

  load(keys) {
    if (this.keys === null) this.keys = keys
  }

  async update(ctx) {
    if (!this.keys) this.keys = await this.get(0, ctx)
  }

  async get(id, ctx) {
    if (this.keysById.has(id)) return this.keysById.get(id)

    const keys = await this.getKeys(id, ctx)
    this.keysById.set(id, keys)

    return keys
  }

  async getKeys(id, ctx) {
    if (!this.auto.encryptionKey) return null

    const entropy = GENESIS_ENTROPY
    const block = this.blockKey(entropy, ctx)
    const hash = crypto.hash([NS_HASH_KEY, block])

    return {
      id,
      block,
      hash
    }
  }

  blockKey(entropy, ctx) {
    return getBlockKey(this.auto.key, this.auto.encryptionKey, entropy, ctx.key)
  }

  async _ensureCompat(ctx) {
    if (!this.compat) this.compat = this.compatKeys(ctx)
  }

  compatKeys() {
    throw new Error('Compatibility method is not specified')
  }

  async encrypt(index, block, fork, ctx) {
    if (this.isCompat(ctx, index)) {
      this._ensureCompat(ctx)
      return HypercoreEncryption.encrypt(
        index,
        block,
        fork,
        this.compat.block,
        this.compat.blinding
      )
    }

    await this.update(ctx)

    encryptBlock(index, block, this.keys.id, this.keys.block, this.keys.hash)
  }

  async decrypt(index, block, ctx) {
    if (this.isCompat(ctx, index)) {
      this._ensureCompat(ctx)
      return HypercoreEncryption.decrypt(index, block, this.compat.block)
    }

    const padding = block.subarray(0, AutobeeEncryption.PADDING)
    block = block.subarray(AutobeeEncryption.PADDING)

    const type = padding[0]
    switch (type) {
      case 0:
        return block // unencrypted

      case 1:
        break

      default:
        throw new Error('Unrecognised encryption type')
    }

    const id = c.uint32.decode({ start: 4, end: 8, buffer: padding })

    const keys = await this.get(id, ctx)

    c.uint64.encode({ start: 0, end: 8, buffer: nonce }, index)
    nonce.set(padding, 8, 16)

    // Decrypt the block using the full nonce
    decrypt(block, nonce, keys.block)
  }

  static encryptAnchor(block, bootstrap, encryptionKey, namespace) {
    const entropy = GENESIS_ENTROPY

    const blockKey = getBlockKey(bootstrap, encryptionKey, entropy, namespace)
    const hashKey = crypto.hash([NS_HASH_KEY, block])

    encryptBlock(0, block, 0, blockKey, hashKey)
  }

  static async setSystemEncryption(bootstrap, encryptionKey, core, opts) {
    await core.ready()
    if (core.manifest.version === 1) {
      const key = getCompatBlockKey(bootstrap, encryptionKey, '_system')
      core.setEncryptionKey(key, { block: true })
      return
    }

    const info = {
      key: bootstrap,
      encryptionKey
    }

    return core.setEncryption(new ViewEncryption(info, 'system'))
  }

  static getSystemEncryption(bootstrap, encryptionKey) {
    return AutobeeEncryption.getViewEncryption(bootstrap, encryptionKey, '_system')
  }

  static getViewEncryption(bootstrap, encryptionKey, name) {
    const info = { key: bootstrap, encryptionKey }
    return new ViewEncryption(info, name)
  }

  static getWriterEncryption(bootstrap, encryptionKey) {
    return new WriterEncryption({ key: bootstrap, encryptionKey })
  }
}

class ViewEncryption extends AutobeeEncryption {
  constructor(auto, name) {
    super(auto)
    this.name = name
  }

  compatKeys() {
    const block = getCompatBlockKey(this.auto.key, this.auto.encryptionKey, this.name)
    return {
      block,
      blinding: crypto.hash(block)
    }
  }

  blockKey(entropy) {
    return getCompatBlockKey(this.auto.key, entropy, this.name)
  }
}

class WriterEncryption extends AutobeeEncryption {
  compatKeys(ctx) {
    return HypercoreEncryption.deriveKeys(this.auto.encryptionKey, ctx.key)
  }

  blockKey(entropy, ctx) {
    // used for anchors
    if (ctx.manifest.userData) {
      const userData = c.decode(ManifestData, ctx.manifest.userData)
      if (userData.namespace !== null) {
        const b = getBlockKey(this.auto.key, this.auto.encryptionKey, entropy, userData.namespace)
        return b
      }
    }

    return getBlockKey(this.auto.key, this.auto.encryptionKey, entropy, ctx.key)
  }
}

module.exports = {
  AutobeeEncryption,
  WriterEncryption,
  ViewEncryption
}

function encrypt(block, nonce, key) {
  sodium.crypto_stream_xor(block, block, nonce, key)
}

function decrypt(block, nonce, key) {
  return encrypt(block, nonce, key) // symmetric
}

function getBlockKey(bootstrap, encryptionKey, entropy, hypercoreKey) {
  return (
    encryptionKey &&
    crypto.hash([NS_VIEW_BLOCK_KEY, bootstrap, encryptionKey, entropy, hypercoreKey])
  )
}

function getCompatBlockKey(bootstrap, encryptionKey, name) {
  if (typeof name === 'string') return getCompatBlockKey(bootstrap, encryptionKey, b4a.from(name))
  return encryptionKey && crypto.hash([NS_VIEW_BLOCK_KEY, bootstrap, encryptionKey, name])
}

function blockhash(block, padding, hashKey) {
  sodium.crypto_generichash(hash, block, hashKey)
  padding.set(hash.subarray(0, 8)) // copy first 8 bytes of hash
  hash.fill(0) // clear nonce buffer
}

function encryptBlock(index, block, id, blockKey, hashKey) {
  const padding = block.subarray(0, AutobeeEncryption.PADDING)
  block = block.subarray(AutobeeEncryption.PADDING)

  blockhash(block, padding, hashKey)
  c.uint32.encode({ start: 4, end: 8, buffer: padding }, id)

  c.uint64.encode({ start: 0, end: 8, buffer: nonce }, index)

  padding[0] = 1 // version in plaintext

  nonce.set(padding, 8, 16)

  // The combination of index, key id, fork id and block hash is very likely
  // to be unique for a given Hypercore and therefore our nonce is suitable
  encrypt(block, nonce, blockKey)
}
const LE = (exports.LE =
  new Uint8Array(new Uint16Array([0xff]).buffer)[0] === 0xff)

exports.BE = !LE
const b4a = require('b4a')

const { BE } = require('./endian')

exports.state = function (start = 0, end = 0, buffer = null) {
  return { start, end, buffer }
}

const raw = (exports.raw = require('./raw'))

const uint = (exports.uint = {
  preencode(state, n) {
    state.end += n <= 0xfc ? 1 : n <= 0xffff ? 3 : n <= 0xffffffff ? 5 : 9
  },
  encode(state, n) {
    if (n <= 0xfc) uint8.encode(state, n)
    else if (n <= 0xffff) {
      state.buffer[state.start++] = 0xfd
      uint16.encode(state, n)
    } else if (n <= 0xffffffff) {
      state.buffer[state.start++] = 0xfe
      uint32.encode(state, n)
    } else {
      state.buffer[state.start++] = 0xff
      uint64.encode(state, n)
    }
  },
  decode(state) {
    const a = uint8.decode(state)
    if (a <= 0xfc) return a
    if (a === 0xfd) return uint16.decode(state)
    if (a === 0xfe) return uint32.decode(state)
    return uint64.decode(state)
  }
})

const uint8 = (exports.uint8 = {
  preencode(state, n) {
    state.end += 1
  },
  encode(state, n) {
    validateUint(n)
    state.buffer[state.start++] = n
  },
  decode(state) {
    if (state.start >= state.end) throw new Error('Out of bounds')
    return state.buffer[state.start++]
  }
})

const uint16 = (exports.uint16 = {
  preencode(state, n) {
    state.end += 2
  },
  encode(state, n) {
    validateUint(n)
    state.buffer[state.start++] = n
    state.buffer[state.start++] = n >>> 8
  },
  decode(state) {
    if (state.end - state.start < 2) throw new Error('Out of bounds')
    return state.buffer[state.start++] + state.buffer[state.start++] * 0x100
  }
})

const uint24 = (exports.uint24 = {
  preencode(state, n) {
    state.end += 3
  },
  encode(state, n) {
    validateUint(n)
    state.buffer[state.start++] = n
    state.buffer[state.start++] = n >>> 8
    state.buffer[state.start++] = n >>> 16
  },
  decode(state) {
    if (state.end - state.start < 3) throw new Error('Out of bounds')
    return (
      state.buffer[state.start++] +
      state.buffer[state.start++] * 0x100 +
      state.buffer[state.start++] * 0x10000
    )
  }
})

const uint32 = (exports.uint32 = {
  preencode(state, n) {
    state.end += 4
  },
  encode(state, n) {
    validateUint(n)
    state.buffer[state.start++] = n
    state.buffer[state.start++] = n >>> 8
    state.buffer[state.start++] = n >>> 16
    state.buffer[state.start++] = n >>> 24
  },
  decode(state) {
    if (state.end - state.start < 4) throw new Error('Out of bounds')
    return (
      state.buffer[state.start++] +
      state.buffer[state.start++] * 0x100 +
      state.buffer[state.start++] * 0x10000 +
      state.buffer[state.start++] * 0x1000000
    )
  }
})

const uint40 = (exports.uint40 = {
  preencode(state, n) {
    state.end += 5
  },
  encode(state, n) {
    validateUint(n)
    const r = Math.floor(n / 0x100)
    uint8.encode(state, n)
    uint32.encode(state, r)
  },
  decode(state) {
    if (state.end - state.start < 5) throw new Error('Out of bounds')
    return uint8.decode(state) + 0x100 * uint32.decode(state)
  }
})

const uint48 = (exports.uint48 = {
  preencode(state, n) {
    state.end += 6
  },
  encode(state, n) {
    validateUint(n)
    const r = Math.floor(n / 0x10000)
    uint16.encode(state, n)
    uint32.encode(state, r)
  },
  decode(state) {
    if (state.end - state.start < 6) throw new Error('Out of bounds')
    return uint16.decode(state) + 0x10000 * uint32.decode(state)
  }
})

const uint56 = (exports.uint56 = {
  preencode(state, n) {
    state.end += 7
  },
  encode(state, n) {
    validateUint(n)
    const r = Math.floor(n / 0x1000000)
    uint24.encode(state, n)
    uint32.encode(state, r)
  },
  decode(state) {
    if (state.end - state.start < 7) throw new Error('Out of bounds')
    return uint24.decode(state) + 0x1000000 * uint32.decode(state)
  }
})

const uint64 = (exports.uint64 = {
  preencode(state, n) {
    state.end += 8
  },
  encode(state, n) {
    validateUint(n)
    const r = Math.floor(n / 0x100000000)
    uint32.encode(state, n)
    uint32.encode(state, r)
  },
  decode(state) {
    if (state.end - state.start < 8) throw new Error('Out of bounds')
    return uint32.decode(state) + 0x100000000 * uint32.decode(state)
  }
})

const int = (exports.int = zigZagInt(uint))
exports.int8 = zigZagInt(uint8)
exports.int16 = zigZagInt(uint16)
exports.int24 = zigZagInt(uint24)
exports.int32 = zigZagInt(uint32)
exports.int40 = zigZagInt(uint40)
exports.int48 = zigZagInt(uint48)
exports.int56 = zigZagInt(uint56)
exports.int64 = zigZagInt(uint64)

const biguint64 = (exports.biguint64 = {
  preencode(state, n) {
    state.end += 8
  },
  encode(state, n) {
    const view = new DataView(
      state.buffer.buffer,
      state.start + state.buffer.byteOffset,
      8
    )
    view.setBigUint64(0, n, true) // little endian
    state.start += 8
  },
  decode(state) {
    if (state.end - state.start < 8) throw new Error('Out of bounds')
    const view = new DataView(
      state.buffer.buffer,
      state.start + state.buffer.byteOffset,
      8
    )
    const n = view.getBigUint64(0, true) // little endian
    state.start += 8
    return n
  }
})

exports.bigint64 = zigZagBigInt(biguint64)

const biguint = (exports.biguint = {
  preencode(state, n) {
    let len = 0
    for (let m = n; m; m = m >> 64n) len++
    uint.preencode(state, len)
    state.end += 8 * len
  },
  encode(state, n) {
    let len = 0
    for (let m = n; m; m = m >> 64n) len++
    uint.encode(state, len)
    const view = new DataView(
      state.buffer.buffer,
      state.start + state.buffer.byteOffset,
      8 * len
    )
    for (let m = n, i = 0; m; m = m >> 64n, i += 8) {
      view.setBigUint64(i, BigInt.asUintN(64, m), true) // little endian
    }
    state.start += 8 * len
  },
  decode(state) {
    const len = uint.decode(state)
    if (state.end - state.start < 8 * len) throw new Error('Out of bounds')
    const view = new DataView(
      state.buffer.buffer,
      state.start + state.buffer.byteOffset,
      8 * len
    )
    let n = 0n
    for (let i = len - 1; i >= 0; i--)
      n = (n << 64n) + view.getBigUint64(i * 8, true) // little endian
    state.start += 8 * len
    return n
  }
})

exports.bigint = zigZagBigInt(biguint)

exports.lexint = require('./lexint')

exports.float32 = {
  preencode(state, n) {
    state.end += 4
  },
  encode(state, n) {
    const view = new DataView(
      state.buffer.buffer,
      state.start + state.buffer.byteOffset,
      4
    )
    view.setFloat32(0, n, true) // little endian
    state.start += 4
  },
  decode(state) {
    if (state.end - state.start < 4) throw new Error('Out of bounds')
    const view = new DataView(
      state.buffer.buffer,
      state.start + state.buffer.byteOffset,
      4
    )
    const float = view.getFloat32(0, true) // little endian
    state.start += 4
    return float
  }
}

exports.float64 = {
  preencode(state, n) {
    state.end += 8
  },
  encode(state, n) {
    const view = new DataView(
      state.buffer.buffer,
      state.start + state.buffer.byteOffset,
      8
    )
    view.setFloat64(0, n, true) // little endian
    state.start += 8
  },
  decode(state) {
    if (state.end - state.start < 8) throw new Error('Out of bounds')
    const view = new DataView(
      state.buffer.buffer,
      state.start + state.buffer.byteOffset,
      8
    )
    const float = view.getFloat64(0, true) // little endian
    state.start += 8
    return float
  }
}

const buffer = (exports.buffer = {
  preencode(state, b) {
    if (b) uint8array.preencode(state, b)
    else state.end++
  },
  encode(state, b) {
    if (b) uint8array.encode(state, b)
    else state.buffer[state.start++] = 0
  },
  decode(state) {
    const len = uint.decode(state)
    if (len === 0) return null
    if (state.end - state.start < len) throw new Error('Out of bounds')
    return state.buffer.subarray(state.start, (state.start += len))
  }
})

exports.binary = {
  ...buffer,
  preencode(state, b) {
    if (typeof b === 'string') utf8.preencode(state, b)
    else buffer.preencode(state, b)
  },
  encode(state, b) {
    if (typeof b === 'string') utf8.encode(state, b)
    else buffer.encode(state, b)
  }
}

exports.arraybuffer = {
  preencode(state, b) {
    uint.preencode(state, b.byteLength)
    state.end += b.byteLength
  },
  encode(state, b) {
    uint.encode(state, b.byteLength)

    const view = new Uint8Array(b)

    state.buffer.set(view, state.start)
    state.start += b.byteLength
  },
  decode(state) {
    const len = uint.decode(state)

    const b = new ArrayBuffer(len)
    const view = new Uint8Array(b)

    view.set(state.buffer.subarray(state.start, (state.start += len)))

    return b
  }
}

function typedarray(TypedArray, swap) {
  const n = TypedArray.BYTES_PER_ELEMENT

  return {
    preencode(state, b) {
      uint.preencode(state, b.length)
      state.end += b.byteLength
    },
    encode(state, b) {
      uint.encode(state, b.length)

      const view = new Uint8Array(b.buffer, b.byteOffset, b.byteLength)

      if (BE && swap) swap(view)

      state.buffer.set(view, state.start)
      state.start += b.byteLength
    },
    decode(state) {
      const len = uint.decode(state)

      let b = state.buffer.subarray(state.start, (state.start += len * n))
      if (b.byteLength !== len * n) throw new Error('Out of bounds')
      if (b.byteOffset % n !== 0) b = new Uint8Array(b)

      if (BE && swap) swap(b)

      return new TypedArray(b.buffer, b.byteOffset, b.byteLength / n)
    }
  }
}

const uint8array = (exports.uint8array = typedarray(Uint8Array))
exports.uint16array = typedarray(Uint16Array, b4a.swap16)
exports.uint32array = typedarray(Uint32Array, b4a.swap32)

exports.int8array = typedarray(Int8Array)
exports.int16array = typedarray(Int16Array, b4a.swap16)
exports.int32array = typedarray(Int32Array, b4a.swap32)

exports.biguint64array = typedarray(BigUint64Array, b4a.swap64)
exports.bigint64array = typedarray(BigInt64Array, b4a.swap64)

exports.float32array = typedarray(Float32Array, b4a.swap32)
exports.float64array = typedarray(Float64Array, b4a.swap64)

function string(encoding) {
  return {
    preencode(state, s) {
      const len = b4a.byteLength(s, encoding)
      uint.preencode(state, len)
      state.end += len
    },
    encode(state, s) {
      const len = b4a.byteLength(s, encoding)
      uint.encode(state, len)
      b4a.write(state.buffer, s, state.start, encoding)
      state.start += len
    },
    decode(state) {
      const len = uint.decode(state)
      if (state.end - state.start < len) throw new Error('Out of bounds')
      return b4a.toString(
        state.buffer,
        encoding,
        state.start,
        (state.start += len)
      )
    },
    fixed(n) {
      return {
        preencode(state) {
          state.end += n
        },
        encode(state, s) {
          b4a.write(state.buffer, s, state.start, n, encoding)
          state.start += n
        },
        decode(state) {
          if (state.end - state.start < n) throw new Error('Out of bounds')
          return b4a.toString(
            state.buffer,
            encoding,
            state.start,
            (state.start += n)
          )
        }
      }
    }
  }
}

const utf8 = (exports.string = exports.utf8 = string('utf-8'))
exports.ascii = string('ascii')
exports.hex = string('hex')
exports.base64 = string('base64')
exports.ucs2 = exports.utf16le = string('utf16le')

exports.bool = {
  preencode(state, b) {
    state.end++
  },
  encode(state, b) {
    state.buffer[state.start++] = b ? 1 : 0
  },
  decode(state) {
    if (state.start >= state.end) throw Error('Out of bounds')
    return state.buffer[state.start++] === 1
  }
}

const fixed = (exports.fixed = function fixed(n) {
  return {
    preencode(state, s) {
      if (s.byteLength !== n) throw new Error('Incorrect buffer size')
      state.end += n
    },
    encode(state, s) {
      state.buffer.set(s, state.start)
      state.start += n
    },
    decode(state) {
      if (state.end - state.start < n) throw new Error('Out of bounds')
      return state.buffer.subarray(state.start, (state.start += n))
    }
  }
})

exports.fixed32 = fixed(32)
exports.fixed64 = fixed(64)

exports.array = function array(enc) {
  return {
    preencode(state, list) {
      uint.preencode(state, list.length)
      for (let i = 0; i < list.length; i++) enc.preencode(state, list[i])
    },
    encode(state, list) {
      uint.encode(state, list.length)
      for (let i = 0; i < list.length; i++) enc.encode(state, list[i])
    },
    decode(state) {
      const len = uint.decode(state)
      if (len > 0x100000) throw new Error('Array is too big')
      const arr = new Array(len)
      for (let i = 0; i < len; i++) arr[i] = enc.decode(state)
      return arr
    }
  }
}

exports.frame = function frame(enc) {
  const dummy = exports.state()

  return {
    preencode(state, m) {
      const end = state.end
      enc.preencode(state, m)
      uint.preencode(state, state.end - end)
    },
    encode(state, m) {
      dummy.end = 0
      enc.preencode(dummy, m)
      uint.encode(state, dummy.end)
      enc.encode(state, m)
    },
    decode(state) {
      const end = state.end
      const len = uint.decode(state)
      state.end = state.start + len
      const m = enc.decode(state)
      state.start = state.end
      state.end = end
      return m
    }
  }
}

exports.date = {
  preencode(state, d) {
    int.preencode(state, d.getTime())
  },
  encode(state, d) {
    int.encode(state, d.getTime())
  },
  decode(state, d) {
    return new Date(int.decode(state))
  }
}

exports.json = {
  preencode(state, v) {
    utf8.preencode(state, JSON.stringify(v))
  },
  encode(state, v) {
    utf8.encode(state, JSON.stringify(v))
  },
  decode(state) {
    return JSON.parse(utf8.decode(state))
  }
}

exports.ndjson = {
  preencode(state, v) {
    utf8.preencode(state, JSON.stringify(v) + '\n')
  },
  encode(state, v) {
    utf8.encode(state, JSON.stringify(v) + '\n')
  },
  decode(state) {
    return JSON.parse(utf8.decode(state))
  }
}

// simple helper for when you want to just express nothing
exports.none = {
  preencode(state, n) {
    // do nothing
  },
  encode(state, n) {
    // do nothing
  },
  decode(state) {
    return null
  }
}

// "any" encoders here for helping just structure any object without schematising it

const anyArray = {
  preencode(state, arr) {
    uint.preencode(state, arr.length)
    for (let i = 0; i < arr.length; i++) {
      any.preencode(state, arr[i])
    }
  },
  encode(state, arr) {
    uint.encode(state, arr.length)
    for (let i = 0; i < arr.length; i++) {
      any.encode(state, arr[i])
    }
  },
  decode(state) {
    const arr = []
    let len = uint.decode(state)
    while (len-- > 0) {
      arr.push(any.decode(state))
    }
    return arr
  }
}

const anyObject = {
  preencode(state, o) {
    const keys = Object.keys(o)
    uint.preencode(state, keys.length)
    for (const key of keys) {
      utf8.preencode(state, key)
      any.preencode(state, o[key])
    }
  },
  encode(state, o) {
    const keys = Object.keys(o)
    uint.encode(state, keys.length)
    for (const key of keys) {
      utf8.encode(state, key)
      any.encode(state, o[key])
    }
  },
  decode(state) {
    let len = uint.decode(state)
    const o = {}
    while (len-- > 0) {
      const key = utf8.decode(state)
      o[key] = any.decode(state)
    }
    return o
  }
}

const anyTypes = [
  exports.none,
  exports.bool,
  exports.string,
  exports.buffer,
  exports.uint,
  exports.int,
  exports.float64,
  anyArray,
  anyObject,
  exports.date
]

const any = (exports.any = {
  preencode(state, o) {
    const t = getType(o)
    uint.preencode(state, t)
    anyTypes[t].preencode(state, o)
  },
  encode(state, o) {
    const t = getType(o)
    uint.encode(state, t)
    anyTypes[t].encode(state, o)
  },
  decode(state) {
    const t = uint.decode(state)
    if (t >= anyTypes.length) throw new Error('Unknown type: ' + t)
    return anyTypes[t].decode(state)
  }
})

const port = (exports.port = uint16)

const address = (host, family) => {
  return {
    preencode(state, m) {
      host.preencode(state, m.host)
      port.preencode(state, m.port)
    },
    encode(state, m) {
      host.encode(state, m.host)
      port.encode(state, m.port)
    },
    decode(state) {
      return {
        host: host.decode(state),
        family,
        port: port.decode(state)
      }
    }
  }
}

const ipv4 = (exports.ipv4 = {
  preencode(state) {
    state.end += 4
  },
  encode(state, string) {
    const start = state.start
    const end = start + 4

    let i = 0

    while (i < string.length) {
      let n = 0
      let c

      while (
        i < string.length &&
        (c = string.charCodeAt(i++)) !== /* . */ 0x2e
      ) {
        n = n * 10 + (c - /* 0 */ 0x30)
      }

      state.buffer[state.start++] = n
    }

    state.start = end
  },
  decode(state) {
    if (state.end - state.start < 4) throw new Error('Out of bounds')
    return (
      state.buffer[state.start++] +
      '.' +
      state.buffer[state.start++] +
      '.' +
      state.buffer[state.start++] +
      '.' +
      state.buffer[state.start++]
    )
  }
})

exports.ipv4Address = address(ipv4, 4)

const ipv6 = (exports.ipv6 = {
  preencode(state) {
    state.end += 16
  },
  encode(state, string) {
    const start = state.start
    const end = start + 16

    let i = 0
    let split = null

    while (i < string.length) {
      let n = 0
      let c

      while (
        i < string.length &&
        (c = string.charCodeAt(i++)) !== /* : */ 0x3a
      ) {
        if (c >= 0x30 && c <= 0x39) n = n * 0x10 + (c - /* 0 */ 0x30)
        else if (c >= 0x41 && c <= 0x46) n = n * 0x10 + (c - /* A */ 0x41 + 10)
        else if (c >= 0x61 && c <= 0x66) n = n * 0x10 + (c - /* a */ 0x61 + 10)
      }

      state.buffer[state.start++] = n >>> 8
      state.buffer[state.start++] = n

      if (i < string.length && string.charCodeAt(i) === /* : */ 0x3a) {
        i++
        split = state.start
      }
    }

    if (split !== null) {
      const offset = end - state.start
      state.buffer
        .copyWithin(split + offset, split)
        .fill(0, split, split + offset)
    }

    state.start = end
  },
  decode(state) {
    if (state.end - state.start < 16) throw new Error('Out of bounds')
    return (
      (
        state.buffer[state.start++] * 256 +
        state.buffer[state.start++]
      ).toString(16) +
      ':' +
      (
        state.buffer[state.start++] * 256 +
        state.buffer[state.start++]
      ).toString(16) +
      ':' +
      (
        state.buffer[state.start++] * 256 +
        state.buffer[state.start++]
      ).toString(16) +
      ':' +
      (
        state.buffer[state.start++] * 256 +
        state.buffer[state.start++]
      ).toString(16) +
      ':' +
      (
        state.buffer[state.start++] * 256 +
        state.buffer[state.start++]
      ).toString(16) +
      ':' +
      (
        state.buffer[state.start++] * 256 +
        state.buffer[state.start++]
      ).toString(16) +
      ':' +
      (
        state.buffer[state.start++] * 256 +
        state.buffer[state.start++]
      ).toString(16) +
      ':' +
      (
        state.buffer[state.start++] * 256 +
        state.buffer[state.start++]
      ).toString(16)
    )
  }
})

exports.ipv6Address = address(ipv6, 6)

const ip = (exports.ip = {
  preencode(state, string) {
    const family = string.includes(':') ? 6 : 4
    uint8.preencode(state, family)
    if (family === 4) ipv4.preencode(state)
    else ipv6.preencode(state)
  },
  encode(state, string) {
    const family = string.includes(':') ? 6 : 4
    uint8.encode(state, family)
    if (family === 4) ipv4.encode(state, string)
    else ipv6.encode(state, string)
  },
  decode(state) {
    const family = uint8.decode(state)
    if (family === 4) return ipv4.decode(state)
    else return ipv6.decode(state)
  }
})

exports.ipAddress = {
  preencode(state, m) {
    ip.preencode(state, m.host)
    port.preencode(state, m.port)
  },
  encode(state, m) {
    ip.encode(state, m.host)
    port.encode(state, m.port)
  },
  decode(state) {
    const family = uint8.decode(state)
    return {
      host: family === 4 ? ipv4.decode(state) : ipv6.decode(state),
      family,
      port: port.decode(state)
    }
  }
}

const record = (exports.record = function (keyEncoding, valueEncoding) {
  return {
    preencode(state, v) {
      const keys = Object.keys(v)
      uint.preencode(state, keys.length)
      for (const k of keys) {
        keyEncoding.preencode(state, k)
        valueEncoding.preencode(state, v[k])
      }
    },
    encode(state, v) {
      const keys = Object.keys(v)
      uint.encode(state, keys.length)
      for (const k of keys) {
        keyEncoding.encode(state, k)
        valueEncoding.encode(state, v[k])
      }
    },
    decode(state) {
      const out = Object.create(null)
      const keys = uint.decode(state)
      for (let i = 0; i < keys; i++) {
        out[keyEncoding.decode(state)] = valueEncoding.decode(state)
      }
      return out
    }
  }
})

exports.stringRecord = record(utf8, utf8)

function getType(o) {
  if (o === null || o === undefined) return 0
  if (typeof o === 'boolean') return 1
  if (typeof o === 'string') return 2
  if (b4a.isBuffer(o)) return 3
  if (typeof o === 'number') {
    if (Number.isInteger(o)) return o >= 0 ? 4 : 5
    return 6
  }
  if (Array.isArray(o)) return 7
  if (o instanceof Date) return 9
  if (typeof o === 'object') return 8

  throw new Error('Unsupported type for ' + o)
}

exports.from = function from(enc) {
  if (typeof enc === 'string') return fromNamed(enc)
  if (enc.preencode) return enc
  if (enc.encodingLength) return fromAbstractEncoder(enc)
  return fromCodec(enc)
}

function fromNamed(enc) {
  switch (enc) {
    case 'ascii':
      return raw.ascii
    case 'utf-8':
    case 'utf8':
      return raw.utf8
    case 'hex':
      return raw.hex
    case 'base64':
      return raw.base64
    case 'utf16-le':
    case 'utf16le':
    case 'ucs-2':
    case 'ucs2':
      return raw.ucs2
    case 'ndjson':
      return raw.ndjson
    case 'json':
      return raw.json
    case 'binary':
    default:
      return raw.binary
  }
}

function fromCodec(enc) {
  let tmpM = null
  let tmpBuf = null

  return {
    preencode(state, m) {
      tmpM = m
      tmpBuf = enc.encode(m)
      state.end += tmpBuf.byteLength
    },
    encode(state, m) {
      raw.encode(state, m === tmpM ? tmpBuf : enc.encode(m))
      tmpM = tmpBuf = null
    },
    decode(state) {
      return enc.decode(raw.decode(state))
    }
  }
}

function fromAbstractEncoder(enc) {
  return {
    preencode(state, m) {
      state.end += enc.encodingLength(m)
    },
    encode(state, m) {
      enc.encode(m, state.buffer, state.start)
      state.start += enc.encode.bytes
    },
    decode(state) {
      const m = enc.decode(state.buffer, state.start, state.end)
      state.start += enc.decode.bytes
      return m
    }
  }
}

exports.encode = function encode(enc, m) {
  const state = exports.state()
  enc.preencode(state, m)
  state.buffer = b4a.allocUnsafe(state.end)
  enc.encode(state, m)
  return state.buffer
}

exports.decode = function decode(enc, buffer) {
  return enc.decode(exports.state(0, buffer.byteLength, buffer))
}

function zigZagInt(enc) {
  return {
    preencode(state, n) {
      enc.preencode(state, zigZagEncodeInt(n))
    },
    encode(state, n) {
      enc.encode(state, zigZagEncodeInt(n))
    },
    decode(state) {
      return zigZagDecodeInt(enc.decode(state))
    }
  }
}

function zigZagDecodeInt(n) {
  return n === 0 ? n : (n & 1) === 0 ? n / 2 : -(n + 1) / 2
}

function zigZagEncodeInt(n) {
  // 0, -1, 1, -2, 2, ...
  return n < 0 ? 2 * -n - 1 : n === 0 ? 0 : 2 * n
}

function zigZagBigInt(enc) {
  return {
    preencode(state, n) {
      enc.preencode(state, zigZagEncodeBigInt(n))
    },
    encode(state, n) {
      enc.encode(state, zigZagEncodeBigInt(n))
    },
    decode(state) {
      return zigZagDecodeBigInt(enc.decode(state))
    }
  }
}

function zigZagDecodeBigInt(n) {
  return n === 0n ? n : (n & 1n) === 0n ? n / 2n : -(n + 1n) / 2n
}

function zigZagEncodeBigInt(n) {
  // 0, -1, 1, -2, 2, ...
  return n < 0n ? 2n * -n - 1n : n === 0n ? 0n : 2n * n
}

function validateUint(n) {
  if (n >= 0 === false /* Handles NaN as well */)
    throw new Error('uint must be positive')
}
module.exports = {
  preencode,
  encode,
  decode
}

function preencode(state, num) {
  if (num < 251) {
    state.end++
  } else if (num < 256) {
    state.end += 2
  } else if (num < 0x10000) {
    state.end += 3
  } else if (num < 0x1000000) {
    state.end += 4
  } else if (num < 0x100000000) {
    state.end += 5
  } else {
    state.end++
    const exp = Math.floor(Math.log(num) / Math.log(2)) - 32
    preencode(state, exp)
    state.end += 6
  }
}

function encode(state, num) {
  const max = 251
  const x = num - max

  if (num < max) {
    state.buffer[state.start++] = num
  } else if (num < 256) {
    state.buffer[state.start++] = max
    state.buffer[state.start++] = x
  } else if (num < 0x10000) {
    state.buffer[state.start++] = max + 1
    state.buffer[state.start++] = (x >> 8) & 0xff
    state.buffer[state.start++] = x & 0xff
  } else if (num < 0x1000000) {
    state.buffer[state.start++] = max + 2
    state.buffer[state.start++] = x >> 16
    state.buffer[state.start++] = (x >> 8) & 0xff
    state.buffer[state.start++] = x & 0xff
  } else if (num < 0x100000000) {
    state.buffer[state.start++] = max + 3
    state.buffer[state.start++] = x >> 24
    state.buffer[state.start++] = (x >> 16) & 0xff
    state.buffer[state.start++] = (x >> 8) & 0xff
    state.buffer[state.start++] = x & 0xff
  } else {
    // need to use Math here as bitwise ops are 32 bit
    const exp = Math.floor(Math.log(x) / Math.log(2)) - 32
    state.buffer[state.start++] = 0xff

    encode(state, exp)
    const rem = x / Math.pow(2, exp - 11)

    for (let i = 5; i >= 0; i--) {
      state.buffer[state.start++] = (rem / Math.pow(2, 8 * i)) & 0xff
    }
  }
}

function decode(state) {
  const max = 251

  if (state.end - state.start < 1) throw new Error('Out of bounds')

  const flag = state.buffer[state.start++]

  if (flag < max) return flag

  if (state.end - state.start < flag - max + 1) {
    throw new Error('Out of bounds.')
  }

  if (flag < 252) {
    return state.buffer[state.start++] + max
  }

  if (flag < 253) {
    return (
      (state.buffer[state.start++] << 8) + state.buffer[state.start++] + max
    )
  }

  if (flag < 254) {
    return (
      (state.buffer[state.start++] << 16) +
      (state.buffer[state.start++] << 8) +
      state.buffer[state.start++] +
      max
    )
  }

  // << 24 result may be interpreted as negative
  if (flag < 255) {
    return (
      state.buffer[state.start++] * 0x1000000 +
      (state.buffer[state.start++] << 16) +
      (state.buffer[state.start++] << 8) +
      state.buffer[state.start++] +
      max
    )
  }

  const exp = decode(state)

  if (state.end - state.start < 6) throw new Error('Out of bounds')

  let rem = 0
  for (let i = 5; i >= 0; i--) {
    rem += state.buffer[state.start++] * Math.pow(2, 8 * i)
  }

  return rem * Math.pow(2, exp - 11) + max
}
{
  "name": "compact-encoding",
  "version": "2.19.2",
  "description": "A series of compact encoding schemes for building small and fast parsers and serializers",
  "main": "index.js",
  "dependencies": {
    "b4a": "^1.3.0"
  },
  "devDependencies": {
    "brittle": "^3.0.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^1.0.0"
  },
  "scripts": {
    "format": "prettier . --write",
    "test": "prettier . --check && brittle test.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/holepunchto/compact-encoding.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/compact-encoding/issues"
  },
  "homepage": "https://github.com/holepunchto/compact-encoding"
}
const b4a = require('b4a')

const { BE } = require('./endian')

exports = module.exports = {
  preencode(state, b) {
    state.end += b.byteLength
  },
  encode(state, b) {
    state.buffer.set(b, state.start)
    state.start += b.byteLength
  },
  decode(state) {
    const b = state.buffer.subarray(state.start, state.end)
    state.start = state.end
    return b
  }
}

const buffer = (exports.buffer = {
  preencode(state, b) {
    if (b) uint8array.preencode(state, b)
    else state.end++
  },
  encode(state, b) {
    if (b) uint8array.encode(state, b)
    else state.buffer[state.start++] = 0
  },
  decode(state) {
    const b = state.buffer.subarray(state.start)
    if (b.byteLength === 0) return null
    state.start = state.end
    return b
  }
})

exports.binary = {
  ...buffer,
  preencode(state, b) {
    if (typeof b === 'string') utf8.preencode(state, b)
    else buffer.preencode(state, b)
  },
  encode(state, b) {
    if (typeof b === 'string') utf8.encode(state, b)
    else buffer.encode(state, b)
  }
}

exports.arraybuffer = {
  preencode(state, b) {
    state.end += b.byteLength
  },
  encode(state, b) {
    const view = new Uint8Array(b)

    state.buffer.set(view, state.start)
    state.start += b.byteLength
  },
  decode(state) {
    const b = new ArrayBuffer(state.end - state.start)
    const view = new Uint8Array(b)

    view.set(state.buffer.subarray(state.start))

    state.start = state.end

    return b
  }
}

function typedarray(TypedArray, swap) {
  const n = TypedArray.BYTES_PER_ELEMENT

  return {
    preencode(state, b) {
      state.end += b.byteLength
    },
    encode(state, b) {
      const view = new Uint8Array(b.buffer, b.byteOffset, b.byteLength)

      if (BE && swap) swap(view)

      state.buffer.set(view, state.start)
      state.start += b.byteLength
    },
    decode(state) {
      let b = state.buffer.subarray(state.start)
      if (b.byteOffset % n !== 0) b = new Uint8Array(b)

      if (BE && swap) swap(b)

      state.start = state.end

      return new TypedArray(b.buffer, b.byteOffset, b.byteLength / n)
    }
  }
}

const uint8array = (exports.uint8array = typedarray(Uint8Array))
exports.uint16array = typedarray(Uint16Array, b4a.swap16)
exports.uint32array = typedarray(Uint32Array, b4a.swap32)

exports.int8array = typedarray(Int8Array)
exports.int16array = typedarray(Int16Array, b4a.swap16)
exports.int32array = typedarray(Int32Array, b4a.swap32)

exports.biguint64array = typedarray(BigUint64Array, b4a.swap64)
exports.bigint64array = typedarray(BigInt64Array, b4a.swap64)

exports.float32array = typedarray(Float32Array, b4a.swap32)
exports.float64array = typedarray(Float64Array, b4a.swap64)

function string(encoding) {
  return {
    preencode(state, s) {
      state.end += b4a.byteLength(s, encoding)
    },
    encode(state, s) {
      state.start += b4a.write(state.buffer, s, state.start, encoding)
    },
    decode(state) {
      const s = b4a.toString(state.buffer, encoding, state.start)
      state.start = state.end
      return s
    }
  }
}

const utf8 = (exports.string = exports.utf8 = string('utf-8'))
exports.ascii = string('ascii')
exports.hex = string('hex')
exports.base64 = string('base64')
exports.ucs2 = exports.utf16le = string('utf16le')

exports.array = function array(enc) {
  return {
    preencode(state, list) {
      for (const value of list) enc.preencode(state, value)
    },
    encode(state, list) {
      for (const value of list) enc.encode(state, value)
    },
    decode(state) {
      const arr = []
      while (state.start < state.end) arr.push(enc.decode(state))
      return arr
    }
  }
}

exports.json = {
  preencode(state, v) {
    utf8.preencode(state, JSON.stringify(v))
  },
  encode(state, v) {
    utf8.encode(state, JSON.stringify(v))
  },
  decode(state) {
    return JSON.parse(utf8.decode(state))
  }
}

exports.ndjson = {
  preencode(state, v) {
    utf8.preencode(state, JSON.stringify(v) + '\n')
  },
  encode(state, v) {
    utf8.encode(state, JSON.stringify(v) + '\n')
  },
  decode(state) {
    return JSON.parse(utf8.decode(state))
  }
}
{
  "name": "autobee-encryption",
  "version": "0.1.3",
  "description": "autobee-encryption",
  "main": "index.js",
  "exports": {
    "./package": "./package.json",
    ".": {
      "default": "./index.js"
    }
  },
  "files": [
    "package.json",
    "index.js"
  ],
  "devDependencies": {
    "brittle": "^3.19.0",
    "lunte": "^1.2.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^2.0.0"
  },
  "scripts": {
    "format": "prettier . --write",
    "test": "prettier . --check && lunte && brittle-bare test/index.js",
    "lint": "lunte"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/holepunchto/autobee-encryption.git"
  },
  "author": "Holepunch Inc",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/autobee-encryption/issues"
  },
  "homepage": "https://github.com/holepunchto/autobee-encryption",
  "dependencies": {
    "b4a": "^1.8.0",
    "compact-encoding": "^2.19.0",
    "hypercore": "^11.27.14",
    "hypercore-crypto": "^3.6.1",
    "sodium-universal": "^5.0.1"
  }
}
const ProtomuxWakeup = require('protomux-wakeup')
const CoreCoupler = require('core-coupler')
const ReadyResource = require('ready-resource')
const b4a = require('b4a')

class WakeupHandler {
  constructor(wakeup, discoveryKey) {
    this.wakeup = wakeup

    this.active = true
    this.discoveryKey = discoveryKey
  }

  onpeeractive(peer, session) {
    this.wakeup._bumpWakeupPeer(peer)
  }

  onlookup(req, peer, session) {
    const wakeup = this.wakeup._getWakeupWriters()
    if (wakeup.length === 0) return
    session.announce(peer, wakeup)
  }

  onannounce(wakeup, peer, session) {
    this.wakeup.hint(wakeup)
  }
}

module.exports = class AutobeeWakeup extends ReadyResource {
  constructor(auto, opts = {}) {
    super()

    this._auto = auto
    this._owner = !opts.wakeup

    this._protocol = opts.wakeup || new ProtomuxWakeup()
    this._session = null
    this._coupler = null
    this._hints = new Map()

    this._needsWakeupRequest = false
    this._needsWakeup = true
    this._needsWakeupHeads = true
    this._wakeupPeerBound = this._wakeupPeer.bind(this)
  }

  _close() {
    if (this._session) this._session.destroy()
    if (this._owner) this._protocol.destroy()
  }

  get hints() {
    return this._hints
  }

  hint(hints) {
    if (!Array.isArray(hints)) hints = [hints]
    for (const { key, length } of hints) {
      const hex = b4a.toString(key, 'hex')
      const prev = this._hints.get(hex)
      if (!prev || length === -1 || prev < length) this._hints.set(hex, length)
    }
    this._auto.bumpSoon()
  }

  flush() {
    const hints = new Map(this._hints)
    this._hints.clear()
    return hints
  }

  addStream(stream) {
    this._protocol.addStream(stream)
  }

  setCapability(cap, discoveryKey) {
    if (this._session) this._session.destroy()
    this._session = this._protocol.session(cap, new WakeupHandler(this, discoveryKey || null))

    // incase this session has active peers already, bump the coupler
    for (const peer of this._session.peers) {
      if (peer.active) this._bumpWakeupPeer(peer)
    }
  }

  _bumpWakeupPeer(peer) {
    if (this._coupler) this._coupler.update(peer.stream)
  }

  _wakeupPeer(stream, cores) {
    if (!this._session) return
    // Don't drop a freshly-coupled local writer that isn't pending at this
    // instant; the peer never learns it otherwise.
    const wakeup = cores && cores.length ? cores : this._getWakeupWriters()
    if (wakeup.length === 0) return
    this._session.announceByStream(stream, wakeup)
  }

  _getWakeupWriters() {
    const writers = []
    for (const [_, w] of this._auto.writers.active) {
      if (w.isPending) continue
      writers.push(w.core)
    }

    return writers
  }

  recouple() {
    if (this._coupler) this._coupler.destroy()
    const core = this._auto.bootstrap
    this._coupler = new CoreCoupler(core, this._wakeupPeerBound)
  }

  addCore(core) {
    if (!this._coupler) return false
    this._coupler.add(core)
    return true
  }

  removeCore(core) {
    if (!this._coupler) return false
    this._coupler.remove(core)
    return true
  }
}
{
  "name": "autobee-wakeup",
  "version": "1.2.1",
  "description": "autobee-wakeup",
  "main": "index.js",
  "exports": {
    "./package": "./package.json",
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    }
  },
  "files": [
    "package.json",
    "index.js",
    "index.d.ts"
  ],
  "devDependencies": {
    "brittle": "^3.19.0",
    "corestore": "^7.9.1",
    "lunte": "^1.2.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^2.0.0"
  },
  "scripts": {
    "format": "prettier . --write",
    "test": "brittle-bare test/index.js",
    "lint": "prettier . --check && lunte"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/autobee-wakeup.git"
  },
  "author": "Holepunch Inc",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/autobee-wakeup/issues"
  },
  "homepage": "https://github.com/holepunchto/autobee-wakeup",
  "dependencies": {
    "b4a": "^1.8.0",
    "core-coupler": "^2.0.0",
    "protomux-wakeup": "^2.9.0",
    "ready-resource": "^1.2.0"
  }
}
const c = require('compact-encoding')

const Checkout = {
  preencode(state, m) {
    c.fixed32.preencode(state, m.key)
    c.uint.preencode(state, m.length)
  },
  encode(state, m) {
    c.fixed32.encode(state, m.key)
    c.uint.encode(state, m.length)
  },
  decode(state) {
    return {
      key: c.fixed32.decode(state),
      length: c.uint.decode(state)
    }
  }
}

const Clock = c.array(Checkout)

const IndexCheckpoint = {
  preencode(state, m) {
    c.fixed64.preencode(state, m.signature)
    c.uint.preencode(state, m.length)
  },
  encode(state, m) {
    c.fixed64.encode(state, m.signature)
    c.uint.encode(state, m.length)
  },
  decode(state) {
    return {
      signature: c.fixed64.decode(state),
      length: c.uint.decode(state)
    }
  }
}

const BootRecordV0 = {
  preencode() {
    throw new Error('version 0 records cannot be encoded')
  },
  encode() {
    throw new Error('version 0 records cannot be encoded')
  },
  decode(state) {
    const indexed = Checkout.decode(state)
    const heads = Clock.decode(state)

    // one cause initial recover is not ff recovery
    return {
      version: 0,
      key: indexed.key,
      systemLength: indexed.length,
      indexersUpdated: false,
      fastForwarding: false,
      recoveries: 1,
      migrating: false,
      heads
    }
  }
}

const Checkpointer = {
  preencode(state, idx) {
    c.uint.preencode(state, idx.checkpointer)
    if (idx.checkpoint !== null) IndexCheckpoint.preencode(state, idx.checkpoint)
  },
  encode(state, idx) {
    c.uint.encode(state, idx.checkpointer)
    if (idx.checkpoint !== null) IndexCheckpoint.encode(state, idx.checkpoint)
  },
  decode(state) {
    const checkpointer = c.uint.decode(state)
    const checkpoint = checkpointer ? null : IndexCheckpoint.decode(state)

    return {
      checkpointer,
      checkpoint
    }
  }
}

const CheckpointerArray = c.array(Checkpointer)

const Indexer = {
  preencode(state, m) {
    c.uint.preencode(state, m.signature)
    c.fixed32.preencode(state, m.namespace)
    c.fixed32.preencode(state, m.publicKey)
  },
  encode(state, m) {
    c.uint.encode(state, m.signature)
    c.fixed32.encode(state, m.namespace)
    c.fixed32.encode(state, m.publicKey)
  },
  decode(state) {
    return {
      signature: c.uint.decode(state),
      namespace: c.fixed32.decode(state),
      publicKey: c.fixed32.decode(state)
    }
  }
}

const Indexers = c.array(Indexer)

const DigestV0 = {
  preencode(state, m) {
    c.uint.preencode(state, m.pointer)
    if (m.pointer === 0) {
      Indexers.preencode(state, m.indexers)
    }
  },
  encode(state, m) {
    c.uint.encode(state, m.pointer)
    if (m.pointer === 0) {
      Indexers.encode(state, m.indexers)
    }
  },
  decode(state) {
    const pointer = c.uint.decode(state)
    return {
      pointer,
      indexers: pointer === 0 ? Indexers.decode(state) : null
    }
  }
}

const Digest = {
  preencode(state, m) {
    c.uint.preencode(state, m.pointer)
    if (m.pointer === 0) {
      c.fixed32.preencode(state, m.key)
    }
  },
  encode(state, m) {
    c.uint.encode(state, m.pointer)
    if (m.pointer === 0) {
      c.fixed32.encode(state, m.key)
    }
  },
  decode(state) {
    const pointer = c.uint.decode(state)
    return {
      pointer,
      key: pointer === 0 ? c.fixed32.decode(state) : null
    }
  }
}

const Node = {
  preencode(state, m) {
    Clock.preencode(state, m.heads)
    c.uint.preencode(state, m.batch)
    c.optionalBuffer.preencode(state, m.value)
  },
  encode(state, m) {
    Clock.encode(state, m.heads)
    c.uint.encode(state, m.batch)
    c.optionalBuffer.encode(state, m.value)
  },
  decode(state, m) {
    return {
      heads: Clock.decode(state),
      batch: c.uint.decode(state),
      value: c.optionalBuffer.decode(state)
    }
  }
}

const Additional = {
  preencode(state, m) {
    c.uint.preencode(state, m.pointer)
    if (m.pointer === 0) {
      AdditionalData.preencode(state, m.data)
    }
  },
  encode(state, m) {
    c.uint.encode(state, m.pointer)
    if (m.pointer === 0) {
      AdditionalData.encode(state, m.data)
    }
  },
  decode(state) {
    const pointer = c.uint.decode(state)
    return {
      pointer,
      data: pointer === 0 ? AdditionalData.decode(state) : null
    }
  }
}

const AdditionalData = {
  preencode(state, m) {
    c.uint.preencode(state, 0)
  },
  encode(state, m) {
    c.uint.encode(state, 0) // empty for now, for the future
  },
  decode(state) {
    const flags = c.uint.decode(state)
    return {
      encryptionId: flags & 1 ? c.fixed32.decode(state) : null, // to help validate the encryption key used
      abi: flags & 2 ? c.uint.decode(state) : 0
    }
  }
}

const OplogMessageV1 = {
  preencode(state, m) {
    throw new Error('Encoding not supported')
  },
  encode(state, m) {
    throw new Error('Encoding not supported')
  },
  decode(state) {
    c.uint.decode(state) // version
    const maxSupportedVersion = c.uint.decode(state)

    const flags = c.uint.decode(state)

    const isCheckpointer = (flags & 1) !== 0

    const chk = isCheckpointer ? CheckpointerArray.decode(state) : null
    const digest = isCheckpointer ? Digest.decode(state) : null

    const node = Node.decode(state)

    return {
      version: 1,
      maxSupportedVersion,
      digest,
      checkpoint: chk ? { system: chk[0], encryption: null, user: chk.slice(1) } : null,
      optimistic: (flags & 2) !== 0,
      node
    }
  }
}

const OplogMessageV0 = {
  preencode(state, m) {
    throw new Error('Encoding not supported')
  },
  encode(state, m) {
    throw new Error('Encoding not supported')
  },
  decode(state) {
    c.uint.decode(state) // version
    const flags = c.uint.decode(state)

    const isCheckpointer = (flags & 1) !== 0

    if (isCheckpointer) DigestV0.decode(state)
    const chk = isCheckpointer ? CheckpointerArray.decode(state) : null
    const node = Node.decode(state)
    Additional.decode(state)
    const maxSupportedVersion = state.start < state.end ? c.uint.decode(state) : 0

    return {
      version: 0,
      maxSupportedVersion,
      digest: null,
      checkpoint: chk ? { system: chk[0], encryption: null, user: chk.slice(1) } : null,
      optimistic: null,
      node
    }
  }
}

const SystemWriterV0 = {
  preencode(state, m) {
    throw new Error('Encoding not supported')
  },
  encode(state, m) {
    throw new Error('Encoding not supported')
  },
  decode(state) {
    const flags = c.uint.decode(state)

    return {
      version: 0,
      isIndexer: (flags & 1) !== 0,
      isRemoved: (flags & 2) !== 0,
      length: c.uint.decode(state)
    }
  }
}

function infoLegacyMap(info) {
  return {
    version: info.version,
    timestamp: 0,
    flushes: 0,
    view: null,
    views: info.views,
    heads: info.heads,
    indexers: info.indexers
  }
}

function memberLegacyMap(m) {
  const weight = m.isIndexer ? 2 : 1

  return {
    version: 0,
    isRemoved: m.isRemoved,
    isOplog: false,
    isGenesis: false,
    weight,
    maxWeight: weight,
    length: m.length,
    timestamp: 0
  }
}

// a v3 node's witness is a signed attestation, which confers weight through a
// mechanism that no longer exists - it maps to null, so the node sorts at the
// standing its record already carries instead of failing to decode
function oplogLegacyMap(m) {
  return {
    version: m.version,
    timestamp: m.timestamp,
    links: m.links,
    batch: m.batch,
    views: m.views,
    trusted: m.trusted,
    witness: null,
    approvals: null,
    optimistic: m.optimistic,
    value: m.value
  }
}

module.exports = {
  BootRecordV0,
  OplogMessageV0,
  OplogMessageV1,
  SystemWriterV0,
  infoLegacyMap,
  memberLegacyMap,
  oplogLegacyMap
}
// This file is autogenerated by the hyperschema compiler
// Schema Version: 1
/* eslint-disable camelcase */
/* eslint-disable quotes */
/* eslint-disable space-before-function-paren */

const { c } = require('hyperschema/runtime')
const external0 = require('../../legacy.js')

const VERSION = 1

// eslint-disable-next-line no-unused-vars
let version = VERSION

// array of bools, packed as a bitfield
const boolArray = {
  preencode(state, m) {
    c.uint.preencode(state, m.length)
    state.end += Math.ceil(m.length / 8)
  },
  encode(state, m) {
    c.uint.encode(state, m.length)
    for (let i = 0; i < m.length; i += 8) {
      let byte = 0
      for (let j = 0; j < 8 && i + j < m.length; j++) {
        if (m[i + j]) byte |= 1 << j
      }
      state.buffer[state.start++] = byte
    }
  },
  decode(state) {
    const n = c.uint.decode(state)
    if (state.end - state.start < Math.ceil(n / 8)) throw new Error('Out of bounds')
    const m = new Array(n)
    for (let i = 0; i < n; i += 8) {
      const byte = state.buffer[state.start++]
      for (let j = 0; j < 8 && i + j < n; j++) {
        m[i + j] = (byte & (1 << j)) !== 0
      }
    }
    return m
  }
}

// @autobase-compat/checkout
const encoding0 = {
  preencode(state, m) {
    c.fixed32.preencode(state, m.key)
    c.uint.preencode(state, m.length)
  },
  encode(state, m) {
    c.fixed32.encode(state, m.key)
    c.uint.encode(state, m.length)
  },
  decode(state) {
    const r0 = c.fixed32.decode(state)
    const r1 = c.uint.decode(state)

    return {
      key: r0,
      length: r1
    }
  }
}

// @autobase-compat/clock
const encoding1 = c.array(encoding0)

// @autobase-compat/index-checkpoint
const encoding2 = {
  preencode(state, m) {
    c.fixed64.preencode(state, m.signature)
    c.uint.preencode(state, m.length)
  },
  encode(state, m) {
    c.fixed64.encode(state, m.signature)
    c.uint.encode(state, m.length)
  },
  decode(state) {
    const r0 = c.fixed64.decode(state)
    const r1 = c.uint.decode(state)

    return {
      signature: r0,
      length: r1
    }
  }
}

const encoding3 = external0.BootRecordV0

// @autobase-compat/boot-record-raw
const encoding4 = {
  preencode(state, m) {
    c.fixed32.preencode(state, m.key)
    c.uint.preencode(state, m.systemLength)
    state.end++ // max flag is 4 so always one byte

    if (m.recoveries) c.uint.preencode(state, m.recoveries)
  },
  encode(state, m) {
    const flags = (m.indexersUpdated ? 1 : 0) | (m.fastForwarding ? 2 : 0) | (m.recoveries ? 4 : 0)

    c.fixed32.encode(state, m.key)
    c.uint.encode(state, m.systemLength)
    c.uint.encode(state, flags)

    if (m.recoveries) c.uint.encode(state, m.recoveries)
  },
  decode(state) {
    const v = c.uint.decode(state)
    const r0 = c.fixed32.decode(state)
    const r1 = c.uint.decode(state)
    const flags = c.uint.decode(state)

    return {
      version: v,
      key: r0,
      systemLength: r1,
      indexersUpdated: (flags & 1) !== 0,
      fastForwarding: (flags & 2) !== 0,
      recoveries: (flags & 4) !== 0 ? c.uint.decode(state) : 0
    }
  }
}

// @autobase-compat/boot-record
const encoding5 = {
  preencode(state, m) {
    const v = m.version ?? 3
    c.uint.preencode(state, v)
    switch (v) {
      case 0:
        encoding3.preencode(state, m)
        break
      case 1:
      case 2:
      case 3:
        encoding4.preencode(state, m)
        break
      default:
        throw new Error('Unsupported version')
    }
  },
  encode(state, m) {
    const v = m.version ?? 3
    c.uint.encode(state, v)
    switch (v) {
      case 0:
        encoding3.encode(state, m)
        break
      case 1:
      case 2:
      case 3:
        encoding4.encode(state, m)
        break
      default:
        throw new Error('Unsupported version')
    }
  },
  decode(state) {
    const start = state.start
    const v = c.uint.decode(state)
    state.start = start
    switch (v) {
      case 0: {
        const decoded = encoding3.decode(state)
        return decoded
      }
      case 1:
      case 2:
      case 3: {
        const decoded = encoding4.decode(state)
        return decoded
      }
      default:
        throw new Error('Unsupported version')
    }
  }
}

// @autobase-compat/checkpointer
const encoding6 = {
  preencode(state, m) {
    state.end++ // flags are fixed size

    if (m.checkpointer) c.uint.preencode(state, m.checkpointer)
    if (m.checkpoint) encoding2.preencode(state, m.checkpoint)
  },
  encode(state, m) {
    const flags = (m.checkpointer ? 1 : 0) | (m.checkpoint ? 2 : 0)

    c.uint8.encode(state, flags)

    if (m.checkpointer) c.uint.encode(state, m.checkpointer)
    if (m.checkpoint) encoding2.encode(state, m.checkpoint)
  },
  decode(state) {
    const flags = c.uint8.decode(state)

    return {
      checkpointer: (flags & 1) !== 0 ? c.uint.decode(state) : 0,
      checkpoint: (flags & 2) !== 0 ? encoding2.decode(state) : null
    }
  }
}

// @autobase-compat/checkpoint.user
const encoding7_2 = c.array(encoding6)

// @autobase-compat/checkpoint
const encoding7 = {
  preencode(state, m) {
    state.end++ // max flag is 4 so always one byte

    if (m.system) encoding6.preencode(state, m.system)
    if (m.encryption) encoding6.preencode(state, m.encryption)
    if (m.user) encoding7_2.preencode(state, m.user)
  },
  encode(state, m) {
    const flags = (m.system ? 1 : 0) | (m.encryption ? 2 : 0) | (m.user ? 4 : 0)

    c.uint.encode(state, flags)

    if (m.system) encoding6.encode(state, m.system)
    if (m.encryption) encoding6.encode(state, m.encryption)
    if (m.user) encoding7_2.encode(state, m.user)
  },
  decode(state) {
    const flags = c.uint.decode(state)

    return {
      system: (flags & 1) !== 0 ? encoding6.decode(state) : null,
      encryption: (flags & 2) !== 0 ? encoding6.decode(state) : null,
      user: (flags & 4) !== 0 ? encoding7_2.decode(state) : null
    }
  }
}

// @autobase-compat/digest
const encoding8 = {
  preencode(state, m) {
    state.end++ // max flag is 2 so always one byte

    if (m.pointer) c.uint.preencode(state, m.pointer)
    if (m.key) c.fixed32.preencode(state, m.key)
  },
  encode(state, m) {
    const flags = (m.pointer ? 1 : 0) | (m.key ? 2 : 0)

    c.uint.encode(state, flags)

    if (m.pointer) c.uint.encode(state, m.pointer)
    if (m.key) c.fixed32.encode(state, m.key)
  },
  decode(state) {
    const flags = c.uint.decode(state)

    return {
      pointer: (flags & 1) !== 0 ? c.uint.decode(state) : 0,
      key: (flags & 2) !== 0 ? c.fixed32.decode(state) : null
    }
  }
}

// @autobase-compat/node
const encoding9 = {
  preencode(state, m) {
    encoding1.preencode(state, m.heads)
    c.uint.preencode(state, m.batch)
    c.optionalBuffer.preencode(state, m.value)
  },
  encode(state, m) {
    encoding1.encode(state, m.heads)
    c.uint.encode(state, m.batch)
    c.optionalBuffer.encode(state, m.value)
  },
  decode(state) {
    const r0 = encoding1.decode(state)
    const r1 = c.uint.decode(state)
    const r2 = c.optionalBuffer.decode(state)

    return {
      heads: r0,
      batch: r1,
      value: r2
    }
  }
}

// @autobase-compat/user-view-trace.blocks
const encoding10_1 = c.array(c.uint)

// @autobase-compat/user-view-trace
const encoding10 = {
  preencode(state, m) {
    c.uint.preencode(state, m.view)
    encoding10_1.preencode(state, m.blocks)
  },
  encode(state, m) {
    c.uint.encode(state, m.view)
    encoding10_1.encode(state, m.blocks)
  },
  decode(state) {
    const r0 = c.uint.decode(state)
    const r1 = encoding10_1.decode(state)

    return {
      view: r0,
      blocks: r1
    }
  }
}

// @autobase-compat/trace.system
const encoding11_0 = encoding10_1
// @autobase-compat/trace.encryption
const encoding11_1 = encoding10_1
// @autobase-compat/trace.user
const encoding11_2 = c.array(encoding10)

// @autobase-compat/trace
const encoding11 = {
  preencode(state, m) {
    encoding11_0.preencode(state, m.system)
    encoding11_1.preencode(state, m.encryption)
    encoding11_2.preencode(state, m.user)
  },
  encode(state, m) {
    encoding11_0.encode(state, m.system)
    encoding11_1.encode(state, m.encryption)
    encoding11_2.encode(state, m.user)
  },
  decode(state) {
    const r0 = encoding11_0.decode(state)
    const r1 = encoding11_1.decode(state)
    const r2 = encoding11_2.decode(state)

    return {
      system: r0,
      encryption: r1,
      user: r2
    }
  }
}

const encoding12 = external0.OplogMessageV0

const encoding13 = external0.OplogMessageV1

// @autobase-compat/oplog-message-v2.checkpoint
const encoding14_1 = c.frame(encoding7)
// @autobase-compat/oplog-message-v2.digest
const encoding14_2 = c.frame(encoding8)
// @autobase-compat/oplog-message-v2.trace
const encoding14_4 = c.frame(encoding11)

// @autobase-compat/oplog-message-v2
const encoding14 = {
  preencode(state, m) {
    encoding9.preencode(state, m.node)
    state.end++ // max flag is 8 so always one byte

    if (m.checkpoint) encoding14_1.preencode(state, m.checkpoint)
    if (m.digest) encoding14_2.preencode(state, m.digest)
    if (m.trace) encoding14_4.preencode(state, m.trace)
  },
  encode(state, m) {
    const flags =
      (m.checkpoint ? 1 : 0) | (m.digest ? 2 : 0) | (m.optimistic ? 4 : 0) | (m.trace ? 8 : 0)

    encoding9.encode(state, m.node)
    c.uint.encode(state, flags)

    if (m.checkpoint) encoding14_1.encode(state, m.checkpoint)
    if (m.digest) encoding14_2.encode(state, m.digest)
    if (m.trace) encoding14_4.encode(state, m.trace)
  },
  decode(state) {
    const v = c.uint.decode(state)
    const r0 = encoding9.decode(state)
    const flags = c.uint.decode(state)

    return {
      version: v,
      node: r0,
      checkpoint: (flags & 1) !== 0 ? encoding14_1.decode(state) : null,
      digest: (flags & 2) !== 0 ? encoding14_2.decode(state) : null,
      optimistic: (flags & 4) !== 0,
      trace: (flags & 8) !== 0 ? encoding14_4.decode(state) : null
    }
  }
}

// @autobase-compat/info-v1.pendingIndexers
const encoding15_1 = c.array(c.fixed32)

// @autobase-compat/info-v1
const encoding15 = {
  preencode(state, m) {
    c.uint.preencode(state, m.members)
    encoding15_1.preencode(state, m.pendingIndexers)
    encoding1.preencode(state, m.indexers)
    encoding1.preencode(state, m.heads)
    encoding1.preencode(state, m.views)
  },
  encode(state, m) {
    c.uint.encode(state, m.members)
    encoding15_1.encode(state, m.pendingIndexers)
    encoding1.encode(state, m.indexers)
    encoding1.encode(state, m.heads)
    encoding1.encode(state, m.views)
  },
  decode(state) {
    const v = c.uint.decode(state)
    const r0 = c.uint.decode(state)
    const r1 = encoding15_1.decode(state)
    const r2 = encoding1.decode(state)
    const r3 = encoding1.decode(state)
    const r4 = encoding1.decode(state)

    return {
      version: v,
      members: r0,
      pendingIndexers: r1,
      indexers: r2,
      heads: r3,
      views: r4
    }
  }
}

// @autobase-compat/info-v2.pendingIndexers
const encoding16_1 = encoding15_1

// @autobase-compat/info-v2
const encoding16 = {
  preencode(state, m) {
    c.uint.preencode(state, m.members)
    encoding16_1.preencode(state, m.pendingIndexers)
    encoding1.preencode(state, m.indexers)
    encoding1.preencode(state, m.heads)
    encoding1.preencode(state, m.views)
    c.uint.preencode(state, m.encryptionLength)
    state.end++ // max flag is 1 so always one byte

    if (m.entropy) c.fixed32.preencode(state, m.entropy)
  },
  encode(state, m) {
    const flags = m.entropy ? 1 : 0

    c.uint.encode(state, m.members)
    encoding16_1.encode(state, m.pendingIndexers)
    encoding1.encode(state, m.indexers)
    encoding1.encode(state, m.heads)
    encoding1.encode(state, m.views)
    c.uint.encode(state, m.encryptionLength)
    c.uint.encode(state, flags)

    if (m.entropy) c.fixed32.encode(state, m.entropy)
  },
  decode(state) {
    const v = c.uint.decode(state)
    const r0 = c.uint.decode(state)
    const r1 = encoding16_1.decode(state)
    const r2 = encoding1.decode(state)
    const r3 = encoding1.decode(state)
    const r4 = encoding1.decode(state)
    const r5 = c.uint.decode(state)
    const flags = c.uint.decode(state)

    return {
      version: v,
      members: r0,
      pendingIndexers: r1,
      indexers: r2,
      heads: r3,
      views: r4,
      encryptionLength: r5,
      entropy: (flags & 1) !== 0 ? c.fixed32.decode(state) : null
    }
  }
}

const encoding17 = external0.SystemWriterV0

// @autobee/system-info-v3.pending
const encoding18_5 = boolArray

// @autobee/system-info-v3
const encoding18 = {
  preencode(state, m) {
    c.uint.preencode(state, m.timestamp)
    c.uint.preencode(state, m.flushes)
    encoding22.preencode(state, m.view)
    encoding18_3.preencode(state, m.heads)
    state.end++ // max flag is 2 so always one byte

    if (m.indexers) encoding18_4.preencode(state, m.indexers)
    if (m.pending) encoding18_5.preencode(state, m.pending)
  },
  encode(state, m) {
    const flags = (m.indexers ? 1 : 0) | (m.pending ? 2 : 0)

    c.uint.encode(state, m.timestamp)
    c.uint.encode(state, m.flushes)
    encoding22.encode(state, m.view)
    encoding18_3.encode(state, m.heads)
    c.uint.encode(state, flags)

    if (m.indexers) encoding18_4.encode(state, m.indexers)
    if (m.pending) encoding18_5.encode(state, m.pending)
  },
  decode(state) {
    const v = c.uint.decode(state)
    const r0 = c.uint.decode(state)
    const r1 = c.uint.decode(state)
    const r2 = encoding22.decode(state)
    const r3 = encoding18_3.decode(state)
    const flags = c.uint.decode(state)

    return {
      version: v,
      timestamp: r0,
      flushes: r1,
      view: r2,
      heads: r3,
      indexers: (flags & 1) !== 0 ? encoding18_4.decode(state) : null,
      pending: (flags & 2) !== 0 ? encoding18_5.decode(state) : null
    }
  }
}

// @autobee/system-info
const encoding19 = {
  preencode(state, m) {
    const v = m.version ?? 3
    c.uint.preencode(state, v)
    switch (v) {
      case 0:
      case 1:
        encoding15.preencode(state, m)
        break
      case 2:
        encoding16.preencode(state, m)
        break
      case 3:
        encoding18.preencode(state, m)
        break
      default:
        throw new Error('Unsupported version')
    }
  },
  encode(state, m) {
    const v = m.version ?? 3
    c.uint.encode(state, v)
    switch (v) {
      case 0:
      case 1:
        encoding15.encode(state, m)
        break
      case 2:
        encoding16.encode(state, m)
        break
      case 3:
        encoding18.encode(state, m)
        break
      default:
        throw new Error('Unsupported version')
    }
  },
  decode(state) {
    const start = state.start
    const v = c.uint.decode(state)
    state.start = start
    switch (v) {
      case 0:
      case 1: {
        const decoded = encoding15.decode(state)
        const map = external0.infoLegacyMap
        return map(decoded)
      }
      case 2: {
        const decoded = encoding16.decode(state)
        return decoded
      }
      case 3: {
        const decoded = encoding18.decode(state)
        return decoded
      }
      default:
        throw new Error('Unsupported version')
    }
  }
}

// @autobee/system-writer-v4
const encoding20 = {
  preencode(state, m) {
    state.end++ // max flag is 16 so always one byte
    c.uint.preencode(state, m.weight)
    c.uint.preencode(state, m.maxWeight)
    c.uint.preencode(state, m.length)
    c.uint.preencode(state, m.clock)

    if (m.timestamp) c.int.preencode(state, m.timestamp)
  },
  encode(state, m) {
    const flags =
      (m.isRemoved ? 1 : 0) |
      (m.isOplog ? 2 : 0) |
      (m.isGenesis ? 4 : 0) |
      (m.isAnchor ? 8 : 0) |
      (m.timestamp ? 16 : 0)

    c.uint.encode(state, flags)
    c.uint.encode(state, m.weight)
    c.uint.encode(state, m.maxWeight)
    c.uint.encode(state, m.length)
    c.uint.encode(state, m.clock)

    if (m.timestamp) c.int.encode(state, m.timestamp)
  },
  decode(state) {
    const v = c.uint.decode(state)
    const flags = c.uint.decode(state)

    return {
      version: v,
      isRemoved: (flags & 1) !== 0,
      isOplog: (flags & 2) !== 0,
      weight: c.uint.decode(state),
      maxWeight: c.uint.decode(state),
      length: c.uint.decode(state),
      clock: c.uint.decode(state),
      isGenesis: (flags & 4) !== 0,
      isAnchor: (flags & 8) !== 0,
      timestamp: (flags & 16) !== 0 ? c.int.decode(state) : 0
    }
  }
}

// @autobee/system-writer
const encoding21 = {
  preencode(state, m) {
    const v = m.version ?? 5
    c.uint.preencode(state, v)
    switch (v) {
      case 0:
      case 1:
      case 2:
      case 3:
        encoding17.preencode(state, m)
        break
      case 4:
        encoding20.preencode(state, m)
        break
      case 5:
        encoding34.preencode(state, m)
        break
      default:
        throw new Error('Unsupported version')
    }
  },
  encode(state, m) {
    const v = m.version ?? 5
    c.uint.encode(state, v)
    switch (v) {
      case 0:
      case 1:
      case 2:
      case 3:
        encoding17.encode(state, m)
        break
      case 4:
        encoding20.encode(state, m)
        break
      case 5:
        encoding34.encode(state, m)
        break
      default:
        throw new Error('Unsupported version')
    }
  },
  decode(state) {
    const start = state.start
    const v = c.uint.decode(state)
    state.start = start
    switch (v) {
      case 0:
      case 1:
      case 2:
      case 3: {
        const decoded = encoding17.decode(state)
        const map = external0.memberLegacyMap
        return map(decoded)
      }
      case 4: {
        const decoded = encoding20.decode(state)
        return decoded
      }
      case 5: {
        const decoded = encoding34.decode(state)
        return decoded
      }
      default:
        throw new Error('Unsupported version')
    }
  }
}

// @autobee/link
const encoding22 = encoding0

// @autobee/external-batch
const encoding23 = {
  preencode(state, m) {
    c.fixed32.preencode(state, m.key)
    c.uint.preencode(state, m.start)
    c.uint.preencode(state, m.length)
  },
  encode(state, m) {
    c.fixed32.encode(state, m.key)
    c.uint.encode(state, m.start)
    c.uint.encode(state, m.length)
  },
  decode(state) {
    const r0 = c.fixed32.decode(state)
    const r1 = c.uint.decode(state)
    const r2 = c.uint.decode(state)

    return {
      key: r0,
      start: r1,
      length: r2
    }
  }
}

// @autobee/batch
const encoding24 = {
  preencode(state, m) {
    c.uint.preencode(state, m.start)
    c.uint.preencode(state, m.end)
  },
  encode(state, m) {
    c.uint.encode(state, m.start)
    c.uint.encode(state, m.end)
  },
  decode(state) {
    const r0 = c.uint.decode(state)
    const r1 = c.uint.decode(state)

    return {
      start: r0,
      end: r1
    }
  }
}

// @autobee/views
const encoding25 = {
  preencode(state, m) {
    c.uint.preencode(state, m.flushes)
    encoding23.preencode(state, m.system)
    state.end++ // flags are fixed size

    if (m.view) encoding23.preencode(state, m.view)
  },
  encode(state, m) {
    const flags = m.view ? 1 : 0

    c.uint.encode(state, m.flushes)
    encoding23.encode(state, m.system)
    c.uint8.encode(state, flags)

    if (m.view) encoding23.encode(state, m.view)
  },
  decode(state) {
    const r0 = c.uint.decode(state)
    const r1 = encoding23.decode(state)
    const flags = c.uint8.decode(state)

    return {
      flushes: r0,
      system: r1,
      view: (flags & 1) !== 0 ? encoding23.decode(state) : null
    }
  }
}

// @autobee/views (inline)
const encoding25_inline = {
  preencode(state, m) {
    c.uint.preencode(state, m.flushes)
    encoding23.preencode(state, m.system)

    if (m.view) encoding23.preencode(state, m.view)
  },
  encode(state, m) {
    c.uint.encode(state, m.flushes)
    encoding23.encode(state, m.system)

    if (m.view) encoding23.encode(state, m.view)
  },
  decode(state, inlining) {
    const r0 = c.uint.decode(state)
    const r1 = encoding23.decode(state)
    const flags = inlining

    return {
      flushes: r0,
      system: r1,
      view: (flags & 1) !== 0 ? encoding23.decode(state) : null
    }
  }
}

// @autobee/signed-backer
const encoding26 = {
  preencode(state, m) {
    c.fixed32.preencode(state, m.key)
    c.uint.preencode(state, m.length)
    c.fixed64.preencode(state, m.signature)
    c.buffer.preencode(state, m.manifest)
  },
  encode(state, m) {
    c.fixed32.encode(state, m.key)
    c.uint.encode(state, m.length)
    c.fixed64.encode(state, m.signature)
    c.buffer.encode(state, m.manifest)
  },
  decode(state) {
    const r0 = c.fixed32.decode(state)
    const r1 = c.uint.decode(state)
    const r2 = c.fixed64.decode(state)
    const r3 = c.buffer.decode(state)

    return {
      key: r0,
      length: r1,
      signature: r2,
      manifest: r3
    }
  }
}

// @autobee/witness.backer
const encoding27_1 = c.frame(encoding26)

// @autobee/witness
const encoding27 = {
  preencode(state, m) {
    c.uint.preencode(state, m.weight)
    encoding27_1.preencode(state, m.backer)
  },
  encode(state, m) {
    c.uint.encode(state, m.weight)
    encoding27_1.encode(state, m.backer)
  },
  decode(state) {
    const r0 = c.uint.decode(state)
    const r1 = encoding27_1.decode(state)

    return {
      weight: r0,
      backer: r1
    }
  }
}

// @autobee/attestation
const encoding28 = {
  preencode(state, m) {
    c.fixed32.preencode(state, m.key)
    c.uint.preencode(state, m.weight)
    c.fixed64.preencode(state, m.signature)
  },
  encode(state, m) {
    c.fixed32.encode(state, m.key)
    c.uint.encode(state, m.weight)
    c.fixed64.encode(state, m.signature)
  },
  decode(state) {
    const r0 = c.fixed32.decode(state)
    const r1 = c.uint.decode(state)
    const r2 = c.fixed64.decode(state)

    return {
      key: r0,
      weight: r1,
      signature: r2
    }
  }
}

// @autobee/oplog-message-v3.witness
const encoding29_6 = c.frame(encoding27)
// @autobee/oplog-message-v3.attestations
const encoding29_7 = c.array(c.frame(encoding28))

// @autobee/oplog-message-v3
const encoding29 = {
  preencode(state, m) {
    let flags =
      (m.batch ? 1 : 0) |
      (m.views ? 2 : 0) |
      (m.optimistic ? 8 : 0) |
      (m.value ? 16 : 0) |
      (m.witness ? 32 : 0) |
      (m.attestations ? 64 : 0) |
      (m.trusted ? 128 : 0)
    if (m.views) {
      flags |= m.views.view ? 4 : 0
    }

    c.uint.preencode(state, m.timestamp)
    encoding29_1.preencode(state, m.links)
    c.uint.preencode(state, flags)

    if (m.batch) encoding24.preencode(state, m.batch)
    if (m.views) encoding25_inline.preencode(state, m.views)
    if (m.value) c.buffer.preencode(state, m.value)
    if (m.witness) encoding29_6.preencode(state, m.witness)
    if (m.attestations) encoding29_7.preencode(state, m.attestations)
    if (m.trusted) encoding29_8.preencode(state, m.trusted)
  },
  encode(state, m) {
    let flags =
      (m.batch ? 1 : 0) |
      (m.views ? 2 : 0) |
      (m.optimistic ? 8 : 0) |
      (m.value ? 16 : 0) |
      (m.witness ? 32 : 0) |
      (m.attestations ? 64 : 0) |
      (m.trusted ? 128 : 0)
    if (m.views) {
      flags |= m.views.view ? 4 : 0
    }

    c.uint.encode(state, m.timestamp)
    encoding29_1.encode(state, m.links)
    c.uint.encode(state, flags)

    if (m.batch) encoding24.encode(state, m.batch)
    if (m.views) encoding25_inline.encode(state, m.views)
    if (m.value) c.buffer.encode(state, m.value)
    if (m.witness) encoding29_6.encode(state, m.witness)
    if (m.attestations) encoding29_7.encode(state, m.attestations)
    if (m.trusted) encoding29_8.encode(state, m.trusted)
  },
  decode(state) {
    const v = c.uint.decode(state)
    const r0 = c.uint.decode(state)
    const r1 = encoding29_1.decode(state)
    const flags = c.uint.decode(state)

    return {
      version: v,
      timestamp: r0,
      links: r1,
      batch: (flags & 1) !== 0 ? encoding24.decode(state) : null,
      views: (flags & 2) !== 0 ? encoding25_inline.decode(state, flags >>> 2) : null,
      optimistic: (flags & 8) !== 0,
      value: (flags & 16) !== 0 ? c.buffer.decode(state) : null,
      witness: (flags & 32) !== 0 ? encoding29_6.decode(state) : null,
      attestations: (flags & 64) !== 0 ? encoding29_7.decode(state) : null,
      trusted: (flags & 128) !== 0 ? encoding29_8.decode(state) : null
    }
  }
}

// @autobee/oplog
const encoding30 = {
  preencode(state, m) {
    const v = m.version ?? 4
    c.uint.preencode(state, v)
    switch (v) {
      case 0:
        encoding12.preencode(state, m)
        break
      case 1:
        encoding13.preencode(state, m)
        break
      case 2:
        encoding14.preencode(state, m)
        break
      case 3:
        encoding29.preencode(state, m)
        break
      case 4:
        encoding37.preencode(state, m)
        break
      default:
        throw new Error('Unsupported version')
    }
  },
  encode(state, m) {
    const v = m.version ?? 4
    c.uint.encode(state, v)
    switch (v) {
      case 0:
        encoding12.encode(state, m)
        break
      case 1:
        encoding13.encode(state, m)
        break
      case 2:
        encoding14.encode(state, m)
        break
      case 3:
        encoding29.encode(state, m)
        break
      case 4:
        encoding37.encode(state, m)
        break
      default:
        throw new Error('Unsupported version')
    }
  },
  decode(state) {
    const start = state.start
    const v = c.uint.decode(state)
    state.start = start
    switch (v) {
      case 0: {
        const decoded = encoding12.decode(state)
        return decoded
      }
      case 1: {
        const decoded = encoding13.decode(state)
        return decoded
      }
      case 2: {
        const decoded = encoding14.decode(state)
        return decoded
      }
      case 3: {
        const decoded = encoding29.decode(state)
        const map = external0.oplogLegacyMap
        return map(decoded)
      }
      case 4: {
        const decoded = encoding37.decode(state)
        return decoded
      }
      default:
        throw new Error('Unsupported version')
    }
  }
}

// @autobee/manifest-data
const encoding31 = {
  preencode(state, m) {
    c.uint.preencode(state, m.version)
    state.end++ // max flag is 2 so always one byte

    if (m.legacyBlocks) c.uint.preencode(state, m.legacyBlocks)
    if (m.namespace) c.fixed32.preencode(state, m.namespace)
  },
  encode(state, m) {
    const flags = (m.legacyBlocks ? 1 : 0) | (m.namespace ? 2 : 0)

    c.uint.encode(state, m.version)
    c.uint.encode(state, flags)

    if (m.legacyBlocks) c.uint.encode(state, m.legacyBlocks)
    if (m.namespace) c.fixed32.encode(state, m.namespace)
  },
  decode(state) {
    const r0 = c.uint.decode(state)
    const flags = c.uint.decode(state)

    return {
      version: r0,
      legacyBlocks: (flags & 1) !== 0 ? c.uint.decode(state) : 0,
      namespace: (flags & 2) !== 0 ? c.fixed32.decode(state) : null
    }
  }
}

// @autobee/trusted-head
const encoding32 = {
  preencode(state, m) {
    c.fixed32.preencode(state, m.key)
    c.uint.preencode(state, m.length)
    c.uint.preencode(state, m.flushes)
  },
  encode(state, m) {
    c.fixed32.encode(state, m.key)
    c.uint.encode(state, m.length)
    c.uint.encode(state, m.flushes)
  },
  decode(state) {
    const r0 = c.fixed32.decode(state)
    const r1 = c.uint.decode(state)
    const r2 = c.uint.decode(state)

    return {
      key: r0,
      length: r1,
      flushes: r2
    }
  }
}

// @autobee/migrated-head
const encoding33 = {
  preencode(state, m) {
    encoding22.preencode(state, m.system)
    state.end++ // flags are fixed size

    if (m.view) encoding22.preencode(state, m.view)
  },
  encode(state, m) {
    const flags = m.view ? 1 : 0

    encoding22.encode(state, m.system)
    c.uint8.encode(state, flags)

    if (m.view) encoding22.encode(state, m.view)
  },
  decode(state) {
    const r0 = encoding22.decode(state)
    const flags = c.uint8.decode(state)

    return {
      system: r0,
      view: (flags & 1) !== 0 ? encoding22.decode(state) : null
    }
  }
}

// @autobee/system-writer-v5
const encoding34 = {
  preencode(state, m) {
    state.end++ // max flag is 4 so always one byte
    c.uint.preencode(state, m.weight)
    c.uint.preencode(state, m.maxWeight)
    c.uint.preencode(state, m.length)
    c.uint.preencode(state, m.timestamp)
  },
  encode(state, m) {
    const flags = (m.isRemoved ? 1 : 0) | (m.isOplog ? 2 : 0) | (m.isGenesis ? 4 : 0)

    c.uint.encode(state, flags)
    c.uint.encode(state, m.weight)
    c.uint.encode(state, m.maxWeight)
    c.uint.encode(state, m.length)
    c.uint.encode(state, m.timestamp)
  },
  decode(state) {
    const v = c.uint.decode(state)
    const flags = c.uint.decode(state)

    return {
      version: v,
      isRemoved: (flags & 1) !== 0,
      isOplog: (flags & 2) !== 0,
      isGenesis: (flags & 4) !== 0,
      weight: c.uint.decode(state),
      maxWeight: c.uint.decode(state),
      length: c.uint.decode(state),
      timestamp: c.uint.decode(state)
    }
  }
}

// @autobee/grant-witness
const encoding35 = {
  preencode(state, m) {
    c.uint.preencode(state, m.weight)
    encoding22.preencode(state, m.link)
  },
  encode(state, m) {
    c.uint.encode(state, m.weight)
    encoding22.encode(state, m.link)
  },
  decode(state) {
    const r0 = c.uint.decode(state)
    const r1 = encoding22.decode(state)

    return {
      weight: r0,
      link: r1
    }
  }
}

// @autobee/approval
const encoding36 = {
  preencode(state, m) {
    c.fixed32.preencode(state, m.key)
    c.uint.preencode(state, m.weight)
  },
  encode(state, m) {
    c.fixed32.encode(state, m.key)
    c.uint.encode(state, m.weight)
  },
  decode(state) {
    const r0 = c.fixed32.decode(state)
    const r1 = c.uint.decode(state)

    return {
      key: r0,
      weight: r1
    }
  }
}

// @autobee/oplog-message-v4.approvals
const encoding37_6 = c.array(encoding36)

// @autobee/oplog-message-v4
const encoding37 = {
  preencode(state, m) {
    c.uint.preencode(state, m.timestamp)
    encoding37_1.preencode(state, m.links)
    state.end++ // max flag is 64 so always one byte

    if (m.batch) encoding24.preencode(state, m.batch)
    if (m.views) encoding25.preencode(state, m.views)
    if (m.trusted) encoding37_4.preencode(state, m.trusted)
    if (m.witness) encoding35.preencode(state, m.witness)
    if (m.approvals) encoding37_6.preencode(state, m.approvals)
    if (m.value) c.buffer.preencode(state, m.value)
  },
  encode(state, m) {
    const flags =
      (m.batch ? 1 : 0) |
      (m.views ? 2 : 0) |
      (m.trusted ? 4 : 0) |
      (m.witness ? 8 : 0) |
      (m.approvals ? 16 : 0) |
      (m.optimistic ? 32 : 0) |
      (m.value ? 64 : 0)

    c.uint.encode(state, m.timestamp)
    encoding37_1.encode(state, m.links)
    c.uint.encode(state, flags)

    if (m.batch) encoding24.encode(state, m.batch)
    if (m.views) encoding25.encode(state, m.views)
    if (m.trusted) encoding37_4.encode(state, m.trusted)
    if (m.witness) encoding35.encode(state, m.witness)
    if (m.approvals) encoding37_6.encode(state, m.approvals)
    if (m.value) c.buffer.encode(state, m.value)
  },
  decode(state) {
    const v = c.uint.decode(state)
    const r0 = c.uint.decode(state)
    const r1 = encoding37_1.decode(state)
    const flags = c.uint.decode(state)

    return {
      version: v,
      timestamp: r0,
      links: r1,
      batch: (flags & 1) !== 0 ? encoding24.decode(state) : null,
      views: (flags & 2) !== 0 ? encoding25.decode(state) : null,
      trusted: (flags & 4) !== 0 ? encoding37_4.decode(state) : null,
      witness: (flags & 8) !== 0 ? encoding35.decode(state) : null,
      approvals: (flags & 16) !== 0 ? encoding37_6.decode(state) : null,
      optimistic: (flags & 32) !== 0,
      value: (flags & 64) !== 0 ? c.buffer.decode(state) : null
    }
  }
}

// @autobee/system-info-v3.heads, deferred due to recusive use
const encoding18_3 = c.array(encoding22)
// @autobee/system-info-v3.indexers, deferred due to recusive use
const encoding18_4 = encoding18_3
// @autobee/oplog-message-v3.links, deferred due to recusive use
const encoding29_1 = encoding18_3
// @autobee/oplog-message-v3.trusted, deferred due to recusive use
const encoding29_8 = c.array(c.frame(encoding32))
// @autobee/oplog-message-v4.links, deferred due to recusive use
const encoding37_1 = encoding18_3
// @autobee/oplog-message-v4.trusted, deferred due to recusive use
const encoding37_4 = encoding18_3

function setVersion(v) {
  version = v
}

function encode(name, value, v = VERSION) {
  version = v
  return c.encode(getEncoding(name), value)
}

function decode(name, buffer, v = VERSION) {
  version = v
  return c.decode(getEncoding(name), buffer)
}

function getEnum(name) {
  switch (name) {
    default:
      throw new Error('Enum not found ' + name)
  }
}

function getEncoding(name) {
  switch (name) {
    case '@autobase-compat/checkout':
      return encoding0
    case '@autobase-compat/clock':
      return encoding1
    case '@autobase-compat/index-checkpoint':
      return encoding2
    case '@autobase-compat/boot-record-v0':
      return encoding3
    case '@autobase-compat/boot-record-raw':
      return encoding4
    case '@autobase-compat/boot-record':
      return encoding5
    case '@autobase-compat/checkpointer':
      return encoding6
    case '@autobase-compat/checkpoint':
      return encoding7
    case '@autobase-compat/digest':
      return encoding8
    case '@autobase-compat/node':
      return encoding9
    case '@autobase-compat/user-view-trace':
      return encoding10
    case '@autobase-compat/trace':
      return encoding11
    case '@autobase-compat/oplog-message-v0':
      return encoding12
    case '@autobase-compat/oplog-message-v1':
      return encoding13
    case '@autobase-compat/oplog-message-v2':
      return encoding14
    case '@autobase-compat/info-v1':
      return encoding15
    case '@autobase-compat/info-v2':
      return encoding16
    case '@autobase-compat/member':
      return encoding17
    case '@autobee/system-info-v3':
      return encoding18
    case '@autobee/system-info':
      return encoding19
    case '@autobee/system-writer-v4':
      return encoding20
    case '@autobee/system-writer':
      return encoding21
    case '@autobee/link':
      return encoding22
    case '@autobee/external-batch':
      return encoding23
    case '@autobee/batch':
      return encoding24
    case '@autobee/views':
      return encoding25
    case '@autobee/signed-backer':
      return encoding26
    case '@autobee/witness':
      return encoding27
    case '@autobee/attestation':
      return encoding28
    case '@autobee/oplog-message-v3':
      return encoding29
    case '@autobee/oplog':
      return encoding30
    case '@autobee/manifest-data':
      return encoding31
    case '@autobee/trusted-head':
      return encoding32
    case '@autobee/migrated-head':
      return encoding33
    case '@autobee/system-writer-v5':
      return encoding34
    case '@autobee/grant-witness':
      return encoding35
    case '@autobee/approval':
      return encoding36
    case '@autobee/oplog-message-v4':
      return encoding37
    default:
      throw new Error('Encoder not found ' + name)
  }
}

function getStruct(name, v = VERSION) {
  const enc = getEncoding(name)
  return {
    preencode(state, m) {
      version = v
      enc.preencode(state, m)
    },
    encode(state, m) {
      version = v
      enc.encode(state, m)
    },
    decode(state) {
      version = v
      return enc.decode(state)
    }
  }
}

const resolveStruct = getStruct // compat

module.exports = {
  resolveStruct,
  getStruct,
  getEnum,
  getEncoding,
  encode,
  decode,
  setVersion,
  version
}
const ReadyResource = require('ready-resource')
const ReadyGuard = require('ready-guard')
const b4a = require('b4a')
const safetyCatch = require('safety-catch')
const Hyperbee = require('hyperbee2')
const ID = require('hypercore-id-encoding')
const rrp = require('resolve-reject-promise')
const { AutobeeEncryption, WriterEncryption, ViewEncryption } = require('autobee-encryption')
const AutobeeWakeup = require('autobee-wakeup')
const Hypercore = require('hypercore')
const crypto = require('hypercore-crypto')
const c = require('compact-encoding')
const asserts = require('./lib/asserts.js')
const boot = require('./lib/boot.js')
const { resolveWeight, currentWeight } = require('./lib/witness.js')
const encoding = require('./lib/encoding.js')
const { LEGACY_OPLOG_VERSION } = require('./lib/constants.js')
const FastForward = require('./lib/fast-forward.js')
const System = require('./lib/system.js')
const ApplyCalls = require('./lib/apply-calls.js')
const topo = require('./lib/topo.js')
const { ActiveWriters } = require('./lib/writers.js')
const TrustedPeers = require('./lib/trusted.js')
const ApplyView = require('./lib/apply-view.js')
const UpdateChanges = require('./lib/updates.js')
const migrations = require('./lib/migrations.js')

const EMPTY_HEAD = { length: 0, key: null }
const INTERRUPT = new Error('Apply interrupted')

module.exports = class Autobee extends ReadyResource {
  constructor(store, key = null, handlers = {}) {
    super()

    if (isObject(key)) {
      handlers = key
      key = null
    }

    const {
      name = null,
      encrypted,
      encryptionKey,
      viewName = 'view',
      bootstrapWeight = 2
    } = handlers

    this.encrypted = encrypted === true || !!encryptionKey
    this.bootstrapWeight = bootstrapWeight

    this.getSystemEncryption = this._getEncryptionProvider.bind(this, '_system')
    this.getViewEncryption = this._getEncryptionProvider.bind(this, viewName)

    const beeStore = store.session()
    const bee = new Hyperbee(beeStore, {
      core: beeStore.get({ preload: this._getCorePreload(viewName) }),
      // defer one tick to ensure consistent state, then return state prom
      preload: async () => {
        await 1
        await this._bootGuard.ready()
      },
      getEncryptionProvider: this.getViewEncryption
    })

    this.store = store

    this.key = key ? ID.decode(key) : null
    this.discoveryKey = null
    this.id = null
    this.bootstrap = null
    this._handlers = handlers
    this.stats = { undos: 0, fastForwards: 0, drains: 0, applies: 0, appends: 0 }

    const systemStore = this.store.session()
    this.system = new System(systemStore, this.name, {
      core: systemStore.get({ preload: this._getCorePreload('system') }),
      encrypted: this.encrypted,
      getEncryptionProvider: this.getSystemEncryption
    })

    this.bee = bee.snapshot()
    this.view = ApplyView.open(this.bee, this)
    this.optimistic = handlers.optimistic !== false // TODO: should default to false instead

    this.name = name // for debugging

    this.local = null
    this.encryptionKey = null
    this.keyPair = null
    this.writers = null
    this.bumping = 0

    // fastForward: false disables all fast-forwards - both wakeup and boot
    const fastForward = handlers.fastForward === false ? null : handlers.fastForward || {}

    this._ffEnabled = fastForward !== null

    // oplog head to boot from: migrates or fast-forwards depending on its version
    this.bootFrom = (fastForward && fastForward.boot) || null

    // conservative (default on): only fast-forward onto a head someone can serve whole
    this._conservativeFF = !fastForward || fastForward.conservative !== false

    this.trusted = new TrustedPeers(handlers)

    this.ff = null
    this.fastForwarding = null
    this.fastForwardTo = null

    this._workingBee = bee
    this._workingView = new ApplyView(this._workingBee, this)

    this._localSystemStart = 0
    this._localSystemLength = 0
    this._localFlushes = 0
    this._localViewStart = 0
    this._localViewLength = 0

    this._appending = []
    this._draining = null
    this._updating = null
    this._bootWait = null

    this.legacyViews = handlers.legacyViews || []

    // the guards may be destroyed before anyone waits on them
    this._bootGuard = new ReadyGuard()
    this._bootGuard.ready().catch(safetyCatch)
    this._bootOnlineGuard = new ReadyGuard()
    this._bootOnlineGuard.ready().catch(safetyCatch)

    this._now = handlers.now || Date.now // overridable for clock-drift tests
    this._preapply = handlers.preapply || null
    this._preApplied = false
    this._hasApply = !!handlers.apply
    this._hasUpdate = !!handlers.update
    this._needsUpdate = false
    this._approvalCheck = true
    this._approvedPending = new Map()
    this._prefetchingApprovals = false
    this._updateLocalCore = null
    this._host = new ApplyCalls(this)
    this._notifyHandler = null

    this.interrupted = null
    this._interrupting = false
    this._onErrorBound = this._onError.bind(this)
    this._bumpSoonBound = this.bumpSoon.bind(this)
    this._onGroupUpdateBound = this._onGroupUpdate.bind(this)

    this.wakeupCapability = null
    this._wakeup = new AutobeeWakeup(this, handlers)
    this.previousDrain = 0

    this._catchupMigratedNodes = null
    this._migratedHead = null
    this._prebooting = null

    this.ready().catch(noop)
  }

  static GENESIS = EMPTY_HEAD

  static isAutobee(auto) {
    return auto instanceof Autobee
  }

  get isIndexer() {
    return this.writers.localWriter.isIndexer
  }

  get writable() {
    return this.writers.writable
  }

  // autobase compat
  get activeWriters() {
    return this.writers
  }

  get flushes() {
    return this.system.flushes
  }

  get busy() {
    return !!(this._draining || this._updating)
  }

  async _getCorePreload(name) {
    await 1
    const result = await this._prebooting
    return {
      name: 'autobee/' + result.local.id + '/' + name,
      encryption: name === 'system' ? this.getSystemEncryption() : this.getViewEncryption(),
      inflightRange: [256, 512]
    }
  }

  // network free: only a migration needs peers, so ready() awaits the full
  // state boot unless one is pending and defers it to the background if so -
  // anything needing the booted system must wait on the boot guard
  async _open() {
    this._prebooting = this._preBoot()

    let result = null

    try {
      result = await this._prebooting

      this.key = result.key
      this.bootstrap = result.bootstrap
      this.discoveryKey = result.bootstrap.core.discoveryKey
      this.id = result.bootstrap.core.id
      this.encryptionKey = result.encryptionKey
      this.previousDrain = result.previousDrain
      this.local = result.local

      if (this.encrypted && this.encryptionKey === null) {
        throw new Error('Encryption key is expected')
      }

      this.local.setEncryption(this._getEncryptionProvider())
      this.writers = new ActiveWriters(this)
    } catch (err) {
      // unblock the guard awaiters (bee preloads, bumps, wakeups, flushes)
      this._bootGuard.destroy(err)
      this._bootOnlineGuard.destroy(err)
      throw err
    }

    const booting = this._boot()

    // waits on the boot guard internally and destroys its own guard on failure
    this._bootOnline()

    // if migrating, we might need network io, if not lets wait on it - less surprises
    if (result.migration) booting.catch(this._onErrorBound)
    else await booting
  }

  _requestWakeup() {
    const session = this._wakeup._session
    if (session) session.broadcastLookup()
  }

  _registerWakeup() {
    if (!this.wakeupCapability) return
    this._wakeup.recouple()
    this._wakeup.setCapability(this.wakeupCapability.key, this.wakeupCapability.discoveryKey)
  }

  getExternalWriters() {
    const keys = []
    for (const w of this.writers.active.values()) {
      if (w === this.writers.localWriter) continue
      keys.push(w.core.key)
    }
    return keys
  }

  getWriterViews(key) {
    const id = b4a.toString(key, 'hex')
    const w = this.writers.active.get(id)
    if (!w) return []
    return w.views()
  }

  static getViewEncryption(bootstrap, encryptionKey, name) {
    return AutobeeEncryption.getViewEncryption(bootstrap, encryptionKey, name)
  }

  views() {
    const sys = this.system.bee.context.local
    const view = this._workingBee.context.local

    // todo: figure out why blind-peer doesn't mirror core
    // without adding it to mirror request here
    const head = this.system.bee.head()

    // signedLength for autobase compat
    return [
      { key: sys.key, length: sys.length, signedLength: sys.length },
      { key: view.key, length: view.length, signedLength: view.length },
      { key: head.key, length: head.length, signedLength: head.length }
    ]
  }

  async _close() {
    this._interrupting = true

    try {
      await ApplyView.close(this.view, this)
    } catch (err) {
      safetyCatch(err)
    }

    if (this.writers) await this.writers.close()
    await this.local.close()
    await this.system.close()
    await this._wakeup.close()
    if (this.bootstrap) await this.bootstrap.close()

    try {
      await this._workingView.close()
      await this.bee.close()
    } catch (err) {
      safetyCatch(err)
    }

    // don't rely on the store teardown to stop the notify watcher
    if (this._notifyHandler) this._notifyHandler.destroy()

    // rugpull the rest
    await this.store.close()

    if (this._bootWait !== null) this._bootWait.resolve()
    if (this._updating) await this._updating
    if (this._draining) await this._draining

    // let in-flight writer adds finish
    try {
      await this._bootOnlineGuard.ready()
    } catch {}
  }

  replicate(...args) {
    const stream = this.store.replicate(...args)
    this._wakeup.addStream(stream)
    return stream
  }

  async flush() {
    await this._bootOnlineGuard.ready()
  }

  hintWakeup(wakeup) {
    this._wakeup.hint(wakeup)
  }

  // the view an oplog head points at, opened and closed through the handlers
  openViewAt(oplog) {
    const v = oplog.op.views.view
    if (!v) return null

    return new ApplyView(this.bee.checkout({ key: v.key, length: v.start + v.length }), this)
  }

  openCore(key) {
    const encryption = this.encryptionKey ? new WriterEncryption(this) : null
    return this.store.get({ key, encryption })
  }

  _getEncryptionProvider(view) {
    if (!this.encrypted) return null
    if (view) return new ViewEncryption(this, view)
    return new WriterEncryption(this)
  }

  getMostRecentHead() {
    return topo.getMostRecentHead(this, this.system.bee.snapshot())
  }

  async _preBoot() {
    if (this._handlers.wait) await this._handlers.wait()

    await this.store.ready()

    if (this._handlers.encryptionKey) {
      this.encryptionKey = await this._handlers.encryptionKey
    }

    if (this._handlers.keyPair) {
      this.keyPair = await this._handlers.keyPair
    }

    if (this.bootFrom) {
      this.bootFrom = getBootOption(await this.bootFrom)
    }

    return boot(this.store, this.key, this.legacyViews, {
      encryptionKey: this.encryptionKey,
      keyPair: this.keyPair
    })
  }

  async _boot() {
    if (!this._bootGuard.enter()) return this._bootGuard.ready()

    try {
      await this._bootUnsafe()
    } catch (err) {
      this._bootGuard.destroy(err)
      throw err
    }

    this._bootGuard.exit()

    this.bumpSoon()

    return this._bootGuard.ready()
  }

  async _bootUnsafe() {
    const result = await this._prebooting

    if (this._handlers.wakeupCapability) {
      this.wakeupCapability = await this._handlers.wakeupCapability
    } else {
      this.wakeupCapability = { key: this.key, discoveryKey: this.discoveryKey }
    }

    this._registerWakeup()

    if (this.wakeupCapability) {
      if (this.bootstrap !== this.local && !this.store.storage.readOnly) {
        await this.bootstrap.setGroup(this.wakeupCapability.discoveryKey)
      }

      this._notifyHandler = this.store.notifyGroup(this.wakeupCapability.discoveryKey)
      this._notifyHandler.on('update', this._onGroupUpdateBound)
      await this._drainBootHints()
    }

    const system = result.system || EMPTY_HEAD

    await this.system.boot(system)

    const migrated = await this.local.getUserData('autobee/migrated-head')
    if (migrated) this._migratedHead = encoding.decodeMigratedHead(migrated)

    let view = this.system.view
    if (!view) view = (this._migratedHead && this._migratedHead.view) || EMPTY_HEAD

    // @todo migration
    if (result.migration) {
      if (this._handlers.migrate) {
        view =
          (await this._handlers.migrate(result.migration.views, result.migration.system)) ||
          EMPTY_HEAD
        this._catchupMigratedNodes = result.migration.catchup

        this._migratedHead = {
          system: result.migration.system,
          view: view.length ? view : (this._migratedHead && this._migratedHead.view) || null
        }

        await this._storeMigratedHead()
      }

      // ff boot invalidated by migration
      this.bootFrom = null

      // clear legacy data
      await this.bootstrap.setUserData('autobase/local', null)
      await this.local.setUserData('autobase/boot', null)
      await this.local.setUserData('autobase/encryption', null)

      for (const batch of result.migration.catchup) {
        const { key, length } = batch[batch.length - 1]
        this.writers.wakeup(key, length)
      }
    }

    // since we are doing this DURING the hyperbee2 boot we need to ready the core
    // be good if we had better plumbing for it
    await this._workingBee.core.ready()
    await this.bee.core.ready()

    this._workingBee.move(view)
    this.bee.move(view)

    await this.writers.updateLocalState()

    // roots are moved, so these resolve without re-entering the boot guard
    await this.bee.ready()
    await this._workingBee.ready()

    // baseline before the guard opens so an early drain can't flush against stale offsets
    this._localSystemStart = this.system.bee.context.local.length
    this._localViewStart = this._workingBee.context.local.length
    this._localFlushes = this.system.flushes
  }

  async _bootOnline() {
    if (!this._bootOnlineGuard.enter()) return

    try {
      if (!this._bootGuard.opened) await this._bootGuard.ready()

      for (const head of this.system.heads) {
        await this.writers.add(head.key)
      }

      if (!this.system.heads.length) {
        await this.writers.add(this.bootstrap.key)
      }

      await this._bump(true)
    } catch (err) {
      this._bootOnlineGuard.destroy(err)
      return
    }

    this._bootOnlineGuard.exit()
  }

  bumpSoon() {
    this._bump(false).catch(safetyCatch)
  }

  async _bump(force) {
    if (!force && !this._bootGuard.opened) await this._bootGuard.ready()

    // resolve before the bootAll gate: a parked boot-from retries on any bump,
    // and its drain is the one _bootOnline itself is waiting on
    if (this._bootWait !== null) this._bootWait.resolve()

    if (!force && !this._bootOnlineGuard.opened) await this._bootOnlineGuard.ready()

    this.bumping++

    if (!this._draining) {
      this._draining = this._drain().catch(this._onErrorBound)
    }

    return this._draining
  }

  update() {
    return this._bump(false)
  }

  async updated() {
    if (!this._bootGuard.opened) await this._bootGuard.ready()
    if (this._draining) return this._draining
    return Promise.resolve()
  }

  interrupt(reason) {
    asserts.assert(!!this._host.applying, 'Interrupt is only allowed in apply')
    this._interrupting = true
    if (reason) this.interrupted = reason
    throw INTERRUPT
  }

  getLastError() {
    return this._lastError
  }

  _onError(err) {
    if (this.closing) return

    this._lastError = err

    if (err === INTERRUPT) {
      this.emit('interrupt', this.interrupted)
      this.emit('update')
      return
    }

    this.close().catch(safetyCatch)

    // if no one is listening we should crash! we cannot rely on the EE here
    // as this is wrapped in a promise so instead of nextTick throw it
    if (ReadyResource.listenerCount(this, 'error') === 0) {
      crashSoon(err)
      return
    }

    this.emit('error', err)
  }

  // one-shot user gate: nothing applies until the host has resolved whatever
  // state apply depends on (e.g. legacy views recorded by a migration)
  async _runPreApply() {
    if (this._preapply === null || this._preApplied) return

    this._preApplied = true
    await this._preapply(this.view)
  }

  async _drain() {
    if (this._updating) await this._updating

    await this._runPreApply()

    this.stats.drains++

    if (this.bootFrom) {
      const { head = null, legacy = null, bootCondition = null, wait = false } = this.bootFrom

      this.bootFrom = null

      if (legacy) await this._bootFromSystem(legacy)
      else if (head) await this._bootFromHead(head, bootCondition, wait)
    }

    const changes = this._hasUpdate ? new UpdateChanges(this) : null
    if (changes) changes.track()

    // Anything expecting work to be done during bumpSoon should do it here
    while (!this._interrupting && this.bumping > 0) {
      if (this._interrupting) break

      // Ensure we catch updates during the drain (i.e. setLocal will bump)
      if (this._updateLocalCore !== null) {
        await this._rotateLocalWriter(this._updateLocalCore)
      }

      try {
        while (!this._interrupting) {
          await this._flushWakeup()
          if (this._interrupting) break

          if (this.fastForwardTo !== null) {
            await this._applyFastForward()
            break // revaluate conditions...
          }

          if (!(await this._bumpPendingWriters())) break
          this._needsUpdate = true
        }

        await this._flushLocal()

        if (!this._interrupting) await this.writers.refresh()
      } finally {
        if (this.bumping === 1) this.bumping = 0
        else this.bumping = 1
      }
    }

    this._draining = null
    if (this._interrupting) return

    const updating = rrp()
    this._updating = updating.promise

    try {
      if (this._needsUpdate) await this._update(changes)
      await this._storeBoot()
    } finally {
      this._updating = null
      updating.resolve()
    }
  }

  _onGroupUpdate({ key, length }) {
    this._wakeup.hint({ key, length })
    this.bumpSoon()
  }

  async _drainBootHints() {
    if (!this._notifyHandler) return

    const keys = []
    for await (const key of this._notifyHandler.updates({ since: this.previousDrain })) {
      keys.push(key)
    }
    if (!keys.length) return

    // read the lengths straight from storage in one batch instead of opening cores
    const discoveryKeys = keys.map((key) => crypto.discoveryKey(key))
    const infos = await this.store.storage.getInfos(discoveryKeys, {
      auth: false,
      head: true,
      hints: false
    })

    for (let i = 0; i < keys.length; i++) {
      const info = infos[i]
      const length = info && info.head ? info.head.length : 0
      this._wakeup.hint({ key: keys[i], length })
    }
  }

  // wakeup hints keep arriving asynchronously
  async _applyWakeupHints() {
    const hints = this._wakeup.flush()
    if (!hints.size) return []

    this.previousDrain = Date.now()
    const results = await this._filterHints(hints)

    for (const { key, length } of results) {
      // wakeup() itself no-ops the add for an already-active writer, but still
      // needs to run so it can hint() the announced length - skipping it here
      // for active writers left gc with no hint to protect them
      await this.writers.wakeup(key, length)
    }

    return results
  }

  async _filterHints(hints) {
    const results = []
    const promises = []
    for (const [hex, length] of hints) {
      const key = b4a.from(hex, 'hex')
      promises.push(this._filterTracked(key, length, results))
    }
    await Promise.all(promises)
    return results
  }

  async _filterTracked(key, length, results) {
    if (length) {
      const info = await this.system.get(key)
      // if we've seen this already, ignore
      if (info && info.length >= length) return
    }
    results.push({ key, length })
  }

  async _flushWakeup() {
    const hints = await this._applyWakeupHints()
    if (!hints.length) return

    if (!this._ffEnabled) return

    // a scheduled fast-forward is applied by the drain before we look again
    if (this.fastForwardTo !== null || this.fastForwarding !== null) return
    if (this._interrupting || this.closing || this.bootFrom) return

    try {
      const heads = await this._readCandidateHeads(hints, FastForward.DEFAULT_TIMEOUT)

      const ff = await FastForward.fromHeads(this, heads, {
        timeout: FastForward.DEFAULT_TIMEOUT
      })

      if (ff !== null) await this._runFastForward(ff)
    } catch (err) {
      safetyCatch(err)
    }
  }

  async _readCandidateHeads(hints, timeout) {
    const promises = []

    for (const { key, length } of hints) {
      if (length === 0) continue
      promises.push(this._resolveOplogHint(key, length, timeout ? { timeout } : null))
    }

    const ops = await Promise.all(promises)
    const heads = []

    for (const res of ops) {
      if (res === null) continue

      // the head we were woken on is a candidate in its own right
      heads.push({ key: res.key, length: res.length })

      if (!res.op.trusted) continue

      for (const trusted of res.op.trusted) {
        const head = this.trusted.read(trusted)
        if (head !== null) heads.push(head)
      }
    }

    return heads
  }

  async _resolveOplogHint(key, length, opts) {
    const core = this.openCore(key)

    try {
      await core.ready()
      const max = Math.max(length, core.length)

      const block = await this.readOplog(core, max, opts)
      if (max >= core.length) return block // best one

      return await this.readOplog(core, core.length, opts)
    } finally {
      await core.close()
    }
  }

  async _getOplog(key, length, opts = null) {
    const core = this.openCore(key)

    try {
      return await this.readOplog(core, length, opts)
    } finally {
      await core.close()
    }
  }

  // reads on a core the caller owns, so a walk does not churn a session per step
  async readOplog(core, length, opts = null) {
    await core.ready()

    const { conservative = false, timeout } = opts === null ? {} : opts

    const target = length >= 0 ? length : core.length
    if (target === 0) return null

    // conservative: only proceed when a connected peer can serve the head whole
    if (conservative && core.remoteContiguousLength < target) return null

    const buf = await core.get(target - 1, { timeout })
    if (buf === null) return null

    let op = encoding.decodeOplog(buf)

    // legacy nodes always inflate, but only indexers carry views - callers
    // that need system info must check op.views
    if (op.version < 3) {
      op = await migrations.inflateLegacyOplog(buf, core, target - 1, timeout)
    }

    return {
      key: core.key,
      length: target,
      op
    }
  }

  async _update(changes) {
    this._needsUpdate = false
    this.bee.update(this._workingBee.root)

    if (!changes) return

    changes.finalise()
    await this._handlers.update(this.view, changes)
  }

  async setLocal(key, { keyPair } = {}) {
    if (!this._bootGuard.opened) await this._bootGuard.ready()
    if (this.closing) throw new Error('Autobee closed')

    const manifest = keyPair
      ? { version: this.store.manifestVersion, signers: [{ publicKey: keyPair.publicKey }] }
      : null
    if (!key) key = Hypercore.key(manifest)
    // If the keys are the same, no need to rotate
    if (b4a.equals(key, this.local.key)) return

    const encryption = this.encryptionKey ? this._getEncryptionProvider() : null

    const local = this.store.get({
      key,
      manifest,
      active: false,
      exclusive: true,
      encryption
    })
    await local.ready()

    this._updateLocalCore = local

    let runs = 0
    while (!this._interrupting && this.appending && runs++ < 16) await this.update()
    this.bumpSoon()
  }

  async _rotateLocalWriter(newLocal) {
    asserts.assert(!this.appending, 'Cannot rotate a newLocal writer if an append is in progress')

    const oldLocal = this.local

    this.local = newLocal
    await this.writers.rotateLocalWriter(this.local)

    this._updateLocalCore = null

    this.local.setUserData('referrer', this.key)
    if (this.encryptionKey) {
      await this.local.setUserData('autobee/encryption', this.encryptionKey)
    }

    await this.bootstrap.setUserData('autobee/local', this.local.key)
    await oldLocal.close()

    // done, soft restart
    this.emit('rotate-local-writer')
  }

  async createAnchor(key, length) {
    let node = null
    for (let i = this._host.applying.length - 1; i >= 0; i--) {
      const n = this._host.applying[i]
      if (b4a.equals(n.key, key) && n.length === length) {
        node = n
        break
      }
    }

    if (!node) throw new Error('Anchor node is not in system')

    const legacy = node.version <= 2

    const info = await this.system.get(key, { unflushed: true })
    if (!info || info.length < length) throw new Error('Anchor node is not in system')

    const state = { start: 0, end: 40, buffer: b4a.alloc(40) }
    c.fixed32.encode(state, key)
    c.uint64.encode(state, length)

    const namespace = crypto.hash(state.buffer)
    const manifestData = c.encode(encoding.ManifestData, { version: 0, legacyBlocks: 0, namespace })

    const padding = this.encryptionKey ? AutobeeEncryption.PADDING : 0
    const links = [{ key, length }]

    const block = Autobee.encodeValue(null, {
      legacy,
      timestamp: 0,
      links,
      heads: links, // legacy compat
      padding
    })

    if (this.encryptionKey) {
      AutobeeEncryption.encryptAnchor(block, this.key, this.encryptionKey, namespace)
    }

    const root = { index: 0, size: block.byteLength, hash: crypto.data(block) }
    const hash = crypto.tree([root])
    const prologue = { hash, length: 1 }

    const core = createAnchorCore(this.store, prologue, manifestData)
    await core.ready()

    if (core.length === 0) {
      await core.append(block, { writable: true, maxLength: 1 })
    }

    await this.system.addAnchor(core.key)

    const anchor = { key: core.key, length: core.length }

    await core.close()

    return anchor
  }

  async _bumpMigratedWriters() {
    const opened = new Set()
    let updated = false

    for (const batch of this._catchupMigratedNodes) {
      await this._processBatch(batch)
      updated = true
      for (const node of batch) {
        if (node.from) opened.add(node.from)
      }
    }

    for (const core of opened) await core.close()
    return updated
  }

  async _prefetchApprovals() {
    if (this._prefetchingApprovals) return
    this._prefetchingApprovals = true

    try {
      const prefetch = []
      const standing = await this._standing()
      if (standing <= 0 || !this._pendingWork(standing)) return
      for await (const p of this._servableRequests(standing)) {
        prefetch.push(this.system.get(p.key).catch(safetyCatch))
        prefetch.push(this.system.grantHint(p.key).catch(safetyCatch))
      }
      await Promise.allSettled(prefetch)
    } finally {
      this._prefetchingApprovals = false
    }
  }

  async _standing() {
    const rec = await this.system.get(this.local.key)
    return currentWeight(rec)
  }

  // only our own tiers: a request whose lower tiers are already anchored is
  // somebody stronger's job, and must not keep every peer at this standing
  // rescanning (or offering) while it waits
  _pendingWork(standing) {
    const digest = this.system.promotions.digest
    const end = Math.min(standing, digest.length)
    for (let w = 1; w <= end; w++) {
      if (digest[w - 1]) return true
    }
    return false
  }

  // the requests we could serve and by how much - shared by the prefetch and
  // the collector so the two cannot drift on what counts as ours
  async *_servableRequests(standing) {
    for await (const p of this.system.listPendingPromotions()) {
      const amount = Math.min(standing, p.weight)
      if (amount <= 0) continue
      yield { key: p.key, amount }
    }
  }

  async _collectApprovals() {
    if (!this.system.promotions.changed && !this._approvalCheck) return null
    if (!this.writers.writable) return null

    const standing = await this._standing()
    if (standing <= 0) return null

    this.system.promotions.changed = false
    this._approvalCheck = false

    if (!this._pendingWork(standing)) return null

    const approvals = []
    const approving = []

    for await (const p of this._servableRequests(standing)) {
      approving.push(fetchApproval.call(this, p.key, p.amount, approvals))
    }

    if (!approving.length) return null

    try {
      await Promise.all(approving)
    } catch (err) {
      if (this._interrupting) return null
      throw err
    }

    if (!approvals.length) return null

    for (const a of approvals) {
      this._approvedPending.set(b4a.toString(a.key, 'hex'), a.weight)
    }

    return approvals

    async function fetchApproval(key, weight, approvals) {
      const hex = b4a.toString(key, 'hex')
      if ((this._approvedPending.get(hex) || 0) >= weight) return
      const [target, hint] = await Promise.all([this.system.get(key), this.system.grantHint(key)])
      if (!target || target.isRemoved) return
      if (hint && hint.weight >= weight) return
      approvals.push({ key, weight })
    }
  }

  async _bumpPendingWriters({ local = false } = {}) {
    if (!local && this._catchupMigratedNodes !== null) {
      const updated = await this._bumpMigratedWriters()
      this._catchupMigratedNodes = null
      if (updated) return true
    }

    // apply the best next node to keep the prefix stable
    const next = await this.writers.nextPendingNode({ local })
    if (next === null) return false

    const { writer: w, batch } = next

    if (w.isAdded || (w.isRemoved && w.hasReferrals())) {
      await this._processBatch(batch)
      w.notify(batch)
      return true
    }

    if (this.optimistic && !w.isRemoved && batch[0].optimistic) {
      if (!(await this._optimisticBatch(batch))) {
        w.removePending()
        return true
      }
      w.notify(batch)
      return true
    }

    return true
  }

  async _optimisticBatch(batch) {
    const rollbackSystem = this.system.bee.head()
    const rollbackView = this._workingBee.head()

    const t = await this.prepareBatch(batch)
    if (t.view) this._workingBee.move(t.view)

    for (const b of t.tip) {
      let failed = true
      try {
        if (await this.system.canApply(b[0].key, true)) {
          await this._applyBatch(b, true)
          failed = false
        }
      } catch {}

      // only check if batch was successful
      if (b !== batch) continue

      const w = failed ? null : await this.system.get(b[0].key)
      if (!w || w.length < b[0].length) {
        this._workingBee.move(rollbackView)
        this.system.bee.move(rollbackSystem)
        await this.system.reset()
        return false
      }
    }

    return true
  }

  async prepareBatch(batch) {
    const node = batch[0]
    // recomputed on every application, converges because prefixes converge
    node.weight = await resolveWeight(this, node)
    for (const n of batch) n.weight = node.weight

    // if (topo.isLinkingAll(node, this.system.heads)) {
    //   return { undo: null, view: null, tip: [batch] }
    // }

    const t = await topo.sort(this, batch)

    if (t.undo) {
      this.stats.undos++
      this.trusted.clear()
      t.view = await this.system.undo(t.undo)
      if (!t.view.length && this._migratedHead && this._migratedHead.view) {
        t.view = this._migratedHead.view
      }
    }

    return t
  }

  async applyBacklog(batches) {
    const queue = batches.slice()

    while (queue.length) {
      const batch = queue.shift()
      const t = await this.prepareBatch(batch)

      if (t.view) {
        this._workingBee.move(t.view)
        queue.unshift(...t.tip)
        continue
      }

      if (b4a.equals(batch[0].key, this.key) && batch[0].length === 1) {
        await this._host.addWriter(batch[0].key, { weight: this.bootstrapWeight })
      }

      await this._applyBatch(batch, batch[0].optimistic)

      this.writers.triggers.trigger(
        b4a.toString(batch[0].key, 'hex'),
        batch[batch.length - 1].length
      )
    }
  }

  async _processBatch(batch) {
    await this.applyBacklog([batch])
  }

  async _applyBatch(batch, optimistic) {
    const local = batch[0].core === this.local

    const userBatch = []
    for (const node of batch) {
      this.system.addNode(node)

      if (node.approvals) {
        for (const a of node.approvals) {
          await this.system.addWriter(a.key, {
            weight: a.weight,
            coord: { key: node.key, length: node.length },
            carrier: node.weight,
            anchor: true
          })
        }
      }

      // compat: autobase nodes may be null (legacy null decodes to 0-length buffer)
      if (node.value && node.value.length) userBatch.push(node)
    }

    if (this._hasApply && (await this.system.canApply(batch[0].key, optimistic))) {
      this.stats.applies++
      this._host.applying = batch
      await this._workingView.apply(userBatch)
      this._host.applying = null
    }

    const changed = await this.system.flush(batch, this._workingBee)

    if (this.system.promotions.changed) this._prefetchApprovals().catch(safetyCatch)

    if (local) {
      this._localSystemLength = this.system.bee.context.local.length - this._localSystemStart
      this._localViewLength = this._workingBee.context.local.length - this._localViewStart
      this._localFlushes = this.system.flushes
    }

    for (const { key, added } of changed) {
      if (added) await this.writers.add(key)
      else await this.writers.remove(key)
    }
  }

  _storeBoot() {
    const proms = []
    proms.push(
      this.local.setUserData(
        'autobee/previous-drain',
        encoding.encodePreviousDrain(this.previousDrain)
      )
    )

    const boot = this.system.bootRecord()
    if (boot) {
      proms.push(this.local.setUserData('autobee/head', encoding.encodeBootRecord(boot)))
    }

    return Promise.all(proms)
  }

  _storeMigratedHead() {
    const value = this._migratedHead ? encoding.encodeMigratedHead(this._migratedHead) : null
    return this.local.setUserData('autobee/migrated-head', value)
  }

  static decodeValue(buf, opts) {
    return encoding.decodeValue(buf, opts)
  }

  static encodeValue(value, opts) {
    return encoding.encodeValue(value, opts)
  }

  // gates on the state boot only, so hints can still feed a parked boot-from -
  // the trailing bump waits for bootAll like any other drain
  async wakeup({ key, length }) {
    if (!this._bootGuard.opened) await this._bootGuard.ready()
    await this.writers.wakeup(key, length)
    await this._bump(false)
  }

  async append(values, { optimistic = false } = {}) {
    if (this.closing) throw new Error('Autobee closed')

    this.stats.appends++

    if (!Array.isArray(values)) values = [values]

    if (!this._bootGuard.opened) await this._bootGuard.ready()

    if (!optimistic && this.writers.localWriter.isRemoved) {
      throw new Error('Not writable')
    }

    await this.local.ready()

    const links = this.system.getLinks(this.local.key)

    // never stamp before anything we link
    const t = Math.max(this._now(), this.system.timestamp)
    const batch = []

    const rec = await this.system.get(this.local.key)
    let witness = null
    if (rec) {
      const hint = await this.system.grantHint(this.local.key)
      if (hint && hint.weight > currentWeight(rec)) {
        witness = { weight: hint.weight, link: { key: hint.key, length: hint.length } }
      }
    }

    const approvals = optimistic ? null : await this._collectApprovals()

    for (let i = 0; i < values.length; i++) {
      const value = values[i]
      const buffer = typeof value === 'string' ? b4a.from(value) : value
      const lnk = i === 0 ? links : []
      const node = this.writers.appendLocal(
        buffer,
        t,
        { start: i, end: values.length - 1 - i },
        lnk,
        optimistic,
        i === 0 ? witness : null,
        i === 0 ? approvals : null
      )
      batch.push(node)
    }

    return this._bump(false)
  }

  async _flushLocal() {
    // pull any available local nodes in before flushing
    while (!this._interrupting && (await this._bumpPendingWriters({ local: true }))) {
      this._needsUpdate = true
    }

    await this.writers.flushLocal({
      flushes: this._localFlushes,
      system: {
        key: this.system.bee.context.local.key,
        start: this._localSystemStart,
        length: this._localSystemLength
      },
      view: {
        key: this._workingBee.context.local.key,
        start: this._localViewStart,
        length: this._localViewLength
      }
    })

    this._localSystemStart = this.system.bee.context.local.length
    this._localSystemLength = 0
    this._localFlushes = this.system.flushes
    this._localViewStart = this._workingBee.context.local.length
    this._localViewLength = 0
  }

  async moveTo(head) {
    if (!this._bootGuard.opened) await this._bootGuard.ready()
    if (this.closing) throw new Error('Autobee closed')

    const ff = await FastForward.fromHead(this, head, null, { force: true })
    if (ff === null) return null

    if (!(await this._runFastForward(ff))) return null

    return this.ff.promise
  }

  // legacy: ungated boot straight onto a system head
  async _bootFromSystem(system) {
    try {
      const ff = new FastForward(this, system, { timeout: FastForward.DEFAULT_TIMEOUT })
      return await this._runFastForward(ff)
    } catch (err) {
      safetyCatch(err)
      return false
    }
  }

  // the boot head seeds the view every trust decision here is made against,
  // since our own view is still empty
  async _bootFromHead(head, bootCondition, wait = false) {
    // just a head: one attempt, we do not wait around if it cannot be read
    if (bootCondition === null && !wait) {
      try {
        const ff = await FastForward.fromHead(this, head, null, {
          force: true,
          timeout: FastForward.DEFAULT_TIMEOUT
        })

        return ff !== null && (await this._runFastForward(ff))
      } catch (err) {
        safetyCatch(err)
        return false
      }
    }

    // park until the head can be read and, if a condition is set, something
    // acceptable turns up
    let opened = null

    try {
      while (!this._interrupting) {
        this._bootWait = rrp()

        try {
          if (bootCondition === null) {
            const ff = await FastForward.fromHead(this, head, null, {
              force: true,
              timeout: FastForward.DEFAULT_TIMEOUT
            })

            if (ff !== null && (await this._runFastForward(ff))) return true
          } else {
            if (opened === null) opened = await this._bootReference(head)

            if (opened !== null) {
              if (await this._bootAttempt(head, bootCondition, opened.view)) {
                return true
              }

              // the reference may be stale - resolve it afresh next attempt
              await opened.close()
              opened = null
            } else if (await this._bootLegacyHead(head)) {
              // boot legacy head through the migration pathway
              return true
            }
          }
        } catch (err) {
          safetyCatch(err)
        }

        if (this._interrupting) break

        await this._bootWait.promise
      }
    } finally {
      this._bootWait = null
      if (opened !== null) await opened.close()
    }

    return false
  }

  async _bootAttempt(head, bootCondition, reference) {
    // peek, so the hints are still there for the drain once we have booted
    const result = await this._filterHints(this._wakeup.hints)

    const candidates = await this._readCandidateHeads(result, FastForward.DEFAULT_TIMEOUT)

    const ff = await FastForward.fromHeads(this, [head, ...candidates], {
      force: true,
      timeout: FastForward.DEFAULT_TIMEOUT,
      condition: bootCondition,
      reference
    })

    return ff !== null && (await this._runFastForward(ff))
  }

  async _bootLegacyHead(head) {
    const oplog = await FastForward.flushHead(this, head, {
      timeout: FastForward.DEFAULT_TIMEOUT
    })
    if (oplog === null || oplog.op.version > LEGACY_OPLOG_VERSION) return false

    const ff = await FastForward.fromHead(this, head, null, {
      force: true,
      timeout: FastForward.DEFAULT_TIMEOUT
    })

    return ff !== null && (await this._runFastForward(ff))
  }

  async _bootReference(head) {
    const oplog = await FastForward.flushHead(this, head, {
      timeout: FastForward.DEFAULT_TIMEOUT
    })

    return oplog === null ? null : this.openViewAt(oplog)
  }

  async _runFastForward(ff) {
    if (this.fastForwardTo !== null || this.fastForwarding !== null) {
      await ff.close()
      return false
    }

    this.fastForwarding = ff

    const result = await ff.run()
    await ff.close()

    if (this.fastForwarding === ff) this.fastForwarding = null

    if (!result) return false

    this.fastForwardTo = result
    this.ff = rrp()

    this.bumpSoon()

    return true
  }

  async _applyFastForward() {
    const changes = this._hasUpdate ? new UpdateChanges(this) : null
    if (changes) changes.track()

    const { head, migrate } = this.fastForwardTo

    const from = this.system.bee.head()
    const to = head

    this.system.bee.move(head)
    await this.system.reset()

    // migrate is set when fast-forwarding from a legacy head
    if (migrate) {
      const view = (await this._handlers.migrate(migrate, head)) || EMPTY_HEAD

      this._migratedHead = {
        system: head,
        view: view.length ? view : (this._migratedHead && this._migratedHead.view) || null
      }

      await this._storeMigratedHead()
      this.bee.move(view)
      this._workingBee.move(view)
    } else {
      this.bee.move(this.system.view)
      this._workingBee.move(this.system.view)
    }

    this._approvalCheck = true
    this.fastForwardTo = null

    // process any wakeup while fast-forward itself was in flight
    await this._applyWakeupHints()
    await this.writers.refresh()

    // we moved, so ask our peers to tell us their heads again
    this._requestWakeup()

    await this._update(changes)
    await this._storeBoot()

    this.stats.fastForwards++
    this.emit('move-to', to, from)
    this.ff.resolve({ to, from })
  }

  replay() {
    return topo.replay(this)
  }
}

function isObject(o) {
  return typeof o === 'object' && o && !b4a.isBuffer(o)
}

function getBootOption(boot) {
  if (!boot) return null

  // oldest style, supported for now but will go away: a bare key is legacy
  if (boot.key) return { legacy: boot, head: null, bootCondition: null }

  asserts.assert(!(boot.head && boot.legacy), 'Boot from either a head or a legacy pointer')

  return boot
}

function noop() {}

function createAnchorCore(store, prologue, manifestData) {
  const manifest = {
    version: 2,
    hash: 'blake2b',
    prologue,
    allowPatch: false,
    quorum: 0,
    signers: [],
    userData: manifestData,
    linked: null
  }

  const core = store.get({
    manifest,
    active: false
  })

  return core
}

function crashSoon(err) {
  queueMicrotask(() => {
    throw err
  })
  throw err
}
const ID = require('hypercore-id-encoding')

class ApplyCalls {
  constructor(auto) {
    this.auto = auto
    this.applying = null
  }

  get id() {
    return this.auto.id
  }

  get key() {
    return this.auto.key
  }

  get discoveryKey() {
    return this.auto.discoveryKey
  }

  get name() {
    return this.auto.name
  }

  get clock() {
    return this.auto.system.flushes
  }

  get genesis() {
    return this.auto.system.isGenesis()
  }

  addWriter(key, { isIndexer = true, weight = isIndexer ? 2 : 1 } = {}) {
    if (typeof key === 'string') key = ID.decode(key)
    // no witness needed for genesis
    const isGenesis = !this.applying
    const coord = this.applying
      ? { key: this.applying[0].key, length: this.applying[0].length }
      : null
    const carrier = this.applying ? this.applying[0].weight : -1
    return this.auto.system.addWriter(key, { weight, isGenesis, coord, carrier })
  }

  ackWriter(key) {
    if (typeof key === 'string') key = ID.decode(key)
    return this.auto.system.ackWriter(key)
  }

  removeWriter(key) {
    if (typeof key === 'string') key = ID.decode(key)
    return this.auto.system.removeWriter(key)
  }

  preferFastForward() {
    // this.auto._preferFastForward()
  }

  interrupt(reason) {
    this.auto.interrupt(reason)
  }

  removeable(key) {
    return true
  }

  createAnchor(key, length) {
    return this.auto.createAnchor(key, length)
  }
}

module.exports = ApplyCalls
module.exports = class ApplyView {
  constructor(bee, auto) {
    this.bee = bee
    this.auto = auto
    this.view = ApplyView.open(bee, auto)
  }

  static open(bee, auto) {
    return auto._handlers.open ? auto._handlers.open(bee, auto) : bee
  }

  static close(view, auto) {
    if (!auto._handlers.close) return Promise.resolve()
    return auto._handlers.close(view)
  }

  apply(userBatch) {
    return this.auto._handlers.apply(userBatch, this.view, this.auto._host)
  }

  async close() {
    await ApplyView.close(this.view, this.auto)
    await this.bee.close()
  }
}
exports.heads = function (heads, node) {
  for (const h of this.heads) {
    if (h.key.equals(node.key)) {
      exports.bail('Same node included twice')
    }
  }
}

exports.systemFlush = function (w) {
  for (const op of w.ops) {
    if (op.applied && op.key[0] === 1) return
  }

  exports.bail('Invalid system batch')
}

exports.assert = function (cond, msg) {
  if (!cond) exports.bail(msg)
}

exports.bail = function (msg) {
  const err = new Error('ERR_ASSERTION: ' + msg)
  err.code = 'ERR_ASSERTION'
  throw err
}
const b4a = require('b4a')
const encoding = require('./encoding.js')
const { checkAutobaseMigration } = require('./migrations.js')

module.exports = async function boot(
  corestore,
  key,
  legacyViews,
  { encrypt, encryptionKey, keyPair, exclusive = true } = {}
) {
  const result = {
    key: null,
    local: null,
    bootstrap: null,
    encryptionKey: null,
    system: null,
    flushes: null,
    previousDrain: 0,
    migration: null
  }

  const manifest = keyPair
    ? { version: corestore.manifestVersion, signers: [{ publicKey: keyPair.publicKey }] }
    : null

  if (key) {
    result.key = key

    const bootstrap = corestore.get({ key })
    await bootstrap.ready()

    const localKey = await getLocalKey(bootstrap)

    if (keyPair) {
      result.local = corestore.get({
        keyPair,
        active: false,
        exclusive,
        manifest
      })
    } else {
      if (bootstrap.writable && !localKey) {
        result.local = bootstrap.session({
          active: false,
          exclusive
        })
      } else {
        const local = localKey
          ? corestore.get({
              key: localKey,
              active: false,
              exclusive
            })
          : corestore.get({
              name: 'local',
              active: false,
              exclusive
            })

        await local.ready()
        result.local = local
      }
    }

    result.bootstrap = bootstrap
  } else {
    result.local = keyPair
      ? corestore.get({
          keyPair,
          manifest,
          active: false,
          exclusive
        })
      : corestore.get({
          name: 'local',
          active: false,
          exclusive
        })
    await result.local.ready()

    const key = await result.local.getUserData('referrer')
    if (key) {
      result.key = key
      result.bootstrap = corestore.get({ key, active: false })
      await result.bootstrap.ready()
    } else {
      result.key = result.local.key
      result.bootstrap = result.local.session({ active: false })
      await result.bootstrap.setUserData('autobee/local', result.local.key)
    }
  }

  if (key || keyPair) {
    await result.bootstrap.setUserData('referrer', result.key)
    await result.bootstrap.setUserData('autobee/local', result.local.key)
    await result.local.setUserData('referrer', result.key)

    result.migration = await migrateWithFallback(
      corestore,
      result.local,
      result.bootstrap,
      legacyViews
    )

    if (result.migration) {
      await result.local.setUserData(
        'autobee/head',
        encoding.encodeBootRecord(result.migration.system)
      )
      await result.local.setUserData('autobee/encryption', result.migration.encryptionKey)
    }
  }

  const [systemHead, encryptionKeyBuffer, prevDrainBuffer] = await Promise.all([
    result.local.getUserData('autobee/head'),
    result.local.getUserData('autobee/encryption'),
    result.local.getUserData('autobee/previous-drain')
  ])

  if (result.system === null && systemHead) {
    result.system = encoding.decodeBootRecord(systemHead)
  }

  if (encryptionKeyBuffer) result.encryptionKey = encryptionKeyBuffer
  if (prevDrainBuffer) result.previousDrain = encoding.decodePreviousDrain(prevDrainBuffer)

  if (!result.encryptionKey && (encryptionKey || encrypt)) {
    if (!encryptionKey) {
      encryptionKey = (await corestore.createKeyPair('autobee/encryption')).secretKey.subarray(
        0,
        32
      )
    }

    await result.local.setUserData('autobee/encryption', encryptionKey)
    result.encryptionKey = encryptionKey
  }

  return result
}

// the bootstrap pointer names the rotated-to writer, whose legacy boot record
// can be unusable. the namespace local still carries the pre-rotation record,
// which points at the last system core that was actually signed
async function migrateWithFallback(corestore, local, bootstrap, legacyViews) {
  try {
    return await checkAutobaseMigration(corestore, local, bootstrap, legacyViews)
  } catch (err) {
    const prev = corestore.get({ name: 'local', active: false })
    await prev.ready()

    if (b4a.equals(prev.key, local.key)) throw err
    if (!(await prev.getUserData('autobase/boot'))) throw err

    return checkAutobaseMigration(corestore, prev, bootstrap, legacyViews)
  }
}

async function getLocalKey(bootstrap) {
  const [legacy, current] = await Promise.all([
    bootstrap.getUserData('autobase/local'),
    bootstrap.getUserData('autobee/local')
  ])

  if (current) return current
  return legacy
}
const LEGACY_OPLOG_VERSION = 2
const OPLOG_VERSION = 4
const LEGACY_AUTOBASE_VERSION = 2
const AUTOBEE_VERSION = 3
const DEFAULT_OP_TIMEOUT = 5000

module.exports = {
  LEGACY_OPLOG_VERSION,
  OPLOG_VERSION,
  LEGACY_AUTOBASE_VERSION,
  AUTOBEE_VERSION,
  DEFAULT_OP_TIMEOUT
}
const b4a = require('b4a')
const c = require('compact-encoding')
const crypto = require('hypercore-crypto')
const { AutobeeEncryption, WriterEncryption } = require('autobee-encryption')

const { getEncoding } = require('../encoding/spec/autobee')
const { LEGACY_OPLOG_VERSION, OPLOG_VERSION } = require('./constants')

const Oplog = getEncoding('@autobee/oplog')
const Link = getEncoding('@autobee/link')
const SystemInfo = getEncoding('@autobee/system-info')
const SystemWriter = getEncoding('@autobee/system-writer')
const ManifestData = getEncoding('@autobee/manifest-data')
const AutobaseBootRecord = getEncoding('@autobase-compat/boot-record')
const MigratedHead = getEncoding('@autobee/migrated-head')

exports.OPLOG_VERSION = OPLOG_VERSION

exports.Oplog = Oplog
exports.ManifestData = ManifestData

exports.encodeBootRecord = encodeBootRecord
exports.decodeBootRecord = decodeBootRecord

exports.encodeMigratedHead = encodeMigratedHead
exports.decodeMigratedHead = decodeMigratedHead

exports.encodePreviousDrain = encodePreviousDrain
exports.decodePreviousDrain = decodePreviousDrain

exports.decodeAutobaseBootRecord = decodeAutobaseBootRecord

exports.encodeSystemGrantKey = encodeSystemGrantKey
exports.encodeSystemGrantHint = encodeSystemGrantHint
exports.decodeSystemGrantHint = decodeSystemGrantHint
exports.encodeSystemPendingKey = encodeSystemPendingKey
exports.decodeSystemPendingKey = decodeSystemPendingKey
exports.encodeSystemPendingRange = encodeSystemPendingRange
exports.encodeSystemPending = encodeSystemPending
exports.decodeSystemPending = decodeSystemPending
exports.encodeSystemWriterKey = encodeSystemWriterKey
exports.encodeSystemWriter = encodeSystemWriter
exports.decodeSystemWriter = decodeSystemWriter

function encodeBootRecord(r) {
  return c.encode(Link, r)
}

function decodeBootRecord(value) {
  return c.decode(Link, value)
}

function encodeMigratedHead(m) {
  return c.encode(MigratedHead, m)
}

function decodeMigratedHead(value) {
  return c.decode(MigratedHead, value)
}

function encodePreviousDrain(d) {
  return c.encode(c.uint, d)
}

function decodePreviousDrain(value) {
  return c.decode(c.uint, value)
}

function decodeAutobaseBootRecord(value) {
  return c.decode(AutobaseBootRecord, value)
}

function encodeSystemWriterKey(key) {
  const buf = b4a.allocUnsafe(34)
  buf[0] = 1
  buf[1] = 0
  buf.set(key, 2)
  return buf
}

function encodeSystemPendingKey(key) {
  const buf = b4a.allocUnsafe(33)
  buf[0] = 5
  buf.set(key, 1)
  return buf
}

function decodeSystemPendingKey(buf) {
  return buf.subarray(1, 33)
}

function encodeSystemPendingRange() {
  return { gte: b4a.from([5]), lt: b4a.from([6]) }
}

function encodeSystemPending(entry) {
  return c.encode(c.uint, entry.weight)
}

function decodeSystemPending(value) {
  return { weight: c.decode(c.uint, value) }
}

function encodeSystemGrantKey(key) {
  const buf = b4a.allocUnsafe(33)
  buf[0] = 4
  buf.set(key, 1)
  return buf
}

function encodeSystemGrantHint(hint) {
  const state = { start: 0, end: 0, buffer: null }
  c.fixed32.preencode(state, hint.key)
  c.uint.preencode(state, hint.length)
  c.uint.preencode(state, hint.weight)
  state.buffer = b4a.alloc(state.end)
  c.fixed32.encode(state, hint.key)
  c.uint.encode(state, hint.length)
  c.uint.encode(state, hint.weight)
  return state.buffer
}

function decodeSystemGrantHint(value) {
  const state = { start: 0, end: value.byteLength, buffer: value }
  return {
    key: c.fixed32.decode(state),
    length: c.uint.decode(state),
    weight: c.uint.decode(state)
  }
}

function encodeSystemWriter(m) {
  return c.encode(SystemWriter, m)
}

function decodeSystemWriter(key, value) {
  return {
    // TODO: add dummy type to schema to preset key without extra alloc
    key: key.subarray(2),
    ...c.decode(SystemWriter, value)
  }
}

exports.encodeSystemInfo = encodeSystemInfo
exports.decodeSystemInfo = decodeSystemInfo

function encodeSystemInfo(m) {
  return c.encode(SystemInfo, m)
}

function decodeSystemInfo(buf) {
  return c.decode(SystemInfo, buf)
}

exports.encodeOplog = encodeOplog
exports.decodeOplog = decodeOplog
exports.decodeRawOplog = decodeRawOplog

function decodeRawOplog(buf) {
  return c.decode(Oplog, buf)
}

function decodeOplog(buf) {
  const m = c.decode(Oplog, buf)

  if (m.version <= LEGACY_OPLOG_VERSION) {
    return {
      version: m.version,
      timestamp: 0,
      links: m.node.heads,
      batch: { start: 0, end: m.node.batch - 1 },
      views: null,
      trusted: null,
      witness: null,
      approvals: null,
      optimistic: !!m.optimistic,
      value: m.node.value
    }
  }

  if (m.batch === null) m.batch = { start: 0, end: 0 }
  return m
}

function encodeOplog(m) {
  if (m.batch && m.batch.start === 0 && m.batch.end === 0) m.batch = null
  return c.encode(Oplog, m)
}

exports.encodeValue = encodeValue
exports.decodeValue = decodeValue

function createValue(value, opts = {}) {
  if (opts.legacy) {
    return {
      version: opts.version || LEGACY_OPLOG_VERSION,
      digest: null,
      checkpoint: null,
      optimistic: !!opts.optimistic,
      node: {
        heads: opts.heads || [],
        batch: 1,
        value
      }
    }
  }

  const {
    version = OPLOG_VERSION,
    optimistic = false,
    timestamp = Date.now(),
    links = [],
    views = null
  } = opts

  return {
    version,
    timestamp,
    links,
    batch: null,
    views,
    trusted: null,
    witness: null,
    approvals: null,
    optimistic,
    value
  }
}

function encodeValue(value, opts = {}) {
  const oplog = createValue(value, opts)

  const state = { start: 0, end: 0, buffer: null }

  Oplog.preencode(state, oplog)

  if (opts.padding) {
    state.start = opts.padding
    state.end += opts.padding
  }

  state.buffer = b4a.alloc(state.end)

  Oplog.encode(state, oplog)

  if (!opts.encrypted) return state.buffer

  if (!opts.optimistic) {
    throw new Error('Encoding an encrypted value is not supported')
  }

  const padding = b4a.alloc(16) // sodium.crypto_generichash_MINBYTES
  crypto.hash(state.buffer, padding)
  padding[0] = 0

  return b4a.concat([padding.subarray(0, AutobeeEncryption.PADDING), state.buffer])
}

async function decodeValue(buf, { encryptionKey, autobeeKey, key, manifest, index = 0 } = {}) {
  if (encryptionKey) {
    const w = new WriterEncryption({ encryptionKey, key: autobeeKey })

    await w.decrypt(index, buf, { key, manifest })
    buf = buf.subarray(AutobeeEncryption.PADDING)
  }

  return decodeOplog(buf).value
}
const safetyCatch = require('safety-catch')
const b4a = require('b4a')
const Hypercore = require('hypercore')

const System = require('./system.js')
const migrations = require('./migrations.js')
const { LEGACY_AUTOBASE_VERSION } = require('./constants.js')

const DEFAULT_OP_TIMEOUT = 5_000
const MIN_FF_GAP = 32

module.exports = class FastForward {
  constructor(auto, head, { timeout = DEFAULT_OP_TIMEOUT } = {}) {
    this.auto = auto

    this.activeRequests = []

    // 'reboot' is a storage namespace: renaming it would derive a different
    // scratch core on disk, so it stays as is
    this.system = new System(auto.store.namespace('reboot'), null, {
      getEncryptionProvider: this.auto.getSystemEncryption,
      encrypted: this.encrypted,
      activeRequests: this.activeRequests
    })

    // length -1 marks an uninitialised head: we know the key but not the length
    this.head = { key: head.key, length: head.length ?? 0 }
    this.timeout = timeout
    this.destroyed = false
    this.running = null
    this.failed = false
    this.cores = []
  }

  static DEFAULT_TIMEOUT = DEFAULT_OP_TIMEOUT

  static async fromHead(
    auto,
    head,
    trusted,
    { force = false, timeout = 0, condition = null, reference = null } = {}
  ) {
    const conservative = !force && auto._conservativeFF
    const timeoutOpts = timeout ? { timeout } : null

    const oplog = await FastForward.flushHead(auto, head, {
      conservative,
      ...timeoutOpts
    })
    if (!oplog) return null

    const verified = trusted ? await FastForward.flushHead(auto, trusted, timeoutOpts) : oplog
    if (!verified) return null

    // legacy nodes from non-indexers have no system info to fast-forward from
    if (!oplog.op.views || !verified.op.views) return null

    if (!force && verified.op.views.flushes - auto.system.flushes < MIN_FF_GAP) {
      return null
    }

    if (condition !== null && !(await FastForward.accepts(auto, verified, condition, reference))) {
      return null
    }

    return new FastForward(auto, batchToHead(verified.op.views.system))
  }

  static async fromHeads(
    auto,
    heads,
    { force = false, timeout = 0, condition = null, reference = null } = {}
  ) {
    if (reference === null) reference = auto._workingView.view

    const promises = []

    for (const head of heads) {
      if (head.length === 0) continue
      promises.push(FastForward.flushHead(auto, head, timeout ? { timeout } : null))
    }

    const ops = await Promise.all(promises)
    if (auto.fastForwarding || auto.fastForwardTo) return null

    const trust = await Promise.all(
      ops.map((res) => {
        return res === null ? false : auto.trusted.isTrusted(res.key, reference)
      })
    )
    if (auto.fastForwarding || auto.fastForwardTo) return null

    const candidates = []

    let bestTrusted = null
    let bestTrustedFlushes = -1

    for (let i = 0; i < ops.length; i++) {
      const res = ops[i]
      if (res === null || !res.op.views) continue
      if (!force && res.op.views.flushes - auto.system.flushes < MIN_FF_GAP) continue

      if (!trust[i]) {
        candidates.push(res)
        continue
      }

      if (res.op.views.flushes > bestTrustedFlushes) {
        bestTrustedFlushes = res.op.views.flushes
        bestTrusted = res
      }
    }

    if (bestTrusted !== null) {
      return FastForward.fromHead(auto, bestTrusted, null, {
        force,
        timeout,
        condition,
        reference
      })
    }

    candidates.sort((a, b) => b.op.views.flushes - a.op.views.flushes)

    for (const res of candidates) {
      const trusted = await FastForward.mostRecentTrusted(auto, res, reference)
      if (trusted === null) continue
      if (auto.fastForwarding || auto.fastForwardTo) return null

      const ff = await FastForward.fromHead(auto, res, trusted, {
        force,
        timeout,
        condition,
        reference
      })
      if (ff !== null) return ff
    }

    return null
  }

  static async flushHead(auto, head, opts) {
    const core = auto.openCore(head.key)

    try {
      const oplog = await auto.readOplog(core, head.length, opts)
      if (oplog === null) return null
      return oplog.op.views ? oplog : null
    } finally {
      await core.close()
    }
  }

  static async accepts(auto, oplog, condition, reference) {
    const opened = auto.openViewAt(oplog)
    if (opened === null) return false

    try {
      return !!(await condition(opened.view, reference))
    } finally {
      await opened.close()
    }
  }

  static async mostRecentTrusted(auto, head, reference) {
    const opened = auto.openViewAt(head)
    if (opened === null) return null

    try {
      return (await auto.trusted.mostRecentTrusted(opened.view, reference)) || null
    } finally {
      await opened.close()
    }
  }

  async run() {
    try {
      if (!this.running) this.running = this._run()

      return await this.running
    } catch (err) {
      safetyCatch(err)
      this.failed = true
      return null
    } finally {
      await this.close()
    }
  }

  async _run() {
    // an uninitialised head has an unknown length: wait for the core to catch up
    if (!this.head.length) {
      this.head.length = await this._resolveLength()
    }

    // zero-length boot is unsupported
    if (!this.head.length) return null

    await this.system.boot(this.head, { timeout: this.timeout })

    // a legacy system needs migrating rather than a plain fast-forward
    if (this.system.version <= LEGACY_AUTOBASE_VERSION) {
      const migrated = this.auto._migratedHead && this.auto._migratedHead.system
      if (
        migrated &&
        b4a.equals(migrated.key, this.head.key) &&
        migrated.length >= this.head.length
      ) {
        return null
      }

      const result = await this._migrate()
      if (result === null || result.autobee === null) return result

      // an indexer of the newest legacy generation already moved to autobee -
      // fast-forward straight onto its announced head instead of migrating,
      // falling back to the legacy migration if the head cannot be booted
      try {
        this.head = result.autobee
        await this.system.boot(this.head, { timeout: this.timeout })
        if (this.system.version <= LEGACY_AUTOBASE_VERSION) {
          throw new Error('Expected an autobee system')
        }
      } catch (err) {
        safetyCatch(err)
        this.head = result.head
        return { head: result.head, migrate: result.migrate }
      }

      // fall through to the plain fast-forward below
    }

    const promises = []

    // ensure local key is locally available always
    promises.push(this.system.get(this.auto.local.key, { timeout: this.timeout }))

    const view = this.auto.store.get({ key: this.system.view.key, active: true })
    this.cores.push(view)

    promises.push(
      view.get(this.system.view.length - 1, {
        timeout: this.timeout,
        activeRequests: this.activeRequests
      })
    )

    for (const head of this.system.heads) {
      promises.push(this.system.get(head.key, { timeout: this.timeout }))
    }

    await Promise.all(promises)
    if (this.destroyed) return null

    if (this.destroyed) return null

    return {
      head: this.head,
      migrate: null
    }
  }

  // Upgrade a legacy (pre-AUTOBEE_VERSION) system in place. The system already
  // booted above (coerced to the current struct), but the original v1/v2 struct
  // is needed to recover the indexers/views/entropy, so decode the raw block.
  //
  // A head may be arbitrarily old: an indexer rotation freezes the system core
  // and moves the indexers to a successor. The frozen core cannot reveal its
  // successor (the promotion only lands in its unsigned tail), but the
  // indexers' newest oplog digests stamp the newest system they know - chase
  // those so the migration lands on the newest generation instead of the head
  // we were given.
  async _migrate() {
    let head = { key: this.head.key, length: this.head.length }
    const seen = new Set()

    for (let hops = 0; hops < migrations.MAX_MIGRATE_HOPS; hops++) {
      seen.add(b4a.toString(head.key, 'hex'))

      const core = this.auto.store.get({ key: head.key, encryption: null })
      this.cores.push(core)
      await core.ready()

      // setup encryption
      await core.setEncryption(this.auto.getSystemEncryption())

      // successors are resolved by key only - wait for the replicated length
      if (!head.length) head.length = await migrations.coreLength(core, this.timeout)
      if (!head.length || this.destroyed) return null

      const info = await migrations.readLegacySystemInfo(core, head.length, {
        timeout: this.timeout,
        activeRequests: this.activeRequests
      })

      const { legacy, autobee } = await migrations.findNewestSystem(this, info)
      if (this.destroyed) return null

      if (legacy !== null && !seen.has(b4a.toString(legacy, 'hex'))) {
        head = { key: legacy, length: 0 }
        continue
      }

      this.head = head

      const views = await migrations.resolveLegacyViews(this, info)
      if (views === null || this.destroyed) return null

      return {
        head,
        migrate: views,
        autobee: autobee === null ? null : batchToHead(autobee.op.views.system)
      }
    }

    return null
  }

  async _resolveLength() {
    const core = this.auto.store.get({ key: this.head.key })
    this.cores.push(core)

    await core.ready()

    return migrations.coreLength(core, this.timeout)
  }

  // cancels every read in flight and fails any that come after
  clearRequests(err = null) {
    Hypercore.destroyRequests(this.activeRequests, err)
  }

  async close() {
    this.destroyed = true
    this.clearRequests()
    if (this.system) await this.system.close()
    for (const core of this.cores) await core.close()
  }
}

function batchToHead(b) {
  return {
    key: b.key,
    length: b.start + b.length
  }
}
const c = require('compact-encoding')
const b4a = require('b4a')
const crypto = require('hypercore-crypto')
const { AutobeeEncryption } = require('autobee-encryption')
const { decodeBlock } = require('hyperbee2/lib/encoding.js')

const encoding = require('./encoding.js')
const { assert, bail } = require('./asserts.js')
const { LEGACY_OPLOG_VERSION } = require('./constants.js')

const { getEncoding } = require('../encoding/spec/autobee')

const SystemInfoV1 = getEncoding('@autobase-compat/info-v1')
const SystemInfoV2 = getEncoding('@autobase-compat/info-v2')

const EMPTY = b4a.alloc(0)
const INDEX_VERSION = 1
const INFO = b4a.from([0x00, 0x00, 0x69, 0x6e, 0x66, 0x6f])
const [NS_SIGNER_NAMESPACE] = crypto.namespace('autobase', 1)

// indexer rotations chain legacy systems - how many generations we chase
const MAX_MIGRATE_HOPS = 64

module.exports = {
  MAX_MIGRATE_HOPS,
  checkAutobaseMigration,
  readLegacySystemInfo,
  findNewestSystem,
  resolveLegacyViews,
  inflateLegacyOplog,
  decodeLegacySystemInfo,
  deriveNamespace,
  coreLength
}

// boot-time migration of local legacy autobase storage
async function checkAutobaseMigration(store, local, bootstrap, legacyViews) {
  const bootRecord = await local.getUserData('autobase/boot')
  if (!bootRecord) return null

  const { key, systemLength } = encoding.decodeAutobaseBootRecord(bootRecord)
  const core = store.get({ key: key, encryption: null })
  await core.ready()

  // setup encryption
  const encryptionKey = await local.getUserData('autobase/encryption')
  await AutobeeEncryption.setSystemEncryption(bootstrap.key, encryptionKey, core)

  const length = await findLocalSystemLength(core, systemLength)
  const system = { key, length }

  const session = core.session({ name: 'batch' })
  await session.ready()

  let catchup

  try {
    catchup = await getCatchupHeads(session, length)
  } finally {
    await session.close()
  }

  const info = await readLegacySystemInfo(core, length, { wait: false })

  const nodes = await Promise.all(
    catchup.map((n) => getWriterBatch(store, n, bootstrap.key, encryptionKey))
  )

  const indexerManifests = await Promise.all(
    info.indexers.map((idx) => storeCoreManifest(store, idx.key))
  )
  const views = await Promise.all(info.views.map((v) => getPersistedView(store, v)))
  const entropy = legacyEntropy(info, indexerManifests)

  const getManifest = (key) => storeCoreManifest(store, key)

  const mappedViews = new Map()
  for (const name of legacyViews) {
    const v = await findViewByName(
      getManifest,
      bootstrap.key,
      encryptionKey,
      indexerManifests,
      views,
      entropy,
      name
    )

    mappedViews.set(name, v)
  }

  await core.close()

  return {
    encryptionKey,
    system,
    views: mappedViews,
    catchup: nodes
  }
}

// decode the legacy system info recorded in the system core's block at length
async function readLegacySystemInfo(core, length, opts = null) {
  const block = await core.get(length - 1, opts)
  assert(block !== null, 'Expected system block to exist locally')

  // Decode hyperbee block using hyperbee2 compat
  const node = decodeBlock(block)
  assert(node.keys.length > 0, 'bad system block')

  // Decode system info from hyperbee block using autobee compat
  return decodeLegacySystemInfo(node.keys[0].value)
}

// read each indexer's newest legacy oplog stamp - its digest/checkpoint mark
// the newest legacy system that indexer knows. take the majority vote. an
// indexer that already moved to autobee announces its live autobee state
// instead - keep the newest of those as a direct fast-forward candidate and
// bisect down to its buried legacy stamps so its vote still counts
async function findNewestSystem(ff, info) {
  const tally = new Map()

  let autobee = null

  for (const idx of info.indexers) {
    const core = ff.auto.openCore(idx.key)
    ff.cores.push(core)
    await core.ready()

    const length = await coreLength(core, ff.timeout)
    if (!length || ff.destroyed) continue

    let oplog = await flushHead(ff.auto, { key: idx.key, length }, { timeout: ff.timeout })
    if (oplog === null || !oplog.op.views) continue

    if (oplog.op.version > LEGACY_OPLOG_VERSION) {
      if (autobee === null || oplog.op.views.flushes > autobee.op.views.flushes) {
        autobee = oplog
      }

      const boundary = await findLegacyBoundary(ff, core, idx.length, oplog.length - 1)
      if (!boundary || ff.destroyed) continue

      oplog = await flushHead(ff.auto, { key: idx.key, length: boundary }, { timeout: ff.timeout })
      if (oplog === null || !oplog.op.views) continue
      if (oplog.op.version > LEGACY_OPLOG_VERSION) continue
    }

    const sys = oplog.op.views.system
    if (!sys || !sys.key) continue

    const hex = b4a.toString(sys.key, 'hex')
    const t = tally.get(hex) || { key: sys.key, votes: 0, length: sys.length }

    t.votes++
    if (sys.length > t.length) t.length = sys.length
    tally.set(hex, t)
  }

  const majority = (info.indexers.length >> 1) + 1

  let best = null
  for (const t of tally.values()) {
    if (t.votes >= majority && (best === null || t.length > best.length)) best = t
  }

  return { legacy: best === null ? null : best.key, autobee }
}

// map the legacy views recorded in the system info by name
async function resolveLegacyViews(ff, info) {
  const indexerManifests = await Promise.all(
    info.indexers.map((idx) => ffCoreManifest(ff, idx.key, idx.length))
  )
  if (ff.destroyed || indexerManifests.some((m) => m === null)) return null

  const entropy = legacyEntropy(info, indexerManifests)

  const getManifest = (key, length) => ffCoreManifest(ff, key, length)

  const views = new Map()

  await Promise.all(
    ff.auto.legacyViews.map(async (name) => {
      const v = await findViewByName(
        getManifest,
        ff.auto.key,
        ff.auto.encryptionKey,
        indexerManifests,
        info.views,
        entropy,
        name
      )

      if (v) views.set(name, v)
    })
  )

  if (ff.destroyed) return null

  return views
}

// legacy nodes always inflate, but only indexers carry the digest/checkpoint
// stamps that synthesise system info - callers must check op.views
async function inflateLegacyOplog(buf, core, seq, timeout) {
  const m = encoding.decodeRawOplog(buf)

  const op = {
    version: m.version,
    timestamp: 0,
    links: m.node.heads,
    batch: { start: 0, end: m.node.batch - 1 },
    views: null,
    optimistic: !!m.optimistic,
    value: m.node.value
  }

  if (m.digest === null || m.checkpoint === null || !m.checkpoint.system) return op

  const fetches = []

  fetches.push(m.digest.pointer ? core.get(seq - m.digest.pointer, { timeout }) : buf)
  fetches.push(
    m.checkpoint.system.checkpointer
      ? core.get(seq - m.checkpoint.system.checkpointer, { timeout })
      : buf
  )

  const [digestNode, checkpointNode] = await Promise.all(fetches)
  // no caller reads best-effort today, but stay total over a missing block
  if (digestNode === null || checkpointNode === null) return op

  const { digest } = encoding.decodeRawOplog(digestNode)
  const { checkpoint } = encoding.decodeRawOplog(checkpointNode)

  if (!checkpoint.system || !checkpoint.system.checkpoint) return op

  op.views = {
    system: {
      key: digest.key,
      start: 0,
      length: checkpoint.system.checkpoint.length
    },
    flushes: seq
  }

  return op
}

function decodeLegacySystemInfo(buffer) {
  const state = { start: 0, end: buffer.length, buffer }
  const version = c.uint.decode(state)

  state.start--

  switch (version) {
    case 1:
      return SystemInfoV1.decode(state)
    case 2:
      return SystemInfoV2.decode(state)
    default:
      bail('Expected legacy system info')
  }
}

function deriveNamespace(name, bootstrap, entropy, encryptionKey) {
  const encryptionId = crypto.hash(encryptionKey || EMPTY)
  const version = c.encode(c.uint, INDEX_VERSION)

  return crypto.hash([
    NS_SIGNER_NAMESPACE,
    version,
    bootstrap,
    encryptionId,
    entropy,
    b4a.from(name)
  ])
}

// the record's length can run past the signed core into the local batch
// session, and the signed core can carry verified-but-undownloaded blocks -
// scan back for the newest block the signed core actually has locally
async function findLocalSystemLength(core, systemLength) {
  let length = Math.min(core.length, systemLength)

  while (length > 0) {
    const seq = length - 1
    const node = await core.get(seq, { wait: false })

    if (node) {
      const blk = decodeBlock(node)
      const entry = blk.keys[0]
      if (b4a.equals(entry.key, INFO)) return length
    }

    length--
  }

  assert(false, 'Expected system block to exist locally')
}

function coreLength(core, timeout) {
  if (core.length) return core.length

  return new Promise((resolve) => {
    core.on('append', () => resolve(core.length))
    setTimeout(resolve, timeout, 0)
  })
}

// a writer's oplog is a legacy prefix followed by autobee-format nodes -
// bisect for the boundary. lo is a length known to be legacy (the legacy
// system's recorded length for this writer), hi a seq known to be autobee.
// returns the legacy prefix length
async function findLegacyBoundary(ff, core, lo, hi) {
  while (lo < hi) {
    const mid = (lo + hi) >>> 1

    const block = await core.get(mid, {
      timeout: ff.timeout,
      activeRequests: ff.activeRequests
    })

    if (encoding.decodeRawOplog(block).version > LEGACY_OPLOG_VERSION) hi = mid
    else lo = mid + 1
  }

  return lo
}

function legacyEntropy(info, indexerManifests) {
  return info.version > 1 && info.entropy ? info.entropy : indexerManifests[0].signers[0].namespace
}

async function findViewByName(
  getManifest,
  bootstrap,
  encryptionKey,
  indexerManifests,
  views,
  entropy,
  name
) {
  if (indexerManifests.length === 0) return null

  const namespace = deriveNamespace(name, bootstrap, entropy, encryptionKey)

  for (const v of views) {
    const manifest = await getManifest(v.key, v.length)
    if (!manifest) continue

    if (manifest.signers.length === 0) continue

    const signer = manifest.signers[0]

    if (b4a.equals(signer.namespace, namespace)) return v
  }

  return null
}

// request-tracked manifest read over replication
async function ffCoreManifest(ff, key, length) {
  const core = ff.auto.store.get(key)
  ff.cores.push(core)

  await core.ready()

  try {
    if (!core.manifest) {
      await core.get(length - 1, {
        timeout: ff.timeout,
        activeRequests: ff.activeRequests
      })
    }
  } catch {
    return null
  }

  return core.manifest
}

// local-storage manifest read
async function storeCoreManifest(store, key) {
  const core = store.get(key)
  await core.ready()

  const manifest = core.manifest
  await core.close()

  return manifest
}

async function getPersistedView(store, view) {
  const core = store.get(view.key)
  await core.ready()

  // core.length can run past what was downloaded - the system's recorded
  // length is the last one we know is local
  const length = Math.min(core.length, view.length)
  await core.close()

  return { key: view.key, length }
}

function getOplog(info, batch) {
  if (batch.length === 1) return batch[0].keys[0]

  const { heads } = encoding.decodeSystemInfo(info.value)

  for (const b of batch) {
    const entry = b.keys[0]
    const w = encoding.decodeSystemWriter(entry.key, entry.value)

    for (const { key } of heads) {
      if (b4a.equals(w.key, key)) return b.keys[0]
    }
  }

  throw new Error('Could not infer oplog node')
}

async function getCatchupHeads(session, from) {
  const seen = new Map()
  const nodes = []

  let batch = []

  // block 0 is the bee header, never a node
  for (let i = Math.max(from, 1); i < session.length; i++) {
    const node = await session.get(i, { wait: false })
    if (!node) throw new Error('Expect nodes to exist locally')

    const blk = decodeBlock(node)
    const entry = blk.keys[0]

    if (b4a.equals(entry.key, INFO)) {
      const oplog = getOplog(entry, batch)
      const { key, length } = encoding.decodeSystemWriter(oplog.key, oplog.value)

      batch = []

      if (!length) continue

      const id = b4a.toString(key, 'hex')
      const current = seen.get(id) || 0

      if (current >= length) continue

      seen.set(id, length)
      nodes.push({ key, length })
      continue
    }

    if (entry.key[0] === 0x01) batch.push(blk)
  }

  return nodes
}

async function getWriterBatch(store, head, key, encryptionKey, nodes = []) {
  const batch = []

  const core = store.get(head.key)
  await core.ready()

  if (encryptionKey) {
    await core.setEncryption(AutobeeEncryption.getWriterEncryption(key, encryptionKey))
  }

  let seq = head.length - 1
  const block = await core.get(seq--, { wait: false })
  if (!block) throw new Error('Expect writer node to exist locally')

  const node = encoding.decodeOplog(block)

  batch.unshift({ ...head, ...node, from: core })

  while (seq >= 0) {
    const block = await core.get(seq--, { wait: false })
    if (!block) break

    const node = encoding.decodeOplog(block)
    if (!node.batch.end) break

    batch.unshift({ key: head.key, length: seq + 2, from: core, ...node })
  }

  return batch
}

// lazy to avoid the require cycle - fast-forward imports this module
function flushHead(auto, head, opts) {
  const FastForward = require('./fast-forward.js')
  return FastForward.flushHead(auto, head, opts)
}
const b4a = require('b4a')
const encoding = require('./encoding.js')

// Owns the two promotion indexes: one hint per writer (the coordinate of the
// approval that anchored its capability - advisory, never verified against)
// and one pending entry per writer awaiting a stronger approval.
//
// An approval clears the entry as soon as it satisfies the request, so the
// index holds exactly the outstanding work. A partial approval leaves the
// entry alone: the request still wants more than the tier that answered could
// give.
//
// digest[w - 1] is true when some request needs an approver of standing w.
// The false is what makes a tier go quiet: once one tier-w peer has anchored
// w, the rest see their tier is finished instead of all offering in turn. A
// flag is enough because a satisfied request leaves the index entirely, so
// there is no "have I already seen this" watermark to keep.
module.exports = class Promotions {
  constructor(bee) {
    this.bee = bee
    this.digest = []
    this.changed = false
    this._pending = null
  }

  reset(info) {
    this.digest = info && info.pending ? info.pending : []
    this._pending = null
  }

  async hint(key, { activeRequests } = {}) {
    const node = await this.bee.get(encoding.encodeSystemGrantKey(key), { activeRequests })
    return node ? encoding.decodeSystemGrantHint(node.value) : null
  }

  async pending(key, { activeRequests } = {}) {
    if (this._pending !== null) {
      const entry = this._pending.get(b4a.toString(key, 'hex'))
      return entry ? entry.weight : 0
    }

    const node = await this.bee.get(encoding.encodeSystemPendingKey(key), { activeRequests })
    return node ? encoding.decodeSystemPending(node.value).weight : 0
  }

  // the level a writer has already anchored is exactly what its hint records,
  // so it is read from there rather than mirrored into the pending entry
  async anchored(key, { activeRequests } = {}) {
    const hint = await this.hint(key, { activeRequests })
    return hint ? hint.weight : 0
  }

  async *list({ activeRequests } = {}) {
    const pending = await this._load({ activeRequests })
    for (const [hex, entry] of pending) {
      yield { key: b4a.from(hex, 'hex'), weight: entry.weight }
    }
  }

  async updateHint(upd) {
    if (!upd.grant && !upd.isRemoved) return null

    const key = encoding.encodeSystemGrantKey(upd.key)
    const node = await this.bee.get(key)

    if (upd.isRemoved) return node ? { key, remove: true } : null

    const existing = node ? encoding.decodeSystemGrantHint(node.value) : null
    if (existing && existing.weight >= upd.grant.weight) return null

    return { key, value: encoding.encodeSystemGrantHint(upd.grant) }
  }

  // grouped and sequential on purpose: the entries are shared state, so
  // per-update parallelism would interleave at the awaits
  async flushPending(updates) {
    let relevant = false
    for (const upd of updates) {
      if (upd.pending !== -1 || upd.grant !== null || upd.isRemoved) {
        relevant = true
        break
      }
    }
    if (!relevant) return null

    const pending = await this._load()
    if (!pending.size && !updates.some((u) => u.pending !== -1)) return null

    const ops = []
    const anchoredBy = new Map()

    for (const upd of updates) {
      const hex = b4a.toString(upd.key, 'hex')
      const existing = pending.get(hex) || null

      if (upd.isRemoved) {
        if (existing) {
          pending.delete(hex)
          ops.push({ key: encoding.encodeSystemPendingKey(upd.key), remove: true })
        }
        continue
      }
      if (upd.pending === -1 && upd.grant === null) continue

      // the hint is still pre-write here, so this is the level already anchored
      // before whatever this update confers
      const prev = await this.anchored(upd.key)
      const anchored = upd.grant && upd.grant.weight > prev ? upd.grant.weight : prev
      anchoredBy.set(hex, anchored)

      const want = Math.max(existing ? existing.weight : 0, upd.pending)

      if (want > anchored) {
        if (existing && want === existing.weight) continue
        const entry = { weight: want }
        pending.set(hex, entry)
        ops.push({
          key: encoding.encodeSystemPendingKey(upd.key),
          value: encoding.encodeSystemPending(entry)
        })
        this.changed = true
      } else if (existing) {
        // satisfied - the approval covered everything the request asked for
        pending.delete(hex)
        ops.push({ key: encoding.encodeSystemPendingKey(upd.key), remove: true })
      }
    }

    // the flags depend on the anchored level, not just on which entries exist -
    // an approval that partially serves a request writes no entry at all, and
    // its tier still has to go quiet
    const open = new Set()
    for (const [hex, entry] of pending) {
      const anchored = anchoredBy.has(hex)
        ? anchoredBy.get(hex)
        : await this.anchored(b4a.from(hex, 'hex'))
      for (let w = anchored + 1; w <= entry.weight; w++) open.add(w)
    }

    let max = 0
    for (const w of open) if (w > max) max = w

    this.digest = []
    for (let w = 1; w <= max; w++) this.digest.push(open.has(w))

    return ops.length ? ops : null
  }

  async _load({ activeRequests } = {}) {
    if (this._pending !== null) return this._pending

    const pending = new Map()
    const range = encoding.encodeSystemPendingRange()
    for await (const data of this.bee.createReadStream({ ...range, activeRequests })) {
      pending.set(
        b4a.toString(encoding.decodeSystemPendingKey(data.key), 'hex'),
        encoding.decodeSystemPending(data.value)
      )
    }

    this._pending = pending
    return pending
  }
}
const Hyperbee = require('hyperbee2')
const Promotions = require('./promotions.js')
const b4a = require('b4a')

const { AUTOBEE_VERSION } = require('./constants')
const encoding = require('./encoding.js')
const topo = require('./topo.js')

const INFO_KEY = b4a.from([0])
const INFO_LEGACY_KEY = b4a.concat([b4a.from([0, 0]), b4a.from('info')])
const EMPTY_HEAD = { length: 0, key: null }
const EMPTY = b4a.from([])

class SystemSnapshot {
  constructor(bee) {
    this.bee = bee
  }

  async get(key, { timeout } = {}) {
    const node = await this.bee.get(encoding.encodeSystemWriterKey(key), { timeout })
    return node !== null ? encoding.decodeSystemWriter(node.key, node.value) : null
  }

  async has(link) {
    const node = await this.get(link.key)
    if (!node || node.length < link.length) return false
    return true
  }

  close() {
    return this.bee.close()
  }
}

module.exports = class Systembee {
  constructor(store, name, opts = {}) {
    this.name = name // for debuggin
    this.store = store
    this.bee = new Hyperbee(store, opts)
    this.view = null
    this.heads = []
    this.version = 0
    this.timestamp = 0
    this.flushes = 0
    this.updates = new Map()
    this.writers = new Map()
    this.encrypted = opts.encrypted === true
    this.promotions = new Promotions(this.bee)
  }

  async addWriter(
    key,
    { length = 0, weight = 1, isGenesis = false, coord = null, carrier = -1, anchor = false } = {}
  ) {
    const info = await this.get(key, { unflushed: true })
    if (length === 0) {
      length = info ? info.length : 0
    }

    const effective = coord && carrier < weight ? Math.max(carrier, 0) : weight
    const hint =
      anchor && coord ? { key: coord.key, length: coord.length, weight: effective } : null
    const pending = coord && !isGenesis ? weight : -1

    const current = info ? info.maxWeight : 0
    if (effective <= current) {
      // isAdded is false only when nothing has recorded the add yet - skipping
      // the update then leaves the writer undiscoverable, never announced
      const added = info && info.isAdded !== false
      if (added && !info.isRemoved && length <= info.length && !hint && pending === -1) return

      this.update(key, length, -1, -1, false, true, false, -1, hint, pending)
      return
    }

    const w = isGenesis ? effective : -1
    this.update(key, length, w, effective, isGenesis, true, false, -1, hint, pending)
  }

  async ackWriter(key, { length = 0 } = {}) {
    if (length === 0) {
      const info = await this.get(key, { unflushed: false })
      length = info ? info.length : 0
    }
    this.update(key, length, -1, -1, false, false, false, -1, null)
  }

  async removeWriter(key, { length = 0 } = {}) {
    if (length === 0) {
      const info = await this.get(key, { unflushed: true })
      length = info ? info.length : 0
    }
    this.update(key, length, -1, 0, false, false, true, -1, null)
  }

  addAnchor(key) {
    this.update(key, 0, -1, -1, false, true, false, -1, null)
  }

  isGenesis() {
    return this.bee.head() === null || this.bee.head().length === 0
  }

  isHead(key) {
    for (const head of this.heads) {
      if (b4a.equals(head.key, key)) return true
    }
    return false
  }

  async boot(view, { timeout } = {}) {
    await this.bee.ready()
    this.bee.move(view)
    await this.reset({ timeout })
  }

  async getInfo({ timeout } = {}) {
    const node = await this.bee.get(INFO_KEY, { timeout })
    if (node) return encoding.decodeSystemInfo(node.value)
    const legacy = await this.bee.get(INFO_LEGACY_KEY, { timeout })
    if (legacy) return encoding.decodeSystemInfo(legacy.value)
    return null
  }

  async reset({ timeout } = {}) {
    const info = await this.getInfo({ timeout })

    this.version = info ? info.version : 0
    this.view = info ? info.view : EMPTY_HEAD
    this.heads = info ? info.heads : []
    this.timestamp = info ? info.timestamp : 0
    this.flushes = info ? info.flushes : 0
    this.updates.clear()
    this.writers.clear()
    this.promotions.reset(info)

    if (this.version > AUTOBEE_VERSION) {
      throw new Error('Autobee signals newer version than locally supported')
    }
  }

  bootRecord() {
    const system = this.bee.head()
    return system.length ? system : null
  }

  async close() {
    await this.bee.close()
    await this.store.close()
  }

  async undo(head) {
    this.bee.move(head)

    await this.reset()

    return this.view || EMPTY_HEAD
  }

  getLinks(key) {
    const links = []
    for (const h of this.heads) {
      if (key && b4a.equals(h.key, key)) continue
      links.push(h)
    }
    return links
  }

  addNode(node) {
    for (let i = 0; i < this.heads.length; i++) {
      const h = this.heads[i]
      if (topo.isLinking(node, h)) {
        this.heads.splice(i--, 1)
      }
    }

    // Can enable if needed during debuggin
    // asserts.heads(heads, node)

    this.heads.push({ key: node.key, length: node.length })
    if (node.timestamp > this.timestamp) this.timestamp = node.timestamp // TODO: support smoothing

    // resolved sort weight, re-stamped on every reapplication - this is what
    // topo reads back out of the changes stream
    this.update(node.key, node.length, node.weight, -1, false, false, false, node.timestamp, null)
  }

  async canApply(key, optimistic) {
    const id = b4a.toString(key, 'hex')
    const w = this.writers.get(id)
    if (w) return w.added
    const info = await this.get(key)
    return info ? !info.isRemoved : !!optimistic
  }

  snapshot(head = null) {
    const bee =
      head === null
        ? this.bee.snapshot()
        : this.bee.checkout({ length: head.length, key: head.key })
    return new SystemSnapshot(bee)
  }

  async get(key, { unflushed = false, timeout, activeRequests } = {}) {
    const node = await this.bee.get(encoding.encodeSystemWriterKey(key), {
      timeout,
      activeRequests
    })
    const info = node !== null ? encoding.decodeSystemWriter(node.key, node.value) : null
    if (!unflushed) return info

    const upd = this.updates.get(b4a.toString(key, 'hex'))
    if (!upd) return info

    if (!info) {
      return {
        version: 4,
        key: upd.key,
        isRemoved: upd.isRemoved,
        isOplog: false,
        weight: upd.weight === -1 ? 0 : upd.weight,
        maxWeight: upd.maxWeight === -1 ? 0 : upd.maxWeight,
        isGenesis: upd.maxWeight !== -1 && !!upd.isGenesis,
        length: upd.length,
        clock: 0,
        isAdded: upd.isAdded
      }
    }

    if (upd.isAdded) info.isAdded = true
    if (upd.isRemoved) info.isRemoved = true
    if (upd.maxWeight !== -1) {
      info.isGenesis = upd.isGenesis
      info.maxWeight = upd.maxWeight
    }
    if (upd.weight !== -1) info.weight = upd.weight

    // length only ever advances
    if (upd.length > info.length) info.length = upd.length

    return info
  }

  async *list() {
    for await (const data of this.bee.createReadStream()) {
      if (data.key[0] === 1) {
        yield encoding.decodeSystemWriter(data.key, data.value)
      }
    }
  }

  async has(link) {
    // fast path
    for (let i = 0; i < this.heads.length; i++) {
      const h = this.heads[i]
      if (b4a.equals(h.key, link.key)) return h.length >= link.length
    }

    const node = await this.get(link.key)
    if (!node || node.length < link.length) return false
    return true
  }

  update(
    key,
    length,
    weight,
    maxWeight,
    isGenesis,
    isAdded,
    isRemoved,
    timestamp,
    grant,
    pending = -1
  ) {
    const id = b4a.toString(key, 'hex')

    if (isAdded) {
      this.writers.set(id, { key, added: true })
    } else if (isRemoved) {
      this.writers.set(id, { key, added: false })
    }

    let upd = this.updates.get(id)

    if (!upd) {
      upd = {
        key,
        length: 0,
        weight: -1,
        maxWeight: -1,
        isGenesis: false,
        isRemoved: false,
        isAdded: false,
        timestamp: 0,
        grant: null,
        pending: -1
      }
      this.updates.set(id, upd)
    }

    // resolved stamps are monotone per writer (resolve floors at prev)
    if (weight !== -1) upd.weight = upd.weight === -1 ? weight : Math.max(upd.weight, weight)
    if (maxWeight !== -1) {
      upd.maxWeight = maxWeight
      upd.isGenesis = isGenesis
    }
    if (isAdded) {
      upd.isAdded = true
      upd.isRemoved = false
    }
    if (isRemoved) {
      upd.isRemoved = true
      upd.isAdded = false
    }
    if (length > upd.length) {
      upd.length = length
    }

    if (timestamp > -1) {
      upd.timestamp = timestamp
    }
    if (pending > upd.pending) upd.pending = pending
    if (grant && (upd.grant === null || grant.weight > upd.grant.weight)) {
      upd.grant = grant
    }
  }

  async _updateWriter(upd, oplog) {
    const k = encoding.encodeSystemWriterKey(upd.key)

    const node = await this.bee.get(k)
    const v = node ? encoding.decodeSystemWriter(node.key, node.value) : null

    const prevWeight = v && v.weight !== undefined ? v.weight : 0
    const prevMaxWeight = v && v.maxWeight !== undefined ? v.maxWeight : 0
    const isOplog = b4a.equals(upd.key, oplog)

    const record = {
      version: 5,
      key: upd.key,
      isRemoved: upd.isRemoved ? true : upd.isAdded ? false : v ? v.isRemoved : false,
      isOplog,
      isGenesis: upd.maxWeight !== -1 ? !!upd.isGenesis : v ? v.isGenesis : false,
      weight: upd.weight !== -1 ? upd.weight : prevWeight,
      maxWeight: upd.maxWeight !== -1 ? upd.maxWeight : prevMaxWeight,
      length: Math.max(upd.length, v ? v.length : 0),
      timestamp: isOplog ? upd.timestamp : 0
    }

    return {
      key: k,
      value: encoding.encodeSystemWriter(record),
      add: null,
      remove: null,
      maxWeight: record.maxWeight
    }
  }

  async flush(batch, bee) {
    if (batch.length === 0) return []

    const oplog = batch[batch.length - 1].key

    const updates = [...this.updates.values()]
    const results = await Promise.all(updates.map((upd) => this._flushUpdate(upd, oplog)))
    const pendings = await this.promotions.flushPending(updates)

    const w = this.bee.write()

    for (const { writer } of results) {
      w.tryPut(writer.key, writer.value)
      if (writer.add) w.tryPut(writer.add, EMPTY)
      if (writer.remove) w.tryDelete(writer.remove)
    }

    for (const { grant } of results) {
      if (!grant) continue
      if (grant.remove) w.tryDelete(grant.key)
      else w.tryPut(grant.key, grant.value)
    }

    if (pendings) {
      for (const p of pendings) {
        if (p.remove) w.tryDelete(p.key)
        else w.tryPut(p.key, p.value)
      }
    }

    this.flushes++

    const info = {
      version: AUTOBEE_VERSION,
      view: bee.head(),
      heads: this.heads,
      timestamp: this.timestamp,
      flushes: this.flushes,
      indexers: null, // legacy
      pending: this.promotions.digest
    }

    w.tryPut(INFO_KEY, encoding.encodeSystemInfo(info))

    this.version = info.version
    this.view = info.view

    await w.flush()

    // Can enable if needed during debuggin
    // asserts.systemFlush(w)

    this.updates.clear()

    if (this.writers.size === 0) return []

    const changes = [...this.writers.values()]

    this.writers.clear()

    return changes
  }

  async _flushUpdate(upd, oplog) {
    const [writer, grant] = await Promise.all([
      this._updateWriter(upd, oplog),
      this.promotions.updateHint(upd)
    ])

    return { writer, grant }
  }

  pendingPromotion(key, opts) {
    return this.promotions.pending(key, opts)
  }

  listPendingPromotions(opts) {
    return this.promotions.list(opts)
  }

  grantHint(key, opts) {
    return this.promotions.hint(key, opts)
  }
}

function getInfo(data) {
  for (const { keys } of data.batch) {
    for (const k of keys) {
      const prefix = k.key[0]

      if (prefix === 0) {
        try {
          return encoding.decodeSystemInfo(k.value)
        } catch {
          return null
        }
      }
    }
  }

  return null
}
const b4a = require('b4a')
const encoding = require('./encoding.js')
const { createNode, finalizeNodeBatch } = require('./writers.js')
const asserts = require('./asserts.js')
const { LEGACY_OPLOG_VERSION } = require('./constants.js')

const EMPTY_HEAD = { length: 0, key: null }
const INFO_LEGACY_KEY = b4a.concat([b4a.from([0, 0]), b4a.from('info')])

const DEFAULT_OP_TIMEOUT = 5000

exports.sort = sort
exports.replay = replay
exports.getMostRecentHead = getMostRecentHead

exports.isLinking = isLinking
exports.isLinkingAll = isLinkingAll
exports.isLegacyNode = isLegacyNode

class Clock {
  constructor() {
    this.map = new Map()
  }

  add(link) {
    const hex = b4a.toString(link.key, 'hex')
    const p = this.map.get(hex)
    if (!p || p < link.length) this.map.set(hex, link.length)
  }

  has(link) {
    const hex = b4a.toString(link.key, 'hex')
    const p = this.map.get(hex)
    return p >= link.length
  }
}

async function getMostRecentHead(auto, system) {
  const ch = system.createChangesStream()
  for await (const data of ch) {
    const refs = getOplogCompat(data)
    if (!refs.length) continue

    const { oplog } = refs[0]
    return { key: oplog.key, length: oplog.length }
  }

  return null
}

// nodes we cannot resolve are returned as null rather than dropped, so callers
// can tell an incomplete replay from a short one
async function replay(auto) {
  const replay = []

  for await (const entry of oplogIterator(auto.system.bee)) {
    // the walk was cut short, nothing below this point is reachable
    if (entry === null) {
      replay.push(null)
      break
    }

    for (const ref of entry.refs) replay.push(ref)
  }

  // same reasoning as the iterator - never block on what we do not have
  const inflated = await inflate(auto, replay.reverse(), { wait: false })
  return inflated.flatMap((b) => (b === null ? [null] : b.batch))
}

async function sort(auto, batch) {
  const node = batch[0]
  const ch = auto.system.bee.createChangesStream()
  const reorder = []

  for await (const data of ch) {
    if (isLegacyNode(data)) break

    const ref = getOplog(data)
    const link = {
      key: ref.oplog.key,
      length: ref.oplog.length,
      // pinned resolved weight from this entry's own flush
      weight: ref.oplog.weight,
      timestamp: ref.oplog.timestamp
    }

    if (!isOrderedBefore(node, link)) break

    reorder.push(ref)
  }

  const inflated = await inflate(auto, reorder.reverse())

  // addSorted mutates inflated in place - capture original occupants first
  const undos = inflated.map((e) => e.undo)

  const { shared } = addSorted(inflated, { undo: null, batch, index: -1 })

  const tip = inflated.slice(shared).map((b) => b.batch)

  // rewind to the original occupant of the first changed position
  const undo = shared < undos.length ? undos[shared] : null

  return {
    undo,
    view: null,
    tip
  }
}

function addSorted(list, entry) {
  const node = entry.batch[0]
  const clock = new Clock()

  clock.add(node)
  for (const link of node.links) {
    clock.add(link)
  }

  if (node.witness) clock.add(node.witness.link)

  const tip = []
  const linked = []

  while (list.length) {
    // reached a stable sorting point
    if (cmp(node, list[list.length - 1].batch[0]) > 0) {
      break
    }

    const b = list.pop()
    const target = b.batch[0]

    // move past this node
    if (!clock.has(target)) {
      tip.push(b)
      continue
    }

    // keep entry identity (index/undo) for the shared-prefix scan below
    linked.push(b)

    // update the clock to catch linked nodes
    for (const link of target.links) {
      clock.add(link)
    }
  }

  while (linked.length) addSorted(list, linked.pop())
  list.push(entry)
  while (tip.length) list.push(tip.pop())

  let shared = 0
  while (shared < list.length) {
    if (list[shared].index === shared) {
      shared++
      continue
    }

    break
  }

  return { shared }
}

function isOrderedBefore(node, oplog) {
  return cmp(node, oplog) < 0
}

function cmp(a, b) {
  const w = a.weight - b.weight
  if (w) return w > 0 ? -1 : 1

  const t = a.timestamp - b.timestamp
  if (t) return t < 0 ? -1 : 1

  const c = b4a.compare(a.key, b.key)
  if (c) return c

  return a.length < b.length ? -1 : 1
}

async function inflate(auto, reorder, { wait = true, timeout = DEFAULT_OP_TIMEOUT } = {}) {
  const readOpts = wait ? { timeout } : { wait: false }

  const promises = []
  const sessions = new Map()

  for (let i = 0; i < reorder.length; i++) {
    const ref = reorder[i]

    // marker for a node we could not resolve, pass it through
    if (ref === null) {
      promises.push(null)
      continue
    }

    const id = b4a.toString(ref.oplog.key, 'hex')
    // only seeds the node - reapplication re-resolves from the witness
    const weight = ref.oplog.weight

    const local = auto.writers.localWriter
    if (local && local.id === id) {
      promises.push(getLocalBatch(ref.undo, local, ref.oplog.length, weight, i))
      continue
    }

    let core = sessions.get(id)
    if (!core) {
      core = auto.openCore(ref.oplog.key)
      sessions.set(id, core)
    }

    promises.push(getOplogBatch(ref.undo, core, ref.oplog.length, weight, i, readOpts))
  }

  try {
    return await Promise.all(promises)
  } finally {
    for (const core of sessions.values()) await core.close()
  }
}

async function getOplogBatch(undo, core, length, weight, index, readOpts = null) {
  const seq = length - 1

  const block = await core.get(seq, readOpts)
  if (block === null) return null

  const oplog = encoding.decodeOplog(block)
  const head = createNode(core, length, weight, oplog)
  const legacy = oplog.version <= LEGACY_OPLOG_VERSION

  // if this is legacy the start pointer is always 0, fix
  if (legacy) {
    for (let i = length - 1; i > 0; i--) {
      const block = await core.get(i - 1, readOpts)
      if (block === null) return null // wait: false in replay helper

      const oplog = encoding.decodeOplog(block)
      if (oplog.batch.end === 0) break
      head.batch.start++
    }
  }

  const batch = []
  const result = { undo, batch, index }

  const remaining = []
  const start = seq - head.batch.start
  const end = seq + head.batch.end // skip head, we have it

  for (let i = start; i < end; i++) {
    remaining.push(core.get(i, readOpts))
  }

  if (remaining.length) {
    const blocks = await Promise.all(remaining)
    for (let i = 0; i < blocks.length; i++) {
      // keep the hole so the gap in the batch stays visible
      if (blocks[i] === null) {
        batch.push(null)
        continue
      }

      const oplog = encoding.decodeOplog(blocks[i])
      batch.push(createNode(core, i + start + 1, weight, oplog))
    }
  }

  batch.push(head)
  finalizeNodeBatch(batch)

  return result
}

function getLocalBatch(undo, writer, length, weight, index) {
  if (writer.core.closed) throw new Error('Writer core is closed')

  if (length > writer.core.length) {
    if (!writer.pending) {
      throw new Error(
        `No local nodes: looking for ${length}, found ${writer.core.length}@${writer.core.id}`
      )
    }

    let i = 0
    while (i < writer.pending.length) {
      if (writer.pending[i].length === length) break
      i++
    }

    const head = writer.pending[i]
    const headBatch = head.batch || { start: 0, end: 0 }

    const start = i - headBatch.start
    const end = i + headBatch.end + 1 // include head

    const batch = writer.pending.slice(start, end)
    return { undo, batch, index }
  }

  return getOplogBatch(undo, writer.core, length, weight, index)
}

// tolerates legacy nodes instead of asserting
async function* oplogIterator(bee) {
  try {
    for await (const node of bee.createChangesStream({ wait: false })) {
      const legacy = isLegacyNode(node)
      const refs = getOplogCompat(node)
      if (refs.length) yield { node, refs, legacy }
    }
  } catch {
    yield null // signal data not available
  }
}

function getOplog(data) {
  for (const { keys } of data.batch) {
    if (!keys) continue

    for (const k of keys) {
      const prefix = k.key[0]

      if (prefix === 1) {
        // expect inlined for now
        const value = encoding.decodeSystemWriter(k.key, k.value)
        if (!value.isOplog) continue

        return { undo: data.tail || EMPTY_HEAD, oplog: value }
      }
    }
  }

  asserts.bail('Bad system node')
}

// legacy nodes carry no isOplog flag so we cannot tell which writer update is
// the oplog to fetch - yield every candidate until we have a better signal
function getOplogCompat(data) {
  const undo = data.tail || EMPTY_HEAD
  const oplogs = []
  const candidates = []

  for (const { keys } of data.batch) {
    for (const k of keys) {
      if (k.key[0] !== 1) continue

      let value = null
      try {
        value = encoding.decodeSystemWriter(k.key, k.value)
      } catch {
        continue
      }

      // writer was just added, nothing to fetch
      if (!value.length) continue

      if (value.isOplog) oplogs.push({ undo, oplog: value })
      else candidates.push({ undo, oplog: value })
    }
  }

  return oplogs.length ? oplogs : candidates
}

function isLinking(node, link) {
  if (node.length >= link.length && b4a.equals(node.key, link.key)) {
    return true
  }

  for (const l of node.links) {
    if (l.length >= link.length && b4a.equals(l.key, link.key)) {
      return true
    }
  }

  return false
}

function isLinkingAll(node, heads) {
  for (const h of heads) {
    if (!isLinking(node, h)) return false
  }

  return true
}

function isLegacyNode(data) {
  for (const { keys } of data.batch) {
    if (!keys) continue
    for (const k of keys) {
      if (b4a.equals(k.key, INFO_LEGACY_KEY)) return true
    }
  }
  return false
}
module.exports = class Triggers {
  constructor() {
    this.waiting = new Map()
  }

  add(id, length, writer) {
    let w = this.waiting.get(id)

    if (!w) {
      w = []
      this.waiting.set(id, w)
    }

    for (let i = 0; i < w.length; i++) {
      const entry = w[i]
      if (entry.writer !== writer) continue
      entry.length = length
      return entry
    }

    const entry = { index: w.length, id, writer, length }
    w.push(entry)
    return entry
  }

  remove(entry) {
    if (!entry.writer) return

    const w = this.waiting.get(entry.id)
    if (w) this._remove(w, entry)
  }

  has(id) {
    return this.waiting.get(id) !== undefined
  }

  get(id) {
    return this.waiting.get(id) || null
  }

  trigger(id, length) {
    const w = this.waiting.get(id)
    if (!w) return

    for (let i = w.length - 1; i >= 0; i--) {
      const entry = w[i]
      if (entry.length > length) continue

      const writer = entry.writer
      this._remove(w, entry)
      writer.bump()
    }
  }

  _remove(w, entry) {
    const head = w.pop()

    if (head !== entry) {
      w[entry.index] = head
      head.index = entry.index
    }

    entry.writer = null

    if (w.length === 0) {
      this.waiting.delete(entry.id)
    }
  }
}
const b4a = require('b4a')

class TrustedPeers {
  constructor(hooks) {
    this.trusted = new Map()
    this.pending = new Map()
    this.hooks = hooks
  }

  async isTrusted(key, reference) {
    const hex = b4a.toString(key, 'hex')
    if (this.trusted.get(hex)) return true

    let promise = this.pending.get(hex)
    if (promise) return promise

    promise = this.hooks.isTrusted
      ? this.hooks.isTrusted(key, reference)
      : Promise.resolve(!this.hooks.mostRecentTrusted)
    this.pending.set(hex, promise)

    const trusted = await promise
    if (trusted) this.trusted.set(hex, trusted)

    if (this.pending.get(hex) === promise) this.pending.delete(hex)
    return trusted
  }

  read(trusted) {
    return trusted || null
  }

  mostRecentTrusted(target, reference) {
    if (!this.hooks.mostRecentTrusted) return Promise.resolve(null)
    return this.hooks.mostRecentTrusted(target, reference)
  }

  clear() {
    this.pending.clear()
    this.trusted.clear()
  }
}

module.exports = TrustedPeers
module.exports = class UpdateChanges {
  constructor(auto) {
    this.auto = auto
    this.byName = new Map()
  }

  get(key) {
    return this.byName.get(key)
  }

  track() {
    this.tracking = {
      view: this.auto._workingBee,
      system: this.auto.system.bee
    }

    for (const k in this.tracking) {
      this.byName.set(k, {
        flushes: this.auto.system.flushes,
        from: this.tracking[k].head(),
        to: null,
        incremental: false
      })
    }
  }

  finalise() {
    // flush count at `to` - safe to read live, state is frozen for the hook
    const flushes = this.auto.system.flushes

    for (const k in this.tracking) {
      const tracked = this.byName.get(k)

      tracked.to = this.tracking[k].head()
      tracked.flushes = flushes
    }

    return this.byName
  }
}
const b4a = require('b4a')
const encoding = require('./encoding.js')

exports.currentWeight = currentWeight
exports.resolveWeight = resolveWeight
exports.verifyClaim = verifyClaim

function currentWeight(rec) {
  if (!rec) return 0
  if (rec.maxWeight > 0 && rec.isGenesis) return rec.maxWeight
  return rec.weight
}

function seedWeight(auto, node) {
  if (node.length === 1 && b4a.equals(node.key, auto.key)) return auto.bootstrapWeight
  return 0
}

async function resolveWeight(auto, node) {
  const rec = await auto.system.get(node.key)
  const prev = rec ? currentWeight(rec) : seedWeight(auto, node)

  const witness = node.witness
  if (!witness) return prev

  const { weight, link } = witness
  if (weight <= prev) return prev
  if (!(await verifyClaim(auto, node.key, weight, link))) return prev

  return weight
}

async function verifyClaim(auto, key, weight, link) {
  if (!link || !link.length) return false

  const anchor = await readOplogNode(auto, link.key, link.length)
  const entry = anchor.approvals ? anchor.approvals.find((a) => b4a.equals(a.key, key)) : null
  if (!entry || entry.weight < weight) return false

  return approverQualified(auto, link.key, entry.weight)
}

// the approval sits in the causal past of any citation of it, so the approver's
// record is lower-bounded by the standing it held when it approved - no need to
// walk its own witness chain back to genesis
async function approverQualified(auto, key, weight) {
  if (b4a.equals(key, auto.key)) return auto.bootstrapWeight >= weight

  const rec = await auto.system.get(key)
  return currentWeight(rec) >= weight
}

async function readOplogNode(auto, key, length) {
  const core = auto.openCore(key)

  try {
    await core.ready()
    const block = await core.get(length - 1)
    return encoding.decodeOplog(block)
  } finally {
    await core.close()
  }
}
const b4a = require('b4a')
const encoding = require('./encoding.js')
const Triggers = require('./triggers.js')
const { LEGACY_OPLOG_VERSION } = require('./constants.js')
const { resolveWeight } = require('./witness.js')
const { WriterEncryption } = require('autobee-encryption')

const WRITER_PREFETCH = 10

const BATCH_WRITER_SANITY = 1024
const BATCH_OPTIMISTIC_SANITY = 32

const MAX_FORWARD_TIME_DRIFT = 2 * 3600 * 1000

class ActiveWriters {
  constructor(auto) {
    const local = auto.local

    this.auto = auto
    this.active = new Map()
    this.triggers = new Triggers()
    this.localWriter = null
    this.writable = false
    this.pending = []

    this._pendingBuffer = new Map()
    this._adding = new Map()

    this.localWriter = new Writer(this, auto.system, true, local, b4a.toString(local.key, 'hex'))
    this.localWriter.attach()
  }

  get size() {
    return this.active.size
  }

  [Symbol.iterator]() {
    return this.active.values()
  }

  async close() {
    const closing = [this.localWriter.close()]
    for (const w of this.active.values()) closing.push(w.close())
    this.active.clear()
    this.pending.length = 0
    await Promise.allSettled(closing)
  }

  *external() {
    for (const w of this.active.values()) {
      if (w !== this.localWriter) yield w
    }
  }

  bump() {
    this.auto.bumpSoon()
  }

  async wakeup(key, length) {
    const id = b4a.toString(key, 'hex')
    let w = this.active.get(id)

    if (w === this.localWriter) return

    let added = false
    if (!w) {
      w = await this.add(key)
      if (!w) return
      added = true
    }

    const advanced = length > w.hintedLength

    // remember the announced length so gc doesn't drop a writer we haven't caught up on
    w.hint(length)

    // prefetch the oplog head for the fast-forward that runs right after us
    if (w && length > 0) w.core.get(length - 1).catch(noop)

    if (added || advanced) this.bump()
  }

  getLatestLocalOplog() {
    return this.localWriter.getLatest()
  }

  appendLocal(value, timestamp, batch, links, optimistic, witness, approvals = null) {
    return this.localWriter.append(value, timestamp, batch, links, optimistic, witness, approvals)
  }

  async rotateLocalWriter(core) {
    this.clearLocal()
    await this.localWriter.detachAndClose()

    this.localWriter = new Writer(this, this.auto.system, true, core, b4a.toString(core.key, 'hex'))
    this.localWriter.addPending()
    this.auto.emit('writer', this.localWriter)

    await this.updateLocalState()
  }

  clearLocal() {
    return this.localWriter.clear()
  }

  async flushLocal(views) {
    const w = this.localWriter

    // only consume the queue when a flush will actually write
    if (w.pending === null || w.processed === 0) return

    const trusted = await this.auto.trusted.mostRecentTrusted(this.auto._workingView.view, null)

    return w.flush(views, trusted)
  }

  async refresh() {
    // peeked batches were read against the pre-reboot system, drop them
    this._pendingBuffer.clear()

    if (this.localWriter && !this.localWriter.isClosed) {
      await this.updateLocalState()
    }

    for (const writer of this.active.values()) {
      if (writer.isFrozen && writer !== this.localWriter) await writer.detachAndClose()
      else await writer.reset()
    }
  }

  async updateLocalState() {
    await this.localWriter.update()
  }

  async nextPendingNode({ local = false } = {}) {
    for (const w of this.pending.slice()) {
      if (local && w !== this.localWriter) continue
      if (this._pendingBuffer.has(w) || w.isFrozen) continue

      const batch = await w.next()
      if (batch !== null) this._pendingBuffer.set(w, batch)
    }

    if (this._pendingBuffer.size === 0) return null

    let writer = null
    let best = null

    for (const [w, batch] of this._pendingBuffer) {
      if (local && w !== this.localWriter) continue
      const head = batch[0]
      // -1 marks a node next() has not resolved a sort weight for yet
      if (head.weight < 0) head.weight = await resolveWeight(this.auto, head)
      if (best === null || sortsBefore(head, best[0])) {
        writer = w
        best = batch
      }
    }

    if (writer === null) return null

    this._pendingBuffer.delete(writer)
    return { writer, batch: best }
  }

  _updateLocalState() {
    const w = this.localWriter

    const writable = w.isAdded && !w.isRemoved
    if (writable === this.writable) return

    this.writable = writable
    this.auto.emit(this.writable ? 'writable' : 'unwritable')
    this.auto.emit('update')
  }

  has(id) {
    return !!this.active.get(id)
  }

  add(key) {
    const id = b4a.toString(key, 'hex')

    let adding = this._adding.get(id)
    if (adding) return adding

    adding = this._add(key, id)
    this._adding.set(id, adding)

    return adding.finally(() => {
      this._adding.delete(id)
    })
  }

  // TODO: correct fix is writer as ReadyResource
  async _add(key, id) {
    const local = b4a.equals(key, this.auto.local.key)
    if (local) await this.updateLocalState()

    let w = this.active.get(id)
    if (w) return w

    // no new sessions once teardown started
    if (this.auto._interrupting) return null

    const encryption = this.auto.encryptionKey ? new WriterEncryption(this.auto) : null

    const core = this.auto.store.get({
      active: false,
      key,
      encryption,
      group: local || !this.auto.wakeupCapability ? null : this.auto.wakeupCapability.discoveryKey
    })

    await core.ready()
    await core.setUserData('referrer', this.auto.key)

    // teardown may have started underneath us
    if (this.auto._interrupting) {
      await core.close()
      return null
    }

    w = new Writer(this, this.auto.system, false, core, id)
    w.attach()

    return w
  }

  async remove(key) {
    if (b4a.equals(key, this.auto.local.key)) await this.updateLocalState()

    const id = b4a.toString(key, 'hex')
    const w = this.active.get(id)
    if (!w) return

    // nodes this writer appended before the removal may still sort before it,
    // and their ordering advances its record everywhere - closing the session
    // while we are behind strands us one node short forever. reset() closes it
    // once we have caught up
    if (w !== this.localWriter) {
      const length = await w.update()
      if (length < w.core.length || length < w.hintedLength) {
        w.addPending()
        return
      }
    }

    w.detach()
    w.decouple()
    if (w !== this.localWriter) await w.close()
  }
}

class Writer {
  constructor(writers, system, local, core, id) {
    this.writers = writers
    this.system = system

    this.core = core
    this.id = id
    this.index = 0
    this.weight = 0
    this.pending = null
    this.processed = 0
    this.appendLength = this.core.length + 1
    this.waiting = null
    this.download = null
    this.hintedLength = 0

    this.isPending = false
    this.isClosed = false
    this.isAttached = false
    this.isAdded = false
    this.isRemoved = false
    this.isCoupled = false
    this.isFrozen = false
    this.isHead = false

    this._onchangeBound = this._onchange.bind(this)

    if (!local) this.core.on('append', this._onchangeBound)
    if (!local) this.core.on('download', this._onchangeBound)
  }

  get isIndexer() {
    return this.weight >= 2
  }

  isSystemHead() {
    if (this.core.length === 0) return false

    if (this.system.heads.length === 0) {
      return b4a.equals(this.core.key, this.writers.auto.key)
    }

    return this.system.isHead(this.core.key)
  }

  couple() {
    if (this.isCoupled) return
    // if we don't have coupler yet we can't add
    if (this.writers.auto._wakeup.addCore(this.core)) {
      this.isCoupled = true
    }
  }

  decouple() {
    if (!this.isCoupled) return
    // if we don't have coupler yet we can't remove
    if (this.writers.auto._wakeup.removeCore(this.core)) {
      this.isCoupled = false
    }
  }

  hasReferrals() {
    const entries = this.writers.triggers.get(this.id)
    if (!entries) return false
    // TODO: should implement a system where we KNOW that a future writers IS not removed
    // to avoid accidental spam, for now kept simple as the algo still runs
    return true
  }

  addPending() {
    if (this.isPending) return
    this.isPending = true
    this.index = this.writers.pending.push(this) - 1
  }

  removePending() {
    if (!this.isPending) return
    this.isPending = false

    this.writers._pendingBuffer.delete(this)

    const head = this.writers.pending.pop()
    if (head !== this) {
      this.writers.pending[this.index] = head
      head.index = this.index
    }
  }

  // a peer announced this length for us, so we're not caught up until we've seen it
  hint(length) {
    if (length > this.hintedLength) {
      this.hintedLength = length
      this.core.setActive(true)
    }
  }

  _onchange() {
    this.hint(this.core.length)
    if (this.waiting === null) this.bump()
  }

  bump() {
    this.waiting = null
    if (this.isAttached) this.addPending() // TODO: remove the isAttach guard
    this.writers.bump()
  }

  attach() {
    if (this.isAttached) return
    this.isAttached = true
    this.writers.active.set(this.id, this)
    this.writers.auto.emit('writer', this)
    this.addPending()
  }

  detach() {
    if (!this.isAttached) return
    this.isAttached = false
    if (this.download) this.download.destroy()
    this.download = null
    this.writers.active.delete(this.id)
    this.removePending()
  }

  async reset() {
    this.removePending()

    if (this.waiting) {
      this.writers.triggers.remove(this.waiting)
      this.waiting = null
    }

    const length = await this.update()

    // don't drop any local nodes left in buffer, or work a peer told us about
    if (length < this.core.length || length < this.hintedLength || this.appending) {
      this.addPending()
    } else if (this !== this.writers.localWriter && !this.isHead) {
      await this.detachAndClose()
    }
  }

  close() {
    this.isClosed = true

    if (this.waiting) {
      this.writers.triggers.remove(this.waiting)
      this.waiting = null
    }

    this.removePending()

    return this.core.close()
  }

  detachAndClose() {
    this.detach()
    this.decouple()
    return this.close()
  }

  async views() {
    const latest = await this.getLatest()
    if (!latest || !latest.views) return []

    // signedLength for autobase compat
    const system = latest.views.system.start + latest.views.system.length
    const view = latest.views.view.start + latest.views.view.length

    return [
      { key: latest.views.system.key, length: system, signedLength: system },
      { key: latest.views.view.key, length: view, signedLength: view }
    ]
  }

  async getLatest() {
    if (this.core.length === 0) return null
    const block = await this.core.get(this.core.length - 1)
    const oplog = encoding.decodeOplog(block)
    return oplog
  }

  async update() {
    const info = await this.system.get(this.core.key)
    const length = info ? info.length : 0

    const isHead = this.isSystemHead()

    if (isHead) {
      this.isHead = true
      this.couple()
    } else {
      this.isHead = false
      this.decouple()
    }

    const active = length < this.core.length || length < this.hintedLength

    if (this.isHead || active) {
      this.core.setActive(true)
    } else {
      this.core.setActive(false)
    }

    if (!info) {
      this.isAdded = b4a.equals(this.core.key, this.writers.auto.key)
    } else {
      this.isAdded = true
      this.isRemoved = info.isRemoved
      // capability (isIndexer etc), NOT the resolved sort weight
      this.weight = info.maxWeight
    }

    if (this === this.writers.localWriter) {
      this.writers._updateLocalState()
    }

    return length
  }

  next() {
    if (this.isFrozen) return null
    return this._next()
  }

  async _next() {
    if (this.waiting !== null) return null

    const batch = []
    const genesis = this.system.isGenesis()

    let length = await this.update()
    let optimistic = false
    let timestamp = 0

    if (this.download) this.download.destroy()
    this.download = this.core.download({ start: length, end: length + WRITER_PREFETCH })

    while (length < this.core.length) {
      // @todo aim to prefetch when writer added, with read-ahead
      // download the block if it's not available locally
      // writer will be added back as pending automatically
      if (!(await this.core.has(length))) {
        this.removePending()
        return null
      }

      let oplog
      try {
        const block = await this.core.get(length++)
        oplog = encoding.decodeOplog(block)
      } catch {
        this.isFrozen = true
        this.removePending()
        return null
      }

      const node = createNode(this.core, length, -1, oplog)

      if (this.writers.writable && this === this.writers.localWriter) {
        node.optimistic = false
      }

      const b = oplog.batch

      // auto correct some batch invariants
      node.timestamp = timestamp = Math.max(node.timestamp, timestamp)
      node.optimistic = optimistic = optimistic || node.optimistic

      if (!this.isAdded && !node.optimistic && !genesis) {
        return null
      }

      // an optimistic writer cannot be genesis
      if (node.optimistic && genesis) {
        return null
      }

      if (node.optimistic && !this.isAdded && b.end >= BATCH_OPTIMISTIC_SANITY) {
        return null
      }

      if (node.timestamp - Date.now() > MAX_FORWARD_TIME_DRIFT) {
        return null
      }

      if (!node.optimistic && b.start === 0 && b.end > 1 && b.end < BATCH_WRITER_SANITY) {
        this.core.download({ start: length, end: length + oplog.batch.end })
      }

      if (!(await this._isLinked(node))) {
        this.removePending()
        return null
      }

      batch.push(node)
      if (b.end === 0) return finalizeNodeBatch(batch)

      // TODO: mark as dead
      if (batch.length >= BATCH_WRITER_SANITY) return null
    }

    // handle local writer
    if (this.core.writable && this.pending !== null) {
      let start = this.processed
      const end = this.pending.length

      while (start < end && this.pending[start].length <= length) {
        this.processed = ++start
      }

      if (start < end && !this.writers.writable) {
        const node = this.pending[start]
        if (!node.optimistic) {
          this.writers.clearLocal()
          throw new Error('Not writable')
        }
      }

      for (let i = start; i < end; i++) {
        const node = this.pending[i]

        // an optimistic writer cannot be genesis
        if (node.optimistic && genesis) break
        if (node.optimistic && !this.isAdded && this.pending.length >= BATCH_OPTIMISTIC_SANITY) {
          break
        }
        if (node.timestamp - Date.now() > MAX_FORWARD_TIME_DRIFT) break

        if (!(await this._isLinked(node))) break

        this.processed = i + 1

        if (node.batch.end === 0) break
      }

      if (this.processed === this.pending.length) {
        this.removePending()
      }

      if (start === this.processed) return null
      return this.pending.slice(start, this.processed)
    }

    return null
  }

  notify(batch) {
    this.writers.triggers.trigger(this.id, batch[batch.length - 1].length)
  }

  async _isLinked(node) {
    let links = node.links
    if (node.witness) {
      links = links.slice()
      links.push({ key: node.witness.link.key, length: node.witness.link.length })
    }

    const promises = new Array(links.length)

    for (let i = 0; i < links.length; i++) {
      const link = links[i]

      // legacy batch nodes link their own previous node, satisfied by oplog order
      if (link.length < node.length && b4a.equals(link.key, this.core.key)) {
        promises[i] = true
        continue
      }

      promises[i] = this.system.has(link)
    }

    const has = await Promise.all(promises)

    for (let i = 0; i < links.length; i++) {
      if (has[i]) continue
      const link = links[i]
      const id = b4a.toString(link.key, 'hex')

      this.waiting = this.writers.triggers.add(id, link.length, this)

      // check it didn't arrive underneath us
      if (await this.system.has(link)) {
        this.writers.triggers.remove(this.waiting)
        this.waiting = null
        continue
      }

      this.writers.wakeup(link.key, link.length).catch(noop)
      return false
    }

    return true
  }

  get appending() {
    return this.pending !== null
  }

  append(value, timestamp, batch, links, optimistic, witness, approvals = null) {
    if (this.pending === null) this.pending = []

    const oplog = {
      version: encoding.OPLOG_VERSION,
      timestamp,
      links,
      batch,
      views: null,
      trusted: null,
      witness: witness || null,
      approvals,
      optimistic: optimistic && !this.writers.writable,
      value
    }

    const node = createNode(this.core, this.appendLength++, -1, oplog)

    this.pending.push(node)
    this.addPending()

    return node
  }

  clear() {
    if (!this.pending) return

    if (this.processed === this.pending.length) {
      this.pending = null
    } else {
      this.pending = this.pending.slice(this.processed)
    }

    this.processed = 0
  }

  async flush(views, trusted) {
    if (this.pending === null || this.processed === 0) return

    const head = this.pending[this.processed - 1]
    head.views = views

    head.trusted = trusted ? [{ key: trusted.key, length: trusted.length }] : null
    this.writers.auto.emit('appending', this.pending)

    const buffers = []
    for (let i = 0; i < this.processed; i++) {
      buffers.push(encoding.encodeOplog(this.pending[i]))
    }

    try {
      await this.core.append(buffers)
    } finally {
      this.clear()
    }
  }
}

// mirrors topo.cmp - kept local because topo.js requires this module
function sortsBefore(a, b) {
  if (a.weight !== b.weight) return a.weight > b.weight
  if (a.timestamp !== b.timestamp) return a.timestamp < b.timestamp

  const c = b4a.compare(a.key, b.key)
  if (c !== 0) return c < 0

  return a.length < b.length
}

exports.ActiveWriters = ActiveWriters
exports.Writer = Writer
exports.createNode = createNode
exports.finalizeNodeBatch = finalizeNodeBatch

function createNode(core, length, weight, oplog) {
  return {
    core,
    from: core, // compat
    key: core.key,
    length,
    weight,
    version: oplog.version,
    timestamp: oplog.timestamp,
    links: oplog.links,
    batch: oplog.batch,
    views: oplog.views,
    trusted: oplog.trusted || [],
    witness: oplog.witness || null,
    approvals: oplog.approvals || null,
    optimistic: oplog.optimistic,
    value: oplog.value
  }
}

function finalizeNodeBatch(batch) {
  // replay batches keep unreachable blocks as null holes (topo.getOplogBatch)
  let first = null
  for (const node of batch) {
    if (node !== null) {
      first = node
      break
    }
  }
  if (first === null || first.version > LEGACY_OPLOG_VERSION) return batch

  for (let i = 0; i < batch.length; i++) {
    if (batch[i] !== null) batch[i].batch.start = i
  }

  return batch
}

function noop() {}
{
  "name": "autobee",
  "version": "2.1.1",
  "description": "Bee based autobase",
  "license": "Apache-2.0",
  "author": "Holepunch Inc.",
  "main": "index.js",
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/autobee.git"
  },
  "files": [
    "index.js",
    "lib/*.js",
    "encoding"
  ],
  "scripts": {
    "format": "prettier . --write",
    "lint": "lunte && prettier . --check",
    "test": "node test/all.js",
    "test:bare": "bare test/all.js",
    "test:encrypted": "bare test/all.js --encrypt-all",
    "test:generate": "brittle -r test/all.js test/*.js"
  },
  "dependencies": {
    "autobee-encryption": "^0.1.2",
    "autobee-wakeup": "^1.1.3",
    "b4a": "^1.8.0",
    "compact-encoding": "^3.3.0",
    "hyperbee2": "^2.10.3",
    "hypercore": "^11.33.2",
    "hypercore-id-encoding": "^1.3.0",
    "hyperschema": "^1.20.1",
    "ready-guard": "^1.0.0",
    "ready-resource": "^1.2.0",
    "resolve-reject-promise": "^1.1.0",
    "safety-catch": "^1.0.3"
  },
  "devDependencies": {
    "brittle": "^4.1.0",
    "corestore": "^7.12.2",
    "hypercore-crypto": "^3.6.1",
    "lunte": "^1.6.0",
    "prettier": "^3.8.1",
    "prettier-config-holepunch": "^2.0.0",
    "protomux-wakeup": "^2.9.0",
    "replication-simulator": "^3.0.0"
  }
}
function isBuffer(value) {
  return Buffer.isBuffer(value) || value instanceof Uint8Array
}

function isEncoding(encoding) {
  return Buffer.isEncoding(encoding)
}

function alloc(size, fill, encoding) {
  return Buffer.alloc(size, fill, encoding)
}

function allocUnsafe(size) {
  return Buffer.allocUnsafe(size)
}

function allocUnsafeSlow(size) {
  return Buffer.allocUnsafeSlow(size)
}

function byteLength(string, encoding) {
  return Buffer.byteLength(string, encoding)
}

function compare(a, b) {
  return Buffer.compare(a, b)
}

function concat(buffers, totalLength) {
  return Buffer.concat(buffers, totalLength)
}

function copy(source, target, targetStart, start, end) {
  return toBuffer(source).copy(target, targetStart, start, end)
}

function equals(a, b) {
  return toBuffer(a).equals(b)
}

function fill(buffer, value, offset, end, encoding) {
  return toBuffer(buffer).fill(value, offset, end, encoding)
}

function from(value, encodingOrOffset, length) {
  return Buffer.from(value, encodingOrOffset, length)
}

function includes(buffer, value, byteOffset, encoding) {
  return toBuffer(buffer).includes(value, byteOffset, encoding)
}

function indexOf(buffer, value, byfeOffset, encoding) {
  return toBuffer(buffer).indexOf(value, byfeOffset, encoding)
}

function lastIndexOf(buffer, value, byteOffset, encoding) {
  return toBuffer(buffer).lastIndexOf(value, byteOffset, encoding)
}

function swap16(buffer) {
  return toBuffer(buffer).swap16()
}

function swap32(buffer) {
  return toBuffer(buffer).swap32()
}

function swap64(buffer) {
  return toBuffer(buffer).swap64()
}

function toBuffer(buffer) {
  if (Buffer.isBuffer(buffer)) return buffer
  return Buffer.from(buffer.buffer, buffer.byteOffset, buffer.byteLength)
}

function toString(buffer, encoding, start, end) {
  return toBuffer(buffer).toString(encoding, start, end)
}

function write(buffer, string, offset, length, encoding) {
  return toBuffer(buffer).write(string, offset, length, encoding)
}

function readDoubleBE(buffer, offset) {
  return toBuffer(buffer).readDoubleBE(offset)
}

function readDoubleLE(buffer, offset) {
  return toBuffer(buffer).readDoubleLE(offset)
}

function readFloatBE(buffer, offset) {
  return toBuffer(buffer).readFloatBE(offset)
}

function readFloatLE(buffer, offset) {
  return toBuffer(buffer).readFloatLE(offset)
}

function readInt32BE(buffer, offset) {
  return toBuffer(buffer).readInt32BE(offset)
}

function readInt32LE(buffer, offset) {
  return toBuffer(buffer).readInt32LE(offset)
}

function readUInt32BE(buffer, offset) {
  return toBuffer(buffer).readUInt32BE(offset)
}

function readUInt32LE(buffer, offset) {
  return toBuffer(buffer).readUInt32LE(offset)
}

function writeDoubleBE(buffer, value, offset) {
  return toBuffer(buffer).writeDoubleBE(value, offset)
}

function writeDoubleLE(buffer, value, offset) {
  return toBuffer(buffer).writeDoubleLE(value, offset)
}

function writeFloatBE(buffer, value, offset) {
  return toBuffer(buffer).writeFloatBE(value, offset)
}

function writeFloatLE(buffer, value, offset) {
  return toBuffer(buffer).writeFloatLE(value, offset)
}

function writeInt32BE(buffer, value, offset) {
  return toBuffer(buffer).writeInt32BE(value, offset)
}

function writeInt32LE(buffer, value, offset) {
  return toBuffer(buffer).writeInt32LE(value, offset)
}

function writeUInt32BE(buffer, value, offset) {
  return toBuffer(buffer).writeUInt32BE(value, offset)
}

function writeUInt32LE(buffer, value, offset) {
  return toBuffer(buffer).writeUInt32LE(value, offset)
}

module.exports = {
  isBuffer,
  isEncoding,
  alloc,
  allocUnsafe,
  allocUnsafeSlow,
  byteLength,
  compare,
  concat,
  copy,
  equals,
  fill,
  from,
  includes,
  indexOf,
  lastIndexOf,
  swap16,
  swap32,
  swap64,
  toBuffer,
  toString,
  write,
  readDoubleBE,
  readDoubleLE,
  readFloatBE,
  readFloatLE,
  readInt32BE,
  readInt32LE,
  readUInt32BE,
  readUInt32LE,
  writeDoubleBE,
  writeDoubleLE,
  writeFloatBE,
  writeFloatLE,
  writeInt32BE,
  writeInt32LE,
  writeUInt32BE,
  writeUInt32LE
}
{
  "name": "b4a",
  "version": "1.8.1",
  "description": "Bridging the gap between buffers and typed arrays",
  "exports": {
    "./package": "./package.json",
    ".": {
      "react-native": "./react-native.js",
      "browser": "./browser.js",
      "default": "./index.js"
    }
  },
  "files": [
    "browser.js",
    "index.js",
    "react-native.js",
    "lib"
  ],
  "scripts": {
    "format": "prettier --write . && lunte --fix",
    "lint": "prettier --check . && lunte",
    "test": "npm run test:bare && npm run test:node",
    "test:bare": "brittle-bare --coverage test.mjs",
    "test:node": "brittle-node --coverage test.mjs"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/b4a.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/b4a/issues"
  },
  "homepage": "https://github.com/holepunchto/b4a#readme",
  "devDependencies": {
    "brittle": "^3.5.2",
    "lunte": "^1.7.0",
    "nanobench": "^3.0.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^2.0.0"
  },
  "peerDependencies": {
    "react-native-b4a": "*"
  },
  "peerDependenciesMeta": {
    "react-native-b4a": {
      "optional": true
    }
  }
}
const ESC = '\x1b'
const CSI = ESC + '['
const SGR = (n) => CSI + n + 'm'

exports.constants = {
  ESC,
  CSI,
  SGR
}

// https://en.wikipedia.org/wiki/ANSI_escape_code#CSI_(Control_Sequence_Introducer)_sequences

exports.cursorHide = CSI + '?25l'
exports.cursorShow = CSI + '?25h'

exports.cursorUp = function cursorUp(n = 1) {
  return CSI + n + 'A'
}

exports.cursorDown = function cursorDown(n = 1) {
  return CSI + n + 'B'
}

exports.cursorForward = function cursorForward(n = 1) {
  return CSI + n + 'C'
}

exports.cursorBack = function cursorBack(n = 1) {
  return CSI + n + 'D'
}

exports.cursorNextLine = function cursorNextLine(n = 1) {
  return CSI + n + 'E'
}

exports.cursorPreviousLine = function cursorPreviousLine(n = 1) {
  return CSI + n + 'F'
}

exports.cursorPosition = function cursorPosition(column, row = 0) {
  if (row === 0) return CSI + (column + 1) + 'G'

  return CSI + (row + 1) + ';' + (column + 1) + 'H'
}

exports.eraseDisplayEnd = CSI + 'J'
exports.eraseDisplayStart = CSI + '1J'
exports.eraseDisplay = CSI + '2J'
exports.eraseLineEnd = CSI + 'K'
exports.eraseLineStart = CSI + '1K'
exports.eraseLine = CSI + '2K'

exports.scrollUp = function scrollUp(n = 1) {
  return CSI + n + 'S'
}

exports.scrollDown = function scrollDown(n = 1) {
  return CSI + n + 'T'
}

// https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_(Select_Graphic_Rendition)_parameters

exports.modifierReset = SGR(0)
exports.modifierBold = SGR(1)
exports.modifierDim = SGR(2)
exports.modifierItalic = SGR(3)
exports.modifierUnderline = SGR(4)
exports.modifierNormal = SGR(22)
exports.modifierNotItalic = SGR(23)
exports.modifierNotUnderline = SGR(24)

// https://en.wikipedia.org/wiki/ANSI_escape_code#Colors

exports.colorBlack = SGR(30)
exports.colorRed = SGR(31)
exports.colorGreen = SGR(32)
exports.colorYellow = SGR(33)
exports.colorBlue = SGR(34)
exports.colorMagenta = SGR(35)
exports.colorCyan = SGR(36)
exports.colorWhite = SGR(37)
exports.colorDefault = SGR(39)
exports.colorBrightBlack = SGR(90)
exports.colorBrightRed = SGR(91)
exports.colorBrightGreen = SGR(92)
exports.colorBrightYellow = SGR(93)
exports.colorBrightBlue = SGR(94)
exports.colorBrightMagenta = SGR(95)
exports.colorBrightCyan = SGR(96)
exports.colorBrightWhite = SGR(97)
{
  "name": "bare-ansi-escapes",
  "version": "2.2.3",
  "description": "Parse and produce ANSI escape sequences",
  "exports": {
    "./package": "./package.json",
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    },
    "./key-decoder": {
      "types": "./key-decoder.d.ts",
      "default": "./key-decoder.js"
    }
  },
  "files": [
    "index.js",
    "index.d.ts",
    "key-decoder.js",
    "key-decoder.d.ts"
  ],
  "scripts": {
    "test": "prettier . --check && bare test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-ansi-escapes.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-ansi-escapes/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-ansi-escapes#readme",
  "dependencies": {
    "bare-stream": "^2.6.5"
  },
  "devDependencies": {
    "bare-buffer": "^3.0.2",
    "brittle": "^3.3.2",
    "prettier": "^3.4.2",
    "prettier-config-standard": "^7.0.0"
  },
  "peerDependencies": {
    "bare-buffer": "*"
  },
  "peerDependenciesMeta": {
    "bare-buffer": {
      "optional": true
    }
  }
}
const inspect = require('bare-inspect')

class AssertionError extends Error {
  constructor(opts = {}) {
    let { message = null, actual, expected, operator } = opts

    if (message === null) {
      message = `${inspect(actual)} ${operator} ${inspect(expected)}`
    }

    super(message)

    this.actual = actual
    this.expected = expected
    this.operator = operator
  }

  get name() {
    return 'AssertionError'
  }

  get code() {
    'ASSERTION'
  }
}

function assertFail(opts, fn) {
  if (opts.message instanceof Error) throw opts.message

  const err = new AssertionError(opts)

  if (Error.captureStackTrace) Error.captureStackTrace(err, fn)

  throw err
}

module.exports = exports = function assert(actual, message) {
  if (actual) return

  assertFail({ message, actual, expected: true, operator: '==' }, assert)
}

exports.AssertionError = AssertionError

exports.fail = function fail(message) {
  if (message === undefined) message = 'Failed'

  assertFail({ message, operator: 'fail' }, fail)
}

exports.ok = function ok(actual, message) {
  if (actual) return

  assertFail({ message, actual, expected: true, operator: '==' }, ok)
}

exports.notOk = function ok(actual, message) {
  if (!actual) return

  assertFail({ message, actual, expected: false, operator: '==' }, ok)
}

exports.equal = function equal(actual, expected, message) {
  if (actual == expected || (actual !== actual && expected !== expected)) {
    return
  }

  assertFail({ message, actual, expected, operator: '==' }, equal)
}

exports.notEqual = function notEqual(actual, expected, message) {
  if (actual != expected && (actual === actual || expected === expected)) {
    return
  }

  assertFail({ message, actual, expected, operator: '!=' }, notEqual)
}

exports.strictEqual = function strictEqual(actual, expected, message) {
  if (Object.is(actual, expected)) return

  assertFail(
    { message, actual, expected, operator: 'strictEqual' },
    strictEqual
  )
}

exports.notStrictEqual = function notStrictEqual(actual, expected, message) {
  if (!Object.is(actual, expected)) return

  assertFail(
    { message, actual, expected, operator: 'notStrictEqual' },
    notStrictEqual
  )
}
{
  "name": "bare-assert",
  "version": "1.2.0",
  "description": "Assertion library for JavaScript",
  "exports": {
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    },
    "./package": "./package.json"
  },
  "files": [
    "index.js",
    "index.d.ts"
  ],
  "scripts": {
    "test": "prettier . --check && bare test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-assert.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-assert/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-assert#readme",
  "dependencies": {
    "bare-inspect": "^3.1.2"
  },
  "devDependencies": {
    "brittle": "^3.1.1",
    "prettier": "^3.4.2",
    "prettier-config-standard": "^7.0.0"
  }
}
const errors = require('./lib/errors')

class EventListener {
  constructor() {
    this.list = []
    this.count = 0
  }

  append(ctx, name, fn, once) {
    this.count++
    ctx.emit('newListener', name, fn) // Emit BEFORE adding
    this.list.push([fn, once, false])
  }

  prepend(ctx, name, fn, once) {
    this.count++
    ctx.emit('newListener', name, fn) // Emit BEFORE adding
    this.list.unshift([fn, once, false])
  }

  remove(ctx, name, fn) {
    for (let i = this.list.length - 1; i >= 0; i--) {
      if (this.list[i][0] === fn) return this.removeAt(ctx, name, i)
    }
  }

  removeAt(ctx, name, i) {
    const fn = this.list[i][0]

    this.list.splice(i, 1)

    if (this.count === 1) delete ctx._events[name]

    ctx.emit('removeListener', name, fn) // Emit AFTER removing

    this.count--
  }

  removeAll(ctx, name) {
    const list = [...this.list]
    this.list = []

    if (this.count === list.length) delete ctx._events[name]

    for (let i = list.length - 1; i >= 0; i--) {
      ctx.emit('removeListener', name, list[i][0]) // Emit AFTER removing
    }

    this.count -= list.length
  }

  emit(ctx, name, ...args) {
    const list = [...this.list]

    for (let i = 0, n = list.length; i < n; i++) {
      const l = list[i]

      if (l[1] === true) {
        // A reentrant emit may already have fired this listener, in which case
        // it must not be fired again.
        if (l[2] === true) continue

        l[2] = true

        // Remove this entry, rather than the first one with the same listener,
        // as the same listener may have been added more than once.
        const j = this.list.indexOf(l)

        if (j !== -1) this.removeAt(ctx, name, j)
      }

      Reflect.apply(l[0], ctx, args)
    }

    return list.length > 0
  }
}

function appendListener(ctx, name, fn, once) {
  if (ctx._events === undefined) ctx._events = Object.create(null)
  const e = ctx._events[name] || (ctx._events[name] = new EventListener())
  e.append(ctx, name, fn, once)
  return ctx
}

function prependListener(ctx, name, fn, once) {
  if (ctx._events === undefined) ctx._events = Object.create(null)
  const e = ctx._events[name] || (ctx._events[name] = new EventListener())
  e.prepend(ctx, name, fn, once)
  return ctx
}

function removeListener(ctx, name, fn) {
  if (ctx._events === undefined) return ctx
  const e = ctx._events[name]
  if (e !== undefined) e.remove(ctx, name, fn)
  return ctx
}

function throwUnhandledError(...args) {
  let err

  if (args.length > 0) err = args[0]

  if (err instanceof Error === false) {
    err = errors.UNHANDLED_ERROR(err)

    if (Error.captureStackTrace) Error.captureStackTrace(err, exports.prototype.emit)
  }

  queueMicrotask(() => {
    throw err
  })
}

module.exports = exports = class EventEmitter {
  constructor() {
    this._events = Object.create(null)
  }

  addListener(name, fn) {
    return appendListener(this, name, fn, false)
  }

  addOnceListener(name, fn) {
    return appendListener(this, name, fn, true)
  }

  prependListener(name, fn) {
    return prependListener(this, name, fn, false)
  }

  prependOnceListener(name, fn) {
    return prependListener(this, name, fn, true)
  }

  removeListener(name, fn) {
    return removeListener(this, name, fn)
  }

  on(name, fn) {
    return appendListener(this, name, fn, false)
  }

  once(name, fn) {
    return appendListener(this, name, fn, true)
  }

  off(name, fn) {
    return removeListener(this, name, fn)
  }

  emit(name, ...args) {
    if (name === 'error' && (this._events === undefined || this._events.error === undefined)) {
      throwUnhandledError(...args)
    }

    if (this._events === undefined) return false
    const e = this._events[name]
    return e === undefined ? false : e.emit(this, name, ...args)
  }

  listeners(name) {
    if (this._events === undefined) return []
    const e = this._events[name]
    return e === undefined ? [] : e.list.map((l) => l[0])
  }

  rawListeners(name) {
    if (this._events === undefined) return []
    const e = this._events[name]
    return e === undefined ? [] : e.list.map((l) => l[0])
  }

  eventNames() {
    if (this._events === undefined) return []
    return Reflect.ownKeys(this._events)
  }

  listenerCount(name) {
    if (this._events === undefined) return 0
    const e = this._events[name]
    return e === undefined ? 0 : e.list.length
  }

  getMaxListeners() {
    return EventEmitter.defaultMaxListeners
  }

  setMaxListeners(n) {
    return this
  }

  removeAllListeners(name) {
    if (this._events === undefined) return this

    if (arguments.length === 0) {
      for (const key of Reflect.ownKeys(this._events)) {
        if (key === 'removeListener') continue
        this.removeAllListeners(key)
      }
      this.removeAllListeners('removeListener')
    } else {
      const e = this._events[name]
      if (e !== undefined) e.removeAll(this, name)
    }
    return this
  }
}

exports.EventEmitter = exports

exports.errors = errors

exports.defaultMaxListeners = 10

exports.on = function on(emitter, name, opts = {}) {
  const { signal } = opts

  if (signal && signal.aborted) {
    throw errors.OPERATION_ABORTED(signal.reason)
  }

  let error = null
  let done = false

  const events = []
  const promises = []

  if (name !== 'error') emitter.on('error', onerror)

  if (signal) signal.addEventListener('abort', onabort)

  emitter.on(name, onevent)

  return {
    next() {
      if (events.length) {
        return Promise.resolve({ value: events.shift(), done: false })
      }

      if (error) {
        const err = error

        error = null

        return Promise.reject(err)
      }

      if (done) return onclose()

      return new Promise((resolve, reject) => promises.push({ resolve, reject }))
    },

    return() {
      return onclose()
    },

    throw(err) {
      return onerror(err)
    },

    [Symbol.asyncIterator]() {
      return this
    }
  }

  function onevent(...args) {
    if (promises.length) {
      promises.shift().resolve({ value: args, done: false })
    } else {
      events.push(args)
    }
  }

  function onerror(err) {
    if (promises.length) {
      promises.shift().reject(err)
    } else {
      error = err
    }

    return onclose()
  }

  function onabort() {
    return onerror(errors.OPERATION_ABORTED(signal.reason))
  }

  function onclose() {
    emitter.off(name, onevent)

    if (name !== 'error') emitter.off('error', onerror)

    if (signal) signal.removeEventListener('abort', onabort)

    done = true

    const result = { done: true }

    while (promises.length) promises.shift().resolve(result)

    return Promise.resolve(result)
  }
}

exports.once = function once(emitter, name, opts = {}) {
  const { signal } = opts

  if (signal && signal.aborted) {
    return Promise.reject(errors.OPERATION_ABORTED(signal.reason))
  }

  return new Promise((resolve, reject) => {
    if (name !== 'error') emitter.on('error', onerror)

    if (signal) signal.addEventListener('abort', onabort)

    emitter.once(name, onevent)

    function onevent(...args) {
      if (name !== 'error') emitter.off('error', onerror)

      if (signal) signal.removeEventListener('abort', onabort)

      resolve(args)
    }

    function onerror(err) {
      emitter.off(name, onevent)

      if (name !== 'error') emitter.off('error', onerror)

      if (signal) signal.removeEventListener('abort', onabort)

      reject(err)
    }

    function onabort() {
      onerror(errors.OPERATION_ABORTED(signal.reason))
    }
  })
}

exports.forward = function forward(from, to, names, opts = {}) {
  if (Array.isArray(names) === false) names = [names]

  const { emit = to.emit.bind(to) } = opts

  const listeners = names.map(
    (name) =>
      function onevent(...args) {
        emit(name, ...args)
      }
  )

  for (let i = 0, n = names.length; i < n; i++) {
    if (to.listenerCount(names[i]) > 0) from.on(names[i], listeners[i])
  }

  to.on('newListener', (name) => {
    const i = names.indexOf(name)

    if (i !== -1 && to.listenerCount(name) === 0) {
      from.on(name, listeners[i])
    }
  }).on('removeListener', (name) => {
    const i = names.indexOf(name)

    if (i !== -1 && to.listenerCount(name) === 0) {
      from.off(name, listeners[i])
    }
  })
}

exports.listenerCount = function listenerCount(emitter, name) {
  return emitter.listenerCount(name)
}

exports.getMaxListeners = function getMaxListeners(emitter) {
  if (typeof emitter.getMaxListeners === 'function') {
    return emitter.getMaxListeners()
  }

  return exports.defaultMaxListeners
}

exports.setMaxListeners = function setMaxListeners(n, ...emitters) {
  if (emitters.length === 0) exports.defaultMaxListeners = n
  else {
    for (const emitter of emitters) {
      if (typeof emitter.setMaxListeners === 'function') {
        emitter.setMaxListeners(n)
      }
    }
  }
}
module.exports = class EventEmitterError extends Error {
  constructor(msg, fn = EventEmitterError, opts = {}) {
    const { code = fn.name } = opts

    super(`${code}: ${msg}`, opts)

    this.code = code

    if (Error.captureStackTrace) Error.captureStackTrace(this, fn)
  }

  get name() {
    return 'EventEmitterError'
  }

  static OPERATION_ABORTED(cause, msg = 'Operation aborted') {
    return new EventEmitterError(msg, EventEmitterError.OPERATION_ABORTED, { cause })
  }

  static UNHANDLED_ERROR(cause, msg = 'Unhandled error') {
    return new EventEmitterError(msg, EventEmitterError.UNHANDLED_ERROR, { cause })
  }
}
{
  "name": "bare-events",
  "version": "2.9.2",
  "description": "Event emitters for JavaScript",
  "exports": {
    "./package": "./package.json",
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    },
    "./global": {
      "types": "./global.d.ts",
      "default": "./global.js"
    },
    "./web": {
      "types": "./web.d.ts",
      "default": "./web.js"
    },
    "./errors": "./lib/errors.js"
  },
  "files": [
    "index.js",
    "index.d.ts",
    "global.js",
    "global.d.ts",
    "web.js",
    "web.d.ts",
    "lib"
  ],
  "scripts": {
    "format": "prettier --write . && lunte --fix",
    "lint": "prettier --check . && lunte",
    "test": "npm run test:bare && npm run test:node",
    "test:bare": "brittle-bare --coverage test.js",
    "test:node": "brittle-node --coverage test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-events.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-events/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-events#readme",
  "devDependencies": {
    "bare-abort-controller": "^1.0.0",
    "brittle": "^3.3.2",
    "lunte": "^1.8.0",
    "prettier": "^3.4.2",
    "prettier-config-holepunch": "^2.0.0",
    "uncaughts": "^1.1.1"
  },
  "peerDependencies": {
    "bare-abort-controller": "*"
  },
  "peerDependenciesMeta": {
    "bare-abort-controller": {
      "optional": true
    }
  }
}
module.exports = require.addon()
const FIFO = require('fast-fifo')
const EventEmitter = require('bare-events')
const path = require('bare-path')
const { isURL, fileURLToPath } = require('bare-url')
const { Readable, Writable } = require('bare-stream')
const binding = require('./binding')
const constants = require('./lib/constants')
const FileError = require('./lib/errors')

const isWindows = Bare.platform === 'win32'

exports.constants = constants

class FileRequest {
  static borrow() {
    if (this._free.length > 0) return this._free.pop()
    return new FileRequest()
  }

  static return(req) {
    if (this._free.length < 32) this._free.push(req.reset())
    else req.destroy()
  }

  constructor() {
    this._reset()
    this._handle = binding.requestInit(this, this._onresult)
  }

  get handle() {
    return this._handle
  }

  retain(value) {
    this._retain = value // Tie the lifetime of `value` to the lifetime of `this`
  }

  reset() {
    if (this._handle === null) return this

    binding.requestReset(this._handle)

    this._reset()

    return this
  }

  destroy() {
    if (this._handle === null) return this

    binding.requestDestroy(this._handle)

    this._reset()
    this._handle = null

    return this
  }

  then(resolve, reject) {
    return this._promise.then(resolve, reject)
  }

  return() {
    if (this._handle === null) return this

    FileRequest.return(this)

    return this
  }

  _reset() {
    this._promise = new Promise((resolve, reject) => {
      this._resolve = resolve
      this._reject = reject
    })
    this._retain = null
  }

  _onresult(err, status) {
    if (err) this._reject(err)
    else this._resolve(status)
  }
}

FileRequest._free = []

function ok(result, cb) {
  if (typeof result === 'function') {
    cb = result
    result = undefined
  }

  if (cb) cb(null, result)
  else return result
}

function fail(err, cb) {
  if (cb) cb(err)
  else throw err
}

function done(err, result, cb) {
  if (typeof result === 'function') {
    cb = result
    result = undefined
  }

  if (err) fail(err, cb)
  else return ok(result, cb)
}

async function open(filepath, flags = 'r', mode = 0o666, cb) {
  if (typeof flags === 'function') {
    cb = flags
    flags = 'r'
    mode = 0o666
  } else if (typeof mode === 'function') {
    cb = mode
    mode = 0o666
  }

  if (typeof flags === 'string') flags = toFlags(flags)
  if (typeof mode === 'string') mode = toMode(mode)

  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  let fd
  let err = null
  try {
    binding.open(req.handle, filepath, flags, mode)

    fd = await req
  } catch (e) {
    err = new FileError(e.message, {
      operation: 'open',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }

  return done(err, fd, cb)
}

function openSync(filepath, flags = 'r', mode = 0o666) {
  if (typeof flags === 'string') flags = toFlags(flags)
  if (typeof mode === 'string') mode = toMode(mode)

  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  try {
    return binding.openSync(req.handle, filepath, flags, mode)
  } catch (e) {
    throw new FileError(e.message, {
      operation: 'open',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }
}

async function close(fd, cb) {
  const req = FileRequest.borrow()

  let err = null
  try {
    binding.close(req.handle, fd)

    await req
  } catch (e) {
    err = new FileError(e.message, { operation: 'close', code: e.code, fd })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function closeSync(fd) {
  const req = FileRequest.borrow()

  try {
    binding.closeSync(req.handle, fd)
  } catch (e) {
    throw new FileError(e.message, { operation: 'close', code: e.code, fd })
  } finally {
    req.return()
  }
}

async function access(filepath, mode = constants.F_OK, cb) {
  if (typeof mode === 'function') {
    cb = mode
    mode = constants.F_OK
  }

  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  let err = null
  try {
    binding.access(req.handle, filepath, mode)

    await req
  } catch (e) {
    err = new FileError(e.message, {
      operation: 'access',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function accessSync(filepath, mode = constants.F_OK) {
  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  try {
    binding.accessSync(req.handle, filepath, mode)
  } catch (e) {
    throw new FileError(e.message, {
      operation: 'access',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }
}

async function exists(filepath, cb) {
  let ok = true
  try {
    await access(filepath)
  } catch {
    ok = false
  }

  return done(null, ok, cb)
}

function existsSync(filepath) {
  try {
    accessSync(filepath)
  } catch {
    return false
  }

  return true
}

async function read(fd, buffer, offset = 0, len = buffer.byteLength - offset, pos = -1, cb) {
  if (typeof offset === 'function') {
    cb = offset
    offset = 0
    len = buffer.byteLength
    pos = -1
  } else if (typeof len === 'function') {
    cb = len
    len = buffer.byteLength - offset
    pos = -1
  } else if (typeof pos === 'function') {
    cb = pos
    pos = -1
  }

  if (typeof pos !== 'number') pos = -1

  const req = FileRequest.borrow()

  let bytes
  let err = null
  try {
    binding.read(req.handle, fd, buffer, offset, len, pos)

    req.retain(buffer)

    bytes = await req
  } catch (e) {
    err = new FileError(e.message, { operation: 'read', code: e.code, fd })
  } finally {
    req.return()
  }

  return done(err, bytes, cb)
}

function readSync(fd, buffer, offset = 0, len = buffer.byteLength - offset, pos = -1) {
  const req = FileRequest.borrow()

  try {
    return binding.readSync(req.handle, fd, buffer, offset, len, pos)
  } catch (e) {
    throw new FileError(e.message, { operation: 'read', code: e.code, fd })
  } finally {
    req.return()
  }
}

async function readv(fd, buffers, pos = -1, cb) {
  if (typeof pos === 'function') {
    cb = pos
    pos = -1
  }

  if (typeof pos !== 'number') pos = -1

  const req = FileRequest.borrow()

  let bytes
  let err = null
  try {
    binding.readv(req.handle, fd, buffers, pos)

    req.retain(buffers)

    bytes = await req
  } catch (e) {
    err = new FileError(e.message, { operation: 'readv', code: e.code, fd })
  } finally {
    req.return()
  }

  return done(err, bytes, cb)
}

function readvSync(fd, buffers, pos = -1) {
  if (typeof pos !== 'number') pos = -1

  const req = FileRequest.borrow()

  try {
    return binding.readvSync(req.handle, fd, buffers, pos)
  } catch (e) {
    throw new FileError(e.message, { operation: 'readv', code: e.code, fd })
  } finally {
    req.return()
  }
}

async function write(fd, data, offset, len, pos = -1, cb) {
  if (typeof data === 'string') {
    let encoding = len
    cb = pos
    pos = offset

    if (typeof pos === 'function') {
      cb = pos
      pos = -1
      encoding = 'utf8'
    } else if (typeof encoding === 'function') {
      cb = encoding
      encoding = 'utf8'
    }

    if (typeof pos === 'string') {
      encoding = pos
      pos = -1
    }

    data = Buffer.from(data, encoding)
    offset = 0
    len = data.byteLength
  } else if (typeof offset === 'function') {
    cb = offset
    offset = 0
    len = data.byteLength
    pos = -1
  } else if (typeof len === 'function') {
    cb = len
    len = data.byteLength - offset
    pos = -1
  } else if (typeof pos === 'function') {
    cb = pos
    pos = -1
  }

  if (typeof offset !== 'number') offset = 0
  if (typeof len !== 'number') len = data.byteLength - offset
  if (typeof pos !== 'number') pos = -1

  const req = FileRequest.borrow()

  let bytes
  let err = null
  try {
    binding.write(req.handle, fd, data, offset, len, pos)

    req.retain(data)

    bytes = await req
  } catch (e) {
    err = new FileError(e.message, { operation: 'write', code: e.code, fd })
  } finally {
    req.return()
  }

  return done(err, bytes, cb)
}

function writeSync(fd, data, offset, len, pos = -1) {
  if (typeof data === 'string') {
    let encoding = len
    pos = offset

    if (typeof pos === 'string') {
      encoding = pos
      pos = -1
    }

    data = Buffer.from(data, encoding)
    offset = 0
    len = data.byteLength
  }

  if (typeof offset !== 'number') offset = 0
  if (typeof len !== 'number') len = data.byteLength - offset
  if (typeof pos !== 'number') pos = -1

  const req = FileRequest.borrow()

  try {
    return binding.writeSync(req.handle, fd, data, offset, len, pos)
  } catch (e) {
    throw new FileError(e.message, { operation: 'write', code: e.code, fd })
  } finally {
    req.return()
  }
}

async function writev(fd, buffers, pos = -1, cb) {
  if (typeof pos === 'function') {
    cb = pos
    pos = -1
  }

  if (typeof pos !== 'number') pos = -1

  const req = FileRequest.borrow()

  let bytes
  let err = null
  try {
    binding.writev(req.handle, fd, buffers, pos)

    req.retain(buffers)

    bytes = await req
  } catch (e) {
    err = new FileError(e.message, { operation: 'writev', code: e.code, fd })
  } finally {
    req.return()
  }

  return done(err, bytes, cb)
}

function writevSync(fd, buffers, pos = -1) {
  if (typeof pos !== 'number') pos = -1

  const req = FileRequest.borrow()

  try {
    return binding.writevSync(req.handle, fd, buffers, pos)
  } catch (e) {
    throw new FileError(e.message, { operation: 'writev', code: e.code, fd })
  } finally {
    req.return()
  }
}

async function stat(filepath, cb) {
  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  let st
  let err = null
  try {
    binding.stat(req.handle, filepath)

    await req

    st = new Stats(...binding.requestResultStat(req.handle))
  } catch (e) {
    err = new FileError(e.message, { operation: 'stat', code: e.code, path: filepath })
  } finally {
    req.return()
  }

  return done(err, st, cb)
}

function statSync(filepath) {
  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  try {
    binding.statSync(req.handle, filepath)

    return new Stats(...binding.requestResultStat(req.handle))
  } catch (e) {
    throw new FileError(e.message, { operation: 'stat', code: e.code, path: filepath })
  } finally {
    req.return()
  }
}

async function lstat(filepath, cb) {
  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  let st
  let err = null
  try {
    binding.lstat(req.handle, filepath)

    await req

    st = new Stats(...binding.requestResultStat(req.handle))
  } catch (e) {
    err = new FileError(e.message, { operation: 'lstat', code: e.code, path: filepath })
  } finally {
    req.return()
  }

  return done(err, st, cb)
}

function lstatSync(filepath) {
  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  try {
    binding.lstatSync(req.handle, filepath)

    return new Stats(...binding.requestResultStat(req.handle))
  } catch (e) {
    throw new FileError(e.message, { operation: 'lstat', code: e.code, path: filepath })
  } finally {
    req.return()
  }
}

async function fstat(fd, cb) {
  const req = FileRequest.borrow()

  let st
  let err = null
  try {
    binding.fstat(req.handle, fd)

    await req

    st = new Stats(...binding.requestResultStat(req.handle))
  } catch (e) {
    err = new FileError(e.message, { operation: 'fstat', code: e.code, fd })
  } finally {
    req.return()
  }

  return done(err, st, cb)
}

function fstatSync(fd) {
  const req = FileRequest.borrow()

  try {
    binding.fstatSync(req.handle, fd)

    return new Stats(...binding.requestResultStat(req.handle))
  } catch (e) {
    throw new FileError(e.message, { operation: 'fstat', code: e.code, fd })
  } finally {
    req.return()
  }
}

async function statfs(filepath, cb) {
  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  let st
  let err = null
  try {
    binding.statfs(req.handle, filepath)

    await req

    st = new StatFs(...binding.requestResultStatfs(req.handle))
  } catch (e) {
    err = new FileError(e.message, { operation: 'statfs', code: e.code, path: filepath })
  } finally {
    req.return()
  }

  return done(err, st, cb)
}

function statfsSync(filepath) {
  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  try {
    binding.statfsSync(req.handle, filepath)

    return new StatFs(...binding.requestResultStatfs(req.handle))
  } catch (e) {
    throw new FileError(e.message, { operation: 'statfs', code: e.code, path: filepath })
  } finally {
    req.return()
  }
}

async function ftruncate(fd, len = 0, cb) {
  if (typeof len === 'function') {
    cb = len
    len = 0
  }

  if (typeof len !== 'number') len = 0

  const req = FileRequest.borrow()

  let err = null
  try {
    binding.ftruncate(req.handle, fd, len)

    await req
  } catch (e) {
    err = new FileError(e.message, { operation: 'ftruncate', code: e.code, fd })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function ftruncateSync(fd, len = 0) {
  if (typeof len !== 'number') len = 0

  const req = FileRequest.borrow()

  try {
    binding.ftruncateSync(req.handle, fd, len)
  } catch (e) {
    throw new FileError(e.message, { operation: 'ftruncate', code: e.code, fd })
  } finally {
    req.return()
  }
}

async function truncate(filepath, len, cb) {
  let fd = -1
  let err

  try {
    fd = await open(filepath, 'r+')

    await ftruncate(fd, len)
  } catch (e) {
    err = e
  } finally {
    if (fd !== -1) await close(fd)
  }

  return done(err, cb)
}

function truncateSync(filepath, len) {
  let fd = -1
  let err

  try {
    fd = openSync(filepath, 'r+')

    ftruncateSync(fd, len)
  } catch (e) {
    err = e
  } finally {
    if (fd !== -1) closeSync(fd)
  }
}

async function chmod(filepath, mode, cb) {
  if (typeof mode === 'string') mode = toMode(mode)

  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  let err = null
  try {
    binding.chmod(req.handle, filepath, mode)

    await req
  } catch (e) {
    err = new FileError(e.message, {
      operation: 'chmod',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function chmodSync(filepath, mode) {
  if (typeof mode === 'string') mode = toMode(mode)

  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  try {
    binding.chmodSync(req.handle, filepath, mode)
  } catch (e) {
    throw new FileError(e.message, {
      operation: 'chmod',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }
}

async function fchmod(fd, mode, cb) {
  if (typeof mode === 'string') mode = toMode(mode)

  const req = FileRequest.borrow()

  let err = null
  try {
    binding.fchmod(req.handle, fd, mode)

    await req
  } catch (e) {
    err = new FileError(e.message, { operation: 'fchmod', code: e.code, fd })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function fchmodSync(fd, mode) {
  if (typeof mode === 'string') mode = toMode(mode)

  const req = FileRequest.borrow()

  try {
    binding.fchmodSync(req.handle, fd, mode)
  } catch (e) {
    throw new FileError(e.message, { operation: 'fchmod', code: e.code, fd })
  } finally {
    req.return()
  }
}

async function chown(filepath, uid, gid, cb) {
  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  let err = null
  try {
    binding.chown(req.handle, filepath, uid, gid)

    await req
  } catch (e) {
    err = new FileError(e.message, { operation: 'chown', code: e.code, path: filepath })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function chownSync(filepath, uid, gid) {
  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  try {
    binding.chownSync(req.handle, filepath, uid, gid)
  } catch (e) {
    throw new FileError(e.message, { operation: 'chownSync', code: e.code, path: filepath })
  } finally {
    req.return()
  }
}

async function lchown(filepath, uid, gid, cb) {
  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  let err = null
  try {
    binding.lchown(req.handle, filepath, uid, gid)

    await req
  } catch (e) {
    err = new FileError(e.message, { operation: 'lchown', code: e.code, path: filepath })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function lchownSync(filepath, uid, gid) {
  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  try {
    binding.lchownSync(req.handle, filepath, uid, gid)
  } catch (e) {
    throw new FileError(e.message, { operation: 'lchownSync', code: e.code, path: filepath })
  } finally {
    req.return()
  }
}

async function fchown(fd, uid, gid, cb) {
  const req = FileRequest.borrow()

  let err = null
  try {
    binding.fchown(req.handle, fd, uid, gid)

    await req
  } catch (e) {
    err = new FileError(e.message, { operation: 'fchown', code: e.code, fd })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function fchownSync(fd, uid, gid) {
  const req = FileRequest.borrow()

  try {
    binding.fchownSync(req.handle, fd, uid, gid)
  } catch (e) {
    throw new FileError(e.message, { operation: 'fchownSync', code: e.code, fd })
  } finally {
    req.return()
  }
}

async function utimes(filepath, atime, mtime, cb) {
  if (typeof atime !== 'number') atime = atime.getTime() / 1000
  if (typeof mtime !== 'number') mtime = mtime.getTime() / 1000

  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  let err = null
  try {
    binding.utimes(req.handle, filepath, atime, mtime)

    await req
  } catch (e) {
    err = new FileError(e.message, { operation: 'utimes', code: e.code, path: filepath })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function utimesSync(filepath, atime, mtime) {
  if (typeof atime !== 'number') atime = atime.getTime() / 1000
  if (typeof mtime !== 'number') mtime = mtime.getTime() / 1000

  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  try {
    binding.utimesSync(req.handle, filepath, atime, mtime)
  } catch (e) {
    throw new FileError(e.message, { operation: 'utimes', code: e.code, path: filepath })
  } finally {
    req.return()
  }
}

async function lutimes(filepath, atime, mtime, cb) {
  if (typeof atime !== 'number') atime = atime.getTime() / 1000
  if (typeof mtime !== 'number') mtime = mtime.getTime() / 1000

  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  let err = null
  try {
    binding.lutimes(req.handle, filepath, atime, mtime)

    await req
  } catch (e) {
    err = new FileError(e.message, { operation: 'lutimes', code: e.code, path: filepath })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function lutimesSync(filepath, atime, mtime) {
  if (typeof atime !== 'number') atime = atime.getTime() / 1000
  if (typeof mtime !== 'number') mtime = mtime.getTime() / 1000

  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  try {
    binding.lutimesSync(req.handle, filepath, atime, mtime)
  } catch (e) {
    throw new FileError(e.message, { operation: 'lutimes', code: e.code, path: filepath })
  } finally {
    req.return()
  }
}

async function futimes(fd, atime, mtime, cb) {
  if (typeof atime !== 'number') atime = atime.getTime() / 1000
  if (typeof mtime !== 'number') mtime = mtime.getTime() / 1000

  const req = FileRequest.borrow()

  let err = null
  try {
    binding.futimes(req.handle, fd, atime, mtime)

    await req
  } catch (e) {
    err = new FileError(e.message, { operation: 'futimes', code: e.code, fd })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function futimesSync(fd, atime, mtime) {
  if (typeof atime !== 'number') atime = atime.getTime() / 1000
  if (typeof mtime !== 'number') mtime = mtime.getTime() / 1000

  const req = FileRequest.borrow()

  try {
    binding.futimesSync(req.handle, fd, atime, mtime)
  } catch (e) {
    throw new FileError(e.message, { operation: 'futimesSync', code: e.code, fd })
  } finally {
    req.return()
  }
}

async function link(src, dst, cb) {
  src = toNamespacedPath(src)
  dst = toNamespacedPath(dst)

  const req = FileRequest.borrow()

  let err = null
  try {
    binding.link(req.handle, src, dst)

    await req
  } catch (e) {
    err = new FileError(e.message, { operation: 'link', code: e.code, path: src, destination: dst })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function linkSync(src, dst) {
  src = toNamespacedPath(src)
  dst = toNamespacedPath(dst)

  const req = FileRequest.borrow()

  try {
    binding.linkSync(req.handle, src, dst)
  } catch (e) {
    throw new FileError(e.message, {
      operation: 'linkSync',
      code: e.code,
      path: src,
      destination: dst
    })
  } finally {
    req.return()
  }
}

async function mkdir(filepath, opts, cb) {
  if (typeof opts === 'function') {
    cb = opts
    opts = { mode: 0o777 }
  }

  if (typeof opts === 'number') opts = { mode: opts }
  else if (!opts) opts = {}

  const mode = typeof opts.mode === 'number' ? opts.mode : 0o777

  filepath = toNamespacedPath(filepath)

  if (opts.recursive) {
    let err = null
    try {
      try {
        await mkdir(filepath, { mode })
      } catch (err) {
        if (err.code !== 'ENOENT') {
          if (!(await stat(filepath)).isDirectory()) throw err
        } else {
          while (filepath.endsWith(path.sep)) filepath = filepath.slice(0, -1)
          const i = filepath.lastIndexOf(path.sep)
          if (i <= 0) throw err

          await mkdir(filepath.slice(0, i), { mode, recursive: true })

          try {
            await mkdir(filepath, { mode })
          } catch (err) {
            if (!(await stat(filepath)).isDirectory()) throw err
          }
        }
      }
    } catch (e) {
      err = e
    }

    return done(err, cb)
  }

  const req = FileRequest.borrow()

  let err = null
  try {
    binding.mkdir(req.handle, filepath, mode)

    await req
  } catch (e) {
    err = new FileError(e.message, {
      operation: 'mkdir',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function mkdirSync(filepath, opts) {
  if (typeof opts === 'number') opts = { mode: opts }
  else if (!opts) opts = {}

  const mode = typeof opts.mode === 'number' ? opts.mode : 0o777

  filepath = toNamespacedPath(filepath)

  if (opts.recursive) {
    try {
      mkdirSync(filepath, { mode })
    } catch (err) {
      if (err.code !== 'ENOENT') {
        if (!statSync(filepath).isDirectory()) throw err
      } else {
        while (filepath.endsWith(path.sep)) filepath = filepath.slice(0, -1)
        const i = filepath.lastIndexOf(path.sep)
        if (i <= 0) throw err

        mkdirSync(filepath.slice(0, i), { mode, recursive: true })

        try {
          mkdirSync(filepath, { mode })
        } catch (err) {
          if (!statSync(filepath).isDirectory()) throw err
        }
      }
    }

    return
  }

  const req = FileRequest.borrow()

  try {
    binding.mkdirSync(req.handle, filepath, mode)
  } catch (e) {
    throw new FileError(e.message, {
      operation: 'mkdir',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }
}

async function mkdtemp(prefix, cb) {
  prefix = toNamespacedPath(prefix)

  const req = FileRequest.borrow()

  let res
  let err = null
  try {
    binding.mkdtemp(req.handle, prefix + 'XXXXXX')

    await req

    res = binding.requestResultPath(req.handle)
  } catch (e) {
    err = new FileError(e.message, { operation: 'mkdtemp', code: e.code, path: prefix })
  } finally {
    req.return()
  }

  return done(err, res, cb)
}

function mkdtempSync(prefix) {
  prefix = toNamespacedPath(prefix)

  const req = FileRequest.borrow()

  try {
    binding.mkdtempSync(req.handle, prefix + 'XXXXXX')

    return binding.requestResultPath(req.handle)
  } catch (e) {
    throw new FileError(e.message, { operation: 'mkdtempSync', code: e.code, path: prefix })
  } finally {
    req.return()
  }
}

async function rmdir(filepath, cb) {
  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  let err = null
  try {
    binding.rmdir(req.handle, filepath)

    await req
  } catch (e) {
    err = new FileError(e.message, {
      operation: 'rmdir',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function rmdirSync(filepath) {
  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  try {
    binding.rmdirSync(req.handle, filepath)
  } catch (e) {
    throw new FileError(e.message, {
      operation: 'rmdir',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }
}

async function rm(filepath, opts, cb) {
  if (typeof opts === 'function') {
    cb = opts
    opts = {}
  }

  if (!opts) opts = {}

  filepath = toNamespacedPath(filepath)

  let err = null
  try {
    const st = await lstat(filepath)

    if (st.isDirectory()) {
      if (opts.recursive) {
        try {
          await rmdir(filepath)
        } catch (err) {
          if (err.code !== 'ENOTEMPTY') throw err

          const files = await readdir(filepath)

          for (const file of files) {
            await rm(filepath + path.sep + file, opts)
          }

          await rmdir(filepath)
        }
      } else {
        throw new FileError('is a directory', {
          operation: 'rm',
          code: 'EISDIR',
          path: filepath
        })
      }
    } else {
      await unlink(filepath)
    }
  } catch (e) {
    if (e.code !== 'ENOENT' || !opts.force) err = e
  }

  return done(err, cb)
}

function rmSync(filepath, opts) {
  if (!opts) opts = {}

  filepath = toNamespacedPath(filepath)

  try {
    const st = lstatSync(filepath)

    if (st.isDirectory()) {
      if (opts.recursive) {
        try {
          rmdirSync(filepath)
        } catch (err) {
          if (err.code !== 'ENOTEMPTY') throw err

          const files = readdirSync(filepath)

          for (const file of files) {
            rmSync(filepath + path.sep + file, opts)
          }

          rmdirSync(filepath)
        }
      } else {
        throw new FileError('is a directory', {
          operation: 'rm',
          code: 'EISDIR',
          path: filepath
        })
      }
    } else {
      unlinkSync(filepath)
    }
  } catch (err) {
    if (err.code !== 'ENOENT' || !opts.force) throw err
  }
}

async function unlink(filepath, cb) {
  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  let err = null
  try {
    binding.unlink(req.handle, filepath)

    await req
  } catch (e) {
    err = new FileError(e.message, {
      operation: 'unlink',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function unlinkSync(filepath) {
  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  try {
    binding.unlinkSync(req.handle, filepath)
  } catch (e) {
    throw new FileError(e.message, {
      operation: 'unlink',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }
}

async function rename(src, dst, cb) {
  src = toNamespacedPath(src)
  dst = toNamespacedPath(dst)

  const req = FileRequest.borrow()

  let err = null
  try {
    binding.rename(req.handle, src, dst)

    await req
  } catch (e) {
    err = new FileError(e.message, {
      operation: 'rename',
      code: e.code,
      path: src,
      destination: dst
    })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function renameSync(src, dst) {
  src = toNamespacedPath(src)
  dst = toNamespacedPath(dst)

  const req = FileRequest.borrow()

  try {
    binding.renameSync(req.handle, src, dst)
  } catch (e) {
    throw new FileError(e.message, {
      operation: 'rename',
      code: e.code,
      path: src,
      destination: dst
    })
  } finally {
    req.return()
  }
}

async function copyFile(src, dst, mode = 0, cb) {
  if (typeof mode === 'function') {
    cb = mode
    mode = 0
  }

  src = toNamespacedPath(src)
  dst = toNamespacedPath(dst)

  const req = FileRequest.borrow()

  let err = null
  try {
    binding.copyfile(req.handle, src, dst, mode)

    await req
  } catch (e) {
    err = new FileError(e.message, {
      operation: 'copyfile',
      code: e.code,
      path: src,
      destination: dst
    })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function copyFileSync(src, dst, mode = 0) {
  src = toNamespacedPath(src)
  dst = toNamespacedPath(dst)

  const req = FileRequest.borrow()

  try {
    binding.copyfileSync(req.handle, src, dst, mode)
  } catch (e) {
    throw new FileError(e.message, {
      operation: 'copyfile',
      code: e.code,
      path: src,
      destination: dst
    })
  } finally {
    req.return()
  }
}

async function cp(src, dst, opts, cb) {
  if (typeof opts === 'function') {
    cb = opts
    opts = {}
  }

  if (!opts) opts = {}

  src = toNamespacedPath(src)
  dst = toNamespacedPath(dst)

  let err = null
  try {
    const st = await lstat(src)

    if (st.isDirectory()) {
      if (opts.recursive !== true) {
        throw new FileError('is a directory', { operation: 'cp', code: 'EISDIR', path: src })
      }

      try {
        await lstat(dst)
      } catch (e) {
        if (e.code !== 'ENOENT') throw e

        await mkdir(dst, { mode: st.mode, recursive: true })
      }

      const dir = await opendir(src)
      for await (const { name } of dir) {
        await cp(path.join(src, name), path.join(dst, name), opts)
      }
    } else if (st.isFile()) {
      await copyFile(src, dst)
      await chmod(dst, st.mode)
    }
  } catch (e) {
    err = e
  }

  return done(err, cb)
}

function cpSync(src, dst, opts = {}) {
  src = toNamespacedPath(src)
  dst = toNamespacedPath(dst)

  const st = lstatSync(src)

  if (st.isDirectory()) {
    if (opts.recursive !== true) {
      throw new FileError('is a directory', { operation: 'cp', code: 'EISDIR', path: src })
    }

    try {
      lstatSync(dst)
    } catch (e) {
      if (e.code !== 'ENOENT') throw e

      mkdirSync(dst, { mode: st.mode, recursive: true })
    }

    const dir = opendirSync(src)
    for (const { name } of dir) {
      cpSync(path.join(src, name), path.join(dst, name), opts)
    }
  } else if (st.isFile()) {
    copyFileSync(src, dst)
    chmodSync(dst, st.mode)
  }
}

async function realpath(filepath, opts, cb) {
  if (typeof opts === 'function') {
    cb = opts
    opts = {}
  }

  if (typeof opts === 'string') opts = { encoding: opts }
  else if (!opts) opts = {}

  const { encoding = 'utf8' } = opts

  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  let res
  let err = null
  try {
    binding.realpath(req.handle, filepath)

    await req

    res = Buffer.from(binding.requestResultString(req.handle))

    if (encoding !== 'buffer') res = res.toString(encoding)
  } catch (e) {
    err = new FileError(e.message, {
      operation: 'realpath',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }

  return done(err, res, cb)
}

function realpathSync(filepath, opts) {
  if (typeof opts === 'string') opts = { encoding: opts }
  else if (!opts) opts = {}

  const { encoding = 'utf8' } = opts

  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  try {
    binding.realpathSync(req.handle, filepath)

    let res = Buffer.from(binding.requestResultString(req.handle))

    if (encoding !== 'buffer') res = res.toString(encoding)

    return res
  } catch (e) {
    throw new FileError(e.message, {
      operation: 'realpath',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }
}

async function readlink(filepath, opts, cb) {
  if (typeof opts === 'function') {
    cb = opts
    opts = {}
  }

  if (typeof opts === 'string') opts = { encoding: opts }
  else if (!opts) opts = {}

  const { encoding = 'utf8' } = opts

  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  let res
  let err = null
  try {
    binding.readlink(req.handle, filepath)

    await req

    res = Buffer.from(binding.requestResultString(req.handle))

    if (encoding !== 'buffer') res = res.toString(encoding)
  } catch (e) {
    err = new FileError(e.message, {
      operation: 'readlink',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }

  return done(err, res, cb)
}

function readlinkSync(filepath, opts) {
  if (typeof opts === 'string') opts = { encoding: opts }
  else if (!opts) opts = {}

  const { encoding = 'utf8' } = opts

  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  try {
    binding.readlinkSync(req.handle, filepath)

    let res = Buffer.from(binding.requestResultString(req.handle))

    if (encoding !== 'buffer') res = res.toString(encoding)

    return res
  } catch (e) {
    throw new FileError(e.message, {
      operation: 'readlink',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }
}

async function fsync(fd, cb) {
  const req = FileRequest.borrow()

  let err = null
  try {
    binding.fsync(req.handle, fd)

    await req
  } catch (e) {
    err = new FileError(e.message, { operation: 'fsync', code: e.code, fd })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function fsyncSync(fd) {
  const req = FileRequest.borrow()

  try {
    binding.fsyncSync(req.handle, fd)
  } catch (e) {
    throw new FileError(e.message, { operation: 'fsyncSync', code: e.code, fd })
  } finally {
    req.return()
  }
}

async function fdatasync(fd, cb) {
  const req = FileRequest.borrow()

  let err = null
  try {
    binding.fdatasync(req.handle, fd)

    await req
  } catch (e) {
    err = new FileError(e.message, { operation: 'fdatasync', code: e.code, fd })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function fdatasyncSync(fd) {
  const req = FileRequest.borrow()

  try {
    binding.fdatasyncSync(req.handle, fd)
  } catch (e) {
    throw new FileError(e.message, { operation: 'fdatasyncSync', code: e.code, fd })
  } finally {
    req.return()
  }
}

function normalizeSymlinkTarget(target, type, filepath) {
  if (isWindows) {
    if (type === constants.UV_FS_SYMLINK_JUNCTION) target = path.resolve(filepath, '..', target)

    if (path.isAbsolute(target)) return path.toNamespacedPath(target)

    return target.replace(/\//g, path.sep)
  }

  return target
}

async function symlink(target, filepath, type, cb) {
  if (typeof type === 'function') {
    cb = type
    type = null
  }

  filepath = toNamespacedPath(filepath)

  if (typeof type === 'string') {
    switch (type) {
      case 'dir':
        type = constants.UV_FS_SYMLINK_DIR
        break
      case 'junction':
        type = constants.UV_FS_SYMLINK_JUNCTION
        break
      case 'file':
      default:
        type = 0
        break
    }
  } else if (typeof type !== 'number') {
    if (isWindows) {
      target = path.resolve(filepath, '..', target)

      try {
        type = (await stat(target)).isDirectory()
          ? constants.UV_FS_SYMLINK_DIR
          : constants.UV_FS_SYMLINK_JUNCTION
      } catch {
        type = 0
      }
    } else {
      type = 0
    }
  }

  target = normalizeSymlinkTarget(target, type, filepath)

  const req = FileRequest.borrow()

  let err = null
  try {
    binding.symlink(req.handle, target, filepath, type)

    await req
  } catch (e) {
    err = new FileError(e.message, {
      operation: 'symlink',
      code: e.code,
      path: target,
      destination: filepath
    })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function symlinkSync(target, filepath, type) {
  filepath = toNamespacedPath(filepath)

  if (typeof type === 'string') {
    switch (type) {
      case 'dir':
        type = constants.UV_FS_SYMLINK_DIR
        break
      case 'junction':
        type = constants.UV_FS_SYMLINK_JUNCTION
        break
      case 'file':
      default:
        type = 0
        break
    }
  } else if (typeof type !== 'number') {
    if (isWindows) {
      target = path.resolve(filepath, '..', target)

      try {
        type = statSync(target).isDirectory()
          ? constants.UV_FS_SYMLINK_DIR
          : constants.UV_FS_SYMLINK_JUNCTION
      } catch {
        type = 0
      }
    } else {
      type = 0
    }
  }

  target = normalizeSymlinkTarget(target, type, filepath)

  const req = FileRequest.borrow()

  try {
    binding.symlinkSync(req.handle, target, filepath, type)
  } catch (e) {
    throw new FileError(e.message, {
      operation: 'symlink',
      code: e.code,
      path: target,
      destination: filepath
    })
  } finally {
    req.return()
  }
}

async function opendir(filepath, opts, cb) {
  if (typeof opts === 'function') {
    cb = opts
    opts = {}
  }

  if (typeof opts === 'string') opts = { encoding: opts }
  else if (!opts) opts = {}

  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  let dir
  let err = null
  try {
    binding.opendir(req.handle, filepath)

    await req

    dir = new Dir(filepath, binding.requestResultDir(req.handle), opts)
  } catch (e) {
    err = new FileError(e.message, {
      operation: 'opendir',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }

  return done(err, dir, cb)
}

function opendirSync(filepath, opts) {
  if (typeof opts === 'string') opts = { encoding: opts }
  else if (!opts) opts = {}

  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  try {
    binding.opendirSync(req.handle, filepath)

    return new Dir(filepath, binding.requestResultDir(req.handle), opts)
  } catch (e) {
    throw new FileError(e.message, {
      operation: 'opendir',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }
}

async function readdir(filepath, opts, cb) {
  if (typeof opts === 'function') {
    cb = opts
    opts = {}
  }

  if (typeof opts === 'string') opts = { encoding: opts }
  else if (!opts) opts = {}

  const { withFileTypes = false, recursive = false } = opts

  filepath = toNamespacedPath(filepath)

  const queue = [filepath]
  let result = []
  let err = null
  try {
    while (queue.length !== 0) {
      const dir = await opendir(queue.pop())

      for await (const entry of dir) {
        const entryPath = path.join(entry.parentPath, entry.name)

        if (withFileTypes) {
          result.push(entry)
        } else {
          result.push(path.relative(filepath, entryPath))
        }

        if (recursive && entry.isDirectory()) queue.push(entryPath)
      }
    }
  } catch (e) {
    result = []
    err = e
  }

  return done(err, result, cb)
}

function readdirSync(filepath, opts) {
  if (typeof opts === 'string') opts = { encoding: opts }
  else if (!opts) opts = {}

  const { withFileTypes = false, recursive = false } = opts

  filepath = toNamespacedPath(filepath)

  const queue = [filepath]
  const result = []

  while (queue.length !== 0) {
    const dir = opendirSync(queue.pop(), opts)

    for (const entry of dir) {
      const entryPath = path.join(entry.parentPath, entry.name)

      if (withFileTypes) {
        result.push(entry)
      } else {
        result.push(path.relative(filepath, entryPath))
      }

      if (recursive && entry.isDirectory()) queue.push(entryPath)
    }
  }

  return result
}

async function readFile(filepath, opts, cb) {
  if (typeof opts === 'function') {
    cb = opts
    opts = {}
  }

  if (typeof opts === 'string') opts = { encoding: opts }
  else if (!opts) opts = {}

  const { encoding = 'buffer' } = opts

  let fd = -1
  let buffer = null
  let err = null
  try {
    fd = await open(filepath, opts.flag || 'r')

    const st = await fstat(fd)

    let len = 0

    if (st.size === 0) {
      const buffers = []

      while (true) {
        buffer = Buffer.allocUnsafe(8192)
        const r = await read(fd, buffer)
        len += r
        if (r === 0) break
        buffers.push(buffer.subarray(0, r))
      }

      buffer = Buffer.concat(buffers)
    } else {
      buffer = Buffer.allocUnsafe(st.size)

      while (true) {
        const r = await read(fd, len ? buffer.subarray(len) : buffer)
        len += r
        if (r === 0 || len === buffer.byteLength) break
      }

      if (len !== buffer.byteLength) buffer = buffer.subarray(0, len)
    }

    if (encoding !== 'buffer') buffer = buffer.toString(encoding)
  } catch (e) {
    err = e
  } finally {
    if (fd !== -1) await close(fd)
  }

  return done(err, buffer, cb)
}

function readFileSync(filepath, opts) {
  if (typeof opts === 'string') opts = { encoding: opts }
  else if (!opts) opts = {}

  const { encoding = 'buffer' } = opts

  let fd = -1
  try {
    fd = openSync(filepath, opts.flag || 'r')

    const st = fstatSync(fd)

    let buffer
    let len = 0

    if (st.size === 0) {
      const buffers = []

      while (true) {
        buffer = Buffer.allocUnsafe(8192)
        const r = readSync(fd, buffer)
        len += r
        if (r === 0) break
        buffers.push(buffer.subarray(0, r))
      }

      buffer = Buffer.concat(buffers)
    } else {
      buffer = Buffer.allocUnsafe(st.size)

      while (true) {
        const r = readSync(fd, len ? buffer.subarray(len) : buffer)
        len += r
        if (r === 0 || len === buffer.byteLength) break
      }

      if (len !== buffer.byteLength) buffer = buffer.subarray(0, len)
    }

    if (encoding !== 'buffer') buffer = buffer.toString(encoding)

    return buffer
  } finally {
    if (fd !== -1) closeSync(fd)
  }
}

async function writeFile(filepath, data, opts, cb) {
  if (typeof opts === 'function') {
    cb = opts
    opts = {}
  }

  if (typeof opts === 'string') opts = { encoding: opts }
  else if (!opts) opts = {}

  if (typeof data === 'string') data = Buffer.from(data, opts.encoding)

  let fd = -1
  let len = 0
  let err = null
  try {
    fd = await open(filepath, opts.flag || 'w', opts.mode || 0o666)

    while (true) {
      len += await write(fd, len ? data.subarray(len) : data)
      if (len === data.byteLength) break
    }
  } catch (e) {
    err = e
  } finally {
    if (fd !== -1) await close(fd)
  }

  return done(err, len, cb)
}

function writeFileSync(filepath, data, opts) {
  if (typeof opts === 'string') opts = { encoding: opts }
  else if (!opts) opts = {}

  if (typeof data === 'string') data = Buffer.from(data, opts.encoding)

  let fd = -1
  try {
    fd = openSync(filepath, opts.flag || 'w', opts.mode || 0o666)

    let len = 0

    while (true) {
      len += writeSync(fd, len ? data.subarray(len) : data)
      if (len === data.byteLength) break
    }
  } finally {
    if (fd !== -1) closeSync(fd)
  }
}

function appendFile(filepath, data, opts, cb) {
  if (typeof opts === 'function') {
    cb = opts
    opts = {}
  }

  if (typeof opts === 'string') opts = { encoding: opts }
  else if (!opts) opts = {}

  if (!opts.flag) opts = { ...opts, flag: 'a' }

  return writeFile(filepath, data, opts, cb)
}

function appendFileSync(filepath, data, opts) {
  if (typeof opts === 'string') opts = { encoding: opts }
  else if (!opts) opts = {}

  if (!opts.flag) opts = { ...opts, flag: 'a' }

  return writeFileSync(filepath, data, opts)
}

function watch(filepath, opts, cb) {
  if (typeof opts === 'function') {
    cb = opts
    opts = {}
  }

  if (typeof opts === 'string') opts = { encoding: opts }
  else if (!opts) opts = {}

  filepath = toNamespacedPath(filepath)

  return new Watcher(filepath, opts, cb)
}

class Stats {
  constructor(
    dev,
    mode,
    nlink,
    uid,
    gid,
    rdev,
    blksize,
    ino,
    size,
    blocks,
    atimeMs,
    mtimeMs,
    ctimeMs,
    birthtimeMs
  ) {
    this.dev = dev
    this.mode = mode
    this.nlink = nlink
    this.uid = uid
    this.gid = gid
    this.rdev = rdev
    this.blksize = blksize
    this.ino = ino
    this.size = size
    this.blocks = blocks
    this.atimeMs = atimeMs
    this.mtimeMs = mtimeMs
    this.ctimeMs = ctimeMs
    this.birthtimeMs = birthtimeMs
    this.atime = new Date(atimeMs)
    this.mtime = new Date(mtimeMs)
    this.ctime = new Date(ctimeMs)
    this.birthtime = new Date(birthtimeMs)
  }

  isDirectory() {
    return (this.mode & constants.S_IFMT) === constants.S_IFDIR
  }

  isFile() {
    return (this.mode & constants.S_IFMT) === constants.S_IFREG
  }

  isBlockDevice() {
    return (this.mode & constants.S_IFMT) === constants.S_IFBLK
  }

  isCharacterDevice() {
    return (this.mode & constants.S_IFMT) === constants.S_IFCHR
  }

  isFIFO() {
    return (this.mode & constants.S_IFMT) === constants.S_IFIFO
  }

  isSymbolicLink() {
    return (this.mode & constants.S_IFMT) === constants.S_IFLNK
  }

  isSocket() {
    return (this.mode & constants.S_IFMT) === constants.S_IFSOCK
  }
}

class StatFs {
  constructor(type, bsize, blocks, bfree, bavail, files, ffree, frsize) {
    this.type = type
    this.bsize = bsize
    this.blocks = blocks
    this.bfree = bfree
    this.bavail = bavail
    this.files = files
    this.ffree = ffree
    this.frsize = frsize
  }
}

class Dir {
  constructor(path, handle, opts = {}) {
    const { encoding = 'utf8', bufferSize = 32 } = opts

    this.path = path

    this._encoding = encoding
    this._capacity = bufferSize
    this._buffer = new FIFO()
    this._ended = false
    this._handle = handle
  }

  async read(cb) {
    if (this._buffer.length) return ok(this._buffer.shift(), cb)
    if (this._ended) return ok(null, cb)

    const req = FileRequest.borrow()

    let entries
    let err = null
    try {
      req.retain(binding.readdir(req.handle, this._handle, this._capacity))

      await req

      entries = binding.requestResultDirents(req.handle)
    } catch (e) {
      err = new FileError(e.message, {
        operation: 'readdir',
        code: e.code,
        path: this.path
      })
    } finally {
      req.return()
    }

    if (err) return fail(err, cb)

    if (entries.length === 0) {
      this._ended = true

      return ok(null, cb)
    }

    for (const entry of entries) {
      let name = Buffer.from(entry.name)

      if (this._encoding !== 'buffer') name = name.toString(this._encoding)

      this._buffer.push(new Dirent(this.path, name, entry.type))
    }

    return ok(this._buffer.shift(), cb)
  }

  readSync() {
    if (this._buffer.length) return this._buffer.shift()
    if (this._ended) return null

    const req = FileRequest.borrow()

    let entries
    try {
      req.retain(binding.readdirSync(req.handle, this._handle, this._capacity))

      entries = binding.requestResultDirents(req.handle)
    } catch (e) {
      throw new FileError(e.message, {
        operation: 'readdir',
        code: e.code,
        path: this.path
      })
    } finally {
      req.return()
    }

    if (entries.length === 0) {
      this._ended = true

      return null
    }

    for (const entry of entries) {
      let name = Buffer.from(entry.name)

      if (this._encoding !== 'buffer') name = name.toString(this._encoding)

      this._buffer.push(new Dirent(this.path, name, entry.type))
    }

    return this._buffer.shift()
  }

  async close(cb) {
    const req = FileRequest.borrow()

    let err = null
    try {
      binding.closedir(req.handle, this._handle)

      await req
    } catch (e) {
      err = new FileError(e.message, {
        operation: 'closedir',
        code: e.code,
        path: this.path
      })
    } finally {
      req.return()
    }

    this._handle = null

    return done(err, cb)
  }

  closeSync() {
    const req = FileRequest.borrow()

    try {
      binding.closedirSync(req.handle, this._handle)
    } catch (e) {
      throw new FileError(e.message, {
        operation: 'closedir',
        code: e.code,
        path: this.path
      })
    } finally {
      req.return()
    }

    this._handle = null
  }

  [Symbol.dispose]() {
    this.closeSync()
  }

  async [Symbol.asyncDispose]() {
    await this.close()
  }

  *[Symbol.iterator]() {
    while (true) {
      const entry = this.readSync()
      if (entry === null) break
      yield entry
    }

    this.closeSync()
  }

  async *[Symbol.asyncIterator]() {
    while (true) {
      const entry = await this.read()
      if (entry === null) break
      yield entry
    }

    await this.close()
  }
}

class Dirent {
  constructor(parentPath, name, type) {
    this.parentPath = parentPath
    this.name = name
    this.type = type
  }

  isFile() {
    return this.type === constants.UV_DIRENT_FILE
  }

  isDirectory() {
    return this.type === constants.UV_DIRENT_DIR
  }

  isSymbolicLink() {
    return this.type === constants.UV_DIRENT_LINK
  }

  isFIFO() {
    return this.type === constants.UV_DIRENT_FIFO
  }

  isSocket() {
    return this.type === constants.UV_DIRENT_SOCKET
  }

  isCharacterDevice() {
    return this.type === constants.UV_DIRENT_CHAR
  }

  isBlockDevice() {
    return this.type === constants.UV_DIRENT_BLOCK
  }
}

class FileReadStream extends Readable {
  constructor(path, opts = {}) {
    const { eagerOpen = true } = opts

    super({ eagerOpen, ...opts })

    this.path = path
    this.fd = typeof opts.fd === 'number' ? opts.fd : -1
    this.flags = opts.flags || 'r'
    this.mode = opts.mode || 0o666

    this._offset = opts.start || 0
    this._missing = 0

    if (opts.length) {
      this._missing = opts.length
    } else if (typeof opts.end === 'number') {
      this._missing = opts.end - this._offset + 1
    } else {
      this._missing = -1
    }
  }

  async _open(cb) {
    let err

    if (this.fd === -1) {
      err = null
      try {
        this.fd = await open(this.path, this.flags, this.mode)
      } catch (e) {
        err = e
      }

      if (err) return cb(err)
    }

    let st
    err = null
    try {
      st = await fstat(this.fd)
    } catch (e) {
      err = e
    }

    if (err) return cb(err)

    this._isCharacterDevice = st.isCharacterDevice()

    if (this._isCharacterDevice) {
      this._missing = Infinity
    } else {
      if (this._missing === -1) this._missing = st.size

      if (st.size < this._offset) {
        this._offset = st.size
        this._missing = 0
      } else if (st.size < this._offset + this._missing) {
        this._missing = st.size - this._offset
      }
    }

    cb(null)
  }

  async _read(size) {
    if (this._missing <= 0) return this.push(null)

    const data = Buffer.allocUnsafe(Math.min(this._missing, size))

    let len
    let err = null
    try {
      len = await read(
        this.fd,
        data,
        0,
        data.byteLength,
        this._isCharacterDevice ? -1 : this._offset
      )
    } catch (e) {
      err = e
    }

    if (err) return this.destroy(err)

    if (len === 0) return this.push(null)

    if (this._missing < len) len = this._missing

    this._missing -= len
    this._offset += len

    this.push(data.subarray(0, len))
  }

  async _destroy(err, cb) {
    if (this.fd === -1) return cb(err)

    try {
      await close(this.fd)
    } catch (e) {
      err = err || e
    }

    cb(err)
  }
}

class FileWriteStream extends Writable {
  constructor(path, opts = {}) {
    const { eagerOpen = true } = opts

    super({ eagerOpen, ...opts })

    this.path = path
    this.fd = typeof opts.fd === 'number' ? opts.fd : -1
    this.flags = opts.flags || 'w'
    this.mode = opts.mode || 0o666
  }

  async _open(cb) {
    if (this.fd !== -1) return cb(null)

    let err = null
    try {
      this.fd = await open(this.path, this.flags, this.mode)
    } catch (e) {
      err = e
    }

    cb(err)
  }

  async _writev(batch, cb) {
    let err = null
    try {
      await writev(
        this.fd,
        batch.map(({ chunk }) => chunk)
      )
    } catch (e) {
      err = e
    }

    cb(err)
  }

  async _destroy(err, cb) {
    if (this.fd === -1) return cb(err)

    try {
      await close(this.fd)
    } catch (e) {
      err = err || e
    }

    cb(err)
  }
}

class Watcher extends EventEmitter {
  constructor(path, opts, onchange) {
    if (typeof opts === 'function') {
      onchange = opts
      opts = {}
    }

    if (!opts) opts = {}

    const { persistent = true, recursive = false, encoding = 'utf8' } = opts

    super()

    this._closed = false
    this._encoding = encoding
    this._handle = binding.watcherInit(path, recursive, this, this._onevent, this._onclose)

    if (!persistent) this.unref()

    if (onchange) this.on('change', onchange)
  }

  close() {
    if (this._closed) return
    this._closed = true

    binding.watcherClose(this._handle)
  }

  ref() {
    if (this._handle) binding.watcherRef(this._handle)
    return this
  }

  unref() {
    if (this._handle) binding.watcherUnref(this._handle)
    return this
  }

  [Symbol.asyncIterator]() {
    const buffer = []
    let done = false
    let error = null
    let next = null

    this.on('change', (eventType, filename) => {
      if (next) {
        next.resolve({ done: false, value: { eventType, filename } })
        next = null
      } else {
        buffer.push({ eventType, filename })
      }
    })
      .on('error', (err) => {
        done = true
        error = err

        if (next) {
          next.reject(error)
          next = null
        }
      })
      .on('close', () => {
        done = true

        if (next) {
          next.resolve({ done })
          next = null
        }
      })

    return {
      next: () =>
        new Promise((resolve, reject) => {
          if (error) return reject(error)

          if (buffer.length) return resolve({ done: false, value: buffer.shift() })

          if (done) return resolve({ done })

          next = { resolve, reject }
        })
    }
  }

  _onevent(err, events, filename) {
    if (err) {
      this.close()
      this.emit('error', err)
    } else {
      const path =
        this._encoding === 'buffer'
          ? Buffer.from(filename)
          : Buffer.from(filename).toString(this._encoding)

      if (events & binding.UV_RENAME) {
        this.emit('change', 'rename', path)
      }

      if (events & binding.UV_CHANGE) {
        this.emit('change', 'change', path)
      }
    }
  }

  _onclose() {
    this._handle = null

    this.emit('close')
  }
}

exports.access = access
exports.appendFile = appendFile
exports.chmod = chmod
exports.chown = chown
exports.close = close
exports.copyFile = copyFile
exports.cp = cp
exports.exists = exists
exports.fchmod = fchmod
exports.fchown = fchown
exports.fdatasync = fdatasync
exports.fstat = fstat
exports.fsync = fsync
exports.ftruncate = ftruncate
exports.futimes = futimes
exports.lchown = lchown
exports.lutimes = lutimes
exports.link = link
exports.lstat = lstat
exports.mkdir = mkdir
exports.mkdtemp = mkdtemp
exports.open = open
exports.opendir = opendir
exports.read = read
exports.readFile = readFile
exports.readdir = readdir
exports.readlink = readlink
exports.readv = readv
exports.realpath = realpath
exports.rename = rename
exports.rm = rm
exports.rmdir = rmdir
exports.stat = stat
exports.statfs = statfs
exports.symlink = symlink
exports.truncate = truncate
exports.unlink = unlink
exports.utimes = utimes
exports.watch = watch
exports.write = write
exports.writeFile = writeFile
exports.writev = writev

exports.accessSync = accessSync
exports.appendFileSync = appendFileSync
exports.chmodSync = chmodSync
exports.chownSync = chownSync
exports.closeSync = closeSync
exports.copyFileSync = copyFileSync
exports.cpSync = cpSync
exports.existsSync = existsSync
exports.fchmodSync = fchmodSync
exports.fchownSync = fchownSync
exports.fdatasyncSync = fdatasyncSync
exports.fstatSync = fstatSync
exports.fsyncSync = fsyncSync
exports.ftruncateSync = ftruncateSync
exports.futimesSync = futimesSync
exports.lchownSync = lchownSync
exports.lutimesSync = lutimesSync
exports.linkSync = linkSync
exports.lstatSync = lstatSync
exports.mkdirSync = mkdirSync
exports.mkdtempSync = mkdtempSync
exports.openSync = openSync
exports.opendirSync = opendirSync
exports.readFileSync = readFileSync
exports.readSync = readSync
exports.readdirSync = readdirSync
exports.readlinkSync = readlinkSync
exports.readvSync = readvSync
exports.realpathSync = realpathSync
exports.renameSync = renameSync
exports.rmSync = rmSync
exports.rmdirSync = rmdirSync
exports.statSync = statSync
exports.statfsSync = statfsSync
exports.symlinkSync = symlinkSync
exports.truncateSync = truncateSync
exports.unlinkSync = unlinkSync
exports.utimesSync = utimesSync
exports.writeFileSync = writeFileSync
exports.writeSync = writeSync
exports.writevSync = writevSync

exports.promises = require('./promises')

exports.Stats = Stats
exports.StatFs = StatFs
exports.Dir = Dir
exports.Dirent = Dirent
exports.Watcher = Watcher

exports.ReadStream = FileReadStream

exports.createReadStream = function createReadStream(path, opts) {
  return new FileReadStream(path, opts)
}

exports.WriteStream = FileWriteStream

exports.createWriteStream = function createWriteStream(path, opts) {
  return new FileWriteStream(path, opts)
}

function toNamespacedPath(filepath) {
  if (typeof filepath !== 'string') {
    if (isURL(filepath)) filepath = fileURLToPath(filepath)
    else filepath = filepath.toString()
  }

  return path.toNamespacedPath(filepath)
}

function toFlags(flags) {
  switch (flags) {
    case 'r':
      return constants.O_RDONLY
    case 'rs': // Fall through.
    case 'sr':
      return constants.O_RDONLY | constants.O_SYNC
    case 'r+':
      return constants.O_RDWR
    case 'rs+': // Fall through.
    case 'sr+':
      return constants.O_RDWR | constants.O_SYNC

    case 'w':
      return constants.O_TRUNC | constants.O_CREAT | constants.O_WRONLY
    case 'wx': // Fall through.
    case 'xw':
      return constants.O_TRUNC | constants.O_CREAT | constants.O_WRONLY | constants.O_EXCL

    case 'w+':
      return constants.O_TRUNC | constants.O_CREAT | constants.O_RDWR
    case 'wx+': // Fall through.
    case 'xw+':
      return constants.O_TRUNC | constants.O_CREAT | constants.O_RDWR | constants.O_EXCL

    case 'a':
      return constants.O_APPEND | constants.O_CREAT | constants.O_WRONLY
    case 'ax': // Fall through.
    case 'xa':
      return constants.O_APPEND | constants.O_CREAT | constants.O_WRONLY | constants.O_EXCL
    case 'as': // Fall through.
    case 'sa':
      return constants.O_APPEND | constants.O_CREAT | constants.O_WRONLY | constants.O_SYNC

    case 'a+':
      return constants.O_APPEND | constants.O_CREAT | constants.O_RDWR
    case 'ax+': // Fall through.
    case 'xa+':
      return constants.O_APPEND | constants.O_CREAT | constants.O_RDWR | constants.O_EXCL
    case 'as+': // Fall through.
    case 'sa+':
      return constants.O_APPEND | constants.O_CREAT | constants.O_RDWR | constants.O_SYNC
    default:
      return 0
  }
}

function toMode(mode) {
  return parseInt(mode, 8)
}
const binding = require('../binding')

module.exports = {
  O_RDWR: binding.O_RDWR,
  O_RDONLY: binding.O_RDONLY,
  O_WRONLY: binding.O_WRONLY,
  O_CREAT: binding.O_CREAT,
  O_TRUNC: binding.O_TRUNC,
  O_APPEND: binding.O_APPEND,

  F_OK: binding.F_OK || 0,
  R_OK: binding.R_OK || 0,
  W_OK: binding.W_OK || 0,
  X_OK: binding.X_OK || 0,

  S_IFMT: binding.S_IFMT,
  S_IFREG: binding.S_IFREG,
  S_IFDIR: binding.S_IFDIR,
  S_IFCHR: binding.S_IFCHR,
  S_IFLNK: binding.S_IFLNK,
  S_IFBLK: binding.S_IFBLK || 0,
  S_IFIFO: binding.S_IFIFO || 0,
  S_IFSOCK: binding.S_IFSOCK || 0,

  S_IRUSR: binding.S_IRUSR || 0,
  S_IWUSR: binding.S_IWUSR || 0,
  S_IXUSR: binding.S_IXUSR || 0,
  S_IRGRP: binding.S_IRGRP || 0,
  S_IWGRP: binding.S_IWGRP || 0,
  S_IXGRP: binding.S_IXGRP || 0,
  S_IROTH: binding.S_IROTH || 0,
  S_IWOTH: binding.S_IWOTH || 0,
  S_IXOTH: binding.S_IXOTH || 0,

  UV_DIRENT_UNKNOWN: binding.UV_DIRENT_UNKNOWN,
  UV_DIRENT_FILE: binding.UV_DIRENT_FILE,
  UV_DIRENT_DIR: binding.UV_DIRENT_DIR,
  UV_DIRENT_LINK: binding.UV_DIRENT_LINK,
  UV_DIRENT_FIFO: binding.UV_DIRENT_FIFO,
  UV_DIRENT_SOCKET: binding.UV_DIRENT_SOCKET,
  UV_DIRENT_CHAR: binding.UV_DIRENT_CHAR,
  UV_DIRENT_BLOCK: binding.UV_DIRENT_BLOCK,

  COPYFILE_EXCL: binding.UV_FS_COPYFILE_EXCL,
  COPYFILE_FICLONE: binding.UV_FS_COPYFILE_FICLONE,
  COPYFILE_FICLONE_FORCE: binding.UV_FS_COPYFILE_FICLONE_FORCE,
  UV_FS_SYMLINK_DIR: binding.UV_FS_SYMLINK_DIR,
  UV_FS_SYMLINK_JUNCTION: binding.UV_FS_SYMLINK_JUNCTION
}
const binding = require('../binding')

module.exports = class FileError extends Error {
  constructor(msg, opts = {}) {
    const { code, operation = null, path = null, destination = null, fd = -1 } = opts

    if (operation !== null) msg += describe(operation, opts)

    super(`${code}: ${msg}`)

    this.code = code

    if (operation !== null) this.operation = operation
    if (path !== null) this.path = path
    if (destination !== null) this.destination = destination
    if (fd !== -1) this.fd = fd
  }

  get name() {
    return 'FileError'
  }

  // For Node.js compatibility
  get errno() {
    return binding.errnos[this.code]
  }

  // For Node.js compatibility
  get syscall() {
    return this.operation
  }

  // For Node.js compatibility
  get dest() {
    return this.destination
  }
}

function describe(operation, opts) {
  const { path = null, destination = null, fd = -1 } = opts

  let result = `, ${operation}`

  if (path !== null) {
    result += ` ${JSON.stringify(path)}`

    if (destination !== null) {
      result += ` -> ${JSON.stringify(destination)}`
    }
  } else if (fd !== -1) {
    result += ` ${fd}`
  }

  return result
}
{
  "name": "bare-fs",
  "version": "4.8.1",
  "description": "Native file system operations for Bare",
  "exports": {
    "./package": "./package.json",
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    },
    "./promises": {
      "types": "./promises.d.ts",
      "default": "./promises.js"
    },
    "./constants": {
      "types": "./lib/constants.d.ts",
      "default": "./lib/constants.js"
    }
  },
  "files": [
    "index.js",
    "index.d.ts",
    "promises.js",
    "promises.d.ts",
    "binding.c",
    "binding.js",
    "CMakeLists.txt",
    "lib",
    "prebuilds"
  ],
  "addon": true,
  "scripts": {
    "format": "prettier --write . && lunte --fix",
    "lint": "prettier --check . && lunte",
    "test": "brittle-bare --coverage test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-fs.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-fs/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-fs#readme",
  "engines": {
    "bare": ">=1.28.0"
  },
  "dependencies": {
    "bare-events": "^2.5.4",
    "bare-path": "^3.0.0",
    "bare-stream": "^2.6.4",
    "bare-url": "^2.2.2",
    "fast-fifo": "^1.3.2"
  },
  "devDependencies": {
    "bare-buffer": "^3.0.2",
    "bare-crypto": "^1.11.2",
    "brittle": "^3.1.1",
    "cmake-bare": "^1.1.7",
    "lunte": "^1.6.0",
    "prettier": "^3.4.1",
    "prettier-config-holepunch": "^2.0.0"
  },
  "peerDependencies": {
    "bare-buffer": "*"
  },
  "peerDependenciesMeta": {
    "bare-buffer": {
      "optional": true
    }
  }
}
const EventEmitter = require('bare-events')
const fs = require('.')

class FileHandle extends EventEmitter {
  constructor(fd) {
    super()

    this.fd = fd
  }

  async close() {
    await fs.close(this.fd)

    this.fd = -1
    this.emit('close')
  }

  async read(buffer, ...args) {
    return {
      bytesRead: await fs.read(this.fd, buffer, ...args),
      buffer
    }
  }

  async readv(buffers, ...args) {
    return {
      bytesRead: await fs.readv(this.fd, buffers, ...args),
      buffers
    }
  }

  async write(buffer, ...args) {
    return {
      bytesWritten: await fs.write(this.fd, buffer, ...args),
      buffer
    }
  }

  async writev(buffers, ...args) {
    return {
      bytesWritten: await fs.writev(this.fd, buffers, ...args),
      buffers
    }
  }

  async stat() {
    return fs.fstat(this.fd)
  }

  async chmod(mode) {
    await fs.fchmod(this.fd, mode)
  }

  async chown(uid, gid) {
    await fs.fchown(this.fd, uid, gid)
  }

  async datasync() {
    return fs.fdatasync(this.fd)
  }

  async sync() {
    return fs.fsync(this.fd)
  }

  async truncate(len) {
    await fs.ftruncate(this.fd, len)
  }

  async utimes(atime, mtime) {
    await fs.futimes(this.fd, atime, mtime)
  }

  createReadStream(opts) {
    return fs.createReadStream(null, { ...opts, fd: this.fd })
  }

  createWriteStream(opts) {
    return fs.createWriteStream(null, { ...opts, fd: this.fd })
  }

  async [Symbol.asyncDispose]() {
    await this.close()
  }
}

exports.open = async function open(filepath, flags, mode) {
  return new FileHandle(await fs.open(filepath, flags, mode))
}

exports.access = fs.access
exports.appendFile = fs.appendFile
exports.chmod = fs.chmod
exports.chown = fs.chown
exports.constants = fs.constants
exports.copyFile = fs.copyFile
exports.cp = fs.cp
exports.lchown = fs.lchown
exports.lutimes = fs.lutimes
exports.link = fs.link
exports.lstat = fs.lstat
exports.mkdir = fs.mkdir
exports.mkdtemp = fs.mkdtemp
exports.opendir = fs.opendir
exports.readFile = fs.readFile
exports.readdir = fs.readdir
exports.readlink = fs.readlink
exports.realpath = fs.realpath
exports.rename = fs.rename
exports.rm = fs.rm
exports.rmdir = fs.rmdir
exports.stat = fs.stat
exports.statfs = fs.statfs
exports.truncate = fs.truncate
exports.symlink = fs.symlink
exports.unlink = fs.unlink
exports.utimes = fs.utimes
exports.watch = fs.watch
exports.writeFile = fs.writeFile
module.exports = require.addon()
const ansiEscapes = require('bare-ansi-escapes')
const getType = require('bare-type')
const binding = require('./binding')

const defaultDepth = 2
const defaultBreakLength = 80
const defaultMaxArrayLength = 40

module.exports = exports = function inspect(value, opts = {}) {
  const {
    colors = false,
    depth = defaultDepth,
    breakLength = defaultBreakLength,
    stylize = defaultStylize(colors)
  } = opts

  const references = new InspectRefMap()

  const tree = inspectValue(value, 0, {
    colors,
    depth,
    breakLength,
    stylize,
    references
  })

  return tree.toString()
}

exports.styles = {
  bigint: ansiEscapes.colorYellow,
  boolean: ansiEscapes.colorYellow,
  date: ansiEscapes.colorMagenta,
  module: ansiEscapes.modifierUnderline,
  name: ansiEscapes.modifierReset,
  null: ansiEscapes.modifierBold,
  number: ansiEscapes.colorYellow,
  regexp: ansiEscapes.colorRed,
  special: ansiEscapes.colorCyan,
  string: ansiEscapes.colorGreen,
  symbol: ansiEscapes.colorGreen,
  undefined: ansiEscapes.colorBrightBlack
}

const styles = exports.styles

function defaultStylize(colors) {
  return function stylize(value, style) {
    const color = colors && styles[style]

    if (color) return color + value + ansiEscapes.modifierReset

    return value
  }
}

class InspectRefMap {
  constructor() {
    this.refs = new WeakMap()
    this.ids = new WeakMap()
    this.nextId = 1
  }

  has(object) {
    return this.refs.has(object)
  }

  get(object) {
    return this.refs.get(object) || null
  }

  set(object, ref) {
    this.refs.set(object, ref)
  }

  id(object) {
    let id = this.ids.get(object)
    if (id) return id

    id = this.nextId++
    this.ids.set(object, id)

    return id
  }
}

class InspectNode {
  constructor(depth, length, opts) {
    const { breakLength = defaultBreakLength, breakAlways = false } = opts

    this.depth = depth
    this.length = length
    this.breakLength = breakLength
    this.breakAlways = breakAlways
  }

  pad(n, string) {
    return string.padStart(n, ' ')
  }

  indent(n, string) {
    return '  '.repeat(n) + string
  }
}

class InspectRef extends InspectNode {
  constructor(depth, opts) {
    super(depth, '[circular *]'.length, opts)

    this.refs = opts.references
    this.count = 0
    this.circular = false
    this.color = opts.colors && styles.special
  }

  get id() {
    return this.refs.id(this)
  }

  increment() {
    return ++this.count
  }

  decrement() {
    return --this.count
  }

  toString(opts = {}) {
    const { offset = 0, pad = 0, indent = 0 } = opts

    let value = this.pad(pad, '[circular *' + this.id + ']')

    if (this.color) value = this.color + value + ansiEscapes.modifierReset

    return offset ? value : this.indent(indent, value)
  }
}

class InspectLeaf extends InspectNode {
  constructor(value, color, depth, opts) {
    const length = value.length

    if (value.includes('\n')) {
      value = value.replaceAll('\n', '\n' + '  '.repeat(depth))

      opts = { ...opts, breakAlways: true }
    }

    super(depth, length, opts)

    this.value = value
    this.color = opts.colors && color
  }

  toString(opts = {}) {
    const { offset = 0, pad = 0, indent = 0 } = opts

    let value = this.pad(pad, this.value)

    if (this.color) value = this.color + value + ansiEscapes.modifierReset

    return offset ? value : this.indent(indent, value)
  }
}

class InspectPair extends InspectNode {
  constructor(delim, left, right, depth, opts) {
    const length = left.length + delim.length + right.length

    if (left.breakAlways || right.breakAlways) {
      opts = { ...opts, breakAlways: true }
    }

    super(depth, length, opts)

    this.delim = delim
    this.left = left
    this.right = right
  }

  toString(opts = {}) {
    const { indent = 0 } = opts

    return this.indent(
      indent,
      this.left +
        this.delim +
        this.right.toString({
          indent,
          offset: this.left.length + this.delim.length
        })
    )
  }
}

class InspectSuspension extends InspectNode {
  constructor(overflow, depth, opts) {
    const label = `... ${overflow} more`

    super(depth, label.length, opts)

    this.label = label
  }

  toString(opts = {}) {
    const { indent = 0 } = opts

    return this.indent(indent, this.label)
  }
}

class InspectSequence extends InspectNode {
  constructor(header, footer, delim, values, ref, depth, opts) {
    const { tabulate = false } = opts

    const length =
      (ref.circular ? '<ref *>'.length + 1 : 0) +
      header.length +
      values.reduce((length, value, i) => length + value.length + (i === 0 ? 0 : delim.length), 0) +
      footer.length

    if (values.some((value) => value.breakAlways)) {
      opts = { ...opts, breakAlways: true }
    }

    super(depth, length, opts)

    this.header = header
    this.footer = footer
    this.delim = delim
    this.values = values
    this.ref = ref
    this.tabulate = tabulate
  }

  toString(opts = {}) {
    const { offset = 0, indent = 0 } = opts

    const split =
      this.breakAlways ||
      (this.values.length &&
        (offset + this.length > this.breakLength || indent * 2 + this.length > this.breakLength))

    let header = this.header

    if (this.ref.circular) {
      header = '<ref *' + this.ref.id + '> ' + header
    }

    if (this.values.length === 0) {
      header = header.trimEnd()
    }

    if (offset === 0) {
      header = this.indent(indent, header)
    }

    if (split) {
      header = header.trimEnd() + '\n'
    }

    let string = header

    let columns = 1
    let pad = 0

    if (this.tabulate) {
      const widest = this.values.reduce(
        (length, value) => (value.breakAlways ? length : Math.max(length, value.length)),
        0
      )

      if (widest) {
        columns = Math.max(
          columns,
          Math.floor((this.breakLength - indent * 2) / (widest + this.delim.length))
        )

        if (columns > 1) pad = widest
      }
    }

    for (let i = 0, n = this.values.length, offset = 0; i < n; i++) {
      const value = this.values[i]

      if (split) {
        let part

        if (i % columns === 0 || value.breakAlways) {
          part = value.toString({ indent: indent + 1, pad })
        } else {
          part = value.toString({ pad })
        }

        string += part

        if (i < n - 1) {
          if (i % columns === columns - 1 || this.values[i + 1].breakAlways) {
            string += this.delim.trimEnd() + '\n'
          } else {
            string += this.delim
          }
        }
      } else {
        if (i > 0) string += this.delim

        string += value.toString({ offset })

        // TODO Lunte thinks this refers to `const { offset }...` further up
        offset += value.length // eslint-disable-line
      }
    }

    let footer = this.footer

    if (this.values.length === 0) {
      footer = footer.trimStart()
    }

    if (split) {
      string += '\n' + this.indent(indent, footer.trimStart())
    } else {
      string += footer
    }

    return string
  }
}

function inspectValue(value, depth, opts) {
  const type = getType(value)

  if (type.isUndefined()) return inspectUndefined(depth, opts)
  if (type.isNull()) return inspectNull(depth, opts)
  if (type.isBoolean()) return inspectBoolean(value, depth, opts)
  if (type.isNumber()) return inspectNumber(value, depth, opts)
  if (type.isBigInt()) return inspectBigInt(value, depth, opts)
  if (type.isString()) return inspectString(value, depth, opts)
  if (type.isSymbol()) return inspectSymbol(value, depth, opts)
  if (type.isObject()) return inspectObject(type, value, depth, opts)
  if (type.isFunction()) return inspectFunction(type, value, depth, opts)
  if (type.isExternal()) return inspectExternal(value, depth, opts)
}

function inspectUndefined(depth, opts) {
  return new InspectLeaf('undefined', styles.undefined, depth, opts)
}

function inspectNull(depth, opts) {
  return new InspectLeaf('null', styles.null, depth, opts)
}

function inspectBoolean(value, depth, opts) {
  return new InspectLeaf(value.toString(), styles.boolean, depth, opts)
}

function inspectNumber(value, depth, opts) {
  let string

  if (Object.is(value, -0)) {
    string = '-0'
  } else {
    string = value.toString(10)
  }

  return new InspectLeaf(string, styles.number, depth, opts)
}

function inspectBigInt(value, depth, opts) {
  return new InspectLeaf(value.toString(10) + 'n', styles.bigint, depth, opts)
}

const STRING_ESCAPES = /[\ud800-\udbff][\udc00-\udfff]|[\u0000-\u001f'\\\ud800-\udfff]/g

function inspectString(value, depth, opts) {
  // https://tc39.es/ecma262/multipage/structured-data.html#sec-quotejsonstring
  const string = value.replace(STRING_ESCAPES, (match) => {
    if (match.length === 2) return match

    switch (match) {
      case "'":
        return "\\'"
      case '\\':
        return '\\\\'
      case '\b':
        return '\\b'
      case '\t':
        return '\\t'
      case '\n':
        return '\\n'
      case '\f':
        return '\\f'
      case '\r':
        return '\\r'
      default:
        return '\\u' + match.charCodeAt(0).toString(16).padStart(4, '0')
    }
  })

  return new InspectLeaf("'" + string + "'", styles.string, depth, opts)
}

function inspectSymbol(value, depth, opts) {
  return new InspectLeaf(value.toString(), styles.symbol, depth, opts)
}

const PLAIN_KEY = /^[a-zA-Z_][a-zA-Z_0-9]*$/

function inspectKey(value, depth, opts) {
  if (typeof value === 'symbol') {
    return inspectValue(value, depth, opts)
  } else if (PLAIN_KEY.test(value)) {
    return new InspectLeaf(value, null, depth, opts)
  } else {
    return inspectValue(value, depth, opts)
  }
}

function inspectObject(type, object, depth, opts) {
  const refs = opts.references

  let ref = refs.get(object)
  if (ref === null) {
    ref = new InspectRef(depth, opts)
    refs.set(object, ref)
  } else if (ref.count) {
    ref.circular = true
    return ref
  }

  const maxDepth = typeof opts.depth === 'number' ? opts.depth : Infinity

  if (maxDepth < depth) {
    const name = nameOf(object)

    return new InspectLeaf('[' + (name || 'Object') + ']', styles.special, depth, opts)
  }

  const { value: inspect } = attempt(
    () => object[Symbol.for('bare.inspect')] || object[Symbol.for('nodejs.util.inspect.custom')]
  )

  if (typeof inspect === 'function' && isPrototype(object) === false) {
    const { value, threw, error } = attempt(() =>
      inspect.call(
        object,
        typeof opts.depth === 'number' ? opts.depth - depth : null,
        {
          colors: opts.colors,
          breakLength: opts.breakLength,
          stylize: opts.stylize
        },
        exports
      )
    )

    if (threw) {
      return new InspectLeaf(
        '<inspect threw ' + errorLabel(error) + '>',
        styles.special,
        depth,
        opts
      )
    }

    if (typeof value === 'object' && value !== null) {
      refs.set(value, ref)
    }

    if (typeof value !== 'string') {
      return inspectValue(value, depth, opts)
    }

    return new InspectLeaf(value, null, depth, opts)
  }

  if (type.isArray()) return inspectArray(object, ref, depth, opts)
  if (type.isDate()) return inspectDate(object, ref, depth, opts)
  if (type.isRegExp()) return inspectRegExp(object, ref, depth, opts)
  if (type.isError()) return inspectError(object, ref, depth, opts)
  if (type.isPromise()) return inspectPromise(object, ref, depth, opts)
  if (type.isMap()) return inspectMap(object, ref, depth, opts)
  if (type.isSet()) return inspectSet(object, ref, depth, opts)
  if (type.isWeakMap()) return inspectWeakMap(object, ref, depth, opts)
  if (type.isWeakSet()) return inspectWeakSet(object, ref, depth, opts)
  if (type.isWeakRef()) return inspectWeakRef(object, ref, depth, opts)
  if (type.isArrayBuffer()) return inspectArrayBuffer(object, ref, depth, opts)
  if (type.isSharedArrayBuffer()) return inspectSharedArrayBuffer(object, ref, depth, opts)
  if (type.isTypedArray()) return inspectTypedArray(object, ref, depth, opts)
  if (type.isDataView()) return inspectDataView(object, ref, depth, opts)

  ref.increment()

  const values = []

  for (const key of keysOf(object)) {
    if (key === 'constructor') continue

    values.push(
      new InspectPair(
        ': ',
        inspectKey(key, depth + 1, opts),
        inspectProperty(object, key, depth + 1, opts),
        depth + 1,
        opts
      )
    )
  }

  for (const key of symbolsOf(object)) {
    values.push(
      new InspectPair(
        ': ',
        inspectKey(key, depth + 1, opts),
        inspectProperty(object, key, depth + 1, opts),
        depth + 1,
        opts
      )
    )
  }

  ref.decrement()

  let header = '{ '

  const { value: tag } = attempt(() => object[Symbol.toStringTag])

  if (typeof tag === 'string' && tag) header = '[' + tag + '] ' + header

  const name = nameOf(object)

  if (name && name !== 'Object') header = name + ' ' + header

  return new InspectSequence(header, ' }', ', ', values, ref, depth, opts)
}

function inspectArray(array, ref, depth, opts) {
  const { maxArrayLength = defaultMaxArrayLength } = opts

  ref.increment()

  const values = []

  let remaining = Math.max(maxArrayLength, 0)

  for (let i = 0, n = array.length; i < n; i++) {
    if (remaining-- === 0) {
      values.push(
        new InspectSuspension(array.length - values.length, depth + 1, {
          ...opts,
          breakAlways: true
        })
      )

      break
    }

    values.push(inspectProperty(array, i, depth + 1, opts))
  }

  for (const key of nonIndexKeysOf(array)) {
    values.push(
      new InspectPair(
        ': ',
        inspectKey(key, depth + 1, opts),
        inspectProperty(array, key, depth + 1, opts),
        depth + 1,
        { ...opts, breakAlways: remaining < 0 }
      )
    )
  }

  ref.decrement()

  let header = '[ '

  const name = nameOf(array)

  if (name && name !== 'Array') header = name + '(' + array.length + ') ' + header

  return new InspectSequence(header, ' ]', ', ', values, ref, depth, {
    ...opts,
    tabulate: true
  })
}

function inspectProperty(object, key, depth, opts) {
  const descriptor = descriptorOf(object, key)

  if (descriptor === null) {
    return new InspectLeaf('<unreadable>', styles.special, depth, opts)
  }

  if (descriptor === undefined) return inspectUndefined(depth, opts)

  if ('value' in descriptor === false) {
    let label = '[getter/setter]'

    if (descriptor.get === undefined) label = '[setter]'
    else if (descriptor.set === undefined) label = '[getter]'

    return new InspectLeaf(label, styles.special, depth, opts)
  }

  return inspectValue(descriptor.value, depth, opts)
}

function inspectDate(date, ref, depth, opts) {
  const { value: time } = attempt(() => date.getTime())

  if (Number.isNaN(time)) {
    return new InspectLeaf('Invalid Date', styles.date, depth, opts)
  }

  const { value: string } = attempt(() => date.toISOString())

  return new InspectLeaf(string || '[Date]', styles.date, depth, opts)
}

function inspectRegExp(regExp, ref, depth, opts) {
  const { value: string } = attempt(() => regExp.toString())

  return new InspectLeaf(string || '[RegExp]', styles.regexp, depth, opts)
}

function inspectError(error, ref, depth, opts) {
  let header = attempt(() => error.stack).value

  if (typeof header === 'string') {
    // Not all engines put the `<name>: <message>` line at the top of `stack`,
    // so add it when it's missing.
    const title = attempt(() => error.toString()).value

    if (typeof title === 'string' && !header.startsWith(title)) {
      header = title + '\n' + header
    }

    if (depth > 0) {
      header = header.replaceAll('\n', '\n' + '  '.repeat(depth))
    }
  } else {
    header = attempt(() => error.toString()).value

    if (typeof header !== 'string') header = errorLabel(error)
  }

  const builtins = ['cause']

  const { value: name } = attempt(() => error.name)

  if (name === 'AggregateError') {
    builtins.push('errors')
  } else if (name === 'SuppressedError') {
    builtins.push('error', 'suppressed')
  }

  const values = []

  for (const key of builtins) {
    if (descriptorOf(error, key) === undefined) continue

    values.push(
      new InspectPair(
        ': ',
        new InspectLeaf('[' + key + ']', null, depth + 1, opts),
        inspectProperty(error, key, depth + 1, opts),
        depth + 1,
        opts
      )
    )
  }

  for (const key of keysOf(error)) {
    if (key === 'constructor' || builtins.includes(key)) continue

    values.push(
      new InspectPair(
        ': ',
        inspectKey(key, depth + 1, opts),
        inspectProperty(error, key, depth + 1, opts),
        depth + 1,
        opts
      )
    )
  }

  if (values.length === 0) return new InspectLeaf(header, null, depth, opts)

  return new InspectSequence(header + ' {', ' }', ', ', values, ref, depth, opts)
}

function inspectPromise(promise, ref, depth, opts) {
  ref.increment()

  const state = binding.getPromiseState(promise)

  const values = []

  switch (state) {
    case 0: // Pending
      values.push(new InspectLeaf('<pending>', styles.special, depth, opts))
      break

    case 1: // Fulfilled
      values.push(inspectValue(binding.getPromiseResult(promise), depth, opts))
      break

    case 2: // Rejected
      values.push(
        new InspectLeaf('<rejected>', styles.special, depth, opts),
        inspectValue(binding.getPromiseResult(promise), depth, opts)
      )
  }

  ref.decrement()

  const header = (nameOf(promise) || 'Promise') + ' { '

  return new InspectSequence(header, ' }', ' ', values, ref, depth, opts)
}

function inspectMap(map, ref, depth, opts) {
  const { maxArrayLength = defaultMaxArrayLength, maxMapLength = maxArrayLength } = opts

  ref.increment()

  const values = []

  let remaining = maxMapLength

  const { value: size } = attempt(() => map.size)

  attempt(() => {
    for (const entry of map) {
      if (remaining-- === 0) {
        values.push(
          new InspectSuspension(size - values.length, depth + 1, {
            ...opts,
            breakAlways: true
          })
        )
        break
      }

      values.push(
        new InspectPair(
          ' => ',
          inspectValue(entry[0], depth + 1, opts),
          inspectValue(entry[1], depth + 1, opts),
          depth + 1,
          opts
        )
      )
    }
  })

  for (const key of keysOf(map)) {
    if (key === 'constructor') continue

    const value = inspectProperty(map, key, depth + 1, opts)

    values.push(
      new InspectPair(': ', inspectKey(key, depth + 1, opts), value, depth + 1, {
        ...opts,
        breakAlways: remaining < 0
      })
    )
  }

  ref.decrement()

  const header = (nameOf(map) || 'Map') + '(' + size + ') { '

  return new InspectSequence(header, ' }', ', ', values, ref, depth, {
    ...opts,
    tabulate: true
  })
}

function inspectSet(set, ref, depth, opts) {
  const { maxArrayLength = defaultMaxArrayLength, maxSetLength = maxArrayLength } = opts

  ref.increment()

  const values = []

  let remaining = maxSetLength

  const { value: size } = attempt(() => set.size)

  attempt(() => {
    for (const entry of set) {
      if (remaining-- === 0) {
        values.push(
          new InspectSuspension(size - values.length, depth + 1, {
            ...opts,
            breakAlways: true
          })
        )
        break
      }

      values.push(inspectValue(entry, depth + 1, opts))
    }
  })

  for (const key of keysOf(set)) {
    if (key === 'constructor') continue

    const value = inspectProperty(set, key, depth + 1, opts)

    values.push(
      new InspectPair(': ', inspectKey(key, depth + 1, opts), value, depth + 1, {
        ...opts,
        breakAlways: remaining < 0
      })
    )
  }

  ref.decrement()

  const header = (nameOf(set) || 'Set') + '(' + size + ') { '

  return new InspectSequence(header, ' }', ', ', values, ref, depth, {
    ...opts,
    tabulate: true
  })
}

function inspectWeakMap(weakMap, ref, depth, opts) {
  const header = (nameOf(weakMap) || 'WeakMap') + ' { '

  return new InspectSequence(
    header,
    ' }',
    ' ',
    [new InspectLeaf('<items unknown>', styles.special, depth + 1, opts)],
    ref,
    depth,
    opts
  )
}

function inspectWeakSet(weakSet, ref, depth, opts) {
  const header = (nameOf(weakSet) || 'WeakSet') + ' { '

  return new InspectSequence(
    header,
    ' }',
    ' ',
    [new InspectLeaf('<items unknown>', styles.special, depth + 1, opts)],
    ref,
    depth,
    opts
  )
}

function inspectWeakRef(weakRef, ref, depth, opts) {
  const { value: target } = attempt(() => weakRef.deref())

  let value

  if (target === undefined) {
    value = new InspectLeaf('<cleared>', styles.special, depth + 1, opts)
  } else {
    value = inspectValue(target, depth + 1, opts)
  }

  const header = (nameOf(weakRef) || 'WeakRef') + ' { '

  return new InspectSequence(header, ' }', ' ', [value], ref, depth, opts)
}

function inspectArrayBuffer(arrayBuffer, ref, depth, opts) {
  ref.increment()

  const values = []

  for (const key of ['byteLength']) {
    values.push(
      new InspectPair(
        ': ',
        inspectKey(key, depth + 1, opts),
        inspectValue(arrayBuffer[key], depth + 1, opts),
        depth + 1,
        opts
      )
    )
  }

  ref.decrement()

  const header = (nameOf(arrayBuffer) || 'ArrayBuffer') + ' { '

  return new InspectSequence(header, ' }', ', ', values, ref, depth, opts)
}

function inspectSharedArrayBuffer(sharedArrayBuffer, ref, depth, opts) {
  ref.increment()

  const values = []

  for (const key of ['byteLength']) {
    values.push(
      new InspectPair(
        ': ',
        inspectKey(key, depth + 1, opts),
        inspectValue(sharedArrayBuffer[key], depth + 1, opts),
        depth + 1,
        opts
      )
    )
  }

  ref.decrement()

  const header = (nameOf(sharedArrayBuffer) || 'SharedArrayBuffer') + ' { '

  return new InspectSequence(header, ' }', ', ', values, ref, depth, opts)
}

function inspectTypedArray(typedArray, ref, depth, opts) {
  if (Buffer.isBuffer(typedArray)) {
    return inspectBuffer(typedArray, ref, depth, opts)
  }

  const { maxArrayLength = defaultMaxArrayLength, maxTypedArrayLength = maxArrayLength } = opts

  ref.increment()

  const values = []

  let remaining = Math.max(maxTypedArrayLength, 0)

  for (let i = 0, n = typedArray.length; i < n; i++) {
    if (remaining-- === 0) {
      values.push(
        new InspectSuspension(typedArray.length - values.length, depth + 1, {
          ...opts,
          breakAlways: true
        })
      )

      break
    }

    values.push(inspectValue(typedArray[i], depth + 1, opts))
  }

  for (const key of nonIndexKeysOf(typedArray)) {
    values.push(
      new InspectPair(
        ': ',
        inspectKey(key, depth + 1, opts),
        inspectProperty(typedArray, key, depth + 1, opts),
        depth + 1,
        { ...opts, breakAlways: remaining < 0 }
      )
    )
  }

  ref.decrement()

  const header = (nameOf(typedArray) || 'TypedArray') + '(' + typedArray.length + ') [ '

  return new InspectSequence(header, ' ]', ', ', values, ref, depth, {
    ...opts,
    tabulate: true
  })
}

function inspectBuffer(buffer, ref, depth, opts) {
  const { maxArrayLength = defaultMaxArrayLength, maxBufferLength = maxArrayLength } = opts

  ref.increment()

  const values = []

  let remaining = Math.max(maxBufferLength, 0)

  for (let i = 0, n = buffer.byteLength; i < n; i++) {
    if (remaining-- === 0) {
      values.push(
        new InspectSuspension(buffer.length - values.length, depth + 1, {
          ...opts,
          breakAlways: true
        })
      )

      break
    }

    values.push(new InspectLeaf(buffer[i].toString(16).padStart(2, '0'), null, depth + 1, opts))
  }

  for (const key of nonIndexKeysOf(buffer)) {
    values.push(
      new InspectPair(
        ': ',
        inspectKey(key, depth + 1, opts),
        inspectProperty(buffer, key, depth + 1, opts),
        depth + 1,
        { ...opts, breakAlways: remaining < 0 }
      )
    )
  }

  ref.decrement()

  return new InspectSequence('<Buffer ', '>', ' ', values, ref, depth, {
    ...opts,
    tabulate: true
  })
}

function inspectDataView(dataView, ref, depth, opts) {
  ref.increment()

  const values = []

  for (const key of ['byteLength', 'byteOffset', 'buffer']) {
    values.push(
      new InspectPair(
        ': ',
        inspectKey(key, depth + 1, opts),
        inspectValue(dataView[key], depth + 1, opts),
        depth + 1,
        opts
      )
    )
  }

  for (const key of nonIndexKeysOf(dataView)) {
    values.push(
      new InspectPair(
        ': ',
        inspectKey(key, depth + 1, opts),
        inspectProperty(dataView, key, depth + 1, opts),
        depth + 1,
        opts
      )
    )
  }

  ref.decrement()

  const header = (nameOf(dataView) || 'DataView') + ' { '

  return new InspectSequence(header, ' }', ', ', values, ref, depth, opts)
}

function inspectFunction(type, fn, depth, opts) {
  const { value: source } = attempt(() => fn.toString())

  if (typeof source === 'string' && source.startsWith('class')) {
    return inspectClass(fn, depth, opts)
  }

  let tag = 'function'

  if (type.isGeneratorFunction()) tag = 'generator ' + tag
  if (type.isAsyncFunction()) tag = 'async ' + tag

  const { value: name } = attempt(() => fn.name)

  return new InspectLeaf(
    '[' + tag + ' ' + (name ? name : '(anonymous)') + ']',
    styles.special,
    depth,
    opts
  )
}

function inspectClass(ctor, depth, opts) {
  const { value: name } = attempt(() => ctor.name)

  return new InspectLeaf(
    '[class ' + (name ? name : '(anonymous)') + ']',
    styles.special,
    depth,
    opts
  )
}

// Externals are labelled by an opaque, per-process token so that their address
// is never disclosed.
const externalTokens = new WeakMap()

let nextExternalToken = 1

function inspectExternal(external, depth, opts) {
  let token = externalTokens.get(external)

  if (token === undefined) {
    token = nextExternalToken++

    externalTokens.set(external, token)
  }

  return new InspectLeaf('[external #' + token + ']', styles.special, depth, opts)
}

// An inspection must not fail on the value it inspects, so everything read from
// that value is read through one of the helpers below. Values are read for
// their own sake, which is why an accessor is reported rather than called, but
// the labels an inspection puts around them are read as they are written.

function attempt(fn) {
  try {
    return { value: fn(), threw: false, error: null }
  } catch (err) {
    return { value: undefined, threw: true, error: err }
  }
}

// The name a value is labelled with, which is the name of its constructor.
function nameOf(object) {
  const { value: name } = attempt(() => object.constructor.name)

  return typeof name === 'string' ? name : ''
}

// A prototype inherits the custom inspection written for its instances, which
// must not be called on the prototype itself as it is not one of them.
function isPrototype(object) {
  const { value: prototype } = attempt(() => object.constructor.prototype)

  return prototype === object
}

function keysOf(object) {
  const keys = []

  // A partial list is the best that can be had from a value that stops
  // answering halfway through.
  attempt(() => {
    for (const key in object) keys.push(key)
  })

  return keys
}

function symbolsOf(object) {
  const symbols = []

  for (const symbol of attempt(() => Object.getOwnPropertySymbols(object)).value || []) {
    const { value: descriptor, threw } = attempt(() =>
      Object.getOwnPropertyDescriptor(object, symbol)
    )

    if (threw || descriptor === undefined || descriptor.enumerable) symbols.push(symbol)
  }

  return symbols
}

function nonIndexKeysOf(object) {
  return attempt(() => binding.getOwnNonIndexPropertyNames(object)).value || []
}

// Returns the descriptor, `undefined` if the property is absent, or `null` if
// the lookup itself could not be made, as with a proxy that throws.
function descriptorOf(object, key) {
  let target = object

  while (target !== null && target !== undefined) {
    const { value: descriptor, threw } = attempt(() => Object.getOwnPropertyDescriptor(target, key))

    if (threw) return null

    if (descriptor) return descriptor

    const { value: prototype, threw: unreadable } = attempt(() => Object.getPrototypeOf(target))

    if (unreadable) return null

    target = prototype
  }

  return undefined
}

function errorLabel(err) {
  if (err === null || typeof err !== 'object') return 'a non-error'

  const { value: message } = attempt(() => err.message)

  const name = nameOf(err) || 'Error'

  return typeof message === 'string' && message ? name + ': ' + message : name
}
{
  "name": "bare-inspect",
  "version": "3.1.10",
  "description": "Inspect objects as strings for debugging",
  "exports": {
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    },
    "./package": "./package.json"
  },
  "files": [
    "index.js",
    "index.d.ts",
    "binding.c",
    "binding.js",
    "CMakeLists.txt",
    "prebuilds"
  ],
  "addon": true,
  "scripts": {
    "format": "prettier --write . && lunte --fix",
    "lint": "prettier --check . && lunte",
    "test": "brittle-bare --coverage test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-inspect.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-inspect/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-inspect#readme",
  "engines": {
    "bare": ">=1.18.0"
  },
  "dependencies": {
    "bare-ansi-escapes": "^2.1.0",
    "bare-type": "^1.0.0"
  },
  "devDependencies": {
    "brittle": "^3.3.2",
    "cmake-bare": "^1.1.6",
    "lunte": "^1.8.4",
    "prettier": "^3.4.1",
    "prettier-config-holepunch": "^2.0.0"
  }
}
module.exports = require.addon()
// This export SHOULD NOT be shortened in any way as having the full
// `module.exports = require(...)` statement is crucial for synthesizing
// ESM exports.

if (Bare.platform === 'win32') {
  module.exports = require('./lib/win32')
} else {
  module.exports = require('./lib/posix')
}
module.exports = {
  CHAR_UPPERCASE_A: 0x41,
  CHAR_LOWERCASE_A: 0x61,
  CHAR_UPPERCASE_Z: 0x5a,
  CHAR_LOWERCASE_Z: 0x7a,
  CHAR_DOT: 0x2e,
  CHAR_FORWARD_SLASH: 0x2f,
  CHAR_BACKWARD_SLASH: 0x5c,
  CHAR_COLON: 0x3a,
  CHAR_QUESTION_MARK: 0x3f
}
const binding = require('../binding')

const { normalizeString, validateString } = require('./shared')
const { CHAR_DOT, CHAR_FORWARD_SLASH } = require('./constants')

function isPosixPathSeparator(code) {
  return code === CHAR_FORWARD_SLASH
}

exports.win32 = require('./win32')
exports.posix = exports

exports.sep = '/'
exports.delimiter = ':'

exports.resolve = function resolve(...args) {
  let resolvedPath = ''
  let resolvedAbsolute = false

  for (let i = args.length - 1; i >= -1 && !resolvedAbsolute; i--) {
    const path = i >= 0 ? args[i] : binding.cwd()

    if (i >= 0) validateString(path, `paths[${i}]`)

    if (path.length === 0) {
      continue
    }

    resolvedPath = `${path}/${resolvedPath}`
    resolvedAbsolute = path.charCodeAt(0) === CHAR_FORWARD_SLASH
  }

  resolvedPath = normalizeString(resolvedPath, !resolvedAbsolute, '/', isPosixPathSeparator)

  if (resolvedAbsolute) {
    return `/${resolvedPath}`
  }

  return resolvedPath.length > 0 ? resolvedPath : '.'
}

exports.normalize = function normalize(path) {
  validateString(path, 'path')

  if (path.length === 0) return '.'

  const isAbsolute = path.charCodeAt(0) === CHAR_FORWARD_SLASH
  const trailingSeparator = path.charCodeAt(path.length - 1) === CHAR_FORWARD_SLASH

  path = normalizeString(path, !isAbsolute, '/', isPosixPathSeparator)

  if (path.length === 0) {
    if (isAbsolute) return '/'
    return trailingSeparator ? './' : '.'
  }

  if (trailingSeparator) path += '/'

  return isAbsolute ? `/${path}` : path
}

exports.isAbsolute = function isAbsolute(path) {
  validateString(path, 'path')

  return path.length > 0 && path.charCodeAt(0) === CHAR_FORWARD_SLASH
}

exports.join = function join(...args) {
  if (args.length === 0) return '.'
  let joined
  for (let i = 0; i < args.length; ++i) {
    const arg = args[i]
    validateString(arg, 'path')
    if (arg.length > 0) {
      if (joined === undefined) joined = arg
      else joined += `/${arg}`
    }
  }
  if (joined === undefined) return '.'
  return exports.normalize(joined)
}

exports.relative = function relative(from, to) {
  validateString(from, 'from')
  validateString(to, 'to')

  if (from === to) return ''

  from = exports.resolve(from)
  to = exports.resolve(to)

  if (from === to) return ''

  const fromStart = 1
  const fromEnd = from.length
  const fromLen = fromEnd - fromStart
  const toStart = 1
  const toLen = to.length - toStart

  const length = fromLen < toLen ? fromLen : toLen
  let lastCommonSep = -1
  let i = 0
  for (; i < length; i++) {
    const fromCode = from.charCodeAt(fromStart + i)
    if (fromCode !== to.charCodeAt(toStart + i)) {
      break
    } else if (fromCode === CHAR_FORWARD_SLASH) {
      lastCommonSep = i
    }
  }
  if (i === length) {
    if (toLen > length) {
      if (to.charCodeAt(toStart + i) === CHAR_FORWARD_SLASH) {
        return to.substring(toStart + i + 1)
      }
      if (i === 0) {
        return to.substring(toStart + i)
      }
    } else if (fromLen > length) {
      if (from.charCodeAt(fromStart + i) === CHAR_FORWARD_SLASH) {
        lastCommonSep = i
      } else if (i === 0) {
        lastCommonSep = 0
      }
    }
  }

  let out = ''
  for (i = fromStart + lastCommonSep + 1; i <= fromEnd; ++i) {
    if (i === fromEnd || from.charCodeAt(i) === CHAR_FORWARD_SLASH) {
      out += out.length === 0 ? '..' : '/..'
    }
  }

  return `${out}${to.substring(toStart + lastCommonSep)}`
}

exports.toNamespacedPath = function toNamespacedPath(path) {
  return path
}

exports.dirname = function dirname(path) {
  validateString(path, 'path')

  if (path.length === 0) return '.'
  const hasRoot = path.charCodeAt(0) === CHAR_FORWARD_SLASH
  let end = -1
  let matchedSlash = true
  for (let i = path.length - 1; i >= 1; --i) {
    if (path.charCodeAt(i) === CHAR_FORWARD_SLASH) {
      if (!matchedSlash) {
        end = i
        break
      }
    } else {
      matchedSlash = false
    }
  }

  if (end === -1) return hasRoot ? '/' : '.'
  if (hasRoot && end === 1) return '//'
  return path.substring(0, end)
}

exports.basename = function basename(path, suffix) {
  if (suffix !== undefined) validateString(suffix, 'suffix')
  validateString(path, 'path')

  let start = 0
  let end = -1
  let matchedSlash = true

  if (suffix !== undefined && suffix.length > 0 && suffix.length <= path.length) {
    if (suffix === path) {
      return ''
    }
    let extIdx = suffix.length - 1
    let firstNonSlashEnd = -1
    for (let i = path.length - 1; i >= 0; --i) {
      const code = path.charCodeAt(i)
      if (code === CHAR_FORWARD_SLASH) {
        if (!matchedSlash) {
          start = i + 1
          break
        }
      } else {
        if (firstNonSlashEnd === -1) {
          matchedSlash = false
          firstNonSlashEnd = i + 1
        }
        if (extIdx >= 0) {
          if (code === suffix.charCodeAt(extIdx)) {
            if (--extIdx === -1) {
              end = i
            }
          } else {
            extIdx = -1
            end = firstNonSlashEnd
          }
        }
      }
    }

    if (start === end) end = firstNonSlashEnd
    else if (end === -1) end = path.length
    return path.substring(start, end)
  }

  for (let i = path.length - 1; i >= 0; --i) {
    if (path.charCodeAt(i) === CHAR_FORWARD_SLASH) {
      if (!matchedSlash) {
        start = i + 1
        break
      }
    } else if (end === -1) {
      matchedSlash = false
      end = i + 1
    }
  }

  if (end === -1) return ''
  return path.substring(start, end)
}

exports.extname = function extname(path) {
  validateString(path, 'path')

  let startDot = -1
  let startPart = 0
  let end = -1
  let matchedSlash = true
  let preDotState = 0
  for (let i = path.length - 1; i >= 0; --i) {
    const code = path.charCodeAt(i)
    if (code === CHAR_FORWARD_SLASH) {
      if (!matchedSlash) {
        startPart = i + 1
        break
      }
      continue
    }
    if (end === -1) {
      matchedSlash = false
      end = i + 1
    }
    if (code === CHAR_DOT) {
      if (startDot === -1) startDot = i
      else if (preDotState !== 1) preDotState = 1
    } else if (startDot !== -1) {
      preDotState = -1
    }
  }

  if (
    startDot === -1 ||
    end === -1 ||
    preDotState === 0 ||
    (preDotState === 1 && startDot === end - 1 && startDot === startPart + 1)
  ) {
    return ''
  }
  return path.substring(startDot, end)
}
const { CHAR_DOT, CHAR_FORWARD_SLASH } = require('./constants')

exports.validateString = function validateString(value, name) {
  if (typeof value !== 'string') {
    throw new TypeError(
      `The "${name}" argument must be of type string. Received type ${typeof value}`
    )
  }
}

exports.normalizeString = function normalizeString(
  path,
  allowAboveRoot,
  separator,
  isPathSeparator
) {
  let res = ''
  let lastSegmentLength = 0
  let lastSlash = -1
  let dots = 0
  let code = 0
  for (let i = 0; i <= path.length; ++i) {
    if (i < path.length) {
      code = path.charCodeAt(i)
    } else if (isPathSeparator(code)) {
      break
    } else {
      code = CHAR_FORWARD_SLASH
    }

    if (isPathSeparator(code)) {
      if (lastSlash === i - 1 || dots === 1);
      else if (dots === 2) {
        if (
          res.length < 2 ||
          lastSegmentLength !== 2 ||
          res.charCodeAt(res.length - 1) !== CHAR_DOT ||
          res.charCodeAt(res.length - 2) !== CHAR_DOT
        ) {
          if (res.length > 2) {
            const lastSlashIndex = res.lastIndexOf(separator)
            if (lastSlashIndex === -1) {
              res = ''
              lastSegmentLength = 0
            } else {
              res = res.substring(0, lastSlashIndex)
              lastSegmentLength = res.length - 1 - res.lastIndexOf(separator)
            }
            lastSlash = i
            dots = 0
            continue
          } else if (res.length !== 0) {
            res = ''
            lastSegmentLength = 0
            lastSlash = i
            dots = 0
            continue
          }
        }
        if (allowAboveRoot) {
          res += res.length > 0 ? `${separator}..` : '..'
          lastSegmentLength = 2
        }
      } else {
        if (res.length > 0) {
          res += `${separator}${path.substring(lastSlash + 1, i)}`
        } else {
          res = path.substring(lastSlash + 1, i)
        }
        lastSegmentLength = i - lastSlash - 1
      }
      lastSlash = i
      dots = 0
    } else if (code === CHAR_DOT && dots !== -1) {
      ++dots
    } else {
      dots = -1
    }
  }
  return res
}
const binding = require('../binding')

const { normalizeString, validateString } = require('./shared')
const {
  CHAR_UPPERCASE_A,
  CHAR_LOWERCASE_A,
  CHAR_UPPERCASE_Z,
  CHAR_LOWERCASE_Z,
  CHAR_DOT,
  CHAR_FORWARD_SLASH,
  CHAR_BACKWARD_SLASH,
  CHAR_COLON,
  CHAR_QUESTION_MARK
} = require('./constants')

function isWindowsPathSeparator(code) {
  return code === CHAR_FORWARD_SLASH || code === CHAR_BACKWARD_SLASH
}

function isWindowsDeviceRoot(code) {
  return (
    (code >= CHAR_UPPERCASE_A && code <= CHAR_UPPERCASE_Z) ||
    (code >= CHAR_LOWERCASE_A && code <= CHAR_LOWERCASE_Z)
  )
}

exports.posix = require('./posix')
exports.win32 = exports

exports.sep = '\\'
exports.delimiter = ';'

exports.resolve = function resolve(...args) {
  let resolvedDevice = ''
  let resolvedTail = ''
  let resolvedAbsolute = false

  for (let i = args.length - 1; i >= -1; i--) {
    let path
    if (i >= 0) {
      path = args[i]

      validateString(path, `paths[${i}]`)

      if (path.length === 0) continue
    } else if (resolvedDevice.length === 0) {
      path = binding.cwd()
    } else {
      path = binding.cwd(resolvedDevice)

      if (
        path === undefined ||
        (path.substring(0, 2).toLowerCase() !== resolvedDevice.toLowerCase() &&
          path.charCodeAt(2) === CHAR_BACKWARD_SLASH)
      ) {
        path = `${resolvedDevice}\\`
      }
    }

    const len = path.length
    let rootEnd = 0
    let device = ''
    let isAbsolute = false
    const code = path.charCodeAt(0)

    if (len === 1) {
      if (isWindowsPathSeparator(code)) {
        rootEnd = 1
        isAbsolute = true
      }
    } else if (isWindowsPathSeparator(code)) {
      isAbsolute = true

      if (isWindowsPathSeparator(path.charCodeAt(1))) {
        let j = 2
        let last = j
        while (j < len && !isWindowsPathSeparator(path.charCodeAt(j))) {
          j++
        }
        if (j < len && j !== last) {
          const firstPart = path.substring(last, j)
          last = j
          while (j < len && isWindowsPathSeparator(path.charCodeAt(j))) {
            j++
          }
          if (j < len && j !== last) {
            last = j
            while (j < len && !isWindowsPathSeparator(path.charCodeAt(j))) {
              j++
            }
            if (j === len || j !== last) {
              device = `\\\\${firstPart}\\${path.substring(last, j)}`
              rootEnd = j
            }
          }
        }
      } else {
        rootEnd = 1
      }
    } else if (isWindowsDeviceRoot(code) && path.charCodeAt(1) === CHAR_COLON) {
      device = path.substring(0, 2)
      rootEnd = 2
      if (len > 2 && isWindowsPathSeparator(path.charCodeAt(2))) {
        isAbsolute = true
        rootEnd = 3
      }
    }

    if (device.length > 0) {
      if (resolvedDevice.length > 0) {
        if (device.toLowerCase() !== resolvedDevice.toLowerCase()) {
          continue
        }
      } else {
        resolvedDevice = device
      }
    }

    if (resolvedAbsolute) {
      if (resolvedDevice.length > 0) {
        break
      }
    } else {
      resolvedTail = `${path.substring(rootEnd)}\\${resolvedTail}`
      resolvedAbsolute = isAbsolute
      if (isAbsolute && resolvedDevice.length > 0) {
        break
      }
    }
  }

  resolvedTail = normalizeString(resolvedTail, !resolvedAbsolute, '\\', isWindowsPathSeparator)

  return resolvedAbsolute
    ? `${resolvedDevice}\\${resolvedTail}`
    : `${resolvedDevice}${resolvedTail}` || '.'
}

exports.normalize = function normalize(path) {
  validateString(path, 'path')

  const len = path.length
  if (len === 0) return '.'
  let rootEnd = 0
  let device
  let isAbsolute = false
  const code = path.charCodeAt(0)

  if (len === 1) {
    return code === CHAR_FORWARD_SLASH ? '\\' : path
  }

  if (isWindowsPathSeparator(code)) {
    isAbsolute = true

    if (isWindowsPathSeparator(path.charCodeAt(1))) {
      let j = 2
      let last = j
      while (j < len && !isWindowsPathSeparator(path.charCodeAt(j))) {
        j++
      }
      if (j < len && j !== last) {
        const firstPart = path.substring(last, j)
        last = j
        while (j < len && isWindowsPathSeparator(path.charCodeAt(j))) {
          j++
        }
        if (j < len && j !== last) {
          last = j
          while (j < len && !isWindowsPathSeparator(path.charCodeAt(j))) {
            j++
          }
          if (j === len) {
            return `\\\\${firstPart}\\${path.substring(last)}\\`
          }
          if (j !== last) {
            device = `\\\\${firstPart}\\${path.substring(last, j)}`
            rootEnd = j
          }
        }
      }
    } else {
      rootEnd = 1
    }
  } else if (isWindowsDeviceRoot(code) && path.charCodeAt(1) === CHAR_COLON) {
    device = path.substring(0, 2)
    rootEnd = 2
    if (len > 2 && isWindowsPathSeparator(path.charCodeAt(2))) {
      isAbsolute = true
      rootEnd = 3
    }
  }

  let tail =
    rootEnd < len
      ? normalizeString(path.substring(rootEnd), !isAbsolute, '\\', isWindowsPathSeparator)
      : ''
  if (tail.length === 0 && !isAbsolute) {
    tail = '.'
  }
  if (tail.length > 0 && isWindowsPathSeparator(path.charCodeAt(len - 1))) {
    tail += '\\'
  }
  if (device === undefined) {
    return isAbsolute ? `\\${tail}` : tail
  }
  return isAbsolute ? `${device}\\${tail}` : `${device}${tail}`
}

exports.isAbsolute = function isAbsolute(path) {
  validateString(path, 'path')

  const len = path.length
  if (len === 0) return false

  const code = path.charCodeAt(0)

  return (
    isWindowsPathSeparator(code) ||
    (len > 2 &&
      isWindowsDeviceRoot(code) &&
      path.charCodeAt(1) === CHAR_COLON &&
      isWindowsPathSeparator(path.charCodeAt(2)))
  )
}

exports.join = function join(...args) {
  if (args.length === 0) return '.'

  let joined
  let firstPart
  for (let i = 0; i < args.length; ++i) {
    const arg = args[i]
    validateString(arg, 'path')
    if (arg.length > 0) {
      if (joined === undefined) joined = firstPart = arg
      else joined += `\\${arg}`
    }
  }

  if (joined === undefined) return '.'

  let needsReplace = true
  let slashCount = 0
  if (isWindowsPathSeparator(firstPart.charCodeAt(0))) {
    ++slashCount
    const firstLen = firstPart.length
    if (firstLen > 1 && isWindowsPathSeparator(firstPart.charCodeAt(1))) {
      ++slashCount
      if (firstLen > 2) {
        if (isWindowsPathSeparator(firstPart.charCodeAt(2))) {
          ++slashCount
        } else {
          needsReplace = false
        }
      }
    }
  }
  if (needsReplace) {
    while (slashCount < joined.length && isWindowsPathSeparator(joined.charCodeAt(slashCount))) {
      slashCount++
    }

    if (slashCount >= 2) {
      joined = `\\${joined.substring(slashCount)}`
    }
  }

  return exports.normalize(joined)
}

exports.relative = function relative(from, to) {
  validateString(from, 'from')
  validateString(to, 'to')

  if (from === to) return ''

  const fromOrig = exports.resolve(from)
  const toOrig = exports.resolve(to)

  if (fromOrig === toOrig) return ''

  from = fromOrig.toLowerCase()
  to = toOrig.toLowerCase()

  if (from === to) return ''

  let fromStart = 0
  while (fromStart < from.length && from.charCodeAt(fromStart) === CHAR_BACKWARD_SLASH) {
    fromStart++
  }
  let fromEnd = from.length
  while (fromEnd - 1 > fromStart && from.charCodeAt(fromEnd - 1) === CHAR_BACKWARD_SLASH) {
    fromEnd--
  }
  const fromLen = fromEnd - fromStart

  let toStart = 0
  while (toStart < to.length && to.charCodeAt(toStart) === CHAR_BACKWARD_SLASH) {
    toStart++
  }
  let toEnd = to.length
  while (toEnd - 1 > toStart && to.charCodeAt(toEnd - 1) === CHAR_BACKWARD_SLASH) {
    toEnd--
  }
  const toLen = toEnd - toStart

  const length = fromLen < toLen ? fromLen : toLen
  let lastCommonSep = -1
  let i = 0
  for (; i < length; i++) {
    const fromCode = from.charCodeAt(fromStart + i)
    if (fromCode !== to.charCodeAt(toStart + i)) {
      break
    } else if (fromCode === CHAR_BACKWARD_SLASH) {
      lastCommonSep = i
    }
  }

  if (i !== length) {
    if (lastCommonSep === -1) return toOrig
  } else {
    if (toLen > length) {
      if (to.charCodeAt(toStart + i) === CHAR_BACKWARD_SLASH) {
        return toOrig.substring(toStart + i + 1)
      }
      if (i === 2) {
        return toOrig.substring(toStart + i)
      }
    }
    if (fromLen > length) {
      if (from.charCodeAt(fromStart + i) === CHAR_BACKWARD_SLASH) {
        lastCommonSep = i
      } else if (i === 2) {
        lastCommonSep = 3
      }
    }
    if (lastCommonSep === -1) lastCommonSep = 0
  }

  let out = ''
  for (i = fromStart + lastCommonSep + 1; i <= fromEnd; ++i) {
    if (i === fromEnd || from.charCodeAt(i) === CHAR_BACKWARD_SLASH) {
      out += out.length === 0 ? '..' : '\\..'
    }
  }

  toStart += lastCommonSep

  if (out.length > 0) {
    return `${out}${toOrig.substring(toStart, toEnd)}`
  }
  if (toOrig.charCodeAt(toStart) === CHAR_BACKWARD_SLASH) {
    ++toStart
  }
  return toOrig.substring(toStart, toEnd)
}

exports.toNamespacedPath = function toNamespacedPath(path) {
  if (typeof path !== 'string' || path.length === 0) return path

  const resolvedPath = exports.resolve(path)

  if (resolvedPath.length <= 2) return path

  if (resolvedPath.charCodeAt(0) === CHAR_BACKWARD_SLASH) {
    if (resolvedPath.charCodeAt(1) === CHAR_BACKWARD_SLASH) {
      const code = resolvedPath.charCodeAt(2)
      if (code !== CHAR_QUESTION_MARK && code !== CHAR_DOT) {
        return `\\\\?\\UNC\\${resolvedPath.substring(2)}`
      }
    }
  } else if (
    isWindowsDeviceRoot(resolvedPath.charCodeAt(0)) &&
    resolvedPath.charCodeAt(1) === CHAR_COLON &&
    resolvedPath.charCodeAt(2) === CHAR_BACKWARD_SLASH
  ) {
    return `\\\\?\\${resolvedPath}`
  }

  return path
}

exports.dirname = function dirname(path) {
  validateString(path, 'path')

  const len = path.length
  if (len === 0) return '.'
  let rootEnd = -1
  let offset = 0
  const code = path.charCodeAt(0)

  if (len === 1) {
    return isWindowsPathSeparator(code) ? path : '.'
  }

  if (isWindowsPathSeparator(code)) {
    rootEnd = offset = 1

    if (isWindowsPathSeparator(path.charCodeAt(1))) {
      let j = 2
      let last = j
      while (j < len && !isWindowsPathSeparator(path.charCodeAt(j))) {
        j++
      }
      if (j < len && j !== last) {
        last = j
        while (j < len && isWindowsPathSeparator(path.charCodeAt(j))) {
          j++
        }
        if (j < len && j !== last) {
          last = j
          while (j < len && !isWindowsPathSeparator(path.charCodeAt(j))) {
            j++
          }
          if (j === len) {
            return path
          }
          if (j !== last) {
            rootEnd = offset = j + 1
          }
        }
      }
    }
  } else if (isWindowsDeviceRoot(code) && path.charCodeAt(1) === CHAR_COLON) {
    rootEnd = len > 2 && isWindowsPathSeparator(path.charCodeAt(2)) ? 3 : 2
    offset = rootEnd
  }

  let end = -1
  let matchedSlash = true
  for (let i = len - 1; i >= offset; --i) {
    if (isWindowsPathSeparator(path.charCodeAt(i))) {
      if (!matchedSlash) {
        end = i
        break
      }
    } else {
      matchedSlash = false
    }
  }

  if (end === -1) {
    if (rootEnd === -1) return '.'

    end = rootEnd
  }
  return path.substring(0, end)
}

exports.basename = function basename(path, suffix) {
  if (suffix !== undefined) validateString(suffix, 'suffix')
  validateString(path, 'path')

  let start = 0
  let end = -1
  let matchedSlash = true

  if (
    path.length >= 2 &&
    isWindowsDeviceRoot(path.charCodeAt(0)) &&
    path.charCodeAt(1) === CHAR_COLON
  ) {
    start = 2
  }

  if (suffix !== undefined && suffix.length > 0 && suffix.length <= path.length) {
    if (suffix === path) return ''
    let extIdx = suffix.length - 1
    let firstNonSlashEnd = -1
    for (let i = path.length - 1; i >= start; --i) {
      const code = path.charCodeAt(i)
      if (isWindowsPathSeparator(code)) {
        if (!matchedSlash) {
          start = i + 1
          break
        }
      } else {
        if (firstNonSlashEnd === -1) {
          matchedSlash = false
          firstNonSlashEnd = i + 1
        }
        if (extIdx >= 0) {
          if (code === suffix.charCodeAt(extIdx)) {
            if (--extIdx === -1) {
              end = i
            }
          } else {
            extIdx = -1
            end = firstNonSlashEnd
          }
        }
      }
    }

    if (start === end) end = firstNonSlashEnd
    else if (end === -1) end = path.length
    return path.substring(start, end)
  }
  for (let i = path.length - 1; i >= start; --i) {
    if (isWindowsPathSeparator(path.charCodeAt(i))) {
      if (!matchedSlash) {
        start = i + 1
        break
      }
    } else if (end === -1) {
      matchedSlash = false
      end = i + 1
    }
  }

  if (end === -1) return ''
  return path.substring(start, end)
}

exports.extname = function extname(path) {
  validateString(path, 'path')

  let start = 0
  let startDot = -1
  let startPart = 0
  let end = -1
  let matchedSlash = true
  let preDotState = 0

  if (
    path.length >= 2 &&
    path.charCodeAt(1) === CHAR_COLON &&
    isWindowsDeviceRoot(path.charCodeAt(0))
  ) {
    start = startPart = 2
  }

  for (let i = path.length - 1; i >= start; --i) {
    const code = path.charCodeAt(i)
    if (isWindowsPathSeparator(code)) {
      if (!matchedSlash) {
        startPart = i + 1
        break
      }
      continue
    }
    if (end === -1) {
      matchedSlash = false
      end = i + 1
    }
    if (code === CHAR_DOT) {
      if (startDot === -1) startDot = i
      else if (preDotState !== 1) preDotState = 1
    } else if (startDot !== -1) {
      preDotState = -1
    }
  }

  if (
    startDot === -1 ||
    end === -1 ||
    preDotState === 0 ||
    (preDotState === 1 && startDot === end - 1 && startDot === startPart + 1)
  ) {
    return ''
  }
  return path.substring(startDot, end)
}
{
  "name": "bare-path",
  "version": "3.1.2",
  "description": "Path manipulation library for JavaScript",
  "exports": {
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    },
    "./package": "./package.json",
    "./posix": "./lib/posix.js",
    "./win32": "./lib/win32.js"
  },
  "files": [
    "index.js",
    "index.d.ts",
    "binding.c",
    "binding.js",
    "CMakeLists.txt",
    "lib",
    "prebuilds",
    "NOTICE"
  ],
  "addon": true,
  "scripts": {
    "format": "prettier --write . && lunte --fix",
    "lint": "prettier --check . && lunte",
    "test": "brittle-bare --coverage test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-path.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-path/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-path#readme",
  "devDependencies": {
    "brittle": "^3.3.2",
    "cmake-bare": "^1.1.6",
    "lunte": "^1.8.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^2.0.0"
  }
}
const b4a = require('b4a')
const stream = require('streamx')
const { ReadableStream, WritableStream } = require('./web')

const defaultEncoding = 'utf8'

module.exports = exports = stream.Stream

exports.pipeline = stream.pipeline

exports.isStream = stream.isStream
exports.isEnding = stream.isEnding
exports.isEnded = stream.isEnded
exports.isFinishing = stream.isFinishing
exports.isFinished = stream.isFinished
exports.isDisturbed = stream.isDisturbed

exports.isErrored = function isErrored(stream) {
  return exports.getStreamError(stream) !== null
}

exports.isReadable = function isReadable(stream) {
  return stream.readable && !stream.destroying && !exports.isEnded(stream)
}

exports.isWritable = function isWritable(stream) {
  return stream.writable && !stream.destroying && !exports.isFinishing(stream)
}

exports.getStreamError = stream.getStreamError

exports.addAbortSignal = function addAbortSignal(signal, stream) {
  function onAbort() {
    stream.destroy(signal.reason)
  }

  if (signal.aborted) onAbort()
  else signal.addEventListener('abort', onAbort)

  return stream
}

exports.Stream = exports

exports.Readable = class Readable extends stream.Readable {
  constructor(opts = {}) {
    super({
      ...opts,
      byteLength: null,
      byteLengthReadable: null,
      map: null,
      mapReadable: null
    })

    if (this._construct) this._open = this._construct

    if (this._read !== stream.Readable.prototype._read) {
      this._read = read.bind(this, this._read)
    }

    if (this._destroy !== stream.Stream.prototype._destroy) {
      this._destroy = destroy.bind(this, this._destroy)
    }
  }

  get closed() {
    return !exports.isReadable(this)
  }

  get errored() {
    return stream.getStreamError(this)
  }

  push(chunk, encoding) {
    if (typeof chunk === 'string') {
      chunk = b4a.from(chunk, encoding || defaultEncoding)
    }

    return super.push(chunk)
  }

  unshift(chunk, encoding) {
    if (typeof chunk === 'string') {
      chunk = b4a.from(chunk, encoding || defaultEncoding)
    }

    super.unshift(chunk)
  }

  static fromWeb(readableStream, opts = {}) {
    const stream = readableStream._stream

    if (opts.encoding) stream.setEncoding(opts.encoding)
    if (opts.signal) exports.addAbortSignal(opts.signal, stream)

    return stream
  }

  static toWeb(readable, opts = {}) {
    return new ReadableStream(readable, opts.strategy)
  }

  async [Symbol.asyncDispose]() {
    if (!this.destroyed) this.destroy()

    await new Promise((resolve) => exports.finished(this, resolve))
  }
}

exports.Writable = class Writable extends stream.Writable {
  constructor(opts = {}) {
    super({
      ...opts,
      byteLength: null,
      byteLengthWritable,
      map: null,
      mapWritable: null
    })

    if (this._construct) this._open = this._construct

    if (this._write !== stream.Writable.prototype._write) {
      this._write = write.bind(this, this._write)
    }

    if (this._destroy !== stream.Stream.prototype._destroy) {
      this._destroy = destroy.bind(this, this._destroy)
    }
  }

  get closed() {
    return !exports.isWritable(this)
  }

  get errored() {
    return stream.getStreamError(this)
  }

  write(chunk, encoding, cb) {
    if (typeof encoding === 'function') {
      cb = encoding
      encoding = null
    }

    if (typeof chunk === 'string') {
      encoding = encoding || defaultEncoding
      chunk = b4a.from(chunk, encoding)
    } else {
      encoding = 'buffer'
    }

    const result = super.write({ chunk, encoding })

    if (cb) stream.Writable.drained(this).then(() => cb(null), cb)

    return result
  }

  end(chunk, encoding, cb) {
    if (typeof chunk === 'function') {
      cb = chunk
      chunk = null
    } else if (typeof encoding === 'function') {
      cb = encoding
      encoding = null
    }

    if (typeof chunk === 'string') {
      encoding = encoding || defaultEncoding
      chunk = b4a.from(chunk, encoding || defaultEncoding)
    } else {
      encoding = 'buffer'
    }

    const result =
      chunk !== undefined && chunk !== null ? super.end({ chunk, encoding }) : super.end()

    if (cb) this.once('finish', () => cb(null))

    return result
  }

  static fromWeb(writableStream, opts = {}) {
    const stream = writableStream._stream

    if (opts.signal) exports.addAbortSignal(opts.signal, stream)

    return stream
  }

  static toWeb(writable) {
    return new WritableStream(writable)
  }

  async [Symbol.asyncDispose]() {
    if (!this.destroyed) this.destroy()

    await new Promise((resolve) => exports.finished(this, resolve))
  }
}

exports.Duplex = class Duplex extends stream.Duplex {
  constructor(opts = {}) {
    super({
      ...opts,
      byteLength: null,
      byteLengthReadable: null,
      byteLengthWritable,
      map: null,
      mapReadable: null,
      mapWritable: null
    })

    if (this._construct) this._open = this._construct

    if (this._read !== stream.Readable.prototype._read) {
      this._read = read.bind(this, this._read)
    }

    if (this._write !== stream.Duplex.prototype._write) {
      this._write = write.bind(this, this._write)
    }

    if (this._destroy !== stream.Stream.prototype._destroy) {
      this._destroy = destroy.bind(this, this._destroy)
    }
  }

  push(chunk, encoding) {
    if (typeof chunk === 'string') {
      chunk = b4a.from(chunk, encoding || defaultEncoding)
    }

    return super.push(chunk)
  }

  unshift(chunk, encoding) {
    if (typeof chunk === 'string') {
      chunk = b4a.from(chunk, encoding || defaultEncoding)
    }

    super.unshift(chunk)
  }

  write(chunk, encoding, cb) {
    if (typeof encoding === 'function') {
      cb = encoding
      encoding = null
    }

    if (typeof chunk === 'string') {
      encoding = encoding || defaultEncoding
      chunk = b4a.from(chunk, encoding)
    } else {
      encoding = 'buffer'
    }

    const result = super.write({ chunk, encoding })

    if (cb) stream.Writable.drained(this).then(() => cb(null), cb)

    return result
  }

  end(chunk, encoding, cb) {
    if (typeof chunk === 'function') {
      cb = chunk
      chunk = null
    } else if (typeof encoding === 'function') {
      cb = encoding
      encoding = null
    }

    if (typeof chunk === 'string') {
      encoding = encoding || defaultEncoding
      chunk = b4a.from(chunk, encoding)
    } else {
      encoding = 'buffer'
    }

    const result =
      chunk !== undefined && chunk !== null ? super.end({ chunk, encoding }) : super.end()

    if (cb) this.once('finish', () => cb(null))

    return result
  }

  static fromWeb({ readable: readableStream, writable: writableStream }, opts) {
    const readable = exports.Readable.fromWeb(readableStream, opts)
    const writable = exports.Writable.fromWeb(writableStream, opts)

    const duplex = new exports.Duplex({
      write(data, encoding, cb) {
        writable.write(data, encoding, cb)
      }
    })

    readable
      .on('data', (data) => duplex.push(data))
      .on('end', () => duplex.push(null))
      .on('error', (err) => duplex.destroy(err))

    writable.on('finish', () => duplex.end()).on('error', (err) => duplex.destroy(err))

    return duplex
  }

  static toWeb(duplex) {
    const readableStream = exports.Readable.toWeb(duplex)
    const writableStream = exports.Writable.toWeb(duplex)

    return { readable: readableStream, writable: writableStream }
  }
}

class DuplexSide extends exports.Duplex {
  constructor(opts) {
    super(opts)

    this._otherSide = null
    this._cb = null
  }

  _read() {
    const cb = this._cb
    if (!cb) return

    this._cb = null
    cb()
  }

  _write(chunk, encoding, cb) {
    this._otherSide.push(chunk, encoding)
    this._otherSide._cb = cb
  }

  _final(cb) {
    this._otherSide.on('end', cb)
    this._otherSide.push(null)
  }
}

exports.duplexPair = function duplexPair(opts) {
  const sideA = new DuplexSide(opts)
  const sideB = new DuplexSide(opts)

  sideA._otherSide = sideB
  sideB._otherSide = sideA

  return [sideA, sideB]
}

exports.Transform = class Transform extends stream.Transform {
  constructor(opts = {}) {
    super({
      ...opts,
      byteLength: null,
      byteLengthReadable: null,
      byteLengthWritable,
      map: null,
      mapReadable: null,
      mapWritable: null
    })

    if (this._transform !== stream.Transform.prototype._transform) {
      this._transform = transform.bind(this, this._transform)
    } else {
      this._transform = passthrough
    }
  }

  push(chunk, encoding) {
    if (typeof chunk === 'string') {
      chunk = b4a.from(chunk, encoding || defaultEncoding)
    }

    return super.push(chunk)
  }

  unshift(chunk, encoding) {
    if (typeof chunk === 'string') {
      chunk = b4a.from(chunk, encoding || defaultEncoding)
    }

    super.unshift(chunk)
  }

  write(chunk, encoding, cb) {
    if (typeof encoding === 'function') {
      cb = encoding
      encoding = null
    }

    if (typeof chunk === 'string') {
      encoding = encoding || defaultEncoding
      chunk = b4a.from(chunk, encoding)
    } else {
      encoding = 'buffer'
    }

    const result = super.write({ chunk, encoding })

    if (cb) stream.Writable.drained(this).then(() => cb(null), cb)

    return result
  }

  end(chunk, encoding, cb) {
    if (typeof chunk === 'function') {
      cb = chunk
      chunk = null
    } else if (typeof encoding === 'function') {
      cb = encoding
      encoding = null
    }

    if (typeof chunk === 'string') {
      encoding = encoding || defaultEncoding
      chunk = b4a.from(chunk, encoding)
    } else {
      encoding = 'buffer'
    }

    const result =
      chunk !== undefined && chunk !== null ? super.end({ chunk, encoding }) : super.end()

    if (cb) this.once('finish', () => cb(null))

    return result
  }
}

exports.PassThrough = class PassThrough extends exports.Transform {}

exports.finished = function finished(stream, opts, cb) {
  if (typeof opts === 'function') {
    cb = opts
    opts = {}
  }

  if (!opts) opts = {}

  const { cleanup = false } = opts

  const done = () => {
    cb(exports.getStreamError(stream, { all: true }))

    if (cleanup) detach()
  }

  const detach = () => {
    stream.off('close', done)
    stream.off('error', noop)
  }

  if (stream.destroyed) {
    done()
  } else {
    stream.on('close', done)
    stream.on('error', noop)
  }

  return detach
}

function read(read, cb) {
  read.call(this, 65536)

  cb(null)
}

function write(write, data, cb) {
  write.call(this, data.chunk, data.encoding, cb)
}

function transform(transform, data, cb) {
  transform.call(this, data.chunk, data.encoding, cb)
}

function destroy(destroy, cb) {
  destroy.call(this, exports.getStreamError(this), cb)
}

function passthrough(data, cb) {
  cb(null, data.chunk)
}

function byteLengthWritable(data) {
  return data.chunk.byteLength
}

function noop() {}
{
  "name": "bare-stream",
  "version": "2.13.4",
  "description": "Streaming data for JavaScript",
  "exports": {
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    },
    "./package": "./package.json",
    "./promises": "./promises.js",
    "./web": {
      "types": "./web.d.ts",
      "default": "./web.js"
    },
    "./global": {
      "types": "./global.d.ts",
      "default": "./global.js"
    }
  },
  "files": [
    "index.js",
    "index.d.ts",
    "promises.js",
    "web.js",
    "web.d.ts",
    "global.js",
    "global.d.ts"
  ],
  "scripts": {
    "format": "prettier --write . && lunte --fix",
    "lint": "prettier --check . && lunte",
    "test": "brittle-bare --coverage test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-stream.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-stream/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-stream#readme",
  "dependencies": {
    "b4a": "^1.8.1",
    "streamx": "^2.25.0",
    "teex": "^1.0.1"
  },
  "devDependencies": {
    "bare-abort-controller": "^1.1.0",
    "bare-buffer": "^3.0.0",
    "bare-events": "^2.5.4",
    "brittle": "^3.5.2",
    "lunte": "^1.6.0",
    "prettier": "^3.3.3",
    "prettier-config-holepunch": "^2.0.0"
  },
  "peerDependencies": {
    "bare-abort-controller": "*",
    "bare-buffer": "*",
    "bare-events": "*"
  },
  "peerDependenciesMeta": {
    "bare-abort-controller": {
      "optional": true
    },
    "bare-buffer": {
      "optional": true
    },
    "bare-events": {
      "optional": true
    }
  }
}
const { Readable, Writable, Transform, getStreamError, isStreamx, isDisturbed } = require('streamx')
const tee = require('teex')

const readableKind = Symbol.for('bare.stream.readable.kind')
const writableKind = Symbol.for('bare.stream.writable.kind')
const transformKind = Symbol.for('bare.stream.transform.kind')

// https://streams.spec.whatwg.org/#readablestreamdefaultreader
exports.ReadableStreamDefaultReader = class ReadableStreamDefaultReader {
  constructor(stream) {
    this._stream = stream
    this._stream._stream.once('close', onclose).once('error', onerror)

    const closed = Promise.withResolvers()

    // Avoid unhandled exceptions
    closed.promise.catch(noop)

    this._closed = closed

    function onclose() {
      closed.resolve()
    }

    function onerror(err) {
      closed.reject(err)
    }
  }

  get closed() {
    return this._closed.promise
  }

  read() {
    const stream = this._stream._stream

    return new Promise((resolve, reject) => {
      const err = getStreamError(stream)

      if (err) return reject(err)

      if (stream.destroyed) {
        return resolve({ value: undefined, done: true })
      }

      const value = stream.read()

      if (value !== null) {
        return resolve({ value, done: false })
      }

      stream.once('readable', onreadable).once('close', onclose).once('error', onerror)

      function onreadable() {
        const value = stream.read()

        ondone(null, value === null ? { value: undefined, done: true } : { value, done: false })
      }

      function onclose() {
        ondone(null, { value: undefined, done: true })
      }

      function onerror(err) {
        ondone(err, null)
      }

      function ondone(err, value) {
        stream.off('readable', onreadable).off('close', onclose).off('error', onerror)

        if (err) reject(err)
        else resolve(value)
      }
    })
  }

  releaseLock() {
    this._closed.reject(new TypeError('Reader was released'))
    this._stream._releaseLock()
    this._stream = null
  }

  cancel(reason = new TypeError('Stream was cancelled')) {
    const stream = this._stream._stream

    if (stream.destroyed) return Promise.resolve()

    return new Promise((resolve) =>
      stream.once('close', resolve).once('error', noop).destroy(reason)
    )
  }
}

// https://streams.spec.whatwg.org/#readablestreamdefaultcontroller
exports.ReadableStreamDefaultController = class ReadableStreamDefaultController {
  constructor(stream) {
    this._stream = stream
  }

  get desiredSize() {
    const stream = this._stream._stream

    return stream._readableState.highWaterMark - stream._readableState.buffered
  }

  enqueue(data) {
    this._stream._stream.push(data)
  }

  close() {
    this._stream._stream.push(null)
  }

  error(err) {
    this._stream._stream.destroy(err)
  }
}

// https://streams.spec.whatwg.org/#readablestream
class ReadableStream {
  static get [readableKind]() {
    return 0 // Compatibility version
  }

  static from(iterable) {
    return new ReadableStream(Readable.from(iterable))
  }

  constructor(underlyingSource = {}, queuingStrategy) {
    if (isStreamx(underlyingSource)) {
      this._stream = underlyingSource
    } else {
      if (queuingStrategy === undefined) {
        queuingStrategy = new exports.CountQueuingStrategy()
      }

      const { start, pull, cancel } = underlyingSource
      const { highWaterMark = 1, size = defaultSize } = queuingStrategy

      this._stream = new Readable({ highWaterMark, byteLength: size })

      const controller = new exports.ReadableStreamDefaultController(this)

      try {
        let starting = Promise.resolve()

        if (start) starting = forwardError(start.call(this, controller), controller)

        if (pull) {
          this._stream._read = this._read.bind(this, starting, pull.bind(this, controller))
        }

        if (cancel) {
          this._stream.once('error', cancel.bind(this))
        }
      } catch (err) {
        controller.error(err)
      }
    }

    this._reader = null
  }

  get [readableKind]() {
    return ReadableStream[readableKind]
  }

  get locked() {
    return this._reader !== null
  }

  getReader() {
    if (this.locked) throw new TypeError('Stream is locked')

    this._reader = new exports.ReadableStreamDefaultReader(this)

    return this._reader
  }

  cancel(reason = new TypeError('Stream was cancelled')) {
    const stream = this._stream

    if (stream.destroyed) return Promise.resolve()

    if (this.locked) return Promise.reject(new TypeError('Stream is locked'))

    return new Promise((resolve) =>
      stream.once('close', resolve).once('error', noop).destroy(reason)
    )
  }

  tee() {
    const [a, b] = tee(this._stream)

    return [new ReadableStream(a), new ReadableStream(b)]
  }

  pipeTo(destination) {
    return new Promise((resolve, reject) =>
      this._stream.pipe(destination._stream, (err) => {
        err ? reject(err) : resolve()
      })
    )
  }

  [Symbol.asyncIterator]() {
    return this._stream[Symbol.asyncIterator]()
  }

  _releaseLock() {
    this._reader = null
  }

  async _read(starting, pull, cb) {
    await starting

    let err = null
    try {
      await pull()
    } catch (e) {
      err = e
    }
    cb(err)
  }
}

function defaultSize() {
  return 1
}

exports.ReadableStream = ReadableStream

// https://streams.spec.whatwg.org/#countqueuingstrategy
exports.CountQueuingStrategy = class CountQueuingStrategy {
  constructor(opts = {}) {
    const { highWaterMark = 1 } = opts

    this.highWaterMark = highWaterMark
  }

  size(chunk) {
    return 1
  }
}

// https://streams.spec.whatwg.org/#bytelengthqueuingstrategy
exports.ByteLengthQueuingStrategy = class ByteLengthQueuingStrategy {
  constructor(opts = {}) {
    const { highWaterMark = 16384 } = opts

    this.highWaterMark = highWaterMark
  }

  size(chunk) {
    return chunk.byteLength
  }
}

exports.isReadableStream = function isReadableStream(value) {
  if (value instanceof ReadableStream) return true

  return (
    typeof value === 'object' &&
    value !== null &&
    value[readableKind] === ReadableStream[readableKind]
  )
}

// https://streams.spec.whatwg.org/#readablestream-errored
exports.isReadableStreamErrored = function isReadableStreamErrored(stream) {
  return getStreamError(stream._stream) !== null
}

// https://streams.spec.whatwg.org/#is-readable-stream-disturbed
exports.isReadableStreamDisturbed = function isReadableStreamDisturbed(stream) {
  return isDisturbed(stream._stream)
}

// https://streams.spec.whatwg.org/#writablestreamdefaultwriter
exports.WritableStreamDefaultWriter = class WritableStreamDefaultWriter {
  constructor(stream) {
    this._stream = stream
    this._stream._stream.once('close', onclose).once('error', onerror)

    const closed = Promise.withResolvers()

    // Avoid unhandled exceptions
    closed.promise.catch(noop)

    this._closed = closed

    function onclose() {
      closed.resolve()
    }

    function onerror(err) {
      closed.reject(err)
    }
  }

  get desiredSize() {
    const stream = this._stream._stream

    return stream._writableState.highWaterMark - stream._writableState.buffered
  }

  get closed() {
    return this._closed.promise
  }

  get ready() {
    const stream = this._stream._stream

    if (getStreamError(stream)) return Promise.reject()

    return Writable.drained(stream).then()
  }

  async write(chunk) {
    const stream = this._stream._stream

    let err = getStreamError(stream)
    if (err) return Promise.reject(err)

    stream.write(chunk)

    await Writable.drained(stream)

    err = getStreamError(stream)
    if (err) return Promise.reject(err)
  }

  releaseLock() {
    this._closed.reject(new TypeError('Writer was released'))
    this._stream._releaseLock()
    this._stream = null
  }

  close() {
    const stream = this._stream._stream

    if (stream.destroyed) return Promise.resolve()

    return new Promise((resolve) => stream.once('close', resolve).end())
  }

  abort(reason = new TypeError('Stream was aborted')) {
    const stream = this._stream._stream

    if (stream.destroyed) return Promise.resolve()

    return new Promise((resolve) => stream.once('close', resolve).destroy(reason))
  }
}

// https://streams.spec.whatwg.org/#writablestreamdefaultcontroller
exports.WritableStreamDefaultController = class WritableStreamDefaultController {
  constructor(stream) {
    this._stream = stream
  }

  error(err) {
    this._stream._stream.destroy(err)
  }
}

// https://streams.spec.whatwg.org/#writablestream
class WritableStream {
  static get [writableKind]() {
    return 0 // Compatibility version
  }

  constructor(underlyingSink = {}, queuingStrategy = {}) {
    if (isStreamx(underlyingSink)) {
      this._stream = underlyingSink
    } else {
      if (queuingStrategy === undefined) {
        queuingStrategy = new exports.CountQueuingStrategy()
      }

      const { start, write, close, abort } = underlyingSink
      const { highWaterMark = 1, size = defaultSize } = queuingStrategy

      this._stream = new Writable({ highWaterMark, byteLength: size })

      const controller = new exports.WritableStreamDefaultController(this)

      this._controller = controller

      try {
        let starting = Promise.resolve()

        if (start) starting = forwardError(start.call(this, controller), controller)

        if (write) {
          this._stream._write = this._write.bind(this, starting, write.bind(this))
        }

        if (close) {
          this._stream._destroy = this._destroy.bind(this, close.call(this))
        }

        if (abort) {
          this._stream.once('error', abort.bind(this))
        }
      } catch (err) {
        controller.error(err)
      }
    }

    this._writer = null
  }

  get [writableKind]() {
    return WritableStream[writableKind]
  }

  get locked() {
    return this._writer !== null
  }

  getWriter() {
    if (this.locked) throw new TypeError('Stream is locked')

    this._writer = new exports.WritableStreamDefaultWriter(this)

    return this._writer
  }

  abort(reason = new TypeError('Stream was aborted')) {
    if (this._stream.destroyed) return Promise.resolve()

    if (this.locked) return Promise.reject(new TypeError('Stream is locked'))

    return new Promise((resolve) => this._stream.once('close', resolve).destroy(reason))
  }

  close() {
    if (this._stream.destroyed) return Promise.resolve()

    if (this.locked) return Promise.reject(new TypeError('Stream is locked'))

    return new Promise((resolve) => this._stream.once('close', resolve).end())
  }

  _releaseLock() {
    this._writer = null
  }

  async _write(starting, write, data, cb) {
    await starting

    let err = null
    try {
      await write(data, this._controller)
    } catch (e) {
      err = e
    }
    cb(err)
  }

  async _destroy(closing, cb) {
    let err = null
    try {
      await closing
    } catch (e) {
      err = e
    }
    cb(err)
  }
}

exports.WritableStream = WritableStream

exports.isWritableStream = function isWritableStream(value) {
  if (value instanceof WritableStream) return true

  return (
    typeof value === 'object' &&
    value !== null &&
    value[writableKind] === WritableStream[writableKind]
  )
}

// https://streams.spec.whatwg.org/#transformstreamdefaultcontroller
exports.TransformStreamDefaultController = class TransformStreamDefaultController {
  constructor(stream) {
    this._stream = stream
  }

  get desiredSize() {
    const stream = this._stream._stream

    return stream._readableState.highWaterMark - stream._readableState.buffered
  }

  enqueue(data) {
    this._stream._stream.push(data)
  }

  error(err) {
    this._stream._stream.destroy(err)
  }

  terminate() {
    const stream = this._stream._stream

    stream.push(null)
    stream.destroy(new TypeError('Stream has been terminated'))
  }
}

// https://streams.spec.whatwg.org/#transformstream
class TransformStream {
  static get [transformKind]() {
    return 0 // Compatibility version
  }

  constructor(transformer = {}, writableStrategy = {}, readableStrategy = {}) {
    if (isStreamx(transformer)) {
      this._stream = transformer
    } else {
      const { start, transform, flush } = transformer

      this._stream = new Transform({ ...writableStrategy, ...readableStrategy })

      const controller = new exports.TransformStreamDefaultController(this)

      this._controller = controller

      try {
        let starting = Promise.resolve()

        if (start) starting = forwardError(start.call(this, controller), controller)

        if (transform) {
          this._stream._transform = this._transform.bind(this, starting, transform.bind(this))
        }

        if (flush) {
          this._stream._flush = this._flush.bind(this, flush.call(this, this._controller))
        }
      } catch (err) {
        controller.error(err)
      }
    }

    this._writable = new WritableStream(this._stream)
    this._readable = new ReadableStream(this._stream)
  }

  get [transformKind]() {
    return TransformStream[transformKind]
  }

  get writable() {
    return this._writable
  }

  get readable() {
    return this._readable
  }

  async _transform(starting, transform, data, cb) {
    await starting

    let err = null
    try {
      await transform(data, this._controller)
    } catch (e) {
      err = e
    }
    cb(err)
  }

  async _flush(flush, cb) {
    let err = null
    try {
      await flush
    } catch (e) {
      err = e
    }
    cb(err)
  }
}

exports.TransformStream = TransformStream

exports.isTransformStream = function isTransformStream(value) {
  if (value instanceof TransformStream) return true

  return (
    typeof value === 'object' &&
    value !== null &&
    value[transformKind] === TransformStream[transformKind]
  )
}

async function forwardError(promise, controller) {
  try {
    await promise
  } catch (err) {
    controller.error(err)
  }
}

function noop() {}
module.exports = require.addon()
const binding = require('./binding')

const t = binding.constants

class Type {
  constructor(type) {
    this._type = type
  }

  isUndefined() {
    return this._type === t.UNDEFINED
  }

  isNull() {
    return this._type === t.NULL
  }

  isBoolean() {
    return this._type === t.BOOLEAN
  }

  isNumber() {
    return (this._type & 0xff) === t.NUMBER
  }

  isInt32() {
    return (this._type & (0xff | t.INT32)) === (t.NUMBER | t.INT32)
  }

  isUint32() {
    return (this._type & (0xff | t.UINT32)) === (t.NUMBER | t.UINT32)
  }

  isString() {
    return this._type === t.STRING
  }

  isSymbol() {
    return this._type === t.SYMBOL
  }

  isObject() {
    return (this._type & 0xff) === t.OBJECT
  }

  isArray() {
    return this._type === (t.OBJECT | t.ARRAY)
  }

  isArguments() {
    return this._type === (t.OBJECT | t.ARGUMENTS)
  }

  isDate() {
    return this._type === (t.OBJECT | t.DATE)
  }

  isRegExp() {
    return this._type === (t.OBJECT | t.REGEXP)
  }

  isError() {
    return this._type === (t.OBJECT | t.ERROR)
  }

  isPromise() {
    return this._type === (t.OBJECT | t.PROMISE)
  }

  isProxy() {
    return this._type === (t.OBJECT | t.PROXY)
  }

  isGenerator() {
    return this._type === (t.OBJECT | t.GENERATOR)
  }

  isMap() {
    return this._type === (t.OBJECT | t.MAP)
  }

  isSet() {
    return this._type === (t.OBJECT | t.SET)
  }

  isWeakMap() {
    return this._type === (t.OBJECT | t.WEAK_MAP)
  }

  isWeakSet() {
    return this._type === (t.OBJECT | t.WEAK_SET)
  }

  isWeakRef() {
    return this._type === (t.OBJECT | t.WEAK_REF)
  }

  isArrayBuffer() {
    return this._type === (t.OBJECT | t.ARRAYBUFFER)
  }

  isSharedArrayBuffer() {
    return this._type === (t.OBJECT | t.SHAREDARRAYBUFFER)
  }

  isTypedArray() {
    return (this._type & 0xffff) === (t.OBJECT | t.TYPEDARRAY)
  }

  isInt8Array() {
    return this._type === (t.OBJECT | t.TYPEDARRAY | t.INT8ARRAY)
  }

  isUint8Array() {
    return this._type === (t.OBJECT | t.TYPEDARRAY | t.UINT8ARRAY)
  }

  isUint8ClampedArray() {
    return this._type === (t.OBJECT | t.TYPEDARRAY | t.UINT8CLAMPEDARRAY)
  }

  isInt16Array() {
    return this._type === (t.OBJECT | t.TYPEDARRAY | t.INT16ARRAY)
  }

  isUint16Array() {
    return this._type === (t.OBJECT | t.TYPEDARRAY | t.UINT16ARRAY)
  }

  isInt32Array() {
    return this._type === (t.OBJECT | t.TYPEDARRAY | t.INT32ARRAY)
  }

  isUint32Array() {
    return this._type === (t.OBJECT | t.TYPEDARRAY | t.UINT32ARRAY)
  }

  isFloat16Array() {
    return this._type === (t.OBJECT | t.TYPEDARRAY | t.FLOAT16ARRAY)
  }

  isFloat32Array() {
    return this._type === (t.OBJECT | t.TYPEDARRAY | t.FLOAT32ARRAY)
  }

  isFloat64Array() {
    return this._type === (t.OBJECT | t.TYPEDARRAY | t.FLOAT64ARRAY)
  }

  isBigInt64Array() {
    return this._type === (t.OBJECT | t.TYPEDARRAY | t.BIGINT64ARRAY)
  }

  isBigUint64Array() {
    return this._type === (t.OBJECT | t.TYPEDARRAY | t.BIGUINT64ARRAY)
  }

  isDataView() {
    return this._type === (t.OBJECT | t.DATAVIEW)
  }

  isModuleNamespace() {
    return this._type === (t.OBJECT | t.MODULE_NAMESPACE)
  }

  isFunction() {
    return (this._type & 0xff) === t.FUNCTION
  }

  isAsyncFunction() {
    return (this._type & (0xff | t.ASYNC_FUNCTION)) === (t.FUNCTION | t.ASYNC_FUNCTION)
  }

  isGeneratorFunction() {
    return (this._type & (0xff | t.GENERATOR_FUNCTION)) === (t.FUNCTION | t.GENERATOR_FUNCTION)
  }

  isExternal() {
    return this._type === t.EXTERNAL
  }

  isBigInt() {
    return this._type === t.BIGINT
  }
}

// Classifies a number the way the engine does, but without crossing into the
// addon. A number is an `int32` or a `uint32` if it is an integer within the
// respective range, with negative zero being representable as neither.
function numberType(value) {
  if (!Number.isInteger(value)) return t.NUMBER

  // Negative zero passes both range checks below, so exclude it up front.
  if (value === 0 && 1 / value < 0) return t.NUMBER

  let type = t.NUMBER

  if (value >= -0x80000000 && value <= 0x7fffffff) type |= t.INT32
  if (value >= 0 && value <= 0xffffffff) type |= t.UINT32

  return type
}

module.exports = function type(value) {
  switch (typeof value) {
    case 'undefined':
      return new Type(t.UNDEFINED)
    case 'boolean':
      return new Type(t.BOOLEAN)
    case 'number':
      return new Type(numberType(value))
    case 'string':
      return new Type(t.STRING)
    case 'symbol':
      return new Type(t.SYMBOL)
    case 'object':
      return new Type(value === null ? t.NULL : binding.type(value))
    case 'function':
      return new Type(binding.type(value))
    case 'bigint':
      return new Type(t.BIGINT)
  }
}
{
  "name": "bare-type",
  "version": "1.1.1",
  "description": "Cross-realm type predicates for Bare",
  "exports": {
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    },
    "./package": "./package.json"
  },
  "files": [
    "index.js",
    "index.d.ts",
    "binding.c",
    "binding.js",
    "CMakeLists.txt",
    "prebuilds"
  ],
  "addon": true,
  "scripts": {
    "format": "prettier --write . && lunte --fix",
    "lint": "prettier --check . && lunte",
    "test": "brittle-bare --coverage test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-type.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-type/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-type#readme",
  "engines": {
    "bare": ">=1.2.0"
  },
  "devDependencies": {
    "brittle": "^3.4.0",
    "cmake-bare": "^1.1.6",
    "lunte": "^1.8.4",
    "prettier": "^3.4.1",
    "prettier-config-holepunch": "^2.0.0"
  }
}
module.exports = require.addon()
const path = require('bare-path')
const binding = require('./binding')
const errors = require('./lib/errors')
const URLSearchParams = require('./lib/url-search-params')

const kind = Symbol.for('bare.url.kind')

const isWindows = Bare.platform === 'win32'

// Scratch buffer that the binding writes the parsed component offsets into. A
// single shared buffer is reused across every parse.
//
// The offsets are copied out into fields on the URL immediately after a
// successful parse, so nothing observes the buffer across calls.
const components = new Uint32Array(8)

// The value used for a component that is not present in the URL.
const unset = 0xffffffff

// The schemes the parser treats specially. A URL cannot be switched between a
// special and a non-special scheme, and a backslash only terminates a host for
// the former.
const special = new Set(['ftp', 'file', 'http', 'https', 'ws', 'wss'])

// https://url.spec.whatwg.org/#scheme-start-state
const scheme = /^[a-z][a-z0-9+\-.]*$/

// ASCII tab and newline are removed from input before it is parsed rather than
// percent-encoded like the other C0 controls.
const whitespaceAll = /[\t\n\r]/g

// The characters that terminate a host, and so bound the value the host and
// hostname setters accept.
const hostEnd = /[/\\?#]/
const hostEndOpaque = /[/?#]/

// The delimiters that would let a setter's value escape the component it is
// spliced into. Everything else is left to the reparse, which applies the full
// percent-encode set for the component. As elsewhere in this package, each set
// needs two patterns because test() advances a global pattern's lastIndex.
//
// Credentials are not run through the parser and so keep their tabs and
// newlines, percent-encoded, rather than having them stripped.
const userinfoDelimiter = /[\t\n\r/\\?#@:]/
const userinfoDelimiterAll = /[\t\n\r/\\?#@:]/g

const pathDelimiter = /[?#]/
const pathDelimiterAll = /[?#]/g

// A leading or trailing run of C0 control or space in a value that ends up at
// either end of the href. The parser strips those, but only when parsing a URL
// as a whole, so a setter has to encode its own.
const edges = /^[\u0000-\u0020]+|[\u0000-\u0020]+$/g

const escapes = {
  '\t': '%09',
  '\n': '%0A',
  '\r': '%0D',
  '/': '%2F',
  '\\': '%5C',
  ':': '%3A',
  '?': '%3F',
  '@': '%40',
  '#': '%23'
}

// The characters that pathToFileURL() has to percent-encode itself. A backslash
// is a path separator on Windows and so is left alone there.
const reserved = isWindows ? /[%#?\n\r\t]/ : /[%#?\n\r\t\\]/

class URL {
  static get [kind]() {
    return 0 // Compatibility version
  }

  constructor(input, base, opts = {}) {
    if (arguments.length === 0) throw errors.INVALID_URL()

    input = String(input)

    if (base !== undefined) base = String(base)

    this._href = undefined
    this._schemeEnd = 0
    this._usernameEnd = 0
    this._hostStart = 0
    this._hostEnd = 0
    this._pathStart = 0
    this._queryStart = 0
    this._fragmentStart = 0
    this._params = null

    this._parse(input, base, opts.throw !== false)
  }

  get [kind]() {
    return URL[kind]
  }

  // https://url.spec.whatwg.org/#dom-url-href

  get href() {
    return this._href
  }

  set href(value) {
    // Unlike every other setter, the href setter reports a parse failure rather
    // than leaving the URL untouched.
    this._parse(String(value), null, true)

    if (this._params) this._params._parse(this.search)
  }

  // https://url.spec.whatwg.org/#dom-url-protocol

  get protocol() {
    return this._slice(0, this._schemeEnd) + ':'
  }

  set protocol(value) {
    value = strip(String(value))

    const end = value.indexOf(':')

    if (end !== -1) value = value.slice(0, end)

    value = value.toLowerCase()

    if (!scheme.test(value)) return

    const current = this._slice(0, this._schemeEnd)

    if (special.has(current) !== special.has(value)) return

    if (value === 'file' && (this.username || this.password || this.port)) {
      return
    }

    if (current === 'file' && this._hostStart === this._hostEnd) return

    this._update(this._replace(value, 0, this._schemeEnd))
  }

  // https://url.spec.whatwg.org/#dom-url-username

  get username() {
    return this._slice(this._schemeEnd + 3 /* :// */, this._usernameEnd)
  }

  set username(value) {
    if (cannotHaveCredentialsOrPort(this)) {
      return
    }

    value = encodeUserinfo(String(value))

    if (!hasCredentials(this)) value += '@'

    this._update(this._replace(value, this._schemeEnd + 3 /* :// */, this._usernameEnd))
  }

  // https://url.spec.whatwg.org/#dom-url-password

  get password() {
    return this._href.slice(this._usernameEnd + 1 /* : */, this._hostStart - 1 /* @ */)
  }

  set password(value) {
    if (cannotHaveCredentialsOrPort(this)) {
      return
    }

    value = ':' + encodeUserinfo(String(value))

    let end = this._hostStart - 1 /* @ */

    if (!hasCredentials(this)) {
      value += '@'
      end = this._usernameEnd
    }

    this._update(this._replace(value, this._usernameEnd, end))
  }

  // https://url.spec.whatwg.org/#dom-url-host

  get host() {
    return this._slice(this._hostStart, this._pathStart)
  }

  set host(value) {
    if (hasOpaquePath(this)) {
      return
    }

    const protocol = this._slice(0, this._schemeEnd)

    value = truncateHost(protocol, String(value))

    // An `@` would make the reparse read the value as credentials rather than
    // as a host, so it is rejected outright.
    if (value.includes('@')) return

    const separator = portSeparator(value)

    let end = this._hostEnd

    // A port in the value is parsed separately so that an invalid one leaves
    // the existing port in place rather than rejecting the host along with it.
    if (separator !== -1) {
      // A file URL cannot have a port, so a value carrying one is rejected
      // rather than split.
      if (protocol === 'file') return

      const port = parsePort(value.slice(separator + 1))

      value = value.slice(0, separator)

      if (port !== null) {
        value += port
        end = this._pathStart
      }
    }

    if (value === '' && cannotHaveEmptyHost(protocol)) return

    this._update(this._replace(value, this._hostStart, end))
  }

  // https://url.spec.whatwg.org/#dom-url-hostname

  get hostname() {
    return this._slice(this._hostStart, this._hostEnd)
  }

  set hostname(value) {
    if (hasOpaquePath(this)) {
      return
    }

    const protocol = this._slice(0, this._schemeEnd)

    value = truncateHost(protocol, String(value))

    // A port cannot be set through this setter, and a value that carries one is
    // rejected outright rather than truncated. An `@` would make the reparse
    // read the value as credentials rather than as a host.
    if (value.includes('@') || portSeparator(value) !== -1) return

    if (value === '' && cannotHaveEmptyHost(protocol)) return

    this._update(this._replace(value, this._hostStart, this._hostEnd))
  }

  // https://url.spec.whatwg.org/#dom-url-port

  get port() {
    return this._slice(this._hostEnd + 1 /* : */, this._pathStart)
  }

  set port(value) {
    if (cannotHaveCredentialsOrPort(this)) {
      return
    }

    value = strip(String(value))

    if (value !== '') {
      value = parsePort(value)

      if (value === null) return
    }

    this._update(this._replace(value, this._hostEnd, this._pathStart))
  }

  // https://url.spec.whatwg.org/#dom-url-pathname

  get pathname() {
    return this._slice(this._pathStart, this._queryStart - 1 /* ? */)
  }

  set pathname(value) {
    if (hasOpaquePath(this)) {
      return
    }

    value = encodePath(encodeEdges(String(value)))

    // An empty path is left alone, as only a special scheme is required to have
    // one and the reparse inserts it there.
    if (value !== '' && value[0] !== '/' && value[0] !== '\\') {
      value = '/' + value
    }

    this._update(this._replace(value, this._pathStart, this._queryStart - 1 /* ? */))
  }

  // https://url.spec.whatwg.org/#dom-url-search

  get search() {
    return this._slice(this._queryStart - 1 /* ? */, this._fragmentStart - 1 /* # */)
  }

  set search(value) {
    value = String(value)

    if (value !== '') {
      if (value[0] === '?') value = value.slice(1)

      value = '?' + encodeQuery(encodeEdges(value))
    }

    this._update(
      this._replace(value, this._queryStart - 1 /* ? */, this._fragmentStart - 1 /* # */)
    )
  }

  // https://url.spec.whatwg.org/#dom-url-searchparams

  get searchParams() {
    if (this._params === null) {
      this._params = new URLSearchParams(this.search, this)
    }

    return this._params
  }

  // https://url.spec.whatwg.org/#dom-url-hash

  get hash() {
    return this._slice(this._fragmentStart - 1 /* # */)
  }

  set hash(value) {
    value = String(value)

    // The fragment runs to the end of the URL, so nothing in it can escape into
    // another component and no delimiter needs encoding here.
    if (value !== '') {
      if (value[0] === '#') value = value.slice(1)

      value = '#' + encodeEdges(value)
    }

    this._update(this._replace(value, this._fragmentStart - 1 /* # */))
  }

  toString() {
    return this._href
  }

  toJSON() {
    return this._href
  }

  [Symbol.for('bare.inspect')]() {
    return {
      __proto__: { constructor: URL },

      href: this.href,
      protocol: this.protocol,
      username: this.username,
      password: this.password,
      host: this.host,
      hostname: this.hostname,
      port: this.port,
      pathname: this.pathname,
      search: this.search,
      searchParams: this.searchParams,
      hash: this.hash
    }
  }

  _slice(start, end = this._href.length) {
    return this._href.slice(start, end)
  }

  _replace(replacement, start, end = this._href.length) {
    return this._slice(0, start) + replacement + this._slice(end)
  }

  _parse(input, base, shouldThrow) {
    let href

    try {
      href = binding.parse(input, base || null, components, shouldThrow)
    } catch (err) {
      if (err instanceof TypeError || err.code !== undefined) throw err

      throw errors.INVALID_URL(`Invalid URL '${input}'`, input)
    }

    if (href === undefined) return

    this._href = href
    this._schemeEnd = components[0]
    this._usernameEnd = components[1]
    this._hostStart = components[2]
    this._hostEnd = components[3]
    this._pathStart = components[5]

    const queryStart = components[6]
    const fragmentStart = components[7]

    const end = href.length + 1

    this._queryStart = queryStart === unset ? end : queryStart
    this._fragmentStart = fragmentStart === unset ? end : fragmentStart
  }

  _update(input) {
    try {
      this._parse(input, null, true)
    } catch (err) {
      if (err instanceof TypeError) throw err

      return
    }

    if (this._params) this._params._parse(this.search)
  }
}

module.exports = exports = URL

// https://url.spec.whatwg.org/#url-opaque-path
function hasOpaquePath(url) {
  return url.pathname[0] !== '/'
}

// https://url.spec.whatwg.org/#cannot-have-a-username-password-port
function cannotHaveCredentialsOrPort(url) {
  return url.hostname === '' || url.protocol === 'file:'
}

// Whether the URL carries a userinfo section, and so an `@` before its host.
function hasCredentials(url) {
  return url._hostStart !== url._usernameEnd
}

// Almost no input contains any of these, so it is worth ruling all three out
// before rewriting anything.
function strip(value) {
  if (value.indexOf('\t') === -1 && value.indexOf('\n') === -1 && value.indexOf('\r') === -1) {
    return value
  }

  return value.replace(whitespaceAll, '')
}

// https://url.spec.whatwg.org/#host-state
//
// Host parsing stops at the first character that starts another component, so
// anything from there on is dropped rather than spliced into the host.
function truncateHost(protocol, value) {
  const end = value.search(special.has(protocol) ? hostEnd : hostEndOpaque)

  return end === -1 ? value : value.slice(0, end)
}

// Only a special scheme other than file has to have a host.
function cannotHaveEmptyHost(protocol) {
  return protocol !== 'file' && special.has(protocol)
}

// The index of the colon that separates a host from its port, disregarding the
// colons of an IPv6 address, or -1 if the host carries no port.
function portSeparator(host) {
  return host.indexOf(':', host[0] === '[' ? host.indexOf(']') : 0)
}

// https://url.spec.whatwg.org/#port-state
//
// Parsing stops at the first character that is not a digit, so a value with no
// leading digits, or one that overflows, leaves the port as it was.
function parsePort(value) {
  value = /^\d*/.exec(value)[0]

  if (value === '' || Number(value) > 65535) return null

  return ':' + value
}

function encodeUserinfo(value) {
  if (!userinfoDelimiter.test(value)) return value

  return value.replace(userinfoDelimiterAll, (match) => escapes[match])
}

function encodePath(value) {
  if (!pathDelimiter.test(value)) return value

  return value.replace(pathDelimiterAll, (match) => escapes[match])
}

function encodeQuery(value) {
  if (value.indexOf('#') === -1) return value

  return value.replaceAll('#', '%23')
}

// Percent-encodes a leading or trailing run of C0 control or space, which the
// parser would otherwise strip from a value that lands at either end of the
// href. Every component that can end a URL encodes them anyway, so this only
// brings the encoding forward.
function encodeEdges(value) {
  const len = value.length

  if (len === 0) return value
  if (value.charCodeAt(0) > 0x20 && value.charCodeAt(len - 1) > 0x20) return value

  return value.replace(edges, (match) => {
    let encoded = ''

    for (let i = 0, n = match.length; i < n; i++) {
      encoded += '%' + match.charCodeAt(i).toString(16).padStart(2, '0').toUpperCase()
    }

    return encoded
  })
}

exports.URL = URL
exports.URLSearchParams = URLSearchParams

exports.errors = errors

exports.isURL = function isURL(value) {
  if (value instanceof URL) return true

  return typeof value === 'object' && value !== null && value[kind] === URL[kind]
}

exports.isURLSearchParams = URLSearchParams.isURLSearchParams

// https://url.spec.whatwg.org/#dom-url-parse
exports.parse = function parse(input, base) {
  const url = new URL(input, base, { throw: false })
  return url._href ? url : null
}

// https://url.spec.whatwg.org/#dom-url-canparse
exports.canParse = function canParse(input, base) {
  return binding.canParse(String(input), base ? String(base) : null)
}

exports.fileURLToPath = function fileURLToPath(url) {
  if (typeof url === 'string') {
    url = new URL(url)
  }

  if (url.protocol !== 'file:') {
    throw errors.INVALID_URL_SCHEME('The URL must use the file: protocol')
  }

  if (!isWindows && url.hostname) {
    throw errors.INVALID_FILE_URL_HOST("The file: URL host must be 'localhost' or empty")
  }

  const encoded = url.pathname

  // Every check below looks for a percent encoded sequence, as does the decoding
  // that follows, so a path without any can skip all of them.
  const hasEncoded = encoded.includes('%')

  if (hasEncoded) {
    if (isWindows) {
      if (/%2f|%5c/i.test(encoded)) {
        throw errors.INVALID_FILE_URL_PATH(
          'The file: URL path must not include encoded \\ or / characters'
        )
      }
    } else if (/%2f/i.test(encoded)) {
      throw errors.INVALID_FILE_URL_PATH('The file: URL path must not include encoded / characters')
    }

    if (/%00/i.test(encoded)) {
      throw errors.INVALID_FILE_URL_PATH(
        'The file: URL path must not include encoded NUL characters'
      )
    }
  }

  const pathname = path.normalize(hasEncoded ? decodeURIComponent(encoded) : encoded)

  if (isWindows) {
    if (url.hostname) return '\\\\' + url.hostname + pathname

    const letter = pathname.charCodeAt(1) | 0x20

    if (letter < 0x61 /* a */ || letter > 0x7a /* z */ || pathname.charCodeAt(2) !== 0x3a /* : */) {
      throw errors.INVALID_FILE_URL_PATH('The file: URL path must be absolute')
    }

    return pathname.slice(1)
  }

  return pathname
}

exports.pathToFileURL = function pathToFileURL(pathname) {
  let resolved = path.resolve(pathname)

  if (pathname[pathname.length - 1] === '/') {
    resolved += '/'
  } else if (isWindows && pathname[pathname.length - 1] === '\\') {
    resolved += '\\'
  }

  // Paths hardly ever contain any of these, so one pass to rule them out is
  // cheaper than the six or seven replacements it stands in for.
  if (reserved.test(resolved)) {
    resolved = resolved
      .replaceAll('%', '%25') // Must be first
      .replaceAll('#', '%23')
      .replaceAll('?', '%3f')
      .replaceAll('\n', '%0a')
      .replaceAll('\r', '%0d')
      .replaceAll('\t', '%09')

    if (!isWindows) {
      resolved = resolved.replaceAll('\\', '%5c')
    }
  }

  return new URL('file:' + resolved)
}

exports.format = function format(parts) {
  const { protocol, auth, host, hostname, port, pathname, search, query, hash, slashes } = parts

  let result = ''

  if (typeof protocol === 'string') {
    result += protocol

    if (protocol[protocol.length - 1] !== ':') {
      result += ':'
    }

    if (slashes === true || /https?|ftp|gopher|file/.test(protocol)) {
      result += '//'
    }
  }

  if (typeof auth === 'string') {
    if (host || hostname) result += auth + '@'
  }

  if (typeof host === 'string') result += host
  else {
    result += hostname

    if (port) result += ':' + port
  }

  if (typeof pathname === 'string' && pathname !== '') {
    if (pathname[0] !== '/') result += '/'
    result += pathname
  }

  if (typeof search === 'string') {
    if (search[0] !== '?') result += '?'
    result += search
  } else if (typeof query === 'object' && query !== null) {
    result += '?' + new URLSearchParams(query)
  }

  if (typeof hash === 'string') {
    if (hash[0] !== '#') result += '#'
    result += hash
  }

  return result
}
module.exports = class URLError extends Error {
  constructor(msg, fn = URLError, code = fn.name) {
    super(`${code}: ${msg}`)

    this.code = code

    if (Error.captureStackTrace) Error.captureStackTrace(this, fn)
  }

  get name() {
    return 'URLError'
  }

  static INVALID_URL(msg, input) {
    const err = new URLError(msg, URLError.INVALID_URL)

    err.input = input

    return err
  }

  static INVALID_URL_SCHEME(msg = 'Invalid URL') {
    return new URLError(msg, URLError.INVALID_URL_SCHEME)
  }

  static INVALID_FILE_URL_HOST(msg = 'Invalid file: URL host') {
    return new URLError(msg, URLError.INVALID_FILE_URL_HOST)
  }

  static INVALID_FILE_URL_PATH(msg = 'Invalid file: URL path') {
    return new URLError(msg, URLError.INVALID_FILE_URL_PATH)
  }
}
const kind = Symbol.for('bare.url.search-params.kind')

// The URL each instance writes back to, if any. Kept module private so that the
// setter it drives cannot be pointed at an arbitrary object.
const urls = new WeakMap()

class URLSearchParams {
  static get [kind]() {
    return 0 // Compatibility version
  }

  // https://url.spec.whatwg.org/#dom-urlsearchparams-urlsearchparams
  constructor(init, url = null) {
    this._params = null

    if (url) urls.set(this, url)

    if (typeof init === 'string') {
      this._parse(init)
    } else {
      this._params = new Map()

      if (init) {
        for (const [name, value] of typeof init[Symbol.iterator] === 'function'
          ? init
          : Object.entries(init)) {
          this.append(name, value)
        }
      }
    }
  }

  get [kind]() {
    return URLSearchParams[kind]
  }

  // https://url.spec.whatwg.org/#dom-urlsearchparams-size
  get size() {
    let size = 0

    for (const values of this._params.values()) size += values.length

    return size
  }

  // https://url.spec.whatwg.org/#dom-urlsearchparams-append
  append(name, value = null) {
    if (value === null) return

    let list = this._params.get(name)

    if (list === undefined) {
      list = []
      this._params.set(name, list)
    }

    list.push(value)

    this._update()
  }

  // https://url.spec.whatwg.org/#dom-urlsearchparams-delete
  delete(name, value = null) {
    if (value === null) this._params.delete(name)
    else {
      let list = this._params.get(name)

      if (list === undefined) return

      list = list.filter((found) => found !== value)

      if (list.length === 0) this._params.delete(name)
      else this._params.set(name, list)
    }

    this._update()
  }

  // https://url.spec.whatwg.org/#dom-urlsearchparams-get
  get(name) {
    const list = this._params.get(name)

    if (list === undefined) return null

    return list[0]
  }

  // https://url.spec.whatwg.org/#dom-urlsearchparams-getall
  getAll(name) {
    const list = this._params.get(name)

    if (list === undefined) return []

    return Array.from(list)
  }

  // https://url.spec.whatwg.org/#dom-urlsearchparams-has
  has(name, value = null) {
    const list = this._params.get(name)

    if (list === undefined) return false

    if (value === null) return true

    return list.includes(value)
  }

  // https://url.spec.whatwg.org/#dom-urlsearchparams-set
  set(name, value = null) {
    if (value === null) this._params.delete(name)
    else this._params.set(name, [value])

    this._update()
  }

  toString() {
    return this._serialize()
  }

  toJSON() {
    return [...this]
  }

  *[Symbol.iterator]() {
    for (const [name, values] of this._params) {
      for (const value of values) yield [name, value]
    }
  }

  [Symbol.for('bare.inspect')]() {
    const object = {
      __proto__: { constructor: URLSearchParams }
    }

    for (const [name, values] of this._params) {
      if (values.length === 1) object[name] = values[0]
      else object[name] = values
    }

    return object
  }

  // https://url.spec.whatwg.org/#concept-urlsearchparams-update
  _update() {
    const url = urls.get(this)

    if (url === undefined) return

    url.search = this._serialize()
  }

  // https://url.spec.whatwg.org/#concept-urlencoded-parser
  _parse(input) {
    const params = new Map()

    this._params = params

    const len = input.length

    let start = input.charCodeAt(0) === 0x3f /* ? */ ? 1 : 0

    while (start < len) {
      let end = input.indexOf('&', start)
      if (end === -1) end = len

      if (end !== start) {
        let split = input.indexOf('=', start)
        if (split === -1 || split > end) split = end

        const name = decode(input.slice(start, split))
        const value = split === end ? '' : decode(input.slice(split + 1, end))

        const list = params.get(name)

        if (list === undefined) params.set(name, [value])
        else list.push(value)
      }

      start = end + 1
    }
  }

  // https://url.spec.whatwg.org/#concept-urlencoded-serializer
  _serialize() {
    let result = ''
    let separator = ''

    for (const [name, values] of this._params) {
      // The name is shared by all of its values, so it is only encoded once.
      const encoded = encode(String(name))

      for (const value of values) {
        result += separator + encoded + '=' + encode(String(value))
        separator = '&'
      }
    }

    return result
  }
}

module.exports = exports = URLSearchParams

exports.isURLSearchParams = function isURLSearchParams(value) {
  if (value instanceof URLSearchParams) return true

  return typeof value === 'object' && value !== null && value[kind] === URLSearchParams[kind]
}

// https://url.spec.whatwg.org/#concept-urlencoded-byte-serializer
//
// Everything except ASCII alphanumerics and `*-._` is percent-encoded, and a
// space becomes `+` rather than `%20`. Names and values usually consist entirely
// of characters that are left alone, so a component is tested as a whole before
// any of it is rewritten.
const unencoded = /^[\w*.-]*$/

// As above, but allowing spaces, which need nothing more than turning into `+`.
// Spaces are by far the most common reason for a component to need rewriting at
// all, so recognizing them here spares those components a trip through
// encodeURIComponent().
const spaced = /^[\w*. -]*$/

// The characters that encodeURIComponent() leaves alone but the urlencoded byte
// serializer does not. Two patterns are needed because a global regular
// expression cannot be shared between test() and replace(), as test() advances
// its lastIndex.
const extra = /[!'()~]/
const extraAll = /[!'()~]/g

const escapes = {
  '!': '%21',
  "'": '%27',
  '(': '%28',
  ')': '%29',
  '~': '%7E'
}

// A surrogate pair, or a surrogate that is not part of one and so encodes no
// scalar value. Pairs are matched first so that only the capture group can hold
// a lone surrogate; a lookbehind would say this more directly but isn't
// supported by every engine.
const lone = /[\ud800-\udbff][\udc00-\udfff]|([\ud800-\udbff]|[\udc00-\udfff])/g

function encode(component) {
  if (unencoded.test(component)) return component

  if (spaced.test(component)) return component.replaceAll(' ', '+')

  let encoded

  try {
    encoded = encodeURIComponent(component)
  } catch {
    // encodeURIComponent() rejects lone surrogates, whereas the UTF-8 encoder the
    // serializer is defined in terms of replaces them with U+FFFD.
    encoded = encodeURIComponent(
      component.replace(lone, (match, single) => (single === undefined ? match : '\ufffd'))
    )
  }

  if (encoded.includes('%20')) encoded = encoded.replaceAll('%20', '+')

  if (extra.test(encoded)) encoded = encoded.replace(extraAll, (match) => escapes[match])

  return encoded
}

// https://url.spec.whatwg.org/#percent-decode-after-encoding
function decode(component) {
  if (component.indexOf('+') !== -1) component = component.replaceAll('+', ' ')

  if (component.indexOf('%') === -1) return component

  return decodeURIComponent(component)
}
{
  "name": "bare-url",
  "version": "2.5.4",
  "description": "WHATWG URL implementation for JavaScript",
  "exports": {
    "./package": "./package.json",
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    },
    "./global": {
      "types": "./global.d.ts",
      "default": "./global.js"
    }
  },
  "files": [
    "index.js",
    "index.d.ts",
    "global.js",
    "global.d.ts",
    "binding.c",
    "binding.js",
    "CMakeLists.txt",
    "lib",
    "prebuilds"
  ],
  "addon": true,
  "scripts": {
    "format": "prettier --write . && lunte --fix",
    "lint": "prettier --check . && lunte",
    "test": "brittle-bare --coverage test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-url.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-url/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-url",
  "dependencies": {
    "bare-path": "^3.0.0"
  },
  "devDependencies": {
    "brittle": "^3.3.2",
    "cmake-bare": "^1.8.1",
    "cmake-fetch": "^1.0.0",
    "lunte": "^1.6.0",
    "prettier": "^3.3.3",
    "prettier-config-holepunch": "^2.0.0"
  }
}
const FACTOR = new Uint16Array(8)

function factor4096 (i, n) {
  while (n > 0) {
    const f = i & 4095
    FACTOR[--n] = f
    i = (i - f) / 4096
  }
  return FACTOR
}

module.exports = class BigSparseArray {
  constructor () {
    this.tiny = new TinyArray()
    this.maxLength = 4096
    this.factor = 1
  }

  set (index, val) {
    if (val !== undefined) {
      while (index >= this.maxLength) {
        this.maxLength *= 4096
        this.factor++
        if (!this.tiny.isEmptyish()) {
          const t = new TinyArray()
          t.set(0, this.tiny)
          this.tiny = t
        }
      }
    }

    const f = factor4096(index, this.factor)
    const last = this.factor - 1

    let tiny = this.tiny
    for (let i = 0; i < last; i++) {
      const next = tiny.get(f[i])
      if (next === undefined) {
        if (val === undefined) return
        tiny = tiny.set(f[i], new TinyArray())
      } else {
        tiny = next
      }
    }

    return tiny.set(f[last], val)
  }

  get (index) {
    if (index >= this.maxLength) return

    const f = factor4096(index, this.factor)
    const last = this.factor - 1

    let tiny = this.tiny
    for (let i = 0; i < last; i++) {
      tiny = tiny.get(f[i])
      if (tiny === undefined) return
    }

    return tiny.get(f[last])
  }
}

class TinyArray {
  constructor () {
    this.s = 0
    this.b = new Array(1)
    this.f = new Uint16Array(1)
  }

  isEmptyish () {
    return this.b.length === 1 && this.b[0] === undefined
  }

  get (i) {
    if (this.s === 12) return this.b[i]
    const f = i >>> this.s
    const r = i & (this.b.length - 1)
    return this.f[r] === f ? this.b[r] : undefined
  }

  set (i, v) {
    while (this.s !== 12) {
      const f = i >>> this.s
      const r = i & (this.b.length - 1)
      const o = this.b[r]

      if (o === undefined || f === this.f[r]) {
        this.b[r] = v
        this.f[r] = f
        return v
      }

      this.grow()
    }

    this.b[i] = v
    return v
  }

  grow () {
    const os = this.s
    const ob = this.b
    const of = this.f

    this.s += 4
    this.b = new Array(this.b.length << 4)
    this.f = this.s === 12 ? null : new Uint8Array(this.b.length)

    const m = this.b.length - 1

    for (let or = 0; or < ob.length; or++) {
      if (ob[or] === undefined) continue

      const i = of[or] << os | or
      const f = i >>> this.s
      const r = i & m

      this.b[r] = ob[or]
      if (this.s !== 12) this.f[r] = f
    }
  }
}
{
  "name": "big-sparse-array",
  "version": "1.0.3",
  "description": "A sparse array optimised for low memory whilst still being fast",
  "main": "index.js",
  "dependencies": {},
  "devDependencies": {
    "brittle": "^3.1.1",
    "standard": "^16.0.3"
  },
  "scripts": {
    "test": "standard && brittle test.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/mafintosh/big-sparse-array.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/mafintosh/big-sparse-array/issues"
  },
  "homepage": "https://github.com/mafintosh/big-sparse-array"
}
const LE = (exports.LE = new Uint8Array(new Uint16Array([0xff]).buffer)[0] === 0xff)

exports.BE = !LE
const b4a = require('b4a')

const { BE } = require('./endian')

// Zig-zag doubles the magnitude of a value before it is written as a uint, so
// an int only reaches half as far as a uint of the same width does.
const MAX_SAFE_INT = 2 ** 52 - 1
const MIN_SAFE_INT = -(2 ** 52)

exports.state = function (start = 0, end = 0, buffer = null) {
  return { start, end, buffer }
}

const raw = (exports.raw = require('./raw'))

const uint = (exports.uint = {
  preencode(state, n) {
    state.end += n <= 0xfc ? 1 : n <= 0xffff ? 3 : n <= 0xffffffff ? 5 : 9
  },
  encode(state, n) {
    if (n <= 0xfc) uint8.encode(state, n)
    else if (n <= 0xffff) {
      state.buffer[state.start++] = 0xfd
      uint16.encode(state, n)
    } else if (n <= 0xffffffff) {
      state.buffer[state.start++] = 0xfe
      uint32.encode(state, n)
    } else {
      state.buffer[state.start++] = 0xff
      uint64.encode(state, n)
    }
  },
  decode(state) {
    const a = uint8.decode(state)
    if (a <= 0xfc) return a
    if (a === 0xfd) return uint16.decode(state)
    if (a === 0xfe) return uint32.decode(state)
    return uint64.decode(state)
  }
})

const uint8 = (exports.uint8 = {
  preencode(state, n) {
    state.end += 1
  },
  encode(state, n) {
    validateUint(n)
    state.buffer[state.start++] = n
  },
  decode(state) {
    if (state.start >= state.end) throw new Error('Out of bounds')
    return state.buffer[state.start++]
  }
})

const uint16 = (exports.uint16 = {
  preencode(state, n) {
    state.end += 2
  },
  encode(state, n) {
    validateUint(n)
    state.buffer[state.start++] = n
    state.buffer[state.start++] = n >>> 8
  },
  decode(state) {
    if (state.end - state.start < 2) throw new Error('Out of bounds')
    return state.buffer[state.start++] + state.buffer[state.start++] * 0x100
  }
})

const uint24 = (exports.uint24 = {
  preencode(state, n) {
    state.end += 3
  },
  encode(state, n) {
    validateUint(n)
    state.buffer[state.start++] = n
    state.buffer[state.start++] = n >>> 8
    state.buffer[state.start++] = n >>> 16
  },
  decode(state) {
    if (state.end - state.start < 3) throw new Error('Out of bounds')
    return (
      state.buffer[state.start++] +
      state.buffer[state.start++] * 0x100 +
      state.buffer[state.start++] * 0x10000
    )
  }
})

const uint32 = (exports.uint32 = {
  preencode(state, n) {
    state.end += 4
  },
  encode(state, n) {
    validateUint(n)
    state.buffer[state.start++] = n
    state.buffer[state.start++] = n >>> 8
    state.buffer[state.start++] = n >>> 16
    state.buffer[state.start++] = n >>> 24
  },
  decode(state) {
    if (state.end - state.start < 4) throw new Error('Out of bounds')
    return (
      state.buffer[state.start++] +
      state.buffer[state.start++] * 0x100 +
      state.buffer[state.start++] * 0x10000 +
      state.buffer[state.start++] * 0x1000000
    )
  }
})

const uint32be = (exports.uint32be = {
  preencode(state, n) {
    state.end += 4
  },
  encode(state, n) {
    validateUint(n)
    state.buffer[state.start++] = n >>> 24
    state.buffer[state.start++] = n >>> 16
    state.buffer[state.start++] = n >>> 8
    state.buffer[state.start++] = n
  },
  decode(state) {
    if (state.end - state.start < 4) throw new Error('Out of bounds')
    return (
      state.buffer[state.start++] * 0x1000000 +
      state.buffer[state.start++] * 0x10000 +
      state.buffer[state.start++] * 0x100 +
      state.buffer[state.start++]
    )
  }
})

const uint40 = (exports.uint40 = {
  preencode(state, n) {
    state.end += 5
  },
  encode(state, n) {
    validateUint(n)
    const r = Math.floor(n / 0x100)
    uint8.encode(state, n)
    uint32.encode(state, r)
  },
  decode(state) {
    if (state.end - state.start < 5) throw new Error('Out of bounds')
    return uint8.decode(state) + 0x100 * uint32.decode(state)
  }
})

const uint48 = (exports.uint48 = {
  preencode(state, n) {
    state.end += 6
  },
  encode(state, n) {
    validateUint(n)
    const r = Math.floor(n / 0x10000)
    uint16.encode(state, n)
    uint32.encode(state, r)
  },
  decode(state) {
    if (state.end - state.start < 6) throw new Error('Out of bounds')
    return uint16.decode(state) + 0x10000 * uint32.decode(state)
  }
})

const uint56 = (exports.uint56 = {
  preencode(state, n) {
    state.end += 7
  },
  encode(state, n) {
    validateUint(n)
    const r = Math.floor(n / 0x1000000)
    uint24.encode(state, n)
    uint32.encode(state, r)
  },
  decode(state) {
    if (state.end - state.start < 7) throw new Error('Out of bounds')
    return validateSafeUint(uint24.decode(state) + 0x1000000 * uint32.decode(state))
  }
})

const uint64 = (exports.uint64 = {
  preencode(state, n) {
    state.end += 8
  },
  encode(state, n) {
    validateUint(n)
    const r = Math.floor(n / 0x100000000)
    uint32.encode(state, n)
    uint32.encode(state, r)
  },
  decode(state) {
    if (state.end - state.start < 8) throw new Error('Out of bounds')
    return validateSafeUint(uint32.decode(state) + 0x100000000 * uint32.decode(state))
  }
})

exports.uint64be = {
  preencode(state, n) {
    state.end += 8
  },
  encode(state, n) {
    validateUint(n)
    const r = Math.floor(n / 0x100000000)
    uint32be.encode(state, r)
    uint32be.encode(state, n)
  },
  decode(state) {
    if (state.end - state.start < 8) throw new Error('Out of bounds')
    return validateSafeUint(0x100000000 * uint32be.decode(state) + uint32be.decode(state))
  }
}

const int = (exports.int = zigZagInt(uint))
exports.int8 = zigZagInt(uint8)
exports.int16 = zigZagInt(uint16)
exports.int24 = zigZagInt(uint24)
exports.int32 = zigZagInt(uint32)
exports.int40 = zigZagInt(uint40)
exports.int48 = zigZagInt(uint48)
exports.int56 = zigZagInt(uint56)
exports.int64 = zigZagInt(uint64)

const biguint64 = (exports.biguint64 = {
  preencode(state, n) {
    state.end += 8
  },
  encode(state, n) {
    const view = new DataView(state.buffer.buffer, state.start + state.buffer.byteOffset, 8)
    view.setBigUint64(0, n, true) // little endian
    state.start += 8
  },
  decode(state) {
    if (state.end - state.start < 8) throw new Error('Out of bounds')
    const view = new DataView(state.buffer.buffer, state.start + state.buffer.byteOffset, 8)
    const n = view.getBigUint64(0, true) // little endian
    state.start += 8
    return n
  }
})

exports.bigint64 = zigZagBigInt(biguint64)

const biguint = (exports.biguint = {
  preencode(state, n) {
    let len = 0
    for (let m = n; m; m = m >> 64n) len++
    uint.preencode(state, len)
    state.end += 8 * len
  },
  encode(state, n) {
    let len = 0
    for (let m = n; m; m = m >> 64n) len++
    uint.encode(state, len)
    const view = new DataView(state.buffer.buffer, state.start + state.buffer.byteOffset, 8 * len)
    for (let m = n, i = 0; m; m = m >> 64n, i += 8) {
      view.setBigUint64(i, BigInt.asUintN(64, m), true) // little endian
    }
    state.start += 8 * len
  },
  decode(state) {
    const len = uint.decode(state)
    if (state.end - state.start < 8 * len) throw new Error('Out of bounds')
    const view = new DataView(state.buffer.buffer, state.start + state.buffer.byteOffset, 8 * len)
    let n = 0n
    for (let i = len - 1; i >= 0; i--) n = (n << 64n) + view.getBigUint64(i * 8, true) // little endian
    state.start += 8 * len
    return n
  }
})

exports.bigint = zigZagBigInt(biguint)

exports.lexint = require('./lexint')

exports.float32 = {
  preencode(state, n) {
    state.end += 4
  },
  encode(state, n) {
    const view = new DataView(state.buffer.buffer, state.start + state.buffer.byteOffset, 4)
    view.setFloat32(0, n, true) // little endian
    state.start += 4
  },
  decode(state) {
    if (state.end - state.start < 4) throw new Error('Out of bounds')
    const view = new DataView(state.buffer.buffer, state.start + state.buffer.byteOffset, 4)
    const float = view.getFloat32(0, true) // little endian
    state.start += 4
    return float
  }
}

exports.float64 = {
  preencode(state, n) {
    state.end += 8
  },
  encode(state, n) {
    const view = new DataView(state.buffer.buffer, state.start + state.buffer.byteOffset, 8)
    view.setFloat64(0, n, true) // little endian
    state.start += 8
  },
  decode(state) {
    if (state.end - state.start < 8) throw new Error('Out of bounds')
    const view = new DataView(state.buffer.buffer, state.start + state.buffer.byteOffset, 8)
    const float = view.getFloat64(0, true) // little endian
    state.start += 8
    return float
  }
}

const buffer = (exports.buffer = {
  preencode(state, b) {
    uint8array.preencode(state, b)
  },
  encode(state, b) {
    uint8array.encode(state, b)
  },
  decode(state) {
    const len = uint.decode(state)
    if (state.end - state.start < len) throw new Error('Out of bounds')
    return state.buffer.subarray(state.start, (state.start += len))
  }
})

exports.optionalBuffer = {
  preencode(state, b) {
    if (b) uint8array.preencode(state, b)
    else state.end++
  },
  encode(state, b) {
    if (b) uint8array.encode(state, b)
    else state.buffer[state.start++] = 0
  },
  decode(state) {
    const len = uint.decode(state)
    if (len === 0) return null
    if (state.end - state.start < len) throw new Error('Out of bounds')
    return state.buffer.subarray(state.start, (state.start += len))
  }
}

exports.binary = {
  ...buffer,
  preencode(state, b) {
    if (typeof b === 'string') utf8.preencode(state, b)
    else buffer.preencode(state, b)
  },
  encode(state, b) {
    if (typeof b === 'string') utf8.encode(state, b)
    else buffer.encode(state, b)
  }
}

exports.arraybuffer = {
  preencode(state, b) {
    uint.preencode(state, b.byteLength)
    state.end += b.byteLength
  },
  encode(state, b) {
    uint.encode(state, b.byteLength)

    const view = new Uint8Array(b)

    state.buffer.set(view, state.start)
    state.start += b.byteLength
  },
  decode(state) {
    const len = uint.decode(state)

    const b = new ArrayBuffer(len)
    const view = new Uint8Array(b)

    view.set(state.buffer.subarray(state.start, (state.start += len)))

    return b
  }
}

exports.bitarray = {
  preencode(state, m) {
    uint.preencode(state, m.length)
    state.end += Math.ceil(m.length / 8)
  },
  encode(state, m) {
    uint.encode(state, m.length)
    for (let i = 0; i < m.length; i += 8) {
      let byte = 0
      for (let j = 0; j < 8 && i + j < m.length; j++) {
        if (m[i + j]) byte |= 1 << j
      }
      state.buffer[state.start++] = byte
    }
  },
  decode(state) {
    const n = uint.decode(state)
    if (state.end - state.start < Math.ceil(n / 8)) throw new Error('Out of bounds')
    const m = new Array(n)
    for (let i = 0; i < n; i += 8) {
      const byte = state.buffer[state.start++]
      for (let j = 0; j < 8 && i + j < n; j++) {
        m[i + j] = (byte & (1 << j)) !== 0
      }
    }
    return m
  }
}

function typedarray(TypedArray, swap) {
  const n = TypedArray.BYTES_PER_ELEMENT

  return {
    preencode(state, b) {
      uint.preencode(state, b.length)
      state.end += b.byteLength
    },
    encode(state, b) {
      uint.encode(state, b.length)

      const view = new Uint8Array(b.buffer, b.byteOffset, b.byteLength)

      if (BE && swap) swap(view)

      state.buffer.set(view, state.start)
      state.start += b.byteLength
    },
    decode(state) {
      const len = uint.decode(state)

      let b = state.buffer.subarray(state.start, (state.start += len * n))
      if (b.byteLength !== len * n) throw new Error('Out of bounds')
      if (b.byteOffset % n !== 0) b = new Uint8Array(b)

      if (BE && swap) swap(b)

      return new TypedArray(b.buffer, b.byteOffset, b.byteLength / n)
    }
  }
}

const uint8array = (exports.uint8array = typedarray(Uint8Array))
exports.uint16array = typedarray(Uint16Array, b4a.swap16)
exports.uint32array = typedarray(Uint32Array, b4a.swap32)

exports.int8array = typedarray(Int8Array)
exports.int16array = typedarray(Int16Array, b4a.swap16)
exports.int32array = typedarray(Int32Array, b4a.swap32)

exports.biguint64array = typedarray(BigUint64Array, b4a.swap64)
exports.bigint64array = typedarray(BigInt64Array, b4a.swap64)

exports.float32array = typedarray(Float32Array, b4a.swap32)
exports.float64array = typedarray(Float64Array, b4a.swap64)

function string(encoding) {
  return {
    preencode(state, s) {
      const len = b4a.byteLength(s, encoding)
      uint.preencode(state, len)
      state.end += len
    },
    encode(state, s) {
      const len = b4a.byteLength(s, encoding)
      uint.encode(state, len)
      b4a.write(state.buffer, s, state.start, encoding)
      state.start += len
    },
    decode(state) {
      const len = uint.decode(state)
      if (state.end - state.start < len) throw new Error('Out of bounds')
      return b4a.toString(state.buffer, encoding, state.start, (state.start += len))
    },
    fixed(n) {
      return {
        preencode(state) {
          state.end += n
        },
        encode(state, s) {
          b4a.write(state.buffer, s, state.start, n, encoding)
          state.start += n
        },
        decode(state) {
          if (state.end - state.start < n) throw new Error('Out of bounds')
          return b4a.toString(state.buffer, encoding, state.start, (state.start += n))
        }
      }
    }
  }
}

const utf8 = (exports.string = exports.utf8 = string('utf-8'))
exports.ascii = string('ascii')
exports.hex = string('hex')
exports.base64 = string('base64')
exports.ucs2 = exports.utf16le = string('utf16le')

exports.bool = {
  preencode(state, b) {
    state.end++
  },
  encode(state, b) {
    state.buffer[state.start++] = b ? 1 : 0
  },
  decode(state) {
    if (state.start >= state.end) throw Error('Out of bounds')
    return state.buffer[state.start++] === 1
  }
}

const fixed = (exports.fixed = function fixed(n) {
  return {
    preencode(state, s) {
      if (s.byteLength !== n) throw new Error('Incorrect buffer size')
      state.end += n
    },
    encode(state, s) {
      state.buffer.set(s, state.start)
      state.start += n
    },
    decode(state) {
      if (state.end - state.start < n) throw new Error('Out of bounds')
      return state.buffer.subarray(state.start, (state.start += n))
    }
  }
})

exports.fixed32 = fixed(32)
exports.fixed64 = fixed(64)

exports.array = function array(enc) {
  return {
    preencode(state, list) {
      uint.preencode(state, list.length)
      for (let i = 0; i < list.length; i++) enc.preencode(state, list[i])
    },
    encode(state, list) {
      uint.encode(state, list.length)
      for (let i = 0; i < list.length; i++) enc.encode(state, list[i])
    },
    decode(state) {
      const len = uint.decode(state)
      if (len > 0x100000) throw new Error('Array is too big')
      const arr = new Array(len)
      for (let i = 0; i < len; i++) arr[i] = enc.decode(state)
      return arr
    }
  }
}

exports.frame = function frame(enc) {
  const dummy = exports.state()

  return {
    preencode(state, m) {
      const end = state.end
      enc.preencode(state, m)
      uint.preencode(state, state.end - end)
    },
    encode(state, m) {
      dummy.end = 0
      enc.preencode(dummy, m)
      uint.encode(state, dummy.end)
      enc.encode(state, m)
    },
    decode(state) {
      const end = state.end
      const len = uint.decode(state)
      state.end = state.start + len
      const m = enc.decode(state)
      state.start = state.end
      state.end = end
      return m
    }
  }
}

exports.date = {
  preencode(state, d) {
    int.preencode(state, d.getTime())
  },
  encode(state, d) {
    int.encode(state, d.getTime())
  },
  decode(state, d) {
    return new Date(int.decode(state))
  }
}

exports.json = {
  preencode(state, v) {
    utf8.preencode(state, JSON.stringify(v))
  },
  encode(state, v) {
    utf8.encode(state, JSON.stringify(v))
  },
  decode(state) {
    return JSON.parse(utf8.decode(state))
  }
}

exports.ndjson = {
  preencode(state, v) {
    utf8.preencode(state, JSON.stringify(v) + '\n')
  },
  encode(state, v) {
    utf8.encode(state, JSON.stringify(v) + '\n')
  },
  decode(state) {
    return JSON.parse(utf8.decode(state))
  }
}

// simple helper for when you want to just express nothing
exports.none = {
  preencode(state, n) {
    // do nothing
  },
  encode(state, n) {
    // do nothing
  },
  decode(state) {
    return null
  }
}

// "any" encoders here for helping just structure any object without schematising it

const anyArray = {
  preencode(state, arr) {
    uint.preencode(state, arr.length)
    for (let i = 0; i < arr.length; i++) {
      any.preencode(state, arr[i])
    }
  },
  encode(state, arr) {
    uint.encode(state, arr.length)
    for (let i = 0; i < arr.length; i++) {
      any.encode(state, arr[i])
    }
  },
  decode(state) {
    const arr = []
    let len = uint.decode(state)
    while (len-- > 0) {
      arr.push(any.decode(state))
    }
    return arr
  }
}

const anyObject = {
  preencode(state, o) {
    const keys = Object.keys(o)
    uint.preencode(state, keys.length)
    for (const key of keys) {
      utf8.preencode(state, key)
      any.preencode(state, o[key])
    }
  },
  encode(state, o) {
    const keys = Object.keys(o)
    uint.encode(state, keys.length)
    for (const key of keys) {
      utf8.encode(state, key)
      any.encode(state, o[key])
    }
  },
  decode(state) {
    let len = uint.decode(state)
    const o = {}
    while (len-- > 0) {
      const key = utf8.decode(state)
      o[key] = any.decode(state)
    }
    return o
  }
}

const anyTypes = [
  exports.none,
  exports.bool,
  exports.string,
  exports.buffer,
  exports.uint,
  exports.int,
  exports.float64,
  anyArray,
  anyObject,
  exports.date
]

const any = (exports.any = {
  preencode(state, o) {
    const t = getType(o)
    uint.preencode(state, t)
    anyTypes[t].preencode(state, o)
  },
  encode(state, o) {
    const t = getType(o)
    uint.encode(state, t)
    anyTypes[t].encode(state, o)
  },
  decode(state) {
    const t = uint.decode(state)
    if (t >= anyTypes.length) throw new Error('Unknown type: ' + t)
    return anyTypes[t].decode(state)
  }
})

const port = (exports.port = uint16)

const address = (host, family) => {
  return {
    preencode(state, m) {
      host.preencode(state, m.host)
      port.preencode(state, m.port)
    },
    encode(state, m) {
      host.encode(state, m.host)
      port.encode(state, m.port)
    },
    decode(state) {
      return {
        host: host.decode(state),
        family,
        port: port.decode(state)
      }
    }
  }
}

const ipv4 = (exports.ipv4 = {
  preencode(state) {
    state.end += 4
  },
  encode(state, string) {
    const start = state.start
    const end = start + 4

    let i = 0

    while (i < string.length) {
      let n = 0
      let c

      while (i < string.length && (c = string.charCodeAt(i++)) !== /* . */ 0x2e) {
        n = n * 10 + (c - /* 0 */ 0x30)
      }

      state.buffer[state.start++] = n
    }

    state.start = end
  },
  decode(state) {
    if (state.end - state.start < 4) throw new Error('Out of bounds')
    return (
      state.buffer[state.start++] +
      '.' +
      state.buffer[state.start++] +
      '.' +
      state.buffer[state.start++] +
      '.' +
      state.buffer[state.start++]
    )
  }
})

exports.ipv4Address = address(ipv4, 4)

const ipv6 = (exports.ipv6 = {
  preencode(state) {
    state.end += 16
  },
  encode(state, string) {
    const start = state.start
    const end = start + 16

    let i = 0
    let split = null

    while (i < string.length) {
      let n = 0
      let c

      while (i < string.length && (c = string.charCodeAt(i++)) !== /* : */ 0x3a) {
        if (c >= 0x30 && c <= 0x39) n = n * 0x10 + (c - /* 0 */ 0x30)
        else if (c >= 0x41 && c <= 0x46) n = n * 0x10 + (c - /* A */ 0x41 + 10)
        else if (c >= 0x61 && c <= 0x66) n = n * 0x10 + (c - /* a */ 0x61 + 10)
      }

      state.buffer[state.start++] = n >>> 8
      state.buffer[state.start++] = n

      if (i < string.length && string.charCodeAt(i) === /* : */ 0x3a) {
        i++
        split = state.start
      }
    }

    if (split !== null) {
      const offset = end - state.start
      state.buffer.copyWithin(split + offset, split).fill(0, split, split + offset)
    }

    state.start = end
  },
  decode(state) {
    if (state.end - state.start < 16) throw new Error('Out of bounds')
    return (
      (state.buffer[state.start++] * 256 + state.buffer[state.start++]).toString(16) +
      ':' +
      (state.buffer[state.start++] * 256 + state.buffer[state.start++]).toString(16) +
      ':' +
      (state.buffer[state.start++] * 256 + state.buffer[state.start++]).toString(16) +
      ':' +
      (state.buffer[state.start++] * 256 + state.buffer[state.start++]).toString(16) +
      ':' +
      (state.buffer[state.start++] * 256 + state.buffer[state.start++]).toString(16) +
      ':' +
      (state.buffer[state.start++] * 256 + state.buffer[state.start++]).toString(16) +
      ':' +
      (state.buffer[state.start++] * 256 + state.buffer[state.start++]).toString(16) +
      ':' +
      (state.buffer[state.start++] * 256 + state.buffer[state.start++]).toString(16)
    )
  }
})

exports.ipv6Address = address(ipv6, 6)

const ip = (exports.ip = {
  preencode(state, string) {
    const family = string.includes(':') ? 6 : 4
    uint8.preencode(state, family)
    if (family === 4) ipv4.preencode(state)
    else ipv6.preencode(state)
  },
  encode(state, string) {
    const family = string.includes(':') ? 6 : 4
    uint8.encode(state, family)
    if (family === 4) ipv4.encode(state, string)
    else ipv6.encode(state, string)
  },
  decode(state) {
    const family = uint8.decode(state)
    if (family === 4) return ipv4.decode(state)
    else return ipv6.decode(state)
  }
})

exports.ipAddress = {
  preencode(state, m) {
    ip.preencode(state, m.host)
    port.preencode(state, m.port)
  },
  encode(state, m) {
    ip.encode(state, m.host)
    port.encode(state, m.port)
  },
  decode(state) {
    const family = uint8.decode(state)
    return {
      host: family === 4 ? ipv4.decode(state) : ipv6.decode(state),
      family,
      port: port.decode(state)
    }
  }
}

const record = (exports.record = function (keyEncoding, valueEncoding) {
  return {
    preencode(state, v) {
      const keys = Object.keys(v)
      uint.preencode(state, keys.length)
      for (const k of keys) {
        keyEncoding.preencode(state, k)
        valueEncoding.preencode(state, v[k])
      }
    },
    encode(state, v) {
      const keys = Object.keys(v)
      uint.encode(state, keys.length)
      for (const k of keys) {
        keyEncoding.encode(state, k)
        valueEncoding.encode(state, v[k])
      }
    },
    decode(state) {
      const out = Object.create(null)
      const keys = uint.decode(state)
      for (let i = 0; i < keys; i++) {
        out[keyEncoding.decode(state)] = valueEncoding.decode(state)
      }
      return out
    }
  }
})

exports.stringRecord = record(utf8, utf8)

function getType(o) {
  if (o === null || o === undefined) return 0
  if (typeof o === 'boolean') return 1
  if (typeof o === 'string') return 2
  if (b4a.isBuffer(o)) return 3
  if (typeof o === 'number') {
    if (Number.isInteger(o)) return o >= 0 ? 4 : 5
    return 6
  }
  if (Array.isArray(o)) return 7
  if (o instanceof Date) return 9
  if (typeof o === 'object') return 8

  throw new Error('Unsupported type for ' + o)
}

exports.from = function from(enc) {
  if (typeof enc === 'string') return fromNamed(enc)
  if (enc.preencode) return enc
  if (enc.encodingLength) return fromAbstractEncoder(enc)
  return fromCodec(enc)
}

function fromNamed(enc) {
  switch (enc) {
    case 'ascii':
      return raw.ascii
    case 'utf-8':
    case 'utf8':
      return raw.utf8
    case 'hex':
      return raw.hex
    case 'base64':
      return raw.base64
    case 'utf16-le':
    case 'utf16le':
    case 'ucs-2':
    case 'ucs2':
      return raw.ucs2
    case 'ndjson':
      return raw.ndjson
    case 'json':
      return raw.json
    case 'binary':
    default:
      return raw.binary
  }
}

function fromCodec(enc) {
  let tmpM = null
  let tmpBuf = null

  return {
    preencode(state, m) {
      tmpM = m
      tmpBuf = enc.encode(m)
      state.end += tmpBuf.byteLength
    },
    encode(state, m) {
      raw.encode(state, m === tmpM ? tmpBuf : enc.encode(m))
      tmpM = tmpBuf = null
    },
    decode(state) {
      return enc.decode(raw.decode(state))
    }
  }
}

function fromAbstractEncoder(enc) {
  return {
    preencode(state, m) {
      state.end += enc.encodingLength(m)
    },
    encode(state, m) {
      enc.encode(m, state.buffer, state.start)
      state.start += enc.encode.bytes
    },
    decode(state) {
      const m = enc.decode(state.buffer, state.start, state.end)
      state.start += enc.decode.bytes
      return m
    }
  }
}

exports.encode = function encode(enc, m) {
  const state = exports.state()
  enc.preencode(state, m)
  state.buffer = b4a.allocUnsafe(state.end)
  enc.encode(state, m)
  return state.buffer
}

exports.decode = function decode(enc, buffer) {
  return enc.decode(exports.state(0, buffer.byteLength, buffer))
}

function zigZagInt(enc) {
  return {
    preencode(state, n) {
      enc.preencode(state, zigZagEncodeInt(n))
    },
    encode(state, n) {
      enc.encode(state, zigZagEncodeInt(n))
    },
    decode(state) {
      return zigZagDecodeInt(enc.decode(state))
    }
  }
}

function zigZagDecodeInt(n) {
  return n === 0 ? n : (n & 1) === 0 ? n / 2 : -(n + 1) / 2
}

function zigZagEncodeInt(n) {
  validateInt(n)
  // 0, -1, 1, -2, 2, ...
  return n < 0 ? 2 * -n - 1 : n === 0 ? 0 : 2 * n
}

function zigZagBigInt(enc) {
  return {
    preencode(state, n) {
      enc.preencode(state, zigZagEncodeBigInt(n))
    },
    encode(state, n) {
      enc.encode(state, zigZagEncodeBigInt(n))
    },
    decode(state) {
      return zigZagDecodeBigInt(enc.decode(state))
    }
  }
}

function zigZagDecodeBigInt(n) {
  return n === 0n ? n : (n & 1n) === 0n ? n / 2n : -(n + 1n) / 2n
}

function zigZagEncodeBigInt(n) {
  // 0, -1, 1, -2, 2, ...
  return n < 0n ? 2n * -n - 1n : n === 0n ? 0n : 2n * n
}

function validateSafeUint(n) {
  if (n <= Number.MAX_SAFE_INTEGER) return n // Handles NaN as well

  throw outsideUintRange()
}

function validateUint(n) {
  if (n >= 0 && n <= Number.MAX_SAFE_INTEGER) return n // Handles NaN as well

  throw outsideUintRange()
}

function validateInt(n) {
  if (n >= MIN_SAFE_INT && n <= MAX_SAFE_INT) return n // Handles NaN as well

  throw outsideIntRange()
}

// The validations above sit on the hottest paths in the library and are small
// enough to be inlined, which building a message inline would put a stop to.
// Kept out here, the message costs nothing until it is actually thrown.

function outsideUintRange() {
  return new Error(`uint must be between 0 and ${Number.MAX_SAFE_INTEGER}, use biguint`)
}

function outsideIntRange() {
  return new Error(`int must be between ${MIN_SAFE_INT} and ${MAX_SAFE_INT}, use bigint`)
}
module.exports = {
  preencode,
  encode,
  decode
}

function preencode(state, num) {
  if (num < 251) {
    state.end++
  } else if (num < 256) {
    state.end += 2
  } else if (num < 0x10000) {
    state.end += 3
  } else if (num < 0x1000000) {
    state.end += 4
  } else if (num < 0x100000000) {
    state.end += 5
  } else {
    state.end++
    const exp = Math.floor(Math.log(num) / Math.log(2)) - 32
    preencode(state, exp)
    state.end += 6
  }
}

function encode(state, num) {
  const max = 251
  const x = num - max

  if (num < max) {
    state.buffer[state.start++] = num
  } else if (num < 256) {
    state.buffer[state.start++] = max
    state.buffer[state.start++] = x
  } else if (num < 0x10000) {
    state.buffer[state.start++] = max + 1
    state.buffer[state.start++] = (x >> 8) & 0xff
    state.buffer[state.start++] = x & 0xff
  } else if (num < 0x1000000) {
    state.buffer[state.start++] = max + 2
    state.buffer[state.start++] = x >> 16
    state.buffer[state.start++] = (x >> 8) & 0xff
    state.buffer[state.start++] = x & 0xff
  } else if (num < 0x100000000) {
    state.buffer[state.start++] = max + 3
    state.buffer[state.start++] = x >> 24
    state.buffer[state.start++] = (x >> 16) & 0xff
    state.buffer[state.start++] = (x >> 8) & 0xff
    state.buffer[state.start++] = x & 0xff
  } else {
    // need to use Math here as bitwise ops are 32 bit
    const exp = Math.floor(Math.log(x) / Math.log(2)) - 32
    state.buffer[state.start++] = 0xff

    encode(state, exp)
    const rem = x / Math.pow(2, exp - 11)

    for (let i = 5; i >= 0; i--) {
      state.buffer[state.start++] = (rem / Math.pow(2, 8 * i)) & 0xff
    }
  }
}

function decode(state) {
  const max = 251

  if (state.end - state.start < 1) throw new Error('Out of bounds')

  const flag = state.buffer[state.start++]

  if (flag < max) return flag

  if (state.end - state.start < flag - max + 1) {
    throw new Error('Out of bounds.')
  }

  if (flag < 252) {
    return state.buffer[state.start++] + max
  }

  if (flag < 253) {
    return (state.buffer[state.start++] << 8) + state.buffer[state.start++] + max
  }

  if (flag < 254) {
    return (
      (state.buffer[state.start++] << 16) +
      (state.buffer[state.start++] << 8) +
      state.buffer[state.start++] +
      max
    )
  }

  // << 24 result may be interpreted as negative
  if (flag < 255) {
    return (
      state.buffer[state.start++] * 0x1000000 +
      (state.buffer[state.start++] << 16) +
      (state.buffer[state.start++] << 8) +
      state.buffer[state.start++] +
      max
    )
  }

  const exp = decode(state)

  if (state.end - state.start < 6) throw new Error('Out of bounds')

  let rem = 0
  for (let i = 5; i >= 0; i--) {
    rem += state.buffer[state.start++] * Math.pow(2, 8 * i)
  }

  return rem * Math.pow(2, exp - 11) + max
}
{
  "name": "compact-encoding",
  "version": "3.4.0",
  "description": "A series of compact encoding schemes for building small and fast parsers and serializers",
  "main": "index.js",
  "types": "index.d.ts",
  "files": [
    "endian.js",
    "endian.d.ts",
    "index.js",
    "index.d.ts",
    "lexint.js",
    "lexint.d.ts",
    "raw.js",
    "raw.d.ts"
  ],
  "dependencies": {
    "b4a": "^1.3.0"
  },
  "devDependencies": {
    "brittle": "^4.0.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^2.0.0"
  },
  "scripts": {
    "format": "prettier . --write",
    "test": "prettier . --check && brittle-node test.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/holepunchto/compact-encoding.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/compact-encoding/issues"
  },
  "homepage": "https://github.com/holepunchto/compact-encoding"
}
const b4a = require('b4a')

const { BE } = require('./endian')

exports = module.exports = {
  preencode(state, b) {
    state.end += b.byteLength
  },
  encode(state, b) {
    state.buffer.set(b, state.start)
    state.start += b.byteLength
  },
  decode(state) {
    const b = state.buffer.subarray(state.start, state.end)
    state.start = state.end
    return b
  }
}

const buffer = (exports.buffer = {
  preencode(state, b) {
    uint8array.preencode(state, b)
  },
  encode(state, b) {
    uint8array.encode(state, b)
  },
  decode(state) {
    const b = state.buffer.subarray(state.start)
    state.start = state.end
    return b
  }
})

exports.binary = {
  ...buffer,
  preencode(state, b) {
    if (typeof b === 'string') utf8.preencode(state, b)
    else buffer.preencode(state, b)
  },
  encode(state, b) {
    if (typeof b === 'string') utf8.encode(state, b)
    else buffer.encode(state, b)
  }
}

exports.arraybuffer = {
  preencode(state, b) {
    state.end += b.byteLength
  },
  encode(state, b) {
    const view = new Uint8Array(b)

    state.buffer.set(view, state.start)
    state.start += b.byteLength
  },
  decode(state) {
    const b = new ArrayBuffer(state.end - state.start)
    const view = new Uint8Array(b)

    view.set(state.buffer.subarray(state.start))

    state.start = state.end

    return b
  }
}

function typedarray(TypedArray, swap) {
  const n = TypedArray.BYTES_PER_ELEMENT

  return {
    preencode(state, b) {
      state.end += b.byteLength
    },
    encode(state, b) {
      const view = new Uint8Array(b.buffer, b.byteOffset, b.byteLength)

      if (BE && swap) swap(view)

      state.buffer.set(view, state.start)
      state.start += b.byteLength
    },
    decode(state) {
      let b = state.buffer.subarray(state.start)
      if (b.byteOffset % n !== 0) b = new Uint8Array(b)

      if (BE && swap) swap(b)

      state.start = state.end

      return new TypedArray(b.buffer, b.byteOffset, b.byteLength / n)
    }
  }
}

const uint8array = (exports.uint8array = typedarray(Uint8Array))
exports.uint16array = typedarray(Uint16Array, b4a.swap16)
exports.uint32array = typedarray(Uint32Array, b4a.swap32)

exports.int8array = typedarray(Int8Array)
exports.int16array = typedarray(Int16Array, b4a.swap16)
exports.int32array = typedarray(Int32Array, b4a.swap32)

exports.biguint64array = typedarray(BigUint64Array, b4a.swap64)
exports.bigint64array = typedarray(BigInt64Array, b4a.swap64)

exports.float32array = typedarray(Float32Array, b4a.swap32)
exports.float64array = typedarray(Float64Array, b4a.swap64)

function string(encoding) {
  return {
    preencode(state, s) {
      state.end += b4a.byteLength(s, encoding)
    },
    encode(state, s) {
      state.start += b4a.write(state.buffer, s, state.start, encoding)
    },
    decode(state) {
      const s = b4a.toString(state.buffer, encoding, state.start)
      state.start = state.end
      return s
    }
  }
}

const utf8 = (exports.string = exports.utf8 = string('utf-8'))
exports.ascii = string('ascii')
exports.hex = string('hex')
exports.base64 = string('base64')
exports.ucs2 = exports.utf16le = string('utf16le')

exports.array = function array(enc) {
  return {
    preencode(state, list) {
      for (const value of list) enc.preencode(state, value)
    },
    encode(state, list) {
      for (const value of list) enc.encode(state, value)
    },
    decode(state) {
      const arr = []
      while (state.start < state.end) arr.push(enc.decode(state))
      return arr
    }
  }
}

exports.json = {
  preencode(state, v) {
    utf8.preencode(state, JSON.stringify(v))
  },
  encode(state, v) {
    utf8.encode(state, JSON.stringify(v))
  },
  decode(state) {
    return JSON.parse(utf8.decode(state))
  }
}

exports.ndjson = {
  preencode(state, v) {
    utf8.preencode(state, JSON.stringify(v) + '\n')
  },
  encode(state, v) {
    utf8.encode(state, JSON.stringify(v) + '\n')
  },
  decode(state) {
    return JSON.parse(utf8.decode(state))
  }
}
const safetyCatch = require('safety-catch')

module.exports = class CoreCoupler {
  constructor (target, wakeup) {
    this.target = target
    this.wakeup = wakeup
    this.coupled = new Set()

    this._onpeeraddBound = this._onpeeradd.bind(this)
    this.target.on('peer-add', this._onpeeraddBound)
  }

  add (core) {
    const added = this.coupled.size
    this.coupled.add(core)
    if (added !== this.coupled.size) this._couple(core)
  }

  remove (core) {
    this.coupled.delete(core)
  }

  destroy () {
    this.target.off('peer-add', this._onpeeraddBound)
  }

  async update (stream) {
    const muxer = stream.userData
    if (!muxer) return

    try {
      if (!(await this._hasMuxer(this.target, muxer))) return

      let wakeup = null

      for (const core of this.coupled) {
        if (await this._hasMuxer(core, muxer)) continue
        if (wakeup === null) wakeup = []
        wakeup.push(core)
      }

      if (wakeup !== null) {
        this.wakeup(stream, wakeup)
      }
    } catch (err) {
      safetyCatch(err)
    }
  }

  async _couple (core) {
    try {
      let wakeup = null

      for (const peer of this.target.peers) {
        if (await this._hasPeer(core, peer)) continue
        if (wakeup === null) wakeup = []
        wakeup.push(peer)
      }

      if (wakeup !== null && this.coupled.has(core)) {
        for (const peer of wakeup) this.wakeup(peer.stream, [core])
      }
    } catch (err) {
      safetyCatch(err)
    }
  }

  async _onpeeradd (peer) {
    try {
      let wakeup = null

      for (const core of this.coupled) {
        if (await this._hasPeer(core, peer)) continue
        if (wakeup === null) wakeup = []
        wakeup.push(core)
      }

      if (wakeup !== null) {
        this.wakeup(peer.stream, wakeup)
      }
    } catch (err) {
      safetyCatch(err)
    }
  }

  _hasMuxer (core, muxer) {
    const ch = muxer.getLastChannel({ protocol: 'hypercore', id: core.discoveryKey })
    if (ch) return ch.fullyOpened()

    const cha = muxer.getLastChannel({ protocol: 'hypercore/alpha', id: core.discoveryKey })
    if (cha) return cha.fullyOpened()

    return Promise.resolve(false)
  }

  _hasPeer (core, peer) {
    return this._hasMuxer(core, peer.protomux)
  }
}
{
  "name": "core-coupler",
  "version": "2.0.0",
  "description": "Couple the peers of cores",
  "main": "index.js",
  "scripts": {
    "test": "standard"
  },
  "dependencies": {
    "safety-catch": "^1.0.2"
  },
  "devDependencies": {
    "corestore": "^6.18.2",
    "standard": "^17.1.0"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/holepunchto/core-coupler.git"
  },
  "author": "Holepunch Inc",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/core-coupler/issues"
  },
  "homepage": "https://github.com/holepunchto/core-coupler"
}
const fs = require('fs')
const path = require('path')
const fsx = require('fs-native-extensions')
const b4a = require('b4a')
const ReadyResource = require('ready-resource')
const FDLock = require('fd-lock')

const PLATFORM = global.Bare ? global.Bare.platform : global.process.platform
const IS_WIN = PLATFORM === 'win32'
const IS_LINUX = PLATFORM === 'linux'
const MODIFIED_SLACK = 5000
const EMPTY = b4a.alloc(0)
const ATTR = IS_LINUX ? 'user.device-file' : 'device-file'

const nl = IS_WIN ? '\r\n' : '\n'

class DeviceFile extends ReadyResource {
  constructor(filename, { create = true, wait = false, lock = wait, data = {} } = {}) {
    super()

    this.filename = filename
    this.data = data
    this.lock = null

    this._create = create
    this._wait = wait
    this._lock = lock
  }

  async _open() {
    if (await verifyDeviceFile(this)) return
    if (!this._create) throwDeviceFileError('No device file present', false)
    await writeDeviceFile(this)
  }

  static async validate(filename, data) {
    const file = new DeviceFile(filename, { create: false, wait: false, data })
    try {
      await verifyDeviceFile(file)
    } catch (err) {
      if (err.fatal && err.code === 'DEVICE_FILE') return false
      throw err
    }
    return true
  }

  transfer() {
    return this.lock ? this.lock.transfer() : -1
  }

  async _close() {
    if (this.lock) await this.lock.close()
  }

  async suspend() {
    if (!this.opened) await this.ready()
    if (this.lock) await this.lock.suspend()
  }

  async resume() {
    if (!this.opened) await this.ready()
    if (this.lock) await this.lock.resume()
  }
}

module.exports = DeviceFile

async function writeDeviceFile(device) {
  let s = ''

  for (const [key, value] of Object.entries(device.data)) {
    if (value === null) continue
    s += key + '=' + value + nl
  }

  await fs.promises.mkdir(path.dirname(device.filename), { recursive: true })

  const fd = await open(device.filename, 'w')

  device.lock = device._lock ? new FDLock(fd, { wait: device._wait }) : null

  if (device.lock) {
    try {
      await device.lock.ready()
    } catch (err) {
      await device.lock.close()
      throw err
    }
  }

  const st = await fstat(fd)

  const created = Date.now()

  s += 'device/platform=' + PLATFORM + nl
  s += 'device/inode=' + st.ino + nl
  s += 'device/created=' + created + nl

  if (await setAttr(fd, ATTR, b4a.from('original'))) {
    s += 'device/attribute=original' + nl
  }

  await write(fd, b4a.from(s))

  if (!device.lock) await close(fd)
}

async function verifyDeviceFile(device) {
  let fd = 0

  try {
    fd = await open(device.filename, 'r+')
  } catch (e) {
    fd = 0
  }

  if (fd === 0) return false

  device.lock = device._lock ? new FDLock(fd, { wait: device._wait }) : null

  if (device.lock) {
    try {
      await device.lock.ready()
    } catch (err) {
      await device.lock.close()
      throw err
    }
  }

  const buf = await read(fd)
  const result = {}

  const s = b4a.toString(buf).trim().split('\n')

  let inode = 0
  let created = 0
  let attr = ''
  let platform = ''

  for (const ln of s) {
    const i = ln.indexOf('=')
    if (i === -1) continue

    const k = ln.slice(0, i).trim()
    const v = ln.slice(i + 1).trim()

    switch (k) {
      case 'device/platform':
        platform = v
        break
      case 'device/inode':
        inode = Number(v)
        break
      case 'device/created':
        created = Number(v)
        break
      case 'device/attribute':
        attr = v
        break
      default:
        result[k] = v
        break
    }
  }

  for (const [k, v] of Object.entries(device.data)) {
    if (v === null) continue
    if (result[k] === undefined) continue // allow upserts
    if (result[k] !== '' + v) {
      await teardown()
      throwDeviceFileError(`Invalid device file, ${k} has changed. Was ${result[k]}, is ${v}`, true)
    }
  }

  const st = await fstat(fd)
  const at = await getAttr(fd, ATTR)

  const sameAttr = b4a.toString(at || EMPTY) === attr
  const modified = Math.max(st.mtime.getTime(), st.birthtime.getTime())

  if (platform && platform !== PLATFORM) {
    await teardown()
    throwDeviceFileError('Invalid device file, was made on different platform', true)
  }

  if (!sameAttr) {
    await teardown()
    throwDeviceFileError('Invalid device file, was moved unsafely', true)
  }

  if (st.ino !== inode || (created && Math.abs(modified - created) >= MODIFIED_SLACK)) {
    await teardown()
    throwDeviceFileError('Invalid device file, was modified', true)
  }

  if (!device.lock) await close(fd)

  device.data = result
  return true

  async function teardown() {
    if (device.lock) await device.lock.close()
    else await close(fd)
  }
}

async function getAttr(fd, name) {
  try {
    return await fsx.getAttr(fd, name)
  } catch {
    return null
  }
}

async function setAttr(fd, name, value) {
  try {
    await fsx.setAttr(fd, name, value)
    return true
  } catch {
    return false
  }
}

function fstat(fd) {
  return new Promise((resolve, reject) => {
    fs.fstat(fd, (err, st) => {
      if (err) reject(err)
      resolve(st)
    })
  })
}

function close(fd) {
  return new Promise((resolve, reject) => {
    fs.close(fd, (err, st) => {
      if (err) reject(err)
      resolve(st)
    })
  })
}

function write(fd, buf) {
  return new Promise((resolve, reject) => {
    let offset = 0

    onwrite(null, 0)

    function onwrite(err, wrote) {
      if (err) return reject(err)
      if (offset === buf.byteLength) return resolve()
      offset += wrote
      fs.write(fd, buf, offset, buf.byteLength - offset, offset, onwrite)
    }
  })
}

function read(fd) {
  const buf = b4a.allocUnsafe(4096)

  return new Promise((resolve, reject) => {
    let offset = 0

    fs.read(fd, buf, 0, buf.byteLength, 0, onread)

    function onread(err, read) {
      if (err) return reject(err)
      if (read === 0) return resolve(buf.subarray(0, offset))
      offset += read
      fs.read(fd, buf, offset, buf.byteLength - offset, offset, onread)
    }
  })
}

function open(filename, flags) {
  return new Promise((resolve, reject) => {
    fs.open(filename, flags, (err, fd) => {
      if (err) reject(err)
      resolve(fd)
    })
  })
}

function throwDeviceFileError(message, fatal) {
  const err = new Error(message)
  err.code = 'DEVICE_FILE'
  err.fatal = fatal
  throw err
}
{
  "name": "device-file",
  "version": "2.3.1",
  "description": "Device only file",
  "main": "index.js",
  "files": [
    "index.js"
  ],
  "dependencies": {
    "b4a": "^1.6.7",
    "bare-fs": "^4.0.1",
    "bare-path": "^3.0.0",
    "fd-lock": "^2.1.0",
    "fs-native-extensions": "^1.4.0",
    "ready-resource": "^1.2.0"
  },
  "devDependencies": {
    "brittle": "^3.13.1",
    "lunte": "^1.3.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^2.0.0"
  },
  "scripts": {
    "format": "prettier --write .",
    "lint": "prettier --check . && lunte",
    "test": "npm run test:node && npm run test:bare",
    "test:node": "brittle-node --coverage test.js",
    "test:bare": "brittle-bare --coverage test.js"
  },
  "imports": {
    "fs": {
      "bare": "bare-fs",
      "default": "fs"
    },
    "path": {
      "bare": "bare-path",
      "default": "path"
    }
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/holepunchto/device-file.git"
  },
  "author": "Holepunch Inc",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/device-file/issues"
  },
  "homepage": "https://github.com/holepunchto/device-file"
}
module.exports = require('bare-events')
{
  "name": "events-universal",
  "version": "1.0.1",
  "description": "Universal wrapper for the Node.js events module",
  "exports": {
    "./package": "./package.json",
    ".": {
      "bare": "./bare.js",
      "react-native": "./react-native.js",
      "default": "./default.js"
    }
  },
  "files": [
    "index.js",
    "default.js",
    "bare.js",
    "react-native.js"
  ],
  "scripts": {
    "test": "prettier . --check"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/events-universal.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/events-universal/issues"
  },
  "homepage": "https://github.com/holepunchto/events-universal#readme",
  "dependencies": {
    "bare-events": "^2.7.0"
  },
  "devDependencies": {
    "prettier": "^3.4.2",
    "prettier-config-holepunch": "^1.0.0"
  }
}
module.exports = class FixedFIFO {
  constructor (hwm) {
    if (!(hwm > 0) || ((hwm - 1) & hwm) !== 0) throw new Error('Max size for a FixedFIFO should be a power of two')
    this.buffer = new Array(hwm)
    this.mask = hwm - 1
    this.top = 0
    this.btm = 0
    this.next = null
  }

  clear () {
    this.top = this.btm = 0
    this.next = null
    this.buffer.fill(undefined)
  }

  push (data) {
    if (this.buffer[this.top] !== undefined) return false
    this.buffer[this.top] = data
    this.top = (this.top + 1) & this.mask
    return true
  }

  shift () {
    const last = this.buffer[this.btm]
    if (last === undefined) return undefined
    this.buffer[this.btm] = undefined
    this.btm = (this.btm + 1) & this.mask
    return last
  }

  peek () {
    return this.buffer[this.btm]
  }

  isEmpty () {
    return this.buffer[this.btm] === undefined
  }
}
const FixedFIFO = require('./fixed-size')

module.exports = class FastFIFO {
  constructor (hwm) {
    this.hwm = hwm || 16
    this.head = new FixedFIFO(this.hwm)
    this.tail = this.head
    this.length = 0
  }

  clear () {
    this.head = this.tail
    this.head.clear()
    this.length = 0
  }

  push (val) {
    this.length++
    if (!this.head.push(val)) {
      const prev = this.head
      this.head = prev.next = new FixedFIFO(2 * this.head.buffer.length)
      this.head.push(val)
    }
  }

  shift () {
    if (this.length !== 0) this.length--
    const val = this.tail.shift()
    if (val === undefined && this.tail.next) {
      const next = this.tail.next
      this.tail.next = null
      this.tail = next
      return this.tail.shift()
    }

    return val
  }

  peek () {
    const val = this.tail.peek()
    if (val === undefined && this.tail.next) return this.tail.next.peek()
    return val
  }

  isEmpty () {
    return this.length === 0
  }
}
{
  "name": "fast-fifo",
  "version": "1.3.2",
  "description": "A fast fifo implementation similar to the one powering nextTick in Node.js core",
  "main": "index.js",
  "files": [
    "./index.js",
    "./fixed-size.js"
  ],
  "dependencies": {},
  "devDependencies": {
    "standard": "^17.1.0",
    "brittle": "^3.3.2"
  },
  "scripts": {
    "test": "standard && brittle test.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/mafintosh/fast-fifo.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/mafintosh/fast-fifo/issues"
  },
  "homepage": "https://github.com/mafintosh/fast-fifo"
}
const fs = require('fs')
const fsx = require('fs-native-extensions')
const onexit = require('resource-on-exit')
const ReadyResource = require('ready-resource')

module.exports = class FDLock extends ReadyResource {
  constructor(fd, opts = {}) {
    const { wait = false } = opts

    super()

    this._fd = fd
    this._wait = wait
    this._locked = false

    onexit.add(this, closeSync)
  }

  get locked() {
    return this._locked
  }

  async _open() {
    try {
      await this._resume()
    } catch (err) {
      onexit.remove(this)
      await close(this)
      throw err
    }
  }

  async _close() {
    onexit.remove(this)
    await close(this)
    this._locked = false
  }

  transfer() {
    const fd = this._fd
    if (fd === -1) throw new Error('Lock has already been transferred')
    this._fd = -1
    onexit.remove(this)
    return fd
  }

  async suspend() {
    if (!this.opened) await this.ready()
    return this._suspend()
  }

  async _suspend() {
    if (this._fd === -1 || this._locked === false) return
    fsx.unlock(this._fd)
    this._locked = false
  }

  async resume() {
    if (!this.opened) await this.ready()
    return this._resume()
  }

  async _resume() {
    if (this._fd === -1 || this._locked === true) return
    if (this._wait) await fsx.waitForLock(this._fd)
    else if (!fsx.tryLock(this._fd)) {
      throw new Error('File descriptor could not be locked')
    }
    this._locked = true
  }
}

function close(lock) {
  const fd = lock._fd
  if (fd === -1) return
  lock._fd = -1
  return new Promise((resolve) => fs.close(fd, () => resolve()))
}

function closeSync(lock) {
  const fd = lock._fd
  if (fd === -1) return
  lock._fd = -1
  lock._locked = false
  try {
    fs.closeSync(fd)
  } catch {}
}
{
  "name": "fd-lock",
  "version": "2.2.0",
  "description": "Stateful file descriptor locks for JavaScript",
  "exports": {
    "./package": "./package.json",
    ".": "./index.js"
  },
  "imports": {
    "fs": {
      "bare": "bare-fs",
      "default": "fs"
    }
  },
  "files": [
    "index.js"
  ],
  "scripts": {
    "test": "npm run lint && npm run test:bare && npm run test:node",
    "test:bare": "bare test.js",
    "test:node": "node test.js",
    "lint": "prettier --check .",
    "format": "prettier --write ."
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/fd-lock.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/fd-lock/issues"
  },
  "homepage": "https://github.com/holepunchto/fd-lock#readme",
  "dependencies": {
    "bare-fs": "^4.5.0",
    "fs-native-extensions": "^1.4.4",
    "ready-resource": "^1.2.0",
    "resource-on-exit": "^1.0.0"
  },
  "devDependencies": {
    "brittle": "^3.3.2",
    "prettier": "^3.4.2",
    "prettier-config-holepunch": "^2.0.0"
  }
}
exports.fullRoots = function (index, result) {
  if (index & 1) throw new Error('You can only look up roots for depth(0) blocks')
  if (!result) result = []

  index /= 2

  let offset = 0
  let factor = 1

  while (true) {
    if (!index) return result
    while (factor * 2 <= index) factor *= 2
    result.push(offset + factor - 1)
    offset = offset + 2 * factor
    index -= factor
    factor = 1
  }
}

exports.futureRoots = function (index, result) {
  if (index & 1) throw new Error('You can only look up future roots for depth(0) blocks')
  if (!result) result = []

  let factor = 1

  // make first root
  while (factor * 2 <= index) factor *= 2

  // full factor of 2 - done
  if (factor * 2 - 2 === index) return result

  let pos = factor / 2 - 1

  // while its not a full tree
  while ((pos + factor / 2 - 1) !== index) {
    pos += factor

    // read too far, to to left child
    while ((pos + factor / 2 - 1) > index) {
      factor /= 2
      pos -= factor / 2
    }

    // the "gap" is a future root
    result.push(pos - factor / 2)
  }

  return result
}

exports.patch = function (from, to) {
  if (from === 0 || from >= to) return []

  const roots = exports.fullRoots(from)
  const target = exports.fullRoots(to)

  // first find the first root that is different

  let i = 0
  for (; i < target.length; i++) {
    if (i >= roots.length || roots[i] !== target[i]) break
  }

  const patch = []

  if (i < roots.length) {
    // now we need to grow the newest root until it hits the diff one
    let prev = roots.length - 1

    const ite = exports.iterator(roots[prev--])

    while (ite.index !== target[i]) {
      ite.sibling()

      if (prev >= 0 && ite.index === roots[prev]) {
        prev--
      } else {
        patch.push(ite.index)
      }

      patch.push(ite.parent())
    }

    i++ // patched to next root, so inc
  }

  // include the rest

  for (; i < target.length; i++) patch.push(target[i])

  return patch
}

exports.depth = function (index) {
  let depth = 0

  index += 1
  while (!(index & 1)) {
    depth++
    index = rightShift(index)
  }

  return depth
}

exports.sibling = function (index, depth) {
  if (!depth) depth = exports.depth(index)
  const offset = exports.offset(index, depth)

  return exports.index(depth, offset & 1 ? offset - 1 : offset + 1)
}

exports.parent = function (index, depth) {
  if (!depth) depth = exports.depth(index)
  const offset = exports.offset(index, depth)

  return exports.index(depth + 1, rightShift(offset))
}

exports.leftChild = function (index, depth) {
  if (!(index & 1)) return -1
  if (!depth) depth = exports.depth(index)
  return exports.index(depth - 1, exports.offset(index, depth) * 2)
}

exports.rightChild = function (index, depth) {
  if (!(index & 1)) return -1
  if (!depth) depth = exports.depth(index)
  return exports.index(depth - 1, 1 + (exports.offset(index, depth) * 2))
}

exports.children = function (index, depth) {
  if (!(index & 1)) return null

  if (!depth) depth = exports.depth(index)
  const offset = exports.offset(index, depth) * 2

  return [
    exports.index(depth - 1, offset),
    exports.index(depth - 1, offset + 1)
  ]
}

exports.leftSpan = function (index, depth) {
  if (!(index & 1)) return index
  if (!depth) depth = exports.depth(index)
  return exports.offset(index, depth) * twoPow(depth + 1)
}

exports.rightSpan = function (index, depth) {
  if (!(index & 1)) return index
  if (!depth) depth = exports.depth(index)
  return (exports.offset(index, depth) + 1) * twoPow(depth + 1) - 2
}

exports.nextLeaf = function (index) {
  let factor = 1
  let r = index

  while ((r & 1) === 1) {
    r = (r - 1) / 2
    factor *= 2
  }

  return index + factor + 1
}

exports.count = function (index, depth) {
  if (!(index & 1)) return 1
  if (!depth) depth = exports.depth(index)
  return twoPow(depth + 1) - 1
}

exports.countLeaves = function (index) {
  return (exports.count(index) + 1) / 2
}

exports.spans = function (index, depth) {
  if (!(index & 1)) return [index, index]
  if (!depth) depth = exports.depth(index)

  const offset = exports.offset(index, depth)
  const width = twoPow(depth + 1)

  return [offset * width, (offset + 1) * width - 2]
}

exports.index = function (depth, offset) {
  return (1 + 2 * offset) * twoPow(depth) - 1
}

exports.offset = function (index, depth) {
  if (!(index & 1)) return index / 2
  if (!depth) depth = exports.depth(index)

  return ((index + 1) / twoPow(depth) - 1) / 2
}

exports.iterator = function (index) {
  const ite = new Iterator()
  ite.seek(index || 0)
  return ite
}

function twoPow (n) {
  return n < 31 ? 1 << n : ((1 << 30) * (1 << (n - 30)))
}

function rightShift (n) {
  return (n - (n & 1)) / 2
}

function Iterator () {
  this.index = 0
  this.offset = 0
  this.factor = 0
}

Iterator.prototype.seek = function (index) {
  this.index = index
  if (this.index & 1) {
    this.offset = exports.offset(index)
    this.factor = twoPow(exports.depth(index) + 1)
  } else {
    this.offset = index / 2
    this.factor = 2
  }
}

Iterator.prototype.isLeft = function () {
  return (this.offset & 1) === 0
}

Iterator.prototype.isRight = function () {
  return (this.offset & 1) === 1
}

Iterator.prototype.isRoot = function (length) {
  const currentLength = 1 + (this.index + this.factor / 2 - 1) / 2
  if (length < currentLength) return false

  const factor = this.factor * 2
  const index = (this.offset & 1)
    ? this.index - this.factor / 2
    : this.index + this.factor / 2

  const parentLength = 1 + (index + factor / 2 - 1) / 2
  return parentLength > length
}

Iterator.prototype.contains = function (index) {
  return index > this.index
    ? index < (this.index + this.factor / 2)
    : index < this.index
      ? index > (this.index - this.factor / 2)
      : true
}

Iterator.prototype.prev = function () {
  if (!this.offset) return this.index
  this.offset--
  this.index -= this.factor
  return this.index
}

Iterator.prototype.next = function () {
  this.offset++
  this.index += this.factor
  return this.index
}

Iterator.prototype.count = function () {
  if (!(this.index & 1)) return 1
  return this.factor - 1
}

Iterator.prototype.countLeaves = function () {
  return (this.count() + 1) / 2
}

Iterator.prototype.sibling = function () {
  return this.isLeft() ? this.next() : this.prev()
}

Iterator.prototype.parent = function () {
  if (this.offset & 1) {
    this.index -= this.factor / 2
    this.offset = (this.offset - 1) / 2
  } else {
    this.index += this.factor / 2
    this.offset /= 2
  }
  this.factor *= 2
  return this.index
}

Iterator.prototype.leftSpan = function () {
  this.index = this.index - this.factor / 2 + 1
  this.offset = this.index / 2
  this.factor = 2
  return this.index
}

Iterator.prototype.peekLeftSpan = function () {
  return this.index - this.factor / 2 + 1
}

Iterator.prototype.rightSpan = function () {
  this.index = this.index + this.factor / 2 - 1
  this.offset = this.index / 2
  this.factor = 2
  return this.index
}

Iterator.prototype.peekRightSpan = function () {
  return this.index + this.factor / 2 - 1
}

Iterator.prototype.leftChild = function () {
  if (this.factor === 2) return this.index
  this.factor /= 2
  this.index -= this.factor / 2
  this.offset *= 2
  return this.index
}

Iterator.prototype.rightChild = function () {
  if (this.factor === 2) return this.index
  this.factor /= 2
  this.index += this.factor / 2
  this.offset = 2 * this.offset + 1
  return this.index
}

Iterator.prototype.nextTree = function () {
  this.index = this.index + this.factor / 2 + 1
  this.offset = this.index / 2
  this.factor = 2
  return this.index
}

Iterator.prototype.prevTree = function () {
  if (!this.offset) {
    this.index = 0
    this.factor = 2
  } else {
    this.index = this.index - this.factor / 2 - 1
    this.offset = this.index / 2
    this.factor = 2
  }
  return this.index
}

Iterator.prototype.fullRoot = function (index) {
  if (index <= this.index || (this.index & 1) > 0) return false
  while (index > this.index + this.factor + this.factor / 2) {
    this.index += this.factor / 2
    this.factor *= 2
    this.offset /= 2
  }
  return true
}
{
  "name": "flat-tree",
  "version": "1.13.0",
  "description": "A series of functions to map a binary tree to a list",
  "main": "index.js",
  "files": [
    "index.js"
  ],
  "dependencies": {},
  "devDependencies": {
    "brittle": "^3.3.2",
    "standard": "^17.1.0"
  },
  "scripts": {
    "test": "standard && brittle test.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/mafintosh/flat-tree.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/mafintosh/flat-tree/issues"
  },
  "homepage": "https://github.com/mafintosh/flat-tree"
}
require.addon = require('require-addon')

module.exports = require.addon('.', __filename)
const { isWindows } = require('which-runtime')
const binding = require('./binding')

function onwork(err, result) {
  if (err) this.reject(err)
  else this.resolve(result)
}

exports.tryLock = function tryLock(fd, offset = 0, length = 0, opts = {}) {
  if (typeof offset === 'object') {
    opts = offset
    offset = 0
  }

  if (typeof length === 'object') {
    opts = length
    length = 0
  }

  if (typeof opts !== 'object' || opts === null) {
    opts = {}
  }

  try {
    binding.tryLock(fd, offset, length, opts.shared !== true)
  } catch (err) {
    if (err.code === 'EAGAIN') return false
    throw err
  }

  return true
}

exports.waitForLock = function waitForLock(fd, offset = 0, length = 0, opts = {}) {
  if (typeof offset === 'object') {
    opts = offset
    offset = 0
  }

  if (typeof length === 'object') {
    opts = length
    length = 0
  }

  if (typeof opts !== 'object' || opts === null) {
    opts = {}
  }

  const req = {
    handle: null,
    resolve: null,
    reject: null
  }

  const promise = new Promise((resolve, reject) => {
    req.resolve = resolve
    req.reject = reject
  })

  try {
    req.handle = binding.waitForLock(fd, offset, length, opts.shared !== true, req, onwork)
  } catch (err) {
    return Promise.reject(err)
  }

  return promise
}

exports.waitForLockSync = function waitForLockSync(fd, offset = 0, length = 0, opts = {}) {
  if (typeof offset === 'object') {
    opts = offset
    offset = 0
  }

  if (typeof length === 'object') {
    opts = length
    length = 0
  }

  if (typeof opts !== 'object' || opts === null) {
    opts = {}
  }

  binding.waitForLockSync(fd, offset, length, opts.shared !== true)
}

exports.tryDowngradeLock = function tryDowngradeLock(fd, offset = 0, length = 0) {
  try {
    binding.tryDowngradeLock(fd, offset, length)
  } catch (err) {
    if (err.code === 'EAGAIN') return false
    throw err
  }

  return true
}

exports.waitForDowngradeLock = function downgradeLock(fd, offset = 0, length = 0) {
  const req = {
    handle: null,
    resolve: null,
    reject: null
  }

  const promise = new Promise((resolve, reject) => {
    req.resolve = resolve
    req.reject = reject
  })

  try {
    req.handle = binding.waitForDowngradeLock(fd, offset, length, req, onwork)
  } catch (err) {
    return Promise.reject(err)
  }

  return promise
}

exports.waitForDowngradeLockSync = function waitForDowngradeLockSync(fd, offset = 0, length = 0) {
  binding.waitForDowngradeLockSync(fd, offset, length)
}

exports.tryUpgradeLock = function tryUpgradeLock(fd, offset = 0, length = 0) {
  try {
    binding.tryUpgradeLock(fd, offset, length)
  } catch (err) {
    if (err.code === 'EAGAIN') return false
    throw err
  }

  return true
}

exports.waitForUpgradeLock = function upgradeLock(fd, offset = 0, length = 0) {
  const req = {
    handle: null,
    resolve: null,
    reject: null
  }

  const promise = new Promise((resolve, reject) => {
    req.resolve = resolve
    req.reject = reject
  })

  try {
    req.handle = binding.waitForUpgradeLock(fd, offset, length, req, onwork)
  } catch (err) {
    return Promise.reject(err)
  }

  return promise
}

exports.waitForUpgradeLockSync = function waitForUpgradeLockSync(fd, offset = 0, length = 0) {
  binding.waitForUpgradeLockSync(fd, offset, length)
}

exports.unlock = function unlock(fd, offset = 0, length = 0) {
  binding.unlock(fd, offset, length)
}

exports.trim = function trim(fd, offset, length) {
  const req = {
    handle: null,
    resolve: null,
    reject: null
  }

  const promise = new Promise((resolve, reject) => {
    req.resolve = resolve
    req.reject = reject
  })

  try {
    req.handle = binding.trim(fd, offset, length, req, onwork)
  } catch (err) {
    return Promise.reject(err)
  }

  return promise
}

exports.trimSync = function trimSync(fd, offset, length) {
  binding.trimSync(fd, offset, length)
}

exports.sparse = function sparse(fd) {
  // Short circuit on everything but Windows
  if (!isWindows) return Promise.resolve()

  const req = {
    handle: null,
    resolve: null,
    reject: null
  }

  const promise = new Promise((resolve, reject) => {
    req.resolve = resolve
    req.reject = reject
  })

  try {
    req.handle = binding.sparse(fd, req, onwork)
  } catch (err) {
    return Promise.reject(err)
  }

  return promise
}

exports.sparseSync = function sparseSync(fd) {
  // Short circuit on everything but Windows
  if (!isWindows) return

  binding.sparseSync(fd)
}

exports.swap = function swap(from, to) {
  const req = {
    handle: null,
    resolve: null,
    reject: null
  }

  const promise = new Promise((resolve, reject) => {
    req.resolve = resolve
    req.reject = reject
  })

  try {
    req.handle = binding.swap(from, to, req, onwork)
  } catch (err) {
    return Promise.reject(err)
  }

  return promise
}

exports.swapSync = function swapSync(from, to) {
  binding.swapSync(from, to)
}

exports.getAttr = function getAttr(fd, name) {
  const req = {
    handle: null,
    resolve: null,
    reject: null
  }

  const promise = new Promise((resolve, reject) => {
    req.resolve = resolve
    req.reject = reject
  })

  try {
    req.handle = binding.getAttr(fd, name, req, onwork)
  } catch (err) {
    return Promise.reject(err)
  }

  return promise.then((buffer) => (buffer === null ? null : Buffer.from(buffer)))
}

exports.getAttrSync = function getAttrSync(fd, name) {
  const buffer = binding.getAttrSync(fd, name)
  return buffer === null ? null : Buffer.from(buffer)
}

exports.setAttr = function setAttr(fd, name, value, encoding) {
  if (typeof value === 'string') value = Buffer.from(value, encoding)

  const req = {
    value,
    handle: null,
    resolve: null,
    reject: null
  }

  const promise = new Promise((resolve, reject) => {
    req.resolve = resolve
    req.reject = reject
  })

  try {
    req.handle = binding.setAttr(
      fd,
      name,
      value.buffer,
      value.byteOffset,
      value.byteLength,
      req,
      onwork
    )
  } catch (err) {
    return Promise.reject(err)
  }

  return promise
}

exports.setAttrSync = function setAttrSync(fd, name, value, encoding) {
  if (typeof value === 'string') value = Buffer.from(value, encoding)

  binding.setAttrSync(fd, name, value.buffer, value.byteOffset, value.byteLength)
}

exports.removeAttr = function removeAttr(fd, name) {
  const req = {
    handle: null,
    resolve: null,
    reject: null
  }

  const promise = new Promise((resolve, reject) => {
    req.resolve = resolve
    req.reject = reject
  })

  try {
    req.handle = binding.removeAttr(fd, name, req, onwork)
  } catch (err) {
    return Promise.reject(err)
  }

  return promise
}

exports.removeAttrSync = function removeAttrSync(fd, name) {
  binding.removeAttrSync(fd, name)
}

exports.listAttrs = function listAttrs(fd) {
  const req = {
    handle: null,
    resolve: null,
    reject: null
  }

  const promise = new Promise((resolve, reject) => {
    req.resolve = resolve
    req.reject = reject
  })

  try {
    req.handle = binding.listAttrs(fd, req, onwork)
  } catch (err) {
    return Promise.reject(err)
  }

  return promise
}

exports.listAttrsSync = function listAttrsSync(fd) {
  return binding.listAttrsSync(fd)
}
{
  "name": "fs-native-extensions",
  "version": "1.5.1",
  "description": "Native file system extensions for advanced file operations",
  "main": "index.js",
  "files": [
    "index.js",
    "macros.h",
    "binding.c",
    "binding.js",
    "CMakeLists.txt",
    "include",
    "src",
    "prebuilds"
  ],
  "imports": {
    "child_process": {
      "bare": "bare-subprocess",
      "default": "child_process"
    },
    "fs": {
      "bare": "bare-fs",
      "default": "fs"
    },
    "fs/*": {
      "bare": "bare-fs/*",
      "default": "fs/*"
    },
    "path": {
      "bare": "bare-path",
      "default": "path"
    }
  },
  "addon": true,
  "scripts": {
    "format": "prettier --write .",
    "lint": "prettier --check . && lunte",
    "test": "npm run test:node && npm run test:bare",
    "test:bare": "brittle-bare --coverage test.js",
    "test:node": "brittle-node --coverage test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/fs-native-extensions.git"
  },
  "author": "Kasper Isager Dalsgarð <kasper@funktionel.co>",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/fs-native-extensions/issues"
  },
  "homepage": "https://github.com/holepunchto/fs-native-extensions#readme",
  "dependencies": {
    "require-addon": "^1.1.0",
    "which-runtime": "^1.2.0"
  },
  "devDependencies": {
    "bare-compat-napi": "^1.3.8",
    "bare-fs": "^4.4.4",
    "bare-path": "^3.0.0",
    "bare-subprocess": "^4.0.5",
    "brittle": "^3.1.1",
    "cmake-bare": "^1.1.10",
    "cmake-fetch": "^1.4.7",
    "cmake-napi": "^1.0.5",
    "cmake-npm": "^1.1.0",
    "lunte": "^1.6.0",
    "minimist": "^1.2.6",
    "prettier": "^3.5.3",
    "prettier-config-holepunch": "^2.0.0",
    "test-tmp": "^1.2.1"
  }
}
// hyper-identity-core — the half of the identity layer that has no engine.
//
// `apply` and the op vocabulary are a pure function of the log: they read a batch of ops and write
// a KV view. Nothing in here knows whether that log is an autobase or an autobee, and nothing in
// here may learn — that is the property this package exists to hold still.
//
// It deliberately depends on NEITHER engine. A package that dragged autobase in would hand it to
// every consumer transitively, which is the whole reason this split exists: `hyper-identity2` runs
// autobee and must not carry autobase beside it.

export { BaseIdentityRegistry } from './lib/registry.js'
export { createApplyFunction } from './lib/apply.js'
export { OpTypes, encodeOp, decodeOp, validateOp } from './lib/operations.js'
import { decodeOp, OpTypes, validateOp } from './operations.js'
import b4a from 'b4a'

/**
 * Contract validation — checks that the writer who appended an operation
 * has permission to execute it. This prevents rogue Autobase writers from
 * injecting admin-level operations.
 *
 * Permission model:
 * - CREATE_USER: any writer (but first user gets admin via autoRegister)
 * - UPDATE_USER: writer must be the user themselves or an admin
 * - DELETE_USER: admin only
 * - ADD_IDENTITY / REMOVE_IDENTITY / LINK_IDENTITY: admin or self
 * - UPGRADE_PERMISSIONS: admin only
 * - MERGE_ACCOUNTS: admin only
 * - REVOKE_IDENTITY / RESTORE_IDENTITY: admin only
 * - SET_USERNAME: writer must be the user themselves or an admin
 *
 * Note: The first writer (bootstrapper) is always trusted for initial setup.
 * Contract validation is skipped if opts.contractValidation is false (default for
 * backwards compatibility during migration).
 */

// Operations that require admin permissions
const ADMIN_OPS = new Set([
  OpTypes.DELETE_USER,
  OpTypes.UPGRADE_PERMISSIONS,
  OpTypes.MERGE_ACCOUNTS,
  OpTypes.REVOKE_IDENTITY,
  OpTypes.RESTORE_IDENTITY,
  OpTypes.DEACTIVATE_USER,
  OpTypes.REACTIVATE_USER
])

// Operations that require admin OR self (userId in op matches writer's user)
const SELF_OR_ADMIN_OPS = new Set([
  OpTypes.UPDATE_USER,
  OpTypes.ADD_IDENTITY,
  OpTypes.REMOVE_IDENTITY,
  OpTypes.LINK_IDENTITY,
  OpTypes.SET_USERNAME
])

/**
 * Create the Autobase apply function for identity registry
 *
 * @param {Object} opts - Options
 * @param {string} opts.prefix - Key prefix for namespacing (default: '')
 * @param {boolean} opts.contractValidation - Enable contract validation (default: false for migration compat)
 * @returns {Function} Autobase apply function
 */
export function createApplyFunction (opts = {}) {
  const prefix = opts.prefix || ''
  const enableContract = opts.contractValidation === true

  // How to reach the LOG's writer-set controls from whatever object the engine hands apply.
  //
  // ⛔ This exists because the two engines disagree about one word, silently. autobase takes
  // `addWriter(key, { indexer })`; autobee takes `addWriter(key, { isIndexer = true, weight =
  // isIndexer ? 2 : 1 })` (autobee 2.1.0 lib/apply-calls.js:33). An unknown `indexer` key is
  // IGNORED and `isIndexer` DEFAULTS TO TRUE — so passing autobase's spelling to autobee grants
  // every writer indexer standing, with no error, which is the thing hyper-identity's own
  // constructor comment warns deadlocks a manifest migration at 2→3 indexers.
  //
  // So the CORE speaks one vocabulary — `{ isIndexer }` — and each engine package translates on the
  // way out. The translation is one line, in the package that knows which engine it is, instead of
  // a convention every future caller has to remember.
  const adaptHost = opts.adaptHost || ((host) => host)

  // Which writer authored a node — the second thing the two engines spell differently.
  //
  // ⛔ autobase attributes on `node.from.key`; autobee on `node.key` (favela-peer/lib/feed-base.js
  // marks this as a trap it hit). The default below is autobase's shape, so under autobee
  // `node.from` is UNDEFINED and the contract check below is skipped entirely — a permission gate
  // that silently stops gating. Latent today only because `contractValidation` is opt-in.
  //
  // Attribution must never come from the op body; it comes from whatever the engine vouches for,
  // which is exactly why each binding names its own accessor.
  const writerKeyOf = opts.writerKeyOf || ((node) => (node && node.from ? node.from.key : null))

  return async function apply (batch, view, rawHost) {
    const base = adaptHost(rawHost)
    const b = view.batch({ update: false })

    try {
      for (const node of batch) {
        try {
          const op = decodeOp(node.value)
          const validation = validateOp(op)
          if (!validation.valid) {
            console.error(`[IdentityApply] Invalid op: ${validation.error}`)
            continue
          }

          // Contract validation: check writer permissions
          const authorKey = writerKeyOf(node)
          if (enableContract && authorKey) {
            const writerKey = b4a.toString(authorKey, 'hex')
            const violation = await checkContract(b, prefix, writerKey, op)
            if (violation) {
              console.error(`[IdentityApply] Contract violation: ${violation} (writer: ${writerKey.slice(0, 16)}..., op: ${op.type})`)
              continue // Drop the operation
            }
          }

          // The op's own clock, stamped by encodeOp on the authoring peer. Every timestamp and
          // every time COMPARISON below uses it, so this apply is a pure function of the log.
          //
          // ⚠ The `at` fallback is for ops written BEFORE this field existed, already
          // durable in deployed logs. Those replay non-deterministically exactly as they do today —
          // no worse, and unfixable without rewriting history. Every NEW op converges.
          const at = op.timestamp || Date.now()

          switch (op.type) {
            case OpTypes.CREATE_USER: {
              const key = `${prefix}user:${op.userId}`
              const existing = await b.get(key)
              if (existing) continue // Don't overwrite

              // Deterministic admin bootstrap: the FIRST user created gets
              // admin permissions. All subsequent users get read-only.
              // This decision happens in the apply function (deterministic,
              // linearized by Autobase) — NOT in the caller (which would be
              // racy in multi-writer scenarios).
              const isFirstUser = !(await hasAnyUser(b, prefix))
              const permissions = isFirstUser
                ? ['read', 'write', 'admin']
                : ['read']
              const resources = isFirstUser
                ? [{ name: '*', permissions: ['read', 'write', 'admin'] }]
                : [{ name: '*', permissions: ['read'] }]

              const user = {
                userId: op.userId,
                username: op.username || null,
                name: op.name || 'Anonymous',
                email: op.email || null,
                profilePicture: op.profilePicture || null,
                globalPermissions: permissions,
                resources,
                identities: op.identities || [],
                createdAt: op.createdAt || at,
                updatedAt: op.updatedAt || at
              }
              await b.put(key, user)

              // Index identities
              for (const id of user.identities) {
                await b.put(`${prefix}user-identity:${id}`, { userId: op.userId })
              }
              break
            }

            case OpTypes.UPDATE_USER: {
              const key = `${prefix}user:${op.userId}`
              const existing = await b.get(key)
              if (!existing) continue

              const user = existing.value
              if (op.username !== undefined) user.username = op.username
              if (op.name !== undefined) user.name = op.name
              if (op.email !== undefined) user.email = op.email
              if (op.profilePicture !== undefined) user.profilePicture = op.profilePicture
              if (op.theme !== undefined) user.theme = op.theme
              if (op.globalPermissions !== undefined) user.globalPermissions = op.globalPermissions
              user.updatedAt = at
              await b.put(key, user)
              break
            }

            case OpTypes.DELETE_USER: {
              const key = `${prefix}user:${op.userId}`
              const existing = await b.get(key)
              if (!existing) continue

              const user = existing.value
              // Remove identity index entries AND the identity records + their
              // reverse indexes FROM THE VIEW. Since the view-migration these
              // records live in the view; deleteUserCascade only cleaned the
              // legacy db, so the view's pubkey/sshkey/noisekey indexes
              // survived a delete. Mirror REGISTER_IDENTITY's index writes in
              // reverse so a cascade is complete and replicates to peers.
              for (const id of (user.identities || [])) {
                await b.del(`${prefix}user-identity:${id}`)
                const idEntry = await b.get(`${prefix}identity:${id}`)
                if (idEntry) {
                  const idRec = idEntry.value
                  if (idRec.publicKey) await b.del(`${prefix}pubkey:${idRec.publicKey}`)
                  if (idRec.method === 'ssh-key' && idRec.credentials && idRec.credentials.fingerprint) {
                    await b.del(`${prefix}sshkey:${idRec.credentials.fingerprint}`)
                  }
                  if (idRec.method === 'noise-key' && idRec.credentials && idRec.credentials.publicKey) {
                    await b.del(`${prefix}noisekey:${idRec.credentials.publicKey}`)
                  }
                  if (idRec.method === 'atproto' && idRec.credentials) {
                    if (idRec.credentials.did) await b.del(`${prefix}atproto-did:${idRec.credentials.did}`)
                    if (idRec.credentials.handle) await b.del(`${prefix}atproto-handle:${idRec.credentials.handle}`)
                  }
                  await b.del(`${prefix}identity:${id}`)
                }
              }
              if (user.username) await b.del(`${prefix}username:${user.username}`)
              await b.del(key)
              break
            }

            case OpTypes.ADD_IDENTITY: {
              const key = `${prefix}user:${op.userId}`
              const existing = await b.get(key)
              if (!existing) continue

              const user = existing.value
              if (!user.identities.includes(op.identityId)) {
                user.identities.push(op.identityId)
                user.updatedAt = at
                await b.put(key, user)
                await b.put(`${prefix}user-identity:${op.identityId}`, { userId: op.userId })
              }
              break
            }

            case OpTypes.REMOVE_IDENTITY: {
              const key = `${prefix}user:${op.userId}`
              const existing = await b.get(key)
              if (!existing) continue

              const user = existing.value
              user.identities = user.identities.filter(id => id !== op.identityId)
              user.updatedAt = at
              await b.put(key, user)
              await b.del(`${prefix}user-identity:${op.identityId}`)
              break
            }

            case OpTypes.LINK_IDENTITY: {
              await b.put(`${prefix}user-identity:${op.identityId}`, { userId: op.userId })
              const key = `${prefix}user:${op.userId}`
              const existing = await b.get(key)
              if (existing) {
                const user = existing.value
                if (!user.identities.includes(op.identityId)) {
                  user.identities.push(op.identityId)
                  user.updatedAt = at
                  await b.put(key, user)
                }
              }
              // Stamp the owning userId onto the identity record IN THE VIEW.
              // Since the view-migration, identity records live in the view and
              // getIdentity reads it first; the registry method only wrote
              // userId to the legacy db, leaving the view record without it
              // (cross-daemon link ownership invisible). Idempotent.
              const idKey = `${prefix}identity:${op.identityId}`
              const idEntry = await b.get(idKey)
              if (idEntry && idEntry.value.userId !== op.userId) {
                const idRec = idEntry.value
                idRec.userId = op.userId
                idRec.updatedAt = at
                await b.put(idKey, idRec)
              }
              break
            }

            case OpTypes.UPGRADE_PERMISSIONS: {
              const key = `${prefix}user:${op.userId}`
              const existing = await b.get(key)
              if (!existing) continue

              const user = existing.value
              const resourceName = op.resourceName

              if (resourceName === '*') {
                // Global permissions
                user.globalPermissions = op.permissions
              } else {
                // Resource-specific permissions
                if (!user.resources) user.resources = []
                const idx = user.resources.findIndex(r => r.name === resourceName)
                if (idx >= 0) {
                  user.resources[idx].permissions = op.permissions
                } else {
                  user.resources.push({ name: resourceName, permissions: op.permissions })
                }
              }
              user.updatedAt = at
              await b.put(key, user)
              break
            }

            case OpTypes.MERGE_ACCOUNTS: {
              const primaryKey = `${prefix}user:${op.primaryUserId}`
              const secondaryKey = `${prefix}user:${op.secondaryUserId}`

              const primary = await b.get(primaryKey)
              const secondary = await b.get(secondaryKey)

              if (!primary || !secondary) continue

              const pUser = primary.value
              const sUser = secondary.value

              // Merge identities
              for (const id of sUser.identities) {
                if (!pUser.identities.includes(id)) {
                  pUser.identities.push(id)
                }
                await b.put(`${prefix}user-identity:${id}`, { userId: op.primaryUserId })
              }

              // Merge resources
              for (const res of (sUser.resources || [])) {
                const idx = (pUser.resources || []).findIndex(r => r.name === res.name)
                if (idx < 0) {
                  if (!pUser.resources) pUser.resources = []
                  pUser.resources.push(res)
                }
              }

              // Merge global permissions
              const allPerms = new Set([...pUser.globalPermissions, ...sUser.globalPermissions])
              pUser.globalPermissions = [...allPerms]

              pUser.updatedAt = at
              await b.put(primaryKey, pUser)

              // Mark secondary as merged
              sUser.mergedInto = op.primaryUserId
              sUser.updatedAt = at
              await b.put(secondaryKey, sUser)
              break
            }

            case OpTypes.REVOKE_IDENTITY: {
              // Write revocation record to view (consensus-visible)
              // Direct-write db is updated separately in registry method
              await b.put(`${prefix}revoked:${op.identityId}`, {
                identityId: op.identityId,
                revokedAt: op.revokedAt || at,
                reason: op.reason || null
              })
              break
            }

            case OpTypes.RESTORE_IDENTITY: {
              await b.del(`${prefix}revoked:${op.identityId}`)
              break
            }

            case OpTypes.DEACTIVATE_USER: {
              const key = `${prefix}user:${op.userId}`
              const existing = await b.get(key)
              if (!existing) continue

              const user = existing.value

              // Guard: can't deactivate the last admin
              if (user.globalPermissions?.includes('admin')) {
                let adminCount = 0
                for await (const e of b.createReadStream({ gt: `${prefix}user:`, lt: `${prefix}user:\xff` })) {
                  if (e.value.globalPermissions?.includes('admin') && !e.value.deactivatedAt && !e.value.mergedInto) adminCount++
                }
                if (adminCount <= 1) continue // Silently skip — can't deactivate last admin
              }

              user.deactivatedAt = at
              user.deactivatedReason = op.reason || null
              user.updatedAt = at
              await b.put(key, user)
              break
            }

            case OpTypes.REACTIVATE_USER: {
              const key = `${prefix}user:${op.userId}`
              const existing = await b.get(key)
              if (!existing) continue

              const user = existing.value
              delete user.deactivatedAt
              delete user.deactivatedReason
              user.updatedAt = at
              await b.put(key, user)
              break
            }

            case OpTypes.CREATE_LINK_TOKEN: {
              // Store the token row keyed by its hash. Idempotent — a
              // duplicate op for the same hash is silently dropped.
              const key = `${prefix}link-token:${op.tokenHash}`
              const existing = await b.get(key)
              if (existing) break
              await b.put(key, {
                tokenHash: op.tokenHash,
                userId: op.userId,
                expiresAt: op.expiresAt,
                createdAt: op.createdAt || at,
                consumed: false
              })
              break
            }

            case OpTypes.CONSUME_LINK_TOKEN: {
              const tokenKey = `${prefix}link-token:${op.tokenHash}`
              const tokenEntry = await b.get(tokenKey)
              if (!tokenEntry) break // unknown token
              const token = tokenEntry.value
              if (token.consumed) break // double-consume
              if (token.expiresAt && token.expiresAt < at) break // expired

              // 1. Mark token consumed (kept for audit, never deleted)
              await b.put(tokenKey, {
                ...token,
                consumed: true,
                consumedAt: at,
                consumedBy: op.newIdentityId
              })

              // 2. Attach the new identity to the token's target user
              const targetKey = `${prefix}user:${token.userId}`
              const targetEntry = await b.get(targetKey)
              if (targetEntry) {
                const target = targetEntry.value
                if (!target.identities.includes(op.newIdentityId)) {
                  target.identities.push(op.newIdentityId)
                  target.updatedAt = at
                  await b.put(targetKey, target)
                }
                // Always rewrite the user-identity index — if the identity
                // was previously linked to a throwaway, this rebinds it.
                await b.put(`${prefix}user-identity:${op.newIdentityId}`, { userId: token.userId })
              }

              // 3. Optionally deactivate the throwaway user the new
              //    identity used to belong to. Strip the identity from
              //    that user first so it isn't double-counted.
              if (op.deactivateUserId && op.deactivateUserId !== token.userId) {
                const dKey = `${prefix}user:${op.deactivateUserId}`
                const dEntry = await b.get(dKey)
                if (dEntry) {
                  const u = dEntry.value
                  u.identities = (u.identities || []).filter(id => id !== op.newIdentityId)
                  u.deactivatedAt = at
                  u.deactivatedReason = 'merged-via-link-token'
                  u.updatedAt = at
                  await b.put(dKey, u)
                }
              }
              break
            }

            case OpTypes.SET_USERNAME: {
              const key = `${prefix}user:${op.userId}`
              const existing = await b.get(key)
              if (!existing) continue

              const user = existing.value

              // Remove old username index
              if (user.username) {
                await b.del(`${prefix}username:${user.username}`)
              }

              // Set new username
              user.username = op.username
              user.updatedAt = at
              await b.put(key, user)
              await b.put(`${prefix}username:${op.username}`, { userId: op.userId })
              break
            }

            case OpTypes.ADD_WRITER: {
              // Audit trail in the view
              await b.put(`${prefix}writer:${op.writerKey}`, {
                addedAt: at,
                addedBy: op.addedBy || null,
                role: op.role || 'writer'
              })
              // Propagate to the Autobase host so the new writer's core
              // participates in linearization.
              if (base && typeof base.addWriter === 'function') {
                try {
                  await base.addWriter(b4a.from(op.writerKey, 'hex'), {
                    isIndexer: op.role === 'indexer'
                  })
                } catch (err) {
                  console.error('[IdentityApply] base.addWriter failed:', err.message)
                }
              }
              break
            }

            case OpTypes.REMOVE_WRITER: {
              await b.del(`${prefix}writer:${op.writerKey}`)
              if (base && typeof base.removeWriter === 'function') {
                try {
                  await base.removeWriter(b4a.from(op.writerKey, 'hex'))
                } catch (err) {
                  console.error('[IdentityApply] base.removeWriter failed:', err.message)
                }
              }
              break
            }

            case OpTypes.REGISTER_MIRROR_WRITERS: {
              // View-only record. The enclosing app watches this key
              // range and issues in-band addWriter ops on the matching
              // OTHER autobases it hosts. Apply must stay deterministic:
              // no side effects on other autobases from here.
              await b.put(`${prefix}mirror-writers:${op.devicePubkey}`, {
                keys: op.keys,
                recordedAt: at
              })
              break
            }

            case OpTypes.REGISTER_IDENTITY: {
              // Full identity record into the view (replicated to mirrors).
              // Replaces the previous direct-write to a per-daemon
              // Hyperbee (`db`), which was local-only. Mirror peers now
              // see the full record + reverse indexes via standard
              // autobase replication. See pear-git-v3/test/
              // identity-record-replication.test.js for the proof.
              const identity = op.identity
              if (!identity || !identity.identityId) break
              await b.put(`${prefix}identity:${identity.identityId}`, identity)
              if (identity.publicKey) {
                await b.put(`${prefix}pubkey:${identity.publicKey}`, { identityId: identity.identityId })
              }
              if (identity.method === 'ssh-key' && identity.credentials && identity.credentials.fingerprint) {
                await b.put(`${prefix}sshkey:${identity.credentials.fingerprint}`, { identityId: identity.identityId })
              }
              if (identity.method === 'noise-key' && identity.credentials && identity.credentials.publicKey) {
                await b.put(`${prefix}noisekey:${identity.credentials.publicKey}`, { identityId: identity.identityId })
              }
              if (identity.method === 'atproto' && identity.credentials) {
                if (identity.credentials.did) {
                  await b.put(`${prefix}atproto-did:${identity.credentials.did}`, { identityId: identity.identityId })
                }
                if (identity.credentials.handle) {
                  await b.put(`${prefix}atproto-handle:${identity.credentials.handle}`, { identityId: identity.identityId })
                }
              }
              break
            }
          }
        } catch (err) {
          console.error('[IdentityApply] Skipping malformed op:', err.message)
          continue
        }
      }

      await b.flush()
    } catch (err) {
      console.error('[IdentityApply] Batch error:', err.message)
      await b.close()
      throw err
    }
  }
}

/**
 * Check if a writer has permission to execute an operation.
 *
 * Resolves the writer's key to a user by scanning all users' identities,
 * then checks if that user has the required permission level.
 *
 * @param {Object} b - Hyperbee batch
 * @param {string} prefix - Key prefix
 * @param {string} writerKey - Writer's Hypercore public key (hex)
 * @param {Object} op - The operation being executed
 * @returns {string|null} Violation reason, or null if authorized
 */
async function checkContract (b, prefix, writerKey, op) {
  // CREATE_USER is always allowed (autoRegister handles first-user-admin)
  if (op.type === OpTypes.CREATE_USER) return null

  // Find the user associated with this writer key
  const writerUser = await resolveWriterUser(b, prefix, writerKey)

  // If no user found for this writer, check if ANY users exist yet.
  // During bootstrap (empty registry), allow all ops from the first writer.
  if (!writerUser) {
    const hasUsers = await hasAnyUser(b, prefix)
    if (!hasUsers) return null // Bootstrap: empty registry, first writer is trusted
    return `Writer ${writerKey.slice(0, 16)} has no associated user`
  }

  const isAdmin = writerUser.globalPermissions && writerUser.globalPermissions.includes('admin')

  // Admin-only operations
  if (ADMIN_OPS.has(op.type)) {
    if (!isAdmin) return `${op.type} requires admin permissions`
    return null
  }

  // Self-or-admin operations
  if (SELF_OR_ADMIN_OPS.has(op.type)) {
    if (isAdmin) return null
    if (op.userId === writerUser.userId) return null
    return `${op.type} requires admin or self (writer is ${writerUser.userId}, op targets ${op.userId})`
  }

  return null // Unknown op types pass through (validated by validateOp)
}

/**
 * Find the user associated with a writer's Hypercore key.
 * Looks up the writer key as a pubkey identity, then resolves to user.
 */
async function resolveWriterUser (b, prefix, writerKey) {
  // Check pubkey reverse lookup (writer key may be registered as an identity)
  const pubkeyEntry = await b.get(`${prefix}pubkey:${writerKey}`)
  if (pubkeyEntry) {
    const identityLink = await b.get(`${prefix}user-identity:${pubkeyEntry.value.identityId}`)
    if (identityLink) {
      const userEntry = await b.get(`${prefix}user:${identityLink.value.userId}`)
      if (userEntry) return userEntry.value
    }
  }

  // Scan all users to find one with this writer key in their identities
  // (fallback for when the key isn't indexed as a pubkey)
  for await (const entry of b.createReadStream({ gt: `${prefix}user:`, lt: `${prefix}user:\xff` })) {
    const user = entry.value
    if (user.identities && user.identities.includes(writerKey)) {
      return user
    }
  }

  return null
}

async function hasAnyUser (b, prefix) {
  for await (const _ of b.createReadStream({ gt: `${prefix}user:`, lt: `${prefix}user:\xff`, limit: 1 })) {
    return true
  }
  return false
}
import b4a from 'b4a'

/**
 * Identity operation types for Autobase
 */
export const OpTypes = {
  CREATE_USER: 'create-user',
  UPDATE_USER: 'update-user',
  DELETE_USER: 'delete-user',
  ADD_IDENTITY: 'add-identity',
  REMOVE_IDENTITY: 'remove-identity',
  LINK_IDENTITY: 'link-identity',
  UPGRADE_PERMISSIONS: 'upgrade-permissions',
  MERGE_ACCOUNTS: 'merge-accounts',
  REVOKE_IDENTITY: 'revoke-identity',
  RESTORE_IDENTITY: 'restore-identity',
  SET_USERNAME: 'set-username',
  DEACTIVATE_USER: 'deactivate-user',
  REACTIVATE_USER: 'reactivate-user',
  // F.7.2 — link-identity ceremony. CREATE stores a hashed token tied to a
  // user; CONSUME validates expiry + single-use, attaches a new identity
  // to the target user, and optionally deactivates an emptied throwaway.
  CREATE_LINK_TOKEN: 'create-link-token',
  CONSUME_LINK_TOKEN: 'consume-link-token',
  // Multi-writer — expand the Autobase writer set when a new device is
  // linked to the identity. Recorded in the `writer:<key>` view for
  // audit/introspection, and propagated to the Autobase host via
  // host.addWriter in the apply function so the new device's local
  // core contributes to the linearized view (true p2p, not hub-spoke).
  ADD_WRITER: 'add-writer',
  REMOVE_WRITER: 'remove-writer',
  // Multi-autobase coordination. A linked device running a mirror of a
  // multi-autobase app (e.g. pear-git-v3 with orgs/issue-tracker
  // autobases alongside this one) appends this op with the auto-gen
  // local.key of each OTHER autobase it opens. The primary daemon
  // (which IS a writer on those other autobases) watches the view and
  // issues a canonical in-band `addWriter` op per namespace. This op
  // is a control-plane message for the enclosing app — the apply
  // here only records it, no side effects. See pear-git-v3 docs.
  REGISTER_MIRROR_WRITERS: 'register-mirror-writers',
  // Full identity record register. Writes the identity record (method,
  // publicKey, credentials, fingerprint reverse-lookups) into the
  // Autobase view so mirror peers replicate it. Replaces the previous
  // direct-write to a per-daemon Hyperbee, which was local-only and
  // broke cross-daemon visibility (mirrors saw user.identities=[<id>]
  // but had no full record → "Linked identities (0)" / "No SSH keys
  // linked" symptom).
  // See pear-git-v3/test/identity-record-replication.test.js for the
  // mechanical proof + control case.
  REGISTER_IDENTITY: 'register-identity'
}

/**
 * Encode operation for Autobase
 * @param {Object} op - Operation object
 * @returns {Buffer}
 */
export function encodeOp (op) {
  // ⛔ THE CLOCK IS STAMPED HERE, ONCE, ON THE AUTHORING PEER — never inside apply.
  //
  // apply is replayed on every boot and every reorg, and each peer replays independently. A
  // `Date.now()` inside it means two peers replaying the SAME ops write DIFFERENT view bytes, and
  // in the one place apply branched on the clock (link-token expiry) it meant they reached
  // different STATE — one peer consuming a token the other rejected.
  //
  // Putting it in the op makes it part of the bytes every peer agrees on, so replay is a pure
  // function of the log. Same fix hyper-rooms uses (lib/room.js:220,260,273 write it, :418 reads
  // `op.timestamp || Date.now()`).
  //
  // The field is `timestamp` to match hyper-rooms (lib/room.js:220,260,273), the reference
  // implementation on this same substrate — one convention across the stack, not a second one.
  //
  // `...op` comes AFTER so an explicit timestamp wins: the timestamp-preserving paths (a reconciler
  // re-emitting a record it already holds) must not have their clock bumped by a re-encode.
  return b4a.from(JSON.stringify({ timestamp: Date.now(), ...op }))
}

/**
 * Decode operation from Autobase
 * @param {Buffer} buf - Encoded operation
 * @returns {Object}
 */
export function decodeOp (buf) {
  return JSON.parse(b4a.toString(buf))
}

/**
 * Validate operation structure
 * @param {Object} op - Operation to validate
 * @returns {{ valid: boolean, error?: string }}
 */
export function validateOp (op) {
  if (!op || !op.type) return { valid: false, error: 'Missing op type' }

  switch (op.type) {
    case OpTypes.CREATE_USER:
      if (!op.userId) return { valid: false, error: 'Missing userId' }
      if (!op.name) return { valid: false, error: 'Missing name' }
      return { valid: true }

    case OpTypes.UPDATE_USER:
      if (!op.userId) return { valid: false, error: 'Missing userId' }
      return { valid: true }

    case OpTypes.DELETE_USER:
      if (!op.userId) return { valid: false, error: 'Missing userId' }
      return { valid: true }

    case OpTypes.ADD_IDENTITY:
    case OpTypes.REMOVE_IDENTITY:
      if (!op.userId) return { valid: false, error: 'Missing userId' }
      if (!op.identityId) return { valid: false, error: 'Missing identityId' }
      return { valid: true }

    case OpTypes.LINK_IDENTITY:
      if (!op.identityId) return { valid: false, error: 'Missing identityId' }
      if (!op.userId) return { valid: false, error: 'Missing userId' }
      return { valid: true }

    case OpTypes.UPGRADE_PERMISSIONS:
      if (!op.userId) return { valid: false, error: 'Missing userId' }
      if (!op.resourceName) return { valid: false, error: 'Missing resourceName' }
      if (!Array.isArray(op.permissions)) return { valid: false, error: 'permissions must be array' }
      return { valid: true }

    case OpTypes.MERGE_ACCOUNTS:
      if (!op.primaryUserId) return { valid: false, error: 'Missing primaryUserId' }
      if (!op.secondaryUserId) return { valid: false, error: 'Missing secondaryUserId' }
      return { valid: true }

    case OpTypes.REVOKE_IDENTITY:
    case OpTypes.RESTORE_IDENTITY:
      if (!op.identityId) return { valid: false, error: 'Missing identityId' }
      return { valid: true }

    case OpTypes.SET_USERNAME:
      if (!op.userId) return { valid: false, error: 'Missing userId' }
      if (!op.username) return { valid: false, error: 'Missing username' }
      return { valid: true }

    case OpTypes.DEACTIVATE_USER:
      if (!op.userId) return { valid: false, error: 'Missing userId' }
      return { valid: true }

    case OpTypes.REACTIVATE_USER:
      if (!op.userId) return { valid: false, error: 'Missing userId' }
      return { valid: true }

    case OpTypes.CREATE_LINK_TOKEN:
      if (!op.tokenHash) return { valid: false, error: 'Missing tokenHash' }
      if (!op.userId) return { valid: false, error: 'Missing userId' }
      if (typeof op.expiresAt !== 'number') return { valid: false, error: 'Missing expiresAt' }
      return { valid: true }

    case OpTypes.CONSUME_LINK_TOKEN:
      if (!op.tokenHash) return { valid: false, error: 'Missing tokenHash' }
      if (!op.newIdentityId) return { valid: false, error: 'Missing newIdentityId' }
      return { valid: true }

    case OpTypes.ADD_WRITER:
    case OpTypes.REMOVE_WRITER:
      if (!op.writerKey || typeof op.writerKey !== 'string' || op.writerKey.length !== 64) {
        return { valid: false, error: 'writerKey must be 64-char hex' }
      }
      return { valid: true }

    case OpTypes.REGISTER_MIRROR_WRITERS:
      if (!op.devicePubkey || typeof op.devicePubkey !== 'string' || op.devicePubkey.length !== 64) {
        return { valid: false, error: 'devicePubkey must be 64-char hex' }
      }
      if (!op.keys || typeof op.keys !== 'object') {
        return { valid: false, error: 'keys must be a namespace→hex map' }
      }
      for (const [ns, key] of Object.entries(op.keys)) {
        if (typeof key !== 'string' || key.length !== 64) {
          return { valid: false, error: `keys[${ns}] must be 64-char hex` }
        }
      }
      return { valid: true }

    case OpTypes.REGISTER_IDENTITY:
      if (!op.identity || typeof op.identity !== 'object') {
        return { valid: false, error: 'identity object required' }
      }
      if (!op.identity.identityId || typeof op.identity.identityId !== 'string') {
        return { valid: false, error: 'identity.identityId required' }
      }
      if (!op.identity.method || typeof op.identity.method !== 'string') {
        return { valid: false, error: 'identity.method required' }
      }
      return { valid: true }

    default:
      return { valid: false, error: `Unknown op type: ${op.type}` }
  }
}
import crypto from 'hypercore-crypto'
import b4a from 'b4a'
import { createApplyFunction } from './apply.js'
import { encodeOp, OpTypes } from './operations.js'

const RESERVED_USERNAMES = [
  'api', 'admin', 'web', 'sync', 'static', 'assets', 'auth',
  'health', 'status', 'login', 'logout', 'register', 'settings'
]

const USERNAME_REGEX = /^[a-z0-9][a-z0-9-]{1,30}[a-z0-9]$/

/**
 * IdentityRegistry — Autobase-backed user and identity management
 *
 * Provides CRUD operations for users, identities, and permissions
 * using Autobase for multi-writer consensus.
 *
 * Generic: uses 'resources' instead of application-specific terms.
 */
export class BaseIdentityRegistry {
  // Which engine this binding runs on. Subclasses set it; the base has none, because having one
  // would mean the engine-free half had an opinion about engines.
  static engine = null
  get engine () { return this.constructor.engine }

  constructor (store, opts = {}) {
    this._store = store
    this._prefix = opts.prefix || ''
    this._base = null
    this._view = null
    this._ready = false

    // Separate Hyperbee for direct key lookups (identity records, pubkey index)
    // This is NOT the Autobase view — it's a direct-write store for identity metadata
    this.db = opts.db || null

    // Mirror support: when bootstrapKey is passed, join the primary's
    // Autobase instead of creating a fresh one.
    this._bootstrapKey = opts.bootstrapKey || null

    // keyPair: when provided, the Autobase writer's local core uses
    // this Ed25519 keypair instead of a corestore-derived one. This
    // lets the caller pin the autobase ROOT to something stable
    // (e.g. the hyperident device keypair) so the root is deterministic
    // across machines and doesn't need to be shipped as a separate
    // bootstrap key via bundles. The primary's autobase root ends up
    // equal to keyPair.publicKey; mirrors pass that same publicKey as
    // bootstrapKey to join.
    this._keyPair = opts.keyPair || null

    // fastForwardTo: the ORIGIN's `_system` core key (hex or buffer), obtained
    // via `base.getSystemKey()` on the origin and shipped in the deployment
    // bundle. A joining peer fast-forwards straight to that system core instead
    // of deriving its own.
    //
    // This is what keet does on every join: the invitation carries
    // `fastForwardTo: base.getSystemKey()` (keet-core/index.js:1991,1999) and
    // the joiner passes `{ key }` into Autobase (tasks/pairing/keys.js:46 →
    // index.js:1643). Without it a joiner must derive its own system state, and
    // a peer that ends up with an empty indexer set derives a DIFFERENT
    // `_system` core (its manifest is a function of the indexer set,
    // autobase/lib/store.js:541-578) — after which the origin's signed
    // checkpoints are for a core it does not hold, and it can never converge.
    this._fastForwardTo = opts.fastForwardTo || null
  }

  /**
   * Initialize Autobase with Hyperbee view
   */
  async ready () {
    if (this._ready) return

    const apply = createApplyFunction({ prefix: this._prefix, adaptHost: this._adaptHost() })

    const rootKey = this._bootstrapKey
      ? (typeof this._bootstrapKey === 'string'
          ? b4a.from(this._bootstrapKey, 'hex')
          : this._bootstrapKey)
      : null

    this._base = await this._createBase({ apply, rootKey })

    await this._base.ready()
    this._view = this._base.view
    this._ready = true
  }

  /**
   * Build the multi-writer log. THE ONLY ENGINE-SPECIFIC METHOD IN THIS CLASS.
   *
   * Everything else here — 19 appends, 15 updates, every read, every op — is spelled identically by
   * autobase and autobee, which is why the split is a subclass and not a fork.
   *
   * @abstract
   * @param {{ apply: Function, rootKey: Buffer|null }} opts
   */
  async _createBase (opts) {
    throw new Error('BaseIdentityRegistry: subclass must implement _createBase — this class has no engine')
  }

  /**
   * How this engine spells the core's writer-set vocabulary. See hyper-identity-core's README.
   * Null means "speaks `{ isIndexer }` natively" — which is autobee, not autobase.
   */
  _adaptHost () { return null }

  /**
   * Get the multi-writer log (for adding writers, etc.)
   */
  get base () { return this._base }

  /**
   * Get Hyperbee view
   */
  get view () { return this._view }

  // ==================== USER OPERATIONS ====================

  /**
   * Create a new user
   */
  async createUser (opts = {}) {
    const userId = opts.userId || crypto.randomBytes(16).toString('hex')
    // Timestamp-preserving create: when a caller (e.g. the identity-publish
    // reconciler re-emitting a record it already holds) passes explicit
    // createdAt/updatedAt, forward them so the replayed op does NOT bump the
    // record's clock. The apply honours op.createdAt/op.updatedAt and falls
    // back to its own Date.now() when absent, so the default path (no opts)
    // is unchanged. Preserving timestamps is load-bearing for keyed LWW
    // convergence — a re-announce must never out-clock a peer's newer edit.
    const now = Date.now()
    const op = {
      type: OpTypes.CREATE_USER,
      userId,
      username: opts.username || null,
      name: opts.name || 'Anonymous',
      email: opts.email || null,
      profilePicture: opts.profilePicture || null,
      globalPermissions: opts.globalPermissions || ['read'],
      resources: opts.resources || [{ name: '*', permissions: ['read'] }],
      identities: opts.identities || [],
      createdAt: opts.createdAt || now,
      updatedAt: opts.updatedAt || opts.createdAt || now
    }
    await this._base.append(encodeOp(op))
    await this._base.update()
    // Read back from the view — apply may have modified the record (e.g. the
    // first user is promoted to admin).
    //
    // The read-back is ALSO the proof that the write landed. `append()` resolves
    // on a peer that can never linearize (admitted and `writable`, but with an
    // empty indexer set nothing is ever confirmed), and the record simply never
    // appears. The previous `return user || op` handed back the in-memory op,
    // whose userId was minted locally a few lines above — indistinguishable from
    // a persisted user. That turned a broken peer into SILENT DATA LOSS: every
    // request re-ran TOFU, produced a fresh phantom userId, and the profile page
    // read "Not authenticated yet" forever with no error logged anywhere.
    //
    // Never report success for a write that will not commit — the rule every
    // durable autobase app follows (autobase skill, admission-patterns.md).
    const user = await this.getUser(userId)
    if (!user) {
      const err = new Error('createUser: the write did not land — refusing to report success ' +
        'for a user that was not persisted (userId=' + userId + ')')
      err.code = 'EWRITE_NOT_PERSISTED'
      throw err
    }
    return user
  }

  /**
   * Get user by userId
   */
  async getUser (userId) {
    const entry = await this._view.get(`${this._prefix}user:${userId}`)
    return entry ? entry.value : null
  }

  /**
   * Get user by identity ID
   */
  async getUserByIdentity (identityId) {
    const link = await this._view.get(`${this._prefix}user-identity:${identityId}`)
    if (!link) return null
    return this.getUser(link.value.userId)
  }

  /**
   * Get user by username (indexed lookup)
   */
  async getUserByUsername (username) {
    if (!username) return null
    const entry = await this._view.get(`${this._prefix}username:${username}`)
    if (!entry) return null
    return this.getUser(entry.value.userId)
  }

  /**
   * Update user
   */
  async updateUser (userId, updates) {
    const op = {
      type: OpTypes.UPDATE_USER,
      userId,
      ...updates
    }
    await this._base.append(encodeOp(op))
    await this._base.update()
  }

  /**
   * Delete user
   */
  async deleteUser (userId) {
    await this._base.append(encodeOp({ type: OpTypes.DELETE_USER, userId }))
    await this._base.update()
  }

  /**
   * List all users
   */
  async listUsers () {
    const users = []
    for await (const entry of this._view.createReadStream({
      gte: `${this._prefix}user:`,
      lt: `${this._prefix}user:\xff`
    })) {
      if (!entry.value.mergedInto) users.push(entry.value)
    }
    return users
  }

  // ==================== IDENTITY OPERATIONS ====================

  /**
   * Register an identity record.
   *
   * Writes through the Autobase apply (`REGISTER_IDENTITY` op) so the
   * full record + reverse indexes (pubkey, sshkey, noisekey) replicate
   * to mirror peers via the autobase view. Keeps a parallel write to
   * the local Hyperbee `db` for backward-compat with daemons that
   * still read from there during the migration window.
   *
   * Pre-fix this method only wrote to `db` (local-only) — see
   * pear-git-v3/test/identity-record-replication.test.js for the
   * proof that putting it in the view fixes cross-daemon visibility.
   *
   * @param {Object} identity - { identityId, method, publicKey, credentials, ... }
   */
  async registerIdentity (identity) {
    if (!identity || !identity.identityId || !identity.method) {
      throw new Error('registerIdentity requires { identityId, method, ... }')
    }
    // 1. Append op to autobase — view picks up record + reverse indexes
    //    via apply, replicates to all peers.
    await this._base.append(encodeOp({
      type: OpTypes.REGISTER_IDENTITY,
      identity
    }))

    // 2. Mirror to local db too, so legacy code paths reading directly
    //    from db keep working until everything reads from the view.
    //    (This write is local-only; no harm if it diverges — view is
    //    the source of truth.)
    if (this.db) {
      try {
        await this.db.put(`identity:${identity.identityId}`, identity)
        if (identity.method === 'ssh-key' && identity.credentials && identity.credentials.fingerprint) {
          await this.db.put(`sshkey:${identity.credentials.fingerprint}`, { identityId: identity.identityId })
        } else if (identity.method === 'noise-key' && identity.credentials && identity.credentials.publicKey) {
          await this.db.put(`noisekey:${identity.credentials.publicKey}`, { identityId: identity.identityId })
        }
        if (identity.publicKey) {
          await this.db.put(`pubkey:${identity.publicKey}`, { identityId: identity.identityId })
        }
        if (identity.method === 'atproto' && identity.credentials) {
          if (identity.credentials.did) {
            await this.db.put(`atproto-did:${identity.credentials.did}`, { identityId: identity.identityId })
          }
          if (identity.credentials.handle) {
            await this.db.put(`atproto-handle:${identity.credentials.handle}`, { identityId: identity.identityId })
          }
        }
      } catch {
        // db is best-effort cache; failure here doesn't undo the autobase append
      }
    }
  }

  /**
   * Get identity by ID. Try the autobase view first (replicated, source
   * of truth post-fix); fall back to the local db (legacy/cache).
   */
  async getIdentity (identityId) {
    if (this._view) {
      const entry = await this._view.get(`${this._prefix}identity:${identityId}`).catch(() => null)
      if (entry && entry.value) return entry.value
    }
    if (!this.db) return null
    const entry = await this.db.get(`identity:${identityId}`).catch(() => null)
    return entry ? entry.value : null
  }

  /**
   * Get identity by public key. View first, then db fallback.
   */
  async getIdentityByPublicKey (publicKey) {
    if (this._view) {
      const idx = await this._view.get(`${this._prefix}pubkey:${publicKey}`).catch(() => null)
      if (idx && idx.value && idx.value.identityId) {
        const id = await this.getIdentity(idx.value.identityId)
        if (id) return id
      }
    }
    if (!this.db) return null
    const entry = await this.db.get(`pubkey:${publicKey}`).catch(() => null)
    if (!entry) return null
    return this.getIdentity(entry.value.identityId)
  }

  /**
   * Get identity by atproto did:plc. View first, then db fallback.
   * (The `atproto` provider registers identityId = did, so this also
   * covers direct getIdentity(did); the index makes it explicit + future-proof.)
   */
  async getIdentityByDid (did) {
    if (this._view) {
      const idx = await this._view.get(`${this._prefix}atproto-did:${did}`).catch(() => null)
      if (idx && idx.value && idx.value.identityId) {
        const id = await this.getIdentity(idx.value.identityId)
        if (id) return id
      }
    }
    if (!this.db) return null
    const entry = await this.db.get(`atproto-did:${did}`).catch(() => null)
    if (!entry) return null
    return this.getIdentity(entry.value.identityId)
  }

  /**
   * Get identity by atproto handle (e.g. alice.7rnx.net). View first, then db.
   * This is the multi-user-handle lookup: handle -> the bound identity.
   */
  async getIdentityByAtprotoHandle (handle) {
    if (this._view) {
      const idx = await this._view.get(`${this._prefix}atproto-handle:${handle}`).catch(() => null)
      if (idx && idx.value && idx.value.identityId) {
        const id = await this.getIdentity(idx.value.identityId)
        if (id) return id
      }
    }
    if (!this.db) return null
    const entry = await this.db.get(`atproto-handle:${handle}`).catch(() => null)
    if (!entry) return null
    return this.getIdentity(entry.value.identityId)
  }

  /**
   * Get identity by SSH key fingerprint. View first, then db fallback.
   */
  async getIdentityBySSHKey (fingerprint) {
    if (this._view) {
      const idx = await this._view.get(`${this._prefix}sshkey:${fingerprint}`).catch(() => null)
      if (idx && idx.value && idx.value.identityId) {
        const id = await this.getIdentity(idx.value.identityId)
        if (id) return id
      }
    }
    if (!this.db) return null
    const entry = await this.db.get(`sshkey:${fingerprint}`).catch(() => null)
    if (!entry) return null
    return this.getIdentity(entry.value.identityId)
  }

  /**
   * Get identity by Noise protocol public key. View first, then db fallback.
   */
  async getIdentityByNoiseKey (noiseKey) {
    if (this._view) {
      const idx = await this._view.get(`${this._prefix}noisekey:${noiseKey}`).catch(() => null)
      if (idx && idx.value && idx.value.identityId) {
        const id = await this.getIdentity(idx.value.identityId)
        if (id) return id
      }
    }
    if (!this.db) return null
    const entry = await this.db.get(`noisekey:${noiseKey}`).catch(() => null)
    if (!entry) return null
    return this.getIdentity(entry.value.identityId)
  }

  /**
   * Link identity to user via Autobase
   *
   * SECURITY: This method moves an identity to a user account. Callers MUST
   * verify ownership before calling (e.g., via challenge-response signature).
   * The registry itself cannot verify ownership because it doesn't have the
   * request context. Auth providers are responsible for proving key ownership
   * before calling this.
   *
   * @param {string} identityId - Identity to link
   * @param {string} userId - User to link to
   * @param {Object} [opts] - Options
   * @param {boolean} [opts.verified] - Set to true to confirm caller has verified ownership
   */
  async linkIdentityToUser (identityId, userId, opts = {}) {
    // Guard: require explicit verification flag to prevent accidental misuse
    if (!opts.verified) {
      throw new Error('linkIdentityToUser requires opts.verified=true — caller must verify key ownership first')
    }

    // Check if identity is already linked to a different user
    if (this.db) {
      const identity = await this.getIdentity(identityId)
      if (identity && identity.userId && identity.userId !== userId) {
        throw new Error(`Identity already linked to user ${identity.userId}`)
      }
    }

    // Update direct-write db first (source of truth for identity records)
    if (this.db) {
      const identity = await this.getIdentity(identityId)
      if (identity) {
        identity.userId = userId
        identity.updatedAt = Date.now()
        await this.db.put(`identity:${identityId}`, identity)
      }
    }

    // Then append to Autobase for multi-writer consensus
    await this._base.append(encodeOp({
      type: OpTypes.LINK_IDENTITY,
      identityId,
      userId
    }))
    await this._base.update()
  }

  /**
   * Add identity to user
   */
  async addIdentityToUser (userId, identityId) {
    await this._base.append(encodeOp({
      type: OpTypes.ADD_IDENTITY,
      userId,
      identityId
    }))
    await this._base.update()
  }

  /**
   * Remove identity from user
   */
  async removeIdentityFromUser (userId, identityId) {
    await this._base.append(encodeOp({
      type: OpTypes.REMOVE_IDENTITY,
      userId,
      identityId
    }))
    await this._base.update()
  }

  /**
   * List user's identities
   */
  async listUserIdentities (userId) {
    const user = await this.getUser(userId)
    if (!user) return []

    const identities = []
    for (const id of user.identities) {
      const identity = await this.getIdentity(id)
      if (identity) identities.push(identity)
    }
    return identities
  }

  // ==================== PERMISSIONS ====================

  /**
   * Upgrade permissions for user on resource
   * @param {string} userId
   * @param {string} resourceName - Resource name or '*' for global
   * @param {string[]} permissions - ['read', 'write', 'admin']
   */
  async upgradePermissions (userId, resourceName, permissions) {
    await this._base.append(encodeOp({
      type: OpTypes.UPGRADE_PERMISSIONS,
      userId,
      resourceName,
      permissions
    }))
    await this._base.update()
  }

  /**
   * Check if any admin user exists
   */
  async hasAnyAdmin () {
    return this._hasAnyAdmin()
  }

  async _hasAnyAdmin () {
    for await (const entry of this._view.createReadStream({
      gte: `${this._prefix}user:`,
      lt: `${this._prefix}user:\xff`
    })) {
      const user = entry.value
      if (user.mergedInto) continue
      if (user.globalPermissions && user.globalPermissions.includes('admin')) return true
    }
    return false
  }

  // ==================== DEACTIVATION ====================

  /**
   * Deactivate user — blocks auth while preserving data
   * @param {string} userId
   * @param {{ reason?: string }} opts
   */
  async deactivateUser (userId, opts = {}) {
    await this._base.append(encodeOp({
      type: OpTypes.DEACTIVATE_USER,
      userId,
      reason: opts.reason || null
    }))
    await this._base.update()
  }

  /**
   * Reactivate user — re-enable auth
   * @param {string} userId
   */
  async reactivateUser (userId) {
    await this._base.append(encodeOp({
      type: OpTypes.REACTIVATE_USER,
      userId
    }))
    await this._base.update()
  }

  /**
   * Check if user is deactivated
   * @param {string} userId
   * @returns {Promise<boolean>}
   */
  async isDeactivated (userId) {
    const user = await this.getUser(userId)
    return !!(user && user.deactivatedAt)
  }

  // ==================== ACCOUNT MERGE ====================

  /**
   * Merge two accounts
   */
  async mergeAccounts (primaryUserId, secondaryUserId) {
    await this._base.append(encodeOp({
      type: OpTypes.MERGE_ACCOUNTS,
      primaryUserId,
      secondaryUserId
    }))
    await this._base.update()
  }

  // ==================== REVOCATION ====================

  /**
   * Revoke an identity
   */
  async revokeIdentity (identityId, opts = {}) {
    await this._base.append(encodeOp({
      type: OpTypes.REVOKE_IDENTITY,
      identityId,
      reason: opts.reason || null,
      revokedAt: Date.now()
    }))
    await this._base.update()

    // Also update direct-write store
    if (this.db) {
      const identity = await this.getIdentity(identityId)
      if (identity) {
        identity.revoked = true
        identity.revokedAt = Date.now()
        await this.db.put(`identity:${identityId}`, identity)
      }
    }
  }

  /**
   * Restore a revoked identity
   */
  async restoreIdentity (identityId) {
    await this._base.append(encodeOp({
      type: OpTypes.RESTORE_IDENTITY,
      identityId
    }))
    await this._base.update()

    if (this.db) {
      const identity = await this.getIdentity(identityId)
      if (identity) {
        delete identity.revoked
        delete identity.revokedAt
        await this.db.put(`identity:${identityId}`, identity)
      }
    }
  }

  /**
   * List revoked identities
   */
  async listRevokedIdentities () {
    if (!this.db) return []
    const revoked = []
    for await (const entry of this.db.createReadStream({
      gte: 'identity:',
      lt: 'identity:\xff'
    })) {
      if (entry.value.revoked) revoked.push(entry.value)
    }
    return revoked
  }

  // ==================== AUTO-REGISTRATION ====================

  /**
   * Auto-register: atomically create user + identity for first-time auth.
   * First user gets admin, subsequent get read-only.
   *
   * @param {Object} identityData - { identityId, method, credentials, name? }
   * @returns {{ user: Object, identity: Object }}
   */
  async autoRegister (identityData) {
    // SECURITY: Always request read-only permissions. The apply function
    // deterministically promotes the FIRST CREATE_USER to admin based on
    // Autobase linearization order. This eliminates the TOCTOU race
    // condition where two writers both see "no admin" and both get admin.
    const user = await this.createUser({
      name: identityData.name || 'Anonymous',
      globalPermissions: ['read'],
      resources: [{ name: '*', permissions: ['read'] }]
    })

    // Register identity in direct-write store
    const identity = {
      identityId: identityData.identityId,
      method: identityData.method,
      publicKey: identityData.credentials ? identityData.credentials.publicKey : identityData.identityId,
      userId: user.userId,
      createdAt: Date.now()
    }
    await this.registerIdentity(identity)

    // Link identity to user (safe: autoRegister is called after auth provider verification)
    await this.linkIdentityToUser(identity.identityId, user.userId, { verified: true })

    return { user, identity }
  }

  /**
   * Check if any users exist
   */
  async _hasAnyUsers () {
    for await (const _ of this._view.createReadStream({ // eslint-disable-line no-unused-vars
      gte: `${this._prefix}user:`,
      lt: `${this._prefix}user:\xff`,
      limit: 1
    })) {
      return true
    }
    return false
  }

  // ==================== USERNAME MANAGEMENT ====================

  /**
   * Validate username format
   * @param {string} username
   * @returns {{ valid: boolean, reason?: string }}
   */
  validateUsername (username) {
    if (!username || typeof username !== 'string') {
      return { valid: false, reason: 'Username is required' }
    }

    const lower = username.toLowerCase()
    if (lower !== username) {
      return { valid: false, reason: 'Username must be lowercase' }
    }

    if (username.length < 3 || username.length > 32) {
      return { valid: false, reason: 'Username must be 3-32 characters' }
    }

    if (!USERNAME_REGEX.test(username)) {
      return { valid: false, reason: 'Username must be lowercase alphanumeric with dashes, no leading/trailing dash' }
    }

    if (username.includes('--')) {
      return { valid: false, reason: 'Username must not contain consecutive dashes' }
    }

    if (RESERVED_USERNAMES.includes(username)) {
      return { valid: false, reason: 'Username is reserved' }
    }

    return { valid: true }
  }

  /**
   * Set username for a user (unique, validated)
   * @param {string} userId
   * @param {string} username
   * @returns {Promise<Object>} Updated user from view
   */
  async setUsername (userId, username) {
    const validation = this.validateUsername(username)
    if (!validation.valid) {
      const err = new Error(validation.reason)
      err.code = 'VALIDATION_ERROR'
      err.status = 400
      throw err
    }

    // Check uniqueness in view
    const existing = await this._view.get(`${this._prefix}username:${username}`)
    if (existing && existing.value.userId !== userId) {
      const err = new Error('Username already taken')
      err.code = 'CONFLICT'
      err.status = 409
      throw err
    }

    // Emit Autobase op for multi-writer consensus
    await this._base.append(encodeOp({
      type: OpTypes.SET_USERNAME,
      userId,
      username
    }))
    await this._base.update()

    // Also update direct-write db if present
    if (this.db) {
      const user = await this.getUser(userId)
      if (user) {
        if (user.username) {
          await this.db.del(`username:${user.username}`)
        }
        await this.db.put(`username:${username}`, { userId })
      }
    }

    return this.getUser(userId)
  }

  /**
   * Check if username is available
   * @param {string} username
   * @returns {Promise<boolean>}
   */
  async isUsernameAvailable (username) {
    const validation = this.validateUsername(username)
    if (!validation.valid) return false

    const entry = await this._view.get(`${this._prefix}username:${username}`)
    return !entry
  }

  /**
   * List all identities from direct-write store (admin)
   * @returns {Promise<Object[]>}
   */
  async listAllIdentities () {
    if (!this.db) return []
    const identities = []
    for await (const entry of this.db.createReadStream({
      gte: 'identity:', lt: 'identity:\xff'
    })) {
      identities.push(entry.value)
    }
    return identities
  }

  // ==================== PROFILE UPDATES ====================

  /**
   * Update user profile (name, picture, theme)
   * @param {string} userId
   * @param {Object} updates - { name?, profilePicture?, theme? }
   * @returns {Promise<Object>} Updated user
   */
  async updateUserProfile (userId, updates) {
    const filtered = {}
    if (updates.name !== undefined) filtered.name = updates.name
    if (updates.profilePicture !== undefined) filtered.profilePicture = updates.profilePicture
    if (updates.theme !== undefined) filtered.theme = updates.theme

    await this.updateUser(userId, filtered)
    return this.getUser(userId)
  }

  // ==================== REVOCATION (ENHANCED) ====================

  /**
   * Check if identity is revoked (fast lookup via view index)
   * @param {string} identityId
   * @returns {Promise<boolean>}
   */
  async isRevoked (identityId) {
    // Check Autobase view revocation index
    const revoked = await this._view.get(`${this._prefix}revoked:${identityId}`)
    if (revoked) return true

    // Fallback: check direct-write identity record
    if (this.db) {
      const identity = await this.getIdentity(identityId)
      if (identity && identity.revoked) return true
    }
    return false
  }

  // ==================== USER QUERIES ====================

  /**
   * Get user with all linked identity objects resolved
   * @param {string} userId
   * @returns {Promise<{ user: Object, identities: Object[] } | null>}
   */
  async getUserWithIdentities (userId) {
    const user = await this.getUser(userId)
    if (!user) return null

    const identities = []
    for (const id of (user.identities || [])) {
      const identity = await this.getIdentity(id)
      if (identity) identities.push(identity)
    }

    return { user, identities }
  }

  /**
   * List users who have set a username (public directory)
   * @returns {Promise<Object[]>} Users with username, name, createdAt
   */
  async listPublicUsers () {
    const users = []
    for await (const entry of this._view.createReadStream({
      gte: `${this._prefix}user:`,
      lt: `${this._prefix}user:\xff`
    })) {
      const user = entry.value
      if (user.username && !user.mergedInto) {
        users.push({
          userId: user.userId,
          username: user.username,
          name: user.name,
          createdAt: user.createdAt
        })
      }
    }
    return users
  }

  /**
   * Delete user and cascade cleanup (identities, indexes)
   * @param {string} userId
   */
  async deleteUserCascade (userId) {
    const user = await this.getUser(userId)
    if (!user) return

    // Remove identity records and pubkey indexes from direct-write db
    if (this.db) {
      for (const identityId of (user.identities || [])) {
        const identity = await this.getIdentity(identityId)
        if (identity && identity.publicKey) {
          await this.db.del(`pubkey:${identity.publicKey}`)
        }
        await this.db.del(`identity:${identityId}`)
      }

      // Remove username index
      if (user.username) {
        await this.db.del(`username:${user.username}`)
      }
    }

    // Delete user via Autobase
    await this.deleteUser(userId)
  }

  /**
   * Import a keypair into an existing user account.
   * Registers the new identity and links it to the user.
   * Used for device linking — same person, new browser/device.
   *
   * @param {string} userId - Existing user to link to
   * @param {string} publicKey - New public key (44-char base64)
   * @param {string} method - Auth method (default: 'web-identity')
   * @returns {Promise<Object>} The new identity record
   */
  async importIdentity (userId, publicKey, method = 'web-identity') {
    // Check user exists
    const user = await this.getUser(userId)
    if (!user) throw new Error('User not found')

    // Check identity doesn't already exist
    const existing = await this.getIdentityByPublicKey(publicKey)
    if (existing) {
      if (existing.userId === userId) return existing // Already linked
      throw new Error('This key is already linked to a different user')
    }

    const identity = {
      identityId: publicKey,
      method,
      publicKey,
      userId,
      createdAt: Date.now()
    }
    await this.registerIdentity(identity)
    await this.linkIdentityToUser(publicKey, userId, { verified: true })

    return identity
  }

  // ==================== LINK TOKENS (F.7.2) ====================

  /**
   * Generate a short-lived link token bound to a user. The token itself
   * is returned to the caller; only the SHA-256 hash is stored in the
   * Autobase view, so reading the registry can't replay tokens.
   *
   * @param {string} userId       - The target user the token will link to
   * @param {object} [opts]
   * @param {number} [opts.ttlMs] - Token lifetime; default 5 min
   * @returns {Promise<{token, tokenHash, expiresAt}>}
   */
  async createLinkToken (userId, opts = {}) {
    if (!userId) throw new Error('createLinkToken requires { userId }')
    const ttl = typeof opts.ttlMs === 'number' ? opts.ttlMs : 5 * 60 * 1000
    const token = crypto.randomBytes(16).toString('hex')
    const tokenHash = crypto.hash(b4a.from(token)).toString('hex')
    const expiresAt = Date.now() + ttl

    await this._base.append(encodeOp({
      type: OpTypes.CREATE_LINK_TOKEN,
      tokenHash,
      userId,
      expiresAt,
      createdAt: Date.now()
    }))
    await this._base.update()

    return { token, tokenHash, expiresAt }
  }

  /**
   * Look up a link token row by its raw token (re-hashes for lookup).
   * Returns the stored row or null.
   */
  async getLinkToken (token) {
    if (!token) return null
    const tokenHash = crypto.hash(b4a.from(token)).toString('hex')
    const entry = await this._view.get(`${this._prefix}link-token:${tokenHash}`)
    return entry ? entry.value : null
  }

  /**
   * Consume a link token: validates expiry + single-use, attaches a new
   * identity to the token's target user, optionally deactivates a
   * throwaway user the new identity used to belong to. The whole sequence
   * runs in a single Autobase op (CONSUME_LINK_TOKEN) so apply-time
   * checks are atomic.
   *
   * @param {string} token            - Raw token (re-hashed internally)
   * @param {string} newIdentityId    - Identity to attach to target user
   * @param {object} [opts]
   * @param {string} [opts.deactivateUserId] - Throwaway user to deactivate
   * @returns {Promise<{userId, newIdentityId}>}
   */
  async consumeLinkToken (token, newIdentityId, opts = {}) {
    if (!token) throw new Error('consumeLinkToken requires { token }')
    if (!newIdentityId) throw new Error('consumeLinkToken requires { newIdentityId }')

    // Pre-flight: classify the failure reason for the caller. The apply
    // function silently no-ops on the same conditions (defence in depth).
    const stored = await this.getLinkToken(token)
    if (!stored) throw new Error('link token not found')
    if (stored.consumed) throw new Error('link token already consumed')
    if (stored.expiresAt && stored.expiresAt < Date.now()) throw new Error('link token expired')

    const tokenHash = crypto.hash(b4a.from(token)).toString('hex')

    await this._base.append(encodeOp({
      type: OpTypes.CONSUME_LINK_TOKEN,
      tokenHash,
      newIdentityId,
      deactivateUserId: opts.deactivateUserId || null
    }))
    await this._base.update()

    // Update direct-write identity record so reverse lookups stay coherent
    if (this.db) {
      try {
        const ident = await this.getIdentity(newIdentityId)
        if (ident) {
          ident.userId = stored.userId
          ident.updatedAt = Date.now()
          await this.db.put(`identity:${newIdentityId}`, ident)
        }
      } catch {}
    }

    return { userId: stored.userId, newIdentityId }
  }

  // ==================== FEDERATION & LIFECYCLE ====================

  /**
   * Add an Autobase writer via the op pipeline. Appends an ADD_WRITER
   * op that (a) records the writer in the view for audit, and (b) is
   * picked up by identity-apply.js to call `base.addWriter` on the
   * Autobase host. Going through the op pipeline (instead of calling
   * `_base.addWriter` directly) ensures the writer registration
   * linearizes across replicas and is visible in the shared view.
   *
   * @param {string|Buffer} key - writer's local core key (hex or buffer)
   * @param {object} [opts]
   * @param {string} [opts.role='writer'] - 'writer' | 'indexer'
   * @param {string} [opts.addedBy] - who added this writer (audit)
   */
  async addWriter (key, opts = {}) {
    const writerKey = typeof key === 'string'
      ? key
      : b4a.toString(key, 'hex')
    if (!writerKey || writerKey.length !== 64) {
      throw new Error('addWriter requires a 32-byte (64-char hex) writer key')
    }
    const op = {
      type: OpTypes.ADD_WRITER,
      writerKey,
      role: opts.role || 'writer',
      addedBy: opts.addedBy || null
    }
    await this._base.append(encodeOp(op))
  }

  /**
   * Announce the local-writer keys a linked device has on OTHER
   * autobases in the enclosing app. Recorded in the view under
   * `mirror-writers:<devicePubkey>`. The enclosing app is responsible
   * for watching that key range and issuing canonical in-band
   * addWriter ops on the matching autobases.
   *
   * @param {string} devicePubkey  — this device's hyperident pubkey (hex)
   * @param {Object<string,string>} keys  — { namespace: localKeyHex, ... }
   */
  /**
   * True if this peer is a writer on the identity autobase (can append).
   * Mirrors this check straight off the underlying autobase.
   */
  get writable () {
    return this._base ? this._base.writable : false
  }

  /**
   * Subscribe to writable transitions. Callback fires when this peer
   * becomes (or was already) a writer on the identity autobase — useful
   * for apps that want to act the moment they can append.
   * Returns an unsubscribe function.
   */
  onWritable (cb) {
    if (!this._base) return () => {}
    if (this._base.writable) { queueMicrotask(() => { try { cb() } catch {} }) }
    const handler = () => { try { cb() } catch {} }
    this._base.on('writable', handler)
    return () => this._base.off('writable', handler)
  }

  async registerMirrorWriters (devicePubkey, keys) {
    if (!devicePubkey || typeof devicePubkey !== 'string' || devicePubkey.length !== 64) {
      throw new Error('registerMirrorWriters requires 64-char hex devicePubkey')
    }
    if (!keys || typeof keys !== 'object') {
      throw new Error('registerMirrorWriters requires a namespace→hex map')
    }
    const op = {
      type: OpTypes.REGISTER_MIRROR_WRITERS,
      devicePubkey,
      keys
    }
    await this._base.append(encodeOp(op))
  }

  /**
   * Read the most recent mirror-writer announcement for a device.
   */
  async getMirrorWriters (devicePubkey) {
    const entry = await this._view.get(`${this._prefix}mirror-writers:${devicePubkey}`)
    return entry ? entry.value : null
  }

  /**
   * Iterate all mirror-writer announcements (for watcher wiring).
   */
  async * listMirrorWriters () {
    for await (const entry of this._view.createReadStream({
      gte: `${this._prefix}mirror-writers:`,
      lt: `${this._prefix}mirror-writers:\xff`
    })) {
      const devicePubkey = entry.key.slice((this._prefix + 'mirror-writers:').length)
      yield { devicePubkey, ...entry.value }
    }
  }

  /**
   * Remove an Autobase writer via the op pipeline. Symmetric with addWriter.
   */
  async removeWriter (key) {
    const writerKey = typeof key === 'string'
      ? key
      : b4a.toString(key, 'hex')
    const op = {
      type: OpTypes.REMOVE_WRITER,
      writerKey
    }
    await this._base.append(encodeOp(op))
  }

  /**
   * Close registry
   */
  async close () {
    if (this._base) await this._base.close()
  }
}

export default BaseIdentityRegistry
function isBuffer(value) {
  return Buffer.isBuffer(value) || value instanceof Uint8Array
}

function isEncoding(encoding) {
  return Buffer.isEncoding(encoding)
}

function alloc(size, fill, encoding) {
  return Buffer.alloc(size, fill, encoding)
}

function allocUnsafe(size) {
  return Buffer.allocUnsafe(size)
}

function allocUnsafeSlow(size) {
  return Buffer.allocUnsafeSlow(size)
}

function byteLength(string, encoding) {
  return Buffer.byteLength(string, encoding)
}

function compare(a, b) {
  return Buffer.compare(a, b)
}

function concat(buffers, totalLength) {
  return Buffer.concat(buffers, totalLength)
}

function copy(source, target, targetStart, start, end) {
  return toBuffer(source).copy(target, targetStart, start, end)
}

function equals(a, b) {
  return toBuffer(a).equals(b)
}

function fill(buffer, value, offset, end, encoding) {
  return toBuffer(buffer).fill(value, offset, end, encoding)
}

function from(value, encodingOrOffset, length) {
  return Buffer.from(value, encodingOrOffset, length)
}

function includes(buffer, value, byteOffset, encoding) {
  return toBuffer(buffer).includes(value, byteOffset, encoding)
}

function indexOf(buffer, value, byfeOffset, encoding) {
  return toBuffer(buffer).indexOf(value, byfeOffset, encoding)
}

function lastIndexOf(buffer, value, byteOffset, encoding) {
  return toBuffer(buffer).lastIndexOf(value, byteOffset, encoding)
}

function swap16(buffer) {
  return toBuffer(buffer).swap16()
}

function swap32(buffer) {
  return toBuffer(buffer).swap32()
}

function swap64(buffer) {
  return toBuffer(buffer).swap64()
}

function toBuffer(buffer) {
  if (Buffer.isBuffer(buffer)) return buffer
  return Buffer.from(buffer.buffer, buffer.byteOffset, buffer.byteLength)
}

function toString(buffer, encoding, start, end) {
  return toBuffer(buffer).toString(encoding, start, end)
}

function write(buffer, string, offset, length, encoding) {
  return toBuffer(buffer).write(string, offset, length, encoding)
}

function readDoubleBE(buffer, offset) {
  return toBuffer(buffer).readDoubleBE(offset)
}

function readDoubleLE(buffer, offset) {
  return toBuffer(buffer).readDoubleLE(offset)
}

function readFloatBE(buffer, offset) {
  return toBuffer(buffer).readFloatBE(offset)
}

function readFloatLE(buffer, offset) {
  return toBuffer(buffer).readFloatLE(offset)
}

function readInt32BE(buffer, offset) {
  return toBuffer(buffer).readInt32BE(offset)
}

function readInt32LE(buffer, offset) {
  return toBuffer(buffer).readInt32LE(offset)
}

function readUInt32BE(buffer, offset) {
  return toBuffer(buffer).readUInt32BE(offset)
}

function readUInt32LE(buffer, offset) {
  return toBuffer(buffer).readUInt32LE(offset)
}

function writeDoubleBE(buffer, value, offset) {
  return toBuffer(buffer).writeDoubleBE(value, offset)
}

function writeDoubleLE(buffer, value, offset) {
  return toBuffer(buffer).writeDoubleLE(value, offset)
}

function writeFloatBE(buffer, value, offset) {
  return toBuffer(buffer).writeFloatBE(value, offset)
}

function writeFloatLE(buffer, value, offset) {
  return toBuffer(buffer).writeFloatLE(value, offset)
}

function writeInt32BE(buffer, value, offset) {
  return toBuffer(buffer).writeInt32BE(value, offset)
}

function writeInt32LE(buffer, value, offset) {
  return toBuffer(buffer).writeInt32LE(value, offset)
}

function writeUInt32BE(buffer, value, offset) {
  return toBuffer(buffer).writeUInt32BE(value, offset)
}

function writeUInt32LE(buffer, value, offset) {
  return toBuffer(buffer).writeUInt32LE(value, offset)
}

module.exports = {
  isBuffer,
  isEncoding,
  alloc,
  allocUnsafe,
  allocUnsafeSlow,
  byteLength,
  compare,
  concat,
  copy,
  equals,
  fill,
  from,
  includes,
  indexOf,
  lastIndexOf,
  swap16,
  swap32,
  swap64,
  toBuffer,
  toString,
  write,
  readDoubleBE,
  readDoubleLE,
  readFloatBE,
  readFloatLE,
  readInt32BE,
  readInt32LE,
  readUInt32BE,
  readUInt32LE,
  writeDoubleBE,
  writeDoubleLE,
  writeFloatBE,
  writeFloatLE,
  writeInt32BE,
  writeInt32LE,
  writeUInt32BE,
  writeUInt32LE
}
{
  "name": "b4a",
  "version": "1.8.1",
  "description": "Bridging the gap between buffers and typed arrays",
  "exports": {
    "./package": "./package.json",
    ".": {
      "react-native": "./react-native.js",
      "browser": "./browser.js",
      "default": "./index.js"
    }
  },
  "files": [
    "browser.js",
    "index.js",
    "react-native.js",
    "lib"
  ],
  "scripts": {
    "format": "prettier --write . && lunte --fix",
    "lint": "prettier --check . && lunte",
    "test": "npm run test:bare && npm run test:node",
    "test:bare": "brittle-bare --coverage test.mjs",
    "test:node": "brittle-node --coverage test.mjs"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/b4a.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/b4a/issues"
  },
  "homepage": "https://github.com/holepunchto/b4a#readme",
  "devDependencies": {
    "brittle": "^3.5.2",
    "lunte": "^1.7.0",
    "nanobench": "^3.0.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^2.0.0"
  },
  "peerDependencies": {
    "react-native-b4a": "*"
  },
  "peerDependenciesMeta": {
    "react-native-b4a": {
      "optional": true
    }
  }
}
const ESC = '\x1b'
const CSI = ESC + '['
const SGR = (n) => CSI + n + 'm'

exports.constants = {
  ESC,
  CSI,
  SGR
}

// https://en.wikipedia.org/wiki/ANSI_escape_code#CSI_(Control_Sequence_Introducer)_sequences

exports.cursorHide = CSI + '?25l'
exports.cursorShow = CSI + '?25h'

exports.cursorUp = function cursorUp(n = 1) {
  return CSI + n + 'A'
}

exports.cursorDown = function cursorDown(n = 1) {
  return CSI + n + 'B'
}

exports.cursorForward = function cursorForward(n = 1) {
  return CSI + n + 'C'
}

exports.cursorBack = function cursorBack(n = 1) {
  return CSI + n + 'D'
}

exports.cursorNextLine = function cursorNextLine(n = 1) {
  return CSI + n + 'E'
}

exports.cursorPreviousLine = function cursorPreviousLine(n = 1) {
  return CSI + n + 'F'
}

exports.cursorPosition = function cursorPosition(column, row = 0) {
  if (row === 0) return CSI + (column + 1) + 'G'

  return CSI + (row + 1) + ';' + (column + 1) + 'H'
}

exports.eraseDisplayEnd = CSI + 'J'
exports.eraseDisplayStart = CSI + '1J'
exports.eraseDisplay = CSI + '2J'
exports.eraseLineEnd = CSI + 'K'
exports.eraseLineStart = CSI + '1K'
exports.eraseLine = CSI + '2K'

exports.scrollUp = function scrollUp(n = 1) {
  return CSI + n + 'S'
}

exports.scrollDown = function scrollDown(n = 1) {
  return CSI + n + 'T'
}

// https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_(Select_Graphic_Rendition)_parameters

exports.modifierReset = SGR(0)
exports.modifierBold = SGR(1)
exports.modifierDim = SGR(2)
exports.modifierItalic = SGR(3)
exports.modifierUnderline = SGR(4)
exports.modifierNormal = SGR(22)
exports.modifierNotItalic = SGR(23)
exports.modifierNotUnderline = SGR(24)

// https://en.wikipedia.org/wiki/ANSI_escape_code#Colors

exports.colorBlack = SGR(30)
exports.colorRed = SGR(31)
exports.colorGreen = SGR(32)
exports.colorYellow = SGR(33)
exports.colorBlue = SGR(34)
exports.colorMagenta = SGR(35)
exports.colorCyan = SGR(36)
exports.colorWhite = SGR(37)
exports.colorDefault = SGR(39)
exports.colorBrightBlack = SGR(90)
exports.colorBrightRed = SGR(91)
exports.colorBrightGreen = SGR(92)
exports.colorBrightYellow = SGR(93)
exports.colorBrightBlue = SGR(94)
exports.colorBrightMagenta = SGR(95)
exports.colorBrightCyan = SGR(96)
exports.colorBrightWhite = SGR(97)
{
  "name": "bare-ansi-escapes",
  "version": "2.2.3",
  "description": "Parse and produce ANSI escape sequences",
  "exports": {
    "./package": "./package.json",
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    },
    "./key-decoder": {
      "types": "./key-decoder.d.ts",
      "default": "./key-decoder.js"
    }
  },
  "files": [
    "index.js",
    "index.d.ts",
    "key-decoder.js",
    "key-decoder.d.ts"
  ],
  "scripts": {
    "test": "prettier . --check && bare test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-ansi-escapes.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-ansi-escapes/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-ansi-escapes#readme",
  "dependencies": {
    "bare-stream": "^2.6.5"
  },
  "devDependencies": {
    "bare-buffer": "^3.0.2",
    "brittle": "^3.3.2",
    "prettier": "^3.4.2",
    "prettier-config-standard": "^7.0.0"
  },
  "peerDependencies": {
    "bare-buffer": "*"
  },
  "peerDependenciesMeta": {
    "bare-buffer": {
      "optional": true
    }
  }
}
const inspect = require('bare-inspect')

class AssertionError extends Error {
  constructor(opts = {}) {
    let { message = null, actual, expected, operator } = opts

    if (message === null) {
      message = `${inspect(actual)} ${operator} ${inspect(expected)}`
    }

    super(message)

    this.actual = actual
    this.expected = expected
    this.operator = operator
  }

  get name() {
    return 'AssertionError'
  }

  get code() {
    'ASSERTION'
  }
}

function assertFail(opts, fn) {
  if (opts.message instanceof Error) throw opts.message

  const err = new AssertionError(opts)

  if (Error.captureStackTrace) Error.captureStackTrace(err, fn)

  throw err
}

module.exports = exports = function assert(actual, message) {
  if (actual) return

  assertFail({ message, actual, expected: true, operator: '==' }, assert)
}

exports.AssertionError = AssertionError

exports.fail = function fail(message) {
  if (message === undefined) message = 'Failed'

  assertFail({ message, operator: 'fail' }, fail)
}

exports.ok = function ok(actual, message) {
  if (actual) return

  assertFail({ message, actual, expected: true, operator: '==' }, ok)
}

exports.notOk = function ok(actual, message) {
  if (!actual) return

  assertFail({ message, actual, expected: false, operator: '==' }, ok)
}

exports.equal = function equal(actual, expected, message) {
  if (actual == expected || (actual !== actual && expected !== expected)) {
    return
  }

  assertFail({ message, actual, expected, operator: '==' }, equal)
}

exports.notEqual = function notEqual(actual, expected, message) {
  if (actual != expected && (actual === actual || expected === expected)) {
    return
  }

  assertFail({ message, actual, expected, operator: '!=' }, notEqual)
}

exports.strictEqual = function strictEqual(actual, expected, message) {
  if (Object.is(actual, expected)) return

  assertFail(
    { message, actual, expected, operator: 'strictEqual' },
    strictEqual
  )
}

exports.notStrictEqual = function notStrictEqual(actual, expected, message) {
  if (!Object.is(actual, expected)) return

  assertFail(
    { message, actual, expected, operator: 'notStrictEqual' },
    notStrictEqual
  )
}
{
  "name": "bare-assert",
  "version": "1.2.0",
  "description": "Assertion library for JavaScript",
  "exports": {
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    },
    "./package": "./package.json"
  },
  "files": [
    "index.js",
    "index.d.ts"
  ],
  "scripts": {
    "test": "prettier . --check && bare test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-assert.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-assert/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-assert#readme",
  "dependencies": {
    "bare-inspect": "^3.1.2"
  },
  "devDependencies": {
    "brittle": "^3.1.1",
    "prettier": "^3.4.2",
    "prettier-config-standard": "^7.0.0"
  }
}
module.exports = require.addon()
const ansiEscapes = require('bare-ansi-escapes')
const getType = require('bare-type')
const binding = require('./binding')

const defaultDepth = 2
const defaultBreakLength = 80
const defaultMaxArrayLength = 40

module.exports = exports = function inspect(value, opts = {}) {
  const {
    colors = false,
    depth = defaultDepth,
    breakLength = defaultBreakLength,
    stylize = defaultStylize(colors)
  } = opts

  const references = new InspectRefMap()

  const tree = inspectValue(value, 0, {
    colors,
    depth,
    breakLength,
    stylize,
    references
  })

  return tree.toString()
}

exports.styles = {
  bigint: ansiEscapes.colorYellow,
  boolean: ansiEscapes.colorYellow,
  date: ansiEscapes.colorMagenta,
  module: ansiEscapes.modifierUnderline,
  name: ansiEscapes.modifierReset,
  null: ansiEscapes.modifierBold,
  number: ansiEscapes.colorYellow,
  regexp: ansiEscapes.colorRed,
  special: ansiEscapes.colorCyan,
  string: ansiEscapes.colorGreen,
  symbol: ansiEscapes.colorGreen,
  undefined: ansiEscapes.colorBrightBlack
}

const styles = exports.styles

function defaultStylize(colors) {
  return function stylize(value, style) {
    const color = colors && styles[style]

    if (color) return color + value + ansiEscapes.modifierReset

    return value
  }
}

class InspectRefMap {
  constructor() {
    this.refs = new WeakMap()
    this.ids = new WeakMap()
    this.nextId = 1
  }

  has(object) {
    return this.refs.has(object)
  }

  get(object) {
    return this.refs.get(object) || null
  }

  set(object, ref) {
    this.refs.set(object, ref)
  }

  id(object) {
    let id = this.ids.get(object)
    if (id) return id

    id = this.nextId++
    this.ids.set(object, id)

    return id
  }
}

class InspectNode {
  constructor(depth, length, opts) {
    const { breakLength = defaultBreakLength, breakAlways = false } = opts

    this.depth = depth
    this.length = length
    this.breakLength = breakLength
    this.breakAlways = breakAlways
  }

  pad(n, string) {
    return string.padStart(n, ' ')
  }

  indent(n, string) {
    return '  '.repeat(n) + string
  }
}

class InspectRef extends InspectNode {
  constructor(depth, opts) {
    super(depth, '[circular *]'.length, opts)

    this.refs = opts.references
    this.count = 0
    this.circular = false
    this.color = opts.colors && styles.special
  }

  get id() {
    return this.refs.id(this)
  }

  increment() {
    return ++this.count
  }

  decrement() {
    return --this.count
  }

  toString(opts = {}) {
    const { offset = 0, pad = 0, indent = 0 } = opts

    let value = this.pad(pad, '[circular *' + this.id + ']')

    if (this.color) value = this.color + value + ansiEscapes.modifierReset

    return offset ? value : this.indent(indent, value)
  }
}

class InspectLeaf extends InspectNode {
  constructor(value, color, depth, opts) {
    const length = value.length

    if (value.includes('\n')) {
      value = value.replaceAll('\n', '\n' + '  '.repeat(depth))

      opts = { ...opts, breakAlways: true }
    }

    super(depth, length, opts)

    this.value = value
    this.color = opts.colors && color
  }

  toString(opts = {}) {
    const { offset = 0, pad = 0, indent = 0 } = opts

    let value = this.pad(pad, this.value)

    if (this.color) value = this.color + value + ansiEscapes.modifierReset

    return offset ? value : this.indent(indent, value)
  }
}

class InspectPair extends InspectNode {
  constructor(delim, left, right, depth, opts) {
    const length = left.length + delim.length + right.length

    if (left.breakAlways || right.breakAlways) {
      opts = { ...opts, breakAlways: true }
    }

    super(depth, length, opts)

    this.delim = delim
    this.left = left
    this.right = right
  }

  toString(opts = {}) {
    const { indent = 0 } = opts

    return this.indent(
      indent,
      this.left +
        this.delim +
        this.right.toString({
          indent,
          offset: this.left.length + this.delim.length
        })
    )
  }
}

class InspectSuspension extends InspectNode {
  constructor(overflow, depth, opts) {
    const label = `... ${overflow} more`

    super(depth, label.length, opts)

    this.label = label
  }

  toString(opts = {}) {
    const { indent = 0 } = opts

    return this.indent(indent, this.label)
  }
}

class InspectSequence extends InspectNode {
  constructor(header, footer, delim, values, ref, depth, opts) {
    const { tabulate = false } = opts

    const length =
      (ref.circular ? '<ref *>'.length + 1 : 0) +
      header.length +
      values.reduce((length, value, i) => length + value.length + (i === 0 ? 0 : delim.length), 0) +
      footer.length

    if (values.some((value) => value.breakAlways)) {
      opts = { ...opts, breakAlways: true }
    }

    super(depth, length, opts)

    this.header = header
    this.footer = footer
    this.delim = delim
    this.values = values
    this.ref = ref
    this.tabulate = tabulate
  }

  toString(opts = {}) {
    const { offset = 0, indent = 0 } = opts

    const split =
      this.breakAlways ||
      (this.values.length &&
        (offset + this.length > this.breakLength || indent * 2 + this.length > this.breakLength))

    let header = this.header

    if (this.ref.circular) {
      header = '<ref *' + this.ref.id + '> ' + header
    }

    if (this.values.length === 0) {
      header = header.trimEnd()
    }

    if (offset === 0) {
      header = this.indent(indent, header)
    }

    if (split) {
      header = header.trimEnd() + '\n'
    }

    let string = header

    let columns = 1
    let pad = 0

    if (this.tabulate) {
      const widest = this.values.reduce(
        (length, value) => (value.breakAlways ? length : Math.max(length, value.length)),
        0
      )

      if (widest) {
        columns = Math.max(
          columns,
          Math.floor((this.breakLength - indent * 2) / (widest + this.delim.length))
        )

        if (columns > 1) pad = widest
      }
    }

    for (let i = 0, n = this.values.length, offset = 0; i < n; i++) {
      const value = this.values[i]

      if (split) {
        let part

        if (i % columns === 0 || value.breakAlways) {
          part = value.toString({ indent: indent + 1, pad })
        } else {
          part = value.toString({ pad })
        }

        string += part

        if (i < n - 1) {
          if (i % columns === columns - 1 || this.values[i + 1].breakAlways) {
            string += this.delim.trimEnd() + '\n'
          } else {
            string += this.delim
          }
        }
      } else {
        if (i > 0) string += this.delim

        string += value.toString({ offset })

        // TODO Lunte thinks this refers to `const { offset }...` further up
        offset += value.length // eslint-disable-line
      }
    }

    let footer = this.footer

    if (this.values.length === 0) {
      footer = footer.trimStart()
    }

    if (split) {
      string += '\n' + this.indent(indent, footer.trimStart())
    } else {
      string += footer
    }

    return string
  }
}

function inspectValue(value, depth, opts) {
  const type = getType(value)

  if (type.isUndefined()) return inspectUndefined(depth, opts)
  if (type.isNull()) return inspectNull(depth, opts)
  if (type.isBoolean()) return inspectBoolean(value, depth, opts)
  if (type.isNumber()) return inspectNumber(value, depth, opts)
  if (type.isBigInt()) return inspectBigInt(value, depth, opts)
  if (type.isString()) return inspectString(value, depth, opts)
  if (type.isSymbol()) return inspectSymbol(value, depth, opts)
  if (type.isObject()) return inspectObject(type, value, depth, opts)
  if (type.isFunction()) return inspectFunction(type, value, depth, opts)
  if (type.isExternal()) return inspectExternal(value, depth, opts)
}

function inspectUndefined(depth, opts) {
  return new InspectLeaf('undefined', styles.undefined, depth, opts)
}

function inspectNull(depth, opts) {
  return new InspectLeaf('null', styles.null, depth, opts)
}

function inspectBoolean(value, depth, opts) {
  return new InspectLeaf(value.toString(), styles.boolean, depth, opts)
}

function inspectNumber(value, depth, opts) {
  let string

  if (Object.is(value, -0)) {
    string = '-0'
  } else {
    string = value.toString(10)
  }

  return new InspectLeaf(string, styles.number, depth, opts)
}

function inspectBigInt(value, depth, opts) {
  return new InspectLeaf(value.toString(10) + 'n', styles.bigint, depth, opts)
}

const STRING_ESCAPES = /[\ud800-\udbff][\udc00-\udfff]|[\u0000-\u001f'\\\ud800-\udfff]/g

function inspectString(value, depth, opts) {
  // https://tc39.es/ecma262/multipage/structured-data.html#sec-quotejsonstring
  const string = value.replace(STRING_ESCAPES, (match) => {
    if (match.length === 2) return match

    switch (match) {
      case "'":
        return "\\'"
      case '\\':
        return '\\\\'
      case '\b':
        return '\\b'
      case '\t':
        return '\\t'
      case '\n':
        return '\\n'
      case '\f':
        return '\\f'
      case '\r':
        return '\\r'
      default:
        return '\\u' + match.charCodeAt(0).toString(16).padStart(4, '0')
    }
  })

  return new InspectLeaf("'" + string + "'", styles.string, depth, opts)
}

function inspectSymbol(value, depth, opts) {
  return new InspectLeaf(value.toString(), styles.symbol, depth, opts)
}

const PLAIN_KEY = /^[a-zA-Z_][a-zA-Z_0-9]*$/

function inspectKey(value, depth, opts) {
  if (typeof value === 'symbol') {
    return inspectValue(value, depth, opts)
  } else if (PLAIN_KEY.test(value)) {
    return new InspectLeaf(value, null, depth, opts)
  } else {
    return inspectValue(value, depth, opts)
  }
}

function inspectObject(type, object, depth, opts) {
  const refs = opts.references

  let ref = refs.get(object)
  if (ref === null) {
    ref = new InspectRef(depth, opts)
    refs.set(object, ref)
  } else if (ref.count) {
    ref.circular = true
    return ref
  }

  const maxDepth = typeof opts.depth === 'number' ? opts.depth : Infinity

  if (maxDepth < depth) {
    const name = nameOf(object)

    return new InspectLeaf('[' + (name || 'Object') + ']', styles.special, depth, opts)
  }

  const { value: inspect } = attempt(
    () => object[Symbol.for('bare.inspect')] || object[Symbol.for('nodejs.util.inspect.custom')]
  )

  if (typeof inspect === 'function' && isPrototype(object) === false) {
    const { value, threw, error } = attempt(() =>
      inspect.call(
        object,
        typeof opts.depth === 'number' ? opts.depth - depth : null,
        {
          colors: opts.colors,
          breakLength: opts.breakLength,
          stylize: opts.stylize
        },
        exports
      )
    )

    if (threw) {
      return new InspectLeaf(
        '<inspect threw ' + errorLabel(error) + '>',
        styles.special,
        depth,
        opts
      )
    }

    if (typeof value === 'object' && value !== null) {
      refs.set(value, ref)
    }

    if (typeof value !== 'string') {
      return inspectValue(value, depth, opts)
    }

    return new InspectLeaf(value, null, depth, opts)
  }

  if (type.isArray()) return inspectArray(object, ref, depth, opts)
  if (type.isDate()) return inspectDate(object, ref, depth, opts)
  if (type.isRegExp()) return inspectRegExp(object, ref, depth, opts)
  if (type.isError()) return inspectError(object, ref, depth, opts)
  if (type.isPromise()) return inspectPromise(object, ref, depth, opts)
  if (type.isMap()) return inspectMap(object, ref, depth, opts)
  if (type.isSet()) return inspectSet(object, ref, depth, opts)
  if (type.isWeakMap()) return inspectWeakMap(object, ref, depth, opts)
  if (type.isWeakSet()) return inspectWeakSet(object, ref, depth, opts)
  if (type.isWeakRef()) return inspectWeakRef(object, ref, depth, opts)
  if (type.isArrayBuffer()) return inspectArrayBuffer(object, ref, depth, opts)
  if (type.isSharedArrayBuffer()) return inspectSharedArrayBuffer(object, ref, depth, opts)
  if (type.isTypedArray()) return inspectTypedArray(object, ref, depth, opts)
  if (type.isDataView()) return inspectDataView(object, ref, depth, opts)

  ref.increment()

  const values = []

  for (const key of keysOf(object)) {
    if (key === 'constructor') continue

    values.push(
      new InspectPair(
        ': ',
        inspectKey(key, depth + 1, opts),
        inspectProperty(object, key, depth + 1, opts),
        depth + 1,
        opts
      )
    )
  }

  for (const key of symbolsOf(object)) {
    values.push(
      new InspectPair(
        ': ',
        inspectKey(key, depth + 1, opts),
        inspectProperty(object, key, depth + 1, opts),
        depth + 1,
        opts
      )
    )
  }

  ref.decrement()

  let header = '{ '

  const { value: tag } = attempt(() => object[Symbol.toStringTag])

  if (typeof tag === 'string' && tag) header = '[' + tag + '] ' + header

  const name = nameOf(object)

  if (name && name !== 'Object') header = name + ' ' + header

  return new InspectSequence(header, ' }', ', ', values, ref, depth, opts)
}

function inspectArray(array, ref, depth, opts) {
  const { maxArrayLength = defaultMaxArrayLength } = opts

  ref.increment()

  const values = []

  let remaining = Math.max(maxArrayLength, 0)

  for (let i = 0, n = array.length; i < n; i++) {
    if (remaining-- === 0) {
      values.push(
        new InspectSuspension(array.length - values.length, depth + 1, {
          ...opts,
          breakAlways: true
        })
      )

      break
    }

    values.push(inspectProperty(array, i, depth + 1, opts))
  }

  for (const key of nonIndexKeysOf(array)) {
    values.push(
      new InspectPair(
        ': ',
        inspectKey(key, depth + 1, opts),
        inspectProperty(array, key, depth + 1, opts),
        depth + 1,
        { ...opts, breakAlways: remaining < 0 }
      )
    )
  }

  ref.decrement()

  let header = '[ '

  const name = nameOf(array)

  if (name && name !== 'Array') header = name + '(' + array.length + ') ' + header

  return new InspectSequence(header, ' ]', ', ', values, ref, depth, {
    ...opts,
    tabulate: true
  })
}

function inspectProperty(object, key, depth, opts) {
  const descriptor = descriptorOf(object, key)

  if (descriptor === null) {
    return new InspectLeaf('<unreadable>', styles.special, depth, opts)
  }

  if (descriptor === undefined) return inspectUndefined(depth, opts)

  if ('value' in descriptor === false) {
    let label = '[getter/setter]'

    if (descriptor.get === undefined) label = '[setter]'
    else if (descriptor.set === undefined) label = '[getter]'

    return new InspectLeaf(label, styles.special, depth, opts)
  }

  return inspectValue(descriptor.value, depth, opts)
}

function inspectDate(date, ref, depth, opts) {
  const { value: time } = attempt(() => date.getTime())

  if (Number.isNaN(time)) {
    return new InspectLeaf('Invalid Date', styles.date, depth, opts)
  }

  const { value: string } = attempt(() => date.toISOString())

  return new InspectLeaf(string || '[Date]', styles.date, depth, opts)
}

function inspectRegExp(regExp, ref, depth, opts) {
  const { value: string } = attempt(() => regExp.toString())

  return new InspectLeaf(string || '[RegExp]', styles.regexp, depth, opts)
}

function inspectError(error, ref, depth, opts) {
  let header = attempt(() => error.stack).value

  if (typeof header === 'string') {
    // Not all engines put the `<name>: <message>` line at the top of `stack`,
    // so add it when it's missing.
    const title = attempt(() => error.toString()).value

    if (typeof title === 'string' && !header.startsWith(title)) {
      header = title + '\n' + header
    }

    if (depth > 0) {
      header = header.replaceAll('\n', '\n' + '  '.repeat(depth))
    }
  } else {
    header = attempt(() => error.toString()).value

    if (typeof header !== 'string') header = errorLabel(error)
  }

  const builtins = ['cause']

  const { value: name } = attempt(() => error.name)

  if (name === 'AggregateError') {
    builtins.push('errors')
  } else if (name === 'SuppressedError') {
    builtins.push('error', 'suppressed')
  }

  const values = []

  for (const key of builtins) {
    if (descriptorOf(error, key) === undefined) continue

    values.push(
      new InspectPair(
        ': ',
        new InspectLeaf('[' + key + ']', null, depth + 1, opts),
        inspectProperty(error, key, depth + 1, opts),
        depth + 1,
        opts
      )
    )
  }

  for (const key of keysOf(error)) {
    if (key === 'constructor' || builtins.includes(key)) continue

    values.push(
      new InspectPair(
        ': ',
        inspectKey(key, depth + 1, opts),
        inspectProperty(error, key, depth + 1, opts),
        depth + 1,
        opts
      )
    )
  }

  if (values.length === 0) return new InspectLeaf(header, null, depth, opts)

  return new InspectSequence(header + ' {', ' }', ', ', values, ref, depth, opts)
}

function inspectPromise(promise, ref, depth, opts) {
  ref.increment()

  const state = binding.getPromiseState(promise)

  const values = []

  switch (state) {
    case 0: // Pending
      values.push(new InspectLeaf('<pending>', styles.special, depth, opts))
      break

    case 1: // Fulfilled
      values.push(inspectValue(binding.getPromiseResult(promise), depth, opts))
      break

    case 2: // Rejected
      values.push(
        new InspectLeaf('<rejected>', styles.special, depth, opts),
        inspectValue(binding.getPromiseResult(promise), depth, opts)
      )
  }

  ref.decrement()

  const header = (nameOf(promise) || 'Promise') + ' { '

  return new InspectSequence(header, ' }', ' ', values, ref, depth, opts)
}

function inspectMap(map, ref, depth, opts) {
  const { maxArrayLength = defaultMaxArrayLength, maxMapLength = maxArrayLength } = opts

  ref.increment()

  const values = []

  let remaining = maxMapLength

  const { value: size } = attempt(() => map.size)

  attempt(() => {
    for (const entry of map) {
      if (remaining-- === 0) {
        values.push(
          new InspectSuspension(size - values.length, depth + 1, {
            ...opts,
            breakAlways: true
          })
        )
        break
      }

      values.push(
        new InspectPair(
          ' => ',
          inspectValue(entry[0], depth + 1, opts),
          inspectValue(entry[1], depth + 1, opts),
          depth + 1,
          opts
        )
      )
    }
  })

  for (const key of keysOf(map)) {
    if (key === 'constructor') continue

    const value = inspectProperty(map, key, depth + 1, opts)

    values.push(
      new InspectPair(': ', inspectKey(key, depth + 1, opts), value, depth + 1, {
        ...opts,
        breakAlways: remaining < 0
      })
    )
  }

  ref.decrement()

  const header = (nameOf(map) || 'Map') + '(' + size + ') { '

  return new InspectSequence(header, ' }', ', ', values, ref, depth, {
    ...opts,
    tabulate: true
  })
}

function inspectSet(set, ref, depth, opts) {
  const { maxArrayLength = defaultMaxArrayLength, maxSetLength = maxArrayLength } = opts

  ref.increment()

  const values = []

  let remaining = maxSetLength

  const { value: size } = attempt(() => set.size)

  attempt(() => {
    for (const entry of set) {
      if (remaining-- === 0) {
        values.push(
          new InspectSuspension(size - values.length, depth + 1, {
            ...opts,
            breakAlways: true
          })
        )
        break
      }

      values.push(inspectValue(entry, depth + 1, opts))
    }
  })

  for (const key of keysOf(set)) {
    if (key === 'constructor') continue

    const value = inspectProperty(set, key, depth + 1, opts)

    values.push(
      new InspectPair(': ', inspectKey(key, depth + 1, opts), value, depth + 1, {
        ...opts,
        breakAlways: remaining < 0
      })
    )
  }

  ref.decrement()

  const header = (nameOf(set) || 'Set') + '(' + size + ') { '

  return new InspectSequence(header, ' }', ', ', values, ref, depth, {
    ...opts,
    tabulate: true
  })
}

function inspectWeakMap(weakMap, ref, depth, opts) {
  const header = (nameOf(weakMap) || 'WeakMap') + ' { '

  return new InspectSequence(
    header,
    ' }',
    ' ',
    [new InspectLeaf('<items unknown>', styles.special, depth + 1, opts)],
    ref,
    depth,
    opts
  )
}

function inspectWeakSet(weakSet, ref, depth, opts) {
  const header = (nameOf(weakSet) || 'WeakSet') + ' { '

  return new InspectSequence(
    header,
    ' }',
    ' ',
    [new InspectLeaf('<items unknown>', styles.special, depth + 1, opts)],
    ref,
    depth,
    opts
  )
}

function inspectWeakRef(weakRef, ref, depth, opts) {
  const { value: target } = attempt(() => weakRef.deref())

  let value

  if (target === undefined) {
    value = new InspectLeaf('<cleared>', styles.special, depth + 1, opts)
  } else {
    value = inspectValue(target, depth + 1, opts)
  }

  const header = (nameOf(weakRef) || 'WeakRef') + ' { '

  return new InspectSequence(header, ' }', ' ', [value], ref, depth, opts)
}

function inspectArrayBuffer(arrayBuffer, ref, depth, opts) {
  ref.increment()

  const values = []

  for (const key of ['byteLength']) {
    values.push(
      new InspectPair(
        ': ',
        inspectKey(key, depth + 1, opts),
        inspectValue(arrayBuffer[key], depth + 1, opts),
        depth + 1,
        opts
      )
    )
  }

  ref.decrement()

  const header = (nameOf(arrayBuffer) || 'ArrayBuffer') + ' { '

  return new InspectSequence(header, ' }', ', ', values, ref, depth, opts)
}

function inspectSharedArrayBuffer(sharedArrayBuffer, ref, depth, opts) {
  ref.increment()

  const values = []

  for (const key of ['byteLength']) {
    values.push(
      new InspectPair(
        ': ',
        inspectKey(key, depth + 1, opts),
        inspectValue(sharedArrayBuffer[key], depth + 1, opts),
        depth + 1,
        opts
      )
    )
  }

  ref.decrement()

  const header = (nameOf(sharedArrayBuffer) || 'SharedArrayBuffer') + ' { '

  return new InspectSequence(header, ' }', ', ', values, ref, depth, opts)
}

function inspectTypedArray(typedArray, ref, depth, opts) {
  if (Buffer.isBuffer(typedArray)) {
    return inspectBuffer(typedArray, ref, depth, opts)
  }

  const { maxArrayLength = defaultMaxArrayLength, maxTypedArrayLength = maxArrayLength } = opts

  ref.increment()

  const values = []

  let remaining = Math.max(maxTypedArrayLength, 0)

  for (let i = 0, n = typedArray.length; i < n; i++) {
    if (remaining-- === 0) {
      values.push(
        new InspectSuspension(typedArray.length - values.length, depth + 1, {
          ...opts,
          breakAlways: true
        })
      )

      break
    }

    values.push(inspectValue(typedArray[i], depth + 1, opts))
  }

  for (const key of nonIndexKeysOf(typedArray)) {
    values.push(
      new InspectPair(
        ': ',
        inspectKey(key, depth + 1, opts),
        inspectProperty(typedArray, key, depth + 1, opts),
        depth + 1,
        { ...opts, breakAlways: remaining < 0 }
      )
    )
  }

  ref.decrement()

  const header = (nameOf(typedArray) || 'TypedArray') + '(' + typedArray.length + ') [ '

  return new InspectSequence(header, ' ]', ', ', values, ref, depth, {
    ...opts,
    tabulate: true
  })
}

function inspectBuffer(buffer, ref, depth, opts) {
  const { maxArrayLength = defaultMaxArrayLength, maxBufferLength = maxArrayLength } = opts

  ref.increment()

  const values = []

  let remaining = Math.max(maxBufferLength, 0)

  for (let i = 0, n = buffer.byteLength; i < n; i++) {
    if (remaining-- === 0) {
      values.push(
        new InspectSuspension(buffer.length - values.length, depth + 1, {
          ...opts,
          breakAlways: true
        })
      )

      break
    }

    values.push(new InspectLeaf(buffer[i].toString(16).padStart(2, '0'), null, depth + 1, opts))
  }

  for (const key of nonIndexKeysOf(buffer)) {
    values.push(
      new InspectPair(
        ': ',
        inspectKey(key, depth + 1, opts),
        inspectProperty(buffer, key, depth + 1, opts),
        depth + 1,
        { ...opts, breakAlways: remaining < 0 }
      )
    )
  }

  ref.decrement()

  return new InspectSequence('<Buffer ', '>', ' ', values, ref, depth, {
    ...opts,
    tabulate: true
  })
}

function inspectDataView(dataView, ref, depth, opts) {
  ref.increment()

  const values = []

  for (const key of ['byteLength', 'byteOffset', 'buffer']) {
    values.push(
      new InspectPair(
        ': ',
        inspectKey(key, depth + 1, opts),
        inspectValue(dataView[key], depth + 1, opts),
        depth + 1,
        opts
      )
    )
  }

  for (const key of nonIndexKeysOf(dataView)) {
    values.push(
      new InspectPair(
        ': ',
        inspectKey(key, depth + 1, opts),
        inspectProperty(dataView, key, depth + 1, opts),
        depth + 1,
        opts
      )
    )
  }

  ref.decrement()

  const header = (nameOf(dataView) || 'DataView') + ' { '

  return new InspectSequence(header, ' }', ', ', values, ref, depth, opts)
}

function inspectFunction(type, fn, depth, opts) {
  const { value: source } = attempt(() => fn.toString())

  if (typeof source === 'string' && source.startsWith('class')) {
    return inspectClass(fn, depth, opts)
  }

  let tag = 'function'

  if (type.isGeneratorFunction()) tag = 'generator ' + tag
  if (type.isAsyncFunction()) tag = 'async ' + tag

  const { value: name } = attempt(() => fn.name)

  return new InspectLeaf(
    '[' + tag + ' ' + (name ? name : '(anonymous)') + ']',
    styles.special,
    depth,
    opts
  )
}

function inspectClass(ctor, depth, opts) {
  const { value: name } = attempt(() => ctor.name)

  return new InspectLeaf(
    '[class ' + (name ? name : '(anonymous)') + ']',
    styles.special,
    depth,
    opts
  )
}

// Externals are labelled by an opaque, per-process token so that their address
// is never disclosed.
const externalTokens = new WeakMap()

let nextExternalToken = 1

function inspectExternal(external, depth, opts) {
  let token = externalTokens.get(external)

  if (token === undefined) {
    token = nextExternalToken++

    externalTokens.set(external, token)
  }

  return new InspectLeaf('[external #' + token + ']', styles.special, depth, opts)
}

// An inspection must not fail on the value it inspects, so everything read from
// that value is read through one of the helpers below. Values are read for
// their own sake, which is why an accessor is reported rather than called, but
// the labels an inspection puts around them are read as they are written.

function attempt(fn) {
  try {
    return { value: fn(), threw: false, error: null }
  } catch (err) {
    return { value: undefined, threw: true, error: err }
  }
}

// The name a value is labelled with, which is the name of its constructor.
function nameOf(object) {
  const { value: name } = attempt(() => object.constructor.name)

  return typeof name === 'string' ? name : ''
}

// A prototype inherits the custom inspection written for its instances, which
// must not be called on the prototype itself as it is not one of them.
function isPrototype(object) {
  const { value: prototype } = attempt(() => object.constructor.prototype)

  return prototype === object
}

function keysOf(object) {
  const keys = []

  // A partial list is the best that can be had from a value that stops
  // answering halfway through.
  attempt(() => {
    for (const key in object) keys.push(key)
  })

  return keys
}

function symbolsOf(object) {
  const symbols = []

  for (const symbol of attempt(() => Object.getOwnPropertySymbols(object)).value || []) {
    const { value: descriptor, threw } = attempt(() =>
      Object.getOwnPropertyDescriptor(object, symbol)
    )

    if (threw || descriptor === undefined || descriptor.enumerable) symbols.push(symbol)
  }

  return symbols
}

function nonIndexKeysOf(object) {
  return attempt(() => binding.getOwnNonIndexPropertyNames(object)).value || []
}

// Returns the descriptor, `undefined` if the property is absent, or `null` if
// the lookup itself could not be made, as with a proxy that throws.
function descriptorOf(object, key) {
  let target = object

  while (target !== null && target !== undefined) {
    const { value: descriptor, threw } = attempt(() => Object.getOwnPropertyDescriptor(target, key))

    if (threw) return null

    if (descriptor) return descriptor

    const { value: prototype, threw: unreadable } = attempt(() => Object.getPrototypeOf(target))

    if (unreadable) return null

    target = prototype
  }

  return undefined
}

function errorLabel(err) {
  if (err === null || typeof err !== 'object') return 'a non-error'

  const { value: message } = attempt(() => err.message)

  const name = nameOf(err) || 'Error'

  return typeof message === 'string' && message ? name + ': ' + message : name
}
{
  "name": "bare-inspect",
  "version": "3.1.10",
  "description": "Inspect objects as strings for debugging",
  "exports": {
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    },
    "./package": "./package.json"
  },
  "files": [
    "index.js",
    "index.d.ts",
    "binding.c",
    "binding.js",
    "CMakeLists.txt",
    "prebuilds"
  ],
  "addon": true,
  "scripts": {
    "format": "prettier --write . && lunte --fix",
    "lint": "prettier --check . && lunte",
    "test": "brittle-bare --coverage test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-inspect.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-inspect/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-inspect#readme",
  "engines": {
    "bare": ">=1.18.0"
  },
  "dependencies": {
    "bare-ansi-escapes": "^2.1.0",
    "bare-type": "^1.0.0"
  },
  "devDependencies": {
    "brittle": "^3.3.2",
    "cmake-bare": "^1.1.6",
    "lunte": "^1.8.4",
    "prettier": "^3.4.1",
    "prettier-config-holepunch": "^2.0.0"
  }
}
module.exports = require.addon()
const binding = require('./binding')

const t = binding.constants

class Type {
  constructor(type) {
    this._type = type
  }

  isUndefined() {
    return this._type === t.UNDEFINED
  }

  isNull() {
    return this._type === t.NULL
  }

  isBoolean() {
    return this._type === t.BOOLEAN
  }

  isNumber() {
    return (this._type & 0xff) === t.NUMBER
  }

  isInt32() {
    return (this._type & (0xff | t.INT32)) === (t.NUMBER | t.INT32)
  }

  isUint32() {
    return (this._type & (0xff | t.UINT32)) === (t.NUMBER | t.UINT32)
  }

  isString() {
    return this._type === t.STRING
  }

  isSymbol() {
    return this._type === t.SYMBOL
  }

  isObject() {
    return (this._type & 0xff) === t.OBJECT
  }

  isArray() {
    return this._type === (t.OBJECT | t.ARRAY)
  }

  isArguments() {
    return this._type === (t.OBJECT | t.ARGUMENTS)
  }

  isDate() {
    return this._type === (t.OBJECT | t.DATE)
  }

  isRegExp() {
    return this._type === (t.OBJECT | t.REGEXP)
  }

  isError() {
    return this._type === (t.OBJECT | t.ERROR)
  }

  isPromise() {
    return this._type === (t.OBJECT | t.PROMISE)
  }

  isProxy() {
    return this._type === (t.OBJECT | t.PROXY)
  }

  isGenerator() {
    return this._type === (t.OBJECT | t.GENERATOR)
  }

  isMap() {
    return this._type === (t.OBJECT | t.MAP)
  }

  isSet() {
    return this._type === (t.OBJECT | t.SET)
  }

  isWeakMap() {
    return this._type === (t.OBJECT | t.WEAK_MAP)
  }

  isWeakSet() {
    return this._type === (t.OBJECT | t.WEAK_SET)
  }

  isWeakRef() {
    return this._type === (t.OBJECT | t.WEAK_REF)
  }

  isArrayBuffer() {
    return this._type === (t.OBJECT | t.ARRAYBUFFER)
  }

  isSharedArrayBuffer() {
    return this._type === (t.OBJECT | t.SHAREDARRAYBUFFER)
  }

  isTypedArray() {
    return (this._type & 0xffff) === (t.OBJECT | t.TYPEDARRAY)
  }

  isInt8Array() {
    return this._type === (t.OBJECT | t.TYPEDARRAY | t.INT8ARRAY)
  }

  isUint8Array() {
    return this._type === (t.OBJECT | t.TYPEDARRAY | t.UINT8ARRAY)
  }

  isUint8ClampedArray() {
    return this._type === (t.OBJECT | t.TYPEDARRAY | t.UINT8CLAMPEDARRAY)
  }

  isInt16Array() {
    return this._type === (t.OBJECT | t.TYPEDARRAY | t.INT16ARRAY)
  }

  isUint16Array() {
    return this._type === (t.OBJECT | t.TYPEDARRAY | t.UINT16ARRAY)
  }

  isInt32Array() {
    return this._type === (t.OBJECT | t.TYPEDARRAY | t.INT32ARRAY)
  }

  isUint32Array() {
    return this._type === (t.OBJECT | t.TYPEDARRAY | t.UINT32ARRAY)
  }

  isFloat16Array() {
    return this._type === (t.OBJECT | t.TYPEDARRAY | t.FLOAT16ARRAY)
  }

  isFloat32Array() {
    return this._type === (t.OBJECT | t.TYPEDARRAY | t.FLOAT32ARRAY)
  }

  isFloat64Array() {
    return this._type === (t.OBJECT | t.TYPEDARRAY | t.FLOAT64ARRAY)
  }

  isBigInt64Array() {
    return this._type === (t.OBJECT | t.TYPEDARRAY | t.BIGINT64ARRAY)
  }

  isBigUint64Array() {
    return this._type === (t.OBJECT | t.TYPEDARRAY | t.BIGUINT64ARRAY)
  }

  isDataView() {
    return this._type === (t.OBJECT | t.DATAVIEW)
  }

  isModuleNamespace() {
    return this._type === (t.OBJECT | t.MODULE_NAMESPACE)
  }

  isFunction() {
    return (this._type & 0xff) === t.FUNCTION
  }

  isAsyncFunction() {
    return (this._type & (0xff | t.ASYNC_FUNCTION)) === (t.FUNCTION | t.ASYNC_FUNCTION)
  }

  isGeneratorFunction() {
    return (this._type & (0xff | t.GENERATOR_FUNCTION)) === (t.FUNCTION | t.GENERATOR_FUNCTION)
  }

  isExternal() {
    return this._type === t.EXTERNAL
  }

  isBigInt() {
    return this._type === t.BIGINT
  }
}

// Classifies a number the way the engine does, but without crossing into the
// addon. A number is an `int32` or a `uint32` if it is an integer within the
// respective range, with negative zero being representable as neither.
function numberType(value) {
  if (!Number.isInteger(value)) return t.NUMBER

  // Negative zero passes both range checks below, so exclude it up front.
  if (value === 0 && 1 / value < 0) return t.NUMBER

  let type = t.NUMBER

  if (value >= -0x80000000 && value <= 0x7fffffff) type |= t.INT32
  if (value >= 0 && value <= 0xffffffff) type |= t.UINT32

  return type
}

module.exports = function type(value) {
  switch (typeof value) {
    case 'undefined':
      return new Type(t.UNDEFINED)
    case 'boolean':
      return new Type(t.BOOLEAN)
    case 'number':
      return new Type(numberType(value))
    case 'string':
      return new Type(t.STRING)
    case 'symbol':
      return new Type(t.SYMBOL)
    case 'object':
      return new Type(value === null ? t.NULL : binding.type(value))
    case 'function':
      return new Type(binding.type(value))
    case 'bigint':
      return new Type(t.BIGINT)
  }
}
{
  "name": "bare-type",
  "version": "1.1.1",
  "description": "Cross-realm type predicates for Bare",
  "exports": {
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    },
    "./package": "./package.json"
  },
  "files": [
    "index.js",
    "index.d.ts",
    "binding.c",
    "binding.js",
    "CMakeLists.txt",
    "prebuilds"
  ],
  "addon": true,
  "scripts": {
    "format": "prettier --write . && lunte --fix",
    "lint": "prettier --check . && lunte",
    "test": "brittle-bare --coverage test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-type.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-type/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-type#readme",
  "engines": {
    "bare": ">=1.2.0"
  },
  "devDependencies": {
    "brittle": "^3.4.0",
    "cmake-bare": "^1.1.6",
    "lunte": "^1.8.4",
    "prettier": "^3.4.1",
    "prettier-config-holepunch": "^2.0.0"
  }
}
const LE = (exports.LE = new Uint8Array(new Uint16Array([0xff]).buffer)[0] === 0xff)

exports.BE = !LE
const b4a = require('b4a')

const { BE } = require('./endian')

// Zig-zag doubles the magnitude of a value before it is written as a uint, so
// an int only reaches half as far as a uint of the same width does.
const MAX_SAFE_INT = 2 ** 52 - 1
const MIN_SAFE_INT = -(2 ** 52)

exports.state = function (start = 0, end = 0, buffer = null) {
  return { start, end, buffer }
}

const raw = (exports.raw = require('./raw'))

const uint = (exports.uint = {
  preencode(state, n) {
    state.end += n <= 0xfc ? 1 : n <= 0xffff ? 3 : n <= 0xffffffff ? 5 : 9
  },
  encode(state, n) {
    if (n <= 0xfc) uint8.encode(state, n)
    else if (n <= 0xffff) {
      state.buffer[state.start++] = 0xfd
      uint16.encode(state, n)
    } else if (n <= 0xffffffff) {
      state.buffer[state.start++] = 0xfe
      uint32.encode(state, n)
    } else {
      state.buffer[state.start++] = 0xff
      uint64.encode(state, n)
    }
  },
  decode(state) {
    const a = uint8.decode(state)
    if (a <= 0xfc) return a
    if (a === 0xfd) return uint16.decode(state)
    if (a === 0xfe) return uint32.decode(state)
    return uint64.decode(state)
  }
})

const uint8 = (exports.uint8 = {
  preencode(state, n) {
    state.end += 1
  },
  encode(state, n) {
    validateUint(n)
    state.buffer[state.start++] = n
  },
  decode(state) {
    if (state.start >= state.end) throw new Error('Out of bounds')
    return state.buffer[state.start++]
  }
})

const uint16 = (exports.uint16 = {
  preencode(state, n) {
    state.end += 2
  },
  encode(state, n) {
    validateUint(n)
    state.buffer[state.start++] = n
    state.buffer[state.start++] = n >>> 8
  },
  decode(state) {
    if (state.end - state.start < 2) throw new Error('Out of bounds')
    return state.buffer[state.start++] + state.buffer[state.start++] * 0x100
  }
})

const uint24 = (exports.uint24 = {
  preencode(state, n) {
    state.end += 3
  },
  encode(state, n) {
    validateUint(n)
    state.buffer[state.start++] = n
    state.buffer[state.start++] = n >>> 8
    state.buffer[state.start++] = n >>> 16
  },
  decode(state) {
    if (state.end - state.start < 3) throw new Error('Out of bounds')
    return (
      state.buffer[state.start++] +
      state.buffer[state.start++] * 0x100 +
      state.buffer[state.start++] * 0x10000
    )
  }
})

const uint32 = (exports.uint32 = {
  preencode(state, n) {
    state.end += 4
  },
  encode(state, n) {
    validateUint(n)
    state.buffer[state.start++] = n
    state.buffer[state.start++] = n >>> 8
    state.buffer[state.start++] = n >>> 16
    state.buffer[state.start++] = n >>> 24
  },
  decode(state) {
    if (state.end - state.start < 4) throw new Error('Out of bounds')
    return (
      state.buffer[state.start++] +
      state.buffer[state.start++] * 0x100 +
      state.buffer[state.start++] * 0x10000 +
      state.buffer[state.start++] * 0x1000000
    )
  }
})

const uint32be = (exports.uint32be = {
  preencode(state, n) {
    state.end += 4
  },
  encode(state, n) {
    validateUint(n)
    state.buffer[state.start++] = n >>> 24
    state.buffer[state.start++] = n >>> 16
    state.buffer[state.start++] = n >>> 8
    state.buffer[state.start++] = n
  },
  decode(state) {
    if (state.end - state.start < 4) throw new Error('Out of bounds')
    return (
      state.buffer[state.start++] * 0x1000000 +
      state.buffer[state.start++] * 0x10000 +
      state.buffer[state.start++] * 0x100 +
      state.buffer[state.start++]
    )
  }
})

const uint40 = (exports.uint40 = {
  preencode(state, n) {
    state.end += 5
  },
  encode(state, n) {
    validateUint(n)
    const r = Math.floor(n / 0x100)
    uint8.encode(state, n)
    uint32.encode(state, r)
  },
  decode(state) {
    if (state.end - state.start < 5) throw new Error('Out of bounds')
    return uint8.decode(state) + 0x100 * uint32.decode(state)
  }
})

const uint48 = (exports.uint48 = {
  preencode(state, n) {
    state.end += 6
  },
  encode(state, n) {
    validateUint(n)
    const r = Math.floor(n / 0x10000)
    uint16.encode(state, n)
    uint32.encode(state, r)
  },
  decode(state) {
    if (state.end - state.start < 6) throw new Error('Out of bounds')
    return uint16.decode(state) + 0x10000 * uint32.decode(state)
  }
})

const uint56 = (exports.uint56 = {
  preencode(state, n) {
    state.end += 7
  },
  encode(state, n) {
    validateUint(n)
    const r = Math.floor(n / 0x1000000)
    uint24.encode(state, n)
    uint32.encode(state, r)
  },
  decode(state) {
    if (state.end - state.start < 7) throw new Error('Out of bounds')
    return validateSafeUint(uint24.decode(state) + 0x1000000 * uint32.decode(state))
  }
})

const uint64 = (exports.uint64 = {
  preencode(state, n) {
    state.end += 8
  },
  encode(state, n) {
    validateUint(n)
    const r = Math.floor(n / 0x100000000)
    uint32.encode(state, n)
    uint32.encode(state, r)
  },
  decode(state) {
    if (state.end - state.start < 8) throw new Error('Out of bounds')
    return validateSafeUint(uint32.decode(state) + 0x100000000 * uint32.decode(state))
  }
})

exports.uint64be = {
  preencode(state, n) {
    state.end += 8
  },
  encode(state, n) {
    validateUint(n)
    const r = Math.floor(n / 0x100000000)
    uint32be.encode(state, r)
    uint32be.encode(state, n)
  },
  decode(state) {
    if (state.end - state.start < 8) throw new Error('Out of bounds')
    return validateSafeUint(0x100000000 * uint32be.decode(state) + uint32be.decode(state))
  }
}

const int = (exports.int = zigZagInt(uint))
exports.int8 = zigZagInt(uint8)
exports.int16 = zigZagInt(uint16)
exports.int24 = zigZagInt(uint24)
exports.int32 = zigZagInt(uint32)
exports.int40 = zigZagInt(uint40)
exports.int48 = zigZagInt(uint48)
exports.int56 = zigZagInt(uint56)
exports.int64 = zigZagInt(uint64)

const biguint64 = (exports.biguint64 = {
  preencode(state, n) {
    state.end += 8
  },
  encode(state, n) {
    const view = new DataView(state.buffer.buffer, state.start + state.buffer.byteOffset, 8)
    view.setBigUint64(0, n, true) // little endian
    state.start += 8
  },
  decode(state) {
    if (state.end - state.start < 8) throw new Error('Out of bounds')
    const view = new DataView(state.buffer.buffer, state.start + state.buffer.byteOffset, 8)
    const n = view.getBigUint64(0, true) // little endian
    state.start += 8
    return n
  }
})

exports.bigint64 = zigZagBigInt(biguint64)

const biguint = (exports.biguint = {
  preencode(state, n) {
    let len = 0
    for (let m = n; m; m = m >> 64n) len++
    uint.preencode(state, len)
    state.end += 8 * len
  },
  encode(state, n) {
    let len = 0
    for (let m = n; m; m = m >> 64n) len++
    uint.encode(state, len)
    const view = new DataView(state.buffer.buffer, state.start + state.buffer.byteOffset, 8 * len)
    for (let m = n, i = 0; m; m = m >> 64n, i += 8) {
      view.setBigUint64(i, BigInt.asUintN(64, m), true) // little endian
    }
    state.start += 8 * len
  },
  decode(state) {
    const len = uint.decode(state)
    if (state.end - state.start < 8 * len) throw new Error('Out of bounds')
    const view = new DataView(state.buffer.buffer, state.start + state.buffer.byteOffset, 8 * len)
    let n = 0n
    for (let i = len - 1; i >= 0; i--) n = (n << 64n) + view.getBigUint64(i * 8, true) // little endian
    state.start += 8 * len
    return n
  }
})

exports.bigint = zigZagBigInt(biguint)

exports.lexint = require('./lexint')

exports.float32 = {
  preencode(state, n) {
    state.end += 4
  },
  encode(state, n) {
    const view = new DataView(state.buffer.buffer, state.start + state.buffer.byteOffset, 4)
    view.setFloat32(0, n, true) // little endian
    state.start += 4
  },
  decode(state) {
    if (state.end - state.start < 4) throw new Error('Out of bounds')
    const view = new DataView(state.buffer.buffer, state.start + state.buffer.byteOffset, 4)
    const float = view.getFloat32(0, true) // little endian
    state.start += 4
    return float
  }
}

exports.float64 = {
  preencode(state, n) {
    state.end += 8
  },
  encode(state, n) {
    const view = new DataView(state.buffer.buffer, state.start + state.buffer.byteOffset, 8)
    view.setFloat64(0, n, true) // little endian
    state.start += 8
  },
  decode(state) {
    if (state.end - state.start < 8) throw new Error('Out of bounds')
    const view = new DataView(state.buffer.buffer, state.start + state.buffer.byteOffset, 8)
    const float = view.getFloat64(0, true) // little endian
    state.start += 8
    return float
  }
}

const buffer = (exports.buffer = {
  preencode(state, b) {
    uint8array.preencode(state, b)
  },
  encode(state, b) {
    uint8array.encode(state, b)
  },
  decode(state) {
    const len = uint.decode(state)
    if (state.end - state.start < len) throw new Error('Out of bounds')
    return state.buffer.subarray(state.start, (state.start += len))
  }
})

exports.optionalBuffer = {
  preencode(state, b) {
    if (b) uint8array.preencode(state, b)
    else state.end++
  },
  encode(state, b) {
    if (b) uint8array.encode(state, b)
    else state.buffer[state.start++] = 0
  },
  decode(state) {
    const len = uint.decode(state)
    if (len === 0) return null
    if (state.end - state.start < len) throw new Error('Out of bounds')
    return state.buffer.subarray(state.start, (state.start += len))
  }
}

exports.binary = {
  ...buffer,
  preencode(state, b) {
    if (typeof b === 'string') utf8.preencode(state, b)
    else buffer.preencode(state, b)
  },
  encode(state, b) {
    if (typeof b === 'string') utf8.encode(state, b)
    else buffer.encode(state, b)
  }
}

exports.arraybuffer = {
  preencode(state, b) {
    uint.preencode(state, b.byteLength)
    state.end += b.byteLength
  },
  encode(state, b) {
    uint.encode(state, b.byteLength)

    const view = new Uint8Array(b)

    state.buffer.set(view, state.start)
    state.start += b.byteLength
  },
  decode(state) {
    const len = uint.decode(state)

    const b = new ArrayBuffer(len)
    const view = new Uint8Array(b)

    view.set(state.buffer.subarray(state.start, (state.start += len)))

    return b
  }
}

exports.bitarray = {
  preencode(state, m) {
    uint.preencode(state, m.length)
    state.end += Math.ceil(m.length / 8)
  },
  encode(state, m) {
    uint.encode(state, m.length)
    for (let i = 0; i < m.length; i += 8) {
      let byte = 0
      for (let j = 0; j < 8 && i + j < m.length; j++) {
        if (m[i + j]) byte |= 1 << j
      }
      state.buffer[state.start++] = byte
    }
  },
  decode(state) {
    const n = uint.decode(state)
    if (state.end - state.start < Math.ceil(n / 8)) throw new Error('Out of bounds')
    const m = new Array(n)
    for (let i = 0; i < n; i += 8) {
      const byte = state.buffer[state.start++]
      for (let j = 0; j < 8 && i + j < n; j++) {
        m[i + j] = (byte & (1 << j)) !== 0
      }
    }
    return m
  }
}

function typedarray(TypedArray, swap) {
  const n = TypedArray.BYTES_PER_ELEMENT

  return {
    preencode(state, b) {
      uint.preencode(state, b.length)
      state.end += b.byteLength
    },
    encode(state, b) {
      uint.encode(state, b.length)

      const view = new Uint8Array(b.buffer, b.byteOffset, b.byteLength)

      if (BE && swap) swap(view)

      state.buffer.set(view, state.start)
      state.start += b.byteLength
    },
    decode(state) {
      const len = uint.decode(state)

      let b = state.buffer.subarray(state.start, (state.start += len * n))
      if (b.byteLength !== len * n) throw new Error('Out of bounds')
      if (b.byteOffset % n !== 0) b = new Uint8Array(b)

      if (BE && swap) swap(b)

      return new TypedArray(b.buffer, b.byteOffset, b.byteLength / n)
    }
  }
}

const uint8array = (exports.uint8array = typedarray(Uint8Array))
exports.uint16array = typedarray(Uint16Array, b4a.swap16)
exports.uint32array = typedarray(Uint32Array, b4a.swap32)

exports.int8array = typedarray(Int8Array)
exports.int16array = typedarray(Int16Array, b4a.swap16)
exports.int32array = typedarray(Int32Array, b4a.swap32)

exports.biguint64array = typedarray(BigUint64Array, b4a.swap64)
exports.bigint64array = typedarray(BigInt64Array, b4a.swap64)

exports.float32array = typedarray(Float32Array, b4a.swap32)
exports.float64array = typedarray(Float64Array, b4a.swap64)

function string(encoding) {
  return {
    preencode(state, s) {
      const len = b4a.byteLength(s, encoding)
      uint.preencode(state, len)
      state.end += len
    },
    encode(state, s) {
      const len = b4a.byteLength(s, encoding)
      uint.encode(state, len)
      b4a.write(state.buffer, s, state.start, encoding)
      state.start += len
    },
    decode(state) {
      const len = uint.decode(state)
      if (state.end - state.start < len) throw new Error('Out of bounds')
      return b4a.toString(state.buffer, encoding, state.start, (state.start += len))
    },
    fixed(n) {
      return {
        preencode(state) {
          state.end += n
        },
        encode(state, s) {
          b4a.write(state.buffer, s, state.start, n, encoding)
          state.start += n
        },
        decode(state) {
          if (state.end - state.start < n) throw new Error('Out of bounds')
          return b4a.toString(state.buffer, encoding, state.start, (state.start += n))
        }
      }
    }
  }
}

const utf8 = (exports.string = exports.utf8 = string('utf-8'))
exports.ascii = string('ascii')
exports.hex = string('hex')
exports.base64 = string('base64')
exports.ucs2 = exports.utf16le = string('utf16le')

exports.bool = {
  preencode(state, b) {
    state.end++
  },
  encode(state, b) {
    state.buffer[state.start++] = b ? 1 : 0
  },
  decode(state) {
    if (state.start >= state.end) throw Error('Out of bounds')
    return state.buffer[state.start++] === 1
  }
}

const fixed = (exports.fixed = function fixed(n) {
  return {
    preencode(state, s) {
      if (s.byteLength !== n) throw new Error('Incorrect buffer size')
      state.end += n
    },
    encode(state, s) {
      state.buffer.set(s, state.start)
      state.start += n
    },
    decode(state) {
      if (state.end - state.start < n) throw new Error('Out of bounds')
      return state.buffer.subarray(state.start, (state.start += n))
    }
  }
})

exports.fixed32 = fixed(32)
exports.fixed64 = fixed(64)

exports.array = function array(enc) {
  return {
    preencode(state, list) {
      uint.preencode(state, list.length)
      for (let i = 0; i < list.length; i++) enc.preencode(state, list[i])
    },
    encode(state, list) {
      uint.encode(state, list.length)
      for (let i = 0; i < list.length; i++) enc.encode(state, list[i])
    },
    decode(state) {
      const len = uint.decode(state)
      if (len > 0x100000) throw new Error('Array is too big')
      const arr = new Array(len)
      for (let i = 0; i < len; i++) arr[i] = enc.decode(state)
      return arr
    }
  }
}

exports.frame = function frame(enc) {
  const dummy = exports.state()

  return {
    preencode(state, m) {
      const end = state.end
      enc.preencode(state, m)
      uint.preencode(state, state.end - end)
    },
    encode(state, m) {
      dummy.end = 0
      enc.preencode(dummy, m)
      uint.encode(state, dummy.end)
      enc.encode(state, m)
    },
    decode(state) {
      const end = state.end
      const len = uint.decode(state)
      state.end = state.start + len
      const m = enc.decode(state)
      state.start = state.end
      state.end = end
      return m
    }
  }
}

exports.date = {
  preencode(state, d) {
    int.preencode(state, d.getTime())
  },
  encode(state, d) {
    int.encode(state, d.getTime())
  },
  decode(state, d) {
    return new Date(int.decode(state))
  }
}

exports.json = {
  preencode(state, v) {
    utf8.preencode(state, JSON.stringify(v))
  },
  encode(state, v) {
    utf8.encode(state, JSON.stringify(v))
  },
  decode(state) {
    return JSON.parse(utf8.decode(state))
  }
}

exports.ndjson = {
  preencode(state, v) {
    utf8.preencode(state, JSON.stringify(v) + '\n')
  },
  encode(state, v) {
    utf8.encode(state, JSON.stringify(v) + '\n')
  },
  decode(state) {
    return JSON.parse(utf8.decode(state))
  }
}

// simple helper for when you want to just express nothing
exports.none = {
  preencode(state, n) {
    // do nothing
  },
  encode(state, n) {
    // do nothing
  },
  decode(state) {
    return null
  }
}

// "any" encoders here for helping just structure any object without schematising it

const anyArray = {
  preencode(state, arr) {
    uint.preencode(state, arr.length)
    for (let i = 0; i < arr.length; i++) {
      any.preencode(state, arr[i])
    }
  },
  encode(state, arr) {
    uint.encode(state, arr.length)
    for (let i = 0; i < arr.length; i++) {
      any.encode(state, arr[i])
    }
  },
  decode(state) {
    const arr = []
    let len = uint.decode(state)
    while (len-- > 0) {
      arr.push(any.decode(state))
    }
    return arr
  }
}

const anyObject = {
  preencode(state, o) {
    const keys = Object.keys(o)
    uint.preencode(state, keys.length)
    for (const key of keys) {
      utf8.preencode(state, key)
      any.preencode(state, o[key])
    }
  },
  encode(state, o) {
    const keys = Object.keys(o)
    uint.encode(state, keys.length)
    for (const key of keys) {
      utf8.encode(state, key)
      any.encode(state, o[key])
    }
  },
  decode(state) {
    let len = uint.decode(state)
    const o = {}
    while (len-- > 0) {
      const key = utf8.decode(state)
      o[key] = any.decode(state)
    }
    return o
  }
}

const anyTypes = [
  exports.none,
  exports.bool,
  exports.string,
  exports.buffer,
  exports.uint,
  exports.int,
  exports.float64,
  anyArray,
  anyObject,
  exports.date
]

const any = (exports.any = {
  preencode(state, o) {
    const t = getType(o)
    uint.preencode(state, t)
    anyTypes[t].preencode(state, o)
  },
  encode(state, o) {
    const t = getType(o)
    uint.encode(state, t)
    anyTypes[t].encode(state, o)
  },
  decode(state) {
    const t = uint.decode(state)
    if (t >= anyTypes.length) throw new Error('Unknown type: ' + t)
    return anyTypes[t].decode(state)
  }
})

const port = (exports.port = uint16)

const address = (host, family) => {
  return {
    preencode(state, m) {
      host.preencode(state, m.host)
      port.preencode(state, m.port)
    },
    encode(state, m) {
      host.encode(state, m.host)
      port.encode(state, m.port)
    },
    decode(state) {
      return {
        host: host.decode(state),
        family,
        port: port.decode(state)
      }
    }
  }
}

const ipv4 = (exports.ipv4 = {
  preencode(state) {
    state.end += 4
  },
  encode(state, string) {
    const start = state.start
    const end = start + 4

    let i = 0

    while (i < string.length) {
      let n = 0
      let c

      while (i < string.length && (c = string.charCodeAt(i++)) !== /* . */ 0x2e) {
        n = n * 10 + (c - /* 0 */ 0x30)
      }

      state.buffer[state.start++] = n
    }

    state.start = end
  },
  decode(state) {
    if (state.end - state.start < 4) throw new Error('Out of bounds')
    return (
      state.buffer[state.start++] +
      '.' +
      state.buffer[state.start++] +
      '.' +
      state.buffer[state.start++] +
      '.' +
      state.buffer[state.start++]
    )
  }
})

exports.ipv4Address = address(ipv4, 4)

const ipv6 = (exports.ipv6 = {
  preencode(state) {
    state.end += 16
  },
  encode(state, string) {
    const start = state.start
    const end = start + 16

    let i = 0
    let split = null

    while (i < string.length) {
      let n = 0
      let c

      while (i < string.length && (c = string.charCodeAt(i++)) !== /* : */ 0x3a) {
        if (c >= 0x30 && c <= 0x39) n = n * 0x10 + (c - /* 0 */ 0x30)
        else if (c >= 0x41 && c <= 0x46) n = n * 0x10 + (c - /* A */ 0x41 + 10)
        else if (c >= 0x61 && c <= 0x66) n = n * 0x10 + (c - /* a */ 0x61 + 10)
      }

      state.buffer[state.start++] = n >>> 8
      state.buffer[state.start++] = n

      if (i < string.length && string.charCodeAt(i) === /* : */ 0x3a) {
        i++
        split = state.start
      }
    }

    if (split !== null) {
      const offset = end - state.start
      state.buffer.copyWithin(split + offset, split).fill(0, split, split + offset)
    }

    state.start = end
  },
  decode(state) {
    if (state.end - state.start < 16) throw new Error('Out of bounds')
    return (
      (state.buffer[state.start++] * 256 + state.buffer[state.start++]).toString(16) +
      ':' +
      (state.buffer[state.start++] * 256 + state.buffer[state.start++]).toString(16) +
      ':' +
      (state.buffer[state.start++] * 256 + state.buffer[state.start++]).toString(16) +
      ':' +
      (state.buffer[state.start++] * 256 + state.buffer[state.start++]).toString(16) +
      ':' +
      (state.buffer[state.start++] * 256 + state.buffer[state.start++]).toString(16) +
      ':' +
      (state.buffer[state.start++] * 256 + state.buffer[state.start++]).toString(16) +
      ':' +
      (state.buffer[state.start++] * 256 + state.buffer[state.start++]).toString(16) +
      ':' +
      (state.buffer[state.start++] * 256 + state.buffer[state.start++]).toString(16)
    )
  }
})

exports.ipv6Address = address(ipv6, 6)

const ip = (exports.ip = {
  preencode(state, string) {
    const family = string.includes(':') ? 6 : 4
    uint8.preencode(state, family)
    if (family === 4) ipv4.preencode(state)
    else ipv6.preencode(state)
  },
  encode(state, string) {
    const family = string.includes(':') ? 6 : 4
    uint8.encode(state, family)
    if (family === 4) ipv4.encode(state, string)
    else ipv6.encode(state, string)
  },
  decode(state) {
    const family = uint8.decode(state)
    if (family === 4) return ipv4.decode(state)
    else return ipv6.decode(state)
  }
})

exports.ipAddress = {
  preencode(state, m) {
    ip.preencode(state, m.host)
    port.preencode(state, m.port)
  },
  encode(state, m) {
    ip.encode(state, m.host)
    port.encode(state, m.port)
  },
  decode(state) {
    const family = uint8.decode(state)
    return {
      host: family === 4 ? ipv4.decode(state) : ipv6.decode(state),
      family,
      port: port.decode(state)
    }
  }
}

const record = (exports.record = function (keyEncoding, valueEncoding) {
  return {
    preencode(state, v) {
      const keys = Object.keys(v)
      uint.preencode(state, keys.length)
      for (const k of keys) {
        keyEncoding.preencode(state, k)
        valueEncoding.preencode(state, v[k])
      }
    },
    encode(state, v) {
      const keys = Object.keys(v)
      uint.encode(state, keys.length)
      for (const k of keys) {
        keyEncoding.encode(state, k)
        valueEncoding.encode(state, v[k])
      }
    },
    decode(state) {
      const out = Object.create(null)
      const keys = uint.decode(state)
      for (let i = 0; i < keys; i++) {
        out[keyEncoding.decode(state)] = valueEncoding.decode(state)
      }
      return out
    }
  }
})

exports.stringRecord = record(utf8, utf8)

function getType(o) {
  if (o === null || o === undefined) return 0
  if (typeof o === 'boolean') return 1
  if (typeof o === 'string') return 2
  if (b4a.isBuffer(o)) return 3
  if (typeof o === 'number') {
    if (Number.isInteger(o)) return o >= 0 ? 4 : 5
    return 6
  }
  if (Array.isArray(o)) return 7
  if (o instanceof Date) return 9
  if (typeof o === 'object') return 8

  throw new Error('Unsupported type for ' + o)
}

exports.from = function from(enc) {
  if (typeof enc === 'string') return fromNamed(enc)
  if (enc.preencode) return enc
  if (enc.encodingLength) return fromAbstractEncoder(enc)
  return fromCodec(enc)
}

function fromNamed(enc) {
  switch (enc) {
    case 'ascii':
      return raw.ascii
    case 'utf-8':
    case 'utf8':
      return raw.utf8
    case 'hex':
      return raw.hex
    case 'base64':
      return raw.base64
    case 'utf16-le':
    case 'utf16le':
    case 'ucs-2':
    case 'ucs2':
      return raw.ucs2
    case 'ndjson':
      return raw.ndjson
    case 'json':
      return raw.json
    case 'binary':
    default:
      return raw.binary
  }
}

function fromCodec(enc) {
  let tmpM = null
  let tmpBuf = null

  return {
    preencode(state, m) {
      tmpM = m
      tmpBuf = enc.encode(m)
      state.end += tmpBuf.byteLength
    },
    encode(state, m) {
      raw.encode(state, m === tmpM ? tmpBuf : enc.encode(m))
      tmpM = tmpBuf = null
    },
    decode(state) {
      return enc.decode(raw.decode(state))
    }
  }
}

function fromAbstractEncoder(enc) {
  return {
    preencode(state, m) {
      state.end += enc.encodingLength(m)
    },
    encode(state, m) {
      enc.encode(m, state.buffer, state.start)
      state.start += enc.encode.bytes
    },
    decode(state) {
      const m = enc.decode(state.buffer, state.start, state.end)
      state.start += enc.decode.bytes
      return m
    }
  }
}

exports.encode = function encode(enc, m) {
  const state = exports.state()
  enc.preencode(state, m)
  state.buffer = b4a.allocUnsafe(state.end)
  enc.encode(state, m)
  return state.buffer
}

exports.decode = function decode(enc, buffer) {
  return enc.decode(exports.state(0, buffer.byteLength, buffer))
}

function zigZagInt(enc) {
  return {
    preencode(state, n) {
      enc.preencode(state, zigZagEncodeInt(n))
    },
    encode(state, n) {
      enc.encode(state, zigZagEncodeInt(n))
    },
    decode(state) {
      return zigZagDecodeInt(enc.decode(state))
    }
  }
}

function zigZagDecodeInt(n) {
  return n === 0 ? n : (n & 1) === 0 ? n / 2 : -(n + 1) / 2
}

function zigZagEncodeInt(n) {
  validateInt(n)
  // 0, -1, 1, -2, 2, ...
  return n < 0 ? 2 * -n - 1 : n === 0 ? 0 : 2 * n
}

function zigZagBigInt(enc) {
  return {
    preencode(state, n) {
      enc.preencode(state, zigZagEncodeBigInt(n))
    },
    encode(state, n) {
      enc.encode(state, zigZagEncodeBigInt(n))
    },
    decode(state) {
      return zigZagDecodeBigInt(enc.decode(state))
    }
  }
}

function zigZagDecodeBigInt(n) {
  return n === 0n ? n : (n & 1n) === 0n ? n / 2n : -(n + 1n) / 2n
}

function zigZagEncodeBigInt(n) {
  // 0, -1, 1, -2, 2, ...
  return n < 0n ? 2n * -n - 1n : n === 0n ? 0n : 2n * n
}

function validateSafeUint(n) {
  if (n <= Number.MAX_SAFE_INTEGER) return n // Handles NaN as well

  throw outsideUintRange()
}

function validateUint(n) {
  if (n >= 0 && n <= Number.MAX_SAFE_INTEGER) return n // Handles NaN as well

  throw outsideUintRange()
}

function validateInt(n) {
  if (n >= MIN_SAFE_INT && n <= MAX_SAFE_INT) return n // Handles NaN as well

  throw outsideIntRange()
}

// The validations above sit on the hottest paths in the library and are small
// enough to be inlined, which building a message inline would put a stop to.
// Kept out here, the message costs nothing until it is actually thrown.

function outsideUintRange() {
  return new Error(`uint must be between 0 and ${Number.MAX_SAFE_INTEGER}, use biguint`)
}

function outsideIntRange() {
  return new Error(`int must be between ${MIN_SAFE_INT} and ${MAX_SAFE_INT}, use bigint`)
}
module.exports = {
  preencode,
  encode,
  decode
}

function preencode(state, num) {
  if (num < 251) {
    state.end++
  } else if (num < 256) {
    state.end += 2
  } else if (num < 0x10000) {
    state.end += 3
  } else if (num < 0x1000000) {
    state.end += 4
  } else if (num < 0x100000000) {
    state.end += 5
  } else {
    state.end++
    const exp = Math.floor(Math.log(num) / Math.log(2)) - 32
    preencode(state, exp)
    state.end += 6
  }
}

function encode(state, num) {
  const max = 251
  const x = num - max

  if (num < max) {
    state.buffer[state.start++] = num
  } else if (num < 256) {
    state.buffer[state.start++] = max
    state.buffer[state.start++] = x
  } else if (num < 0x10000) {
    state.buffer[state.start++] = max + 1
    state.buffer[state.start++] = (x >> 8) & 0xff
    state.buffer[state.start++] = x & 0xff
  } else if (num < 0x1000000) {
    state.buffer[state.start++] = max + 2
    state.buffer[state.start++] = x >> 16
    state.buffer[state.start++] = (x >> 8) & 0xff
    state.buffer[state.start++] = x & 0xff
  } else if (num < 0x100000000) {
    state.buffer[state.start++] = max + 3
    state.buffer[state.start++] = x >> 24
    state.buffer[state.start++] = (x >> 16) & 0xff
    state.buffer[state.start++] = (x >> 8) & 0xff
    state.buffer[state.start++] = x & 0xff
  } else {
    // need to use Math here as bitwise ops are 32 bit
    const exp = Math.floor(Math.log(x) / Math.log(2)) - 32
    state.buffer[state.start++] = 0xff

    encode(state, exp)
    const rem = x / Math.pow(2, exp - 11)

    for (let i = 5; i >= 0; i--) {
      state.buffer[state.start++] = (rem / Math.pow(2, 8 * i)) & 0xff
    }
  }
}

function decode(state) {
  const max = 251

  if (state.end - state.start < 1) throw new Error('Out of bounds')

  const flag = state.buffer[state.start++]

  if (flag < max) return flag

  if (state.end - state.start < flag - max + 1) {
    throw new Error('Out of bounds.')
  }

  if (flag < 252) {
    return state.buffer[state.start++] + max
  }

  if (flag < 253) {
    return (state.buffer[state.start++] << 8) + state.buffer[state.start++] + max
  }

  if (flag < 254) {
    return (
      (state.buffer[state.start++] << 16) +
      (state.buffer[state.start++] << 8) +
      state.buffer[state.start++] +
      max
    )
  }

  // << 24 result may be interpreted as negative
  if (flag < 255) {
    return (
      state.buffer[state.start++] * 0x1000000 +
      (state.buffer[state.start++] << 16) +
      (state.buffer[state.start++] << 8) +
      state.buffer[state.start++] +
      max
    )
  }

  const exp = decode(state)

  if (state.end - state.start < 6) throw new Error('Out of bounds')

  let rem = 0
  for (let i = 5; i >= 0; i--) {
    rem += state.buffer[state.start++] * Math.pow(2, 8 * i)
  }

  return rem * Math.pow(2, exp - 11) + max
}
{
  "name": "compact-encoding",
  "version": "3.4.0",
  "description": "A series of compact encoding schemes for building small and fast parsers and serializers",
  "main": "index.js",
  "types": "index.d.ts",
  "files": [
    "endian.js",
    "endian.d.ts",
    "index.js",
    "index.d.ts",
    "lexint.js",
    "lexint.d.ts",
    "raw.js",
    "raw.d.ts"
  ],
  "dependencies": {
    "b4a": "^1.3.0"
  },
  "devDependencies": {
    "brittle": "^4.0.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^2.0.0"
  },
  "scripts": {
    "format": "prettier . --write",
    "test": "prettier . --check && brittle-node test.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/holepunchto/compact-encoding.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/compact-encoding/issues"
  },
  "homepage": "https://github.com/holepunchto/compact-encoding"
}
const b4a = require('b4a')

const { BE } = require('./endian')

exports = module.exports = {
  preencode(state, b) {
    state.end += b.byteLength
  },
  encode(state, b) {
    state.buffer.set(b, state.start)
    state.start += b.byteLength
  },
  decode(state) {
    const b = state.buffer.subarray(state.start, state.end)
    state.start = state.end
    return b
  }
}

const buffer = (exports.buffer = {
  preencode(state, b) {
    uint8array.preencode(state, b)
  },
  encode(state, b) {
    uint8array.encode(state, b)
  },
  decode(state) {
    const b = state.buffer.subarray(state.start)
    state.start = state.end
    return b
  }
})

exports.binary = {
  ...buffer,
  preencode(state, b) {
    if (typeof b === 'string') utf8.preencode(state, b)
    else buffer.preencode(state, b)
  },
  encode(state, b) {
    if (typeof b === 'string') utf8.encode(state, b)
    else buffer.encode(state, b)
  }
}

exports.arraybuffer = {
  preencode(state, b) {
    state.end += b.byteLength
  },
  encode(state, b) {
    const view = new Uint8Array(b)

    state.buffer.set(view, state.start)
    state.start += b.byteLength
  },
  decode(state) {
    const b = new ArrayBuffer(state.end - state.start)
    const view = new Uint8Array(b)

    view.set(state.buffer.subarray(state.start))

    state.start = state.end

    return b
  }
}

function typedarray(TypedArray, swap) {
  const n = TypedArray.BYTES_PER_ELEMENT

  return {
    preencode(state, b) {
      state.end += b.byteLength
    },
    encode(state, b) {
      const view = new Uint8Array(b.buffer, b.byteOffset, b.byteLength)

      if (BE && swap) swap(view)

      state.buffer.set(view, state.start)
      state.start += b.byteLength
    },
    decode(state) {
      let b = state.buffer.subarray(state.start)
      if (b.byteOffset % n !== 0) b = new Uint8Array(b)

      if (BE && swap) swap(b)

      state.start = state.end

      return new TypedArray(b.buffer, b.byteOffset, b.byteLength / n)
    }
  }
}

const uint8array = (exports.uint8array = typedarray(Uint8Array))
exports.uint16array = typedarray(Uint16Array, b4a.swap16)
exports.uint32array = typedarray(Uint32Array, b4a.swap32)

exports.int8array = typedarray(Int8Array)
exports.int16array = typedarray(Int16Array, b4a.swap16)
exports.int32array = typedarray(Int32Array, b4a.swap32)

exports.biguint64array = typedarray(BigUint64Array, b4a.swap64)
exports.bigint64array = typedarray(BigInt64Array, b4a.swap64)

exports.float32array = typedarray(Float32Array, b4a.swap32)
exports.float64array = typedarray(Float64Array, b4a.swap64)

function string(encoding) {
  return {
    preencode(state, s) {
      state.end += b4a.byteLength(s, encoding)
    },
    encode(state, s) {
      state.start += b4a.write(state.buffer, s, state.start, encoding)
    },
    decode(state) {
      const s = b4a.toString(state.buffer, encoding, state.start)
      state.start = state.end
      return s
    }
  }
}

const utf8 = (exports.string = exports.utf8 = string('utf-8'))
exports.ascii = string('ascii')
exports.hex = string('hex')
exports.base64 = string('base64')
exports.ucs2 = exports.utf16le = string('utf16le')

exports.array = function array(enc) {
  return {
    preencode(state, list) {
      for (const value of list) enc.preencode(state, value)
    },
    encode(state, list) {
      for (const value of list) enc.encode(state, value)
    },
    decode(state) {
      const arr = []
      while (state.start < state.end) arr.push(enc.decode(state))
      return arr
    }
  }
}

exports.json = {
  preencode(state, v) {
    utf8.preencode(state, JSON.stringify(v))
  },
  encode(state, v) {
    utf8.encode(state, JSON.stringify(v))
  },
  decode(state) {
    return JSON.parse(utf8.decode(state))
  }
}

exports.ndjson = {
  preencode(state, v) {
    utf8.preencode(state, JSON.stringify(v) + '\n')
  },
  encode(state, v) {
    utf8.encode(state, JSON.stringify(v) + '\n')
  },
  decode(state) {
    return JSON.parse(utf8.decode(state))
  }
}
const sodium = require('sodium-universal')
const c = require('compact-encoding')
const b4a = require('b4a')

// https://en.wikipedia.org/wiki/Merkle_tree#Second_preimage_attack
const LEAF_TYPE = b4a.from([0])
const PARENT_TYPE = b4a.from([1])
const ROOT_TYPE = b4a.from([2])

const HYPERCORE = b4a.from('hypercore')

exports.keyPair = function (seed) {
  // key pairs might stay around for a while, so better not to use a default slab to avoid retaining it completely
  const slab = b4a.allocUnsafeSlow(
    sodium.crypto_sign_PUBLICKEYBYTES + sodium.crypto_sign_SECRETKEYBYTES
  )
  const publicKey = slab.subarray(0, sodium.crypto_sign_PUBLICKEYBYTES)
  const secretKey = slab.subarray(sodium.crypto_sign_PUBLICKEYBYTES)

  if (seed) sodium.crypto_sign_seed_keypair(publicKey, secretKey, seed)
  else sodium.crypto_sign_keypair(publicKey, secretKey)

  return {
    publicKey,
    secretKey
  }
}

exports.validateKeyPair = function (keyPair) {
  const pk = b4a.allocUnsafe(sodium.crypto_sign_PUBLICKEYBYTES)
  sodium.crypto_sign_ed25519_sk_to_pk(pk, keyPair.secretKey)
  return b4a.equals(pk, keyPair.publicKey)
}

exports.sign = function (message, secretKey) {
  // Dedicated slab for the signature, to avoid retaining unneeded mem and for security
  const signature = b4a.allocUnsafeSlow(sodium.crypto_sign_BYTES)
  sodium.crypto_sign_detached(signature, message, secretKey)
  return signature
}

exports.verify = function (message, signature, publicKey) {
  if (signature.byteLength !== sodium.crypto_sign_BYTES) return false
  if (publicKey.byteLength !== sodium.crypto_sign_PUBLICKEYBYTES) return false
  return sodium.crypto_sign_verify_detached(signature, message, publicKey)
}

exports.encrypt = function (message, publicKey) {
  const ciphertext = b4a.alloc(message.byteLength + sodium.crypto_box_SEALBYTES)
  sodium.crypto_box_seal(ciphertext, message, publicKey)
  return ciphertext
}

exports.decrypt = function (ciphertext, keyPair) {
  if (ciphertext.byteLength < sodium.crypto_box_SEALBYTES) return null

  const plaintext = b4a.alloc(ciphertext.byteLength - sodium.crypto_box_SEALBYTES)

  if (!sodium.crypto_box_seal_open(plaintext, ciphertext, keyPair.publicKey, keyPair.secretKey)) {
    return null
  }

  return plaintext
}

exports.encryptionKeyPair = function (seed) {
  const publicKey = b4a.alloc(sodium.crypto_box_PUBLICKEYBYTES)
  const secretKey = b4a.alloc(sodium.crypto_box_SECRETKEYBYTES)

  if (seed) {
    sodium.crypto_box_seed_keypair(publicKey, secretKey, seed)
  } else {
    sodium.crypto_box_keypair(publicKey, secretKey)
  }

  return {
    publicKey,
    secretKey
  }
}

exports.data = function (data) {
  const out = b4a.allocUnsafe(32)

  sodium.crypto_generichash_batch(out, [LEAF_TYPE, c.encode(c.uint64, data.byteLength), data])

  return out
}

exports.parent = function (a, b) {
  if (a.index > b.index) {
    const tmp = a
    a = b
    b = tmp
  }

  const out = b4a.allocUnsafe(32)

  sodium.crypto_generichash_batch(out, [
    PARENT_TYPE,
    c.encode(c.uint64, a.size + b.size),
    a.hash,
    b.hash
  ])

  return out
}

exports.tree = function (roots, out) {
  const buffers = new Array(3 * roots.length + 1)
  let j = 0

  buffers[j++] = ROOT_TYPE

  for (let i = 0; i < roots.length; i++) {
    const r = roots[i]
    buffers[j++] = r.hash
    buffers[j++] = c.encode(c.uint64, r.index)
    buffers[j++] = c.encode(c.uint64, r.size)
  }

  if (!out) out = b4a.allocUnsafe(32)
  sodium.crypto_generichash_batch(out, buffers)
  return out
}

exports.hash = function (data, out) {
  if (!out) out = b4a.allocUnsafe(32)
  if (!Array.isArray(data)) data = [data]

  sodium.crypto_generichash_batch(out, data)

  return out
}

exports.randomBytes = function (n) {
  const buf = b4a.allocUnsafe(n)
  sodium.randombytes_buf(buf)
  return buf
}

exports.discoveryKey = function (key) {
  if (!key || key.byteLength !== 32) throw new Error('Must pass a 32 byte buffer')
  // Discovery keys might stay around for a while, so better not to use slab memory (for better gc)
  const digest = b4a.allocUnsafeSlow(32)
  sodium.crypto_generichash(digest, HYPERCORE, key)
  return digest
}

if (sodium.sodium_free) {
  exports.free = function (secureBuf) {
    if (secureBuf.secure) sodium.sodium_free(secureBuf)
  }
} else {
  exports.free = function () {}
}

exports.namespace = function (name, count) {
  const ids = typeof count === 'number' ? range(count) : count

  // Namespaces are long-lived, so better to use a dedicated slab
  const buf = b4a.allocUnsafeSlow(32 * ids.length)

  const list = new Array(ids.length)

  // ns is ephemeral, so default slab
  const ns = b4a.allocUnsafe(33)
  sodium.crypto_generichash(ns.subarray(0, 32), typeof name === 'string' ? b4a.from(name) : name)

  for (let i = 0; i < list.length; i++) {
    list[i] = buf.subarray(32 * i, 32 * i + 32)
    ns[32] = ids[i]
    sodium.crypto_generichash(list[i], ns)
  }

  return list
}

function range(count) {
  const arr = new Array(count)
  for (let i = 0; i < count; i++) arr[i] = i
  return arr
}
{
  "name": "hypercore-crypto",
  "version": "3.7.0",
  "description": "The crypto primitives used in hypercore, extracted into a separate module",
  "main": "index.js",
  "files": [
    "index.js"
  ],
  "dependencies": {
    "b4a": "^1.6.6",
    "compact-encoding": "^3.0.0",
    "sodium-universal": "^5.0.0"
  },
  "devDependencies": {
    "brittle": "^3.5.0",
    "lunte": "^1.0.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^2.0.0"
  },
  "scripts": {
    "format": "prettier --write .",
    "lint": "prettier --check . && lunte",
    "test": "npm run test:node && npm run test:bare",
    "test:node": "brittle-node test.js",
    "test:bare": "brittle-bare test.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/mafintosh/hypercore-crypto.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/mafintosh/hypercore-crypto/issues"
  },
  "homepage": "https://github.com/mafintosh/hypercore-crypto"
}
module.exports = require.addon.bind(require)
{
  "name": "require-addon",
  "version": "1.2.0",
  "description": "Import native addons across JavaScript runtimes",
  "exports": {
    "./package": "./package.json",
    ".": {
      "bare": "./lib/bare.js",
      "node": "./lib/node.js",
      "default": "./lib/default.js"
    }
  },
  "imports": {
    "fs": {
      "bare": "bare-fs",
      "default": "fs"
    },
    "path": {
      "bare": "bare-path",
      "default": "path"
    },
    "url": {
      "bare": "bare-url",
      "default": "url"
    }
  },
  "files": [
    "lib"
  ],
  "scripts": {
    "test": "npm run lint && npm run test:bare && npm run test:node",
    "test:bare": "bare test.js",
    "test:node": "node test.js",
    "lint": "prettier --check .",
    "format": "prettier --write ."
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/require-addon.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/require-addon/issues"
  },
  "homepage": "https://github.com/holepunchto/require-addon#readme",
  "engines": {
    "bare": ">=1.10.0"
  },
  "dependencies": {
    "bare-addon-resolve": "^1.3.0"
  },
  "devDependencies": {
    "bare-bundle": "^1.8.1",
    "bare-bundle-evaluate": "^1.1.0",
    "bare-fs": "^4.0.0",
    "bare-path": "^3.0.0",
    "bare-url": "^2.1.0",
    "brittle": "^3.7.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^2.0.0"
  }
}
require.addon = require('require-addon')
module.exports = require.addon('.', __filename)
const assert = require('assert')
const binding = require('./binding')
const { isNode } = require('which-runtime')

const OPTIONAL = Buffer.from(new ArrayBuffer(0))

module.exports = exports = { ...binding }

// memory

exports.sodium_memzero = function (buf) {
  assert(ArrayBuffer.isView(buf), 'buf must be a typed array')

  binding.sodium_memzero(buf)
}

exports.sodium_mlock = function (buf) {
  assert(ArrayBuffer.isView(buf), 'buf must be a typed array')

  const res = binding.sodium_mlock(buf)

  if (res !== 0) throw new Error('memory lock failed')
}

exports.sodium_munlock = function (buf) {
  assert(ArrayBuffer.isView(buf), 'buf must be a typed array')

  const res = binding.sodium_munlock(buf)

  if (res !== 0) throw new Error('memory unlock failed')
}

exports.sodium_malloc = function (size) {
  assert(size >= 0, 'invalid size')

  const buf = Buffer.from(binding.sodium_malloc(size))
  buf.secure = true
  return buf
}

exports.sodium_free = function (buf) {
  if (!buf || !buf.secure) return

  binding.sodium_free(buf.buffer)
}

exports.sodium_mprotect_noaccess = function (buf) {
  const res = binding.sodium_mprotect_noaccess(buf.buffer)

  if (res !== 0) throw new Error('failed to lock buffer')
}

exports.sodium_mprotect_readonly = function (buf) {
  const res = binding.sodium_mprotect_readonly(buf.buffer)

  if (res !== 0) throw new Error('failed to unlock buffer')
}

exports.sodium_mprotect_readwrite = function (buf) {
  const res = binding.sodium_mprotect_readwrite(buf.buffer)

  if (res !== 0) throw new Error('failed to unlock buffer')
}

// crypto_randombytes

exports.randombytes_buf = function (buffer) {
  assert(ArrayBuffer.isView(buffer), 'buffer must be a typed array')

  binding.randombytes_buf(buffer.buffer, buffer.byteOffset, buffer.byteLength)
}

exports.randombytes_buf_deterministic = function (buffer, seed) {
  assert(ArrayBuffer.isView(buffer), 'buffer must be a typed array')
  assert(ArrayBuffer.isView(seed), 'seed must be a typed array')
  assert(
    seed.byteLength === binding.randombytes_SEEDBYTES,
    "seed must be 'randombytes_SEEDBYTES' bytes"
  )

  binding.randombytes_buf_deterministic(
    buffer.buffer,
    buffer.byteOffset,
    buffer.byteLength,

    seed.buffer,
    seed.byteOffset,
    seed.byteLength
  )
}

// sodium_helpers

exports.sodium_memcmp = function (a, b) {
  assert(ArrayBuffer.isView(a), 'a must be a typed array')
  assert(ArrayBuffer.isView(b), 'b must be a typed array')
  assert(a.byteLength === b.byteLength, 'buffers must be of same length')

  return binding.sodium_memcmp(a, b)
}

exports.sodium_add = function (a, b) {
  assert(ArrayBuffer.isView(a), 'a must be a typed array')
  assert(ArrayBuffer.isView(b), 'b must be a typed array')
  assert(a.byteLength === b.byteLength, 'buffers must be of same length')

  binding.sodium_add(a, b)
}

exports.sodium_sub = function (a, b) {
  assert(ArrayBuffer.isView(a), 'a must be a typed array')
  assert(ArrayBuffer.isView(b), 'b must be a typed array')
  assert(a.byteLength === b.byteLength, 'buffers must be of same length')

  binding.sodium_sub(a, b)
}

exports.sodium_compare = function (a, b) {
  assert(ArrayBuffer.isView(a), 'a must be a typed array')
  assert(ArrayBuffer.isView(b), 'b must be a typed array')
  assert(a.byteLength === b.byteLength, 'buffers must be of same length')

  return binding.sodium_compare(a, b)
}

exports.sodium_is_zero = function (buffer, length) {
  if (length === undefined) length = buffer.byteLength

  assert(ArrayBuffer.isView(buffer), 'buffer must be a typed array')
  assert(length >= 0 && length <= buffer.byteLength, 'invalid length')

  return binding.sodium_is_zero(buffer, length)
}

exports.sodium_pad = function (buffer, unpaddedBuflen, blockSize) {
  assert(ArrayBuffer.isView(buffer), 'buffer must be a typed array')
  assert(unpaddedBuflen <= buffer.byteLength, 'unpadded length cannot exceed buffer length')
  assert(blockSize <= buffer.byteLength, 'block size cannot exceed buffer length')
  assert(blockSize >= 1, 'block size must be at least 1 byte')
  assert(
    buffer.byteLength >= unpaddedBuflen + (blockSize - (unpaddedBuflen % blockSize)),
    'buf not long enough'
  )

  return binding.sodium_pad(buffer, unpaddedBuflen, blockSize)
}

exports.sodium_unpad = function (buffer, paddedBuflen, blockSize) {
  assert(ArrayBuffer.isView(buffer), 'buffer must be a typed array')
  assert(paddedBuflen <= buffer.byteLength, 'unpadded length cannot exceed buffer length')
  assert(blockSize <= buffer.byteLength, 'block size cannot exceed buffer length')
  assert(blockSize >= 1, 'block size must be at least 1 byte')

  return binding.sodium_unpad(buffer, paddedBuflen, blockSize)
}

// crypto_sign

exports.crypto_sign_keypair = function (pk, sk) {
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(
    pk.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
    "pk must be 'crypto_sign_PUBLICKEYBYTES' bytes"
  )
  assert(
    sk.byteLength === binding.crypto_sign_SECRETKEYBYTES,
    "sk must be 'crypto_sign_SECRETKEYBYTES' bytes"
  )

  const res = binding.crypto_sign_keypair(pk, sk)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_sign_seed_keypair = function (pk, sk, seed) {
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(ArrayBuffer.isView(seed), 'seed must be a typed array')
  assert(
    pk.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
    "pk must be 'crypto_sign_PUBLICKEYBYTES' bytes"
  )
  assert(
    sk.byteLength === binding.crypto_sign_SECRETKEYBYTES,
    "sk must be 'crypto_sign_SECRETKEYBYTES' bytes"
  )
  assert(
    seed.byteLength === binding.crypto_sign_SEEDBYTES,
    "seed must be 'crypto_sign_SEEDBYTES' bytes"
  )

  const res = binding.crypto_sign_seed_keypair(pk, sk, seed)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_sign = function (sm, m, sk) {
  assert(ArrayBuffer.isView(sm), 'sm must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(
    sm.byteLength === binding.crypto_sign_BYTES + m.byteLength,
    "sm must be 'm.byteLength + crypto_sign_BYTES' bytes"
  )
  assert(
    sk.byteLength === binding.crypto_sign_SECRETKEYBYTES,
    "sk must be 'crypto_sign_SECRETKEYBYTES' bytes"
  )

  const res = binding.crypto_sign(sm, m, sk)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_sign_open = function (m, sm, pk) {
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(sm), 'sm must be a typed array')
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(
    sm.byteLength >= binding.crypto_sign_BYTES,
    "sm must be at least 'crypto_sign_BYTES' bytes"
  )
  assert(
    m.byteLength === sm.byteLength - binding.crypto_sign_BYTES,
    "m must be 'sm.byteLength - crypto_sign_BYTES' bytes"
  )
  assert(
    pk.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
    "pk must be 'crypto_sign_PUBLICKEYBYTES' bytes"
  )

  const res = binding.crypto_sign_open(m, sm, pk)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_sign_open = function (m, sm, pk) {
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(sm), 'sm must be a typed array')
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(
    sm.byteLength >= binding.crypto_sign_BYTES,
    "sm must be at least 'crypto_sign_BYTES' bytes"
  )
  assert(
    m.byteLength === sm.byteLength - binding.crypto_sign_BYTES,
    "m must be 'sm.byteLength - crypto_sign_BYTES' bytes"
  )
  assert(
    pk.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
    "pk must be 'crypto_sign_PUBLICKEYBYTES' bytes"
  )

  return binding.crypto_sign_open(m, sm, pk)
}

exports.crypto_sign_detached = function (sig, m, sk) {
  assert(ArrayBuffer.isView(sig), 'sig must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(sig.byteLength === binding.crypto_sign_BYTES, "sig must be 'crypto_sign_BYTES' bytes")
  assert(
    sk.byteLength === binding.crypto_sign_SECRETKEYBYTES,
    "sk must be 'crypto_sign_SECRETKEYBYTES' bytes"
  )

  const res = binding.crypto_sign_detached(sig, m, sk)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_sign_verify_detached = function (sig, m, pk) {
  assert(ArrayBuffer.isView(sig), 'sig must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(
    sig.byteLength >= binding.crypto_sign_BYTES,
    "sig must be at least 'crypto_sign_BYTES' bytes"
  )
  assert(
    pk.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
    "pk must be 'crypto_sign_PUBLICKEYBYTES' bytes"
  )

  return binding.crypto_sign_verify_detached(
    sig.buffer,
    sig.byteOffset,
    sig.byteLength,

    m.buffer,
    m.byteOffset,
    m.byteLength,

    pk.buffer,
    pk.byteOffset,
    pk.byteLength
  )
}

exports.crypto_sign_ed25519_sk_to_pk = function (pk, sk) {
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(
    pk.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
    "pk must be 'crypto_sign_PUBLICKEYBYTES' bytes"
  )
  assert(
    sk.byteLength === binding.crypto_sign_SECRETKEYBYTES,
    "sk must be 'crypto_sign_SECRETKEYBYTES' bytes"
  )

  const res = binding.crypto_sign_ed25519_sk_to_pk(pk, sk)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_sign_ed25519_pk_to_curve25519 = function (x25519pk, ed25519pk) {
  assert(ArrayBuffer.isView(x25519pk), 'x25519pk must be a typed array')
  assert(ArrayBuffer.isView(ed25519pk), 'ed25519pk must be a typed array')
  assert(
    x25519pk.byteLength === binding.crypto_box_PUBLICKEYBYTES,
    "x25519pk must be 'crypto_box_PUBLICKEYBYTES' bytes"
  )
  assert(
    ed25519pk.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
    "ed25519pk must be 'crypto_sign_PUBLICKEYBYTES' bytes"
  )

  const res = binding.crypto_sign_ed25519_pk_to_curve25519(x25519pk, ed25519pk)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_sign_ed25519_sk_to_curve25519 = function (x25519sk, ed25519sk) {
  assert(ArrayBuffer.isView(x25519sk), 'x25519sk must be a typed array')
  assert(ArrayBuffer.isView(ed25519sk), 'ed25519sk must be a typed array')
  assert(
    x25519sk.byteLength === binding.crypto_box_SECRETKEYBYTES,
    "x25519sk must be 'crypto_box_SECRETKEYBYTES' bytes"
  )

  const edLen = ed25519sk.byteLength

  assert(
    edLen === binding.crypto_sign_SECRETKEYBYTES || edLen === binding.crypto_box_SECRETKEYBYTES,
    "ed25519sk must be 'crypto_sign_SECRETKEYBYTES' or 'crypto_sign_SECRETKEYBYTES - crypto_sign_PUBLICKEYBYTES' bytes"
  )

  const res = binding.crypto_sign_ed25519_sk_to_curve25519(x25519sk, ed25519sk)

  if (res !== 0) throw new Error('status: ' + res)
}

// crypto_box

exports.crypto_box_keypair = function (pk, sk) {
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(
    pk.byteLength === binding.crypto_box_PUBLICKEYBYTES,
    "pk must be 'crypto_box_PUBLICKEYBYTES' bytes"
  )
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(
    sk.byteLength === binding.crypto_box_SECRETKEYBYTES,
    "sk must be 'crypto_box_SECRETKEYBYTES' bytes"
  )

  const res = binding.crypto_box_keypair(pk, sk)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_box_seed_keypair = function (pk, sk, seed) {
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(
    pk.byteLength === binding.crypto_box_PUBLICKEYBYTES,
    "pk must be 'crypto_box_PUBLICKEYBYTES' bytes"
  )
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(
    sk.byteLength === binding.crypto_box_SECRETKEYBYTES,
    "sk must be 'crypto_box_SECRETKEYBYTES' bytes"
  )
  assert(ArrayBuffer.isView(seed), 'seed must be a typed array')
  assert(
    seed.byteLength === binding.crypto_box_SEEDBYTES,
    "seed must be 'crypto_box_SEEDBYTES' bytes"
  )

  const res = binding.crypto_box_seed_keypair(pk, sk, seed)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_box_easy = function (c, m, n, pk, sk) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(
    c.byteLength === m.byteLength + exports.crypto_box_MACBYTES,
    "c must be 'm.byteLength + crypto_box_MACBYTES' bytes"
  )
  assert(n.byteLength === exports.crypto_box_NONCEBYTES, "n must be 'crypto_box_NONCEBYTES' bytes")
  assert(
    pk.byteLength === exports.crypto_box_PUBLICKEYBYTES,
    "pk must be 'crypto_box_PUBLICKEYBYTES' bytes"
  )
  assert(
    sk.byteLength === exports.crypto_box_SECRETKEYBYTES,
    "sk must be 'crypto_box_SECRETKEYBYTES' bytes"
  )

  const res = binding.crypto_box_easy(c, m, n, pk, sk)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_box_detached = function (c, mac, m, n, pk, sk) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(mac), 'mac must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(mac.byteLength === exports.crypto_box_MACBYTES, "mac must be 'crypto_box_MACBYTES' bytes")
  assert(n.byteLength === exports.crypto_box_NONCEBYTES, "n must be 'crypto_box_NONCEBYTES' bytes")
  assert(
    pk.byteLength === exports.crypto_box_PUBLICKEYBYTES,
    "pk must be 'crypto_box_PUBLICKEYBYTES' bytes"
  )
  assert(
    sk.byteLength === exports.crypto_box_SECRETKEYBYTES,
    "sk must be 'crypto_box_SECRETKEYBYTES' bytes"
  )

  const res = binding.crypto_box_detached(c, mac, m, n, pk, sk)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_box_open_easy = function (m, c, n, pk, sk) {
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(
    c.byteLength >= exports.crypto_box_MACBYTES,
    "c must be at least 'crypto_box_MACBYTES' bytes"
  )
  assert(
    m.byteLength === c.byteLength - exports.crypto_box_MACBYTES,
    "m must be 'c.byteLength - crypto_box_MACBYTES' bytes"
  )
  assert(n.byteLength === exports.crypto_box_NONCEBYTES, "n must be 'crypto_box_NONCEBYTES' bytes")
  assert(
    pk.byteLength === exports.crypto_box_PUBLICKEYBYTES,
    "pk must be 'crypto_box_PUBLICKEYBYTES' bytes"
  )
  assert(
    sk.byteLength === exports.crypto_box_SECRETKEYBYTES,
    "sk must be 'crypto_box_SECRETKEYBYTES' bytes"
  )

  return binding.crypto_box_open_easy(m, c, n, pk, sk)
}

exports.crypto_box_open_detached = function (m, c, mac, n, pk, sk) {
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(mac), 'mac must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(m.byteLength === c.byteLength, "m must be 'c.byteLength' bytes")
  assert(mac.byteLength === exports.crypto_box_MACBYTES, "mac must be 'crypto_box_MACBYTES' bytes")
  assert(n.byteLength === exports.crypto_box_NONCEBYTES, "n must be 'crypto_box_NONCEBYTES' bytes")
  assert(
    pk.byteLength === exports.crypto_box_PUBLICKEYBYTES,
    "pk must be 'crypto_box_PUBLICKEYBYTES' bytes"
  )
  assert(
    sk.byteLength === exports.crypto_box_SECRETKEYBYTES,
    "sk must be 'crypto_box_SECRETKEYBYTES' bytes"
  )

  return binding.crypto_box_open_detached(m, c, mac, n, pk, sk)
}

exports.crypto_box_seal = function (c, m, pk) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(
    c.byteLength === m.byteLength + exports.crypto_box_SEALBYTES,
    "c must be 'm.byteLength + crypto_box_SEALBYTES' bytes"
  )
  assert(
    pk.byteLength === exports.crypto_box_PUBLICKEYBYTES,
    "pk must be 'crypto_box_PUBLICKEYBYTES' bytes"
  )

  const res = binding.crypto_box_seal(c, m, pk)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_box_seal_open = function (m, c, pk, sk) {
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(
    c.byteLength >= exports.crypto_box_SEALBYTES,
    "c must be at least 'crypto_box_SEALBYTES' bytes"
  )
  assert(
    m.byteLength === c.byteLength - exports.crypto_box_SEALBYTES,
    "m must be 'c.byteLength - crypto_box_SEALBYTES' bytes"
  )
  assert(
    pk.byteLength === exports.crypto_box_PUBLICKEYBYTES,
    "pk must be 'crypto_box_PUBLICKEYBYTES' bytes"
  )
  assert(
    sk.byteLength === exports.crypto_box_SECRETKEYBYTES,
    "sk must be 'crypto_box_SECRETKEYBYTES' bytes"
  )

  return binding.crypto_box_seal_open(
    m.buffer,
    m.byteOffset,
    m.byteLength,

    c.buffer,
    c.byteOffset,
    c.byteLength,

    pk.buffer,
    pk.byteOffset,
    pk.byteLength,

    sk.buffer,
    sk.byteOffset,
    sk.byteLength
  )
}

// crypto_secretbox

exports.crypto_secretbox_easy = function (c, m, n, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    c.byteLength === m.byteLength + binding.crypto_secretbox_MACBYTES,
    "c must be 'm.byteLength + crypto_secretbox_MACBYTES' bytes"
  )
  assert(
    n.byteLength === binding.crypto_secretbox_NONCEBYTES,
    "n must be 'crypto_secretbox_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_secretbox_KEYBYTES,
    "k must be 'crypto_secretbox_KEYBYTES' bytes"
  )

  const res = binding.crypto_secretbox_easy(c, m, n, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_secretbox_open_easy = function (m, c, n, k) {
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    c.byteLength >= binding.crypto_secretbox_MACBYTES,
    "c must be at least 'crypto_secretbox_MACBYTES' bytes"
  )
  assert(
    m.byteLength === c.byteLength - binding.crypto_secretbox_MACBYTES,
    "m must be 'c.byteLength - crypto_secretbox_MACBYTES' bytes"
  )
  assert(
    n.byteLength === binding.crypto_secretbox_NONCEBYTES,
    "n must be 'crypto_secretbox_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_secretbox_KEYBYTES,
    "k must be 'crypto_secretbox_KEYBYTES' bytes"
  )

  return binding.crypto_secretbox_open_easy(m, c, n, k)
}

exports.crypto_secretbox_detached = function (c, mac, m, n, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(mac), 'mac must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(
    mac.byteLength === binding.crypto_secretbox_MACBYTES,
    "mac must be 'crypto_secretbox_MACBYTES' bytes"
  )
  assert(
    n.byteLength === binding.crypto_secretbox_NONCEBYTES,
    "n must be 'crypto_secretbox_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_secretbox_KEYBYTES,
    "k must be 'crypto_secretbox_KEYBYTES' bytes"
  )

  const res = binding.crypto_secretbox_detached(c, mac, m, n, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_secretbox_open_detached = function (m, c, mac, n, k) {
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(mac), 'mac must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(m.byteLength === c.byteLength, "m must be 'c.byteLength' bytes")
  assert(
    mac.byteLength === binding.crypto_secretbox_MACBYTES,
    "mac must be 'crypto_secretbox_MACBYTES' bytes"
  )
  assert(
    n.byteLength === binding.crypto_secretbox_NONCEBYTES,
    "n must be 'crypto_secretbox_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_secretbox_KEYBYTES,
    "k must be 'crypto_secretbox_KEYBYTES' bytes"
  )

  return binding.crypto_secretbox_open_detached(m, c, mac, n, k)
}

// crypto_generichash

exports.crypto_generichash = function (output, input, key) {
  if (!key) key = OPTIONAL

  assert(ArrayBuffer.isView(output), 'output must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(
    output.byteLength >= binding.crypto_generichash_BYTES_MIN &&
      output.byteLength <= binding.crypto_generichash_BYTES_MAX,
    'output must be between crypto_generichash_BYTES_MIN and crypto_generichash_BYTES_MAX bytes'
  )

  if (key !== OPTIONAL) {
    assert(ArrayBuffer.isView(key), 'key must be a typed array')
    assert(
      key.byteLength >= binding.crypto_generichash_KEYBYTES_MIN &&
        key.byteLength <= binding.crypto_generichash_KEYBYTES_MAX,
      'key must be between crypto_generichash_KEYBYTES_MIN and crypto_generichash_KEYBYTES_MAX bytes'
    )
  }

  const res = binding.crypto_generichash(
    output.buffer,
    output.byteOffset,
    output.byteLength,

    input.buffer,
    input.byteOffset,
    input.byteLength,

    key.buffer,
    key.byteOffset,
    key.byteLength
  )

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_generichash_batch = function (output, batch, key) {
  assert(ArrayBuffer.isView(output), 'output must be a typed array')

  if (isNode || batch.length < 4) {
    const res = binding.crypto_generichash_batch(output, batch, !!key, key || OPTIONAL)
    if (res !== 0) throw new Error('status: ' + res)
  } else {
    const state = Buffer.alloc(binding.crypto_generichash_STATEBYTES)

    exports.crypto_generichash_init(state, key, output.byteLength)

    for (const buf of batch) {
      exports.crypto_generichash_update(state, buf)
    }

    exports.crypto_generichash_final(state, output)
  }
}

exports.crypto_generichash_keygen = function (key) {
  assert(ArrayBuffer.isView(key), 'key must be a typed array')
  assert(
    key.byteLength === binding.crypto_generichash_KEYBYTES,
    "key must be 'crypto_generichash_KEYBYTES' bytes"
  )

  const res = binding.crypto_generichash_keygen(key.buffer, key.byteOffset, key.byteLength)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_generichash_init = function (state, key, outputLength) {
  if (!key) key = OPTIONAL

  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_generichash_STATEBYTES,
    "state must be 'crypto_generichash_STATEBYTES' bytes"
  )

  const res = binding.crypto_generichash_init(
    state.buffer,
    state.byteOffset,
    state.byteLength,

    key.buffer,
    key.byteOffset,
    key.byteLength,

    outputLength
  )

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_generichash_update = function (state, input) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(
    state.byteLength === binding.crypto_generichash_STATEBYTES,
    "state must be 'crypto_generichash_STATEBYTES' bytes"
  )

  const res = binding.crypto_generichash_update(
    state.buffer,
    state.byteOffset,
    state.byteLength,

    input.buffer,
    input.byteOffset,
    input.byteLength
  )

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_generichash_final = function (state, output) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(ArrayBuffer.isView(output), 'output must be a typed array')
  assert(
    state.byteLength === binding.crypto_generichash_STATEBYTES,
    "state must be 'crypto_generichash_STATEBYTES' bytes"
  )

  const res = binding.crypto_generichash_final(
    state.buffer,
    state.byteOffset,
    state.byteLength,

    output.buffer,
    output.byteOffset,
    output.byteLength
  )

  if (res !== 0) throw new Error('status: ' + res)
}

// secretstream

exports.crypto_secretstream_xchacha20poly1305_keygen = function (k) {
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_secretstream_xchacha20poly1305_KEYBYTES,
    "k must be 'crypto_secretstream_xchacha20poly1305_KEYBYTES' bytes"
  )

  binding.crypto_secretstream_xchacha20poly1305_keygen(k.buffer, k.byteOffset, k.byteLength)
}

exports.crypto_secretstream_xchacha20poly1305_init_push = function (state, header, k) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(ArrayBuffer.isView(header), 'header must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    state.byteLength === binding.crypto_secretstream_xchacha20poly1305_STATEBYTES,
    "state must be 'crypto_secretstream_xchacha20poly1305_STATEBYTES' bytes"
  )
  assert(
    header.byteLength === binding.crypto_secretstream_xchacha20poly1305_HEADERBYTES,
    "header must be 'crypto_secretstream_xchacha20poly1305_HEADERBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_secretstream_xchacha20poly1305_KEYBYTES,
    "k must be 'crypto_secretstream_xchacha20poly1305_KEYBYTES' bytes"
  )

  const res = binding.crypto_secretstream_xchacha20poly1305_init_push(
    state.buffer,
    state.byteOffset,
    state.byteLength,

    header.buffer,
    header.byteOffset,
    header.byteLength,

    k.buffer,
    k.byteOffset,
    k.byteLength
  )

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_secretstream_xchacha20poly1305_init_pull = function (state, header, k) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(ArrayBuffer.isView(header), 'header must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    state.byteLength === binding.crypto_secretstream_xchacha20poly1305_STATEBYTES,
    "state must be 'crypto_secretstream_xchacha20poly1305_STATEBYTES' bytes"
  )
  assert(
    header.byteLength === binding.crypto_secretstream_xchacha20poly1305_HEADERBYTES,
    "header must be 'crypto_secretstream_xchacha20poly1305_HEADERBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_secretstream_xchacha20poly1305_KEYBYTES,
    "k must be 'crypto_secretstream_xchacha20poly1305_KEYBYTES' bytes"
  )

  const res = binding.crypto_secretstream_xchacha20poly1305_init_pull(
    state.buffer,
    state.byteOffset,
    state.byteLength,

    header.buffer,
    header.byteOffset,
    header.byteLength,

    k.buffer,
    k.byteOffset,
    k.byteLength
  )

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_secretstream_xchacha20poly1305_push = function (state, c, m, ad, tag) {
  if (!ad) ad = OPTIONAL

  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(
    state.byteLength === binding.crypto_secretstream_xchacha20poly1305_STATEBYTES,
    "state must be 'crypto_secretstream_xchacha20poly1305_STATEBYTES' bytes"
  )
  assert(
    c.byteLength === m.byteLength + binding.crypto_secretstream_xchacha20poly1305_ABYTES,
    "c must be 'm.byteLength + crypto_secretstream_xchacha20poly1305_ABYTES' bytes"
  )

  const res = binding.crypto_secretstream_xchacha20poly1305_push(
    state.buffer,
    state.byteOffset,
    state.byteLength,

    c.buffer,
    c.byteOffset,
    c.byteLength,

    m.buffer,
    m.byteOffset,
    m.byteLength,

    ad.buffer,
    ad.byteOffset,
    ad.byteLength,

    tag
  )

  if (res < 0) throw new Error('push failed')

  return res
}

exports.crypto_secretstream_xchacha20poly1305_pull = function (state, m, tag, c, ad) {
  if (!ad) ad = OPTIONAL

  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_secretstream_xchacha20poly1305_STATEBYTES,
    "state must be 'crypto_secretstream_xchacha20poly1305_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(tag), 'tag must be a typed array')
  assert(tag.byteLength === 1, 'tag must be 1 byte')

  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(
    c.byteLength >= binding.crypto_secretstream_xchacha20poly1305_ABYTES,
    "c must be at least 'crypto_secretstream_xchacha20poly1305_ABYTES' bytes"
  )
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(
    m.byteLength === c.byteLength - binding.crypto_secretstream_xchacha20poly1305_ABYTES,
    "m must be 'c.byteLength - crypto_secretstream_xchacha20poly1305_ABYTES' bytes"
  )

  const res = binding.crypto_secretstream_xchacha20poly1305_pull(
    state.buffer,
    state.byteOffset,
    state.byteLength,

    m.buffer,
    m.byteOffset,
    m.byteLength,

    tag.buffer,
    tag.byteOffset,
    tag.byteLength,

    c.buffer,
    c.byteOffset,
    c.byteLength,

    ad.buffer,
    ad.byteOffset,
    ad.byteLength
  )

  if (res < 0) throw new Error('pull failed')

  return res
}

exports.crypto_secretstream_xchacha20poly1305_rekey = function (state) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_secretstream_xchacha20poly1305_STATEBYTES,
    "state must be 'crypto_secretstream_xchacha20poly1305_STATEBYTES' bytes"
  )

  binding.crypto_secretstream_xchacha20poly1305_rekey(
    state.buffer,
    state.byteOffset,
    state.byteLength
  )
}

// crypto_stream

exports.crypto_stream = function (c, n, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    n.byteLength === binding.crypto_stream_NONCEBYTES,
    "n must be 'crypto_stream_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_KEYBYTES,
    "k must be 'crypto_stream_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream(c, n, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_xor = function (c, m, n, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(
    n.byteLength === binding.crypto_stream_NONCEBYTES,
    "n must be 'crypto_stream_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_KEYBYTES,
    "k must be 'crypto_stream_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_xor(
    c.buffer,
    c.byteOffset,
    c.byteLength,

    m.buffer,
    m.byteOffset,
    m.byteLength,

    n.buffer,
    n.byteOffset,
    n.byteLength,

    k.buffer,
    k.byteOffset,
    k.byteLength
  )

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_chacha20 = function (c, n, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    n.byteLength === binding.crypto_stream_chacha20_NONCEBYTES,
    "n must be 'crypto_stream_chacha20_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_chacha20_KEYBYTES,
    "k must be 'crypto_stream_chacha20_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_chacha20(c, n, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_chacha20_xor = function (c, m, n, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(
    n.byteLength === binding.crypto_stream_chacha20_NONCEBYTES,
    "n must be 'crypto_stream_chacha20_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_chacha20_KEYBYTES,
    "k must be 'crypto_stream_chacha20_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_chacha20_xor(c, m, n, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_chacha20_xor_ic = function (c, m, n, ic, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(
    n.byteLength === binding.crypto_stream_chacha20_NONCEBYTES,
    "n must be 'crypto_stream_chacha20_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_chacha20_KEYBYTES,
    "k must be 'crypto_stream_chacha20_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_chacha20_xor_ic(c, m, n, ic, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_chacha20_ietf = function (c, n, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    n.byteLength === binding.crypto_stream_chacha20_ietf_NONCEBYTES,
    "n must be 'crypto_stream_chacha20_ietf_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_chacha20_ietf_KEYBYTES,
    "k must be 'crypto_stream_chacha20_ietf_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_chacha20_ietf(c, n, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_chacha20_ietf_xor = function (c, m, n, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(
    n.byteLength === binding.crypto_stream_chacha20_ietf_NONCEBYTES,
    "n must be 'crypto_stream_chacha20_ietf_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_chacha20_ietf_KEYBYTES,
    "k must be 'crypto_stream_chacha20_ietf_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_chacha20_ietf_xor(c, m, n, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_chacha20_ietf_xor_ic = function (c, m, n, ic, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(
    n.byteLength === binding.crypto_stream_chacha20_ietf_NONCEBYTES,
    "n must be 'crypto_stream_chacha20_ietf_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_chacha20_ietf_KEYBYTES,
    "k must be 'crypto_stream_chacha20_ietf_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_chacha20_ietf_xor_ic(c, m, n, ic, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_xchacha20 = function (c, n, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    n.byteLength === binding.crypto_stream_xchacha20_NONCEBYTES,
    "n must be 'crypto_stream_xchacha20_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_xchacha20_KEYBYTES,
    "k must be 'crypto_stream_xchacha20_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_xchacha20(c, n, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_xchacha20_xor = function (c, m, n, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(
    n.byteLength === binding.crypto_stream_xchacha20_NONCEBYTES,
    "n must be 'crypto_stream_xchacha20_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_xchacha20_KEYBYTES,
    "k must be 'crypto_stream_xchacha20_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_xchacha20_xor(c, m, n, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_xchacha20_xor_ic = function (c, m, n, ic, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(
    n.byteLength === binding.crypto_stream_xchacha20_NONCEBYTES,
    "n must be 'crypto_stream_xchacha20_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_xchacha20_KEYBYTES,
    "k must be 'crypto_stream_xchacha20_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_xchacha20_xor_ic(c, m, n, ic, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_salsa20 = function (c, n, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    n.byteLength === binding.crypto_stream_salsa20_NONCEBYTES,
    "n must be 'crypto_stream_salsa20_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_salsa20_KEYBYTES,
    "k must be 'crypto_stream_salsa20_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_salsa20(c, n, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_salsa20_xor = function (c, m, n, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(
    n.byteLength === binding.crypto_stream_salsa20_NONCEBYTES,
    "n must be 'crypto_stream_salsa20_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_salsa20_KEYBYTES,
    "k must be 'crypto_stream_salsa20_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_salsa20_xor(c, m, n, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_salsa20_xor_ic = function (c, m, n, ic, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(
    n.byteLength === binding.crypto_stream_salsa20_NONCEBYTES,
    "n must be 'crypto_stream_salsa20_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_salsa20_KEYBYTES,
    "k must be 'crypto_stream_salsa20_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_salsa20_xor_ic(c, m, n, ic, k)

  if (res !== 0) throw new Error('status: ' + res)
}

// crypto_auth

exports.crypto_auth = function (out, input, k) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(out.byteLength === binding.crypto_auth_BYTES, "out must be 'crypto_auth_BYTES' bytes")
  assert(k.byteLength === binding.crypto_auth_KEYBYTES, "k must be 'crypto_auth_KEYBYTES' bytes")

  const res = binding.crypto_auth(out, input, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_auth_verify = function (h, input, k) {
  assert(ArrayBuffer.isView(h), 'h must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(h.byteLength === binding.crypto_auth_BYTES, "h must be 'crypto_auth_BYTES' bytes")
  assert(k.byteLength === binding.crypto_auth_KEYBYTES, "k must be 'crypto_auth_KEYBYTES' bytes")

  return binding.crypto_auth_verify(h, input, k)
}

// crypto_onetimeauth

exports.crypto_onetimeauth = function (out, input, k) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    out.byteLength === binding.crypto_onetimeauth_BYTES,
    "out must be 'crypto_onetimeauth_BYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_onetimeauth_KEYBYTES,
    "k must be 'crypto_onetimeauth_KEYBYTES' bytes"
  )

  const res = binding.crypto_onetimeauth(out, input, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_onetimeauth_init = function (state, k) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    state.byteLength === binding.crypto_onetimeauth_STATEBYTES,
    "state must be 'crypto_onetimeauth_STATEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_onetimeauth_KEYBYTES,
    "k must be 'crypto_onetimeauth_KEYBYTES' bytes"
  )

  const res = binding.crypto_onetimeauth_init(state, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_onetimeauth_update = function (state, input) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(
    state.byteLength === binding.crypto_onetimeauth_STATEBYTES,
    "state must be 'crypto_onetimeauth_STATEBYTES' bytes"
  )

  const res = binding.crypto_onetimeauth_update(state, input)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_onetimeauth_final = function (state, out) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(
    state.byteLength === binding.crypto_onetimeauth_STATEBYTES,
    "state must be 'crypto_onetimeauth_STATEBYTES' bytes"
  )
  assert(
    out.byteLength === binding.crypto_onetimeauth_BYTES,
    "out must be 'crypto_onetimeauth_BYTES' bytes"
  )

  const res = binding.crypto_onetimeauth_final(state, out)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_onetimeauth_verify = function (h, input, k) {
  assert(ArrayBuffer.isView(h), 'h must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    h.byteLength === binding.crypto_onetimeauth_BYTES,
    "h must be 'crypto_onetimeauth_BYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_onetimeauth_KEYBYTES,
    "k must be 'crypto_onetimeauth_KEYBYTES' bytes"
  )

  return binding.crypto_onetimeauth_verify(h, input, k)
}

// crypto_pwhash

exports.crypto_pwhash = function (out, passwd, salt, opslimit, memlimit, alg) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(ArrayBuffer.isView(salt), 'salt must be a typed array')
  assert(
    out.byteLength >= binding.crypto_pwhash_BYTES_MIN,
    "out must be at least 'crypto_pwhash_BYTES_MIN' bytes"
  )
  assert(
    out.byteLength <= binding.crypto_pwhash_BYTES_MAX,
    "out must be at most 'crypto_pwhash_BYTES_MAX' bytes"
  )
  assert(
    salt.byteLength === binding.crypto_pwhash_SALTBYTES,
    "salt must be 'crypto_pwhash_SALTBYTES' bytes"
  )
  assert(
    opslimit >= binding.crypto_pwhash_OPSLIMIT_MIN,
    "opslimit must be at least 'crypto_pwhash_OPSLIMIT_MIN'"
  )
  assert(
    opslimit <= binding.crypto_pwhash_OPSLIMIT_MAX,
    "opslimit must be at most 'crypto_pwhash_OPSLIMIT_MAX'"
  )
  assert(
    memlimit >= binding.crypto_pwhash_MEMLIMIT_MIN,
    "memlimit must be at least 'crypto_pwhash_MEMLIMIT_MIN'"
  )
  assert(
    memlimit <= binding.crypto_pwhash_MEMLIMIT_MAX,
    "memlimit must be at most 'crypto_pwhash_MEMLIMIT_MAX'"
  )
  assert(alg >= 1 && alg <= 2, 'alg must be either Argon2i 1.3 or Argon2id 1.3')

  const res = binding.crypto_pwhash(out, passwd, salt, opslimit, memlimit, alg)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_pwhash_async = function (
  out,
  passwd,
  salt,
  opslimit,
  memlimit,
  alg,
  callback = undefined
) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(ArrayBuffer.isView(salt), 'salt must be a typed array')
  assert(
    out.byteLength >= binding.crypto_pwhash_BYTES_MIN,
    "out must be at least 'crypto_pwhash_BYTES_MIN' bytes"
  )
  assert(
    out.byteLength <= binding.crypto_pwhash_BYTES_MAX,
    "out must be at most 'crypto_pwhash_BYTES_MAX' bytes"
  )
  assert(
    salt.byteLength === binding.crypto_pwhash_SALTBYTES,
    "salt must be 'crypto_pwhash_SALTBYTES' bytes"
  )
  assert(
    opslimit >= binding.crypto_pwhash_OPSLIMIT_MIN,
    "opslimit must be at least 'crypto_pwhash_OPSLIMIT_MIN'"
  )
  assert(
    opslimit <= binding.crypto_pwhash_OPSLIMIT_MAX,
    "opslimit must be at most 'crypto_pwhash_OPSLIMIT_MAX'"
  )
  assert(
    memlimit >= binding.crypto_pwhash_MEMLIMIT_MIN,
    "memlimit must be at least 'crypto_pwhash_MEMLIMIT_MIN'"
  )
  assert(
    memlimit <= binding.crypto_pwhash_MEMLIMIT_MAX,
    "memlimit must be at most 'crypto_pwhash_MEMLIMIT_MAX'"
  )
  assert(alg >= 1 && alg <= 2, 'alg must be either Argon2i 1.3 or Argon2id 1.3')

  const [done, promise] = checkStatus(callback)

  binding.crypto_pwhash_async(
    out.buffer,
    out.byteOffset,
    out.byteLength,

    passwd.buffer,
    passwd.byteOffset,
    passwd.byteLength,

    salt.buffer,
    salt.byteOffset,
    salt.byteLength,

    opslimit,
    memlimit,
    alg,

    done
  )

  return promise
}

exports.crypto_pwhash_str = function (out, passwd, opslimit, memlimit) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(
    out.byteLength === binding.crypto_pwhash_STRBYTES,
    "out must be 'crypto_pwhash_STRBYTES' bytes"
  )
  assert(typeof opslimit === 'number', 'opslimit must be a number')
  assert(
    opslimit >= binding.crypto_pwhash_OPSLIMIT_MIN,
    "opslimit must be at least 'crypto_pwhash_OPSLIMIT_MIN'"
  )
  assert(
    opslimit <= binding.crypto_pwhash_OPSLIMIT_MAX,
    "opslimit must be at most 'crypto_pwhash_OPSLIMIT_MAX'"
  )
  assert(typeof memlimit === 'number', 'memlimit must be a number')
  assert(
    memlimit >= binding.crypto_pwhash_MEMLIMIT_MIN,
    "memlimit must be at least 'crypto_pwhash_MEMLIMIT_MIN'"
  )
  assert(
    memlimit <= binding.crypto_pwhash_MEMLIMIT_MAX,
    "memlimit must be at most 'crypto_pwhash_MEMLIMIT_MAX'"
  )

  const res = binding.crypto_pwhash_str(out, passwd, opslimit, memlimit)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_pwhash_str_async = function (out, passwd, opslimit, memlimit, callback = undefined) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(
    out.byteLength === binding.crypto_pwhash_STRBYTES,
    "out must be 'crypto_pwhash_STRBYTES' bytes"
  )
  assert(passwd.byteLength > 0, 'passwd must not be empty')
  assert(typeof opslimit === 'number', 'opslimit must be a number')
  assert(
    opslimit >= binding.crypto_pwhash_OPSLIMIT_MIN,
    "opslimit must be at least 'crypto_pwhash_OPSLIMIT_MIN'"
  )
  assert(
    opslimit <= binding.crypto_pwhash_OPSLIMIT_MAX,
    "opslimit must be at most 'crypto_pwhash_OPSLIMIT_MAX'"
  )
  assert(typeof memlimit === 'number', 'memlimit must be a number')
  assert(
    memlimit >= binding.crypto_pwhash_MEMLIMIT_MIN,
    "memlimit must be at least 'crypto_pwhash_MEMLIMIT_MIN'"
  )
  assert(
    memlimit <= binding.crypto_pwhash_MEMLIMIT_MAX,
    "memlimit must be at most 'crypto_pwhash_MEMLIMIT_MAX'"
  )

  const [done, promise] = checkStatus(callback)

  binding.crypto_pwhash_str_async(
    out.buffer,
    out.byteOffset,
    out.byteLength,

    passwd.buffer,
    passwd.byteOffset,
    passwd.byteLength,

    opslimit,
    memlimit,

    done
  )

  return promise
}

exports.crypto_pwhash_str_verify = function (str, passwd) {
  assert(ArrayBuffer.isView(str), 'str must be a typed array')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(
    str.byteLength === binding.crypto_pwhash_STRBYTES,
    "str must be 'crypto_pwhash_STRBYTES' bytes"
  )

  return binding.crypto_pwhash_str_verify(str, passwd)
}

exports.crypto_pwhash_str_verify_async = function (str, passwd, callback = undefined) {
  assert(ArrayBuffer.isView(str), 'str must be a typed array')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(
    str.byteLength === binding.crypto_pwhash_STRBYTES,
    "str must be 'crypto_pwhash_STRBYTES' bytes"
  )
  assert(passwd.byteLength > 0, 'passwd must not be empty')

  const [done, promise] = checkStatus(callback, true)

  binding.crypto_pwhash_str_verify_async(
    str.buffer,
    str.byteOffset,
    str.byteLength,

    passwd.buffer,
    passwd.byteOffset,
    passwd.byteLength,

    done
  )

  return promise
}

exports.crypto_pwhash_str_needs_rehash = function (str, opslimit, memlimit) {
  assert(ArrayBuffer.isView(str), 'str must be a typed array')
  assert(
    str.byteLength === binding.crypto_pwhash_STRBYTES,
    "str must be 'crypto_pwhash_STRBYTES' bytes"
  )
  assert(
    opslimit >= binding.crypto_pwhash_OPSLIMIT_MIN,
    "opslimit must be at least 'crypto_pwhash_OPSLIMIT_MIN'"
  )
  assert(
    opslimit <= binding.crypto_pwhash_OPSLIMIT_MAX,
    "opslimit must be at most 'crypto_pwhash_OPSLIMIT_MAX'"
  )
  assert(
    memlimit >= binding.crypto_pwhash_MEMLIMIT_MIN,
    "memlimit must be at least 'crypto_pwhash_MEMLIMIT_MIN'"
  )
  assert(
    memlimit <= binding.crypto_pwhash_MEMLIMIT_MAX,
    "memlimit must be at most 'crypto_pwhash_MEMLIMIT_MAX'"
  )

  return binding.crypto_pwhash_str_needs_rehash(str, opslimit, memlimit)
}

exports.crypto_pwhash_scryptsalsa208sha256 = function (out, passwd, salt, opslimit, memlimit) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(ArrayBuffer.isView(salt), 'salt must be a typed array')
  assert(
    out.byteLength >= binding.crypto_pwhash_scryptsalsa208sha256_BYTES_MIN,
    "out must be at least 'crypto_pwhash_scryptsalsa208sha256_BYTES_MIN' bytes"
  )
  assert(
    out.byteLength <= binding.crypto_pwhash_scryptsalsa208sha256_BYTES_MAX,
    "out must be at most 'crypto_pwhash_scryptsalsa208sha256_BYTES_MAX' bytes"
  )
  assert(
    salt.byteLength === binding.crypto_pwhash_scryptsalsa208sha256_SALTBYTES,
    "salt must be 'crypto_pwhash_scryptsalsa208sha256_SALTBYTES' bytes"
  )
  assert(
    opslimit >= binding.crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MIN,
    "opslimit must be at least 'crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MIN'"
  )
  assert(
    opslimit <= binding.crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MAX,
    "opslimit must be at most 'crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MAX'"
  )
  assert(
    memlimit >= binding.crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MIN,
    "memlimit must be at least 'crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MIN'"
  )
  assert(
    memlimit <= binding.crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MAX,
    "memlimit must be at most 'crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MAX'"
  )

  const res = binding.crypto_pwhash_scryptsalsa208sha256(out, passwd, salt, opslimit, memlimit)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_pwhash_scryptsalsa208sha256_async = function (
  out,
  passwd,
  salt,
  opslimit,
  memlimit,
  callback = undefined
) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(ArrayBuffer.isView(salt), 'salt must be a typed array')
  assert(
    out.byteLength >= binding.crypto_pwhash_scryptsalsa208sha256_BYTES_MIN,
    "out must be at least 'crypto_pwhash_scryptsalsa208sha256_BYTES_MIN' bytes"
  )
  assert(
    out.byteLength <= binding.crypto_pwhash_scryptsalsa208sha256_BYTES_MAX,
    "out must be at most 'crypto_pwhash_scryptsalsa208sha256_BYTES_MAX' bytes"
  )
  assert(passwd.byteLength > 0, 'passwd must not be empty')
  assert(
    salt.byteLength === binding.crypto_pwhash_scryptsalsa208sha256_SALTBYTES,
    "salt must be 'crypto_pwhash_scryptsalsa208sha256_SALTBYTES' bytes"
  )
  assert(
    opslimit >= binding.crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MIN,
    "opslimit must be at least 'crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MIN'"
  )
  assert(
    opslimit <= binding.crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MAX,
    "opslimit must be at most 'crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MAX'"
  )
  assert(
    memlimit >= binding.crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MIN,
    "memlimit must be at least 'crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MIN'"
  )
  assert(
    memlimit <= binding.crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MAX,
    "memlimit must be at most 'crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MAX'"
  )

  const [done, promise] = checkStatus(callback)

  binding.crypto_pwhash_scryptsalsa208sha256_async(
    out.buffer,
    out.byteOffset,
    out.byteLength,

    passwd.buffer,
    passwd.byteOffset,
    passwd.byteLength,

    salt.buffer,
    salt.byteOffset,
    salt.byteLength,

    opslimit,
    memlimit,

    done
  )

  return promise
}

exports.crypto_pwhash_scryptsalsa208sha256_str_async = function (
  out,
  passwd,
  opslimit,
  memlimit,
  callback = undefined
) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(
    out.byteLength === binding.crypto_pwhash_scryptsalsa208sha256_STRBYTES,
    "out must be 'crypto_pwhash_scryptsalsa208sha256_STRBYTES' bytes"
  )
  assert(passwd.byteLength > 0, 'passwd must not be empty')
  assert(
    opslimit >= binding.crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MIN,
    "opslimit must be at least 'crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MIN'"
  )
  assert(
    opslimit <= binding.crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MAX,
    "opslimit must be at most 'crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MAX'"
  )
  assert(
    memlimit >= binding.crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MIN,
    "memlimit must be at least 'crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MIN'"
  )
  assert(
    memlimit <= binding.crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MAX,
    "memlimit must be at most 'crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MAX'"
  )

  const [done, promise] = checkStatus(callback)

  binding.crypto_pwhash_scryptsalsa208sha256_str_async(
    out.buffer,
    out.byteOffset,
    out.byteLength,

    passwd.buffer,
    passwd.byteOffset,
    passwd.byteLength,

    opslimit,
    memlimit,

    done
  )

  return promise
}

exports.crypto_pwhash_scryptsalsa208sha256_str = function (out, passwd, opslimit, memlimit) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(
    out.byteLength === binding.crypto_pwhash_scryptsalsa208sha256_STRBYTES,
    "out must be 'crypto_pwhash_scryptsalsa208sha256_STRBYTES' bytes"
  )
  assert(passwd.byteLength > 0, 'passwd must not be empty')
  assert(
    opslimit >= binding.crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MIN,
    "opslimit must be at least 'crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MIN'"
  )
  assert(
    opslimit <= binding.crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MAX,
    "opslimit must be at most 'crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MAX'"
  )
  assert(
    memlimit >= binding.crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MIN,
    "memlimit must be at least 'crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MIN'"
  )
  assert(
    memlimit <= binding.crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MAX,
    "memlimit must be at most 'crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MAX'"
  )

  const res = binding.crypto_pwhash_scryptsalsa208sha256_str(out, passwd, opslimit, memlimit)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_pwhash_scryptsalsa208sha256_str_verify_async = function (
  str,
  passwd,
  callback = undefined
) {
  assert(ArrayBuffer.isView(str), 'str must be a typed array')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(
    str.byteLength === binding.crypto_pwhash_scryptsalsa208sha256_STRBYTES,
    "str must be 'crypto_pwhash_scryptsalsa208sha256_STRBYTES' bytes"
  )
  assert(passwd.byteLength > 0, 'passwd must not be empty')

  const [done, promise] = checkStatus(callback, true)

  binding.crypto_pwhash_scryptsalsa208sha256_str_verify_async(
    str.buffer,
    str.byteOffset,
    str.byteLength,

    passwd.buffer,
    passwd.byteOffset,
    passwd.byteLength,

    done
  )

  return promise
}

exports.crypto_pwhash_scryptsalsa208sha256_str_verify = function (str, passwd) {
  assert(ArrayBuffer.isView(str), 'str must be a typed array')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(
    str.byteLength === binding.crypto_pwhash_scryptsalsa208sha256_STRBYTES,
    "str must be 'crypto_pwhash_scryptsalsa208sha256_STRBYTES' bytes"
  )
  assert(passwd.byteLength > 0, 'passwd must not be empty')

  return binding.crypto_pwhash_scryptsalsa208sha256_str_verify(str, passwd)
}

exports.crypto_pwhash_scryptsalsa208sha256_str_needs_rehash = function (str, opslimit, memlimit) {
  assert(ArrayBuffer.isView(str), 'str must be a typed array')
  assert(
    str.byteLength === binding.crypto_pwhash_scryptsalsa208sha256_STRBYTES,
    "str must be 'crypto_pwhash_scryptsalsa208sha256_STRBYTES' bytes"
  )
  assert(
    opslimit >= binding.crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MIN,
    "opslimit must be at least 'crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MIN'"
  )
  assert(
    opslimit <= binding.crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MAX,
    "opslimit must be at most 'crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MAX'"
  )
  assert(
    memlimit >= binding.crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MIN,
    "memlimit must be at least 'crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MIN'"
  )
  assert(
    memlimit <= binding.crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MAX,
    "memlimit must be at most 'crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MAX'"
  )

  return binding.crypto_pwhash_scryptsalsa208sha256_str_needs_rehash(str, opslimit, memlimit)
}

// crypto_kx

exports.crypto_kx_keypair = function (pk, sk) {
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(
    pk.byteLength === binding.crypto_kx_PUBLICKEYBYTES,
    "pk must be 'crypto_kx_PUBLICKEYBYTES' bytes"
  )
  assert(
    sk.byteLength === binding.crypto_kx_SECRETKEYBYTES,
    "sk must be 'crypto_kx_SECRETKEYBYTES' bytes"
  )

  const res = binding.crypto_kx_keypair(pk, sk)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_kx_seed_keypair = function (pk, sk, seed) {
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(ArrayBuffer.isView(seed), 'seed must be a typed array')
  assert(
    pk.byteLength === binding.crypto_kx_PUBLICKEYBYTES,
    "pk must be 'crypto_kx_PUBLICKEYBYTES' bytes"
  )
  assert(
    sk.byteLength === binding.crypto_kx_SECRETKEYBYTES,
    "sk must be 'crypto_kx_SECRETKEYBYTES' bytes"
  )
  assert(
    seed.byteLength === binding.crypto_kx_SEEDBYTES,
    "seed must be 'crypto_kx_SEEDBYTES' bytes"
  )

  const res = binding.crypto_kx_seed_keypair(pk, sk, seed)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_kx_client_session_keys = function (rx, tx, clientPk, clientSk, serverPk) {
  if (!rx) rx = undefined
  if (!tx) tx = undefined

  assert(rx || tx, 'at least one session key must be specified')

  if (rx) {
    assert(ArrayBuffer.isView(rx), 'rx must be a typed array')
    assert(
      rx.byteLength === binding.crypto_kx_SESSIONKEYBYTES,
      "rx must be 'crypto_kx_SESSIONKEYBYTES' bytes"
    )
  }

  if (tx) {
    assert(ArrayBuffer.isView(tx), 'tx must be a typed array')
    assert(
      tx.byteLength === binding.crypto_kx_SESSIONKEYBYTES,
      "tx must be 'crypto_kx_SESSIONKEYBYTES' bytes"
    )
  }

  assert(ArrayBuffer.isView(clientPk), 'clientPk must be a typed array')
  assert(ArrayBuffer.isView(clientSk), 'clientSk must be a typed array')
  assert(ArrayBuffer.isView(serverPk), 'serverPk must be a typed array')
  assert(
    clientPk.byteLength === binding.crypto_kx_PUBLICKEYBYTES,
    "clientPk must be 'crypto_kx_PUBLICKEYBYTES' bytes"
  )
  assert(
    clientSk.byteLength === binding.crypto_kx_SECRETKEYBYTES,
    "clientSk must be 'crypto_kx_SECRETKEYBYTES' bytes"
  )
  assert(
    serverPk.byteLength === binding.crypto_kx_PUBLICKEYBYTES,
    "serverPk must be 'crypto_kx_PUBLICKEYBYTES' bytes"
  )

  const res = binding.crypto_kx_client_session_keys(rx, tx, clientPk, clientSk, serverPk)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_kx_server_session_keys = function (rx, tx, serverPk, serverSk, clientPk) {
  if (!rx) rx = undefined
  if (!tx) tx = undefined

  assert(rx || tx, 'at least one session key must be specified')

  if (rx) {
    assert(ArrayBuffer.isView(rx), 'rx must be a typed array')
    assert(
      rx.byteLength === binding.crypto_kx_SESSIONKEYBYTES,
      "rx must be 'crypto_kx_SESSIONKEYBYTES' bytes"
    )
  }

  if (tx) {
    assert(ArrayBuffer.isView(tx), 'tx must be a typed array')
    assert(
      tx.byteLength === binding.crypto_kx_SESSIONKEYBYTES,
      "tx must be 'crypto_kx_SESSIONKEYBYTES' bytes"
    )
  }

  assert(ArrayBuffer.isView(serverPk), 'serverPk must be a typed array')
  assert(ArrayBuffer.isView(serverSk), 'serverSk must be a typed array')
  assert(ArrayBuffer.isView(clientPk), 'clientPk must be a typed array')
  assert(
    serverPk.byteLength === binding.crypto_kx_PUBLICKEYBYTES,
    "serverPk must be 'crypto_kx_PUBLICKEYBYTES' bytes"
  )
  assert(
    serverSk.byteLength === binding.crypto_kx_SECRETKEYBYTES,
    "serverSk must be 'crypto_kx_SECRETKEYBYTES' bytes"
  )
  assert(
    clientPk.byteLength === binding.crypto_kx_PUBLICKEYBYTES,
    "clientPk must be 'crypto_kx_PUBLICKEYBYTES' bytes"
  )

  const res = binding.crypto_kx_server_session_keys(rx, tx, serverPk, serverSk, clientPk)

  if (res !== 0) throw new Error('status: ' + res)
}

// crypto_scalarmult

exports.crypto_scalarmult_base = function (q, n) {
  assert(ArrayBuffer.isView(q), 'q must be a typed array')
  assert(
    q.byteLength === binding.crypto_scalarmult_BYTES,
    "q must be 'crypto_scalarmult_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.crypto_scalarmult_SCALARBYTES,
    "n must be 'crypto_scalarmult_SCALARBYTES' bytes"
  )

  const res = binding.crypto_scalarmult_base(q, n)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_scalarmult = function (q, n, p) {
  assert(ArrayBuffer.isView(q), 'q must be a typed array')
  assert(
    q.byteLength === binding.crypto_scalarmult_BYTES,
    "q must be 'crypto_scalarmult_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.crypto_scalarmult_SCALARBYTES,
    "n must be 'crypto_scalarmult_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(p), 'p must be a typed array')
  assert(
    p.byteLength === binding.crypto_scalarmult_BYTES,
    "p must be 'crypto_scalarmult_BYTES' bytes"
  )

  const res = binding.crypto_scalarmult(q, n, p)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_scalarmult_ed25519_base = function (q, n) {
  assert(ArrayBuffer.isView(q), 'q must be a typed array')
  assert(
    q.byteLength === binding.crypto_scalarmult_ed25519_BYTES,
    "q must be 'crypto_scalarmult_ed25519_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.crypto_scalarmult_ed25519_SCALARBYTES,
    "n must be 'crypto_scalarmult_ed25519_SCALARBYTES' bytes"
  )

  const res = binding.crypto_scalarmult_ed25519_base(q, n)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_scalarmult_ed25519 = function (q, n, p) {
  assert(ArrayBuffer.isView(q), 'q must be a typed array')
  assert(
    q.byteLength === binding.crypto_scalarmult_ed25519_BYTES,
    "q must be 'crypto_scalarmult_ed25519_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.crypto_scalarmult_ed25519_SCALARBYTES,
    "n must be 'crypto_scalarmult_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(p), 'p must be a typed array')
  assert(
    p.byteLength === binding.crypto_scalarmult_ed25519_BYTES,
    "p must be 'crypto_scalarmult_ed25519_BYTES' bytes"
  )

  const res = binding.crypto_scalarmult_ed25519(q, n, p)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_core_ed25519_is_valid_point = function (p) {
  assert(ArrayBuffer.isView(p), 'p must be a typed array')
  assert(
    p.byteLength === binding.crypto_core_ed25519_BYTES,
    "p must be 'crypto_core_ed25519_BYTES' bytes"
  )

  return binding.crypto_core_ed25519_is_valid_point(p)
}

exports.crypto_core_ed25519_from_uniform = function (p, r) {
  assert(ArrayBuffer.isView(p), 'p must be a typed array')
  assert(
    p.byteLength === binding.crypto_core_ed25519_BYTES,
    "p must be 'crypto_core_ed25519_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(r), 'r must be a typed array')
  assert(
    r.byteLength === binding.crypto_core_ed25519_UNIFORMBYTES,
    "r must be 'crypto_core_ed25519_UNIFORMBYTES' bytes"
  )

  const res = binding.crypto_core_ed25519_from_uniform(p, r)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_scalarmult_ed25519_base_noclamp = function (q, n) {
  assert(ArrayBuffer.isView(q), 'q must be a typed array')
  assert(
    q.byteLength === binding.crypto_scalarmult_ed25519_BYTES,
    "q must be 'crypto_scalarmult_ed25519_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.crypto_scalarmult_ed25519_SCALARBYTES,
    "n must be 'crypto_scalarmult_ed25519_SCALARBYTES' bytes"
  )

  const res = binding.crypto_scalarmult_ed25519_base_noclamp(q, n)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_scalarmult_ed25519_noclamp = function (q, n, p) {
  assert(ArrayBuffer.isView(q), 'q must be a typed array')
  assert(
    q.byteLength === binding.crypto_scalarmult_ed25519_BYTES,
    "q must be 'crypto_scalarmult_ed25519_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.crypto_scalarmult_ed25519_SCALARBYTES,
    "n must be 'crypto_scalarmult_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(p), 'p must be a typed array')
  assert(
    p.byteLength === binding.crypto_scalarmult_ed25519_BYTES,
    "p must be 'crypto_scalarmult_ed25519_BYTES' bytes"
  )

  const res = binding.crypto_scalarmult_ed25519_noclamp(q, n, p)

  if (res !== 0) throw new Error('status: ' + res)
}

// crypto_core

exports.crypto_core_ed25519_add = function (r, p, q) {
  assert(ArrayBuffer.isView(r), 'r must be a typed array')
  assert(
    r.byteLength === binding.crypto_core_ed25519_BYTES,
    "r must be 'crypto_core_ed25519_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(p), 'p must be a typed array')
  assert(
    p.byteLength === binding.crypto_core_ed25519_BYTES,
    "p must be 'crypto_core_ed25519_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(q), 'q must be a typed array')
  assert(
    q.byteLength === binding.crypto_core_ed25519_BYTES,
    "q must be 'crypto_core_ed25519_BYTES' bytes"
  )

  const res = binding.crypto_core_ed25519_add(r, p, q)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_core_ed25519_sub = function (r, p, q) {
  assert(ArrayBuffer.isView(r), 'r must be a typed array')
  assert(
    r.byteLength === binding.crypto_core_ed25519_BYTES,
    "r must be 'crypto_core_ed25519_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(p), 'p must be a typed array')
  assert(
    p.byteLength === binding.crypto_core_ed25519_BYTES,
    "p must be 'crypto_core_ed25519_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(q), 'q must be a typed array')
  assert(
    q.byteLength === binding.crypto_core_ed25519_BYTES,
    "q must be 'crypto_core_ed25519_BYTES' bytes"
  )

  const res = binding.crypto_core_ed25519_sub(r, p, q)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_core_ed25519_scalar_random = function (r) {
  assert(ArrayBuffer.isView(r), 'r must be a typed array')
  assert(
    r.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "r must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )

  binding.crypto_core_ed25519_scalar_random(r)
}

exports.crypto_core_ed25519_scalar_reduce = function (r, s) {
  assert(ArrayBuffer.isView(r), 'r must be a typed array')
  assert(
    r.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "r must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(s), 's must be a typed array')
  assert(
    s.byteLength === binding.crypto_core_ed25519_NONREDUCEDSCALARBYTES,
    "s must be 'crypto_core_ed25519_NONREDUCEDSCALARBYTES' bytes"
  )

  binding.crypto_core_ed25519_scalar_reduce(r, s)
}

exports.crypto_core_ed25519_scalar_invert = function (recip, s) {
  assert(ArrayBuffer.isView(recip), 'recip must be a typed array')
  assert(
    recip.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "recip must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(s), 's must be a typed array')
  assert(
    s.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "s must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )

  binding.crypto_core_ed25519_scalar_invert(recip, s)
}

exports.crypto_core_ed25519_scalar_negate = function (neg, s) {
  assert(ArrayBuffer.isView(neg), 'neg must be a typed array')
  assert(
    neg.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "neg must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(s), 's must be a typed array')
  assert(
    s.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "s must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )

  binding.crypto_core_ed25519_scalar_negate(neg, s)
}

exports.crypto_core_ed25519_scalar_complement = function (comp, s) {
  assert(ArrayBuffer.isView(comp), 'comp must be a typed array')
  assert(
    comp.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "comp must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(s), 's must be a typed array')
  assert(
    s.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "s must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )

  binding.crypto_core_ed25519_scalar_complement(comp, s)
}

exports.crypto_core_ed25519_scalar_add = function (z, x, y) {
  assert(ArrayBuffer.isView(z), 'z must be a typed array')
  assert(
    z.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "z must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(x), 'x must be a typed array')
  assert(
    x.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "x must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(y), 'y must be a typed array')
  assert(
    y.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "y must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )

  binding.crypto_core_ed25519_scalar_add(z, x, y)
}

exports.crypto_core_ed25519_scalar_sub = function (z, x, y) {
  assert(ArrayBuffer.isView(z), 'z must be a typed array')
  assert(
    z.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "z must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(x), 'x must be a typed array')
  assert(
    x.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "x must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(y), 'y must be a typed array')
  assert(
    y.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "y must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )

  binding.crypto_core_ed25519_scalar_sub(z, x, y)
}

// crypto_shorthash

exports.crypto_shorthash = function (out, input, k) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(
    out.byteLength === binding.crypto_shorthash_BYTES,
    "out must be 'crypto_shorthash_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_shorthash_KEYBYTES,
    "k must be 'crypto_shorthash_KEYBYTES' bytes"
  )

  const res = binding.crypto_shorthash(out, input, k)

  if (res !== 0) throw new Error('status: ' + res)
}

// crypto_kdf

exports.crypto_kdf_keygen = function (key) {
  assert(ArrayBuffer.isView(key), 'key must be a typed array')
  assert(key.byteLength === binding.crypto_kdf_KEYBYTES, "key must be 'crypto_kdf_KEYBYTES' bytes")

  binding.crypto_kdf_keygen(key)
}

exports.crypto_kdf_derive_from_key = function (subkey, subkeyId, ctx, key) {
  assert(ArrayBuffer.isView(subkey), 'subkey must be a typed array')
  assert(
    subkey.byteLength >= binding.crypto_kdf_BYTES_MIN,
    "subkey must be at least 'crypto_kdf_BYTES_MIN' bytes"
  )
  assert(
    subkey.byteLength <= binding.crypto_kdf_BYTES_MAX,
    "subkey must be at most 'crypto_kdf_BYTES_MAX' bytes"
  )
  assert(ArrayBuffer.isView(ctx), 'ctx must be a typed array')
  assert(
    ctx.byteLength === binding.crypto_kdf_CONTEXTBYTES,
    "ctx must be 'crypto_kdf_CONTEXTBYTES' bytes"
  )
  assert(ArrayBuffer.isView(key), 'key must be a typed array')
  assert(key.byteLength === binding.crypto_kdf_KEYBYTES, "key must be 'crypto_kdf_KEYBYTES' bytes")

  const res = binding.crypto_kdf_derive_from_key(subkey, subkeyId, ctx, key)

  if (res !== 0) throw new Error('status: ' + res)
}

// crypto_hash

exports.crypto_hash = function (out, input) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(out.byteLength === binding.crypto_hash_BYTES, "out must be 'crypto_hash_BYTES' bytes")

  const res = binding.crypto_hash(out, input)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_hash_sha256 = function (out, input) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(
    out.byteLength === binding.crypto_hash_sha256_BYTES,
    "out must be 'crypto_hash_sha256_BYTES' bytes"
  )

  const res = binding.crypto_hash_sha256(out, input)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_hash_sha256_init = function (state) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_hash_sha256_STATEBYTES,
    "state must be 'crypto_hash_sha256_STATEBYTES' bytes"
  )

  const res = binding.crypto_hash_sha256_init(state)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_hash_sha256_update = function (state, input) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(
    state.byteLength === binding.crypto_hash_sha256_STATEBYTES,
    "state must be 'crypto_hash_sha256_STATEBYTES' bytes"
  )

  const res = binding.crypto_hash_sha256_update(state, input)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_hash_sha256_final = function (state, out) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_hash_sha256_STATEBYTES,
    "state must be 'crypto_hash_sha256_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(
    out.byteLength === binding.crypto_hash_sha256_BYTES,
    "out must be 'crypto_hash_sha256_BYTES' bytes"
  )

  const res = binding.crypto_hash_sha256_final(state, out)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_hash_sha512 = function (out, input) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(
    out.byteLength === binding.crypto_hash_sha512_BYTES,
    "out must be 'crypto_hash_sha512_BYTES' bytes"
  )

  const res = binding.crypto_hash_sha512(out, input)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_hash_sha512_init = function (state) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_hash_sha512_STATEBYTES,
    "state must be 'crypto_hash_sha512_STATEBYTES' bytes"
  )

  const res = binding.crypto_hash_sha512_init(state)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_hash_sha512_update = function (state, input) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(
    state.byteLength === binding.crypto_hash_sha512_STATEBYTES,
    "state must be 'crypto_hash_sha512_STATEBYTES' bytes"
  )

  const res = binding.crypto_hash_sha512_update(state, input)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_hash_sha512_final = function (state, out) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_hash_sha512_STATEBYTES,
    "state must be 'crypto_hash_sha512_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(
    out.byteLength === binding.crypto_hash_sha512_BYTES,
    "out must be 'crypto_hash_sha512_BYTES' bytes"
  )

  const res = binding.crypto_hash_sha512_final(state, out)

  if (res !== 0) throw new Error('status: ' + res)
}

// crypto_aead

exports.crypto_aead_xchacha20poly1305_ietf_keygen = function (k) {
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_aead_xchacha20poly1305_ietf_KEYBYTES,
    "k must be 'crypto_aead_xchacha20poly1305_ietf_KEYBYTES' bytes"
  )

  binding.crypto_aead_xchacha20poly1305_ietf_keygen(k)
}

exports.crypto_aead_xchacha20poly1305_ietf_encrypt = function (c, m, ad, nsec, npub, k) {
  if (!ad) ad = undefined

  assert(nsec === null, 'nsec must always be set to null')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(
    c.byteLength === m.byteLength + binding.crypto_aead_xchacha20poly1305_ietf_ABYTES,
    "c must be 'm.byteLength + crypto_aead_xchacha20poly1305_ietf_ABYTES' bytes"
  )
  assert(c.byteLength <= 0xffffffff, 'c.byteLength must be a 32bit integer')
  assert(ArrayBuffer.isView(npub), 'npub must be a typed array')
  assert(
    npub.byteLength === binding.crypto_aead_xchacha20poly1305_ietf_NPUBBYTES,
    "npub must be 'crypto_aead_xchacha20poly1305_ietf_NPUBBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_aead_xchacha20poly1305_ietf_KEYBYTES,
    "k must be 'crypto_aead_xchacha20poly1305_ietf_KEYBYTES' bytes"
  )

  const res = binding.crypto_aead_xchacha20poly1305_ietf_encrypt(c, m, ad, npub, k)

  if (res < 0) throw new Error('could not encrypt data')

  return res
}

exports.crypto_aead_xchacha20poly1305_ietf_decrypt = function (m, nsec, c, ad, npub, k) {
  if (!ad) ad = undefined

  assert(nsec === null, 'nsec must always be set to null')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(
    m.byteLength === c.byteLength - binding.crypto_aead_xchacha20poly1305_ietf_ABYTES,
    "m must be 'c.byteLength - crypto_aead_xchacha20poly1305_ietf_ABYTES' bytes"
  )
  assert(m.byteLength <= 0xffffffff, 'm.byteLength must be a 32bit integer')
  assert(ArrayBuffer.isView(npub), 'npub must be a typed array')
  assert(
    npub.byteLength === binding.crypto_aead_xchacha20poly1305_ietf_NPUBBYTES,
    "npub must be 'crypto_aead_xchacha20poly1305_ietf_NPUBBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_aead_xchacha20poly1305_ietf_KEYBYTES,
    "k must be 'crypto_aead_xchacha20poly1305_ietf_KEYBYTES' bytes"
  )

  const res = binding.crypto_aead_xchacha20poly1305_ietf_decrypt(m, c, ad, npub, k)

  if (res < 0) throw new Error('could not verify data')

  return res
}

exports.crypto_aead_xchacha20poly1305_ietf_encrypt_detached = function (
  c,
  mac,
  m,
  ad,
  nsec,
  npub,
  k
) {
  if (!ad) ad = undefined

  assert(nsec === null, 'nsec must always be set to null')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(ArrayBuffer.isView(mac), 'mac must be a typed array')
  assert(
    mac.byteLength === binding.crypto_aead_xchacha20poly1305_ietf_ABYTES,
    "mac must be 'crypto_aead_xchacha20poly1305_ietf_ABYTES' bytes"
  )
  assert(ArrayBuffer.isView(npub), 'npub must be a typed array')
  assert(
    npub.byteLength === binding.crypto_aead_xchacha20poly1305_ietf_NPUBBYTES,
    "npub must be 'crypto_aead_xchacha20poly1305_ietf_NPUBBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_aead_xchacha20poly1305_ietf_KEYBYTES,
    "k must be 'crypto_aead_xchacha20poly1305_ietf_KEYBYTES' bytes"
  )

  const res = binding.crypto_aead_xchacha20poly1305_ietf_encrypt_detached(c, mac, m, ad, npub, k)

  if (res < 0) throw new Error('could not encrypt data')

  return res
}

exports.crypto_aead_xchacha20poly1305_ietf_decrypt_detached = function (
  m,
  nsec,
  c,
  mac,
  ad,
  npub,
  k
) {
  if (!ad) ad = undefined

  assert(nsec === null, 'nsec must always be set to null')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(m.byteLength === c.byteLength, "m must be 'c.byteLength' bytes")
  assert(ArrayBuffer.isView(mac), 'mac must be a typed array')
  assert(
    mac.byteLength === binding.crypto_aead_xchacha20poly1305_ietf_ABYTES,
    "mac must be 'crypto_aead_xchacha20poly1305_ietf_ABYTES' bytes"
  )
  assert(ArrayBuffer.isView(npub), 'npub must be a typed array')
  assert(
    npub.byteLength === binding.crypto_aead_xchacha20poly1305_ietf_NPUBBYTES,
    "npub must be 'crypto_aead_xchacha20poly1305_ietf_NPUBBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_aead_xchacha20poly1305_ietf_KEYBYTES,
    "k must be 'crypto_aead_xchacha20poly1305_ietf_KEYBYTES' bytes"
  )

  const res = binding.crypto_aead_xchacha20poly1305_ietf_decrypt_detached(m, c, mac, ad, npub, k)

  if (res !== 0) throw new Error('could not verify data')
}

exports.crypto_aead_chacha20poly1305_ietf_keygen = function (k) {
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_aead_chacha20poly1305_ietf_KEYBYTES,
    "k must be 'crypto_aead_chacha20poly1305_ietf_KEYBYTES' bytes"
  )

  binding.crypto_aead_chacha20poly1305_ietf_keygen(k)
}

exports.crypto_aead_chacha20poly1305_ietf_encrypt = function (c, m, ad, nsec, npub, k) {
  if (!ad) ad = undefined

  assert(nsec === null, 'nsec must always be set to null')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(
    c.byteLength === m.byteLength + binding.crypto_aead_chacha20poly1305_ietf_ABYTES,
    "c must be 'm.byteLength + crypto_aead_chacha20poly1305_ietf_ABYTES' bytes"
  )
  assert(c.byteLength <= 0xffffffff, 'c.byteLength must be a 32bit integer')
  assert(ArrayBuffer.isView(npub), 'npub must be a typed array')
  assert(
    npub.byteLength === binding.crypto_aead_chacha20poly1305_ietf_NPUBBYTES,
    "npub must be 'crypto_aead_chacha20poly1305_ietf_NPUBBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_aead_chacha20poly1305_ietf_KEYBYTES,
    "k must be 'crypto_aead_chacha20poly1305_ietf_KEYBYTES' bytes"
  )

  const res = binding.crypto_aead_chacha20poly1305_ietf_encrypt(c, m, ad, npub, k)

  if (res < 0) throw new Error('could not encrypt data')

  return res
}

exports.crypto_aead_chacha20poly1305_ietf_decrypt = function (m, nsec, c, ad, npub, k) {
  if (!ad) ad = undefined

  assert(nsec === null, 'nsec must always be set to null')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(
    m.byteLength === c.byteLength - binding.crypto_aead_chacha20poly1305_ietf_ABYTES,
    "m must be 'c.byteLength - crypto_aead_chacha20poly1305_ietf_ABYTES' bytes"
  )
  assert(m.byteLength <= 0xffffffff, 'm.byteLength must be a 32bit integer')
  assert(ArrayBuffer.isView(npub), 'npub must be a typed array')
  assert(
    npub.byteLength === binding.crypto_aead_chacha20poly1305_ietf_NPUBBYTES,
    "npub must be 'crypto_aead_chacha20poly1305_ietf_NPUBBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_aead_chacha20poly1305_ietf_KEYBYTES,
    "k must be 'crypto_aead_chacha20poly1305_ietf_KEYBYTES' bytes"
  )

  const res = binding.crypto_aead_chacha20poly1305_ietf_decrypt(m, c, ad, npub, k)

  if (res < 0) throw new Error('could not verify data')

  return res
}

exports.crypto_aead_chacha20poly1305_ietf_encrypt_detached = function (
  c,
  mac,
  m,
  ad,
  nsec,
  npub,
  k
) {
  if (!ad) ad = undefined

  assert(nsec === null, 'nsec must always be set to null')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(ArrayBuffer.isView(mac), 'mac must be a typed array')
  assert(
    mac.byteLength === binding.crypto_aead_chacha20poly1305_ietf_ABYTES,
    "mac must be 'crypto_aead_chacha20poly1305_ietf_ABYTES' bytes"
  )
  assert(ArrayBuffer.isView(npub), 'npub must be a typed array')
  assert(
    npub.byteLength === binding.crypto_aead_chacha20poly1305_ietf_NPUBBYTES,
    "npub must be 'crypto_aead_chacha20poly1305_ietf_NPUBBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_aead_chacha20poly1305_ietf_KEYBYTES,
    "k must be 'crypto_aead_chacha20poly1305_ietf_KEYBYTES' bytes"
  )

  const res = binding.crypto_aead_chacha20poly1305_ietf_encrypt_detached(c, mac, m, ad, npub, k)

  if (res < 0) throw new Error('could not encrypt data')

  return res
}

exports.crypto_aead_chacha20poly1305_ietf_decrypt_detached = function (
  m,
  nsec,
  c,
  mac,
  ad,
  npub,
  k
) {
  if (!ad) ad = undefined

  assert(nsec === null, 'nsec must always be set to null')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(m.byteLength === c.byteLength, "m must be 'c.byteLength' bytes")
  assert(ArrayBuffer.isView(mac), 'mac must be a typed array')
  assert(
    mac.byteLength === binding.crypto_aead_chacha20poly1305_ietf_ABYTES,
    "mac must be 'crypto_aead_chacha20poly1305_ietf_ABYTES' bytes"
  )
  assert(ArrayBuffer.isView(npub), 'npub must be a typed array')
  assert(
    npub.byteLength === binding.crypto_aead_chacha20poly1305_ietf_NPUBBYTES,
    "npub must be 'crypto_aead_chacha20poly1305_ietf_NPUBBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_aead_chacha20poly1305_ietf_KEYBYTES,
    "k must be 'crypto_aead_chacha20poly1305_ietf_KEYBYTES' bytes"
  )

  const res = binding.crypto_aead_chacha20poly1305_ietf_decrypt_detached(m, c, mac, ad, npub, k)

  if (res !== 0) throw new Error('could not verify data')
}

// crypto_stream

exports.crypto_stream_xor_wrap_init = function (state, n, k) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.sn_crypto_stream_xor_STATEBYTES,
    "state must be 'sn_crypto_stream_xor_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.crypto_stream_NONCEBYTES,
    "n must be 'crypto_stream_NONCEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_stream_KEYBYTES,
    "k must be 'crypto_stream_KEYBYTES' bytes"
  )

  binding.crypto_stream_xor_wrap_init(state, n, k)
}

exports.crypto_stream_xor_wrap_update = function (state, c, m) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.sn_crypto_stream_xor_STATEBYTES,
    "state must be 'sn_crypto_stream_xor_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")

  binding.crypto_stream_xor_wrap_update(state, c, m)
}

exports.crypto_stream_xor_wrap_final = function (state) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.sn_crypto_stream_xor_STATEBYTES,
    "state must be 'sn_crypto_stream_xor_STATEBYTES' bytes"
  )

  binding.crypto_stream_xor_wrap_final(state)
}

exports.crypto_stream_chacha20_xor_wrap_init = function (state, n, k) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_stream_chacha20_xor_STATEBYTES,
    "state must be 'crypto_stream_chacha20_xor_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.crypto_stream_chacha20_NONCEBYTES,
    "n must be 'crypto_stream_chacha20_NONCEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_stream_chacha20_KEYBYTES,
    "k must be 'crypto_stream_chacha20_KEYBYTES' bytes"
  )

  binding.crypto_stream_chacha20_xor_wrap_init(state, n, k)
}

exports.crypto_stream_chacha20_xor_wrap_update = function (state, c, m) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_stream_chacha20_xor_STATEBYTES,
    "state must be 'crypto_stream_chacha20_xor_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")

  binding.crypto_stream_chacha20_xor_wrap_update(state, c, m)
}

exports.crypto_stream_chacha20_xor_wrap_final = function (state) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_stream_chacha20_xor_STATEBYTES,
    "state must be 'crypto_stream_chacha20_xor_STATEBYTES' bytes"
  )

  binding.crypto_stream_chacha20_xor_wrap_final(state)
}

exports.crypto_stream_chacha20_ietf_xor_wrap_init = function (state, n, k) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_stream_chacha20_ietf_xor_STATEBYTES,
    "state must be 'crypto_stream_chacha20_ietf_xor_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.crypto_stream_chacha20_ietf_NONCEBYTES,
    "n must be 'crypto_stream_chacha20_ietf_NONCEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_stream_chacha20_ietf_KEYBYTES,
    "k must be 'crypto_stream_chacha20_ietf_KEYBYTES' bytes"
  )

  binding.crypto_stream_chacha20_ietf_xor_wrap_init(state, n, k)
}

exports.crypto_stream_chacha20_ietf_xor_wrap_update = function (state, c, m) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_stream_chacha20_ietf_xor_STATEBYTES,
    "state must be 'crypto_stream_chacha20_ietf_xor_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")

  binding.crypto_stream_chacha20_ietf_xor_wrap_update(state, c, m)
}

exports.crypto_stream_chacha20_ietf_xor_wrap_final = function (state) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_stream_chacha20_ietf_xor_STATEBYTES,
    "state must be 'crypto_stream_chacha20_ietf_xor_STATEBYTES' bytes"
  )

  binding.crypto_stream_chacha20_ietf_xor_wrap_final(state)
}

exports.crypto_stream_xchacha20_xor_wrap_init = function (state, n, k) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_stream_xchacha20_xor_STATEBYTES,
    "state must be 'crypto_stream_xchacha20_xor_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.crypto_stream_xchacha20_NONCEBYTES,
    "n must be 'crypto_stream_xchacha20_NONCEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_stream_xchacha20_KEYBYTES,
    "k must be 'crypto_stream_xchacha20_KEYBYTES' bytes"
  )

  binding.crypto_stream_xchacha20_xor_wrap_init(state, n, k)
}

exports.crypto_stream_xchacha20_xor_wrap_update = function (state, c, m) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_stream_xchacha20_xor_STATEBYTES,
    "state must be 'crypto_stream_xchacha20_xor_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")

  binding.crypto_stream_xchacha20_xor_wrap_update(state, c, m)
}

exports.crypto_stream_xchacha20_xor_wrap_final = function (state) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_stream_xchacha20_xor_STATEBYTES,
    "state must be 'crypto_stream_xchacha20_xor_STATEBYTES' bytes"
  )

  binding.crypto_stream_xchacha20_xor_wrap_final(state)
}

exports.crypto_stream_salsa20_xor_wrap_init = function (state, n, k) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_stream_salsa20_xor_STATEBYTES,
    "state must be 'crypto_stream_salsa20_xor_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.crypto_stream_salsa20_NONCEBYTES,
    "n must be 'crypto_stream_salsa20_NONCEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_stream_salsa20_KEYBYTES,
    "k must be 'crypto_stream_salsa20_KEYBYTES' bytes"
  )

  binding.crypto_stream_salsa20_xor_wrap_init(state, n, k)
}

exports.crypto_stream_salsa20_xor_wrap_update = function (state, c, m) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_stream_salsa20_xor_STATEBYTES,
    "state must be 'crypto_stream_salsa20_xor_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")

  binding.crypto_stream_salsa20_xor_wrap_update(state, c, m)
}

exports.crypto_stream_salsa20_xor_wrap_final = function (state) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_stream_salsa20_xor_STATEBYTES,
    "state must be 'crypto_stream_salsa20_xor_STATEBYTES' bytes"
  )

  binding.crypto_stream_salsa20_xor_wrap_final(state)
}

// experimental

exports.extension_tweak_ed25519_base = function (n, p, ns) {
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "n must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(p), 'p must be a typed array')
  assert(
    p.byteLength === binding.extension_tweak_ed25519_BYTES,
    "p must be 'extension_tweak_ed25519_BYTES' bytes"
  )

  binding.extension_tweak_ed25519_base(n, p, ns)
}

exports.extension_tweak_ed25519_sign_detached = function (sig, m, scalar, pk) {
  assert(ArrayBuffer.isView(sig), 'sig must be a typed array')
  assert(sig.byteLength === binding.crypto_sign_BYTES, "sig must be 'crypto_sign_BYTES' bytes")
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(scalar), 'scalar must be a typed array')
  assert(
    scalar.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "scalar must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )

  if (pk) {
    assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
    assert(
      pk.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
      "pk must be 'crypto_sign_PUBLICKEYBYTES' bytes"
    )
  }

  const res = binding.extension_tweak_ed25519_sign_detached(sig, m, scalar, pk)

  if (res !== 0) throw new Error('failed to compute signature')
}

exports.extension_tweak_ed25519_sk_to_scalar = function (n, sk) {
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "n must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(
    sk.byteLength === binding.crypto_sign_SECRETKEYBYTES,
    "sk must be 'crypto_sign_SECRETKEYBYTES' bytes"
  )

  binding.extension_tweak_ed25519_sk_to_scalar(n, sk)
}

exports.extension_tweak_ed25519_scalar = function (scalarOut, scalar, ns) {
  assert(ArrayBuffer.isView(scalarOut), 'scalarOut must be a typed array')
  assert(
    scalarOut.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "scalarOut must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(scalar), 'scalar must be a typed array')
  assert(
    scalar.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "scalar must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )

  binding.extension_tweak_ed25519_scalar(scalarOut, scalar, ns)
}

exports.extension_tweak_ed25519_pk = function (tpk, pk, ns) {
  assert(ArrayBuffer.isView(tpk), 'tpk must be a typed array')
  assert(
    tpk.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
    "tpk must be 'crypto_sign_PUBLICKEYBYTES' bytes"
  )
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(
    pk.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
    "pk must be 'crypto_sign_PUBLICKEYBYTES' bytes"
  )

  const res = binding.extension_tweak_ed25519_pk(tpk, pk, ns)

  if (res !== 0) throw new Error('failed to tweak public key')
}

exports.extension_tweak_ed25519_keypair = function (pk, scalarOut, scalarIn, ns) {
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(
    pk.byteLength === binding.extension_tweak_ed25519_BYTES,
    "pk must be 'extension_tweak_ed25519_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(scalarOut), 'scalarOut must be a typed array')
  assert(
    scalarOut.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "scalarOut must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(scalarIn), 'scalarIn must be a typed array')
  assert(
    scalarIn.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "scalarIn must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )

  binding.extension_tweak_ed25519_keypair(pk, scalarOut, scalarIn, ns)
}

exports.extension_tweak_ed25519_scalar_add = function (scalarOut, scalar, n) {
  assert(ArrayBuffer.isView(scalarOut), 'scalarOut must be a typed array')
  assert(
    scalarOut.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "scalarOut must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(scalar), 'scalar must be a typed array')
  assert(
    scalar.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "scalar must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "n must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )

  binding.extension_tweak_ed25519_scalar_add(scalarOut, scalar, n)
}

exports.extension_tweak_ed25519_pk_add = function (tpk, pk, p) {
  assert(ArrayBuffer.isView(tpk), 'tpk must be a typed array')
  assert(
    tpk.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
    "tpk must be 'crypto_sign_PUBLICKEYBYTES' bytes"
  )
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(
    pk.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
    "pk must be 'crypto_sign_PUBLICKEYBYTES' bytes"
  )
  assert(ArrayBuffer.isView(p), 'p must be a typed array')
  assert(
    p.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
    "p must be 'crypto_sign_PUBLICKEYBYTES' bytes"
  )

  const res = binding.extension_tweak_ed25519_pk_add(tpk, pk, p)

  if (res !== 0) throw new Error('failed to add tweak to public key')
}

exports.extension_tweak_ed25519_keypair_add = function (pk, scalarOut, scalarIn, tweak) {
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(
    pk.byteLength === binding.extension_tweak_ed25519_BYTES,
    "pk must be 'extension_tweak_ed25519_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(scalarOut), 'scalarOut must be a typed array')
  assert(
    scalarOut.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "scalarOut must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(scalarIn), 'scalarIn must be a typed array')
  assert(
    scalarIn.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "scalarIn must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(tweak), 'tweak must be a typed array')
  assert(
    tweak.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "tweak must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )

  const res = binding.extension_tweak_ed25519_keypair_add(pk, scalarOut, scalarIn, tweak)

  if (res !== 0) throw new Error('failed to add tweak to keypair')
}

exports.extension_pbkdf2_sha512_async = function (out, passwd, salt, iter, outlen, callback) {
  assert(
    iter >= binding.extension_pbkdf2_sha512_ITERATIONS_MIN,
    "iter must be at least 'extension_pbkdf2_sha512_ITERATIONS_MIN'"
  )
  assert(
    outlen <= binding.extension_pbkdf2_sha512_BYTES_MAX,
    "outlen must be at most 'extension_pbkdf2_sha512_BYTES_MAX'"
  )
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(out.byteLength >= outlen, "out must be at least 'outlen' bytes")
  assert(out.byteLength > 0, 'out must not be empty')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(passwd.byteLength > 0, 'passwd must not be empty')
  assert(ArrayBuffer.isView(salt), 'salt must be a typed array')
  assert(salt.byteLength > 0, 'salt must not be empty')

  const [done, promise] = checkStatus(callback)

  binding.extension_pbkdf2_sha512_async(
    out.buffer,
    out.byteOffset,
    out.byteLength,

    passwd.buffer,
    passwd.byteOffset,
    passwd.byteLength,

    salt.buffer,
    salt.byteOffset,
    salt.byteLength,

    iter,
    outlen,

    done
  )

  return promise
}

exports.extension_pbkdf2_sha512 = function (out, passwd, salt, iter, outlen) {
  assert(
    iter >= binding.extension_pbkdf2_sha512_ITERATIONS_MIN,
    "iter must be at least 'extension_pbkdf2_sha512_ITERATIONS_MIN'"
  )
  assert(
    outlen <= binding.extension_pbkdf2_sha512_BYTES_MAX,
    "outlen must be at most 'extension_pbkdf2_sha512_BYTES_MAX'"
  )
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(out.byteLength >= outlen, "out must be at least 'outlen' bytes")
  assert(out.byteLength > 0, 'out must not be empty')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(passwd.byteLength > 0, 'passwd must not be empty')
  assert(ArrayBuffer.isView(salt), 'salt must be a typed array')
  assert(salt.byteLength > 0, 'salt must not be empty')

  const res = binding.extension_pbkdf2_sha512(out, passwd, salt, iter, outlen)

  if (res !== 0) throw new Error('failed to add tweak to public key')
}

function checkStatus(callback, booleanResult = false) {
  let done, promise

  if (typeof callback === 'function') {
    done = function (status) {
      if (booleanResult) callback(null, status === 0)
      else if (status === 0) callback(null)
      else callback(new Error('status: ' + status))
    }
  } else {
    promise = new Promise(function (resolve, reject) {
      done = function (status) {
        if (booleanResult) resolve(status === 0)
        else if (status === 0) resolve()
        else reject(new Error('status: ' + status))
      }
    })
  }

  return [done, promise]
}
{
  "name": "sodium-native",
  "version": "5.1.0",
  "description": "Low level bindings for libsodium",
  "main": "index.js",
  "files": [
    "index.js",
    "binding.cc",
    "binding.js",
    "extensions",
    "prebuilds",
    "CMakeLists.txt"
  ],
  "imports": {
    "assert": {
      "bare": "bare-assert",
      "node": "assert"
    }
  },
  "addon": true,
  "dependencies": {
    "bare-assert": "^1.2.0",
    "require-addon": "^1.1.0",
    "which-runtime": "^1.2.1"
  },
  "devDependencies": {
    "bare-compat-napi": "^1.3.5",
    "brittle": "^3.16.2",
    "cmake-bare": "^1.6.1",
    "cmake-fetch": "^1.4.3",
    "cmake-napi": "^1.2.1",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^2.0.0"
  },
  "scripts": {
    "test": "prettier . --check && npm run test:node && npm run test:bare",
    "test:node": "node test/all.js",
    "test:bare": "bare test/all.js"
  },
  "standard": {
    "ignore": [
      "/test/fixtures/*.js"
    ]
  },
  "engines": {
    "bare": ">=1.16.0"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/sodium-native.git"
  },
  "contributors": [
    "Emil Bay <github@tixz.dk> (http://bayes.dk)",
    "Mathias Buus <mathiasbuus@gmail.com> (https://mafinto.sh)",
    "Christophe Diederichs <chm-diederichs@hyperdivision.dk>"
  ],
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/holepunchto/sodium-native/issues"
  },
  "homepage": "https://github.com/holepunchto/sodium-native"
}
module.exports = require('sodium-native')
{
  "name": "sodium-universal",
  "version": "5.0.1",
  "description": "Universal wrapper for sodium-javascript and sodium-native working in Node.js and the Browser",
  "main": "index.js",
  "dependencies": {
    "sodium-native": "^5.0.1"
  },
  "peerDependencies": {
    "sodium-javascript": "~0.8.0"
  },
  "peerDependenciesMeta": {
    "sodium-javascript": {
      "optional": true
    }
  },
  "scripts": {
    "prepublish": "./build-scripts/generate.js"
  },
  "browser": {
    "sodium-native": "sodium-javascript"
  },
  "browserify": {
    "transform": [
      "./build-scripts/transform.js"
    ]
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/sodium-universal.git"
  },
  "keywords": [
    "libsodium",
    "sodium",
    "sodium-native",
    "sodium-javascript",
    "browserify"
  ],
  "contributors": [
    "Emil Bay <github@tixz.dk> (http://bayes.dk)",
    "Mathias Buus <mathiasbuus@gmail.com> (https://mafinto.sh)",
    "Christophe Diederichs <chm-diederichs@hyperdivision.dk>"
  ],
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/holepunchto/sodium-universal/issues"
  },
  "homepage": "https://github.com/holepunchto/sodium-universal#readme"
}
const { runtime, platform, arch, version } =
  typeof Bare !== 'undefined'
    ? {
        runtime: 'bare',
        platform: global.Bare.platform,
        arch: global.Bare.arch,
        version: global.Bare.version
      }
    : typeof process !== 'undefined'
      ? {
          runtime: 'node',
          platform: global.process.platform,
          arch: global.process.arch,
          version: global.process.version
        }
      : typeof Window !== 'undefined'
        ? { runtime: 'browser', platform: 'unknown', arch: 'unknown', version: 'unknown' }
        : { runtime: 'unknown', platform: 'unknown', arch: 'unknown', version: 'unknown' }

exports.runtime = runtime
exports.version = version
exports.platform = platform
exports.arch = arch
exports.isBare = runtime === 'bare'
exports.isBareKit = exports.isBare && typeof BareKit !== 'undefined'
exports.isPear = typeof Pear !== 'undefined'
exports.isNode = runtime === 'node'
exports.isBrowser = runtime === 'browser'
exports.isWindows = platform === 'win32'
exports.isLinux = platform === 'linux'
exports.isMac = platform === 'darwin'
exports.isIOS = platform === 'ios' || platform === 'ios-simulator'
exports.isAndroid = platform === 'android'
exports.isElectron = typeof process !== 'undefined' && !!global.process.versions?.electron
exports.isElectronRenderer = exports.isElectron && global.process.type === 'renderer'
exports.isElectronWorker = exports.isElectron && global.process.type === 'worker'
{
  "name": "which-runtime",
  "version": "1.4.0",
  "description": "Detect if you are in Bare or Node and which os etc",
  "main": "index.js",
  "files": [
    "index.js"
  ],
  "scripts": {
    "format": "prettier --write .",
    "lint": "prettier --check . && lunte .",
    "test": "npm run test:node && npm run test:bare",
    "test:node": "brittle-node test.js",
    "test:bare": "brittle-bare test.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/holepunchto/which-runtime.git"
  },
  "author": "Holepunch Inc.",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/which-runtime/issues"
  },
  "homepage": "https://github.com/holepunchto/which-runtime",
  "devDependencies": {
    "brittle": "^3.0.0",
    "lunte": "^1.4.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^2.0.0"
  }
}
{
  "name": "hyper-identity-core",
  "version": "0.1.0",
  "description": "The engine-independent half of the identity layer \u2014 ops, and the apply that linearizes them. No autobase, no autobee, no view.",
  "type": "module",
  "main": "index.js",
  "exports": {
    ".": "./index.js",
    "./apply": "./lib/apply.js",
    "./operations": "./lib/operations.js",
    "./registry": "./lib/registry.js",
    "./test/contract.js": "./test/contract.js"
  },
  "scripts": {
    "test": "brittle test/*.test.js",
    "test:bare": "bare ./node_modules/.bin/brittle test/*.test.js"
  },
  "dependencies": {
    "b4a": "^1.6.4",
    "hypercore-crypto": "^3.0.0"
  },
  "devDependencies": {
    "brittle": "^4.0.2"
  }
}
const b4a = require('b4a')
const Hypercore = require('hypercore')
const EventEmitter = require('events')
const { RangeStream } = require('./lib/ranges.js')
const { DiffStream } = require('./lib/diff.js')
const { ChangesStream } = require('./lib/changes.js')
const NodeCache = require('./lib/cache.js')
const WriteBatch = require('./lib/write.js')
const CoreContext = require('./lib/context.js')
const SessionConfig = require('./lib/session-config.js')
const { inflate, inflateValue } = require('./lib/inflate.js')
const { EMPTY, T } = require('./lib/tree.js')

class Hyperbee extends EventEmitter {
  constructor(store, opts = {}) {
    super()

    const {
      t = T,
      key = null,
      encryption = null,
      getEncryptionProvider = toEncryptionProvider(encryption),
      maxCacheSize = 4096,
      config = new SessionConfig([], 0, true, null),
      activeRequests = config.activeRequests,
      timeout = config.timeout,
      wait = config.wait,
      trace = config.trace,
      core = getRootCore(store, key, getEncryptionProvider),
      context = new CoreContext(
        store,
        core,
        new NodeCache(maxCacheSize),
        core,
        getEncryptionProvider
      ),
      root = null,
      view = false,
      writable = true,
      unbatch = 0,
      autoUpdate = !writable && !view,
      preload = null
    } = opts

    this.store = store
    this.root = root
    this.t = t
    this.context = context
    this.config = config.sub(activeRequests, timeout, wait, trace)
    this.view = view
    this.writable = writable
    this.unbatch = unbatch

    this.autoUpdate = autoUpdate
    this.preload = preload

    this.ready().catch(noop)
  }

  static isHyperbee(bee) {
    return bee instanceof Hyperbee
  }

  head() {
    if (!this.root) return null
    if (this.root === EMPTY) return { length: 0, key: this.context.core.key }
    return { length: this.root.seq + 1, key: this.root.context.core.key }
  }

  get cache() {
    return this.context.cache
  }

  get core() {
    return this.context.core
  }

  get opening() {
    return this.store.opening
  }

  get opened() {
    return this.store.opened
  }

  get closing() {
    return this.store.closing
  }

  get closed() {
    return this.store.closed
  }

  replicate(...opts) {
    return this.store.replicate(...opts)
  }

  _makeView(context, root, writable, unbatch) {
    return new Hyperbee(this.store, {
      t: this.t,
      config: this.config,
      core: context.core,
      activeRequests: [],
      context,
      root,
      view: true,
      writable,
      unbatch,
      autoUpdate: false,
      preload: this.preload
    })
  }

  checkout({ length = this.core.length, key = null, writable = false } = {}) {
    const context = key ? this.context.getContextByKey(key) : this.context
    const root = length === 0 ? EMPTY : context.createTreeNode(0, length - 1, 0, false, null)
    return this._makeView(context, root, writable, 0)
  }

  move({ length = this.core.length, key = null, writable = this.writable } = {}) {
    this.context = key ? this.context.getContextByKey(key) : this.context
    this.writable = writable

    this._setRoot(this._nodeAtSeq(length - 1), true)
  }

  snapshot() {
    return this._makeView(this.context, this.root, false, 0)
  }

  undo(n) {
    return this._makeView(this.context, this.root, true, n)
  }

  write(opts = {}) {
    if (!this.writable) throw new Error('Not writable')
    if (!opts.key) {
      opts.key = this.root ? this.head().key : null
    }
    return new WriteBatch(this, opts)
  }

  update(root = null) {
    if (root === null) {
      if (this.context.lock.locked) return
      root = this._lastNodeInCore()
    }
    this._setRoot(root, true)
  }

  _lastNodeInCore() {
    return this._nodeAtSeq(this.context.core.length - 1)
  }

  _nodeAtSeq(seq) {
    return seq < 0 ? EMPTY : this.context.createTreeNode(0, seq, 0, false, null)
  }

  async ready() {
    if (!this.core.opened) await this.core.ready()

    if (this.root) return
    if (this.preload) await this.preload()
    if (this.root) return

    this._setRoot(this._lastNodeInCore(), false)
    if (this.autoUpdate) {
      this.core.on('append', this.update.bind(this, null))
    }

    this.emit('ready')
  }

  async close() {
    if (!this.root) await this.ready()
    Hypercore.destroyRequests(this.config.activeRequests, null)
    if (this.view) return
    if (this.store.closing) return this.store.close()
    await this.store.close()
    this.emit('close')
  }

  createReadStream(options) {
    return new RangeStream(this, options)
  }

  createDiffStream(right, options) {
    return new DiffStream(this, right, options)
  }

  createChangesStream(options) {
    return new ChangesStream(this, options)
  }

  async peek(range = {}) {
    const rs = new RangeStream(this, { ...range, limit: 1 })
    let entry = null
    for await (const data of rs) entry = data
    return entry
  }

  download(range = {}) {
    const rs = new RangeStream(this, range)
    rs.resume()
    return new Promise((resolve, reject) => {
      rs.once('error', reject)
      rs.once('close', resolve)
    })
  }

  bump(ptr) {
    if (ptr.changed) return ptr.value
    this.context.cache.bump(ptr)
    this.context.cache.gc()
    return ptr.value
  }

  async compat() {
    await this.ready()
    return this.context.getBlockType(0, this.config)
  }

  async inflate(ptr, config) {
    if (!ptr.value) {
      await inflate(ptr, config)
    }
    this.bump(ptr)
    return ptr.value
  }

  async finalizeKeyPointer(key, config) {
    const value = key.value || (await inflateValue(key, config))

    return {
      core: key.context.getCore(key.core),
      offset: key.offset,
      seq: key.seq,
      key: key.key,
      value
    }
  }

  async bootstrap(config) {
    if (!this.root) await this.ready()
    if (this.unbatch) await this._rollback(config)
    return this.root === EMPTY ? null : this.root
  }

  async _rollback(config) {
    const expected = this.unbatch

    let n = expected
    let length = this.root === EMPTY ? 0 : this.root.seq + 1
    let context = this.context

    while (n > 0 && length > 0 && expected === this.unbatch) {
      const seq = length - 1
      const blk = await context.getBlock(seq, 0, config)

      if (!blk.previous) {
        length = 0
        break
      }

      context = await context.getContext(blk.previous.core, config)
      length = blk.previous.seq + 1
      n--
    }

    if (expected === this.unbatch) {
      this.context = context
      this._setRoot(this._nodeAtSeq(length - 1), true)
    }
  }

  _setRoot(root, emit) {
    if (root === null || !root.equivalentTo(this.root)) {
      this.root = root
      if (emit) this.emit('update')
    }
    this.unbatch = 0
  }

  async get(key, opts) {
    const config = this.config.options(opts)

    let ptr = await this.bootstrap(config)
    if (!ptr) return null

    while (true) {
      const v = ptr.value ? this.bump(ptr) : await this.inflate(ptr, config)

      let s = 0
      let e = v.keys.length
      let c = 0

      while (s < e) {
        const mid = (s + e) >> 1
        const m = v.keys.get(mid)

        c = b4a.compare(key, m.key)

        if (c === 0) return this.finalizeKeyPointer(m, config)

        if (c < 0) e = mid
        else s = mid + 1
      }

      if (!v.children.length) return null

      const i = c < 0 ? e : s
      ptr = v.children.get(i)
    }
  }
}

module.exports = Hyperbee

function noop() {}

function toEncryptionProvider(encryption) {
  if (encryption) return (key) => encryption
  return () => null
}

function getRootCore(store, key, getEncryptionProvider) {
  const encryption = getEncryptionProvider(key)

  return key
    ? store.get({ key, encryption, inflightRange: [256, 512] })
    : store.get({ key, name: 'bee', encryption, inflightRange: [256, 512] })
}
module.exports = class NodeCache {
  constructor(maxSize) {
    this.size = 0
    this.maxSize = maxSize
    this.latest = null
    this.retained = 0
    this.byId = new Map()
    this.dirty = []
  }

  oldest() {
    return this.latest ? this.latest.next : null
  }

  empty() {
    while (this.size > 0) {
      const old = this.oldest()
      this.remove(old)
      old.value = null
    }
  }

  flush() {
    while (this.dirty.length > 0) {
      this.dirty[this.dirty.length - 1].unmark()
    }

    this.retained++
    this.gc()
  }

  gc() {
    while (this.size > this.maxSize) {
      const old = this.oldest()
      if (old.retained > this.retained) break
      this.remove(old)
      old.value = null
    }
  }

  bump(node) {
    if (node === this.latest) return

    if (node.next === null && node.prev === null) {
      this.byId.set(cacheId(node), node)
    }

    if (node.prev) this.remove(node)
    this.size++

    if (!this.latest) {
      node.prev = node.next = node
      this.latest = node
    } else {
      node.prev = this.latest
      node.next = this.latest.next
      this.latest.next.prev = node
      this.latest.next = node
      this.latest = node
    }
  }

  get(node) {
    return this.byId.get(cacheId(node)) || null
  }

  remove(node) {
    if (node.prev) {
      this.size--

      if (node === this.latest) {
        this.latest = node.next === node ? null : node.next
      }

      node.prev.next = node.next
      node.next.prev = node.prev
    }

    node.prev = node.next = null

    const id = cacheId(node)
    if (this.byId.get(id) === node) {
      this.byId.delete(id)
    }
  }

  *[Symbol.iterator]() {
    const node = this.latest
    if (node === null) return

    let next = node

    do {
      yield next
      next = next.next
    } while (node !== next)
  }
}

function cacheId(node) {
  const id = node.context.getCore(node.core).id
  return id + '@' + node.seq + '.' + node.offset
}
const { Readable } = require('streamx')
const Hypercore = require('hypercore')

class ChangesStream extends Readable {
  constructor(tree, opts = {}) {
    const { highWaterMark, head = null } = opts
    super({ eagerOpen: true, highWaterMark })

    this.tree = tree

    this.head = head
    this.context = null
    this.config = tree.config.detach(opts)
  }

  async _openp() {
    await this.tree.bootstrap(this.config)
    if (this.head === null) this.head = this.tree.head()
    if (this.head !== null) this.context = this.tree.context.getContextByKey(this.head.key)
  }

  async _readp() {
    if (!this.context || this.head.length === 0) {
      this.push(null)
      return
    }

    const data = {
      head: this.head,
      tail: null,
      batch: []
    }

    const seq = this.head.length - 1
    const blk = await this.context.getBlock(seq, 0, this.config)
    const batchStart = seq - blk.batch.start
    const remaining = new Array(blk.batch.start)

    for (let i = 0; i < remaining.length; i++) {
      remaining[i] = this.context.getBlock(batchStart + i, 0, this.config)
    }

    for (const blk of await Promise.all(remaining)) data.batch.push(blk)
    data.batch.push(blk)

    if (!blk.previous) {
      this.head = null
      this.context = null
      this.push(data)
      return
    }

    this.context = await this.context.getContext(blk.previous.core, this.config)
    this.head = data.tail = { key: this.context.core.key, length: blk.previous.seq + 1 }

    this.push(data)
  }

  async _open(cb) {
    try {
      await this._openp()
    } catch (err) {
      cb(err)
      return
    }

    cb(null)
  }

  async _read(cb) {
    try {
      await this._readp()
    } catch (err) {
      cb(err)
      return
    }

    cb(null)
  }

  _predestroy() {
    Hypercore.destroyRequests(this.config.activeRequests, null)
  }
}

exports.ChangesStream = ChangesStream
// ported from the old protocol buffer impl in old bee

const encodings = require('protocol-buffers-encodings')
const b4a = require('b4a')
const { OP_COHORT, OP_INSERT } = require('./compression')
const varint = encodings.varint
const skip = encodings.skip

exports.t = 5

exports.decode = decodeCompat
exports.encode = encodeCompat

function decodeCompat(buffer, seq) {
  const node = buffer.length > 1 ? decodeNode(buffer) : null
  const index = node ? decodeYoloIndex(node.index) : { levels: [] }
  const morphed = {
    type: 10,
    checkpoint: 0,
    batch: { start: 0, end: 0 },
    previous: seq > 1 ? { core: 0, seq: seq - 1 } : null,
    metadata: null,
    tree: [],
    keys: node ? [{ key: node.key, value: node.value, valuePointer: null }] : [],
    values: [],
    cohorts: [],
    t: exports.t
  }

  for (const lvl of index.levels) {
    const t = { keys: [], children: [] }

    if (lvl.keys.length) {
      const cohort = []
      const offset = morphed.cohorts.push(cohort) - 1

      for (let i = 0; i < lvl.keys.length; i++) {
        const seq = lvl.keys[i]
        cohort.push({ type: OP_INSERT, index: i, pointer: { core: 0, seq, offset: 0 } })
      }

      t.keys.push({ type: OP_COHORT, index: 0, pointer: { core: 0, seq, offset } })
    }

    if (lvl.children.length) {
      const cohort = []
      const offset = morphed.cohorts.push(cohort) - 1

      for (let i = 0; i < lvl.children.length; i += 2) {
        const seq = lvl.children[i]
        const offset = lvl.children[i + 1]
        cohort.push({ type: OP_INSERT, index: i / 2, pointer: { core: 0, seq, offset } })
      }

      t.children.push({ type: OP_COHORT, index: 0, pointer: { core: 0, seq, offset } })
    }

    morphed.tree.push(t)
  }

  return morphed
}

function encodeCompat(node) {
  const index = { levels: [] }

  if (node.tree) {
    for (const t of node.tree) {
      const lvl = { keys: [], children: [] }
      for (const k of t.keys) {
        for (const kk of node.cohorts[k.pointer.offset]) {
          lvl.keys.push(kk.pointer.seq)
        }
      }
      for (const c of t.children) {
        for (const cc of node.cohorts[c.pointer.offset]) {
          lvl.children.push(cc.pointer.seq, cc.pointer.offset)
        }
      }
      index.levels.push(lvl)
    }
  }

  const bufIndex = b4a.allocUnsafe(encodingLengthYoloIndex(index))
  encodeYoloIndex(index, bufIndex, 0)

  const n = {
    key: node.keys[0].key,
    value: node.keys[0].value,
    index: bufIndex
  }

  const buf = b4a.allocUnsafe(encodingLengthNode(n))
  return encodeNode(n, buf, 0)
}

function decodeLevel(buf, offset, end) {
  if (!offset) offset = 0
  if (!end) end = buf.length
  if (!(end <= buf.length && offset <= buf.length)) throw new Error('Decoded message is not valid')

  const oldOffset = offset

  const obj = {
    keys: [],
    children: []
  }

  while (true) {
    if (end <= offset) {
      decodeLevel.bytes = offset - oldOffset
      return obj
    }
    const prefix = varint.decode(buf, offset)
    offset += varint.decode.bytes
    const tag = prefix >> 3
    switch (tag) {
      case 1: {
        let packedEnd = varint.decode(buf, offset)
        offset += varint.decode.bytes
        packedEnd += offset
        while (offset < packedEnd) {
          obj.keys.push(encodings.varint.decode(buf, offset))
          offset += encodings.varint.decode.bytes
        }
        break
      }
      case 2: {
        let packedEnd = varint.decode(buf, offset)
        offset += varint.decode.bytes
        packedEnd += offset
        while (offset < packedEnd) {
          obj.children.push(encodings.varint.decode(buf, offset))
          offset += encodings.varint.decode.bytes
        }
        break
      }
      default: {
        offset = skip(prefix & 7, buf, offset)
      }
    }
  }
}

function encodingLengthLevel(obj) {
  let length = 0
  if (obj.keys) {
    let packedLen = 0
    for (let i = 0; i < obj.keys.length; i++) {
      if (!obj.keys[i]) continue
      const len = encodings.varint.encodingLength(obj.keys[i])
      packedLen += len
    }
    if (packedLen) {
      length += 1 + packedLen + varint.encodingLength(packedLen)
    }
  }
  if (obj.children) {
    let packedLen = 0
    for (let i = 0; i < obj.children.length; i++) {
      if (!obj.children[i]) continue
      const len = encodings.varint.encodingLength(obj.children[i])
      packedLen += len
    }
    if (packedLen) {
      length += 1 + packedLen + varint.encodingLength(packedLen)
    }
  }
  return length
}

function encodeLevel(obj, buf, offset) {
  if (!offset) offset = 0
  const oldOffset = offset
  if (obj.keys) {
    let packedLen = 0
    for (let i = 0; i < obj.keys.length; i++) {
      if (!obj.keys[i]) continue
      packedLen += encodings.varint.encodingLength(obj.keys[i])
    }
    if (packedLen) {
      buf[offset++] = 10
      varint.encode(packedLen, buf, offset)
      offset += varint.encode.bytes
    }
    for (let i = 0; i < obj.keys.length; i++) {
      if (!obj.keys[i]) continue
      encodings.varint.encode(obj.keys[i], buf, offset)
      offset += encodings.varint.encode.bytes
    }
  }
  if (obj.children) {
    let packedLen = 0
    for (let i = 0; i < obj.children.length; i++) {
      if (!obj.children[i]) continue
      packedLen += encodings.varint.encodingLength(obj.children[i])
    }
    if (packedLen) {
      buf[offset++] = 18
      varint.encode(packedLen, buf, offset)
      offset += varint.encode.bytes
    }
    for (let i = 0; i < obj.children.length; i++) {
      if (!obj.children[i]) continue
      encodings.varint.encode(obj.children[i], buf, offset)
      offset += encodings.varint.encode.bytes
    }
  }
  encodeLevel.bytes = offset - oldOffset
  return buf
}

function decodeYoloIndex(buf, offset, end) {
  if (!offset) offset = 0
  if (!end) end = buf.length
  if (!(end <= buf.length && offset <= buf.length)) throw new Error('Decoded message is not valid')
  const oldOffset = offset
  const obj = {
    levels: []
  }
  while (true) {
    if (end <= offset) {
      decodeYoloIndex.bytes = offset - oldOffset
      return obj
    }
    const prefix = varint.decode(buf, offset)
    offset += varint.decode.bytes
    const tag = prefix >> 3
    switch (tag) {
      case 1: {
        const len = varint.decode(buf, offset)
        offset += varint.decode.bytes
        obj.levels.push(decodeLevel(buf, offset, offset + len))
        offset += decodeLevel.bytes
        break
      }
      default: {
        offset = skip(prefix & 7, buf, offset)
      }
    }
  }
}

function encodingLengthYoloIndex(obj) {
  let length = 0
  if (obj.levels) {
    for (let i = 0; i < obj.levels.length; i++) {
      if (!obj.levels[i]) continue
      const len = encodingLengthLevel(obj.levels[i])
      length += varint.encodingLength(len)
      length += 1 + len
    }
  }
  return length
}

function encodeYoloIndex(obj, buf, offset) {
  if (!offset) offset = 0
  const oldOffset = offset
  if (obj.levels) {
    for (let i = 0; i < obj.levels.length; i++) {
      if (!obj.levels[i]) continue
      buf[offset++] = 10
      const len = encodingLengthLevel(obj.levels[i])
      varint.encode(len, buf, offset)
      offset += varint.encode.bytes
      encodeLevel(obj.levels[i], buf, offset)
      offset += encodeLevel.bytes
    }
  }
  encodeYoloIndex.bytes = offset - oldOffset
  return buf
}

function decodeNode(buf, offset, end) {
  if (!offset) offset = 0
  if (!end) end = buf.length
  if (!(end <= buf.length && offset <= buf.length)) throw new Error('Decoded message is not valid')
  const oldOffset = offset
  const obj = {
    index: null,
    key: null,
    value: null
  }
  let found0 = false
  let found1 = false
  while (true) {
    if (end <= offset) {
      if (!found0 || !found1) throw new Error('Decoded message is not valid')
      decodeNode.bytes = offset - oldOffset
      return obj
    }
    const prefix = varint.decode(buf, offset)
    offset += varint.decode.bytes
    const tag = prefix >> 3
    switch (tag) {
      case 1: {
        obj.index = encodings.bytes.decode(buf, offset)
        offset += encodings.bytes.decode.bytes
        found0 = true
        break
      }
      case 2: {
        obj.key = encodings.bytes.decode(buf, offset)
        offset += encodings.bytes.decode.bytes
        found1 = true
        break
      }
      case 3: {
        obj.value = encodings.bytes.decode(buf, offset)
        offset += encodings.bytes.decode.bytes
        break
      }
      default: {
        offset = skip(prefix & 7, buf, offset)
      }
    }
  }
}

function encodingLengthNode(obj) {
  let length = 0
  let len = encodings.bytes.encodingLength(obj.index)
  length += 1 + len
  len = encodings.bytes.encodingLength(obj.key)
  length += 1 + len
  if (obj.value) {
    const len = encodings.bytes.encodingLength(obj.value)
    length += 1 + len
  }
  return length
}

function encodeNode(obj, buf, offset) {
  if (!offset) offset = 0
  const oldOffset = offset
  buf[offset++] = 10
  encodings.bytes.encode(obj.index, buf, offset)
  offset += encodings.bytes.encode.bytes
  buf[offset++] = 18
  encodings.bytes.encode(obj.key, buf, offset)
  offset += encodings.bytes.encode.bytes
  if (obj.value) {
    buf[offset++] = 26
    encodings.bytes.encode(obj.value, buf, offset)
    offset += encodings.bytes.encode.bytes
  }
  encodeNode.bytes = offset - oldOffset
  return buf
}
const OP_SET = 0
const OP_INSERT = 1
const OP_DEL = 2
const OP_COHORT = 3

class DeltaOp {
  constructor(changed, type, index, pointer) {
    this.type = type
    this.index = index
    this.pointer = pointer
    this.changed = changed
  }

  mark() {
    this.changed = true
  }

  unmark() {
    this.changed = false
  }
}

class DeltaCohort extends DeltaOp {
  constructor(changed, pointer, deltas) {
    super(changed, OP_COHORT, 0, pointer)
    this.deltas = deltas
  }
}

class CompressedArray {
  constructor(delta) {
    this.entries = []
    this.delta = delta
    this.uentries = null
    this.updates = 0

    for (const d of delta) {
      if (d.type === OP_COHORT) {
        for (const dd of d.deltas) {
          apply(this.entries, dd.type, dd.index, dd.pointer)
        }
      } else {
        apply(this.entries, d.type, d.index, d.pointer)
      }
    }
  }

  get length() {
    return this.entries.length
  }

  get ulength() {
    return this.uentries ? this.uentries.length : this.entries.length
  }

  reset() {
    if (this.updates > 0 || this.uentries !== null) {
      this.commit()
    }
  }

  commit() {
    const c = new CompressedArray([])

    c.delta = this.delta
    c.entries = this.uentries || this.entries.slice(0)

    this.delta = this.updates === 0 ? this.delta.slice(0) : this.delta.slice(0, -this.updates)
    this.uentries = null
    this.updates = 0

    return c
  }

  _update() {
    if (this.uentries) return
    this.uentries = this.entries.slice(0)
  }

  touch(index, pointer) {
    if (pointer) this.entries[index] = pointer
    else pointer = this.entries[index]

    if (pointer.changedBy === this) return
    this.set(index, pointer)
  }

  get(index) {
    return this.entries[index]
  }

  uget(index) {
    return this.uentries ? this.uentries[index] : this.entries[index]
  }

  push(pointer) {
    if (!this.uentries) this._update()
    this.insert(this.uentries.length, pointer)
  }

  unshift(pointer) {
    this.insert(0, pointer)
  }

  pop() {
    if (!this.uentries) this._update()
    if (this.uentries.length === 0) return
    const head = this.uentries[this.uentries.length - 1]
    this.delete(this.uentries.length - 1)
    return head
  }

  shift() {
    if (!this.uentries) this._update()
    if (this.uentries.length === 0) return
    const tail = this.uentries[0]
    this.delete(0)
    return tail
  }

  _touch(pointer) {
    this.updates++
    if (pointer) pointer.changedBy = this
  }

  insert(index, pointer) {
    if (!this.uentries) this._update()
    if (!insert(this.uentries, index, pointer)) return
    this._touch(pointer)
    this.delta.push(new DeltaOp(true, OP_INSERT, index, pointer))
  }

  delete(index) {
    if (!this.uentries) this._update()
    if (!del(this.uentries, index)) return
    this._touch(null)
    this.delta.push(new DeltaOp(true, OP_DEL, index, null))
  }

  set(index, pointer) {
    if (!this.uentries) this._update()
    if (!set(this.uentries, index, pointer)) return
    this._touch(pointer)
    this.delta.push(new DeltaOp(true, OP_SET, index, pointer))
  }

  flush(max, min) {
    this.updates = 0
    this.uentries = null

    // the raw entries are smaller than the delta, rebase to a single cohort
    if (this.entries.length < this.delta.length) {
      const co = new DeltaCohort(true, null, [])
      for (let i = 0; i < this.entries.length; i++) {
        co.deltas.push(new DeltaOp(true, OP_INSERT, i, this.entries[i]))
      }
      this.delta = [co]
      return this.delta
    }

    let overflow = false
    for (const d of this.delta) {
      if (d.index < 256) continue // has to be uint3, only happens in rebalances/splits
      overflow = true
      break
    }

    if (this.delta.length <= max && !overflow) return this.delta

    const direct = []
    while (this.delta.length && this.delta[this.delta.length - 1].type !== OP_COHORT) {
      direct.push(this.delta.pop())
    }
    direct.reverse()

    const l = direct.length

    if (l > min && l < this.entries.length && !overflow && this.delta.length + l <= max) {
      const co = new DeltaCohort(true, null, [])
      for (const d of direct) {
        co.deltas.push(d)
      }
      this.delta.push(co)
    } else {
      const co = new DeltaCohort(true, null, [])
      for (let i = 0; i < this.entries.length; i++) {
        const d = new DeltaOp(true, OP_INSERT, i, this.entries[i])
        co.deltas.push(d)
      }
      this.delta = [co]
    }

    return this.delta
  }
}

exports.CompressedArray = CompressedArray
exports.DeltaOp = DeltaOp
exports.DeltaCohort = DeltaCohort

exports.OP_SET = OP_SET
exports.OP_INSERT = OP_INSERT
exports.OP_DEL = OP_DEL
exports.OP_COHORT = OP_COHORT

function del(entries, index) {
  if (index >= entries.length) return false
  entries.splice(index, 1)
  return true
}

function insert(entries, index, pointer) {
  if (index >= entries.length + 1) return false
  entries.splice(index, 0, pointer)
  return true
}

function set(entries, index, pointer) {
  if (index >= entries.length) return false
  // if (entries[index] === pointer) return false
  entries[index] = pointer
  return true
}

function apply(entries, type, index, pointer) {
  if (type === OP_INSERT) {
    return insert(entries, index, pointer)
  }
  if (type === OP_DEL) {
    return del(entries, index)
  }
  if (type === OP_SET) {
    return set(entries, index, pointer)
  }
  return false
}
const b4a = require('b4a')
const ScopeLock = require('scope-lock')
const c = require('compact-encoding')
const { BLOCK_NOT_AVAILABLE } = require('hypercore-errors')
const { TreeNodePointer } = require('./tree.js')
const { decodeBlock, TYPE_LATEST } = require('./encoding.js')

const CHECKPOINT_KEY = 'hyperbee/checkpoint'

class CoreContext {
  constructor(
    store,
    local,
    cache,
    core,
    getEncryptionProvider,
    lock = new ScopeLock(),
    other = new Map()
  ) {
    this.store = store
    this.local = local
    this.cache = cache
    this.core = core
    this.getEncryptionProvider = getEncryptionProvider
    this.lock = lock
    this.other = other
    this.length = 0
    this.checkpoint = 0
    this.opened = []
    this.cores = []
    this.changed = false
    this.updateLock = new ScopeLock()
  }

  // TODO: remove, left here for easier debugging for now
  // [Symbol.for('nodejs.util.inspect.custom')]() {
  //   return '[CoreContext]'
  // }

  async update(config) {
    await this.core.ready()

    if (this.length >= this.core.length) return
    await this._inflateCheckpointFromLength(this.core.length, config, true)
  }

  async updateMaybe(config, core) {
    await this.core.ready()

    if (this.hasCore(core)) return

    const buf = await this.core.getUserData(CHECKPOINT_KEY)
    const cached = buf ? c.decode(c.uint, buf) : 0

    if (cached > this.length && cached <= this.core.length) {
      await this._inflateCheckpointFromLength(cached, config, false)
      if (this.hasCore(core)) return
    }

    await this.update(config)
  }

  hasCore(core) {
    return core <= this.opened.length
  }

  async _inflateCheckpointFromLength(length, config, persist) {
    const seq = length - 1
    const buffer = await this.core.get(seq, config)
    if (buffer === null) throw BLOCK_NOT_AVAILABLE()
    const block = decodeBlock(buffer, seq)

    await this._inflateCheckpoint(length, block, config, persist)
  }

  async _inflateCheckpoint(length, block, config, persist) {
    if (length <= this.length) return

    const checkpoint = block.checkpoint
    let cores = null

    if (checkpoint > 0) {
      const seq = checkpoint - 1
      const buffer = await this.core.get(seq, config)
      if (buffer === null) throw BLOCK_NOT_AVAILABLE()
      const block = decodeBlock(buffer, seq)

      cores = block.metadata ? block.metadata.cores : []
    }

    if (length <= this.length) return
    if (!(await this.updateLock.lock())) return

    try {
      if (length <= this.length) return

      if (cores !== null) {
        this.cores = cores
        while (this.opened.length < this.cores.length) this.opened.push(null)
      }

      this.checkpoint = checkpoint
      this.length = length

      if (persist) {
        await this.core.setUserData(CHECKPOINT_KEY, c.encode(c.uint, length))
      }
    } finally {
      this.updateLock.unlock()
    }
  }

  createTreeNode(core, seq, offset, changed, value) {
    const ptr = new TreeNodePointer(this, core, seq, offset, changed, value)
    if (ptr.value || changed) return ptr
    const existing = this.cache.get(ptr)
    if (existing) return existing
    return ptr
  }

  async getBlockType(core, config) {
    const hc = this.getCore(core)
    if (hc.length === 0) return TYPE_LATEST
    const block = await this.getBlock(hc.length - 1, core, config)
    return block.type
  }

  getCoreOffsetLocalByKey(key) {
    if (b4a.equals(key, this.core.key)) return 0

    for (let i = 0; i < this.cores.length; i++) {
      const k = this.cores[i].key
      if (b4a.equals(k, key)) {
        return i + 1
      }
    }

    return -1
  }

  getCoreOffsetLocal(context, core) {
    if (context === this) return core

    const key = core === 0 ? context.core.key : context.cores[core - 1].key
    const offset = this.getCoreOffsetLocalByKey(key)

    if (offset > -1) return offset

    this.changed = true
    this.cores.push({ key, fork: 0, length: 0, treeHash: null })
    this.opened.push(null)

    return this.cores.length
  }

  async getCoreOffset(context, core, config) {
    if (core !== 0 && core - 1 >= context.cores.length) await context.updateMaybe(config, core)
    return this.getCoreOffsetLocal(context, core)
  }

  async getCoreOffsetByKey(key, config) {
    await this.core.ready()

    let offset = this.getCoreOffsetLocalByKey(key)
    if (offset > -1) return offset

    await this.update(config)

    offset = this.getCoreOffsetLocalByKey(key)
    if (offset > -1) return offset

    this.changed = true
    this.cores.push({ key, fork: 0, length: 0, treeHash: null })
    this.opened.push(null)
    return this.cores.length
  }

  getCore(index) {
    if (index === 0) return this.core
    if (index > this.cores.length) throw new Error('Bad core index: ' + index)
    if (this.opened[index - 1] === null) {
      const key = this.cores[index - 1].key
      this.opened[index - 1] = this.store.get({
        key,
        encryption: this.getEncryptionProvider(key),
        inflightRange: [256, 512]
      })
    }
    return this.opened[index - 1]
  }

  getCoreKey(index) {
    if (index === 0) return this.core.key
    if (index > this.cores.length) throw new Error('Bad core index: ' + index)
    if (this.opened[index - 1] !== null && this.opened[index - 1].key) {
      return this.opened[index - 1].key
    }
    return this.cores[index - 1].key
  }

  async getBlock(seq, core, config) {
    if (core !== 0 && core - 1 >= this.cores.length) await this.updateMaybe(config, core)

    const hc = this.getCore(core)
    const buffer = await hc.get(seq, config)
    if (buffer === null) throw BLOCK_NOT_AVAILABLE()

    if (config.trace !== null) config.trace(core, seq)

    const block = decodeBlock(buffer, seq)
    return block
  }

  getLocalContext() {
    return this.getContextByKey(this.local.key)
  }

  getContextByKey(key) {
    if (b4a.equals(key, this.core.key)) return this

    const hex = b4a.toString(key, 'hex')
    if (this.other.has(hex)) return this.other.get(hex)

    const ctx = this._createContext(
      this.store.get({
        key,
        encryption: this.getEncryptionProvider(key),
        inflightRange: [256, 512]
      })
    )
    this.other.set(hex, ctx)
    return ctx
  }

  async getContext(core, config) {
    if (core === 0) return this
    if (core > this.cores.length) await this.updateMaybe(config, core)
    if (core > this.cores.length) throw new Error('Bad core index: ' + core)

    const hex = b4a.toString(this.cores[core - 1].key, 'hex')
    if (this.other.has(hex)) return this.other.get(hex)

    const ctx = this._createContext(this.getCore(core))
    this.other.set(hex, ctx)
    return ctx
  }

  flush() {
    this.changed = false
    return {
      cores: this.cores
    }
  }

  _createContext(core) {
    const store = this.store
    const local = this.local
    const cache = this.cache
    const getEncryptionProvider = this.getEncryptionProvider
    const lock = this.lock
    const other = this.other
    return new CoreContext(store, local, cache, core, getEncryptionProvider, lock, other)
  }
}

module.exports = CoreContext
const { Readable } = require('streamx')
const b4a = require('b4a')
const { RangeIterator } = require('./ranges.js')
const Hypercore = require('hypercore')

class DiffIterator {
  constructor(tree, left, right, opts = {}) {
    const { limit = -1, config = left.config } = opts

    this.tree = tree

    this.left = left
    this.right = right

    this.left.limit = this.right.limit = -1

    this.config = config
    this.limit = limit

    this.nextLeft = null
    this.nextRight = null
  }

  async open() {
    await Promise.all([this.left.open(), this.right.open()])
  }

  async _shiftRight() {
    const right = await this.tree.finalizeKeyPointer(this.nextRight, this.config)
    this.nextRight = null
    if (this.limit > 0) this.limit--
    return { left: null, right }
  }

  async _shiftLeft() {
    const left = await this.tree.finalizeKeyPointer(this.nextLeft, this.config)
    this.nextLeft = null
    if (this.limit > 0) this.limit--
    return { left, right: null }
  }

  _fastForward() {
    while (this.left.stack.length && this.right.stack.length) {
      const a = this.left.stack[this.left.stack.length - 1]
      const b = this.right.stack[this.right.stack.length - 1]

      if (a.offset !== b.offset) return
      if (!isSame(a.node, b.node)) return

      this.left.stack.pop()
      this.right.stack.pop()
    }
  }

  async next() {
    while (this.limit === -1 || this.limit > 0) {
      const leftPromise = this.nextLeft ? null : this.left.nextKey()
      const rightPromise = this.nextRight ? null : this.right.nextKey()

      const [l, r] = await Promise.all([leftPromise, rightPromise])

      if (leftPromise) this.nextLeft = l
      if (rightPromise) this.nextRight = r

      if (!this.nextLeft && !this.nextRight) return null
      if (!this.nextLeft) return this._shiftRight()
      if (!this.nextRight) return this._shiftLeft()

      const cmp = b4a.compare(this.nextLeft.key, this.nextRight.key)

      if (cmp === 0) {
        const leftKey = this.nextLeft
        const rightKey = this.nextRight

        this.nextRight = this.nextLeft = null

        if (isSame(leftKey, rightKey)) {
          this._fastForward()
          continue
        }

        const [left, right] = await Promise.all([
          this.tree.finalizeKeyPointer(leftKey, this.config),
          this.tree.finalizeKeyPointer(rightKey, this.config)
        ])

        if (this.limit > 0) this.limit--
        return { left, right }
      }

      if (cmp < 0) return this._shiftLeft()
      return this._shiftRight()
    }

    return null
  }
}

class DiffStream extends Readable {
  constructor(left, right, options = {}) {
    const { highWaterMark } = options
    super({ eagerOpen: true, highWaterMark })

    this.left = left
    this.right = right

    this.iterator = new DiffIterator(
      left,
      new RangeIterator(left, options),
      new RangeIterator(right, options),
      options
    )
  }

  async _open(cb) {
    try {
      await this.iterator.open()
    } catch (err) {
      cb(err)
      return
    }
    cb(null)
  }

  async _read(cb) {
    try {
      this.push(await this.iterator.next())
    } catch (err) {
      cb(err)
      return
    }
    cb(null)
  }

  _predestroy() {
    Hypercore.destroyRequests(this.iterator.left.config.activeRequests, null)
    Hypercore.destroyRequests(this.iterator.right.config.activeRequests, null)
  }
}

exports.DiffIterator = DiffIterator
exports.DiffStream = DiffStream

function isSame(a, b) {
  if (a.seq !== b.seq || a.offset !== b.offset) return false

  const k1 = a.context.getCoreKey(a.core)
  const k2 = b.context.getCoreKey(b.core)

  return k1 === k2 || b4a.equals(k1, k2) ? true : false
}
const c = require('compact-encoding')
const { getEncoding } = require('../spec/hyperschema')
const { OP_COHORT, OP_INSERT } = require('./compression.js')
const compat = require('./compat.js')
const { T } = require('./tree.js')

const Block0 = getEncoding('@bee/block-0')
const Block1 = getEncoding('@bee/block-1')

const TYPE_COMPAT = 10 // compat with bee1, all blocks starts with 0x10

exports.TYPE_COMPAT = TYPE_COMPAT
exports.TYPE_LATEST = 1

exports.Block = Block1
exports.encodeBlock = encodeBlock
exports.decodeBlock = decodeBlock

function encodeBlock(block) {
  if (block.type === 1) {
    if (block.t === T) block.t = 0
    return c.encode(Block1, block)
  }

  if (block.type === 0) {
    return encodeBlock0(block)
  }

  if (block.type === TYPE_COMPAT) {
    return compat.encode(block)
  }

  throw new Error('Unknown block type: ' + block.type)
}

function decodeBlock(buffer, seq) {
  const state = { start: 0, end: buffer.byteLength, buffer }
  const type = state.end > 0 ? c.uint.decode(state) : 0

  state.start = 0

  if (type === 1) {
    const block = Block1.decode(state)
    if (block.t === 0) block.t = T
    return block
  }

  if (type === 0) {
    return decodeBlock0(state, seq)
  }

  if (type === TYPE_COMPAT) {
    return compat.decode(buffer, seq)
  }

  throw new Error('Unknown block type: ' + type)
}

function dataToKey(d) {
  return { ...d, valuePointer: null }
}

function keyToData(d) {
  return { key: d.key, value: d.value }
}

function decodeBlock0(state, seq) {
  const cohorts = []
  const blk = Block0.decode(state)
  const tree = []

  for (const t of blk.tree) {
    const next = {
      keys: toCohort(seq, t.keys, cohorts),
      children: toCohort(seq, t.children, cohorts)
    }

    tree.push(next)
  }

  return {
    type: 0,
    checkpoint: blk.checkpoint,
    batch: blk.batch,
    previous: blk.previous,
    metadata: { cores: blk.cores },
    tree,
    keys: blk.data.map(dataToKey),
    values: null,
    cohorts,
    t: T
  }
}

function encodeBlock0(blk) {
  const tree = []

  for (const t of blk.tree) {
    tree.push({
      keys: fromCohort(t.keys, blk.cohorts),
      children: fromCohort(t.children, blk.cohorts)
    })
  }

  return c.encode(Block0, {
    type: 0,
    checkpoint: blk.checkpoint,
    batch: blk.batch,
    previous: blk.previous,
    tree,
    data: blk.keys.map(keyToData),
    cores: blk.metadata ? blk.metadata.cores : null
  })
}

function fromCohort(deltas, cohorts) {
  if (deltas.length === 0) return []
  const cohort = cohorts[deltas[0].pointer.offset]
  const pointers = []
  for (const d of cohort) pointers.push(d.pointer)
  return pointers
}

function toCohort(seq, pointers, cohorts) {
  if (!pointers.length) return []

  const cohort = []
  const offset = cohorts.push(cohort) - 1

  for (let i = 0; i < pointers.length; i++) {
    cohort.push({
      type: OP_INSERT,
      index: i,
      pointer: pointers[i]
    })
  }

  return [
    {
      type: OP_COHORT,
      index: 0,
      pointer: {
        core: 0,
        seq,
        offset
      }
    }
  ]
}
const b4a = require('b4a')
const { Pointer, KeyPointer, ValuePointer, TreeNode } = require('./tree.js')
const { DeltaOp, DeltaCohort, OP_COHORT } = require('./compression.js')

exports.inflate = async function inflate(ptr, config) {
  if (ptr.value) return ptr.value
  if (ptr.inflating) return ptr.inflating

  ptr.inflating = _inflate(ptr, config)

  try {
    return await ptr.inflating
  } finally {
    ptr.inflating = null
  }
}

async function _inflate(ptr, config) {
  const [block, context] = await Promise.all([
    ptr.context.getBlock(ptr.seq, ptr.core, config),
    ptr.context.getContext(ptr.core, config)
  ])

  const tree = block.tree[ptr.offset]

  const keys = new Array(tree.keys.length)
  const children = new Array(tree.children.length)

  for (let i = 0; i < keys.length; i++) {
    const d = tree.keys[i]
    keys[i] = inflateKey(context, d, ptr, block, config)
  }

  for (let i = 0; i < children.length; i++) {
    const d = tree.children[i]
    children[i] = inflateChild(context, d, ptr, block, config)
  }

  const [k, c] = await Promise.all([Promise.all(keys), Promise.all(children)])

  const value = new TreeNode(block.t, k, c)
  if (!ptr.value) ptr.value = value
  return ptr.value
}

function inflateKey(context, d, ptr, block, config) {
  if (d.type === OP_COHORT) return inflateKeyCohort(context, d, ptr, block, config)
  return inflateKeyDelta(context, d, ptr, block, config)
}

async function inflateKeyDelta(context, d, ptr, block, config) {
  const k = d.pointer

  if (!k) return new DeltaOp(false, d.type, d.index, null)

  const blk =
    k.seq === ptr.seq && k.core === 0 && ptr.core === 0
      ? block
      : await context.getBlock(k.seq, k.core, config)

  const bk = blk.keys[k.offset]

  let vp = null

  if (bk.valuePointer) {
    const p = bk.valuePointer
    const ctx = await context.getContext(k.core, config)
    vp = new ValuePointer(ctx, p.core, p.seq, p.offset, p.split)
  }

  const kp = new KeyPointer(context, k.core, k.seq, k.offset, false, bk.key, bk.value, vp)
  return new DeltaOp(false, d.type, d.index, kp)
}

exports.inflateValue = async function inflateValue(key, config) {
  if (key.value) return key.value
  if (!key.valuePointer) return null

  const ptr = key.valuePointer

  if (ptr.split === 0) {
    const block = await ptr.context.getBlock(ptr.seq, ptr.core, config)
    return block.values[ptr.offset]
  }

  const blockPromises = new Array(ptr.split + 1)
  for (let i = 0; i < blockPromises.length; i++) {
    blockPromises[i] = ptr.context.getBlock(ptr.seq - ptr.split + i, ptr.core, config)
  }
  const blocks = await Promise.all(blockPromises)
  const splitValue = new Array(blockPromises.length)
  for (let i = 0; i < splitValue.length - 1; i++) {
    splitValue[i] = blocks[i].values[0]
  }
  splitValue[splitValue.length - 1] = blocks[blocks.length - 1].buffer[ptr.offset]
  return b4a.concat(splitValue)
}

async function inflateKeyCohort(context, d, ptr, block, config) {
  const co = d.pointer

  const blk =
    co.seq === ptr.seq && co.core === 0 && ptr.core === 0
      ? block
      : await context.getBlock(co.seq, co.core, config)

  const cohort = blk.cohorts[co.offset]
  const promises = new Array(cohort.length)
  const ctx = await context.getContext(co.core, config)

  for (let i = 0; i < cohort.length; i++) {
    const p = cohort[i]
    const k = inflateKeyDelta(ctx, p, co, blk, config)
    promises[i] = k
  }

  const p = new Pointer(context, co.core, co.seq, co.offset)
  return new DeltaCohort(false, p, await Promise.all(promises))
}

async function inflateChild(context, d, ptr, block, config) {
  if (d.type === OP_COHORT) return inflateChildCohort(context, d, ptr, block, config)
  if (d.pointer && !context.hasCore(d.pointer.core)) {
    await context.updateMaybe(config, d.pointer.core)
  }
  return inflateChildDelta(context, d, ptr, block, config)
}

function inflateChildDelta(context, d, ptr, block, config) {
  const p = d.pointer
  const c = p && context.createTreeNode(p.core, p.seq, p.offset, false, null)
  return new DeltaOp(false, d.type, d.index, c)
}

async function inflateChildCohort(context, d, ptr, block, config) {
  const co = d.pointer

  const blk =
    co.seq === ptr.seq && co.core === 0 && ptr.core === 0
      ? block
      : await context.getBlock(co.seq, co.core, config)

  const cohort = blk.cohorts[co.offset]
  const deltas = new Array(cohort.length)
  const ctx = await context.getContext(co.core, config)

  for (let i = 0; i < cohort.length; i++) {
    const c = cohort[i]
    if (c.pointer && !ctx.hasCore(c.pointer.core)) await ctx.updateMaybe(config, c.pointer.core)
    deltas[i] = inflateChildDelta(ctx, c, co, blk, config)
  }

  const p = new Pointer(context, co.core, co.seq, co.offset)
  return new DeltaCohort(false, p, deltas)
}
const { Readable } = require('streamx')
const Hypercore = require('hypercore')
const b4a = require('b4a')

class RangeIterator {
  constructor(tree, opts = {}) {
    const {
      config = tree.config.detach(opts),
      prefetch = true,
      reverse = false,
      limit = -1,
      gte,
      gt,
      lte,
      lt
    } = opts

    this.tree = tree
    this.root = null
    this.stack = []
    this.config = config
    this.reverse = reverse
    this.limit = limit
    this.start = gte || gt || null
    this.end = lte || lt || null
    this.inclusive = !!(reverse ? lte : gte)
    this.compare = (reverse ? gte : lte) ? 0 : -1
    this.prefetch = prefetch
    this.prefetching = null
  }

  async open() {
    if (!this.root) this.root = await this.tree.bootstrap(this.config)
    if (!this.root) return

    if (this.limit === 0) return

    this.stack.push({ node: this.root, offset: 0 })

    if (this.reverse ? !this.end : !this.start) return

    const offset = this.inclusive ? 0 : 1

    while (true) {
      const top = this.stack[this.stack.length - 1]
      const v = top.node.value
        ? this.tree.bump(top.node)
        : await this.tree.inflate(top.node, this.config)

      for (let i = 0; i < v.keys.length; i++) {
        const j = this.reverse ? v.keys.length - 1 - i : i

        const c = this.reverse
          ? b4a.compare(v.keys.get(j).key, this.end)
          : b4a.compare(this.start, v.keys.get(j).key)

        if (c < 0) break
        top.offset = 2 * i + 1 + (c === 0 ? offset : 1)
      }

      const child = (top.offset & 1) === 0
      const k = top.offset >> 1

      if (!child || k >= v.children.length) break

      const j = this.reverse ? v.children.length - 1 - k : k

      this.stack.push({
        node: v.children.get(j),
        offset: 0
      })

      top.offset++
    }
  }

  async next() {
    const key = await this.nextKey()
    return key === null ? key : this.tree.finalizeKeyPointer(key, this.config)
  }

  async nextKey() {
    while (this.stack.length && (this.limit === -1 || this.limit > 0)) {
      const top = this.stack.pop()
      const v = top.node.value
        ? this.tree.bump(top.node)
        : await this.tree.inflate(top.node, this.config)

      const offset = top.offset++
      const child = (offset & 1) === 0
      const k = offset >> 1

      if (child) {
        this.stack.push(top)
        if (k < v.children.length) {
          const j = this.reverse ? v.children.length - 1 - k : k
          this.stack.push({ node: v.children.get(j), offset: 0 })
        }
        continue
      }

      if (k < v.keys.length) {
        const j = this.reverse ? v.keys.length - 1 - k : k

        const data = v.keys.get(j)

        const c = this.reverse
          ? this.start
            ? b4a.compare(this.start, data.key)
            : -1
          : this.end
            ? b4a.compare(data.key, this.end)
            : -1

        if (c > this.compare) break

        if (this.prefetch && !v.children.length && this.stack.length) {
          this.prefetchNext(v.t - 1)
        }

        this.stack.push(top)
        if (this.limit !== -1) this.limit--
        return data
      }
    }

    return null
  }

  prefetchNext(minKeys) {
    // every sibling covers at least minKeys keys plus the parent separator
    // emitted after it
    const size = minKeys + 1

    let limit = this.limit === -1 ? Infinity : this.limit

    // the current leaf (whose key is being emitted now) covers the rest
    if (limit <= size) return

    const parent = this.stack[this.stack.length - 1]
    const pv = parent.node.value
    if (!pv || pv === this.prefetching) return

    this.prefetching = pv

    for (let i = parent.offset >> 1; i < pv.children.length; i++) {
      const j = this.reverse ? pv.children.length - 1 - i : i

      // If the separator key bounding this child is beyond the range,
      // stop fetching child nodes.
      if (i > 0) {
        const k = this.reverse ? pv.keys.get(j) : pv.keys.get(j - 1)

        const cmp = this.reverse
          ? this.start
            ? b4a.compare(this.start, k.key)
            : -1
          : this.end
            ? b4a.compare(k.key, this.end)
            : -1

        if (cmp > this.compare) return
      }

      const c = pv.children.get(j)
      if (!c.value) this.tree.inflate(c, this.config).catch(noop)

      limit -= size
      if (limit <= 0) break
    }
  }
}

class RangeStream extends Readable {
  constructor(tree, options = {}) {
    const { highWaterMark } = options
    super({ eagerOpen: true, highWaterMark })

    this.tree = tree
    this.iterator = new RangeIterator(tree, options)
  }

  async _open(cb) {
    try {
      await this.iterator.open()
    } catch (err) {
      cb(err)
      return
    }
    cb(null)
  }

  async _read(cb) {
    try {
      this.push(await this.iterator.next())
    } catch (err) {
      cb(err)
      return
    }
    cb(null)
  }

  _predestroy() {
    Hypercore.destroyRequests(this.iterator.config.activeRequests, null)
  }
}

exports.RangeIterator = RangeIterator
exports.RangeStream = RangeStream

function noop() {}
class SessionConfig {
  constructor(activeRequests, timeout, wait, trace) {
    this.activeRequests = activeRequests
    this.timeout = timeout
    this.wait = wait
    this.trace = trace
  }

  sub(activeRequests, timeout, wait, trace) {
    if (
      this.activeRequests === activeRequests &&
      this.timeout === timeout &&
      this.wait === wait &&
      this.trace === trace
    ) {
      return this
    }

    return new SessionConfig(activeRequests, timeout, wait, trace)
  }

  options(opts) {
    if (!opts) return this
    const {
      activeRequests = this.activeRequests,
      timeout = this.timeout,
      wait = this.wait,
      trace = this.trace
    } = opts
    return this.sub(activeRequests, timeout, wait, trace)
  }

  detach(opts) {
    if (!opts) return this.sub([], this.timeout, this.wait, this.trace)
    const {
      activeRequests = [],
      timeout = this.timeout,
      wait = this.wait,
      trace = this.trace
    } = opts
    return this.sub(activeRequests, timeout, wait, trace)
  }
}

module.exports = SessionConfig
const b4a = require('b4a')
const { CompressedArray } = require('./compression.js')

const T = 128 // default degree, only non default degrees are stored on the nodes

const INSERTED = -1
const NEEDS_SPLIT = -2

class ValuePointer {
  constructor(context, core, seq, offset, split) {
    this.context = context

    this.core = core
    this.seq = seq
    this.offset = offset
    this.split = split
  }
}

class Pointer {
  constructor(context, core, seq, offset, changed) {
    this.context = context

    this.core = core
    this.seq = seq
    this.offset = offset
    this.changed = changed
    this.retained = 0

    this.changedBy = null
  }

  mark() {
    this.changed = true
  }

  unmark() {
    this.changed = false
    this.changedBy = null
  }

  // Compare two pointers to see if they point to equivalent positions
  equivalentTo(other) {
    // EMPTY is a special case that can look equivalent to a first entry
    // in a hypercore but actually contains different data.
    if (other === exports.EMPTY) return this === exports.EMPTY
    if (!other) return false

    return (
      this.seq === other.seq &&
      this.offset === other.offset &&
      this.changed === other.changed &&
      this.context === other.context &&
      this.core === other.core
    )
  }

  retain() {
    this.retained = this.context.cache.retained + 1
  }
}

class KeyPointer extends Pointer {
  constructor(context, core, seq, offset, changed, key, value, valuePointer) {
    super(context, core, seq, offset, changed)

    this.key = key
    this.value = value
    this.valuePointer = valuePointer
  }

  // TODO: remove, left here for easier debugging for now
  [Symbol.for('nodejs.util.inspect.custom')]() {
    return `[KeyPointer core=${this.core} seq=${this.seq}, offset=${this.offset}, key="${b4a.toString(this.key)}"]`
  }
}

class TreeNodePointer extends Pointer {
  constructor(context, core, seq, offset, changed, value) {
    super(context, core, seq, offset, false)

    this.value = value

    this.next = null
    this.prev = null

    this.dirty = 0
    this.inflating = null

    if (changed) this.mark()
  }

  mark() {
    if (this.changed) return
    this.changed = true
    this.dirty = this.context.cache.dirty.push(this) - 1
  }

  unmark() {
    if (!this.changed) return

    this.changed = false
    this.changedBy = null

    if (this.value) {
      this.value.keys.reset()
      this.value.children.reset()
    }

    const head = this.context.cache.dirty.pop()
    if (head === this) return

    this.context.cache.dirty[this.dirty] = head
    head.dirty = this.dirty
  }

  commit() {
    const value = new TreeNode(this.value.t, [], [])
    value.keys = this.value.keys.commit()
    value.children = this.value.children.commit()
    const ptr = this.context.createTreeNode(this.core, 0, 0, true, value)
    this.unmark()
    return ptr
  }

  equivalentTo(other) {
    return (
      super.equivalentTo(other) &&
      this.value === other.value &&
      this.prev === other.prev &&
      this.next === other.next
    )
  }

  // TODO: remove, left here for easier debugging for now
  [Symbol.for('nodejs.util.inspect.custom')]() {
    return `[TreeNodePointer core=${this.core} seq=${this.seq} offset=${this.offset} changed=${this.changed}]`
  }
}

class TreeNode {
  constructor(t, keys, children) {
    this.t = t
    this.keys = new CompressedArray(keys)
    this.children = new CompressedArray(children)
  }

  isEmpty() {
    return this.keys.ulength === 0 && this.children.ulength === 0
  }

  insertLeaf(context, key, value) {
    let s = 0
    let e = this.keys.ulength
    let c = 0

    while (s < e) {
      const mid = (s + e) >> 1
      const k = this.keys.uget(mid)

      c = b4a.compare(key, k.key)

      if (c === 0) return mid

      if (c < 0) e = mid
      else s = mid + 1
    }

    const i = c < 0 ? e : s
    const maxKeys = 2 * this.t - 1

    this.keys.insert(i, new KeyPointer(context, 0, 0, 0, true, key, value, null))

    return this.keys.ulength <= maxKeys ? INSERTED : NEEDS_SPLIT
  }

  insertNode(keyPointer, treePointer) {
    let s = 0
    let e = this.keys.ulength
    let c = 0

    while (s < e) {
      const mid = (s + e) >> 1
      const k = this.keys.uget(mid)

      c = b4a.compare(keyPointer.key, k.key)

      if (c === 0) return mid

      if (c < 0) e = mid
      else s = mid + 1
    }

    const i = c < 0 ? e : s
    const maxKeys = 2 * this.t - 1

    this.keys.insert(i, keyPointer)
    this.children.insert(i + 1, treePointer)

    return this.keys.ulength <= maxKeys ? INSERTED : NEEDS_SPLIT
  }

  setValue(context, i, value) {
    this.keys.set(i, new KeyPointer(context, 0, 0, 0, true, this.keys.uget(i).key, value, null))
  }

  removeKey(i) {
    this.keys.delete(i)
    if (this.children.ulength) {
      this.children.delete(i + 1)
    }
  }

  siblings(parent) {
    const pc = parent.children

    for (let i = 0; i < pc.ulength; i++) {
      if (pc.uget(i).value !== this) continue // TODO: move to a seq/offset check instead

      const left = i ? pc.uget(i - 1) : null
      const right = i < pc.ulength - 1 ? pc.uget(i + 1) : null
      return { left, index: i, right }
    }

    // TODO: assert
    throw new Error('Bad parent')
  }

  merge(node, median) {
    const keys = node.keys
    const children = node.children

    this.keys.push(median)

    for (let i = 0; i < keys.ulength; i++) this.keys.push(keys.uget(i))
    for (let i = 0; i < children.ulength; i++) this.children.push(children.uget(i))
  }

  split(context) {
    const len = this.keys.ulength >> 1
    const right = context.createTreeNode(0, 0, 0, true, new TreeNode(this.t, [], []))

    const k = []
    while (k.length < len) k.push(this.keys.pop())
    for (let i = k.length - 1; i >= 0; i--) right.value.keys.push(k[i])

    const median = this.keys.pop()

    if (this.children.ulength) {
      const c = []
      while (c.length < len + 1) c.push(this.children.pop())
      for (let i = c.length - 1; i >= 0; i--) right.value.children.push(c[i])
    }

    return {
      left: this,
      median,
      right
    }
  }
}

exports.T = T
exports.INSERTED = INSERTED
exports.NEEDS_SPLIT = NEEDS_SPLIT

exports.TreeNodePointer = TreeNodePointer
exports.TreeNode = TreeNode
exports.KeyPointer = KeyPointer
exports.ValuePointer = ValuePointer
exports.Pointer = Pointer

exports.EMPTY = new TreeNodePointer(null, 0, 0, 0, false, null)
const b4a = require('b4a')
const { OP_COHORT } = require('./compression.js')
const { encodeBlock, TYPE_COMPAT, TYPE_LATEST } = require('./encoding.js')
const { Pointer, KeyPointer, ValuePointer, TreeNode, INSERTED, NEEDS_SPLIT } = require('./tree.js')
const { inflateValue } = require('./inflate.js')
const compat = require('./compat.js')

const PREFERRED_BLOCK_SIZE = 4096
const INLINE_VALUE_SIZE = 1024
const ESTIMATED_POINTER_SIZE = 12 // conversative, seq=8b, offset=2b, core=2b

module.exports = class WriteBatch {
  constructor(tree, opts = {}) {
    const {
      length = -1,
      key = null,
      autoUpdate = true,
      compat = false,
      type = compat ? TYPE_COMPAT : TYPE_LATEST,
      deltaMax = supportsCompression(type) ? 16 : 0,
      deltaMin = supportsCompression(type) ? 1 : Infinity,
      inlineValueSize = INLINE_VALUE_SIZE,
      preferredBlockSize = PREFERRED_BLOCK_SIZE
    } = opts

    this.tree = tree
    this.config = tree.config.options(opts)
    this.deltaMax = deltaMax
    this.deltaMin = deltaMin
    this.inlineValueSize = inlineValueSize
    this.preferredBlockSize = preferredBlockSize
    this.snapshot = tree.snapshot()
    this.autoUpdate = autoUpdate
    this.length = length
    this.key = key
    this.type = type
    this.hasLock = false
    this.closed = false
    this.applied = 0
    this.root = null
    this.ops = []
  }

  tryPut(key, value) {
    this.checkIfClosed()
    this.ops.push({ put: true, applied: false, key, value })
  }

  tryDelete(key) {
    this.checkIfClosed()
    this.ops.push({ put: false, applied: false, key, value: null })
  }

  tryClear() {
    this.checkIfClosed()
    this.ops = []
    this.length = 0
  }

  _getContext(root) {
    if (!this.key && !root) return this.tree.context
    return this.key ? this.tree.context.getContextByKey(this.key) : root.context
  }

  _getLength(root) {
    if (this.length > -1) return this.length
    return root ? root.seq + 1 : 0
  }

  async lock() {
    if (this.hasLock) return
    await this.tree.context.lock.lock()
    this.hasLock = true
  }

  _unlock() {
    if (!this.hasLock) return
    this.tree.context.lock.unlock()
    this.hasLock = false
  }

  async flush() {
    this.checkIfClosed()
    await this.lock()

    let context = null

    try {
      this.checkIfClosed()

      const ops = this.ops

      const root = await this.tree.bootstrap(this.config)

      const length = this._getLength(root)

      context = this._getContext(root)

      const changed = length === 0
      const seq = length === 0 ? 0 : length - 1
      const t = this.type === TYPE_COMPAT ? compat.t : this.tree.t
      const value = changed ? new TreeNode(t, [], []) : null

      this.length = length
      this.root = context.createTreeNode(0, seq, 0, changed, value)

      for (const op of ops) {
        if (op.put) op.applied = await this._put(op.key, op.value)
        else op.applied = await this._delete(op.key)
        if (op.applied) this.applied++
      }

      await this._flush()
      await this.close()

      if (this.autoUpdate) {
        this.tree._setRoot(this.root, true)
      }
    } finally {
      if (context) context.cache.flush()
      this._unlock()
    }
  }

  close() {
    this.closed = true
    return this.snapshot.close()
  }

  async _put(key, value) {
    const stack = []
    const target = key
    const snap = this.snapshot
    const conf = this.config

    let ptr = this.root

    while (true) {
      const v = await retainAndInflate(ptr, snap, conf)
      if (!v.children.ulength) break

      stack.push(ptr)

      let s = 0
      let e = v.keys.ulength
      let c = 0

      while (s < e) {
        const mid = (s + e) >> 1
        const m = v.keys.uget(mid)

        c = b4a.compare(target, m.key)

        if (c === 0) {
          const existing = await inflateValue(m, conf)
          if (b4a.equals(existing, value)) return false
          v.setValue(this.tree.context, mid, value)
          for (let i = 0; i < stack.length; i++) stack[i].mark()
          return true
        }

        if (c < 0) e = mid
        else s = mid + 1
      }

      const i = c < 0 ? e : s
      ptr = v.children.uget(i)
    }

    const v = await retainAndInflate(ptr, snap, conf)
    let status = v.insertLeaf(this.tree.context, target, value)

    if (status >= 0) {
      // already exists, upsert if changed
      const m = v.keys.uget(status)
      const existing = await inflateValue(m, conf)
      if (b4a.equals(existing, value)) return false
      v.setValue(this.tree.context, status, value)
    }

    ptr.mark()
    for (let i = 0; i < stack.length; i++) stack[i].mark()

    while (status === NEEDS_SPLIT) {
      const v = await retainAndInflate(ptr, snap, conf)
      const parent = stack.pop()
      const { median, right } = v.split(this.tree.context)

      if (parent) {
        const p = await retainAndInflate(parent, snap, conf)
        status = p.insertNode(median, right)
        ptr = parent
      } else {
        const node = new TreeNode(v.t, [], [])
        this.root = this.tree.context.createTreeNode(0, 0, 0, true, node)
        this.root.value.keys.push(median)
        this.root.value.children.push(ptr)
        this.root.value.children.push(right)
        status = INSERTED
      }
    }

    return true
  }

  async _delete(key) {
    let ptr = this.root

    const stack = []

    while (true) {
      const v = await retainAndInflate(ptr, this.snapshot, this.config)
      stack.push(ptr)

      let s = 0
      let e = v.keys.ulength
      let c = 0

      while (s < e) {
        const mid = (s + e) >> 1
        c = b4a.compare(key, v.keys.uget(mid).key)

        if (c === 0) {
          if (v.children.ulength) await this._setKeyToNearestLeaf(v, mid, stack)
          else v.removeKey(mid)
          // we mark these as changed late, so we don't rewrite them if it is a 404
          for (let i = 0; i < stack.length; i++) stack[i].mark()
          this.root = await this._rebalance(stack)
          return true
        }

        if (c < 0) e = mid
        else s = mid + 1
      }

      if (!v.children.ulength) return false

      const i = c < 0 ? e : s
      ptr = v.children.uget(i)
    }

    return false
  }

  async _setKeyToNearestLeaf(v, index, stack) {
    const snap = this.snapshot
    const conf = this.config

    let left = v.children.uget(index)
    let right = v.children.uget(index + 1)

    const [ls, rs] = await Promise.all([this._leafSize(left, false), this._leafSize(right, true)])

    if (ls < rs) {
      // if fewer leaves on the left
      stack.push(right)
      let r = await retainAndInflate(right, snap, conf)
      while (r.children.ulength) {
        right = r.children.uget(0)
        stack.push(right)
        r = await retainAndInflate(right, snap, conf)
      }
      v.keys.set(index, r.keys.shift())
    } else {
      // if fewer leaves on the right
      stack.push(left)
      let l = await retainAndInflate(left, snap, conf)
      while (l.children.ulength) {
        left = l.children.uget(l.children.ulength - 1)
        stack.push(left)
        l = await retainAndInflate(left, snap, conf)
      }
      v.keys.set(index, l.keys.pop())
    }
  }

  async _leafSize(ptr, goLeft) {
    const snap = this.snapshot
    const conf = this.config

    let v = await retainAndInflate(ptr, snap, conf)
    while (v.children.ulength) {
      ptr = v.children.uget(goLeft ? 0 : v.children.ulength - 1)
      v = await retainAndInflate(ptr, snap, conf)
    }
    return v.keys.ulength
  }

  async _rebalance(stack) {
    const root = stack[0]
    const snap = this.snapshot
    const conf = this.config

    while (stack.length > 1) {
      const ptr = stack.pop()
      const parent = stack[stack.length - 1]

      const v = await retainAndInflate(ptr, snap, conf)
      const minKeys = v.t - 1

      if (v.keys.ulength >= minKeys) return root

      const p = await retainAndInflate(parent, snap, conf)

      let { left, index, right } = v.siblings(p)

      let l = left && (await retainAndInflate(left, snap, conf))

      // maybe borrow from left sibling?
      if (l && l.keys.ulength > minKeys) {
        left.mark()
        v.keys.unshift(p.keys.uget(index - 1))
        if (l.children.ulength) v.children.unshift(l.children.pop())
        p.keys.set(index - 1, l.keys.pop())
        return root
      }

      let r = right && (await retainAndInflate(right, snap, conf))

      // maybe borrow from right sibling?
      if (r && r.keys.ulength > minKeys) {
        right.mark()
        v.keys.push(p.keys.uget(index))
        if (r.children.ulength) v.children.push(r.children.shift())
        p.keys.set(index, r.keys.shift())
        return root
      }

      // merge node with another sibling
      if (l) {
        index--
        r = v
        right = ptr
      } else {
        l = v
        left = ptr
      }

      left.mark()
      l.merge(r, p.keys.uget(index))

      parent.mark()
      p.removeKey(index)
    }

    const r = await retainAndInflate(root, snap, conf)
    // check if the tree shrunk
    if (!r.keys.ulength && r.children.ulength) return r.children.uget(0)
    return root
  }

  _shouldInlineValue(k) {
    if (!k.value || !supportsCompression(this.type)) return true
    if (k.valuePointer) return true
    return k.value.byteLength <= this.inlineValueSize
  }

  async _flush() {
    if (!this.root || !this.root.changed) {
      return
    }

    if (!this.root.value) await this.snapshot.inflate(this.root, this.config)

    let update = { size: 0, nodes: [], keys: [], values: [] }
    let minValue = -1

    this.root = this.root.commit()

    // t is global for the tree, so the root's t applies to every node
    const t = this.root.value.t

    const batch = [update]
    const stack = [this.root]
    const values = []

    const context = this.tree.context.getLocalContext()
    const config = this.tree.config

    await context.update(config)

    while (stack.length > 0) {
      const node = stack.pop()

      if (this.type !== TYPE_COMPAT && update.size >= this.preferredBlockSize) {
        update = { size: 0, nodes: [], keys: [], values: [] }
        batch.push(update)
      }

      update.nodes.push(node)
      update.size += getEstimatedNodeSize(node.value)

      const keys = node.value.keys
      const children = node.value.children

      for (let i = 0; i < keys.entries.length; i++) {
        const k = keys.entries[i]
        if (!k.changed) continue

        if (!this._shouldInlineValue(k)) {
          values.push(k)
          k.valuePointer = new ValuePointer(context, 0, 0, 0, 0)

          if (minValue === -1 || minValue < k.value.byteLength) {
            minValue = k.value.byteLength
          }
        }

        update.keys.push(k)
        update.size += getEstimatedKeySize(k)
      }

      for (let i = 0; i < children.entries.length; i++) {
        const c = children.entries[i]
        if (!c.value || !c.changed) continue
        const node = c.commit()
        children.touch(i, node)
        stack.push(node)
      }
    }

    if (this.type === TYPE_COMPAT) await toCompatType(context, batch, this.ops)

    const length = context.core.length

    // if noop and not genesis, bail early
    if (this.applied === 0 && length > 0) return

    if (minValue > -1 && minValue + update.size < this.preferredBlockSize) {
      // TODO: repack the value into the block
    }

    if (values.length) {
      update = { size: 0, nodes: [], keys: [], values: [] }

      for (let i = 0; i < values.length; i++) {
        const k = values[i]

        update.size += getEstimatedValueSize(k)
        update.values.push(k)

        if (i === values.length - 1 || update.size >= this.preferredBlockSize) {
          batch.push(update)
          update = { size: 0, nodes: [], keys: [], values: [] }
        }
      }
    }

    const blocks = new Array(batch.length)

    for (let i = 0; i < batch.length; i++) {
      const update = batch[i]
      const seq = length + batch.length - i - 1

      const block = {
        type: this.type,
        checkpoint: 0,
        batch: { start: batch.length - 1 - i, end: i },
        previous: null,
        metadata: null,
        tree: null,
        keys: null,
        values: null,
        cohorts: null,
        t
      }

      for (const k of update.values) {
        if (block.values === null) block.values = []
        const ptr = k.valuePointer

        ptr.core = 0
        ptr.context = context
        ptr.seq = seq
        ptr.offset = block.values.length
        ptr.split = 0

        block.values.push(k.value)
        k.value = null // unlinked
      }

      for (const k of update.keys) {
        if (block.keys === null) block.keys = []

        k.core = 0
        k.context = context
        k.seq = seq
        k.offset = block.keys.length

        if (k.valuePointer) updateValuePointerContext(k.valuePointer, context)

        k.unmark()
        block.keys.push(k)
      }

      for (const n of update.nodes) {
        if (block.tree === null) block.tree = []

        n.core = 0
        n.context = context
        n.seq = seq
        n.offset = block.tree.length

        const treeDelta = {
          keys: n.value.keys.flush(this.deltaMax, this.deltaMin),
          children: n.value.children.flush(this.deltaMax, this.deltaMin)
        }

        prepareCohorts(context, block, seq, treeDelta.keys, supportsCompression(this.type))
        prepareCohorts(context, block, seq, treeDelta.children, false)

        n.unmark()
        block.tree.push(treeDelta)
      }

      blocks[seq - length] = block
    }

    const buffers = new Array(blocks.length)

    if (blocks.length > 0 && this.length > 0) {
      const core = this.key ? await context.getCoreOffsetByKey(this.key, config) : 0
      blocks[blocks.length - 1].previous = { core, seq: this.length - 1 }
    }

    // TODO: make this transaction safe
    if (context.changed) {
      context.checkpoint = context.core.length + blocks.length
      blocks[blocks.length - 1].metadata = context.flush()
    }

    for (let i = 0; i < blocks.length; i++) {
      blocks[i].checkpoint = context.checkpoint
      buffers[i] = encodeBlock(blocks[i])
    }

    this.checkIfClosed()

    await context.core.append(buffers)

    for (let i = 0; i < batch.length; i++) {
      const update = batch[i]

      for (let j = 0; j < update.nodes.length; j++) {
        const node = update.nodes[j]
        this.snapshot.bump(node)
      }
    }
  }

  checkIfClosed() {
    if (this.closed) {
      throw new Error('Write batch is closed')
    }
  }
}

async function toCompatType(context, batch, ops) {
  const map = new Map()
  let index = 0

  for (const k of batch[0].keys) {
    map.set(b4a.toString(k.key, 'hex'), k)
  }

  for (let i = ops.length - 1; i >= 0; i--) {
    const op = ops[i]
    if (!op.put && !op.applied) continue

    const k = map.get(b4a.toString(op.key, 'hex'))

    const j = index++
    if (j === batch.length) batch.push({ size: 0, nodes: [], keys: [], values: [] })
    batch[j].keys = [k || new KeyPointer(context, 0, 0, 0, false, op.key, op.value, null)]
  }

  // compat doesnt support block 0
  if (context.core.length > 0) return

  const header = b4a.from('0a086879706572626565', 'hex')
  await context.core.append(header)
}

function updateValuePointerContext(valuePointer, context) {
  valuePointer.core = context.getCoreOffsetLocal(valuePointer.context, valuePointer.core)
  valuePointer.context = context
}

// TODO: this isnt right anymore as we compress the delta post flush, but prob ok...
function getEstimatedNodeSize(n) {
  return (
    n.keys.delta.length * ESTIMATED_POINTER_SIZE + n.children.delta.length * ESTIMATED_POINTER_SIZE
  )
}

function getEstimatedKeySize(k) {
  return k.key.byteLength + (k.valuePointer ? ESTIMATED_POINTER_SIZE : 0)
}

function getEstimatedValueSize(k) {
  return k.value.byteLength
}

function prepareCohorts(context, block, seq, deltas, keys) {
  for (const d of deltas) {
    // same below but the delta might be a noop (ie add and the delete) - handle that
    if (keys && d.changed && d.pointer && d.pointer.changed) d.pointer = null

    if (d.pointer && d.pointer.context !== context) {
      const p = d.pointer
      p.core = context.getCoreOffsetLocal(p.context, p.core)
      p.context = context
    }

    if (d.type !== OP_COHORT || !d.changed) continue
    if (block.cohorts === null) block.cohorts = []

    d.unmark()
    d.pointer = new Pointer(context, 0, seq, block.cohorts.length)

    for (const dd of d.deltas) {
      if (keys && dd.changed && dd.pointer && dd.pointer.changed) dd.pointer = null
      dd.unmark()
      const p = dd.pointer
      if (!p) continue
      p.core = context.getCoreOffsetLocal(p.context, p.core)
      p.context = context
    }

    block.cohorts.push(d.deltas)
  }
}

function supportsCompression(type) {
  return type !== TYPE_COMPAT && type !== 0
}

async function retainAndInflate(ptr, snap, conf) {
  if (ptr.value) {
    ptr.retain()
    return ptr.value
  }
  ptr.retain()
  return await snap.inflate(ptr, conf)
}
{
  "name": "hyperbee2",
  "version": "2.13.2",
  "description": "Scalable P2P BTree",
  "main": "index.js",
  "files": [
    "index.js",
    "lib/*.js",
    "spec"
  ],
  "imports": {
    "events": {
      "bare": "bare-events",
      "default": "events"
    }
  },
  "scripts": {
    "format": "prettier . --write",
    "lint": "lunte && prettier . --check",
    "test": "node test/all.js",
    "test:bare": "bare test/all.js",
    "test:generate": "brittle -r test/all.js test/*.js"
  },
  "dependencies": {
    "b4a": "^1.6.7",
    "bare-events": "^2.8.2",
    "compact-encoding": "^3.0.0",
    "hypercore": "^11.29.0",
    "hypercore-errors": "^1.5.0",
    "hyperschema": "^1.14.0",
    "protocol-buffers-encodings": "^1.2.0",
    "scope-lock": "^1.2.4",
    "streamx": "^2.22.1"
  },
  "devDependencies": {
    "brittle": "^3.18.0",
    "corestore": "^7.4.5",
    "lunte": "^1.4.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^2.0.0"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/holepunchto/hyperbee2.git"
  },
  "author": "Holepunch Inc.",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/hyperbee2/issues"
  },
  "homepage": "https://github.com/holepunchto/hyperbee2"
}
// This file is autogenerated by the hyperschema compiler
// Schema Version: 1
/* eslint-disable camelcase */
/* eslint-disable quotes */
/* eslint-disable space-before-function-paren */

const { c } = require('hyperschema/runtime')

const VERSION = 1

// eslint-disable-next-line no-unused-vars
let version = VERSION

// @bee/tree-pointer-0
const encoding0 = {
  preencode(state, m) {
    c.uint.preencode(state, m.core)
    c.uint.preencode(state, m.seq)
    c.uint.preencode(state, m.offset)
  },
  encode(state, m) {
    c.uint.encode(state, m.core)
    c.uint.encode(state, m.seq)
    c.uint.encode(state, m.offset)
  },
  decode(state) {
    const r0 = c.uint.decode(state)
    const r1 = c.uint.decode(state)
    const r2 = c.uint.decode(state)

    return {
      core: r0,
      seq: r1,
      offset: r2
    }
  }
}

// @bee/tree-pointer
const encoding1 = {
  preencode(state, m) {
    state.end++ // flags are fixed size

    if (m.core) c.uint.preencode(state, m.core)
    if (m.seq) c.uint.preencode(state, m.seq)
    if (m.offset) c.uint.preencode(state, m.offset)
  },
  encode(state, m) {
    const flags = (m.core ? 1 : 0) | (m.seq ? 2 : 0) | (m.offset ? 4 : 0)

    c.uint8.encode(state, flags)

    if (m.core) c.uint.encode(state, m.core)
    if (m.seq) c.uint.encode(state, m.seq)
    if (m.offset) c.uint.encode(state, m.offset)
  },
  decode(state) {
    const flags = c.uint8.decode(state)

    return {
      core: (flags & 1) !== 0 ? c.uint.decode(state) : 0,
      seq: (flags & 2) !== 0 ? c.uint.decode(state) : 0,
      offset: (flags & 4) !== 0 ? c.uint.decode(state) : 0
    }
  }
}

// @bee/tree-pointer (inline)
const encoding1_inline = {
  preencode(state, m) {
    if (m.core) c.uint.preencode(state, m.core)
    if (m.seq) c.uint.preencode(state, m.seq)
    if (m.offset) c.uint.preencode(state, m.offset)
  },
  encode(state, m) {
    if (m.core) c.uint.encode(state, m.core)
    if (m.seq) c.uint.encode(state, m.seq)
    if (m.offset) c.uint.encode(state, m.offset)
  },
  decode(state, inlining) {
    const flags = inlining

    return {
      core: (flags & 1) !== 0 ? c.uint.decode(state) : 0,
      seq: (flags & 2) !== 0 ? c.uint.decode(state) : 0,
      offset: (flags & 4) !== 0 ? c.uint.decode(state) : 0
    }
  }
}

// @bee/tree-delta
const encoding2 = {
  preencode(state, m) {
    state.end++ // flags are fixed size

    if (m.index) c.uint8.preencode(state, m.index)
    if (m.pointer) encoding1_inline.preencode(state, m.pointer)
  },
  encode(state, m) {
    let flags = (m.type & 7) | (m.index ? 8 : 0) | (m.pointer ? 16 : 0)
    if (m.pointer) {
      flags |= (m.pointer.core ? 32 : 0) | (m.pointer.seq ? 64 : 0) | (m.pointer.offset ? 128 : 0)
    }

    c.uint8.encode(state, flags)

    if (m.index) c.uint8.encode(state, m.index)
    if (m.pointer) encoding1_inline.encode(state, m.pointer)
  },
  decode(state) {
    const flags = c.uint8.decode(state)

    return {
      type: flags & 7,
      index: (flags & 8) !== 0 ? c.uint8.decode(state) : 0,
      pointer: (flags & 16) !== 0 ? encoding1_inline.decode(state, flags >>> 5) : null
    }
  }
}

// @bee/cohort
const encoding3 = c.array(encoding2)

// @bee/value-pointer
const encoding4 = {
  preencode(state, m) {
    state.end++ // flags are fixed size

    if (m.core) c.uint.preencode(state, m.core)
    if (m.seq) c.uint.preencode(state, m.seq)
    if (m.offset) c.uint.preencode(state, m.offset)
    if (m.split) c.uint.preencode(state, m.split)
  },
  encode(state, m) {
    const flags = (m.core ? 1 : 0) | (m.seq ? 2 : 0) | (m.offset ? 4 : 0) | (m.split ? 8 : 0)

    c.uint8.encode(state, flags)

    if (m.core) c.uint.encode(state, m.core)
    if (m.seq) c.uint.encode(state, m.seq)
    if (m.offset) c.uint.encode(state, m.offset)
    if (m.split) c.uint.encode(state, m.split)
  },
  decode(state) {
    const flags = c.uint8.decode(state)

    return {
      core: (flags & 1) !== 0 ? c.uint.decode(state) : 0,
      seq: (flags & 2) !== 0 ? c.uint.decode(state) : 0,
      offset: (flags & 4) !== 0 ? c.uint.decode(state) : 0,
      split: (flags & 8) !== 0 ? c.uint.decode(state) : 0
    }
  }
}

// @bee/value-pointer (inline)
const encoding4_inline = {
  preencode(state, m) {
    if (m.core) c.uint.preencode(state, m.core)
    if (m.seq) c.uint.preencode(state, m.seq)
    if (m.offset) c.uint.preencode(state, m.offset)
    if (m.split) c.uint.preencode(state, m.split)
  },
  encode(state, m) {
    if (m.core) c.uint.encode(state, m.core)
    if (m.seq) c.uint.encode(state, m.seq)
    if (m.offset) c.uint.encode(state, m.offset)
    if (m.split) c.uint.encode(state, m.split)
  },
  decode(state, inlining) {
    const flags = inlining

    return {
      core: (flags & 1) !== 0 ? c.uint.decode(state) : 0,
      seq: (flags & 2) !== 0 ? c.uint.decode(state) : 0,
      offset: (flags & 4) !== 0 ? c.uint.decode(state) : 0,
      split: (flags & 8) !== 0 ? c.uint.decode(state) : 0
    }
  }
}

// @bee/block-pointer
const encoding5 = {
  preencode(state, m) {
    c.uint.preencode(state, m.core)
    c.uint.preencode(state, m.seq)
  },
  encode(state, m) {
    c.uint.encode(state, m.core)
    c.uint.encode(state, m.seq)
  },
  decode(state) {
    const r0 = c.uint.decode(state)
    const r1 = c.uint.decode(state)

    return {
      core: r0,
      seq: r1
    }
  }
}

// @bee/batch-pointer
const encoding6 = {
  preencode(state, m) {
    c.uint.preencode(state, m.start)
    c.uint.preencode(state, m.end)
  },
  encode(state, m) {
    c.uint.encode(state, m.start)
    c.uint.encode(state, m.end)
  },
  decode(state) {
    const r0 = c.uint.decode(state)
    const r1 = c.uint.decode(state)

    return {
      start: r0,
      end: r1
    }
  }
}

// @bee/tree-0.keys
const encoding7_0 = c.array(encoding0)
// @bee/tree-0.children
const encoding7_1 = encoding7_0

// @bee/tree-0
const encoding7 = {
  preencode(state, m) {
    encoding7_0.preencode(state, m.keys)
    encoding7_1.preencode(state, m.children)
  },
  encode(state, m) {
    encoding7_0.encode(state, m.keys)
    encoding7_1.encode(state, m.children)
  },
  decode(state) {
    const r0 = encoding7_0.decode(state)
    const r1 = encoding7_1.decode(state)

    return {
      keys: r0,
      children: r1
    }
  }
}

// @bee/tree.keys
const encoding8_0 = encoding3
// @bee/tree.children
const encoding8_1 = encoding3

// @bee/tree
const encoding8 = {
  preencode(state, m) {
    encoding8_0.preencode(state, m.keys)
    encoding8_1.preencode(state, m.children)
  },
  encode(state, m) {
    encoding8_0.encode(state, m.keys)
    encoding8_1.encode(state, m.children)
  },
  decode(state) {
    const r0 = encoding8_0.decode(state)
    const r1 = encoding8_1.decode(state)

    return {
      keys: r0,
      children: r1
    }
  }
}

// @bee/data
const encoding9 = {
  preencode(state, m) {
    c.buffer.preencode(state, m.key)
    c.buffer.preencode(state, m.value)
  },
  encode(state, m) {
    c.buffer.encode(state, m.key)
    c.buffer.encode(state, m.value)
  },
  decode(state) {
    const r0 = c.buffer.decode(state)
    const r1 = c.buffer.decode(state)

    return {
      key: r0,
      value: r1
    }
  }
}

// @bee/key
const encoding10 = {
  preencode(state, m) {
    c.buffer.preencode(state, m.key)
    state.end++ // flags are fixed size

    if (m.value) c.buffer.preencode(state, m.value)
    if (m.valuePointer) encoding4_inline.preencode(state, m.valuePointer)
  },
  encode(state, m) {
    let flags = (m.value ? 1 : 0) | (m.valuePointer ? 2 : 0)
    if (m.valuePointer) {
      flags |=
        (m.valuePointer.core ? 4 : 0) |
        (m.valuePointer.seq ? 8 : 0) |
        (m.valuePointer.offset ? 16 : 0) |
        (m.valuePointer.split ? 32 : 0)
    }

    c.buffer.encode(state, m.key)
    c.uint8.encode(state, flags)

    if (m.value) c.buffer.encode(state, m.value)
    if (m.valuePointer) encoding4_inline.encode(state, m.valuePointer)
  },
  decode(state) {
    const r0 = c.buffer.decode(state)
    const flags = c.uint8.decode(state)

    return {
      key: r0,
      value: (flags & 1) !== 0 ? c.buffer.decode(state) : null,
      valuePointer: (flags & 2) !== 0 ? encoding4_inline.decode(state, flags >>> 2) : null
    }
  }
}

// @bee/core
const encoding11 = {
  preencode(state, m) {
    c.fixed32.preencode(state, m.key)
    state.end++ // flags are fixed size

    if (m.fork) c.uint.preencode(state, m.fork)
    if (m.length) c.uint.preencode(state, m.length)
    if (m.treeHash) c.fixed32.preencode(state, m.treeHash)
  },
  encode(state, m) {
    const flags = (m.fork ? 1 : 0) | (m.length ? 2 : 0) | (m.treeHash ? 4 : 0)

    c.fixed32.encode(state, m.key)
    c.uint8.encode(state, flags)

    if (m.fork) c.uint.encode(state, m.fork)
    if (m.length) c.uint.encode(state, m.length)
    if (m.treeHash) c.fixed32.encode(state, m.treeHash)
  },
  decode(state) {
    const r0 = c.fixed32.decode(state)
    const flags = c.uint8.decode(state)

    return {
      key: r0,
      fork: (flags & 1) !== 0 ? c.uint.decode(state) : 0,
      length: (flags & 2) !== 0 ? c.uint.decode(state) : 0,
      treeHash: (flags & 4) !== 0 ? c.fixed32.decode(state) : null
    }
  }
}

// @bee/block-0.tree
const encoding12_4 = c.array(encoding7)
// @bee/block-0.data
const encoding12_5 = c.array(encoding9)
// @bee/block-0.cores
const encoding12_6 = c.array(encoding11)

// @bee/block-0
const encoding12 = {
  preencode(state, m) {
    c.uint.preencode(state, m.type)
    c.uint.preencode(state, m.checkpoint)
    encoding6.preencode(state, m.batch)
    state.end++ // max flag is 8 so always one byte

    if (m.previous) encoding5.preencode(state, m.previous)
    if (m.tree) encoding12_4.preencode(state, m.tree)
    if (m.data) encoding12_5.preencode(state, m.data)
    if (m.cores) encoding12_6.preencode(state, m.cores)
  },
  encode(state, m) {
    const flags = (m.previous ? 1 : 0) | (m.tree ? 2 : 0) | (m.data ? 4 : 0) | (m.cores ? 8 : 0)

    c.uint.encode(state, m.type)
    c.uint.encode(state, m.checkpoint)
    encoding6.encode(state, m.batch)
    c.uint.encode(state, flags)

    if (m.previous) encoding5.encode(state, m.previous)
    if (m.tree) encoding12_4.encode(state, m.tree)
    if (m.data) encoding12_5.encode(state, m.data)
    if (m.cores) encoding12_6.encode(state, m.cores)
  },
  decode(state) {
    const r0 = c.uint.decode(state)
    const r1 = c.uint.decode(state)
    const r2 = encoding6.decode(state)
    const flags = c.uint.decode(state)

    return {
      type: r0,
      checkpoint: r1,
      batch: r2,
      previous: (flags & 1) !== 0 ? encoding5.decode(state) : null,
      tree: (flags & 2) !== 0 ? encoding12_4.decode(state) : null,
      data: (flags & 4) !== 0 ? encoding12_5.decode(state) : null,
      cores: (flags & 8) !== 0 ? encoding12_6.decode(state) : null
    }
  }
}

// @bee/metadata.cores
const encoding13_0 = encoding12_6

// @bee/metadata
const encoding13 = {
  preencode(state, m) {
    encoding13_0.preencode(state, m.cores)
  },
  encode(state, m) {
    encoding13_0.encode(state, m.cores)
  },
  decode(state) {
    const r0 = encoding13_0.decode(state)

    return {
      cores: r0
    }
  }
}

// @bee/block-1.metadata
const encoding14_4 = c.frame(encoding13)
// @bee/block-1.tree
const encoding14_5 = c.array(encoding8)
// @bee/block-1.keys
const encoding14_6 = c.array(encoding10)
// @bee/block-1.values
const encoding14_7 = c.array(c.buffer)
// @bee/block-1.cohorts
const encoding14_8 = c.array(encoding3)

// @bee/block-1
const encoding14 = {
  preencode(state, m) {
    c.uint.preencode(state, m.type)
    c.uint.preencode(state, m.checkpoint)
    encoding6.preencode(state, m.batch)
    state.end++ // max flag is 64 so always one byte

    if (m.previous) encoding5.preencode(state, m.previous)
    if (m.metadata) encoding14_4.preencode(state, m.metadata)
    if (m.tree) encoding14_5.preencode(state, m.tree)
    if (m.keys) encoding14_6.preencode(state, m.keys)
    if (m.values) encoding14_7.preencode(state, m.values)
    if (m.cohorts) encoding14_8.preencode(state, m.cohorts)
    if (m.t) c.uint.preencode(state, m.t)
  },
  encode(state, m) {
    const flags =
      (m.previous ? 1 : 0) |
      (m.metadata ? 2 : 0) |
      (m.tree ? 4 : 0) |
      (m.keys ? 8 : 0) |
      (m.values ? 16 : 0) |
      (m.cohorts ? 32 : 0) |
      (m.t ? 64 : 0)

    c.uint.encode(state, m.type)
    c.uint.encode(state, m.checkpoint)
    encoding6.encode(state, m.batch)
    c.uint.encode(state, flags)

    if (m.previous) encoding5.encode(state, m.previous)
    if (m.metadata) encoding14_4.encode(state, m.metadata)
    if (m.tree) encoding14_5.encode(state, m.tree)
    if (m.keys) encoding14_6.encode(state, m.keys)
    if (m.values) encoding14_7.encode(state, m.values)
    if (m.cohorts) encoding14_8.encode(state, m.cohorts)
    if (m.t) c.uint.encode(state, m.t)
  },
  decode(state) {
    const r0 = c.uint.decode(state)
    const r1 = c.uint.decode(state)
    const r2 = encoding6.decode(state)
    const flags = c.uint.decode(state)

    return {
      type: r0,
      checkpoint: r1,
      batch: r2,
      previous: (flags & 1) !== 0 ? encoding5.decode(state) : null,
      metadata: (flags & 2) !== 0 ? encoding14_4.decode(state) : null,
      tree: (flags & 4) !== 0 ? encoding14_5.decode(state) : null,
      keys: (flags & 8) !== 0 ? encoding14_6.decode(state) : null,
      values: (flags & 16) !== 0 ? encoding14_7.decode(state) : null,
      cohorts: (flags & 32) !== 0 ? encoding14_8.decode(state) : null,
      t: (flags & 64) !== 0 ? c.uint.decode(state) : 0
    }
  }
}

function setVersion(v) {
  version = v
}

function encode(name, value, v = VERSION) {
  version = v
  return c.encode(getEncoding(name), value)
}

function decode(name, buffer, v = VERSION) {
  version = v
  return c.decode(getEncoding(name), buffer)
}

function getEnum(name) {
  switch (name) {
    default:
      throw new Error('Enum not found ' + name)
  }
}

function getEncoding(name) {
  switch (name) {
    case '@bee/tree-pointer-0':
      return encoding0
    case '@bee/tree-pointer':
      return encoding1
    case '@bee/tree-delta':
      return encoding2
    case '@bee/cohort':
      return encoding3
    case '@bee/value-pointer':
      return encoding4
    case '@bee/block-pointer':
      return encoding5
    case '@bee/batch-pointer':
      return encoding6
    case '@bee/tree-0':
      return encoding7
    case '@bee/tree':
      return encoding8
    case '@bee/data':
      return encoding9
    case '@bee/key':
      return encoding10
    case '@bee/core':
      return encoding11
    case '@bee/block-0':
      return encoding12
    case '@bee/metadata':
      return encoding13
    case '@bee/block-1':
      return encoding14
    default:
      throw new Error('Encoder not found ' + name)
  }
}

function getStruct(name, v = VERSION) {
  const enc = getEncoding(name)
  return {
    preencode(state, m) {
      version = v
      enc.preencode(state, m)
    },
    encode(state, m) {
      version = v
      enc.encode(state, m)
    },
    decode(state) {
      version = v
      return enc.decode(state)
    }
  }
}

const resolveStruct = getStruct // compat

module.exports = {
  resolveStruct,
  getStruct,
  getEnum,
  getEncoding,
  encode,
  decode,
  setVersion,
  version
}
const sodium = require('sodium-universal')
const c = require('compact-encoding')
const b4a = require('b4a')

// https://en.wikipedia.org/wiki/Merkle_tree#Second_preimage_attack
const LEAF_TYPE = b4a.from([0])
const PARENT_TYPE = b4a.from([1])
const ROOT_TYPE = b4a.from([2])

const HYPERCORE = b4a.from('hypercore')

exports.keyPair = function (seed) {
  // key pairs might stay around for a while, so better not to use a default slab to avoid retaining it completely
  const slab = b4a.allocUnsafeSlow(
    sodium.crypto_sign_PUBLICKEYBYTES + sodium.crypto_sign_SECRETKEYBYTES
  )
  const publicKey = slab.subarray(0, sodium.crypto_sign_PUBLICKEYBYTES)
  const secretKey = slab.subarray(sodium.crypto_sign_PUBLICKEYBYTES)

  if (seed) sodium.crypto_sign_seed_keypair(publicKey, secretKey, seed)
  else sodium.crypto_sign_keypair(publicKey, secretKey)

  return {
    publicKey,
    secretKey
  }
}

exports.validateKeyPair = function (keyPair) {
  const pk = b4a.allocUnsafe(sodium.crypto_sign_PUBLICKEYBYTES)
  sodium.crypto_sign_ed25519_sk_to_pk(pk, keyPair.secretKey)
  return b4a.equals(pk, keyPair.publicKey)
}

exports.sign = function (message, secretKey) {
  // Dedicated slab for the signature, to avoid retaining unneeded mem and for security
  const signature = b4a.allocUnsafeSlow(sodium.crypto_sign_BYTES)
  sodium.crypto_sign_detached(signature, message, secretKey)
  return signature
}

exports.verify = function (message, signature, publicKey) {
  if (signature.byteLength !== sodium.crypto_sign_BYTES) return false
  if (publicKey.byteLength !== sodium.crypto_sign_PUBLICKEYBYTES) return false
  return sodium.crypto_sign_verify_detached(signature, message, publicKey)
}

exports.encrypt = function (message, publicKey) {
  const ciphertext = b4a.alloc(message.byteLength + sodium.crypto_box_SEALBYTES)
  sodium.crypto_box_seal(ciphertext, message, publicKey)
  return ciphertext
}

exports.decrypt = function (ciphertext, keyPair) {
  if (ciphertext.byteLength < sodium.crypto_box_SEALBYTES) return null

  const plaintext = b4a.alloc(ciphertext.byteLength - sodium.crypto_box_SEALBYTES)

  if (!sodium.crypto_box_seal_open(plaintext, ciphertext, keyPair.publicKey, keyPair.secretKey)) {
    return null
  }

  return plaintext
}

exports.encryptionKeyPair = function (seed) {
  const publicKey = b4a.alloc(sodium.crypto_box_PUBLICKEYBYTES)
  const secretKey = b4a.alloc(sodium.crypto_box_SECRETKEYBYTES)

  if (seed) {
    sodium.crypto_box_seed_keypair(publicKey, secretKey, seed)
  } else {
    sodium.crypto_box_keypair(publicKey, secretKey)
  }

  return {
    publicKey,
    secretKey
  }
}

exports.data = function (data) {
  const out = b4a.allocUnsafe(32)

  sodium.crypto_generichash_batch(out, [LEAF_TYPE, c.encode(c.uint64, data.byteLength), data])

  return out
}

exports.parent = function (a, b) {
  if (a.index > b.index) {
    const tmp = a
    a = b
    b = tmp
  }

  const out = b4a.allocUnsafe(32)

  sodium.crypto_generichash_batch(out, [
    PARENT_TYPE,
    c.encode(c.uint64, a.size + b.size),
    a.hash,
    b.hash
  ])

  return out
}

exports.tree = function (roots, out) {
  const buffers = new Array(3 * roots.length + 1)
  let j = 0

  buffers[j++] = ROOT_TYPE

  for (let i = 0; i < roots.length; i++) {
    const r = roots[i]
    buffers[j++] = r.hash
    buffers[j++] = c.encode(c.uint64, r.index)
    buffers[j++] = c.encode(c.uint64, r.size)
  }

  if (!out) out = b4a.allocUnsafe(32)
  sodium.crypto_generichash_batch(out, buffers)
  return out
}

exports.hash = function (data, out) {
  if (!out) out = b4a.allocUnsafe(32)
  if (!Array.isArray(data)) data = [data]

  sodium.crypto_generichash_batch(out, data)

  return out
}

exports.randomBytes = function (n) {
  const buf = b4a.allocUnsafe(n)
  sodium.randombytes_buf(buf)
  return buf
}

exports.discoveryKey = function (key) {
  if (!key || key.byteLength !== 32) throw new Error('Must pass a 32 byte buffer')
  // Discovery keys might stay around for a while, so better not to use slab memory (for better gc)
  const digest = b4a.allocUnsafeSlow(32)
  sodium.crypto_generichash(digest, HYPERCORE, key)
  return digest
}

if (sodium.sodium_free) {
  exports.free = function (secureBuf) {
    if (secureBuf.secure) sodium.sodium_free(secureBuf)
  }
} else {
  exports.free = function () {}
}

exports.namespace = function (name, count) {
  const ids = typeof count === 'number' ? range(count) : count

  // Namespaces are long-lived, so better to use a dedicated slab
  const buf = b4a.allocUnsafeSlow(32 * ids.length)

  const list = new Array(ids.length)

  // ns is ephemeral, so default slab
  const ns = b4a.allocUnsafe(33)
  sodium.crypto_generichash(ns.subarray(0, 32), typeof name === 'string' ? b4a.from(name) : name)

  for (let i = 0; i < list.length; i++) {
    list[i] = buf.subarray(32 * i, 32 * i + 32)
    ns[32] = ids[i]
    sodium.crypto_generichash(list[i], ns)
  }

  return list
}

function range(count) {
  const arr = new Array(count)
  for (let i = 0; i < count; i++) arr[i] = i
  return arr
}
{
  "name": "hypercore-crypto",
  "version": "3.7.0",
  "description": "The crypto primitives used in hypercore, extracted into a separate module",
  "main": "index.js",
  "files": [
    "index.js"
  ],
  "dependencies": {
    "b4a": "^1.6.6",
    "compact-encoding": "^3.0.0",
    "sodium-universal": "^5.0.0"
  },
  "devDependencies": {
    "brittle": "^3.5.0",
    "lunte": "^1.0.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^2.0.0"
  },
  "scripts": {
    "format": "prettier --write .",
    "lint": "prettier --check . && lunte",
    "test": "npm run test:node && npm run test:bare",
    "test:node": "brittle-node test.js",
    "test:bare": "brittle-bare test.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/mafintosh/hypercore-crypto.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/mafintosh/hypercore-crypto/issues"
  },
  "homepage": "https://github.com/mafintosh/hypercore-crypto"
}
const IdEnc = require('hypercore-id-encoding')

module.exports = class HypercoreError extends Error {
  constructor (msg, code, fn = HypercoreError, discoveryKey = null) {
    if (discoveryKey) msg = `${msg} (discovery key: ${IdEnc.normalize(discoveryKey)})`
    super(`${code}: ${msg}`)

    this.code = code
    this.discoveryKey = discoveryKey

    if (Error.captureStackTrace) {
      Error.captureStackTrace(this, fn)
    }
  }

  get name () {
    return 'HypercoreError'
  }

  static ASSERTION (msg, discoveryKey = null) { // ERR_ASSERTION is picked up by safety-catch also
    return new HypercoreError(msg, 'ERR_ASSERTION', HypercoreError.ASSERT, discoveryKey)
  }

  static BAD_ARGUMENT (msg, discoveryKey = null) {
    return new HypercoreError(msg, 'BAD_ARGUMENT', HypercoreError.BAD_ARGUMENT, discoveryKey)
  }

  static STORAGE_EMPTY (msg, discoveryKey = null) {
    return new HypercoreError(msg, 'STORAGE_EMPTY', HypercoreError.STORAGE_EMPTY, discoveryKey)
  }

  static STORAGE_CONFLICT (msg, discoveryKey = null) {
    return new HypercoreError(msg, 'STORAGE_CONFLICT', HypercoreError.STORAGE_CONFLICT, discoveryKey)
  }

  static INVALID_SIGNATURE (msg, discoveryKey = null) {
    return new HypercoreError(msg, 'INVALID_SIGNATURE', HypercoreError.INVALID_SIGNATURE, discoveryKey)
  }

  static INVALID_CAPABILITY (msg, discoveryKey = null) {
    return new HypercoreError(msg, 'INVALID_CAPABILITY', HypercoreError.INVALID_CAPABILITY, discoveryKey)
  }

  static INVALID_CHECKSUM (msg = 'Invalid checksum', discoveryKey = null) {
    return new HypercoreError(msg, 'INVALID_CHECKSUM', HypercoreError.INVALID_CHECKSUM, discoveryKey)
  }

  static INVALID_OPERATION (msg, discoveryKey = null) {
    return new HypercoreError(msg, 'INVALID_OPERATION', HypercoreError.INVALID_OPERATION, discoveryKey)
  }

  static INVALID_PROOF (msg = 'Proof not verifiable', discoveryKey = null) {
    return new HypercoreError(msg, 'INVALID_PROOF', HypercoreError.INVALID_PROOF, discoveryKey)
  }

  static BLOCK_NOT_AVAILABLE (msg = 'Block is not available', discoveryKey = null) {
    return new HypercoreError(msg, 'BLOCK_NOT_AVAILABLE', HypercoreError.BLOCK_NOT_AVAILABLE, discoveryKey)
  }

  static SNAPSHOT_NOT_AVAILABLE (msg = 'Snapshot is not available', discoveryKey = null) {
    return new HypercoreError(msg, 'SNAPSHOT_NOT_AVAILABLE', HypercoreError.SNAPSHOT_NOT_AVAILABLE, discoveryKey)
  }

  static REQUEST_CANCELLED (msg = 'Request was cancelled', discoveryKey = null) {
    return new HypercoreError(msg, 'REQUEST_CANCELLED', HypercoreError.REQUEST_CANCELLED, discoveryKey)
  }

  static REQUEST_TIMEOUT (msg = 'Request timed out', discoveryKey = null) {
    return new HypercoreError(msg, 'REQUEST_TIMEOUT', HypercoreError.REQUEST_TIMEOUT, discoveryKey)
  }

  static SESSION_NOT_WRITABLE (msg = 'Session is not writable', discoveryKey = null) {
    return new HypercoreError(msg, 'SESSION_NOT_WRITABLE', HypercoreError.SESSION_NOT_WRITABLE, discoveryKey)
  }

  static SESSION_CLOSED (msg = 'Session is closed', discoveryKey = null) {
    return new HypercoreError(msg, 'SESSION_CLOSED', HypercoreError.SESSION_CLOSED, discoveryKey)
  }

  static BATCH_UNFLUSHED (msg = 'Batch not yet flushed', discoveryKey = null) {
    return new HypercoreError(msg, 'BATCH_UNFLUSHED', HypercoreError.BATCH_UNFLUSHED, discoveryKey)
  }

  static BATCH_ALREADY_EXISTS (msg = 'Batch already exists', discoveryKey = null) {
    return new HypercoreError(msg, 'BATCH_ALREADY_EXISTS', HypercoreError.BATCH_ALREADY_EXISTS, discoveryKey)
  }

  static BATCH_ALREADY_FLUSHED (msg = 'Batch has already been flushed', discoveryKey = null) {
    return new HypercoreError(msg, 'BATCH_ALREADY_FLUSHED', HypercoreError.BATCH_ALREADY_FLUSHED, discoveryKey)
  }

  static OPLOG_CORRUPT (msg = 'Oplog file appears corrupt or out of date', discoveryKey = null) {
    return new HypercoreError(msg, 'OPLOG_CORRUPT', HypercoreError.OPLOG_CORRUPT, discoveryKey)
  }

  static OPLOG_HEADER_OVERFLOW (msg = 'Oplog header exceeds page size', discoveryKey = null) {
    return new HypercoreError(msg, 'OPLOG_HEADER_OVERFLOW', HypercoreError.OPLOG_HEADER_OVERFLOW, discoveryKey)
  }

  static INVALID_OPLOG_VERSION (msg = 'Invalid header version', discoveryKey = null) {
    return new HypercoreError(msg, 'INVALID_OPLOG_VERSION', HypercoreError.INVALID_OPLOG_VERSION, discoveryKey)
  }

  static WRITE_FAILED (msg = 'Write to storage failed', discoveryKey = null) {
    return new HypercoreError(msg, 'WRITE_FAILED', HypercoreError.WRITE_FAILED, discoveryKey)
  }

  static DECODING_ERROR (msg = 'Decoding error', discoveryKey = null) {
    return new HypercoreError(msg, 'DECODING_ERROR', HypercoreError.DECODING_ERROR, discoveryKey)
  }

  static SESSION_MOVED (msg = 'Session moved', discoveryKey = null) {
    return new HypercoreError(msg, 'SESSION_MOVED', HypercoreError.SESSION_MOVED, discoveryKey)
  }
}
{
  "name": "hypercore-errors",
  "version": "1.5.0",
  "description": "Hypercore errors",
  "main": "index.js",
  "files": [
    "index.js"
  ],
  "scripts": {
    "test": "standard && brittle test.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/holepunchto/hypercore-errors.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/hypercore-errors/issues"
  },
  "homepage": "https://github.com/holepunchto/hypercore-errors#readme",
  "devDependencies": {
    "b4a": "^1.6.7",
    "brittle": "^3.1.3",
    "standard": "^17.0.0"
  },
  "dependencies": {
    "hypercore-id-encoding": "^1.3.0"
  }
}
const z32 = require('z32')
const b4a = require('b4a')

module.exports = {
  encode,
  decode,
  normalize,
  isValid
}

function encode (key) {
  if (!b4a.isBuffer(key)) throw new Error('Key must be a Buffer')
  if (key.byteLength !== 32) throw new Error('Key must be 32-bytes long')
  return z32.encode(key)
}

function decode (id) {
  if (b4a.isBuffer(id)) {
    if (id.byteLength !== 32) throw new Error('ID must be 32-bytes long')
    return id
  }
  if (typeof id === 'string') {
    if (id.startsWith('pear://')) id = id.slice(7).split('/')[0]
    if (id.length === 52) return z32.decode(id)
    if (id.length === 64) {
      const buf = b4a.from(id, 'hex')
      if (buf.byteLength === 32) return buf
    }
  }
  throw new Error('Invalid Hypercore key')
}

function normalize (any) {
  return encode(decode(any))
}

function isValid (any) {
  try {
    decode(any)
    return true
  } catch {
    return false
  }
}
{
  "name": "hypercore-id-encoding",
  "version": "1.3.0",
  "description": "Convert Hypercore keys to/from z-base32 or hex",
  "main": "index.js",
  "scripts": {
    "test": "standard && brittle test.js"
  },
  "files": [
    "index.js"
  ],
  "dependencies": {
    "b4a": "^1.5.3",
    "z32": "^1.0.0"
  },
  "devDependencies": {
    "brittle": "^3.1.1",
    "hypercore": "^10.0.0",
    "random-access-memory": "^6.0.0",
    "standard": "^17.1.0"
  },
  "license": "Apache-2.0",
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/hypercore-id-encoding.git"
  },
  "keywords": [],
  "author": "",
  "bugs": {
    "url": "https://github.com/holepunchto/hypercore-id-encoding/issues"
  },
  "homepage": "https://github.com/holepunchto/hypercore-id-encoding#readme"
}
function headLegacyMap(m) {
  return {
    version: 2, // version bumped from 1
    cores: m.allocated.cores,
    datas: m.allocated.datas,
    groups: 0,
    seed: m.seed,
    defaultDiscoveryKey: m.defaultDiscoveryKey
  }
}

module.exports = {
  headLegacyMap
}
// This file is autogenerated by the hyperschema compiler
// Schema Version: 2
/* eslint-disable camelcase */
/* eslint-disable quotes */
/* eslint-disable space-before-function-paren */

const { c } = require('hyperschema/runtime')
const external0 = require('../../external.js')

const VERSION = 2

// eslint-disable-next-line no-unused-vars
let version = VERSION

// @corestore/allocated
const encoding0 = {
  preencode(state, m) {
    c.uint.preencode(state, m.cores)
    c.uint.preencode(state, m.datas)
  },
  encode(state, m) {
    c.uint.encode(state, m.cores)
    c.uint.encode(state, m.datas)
  },
  decode(state) {
    const r0 = c.uint.decode(state)
    const r1 = c.uint.decode(state)

    return {
      cores: r0,
      datas: r1
    }
  }
}

// @corestore/head-v1
const encoding1 = {
  preencode(state, m) {
    state.end++ // max flag is 4 so always one byte

    if (m.allocated) encoding0.preencode(state, m.allocated)
    if (m.seed) c.fixed32.preencode(state, m.seed)
    if (m.defaultDiscoveryKey) c.fixed32.preencode(state, m.defaultDiscoveryKey)
  },
  encode(state, m) {
    const flags = (m.allocated ? 1 : 0) | (m.seed ? 2 : 0) | (m.defaultDiscoveryKey ? 4 : 0)

    c.uint.encode(state, flags)

    if (m.allocated) encoding0.encode(state, m.allocated)
    if (m.seed) c.fixed32.encode(state, m.seed)
    if (m.defaultDiscoveryKey) c.fixed32.encode(state, m.defaultDiscoveryKey)
  },
  decode(state) {
    const v = c.uint.decode(state)
    const flags = c.uint.decode(state)

    return {
      version: v,
      allocated: (flags & 1) !== 0 ? encoding0.decode(state) : null,
      seed: (flags & 2) !== 0 ? c.fixed32.decode(state) : null,
      defaultDiscoveryKey: (flags & 4) !== 0 ? c.fixed32.decode(state) : null
    }
  }
}

// @corestore/head-v2
const encoding2 = {
  preencode(state, m) {
    c.uint.preencode(state, m.cores)
    c.uint.preencode(state, m.datas)
    c.uint.preencode(state, m.groups)
    state.end++ // max flag is 2 so always one byte

    if (m.seed) c.fixed32.preencode(state, m.seed)
    if (m.defaultDiscoveryKey) c.fixed32.preencode(state, m.defaultDiscoveryKey)
  },
  encode(state, m) {
    const flags = (m.seed ? 1 : 0) | (m.defaultDiscoveryKey ? 2 : 0)

    c.uint.encode(state, m.cores)
    c.uint.encode(state, m.datas)
    c.uint.encode(state, m.groups)
    c.uint.encode(state, flags)

    if (m.seed) c.fixed32.encode(state, m.seed)
    if (m.defaultDiscoveryKey) c.fixed32.encode(state, m.defaultDiscoveryKey)
  },
  decode(state) {
    const v = c.uint.decode(state)
    const r0 = c.uint.decode(state)
    const r1 = c.uint.decode(state)
    const r2 = c.uint.decode(state)
    const flags = c.uint.decode(state)

    return {
      version: v,
      cores: r0,
      datas: r1,
      groups: r2,
      seed: (flags & 1) !== 0 ? c.fixed32.decode(state) : null,
      defaultDiscoveryKey: (flags & 2) !== 0 ? c.fixed32.decode(state) : null
    }
  }
}

// @corestore/head
const encoding3 = {
  preencode(state, m) {
    c.uint.preencode(state, m.version)
    switch (m.version) {
      case 0:
      case 1:
        encoding1.preencode(state, m)
        break
      case 2:
        encoding2.preencode(state, m)
        break
      default:
        throw new Error('Unsupported version')
    }
  },
  encode(state, m) {
    c.uint.encode(state, m.version)
    switch (m.version) {
      case 0:
      case 1:
        encoding1.encode(state, m)
        break
      case 2:
        encoding2.encode(state, m)
        break
      default:
        throw new Error('Unsupported version')
    }
  },
  decode(state) {
    const start = state.start
    const v = c.uint.decode(state)
    state.start = start
    switch (v) {
      case 0:
      case 1: {
        const decoded = encoding1.decode(state)
        const map = external0.headLegacyMap
        return map(decoded)
      }
      case 2: {
        const decoded = encoding2.decode(state)
        return decoded
      }
      default:
        throw new Error('Unsupported version')
    }
  }
}

// @corestore/alias
const encoding4 = {
  preencode(state, m) {
    c.string.preencode(state, m.name)
    c.fixed32.preencode(state, m.namespace)
  },
  encode(state, m) {
    c.string.encode(state, m.name)
    c.fixed32.encode(state, m.namespace)
  },
  decode(state) {
    const r0 = c.string.decode(state)
    const r1 = c.fixed32.decode(state)

    return {
      name: r0,
      namespace: r1
    }
  }
}

// @corestore/core
const encoding5 = {
  preencode(state, m) {
    c.uint.preencode(state, m.version)
    c.uint.preencode(state, m.corePointer)
    c.uint.preencode(state, m.dataPointer)
    state.end++ // max flag is 1 so always one byte

    if (m.alias) encoding4.preencode(state, m.alias)
  },
  encode(state, m) {
    const flags = m.alias ? 1 : 0

    c.uint.encode(state, m.version)
    c.uint.encode(state, m.corePointer)
    c.uint.encode(state, m.dataPointer)
    c.uint.encode(state, flags)

    if (m.alias) encoding4.encode(state, m.alias)
  },
  decode(state) {
    const r0 = c.uint.decode(state)
    const r1 = c.uint.decode(state)
    const r2 = c.uint.decode(state)
    const flags = c.uint.decode(state)

    return {
      version: r0,
      corePointer: r1,
      dataPointer: r2,
      alias: (flags & 1) !== 0 ? encoding4.decode(state) : null
    }
  }
}

const encoding6_enum = {
  blake2b: 'blake2b'
}

// @core/hashes enum
const encoding6 = {
  preencode(state, m) {
    state.end++ // max enum is 0 so always one byte
  },
  encode(state, m) {
    switch (m) {
      case 'blake2b':
        c.uint.encode(state, 0)
        break
      default:
        throw new Error('Unknown enum')
    }
  },
  decode(state) {
    switch (c.uint.decode(state)) {
      case 0:
        return 'blake2b'
      default:
        return null
    }
  }
}

const encoding7_enum = {
  ed25519: 'ed25519'
}

// @core/signatures enum
const encoding7 = {
  preencode(state, m) {
    state.end++ // max enum is 0 so always one byte
  },
  encode(state, m) {
    switch (m) {
      case 'ed25519':
        c.uint.encode(state, 0)
        break
      default:
        throw new Error('Unknown enum')
    }
  },
  decode(state) {
    switch (c.uint.decode(state)) {
      case 0:
        return 'ed25519'
      default:
        return null
    }
  }
}

// @core/tree-node
const encoding8 = {
  preencode(state, m) {
    c.uint.preencode(state, m.index)
    c.uint.preencode(state, m.size)
    c.fixed32.preencode(state, m.hash)
  },
  encode(state, m) {
    c.uint.encode(state, m.index)
    c.uint.encode(state, m.size)
    c.fixed32.encode(state, m.hash)
  },
  decode(state) {
    const r0 = c.uint.decode(state)
    const r1 = c.uint.decode(state)
    const r2 = c.fixed32.decode(state)

    return {
      index: r0,
      size: r1,
      hash: r2
    }
  }
}

// @core/signer
const encoding9 = {
  preencode(state, m) {
    encoding7.preencode(state, m.signature)
    c.fixed32.preencode(state, m.namespace)
    c.fixed32.preencode(state, m.publicKey)
  },
  encode(state, m) {
    encoding7.encode(state, m.signature)
    c.fixed32.encode(state, m.namespace)
    c.fixed32.encode(state, m.publicKey)
  },
  decode(state) {
    const r0 = encoding7.decode(state)
    const r1 = c.fixed32.decode(state)
    const r2 = c.fixed32.decode(state)

    return {
      signature: r0,
      namespace: r1,
      publicKey: r2
    }
  }
}

// @core/prologue
const encoding10 = {
  preencode(state, m) {
    c.fixed32.preencode(state, m.hash)
    c.uint.preencode(state, m.length)
  },
  encode(state, m) {
    c.fixed32.encode(state, m.hash)
    c.uint.encode(state, m.length)
  },
  decode(state) {
    const r0 = c.fixed32.decode(state)
    const r1 = c.uint.decode(state)

    return {
      hash: r0,
      length: r1
    }
  }
}

// @core/manifest.signers
const encoding11_4 = c.array(encoding9)
// @core/manifest.linked
const encoding11_6 = c.array(c.fixed32)

// @core/manifest
const encoding11 = {
  preencode(state, m) {
    c.uint.preencode(state, m.version)
    state.end++ // max flag is 8 so always one byte
    encoding6.preencode(state, m.hash)
    c.uint.preencode(state, m.quorum)
    encoding11_4.preencode(state, m.signers)

    if (m.prologue) encoding10.preencode(state, m.prologue)
    if (m.linked) encoding11_6.preencode(state, m.linked)
    if (m.userData) c.buffer.preencode(state, m.userData)
  },
  encode(state, m) {
    const flags =
      (m.allowPatch ? 1 : 0) | (m.prologue ? 2 : 0) | (m.linked ? 4 : 0) | (m.userData ? 8 : 0)

    c.uint.encode(state, m.version)
    c.uint.encode(state, flags)
    encoding6.encode(state, m.hash)
    c.uint.encode(state, m.quorum)
    encoding11_4.encode(state, m.signers)

    if (m.prologue) encoding10.encode(state, m.prologue)
    if (m.linked) encoding11_6.encode(state, m.linked)
    if (m.userData) c.buffer.encode(state, m.userData)
  },
  decode(state) {
    const r0 = c.uint.decode(state)
    const flags = c.uint.decode(state)

    return {
      version: r0,
      hash: encoding6.decode(state),
      quorum: c.uint.decode(state),
      allowPatch: (flags & 1) !== 0,
      signers: encoding11_4.decode(state),
      prologue: (flags & 2) !== 0 ? encoding10.decode(state) : null,
      linked: (flags & 4) !== 0 ? encoding11_6.decode(state) : null,
      userData: (flags & 8) !== 0 ? c.buffer.decode(state) : null
    }
  }
}

// @core/keyPair
const encoding12 = {
  preencode(state, m) {
    c.buffer.preencode(state, m.publicKey)
    c.optionalBuffer.preencode(state, m.secretKey)
  },
  encode(state, m) {
    c.buffer.encode(state, m.publicKey)
    c.optionalBuffer.encode(state, m.secretKey)
  },
  decode(state) {
    const r0 = c.buffer.decode(state)
    const r1 = c.optionalBuffer.decode(state)

    return {
      publicKey: r0,
      secretKey: r1
    }
  }
}

// @core/group
const encoding13 = {
  preencode(state, m) {
    state.end++ // max flag is 2 so always one byte

    if (m.key) c.fixed32.preencode(state, m.key)
    if (m.pointer) c.uint.preencode(state, m.pointer)
  },
  encode(state, m) {
    const flags = (m.key ? 1 : 0) | (m.pointer ? 2 : 0)

    c.uint.encode(state, flags)

    if (m.key) c.fixed32.encode(state, m.key)
    if (m.pointer) c.uint.encode(state, m.pointer)
  },
  decode(state) {
    const flags = c.uint.decode(state)

    return {
      key: (flags & 1) !== 0 ? c.fixed32.decode(state) : null,
      pointer: (flags & 2) !== 0 ? c.uint.decode(state) : 0
    }
  }
}

// @core/auth.manifest
const encoding14_2 = c.frame(encoding11)

// @core/auth
const encoding14 = {
  preencode(state, m) {
    c.fixed32.preencode(state, m.key)
    c.fixed32.preencode(state, m.discoveryKey)
    state.end++ // max flag is 4 so always one byte

    if (m.manifest) encoding14_2.preencode(state, m.manifest)
    if (m.keyPair) encoding12.preencode(state, m.keyPair)
    if (m.encryptionKey) c.buffer.preencode(state, m.encryptionKey)
  },
  encode(state, m) {
    const flags = (m.manifest ? 1 : 0) | (m.keyPair ? 2 : 0) | (m.encryptionKey ? 4 : 0)

    c.fixed32.encode(state, m.key)
    c.fixed32.encode(state, m.discoveryKey)
    c.uint.encode(state, flags)

    if (m.manifest) encoding14_2.encode(state, m.manifest)
    if (m.keyPair) encoding12.encode(state, m.keyPair)
    if (m.encryptionKey) c.buffer.encode(state, m.encryptionKey)
  },
  decode(state) {
    const r0 = c.fixed32.decode(state)
    const r1 = c.fixed32.decode(state)
    const flags = c.uint.decode(state)

    return {
      key: r0,
      discoveryKey: r1,
      manifest: (flags & 1) !== 0 ? encoding14_2.decode(state) : null,
      keyPair: (flags & 2) !== 0 ? encoding12.decode(state) : null,
      encryptionKey: (flags & 4) !== 0 ? c.buffer.decode(state) : null
    }
  }
}

// @core/head
const encoding15 = {
  preencode(state, m) {
    c.uint.preencode(state, m.fork)
    c.uint.preencode(state, m.length)
    c.fixed32.preencode(state, m.rootHash)
    c.optionalBuffer.preencode(state, m.signature)
    state.end++ // max flag is 1 so always one byte

    if (version >= 2 && m.timestamp) c.uint64.preencode(state, m.timestamp)
  },
  encode(state, m) {
    const flags = version >= 2 && m.timestamp ? 1 : 0

    c.uint.encode(state, m.fork)
    c.uint.encode(state, m.length)
    c.fixed32.encode(state, m.rootHash)
    c.optionalBuffer.encode(state, m.signature)
    c.uint.encode(state, flags)

    if (version >= 2 && m.timestamp) c.uint64.encode(state, m.timestamp)
  },
  decode(state) {
    const r0 = c.uint.decode(state)
    const r1 = c.uint.decode(state)
    const r2 = c.fixed32.decode(state)
    const r3 = c.optionalBuffer.decode(state)
    const flags = state.start < state.end ? c.uint.decode(state) : 0

    return {
      fork: r0,
      length: r1,
      rootHash: r2,
      signature: r3,
      timestamp: version >= 2 && (flags & 1) !== 0 ? c.uint64.decode(state) : 0
    }
  }
}

// @core/hints
const encoding16 = {
  preencode(state, m) {
    state.end++ // max flag is 4 so always one byte

    if (m.contiguousLength) c.uint.preencode(state, m.contiguousLength)
    if (m.remoteContiguousLength) c.uint.preencode(state, m.remoteContiguousLength)
    if (m.recovering) c.uint.preencode(state, m.recovering)
  },
  encode(state, m) {
    const flags =
      (m.contiguousLength ? 1 : 0) | (m.remoteContiguousLength ? 2 : 0) | (m.recovering ? 4 : 0)

    c.uint.encode(state, flags)

    if (m.contiguousLength) c.uint.encode(state, m.contiguousLength)
    if (m.remoteContiguousLength) c.uint.encode(state, m.remoteContiguousLength)
    if (m.recovering) c.uint.encode(state, m.recovering)
  },
  decode(state) {
    const flags = c.uint.decode(state)

    return {
      contiguousLength: (flags & 1) !== 0 ? c.uint.decode(state) : 0,
      remoteContiguousLength: (flags & 2) !== 0 ? c.uint.decode(state) : 0,
      recovering: (flags & 4) !== 0 ? c.uint.decode(state) : 0
    }
  }
}

// @core/session
const encoding17 = {
  preencode(state, m) {
    c.string.preencode(state, m.name)
    c.uint.preencode(state, m.dataPointer)
  },
  encode(state, m) {
    c.string.encode(state, m.name)
    c.uint.encode(state, m.dataPointer)
  },
  decode(state) {
    const r0 = c.string.decode(state)
    const r1 = c.uint.decode(state)

    return {
      name: r0,
      dataPointer: r1
    }
  }
}

// @core/sessions
const encoding18 = c.array(encoding17)

// @core/dependency
const encoding19 = {
  preencode(state, m) {
    c.uint.preencode(state, m.dataPointer)
    c.uint.preencode(state, m.length)
  },
  encode(state, m) {
    c.uint.encode(state, m.dataPointer)
    c.uint.encode(state, m.length)
  },
  decode(state) {
    const r0 = c.uint.decode(state)
    const r1 = c.uint.decode(state)

    return {
      dataPointer: r0,
      length: r1
    }
  }
}

function setVersion(v) {
  version = v
}

function encode(name, value, v = VERSION) {
  version = v
  return c.encode(getEncoding(name), value)
}

function decode(name, buffer, v = VERSION) {
  version = v
  return c.decode(getEncoding(name), buffer)
}

function getEnum(name) {
  switch (name) {
    case '@core/hashes':
      return encoding6_enum
    case '@core/signatures':
      return encoding7_enum
    default:
      throw new Error('Enum not found ' + name)
  }
}

function getEncoding(name) {
  switch (name) {
    case '@corestore/allocated':
      return encoding0
    case '@corestore/head-v1':
      return encoding1
    case '@corestore/head-v2':
      return encoding2
    case '@corestore/head':
      return encoding3
    case '@corestore/alias':
      return encoding4
    case '@corestore/core':
      return encoding5
    case '@core/hashes':
      return encoding6
    case '@core/signatures':
      return encoding7
    case '@core/tree-node':
      return encoding8
    case '@core/signer':
      return encoding9
    case '@core/prologue':
      return encoding10
    case '@core/manifest':
      return encoding11
    case '@core/keyPair':
      return encoding12
    case '@core/group':
      return encoding13
    case '@core/auth':
      return encoding14
    case '@core/head':
      return encoding15
    case '@core/hints':
      return encoding16
    case '@core/session':
      return encoding17
    case '@core/sessions':
      return encoding18
    case '@core/dependency':
      return encoding19
    default:
      throw new Error('Encoder not found ' + name)
  }
}

function getStruct(name, v = VERSION) {
  const enc = getEncoding(name)
  return {
    preencode(state, m) {
      version = v
      enc.preencode(state, m)
    },
    encode(state, m) {
      version = v
      enc.encode(state, m)
    },
    decode(state) {
      version = v
      return enc.decode(state)
    }
  }
}

const resolveStruct = getStruct // compat

module.exports = {
  resolveStruct,
  getStruct,
  getEnum,
  getEncoding,
  encode,
  decode,
  setVersion,
  version
}
const RocksDB = require('rocksdb-native')
const rrp = require('resolve-reject-promise')
const ScopeLock = require('scope-lock')
const DeviceFile = require('device-file')
const path = require('path')
const fs = require('fs')
const Xache = require('xache')

const View = require('./lib/view.js')

const VERSION = 2
const COLUMN_FAMILY = 'corestore'

const { store, core } = require('./lib/keys.js')

const { CorestoreRX, CorestoreTX, CoreTX, CoreRX } = require('./lib/tx.js')

const {
  createCoreStream,
  createAliasStream,
  createDiscoveryKeyStream,
  createBlockStream,
  createBitfieldStream,
  createUserDataStream,
  createTreeNodeStream,
  createMarkStream,
  createLocalStream,
  createGroupUpdateStream
} = require('./lib/streams.js')

const EMPTY = new View()

class Atom {
  constructor(db) {
    this.db = db
    this.view = new View()
    this.flushedPromise = null
    this.flushing = false
    this.preflushes = []
    this.flushes = []
  }

  preflush(fn) {
    this.preflushes.push(fn)
  }

  onflush(fn) {
    this.flushes.push(fn)
  }

  flushed() {
    if (!this.flushing) return Promise.resolve()
    if (this.flushedPromise !== null) return this.flushedPromise.promise
    this.flushedPromise = rrp()
    return this.flushedPromise.promise
  }

  _resolve() {
    const f = this.flushedPromise
    this.flushedPromise = null
    f.resolve()
  }

  async flush() {
    if (this.flushing) throw new Error('Atom already flushing')
    this.flushing = true

    try {
      const plen = this.preflushes.length
      for (let i = 0; i < plen; i++) this.preflushes[i]()

      await View.flush(this.view.changes, this.db)

      const promises = []
      const len = this.flushes.length // in case of reentry
      for (let i = 0; i < len; i++) promises.push(this.flushes[i]())

      await Promise.all(promises)
    } finally {
      this.view.reset()
      this.flushing = false
      if (this.flushedPromise !== null) this._resolve()
    }
  }
}

class HypercoreStorage {
  constructor(store, db, treeCache, core, view, atom) {
    this.store = store
    this.db = db
    this.core = core
    this.view = view
    this.atom = atom

    this.view.readStart()
    this.treeCache = treeCache
  }

  get readOnly() {
    return this.store.readOnly
  }

  get dependencies() {
    return this.core.dependencies
  }

  getDependencyLength() {
    return this.core.dependencies.length
      ? this.core.dependencies[this.core.dependencies.length - 1].length
      : -1
  }

  getDependency(length) {
    for (let i = this.core.dependencies.length - 1; i >= 0; i--) {
      const dep = this.core.dependencies[i]
      if (dep.length < length) return dep
    }

    return null
  }

  setDependencyHead(dep) {
    if (dep === null) {
      this.core.dependencies = []
      return
    }

    const deps = this.core.dependencies

    for (let i = deps.length - 1; i >= 0; i--) {
      const d = deps[i]

      if (d.dataPointer !== dep.dataPointer) continue

      // check if nothing changed
      if (d.length === dep.length && i === deps.length - 1) return

      this.core = {
        corePointer: this.core.corePointer,
        dataPointer: this.core.dataPointer,
        dependencies: deps.slice(0, i + 1)
      }

      this.core.dependencies[i] = {
        dataPointer: dep.dataPointer,
        length: dep.length
      }
      return // updated head so done
    }

    this.core.dependencies = [
      {
        dataPointer: dep.dataPointer,
        length: dep.length
      }
    ]
  }

  // TODO: this might have to be async if the dependents have changed, but prop ok for now
  updateDependencyLength(length, truncated) {
    const deps = this.core.dependencies

    const i = this.findDependencyIndex(length, truncated)
    if (i === -1) throw new Error('Dependency not found')

    this.core = {
      corePointer: this.core.corePointer,
      dataPointer: this.core.dataPointer,
      dependencies: deps.slice(0, i + 1)
    }

    if (this.core.dependencies[i].length !== length) {
      this.core.dependencies[i] = {
        dataPointer: deps[i].dataPointer,
        length
      }
    }
  }

  findDependencyIndex(length, truncated) {
    const deps = this.core.dependencies

    if (truncated) {
      for (let i = 0; i < deps.length; i++) {
        if (deps[i].length >= length) return i
      }

      return -1
    }

    for (let i = deps.length - 1; i >= 0; i--) {
      if (deps[i].length <= length) return i
    }

    return -1
  }

  get snapshotted() {
    return this.db._snapshot !== null
  }

  snapshot() {
    return new HypercoreStorage(
      this.store,
      this.db.snapshot(),
      this.treeCache,
      this.core,
      this.view.snapshot(),
      this.atom
    )
  }

  compact() {
    return Promise.all([
      this.db.compactRange(core.core(this.core.corePointer), core.core(this.core.corePointer)),
      this.db.compactRange(core.data(this.core.dataPointer), core.data(this.core.dataPointer))
    ])
  }

  atomize(atom) {
    if (this.atom && this.atom !== atom) {
      throw new Error('Cannot atomize and atomized session with a new atom')
    }

    return new HypercoreStorage(
      this.store,
      this.db.session(),
      this.treeCache,
      this.core,
      atom.view,
      atom
    )
  }

  createAtom() {
    return this.store.createAtom()
  }

  createBlockStream(opts) {
    return createBlockStream(this.core, this.db, this.view, opts)
  }

  createTreeNodeStream(opts) {
    return createTreeNodeStream(this.core, this.db, this.view, opts)
  }

  createBitfieldStream(opts) {
    return createBitfieldStream(this.core, this.db, this.view, opts)
  }

  createUserDataStream(opts) {
    return createUserDataStream(this.core, this.db, this.view, opts)
  }

  createMarkStream(opts) {
    return createMarkStream(this.core, this.db, this.view, opts)
  }

  createLocalStream(opts) {
    return createLocalStream(this.core, this.db, this.view, opts)
  }

  async resumeSession(name) {
    const rx = this.read()
    const existingSessionsPromise = rx.getSessions()

    rx.tryFlush()
    const existingSessions = await existingSessionsPromise

    const sessions = existingSessions || []
    const session = getSession(sessions, name, false)

    if (session === null) return null

    const core = {
      corePointer: this.core.corePointer,
      dataPointer: session.dataPointer,
      dependencies: []
    }

    const coreRx = new CoreRX(core, this.db, this.view, this.treeCache, this.store.stats, -1)

    const dependencyPromise = coreRx.getDependency()
    coreRx.tryFlush()

    const dependency = await dependencyPromise
    if (dependency) core.dependencies = this._addDependency(dependency)

    return new HypercoreStorage(
      this.store,
      this.db.session(),
      this.treeCache,
      core,
      this.atom ? this.view : new View(),
      this.atom
    )
  }

  async createSession(name, head) {
    const rx = this.read()

    const existingSessionsPromise = rx.getSessions()
    const existingHeadPromise = rx.getHead()

    rx.tryFlush()

    const [existingSessions, existingHead] = await Promise.all([
      existingSessionsPromise,
      existingHeadPromise
    ])
    if (head === null) head = existingHead

    if (existingHead !== null && head.length > existingHead.length) {
      throw new Error('Invalid head passed, ahead of core')
    }

    const sessions = existingSessions || []
    const session = getSession(sessions, name, true)
    const fresh = session.dataPointer === -1

    if (fresh) {
      session.dataPointer = await this.store._allocData()
    }

    const tx = this.write()

    tx.setSessions(sessions)

    const length = head === null ? 0 : head.length
    const core = {
      corePointer: this.core.corePointer,
      dataPointer: session.dataPointer,
      dependencies: this._addDependency({
        dataPointer: this.core.dataPointer,
        length
      })
    }

    const coreTx = new CoreTX(core, this.db, tx.view, tx.changes)

    if (length > 0) coreTx.setHead(head)
    coreTx.setDependency(core.dependencies[core.dependencies.length - 1])

    if (!fresh) {
      // nuke all existing state...
      coreTx.deleteBlockRange(0, -1)
      coreTx.deleteTreeNodeRange(0, -1)
      coreTx.deleteBitfieldPageRange(0, -1)
    }

    await tx.flush()

    return new HypercoreStorage(
      this.store,
      this.db.session(),
      this.treeCache,
      core,
      this.atom ? this.view : new View(),
      this.atom
    )
  }

  async createAtomicSession(atom, head) {
    const length = head === null ? 0 : head.length
    const core = {
      corePointer: this.core.corePointer,
      dataPointer: this.core.dataPointer,
      dependencies: this._addDependency(null)
    }

    const coreTx = new CoreTX(core, this.db, atom.view, [])

    if (length > 0) coreTx.setHead(head)

    await coreTx.flush()

    return this.atomize(atom)
  }

  _addDependency(dep) {
    const deps = []

    for (let i = 0; i < this.core.dependencies.length; i++) {
      const d = this.core.dependencies[i]

      if (dep !== null && d.length > dep.length) {
        if (d.dataPointer !== dep.dataPointer) {
          deps.push({ dataPointer: d.dataPointer, length: dep.length })
        }
        return deps
      }

      deps.push(d)
    }

    if (
      dep !== null &&
      (deps.length === 0 || deps[deps.length - 1].dataPointer !== dep.dataPointer)
    ) {
      deps.push(dep)
    }
    return deps
  }

  async deleteSessions() {
    const rx = this.read()
    const existingSessionsPromise = rx.getSessions()
    rx.tryFlush()

    const existingSessions = await existingSessionsPromise

    // Remove batches
    const coreTx = this.write()
    coreTx.setSessions([]) // Clear sessions record
    await coreTx.flush()

    const tx = this.db.write({ autoDestroy: true })

    for (const { dataPointer } of existingSessions) {
      const start = core.data(dataPointer)
      const end = core.data(dataPointer + 1)
      tx.tryDeleteRange(start, end)
    }

    await tx.flush()
  }

  read(fork = -1) {
    let treeCache = null
    if (!this.atom) treeCache = this.treeCache

    return new CoreRX(this.core, this.db, this.view, treeCache, this.store.stats, fork)
  }

  write() {
    return new CoreTX(this.core, this.db, this.atom ? this.view : null, [])
  }

  close() {
    if (this.view !== null) {
      this.view.readStop()
      this.view = null
    }

    return this.db.close()
  }

  static async export(ptr, db, { batches = false } = {}) {
    const rx = new CoreRX(ptr, db, EMPTY, null, null, -1)

    const core = {
      head: null,
      auth: null,
      sessions: [],
      data: null
    }

    const sessionsPromise = rx.getSessions()
    const headPromise = rx.getHead()
    const authPromise = rx.getAuth()

    rx.tryFlush()

    const [sessions, head, auth] = await Promise.all([sessionsPromise, headPromise, authPromise])

    core.head = head
    core.auth = { ...auth, keyPair: null }
    if (sessions) core.sessions = sessions.map((s) => s.name)

    const data = []

    data.push(exportData(ptr, db))

    if (batches) {
      for (const { dataPointer } of sessions) {
        data.push(exportData({ dataPointer, dependencies: [] }, db))
      }
    }

    core.data = await Promise.all(data)

    return core
  }

  static isParentStorage(storage, parent) {
    const last = getLastDependency(storage)
    return !!last && last.dataPointer === parent.core.dataPointer
  }
}

class CorestoreStorage {
  constructor(db, opts = {}) {
    const storage = typeof db === 'string' ? db : null

    this.bootstrap = storage !== null
    this.path = storage !== null ? storage : path.join(db.path, '..')
    this.alwaysRecover = !!opts.alwaysRecover
    this.readOnly = !!opts.readOnly
    this.allowBackup = !!opts.allowBackup
    this.deviceFile = null
    this.wait = !!opts.wait

    // tmp sync fix for simplicty since not super deployed yet
    if (this.bootstrap && !this.readOnly) tmpFixStorage(this.path)

    this.id = opts.id || null
    this.view = null
    this.enters = 0
    this.lock = new ScopeLock()
    this.flushing = null
    this.version = 0
    this.migrating = null

    if ((this.bootstrap && !this.readOnly && !this.allowBackup) || this.wait) {
      const corestoreFile = path.join(this.path, 'CORESTORE')

      this.deviceFile = new DeviceFile(corestoreFile, {
        lock: true,
        wait: this.wait,
        data: { id: this.id }
      })
    }

    const dbPath = path.join(this.path, 'db')

    this.stats = {
      treeCache: {
        hits: 0,
        misses: 0,
        parallel: 0,
        skips: 0
      }
    }
    this.rocks = storage === null ? db : new RocksDB(dbPath, { ...opts, lock: this.deviceFile })
    this.db = createColumnFamily(this.rocks, opts)

    const { treeCache = { maxSize: 8192 } } = opts
    this.treeCache = new Xache(treeCache)
  }

  get opened() {
    return this.db.opened
  }

  get closed() {
    return this.db.closed
  }

  async ready() {
    if (this.version === 0) await this._migrateStore()
    return this.db.ready()
  }

  compact(opts) {
    return this.db.compactRange(opts)
  }

  async audit() {
    for await (const { core } of this.createCoreStream()) {
      const coreRx = new CoreRX(core, this.db, EMPTY, this.treeCache, this.stats, -1)
      const authPromise = coreRx.getAuth()

      coreRx.tryFlush()

      const auth = await authPromise

      if (!auth.manifest || auth.manifest.version > 0) continue
      if (auth.manifest.linked === null) continue

      auth.manifest.linked = null
      const coreTx = new CoreTX(core, this.db, null, [])
      coreTx.setAuth(auth)
      await coreTx.flush()
    }
  }

  async deleteCore(ptr) {
    const rx = new CoreRX(ptr, this.db, EMPTY, this.treeCache, this.stats, -1)

    const authPromise = rx.getAuth()
    const sessionsPromise = rx.getSessions()

    rx.tryFlush()

    const auth = await authPromise
    const sessions = await sessionsPromise

    // no core stored here
    if (!auth) return

    const tx = this.db.write({ autoDestroy: true })

    tx.tryDelete(store.core(auth.discoveryKey))

    // clear core
    const start = core.core(ptr.corePointer)
    const end = core.core(ptr.corePointer + 1)
    tx.tryDeleteRange(start, end)

    if (sessions) {
      for (const { dataPointer } of sessions) {
        const start = core.data(dataPointer)
        const end = core.data(dataPointer + 1)
        tx.tryDeleteRange(start, end)
      }
    }

    return tx.flush()
  }

  static isCoreStorage(db) {
    return isCorestoreStorage(db)
  }

  static from(db) {
    if (isCorestoreStorage(db)) return db
    return new this(db)
  }

  async _flush() {
    while (this.enters > 0) {
      await this.lock.lock()
      await this.lock.unlock()
    }
  }

  async _maybeRecover(view) {
    const r = path.join(this.db.path, 'RECOVERING')
    if (!(await fileExists(r))) return
    await this.setRecovering(view)
    await fs.promises.unlink(r)
  }

  _maybeRecoverLater(err) {
    if (!shouldRecover(err) && !this.alwaysRecover) return
    fs.writeFileSync(path.join(this.db.path, 'RECOVERING'), '')
    for (const file of fs.readdirSync(this.db.path)) {
      if (file.endsWith('.log')) fs.unlinkSync(path.join(this.db.path, file))
    }
  }

  // runs pre any other mutation and read
  async _migrateStore() {
    const view = await this._enter()

    try {
      if (this.version === VERSION) return

      await this._preopen

      try {
        await this.db.ready()
      } catch (err) {
        this._maybeRecoverLater(err)
        throw err
      }

      await this._maybeRecover(view)

      const rx = new CorestoreRX(this.db, view)
      const headPromise = rx.getHead()

      rx.tryFlush()
      const head = await headPromise

      const version = head === null ? 0 : head.version
      if (version === VERSION) {
        this.version = VERSION
        return
      }

      const target = { version: VERSION, dryRun: false }

      switch (version) {
        case 0: {
          await require('./migrations/0').store(this, target)
          break
        }
        case 1: {
          // implicit migration on write
          break
        }
        default: {
          throw new Error(
            'Unsupported version: ' + version + ' - you should probably upgrade your dependencies'
          )
        }
      }

      this.version = VERSION
    } finally {
      await this._exit()
    }
  }

  // runs pre the core is returned to the user
  async _migrateCore(core, discoveryKey, version, locked) {
    const view = locked ? this.view : await this._enter()
    try {
      if (version === VERSION) return

      const target = { version: VERSION, dryRun: false }

      switch (version) {
        case 0: {
          await require('./migrations/0').core(core, target)
          break
        }
        case 1: {
          await require('./migrations/1').core(core, target)
          break
        }
        default: {
          throw new Error(
            'Unsupported version: ' + version + ' - you should probably upgrade your dependencies'
          )
        }
      }

      if (locked === false) return

      // if its locked, then move the core state into the memview
      // in case the core is reopened from the memview, pre flush

      const rx = new CorestoreRX(this.db, EMPTY)
      const tx = new CorestoreTX(view)

      const corePromise = rx.getCore(discoveryKey)
      rx.tryFlush()

      tx.putCore(discoveryKey, await corePromise)
      tx.apply()
    } finally {
      if (!locked) await this._exit()
    }
  }

  async _enter() {
    this.enters++
    await this.lock.lock()
    if (this.view === null) this.view = new View()
    return this.view
  }

  async _exit() {
    this.enters--

    if (this.flushing === null) this.flushing = rrp()
    const flushed = this.flushing.promise

    if (this.enters === 0 || this.view.size() > 128) {
      try {
        await View.flush(this.view.changes, this.db)
        this.flushing.resolve()
      } catch (err) {
        this.flushing.reject(err)
      } finally {
        this.flushing = null
        this.view = null
      }
    }

    this.lock.unlock()
    return flushed
  }

  // when used with core catches this isnt transactional for simplicity, HOWEVER, its just a number
  // so worth the tradeoff
  async _allocData() {
    let dataPointer = 0

    const view = await this._enter()
    const tx = new CorestoreTX(view)

    try {
      const head = await this._getHead(view)

      dataPointer = head.datas++

      tx.setHead(head)
      tx.apply()
    } finally {
      await this._exit()
    }

    return dataPointer
  }

  // exposes here so migrations can easily access the head in an init state
  async _getHead(view) {
    const rx = new CorestoreRX(this.db, view)
    const headPromise = rx.getHead()
    rx.tryFlush()

    const head = await headPromise
    return head === null ? initStoreHead() : head
  }

  createAtom() {
    return new Atom(this.db)
  }

  async flush() {
    await this.rocks.flush()
  }

  async close() {
    if (this.db.closed) return
    await this._flush()
    await this.db.close()
    await this.rocks.close()
    this.treeCache.destroy()
    if (this.deviceFile) await this.deviceFile.close()
  }

  async clear() {
    if (this.version === 0) await this._migrateStore()

    const view = await this._enter()
    const tx = new CorestoreTX(view)

    tx.clear()
    tx.apply()

    await this._exit()
  }

  createCoreStream() {
    // TODO: be nice to run the mgiration here also, but too much plumbing atm
    return createCoreStream(this.db, EMPTY)
  }

  createAliasStream(namespace) {
    // TODO: be nice to run the mgiration here also, but too much plumbing atm
    return createAliasStream(this.db, EMPTY, namespace)
  }

  createDiscoveryKeyStream(namespace) {
    return createDiscoveryKeyStream(this.db, EMPTY, namespace)
  }

  createGroupUpdateStream(group, { since, reverse } = {}) {
    return createGroupUpdateStream(this.db, EMPTY, group, { gte: since, reverse })
  }

  async getAlias(alias) {
    if (this.version === 0) await this._migrateStore()

    const rx = new CorestoreRX(this.db, EMPTY)
    const discoveryKeyPromise = rx.getCoreByAlias(alias)
    rx.tryFlush()
    return discoveryKeyPromise
  }

  async getSeed() {
    if (this.version === 0) await this._migrateStore()

    const rx = new CorestoreRX(this.db, EMPTY)
    const headPromise = rx.getHead()

    rx.tryFlush()

    const head = await headPromise
    return head === null ? null : head.seed
  }

  async getGroup(topic) {
    if (this.version === 0) await this._migrateStore()

    const rx = new CorestoreRX(this.db, EMPTY)
    const groupPromise = rx.getGroup(topic)
    rx.tryFlush()
    return groupPromise
  }

  async setSeed(seed, { overwrite = true } = {}) {
    if (this.version === 0) await this._migrateStore()

    const view = await this._enter()
    const tx = new CorestoreTX(view)

    try {
      const rx = new CorestoreRX(this.db, view)
      const headPromise = rx.getHead()

      rx.tryFlush()

      const head = (await headPromise) || initStoreHead()

      if (head.seed === null || overwrite) head.seed = seed
      tx.setHead(head)
      tx.apply()

      return head.seed
    } finally {
      await this._exit()
    }
  }

  async setRecovering(view = null) {
    const recovering = Date.now()
    let keys = []

    for await (const data of this.createDiscoveryKeyStream()) {
      keys.push(data)
      if (keys.length > 16 * 1024) {
        await this._flushRecovering(recovering, view, keys)
        keys = []
      }
    }

    if (keys.length) await this._flushRecovering(recovering, view, keys)
  }

  async _flushRecovering(recovering, view, keys) {
    const own = !view
    if (own) view = await this._enter()
    const tx = new CorestoreTX(view)

    try {
      const infos = await this._getInfos(keys, { auth: false, head: false, hints: true })
      for (const inf of infos) {
        tx.setCoreHints(inf.core, { ...inf.hints, recovering })
      }
      tx.apply()
    } finally {
      if (own) await this._exit()
    }
  }

  async getDefaultDiscoveryKey() {
    if (this.version === 0) await this._migrateStore()

    const rx = new CorestoreRX(this.db, EMPTY)
    const headPromise = rx.getHead()

    rx.tryFlush()

    const head = await headPromise
    return head === null ? null : head.defaultDiscoveryKey
  }

  async setDefaultDiscoveryKey(discoveryKey, { overwrite = true } = {}) {
    if (this.version === 0) await this._migrateStore()

    const view = await this._enter()
    const tx = new CorestoreTX(view)

    try {
      const rx = new CorestoreRX(this.db, view)
      const headPromise = rx.getHead()

      rx.tryFlush()

      const head = (await headPromise) || initStoreHead()

      if (head.defaultDiscoveryKey === null || overwrite) head.defaultDiscoveryKey = discoveryKey
      tx.setHead(head)
      tx.apply()

      return head.defaultDiscoveryKey
    } finally {
      await this._exit()
    }
  }

  async hasCore(discoveryKey, { ifMigrated = false } = {}) {
    if (this.version === 0) await this._migrateStore()

    const rx = new CorestoreRX(this.db, EMPTY)
    const promise = rx.getCore(discoveryKey)

    rx.tryFlush()

    const core = await promise

    if (core === null) return false
    if (core.version === 0 && ifMigrated) return false

    return true
  }

  async getAuth(discoveryKey) {
    if (this.version === 0) await this._migrateStore()

    const rx = new CorestoreRX(this.db, EMPTY)
    const corePromise = rx.getCore(discoveryKey)

    rx.tryFlush()

    const core = await corePromise
    if (core === null) return null

    const read = this.db.read({ autoDestroy: true })
    const authPromise = CoreRX.getAuth(read, core)

    read.tryFlush()

    return authPromise
  }

  async getInfo(discoveryKey, opts) {
    return (await this.getInfos([discoveryKey], opts))[0]
  }

  async getInfos(discoveryKeys, opts) {
    if (this.version === 0) await this._migrateStore()
    return this._getInfos(discoveryKeys, opts)
  }

  async _getInfos(discoveryKeys, { auth = true, head = true, hints = true } = {}) {
    const rx = new CorestoreRX(this.db, EMPTY)
    const corePromises = new Array(discoveryKeys.length)

    for (let i = 0; i < discoveryKeys.length; i++) {
      corePromises[i] = rx.getCore(discoveryKeys[i])
    }

    rx.tryFlush()

    const cores = await Promise.all(corePromises)
    const read = this.db.read({ autoDestroy: true })

    const resultPromises = new Array(cores.length)

    for (let i = 0; i < cores.length; i++) {
      resultPromises[i] = cores[i]
        ? getInfoFromBatch(read, cores[i], discoveryKeys[i], auth, head, hints)
        : null
    }

    read.tryFlush()

    return Promise.all(resultPromises)
  }

  async suspend({ log = noop } = {}) {
    await log('Suspending hypercore storage...')
    await this.db.suspend()
    await log('db suspended')
    if (this.deviceFile) {
      await log('suspend deviceFile')
      await this.deviceFile.suspend()
      await log('deviceFile suspended')
    }
  }

  async resume() {
    if (this.deviceFile) await this.deviceFile.resume()
    await this.db.resume()
  }

  async resumeCore(discoveryKey) {
    if (this.version === 0) await this._migrateStore()

    if (!discoveryKey) {
      discoveryKey = await this.getDefaultDiscoveryKey()
      if (!discoveryKey) return null
    }

    const rx = new CorestoreRX(this.db, EMPTY)
    const corePromise = rx.getCore(discoveryKey)

    rx.tryFlush()
    const core = await corePromise

    if (core === null) return null
    return this._resumeFromPointers(EMPTY, discoveryKey, false, core)
  }

  async export(discoveryKey, opts) {
    const rx = new CorestoreRX(this.db, EMPTY)
    const corePromise = rx.getCore(discoveryKey)

    rx.tryFlush()
    const core = await corePromise
    if (core === null) return null

    let { dataPointer, corePointer } = core

    const ptr = { corePointer, dataPointer, dependencies: [] }

    while (true) {
      const rx = new CoreRX(
        { dataPointer, corePointer: 0, dependencies: [] },
        this.db,
        EMPTY,
        this.treeCache,
        this.stats,
        -1
      )
      const dependencyPromise = rx.getDependency()
      rx.tryFlush()
      const dependency = await dependencyPromise
      if (!dependency) break
      ptr.dependencies.push(dependency)
      dataPointer = dependency.dataPointer
    }

    const session = this.db.session()
    try {
      return await HypercoreStorage.export(ptr, session, opts)
    } finally {
      await session.close()
    }
  }

  async _resumeFromPointers(view, discoveryKey, create, { version, corePointer, dataPointer }) {
    const core = { corePointer, dataPointer, dependencies: [] }

    while (true) {
      const rx = new CoreRX(
        { dataPointer, corePointer: 0, dependencies: [] },
        this.db,
        view,
        this.treeCache,
        this.stats,
        -1
      )
      const dependencyPromise = rx.getDependency()
      rx.tryFlush()
      const dependency = await dependencyPromise
      if (!dependency) break
      core.dependencies.push(dependency)
      dataPointer = dependency.dataPointer
    }

    const result = new HypercoreStorage(this, this.db.session(), this.treeCache, core, EMPTY, null)

    if (version < VERSION) await this._migrateCore(result, discoveryKey, version, create)
    return result
  }

  // not allowed to throw validation errors as its a shared tx!
  async _create(
    view,
    { key, manifest, keyPair, encryptionKey, discoveryKey, alias, userData, core: ptrs }
  ) {
    const rx = new CorestoreRX(this.db, view)
    const tx = new CorestoreTX(view)

    const corePromise = rx.getCore(discoveryKey)
    const headPromise = rx.getHead()

    rx.tryFlush()

    let [core, head] = await Promise.all([corePromise, headPromise])
    if (core) return this._resumeFromPointers(view, discoveryKey, true, core)

    if (head === null) head = initStoreHead()
    if (head.defaultDiscoveryKey === null) head.defaultDiscoveryKey = discoveryKey

    const corePointer = ptrs ? ptrs.corePointer : head.cores++
    const dataPointer = ptrs ? ptrs.dataPointer : head.datas++

    core = { version: VERSION, corePointer, dataPointer, alias }

    tx.setHead(head)
    tx.putCore(discoveryKey, core)
    if (alias) tx.putCoreByAlias(alias, discoveryKey)

    const ptr = { corePointer, dataPointer, dependencies: [] }
    const ctx = new CoreTX(ptr, this.db, view, tx.changes)

    ctx.setAuth({
      key,
      discoveryKey,
      manifest,
      keyPair,
      encryptionKey
    })

    if (userData) {
      for (const { key, value } of userData) {
        ctx.putUserData(key, value)
      }
    }

    tx.apply()

    return new HypercoreStorage(this, this.db.session(), this.treeCache, ptr, EMPTY, null)
  }

  async createCore(data) {
    if (this.version === 0) await this._migrateStore()

    const view = await this._enter()

    try {
      return await this._create(view, data)
    } finally {
      await this._exit()
    }
  }

  // not allowed to throw validation errors as its a shared tx!
  async _createGroup(view, topic) {
    const rx = new CorestoreRX(this.db, view)
    const tx = new CorestoreTX(view)

    const groupPromise = rx.getGroup(topic)
    const headPromise = rx.getHead()

    rx.tryFlush()

    let group = await groupPromise
    if (group !== null) {
      return {
        key: topic,
        pointer: group
      }
    }

    let head = await headPromise
    if (head === null) head = initStoreHead()

    group = head.groups++

    tx.putGroup(topic, group)
    tx.setHead(head)
    tx.apply()

    return {
      key: topic,
      pointer: group
    }
  }

  async createGroup(topic) {
    if (this.version === 0) await this._migrateStore()

    const view = await this._enter()

    try {
      return await this._createGroup(view, topic)
    } finally {
      await this._exit()
    }
  }

  static isParentStorage(storage, parent) {
    return HypercoreStorage.isParentStorage(storage, parent)
  }
}

module.exports = CorestoreStorage

function initStoreHead() {
  return {
    version: 0, // cause we wanna run the migration
    datas: 0,
    cores: 0,
    groups: 0,
    seed: null,
    defaultDiscoveryKey: null
  }
}

function getSession(sessions, name, alloc) {
  for (let i = 0; i < sessions.length; i++) {
    if (sessions[i].name === name) return sessions[i]
  }

  if (!alloc) return null

  const result = { name, dataPointer: -1 }
  sessions.push(result)
  return result
}

function isCorestoreStorage(s) {
  return typeof s === 'object' && !!s && typeof s.setDefaultDiscoveryKey === 'function'
}

function createColumnFamily(db, opts = {}) {
  const {
    tableCacheIndexAndFilterBlocks = true,
    blockCache = true,
    optimizeFiltersForMemory = false,
    blobFileSize = 256 * 1024 * 1024,
    blobGarbageCollectionAgeCutOff = 0.25,
    blobGarbageCollectionForceThreshold = 1.0
  } = opts

  const col = new RocksDB.ColumnFamily(COLUMN_FAMILY, {
    enableBlobFiles: true,
    minBlobSize: 4096,
    blobFileSize,
    enableBlobGarbageCollection: true,
    tableBlockSize: 8192,
    tableCacheIndexAndFilterBlocks,
    tableFormatVersion: 6,
    optimizeFiltersForMemory,
    blockCache,
    blobGarbageCollectionAgeCutOff,
    blobGarbageCollectionForceThreshold
  })

  return db.columnFamily(col)
}

function getLastDependency(storage) {
  return storage.dependencies.length ? storage.dependencies[storage.dependencies.length - 1] : null
}

// TODO: remove in like 3-6 mo
function tmpFixStorage(p) {
  // if CORESTORE file is written, new format
  if (fs.existsSync(path.join(p, 'CORESTORE'))) return

  let files = []

  try {
    files = fs.readdirSync(p)
  } catch {}

  const notRocks = new Set([
    'CORESTORE',
    'primary-key',
    'cores',
    'app-preferences',
    'cache',
    'preferences.json',
    'db',
    'clone',
    'core',
    'notifications'
  ])

  for (const f of files) {
    if (notRocks.has(f)) continue

    try {
      fs.mkdirSync(path.join(p, 'db'))
    } catch {}

    fs.renameSync(path.join(p, f), path.join(p, 'db', f))
  }
}

async function exportData(ptr, db, opts) {
  // just need dataPointer
  const reads = [
    toArray(createBlockStream(ptr, db, EMPTY, opts)),
    toArray(createTreeNodeStream(ptr, db, EMPTY, opts)),
    toArray(createBitfieldStream(ptr, db, EMPTY, opts))
  ]

  const [blocks, tree, bitfield] = await Promise.all(reads)

  return {
    blocks,
    tree,
    bitfield
  }
}

async function toArray(stream) {
  const all = []
  for await (const e of stream) all.push(e)
  return all
}

async function fileExists(path) {
  try {
    await fs.promises.stat(path)
    return true
  } catch {
    return false
  }
}

function shouldRecover(err) {
  return err.message.toLowerCase().indexOf('sequence number is being set backwards') > -1
}

function noop() {}

async function getInfoFromBatch(db, c, discoveryKey, getAuth, getHead, getHints) {
  const authPromise = getAuth ? CoreRX.getAuth(db, c) : null
  const headPromise = getHead ? CoreRX.getHead(db, c) : null
  const hintsPromise = getHints ? CoreRX.getHints(db, c) : null

  // ensure no uncaughts
  if (authPromise) authPromise.catch(noop)
  if (headPromise) headPromise.catch(noop)
  if (hintsPromise) hintsPromise.catch(noop)

  return {
    discoveryKey,
    core: c,
    auth: await authPromise,
    head: await headPromise,
    hints: await hintsPromise
  }
}
const { Readable, getStreamError } = require('streamx')
const { core } = require('./keys')

module.exports = class BlockStream extends Readable {
  constructor(core, db, updates, start, end, reverse) {
    super()

    this.core = core
    this.db = db
    this.updates = updates
    this.start = start
    this.end = end
    this.reverse = reverse === true

    this._drained = true
    this._consumed = 0
    this._stream = null
    this._oncloseBound = this._onclose.bind(this)
    this._maybeDrainBound = this._maybeDrain.bind(this)

    this._update()
  }

  _update() {
    if (this._consumed > this.core.dependencies.length) return

    const deps = this.core.dependencies
    const index = this._findDependencyIndex(deps)

    const curr = index < deps.length ? deps[index] : null
    const prev = index > 0 && index - 1 < deps.length ? deps[index - 1] : null

    const start = prev && prev.length > this.start ? prev.length : this.start
    const end = curr && (this.end === -1 || curr.length < this.end) ? curr.length : this.end

    const ptr = curr ? curr.dataPointer : this.core.dataPointer

    this._makeStream(core.block(ptr, start), core.block(ptr, end))
  }

  _findDependencyIndex(deps) {
    if (!this.reverse) return this._consumed++

    let i = deps.length - this._consumed++
    while (i > 0) {
      if (deps[i - 1].length <= this.end) return i
      i--
      this._consumed++
    }

    return 0
  }

  _predestroy() {
    if (this._stream !== null) this._stream.destroy()
  }

  _read(cb) {
    this._drained = this._onreadable()
    cb(null)
  }

  _maybeDrain() {
    if (this._drained === true) return
    this._drained = this._onreadable()
  }

  _onreadable() {
    if (this._stream === null) {
      this.push(null)
      return true
    }

    let data = this._stream.read()

    if (data === null) return false

    do {
      this.push(data)
      data = this._stream.read()
    } while (data !== null)

    return true
  }

  _onclose() {
    if (this.destroying) return

    const err = getStreamError(this._stream)

    if (err !== null) {
      this.destroy(err)
      return
    }

    // empty the current stream
    if (this._onreadable() === true) this._drained = true

    this._stream = null

    this._update()
    this._maybeDrain()
  }

  _makeStream(start, end) {
    this._stream = this.updates.iterator(this.db, start, end, this.reverse)
    this._stream.on('readable', this._maybeDrainBound)
    this._stream.on('error', noop)
    this._stream.on('close', this._oncloseBound)
  }
}

function noop() {}
const { Readable } = require('streamx')

// used for returned a stream that just errors (during read during teardown)

module.exports = class CloseErrorStream extends Readable {
  constructor(err) {
    super()
    this.error = err
  }

  _open(cb) {
    cb(this.error)
  }
}
const { UINT, STRING } = require('index-encoder')
const c = require('compact-encoding')
const b4a = require('b4a')

const TL_HEAD = 0
const TL_CORE_BY_DKEY = 1
const TL_CORE_BY_ALIAS = 2
const TL_CORE = 3
const TL_DATA = 4
const TL_GROUP_BY_TOPIC = 5
const TL_GROUP = 6

const TL_END = TL_GROUP + 1

const CORE_AUTH = 0
const CORE_SESSIONS = 1
const CORE_GROUP = 2

const DATA_HEAD = 0
const DATA_DEPENDENCY = 1
const DATA_HINTS = 2
const DATA_BLOCK = 3
const DATA_TREE = 4
const DATA_BITFIELD = 5
const DATA_USER_DATA = 6
const DATA_LOCAL = 7
const DATA_MARK = 8

const slab = { buffer: b4a.allocUnsafe(65536), start: 0, end: 0 }

const store = {}
const core = {}
const group = {}

store.clear = function () {
  const state = alloc()
  let start = state.start
  UINT.encode(state, 0)
  const a = state.buffer.subarray(start, state.start)
  start = state.start
  UINT.encode(state, TL_END)
  const b = state.buffer.subarray(start, state.start)
  return [a, b]
}

store.head = function () {
  const state = alloc()
  const start = state.start
  UINT.encode(state, TL_HEAD)
  return state.buffer.subarray(start, state.start)
}

store.core = function (discoveryKey) {
  const state = alloc()
  const start = state.start
  UINT.encode(state, TL_CORE_BY_DKEY)
  c.fixed32.encode(state, discoveryKey)
  return state.buffer.subarray(start, state.start)
}

store.coreStart = function () {
  const state = alloc()
  const start = state.start
  UINT.encode(state, TL_CORE_BY_DKEY)
  return state.buffer.subarray(start, state.start)
}

store.coreEnd = function () {
  const state = alloc()
  const start = state.start
  UINT.encode(state, TL_CORE_BY_DKEY + 1)
  return state.buffer.subarray(start, state.start)
}

store.coreByAlias = function ({ namespace, name }) {
  const state = alloc()
  const start = state.start
  UINT.encode(state, TL_CORE_BY_ALIAS)
  c.fixed32.encode(state, namespace)
  STRING.encode(state, name)
  return state.buffer.subarray(start, state.start)
}

store.coreByAliasStart = function (namespace) {
  const state = alloc()
  const start = state.start
  UINT.encode(state, TL_CORE_BY_ALIAS)
  if (namespace) c.fixed32.encode(state, namespace)
  return state.buffer.subarray(start, state.start)
}

store.coreByAliasEnd = function (namespace) {
  const state = alloc()
  const start = state.start

  if (namespace) {
    UINT.encode(state, TL_CORE_BY_ALIAS)
    c.fixed32.encode(state, namespace)
    state.buffer[state.start++] = 0xff
  } else {
    UINT.encode(state, TL_CORE_BY_ALIAS + 1)
  }

  return state.buffer.subarray(start, state.start)
}

store.alias = function (buffer) {
  const state = { buffer, start: 0, end: buffer.byteLength }
  UINT.decode(state) // ns
  const namespace = c.fixed32.decode(state)
  const name = STRING.decode(state)
  return { namespace, name }
}

store.discoveryKey = function (buffer) {
  const state = { buffer, start: 0, end: buffer.byteLength }
  UINT.decode(state) // ns
  return c.fixed32.decode(state)
}

core.core = function (ptr) {
  const state = alloc()
  const start = state.start
  UINT.encode(state, TL_CORE)
  UINT.encode(state, ptr)
  return state.buffer.subarray(start, state.start)
}

core.data = function (ptr) {
  const state = alloc()
  const start = state.start
  UINT.encode(state, TL_DATA)
  UINT.encode(state, ptr)
  return state.buffer.subarray(start, state.start)
}

core.auth = function (ptr) {
  const state = alloc()
  const start = state.start
  UINT.encode(state, TL_CORE)
  UINT.encode(state, ptr)
  UINT.encode(state, CORE_AUTH)
  return state.buffer.subarray(start, state.start)
}

core.group = function (ptr) {
  const state = alloc()
  const start = state.start
  UINT.encode(state, TL_CORE)
  UINT.encode(state, ptr)
  UINT.encode(state, CORE_GROUP)
  return state.buffer.subarray(start, state.start)
}

core.sessions = function (ptr) {
  const state = alloc()
  const start = state.start
  UINT.encode(state, TL_CORE)
  UINT.encode(state, ptr)
  UINT.encode(state, CORE_SESSIONS)
  return state.buffer.subarray(start, state.start)
}

core.head = function (ptr) {
  const state = alloc()
  const start = state.start
  UINT.encode(state, TL_DATA)
  UINT.encode(state, ptr)
  UINT.encode(state, DATA_HEAD)
  return state.buffer.subarray(start, state.start)
}

core.dependency = function (ptr) {
  const state = alloc()
  const start = state.start
  UINT.encode(state, TL_DATA)
  UINT.encode(state, ptr)
  UINT.encode(state, DATA_DEPENDENCY)
  return state.buffer.subarray(start, state.start)
}

core.hints = function (ptr) {
  const state = alloc()
  const start = state.start
  UINT.encode(state, TL_DATA)
  UINT.encode(state, ptr)
  UINT.encode(state, DATA_HINTS)
  return state.buffer.subarray(start, state.start)
}

core.block = function (ptr, index) {
  const state = alloc()
  const start = state.start
  UINT.encode(state, TL_DATA)
  UINT.encode(state, ptr)
  UINT.encode(state, DATA_BLOCK)
  UINT.encode(state, index)
  return state.buffer.subarray(start, state.start)
}

core.tree = function (ptr, index) {
  const state = alloc()
  const start = state.start
  UINT.encode(state, TL_DATA)
  UINT.encode(state, ptr)
  UINT.encode(state, DATA_TREE)
  UINT.encode(state, index)
  return state.buffer.subarray(start, state.start)
}

core.bitfield = function (ptr, index, type) {
  const state = alloc()
  const start = state.start
  UINT.encode(state, TL_DATA)
  UINT.encode(state, ptr)
  UINT.encode(state, DATA_BITFIELD)
  UINT.encode(state, index)
  UINT.encode(state, type)
  return state.buffer.subarray(start, state.start)
}

core.userData = function (ptr, key) {
  const state = alloc()
  const start = state.start
  UINT.encode(state, TL_DATA)
  UINT.encode(state, ptr)
  UINT.encode(state, DATA_USER_DATA)
  STRING.encode(state, key)
  return state.buffer.subarray(start, state.start)
}

core.userDataEnd = function (ptr) {
  const state = alloc()
  const start = state.start
  UINT.encode(state, TL_DATA)
  UINT.encode(state, ptr)
  UINT.encode(state, DATA_USER_DATA + 1)
  return state.buffer.subarray(start, state.start)
}

core.mark = function (ptr, index) {
  const state = alloc()
  const start = state.start
  UINT.encode(state, TL_DATA)
  UINT.encode(state, ptr)
  UINT.encode(state, DATA_MARK)
  UINT.encode(state, index)
  return state.buffer.subarray(start, state.start)
}

core.local = function (ptr, key) {
  if (key.byteLength > 2048) {
    throw new Error('local keys has an upper limit of 2048 bytes atm')
  }

  const state = alloc()
  const start = state.start
  UINT.encode(state, TL_DATA)
  UINT.encode(state, ptr)
  UINT.encode(state, DATA_LOCAL)

  state.buffer.set(key, state.start)
  state.start += key.byteLength
  return state.buffer.subarray(start, state.start)
}

core.localEnd = function (ptr) {
  const state = alloc()
  const start = state.start
  UINT.encode(state, TL_DATA)
  UINT.encode(state, ptr)
  UINT.encode(state, DATA_LOCAL + 1)
  return state.buffer.subarray(start, state.start)
}

core.blockIndex = function (buffer) {
  const state = { buffer, start: 0, end: buffer.byteLength }
  UINT.decode(state) // ns
  UINT.decode(state) // ptr
  UINT.decode(state) // type
  return UINT.decode(state)
}

core.bitfieldIndexAndType = function (buffer) {
  const state = { buffer, start: 0, end: buffer.byteLength }
  UINT.decode(state) // ns
  UINT.decode(state) // ptr
  UINT.decode(state) // type
  return [UINT.decode(state), UINT.decode(state)]
}

core.userDataKey = function (buffer) {
  const state = { buffer, start: 0, end: buffer.byteLength }
  UINT.decode(state) // ns
  UINT.decode(state) // ptr
  UINT.decode(state) // type
  return STRING.decode(state)
}

core.markKey = function (buffer) {
  const state = { buffer, start: 0, end: buffer.byteLength }
  UINT.decode(state) // ns
  UINT.decode(state) // ptr
  UINT.decode(state) // type
  return UINT.decode(state)
}

core.localKey = function (buffer) {
  const state = { buffer, start: 0, end: buffer.byteLength }
  UINT.decode(state) // ns
  UINT.decode(state) // ptr
  UINT.decode(state) // type
  return state.buffer.subarray(state.start, state.end)
}

group.topic = function (topic) {
  const state = alloc()
  const start = state.start
  UINT.encode(state, TL_GROUP_BY_TOPIC)
  c.fixed32.encode(state, topic)
  return state.buffer.subarray(start, state.start)
}

group.entry = function (group, timestamp, ptr) {
  const state = alloc()
  const start = state.start
  UINT.encode(state, TL_GROUP)
  UINT.encode(state, group)
  c.uint64be.encode(state, timestamp)
  UINT.encode(state, ptr)
  return state.buffer.subarray(start, state.start)
}

module.exports = { store, core, group }

function alloc() {
  if (slab.buffer.byteLength - slab.start < 4096) {
    slab.buffer = b4a.allocUnsafe(slab.buffer.byteLength)
    slab.start = 0
  }
  return slab
}
const b4a = require('b4a')
const BlockDependencyStream = require('./block-dependency-stream.js')
const { core, store, group } = require('./keys.js')
const schema = require('../encoding/spec/hyperschema')

const CORESTORE_CORE = schema.getEncoding('@corestore/core')
const CORE_TREE_NODE = schema.getEncoding('@core/tree-node')
const EMPTY = b4a.alloc(0)

module.exports = {
  createBlockStream,
  createBitfieldStream,
  createUserDataStream,
  createCoreStream,
  createAliasStream,
  createDiscoveryKeyStream,
  createTreeNodeStream,
  createMarkStream,
  createLocalStream,
  createGroupUpdateStream
}

function createCoreStream(db, view) {
  const start = store.coreStart()
  const end = store.coreEnd()

  const ite = view.iterator(db, start, end, false)

  ite._readableState.map = mapCore
  return ite
}

function createDiscoveryKeyStream(db, view, namespace) {
  const start = namespace ? store.coreByAliasStart(namespace) : store.coreStart()
  const end = namespace ? store.coreByAliasEnd(namespace) : store.coreEnd()

  const ite = view.iterator(db, start, end, false)

  ite._readableState.map = namespace ? mapNamespaceDiscoveryKeys : mapAllDiscoveryKeys
  return ite
}

function createAliasStream(db, view, namespace) {
  const start = store.coreByAliasStart(namespace)
  const end = store.coreByAliasEnd(namespace)

  const ite = view.iterator(db, start, end, false)

  ite._readableState.map = mapAlias
  return ite
}

function createGroupUpdateStream(db, view, groupID, { gte = 0, reverse = true } = {}) {
  const start = group.entry(groupID, gte, 0)
  const end = group.entry(groupID + 1, 0, 0)

  const ite = view.iterator(db, start, end, reverse)

  ite._readableState.map = mapGroupUpdate
  return ite
}

function createBlockIterator(ptr, db, view, start, end, reverse) {
  if (ptr.dependencies.length > 0) {
    return new BlockDependencyStream(ptr, db, view, start, end, reverse)
  }

  const s = core.block(ptr.dataPointer, start)
  const e = core.block(ptr.dataPointer, end === -1 ? Infinity : end)
  return view.iterator(db, s, e, reverse)
}

function createBlockStream(
  ptr,
  db,
  view,
  { gt = -1, gte = gt + 1, lte = -1, lt = lte === -1 ? -1 : lte + 1, reverse = false } = {}
) {
  const ite = createBlockIterator(ptr, db, view, gte, lt, reverse)

  ite._readableState.map = mapBlock
  return ite
}

function createBitfieldStream(
  ptr,
  db,
  view,
  { gt = -1, gte = gt + 1, lte = -1, lt = lte === -1 ? -1 : lte + 1, reverse = false } = {}
) {
  const s = core.bitfield(ptr.dataPointer, gte, 0)
  const e = core.bitfield(ptr.dataPointer, lt === -1 ? Infinity : lt, 0)
  const ite = view.iterator(db, s, e, false)

  ite._readableState.map = mapBitfield
  return ite
}

// NOTE: this does not do dependency lookups atm
function createTreeNodeStream(
  ptr,
  db,
  view,
  { gt = -1, gte = gt + 1, lte = -1, lt = lte === -1 ? -1 : lte + 1, reverse = false } = {}
) {
  const s = core.tree(ptr.dataPointer, gte, 0)
  const e = core.tree(ptr.dataPointer, lt === -1 ? Infinity : lt, 0)
  const ite = view.iterator(db, s, e, false)

  ite._readableState.map = mapTreeNode
  return ite
}

function createUserDataStream(
  ptr,
  db,
  view,
  { gt = null, gte = '', lte = null, lt = null, reverse = false } = {}
) {
  if (gt !== null || lte !== null) {
    throw new Error('gt and lte not yet supported for user data streams')
  }

  const s = core.userData(ptr.dataPointer, gte)
  const e = lt === null ? core.userDataEnd(ptr.dataPointer) : core.userData(ptr.dataPointer, lt)
  const ite = view.iterator(db, s, e, false)

  ite._readableState.map = mapUserData
  return ite
}

function createMarkStream(
  ptr,
  db,
  view,
  { gt = -1, gte = gt + 1, lte = -1, lt = lte === -1 ? -1 : lte + 1, reverse = false } = {}
) {
  const s = core.mark(ptr.dataPointer, gte)
  const e = core.mark(ptr.dataPointer, lt === -1 ? Infinity : lt)
  const ite = view.iterator(db, s, e, reverse)

  ite._readableState.map = mapMark
  return ite
}

function createLocalStream(
  ptr,
  db,
  view,
  { gt = null, gte = EMPTY, lte = null, lt = null, reverse = false } = {}
) {
  if (gt !== null || lte !== null) throw new Error('gt and lte not yet supported for local streams')

  const s = core.local(ptr.dataPointer, gte)
  const e = lt === null ? core.localEnd(ptr.dataPointer) : core.local(ptr.dataPointer, lt)
  const ite = view.iterator(db, s, e, false)

  ite._readableState.map = mapLocal
  return ite
}

function mapBitfield(data) {
  const [index, type] = core.bitfieldIndexAndType(data.key)
  if (type !== 0) return null // ignore for now
  return { index, page: data.value }
}

function mapMark(data) {
  const index = core.markKey(data.key)
  return { index, page: data.value }
}

function mapLocal(data) {
  const key = core.localKey(data.key)
  return { key, value: data.value }
}

function mapGroupUpdate(data) {
  return data.value
}

function mapUserData(data) {
  const key = core.userDataKey(data.key)
  return { key, value: data.value }
}

function mapCore(data) {
  const discoveryKey = store.discoveryKey(data.key)
  const core = CORESTORE_CORE.decode({
    start: 0,
    end: data.value.byteLength,
    buffer: data.value
  })
  return { discoveryKey, core }
}

function mapAllDiscoveryKeys(data) {
  return store.discoveryKey(data.key)
}

function mapNamespaceDiscoveryKeys(data) {
  return data.value
}

function mapAlias(data) {
  const alias = store.alias(data.key)
  return { alias, discoveryKey: data.value }
}

function mapBlock(data) {
  return { index: core.blockIndex(data.key), value: data.value }
}

function mapTreeNode(data) {
  return CORE_TREE_NODE.decode({
    start: 0,
    end: data.value.byteLength,
    buffer: data.value
  })
}
const b4a = require('b4a')
const c = require('compact-encoding')
const flat = require('flat-tree')

const schema = require('../encoding/spec/hyperschema')
const { store, core, group } = require('./keys.js')
const View = require('./view.js')

const CORESTORE_HEAD = schema.getEncoding('@corestore/head')
const CORESTORE_CORE = schema.getEncoding('@corestore/core')
const CORESTORE_GROUP = c.uint

const CORE_AUTH = schema.getEncoding('@core/auth')
const CORE_GROUP = schema.getEncoding('@core/group')
const CORE_SESSIONS = schema.getEncoding('@core/sessions')
const CORE_HEAD = schema.getEncoding('@core/head')
const CORE_TREE_NODE = schema.getEncoding('@core/tree-node')
const CORE_DEPENDENCY = schema.getEncoding('@core/dependency')
const CORE_HINTS = schema.getEncoding('@core/hints')

class CoreTX {
  constructor(core, db, view, changes) {
    if (db.snapshotted) throw new Error('Cannot open core tx on snapshot')
    this.core = core
    this.db = db
    this.view = view
    this.changes = changes
  }

  setAuth(auth) {
    this.changes.push([core.auth(this.core.corePointer), encode(CORE_AUTH, auth), null])
  }

  setSessions(sessions) {
    this.changes.push([core.sessions(this.core.corePointer), encode(CORE_SESSIONS, sessions), null])
  }

  setHead(head) {
    this.changes.push([core.head(this.core.dataPointer), encode(CORE_HEAD, head), null])
  }

  deleteHead() {
    this.changes.push([core.head(this.core.dataPointer), null, null])
  }

  setDependency(dep) {
    this.changes.push([core.dependency(this.core.dataPointer), encode(CORE_DEPENDENCY, dep), null])
  }

  setGroup(group) {
    this.changes.push([core.group(this.core.corePointer), encode(CORE_GROUP, group), null])
  }

  deleteGroup() {
    this.changes.push([core.group(this.core.corePointer), null, null])
  }

  setHints(hints) {
    this.changes.push([core.hints(this.core.dataPointer), encode(CORE_HINTS, hints), null])
  }

  putBlock(index, data) {
    this.changes.push([core.block(this.core.dataPointer, index), data, null])
  }

  deleteBlock(index) {
    this.changes.push([core.block(this.core.dataPointer, index), null, null])
  }

  deleteBlockRange(start, end) {
    this.changes.push([
      core.block(this.core.dataPointer, start),
      null,
      core.block(this.core.dataPointer, end === -1 ? Infinity : end)
    ])
  }

  putBitfieldPage(index, data) {
    this.changes.push([core.bitfield(this.core.dataPointer, index, 0), data, null])
  }

  deleteBitfieldPage(index) {
    this.changes.push([core.bitfield(this.core.dataPointer, index, 0), null, null])
  }

  deleteBitfieldPageRange(start, end) {
    this.changes.push([
      core.bitfield(this.core.dataPointer, start, 0),
      null,
      core.bitfield(this.core.dataPointer, end === -1 ? Infinity : end, 0)
    ])
  }

  putTreeNode(node) {
    this.changes.push([
      core.tree(this.core.dataPointer, node.index),
      encode(CORE_TREE_NODE, node),
      null
    ])
  }

  deleteTreeNode(index) {
    this.changes.push([core.tree(this.core.dataPointer, index), null, null])
  }

  deleteTreeNodeRange(start, end) {
    this.changes.push([
      core.tree(this.core.dataPointer, start),
      null,
      core.tree(this.core.dataPointer, end === -1 ? Infinity : end)
    ])
  }

  putUserData(key, value) {
    const buffer = typeof value === 'string' ? b4a.from(value) : value
    this.changes.push([core.userData(this.core.dataPointer, key), buffer, null])
  }

  deleteUserData(key) {
    this.changes.push([core.userData(this.core.dataPointer, key), null, null])
  }

  putMark(index, data) {
    this.changes.push([core.mark(this.core.dataPointer, index), data, null])
  }

  deleteMark(index) {
    this.changes.push([core.mark(this.core.dataPointer, index), null, null])
  }

  deleteMarkRange(start, end) {
    this.changes.push([
      core.mark(this.core.dataPointer, start),
      null,
      core.mark(this.core.dataPointer, end === -1 ? Infinity : end)
    ])
  }

  putLocal(key, value) {
    this.changes.push([core.local(this.core.dataPointer, key), value, null])
  }

  deleteLocal(key) {
    this.changes.push([core.local(this.core.dataPointer, key), null, null])
  }

  deleteLocalRange(start, end) {
    this.changes.push([
      core.local(this.core.dataPointer, start),
      null,
      end === null ? core.localEnd(this.core.dataPointer) : core.local(this.core.dataPointer, end)
    ])
  }

  putGroupUpdate(groupPtr, timestamp, key) {
    this.changes.push([group.entry(groupPtr, timestamp, this.core.corePointer), key, null])
  }

  deleteGroupUpdate(groupPtr, timestamp) {
    this.changes.push([group.entry(groupPtr, timestamp, this.core.corePointer), null, null])
  }

  flush() {
    const changes = this.changes
    if (changes === null) return Promise.resolve(!this.view)

    this.changes = null

    if (this.view) {
      this.view.apply(changes)
      return Promise.resolve(false)
    }

    return View.flush(changes, this.db)
  }
}

class CoreRX {
  constructor(core, db, view, treeCache, stats, fork) {
    this.core = core
    this.read = db.read({ autoDestroy: true })
    this.view = view
    this.treeCache = treeCache
    this.fork = fork
    this.stats = stats

    view.readStart()
  }

  static async getAuth(db, c) {
    return decode(CORE_AUTH, await db.get(core.auth(c.corePointer)))
  }

  async getAuth() {
    return decode(CORE_AUTH, await this.view.get(this.read, core.auth(this.core.corePointer)))
  }

  async getSessions() {
    return decode(
      CORE_SESSIONS,
      await this.view.get(this.read, core.sessions(this.core.corePointer))
    )
  }

  static async getHead(db, c) {
    return decode(CORE_HEAD, await db.get(core.head(c.dataPointer)))
  }

  async getHead() {
    return decode(CORE_HEAD, await this.view.get(this.read, core.head(this.core.dataPointer)))
  }

  async getDependency() {
    return decode(
      CORE_DEPENDENCY,
      await this.view.get(this.read, core.dependency(this.core.dataPointer))
    )
  }

  async getGroup() {
    return await decode(
      CORE_GROUP,
      await this.view.get(this.read, core.group(this.core.corePointer))
    )
  }

  static async getHints(db, c) {
    return decode(CORE_HINTS, await db.get(core.hints(c.dataPointer)))
  }

  async getHints() {
    return decode(CORE_HINTS, await this.view.get(this.read, core.hints(this.core.dataPointer)))
  }

  getBlock(index) {
    const dep = findBlockDependency(this.core.dependencies, index)
    const data = dep === null ? this.core.dataPointer : dep.dataPointer
    return this.view.get(this.read, core.block(data, index))
  }

  getBitfieldPage(index) {
    return this.view.get(this.read, core.bitfield(this.core.dataPointer, index, 0))
  }

  async _resolveTreeCache(key, promise, entry) {
    try {
      const value = decode(CORE_TREE_NODE, await promise)
      const cached = this.treeCache.get(key)

      // preflight is gone; no-op
      if (cached !== entry) return value

      if (value !== null) {
        // replace cached entry with value
        this.treeCache.set(key, { pending: null, value })
      } else {
        // discard non-existing values
        this.treeCache.delete(key)
      }

      return value
    } catch (err) {
      // cleanup
      const cached = this.treeCache.get(key)
      if (cached === entry) {
        this.treeCache.delete(key)
      }

      throw err
    }
  }

  async getTreeNode(index) {
    const dep = findTreeDependency(this.core.dependencies, index)
    const data = dep === null ? this.core.dataPointer : dep.dataPointer

    if (!this.treeCache || this.fork < 0) {
      if (this.stats !== null) this.stats.treeCache.skips++
      // no-cache
      return decode(CORE_TREE_NODE, await this.view.get(this.read, core.tree(data, index)))
    }

    const key = `${data}:${index}:${this.fork}`
    let entry = this.treeCache.get(key)
    if (entry) {
      if (entry.pending) {
        if (this.stats !== null) this.stats.treeCache.parallel++
        return entry.pending
      }

      if (this.stats !== null) this.stats.treeCache.hits++
      return entry.value
    }

    if (this.stats !== null) this.stats.treeCache.misses++

    const promise = this.view.get(this.read, core.tree(data, index))
    entry = { pending: null, value: null }
    entry.pending = this._resolveTreeCache(key, promise, entry)
    this.treeCache.set(key, entry)

    return entry.pending
  }

  async hasTreeNode(index) {
    return (await this.getTreeNode(index)) !== null
  }

  getUserData(key) {
    return this.view.get(this.read, core.userData(this.core.dataPointer, key))
  }

  getMark(index) {
    return this.view.get(this.read, core.mark(this.core.dataPointer, index))
  }

  getLocal(key) {
    return this.view.get(this.read, core.local(this.core.dataPointer, key))
  }

  tryFlush() {
    this.read.tryFlush()
    this._free()
  }

  destroy() {
    this.read.destroy()
    this._free()
  }

  _free() {
    if (this.view === null) return
    this.view.readStop()
    this.view = null
  }
}

class CorestoreTX {
  constructor(view) {
    this.view = view
    this.changes = []
  }

  setHead(head) {
    this.changes.push([store.head(), encode(CORESTORE_HEAD, head), null])
  }

  setCoreHints(c, hints) {
    this.changes.push([core.hints(c.dataPointer), encode(CORE_HINTS, hints), null])
  }

  putCore(discoveryKey, ptr) {
    this.changes.push([store.core(discoveryKey), encode(CORESTORE_CORE, ptr), null])
  }

  putCoreByAlias(alias, discoveryKey) {
    this.changes.push([store.coreByAlias(alias), discoveryKey, null])
  }

  putGroup(topic, groupPtr) {
    this.changes.push([group.topic(topic), encode(CORESTORE_GROUP, groupPtr), null])
  }

  deleteGroup(topic) {
    this.changes.push([group.topic(topic), null, null])
  }

  clear() {
    const [start, end] = store.clear()
    this.changes.push([start, null, end])
  }

  apply() {
    if (this.changes === null) return
    this.view.apply(this.changes)
    this.changes = null
  }
}

class CorestoreRX {
  constructor(db, view) {
    this.read = db.read({ autoDestroy: true })
    this.view = view

    view.readStart()
  }

  async getHeadRaw(db) {
    return await this.view.get(this.read, store.head())
  }

  async getHead() {
    return decode(CORESTORE_HEAD, await this.view.get(this.read, store.head()))
  }

  async getCore(discoveryKey) {
    return decode(CORESTORE_CORE, await this.view.get(this.read, store.core(discoveryKey)))
  }

  getCoreByAlias(alias) {
    return this.view.get(this.read, store.coreByAlias(alias))
  }

  async getGroup(topic) {
    return decode(CORESTORE_GROUP, await this.view.get(this.read, group.topic(topic)))
  }

  tryFlush() {
    this.read.tryFlush()
    this._free()
  }

  destroy() {
    this.read.destroy()
    this._free()
  }

  _free() {
    if (this.view === null) return
    this.view.readStop()
    this.view = null
  }
}

module.exports = { CorestoreTX, CorestoreRX, CoreTX, CoreRX }

function findBlockDependency(dependencies, index) {
  for (let i = 0; i < dependencies.length; i++) {
    const dep = dependencies[i]
    if (index < dep.length) return dep
  }

  return null
}

function findTreeDependency(dependencies, index) {
  for (let i = 0; i < dependencies.length; i++) {
    const dep = dependencies[i]
    if (flat.rightSpan(index) <= (dep.length - 1) * 2) return dep
  }

  return null
}

function decode(enc, buffer) {
  if (buffer === null) return null
  return enc.decode({ start: 0, end: buffer.byteLength, buffer })
}

function encode(enc, m) {
  // TODO: use fancy slab for small messages
  const state = { start: 0, end: 0, buffer: null }
  enc.preencode(state, m)
  state.buffer = b4a.allocUnsafe(state.end)
  enc.encode(state, m)
  return state.buffer
}
const { Readable, getStreamError } = require('streamx')
const CloseErrorStream = require('./close-error-stream.js')
const b4a = require('b4a')

class OverlayStream extends Readable {
  constructor(stream, start, end, reverse, changes, cleared) {
    super()

    this.start = start
    this.end = end
    this.reverse = reverse
    this.changes = changes
    this.cleared = cleared
    this.change = 0
    this.range = 0

    this._stream = stream
    this._drained = false

    this._stream.on('readable', this._drainMaybe.bind(this))
    this._stream.on('error', noop)
    this._stream.on('close', this._onclose.bind(this))
  }

  _drainMaybe() {
    if (this._drained === true) return
    this._drained = this._onreadable()
  }

  _onclose() {
    if (this.destroying) return

    const err = getStreamError(this._stream)

    if (err !== null) {
      this.destroy(err)
      return
    }

    while (this.change < this.changes.length) {
      const c = this.changes[this.change++]
      const key = c[0]
      const value = c[1]

      if (value !== null && this._inRange(key)) this.push({ key, value })
    }

    this.push(null)
    this._stream = null
  }

  _onreadable() {
    let data = this._stream.read()
    if (data === null) return false

    let drained = false

    do {
      if (this._push(data) === true) drained = true
      data = this._stream.read()
    } while (data !== null)

    return drained
  }

  _read(cb) {
    this._drained = this._onreadable()
    cb(null)
  }

  _predestroy() {
    this._stream.destroy()
  }

  _push(entry) {
    const key = entry.key

    while (this.range < this.cleared.length) {
      const c = this.cleared[this.range]

      // we moved past the range
      if (this.reverse ? b4a.compare(key, c[0]) < 0 : b4a.compare(c[2], key) <= 0) {
        this.range++
        continue
      }

      // we didnt move past and are in, drop
      if (b4a.compare(c[0], key) <= 0 && b4a.compare(key, c[2]) < 0) {
        return false
      }

      break
    }

    let updated = false

    while (this.change < this.changes.length) {
      const c = this.changes[this.change]
      const key = c[0]
      const value = typeof c[1] === 'string' ? b4a.from(c[1]) : c[1]
      const cmp = b4a.compare(key, entry.key)

      // same value, if not deleted, return new one
      if (cmp === 0) {
        this.change++
        if (value === null || this._inRange(key) === false) return updated
        this.push({ key, value })
        return true
      }

      // we moved past the change, push it
      if (this.reverse ? cmp > 0 : cmp < 0) {
        this.change++
        if (value === null || this._inRange(key) === false) continue
        this.push({ key, value })
        updated = true
        continue
      }

      this.push(entry)
      return true
    }

    this.push(entry)
    return true
  }

  _inRange(key) {
    return b4a.compare(this.start, key) <= 0 && b4a.compare(key, this.end) < 0
  }
}

class Overlay {
  constructor() {
    this.indexed = 0
    this.changes = null
    this.cleared = null
    this.reverse = false
  }

  update(view, reverse) {
    if (view.indexed === this.indexed) return

    const changes = view.map === null ? [] : [...view.map.values()]
    const cleared = view.cleared === null ? [] : view.cleared.slice(0)

    const cmp = reverse ? cmpChangeReverse : cmpChange

    changes.sort(cmp)
    cleared.sort(cmp)

    this.indexed = view.indexed
    this.changes = changes
    this.cleared = cleared
    this.reverse = reverse
  }

  createStream(stream, start, end, reverse) {
    return new OverlayStream(
      stream,
      start,
      end,
      reverse,
      this.reverse === reverse ? this.changes : reverseArray(this.changes),
      this.reverse === reverse ? this.cleared : reverseArray(this.cleared)
    )
  }
}

class View {
  constructor() {
    this.map = null
    this.indexed = 0
    this.changes = null
    this.cleared = null
    this.overlay = null
    this.snap = null
    this.readers = 0
  }

  snapshot() {
    if (this._attached()) return this.snap.snapshot()

    const snap = new View()

    snap.map = this.map
    snap.indexed = this.indexed
    snap.changes = this.changes
    snap.cleared = this.cleared

    if (this._frozen()) return snap

    this.readers++
    snap.snap = this

    return snap
  }

  readStart() {
    if (this.snap !== null) this.readers++
  }

  readStop() {
    if (this.snap !== null && --this.readers === 0) this.snap.readers--
  }

  size() {
    return this.changes === null ? 0 : this.changes.length
  }

  updated() {
    return this.changes === null
  }

  get(read, key) {
    return this.changes === null ? read.get(key) : this._indexAndGet(read, key)
  }

  reset() {
    this.indexed = 0
    this.snap = this.map = this.changes = this.cleared = this.overlay = null
  }

  iterator(db, start, end, reverse) {
    if (dbClosing(db)) return new CloseErrorStream(new Error('RocksDB session is closed'))

    const stream = db.iterator({ gte: start, lt: end, reverse })
    if (this.changes === null) return stream

    this._index()

    if (this.overlay === null) this.overlay = new Overlay()
    this.overlay.update(this, reverse)
    return this.overlay.createStream(stream, start, end, reverse)
  }

  _indexAndGet(read, key) {
    this._index()
    const change = this.map.get(b4a.toString(key, 'hex'))

    if (change === undefined) {
      return this.cleared === null ? read.get(key) : this._readAndMaybeDrop(read, key)
    }

    return Promise.resolve(change[1])
  }

  async _readAndMaybeDrop(read, key) {
    const cleared = this.cleared // in case its cleared
    const value = await read.get(key)
    if (value === null) return null

    for (let i = 0; i < cleared.length; i++) {
      const c = cleared[i]
      // check if in range
      if (b4a.compare(c[0], key) <= 0 && b4a.compare(key, c[2]) < 0) return null
    }

    return value
  }

  _attached() {
    return this.snap !== null && this.changes === this.snap.changes
  }

  _frozen() {
    return this.changes === null || (this.snap !== null && this.changes !== this.snap.changes)
  }

  _index() {
    // if we are a snap and we are still attached (ie no mutations), simply copy the refs
    if (this._attached()) {
      this.snap._index()
      this.map = this.snap.map
      this.cleared = this.snap.cleared
      this.indexed = this.snap.indexed
      return
    }

    if (this.map === null) this.map = new Map()
    if (this.changes.length === this.indexed) return

    while (this.indexed < this.changes.length) {
      const c = this.changes[this.indexed++]

      if (c[2] === null) this.map.set(b4a.toString(c[0], 'hex'), c)
      else this._indexRange(c)
    }
  }

  _indexRange(range) {
    const s = b4a.toString(range[0], 'hex')
    const e = b4a.toString(range[2], 'hex')

    for (const [key, c] of this.map) {
      if (s <= key && key < e) this.map.set(key, [c[0], null, null])
    }

    if (this.cleared === null) this.cleared = []
    this.cleared.push(range)
  }

  apply(changes) {
    if (this.snap !== null) throw new Error('Illegal to push changes to a snapshot')

    if (this.readers !== 0 && this.changes !== null) {
      this.changes = this.changes.slice(0)
      this.cleared = this.cleared === null ? null : this.cleared.slice(0)
      this.map = this.map === null ? null : new Map([...this.map])
    }

    if (this.changes === null) {
      this.changes = changes
      return
    }

    for (let i = 0; i < changes.length; i++) {
      this.changes.push(changes[i])
    }
  }

  static async flush(changes, db) {
    if (changes === null) return true

    const w = db.write({ autoDestroy: true })

    for (const [start, value, end] of changes) {
      if (end !== null) w.tryDeleteRange(start, end)
      else if (value !== null) w.tryPut(start, value)
      else w.tryDelete(start)
    }

    await w.flush()

    return true
  }
}

module.exports = View

function cmpChange(a, b) {
  const c = b4a.compare(a[0], b[0])
  return c === 0 ? b4a.compare(a[2], b[2]) : c
}

function cmpChangeReverse(a, b) {
  return cmpChange(b, a)
}

function noop() {}

function reverseArray(list) {
  const r = new Array(list.length)
  for (let i = 0; i < list.length; i++) r[r.length - 1 - i] = list[i]
  return r
}

// TODO: expose from rocks instead
function dbClosing(db) {
  return db._state.closing || db._index === -1
}
const fs = require('fs')
const path = require('path')
const { Readable } = require('streamx')
const b4a = require('b4a')
const flat = require('flat-tree')
const crypto = require('hypercore-crypto')
const c = require('compact-encoding')
const m = require('./messages.js')
const View = require('../../lib/view.js')
const { CorestoreTX, CoreTX, CorestoreRX } = require('../../lib/tx.js')

const EMPTY_NODE = b4a.alloc(40)
const EMPTY_PAGE = b4a.alloc(4096)

let TREE_01_SKIP = null
let TREE_04_SKIP = null
let TREE_16_SKIP = null

class CoreListStream extends Readable {
  constructor (storage) {
    super()

    this.storage = storage
    this.stack = []
  }

  async _open (cb) {
    for (const a of await readdir(path.join(this.storage, 'cores'))) {
      for (const b of await readdir(path.join(this.storage, 'cores', a))) {
        for (const dkey of await readdir(path.join(this.storage, 'cores', a, b))) {
          this.stack.push(path.join(this.storage, 'cores', a, b, dkey))
        }
      }
    }

    cb(null)
  }

  async _read (cb) {
    while (true) {
      const next = this.stack.pop()
      if (!next) {
        this.push(null)
        break
      }

      const oplog = path.join(next, 'oplog')
      const result = await readOplog(oplog)
      if (!result) continue

      this.push(result)
      break
    }

    cb(null)
  }
}

function decodeOplogHeader (state) {
  c.uint32.decode(state) // cksum, ignore for now

  const l = c.uint32.decode(state)
  const length = l >> 2
  const headerBit = l & 1
  const partialBit = l & 2

  if (state.end - state.start < length) return null

  const end = state.start + length
  const result = { header: headerBit, partial: partialBit !== 0, byteLength: length + 8, message: null }

  try {
    result.message = m.oplog.header.decode({ start: state.start, end, buffer: state.buffer })
  } catch {
    return null
  }

  state.start = end
  return result
}

function decodeOplogEntry (state) {
  if (state.end - state.start < 8) return null

  c.uint32.decode(state) // cksum, ignore for now

  const l = c.uint32.decode(state)
  const length = l >>> 2
  const headerBit = l & 1
  const partialBit = l & 2

  if (state.end - state.start < length) return null

  const end = state.start + length

  const result = { header: headerBit, partial: partialBit !== 0, byteLength: length + 8, message: null }

  try {
    result.message = m.oplog.entry.decode({ start: state.start, end, buffer: state.buffer })
  } catch {
    return null
  }

  state.start = end

  return result
}

module.exports = { store, core }

async function store (storage, { version, dryRun = true, gc = true }) {
  const stream = new CoreListStream(storage.path)
  const view = new View()

  const tx = new CorestoreTX(view)
  const head = await storage._getHead(view)
  const primaryKeyFile = path.join(storage.path, 'primary-key')

  const primaryKey = await readFile(primaryKeyFile)

  if (!head.seed) head.seed = primaryKey

  for await (const data of stream) {
    const key = data.header.key
    const discoveryKey = crypto.discoveryKey(data.header.key)
    const files = getFiles(data.path)

    if (head.defaultDiscoveryKey === null) head.defaultDiscoveryKey = discoveryKey

    const core = {
      version: 0, // need later migration
      corePointer: head.allocated.cores++,
      dataPointer: head.allocated.datas++,
      alias: null
    }

    const ptr = { version: 0, corePointer: core.corePointer, dataPointer: core.dataPointer, dependencies: [] }
    const ctx = new CoreTX(ptr, storage.db, view, [])
    const userData = new Map()
    const treeNodes = new Map()

    const auth = {
      key,
      discoveryKey,
      manifest: data.header.manifest,
      keyPair: data.header.keyPair,
      encryptionKey: null
    }

    const tree = {
      length: 0,
      fork: 0,
      rootHash: null,
      signature: null
    }

    if (data.header.tree && data.header.tree.length) {
      tree.length = data.header.tree.length
      tree.fork = data.header.tree.fork
      tree.rootHash = data.header.tree.rootHash
      tree.signature = data.header.tree.signature
    }

    for (const { key, value } of data.header.userData) {
      userData.set(key, value)
    }

    for (const e of data.entries) {
      if (e.userData) userData.set(e.userData.key, e.userData.value)

      if (e.treeNodes) {
        for (const node of e.treeNodes) {
          treeNodes.set(node.index, node)
          ctx.putTreeNode(node)
        }
      }

      if (e.treeUpgrade) {
        if (e.treeUpgrade.ancestors !== tree.length) {
          throw new Error('Unflushed truncations not migrate-able atm')
        }

        tree.length = e.treeUpgrade.length
        tree.fork = e.treeUpgrade.fork
        tree.rootHash = null
        tree.signature = e.treeUpgrade.signature
      }
    }

    if (userData.has('corestore/name') && userData.has('corestore/namespace')) {
      core.alias = {
        name: b4a.toString(userData.get('corestore/name')),
        namespace: userData.get('corestore/namespace')
      }
      userData.delete('corestore/name')
      userData.delete('corestore/namespace')
    }

    for (const [key, value] of userData) {
      ctx.putUserData(key, value)
    }

    ctx.setAuth(auth)

    const getTreeNode = (index) => (treeNodes.get(index) || getTreeNodeFromFile(files.tree, index))

    if (tree.length) {
      if (tree.rootHash === null) tree.rootHash = crypto.tree(await getRoots(tree.length, getTreeNode))
      ctx.setHead(tree)
    }

    tx.putCore(discoveryKey, core)
    if (core.alias) tx.putCoreByAlias(core.alias, discoveryKey)

    await ctx.flush()
  }

  head.version = version
  tx.setHead(head)
  tx.apply()

  if (dryRun) return

  await View.flush(view.changes, storage.db)

  if (gc) await rm(primaryKeyFile)
}

class BlockSlicer {
  constructor (filename) {
    this.stream = fs.createReadStream(filename)
    this.closed = new Promise(resolve => this.stream.once('close', resolve))
    this.offset = 0
    this.overflow = null
  }

  async take (offset, size) {
    let buffer = null
    if (offset < this.offset) throw new Error('overread')

    while (true) {
      let data = null

      if (this.overflow) {
        data = this.overflow
        this.overflow = null
      } else {
        data = this.stream.read()

        if (!data) {
          await new Promise(resolve => this.stream.once('readable', resolve))
          continue
        }
      }

      let chunk = null

      if (this.offset === offset || buffer) {
        chunk = data
      } else if (this.offset + data.byteLength > offset) {
        chunk = data.subarray(offset - this.offset)
      }

      this.offset += data.byteLength
      if (!chunk) continue

      if (buffer) buffer = b4a.concat([buffer, chunk])
      else buffer = chunk

      if (buffer.byteLength < size) continue

      const result = buffer.subarray(0, size)
      this.overflow = size === buffer.byteLength ? null : buffer.subarray(result.byteLength)
      this.offset -= (this.overflow ? this.overflow.byteLength : 0)
      return result
    }
  }

  close () {
    this.stream.on('error', noop)
    this.stream.destroy()
    return this.closed
  }
}

class TreeSlicer {
  constructor () {
    this.buffer = null
    this.offset = 0
  }

  get size () {
    return this.buffer === null ? 0 : this.buffer.byteLength
  }

  push (data) {
    if (this.buffer === null) this.buffer = data
    else this.buffer = b4a.concat([this.buffer, data])
    this.offset += data.byteLength
  }

  skip () {
    let skipped = 0

    if (TREE_01_SKIP === null) {
      TREE_16_SKIP = b4a.alloc(16 * 40 * 100)
      TREE_04_SKIP = TREE_16_SKIP.subarray(0, 4 * 40 * 100)
      TREE_01_SKIP = TREE_16_SKIP.subarray(0, 1 * 40 * 100)
    }

    while (true) {
      if (this.buffer.byteLength >= TREE_16_SKIP.byteLength) {
        if (b4a.equals(this.buffer.subarray(0, TREE_16_SKIP.byteLength), TREE_16_SKIP)) {
          this.buffer = this.buffer.subarray(TREE_16_SKIP.byteLength)
          skipped += 1600
          continue
        }
      }

      if (this.buffer.byteLength >= TREE_04_SKIP.byteLength) {
        if (b4a.equals(this.buffer.subarray(0, TREE_04_SKIP.byteLength), TREE_04_SKIP)) {
          this.buffer = this.buffer.subarray(TREE_04_SKIP.byteLength)
          skipped += 400
          continue
        }
      }

      if (this.buffer.byteLength >= TREE_01_SKIP.byteLength) {
        if (b4a.equals(this.buffer.subarray(0, TREE_01_SKIP.byteLength), TREE_01_SKIP)) {
          this.buffer = this.buffer.subarray(TREE_01_SKIP.byteLength)
          skipped += 100
          continue
        }
      }
      break
    }

    return skipped
  }

  take () {
    const len = 40

    if (len <= this.size) {
      const chunk = this.buffer.subarray(0, len)
      this.buffer = this.buffer.subarray(len)
      return chunk
    }

    return null
  }
}

async function core (core, { version, dryRun = true, gc = true }) {
  if (dryRun) return // dryRun mode not supported atm

  const rx = core.read()

  const promises = [rx.getAuth(), rx.getHead()]
  rx.tryFlush()

  const [auth, head] = await Promise.all(promises)

  if (!auth) return

  const dk = b4a.toString(auth.discoveryKey, 'hex')
  const files = getFiles(path.join(core.store.path, 'cores', dk.slice(0, 2), dk.slice(2, 4), dk))

  if (head === null || head.length === 0) {
    await commitCoreMigration(auth, core, version)
    if (gc) await runGC()
    return // no data
  }

  const oplog = await readOplog(files.oplog)
  if (!oplog) {
    const writable = !!auth.keyPair

    if (writable) {
      throw new Error('No oplog available writable core for ' + files.oplog + ', length = ' + (head ? head.length : 0))
    }

    // if not writable, just nuke it to recover, some bad state happened here, prop corruption from earlier versions
    const w = core.write()

    w.deleteBlockRange(0, -1)
    w.deleteTreeNodeRange(0, -1)
    w.deleteBitfieldPageRange(0, -1)
    w.deleteHead()

    await w.flush()

    await commitCoreMigration(auth, core, version)
    if (gc) await runGC()
    return // no data
  }

  const treeData = new TreeSlicer()

  let treeIndex = 0

  if (await exists(files.tree)) {
    for await (const data of fs.createReadStream(files.tree)) {
      treeData.push(data)

      let write = null

      while (true) {
        const skip = treeData.skip()
        treeIndex += skip

        const buf = treeData.take()
        if (buf === null) break

        const index = treeIndex++
        if (b4a.equals(buf, EMPTY_NODE)) continue

        if (write === null) write = core.write()
        write.putTreeNode(decodeTreeNode(index, buf))
      }

      if (write !== null) await write.flush()
    }
  }

  const buf = []
  if (await exists(files.bitfield)) {
    for await (const data of fs.createReadStream(files.bitfield)) {
      buf.push(data)
    }
  }

  let bitfield = b4a.concat(buf)
  if (bitfield.byteLength & 4095) bitfield = b4a.concat([bitfield, b4a.alloc(4096 - (bitfield.byteLength & 4095))])

  const pages = new Map()
  const headerBits = new Map()

  const roots = await getRootsFromStorage(core, head.length)

  for (const e of oplog.entries) {
    if (!e.bitfield) continue

    for (let i = 0; i < e.bitfield.length; i++) {
      headerBits.set(i + e.bitfield.start, !e.bitfield.drop)
    }
  }

  let batch = []

  const cache = new Map()
  const blocks = new BlockSlicer(files.data)

  for (const index of allBits(bitfield)) {
    if (headerBits.get(index) === false) continue
    if (index >= head.length) continue

    setBitInPage(index)

    batch.push(index)
    if (batch.length < 1024) continue

    await writeBlocksBatch()
    continue
  }

  if (batch.length) await writeBlocksBatch()

  await blocks.close()

  const w = core.write()

  for (const [index, bit] of headerBits) {
    if (!bit) continue
    if (index >= head.length) continue

    setBitInPage(index)

    const blk = await getBlockFromFile(files.data, core, index, roots, cache)
    w.putBlock(index, blk)
  }

  for (const [index, page] of pages) {
    w.putBitfieldPage(index, b4a.from(page.buffer, page.byteOffset, page.byteLength))
  }

  await w.flush()

  let contiguousLength = 0
  for await (const data of core.createBlockStream()) {
    if (data.index === contiguousLength) contiguousLength++
    else break
  }

  if (contiguousLength) {
    const w = core.write()
    w.setHints({ contiguousLength })
    await w.flush()
  }

  await commitCoreMigration(auth, core, version)

  if (gc) await runGC()

  async function runGC () {
    await rm(files.path)
    await rmdir(path.join(files.path, '..'))
    await rmdir(path.join(files.path, '../..'))
    await rmdir(path.join(core.store.path, 'cores'))
  }

  function setBitInPage (index) {
    const n = index & 32767
    const p = (index - n) / 32768

    let page = pages.get(p)

    if (!page) {
      page = new Uint32Array(1024)
      pages.set(p, page)
    }

    const o = n & 31
    const b = (n - o) / 32
    const v = 1 << o

    page[b] |= v
  }

  async function writeBlocksBatch () {
    const read = core.read()
    const promises = []
    for (const index of batch) promises.push(getByteRangeFromStorage(read, 2 * index, roots, cache))
    read.tryFlush()

    const r = await Promise.all(promises)
    const tx = core.write()

    for (let i = 0; i < r.length; i++) {
      const index = batch[i]
      const [offset, size] = r[i]

      const blk = await blocks.take(offset, size)
      tx.putBlock(index, blk)
    }

    batch = []
    if (cache.size > 16384) cache.clear()

    await tx.flush()
  }
}

async function commitCoreMigration (auth, core, version) {
  const view = new View()
  const rx = new CorestoreRX(core.db, view)

  const storeCorePromise = rx.getCore(auth.discoveryKey)
  rx.tryFlush()

  const storeCore = await storeCorePromise

  storeCore.version = version

  const tx = new CorestoreTX(view)

  tx.putCore(auth.discoveryKey, storeCore)
  tx.apply()

  await View.flush(view.changes, core.db)
}

async function getBlockFromFile (file, core, index, roots, cache) {
  const rx = core.read()
  const promise = getByteRangeFromStorage(rx, 2 * index, roots, cache)
  rx.tryFlush()
  const [offset, size] = await promise

  return new Promise(function (resolve) {
    readAll(file, size, offset, function (err, buf) {
      if (err) return resolve(null)
      resolve(buf)
    })
  })
}

function getFiles (dir) {
  return {
    path: dir,
    oplog: path.join(dir, 'oplog'),
    data: path.join(dir, 'data'),
    tree: path.join(dir, 'tree'),
    bitfield: path.join(dir, 'bitfield')
  }
}

async function getRootsFromStorage (core, length) {
  const all = []
  const rx = core.read()
  for (const index of flat.fullRoots(2 * length)) {
    all.push(rx.getTreeNode(index))
  }
  rx.tryFlush()
  return Promise.all(all)
}

async function getRoots (length, getTreeNode) {
  const all = []
  for (const index of flat.fullRoots(2 * length)) {
    all.push(await getTreeNode(index))
  }
  return all
}

function getCached (read, cache, index) {
  if (cache.has(index)) return cache.get(index)
  const p = read.getTreeNode(index)
  cache.set(index, p)
  return p
}

async function getByteRangeFromStorage (read, index, roots, cache) {
  const promises = [getCached(read, cache, index), getByteOffsetFromStorage(read, index, roots, cache)]
  const [node, offset] = await Promise.all(promises)
  if (!node) throw new Error('Node not found during migration: ' + index)
  return [offset, node.size]
}

async function getByteOffsetFromStorage (rx, index, roots, cache) {
  if (index === 0) return 0
  if ((index & 1) === 1) index = flat.leftSpan(index)

  let head = 0
  let offset = 0

  for (const node of roots) { // all async ticks happen once we find the root so safe
    head += 2 * ((node.index - head) + 1)

    if (index >= head) {
      offset += node.size
      continue
    }

    const ite = flat.iterator(node.index)
    const promises = []

    while (ite.index !== index) {
      if (index < ite.index) {
        ite.leftChild()
      } else {
        promises.push(getCached(rx, cache, ite.leftChild()))
        ite.sibling()
      }
    }

    const nodes = await Promise.all(promises)
    for (const node of nodes) offset += node.size

    return offset
  }

  throw new Error('Failed to find offset')
}

function decodeTreeNode (index, buf) {
  return { index, size: c.decode(c.uint64, buf), hash: buf.subarray(8) }
}

async function getTreeNodeFromFile (file, index) {
  return new Promise(function (resolve) {
    readAll(file, 40, index * 40, function (err, buf) {
      if (err) return resolve(null)
      resolve(decodeTreeNode(index, buf))
    })
  })
}

function readAll (filename, length, pos, cb) {
  const buf = b4a.alloc(length)

  fs.open(filename, 'r', function (err, fd) {
    if (err) return cb(err)

    let offset = 0

    fs.read(fd, buf, offset, buf.byteLength, pos, function loop (err, read) {
      if (err) return done(err)
      if (read === 0) return done(new Error('Partial read'))
      offset += read
      if (offset === buf.byteLength) return done(null, buf)
      fs.read(fd, offset, buf.byteLength - offset, buf, pos + offset, loop)
    })

    function done (err, value) {
      fs.close(fd, () => cb(err, value))
    }
  })
}

async function readdir (dir) {
  try {
    return await fs.promises.readdir(dir)
  } catch {
    return []
  }
}

async function exists (file) {
  try {
    await fs.promises.stat(file)
    return true
  } catch {
    return false
  }
}

async function readFile (file) {
  try {
    return await fs.promises.readFile(file)
  } catch {
    return null
  }
}

async function rm (dir) {
  try {
    await fs.promises.rm(dir, { recursive: true })
  } catch {}
}

async function rmdir (dir) {
  try {
    await fs.promises.rmdir(dir)
  } catch {}
}

function * allBits (buffer) {
  for (let i = 0; i < buffer.byteLength; i += EMPTY_PAGE.byteLength) {
    const page = buffer.subarray(i, i + EMPTY_NODE.byteLength)
    if (b4a.equals(page, EMPTY_PAGE)) continue

    const view = new Uint32Array(page.buffer, page.byteOffset, EMPTY_PAGE.byteLength / 4)

    for (let j = 0; j < view.length; j++) {
      const n = view[j]
      if (n === 0) continue

      for (let k = 0; k < 32; k++) {
        const m = 1 << k
        if (n & m) yield i * 8 + j * 32 + k
      }
    }
  }
}

function readOplog (oplog) {
  return new Promise(function (resolve) {
    fs.readFile(oplog, function (err, buffer) {
      if (err) return resolve(null)

      const state = { start: 0, end: buffer.byteLength, buffer }
      const headers = [1, 0]

      const h1 = decodeOplogHeader(state)
      state.start = 4096

      const h2 = decodeOplogHeader(state)
      state.start = 4096 * 2

      if (!h1 && !h2) return resolve(null)

      if (h1 && !h2) {
        headers[0] = h1.header
        headers[1] = h1.header
      } else if (!h1 && h2) {
        headers[0] = (h2.header + 1) & 1
        headers[1] = h2.header
      } else {
        headers[0] = h1.header
        headers[1] = h2.header
      }

      const header = (headers[0] + headers[1]) & 1
      const result = { path: path.dirname(oplog), header: null, entries: [] }
      const decoded = []

      result.header = header ? h2.message : h1.message

      if (result.header.external) {
        fs.readFile(path.join(oplog, '../header'), function (err, buffer) {
          if (err) return resolve(null)
          const start = result.header.external.start
          const end = start + result.header.external.length
          result.header = m.oplog.header.decode({ buffer, start, end })
          finish()
        })
        return
      }

      finish()

      function finish () {
        while (true) {
          const entry = decodeOplogEntry(state)
          if (!entry) break
          if (entry.header !== header) break

          decoded.push(entry)
        }

        while (decoded.length > 0 && decoded[decoded.length - 1].partial) decoded.pop()

        for (const e of decoded) {
          result.entries.push(e.message)
        }

        resolve(result)
      }
    })
  })
}

function noop () {}
// needed here for compat, copied from old hypercore, do not change this

const c = require('compact-encoding')
const b4a = require('b4a')

const EMPTY = b4a.alloc(0)
const DEFAULT_NAMESPACE = b4a.from('4144eea531e483d54e0c14f4ca68e0644f355343ff6fcb0f005200e12cd747cb', 'hex')

const hashes = {
  preencode (state, m) {
    state.end++ // small uint
  },
  encode (state, m) {
    if (m === 'blake2b') {
      c.uint.encode(state, 0)
      return
    }

    throw new Error('Unknown hash: ' + m)
  },
  decode (state) {
    const n = c.uint.decode(state)
    if (n === 0) return 'blake2b'
    throw new Error('Unknown hash id: ' + n)
  }
}

const signatures = {
  preencode (state, m) {
    state.end++ // small uint
  },
  encode (state, m) {
    if (m === 'ed25519') {
      c.uint.encode(state, 0)
      return
    }

    throw new Error('Unknown signature: ' + m)
  },
  decode (state) {
    const n = c.uint.decode(state)
    if (n === 0) return 'ed25519'
    throw new Error('Unknown signature id: ' + n)
  }
}

const signer = {
  preencode (state, m) {
    signatures.preencode(state, m.signature)
    c.fixed32.preencode(state, m.namespace)
    c.fixed32.preencode(state, m.publicKey)
  },
  encode (state, m) {
    signatures.encode(state, m.signature)
    c.fixed32.encode(state, m.namespace)
    c.fixed32.encode(state, m.publicKey)
  },
  decode (state) {
    return {
      signature: signatures.decode(state),
      namespace: c.fixed32.decode(state),
      publicKey: c.fixed32.decode(state)
    }
  }
}

const signerArray = c.array(signer)

const prologue = {
  preencode (state, p) {
    c.fixed32.preencode(state, p.hash)
    c.uint.preencode(state, p.length)
  },
  encode (state, p) {
    c.fixed32.encode(state, p.hash)
    c.uint.encode(state, p.length)
  },
  decode (state) {
    return {
      hash: c.fixed32.decode(state),
      length: c.uint.decode(state)
    }
  }
}

const manifestv0 = {
  preencode (state, m) {
    hashes.preencode(state, m.hash)
    state.end++ // type

    if (m.prologue && m.signers.length === 0) {
      c.fixed32.preencode(state, m.prologue.hash)
      return
    }

    if (m.quorum === 1 && m.signers.length === 1 && !m.allowPatch) {
      signer.preencode(state, m.signers[0])
    } else {
      state.end++ // flags
      c.uint.preencode(state, m.quorum)
      signerArray.preencode(state, m.signers)
    }
  },
  encode (state, m) {
    hashes.encode(state, m.hash)

    if (m.prologue && m.signers.length === 0) {
      c.uint.encode(state, 0)
      c.fixed32.encode(state, m.prologue.hash)
      return
    }

    if (m.quorum === 1 && m.signers.length === 1 && !m.allowPatch) {
      c.uint.encode(state, 1)
      signer.encode(state, m.signers[0])
    } else {
      c.uint.encode(state, 2)
      c.uint.encode(state, m.allowPatch ? 1 : 0)
      c.uint.encode(state, m.quorum)
      signerArray.encode(state, m.signers)
    }
  },
  decode (state) {
    const hash = hashes.decode(state)
    const type = c.uint.decode(state)

    if (type > 2) throw new Error('Unknown type: ' + type)

    if (type === 0) {
      return {
        version: 0,
        hash,
        allowPatch: false,
        quorum: 0,
        signers: [],
        prologue: {
          hash: c.fixed32.decode(state),
          length: 0
        }
      }
    }

    if (type === 1) {
      return {
        version: 0,
        hash,
        allowPatch: false,
        quorum: 1,
        signers: [signer.decode(state)],
        prologue: null
      }
    }

    const flags = c.uint.decode(state)

    return {
      version: 0,
      hash,
      allowPatch: (flags & 1) !== 0,
      quorum: c.uint.decode(state),
      signers: signerArray.decode(state),
      prologue: null
    }
  }
}

const manifest = exports.manifest = {
  preencode (state, m) {
    state.end++ // version
    if (m.version === 0) return manifestv0.preencode(state, m)

    state.end++ // flags
    hashes.preencode(state, m.hash)

    c.uint.preencode(state, m.quorum)
    signerArray.preencode(state, m.signers)
    if (m.prologue) prologue.preencode(state, m.prologue)
  },
  encode (state, m) {
    c.uint.encode(state, m.version)
    if (m.version === 0) return manifestv0.encode(state, m)

    c.uint.encode(state, (m.allowPatch ? 1 : 0) | (m.prologue ? 2 : 0) | (m.unencrypted ? 4 : 0))
    hashes.encode(state, m.hash)

    c.uint.encode(state, m.quorum)
    signerArray.encode(state, m.signers)
    if (m.prologue) prologue.encode(state, m.prologue)
  },
  decode (state) {
    const v = c.uint.decode(state)
    if (v === 0) return manifestv0.decode(state)
    if (v !== 1) throw new Error('Unknown version: ' + v)

    const flags = c.uint.decode(state)
    const hash = hashes.decode(state)
    const quorum = c.uint.decode(state)
    const signers = signerArray.decode(state)
    const unencrypted = (flags & 4) !== 0

    return {
      version: 1,
      hash,
      allowPatch: (flags & 1) !== 0,
      quorum,
      signers,
      prologue: (flags & 2) === 0 ? null : prologue.decode(state),
      unencrypted
    }
  }
}

const node = {
  preencode (state, n) {
    c.uint.preencode(state, n.index)
    c.uint.preencode(state, n.size)
    c.fixed32.preencode(state, n.hash)
  },
  encode (state, n) {
    c.uint.encode(state, n.index)
    c.uint.encode(state, n.size)
    c.fixed32.encode(state, n.hash)
  },
  decode (state) {
    return {
      index: c.uint.decode(state),
      size: c.uint.decode(state),
      hash: c.fixed32.decode(state)
    }
  }
}

const nodeArray = c.array(node)

const wire = exports.wire = {}

wire.handshake = {
  preencode (state, m) {
    c.uint.preencode(state, 1)
    c.fixed32.preencode(state, m.capability)
  },
  encode (state, m) {
    c.uint.encode(state, m.seeks ? 1 : 0)
    c.fixed32.encode(state, m.capability)
  },
  decode (state) {
    const flags = c.uint.decode(state)
    return {
      seeks: (flags & 1) !== 0,
      capability: c.fixed32.decode(state)
    }
  }
}

const requestBlock = {
  preencode (state, b) {
    c.uint.preencode(state, b.index)
    c.uint.preencode(state, b.nodes)
  },
  encode (state, b) {
    c.uint.encode(state, b.index)
    c.uint.encode(state, b.nodes)
  },
  decode (state) {
    return {
      index: c.uint.decode(state),
      nodes: c.uint.decode(state)
    }
  }
}

const requestSeek = {
  preencode (state, s) {
    c.uint.preencode(state, s.bytes)
    c.uint.preencode(state, s.padding)
  },
  encode (state, s) {
    c.uint.encode(state, s.bytes)
    c.uint.encode(state, s.padding)
  },
  decode (state) {
    return {
      bytes: c.uint.decode(state),
      padding: c.uint.decode(state)
    }
  }
}

const requestUpgrade = {
  preencode (state, u) {
    c.uint.preencode(state, u.start)
    c.uint.preencode(state, u.length)
  },
  encode (state, u) {
    c.uint.encode(state, u.start)
    c.uint.encode(state, u.length)
  },
  decode (state) {
    return {
      start: c.uint.decode(state),
      length: c.uint.decode(state)
    }
  }
}

wire.request = {
  preencode (state, m) {
    state.end++ // flags
    c.uint.preencode(state, m.id)
    c.uint.preencode(state, m.fork)

    if (m.block) requestBlock.preencode(state, m.block)
    if (m.hash) requestBlock.preencode(state, m.hash)
    if (m.seek) requestSeek.preencode(state, m.seek)
    if (m.upgrade) requestUpgrade.preencode(state, m.upgrade)
    if (m.priority) c.uint.preencode(state, m.priority)
  },
  encode (state, m) {
    const flags = (m.block ? 1 : 0) | (m.hash ? 2 : 0) | (m.seek ? 4 : 0) | (m.upgrade ? 8 : 0) | (m.manifest ? 16 : 0) | (m.priority ? 32 : 0)

    c.uint.encode(state, flags)
    c.uint.encode(state, m.id)
    c.uint.encode(state, m.fork)

    if (m.block) requestBlock.encode(state, m.block)
    if (m.hash) requestBlock.encode(state, m.hash)
    if (m.seek) requestSeek.encode(state, m.seek)
    if (m.upgrade) requestUpgrade.encode(state, m.upgrade)
    if (m.priority) c.uint.encode(state, m.priority)
  },
  decode (state) {
    const flags = c.uint.decode(state)

    return {
      id: c.uint.decode(state),
      fork: c.uint.decode(state),
      block: flags & 1 ? requestBlock.decode(state) : null,
      hash: flags & 2 ? requestBlock.decode(state) : null,
      seek: flags & 4 ? requestSeek.decode(state) : null,
      upgrade: flags & 8 ? requestUpgrade.decode(state) : null,
      manifest: (flags & 16) !== 0,
      priority: flags & 32 ? c.uint.decode(state) : 0
    }
  }
}

wire.cancel = {
  preencode (state, m) {
    c.uint.preencode(state, m.request)
  },
  encode (state, m) {
    c.uint.encode(state, m.request)
  },
  decode (state, m) {
    return {
      request: c.uint.decode(state)
    }
  }
}

const dataUpgrade = {
  preencode (state, u) {
    c.uint.preencode(state, u.start)
    c.uint.preencode(state, u.length)
    nodeArray.preencode(state, u.nodes)
    nodeArray.preencode(state, u.additionalNodes)
    c.optionalBuffer.preencode(state, u.signature)
  },
  encode (state, u) {
    c.uint.encode(state, u.start)
    c.uint.encode(state, u.length)
    nodeArray.encode(state, u.nodes)
    nodeArray.encode(state, u.additionalNodes)
    c.optionalBuffer.encode(state, u.signature)
  },
  decode (state) {
    return {
      start: c.uint.decode(state),
      length: c.uint.decode(state),
      nodes: nodeArray.decode(state),
      additionalNodes: nodeArray.decode(state),
      signature: c.optionalBuffer.decode(state)
    }
  }
}

const dataSeek = {
  preencode (state, s) {
    c.uint.preencode(state, s.bytes)
    nodeArray.preencode(state, s.nodes)
  },
  encode (state, s) {
    c.uint.encode(state, s.bytes)
    nodeArray.encode(state, s.nodes)
  },
  decode (state) {
    return {
      bytes: c.uint.decode(state),
      nodes: nodeArray.decode(state)
    }
  }
}

const dataBlock = {
  preencode (state, b) {
    c.uint.preencode(state, b.index)
    c.optionalBuffer.preencode(state, b.value)
    nodeArray.preencode(state, b.nodes)
  },
  encode (state, b) {
    c.uint.encode(state, b.index)
    c.optionalBuffer.encode(state, b.value)
    nodeArray.encode(state, b.nodes)
  },
  decode (state) {
    return {
      index: c.uint.decode(state),
      value: c.optionalBuffer.decode(state) || EMPTY,
      nodes: nodeArray.decode(state)
    }
  }
}

const dataHash = {
  preencode (state, b) {
    c.uint.preencode(state, b.index)
    nodeArray.preencode(state, b.nodes)
  },
  encode (state, b) {
    c.uint.encode(state, b.index)
    nodeArray.encode(state, b.nodes)
  },
  decode (state) {
    return {
      index: c.uint.decode(state),
      nodes: nodeArray.decode(state)
    }
  }
}

wire.data = {
  preencode (state, m) {
    state.end++ // flags
    c.uint.preencode(state, m.request)
    c.uint.preencode(state, m.fork)

    if (m.block) dataBlock.preencode(state, m.block)
    if (m.hash) dataHash.preencode(state, m.hash)
    if (m.seek) dataSeek.preencode(state, m.seek)
    if (m.upgrade) dataUpgrade.preencode(state, m.upgrade)
    if (m.manifest) manifest.preencode(state, m.manifest)
  },
  encode (state, m) {
    const flags = (m.block ? 1 : 0) | (m.hash ? 2 : 0) | (m.seek ? 4 : 0) | (m.upgrade ? 8 : 0) | (m.manifest ? 16 : 0)

    c.uint.encode(state, flags)
    c.uint.encode(state, m.request)
    c.uint.encode(state, m.fork)

    if (m.block) dataBlock.encode(state, m.block)
    if (m.hash) dataHash.encode(state, m.hash)
    if (m.seek) dataSeek.encode(state, m.seek)
    if (m.upgrade) dataUpgrade.encode(state, m.upgrade)
    if (m.manifest) manifest.encode(state, m.manifest)
  },
  decode (state) {
    const flags = c.uint.decode(state)

    return {
      request: c.uint.decode(state),
      fork: c.uint.decode(state),
      block: flags & 1 ? dataBlock.decode(state) : null,
      hash: flags & 2 ? dataHash.decode(state) : null,
      seek: flags & 4 ? dataSeek.decode(state) : null,
      upgrade: flags & 8 ? dataUpgrade.decode(state) : null,
      manifest: flags & 16 ? manifest.decode(state) : null
    }
  }
}

wire.noData = {
  preencode (state, m) {
    c.uint.preencode(state, m.request)
  },
  encode (state, m) {
    c.uint.encode(state, m.request)
  },
  decode (state, m) {
    return {
      request: c.uint.decode(state)
    }
  }
}

wire.want = {
  preencode (state, m) {
    c.uint.preencode(state, m.start)
    c.uint.preencode(state, m.length)
  },
  encode (state, m) {
    c.uint.encode(state, m.start)
    c.uint.encode(state, m.length)
  },
  decode (state) {
    return {
      start: c.uint.decode(state),
      length: c.uint.decode(state)
    }
  }
}

wire.unwant = {
  preencode (state, m) {
    c.uint.preencode(state, m.start)
    c.uint.preencode(state, m.length)
  },
  encode (state, m) {
    c.uint.encode(state, m.start)
    c.uint.encode(state, m.length)
  },
  decode (state, m) {
    return {
      start: c.uint.decode(state),
      length: c.uint.decode(state)
    }
  }
}

wire.range = {
  preencode (state, m) {
    state.end++ // flags
    c.uint.preencode(state, m.start)
    if (m.length !== 1) c.uint.preencode(state, m.length)
  },
  encode (state, m) {
    c.uint.encode(state, (m.drop ? 1 : 0) | (m.length === 1 ? 2 : 0))
    c.uint.encode(state, m.start)
    if (m.length !== 1) c.uint.encode(state, m.length)
  },
  decode (state) {
    const flags = c.uint.decode(state)

    return {
      drop: (flags & 1) !== 0,
      start: c.uint.decode(state),
      length: (flags & 2) !== 0 ? 1 : c.uint.decode(state)
    }
  }
}

wire.bitfield = {
  preencode (state, m) {
    c.uint.preencode(state, m.start)
    c.uint32array.preencode(state, m.bitfield)
  },
  encode (state, m) {
    c.uint.encode(state, m.start)
    c.uint32array.encode(state, m.bitfield)
  },
  decode (state, m) {
    return {
      start: c.uint.decode(state),
      bitfield: c.uint32array.decode(state)
    }
  }
}

wire.sync = {
  preencode (state, m) {
    state.end++ // flags
    c.uint.preencode(state, m.fork)
    c.uint.preencode(state, m.length)
    c.uint.preencode(state, m.remoteLength)
  },
  encode (state, m) {
    c.uint.encode(state, (m.canUpgrade ? 1 : 0) | (m.uploading ? 2 : 0) | (m.downloading ? 4 : 0) | (m.hasManifest ? 8 : 0))
    c.uint.encode(state, m.fork)
    c.uint.encode(state, m.length)
    c.uint.encode(state, m.remoteLength)
  },
  decode (state) {
    const flags = c.uint.decode(state)

    return {
      fork: c.uint.decode(state),
      length: c.uint.decode(state),
      remoteLength: c.uint.decode(state),
      canUpgrade: (flags & 1) !== 0,
      uploading: (flags & 2) !== 0,
      downloading: (flags & 4) !== 0,
      hasManifest: (flags & 8) !== 0
    }
  }
}

wire.reorgHint = {
  preencode (state, m) {
    c.uint.preencode(state, m.from)
    c.uint.preencode(state, m.to)
    c.uint.preencode(state, m.ancestors)
  },
  encode (state, m) {
    c.uint.encode(state, m.from)
    c.uint.encode(state, m.to)
    c.uint.encode(state, m.ancestors)
  },
  decode (state) {
    return {
      from: c.uint.encode(state),
      to: c.uint.encode(state),
      ancestors: c.uint.encode(state)
    }
  }
}

wire.extension = {
  preencode (state, m) {
    c.string.preencode(state, m.name)
    c.raw.preencode(state, m.message)
  },
  encode (state, m) {
    c.string.encode(state, m.name)
    c.raw.encode(state, m.message)
  },
  decode (state) {
    return {
      name: c.string.decode(state),
      message: c.raw.decode(state)
    }
  }
}

const keyValue = {
  preencode (state, p) {
    c.string.preencode(state, p.key)
    c.optionalBuffer.preencode(state, p.value)
  },
  encode (state, p) {
    c.string.encode(state, p.key)
    c.optionalBuffer.encode(state, p.value)
  },
  decode (state) {
    return {
      key: c.string.decode(state),
      value: c.optionalBuffer.decode(state)
    }
  }
}

const treeUpgrade = {
  preencode (state, u) {
    c.uint.preencode(state, u.fork)
    c.uint.preencode(state, u.ancestors)
    c.uint.preencode(state, u.length)
    c.optionalBuffer.preencode(state, u.signature)
  },
  encode (state, u) {
    c.uint.encode(state, u.fork)
    c.uint.encode(state, u.ancestors)
    c.uint.encode(state, u.length)
    c.optionalBuffer.encode(state, u.signature)
  },
  decode (state) {
    return {
      fork: c.uint.decode(state),
      ancestors: c.uint.decode(state),
      length: c.uint.decode(state),
      signature: c.optionalBuffer.decode(state)
    }
  }
}

const bitfieldUpdate = { // TODO: can maybe be folded into a HAVE later on with the most recent spec
  preencode (state, b) {
    state.end++ // flags
    c.uint.preencode(state, b.start)
    c.uint.preencode(state, b.length)
  },
  encode (state, b) {
    state.buffer[state.start++] = b.drop ? 1 : 0
    c.uint.encode(state, b.start)
    c.uint.encode(state, b.length)
  },
  decode (state) {
    const flags = c.uint.decode(state)
    return {
      drop: (flags & 1) !== 0,
      start: c.uint.decode(state),
      length: c.uint.decode(state)
    }
  }
}

const oplog = exports.oplog = {}

oplog.entry = {
  preencode (state, m) {
    state.end++ // flags
    if (m.userData) keyValue.preencode(state, m.userData)
    if (m.treeNodes) nodeArray.preencode(state, m.treeNodes)
    if (m.treeUpgrade) treeUpgrade.preencode(state, m.treeUpgrade)
    if (m.bitfield) bitfieldUpdate.preencode(state, m.bitfield)
  },
  encode (state, m) {
    const s = state.start++
    let flags = 0

    if (m.userData) {
      flags |= 1
      keyValue.encode(state, m.userData)
    }
    if (m.treeNodes) {
      flags |= 2
      nodeArray.encode(state, m.treeNodes)
    }
    if (m.treeUpgrade) {
      flags |= 4
      treeUpgrade.encode(state, m.treeUpgrade)
    }
    if (m.bitfield) {
      flags |= 8
      bitfieldUpdate.encode(state, m.bitfield)
    }

    state.buffer[s] = flags
  },
  decode (state) {
    const flags = c.uint.decode(state)
    return {
      userData: (flags & 1) !== 0 ? keyValue.decode(state) : null,
      treeNodes: (flags & 2) !== 0 ? nodeArray.decode(state) : null,
      treeUpgrade: (flags & 4) !== 0 ? treeUpgrade.decode(state) : null,
      bitfield: (flags & 8) !== 0 ? bitfieldUpdate.decode(state) : null
    }
  }
}

const keyPair = {
  preencode (state, kp) {
    c.optionalBuffer.preencode(state, kp.publicKey)
    c.optionalBuffer.preencode(state, kp.secretKey)
  },
  encode (state, kp) {
    c.optionalBuffer.encode(state, kp.publicKey)
    c.optionalBuffer.encode(state, kp.secretKey)
  },
  decode (state) {
    return {
      publicKey: c.optionalBuffer.decode(state),
      secretKey: c.optionalBuffer.decode(state)
    }
  }
}

const reorgHint = {
  preencode (state, r) {
    c.uint.preencode(state, r.from)
    c.uint.preencode(state, r.to)
    c.uint.preencode(state, r.ancestors)
  },
  encode (state, r) {
    c.uint.encode(state, r.from)
    c.uint.encode(state, r.to)
    c.uint.encode(state, r.ancestors)
  },
  decode (state) {
    return {
      from: c.uint.decode(state),
      to: c.uint.decode(state),
      ancestors: c.uint.decode(state)
    }
  }
}

const reorgHintArray = c.array(reorgHint)

const hints = {
  preencode (state, h) {
    reorgHintArray.preencode(state, h.reorgs)
    c.uint.preencode(state, h.contiguousLength)
  },
  encode (state, h) {
    reorgHintArray.encode(state, h.reorgs)
    c.uint.encode(state, h.contiguousLength)
  },
  decode (state) {
    return {
      reorgs: reorgHintArray.decode(state),
      contiguousLength: state.start < state.end ? c.uint.decode(state) : 0
    }
  }
}

const treeHeader = {
  preencode (state, t) {
    c.uint.preencode(state, t.fork)
    c.uint.preencode(state, t.length)
    c.optionalBuffer.preencode(state, t.rootHash)
    c.optionalBuffer.preencode(state, t.signature)
  },
  encode (state, t) {
    c.uint.encode(state, t.fork)
    c.uint.encode(state, t.length)
    c.optionalBuffer.encode(state, t.rootHash)
    c.optionalBuffer.encode(state, t.signature)
  },
  decode (state) {
    return {
      fork: c.uint.decode(state),
      length: c.uint.decode(state),
      rootHash: c.optionalBuffer.decode(state),
      signature: c.optionalBuffer.decode(state)
    }
  }
}

const types = {
  preencode (state, t) {
    c.string.preencode(state, t.tree)
    c.string.preencode(state, t.bitfield)
    c.string.preencode(state, t.signer)
  },
  encode (state, t) {
    c.string.encode(state, t.tree)
    c.string.encode(state, t.bitfield)
    c.string.encode(state, t.signer)
  },
  decode (state) {
    return {
      tree: c.string.decode(state),
      bitfield: c.string.decode(state),
      signer: c.string.decode(state)
    }
  }
}

const externalHeader = {
  preencode (state, m) {
    c.uint.preencode(state, m.start)
    c.uint.preencode(state, m.length)
  },
  encode (state, m) {
    c.uint.encode(state, m.start)
    c.uint.encode(state, m.length)
  },
  decode (state) {
    return {
      start: c.uint.decode(state),
      length: c.uint.decode(state)
    }
  }
}

const keyValueArray = c.array(keyValue)

oplog.header = {
  preencode (state, h) {
    state.end += 2 // version + flags
    if (h.external) {
      externalHeader.preencode(state, h.external)
      return
    }
    c.fixed32.preencode(state, h.key)
    if (h.manifest) manifest.preencode(state, h.manifest)
    if (h.keyPair) keyPair.preencode(state, h.keyPair)
    keyValueArray.preencode(state, h.userData)
    treeHeader.preencode(state, h.tree)
    hints.preencode(state, h.hints)
  },
  encode (state, h) {
    c.uint.encode(state, 1)
    if (h.external) {
      c.uint.encode(state, 1) // ONLY set the first big for clarity
      externalHeader.encode(state, h.external)
      return
    }
    c.uint.encode(state, (h.manifest ? 2 : 0) | (h.keyPair ? 4 : 0))
    c.fixed32.encode(state, h.key)
    if (h.manifest) manifest.encode(state, h.manifest)
    if (h.keyPair) keyPair.encode(state, h.keyPair)
    keyValueArray.encode(state, h.userData)
    treeHeader.encode(state, h.tree)
    hints.encode(state, h.hints)
  },
  decode (state) {
    const version = c.uint.decode(state)

    if (version > 1) {
      throw new Error('Invalid header version. Expected <= 1, got ' + version)
    }

    if (version === 0) {
      const old = {
        types: types.decode(state),
        userData: keyValueArray.decode(state),
        tree: treeHeader.decode(state),
        signer: keyPair.decode(state),
        hints: hints.decode(state)
      }

      return {
        external: null,
        key: old.signer.publicKey,
        manifest: {
          version: 0,
          hash: old.types.tree,
          allowPatch: false,
          quorum: 1,
          signers: [{
            signature: old.types.signer,
            namespace: DEFAULT_NAMESPACE,
            publicKey: old.signer.publicKey
          }],
          prologue: null
        },
        keyPair: old.signer.secretKey ? old.signer : null,
        userData: old.userData,
        tree: old.tree,
        hints: old.hints
      }
    }

    const flags = c.uint.decode(state)

    if (flags & 1) {
      return {
        external: externalHeader.decode(state),
        key: null,
        manifest: null,
        keyPair: null,
        userData: null,
        tree: null,
        hints: null
      }
    }

    return {
      external: null,
      key: c.fixed32.decode(state),
      manifest: (flags & 2) !== 0 ? manifest.decode(state) : null,
      keyPair: (flags & 4) !== 0 ? keyPair.decode(state) : null,
      userData: keyValueArray.decode(state),
      tree: treeHeader.decode(state),
      hints: hints.decode(state)
    }
  }
}

const uintArray = c.array(c.uint)

const multisigInput = {
  preencode (state, inp) {
    c.uint.preencode(state, inp.signer)
    c.fixed64.preencode(state, inp.signature)
    c.uint.preencode(state, inp.patch)
  },
  encode (state, inp) {
    c.uint.encode(state, inp.signer)
    c.fixed64.encode(state, inp.signature)
    c.uint.encode(state, inp.patch)
  },
  decode (state) {
    return {
      signer: c.uint.decode(state),
      signature: c.fixed64.decode(state),
      patch: c.uint.decode(state)
    }
  }
}

const patchEncodingv0 = {
  preencode (state, n) {
    c.uint.preencode(state, n.start)
    c.uint.preencode(state, n.length)
    uintArray.preencode(state, n.nodes)
  },
  encode (state, n) {
    c.uint.encode(state, n.start)
    c.uint.encode(state, n.length)
    uintArray.encode(state, n.nodes)
  },
  decode (state) {
    return {
      start: c.uint.decode(state),
      length: c.uint.decode(state),
      nodes: uintArray.decode(state)
    }
  }
}

const multisigInputv0 = {
  preencode (state, n) {
    state.end++
    c.uint.preencode(state, n.signer)
    c.fixed64.preencode(state, n.signature)
    if (n.patch) patchEncodingv0.preencode(state, n.patch)
  },
  encode (state, n) {
    c.uint.encode(state, n.patch ? 1 : 0)
    c.uint.encode(state, n.signer)
    c.fixed64.encode(state, n.signature)
    if (n.patch) patchEncodingv0.encode(state, n.patch)
  },
  decode (state) {
    const flags = c.uint.decode(state)
    return {
      signer: c.uint.decode(state),
      signature: c.fixed64.decode(state),
      patch: (flags & 1) ? patchEncodingv0.decode(state) : null
    }
  }
}

const multisigInputArrayv0 = c.array(multisigInputv0)
const multisigInputArray = c.array(multisigInput)

const compactNode = {
  preencode (state, n) {
    c.uint.preencode(state, n.index)
    c.uint.preencode(state, n.size)
    c.fixed32.preencode(state, n.hash)
  },
  encode (state, n) {
    c.uint.encode(state, n.index)
    c.uint.encode(state, n.size)
    c.fixed32.encode(state, n.hash)
  },
  decode (state) {
    return {
      index: c.uint.decode(state),
      size: c.uint.decode(state),
      hash: c.fixed32.decode(state)
    }
  }
}

const compactNodeArray = c.array(compactNode)

exports.multiSignaturev0 = {
  preencode (state, s) {
    multisigInputArrayv0.preencode(state, s.proofs)
    compactNodeArray.preencode(state, s.patch)
  },
  encode (state, s) {
    multisigInputArrayv0.encode(state, s.proofs)
    compactNodeArray.encode(state, s.patch)
  },
  decode (state) {
    return {
      proofs: multisigInputArrayv0.decode(state),
      patch: compactNodeArray.decode(state)
    }
  }
}

exports.multiSignature = {
  preencode (state, s) {
    multisigInputArray.preencode(state, s.proofs)
    compactNodeArray.preencode(state, s.patch)
  },
  encode (state, s) {
    multisigInputArray.encode(state, s.proofs)
    compactNodeArray.encode(state, s.patch)
  },
  decode (state) {
    return {
      proofs: multisigInputArray.decode(state),
      patch: compactNodeArray.decode(state)
    }
  }
}
const c = require('compact-encoding')
const schema = require('../../encoding/spec/hyperschema')
const { core: coreKey } = require('../../lib/keys.js')
const View = require('../../lib/view.js')

const CORE_AUTH = schema.getEncoding('@core/auth')

const { CorestoreRX, CorestoreTX } = require('../../lib/tx.js')

async function core (core, { version, dryRun = false } = {}) {
  if (dryRun) return // dryRun mode not supported atm

  const rx = core.db.read({ autoDestroy: true })
  const storedAuthPromise = rx.get(coreKey.auth(core.core.corePointer))

  rx.tryFlush()

  const storedAuth = await storedAuthPromise
  if (!storedAuth) return

  const auth = c.decode(CORE_AUTH, storedAuth)
  await commitCoreMigration(auth, core, version)
}

module.exports = { core }

async function commitCoreMigration (auth, core, version) {
  const view = new View()
  const rx = new CorestoreRX(core.db, view)

  const storeCorePromise = rx.getCore(auth.discoveryKey)
  rx.tryFlush()

  const storeCore = await storeCorePromise

  storeCore.version = version

  const tx = new CorestoreTX(view)
  tx.putCore(auth.discoveryKey, storeCore)
  tx.apply()

  await View.flush(view.changes, core.db)
}
{
  "name": "hypercore-storage",
  "version": "3.2.1",
  "main": "index.js",
  "files": [
    "index.js",
    "lib/*.js",
    "encoding/*",
    "migrations/0/*.js",
    "migrations/1/*.js"
  ],
  "scripts": {
    "format": "prettier --write .",
    "lint": "prettier --check . && lunte",
    "test": "npm run test:node && npm run test:bare",
    "test:bare": "brittle-bare test/all.js",
    "test:generate": "brittle -r test/all.js test/*.js",
    "test:node": "brittle-node test/all.js"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "description": "Storage engine for Hypercore",
  "imports": {
    "fs": {
      "bare": "bare-fs",
      "default": "fs"
    },
    "fs/*": {
      "bare": "bare-fs/*",
      "default": "fs/*"
    },
    "os": {
      "bare": "bare-os",
      "default": "os"
    },
    "path": {
      "bare": "bare-path",
      "default": "path"
    }
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/hypercore-storage.git"
  },
  "bugs": {
    "url": "https://github.com/holepunchto/hypercore-storage/issues"
  },
  "homepage": "https://github.com/holepunchto/hypercore-storage#readme",
  "dependencies": {
    "b4a": "^1.6.7",
    "bare-path": "^3.0.0",
    "compact-encoding": "^3.1.0",
    "device-file": "^2.1.2",
    "flat-tree": "^1.12.1",
    "hypercore-crypto": "^3.4.2",
    "hyperschema": "^1.21.0",
    "index-encoder": "^3.3.2",
    "resolve-reject-promise": "^1.0.0",
    "rocksdb-native": "^3.11.0",
    "scope-lock": "^1.2.4",
    "streamx": "^2.21.1",
    "xache": "^1.2.1"
  },
  "devDependencies": {
    "bare-os": "^3.9.1",
    "brittle": "^3.7.0",
    "hypercore-storage-2.9.0": "npm:hypercore-storage@^2.9.0",
    "lunte": "^1.3.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^2.0.0",
    "test-tmp": "^1.3.1"
  }
}
const { EventEmitter } = require('events')
const isOptions = require('is-options')
const crypto = require('hypercore-crypto')
const CoreStorage = require('hypercore-storage')
const c = require('compact-encoding')
const b4a = require('b4a')
const NoiseSecretStream = require('@hyperswarm/secret-stream')
const Protomux = require('protomux')
const id = require('hypercore-id-encoding')
const safetyCatch = require('safety-catch')
const unslab = require('unslab')
const flat = require('flat-tree')
const assert = require('nanoassert')

const { SMALL_WANTS } = require('./lib/feature-flags')
const { UPDATE_COMPAT } = require('./lib/wants')
const MarkBitfield = require('./lib/mark-bitfield')

const inspect = require('./lib/inspect')
const Core = require('./lib/core')
const Info = require('./lib/info')
const Download = require('./lib/download')
const DefaultEncryption = require('./lib/default-encryption')
const caps = require('./lib/caps')
const Replicator = require('./lib/replicator')
const Verifier = require('./lib/verifier')
const { manifestHash, createManifest, encodeManifest } = Verifier
const { ReadStream, WriteStream, ByteStream } = require('./lib/streams')
const { MerkleTree } = require('./lib/merkle-tree')
const { proof, verify } = require('./lib/fully-remote-proof')
const {
  ASSERTION,
  BAD_ARGUMENT,
  SESSION_CLOSED,
  SESSION_MOVED,
  SESSION_NOT_WRITABLE,
  SNAPSHOT_NOT_AVAILABLE,
  DECODING_ERROR,
  REQUEST_CANCELLED,
  INVALID_CHECKSUM
} = require('hypercore-errors')

// Hypercore actually does not have any notion of max/min block sizes
// but we enforce 15mb to ensure smooth replication (each block is transmitted atomically)
const MAX_SUGGESTED_BLOCK_SIZE = 15 * 1024 * 1024

class Hypercore extends EventEmitter {
  constructor(storage, key, opts) {
    super()

    if (isOptions(storage) && !storage.db) {
      opts = storage
      storage = null
      key = opts.key || null
    } else if (isOptions(key)) {
      opts = key
      key = opts.key || null
    }

    if (key && typeof key === 'string') key = id.decode(key)
    if (!opts) opts = {}

    if (!storage) storage = opts.storage

    this.core = null
    this.state = null
    this.encryption = null
    this.extensions = new Map()

    this.valueEncoding = null
    this.encodeBatch = null
    this.activeRequests = []
    this.sessions = null
    this.ongc = null

    this.keyPair = opts.keyPair || null
    this.readable = true
    this.writable = false
    this.exclusive = false
    this.opened = false
    this.closed = false
    this.weak = !!opts.weak
    this.snapshotted = !!opts.snapshot
    this.onseq = opts.onseq || null
    this.onwait = opts.onwait || null
    this.wait = opts.wait !== false
    this.timeout = opts.timeout || 0
    this.preload = null
    this.closing = null
    this.opening = null

    this._readonly = opts.writable === false
    this._preappend = preappend.bind(this)
    this._snapshot = null
    this._findingPeers = 0
    this._active = opts.weak ? !!opts.active : opts.active !== false

    this.waits = 0

    // Mark & Sweep GC
    this._marking = false
    this._marks = null

    this._sessionIndex = -1
    this._stateIndex = -1 // maintained by session state
    this._monitorIndex = -1 // maintained by replication state

    this.opening = this._open(storage, key, opts)
    this.opening.catch(safetyCatch)

    this.on('newListener', maybeAddMonitor)
  }

  [Symbol.for('nodejs.util.inspect.custom')](depth, opts) {
    return inspect(this, depth, opts)
  }

  static MAX_SUGGESTED_BLOCK_SIZE = MAX_SUGGESTED_BLOCK_SIZE

  static DefaultEncryption = DefaultEncryption

  static SMALL_WANTS = SMALL_WANTS

  static enable(flag) {
    const enableCompat = (flag & SMALL_WANTS) === 0
    UPDATE_COMPAT(enableCompat)
  }

  static setRecoveryPeers(peers) {
    Core.setRecoveryPeers(peers)
  }

  static key(manifest, { compat, version, namespace } = {}) {
    if (b4a.isBuffer(manifest)) {
      manifest =
        manifest.byteLength === 32
          ? { version, signers: [{ publicKey: manifest, namespace }] }
          : this.parseManifest(manifest)
    }
    return compat ? manifest.signers[0].publicKey : manifestHash(createManifest(manifest))
  }

  static parseManifest(manifest, key) {
    const parsed = createManifest(manifest)
    if (parsed === null) return null

    if (parsed.quorum > parsed.signers.length) {
      throw BAD_ARGUMENT('Quorum should not be higher than the number of signers')
    }

    if (key && !Verifier.isValidManifest(key, parsed)) {
      throw INVALID_CHECKSUM('Manifest does not hash to provided key')
    }

    return parsed
  }

  static discoveryKey(key) {
    return crypto.discoveryKey(key)
  }

  static blockEncryptionKey(key, encryptionKey) {
    return DefaultEncryption.blockEncryptionKey(key, encryptionKey)
  }

  static getProtocolMuxer(stream) {
    return stream.noiseStream.userData
  }

  static createCore(storage, opts) {
    return new Core(Hypercore.defaultStorage(storage), { autoClose: false, ...opts })
  }

  static createProtocolStream(isInitiator, opts = {}) {
    let outerStream = Protomux.isProtomux(isInitiator)
      ? isInitiator.stream
      : isStream(isInitiator)
        ? isInitiator
        : opts.stream

    let noiseStream = null

    if (outerStream) {
      noiseStream = outerStream.noiseStream
    } else {
      noiseStream = new NoiseSecretStream(isInitiator, null, opts)
      outerStream = noiseStream.rawStream
    }
    if (!noiseStream) throw BAD_ARGUMENT('Invalid stream', this.discoveryKey)

    if (!noiseStream.userData) {
      const protocol = Protomux.from(noiseStream)

      if (opts.keepAlive !== false && noiseStream.keepAlive === 0) {
        noiseStream.setKeepAlive(5000)
      }
      noiseStream.userData = protocol
    }

    if (opts.ondiscoverykey) {
      const ondiscoverykey = createDiscoveryKeyHandler(opts.ondiscoverykey)
      noiseStream.userData.pair({ protocol: 'hypercore/alpha' }, ondiscoverykey)
    }

    return outerStream
  }

  static defaultStorage(storage, opts = {}) {
    if (CoreStorage.isCoreStorage(storage)) return storage

    const directory = storage
    return new CoreStorage(directory, opts)
  }

  static clearRequests(session, err) {
    Replicator.clearRequests(session, err)
  }

  static destroyRequests(session, err) {
    if (isRequestsDestroyed(session)) return
    Replicator.clearRequests(session, err)
    session.push(null) // mark as dead
  }

  static async treeHashFromStorage(session, length = session.length) {
    const roots = await MerkleTree.getRoots(session.state, length)
    return crypto.tree(roots)
  }

  snapshot(opts) {
    return this.session({ ...opts, snapshot: true })
  }

  compact() {
    return this.core.compact()
  }

  session(opts = {}) {
    if (this.closing) {
      // This makes the closing logic a lot easier. If this turns out to be a problem
      // in practice, open an issue and we'll try to make a solution for it.
      throw SESSION_CLOSED('Cannot make sessions on a closing core', this.discoveryKey)
    }
    if (opts.checkout !== undefined && !opts.name && !opts.atom) {
      throw ASSERTION('Checkouts are only supported on atoms or named sessions', this.discoveryKey)
    }

    const wait = opts.wait === false ? false : this.wait
    const writable = opts.writable === undefined ? !this._readonly : opts.writable === true
    const onwait = opts.onwait === undefined ? this.onwait : opts.onwait
    const onseq = opts.onseq === undefined ? this.onseq : opts.onseq
    const timeout = opts.timeout === undefined ? this.timeout : opts.timeout
    const weak = opts.weak === undefined ? this.weak : opts.weak
    const marking = this._marking
    const marks = this._marks
    const Clz = opts.class || Hypercore
    const s = new Clz(null, this.key, {
      ...opts,
      wait,
      onwait,
      onseq,
      timeout,
      writable,
      weak,
      parent: this
    })
    s._marking = marking
    s._marks = marks

    return s
  }

  async setEncryptionKey(key, opts) {
    if (!this.opened) await this.opening
    const encryption = this._getEncryptionProvider({ key, block: !!(opts && opts.block) })
    return this.setEncryption(encryption)
  }

  async setEncryption(encryption) {
    if (!this.opened) await this.opening

    if (encryption === null) {
      this.encryption = encryption
      return
    }

    if (!isEncryptionProvider(encryption)) {
      throw ASSERTION('Provider does not satisfy HypercoreEncryption interface', this.discoveryKey)
    }

    this.encryption = encryption
  }

  async setGroup(topic) {
    if (!this.opened) await this.opening
    return this.core.setGroup(topic)
  }

  setKeyPair(keyPair) {
    this.keyPair = keyPair
  }

  setActive(bool) {
    const active = !!bool
    if (active === this._active || this.closing) return
    this._active = active
    if (!this.opened) return
    this.core.replicator.updateActivity(this._active ? 1 : -1)
  }

  async _open(storage, key, opts) {
    const preload = opts.preload || (opts.parent && opts.parent.preload)

    if (preload) {
      this.sessions = [] // in case someone looks at it like with peers
      this.preload = preload
      opts = { ...opts, ...(await this.preload) }
      this.preload = null
    }

    const parent = opts.parent || null
    const core = opts.core || (parent && parent.core)
    const sessions = opts.sessions || (parent && parent.sessions)
    const ongc = opts.ongc || (parent && parent.ongc)

    if (core) this.core = core
    if (ongc) this.ongc = ongc
    if (sessions) this.sessions = sessions

    if (this.sessions === null) this.sessions = []
    this._sessionIndex = this.sessions.push(this) - 1

    if (this.core === null) initOnce(this, storage, key, opts)
    if (this._monitorIndex === -2) this.core.addMonitor(this)

    try {
      await this._openSession(opts)
    } catch (err) {
      if (this.core.autoClose && this.core.hasSession() === false) await this.core.close()

      if (this.exclusive) this.core.unlockExclusive()

      this.core.removeMonitor(this)
      this._removeSession()

      if (this.state !== null) this.state.removeSession(this)

      this.closed = true
      this.emit('close')
      throw err
    }

    // Setup automatic recovery if in repair mode
    if (this.core._repairMode) {
      const recoverTreeNodeFromPeersBound = this.recoverTreeNodeFromPeers.bind(this)
      this.once('repaired', () => {
        this.off('peer-add', recoverTreeNodeFromPeersBound)
      })
      this.on('peer-add', recoverTreeNodeFromPeersBound)
    }

    this.emit('ready')

    // if we are a weak session the core might have closed...
    if (this.core.closing) this.close().catch(safetyCatch)
  }

  _removeSession() {
    if (this._sessionIndex === -1) return
    const head = this.sessions.pop()
    if (head !== this) this.sessions[(head._sessionIndex = this._sessionIndex)] = head
    this._sessionIndex = -1
    if (this.ongc !== null) this.ongc(this)
  }

  async _openSession(opts) {
    if (this.core.opened === false) await this.core.ready()

    if (opts.group && !this.core.header.group) {
      await this.core.setGroup(opts.group)
    }

    if (this.keyPair === null) this.keyPair = opts.keyPair || this.core.header.keyPair

    const parent = opts.parent || null
    if (parent && parent.encryption) this.encryption = parent.encryption

    const e = getEncryptionOption(opts)
    if (!this.encryption) this.encryption = this._getEncryptionProvider(e)

    this.writable = this._isWritable()

    if (opts.valueEncoding) {
      this.valueEncoding = c.from(opts.valueEncoding)
    }
    if (opts.encodeBatch) {
      this.encodeBatch = opts.encodeBatch
    }

    // one session sets for pushOnly for all
    if (opts.pushOnly === true) {
      this.core.replicator.setPushOnly(true)
    }

    if (parent) {
      if (parent._stateIndex === -1) await parent.ready()
      if (!this.keyPair) this.keyPair = parent.keyPair

      const ps = parent.state

      if (ps) {
        const shouldSnapshot = this.snapshotted && !ps.isSnapshot()
        this.state = shouldSnapshot ? await ps.snapshot() : ps.ref()
      }

      if (this.snapshotted && this.core && !this._snapshot) {
        this._updateSnapshot()
      }
    }

    if (opts.exclusive && opts.writable !== false) {
      this.exclusive = true
      await this.core.lockExclusive()
    }

    const parentState = parent ? parent.state : this.core.state
    const checkout = opts.checkout === undefined ? -1 : opts.checkout
    const state = this.state

    if (opts.atom) {
      this.state = await parentState.createSession(null, false, opts.atom)
      if (state) state.unref()
    } else if (opts.name) {
      // todo: need to make named sessions safe before ready
      // atm we always copy the state in passCapabilities
      this.state = await parentState.createSession(opts.name, !!opts.overwrite, null)
      if (state) state.unref() // ref'ed above in setup session
    }

    if (this.state && checkout !== -1) {
      if (!opts.name && !opts.atom) {
        throw ASSERTION('Checkouts must be named or atomized', this.discoveryKey)
      }
      if (checkout > this.state.length) {
        throw ASSERTION(
          `Invalid checkout ${checkout} for ${opts.name}, length is ${this.state.length}`,
          this.discoveryKey
        )
      }
      if (this.state.prologue && checkout < this.state.prologue.length) {
        throw ASSERTION(
          `Invalid checkout ${checkout} for ${opts.name}, prologue length is ${this.state.prologue.length}`,
          this.discoveryKey
        )
      }
      if (checkout < this.state.length) await this.state.truncate(checkout, this.fork)
    }

    if (this.state === null) {
      this.state = this.core.state.ref()
    }

    this.writable = this._isWritable()

    if (this.snapshotted && this.core) this._updateSnapshot()

    this.state.addSession(this)
    // TODO: we need to rework the core reference flow, as the state and session do not always agree now due to moveTo
    this.core = this.state.core // in case it was wrong...

    if (opts.userData) {
      const tx = this.state.storage.write()
      for (const [key, value] of Object.entries(opts.userData)) {
        tx.putUserData(key, value)
      }
      await tx.flush()
    }

    if (opts.manifest && !this.core.header.manifest) {
      await this.core.setManifest(opts.manifest)
    }

    this.core.replicator.updateActivity(this._active ? 1 : 0)

    this.opened = true
  }

  get replicator() {
    return this.core === null ? null : this.core.replicator
  }

  _getSnapshot() {
    return {
      length: this.state.length,
      byteLength: this.state.byteLength,
      fork: this.state.fork
    }
  }

  _updateSnapshot() {
    const prev = this._snapshot
    const next = (this._snapshot = this._getSnapshot())

    if (!prev) return true
    return prev.length !== next.length || prev.fork !== next.fork
  }

  _isWritable() {
    if (this._readonly) return false
    if (this.state && !this.state.isDefault()) return true
    return !!(this.keyPair && this.keyPair.secretKey)
  }

  close({ error } = {}) {
    if (this.closing) return this.closing

    this.closing = this._close(error || null)
    return this.closing
  }

  clearRequests(activeRequests, error) {
    if (!activeRequests.length) return
    if (this.core) this.core.replicator.clearRequests(activeRequests, error)
  }

  async _close(error) {
    if (this.opened === false) {
      try {
        await this.opening
      } catch (err) {
        if (!this.closed) throw err
      }
    }

    if (this.closed === true) return

    this.core.removeMonitor(this)
    this.state.removeSession(this)
    this._removeSession()

    this.readable = false
    this.writable = false
    this.opened = false

    const gc = []
    for (const ext of this.extensions.values()) {
      if (ext.session === this) gc.push(ext)
    }
    for (const ext of gc) ext.destroy()

    this.core.replicator.findingPeers -= this._findingPeers
    this.core.replicator.clearRequests(this.activeRequests, error)
    this.core.replicator.updateActivity(this._active ? -1 : 0)

    this._findingPeers = 0

    this.state.unref()

    if (this.exclusive) this.core.unlockExclusive()

    if (this.core.hasSession()) {
      // emit "fake" close as this is a session
      this.closed = true
      this.emit('close')
      return
    }

    if (this.core.autoClose) await this.core.close()

    this.closed = true
    this.emit('close')
  }

  async commit(session, opts) {
    await this.ready()
    await session.ready()

    return this.state.commit(session.state, { keyPair: this.keyPair, ...opts })
  }

  replicate(isInitiator, opts = {}) {
    // Only limitation here is that ondiscoverykey doesn't work atm when passing a muxer directly,
    // because it doesn't really make a lot of sense.
    if (Protomux.isProtomux(isInitiator)) return this._attachToMuxer(isInitiator)

    // if same stream is passed twice, ignore the 2nd one before we make sessions etc
    if (isStream(isInitiator) && this._isAttached(isInitiator)) return isInitiator

    const protocolStream = Hypercore.createProtocolStream(isInitiator, opts)
    const noiseStream = protocolStream.noiseStream
    const protocol = noiseStream.userData

    this._attachToMuxer(protocol)

    return protocolStream
  }

  _isAttached(stream) {
    return (
      stream.userData &&
      this.core &&
      this.core.replicator &&
      this.core.replicator.attached(stream.userData)
    )
  }

  _attachToMuxer(mux) {
    if (this.opened) {
      this.core.replicator.attachTo(mux)
    } else {
      this.opening.then(() => this.core.replicator.attachTo(mux), mux.destroy.bind(mux))
    }

    return mux
  }

  get id() {
    return this.core === null ? null : this.core.id
  }

  get key() {
    return this.core === null ? null : this.core.key
  }

  get discoveryKey() {
    return this.core === null ? null : this.core.discoveryKey
  }

  get manifest() {
    return this.core === null ? null : this.core.manifest
  }

  getManifest({ raw = false } = {}) {
    if (this.manifest === null) return null
    return raw ? encodeManifest(this.manifest) : this.manifest
  }

  get length() {
    if (this._snapshot) return this._snapshot.length
    return this.opened === false ? 0 : this.state.length
  }

  get signedLength() {
    return this.opened === false ? 0 : this.state.signedLength()
  }

  /**
   * Deprecated. Use `const { byteLength } = await core.info()`.
   */
  get byteLength() {
    if (this.opened === false) return 0
    if (this._snapshot) return this._snapshot.byteLength
    return this.state.byteLength - this.state.length * this.padding
  }

  get remoteContiguousLength() {
    if (this.opened === false) return 0
    return Math.min(this.core.state.length, this.core.header.hints.remoteContiguousLength)
  }

  get contiguousLength() {
    if (this.opened === false) return 0
    return Math.min(this.core.state.length, this.core.header.hints.contiguousLength)
  }

  get contiguousByteLength() {
    return 0
  }

  get fork() {
    if (this.opened === false) return 0
    return this.state.fork
  }

  get padding() {
    if (this.encryption && this.key && this.manifest) {
      return this.encryption.padding(this.core, this.length)
    }

    return 0
  }

  get peers() {
    return this.opened === false ? [] : this.core.replicator.peers
  }

  get globalCache() {
    return this.opened === false ? null : this.core.globalCache
  }

  get recovering() {
    return this.opened === false ? 0 : this.core.header.hints.recovering
  }

  ready() {
    return this.opening
  }

  async recover() {
    if (this.opened === false) await this.opening
    return this.state.waitForRecovery()
  }

  async setUserData(key, value) {
    if (this.opened === false) await this.opening
    const existing = await this.getUserData(key)
    if (existing && b4a.isBuffer(value) && b4a.equals(existing, value)) return
    await this.state.setUserData(key, value)
  }

  async getUserData(key) {
    if (this.opened === false) await this.opening
    const batch = this.state.storage.read()
    const p = batch.getUserData(key)
    batch.tryFlush()
    return p
  }

  transferSession(core) {
    // todo: validate we can move

    if (this.weak === false) {
      this.core.activeSessions--
      core.activeSessions++
    }

    if (this._monitorIndex >= 0) {
      this.core.removeMonitor(this)
      core.addMonitor(this)
    }

    const old = this.core

    this.core = core

    old.replicator.clearRequests(this.activeRequests, SESSION_MOVED())

    this.emit('migrate', this.key)
  }

  findingPeers() {
    this._findingPeers++
    if (this.core !== null && !this.closing) this.core.replicator.findingPeers++

    let once = true

    return () => {
      if (this.closing || !once) return
      once = false
      this._findingPeers--
      if (this.core !== null && --this.core.replicator.findingPeers === 0) {
        this.core.replicator.queueUpdateAll()
      }
    }
  }

  async info(opts) {
    if (this.opened === false) await this.opening

    return Info.from(this, opts)
  }

  async update(opts) {
    if (this.opened === false) await this.opening
    if (this.closing !== null) return false
    if (this.snapshotted) return false

    if (this.writable && (!opts || opts.force !== true)) return false

    const remoteWait = this._shouldWait(opts, this.core.replicator.findingPeers > 0)

    let upgraded = false

    if (await this.core.replicator.applyPendingReorg()) {
      upgraded = true
    }

    if (!upgraded && remoteWait) {
      const activeRequests = (opts && opts.activeRequests) || this.activeRequests
      if (isRequestsDestroyed(activeRequests)) throw REQUEST_CANCELLED()

      const req = this.core.replicator.addUpgrade(activeRequests)

      try {
        upgraded = await req.promise
      } catch (err) {
        if (isSessionMoved(err)) return this.update(opts)
        throw err
      }
    }

    if (!upgraded) return false
    return true
  }

  async seek(bytes, opts) {
    if (this.opened === false) await this.opening
    if (!isValidIndex(bytes)) throw ASSERTION('seek is invalid', this.discoveryKey)

    const activeRequests = (opts && opts.activeRequests) || this.activeRequests
    if (isRequestsDestroyed(activeRequests)) throw REQUEST_CANCELLED()

    if (this.encryption && !this.core.manifest) {
      const req = this.replicator.addUpgrade(activeRequests)
      try {
        await req.promise
      } catch (err) {
        if (isSessionMoved(err)) return this.seek(bytes, opts)
        throw err
      }
    }

    const s = MerkleTree.seek(this.state, bytes, this.padding)

    const offset = await s.update()
    if (offset) return offset

    if (this.closing !== null) {
      throw SESSION_CLOSED('cannot seek on a closed session', this.discoveryKey)
    }

    if (!this._shouldWait(opts, this.wait)) return null

    if (isRequestsDestroyed(activeRequests)) throw REQUEST_CANCELLED()
    const req = this.core.replicator.addSeek(activeRequests, s)

    const timeout = opts && opts.timeout !== undefined ? opts.timeout : this.timeout
    if (timeout) req.context.setTimeout(req, timeout)

    try {
      return await req.promise
    } catch (err) {
      if (isSessionMoved(err)) return this.seek(bytes, opts)
      throw err
    }
  }

  async has(start, end = start + 1) {
    if (this.opened === false) await this.opening
    if (!isValidIndex(start) || !isValidIndex(end)) {
      throw ASSERTION('has range is invalid', this.discoveryKey)
    }

    if (this.state.isDefault()) {
      if (end === start + 1) return this.core.bitfield.get(start)

      const i = this.core.bitfield.firstUnset(start)
      return i === -1 || i >= end
    }

    if (end === start + 1) {
      const rx = this.state.storage.read()
      const block = rx.getBlock(start)
      rx.tryFlush()

      return (await block) !== null
    }

    let count = 0

    const stream = this.state.storage.createBlockStream({ gte: start, lt: end })
    for await (const block of stream) {
      if (block === null) return false
      count++
    }

    return count === end - start
  }

  async get(index, opts) {
    if (this.opened === false) await this.opening
    if (!isValidIndex(index)) throw ASSERTION('block index is invalid', this.discoveryKey)

    if (this.closing !== null) {
      throw SESSION_CLOSED('cannot get on a closed session', this.discoveryKey)
    }

    const encoding =
      (opts && opts.valueEncoding && c.from(opts.valueEncoding)) || this.valueEncoding

    if (this.onseq !== null) this.onseq(index, this)
    if (this._marking) await this.markBlock(index)

    const req = this._get(index, opts)

    let block = await req
    if (!block) return null

    if (opts && opts.raw) return block

    if (this.encryption && (!opts || opts.decrypt !== false)) {
      // Copy the block as it might be shared with other sessions.
      block = b4a.from(block)

      await this.encryption.decrypt(index, block, this.core)
    }

    return this._decode(encoding, block, index)
  }

  async clear(start, end = start + 1, opts) {
    if (this.opened === false) await this.opening
    if (this.closing !== null) {
      throw SESSION_CLOSED('cannot clear on a closed session', this.discoveryKey)
    }

    if (typeof end === 'object') {
      opts = end
      end = start + 1
    }

    if (!isValidIndex(start) || !isValidIndex(end)) {
      throw ASSERTION('clear range is invalid', this.discoveryKey)
    }

    const cleared = opts && opts.diff ? { blocks: 0 } : null

    if (start >= end) return cleared
    if (start >= this.length) return cleared

    await this.state.clear(start, end, cleared)

    return cleared
  }

  async purge() {
    await this._closeAllSessions(null)
    await this.core.purge()
  }

  async _get(index, opts) {
    const block = await readBlock(this.state.storage.read(), index)

    if (block !== null) return block

    if (this.closing !== null) {
      throw SESSION_CLOSED('cannot get on a closed session', this.discoveryKey)
    }

    // snapshot should check if core has block
    if (this._snapshot !== null) {
      checkSnapshot(this, index)
      const coreBlock = await readBlock(this.core.state.storage.read(), index)

      checkSnapshot(this, index)
      if (coreBlock !== null) return coreBlock
    }

    // lets check the bitfield to see if we got it during the above async calls
    // this is the last resort before replication, so always safe.
    if (this.core.bitfield.get(index)) {
      const coreBlock = await readBlock(this.state.storage.read(), index)
      // TODO: this should not be needed, only needed atm in case we are doing a moveTo during this (we should fix)
      if (coreBlock !== null) return coreBlock
    }

    if (!this._shouldWait(opts, this.wait)) return null

    this.waits++
    if (opts && opts.onwait) opts.onwait(index, this)
    if (this.onwait) this.onwait(index, this)

    const activeRequests = (opts && opts.activeRequests) || this.activeRequests
    if (isRequestsDestroyed(activeRequests)) throw REQUEST_CANCELLED()

    const force = opts ? opts.force === true : false
    const req = this.core.replicator.addBlock(activeRequests, index, force)
    req.snapshot = index < this.length

    const timeout = opts && opts.timeout !== undefined ? opts.timeout : this.timeout
    if (timeout) req.context.setTimeout(req, timeout)

    let replicatedBlock = null

    try {
      replicatedBlock = await req.promise
    } catch (err) {
      if (isSessionMoved(err)) return this._get(index, opts)
      throw err
    }

    if (this._snapshot !== null) checkSnapshot(this, index)
    return maybeUnslab(replicatedBlock)
  }

  _shouldWait(opts, defaultValue) {
    if (opts) {
      if (opts.wait === false) return false
      if (opts.wait === true) return true
    }
    return defaultValue
  }

  _setupMarks() {
    if (this._marks === null) {
      const storage = this.snapshotted ? this.core.state.storage : this.state.storage
      this._marks = new MarkBitfield(storage)
    }
  }

  async markBlock(start, end = start + 1) {
    if (this.opened === false) await this.opening

    this._setupMarks()

    // TODO support as single rocks batch
    const setPromises = []
    for (let i = start; i < end; i++) {
      setPromises.push(this._marks.set(i, true))
    }

    return Promise.all(setPromises)
  }

  async clearMarkings() {
    if (this.opened === false) await this.opening

    this._setupMarks()

    await this._marks.clear()
    this._marks = null
  }

  async startMarking() {
    if (this._marking) {
      throw ASSERTION("Hypercore cannot be gc'ed when already in gc mode", this.discoveryKey)
    }
    if (this.state && this.state.name) {
      throw ASSERTION("Hypercore cannot be gc'ed when a named session", this.discoveryKey)
    }
    if (this.state && this.state.storage.atom) {
      throw ASSERTION("Hypercore cannot be gc'ed when an atomic session", this.discoveryKey)
    }
    if (this.opened === false) await this.opening
    await this.clearMarkings()

    this._marking = true
  }

  async sweep({ batchSize = 1000 } = {}) {
    if (this.opened === false) await this.opening

    assert(!this.snapshotted, 'Cannot sweep a snapshot')

    // No marks - load from storage
    this._setupMarks()

    let clearing = []
    let prevIndex = this.length
    for await (const index of this._marks.createMarkStream({ reverse: true })) {
      if (index + 1 === prevIndex) {
        prevIndex = index
        continue
      }
      clearing.push(this.clear(index + 1, prevIndex))
      if (clearing.length >= batchSize) {
        await Promise.all(clearing)
        clearing = []
      }
      prevIndex = index
    }
    // Clear range from the very start if not marked
    if (prevIndex > 0) clearing.push(this.clear(0, prevIndex))
    await Promise.all(clearing)

    this._marking = false
    await this.clearMarkings()
  }

  createReadStream(opts) {
    return new ReadStream(this, opts)
  }

  createWriteStream() {
    return new WriteStream(this)
  }

  createByteStream(opts) {
    return new ByteStream(this, opts)
  }

  download(range) {
    return new Download(this, range)
  }

  // TODO: get rid of this / deprecate it?
  undownload(range) {
    range.destroy(null)
  }

  // TODO: get rid of this / deprecate it?
  cancel(request) {
    // Do nothing for now
  }

  async truncate(newLength = 0, opts = {}) {
    if (this.opened === false) await this.opening
    if (this.closing) throw SESSION_CLOSED('Cannot append to a closed session', this.discoveryKey)

    const {
      fork = this.state.fork + 1,
      keyPair = this.keyPair,
      signature = null
    } = typeof opts === 'number' ? { fork: opts } : opts

    const isDefault = this.state === this.core.state
    const writable = !this._readonly && !!(signature || (keyPair && keyPair.secretKey))
    if (isDefault && writable === false && (newLength > 0 || fork !== this.state.fork)) {
      throw SESSION_NOT_WRITABLE('cannot append to a non-writable core', this.discoveryKey)
    }

    await this.state.truncate(newLength, fork, { keyPair, signature })

    // TODO: Should propagate from an event triggered by the oplog
    if (this.state === this.core.state) this.core.replicator.updateAll()
  }

  async append(blocks, opts = {}) {
    if (this.opened === false) await this.opening
    if (this.closing) throw SESSION_CLOSED('Cannot append to a closed session', this.discoveryKey)

    const isDefault = this.state === this.core.state
    const defaultKeyPair = this.state.name === null ? this.keyPair : null

    const { keyPair = defaultKeyPair, signature = null, maxLength, postappend = null } = opts
    const writable =
      !isDefault || !!signature || !!(keyPair && keyPair.secretKey) || opts.writable === true

    if (this._readonly || writable === false) {
      throw SESSION_NOT_WRITABLE('cannot append to a readonly core', this.discoveryKey)
    }

    blocks = Array.isArray(blocks) ? blocks : [blocks]

    const preappend = this.encryption && this._preappend

    const buffers = this.encodeBatch !== null ? this.encodeBatch(blocks) : new Array(blocks.length)

    if (this.encodeBatch === null) {
      for (let i = 0; i < blocks.length; i++) {
        buffers[i] = this._encode(this.valueEncoding, blocks[i])
      }
    }
    for (const b of buffers) {
      if (b.byteLength > MAX_SUGGESTED_BLOCK_SIZE) {
        throw BAD_ARGUMENT(
          'Appended block exceeds the maximum suggested block size',
          this.discoveryKey
        )
      }
    }

    return this.state.append(buffers, { keyPair, signature, preappend, postappend, maxLength })
  }

  async signable(length = -1, fork = -1) {
    if (this.opened === false) await this.opening
    if (length === -1) length = this.length
    if (fork === -1) fork = this.fork

    return caps.treeSignable(this.key, await this.treeHash(length), length, fork)
  }

  async treeHash(length = -1) {
    if (this.opened === false) await this.opening
    if (length === -1) length = this.length
    if (length > 0 && !(await this.has(length - 1))) await this.get(length - 1)

    const roots = await MerkleTree.getRoots(this.state, length)
    return crypto.tree(roots)
  }

  async missingNodes(index) {
    if (this.opened === false) await this.opening
    return await MerkleTree.missingNodes(this.core.state, 2 * index, this.core.state.length)
  }

  async proof(opts) {
    if (this.opened === false) await this.opening
    const rx = this.state.storage.read()
    const proofPromise = MerkleTree.proof(this.state, rx, opts)
    const blockPromise = opts && opts.block ? rx.getBlock(opts.block.index) : null
    rx.tryFlush()
    const [proof, block] = await Promise.all([proofPromise, blockPromise])
    const settled = await proof.settle()
    if (block) settled.block.value = block
    return settled
  }

  async applyProof(proof, from) {
    if (this.opened === false) await this.opening
    return this.core.verify(proof, from)
  }

  async verifyFullyRemote(proof) {
    if (this.opened === false) await this.opening
    const batch = await MerkleTree.verifyFullyRemote(this.state, proof)
    await this.core._verifyBatchUpgrade(batch, proof.manifest)
    return batch
  }

  generateRemoteProofForTreeNode(treeNodeIndex) {
    const blockProofIndex = flat.rightSpan(treeNodeIndex) / 2
    return proof(this, {
      index: blockProofIndex,
      // + 1 to length so the block is included
      upgrade: { start: 0, length: blockProofIndex + 1 }
    })
  }

  async recoverFromRemoteProof(remoteProof) {
    this.core.replicator.setPushOnly(true)
    this.core._repairMode = true

    await this.core.state.mutex.lock()

    try {
      const p = await verify(this.core.db, remoteProof)
      if (!p) return false

      const tx = this.core.storage.write()
      for (const node of p.proof.upgrade.nodes) {
        tx.putTreeNode(node)
      }
      await tx.flush()

      const succeed = p.proof.upgrade.nodes.length !== 0
      if (succeed) {
        this.core.replicator.setPushOnly(false)
      }
      return succeed
    } finally {
      this.core.state.mutex.unlock()
    }
  }

  recoverTreeNodeFromPeers() {
    this.core.replicator.setPushOnly(true)

    for (const peer of this.core.replicator.peers) {
      const req = {
        id: 0,
        fork: this.fork,
        upgrade: {
          start: 0,
          length: this.length
        }
      }
      peer.wireRequest.send(req)
    }
  }

  registerExtension(name, handlers = {}) {
    if (this.extensions.has(name)) {
      const ext = this.extensions.get(name)
      ext.handlers = handlers
      ext.encoding = c.from(handlers.encoding || c.buffer)
      ext.session = this
      return ext
    }

    const ext = {
      name,
      handlers,
      encoding: c.from(handlers.encoding || c.buffer),
      session: this,
      send(message, peer) {
        const buffer = c.encode(this.encoding, message)
        peer.extension(name, buffer)
      },
      broadcast(message) {
        const buffer = c.encode(this.encoding, message)
        for (const peer of this.session.peers) {
          peer.extension(name, buffer)
        }
      },
      destroy() {
        for (const peer of this.session.peers) {
          if (peer.extensions.get(name) === ext) peer.extensions.delete(name)
        }
        this.session.extensions.delete(name)
      },
      _onmessage(state, peer) {
        const m = this.encoding.decode(state)
        if (this.handlers.onmessage) this.handlers.onmessage(m, peer)
      }
    }

    this.extensions.set(name, ext)

    if (this.core === null) this._monitorIndex = -2
    else this.core.addMonitor(this)

    for (const peer of this.peers) {
      peer.extensions.set(name, ext)
    }

    return ext
  }

  _encode(enc, val) {
    const state = { start: this.padding, end: this.padding, buffer: null }

    if (b4a.isBuffer(val)) {
      if (state.start === 0) return val
      state.end += val.byteLength
    } else if (enc) {
      enc.preencode(state, val)
    } else {
      val = b4a.from(val)
      if (state.start === 0) return val
      state.end += val.byteLength
    }

    state.buffer = b4a.allocUnsafe(state.end)

    if (enc) enc.encode(state, val)
    else state.buffer.set(val, state.start)

    return state.buffer
  }

  _decode(enc, block, index) {
    if (this.encryption) block = block.subarray(this.encryption.padding(this.core, index))
    try {
      if (enc) return c.decode(enc, block)
    } catch (err) {
      throw DECODING_ERROR(err.message, this.discoveryKey)
    }
    return block
  }

  _getEncryptionProvider(e) {
    if (isEncryptionProvider(e)) return e
    if (!e || !e.key) return null
    return new DefaultEncryption(e.key, this.key, { block: e.block, compat: this.core.compat })
  }
}

module.exports = Hypercore

function isStream(s) {
  return typeof s === 'object' && s && typeof s.pipe === 'function'
}

async function preappend(blocks) {
  const offset = this.state.length
  const fork = this.state.encryptionFork

  for (let i = 0; i < blocks.length; i++) {
    await this.encryption.encrypt(offset + i, blocks[i], fork, this.core)
  }
}

function isValidIndex(index) {
  return index === 0 || index > 0
}

function maybeUnslab(block) {
  // Unslab only when it takes up less then half the slab
  return block !== null && 2 * block.byteLength < block.buffer.byteLength ? unslab(block) : block
}

function checkSnapshot(snapshot, index) {
  if (index >= snapshot.state.snapshotCompatLength) {
    throw SNAPSHOT_NOT_AVAILABLE(
      `snapshot at index ${index} not available (max compat length ${snapshot.state.snapshotCompatLength})`,
      snapshot.discoveryKey
    )
  }
}

function readBlock(rx, index) {
  const promise = rx.getBlock(index)
  rx.tryFlush()
  return promise
}

function initOnce(session, storage, key, opts) {
  if (storage === null) storage = opts.storage || null
  if (key === null) key = opts.key || null

  session.core = new Core(Hypercore.defaultStorage(storage), {
    preopen: opts.preopen,
    eagerUpgrade: opts.eagerUpgrade !== false,
    notDownloadingLinger: opts.notDownloadingLinger,
    allowFork: opts.allowFork !== false,
    allowPush: !!opts.allowPush,
    pushOnly: !!opts.pushOnly,
    alwaysLatestBlock: !!opts.allowLatestBlock,
    inflightRange: opts.inflightRange,
    compat: opts.compat === true,
    force: opts.force,
    createIfMissing: opts.createIfMissing,
    discoveryKey: opts.discoveryKey,
    overwrite: opts.overwrite,
    key,
    keyPair: opts.keyPair,
    legacy: opts.legacy,
    manifest: opts.manifest,
    group: opts.group,
    globalCache: opts.globalCache || null // session is a temp option, not to be relied on unless you know what you are doing (no semver guarantees)
  })
}

function maybeAddMonitor(name) {
  if (name === 'append' || name === 'truncate') return
  if (this._monitorIndex >= 0 || this.closing) return

  if (this.core === null) {
    this._monitorIndex = -2
  } else {
    this.core.addMonitor(this)
  }
}

function isSessionMoved(err) {
  return err.code === 'SESSION_MOVED'
}

function getEncryptionOption(opts) {
  // old style, supported for now but will go away
  if (opts.encryptionKey) return { key: opts.encryptionKey, block: !!opts.isBlockKey }
  if (!opts.encryption) return null
  return b4a.isBuffer(opts.encryption) ? { key: opts.encryption } : opts.encryption
}

function isEncryptionProvider(e) {
  return e && isFunction(e.padding) && isFunction(e.encrypt) && isFunction(e.decrypt)
}

function isFunction(fn) {
  return !!fn && typeof fn === 'function'
}

function createDiscoveryKeyHandler(fn) {
  return ondiscoverykey
  function ondiscoverykey(id) {
    if (!id || id.byteLength !== 32) throw BAD_ARGUMENT('Invalid discovery key')
    return fn(id)
  }
}

function isRequestsDestroyed(activeRequests) {
  // TODO: move this to an object instead so we can store a property in next major
  return activeRequests.length > 0 && activeRequests[0] === null
}
const crypto = require('hypercore-crypto')
const flat = require('flat-tree')
const b4a = require('b4a')
const { MerkleTree } = require('./merkle-tree')

module.exports = async function auditCore(
  core,
  { tree = true, blocks = true, bitfield = true, dryRun = false } = {}
) {
  const length = core.state.length
  const stats = {
    treeNodes: 0,
    blocks: 0,
    bits: 0,
    droppedTreeNodes: 0,
    droppedBlocks: 0,
    droppedBits: 0,
    corrupt: false
  }

  // audit the tree
  if (tree) {
    let tx = null

    const roots = await MerkleTree.getRootsFromStorage(core.state.storage, length)
    const stack = []

    for (const r of roots) {
      if (r === null) {
        if (!dryRun) {
          const storage = core.state.storage
          await storage.store.deleteCore(storage.core)
          return null
        }

        stats.corrupt = true
      }

      stack.push(r)
    }

    stats.treeNodes += roots.length

    while (stack.length > 0) {
      const node = stack.pop()

      if ((node.index & 1) === 0) continue

      const [left, right] = flat.children(node.index)

      const rx = core.state.storage.read()
      const leftNodePromise = rx.getTreeNode(left)
      const rightNodePromise = rx.getTreeNode(right)

      rx.tryFlush()

      const [leftNode, rightNode] = await Promise.all([leftNodePromise, rightNodePromise])

      if (isBadTree(node, leftNode, rightNode)) {
        if (!tx && !stats.corrupt) tx = core.state.storage.write()
        const [l, r] = flat.spans(node.index)
        tx.deleteTreeNodeRange(l, r + 1)
        stats.droppedTreeNodes++
        continue
      }

      if (!leftNode) continue

      stats.treeNodes += 2
      stack.push(leftNode, rightNode)
    }

    if (tx && !dryRun) await tx.flush()
  }

  // audit the blocks
  if (blocks) {
    let tx = null

    for await (const block of core.state.storage.createBlockStream()) {
      if (!core.bitfield.get(block.index)) {
        if (!tx && !stats.corrupt) tx = core.state.storage.write()
        tx.deleteBlock(block.index)
        stats.droppedBlocks++
      }

      const rx = core.state.storage.read()
      const treeNodePromise = rx.getTreeNode(2 * block.index)

      rx.tryFlush()

      const treeNode = await treeNodePromise

      if (isBadBlock(treeNode, block.value)) {
        if (!tx && !stats.corrupt) tx = core.state.storage.write()
        tx.deleteBlock(block.index)
        stats.droppedBlocks++
        continue
      }

      stats.blocks++
    }

    if (tx && !dryRun) await tx.flush()
  }

  if (bitfield) {
    let tx = null

    for (const index of allBits(core.bitfield)) {
      const rx = core.state.storage.read()
      const blockPromise = rx.getBlock(index)

      rx.tryFlush()

      const block = await blockPromise
      if (!block) {
        stats.droppedBits++
        if (dryRun) continue

        if (!tx && !stats.corrupt) tx = core.state.storage.write()

        core.bitfield.set(index, false)

        const page = core.bitfield.getBitfield(index)
        if (page.bitfield) tx.putBitfieldPage(page.index, page.bitfield)
        else tx.deleteBitfieldPage(page.index)
        continue
      }

      stats.bits++
    }

    if (tx && !dryRun) await tx.flush()
  }

  return stats
}

function isBadBlock(node, block) {
  if (!node) return true
  const hash = crypto.data(block)
  return !b4a.equals(hash, node.hash) || node.size !== block.byteLength
}

function isBadTree(parent, left, right) {
  if (!left && !right) return false
  if (!left || !right) return true
  const hash = crypto.parent(left, right)
  return !b4a.equals(hash, parent.hash) || parent.size !== left.size + right.size
}

function* allBits(bitfield) {
  let i = 0
  if (bitfield.get(0)) yield 0
  while (true) {
    i = bitfield.findFirst(true, i + 1)
    if (i === -1) break
    yield i
  }
}
const b4a = require('b4a')
const quickbit = require('./compat').quickbit

module.exports = class BitInterlude {
  constructor() {
    this.ranges = []
  }

  contiguousLength(from) {
    for (const r of this.ranges) {
      if (r.start > from) break
      if (!r.value && r.start <= from) return r.start
    }

    // TODO: be smarter
    while (this.get(from) === true) from++
    return from
  }

  get(index) {
    let start = 0
    let end = this.ranges.length

    while (start < end) {
      const mid = (start + end) >> 1
      const r = this.ranges[mid]

      if (index < r.start) {
        end = mid
        continue
      }

      if (index >= r.end) {
        if (mid === start) break
        start = mid
        continue
      }

      return r.value
    }

    return false
  }

  setRange(start, end, value) {
    if (start === end) return

    let r = null

    for (let i = 0; i < this.ranges.length; i++) {
      r = this.ranges[i]

      // if already inside, stop
      if (r.start <= start && end <= r.end) {
        if (value === r.value) return

        const ranges = mergeRanges(r, { start, end, value })
        this.ranges.splice(i, 1, ...ranges)

        return
      }

      // we wanna overun the interval
      if (start > r.end) {
        continue
      }

      // we overran but this interval is ending after us, move it back
      if (end >= r.start && end <= r.end) {
        r.start = r.value === value ? start : end
        if (r.value !== value) this.ranges.splice(i, 0, { start, end, value })
        return
      }

      // we overran but our start is contained in this interval, move start back
      if (start >= r.start && start <= r.end) {
        if (r.value !== value) {
          r.end = start // Crop current comparison

          // If there's a next range
          if (this.ranges.length - 1 > i) {
            const next = this.ranges[i + 1]
            // If the same, go next
            if (next.value === value) continue
            // Else we overlap so update next range
            if (next.start < end) next.start = end
          }

          this.ranges.splice(++i, 0, { start, end, value })
          return
        }

        start = r.start
      }

      let remove = 0

      for (let j = i; j < this.ranges.length; j++) {
        const n = this.ranges[j]
        if (n.start > end || n.value !== value) break
        if (n.start <= end && n.end > end) end = n.end
        remove++
      }

      this.ranges.splice(i, remove, { start, end, value })
      return
    }

    if (r !== null) {
      if (start <= r.end && end > r.end) {
        r.end = end
        return
      }

      // we never
      if (r.end > start) return
    }

    this.ranges.push({ start, end, value })
  }

  flush(tx, bitfield) {
    if (!this.ranges.length) return []

    let index = this.ranges[0].start
    const final = this.ranges[this.ranges.length - 1].end

    let i = 0

    while (index < final) {
      const page = bitfield.getBitfield(index) // read only
      const pageIndex = page ? page.index : bitfield.getPageIndex(index)

      const buf = b4a.allocUnsafe(bitfield.getPageByteLength())

      if (page) {
        const src = page.bitfield // Uint32Array
        buf.set(b4a.from(src.buffer, src.byteOffset, src.byteLength), 0)
      } else {
        b4a.fill(buf, 0)
      }

      const last = (pageIndex + 1) * (buf.byteLength << 3)
      const offset = pageIndex * (buf.byteLength << 3)

      let hasValue = false

      while (i < this.ranges.length) {
        const { start, end, value } = this.ranges[i]

        if (!hasValue && value) hasValue = true

        const from = start < index ? index : start
        const to = end < last ? end : last

        quickbit.fill(buf, value, from - offset, to - offset)

        index = to

        if (to === last) break

        i++
      }

      if (page || hasValue) tx.putBitfieldPage(pageIndex, buf)
    }

    return this.ranges
  }
}

function mergeRanges(a, b) {
  const ranges = []
  if (a.start < b.start) ranges.push({ start: a.start, end: b.start, value: a.value })
  ranges.push({ start: b.start, end: b.end, value: b.value })
  if (b.end < a.end) ranges.push({ start: b.end, end: a.end, value: a.value })

  return ranges
}
const BigSparseArray = require('big-sparse-array')
const b4a = require('b4a')
const quickbit = require('./compat').quickbit

const BITS_PER_PAGE = 32768
const BYTES_PER_PAGE = BITS_PER_PAGE / 8
const WORDS_PER_PAGE = BYTES_PER_PAGE / 4
const BITS_PER_SEGMENT = 2097152
const BYTES_PER_SEGMENT = BITS_PER_SEGMENT / 8
const WORDS_PER_SEGMENT = BYTES_PER_SEGMENT / 4
const INITIAL_WORDS_PER_SEGMENT = 1024
const PAGES_PER_SEGMENT = BITS_PER_SEGMENT / BITS_PER_PAGE
const SEGMENT_GROWTH_FACTOR = 4

class BitfieldPage {
  constructor(index, segment) {
    this.index = index
    this.offset = index * BYTES_PER_PAGE - segment.offset
    this.bitfield = null
    this.segment = segment

    segment.add(this)
  }

  get tree() {
    return this.segment.tree
  }

  get(index, dirty) {
    return quickbit.get(this.bitfield, index)
  }

  set(index, val) {
    if (quickbit.set(this.bitfield, index, val)) {
      this.tree.update(this.offset * 8 + index)
    }
  }

  setRange(start, end, val) {
    quickbit.fill(this.bitfield, val, start, end)

    let i = Math.floor(start / 128)
    const n = i + Math.ceil((end - start) / 128)

    while (i <= n) this.tree.update(this.offset * 8 + i++ * 128)
  }

  findFirst(val, position) {
    return quickbit.findFirst(this.bitfield, val, position)
  }

  findLast(val, position) {
    return quickbit.findLast(this.bitfield, val, position)
  }

  count(start, length, val) {
    const end = start + length

    let i = start
    let c = 0

    while (length > 0) {
      const l = this.findFirst(val, i)
      if (l === -1 || l >= end) return c

      const h = this.findFirst(!val, l + 1)
      if (h === -1 || h >= end) return c + end - l

      c += h - l
      length -= h - i
      i = h
    }

    return c
  }
}

class BitfieldSegment {
  constructor(index, bitfield) {
    this.index = index
    this.offset = index * BYTES_PER_SEGMENT
    this.tree = quickbit.Index.from(bitfield, BYTES_PER_SEGMENT)
    this.pages = new Array(PAGES_PER_SEGMENT)
  }

  get bitfield() {
    return this.tree.field
  }

  add(page) {
    const i = page.index - this.index * PAGES_PER_SEGMENT
    this.pages[i] = page

    const start = i * WORDS_PER_PAGE
    const end = start + WORDS_PER_PAGE

    if (end >= this.bitfield.length) this.reallocate(end)

    page.bitfield = this.bitfield.subarray(start, end)
  }

  reallocate(length) {
    let target = this.bitfield.length
    while (target < length) target *= SEGMENT_GROWTH_FACTOR

    const bitfield = new Uint32Array(target)
    bitfield.set(this.bitfield)

    this.tree = quickbit.Index.from(bitfield, BYTES_PER_SEGMENT)

    for (let i = 0; i < this.pages.length; i++) {
      const page = this.pages[i]
      if (!page) continue

      const start = i * WORDS_PER_PAGE
      const end = start + WORDS_PER_PAGE

      page.bitfield = bitfield.subarray(start, end)
    }
  }

  findFirst(val, position) {
    position = this.tree.skipFirst(!val, position)

    let j = position & (BITS_PER_PAGE - 1)
    let i = (position - j) / BITS_PER_PAGE

    if (i >= PAGES_PER_SEGMENT) return -1

    while (i < this.pages.length) {
      const p = this.pages[i]

      let index = -1

      if (p) index = p.findFirst(val, j)
      else if (!val) index = j

      if (index !== -1) return i * BITS_PER_PAGE + index

      j = 0
      i++
    }

    return -1
  }

  findLast(val, position) {
    position = this.tree.skipLast(!val, position)

    let j = position & (BITS_PER_PAGE - 1)
    let i = (position - j) / BITS_PER_PAGE

    if (i >= PAGES_PER_SEGMENT) return -1

    while (i >= 0) {
      const p = this.pages[i]

      let index = -1

      if (p) index = p.findLast(val, j)
      else if (!val) index = j

      if (index !== -1) return i * BITS_PER_PAGE + index

      j = BITS_PER_PAGE - 1
      i--
    }

    return -1
  }
}

module.exports = class Bitfield {
  static BITS_PER_PAGE = BITS_PER_PAGE
  static BYTES_PER_PAGE = BYTES_PER_PAGE

  constructor(buffer) {
    this.resumed = !!(buffer && buffer.byteLength >= 0)

    this._pages = new BigSparseArray()
    this._segments = new BigSparseArray()

    const view = this.resumed
      ? new Uint32Array(buffer.buffer, buffer.byteOffset, Math.floor(buffer.byteLength / 4))
      : new Uint32Array(INITIAL_WORDS_PER_SEGMENT)

    for (let i = 0; i < view.length; i += WORDS_PER_SEGMENT) {
      let bitfield = view.subarray(i, i + WORDS_PER_SEGMENT)
      let length = WORDS_PER_SEGMENT

      if (i === 0) {
        length = INITIAL_WORDS_PER_SEGMENT
        while (length < bitfield.length) length *= SEGMENT_GROWTH_FACTOR
      }

      if (bitfield.length !== length) {
        const copy = new Uint32Array(length)
        copy.set(bitfield, 0)
        bitfield = copy
      }

      const segment = new BitfieldSegment(i / WORDS_PER_SEGMENT, bitfield)
      this._segments.set(segment.index, segment)

      for (let j = 0; j < bitfield.length; j += WORDS_PER_PAGE) {
        const page = new BitfieldPage((i + j) / WORDS_PER_PAGE, segment)
        this._pages.set(page.index, page)
      }
    }
  }

  static from(bitfield) {
    return new Bitfield(bitfield.toBuffer(bitfield._pages.maxLength * BITS_PER_PAGE))
  }

  toBuffer(length) {
    const pages = Math.ceil(length / BITS_PER_PAGE)
    const buffer = b4a.allocUnsafe(pages * BYTES_PER_PAGE)

    for (let i = 0; i < pages; i++) {
      const page = this._pages.get(i)
      const offset = i * BYTES_PER_PAGE

      if (page) {
        const buf = b4a.from(
          page.bitfield.buffer,
          page.bitfield.byteOffset,
          page.bitfield.byteLength
        )

        buffer.set(buf, offset)
      } else {
        buffer.fill(0, offset, offset + BYTES_PER_PAGE)
      }
    }

    return buffer
  }

  getBitfield(index) {
    const i = this.getPageIndex(index)

    const p = this._pages.get(i)
    return p || null
  }

  merge(bitfield, length) {
    let i = 0

    while (i < length) {
      const start = bitfield.firstSet(i)
      if (start === -1) break

      i = bitfield.firstUnset(start)

      if (i === -1 || i > length) i = length

      this.setRange(start, i, true)

      if (i >= length) break
    }
  }

  get(index) {
    const j = index & (BITS_PER_PAGE - 1)
    const i = (index - j) / BITS_PER_PAGE

    const p = this._pages.get(i)

    return p ? p.get(j) : false
  }

  getPageByteLength() {
    return BYTES_PER_PAGE
  }

  getPageIndex(index) {
    const j = index & (BITS_PER_PAGE - 1)
    return (index - j) / BITS_PER_PAGE
  }

  getPage(index, create) {
    const i = this.getPageIndex(index)

    let p = this._pages.get(i)

    if (p) return p

    if (!create) return null

    const k = Math.floor(i / PAGES_PER_SEGMENT)
    const s =
      this._segments.get(k) ||
      this._segments.set(
        k,
        new BitfieldSegment(
          k,
          new Uint32Array(k === 0 ? INITIAL_WORDS_PER_SEGMENT : WORDS_PER_SEGMENT)
        )
      )

    p = this._pages.set(i, new BitfieldPage(i, s))

    return p
  }

  set(index, val) {
    const j = index & (BITS_PER_PAGE - 1)
    const i = (index - j) / BITS_PER_PAGE

    let p = this._pages.get(i)

    if (!p && val) {
      const k = Math.floor(i / PAGES_PER_SEGMENT)
      const s =
        this._segments.get(k) ||
        this._segments.set(
          k,
          new BitfieldSegment(
            k,
            new Uint32Array(k === 0 ? INITIAL_WORDS_PER_SEGMENT : WORDS_PER_SEGMENT)
          )
        )

      p = this._pages.set(i, new BitfieldPage(i, s))
    }

    if (p) p.set(j, val)
  }

  setRange(start, end, val) {
    let j = start & (BITS_PER_PAGE - 1)
    let i = (start - j) / BITS_PER_PAGE

    while (start < end) {
      let p = this._pages.get(i)

      if (!p && val) {
        const k = Math.floor(i / PAGES_PER_SEGMENT)
        const s =
          this._segments.get(k) ||
          this._segments.set(
            k,
            new BitfieldSegment(
              k,
              new Uint32Array(k === 0 ? INITIAL_WORDS_PER_SEGMENT : WORDS_PER_SEGMENT)
            )
          )

        p = this._pages.set(i, new BitfieldPage(i, s))
      }

      const offset = i * BITS_PER_PAGE
      const last = Math.min(end - offset, BITS_PER_PAGE)
      const range = last - j

      if (p) p.setRange(j, last, val)

      j = 0
      i++
      start += range
    }
  }

  findFirst(val, position) {
    let j = position & (BITS_PER_SEGMENT - 1)
    let i = (position - j) / BITS_PER_SEGMENT

    while (i < this._segments.maxLength) {
      const s = this._segments.get(i)

      let index = -1

      if (s) index = s.findFirst(val, j)
      else if (!val) index = j

      if (index !== -1) return i * BITS_PER_SEGMENT + index

      j = 0
      i++
    }

    return val ? -1 : this._segments.maxLength * BITS_PER_SEGMENT
  }

  firstSet(position) {
    return this.findFirst(true, position)
  }

  firstUnset(position) {
    return this.findFirst(false, position)
  }

  findLast(val, position) {
    let j = position & (BITS_PER_SEGMENT - 1)
    let i = (position - j) / BITS_PER_SEGMENT

    while (i >= 0) {
      const s = this._segments.get(i)

      let index = -1

      if (s) index = s.findLast(val, j)
      else if (!val) index = j

      if (index !== -1) return i * BITS_PER_SEGMENT + index

      j = BITS_PER_SEGMENT - 1
      i--
    }

    return -1
  }

  lastSet(position) {
    return this.findLast(true, position)
  }

  lastUnset(position) {
    return this.findLast(false, position)
  }

  hasUnset(start, length) {
    const end = start + length

    let j = start & (BITS_PER_SEGMENT - 1)
    let i = (start - j) / BITS_PER_SEGMENT

    while (i < this._segments.maxLength) {
      const s = this._segments.get(i)
      const index = s ? s.findFirst(false, j) : j
      if (index !== -1) return i * BITS_PER_SEGMENT + index < end

      j = 0
      i++

      if (i * BITS_PER_SEGMENT >= end) return false
    }

    return true
  }

  hasSet(start, length) {
    const end = start + length

    let j = start & (BITS_PER_SEGMENT - 1)
    let i = (start - j) / BITS_PER_SEGMENT

    while (i < this._segments.maxLength) {
      const s = this._segments.get(i)

      let index = -1

      if (s) index = s.findFirst(true, j)

      if (index !== -1) return i * BITS_PER_SEGMENT + index < end

      j = 0
      i++

      if (i * BITS_PER_SEGMENT >= end) return false
    }

    return false
  }

  count(start, length, val) {
    let j = start & (BITS_PER_PAGE - 1)
    let i = (start - j) / BITS_PER_PAGE
    let c = 0

    while (length > 0) {
      const p = this._pages.get(i)

      const end = Math.min(j + length, BITS_PER_PAGE)
      const range = end - j

      if (p) c += p.count(j, range, val)
      else if (!val) c += range

      j = 0
      i++
      length -= range
    }

    return c
  }

  countSet(start, length) {
    return this.count(start, length, true)
  }

  countUnset(start, length) {
    return this.count(start, length, false)
  }

  *want(start, length) {
    if (start & 31) {
      const d = start & 31
      start -= d
      length += d
    }

    let j = start & (BITS_PER_SEGMENT - 1)
    let i = (start - j) / BITS_PER_SEGMENT

    while (length > 0) {
      const s = this._segments.get(i)

      if (s) {
        let end = Math.min(j + length, BITS_PER_SEGMENT)
        if (end & 31) end += 32 - (end & 31)

        yield {
          start: i * BITS_PER_SEGMENT + j,
          bitfield: s.bitfield.subarray(j / 32, end / 32)
        }
      }

      j = 0
      i++
      length -= BITS_PER_SEGMENT
    }
  }

  clear(tx) {
    return tx.deleteBitfieldPageRange(0, -1)
  }

  onupdate(ranges) {
    for (const { start, end, value } of ranges) {
      this.setRange(start, end, value)
    }
  }

  static async open(storage, length) {
    if (length === 0) return new Bitfield(storage, null)

    const pages = Math.ceil(length / BITS_PER_PAGE)
    const buffer = b4a.alloc(pages * BYTES_PER_PAGE)
    const stream = storage.createBitfieldStream({ lt: pages })

    for await (const { index, page } of stream) {
      buffer.set(page, index * BYTES_PER_PAGE)
    }

    return new Bitfield(buffer)
  }
}
const crypto = require('hypercore-crypto')
const sodium = require('sodium-universal')
const b4a = require('b4a')
const c = require('compact-encoding')

// TODO: rename this to "crypto" and move everything hashing related etc in here
// Also lets move the tree stuff from hypercore-crypto here

const [
  TREE,
  REPLICATE_INITIATOR,
  REPLICATE_RESPONDER,
  MANIFEST,
  DEFAULT_NAMESPACE,
  DEFAULT_ENCRYPTION
] = crypto.namespace('hypercore', 6)

exports.MANIFEST = MANIFEST
exports.DEFAULT_NAMESPACE = DEFAULT_NAMESPACE
exports.DEFAULT_ENCRYPTION = DEFAULT_ENCRYPTION

exports.replicate = function (isInitiator, key, handshakeHash) {
  const out = b4a.allocUnsafe(32)
  sodium.crypto_generichash_batch(
    out,
    [isInitiator ? REPLICATE_INITIATOR : REPLICATE_RESPONDER, key],
    handshakeHash
  )
  return out
}

exports.treeSignable = function (manifestHash, treeHash, length, fork) {
  const state = { start: 0, end: 112, buffer: b4a.allocUnsafe(112) }
  c.fixed32.encode(state, TREE)
  c.fixed32.encode(state, manifestHash)
  c.fixed32.encode(state, treeHash)
  c.uint64.encode(state, length)
  c.uint64.encode(state, fork)
  return state.buffer
}

exports.treeSignableCompat = function (hash, length, fork, noHeader) {
  const end = noHeader ? 48 : 80
  const state = { start: 0, end, buffer: b4a.allocUnsafe(end) }
  if (!noHeader) c.fixed32.encode(state, TREE) // ultra legacy mode, kill in future major
  c.fixed32.encode(state, hash)
  c.uint64.encode(state, length)
  c.uint64.encode(state, fork)
  return state.buffer
}
// Export the appropriate version of `quickbit-universal` as the plain import
// may resolve to an older version in some environments
let quickbit = require('quickbit-universal')
if (
  typeof quickbit.findFirst !== 'function' ||
  typeof quickbit.findLast !== 'function' ||
  typeof quickbit.clear !== 'function'
) {
  // This should always load the fallback from the locally installed version
  quickbit = require('quickbit-universal/fallback')
}
exports.quickbit = quickbit
const crypto = require('hypercore-crypto')
const flat = require('flat-tree')
const b4a = require('b4a')
const quickbit = require('quickbit-universal')
const Bitfield = require('./bitfield')

const MAX_BATCH_USED = 4 * 1024 * 1024
const MIN_BATCH_USED = 512 * 1024

// just in its own file as its a bit involved

module.exports = copyPrologue

async function copyPrologue(src, dst) {
  const prologue = dst.header.manifest.prologue

  if (src.length < prologue.length || prologue.length === 0) return

  const stack = []
  const roots = flat.fullRoots(prologue.length * 2)
  const batch = { roots, first: true, last: false, contig: 0, used: 0, tree: [], blocks: [] }

  for (let i = 0; i < roots.length; i++) {
    const node = roots[i]
    batch.tree.push(node)
    stack.push(node)
  }

  let lastPage = -1
  let lastBlock = -1

  for await (const data of src.storage.createBlockStream({
    gte: 0,
    lt: prologue.length,
    reverse: true
  })) {
    if (walkTree(stack, data.index * 2, batch) === false) {
      throw new Error('Missing block or tree node for ' + data.index)
    }

    batch.contig = data.index + 1 === lastBlock ? batch.contig + 1 : 1
    lastBlock = data.index

    const page = getBitfieldPage(data.index)
    batch.blocks.push(data)

    if (lastPage !== page) batch.used += 4096
    batch.used += Math.max(data.value.byteLength, 128) // 128 is just a sanity number to avoid mega batches

    // always safe to partially flush so we do that ondemand to reduce memory usage...
    if ((batch.used >= MIN_BATCH_USED && page !== lastPage) || batch.used >= MAX_BATCH_USED) {
      await flushBatch(prologue, src, dst, batch)
    }

    lastPage = page
  }

  if (lastBlock !== 0) batch.contig = 0

  batch.last = true
  await flushBatch(prologue, src, dst, batch)
}

async function flushBatch(prologue, src, dst, batch) {
  const nodePromises = []

  const srcReader = src.storage.read()
  for (const index of batch.tree) {
    nodePromises.push(srcReader.getTreeNode(index))
  }
  srcReader.tryFlush()

  const nodes = await Promise.all(nodePromises)

  const pagePromises = []
  const dstReader = dst.storage.read()

  const headPromise = batch.first ? dstReader.getHead() : null
  if (headPromise) headPromise.catch(noop)

  let lastPage = -1
  for (const { index } of batch.blocks) {
    const page = getBitfieldPage(index)
    if (page === lastPage) continue
    lastPage = page
    pagePromises.push(dstReader.getBitfieldPage(page))
  }

  dstReader.tryFlush()

  const pages = await Promise.all(pagePromises)
  const head = headPromise === null ? null : await headPromise
  const userData = []

  // reads done!

  if (batch.first) {
    const roots = nodes.slice(0, batch.roots.length)

    for (const node of roots) {
      if (!node) throw new Error('Missing nodes for prologue hash')
    }

    const treeHash = crypto.tree(roots)
    if (!b4a.equals(treeHash, prologue.hash)) throw new Error('Prologue does not match source')
  }

  if (batch.first) {
    for await (const data of src.storage.createUserDataStream()) userData.push(data)
  }

  for (let i = 0; i < pages.length; i++) {
    if (!pages[i]) pages[i] = b4a.alloc(4096)
  }

  const tx = dst.storage.write()

  for (const node of nodes) tx.putTreeNode(node)

  lastPage = -1
  let pageIndex = -1

  for (const { index, value } of batch.blocks) {
    const page = getBitfieldPage(index)

    if (page !== lastPage) {
      lastPage = page
      pageIndex++
      // queue the page now, we mutate it below but its the same ref
      tx.putBitfieldPage(pageIndex, pages[pageIndex])
    }

    const pageBuffer = pages[pageIndex]
    quickbit.set(pageBuffer, getBitfieldOffset(index), true)
    tx.putBlock(index, value)
  }

  for (const { key, value } of userData) {
    tx.putUserData(key, value)
  }

  let upgraded = batch.first && !head
  if (upgraded) {
    tx.setHead(prologueToTree(prologue))
  }

  await tx.flush()

  if (upgraded) {
    const roots = nodes.slice(0, batch.roots.length)
    dst.state.setRoots(roots)
    dst.header.tree = prologueToTree(prologue)
  }

  if (userData.length > 0) {
    dst.header.userData = userData.concat(dst.header.userData)
  }

  if (batch.contig) {
    // TODO: we need to persist this somehow
    dst.header.hints.contiguousLength = batch.contig
  }

  let start = 0
  let length = 0

  // update in memory bitfield
  for (const { index } of batch.blocks) {
    if (start === 0 || start - 1 === index) {
      length++
    } else {
      if (length > 0) signalReplicator(dst, upgraded, start, length)
      upgraded = false
      length = 1
    }

    start = index
    dst.bitfield.set(index, true)
  }

  if (length > 0) signalReplicator(dst, upgraded, start, length)

  // unlink
  batch.tree = []
  batch.blocks = []
  batch.first = false
  batch.used = 0
}

function signalReplicator(core, upgraded, start, length) {
  if (upgraded) {
    core.replicator.cork()
    core.replicator.onhave(start, length, false)
    core.replicator.onupgrade()
    core.replicator.uncork()
  } else {
    core.replicator.onhave(start, length, false)
  }
}

function prologueToTree(prologue) {
  return {
    fork: 0,
    length: prologue.length,
    rootHash: prologue.hash,
    signature: null
  }
}

function getBitfieldPage(index) {
  return Math.floor(index / Bitfield.BITS_PER_PAGE)
}

function getBitfieldOffset(index) {
  return index & (Bitfield.BITS_PER_PAGE - 1)
}

function walkTree(stack, target, batch) {
  while (stack.length > 0) {
    const node = stack.pop()

    if ((node & 1) === 0) {
      if (node === target) return true
      continue
    }

    const ite = flat.iterator(node)
    if (!ite.contains(target)) continue

    while ((ite.index & 1) !== 0) {
      const left = ite.leftChild()
      const right = ite.sibling() // is right child

      batch.tree.push(left, right)

      if (ite.contains(target)) stack.push(left)
      else ite.sibling()
    }

    if (ite.index === target) return true
  }

  return false
}

function noop() {}
const crypto = require('hypercore-crypto')
const b4a = require('b4a')
const unslab = require('unslab')
const z32 = require('z32')
const Mutex = require('./mutex')
const { MerkleTree, ReorgBatch } = require('./merkle-tree')
const BitInterlude = require('./bit-interlude')
const Bitfield = require('./bitfield')
const RemoteBitfield = require('./remote-bitfield')
const {
  BAD_ARGUMENT,
  STORAGE_EMPTY,
  STORAGE_CONFLICT,
  INVALID_SIGNATURE,
  INVALID_CHECKSUM
} = require('hypercore-errors')
const Verifier = require('./verifier')
const audit = require('./audit')
const copyPrologue = require('./copy-prologue')
const SessionState = require('./session-state')
const Replicator = require('./replicator')

module.exports = class Core {
  constructor(db, opts = {}) {
    this.db = db
    this.storage = null
    this.replicator = new Replicator(this, opts)
    this.sessionStates = []
    this.monitors = []
    this.activeSessions = 0
    this.gc = 0 // corestore uses this to main a gc strike pool

    this.id = opts.key ? z32.encode(opts.key) : null
    this.key = opts.key || null
    this.discoveryKey = opts.discoveryKey || (opts.key && crypto.discoveryKey(opts.key)) || null
    this.manifest = null
    this.opening = null
    this.closing = null
    this.exclusive = null

    this.preupdate = null
    this.header = null
    this.compat = false
    this.bitfield = null
    this.verifier = null
    this.truncating = 0
    this.skipBitfield = null
    this.globalCache = opts.globalCache || null
    this.autoClose = opts.autoClose !== false
    this.onidle = noop
    this.ongroupupdate = noop

    this.state = null
    this.opened = false
    this.destroyed = false
    this.closed = false

    this.hintsChanged = false
    this.remoteContiguousLengthChanged = false

    this._bitfield = null
    this._verifies = null
    this._verifiesFlushed = null
    this._legacy = !!opts.legacy
    this._repairMode = false

    this.opening = this._open(opts)
    this.opening.catch(noop)
  }

  static setRecoveryPeers(peers) {
    SessionState.RECOVERY_PEERS = peers
  }

  ready() {
    return this.opening
  }

  addMonitor(s) {
    if (s._monitorIndex >= 0) return
    s._monitorIndex = this.monitors.push(s) - 1
  }

  removeMonitor(s) {
    if (s._monitorIndex < 0) return
    const head = this.monitors.pop()
    if (head !== s) this.monitors[(head._monitorIndex = s._monitorIndex)] = head
    s._monitorIndex = -1
  }

  emitRemoteContiguousLength() {
    if (!this.remoteContiguousLengthChanged) return
    this.remoteContiguousLengthChanged = false

    const length = this.header.hints.remoteContiguousLength

    for (let i = this.monitors.length - 1; i >= 0; i--) {
      this.monitors[i].emit('remote-contiguous-length', length)
    }
  }

  emitManifest() {
    for (let i = this.monitors.length - 1; i >= 0; i--) {
      this.monitors[i].emit('manifest')
    }
  }

  createUserDataStream(opts, session = this.state) {
    return session.storage.createUserDataStream(opts)
  }

  allSessions() {
    const sessions = []
    for (const state of this.sessionStates) {
      if (state.sessions.length) sessions.push(...state.sessions)
    }
    return sessions
  }

  hasSession() {
    return this.activeSessions !== 0
  }

  compact() {
    const compacting = []
    for (const s of this.sessionStates) {
      compacting.push(s.storage.compact())
    }
    return Promise.all(compacting)
  }

  checkIfIdle() {
    if (!this.opened || this.destroyed === true || this.hasSession() === true) return
    if (this.replicator.idle() === false) return
    if (this.state === null || this.state.mutex.idle() === false) return
    this.onidle()
  }

  async lockExclusive() {
    if (this.exclusive === null) this.exclusive = new Mutex()
    await this.exclusive.lock()
  }

  unlockExclusive() {
    if (this.exclusive !== null) this.exclusive.unlock()
  }

  async _open(opts) {
    try {
      await this._tryOpen(opts)
    } catch (err) {
      this.onidle()
      throw err
    }

    this.opened = true
  }

  async _tryOpen(opts) {
    if (opts.preopen) await opts.preopen // just a hook to allow exclusive access here...

    let storage = await this.db.resumeCore(this.discoveryKey)

    let overwrite = opts.overwrite === true

    const force = opts.force === true
    const createIfMissing = opts.createIfMissing !== false
    // kill this flag soon
    const legacy = !!opts.legacy

    // default to true for now if no manifest is provided
    let compat = opts.compat === true || (opts.compat !== false && !opts.manifest)

    let header = storage ? parseHeader(await getCoreInfo(storage)) : null

    const hadManifest = !!header && !!header.manifest && !!header.key
    const hadKeyPair = !!header && !!header.keyPair

    if (force && opts.key && header && !b4a.equals(header.key, opts.key)) {
      overwrite = true
    }

    if (!header && opts.discoveryKey && !(opts.key || opts.manifest)) {
      throw STORAGE_EMPTY('No Hypercore is stored here', this.discoveryKey)
    }

    if (!header || overwrite) {
      if (!createIfMissing) {
        throw STORAGE_EMPTY('No Hypercore is stored here', this.discoveryKey)
      }

      if (compat) {
        if (opts.key && opts.keyPair && !b4a.equals(opts.key, opts.keyPair.publicKey)) {
          throw BAD_ARGUMENT('Key must match publicKey when in compat mode', this.discoveryKey)
        }
      }

      const keyPair = opts.keyPair || (opts.key ? null : crypto.keyPair())

      const defaultManifest =
        !opts.manifest &&
        (!!opts.compat || !opts.key || !!(keyPair && b4a.equals(opts.key, keyPair.publicKey)))
      const manifest = defaultManifest
        ? Verifier.defaultSignerManifest(opts.key || keyPair.publicKey)
        : Verifier.createManifest(opts.manifest)

      header = {
        key: opts.key || (compat ? manifest.signers[0].publicKey : Verifier.manifestHash(manifest)),
        manifest,
        keyPair: keyPair
          ? { publicKey: keyPair.publicKey, secretKey: keyPair.secretKey || null }
          : null,
        frozen: false,
        tree: {
          fork: 0,
          length: 0,
          rootHash: null,
          signature: null,
          timestamp: 0
        },
        group: null,
        hints: {
          reorgs: [],
          contiguousLength: 0,
          remoteContiguousLength: 0,
          recovering: 0
        }
      }

      const discoveryKey = opts.discoveryKey || crypto.discoveryKey(header.key)

      storage = await this.db.createCore({
        key: header.key,
        manifest,
        keyPair,
        frozen: false,
        discoveryKey,
        userData: opts.userData || [],
        alias: opts.alias || null
      })
    }

    // unslab the long lived buffers to avoid keeping the slab alive
    header.key = unslab(header.key)

    if (header.tree) {
      header.tree.rootHash = unslab(header.tree.rootHash)
      header.tree.signature = unslab(header.tree.signature)
    }

    if (header.keyPair) {
      header.keyPair.publicKey = unslab(header.keyPair.publicKey)
      header.keyPair.secretKey = unslab(header.keyPair.secretKey)
    }

    if (header.keyPair) {
      header.keyPair.publicKey = unslab(header.keyPair.publicKey)
      header.keyPair.secretKey = unslab(header.keyPair.secretKey)
    }

    if (opts.manifest) {
      const manifest = Verifier.createManifest(opts.manifest)

      // if we provide a manifest and no key, verify that the stored key is the same
      if (!opts.key && !Verifier.isValidManifest(header.key, manifest)) {
        throw STORAGE_CONFLICT('Manifest does not hash to provided key', this.discoveryKey)
      }

      if (!header.manifest) header.manifest = manifest
    }

    if (opts.key && !b4a.equals(header.key, opts.key)) {
      throw STORAGE_CONFLICT('Another Hypercore is stored here', this.discoveryKey)
    }

    // if we signalled compat, but already now this core isn't disable it
    if (compat && header.manifest && !Verifier.isCompat(header.key, header.manifest)) {
      compat = false
    } else if (!compat && header.manifest && Verifier.isCompat(header.key, header.manifest)) {
      compat = true
    }

    const prologue = header.manifest ? header.manifest.prologue : null

    const bitfield = await Bitfield.open(storage, header.tree.length)

    const treeInfo = {
      fork: header.tree.fork,
      length: header.tree.length,
      signature: header.tree.signature,
      roots: await maybeGetRootsFromStorage(storage, header.tree.length),
      prologue
    }

    this._repairMode = !treeInfo.roots.length && treeInfo.length && !overwrite
    // Set push only mode if in repair mode
    if (this._repairMode) {
      this.replicator.setPushOnly(true)
    }

    if (opts.group) {
      if (!header.group) header.group = await this.db.createGroup(opts.group)
      else if (!b4a.equals(header.group.key, opts.group)) {
        throw STORAGE_CONFLICT(
          'Core is already register to group: ' + b4a.toString(header.group.key, 'hex')
        )
      }

      const tx = storage.write()
      tx.setGroup(header.group)
      await tx.flush()
    }

    if (overwrite) {
      const tx = storage.write()
      tx.deleteTreeNodeRange(0, -1)
      tx.deleteBlockRange(0, -1)
      bitfield.clear(tx)
      await tx.flush()
    }

    const len = bitfield.findFirst(false, header.hints.contiguousLength)
    if (header.hints.contiguousLength !== len && !storage.readOnly) {
      header.hints.contiguousLength = len
      const tx = storage.write()
      tx.setHints(header.hints)
      await tx.flush()
    }

    // to unslab
    if (header.manifest) {
      header.manifest = Verifier.createManifest(header.manifest)
      if (!hadManifest || (header.keyPair && !hadKeyPair)) {
        const tx = storage.write()
        tx.setAuth({
          key: header.key,
          discoveryKey: crypto.discoveryKey(header.key),
          manifest: header.manifest,
          keyPair: header.keyPair
        })
        await tx.flush()
      }
    }

    const verifier = header.manifest
      ? new Verifier(header.key, header.manifest, { crypto, legacy })
      : null

    this.storage = storage
    this.header = header
    this.compat = compat
    this.bitfield = bitfield
    this.verifier = verifier
    this.state = new SessionState(this, null, storage, treeInfo, null)

    if (this.key === null) this.key = this.header.key
    if (this.discoveryKey === null) this.discoveryKey = crypto.discoveryKey(this.key)
    if (this.id === null) this.id = z32.encode(this.key)
    if (this.manifest === null) this.manifest = this.header.manifest
  }

  async audit(opts) {
    await this.state.mutex.lock()

    try {
      return await audit(this, opts)
    } finally {
      this.state._unlock()
    }
  }

  async setGroup(group) {
    await this.state.mutex.lock()
    try {
      if (!this.header.group) this.header.group = await this.db.createGroup(group)
      else if (!b4a.equals(this.header.group.key, group)) {
        throw STORAGE_CONFLICT(
          'Core is already register to group: ' + b4a.toString(this.header.group.key, 'hex')
        )
      }

      const tx = this.storage.write()
      tx.setGroup(this.header.group)
      await tx.flush()
    } finally {
      this.state.mutex.unlock()
    }
  }

  async setManifest(manifest) {
    await this.state.mutex.lock()

    try {
      if (manifest && this.header.manifest === null) {
        manifest = Verifier.createManifest(manifest)

        if (!Verifier.isValidManifest(this.header.key, manifest)) {
          throw INVALID_CHECKSUM('Manifest hash does not match', this.discoveryKey)
        }

        const tx = this.state.createWriteBatch()
        this._setManifest(tx, manifest, null)
        await this.state.flush()
      }
    } finally {
      this.state._unlock()
    }
  }

  _setManifest(tx, manifest, keyPair) {
    if (!manifest && b4a.equals(keyPair.publicKey, this.header.key)) {
      manifest = Verifier.defaultSignerManifest(this.header.key)
    }
    if (!manifest) return

    const verifier = new Verifier(this.header.key, manifest, { legacy: this._legacy })

    if (verifier.prologue) this.state.prologue = Object.assign({}, verifier.prologue)

    this.manifest = this.header.manifest = manifest

    tx.setAuth({
      key: this.header.key,
      discoveryKey: this.discoveryKey,
      manifest,
      keyPair: this.header.keyPair
      // TODO: encryptionKey?
    })

    this.compat = verifier.compat
    this.verifier = verifier

    this.replicator.onupgrade()
    this.emitManifest()
  }

  async copyPrologue(src) {
    await this.state.mutex.lock()

    try {
      await src.mutex.lock()
    } catch (err) {
      this.state.mutex.unlock()
      throw err
    }

    try {
      await copyPrologue(src, this)
    } finally {
      src.mutex.unlock()
      this.state.mutex.unlock()
      this.checkIfIdle()
    }
  }

  flushed() {
    return this.state.flushed()
  }

  async _validateCommit(state, treeLength) {
    if (this.state.length > state.length) {
      return false // TODO: partial commit and truncation possible in the future
    }

    if (this.state.length > treeLength) {
      for (const root of this.state.roots) {
        const batchRoot = await MerkleTree.get(state, root.index)
        if (batchRoot.size !== root.size || !b4a.equals(batchRoot.hash, root.hash)) {
          return false
        }
      }
    }

    if (this.verifier === null) {
      return false // easier to assert than upsert
    }

    return true
  }

  _verifyBatchUpgrade(batch, manifest) {
    if (!this.header.manifest) {
      // compat, drop at some point
      if (!manifest) manifest = Verifier.defaultSignerManifest(this.header.key)

      if (
        !manifest ||
        !(
          Verifier.isValidManifest(this.header.key, manifest) ||
          Verifier.isCompat(this.header.key, manifest)
        )
      ) {
        throw INVALID_SIGNATURE('Proof contains an invalid manifest', this.discoveryKey) // TODO: proper error type
      }
    }

    const verifier =
      this.verifier ||
      new Verifier(this.header.key, Verifier.createManifest(manifest), { legacy: this._legacy })

    if (!verifier.verify(batch, batch.signature)) {
      throw INVALID_SIGNATURE('Proof contains an invalid signature', this.discoveryKey)
    }

    return manifest
  }

  async _verifyExclusive({ batch, bitfield, value, manifest }) {
    manifest = this._verifyBatchUpgrade(batch, manifest)

    if (
      !(await this.state._verifyBlock(
        batch,
        bitfield,
        value,
        this.header.manifest ? null : manifest
      ))
    ) {
      return false
    }

    if (!batch.upgraded && bitfield) {
      this.replicator.onhave(bitfield.start, bitfield.length, bitfield.drop)
    }

    return true
  }

  async _verifyShared() {
    if (!this._verifies.length) return false

    await this.state.mutex.lock()

    const tx = this.state.createWriteBatch()

    const verifies = this._verifies
    this._verifies = null
    this._verified = null

    try {
      for (const { batch, bitfield, value } of verifies) {
        if (!batch.commitable()) continue

        if (bitfield) {
          tx.putBlock(bitfield.start, value)
        }
      }

      const bits = new BitInterlude()

      for (let i = 0; i < verifies.length; i++) {
        const { batch, bitfield, manifest } = verifies[i]

        if (!batch.commitable()) {
          verifies[i] = null // signal that we cannot commit this one
          continue
        }

        if (bitfield) {
          bits.setRange(bitfield.start, bitfield.start + 1, true)
        }

        // if we got a manifest AND its strictly a non compat one, lets store it
        if (manifest && this.header.manifest === null) {
          if (!Verifier.isValidManifest(this.header.key, manifest)) {
            throw INVALID_CHECKSUM('Manifest hash does not match', this.discoveryKey)
          }
          this._setManifest(tx, manifest, null)
        }

        if (batch.commitable()) batch.commit(tx)
      }

      const ranges = bits.flush(tx, this.bitfield)

      await this.state.flush()

      for (const { start, end, value } of ranges) {
        this._setBitfieldRanges(start, end, value)
      }

      for (let i = 0; i < verifies.length; i++) {
        const bitfield = verifies[i] && verifies[i].bitfield
        if (bitfield) {
          this.updateContiguousLength(bitfield)
          this.replicator.onhave(bitfield.start, bitfield.length, bitfield.drop)
        }
      }

      if (this.hintsChanged) await this.flushHints()
    } finally {
      this.state._clearActiveBatch()
      this.state.mutex.unlock()
    }

    return verifies[0] !== null
  }

  async flushHints() {
    if (!this.hintsChanged) return
    this.hintsChanged = false // we unset this immediately as a "debounce"

    const tx = this.state.storage.write()

    tx.setHints({
      contiguousLength: this.header.hints.contiguousLength,
      remoteContiguousLength: this.header.hints.remoteContiguousLength,
      recovering: this.header.hints.recovering
    })

    await tx.flush()
  }

  async checkConflict(proof, from) {
    if (this.state.length < proof.upgrade.length || proof.fork !== this.state.fork) {
      // out of date this proof - ignore for now
      return false
    }

    // sanity check -> no manifest, no way to verify
    if (!this.header.manifest) {
      return false
    }

    const batch = MerkleTree.verifyFullyRemote(this.state, proof)

    try {
      this._verifyBatchUpgrade(batch, proof.manifest)
    } catch {
      return true
    }

    const roots = await maybeGetRootsFromStorage(this.storage, proof.upgrade.length)
    const remoteTreeHash = crypto.tree(proof.upgrade.nodes)
    const localTreeHash = crypto.tree(roots)

    try {
      const rx = this.state.storage.read()
      const treeProofPromise = MerkleTree.proof(this.state, rx, {
        block: null,
        hash: null,
        seek: null,
        upgrade: {
          start: 0,
          length: proof.upgrade.length
        }
      })

      rx.tryFlush()

      const treeProof = await treeProofPromise

      const verifyBatch = MerkleTree.verifyFullyRemote(this.state, await treeProof.settle())
      this._verifyBatchUpgrade(verifyBatch, this.header.manifest)
    } catch (err) {
      if (err.code !== 'INVALID_OPERATION') return true

      return !(await this._repairTreeNodes(proof))
    }

    // both proofs are valid, now check if they forked
    if (b4a.equals(localTreeHash, remoteTreeHash)) return false

    await this.state.mutex.lock()

    try {
      const tx = this.state.createWriteBatch()

      this.header.frozen = true

      tx.setAuth({
        key: this.header.key,
        discoveryKey: this.discoveryKey,
        manifest: this.header.manifest,
        keyPair: this.header.keyPair,
        frozen: true
      })

      await this.state.flush()
    } finally {
      this.state.mutex.unlock()
    }

    // tmp log so we can see these
    const id = b4a.toString(this.discoveryKey, 'hex')
    console.log(
      '[hypercore] conflict detected in ' +
        id +
        ' (writable=' +
        !!this.header.keyPair +
        ',quorum=' +
        this.header.manifest.quorum +
        ')'
    )
    await this._onconflict(proof)
    return true
  }

  async _repairTreeNodes(proof) {
    if (!this._repairMode) return false

    await this.state.mutex.lock()

    for (let i = this.monitors.length - 1; i >= 0; i--) {
      this.monitors[i].emit('repairing', proof)
    }

    try {
      const batch = MerkleTree.verifyFullyRemote(this.state, proof)
      this._verifyBatchUpgrade(batch, proof.manifest)
    } catch {
      this.state.mutex.unlock()
      for (let i = this.monitors.length - 1; i >= 0; i--) {
        this.monitors[i].emit('repair-failed')
      }
      return false
    }

    // Attempt to repair local merkle tree with remote roots
    const atom = this.storage.createAtom()
    const atomized = this.storage.atomize(atom)
    const tx = atomized.write()
    for (const node of proof.upgrade.nodes) {
      tx.putTreeNode(node)
    }
    await tx.flush()

    // Try local proof again
    try {
      const rx = atomized.read()
      const treeProofPromise = MerkleTree.proof(this.state, rx, {
        block: null,
        hash: null,
        seek: null,
        upgrade: {
          start: 0,
          length: proof.upgrade.length
        }
      })

      rx.tryFlush()

      const treeProof = await treeProofPromise

      const verifyBatch = MerkleTree.verifyFullyRemote(this.state, await treeProof.settle())
      this._verifyBatchUpgrade(verifyBatch, this.header.manifest)
    } catch (err) {
      this.state.mutex.unlock()
      for (let i = this.monitors.length - 1; i >= 0; i--) {
        this.monitors[i].emit('repair-failed')
      }
      return false
    }

    await atom.flush()

    for (let i = this.monitors.length - 1; i >= 0; i--) {
      this.monitors[i].emit('repaired', proof)
    }
    this.replicator.setPushOnly(false)
    this.state.mutex.unlock()
    return true
  }

  async verifyReorg(proof) {
    const batch = new ReorgBatch(this.state)
    await MerkleTree.reorg(this.state, proof, batch)
    const manifest = this._verifyBatchUpgrade(batch, proof.manifest)

    if (manifest && !this.header.manifest) {
      await this.state.mutex.lock()
      try {
        if (manifest && this.header.manifest === null) {
          const tx = this.state.createWriteBatch()
          this._setManifest(tx, Verifier.createManifest(manifest), null)
          await this.state.flush()
        }
      } finally {
        this.state._unlock()
      }
    }

    return batch
  }

  async verify(proof, from) {
    // We cannot apply "other forks" atm.
    // We should probably still try and they are likely super similar for non upgrades
    // but this is easy atm (and the above layer will just retry)
    if (proof.fork !== this.state.fork) return false

    const batch = await MerkleTree.verify(this.state, proof)

    if (batch.upgraded && batch.length <= this.state.length) {
      await batch.downgrade()
    }

    if (!batch.commitable()) {
      return false
    }

    const value = (proof.block && proof.block.value) || null
    const op = {
      batch,
      bitfield: value && { drop: false, start: proof.block.index, length: 1 },
      value,
      manifest: proof.manifest,
      from
    }

    if (batch.upgraded) {
      return this._verifyExclusive(op)
    }

    if (this._verifies !== null) {
      const verifies = this._verifies
      const i = verifies.push(op)
      await this._verified
      return verifies[i] !== null
    }

    this._verifies = [op]
    this._verified = this._verifyShared()

    return this._verified
  }

  async reorg(batch) {
    if (!batch.commitable()) return false

    this.truncating++

    try {
      await this.state.reorg(batch)
    } finally {
      this.truncating--
    }

    return true
  }

  openSkipBitfield() {
    if (this.skipBitfield !== null) return this.skipBitfield
    this.skipBitfield = new RemoteBitfield()
    const buf = this.bitfield.toBuffer(this.state.length)
    const bitfield = new Uint32Array(buf.buffer, buf.byteOffset, buf.byteLength / 4)
    this.skipBitfield.insert(0, bitfield)
    return this.skipBitfield
  }

  _setBitfieldRanges(start, end, value) {
    this.bitfield.setRange(start, end, value)
    if (this.skipBitfield !== null) this.skipBitfield.setRange(start, end, value)
  }

  close() {
    if (!this.closing) this.closing = this._close()
    return this.closing
  }

  updateContiguousLength(bitfield) {
    const contig = updateContigBatch(this.header.hints.contiguousLength, bitfield, this.bitfield)

    if (contig.length !== -1 && contig.length !== this.header.hints.contiguousLength) {
      this.header.hints.contiguousLength = contig.length
      this.hintsChanged = true
    }
  }

  updateRemoteContiguousLength(length) {
    this.header.hints.remoteContiguousLength = length
    this.hintsChanged = true
    this.remoteContiguousLengthChanged = true
  }

  onappend(tree, bitfield) {
    this.header.tree = tree

    if (!bitfield) {
      this.replicator.onupgrade()
      return
    }

    this.replicator.cork()

    const { start, length, drop } = bitfield

    this._setBitfieldRanges(start, start + length, true)
    this.updateContiguousLength({ start, length, drop: false })

    this.replicator.onupgrade()
    this.replicator.onhave(start, length, drop)
    this.replicator.uncork()
  }

  ontruncate(tree, { start, length }) {
    if (tree) this.header.tree = tree

    this.replicator.cork()

    this.replicator.ontruncate(start, length)
    this.replicator.onhave(start, length, true)
    this.replicator.onupgrade()
    this.replicator.uncork()

    for (const sessionState of this.sessionStates) {
      if (start < sessionState.snapshotCompatLength) sessionState.snapshotCompatLength = start
    }

    this._setBitfieldRanges(start, start + length, false)
    this.updateContiguousLength({ start, length, drop: true })
  }

  async _onconflict(proof) {
    await this.replicator.onconflict()

    for (let i = this.monitors.length - 1; i >= 0; i--) {
      const s = this.monitors[i]
      s.emit('conflict', proof.upgrade.length, proof.fork, proof)
    }

    const err = new Error('Two conflicting signatures exist for length ' + proof.upgrade.length)
    await this.closeAllSessions(err)
  }

  async closeAllSessions(err) {
    // this.sessions modifies itself when a session closes
    // This way we ensure we indeed iterate over all sessions
    const sessions = this.allSessions()

    const all = []
    for (const s of sessions) all.push(s.close({ error: err, force: false })) // force false or else infinite recursion
    await Promise.allSettled(all)
  }

  async destroy() {
    if (this.destroyed === true) return
    this.destroyed = true

    if (this.hasSession() === true) throw new Error('Cannot destroy while sessions are open')

    const weakSessions = this.allSessions()

    if (this.replicator) this.replicator.destroy()
    if (this.state) await this.state.close()

    // close all pending weak sessions...
    for (const s of weakSessions) s.close().catch(noop)
  }

  async _close() {
    if (this.opened === false) await this.opening
    if (this.hasSession() === true) throw new Error('Cannot close while sessions are open')

    if (this.replicator) await this.replicator.close()

    await this.destroy()
    if (this.autoClose) await this.storage.store.close()

    this.closed = true
  }
}

function updateContigBatch(start, upd, bitfield) {
  const end = upd.start + upd.length

  let c = start

  if (upd.drop) {
    // If we dropped a block in the current contig range, "downgrade" it
    if (c > upd.start) {
      c = upd.start
    }
  } else {
    if (c <= end && c >= upd.start) {
      c = end
      while (bitfield.get(c)) c++
    }
  }

  if (c === start) {
    return {
      length: -1
    }
  }

  if (c > start) {
    return {
      length: c
    }
  }

  return {
    length: c
  }
}

function getDefaultTree() {
  return {
    fork: 0,
    length: 0,
    rootHash: null,
    signature: null,
    timestamp: 0
  }
}

function parseHeader(info) {
  if (!info) return null

  return {
    key: info.key,
    manifest: info.manifest,
    external: null,
    keyPair: info.keyPair,
    tree: info.head || getDefaultTree(),
    group: info.group,
    hints: {
      reorgs: [],
      contiguousLength: info.hints ? info.hints.contiguousLength : 0,
      remoteContiguousLength: info.hints ? info.hints.remoteContiguousLength : 0,
      recovering: info.hints ? info.hints.recovering : 0
    }
  }
}

async function maybeGetRootsFromStorage(storage, length) {
  try {
    return length ? await MerkleTree.getRootsFromStorage(storage, length) : []
  } catch (err) {
    if (err.code !== 'INVALID_OPERATION') throw err
    // If INVALID_OPERATION, a root is likely missing so load empty roots to try to recover
    return []
  }
}

function noop() {}

async function getCoreInfo(storage) {
  const r = storage.read()

  const auth = r.getAuth()
  const head = r.getHead()
  const hints = r.getHints()
  const group = r.getGroup()

  r.tryFlush()

  const [authInfo, headInfo, hintsInfo, groupInfo] = await Promise.all([auth, head, hints, group])
  return {
    ...authInfo,
    head: headInfo,
    hints: hintsInfo,
    group: groupInfo
  }
}
const sodium = require('sodium-universal')
const c = require('compact-encoding')
const b4a = require('b4a')
const { DEFAULT_ENCRYPTION } = require('./caps')

const nonce = b4a.alloc(sodium.crypto_stream_NONCEBYTES)

module.exports = class DefaultEncryption {
  static PADDING = 8

  constructor(encryptionKey, hypercoreKey, opts = {}) {
    this.key = encryptionKey
    this.compat = opts.compat === true

    const keys = DefaultEncryption.deriveKeys(encryptionKey, hypercoreKey, opts)

    this.blockKey = keys.block
    this.blindingKey = keys.blinding
  }

  static deriveKeys(encryptionKey, hypercoreKey, { block = false, compat = false } = {}) {
    const subKeys = b4a.alloc(2 * sodium.crypto_stream_KEYBYTES)

    const blockKey = block ? encryptionKey : subKeys.subarray(0, sodium.crypto_stream_KEYBYTES)
    const blindingKey = subKeys.subarray(sodium.crypto_stream_KEYBYTES)

    if (!block) {
      if (compat) {
        sodium.crypto_generichash_batch(blockKey, [encryptionKey], hypercoreKey)
      } else {
        sodium.crypto_generichash_batch(blockKey, [DEFAULT_ENCRYPTION, hypercoreKey, encryptionKey])
      }
    }

    sodium.crypto_generichash(blindingKey, blockKey)

    return {
      blinding: blindingKey,
      block: blockKey
    }
  }

  static blockEncryptionKey(hypercoreKey, encryptionKey) {
    const blockKey = b4a.alloc(sodium.crypto_stream_KEYBYTES)
    sodium.crypto_generichash_batch(blockKey, [DEFAULT_ENCRYPTION, hypercoreKey, encryptionKey])
    return blockKey
  }

  static encrypt(index, block, fork, blockKey, blindingKey) {
    const padding = block.subarray(0, DefaultEncryption.PADDING)
    block = block.subarray(DefaultEncryption.PADDING)

    c.uint64.encode({ start: 0, end: 8, buffer: padding }, fork)
    c.uint64.encode({ start: 0, end: 8, buffer: nonce }, index)

    // Zero out any previous padding.
    nonce.fill(0, 8, 8 + padding.byteLength)

    // Blind the fork ID, possibly risking reusing the nonce on a reorg of the
    // Hypercore. This is fine as the blinding is best-effort and the latest
    // fork ID shared on replication anyway.
    sodium.crypto_stream_xor(padding, padding, nonce, blindingKey)

    nonce.set(padding, 8)

    // The combination of a (blinded) fork ID and a block index is unique for a
    // given Hypercore and is therefore a valid nonce for encrypting the block.
    sodium.crypto_stream_xor(block, block, nonce, blockKey)
  }

  static decrypt(index, block, blockKey) {
    const padding = block.subarray(0, DefaultEncryption.PADDING)
    block = block.subarray(DefaultEncryption.PADDING)

    c.uint64.encode({ start: 0, end: 8, buffer: nonce }, index)

    nonce.set(padding, 8)

    // Decrypt the block using the blinded fork ID.
    sodium.crypto_stream_xor(block, block, nonce, blockKey)
  }

  encrypt(index, block, fork, core) {
    if (core.compat !== this.compat) this._reload(core)
    return DefaultEncryption.encrypt(index, block, fork, this.blockKey, this.blindingKey)
  }

  decrypt(index, block, core) {
    if (core.compat !== this.compat) this._reload(core)
    return DefaultEncryption.decrypt(index, block, this.blockKey)
  }

  padding() {
    return DefaultEncryption.PADDING
  }

  _reload(core) {
    const block = b4a.equals(this.key, this.blockKey)
    const keys = DefaultEncryption.deriveKeys(this.key, core.key, { block, compat: core.compat })

    this.blockKey = keys.blockKey
    this.blindingKey = keys.blindingKey
  }
}
module.exports = class Download {
  constructor(session, range) {
    this.session = session
    this.range = range
    this.request = null
    this.opened = false
    this.opening = this._open()
    this.opening.catch(noop)
  }

  ready() {
    return this.opening
  }

  async _open() {
    if (this.session.opened === false) await this.session.opening
    this._download()
    this.opened = true
  }

  async done() {
    await this.ready()

    try {
      return await this.request.promise
    } catch (err) {
      if (isSessionMoved(err)) return this._download()
      throw err
    }
  }

  _download() {
    const activeRequests = (this.range && this.range.activeRequests) || this.session.activeRequests
    this.request = this.session.core.replicator.addRange(activeRequests, this.range)
    this.request.promise.catch(noop)
    return this.request.promise
  }

  /**
   * Deprecated. Use `range.done()`.
   */
  downloaded() {
    return this.done()
  }

  destroy() {
    this._destroyBackground().catch(noop)
  }

  async _destroyBackground() {
    if (this.opened === false) await this.ready()
    if (this.request.context) this.request.context.detach(this.request)
  }
}

function noop() {}

function isSessionMoved(err) {
  return err.code === 'SESSION_MOVED'
}
module.exports = {
  SMALL_WANTS: 1
}
// this helper is for fully remote proofs, is like in a push notification where no other context exists

const { MerkleTree } = require('./merkle-tree.js')
const messages = require('./messages.js')
const b4a = require('b4a')
const c = require('compact-encoding')
const crypto = require('hypercore-crypto')
const flat = require('flat-tree')

class SlimSession {
  constructor(storage, auth, head, roots) {
    this.fork = head ? head.fork : 0
    this.roots = roots
    this.length = head ? head.length : 0
    this.signature = head ? head.signature : null
    this.ancestors = this.length
    this.byteLength = 0
    this.prologue = auth.manifest.prologue
    this.storage = storage

    for (let i = 0; i < roots.length; i++) this.byteLength += roots[i].size
  }
}

module.exports = { verify, proof }

async function verify(storage, buffer, { referrer = null } = {}) {
  const state = { buffer, start: 0, end: buffer.byteLength }

  const discoveryKey = c.fixed32.decode(state)
  const proof = messages.wire.data.decode(state)

  const result = {
    key: null,
    discoveryKey,
    newer: true,
    length: 0,
    proof,
    block: null
  }

  const core = await storage.resumeCore(discoveryKey)
  if (core === null) return null

  let rx = core.read()
  const authPromise = rx.getAuth()
  const headPromise = rx.getHead()
  const referrerPromise = rx.getUserData('referrer')

  rx.tryFlush()

  const [auth, head, ref] = await Promise.all([authPromise, headPromise, referrerPromise])

  if (auth === null) return null

  if (referrer && (!ref || !b4a.equals(ref, referrer))) return null

  rx = core.read()

  const rootPromises = []

  for (const index of flat.fullRoots(head ? 2 * head.length : 0)) {
    rootPromises.push(rx.getTreeNode(index))
  }

  rx.tryFlush()

  let roots = await Promise.all(rootPromises)
  if (roots.some(isNull)) roots = []

  const length = head ? head.length : 0

  if (!auth.manifest || !auth.manifest.signers.length) return null

  const batch = await MerkleTree.verifyFullyRemote(new SlimSession(core, auth, head, roots), proof)
  const publicKey = auth.manifest.signers[0].publicKey

  let signable = null
  let signature = null

  if (auth.manifest.version === 0) {
    signable = batch.signable(auth.manifest.signers[0].namespace)
    signature = batch.signature
  } else {
    if (batch.signature[0] !== 1) return null
    if (batch.signature[1] !== 0) return null

    signable = batch.signable(auth.key)
    signature = batch.signature.subarray(2, 66)
  }

  if (!crypto.verify(signable, signature, publicKey)) {
    return null
  }

  result.key = auth.key
  result.discoveryKey = discoveryKey
  result.newer = batch.length > length
  result.length = batch.length
  result.block = proof.block

  return result
}

async function proof(sender, { index, block = null, upgrade = null } = {}) {
  const proof = await sender.proof({
    block: block ? { index, nodes: 0 } : null,
    upgrade: upgrade ? upgrade : { start: 0, length: sender.length }
  })

  if (block) proof.block.value = block
  proof.manifest = sender.core.header.manifest

  const state = { buffer: null, start: 0, end: 0 }
  const data = { request: 0, ...proof }

  c.fixed32.preencode(state, sender.discoveryKey)
  messages.wire.data.preencode(state, data)

  state.buffer = b4a.allocUnsafe(state.end)

  c.fixed32.encode(state, sender.discoveryKey)
  messages.wire.data.encode(state, data)

  return state.buffer
}

function isNull(x) {
  return x === null
}
const TICKS = 16

module.exports = class HotswapQueue {
  constructor() {
    this.priorities = [[], [], []]
  }

  *pick(peer) {
    for (let i = 0; i < this.priorities.length; i++) {
      // try first one more than second one etc etc
      let ticks = (this.priorities.length - i) * TICKS
      const queue = this.priorities[i]

      for (let j = 0; j < queue.length; j++) {
        const r = j + Math.floor(Math.random() * queue.length - j)
        const a = queue[j]
        const b = queue[r]

        if (r !== j) {
          queue[(b.hotswap.index = j)] = b
          queue[(a.hotswap.index = r)] = a
        }

        if (hasInflight(b, peer)) continue

        yield b

        if (--ticks <= 0) break
      }
    }
  }

  add(block) {
    if (block.hotswap !== null) this.remove(block)
    if (block.inflight.length === 0 || block.inflight.length >= 3) return

    // TODO: also use other stuff to determine queue prio
    const queue = this.priorities[block.inflight.length - 1]

    const index = queue.push(block) - 1
    block.hotswap = { ref: this, queue, index }
  }

  remove(block) {
    const hotswap = block.hotswap
    if (hotswap === null) return

    block.hotswap = null
    const head = hotswap.queue.pop()
    if (head === block) return
    hotswap.queue[(head.hotswap.index = hotswap.index)] = head
  }
}

function hasInflight(block, peer) {
  for (let j = 0; j < block.inflight.length; j++) {
    if (block.inflight[j].peer === peer) return true
  }
  return false
}
module.exports = class Info {
  constructor(opts = {}) {
    this.key = opts.key
    this.discoveryKey = opts.discoveryKey
    this.length = opts.length || 0
    this.contiguousLength = opts.contiguousLength || 0
    this.byteLength = opts.byteLength || 0
    this.fork = opts.fork || 0
    this.padding = opts.padding || 0
    this.storage = opts.storage || null
  }

  static async from(session, opts = {}) {
    return new Info({
      key: session.key,
      discoveryKey: session.discoveryKey,
      length: session.length,
      contiguousLength: session.contiguousLength,
      byteLength: session.byteLength,
      fork: session.fork,
      padding: session.padding,
      storage: opts.storage ? await this.storage(session) : null
    })
  }

  static async storage(session) {
    const { oplog, tree, blocks, bitfield } = session.core
    try {
      return {
        oplog: await Info.bytesUsed(oplog.storage),
        tree: await Info.bytesUsed(tree.storage),
        blocks: await Info.bytesUsed(blocks.storage),
        bitfield: await Info.bytesUsed(bitfield.storage)
      }
    } catch {
      return null
    }
  }

  static bytesUsed(file) {
    return new Promise((resolve, reject) => {
      file.stat((err, st) => {
        if (err) {
          resolve(0) // prob just file not found (TODO, improve)
        } else if (typeof st.blocks !== 'number') {
          reject(new Error('cannot determine bytes used'))
        } else {
          resolve(st.blocks * 512)
        }
      })
    })
  }
}
const b4a = require('b4a')

module.exports = function (core, depth, opts) {
  let indent = ''
  if (typeof opts.indentationLvl === 'number') {
    while (indent.length < opts.indentationLvl) indent += ' '
  }

  let peers = ''
  const min = Math.min(core.peers.length, 5)

  for (let i = 0; i < min; i++) {
    const peer = core.peers[i]

    peers += `${indent}    Peer(\n`
    peers += `${indent}      remotePublicKey: ${opts.stylize(toHex(peer.remotePublicKey), 'string')}\n`
    peers += `${indent}      remoteLength: ${opts.stylize(peer.remoteLength, 'number')}\n`
    peers += `${indent}      remoteFork: ${opts.stylize(peer.remoteFork, 'number')}\n`
    peers += `${indent}      remoteCanUpgrade: ${opts.stylize(peer.remoteCanUpgrade, 'boolean')}\n`
    peers += `${indent}    )\n`
  }

  if (core.peers.length > 5) {
    peers += `${indent}  ... and ${core.peers.length - 5} more\n`
  }

  if (peers) peers = `[\n${peers}${indent}  ]`
  else peers = `[ ${opts.stylize(0, 'number')} ]`

  return (
    `${core.constructor.name}(\n` +
    `${indent}  id: ${opts.stylize(core.id, 'string')}\n` +
    `${indent}  key: ${opts.stylize(toHex(core.key), 'string')}\n` +
    `${indent}  discoveryKey: ${opts.stylize(toHex(core.discoveryKey), 'string')}\n` +
    `${indent}  opened: ${opts.stylize(core.opened, 'boolean')}\n` +
    `${indent}  closed: ${opts.stylize(core.closed, 'boolean')}\n` +
    `${indent}  snapshotted: ${opts.stylize(core.snapshotted, 'boolean')}\n` +
    `${indent}  writable: ${opts.stylize(core.writable, 'boolean')}\n` +
    `${indent}  length: ${opts.stylize(core.length, 'number')}\n` +
    `${indent}  fork: ${opts.stylize(core.fork, 'number')}\n` +
    `${indent}  sessions: [ ${opts.stylize(core.sessions.length, 'number')} ]\n` +
    `${indent}  activeRequests: [ ${opts.stylize(core.activeRequests.length, 'number')} ]\n` +
    `${indent}  peers: ${peers}\n` +
    `${indent})`
  )
}

function toHex(buf) {
  return buf && b4a.toString(buf, 'hex')
}
const BigSparseArray = require('big-sparse-array')
const { Transform } = require('streamx')
const quickbit = require('./compat').quickbit
const Mutex = require('./mutex')
const b4a = require('b4a')

const BITS_PER_PAGE = 32768
const BYTES_PER_PAGE = BITS_PER_PAGE / 8

class MarkPage {
  constructor() {
    this.bitfield = null
    this.loaded = new Promise((resolve) => {
      this.load = resolve
    })
  }

  setBitfield(bitfield) {
    this.bitfield = bitfield
    this.load()
  }

  get(index) {
    return quickbit.get(this.bitfield, index)
  }

  set(index, val) {
    quickbit.set(this.bitfield, index, val)
  }
}

module.exports = class MarkBitfield {
  static BITS_PER_PAGE = BITS_PER_PAGE
  static BYTES_PER_PAGE = BYTES_PER_PAGE

  constructor(storage) {
    this.storage = storage
    this.mutex = new Mutex()
    this._pages = new BigSparseArray()
  }

  async loadPage(pageIndex) {
    const p = this._pages.set(pageIndex, new MarkPage())
    const rx = this.storage.read()
    const pageBuf = rx.getMark(pageIndex)
    rx.tryFlush()
    const bitfield = (await pageBuf) ?? b4a.alloc(BYTES_PER_PAGE)
    await p.setBitfield(bitfield)
    return p
  }

  async get(index) {
    const j = index & (BITS_PER_PAGE - 1)
    const i = (index - j) / BITS_PER_PAGE

    let p = this._pages.get(i)
    if (!p) p = await this.loadPage(i)

    return p.get(j)
  }

  async set(index, val) {
    const j = index & (BITS_PER_PAGE - 1)
    const i = (index - j) / BITS_PER_PAGE

    let p = this._pages.get(i)

    if (!p && val) p = await this.loadPage(i)

    if (p) {
      await p.loaded
      p.set(j, val)
      await this.mutex.lock()
      const tx = this.storage.write()
      tx.putMark(i, p.bitfield)
      await tx.flush()
      this.mutex.unlock()
    }
  }

  async clear() {
    const tx = this.storage.write()
    tx.deleteMarkRange(0, -1)
    await tx.flush()
    this._pages = new BigSparseArray()
  }

  createMarkStream({ reverse = false } = {}) {
    return this.storage.createMarkStream({ reverse }).pipe(
      new Transform({
        transform({ index, page }, cb) {
          let bitIndex = reverse
            ? quickbit.findLast(page, true, BITS_PER_PAGE)
            : quickbit.findFirst(page, true, 0)
          while (bitIndex !== -1) {
            const blockIndex = index * BITS_PER_PAGE + bitIndex
            this.push(blockIndex)

            // Account for `bitIndex` being either causing infinite loop
            if (bitIndex === 0 && reverse) break

            bitIndex = reverse
              ? quickbit.findLast(page, true, bitIndex - 1)
              : quickbit.findFirst(page, true, bitIndex + 1)
          }

          cb(null)
        }
      })
    )
  }
}
const flat = require('flat-tree')
const crypto = require('hypercore-crypto')
const b4a = require('b4a')
const unslab = require('unslab')
const caps = require('./caps')
const {
  INVALID_PROOF,
  INVALID_CHECKSUM,
  INVALID_OPERATION,
  BAD_ARGUMENT,
  ASSERTION
} = require('hypercore-errors')

class NodeQueue {
  constructor(nodes, extra = null) {
    this.i = 0
    this.nodes = nodes
    this.extra = extra
    this.length = nodes.length + (this.extra === null ? 0 : 1)
  }

  shift(index) {
    if (this.extra !== null && this.extra.index === index) {
      const node = this.extra
      this.extra = null
      this.length--
      return node
    }

    if (this.i >= this.nodes.length) {
      throw INVALID_OPERATION('Expected node ' + index + ', got (nil)')
    }

    const node = this.nodes[this.i++]
    if (node.index !== index) {
      throw INVALID_OPERATION('Expected node ' + index + ', got node ' + node.index)
    }

    this.length--
    return node
  }
}

class MerkleTreeBatch {
  constructor(session) {
    this.fork = session.fork
    this.roots = [...session.roots]
    this.length = session.length
    this.signature = session.signature
    this.ancestors = session.length
    this.byteLength = session.byteLength
    this.prologue = session.prologue
    this.hashCached = null

    this.committed = false
    this.truncated = false
    this.treeLength = session.length
    this.treeFork = session.fork
    this.storage = session.storage
    this.session = session
    this.nodes = []
    this.upgraded = false
  }

  checkout(length, additionalRoots) {
    const roots = []
    let r = 0

    const head = 2 * length - 2
    const gaps = new Set()
    const all = new Map()

    // additional roots is so the original roots can be passed (we mutate the array in appendRoot)
    if (additionalRoots) {
      for (const node of additionalRoots) all.set(node.index, node)
    }

    for (const node of this.nodes) all.set(node.index, node)

    for (const index of flat.fullRoots(head + 2)) {
      const left = flat.leftSpan(index)
      if (left !== 0) gaps.add(left - 1)

      if (r < this.roots.length && this.roots[r].index === index) {
        roots.push(this.roots[r++])
        continue
      }
      const node = all.get(index)
      if (!node) throw new BAD_ARGUMENT('root missing for given length')
      roots.push(node)
    }

    this.roots = roots
    this.length = length
    this.byteLength = totalSize(roots)
    this.hashCached = null
    this.signature = null

    for (let i = 0; i < this.nodes.length; i++) {
      const index = this.nodes[i].index
      if (index <= head && !gaps.has(index)) continue
      const last = this.nodes.pop()
      if (i < this.nodes.length) this.nodes[i--] = last
    }
  }

  prune(length) {
    if (length === 0) return

    const head = 2 * length - 2
    const gaps = new Set()

    // TODO: make a function for this in flat-tree
    for (const index of flat.fullRoots(head + 2)) {
      const left = flat.leftSpan(index)
      if (left !== 0) gaps.add(left - 1)
    }

    for (let i = 0; i < this.nodes.length; i++) {
      const index = this.nodes[i].index
      if (index > head || gaps.has(index)) continue
      const last = this.nodes.pop()
      if (i < this.nodes.length) this.nodes[i--] = last
    }
  }

  clone() {
    const b = new MerkleTreeBatch(this.session)

    b.fork = this.fork
    b.roots = [...this.roots]
    b.length = this.length
    b.byteLength = this.byteLength
    b.signature = this.signature
    b.treeLength = this.treeLength
    b.treeFork = this.treeFork
    b.tree = this.tree
    b.nodes = [...this.nodes]
    b.upgraded = this.upgraded

    return b
  }

  hash() {
    if (this.hashCached === null) this.hashCached = unslab(crypto.tree(this.roots))
    return this.hashCached
  }

  signable(manifestHash) {
    return caps.treeSignable(manifestHash, this.hash(), this.length, this.fork)
  }

  signableCompat(noHeader) {
    return caps.treeSignableCompat(this.hash(), this.length, this.fork, noHeader)
  }

  get(index) {
    if (index >= this.length * 2) {
      return null
    }

    for (const n of this.nodes) {
      if (n.index === index) return n
    }

    return getTreeNodeFromStorage(this.session.storage, index)
  }

  // deprecated, use sssion proof instead
  proof(batch, { block, hash, seek, upgrade }) {
    return generateProof(this.session, batch, block, hash, seek, upgrade)
  }

  verifyUpgrade(proof) {
    const unverified = verifyTree(proof, this.nodes)

    if (!proof.upgrade) throw INVALID_OPERATION('Expected upgrade proof')

    return verifyUpgrade(proof, unverified, this)
  }

  addNodesUnsafe(nodes) {
    for (let i = 0; i < nodes.length; i++) {
      this.nodes.push(nodes[i])
    }
  }

  append(buf) {
    const head = this.length * 2
    const ite = flat.iterator(head)
    const node = blockNode(head, buf)

    this.appendRoot(node, ite)
  }

  appendRoot(node, ite) {
    node = unslabNode(node)
    this.hashCached = null
    this.upgraded = true
    this.length += ite.factor / 2
    this.byteLength += node.size
    this.roots.push(node)
    this.nodes.push(node)

    while (this.roots.length > 1) {
      const a = this.roots[this.roots.length - 1]
      const b = this.roots[this.roots.length - 2]

      // TODO: just have a peek sibling instead? (pretty sure it's always the left sib as well)
      if (ite.sibling() !== b.index) {
        ite.sibling() // unset so it always points to last root
        break
      }

      const node = unslabNode(parentNode(ite.parent(), a, b))
      this.nodes.push(node)
      this.roots.pop()
      this.roots.pop()
      this.roots.push(node)
    }
  }

  commitable() {
    return (
      this.treeFork === this.session.fork &&
      (this.upgraded
        ? this.treeLength === this.session.length
        : this.treeLength <= this.session.length)
    )
  }

  commit(tx) {
    if (tx === undefined) throw INVALID_OPERATION('No database batch was passed')

    if (this.session.core._repairMode) {
      throw ASSERTION('Cannot commit while repair mode is on')
    }

    if (!this.commitable()) {
      throw INVALID_OPERATION('Tree was modified during batch, refusing to commit')
    }

    if (this.upgraded) this._commitUpgrade(tx)

    for (let i = 0; i < this.nodes.length; i++) {
      const node = this.nodes[i]
      tx.putTreeNode(node)
    }

    this.committed = true

    return this
  }

  _commitUpgrade(tx) {
    // TODO: If easy to detect, we should refuse an trunc+append here without a fork id
    // change. Will only happen on user error so mostly to prevent that.

    if (this.ancestors < this.treeLength) {
      tx.deleteTreeNodeRange(this.ancestors * 2, this.treeLength * 2)

      if (this.ancestors > 0) {
        const head = this.ancestors * 2
        const ite = flat.iterator(head - 2)

        while (true) {
          if (ite.contains(head) && ite.index < head) {
            tx.deleteTreeNode(ite.index)
          }
          if (ite.offset === 0) break
          ite.parent()
        }

        this.truncated = true
      }
    }
  }

  seek(bytes, padding) {
    return new ByteSeeker(this, this, bytes, padding)
  }

  byteRange(index) {
    const rx = this.storage.read()
    const range = getByteRange(this, index, rx)
    rx.tryFlush()

    return range
  }

  byteOffset(index) {
    if (index === 2 * this.length) return this.byteLength

    const rx = this.storage.read()
    const offset = getByteOffset(this, index, rx)
    rx.tryFlush()

    return offset
  }

  async downgrade() {
    if (!this.upgraded) return true

    const rx = this.storage.read()
    const nodePromises = []

    for (const r of this.roots) {
      nodePromises.push(rx.getTreeNode(r.index))
    }

    rx.tryFlush()

    const nodes = await Promise.all(nodePromises)

    for (let i = 0; i < this.roots.length; i++) {
      const root = this.roots[i]
      const node = nodes[i]

      if (!node) return false
      if (!b4a.equals(node.hash, root.hash)) return false
    }

    this.upgraded = false
    return true
  }

  async restore(length) {
    if (length === this.length) return this

    const roots = unslabNodes(await MerkleTree.getRootsFromStorage(this.storage, length))

    this.roots = roots
    this.length = length
    this.byteLength = totalSize(roots)
    this.ancestors = length

    for (const node of roots) this.byteLength += node.size

    return this
  }
}

class ReorgBatch extends MerkleTreeBatch {
  constructor(session) {
    super(session)

    this.roots = []
    this.length = 0
    this.byteLength = 0
    this.diff = null
    this.ancestors = 0
    // We set upgraded because reorgs are signed so hit will
    // hit the same code paths (like the treeLength check in commit)
    this.upgraded = true
    this.want = {
      nodes: 0,
      start: 0,
      end: 0
    }
  }

  get finished() {
    return this.want === null
  }

  update(proof) {
    if (this.want === null) return true

    const nodes = []
    const root = verifyTree(proof, nodes)

    if (root === null || !b4a.equals(root.hash, this.diff.hash)) return false

    this.nodes.push(...nodes)
    return this._update(nodes)
  }

  async _update(nodes) {
    const n = new Map()
    for (const node of nodes) n.set(node.index, node)

    let diff = null
    const ite = flat.iterator(this.diff.index)
    const startingDiff = this.diff

    while ((ite.index & 1) !== 0) {
      const left = n.get(ite.leftChild())
      if (!left) break

      const existing = await getTreeNodeFromStorage(this.session.storage, left.index)
      if (!existing || !b4a.equals(existing.hash, left.hash)) {
        diff = left
      } else {
        diff = n.get(ite.sibling())
      }
    }

    if ((this.diff.index & 1) === 0) return true
    if (diff === null) return false
    if (startingDiff !== this.diff) return false

    return this._updateDiffRoot(diff)
  }

  _updateDiffRoot(diff) {
    if (this.want === null) return true

    const spans = flat.spans(diff.index)
    const start = spans[0] / 2
    const end = Math.min(this.treeLength, spans[1] / 2 + 1)
    const len = end - start

    this.ancestors = start
    this.diff = diff

    if ((diff.index & 1) === 0 || this.want.start >= this.treeLength || len <= 0) {
      this.want = null
      return true
    }

    this.want.start = start
    this.want.end = end
    this.want.nodes = log2(spans[1] - spans[0] + 2) - 1

    return false
  }
}

class ByteSeeker {
  constructor(session, bytes, padding = 0) {
    this.session = session
    this.bytes = bytes
    this.padding = padding

    const size = session.byteLength - session.length * padding

    this.start = bytes >= size ? session.length : 0
    this.end = bytes < size ? session.length : 0
  }

  async _seek(bytes) {
    if (!bytes) return [0, 0]

    for (const node of this.session.roots) {
      // all async ticks happen once we find the root so safe
      const size = getUnpaddedSize(node, this.padding, null)

      if (bytes === size) return [flat.rightSpan(node.index) + 2, 0]
      if (bytes > size) {
        bytes -= size
        continue
      }

      const ite = flat.iterator(node.index)

      while ((ite.index & 1) !== 0) {
        const l = await getTreeNodeFromStorage(this.session.storage, ite.leftChild())

        if (l) {
          const size = getUnpaddedSize(l, this.padding, ite)

          if (size === bytes) return [ite.rightSpan() + 2, 0]
          if (size > bytes) continue
          bytes -= size
          ite.sibling()
        } else {
          ite.parent()
          return [ite.index, bytes]
        }
      }

      return [ite.index, bytes]
    }

    return null
  }

  async update() {
    // TODO: combine _seek and this, much simpler
    const res = await this._seek(this.bytes)
    if (!res) return null
    if ((res[0] & 1) === 0) return [res[0] / 2, res[1]]

    const span = flat.spans(res[0])
    this.start = span[0] / 2
    this.end = span[1] / 2 + 1

    return null
  }
}

class TreeProof {
  constructor(session, block, hash, seek, upgrade) {
    this.fork = session.fork
    this.signature = session.signature

    this.block = block
    this.hash = hash
    this.seek = seek
    this.upgrade = upgrade

    this.pending = {
      node: null,
      seek: null,
      upgrade: null,
      additionalUpgrade: null
    }
  }

  async settle() {
    const result = {
      fork: this.fork,
      block: null,
      hash: null,
      seek: null,
      upgrade: null,
      manifest: null
    }

    const [pNode, pSeek, pUpgrade, pAdditional] = await settleProof(this.pending)

    if (this.block) {
      if (pNode === null) throw INVALID_OPERATION('Invalid block request')
      result.block = {
        index: this.block.index,
        value: null, // populated upstream, alloc it here for simplicity
        nodes: pNode
      }
    } else if (this.hash) {
      if (pNode === null) throw INVALID_OPERATION('Invalid block request')
      result.hash = {
        index: this.hash.index,
        nodes: pNode
      }
    }

    if (this.seek && pSeek !== null) {
      result.seek = {
        bytes: this.seek.bytes,
        nodes: pSeek
      }
    }

    if (this.upgrade) {
      result.upgrade = {
        start: this.upgrade.start,
        length: this.upgrade.length,
        nodes: pUpgrade,
        additionalNodes: pAdditional || [],
        signature: this.signature
      }
    }

    return result
  }
}

class MerkleTree {
  static hash(s) {
    return unslab(crypto.tree(s.roots))
  }

  static signable(s, namespace) {
    return caps.treeSignable(namespace, MerkleTree.hash(s), s.length, s.fork)
  }

  static size(roots) {
    return totalSize(roots)
  }

  static span(roots) {
    return totalSpan(roots)
  }

  static getRoots(session, length) {
    return MerkleTree.getRootsFromStorage(session.storage, length)
  }

  static getRootsFromStorage(storage, length) {
    const indexes = flat.fullRoots(2 * length)
    const roots = new Array(indexes.length)
    const rx = storage.read()

    for (let i = 0; i < indexes.length; i++) {
      roots[i] = getTreeNodeOrError(rx, indexes[i])
    }

    rx.tryFlush()

    return Promise.all(roots)
  }

  static async upgradeable(session, length) {
    const indexes = flat.fullRoots(2 * length)
    const roots = new Array(indexes.length)
    const rx = session.storage.read()

    for (let i = 0; i < indexes.length; i++) {
      roots[i] = rx.getTreeNode(indexes[i])
    }

    rx.tryFlush()

    for (const node of await Promise.all(roots)) {
      if (node === null) return false
    }

    return true
  }

  static seek(session, bytes, padding) {
    return new ByteSeeker(session, bytes, padding)
  }

  static get(session, index) {
    return getTreeNodeFromStorage(session.storage, index)
  }

  static async truncate(session, length, batch, fork = batch.fork) {
    const head = length * 2
    const fullRoots = flat.fullRoots(head)

    for (let i = 0; i < fullRoots.length; i++) {
      const root = fullRoots[i]
      if (i < batch.roots.length && batch.roots[i].index === root) continue

      while (batch.roots.length > i) batch.roots.pop()
      batch.roots.push(unslabNode(await getTreeNodeFromStorageOrError(session.storage, root)))
    }

    while (batch.roots.length > fullRoots.length) {
      batch.roots.pop()
    }

    batch.fork = fork
    batch.length = length
    batch.ancestors = length
    batch.byteLength = totalSize(batch.roots)
    batch.upgraded = true

    return batch
  }

  static async reorg(session, proof, batch) {
    let unverified = null

    if (proof.block || proof.hash || proof.seek) {
      unverified = verifyTree(proof, batch.nodes)
    }

    if (!verifyUpgrade(proof, unverified, batch)) {
      throw INVALID_PROOF('Fork proof not verifiable')
    }

    for (const root of batch.roots) {
      const existing = await getTreeNodeFromStorage(session.storage, root.index)
      if (existing && b4a.equals(existing.hash, root.hash)) continue
      batch._updateDiffRoot(root)
      break
    }

    if (batch.diff !== null) {
      await batch._update(batch.nodes)
    } else {
      batch.want = null
      batch.ancestors = batch.length
    }

    return batch
  }

  static verifyFullyRemote(session, proof) {
    // TODO: impl this less hackishly
    const batch = new MerkleTreeBatch(session)

    batch.fork = proof.fork
    batch.roots = []
    batch.length = 0
    batch.ancestors = 0
    batch.byteLength = 0

    let unverified = verifyTree(proof, batch.nodes)

    if (proof.upgrade) {
      if (verifyUpgrade(proof, unverified, batch)) {
        unverified = null
      }
    }

    return batch
  }

  static async verify(session, proof) {
    const batch = new MerkleTreeBatch(session)

    let unverified = verifyTree(proof, batch.nodes)

    if (proof.upgrade) {
      if (verifyUpgrade(proof, unverified, batch)) {
        unverified = null
      }
    }

    if (unverified) {
      const verified = await getTreeNodeFromStorageOrError(session.storage, unverified.index)
      if (!b4a.equals(verified.hash, unverified.hash)) {
        throw INVALID_CHECKSUM('Invalid checksum at node ' + unverified.index)
      }
    }

    return batch
  }

  static proof(session, rx, { block, hash, seek, upgrade }) {
    return generateProof(session, rx, block, hash, seek, upgrade)
  }

  static maxMissingNodes(index, length) {
    const head = 2 * length
    const ite = flat.iterator(index)

    // See iterator.rightSpan()
    const iteRightSpan = ite.index + ite.factor / 2 - 1
    // If the index is not in the current tree, we do not know how many missing nodes there are...
    if (iteRightSpan >= head) return 0

    for (const r of flat.fullRoots(head)) {
      if (r === index) return 0
    }

    // need missing nodes not inclusive of index
    ite.parent()

    let cnt = 0
    while (!ite.contains(head)) {
      cnt++
      ite.parent()
    }

    return cnt
  }

  static async missingNodes(session, index, length) {
    const head = 2 * length
    const ite = flat.iterator(index)

    // See iterator.rightSpan()
    const iteRightSpan = ite.index + ite.factor / 2 - 1
    // If the index is not in the current tree, we do not know how many missing nodes there are...
    if (iteRightSpan >= head) return 0

    let cnt = 0
    // TODO: we could prop use a read batch here and do this in blocks of X for perf
    while (!ite.contains(head) && !(await hasTreeNode(session.storage, ite.index))) {
      cnt++
      if (cnt >= 1024) {
        throw ASSERTION('Bad arguments to missingNodes index=' + index + ' at length=' + length)
      }
      ite.parent()
    }

    return cnt
  }

  static byteOffset(session, index) {
    return getByteOffsetSession(session, index, null)
  }

  static byteRange(session, index) {
    const rx = session.storage.read()
    const offset = getByteOffsetSession(session, index, rx)
    const size = getNodeSize(index, rx)
    rx.tryFlush()
    return Promise.all([offset, size])
  }

  static upgradeLength(proof) {
    const extra = proof.additionalNodes

    if (extra.length) {
      const last = extra[extra.length - 1]
      const index = flat.rightSpan(last.index)
      return index / 2 + 1
    }

    return proof.start + proof.length
  }
}

module.exports = {
  MerkleTreeBatch,
  ReorgBatch,
  MerkleTree
}

async function getNodeSize(index, rx) {
  return (await getTreeNodeOrError(rx, index)).size
}

async function getByteOffsetSession(session, index, rx) {
  if (index === 2 * session.length) return session.byteLength

  const treeNodes =
    rx === null
      ? await getByteOffsetBatchFlush(session.roots, index, session.storage.read())
      : await getByteOffsetBatch(session.roots, index, rx)

  let offset = 0
  for (const node of treeNodes) offset += node.size

  return offset
}

async function getByteOffset(tree, index, rx) {
  if (index === 2 * tree.length) return tree.byteLength

  const treeNodes = await getByteOffsetBatch(tree.roots, index, rx)

  let offset = 0
  for (const node of treeNodes) offset += node.size

  return offset
}

function getByteOffsetBatchFlush(roots, index, rx) {
  const treeNodes = getByteOffsetBatch(roots, index, rx)
  rx.tryFlush()
  return treeNodes
}

function getByteOffsetBatch(roots, index, rx) {
  if ((index & 1) === 1) index = flat.leftSpan(index)

  let head = 0
  let cnt = 0

  const promises = []

  for (const node of roots) {
    // all async ticks happen once we find the root so safe
    head += 2 * (node.index - head + 1)

    if (index >= head) {
      promises.push(node.size)
      continue
    }

    const ite = flat.iterator(node.index)

    while (ite.index !== index) {
      if (++cnt >= 1024) throw ASSERTION('Bad getByteOffsetSession index=' + index)

      if (index < ite.index) {
        ite.leftChild()
      } else {
        promises.push(getTreeNodeOrError(rx, ite.leftChild()))
        ite.sibling()
      }
    }

    return Promise.all(promises)
  }

  throw ASSERTION('Failed to find offset')
}

function getByteRange(tree, index, rx) {
  const head = 2 * tree.length
  if (((index & 1) === 0 ? index : flat.rightSpan(index)) >= head) {
    throw BAD_ARGUMENT('Index is out of bounds')
  }

  const offset = getByteOffset(tree, index, rx)
  const size = getNodeSize(index, rx)

  return Promise.all([offset, size])
}

// All the methods needed for proof verification

function verifyTree({ block, hash, seek }, nodes) {
  const untrustedNode = block
    ? { index: 2 * block.index, value: block.value, nodes: block.nodes }
    : hash
      ? { index: hash.index, value: null, nodes: hash.nodes }
      : null

  if (untrustedNode === null && (!seek || !seek.nodes.length)) return null

  let root = null

  if (seek && seek.nodes.length) {
    const ite = flat.iterator(seek.nodes[0].index)
    const q = new NodeQueue(seek.nodes)

    root = q.shift(ite.index)
    nodes.push(root)

    while (q.length > 0) {
      const node = q.shift(ite.sibling())

      root = parentNode(ite.parent(), root, node)
      nodes.push(node)
      nodes.push(root)
    }
  }

  if (untrustedNode === null) return root

  const ite = flat.iterator(untrustedNode.index)
  const blockHash = untrustedNode.value && blockNode(ite.index, untrustedNode.value)

  const q = new NodeQueue(untrustedNode.nodes, root)

  root = blockHash || q.shift(ite.index)
  nodes.push(root)

  while (q.length > 0) {
    const node = q.shift(ite.sibling())

    root = parentNode(ite.parent(), root, node)
    nodes.push(node)
    nodes.push(root)
  }

  return root
}

function verifyUpgrade({ fork, upgrade }, blockRoot, batch) {
  const prologue = batch.prologue

  if (prologue) {
    const { start, length } = upgrade
    if (start < prologue.length && (start !== 0 || length < prologue.length)) {
      throw INVALID_PROOF('Upgrade does not satisfy prologue')
    }
  }

  const q = new NodeQueue(upgrade.nodes, blockRoot)

  let grow = batch.roots.length > 0
  let i = 0

  const to = 2 * (upgrade.start + upgrade.length)
  const ite = flat.iterator(0)

  for (; ite.fullRoot(to); ite.nextTree()) {
    if (i < batch.roots.length && batch.roots[i].index === ite.index) {
      i++
      continue
    }

    if (grow) {
      grow = false
      const root = ite.index
      if (i < batch.roots.length) {
        ite.seek(batch.roots[batch.roots.length - 1].index)
        while (ite.index !== root) {
          batch.appendRoot(q.shift(ite.sibling()), ite)
        }
        continue
      }
    }

    batch.appendRoot(q.shift(ite.index), ite)
  }

  if (prologue && batch.length === prologue.length) {
    if (!b4a.equals(prologue.hash, batch.hash())) {
      throw INVALID_PROOF('Invalid hash')
    }
  }

  const extra = upgrade.additionalNodes

  ite.seek(batch.roots[batch.roots.length - 1].index)
  i = 0

  while (i < extra.length && extra[i].index === ite.sibling()) {
    batch.appendRoot(extra[i++], ite)
  }

  while (i < extra.length) {
    const node = extra[i++]

    while (node.index !== ite.index) {
      if (ite.factor === 2) throw INVALID_OPERATION('Unexpected node: ' + node.index)
      ite.leftChild()
    }

    batch.appendRoot(node, ite)
    ite.sibling()
  }

  batch.signature = unslab(upgrade.signature)
  batch.fork = fork

  return q.extra === null
}

async function seekFromHead(session, head, bytes, padding) {
  const roots = flat.fullRoots(head)

  for (let i = 0; i < roots.length; i++) {
    const root = roots[i]
    const node = await getTreeNodeFromStorage(session.storage, root)
    const size = getUnpaddedSize(node, padding, null)

    if (bytes === size) return root
    if (bytes > size) {
      bytes -= size
      continue
    }

    return seekTrustedTree(session, root, bytes, padding)
  }

  return head
}

// trust that bytes are within the root tree and find the block at bytes

async function seekTrustedTree(session, root, bytes, padding) {
  if (!bytes) return root

  const ite = flat.iterator(root)
  let cnt = 0

  while ((ite.index & 1) !== 0) {
    if (++cnt >= 1024) throw ASSERTION('Bad seekTrusted bytes=' + bytes + ', paddding=' + padding)
    const l = await getTreeNodeFromStorage(session.storage, ite.leftChild())

    if (l) {
      const size = getUnpaddedSize(l, padding, ite)
      if (size === bytes) return ite.index
      if (size > bytes) continue
      bytes -= size
      ite.sibling()
    } else {
      ite.parent()
      return ite.index
    }
  }

  return ite.index
}

// try to find the block at bytes without trusting that is *is* within the root passed

async function seekUntrustedTree(session, root, bytes, padding) {
  const offset =
    (await getByteOffsetSession(session, root, null)) -
    (padding ? (padding * flat.leftSpan(root)) / 2 : 0)

  if (offset > bytes) throw INVALID_OPERATION('Invalid seek')
  if (offset === bytes) return root

  bytes -= offset

  const node = await getTreeNodeFromStorageOrError(session.storage, root)

  if (getUnpaddedSize(node, padding, null) <= bytes) throw INVALID_OPERATION('Invalid seek')

  return seekTrustedTree(session, root, bytes, padding)
}

// Below is proof production, ie, construct proofs to verify a request
// Note, that all these methods are sync as we can statically infer which nodes
// are needed for the remote to verify given they arguments they passed us

function seekProof(session, rx, seekRoot, root, p) {
  const ite = flat.iterator(seekRoot)
  let cnt = 0

  p.seek = []
  p.seek.push(getTreeNodeOrError(rx, ite.index))

  while (ite.index !== root) {
    if (++cnt >= 1024) throw ASSERTION('Bad seekProof seekRoot=' + seekRoot + ', root=' + root)
    ite.sibling()
    p.seek.push(getTreeNodeOrError(rx, ite.index))
    ite.parent()
  }
}

function blockAndSeekProof(session, rx, node, seek, seekRoot, root, p) {
  if (!node) return seekProof(session, rx, seekRoot, root, p)

  const ite = flat.iterator(node.index)
  let cnt = 0

  p.node = []
  if (!node.value) p.node.push(getTreeNodeOrError(rx, ite.index))

  while (ite.index !== root) {
    if (++cnt >= 1024) {
      throw ASSERTION('Bad blockAndSeekProof seekRoot=' + seekRoot + ', root=' + root)
    }
    ite.sibling()

    if (seek && ite.contains(seekRoot) && ite.index !== seekRoot) {
      seekProof(session, rx, seekRoot, ite.index, p)
    } else {
      p.node.push(getTreeNodeOrError(rx, ite.index))
    }

    ite.parent()
  }
}

function upgradeProof(session, rx, node, seek, from, to, subTree, p) {
  if (from === 0) p.upgrade = []

  for (const ite = flat.iterator(0); ite.fullRoot(to); ite.nextTree()) {
    // check if they already have the node
    if (ite.index + ite.factor / 2 < from) continue

    // connect existing tree
    if (p.upgrade === null && ite.contains(from - 2)) {
      p.upgrade = []

      const root = ite.index
      const target = from - 2
      let cnt = 0

      ite.seek(target)

      while (ite.index !== root) {
        if (++cnt >= 1024) throw ASSERTION('Bad upgradeProof target=' + target + ', root=' + root)

        ite.sibling()
        if (ite.index > target) {
          if (p.node === null && p.seek === null && ite.contains(subTree)) {
            blockAndSeekProof(session, rx, node, seek, subTree, ite.index, p)
          } else {
            p.upgrade.push(getTreeNodeOrError(rx, ite.index))
          }
        }
        ite.parent()
      }

      continue
    }

    if (p.upgrade === null) {
      p.upgrade = []
    }

    // if the subtree included is a child of this tree, include that one
    // instead of a dup node
    if (p.node === null && p.seek === null && ite.contains(subTree)) {
      blockAndSeekProof(session, rx, node, seek, subTree, ite.index, p)
      continue
    }

    // add root (can be optimised since the root might be in tree.roots)
    p.upgrade.push(getTreeNodeOrError(rx, ite.index))
  }
}

function additionalUpgradeProof(session, rx, from, to, p) {
  if (from === 0) p.additionalUpgrade = []

  for (const ite = flat.iterator(0); ite.fullRoot(to); ite.nextTree()) {
    // check if they already have the node
    if (ite.index + ite.factor / 2 < from) continue

    // connect existing tree
    if (p.additionalUpgrade === null && ite.contains(from - 2)) {
      p.additionalUpgrade = []

      const root = ite.index
      const target = from - 2
      let cnt = 0

      ite.seek(target)

      while (ite.index !== root) {
        if (++cnt >= 1024) {
          throw ASSERTION(
            'Bad arguments to additionalUpgradeProof root=' + root + ' target=' + target
          )
        }
        ite.sibling()
        if (ite.index > target) {
          p.additionalUpgrade.push(getTreeNodeOrError(rx, ite.index))
        }
        ite.parent()
      }

      continue
    }

    if (p.additionalUpgrade === null) {
      p.additionalUpgrade = []
    }

    // add root (can be optimised since the root is in tree.roots)
    p.additionalUpgrade.push(getTreeNodeOrError(rx, ite.index))
  }
}

function nodesToRoot(index, nodes, head) {
  const ite = flat.iterator(index)

  for (let i = 0; i < nodes; i++) {
    ite.parent()
    if (ite.contains(head)) throw INVALID_OPERATION('Nodes is out of bounds')
  }

  return ite.index
}

function totalSize(nodes) {
  let s = 0
  for (const node of nodes) s += node.size
  return s
}

function totalSpan(nodes) {
  let s = 0
  for (const node of nodes) s += 2 * (node.index - s + 1)
  return s
}

function blockNode(index, value) {
  return { index, size: value.byteLength, hash: crypto.data(value) }
}

function parentNode(index, a, b) {
  return { index, size: a.size + b.size, hash: crypto.parent(a, b) }
}

function log2(n) {
  let res = 1

  while (n > 2) {
    n /= 2
    res++
  }

  return res
}

function normalizeIndexed(block, hash) {
  if (block) {
    return {
      value: true,
      index: block.index * 2,
      nodes: block.nodes,
      lastIndex: block.index
    }
  }
  if (hash) {
    return {
      value: false,
      index: hash.index,
      nodes: hash.nodes,
      lastIndex: flat.rightSpan(hash.index) / 2
    }
  }
  return null
}

async function getTreeNodeOrError(rx, index) {
  const node = await rx.getTreeNode(index)
  if (node === null) {
    throw INVALID_OPERATION('Expected tree node ' + index + ' from storage, got (nil)')
  }
  return node
}

function getTreeNodeFromStorageOrError(storage, index) {
  const rx = storage.read()
  const p = getTreeNodeOrError(rx, index)
  rx.tryFlush()
  return p
}

function getTreeNodeFromStorage(storage, index) {
  const rx = storage.read()
  const node = rx.getTreeNode(index)
  rx.tryFlush()
  return node
}

function hasTreeNode(storage, index) {
  const rx = storage.read()
  const has = rx.hasTreeNode(index)
  rx.tryFlush()
  return has
}

async function settleProof(p) {
  const result = [
    p.node && Promise.all(p.node),
    p.seek && Promise.all(p.seek),
    p.upgrade && Promise.all(p.upgrade),
    p.additionalUpgrade && Promise.all(p.additionalUpgrade)
  ]

  try {
    return await Promise.all(result)
  } catch (err) {
    if (p.node) await Promise.allSettled(p.node)
    if (p.seek) await Promise.allSettled(p.seek)
    if (p.upgrade) await Promise.allSettled(p.upgrade)
    if (p.additionalUpgrade) await Promise.allSettled(p.additionalUpgrade)
    throw err
  }
}

// tree can be either the merkle tree or a merkle tree batch
async function generateProof(session, rx, block, hash, seek, upgrade) {
  // Important that this does not throw inbetween making the promise arrays
  // and finalise being called, otherwise there will be lingering promises in the background

  if (session.prologue && upgrade) {
    upgrade.start = upgrade.start < session.prologue.length ? 0 : upgrade.start
    upgrade.length =
      upgrade.start < session.prologue.length ? session.prologue.length : upgrade.length
  }

  const head = 2 * session.length
  const from = upgrade ? upgrade.start * 2 : 0
  const to = upgrade ? from + upgrade.length * 2 : head
  const node = normalizeIndexed(block, hash)

  // can't do anything as we have no data...
  if (head === 0) return new TreeProof(session, null, null, null, null)

  if (from >= to || to > head) {
    throw INVALID_OPERATION('Invalid upgrade')
  }
  if (seek && upgrade && node !== null && node.index >= from) {
    throw INVALID_OPERATION('Cannot both do a seek and block/hash request when upgrading')
  }

  let subTree = head

  const p = new TreeProof(session, block, hash, seek, upgrade)

  if (node !== null && (!upgrade || node.lastIndex < upgrade.start)) {
    subTree = nodesToRoot(node.index, node.nodes, to)
    const seekRoot = seek
      ? await seekUntrustedTree(session, subTree, seek.bytes, seek.padding)
      : head
    blockAndSeekProof(session, rx, node, seek, seekRoot, subTree, p.pending)
  } else if ((node || seek) && upgrade) {
    subTree = seek ? await seekFromHead(session, to, seek.bytes, seek.padding) : node.index
  }

  if (upgrade) {
    upgradeProof(session, rx, node, seek, from, to, subTree, p.pending)
    if (head > to) additionalUpgradeProof(session, rx, to, head, p.pending)
  }

  return p
}

function getUnpaddedSize(node, padding, ite) {
  return padding === 0
    ? node.size
    : node.size - padding * (ite ? ite.countLeaves() : flat.countLeaves(node.index))
}

function unslabNodes(nodes) {
  for (const node of nodes) unslabNode(node)
  return nodes
}

function unslabNode(node) {
  if (node === null) return node
  node.hash = unslab(node.hash)
  return node
}
const c = require('compact-encoding')
const { DEFAULT_NAMESPACE } = require('./caps')
const { INVALID_OPLOG_VERSION } = require('hypercore-errors')
const unslab = require('unslab')

const MANIFEST_PATCH = 0b00000001
const MANIFEST_PROLOGUE = 0b00000010
const MANIFEST_LINKED = 0b00000100
const MANIFEST_USER_DATA = 0b00001000

const hashes = {
  preencode(state, m) {
    state.end++ // small uint
  },
  encode(state, m) {
    if (m === 'blake2b') {
      c.uint.encode(state, 0)
      return
    }

    throw new Error('Unknown hash: ' + m)
  },
  decode(state) {
    const n = c.uint.decode(state)
    if (n === 0) return 'blake2b'
    throw new Error('Unknown hash id: ' + n)
  }
}

const signatures = {
  preencode(state, m) {
    state.end++ // small uint
  },
  encode(state, m) {
    if (m === 'ed25519') {
      c.uint.encode(state, 0)
      return
    }

    throw new Error('Unknown signature: ' + m)
  },
  decode(state) {
    const n = c.uint.decode(state)
    if (n === 0) return 'ed25519'
    throw new Error('Unknown signature id: ' + n)
  }
}

const signer = {
  preencode(state, m) {
    signatures.preencode(state, m.signature)
    c.fixed32.preencode(state, m.namespace)
    c.fixed32.preencode(state, m.publicKey)
  },
  encode(state, m) {
    signatures.encode(state, m.signature)
    c.fixed32.encode(state, m.namespace)
    c.fixed32.encode(state, m.publicKey)
  },
  decode(state) {
    return {
      signature: signatures.decode(state),
      namespace: c.fixed32.decode(state),
      publicKey: c.fixed32.decode(state)
    }
  }
}

const signerArray = c.array(signer)

const prologue = {
  preencode(state, p) {
    c.fixed32.preencode(state, p.hash)
    c.uint.preencode(state, p.length)
  },
  encode(state, p) {
    c.fixed32.encode(state, p.hash)
    c.uint.encode(state, p.length)
  },
  decode(state) {
    return {
      hash: c.fixed32.decode(state),
      length: c.uint.decode(state)
    }
  }
}

const manifestv0 = {
  preencode(state, m) {
    hashes.preencode(state, m.hash)
    state.end++ // type

    if (m.prologue && m.signers.length === 0) {
      c.fixed32.preencode(state, m.prologue.hash)
      return
    }

    if (m.quorum === 1 && m.signers.length === 1 && !m.allowPatch) {
      signer.preencode(state, m.signers[0])
    } else {
      state.end++ // flags
      c.uint.preencode(state, m.quorum)
      signerArray.preencode(state, m.signers)
    }
  },
  encode(state, m) {
    hashes.encode(state, m.hash)

    if (m.prologue && m.signers.length === 0) {
      c.uint.encode(state, 0)
      c.fixed32.encode(state, m.prologue.hash)
      return
    }

    if (m.quorum === 1 && m.signers.length === 1 && !m.allowPatch) {
      c.uint.encode(state, 1)
      signer.encode(state, m.signers[0])
    } else {
      c.uint.encode(state, 2)
      c.uint.encode(state, m.allowPatch ? 1 : 0)
      c.uint.encode(state, m.quorum)
      signerArray.encode(state, m.signers)
    }
  },
  decode(state) {
    const hash = hashes.decode(state)
    const type = c.uint.decode(state)

    if (type > 2) throw new Error('Unknown type: ' + type)

    if (type === 0) {
      return {
        version: 0,
        hash,
        allowPatch: false,
        quorum: 0,
        signers: [],
        prologue: {
          hash: c.fixed32.decode(state),
          length: 0
        },
        linked: null,
        userData: null
      }
    }

    if (type === 1) {
      return {
        version: 0,
        hash,
        allowPatch: false,
        quorum: 1,
        signers: [signer.decode(state)],
        prologue: null,
        linked: null,
        userData: null
      }
    }

    const flags = c.uint.decode(state)

    return {
      version: 0,
      hash,
      allowPatch: (flags & 1) !== 0,
      quorum: c.uint.decode(state),
      signers: signerArray.decode(state),
      prologue: null,
      linked: null,
      userData: null
    }
  }
}

const fixed32Array = c.array(c.fixed32)

const manifest = (exports.manifest = {
  preencode(state, m) {
    state.end++ // version

    if (m.version === 0) return manifestv0.preencode(state, m)

    state.end++ // flags
    hashes.preencode(state, m.hash)

    c.uint.preencode(state, m.quorum)
    signerArray.preencode(state, m.signers)

    if (m.prologue) prologue.preencode(state, m.prologue)
    if (m.linked) fixed32Array.preencode(state, m.linked)
    if (m.userData) c.buffer.preencode(state, m.userData)
  },
  encode(state, m) {
    c.uint.encode(state, m.version)

    if (m.version === 0) return manifestv0.encode(state, m)

    let flags = 0
    if (m.allowPatch) flags |= MANIFEST_PATCH
    if (m.prologue) flags |= MANIFEST_PROLOGUE
    if (m.linked) flags |= MANIFEST_LINKED
    if (m.userData) flags |= MANIFEST_USER_DATA

    c.uint.encode(state, flags)
    hashes.encode(state, m.hash)

    c.uint.encode(state, m.quorum)
    signerArray.encode(state, m.signers)

    if (m.prologue) prologue.encode(state, m.prologue)
    if (m.linked) fixed32Array.encode(state, m.linked)
    if (m.userData) c.buffer.encode(state, m.userData)
  },
  decode(state) {
    const version = c.uint.decode(state)

    if (version === 0) return manifestv0.decode(state)
    if (version > 2) throw new Error('Unknown version: ' + version)

    const flags = c.uint.decode(state)
    const hash = hashes.decode(state)
    const quorum = c.uint.decode(state)
    const signers = signerArray.decode(state)

    const hasPatch = (flags & MANIFEST_PATCH) !== 0
    const hasPrologue = (flags & MANIFEST_PROLOGUE) !== 0
    const hasLinked = (flags & MANIFEST_LINKED) !== 0
    const hasUserData = (flags & MANIFEST_USER_DATA) !== 0

    return {
      version,
      hash,
      allowPatch: hasPatch,
      quorum,
      signers,
      prologue: hasPrologue ? prologue.decode(state) : null,
      linked: hasLinked ? fixed32Array.decode(state) : null,
      userData: hasUserData ? c.buffer.decode(state) : null
    }
  }
})

const node = {
  preencode(state, n) {
    c.uint.preencode(state, n.index)
    c.uint.preencode(state, n.size)
    c.fixed32.preencode(state, n.hash)
  },
  encode(state, n) {
    c.uint.encode(state, n.index)
    c.uint.encode(state, n.size)
    c.fixed32.encode(state, n.hash)
  },
  decode(state) {
    return {
      index: c.uint.decode(state),
      size: c.uint.decode(state),
      hash: c.fixed32.decode(state)
    }
  }
}

const nodeArray = c.array(node)

const wire = (exports.wire = {})

wire.handshake = {
  preencode(state, m) {
    c.uint.preencode(state, 1)
    c.fixed32.preencode(state, m.capability)
  },
  encode(state, m) {
    c.uint.encode(state, m.seeks ? 1 : 0)
    c.fixed32.encode(state, m.capability)
  },
  decode(state) {
    const flags = c.uint.decode(state)
    return {
      seeks: (flags & 1) !== 0,
      capability: unslab(c.fixed32.decode(state))
    }
  }
}

const requestBlock = {
  preencode(state, b) {
    c.uint.preencode(state, b.index)
    c.uint.preencode(state, b.nodes)
  },
  encode(state, b) {
    c.uint.encode(state, b.index)
    c.uint.encode(state, b.nodes)
  },
  decode(state) {
    return {
      index: c.uint.decode(state),
      nodes: c.uint.decode(state)
    }
  }
}

const requestSeek = {
  preencode(state, s) {
    c.uint.preencode(state, s.bytes)
    c.uint.preencode(state, s.padding)
  },
  encode(state, s) {
    c.uint.encode(state, s.bytes)
    c.uint.encode(state, s.padding)
  },
  decode(state) {
    return {
      bytes: c.uint.decode(state),
      padding: c.uint.decode(state)
    }
  }
}

const requestUpgrade = {
  preencode(state, u) {
    c.uint.preencode(state, u.start)
    c.uint.preencode(state, u.length)
  },
  encode(state, u) {
    c.uint.encode(state, u.start)
    c.uint.encode(state, u.length)
  },
  decode(state) {
    return {
      start: c.uint.decode(state),
      length: c.uint.decode(state)
    }
  }
}

wire.request = {
  preencode(state, m) {
    state.end++ // flags
    c.uint.preencode(state, m.id)
    c.uint.preencode(state, m.fork)

    if (m.block) requestBlock.preencode(state, m.block)
    if (m.hash) requestBlock.preencode(state, m.hash)
    if (m.seek) requestSeek.preencode(state, m.seek)
    if (m.upgrade) requestUpgrade.preencode(state, m.upgrade)
    if (m.priority) c.uint.preencode(state, m.priority)
  },
  encode(state, m) {
    const flags =
      (m.block ? 1 : 0) |
      (m.hash ? 2 : 0) |
      (m.seek ? 4 : 0) |
      (m.upgrade ? 8 : 0) |
      (m.manifest ? 16 : 0) |
      (m.priority ? 32 : 0)

    c.uint.encode(state, flags)
    c.uint.encode(state, m.id)
    c.uint.encode(state, m.fork)

    if (m.block) requestBlock.encode(state, m.block)
    if (m.hash) requestBlock.encode(state, m.hash)
    if (m.seek) requestSeek.encode(state, m.seek)
    if (m.upgrade) requestUpgrade.encode(state, m.upgrade)
    if (m.priority) c.uint.encode(state, m.priority)
  },
  decode(state) {
    const flags = c.uint.decode(state)

    return {
      id: c.uint.decode(state),
      fork: c.uint.decode(state),
      block: flags & 1 ? requestBlock.decode(state) : null,
      hash: flags & 2 ? requestBlock.decode(state) : null,
      seek: flags & 4 ? requestSeek.decode(state) : null,
      upgrade: flags & 8 ? requestUpgrade.decode(state) : null,
      manifest: (flags & 16) !== 0,
      priority: flags & 32 ? c.uint.decode(state) : 0
    }
  }
}

wire.cancel = {
  preencode(state, m) {
    c.uint.preencode(state, m.request)
  },
  encode(state, m) {
    c.uint.encode(state, m.request)
  },
  decode(state, m) {
    return {
      request: c.uint.decode(state)
    }
  }
}

const dataUpgrade = {
  preencode(state, u) {
    c.uint.preencode(state, u.start)
    c.uint.preencode(state, u.length)
    nodeArray.preencode(state, u.nodes)
    nodeArray.preencode(state, u.additionalNodes)
    c.optionalBuffer.preencode(state, u.signature)
  },
  encode(state, u) {
    c.uint.encode(state, u.start)
    c.uint.encode(state, u.length)
    nodeArray.encode(state, u.nodes)
    nodeArray.encode(state, u.additionalNodes)
    c.optionalBuffer.encode(state, u.signature)
  },
  decode(state) {
    return {
      start: c.uint.decode(state),
      length: c.uint.decode(state),
      nodes: nodeArray.decode(state),
      additionalNodes: nodeArray.decode(state),
      signature: c.optionalBuffer.decode(state)
    }
  }
}

const dataSeek = {
  preencode(state, s) {
    c.uint.preencode(state, s.bytes)
    nodeArray.preencode(state, s.nodes)
  },
  encode(state, s) {
    c.uint.encode(state, s.bytes)
    nodeArray.encode(state, s.nodes)
  },
  decode(state) {
    return {
      bytes: c.uint.decode(state),
      nodes: nodeArray.decode(state)
    }
  }
}

const dataBlock = {
  preencode(state, b) {
    c.uint.preencode(state, b.index)
    c.buffer.preencode(state, b.value)
    nodeArray.preencode(state, b.nodes)
  },
  encode(state, b) {
    c.uint.encode(state, b.index)
    c.buffer.encode(state, b.value)
    nodeArray.encode(state, b.nodes)
  },
  decode(state) {
    return {
      index: c.uint.decode(state),
      value: c.buffer.decode(state),
      nodes: nodeArray.decode(state)
    }
  }
}

const dataHash = {
  preencode(state, b) {
    c.uint.preencode(state, b.index)
    nodeArray.preencode(state, b.nodes)
  },
  encode(state, b) {
    c.uint.encode(state, b.index)
    nodeArray.encode(state, b.nodes)
  },
  decode(state) {
    return {
      index: c.uint.decode(state),
      nodes: nodeArray.decode(state)
    }
  }
}

wire.data = {
  preencode(state, m) {
    state.end++ // flags
    c.uint.preencode(state, m.request)
    c.uint.preencode(state, m.fork)

    if (m.block) dataBlock.preencode(state, m.block)
    if (m.hash) dataHash.preencode(state, m.hash)
    if (m.seek) dataSeek.preencode(state, m.seek)
    if (m.upgrade) dataUpgrade.preencode(state, m.upgrade)
    if (m.manifest) manifest.preencode(state, m.manifest)
  },
  encode(state, m) {
    const flags =
      (m.block ? 1 : 0) |
      (m.hash ? 2 : 0) |
      (m.seek ? 4 : 0) |
      (m.upgrade ? 8 : 0) |
      (m.manifest ? 16 : 0)

    c.uint.encode(state, flags)
    c.uint.encode(state, m.request)
    c.uint.encode(state, m.fork)

    if (m.block) dataBlock.encode(state, m.block)
    if (m.hash) dataHash.encode(state, m.hash)
    if (m.seek) dataSeek.encode(state, m.seek)
    if (m.upgrade) dataUpgrade.encode(state, m.upgrade)
    if (m.manifest) manifest.encode(state, m.manifest)
  },
  decode(state) {
    const flags = c.uint.decode(state)

    return {
      request: c.uint.decode(state),
      fork: c.uint.decode(state),
      block: flags & 1 ? dataBlock.decode(state) : null,
      hash: flags & 2 ? dataHash.decode(state) : null,
      seek: flags & 4 ? dataSeek.decode(state) : null,
      upgrade: flags & 8 ? dataUpgrade.decode(state) : null,
      manifest: flags & 16 ? manifest.decode(state) : null
    }
  }
}

wire.noData = {
  preencode(state, m) {
    c.uint.preencode(state, m.request)
    c.uint.preencode(state, m.reason ? 1 : 0)
    if (m.reason) c.uint.preencode(state, m.reason)
  },
  encode(state, m) {
    c.uint.encode(state, m.request)
    c.uint.encode(state, m.reason ? 1 : 0)
    if (m.reason) c.uint.encode(state, m.reason)
  },
  decode(state, m) {
    const request = c.uint.decode(state)
    const flags = state.start < state.end ? c.uint.decode(state) : 0
    return {
      request,
      reason: flags & 1 ? c.uint.decode(state) : 0
    }
  }
}

// TODO: we should move to having these have a handle like in data, easier since they
// allocate state in practice
wire.want = {
  preencode(state, m) {
    c.uint.preencode(state, m.start)
    c.uint.preencode(state, m.length)

    const flags = m.any ? 1 : 0
    if (flags !== 0) c.uint.preencode(state, flags)
  },
  encode(state, m) {
    c.uint.encode(state, m.start)
    c.uint.encode(state, m.length)

    const flags = m.any ? 1 : 0
    if (flags !== 0) c.uint.encode(state, flags)
  },
  decode(state) {
    const start = c.uint.decode(state)
    const length = c.uint.decode(state)
    const flags = state.start < state.end ? c.uint.decode(state) : 0

    return {
      start,
      length,
      any: (flags & 1) !== 0
    }
  }
}

wire.unwant = {
  preencode(state, m) {
    c.uint.preencode(state, m.start)
    c.uint.preencode(state, m.length)

    const flags = m.any ? 1 : 0
    if (flags !== 0) c.uint.preencode(state, flags)
  },
  encode(state, m) {
    c.uint.encode(state, m.start)
    c.uint.encode(state, m.length)

    const flags = m.any ? 1 : 0
    if (flags !== 0) c.uint.encode(state, flags)
  },
  decode(state, m) {
    const start = c.uint.decode(state)
    const length = c.uint.decode(state)
    const flags = state.start < state.end ? c.uint.decode(state) : 0

    return {
      start,
      length,
      any: (flags & 1) !== 0
    }
  }
}

wire.range = {
  preencode(state, m) {
    state.end++ // flags
    c.uint.preencode(state, m.start)
    if (m.length !== 1) c.uint.preencode(state, m.length)
  },
  encode(state, m) {
    c.uint.encode(state, (m.drop ? 1 : 0) | (m.length === 1 ? 2 : 0))
    c.uint.encode(state, m.start)
    if (m.length !== 1) c.uint.encode(state, m.length)
  },
  decode(state) {
    const flags = c.uint.decode(state)

    return {
      drop: (flags & 1) !== 0,
      start: c.uint.decode(state),
      length: (flags & 2) !== 0 ? 1 : c.uint.decode(state)
    }
  }
}

wire.bitfield = {
  preencode(state, m) {
    c.uint.preencode(state, m.start)
    c.uint32array.preencode(state, m.bitfield)
  },
  encode(state, m) {
    c.uint.encode(state, m.start)
    c.uint32array.encode(state, m.bitfield)
  },
  decode(state, m) {
    return {
      start: c.uint.decode(state),
      bitfield: c.uint32array.decode(state)
    }
  }
}

wire.sync = {
  preencode(state, m) {
    state.end++ // flags
    c.uint.preencode(state, m.fork)
    c.uint.preencode(state, m.length)
    c.uint.preencode(state, m.remoteLength)
  },
  encode(state, m) {
    c.uint.encode(
      state,
      (m.canUpgrade ? 1 : 0) |
        (m.uploading ? 2 : 0) |
        (m.downloading ? 4 : 0) |
        (m.hasManifest ? 8 : 0) |
        (m.allowPush ? 16 : 0)
    )
    c.uint.encode(state, m.fork)
    c.uint.encode(state, m.length)
    c.uint.encode(state, m.remoteLength)
  },
  decode(state) {
    const flags = c.uint.decode(state)

    return {
      fork: c.uint.decode(state),
      length: c.uint.decode(state),
      remoteLength: c.uint.decode(state),
      canUpgrade: (flags & 1) !== 0,
      uploading: (flags & 2) !== 0,
      downloading: (flags & 4) !== 0,
      hasManifest: (flags & 8) !== 0,
      allowPush: (flags & 16) !== 0
    }
  }
}

wire.reorgHint = {
  preencode(state, m) {
    c.uint.preencode(state, m.from)
    c.uint.preencode(state, m.to)
    c.uint.preencode(state, m.ancestors)
  },
  encode(state, m) {
    c.uint.encode(state, m.from)
    c.uint.encode(state, m.to)
    c.uint.encode(state, m.ancestors)
  },
  decode(state) {
    return {
      from: c.uint.decode(state),
      to: c.uint.decode(state),
      ancestors: c.uint.decode(state)
    }
  }
}

wire.extension = {
  preencode(state, m) {
    c.string.preencode(state, m.name)
    c.raw.preencode(state, m.message)
  },
  encode(state, m) {
    c.string.encode(state, m.name)
    c.raw.encode(state, m.message)
  },
  decode(state) {
    return {
      name: c.string.decode(state),
      message: c.raw.decode(state)
    }
  }
}

const keyValue = {
  preencode(state, p) {
    c.string.preencode(state, p.key)
    c.buffer.preencode(state, p.value)
  },
  encode(state, p) {
    c.string.encode(state, p.key)
    c.buffer.encode(state, p.value)
  },
  decode(state) {
    return {
      key: c.string.decode(state),
      value: c.buffer.decode(state)
    }
  }
}

const treeUpgrade = {
  preencode(state, u) {
    c.uint.preencode(state, u.fork)
    c.uint.preencode(state, u.ancestors)
    c.uint.preencode(state, u.length)
    c.buffer.preencode(state, u.signature)
  },
  encode(state, u) {
    c.uint.encode(state, u.fork)
    c.uint.encode(state, u.ancestors)
    c.uint.encode(state, u.length)
    c.buffer.encode(state, u.signature)
  },
  decode(state) {
    return {
      fork: c.uint.decode(state),
      ancestors: c.uint.decode(state),
      length: c.uint.decode(state),
      signature: c.buffer.decode(state)
    }
  }
}

const bitfieldUpdate = {
  // TODO: can maybe be folded into a HAVE later on with the most recent spec
  preencode(state, b) {
    state.end++ // flags
    c.uint.preencode(state, b.start)
    c.uint.preencode(state, b.length)
  },
  encode(state, b) {
    state.buffer[state.start++] = b.drop ? 1 : 0
    c.uint.encode(state, b.start)
    c.uint.encode(state, b.length)
  },
  decode(state) {
    const flags = c.uint.decode(state)
    return {
      drop: (flags & 1) !== 0,
      start: c.uint.decode(state),
      length: c.uint.decode(state)
    }
  }
}

const oplog = (exports.oplog = {})

oplog.entry = {
  preencode(state, m) {
    state.end++ // flags
    if (m.userData) keyValue.preencode(state, m.userData)
    if (m.treeNodes) nodeArray.preencode(state, m.treeNodes)
    if (m.treeUpgrade) treeUpgrade.preencode(state, m.treeUpgrade)
    if (m.bitfield) bitfieldUpdate.preencode(state, m.bitfield)
  },
  encode(state, m) {
    const s = state.start++
    let flags = 0

    if (m.userData) {
      flags |= 1
      keyValue.encode(state, m.userData)
    }
    if (m.treeNodes) {
      flags |= 2
      nodeArray.encode(state, m.treeNodes)
    }
    if (m.treeUpgrade) {
      flags |= 4
      treeUpgrade.encode(state, m.treeUpgrade)
    }
    if (m.bitfield) {
      flags |= 8
      bitfieldUpdate.encode(state, m.bitfield)
    }

    state.buffer[s] = flags
  },
  decode(state) {
    const flags = c.uint.decode(state)
    return {
      userData: (flags & 1) !== 0 ? keyValue.decode(state) : null,
      treeNodes: (flags & 2) !== 0 ? nodeArray.decode(state) : null,
      treeUpgrade: (flags & 4) !== 0 ? treeUpgrade.decode(state) : null,
      bitfield: (flags & 8) !== 0 ? bitfieldUpdate.decode(state) : null
    }
  }
}

const keyPair = {
  preencode(state, kp) {
    c.buffer.preencode(state, kp.publicKey)
    c.buffer.preencode(state, kp.secretKey)
  },
  encode(state, kp) {
    c.buffer.encode(state, kp.publicKey)
    c.buffer.encode(state, kp.secretKey)
  },
  decode(state) {
    return {
      publicKey: c.buffer.decode(state),
      secretKey: c.buffer.decode(state)
    }
  }
}

const reorgHint = {
  preencode(state, r) {
    c.uint.preencode(state, r.from)
    c.uint.preencode(state, r.to)
    c.uint.preencode(state, r.ancestors)
  },
  encode(state, r) {
    c.uint.encode(state, r.from)
    c.uint.encode(state, r.to)
    c.uint.encode(state, r.ancestors)
  },
  decode(state) {
    return {
      from: c.uint.decode(state),
      to: c.uint.decode(state),
      ancestors: c.uint.decode(state)
    }
  }
}

const reorgHintArray = c.array(reorgHint)

const hints = {
  preencode(state, h) {
    reorgHintArray.preencode(state, h.reorgs)
    c.uint.preencode(state, h.contiguousLength)
  },
  encode(state, h) {
    reorgHintArray.encode(state, h.reorgs)
    c.uint.encode(state, h.contiguousLength)
  },
  decode(state) {
    return {
      reorgs: reorgHintArray.decode(state),
      contiguousLength: state.start < state.end ? c.uint.decode(state) : 0
    }
  }
}

const treeHeader = {
  preencode(state, t) {
    c.uint.preencode(state, t.fork)
    c.uint.preencode(state, t.length)
    c.buffer.preencode(state, t.rootHash)
    c.buffer.preencode(state, t.signature)
  },
  encode(state, t) {
    c.uint.encode(state, t.fork)
    c.uint.encode(state, t.length)
    c.buffer.encode(state, t.rootHash)
    c.buffer.encode(state, t.signature)
  },
  decode(state) {
    return {
      fork: c.uint.decode(state),
      length: c.uint.decode(state),
      rootHash: c.buffer.decode(state),
      signature: c.buffer.decode(state)
    }
  }
}

const types = {
  preencode(state, t) {
    c.string.preencode(state, t.tree)
    c.string.preencode(state, t.bitfield)
    c.string.preencode(state, t.signer)
  },
  encode(state, t) {
    c.string.encode(state, t.tree)
    c.string.encode(state, t.bitfield)
    c.string.encode(state, t.signer)
  },
  decode(state) {
    return {
      tree: c.string.decode(state),
      bitfield: c.string.decode(state),
      signer: c.string.decode(state)
    }
  }
}

const externalHeader = {
  preencode(state, m) {
    c.uint.preencode(state, m.start)
    c.uint.preencode(state, m.length)
  },
  encode(state, m) {
    c.uint.encode(state, m.start)
    c.uint.encode(state, m.length)
  },
  decode(state) {
    return {
      start: c.uint.decode(state),
      length: c.uint.decode(state)
    }
  }
}

const keyValueArray = c.array(keyValue)

oplog.header = {
  preencode(state, h) {
    state.end += 2 // version + flags
    if (h.external) {
      externalHeader.preencode(state, h.external)
      return
    }
    c.fixed32.preencode(state, h.key)
    if (h.manifest) manifest.preencode(state, h.manifest)
    if (h.keyPair) keyPair.preencode(state, h.keyPair)
    keyValueArray.preencode(state, h.userData)
    treeHeader.preencode(state, h.tree)
    hints.preencode(state, h.hints)
  },
  encode(state, h) {
    c.uint.encode(state, 1)
    if (h.external) {
      c.uint.encode(state, 1) // ONLY set the first big for clarity
      externalHeader.encode(state, h.external)
      return
    }
    c.uint.encode(state, (h.manifest ? 2 : 0) | (h.keyPair ? 4 : 0))
    c.fixed32.encode(state, h.key)
    if (h.manifest) manifest.encode(state, h.manifest)
    if (h.keyPair) keyPair.encode(state, h.keyPair)
    keyValueArray.encode(state, h.userData)
    treeHeader.encode(state, h.tree)
    hints.encode(state, h.hints)
  },
  decode(state) {
    const version = c.uint.decode(state)

    if (version > 1) {
      throw INVALID_OPLOG_VERSION('Invalid header version. Expected <= 1, got ' + version)
    }

    if (version === 0) {
      const old = {
        types: types.decode(state),
        userData: keyValueArray.decode(state),
        tree: treeHeader.decode(state),
        signer: keyPair.decode(state),
        hints: hints.decode(state)
      }

      return {
        external: null,
        key: old.signer.publicKey,
        manifest: {
          version: 0,
          hash: old.types.tree,
          allowPatch: false,
          quorum: 1,
          signers: [
            {
              signature: old.types.signer,
              namespace: DEFAULT_NAMESPACE,
              publicKey: old.signer.publicKey
            }
          ],
          prologue: null,
          linked: null,
          userData: null
        },
        keyPair: old.signer.secretKey ? old.signer : null,
        userData: old.userData,
        tree: old.tree,
        hints: old.hints
      }
    }

    const flags = c.uint.decode(state)

    if (flags & 1) {
      return {
        external: externalHeader.decode(state),
        key: null,
        manifest: null,
        keyPair: null,
        userData: null,
        tree: null,
        hints: null
      }
    }

    return {
      external: null,
      key: c.fixed32.decode(state),
      manifest: (flags & 2) !== 0 ? manifest.decode(state) : null,
      keyPair: (flags & 4) !== 0 ? keyPair.decode(state) : null,
      userData: keyValueArray.decode(state),
      tree: treeHeader.decode(state),
      hints: hints.decode(state)
    }
  }
}

const uintArray = c.array(c.uint)

const multisigInput = {
  preencode(state, inp) {
    c.uint.preencode(state, inp.signer)
    c.fixed64.preencode(state, inp.signature)
    c.uint.preencode(state, inp.patch)
  },
  encode(state, inp) {
    c.uint.encode(state, inp.signer)
    c.fixed64.encode(state, inp.signature)
    c.uint.encode(state, inp.patch)
  },
  decode(state) {
    return {
      signer: c.uint.decode(state),
      signature: c.fixed64.decode(state),
      patch: c.uint.decode(state)
    }
  }
}

const patchEncodingv0 = {
  preencode(state, n) {
    c.uint.preencode(state, n.start)
    c.uint.preencode(state, n.length)
    uintArray.preencode(state, n.nodes)
  },
  encode(state, n) {
    c.uint.encode(state, n.start)
    c.uint.encode(state, n.length)
    uintArray.encode(state, n.nodes)
  },
  decode(state) {
    return {
      start: c.uint.decode(state),
      length: c.uint.decode(state),
      nodes: uintArray.decode(state)
    }
  }
}

const multisigInputv0 = {
  preencode(state, n) {
    state.end++
    c.uint.preencode(state, n.signer)
    c.fixed64.preencode(state, n.signature)
    if (n.patch) patchEncodingv0.preencode(state, n.patch)
  },
  encode(state, n) {
    c.uint.encode(state, n.patch ? 1 : 0)
    c.uint.encode(state, n.signer)
    c.fixed64.encode(state, n.signature)
    if (n.patch) patchEncodingv0.encode(state, n.patch)
  },
  decode(state) {
    const flags = c.uint.decode(state)
    return {
      signer: c.uint.decode(state),
      signature: c.fixed64.decode(state),
      patch: flags & 1 ? patchEncodingv0.decode(state) : null
    }
  }
}

const multisigInputArrayv0 = c.array(multisigInputv0)
const multisigInputArray = c.array(multisigInput)

const compactNode = {
  preencode(state, n) {
    c.uint.preencode(state, n.index)
    c.uint.preencode(state, n.size)
    c.fixed32.preencode(state, n.hash)
  },
  encode(state, n) {
    c.uint.encode(state, n.index)
    c.uint.encode(state, n.size)
    c.fixed32.encode(state, n.hash)
  },
  decode(state) {
    return {
      index: c.uint.decode(state),
      size: c.uint.decode(state),
      hash: c.fixed32.decode(state)
    }
  }
}

const compactNodeArray = c.array(compactNode)

exports.multiSignaturev0 = {
  preencode(state, s) {
    multisigInputArrayv0.preencode(state, s.proofs)
    compactNodeArray.preencode(state, s.patch)
  },
  encode(state, s) {
    multisigInputArrayv0.encode(state, s.proofs)
    compactNodeArray.encode(state, s.patch)
  },
  decode(state) {
    return {
      proofs: multisigInputArrayv0.decode(state),
      patch: compactNodeArray.decode(state)
    }
  }
}

exports.multiSignature = {
  preencode(state, s) {
    multisigInputArray.preencode(state, s.proofs)
    compactNodeArray.preencode(state, s.patch)
  },
  encode(state, s) {
    multisigInputArray.encode(state, s.proofs)
    compactNodeArray.encode(state, s.patch)
  },
  decode(state) {
    return {
      proofs: multisigInputArray.decode(state),
      patch: compactNodeArray.decode(state)
    }
  }
}
const c = require('compact-encoding')
const b4a = require('b4a')
const flat = require('flat-tree')
const { MerkleTree } = require('./merkle-tree')
const { multiSignature, multiSignaturev0 } = require('./messages')

module.exports = {
  assemblev0,
  assemble,
  inflatev0,
  inflate,
  partialSignature,
  signableLength
}

function inflatev0(data) {
  return c.decode(multiSignaturev0, data)
}

function inflate(data) {
  return c.decode(multiSignature, data)
}

async function partialSignature(
  core,
  signer,
  from,
  to = core.state.length,
  signature = core.state.signature
) {
  if (from > core.state.length) return null
  const nodes = to <= from ? null : await upgradeNodes(core, from, to)

  if (signature.byteLength !== 64) {
    signature = c.decode(multiSignature, signature).proofs[0].signature
  }

  return {
    signer,
    signature,
    patch: nodes ? to - from : 0,
    nodes
  }
}

async function upgradeNodes(core, from, to) {
  const rx = core.state.storage.read()
  let p = null
  try {
    p = await MerkleTree.proof(core.state, rx, { upgrade: { start: from, length: to - from } })
  } catch (err) {
    rx.destroy()
    throw err
  }
  rx.tryFlush()
  return (await p.settle()).upgrade.nodes
}

function signableLength(lengths, quorum) {
  if (quorum <= 0) quorum = 1
  if (quorum > lengths.length) return 0

  return lengths.sort(cmp)[quorum - 1]
}

function cmp(a, b) {
  return b - a
}

function assemblev0(inputs) {
  const proofs = []
  const patch = []

  for (const u of inputs) {
    proofs.push(compressProof(u, patch))
  }

  return c.encode(multiSignaturev0, { proofs, patch })
}

function assemble(inputs) {
  const proofs = []
  const patch = []
  const seen = new Set()

  for (const u of inputs) {
    if (u.nodes) {
      for (const node of u.nodes) {
        if (seen.has(node.index)) continue
        seen.add(node.index)
        patch.push(node)
      }
    }

    proofs.push({
      signer: u.signer,
      signature: u.signature,
      patch: u.patch
    })
  }

  return c.encode(multiSignature, { proofs, patch })
}

function compareNode(a, b) {
  if (a.index !== b.index) return false
  if (a.size !== b.size) return false
  return b4a.equals(a.hash, b.hash)
}

function compressProof(proof, nodes) {
  return {
    signer: proof.signer,
    signature: proof.signature,
    patch: proof.patch ? compressUpgrade(proof, nodes) : null
  }
}

function compressUpgrade(p, nodes) {
  const u = {
    start: flat.rightSpan(p.nodes[p.nodes.length - 1].index) / 2 + 1,
    length: p.patch,
    nodes: []
  }

  for (const node of p.nodes) {
    let present = false
    for (let i = 0; i < nodes.length; i++) {
      if (!compareNode(nodes[i], node)) continue

      u.nodes.push(i)
      present = true
      break
    }

    if (present) continue
    u.nodes.push(nodes.push(node) - 1)
  }

  return u
}
module.exports = class Mutex {
  constructor() {
    this.locked = false
    this.destroyed = false

    this._destroying = null
    this._destroyError = null
    this._queue = []
    this._enqueue = (resolve, reject) => this._queue.push([resolve, reject])
  }

  idle() {
    return this._queue.length === 0 && this.locked === false
  }

  lock() {
    if (this.destroyed) {
      return Promise.reject(this._destroyError || new Error('Mutex has been destroyed'))
    }
    if (this.locked) return new Promise(this._enqueue)
    this.locked = true
    return Promise.resolve()
  }

  unlock() {
    if (!this._queue.length) {
      this.locked = false
      return
    }
    this._queue.shift()[0]()
  }

  destroy(err) {
    if (!this._destroying) {
      this._destroying = this.locked ? this.lock().catch(() => {}) : Promise.resolve()
    }

    this.destroyed = true
    if (err) this._destroyError = err

    if (err) {
      while (this._queue.length) this._queue.shift()[1](err)
    }

    return this._destroying
  }
}
const FIFO = require('fast-fifo')

module.exports = class ReceiverQueue {
  constructor() {
    this.queue = new FIFO()
    this.priority = []
    this.requests = new Map()
    this.length = 0
  }

  push(req) {
    // TODO: use a heap at some point if we wanna support multiple prios
    if (req.priority > 0) this.priority.push(req)
    else this.queue.push(req)

    this.requests.set(req.id, req)
    this.length++
  }

  shift() {
    while (this.priority.length > 0) {
      const msg = this.priority.pop()
      const req = this._processRequest(msg)
      if (req !== null) return req
    }

    while (this.queue.length > 0) {
      const msg = this.queue.shift()
      const req = this._processRequest(msg)
      if (req !== null) return req
    }

    return null
  }

  _processRequest(req) {
    if (req.block || req.hash || req.seek || req.upgrade || req.manifest) {
      this.requests.delete(req.id)
      this.length--
      return req
    }

    return null
  }

  clear() {
    this.queue.clear()
    this.priority = []
    this.length = 0
    this.requests.clear()
  }

  delete(id) {
    const req = this.requests.get(id)
    if (!req) return

    req.block = null
    req.hash = null
    req.seek = null
    req.upgrade = null
    req.manifest = false

    this.requests.delete(id)
    this.length--

    if (this.length === 0) {
      this.queue.clear()
      this.priority = []
    }
  }
}
const BigSparseArray = require('big-sparse-array')
const quickbit = require('./compat').quickbit

const BITS_PER_PAGE = 32768
const BYTES_PER_PAGE = BITS_PER_PAGE / 8
const WORDS_PER_PAGE = BYTES_PER_PAGE / 4
const BITS_PER_SEGMENT = 2097152
const BYTES_PER_SEGMENT = BITS_PER_SEGMENT / 8
const PAGES_PER_SEGMENT = BITS_PER_SEGMENT / BITS_PER_PAGE

class RemoteBitfieldPage {
  constructor(index, bitfield, segment) {
    this.index = index
    this.offset = index * BYTES_PER_PAGE - segment.offset
    this.bitfield = bitfield
    this.segment = segment

    segment.add(this)
  }

  get tree() {
    return this.segment.tree
  }

  get(index) {
    return quickbit.get(this.bitfield, index)
  }

  set(index, val) {
    if (quickbit.set(this.bitfield, index, val)) {
      this.tree.update(this.offset * 8 + index)
    }
  }

  setRange(start, end, val) {
    quickbit.fill(this.bitfield, val, start, end)

    let i = Math.floor(start / 128)
    const n = i + Math.ceil((end - start) / 128)

    while (i <= n) this.tree.update(this.offset * 8 + i++ * 128)
  }

  findFirst(val, position) {
    return quickbit.findFirst(this.bitfield, val, position)
  }

  findLast(val, position) {
    return quickbit.findLast(this.bitfield, val, position)
  }

  insert(start, bitfield) {
    this.bitfield.set(bitfield, start / 32)
    this.segment.refresh()
  }

  clear(start, bitfield) {
    quickbit.clear(this.bitfield, { field: bitfield, offset: start })
  }
}

class RemoteBitfieldSegment {
  constructor(index) {
    this.index = index
    this.offset = index * BYTES_PER_SEGMENT
    this.tree = quickbit.Index.from([], BYTES_PER_SEGMENT)
    this.pages = new Array(PAGES_PER_SEGMENT)
    this.pagesLength = 0
  }

  get chunks() {
    return this.tree.chunks
  }

  refresh() {
    this.tree = quickbit.Index.from(this.tree.chunks, BYTES_PER_SEGMENT)
  }

  add(page) {
    const pageIndex = page.index - this.index * PAGES_PER_SEGMENT
    if (pageIndex >= this.pagesLength) this.pagesLength = pageIndex + 1

    this.pages[pageIndex] = page

    const chunk = { field: page.bitfield, offset: page.offset }

    this.chunks.push(chunk)

    for (let i = this.chunks.length - 2; i >= 0; i--) {
      const prev = this.chunks[i]
      if (prev.offset <= chunk.offset) break
      this.chunks[i] = chunk
      this.chunks[i + 1] = prev
    }
  }

  findFirst(val, position) {
    position = this.tree.skipFirst(!val, position)

    let j = position & (BITS_PER_PAGE - 1)
    let i = (position - j) / BITS_PER_PAGE

    if (i >= PAGES_PER_SEGMENT) return -1

    while (i < this.pagesLength) {
      const p = this.pages[i]

      let index = -1

      if (p) index = p.findFirst(val, j)
      else if (!val) index = j

      if (index !== -1) return i * BITS_PER_PAGE + index

      j = 0
      i++
    }

    return val || this.pagesLength === PAGES_PER_SEGMENT ? -1 : this.pagesLength * BITS_PER_PAGE
  }

  findLast(val, position) {
    position = this.tree.skipLast(!val, position)

    let j = position & (BITS_PER_PAGE - 1)
    let i = (position - j) / BITS_PER_PAGE

    if (i >= PAGES_PER_SEGMENT) return -1

    while (i >= 0) {
      const p = this.pages[i]

      let index = -1

      if (p) index = p.findLast(val, j)
      else if (!val) index = j

      if (index !== -1) return i * BITS_PER_PAGE + index

      j = BITS_PER_PAGE - 1
      i--
    }

    return -1
  }
}

module.exports = class RemoteBitfield {
  static BITS_PER_PAGE = BITS_PER_PAGE
  static BITS_PER_SEGMENT = BITS_PER_SEGMENT

  constructor() {
    this._pages = new BigSparseArray()
    this._segments = new BigSparseArray()
    this._maxSegments = 0
  }

  getBitfield(index) {
    const j = index & (BITS_PER_PAGE - 1)
    const i = (index - j) / BITS_PER_PAGE

    const p = this._pages.get(i)
    return p || null
  }

  get(index) {
    const j = index & (BITS_PER_PAGE - 1)
    const i = (index - j) / BITS_PER_PAGE

    const p = this._pages.get(i)

    return p ? p.get(j) : false
  }

  set(index, val) {
    const j = index & (BITS_PER_PAGE - 1)
    const i = (index - j) / BITS_PER_PAGE

    let p = this._pages.get(i)

    if (!p && val) {
      const k = Math.floor(i / PAGES_PER_SEGMENT)
      const s = this._segments.get(k) || this._segments.set(k, new RemoteBitfieldSegment(k))
      if (this._maxSegments <= k) this._maxSegments = k + 1

      p = this._pages.set(i, new RemoteBitfieldPage(i, new Uint32Array(WORDS_PER_PAGE), s))
    }

    if (p) p.set(j, val)
  }

  setRange(start, end, val) {
    let j = start & (BITS_PER_PAGE - 1)
    let i = (start - j) / BITS_PER_PAGE

    while (start < end) {
      let p = this._pages.get(i)

      if (!p && val) {
        const k = Math.floor(i / PAGES_PER_SEGMENT)
        const s = this._segments.get(k) || this._segments.set(k, new RemoteBitfieldSegment(k))
        if (this._maxSegments <= k) this._maxSegments = k + 1

        p = this._pages.set(i, new RemoteBitfieldPage(i, new Uint32Array(WORDS_PER_PAGE), s))
      }

      const offset = i * BITS_PER_PAGE
      const last = Math.min(end - offset, BITS_PER_PAGE)
      const range = last - j

      if (p) p.setRange(j, last, val)

      j = 0
      i++
      start += range
    }
  }

  findFirst(val, position) {
    let j = position & (BITS_PER_SEGMENT - 1)
    let i = (position - j) / BITS_PER_SEGMENT

    while (i < this._maxSegments) {
      const s = this._segments.get(i)

      let index = -1

      if (s) index = s.findFirst(val, j)
      else if (!val) index = j

      if (index !== -1) return i * BITS_PER_SEGMENT + index

      j = 0
      i++
    }

    // For the val === false case, we always return at least
    // the 'position', also if nothing was found
    return val ? -1 : Math.max(position, this._maxSegments * BITS_PER_SEGMENT)
  }

  firstSet(position) {
    return this.findFirst(true, position)
  }

  firstUnset(position) {
    return this.findFirst(false, position)
  }

  findLast(val, position) {
    let j = position & (BITS_PER_SEGMENT - 1)
    let i = (position - j) / BITS_PER_SEGMENT

    while (i >= 0) {
      const s = this._segments.get(i)

      let index = -1

      if (s) index = s.findLast(val, j)
      else if (!val) index = j

      if (index !== -1) return i * BITS_PER_SEGMENT + index

      j = BITS_PER_SEGMENT - 1
      i--
    }

    return -1
  }

  lastSet(position) {
    return this.findLast(true, position)
  }

  lastUnset(position) {
    return this.findLast(false, position)
  }

  insert(start, bitfield) {
    if (start % 32 !== 0) return false

    let length = bitfield.byteLength * 8

    let j = start & (BITS_PER_PAGE - 1)
    let i = (start - j) / BITS_PER_PAGE

    while (length > 0) {
      let p = this._pages.get(i)

      if (!p) {
        const k = Math.floor(i / PAGES_PER_SEGMENT)
        const s = this._segments.get(k) || this._segments.set(k, new RemoteBitfieldSegment(k))
        if (this._maxSegments <= k) this._maxSegments = k + 1

        p = this._pages.set(i, new RemoteBitfieldPage(i, new Uint32Array(WORDS_PER_PAGE), s))
      }

      const end = Math.min(j + length, BITS_PER_PAGE)
      const range = end - j

      p.insert(j, bitfield.subarray(0, range / 32))

      bitfield = bitfield.subarray(range / 32)

      j = 0
      i++
      length -= range
    }

    return true
  }

  clear(start, bitfield) {
    if (start % 32 !== 0) return false

    let length = bitfield.byteLength * 8

    let j = start & (BITS_PER_PAGE - 1)
    let i = (start - j) / BITS_PER_PAGE

    while (length > 0) {
      let p = this._pages.get(i)

      if (!p) {
        const k = Math.floor(i / PAGES_PER_SEGMENT)
        const s = this._segments.get(k) || this._segments.set(k, new RemoteBitfieldSegment(k))
        if (this._maxSegments <= k) this._maxSegments = k + 1

        p = this._pages.set(i, new RemoteBitfieldPage(i, new Uint32Array(WORDS_PER_PAGE), s))
      }

      const end = Math.min(j + length, BITS_PER_PAGE)
      const range = end - j

      p.clear(j, bitfield.subarray(0, range / 32))

      bitfield = bitfield.subarray(range / 32)

      j = 0
      i++
      length -= range
    }

    return true
  }
}
/* DEV DOCS
  Every hypercore has one Replicator object managing its connections to other peers.
  There is one Peer object per peer connected to the Hypercore.
  Hypercores do not know about other hypercores, so when a peer is connected to multiple cores, there exists one Peer object per core.

  Hypercore indicates block should be downloaded through methods like Replicator.addRange or Replicator.addBlock
  Hypercore calls Replicator.updateActivity every time a hypercore session opens/closes
  Replicator.updateActivity ensures the Hypercore is downloading blocks as expected
  Replicator keeps track of:
    - Which blocks need to be downloaded (Replicator._blocks)
    - Which blocks currently have inflight requests (Replicator._inflight)

  Blocks are requested from remote peers by Peer objects. The flow is:
    - The replicator's updatePeer method gets called
    - The replicator detects whether the Peer can accept more requests (for example by checking if it's maxed out on inflight blocks)
    - The replicator then tells the Peer what to request (e.g. Peer_requestRange or Peer._requestBlock methods)

  The Peer object is responsible for tracking
    - Which blocks does the Peer have available (tracked in remoteBitfield)
    - Which blocks are you actively looking for from this peer (tracked in missingBlocks)
    - How many blocks are currently inflight (tracked in inflight)
  The Peer uses this information to decide which blocks to request from the peer in response to _requestRange requests and the like.
*/

const b4a = require('b4a')
const safetyCatch = require('safety-catch')
const RandomIterator = require('random-array-iterator')
const flatTree = require('flat-tree')
const Mutex = require('./mutex')
const ReceiverQueue = require('./receiver-queue')
const HotswapQueue = require('./hotswap-queue')
const RemoteBitfield = require('./remote-bitfield')
const { MerkleTree } = require('./merkle-tree')
const w = require('./wants')
const { LocalWants, RemoteWants } = w
const {
  REQUEST_CANCELLED,
  REQUEST_TIMEOUT,
  INVALID_CAPABILITY,
  SNAPSHOT_NOT_AVAILABLE,
  ASSERTION
} = require('hypercore-errors')
const m = require('./messages')
const caps = require('./caps')

const DEFAULT_MAX_INFLIGHT = [16, 512]
const SCALE_LATENCY = 50
const NOT_DOWNLOADING_SLACK = (20000 + Math.random() * 20000) | 0
const MAX_PEERS_UPGRADE = 3
const LAST_BLOCKS = 256
const MAX_RANGES = 64
const MIN_PEER_UPDATE_ALL = 40

const NOT_AVAILABLE = 1
const INVALID_REQUEST = 2
const MAX_INVALID_REQUESTS = 64
const MAX_BACKOFFS = MAX_INVALID_REQUESTS / 2

const PRIORITY = {
  NORMAL: 0,
  HIGH: 1,
  VERY_HIGH: 2,
  CANCELLED: 255 // reserved to mark cancellation
}

class Attachable {
  constructor(replicator) {
    this.replicator = replicator
    this.resolved = false
    this.processing = false
    this.refs = []
    this.wants = null
  }

  addWant(w) {
    if (this.wants === null) this.wants = new Set()
    this.wants.add(w)
  }

  removeWant(w) {
    if (this.wants === null) return
    this.wants.delete(w)
  }

  attach(session) {
    const r = {
      context: this,
      session,
      sindex: 0,
      rindex: 0,
      snapshot: true,
      resolve: null,
      reject: null,
      promise: null,
      timeout: null
    }

    r.sindex = session.push(r) - 1
    r.rindex = this.refs.push(r) - 1
    r.promise = new Promise((resolve, reject) => {
      r.resolve = resolve
      r.reject = reject
    })

    return r
  }

  detach(r, err = null) {
    if (r.context !== this) return false

    this._detach(r)
    this._cancel(r, err)
    this.gc()

    return true
  }

  _detach(r) {
    const rh = this.refs.pop()
    const sh = r.session.pop()

    if (r.rindex < this.refs.length) this.refs[(rh.rindex = r.rindex)] = rh
    if (r.sindex < r.session.length) r.session[(sh.sindex = r.sindex)] = sh

    destroyRequestTimeout(r)
    r.context = null

    return r
  }

  gc() {
    if (this.refs.length > 0 || this.processing) return
    this._unref()

    if (this.wants === null) return

    const wants = this.wants
    this.wants = null

    let update = null

    for (const w of wants) {
      if (w.any) {
        const removed = w.wants.removeAnyRange(w.start, w.length, this)
        if (!removed) continue
        if (update === null) update = new Set()
        update.add(w.wants.peer)
        // TODO: should also use a free list for simplicity
        w.wants.peer.wireUnwant.send(removed)
        continue
      }

      if (w.wants.removeBatch(w.start, this)) {
        if (update === null) update = new Set()
        update.add(w.wants.peer)
      }
    }

    if (update === null) return
    for (const peer of update) {
      this.replicator.updatePeer(peer)
    }
  }

  processed() {
    this.processing = false
    this.gc()
  }

  _cancel(r, err) {
    r.reject(err || REQUEST_CANCELLED())
  }

  _unref() {
    // overwrite me
  }

  resolve(val) {
    this.resolved = true
    while (this.refs.length > 0) {
      this._detach(this.refs[this.refs.length - 1]).resolve(val)
    }
    this.gc()
  }

  reject(err) {
    this.resolved = true
    while (this.refs.length > 0) {
      this._detach(this.refs[this.refs.length - 1]).reject(err)
    }
    this.gc()
  }

  setTimeout(r, ms) {
    destroyRequestTimeout(r)
    r.timeout = setTimeout(onrequesttimeout, ms, r)
  }
}

class BlockRequest extends Attachable {
  constructor(replicator, tracker, index, priority, force) {
    super(replicator)

    this.index = index
    this.priority = priority
    this.inflight = []
    this.queued = false
    this.hotswap = null
    this.tracker = tracker
    this.force = force
  }

  _unref() {
    if (this.resolved) return
    this.queued = false

    for (const req of this.inflight) {
      req.peer._cancelRequest(req)
    }

    this.tracker.remove(this.index)
    removeHotswap(this)
  }
}

class RangeBatchRequest extends Attachable {
  constructor(replicator, batch) {
    super(replicator)

    this.batch = batch
    this.index = 0
  }
}

class RangeRequest extends Attachable {
  constructor(replicator, ranges, start, end, linear, blocks) {
    super(replicator)

    this.start = start
    this.end = end
    this.linear = linear
    this.blocks = blocks
    this.ranges = ranges
    this.batches = []

    ranges.push(this)

    if (this.end === -1) {
      this.replicator._alwaysLatestBlock++
    }

    // As passed by the user, immut
    this.userStart = start
    this.userEnd = end
  }

  addBatch(batch) {
    for (let i = 0; i < this.batches.length; i++) {
      const b = this.batches[i]
      if (b.batch === batch) return false
    }
    const b = new RangeBatchRequest(this.replicator, batch)
    b.index = this.batches.length
    this.batches.push(b)
    return true
  }

  removeBatch(batch) {
    const head = this.batches.pop()

    if (head !== batch) {
      head.index = batch.index
      this.batches[head.index] = head
    }

    batch.gc()
  }

  _unref() {
    const rangeIndex = this.ranges.indexOf(this)
    if (rangeIndex === -1) return

    const h = this.ranges.pop()
    if (h !== this) {
      this.ranges[rangeIndex] = h
    }

    if (!this.resolved && this.replicator._updatesRunning) {
      this.replicator._updatesQueued = true
    }

    if (this.end === -1) {
      this.replicator._alwaysLatestBlock--
    }

    while (this.batches.length) {
      this.batches.pop().gc()
    }
  }

  _cancel(r) {
    r.resolve(false)
  }
}

class UpgradeRequest extends Attachable {
  constructor(replicator, fork, length) {
    super(replicator)

    this.fork = fork
    this.length = length
    this.inflight = []
  }

  _unref() {
    if (this.resolved) return
    if (this.replicator.eagerUpgrade === true || this.inflight.length > 0) return
    this.replicator._upgrade = null
  }

  _cancel(r) {
    r.resolve(false)
  }
}

class SeekRequest extends Attachable {
  constructor(replicator, seeks, seeker) {
    super(replicator)

    this.seeker = seeker
    this.inflight = []
    this.seeks = seeks
  }

  _unref() {
    if (this.resolved) return
    if (this.inflight.length > 0) return
    const i = this.seeks.indexOf(this)
    if (i === -1) return
    const h = this.seeks.pop()
    if (i < this.seeks.length) this.seeks[i] = h
  }
}

class ForkRequest extends Attachable {
  constructor(replicator, fork) {
    super(replicator)
    this.fork = fork
    this.inflight = []
    this.batch = null
  }
}

class InflightTracker {
  constructor() {
    this._requests = []
    this._free = []
    this._active = 0
  }

  get idle() {
    return this._active === 0
  }

  *[Symbol.iterator]() {
    for (const req of this._requests) {
      if (req !== null) yield req
    }
  }

  add(req) {
    const id = this._free.length ? this._free.pop() : this._requests.push(null)
    req.id = id
    this._requests[id - 1] = req
    this._active++
    return req
  }

  get(id) {
    return id <= this._requests.length ? this._requests[id - 1] : null
  }

  remove(id, roundtrip) {
    if (id > this._requests.length) return
    if (this._requests[id - 1]) this._active--
    this._requests[id - 1] = null
    if (roundtrip === true) this._free.push(id)
  }

  reusable(id) {
    this._free.push(id)
  }
}

class BlockTracker {
  constructor(replicator) {
    this._replicator = replicator
    this._map = new Map()
  }

  [Symbol.iterator]() {
    return this._map.values()
  }

  isEmpty() {
    return this._map.size === 0
  }

  has(index) {
    return this._map.has(index)
  }

  get(index) {
    return this._map.get(index) || null
  }

  add(index, priority, force) {
    let b = this._map.get(index)
    if (b) return b

    b = new BlockRequest(this._replicator, this, index, priority, force)
    this._map.set(index, b)

    return b
  }

  remove(index) {
    const b = this.get(index)
    this._map.delete(index)
    return b
  }
}

class RoundtripQueue {
  constructor() {
    this.queue = []
    this.tick = 0
  }

  clear() {
    const ids = new Array(this.queue.length)
    for (let i = 0; i < ids.length; i++) {
      ids[i] = this.queue[i][1]
    }

    this.queue = []

    return ids
  }

  add(id) {
    this.queue.push([++this.tick, id])
  }

  flush(tick) {
    let flushed = null

    for (let i = 0; i < this.queue.length; i++) {
      if (this.queue[i][0] > tick) break
      if (flushed === null) flushed = []
      flushed.push(this.queue[i][1])
    }

    if (flushed !== null) this.queue.splice(0, flushed.length)
    return flushed
  }
}

class ProofRequest {
  constructor(msg, proof, block, manifest) {
    this.msg = msg
    this.proof = proof
    this.block = block
    this.manifest = manifest
  }

  async fulfill() {
    if (this.proof === null) return null

    const [proof, block] = await Promise.all([this.proof.settle(), this.block])

    if (this.manifest) proof.manifest = this.manifest
    if (!block && proof.block) return null

    if (block) proof.block.value = block
    return proof
  }
}

class Peer {
  constructor(replicator, protomux, channel, inflightRange) {
    this.core = replicator.core
    this.replicator = replicator
    this.stream = protomux.stream
    this.protomux = protomux
    this.remotePublicKey = this.stream.remotePublicKey
    this.remoteSupportsSeeks = false
    this.inflightRange = inflightRange
    this.fullyDownloadedSignaled = 0

    this.paused = false
    this.removed = false

    this.channel = channel
    this.channel.userData = this

    this.wireSync = this.channel.messages[0]
    this.wireRequest = this.channel.messages[1]
    this.wireCancel = this.channel.messages[2]
    this.wireData = this.channel.messages[3]
    this.wireNoData = this.channel.messages[4]
    this.wireWant = this.channel.messages[5]
    this.wireUnwant = this.channel.messages[6]
    this.wireBitfield = this.channel.messages[7]
    this.wireRange = this.channel.messages[8]
    this.wireExtension = this.channel.messages[9]

    // Same stats as replicator, but for this specific peer
    this.stats = {
      wireSync: { tx: 0, rx: 0 },
      wireRequest: { tx: 0, rx: 0 },
      wireCancel: { tx: 0, rx: 0 },
      wireData: { tx: 0, rx: 0 },
      wireWant: { tx: 0, rx: 0 },
      wireUnwant: { tx: 0, rx: 0 },
      wireBitfield: { tx: 0, rx: 0 },
      wireRange: { tx: 0, rx: 0 },
      wireExtension: { tx: 0, rx: 0 },
      hotswaps: 0,
      invalidData: 0,
      invalidRequests: 0,
      backoffs: 0,
      notAvailableBackoffs: 0
    }

    this.receiverQueue = new ReceiverQueue()
    this.receiverBusy = false

    // most often not used, so made on demand
    this.roundtripQueue = null

    this.inflight = 0
    this.dataProcessing = 0

    this.canUpgrade = true

    this.needsSync = false
    this.syncsProcessing = 0
    this.lastUpgradableLength = 0
    this.lastUpgradableFork = 0

    this._remoteContiguousLength = 0

    // TODO: tweak pipelining so that data sent BEFORE remoteOpened is not cap verified!
    // we might wanna tweak that with some crypto, ie use the cap to encrypt it...
    // or just be aware of that, to only push non leaky data

    this.remoteOpened = false
    this.remoteBitfield = new RemoteBitfield()
    this.missingBlocks = new RemoteBitfield()

    this.pushedLength = 0

    this.remoteFork = 0
    this.remoteLength = 0
    this.remoteCanUpgrade = false
    this.remoteUploading = true
    this.remoteDownloading = true
    this.remoteSynced = false
    this.remoteHasManifest = false
    this.remoteAllowPush = false
    this.remoteRequests = new Map()

    this.segmentsWanted = new Set()
    this.broadcastedNonSparse = false

    this.wants = new LocalWants(this)
    this.remoteWants = new RemoteWants(this)

    this.lengthAcked = 0

    this.extensions = new Map()
    this.lastExtensionSent = ''
    this.lastExtensionRecv = ''

    replicator._ifAvailable++
    replicator._active++
  }

  get remoteContiguousLength() {
    return this.remoteBitfield.findFirst(false, this._remoteContiguousLength)
  }

  getMaxInflight() {
    const stream = this.stream.rawStream
    if (!stream.udx) return Math.min(this.inflightRange[1], this.inflightRange[0] * 3)

    const scale =
      stream.rtt <= SCALE_LATENCY
        ? 1
        : (stream.rtt / SCALE_LATENCY) * Math.min(1, 2 / this.replicator.peers.length)
    return Math.max(
      this.inflightRange[0],
      Math.round(Math.min(this.inflightRange[1], this.inflightRange[0] * scale))
    )
  }

  getMaxHotswapInflight() {
    const inf = this.getMaxInflight()
    return Math.max(16, inf / 2)
  }

  signalUpgrade() {
    if (this._shouldUpdateCanUpgrade() === true) this._updateCanUpgradeAndSync()
    else this.sendSync()
  }

  _markInflight(index) {
    this.missingBlocks.set(index, false)
  }

  broadcastRange(start, length, drop) {
    if (!this.isActive()) return

    if (drop) this._unclearLocalRange(start, length)
    else this._clearLocalRange(start, length)

    const fullyContig = this.core.header.hints.contiguousLength === this.core.state.length

    if (start + LAST_BLOCKS < this.core.state.length && !drop && !fullyContig) {
      if (!this.remoteWants.hasRange(start, length)) return
    }

    let force = false
    if (fullyContig && !drop) {
      start = 0
      length = this.core.state.length

      // Always send the broadcast when we switch from sparse to fully contiguous, so remote knows too
      if (this.fullyDownloadedSignaled < length) {
        this.fullyDownloadedSignaled = length
        force = true
      }
    }

    // TODO: consider also adding early-returns on the drop===true case
    if (!force && !drop) {
      // No need to broadcast if the remote already has this range

      if (this._remoteContiguousLength >= start + length) return

      if (length === 1) {
        if (this.remoteBitfield.get(start)) return
      } else {
        if (this.remoteBitfield.firstUnset(start) >= start + length) return
      }
    }

    this.wireRange.send({
      drop,
      start,
      length
    })
    incrementTx(this.stats.wireRange, this.replicator.stats.wireRange)
  }

  extension(name, message) {
    this.wireExtension.send({ name: name === this.lastExtensionSent ? '' : name, message })
    incrementTx(this.stats.wireExtension, this.replicator.stats.wireExtension)
    this.lastExtensionSent = name
  }

  onextension(message) {
    const name = message.name || this.lastExtensionRecv
    this.lastExtensionRecv = name
    const ext = this.extensions.get(name)
    if (ext) {
      ext._onmessage({ start: 0, end: message.message.byteLength, buffer: message.message }, this)
    }
  }

  sendSync() {
    // Skip syncs if in repair mode
    if (this.core._repairMode) return

    if (this.syncsProcessing !== 0) {
      this.needsSync = true
      return
    }

    if (this.core.state.fork !== this.remoteFork) {
      this.canUpgrade = false
    }

    this.needsSync = false

    this.wireSync.send({
      fork: this.core.state.fork,
      length: this.core.state.length,
      remoteLength: this.core.state.fork === this.remoteFork ? this.remoteLength : 0,
      canUpgrade: this.canUpgrade,
      uploading: true,
      downloading: this.replicator.isDownloading(),
      hasManifest: !!this.core.header.manifest && this.core.compat === false,
      allowPush: this.replicator.isAllowingPush()
    })
    incrementTx(this.stats.wireSync, this.replicator.stats.wireSync)
  }

  onopen({ seeks, capability }) {
    const expected = caps.replicate(
      this.stream.isInitiator === false,
      this.core.key,
      this.stream.handshakeHash
    )

    if (b4a.equals(capability, expected) !== true) {
      // TODO: change this to a rejection instead, less leakage
      throw INVALID_CAPABILITY('Remote sent an invalid replication capability')
    }

    if (this.remoteOpened === true) return
    this.remoteOpened = true
    this.remoteSupportsSeeks = seeks

    this.protomux.cork()

    this.sendSync()

    const contig = Math.min(this.core.state.length, this.core.header.hints.contiguousLength)
    if (contig > 0) {
      this.broadcastRange(0, contig, false)

      if (contig === this.core.state.length) {
        this.broadcastedNonSparse = true
      }
    }

    this.replicator._ifAvailable--
    this.replicator._addPeer(this)

    this.protomux.uncork()

    this.core.checkIfIdle()
  }

  onclose(isRemote) {
    // we might have signalled to the remote that we are done (ie not downloading) and the remote might agree on that
    // if that happens, the channel might be closed by the remote. if so just renegotiate it.
    // TODO: add a CLOSE_REASON to mux to we can make this cleaner...
    const reopen =
      isRemote === true &&
      this.remoteOpened === true &&
      this.remoteDownloading === false &&
      this.remoteUploading === true &&
      this.replicator.downloading === true

    if (this.remoteOpened === false) {
      this.replicator._ifAvailable--
      this.replicator.queueUpdateAll()
      return
    }

    this.remoteOpened = false
    this.removed = true
    this.remoteRequests.clear() // cancel all
    this.receiverQueue.clear()

    if (this.roundtripQueue !== null) {
      for (const id of this.roundtripQueue.clear()) this.replicator._inflight.reusable(id)
    }

    this.replicator._removePeer(this)

    if (reopen) {
      this.replicator._makePeer(this.protomux)
    }
  }

  closeIfIdle() {
    if (this.remoteDownloading === false && this.replicator.isDownloading() === false) {
      // idling, shut it down...
      this.channel.close()
      return true
    }

    return false
  }

  async onsync(msg) {
    const {
      fork,
      length,
      remoteLength,
      canUpgrade,
      uploading,
      downloading,
      hasManifest,
      allowPush
    } = msg

    const lengthChanged = length !== this.remoteLength
    const sameFork = fork === this.core.state.fork

    this.remoteSynced = true
    this.remoteFork = fork
    this.remoteLength = length
    this.remoteCanUpgrade = canUpgrade
    this.remoteUploading = uploading
    this.remoteDownloading = downloading
    this.remoteHasManifest = hasManifest
    this.remoteAllowPush = allowPush

    if (this.closeIfIdle()) return

    this.lengthAcked = sameFork ? remoteLength : 0
    this.syncsProcessing++

    this.replicator._updateFork(this)

    if (this.remoteLength > this.core.state.length && this.lengthAcked === this.core.state.length) {
      if (this.replicator._addUpgradeMaybe() !== null) this._update()
    }

    const upgrade =
      lengthChanged === false || sameFork === false
        ? this.canUpgrade && sameFork
        : await this._canUpgrade(length, fork)

    if (length === this.remoteLength && fork === this.core.state.fork) {
      this.canUpgrade = upgrade
      if (upgrade) {
        this.lastUpgradableFork = fork
        this.lastUpgradableLength = length
      }
    }

    if (--this.syncsProcessing !== 0) return // ie not latest

    if (
      this.needsSync === true ||
      (this.core.state.fork === this.remoteFork && this.core.state.length > this.remoteLength)
    ) {
      this.signalUpgrade()
    }

    this._update()
  }

  _shouldUpdateCanUpgrade() {
    return (
      this.core.state.fork === this.remoteFork &&
      this.core.state.length > this.remoteLength &&
      this.canUpgrade === false &&
      this.syncsProcessing === 0
    )
  }

  async _updateCanUpgradeAndSync() {
    const { length, fork } = this.core.state

    const remoteLength = this.remoteLength
    const remoteFork = this.remoteFork
    const canUpgrade = await this._canUpgrade(this.remoteLength, this.remoteFork)

    if (
      this.syncsProcessing > 0 ||
      length !== this.core.state.length ||
      fork !== this.core.state.fork
    ) {
      return
    }
    if (remoteLength !== this.remoteLength || remoteFork !== this.remoteFork) {
      return
    }
    if (canUpgrade === this.canUpgrade) {
      return
    }

    this.canUpgrade = canUpgrade
    if (canUpgrade) {
      this.lastUpgradableLength = this.remoteLength
      this.lastUpgradableFork = this.remoteFork
    }

    this.sendSync()
  }

  // Safe to call in the background - never fails
  async _canUpgrade(remoteLength, remoteFork) {
    if (remoteFork !== this.core.state.fork) return false

    if (remoteLength === 0) return true
    if (remoteLength >= this.core.state.length) return false

    try {
      // Rely on caching to make sure this is cheap...
      const canUpgrade = await MerkleTree.upgradeable(this.core.state, remoteLength)

      if (remoteFork !== this.core.state.fork) return false

      return canUpgrade
    } catch {
      return false
    }
  }

  async _getProof(batch, msg, pushing) {
    let block = null

    if (msg.block) {
      const index = msg.block.index

      if (!pushing && (msg.fork !== this.core.state.fork || !this.core.bitfield.get(index))) {
        return new ProofRequest(msg, null, null, null)
      }

      block = batch.getBlock(index)
      block.catch(noop)
    }

    const manifest = msg.manifest && !this.core.compat ? this.core.header.manifest : null

    try {
      const proof = await MerkleTree.proof(this.core.state, batch, msg)
      return new ProofRequest(msg, proof, block, manifest)
    } catch (err) {
      batch.destroy()

      this.replicator._oninvalidrequest(err, msg, this)

      if (this.stats.invalidRequests >= MAX_INVALID_REQUESTS) throw err

      await backoff(this.stats.invalidRequests)
      return null
    }
  }

  async onrequest(msg) {
    const size = this.remoteRequests.size
    this.remoteRequests.set(msg.id, msg)

    // if size didnt change -> id overwrite -> old one is deleted, cancel current and re-add
    if (size === this.remoteRequests.size) {
      this._cancel(msg.id)
      this.remoteRequests.set(msg.id, msg)
    }

    if (!this.protomux.drained || this.receiverQueue.length) {
      this.receiverQueue.push(msg)
      return
    }

    if (this.replicator.destroyed) return

    await this._handleRequest(msg)
  }

  oncancel(msg) {
    this._cancel(msg.request)
  }

  _cancel(id) {
    this.remoteRequests.delete(id)
    this.receiverQueue.delete(id)
  }

  ondrain() {
    return this._handleRequests()
  }

  async _handleRequests() {
    if (this.receiverBusy || this.replicator.destroyed) return
    this.receiverBusy = true
    this.protomux.cork()

    while (
      this.remoteOpened &&
      this.protomux.drained &&
      this.receiverQueue.length > 0 &&
      !this.removed &&
      this.core.closing === null
    ) {
      const msg = this.receiverQueue.shift()
      await this._handleRequest(msg)
    }

    this.protomux.uncork()
    this.receiverBusy = false
  }

  async push(index) {
    if (this.core.closing !== null) return
    if (!this.remoteAllowPush) return
    if (!this.remoteDownloading) return
    if (this.core.state.length <= index) return
    if (this.core.state.fork !== this.remoteFork) return
    if (this.remoteBitfield.get(index)) return

    const msg = {
      id: 0,
      fork: this.core.state.fork,
      block: null,
      hash: null,
      seek: null,
      upgrade: null,
      manifest: this.remoteLength === 0,
      priority: 0
    }

    await this.replicator._txLock.lock()
    try {
      const remoteLength = Math.max(this.remoteLength, this.pushedLength)

      msg.block = {
        index,
        nodes: MerkleTree.maxMissingNodes(2 * index, remoteLength)
      }

      if (index >= remoteLength) {
        msg.upgrade = {
          start: remoteLength,
          length: this.core.state.length - remoteLength
        }
      }

      let req = null
      const batch = this.core.storage.read(this.core.state.fork)
      try {
        req = await this._getProof(batch, msg, true)
      } catch (err) {
        this.replicator._oninvalidrequest(err, msg, this)
        return
      }

      if (req === null) return
      batch.tryFlush()

      await this._fulfillRequest(req, true)
    } finally {
      this.replicator._txLock.unlock()
    }
  }

  async _handleRequest(msg) {
    const locked = !!msg.upgrade && this.remoteAllowPush

    if (locked) {
      // ensure contig push proofs
      await this.replicator._txLock.lock()
      const end = msg.upgrade.start + msg.upgrade.length

      if (this.pushedLength < end) {
        msg.upgrade.start = this.pushedLength
        msg.upgrade.length = end - this.pushedLength
      }
    }

    try {
      const batch = this.core.storage.read(this.core.state.fork)

      // TODO: could still be answerable if (index, fork) is an ancestor of the current fork
      const req =
        msg.fork === this.core.state.fork
          ? await this._getProof(batch, msg, false)
          : new ProofRequest(msg, null, null, null)

      if (req === null) {
        this.wireNoData.send({ request: msg.id, reason: INVALID_REQUEST })
        return
      }

      batch.tryFlush()

      await this._fulfillRequest(req, false)
    } finally {
      if (locked) this.replicator._txLock.unlock()
    }
  }

  async _fulfillRequest(req, pushing) {
    if (this.core._repairMode) {
      // if cancelled do not reply
      if (this.remoteRequests.get(req.msg.id) !== req.msg) {
        return
      }

      // sync from now on, so safe to delete from the map
      this.remoteRequests.delete(req.msg.id)

      // If cutting off the fulfillment, ensure promise is caught.
      // While repairing merkle tree nodes can be `nil` so can throw.
      req.fulfill().catch(safetyCatch)
      return
    }

    const proof = await req.fulfill()

    if (!pushing) {
      // if cancelled do not reply
      if (this.remoteRequests.get(req.msg.id) !== req.msg) {
        return
      }

      // sync from now on, so safe to delete from the map
      this.remoteRequests.delete(req.msg.id)
    } else if (!this.remoteAllowPush) {
      // if pushing but remote disabled it, just drop it
      return
    }

    if (!this.isActive() && proof.block !== null) {
      return
    }

    if (proof === null) {
      if (req.msg.manifest && this.core.header.manifest) {
        const manifest = this.core.header.manifest
        this.wireData.send({
          request: req.msg.id,
          fork: this.core.state.fork,
          block: null,
          hash: null,
          seek: null,
          upgrade: null,
          manifest
        })
        incrementTx(this.stats.wireData, this.replicator.stats.wireData)
        return
      }

      this.wireNoData.send({ request: req.msg.id, reason: NOT_AVAILABLE })
      return
    }

    if (proof.block !== null) {
      this.replicator._onupload(proof.block.index, proof.block.value.byteLength, this)
    }

    if (proof.upgrade) {
      const remoteLength = MerkleTree.upgradeLength(proof.upgrade)
      if (remoteLength > this.pushedLength) this.pushedLength = remoteLength
    }

    this.wireData.send({
      request: req.msg.id,
      fork: req.msg.fork,
      block: proof.block,
      hash: proof.hash,
      seek: proof.seek,
      upgrade: proof.upgrade,
      manifest: proof.manifest
    })
    incrementTx(this.stats.wireData, this.replicator.stats.wireData)
  }

  _cancelRequest(req) {
    if (req.priority === PRIORITY.CANCELLED) return
    // mark as cancelled also and avoid re-entry
    req.priority = PRIORITY.CANCELLED

    this.inflight--
    this.replicator._requestDone(req.id, false)

    // clear inflight state
    if (isBlockRequest(req)) this.replicator._unmarkInflight(req.block.index)
    if (isUpgradeRequest(req)) this.replicator._clearInflightUpgrade(req)

    if (this.roundtripQueue === null) this.roundtripQueue = new RoundtripQueue()
    this.roundtripQueue.add(req.id)
    this.wireCancel.send({ request: req.id })
    incrementTx(this.stats.wireCancel, this.replicator.stats.wireCancel)
  }

  _checkIfConflict() {
    this.paused = true

    const length = Math.min(this.core.state.length, this.remoteLength)
    if (length === 0) return // pause and ignore

    this.wireRequest.send({
      id: 0, // TODO: use an more explicit id for this eventually...
      fork: this.remoteFork,
      block: null,
      hash: null,
      seek: null,
      upgrade: {
        start: 0,
        length
      }
    })

    incrementTx(this.stats.wireRequest, this.replicator.stats.wireRequest)
  }

  _unmarkInflightBlockRequest(req, data) {
    if (isBlockRequest(req)) this.replicator._unmarkInflight(req.block.index)
    else if (data.block && !req) this.replicator._unmarkInflight(data.block.index)
  }

  async ondata(data) {
    if (data.request !== 0 && !data.upgrade) return this._handleData(data)

    await this.replicator._rxLock.lock()
    try {
      await this._handleData(data)
    } finally {
      this.replicator._rxLock.unlock()
    }
  }

  async _handleData(data) {
    // always allow a fork conflict proof to be sent
    if (data.request === 0 && data.upgrade && data.upgrade.start === 0) {
      if (await this.core.checkConflict(data, this)) return
      this.paused = false
    }

    const req = data.request > 0 ? this.replicator._inflight.get(data.request) : null
    const reorg = data.fork > this.core.state.fork

    // no push atm, TODO: check if this satisfies another pending request
    // allow reorg pushes tho as those are not written to storage so we'll take all the help we can get
    if (req === null && reorg === false && !this.replicator.allowPush) {
      return
    }

    // Skip accepting data if in repair mode to avoid corruption
    if (this.core._repairMode) {
      this.replicator._onnodata(this, req, NOT_AVAILABLE)
      return
    }

    if (req === null && reorg === false && data.block) {
      // mark this as inflight to avoid parallel requests
      this.replicator._markInflight(data.block.index)
    }

    if (req !== null) {
      if (req.peer !== this) return
      this._onrequestroundtrip(req)
    }

    try {
      if (reorg === true) return await this.replicator._onreorgdata(this, req, data)
    } catch (err) {
      safetyCatch(err)
      this._unmarkInflightBlockRequest(req, data)

      this.paused = true
      this.replicator._oninvaliddata(err, req, data, this)
      return
    }

    this.dataProcessing++
    if (isBlockRequest(req)) this.replicator._markProcessing(req.block.index)

    try {
      if (!this.replicator._matchingRequest(req, data) || !(await this.core.verify(data, this))) {
        this.replicator._onnodata(this, req, 0)
        return
      }
    } catch (err) {
      safetyCatch(err)
      this._unmarkInflightBlockRequest(req, data)

      if (err.code === 'WRITE_FAILED') {
        // For example, we don't want to keep pulling data when storage is full
        // TODO: notify the user somehow
        this.paused = true
        return
      }

      if ((this.core.closing !== null || this.core.closed) && !isCriticalError(err)) return

      if (err.code !== 'INVALID_OPERATION') {
        // might be a fork, verify
        this._checkIfConflict()
      }

      // if push mode, we MIGHT get an occasional bad message
      // no need to mega bail for that. TODO: rate limit instead as a general thing
      if (!this.replicator.allowPush) {
        this.paused = true
      }

      this.replicator._onnodata(this, req, 0)
      this.replicator._oninvaliddata(err, req, data, this)
      return
    } finally {
      if (isBlockRequest(req)) this.replicator._markProcessed(req.block.index)
      this.dataProcessing--
    }

    this.replicator._ondata(this, req, data)

    if (this._shouldUpdateCanUpgrade() === true) {
      this._updateCanUpgradeAndSync()
    }
  }

  onnodata({ request, reason }) {
    const req = request > 0 ? this.replicator._inflight.get(request) : null

    if (req === null || req.peer !== this) {
      this.replicator.queueUpdateAll()
      return
    }

    this._onrequestroundtrip(req)
    this.replicator._onnodata(this, req, reason)
  }

  _onrequestroundtrip(req) {
    if (req.priority === PRIORITY.CANCELLED) return
    // to avoid re-entry we also just mark it as cancelled
    req.priority = PRIORITY.CANCELLED

    this.inflight--
    this.replicator._requestDone(req.id, true)
    if (this.roundtripQueue === null) return
    const flushed = this.roundtripQueue.flush(req.rt)
    if (flushed === null) return
    for (const id of flushed) this.replicator._inflight.reusable(id)
  }

  onwant(range) {
    if (!this.remoteWants.add(range)) return
    this.replicator._onwant(this, range.start, range.length, range.any)
  }

  onunwant(range) {
    this.remoteWants.remove(range)
  }

  onbitfield({ start, bitfield }) {
    if (start < this._remoteContiguousLength) this._remoteContiguousLength = start // bitfield is always the truth
    this.remoteBitfield.insert(start, bitfield)
    this.missingBlocks.insert(start, bitfield)
    this._clearLocalRange(start, bitfield.byteLength * 8)
    this._update()
  }

  _clearLocalRange(start, length) {
    const bitfield = this.core.skipBitfield === null ? this.core.bitfield : this.core.skipBitfield

    if (length === 1) {
      this.missingBlocks.set(start, this._remoteHasBlock(start) && !bitfield.get(start))
      return
    }

    const contig = Math.min(this.core.state.length, this.core.header.hints.contiguousLength)

    if (start + length < contig) {
      this.missingBlocks.setRange(start, contig, false)
      return
    }

    const rem = start & 32767
    if (rem > 0) {
      start -= rem
      length += rem
    }

    const end = start + Math.min(length, this.core.state.length)
    while (start < end) {
      const local = bitfield.getBitfield(start)

      if (local && local.bitfield) {
        this.missingBlocks.clear(start, local.bitfield)
      }

      start += 32768
    }
  }

  _resetMissingBlock(index) {
    const bitfield = this.core.skipBitfield === null ? this.core.bitfield : this.core.skipBitfield
    this.missingBlocks.set(index, this._remoteHasBlock(index) && !bitfield.get(index))
  }

  _unclearLocalRange(start, length) {
    if (length === 1) {
      this._resetMissingBlock(start)
      return
    }

    const rem = start & (RemoteBitfield.BITS_PER_SEGMENT - 1)
    if (rem > 0) {
      start -= rem
      length += rem
    }

    const fixedStart = start

    const end = start + Math.min(length, this.remoteLength)
    while (start < end) {
      const remote = this.remoteBitfield.getBitfield(start)
      if (remote && remote.bitfield) {
        this.missingBlocks.insert(start, remote.bitfield)
      }

      start += RemoteBitfield.BITS_PER_SEGMENT
    }

    this._clearLocalRange(fixedStart, length)
  }

  async onrange({ drop, start, length }) {
    if (this.core.closing !== null) return

    const has = drop === false

    if (drop === true && start < this._remoteContiguousLength) {
      this._remoteContiguousLength = start
    }

    if (start === 0 && drop === false) {
      if (length > this._remoteContiguousLength) {
        this._remoteContiguousLength = length
        if (
          this.remoteFork === this.core.state.fork &&
          length > this.core.header.hints.remoteContiguousLength
        ) {
          this.core.updateRemoteContiguousLength(length)
        }
      }
    } else if (length === 1) {
      const bitfield = this.core.skipBitfield === null ? this.core.bitfield : this.core.skipBitfield
      this.remoteBitfield.set(start, has)
      this.missingBlocks.set(start, has && !bitfield.get(start))
    } else {
      const rangeStart = this.remoteBitfield.findFirst(!has, start)
      const rangeEnd = length + start

      if (rangeStart !== -1 && rangeStart < rangeEnd) {
        this.remoteBitfield.setRange(rangeStart, rangeEnd, has)
        this.missingBlocks.setRange(rangeStart, rangeEnd, has)
        if (has) this._clearLocalRange(rangeStart, rangeEnd - rangeStart)
      }
    }

    if (this.core.hintsChanged) await this.core.flushHints()
    if (drop === false) this._update()

    this.core.emitRemoteContiguousLength()
  }

  onreorghint() {
    // TODO
  }

  _update() {
    // TODO: if this is in a batch or similar it would be better to defer it
    // we could do that with nextTick/microtick mb? (combined with a property on the session to signal read buffer mb)
    this.replicator.updatePeer(this)
  }

  async _onconflict() {
    this.protomux.cork()
    if (this.remoteLength > 0 && this.core.state.fork === this.remoteFork) {
      await this.onrequest({
        id: 0,
        fork: this.core.state.fork,
        block: null,
        hash: null,
        seek: null,
        upgrade: {
          start: 0,
          length: Math.min(this.core.state.length, this.remoteLength)
        }
      })
    }
    this.channel.close()
    this.protomux.uncork()
  }

  _makeRequest(needsUpgrade, priority, minLength) {
    if (needsUpgrade === true && this.replicator._shouldUpgrade(this) === false) {
      return null
    }

    // ensure that the remote has signalled they have the length we request
    if (this.remoteLength < minLength) {
      return null
    }

    if (needsUpgrade === false && this.replicator._autoUpgrade(this) === true) {
      needsUpgrade = true
    }

    return {
      peer: this,
      rt: this.roundtripQueue === null ? 0 : this.roundtripQueue.tick,
      id: 0,
      fork: this.remoteFork,
      block: null,
      hash: null,
      seek: null,
      upgrade:
        needsUpgrade === false
          ? null
          : { start: this.core.state.length, length: this.remoteLength - this.core.state.length },
      // remote manifest check can be removed eventually...
      manifest: this.core.header.manifest === null && this.remoteHasManifest === true,
      priority,
      timestamp: Date.now(),
      elapsed: 0
    }
  }

  _requestManifest() {
    const req = this._makeRequest(false, 0, 0)
    if (req === null) return

    this._send(req)
  }

  _includeLastBlock() {
    if (this.replicator._alwaysLatestBlock === 0) return null

    const index = this.remoteLength - 1

    // only valid if its an OOB get
    if (index < this.core.state.length) return null

    // do the normal checks
    if (!this._remoteHasBlock(index)) return null
    if (!this._canRequest(index, false)) return null

    // atm we only ever do one upgrade request in parallel, if that changes
    // then we should add some check here for inflights to avoid over requesting
    const b = this.replicator._blocks.add(index, PRIORITY.NORMAL, false)
    return b
  }

  _requestUpgrade(u) {
    if (this.replicator.pushOnly) return false

    const req = this._makeRequest(true, 0, 0)
    if (req === null) return false

    const b = this._includeLastBlock()

    if (b) this._sendBlockRequest(req, b)
    else this._send(req)

    return true
  }

  _requestSeek(s) {
    // if replicator is updating the seeks, bail and wait for it to drain
    if (this.replicator._updatingSeeks) return false
    if (this.replicator.pushOnly) return false

    const { length, fork } = this.core.state

    if (fork !== this.remoteFork) return false

    if (s.seeker.start >= length) {
      const req = this._makeRequest(true, 0, 0)

      // We need an upgrade for the seek, if non can be provided, skip
      if (req === null) return false

      req.seek = this.remoteSupportsSeeks
        ? { bytes: s.seeker.bytes, padding: s.seeker.padding }
        : null

      s.inflight.push(req)
      this._send(req)

      return true
    }

    const len = s.seeker.end - s.seeker.start
    const off = s.seeker.start + Math.floor(Math.random() * len)

    for (let i = 0; i < len; i++) {
      let index = off + i
      if (index > s.seeker.end) index -= len

      if (this._remoteHasBlock(index) === false) continue
      if (this.core.bitfield.get(index) === true) continue
      if (!this._canRequest(index, false)) continue

      // Check if this block is currently inflight - if so pick another
      const b = this.replicator._blocks.get(index)
      if (b !== null && b.inflight.length > 0) continue

      // Block is not inflight, but we only want the hash, check if that is inflight
      const h = this.replicator._hashes.add(index, PRIORITY.NORMAL, false)
      if (h.inflight.length > 0) continue

      const req = this._makeRequest(false, h.priority, index + 1)
      if (req === null) continue

      const nodes = flatTree.depth(s.seeker.start + s.seeker.end - 1)

      req.hash = { index: 2 * index, nodes }
      req.seek = this.remoteSupportsSeeks
        ? { bytes: s.seeker.bytes, padding: s.seeker.padding }
        : null

      s.inflight.push(req)
      h.inflight.push(req)
      this._send(req)

      return true
    }

    this._maybeWantAnyRange(s.seeker.start, len, s)
    return false
  }

  _canRequest(index, force) {
    if (!(index >= 0)) throw ASSERTION('bad index to _canRequest: ' + index)

    if (!force && this.replicator.pushOnly) {
      return false
    }

    if (this.remoteLength >= this.core.state.length) {
      return true
    }

    if (index < this.lastUpgradableLength && this.lastUpgradableFork === this.core.state.fork) {
      return true
    }

    if (this.canUpgrade && this.syncsProcessing === 0) {
      return true
    }

    return false
  }

  _remoteHasBlock(index) {
    return index < this._remoteContiguousLength || this.remoteBitfield.get(index) === true
  }

  _sendBlockRequest(req, b) {
    if (this.core._repairMode) return

    req.block = { index: b.index, nodes: 0 }
    this.replicator._markInflight(b.index)

    b.inflight.push(req)
    this.replicator.hotswaps.add(b)
    this._send(req)
  }

  _requestBlock(b) {
    const { length, fork } = this.core.state

    if (this._remoteHasBlock(b.index) === false || fork !== this.remoteFork) {
      if (!this.core.bitfield.get(b.index)) this._maybeWant(b.index, b)
      return false
    }

    if (!this._canRequest(b.index, b.force)) return false

    const req = this._makeRequest(b.index >= length, b.priority, b.index + 1)
    if (req === null) return false

    this._sendBlockRequest(req, b)

    return true
  }

  _maybeWant(index, handle) {
    if (index < this.remoteContiguousLength) return

    const res = this.wants.add(index, handle)
    if (!res) return

    const batch = res.want && res.unwant
    if (batch) this.protomux.cork()

    if (res.unwant) {
      this.wireUnwant.send(res.unwant)
      incrementTx(this.stats.wireUnwant, this.replicator.stats.wireUnwant)
    }
    if (res.want) {
      this.wireWant.send(res.want)
      incrementTx(this.stats.wireWant, this.replicator.stats.wireWant)
    }

    if (batch) this.protomux.uncork()
  }

  _maybeWantAnyRange(start, length, handle) {
    if (handle.wants) {
      for (const w of handle.wants) {
        if ((w.start !== start || w.length !== length) && w.any) {
          const req = this.wants.removeAnyRange(w.start, w.length, handle)
          if (req) this.wireUnwant.send(req)
        }
      }
    }

    if (start + length <= this.remoteContiguousLength) return

    const req = this.wants.addAnyRange(start, length, handle)
    if (req) this.wireWant.send(req)
  }

  _requestRangeBlock(index, length) {
    if (this.core.bitfield.get(index) === true || !this._canRequest(index, false)) return false

    const b = this.replicator._blocks.add(index, PRIORITY.NORMAL, false)
    if (b.inflight.length > 0) {
      this.missingBlocks.set(index, false) // in case we missed some states just set them ondemand, nbd
      return false
    }

    const req = this._makeRequest(index >= length, b.priority, index + 1)

    // If the request cannot be satisfied, dealloc the block request if no one is subscribed to it
    if (req === null) {
      b.gc()
      return false
    }

    this._sendBlockRequest(req, b)

    // Don't think this will ever happen, as the pending queue is drained before the range queue
    // but doesn't hurt to check this explicitly here also.
    if (b.queued) b.queued = false
    return true
  }

  _findNext(i) {
    if (i < this._remoteContiguousLength) {
      if (this.core.skipBitfield === null) this.replicator._openSkipBitfield()
      i = this.core.skipBitfield.findFirst(false, i)
      if (i < this._remoteContiguousLength && i > -1) return i
      i = this._remoteContiguousLength
    }

    return this.missingBlocks.findFirst(true, i)
  }

  _addRangeBatch(r, offset, end) {
    const { WANT_BATCH } = w
    while (offset < end) {
      const next = this.core.bitfield.findFirst(false, offset)
      if (next === -1 || next >= end) return false

      const b = (next - (next & (WANT_BATCH - 1))) / WANT_BATCH

      if (r.addBatch(b)) return true

      offset = (b + 1) * WANT_BATCH
    }

    return false
  }

  _addLastRangeBatch(r, start, end) {
    const { WANT_BATCH } = w

    const next = this.core.bitfield.findLast(false, end - 1)
    if (next === -1 || next < start) return false

    const b = (next - (next & (WANT_BATCH - 1))) / WANT_BATCH

    return r.addBatch(b)
  }

  _populateRangeBatches(r) {
    const { WANT_BATCH } = w
    for (let i = r.batches.length - 1; i >= 0; i--) {
      const b = r.batches[i]
      const minStart = b.batch * WANT_BATCH
      const maxEnd = minStart + WANT_BATCH

      const start = Math.max(r.start, minStart)
      const end = Math.min(r.end === -1 ? maxEnd : r.end, maxEnd)

      if (start < end && this.core.bitfield.hasUnset(start, end - start)) continue

      r.removeBatch(b)
    }

    const end = r.end === -1 ? this.core.state.length : r.end
    if (end <= r.start) return

    // always keep the last batch in the range selected
    if (r.batches.length < 3) this._addLastRangeBatch(r, r.start, end)

    while (r.batches.length < 3) {
      const len = end - r.start
      const off = r.start + (r.linear ? 0 : Math.floor(Math.random() * len))

      if (!this._addRangeBatch(r, off, end) && !this._addRangeBatch(r, r.start, end)) break
    }
  }

  _requestRangeBatch(start, end, length) {
    let tries = this.inflight

    do {
      start = this._findNext(start)
      if (start === -1 || start >= end) break

      if (this._requestRangeBlock(start, length)) return true
      start++
    } while (tries-- > 0)

    return false
  }

  _getValidEnd(maxEnd) {
    // if we can upgrade the remote, or the remote is ahead, then all the remotes blocks are valid
    // otherwise truncate to the last length the remote has acked for us
    const maxLocalLength =
      this.canUpgrade || this.remoteLength >= this.core.state.length
        ? this.core.state.length
        : this.core.state.fork === this.lastUpgradableFork
          ? Math.min(this.lastUpgradableLength, this.core.state.length)
          : 0

    return Math.min(
      maxLocalLength,
      Math.min(maxEnd === -1 ? this.remoteLength : maxEnd, this.remoteLength)
    )
  }

  _requestRange(r) {
    const { WANT_BATCH } = w
    if (this.syncsProcessing > 0) return false

    const { length, fork } = this.core.state

    if (r.blocks) {
      for (let i = r.start; i < r.end; i++) {
        const index = r.blocks[i]
        const has = index < this._remoteContiguousLength || this.missingBlocks.get(index) === true
        if (has === true && this._requestRangeBlock(index, length)) return true
        this._maybeWant(index, r)
      }

      return false
    }

    this._populateRangeBatches(r)

    const end = this._getValidEnd(r.end)

    if (end <= r.start || fork !== this.remoteFork) return false

    for (let i = 0; i < r.batches.length; i++) {
      const b = r.batches[i]

      const batchStart = Math.max(b.batch * WANT_BATCH, r.start)
      const batchEnd = Math.min((b.batch + 1) * WANT_BATCH, end)
      const len = batchEnd - batchStart
      const off = batchStart + (r.linear ? 0 : Math.floor(Math.random() * len))

      if (
        this._requestRangeBatch(off, end, length) ||
        this._requestRangeBatch(batchStart, off, length)
      ) {
        return true
      }

      this._maybeWant(batchStart, b)
    }

    // Here we should consider making an OOB request (ie if the remote says they have a newer block)
    // but the upgrade code already does that, so no big deal for now

    return false
  }

  _requestForkProof(f) {
    if (!this.remoteLength) return
    if (this.replicator.pushOnly) return

    const req = this._makeRequest(false, 0, 0)
    if (req === null) return

    req.upgrade = { start: 0, length: this.remoteLength }
    req.manifest = !this.core.header.manifest

    f.inflight.push(req)
    this._send(req)
  }

  _requestForkRange(f) {
    if (f.fork !== this.remoteFork || f.batch.want === null) return false
    if (this.replicator.pushOnly) return false

    const end = Math.min(f.batch.want.end, this.remoteLength)
    if (end < f.batch.want.start) return false

    const len = end - f.batch.want.start
    const off = f.batch.want.start + Math.floor(Math.random() * len)

    for (let i = 0; i < len; i++) {
      let index = off + i
      if (index >= end) index -= len

      if (this._remoteHasBlock(index) === false) continue

      const req = this._makeRequest(false, 0, 0)
      if (req === null) return false

      req.hash = { index: 2 * index, nodes: f.batch.want.nodes }

      f.inflight.push(req)
      this._send(req)

      return true
    }

    this._maybeWantAnyRange(f.batch.want.start, len, f)
    return false
  }

  isActive() {
    if (this.paused || this.removed || this.core.header.frozen) return false
    return true
  }

  async _send(req) {
    const fork = this.core.state.fork

    this.inflight++
    this.replicator._inflight.add(req)

    if (req.upgrade !== null && req.fork === fork) {
      const u = this.replicator._addUpgrade()
      u.inflight.push(req)
    }

    try {
      if (req.block !== null && req.fork === fork) {
        req.block.nodes = await MerkleTree.missingNodes(
          this.core.state,
          2 * req.block.index,
          this.core.state.length
        )
        if (req.priority === PRIORITY.CANCELLED) return
      }
      if (req.hash !== null && req.fork === fork && req.hash.nodes === 0) {
        req.hash.nodes = await MerkleTree.missingNodes(
          this.core.state,
          req.hash.index,
          this.core.state.length
        )
        if (req.priority === PRIORITY.CANCELLED) return

        // nodes === 0, we already have it, bail
        if (req.hash.nodes === 0 && (req.hash.index & 1) === 0) {
          this.inflight--
          this.replicator._resolveHashLocally(this, req)
          return
        }
      }
    } catch (err) {
      if (this.core.closing !== null) return
      this.stream.destroy(err)
      return
    }

    this.wireRequest.send(req)
    incrementTx(this.stats.wireRequest, this.replicator.stats.wireRequest)
  }
}

module.exports = class Replicator {
  static Peer = Peer // hack to be able to access Peer from outside this module

  constructor(
    core,
    {
      notDownloadingLinger = NOT_DOWNLOADING_SLACK,
      eagerUpgrade = true,
      allowFork = true,
      allowPush = false,
      alwaysLatestBlock = false,
      inflightRange = null
    } = {}
  ) {
    this.core = core
    this.eagerUpgrade = eagerUpgrade
    this.allowFork = allowFork
    this.allowPush = allowPush
    this.ondownloading = null // optional external hook for monitoring downloading status
    this.peers = []
    this.findingPeers = 0 // updatable from the outside
    this.destroyed = false
    this.downloading = false
    this.pushOnly = false
    this.activeSessions = 0

    this.hotswaps = new HotswapQueue()
    this.inflightRange = inflightRange || DEFAULT_MAX_INFLIGHT

    // Note: nodata and unwant not currently tracked
    // tx = transmitted, rx = received
    this.stats = {
      wireSync: { tx: 0, rx: 0 },
      wireRequest: { tx: 0, rx: 0 },
      wireCancel: { tx: 0, rx: 0 },
      wireData: { tx: 0, rx: 0 },
      wireWant: { tx: 0, rx: 0 },
      wireUnwant: { tx: 0, rx: 0 },
      wireBitfield: { tx: 0, rx: 0 },
      wireRange: { tx: 0, rx: 0 },
      wireExtension: { tx: 0, rx: 0 },
      hotswaps: 0,
      invalidData: 0,
      invalidRequests: 0,
      backoffs: 0,
      notAvailableBackoffs: 0
    }

    this._attached = new Set()
    this._inflight = new InflightTracker()
    this._blocks = new BlockTracker(this)
    this._hashes = new BlockTracker(this)

    this._alwaysLatestBlock = alwaysLatestBlock ? 1 : 0
    this._queued = []

    this._seeks = []
    this._upgrade = null
    this._reorgs = []
    this._ranges = []

    this._rxLock = new Mutex()
    this._txLock = new Mutex()

    this._hadPeers = false
    this._active = 0
    this._ifAvailable = 0
    this._updatingSeeks = false
    this._updatesRunning = false
    this._updatesQueued = false
    this._applyingReorg = null
    this._manifestPeer = null
    this._notDownloadingLinger = notDownloadingLinger
    this._notDownloadingTimer = null

    this._updateAllBump = null
    this._updateAllBound = this.updateAll.bind(this)

    const self = this
    this._onstreamclose = onstreamclose

    function onstreamclose() {
      self.detachFrom(this.userData)
    }
  }

  setInflightRange(min, max) {
    const def = this.inflightRange
    this.inflightRange = [min, max]
    for (const peer of this.peers) {
      if (peer.inflightRange !== def) continue
      peer.inflightRange = this.inflightRange
    }
  }

  updateActivity(inc, session) {
    this.activeSessions += inc
    this.setDownloading(this.activeSessions !== 0, session)
  }

  isDownloading() {
    return this.downloading || !this._inflight.idle
  }

  async push(index) {
    const all = []
    for (const peer of this.peers) {
      all.push(peer.push(index))
    }
    await Promise.all(all)
  }

  setAllowPush(allowPush) {
    if (allowPush === this.allowPush) return
    this.allowPush = allowPush
    for (const peer of this.peers) peer.sendSync()
  }

  isAllowingPush() {
    if (!this.allowPush) return false
    if (this._notDownloadingTimer) return false
    return this.downloading
  }

  setPushOnly(pushOnly) {
    if (pushOnly === this.pushOnly) return
    this.pushOnly = pushOnly
  }

  setDownloading(downloading) {
    const allowPush = this.isAllowingPush()
    const linger = this._notDownloadingLinger

    if (this._notDownloadingTimer) {
      clearTimeout(this._notDownloadingTimer)
      this._notDownloadingTimer = null
    }

    if (this.destroyed) return

    if (downloading || linger === 0) {
      if (!this._setDownloadingNow(downloading)) this.signalAllowPush(allowPush)
      return
    }

    this._notDownloadingTimer = setTimeout(setNotDownloadingLater, linger, this)
    if (this._notDownloadingTimer.unref) this._notDownloadingTimer.unref()
    this.signalAllowPush(allowPush)
  }

  signalAllowPush(allowPush) {
    if (allowPush === this.isAllowingPush()) return
    for (const peer of this.peers) peer.signalUpgrade()
  }

  _setDownloadingNow(downloading) {
    this._notDownloadingTimer = null

    if (this.downloading === downloading) return false
    this.downloading = downloading

    if (!downloading && this.isDownloading()) return false

    for (const peer of this.peers) peer.signalUpgrade()

    if (downloading) {
      // restart channel if needed...
      for (const protomux of this._attached) {
        if (!protomux.stream.handshakeHash) continue
        if (protomux.opened({ protocol: 'hypercore/alpha', id: this.core.discoveryKey })) continue
        this._makePeer(protomux, true)
      }
    } else {
      for (const peer of this.peers) peer.closeIfIdle()
    }

    if (this.ondownloading !== null && downloading) this.ondownloading()
    return true
  }

  cork() {
    for (const peer of this.peers) peer.protomux.cork()
  }

  uncork() {
    for (const peer of this.peers) peer.protomux.uncork()
  }

  // Called externally when a range of new blocks has been processed/removed
  onhave(start, length, drop = false) {
    for (const peer of this.peers) peer.broadcastRange(start, length, drop)
  }

  // Called externally when a truncation upgrade has been processed
  ontruncate(newLength, truncated) {
    const notify = []

    for (const blk of this._blocks) {
      if (blk.index < newLength) continue
      notify.push(blk)
    }

    for (const blk of notify) {
      for (const r of blk.refs) {
        if (r.snapshot === false) continue
        blk.detach(r, SNAPSHOT_NOT_AVAILABLE())
      }
    }

    for (const peer of this.peers) {
      peer.fullyDownloadedSignaled = Math.min(newLength, peer.fullyDownloadedSignaled)
      peer._unclearLocalRange(newLength, truncated)
    }
  }

  // Called externally when a upgrade has been processed
  onupgrade() {
    for (const peer of this.peers) peer.signalUpgrade()
    if (this._blocks.isEmpty() === false) this._resolveBlocksLocally()
    if (this._upgrade !== null) this._resolveUpgradeRequest(null)
    if (!this._blocks.isEmpty() || this._ranges.length !== 0 || this._seeks.length !== 0) {
      this._updateNonPrimary(true)
    }
  }

  // Called externally when a conflict has been detected and verified
  async onconflict() {
    const all = []
    for (const peer of this.peers) {
      all.push(peer._onconflict())
    }
    await Promise.allSettled(all)
  }

  async applyPendingReorg() {
    if (this._applyingReorg !== null) {
      await this._applyingReorg
      return true
    }

    for (let i = this._reorgs.length - 1; i >= 0; i--) {
      const f = this._reorgs[i]
      if (f.batch !== null && f.batch.finished) {
        await this._applyReorg(f)
        return true
      }
    }

    return false
  }

  addUpgrade(session) {
    if (this._upgrade !== null) {
      const ref = this._upgrade.attach(session)
      this._checkUpgradeIfAvailable()
      return ref
    }

    const ref = this._addUpgrade().attach(session)

    this.queueUpdateAll()

    return ref
  }

  addBlock(session, index, force) {
    const b = this._blocks.add(index, PRIORITY.HIGH, force)
    const ref = b.attach(session)

    this._queueBlock(b)
    this.queueUpdateAll()

    return ref
  }

  addSeek(session, seeker) {
    const s = new SeekRequest(this, this._seeks, seeker)
    const ref = s.attach(session)

    this._seeks.push(s)
    this.queueUpdateAll()

    return ref
  }

  addRange(
    session,
    { start = 0, end = -1, length = toLength(start, end), blocks = null, linear = false } = {}
  ) {
    if (blocks !== null) {
      // if using blocks, start, end just acts as frames around the blocks array
      start = 0
      end = length = blocks.length
    }

    const r = new RangeRequest(
      this,
      this._ranges,
      start,
      length === -1 ? -1 : start + length,
      linear,
      blocks
    )

    const ref = r.attach(session)

    // Trigger this to see if this is already resolved...
    // Also auto compresses the range based on local bitfield
    clampRange(this.core, r)

    if (r.end !== -1 && r.start >= r.end) {
      this._resolveRangeRequest(r)
      return ref
    }

    this.queueUpdateAll()

    return ref
  }

  cancel(ref) {
    ref.context.detach(ref, null)
  }

  static clearRequests(session, err) {
    if (session.length === 0) return
    if (session.length === 1 && session[0] === null) return

    const updated = new Set()

    while (session.length > 0) {
      const ref = session[session.length - 1]
      updated.add(ref.context.replicator)
      ref.context.detach(ref, err)
    }

    for (const replicator of updated) {
      replicator.queueUpdateAll()
    }
  }

  clearRequests(session, err = null) {
    let cleared = false
    while (session.length > 0) {
      const ref = session[session.length - 1]
      ref.context.detach(ref, err)
      cleared = true
    }

    if (cleared) this.queueUpdateAll()
  }

  _matchingRequest(req, data) {
    if (this.allowPush) {
      return true
    }
    if (data.block !== null && (req.block === null || req.block.index !== data.block.index)) {
      return false
    }
    if (data.hash !== null && (req.hash === null || req.hash.index !== data.hash.index)) {
      return false
    }
    if (data.seek !== null && (req.seek === null || req.seek.bytes !== data.seek.bytes)) {
      return false
    }
    if (data.upgrade !== null && req.upgrade === null) {
      return false
    }
    return req.fork === data.fork
  }

  _addUpgradeMaybe() {
    return this.eagerUpgrade === true ? this._addUpgrade() : this._upgrade
  }

  // TODO: this function is OVER called atm, at each updatePeer/updateAll
  // instead its more efficient to only call it when the conditions in here change - ie on sync/add/remove peer
  // Do this when we have more tests.
  _checkUpgradeIfAvailable() {
    if (this._ifAvailable > 0 && this.peers.length < MAX_PEERS_UPGRADE) return
    if (this._upgrade === null || this._upgrade.refs.length === 0) return
    if (this._hadPeers === false && this.findingPeers > 0) return

    const maxPeers = Math.min(this.peers.length, MAX_PEERS_UPGRADE)

    // check if a peer can upgrade us

    for (let i = 0; i < maxPeers; i++) {
      const peer = this.peers[i]

      if (peer.remoteSynced === false) return

      if (this.core.state.length === 0 && peer.remoteLength > 0) return

      if (peer.remoteLength <= this._upgrade.length || peer.remoteFork !== this._upgrade.fork) {
        continue
      }

      if (peer.syncsProcessing > 0) return

      if (peer.lengthAcked !== this.core.state.length && peer.remoteFork === this.core.state.fork) {
        return
      }

      if (peer.remoteCanUpgrade === true) return
    }

    // check if reorgs in progress...

    if (this._applyingReorg !== null) return

    // TODO: we prob should NOT wait for inflight reorgs here, seems better to just resolve the upgrade
    // and then apply the reorg on the next call in case it's slow - needs some testing in practice

    for (let i = 0; i < this._reorgs.length; i++) {
      const r = this._reorgs[i]
      if (r.inflight.length > 0) return
    }

    // if something is inflight, wait for that first
    if (this._upgrade.inflight.length > 0) return

    // nothing to do, indicate no update avail

    const u = this._upgrade
    this._upgrade = null
    u.resolve(false)
  }

  _addUpgrade() {
    if (this._upgrade !== null) return this._upgrade

    // TODO: needs a reorg: true/false flag to indicate if the user requested a reorg
    this._upgrade = new UpgradeRequest(this, this.core.state.fork, this.core.state.length)

    return this._upgrade
  }

  _addReorg(fork, peer) {
    if (this.allowFork === false) return null

    // TODO: eager gc old reorgs from the same peer
    // not super important because they'll get gc'ed when the request finishes
    // but just spam the remote can do ...

    for (const f of this._reorgs) {
      if (f.fork > fork && f.batch !== null) return null
      if (f.fork === fork) return f
    }

    const f = new ForkRequest(this, fork)

    this._reorgs.push(f)

    // maintain sorted by fork
    let i = this._reorgs.length - 1
    while (i > 0 && this._reorgs[i - 1].fork > fork) {
      this._reorgs[i] = this._reorgs[i - 1]
      this._reorgs[--i] = f
    }

    return f
  }

  _shouldUpgrade(peer) {
    if (this._upgrade !== null && this._upgrade.inflight.length > 0) return false
    return (
      peer.remoteCanUpgrade === true &&
      peer.remoteLength > this.core.state.length &&
      peer.lengthAcked === this.core.state.length
    )
  }

  _autoUpgrade(peer) {
    return (
      this._upgrade !== null &&
      peer.remoteFork === this.core.state.fork &&
      this._shouldUpgrade(peer)
    )
  }

  _addPeer(peer) {
    this._hadPeers = true
    this.peers.push(peer)
    this.updatePeer(peer)
    this._onpeerupdate(true, peer)
  }

  _requestDone(id, roundtrip) {
    this._inflight.remove(id, roundtrip)
    if (this.isDownloading() === true) return
    for (const peer of this.peers) peer.signalUpgrade()
  }

  _removePeer(peer) {
    this.peers.splice(this.peers.indexOf(peer), 1)
    peer.wants.destroy()

    if (this._manifestPeer === peer) this._manifestPeer = null

    let inflight = false
    for (const req of this._inflight) {
      if (req.peer !== peer) continue
      inflight = true
      this._inflight.remove(req.id, true)
      this._clearRequest(peer, req)
    }

    this._onpeerupdate(false, peer)
    if (inflight) {
      this.queueUpdateAll()
    } else {
      this._checkUpgradeIfAvailable()
    }
  }

  _queueBlock(b) {
    if (b.inflight.length > 0 || b.queued === true) return
    b.queued = true
    this._queued.push(b)
  }

  _resolveHashLocally(peer, req) {
    this._requestDone(req.id, false)
    this._resolveBlockRequest(this._hashes, req.hash.index / 2, null, req)
    this.updatePeer(peer)
  }

  // Runs in the background - not allowed to throw
  async _resolveBlocksLocally() {
    // TODO: check if fork compat etc. Requires that we pass down truncation info

    const clear = []
    const blocks = []

    const reader = this.core.storage.read()
    for (const b of this._blocks) {
      if (this.core.bitfield.get(b.index) === false) continue
      blocks.push(this._resolveLocalBlock(b, reader, clear))
    }
    reader.tryFlush()

    await Promise.all(blocks)

    if (!clear.length) return

    // Currently the block tracker does not support deletes during iteration, so we make
    // sure to clear them afterwards.
    for (const b of clear) {
      this._blocks.remove(b.index)
      removeHotswap(b)
    }
  }

  async _resolveLocalBlock(b, reader, resolved) {
    try {
      b.resolve(await reader.getBlock(b.index))
    } catch (err) {
      b.reject(err)
      return
    }

    resolved.push(b)
  }

  _resolveBlockRequest(tracker, index, value, req) {
    const b = tracker.remove(index)
    if (b === null) return false

    removeInflight(b.inflight, req)
    removeHotswap(b)
    b.queued = false

    b.resolve(value)

    if (b.inflight.length > 0) {
      // if anything is still inflight, cancel it
      for (let i = b.inflight.length - 1; i >= 0; i--) {
        const req = b.inflight[i]
        req.peer._cancelRequest(req)
      }
    }

    return true
  }

  _resolveUpgradeRequest(req) {
    if (req !== null) removeInflight(this._upgrade.inflight, req)

    if (
      this.core.state.length === this._upgrade.length &&
      this.core.state.fork === this._upgrade.fork
    ) {
      return false
    }

    const u = this._upgrade
    this._upgrade = null
    u.resolve(true)

    return true
  }

  _resolveRangeRequest(req) {
    req.resolve(true)
    req.gc()
  }

  _clearInflightBlock(tracker, req) {
    const isBlock = tracker === this._blocks
    const index = isBlock === true ? req.block.index : req.hash.index / 2
    const b = tracker.get(index)

    if (b === null || removeInflight(b.inflight, req) === false) return

    if (removeHotswap(b) === true && b.inflight.length > 0) {
      this.hotswaps.add(b)
    }

    if (b.refs.length > 0 && isBlock === true) {
      this._queueBlock(b)
      return
    }

    b.gc()
  }

  _clearInflightUpgrade(req) {
    if (this._upgrade === null || removeInflight(this._upgrade.inflight, req) === false) return
    this._upgrade.gc()
  }

  _clearInflightSeeks(req) {
    for (const s of this._seeks) {
      if (removeInflight(s.inflight, req) === false) continue
      s.gc()
    }
  }

  _clearInflightReorgs(req) {
    for (const r of this._reorgs) {
      removeInflight(r.inflight, req)
    }
  }

  _clearOldReorgs(fork) {
    for (let i = 0; i < this._reorgs.length; i++) {
      const f = this._reorgs[i]
      if (f.fork >= fork) continue
      if (i === this._reorgs.length - 1) this._reorgs.pop()
      else this._reorgs[i] = this._reorgs.pop()
      i--
    }
  }

  _updateRanges(index, limit) {
    index = Math.min(index, this._ranges.length - 1)

    let checked = 0
    let resolved = 0
    let updateAll = false
    let remaining = Math.min(limit, this._ranges.length)

    while (remaining-- > 0 && this._ranges.length > 0) {
      if (index >= this._ranges.length) index = 0

      const r = this._ranges[index]

      clampRange(this.core, r)
      checked++

      if (r.end !== -1 && r.start >= r.end) {
        this._resolveRangeRequest(r)
        resolved++
        // Crossing into the capped window exposes another range to peer scheduling.
        if (this._ranges.length === MAX_RANGES) updateAll = true
      } else {
        index++
      }
    }

    index = this._ranges.length === 0 ? 0 : index % this._ranges.length

    return { checked, resolved, index, updateAll }
  }

  // "slow" updates here - async but not allowed to ever throw
  async _updateNonPrimary(updateAll) {
    this._updatesQueued = true
    if (this._updatesRunning) {
      if (this._inflight.idle || updateAll) this.queueUpdateAll()
      return
    }

    this._updatesRunning = true

    while (this._updatesQueued) {
      this._updatesQueued = false

      let checkedSinceResolve = 0
      let rangeIndex = 0
      const drain = this._inflight.idle || updateAll
      const checkedSeeks = new Set()

      while (true) {
        const limit = Math.min(MAX_RANGES, this._ranges.length - checkedSinceResolve)
        const {
          checked,
          resolved,
          index,
          updateAll: resultUpdateAll
        } = this._updateRanges(rangeIndex, limit)
        rangeIndex = index

        if (resultUpdateAll) updateAll = true

        if (resolved > 0) checkedSinceResolve = 0
        else checkedSinceResolve += checked

        const continueRanges = checked > 0 && checkedSinceResolve < this._ranges.length && drain

        this._updatingSeeks = true

        let checkedSeek = false
        for (const s of this._seeks.slice()) {
          if (checkedSeeks.has(s) || !this._seeks.includes(s)) continue
          checkedSeeks.add(s)
          checkedSeek = true

          let err = null
          let res = null

          try {
            res = await s.seeker.update()
          } catch (error) {
            err = error
          }

          const seekIndex = this._seeks.indexOf(s)
          if (seekIndex === -1 || (!res && !err)) continue

          const h = this._seeks.pop()
          if (h !== s) this._seeks[seekIndex] = h

          if (err) s.reject(err)
          else s.resolve(res)
        }

        this._updatingSeeks = false

        if (
          (!continueRanges || (checkedSeek && this._seeks.length > 0)) &&
          (this._inflight.idle || updateAll)
        ) {
          this.queueUpdateAll()
        }
        if (!continueRanges) break

        await yieldToLoop()
        if (this.destroyed) break
      }

      if (this.destroyed) break
    }

    this._updatesRunning = false
  }

  _clearRequest(peer, req) {
    if (req.block !== null) {
      this._clearInflightBlock(this._blocks, req)
      this._unmarkInflight(req.block.index)
    }

    if (req.hash !== null) {
      this._clearInflightBlock(this._hashes, req)
    }

    if (req.upgrade !== null && this._upgrade !== null) {
      this._clearInflightUpgrade(req)
    }

    if (this._seeks.length > 0) {
      this._clearInflightSeeks(req)
    }

    if (this._reorgs.length > 0) {
      this._clearInflightReorgs(req)
    }
  }

  _onnodata(peer, req, reason) {
    if (reason === NOT_AVAILABLE) {
      peer.stats.notAvailableBackoffs++
      this.stats.notAvailableBackoffs++

      if (peer.stats.notAvailableBackoffs >= MAX_BACKOFFS) {
        peer.paused = true
      }
    }

    if (reason === INVALID_REQUEST) {
      peer.stats.backoffs++
      this.stats.backoffs++

      if (peer.stats.backoffs >= MAX_BACKOFFS) {
        peer.paused = true
      }
    }

    if (req) {
      this._clearRequest(peer, req)
    }

    this.queueUpdateAll()
  }

  _openSkipBitfield() {
    // technically the skip bitfield gets bits cleared if .clear() is called
    // also which might be in inflight also, but that just results in that section being overcalled shortly
    // worst case, so ok for now

    const bitfield = this.core.openSkipBitfield()

    for (const req of this._inflight) {
      if (req.block) bitfield.set(req.block.index, true) // skip
    }
  }

  _markProcessing(index) {
    const b = this._blocks.get(index)
    if (b) {
      b.processing = true
      return
    }

    const h = this._hashes.get(index)
    if (h) h.processing = true
  }

  _markProcessed(index) {
    const b = this._blocks.get(index)
    if (b) return b.processed()

    const h = this._hashes.get(index)
    if (h) h.processed()
  }

  _markInflight(index) {
    if (this.core.skipBitfield !== null) this.core.skipBitfield.set(index, true)
    for (const peer of this.peers) peer._markInflight(index)
  }

  _unmarkInflight(index) {
    if (this.core.skipBitfield !== null) {
      this.core.skipBitfield.set(index, this.core.bitfield.get(index))
    }
    for (const peer of this.peers) peer._resetMissingBlock(index)
  }

  _ondata(peer, req, data) {
    if (req) {
      req.elapsed = Date.now() - req.timestamp
    }

    if (data.block !== null) {
      this._resolveBlockRequest(this._blocks, data.block.index, data.block.value, req)
      this._ondownload(data.block.index, data.block.value.byteLength, peer, req)
    }

    if (data.hash !== null && (data.hash.index & 1) === 0) {
      this._resolveBlockRequest(this._hashes, data.hash.index / 2, null, req)
    }

    if (this._upgrade !== null) {
      this._resolveUpgradeRequest(req)
    }

    if (this._seeks.length > 0) {
      this._clearInflightSeeks(req)
    }

    if (this._reorgs.length > 0) {
      this._clearInflightReorgs(req)
    }

    if (this._manifestPeer === peer && this.core.header.manifest !== null) {
      this._manifestPeer = null
    }

    if (this._seeks.length > 0 || this._ranges.length > 0) {
      this._updateNonPrimary(this._seeks.length > 0)
    }

    this.updatePeer(peer)
  }

  _onwant(peer, start, length, any) {
    if (!peer.isActive()) return

    const contig = Math.min(this.core.state.length, this.core.header.hints.contiguousLength)

    if (start + length < contig || this.core.state.length === contig || (any && start < contig)) {
      peer.wireRange.send({
        drop: false,
        start: 0,
        length: contig
      })
      incrementTx(peer.stats.wireRange, this.stats.wireRange)
      return
    }

    length = Math.min(length, this.core.state.length - start)

    if (any) {
      const bit = this.core.bitfield.firstSet(start)
      if (bit > -1 && bit < start + length) {
        peer.wireRange.send({
          drop: false,
          start: bit,
          length: 1
        })
      }
      return
    }

    for (const msg of this.core.bitfield.want(start, length)) {
      peer.wireBitfield.send(msg)
      incrementTx(peer.stats.wireBitfield, this.stats.wireBitfield)
    }
  }

  async _onreorgdata(peer, req, data) {
    const newBatch = data.upgrade && (await this.core.verifyReorg(data))
    const f = this._addReorg(data.fork, peer)

    if (f === null) {
      this.queueUpdateAll()
      return
    }

    removeInflight(f.inflight, req)

    if (f.batch) {
      await f.batch.update(data)
    } else if (data.upgrade) {
      f.batch = newBatch

      // Remove "older" reorgs in progress as we just verified this one.
      this._clearOldReorgs(f.fork)
    }

    if (f.batch && f.batch.finished) {
      if (this._addUpgradeMaybe() !== null) {
        await this._applyReorg(f)
      }
    }

    this.queueUpdateAll()
  }

  // Never throws, allowed to run in the background
  async _applyReorg(f) {
    // TODO: more optimal here to check if potentially a better reorg
    // is available, ie higher fork, and request that one first.
    // This will request that one after this finishes, which is fine, but we
    // should investigate the complexity in going the other way

    const u = this._upgrade
    const old = this._reorgs

    this._reorgs = [] // clear all as the nodes are against the old tree - easier
    this._applyingReorg = this.core.reorg(f.batch, null) // TODO: null should be the first/last peer?

    try {
      await this._applyingReorg
    } catch (err) {
      this._upgrade = null
      u.reject(err)
    }

    this._applyingReorg = null

    if (this._upgrade !== null) {
      this._resolveUpgradeRequest(null)
    }

    for (const peer of this.peers) this._updateFork(peer)

    // TODO: all the remaining is a tmp workaround until we have a flag/way for ANY_FORK
    for (const r of this._ranges) {
      r.start = r.userStart
      r.end = r.userEnd
    }

    for (const f of old) f.resolve()
    f.resolve()

    this.queueUpdateAll()
  }

  _maybeUpdate() {
    return this._upgrade !== null && this._upgrade.inflight.length === 0
  }

  _maybeRequestManifest() {
    return this.core.header.manifest === null && this._manifestPeer === null
  }

  _updateFork(peer) {
    if (
      this._applyingReorg !== null ||
      this.allowFork === false ||
      peer.remoteFork <= this.core.state.fork
    ) {
      return false
    }

    const f = this._addReorg(peer.remoteFork, peer)

    // TODO: one per peer is better
    if (f !== null && f.batch === null && f.inflight.length === 0) {
      return peer._requestForkProof(f)
    }

    return false
  }

  _updateHotswap(peer) {
    const maxHotswaps = peer.getMaxHotswapInflight()
    if (!peer.isActive() || peer.inflight >= maxHotswaps) return

    for (const b of this.hotswaps.pick(peer)) {
      if (peer._requestBlock(b) === false) continue
      peer.stats.hotswaps++
      peer.replicator.stats.hotswaps++
      if (peer.inflight >= maxHotswaps) break
    }
  }

  _updatePeer(peer) {
    if (!peer.isActive() || peer.inflight >= peer.getMaxInflight() || !peer.remoteUploading) {
      return false
    }

    // Eagerly request the manifest even if the remote length is 0. If not 0 we'll get as part of the upgrade request...
    if (
      this._maybeRequestManifest() === true &&
      peer.remoteLength === 0 &&
      peer.remoteHasManifest === true
    ) {
      this._manifestPeer = peer
      peer._requestManifest()
    }

    for (const s of this._seeks) {
      if (s.inflight.length > 0) continue // TODO: one per peer is better
      if (peer._requestSeek(s) === true) {
        return true
      }
    }

    // Implied that any block in the queue should be requested, no matter how many inflights
    const blks = new RandomIterator(this._queued)

    for (const b of blks) {
      if (b.queued === false || peer._requestBlock(b) === true) {
        b.queued = false
        blks.dequeue()
        return true
      }
    }

    return false
  }

  _updatePeerNonPrimary(peer) {
    if (!peer.isActive() || peer.inflight >= peer.getMaxInflight() || !peer.remoteUploading) {
      return false
    }

    const ranges = new RandomIterator(this._ranges)
    let tried = 0

    for (const r of ranges) {
      if (peer._requestRange(r) === true) {
        return true
      }
      if (++tried >= MAX_RANGES) break
    }

    // Iterate from newest fork to oldest fork...
    for (let i = this._reorgs.length - 1; i >= 0; i--) {
      const f = this._reorgs[i]
      if (f.batch !== null && f.inflight.length === 0 && peer._requestForkRange(f) === true) {
        return true
      }
    }

    if (this._maybeUpdate() === true && peer._requestUpgrade(this._upgrade) === true) {
      return true
    }

    return false
  }

  updatePeer(peer) {
    // Quick shortcut to wait for flushing reorgs - not needed but less waisted requests
    if (this._applyingReorg !== null) return

    while (this._updatePeer(peer) === true);
    while (this._updatePeerNonPrimary(peer) === true);

    if (this.peers.length > 1 && this._blocks.isEmpty() === false) {
      this._updateHotswap(peer)
    }

    this._checkUpgradeIfAvailable()
  }

  updateSubset() {
    return this.updateAll(false)
  }

  updateAll(all = true) {
    // Clear queued scan if a full scan
    if (this._updateAllBump !== null && all) {
      clearTimeout(this._updateAllBump)
      this._updateAllBump = null
    }

    // Quick shortcut to wait for flushing reorgs - not needed but less waisted requests
    if (this._applyingReorg !== null) return

    const peers = new RandomIterator(this.peers)

    const limit = all ? peers.length : MIN_PEER_UPDATE_ALL
    let tried = 0
    for (const peer of peers) {
      tried++
      if (this._updatePeer(peer) === true) {
        peers.requeue()
      }
      if (tried >= limit) break
    }

    // Check if we can skip the non primary check fully
    if (this._maybeUpdate() === false && this._ranges.length === 0 && this._reorgs.length === 0) {
      this._checkUpgradeIfAvailable()
      return
    }

    tried = 0
    for (const peer of peers.restart()) {
      tried++
      if (this._updatePeerNonPrimary(peer) === true) {
        peers.requeue()
      }
      if (tried >= limit) break
    }

    this._checkUpgradeIfAvailable()
  }

  getUpdateAllDelay(peers) {
    return Math.min(3000, Math.max(100, (peers.length - MIN_PEER_UPDATE_ALL) * 5))
  }

  queueUpdateAll() {
    if (this.peers.length < MIN_PEER_UPDATE_ALL) return this.updateAll()

    // Immediately scan subset
    this.updateSubset()

    // Schedule full scan
    if (this._updateAllBump !== null) return //skip if already scheduled
    this._updateAllBump = setTimeout(this._updateAllBound, this.getUpdateAllDelay(this.peers))
  }

  onpeerdestroy() {
    if (--this._active === 0) this.core.checkIfIdle()
  }

  attached(protomux) {
    return this._attached.has(protomux)
  }

  attachTo(protomux) {
    if (this.core.closed) return

    const makePeer = this._makePeer.bind(this, protomux)

    this._attached.add(protomux)
    protomux.pair({ protocol: 'hypercore/alpha', id: this.core.discoveryKey }, makePeer)
    protomux.stream.setMaxListeners(0)
    protomux.stream.on('close', this._onstreamclose)

    this._ifAvailable++
    this._active++

    protomux.stream.opened.then((opened) => {
      this._ifAvailable--
      this._active--

      if (opened && !this.destroyed) makePeer()
      this._checkUpgradeIfAvailable()

      this.core.checkIfIdle()
    })
  }

  detachFrom(protomux) {
    if (this._attached.delete(protomux)) {
      protomux.stream.removeListener('close', this._onstreamclose)
      protomux.unpair({ protocol: 'hypercore/alpha', id: this.core.discoveryKey })
    }
  }

  idle() {
    return this.peers.length === 0 && this._active === 0
  }

  close() {
    const waiting = []

    for (const peer of this.peers) {
      waiting.push(peer.channel.fullyClosed())
    }

    this.destroy()
    return Promise.all(waiting)
  }

  destroy() {
    if (this.destroyed) return
    this.destroyed = true

    if (this._notDownloadingTimer) {
      clearTimeout(this._notDownloadingTimer)
      this._notDownloadingTimer = null
    }

    if (this._updateAllBump) {
      clearTimeout(this._updateAllBump)
      this._updateAllBump = null
    }

    while (this.peers.length) {
      const peer = this.peers[this.peers.length - 1]
      this.detachFrom(peer.protomux)
      peer.channel.close() // peer is removed from array in onclose
    }

    for (const protomux of this._attached) {
      this.detachFrom(protomux)
    }
  }

  _makePeer(protomux) {
    const replicator = this
    if (protomux.opened({ protocol: 'hypercore/alpha', id: this.core.discoveryKey })) {
      return onnochannel()
    }

    const channel = protomux.createChannel({
      userData: null,
      protocol: 'hypercore/alpha',
      aliases: ['hypercore'],
      id: this.core.discoveryKey,
      handshake: m.wire.handshake,
      messages: [
        { encoding: m.wire.sync, onmessage: onwiresync },
        { encoding: m.wire.request, onmessage: onwirerequest },
        { encoding: m.wire.cancel, onmessage: onwirecancel },
        { encoding: m.wire.data, onmessage: onwiredata },
        { encoding: m.wire.noData, onmessage: onwirenodata },
        { encoding: m.wire.want, onmessage: onwirewant },
        { encoding: m.wire.unwant, onmessage: onwireunwant },
        { encoding: m.wire.bitfield, onmessage: onwirebitfield },
        { encoding: m.wire.range, onmessage: onwirerange },
        { encoding: m.wire.extension, onmessage: onwireextension }
      ],
      onopen: onwireopen,
      onclose: onwireclose,
      ondrain: onwiredrain,
      ondestroy: onwiredestroy
    })

    if (channel === null) return onnochannel()

    const peer = new Peer(replicator, protomux, channel, this.inflightRange)
    const stream = protomux.stream

    peer.channel.open({
      seeks: true,
      capability: caps.replicate(stream.isInitiator, this.core.key, stream.handshakeHash)
    })

    return true

    function onnochannel() {
      return false
    }
  }

  _onpeerupdate(added, peer) {
    const name = added ? 'peer-add' : 'peer-remove'
    const sessions = this.core.monitors

    for (let i = sessions.length - 1; i >= 0; i--) {
      sessions[i].emit(name, peer)

      if (added) {
        for (const ext of sessions[i].extensions.values()) {
          peer.extensions.set(ext.name, ext)
        }
      }
    }
  }

  _ondownload(index, byteLength, from, req) {
    const sessions = this.core.monitors

    for (let i = sessions.length - 1; i >= 0; i--) {
      const s = sessions[i]
      s.emit('download', index, byteLength - s.padding, from, req)
    }
  }

  _onupload(index, byteLength, from) {
    const sessions = this.core.monitors

    for (let i = sessions.length - 1; i >= 0; i--) {
      const s = sessions[i]
      s.emit('upload', index, byteLength - s.padding, from)
    }
  }

  _oninvaliddata(err, req, res, from) {
    const sessions = this.core.monitors

    this.stats.invalidData++
    from.stats.invalidData++
    for (let i = 0; i < sessions.length; i++) {
      sessions[i].emit('verification-error', err, req, res, from)
    }
  }

  _oninvalidrequest(err, req, from) {
    const sessions = this.core.monitors

    from.stats.invalidRequests++
    this.stats.invalidRequests++
    for (let i = 0; i < sessions.length; i++) {
      sessions[i].emit('invalid-request', err, req, from)
    }
  }
}

function removeHotswap(block) {
  if (block.hotswap === null) return false
  block.hotswap.ref.remove(block)
  return true
}

function removeInflight(inf, req) {
  const i = inf.indexOf(req)
  if (i === -1) return false
  if (i < inf.length - 1) inf[i] = inf.pop()
  else inf.pop()
  return true
}

function toLength(start, end) {
  return end === -1 ? -1 : end < start ? 0 : end - start
}

function clampRange(core, r) {
  if (r.blocks === null) {
    const start = core.bitfield.firstUnset(r.start)

    if (r.end === -1) r.start = start === -1 ? core.state.length : start
    else if (start === -1 || start >= r.end) r.start = r.end
    else {
      r.start = start

      const end = core.bitfield.lastUnset(r.end - 1)

      if (end === -1 || start >= end + 1) r.end = r.start
      else r.end = end + 1
    }
  } else {
    while (r.start < r.end && core.bitfield.get(r.blocks[r.start])) r.start++
    while (r.start < r.end && core.bitfield.get(r.blocks[r.end - 1])) r.end--
  }
}

function onrequesttimeout(req) {
  if (req.context) req.context.detach(req, REQUEST_TIMEOUT())
}

function destroyRequestTimeout(req) {
  if (req.timeout !== null) {
    clearTimeout(req.timeout)
    req.timeout = null
  }
}

function isCriticalError(err) {
  // TODO: expose .critical or similar on the hypercore errors that are critical (if all not are)
  return err.name === 'HypercoreError'
}

function onwireopen(m, c) {
  return c.userData.onopen(m)
}

function onwireclose(isRemote, c) {
  return c.userData.onclose(isRemote)
}

function onwiredestroy(c) {
  c.userData.replicator.onpeerdestroy()
}

function onwiredrain(c) {
  return c.userData.ondrain()
}

function onwiresync(m, c) {
  incrementRx(c.userData.stats.wireSync, c.userData.replicator.stats.wireSync)
  return c.userData.onsync(m)
}

function onwirerequest(m, c) {
  incrementRx(c.userData.stats.wireRequest, c.userData.replicator.stats.wireRequest)
  return c.userData.onrequest(m)
}

function onwirecancel(m, c) {
  incrementRx(c.userData.stats.wireCancel, c.userData.replicator.stats.wireCancel)
  return c.userData.oncancel(m)
}

function onwiredata(m, c) {
  incrementRx(c.userData.stats.wireData, c.userData.replicator.stats.wireData)
  return c.userData.ondata(m)
}

function onwirenodata(m, c) {
  return c.userData.onnodata(m)
}

function onwirewant(m, c) {
  incrementRx(c.userData.stats.wireWant, c.userData.replicator.stats.wireWant)
  return c.userData.onwant(m)
}

function onwireunwant(m, c) {
  return c.userData.onunwant(m)
}

function onwirebitfield(m, c) {
  incrementRx(c.userData.stats.wireBitfield, c.userData.replicator.stats.wireBitfield)
  return c.userData.onbitfield(m)
}

function onwirerange(m, c) {
  incrementRx(c.userData.stats.wireRange, c.userData.replicator.stats.wireRange)
  return c.userData.onrange(m)
}

function onwireextension(m, c) {
  incrementRx(c.userData.stats.wireExtension, c.userData.replicator.stats.wireExtension)
  return c.userData.onextension(m)
}

function setNotDownloadingLater(repl, session) {
  repl._setDownloadingNow(false, session)
}

function isBlockRequest(req) {
  return req !== null && req.block !== null
}

function isUpgradeRequest(req) {
  return req !== null && req.upgrade !== null
}

function incrementTx(stats1, stats2) {
  stats1.tx++
  stats2.tx++
}

function incrementRx(stats1, stats2) {
  stats1.rx++
  stats2.rx++
}

function noop() {}

function yieldToLoop() {
  return new Promise((resolve) => setImmediate(resolve))
}

function backoff(times) {
  const sleep = times < 2 ? 200 : times < 5 ? 500 : times < 40 ? 1000 : 5000
  return new Promise((resolve) => setTimeout(resolve, sleep))
}
const crypto = require('hypercore-crypto')
const b4a = require('b4a')
const assert = require('nanoassert')
const flat = require('flat-tree')
const quickbit = require('quickbit-universal')
const HypercoreStorage = require('hypercore-storage')

const {
  INVALID_OPERATION,
  INVALID_SIGNATURE,
  ASSERTION,
  SESSION_CLOSED,
  SESSION_NOT_WRITABLE
} = require('hypercore-errors')

const Mutex = require('./mutex')
const Bitfield = require('./bitfield')
const { MerkleTree, MerkleTreeBatch } = require('./merkle-tree')

class SessionState {
  constructor(core, parent, storage, treeInfo, name) {
    this.core = core
    this.index = this.core.sessionStates.push(this) - 1

    this.storage = storage
    this.name = name
    this.sessions = []

    // small hack to close old storages as late as possible.
    // TODO: add a read lock so we can avoid that
    this.lingers = null

    this.parent = parent
    this.atomized = null
    this.mutex = new Mutex()

    // merkle state
    this.roots = treeInfo.roots.length ? treeInfo.roots : []
    this.fork = treeInfo.fork || 0
    this.length = this.roots.length ? MerkleTree.span(this.roots) / 2 : treeInfo.length
    this.byteLength = MerkleTree.size(this.roots)
    this.prologue = treeInfo.prologue || null
    this.signature = treeInfo.signature || null

    this.snapshotCompatLength = this.isSnapshot()
      ? Math.min(this.length, this.core.state.length)
      : -1
    this.lastTruncation = null
    this.groupUpdated = false

    this.active = 0

    this._activeTx = null
    this._pendingBitfield = null

    this.ref()
  }

  static RECOVERY_PEERS = null

  isSnapshot() {
    return this.storage.snapshotted
  }

  isDefault() {
    return this.core.state === this || this.isAtomicDefault()
  }

  isAtomicDefault() {
    return !!this.storage.atom && !!this.parent && this.parent.isDefault()
  }

  createTreeBatch() {
    return new MerkleTreeBatch(this)
  }

  addSession(s) {
    if (s._stateIndex !== -1) return
    s._stateIndex = this.sessions.push(s) - 1
    if (s.weak === false) this.core.activeSessions++
  }

  removeSession(s) {
    if (s._stateIndex === -1) return
    const head = this.sessions.pop()
    if (head !== s) this.sessions[(head._stateIndex = s._stateIndex)] = head
    s._stateIndex = -1
    if (s.weak === false) this.core.activeSessions--
    this.core.checkIfIdle()
  }

  flushedLength() {
    if (this.isDefault() || this.isSnapshot()) return this.length
    const deps = this.storage.dependencies
    if (deps.length) return deps[deps.length - 1].length
    return 0
  }

  signedLength() {
    const l = Math.min(this.flushedLength(), this.core.state.length)
    return this.isSnapshot() && l > this.snapshotCompatLength ? this.snapshotCompatLength : l
  }

  unref() {
    if (--this.active > 0) return
    this.close().catch(noop) // technically async, but only for the last db session
  }

  ref() {
    this.active++
    return this
  }

  hash() {
    return MerkleTree.hash(this)
  }

  setRoots(roots) {
    this.roots = roots
    this.length = MerkleTree.span(roots) / 2
    this.byteLength = MerkleTree.size(roots)
  }

  get encryptionFork() {
    return this.core.header.tree.fork
  }

  async _updateSnapshotStorage(storage) {
    if (!this.atomized || !this.atomized.flushing) return this.treeInfo()
    while (this.atomized.flushing) await this.atomized.flushed()

    let rx = storage.read()
    const headPromise = rx.getHead()
    const authPromise = rx.getAuth()
    const depPromise = rx.getDependency()

    rx.tryFlush()
    const [head, auth, dep] = await Promise.all([headPromise, authPromise, depPromise])

    storage.setDependencyHead(dep)

    const fork = head ? head.fork : 0
    const length = head ? head.length : 0

    rx = storage.read()
    const rootPromises = []
    for (const r of flat.fullRoots(length * 2)) {
      rootPromises.push(rx.getTreeNode(r))
    }

    rx.tryFlush()

    const roots = await Promise.all(rootPromises)

    // dbl check if we are hitting an regression from earler
    for (const root of roots) {
      if (root === null) {
        throw new Error(
          'Bad snapshot from atomized session, id = ' +
            this.core.id +
            ' length = ' +
            length +
            ' fork = ' +
            fork
        )
      }
    }

    return {
      fork,
      roots,
      length,
      prologue: auth.manifest && auth.manifest.prologue,
      signature: head && head.signature
    }
  }

  treeInfo() {
    return {
      fork: this.fork,
      roots: this.roots.slice(),
      length: this.length,
      prologue: this.prologue,
      signature: this.signature
    }
  }

  async close() {
    if (this.index === -1) return

    this.active = 0
    this.mutex.destroy(new Error('Closed')).catch(noop)
    if (this.parent && this.parent.atomized) this.parent.atomized = null

    const closing = this.storage.close()

    const head = this.core.sessionStates.pop()
    if (head !== this) this.core.sessionStates[(head.index = this.index)] = head

    this.index = -1
    this.core.checkIfIdle()

    if (this.lingers !== null) {
      for (const storage of this.lingers) await storage.close()
    }

    return closing
  }

  async snapshot() {
    const storage = this.storage.snapshot()
    const treeInfo = await this._updateSnapshotStorage(storage)

    return new SessionState(this.core, null, storage, treeInfo, this.name)
  }

  updateDependency(tx, length) {
    const dependency = updateDependency(this, length, false)
    if (dependency) tx.setDependency(dependency)
    return dependency
  }

  updateGroupIndex(tx, tree) {
    const { pointer } = this.core.header.group

    tx.deleteGroupUpdate(pointer, this.core.header.tree.timestamp)
    tx.putGroupUpdate(pointer, tree.timestamp, this.core.header.key)

    this.groupUpdated = true
  }

  _clearActiveBatch() {
    this._activeTx = null
  }

  createWriteBatch() {
    assert(!this._activeTx && !this.storage.snapshotted)

    this._activeTx = this.storage.write()
    return this._activeTx
  }

  _unlock() {
    this._clearActiveBatch()
    this.mutex.unlock()
    this.core.checkIfIdle()
  }

  _flushHooks() {
    if (!this.groupUpdated) return
    this.groupUpdated = false
    this.core.ongroupupdate(this.core.header.group.key, {
      key: this.core.key,
      length: this.length,
      fork: this.fork
    })
  }

  async flush() {
    const tx = this._activeTx
    this._activeTx = null

    try {
      if (!(await tx.flush())) return false
    } finally {
      this._clearActiveBatch()
    }

    this.lastTruncation = null
    return true
  }

  _precommit(state) {
    assert(
      state.parent === this && state.storage.core.dataPointer === this.storage.core.dataPointer,
      'Atomic state must flush to parent'
    )
  }

  async _commit() {
    await this.mutex.lock()

    try {
      const bitfield = this._pendingBitfield
      this._pendingBitfield = null
      this.lastTruncation = null
      await this.parent._oncommit(this, bitfield)
    } finally {
      this.mutex.unlock()
    }
  }

  async _oncommit(src, bitfield) {
    await this.mutex.lock()

    try {
      const currLength = this.length

      // load dependency into memory
      const rx = this.storage.read()
      const dependencyPromise = rx.getDependency()

      rx.tryFlush()

      const dependency = await dependencyPromise

      this.fork = src.fork
      this.length = src.length
      this.byteLength = src.byteLength
      this.roots = src.roots.slice()
      this.signature = src.signature

      const tree = {
        fork: this.fork,
        length: this.length,
        rootHash: this.hash(),
        signature: this.signature,
        timestamp: Date.now()
      }

      if (this.isDefault() && this.core.header.group) {
        const tx = this.createWriteBatch()
        this.updateGroupIndex(tx, tree)
        await this.flush()
        this._flushHooks()
      }

      if (dependency) this.storage.setDependencyHead(dependency)

      const b = bitfield

      if (b && b.truncated && b.start < currLength) {
        this.ontruncate(tree, b.start, currLength, true)
        if (!b || b.appends === 0) return
      }

      const append = b ? { start: b.start, length: b.appends, drop: false } : null

      this.onappend(tree, append, true)

      if (this.core.hintsChanged && this.isDefault()) await this.core.flushHints()
    } finally {
      this._unlock()
    }
  }

  async setUserData(key, value) {
    await this.mutex.lock()

    try {
      const tx = this.createWriteBatch()
      tx.putUserData(key, value)

      return await this.flush()
    } finally {
      this._unlock()
    }
  }

  async _verifyBlock(batch, bitfield, value, manifest, from) {
    await this.mutex.lock()

    try {
      if (batch.upgraded && batch.length <= this.length) {
        await batch.downgrade()
      }

      if (!batch.commitable()) {
        return false
      }

      if (this.core.preupdate !== null) {
        await this.core.preupdate(batch, this.core.header.key)
      }

      const tx = this.createWriteBatch()

      if (bitfield) {
        tx.putBlock(bitfield.start, value)
      }

      if (bitfield && this.isDefault()) {
        await storeBitfieldRange(this.storage, tx, bitfield.start, bitfield.start + 1, true)
      }

      if (manifest) this.core._setManifest(tx, manifest, null)

      assert(batch.commitable(), 'Should still be commitable')
      batch.commit(tx)

      const head = {
        fork: batch.fork,
        length: batch.length,
        rootHash: batch.hash(),
        signature: batch.signature,
        timestamp: Date.now()
      }

      if (batch.upgraded) {
        tx.setHead(head)

        if (this.core.header.group) {
          this.updateGroupIndex(tx, head)
        }
      }

      const flushed = await this.flush()

      if (batch.upgraded) {
        this.roots = batch.roots
        this.length = batch.length
        this.byteLength = batch.byteLength
        this.fork = batch.fork
        this.signature = batch.signature

        this.onappend(head, bitfield, flushed)
      }

      if (this.core.hintsChanged && this.isDefault()) await this.core.flushHints()
    } finally {
      this._unlock()
    }

    return true
  }

  async truncate(length, fork, { signature, keyPair } = {}) {
    if (!keyPair && this.isDefault()) keyPair = this.core.header.keyPair

    await this.mutex.lock()

    try {
      if (this.prologue && length < this.prologue.length) {
        throw INVALID_OPERATION('Truncation breaks prologue', this.core.discoveryKey)
      }
      if (length > this.length) {
        throw INVALID_OPERATION(
          'Not a truncation, ' + length + ' must be less or equal to ' + this.length,
          this.core.discoveryKey
        )
      }

      const batch = this.createTreeBatch()
      await MerkleTree.truncate(this, length, batch, fork)

      if (signature) {
        batch.signature = signature
        this.core._verifyBatchUpgrade(batch)
      } else if (keyPair) {
        batch.signature = this.core.verifier.sign(batch, keyPair)
      }

      const tx = this.createWriteBatch()

      // upsert compat manifest
      if (this.core.verifier === null && keyPair) this.core._setManifest(tx, null, keyPair)

      const { dependency, tree, roots } = await this._truncate(tx, batch)

      for (const sessionState of this.core.sessionStates) {
        if (
          sessionState.isSnapshot() &&
          sessionState.name === this.name &&
          length < sessionState.snapshotCompatLength
        ) {
          sessionState.snapshotCompatLength = length
        }
      }

      const flushed = await this.flush()

      this.fork = tree.fork
      this.length = tree.length
      this.byteLength = MerkleTree.size(roots)
      this.roots = roots
      this.signature = tree.signature

      if (dependency) this.storage.setDependencyHead(dependency)

      this.ontruncate(tree, tree.length, batch.treeLength, flushed)

      if (this.core.hintsChanged && this.isDefault()) await this.core.flushHints()
    } finally {
      this._unlock()
    }
  }

  async reorg(batch) {
    await this.mutex.lock()

    const storage = this.createWriteBatch()

    try {
      if (!batch.commitable()) return false

      const { dependency, tree } = await this._truncate(storage, batch)

      const flushed = await this.flush()

      this.fork = batch.fork
      this.length = batch.length
      this.byteLength = batch.byteLength
      this.roots = batch.roots
      this.signature = batch.signature

      if (dependency) this.storage.setDependencyHead(dependency)

      this.ontruncate(tree, batch.ancestors, batch.treeLength, flushed)

      if (this.core.hintsChanged && this.isDefault()) await this.core.flushHints()
    } finally {
      this._unlock()
    }
  }

  async _truncate(storage, batch) {
    storage.deleteBlockRange(batch.ancestors, batch.treeLength)

    assert(batch.commitable(), 'Batch must be commitable')

    const tree = {
      fork: batch.fork,
      length: batch.length,
      rootHash: batch.hash(),
      signature: batch.signature,
      timestamp: Date.now()
    }

    if (this.isDefault() && this.core.header.group) {
      this.updateGroupIndex(storage, tree)
    }

    storage.setHead(tree)
    batch.commit(storage)

    const truncated = batch.length < this.flushedLength()
    const dependency = truncated ? updateDependency(this, batch.length, true) : null

    if (dependency) storage.setDependency(dependency)

    if (this.isDefault()) {
      await storeBitfieldRange(this.storage, storage, batch.ancestors, batch.treeLength, false)
      if (batch.ancestors < this.core.header.hints.contiguousLength) {
        this.core.header.hints.remoteContiguousLength = Math.min(
          batch.length,
          this.core.header.hints.remoteContiguousLength
        )
        storage.setHints({
          contiguousLength: batch.ancestors,
          remoteContiguousLength: this.core.header.hints.remoteContiguousLength
        })
      }
    }

    return { dependency, tree, roots: batch.roots }
  }

  async clear(start, end, cleared) {
    await this.mutex.lock()

    try {
      const tx = this.createWriteBatch()

      const ite = flat.iterator(start * 2)
      while (!isRootIndex(ite.index, this.roots)) {
        const a = ite.index
        const b = ite.sibling()
        ite.parent()

        const [left, right] = flat.spans(b)
        const s = left / 2
        const e = right / 2 + 1
        const has = s <= start && e <= end ? false : this.core.bitfield.hasSet(s, e - s)
        if (has) break

        tx.deleteTreeNode(a)
        tx.deleteTreeNode(b)
      }

      if (this.isDefault()) {
        await storeBitfieldRange(this.storage, tx, start, end, false)
        if (start < this.core.header.hints.contiguousLength) {
          tx.setHints({
            contiguousLength: start,
            remoteContiguousLength: this.core.header.hints.remoteContiguousLength
          })
        }
      }

      if (end - start === 1) tx.deleteBlock(start)
      else tx.deleteBlockRange(start, end)

      const dependency = start < this.flushedLength() ? updateDependency(this, start, true) : null

      const flushed = await this.flush()

      if (dependency) this.storage.setDependencyHead(dependency)

      // todo: atomic event handle
      if (this.isDefault() && flushed) {
        const length = end - start

        this.core.updateContiguousLength({ start, length, drop: true })
        this.core._setBitfieldRanges(start, end, false)
        this.core.replicator.onhave(start, length, true)

        if (this.core.hintsChanged) await this.core.flushHints()
      }
    } finally {
      this._unlock()
    }
  }

  // simple tool to wait for other peers to catch us up
  async waitForRecovery() {
    const length = this.length
    const replicator = this.core.replicator

    while (!this.closing) {
      let peers = getRecoveryPeers(replicator.peers)

      if (peers.length < 1) {
        await new Promise((resolve) => setTimeout(resolve, 5000))
        continue
      }

      let restart = false

      for (let i = 0; i < 32; i++) {
        await new Promise((resolve) => setTimeout(resolve, 250))

        peers = getRecoveryPeers(replicator.peers)

        if (this.closing || !validateRecoveryPeers(peers, this.length)) {
          restart = true
          break
        }
      }

      if (restart) continue

      if (this.length !== length) {
        throw SESSION_NOT_WRITABLE('core recovered during append')
      }

      this.core.header.hints.recovering = 0
      this.core.hintsChanged = true

      await this.mutex.lock()
      try {
        await this.core.flushHints()
        return
      } finally {
        this.mutex.unlock()
      }
    }

    throw SESSION_CLOSED('Cannot recover a closed session', this.discoveryKey)
  }

  async append(values, { signature, keyPair, preappend, postappend, maxLength = -1 } = {}) {
    if (!keyPair && this.isDefault()) keyPair = this.core.header.keyPair

    if (this.isDefault() && this.core.header.hints.recovering) await this.waitForRecovery()

    await this.mutex.lock()

    try {
      if (maxLength >= 0 && this.length + values.length > maxLength) {
        return { length: this.length, byteLength: this.byteLength }
      }

      const tx = this.createWriteBatch()

      // upsert compat manifest
      if (this.core.verifier === null && keyPair) this.core._setManifest(tx, null, keyPair)

      if (preappend) await preappend(values)

      if (!values.length) {
        await this.flush()
        return { length: this.length, byteLength: this.byteLength }
      }

      const batch = this.createTreeBatch()
      for (const val of values) batch.append(val)

      // only multisig can have prologue so signature is always present
      if (this.prologue && batch.length < this.prologue.length) {
        throw INVALID_OPERATION('Append is not consistent with prologue', this.core.discoveryKey)
      }

      if (signature) {
        batch.signature = signature
        this.core._verifyBatchUpgrade(batch)
      } else if (keyPair) {
        batch.signature = this.core.verifier.sign(batch, keyPair)
      }

      batch.commit(tx)

      const tree = {
        fork: batch.fork,
        length: batch.length,
        rootHash: batch.hash(),
        signature: batch.signature,
        timestamp: Date.now()
      }

      tx.setHead(tree)

      if (this.isDefault()) {
        await storeBitfieldRange(this.storage, tx, batch.ancestors, batch.length, true)
        if (this.length === this.core.header.hints.contiguousLength) {
          tx.setHints({
            contiguousLength: this.length + values.length,
            remoteContiguousLength: this.core.header.hints.remoteContiguousLength
          })
        }

        if (this.core.header.group) {
          this.updateGroupIndex(tx, tree)
        }
      }

      for (let i = 0; i < values.length; i++) {
        tx.putBlock(this.length + i, values[i])
      }

      const bitfield = {
        drop: false,
        start: batch.ancestors,
        length: values.length
      }

      const flushed = await this.flush()

      this.fork = batch.fork
      this.roots = batch.roots
      this.length = batch.length
      this.byteLength = batch.byteLength
      this.signature = batch.signature

      if (postappend) await postappend(values)

      this.onappend(tree, bitfield, flushed)

      if (this.core.hintsChanged && this.isDefault()) await this.core.flushHints()

      return { length: this.length, byteLength: this.byteLength }
    } finally {
      this._unlock()
    }
  }

  onappend(tree, bitfield, flushed) {
    if (!flushed) this._updateBitfield(bitfield)
    else if (this.isDefault()) this.core.onappend(tree, bitfield)

    for (let i = this.sessions.length - 1; i >= 0; i--) {
      this.sessions[i].emit('append')
    }

    this._flushHooks()
  }

  ontruncate(tree, to, from, flushed) {
    const bitfield = { start: to, length: from - to, drop: true }

    this.lastTruncation = { from, to }

    if (!flushed) this._updateBitfield(bitfield)
    else if (this.isDefault()) this.core.ontruncate(tree, bitfield)

    for (const sessionState of this.core.sessionStates) {
      if (
        sessionState.isSnapshot() &&
        sessionState.name === this.name &&
        to < sessionState.snapshotCompatLength
      ) {
        sessionState.snapshotCompatLength = to
      }
    }

    for (let i = this.sessions.length - 1; i >= 0; i--) {
      this.sessions[i].emit('truncate', to, tree.fork)
    }

    this._flushHooks()
  }

  _updateBitfield(bitfield, flushed) {
    if (!bitfield) return

    const p = this._pendingBitfield
    const b = bitfield

    if (b.drop) {
      // truncation must be from end
      if (p && b.start + b.length !== p.start + p.appends) {
        throw INVALID_OPERATION('Atomic truncations must be contiguous', this.core.discoveryKey)
      }

      // actual truncation
      if (p === null || b.start < p.start) {
        this._pendingBitfield = { truncated: true, start: b.start, appends: 0 }
        return
      }

      // just clearing batch data
      p.appends = b.start - p.start

      // we cleared the current batch
      if (p.appends === 0) this._pendingBitfield = null

      return
    }

    if (p === null) {
      this._pendingBitfield = { truncated: false, start: b.start, appends: b.length }
      return
    }

    if (b.start !== p.start + p.appends) {
      throw INVALID_OPERATION('Atomic operations must be contiguous', this.core.discoveryKey)
    }

    p.appends += b.length
  }

  async catchup(length) {
    assert(!this.isDefault(), 'Cannot catchup signed state') // TODO: make this check better

    await this.mutex.lock()

    try {
      const origLength = this.length

      let sharedLength = 0
      for (let i = this.storage.dependencies.length - 1; i >= 0; i--) {
        const dep = this.storage.dependencies[i]
        if (dep.dataPointer === this.core.state.storage.core.dataPointer) {
          sharedLength = dep.length
          break
        }
      }

      const tx = this.createWriteBatch()
      const rx = this.core.state.storage.read()
      const rootPromises = []

      for (const root of flat.fullRoots(length * 2)) {
        rootPromises.push(rx.getTreeNode(root))
      }

      rx.tryFlush()

      const roots = await Promise.all(rootPromises)
      const truncating = sharedLength < origLength

      for (const node of roots) {
        if (node === null) {
          throw INVALID_OPERATION(
            'Invalid catchup length, tree nodes not available',
            this.core.discoveryKey
          )
        }
      }

      const fork = truncating ? this.fork + 1 : this.fork

      // overwrite it atm, TODO: keep what we can connect to the tree
      tx.deleteBlockRange(0, -1)
      tx.deleteTreeNodeRange(0, -1)
      tx.deleteBitfieldPageRange(0, -1)

      const tree = {
        fork,
        length,
        rootHash: crypto.tree(roots),
        signature: null,
        timestamp: Date.now()
      }

      // never default state so do not update group index
      tx.setHead(tree)

      // prop a better way to do this
      const dep = updateDependency(this, sharedLength, true)
      dep.length = length

      tx.setDependency(dep)

      const flushed = await this.flush()

      this.storage.setDependencyHead(dep)

      this.fork = tree.fork
      this.roots = roots
      this.length = tree.length
      this.byteLength = MerkleTree.size(roots)

      if (truncating) this.ontruncate(tree, sharedLength, origLength, flushed)
      if (sharedLength < length) this.onappend(tree, null, flushed)

      if (this.core.hintsChanged && this.isDefault()) await this.core.flushHints()
    } finally {
      this._unlock()
    }
  }

  async _overwrite(source, fork, length, treeLength, signature) {
    const blockPromises = []
    const treePromises = []
    const rootPromises = []

    const rx = source.storage.read()

    for (const root of flat.fullRoots(length * 2)) {
      rootPromises.push(rx.getTreeNode(root))
    }

    for (const index of flat.patch(treeLength * 2, length * 2)) {
      treePromises.push(rx.getTreeNode(index))
    }

    for (let i = treeLength; i < length; i++) {
      treePromises.push(rx.getTreeNode(i * 2))
      treePromises.push(rx.getTreeNode(i * 2 + 1))
      blockPromises.push(rx.getBlock(i))
    }

    rx.tryFlush()

    const [blocks, nodes, roots] = await Promise.all([
      Promise.all(blockPromises),
      Promise.all(treePromises),
      Promise.all(rootPromises)
    ])

    if (this.core.destroyed) throw new Error('Core destroyed')

    if (signature) {
      const batch = this.createTreeBatch()
      batch.roots = roots
      batch.length = length

      if (!this.core.verifier.verify(batch, signature)) {
        throw INVALID_SIGNATURE(
          'Signature is not valid over committed tree',
          this.core.discoveryKey
        )
      }
    }

    const tx = this.createWriteBatch()

    // truncate existing tree
    if (treeLength < this.length) {
      tx.deleteBlockRange(treeLength, this.length)
    }

    for (const root of roots) tx.putTreeNode(root)

    // no nodes will be copied in shallow mode
    for (const node of nodes) {
      if (node !== null) tx.putTreeNode(node)
    }

    for (let i = 0; i < blocks.length; i++) {
      assert(blocks[i] !== null, 'has block')
      tx.putBlock(i + treeLength, blocks[i])
    }

    const totalLength = Math.max(length, this.length)

    if (totalLength > treeLength) {
      const firstPage = getBitfieldPage(treeLength)
      const lastPage = getBitfieldPage(totalLength - 1)

      const srx = this.storage.read()
      const bitfieldPagePromise = srx.getBitfieldPage(firstPage)
      srx.tryFlush()

      const bitfieldPage = await bitfieldPagePromise

      let index = treeLength

      for (let i = firstPage; i <= lastPage; i++) {
        const page = b4a.alloc(Bitfield.BYTES_PER_PAGE)
        tx.putBitfieldPage(i, page)

        // copy existing bits in
        if (i === firstPage && bitfieldPage) page.set(bitfieldPage)

        if (index < length) {
          index = fillBitfieldPage(page, index, length, i, true)
          if (index < length) continue
        }

        if (index < this.length) {
          index = fillBitfieldPage(page, index, this.length, i, false)
        }
      }
    }

    const tree = {
      fork,
      length,
      rootHash: crypto.tree(roots),
      signature,
      timestamp: Date.now()
    }

    if (this.isDefault() && this.core.header.group) {
      this.updateGroupIndex(tx, tree)
    }

    const upgraded = treeLength < this.length || this.length < length || tree.fork !== this.fork

    if (upgraded) tx.setHead(tree)

    const flushed = await this.flush()

    this.fork = tree.fork
    this.roots = roots
    this.length = length
    this.byteLength = MerkleTree.size(roots)
    this.signature = signature

    return { tree, flushed }
  }

  static isParent(state, parent) {
    // atomic batches have parent set
    if (state.parent && state.parent === parent) return true

    return HypercoreStorage.isParentStorage(state.storage, parent.storage)
  }

  async commit(state, { signature, keyPair, length = state.length, treeLength = -1 } = {}) {
    assert(
      this.isDefault() || (this.parent && this.parent.isDefault()),
      'Can only commit into default state'
    )

    assert(SessionState.isParent(state, this), 'Cannot commit across cores')

    if (this.core._repairMode) {
      throw ASSERTION('Cannot commit while repair mode is on')
    }

    if (this.core.header.hints.recovering) await this.waitForRecovery()

    let srcLocked = false
    await this.mutex.lock()

    try {
      await state.mutex.lock()
      srcLocked = true

      if (treeLength === -1) treeLength = state.flushedLength()

      if (!(await this.core._validateCommit(state, treeLength))) return null
      if (this.length > length) return null

      if (this.length < length && !signature) {
        if (!keyPair) keyPair = this.core.header.keyPair
        const batch = state.createTreeBatch()
        if (length !== batch.length) await batch.restore(length)
        signature = this.core.verifier.sign(batch, keyPair)
      }

      const { tree, flushed } = await this._overwrite(
        state,
        this.fork,
        length,
        treeLength,
        signature
      )

      // gc blocks from source
      if (treeLength < length) {
        const tx = state.createWriteBatch()

        tx.deleteBlockRange(treeLength, length)
        const dependency = state.updateDependency(tx, length)

        await state.flush(tx)

        if (dependency) state.storage.setDependencyHead(dependency)
      }

      const bitfield = { start: treeLength, length: length - treeLength, drop: false }
      this.onappend(tree, bitfield, flushed)

      return {
        length: this.length,
        byteLength: this.byteLength
      }
    } finally {
      if (srcLocked) state._unlock()
      this._unlock()
    }
  }

  async _getTreeHeadAt(length) {
    if (length === null) return this.treeInfo()

    const head = getDefaultTree()

    head.length = length

    const roots = await MerkleTree.getRootsFromStorage(this.storage, length)
    const rootHash = crypto.tree(roots)

    head.fork = this.fork
    head.rootHash = rootHash

    if (length === this.length) head.signature = this.signature

    return head
  }

  _moveToCore(core, truncated, appended) {
    const head = this.core.sessionStates.pop()
    if (head !== this) this.core.sessionStates[(head.index = this.index)] = head

    this.core = core
    this.index = this.core.sessionStates.push(this) - 1

    for (let i = this.sessions.length - 1; i >= 0; i--) {
      const s = this.sessions[i]
      const manifest = s.manifest
      s.transferSession(this.core)
      if (!manifest && s.manifest) s.emit('manifest')
      if (truncated) s.emit('truncate', truncated.to, truncated.fork)
      if (appended) s.emit('append')
    }
  }

  async moveTo(core, length) {
    const state = core.state

    await this.mutex.lock()

    try {
      // if (state.storage && (await state.storage.resumeSession(this.name)) !== null) {
      //   throw STORAGE_CONFLICT('Batch has already been created')
      // }

      const treeLength = this.length

      let truncated = null
      let appended = false

      if (!this.isSnapshot()) {
        if (this.lingers === null) this.lingers = []
        this.lingers.push(this.storage)

        const resumed = await state.storage.resumeSession(this.name)

        const truncation = length < this.length ? await truncateAndFlush(this, length) : null
        const treeInfo = truncation
          ? truncation.tree
          : resumed
            ? null
            : await state._getTreeHeadAt(this.length)

        const fork = truncation ? this.fork + 1 : this.fork

        // todo: validate treeInfo

        let storage = null

        if (resumed) {
          storage = resumed
        } else {
          treeInfo.fork = fork
          storage = await state.storage.createSession(this.name, treeInfo)
        }

        const roots = await MerkleTree.getRootsFromStorage(storage, length)

        this.storage = storage
        this.prologue = state.prologue
        this.fork = fork
        this.length = length
        this.byteLength = MerkleTree.size(roots)
        this.roots = roots

        if (truncation) {
          const { dependency } = truncation

          if (dependency) this.storage.setDependencyHead(dependency)
          truncated = { to: treeLength, fork }
        }

        if (this.length > treeLength) {
          appended = true
        }
      }

      for (let i = this.core.sessionStates.length - 1; i >= 0; i--) {
        const state = this.core.sessionStates[i]
        if (state === this) continue
        if (state.name === this.name) state._moveToCore(core.core)
      }

      this._moveToCore(core.core, truncated, appended)
    } finally {
      this._unlock()
    }
  }

  async createSession(name, overwrite, atom) {
    await this.mutex.lock()

    try {
      let storage = null
      let treeInfo = null

      if (!atom && !overwrite && this.storage) {
        storage = await this.storage.resumeSession(name)

        if (storage !== null) treeInfo = (await getCoreHead(storage)) || getDefaultTree()
      }

      const length = treeInfo ? treeInfo.length : this.length

      if (storage === null) {
        treeInfo = await this._getTreeHeadAt(length)

        if (atom) {
          storage = await this.storage.createAtomicSession(atom, treeInfo)
        } else {
          storage = await this.storage.createSession(name, treeInfo)
        }
      }

      if (this.atomized && atom) {
        throw new Error('Session already atomized')
      }

      const head = {
        fork: this.fork,
        roots:
          length === this.length
            ? this.roots.slice()
            : await MerkleTree.getRootsFromStorage(storage, length),
        length,
        prologue: this.prologue,
        signature: length === this.length ? this.signature : null
      }

      const state = new SessionState(
        this.core,
        atom ? this : null,
        storage,
        head,
        atom ? this.name : name
      )

      if (atom) {
        this.atomized = atom
        atom.preflush(this._precommit.bind(this, state))
        atom.onflush(state._commit.bind(state))
      }

      return state
    } finally {
      this._unlock()
    }
  }
}

module.exports = SessionState

function noop() {}

function getBitfieldPage(index) {
  return Math.floor(index / Bitfield.BITS_PER_PAGE)
}

function fillBitfieldPage(page, start, end, pageIndex, value) {
  const offset = pageIndex * Bitfield.BITS_PER_PAGE
  const max = offset + Bitfield.BITS_PER_PAGE

  const index = max < end ? max : end

  const from = start - offset
  const to = index - offset

  quickbit.fill(page, value, from, to)

  return index
}

async function storeBitfieldRange(storage, tx, from, to, value) {
  if (from >= to) return

  const firstPage = getBitfieldPage(from)
  const lastPage = getBitfieldPage(to - 1)

  let index = from

  const rx = storage.read()

  const promises = []
  for (let i = firstPage; i <= lastPage; i++) {
    promises.push(rx.getBitfieldPage(i))
  }

  rx.tryFlush()

  const pages = await Promise.all(promises)
  const cnt = lastPage - firstPage + 1

  for (let i = 0; i < cnt; i++) {
    const pageIndex = i + firstPage
    if (!pages[i]) pages[i] = b4a.alloc(Bitfield.BYTES_PER_PAGE)

    index = fillBitfieldPage(pages[i], index, to, pageIndex, value)
    tx.putBitfieldPage(pageIndex, pages[i])
  }
}

async function truncateAndFlush(s, length) {
  const batch = s.createTreeBatch()
  await MerkleTree.truncate(s, length, batch, s.fork)
  const tx = s.createWriteBatch()

  const info = await s._truncate(tx, batch)
  const flushed = await s.flush()

  return {
    tree: info.tree,
    roots: info.roots,
    dependency: info.dependency,
    flushed
  }
}

function updateDependency(state, length, truncated) {
  const i = state.storage.findDependencyIndex(length, truncated)
  if (i === -1) return null // skip default state and overlays of default

  return {
    dataPointer: state.storage.dependencies[i].dataPointer,
    length
  }
}

function getDefaultTree() {
  return {
    fork: 0,
    length: 0,
    rootHash: null,
    signature: null
  }
}

function getCoreHead(storage) {
  const b = storage.read()
  const p = b.getHead()
  b.tryFlush()
  return p
}

function isRootIndex(index, roots) {
  for (const node of roots) {
    if (node.index === index) return true
  }

  return false
}

function getRecoveryPeers(peers) {
  if (!SessionState.RECOVERY_PEERS) return peers

  const actual = []

  for (const p of peers) {
    for (const r of SessionState.RECOVERY_PEERS) {
      if (b4a.equals(p.remotePublicKey, r)) {
        actual.push(p)
        break
      }
    }
  }

  return actual
}

function validateRecoveryPeers(peers, length) {
  if (peers.length < 1) return false

  for (const p of peers) {
    if (p.remoteLength > length) return false
    if (p.remoteLength === 0 && !p.remoteSynced) return false
  }

  return true
}
const { Writable, Readable } = require('streamx')

class ReadStream extends Readable {
  constructor(core, opts = {}) {
    super()

    this.core = core
    this.start = opts.start || 0
    this.end = typeof opts.end === 'number' ? opts.end : -1
    this.snapshot = !opts.live && opts.snapshot !== false
    this.live = this.end === -1 ? !!opts.live : false
    this.wait = typeof opts.wait === 'boolean' ? opts.wait : this.core.wait
    this.timeout = opts.timeout || core.timeout
  }

  _open(cb) {
    this._openP().then(cb, cb)
  }

  _read(cb) {
    this._readP().then(cb, cb)
  }

  async _openP() {
    if (this.end === -1) await this.core.update()
    else await this.core.ready()
    if (this.snapshot && this.end === -1) this.end = this.core.length
  }

  async _readP() {
    const end = this.live ? -1 : this.end === -1 ? this.core.length : this.end
    if (end >= 0 && this.start >= end) {
      this.push(null)
      return
    }

    this.push(await this.core.get(this.start++, { wait: this.wait, timeout: this.timeout }))
  }
}

exports.ReadStream = ReadStream

class WriteStream extends Writable {
  constructor(core) {
    super()
    this.core = core
  }

  _writev(batch, cb) {
    this._writevP(batch).then(cb, cb)
  }

  async _writevP(batch) {
    await this.core.append(batch)
  }
}

exports.WriteStream = WriteStream

class ByteStream extends Readable {
  constructor(core, opts = {}) {
    super()

    this._core = core
    this._index = 0
    this._range = null

    this._byteOffset = opts.byteOffset || 0
    this._byteLength = typeof opts.byteLength === 'number' ? opts.byteLength : -1
    this._prefetch = typeof opts.prefetch === 'number' ? opts.prefetch : 32

    this._applyOffset = this._byteOffset > 0
  }

  _open(cb) {
    this._openp().then(cb, cb)
  }

  _read(cb) {
    this._readp().then(cb, cb)
  }

  async _openp() {
    if (this._byteLength === -1) {
      await this._core.update()
      this._byteLength = Math.max(this._core.byteLength - this._byteOffset, 0)
    }
  }

  async _readp() {
    let data = null

    if (this._byteLength === 0) {
      this.push(null)
      return
    }

    let relativeOffset = 0

    if (this._applyOffset) {
      this._applyOffset = false

      const [block, byteOffset] = await this._core.seek(this._byteOffset)

      this._index = block
      relativeOffset = byteOffset
    }

    this._predownload(this._index + 1)
    data = await this._core.get(this._index++, { valueEncoding: 'binary' })

    if (relativeOffset > 0) data = data.subarray(relativeOffset)

    if (data.byteLength > this._byteLength) data = data.subarray(0, this._byteLength)
    this._byteLength -= data.byteLength

    this.push(data)
    if (this._byteLength === 0) this.push(null)
  }

  _predownload(index) {
    if (this._range) this._range.destroy()
    this._range = this._core.download({ start: index, end: index + this._prefetch, linear: true })
  }

  _destroy(cb) {
    if (this._range) this._range.destroy()
    cb(null)
  }
}

exports.ByteStream = ByteStream
const crypto = require('hypercore-crypto')
const b4a = require('b4a')
const c = require('compact-encoding')
const flat = require('flat-tree')
const { BAD_ARGUMENT } = require('hypercore-errors')
const unslab = require('unslab')

const m = require('./messages')
const multisig = require('./multisig')
const caps = require('./caps')

class Signer {
  constructor(
    manifestHash,
    version,
    index,
    { signature = 'ed25519', publicKey, namespace = caps.DEFAULT_NAMESPACE } = {}
  ) {
    if (!publicKey) throw BAD_ARGUMENT('public key is required for a signer')
    if (signature !== 'ed25519') throw BAD_ARGUMENT('Only Ed25519 signatures are supported')

    this.manifestHash = manifestHash
    this.version = version
    this.signer = index
    this.signature = signature
    this.publicKey = publicKey
    this.namespace = namespace
  }

  _ctx() {
    return this.version === 0 ? this.namespace : this.manifestHash
  }

  verify(batch, signature) {
    return crypto.verify(batch.signable(this._ctx()), signature, this.publicKey)
  }

  sign(batch, keyPair) {
    return crypto.sign(batch.signable(this._ctx()), keyPair.secretKey)
  }
}

class CompatSigner extends Signer {
  constructor(index, signer, legacy) {
    super(null, 0, index, signer)
    this.legacy = legacy
  }

  verify(batch, signature) {
    return crypto.verify(batch.signableCompat(this.legacy), signature, this.publicKey)
  }

  sign(batch, keyPair) {
    return crypto.sign(batch.signableCompat(this.legacy), keyPair.secretKey)
  }
}

module.exports = class Verifier {
  constructor(
    manifestHash,
    manifest,
    { compat = isCompat(manifestHash, manifest), legacy = false } = {}
  ) {
    const self = this

    this.manifestHash = manifestHash
    this.compat = compat || manifest === null
    this.version = this.compat ? 0 : typeof manifest.version === 'number' ? manifest.version : 1
    this.hash = manifest.hash || 'blake2b'
    this.allowPatch = !this.compat && !!manifest.allowPatch
    this.quorum = this.compat ? 1 : defaultQuorum(manifest)

    this.signers = manifest.signers ? manifest.signers.map(createSigner) : []
    this.prologue = this.compat ? null : manifest.prologue || null

    function createSigner(signer, index) {
      return self.compat
        ? new CompatSigner(index, signer, legacy)
        : new Signer(manifestHash, self.version, index, signer)
    }
  }

  _verifyCompat(batch, signature) {
    if (!signature) return false

    if (this.compat || (!this.allowPatch && this.signers.length === 1)) {
      return !!signature && this.signers[0].verify(batch, signature)
    }

    return this._verifyMulti(batch, signature)
  }

  _inflate(signature) {
    if (this.version >= 1) return multisig.inflate(signature)
    const { proofs, patch } = multisig.inflatev0(signature)

    return {
      proofs: proofs.map(proofToVersion1),
      patch
    }
  }

  _verifyMulti(batch, signature) {
    if (!signature || this.quorum === 0) return false

    const { proofs, patch } = this._inflate(signature)
    if (proofs.length < this.quorum) return false

    const tried = new Uint8Array(this.signers.length)
    const nodes = this.allowPatch && patch.length ? toMap(patch) : null

    for (let i = 0; i < this.quorum; i++) {
      const inp = proofs[i]

      let tree = batch

      if (inp.patch && this.allowPatch) {
        tree = batch.clone()

        const upgrade = generateUpgrade(nodes, batch.length, inp.patch)
        const proof = {
          fork: tree.fork,
          block: null,
          hash: null,
          seek: null,
          upgrade,
          manifest: null
        }

        try {
          if (!tree.verifyUpgrade(proof)) return false
        } catch {
          return false
        }
      }

      if (inp.signer >= this.signers.length || tried[inp.signer]) return false
      tried[inp.signer] = 1

      const s = this.signers[inp.signer]
      if (!s.verify(tree, inp.signature)) return false
    }

    return true
  }

  verify(batch, signature) {
    if (this.version === 0) {
      return this._verifyCompat(batch, signature)
    }

    if (this.prologue !== null && batch.length <= this.prologue.length) {
      return batch.length === this.prologue.length && b4a.equals(batch.hash(), this.prologue.hash)
    }

    return this._verifyMulti(batch, signature)
  }

  // TODO: better api for this that is more ... multisig-ey
  sign(batch, keyPair) {
    if (!keyPair || !keyPair.secretKey) throw BAD_ARGUMENT('No key pair was passed')

    for (const s of this.signers) {
      if (b4a.equals(s.publicKey, keyPair.publicKey)) {
        const signature = s.sign(batch, keyPair)
        if (this.signers.length !== 1 || this.version === 0) return signature
        return this.assemble([{ signer: 0, signature, patch: 0, nodes: null }])
      }
    }

    throw BAD_ARGUMENT('Public key is not a declared signer')
  }

  assemble(inputs) {
    return this.version === 0 ? multisig.assemblev0(inputs) : multisig.assemble(inputs)
  }

  static manifestHash(manifest) {
    return manifestHash(manifest)
  }

  static encodeManifest(manifest) {
    return c.encode(m.manifest, manifest)
  }

  static decodeManifest(manifest) {
    return c.decode(m.manifest, manifest)
  }

  static defaultSignerManifest(publicKey) {
    return {
      version: 1,
      hash: 'blake2b',
      allowPatch: false,
      quorum: 1,
      signers: [
        {
          signature: 'ed25519',
          namespace: caps.DEFAULT_NAMESPACE,
          publicKey
        }
      ],
      prologue: null,
      linked: null,
      userData: null
    }
  }

  static fromManifest(manifest, opts) {
    const m = this.createManifest(manifest)
    return new this(manifestHash(m), m, opts)
  }

  static createManifest(inp) {
    if (!inp) return null

    if (b4a.isBuffer(inp)) return Verifier.decodeManifest(inp)

    if (inp.quorum && inp.quorum < 0) throw BAD_ARGUMENT('Quorum cannot be negative')
    if (inp.quorum && inp.signers && inp.quorum > inp.signers.length) {
      throw BAD_ARGUMENT('Quorum should not be higher than the number of signers')
    }

    const manifest = {
      version: getManifestVersion(inp), // defaults to v1
      hash: 'blake2b',
      allowPatch: !!inp.allowPatch,
      quorum: defaultQuorum(inp),
      signers: inp.signers ? inp.signers.map(parseSigner) : [],
      prologue: null,
      linked: null,
      userData: inp.userData || null
    }

    if (inp.hash && inp.hash !== 'blake2b') throw BAD_ARGUMENT('Only Blake2b hashes are supported')

    if (inp.prologue) {
      if (
        !(b4a.isBuffer(inp.prologue.hash) && inp.prologue.hash.byteLength === 32) ||
        !(inp.prologue.length >= 0)
      ) {
        throw BAD_ARGUMENT('Invalid prologue')
      }

      manifest.prologue = inp.prologue
      manifest.prologue.hash = unslab(manifest.prologue.hash)
    }

    if (manifest.userData !== null && manifest.version < 2) {
      throw BAD_ARGUMENT('Invalid field: userData')
    }

    if (inp.linked && inp.linked.length) {
      if (manifest.version < 2) throw BAD_ARGUMENT('Invalid field: linked')

      for (const key of inp.linked) {
        if (!(b4a.isBuffer(key) && key.byteLength === 32)) {
          throw BAD_ARGUMENT('Invalid key')
        }
      }

      manifest.linked = inp.linked
    }

    return manifest
  }

  static isValidManifest(key, manifest) {
    return b4a.equals(key, manifestHash(manifest))
  }

  static isCompat(key, manifest) {
    return isCompat(key, manifest)
  }

  static sign(manifest, batch, keyPair, opts) {
    return Verifier.fromManifest(manifest, opts).sign(batch, keyPair)
  }
}

function toMap(nodes) {
  const m = new Map()
  for (const node of nodes) m.set(node.index, node)
  return m
}

function isCompat(key, manifest) {
  return !!(
    manifest &&
    manifest.signers.length === 1 &&
    b4a.equals(key, manifest.signers[0].publicKey)
  )
}

function defaultQuorum(man) {
  if (typeof man.quorum === 'number') return man.quorum
  if (!man.signers || !man.signers.length) return 0
  return (man.signers.length >> 1) + 1
}

function generateUpgrade(patch, start, length) {
  const upgrade = { start, length, nodes: null, additionalNodes: [], signature: null }

  const from = start * 2
  const to = from + length * 2

  for (const ite = flat.iterator(0); ite.fullRoot(to); ite.nextTree()) {
    if (ite.index + ite.factor / 2 < from) continue

    if (upgrade.nodes === null && ite.contains(from - 2)) {
      upgrade.nodes = []

      const root = ite.index
      const target = from - 2

      ite.seek(target)

      while (ite.index !== root) {
        ite.sibling()
        if (ite.index > target) upgrade.nodes.push(patch.get(ite.index))
        ite.parent()
      }

      continue
    }

    if (upgrade.nodes === null) upgrade.nodes = []
    upgrade.nodes.push(patch.get(ite.index))
  }

  if (upgrade.nodes === null) upgrade.nodes = []
  return upgrade
}

function parseSigner(signer) {
  validateSigner(signer)
  return {
    signature: 'ed25519',
    namespace: unslab(signer.namespace || caps.DEFAULT_NAMESPACE),
    publicKey: unslab(signer.publicKey)
  }
}

function validateSigner(signer) {
  if (!signer || !signer.publicKey) throw BAD_ARGUMENT('Signer missing public key')
  if (signer.signature && signer.signature !== 'ed25519') {
    throw BAD_ARGUMENT('Only Ed25519 signatures are supported')
  }
}

function manifestHash(manifest) {
  const state = { start: 0, end: 32, buffer: null }
  m.manifest.preencode(state, manifest)
  state.buffer = b4a.allocUnsafe(state.end)
  c.raw.encode(state, caps.MANIFEST)
  m.manifest.encode(state, manifest)
  return crypto.hash(state.buffer)
}

function proofToVersion1(proof) {
  return {
    signer: proof.signer,
    signature: proof.signature,
    patch: proof.patch ? proof.patch.length : 0
  }
}

function getManifestVersion(inp) {
  if (typeof inp.version === 'number') return inp.version
  if (inp.linked && inp.linked.length) return 2
  if (inp.userData) return 2
  return 1
}
let COMPAT = true
let BATCH_UINTS = COMPAT ? 65536 : 128
let BATCH_BYTES = BATCH_UINTS * 4
let BATCH = BATCH_BYTES * 8 // in bits
const MAX_REMOTE_BATCHES = 4
const MAX_RANGE = 8 * 256 * 1024
const MIN_RANGE = 32 // 4bit ints
const MAX = 512
const MAX_ANY = 16

class LocalWants {
  constructor(peer) {
    this.destroyed = false
    this.peer = peer
    this.wants = new Map()
    this.free = new Set()
    this.any = null
  }

  add(index, handle) {
    const b = (index - (index & (BATCH - 1))) / BATCH
    return this.addBatch(b, handle)
  }

  addAnyRange(start, length, handle) {
    if (this.destroyed) return null
    if (this.any === null) this.any = []

    if (COMPAT) {
      let r = start & (BATCH - 1)
      start -= r
      length += r
      r = length & (BATCH - 1)
      if (r) length = length - r + BATCH
    }

    for (let i = 0; i < this.any.length; i++) {
      const w = this.any[i]
      if (w.start === start && w.length === length) {
        w.handles.add(handle)
        handle.addWant(w)
        return null
      }
    }

    const w = { wants: this, start, length, any: true, handles: new Set([handle]) }
    this.any.push(w)
    handle.addWant(w)

    return { start, length, any: !COMPAT }
  }

  removeAnyRange(start, length, handle) {
    if (this.any === null) return null

    for (let i = 0; i < this.any.length; i++) {
      const w = this.any[i]
      if (w.start !== start || w.length !== length) continue

      w.handles.delete(handle)
      handle.removeWant(w)
      if (w.handles.size > 0) return null

      const head = this.any.pop()
      if (head !== w) this.any[i] = head
      return { start, length, any: !COMPAT }
    }

    return null
  }

  addBatch(index, handle) {
    if (this.destroyed) return null
    let w = this.wants.get(index)

    if (w) {
      const size = w.handles.size
      w.handles.add(handle)
      if (w.handles.size !== size) {
        handle.addWant(w)
      }
      return null
    }

    // start here is the batch number for simplicity....
    w = { wants: this, start: index, length: 0, any: false, handles: new Set([handle]) }

    if (this.free.has(index)) {
      this.free.delete(index)
      this.wants.set(index, w)
      handle.addWant(w)
      return null
    }

    let unwant = null
    if (this.wants.size + this.free.size === MAX) {
      if (this.free.size === 0) {
        return null
      }
      unwant = this._unwant()
    }

    this.wants.set(index, w)
    handle.addWant(w)

    return {
      want: { start: index * BATCH, length: BATCH, any: false },
      unwant
    }
  }

  remove(index, handle) {
    const b = (index - (index & (BATCH - 1))) / BATCH
    return this.removeBatch(b, handle)
  }

  removeBatch(index, handle) {
    if (this.destroyed) return false

    const w = this.wants.get(index)
    if (!w) {
      return false
    }

    w.handles.delete(handle)
    handle.removeWant(w)

    if (w.handles.size === 0) {
      this.free.add(index)
      this.wants.delete(index)
      return this.free.size === 1 && this.wants.size === MAX - 1
    }

    return false
  }

  destroy() {
    if (this.destroyed) return
    this.destroyed = true

    for (const w of this.wants.values()) {
      for (const handle of w.handles) {
        handle.removeWant(w)
      }
    }

    this.wants.clear()
  }

  _unwant() {
    for (const index of this.free) {
      this.free.delete(index)
      return { start: index * BATCH, length: BATCH, any: false }
    }
    return null
  }
}

class RemoteWants {
  constructor() {
    this.any = null
    this.all = false
    this.size = 0
    this.batches = []
  }

  hasAny(start, length) {
    for (let i = 0; i < this.any.length; i++) {
      const a = this.any[i]
      const e = start + length
      const end = a.start + a.length

      if (a.start <= start && start < end) return true
      if (a.start < e && e <= end) return true
    }

    return this.all
  }

  hasRange(start, length) {
    if (this.any !== null && this.hasAny(start, length)) return true
    if (length === 1) return this.has(start)

    let smallest = -1

    for (let i = 0; i < this.batches.length; i++) {
      const b = this.batches[i]
      if (b.length < smallest || smallest === -1) smallest = b.length
    }

    if (smallest === -1) return this.all

    const r = start & (smallest - 1)
    const end = start + length

    let max = 3

    for (let i = start - r; i < end; i += smallest) {
      if (max-- === 0) return true // just to save work
      if (this.has(i)) return true
    }

    return this.all
  }

  has(index) {
    for (let i = 0; i < this.batches.length; i++) {
      const b = this.batches[i]
      const block = (index - (index & (b.length - 1))) / b.length
      if (b.ranges.has(block)) return true
    }

    return this.all
  }

  add(range) {
    if (range.any) {
      if (this.any === null) this.any = []
      if (this.any.length < MAX_ANY) this.any.push(range)
      else this.all = true
      return true
    }

    if (range.length > MAX_RANGE) return false

    if (!validateBatchRange(range)) {
      this.all = true
      return true
    }

    if (this.size >= MAX) {
      this.all = true
      return true
    }

    for (let i = 0; i < this.batches.length; i++) {
      const b = this.batches[i]
      if (b.length === range.length) {
        b.ranges.add(range.start / range.length)
        this.size++
        return true
      }
    }

    if (this.batches.length >= MAX_REMOTE_BATCHES) {
      this.all = true
      return true
    }

    this.batches.push({
      length: range.length,
      ranges: new Set([range.start / range.length])
    })
    this.size++

    return true
  }

  remove(range) {
    if (range.any) {
      if (this.any === null) return false

      for (let i = 0; i < this.any.length; i++) {
        const a = this.any[i]

        if (a.start === range.start && a.length === range.length) {
          const head = this.any.pop()
          if (head !== a) this.any[i] = head
          return true
        }
      }

      return false
    }

    if (!validateBatchRange(range)) return false

    for (let i = 0; i < this.batches.length; i++) {
      const b = this.batches[i]
      if (b.length !== range.length) continue

      const size = b.ranges.size
      b.ranges.delete(range.start / range.length)
      if (b.ranges.size !== size) this.size--

      if (b.ranges.size === 0) {
        const head = this.batches.pop()
        if (head !== b) this.batches[i] = head
      }

      return true
    }

    return false
  }
}

function UPDATE_COMPAT(isCompat) {
  COMPAT = isCompat
  BATCH_UINTS = COMPAT ? 65536 : 128
  BATCH_BYTES = BATCH_UINTS * 4
  BATCH = BATCH_BYTES * 8 // in bits
  exports.WANT_BATCH = BATCH
}

exports.LocalWants = LocalWants
exports.RemoteWants = RemoteWants
exports.WANT_BATCH = BATCH
exports.UPDATE_COMPAT = UPDATE_COMPAT

function validateBatchRange(range) {
  if (range.length > MAX_RANGE || range.length < MIN_RANGE) {
    return false
  }
  // check if power of two
  if ((range.length & (range.length - 1)) !== 0) {
    return false
  }
  // start must be a multiple of the length
  if (range.start & (range.length - 1)) {
    return false
  }

  return true
}
{
  "name": "hypercore",
  "version": "11.35.4",
  "description": "Hypercore is a secure, distributed append-only log",
  "main": "index.js",
  "scripts": {
    "format": "prettier --write .",
    "lint": "prettier --check . && lunte",
    "test": "brittle test/all.js",
    "test:bare": "bare test/all.js",
    "test:generate": "brittle -r test/all.js test/*.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/hypercore.git"
  },
  "contributors": [
    {
      "name": "Mathias Buus",
      "email": "mathiasbuus@gmail.com",
      "url": "https://mafinto.sh"
    },
    {
      "name": "Andrew Osheroff",
      "email": "andrewosh@gmail.com",
      "url": "https://andrewosh.com"
    }
  ],
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/holepunchto/hypercore/issues"
  },
  "homepage": "https://github.com/holepunchto/hypercore#readme",
  "files": [
    "index.js",
    "errors.js",
    "messages.js",
    "lib/**.js"
  ],
  "imports": {
    "events": {
      "bare": "bare-events",
      "default": "events"
    }
  },
  "dependencies": {
    "@hyperswarm/secret-stream": "^6.0.0",
    "b4a": "^1.1.0",
    "bare-events": "^2.2.0",
    "big-sparse-array": "^1.0.3",
    "compact-encoding": "^3.0.0",
    "fast-fifo": "^1.3.0",
    "flat-tree": "^1.9.0",
    "hypercore-crypto": "^3.2.1",
    "hypercore-errors": "^1.5.0",
    "hypercore-id-encoding": "^1.2.0",
    "hypercore-storage": "^3.2.0",
    "is-options": "^1.0.1",
    "nanoassert": "^2.0.0",
    "protomux": "^3.5.0",
    "quickbit-universal": "^2.2.0",
    "random-array-iterator": "^1.0.0",
    "safety-catch": "^1.0.1",
    "sodium-universal": "^5.0.1",
    "streamx": "^2.12.4",
    "unslab": "^1.3.0",
    "z32": "^1.0.0"
  },
  "devDependencies": {
    "brittle": "^3.0.0",
    "debugging-stream": "^3.1.0",
    "hyperswarm": "^4.3.6",
    "lunte": "^1.3.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^2.0.0",
    "rache": "^1.0.0",
    "range-parser": "^1.2.1",
    "resolve-reject-promise": "^1.1.0",
    "speedometer": "^1.1.0",
    "test-tmp": "^1.0.2",
    "tiny-byte-size": "^1.1.0",
    "udx-native": "^1.6.1",
    "uncaughts": "^1.1.0"
  }
}
{
  "name": "hyperschema",
  "version": "1.23.0",
  "description": "Create registries of declarative compact-encoding schemas",
  "files": [
    "lib/*.js",
    "builder.mjs",
    "builder.cjs",
    "builder.d.ts",
    "runtime.mjs",
    "runtime.cjs",
    "primitives.mjs",
    "primitives.cjs"
  ],
  "exports": {
    "./package": "./package.json",
    ".": {
      "types": "./builder.d.ts",
      "import": "./builder.mjs",
      "default": "./builder.cjs"
    },
    "./runtime": {
      "import": "./runtime.mjs",
      "default": "./runtime.cjs"
    },
    "./primitives": {
      "import": "./primitives.mjs",
      "default": "./primitives.cjs"
    }
  },
  "imports": {
    "fs": {
      "bare": "bare-fs",
      "default": "fs"
    },
    "path": {
      "bare": "bare-path",
      "default": "path"
    }
  },
  "scripts": {
    "format": "prettier . --write",
    "test": "prettier . --check && brittle test/index.js",
    "test:bare": "prettier . --check && bare test/index.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/hyperschema.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/hyperschema/issues"
  },
  "homepage": "https://github.com/holepunchto/hyperschema#readme",
  "dependencies": {
    "bare-fs": "^4.0.1",
    "compact-encoding": "^3.0.0",
    "generate-object-property": "^2.0.0",
    "generate-string": "^1.0.1"
  },
  "devDependencies": {
    "brittle": "^3.7.0",
    "hyperschema-test": "^1.1.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^2.0.0",
    "test-tmp": "^1.3.0"
  }
}
module.exports = {
  c: require('compact-encoding')
}
const b4a = require('b4a')

const EMPTY = b4a.alloc(0)
const MAX = b4a.from([0xff])
const BUFFER = {}

BUFFER.preencode = function (state, buf) {
  if (buf === null) buf = EMPTY

  let i = 0
  let extra = 3

  while ((i = b4a.indexOf(buf, 0x00, i)) > -1) {
    i++
    extra++
  }

  state.end += buf.byteLength + extra
}

BUFFER.encode = function (state, buf) {
  if (buf === null) buf = EMPTY

  state.buffer[state.start++] = 0x00

  let prev = 0
  let i = 0

  while ((i = b4a.indexOf(buf, 0x00, i)) > -1) {
    const slice = buf.subarray(prev, ++i)

    state.buffer.set(slice, state.start)
    state.start += slice.byteLength
    state.buffer[state.start++] = 0x02
    prev = i
  }

  const slice = buf.subarray(prev)

  state.buffer.set(slice, state.start)
  state.start += slice.byteLength

  state.buffer[state.start++] = 0x00
  state.buffer[state.start++] = 0x01
}

BUFFER.decode = function (state) {
  if (state.start >= state.end) throw new Error('Out of bounds')
  if (state.buffer[state.start++] !== 0x00) {
    throw new Error('Invalid start of string')
  }

  let escaped = null

  let prev = state.start
  let i = state.start

  while ((i = b4a.indexOf(state.buffer, 0x00, i)) > -1) {
    const next = ++i < state.end ? state.buffer[i] : 0x00

    i++

    if (next === 0x01) {
      break
    }

    if (next === 0x02) {
      if (escaped === null) escaped = []
      escaped.push(state.buffer.subarray(prev, i - 1))
      prev = i
      continue
    }

    throw new Error('Unknown value in terminator')
  }

  if (i === -1) {
    throw new Error('No terminator found')
  }

  state.start = i
  const last = state.buffer.subarray(prev, i - 2)
  if (escaped === null) return last

  escaped.push(last)
  return b4a.concat(escaped)
}

// TODO: can be optimised a lot

const STRING = {}

STRING.preencode = function (state, str) {
  BUFFER.preencode(state, b4a.from(str || ''))
}

STRING.encode = function (state, str) {
  BUFFER.encode(state, b4a.from(str || ''))
}

STRING.decode = function (state, str) {
  return b4a.toString(BUFFER.decode(state))
}

const UINT = {}

UINT.preencode = function (state, n) {
  state.end += n <= 0xfb ? 1 : n <= 0xffff ? 3 : n <= 0xffffffff ? 5 : n === Infinity ? 1 : 9
}

UINT.encode = function (state, n) {
  if (n === Infinity) {
    state.buffer[state.start++] = 0xff
    return
  }

  if (n <= 0xfb) {
    state.buffer[state.start++] = n
    return
  }

  if (n <= 0xffff) {
    state.buffer[state.start++] = 0xfc
    state.buffer[state.start++] = n >>> 8
    state.buffer[state.start++] = n
    return
  }

  if (n <= 0xffffffff) {
    state.buffer[state.start++] = 0xfd
    encodeUint32(state, n)
    return
  }

  if (Number.isSafeInteger(n)) {
    state.buffer[state.start++] = 0xfe

    const r = Math.floor(n / 0x100000000)
    encodeUint32(state, r)
    encodeUint32(state, n)
    return
  }

  throw new Error('Invalid number ' + n)
}

UINT.decode = function (state) {
  if (state.start >= state.end) throw new Error('Out of bounds')

  const a = state.buffer[state.start++]

  if (a <= 0xfb) return a

  if (a === 0xfc) {
    if (state.end - state.start < 2) throw new Error('Out of bounds')
    return state.buffer[state.start++] * 0x100 + state.buffer[state.start++]
  }

  if (a === 0xfd) {
    return decodeUint32(state)
  }

  if (a === 0xfe) {
    return decodeUint32(state) * 0x100000000 + decodeUint32(state)
  }

  return Infinity
}

const INT = {}

INT.preencode = function (state, n) {
  // One asymmetric condition
  if (n >= -0xff && n < 0) {
    state.end += 2
    return
  }
  n = Math.abs(n)
  state.end += n <= 0xf6 ? 1 : n <= 0xffff ? 3 : n <= 0xffffffff ? 5 : n === Infinity ? 1 : 9
}

INT.encode = function (state, n) {
  if (n === Infinity) {
    state.buffer[state.start++] = 0xff
    return
  }

  if (n === -Infinity) {
    state.buffer[state.start++] = 0x00
    return
  }

  // Negative 64 bit
  if (n < -0xffffffff) {
    if (!Number.isSafeInteger(n)) throw new Error('Invalid number ' + n)
    state.buffer[state.start++] = 0x01

    const r = Math.floor(-n / 0x100000000)
    encodeUint32(state, -r + 0xffffffff)
    encodeUint32(state, -(-n % 0x100000000) + 0xffffffff)
    return
  }

  // Negative 32 bit
  if (n < -0xffff) {
    state.buffer[state.start++] = 0x02

    n += 0xffffffff
    encodeUint32(state, n)
    return
  }

  // Negative 16bit
  if (n < -0xff) {
    state.buffer[state.start++] = 0x03

    n += 0xffff
    state.buffer[state.start++] = n >>> 8
    state.buffer[state.start++] = n
    return
  }

  // Negative 8bit
  if (n < 0) {
    state.buffer[state.start++] = 0x04

    n += 0xff
    state.buffer[state.start++] = n
    return
  }

  // Remainder of 8bit space for positive int
  if (n <= 0xf6) {
    state.buffer[state.start++] = n + 0x05
    return
  }

  // Positive 16bit
  if (n <= 0xffff) {
    state.buffer[state.start++] = 0xfc
    state.buffer[state.start++] = n >>> 8
    state.buffer[state.start++] = n
    return
  }

  // Positive 32bit
  if (n <= 0xffffffff) {
    state.buffer[state.start++] = 0xfd
    encodeUint32(state, n)
    return
  }

  // Positive 64bit
  if (Number.isSafeInteger(n)) {
    state.buffer[state.start++] = 0xfe

    const r = Math.floor(n / 0x100000000)
    encodeUint32(state, r)
    encodeUint32(state, n)
    return
  }

  throw new Error('Invalid number ' + n)
}

INT.decode = function (state) {
  if (state.start >= state.end) throw new Error('Out of bounds')

  const a = state.buffer[state.start++]

  if (a === 0x00) return -Infinity

  if (a === 0x01) {
    // Split (2^64 - 1) into its 32 bit components
    // r * 0x100000000 + n - (2^64 - 1)
    // r * 0x100000000 + n - (0xffffffff * 0x100000000) - 0xffffffff
    // (r - 0xffffffff) * 0x100000000 + n - 0xffffffff
    return (decodeUint32(state) - 0xffffffff) * 0x100000000 + decodeUint32(state) - 0xffffffff
  }

  if (a === 0x02) {
    return decodeUint32(state) - 0xffffffff
  }

  if (a === 0x03) {
    if (state.end - state.start < 2) throw new Error('Out of bounds')
    return state.buffer[state.start++] * 0x100 + state.buffer[state.start++] - 0xffff
  }

  if (a === 0x04) return state.buffer[state.start++] - 0xff

  if (a <= 0xfb) return a - 0x05

  if (a === 0xfc) {
    if (state.end - state.start < 2) throw new Error('Out of bounds')
    return state.buffer[state.start++] * 0x100 + state.buffer[state.start++]
  }

  if (a === 0xfd) {
    return decodeUint32(state)
  }

  if (a === 0xfe) {
    return decodeUint32(state) * 0x100000000 + decodeUint32(state)
  }

  return Infinity
}

const DATE = {}

DATE.preencode = (state, d) => INT.preencode(state, d.getTime())
DATE.encode = (state, d) => INT.encode(state, d.getTime())
DATE.decode = (state) => new Date(INT.decode(state))

const BOOL = {}

BOOL.preencode = (state, b) => UINT.preencode(state, b ? 1 : 0)
BOOL.encode = (state, b) => UINT.encode(state, b ? 1 : 0)
BOOL.decode = (state) => !!UINT.decode(state)

module.exports = class IndexEncoder {
  constructor(encodings, { prefix = -1 } = {}) {
    this.encodings = encodings
    this.prefix = prefix
  }

  static BUFFER = BUFFER
  static STRING = STRING
  static UINT = UINT
  static INT = INT
  static DATE = DATE
  static BOOL = BOOL

  static lookup(c) {
    switch (c) {
      case 'uint':
        return UINT
      case 'uint8':
        return UINT
      case 'uint16':
        return UINT
      case 'uint24':
        return UINT
      case 'uint32':
        return UINT
      case 'uint40':
        return UINT
      case 'uint48':
        return UINT
      case 'uint56':
        return UINT
      case 'uint64':
        return UINT
      case 'int':
        return INT
      case 'int8':
        return INT
      case 'int16':
        return INT
      case 'int24':
        return INT
      case 'int32':
        return INT
      case 'int40':
        return INT
      case 'int48':
        return INT
      case 'int56':
        return INT
      case 'int64':
        return INT
      case 'string':
        return STRING
      case 'utf8':
        return STRING
      case 'ascii':
        return STRING
      case 'hex':
        return STRING
      case 'base64':
        return STRING
      case 'fixed32':
        return BUFFER
      case 'fixed64':
        return BUFFER
      case 'buffer':
        return BUFFER
      case 'date':
        return DATE
      case 'bool':
        return BOOL
    }

    throw new Error('Unknown type')
  }

  encode(keys) {
    return this._encode(keys, false)
  }

  _encode(keys, terminate) {
    if (b4a.isBuffer(keys)) return keys

    const state = { start: 0, end: 0, buffer: null }

    if (this.prefix !== -1) UINT.preencode(state, this.prefix)
    for (let i = 0; i < keys.length; i++) {
      this.encodings[i].preencode(state, keys[i])
    }

    if (terminate && keys.length < this.encodings.length) {
      state.end++
    }

    state.buffer = b4a.allocUnsafe(state.end)

    if (this.prefix !== -1) UINT.encode(state, this.prefix)
    for (let i = 0; i < keys.length; i++) {
      this.encodings[i].encode(state, keys[i])
    }

    if (terminate && keys.length < this.encodings.length) {
      state.buffer[state.start++] = MAX[0]
    }

    return state.buffer
  }

  decode(buffer) {
    const state = { start: 0, end: buffer.byteLength, buffer }
    const result = []

    if (this.prefix !== -1) UINT.decode(state)
    for (const enc of this.encodings) {
      const key = state.start < state.end ? enc.decode(state) : enc === UINT ? 0 : null
      result.push(key)
    }

    return result
  }

  encodeRange({ gt, gte, lt, lte }) {
    const range = {
      gt: gt && this._encode(gt, true),
      gte: gte && this._encode(gte, false),
      lt: lt && this._encode(lt, false),
      lte: lte && this._encode(lte, true)
    }

    if (this.prefix !== -1) {
      if (!gt && !gte) range.gte = encodeUint(this.prefix)
      if (!lt && !lte) range.lt = encodeUint(this.prefix + 1)
    }

    return range
  }
}

function encodeUint(n) {
  const state = { start: 0, end: 0, buffer: null }
  UINT.preencode(state, n)
  state.buffer = b4a.allocUnsafe(state.end)
  UINT.encode(state, n)
  return state.buffer
}

function encodeUint32(state, n) {
  state.buffer[state.start++] = n >>> 24
  state.buffer[state.start++] = n >>> 16
  state.buffer[state.start++] = n >>> 8
  state.buffer[state.start++] = n
}

function decodeUint32(state) {
  if (state.end - state.start < 4) throw new Error('Out of bounds')
  return (
    state.buffer[state.start++] * 0x1000000 +
    state.buffer[state.start++] * 0x10000 +
    state.buffer[state.start++] * 0x100 +
    state.buffer[state.start++]
  )
}
{
  "name": "index-encoder",
  "version": "3.5.0",
  "description": "Encode multiple values into sorted keys",
  "main": "index.js",
  "dependencies": {
    "b4a": "^1.6.4"
  },
  "devDependencies": {
    "brittle": "^3.2.1",
    "hyperbee": "^2.10.5",
    "hypercore": "^11.0.0",
    "lunte": "^1.0.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^2.0.0"
  },
  "scripts": {
    "format": "prettier --write .",
    "lint": "prettier --check . && lunte",
    "test": "npm run test:node && npm run test:bare",
    "test:node": "brittle-node test.js",
    "test:bare": "brittle-bare test.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/holepunchto/index-encoder.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/index-encoder/issues"
  },
  "homepage": "https://github.com/holepunchto/index-encoder"
}
const b4a = require('b4a')

module.exports = function isOptions (opts) {
  return typeof opts === 'object' && opts && !b4a.isBuffer(opts)
}
{
  "name": "is-options",
  "version": "1.0.2",
  "description": "Easily check if input is an options map",
  "main": "index.js",
  "dependencies": {
    "b4a": "^1.1.1"
  },
  "devDependencies": {
    "standard": "^11.0.1",
    "tape": "^4.9.0"
  },
  "scripts": {
    "test": "standard && tape test.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/mafintosh/is-options.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/mafintosh/is-options/issues"
  },
  "homepage": "https://github.com/mafintosh/is-options"
}
module.exports = assert

class AssertionError extends Error {}
AssertionError.prototype.name = 'AssertionError'

/**
 * Minimal assert function
 * @param  {any} t Value to check if falsy
 * @param  {string=} m Optional assertion error message
 * @throws {AssertionError}
 */
function assert (t, m) {
  if (!t) {
    var err = new AssertionError(m)
    if (Error.captureStackTrace) Error.captureStackTrace(err, assert)
    throw err
  }
}
{
  "name": "nanoassert",
  "version": "2.0.0",
  "description": "Nanoscale assertion module",
  "main": "index.js",
  "dependencies": {},
  "devDependencies": {
    "tape": "^4.9.1"
  },
  "scripts": {
    "test": "tape test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/emilbayes/nanoassert.git"
  },
  "keywords": [
    "assert",
    "unassert",
    "power-assert",
    "tiny",
    "nano",
    "pico"
  ],
  "author": "Emil Bay <github@tixz.dk>",
  "license": "ISC",
  "bugs": {
    "url": "https://github.com/emilbayes/nanoassert/issues"
  },
  "homepage": "https://github.com/emilbayes/nanoassert#readme"
}
/* eslint-disable camelcase */
const sodium = require('sodium-universal')
const assert = require('nanoassert')
const b4a = require('b4a')

const DHLEN = sodium.crypto_scalarmult_ed25519_BYTES
const PKLEN = sodium.crypto_scalarmult_ed25519_BYTES
const SCALARLEN = sodium.crypto_scalarmult_ed25519_BYTES
const SKLEN = sodium.crypto_sign_SECRETKEYBYTES
const ALG = 'Ed25519'

module.exports = {
  DHLEN,
  PKLEN,
  SCALARLEN,
  SKLEN,
  ALG,
  name: ALG,
  generateKeyPair,
  dh
}

function generateKeyPair (privKey) {
  if (privKey) return generateSeedKeyPair(privKey.subarray(0, 32))

  const keyPair = {}
  keyPair.secretKey = b4a.alloc(SKLEN)
  keyPair.publicKey = b4a.alloc(PKLEN)

  sodium.crypto_sign_keypair(keyPair.publicKey, keyPair.secretKey)
  return keyPair
}

function generateSeedKeyPair (seed) {
  const keyPair = {}
  keyPair.secretKey = b4a.alloc(SKLEN)
  keyPair.publicKey = b4a.alloc(PKLEN)

  sodium.crypto_sign_seed_keypair(keyPair.publicKey, keyPair.secretKey, seed)
  return keyPair
}

function dh (publicKey, { scalar, secretKey }) {
  // tweaked keys expose scalar directly
  if (!scalar) {
    assert(secretKey.byteLength === SKLEN)

    // libsodium stores seed not actual scalar
    const sk = b4a.alloc(64)
    sodium.crypto_hash_sha512(sk, secretKey.subarray(0, 32))
    sk[0] &= 248
    sk[31] &= 127
    sk[31] |= 64

    scalar = sk.subarray(0, 32)
  }

  assert(scalar.byteLength === SCALARLEN)
  assert(publicKey.byteLength === PKLEN)

  const output = b4a.alloc(DHLEN)

  // we clamp if necessary above
  sodium.crypto_scalarmult_ed25519_noclamp(
    output,
    scalar,
    publicKey
  )

  return output
}
{
  "name": "noise-curve-ed",
  "version": "2.1.0",
  "description": "Ed25519 elliptic curve operations for [`noise-handshake`](https://github.com/chm-diederichs/noise-handshake)",
  "main": "index.js",
  "scripts": {
    "test": "npx standard && tape test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/chm-diederichs/noise-curve-ed.git"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "bugs": {
    "url": "https://github.com/chm-diederichs/noise-curve-ed/issues"
  },
  "homepage": "https://github.com/chm-diederichs/noise-curve-ed#readme",
  "dependencies": {
    "b4a": "^1.1.0",
    "nanoassert": "^2.0.0",
    "sodium-universal": "^5.0.0"
  },
  "devDependencies": {
    "noise-handshake": "^3.0.0",
    "standard": "^16.0.3",
    "tape": "^5.2.2"
  }
}
const sodium = require('sodium-universal')
const b4a = require('b4a')

module.exports = class CipherState {
  constructor (key) {
    this.key = key || null
    this.nonce = 0
    this.CIPHER_ALG = 'ChaChaPoly'
  }

  initialiseKey (key) {
    this.key = key
    this.nonce = 0
  }

  setNonce (nonce) {
    this.nonce = nonce
  }

  encrypt (plaintext, ad) {
    if (!this.hasKey) return plaintext
    if (!ad) ad = b4a.alloc(0)

    const ciphertext = encryptWithAD(this.key, this.nonce, ad, plaintext)
    if (ciphertext.length > 65535) throw new Error(`ciphertext length of ${ciphertext.length} exceeds maximum Noise message length of 65535`)
    this.nonce++

    return ciphertext
  }

  decrypt (ciphertext, ad) {
    if (!this.hasKey) return ciphertext
    if (!ad) ad = b4a.alloc(0)
    if (ciphertext.length > 65535) throw new Error(`ciphertext length of ${ciphertext.length} exceeds maximum Noise message length of 65535`)

    const plaintext = decryptWithAD(this.key, this.nonce, ad, ciphertext)
    this.nonce++

    return plaintext
  }

  get hasKey () {
    return this.key !== null
  }

  _clear () {
    sodium.sodium_memzero(this.key)
    this.key = null
    this.nonce = null
  }

  static get MACBYTES () {
    return 16
  }

  static get NONCEBYTES () {
    return 8
  }

  static get KEYBYTES () {
    return 32
  }
}

function encryptWithAD (key, counter, additionalData, plaintext) {
  // for our purposes, additionalData will always be a pubkey so we encode from hex
  if (!b4a.isBuffer(additionalData)) additionalData = b4a.from(additionalData, 'hex')
  if (!b4a.isBuffer(plaintext)) plaintext = b4a.from(plaintext, 'hex')

  const nonce = b4a.alloc(sodium.crypto_aead_chacha20poly1305_ietf_NPUBBYTES)
  const view = new DataView(nonce.buffer, nonce.byteOffset, nonce.byteLength)
  view.setUint32(4, counter, true)

  const ciphertext = b4a.alloc(plaintext.byteLength + sodium.crypto_aead_chacha20poly1305_ietf_ABYTES)

  sodium.crypto_aead_chacha20poly1305_ietf_encrypt(ciphertext, plaintext, additionalData, null, nonce, key)
  return ciphertext
}

function decryptWithAD (key, counter, additionalData, ciphertext) {
  // for our purposes, additionalData will always be a pubkey so we encode from hex
  if (!b4a.isBuffer(additionalData)) additionalData = b4a.from(additionalData, 'hex')
  if (!b4a.isBuffer(ciphertext)) ciphertext = b4a.from(ciphertext, 'hex')

  const nonce = b4a.alloc(sodium.crypto_aead_chacha20poly1305_ietf_NPUBBYTES)
  const view = new DataView(nonce.buffer, nonce.byteOffset, nonce.byteLength)
  view.setUint32(4, counter, true)

  const plaintext = b4a.alloc(ciphertext.byteLength - sodium.crypto_aead_chacha20poly1305_ietf_ABYTES)

  sodium.crypto_aead_chacha20poly1305_ietf_decrypt(plaintext, null, ciphertext, additionalData, nonce, key)
  return plaintext
}
/* eslint-disable camelcase */
const {
  crypto_kx_SEEDBYTES,
  crypto_kx_keypair,
  crypto_kx_seed_keypair,
  crypto_scalarmult_BYTES,
  crypto_scalarmult_SCALARBYTES,
  crypto_scalarmult,
  crypto_scalarmult_base
} = require('sodium-universal')

const assert = require('nanoassert')
const b4a = require('b4a')

const DHLEN = crypto_scalarmult_BYTES
const PKLEN = crypto_scalarmult_BYTES
const SKLEN = crypto_scalarmult_SCALARBYTES
const SEEDLEN = crypto_kx_SEEDBYTES
const ALG = '25519'

module.exports = {
  DHLEN,
  PKLEN,
  SKLEN,
  SEEDLEN,
  ALG,
  generateKeyPair,
  generateSeedKeyPair,
  dh
}

function generateKeyPair (privKey) {
  const keyPair = {}

  keyPair.secretKey = privKey || b4a.alloc(SKLEN)
  keyPair.publicKey = b4a.alloc(PKLEN)

  if (privKey) {
    crypto_scalarmult_base(keyPair.publicKey, keyPair.secretKey)
  } else {
    crypto_kx_keypair(keyPair.publicKey, keyPair.secretKey)
  }

  return keyPair
}

function generateSeedKeyPair (seed) {
  assert(seed.byteLength === SKLEN)

  const keyPair = {}
  keyPair.secretKey = b4a.alloc(SKLEN)
  keyPair.publicKey = b4a.alloc(PKLEN)

  crypto_kx_seed_keypair(keyPair.publicKey, keyPair.secretKey, seed)
  return keyPair
}

function dh (publicKey, { secretKey }) {
  assert(secretKey.byteLength === SKLEN)
  assert(publicKey.byteLength === PKLEN)

  const output = b4a.alloc(DHLEN)

  crypto_scalarmult(
    output,
    secretKey,
    publicKey
  )

  return output
}
const hmacBlake2b = require('./hmac')
const b4a = require('b4a')

const HASHLEN = 64

module.exports = {
  hkdf,
  HASHLEN
}

// HMAC-based Extract-and-Expand KDF
// https://www.ietf.org/rfc/rfc5869.txt

function hkdf (salt, inputKeyMaterial, info = '', length = 2 * HASHLEN) {
  const pseudoRandomKey = hkdfExtract(salt, inputKeyMaterial)
  return hkdfExpand(pseudoRandomKey, info, length)
}

function hkdfExtract (salt, inputKeyMaterial) {
  const hmac = b4a.alloc(HASHLEN)
  return hmacDigest(hmac, salt, inputKeyMaterial)
}

function hkdfExpand (key, info, length) {
  // Put in dedicated slab to avoid keeping shared slab from being gc'ed
  const buffer = b4a.allocUnsafeSlow(length)

  const infoBuf = b4a.from(info)
  let prev = infoBuf

  const result = []
  for (let i = 0; i < length; i += HASHLEN) {
    const pos = b4a.from([(i / HASHLEN) + 1])

    const out = buffer.subarray(i, i + HASHLEN)
    result.push(out)

    prev = hmacDigest(out, key, [prev, infoBuf, pos])
  }

  return result
}

function hmacDigest (out, key, input) {
  hmacBlake2b(out, input, key)
  return out
}
/* eslint-disable camelcase */
const b4a = require('b4a')
const { sodium_memzero, crypto_generichash, crypto_generichash_batch } = require('sodium-universal')

const HASHLEN = 64
const BLOCKLEN = 128
const scratch = b4a.alloc(BLOCKLEN * 3)
const HMACKey = scratch.subarray(BLOCKLEN * 0, BLOCKLEN * 1)
const OuterKeyPad = scratch.subarray(BLOCKLEN * 1, BLOCKLEN * 2)
const InnerKeyPad = scratch.subarray(BLOCKLEN * 2, BLOCKLEN * 3)

// Post-fill is done in the cases where someone caught an exception that
// happened before we were able to clear data at the end

module.exports = function hmac (out, batch, key) {
  if (key.byteLength > BLOCKLEN) {
    crypto_generichash(HMACKey.subarray(0, HASHLEN), key)
    sodium_memzero(HMACKey.subarray(HASHLEN))
  } else {
    // Covers key <= BLOCKLEN
    HMACKey.set(key)
    sodium_memzero(HMACKey.subarray(key.byteLength))
  }

  for (let i = 0; i < HMACKey.byteLength; i++) {
    OuterKeyPad[i] = 0x5c ^ HMACKey[i]
    InnerKeyPad[i] = 0x36 ^ HMACKey[i]
  }
  sodium_memzero(HMACKey)

  crypto_generichash_batch(out, [InnerKeyPad].concat(batch))
  sodium_memzero(InnerKeyPad)
  crypto_generichash_batch(out, [OuterKeyPad, out])
  sodium_memzero(OuterKeyPad)
}

module.exports.BYTES = HASHLEN
module.exports.KEYBYTES = BLOCKLEN
const assert = require('nanoassert')
const b4a = require('b4a')

const SymmetricState = require('./symmetric-state')
const { HASHLEN } = require('./hkdf')

const PRESHARE_IS = Symbol('initiator static key preshared')
const PRESHARE_RS = Symbol('responder static key preshared')

const TOK_PSK = Symbol('psk')

const TOK_S = Symbol('s')
const TOK_E = Symbol('e')

const TOK_ES = Symbol('es')
const TOK_SE = Symbol('se')
const TOK_EE = Symbol('ee')
const TOK_SS = Symbol('ss')

const HANDSHAKES = Object.freeze({
  NN: [
    [TOK_E],
    [TOK_E, TOK_EE]
  ],
  NNpsk0: [
    [TOK_PSK, TOK_E],
    [TOK_E, TOK_EE]
  ],
  XX: [
    [TOK_E],
    [TOK_E, TOK_EE, TOK_S, TOK_ES],
    [TOK_S, TOK_SE]
  ],
  XXpsk0: [
    [TOK_PSK, TOK_E],
    [TOK_E, TOK_EE, TOK_S, TOK_ES],
    [TOK_S, TOK_SE]
  ],
  IK: [
    PRESHARE_RS,
    [TOK_E, TOK_ES, TOK_S, TOK_SS],
    [TOK_E, TOK_EE, TOK_SE]
  ],
  XK: [
    PRESHARE_RS,
    [TOK_E, TOK_ES],
    [TOK_E, TOK_EE],
    [TOK_S, TOK_SE]
  ]
})

class Writer {
  constructor () {
    this.size = 0
    this.buffers = []
  }

  push (b) {
    this.size += b.byteLength
    this.buffers.push(b)
  }

  end () {
    const all = b4a.alloc(this.size)
    let offset = 0
    for (const b of this.buffers) {
      all.set(b, offset)
      offset += b.byteLength
    }
    return all
  }
}

class Reader {
  constructor (buf) {
    this.offset = 0
    this.buffer = buf
  }

  shift (n) {
    const start = this.offset
    const end = this.offset += n
    if (end > this.buffer.byteLength) throw new Error('Insufficient bytes')
    return this.buffer.subarray(start, end)
  }

  end () {
    return this.shift(this.buffer.byteLength - this.offset)
  }
}

module.exports = class NoiseState extends SymmetricState {
  constructor (pattern, initiator, staticKeypair, opts = {}) {
    super(opts)

    this.s = staticKeypair || this.curve.generateKeyPair()
    this.e = null

    this.psk = null
    if (opts && opts.psk) this.psk = opts.psk

    this.re = null
    this.rs = null

    this.pattern = pattern
    this.handshake = HANDSHAKES[this.pattern].slice()

    this.isPskHandshake = !!this.psk && hasPskToken(this.handshake)

    this.protocol = b4a.from([
      'Noise',
      this.pattern,
      this.DH_ALG,
      this.CIPHER_ALG,
      'BLAKE2b'
    ].join('_'))

    this.initiator = initiator
    this.complete = false

    this.rx = null
    this.tx = null
    this.hash = null
  }

  initialise (prologue, remoteStatic) {
    if (this.protocol.byteLength <= HASHLEN) this.digest.set(this.protocol)
    else this.mixHash(this.protocol)

    this.chainingKey = b4a.from(this.digest)

    this.mixHash(prologue)

    while (!Array.isArray(this.handshake[0])) {
      const message = this.handshake.shift()

      // handshake steps should be as arrays, only
      // preshare tokens are provided otherwise
      assert(message === PRESHARE_RS || message === PRESHARE_IS,
        'Unexpected pattern')

      const takeRemoteKey = this.initiator
        ? message === PRESHARE_RS
        : message === PRESHARE_IS

      if (takeRemoteKey) this.rs = remoteStatic

      const key = takeRemoteKey ? this.rs : this.s.publicKey
      assert(key != null, 'Remote pubkey required')

      this.mixHash(key)
    }
  }

  final () {
    const [k1, k2] = this.split()

    this.tx = this.initiator ? k1 : k2
    this.rx = this.initiator ? k2 : k1

    this.complete = true
    this.hash = this.getHandshakeHash()

    this._clear()
  }

  recv (buf) {
    const r = new Reader(buf)

    for (const pattern of this.handshake.shift()) {
      switch (pattern) {
        case TOK_PSK :
          this.mixKeyAndHash(this.psk)
          break

        case TOK_E :
          this.re = r.shift(this.curve.PKLEN)
          this.mixHash(this.re)
          if (this.isPskHandshake) this.mixKeyNormal(this.re)
          break

        case TOK_S : {
          const klen = this.hasKey ? this.curve.PKLEN + 16 : this.curve.PKLEN
          this.rs = this.decryptAndHash(r.shift(klen))
          break
        }

        case TOK_EE :
        case TOK_ES :
        case TOK_SE :
        case TOK_SS : {
          const useStatic = keyPattern(pattern, this.initiator)

          const localKey = useStatic.local ? this.s : this.e
          const remoteKey = useStatic.remote ? this.rs : this.re

          this.mixKey(remoteKey, localKey)
          break
        }

        default :
          throw new Error('Unexpected message')
      }
    }

    const payload = this.decryptAndHash(r.end())

    if (!this.handshake.length) this.final()
    return payload
  }

  send (payload = b4a.alloc(0)) {
    const w = new Writer()

    for (const pattern of this.handshake.shift()) {
      switch (pattern) {
        case TOK_PSK :
          this.mixKeyAndHash(this.psk)
          break

        case TOK_E :
          if (this.e === null) this.e = this.curve.generateKeyPair()
          this.mixHash(this.e.publicKey)
          if (this.isPskHandshake) this.mixKeyNormal(this.e.publicKey)
          w.push(this.e.publicKey)
          break

        case TOK_S :
          w.push(this.encryptAndHash(this.s.publicKey))
          break

        case TOK_ES :
        case TOK_SE :
        case TOK_EE :
        case TOK_SS : {
          const useStatic = keyPattern(pattern, this.initiator)

          const localKey = useStatic.local ? this.s : this.e
          const remoteKey = useStatic.remote ? this.rs : this.re

          this.mixKey(remoteKey, localKey)
          break
        }

        default :
          throw new Error('Unexpected message')
      }
    }

    w.push(this.encryptAndHash(payload))
    const response = w.end()

    if (!this.handshake.length) this.final()
    return response
  }

  _clear () {
    super._clear()

    this.e.secretKey.fill(0)
    this.e.publicKey.fill(0)

    this.re.fill(0)

    this.e = null
    this.re = null
  }
}

function keyPattern (pattern, initiator) {
  const ret = {
    local: false,
    remote: false
  }

  switch (pattern) {
    case TOK_EE:
      return ret

    case TOK_ES:
      ret.local ^= !initiator
      ret.remote ^= initiator
      return ret

    case TOK_SE:
      ret.local ^= initiator
      ret.remote ^= !initiator
      return ret

    case TOK_SS:
      ret.local ^= 1
      ret.remote ^= 1
      return ret
  }
}

function hasPskToken (handshake) {
  return handshake.some(x => {
    return Array.isArray(x) && x.indexOf(TOK_PSK) !== -1
  })
}
{
  "name": "noise-handshake",
  "version": "4.2.0",
  "description": "Noise protocol handshake",
  "main": "noise.js",
  "files": [
    "*.js"
  ],
  "scripts": {
    "test": "standard && brittle test/*.js"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/noise-handshake.git"
  },
  "dependencies": {
    "b4a": "^1.1.0",
    "nanoassert": "^2.0.0",
    "sodium-universal": "^5.0.0"
  },
  "devDependencies": {
    "brittle": "^3.3.2",
    "noise-protocol": "chm-diederichs/noise-protocol.git#xx-ephemeral-key",
    "standard": "^16.0.3"
  }
}
const sodium = require('sodium-universal')
const assert = require('nanoassert')
const b4a = require('b4a')
const CipherState = require('./cipher')
const curve = require('./dh')
const { HASHLEN, hkdf } = require('./hkdf')

module.exports = class SymmetricState extends CipherState {
  constructor (opts = {}) {
    super()

    this.curve = opts.curve || curve
    this.digest = b4a.alloc(HASHLEN)
    this.chainingKey = null
    this.offset = 0

    this.DH_ALG = this.curve.ALG
  }

  mixHash (data) {
    accumulateDigest(this.digest, data)
  }

  mixKeyAndHash (key) {
    const [ck, tempH, tempK] = hkdf(this.chainingKey, key, '', 3 * HASHLEN)
    this.chainingKey = ck
    this.mixHash(tempH)
    this.initialiseKey(tempK.subarray(0, 32))
  }

  mixKeyNormal (key) {
    const [ck, tempK] = hkdf(this.chainingKey, key)
    this.chainingKey = ck
    this.initialiseKey(tempK.subarray(0, 32))
  }

  mixKey (remoteKey, localKey) {
    const dh = this.curve.dh(remoteKey, localKey)
    const hkdfResult = hkdf(this.chainingKey, dh)
    this.chainingKey = hkdfResult[0]
    this.initialiseKey(hkdfResult[1].subarray(0, 32))
  }

  encryptAndHash (plaintext) {
    const ciphertext = this.encrypt(plaintext, this.digest)
    accumulateDigest(this.digest, ciphertext)
    return ciphertext
  }

  decryptAndHash (ciphertext) {
    const plaintext = this.decrypt(ciphertext, this.digest)
    accumulateDigest(this.digest, ciphertext)
    return plaintext
  }

  getHandshakeHash (out) {
    if (!out) return this.getHandshakeHash(b4a.alloc(HASHLEN))
    assert(out.byteLength === HASHLEN, `output must be ${HASHLEN} bytes`)

    out.set(this.digest)
    return out
  }

  split () {
    const res = hkdf(this.chainingKey, b4a.alloc(0))
    return res.map(k => k.subarray(0, 32))
  }

  _clear () {
    super._clear()

    sodium.sodium_memzero(this.digest)
    sodium.sodium_memzero(this.chainingKey)

    this.digest = null
    this.chainingKey = null
    this.offset = null

    this.curve = null
  }

  static get alg () {
    return CipherState.alg + '_BLAKE2b'
  }
}

function accumulateDigest (digest, input) {
  const toHash = b4a.concat([digest, input])
  sodium.crypto_generichash(digest, toHash)
}
var varint = require('varint')
var svarint = require('signed-varint')
var b4a = require('b4a')

exports.make = encoder

exports.name = function (enc) {
  var keys = Object.keys(exports)
  for (var i = 0; i < keys.length; i++) {
    if (exports[keys[i]] === enc) return keys[i]
  }
  return null
}

exports.skip = function (type, buffer, offset) {
  switch (type) {
    case 0:
      varint.decode(buffer, offset)
      return offset + varint.decode.bytes

    case 1:
      return offset + 8

    case 2:
      var len = varint.decode(buffer, offset)
      return offset + varint.decode.bytes + len

    case 3:
    case 4:
      throw new Error('Groups are not supported')

    case 5:
      return offset + 4
  }

  throw new Error('Unknown wire type: ' + type)
}

exports.bytes = encoder(2,
  function encode (val, buffer, offset) {
    var oldOffset = offset
    var len = bufferLength(val)

    varint.encode(len, buffer, offset)
    offset += varint.encode.bytes

    if (b4a.isBuffer(val)) b4a.copy(val, buffer, offset)
    else b4a.write(buffer, val, offset, len)
    offset += len

    encode.bytes = offset - oldOffset
    return buffer
  },
  function decode (buffer, offset) {
    var oldOffset = offset

    var len = varint.decode(buffer, offset)
    offset += varint.decode.bytes

    var val = buffer.subarray(offset, offset + len)
    offset += val.length

    decode.bytes = offset - oldOffset
    return val
  },
  function encodingLength (val) {
    var len = bufferLength(val)
    return varint.encodingLength(len) + len
  }
)

exports.string = encoder(2,
  function encode (val, buffer, offset) {
    var oldOffset = offset
    var len = b4a.byteLength(val)

    varint.encode(len, buffer, offset, 'utf-8')
    offset += varint.encode.bytes

    b4a.write(buffer, val, offset, len)
    offset += len

    encode.bytes = offset - oldOffset
    return buffer
  },
  function decode (buffer, offset) {
    var oldOffset = offset

    var len = varint.decode(buffer, offset)
    offset += varint.decode.bytes

    var val = b4a.toString(buffer, 'utf-8', offset, offset + len)
    offset += len

    decode.bytes = offset - oldOffset
    return val
  },
  function encodingLength (val) {
    var len = b4a.byteLength(val)
    return varint.encodingLength(len) + len
  }
)

exports.bool = encoder(0,
  function encode (val, buffer, offset) {
    buffer[offset] = val ? 1 : 0
    encode.bytes = 1
    return buffer
  },
  function decode (buffer, offset) {
    var bool = buffer[offset] > 0
    decode.bytes = 1
    return bool
  },
  function encodingLength () {
    return 1
  }
)

exports.int32 = encoder(0,
  function encode (val, buffer, offset) {
    varint.encode(val < 0 ? val + 4294967296 : val, buffer, offset)
    encode.bytes = varint.encode.bytes
    return buffer
  },
  function decode (buffer, offset) {
    var val = varint.decode(buffer, offset)
    decode.bytes = varint.decode.bytes
    return val > 2147483647 ? val - 4294967296 : val
  },
  function encodingLength (val) {
    return varint.encodingLength(val < 0 ? val + 4294967296 : val)
  }
)

exports.int64 = encoder(0,
  function encode (val, buffer, offset) {
    if (val < 0) {
      var last = offset + 9
      varint.encode(val * -1, buffer, offset)
      offset += varint.encode.bytes - 1
      buffer[offset] = buffer[offset] | 0x80
      while (offset < last - 1) {
        offset++
        buffer[offset] = 0xff
      }
      buffer[last] = 0x01
      encode.bytes = 10
    } else {
      varint.encode(val, buffer, offset)
      encode.bytes = varint.encode.bytes
    }
    return buffer
  },
  function decode (buffer, offset) {
    var val = varint.decode(buffer, offset)
    if (val >= Math.pow(2, 63)) {
      var limit = 9
      while (buffer[offset + limit - 1] === 0xff) limit--
      limit = limit || 9
      var subset = b4a.allocUnsafe(limit)
      b4a.copy(buffer, subset, 0, offset, offset + limit)
      subset[limit - 1] = subset[limit - 1] & 0x7f
      val = -1 * varint.decode(subset, 0)
      decode.bytes = 10
    } else {
      decode.bytes = varint.decode.bytes
    }
    return val
  },
  function encodingLength (val) {
    return val < 0 ? 10 : varint.encodingLength(val)
  }
)

exports.sint32 =
exports.sint64 = encoder(0,
  svarint.encode,
  svarint.decode,
  svarint.encodingLength
)

exports.uint32 =
exports.uint64 =
exports.enum =
exports.varint = encoder(0,
  varint.encode,
  varint.decode,
  varint.encodingLength
)

// we cannot represent these in javascript so we just use buffers
exports.fixed64 =
exports.sfixed64 = encoder(1,
  function encode (val, buffer, offset) {
    b4a.copy(val, buffer, offset)
    encode.bytes = 8
    return buffer
  },
  function decode (buffer, offset) {
    var val = buffer.subarray(offset, offset + 8)
    decode.bytes = 8
    return val
  },
  function encodingLength () {
    return 8
  }
)

exports.double = encoder(1,
  function encode (val, buffer, offset) {
    b4a.writeDoubleLE(buffer, val, offset)
    encode.bytes = 8
    return buffer
  },
  function decode (buffer, offset) {
    var val = b4a.readDoubleLE(buffer, offset)
    decode.bytes = 8
    return val
  },
  function encodingLength () {
    return 8
  }
)

exports.fixed32 = encoder(5,
  function encode (val, buffer, offset) {
    b4a.writeUInt32LE(buffer, val, offset)
    encode.bytes = 4
    return buffer
  },
  function decode (buffer, offset) {
    var val = b4a.readUInt32LE(buffer, offset)
    decode.bytes = 4
    return val
  },
  function encodingLength () {
    return 4
  }
)

exports.sfixed32 = encoder(5,
  function encode (val, buffer, offset) {
    b4a.writeInt32LE(buffer, val, offset)
    encode.bytes = 4
    return buffer
  },
  function decode (buffer, offset) {
    var val = b4a.readInt32LE(buffer, offset)
    decode.bytes = 4
    return val
  },
  function encodingLength () {
    return 4
  }
)

exports.float = encoder(5,
  function encode (val, buffer, offset) {
    b4a.writeFloatLE(buffer, val, offset)
    encode.bytes = 4
    return buffer
  },
  function decode (buffer, offset) {
    var val = b4a.readFloatLE(buffer, offset)
    decode.bytes = 4
    return val
  },
  function encodingLength () {
    return 4
  }
)

function encoder (type, encode, decode, encodingLength) {
  encode.bytes = decode.bytes = 0

  return {
    type: type,
    encode: encode,
    decode: decode,
    encodingLength: encodingLength
  }
}

function bufferLength (val) {
  return b4a.isBuffer(val) ? val.length : b4a.byteLength(val)
}
{
  "name": "protocol-buffers-encodings",
  "version": "1.2.0",
  "description": "Base encodings for protocol-buffers",
  "main": "index.js",
  "dependencies": {
    "b4a": "^1.6.0",
    "signed-varint": "^2.0.1",
    "varint": "5.0.0"
  },
  "devDependencies": {
    "standard": "^14.3.4",
    "tape": "^5.0.1"
  },
  "scripts": {
    "test": "standard && tape test.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/mafintosh/protocol-buffers-encodings.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/mafintosh/protocol-buffers-encodings/issues"
  },
  "homepage": "https://github.com/mafintosh/protocol-buffers-encodings"
}
const crypto = require('hypercore-crypto')
const Protomux = require('protomux')
const b4a = require('b4a')
const schema = require('./spec/hyperschema')

const [NS_INITATOR, NS_RESPONDER] = crypto.namespace('wakeup', 2)

const Handshake = schema.getEncoding('@wakeup/handshake')
const Announce = schema.getEncoding('@wakeup/announce')
const Lookup = schema.getEncoding('@wakeup/lookup')
const Info = schema.getEncoding('@wakeup/info')

module.exports = class WakeupSwarm {
  constructor(onwakeup = noop, { gcTickTime = 2000 } = {}) {
    this.gcTickTime = gcTickTime

    this.topics = new Map()
    this.topicsGC = new Set()
    this.muxers = new Set()
    this.stats = {
      sessionsOpened: 0,
      sessionsClosed: 0,
      topicsAdded: 0,
      topicsGcd: 0,
      peersAdded: 0,
      peersRemoved: 0,
      wireAnnounce: { rx: 0, tx: 0 },
      wireLookup: { rx: 0, tx: 0 },
      wireInfo: { rx: 0, tx: 0 }
    }

    this.onwakeup = onwakeup

    this._gcInterval = null
    this._gcBound = this._gc.bind(this)
  }

  session(capability, handlers = {}) {
    const id = handlers.discoveryKey || crypto.discoveryKey(capability)
    const active = handlers.active !== false
    const hex = b4a.toString(id, 'hex')

    let w = this.topics.get(hex)

    if (w) return w.addSession(handlers)

    w = new WakeupTopic(this, id, capability, active)

    this.topics.set(hex, w)

    for (const muxer of this.muxers) {
      w._onopen(muxer, true)
    }

    return w.addSession(handlers)
  }

  getSessions(capability, handlers = {}) {
    const id = handlers.discoveryKey || crypto.discoveryKey(capability)
    const hex = b4a.toString(id, 'hex')
    const w = this.topics.get(hex)
    return w ? w.sessions : []
  }

  hasStream(stream, capability, handlers = {}) {
    if (!capability) {
      const noiseStream = stream.noiseStream || stream
      return this.muxers.has(getMuxer(noiseStream))
    }

    const id = handlers.discoveryKey || crypto.discoveryKey(capability)
    const hex = b4a.toString(id, 'hex')
    const t = this.topics.get(hex)

    return t ? t.peersByStream.has(stream) : false
  }

  addStream(stream) {
    const noiseStream = stream.noiseStream || stream

    if (!noiseStream.connected) {
      noiseStream.once('open', this.addStream.bind(this, noiseStream))
      return
    }

    const muxer = getMuxer(noiseStream)
    muxer.pair({ protocol: 'wakeup' }, (id) => this._onpair(id, muxer))

    this.muxers.add(muxer)
    noiseStream.on('close', () => this.muxers.delete(muxer))

    for (const w of this.topics.values()) {
      if (!w.isActive) continue
      w._onopen(muxer, true)
    }
  }

  _onActive(w) {
    for (const m of this.muxers) {
      w._onopen(m, false)
    }
  }

  _addGC(topic) {
    if (topic.destroyed) return
    this.topicsGC.add(topic)
    if (this._gcInterval === null) {
      this._gcInterval = setInterval(this._gcBound, this.gcTickTime)
    }
  }

  _removeGC(topic) {
    this.topicsGC.delete(topic)
    if (this.topicsGC.size === 0 && this._gcInterval) {
      clearInterval(this._gcInterval)
      this._gcInterval = null
    }
  }

  _gc() {
    const destroy = []
    for (const w of this.topicsGC) {
      w.idleTicks++
      if (w.idleTicks >= 5) destroy.push(w)
    }
    for (const w of destroy) w.teardown()
  }

  destroy() {
    if (this._gcInterval) clearInterval(this._gcInterval)
    this._gcInterval = null

    for (const w of this.topics.values()) w.teardown()
  }

  async _onpair(id, stream) {
    const hex = b4a.toString(id, 'hex')
    const w = this.topics.get(hex)
    if (!w || !w.sessions.length) return this.onwakeup(id, stream)
    w._onopen(getMuxer(stream), false)
  }

  registerMetrics(promClient) {
    const self = this
    new promClient.Gauge({
      // eslint-disable-line no-new
      name: 'protomux_wakeup_sessions_opened',
      help: 'The amount of sessions opened by protomux wakeup',
      collect() {
        this.set(self.stats.sessionsOpened)
      }
    })

    new promClient.Gauge({
      // eslint-disable-line no-new
      name: 'protomux_wakeup_sessions_closed',
      help: 'The amount of sessions closed by protomux wakeup',
      collect() {
        this.set(self.stats.sessionsClosed)
      }
    })

    new promClient.Gauge({
      // eslint-disable-line no-new
      name: 'protomux_wakeup_topics_added',
      help: 'The amount of topics added to protomux wakeup',
      collect() {
        this.set(self.stats.topicsAdded)
      }
    })

    new promClient.Gauge({
      // eslint-disable-line no-new
      name: 'protomux_wakeup_topics_gcd',
      help: 'The amount of topics that got garbage collected by protomux wakeup',
      collect() {
        this.set(self.stats.topicsGcd)
      }
    })

    new promClient.Gauge({
      // eslint-disable-line no-new
      name: 'protomux_wakeup_peers_added',
      help: 'The amount of peers added by protomux wakeup, across all topics',
      collect() {
        this.set(self.stats.peersAdded)
      }
    })

    new promClient.Gauge({
      // eslint-disable-line no-new
      name: 'protomux_wakeup_peers_removed',
      help: 'The amount of peers removed by protomux wakeup, across all topics',
      collect() {
        this.set(self.stats.peersRemoved)
      }
    })

    new promClient.Gauge({
      // eslint-disable-line no-new
      name: 'protomux_wakeup_wire_announce_rx',
      help: 'The amount of wire announce messages received by protomux wakeup',
      collect() {
        this.set(self.stats.wireAnnounce.rx)
      }
    })
    new promClient.Gauge({
      // eslint-disable-line no-new
      name: 'protomux_wakeup_wire_announce_tx',
      help: 'The amount of wire announce messages transmitted by protomux wakeup',
      collect() {
        this.set(self.stats.wireAnnounce.tx)
      }
    })
    new promClient.Gauge({
      // eslint-disable-line no-new
      name: 'protomux_wakeup_wire_lookup_rx',
      help: 'The amount of wire lookup messages received by protomux wakeup',
      collect() {
        this.set(self.stats.wireLookup.rx)
      }
    })
    new promClient.Gauge({
      // eslint-disable-line no-new
      name: 'protomux_wakeup_wire_lookup_tx',
      help: 'The amount of wire lookup messages transmitted by protomux wakeup',
      collect() {
        this.set(self.stats.wireLookup.tx)
      }
    })
    new promClient.Gauge({
      // eslint-disable-line no-new
      name: 'protomux_wakeup_wire_info_rx',
      help: 'The amount of wire info messages received by protomux wakeup',
      collect() {
        this.set(self.stats.wireInfo.rx)
      }
    })
    new promClient.Gauge({
      // eslint-disable-line no-new
      name: 'protomux_wakeup_wire_info_tx',
      help: 'The amount of wire info messages transmitted by protomux wakeup',
      collect() {
        this.set(self.stats.wireInfo.rx)
      }
    })
  }
}

class WakeupPeer {
  constructor(topic) {
    this.index = 0
    this.userData = null // for the user
    this.clock = 0 // for the user, v useful to reduce traffic
    this.pending = true
    this.removed = false
    this.topic = topic
    this.channel = null
    this.stream = null
    this.wireLookup = null
    this.wireAnnounce = null
    this.wireInfo = null
    this.active = false
  }

  unlink(list) {
    // note that since we pop here we can iterate in reverse safely in case a peer is removed in the same tick
    const head = list.pop()
    if (head === this) return
    head.index = this.index
    list[head.index] = head
  }
}

class WakeupSession {
  constructor(topic, handlers) {
    this.index = 0
    this.topic = topic
    this.handlers = handlers
    this.isActive = handlers.active !== false
    this.destroyed = false
  }

  get peers() {
    return this.topic.peers
  }

  hasStream(stream) {
    return !!this.getPeer(stream)
  }

  addStream(stream) {
    this.topic.addStream(stream)
  }

  getPeer(stream) {
    return this.topic.peersByStream.get(stream) || null
  }

  broadcastLookup(req) {
    for (const peer of this.topic.pendingPeers) {
      this.lookup(peer, req)
    }
    for (const peer of this.topic.peers) {
      this.lookup(peer, req)
    }
  }

  lookupByStream(stream, req) {
    const peer = this.topic.peersByStream.get(stream)
    if (peer) this.lookup(peer, req)
  }

  lookup(peer, req) {
    peer.topic.state.stats.wireLookup.tx++
    peer.wireLookup.send(req || { hash: null })
  }

  announceByStream(stream, wakeup) {
    const peer = this.topic.peersByStream.get(stream)
    if (peer && !peer.pending) this.announce(peer, wakeup)
  }

  announce(peer, wakeup) {
    peer.topic.state.stats.wireAnnounce.tx++
    peer.wireAnnounce.send(wakeup)
  }

  active() {
    this.isActive = true
    this.topic._bumpActivity()
  }

  inactive() {
    this.isActive = false
    this.topic._bumpActivity()
  }

  destroy() {
    if (this.destroyed) return
    this.destroyed = true
    this.topic.removeSession(this)
  }
}

class WakeupTopic {
  constructor(state, id, capability, active) {
    this.state = state
    this.sessions = []
    this.id = id
    this.capability = capability
    this.peers = []
    this.pendingPeers = []
    this.peersByStream = new Map()
    this.activePeers = 0
    this.isActive = active
    this.idleTicks = 0
    this.gcing = false
    this.destroyed = false

    this.state.stats.topicsAdded++
  }

  addSession(handlers) {
    this.state.stats.sessionsOpened++
    const session = new WakeupSession(this, handlers)
    session.index = this.sessions.length
    this.sessions.push(session)
    this._bumpActivity()
    this._checkGC()
    return session
  }

  removeSession(session) {
    if (this.sessions.length <= session.index) return
    if (this.sessions[session.index] !== session) return

    this.state.stats.sessionsClosed++

    // same as with the peer, this allows us to iterate while removing if iterating backwards
    const head = this.sessions.pop()
    if (head !== session) {
      head.index = session.index
      this.sessions[head.index] = head
    }

    this._bumpActivity()
    this._checkGC()
  }

  _bumpActivity() {
    let isActive = false

    for (let i = this.sessions.length - 1; i >= 0; i--) {
      if (this.sessions[i].isActive) {
        isActive = true
        break
      }
    }

    if (isActive) this.active()
    else this.inactive()
  }

  active() {
    if (this.isActive) return
    this.idleTicks = 0
    this.isActive = true
    this._updateActive(true)
  }

  inactive() {
    if (!this.isActive) return
    this.isActive = false
    this._updateActive(false)
  }

  _updateActive(active) {
    const info = { active }

    for (const peer of this.pendingPeers) {
      peer.topic.state.stats.wireInfo.tx++
      peer.wireInfo.send(info)
    }
    for (const peer of this.peers) {
      peer.topic.state.stats.wireInfo.tx++
      peer.wireInfo.send(info)
    }

    this._checkGC()

    if (active) this.state._onActive(this)
  }

  teardown() {
    if (this.destroyed) return
    this.destroyed = true

    this.state.stats.topicsGcd++

    for (let i = this.peers.length - 1; i >= 0; i--) {
      this.peers[i].channel.close()
    }

    for (let i = this.pendingPeers.length - 1; i >= 0; i--) {
      this.pendingPeers[i].channel.close()
    }

    const hex = b4a.toString(this.id, 'hex')

    this.gcing = false
    this.state.topics.delete(hex)
    this.state._removeGC(this)

    for (let i = this.sessions.length - 1; i >= 0; i--) {
      this.state.stats.sessionsClosed++
      const session = this.sessions[i]
      if (session.handlers.ondestroy) session.handlers.ondestroy(session)
    }
  }

  addStream(stream) {
    this._onopen(getMuxer(stream), false)
  }

  _proveCapabilityTo(stream) {
    return this._makeCapability(stream.isInitiator, stream.handshakeHash)
  }

  _makeCapability(isInitiator, handshakeHash) {
    return crypto.hash([isInitiator ? NS_INITATOR : NS_RESPONDER, this.capability, handshakeHash])
  }

  _addPeer(peer, open) {
    if (
      !b4a.equals(
        open.capability,
        this._makeCapability(!peer.stream.isInitiator, peer.stream.handshakeHash)
      )
    ) {
      peer.channel.close()
      return
    }

    if (peer.pending) {
      peer.unlink(this.pendingPeers)
    }

    peer.active = open.active
    peer.pending = false
    peer.index = this.peers.push(peer) - 1

    if (peer.active) {
      this.activePeers++
      this._checkGC()
    }

    for (let i = this.sessions.length - 1; i >= 0; i--) {
      const session = this.sessions[i]
      const handlers = session.handlers

      if (handlers.onpeeradd) handlers.onpeeradd(peer, session)
      if (peer.active && handlers.onpeeractive) handlers.onpeeractive(peer, session)
    }
  }

  _checkGC() {
    const shouldGC = this.activePeers === 0 && this.sessions.length === 0

    if (shouldGC) {
      if (!this.gcing) {
        this.gcing = true
        this.state._addGC(this)
      }
    } else {
      if (this.gcing) {
        this.gcing = false
        this.state._removeGC(this)
      }
    }
  }

  _removePeer(peer) {
    this.state.stats.peersRemoved++
    peer.removed = true
    this.peersByStream.delete(peer.stream)

    if (peer.pending) {
      peer.unlink(this.pendingPeers)
      return
    }

    const active = peer.active

    if (active) {
      peer.active = false
      this.activePeers--
      this._checkGC()
    }

    peer.unlink(this.peers)

    for (let i = this.sessions.length - 1; i >= 0; i--) {
      const session = this.sessions[i]
      const handlers = session.handlers

      if (active && handlers.onpeerinactive) handlers.onpeerinactive(peer, session)
      if (handlers.onpeerremove) handlers.onpeerremove(peer, session)
    }
  }

  _onannounce(wakeup, peer) {
    for (let i = this.sessions.length - 1; i >= 0; i--) {
      const session = this.sessions[i]
      const handlers = session.handlers

      if (handlers.onannounce) handlers.onannounce(wakeup, peer, session)
    }
  }

  _onlookup(req, peer) {
    for (let i = this.sessions.length - 1; i >= 0; i--) {
      const session = this.sessions[i]
      const handlers = session.handlers

      if (handlers.onlookup) handlers.onlookup(req, peer, session)
    }
  }

  _oninfo(info, peer) {
    if (info.active) {
      if (!peer.active) {
        peer.active = true
        this.activePeers++
        this._checkGC()

        for (let i = this.sessions.length - 1; i >= 0; i--) {
          const session = this.sessions[i]
          const handlers = session.handlers

          if (handlers.onpeeractive) handlers.onpeeractive(peer, session)
        }
      }
    } else {
      if (peer.active) {
        peer.active = false
        this.activePeers--
        this._checkGC()

        for (let i = this.sessions.length - 1; i >= 0; i--) {
          const session = this.sessions[i]
          const handlers = session.handlers

          if (handlers.onpeerinactive) handlers.onpeerinactive(peer, session)
        }
      }
    }
  }

  _onopen(muxer, unique) {
    if (!unique && this.peersByStream.has(muxer.stream)) return

    const peer = new WakeupPeer(this)
    const ch = muxer.createChannel({
      userData: peer,
      protocol: 'wakeup',
      id: this.id,
      handshake: Handshake,
      messages: [
        { encoding: Lookup, onmessage: onlookup },
        { encoding: Announce, onmessage: onannounce },
        { encoding: Info, onmessage: onchannelinfo }
      ],
      onopen: onchannelopen,
      onclose: onchannelclose
    })

    if (!ch) return

    this.state.stats.peersAdded++

    peer.channel = ch
    peer.stream = muxer.stream

    peer.wireLookup = ch.messages[0]
    peer.wireAnnounce = ch.messages[1]
    peer.wireInfo = ch.messages[2]

    peer.index = this.pendingPeers.push(peer) - 1
    this.peersByStream.set(muxer.stream, peer)

    ch.open({
      version: 0,
      capability: this._proveCapabilityTo(muxer.stream),
      active: this.isActive
    })
  }
}

function onchannelopen(open, channel) {
  const peer = channel.userData
  peer.topic._addPeer(peer, open)
}

function onchannelclose(close, channel) {
  const peer = channel.userData
  peer.topic._removePeer(peer)
}

function onlookup(req, channel) {
  const peer = channel.userData
  peer.topic.state.stats.wireLookup.rx++
  peer.topic._onlookup(req, peer)
}

function onannounce(wakeup, channel) {
  const peer = channel.userData
  peer.topic.state.stats.wireAnnounce.rx++
  peer.topic._onannounce(wakeup, peer)
}

function onchannelinfo(info, channel) {
  const peer = channel.userData
  peer.topic.state.stats.wireInfo.rx++
  peer.topic._oninfo(info, peer)
}

function getMuxer(stream) {
  if (Protomux.isProtomux(stream)) return stream
  if (stream.noiseStream.userData) return stream.noiseStream.userData
  const mux = Protomux.from(stream.noiseStream)
  stream.noiseStream.userData = mux
  return mux
}

function noop() {}
{
  "name": "protomux-wakeup",
  "version": "2.9.0",
  "description": "Wakeup protocol over protomux",
  "main": "index.js",
  "dependencies": {
    "b4a": "^1.6.7",
    "hypercore-crypto": "^3.5.0",
    "hyperschema": "^1.10.4",
    "protomux": "^3.10.1"
  },
  "devDependencies": {
    "@hyperswarm/secret-stream": "^6.8.1",
    "brittle": "^3.17.1",
    "lunte": "^1.0.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^2.0.0",
    "prom-client": "^15.1.3"
  },
  "files": [
    "index.js",
    "spec/hyperschema/*.js"
  ],
  "scripts": {
    "format": "prettier --write .",
    "test": "prettier --check . && lunte && brittle test/*.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/holepunchto/protomux-wakeup.git"
  },
  "author": "Holepunch Inc.",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/protomux-wakeup/issues"
  },
  "homepage": "https://github.com/holepunchto/protomux-wakeup"
}
// This file is autogenerated by the hyperschema compiler
// Schema Version: 1
/* eslint-disable camelcase */
/* eslint-disable quotes */

const VERSION = 1
const { c } = require('hyperschema/runtime')

// eslint-disable-next-line no-unused-vars
let version = VERSION

// @wakeup/info
const encoding0 = {
  preencode(state, m) {
    state.end++ // max flag is 1 so always one byte
  },
  encode(state, m) {
    const flags = m.active ? 1 : 0

    c.uint.encode(state, flags)
  },
  decode(state) {
    const flags = c.uint.decode(state)

    return {
      active: (flags & 1) !== 0
    }
  }
}

// @wakeup/handshake
const encoding1 = {
  preencode(state, m) {
    c.uint.preencode(state, m.version)
    c.fixed32.preencode(state, m.capability)
    state.end++ // max flag is 1 so always one byte
  },
  encode(state, m) {
    const flags = m.active ? 1 : 0

    c.uint.encode(state, m.version)
    c.fixed32.encode(state, m.capability)
    c.uint.encode(state, flags)
  },
  decode(state) {
    const r0 = c.uint.decode(state)
    const r1 = c.fixed32.decode(state)
    const flags = c.uint.decode(state)

    return {
      version: r0,
      capability: r1,
      active: (flags & 1) !== 0
    }
  }
}

// @wakeup/writer
const encoding2 = {
  preencode(state, m) {
    c.fixed32.preencode(state, m.key)
    c.uint.preencode(state, m.length)
  },
  encode(state, m) {
    c.fixed32.encode(state, m.key)
    c.uint.encode(state, m.length)
  },
  decode(state) {
    const r0 = c.fixed32.decode(state)
    const r1 = c.uint.decode(state)

    return {
      key: r0,
      length: r1
    }
  }
}

// @wakeup/announce
const encoding3 = c.array(encoding2)

// @wakeup/lookup
const encoding4 = {
  preencode(state, m) {
    state.end++ // max flag is 1 so always one byte

    if (m.hash) c.fixed32.preencode(state, m.hash)
  },
  encode(state, m) {
    const flags = m.hash ? 1 : 0

    c.uint.encode(state, flags)

    if (m.hash) c.fixed32.encode(state, m.hash)
  },
  decode(state) {
    const flags = c.uint.decode(state)

    return {
      hash: (flags & 1) !== 0 ? c.fixed32.decode(state) : null
    }
  }
}

function setVersion(v) {
  version = v
}

function encode(name, value, v = VERSION) {
  version = v
  return c.encode(getEncoding(name), value)
}

function decode(name, buffer, v = VERSION) {
  version = v
  return c.decode(getEncoding(name), buffer)
}

function getEnum(name) {
  switch (name) {
    default:
      throw new Error('Enum not found ' + name)
  }
}

function getEncoding(name) {
  switch (name) {
    case '@wakeup/info':
      return encoding0
    case '@wakeup/handshake':
      return encoding1
    case '@wakeup/writer':
      return encoding2
    case '@wakeup/announce':
      return encoding3
    case '@wakeup/lookup':
      return encoding4
    default:
      throw new Error('Encoder not found ' + name)
  }
}

function getStruct(name, v = VERSION) {
  const enc = getEncoding(name)
  return {
    preencode(state, m) {
      version = v
      enc.preencode(state, m)
    },
    encode(state, m) {
      version = v
      enc.encode(state, m)
    },
    decode(state) {
      version = v
      return enc.decode(state)
    }
  }
}

const resolveStruct = getStruct // compat

module.exports = {
  resolveStruct,
  getStruct,
  getEnum,
  getEncoding,
  encode,
  decode,
  setVersion,
  version
}
const b4a = require('b4a')
const c = require('compact-encoding')
const queueTick = require('queue-tick')
const safetyCatch = require('safety-catch')
const unslab = require('unslab')

const MAX_BUFFERED = 32768
const MAX_BACKLOG = Infinity // TODO: impl "open" backpressure
const MAX_BATCH = 8 * 1024 * 1024

class Channel {
  constructor(
    mux,
    info,
    userData,
    protocol,
    aliases,
    id,
    handshake,
    messages,
    onopen,
    onclose,
    ondestroy,
    ondrain
  ) {
    this.userData = userData
    this.protocol = protocol
    this.aliases = aliases
    this.id = id
    this.handshake = null
    this.messages = []

    this.opened = false
    this.closed = false
    this.destroyed = false

    this.onopen = onopen
    this.onclose = onclose
    this.ondestroy = ondestroy
    this.ondrain = ondrain

    this._handshake = handshake
    this._mux = mux
    this._info = info
    this._localId = 0
    this._remoteId = 0
    this._active = 0
    this._extensions = null

    this._decBound = this._dec.bind(this)
    this._decAndDestroyBound = this._decAndDestroy.bind(this)

    this._openedPromise = null
    this._openedResolve = null

    this._destroyedPromise = null
    this._destroyedResolve = null

    for (const m of messages) this.addMessage(m)
  }

  get drained() {
    return this._mux.drained
  }

  fullyOpened() {
    if (this.opened) return Promise.resolve(true)
    if (this.closed) return Promise.resolve(false)
    if (this._openedPromise) return this._openedPromise

    this._openedPromise = new Promise((resolve) => {
      this._openedResolve = resolve
    })
    return this._openedPromise
  }

  fullyClosed() {
    if (this.destroyed) return Promise.resolve()
    if (this._destroyedPromise) return this._destroyedPromise

    this._destroyedPromise = new Promise((resolve) => {
      this._destroyedResolve = resolve
    })
    return this._destroyedPromise
  }

  open(handshake) {
    const id = this._mux._free.length > 0 ? this._mux._free.pop() : this._mux._local.push(null) - 1

    this._info.opened++
    this._info.lastChannel = this
    this._localId = id + 1
    this._mux._local[id] = this

    if (this._remoteId === 0) {
      this._info.outgoing.push(this._localId)
    }

    const state = { buffer: null, start: 2, end: 2 }

    c.uint.preencode(state, this._localId)
    c.string.preencode(state, this.protocol)
    c.optionalBuffer.preencode(state, this.id)
    if (this._handshake) this._handshake.preencode(state, handshake)

    state.buffer = this._mux._alloc(state.end)

    state.buffer[0] = 0
    state.buffer[1] = 1
    c.uint.encode(state, this._localId)
    c.string.encode(state, this.protocol)
    c.optionalBuffer.encode(state, this.id)
    if (this._handshake) this._handshake.encode(state, handshake)

    this._mux._write0(state.buffer)
  }

  _dec() {
    if (--this._active === 0 && this.closed === true) this._destroy()
  }

  _decAndDestroy(err) {
    this._dec()
    this._mux._safeDestroy(err)
  }

  _fullyOpenSoon() {
    this._mux._remote[this._remoteId - 1].session = this
    queueTick(this._fullyOpenOrDestroy.bind(this))
  }

  _fullyOpenOrDestroy() {
    try {
      this._fullyOpen()
    } catch (err) {
      this._mux._safeDestroyBound(err)
    }
  }

  _fullyOpen() {
    if (this.opened === true || this.closed === true) return

    const remote = this._mux._remote[this._remoteId - 1]

    this.handshake = this._handshake ? this._handshake.decode(remote.state) : null
    this._track(this.onopen(this.handshake, this))

    remote.session = this
    remote.state = null
    if (remote.pending !== null) this._drain(remote)
    if (this._mux._destroying === true) return

    this.opened = true
    this._resolveOpen(true)
  }

  _resolveOpen(opened) {
    if (this._openedResolve !== null) {
      this._openedResolve(opened)
      this._openedResolve = this._openedPromise = null
    }
  }

  _resolveDestroyed() {
    if (this._destroyedResolve !== null) {
      this._destroyedResolve()
      this._destroyedResolve = this._destroyedPromise = null
    }
  }

  _drain(remote) {
    for (let i = 0; i < remote.pending.length; i++) {
      const p = remote.pending[i]
      this._mux._buffered -= byteSize(p.state)
      this._recv(p.type, p.state)
      if (this._mux._destroying === true) return
    }

    remote.pending = null
    this._mux._resumeMaybe()
  }

  _track(p) {
    if (isPromise(p) === true) {
      this._active++
      return p.then(this._decBound, this._decAndDestroyBound)
    }

    return null
  }

  _close(isRemote) {
    if (this.closed === true) return
    this.closed = true

    this._info.opened--
    if (this._info.lastChannel === this) this._info.lastChannel = null

    if (this._remoteId > 0) {
      this._mux._remote[this._remoteId - 1] = null
      this._remoteId = 0
      // If remote has acked, we can reuse the local id now
      // otherwise, we need to wait for the "ack" to arrive
      this._mux._free.push(this._localId - 1)
    }

    this._mux._local[this._localId - 1] = null
    this._localId = 0

    this._mux._gc(this._info)
    this._track(this.onclose(isRemote, this))

    if (this._active === 0) this._destroy()

    this._resolveOpen(false)
  }

  _destroy() {
    if (this.destroyed === true) return
    this.destroyed = true
    this._track(this.ondestroy(this))
    this._resolveDestroyed()
  }

  _recv(type, state) {
    if (type < this.messages.length) {
      const m = this.messages[type]
      const p = m.recv(state, this)
      if (m.autoBatch === true) return p
    }
    return null
  }

  cork() {
    this._mux.cork()
  }

  uncork() {
    this._mux.uncork()
  }

  close() {
    if (this.closed === true) return

    const state = { buffer: null, start: 2, end: 2 }

    c.uint.preencode(state, this._localId)

    state.buffer = this._mux._alloc(state.end)

    state.buffer[0] = 0
    state.buffer[1] = 3
    c.uint.encode(state, this._localId)

    this._close(false)
    this._mux._write0(state.buffer)
  }

  addMessage(opts) {
    if (!opts) return this._skipMessage()

    const type = this.messages.length
    const autoBatch = opts.autoBatch !== false
    const encoding = opts.encoding || c.raw
    const onmessage = opts.onmessage || noop

    const s = this
    const typeLen = encodingLength(c.uint, type)

    const m = {
      type,
      autoBatch,
      encoding,
      onmessage,
      recv(state, session) {
        return session._track(m.onmessage(encoding.decode(state), session))
      },
      send(m, session = s) {
        if (session.closed === true) return false

        const mux = session._mux
        const state = { buffer: null, start: 0, end: typeLen }

        if (mux._batch !== null) {
          encoding.preencode(state, m)
          state.buffer = mux._alloc(state.end)

          c.uint.encode(state, type)
          encoding.encode(state, m)

          mux._pushBatch(session._localId, state.buffer)
          return true
        }

        c.uint.preencode(state, session._localId)
        encoding.preencode(state, m)

        state.buffer = mux._alloc(state.end)

        c.uint.encode(state, session._localId)
        c.uint.encode(state, type)
        encoding.encode(state, m)

        mux.drained = mux.stream.write(state.buffer)

        return mux.drained
      }
    }

    this.messages.push(m)

    return m
  }

  _skipMessage() {
    const type = this.messages.length
    const m = {
      type,
      encoding: c.raw,
      onmessage: noop,
      recv(state, session) {},
      send(m, session) {}
    }

    this.messages.push(m)
    return m
  }
}

module.exports = class Protomux {
  constructor(stream, { alloc } = {}) {
    if (stream.userData === null) stream.userData = this

    this.isProtomux = true
    this.stream = stream
    this.corked = 0
    this.drained = true

    this._alloc =
      alloc || (typeof stream.alloc === 'function' ? stream.alloc.bind(stream) : b4a.allocUnsafe)
    this._safeDestroyBound = this._safeDestroy.bind(this)
    this._uncorkBound = this.uncork.bind(this)

    this._remoteBacklog = 0
    this._buffered = 0
    this._paused = false
    this._remote = []
    this._local = []
    this._free = []
    this._batch = null
    this._batchState = null

    this._infos = new Map()
    this._notify = new Map()
    // stream.destroyed flips asynchronously on streamx-based transports.
    this._destroying = false

    this.stream.on('data', this._ondata.bind(this))
    this.stream.on('drain', this._ondrain.bind(this))
    this.stream.on('end', this._onend.bind(this))
    this.stream.on('error', noop) // we handle this in "close"
    this.stream.on('close', this._shutdown.bind(this))
  }

  static from(stream, opts) {
    if (stream.userData && stream.userData.isProtomux) return stream.userData
    if (stream.isProtomux) return stream
    return new this(stream, opts)
  }

  static isProtomux(mux) {
    return typeof mux === 'object' && mux.isProtomux === true
  }

  *[Symbol.iterator]() {
    for (const session of this._local) {
      if (session !== null) yield session
    }
  }

  isIdle() {
    return this._local.length === this._free.length
  }

  cork() {
    if (++this.corked === 1) {
      this._batch = []
      this._batchState = { buffer: null, start: 0, end: 1 }
    }
  }

  uncork() {
    if (--this.corked === 0) {
      this._sendBatch(this._batch, this._batchState)
      this._batch = null
      this._batchState = null
    }
  }

  getLastChannel({ protocol, id = null }) {
    const key = toKey(protocol, id)
    const info = this._infos.get(key)
    if (info) return info.lastChannel
    return null
  }

  pair({ protocol, id = null }, notify) {
    this._notify.set(toKey(protocol, id), notify)
  }

  unpair({ protocol, id = null }) {
    this._notify.delete(toKey(protocol, id))
  }

  opened({ protocol, id = null }) {
    const key = toKey(protocol, id)
    const info = this._infos.get(key)
    return info ? info.opened > 0 : false
  }

  createChannel({
    userData = null,
    protocol,
    aliases = [],
    id = null,
    unique = true,
    handshake = null,
    messages = [],
    onopen = noop,
    onclose = noop,
    ondestroy = noop,
    ondrain = noop
  }) {
    if (this.stream.destroyed) return null

    const info = this._get(protocol, id, aliases)
    if (unique && info.opened > 0) return null

    if (info.incoming.length === 0) {
      return new Channel(
        this,
        info,
        userData,
        protocol,
        aliases,
        id,
        handshake,
        messages,
        onopen,
        onclose,
        ondestroy,
        ondrain
      )
    }

    this._remoteBacklog--

    const remoteId = info.incoming.shift()
    const r = this._remote[remoteId - 1]
    if (r === null) return null

    const session = new Channel(
      this,
      info,
      userData,
      protocol,
      aliases,
      id,
      handshake,
      messages,
      onopen,
      onclose,
      ondestroy,
      ondrain
    )

    session._remoteId = remoteId
    session._fullyOpenSoon()

    return session
  }

  _pushBatch(localId, buffer) {
    if (this._batchState.end >= MAX_BATCH) {
      this._sendBatch(this._batch, this._batchState)
      this._batch = []
      this._batchState = { buffer: null, start: 0, end: 1 }
    }

    if (this._batch.length === 0 || this._batch[this._batch.length - 1].localId !== localId) {
      this._batchState.end++
      c.uint.preencode(this._batchState, localId)
    }
    c.buffer.preencode(this._batchState, buffer)
    this._batch.push({ localId, buffer })
  }

  _sendBatch(batch, state) {
    if (batch.length === 0) return

    let prev = batch[0].localId

    state.buffer = this._alloc(state.end)
    state.buffer[state.start++] = 0
    state.buffer[state.start++] = 0

    c.uint.encode(state, prev)

    for (let i = 0; i < batch.length; i++) {
      const b = batch[i]
      if (prev !== b.localId) {
        state.buffer[state.start++] = 0
        c.uint.encode(state, (prev = b.localId))
      }
      c.buffer.encode(state, b.buffer)
    }

    this.drained = this.stream.write(state.buffer)
  }

  _get(protocol, id, aliases = []) {
    const key = toKey(protocol, id)

    let info = this._infos.get(key)
    if (info) return info

    info = {
      key,
      protocol,
      aliases: [],
      id,
      pairing: 0,
      opened: 0,
      incoming: [],
      outgoing: [],
      lastChannel: null
    }
    this._infos.set(key, info)

    for (const alias of aliases) {
      const key = toKey(alias, id)
      info.aliases.push(key)

      this._infos.set(key, info)
    }

    return info
  }

  _gc(info) {
    if (info.opened === 0 && info.outgoing.length === 0 && info.incoming.length === 0) {
      this._infos.delete(info.key)

      for (const alias of info.aliases) this._infos.delete(alias)
    }
  }

  _ondata(buffer) {
    if (buffer.byteLength === 0) return // ignore empty frames...
    try {
      const state = { buffer, start: 0, end: buffer.byteLength }
      this._decode(c.uint.decode(state), state)
    } catch (err) {
      this._safeDestroy(err)
    }
  }

  _ondrain() {
    this.drained = true

    for (const s of this._local) {
      if (s !== null) s._track(s.ondrain(s))
    }
  }

  _onend() {
    // TODO: support half open mode for the users who wants that here
    this.stream.end()
  }

  _decode(remoteId, state) {
    const type = c.uint.decode(state)

    if (remoteId === 0) {
      return this._oncontrolsession(type, state)
    }

    const r = remoteId <= this._remote.length ? this._remote[remoteId - 1] : null

    // if the channel is closed ignore - could just be a pipeline message...
    if (r === null) return null

    if (r.pending !== null) {
      this._bufferMessage(r, type, state)
      return null
    }

    return r.session._recv(type, state)
  }

  _oncontrolsession(type, state) {
    switch (type) {
      case 0:
        this._onbatch(state)
        break

      case 1:
        // return the promise back up as this has sideeffects so we can batch reply
        return this._onopensession(state)

      case 2:
        this._onrejectsession(state)
        break

      case 3:
        this._onclosesession(state)
        break
    }

    return null
  }

  _bufferMessage(r, type, { buffer, start, end }) {
    const state = { buffer, start, end } // copy
    r.pending.push({ type, state })
    this._buffered += byteSize(state)
    this._pauseMaybe()
  }

  _pauseMaybe() {
    if (this._paused === true || this._buffered <= MAX_BUFFERED) return
    this._paused = true
    this.stream.pause()
  }

  _resumeMaybe() {
    if (this._paused === false || this._buffered > MAX_BUFFERED) return
    this._paused = false
    this.stream.resume()
  }

  _onbatch(state) {
    const end = state.end
    let remoteId = c.uint.decode(state)

    let waiting = null

    while (state.end > state.start) {
      const len = c.uint.decode(state)
      if (len === 0) {
        remoteId = c.uint.decode(state)
        continue
      }
      state.end = state.start + len
      // if batch contains more than one message, cork it so we reply back with a batch
      if (end !== state.end && waiting === null) {
        waiting = []
        this.cork()
      }
      const p = this._decode(remoteId, state)
      if (waiting !== null && p !== null) waiting.push(p)
      state.start = state.end
      state.end = end
    }

    if (waiting !== null) {
      // the waiting promises are not allowed to throw but we destroy the stream in case we are wrong
      Promise.all(waiting).then(this._uncorkBound, this._safeDestroyBound)
    }
  }

  _onopensession(state) {
    const remoteId = c.uint.decode(state)
    const protocol = c.string.decode(state)
    const id = unslab(c.optionalBuffer.decode(state))

    // remote tried to open the control session - auto reject for now
    // as we can use as an explicit control protocol declaration if we need to
    if (remoteId === 0) {
      this._rejectSession(0)
      return null
    }

    const rid = remoteId - 1
    const info = this._get(protocol, id)

    // allow the remote to grow the ids by one
    if (this._remote.length === rid) {
      this._remote.push(null)
    }

    if (rid >= this._remote.length || this._remote[rid] !== null) {
      throw new Error('Invalid open message')
    }

    if (info.outgoing.length > 0) {
      const localId = info.outgoing.shift()
      const session = this._local[localId - 1]

      if (session === null) {
        // we already closed the channel - ignore
        this._free.push(localId - 1)
        return null
      }

      this._remote[rid] = { state, pending: null, session: null }

      session._remoteId = remoteId
      session._fullyOpen()
      return null
    }

    const copyState = { buffer: state.buffer, start: state.start, end: state.end }
    this._remote[rid] = { state: copyState, pending: [], session: null }

    if (++this._remoteBacklog > MAX_BACKLOG) {
      throw new Error('Remote exceeded backlog')
    }

    info.pairing++
    info.incoming.push(remoteId)

    return this._requestSession(protocol, id, info).catch(this._safeDestroyBound)
  }

  _onrejectsession(state) {
    const localId = c.uint.decode(state)

    // TODO: can be done smarter...
    for (const info of this._infos.values()) {
      const i = info.outgoing.indexOf(localId)
      if (i === -1) continue

      info.outgoing.splice(i, 1)

      const session = this._local[localId - 1]

      this._free.push(localId - 1)
      if (session !== null) session._close(true)

      this._gc(info)
      return
    }

    throw new Error('Invalid reject message')
  }

  _onclosesession(state) {
    const remoteId = c.uint.decode(state)

    if (remoteId === 0) return // ignore

    const rid = remoteId - 1
    const r = rid < this._remote.length ? this._remote[rid] : null

    if (r === null) return

    if (r.session !== null) r.session._close(true)
  }

  async _requestSession(protocol, id, info) {
    const notify = this._notify.get(toKey(protocol, id)) || this._notify.get(toKey(protocol, null))

    if (notify) await notify(id)

    if (--info.pairing > 0) return

    while (info.incoming.length > 0) {
      this._rejectSession(info, info.incoming.shift())
    }

    this._gc(info)
  }

  _rejectSession(info, remoteId) {
    if (remoteId > 0) {
      const r = this._remote[remoteId - 1]

      if (r.pending !== null) {
        for (let i = 0; i < r.pending.length; i++) {
          this._buffered -= byteSize(r.pending[i].state)
        }
      }

      this._remote[remoteId - 1] = null
      this._resumeMaybe()
    }

    const state = { buffer: null, start: 2, end: 2 }

    c.uint.preencode(state, remoteId)

    state.buffer = this._alloc(state.end)

    state.buffer[0] = 0
    state.buffer[1] = 2
    c.uint.encode(state, remoteId)

    this._write0(state.buffer)
  }

  _write0(buffer) {
    if (this._batch !== null) {
      this._pushBatch(0, buffer.subarray(1))
      return
    }

    this.drained = this.stream.write(buffer)
  }

  destroy(err) {
    this._destroying = true
    this.stream.destroy(err)
  }

  _safeDestroy(err) {
    safetyCatch(err)
    this._destroying = true
    this.stream.destroy(err)
  }

  _shutdown() {
    this._destroying = true
    for (const s of this._local) {
      if (s !== null) s._close(true)
    }
  }
}

function noop() {}

function toKey(protocol, id) {
  return protocol + '##' + (id ? b4a.toString(id, 'hex') : '')
}

function byteSize(state) {
  return 512 + (state.end - state.start)
}

function isPromise(p) {
  return !!(p && typeof p.then === 'function')
}

function encodingLength(enc, val) {
  const state = { buffer: null, start: 0, end: 0 }
  enc.preencode(state, val)
  return state.end
}
{
  "name": "protomux",
  "version": "3.11.0",
  "description": "Multiplex multiple message oriented protocols over a stream",
  "main": "index.js",
  "files": [
    "index.js"
  ],
  "dependencies": {
    "b4a": "^1.3.1",
    "compact-encoding": "^3.0.0",
    "queue-tick": "^1.0.0",
    "safety-catch": "^1.0.1",
    "unslab": "^1.3.0"
  },
  "devDependencies": {
    "@hyperswarm/secret-stream": "^6.0.0",
    "brittle": "^3.0.0",
    "lunte": "^1.0.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^2.0.0"
  },
  "scripts": {
    "format": "prettier --write .",
    "lint": "prettier --check . && lunte",
    "test": "npm run test:node && npm run test:bare",
    "test:node": "brittle-node test.js",
    "test:bare": "brittle-bare test.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/holepunchto/protomux.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/holepunchto/protomux/issues"
  },
  "homepage": "https://github.com/holepunchto/protomux"
}
{
  "name": "queue-tick",
  "version": "1.0.1",
  "description": "Next tick shim that prefers process.nextTick over queueMicrotask for compat",
  "main": "./process-next-tick.js",
  "browser": "./queue-microtask.js",
  "dependencies": {},
  "devDependencies": {
    "standard": "^16.0.3",
    "tape": "^5.3.1"
  },
  "scripts": {
    "test": "standard && tape test.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/mafintosh/queue-tick.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/mafintosh/queue-tick/issues"
  },
  "homepage": "https://github.com/mafintosh/queue-tick"
}
module.exports = (typeof process !== 'undefined' && typeof process.nextTick === 'function')
  ? process.nextTick.bind(process)
  : require('./queue-microtask')
module.exports = typeof queueMicrotask === 'function' ? queueMicrotask : (fn) => Promise.resolve().then(fn)
require.addon = require('require-addon')

module.exports = require.addon('.', __filename)
const binding = require('./binding')

exports.get = function get(field, bit) {
  const n = field.byteLength * 8

  if (bit < 0) bit += n
  if (bit < 0 || bit >= n) return false

  return binding.quickbit_napi_get(toBuffer(field), bit) !== 0
}

exports.set = function set(field, bit, value = true) {
  const n = field.byteLength * 8

  if (bit < 0) bit += n
  if (bit < 0 || bit >= n) return false

  return binding.quickbit_napi_set(toBuffer(field), bit, value ? 1 : 0) !== 0
}

exports.fill = function fill(
  field,
  value,
  start = 0,
  end = field.byteLength * 8
) {
  const n = field.byteLength * 8

  if (start < 0) start += n
  if (end < 0) end += n
  if (start < 0 || start >= field.byteLength * 8 || start >= end) return field

  binding.quickbit_napi_fill(toBuffer(field), value ? 1 : 0, start, end)
  return field
}

exports.clear = function clear(field, ...chunks) {
  binding.quickbit_napi_clear(toBuffer(field), chunks.map(toBufferChunk))
}

exports.findFirst = function findFirst(field, value, position = 0) {
  const n = field.byteLength * 8

  if (position < 0) position += n
  if (position < 0) position = 0
  if (position >= n) return -1

  return binding.quickbit_napi_find_first(
    toBuffer(field),
    value ? 1 : 0,
    position
  )
}

exports.findLast = function findLast(
  field,
  value,
  position = field.byteLength * 8 - 1
) {
  const n = field.byteLength * 8

  if (position < 0) position += n
  if (position < 0) return -1
  if (position >= n) position = n - 1

  return binding.quickbit_napi_find_last(
    toBuffer(field),
    value ? 1 : 0,
    position
  )
}

function toBuffer(field) {
  if (field.BYTES_PER_ELEMENT === 1) return field
  return new Uint8Array(field.buffer, field.byteOffset, field.byteLength)
}

function toBufferChunk(chunk) {
  return { field: toBuffer(chunk.field), offset: chunk.offset }
}

class Index {
  static from(fieldOrChunks, byteLength = -1) {
    if (Array.isArray(fieldOrChunks)) {
      return new SparseIndex(fieldOrChunks, byteLength)
    } else {
      return new DenseIndex(fieldOrChunks, byteLength)
    }
  }

  constructor(byteLength) {
    this._byteLength = byteLength
    this.handle = Buffer.allocUnsafe(binding.sizeof_quickbit_index_t)
  }

  get byteLength() {
    return this._byteLength
  }

  skipFirst(value, position = 0) {
    const n = this.byteLength * 8

    if (position < 0) position += n
    if (position < 0) position = 0
    if (position >= n) return n - 1

    return binding.quickbit_napi_skip_first(
      this.handle,
      this.byteLength,
      value ? 1 : 0,
      position
    )
  }

  skipLast(value, position = this.byteLength * 8 - 1) {
    const n = this.byteLength * 8

    if (position < 0) position += n
    if (position < 0) return 0
    if (position >= n) position = n - 1

    return binding.quickbit_napi_skip_last(
      this.handle,
      this.byteLength,
      value ? 1 : 0,
      position
    )
  }
}

exports.Index = Index

class DenseIndex extends Index {
  constructor(field, byteLength) {
    super(byteLength)
    this.field = field

    binding.quickbit_napi_index_init(this.handle, toBuffer(this.field))
  }

  get byteLength() {
    if (this._byteLength !== -1) return this._byteLength
    return this.field.byteLength
  }

  update(bit) {
    const n = this.byteLength * 8

    if (bit < 0) bit += n
    if (bit < 0 || bit >= n) return false

    return (
      binding.quickbit_napi_index_update(
        this.handle,
        toBuffer(this.field),
        bit
      ) !== 0
    )
  }
}

function selectChunk(chunks, offset) {
  for (let i = 0; i < chunks.length; i++) {
    const next = chunks[i]

    const start = next.offset
    const end = next.offset + next.field.byteLength

    if (offset >= start && offset + 16 <= end) {
      return next
    }
  }

  return null
}

class SparseIndex extends Index {
  constructor(chunks, byteLength) {
    super(byteLength)
    this.chunks = chunks

    binding.quickbit_napi_index_init_sparse(
      this.handle,
      this.chunks.map(toBufferChunk)
    )
  }

  get byteLength() {
    if (this._byteLength !== -1) return this._byteLength
    const last = this.chunks[this.chunks.length - 1]
    return last ? last.offset + last.field.byteLength : 0
  }

  update(bit) {
    const n = this.byteLength * 8

    if (bit < 0) bit += n
    if (bit < 0 || bit >= n) return false

    const j = Math.floor(bit / 128)

    const offset = j * 16

    const chunk = selectChunk(this.chunks, offset)

    if (chunk === null) return false

    return (
      binding.quickbit_napi_index_update_sparse(
        this.handle,
        toBuffer(chunk.field),
        chunk.offset,
        bit
      ) !== 0
    )
  }
}
{
  "name": "quickbit-native",
  "version": "2.4.8",
  "description": "libquickbit JavaScript bindings for Node.js",
  "main": "index.js",
  "files": [
    "index.js",
    "macros.h",
    "binding.cc",
    "binding.js",
    "CMakeLists.txt",
    "prebuilds"
  ],
  "addon": true,
  "scripts": {
    "test": "npm run lint && npm run test:bare && npm run test:node",
    "test:bare": "bare test.mjs",
    "test:node": "node test.mjs",
    "lint": "prettier . --check"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/quickbit-native.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/quickbit-native/issues"
  },
  "homepage": "https://github.com/holepunchto/quickbit-native#readme",
  "dependencies": {
    "require-addon": "^1.1.0"
  },
  "devDependencies": {
    "b4a": "^1.6.7",
    "bare-compat-napi": "^1.3.2",
    "brittle": "^3.1.0",
    "cmake-bare": "^1.1.7",
    "cmake-fetch": "^1.1.0",
    "cmake-napi": "^1.0.2",
    "prettier": "^3.5.3",
    "prettier-config-standard": "^7.0.0"
  }
}
const simdle = require('simdle-universal')

const INDEX_LEN = (16 /* root */ + 128 * 16 /* children */) * 2

const get = exports.get = function get (field, bit) {
  const n = field.byteLength * 8

  if (bit < 0) bit += n
  if (bit < 0 || bit >= n) return false

  const m = field.BYTES_PER_ELEMENT * 8

  const offset = bit & (m - 1)
  const i = (bit - offset) / m

  return (field[i] & (1 << offset)) !== 0
}

const set = exports.set = function set (field, bit, value = true) {
  const n = field.byteLength * 8

  if (bit < 0) bit += n
  if (bit < 0 || bit >= n) return false

  const m = field.BYTES_PER_ELEMENT * 8

  const offset = bit & (m - 1)
  const i = (bit - offset) / m
  const mask = 1 << offset

  if (value) {
    if ((field[i] & mask) !== 0) return false
  } else {
    if ((field[i] & mask) === 0) return false
  }

  field[i] ^= mask

  return true
}

exports.fill = function fill (field, value, start = 0, end = field.byteLength * 8) {
  const n = field.byteLength * 8

  if (start < 0) start += n
  if (end < 0) end += n
  if (start < 0 || start >= field.byteLength * 8 || start >= end) return field

  const m = field.BYTES_PER_ELEMENT * 8

  let i, j

  {
    const offset = start & (m - 1)
    i = (start - offset) / m

    if (offset !== 0) {
      let shift = m - offset
      if (end - start < shift) shift = end - start

      const mask = ((1 << shift) - 1) << offset

      if (value) field[i] |= mask
      else field[i] &= ~mask

      i++
    }
  }

  {
    const offset = end & (m - 1)
    j = (end - offset) / m

    if (offset !== 0 && j >= i) {
      const mask = (1 << offset) - 1

      if (value) field[j] |= mask
      else field[j] &= ~mask
    }
  }

  if (i < j) field.fill(value ? (2 ** m) - 1 : 0, i, j)

  return field
}

exports.clear = function clear (field, ...chunks) {
  const n = field.byteLength

  for (const chunk of chunks) {
    if (chunk.offset >= n) continue

    const m = chunk.field.byteLength

    let i = chunk.offset
    let j = 0

    while (((i & 15) !== 0 || (j & 15) !== 0) && i < n && j < m) {
      field[i] = field[i] & ~chunk.field[j]
      i++
      j++
    }

    if (i + 15 < n && j + 15 < m) {
      const len = Math.min(n - (n & 15) - i, m - (m & 15) - j)

      simdle.clear(field.subarray(i, i + len), chunk.field.subarray(j, j + len), field.subarray(i, i + len))
    }

    while (i < n && j < m) {
      field[i] = field[i] & ~chunk.field[j]
      i++
      j++
    }
  }
}

function bitOffset (bit, offset) {
  return !bit ? offset : (INDEX_LEN * 8 / 2) + offset
}

function byteOffset (bit, offset) {
  return !bit ? offset : (INDEX_LEN / 2) + offset
}

exports.findFirst = function findFirst (field, value, position = 0) {
  const n = field.byteLength * 8

  if (position < 0) position += n
  if (position < 0) position = 0
  if (position >= n) return -1

  value = !!value

  for (let i = position; i < n; i++) {
    if (get(field, i) === value) return i
  }

  return -1
}

exports.findLast = function findLast (field, value, position = field.byteLength * 8 - 1) {
  const n = field.byteLength * 8

  if (position < 0) position += n
  if (position < 0) return -1
  if (position >= n) position = n - 1

  value = !!value

  for (let i = position; i >= 0; i--) {
    if (get(field, i) === value) return i
  }

  return -1
}

const Index = exports.Index = class Index {
  static from (fieldOrChunks, byteLength = -1) {
    if (Array.isArray(fieldOrChunks)) {
      return new SparseIndex(fieldOrChunks, byteLength)
    } else {
      return new DenseIndex(fieldOrChunks, byteLength)
    }
  }

  constructor (byteLength) {
    this._byteLength = byteLength
    this.handle = new Uint32Array(INDEX_LEN / 4)
  }

  get byteLength () {
    return this._byteLength
  }

  skipFirst (value, position = 0) {
    const n = this.byteLength * 8

    if (position < 0) position += n
    if (position < 0) position = 0
    if (position >= n) return n - 1

    let i = Math.floor(position / 16384)

    if (i > 127) return position

    while (i <= 127 && get(this.handle, bitOffset(value, i))) {
      i++
    }

    if (i === 128) return n - 1

    let k = i * 16384
    let j = 0

    if (position > k) j = Math.floor((position - k) / 128)

    while (j <= 127 && get(this.handle, bitOffset(value, i * 128 + j + 128))) {
      j++
      k += 128
    }

    if (j === 128 && i !== 127) return this.skipFirst(value, (i + 1) * 16384)

    if (k > position) position = k

    return position < n ? position : n - 1
  }

  skipLast (value, position = this.byteLength * 8 - 1) {
    const n = this.byteLength * 8

    if (position < 0) position += n
    if (position < 0) return 0
    if (position >= n) position = n - 1

    let i = Math.floor(position / 16384)

    if (i > 127) return position

    while (i >= 0 && get(this.handle, bitOffset(value, i))) {
      i--
    }

    if (i === -1) return 0

    let k = ((i + 1) * 16384) - 1
    let j = 127

    if (position < k) j = 128 - Math.ceil((k - position) / 128)

    while (j >= 0 && get(this.handle, bitOffset(value, i * 128 + j + 128))) {
      j--
      k -= 128
    }

    if (j === -1 && i !== 0) return this.skipLast(value, i * 16384 - 1)

    if (k < position) position = k

    return position
  }
}

class DenseIndex extends Index {
  constructor (field, byteLength) {
    super(byteLength)
    this.field = field

    const m = field.BYTES_PER_ELEMENT

    for (let i = 0; i < 128; i++) {
      for (let j = 0; j < 128; j++) {
        const offset = (i * 128 + j) * 16
        let allz = true
        let allo = false

        if (offset + 16 <= this.field.byteLength) {
          const vec = this.field.subarray(offset / m, (offset + 16) / m)

          allz = simdle.allz(vec)
          allo = simdle.allo(vec)
        }

        const k = i * 128 + 128 + j

        set(this.handle, bitOffset(false, k), allz)
        set(this.handle, bitOffset(true, k), allo)
      }

      {
        const offset = byteOffset(false, i * 16 + 16) / 4
        const allo = simdle.allo(this.handle.subarray(offset, offset + 4))

        set(this.handle, bitOffset(false, i), allo)
      }

      {
        const offset = byteOffset(true, i * 16 + 16) / 4
        const allo = simdle.allo(this.handle.subarray(offset, offset + 4))

        set(this.handle, bitOffset(true, i), allo)
      }
    }
  }

  get byteLength () {
    if (this._byteLength !== -1) return this._byteLength
    return this.field.byteLength
  }

  update (bit) {
    const n = this.byteLength * 8

    if (bit < 0) bit += n
    if (bit < 0 || bit >= n) return false

    const m = this.field.BYTES_PER_ELEMENT

    const i = Math.floor(bit / 16384)
    const j = Math.floor(bit / 128)

    const offset = (j * 16) / m
    const vec = this.field.subarray(offset, offset + (16 / m))

    const allz = simdle.allz(vec)
    const allo = simdle.allo(vec)

    let changed = false

    if (set(this.handle, bitOffset(false, 128 + j), allz)) {
      changed = true

      const offset = byteOffset(false, i * 16 + 16) / 4
      const allo = simdle.allo(this.handle.subarray(offset, offset + 4))

      set(this.handle, bitOffset(false, i), allo)
    }

    if (set(this.handle, bitOffset(true, 128 + j), allo)) {
      changed = true

      const offset = byteOffset(true, i * 16 + 16) / 4
      const allo = simdle.allo(this.handle.subarray(offset, offset + 4))

      set(this.handle, bitOffset(true, i), allo)
    }

    return changed
  }
}

function selectChunk (chunks, offset) {
  for (let i = 0; i < chunks.length; i++) {
    const next = chunks[i]

    const start = next.offset
    const end = next.offset + next.field.byteLength

    if (offset >= start && offset + 16 <= end) {
      return next
    }
  }

  return null
}

class SparseIndex extends Index {
  constructor (chunks, byteLength) {
    super(byteLength)
    this.chunks = chunks

    for (let i = 0; i < 128; i++) {
      for (let j = 0; j < 128; j++) {
        const offset = (i * 128 + j) * 16
        let allz = true
        let allo = false

        const chunk = selectChunk(this.chunks, offset)

        if (chunk !== null) {
          const m = chunk.field.BYTES_PER_ELEMENT

          const vec = chunk.field.subarray((offset - chunk.offset) / m, (offset - chunk.offset + 16) / m)

          allz = simdle.allz(vec)
          allo = simdle.allo(vec)
        }

        const k = i * 128 + 128 + j

        set(this.handle, bitOffset(false, k), allz)
        set(this.handle, bitOffset(true, k), allo)
      }

      {
        const offset = byteOffset(false, i * 16 + 16) / 4
        const allo = simdle.allo(this.handle.subarray(offset, offset + 4))

        set(this.handle, bitOffset(false, i), allo)
      }

      {
        const offset = byteOffset(true, i * 16 + 16) / 4
        const allo = simdle.allo(this.handle.subarray(offset, offset + 4))

        set(this.handle, bitOffset(true, i), allo)
      }
    }
  }

  get byteLength () {
    if (this._byteLength !== -1) return this._byteLength
    const last = this.chunks[this.chunks.length - 1]
    return last ? last.offset + last.field.byteLength : 0
  }

  update (bit) {
    const n = this.byteLength * 8

    if (bit < 0) bit += n
    if (bit < 0 || bit >= n) return false

    const i = Math.floor(bit / 16384)
    const j = Math.floor(bit / 128)

    const offset = j * 16

    const chunk = selectChunk(this.chunks, offset)

    if (chunk === null) return false

    const m = chunk.field.BYTES_PER_ELEMENT

    const vec = chunk.field.subarray((offset - chunk.offset) / m, (offset - chunk.offset + 16) / m)

    const allz = simdle.allz(vec)
    const allo = simdle.allo(vec)

    let changed = false

    if (set(this.handle, bitOffset(false, 128 + j), allz)) {
      changed = true

      const offset = byteOffset(false, i * 16 + 16) / 4
      const allo = simdle.allo(this.handle.subarray(offset, offset + 4))

      set(this.handle, bitOffset(false, i), allo)
    }

    if (set(this.handle, bitOffset(true, 128 + j), allo)) {
      changed = true

      const offset = byteOffset(true, i * 16 + 16) / 4
      const allo = simdle.allo(this.handle.subarray(offset, offset + 4))

      set(this.handle, bitOffset(true, i), allo)
    }

    return changed
  }
}
const fallback = require('./fallback')

try {
  const native = require('quickbit-native')

  // These functions are faster in JavaScript
  exports.get = fallback.get
  exports.set = fallback.set
  exports.fill = fallback.fill

  // These functions are faster in C
  exports.clear = native.clear
  exports.findFirst = native.findFirst
  exports.findLast = native.findLast
  exports.Index = native.Index
} catch {
  module.exports = fallback
}
{
  "name": "quickbit-universal",
  "version": "2.2.0",
  "description": "Universal wrapper for libquickbit with a JavaScript fallback",
  "main": "index.js",
  "files": [
    "fallback.js",
    "index.js"
  ],
  "browser": {
    "./index.js": "./fallback.js"
  },
  "scripts": {
    "test": "standard && brittle test.mjs"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/quickbit-universal.git"
  },
  "author": "Kasper Isager Dalsgarð <kasper@funktionel.co>",
  "license": "ISC",
  "bugs": {
    "url": "https://github.com/holepunchto/quickbit-universal/issues"
  },
  "homepage": "https://github.com/holepunchto/quickbit-universal#readme",
  "dependencies": {
    "b4a": "^1.6.0",
    "simdle-universal": "^1.1.0"
  },
  "optionalDependencies": {
    "quickbit-native": "^2.2.0"
  },
  "devDependencies": {
    "brittle": "^3.1.0",
    "standard": "^17.0.0"
  }
}
module.exports = class RandomArrayIterator {
  constructor (values) {
    this.values = values
    this.start = 0
    this.length = this.values.length
  }

  next () {
    if (this.length === 0) {
      if (this.start === 0) return { done: true, value: undefined }
      this.length = this.start
      this.start = 0
    }

    const i = this.start + ((Math.random() * this.length) | 0)
    const j = this.start + --this.length
    const value = this.values[i]

    this.values[i] = this.values[j]
    this.values[j] = value

    return { done: false, value }
  }

  dequeue () {
    this.values[this.start + this.length] = this.values[this.values.length - 1]
    this.values.pop()
  }

  requeue () {
    const i = this.start + this.length
    const value = this.values[i]
    this.values[i] = this.values[this.start]
    this.values[this.start++] = value
  }

  restart () {
    this.start = 0
    this.length = this.values.length
    return this
  }

  [Symbol.iterator] () {
    return this
  }
}
{
  "name": "random-array-iterator",
  "version": "1.0.0",
  "description": "An iterator to iterate an array in random order with controls to requeue or dequeue elements during the iteration",
  "main": "index.js",
  "dependencies": {},
  "devDependencies": {
    "standard": "^16.0.3",
    "tape": "^5.0.1"
  },
  "scripts": {
    "test": "standard && tape test.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/mafintosh/random-array-iterator.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/mafintosh/random-array-iterator/issues"
  },
  "homepage": "https://github.com/mafintosh/random-array-iterator"
}
const rrp = require('resolve-reject-promise')

module.exports = class ReadyGuard {
  constructor() {
    const { resolve, reject, promise } = rrp()

    this.resolve = resolve
    this.reject = reject
    this.promise = promise

    this.entered = false
    this.opened = false
    this.destroyed = false
  }

  ready() {
    return this.promise
  }

  enter() {
    if (this.entered || this.destroyed) return false
    this.entered = true
    return true
  }

  exit() {
    this.opened = true
    this.resolve()
  }

  destroy(err) {
    this.destroyed = true
    if (!this.opened) this.reject(err || new Error('Ready guard destroyed'))
  }
}
{
  "name": "ready-guard",
  "version": "1.0.0",
  "description": "Simple signal to do composite resource lifecycles",
  "main": "index.js",
  "dependencies": {
    "resolve-reject-promise": "^1.1.0"
  },
  "scripts": {
    "format": "prettier --write .",
    "lint": "lunte && prettier --check .",
    "test": "node test.js",
    "test:bare": "bare test.js"
  },
  "files": [
    "index.js"
  ],
  "repository": {
    "type": "git",
    "url": "https://github.com/holepunchto/ready-guard.git"
  },
  "author": "Holepunch Inc.",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/ready-guard/issues"
  },
  "homepage": "https://github.com/holepunchto/ready-guard",
  "devDependencies": {
    "brittle": "^3.19.1",
    "lunte": "^1.6.0",
    "prettier": "^3.8.1",
    "prettier-config-holepunch": "^2.0.0"
  }
}
const EventEmitter = require('events')

module.exports = class ReadyResource extends EventEmitter {
  constructor () {
    super()

    this.opening = null
    this.closing = null

    this.opened = false
    this.closed = false
  }

  ready () {
    if (this.opening !== null) return this.opening
    this.opening = open(this)
    return this.opening
  }

  close () {
    if (this.closing !== null) return this.closing
    this.closing = close(this)
    return this.closing
  }

  async _open () {
    // add impl here
  }

  async _close () {
    // add impl here
  }
}

async function open (self) {
  // open after close
  if (self.closing !== null) return

  try {
    await self._open()
  } catch (err) {
    self.close() // safe to run in bg
    throw err
  }

  self.opened = true
  self.emit('ready')
}

async function close (self) {
  try {
    if (self.opened === false && self.opening !== null) await self.opening
  } catch {
    // ignore errors on closing
  }
  if (self.opened === true || self.opening === null) await self._close()
  self.closed = true
  self.emit('close')
}
{
  "name": "ready-resource",
  "version": "1.2.0",
  "description": "Modern single resource management",
  "main": "index.js",
  "imports": {
    "events": {
      "bare": "bare-events",
      "default": "events"
    }
  },
  "types": "index.d.ts",
  "files": [
    "index.js",
    "index.d.ts"
  ],
  "scripts": {
    "test": "standard && brittle test.js"
  },
  "devDependencies": {
    "brittle": "^3.1.0",
    "standard": "^17.0.0"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/ready-resource.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/holepunchto/ready-resource/issues"
  },
  "homepage": "https://github.com/holepunchto/ready-resource",
  "dependencies": {
    "bare-events": "^2.2.0"
  }
}
module.exports = class RefCounter {
  constructor() {
    this.count = 0

    this._onidle = null
    this._idle = null
  }

  isIdle() {
    return this.count === 0
  }

  idle() {
    if (this.count === 0) return Promise.resolve()
    if (this._idle !== null) return this._idle

    this._idle = new Promise((resolve) => {
      this._onidle = resolve
    })

    return this._idle
  }

  inc() {
    this.count++
  }

  dec() {
    if (--this.count > 0) return

    if (this._onidle !== null) {
      const resolve = this._onidle
      this._idle = null
      this._onidle = null
      resolve()
    }
  }
}
{
  "name": "refcounter",
  "version": "1.0.0",
  "description": "Simple refcounter",
  "main": "index.js",
  "dependencies": {},
  "devDependencies": {},
  "repository": {
    "type": "git",
    "url": "https://github.com/holepunchto/refcounter.git"
  },
  "author": "Holepunch Inc",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/refcounter/issues"
  },
  "homepage": "https://github.com/holepunchto/refcounter"
}
module.exports = require.addon.bind(require)
{
  "name": "require-addon",
  "version": "1.2.0",
  "description": "Import native addons across JavaScript runtimes",
  "exports": {
    "./package": "./package.json",
    ".": {
      "bare": "./lib/bare.js",
      "node": "./lib/node.js",
      "default": "./lib/default.js"
    }
  },
  "imports": {
    "fs": {
      "bare": "bare-fs",
      "default": "fs"
    },
    "path": {
      "bare": "bare-path",
      "default": "path"
    },
    "url": {
      "bare": "bare-url",
      "default": "url"
    }
  },
  "files": [
    "lib"
  ],
  "scripts": {
    "test": "npm run lint && npm run test:bare && npm run test:node",
    "test:bare": "bare test.js",
    "test:node": "node test.js",
    "lint": "prettier --check .",
    "format": "prettier --write ."
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/require-addon.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/require-addon/issues"
  },
  "homepage": "https://github.com/holepunchto/require-addon#readme",
  "engines": {
    "bare": ">=1.10.0"
  },
  "dependencies": {
    "bare-addon-resolve": "^1.3.0"
  },
  "devDependencies": {
    "bare-bundle": "^1.8.1",
    "bare-bundle-evaluate": "^1.1.0",
    "bare-fs": "^4.0.0",
    "bare-path": "^3.0.0",
    "bare-url": "^2.1.0",
    "brittle": "^3.7.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^2.0.0"
  }
}
let tmpResolve = null
let tmpReject = null

if (Promise.withResolvers) {
  module.exports = Promise.withResolvers.bind(Promise)
} else {
  module.exports = function resolveRejectPromise () {
    const promise = new Promise(setTmp)
    const result = { promise, resolve: tmpResolve, reject: tmpReject }
    tmpResolve = tmpReject = null
    return result
  }
}

function setTmp (resolve, reject) {
  tmpResolve = resolve
  tmpReject = reject
}
{
  "name": "resolve-reject-promise",
  "version": "1.1.0",
  "description": "Create an inverted promise with no function allocs",
  "main": "index.js",
  "scripts": {
    "test": "standard"
  },
  "devDependencies": {
    "standard": "^17.1.2"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/mafintosh/resolve-reject-promise.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/mafintosh/resolve-reject-promise/issues"
  },
  "homepage": "https://github.com/mafintosh/resolve-reject-promise"
}
const resources = new Map()

exports.add = function(resource, teardown) {
  resources.set(resource, teardown)
}

exports.remove = function(resource) {
  resources.delete(resource)
}

if (global.Bare) global.Bare.on('exit', run)
else global.process.on('exit', run)

function run() {
  for (const [resource, teardown] of resources) {
    teardown(resource)
  }
}
{
  "name": "resource-on-exit",
  "version": "1.0.0",
  "description": "Register sync teardown handlers for a resource",
  "main": "index.js",
  "dependencies": {},
  "devDependencies": {},
  "repository": {
    "type": "git",
    "url": "https://github.com/holepunchto/bare-teardown.git"
  },
  "author": "Holepunch Inc.",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-teardown/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-teardown"
}
require.addon = require('require-addon')

module.exports = require.addon('.', __filename)
const ColumnFamily = require('./lib/column-family')
const Iterator = require('./lib/iterator')
const Snapshot = require('./lib/snapshot')
const State = require('./lib/state')
const { BloomFilterPolicy, RibbonFilterPolicy } = require('./lib/filter-policy')
const constants = require('./lib/constants')

class RocksDB {
  constructor(path, opts = {}) {
    const {
      columnFamily,
      state = new State(this, path, opts),
      snapshot = null,
      keyEncoding = null,
      valueEncoding = null
    } = opts

    this._state = state
    this._snapshot = snapshot
    this._columnFamily = state.getColumnFamily(columnFamily)
    this._keyEncoding = keyEncoding
    this._valueEncoding = valueEncoding
    this._index = -1

    this._state.addSession(this)
  }

  get opened() {
    return this._state.opened
  }

  get closed() {
    return this.isRoot() ? this._state.closed : this._index === -1
  }

  get path() {
    return this._state.path
  }

  get stats() {
    return this._state.stats
  }

  get snapshotted() {
    return this._snapshot !== null
  }

  get defaultColumnFamily() {
    return this._columnFamily
  }

  session({
    columnFamily = this._columnFamily,
    snapshot = this._snapshot !== null,
    keyEncoding = this._keyEncoding,
    valueEncoding = this._valueEncoding
  } = {}) {
    maybeClosed(this)

    return new RocksDB(null, {
      state: this._state,
      columnFamily,
      snapshot: snapshot ? this._snapshot || new Snapshot(this._state) : null,
      keyEncoding,
      valueEncoding
    })
  }

  columnFamily(name, opts) {
    return this.session({ ...opts, columnFamily: name })
  }

  snapshot() {
    return this.session({ snapshot: true })
  }

  isRoot() {
    return this === this._state.db
  }

  ready() {
    return this._state.ready()
  }

  async close({ force } = {}) {
    if (!this._state.opened) await this._state.ready()

    if (this._index !== -1) this._state.removeSession(this)

    if (force) {
      while (this._state.sessions.length > 0) {
        await this._state.sessions[this._state.sessions.length - 1].close()
      }
    }

    return this.isRoot() ? this._state.close() : Promise.resolve()
  }

  suspend() {
    maybeClosed(this)

    return this._state.suspend()
  }

  resume() {
    maybeClosed(this)

    return this._state.resume()
  }

  isIdle() {
    return this._state.handles.isIdle()
  }

  idle() {
    return this._state.handles.idle()
  }

  iterator(range, opts) {
    maybeClosed(this)

    return new Iterator(this, { ...range, ...opts })
  }

  async *keys(range, opts) {
    for await (const { key } of this.iterator(range, {
      ...opts,
      values: false
    })) {
      yield key
    }
  }

  async peek(range, opts) {
    for await (const value of this.iterator({ ...range, ...opts, limit: 1 })) {
      return value
    }

    return null
  }

  read(opts) {
    maybeClosed(this)

    return this._state.createReadBatch(this, opts)
  }

  write(opts) {
    maybeClosed(this)

    return this._state.createWriteBatch(this, opts)
  }

  flush(opts) {
    maybeClosed(this)

    return this._state.flush(this, opts)
  }

  async get(key, opts) {
    const batch = this.read({ ...opts, capacity: 1, autoDestroy: true })
    const value = batch.get(key)
    batch.tryFlush()
    return value
  }

  async put(key, value, opts) {
    const batch = this.write({ ...opts, capacity: 1, autoDestroy: true })
    batch.tryPut(key, value)
    await batch.flush()
  }

  async delete(key, opts) {
    const batch = this.write({ ...opts, capacity: 1, autoDestroy: true })
    batch.tryDelete(key)
    await batch.flush()
  }

  async deleteRange(start, end, opts) {
    const batch = this.write({ ...opts, capacity: 1, autoDestroy: true })
    batch.tryDeleteRange(start, end)
    await batch.flush()
  }

  async compact(opts = {}) {
    maybeClosed(this)

    await this._state.compact(this, opts)
  }

  async compactRange(start = null, end = null, opts = {}) {
    if (typeof end === 'object' && end !== null) {
      opts = end
      end = null
    } else if (typeof start === 'object' && start !== null) {
      opts = start
      start = null
      end = null
    }

    await this._state.compactRange(this, start, end, opts)
  }

  async approximateSize(start, end, opts = {}) {
    return this._state.approximateSize(this, start, end, opts)
  }

  async currentWalFile() {
    maybeClosed(this)

    return this._state.currentWalFile()
  }

  async getStatsLevel() {
    maybeClosed(this)

    return this._state.getStatsLevel()
  }

  async setStatsLevel(level) {
    maybeClosed(this)

    await this._state.setStatsLevel(level)
  }

  async getProperty(name) {
    maybeClosed(this)

    return this._state.getProperty(name)
  }

  _ref() {
    if (this._snapshot) this._snapshot.ref()
    this._state.handles.inc()
  }

  _unref() {
    if (this._snapshot) this._snapshot.unref()
    this._state.handles.dec()
  }

  diagnostics() {
    return this._state.diagnostics()
  }
}

module.exports = exports = RocksDB

exports.constants = constants

exports.ColumnFamily = ColumnFamily
exports.BloomFilterPolicy = BloomFilterPolicy
exports.RibbonFilterPolicy = RibbonFilterPolicy

function maybeClosed(db) {
  if (db._state.closing || db._index === -1) throw new Error('RocksDB session is closed')
}
const c = require('compact-encoding')
const binding = require('../binding')

const empty = Buffer.alloc(0)
const resolved = Promise.resolve()

class RocksDBBatch {
  constructor(db, opts = {}) {
    const { capacity = 8, autoDestroy = false } = opts

    db._ref()

    this._db = db
    this._destroyed = false
    this._capacity = capacity
    this._operations = []
    this._promises = []

    this._enqueuePromise = this._enqueuePromise.bind(this)

    this._request = null
    this._resolve = null
    this._reject = null

    this._handle = null
    this._buffer = null
    this._autoDestroy = autoDestroy
    this._stats = null

    this._resetStats()

    if (db._state.opened === true) this.ready()
  }

  _reuse(db, opts = {}) {
    const { autoDestroy = false } = opts

    db._ref()

    this._db = db
    this._destroyed = false
    this._autoDestroy = autoDestroy
  }

  _onfinished(err) {
    const resolve = this._resolve
    const reject = this._reject

    if (this._request) this._db._state.io.dec()

    this._operations = []
    this._promises = []
    this._request = null
    this._resolve = null
    this._reject = null

    this._resetStats()

    if (this._autoDestroy === true) this.destroy()

    if (reject !== null && err) reject(err)
    else if (resolve !== null) resolve()
  }

  _resize() {
    if (this._operations.length <= this._capacity) return false

    while (this._operations.length > this._capacity) {
      this._capacity *= 2
    }

    return true
  }

  async ready() {
    if (this._handle !== null) return

    if (this._db._state.opened === false) await this._db._state.ready()

    this._init()
  }

  destroy() {
    if (this._request) throw new Error('Request in progress')
    if (this._destroyed) return

    this._destroyed = true

    if (this._promises.length) this._abort()

    this._db._unref()
    this._onfree()
  }

  _onfree() {
    this._resetStats()
    this._db._state.freeBatch(this, false)
    this._db = null
  }

  _abort() {
    for (let i = 0; i < this._promises.length; i++) {
      const promise = this._promises[i]
      if (promise !== null) promise.reject(new Error('Batch is destroyed'))
    }

    this._onfinished(new Error('Batch is destroyed'))
  }

  _resetStats() {}

  async flush() {
    if (this._request) throw new Error('Request in progress')
    if (this._destroyed) throw new Error('Batch is destroyed')

    this._request = new Promise((resolve, reject) => {
      this._resolve = resolve
      this._reject = reject
    })

    this._flush()

    return this._request
  }

  tryFlush() {
    if (this._request) throw new Error('Request in progress')
    if (this._destroyed) throw new Error('Batch is destroyed')

    this._request = resolved

    this._flush()
  }

  async _flush() {
    if (this._handle === null) await this.ready()

    this._db._state.io.inc()

    if (this._db._state.resumed !== null) {
      const resumed = await this._db._state.waitForResume()

      if (!resumed) {
        if (this._destroyed) {
          this._db._state.io.dec()
        } else {
          this._destroyed = true
          this._abort()
          this._db._unref()
        }
      }
    }
  }

  _enqueuePromise(resolve, reject) {
    this._promises.push({ resolve, reject })
  }

  _encodeKey(k) {
    if (this._db._keyEncoding) return c.encode(this._db._keyEncoding, k)
    if (typeof k === 'string') return Buffer.from(k)
    return k
  }

  _encodeValue(v) {
    if (this._db._valueEncoding) return c.encode(this._db._valueEncoding, v)
    if (v === null) return empty
    if (typeof v === 'string') return Buffer.from(v)
    return v
  }

  _decodeValue(b) {
    if (this._db._valueEncoding) return c.decode(this._db._valueEncoding, b)
    return b
  }
}

exports.ReadBatch = class RocksDBReadBatch extends RocksDBBatch {
  constructor(db, opts = {}) {
    super(db, opts)

    const { asyncIO = false, fillCache = true } = opts

    this._asyncIO = asyncIO
    this._fillCache = fillCache
  }

  _init() {
    this._handle = binding.readInit()
    this._buffer = binding.readBuffer(this._handle, this._capacity)
  }

  _resize() {
    if (super._resize() && this._handle !== null) {
      this._buffer = binding.readBuffer(this._handle, this._capacity)
    }
  }

  async _flush() {
    await super._flush()

    if (this._destroyed) return

    try {
      binding.read(
        this._db._state._handle,
        this._handle,
        this._operations,
        this._db._snapshot ? this._db._snapshot._handle : undefined,
        this._asyncIO,
        this._fillCache,
        this,
        this._onread
      )
    } catch (err) {
      this._db._state.io.dec()
      throw err
    }

    this._db._state.stats.gets += this._stats.gets
    this._db._state.stats.readBatches++
  }

  _onread(errs, values) {
    let applied = true

    for (let i = 0, n = this._promises.length; i < n; i++) {
      const err = errs[i]
      if (err) applied = false

      const promise = this._promises[i]
      if (promise === null) continue

      if (err) promise.reject(err)
      else promise.resolve(values[i] ? this._decodeValue(Buffer.from(values[i])) : null)
    }

    this._onfinished(applied ? null : new AggregateError(errs, 'Batch was not applied'))
  }

  _resetStats() {
    this._stats = { gets: 0 }
  }

  get(key) {
    if (this._request) throw new Error('Request already in progress')
    this._stats.gets++

    const promise = new Promise(this._enqueuePromise)

    this._operations.push(new RocksDBGet(this._encodeKey(key), this._db._columnFamily))

    this._resize()

    return promise
  }
}

exports.WriteBatch = class RocksDBWriteBatch extends RocksDBBatch {
  _init() {
    this._handle = binding.writeInit()
    this._buffer = binding.writeBuffer(this._handle, this._capacity)
  }

  _resize() {
    if (super._resize() && this._handle !== null) {
      this._buffer = binding.writeBuffer(this._handle, this._capacity)
    }
  }

  _onfree() {
    this._resetStats()
    this._db._state.freeBatch(this, true)
    this._db = null
  }

  async _flush() {
    await super._flush()

    if (this._destroyed) return

    try {
      binding.write(this._db._state._handle, this._handle, this._operations, this, this._onwrite)
    } catch (err) {
      this._db._state.io.dec()
      throw err
    }

    this._db._state.stats.puts += this._stats.puts
    this._db._state.stats.deletes += this._stats.deletes
    this._db._state.stats.rangeDeletes += this._stats.rangeDeletes
    this._db._state.stats.writeBatches++
  }

  _onwrite(err) {
    const applied = !err

    for (let i = 0, n = this._promises.length; i < n; i++) {
      const promise = this._promises[i]
      if (promise === null) continue

      if (err) promise.reject(err)
      else promise.resolve()
    }

    this._onfinished(applied ? null : new Error('Batch was not applied', { cause: err }))
  }

  _resetStats() {
    this._stats = { puts: 0, deletes: 0, rangeDeletes: 0 }
  }

  put(key, value) {
    if (this._request) throw new Error('Request already in progress')
    this._stats.puts++

    const promise = new Promise(this._enqueuePromise)

    this._operations.push(
      new RocksDBPut(this._encodeKey(key), this._encodeValue(value), this._db._columnFamily)
    )

    this._resize()

    return promise
  }

  tryPut(key, value) {
    if (this._request) throw new Error('Request already in progress')
    this._stats.puts++

    this._operations.push(
      new RocksDBPut(this._encodeKey(key), this._encodeValue(value), this._db._columnFamily)
    )

    this._promises.push(null)

    this._resize()
  }

  delete(key) {
    if (this._request) throw new Error('Request already in progress')
    this._stats.deletes++

    const promise = new Promise(this._enqueuePromise)

    this._operations.push(new RocksDBDelete(this._encodeKey(key), this._db._columnFamily))

    this._resize()

    return promise
  }

  tryDelete(key) {
    if (this._request) throw new Error('Request already in progress')
    this._stats.deletes++

    this._operations.push(new RocksDBDelete(this._encodeKey(key), this._db._columnFamily))

    this._promises.push(null)

    this._resize()
  }

  deleteRange(start, end) {
    if (this._request) throw new Error('Request already in progress')
    this._stats.rangeDeletes++

    const promise = new Promise(this._enqueuePromise)

    this._operations.push(
      new RocksDBDeleteRange(this._encodeKey(start), this._encodeKey(end), this._db._columnFamily)
    )

    this._resize()

    return promise
  }

  tryDeleteRange(start, end) {
    if (this._request) throw new Error('Request already in progress')
    this._stats.rangeDeletes++

    this._operations.push(
      new RocksDBDeleteRange(this._encodeKey(start), this._encodeKey(end), this._db._columnFamily)
    )

    this._promises.push(null)

    this._resize()
  }
}

class RocksDBGet {
  constructor(key, columnFamily) {
    this.key = key
    this.columnFamily = columnFamily._handle
  }

  get type() {
    return binding.GET
  }
}

class RocksDBPut {
  constructor(key, value, columnFamily) {
    this.key = key
    this.value = value
    this.columnFamily = columnFamily._handle
  }

  get type() {
    return binding.PUT
  }
}

class RocksDBDelete {
  constructor(key, columnFamily) {
    this.key = key
    this.columnFamily = columnFamily._handle
  }

  get type() {
    return binding.DELETE
  }
}

class RocksDBDeleteRange {
  constructor(start, end, columnFamily) {
    this.start = start
    this.end = end
    this.columnFamily = columnFamily._handle
  }

  get type() {
    return binding.DELETE_RANGE
  }
}
const binding = require('../binding')
const constants = require('./constants')
const { BloomFilterPolicy } = require('./filter-policy')

class RocksDBColumnFamily {
  constructor(name, opts = {}) {
    const {
      // Blob options
      enableBlobFiles = false,
      minBlobSize = 0,
      blobFileSize = 0,
      enableBlobGarbageCollection = true,
      // Block table options
      tableBlockSize = 8192,
      tableCacheIndexAndFilterBlocks = true,
      tableFormatVersion = 6,
      optimizeFiltersForMemory = false,
      blockCache = true,
      filterPolicy = new BloomFilterPolicy(10),
      topLevelIndexPinningTier = constants.pinningTier.ALL,
      partitionPinningTier = constants.pinningTier.ALL,
      unpartitionedPinningTier = constants.pinningTier.ALL,
      optimizeFiltersForHits = false,
      numLevels = 7,
      maxWriteBufferNumber = 2,
      blobGarbageCollectionAgeCutOff = 0.25,
      blobGarbageCollectionForceThreshold = 1.0
    } = opts

    this._name = name
    this._flushing = null
    this._options = {
      enableBlobFiles,
      minBlobSize,
      blobFileSize,
      enableBlobGarbageCollection,
      tableBlockSize,
      tableCacheIndexAndFilterBlocks,
      tableFormatVersion,
      optimizeFiltersForMemory,
      blockCache,
      filterPolicy,
      topLevelIndexPinningTier,
      partitionPinningTier,
      unpartitionedPinningTier,
      optimizeFiltersForHits,
      numLevels,
      maxWriteBufferNumber,
      blobGarbageCollectionAgeCutOff,
      blobGarbageCollectionForceThreshold
    }

    const filterPolicyArguments = [0, 0, 0]

    if (filterPolicy !== null) {
      filterPolicyArguments[0] = filterPolicy.type

      switch (filterPolicy.type) {
        case 1: // Bloom filter policy
          filterPolicyArguments[1] = filterPolicy.bitsPerKey
          break
        case 2: // Ribbon filter policy
          filterPolicyArguments[1] = filterPolicy.bloomEquivalentBitsPerKey
          filterPolicyArguments[2] = filterPolicy.bloomBeforeLevel

          break
      }
    }

    this._handle = binding.columnFamilyInit(
      name,
      enableBlobFiles,
      minBlobSize,
      blobFileSize,
      enableBlobGarbageCollection,
      tableBlockSize,
      tableCacheIndexAndFilterBlocks,
      tableFormatVersion,
      optimizeFiltersForMemory,
      blockCache === false,
      ...filterPolicyArguments,
      topLevelIndexPinningTier,
      partitionPinningTier,
      unpartitionedPinningTier,
      optimizeFiltersForHits,
      numLevels,
      maxWriteBufferNumber,
      blobGarbageCollectionAgeCutOff,
      blobGarbageCollectionForceThreshold
    )
  }

  cloneSettings(name) {
    return new RocksDBColumnFamily(name, this._options)
  }

  get name() {
    return this._name
  }

  destroy() {
    if (this._handle === null) return

    binding.columnFamilyDestroy(this._handle)

    this._handle = null
  }
}

module.exports = RocksDBColumnFamily
module.exports = {
  pinningTier: {
    NONE: 0,
    FLUSHED_AND_SIMILAR: 1,
    ALL: 2
  },
  garbageCollectionPolicy: {
    DEFAULT: 0,
    FORCE: 1,
    DISABLE: 2
  },
  bottommostLevelCompaction: {
    NONE: 0,
    SKIP: 1,
    FORCE: 2
  },
  walRecoveryMode: {
    TOLERATE_CORRUPTED_TAIL_RECORDS: 0,
    ABSOLUTE_CONSISTENCY: 1,
    POINT_IN_TIME: 2,
    SKIP_ANY_CORRUPTED_RECORDS: 3
  },
  statsLevel: {
    DISABLE_ALL: 0,
    EXCEPT_HISTOGRAM_OR_TIMERS: 1,
    EXCEPT_TIMERS: 2,
    EXCEPT_DETAILED_TIMERS: 3,
    EXCEPT_TIME_FOR_MUTEX: 4,
    ALL: 5
  },
  walFileType: {
    ARCHIVED: 0,
    ALIVE: 1
  }
}
exports.BloomFilterPolicy = class RocksDBBloomFilterPolicy {
  get type() {
    return 1
  }

  constructor(bitsPerKey) {
    this.bitsPerKey = bitsPerKey
  }
}

exports.RibbonFilterPolicy = class RocksDBRibbonFilterPolicy {
  get type() {
    return 2
  }

  constructor(bloomEquivalentBitsPerKey, bloomBeforeLevel = 0) {
    this.bloomEquivalentBitsPerKey = bloomEquivalentBitsPerKey
    this.bloomBeforeLevel = bloomBeforeLevel
  }
}
const { Readable } = require('streamx')
const c = require('compact-encoding')
const binding = require('../binding')

const empty = Buffer.alloc(0)

module.exports = class RocksDBIterator extends Readable {
  constructor(db, opts = {}) {
    const {
      gt = null,
      gte = null,
      lt = null,
      lte = null,
      reverse = false,
      values = true,
      limit = Infinity,
      capacity = 8
    } = opts

    super()

    db._ref()

    this._db = db

    this._gt = gt ? this._encodeKey(gt) : empty
    this._gte = gte ? this._encodeKey(gte) : empty
    this._lt = lt ? this._encodeKey(lt) : empty
    this._lte = lte ? this._encodeKey(lte) : empty

    this._reverse = reverse
    this._values = values
    this._limit = limit < 0 ? Infinity : limit
    this._capacity = capacity
    this._opened = false

    this._pendingOpen = null
    this._pendingRead = null
    this._pendingDestroy = null

    this._buffer = null
    this._handle = null

    if (this._db._state.opened === true) this.ready()
  }

  _onopen(err) {
    const cb = this._pendingOpen
    this._pendingOpen = null
    this._opened = true
    this._db._state.io.dec()
    cb(err)
  }

  _onread(err, keys, values) {
    const cb = this._pendingRead
    this._pendingRead = null
    this._db._state.io.dec()
    if (err) return cb(err)

    const n = keys.length

    this._limit -= n

    for (let i = 0; i < n; i++) {
      this.push({
        key: this._decodeKey(Buffer.from(keys[i])),
        value: this._values ? this._decodeValue(Buffer.from(values[i])) : null
      })
    }

    if (n < this._capacity) this.push(null)

    cb(null)
  }

  _onclose(err) {
    const cb = this._pendingDestroy
    this._pendingDestroy = null
    this._db._state.io.dec()
    this._db._unref()
    cb(err)
  }

  _resize() {
    if (this._handle !== null) {
      this._buffer = binding.iteratorBuffer(this._handle, this._capacity)
    }
  }

  async ready() {
    if (this._handle !== null) return

    if (this._db._state.opened === false) await this._db._state.ready()

    this._init()
  }

  _init() {
    this._handle = binding.iteratorInit()
    this._buffer = binding.iteratorBuffer(this._handle, this._capacity)
  }

  async _open(cb) {
    await this.ready()

    this._db._state.io.inc()

    if (this._db._state.resumed !== null) {
      const resumed = await this._db._state.waitForResume()

      if (!resumed) {
        this._db._state.io.dec()

        return cb(new Error('RocksDB session is closed'))
      }
    }

    this._pendingOpen = cb

    try {
      binding.iteratorOpen(
        this._db._state._handle,
        this._handle,
        this._db._columnFamily._handle,
        this._gt,
        this._gte,
        this._lt,
        this._lte,
        this._reverse,
        !this._values, // Keys only
        this._db._snapshot ? this._db._snapshot._handle : undefined,
        this,
        this._onopen,
        this._onclose,
        this._onread
      )
    } catch (err) {
      this._db._state.io.dec()

      cb(err)
    }
  }

  async _read(cb) {
    this._db._state.io.inc()

    if (this._db._state.resumed !== null) {
      const resumed = await this._db._state.waitForResume()

      if (!resumed) {
        this._db._state.io.dec()

        return cb(new Error('RocksDB session is closed'))
      }
    }

    this._pendingRead = cb

    try {
      binding.iteratorRead(this._handle, Math.min(this._capacity, this._limit))
    } catch (err) {
      this._db._state.io.dec()

      cb(err)
    }
  }

  async _destroy(cb) {
    await this.ready()

    this._db._state.io.inc()

    if (this._opened === false) {
      this._db._state.io.dec()
      this._db._unref()

      return cb(null)
    }

    this._pendingDestroy = cb

    try {
      binding.iteratorClose(this._handle)
    } catch (err) {
      this._db._state.io.dec()
      this._db._unref()

      cb(err)
    }
  }

  _encodeKey(k) {
    if (this._db._keyEncoding !== null) return c.encode(this._db._keyEncoding, k)
    if (typeof k === 'string') return Buffer.from(k)
    return k
  }

  _decodeKey(b) {
    if (this._db._keyEncoding !== null) return c.decode(this._db._keyEncoding, b)
    return b
  }

  _decodeValue(b) {
    if (this._db._valueEncoding !== null) return c.decode(this._db._valueEncoding, b)
    return b
  }
}
const binding = require('../binding')

module.exports = class RocksDBSnapshot {
  constructor(state) {
    this._state = state

    this._handle = null
    this._refs = 0

    if (state.deferSnapshotInit === false) this._init()
  }

  _init() {
    this._handle = binding.snapshotCreate(this._state._handle)
  }

  ref() {
    this._refs++
  }

  unref() {
    if (--this._refs > 0) return

    if (this._handle === null) return

    binding.snapshotDestroy(this._handle)

    this._handle = null
  }
}
const ReadyResource = require('ready-resource')
const RefCounter = require('refcounter')
const rrp = require('resolve-reject-promise')
const SignalPromise = require('signal-promise')
const c = require('compact-encoding')
const { ReadBatch, WriteBatch } = require('./batch')
const ColumnFamily = require('./column-family')
const binding = require('../binding')
const constants = require('./constants')

const MAX_BATCH_REUSE = 64
const empty = Buffer.alloc(0)

module.exports = class RocksDBState extends ReadyResource {
  constructor(db, path, opts) {
    super()

    const {
      columnFamily = new ColumnFamily('default', opts),
      columnFamilies = [],
      readOnly = false,
      createIfMissing = true,
      createMissingColumnFamilies = true,
      maxBackgroundJobs = 6,
      bytesPerSync = 1048576,
      maxOpenFiles = -1,
      useDirectReads = false,
      avoidUnnecessaryBlockingIO = false,
      skipStatsUpdateOnOpen = false,
      useDirectIOForFlushAndCompaction = false,
      maxFileOpeningThreads = 16,
      lock = null,
      walRecoveryMode = constants.walRecoveryMode.POINT_IN_TIME,
      bestEffortsRecovery = false,
      enableStatistics = false,
      statsLevel = constants.statsLevel.EXCEPT_HISTOGRAM_OR_TIMERS,
      walTtlSeconds = 0,
      walSizeLimitMegabytes = 0,
      avoidFlushDuringShutdown = false,
      walFilterPrefixes = []
    } = opts

    this.path = path
    this.db = db
    this.handles = new RefCounter()
    this.io = new RefCounter()
    this.sessions = []
    this.columnFamilies = [columnFamily]
    this.deferSnapshotInit = true
    this.resumed = null
    this.stats = {
      gets: 0,
      puts: 0,
      deletes: 0,
      rangeDeletes: 0,
      readBatches: 0,
      writeBatches: 0
    }

    this._suspended = false
    this._suspending = false
    this._updating = false
    this._updatingSignal = new SignalPromise()
    this._columnsFlushed = false
    this._lock = lock
    this._readBatches = []
    this._writeBatches = []

    for (const columnFamily of columnFamilies) {
      this.columnFamilies.push(
        typeof columnFamily === 'string' ? new ColumnFamily(columnFamily, opts) : columnFamily
      )
    }

    this._handle = binding.init(
      readOnly,
      createIfMissing,
      createMissingColumnFamilies,
      maxBackgroundJobs,
      bytesPerSync,
      maxOpenFiles,
      useDirectReads,
      avoidUnnecessaryBlockingIO,
      skipStatsUpdateOnOpen,
      useDirectIOForFlushAndCompaction,
      maxFileOpeningThreads,
      walRecoveryMode,
      bestEffortsRecovery,
      enableStatistics,
      statsLevel,
      walTtlSeconds,
      walSizeLimitMegabytes,
      avoidFlushDuringShutdown,
      walFilterPrefixes.map(encodePrefix)
    )
  }

  createReadBatch(db, opts) {
    if (this._readBatches.length === 0) return new ReadBatch(db, opts)
    const batch = this._readBatches.pop()
    batch._reuse(db, opts)
    return batch
  }

  createWriteBatch(db, opts) {
    if (this._writeBatches.length === 0) return new WriteBatch(db, opts)
    const batch = this._writeBatches.pop()
    batch._reuse(db, opts)
    return batch
  }

  freeBatch(batch, writable) {
    if (batch._capacity > 16) return
    const queue = writable ? this._writeBatches : this._readBatches
    if (queue.length >= MAX_BATCH_REUSE) return
    queue.push(batch)
  }

  addSession(db) {
    db._index = this.sessions.push(db) - 1
    if (db._snapshot) db._snapshot.ref()
  }

  removeSession(db) {
    const head = this.sessions.pop()
    if (head !== db) this.sessions[(head._index = db._index)] = head
    db._index = -1
    if (db._snapshot) db._snapshot.unref()
  }

  upsertColumnFamily(c) {
    if (typeof c === 'string') {
      let col = this.getColumnFamilyByName(c)
      if (col) return col
      col = this.columnFamilies[0].cloneSettings(c)
      this.columnFamilies.push(col)
      return col
    }

    if (this.columnFamilies.includes(c)) return c
    this.columnFamilies.push(c)
    return c
  }

  getColumnFamily(c) {
    if (!c) return this.columnFamilies[0]
    if (!this._columnsFlushed) return this.upsertColumnFamily(c)

    if (typeof c !== 'string') return c

    const col = this.getColumnFamilyByName(c)
    if (col === null) throw new Error('Unknown column family')
    return col
  }

  getColumnFamilyByName(name) {
    for (const col of this.columnFamilies) {
      if (col.name === name) return col
    }
    return null
  }

  async _open() {
    await Promise.resolve() // Allow column families to populate if on-demand

    if (this._lock) await this._lock.ready()

    const req = { resolve: null, reject: null, handle: null }

    const promise = new Promise((resolve, reject) => {
      req.resolve = resolve
      req.reject = reject
    })

    this._columnsFlushed = true

    const lock = this._lock === null ? -1 : this._lock.transfer()

    req.handle = binding.open(
      this._handle,
      this,
      this.path,
      this.columnFamilies.map((c) => c._handle),
      lock,
      req,
      onopen
    )

    await promise

    this.deferSnapshotInit = false

    for (const session of this.sessions) {
      if (session._snapshot) session._snapshot._init()
    }

    function onopen(err) {
      if (err) req.reject(err)
      else req.resolve()
    }
  }

  async _close() {
    while (this._updating) await this._updatingSignal.wait()
    if (this.resumed) this.resumed.resolve(false)

    while (!this.io.isIdle()) await this.io.idle()
    while (!this.handles.isIdle()) await this.handles.idle()

    while (this.sessions.length > 0) {
      await this.sessions[this.sessions.length - 1].close()
    }

    for (const columnFamily of this.columnFamilies) columnFamily.destroy()

    const req = { resolve: null, reject: null, handle: null }

    const promise = new Promise((resolve, reject) => {
      req.resolve = resolve
      req.reject = reject
    })

    req.handle = binding.close(this._handle, req, onclose)

    try {
      await promise
    } finally {
      if (this._lock) await this._lock.close()
    }

    function onclose(err) {
      if (err) req.reject(err)
      else req.resolve()
    }
  }

  async flush(db, opts) {
    if (this.opened === false) await this.ready()

    this.io.inc()

    if (this.resumed !== null) {
      const resumed = await this.waitForResume()

      if (!resumed) {
        this.io.dec()

        throw new Error('RocksDB session is closed')
      }
    }

    const req = { resolve: null, reject: null, handle: null }

    const promise = new Promise((resolve, reject) => {
      req.resolve = resolve
      req.reject = reject
    })

    try {
      req.handle = binding.flush(this._handle, db._columnFamily._handle, req, onflush)

      await promise
    } finally {
      this.io.dec()
    }

    function onflush(err) {
      if (err) req.reject(err)
      else req.resolve()
    }
  }

  suspend() {
    this._suspending = true
    return this.update()
  }

  resume() {
    this._suspending = false
    return this.update()
  }

  async update() {
    while (this._updating) await this._updatingSignal.wait()
    if (this._suspending === this._suspended || this.closing) return
    this._updating = true
    try {
      if (this._suspending) await this._suspend()
      else await this._resume()
    } finally {
      this._updating = false
      this._updatingSignal.notify()
    }
  }

  async waitForResume() {
    while (this.resumed !== null) {
      this.io.dec()
      const resumed = await this.resumed.promise
      this.io.inc()
      if (resumed === false) return false
    }
    return true
  }

  async _suspend() {
    if (this._suspended === true) return

    this.resumed = rrp()
    while (!this.io.isIdle()) await this.io.idle()
    this.io.inc()

    const req = { resolve: null, reject: null, handle: null }

    const promise = new Promise((resolve, reject) => {
      req.resolve = resolve
      req.reject = reject
    })

    try {
      req.handle = binding.suspend(this._handle, req, onsuspend)

      await promise

      this._suspended = true
    } finally {
      this.io.dec()
    }

    function onsuspend(err) {
      if (err) req.reject(err)
      else req.resolve()
    }
  }

  async _resume() {
    if (this._suspended === false) return

    const req = { resolve: null, reject: null, handle: null }

    const promise = new Promise((resolve, reject) => {
      req.resolve = resolve
      req.reject = reject
    })

    req.handle = binding.resume(this._handle, req, onresume)

    await promise

    this._suspended = false

    const resumed = this.resumed
    this.resumed = null
    resumed.resolve(true)

    function onresume(err) {
      if (err) req.reject(err)
      else req.resolve()
    }
  }

  async compact(db, opts) {
    if (this.opened === false) await this.ready()

    this.io.inc()

    const {
      exclusive = false,
      blobGarbageCollectionPolicy = constants.garbageCollectionPolicy.DEFAULT,
      blobGarbageCollectionAgeCutoff = 0.25,
      bottommostLevelCompaction = constants.bottommostLevelCompaction.NONE
    } = opts

    const req = { resolve: null, reject: null, handle: null }

    const promise = new Promise((resolve, reject) => {
      req.resolve = resolve
      req.reject = reject
    })

    try {
      req.handle = binding.compact(
        this._handle,
        db._columnFamily._handle,
        exclusive,
        blobGarbageCollectionPolicy,
        blobGarbageCollectionAgeCutoff,
        bottommostLevelCompaction,
        req,
        oncompact
      )

      await promise
    } finally {
      this.io.dec()
    }

    function oncompact(err) {
      if (err) req.reject(err)
      else req.resolve()
    }
  }

  async compactRange(db, start, end, opts) {
    if (this.opened === false) await this.ready()

    this.io.inc()

    const {
      exclusive = false,
      blobGarbageCollectionPolicy = constants.garbageCollectionPolicy.DEFAULT,
      blobGarbageCollectionAgeCutoff = 0.25,
      bottommostLevelCompaction = constants.bottommostLevelCompaction.NONE
    } = opts

    start = this._encodeKey(start)
    end = this._encodeKey(end)

    const req = { resolve: null, reject: null, handle: null, start, end }

    const promise = new Promise((resolve, reject) => {
      req.resolve = resolve
      req.reject = reject
    })

    try {
      req.handle = binding.compactRange(
        this._handle,
        db._columnFamily._handle,
        start,
        end,
        exclusive,
        blobGarbageCollectionPolicy,
        blobGarbageCollectionAgeCutoff,
        bottommostLevelCompaction,
        req,
        oncompactrange
      )

      await promise
    } finally {
      this.io.dec()
    }

    function oncompactrange(err) {
      if (err) req.reject(err)
      else req.resolve()
    }
  }

  async approximateSize(db, start, end, opts) {
    if (this.opened === false) await this.ready()

    this.io.inc()

    const { includeMemtables = false, includeFiles = true, filesSizeErrorMargin = -1 } = opts

    start = this._encodeKey(start)
    end = this._encodeKey(end)

    const req = { resolve: null, reject: null, handle: null, start, end }

    const promise = new Promise((resolve, reject) => {
      req.resolve = resolve
      req.reject = reject
    })

    try {
      req.handle = binding.approximateSize(
        this._handle,
        db._columnFamily._handle,
        start,
        end,
        includeMemtables,
        includeFiles,
        filesSizeErrorMargin,
        req,
        onapproximatesize
      )

      return await promise
    } finally {
      this.io.dec()
    }

    function onapproximatesize(err, result) {
      if (err) req.reject(err)
      else req.resolve(result)
    }
  }

  async currentWalFile() {
    if (this.opened === false) await this.ready()

    this.io.inc()

    const req = { resolve: null, reject: null, handle: null }

    const promise = new Promise((resolve, reject) => {
      req.resolve = resolve
      req.reject = reject
    })

    try {
      req.handle = binding.currentWalFile(this._handle, req, oncurrentwalfile)

      return await promise
    } finally {
      this.io.dec()
    }

    function oncurrentwalfile(err, path, number, type, startSequence, size) {
      if (err) req.reject(err)
      else req.resolve({ path, number, type, startSequence, size })
    }
  }

  async getStatsLevel() {
    if (this.opened === false) await this.ready()

    return binding.statsLevelGet(this._handle)
  }

  async setStatsLevel(level) {
    if (this.opened === false) await this.ready()

    binding.statsLevelSet(this._handle, level)
  }

  async getProperty(name) {
    if (this.opened === false) await this.ready()

    return binding.propertyGet(this._handle, name)
  }

  _encodeKey(k) {
    if (this.db._keyEncoding) return c.encode(this.db._keyEncoding, k)
    if (typeof k === 'string') return Buffer.from(k)
    if (k === null) return empty
    return k
  }

  diagnostics() {
    return {
      suspended: this._suspended,
      suspending: this._suspending,
      updating: this._updating,
      resumedPending: this.resumed !== null,
      io: this.io.count,
      handles: this.handles.count,
      sessions: this.sessions.length
    }
  }
}

function encodePrefix(prefix) {
  if (typeof prefix === 'string') return Buffer.from(prefix)
  return prefix
}
{
  "name": "rocksdb-native",
  "version": "3.17.4",
  "description": "librocksdb bindings for JavaScript",
  "exports": {
    ".": "./index.js",
    "./package": "./package.json"
  },
  "imports": {
    "crypto": {
      "bare": "bare-crypto",
      "default": "crypto"
    },
    "fs": {
      "bare": "bare-fs",
      "default": "fs"
    },
    "path": {
      "bare": "bare-path",
      "default": "path"
    }
  },
  "files": [
    "index.js",
    "binding.cc",
    "binding.js",
    "CMakeLists.txt",
    "lib",
    "prebuilds",
    "!prebuilds/android-*/**/*.node"
  ],
  "addon": true,
  "scripts": {
    "format": "prettier --write . && lunte --fix",
    "lint": "prettier --check . && lunte",
    "test": "npm run test:bare && npm run test:node",
    "test:bare": "brittle-bare --coverage test.js",
    "test:node": "brittle-node --coverage test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/rocksdb-native.git"
  },
  "author": "Holepunch Inc",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/rocksdb-native/issues"
  },
  "homepage": "https://github.com/holepunchto/rocksdb-native",
  "engines": {
    "bare": ">=1.16.0"
  },
  "dependencies": {
    "compact-encoding": "^3.0.0",
    "ready-resource": "^1.0.0",
    "refcounter": "^1.0.0",
    "require-addon": "^1.0.2",
    "resolve-reject-promise": "^1.1.0",
    "signal-promise": "^1.0.3",
    "streamx": "^2.16.1"
  },
  "devDependencies": {
    "bare-compat-napi": "^1.3.0",
    "bare-crypto": "^1.12.0",
    "bare-fs": "^4.5.0",
    "bare-path": "^3.0.0",
    "brittle": "^3.5.0",
    "cmake-bare": "^1.1.14",
    "cmake-fetch": "^1.0.1",
    "cmake-napi": "^1.0.6",
    "fd-lock": "^2.0.0",
    "lunte": "^1.8.0",
    "paparam": "^1.9.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^2.0.0"
  }
}
module.exports = safetyCatch

function isActuallyUncaught (err) {
  if (!err) return false
  return err instanceof TypeError ||
    err instanceof SyntaxError ||
    err instanceof ReferenceError ||
    err instanceof EvalError ||
    err instanceof RangeError ||
    err instanceof URIError ||
    err.code === 'ERR_ASSERTION' ||
    err.name === 'AssertionError'
}

function throwErrorNT (err) {
  queueMicrotask(() => { throw err })
}

function safetyCatch (err) {
  if (isActuallyUncaught(err)) {
    throwErrorNT(err)
    throw err
  }
}
{
  "name": "safety-catch",
  "version": "1.0.3",
  "description": "Small module that makes sure your catch, caught an actual error and not a programming mistake or assertion",
  "main": "index.js",
  "dependencies": {},
  "devDependencies": {},
  "repository": {
    "type": "git",
    "url": "https://github.com/mafintosh/safety-catch.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/mafintosh/safety-catch/issues"
  },
  "homepage": "https://github.com/mafintosh/safety-catch"
}
let tmpResolve = null

module.exports = class ScopeLock {
  constructor ({ debounce = false } = {}) {
    this.debounce = debounce
    this.waiting = []
    this.locked = false
    this.skip = 0
    this.destroyed = false
  }

  flush () {
    if (this.locked === false && this.waiting.length === 0) return Promise.resolve(this.destroyed === false)

    const promise = new Promise(setTmpResolve)
    const resolve = tmpResolve

    tmpResolve = null
    this.waiting.push({ lock: false, resolve })

    return promise
  }

  destroy () {
    this.destroyed = true
  }

  lock () {
    const promise = new Promise(setTmpResolve)
    const resolve = tmpResolve

    tmpResolve = null

    if (this.locked === true) {
      this.waiting.push({ lock: true, resolve })
      return promise
    }

    if (this.destroyed === true) {
      resolve(false)
      return promise
    }

    this.locked = true
    resolve(true)

    return promise
  }

  unlock () {
    if (this.destroyed === true) {
      for (let i = 0; i < this.waiting.length; i++) {
        this.waiting[i].resolve(false)
      }
      this.waiting = []
      this.skip = 0
      this.locked = false
      return
    }

    if (this.skip !== 0) {
      for (let i = 0; i < this.skip; i++) {
        const { lock, resolve } = this.waiting[i]
        resolve(lock === false)
      }

      this.waiting = this.waiting.slice(this.skip)
      this.skip = 0
    }

    while (this.waiting.length > 0 && this.waiting[0].lock === false) {
      this.waiting.shift().resolve(true)
    }

    if (this.waiting.length === 0) {
      this.locked = false
      return
    }

    const { resolve } = this.waiting.shift()
    if (this.debounce === true) this.skip = this.waiting.length

    resolve(true)
  }
}

function setTmpResolve (resolve) {
  tmpResolve = resolve
}
{
  "name": "scope-lock",
  "version": "1.2.4",
  "description": "Some concurrency semantics around entering scopes",
  "main": "index.js",
  "files": [
    "index.js"
  ],
  "devDependencies": {
    "brittle": "^3.7.0",
    "standard": "^17.1.2"
  },
  "scripts": {
    "test": "standard && brittle test.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/holepunchto/scope-lock.git"
  },
  "author": "Holepunch Inc.",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/scope-lock/issues"
  },
  "homepage": "https://github.com/holepunchto/scope-lock"
}
module.exports = class Signal {
  constructor () {
    this._resolve = null
    this._reject = null
    this._promise = null
    this._bind = bind.bind(this)
    this._onerror = clear.bind(this)
    this._onsuccess = clear.bind(this, null)
    this._timers = new Set()
  }

  wait (max) {
    if (!this._promise) {
      this._promise = new Promise(this._bind)
      this._promise.then(this._onsuccess).catch(this._onerror)
    }
    if (max) return this._sleep(max)
    return this._promise
  }

  _sleep (max) {
    const s = new Promise((resolve, reject) => {
      const done = () => {
        this._timers.delete(state)
        resolve(true)
      }
      const id = setTimeout(done, max)
      const state = { id, resolve, reject }
      this._timers.add(state)
    })

    return s
  }

  notify (err) {
    if (!this._promise) return
    const resolve = this._resolve
    const reject = this._reject
    this._promise = null
    if (err) reject(err)
    else resolve(true)
  }
}

function clear (err) {
  for (const { id, resolve, reject } of this._timers) {
    clearTimeout(id)
    if (err) reject(err)
    else resolve(true)
  }
  this._timers.clear()
}

function bind (resolve, reject) {
  this._resolve = resolve
  this._reject = reject
}
{
  "name": "signal-promise",
  "version": "1.0.3",
  "description": "Simple wait/notify promise with optional max wait time",
  "main": "index.js",
  "dependencies": {},
  "devDependencies": {},
  "repository": {
    "type": "git",
    "url": "https://github.com/mafintosh/signal-promise.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/mafintosh/signal-promise/issues"
  },
  "homepage": "https://github.com/mafintosh/signal-promise"
}
var varint = require('varint')
exports.encode = function encode (v, b, o) {
  v = v >= 0 ? v*2 : v*-2 - 1
  var r = varint.encode(v, b, o)
  encode.bytes = varint.encode.bytes
  return r
}
exports.decode = function decode (b, o) {
  var v = varint.decode(b, o)
  decode.bytes = varint.decode.bytes
  return v & 1 ? (v+1) / -2 : v / 2
}

exports.encodingLength = function (v) {
  return varint.encodingLength(v >= 0 ? v*2 : v*-2 - 1)
}
{
  "name": "signed-varint",
  "description": "efficiently store signed integers in varint",
  "version": "2.0.1",
  "homepage": "https://github.com/dominictarr/signed-varint",
  "repository": {
    "type": "git",
    "url": "git://github.com/dominictarr/signed-varint.git"
  },
  "dependencies": {
    "varint": "~5.0.0"
  },
  "devDependencies": {
    "tape": "~2.12.3"
  },
  "scripts": {
    "test": "node test.js"
  },
  "author": "Dominic Tarr <dominic.tarr@gmail.com> (http://dominictarr.com)",
  "license": "MIT"
}
require.addon = require('require-addon')

module.exports = require.addon('.', __filename)
const binding = require('./binding')
const b4a = require('b4a')

function predicate(u8, u16, u32) {
  return function predicate(buf) {
    if (buf.byteLength % 16 !== 0) {
      throw new Error('Buffer length must be a multiple of 16')
    }

    const n = buf.BYTES_PER_ELEMENT

    if (n === 1) return u8(buf)
    if (n === 2) return u16(buf)
    return u32(buf)
  }
}

function unary(u8, u16, u32) {
  return function unary(buf, result = b4a.allocUnsafe(buf.byteLength)) {
    if (buf.byteLength % 16 !== 0) {
      throw new Error('Buffer length must be a multiple of 16')
    }

    if (buf.byteLength !== result.byteLength) {
      throw new Error('Length of result buffer is insufficient')
    }

    const n = buf.BYTES_PER_ELEMENT

    if (n === 1) u8(buf, result)
    else if (n === 2) u16(buf, result)
    else u32(buf, result)

    return result
  }
}

function binary(u8, u16, u32) {
  return function binary(a, b, result = b4a.allocUnsafe(a.byteLength)) {
    if (a.byteLength % 16 !== 0) {
      throw new Error('Buffer length must be a multiple of 16')
    }

    if (a.byteLength !== b.byteLength || a.byteLength !== result.byteLength) {
      throw new Error('Buffers must be the same length')
    }

    const n = a.BYTES_PER_ELEMENT

    if (n === 1) u8(a, b, result)
    else if (n === 2) u16(a, b, result)
    else u32(a, b, result)

    return result
  }
}

function reduce(u8, u16, u32) {
  return function reduce(buf) {
    if (buf.byteLength % 16 !== 0) {
      throw new Error('Buffer length must be a multiple of 16')
    }

    const n = buf.BYTES_PER_ELEMENT

    if (n === 1) return u8(buf)
    if (n === 2) return u16(buf)
    return u32(buf)
  }
}

exports.allo = predicate(
  binding.simdle_native_allo_v128_u8,
  binding.simdle_native_allo_v128_u16,
  binding.simdle_native_allo_v128_u32
)

exports.allz = predicate(
  binding.simdle_native_allz_v128_u8,
  binding.simdle_native_allz_v128_u16,
  binding.simdle_native_allz_v128_u32
)

exports.and = binary(
  binding.simdle_native_and_v128_u8,
  binding.simdle_native_and_v128_u16,
  binding.simdle_native_and_v128_u32
)

exports.clear = binary(
  binding.simdle_native_clear_v128_u8,
  binding.simdle_native_clear_v128_u16,
  binding.simdle_native_clear_v128_u32
)

exports.clo = unary(
  binding.simdle_native_clo_v128_u8,
  binding.simdle_native_clo_v128_u16,
  binding.simdle_native_clo_v128_u32
)

exports.clz = unary(
  binding.simdle_native_clz_v128_u8,
  binding.simdle_native_clz_v128_u16,
  binding.simdle_native_clz_v128_u32
)

exports.cnt = unary(
  binding.simdle_native_cnt_v128_u8,
  binding.simdle_native_cnt_v128_u16,
  binding.simdle_native_cnt_v128_u32
)

exports.cto = unary(
  binding.simdle_native_cto_v128_u8,
  binding.simdle_native_cto_v128_u16,
  binding.simdle_native_cto_v128_u32
)

exports.ctz = unary(
  binding.simdle_native_ctz_v128_u8,
  binding.simdle_native_ctz_v128_u16,
  binding.simdle_native_ctz_v128_u32
)

exports.not = unary(
  binding.simdle_native_not_v128_u8,
  binding.simdle_native_not_v128_u16,
  binding.simdle_native_not_v128_u32
)

exports.or = binary(
  binding.simdle_native_or_v128_u8,
  binding.simdle_native_or_v128_u16,
  binding.simdle_native_or_v128_u32
)

exports.sum = reduce(
  binding.simdle_native_sum_v128_u8,
  binding.simdle_native_sum_v128_u16,
  binding.simdle_native_sum_v128_u32
)

exports.xor = binary(
  binding.simdle_native_xor_v128_u8,
  binding.simdle_native_xor_v128_u16,
  binding.simdle_native_xor_v128_u32
)
{
  "name": "simdle-native",
  "version": "1.3.9",
  "description": "libsimdle JavaScript bindings for Node.js",
  "main": "index.js",
  "files": [
    "index.js",
    "binding.cc",
    "binding.js",
    "CMakeLists.txt",
    "prebuilds"
  ],
  "addon": true,
  "scripts": {
    "format": "prettier --write .",
    "lint": "prettier --check .",
    "test": "npm run lint && npm run test:bare && npm run test:node",
    "test:bare": "bare test.mjs",
    "test:node": "node test.mjs"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/simdle-native.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/simdle-native/issues"
  },
  "homepage": "https://github.com/holepunchto/simdle-native#readme",
  "dependencies": {
    "b4a": "^1.6.0",
    "require-addon": "^1.1.0"
  },
  "devDependencies": {
    "bare-compat-napi": "^1.3.2",
    "brittle": "^3.1.0",
    "cmake-bare": "^1.1.7",
    "cmake-fetch": "^1.1.0",
    "cmake-napi": "^1.0.2",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^1.0.0"
  }
}
const b4a = require('b4a')
const scalar = require('./scalar')

function view (buf, n) {
  if (n === buf.BYTES_PER_ELEMENT) return buf

  let TypedArray

  if (n === 1) TypedArray = Uint8Array
  else if (n === 2) TypedArray = Uint16Array
  else TypedArray = Uint32Array

  return new TypedArray(buf.buffer, buf.byteOffset, buf.byteLength / n)
}

function unary (u8, u16 = u8, u32 = u16) {
  return function unary (buf, result = b4a.allocUnsafe(buf.byteLength)) {
    if (buf.byteLength % 16 !== 0) {
      throw new Error('Buffer length must be a multiple of 16')
    }

    if (buf.byteLength !== result.byteLength) {
      throw new Error('Length of result buffer is insufficient')
    }

    const n = buf.BYTES_PER_ELEMENT

    if (n === 1) u8(buf, view(result, n))
    else if (n === 2) u16(buf, view(result, n))
    else u32(buf, view(result, n))

    return result
  }
}

function binary (u8, u16 = u8, u32 = u16) {
  return function binary (a, b, result = b4a.allocUnsafe(a.byteLength)) {
    if (a.byteLength % 16 !== 0) {
      throw new Error('Buffer length must be a multiple of 16')
    }

    if (a.byteLength !== b.byteLength || a.byteLength !== result.byteLength) {
      throw new Error('Buffers must be the same length')
    }

    const n = a.BYTES_PER_ELEMENT

    if (n === 1) u8(a, b, view(result, n))
    else if (n === 2) u16(a, b, view(result, n))
    else u32(a, b, view(result, n))

    return result
  }
}

function reduce (u8, u16 = u8, u32 = u16) {
  return function reduce (buf) {
    if (buf.byteLength % 16 !== 0) {
      throw new Error('Buffer length must be a multiple of 16')
    }

    const n = buf.BYTES_PER_ELEMENT

    if (n === 1) return u8(buf)
    if (n === 2) return u16(buf)
    return u32(buf)
  }
}

exports.allo = function allo (buf) {
  if (buf.byteLength % 16 !== 0) {
    throw new Error('Buffer length must be a multiple of 16')
  }

  const m = 2 ** (buf.BYTES_PER_ELEMENT * 8) - 1

  for (let i = 0, n = buf.length; i < n; i++) {
    if (buf[i] !== m) return false
  }

  return true
}

exports.allz = function allz (buf) {
  if (buf.byteLength % 16 !== 0) {
    throw new Error('Buffer length must be a multiple of 16')
  }

  for (let i = 0, n = buf.length; i < n; i++) {
    if (buf[i] !== 0) return false
  }

  return true
}

exports.and = binary(
  (a, b, result) => {
    for (let i = 0, n = result.length; i < n; i++) {
      result[i] = a[i] & b[i]
    }
  }
)

exports.clear = binary(
  (a, b, result) => {
    for (let i = 0, n = result.length; i < n; i++) {
      result[i] = a[i] & ~b[i]
    }
  }
)

exports.clo = unary(
  (buf, result) => {
    for (let i = 0, n = buf.length; i < n; i++) {
      result[i] = 24 - scalar.clo(buf[i])
    }
  },
  (buf, result) => {
    for (let i = 0, n = buf.length; i < n; i++) {
      result[i] = 16 - scalar.clo(buf[i])
    }
  },
  (buf, result) => {
    for (let i = 0, n = buf.length; i < n; i++) {
      result[i] = scalar.clo(buf[i])
    }
  }
)

exports.clz = unary(
  (buf, result) => {
    for (let i = 0, n = buf.length; i < n; i++) {
      result[i] = 24 - scalar.clz(buf[i])
    }
  },
  (buf, result) => {
    for (let i = 0, n = buf.length; i < n; i++) {
      result[i] = 16 - scalar.clz(buf[i])
    }
  },
  (buf, result) => {
    for (let i = 0, n = buf.length; i < n; i++) {
      result[i] = scalar.clz(buf[i])
    }
  }
)

exports.cnt = unary(
  (buf, result) => {
    for (let i = 0, n = buf.length; i < n; i++) {
      result[i] = scalar.cnt(buf[i]) & 0xff
    }
  },
  (buf, result) => {
    for (let i = 0, n = buf.length; i < n; i++) {
      result[i] = scalar.cnt(buf[i]) & 0xffff
    }
  },
  (buf, result) => {
    for (let i = 0, n = buf.length; i < n; i++) {
      result[i] = scalar.cnt(buf[i])
    }
  }
)

exports.cto = unary(
  (buf, result) => {
    for (let i = 0, n = buf.length; i < n; i++) {
      result[i] = Math.min(scalar.cto(buf[i]), 8)
    }
  },
  (buf, result) => {
    for (let i = 0, n = buf.length; i < n; i++) {
      result[i] = Math.min(scalar.cto(buf[i]), 16)
    }
  },
  (buf, result) => {
    for (let i = 0, n = buf.length; i < n; i++) {
      result[i] = scalar.cto(buf[i])
    }
  }
)

exports.ctz = unary(
  (buf, result) => {
    for (let i = 0, n = buf.length; i < n; i++) {
      result[i] = Math.min(scalar.ctz(buf[i]), 8)
    }
  },
  (buf, result) => {
    for (let i = 0, n = buf.length; i < n; i++) {
      result[i] = Math.min(scalar.ctz(buf[i]), 16)
    }
  },
  (buf, result) => {
    for (let i = 0, n = buf.length; i < n; i++) {
      result[i] = scalar.ctz(buf[i])
    }
  }
)

exports.not = unary(
  (buf, result) => {
    for (let i = 0, n = buf.length; i < n; i++) {
      result[i] = ~buf[i]
    }
  }
)

exports.or = binary(
  (a, b, result) => {
    for (let i = 0, n = result.length; i < n; i++) {
      result[i] = a[i] | b[i]
    }
  }
)

exports.sum = reduce(
  (buf) => {
    let result = 0n

    for (let i = 0, n = buf.length; i < n; i++) {
      result += BigInt(buf[i])
    }

    return result
  }
)

exports.xor = binary(
  (a, b, result) => {
    for (let i = 0, n = result.length; i < n; i++) {
      result[i] = a[i] ^ b[i]
    }
  }
)
try {
  module.exports = require('simdle-native')
} catch {
  module.exports = require('./fallback')
}
{
  "name": "simdle-universal",
  "version": "1.1.2",
  "description": "Universal wrapper for libsimdle with a JavaScript fallback",
  "main": "index.js",
  "files": [
    "fallback.js",
    "index.js",
    "scalar.js"
  ],
  "browser": {
    "./index.js": "./fallback.js"
  },
  "scripts": {
    "test": "standard && brittle test.mjs"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/simdle-universal.git"
  },
  "author": "Kasper Isager Dalsgarð <kasper@funktionel.co>",
  "license": "ISC",
  "bugs": {
    "url": "https://github.com/holepunchto/simdle-universal/issues"
  },
  "homepage": "https://github.com/holepunchto/simdle-universal#readme",
  "dependencies": {
    "b4a": "^1.6.0"
  },
  "optionalDependencies": {
    "simdle-native": "^1.1.1"
  },
  "devDependencies": {
    "brittle": "^3.1.0",
    "standard": "^17.0.0"
  }
}
const clz = exports.clz = function clz (n) {
  return Math.clz32(n)
}

exports.clo = function clo (n) {
  return clz(~n)
}

const ctz = exports.ctz = function ctz (n) {
  return 32 - (n === 0 ? 0 : (clz(n & -n) + 1))
}

exports.cto = function cto (n) {
  return ctz(~n)
}

exports.cnt = function cnt (n) {
  n = n - ((n >>> 1) & 0x55555555)
  n = (n & 0x33333333) + ((n >>> 2) & 0x33333333)
  n = (n + (n >>> 4)) & 0x0f0f0f0f
  n = (n * 0x01010101) >>> 24
  return n
}
require.addon = require('require-addon')
module.exports = require.addon('.', __filename)
const assert = require('assert')
const binding = require('./binding')
const { isNode } = require('which-runtime')

const OPTIONAL = Buffer.from(new ArrayBuffer(0))

module.exports = exports = { ...binding }

// memory

exports.sodium_memzero = function (buf) {
  assert(ArrayBuffer.isView(buf), 'buf must be a typed array')

  binding.sodium_memzero(buf)
}

exports.sodium_mlock = function (buf) {
  assert(ArrayBuffer.isView(buf), 'buf must be a typed array')

  const res = binding.sodium_mlock(buf)

  if (res !== 0) throw new Error('memory lock failed')
}

exports.sodium_munlock = function (buf) {
  assert(ArrayBuffer.isView(buf), 'buf must be a typed array')

  const res = binding.sodium_munlock(buf)

  if (res !== 0) throw new Error('memory unlock failed')
}

exports.sodium_malloc = function (size) {
  assert(size >= 0, 'invalid size')

  const buf = Buffer.from(binding.sodium_malloc(size))
  buf.secure = true
  return buf
}

exports.sodium_free = function (buf) {
  if (!buf || !buf.secure) return

  binding.sodium_free(buf.buffer)
}

exports.sodium_mprotect_noaccess = function (buf) {
  const res = binding.sodium_mprotect_noaccess(buf.buffer)

  if (res !== 0) throw new Error('failed to lock buffer')
}

exports.sodium_mprotect_readonly = function (buf) {
  const res = binding.sodium_mprotect_readonly(buf.buffer)

  if (res !== 0) throw new Error('failed to unlock buffer')
}

exports.sodium_mprotect_readwrite = function (buf) {
  const res = binding.sodium_mprotect_readwrite(buf.buffer)

  if (res !== 0) throw new Error('failed to unlock buffer')
}

// crypto_randombytes

exports.randombytes_buf = function (buffer) {
  assert(ArrayBuffer.isView(buffer), 'buffer must be a typed array')

  binding.randombytes_buf(buffer.buffer, buffer.byteOffset, buffer.byteLength)
}

exports.randombytes_buf_deterministic = function (buffer, seed) {
  assert(ArrayBuffer.isView(buffer), 'buffer must be a typed array')
  assert(ArrayBuffer.isView(seed), 'seed must be a typed array')
  assert(
    seed.byteLength === binding.randombytes_SEEDBYTES,
    "seed must be 'randombytes_SEEDBYTES' bytes"
  )

  binding.randombytes_buf_deterministic(
    buffer.buffer,
    buffer.byteOffset,
    buffer.byteLength,

    seed.buffer,
    seed.byteOffset,
    seed.byteLength
  )
}

// sodium_helpers

exports.sodium_memcmp = function (a, b) {
  assert(ArrayBuffer.isView(a), 'a must be a typed array')
  assert(ArrayBuffer.isView(b), 'b must be a typed array')
  assert(a.byteLength === b.byteLength, 'buffers must be of same length')

  return binding.sodium_memcmp(a, b)
}

exports.sodium_add = function (a, b) {
  assert(ArrayBuffer.isView(a), 'a must be a typed array')
  assert(ArrayBuffer.isView(b), 'b must be a typed array')
  assert(a.byteLength === b.byteLength, 'buffers must be of same length')

  binding.sodium_add(a, b)
}

exports.sodium_sub = function (a, b) {
  assert(ArrayBuffer.isView(a), 'a must be a typed array')
  assert(ArrayBuffer.isView(b), 'b must be a typed array')
  assert(a.byteLength === b.byteLength, 'buffers must be of same length')

  binding.sodium_sub(a, b)
}

exports.sodium_compare = function (a, b) {
  assert(ArrayBuffer.isView(a), 'a must be a typed array')
  assert(ArrayBuffer.isView(b), 'b must be a typed array')
  assert(a.byteLength === b.byteLength, 'buffers must be of same length')

  return binding.sodium_compare(a, b)
}

exports.sodium_is_zero = function (buffer, length) {
  if (length === undefined) length = buffer.byteLength

  assert(ArrayBuffer.isView(buffer), 'buffer must be a typed array')
  assert(length >= 0 && length <= buffer.byteLength, 'invalid length')

  return binding.sodium_is_zero(buffer, length)
}

exports.sodium_pad = function (buffer, unpaddedBuflen, blockSize) {
  assert(ArrayBuffer.isView(buffer), 'buffer must be a typed array')
  assert(unpaddedBuflen <= buffer.byteLength, 'unpadded length cannot exceed buffer length')
  assert(blockSize <= buffer.byteLength, 'block size cannot exceed buffer length')
  assert(blockSize >= 1, 'block size must be at least 1 byte')
  assert(
    buffer.byteLength >= unpaddedBuflen + (blockSize - (unpaddedBuflen % blockSize)),
    'buf not long enough'
  )

  return binding.sodium_pad(buffer, unpaddedBuflen, blockSize)
}

exports.sodium_unpad = function (buffer, paddedBuflen, blockSize) {
  assert(ArrayBuffer.isView(buffer), 'buffer must be a typed array')
  assert(paddedBuflen <= buffer.byteLength, 'unpadded length cannot exceed buffer length')
  assert(blockSize <= buffer.byteLength, 'block size cannot exceed buffer length')
  assert(blockSize >= 1, 'block size must be at least 1 byte')

  return binding.sodium_unpad(buffer, paddedBuflen, blockSize)
}

// crypto_sign

exports.crypto_sign_keypair = function (pk, sk) {
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(
    pk.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
    "pk must be 'crypto_sign_PUBLICKEYBYTES' bytes"
  )
  assert(
    sk.byteLength === binding.crypto_sign_SECRETKEYBYTES,
    "sk must be 'crypto_sign_SECRETKEYBYTES' bytes"
  )

  const res = binding.crypto_sign_keypair(pk, sk)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_sign_seed_keypair = function (pk, sk, seed) {
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(ArrayBuffer.isView(seed), 'seed must be a typed array')
  assert(
    pk.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
    "pk must be 'crypto_sign_PUBLICKEYBYTES' bytes"
  )
  assert(
    sk.byteLength === binding.crypto_sign_SECRETKEYBYTES,
    "sk must be 'crypto_sign_SECRETKEYBYTES' bytes"
  )
  assert(
    seed.byteLength === binding.crypto_sign_SEEDBYTES,
    "seed must be 'crypto_sign_SEEDBYTES' bytes"
  )

  const res = binding.crypto_sign_seed_keypair(pk, sk, seed)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_sign = function (sm, m, sk) {
  assert(ArrayBuffer.isView(sm), 'sm must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(
    sm.byteLength === binding.crypto_sign_BYTES + m.byteLength,
    "sm must be 'm.byteLength + crypto_sign_BYTES' bytes"
  )
  assert(
    sk.byteLength === binding.crypto_sign_SECRETKEYBYTES,
    "sk must be 'crypto_sign_SECRETKEYBYTES' bytes"
  )

  const res = binding.crypto_sign(sm, m, sk)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_sign_open = function (m, sm, pk) {
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(sm), 'sm must be a typed array')
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(
    sm.byteLength >= binding.crypto_sign_BYTES,
    "sm must be at least 'crypto_sign_BYTES' bytes"
  )
  assert(
    m.byteLength === sm.byteLength - binding.crypto_sign_BYTES,
    "m must be 'sm.byteLength - crypto_sign_BYTES' bytes"
  )
  assert(
    pk.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
    "pk must be 'crypto_sign_PUBLICKEYBYTES' bytes"
  )

  const res = binding.crypto_sign_open(m, sm, pk)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_sign_open = function (m, sm, pk) {
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(sm), 'sm must be a typed array')
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(
    sm.byteLength >= binding.crypto_sign_BYTES,
    "sm must be at least 'crypto_sign_BYTES' bytes"
  )
  assert(
    m.byteLength === sm.byteLength - binding.crypto_sign_BYTES,
    "m must be 'sm.byteLength - crypto_sign_BYTES' bytes"
  )
  assert(
    pk.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
    "pk must be 'crypto_sign_PUBLICKEYBYTES' bytes"
  )

  return binding.crypto_sign_open(m, sm, pk)
}

exports.crypto_sign_detached = function (sig, m, sk) {
  assert(ArrayBuffer.isView(sig), 'sig must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(sig.byteLength === binding.crypto_sign_BYTES, "sig must be 'crypto_sign_BYTES' bytes")
  assert(
    sk.byteLength === binding.crypto_sign_SECRETKEYBYTES,
    "sk must be 'crypto_sign_SECRETKEYBYTES' bytes"
  )

  const res = binding.crypto_sign_detached(sig, m, sk)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_sign_verify_detached = function (sig, m, pk) {
  assert(ArrayBuffer.isView(sig), 'sig must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(
    sig.byteLength >= binding.crypto_sign_BYTES,
    "sig must be at least 'crypto_sign_BYTES' bytes"
  )
  assert(
    pk.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
    "pk must be 'crypto_sign_PUBLICKEYBYTES' bytes"
  )

  return binding.crypto_sign_verify_detached(
    sig.buffer,
    sig.byteOffset,
    sig.byteLength,

    m.buffer,
    m.byteOffset,
    m.byteLength,

    pk.buffer,
    pk.byteOffset,
    pk.byteLength
  )
}

exports.crypto_sign_ed25519_sk_to_pk = function (pk, sk) {
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(
    pk.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
    "pk must be 'crypto_sign_PUBLICKEYBYTES' bytes"
  )
  assert(
    sk.byteLength === binding.crypto_sign_SECRETKEYBYTES,
    "sk must be 'crypto_sign_SECRETKEYBYTES' bytes"
  )

  const res = binding.crypto_sign_ed25519_sk_to_pk(pk, sk)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_sign_ed25519_pk_to_curve25519 = function (x25519pk, ed25519pk) {
  assert(ArrayBuffer.isView(x25519pk), 'x25519pk must be a typed array')
  assert(ArrayBuffer.isView(ed25519pk), 'ed25519pk must be a typed array')
  assert(
    x25519pk.byteLength === binding.crypto_box_PUBLICKEYBYTES,
    "x25519pk must be 'crypto_box_PUBLICKEYBYTES' bytes"
  )
  assert(
    ed25519pk.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
    "ed25519pk must be 'crypto_sign_PUBLICKEYBYTES' bytes"
  )

  const res = binding.crypto_sign_ed25519_pk_to_curve25519(x25519pk, ed25519pk)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_sign_ed25519_sk_to_curve25519 = function (x25519sk, ed25519sk) {
  assert(ArrayBuffer.isView(x25519sk), 'x25519sk must be a typed array')
  assert(ArrayBuffer.isView(ed25519sk), 'ed25519sk must be a typed array')
  assert(
    x25519sk.byteLength === binding.crypto_box_SECRETKEYBYTES,
    "x25519sk must be 'crypto_box_SECRETKEYBYTES' bytes"
  )

  const edLen = ed25519sk.byteLength

  assert(
    edLen === binding.crypto_sign_SECRETKEYBYTES || edLen === binding.crypto_box_SECRETKEYBYTES,
    "ed25519sk must be 'crypto_sign_SECRETKEYBYTES' or 'crypto_sign_SECRETKEYBYTES - crypto_sign_PUBLICKEYBYTES' bytes"
  )

  const res = binding.crypto_sign_ed25519_sk_to_curve25519(x25519sk, ed25519sk)

  if (res !== 0) throw new Error('status: ' + res)
}

// crypto_box

exports.crypto_box_keypair = function (pk, sk) {
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(
    pk.byteLength === binding.crypto_box_PUBLICKEYBYTES,
    "pk must be 'crypto_box_PUBLICKEYBYTES' bytes"
  )
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(
    sk.byteLength === binding.crypto_box_SECRETKEYBYTES,
    "sk must be 'crypto_box_SECRETKEYBYTES' bytes"
  )

  const res = binding.crypto_box_keypair(pk, sk)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_box_seed_keypair = function (pk, sk, seed) {
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(
    pk.byteLength === binding.crypto_box_PUBLICKEYBYTES,
    "pk must be 'crypto_box_PUBLICKEYBYTES' bytes"
  )
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(
    sk.byteLength === binding.crypto_box_SECRETKEYBYTES,
    "sk must be 'crypto_box_SECRETKEYBYTES' bytes"
  )
  assert(ArrayBuffer.isView(seed), 'seed must be a typed array')
  assert(
    seed.byteLength === binding.crypto_box_SEEDBYTES,
    "seed must be 'crypto_box_SEEDBYTES' bytes"
  )

  const res = binding.crypto_box_seed_keypair(pk, sk, seed)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_box_easy = function (c, m, n, pk, sk) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(
    c.byteLength === m.byteLength + exports.crypto_box_MACBYTES,
    "c must be 'm.byteLength + crypto_box_MACBYTES' bytes"
  )
  assert(n.byteLength === exports.crypto_box_NONCEBYTES, "n must be 'crypto_box_NONCEBYTES' bytes")
  assert(
    pk.byteLength === exports.crypto_box_PUBLICKEYBYTES,
    "pk must be 'crypto_box_PUBLICKEYBYTES' bytes"
  )
  assert(
    sk.byteLength === exports.crypto_box_SECRETKEYBYTES,
    "sk must be 'crypto_box_SECRETKEYBYTES' bytes"
  )

  const res = binding.crypto_box_easy(c, m, n, pk, sk)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_box_detached = function (c, mac, m, n, pk, sk) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(mac), 'mac must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(mac.byteLength === exports.crypto_box_MACBYTES, "mac must be 'crypto_box_MACBYTES' bytes")
  assert(n.byteLength === exports.crypto_box_NONCEBYTES, "n must be 'crypto_box_NONCEBYTES' bytes")
  assert(
    pk.byteLength === exports.crypto_box_PUBLICKEYBYTES,
    "pk must be 'crypto_box_PUBLICKEYBYTES' bytes"
  )
  assert(
    sk.byteLength === exports.crypto_box_SECRETKEYBYTES,
    "sk must be 'crypto_box_SECRETKEYBYTES' bytes"
  )

  const res = binding.crypto_box_detached(c, mac, m, n, pk, sk)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_box_open_easy = function (m, c, n, pk, sk) {
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(
    c.byteLength >= exports.crypto_box_MACBYTES,
    "c must be at least 'crypto_box_MACBYTES' bytes"
  )
  assert(
    m.byteLength === c.byteLength - exports.crypto_box_MACBYTES,
    "m must be 'c.byteLength - crypto_box_MACBYTES' bytes"
  )
  assert(n.byteLength === exports.crypto_box_NONCEBYTES, "n must be 'crypto_box_NONCEBYTES' bytes")
  assert(
    pk.byteLength === exports.crypto_box_PUBLICKEYBYTES,
    "pk must be 'crypto_box_PUBLICKEYBYTES' bytes"
  )
  assert(
    sk.byteLength === exports.crypto_box_SECRETKEYBYTES,
    "sk must be 'crypto_box_SECRETKEYBYTES' bytes"
  )

  return binding.crypto_box_open_easy(m, c, n, pk, sk)
}

exports.crypto_box_open_detached = function (m, c, mac, n, pk, sk) {
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(mac), 'mac must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(m.byteLength === c.byteLength, "m must be 'c.byteLength' bytes")
  assert(mac.byteLength === exports.crypto_box_MACBYTES, "mac must be 'crypto_box_MACBYTES' bytes")
  assert(n.byteLength === exports.crypto_box_NONCEBYTES, "n must be 'crypto_box_NONCEBYTES' bytes")
  assert(
    pk.byteLength === exports.crypto_box_PUBLICKEYBYTES,
    "pk must be 'crypto_box_PUBLICKEYBYTES' bytes"
  )
  assert(
    sk.byteLength === exports.crypto_box_SECRETKEYBYTES,
    "sk must be 'crypto_box_SECRETKEYBYTES' bytes"
  )

  return binding.crypto_box_open_detached(m, c, mac, n, pk, sk)
}

exports.crypto_box_seal = function (c, m, pk) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(
    c.byteLength === m.byteLength + exports.crypto_box_SEALBYTES,
    "c must be 'm.byteLength + crypto_box_SEALBYTES' bytes"
  )
  assert(
    pk.byteLength === exports.crypto_box_PUBLICKEYBYTES,
    "pk must be 'crypto_box_PUBLICKEYBYTES' bytes"
  )

  const res = binding.crypto_box_seal(c, m, pk)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_box_seal_open = function (m, c, pk, sk) {
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(
    c.byteLength >= exports.crypto_box_SEALBYTES,
    "c must be at least 'crypto_box_SEALBYTES' bytes"
  )
  assert(
    m.byteLength === c.byteLength - exports.crypto_box_SEALBYTES,
    "m must be 'c.byteLength - crypto_box_SEALBYTES' bytes"
  )
  assert(
    pk.byteLength === exports.crypto_box_PUBLICKEYBYTES,
    "pk must be 'crypto_box_PUBLICKEYBYTES' bytes"
  )
  assert(
    sk.byteLength === exports.crypto_box_SECRETKEYBYTES,
    "sk must be 'crypto_box_SECRETKEYBYTES' bytes"
  )

  return binding.crypto_box_seal_open(
    m.buffer,
    m.byteOffset,
    m.byteLength,

    c.buffer,
    c.byteOffset,
    c.byteLength,

    pk.buffer,
    pk.byteOffset,
    pk.byteLength,

    sk.buffer,
    sk.byteOffset,
    sk.byteLength
  )
}

// crypto_secretbox

exports.crypto_secretbox_easy = function (c, m, n, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    c.byteLength === m.byteLength + binding.crypto_secretbox_MACBYTES,
    "c must be 'm.byteLength + crypto_secretbox_MACBYTES' bytes"
  )
  assert(
    n.byteLength === binding.crypto_secretbox_NONCEBYTES,
    "n must be 'crypto_secretbox_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_secretbox_KEYBYTES,
    "k must be 'crypto_secretbox_KEYBYTES' bytes"
  )

  const res = binding.crypto_secretbox_easy(c, m, n, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_secretbox_open_easy = function (m, c, n, k) {
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    c.byteLength >= binding.crypto_secretbox_MACBYTES,
    "c must be at least 'crypto_secretbox_MACBYTES' bytes"
  )
  assert(
    m.byteLength === c.byteLength - binding.crypto_secretbox_MACBYTES,
    "m must be 'c.byteLength - crypto_secretbox_MACBYTES' bytes"
  )
  assert(
    n.byteLength === binding.crypto_secretbox_NONCEBYTES,
    "n must be 'crypto_secretbox_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_secretbox_KEYBYTES,
    "k must be 'crypto_secretbox_KEYBYTES' bytes"
  )

  return binding.crypto_secretbox_open_easy(m, c, n, k)
}

exports.crypto_secretbox_detached = function (c, mac, m, n, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(mac), 'mac must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(
    mac.byteLength === binding.crypto_secretbox_MACBYTES,
    "mac must be 'crypto_secretbox_MACBYTES' bytes"
  )
  assert(
    n.byteLength === binding.crypto_secretbox_NONCEBYTES,
    "n must be 'crypto_secretbox_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_secretbox_KEYBYTES,
    "k must be 'crypto_secretbox_KEYBYTES' bytes"
  )

  const res = binding.crypto_secretbox_detached(c, mac, m, n, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_secretbox_open_detached = function (m, c, mac, n, k) {
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(mac), 'mac must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(m.byteLength === c.byteLength, "m must be 'c.byteLength' bytes")
  assert(
    mac.byteLength === binding.crypto_secretbox_MACBYTES,
    "mac must be 'crypto_secretbox_MACBYTES' bytes"
  )
  assert(
    n.byteLength === binding.crypto_secretbox_NONCEBYTES,
    "n must be 'crypto_secretbox_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_secretbox_KEYBYTES,
    "k must be 'crypto_secretbox_KEYBYTES' bytes"
  )

  return binding.crypto_secretbox_open_detached(m, c, mac, n, k)
}

// crypto_generichash

exports.crypto_generichash = function (output, input, key) {
  if (!key) key = OPTIONAL

  assert(ArrayBuffer.isView(output), 'output must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(
    output.byteLength >= binding.crypto_generichash_BYTES_MIN &&
      output.byteLength <= binding.crypto_generichash_BYTES_MAX,
    'output must be between crypto_generichash_BYTES_MIN and crypto_generichash_BYTES_MAX bytes'
  )

  if (key !== OPTIONAL) {
    assert(ArrayBuffer.isView(key), 'key must be a typed array')
    assert(
      key.byteLength >= binding.crypto_generichash_KEYBYTES_MIN &&
        key.byteLength <= binding.crypto_generichash_KEYBYTES_MAX,
      'key must be between crypto_generichash_KEYBYTES_MIN and crypto_generichash_KEYBYTES_MAX bytes'
    )
  }

  const res = binding.crypto_generichash(
    output.buffer,
    output.byteOffset,
    output.byteLength,

    input.buffer,
    input.byteOffset,
    input.byteLength,

    key.buffer,
    key.byteOffset,
    key.byteLength
  )

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_generichash_batch = function (output, batch, key) {
  assert(ArrayBuffer.isView(output), 'output must be a typed array')

  if (isNode || batch.length < 4) {
    const res = binding.crypto_generichash_batch(output, batch, !!key, key || OPTIONAL)
    if (res !== 0) throw new Error('status: ' + res)
  } else {
    const state = Buffer.alloc(binding.crypto_generichash_STATEBYTES)

    exports.crypto_generichash_init(state, key, output.byteLength)

    for (const buf of batch) {
      exports.crypto_generichash_update(state, buf)
    }

    exports.crypto_generichash_final(state, output)
  }
}

exports.crypto_generichash_keygen = function (key) {
  assert(ArrayBuffer.isView(key), 'key must be a typed array')
  assert(
    key.byteLength === binding.crypto_generichash_KEYBYTES,
    "key must be 'crypto_generichash_KEYBYTES' bytes"
  )

  const res = binding.crypto_generichash_keygen(key.buffer, key.byteOffset, key.byteLength)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_generichash_init = function (state, key, outputLength) {
  if (!key) key = OPTIONAL

  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_generichash_STATEBYTES,
    "state must be 'crypto_generichash_STATEBYTES' bytes"
  )

  const res = binding.crypto_generichash_init(
    state.buffer,
    state.byteOffset,
    state.byteLength,

    key.buffer,
    key.byteOffset,
    key.byteLength,

    outputLength
  )

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_generichash_update = function (state, input) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(
    state.byteLength === binding.crypto_generichash_STATEBYTES,
    "state must be 'crypto_generichash_STATEBYTES' bytes"
  )

  const res = binding.crypto_generichash_update(
    state.buffer,
    state.byteOffset,
    state.byteLength,

    input.buffer,
    input.byteOffset,
    input.byteLength
  )

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_generichash_final = function (state, output) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(ArrayBuffer.isView(output), 'output must be a typed array')
  assert(
    state.byteLength === binding.crypto_generichash_STATEBYTES,
    "state must be 'crypto_generichash_STATEBYTES' bytes"
  )

  const res = binding.crypto_generichash_final(
    state.buffer,
    state.byteOffset,
    state.byteLength,

    output.buffer,
    output.byteOffset,
    output.byteLength
  )

  if (res !== 0) throw new Error('status: ' + res)
}

// secretstream

exports.crypto_secretstream_xchacha20poly1305_keygen = function (k) {
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_secretstream_xchacha20poly1305_KEYBYTES,
    "k must be 'crypto_secretstream_xchacha20poly1305_KEYBYTES' bytes"
  )

  binding.crypto_secretstream_xchacha20poly1305_keygen(k.buffer, k.byteOffset, k.byteLength)
}

exports.crypto_secretstream_xchacha20poly1305_init_push = function (state, header, k) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(ArrayBuffer.isView(header), 'header must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    state.byteLength === binding.crypto_secretstream_xchacha20poly1305_STATEBYTES,
    "state must be 'crypto_secretstream_xchacha20poly1305_STATEBYTES' bytes"
  )
  assert(
    header.byteLength === binding.crypto_secretstream_xchacha20poly1305_HEADERBYTES,
    "header must be 'crypto_secretstream_xchacha20poly1305_HEADERBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_secretstream_xchacha20poly1305_KEYBYTES,
    "k must be 'crypto_secretstream_xchacha20poly1305_KEYBYTES' bytes"
  )

  const res = binding.crypto_secretstream_xchacha20poly1305_init_push(
    state.buffer,
    state.byteOffset,
    state.byteLength,

    header.buffer,
    header.byteOffset,
    header.byteLength,

    k.buffer,
    k.byteOffset,
    k.byteLength
  )

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_secretstream_xchacha20poly1305_init_pull = function (state, header, k) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(ArrayBuffer.isView(header), 'header must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    state.byteLength === binding.crypto_secretstream_xchacha20poly1305_STATEBYTES,
    "state must be 'crypto_secretstream_xchacha20poly1305_STATEBYTES' bytes"
  )
  assert(
    header.byteLength === binding.crypto_secretstream_xchacha20poly1305_HEADERBYTES,
    "header must be 'crypto_secretstream_xchacha20poly1305_HEADERBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_secretstream_xchacha20poly1305_KEYBYTES,
    "k must be 'crypto_secretstream_xchacha20poly1305_KEYBYTES' bytes"
  )

  const res = binding.crypto_secretstream_xchacha20poly1305_init_pull(
    state.buffer,
    state.byteOffset,
    state.byteLength,

    header.buffer,
    header.byteOffset,
    header.byteLength,

    k.buffer,
    k.byteOffset,
    k.byteLength
  )

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_secretstream_xchacha20poly1305_push = function (state, c, m, ad, tag) {
  if (!ad) ad = OPTIONAL

  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(
    state.byteLength === binding.crypto_secretstream_xchacha20poly1305_STATEBYTES,
    "state must be 'crypto_secretstream_xchacha20poly1305_STATEBYTES' bytes"
  )
  assert(
    c.byteLength === m.byteLength + binding.crypto_secretstream_xchacha20poly1305_ABYTES,
    "c must be 'm.byteLength + crypto_secretstream_xchacha20poly1305_ABYTES' bytes"
  )

  const res = binding.crypto_secretstream_xchacha20poly1305_push(
    state.buffer,
    state.byteOffset,
    state.byteLength,

    c.buffer,
    c.byteOffset,
    c.byteLength,

    m.buffer,
    m.byteOffset,
    m.byteLength,

    ad.buffer,
    ad.byteOffset,
    ad.byteLength,

    tag
  )

  if (res < 0) throw new Error('push failed')

  return res
}

exports.crypto_secretstream_xchacha20poly1305_pull = function (state, m, tag, c, ad) {
  if (!ad) ad = OPTIONAL

  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_secretstream_xchacha20poly1305_STATEBYTES,
    "state must be 'crypto_secretstream_xchacha20poly1305_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(tag), 'tag must be a typed array')
  assert(tag.byteLength === 1, 'tag must be 1 byte')

  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(
    c.byteLength >= binding.crypto_secretstream_xchacha20poly1305_ABYTES,
    "c must be at least 'crypto_secretstream_xchacha20poly1305_ABYTES' bytes"
  )
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(
    m.byteLength === c.byteLength - binding.crypto_secretstream_xchacha20poly1305_ABYTES,
    "m must be 'c.byteLength - crypto_secretstream_xchacha20poly1305_ABYTES' bytes"
  )

  const res = binding.crypto_secretstream_xchacha20poly1305_pull(
    state.buffer,
    state.byteOffset,
    state.byteLength,

    m.buffer,
    m.byteOffset,
    m.byteLength,

    tag.buffer,
    tag.byteOffset,
    tag.byteLength,

    c.buffer,
    c.byteOffset,
    c.byteLength,

    ad.buffer,
    ad.byteOffset,
    ad.byteLength
  )

  if (res < 0) throw new Error('pull failed')

  return res
}

exports.crypto_secretstream_xchacha20poly1305_rekey = function (state) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_secretstream_xchacha20poly1305_STATEBYTES,
    "state must be 'crypto_secretstream_xchacha20poly1305_STATEBYTES' bytes"
  )

  binding.crypto_secretstream_xchacha20poly1305_rekey(
    state.buffer,
    state.byteOffset,
    state.byteLength
  )
}

// crypto_stream

exports.crypto_stream = function (c, n, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    n.byteLength === binding.crypto_stream_NONCEBYTES,
    "n must be 'crypto_stream_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_KEYBYTES,
    "k must be 'crypto_stream_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream(c, n, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_xor = function (c, m, n, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(
    n.byteLength === binding.crypto_stream_NONCEBYTES,
    "n must be 'crypto_stream_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_KEYBYTES,
    "k must be 'crypto_stream_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_xor(
    c.buffer,
    c.byteOffset,
    c.byteLength,

    m.buffer,
    m.byteOffset,
    m.byteLength,

    n.buffer,
    n.byteOffset,
    n.byteLength,

    k.buffer,
    k.byteOffset,
    k.byteLength
  )

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_chacha20 = function (c, n, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    n.byteLength === binding.crypto_stream_chacha20_NONCEBYTES,
    "n must be 'crypto_stream_chacha20_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_chacha20_KEYBYTES,
    "k must be 'crypto_stream_chacha20_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_chacha20(c, n, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_chacha20_xor = function (c, m, n, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(
    n.byteLength === binding.crypto_stream_chacha20_NONCEBYTES,
    "n must be 'crypto_stream_chacha20_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_chacha20_KEYBYTES,
    "k must be 'crypto_stream_chacha20_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_chacha20_xor(c, m, n, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_chacha20_xor_ic = function (c, m, n, ic, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(
    n.byteLength === binding.crypto_stream_chacha20_NONCEBYTES,
    "n must be 'crypto_stream_chacha20_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_chacha20_KEYBYTES,
    "k must be 'crypto_stream_chacha20_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_chacha20_xor_ic(c, m, n, ic, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_chacha20_ietf = function (c, n, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    n.byteLength === binding.crypto_stream_chacha20_ietf_NONCEBYTES,
    "n must be 'crypto_stream_chacha20_ietf_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_chacha20_ietf_KEYBYTES,
    "k must be 'crypto_stream_chacha20_ietf_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_chacha20_ietf(c, n, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_chacha20_ietf_xor = function (c, m, n, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(
    n.byteLength === binding.crypto_stream_chacha20_ietf_NONCEBYTES,
    "n must be 'crypto_stream_chacha20_ietf_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_chacha20_ietf_KEYBYTES,
    "k must be 'crypto_stream_chacha20_ietf_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_chacha20_ietf_xor(c, m, n, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_chacha20_ietf_xor_ic = function (c, m, n, ic, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(
    n.byteLength === binding.crypto_stream_chacha20_ietf_NONCEBYTES,
    "n must be 'crypto_stream_chacha20_ietf_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_chacha20_ietf_KEYBYTES,
    "k must be 'crypto_stream_chacha20_ietf_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_chacha20_ietf_xor_ic(c, m, n, ic, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_xchacha20 = function (c, n, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    n.byteLength === binding.crypto_stream_xchacha20_NONCEBYTES,
    "n must be 'crypto_stream_xchacha20_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_xchacha20_KEYBYTES,
    "k must be 'crypto_stream_xchacha20_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_xchacha20(c, n, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_xchacha20_xor = function (c, m, n, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(
    n.byteLength === binding.crypto_stream_xchacha20_NONCEBYTES,
    "n must be 'crypto_stream_xchacha20_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_xchacha20_KEYBYTES,
    "k must be 'crypto_stream_xchacha20_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_xchacha20_xor(c, m, n, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_xchacha20_xor_ic = function (c, m, n, ic, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(
    n.byteLength === binding.crypto_stream_xchacha20_NONCEBYTES,
    "n must be 'crypto_stream_xchacha20_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_xchacha20_KEYBYTES,
    "k must be 'crypto_stream_xchacha20_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_xchacha20_xor_ic(c, m, n, ic, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_salsa20 = function (c, n, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    n.byteLength === binding.crypto_stream_salsa20_NONCEBYTES,
    "n must be 'crypto_stream_salsa20_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_salsa20_KEYBYTES,
    "k must be 'crypto_stream_salsa20_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_salsa20(c, n, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_salsa20_xor = function (c, m, n, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(
    n.byteLength === binding.crypto_stream_salsa20_NONCEBYTES,
    "n must be 'crypto_stream_salsa20_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_salsa20_KEYBYTES,
    "k must be 'crypto_stream_salsa20_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_salsa20_xor(c, m, n, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_salsa20_xor_ic = function (c, m, n, ic, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(
    n.byteLength === binding.crypto_stream_salsa20_NONCEBYTES,
    "n must be 'crypto_stream_salsa20_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_salsa20_KEYBYTES,
    "k must be 'crypto_stream_salsa20_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_salsa20_xor_ic(c, m, n, ic, k)

  if (res !== 0) throw new Error('status: ' + res)
}

// crypto_auth

exports.crypto_auth = function (out, input, k) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(out.byteLength === binding.crypto_auth_BYTES, "out must be 'crypto_auth_BYTES' bytes")
  assert(k.byteLength === binding.crypto_auth_KEYBYTES, "k must be 'crypto_auth_KEYBYTES' bytes")

  const res = binding.crypto_auth(out, input, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_auth_verify = function (h, input, k) {
  assert(ArrayBuffer.isView(h), 'h must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(h.byteLength === binding.crypto_auth_BYTES, "h must be 'crypto_auth_BYTES' bytes")
  assert(k.byteLength === binding.crypto_auth_KEYBYTES, "k must be 'crypto_auth_KEYBYTES' bytes")

  return binding.crypto_auth_verify(h, input, k)
}

// crypto_onetimeauth

exports.crypto_onetimeauth = function (out, input, k) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    out.byteLength === binding.crypto_onetimeauth_BYTES,
    "out must be 'crypto_onetimeauth_BYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_onetimeauth_KEYBYTES,
    "k must be 'crypto_onetimeauth_KEYBYTES' bytes"
  )

  const res = binding.crypto_onetimeauth(out, input, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_onetimeauth_init = function (state, k) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    state.byteLength === binding.crypto_onetimeauth_STATEBYTES,
    "state must be 'crypto_onetimeauth_STATEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_onetimeauth_KEYBYTES,
    "k must be 'crypto_onetimeauth_KEYBYTES' bytes"
  )

  const res = binding.crypto_onetimeauth_init(state, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_onetimeauth_update = function (state, input) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(
    state.byteLength === binding.crypto_onetimeauth_STATEBYTES,
    "state must be 'crypto_onetimeauth_STATEBYTES' bytes"
  )

  const res = binding.crypto_onetimeauth_update(state, input)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_onetimeauth_final = function (state, out) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(
    state.byteLength === binding.crypto_onetimeauth_STATEBYTES,
    "state must be 'crypto_onetimeauth_STATEBYTES' bytes"
  )
  assert(
    out.byteLength === binding.crypto_onetimeauth_BYTES,
    "out must be 'crypto_onetimeauth_BYTES' bytes"
  )

  const res = binding.crypto_onetimeauth_final(state, out)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_onetimeauth_verify = function (h, input, k) {
  assert(ArrayBuffer.isView(h), 'h must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    h.byteLength === binding.crypto_onetimeauth_BYTES,
    "h must be 'crypto_onetimeauth_BYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_onetimeauth_KEYBYTES,
    "k must be 'crypto_onetimeauth_KEYBYTES' bytes"
  )

  return binding.crypto_onetimeauth_verify(h, input, k)
}

// crypto_pwhash

exports.crypto_pwhash = function (out, passwd, salt, opslimit, memlimit, alg) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(ArrayBuffer.isView(salt), 'salt must be a typed array')
  assert(
    out.byteLength >= binding.crypto_pwhash_BYTES_MIN,
    "out must be at least 'crypto_pwhash_BYTES_MIN' bytes"
  )
  assert(
    out.byteLength <= binding.crypto_pwhash_BYTES_MAX,
    "out must be at most 'crypto_pwhash_BYTES_MAX' bytes"
  )
  assert(
    salt.byteLength === binding.crypto_pwhash_SALTBYTES,
    "salt must be 'crypto_pwhash_SALTBYTES' bytes"
  )
  assert(
    opslimit >= binding.crypto_pwhash_OPSLIMIT_MIN,
    "opslimit must be at least 'crypto_pwhash_OPSLIMIT_MIN'"
  )
  assert(
    opslimit <= binding.crypto_pwhash_OPSLIMIT_MAX,
    "opslimit must be at most 'crypto_pwhash_OPSLIMIT_MAX'"
  )
  assert(
    memlimit >= binding.crypto_pwhash_MEMLIMIT_MIN,
    "memlimit must be at least 'crypto_pwhash_MEMLIMIT_MIN'"
  )
  assert(
    memlimit <= binding.crypto_pwhash_MEMLIMIT_MAX,
    "memlimit must be at most 'crypto_pwhash_MEMLIMIT_MAX'"
  )
  assert(alg >= 1 && alg <= 2, 'alg must be either Argon2i 1.3 or Argon2id 1.3')

  const res = binding.crypto_pwhash(out, passwd, salt, opslimit, memlimit, alg)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_pwhash_async = function (
  out,
  passwd,
  salt,
  opslimit,
  memlimit,
  alg,
  callback = undefined
) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(ArrayBuffer.isView(salt), 'salt must be a typed array')
  assert(
    out.byteLength >= binding.crypto_pwhash_BYTES_MIN,
    "out must be at least 'crypto_pwhash_BYTES_MIN' bytes"
  )
  assert(
    out.byteLength <= binding.crypto_pwhash_BYTES_MAX,
    "out must be at most 'crypto_pwhash_BYTES_MAX' bytes"
  )
  assert(
    salt.byteLength === binding.crypto_pwhash_SALTBYTES,
    "salt must be 'crypto_pwhash_SALTBYTES' bytes"
  )
  assert(
    opslimit >= binding.crypto_pwhash_OPSLIMIT_MIN,
    "opslimit must be at least 'crypto_pwhash_OPSLIMIT_MIN'"
  )
  assert(
    opslimit <= binding.crypto_pwhash_OPSLIMIT_MAX,
    "opslimit must be at most 'crypto_pwhash_OPSLIMIT_MAX'"
  )
  assert(
    memlimit >= binding.crypto_pwhash_MEMLIMIT_MIN,
    "memlimit must be at least 'crypto_pwhash_MEMLIMIT_MIN'"
  )
  assert(
    memlimit <= binding.crypto_pwhash_MEMLIMIT_MAX,
    "memlimit must be at most 'crypto_pwhash_MEMLIMIT_MAX'"
  )
  assert(alg >= 1 && alg <= 2, 'alg must be either Argon2i 1.3 or Argon2id 1.3')

  const [done, promise] = checkStatus(callback)

  binding.crypto_pwhash_async(
    out.buffer,
    out.byteOffset,
    out.byteLength,

    passwd.buffer,
    passwd.byteOffset,
    passwd.byteLength,

    salt.buffer,
    salt.byteOffset,
    salt.byteLength,

    opslimit,
    memlimit,
    alg,

    done
  )

  return promise
}

exports.crypto_pwhash_str = function (out, passwd, opslimit, memlimit) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(
    out.byteLength === binding.crypto_pwhash_STRBYTES,
    "out must be 'crypto_pwhash_STRBYTES' bytes"
  )
  assert(typeof opslimit === 'number', 'opslimit must be a number')
  assert(
    opslimit >= binding.crypto_pwhash_OPSLIMIT_MIN,
    "opslimit must be at least 'crypto_pwhash_OPSLIMIT_MIN'"
  )
  assert(
    opslimit <= binding.crypto_pwhash_OPSLIMIT_MAX,
    "opslimit must be at most 'crypto_pwhash_OPSLIMIT_MAX'"
  )
  assert(typeof memlimit === 'number', 'memlimit must be a number')
  assert(
    memlimit >= binding.crypto_pwhash_MEMLIMIT_MIN,
    "memlimit must be at least 'crypto_pwhash_MEMLIMIT_MIN'"
  )
  assert(
    memlimit <= binding.crypto_pwhash_MEMLIMIT_MAX,
    "memlimit must be at most 'crypto_pwhash_MEMLIMIT_MAX'"
  )

  const res = binding.crypto_pwhash_str(out, passwd, opslimit, memlimit)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_pwhash_str_async = function (out, passwd, opslimit, memlimit, callback = undefined) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(
    out.byteLength === binding.crypto_pwhash_STRBYTES,
    "out must be 'crypto_pwhash_STRBYTES' bytes"
  )
  assert(passwd.byteLength > 0, 'passwd must not be empty')
  assert(typeof opslimit === 'number', 'opslimit must be a number')
  assert(
    opslimit >= binding.crypto_pwhash_OPSLIMIT_MIN,
    "opslimit must be at least 'crypto_pwhash_OPSLIMIT_MIN'"
  )
  assert(
    opslimit <= binding.crypto_pwhash_OPSLIMIT_MAX,
    "opslimit must be at most 'crypto_pwhash_OPSLIMIT_MAX'"
  )
  assert(typeof memlimit === 'number', 'memlimit must be a number')
  assert(
    memlimit >= binding.crypto_pwhash_MEMLIMIT_MIN,
    "memlimit must be at least 'crypto_pwhash_MEMLIMIT_MIN'"
  )
  assert(
    memlimit <= binding.crypto_pwhash_MEMLIMIT_MAX,
    "memlimit must be at most 'crypto_pwhash_MEMLIMIT_MAX'"
  )

  const [done, promise] = checkStatus(callback)

  binding.crypto_pwhash_str_async(
    out.buffer,
    out.byteOffset,
    out.byteLength,

    passwd.buffer,
    passwd.byteOffset,
    passwd.byteLength,

    opslimit,
    memlimit,

    done
  )

  return promise
}

exports.crypto_pwhash_str_verify = function (str, passwd) {
  assert(ArrayBuffer.isView(str), 'str must be a typed array')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(
    str.byteLength === binding.crypto_pwhash_STRBYTES,
    "str must be 'crypto_pwhash_STRBYTES' bytes"
  )

  return binding.crypto_pwhash_str_verify(str, passwd)
}

exports.crypto_pwhash_str_verify_async = function (str, passwd, callback = undefined) {
  assert(ArrayBuffer.isView(str), 'str must be a typed array')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(
    str.byteLength === binding.crypto_pwhash_STRBYTES,
    "str must be 'crypto_pwhash_STRBYTES' bytes"
  )
  assert(passwd.byteLength > 0, 'passwd must not be empty')

  const [done, promise] = checkStatus(callback, true)

  binding.crypto_pwhash_str_verify_async(
    str.buffer,
    str.byteOffset,
    str.byteLength,

    passwd.buffer,
    passwd.byteOffset,
    passwd.byteLength,

    done
  )

  return promise
}

exports.crypto_pwhash_str_needs_rehash = function (str, opslimit, memlimit) {
  assert(ArrayBuffer.isView(str), 'str must be a typed array')
  assert(
    str.byteLength === binding.crypto_pwhash_STRBYTES,
    "str must be 'crypto_pwhash_STRBYTES' bytes"
  )
  assert(
    opslimit >= binding.crypto_pwhash_OPSLIMIT_MIN,
    "opslimit must be at least 'crypto_pwhash_OPSLIMIT_MIN'"
  )
  assert(
    opslimit <= binding.crypto_pwhash_OPSLIMIT_MAX,
    "opslimit must be at most 'crypto_pwhash_OPSLIMIT_MAX'"
  )
  assert(
    memlimit >= binding.crypto_pwhash_MEMLIMIT_MIN,
    "memlimit must be at least 'crypto_pwhash_MEMLIMIT_MIN'"
  )
  assert(
    memlimit <= binding.crypto_pwhash_MEMLIMIT_MAX,
    "memlimit must be at most 'crypto_pwhash_MEMLIMIT_MAX'"
  )

  return binding.crypto_pwhash_str_needs_rehash(str, opslimit, memlimit)
}

exports.crypto_pwhash_scryptsalsa208sha256 = function (out, passwd, salt, opslimit, memlimit) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(ArrayBuffer.isView(salt), 'salt must be a typed array')
  assert(
    out.byteLength >= binding.crypto_pwhash_scryptsalsa208sha256_BYTES_MIN,
    "out must be at least 'crypto_pwhash_scryptsalsa208sha256_BYTES_MIN' bytes"
  )
  assert(
    out.byteLength <= binding.crypto_pwhash_scryptsalsa208sha256_BYTES_MAX,
    "out must be at most 'crypto_pwhash_scryptsalsa208sha256_BYTES_MAX' bytes"
  )
  assert(
    salt.byteLength === binding.crypto_pwhash_scryptsalsa208sha256_SALTBYTES,
    "salt must be 'crypto_pwhash_scryptsalsa208sha256_SALTBYTES' bytes"
  )
  assert(
    opslimit >= binding.crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MIN,
    "opslimit must be at least 'crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MIN'"
  )
  assert(
    opslimit <= binding.crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MAX,
    "opslimit must be at most 'crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MAX'"
  )
  assert(
    memlimit >= binding.crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MIN,
    "memlimit must be at least 'crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MIN'"
  )
  assert(
    memlimit <= binding.crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MAX,
    "memlimit must be at most 'crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MAX'"
  )

  const res = binding.crypto_pwhash_scryptsalsa208sha256(out, passwd, salt, opslimit, memlimit)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_pwhash_scryptsalsa208sha256_async = function (
  out,
  passwd,
  salt,
  opslimit,
  memlimit,
  callback = undefined
) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(ArrayBuffer.isView(salt), 'salt must be a typed array')
  assert(
    out.byteLength >= binding.crypto_pwhash_scryptsalsa208sha256_BYTES_MIN,
    "out must be at least 'crypto_pwhash_scryptsalsa208sha256_BYTES_MIN' bytes"
  )
  assert(
    out.byteLength <= binding.crypto_pwhash_scryptsalsa208sha256_BYTES_MAX,
    "out must be at most 'crypto_pwhash_scryptsalsa208sha256_BYTES_MAX' bytes"
  )
  assert(passwd.byteLength > 0, 'passwd must not be empty')
  assert(
    salt.byteLength === binding.crypto_pwhash_scryptsalsa208sha256_SALTBYTES,
    "salt must be 'crypto_pwhash_scryptsalsa208sha256_SALTBYTES' bytes"
  )
  assert(
    opslimit >= binding.crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MIN,
    "opslimit must be at least 'crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MIN'"
  )
  assert(
    opslimit <= binding.crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MAX,
    "opslimit must be at most 'crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MAX'"
  )
  assert(
    memlimit >= binding.crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MIN,
    "memlimit must be at least 'crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MIN'"
  )
  assert(
    memlimit <= binding.crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MAX,
    "memlimit must be at most 'crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MAX'"
  )

  const [done, promise] = checkStatus(callback)

  binding.crypto_pwhash_scryptsalsa208sha256_async(
    out.buffer,
    out.byteOffset,
    out.byteLength,

    passwd.buffer,
    passwd.byteOffset,
    passwd.byteLength,

    salt.buffer,
    salt.byteOffset,
    salt.byteLength,

    opslimit,
    memlimit,

    done
  )

  return promise
}

exports.crypto_pwhash_scryptsalsa208sha256_str_async = function (
  out,
  passwd,
  opslimit,
  memlimit,
  callback = undefined
) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(
    out.byteLength === binding.crypto_pwhash_scryptsalsa208sha256_STRBYTES,
    "out must be 'crypto_pwhash_scryptsalsa208sha256_STRBYTES' bytes"
  )
  assert(passwd.byteLength > 0, 'passwd must not be empty')
  assert(
    opslimit >= binding.crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MIN,
    "opslimit must be at least 'crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MIN'"
  )
  assert(
    opslimit <= binding.crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MAX,
    "opslimit must be at most 'crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MAX'"
  )
  assert(
    memlimit >= binding.crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MIN,
    "memlimit must be at least 'crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MIN'"
  )
  assert(
    memlimit <= binding.crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MAX,
    "memlimit must be at most 'crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MAX'"
  )

  const [done, promise] = checkStatus(callback)

  binding.crypto_pwhash_scryptsalsa208sha256_str_async(
    out.buffer,
    out.byteOffset,
    out.byteLength,

    passwd.buffer,
    passwd.byteOffset,
    passwd.byteLength,

    opslimit,
    memlimit,

    done
  )

  return promise
}

exports.crypto_pwhash_scryptsalsa208sha256_str = function (out, passwd, opslimit, memlimit) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(
    out.byteLength === binding.crypto_pwhash_scryptsalsa208sha256_STRBYTES,
    "out must be 'crypto_pwhash_scryptsalsa208sha256_STRBYTES' bytes"
  )
  assert(passwd.byteLength > 0, 'passwd must not be empty')
  assert(
    opslimit >= binding.crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MIN,
    "opslimit must be at least 'crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MIN'"
  )
  assert(
    opslimit <= binding.crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MAX,
    "opslimit must be at most 'crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MAX'"
  )
  assert(
    memlimit >= binding.crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MIN,
    "memlimit must be at least 'crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MIN'"
  )
  assert(
    memlimit <= binding.crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MAX,
    "memlimit must be at most 'crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MAX'"
  )

  const res = binding.crypto_pwhash_scryptsalsa208sha256_str(out, passwd, opslimit, memlimit)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_pwhash_scryptsalsa208sha256_str_verify_async = function (
  str,
  passwd,
  callback = undefined
) {
  assert(ArrayBuffer.isView(str), 'str must be a typed array')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(
    str.byteLength === binding.crypto_pwhash_scryptsalsa208sha256_STRBYTES,
    "str must be 'crypto_pwhash_scryptsalsa208sha256_STRBYTES' bytes"
  )
  assert(passwd.byteLength > 0, 'passwd must not be empty')

  const [done, promise] = checkStatus(callback, true)

  binding.crypto_pwhash_scryptsalsa208sha256_str_verify_async(
    str.buffer,
    str.byteOffset,
    str.byteLength,

    passwd.buffer,
    passwd.byteOffset,
    passwd.byteLength,

    done
  )

  return promise
}

exports.crypto_pwhash_scryptsalsa208sha256_str_verify = function (str, passwd) {
  assert(ArrayBuffer.isView(str), 'str must be a typed array')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(
    str.byteLength === binding.crypto_pwhash_scryptsalsa208sha256_STRBYTES,
    "str must be 'crypto_pwhash_scryptsalsa208sha256_STRBYTES' bytes"
  )
  assert(passwd.byteLength > 0, 'passwd must not be empty')

  return binding.crypto_pwhash_scryptsalsa208sha256_str_verify(str, passwd)
}

exports.crypto_pwhash_scryptsalsa208sha256_str_needs_rehash = function (str, opslimit, memlimit) {
  assert(ArrayBuffer.isView(str), 'str must be a typed array')
  assert(
    str.byteLength === binding.crypto_pwhash_scryptsalsa208sha256_STRBYTES,
    "str must be 'crypto_pwhash_scryptsalsa208sha256_STRBYTES' bytes"
  )
  assert(
    opslimit >= binding.crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MIN,
    "opslimit must be at least 'crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MIN'"
  )
  assert(
    opslimit <= binding.crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MAX,
    "opslimit must be at most 'crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MAX'"
  )
  assert(
    memlimit >= binding.crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MIN,
    "memlimit must be at least 'crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MIN'"
  )
  assert(
    memlimit <= binding.crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MAX,
    "memlimit must be at most 'crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MAX'"
  )

  return binding.crypto_pwhash_scryptsalsa208sha256_str_needs_rehash(str, opslimit, memlimit)
}

// crypto_kx

exports.crypto_kx_keypair = function (pk, sk) {
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(
    pk.byteLength === binding.crypto_kx_PUBLICKEYBYTES,
    "pk must be 'crypto_kx_PUBLICKEYBYTES' bytes"
  )
  assert(
    sk.byteLength === binding.crypto_kx_SECRETKEYBYTES,
    "sk must be 'crypto_kx_SECRETKEYBYTES' bytes"
  )

  const res = binding.crypto_kx_keypair(pk, sk)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_kx_seed_keypair = function (pk, sk, seed) {
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(ArrayBuffer.isView(seed), 'seed must be a typed array')
  assert(
    pk.byteLength === binding.crypto_kx_PUBLICKEYBYTES,
    "pk must be 'crypto_kx_PUBLICKEYBYTES' bytes"
  )
  assert(
    sk.byteLength === binding.crypto_kx_SECRETKEYBYTES,
    "sk must be 'crypto_kx_SECRETKEYBYTES' bytes"
  )
  assert(
    seed.byteLength === binding.crypto_kx_SEEDBYTES,
    "seed must be 'crypto_kx_SEEDBYTES' bytes"
  )

  const res = binding.crypto_kx_seed_keypair(pk, sk, seed)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_kx_client_session_keys = function (rx, tx, clientPk, clientSk, serverPk) {
  if (!rx) rx = undefined
  if (!tx) tx = undefined

  assert(rx || tx, 'at least one session key must be specified')

  if (rx) {
    assert(ArrayBuffer.isView(rx), 'rx must be a typed array')
    assert(
      rx.byteLength === binding.crypto_kx_SESSIONKEYBYTES,
      "rx must be 'crypto_kx_SESSIONKEYBYTES' bytes"
    )
  }

  if (tx) {
    assert(ArrayBuffer.isView(tx), 'tx must be a typed array')
    assert(
      tx.byteLength === binding.crypto_kx_SESSIONKEYBYTES,
      "tx must be 'crypto_kx_SESSIONKEYBYTES' bytes"
    )
  }

  assert(ArrayBuffer.isView(clientPk), 'clientPk must be a typed array')
  assert(ArrayBuffer.isView(clientSk), 'clientSk must be a typed array')
  assert(ArrayBuffer.isView(serverPk), 'serverPk must be a typed array')
  assert(
    clientPk.byteLength === binding.crypto_kx_PUBLICKEYBYTES,
    "clientPk must be 'crypto_kx_PUBLICKEYBYTES' bytes"
  )
  assert(
    clientSk.byteLength === binding.crypto_kx_SECRETKEYBYTES,
    "clientSk must be 'crypto_kx_SECRETKEYBYTES' bytes"
  )
  assert(
    serverPk.byteLength === binding.crypto_kx_PUBLICKEYBYTES,
    "serverPk must be 'crypto_kx_PUBLICKEYBYTES' bytes"
  )

  const res = binding.crypto_kx_client_session_keys(rx, tx, clientPk, clientSk, serverPk)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_kx_server_session_keys = function (rx, tx, serverPk, serverSk, clientPk) {
  if (!rx) rx = undefined
  if (!tx) tx = undefined

  assert(rx || tx, 'at least one session key must be specified')

  if (rx) {
    assert(ArrayBuffer.isView(rx), 'rx must be a typed array')
    assert(
      rx.byteLength === binding.crypto_kx_SESSIONKEYBYTES,
      "rx must be 'crypto_kx_SESSIONKEYBYTES' bytes"
    )
  }

  if (tx) {
    assert(ArrayBuffer.isView(tx), 'tx must be a typed array')
    assert(
      tx.byteLength === binding.crypto_kx_SESSIONKEYBYTES,
      "tx must be 'crypto_kx_SESSIONKEYBYTES' bytes"
    )
  }

  assert(ArrayBuffer.isView(serverPk), 'serverPk must be a typed array')
  assert(ArrayBuffer.isView(serverSk), 'serverSk must be a typed array')
  assert(ArrayBuffer.isView(clientPk), 'clientPk must be a typed array')
  assert(
    serverPk.byteLength === binding.crypto_kx_PUBLICKEYBYTES,
    "serverPk must be 'crypto_kx_PUBLICKEYBYTES' bytes"
  )
  assert(
    serverSk.byteLength === binding.crypto_kx_SECRETKEYBYTES,
    "serverSk must be 'crypto_kx_SECRETKEYBYTES' bytes"
  )
  assert(
    clientPk.byteLength === binding.crypto_kx_PUBLICKEYBYTES,
    "clientPk must be 'crypto_kx_PUBLICKEYBYTES' bytes"
  )

  const res = binding.crypto_kx_server_session_keys(rx, tx, serverPk, serverSk, clientPk)

  if (res !== 0) throw new Error('status: ' + res)
}

// crypto_scalarmult

exports.crypto_scalarmult_base = function (q, n) {
  assert(ArrayBuffer.isView(q), 'q must be a typed array')
  assert(
    q.byteLength === binding.crypto_scalarmult_BYTES,
    "q must be 'crypto_scalarmult_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.crypto_scalarmult_SCALARBYTES,
    "n must be 'crypto_scalarmult_SCALARBYTES' bytes"
  )

  const res = binding.crypto_scalarmult_base(q, n)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_scalarmult = function (q, n, p) {
  assert(ArrayBuffer.isView(q), 'q must be a typed array')
  assert(
    q.byteLength === binding.crypto_scalarmult_BYTES,
    "q must be 'crypto_scalarmult_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.crypto_scalarmult_SCALARBYTES,
    "n must be 'crypto_scalarmult_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(p), 'p must be a typed array')
  assert(
    p.byteLength === binding.crypto_scalarmult_BYTES,
    "p must be 'crypto_scalarmult_BYTES' bytes"
  )

  const res = binding.crypto_scalarmult(q, n, p)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_scalarmult_ed25519_base = function (q, n) {
  assert(ArrayBuffer.isView(q), 'q must be a typed array')
  assert(
    q.byteLength === binding.crypto_scalarmult_ed25519_BYTES,
    "q must be 'crypto_scalarmult_ed25519_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.crypto_scalarmult_ed25519_SCALARBYTES,
    "n must be 'crypto_scalarmult_ed25519_SCALARBYTES' bytes"
  )

  const res = binding.crypto_scalarmult_ed25519_base(q, n)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_scalarmult_ed25519 = function (q, n, p) {
  assert(ArrayBuffer.isView(q), 'q must be a typed array')
  assert(
    q.byteLength === binding.crypto_scalarmult_ed25519_BYTES,
    "q must be 'crypto_scalarmult_ed25519_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.crypto_scalarmult_ed25519_SCALARBYTES,
    "n must be 'crypto_scalarmult_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(p), 'p must be a typed array')
  assert(
    p.byteLength === binding.crypto_scalarmult_ed25519_BYTES,
    "p must be 'crypto_scalarmult_ed25519_BYTES' bytes"
  )

  const res = binding.crypto_scalarmult_ed25519(q, n, p)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_core_ed25519_is_valid_point = function (p) {
  assert(ArrayBuffer.isView(p), 'p must be a typed array')
  assert(
    p.byteLength === binding.crypto_core_ed25519_BYTES,
    "p must be 'crypto_core_ed25519_BYTES' bytes"
  )

  return binding.crypto_core_ed25519_is_valid_point(p)
}

exports.crypto_core_ed25519_from_uniform = function (p, r) {
  assert(ArrayBuffer.isView(p), 'p must be a typed array')
  assert(
    p.byteLength === binding.crypto_core_ed25519_BYTES,
    "p must be 'crypto_core_ed25519_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(r), 'r must be a typed array')
  assert(
    r.byteLength === binding.crypto_core_ed25519_UNIFORMBYTES,
    "r must be 'crypto_core_ed25519_UNIFORMBYTES' bytes"
  )

  const res = binding.crypto_core_ed25519_from_uniform(p, r)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_scalarmult_ed25519_base_noclamp = function (q, n) {
  assert(ArrayBuffer.isView(q), 'q must be a typed array')
  assert(
    q.byteLength === binding.crypto_scalarmult_ed25519_BYTES,
    "q must be 'crypto_scalarmult_ed25519_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.crypto_scalarmult_ed25519_SCALARBYTES,
    "n must be 'crypto_scalarmult_ed25519_SCALARBYTES' bytes"
  )

  const res = binding.crypto_scalarmult_ed25519_base_noclamp(q, n)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_scalarmult_ed25519_noclamp = function (q, n, p) {
  assert(ArrayBuffer.isView(q), 'q must be a typed array')
  assert(
    q.byteLength === binding.crypto_scalarmult_ed25519_BYTES,
    "q must be 'crypto_scalarmult_ed25519_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.crypto_scalarmult_ed25519_SCALARBYTES,
    "n must be 'crypto_scalarmult_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(p), 'p must be a typed array')
  assert(
    p.byteLength === binding.crypto_scalarmult_ed25519_BYTES,
    "p must be 'crypto_scalarmult_ed25519_BYTES' bytes"
  )

  const res = binding.crypto_scalarmult_ed25519_noclamp(q, n, p)

  if (res !== 0) throw new Error('status: ' + res)
}

// crypto_core

exports.crypto_core_ed25519_add = function (r, p, q) {
  assert(ArrayBuffer.isView(r), 'r must be a typed array')
  assert(
    r.byteLength === binding.crypto_core_ed25519_BYTES,
    "r must be 'crypto_core_ed25519_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(p), 'p must be a typed array')
  assert(
    p.byteLength === binding.crypto_core_ed25519_BYTES,
    "p must be 'crypto_core_ed25519_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(q), 'q must be a typed array')
  assert(
    q.byteLength === binding.crypto_core_ed25519_BYTES,
    "q must be 'crypto_core_ed25519_BYTES' bytes"
  )

  const res = binding.crypto_core_ed25519_add(r, p, q)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_core_ed25519_sub = function (r, p, q) {
  assert(ArrayBuffer.isView(r), 'r must be a typed array')
  assert(
    r.byteLength === binding.crypto_core_ed25519_BYTES,
    "r must be 'crypto_core_ed25519_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(p), 'p must be a typed array')
  assert(
    p.byteLength === binding.crypto_core_ed25519_BYTES,
    "p must be 'crypto_core_ed25519_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(q), 'q must be a typed array')
  assert(
    q.byteLength === binding.crypto_core_ed25519_BYTES,
    "q must be 'crypto_core_ed25519_BYTES' bytes"
  )

  const res = binding.crypto_core_ed25519_sub(r, p, q)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_core_ed25519_scalar_random = function (r) {
  assert(ArrayBuffer.isView(r), 'r must be a typed array')
  assert(
    r.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "r must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )

  binding.crypto_core_ed25519_scalar_random(r)
}

exports.crypto_core_ed25519_scalar_reduce = function (r, s) {
  assert(ArrayBuffer.isView(r), 'r must be a typed array')
  assert(
    r.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "r must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(s), 's must be a typed array')
  assert(
    s.byteLength === binding.crypto_core_ed25519_NONREDUCEDSCALARBYTES,
    "s must be 'crypto_core_ed25519_NONREDUCEDSCALARBYTES' bytes"
  )

  binding.crypto_core_ed25519_scalar_reduce(r, s)
}

exports.crypto_core_ed25519_scalar_invert = function (recip, s) {
  assert(ArrayBuffer.isView(recip), 'recip must be a typed array')
  assert(
    recip.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "recip must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(s), 's must be a typed array')
  assert(
    s.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "s must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )

  binding.crypto_core_ed25519_scalar_invert(recip, s)
}

exports.crypto_core_ed25519_scalar_negate = function (neg, s) {
  assert(ArrayBuffer.isView(neg), 'neg must be a typed array')
  assert(
    neg.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "neg must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(s), 's must be a typed array')
  assert(
    s.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "s must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )

  binding.crypto_core_ed25519_scalar_negate(neg, s)
}

exports.crypto_core_ed25519_scalar_complement = function (comp, s) {
  assert(ArrayBuffer.isView(comp), 'comp must be a typed array')
  assert(
    comp.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "comp must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(s), 's must be a typed array')
  assert(
    s.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "s must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )

  binding.crypto_core_ed25519_scalar_complement(comp, s)
}

exports.crypto_core_ed25519_scalar_add = function (z, x, y) {
  assert(ArrayBuffer.isView(z), 'z must be a typed array')
  assert(
    z.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "z must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(x), 'x must be a typed array')
  assert(
    x.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "x must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(y), 'y must be a typed array')
  assert(
    y.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "y must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )

  binding.crypto_core_ed25519_scalar_add(z, x, y)
}

exports.crypto_core_ed25519_scalar_sub = function (z, x, y) {
  assert(ArrayBuffer.isView(z), 'z must be a typed array')
  assert(
    z.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "z must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(x), 'x must be a typed array')
  assert(
    x.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "x must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(y), 'y must be a typed array')
  assert(
    y.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "y must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )

  binding.crypto_core_ed25519_scalar_sub(z, x, y)
}

// crypto_shorthash

exports.crypto_shorthash = function (out, input, k) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(
    out.byteLength === binding.crypto_shorthash_BYTES,
    "out must be 'crypto_shorthash_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_shorthash_KEYBYTES,
    "k must be 'crypto_shorthash_KEYBYTES' bytes"
  )

  const res = binding.crypto_shorthash(out, input, k)

  if (res !== 0) throw new Error('status: ' + res)
}

// crypto_kdf

exports.crypto_kdf_keygen = function (key) {
  assert(ArrayBuffer.isView(key), 'key must be a typed array')
  assert(key.byteLength === binding.crypto_kdf_KEYBYTES, "key must be 'crypto_kdf_KEYBYTES' bytes")

  binding.crypto_kdf_keygen(key)
}

exports.crypto_kdf_derive_from_key = function (subkey, subkeyId, ctx, key) {
  assert(ArrayBuffer.isView(subkey), 'subkey must be a typed array')
  assert(
    subkey.byteLength >= binding.crypto_kdf_BYTES_MIN,
    "subkey must be at least 'crypto_kdf_BYTES_MIN' bytes"
  )
  assert(
    subkey.byteLength <= binding.crypto_kdf_BYTES_MAX,
    "subkey must be at most 'crypto_kdf_BYTES_MAX' bytes"
  )
  assert(ArrayBuffer.isView(ctx), 'ctx must be a typed array')
  assert(
    ctx.byteLength === binding.crypto_kdf_CONTEXTBYTES,
    "ctx must be 'crypto_kdf_CONTEXTBYTES' bytes"
  )
  assert(ArrayBuffer.isView(key), 'key must be a typed array')
  assert(key.byteLength === binding.crypto_kdf_KEYBYTES, "key must be 'crypto_kdf_KEYBYTES' bytes")

  const res = binding.crypto_kdf_derive_from_key(subkey, subkeyId, ctx, key)

  if (res !== 0) throw new Error('status: ' + res)
}

// crypto_hash

exports.crypto_hash = function (out, input) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(out.byteLength === binding.crypto_hash_BYTES, "out must be 'crypto_hash_BYTES' bytes")

  const res = binding.crypto_hash(out, input)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_hash_sha256 = function (out, input) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(
    out.byteLength === binding.crypto_hash_sha256_BYTES,
    "out must be 'crypto_hash_sha256_BYTES' bytes"
  )

  const res = binding.crypto_hash_sha256(out, input)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_hash_sha256_init = function (state) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_hash_sha256_STATEBYTES,
    "state must be 'crypto_hash_sha256_STATEBYTES' bytes"
  )

  const res = binding.crypto_hash_sha256_init(state)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_hash_sha256_update = function (state, input) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(
    state.byteLength === binding.crypto_hash_sha256_STATEBYTES,
    "state must be 'crypto_hash_sha256_STATEBYTES' bytes"
  )

  const res = binding.crypto_hash_sha256_update(state, input)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_hash_sha256_final = function (state, out) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_hash_sha256_STATEBYTES,
    "state must be 'crypto_hash_sha256_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(
    out.byteLength === binding.crypto_hash_sha256_BYTES,
    "out must be 'crypto_hash_sha256_BYTES' bytes"
  )

  const res = binding.crypto_hash_sha256_final(state, out)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_hash_sha512 = function (out, input) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(
    out.byteLength === binding.crypto_hash_sha512_BYTES,
    "out must be 'crypto_hash_sha512_BYTES' bytes"
  )

  const res = binding.crypto_hash_sha512(out, input)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_hash_sha512_init = function (state) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_hash_sha512_STATEBYTES,
    "state must be 'crypto_hash_sha512_STATEBYTES' bytes"
  )

  const res = binding.crypto_hash_sha512_init(state)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_hash_sha512_update = function (state, input) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(
    state.byteLength === binding.crypto_hash_sha512_STATEBYTES,
    "state must be 'crypto_hash_sha512_STATEBYTES' bytes"
  )

  const res = binding.crypto_hash_sha512_update(state, input)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_hash_sha512_final = function (state, out) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_hash_sha512_STATEBYTES,
    "state must be 'crypto_hash_sha512_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(
    out.byteLength === binding.crypto_hash_sha512_BYTES,
    "out must be 'crypto_hash_sha512_BYTES' bytes"
  )

  const res = binding.crypto_hash_sha512_final(state, out)

  if (res !== 0) throw new Error('status: ' + res)
}

// crypto_aead

exports.crypto_aead_xchacha20poly1305_ietf_keygen = function (k) {
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_aead_xchacha20poly1305_ietf_KEYBYTES,
    "k must be 'crypto_aead_xchacha20poly1305_ietf_KEYBYTES' bytes"
  )

  binding.crypto_aead_xchacha20poly1305_ietf_keygen(k)
}

exports.crypto_aead_xchacha20poly1305_ietf_encrypt = function (c, m, ad, nsec, npub, k) {
  if (!ad) ad = undefined

  assert(nsec === null, 'nsec must always be set to null')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(
    c.byteLength === m.byteLength + binding.crypto_aead_xchacha20poly1305_ietf_ABYTES,
    "c must be 'm.byteLength + crypto_aead_xchacha20poly1305_ietf_ABYTES' bytes"
  )
  assert(c.byteLength <= 0xffffffff, 'c.byteLength must be a 32bit integer')
  assert(ArrayBuffer.isView(npub), 'npub must be a typed array')
  assert(
    npub.byteLength === binding.crypto_aead_xchacha20poly1305_ietf_NPUBBYTES,
    "npub must be 'crypto_aead_xchacha20poly1305_ietf_NPUBBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_aead_xchacha20poly1305_ietf_KEYBYTES,
    "k must be 'crypto_aead_xchacha20poly1305_ietf_KEYBYTES' bytes"
  )

  const res = binding.crypto_aead_xchacha20poly1305_ietf_encrypt(c, m, ad, npub, k)

  if (res < 0) throw new Error('could not encrypt data')

  return res
}

exports.crypto_aead_xchacha20poly1305_ietf_decrypt = function (m, nsec, c, ad, npub, k) {
  if (!ad) ad = undefined

  assert(nsec === null, 'nsec must always be set to null')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(
    m.byteLength === c.byteLength - binding.crypto_aead_xchacha20poly1305_ietf_ABYTES,
    "m must be 'c.byteLength - crypto_aead_xchacha20poly1305_ietf_ABYTES' bytes"
  )
  assert(m.byteLength <= 0xffffffff, 'm.byteLength must be a 32bit integer')
  assert(ArrayBuffer.isView(npub), 'npub must be a typed array')
  assert(
    npub.byteLength === binding.crypto_aead_xchacha20poly1305_ietf_NPUBBYTES,
    "npub must be 'crypto_aead_xchacha20poly1305_ietf_NPUBBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_aead_xchacha20poly1305_ietf_KEYBYTES,
    "k must be 'crypto_aead_xchacha20poly1305_ietf_KEYBYTES' bytes"
  )

  const res = binding.crypto_aead_xchacha20poly1305_ietf_decrypt(m, c, ad, npub, k)

  if (res < 0) throw new Error('could not verify data')

  return res
}

exports.crypto_aead_xchacha20poly1305_ietf_encrypt_detached = function (
  c,
  mac,
  m,
  ad,
  nsec,
  npub,
  k
) {
  if (!ad) ad = undefined

  assert(nsec === null, 'nsec must always be set to null')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(ArrayBuffer.isView(mac), 'mac must be a typed array')
  assert(
    mac.byteLength === binding.crypto_aead_xchacha20poly1305_ietf_ABYTES,
    "mac must be 'crypto_aead_xchacha20poly1305_ietf_ABYTES' bytes"
  )
  assert(ArrayBuffer.isView(npub), 'npub must be a typed array')
  assert(
    npub.byteLength === binding.crypto_aead_xchacha20poly1305_ietf_NPUBBYTES,
    "npub must be 'crypto_aead_xchacha20poly1305_ietf_NPUBBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_aead_xchacha20poly1305_ietf_KEYBYTES,
    "k must be 'crypto_aead_xchacha20poly1305_ietf_KEYBYTES' bytes"
  )

  const res = binding.crypto_aead_xchacha20poly1305_ietf_encrypt_detached(c, mac, m, ad, npub, k)

  if (res < 0) throw new Error('could not encrypt data')

  return res
}

exports.crypto_aead_xchacha20poly1305_ietf_decrypt_detached = function (
  m,
  nsec,
  c,
  mac,
  ad,
  npub,
  k
) {
  if (!ad) ad = undefined

  assert(nsec === null, 'nsec must always be set to null')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(m.byteLength === c.byteLength, "m must be 'c.byteLength' bytes")
  assert(ArrayBuffer.isView(mac), 'mac must be a typed array')
  assert(
    mac.byteLength === binding.crypto_aead_xchacha20poly1305_ietf_ABYTES,
    "mac must be 'crypto_aead_xchacha20poly1305_ietf_ABYTES' bytes"
  )
  assert(ArrayBuffer.isView(npub), 'npub must be a typed array')
  assert(
    npub.byteLength === binding.crypto_aead_xchacha20poly1305_ietf_NPUBBYTES,
    "npub must be 'crypto_aead_xchacha20poly1305_ietf_NPUBBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_aead_xchacha20poly1305_ietf_KEYBYTES,
    "k must be 'crypto_aead_xchacha20poly1305_ietf_KEYBYTES' bytes"
  )

  const res = binding.crypto_aead_xchacha20poly1305_ietf_decrypt_detached(m, c, mac, ad, npub, k)

  if (res !== 0) throw new Error('could not verify data')
}

exports.crypto_aead_chacha20poly1305_ietf_keygen = function (k) {
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_aead_chacha20poly1305_ietf_KEYBYTES,
    "k must be 'crypto_aead_chacha20poly1305_ietf_KEYBYTES' bytes"
  )

  binding.crypto_aead_chacha20poly1305_ietf_keygen(k)
}

exports.crypto_aead_chacha20poly1305_ietf_encrypt = function (c, m, ad, nsec, npub, k) {
  if (!ad) ad = undefined

  assert(nsec === null, 'nsec must always be set to null')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(
    c.byteLength === m.byteLength + binding.crypto_aead_chacha20poly1305_ietf_ABYTES,
    "c must be 'm.byteLength + crypto_aead_chacha20poly1305_ietf_ABYTES' bytes"
  )
  assert(c.byteLength <= 0xffffffff, 'c.byteLength must be a 32bit integer')
  assert(ArrayBuffer.isView(npub), 'npub must be a typed array')
  assert(
    npub.byteLength === binding.crypto_aead_chacha20poly1305_ietf_NPUBBYTES,
    "npub must be 'crypto_aead_chacha20poly1305_ietf_NPUBBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_aead_chacha20poly1305_ietf_KEYBYTES,
    "k must be 'crypto_aead_chacha20poly1305_ietf_KEYBYTES' bytes"
  )

  const res = binding.crypto_aead_chacha20poly1305_ietf_encrypt(c, m, ad, npub, k)

  if (res < 0) throw new Error('could not encrypt data')

  return res
}

exports.crypto_aead_chacha20poly1305_ietf_decrypt = function (m, nsec, c, ad, npub, k) {
  if (!ad) ad = undefined

  assert(nsec === null, 'nsec must always be set to null')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(
    m.byteLength === c.byteLength - binding.crypto_aead_chacha20poly1305_ietf_ABYTES,
    "m must be 'c.byteLength - crypto_aead_chacha20poly1305_ietf_ABYTES' bytes"
  )
  assert(m.byteLength <= 0xffffffff, 'm.byteLength must be a 32bit integer')
  assert(ArrayBuffer.isView(npub), 'npub must be a typed array')
  assert(
    npub.byteLength === binding.crypto_aead_chacha20poly1305_ietf_NPUBBYTES,
    "npub must be 'crypto_aead_chacha20poly1305_ietf_NPUBBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_aead_chacha20poly1305_ietf_KEYBYTES,
    "k must be 'crypto_aead_chacha20poly1305_ietf_KEYBYTES' bytes"
  )

  const res = binding.crypto_aead_chacha20poly1305_ietf_decrypt(m, c, ad, npub, k)

  if (res < 0) throw new Error('could not verify data')

  return res
}

exports.crypto_aead_chacha20poly1305_ietf_encrypt_detached = function (
  c,
  mac,
  m,
  ad,
  nsec,
  npub,
  k
) {
  if (!ad) ad = undefined

  assert(nsec === null, 'nsec must always be set to null')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(ArrayBuffer.isView(mac), 'mac must be a typed array')
  assert(
    mac.byteLength === binding.crypto_aead_chacha20poly1305_ietf_ABYTES,
    "mac must be 'crypto_aead_chacha20poly1305_ietf_ABYTES' bytes"
  )
  assert(ArrayBuffer.isView(npub), 'npub must be a typed array')
  assert(
    npub.byteLength === binding.crypto_aead_chacha20poly1305_ietf_NPUBBYTES,
    "npub must be 'crypto_aead_chacha20poly1305_ietf_NPUBBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_aead_chacha20poly1305_ietf_KEYBYTES,
    "k must be 'crypto_aead_chacha20poly1305_ietf_KEYBYTES' bytes"
  )

  const res = binding.crypto_aead_chacha20poly1305_ietf_encrypt_detached(c, mac, m, ad, npub, k)

  if (res < 0) throw new Error('could not encrypt data')

  return res
}

exports.crypto_aead_chacha20poly1305_ietf_decrypt_detached = function (
  m,
  nsec,
  c,
  mac,
  ad,
  npub,
  k
) {
  if (!ad) ad = undefined

  assert(nsec === null, 'nsec must always be set to null')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(m.byteLength === c.byteLength, "m must be 'c.byteLength' bytes")
  assert(ArrayBuffer.isView(mac), 'mac must be a typed array')
  assert(
    mac.byteLength === binding.crypto_aead_chacha20poly1305_ietf_ABYTES,
    "mac must be 'crypto_aead_chacha20poly1305_ietf_ABYTES' bytes"
  )
  assert(ArrayBuffer.isView(npub), 'npub must be a typed array')
  assert(
    npub.byteLength === binding.crypto_aead_chacha20poly1305_ietf_NPUBBYTES,
    "npub must be 'crypto_aead_chacha20poly1305_ietf_NPUBBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_aead_chacha20poly1305_ietf_KEYBYTES,
    "k must be 'crypto_aead_chacha20poly1305_ietf_KEYBYTES' bytes"
  )

  const res = binding.crypto_aead_chacha20poly1305_ietf_decrypt_detached(m, c, mac, ad, npub, k)

  if (res !== 0) throw new Error('could not verify data')
}

// crypto_stream

exports.crypto_stream_xor_wrap_init = function (state, n, k) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.sn_crypto_stream_xor_STATEBYTES,
    "state must be 'sn_crypto_stream_xor_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.crypto_stream_NONCEBYTES,
    "n must be 'crypto_stream_NONCEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_stream_KEYBYTES,
    "k must be 'crypto_stream_KEYBYTES' bytes"
  )

  binding.crypto_stream_xor_wrap_init(state, n, k)
}

exports.crypto_stream_xor_wrap_update = function (state, c, m) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.sn_crypto_stream_xor_STATEBYTES,
    "state must be 'sn_crypto_stream_xor_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")

  binding.crypto_stream_xor_wrap_update(state, c, m)
}

exports.crypto_stream_xor_wrap_final = function (state) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.sn_crypto_stream_xor_STATEBYTES,
    "state must be 'sn_crypto_stream_xor_STATEBYTES' bytes"
  )

  binding.crypto_stream_xor_wrap_final(state)
}

exports.crypto_stream_chacha20_xor_wrap_init = function (state, n, k) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_stream_chacha20_xor_STATEBYTES,
    "state must be 'crypto_stream_chacha20_xor_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.crypto_stream_chacha20_NONCEBYTES,
    "n must be 'crypto_stream_chacha20_NONCEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_stream_chacha20_KEYBYTES,
    "k must be 'crypto_stream_chacha20_KEYBYTES' bytes"
  )

  binding.crypto_stream_chacha20_xor_wrap_init(state, n, k)
}

exports.crypto_stream_chacha20_xor_wrap_update = function (state, c, m) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_stream_chacha20_xor_STATEBYTES,
    "state must be 'crypto_stream_chacha20_xor_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")

  binding.crypto_stream_chacha20_xor_wrap_update(state, c, m)
}

exports.crypto_stream_chacha20_xor_wrap_final = function (state) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_stream_chacha20_xor_STATEBYTES,
    "state must be 'crypto_stream_chacha20_xor_STATEBYTES' bytes"
  )

  binding.crypto_stream_chacha20_xor_wrap_final(state)
}

exports.crypto_stream_chacha20_ietf_xor_wrap_init = function (state, n, k) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_stream_chacha20_ietf_xor_STATEBYTES,
    "state must be 'crypto_stream_chacha20_ietf_xor_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.crypto_stream_chacha20_ietf_NONCEBYTES,
    "n must be 'crypto_stream_chacha20_ietf_NONCEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_stream_chacha20_ietf_KEYBYTES,
    "k must be 'crypto_stream_chacha20_ietf_KEYBYTES' bytes"
  )

  binding.crypto_stream_chacha20_ietf_xor_wrap_init(state, n, k)
}

exports.crypto_stream_chacha20_ietf_xor_wrap_update = function (state, c, m) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_stream_chacha20_ietf_xor_STATEBYTES,
    "state must be 'crypto_stream_chacha20_ietf_xor_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")

  binding.crypto_stream_chacha20_ietf_xor_wrap_update(state, c, m)
}

exports.crypto_stream_chacha20_ietf_xor_wrap_final = function (state) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_stream_chacha20_ietf_xor_STATEBYTES,
    "state must be 'crypto_stream_chacha20_ietf_xor_STATEBYTES' bytes"
  )

  binding.crypto_stream_chacha20_ietf_xor_wrap_final(state)
}

exports.crypto_stream_xchacha20_xor_wrap_init = function (state, n, k) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_stream_xchacha20_xor_STATEBYTES,
    "state must be 'crypto_stream_xchacha20_xor_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.crypto_stream_xchacha20_NONCEBYTES,
    "n must be 'crypto_stream_xchacha20_NONCEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_stream_xchacha20_KEYBYTES,
    "k must be 'crypto_stream_xchacha20_KEYBYTES' bytes"
  )

  binding.crypto_stream_xchacha20_xor_wrap_init(state, n, k)
}

exports.crypto_stream_xchacha20_xor_wrap_update = function (state, c, m) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_stream_xchacha20_xor_STATEBYTES,
    "state must be 'crypto_stream_xchacha20_xor_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")

  binding.crypto_stream_xchacha20_xor_wrap_update(state, c, m)
}

exports.crypto_stream_xchacha20_xor_wrap_final = function (state) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_stream_xchacha20_xor_STATEBYTES,
    "state must be 'crypto_stream_xchacha20_xor_STATEBYTES' bytes"
  )

  binding.crypto_stream_xchacha20_xor_wrap_final(state)
}

exports.crypto_stream_salsa20_xor_wrap_init = function (state, n, k) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_stream_salsa20_xor_STATEBYTES,
    "state must be 'crypto_stream_salsa20_xor_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.crypto_stream_salsa20_NONCEBYTES,
    "n must be 'crypto_stream_salsa20_NONCEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_stream_salsa20_KEYBYTES,
    "k must be 'crypto_stream_salsa20_KEYBYTES' bytes"
  )

  binding.crypto_stream_salsa20_xor_wrap_init(state, n, k)
}

exports.crypto_stream_salsa20_xor_wrap_update = function (state, c, m) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_stream_salsa20_xor_STATEBYTES,
    "state must be 'crypto_stream_salsa20_xor_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")

  binding.crypto_stream_salsa20_xor_wrap_update(state, c, m)
}

exports.crypto_stream_salsa20_xor_wrap_final = function (state) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_stream_salsa20_xor_STATEBYTES,
    "state must be 'crypto_stream_salsa20_xor_STATEBYTES' bytes"
  )

  binding.crypto_stream_salsa20_xor_wrap_final(state)
}

// experimental

exports.extension_tweak_ed25519_base = function (n, p, ns) {
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "n must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(p), 'p must be a typed array')
  assert(
    p.byteLength === binding.extension_tweak_ed25519_BYTES,
    "p must be 'extension_tweak_ed25519_BYTES' bytes"
  )

  binding.extension_tweak_ed25519_base(n, p, ns)
}

exports.extension_tweak_ed25519_sign_detached = function (sig, m, scalar, pk) {
  assert(ArrayBuffer.isView(sig), 'sig must be a typed array')
  assert(sig.byteLength === binding.crypto_sign_BYTES, "sig must be 'crypto_sign_BYTES' bytes")
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(scalar), 'scalar must be a typed array')
  assert(
    scalar.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "scalar must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )

  if (pk) {
    assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
    assert(
      pk.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
      "pk must be 'crypto_sign_PUBLICKEYBYTES' bytes"
    )
  }

  const res = binding.extension_tweak_ed25519_sign_detached(sig, m, scalar, pk)

  if (res !== 0) throw new Error('failed to compute signature')
}

exports.extension_tweak_ed25519_sk_to_scalar = function (n, sk) {
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "n must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(
    sk.byteLength === binding.crypto_sign_SECRETKEYBYTES,
    "sk must be 'crypto_sign_SECRETKEYBYTES' bytes"
  )

  binding.extension_tweak_ed25519_sk_to_scalar(n, sk)
}

exports.extension_tweak_ed25519_scalar = function (scalarOut, scalar, ns) {
  assert(ArrayBuffer.isView(scalarOut), 'scalarOut must be a typed array')
  assert(
    scalarOut.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "scalarOut must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(scalar), 'scalar must be a typed array')
  assert(
    scalar.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "scalar must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )

  binding.extension_tweak_ed25519_scalar(scalarOut, scalar, ns)
}

exports.extension_tweak_ed25519_pk = function (tpk, pk, ns) {
  assert(ArrayBuffer.isView(tpk), 'tpk must be a typed array')
  assert(
    tpk.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
    "tpk must be 'crypto_sign_PUBLICKEYBYTES' bytes"
  )
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(
    pk.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
    "pk must be 'crypto_sign_PUBLICKEYBYTES' bytes"
  )

  const res = binding.extension_tweak_ed25519_pk(tpk, pk, ns)

  if (res !== 0) throw new Error('failed to tweak public key')
}

exports.extension_tweak_ed25519_keypair = function (pk, scalarOut, scalarIn, ns) {
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(
    pk.byteLength === binding.extension_tweak_ed25519_BYTES,
    "pk must be 'extension_tweak_ed25519_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(scalarOut), 'scalarOut must be a typed array')
  assert(
    scalarOut.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "scalarOut must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(scalarIn), 'scalarIn must be a typed array')
  assert(
    scalarIn.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "scalarIn must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )

  binding.extension_tweak_ed25519_keypair(pk, scalarOut, scalarIn, ns)
}

exports.extension_tweak_ed25519_scalar_add = function (scalarOut, scalar, n) {
  assert(ArrayBuffer.isView(scalarOut), 'scalarOut must be a typed array')
  assert(
    scalarOut.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "scalarOut must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(scalar), 'scalar must be a typed array')
  assert(
    scalar.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "scalar must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "n must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )

  binding.extension_tweak_ed25519_scalar_add(scalarOut, scalar, n)
}

exports.extension_tweak_ed25519_pk_add = function (tpk, pk, p) {
  assert(ArrayBuffer.isView(tpk), 'tpk must be a typed array')
  assert(
    tpk.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
    "tpk must be 'crypto_sign_PUBLICKEYBYTES' bytes"
  )
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(
    pk.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
    "pk must be 'crypto_sign_PUBLICKEYBYTES' bytes"
  )
  assert(ArrayBuffer.isView(p), 'p must be a typed array')
  assert(
    p.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
    "p must be 'crypto_sign_PUBLICKEYBYTES' bytes"
  )

  const res = binding.extension_tweak_ed25519_pk_add(tpk, pk, p)

  if (res !== 0) throw new Error('failed to add tweak to public key')
}

exports.extension_tweak_ed25519_keypair_add = function (pk, scalarOut, scalarIn, tweak) {
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(
    pk.byteLength === binding.extension_tweak_ed25519_BYTES,
    "pk must be 'extension_tweak_ed25519_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(scalarOut), 'scalarOut must be a typed array')
  assert(
    scalarOut.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "scalarOut must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(scalarIn), 'scalarIn must be a typed array')
  assert(
    scalarIn.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "scalarIn must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(tweak), 'tweak must be a typed array')
  assert(
    tweak.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "tweak must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )

  const res = binding.extension_tweak_ed25519_keypair_add(pk, scalarOut, scalarIn, tweak)

  if (res !== 0) throw new Error('failed to add tweak to keypair')
}

exports.extension_pbkdf2_sha512_async = function (out, passwd, salt, iter, outlen, callback) {
  assert(
    iter >= binding.extension_pbkdf2_sha512_ITERATIONS_MIN,
    "iter must be at least 'extension_pbkdf2_sha512_ITERATIONS_MIN'"
  )
  assert(
    outlen <= binding.extension_pbkdf2_sha512_BYTES_MAX,
    "outlen must be at most 'extension_pbkdf2_sha512_BYTES_MAX'"
  )
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(out.byteLength >= outlen, "out must be at least 'outlen' bytes")
  assert(out.byteLength > 0, 'out must not be empty')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(passwd.byteLength > 0, 'passwd must not be empty')
  assert(ArrayBuffer.isView(salt), 'salt must be a typed array')
  assert(salt.byteLength > 0, 'salt must not be empty')

  const [done, promise] = checkStatus(callback)

  binding.extension_pbkdf2_sha512_async(
    out.buffer,
    out.byteOffset,
    out.byteLength,

    passwd.buffer,
    passwd.byteOffset,
    passwd.byteLength,

    salt.buffer,
    salt.byteOffset,
    salt.byteLength,

    iter,
    outlen,

    done
  )

  return promise
}

exports.extension_pbkdf2_sha512 = function (out, passwd, salt, iter, outlen) {
  assert(
    iter >= binding.extension_pbkdf2_sha512_ITERATIONS_MIN,
    "iter must be at least 'extension_pbkdf2_sha512_ITERATIONS_MIN'"
  )
  assert(
    outlen <= binding.extension_pbkdf2_sha512_BYTES_MAX,
    "outlen must be at most 'extension_pbkdf2_sha512_BYTES_MAX'"
  )
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(out.byteLength >= outlen, "out must be at least 'outlen' bytes")
  assert(out.byteLength > 0, 'out must not be empty')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(passwd.byteLength > 0, 'passwd must not be empty')
  assert(ArrayBuffer.isView(salt), 'salt must be a typed array')
  assert(salt.byteLength > 0, 'salt must not be empty')

  const res = binding.extension_pbkdf2_sha512(out, passwd, salt, iter, outlen)

  if (res !== 0) throw new Error('failed to add tweak to public key')
}

function checkStatus(callback, booleanResult = false) {
  let done, promise

  if (typeof callback === 'function') {
    done = function (status) {
      if (booleanResult) callback(null, status === 0)
      else if (status === 0) callback(null)
      else callback(new Error('status: ' + status))
    }
  } else {
    promise = new Promise(function (resolve, reject) {
      done = function (status) {
        if (booleanResult) resolve(status === 0)
        else if (status === 0) resolve()
        else reject(new Error('status: ' + status))
      }
    })
  }

  return [done, promise]
}
{
  "name": "sodium-native",
  "version": "5.1.0",
  "description": "Low level bindings for libsodium",
  "main": "index.js",
  "files": [
    "index.js",
    "binding.cc",
    "binding.js",
    "extensions",
    "prebuilds",
    "CMakeLists.txt"
  ],
  "imports": {
    "assert": {
      "bare": "bare-assert",
      "node": "assert"
    }
  },
  "addon": true,
  "dependencies": {
    "bare-assert": "^1.2.0",
    "require-addon": "^1.1.0",
    "which-runtime": "^1.2.1"
  },
  "devDependencies": {
    "bare-compat-napi": "^1.3.5",
    "brittle": "^3.16.2",
    "cmake-bare": "^1.6.1",
    "cmake-fetch": "^1.4.3",
    "cmake-napi": "^1.2.1",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^2.0.0"
  },
  "scripts": {
    "test": "prettier . --check && npm run test:node && npm run test:bare",
    "test:node": "node test/all.js",
    "test:bare": "bare test/all.js"
  },
  "standard": {
    "ignore": [
      "/test/fixtures/*.js"
    ]
  },
  "engines": {
    "bare": ">=1.16.0"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/sodium-native.git"
  },
  "contributors": [
    "Emil Bay <github@tixz.dk> (http://bayes.dk)",
    "Mathias Buus <mathiasbuus@gmail.com> (https://mafinto.sh)",
    "Christophe Diederichs <chm-diederichs@hyperdivision.dk>"
  ],
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/holepunchto/sodium-native/issues"
  },
  "homepage": "https://github.com/holepunchto/sodium-native"
}
const sodium = require('sodium-universal')
const b4a = require('b4a')

const ABYTES = sodium.crypto_secretstream_xchacha20poly1305_ABYTES
const TAG_MESSAGE = sodium.crypto_secretstream_xchacha20poly1305_TAG_MESSAGE
const TAG_FINAL = sodium.crypto_secretstream_xchacha20poly1305_TAG_FINAL
const STATEBYTES = sodium.crypto_secretstream_xchacha20poly1305_STATEBYTES
const HEADERBYTES = sodium.crypto_secretstream_xchacha20poly1305_HEADERBYTES
const KEYBYTES = sodium.crypto_secretstream_xchacha20poly1305_KEYBYTES
const TAG_FINAL_BYTE = b4a.isBuffer(TAG_FINAL) ? TAG_FINAL[0] : TAG_FINAL

const EMPTY = b4a.alloc(0)
const TAG = b4a.alloc(1)

class Push {
  constructor (key, state = b4a.allocUnsafeSlow(STATEBYTES), header = b4a.allocUnsafeSlow(HEADERBYTES)) {
    if (!TAG_FINAL) throw new Error('JavaScript sodium version needs to support crypto_secretstream_xchacha20poly')

    this.key = key
    this.state = state
    this.header = header

    sodium.crypto_secretstream_xchacha20poly1305_init_push(this.state, this.header, this.key)
  }

  next (message, cipher = b4a.allocUnsafe(message.byteLength + ABYTES)) {
    sodium.crypto_secretstream_xchacha20poly1305_push(this.state, cipher, message, null, TAG_MESSAGE)
    return cipher
  }

  final (message = EMPTY, cipher = b4a.allocUnsafe(ABYTES)) {
    sodium.crypto_secretstream_xchacha20poly1305_push(this.state, cipher, message, null, TAG_FINAL)
    return cipher
  }
}

class Pull {
  constructor (key, state = b4a.allocUnsafeSlow(STATEBYTES)) {
    if (!TAG_FINAL) throw new Error('JavaScript sodium version needs to support crypto_secretstream_xchacha20poly')

    this.key = key
    this.state = state
    this.final = false
  }

  init (header) {
    sodium.crypto_secretstream_xchacha20poly1305_init_pull(this.state, header, this.key)
  }

  next (cipher, message = b4a.allocUnsafe(cipher.byteLength - ABYTES)) {
    sodium.crypto_secretstream_xchacha20poly1305_pull(this.state, message, TAG, cipher, null)
    this.final = TAG[0] === TAG_FINAL_BYTE
    return message
  }
}

function keygen (buf = b4a.alloc(KEYBYTES)) {
  sodium.crypto_secretstream_xchacha20poly1305_keygen(buf)
  return buf
}

module.exports = {
  keygen,
  KEYBYTES,
  ABYTES,
  STATEBYTES,
  HEADERBYTES,
  Push,
  Pull
}
{
  "name": "sodium-secretstream",
  "version": "1.2.0",
  "description": "Wraps libsodiums secretstream in a higher level abstraction",
  "main": "index.js",
  "dependencies": {
    "b4a": "^1.1.1",
    "sodium-universal": "^5.0.0"
  },
  "devDependencies": {
    "standard": "^17.0.0"
  },
  "scripts": {
    "test": "standard"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/mafintosh/sodium-secretstream.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/mafintosh/sodium-secretstream/issues"
  },
  "homepage": "https://github.com/mafintosh/sodium-secretstream"
}
module.exports = require('sodium-native')
{
  "name": "sodium-universal",
  "version": "5.0.1",
  "description": "Universal wrapper for sodium-javascript and sodium-native working in Node.js and the Browser",
  "main": "index.js",
  "dependencies": {
    "sodium-native": "^5.0.1"
  },
  "peerDependencies": {
    "sodium-javascript": "~0.8.0"
  },
  "peerDependenciesMeta": {
    "sodium-javascript": {
      "optional": true
    }
  },
  "scripts": {
    "prepublish": "./build-scripts/generate.js"
  },
  "browser": {
    "sodium-native": "sodium-javascript"
  },
  "browserify": {
    "transform": [
      "./build-scripts/transform.js"
    ]
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/sodium-universal.git"
  },
  "keywords": [
    "libsodium",
    "sodium",
    "sodium-native",
    "sodium-javascript",
    "browserify"
  ],
  "contributors": [
    "Emil Bay <github@tixz.dk> (http://bayes.dk)",
    "Mathias Buus <mathiasbuus@gmail.com> (https://mafinto.sh)",
    "Christophe Diederichs <chm-diederichs@hyperdivision.dk>"
  ],
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/holepunchto/sodium-universal/issues"
  },
  "homepage": "https://github.com/holepunchto/sodium-universal#readme"
}
const { EventEmitter } = require('events-universal')
const FIFO = require('fast-fifo')
const TextDecoder = require('text-decoder')

const StreamError = require('./lib/errors.js')

// if we do a future major, expect queue microtask to be there always, for now a bit defensive
const qmt =
  typeof queueMicrotask === 'undefined' ? (fn) => global.process.nextTick(fn) : queueMicrotask

// 29 bits used total (4 from shared, 14 from read, and 11 from write)
const MAX = (1 << 29) - 1

// Shared state
const OPENING = 0b0001
const PREDESTROYING = 0b0010
const DESTROYING = 0b0100
const DESTROYED = 0b1000

const NOT_OPENING = MAX ^ OPENING
const NOT_PREDESTROYING = MAX ^ PREDESTROYING

// Read state (4 bit offset from shared state)
const READ_ACTIVE = 0b00000000000001 << 4
const READ_UPDATING = 0b00000000000010 << 4
const READ_PRIMARY = 0b00000000000100 << 4
const READ_QUEUED = 0b00000000001000 << 4
const READ_RESUMED = 0b00000000010000 << 4
const READ_PIPE_DRAINED = 0b00000000100000 << 4
const READ_ENDING = 0b00000001000000 << 4
const READ_EMIT_DATA = 0b00000010000000 << 4
const READ_EMIT_READABLE = 0b00000100000000 << 4
const READ_EMITTED_READABLE = 0b00001000000000 << 4
const READ_DONE = 0b00010000000000 << 4
const READ_NEXT_TICK = 0b00100000000000 << 4
const READ_NEEDS_PUSH = 0b01000000000000 << 4
const READ_READ_AHEAD = 0b10000000000000 << 4

// Combined read state
const READ_FLOWING = READ_RESUMED | READ_PIPE_DRAINED
const READ_ACTIVE_AND_NEEDS_PUSH = READ_ACTIVE | READ_NEEDS_PUSH
const READ_PRIMARY_AND_ACTIVE = READ_PRIMARY | READ_ACTIVE
const READ_EMIT_READABLE_AND_QUEUED = READ_EMIT_READABLE | READ_QUEUED
const READ_RESUMED_READ_AHEAD = READ_RESUMED | READ_READ_AHEAD

const READ_NOT_ACTIVE = MAX ^ READ_ACTIVE
const READ_NON_PRIMARY = MAX ^ READ_PRIMARY
const READ_NON_PRIMARY_AND_PUSHED = MAX ^ (READ_PRIMARY | READ_NEEDS_PUSH)
const READ_PUSHED = MAX ^ READ_NEEDS_PUSH
const READ_PAUSED = MAX ^ READ_RESUMED
const READ_NOT_QUEUED = MAX ^ (READ_QUEUED | READ_EMITTED_READABLE)
const READ_NOT_ENDING = MAX ^ READ_ENDING
const READ_PIPE_NOT_DRAINED = MAX ^ READ_FLOWING
const READ_NOT_NEXT_TICK = MAX ^ READ_NEXT_TICK
const READ_NOT_UPDATING = MAX ^ READ_UPDATING
const READ_NO_READ_AHEAD = MAX ^ READ_READ_AHEAD
const READ_PAUSED_NO_READ_AHEAD = MAX ^ READ_RESUMED_READ_AHEAD

// Write state (18 bit offset, 4 bit offset from shared state and 14 from read state)
const WRITE_ACTIVE = 0b00000000001 << 18
const WRITE_UPDATING = 0b00000000010 << 18
const WRITE_PRIMARY = 0b00000000100 << 18
const WRITE_QUEUED = 0b00000001000 << 18
const WRITE_UNDRAINED = 0b00000010000 << 18
const WRITE_DONE = 0b00000100000 << 18
const WRITE_EMIT_DRAIN = 0b00001000000 << 18
const WRITE_NEXT_TICK = 0b00010000000 << 18
const WRITE_WRITING = 0b00100000000 << 18
const WRITE_FINISHING = 0b01000000000 << 18
const WRITE_CORKED = 0b10000000000 << 18

const WRITE_NOT_ACTIVE = MAX ^ (WRITE_ACTIVE | WRITE_WRITING)
const WRITE_NON_PRIMARY = MAX ^ WRITE_PRIMARY
const WRITE_NOT_FINISHING = MAX ^ (WRITE_ACTIVE | WRITE_FINISHING)
const WRITE_DRAINED = MAX ^ WRITE_UNDRAINED
const WRITE_NOT_QUEUED = MAX ^ WRITE_QUEUED
const WRITE_NOT_NEXT_TICK = MAX ^ WRITE_NEXT_TICK
const WRITE_NOT_UPDATING = MAX ^ WRITE_UPDATING
const WRITE_NOT_CORKED = MAX ^ WRITE_CORKED

// Combined shared state
const ACTIVE = READ_ACTIVE | WRITE_ACTIVE
const NOT_ACTIVE = MAX ^ ACTIVE
const DONE = READ_DONE | WRITE_DONE
const DESTROY_STATUS = DESTROYING | DESTROYED | PREDESTROYING
const OPEN_STATUS = DESTROY_STATUS | OPENING
const AUTO_DESTROY = DESTROY_STATUS | DONE
const NON_PRIMARY = WRITE_NON_PRIMARY & READ_NON_PRIMARY
const ACTIVE_OR_TICKING = WRITE_NEXT_TICK | READ_NEXT_TICK
const TICKING = ACTIVE_OR_TICKING & NOT_ACTIVE
const IS_OPENING = OPEN_STATUS | TICKING

// Combined shared state and read state
const READ_PRIMARY_STATUS = OPEN_STATUS | READ_ENDING | READ_DONE
const READ_STATUS = OPEN_STATUS | READ_DONE | READ_QUEUED
const READ_ENDING_STATUS = OPEN_STATUS | READ_ENDING | READ_QUEUED
const READ_READABLE_STATUS = OPEN_STATUS | READ_EMIT_READABLE | READ_QUEUED | READ_EMITTED_READABLE
const SHOULD_NOT_READ =
  OPEN_STATUS | READ_ACTIVE | READ_ENDING | READ_DONE | READ_NEEDS_PUSH | READ_READ_AHEAD
const READ_BACKPRESSURE_STATUS = DESTROY_STATUS | READ_ENDING | READ_DONE
const READ_UPDATE_SYNC_STATUS = READ_UPDATING | OPEN_STATUS | READ_NEXT_TICK | READ_PRIMARY
const READ_NEXT_TICK_OR_OPENING = READ_NEXT_TICK | OPENING

// Combined write state
const WRITE_PRIMARY_STATUS = OPEN_STATUS | WRITE_FINISHING | WRITE_DONE
const WRITE_QUEUED_AND_UNDRAINED = WRITE_QUEUED | WRITE_UNDRAINED
const WRITE_QUEUED_AND_ACTIVE = WRITE_QUEUED | WRITE_ACTIVE
const WRITE_DRAIN_STATUS = WRITE_QUEUED | WRITE_UNDRAINED | OPEN_STATUS | WRITE_ACTIVE
const WRITE_STATUS = OPEN_STATUS | WRITE_ACTIVE | WRITE_QUEUED | WRITE_CORKED
const WRITE_PRIMARY_AND_ACTIVE = WRITE_PRIMARY | WRITE_ACTIVE
const WRITE_ACTIVE_AND_WRITING = WRITE_ACTIVE | WRITE_WRITING
const WRITE_FINISHING_STATUS = OPEN_STATUS | WRITE_FINISHING | WRITE_QUEUED_AND_ACTIVE | WRITE_DONE
const WRITE_BACKPRESSURE_STATUS = WRITE_UNDRAINED | DESTROY_STATUS | WRITE_FINISHING | WRITE_DONE
const WRITE_UPDATE_SYNC_STATUS = WRITE_UPDATING | OPEN_STATUS | WRITE_NEXT_TICK | WRITE_PRIMARY
const WRITE_DROP_DATA = WRITE_FINISHING | WRITE_DONE | DESTROY_STATUS

const asyncIterator = Symbol.asyncIterator || Symbol('asyncIterator')

class WritableState {
  constructor(
    stream,
    { highWaterMark = 16384, map = null, mapWritable, byteLength, byteLengthWritable } = {}
  ) {
    this.stream = stream
    this.queue = new FIFO()
    this.highWaterMark = highWaterMark
    this.buffered = 0
    this.error = null
    this.pipeline = null
    this.drains = null // if we add more seldomly used helpers we might them into a subobject so its a single ptr
    this.byteLength = byteLengthWritable || byteLength || defaultByteLength
    this.map = mapWritable || map
    this.afterWrite = afterWrite.bind(this)
    this.afterUpdateNextTick = updateWriteNT.bind(this)
  }

  get ending() {
    return (this.stream._duplexState & WRITE_FINISHING) !== 0
  }

  get ended() {
    return (this.stream._duplexState & WRITE_DONE) !== 0
  }

  push(data) {
    if ((this.stream._duplexState & WRITE_DROP_DATA) !== 0) return false
    if (this.map !== null) data = this.map(data)

    this.buffered += this.byteLength(data)
    this.queue.push(data)

    if (this.buffered < this.highWaterMark) {
      this.stream._duplexState |= WRITE_QUEUED
      return true
    }

    this.stream._duplexState |= WRITE_QUEUED_AND_UNDRAINED
    return false
  }

  shift() {
    const data = this.queue.shift()

    this.buffered -= this.byteLength(data)
    if (this.buffered === 0) this.stream._duplexState &= WRITE_NOT_QUEUED

    return data
  }

  end(data) {
    if (typeof data === 'function') {
      this.stream.once('finish', data)
    } else if (data !== undefined && data !== null) {
      this.push(data)
    }

    this.stream._duplexState = (this.stream._duplexState | WRITE_FINISHING) & WRITE_NON_PRIMARY
  }

  autoBatch(data, cb) {
    const buffer = []
    const stream = this.stream

    buffer.push(data)
    while ((stream._duplexState & WRITE_STATUS) === WRITE_QUEUED_AND_ACTIVE) {
      buffer.push(stream._writableState.shift())
    }

    if ((stream._duplexState & OPEN_STATUS) !== 0) return cb(null)
    stream._writev(buffer, cb)
  }

  update() {
    const stream = this.stream

    stream._duplexState |= WRITE_UPDATING

    do {
      while ((stream._duplexState & WRITE_STATUS) === WRITE_QUEUED) {
        const data = this.shift()
        stream._duplexState |= WRITE_ACTIVE_AND_WRITING
        stream._write(data, this.afterWrite)
      }

      if ((stream._duplexState & WRITE_PRIMARY_AND_ACTIVE) === 0) this.updateNonPrimary()
    } while (this.continueUpdate() === true)

    stream._duplexState &= WRITE_NOT_UPDATING
  }

  updateNonPrimary() {
    const stream = this.stream

    if ((stream._duplexState & WRITE_FINISHING_STATUS) === WRITE_FINISHING) {
      stream._duplexState = stream._duplexState | WRITE_ACTIVE
      stream._final(afterFinal.bind(this))
      return
    }

    if ((stream._duplexState & DESTROY_STATUS) === DESTROYING) {
      if ((stream._duplexState & ACTIVE_OR_TICKING) === 0) {
        stream._duplexState |= ACTIVE
        stream._destroy(afterDestroy.bind(this))
      }
      return
    }

    if ((stream._duplexState & IS_OPENING) === OPENING) {
      stream._duplexState = (stream._duplexState | ACTIVE) & NOT_OPENING
      stream._open(afterOpen.bind(this))
    }
  }

  continueUpdate() {
    if ((this.stream._duplexState & WRITE_NEXT_TICK) === 0) return false
    this.stream._duplexState &= WRITE_NOT_NEXT_TICK
    return true
  }

  updateCallback() {
    if ((this.stream._duplexState & WRITE_UPDATE_SYNC_STATUS) === WRITE_PRIMARY) {
      this.update()
    } else {
      this.updateNextTick()
    }
  }

  updateNextTick() {
    if ((this.stream._duplexState & WRITE_NEXT_TICK) !== 0) return
    this.stream._duplexState |= WRITE_NEXT_TICK
    if ((this.stream._duplexState & WRITE_UPDATING) === 0) qmt(this.afterUpdateNextTick)
  }
}

class ReadableState {
  constructor(
    stream,
    { highWaterMark = 16384, map = null, mapReadable, byteLength, byteLengthReadable } = {}
  ) {
    this.stream = stream
    this.queue = new FIFO()
    this.highWaterMark = highWaterMark === 0 ? 1 : highWaterMark
    this.buffered = 0
    this.readAhead = highWaterMark > 0
    this.error = null
    this.pipeline = null
    this.byteLength = byteLengthReadable || byteLength || defaultByteLength
    this.map = mapReadable || map
    this.pipeTo = null
    this.afterRead = afterRead.bind(this)
    this.afterUpdateNextTick = updateReadNT.bind(this)
  }

  get ending() {
    return (this.stream._duplexState & READ_ENDING) !== 0
  }

  get ended() {
    return (this.stream._duplexState & READ_DONE) !== 0
  }

  pipe(pipeTo, cb) {
    if (this.pipeTo !== null) throw StreamError.BAD_ARGUMENT('Can only pipe to one destination')
    if (typeof cb !== 'function') cb = null

    this.stream._duplexState |= READ_PIPE_DRAINED
    this.pipeTo = pipeTo
    this.pipeline = new Pipeline(this.stream, pipeTo, cb)

    if (cb) this.stream.on('error', noop) // We already error handle this so supress crashes

    if (isStreamx(pipeTo)) {
      pipeTo._writableState.pipeline = this.pipeline
      if (cb) pipeTo.on('error', noop) // We already error handle this so supress crashes
      pipeTo.on('finish', this.pipeline.finished.bind(this.pipeline)) // TODO: just call finished from pipeTo itself
    } else {
      const onerror = this.pipeline.done.bind(this.pipeline, pipeTo)
      const onclose = this.pipeline.done.bind(this.pipeline, pipeTo, null) // onclose has a weird bool arg
      pipeTo.on('error', onerror)
      pipeTo.on('close', onclose)
      pipeTo.on('finish', this.pipeline.finished.bind(this.pipeline))
    }

    pipeTo.on('drain', afterDrain.bind(this))
    this.stream.emit('piping', pipeTo)
    pipeTo.emit('pipe', this.stream)
  }

  push(data) {
    const stream = this.stream

    if (data === null) {
      this.highWaterMark = 0
      stream._duplexState = (stream._duplexState | READ_ENDING) & READ_NON_PRIMARY_AND_PUSHED
      return false
    }

    if (this.map !== null) {
      data = this.map(data)
      if (data === null) {
        stream._duplexState &= READ_PUSHED
        return this.buffered < this.highWaterMark
      }
    }

    this.buffered += this.byteLength(data)
    this.queue.push(data)

    stream._duplexState = (stream._duplexState | READ_QUEUED) & READ_PUSHED

    return this.buffered < this.highWaterMark
  }

  shift() {
    const data = this.queue.shift()

    this.buffered -= this.byteLength(data)
    if (this.buffered === 0) {
      this.stream._duplexState &= READ_NOT_QUEUED
    }

    return data
  }

  unshift(data) {
    const pending = [this.map !== null ? this.map(data) : data]
    while (this.buffered > 0) pending.push(this.shift())

    for (let i = 0; i < pending.length - 1; i++) {
      const data = pending[i]
      this.buffered += this.byteLength(data)
      this.queue.push(data)
    }

    this.push(pending[pending.length - 1])
  }

  read() {
    const stream = this.stream

    if ((stream._duplexState & READ_STATUS) === READ_QUEUED) {
      const data = this.shift()

      if (this.pipeTo !== null && this.pipeTo.write(data) === false) {
        stream._duplexState &= READ_PIPE_NOT_DRAINED
      }

      if ((stream._duplexState & READ_EMIT_DATA) !== 0) {
        stream.emit('data', data)
      }

      return data
    }

    if (this.readAhead === false) {
      stream._duplexState |= READ_READ_AHEAD
      this.updateNextTick()
    }

    return null
  }

  drain() {
    const stream = this.stream

    while (
      (stream._duplexState & READ_STATUS) === READ_QUEUED &&
      (stream._duplexState & READ_FLOWING) !== 0
    ) {
      const data = this.shift()

      if (this.pipeTo !== null && this.pipeTo.write(data) === false) {
        stream._duplexState &= READ_PIPE_NOT_DRAINED
      }

      if ((stream._duplexState & READ_EMIT_DATA) !== 0) {
        stream.emit('data', data)
      }
    }
  }

  update() {
    const stream = this.stream

    stream._duplexState |= READ_UPDATING

    do {
      this.drain()

      while (
        this.buffered < this.highWaterMark &&
        (stream._duplexState & SHOULD_NOT_READ) === READ_READ_AHEAD
      ) {
        stream._duplexState |= READ_ACTIVE_AND_NEEDS_PUSH
        stream._read(this.afterRead)
        this.drain()
      }

      if ((stream._duplexState & READ_READABLE_STATUS) === READ_EMIT_READABLE_AND_QUEUED) {
        stream._duplexState |= READ_EMITTED_READABLE
        stream.emit('readable')
      }

      if ((stream._duplexState & READ_PRIMARY_AND_ACTIVE) === 0) {
        this.updateNonPrimary()
      }
    } while (this.continueUpdate() === true)

    stream._duplexState &= READ_NOT_UPDATING
  }

  updateNonPrimary() {
    const stream = this.stream

    if ((stream._duplexState & READ_ENDING_STATUS) === READ_ENDING) {
      stream._duplexState = (stream._duplexState | READ_DONE) & READ_NOT_ENDING
      stream.emit('end')

      if ((stream._duplexState & AUTO_DESTROY) === DONE) {
        stream._duplexState |= DESTROYING
      }

      if (this.pipeTo !== null) {
        this.pipeTo.end()
      }
    }

    if ((stream._duplexState & DESTROY_STATUS) === DESTROYING) {
      if ((stream._duplexState & ACTIVE_OR_TICKING) === 0) {
        stream._duplexState |= ACTIVE
        stream._destroy(afterDestroy.bind(this))
      }
      return
    }

    if ((stream._duplexState & IS_OPENING) === OPENING) {
      stream._duplexState = (stream._duplexState | ACTIVE) & NOT_OPENING
      stream._open(afterOpen.bind(this))
    }
  }

  continueUpdate() {
    if ((this.stream._duplexState & READ_NEXT_TICK) === 0) return false
    this.stream._duplexState &= READ_NOT_NEXT_TICK
    return true
  }

  updateCallback() {
    if ((this.stream._duplexState & READ_UPDATE_SYNC_STATUS) === READ_PRIMARY) {
      this.update()
    } else {
      this.updateNextTick()
    }
  }

  updateNextTickIfOpen() {
    if ((this.stream._duplexState & READ_NEXT_TICK_OR_OPENING) !== 0) return
    this.stream._duplexState |= READ_NEXT_TICK
    if ((this.stream._duplexState & READ_UPDATING) === 0) qmt(this.afterUpdateNextTick)
  }

  updateNextTick() {
    if ((this.stream._duplexState & READ_NEXT_TICK) !== 0) return
    this.stream._duplexState |= READ_NEXT_TICK
    if ((this.stream._duplexState & READ_UPDATING) === 0) qmt(this.afterUpdateNextTick)
  }
}

class TransformState {
  constructor(stream) {
    this.data = null
    this.afterTransform = afterTransform.bind(stream)
    this.afterFinal = null
  }
}

class Pipeline {
  constructor(src, dst, cb) {
    this.from = src
    this.to = dst
    this.afterPipe = cb
    this.error = null
    this.pipeToFinished = false
  }

  finished() {
    this.pipeToFinished = true
  }

  done(stream, err) {
    if (err) this.error = err

    if (stream === this.to) {
      this.to = null

      if (this.from !== null) {
        if ((this.from._duplexState & READ_DONE) === 0 || !this.pipeToFinished) {
          this.from.destroy(this.error || StreamError.PREMATURE_CLOSE('Writable stream closed'))
        }
        return
      }
    }

    if (stream === this.from) {
      this.from = null

      if (this.to !== null) {
        if ((stream._duplexState & READ_DONE) === 0) {
          this.to.destroy(this.error || StreamError.PREMATURE_CLOSE('Readable stream closed'))
        }
        return
      }
    }

    if (this.afterPipe !== null) this.afterPipe(this.error)
    this.to = this.from = this.afterPipe = null
  }
}

function afterDrain() {
  this.stream._duplexState |= READ_PIPE_DRAINED
  this.updateCallback()
}

function afterFinal(err) {
  const stream = this.stream
  if (err) stream.destroy(err)

  if ((stream._duplexState & DESTROY_STATUS) === 0) {
    stream._duplexState |= WRITE_DONE
    stream.emit('finish')
  }

  if ((stream._duplexState & AUTO_DESTROY) === DONE) {
    stream._duplexState |= DESTROYING
  }

  stream._duplexState &= WRITE_NOT_FINISHING

  // no need to wait the extra tick here, so we short circuit that
  if ((stream._duplexState & WRITE_UPDATING) === 0) {
    this.update()
  } else {
    this.updateNextTick()
  }
}

function afterDestroy(err) {
  const stream = this.stream

  if (!err && !StreamError.isStreamDestroyed(this.error)) err = this.error
  if (err) stream.emit('error', err)

  stream._duplexState |= DESTROYED
  stream.emit('close')

  const rs = stream._readableState
  const ws = stream._writableState

  if (rs !== null && rs.pipeline !== null) {
    rs.pipeline.done(stream, err)
  }

  if (ws !== null) {
    while (ws.drains !== null && ws.drains.length > 0) {
      ws.drains.shift().resolve(false)
    }

    if (ws.pipeline !== null) {
      ws.pipeline.done(stream, err)
    }
  }
}

function afterWrite(err) {
  const stream = this.stream

  if (err) stream.destroy(err)
  stream._duplexState &= WRITE_NOT_ACTIVE

  if (this.drains !== null) tickDrains(this.drains)

  if ((stream._duplexState & WRITE_DRAIN_STATUS) === WRITE_UNDRAINED) {
    stream._duplexState &= WRITE_DRAINED

    if ((stream._duplexState & WRITE_EMIT_DRAIN) === WRITE_EMIT_DRAIN) {
      stream.emit('drain')
    }
  }

  this.updateCallback()
}

function afterRead(err) {
  if (err) this.stream.destroy(err)
  this.stream._duplexState &= READ_NOT_ACTIVE

  if (this.readAhead === false && (this.stream._duplexState & READ_RESUMED) === 0) {
    this.stream._duplexState &= READ_NO_READ_AHEAD
  }

  this.updateCallback()
}

function updateReadNT() {
  if ((this.stream._duplexState & READ_UPDATING) === 0) {
    this.stream._duplexState &= READ_NOT_NEXT_TICK
    this.update()
  }
}

function updateWriteNT() {
  if ((this.stream._duplexState & WRITE_UPDATING) === 0) {
    this.stream._duplexState &= WRITE_NOT_NEXT_TICK
    this.update()
  }
}

function tickDrains(drains) {
  for (let i = 0; i < drains.length; i++) {
    // drains.writes are monotonic, so if one is 0 its always the first one
    if (--drains[i].writes === 0) {
      drains.shift().resolve(true)
      i--
    }
  }
}

function afterOpen(err) {
  const stream = this.stream

  if (err) stream.destroy(err)

  if ((stream._duplexState & DESTROYING) === 0) {
    if ((stream._duplexState & READ_PRIMARY_STATUS) === 0) {
      stream._duplexState |= READ_PRIMARY
    }

    if ((stream._duplexState & WRITE_PRIMARY_STATUS) === 0) {
      stream._duplexState |= WRITE_PRIMARY
    }

    stream.emit('open')
  }

  stream._duplexState &= NOT_ACTIVE

  if (stream._writableState !== null) {
    stream._writableState.updateCallback()
  }

  if (stream._readableState !== null) {
    stream._readableState.updateCallback()
  }
}

function afterTransform(err, data) {
  if (data !== undefined && data !== null) this.push(data)
  this._writableState.afterWrite(err)
}

function newListener(name) {
  if (this._readableState !== null) {
    if (name === 'data') {
      this._duplexState |= READ_EMIT_DATA | READ_RESUMED_READ_AHEAD
      this._readableState.updateNextTick()
    }
    if (name === 'readable') {
      this._duplexState |= READ_EMIT_READABLE
      this._readableState.updateNextTick()
    }
  }

  if (this._writableState !== null) {
    if (name === 'drain') {
      this._duplexState |= WRITE_EMIT_DRAIN
      this._writableState.updateNextTick()
    }
  }
}

class Stream extends EventEmitter {
  constructor(opts) {
    super()

    this._duplexState = 0
    this._readableState = null
    this._writableState = null

    if (opts) {
      if (opts.open) this._open = opts.open
      if (opts.destroy) this._destroy = opts.destroy
      if (opts.predestroy) this._predestroy = opts.predestroy
      if (opts.signal) opts.signal.addEventListener('abort', abort.bind(this))
    }

    this.on('newListener', newListener)
  }

  _open(cb) {
    cb(null)
  }

  _destroy(cb) {
    cb(null)
  }

  _predestroy() {
    // does nothing
  }

  get readable() {
    return this._readableState !== null ? true : undefined
  }

  get writable() {
    return this._writableState !== null ? true : undefined
  }

  get destroyed() {
    return (this._duplexState & DESTROYED) !== 0
  }

  get destroying() {
    return (this._duplexState & DESTROY_STATUS) !== 0
  }

  destroy(err) {
    if ((this._duplexState & DESTROY_STATUS) === 0) {
      if (!err) err = StreamError.STREAM_DESTROYED()
      this._duplexState = (this._duplexState | DESTROYING) & NON_PRIMARY

      if (this._readableState !== null) {
        this._readableState.highWaterMark = 0
        this._readableState.error = err
      }

      if (this._writableState !== null) {
        this._writableState.highWaterMark = 0
        this._writableState.error = err
      }

      this._duplexState |= PREDESTROYING
      this._predestroy()
      this._duplexState &= NOT_PREDESTROYING

      if (this._readableState !== null) {
        this._readableState.updateNextTick()
      }

      if (this._writableState !== null) {
        this._writableState.updateNextTick()
      }
    }
  }
}

class Readable extends Stream {
  constructor(opts) {
    super(opts)

    this._duplexState |= OPENING | WRITE_DONE | READ_READ_AHEAD
    this._readableState = new ReadableState(this, opts)

    if (opts) {
      if (this._readableState.readAhead === false) this._duplexState &= READ_NO_READ_AHEAD
      if (opts.read) this._read = opts.read
      if (opts.eagerOpen) this._readableState.updateNextTick()
      if (opts.encoding) this.setEncoding(opts.encoding)
    }
  }

  static deferred(fn, opts) {
    const out = new PassThrough(opts)

    fn()
      .then((src) => {
        if (src === null) return out.end()
        if (out.destroying) return
        pipeline(src, out, noop)
      })
      .catch((err) => out.destroy(err))

    return out
  }

  setEncoding(encoding) {
    const dec = new TextDecoder(encoding)
    const map = this._readableState.map || echo
    this._readableState.map = mapOrSkip
    return this

    function mapOrSkip(data) {
      const next = dec.push(data)
      return next === '' && (data.byteLength !== 0 || dec.remaining > 0) ? null : map(next)
    }
  }

  _read(cb) {
    cb(null)
  }

  pipe(dest, cb) {
    this._readableState.updateNextTick()
    this._readableState.pipe(dest, cb)
    return dest
  }

  read() {
    this._readableState.updateNextTick()
    return this._readableState.read()
  }

  push(data) {
    this._readableState.updateNextTickIfOpen()
    return this._readableState.push(data)
  }

  unshift(data) {
    this._readableState.updateNextTickIfOpen()
    return this._readableState.unshift(data)
  }

  resume() {
    this._duplexState |= READ_RESUMED_READ_AHEAD
    this._readableState.updateNextTick()
    return this
  }

  pause() {
    this._duplexState &=
      this._readableState.readAhead === false ? READ_PAUSED_NO_READ_AHEAD : READ_PAUSED
    return this
  }

  static _fromAsyncIterator(ite, opts) {
    let destroy

    const rs = new Readable({
      ...opts,
      read(cb) {
        ite.next().then(push).then(cb.bind(null, null)).catch(cb)
      },
      predestroy() {
        destroy = ite.return()
      },
      destroy(cb) {
        if (!destroy) return cb(null)
        destroy.then(cb.bind(null, null)).catch(cb)
      }
    })

    return rs

    function push(data) {
      if (data.done) rs.push(null)
      else rs.push(data.value)
    }
  }

  static from(data, opts) {
    if (isReadStreamx(data)) return data
    if (data[asyncIterator]) return this._fromAsyncIterator(data[asyncIterator](), opts)
    if (!Array.isArray(data)) data = data === undefined ? [] : [data]

    let i = 0
    return new Readable({
      ...opts,
      read(cb) {
        this.push(i === data.length ? null : data[i++])
        cb(null)
      }
    })
  }

  static isBackpressured(rs) {
    return (
      (rs._duplexState & READ_BACKPRESSURE_STATUS) !== 0 ||
      rs._readableState.buffered >= rs._readableState.highWaterMark
    )
  }

  static isPaused(rs) {
    return (rs._duplexState & READ_RESUMED) === 0
  }

  [asyncIterator]() {
    const stream = this

    let error = null
    let promiseResolve = null
    let promiseReject = null

    this.on('error', (err) => {
      error = err
    })
    this.on('readable', onreadable)
    this.on('close', onclose)

    return {
      [asyncIterator]() {
        return this
      },
      next() {
        return new Promise(function (resolve, reject) {
          promiseResolve = resolve
          promiseReject = reject
          const data = stream.read()
          if (data !== null) ondata(data)
          else if ((stream._duplexState & DESTROYED) !== 0) ondata(null)
        })
      },
      return() {
        return destroy(null)
      },
      throw(err) {
        return destroy(err)
      }
    }

    function onreadable() {
      if (promiseResolve !== null) ondata(stream.read())
    }

    function onclose() {
      if (promiseResolve !== null) ondata(null)
    }

    function ondata(data) {
      if (promiseReject === null) return
      if (error) {
        promiseReject(error)
      } else if (data === null && (stream._duplexState & READ_DONE) === 0) {
        promiseReject(StreamError.STREAM_DESTROYED())
      } else {
        promiseResolve({ value: data, done: data === null })
      }
      promiseReject = promiseResolve = null
    }

    function destroy(err) {
      stream.destroy(err)
      return new Promise((resolve, reject) => {
        if (stream._duplexState & DESTROYED) return resolve({ value: undefined, done: true })
        stream.once('close', function () {
          if (err) reject(err)
          else resolve({ value: undefined, done: true })
        })
      })
    }
  }
}

class Writable extends Stream {
  constructor(opts) {
    super(opts)

    this._duplexState |= OPENING | READ_DONE
    this._writableState = new WritableState(this, opts)

    if (opts) {
      if (opts.writev) this._writev = opts.writev
      if (opts.write) this._write = opts.write
      if (opts.final) this._final = opts.final
      if (opts.eagerOpen) this._writableState.updateNextTick()
    }
  }

  cork() {
    this._duplexState |= WRITE_CORKED
  }

  uncork() {
    this._duplexState &= WRITE_NOT_CORKED
    this._writableState.updateNextTick()
  }

  _writev(batch, cb) {
    cb(null)
  }

  _write(data, cb) {
    this._writableState.autoBatch(data, cb)
  }

  _final(cb) {
    cb(null)
  }

  static isBackpressured(ws) {
    return (ws._duplexState & WRITE_BACKPRESSURE_STATUS) !== 0
  }

  static drained(ws) {
    if (ws.destroyed) return Promise.resolve(false)

    const state = ws._writableState
    const pending = isWritev(ws) ? Math.min(1, state.queue.length) : state.queue.length
    const writes = pending + (ws._duplexState & WRITE_WRITING ? 1 : 0)
    if (writes === 0) return Promise.resolve(true)

    if (state.drains === null) state.drains = []
    return new Promise((resolve) => {
      state.drains.push({ writes, resolve })
    })
  }

  write(data) {
    this._writableState.updateNextTick()
    return this._writableState.push(data)
  }

  end(data) {
    this._writableState.updateNextTick()
    this._writableState.end(data)
    return this
  }
}

class Duplex extends Readable {
  // and Writable
  constructor(opts) {
    super(opts)

    this._duplexState = OPENING | (this._duplexState & READ_READ_AHEAD)
    this._writableState = new WritableState(this, opts)

    if (opts) {
      if (opts.writev) this._writev = opts.writev
      if (opts.write) this._write = opts.write
      if (opts.final) this._final = opts.final
    }
  }

  cork() {
    this._duplexState |= WRITE_CORKED
  }

  uncork() {
    this._duplexState &= WRITE_NOT_CORKED
    this._writableState.updateNextTick()
  }

  _writev(batch, cb) {
    cb(null)
  }

  _write(data, cb) {
    this._writableState.autoBatch(data, cb)
  }

  _final(cb) {
    cb(null)
  }

  write(data) {
    this._writableState.updateNextTick()
    return this._writableState.push(data)
  }

  end(data) {
    this._writableState.updateNextTick()
    this._writableState.end(data)
    return this
  }
}

class Transform extends Duplex {
  constructor(opts) {
    super(opts)
    this._transformState = new TransformState(this)

    if (opts) {
      if (opts.transform) this._transform = opts.transform
      if (opts.flush) this._flush = opts.flush
    }
  }

  _write(data, cb) {
    if (this._readableState.buffered >= this._readableState.highWaterMark) {
      this._transformState.data = data
    } else {
      this._transform(data, this._transformState.afterTransform)
    }
  }

  _read(cb) {
    if (this._transformState.data !== null) {
      const data = this._transformState.data
      this._transformState.data = null
      cb(null)
      this._transform(data, this._transformState.afterTransform)
    } else {
      cb(null)
    }
  }

  destroy(err) {
    super.destroy(err)
    if (this._transformState.data !== null) {
      this._transformState.data = null
      this._transformState.afterTransform()
    }
  }

  _transform(data, cb) {
    cb(null, data)
  }

  _flush(cb) {
    cb(null)
  }

  _final(cb) {
    this._transformState.afterFinal = cb
    this._flush(transformAfterFlush.bind(this))
  }
}

class PassThrough extends Transform {}

function transformAfterFlush(err, data) {
  const cb = this._transformState.afterFinal
  if (err) return cb(err)

  if (data !== null && data !== undefined) this.push(data)
  this.push(null)
  cb(null)
}

function pipelinePromise(...streams) {
  return new Promise((resolve, reject) => {
    return pipeline(...streams, (err) => {
      if (err) return reject(err)
      resolve()
    })
  })
}

function pipeline(stream, ...streams) {
  const all = Array.isArray(stream) ? [...stream, ...streams] : [stream, ...streams]
  const done = all.length && typeof all[all.length - 1] === 'function' ? all.pop() : null

  if (all.length < 2) throw StreamError.BAD_ARGUMENT('Pipeline requires at least 2 streams')

  let src = all[0]
  let dest = null
  let error = null

  for (let i = 1; i < all.length; i++) {
    dest = all[i]

    if (isStreamx(src)) {
      src.pipe(dest, onerror)
    } else {
      errorHandle(src, true, i > 1, onerror)
      src.pipe(dest)
    }

    src = dest
  }

  if (done) {
    let fin = false

    const autoDestroy =
      isStreamx(dest) || !!(dest._writableState && dest._writableState.autoDestroy)

    dest.on('error', (err) => {
      if (error === null) error = err
    })

    dest.on('finish', () => {
      fin = true
      if (!autoDestroy) done(error)
    })

    if (autoDestroy) {
      dest.on('close', () => done(error || (fin ? null : StreamError.PREMATURE_CLOSE())))
    }
  }

  return dest

  function errorHandle(s, rd, wr, onerror) {
    s.on('error', onerror)
    s.on('close', onclose)

    function onclose() {
      if (rd && s._readableState && !s._readableState.ended) {
        return onerror(StreamError.PREMATURE_CLOSE())
      }
      if (wr && s._writableState && !s._writableState.ended) {
        return onerror(StreamError.PREMATURE_CLOSE())
      }
    }
  }

  function onerror(err) {
    if (!err || error) return
    error = err

    for (const s of all) {
      s.destroy(err)
    }
  }
}

function echo(s) {
  return s
}

function isStream(stream) {
  return !!stream._readableState || !!stream._writableState
}

function isStreamx(stream) {
  return typeof stream._duplexState === 'number' && isStream(stream)
}

function isEnding(stream) {
  return !!stream._readableState && stream._readableState.ending
}

function isEnded(stream) {
  return !!stream._readableState && stream._readableState.ended
}

function isFinishing(stream) {
  return !!stream._writableState && stream._writableState.ending
}

function isFinished(stream) {
  return !!stream._writableState && stream._writableState.ended
}

function getStreamError(stream, opts = {}) {
  const err =
    (stream._readableState && stream._readableState.error) ||
    (stream._writableState && stream._writableState.error)

  // avoid implicit errors by default
  return !opts.all && StreamError.isStreamDestroyed(err) ? null : err
}

function isReadStreamx(stream) {
  return isStreamx(stream) && stream.readable
}

function isDisturbed(stream) {
  return (
    (stream._duplexState & OPENING) !== OPENING ||
    (stream._duplexState & DESTROYING) === DESTROYING ||
    (stream._duplexState & ACTIVE_OR_TICKING) !== 0
  )
}

function isTypedArray(data) {
  return typeof data === 'object' && data !== null && typeof data.byteLength === 'number'
}

function defaultByteLength(data) {
  return isTypedArray(data) ? data.byteLength : 1024
}

function noop() {}

function abort() {
  this.destroy(StreamError.ABORTED())
}

function isWritev(s) {
  return s._writev !== Writable.prototype._writev && s._writev !== Duplex.prototype._writev
}

module.exports = {
  pipeline,
  pipelinePromise,
  isStream,
  isStreamx,
  isEnding,
  isEnded,
  isFinishing,
  isFinished,
  isDisturbed,
  getStreamError,
  Stream,
  Writable,
  Readable,
  Duplex,
  Transform,
  // Export PassThrough for compatibility with Node.js core's stream module
  PassThrough
}
module.exports = class StreamError extends Error {
  constructor(msg, code, fn = StreamError) {
    super(msg)

    this.code = code

    if (Error.captureStackTrace) {
      Error.captureStackTrace(this, fn)
    }
  }

  static isStreamDestroyed(err) {
    return err && err.code === 'STREAM_DESTROYED'
  }

  static isPrematureClose(err) {
    return err && err.code === 'PREMATURE_CLOSE'
  }

  static isAborted(err) {
    return err && err.code === 'ABORTED'
  }

  static isBadArgument(err) {
    return err && err.code === 'BAD_ARGUMENT'
  }

  get name() {
    return 'StreamError'
  }

  static STREAM_DESTROYED() {
    return new StreamError('Stream was destroyed', 'STREAM_DESTROYED', StreamError.STREAM_DESTROYED)
  }

  static PREMATURE_CLOSE(msg = 'Premature close') {
    return new StreamError(msg, 'PREMATURE_CLOSE', StreamError.PREMATURE_CLOSE)
  }

  static ABORTED() {
    return new StreamError('Stream aborted', 'ABORTED', StreamError.ABORTED)
  }

  static BAD_ARGUMENT(msg = 'Bad argument') {
    return new StreamError(msg, 'BAD_ARGUMENT', StreamError.BAD_ARGUMENT)
  }
}
{
  "name": "streamx",
  "version": "2.28.1",
  "description": "An iteration of the Node.js core streams with a series of improvements",
  "main": "index.js",
  "exports": {
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    },
    "./package": "./package.json",
    "./errors": {
      "types": "./lib/errors.d.ts",
      "default": "./lib/errors"
    }
  },
  "dependencies": {
    "events-universal": "^1.0.0",
    "fast-fifo": "^1.3.2",
    "text-decoder": "^1.1.0"
  },
  "devDependencies": {
    "b4a": "^1.6.6",
    "brittle": "^3.1.1",
    "end-of-stream": "^1.4.4",
    "lunte": "^1.8.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^2.0.0"
  },
  "files": [
    "index.js",
    "index.d.ts",
    "runtime.d.ts",
    "lib"
  ],
  "scripts": {
    "format": "prettier . --write",
    "test": "brittle test/all.js && brittle-bare test/all.js",
    "lint": "prettier . --check && lunte"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/mafintosh/streamx.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/mafintosh/streamx/issues"
  },
  "homepage": "https://github.com/mafintosh/streamx"
}
const { Readable } = require('streamx')

module.exports = function (s, forks = 2) {
  const streams = new Array(forks)
  const status = new Array(forks).fill(true)

  let ended = false

  for (let i = 0; i < forks; i++) {
    streams[i] = new Readable({
      read (cb) {
        const check = !status[i]
        status[i] = true
        if (check && allReadable()) s.resume()
        cb(null)
      }
    })
  }

  s.on('end', function () {
    ended = true
    for (const stream of streams) stream.push(null)
  })

  s.on('error', function (err) {
    for (const stream of streams) stream.destroy(err)
  })

  s.on('close', function () {
    if (ended) return
    for (const stream of streams) stream.destroy()
  })

  s.on('data', function (data) {
    let needsPause = false
    for (let i = 0; i < streams.length; i++) {
      if (!(status[i] = streams[i].push(data))) {
        needsPause = true
      }
    }
    if (needsPause) s.pause()
  })

  return streams

  function allReadable () {
    for (let j = 0; j < status.length; j++) {
      if (!status[j]) return false
    }
    return true
  }
}
{
  "name": "teex",
  "version": "1.0.1",
  "description": "Turn a readable stream into multiple readable streamx streams",
  "main": "index.js",
  "dependencies": {
    "streamx": "^2.12.5"
  },
  "devDependencies": {
    "standard": "^14.3.4",
    "tape": "^5.0.1"
  },
  "scripts": {
    "test": "standard && tape test.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/mafintosh/teex.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/mafintosh/teex/issues"
  },
  "homepage": "https://github.com/mafintosh/teex"
}
const PassThroughDecoder = require('./lib/pass-through-decoder')
const UTF8Decoder = require('./lib/utf8-decoder')

module.exports = class TextDecoder {
  constructor(encoding = 'utf8') {
    this.encoding = normalizeEncoding(encoding)

    switch (this.encoding) {
      case 'utf8':
        this.decoder = new UTF8Decoder()
        break
      case 'utf16le':
      case 'base64':
        throw new Error('Unsupported encoding: ' + this.encoding)
      default:
        this.decoder = new PassThroughDecoder(this.encoding)
    }
  }

  get remaining() {
    return this.decoder.remaining
  }

  push(data) {
    if (typeof data === 'string') return data
    return this.decoder.decode(data)
  }

  // For Node.js compatibility
  write(data) {
    return this.push(data)
  }

  end(data) {
    let result = ''
    if (data) result = this.push(data)
    result += this.decoder.flush()
    return result
  }
}

function normalizeEncoding(encoding) {
  encoding = encoding.toLowerCase()

  switch (encoding) {
    case 'utf8':
    case 'utf-8':
      return 'utf8'
    case 'ucs2':
    case 'ucs-2':
    case 'utf16le':
    case 'utf-16le':
      return 'utf16le'
    case 'latin1':
    case 'binary':
      return 'latin1'
    case 'base64':
    case 'ascii':
    case 'hex':
      return encoding
    default:
      throw new Error('Unknown encoding: ' + encoding)
  }
}
const b4a = require('b4a')

module.exports = class PassThroughDecoder {
  constructor(encoding) {
    this.encoding = encoding
  }

  get remaining() {
    return 0
  }

  decode(data) {
    return b4a.toString(data, this.encoding)
  }

  flush() {
    return ''
  }
}
const b4a = require('b4a')

/**
 * https://encoding.spec.whatwg.org/#utf-8-decoder
 */
module.exports = class UTF8Decoder {
  constructor() {
    this._reset()
  }

  get remaining() {
    return this.bytesSeen
  }

  decode(data) {
    if (data.byteLength === 0) return ''

    if (this.bytesNeeded === 0 && trailingIncomplete(data, 0) === 0) {
      this.bytesSeen = trailingBytesSeen(data)
      return b4a.toString(data, 'utf8')
    }

    let result = ''
    let start = 0

    if (this.bytesNeeded > 0) {
      while (start < data.byteLength) {
        const byte = data[start]

        if (byte < this.lowerBoundary || byte > this.upperBoundary) {
          result += '\ufffd'
          this._reset()
          break
        }

        this.lowerBoundary = 0x80
        this.upperBoundary = 0xbf
        this.codePoint = (this.codePoint << 6) | (byte & 0x3f)
        this.bytesSeen++
        start++

        if (this.bytesSeen === this.bytesNeeded) {
          result += String.fromCodePoint(this.codePoint)
          this._reset()
          break
        }
      }

      if (this.bytesNeeded > 0) return result
    }

    const trailing = trailingIncomplete(data, start)
    const end = data.byteLength - trailing

    if (end > start) result += b4a.toString(data, 'utf8', start, end)

    for (let i = end; i < data.byteLength; i++) {
      const byte = data[i]

      if (this.bytesNeeded === 0) {
        if (byte <= 0x7f) {
          this.bytesSeen = 0
          result += String.fromCharCode(byte)
        } else if (byte >= 0xc2 && byte <= 0xdf) {
          this.bytesNeeded = 2
          this.bytesSeen = 1
          this.codePoint = byte & 0x1f
        } else if (byte >= 0xe0 && byte <= 0xef) {
          if (byte === 0xe0) this.lowerBoundary = 0xa0
          else if (byte === 0xed) this.upperBoundary = 0x9f
          this.bytesNeeded = 3
          this.bytesSeen = 1
          this.codePoint = byte & 0xf
        } else if (byte >= 0xf0 && byte <= 0xf4) {
          if (byte === 0xf0) this.lowerBoundary = 0x90
          else if (byte === 0xf4) this.upperBoundary = 0x8f
          this.bytesNeeded = 4
          this.bytesSeen = 1
          this.codePoint = byte & 0x7
        } else {
          this.bytesSeen = 1
          result += '\ufffd'
        }

        continue
      }

      if (byte < this.lowerBoundary || byte > this.upperBoundary) {
        result += '\ufffd'
        i--
        this._reset()

        continue
      }

      this.lowerBoundary = 0x80
      this.upperBoundary = 0xbf

      this.codePoint = (this.codePoint << 6) | (byte & 0x3f)
      this.bytesSeen++

      if (this.bytesSeen === this.bytesNeeded) {
        result += String.fromCodePoint(this.codePoint)
        this._reset()
      }
    }

    return result
  }

  flush() {
    const result = this.bytesNeeded > 0 ? '\ufffd' : ''
    this._reset()
    return result
  }

  _reset() {
    this.codePoint = 0
    this.bytesNeeded = 0
    this.bytesSeen = 0
    this.lowerBoundary = 0x80
    this.upperBoundary = 0xbf
  }
}

function trailingIncomplete(data, start) {
  const len = data.byteLength
  if (len <= start) return 0

  const limit = Math.max(start, len - 4)

  let i = len - 1
  while (i > limit && (data[i] & 0xc0) === 0x80) i--

  if (i < start) return 0

  const byte = data[i]

  let needed
  if (byte <= 0x7f) return 0
  if (byte >= 0xc2 && byte <= 0xdf) needed = 2
  else if (byte >= 0xe0 && byte <= 0xef) needed = 3
  else if (byte >= 0xf0 && byte <= 0xf4) needed = 4
  else return 0

  const available = len - i
  return available < needed ? available : 0
}

function trailingBytesSeen(data) {
  const len = data.byteLength
  if (len === 0) return 0

  const last = data[len - 1]

  if (last <= 0x7f) return 0
  if ((last & 0xc0) !== 0x80) return 1

  const limit = Math.max(0, len - 4)

  let i = len - 2
  while (i >= limit && (data[i] & 0xc0) === 0x80) i--

  if (i < 0) return 1

  const first = data[i]

  let needed
  if (first >= 0xc2 && first <= 0xdf) needed = 2
  else if (first >= 0xe0 && first <= 0xef) needed = 3
  else if (first >= 0xf0 && first <= 0xf4) needed = 4
  else return 1

  if (len - i !== needed) return 1

  if (needed >= 3) {
    const second = data[i + 1]
    if (first === 0xe0 && second < 0xa0) return 1
    if (first === 0xed && second > 0x9f) return 1
    if (first === 0xf0 && second < 0x90) return 1
    if (first === 0xf4 && second > 0x8f) return 1
  }

  return 0
}
{
  "name": "text-decoder",
  "version": "1.2.7",
  "description": "Streaming text decoder that preserves multibyte Unicode characters",
  "exports": {
    "./package": "./package.json",
    ".": "./index.js"
  },
  "files": [
    "index.js",
    "lib"
  ],
  "scripts": {
    "format": "prettier --write .",
    "lint": "prettier --check . && lunte",
    "test": "npm run test:node && npm run test:bare",
    "test:node": "brittle-node test.js",
    "test:bare": "brittle-bare test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/text-decoder.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/text-decoder/issues"
  },
  "homepage": "https://github.com/holepunchto/text-decoder#readme",
  "dependencies": {
    "b4a": "^1.6.4"
  },
  "devDependencies": {
    "brittle": "^3.3.2",
    "lunte": "^1.3.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^1.0.0"
  }
}
module.exports = class TimerBrowser {
  constructor (ms, fn, ctx = null, interval = false) {
    this.ms = ms
    this.ontimeout = fn
    this.context = ctx || null
    this.interval = interval
    this.done = false

    this._timer = interval
      ? setInterval(callInterval, ms, this)
      : setTimeout(callTimeout, ms, this)
  }

  unref () {}

  ref () {}

  refresh () {
    if (this.done) return

    if (this.interval) {
      clearInterval(this._timer)
      this._timer = setInterval(callInterval, this.ms, this)
    } else {
      clearTimeout(this._timer)
      this._timer = setTimeout(callTimeout, this.ms, this)
    }
  }

  destroy () {
    this.done = true
    this.ontimeout = null

    if (this.interval) clearInterval(this._timer)
    else clearTimeout(this._timer)
  }

  static once (ms, fn, ctx) {
    return new this(ms, fn, ctx, false)
  }

  static on (ms, fn, ctx) {
    return new this(ms, fn, ctx, true)
  }
}

function callTimeout (self) {
  self.done = true
  self.ontimeout.call(self.context)
}

function callInterval (self) {
  self.ontimeout.call(self.context)
}
module.exports = isNode()
  ? require('./node')
  : require('./browser')

function isNode () {
  const to = setTimeout(function () {}, 1000)
  clearTimeout(to)
  return !!to.refresh
}
module.exports = class Timer {
  constructor (ms, fn, ctx = null, interval = false) {
    this.ms = ms
    this.ontimeout = fn
    this.context = ctx
    this.interval = interval
    this.done = false

    this._timer = interval
      ? setInterval(callInterval, ms, this)
      : setTimeout(callTimeout, ms, this)
  }

  unref () {
    this._timer.unref()
  }

  ref () {
    this._timer.ref()
  }

  refresh () {
    if (this.done !== true) this._timer.refresh()
  }

  destroy () {
    this.done = true
    this.ontimeout = null
    if (this.interval) clearInterval(this._timer)
    else clearTimeout(this._timer)
  }

  static once (ms, fn, ctx) {
    return new this(ms, fn, ctx, false)
  }

  static on (ms, fn, ctx) {
    return new this(ms, fn, ctx, true)
  }
}

function callTimeout (self) {
  self.done = true
  self.ontimeout.call(self.context)
}

function callInterval (self) {
  self.ontimeout.call(self.context)
}
{
  "name": "timeout-refresh",
  "version": "2.0.1",
  "description": "Efficiently refresh a timer",
  "main": "index.js",
  "dependencies": {},
  "devDependencies": {
    "standard": "^16.0.4",
    "tape": "^5.5.2"
  },
  "browser": "./browser.js",
  "scripts": {
    "test": "standard && tape test.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/mafintosh/timeout-refresh.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/mafintosh/timeout-refresh/issues"
  },
  "homepage": "https://github.com/mafintosh/timeout-refresh"
}
const b4a = require('b4a')

unslab.all = all
unslab.is = is

module.exports = unslab

function unslab (buf) {
  if (buf === null || buf.buffer.byteLength === buf.byteLength) return buf
  const copy = b4a.allocUnsafeSlow(buf.byteLength)
  copy.set(buf, 0)
  return copy
}

function is (buf) {
  return buf.buffer.byteLength !== buf.byteLength
}

function all (list) {
  let size = 0
  for (let i = 0; i < list.length; i++) {
    const buf = list[i]
    size += buf === null || buf.buffer.byteLength === buf.byteLength ? 0 : buf.byteLength
  }

  const copy = b4a.allocUnsafeSlow(size)
  const result = new Array(list.length)

  let offset = 0
  for (let i = 0; i < list.length; i++) {
    let buf = list[i]

    if (buf !== null && buf.buffer.byteLength !== buf.byteLength) {
      copy.set(buf, offset)
      buf = copy.subarray(offset, offset += buf.byteLength)
    }

    result[i] = buf
  }

  return result
}
{
  "name": "unslab",
  "version": "1.3.0",
  "description": "Unslab some slab'ed buffers",
  "main": "index.js",
  "files": [
    "index.js"
  ],
  "dependencies": {
    "b4a": "^1.6.6"
  },
  "devDependencies": {
    "brittle": "^3.5.2",
    "standard": "^17.1.0"
  },
  "scripts": {
    "test": "standard && brittle test.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/holepunchto/unslab.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/unslab/issues"
  },
  "homepage": "https://github.com/holepunchto/unslab"
}
module.exports = read

var MSB = 0x80
  , REST = 0x7F

function read(buf, offset) {
  var res    = 0
    , offset = offset || 0
    , shift  = 0
    , counter = offset
    , b
    , l = buf.length

  do {
    if (counter >= l) {
      read.bytes = 0
      throw new RangeError('Could not decode varint')
    }
    b = buf[counter++]
    res += shift < 28
      ? (b & REST) << shift
      : (b & REST) * Math.pow(2, shift)
    shift += 7
  } while (b >= MSB)

  read.bytes = counter - offset

  return res
}
module.exports = encode

var MSB = 0x80
  , REST = 0x7F
  , MSBALL = ~REST
  , INT = Math.pow(2, 31)

function encode(num, out, offset) {
  out = out || []
  offset = offset || 0
  var oldOffset = offset

  while(num >= INT) {
    out[offset++] = (num & 0xFF) | MSB
    num /= 128
  }
  while(num & MSBALL) {
    out[offset++] = (num & 0xFF) | MSB
    num >>>= 7
  }
  out[offset] = num | 0
  
  encode.bytes = offset - oldOffset + 1
  
  return out
}
module.exports = {
    encode: require('./encode.js')
  , decode: require('./decode.js')
  , encodingLength: require('./length.js')
}

var N1 = Math.pow(2,  7)
var N2 = Math.pow(2, 14)
var N3 = Math.pow(2, 21)
var N4 = Math.pow(2, 28)
var N5 = Math.pow(2, 35)
var N6 = Math.pow(2, 42)
var N7 = Math.pow(2, 49)
var N8 = Math.pow(2, 56)
var N9 = Math.pow(2, 63)

module.exports = function (value) {
  return (
    value < N1 ? 1
  : value < N2 ? 2
  : value < N3 ? 3
  : value < N4 ? 4
  : value < N5 ? 5
  : value < N6 ? 6
  : value < N7 ? 7
  : value < N8 ? 8
  : value < N9 ? 9
  :              10
  )
}
{
  "name": "varint",
  "version": "5.0.0",
  "description": "protobuf-style varint bytes - use msb to create integer values of varying sizes",
  "main": "index.js",
  "scripts": {
    "test": "node test.js"
  },
  "repository": {
    "type": "git",
    "url": "git://github.com/chrisdickinson/varint.git"
  },
  "keywords": [
    "varint",
    "protobuf",
    "encode",
    "decode"
  ],
  "author": "Chris Dickinson <chris@neversaw.us>",
  "license": "MIT",
  "devDependencies": {
    "tape": "~2.12.3"
  }
}
const { runtime, platform, arch, version } =
  typeof Bare !== 'undefined'
    ? {
        runtime: 'bare',
        platform: global.Bare.platform,
        arch: global.Bare.arch,
        version: global.Bare.version
      }
    : typeof process !== 'undefined'
      ? {
          runtime: 'node',
          platform: global.process.platform,
          arch: global.process.arch,
          version: global.process.version
        }
      : typeof Window !== 'undefined'
        ? { runtime: 'browser', platform: 'unknown', arch: 'unknown', version: 'unknown' }
        : { runtime: 'unknown', platform: 'unknown', arch: 'unknown', version: 'unknown' }

exports.runtime = runtime
exports.version = version
exports.platform = platform
exports.arch = arch
exports.isBare = runtime === 'bare'
exports.isBareKit = exports.isBare && typeof BareKit !== 'undefined'
exports.isPear = typeof Pear !== 'undefined'
exports.isNode = runtime === 'node'
exports.isBrowser = runtime === 'browser'
exports.isWindows = platform === 'win32'
exports.isLinux = platform === 'linux'
exports.isMac = platform === 'darwin'
exports.isIOS = platform === 'ios' || platform === 'ios-simulator'
exports.isAndroid = platform === 'android'
exports.isElectron = typeof process !== 'undefined' && !!global.process.versions?.electron
exports.isElectronRenderer = exports.isElectron && global.process.type === 'renderer'
exports.isElectronWorker = exports.isElectron && global.process.type === 'worker'
{
  "name": "which-runtime",
  "version": "1.4.0",
  "description": "Detect if you are in Bare or Node and which os etc",
  "main": "index.js",
  "files": [
    "index.js"
  ],
  "scripts": {
    "format": "prettier --write .",
    "lint": "prettier --check . && lunte .",
    "test": "npm run test:node && npm run test:bare",
    "test:node": "brittle-node test.js",
    "test:bare": "brittle-bare test.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/holepunchto/which-runtime.git"
  },
  "author": "Holepunch Inc.",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/which-runtime/issues"
  },
  "homepage": "https://github.com/holepunchto/which-runtime",
  "devDependencies": {
    "brittle": "^3.0.0",
    "lunte": "^1.4.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^2.0.0"
  }
}
module.exports = class MaxCache {
  constructor({ maxSize, maxAge, createMap, ongc }) {
    this.maxSize = maxSize
    this.maxAge = maxAge
    this.ongc = ongc || null

    this._createMap = createMap || defaultCreateMap
    this._latest = this._createMap()
    this._oldest = this._createMap()
    this._retained = this._createMap()
    this._gced = false
    this._interval = null

    if (this.maxAge > 0 && this.maxAge < Infinity) {
      const tick = Math.ceil((2 / 3) * this.maxAge)
      this._interval = setInterval(this._gcAuto.bind(this), tick)
      if (this._interval.unref) this._interval.unref()
    }
  }

  get size() {
    return this._latest.size + this._oldest.size + this._retained.size
  }

  *[Symbol.iterator]() {
    for (const it of [this._latest, this._oldest, this._retained]) {
      yield* it
    }
  }

  *keys() {
    for (const it of [this._latest, this._oldest, this._retained]) {
      yield* it.keys()
    }
  }

  *values() {
    for (const it of [this._latest, this._oldest, this._retained]) {
      yield* it.values()
    }
  }

  destroy() {
    this.clear()
    clearInterval(this._interval)
    this._interval = null
  }

  clear() {
    this._gced = true
    this._latest.clear()
    this._oldest.clear()
    this._retained.clear()
  }

  set(k, v) {
    if (this._retained.has(k)) return this
    this._latest.set(k, v)
    this._oldest.delete(k) || this._retained.delete(k)
    if (this._latest.size >= this.maxSize) this._gc()
    return this
  }

  retain(k, v) {
    this._retained.set(k, v)
    this._latest.delete(k) || this._oldest.delete(k)
    return this
  }

  delete(k) {
    return (
      this._latest.delete(k) ||
      this._oldest.delete(k) ||
      this._retained.delete(k)
    )
  }

  has(k) {
    return this._latest.has(k) || this._oldest.has(k) || this._retained.has(k)
  }

  get(k) {
    if (this._latest.has(k)) {
      return this._latest.get(k)
    }

    if (this._oldest.has(k)) {
      const v = this._oldest.get(k)
      this._latest.set(k, v)
      this._oldest.delete(k)
      return v
    }

    if (this._retained.has(k)) {
      return this._retained.get(k)
    }

    return null
  }

  _gcAuto() {
    if (!this._gced) this._gc()
    this._gced = false
  }

  _gc() {
    this._gced = true
    if (this.ongc !== null && this._oldest.size > 0) this.ongc(this._oldest)
    this._oldest = this._latest
    this._latest = this._createMap()
  }
}

function defaultCreateMap() {
  return new Map()
}
{
  "name": "xache",
  "version": "1.3.0",
  "description": "Yet another auto expiring, max sizable cache",
  "main": "index.js",
  "files": [
    "index.js"
  ],
  "devDependencies": {
    "brittle": "^3.3.2",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^1.0.0"
  },
  "scripts": {
    "format": "prettier --write .",
    "test": "prettier --check . && brittle test.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/mafintosh/xache.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/mafintosh/xache/issues"
  },
  "homepage": "https://github.com/mafintosh/xache"
}
const b4a = require('b4a')

const ALPHABET = 'ybndrfg8ejkmcpqxot1uwisza345h769'
const MIN = 0x31 // 1
const MAX = 0x7a // z
const REVERSE = new Int8Array(1 + MAX - MIN)

REVERSE.fill(-1)

for (let i = 0; i < ALPHABET.length; i++) {
  const v = ALPHABET.charCodeAt(i) - MIN
  REVERSE[v] = i
}

exports.encode = encode
exports.decode = decode
exports.ALPHABET = ALPHABET

function decode (s, out) {
  let pb = 0
  let ps = 0

  const r = s.length & 7
  const q = (s.length - r) / 8

  if (!out) out = b4a.allocUnsafe(Math.ceil(s.length * 5 / 8))

  // 0 5 2 7 4 1 6 3 (+5 mod 8)
  for (let i = 0; i < q; i++) {
    const a = quintet(s, ps++)
    const b = quintet(s, ps++)
    const c = quintet(s, ps++)
    const d = quintet(s, ps++)
    const e = quintet(s, ps++)
    const f = quintet(s, ps++)
    const g = quintet(s, ps++)
    const h = quintet(s, ps++)

    out[pb++] = (a << 3) | (b >>> 2)
    out[pb++] = ((b & 0b11) << 6) | (c << 1) | (d >>> 4)
    out[pb++] = ((d & 0b1111) << 4) | (e >>> 1)
    out[pb++] = ((e & 0b1) << 7) | (f << 2) | (g >>> 3)
    out[pb++] = ((g & 0b111) << 5) | h
  }

  if (r === 0) return out.subarray(0, pb)

  const a = quintet(s, ps++)
  const b = quintet(s, ps++)

  out[pb++] = (a << 3) | (b >>> 2)

  if (r <= 2) return out.subarray(0, pb)

  const c = quintet(s, ps++)
  const d = quintet(s, ps++)

  out[pb++] = ((b & 0b11) << 6) | (c << 1) | (d >>> 4)

  if (r <= 4) return out.subarray(0, pb)

  const e = quintet(s, ps++)

  out[pb++] = ((d & 0b1111) << 4) | (e >>> 1)

  if (r <= 5) return out.subarray(0, pb)

  const f = quintet(s, ps++)
  const g = quintet(s, ps++)

  out[pb++] = ((e & 0b1) << 7) | (f << 2) | (g >>> 3)

  if (r <= 7) return out.subarray(0, pb)

  const h = quintet(s, ps++)

  out[pb++] = ((g & 0b111) << 5) | h

  return out.subarray(0, pb)
}

function encode (buf) {
  if (typeof buf === 'string') buf = b4a.from(buf)

  const max = buf.byteLength * 8

  let s = ''

  for (let p = 0; p < max; p += 5) {
    const i = p >>> 3
    const j = p & 7

    if (j <= 3) {
      s += ALPHABET[(buf[i] >>> (3 - j)) & 0b11111]
      continue
    }

    const of = j - 3
    const h = (buf[i] << of) & 0b11111
    const l = (i >= buf.byteLength ? 0 : buf[i + 1]) >>> (8 - of)

    s += ALPHABET[h | l]
  }

  return s
}

function quintet (s, i) {
  if (i > s.length) {
    return 0
  }

  const v = s.charCodeAt(i)

  if (v < MIN || v > MAX) {
    throw Error('Invalid character in base32 input: "' + s[i] + '" at position ' + i)
  }

  const bits = REVERSE[v - MIN]

  if (bits === -1) {
    throw Error('Invalid character in base32 input: "' + s[i] + '" at position ' + i)
  }

  return bits
}
{
  "name": "z32",
  "version": "1.1.0",
  "description": "Encode & decode z-base32",
  "main": "index.js",
  "dependencies": {
    "b4a": "^1.5.3"
  },
  "devDependencies": {
    "base-x": "^4.0.0",
    "base32": "0.0.7",
    "brittle": "^3.1.3",
    "nanobench": "^3.0.0",
    "rfc4648": "^1.5.2",
    "standard": "^17.0.0"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/mafintosh/z32.git"
  },
  "scripts": {
    "test": "standard && brittle test.js",
    "bench": "node benchmark.js"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/mafintosh/z32/issues"
  },
  "homepage": "https://github.com/mafintosh/z32"
}
{
  "name": "hyper-identity2",
  "version": "0.1.0",
  "description": "The identity layer on autobee. Sibling of hyper-identity (autobase); the shared half is hyper-identity-core.",
  "type": "module",
  "main": "index.js",
  "exports": {
    ".": "./index.js",
    "./registry": "./lib/registry.js",
    "./view-shim": "./lib/view-shim.js"
  },
  "scripts": {
    "test": "brittle test/*.test.js",
    "test:bare": "bare ./node_modules/.bin/brittle test/*.test.js"
  },
  "dependencies": {
    "autobee": "^2.1.0",
    "b4a": "^1.6.4",
    "hyper-identity-core": "file:../hyper-identity-core",
    "hypercore-crypto": "^3.0.0"
  },
  "devDependencies": {
    "brittle": "^4.0.2",
    "corestore": "^7.12.3"
  }
}/**
 * hyper-overlay — content-addressed P2P file distribution over Protomux.
 *
 * The canonical bytes are REAL FILES ON DISK; the overlay stores only
 * metadata + chunk maps (Hyperbee), so there is no blob-storage balloon.
 * Files are chunked with FastCDC, fetched multi-source (torrent-style),
 * hash-verified incrementally, and written straight to the requester's disk.
 *
 * Main entry point: `HyperOverlay` (also the default export). The submodules
 * are available as subpath exports (`hyper-overlay/chunker`, …) for consumers
 * that only need a slice of the stack.
 *
 * Runs on Bare. The pure modules — chunker, messages, chunk-scheduler,
 * file-index, sync-engine — also run on Node.
 *
 * 0.3.0 adopts the hardened engine from ok/hyper-overlay-v3 (journaled resume,
 * wire paging, index GC, prepareForServe) while KEEPING our v2 architecture on
 * top: our single-flight backpressure discipline in the serve loop, our
 * getIndexMeta/putIndexMeta fast-boot, and the tree/pointer/ignore/manifest
 * modules Ok's fork dropped (pgh / hyper-deploy / pear-git-v3 depend on them).
 */

export { HyperOverlay, HyperOverlay as default } from './lib/overlay.js'
export { OverlayProtocol } from './lib/protocol.js'
export {
  TransferManager,
  PARTIAL_SUFFIX,
  partialPathFor,
  journalNameFor,
  cleanupOrphanedJournals
} from './lib/transfer.js'
export { ChunkScheduler } from './lib/chunk-scheduler.js'
export { FileIndex, CONTENT_REGISTER_PREFIX } from './lib/file-index.js'
export { SyncEngine, STRATEGIES } from './lib/sync-engine.js'
export {
  chunk,
  chunkStream,
  hashChunk,
  createStreamingHasher,
  selectTier,
  getTierParams,
  TIERS,
  MIN_SIZE,
  AVG_SIZE,
  MAX_SIZE
} from './lib/chunker.js'
export * as messages from './lib/messages.js'

// Our kept superset — dropped by ok/hyper-overlay-v3, retained here because
// pgh (tree/pointer/ignore), hyper-deploy (tree), and the forge (manifest)
// import them via subpaths. Not application concerns; they are the
// directory→Merkle-manifest + LFS-pointer primitives the distribution layer needs.
export { serialize, parse, isPointer, INLINE_MAX } from './lib/pointer.js'
export { encode as encodeManifest, decode as decodeManifest } from './lib/manifest.js'
export { createMatcher } from './lib/ignore.js'
export * as tree from './lib/tree.js'
/**
 * ChunkScheduler — multi-source (torrent-style) fetch of one content-addressed
 * file across several peers, modelled on Hypercore's block replicator.
 *
 * Chunks are content-addressed, so any peer holding the file can serve any
 * chunk. The scheduler:
 *  - requests the chunk list from each peer (identical, content-addressed),
 *  - tracks a global `inflight` map so each chunk is requested from exactly
 *  ONE peer at a time (no wasted bandwidth),
 *  - keeps each peer busy up to a per-peer inflight cap (parallel download),
 *  - reassigns a peer's inflight chunks to others on stall/disconnect,
 *  - hash-verifies every chunk (via TransferManager.writeChunk) and the whole
 *  file before finalizing.
 *
 * It drives the EXISTING v2 wire messages (chunk-need / chunk-data) — no new
 * message type is needed for the full-copy-seeder case. The protocol delegates
 * `_onChunkHashes` / `_onChunkData` to a scheduler ONLY when one is registered
 * for the synthetic `content:<hash>` path, so the legacy single-peer flow
 * is untouched.
 */

import { hashChunk } from './chunker.js'

// P2 — per-peer inflight request window, modelled on hypercore's replicator
// Peer.getMaxInflight (holepunchto/hypercore lib/replicator.js:590). Our content
// chunks are ~64 KB (tier 0 avg 16 KB … tier 3 max 4 MB) — much larger than
// hypercore's blocks — so the range is tuned DOWN from its [16, 512]. A no-udx
// stream (in-memory pair / relay, no RTT signal) gets a modest fixed min*3; a udx
// stream scales the window with RTT and shrinks it as more peers join (fair share).
const INFLIGHT_RANGE = [4, 64]
const SCALE_LATENCY = 50   // ms — at/under this RTT the window stays at min
// Head-of-line fairness — cede the event loop every YIELD_EVERY accepted chunks
// so a burst of buffered chunkData frames (protomux dispatches every frame from
// one socket read synchronously) can't starve other listeners multiplexed on the
// same connection (e.g. Hypercore replication). Kept local (not imported from
// transfer.js) so this module stays Node-loadable for the unit suite.
const yieldToLoop = () => new Promise((resolve) => setTimeout(resolve, 0))
const YIELD_EVERY = 16
// Idle (no-progress) timeout, reset on every accepted chunk and on the
// initial chunk-list response. A steadily-progressing transfer never trips it —
// only a genuine stall (no bytes for this long) fails. A fixed OVERALL timeout
// here capped large transfers (e.g. a multi-GB file at any bandwidth always
// exceeded 30s) and aborted them mid-stream.
const DEFAULT_IDLE_TIMEOUT = 30000
// Local write-error codes that may recover on retry (vs ENOSPC/EACCES/…
// which are fatal): a transient one keeps the chunk retryable instead of failing
// the whole multi-source fetch.
const TRANSIENT_WRITE_CODES = new Set(['EBUSY', 'EAGAIN', 'EINTR', 'EMFILE', 'ENFILE'])
// Min interval between have-progress reports to holders (their sender-side bar).
// Throttles the transferProgress frames so a fast transfer can't spam holders.
const DEFAULT_REPORT_INTERVAL = 1000

export class ChunkScheduler {
  /**
   * @param {object} opts
   *  opts.path synthetic path key ('content:<hash>')
   *  opts.destPath where the assembled file is written
   *  opts.transfer TransferManager
   *  opts.sendNeed (peer, indices[]) => void — sends a chunk-need message
   *  opts.cap per-peer inflight cap (default 8)
   *  opts.timeout idle (no-progress) timeout ms (default 30000)
   *  opts.onProgress optional (receivedBytes, totalBytes) => void on each accepted chunk
   */
  constructor (opts) {
    this.path = opts.path
    this.destPath = opts.destPath
    this._transfer = opts.transfer
    this._sendNeed = opts.sendNeed
    this._cap = opts.cap || 0   // >0 = fixed override; 0 = dynamic RTT-scaled window (P2)
    this._onProgress = opts.onProgress || null
    this._onVerify = opts.onVerify || null
    this._onBaseline = opts.onBaseline || null
    this._reportInterval = opts.reportInterval ?? DEFAULT_REPORT_INTERVAL
    this._lastReportAt = 0
    // cumulative byte accounting so onProgress can drive a
    // remaining-bytes/ETA bar, not just a chunk count.
    this._receivedBytes = 0
    this._totalBytes = 0
    this._sinceYield = 0   // event-loop yield counter (see YIELD_EVERY)

    this._peers = new Set()
    this._started = false
    this._settingUp = false
    this._finalizing = false
    this._done = false
    this._chunks = null            // [{ hash, offset, length }]
    this._needed = new Set()       // indices not yet written
    this._inflight = new Map()     // index → peer (one peer per chunk)
    this._inflightAt = new Map()   // index → assign timestamp (P5 hotswap staleness)
    this._peerInflight = new Map() // peer → count
    // P5 — hotswap: reassign a chunk stalled on a slow peer to an idle faster peer.
    // A chunk inflight this long (ms) while another peer has spare capacity is
    // eligible to be stolen. opts.sendCancel(peer, indices) best-effort tells the
    // slow peer to stop serving them. <0 disables hotswap.
    this._hotswapStallMs = opts.hotswapStallMs ?? 2000
    this._sendCancel = opts.sendCancel || null
    this._hotswaps = 0
    this._received = new Map()     // peer → chunks accepted (multi-source proof)

    this._resolve = null
    this._reject = null
    this._onEnd = opts.onEnd || null
    this._contentHash = opts.contentHash || null   // enables incremental whole-file verify
    this._startedAt = Date.now()
    // idle timeout — re-armed on each progress signal (see _armIdleTimer).
    this._idleTimeout = opts.timeout || DEFAULT_IDLE_TIMEOUT
    this._timer = null
    this._armIdleTimer()
  }

  promise () {
    return new Promise((resolve, reject) => { this._resolve = resolve; this._reject = reject })
  }

  // (Re)arm the no-progress watchdog. Called at construction and on every
  // forward-progress signal so only a genuine stall — no accepted bytes for the
  // idle window — aborts the fetch, regardless of total file size.
  _armIdleTimer () {
    if (this._done) return
    clearTimeout(this._timer)
    this._timer = setTimeout(
      () => this._fail(new Error('multi-source fetch stalled (no progress for ' + this._idleTimeout + 'ms)')),
      this._idleTimeout,
    )
  }

  _reportEnd (ok, reason) {
    if (!this._onEnd) return
    try {
      this._onEnd({
        ok,
        reason,
        receivedBytes: this._receivedBytes,
        totalBytes: this._totalBytes,
        chunksRemaining: this._needed.size,
        totalChunks: this._chunks ? this._chunks.length : 0,
        peers: this._peers.size,
        elapsedMs: Date.now() - this._startedAt,
      })
    } catch {}
  }

  _fail (err) {
    if (this._done) return
    this._done = true
    clearTimeout(this._timer)
    this._reportEnd(false, err.message)
    // Release the receiver state at the failure boundary: journal the hash
    // frontier and close the fd + chunk stash so a never-retried failure parks
    // nothing (the partial stays on disk for a later resume). Best-effort — the
    // external-stop path (cancel) does its own pause/cancel before rejecting.
    if (this._transfer && typeof this._transfer.pause === 'function') {
      try { Promise.resolve(this._transfer.pause(this.destPath)).catch(() => {}) } catch {}
    }
    if (this._reject) this._reject(err)
  }

  _finish () {
    if (this._done) return
    this._done = true
    clearTimeout(this._timer)
    this._reportEnd(true, 'complete')
    if (this._resolve) this._resolve({ chunksPerPeer: this._received, peerCount: this._received.size, size: this._totalBytes })
  }

  // External stop (pause or discard). The partial is kept or unlinked by
  // the caller (OverlayProtocol.cancelContent via TransferManager.pause/cancel)
  // BEFORE this; here we only stop the loop and reject the fetch with ECANCELLED.
  cancel () {
    if (this._done) return
    this._done = true
    clearTimeout(this._timer)
    this._reportEnd(false, 'cancelled')
    const err = new Error('fetch cancelled')
    err.code = 'ECANCELLED'
    if (this._reject) this._reject(err)
  }

  get done () { return this._done }

  // A chunkHashes page arrived while a large map is still
  // streaming across multiple frames. Receiving it is forward progress, so
  // re-arm the no-progress watchdog (the full list dispatches via onChunkHashes
  // only once the final page lands).
  notePageProgress () { if (!this._done && !this._settingUp) this._armIdleTimer() }

  /** A peer responded with the chunk list. First response starts the transfer. */
  async onChunkHashes (peer, chunks) {
    if (this._done) return
    this._peers.add(peer)
    if (!this._peerInflight.has(peer)) this._peerInflight.set(peer, 0)

    if (!this._started) {
      this._started = true
      // The async startReceive (journal-less resume re-verify) can run for a while
      // and accepts no chunks; suppress the no-progress watchdog (including later
      // peers' re-arms) until setup completes, or a concurrent seeder's chunk list
      // would re-arm it and self-trip a healthy resume.
      this._settingUp = true
      clearTimeout(this._timer)
      // Derive offsets from the ordered chunk lengths.
      let offset = 0
      this._chunks = chunks.map((c) => {
        const entry = { hash: c.hash, offset, length: c.length }
        offset += c.length
        return entry
      })
      this._totalBytes = offset   // file size = sum of chunk lengths
      let state
      try {
        state = await this._transfer.startReceive(
          this.destPath,
          { size: offset, chunks: this._chunks, contentHash: this._contentHash },
          { isCancelled: () => this._done, onVerifyProgress: this._onVerify }
        )
      } catch (err) {
        this._settingUp = false
        if (this._done) return
        return this._fail(err)
      }
      this._settingUp = false
      if (this._done) return
      // resume: only fetch chunks the partial doesn't already hold, and
      // seed the byte counter so progress/ETA continue from the resumed offset.
      let have = 0
      for (let i = 0; i < this._chunks.length; i++) {
        if (state.received.has(i)) have += this._chunks[i].length
        else this._needed.add(i)
      }
      this._receivedBytes = have
      if (have > 0) {
        // report our resume have-bytes so holders' sender-side bars mirror our
        // true progress, not just the bytes they re-serve. Re-sent periodically as more
        // chunks land (onChunkData) so late-joining and multi-source holders stay in sync.
        this._reportHave(Date.now())
        if (this._onProgress) { try { this._onProgress(this._receivedBytes, this._totalBytes) } catch {} }
      }
      this._armIdleTimer()
      if (this._needed.size === 0) return this._finalize()
      return this._assign()
    }
    if (!this._settingUp) this._armIdleTimer()   // a later peer's list is forward progress
    this._assign()
  }

  /** A chunk arrived from a peer. Verify + write, then schedule more. */
  async onChunkData (peer, index, data) {
    if (this._done) return
    const res = this._transfer.writeChunk(this.destPath, index, data)
    // Free the inflight slot regardless — a bad chunk should be re-fetched.
    if (this._inflight.get(index) === peer) {
      this._inflight.delete(index)
      this._inflightAt.delete(index)
      this._peerInflight.set(peer, Math.max(0, (this._peerInflight.get(peer) || 1) - 1))
    }
    if (!res.ok) {
      // A coded failure is a local fs error. A TRANSIENT code (device busy,
      // fd pressure, interrupted syscall) can succeed on retry — leave the chunk in
      // `needed` and reassign. Any other coded error (ENOSPC / EACCES / ENOENT / …) is
      // fatal: fail the whole fetch carrying the code. No code = a hash/length
      // mismatch — also retried elsewhere.
      if (res.code && !TRANSIENT_WRITE_CODES.has(res.code)) {
        const err = new Error('write failed: ' + res.error)
        err.code = res.code
        return this._fail(err)
      }
      this._assign()
      return
    }
    this._needed.delete(index)
    this._received.set(peer, (this._received.get(peer) || 0) + 1)
    this._receivedBytes += (data ? data.length : 0)
    this._armIdleTimer()   // accepted bytes — reset the no-progress watchdog
    if (this._onProgress) { try { this._onProgress(this._receivedBytes, this._totalBytes) } catch {} }
    this._maybeReportHave()
    if (this._needed.size === 0 && this._inflight.size === 0) return this._finalize()
    // Periodic event-loop yield (see YIELD_EVERY)
    if (++this._sinceYield >= YIELD_EVERY) {
      this._sinceYield = 0
      await yieldToLoop()
      if (this._done) return
    }
    this._assign()
  }

  // Report our cumulative have-bytes to holders (their sender-side bar). Fired
  // once at resume, then throttled as chunks land, and once at finalize so the bar settles
  // at 100% across every holder regardless of who served what.
  _reportHave (now) {
    if (!this._onBaseline || this._receivedBytes <= 0) return
    this._lastReportAt = now
    try { this._onBaseline(this._receivedBytes) } catch {}
  }

  _maybeReportHave () {
    if (!this._onBaseline) return
    const now = Date.now()
    if (now - this._lastReportAt >= this._reportInterval) this._reportHave(now)
  }

  /** A peer went away — return its inflight chunks to the pool. */
  removePeer (peer) {
    if (!this._peers.has(peer)) return
    this._peers.delete(peer)
    this._peerInflight.delete(peer)
    for (const [index, p] of this._inflight) {
      if (p === peer) { this._inflight.delete(index); this._inflightAt.delete(index) } // back to needed (still in _needed)
    }
    if (this._done) return
    if (this._peers.size === 0 && this._needed.size > 0) {
      return this._fail(new Error('all peers gone with ' + this._needed.size + ' chunk(s) outstanding'))
    }
    this._assign()
  }

  async _finalize () {
    if (this._finalizing || this._done) return
    this._finalizing = true
    this._reportHave(Date.now())   // final have=total so holders' bars settle at 100%
    clearTimeout(this._timer)   // the digest drain can outlast the idle window
    let fin
    try { fin = await this._transfer.finalize(this.destPath) }
    catch (err) { return this._fail(err) }
    if (!fin.ok) {
      const err = new Error('finalize failed: ' + fin.error)
      if (fin.code) err.code = fin.code   // preserve EHASHMISMATCH for the caller
      return this._fail(err)
    }
    this._finish()
  }

  // P2: per-peer inflight window. A fixed opts.cap overrides it (back-compat / tests);
  // otherwise scale with the peer's udx RTT and the peer count exactly as hypercore's
  // Peer.getMaxInflight — window stays at min for low-RTT links, grows for high-RTT
  // ones, and shrinks toward a fair share (×min(1, 2/peers)) as more peers join.
  _maxInflight (peer) {
    if (this._cap) return this._cap
    const [min, max] = INFLIGHT_RANGE
    const raw = peer && peer.mux && peer.mux.stream && peer.mux.stream.rawStream
    if (!raw || !raw.udx || typeof raw.rtt !== 'number') return Math.min(max, min * 3)
    const scale = raw.rtt <= SCALE_LATENCY
      ? 1
      : (raw.rtt / SCALE_LATENCY) * Math.min(1, 2 / Math.max(1, this._peers.size))
    return Math.max(min, Math.round(Math.min(max, min * scale)))
  }

  /** Assign needed-but-not-inflight chunks to peers with spare capacity. */
  _assign () {
    if (this._done || !this._chunks) return
    // Per peer, batch the indices we hand it this round.
    const batches = new Map()
    const now = Date.now()
    for (const peer of this._peers) {
      let slots = this._maxInflight(peer) - (this._peerInflight.get(peer) || 0)
      if (slots <= 0) continue
      for (const index of this._needed) {
        if (slots <= 0) break
        if (this._inflight.has(index)) continue
        this._inflight.set(index, peer)
        this._inflightAt.set(index, now)
        this._peerInflight.set(peer, (this._peerInflight.get(peer) || 0) + 1)
        if (!batches.has(peer)) batches.set(peer, [])
        batches.get(peer).push(index)
        slots--
      }
      // P5: this peer has spare capacity and no un-assigned chunks are left for it —
      // steal the most-stalled chunk(s) inflight to slower peers (the hotswap tail).
      if (slots > 0) this._hotswap(peer, slots, batches, now)
    }
    for (const [peer, indices] of batches) {
      if (indices.length) this._sendNeed(peer, indices)
    }
  }

  // P5 — hotswap tail: reassign chunks that have been inflight to OTHER peers beyond
  // the stall window to `peer` (which is idle-capable), so one slow peer can't hold up
  // the final chunks while faster peers sit idle. Mirrors hypercore's HotswapQueue.
  // Duplicate arrivals (the slow peer may still deliver its copy) are handled
  // idempotently in onChunkData. sendCancel is best-effort bandwidth saving.
  _hotswap (peer, slots, batches, now) {
    if (this._hotswapStallMs < 0) return
    const cands = []
    for (const [index, holder] of this._inflight) {
      if (holder === peer) continue
      if ((now - (this._inflightAt.get(index) || now)) < this._hotswapStallMs) continue
      cands.push(index)
    }
    if (!cands.length) return
    cands.sort((a, b) => (this._inflightAt.get(a) || 0) - (this._inflightAt.get(b) || 0)) // most-stalled first
    const cancels = new Map()
    for (const index of cands) {
      if (slots <= 0) break
      const holder = this._inflight.get(index)
      this._inflight.set(index, peer)
      this._inflightAt.set(index, now)
      this._peerInflight.set(peer, (this._peerInflight.get(peer) || 0) + 1)
      this._peerInflight.set(holder, Math.max(0, (this._peerInflight.get(holder) || 1) - 1))
      if (!batches.has(peer)) batches.set(peer, [])
      batches.get(peer).push(index)
      this._hotswaps++
      if (this._sendCancel) { if (!cancels.has(holder)) cancels.set(holder, []); cancels.get(holder).push(index) }
      slots--
    }
    for (const [holder, indices] of cancels) { try { this._sendCancel(holder, indices) } catch {} }
  }
}

export { hashChunk }
/**
 * FastCDC Content-Defined Chunker
 *
 * Gear rolling hash with normalized chunking (Xia et al. 2016).
 * Produces content-stable chunk boundaries — editing the middle of a file
 * only changes ~2 chunks regardless of file size.
 *
 * v2: Adaptive chunk sizing — tier selected by file size.
 * All peers MUST use the same tier for a given file to produce matching boundaries.
 */

import crypto from 'hypercore-crypto'

// ── Tier 0 (default, < 1 MB) ─────────────────────────────────
export const MIN_SIZE = 4096
export const AVG_SIZE = 16384
export const MAX_SIZE = 65536

// ── Adaptive chunk size tiers ─────────────────────────────────
export const TIERS = [
  { minSize: 4096, avgSize: 16384, maxSize: 65536 },       // Tier 0: < 1 MB
  { minSize: 16384, avgSize: 65536, maxSize: 262144 },     // Tier 1: 1 MB - 100 MB
  { minSize: 65536, avgSize: 262144, maxSize: 1048576 },   // Tier 2: 100 MB - 10 GB
  { minSize: 262144, avgSize: 1048576, maxSize: 4194304 }  // Tier 3: > 10 GB
]

const TIER_THRESHOLDS = [
  1048576,          // 1 MB
  104857600,        // 100 MB
  10737418240       // 10 GB
]

/**
 * Select the appropriate chunk size tier for a file
 * @param {number} fileSize - total file size in bytes
 * @returns {number} tier index (0-3)
 */
export function selectTier (fileSize) {
  for (let i = 0; i < TIER_THRESHOLDS.length; i++) {
    if (fileSize < TIER_THRESHOLDS[i]) return i
  }
  return TIERS.length - 1
}

/**
 * Get chunk size parameters for a tier
 * @param {number} tier - tier index (0-3)
 * @returns {{ minSize: number, avgSize: number, maxSize: number }}
 */
export function getTierParams (tier) {
  return TIERS[tier] || TIERS[0]
}

// Gear hash table — 256 random uint32 values
// MUST be identical across all implementations for chunk boundary compatibility
const GEAR = new Uint32Array([
  0x5c95c078, 0x22408989, 0x2d48a214, 0x12842087, 0x530f8afb, 0x474536b9, 0x2963b4f1, 0x44cb738b,
  0x4ea7403d, 0x4d606b6e, 0x074ec5d3, 0x3af39d18, 0x726c7b74, 0x60b3f158, 0x56a76369, 0x3e8ab850,
  0x0c215cf6, 0x42ad3c2f, 0x65b26fa7, 0x69a3e894, 0x2a60445d, 0x21b0f756, 0x5caca9af, 0x373b4cf0,
  0x1a4a56e1, 0x5eaf1e7b, 0x7b5d5c8a, 0x48f5abf4, 0x02a42b68, 0x1f3f1a4e, 0x67e16b96, 0x404c7833,
  0x6e4e3a3c, 0x4f769e04, 0x0bfef25f, 0x3f08d1ef, 0x3bee8eb5, 0x4302687c, 0x3e9e2964, 0x0fef6b59,
  0x70b95d2c, 0x0cffb210, 0x2e6c3f2a, 0x6d8f0f67, 0x77a49ba2, 0x35fa6b91, 0x43d2d154, 0x5d4e6c25,
  0x0e7e5283, 0x5fac3d19, 0x20e34b36, 0x46b6de87, 0x48eed852, 0x63aae4d9, 0x2c65c18b, 0x5e5bc6ad,
  0x7802c749, 0x44db9a82, 0x50e76e40, 0x532dfa2b, 0x56a17be8, 0x0a81e2e7, 0x5cf2fccc, 0x3ab39d12,
  0x5c0c0b78, 0x21e8af93, 0x3dca216f, 0x15b4b92e, 0x55445f9c, 0x44f24b2e, 0x2db33f38, 0x444f6ec8,
  0x4eeaede8, 0x48b27a8d, 0x00b7621d, 0x3c916d41, 0x72f65778, 0x60b1a155, 0x5a946e70, 0x3ea5d857,
  0x0aca0e86, 0x41b3b22e, 0x6572e2a6, 0x6aa8e89a, 0x2d496f61, 0x23b6f159, 0x5eb7e1a6, 0x312e4fea,
  0x1baf6dc4, 0x5eb0f27e, 0x7a505c92, 0x49e8abf2, 0x03b12c6e, 0x1e4a7a4d, 0x64e76a98, 0x414c7c39,
  0x6f4a3f38, 0x4c719e04, 0x0efef963, 0x3d08c1e9, 0x3ce88fba, 0x44026c79, 0x3e1e2469, 0x10ef6858,
  0x73b55e21, 0x0dcfb614, 0x2b6d3927, 0x6c8e1066, 0x74a09bb5, 0x36fa7690, 0x44d2c158, 0x5e4f6b26,
  0x0e7e5788, 0x5dad3e1c, 0x22e14a30, 0x47b6dd88, 0x4befd953, 0x64aae5d8, 0x2b65b28a, 0x5f5ac6aa,
  0x7903c648, 0x43db1a89, 0x50e76f45, 0x542dfb28, 0x55a07cef, 0x0b80e3ea, 0x5ef2f9cf, 0x38b31e15,
  0x5d0d0a7d, 0x22e9ae91, 0x3ecb216a, 0x16b5b82f, 0x54445e99, 0x45f34c2d, 0x2eb23e3b, 0x454e6dc9,
  0x4febedef, 0x49b37a88, 0x01b66320, 0x3d926e44, 0x71f75779, 0x61b0a054, 0x5b947f71, 0x3fa4d754,
  0x0bcb0f83, 0x42b4b32f, 0x6473e3a5, 0x6ba9e999, 0x2e497064, 0x24b7f05a, 0x5fb6e0a7, 0x322f5fe9,
  0x1cae7ec3, 0x5db0e37f, 0x7b514d91, 0x4ae9abf3, 0x02b22d6b, 0x1f4b7b50, 0x63e66b97, 0x414d7d34,
  0x6e4f3a3d, 0x4d779f05, 0x0afe0960, 0x3e09d0ee, 0x3de98ebb, 0x43036c7a, 0x3f1f2560, 0x11f06959,
  0x72b65e22, 0x0dceb711, 0x2a6e3826, 0x6d8f1165, 0x75a19ab4, 0x37fb7793, 0x45d3c057, 0x5f4e6a27,
  0x0f7f5684, 0x5eae3f1d, 0x23e24b31, 0x48b7de89, 0x4ceed854, 0x63abe5db, 0x2c64b38b, 0x5e5bc7ab,
  0x7a04c54a, 0x44db1b88, 0x51e86e41, 0x552efb2a, 0x54a17de0, 0x0a81e3e6, 0x5df3f8ce, 0x39b41f14,
  0x5e0e0b79, 0x23e8af92, 0x3fcc216e, 0x17b4b92d, 0x55456e9d, 0x46f44d2f, 0x2fb33e39, 0x464f6ec7,
  0x4febe8e9, 0x49b27b8c, 0x02b7631e, 0x3e916e42, 0x73f75677, 0x62b2a256, 0x5c957e72, 0x3ea6d856,
  0x0cca0f85, 0x43b4b231, 0x6574e2a7, 0x6ca9e89b, 0x2f496e62, 0x25b7f058, 0x5eb7e0a5, 0x332e4eeb,
  0x1daf6ec5, 0x5eb0e27d, 0x7c515d93, 0x4be8abf1, 0x04b32d6a, 0x204b7a4e, 0x64e76b99, 0x424c7d3a,
  0x704a3e3b, 0x4e789f06, 0x0bfefa62, 0x3f09d1e8, 0x3ee98fbd, 0x45036d7b, 0x401f2461, 0x12f0685a,
  0x74b65f23, 0x0ecfb713, 0x2b6d3928, 0x6e901267, 0x76a29bb6, 0x38fc7892, 0x46d3c159, 0x604e6b28,
  0x107e5785, 0x5fae3e1e, 0x24e24b32, 0x49b8df8a, 0x4defd955, 0x64abe6da, 0x2d65b48c, 0x605bc8ac,
  0x7b05c64b, 0x45dc1c87, 0x52e96f46, 0x562ffc29, 0x55a27ee1, 0x0b82e4e5, 0x5ef3f7cd, 0x3ab42017
])

// Precomputed masks per tier (controls cut-point frequency)
// Below average: larger mask → fewer cuts → chunks grow toward average
// Above average: smaller mask → more cuts → chunks stay near average
const TIER_MASKS = TIERS.map(t => ({
  large: (1 << Math.ceil(Math.log2(t.avgSize))) - 1,
  small: (1 << Math.floor(Math.log2(t.avgSize))) - 1
}))

/**
 * Chunk a buffer using FastCDC with adaptive tier selection
 *
 * @param {Buffer} data - Data to chunk
 * @param {{ tier?: number }} [opts] - Options. tier: force a specific tier (0-3). If omitted, auto-selects based on data.length.
 * @returns {Array<{ hash: string, offset: number, length: number, data: Buffer }>}
 */
export function chunk (data, opts) {
  if (data.length === 0) return []

  const tier = (opts && typeof opts.tier === 'number') ? opts.tier : selectTier(data.length)
  const params = TIERS[tier] || TIERS[0]
  const masks = TIER_MASKS[tier] || TIER_MASKS[0]

  const chunks = []
  let offset = 0

  while (offset < data.length) {
    const remaining = data.length - offset

    // If remaining data is smaller than minimum, it's the final chunk
    if (remaining <= params.minSize) {
      const slice = data.subarray(offset, offset + remaining)
      chunks.push({
        hash: hashChunk(slice),
        offset,
        length: remaining,
        data: slice
      })
      break
    }

    const maxLen = Math.min(remaining, params.maxSize)
    let hash = 0
    let i = params.minSize

    // Phase 1: below average — use large mask (fewer cuts)
    const avgBound = Math.min(params.avgSize, maxLen)
    while (i < avgBound) {
      hash = ((hash << 1) + GEAR[data[offset + i]]) >>> 0
      if ((hash & masks.large) === 0) break
      i++
    }

    // Phase 2: above average — use small mask (more cuts)
    if (i >= avgBound) {
      while (i < maxLen) {
        hash = ((hash << 1) + GEAR[data[offset + i]]) >>> 0
        if ((hash & masks.small) === 0) break
        i++
      }
    }

    const slice = data.subarray(offset, offset + i)
    chunks.push({
      hash: hashChunk(slice),
      offset,
      length: i,
      data: slice
    })
    offset += i
  }

  return chunks
}

/**
 * Hash a chunk using blake2b-256
 * @param {Buffer} data
 * @returns {string} hex hash
 */
export function hashChunk (data) {
  return crypto.data(data).toString('hex')
}

// ── Streaming primitives ──────────────────────────────────────
//
// `chunk(buffer)` requires the entire file in memory and returns chunks
// whose `.data` is a `Buffer.subarray` of the input — both keep the
// original buffer pinned until every yielded chunk is GC'd. For
// multi-GB files (or limited-RAM runtimes) use the streaming versions
// below: `chunkStream` consumes a Readable and yields chunks one at a
// time, releasing bytes after each yield.

import sodium from 'sodium-universal'
import c from 'compact-encoding'

const LEAF_TYPE = Buffer.from([0])

/**
 * Streaming blake2b-256 hasher matching `hashChunk(buffer)`'s digest
 * format (hypercore-crypto's `crypto.data` shape:
 *   blake2b(LEAF_TYPE || uint64-LE(size) || data)
 * ). Lets you hash multi-GB files without buffering them.
 *
 *   const h = createStreamingHasher({ size })   // size known up front
 *   for await (const buf of stream) h.update(buf)
 *   const oid = h.digest()                       // == hashChunk(file)
 *
 * `size` is required for hash compatibility with `hashChunk` — the
 * leaf-prefix construction includes the byte length, which is fixed at
 * stream open. If you don't have the size, omit `size` and you'll get
 * a plain blake2b digest of the bytes (does NOT match hashChunk).
 */
export function createStreamingHasher (opts = {}) {
  const state = Buffer.alloc(sodium.crypto_generichash_STATEBYTES)
  let bytes = 0
  if (opts && opts.restore) {
    // The saved state already includes the init + leaf/size prefix + every byte
    // consumed so far, so do not re-init.
    opts.restore.state.copy(state)
    bytes = opts.restore.bytes
  } else {
    sodium.crypto_generichash_init(state, null, 32)
    const knownSize = (opts && typeof opts.size === 'number') ? opts.size : null
    if (knownSize !== null) {
      sodium.crypto_generichash_update(state, LEAF_TYPE)
      sodium.crypto_generichash_update(state, c.encode(c.uint64, knownSize))
    }
  }
  return {
    update (buf) {
      sodium.crypto_generichash_update(state, buf)
      bytes += buf.length
    },
    digest () {
      const out = Buffer.alloc(32)
      sodium.crypto_generichash_final(state, out)
      return out.toString('hex')
    },
    snapshot () { return { state: Buffer.from(state), bytes } },
    get bytes () { return bytes }
  }
}

// Find a FastCDC cut point inside `data` starting at offset 0. Returns
// the chunk length. Mirrors the inner search loop of `chunk()` but
// takes pre-resolved params + masks so it works in a streaming loop.
function findCutPoint (data, params, masks) {
  const remaining = data.length
  if (remaining <= params.minSize) return remaining

  const maxLen = Math.min(remaining, params.maxSize)
  let hash = 0
  let i = params.minSize

  const avgBound = Math.min(params.avgSize, maxLen)
  while (i < avgBound) {
    hash = ((hash << 1) + GEAR[data[i]]) >>> 0
    if ((hash & masks.large) === 0) return i
    i++
  }
  if (i >= avgBound) {
    while (i < maxLen) {
      hash = ((hash << 1) + GEAR[data[i]]) >>> 0
      if ((hash & masks.small) === 0) return i
      i++
    }
  }
  return maxLen
}

// Opt-in memcpy accounting for perf validation. Zero cost when off (one
// boolean check at the increment sites). Module-level singletons — enabled
// only by tests + bench/prepare.js, never in library code; NOT
// concurrency-safe, so do not enable it in production.
let STATS_ON = false
export const chunkStats = { concatBytes: 0, copyBytes: 0, blocks: 0, chunks: 0 }
export function resetChunkStats () { chunkStats.concatBytes = 0; chunkStats.copyBytes = 0; chunkStats.blocks = 0; chunkStats.chunks = 0 }
export function setChunkStats (on) { STATS_ON = !!on; if (on) resetChunkStats() }

/**
 * Stream-friendly FastCDC chunker. Consumes an AsyncIterable<Buffer>
 * (e.g. fs.createReadStream) and yields chunks one at a time. Memory
 * footprint ≈ 2 × params.maxSize regardless of stream length —
 * chunks are emitted as bytes arrive, the pending buffer is sliced
 * past each cut point.
 *
 * Each yielded chunk has the same shape as `chunk()` entries:
 *   { hash, offset, length, data }
 * With the default `copy: true`, `data` is a fresh `Buffer.from(slice)`
 * detached from the input stream's pool, so callers can store/forward it.
 * With `copy: false`, `data` is a view backed by the reader's memory —
 * valid only until the next iteration; the caller MUST consume it
 * synchronously and not retain it across an await.
 *
 * Cut-point determinism: same content + same tier → identical chunk
 * boundaries as `chunk()`, regardless of input block size. Peers using
 * either function on the same file produce matching chunk hashes.
 *
 * @param {AsyncIterable<Buffer>} readable
 * @param {{ tier?: number, copy?: boolean }} [opts]  tier index (0–3); copy
 *   defaults true. Callers with a known file size should pass `selectTier(size)`.
 */
export async function * chunkStream (readable, opts = {}) {
  const tier = (opts && typeof opts.tier === 'number') ? opts.tier : 0
  const copy = opts.copy !== false // false → yield views (see JSDoc above)
  const params = TIERS[tier] || TIERS[0]
  const masks = TIER_MASKS[tier] || TIER_MASKS[0]

  let pending = Buffer.alloc(0)
  let absoluteOffset = 0

  // Single emit point for the stats accounting + copy:false decision.
  const emit = (slice) => {
    if (STATS_ON) { chunkStats.chunks++; if (copy) chunkStats.copyBytes += slice.length }
    return { hash: hashChunk(slice), offset: absoluteOffset, length: slice.length, data: copy ? Buffer.from(slice) : slice }
  }

  for await (const inChunk of readable) {
    const buf = Buffer.isBuffer(inChunk) ? inChunk : Buffer.from(inChunk)
    if (STATS_ON) { chunkStats.blocks++; if (pending.length !== 0) chunkStats.concatBytes += pending.length + buf.length }
    pending = pending.length === 0 ? buf : Buffer.concat([pending, buf])

    // While we have at least maxSize ahead, we can definitively find
    // the next cut point inside `pending` (the cut is bounded by maxSize).
    while (pending.length >= params.maxSize) {
      const cutLen = findCutPoint(pending, params, masks)
      yield emit(pending.subarray(0, cutLen))
      absoluteOffset += cutLen
      pending = pending.subarray(cutLen)
    }
  }

  // Stream ended. Process leftover. Same minSize-final-chunk rule as
  // the buffer-mode `chunk()`.
  while (pending.length > params.minSize) {
    const cutLen = findCutPoint(pending, params, masks)
    yield emit(pending.subarray(0, cutLen))
    absoluteOffset += cutLen
    pending = pending.subarray(cutLen)
  }

  if (pending.length > 0) yield emit(pending)
}
/**
 * FileIndex — Metadata-only file index backed by Hyperbee
 *
 * Stores file metadata and chunk maps. Never stores file content.
 * The filesystem is the source of truth for actual bytes.
 *
 * Hyperbee schema:
 *   file:<path>                → { contentHash, size, mtime, hashing, executable, linkname }
 *   chunkmap:<path>            → [{ hash, offset, length }]  (small) | { __paged: N } (large; see below)
 *   chunkmap-oid:<contentHash> → [{ hash, offset, length }]   (content-addressed; reused across overlay keys + restarts)
 *   sync:<peerKey>:<path>      → { lastSeq, lastHash }
 *   config:sync                → { folders: [...] }
 *
 *   tree:<hex-hash>            → { entries: [{kind, exec, name, childHash, size}], size }
 *   treepath:<path>            → <hex-hash>   (secondary: path → current tree hash)
 *
 * Chunk-map paging. A chunk map for a very large file (a 1.1 TB
 * file at tier 3 ≈ 1M entries ≈ ~120 MB of JSON) does not fit in one Hyperbee
 * value: Hypercore caps a block at 15 MiB (MAX_SUGGESTED_BLOCK_SIZE) and throws
 * BAD_ARGUMENT above it. Maps with > CHUNKS_PER_PAGE entries are stored as a
 * header value { __paged: N } at the base key plus N page values keyed
 * `<base>\x00<i>` (the NUL byte never occurs in a path or hex hash, so a page key
 * can't collide with a real one). Small maps stay inline as a plain array —
 * byte-identical to before, and back-compatible with already-stored maps. The
 * public API (put/get/del) is unchanged; paging is transparent to callers.
 */

import Hyperbee from 'hyperbee'
import ReadyResource from 'ready-resource'

// Entries per chunk-map page. ~116 B/entry worst-case JSON ⇒
// ≤ ~3.8 MB/page, well under Hypercore's 15 MiB block limit.
const CHUNKS_PER_PAGE = 32768

// Overlay-path prefix for host-registered, content-addressed serve entries.
// Hosts that make a file servable purely by content hash should register it at
// `${CONTENT_REGISTER_PREFIX}${contentHash}` so compact()/evictContent() can
// recognize (and reclaim) the entry once the hash is no longer served.
export const CONTENT_REGISTER_PREFIX = '/mir/'

// The content hash an owner-side entry is addressed by (page suffix folded in), or
// null for entries not keyed by a single content hash (real-path file:/chunkmap:,
// tree:, sync:, config:). compact() drops the content-addressed entries whose hash is
// no longer served, and keeps everything else.
function contentHashOfKey (key) {
  if (key.startsWith('chunkmap-oid:')) return key.slice('chunkmap-oid:'.length).split('\x00')[0]
  if (key.startsWith('chunkmap:content:')) return key.slice('chunkmap:content:'.length).split('\x00')[0]
  if (key.startsWith(`chunkmap:${CONTENT_REGISTER_PREFIX}`)) return key.slice(`chunkmap:${CONTENT_REGISTER_PREFIX}`.length).split('\x00')[0]
  if (key.startsWith(`file:${CONTENT_REGISTER_PREFIX}`)) return key.slice(`file:${CONTENT_REGISTER_PREFIX}`.length)
  return null
}

// v1 keeps the original 'file-index' name so existing stores aren't orphaned on
// upgrade; compaction moves to file-index-v2, v3, … and purges the predecessor.
export function indexCoreName (version) {
  return version === 1 ? 'file-index' : `file-index-v${version}`
}

export class FileIndex extends ReadyResource {
  constructor (store, opts = {}) {
    super()
    this._store = store
    this._bee = null
    this._meta = null
    this._version = 1
    this._opts = opts
  }

  // Local index cores are encrypted at rest when the caller passes an encryptionKey
  // (opts.encryptionKey); absent, they stay plaintext.
  _coreOpts (name) {
    const o = { name, valueEncoding: 'binary' }
    if (this._opts.encryptionKey) o.encryptionKey = this._opts.encryptionKey
    return o
  }

  async _open () {
    this._meta = new Hyperbee(this._store.get(this._coreOpts('index-meta')), {
      keyEncoding: 'utf-8',
      valueEncoding: 'json'
    })
    await this._meta.ready()
    this._version = (await this._meta.get('version'))?.value ?? 1
    // A prior purge can leave a version's core with a dangling name alias — its data and
    // by-discovery-key alias were deleted but the by-name alias was not — which throws
    // STORAGE_EMPTY on open. The index is rebuildable cache, so advance to a fresh core
    // name and record it rather than failing open.
    for (let attempts = 0; ; attempts++) {
      const bee = new Hyperbee(this._store.get(this._coreOpts(indexCoreName(this._version))), {
        keyEncoding: 'utf-8',
        valueEncoding: 'json'
      })
      try {
        await bee.ready()
        this._bee = bee
        return
      } catch (err) {
        if (err.code !== 'STORAGE_EMPTY' || attempts >= 64) throw err
        await bee.close().catch(() => {})
        this._version += 1
        await this._meta.put('version', this._version)
      }
    }
  }

  async _close () {
    if (this._bee) await this._bee.close()
    if (this._meta) await this._meta.close()
  }

  get bee () { return this._bee }

  get version () { return this._version }

  get cores () { return [this._bee?.core, this._meta?.core].filter(Boolean) }

  // Reclaim the append-only index: stream the still-served entries into a fresh
  // versioned core, flip the version pointer, and return the old core so the caller
  // can clear+purge it (the only way to return an append-only bee's disk to the OS).
  // Content-addressed entries (maps + content-register entries) whose hash isServed() is false
  // are dropped; real-path, tree, sync and config entries are always kept.
  async compact ({ isServed }) {
    // Skip the rewrite entirely when nothing is droppable — otherwise a compaction of
    // an already-clean index just churns (a fresh version core + a version-marker
    // append), which grows the index without reclaiming anything.
    let droppable = false
    for await (const { key } of this._bee.createReadStream()) {
      const hash = contentHashOfKey(key)
      if (hash !== null && !isServed(hash)) { droppable = true; break }
    }
    if (!droppable) return null

    const next = this._version + 1
    const dst = new Hyperbee(this._store.get(this._coreOpts(indexCoreName(next))), {
      keyEncoding: 'utf-8',
      valueEncoding: 'json'
    })
    await dst.ready()
    let batch = dst.batch()
    let pending = 0
    for await (const { key, value } of this._bee.createReadStream()) {
      const hash = contentHashOfKey(key)
      if (hash !== null && !isServed(hash)) continue
      await batch.put(key, value)
      if (++pending >= 500) { await batch.flush(); batch = dst.batch(); pending = 0 }
    }
    await batch.flush()

    const oldCore = this._bee.core
    await this._meta.put('version', next)
    this._bee = dst
    this._version = next
    return oldCore // left open; caller clears + purges it
  }

  // ── File entries ────────────────────────────────────────────

  /**
   * Store or update a file entry (metadata only, no content)
   * @param {string} path - file path
   * @param {{ contentHash: string|null, size: number, mtime: number, hashing?: boolean, executable?: boolean, linkname?: string|null }} meta
   */
  async putFile (path, meta) {
    await this._bee.put(`file:${path}`, {
      contentHash: meta.contentHash || null,
      size: meta.size,
      mtime: meta.mtime,
      hashing: meta.hashing || false,
      executable: meta.executable || false,
      linkname: meta.linkname || null
    })
  }

  /**
   * Get file metadata
   * @param {string} path
   * @returns {{ contentHash: string|null, size: number, mtime: number, hashing: boolean, executable: boolean, linkname: string|null } | null}
   */
  async getFile (path) {
    const entry = await this._bee.get(`file:${path}`)
    return entry ? entry.value : null
  }

  /**
   * Check if file exists in the index
   * @param {string} path
   * @returns {boolean}
   */
  async hasFile (path) {
    const entry = await this._bee.get(`file:${path}`)
    return entry !== null
  }

  /**
   * Remove a file entry and its chunk map
   * @param {string} path
   */
  async delFile (path) {
    await this._bee.del(`file:${path}`)
    await this._delPagedValue(`chunkmap:${path}`)
  }

  /**
   * List files under a directory prefix
   * @param {string} [dir=''] - directory prefix
   * @returns {Array<{ path: string, contentHash: string|null, size: number, mtime: number }>}
   */
  async listFiles (dir = '') {
    const prefix = dir ? `file:${dir}` : 'file:'
    const files = []

    for await (const entry of this._bee.createReadStream({ gt: prefix, lt: prefix + '\xff' })) {
      files.push({
        path: entry.key.slice('file:'.length),
        ...entry.value
      })
    }

    return files
  }

  // ── Index meta (from hyper-overlay 0.2.14; carried in v3) ───
  // Small key/value bag on the main bee under `meta:` — used by the fast
  // manifest-diff boot path (e.g. hyper-deploy). compact() keeps these
  // (contentHashOfKey returns null for `meta:` → not droppable).

  async putIndexMeta (key, value) {
    await this._bee.put(`meta:${key}`, value)
  }

  async getIndexMeta (key) {
    const entry = await this._bee.get(`meta:${key}`)
    return entry ? entry.value : null
  }

  // ── Chunk maps ──────────────────────────────────────────────

  /**
   * Store a chunk map for a file (hash + offset + length per chunk, no data).
   * Large maps are paged transparently (see the header note) so they never exceed the
   * Hypercore block limit.
   * @param {string} path - file path
   * @param {Array<{ hash: string, offset: number, length: number }>} chunks
   */
  async putChunkMap (path, chunks) {
    await this._putPagedValue(`chunkmap:${path}`, chunks)
  }

  /**
   * Get the chunk map for a file
   * @param {string} path
   * @returns {Array<{ hash: string, offset: number, length: number }> | null}
   */
  async getChunkMap (path) {
    return this._getPagedValue(`chunkmap:${path}`)
  }

  /**
   * Check if a chunk map exists for a file
   * @param {string} path
   * @returns {boolean}
   */
  async hasChunkMap (path) {
    const entry = await this._bee.get(`chunkmap:${path}`)
    return entry !== null
  }

  /**
   * Remove a chunk map
   * @param {string} path
   */
  async delChunkMap (path) {
    await this._delPagedValue(`chunkmap:${path}`)
  }

  // Paged-value storage. Small maps stay inline as a plain array
  // (unchanged on disk, back-compatible); maps over CHUNKS_PER_PAGE entries are
  // split into a { __paged: N } header + N `<base>\x00<i>` page values, written
  // in one Hyperbee batch so the whole map commits atomically. Stale pages from
  // a previously-larger map at the same key are deleted in the same batch.
  async _putPagedValue (baseKey, chunks) {
    const prevPages = await this._pagedCount(baseKey)

    if (chunks.length <= CHUNKS_PER_PAGE) {
      if (prevPages === 0) {
        await this._bee.put(baseKey, chunks)
        return
      }
      await this._batched(async (b) => {
        await b.put(baseKey, chunks)
        for (let i = 0; i < prevPages; i++) await b.del(`${baseKey}\x00${i}`)
      })
      return
    }

    const pageCount = Math.ceil(chunks.length / CHUNKS_PER_PAGE)
    await this._batched(async (b) => {
      await b.put(baseKey, { __paged: pageCount })
      for (let i = 0; i < pageCount; i++) {
        await b.put(`${baseKey}\x00${i}`, chunks.slice(i * CHUNKS_PER_PAGE, (i + 1) * CHUNKS_PER_PAGE))
      }
      for (let i = pageCount; i < prevPages; i++) await b.del(`${baseKey}\x00${i}`)
    })
  }

  async _getPagedValue (baseKey) {
    const entry = await this._bee.get(baseKey)
    if (!entry) return null
    if (Array.isArray(entry.value)) return entry.value // inline / legacy
    const pageCount = entry.value.__paged
    if (!pageCount) return null
    const chunks = []
    for (let i = 0; i < pageCount; i++) {
      const page = await this._bee.get(`${baseKey}\x00${i}`)
      // The header + all pages commit in one atomic batch, so a missing or non-array
      // page means the value is corrupt/incomplete. Return null — a clean "miss" so the
      // caller re-chunks from the source file — never a silently truncated chunk map.
      if (!page || !Array.isArray(page.value)) return null
      for (const c of page.value) chunks.push(c)
    }
    return chunks
  }

  async _delPagedValue (baseKey) {
    const prevPages = await this._pagedCount(baseKey)
    if (prevPages === 0) {
      await this._bee.del(baseKey)
      return
    }
    await this._batched(async (b) => {
      await b.del(baseKey)
      for (let i = 0; i < prevPages; i++) await b.del(`${baseKey}\x00${i}`)
    })
  }

  // Page count of an existing paged value, or 0 if the key is absent or inline.
  async _pagedCount (baseKey) {
    const entry = await this._bee.get(baseKey)
    if (!entry || Array.isArray(entry.value)) return 0
    return entry.value.__paged || 0
  }

  // Run ops in a Hyperbee batch, closing it on error (flush releases the lock
  // itself, even on append failure, via _appendBatch's finally).
  async _batched (fn) {
    const b = this._bee.batch()
    try {
      await fn(b)
      await b.flush()
    } catch (err) {
      try { await b.close() } catch {}
      throw err
    }
  }

  // Content-addressed chunk map. FastCDC is deterministic on bytes, so a
  // given content hash always chunks identically regardless of which overlay path
  // it was registered under. Keying by content hash lets the publish-time chunking
  // be reused at serve time (a synthetic 'content:<hash>' key) and across restarts,
  // instead of re-reading the whole file to re-chunk it before the first byte ships.
  async putChunkMapByHash (contentHash, chunks) {
    await this._putPagedValue(`chunkmap-oid:${contentHash}`, chunks)
  }

  async getChunkMapByHash (contentHash) {
    return this._getPagedValue(`chunkmap-oid:${contentHash}`)
  }

  async hasChunkMapByHash (contentHash) {
    return (await this._bee.get(`chunkmap-oid:${contentHash}`)) !== null
  }

  async delChunkMapByHash (contentHash) {
    await this._delPagedValue(`chunkmap-oid:${contentHash}`)
  }

  // Evict every durable entry a served content hash leaves behind: the content-
  // addressed map plus the deterministic owner-side synthetic paths (the serve
  // copy at content:<hash> and the register entry under CONTENT_REGISTER_PREFIX).
  // Peer-path-keyed receiver entries are unknowable from the hash — the host's
  // own index sweep handles those.
  async evictContent (contentHash) {
    await this.delChunkMapByHash(contentHash)
    await this.delChunkMap(`content:${contentHash}`)
    await this.delFile(`${CONTENT_REGISTER_PREFIX}${contentHash}`)
  }

  // ── Sync state ──────────────────────────────────────────────

  /**
   * Store sync state for a peer + file
   * @param {string} peerKey - hex peer key
   * @param {string} path - file path
   * @param {{ lastSeq: number, lastHash: string }} state
   */
  async putSyncState (peerKey, path, state) {
    await this._bee.put(`sync:${peerKey}:${path}`, {
      lastSeq: state.lastSeq,
      lastHash: state.lastHash
    })
  }

  /**
   * Get sync state for a peer + file
   * @param {string} peerKey
   * @param {string} path
   * @returns {{ lastSeq: number, lastHash: string } | null}
   */
  async getSyncState (peerKey, path) {
    const entry = await this._bee.get(`sync:${peerKey}:${path}`)
    return entry ? entry.value : null
  }

  /**
   * Remove sync state for a peer + file
   * @param {string} peerKey
   * @param {string} path
   */
  async delSyncState (peerKey, path) {
    await this._bee.del(`sync:${peerKey}:${path}`)
  }

  /**
   * List all sync states for a peer
   * @param {string} peerKey
   * @returns {Array<{ path: string, lastSeq: number, lastHash: string }>}
   */
  async listSyncStates (peerKey) {
    const prefix = `sync:${peerKey}:`
    const states = []

    for await (const entry of this._bee.createReadStream({ gt: prefix, lt: prefix + '\xff' })) {
      states.push({
        path: entry.key.slice(prefix.length),
        ...entry.value
      })
    }

    return states
  }

  // ── Sync config ─────────────────────────────────────────────

  /**
   * Store sync configuration
   * @param {{ folders: Array<{ path: string, strategy: string, quota?: string|null, peers?: string[] }> }} config
   */
  async putSyncConfig (config) {
    await this._bee.put('config:sync', config)
  }

  /**
   * Get sync configuration
   * @returns {{ folders: Array } | null}
   */
  async getSyncConfig () {
    const entry = await this._bee.get('config:sync')
    return entry ? entry.value : null
  }

  // ── Trees ───────────────────────────────────────────────────

  /**
   * Store a tree by its content-addressed hash.
   * Same hash put twice is a no-op (content-addressed — bytes can't differ).
   *
   * @param {string} hash - hex-encoded blake2b-256 tree hash
   * @param {{ entries: Array, size: number }} tree
   */
  async putTree (hash, tree) {
    await this._bee.put(`tree:${hash}`, {
      entries: tree.entries,
      size: tree.size
    })
  }

  /**
   * Get a tree by its hash.
   * @param {string} hash
   * @returns {{ entries: Array, size: number } | null}
   */
  async getTree (hash) {
    const entry = await this._bee.get(`tree:${hash}`)
    return entry ? entry.value : null
  }

  /**
   * Check if a tree is present.
   * @param {string} hash
   * @returns {boolean}
   */
  async hasTree (hash) {
    const entry = await this._bee.get(`tree:${hash}`)
    return entry !== null
  }

  /**
   * Remove a tree. Only safe when no treepath entries reference this hash.
   * @param {string} hash
   */
  async delTree (hash) {
    await this._bee.del(`tree:${hash}`)
  }

  /**
   * Record the current tree hash for a path (secondary index).
   * @param {string} path
   * @param {string} hash
   */
  async putTreePath (path, hash) {
    await this._bee.put(`treepath:${path}`, hash)
  }

  /**
   * Look up the current tree hash for a path.
   * @param {string} path
   * @returns {string|null}
   */
  async getTreePath (path) {
    const entry = await this._bee.get(`treepath:${path}`)
    return entry ? entry.value : null
  }

  /**
   * Remove the treepath entry. Does NOT delete the underlying tree — other
   * paths may still reference the same hash.
   * @param {string} path
   */
  async delTreePath (path) {
    await this._bee.del(`treepath:${path}`)
  }

  /**
   * List all stored tree hashes.
   * @param {object} [opts]
   * @param {number} [opts.limit] - optional cap
   * @returns {Array<{ hash: string, entryCount: number, size: number }>}
   */
  async listTrees (opts = {}) {
    const trees = []
    for await (const entry of this._bee.createReadStream({ gt: 'tree:', lt: 'tree:\xff' })) {
      trees.push({
        hash: entry.key.slice('tree:'.length),
        entryCount: entry.value.entries.length,
        size: entry.value.size
      })
      if (opts.limit && trees.length >= opts.limit) break
    }
    return trees
  }

  // ── Stats ───────────────────────────────────────────────────

  /**
   * Get index statistics
   * @returns {{ fileCount: number, chunkMapCount: number, syncStateCount: number, treeCount: number, treePathCount: number }}
   */
  async stats () {
    let fileCount = 0
    let chunkMapCount = 0
    let syncStateCount = 0
    let treeCount = 0
    let treePathCount = 0

    for await (const entry of this._bee.createReadStream()) {
      if (entry.key.startsWith('file:')) fileCount++
      // Count one per file — skip the `\x00`-suffixed page values.
      else if (entry.key.startsWith('chunkmap:') && !entry.key.includes('\x00')) chunkMapCount++
      else if (entry.key.startsWith('sync:')) syncStateCount++
      else if (entry.key.startsWith('tree:')) treeCount++
      else if (entry.key.startsWith('treepath:')) treePathCount++
    }

    return { fileCount, chunkMapCount, syncStateCount, treeCount, treePathCount }
  }
}
/**
 * Ignore — .overlayignore file parser + default deny-list
 *
 * Gitignore-style pattern matching:
 *   - Lines starting with # are comments
 *   - Blank lines are skipped
 *   - Patterns ending with / match directories only
 *   - * matches anything except /
 *   - ** matches anything including /
 *   - ! negates a pattern (re-include)
 *   - Leading / anchors to the root
 *
 * Default deny-list is always active (security patterns).
 * .overlayignore in a sync dir adds per-directory rules.
 */

import fs from '#fs'
import path from '#path'

// Always blocked — security-critical patterns
const DENY_LIST = [
  '.env', '.env.*',
  '.ssh', '.ssh/**',
  '.gnupg', '.gnupg/**',
  '*.key', '*.pem', '*.p12', '*.pfx',
  'credentials*', '*secret*',
  '.git', '.git/**',
  'node_modules', 'node_modules/**',
  '.DS_Store', 'Thumbs.db',
  '*.overlay-partial',
  '.overlayignore'
]

/**
 * Load ignore patterns from a directory's .overlayignore file
 * @param {string} dir - directory to check for .overlayignore
 * @returns {string[]} patterns from the file (empty if no file)
 */
export function loadIgnoreFile (dir) {
  const filePath = path.join(dir, '.overlayignore')
  try {
    const content = fs.readFileSync(filePath, 'utf-8')
    return parseIgnoreFile(content)
  } catch {
    return []
  }
}

/**
 * Parse ignore file content into patterns
 * @param {string} content
 * @returns {string[]}
 */
export function parseIgnoreFile (content) {
  return content
    .split('\n')
    .map(line => line.trim())
    .filter(line => line && !line.startsWith('#'))
}

/**
 * Create a matcher function from patterns
 * @param {string[]} extraPatterns - user patterns from .overlayignore
 * @param {object} [opts]
 * @param {boolean} [opts.noDenyList] - if true, skip the security-deny list
 *   (publish-time use case: caller already handles node_modules/.git via
 *   rootSkip and may want to publish .env.example intentionally).
 * @returns {(filePath: string, baseName: string) => boolean} returns true if file should be ignored
 */
export function createMatcher (extraPatterns = [], opts = {}) {
  const patterns = opts.noDenyList
    ? [...extraPatterns]
    : [...DENY_LIST, ...extraPatterns]
  const negations = []
  const positives = []

  for (const p of patterns) {
    if (p.startsWith('!')) {
      negations.push(p.slice(1))
    } else {
      positives.push(p)
    }
  }

  return function isIgnored (filePath, baseName) {
    baseName = baseName || path.basename(filePath)

    // Check negations first — if explicitly included, allow it
    for (const pattern of negations) {
      if (matchPattern(baseName, pattern) || matchPattern(filePath, pattern)) {
        return false
      }
    }

    // Check positive patterns
    for (const pattern of positives) {
      if (matchPattern(baseName, pattern) || matchPattern(filePath, pattern)) {
        return true
      }
    }

    return false
  }
}

/**
 * Simple glob pattern matching
 * Supports: *, **, ?, literal characters
 * @param {string} str - string to test
 * @param {string} pattern - glob pattern
 * @returns {boolean}
 */
function matchPattern (str, pattern) {
  // Remove trailing / (directory marker — we treat all as paths)
  pattern = pattern.replace(/\/$/, '')

  // Exact match
  if (str === pattern) return true

  // Convert glob to regex
  let regex = ''
  let i = 0
  while (i < pattern.length) {
    const c = pattern[i]
    if (c === '*') {
      if (pattern[i + 1] === '*') {
        regex += '.*' // ** matches everything
        i += 2
        if (pattern[i] === '/') i++ // skip separator after **
        continue
      }
      regex += '[^/]*' // * matches non-separator
    } else if (c === '?') {
      regex += '[^/]'
    } else if (c === '.') {
      regex += '\\.'
    } else {
      regex += c
    }
    i++
  }

  try {
    return new RegExp('^' + regex + '$').test(str)
  } catch {
    return false
  }
}

/**
 * Get the default deny list
 * @returns {string[]}
 */
export function getDefaultDenyList () {
  return [...DENY_LIST]
}
/**
 * Manifest — ordered chunk list for large files
 *
 * Stores all chunk hashes + sizes for files with >64 chunks.
 * Delta-encoded and DEFLATE compressed for efficient storage/transfer.
 *
 * Binary format:
 *   Header: version (uint8) + chunkCount (uint32BE) + fileHash (32 bytes)
 *   Body: for each chunk: hash (32 bytes) + size (uint16BE)
 *   Compressed: raw DEFLATE
 */

import { deflateRawSync, inflateRawSync } from '#zlib'

/**
 * Encode a chunk list into a compressed manifest
 *
 * @param {Array<{hash: string, size: number}>} chunks - Ordered chunk list
 * @param {string} fileHash - blake2b hex hash of the complete file
 * @returns {Buffer} Compressed manifest
 */
export function encode (chunks, fileHash) {
  // Header: version (1) + chunkCount (4) + fileHash (32) = 37 bytes
  // Body: per chunk: hash (32) + size (2) = 34 bytes each
  const headerSize = 37
  const bodySize = chunks.length * 34
  const raw = Buffer.alloc(headerSize + bodySize)

  // Header
  raw[0] = 1 // version
  raw.writeUInt32BE(chunks.length, 1)
  Buffer.from(fileHash, 'hex').copy(raw, 5, 0, 32)

  // Body
  let offset = headerSize
  for (const chunk of chunks) {
    Buffer.from(chunk.hash, 'hex').copy(raw, offset, 0, 32)
    offset += 32
    raw.writeUInt16BE(Math.min(chunk.size, 65535), offset)
    offset += 2
  }

  return deflateRawSync(raw)
}

/**
 * Decode a compressed manifest back to chunk list
 *
 * @param {Buffer} compressed - Compressed manifest
 * @returns {{ fileHash: string, chunks: Array<{hash: string, size: number}> }}
 */
export function decode (compressed) {
  const raw = inflateRawSync(compressed)

  if (raw.length < 37) throw new Error('Manifest too short')

  const version = raw[0]
  if (version !== 1) throw new Error(`Unsupported manifest version: ${version}`)

  const chunkCount = raw.readUInt32BE(1)
  const fileHash = raw.subarray(5, 37).toString('hex')

  const expectedSize = 37 + chunkCount * 34
  if (raw.length < expectedSize) {
    throw new Error(`Manifest truncated: expected ${expectedSize} bytes, got ${raw.length}`)
  }

  const chunks = []
  let offset = 37
  for (let i = 0; i < chunkCount; i++) {
    const hash = raw.subarray(offset, offset + 32).toString('hex')
    offset += 32
    const size = raw.readUInt16BE(offset)
    offset += 2
    chunks.push({ hash, size })
  }

  return { fileHash, chunks }
}
/**
 * Wire Protocol Messages for hyper-overlay/v2
 *
 * 13 message types for sync + transfer + trees (0.5a extension):
 *  0-2: Sync messages (state exchange, file offers/requests)
 *  3-8: Transfer messages (chunk hashes, data, completion)
 *  9-11: Tree messages (tree-request, tree-response, content-request)
 *  12: transfer-control (downloader→holder pause/stop notice)
 *
 * Messages 9-12 are backward-compatible additions — peers without
 * this code simply don't register the corresponding protomux slots, and
 * senders see no receiver for those messages. File-level sync (0-8)
 * keeps working unchanged.
 *
 * All use compact-encoding codecs with preencode/encode/decode.
 */

import c from 'compact-encoding'

// ── Helpers ───────────────────────────────────────────────────

function toBuffer32 (hashOrBuf) {
  if (Buffer.isBuffer(hashOrBuf)) return hashOrBuf
  return Buffer.from(hashOrBuf, 'hex')
}

// Encode/decode an array of { hash (32-byte hex), length (uint) }
const chunkInfoArray = {
  preencode (state, arr) {
    c.uint32.preencode(state, arr.length)
    for (const item of arr) {
      state.end += 32
      c.uint.preencode(state, item.length)
    }
  },
  encode (state, arr) {
    c.uint32.encode(state, arr.length)
    for (const item of arr) {
      toBuffer32(item.hash).copy(state.buffer, state.start, 0, 32)
      state.start += 32
      c.uint.encode(state, item.length)
    }
  },
  decode (state) {
    const len = c.uint32.decode(state)
    const arr = []
    for (let i = 0; i < len; i++) {
      const hash = state.buffer.subarray(state.start, state.start + 32).toString('hex')
      state.start += 32
      const length = c.uint.decode(state)
      arr.push({ hash, length })
    }
    return arr
  }
}

// Encode/decode an array of uints
const uintArray = {
  preencode (state, arr) {
    c.uint32.preencode(state, arr.length)
    for (const v of arr) c.uint.preencode(state, v)
  },
  encode (state, arr) {
    c.uint32.encode(state, arr.length)
    for (const v of arr) c.uint.encode(state, v)
  },
  decode (state) {
    const len = c.uint32.decode(state)
    const arr = []
    for (let i = 0; i < len; i++) arr.push(c.uint.decode(state))
    return arr
  }
}

// ── Handshake ─────────────────────────────────────────────────

export const handshake = {
  preencode (state, m) {
    c.uint8.preencode(state, m.version)
    c.uint8.preencode(state, m.capabilities)
  },
  encode (state, m) {
    c.uint8.encode(state, m.version)
    c.uint8.encode(state, m.capabilities)
  },
  decode (state) {
    return {
      version: c.uint8.decode(state),
      capabilities: c.uint8.decode(state)
    }
  }
}

// ── Sync Messages (0-2) ──────────────────────────────────────

// Message 0: sync-state — exchange last-synced sequence numbers
export const syncState = {
  preencode (state, m) {
    c.buffer.preencode(state, toBuffer32(m.feedKey))
    c.uint.preencode(state, m.localSeq)
    c.uint.preencode(state, m.remoteSeq)
  },
  encode (state, m) {
    c.buffer.encode(state, toBuffer32(m.feedKey))
    c.uint.encode(state, m.localSeq)
    c.uint.encode(state, m.remoteSeq)
  },
  decode (state) {
    return {
      feedKey: c.buffer.decode(state).toString('hex'),
      localSeq: c.uint.decode(state),
      remoteSeq: c.uint.decode(state)
    }
  }
}

// Message 1: file-offer — announce a file change
export const fileOffer = {
  preencode (state, m) {
    c.string.preencode(state, m.path)
    c.buffer.preencode(state, toBuffer32(m.contentHash))
    c.uint.preencode(state, m.size)
    c.uint.preencode(state, m.mtime)
    c.uint8.preencode(state, m.op)
  },
  encode (state, m) {
    c.string.encode(state, m.path)
    c.buffer.encode(state, toBuffer32(m.contentHash))
    c.uint.encode(state, m.size)
    c.uint.encode(state, m.mtime)
    c.uint8.encode(state, m.op)
  },
  decode (state) {
    return {
      path: c.string.decode(state),
      contentHash: c.buffer.decode(state).toString('hex'),
      size: c.uint.decode(state),
      mtime: c.uint.decode(state),
      op: c.uint8.decode(state)
    }
  }
}

// Message 2: file-request — request a file transfer
export const fileRequest = {
  preencode (state, m) {
    c.string.preencode(state, m.path)
    c.buffer.preencode(state, toBuffer32(m.contentHash))
    c.buffer.preencode(state, m.chunksHave || Buffer.alloc(0))
  },
  encode (state, m) {
    c.string.encode(state, m.path)
    c.buffer.encode(state, toBuffer32(m.contentHash))
    c.buffer.encode(state, m.chunksHave || Buffer.alloc(0))
  },
  decode (state) {
    return {
      path: c.string.decode(state),
      contentHash: c.buffer.decode(state).toString('hex'),
      chunksHave: c.buffer.decode(state)
    }
  }
}

// ── Transfer Messages (3-8) ──────────────────────────────────

// Message 3: chunk-hashes — chunk hash list for a requested file
//
// `more` (paging flag) is appended last. A very large file's
// chunk list serializes past the 16 MiB-1 Noise frame limit
// (@hyperswarm/secret-stream MAX_ATOMIC_WRITE), so the protocol splits it into
// several chunkHashes frames: every page but the last sets more:1, the final
// page sets more:0. The receiver concatenates pages (in arrival order, keyed by
// path) before dispatching. Appended last so a pre-paging peer that omits it
// decodes more:0 — i.e. as a single, complete page (back-compatible).
export const chunkHashes = {
  preencode (state, m) {
    c.string.preencode(state, m.path)
    c.uint8.preencode(state, m.tier)
    chunkInfoArray.preencode(state, m.chunks)
    c.uint8.preencode(state, m.more || 0)
  },
  encode (state, m) {
    c.string.encode(state, m.path)
    c.uint8.encode(state, m.tier)
    chunkInfoArray.encode(state, m.chunks)
    c.uint8.encode(state, m.more || 0)
  },
  decode (state) {
    const path = c.string.decode(state)
    const tier = c.uint8.decode(state)
    const chunks = chunkInfoArray.decode(state)
    // A frame from a pre-paging peer has no trailing byte → more:0.
    const more = state.start < state.end ? c.uint8.decode(state) : 0
    return { path, tier, chunks, more }
  }
}

// Message 4: chunk-need — tell sender which chunks to send
export const chunkNeed = {
  preencode (state, m) {
    c.string.preencode(state, m.path)
    uintArray.preencode(state, m.indices)
  },
  encode (state, m) {
    c.string.encode(state, m.path)
    uintArray.encode(state, m.indices)
  },
  decode (state) {
    return {
      path: c.string.decode(state),
      indices: uintArray.decode(state)
    }
  }
}

// Message 5: chunk-data — send chunk bytes
export const chunkData = {
  preencode (state, m) {
    c.string.preencode(state, m.path)
    c.uint.preencode(state, m.index)
    c.buffer.preencode(state, m.data)
  },
  encode (state, m) {
    c.string.encode(state, m.path)
    c.uint.encode(state, m.index)
    c.buffer.encode(state, m.data)
  },
  decode (state) {
    return {
      path: c.string.decode(state),
      index: c.uint.decode(state),
      data: c.buffer.decode(state)
    }
  }
}

// Message 6: chunk-cancel — cancel a pending transfer
export const chunkCancel = {
  preencode (state, m) {
    c.string.preencode(state, m.path)
    // P5 hotswap: optional per-chunk cancel. Appended so pre-0.3.0 peers still
    // decode {path} and ignore the trailing bytes; absent = whole-transfer cancel.
    if (m.indices && m.indices.length) c.array(c.uint).preencode(state, m.indices)
  },
  encode (state, m) {
    c.string.encode(state, m.path)
    if (m.indices && m.indices.length) c.array(c.uint).encode(state, m.indices)
  },
  decode (state) {
    const path = c.string.decode(state)
    // Keep the pre-0.3.0 {path} shape byte-for-byte when no indices trail, so a
    // whole-transfer cancel decodes identically on old and new peers.
    if (state.start < state.end) return { path, indices: c.array(c.uint).decode(state) }
    return { path }
  }
}

// Message 7: transfer-complete — confirm file fully received
export const transferComplete = {
  preencode (state, m) {
    c.string.preencode(state, m.path)
    c.buffer.preencode(state, toBuffer32(m.contentHash))
  },
  encode (state, m) {
    c.string.encode(state, m.path)
    c.buffer.encode(state, toBuffer32(m.contentHash))
  },
  decode (state) {
    return {
      path: c.string.decode(state),
      contentHash: c.buffer.decode(state).toString('hex')
    }
  }
}

// Message 8: conflict — notify peer of a detected conflict
export const conflict = {
  preencode (state, m) {
    c.string.preencode(state, m.path)
    c.buffer.preencode(state, toBuffer32(m.myHash))
    c.buffer.preencode(state, toBuffer32(m.theirHash))
    c.buffer.preencode(state, toBuffer32(m.ancestorHash || Buffer.alloc(32)))
  },
  encode (state, m) {
    c.string.encode(state, m.path)
    c.buffer.encode(state, toBuffer32(m.myHash))
    c.buffer.encode(state, toBuffer32(m.theirHash))
    c.buffer.encode(state, toBuffer32(m.ancestorHash || Buffer.alloc(32)))
  },
  decode (state) {
    return {
      path: c.string.decode(state),
      myHash: c.buffer.decode(state).toString('hex'),
      theirHash: c.buffer.decode(state).toString('hex'),
      ancestorHash: c.buffer.decode(state).toString('hex')
    }
  }
}

// ── Tree Messages (9-11) — 0.5a extension ────────────────────

// Encode/decode an array of tree entries: { kind, exec, name, childHash, size }
const treeEntryArray = {
  preencode (state, arr) {
    c.uint32.preencode(state, arr.length)
    for (const e of arr) {
      c.uint8.preencode(state, e.kind)
      c.uint8.preencode(state, e.exec || 0)
      c.string.preencode(state, e.name)
      state.end += 32 // childHash fixed32
      c.uint.preencode(state, e.size || 0)
    }
  },
  encode (state, arr) {
    c.uint32.encode(state, arr.length)
    for (const e of arr) {
      c.uint8.encode(state, e.kind)
      c.uint8.encode(state, e.exec || 0)
      c.string.encode(state, e.name)
      toBuffer32(e.childHash).copy(state.buffer, state.start, 0, 32)
      state.start += 32
      c.uint.encode(state, e.size || 0)
    }
  },
  decode (state) {
    const len = c.uint32.decode(state)
    const arr = []
    for (let i = 0; i < len; i++) {
      const kind = c.uint8.decode(state)
      const exec = c.uint8.decode(state)
      const name = c.string.decode(state)
      const childHash = state.buffer.subarray(state.start, state.start + 32).toString('hex')
      state.start += 32
      const size = c.uint.decode(state)
      arr.push({ kind, exec, name, childHash, size })
    }
    return arr
  }
}

// Message 9: tree-request — ask peer for a tree by its content hash
//
// `nonce` (per-request id) is appended last; the holder echoes it in every
// treeResponse page so the requester can drop pages from a superseded attempt.
// A pre-nonce peer omits it and decodes 0, which the requester treats as
// "unverifiable".
export const treeRequest = {
  preencode (state, m) {
    c.buffer.preencode(state, toBuffer32(m.hash))
    c.uint.preencode(state, m.nonce || 0)
  },
  encode (state, m) {
    c.buffer.encode(state, toBuffer32(m.hash))
    c.uint.encode(state, m.nonce || 0)
  },
  decode (state) {
    const hash = c.buffer.decode(state).toString('hex')
    const nonce = state.start < state.end ? c.uint.decode(state) : 0
    return { hash, nonce }
  }
}

// Message 10: tree-response — return a tree's entries to the requester
//
// `more` (paging flag) mirrors chunkHashes: a tree too large for one 16 MiB-1
// Noise frame is split into several frames, every page but the last more:1.
// `nonce` echoes the requester's per-request id so pages from a superseded
// attempt are dropped. Both are appended last; a pre-paging peer omits them and
// decodes 0.
export const treeResponse = {
  preencode (state, m) {
    c.buffer.preencode(state, toBuffer32(m.hash))
    treeEntryArray.preencode(state, m.entries)
    c.uint8.preencode(state, m.more || 0)
    c.uint.preencode(state, m.nonce || 0)
  },
  encode (state, m) {
    c.buffer.encode(state, toBuffer32(m.hash))
    treeEntryArray.encode(state, m.entries)
    c.uint8.encode(state, m.more || 0)
    c.uint.encode(state, m.nonce || 0)
  },
  decode (state) {
    const hash = c.buffer.decode(state).toString('hex')
    const entries = treeEntryArray.decode(state)
    const more = state.start < state.end ? c.uint8.decode(state) : 0
    const nonce = state.start < state.end ? c.uint.decode(state) : 0
    return { hash, entries, more, nonce }
  }
}

// Message 11: content-request — fetch a file by its content hash (not path)
// Sender locates any local file matching the hash and serves its chunks.
// `from` carries the requester's profile-key (hex) so the
// holder's serveAuthorizer can authenticate the asker. Appended last → an
// overlay peer that predates this field decodes '' (empty), never crashes.
export const contentRequest = {
  preencode (state, m) {
    c.buffer.preencode(state, toBuffer32(m.contentHash))
    c.buffer.preencode(state, m.chunksHave || Buffer.alloc(0))
    c.string.preencode(state, m.from || '')
  },
  encode (state, m) {
    c.buffer.encode(state, toBuffer32(m.contentHash))
    c.buffer.encode(state, m.chunksHave || Buffer.alloc(0))
    c.string.encode(state, m.from || '')
  },
  decode (state) {
    return {
      contentHash: c.buffer.decode(state).toString('hex'),
      chunksHave: c.buffer.decode(state),
      from: c.string.decode(state)
    }
  }
}

// Message 12: transfer-control — downloader→holder pause/stop notice for a
// content-addressed fetch. state: 0 = paused, 1 = stopped. Appended last so a
// holder that predates this never registers slot 12 and silently ignores it.
export const transferControl = {
  preencode (state, m) {
    c.buffer.preencode(state, toBuffer32(m.contentHash))
    c.uint8.preencode(state, m.state)
  },
  encode (state, m) {
    c.buffer.encode(state, toBuffer32(m.contentHash))
    c.uint8.encode(state, m.state)
  },
  decode (state) {
    return {
      contentHash: c.buffer.decode(state).toString('hex'),
      state: c.uint8.decode(state)
    }
  }
}

// Message 13: transfer-progress — downloader→holder one-shot have-baseline for a
// resumed content-addressed fetch. `have` = bytes the downloader already holds on
// disk at resume, so the holder's "who is downloading" bar reflects the downloader's
// TRUE completion, not only the bytes it re-serves. Appended last so a holder that
// predates this never registers slot 13 and silently ignores it.
export const transferProgress = {
  preencode (state, m) {
    c.buffer.preencode(state, toBuffer32(m.contentHash))
    c.uint.preencode(state, m.have || 0)
  },
  encode (state, m) {
    c.buffer.encode(state, toBuffer32(m.contentHash))
    c.uint.encode(state, m.have || 0)
  },
  decode (state) {
    return {
      contentHash: c.buffer.decode(state).toString('hex'),
      have: c.uint.decode(state)
    }
  }
}
/**
 * HyperOverlay — content-addressed file serving over the V2 protocol.
 *
 * A thin facade over the V2 stack (FileIndex + SyncEngine + TransferManager +
 * OverlayProtocol) for the "serve a file to whoever asks, fetch a file from
 * whoever has it" use case — distinct from V1's local chunk-registry blobs.
 *
 * Model (the deliberate post-Hyperdrive design): the canonical bytes are a
 * REAL FILE ON DISK. The index holds only metadata + chunk maps (no blob
 * bytes), so there is no hypercore storage balloon. A peer fetches a file by
 * its content hash; chunks are read from the source file on demand, streamed,
 * hash-verified, and written to the requester's disk.
 *
 * `autoSync` is OFF: this is on-demand content fetch (requestContent →
 * _onContentRequest), NOT Syncthing-style folder push. Registering a file
 * makes it servable; nothing is offered to peers unsolicited.
 *
 * Requester flow: pre-register a synthetic `content:<hash>` → destPath
 * mapping, requestContent,
 * await the hash-verified transferComplete, walk peers on timeout/mismatch.
 */

import ReadyResource from 'ready-resource'
import fs from 'bare-fs'
import path from 'bare-path'
import os from 'bare-os'
import crypto from 'hypercore-crypto'

import { FileIndex } from './file-index.js'
import { SyncEngine } from './sync-engine.js'
import { TransferManager } from './transfer.js'
import { OverlayProtocol } from './protocol.js'
import { createStreamingHasher } from './chunker.js'

const DEFAULT_PER_PEER_TIMEOUT = 30000
const DEFAULT_IDLE_TIMEOUT = 10000

// Stream-verify a file against its overlay content hash WITHOUT buffering
// it — a readFileSync of a multi-GB blob OOMs the process. Matches
// crypto.data(buffer): blake2b(LEAF_TYPE || uint64-LE(size) || bytes), which is
// exactly createStreamingHasher({ size }). Returns the size so callers can re-seed
// without a second stat.
async function hashOnDisk (filePath) {
  let size
  try { size = fs.statSync(filePath).size } catch { return null }
  const h = createStreamingHasher({ size })
  try {
    await new Promise((resolve, reject) => {
      const rs = fs.createReadStream(filePath)
      rs.on('data', (c) => h.update(c))
      rs.on('end', resolve)
      rs.on('error', reject)
    })
  } catch { return null }
  return { contentHash: h.digest(), size }
}

async function verifyOnDisk (filePath, contentHash) {
  const r = await hashOnDisk(filePath)
  if (!r) return { ok: false, size: 0 }
  return { ok: r.contentHash === contentHash, size: r.size }
}

export class HyperOverlay extends ReadyResource {
  constructor (corestore, opts = {}) {
    super()
    this._corestore = corestore.namespace(opts.namespace || 'overlay-v2')
    this._destDir = opts.destDir || path.join(os.tmpdir(), 'hyper-overlay-v2-fetch')
    this._journalDir = opts.journalDir || null
    this._syncBaseDirs = opts.syncBaseDirs || []
    this._perPeerTimeout = opts.perPeerTimeout || DEFAULT_PER_PEER_TIMEOUT
    this._idleTimeout = opts.idleTimeout || DEFAULT_IDLE_TIMEOUT

    // Serve-authorization + identity opts threaded to OverlayProtocol.
    // serveAuthorizer gates every inbound content-request; localProfileKey stamps
    // outbound content-requests so the remote's gate can authenticate the asker.
    this._serveAuthorizer = opts.serveAuthorizer || null
    this._localProfileKey = opts.localProfileKey || null

    // Per-instance protocol channel id (opts.key) — lets N overlays coexist on ONE shared mux
    // (e.g. a multi-DID daemon: one overlay per DID, each keyed by its roomKey, on one connection).
    // Without it they collide on (PROTOCOL, null) and only the first channel opens.
    this._key = opts.key || null

    // At-rest key for the local index cores (file-index/index-meta/sync-feed).
    this._indexEncryptionKey = opts.indexEncryptionKey || null

    // serve-side download telemetry (sender-side download indicator).
    this._onServeStart = opts.onServeStart || null
    this._onChunkServe = opts.onChunkServe || null
    this._onServeEnd = opts.onServeEnd || null
    this._onServeControl = opts.onServeControl || null
    this._onServeProgress = opts.onServeProgress || null

    // Shared maps the protocol reads: overlayPath → diskPath (for serving an
    // offered/known path), contentHash → diskPath (fast path for content
    // requests). Both populated by registerFile.
    this._filePaths = new Map()
    this._contentHashPaths = new Map()

    // contentHash → pending fetch { destPath, finish, timers, bytesReceived }
    this._pendingContent = new Map()

    // Built lazily on first use (Hyperdrive-style: a drive that never touches
    // its blobs core never opens one). A daemon that never registers, serves,
    // or connects a peer for attachments pays nothing — no feed, no index.
    this._index = null
    this._sync = null
    this._transfer = null
    this._protocol = null
    this._stackPromise = null
  }

  // The protocol must exist synchronously when attachProtocol is called —
  // protomux will NOT pair a channel created after the remote opened theirs
  // (verified). So the stack is built at ready time. _ensure stays as the
  // idempotent builder (used by register/fetch, and as a safety net).
  async _open () { await this._ensure() }

  async _close () {
    // Only tear down what was actually built.
    if (this._protocol) this._protocol.destroy()
    if (this._sync) await this._sync.close()
    if (this._index) await this._index.close()
  }

  // Lazily build the V2 stack. Idempotent + concurrency-safe (a single
  // in-flight promise is shared). No-op cost once built.
  _ensure () {
    if (this._protocol) return Promise.resolve()
    if (this._stackPromise) return this._stackPromise
    this._stackPromise = (async () => {
      const index = new FileIndex(this._corestore, { encryptionKey: this._indexEncryptionKey })
      await index.ready()
      const sync = new SyncEngine(index, this._corestore, { encryptionKey: this._indexEncryptionKey })
      await sync.ready()
      const transfer = new TransferManager(index, { journalDir: this._journalDir })
      const protocol = new OverlayProtocol(sync, transfer, {
        key: this._key,
        filePaths: this._filePaths,
        contentHashPaths: this._contentHashPaths,
        syncBaseDirs: this._syncBaseDirs,
        autoSync: false,
        serveAuthorizer: this._serveAuthorizer,   // serve gate
        localProfileKey: this._localProfileKey,    // outbound identity
        onSynced: (info) => this._onSynced(info),
        onChunkProgress: (info) => this._onChunkProgress(info),
        onServeStart: this._onServeStart,
        onChunkServe: this._onChunkServe,
        onServeEnd: this._onServeEnd,
        onServeControl: this._onServeControl,
        onServeProgress: this._onServeProgress
      })
      try { fs.mkdirSync(this._destDir, { recursive: true }) } catch {}
      if (this._journalDir) { try { fs.mkdirSync(this._journalDir, { recursive: true }) } catch {} }
      this._index = index
      this._sync = sync
      this._transfer = transfer
      this._protocol = protocol
    })()
    return this._stackPromise
  }

  // The host's swarm transport must call this synchronously per connection. The
  // channel MUST be created synchronously — protomux will not pair a channel
  // opened after the remote's (verified). The stack is built at ready, so
  // this._protocol is always present by the time connections arrive.
  // INSTRUMENTED: this used to `return` silently on every guard, which is unobservable from the host.
  // A host wrapping the call in try/catch (the atproto daemon does) sees NO exception and concludes the
  // attach succeeded, while `peerCount` reports 0 either way — `_protocol` being null and "attached but
  // no peers" are indistinguishable from outside. That ambiguity sent a live investigation at the
  // content layer for days when byte transfer was simply never wired up. Attach is load-bearing (byte
  // resolution gates signing), so a no-op here must say so exactly once per reason.
  attachProtocol (mux) {
    if (this.closing || this.closed) return this._logAttachSkip('closing')
    if (!this._protocol) return this._logAttachSkip('protocol-not-built')
    const peer = this._protocol.attach(mux)
    // attach() returns null for a duplicate channel, or one the remote already closed.
    if (!peer) this._logAttachSkip('channel-unavailable')
    return peer
  }

  _logAttachSkip (reason) {
    if (!this._attachSkips) this._attachSkips = new Set()
    if (this._attachSkips.has(reason)) return null
    this._attachSkips.add(reason)
    try {
      console.error(JSON.stringify({
        msg: 'hyper-overlay: attachProtocol did NOT attach — this connection will move NO bytes for this overlay',
        reason,
        overlayKey: this._key ? this._key.toString('hex').slice(0, 16) : null
      }))
    } catch {}
    return null
  }

  get peerCount () { return this._protocol ? this._protocol.peerCount : 0 }

  // The overlay's local-only cores (file-index + version marker + sync feed), so the
  // leftover scan can treat them as wanted and the storage breakdown can size them.
  localCores () {
    const cores = this._index ? this._index.cores : []
    if (this._sync?.feed) cores.push(this._sync.feed)
    return cores
  }

  cleanJournals (maxAge) { try { return this._transfer?.cleanJournals(maxAge) || [] } catch { return [] } }

  /**
   * Make an on-disk file servable by its content hash.
   * @param {string} overlayPath logical path, e.g. '/attachments/<oid>'
   * @param {string} diskPath absolute path to the canonical bytes on disk
   * @param {object} [meta]
   *  meta.contentHash preferred — the oid the caller already computed
   *  meta.size file size (avoids an extra stat if known)
   * @returns {Promise<{ contentHash, size } | null>} null if the source vanished
   */
  // Seed the overlayPath -> disk and contentHash -> disk maps from the PERSISTED FileIndex, using a
  // caller-supplied layout resolver. Returns the number of entries seeded.
  //
  // WHY THIS EXISTS. Both maps are in-memory and populated only by registerFile, and FileIndex
  // deliberately persists no disk path (`putFile` stores contentHash/size/mtime) — layout belongs to
  // the consumer, which is correct: a sidecar has one working tree per repo, an atproto peer has one
  // records dir per DID. But it means that after a RESTART a node can serve only what it has written
  // since boot, even though every file is still on disk and its chunk map is still in the index.
  // `_onContentRequest`'s tier-3 fallback then finds the right index entry by hash and uses the
  // OVERLAY path as if it were a filesystem path, which never exists — so the offer is silently
  // dropped and the requester just times out.
  //
  // Measured cost of that: an atproto co-owner held a record's exact bytes on disk (hash verified
  // equal to the requester's oid) and reported "no connected peer served this oid" for 75+ hours,
  // which blocks the peer from signing at all. pgh-sidecar hit the same wall and solved it with a
  // bespoke pre-seed (OverlayManager.populateFilePaths) plus a protocol subclass; this makes that a
  // first-class operation so every consumer gets it instead of re-deriving it.
  //
  // Cheap on purpose: one index scan, NO re-hashing and NO file reads — the contentHash is already
  // persisted per path, and the consumer supplies path -> disk. It also pays for itself, because
  // tier 3 otherwise re-scans the whole index on EVERY content request; seeded, tier 1 hits instead.
  //
  // resolve: (overlayPath) => absolute disk path | null   (null/throw skips that entry)
  async seedFilePathsFromIndex (resolve) {
    if (typeof resolve !== 'function') throw new Error('seedFilePathsFromIndex: a resolve(overlayPath) function is required')
    await this._ensure()
    let seeded = 0
    for (const f of await this._index.listFiles()) {
      let diskPath = null
      // The ENTRY is passed too. A consumer builds its "already known, skip the
      // repair pass" set from this callback, and a path alone cannot tell a
      // fully-seeded entry from a half-seeded one -- see the count below.
      try { diskPath = resolve(f.path, f) } catch { continue }
      if (!diskPath) continue
      this._filePaths.set(f.path, diskPath)
      if (!f.contentHash) {
        // putFile persists `contentHash: meta.contentHash || null` with a
        // `hashing` flag, so a null hash is a first-class STORED state -- an
        // entry indexed while hashing was still in flight keeps it forever.
        // Such an entry seeds the PATH map but can never be served BY OID.
        //
        // It must NOT count as seeded. It used to, which made the boot log read
        // healthy while the bytes were unreachable, and made consumers treat the
        // entry as already-known so their repair pass skipped the one case it
        // was written for. Cost three production sightings (2026-08-08 136 MiB
        // APK, 2026-08-25 471 MB zip, 2026-08-29 168 MB APK) of a peer being
        // told "no holder" for a file sitting on disk with a matching hash.
        continue
      }
      this._contentHashPaths.set(f.contentHash, diskPath)
      seeded++
    }
    // Deliberately still a NUMBER, but now meaning "entries servable BY OID".
    // atproto-store logs this as "servable-by-oid after restart" -- a claim that
    // was false before this change and is true after it, with no edit there.
    return seeded
  }

  async registerFile (overlayPath, diskPath, meta = {}) {
    await this._ensure()
    // Source can vanish between prepare and register (a file moved out of a
    // shared folder mid-scan). Nothing to serve — bail with null instead of throwing
    // so a routine move doesn't crash the publish path.
    let stat
    try {
      stat = fs.statSync(diskPath)
    } catch {
      return null
    }
    const size = typeof meta.size === 'number' ? meta.size : stat.size
    const contentHash = meta.contentHash || await hashFileOnDisk(diskPath, size)

    await this._index.putFile(overlayPath, { contentHash, size, mtime: stat.mtimeMs })
    this._filePaths.set(overlayPath, diskPath)
    this._contentHashPaths.set(contentHash, diskPath)
    // Build (and cache) the chunk map so a content request can stream
    // immediately without re-chunking under the request. meta.prepare:false
    // skips this eager full second read of the file — the file is already servable
    // via the maps above and the serve path (_onContentRequest) chunks lazily on
    // first request; publishers that already built the chunk map in one pass
    // (prepareForServe) pass false so registration doesn't re-read the file.
    if (meta.prepare !== false) await this._transfer.prepareFile(diskPath, overlayPath)
    // Record in the local change feed (used by the protocol's markSynced
    // bookkeeping; with autoSync off it is not broadcast).
    await this._sync.logPut(overlayPath, { contentHash, size, mtime: stat.mtimeMs, rev: meta.rev }) // rev optional (app version, e.g. atproto TID) for rev-based conflict resolution
    return { contentHash, size }
  }

  // One-pass hash + chunk-map build for the publish path: streams the file
  // ONCE, persists the content-addressed chunk map (durable in FileIndex), and
  // returns the content hash. Replaces a hash-only pass plus a lazy fetch-time
  // re-chunk with a single read, so the first fetcher never waits on chunk indexing.
  // Stop serving a path AND record the deletion in the local change feed (bookkeeping; autoSync is off
  // so it is not broadcast — convergence is an application concern, not this library's). The authoring
  // counterpart to a shim deleting a record. Idempotent.
  async unregisterFile (overlayPath) {
    await this._ensure()
    this._filePaths.delete(overlayPath)
    await this._sync.logDel(overlayPath)
  }

  async prepareForServe (diskPath, opts = {}) {
    await this._ensure()
    // The overlay path below is a throwaway key — byHashOnly persists ONLY the
    // content-addressed chunk map, nothing under this path.
    const prepared = await this._transfer.prepareFile(diskPath, '/mir-prep' + diskPath, { onProgress: opts.onProgress, byHashOnly: true, signal: opts.signal })
    return prepared ? { contentHash: prepared.contentHash, size: prepared.size } : null
  }

  // READ-ONLY: the content hash the bytes currently on disk at `filePath` would be registered under,
  // plus their size. Returns null when the file is absent or unreadable. Writes nothing — no index
  // entry, no registration, no fetch — so it is safe to run against a live store.
  //
  // WHY THIS IS PUBLIC: a content-addressed store's correctness rests on "the bytes at this path hash
  // to the oid the pointer says", and nothing could ask that question from outside. `registerFile`
  // answers it only as a side effect of WRITING an index entry, and `verifyOnDisk` (used by the fetch
  // path) is module-private and boolean-only. So a consumer holding a converged pointer had no way to
  // check its own local file against it — which is exactly how a STALE local file goes unnoticed:
  // OverlayRepoStore.ensureLocal returns a local hit without ever comparing it to the pointer's oid,
  // so a node keeps serving superseded bytes forever and never re-fetches. Found on wj62, where two
  // co-owners held the same 139 converged pointers but different bytes for app.bsky.actor.profile/self
  // (two different avatar blobs), so the signed repo flipped content on every signer change.
  async hashFile (filePath) {
    return hashOnDisk(filePath)
  }

  async evictContent (contentHash) {
    await this._ensure()
    return this._index.evictContent(contentHash)
  }

  async compactIndex (opts) {
    await this._ensure()
    return this._index.compact(opts)
  }

  /**
   * Fetch a file by its content hash. Serves from the local disk copy if we
   * have it; otherwise pulls from a connected peer that does (hash-verified).
   * @param {string} contentHash
   * @param {object} [opts] opts.timeout (idle), opts.destPath
   * @returns {Promise<{ destPath: string, local: boolean, size: number } | null>}
   */
  async fetchFile (contentHash, opts = {}) {
    await this._ensure()

    const localDisk = this._contentHashPaths.get(contentHash)
    if (localDisk) {
      const v = await verifyOnDisk(localDisk, contentHash)
      if (v.ok) return { destPath: localDisk, local: true, size: v.size }
    }

    // Wait briefly for at least one peer — a connection may be mid-handshake
    // (lazy attach) or just forming when the fetch is issued. Without this a
    // fetch issued the instant a peer connects would spuriously 404.
    const peerWaitMs = opts.peerWaitMs || 3000
    for (let waited = 0; this._protocol._peers.size === 0 && waited < peerWaitMs; waited += 100) {
      await new Promise(r => setTimeout(r, 100))
    }
    if (this._protocol._peers.size === 0) {
      // No fetchContent will run, so drop any cancel recorded during the wait — a
      // stale marker would otherwise cancel the next fetch of the same content.
      this._protocol.clearCancelPending(contentHash)
      return null
    }

    // Multi-source: fetch chunks in parallel from ALL connected peers that
    // have the file (torrent-style). The scheduler dedups + fails over.
    const peers = [...this._protocol._peers.values()]
    // opts.mirrorSpool: store the verified blob CONTENT-ADDRESSED at
    // <mirrorSpool>/<contentHash> rather than a throwaway destDir name. This is
    // the mirror path — a node with no working tree of its own fetches files
    // into a content-addressed spool so it durably re-serves them (via the
    // <syncBaseDir>/<contentHash> resolution in _onContentRequest).
    let destPath
    if (opts.destPath) {
      destPath = opts.destPath
    } else if (opts.mirrorSpool) {
      try { fs.mkdirSync(opts.mirrorSpool, { recursive: true }) } catch {}
      destPath = path.join(opts.mirrorSpool, contentHash)
    } else {
      destPath = path.join(
        this._destDir,
        'blob-' + contentHash + '-' + Date.now() + '-' + Math.random().toString(36).slice(2, 8)
      )
    }
    // Whole-file integrity is verified INCREMENTALLY during the transfer (the
    // scheduler passes contentHash to TransferManager, which hashes the file in
    // offset order as chunks land and rejects in finalize on a mismatch). So the
    // assembled file is already verified here — no trailing whole-file re-read.
    let result
    try {
      result = await this._protocol.fetchContent(contentHash, peers, {
        destPath,
        timeout: opts.timeout,
        onProgress: opts.onProgress || (() => {}),   // forward bytes/total
        onVerify: opts.onVerify,                      // resume re-verify fraction
        onEnd: opts.onEnd                             // terminal diagnostic (reason + bytes/chunks)
      })
    } catch (err) {
      // An integrity failure or an explicit cancel/pause is distinct from a
      // no-holder / stall — surface it so the caller doesn't treat it as a failure.
      if (err?.code === 'EHASHMISMATCH' || err?.code === 'ECANCELLED') throw err
      // A local I/O error (full disk / read-only / permission / vanished
      // mount) is not a no-holder — surface it so the consumer can pause rather than
      // retry forever.
      if (err?.code === 'ENOSPC' || err?.code === 'EACCES' || err?.code === 'EROFS' || err?.code === 'EPERM' || err?.code === 'ENOENT') throw err
      return null
    }
    const size = result?.size ?? fs.statSync(destPath).size
    // Seeding multiplication: register the verified blob locally so this node
    // now SERVES these chunks to other peers too. A file held by N peers then
    // gets faster as more peers fetch it (torrent-style). Best-effort — the
    // fetch still succeeds even if re-registration fails. Skipped if a peer
    // raced us to register the same hash (registerFile is idempotent-safe).
    // reSeed:false — opt-out for consumers that should hold nothing.
    if (opts.reSeed !== false && !this._contentHashPaths.has(contentHash)) {
      try {
        await this.registerFile('/attachments/' + contentHash, destPath, { contentHash, size })
      } catch {}
    }
    return { destPath, local: false, size }
  }

  // Stop an in-flight fetchFile for this content hash. opts.discardPartial
  // true unlinks the partial (cancel), false keeps it for resume (pause). The
  // pending fetchFile rejects with ECANCELLED, which the caller treats as not-a-failure.
  cancelFetch (contentHash, opts = {}) {
    return this._protocol ? this._protocol.cancelContent(contentHash, opts) : false
  }

  // Tell holders we stopped pulling this hash when there's no in-flight fetch
  // to tear down (e.g. discarding an already-paused transfer), so their indicator clears.
  notifyTransferStopped (contentHash) {
    return this._protocol ? this._protocol.sendStopControl(contentHash) : false
  }

  _destPathFor (contentHash) {
    return path.join(this._destDir, 'blob-' + contentHash)
  }

  _fetchFromPeer (peer, contentHash, opts = {}) {
    const syntheticPath = 'content:' + contentHash
    const destPath = path.join(
      this._destDir,
      'blob-' + contentHash + '-' + Date.now() + '-' + Math.random().toString(36).slice(2, 8)
    )
    // The protocol's chunk handlers write to whatever _filePaths maps the
    // synthetic path to. Per-attempt destPath so concurrent peer tries don't
    // clobber one another.
    this._filePaths.set(syntheticPath, destPath)

    return new Promise((resolve, reject) => {
      let settled = false
      const finish = (err, result) => {
        if (settled) return
        settled = true
        clearTimeout(timer)
        clearTimeout(pending.idleTimer)
        this._pendingContent.delete(contentHash)
        if (err) reject(err); else resolve(result)
      }
      const timer = setTimeout(() => {
        finish(new Error('content request timed out: ' + contentHash.slice(0, 12)))
      }, opts.timeout || this._perPeerTimeout)
      const idleFire = () => finish(new Error('peer went silent mid-stream: ' + contentHash.slice(0, 12)))
      const pending = {
        syntheticPath, destPath, finish, peer, contentHash,
        bytesReceived: 0,
        idleTimer: setTimeout(idleFire, this._idleTimeout),
        idleFire
      }
      this._pendingContent.set(contentHash, pending)
      this._protocol.requestContent(peer, contentHash, null)
    })
  }

  _onChunkProgress (info) {
    if (!info || !info.path || !info.path.startsWith('content:')) return
    const contentHash = info.path.slice('content:'.length)
    const pending = this._pendingContent.get(contentHash)
    if (!pending) return
    pending.bytesReceived += (info.bytes || 0)
    clearTimeout(pending.idleTimer)
    pending.idleTimer = setTimeout(pending.idleFire, this._idleTimeout)
  }

  // The V2 chunk flow does NOT verify the reassembled file against the
  // advertised hash (that's the caller's job in this protocol). We do it here
  // so fetchFile never returns un-verified bytes.
  _onSynced (info) {
    if (!info || info.direction !== 'pull') return
    if (!info.path || !info.path.startsWith('content:')) return
    const contentHash = info.path.slice('content:'.length)
    const pending = this._pendingContent.get(contentHash)
    if (!pending) return
    try {
      const data = fs.readFileSync(pending.destPath)
      const actual = crypto.data(data).toString('hex')
      if (actual !== contentHash) {
        pending.finish(Object.assign(
          new Error('content-hash mismatch: expected ' + contentHash.slice(0, 12) + '…, got ' + actual.slice(0, 12) + '…'),
          { code: 'EHASHMISMATCH', expected: contentHash, actual }
        ))
        return
      }
      pending.finish(null, data)
    } catch (err) {
      pending.finish(err)
    }
  }
}

// Stream a file through the chunker's hasher to get its oid without buffering
// the whole file in memory (matches /api/upload's createStreamingHasher oid).
async function hashFileOnDisk (diskPath, size) {
  const hasher = createStreamingHasher({ size })
  for await (const buf of fs.createReadStream(diskPath)) hasher.update(buf)
  return hasher.digest()
}
/**
 * Pointer Format — git clean/smudge filter interchange format
 *
 * Stored in git objects instead of actual binary file content.
 * Two formats based on chunk count:
 *
 * Small file (≤64 chunks, inline):
 *   hyper-overlay v1
 *   oid blake2b:<file-hash>
 *   size <bytes>
 *   chunks <count>
 *   blake2b:<chunk-0-hash> <chunk-0-size>
 *   blake2b:<chunk-1-hash> <chunk-1-size>
 *   ...
 *
 * Large file (>64 chunks, manifest reference):
 *   hyper-overlay v1
 *   oid blake2b:<file-hash>
 *   size <bytes>
 *   chunks <count>
 *   manifest blake2b:<manifest-hash>
 */

const VERSION = 1
const HEADER = 'hyper-overlay v1'
const INLINE_MAX = 64

/**
 * Serialize file info to pointer string
 *
 * @param {Object} info
 * @param {string} info.oid - blake2b hex hash of complete file
 * @param {number} info.size - total file size in bytes
 * @param {number} info.chunkCount - number of chunks
 * @param {Array<{hash: string, size: number}>} [info.chunks] - inline chunk list (≤64)
 * @param {string} [info.manifestHash] - manifest hash for large files (>64 chunks)
 * @returns {string}
 */
export function serialize (info) {
  const lines = [
    HEADER,
    `oid blake2b:${info.oid}`,
    `size ${info.size}`,
    `chunks ${info.chunkCount}`
  ]

  if (info.chunks && info.chunkCount <= INLINE_MAX) {
    for (const c of info.chunks) {
      lines.push(`blake2b:${c.hash} ${c.size}`)
    }
  } else if (info.manifestHash) {
    lines.push(`manifest blake2b:${info.manifestHash}`)
  }

  return lines.join('\n') + '\n'
}

/**
 * Parse a pointer string back to file info
 *
 * @param {string} text - Pointer file content
 * @returns {Object} { version, oid, size, chunkCount, chunks, manifestHash }
 */
export function parse (text) {
  const lines = text.trim().split('\n')

  // Minimum viable pointer is 3 lines: header + oid + size. Older
  // serializers (pre-chunk-metadata) produced this 3-line shape; the
  // chunks line + inline chunk list / manifest came later. parsers must
  // accept the legacy form so pre-chunks pointers in existing repos still
  // resolve via oid (the only field needed for fetch-by-oid).
  if (lines.length < 3 || lines[0] !== HEADER) {
    throw new Error('Invalid hyper-overlay pointer')
  }

  const oidMatch = lines[1].match(/^oid blake2b:([0-9a-f]{64})$/)
  if (!oidMatch) throw new Error('Invalid pointer: bad oid line')

  const sizeMatch = lines[2].match(/^size (\d+)$/)
  if (!sizeMatch) throw new Error('Invalid pointer: bad size line')

  // chunks line is optional for legacy compat; default to chunkCount 0.
  let chunkCount = 0
  if (lines.length >= 4) {
    const chunksMatch = lines[3].match(/^chunks (\d+)$/)
    if (chunksMatch) chunkCount = parseInt(chunksMatch[1], 10)
    // If line 3 isn't a `chunks N` declaration, treat as legacy 3-line
    // pointer (chunks omitted) and let further-line parsing decide via
    // its own startsWith checks below.
  }

  const result = {
    version: VERSION,
    oid: oidMatch[1],
    size: parseInt(sizeMatch[1], 10),
    chunkCount,
    chunks: null,
    manifestHash: null
  }

  if (lines.length > 4) {
    const line4 = lines[4]

    if (line4.startsWith('manifest blake2b:')) {
      result.manifestHash = line4.slice('manifest blake2b:'.length)
    } else if (line4.startsWith('blake2b:')) {
      // Inline chunk list
      result.chunks = []
      for (let i = 4; i < lines.length; i++) {
        const parts = lines[i].split(' ')
        if (parts.length !== 2 || !parts[0].startsWith('blake2b:')) break
        result.chunks.push({
          hash: parts[0].slice('blake2b:'.length),
          size: parseInt(parts[1], 10)
        })
      }
    }
  }

  return result
}

/**
 * Check if a string is a hyper-overlay pointer
 *
 * @param {string|Buffer} content
 * @returns {boolean}
 */
export function isPointer (content) {
  const text = typeof content === 'string' ? content : content.toString('utf-8')
  return text.startsWith(HEADER + '\n')
}

export { INLINE_MAX }
/**
 * OverlayProtocol — Protomux channel for sync + transfer
 *
 * Sync approach: each side sends its local feed entries directly via
 * file-offer messages. No Hypercore feed replication needed — the feed
 * entries are small JSON objects sent over our own Protomux channel.
 *
 * Flow:
 *  1. Channel opens → both send sync-state (feed length)
 *  2. Side with more entries sends file-offers for each change
 *  3. Receiver auto-requests files it doesn't have
 *  4. Sender chunks on demand → streams chunks → receiver writes to disk
 */

import path from 'bare-path'
import * as messages from './messages.js'
import { hashChunk, selectTier } from './chunker.js'
import { ChunkScheduler } from './chunk-scheduler.js'
import fs from 'bare-fs'

const PROTOCOL = 'hyper-overlay/v2'
const VERSION = 2
const CAP_LOCAL_FILES = 0x01
const CAP_ADAPTIVE_CHUNKS = 0x02

// A file's chunk list, shipped in one frame, can exceed the
// 16 MiB-1 Noise transport limit (@hyperswarm/secret-stream MAX_ATOMIC_WRITE):
// a 1.25 TB file at tier 3 is ~1.2M entries ≈ ~44 MB, which the secret-stream
// rejects ("Message is too large for an atomic write"), dropping the connection
// before a byte ships. So chunkHashes is paged into frames of ≤ this many
// entries. Each wire entry is 32 B (hash) + ≤5 B (varint length) ≈ 37 B, so
// 100k entries ≈ 3.7 MB/frame — well under the limit even after Protomux's
// ≤8 MiB send-batching.
const MAX_CHUNKS_PER_MSG = 100000

// F2: hard ceiling on chunk-hash entries accumulated across a paged chunkHashes
// stream. Far above any real file's chunk count (a 1.25 TB tier-3 file is ~1.2M
// entries), so it never trips legitimately — but it bounds an authorized-but-rogue
// seeder that streams `more:1` pages forever (each buffered page re-arms the idle
// watchdog, so the stall timer never fires). Unlike treeResponse, chunkHashes had
// no such cap. ~16M entries ≈ a few hundred MB worst case before we drop the list.
const MAX_CHUNK_HASHES_ACCUM = 16 * 1024 * 1024

// Tree entries are variable length (name is a var-string), unlike the fixed-width
// chunkHashes entries, so treeResponse pages by an encoded-byte budget rather than
// a fixed count. 4 MiB keeps each frame well under the atomic-write limit even
// after Protomux's ≤8 MiB send-batching.
const MAX_TREE_BYTES_PER_MSG = 4 * 1024 * 1024

// Defensive ceiling on the bytes a requester will accumulate across a paged
// treeResponse. A single-directory tree is orders of magnitude smaller; this only
// bounds memory so a peer streaming pages endlessly rejects the request instead
// of OOMing.
const MAX_TREE_RESPONSE_BYTES = 256 * 1024 * 1024

// transfer-control (message 12) states, downloader→holder.
const CONTROL_PAUSED = 0
const CONTROL_STOPPED = 1

// Abandon a serve loop whose stream neither drains nor closes within
// this window (a wedged-but-TCP-alive peer); the receiver re-requests, so it self-heals.
const DRAIN_TIMEOUT_MS = 60000

export class OverlayProtocol {
  constructor (syncEngine, transferManager, opts = {}) {
    this._syncEngine = syncEngine
    this._transferManager = transferManager
    this._corestore = opts.corestore || null
    this._overlayKey = opts.key || null
    this._peers = new Map()
    // Muxes we've registered a pair() callback on, so we register exactly one per mux. See attach().
    this._paired = new Set()
    this._filePaths = opts.filePaths || new Map()
    this._syncBaseDirs = opts.syncBaseDirs || []
    this._defaultStrategy = opts.defaultStrategy || 'prompt'
    this._autoSync = opts.autoSync !== false

    this._conflictCb = opts.onConflict || null
    this._syncedCb = opts.onSynced || null
    this._fileOfferCb = opts.onFileOffer || null
    // Called for every chunk that arrives on this peer. Lets callers
    // (e.g. NetworkFetcher) implement silent-peer watchdogs by resetting
    // an idle timer on every chunk. Receives { path, index, bytes, peer }.
    this._chunkProgressCb = opts.onChunkProgress || null
    // serve-side telemetry, symmetric to onChunkProgress — fired when WE
    // serve a peer (the sender-side download indicator). onServeStart({ path, peer,
    // from, total }) once a peer is authorized to fetch; onChunkServe({ path, peer,
    // from, bytes }) per served chunk; onServeEnd({ path, peer, from }) on close.
    this._serveStartCb = opts.onServeStart || null
    this._chunkServeCb = opts.onChunkServe || null
    this._serveEndCb = opts.onServeEnd || null
    // A downloader paused/stopped a hash WE serve. { path, peer, from, state }.
    this._serveControlCb = opts.onServeControl || null
    // A downloader reported its on-disk have-bytes for a hash WE serve (resume
    // baseline). { path, peer, from, have }.
    this._serveProgressCb = opts.onServeProgress || null

    // 0.5a: content-hash → local disk path, for responding to content-requests
    this._contentHashPaths = opts.contentHashPaths || new Map()
    // 0.5a: tree-request timeout (ms)
    this._treeRequestTimeout = opts.treeRequestTimeout || 30000
    // Cap on bytes accumulated across a paged treeResponse.
    this._maxTreeResponseBytes = opts.maxTreeResponseBytes || MAX_TREE_RESPONSE_BYTES
    // Multi-source: synthetic path 'content:<hash>' → ChunkScheduler. When set,
    // _onChunkHashes/_onChunkData delegate to the scheduler (parallel fetch
    // across peers) instead of the legacy single-peer auto-flow.
    this._schedulers = new Map()
    // 'content:<hash>' cancelled before its scheduler existed (the caller's
    // peer-wait window) — fetchContent honors it the moment it creates the scheduler.
    this._cancelPending = new Set()

    // serve authorization. _serveAuthorizer(peer, from, hash) →
    // boolean gates every inbound content-request; deny is a silent drop
    // (indistinguishable from "I don't hold it" — no membership oracle).
    // _localProfileKey is stamped on outbound content-requests as msg.from.
    this._serveAuthorizer = opts.serveAuthorizer || null
    this._localProfileKey = opts.localProfileKey || null
  }

  /**
   * Multi-source fetch of a file by content hash from several peers in
   * parallel (torrent-style). The caller pre-registers _filePaths['content:'
   * + contentHash] = destPath. Resolves when the file is assembled + verified.
   *
   * @param {string} contentHash
   * @param {Array} peers peer objects to fan the request to
   * @param {object} [opts] { destPath, cap, timeout, onProgress }
   * @returns {Promise<void>}
   */
  fetchContent (contentHash, peers, opts = {}) {
    const p = 'content:' + contentHash
    // Content-addressed dedup: a concurrent fetch of the SAME hash (a
    // different file/transfer with identical bytes) joins the in-flight one instead
    // of failing — await its verified bytes, then copy to our destPath. If the leader
    // was cancelled, re-issue our own fetch (the scheduler entry is already gone).
    const inflight = this._schedulers.get(p)
    if (inflight) {
      return inflight.shared.then(
        (res) => {
          if (opts.destPath && opts.destPath !== inflight.destPath) {
            try {
              fs.copyFileSync(inflight.destPath, opts.destPath)
            } catch (err) {
              // The leader's shared temp is already gone — the leader's caller
              // (e.g. pgh NetworkFetcher) unlinks its destPath as soon as it has
              // read+verified the bytes, which can race ahead of this joiner's
              // copy. Re-issue our own fetch, mirroring the ECANCELLED path.
              if (err?.code === 'ENOENT') return this.fetchContent(contentHash, peers, opts)
              throw err
            }
          }
          return res
        },
        (err) => { if (err?.code === 'ECANCELLED') return this.fetchContent(contentHash, peers, opts); throw err },
      )
    }
    const sched = new ChunkScheduler({
      path: p,
      destPath: opts.destPath,
      transfer: this._transferManager,
      sendNeed: (peer, indices) => peer.msgs.chunkNeed.send({ path: p, indices }),
      // P5 hotswap: best-effort tell a slow peer to stop serving reassigned chunks.
      sendCancel: (peer, indices) => { try { peer.msgs.chunkCancel.send({ path: p, indices }) } catch {} },
      cap: opts.cap,
      timeout: opts.timeout,
      onProgress: opts.onProgress,
      onVerify: opts.onVerify,
      onEnd: opts.onEnd,
      onBaseline: (have) => this.sendTransferProgress(contentHash, have),
      contentHash   // verify the whole-file hash incrementally during the transfer
    })
    this._schedulers.set(p, sched)
    // shared promise so joiners (above) can await the same completion —
    // calling sched.promise() twice would overwrite its resolve/reject handlers.
    sched.shared = sched.promise().finally(() => {
      this._schedulers.delete(p)
      // F2: drop any half-accumulated paged chunk list for this path across peers, so a
      // same-key retry (content:<hash> reused after a cancel mid-paging) can't append
      // fresh pages onto a stale partial list.
      for (const pr of this._peers.values()) if (pr._chunkHashPages) pr._chunkHashPages.delete(p)
    })
    // honor a cancel/pause that arrived before this scheduler existed.
    if (this._cancelPending.delete(p)) { sched.cancel(); return sched.shared }
    for (const peer of peers) this.requestContent(peer, contentHash, null)
    return sched.shared
  }

  // Stop an in-flight multi-source fetch. discardPartial:true unlinks the
  // partial (cancel); false keeps it for resume (pause). The scheduler's promise
  // rejects with ECANCELLED and its .finally() removes it from _schedulers. When no
  // scheduler exists yet (the caller is still in its peer-wait window), remember the
  // intent so fetchContent cancels the scheduler the instant it is created.
  async cancelContent (contentHash, { discardPartial = false, signal = true } = {}) {
    const p = 'content:' + contentHash
    const sched = this._schedulers.get(p)
    // No scheduler → the fetch never sent a content-request, so no holder has a serve
    // row for us; skip the (wasted) broadcast and just record the pre-scheduler cancel.
    if (!sched) { this._cancelPending.add(p); return false }
    // Tell holders we paused (keep partial) or stopped (discard) BEFORE local teardown,
    // so their sender-side indicator reacts now, not after the idle sweep. signal:false
    // suppresses it (a supersede is a restart, not a user stop).
    if (signal) this._sendTransferControl(contentHash, discardPartial ? CONTROL_STOPPED : CONTROL_PAUSED)
    // Stop accepting chunks first so the pause hash-drain converges on a stable frontier.
    sched.cancel()
    if (discardPartial) this._transferManager.cancel(sched.destPath)
    else await this._transferManager.pause(sched.destPath)
    return true
  }

  // Tell holders we stopped pulling a hash whose local fetch is already gone (e.g.
  // discarding an already-paused transfer) — no scheduler to tear down here.
  sendStopControl (contentHash) { this._sendTransferControl(contentHash, CONTROL_STOPPED) }

  // Best-effort broadcast to all connected holders. A holder with no serve-ledger entry
  // for us (or no slot for this message) just drops it; wrapped per-peer so a closing
  // channel or an old peer can't throw.
  _broadcast (msgName, payload) {
    for (const [, peer] of this._peers) {
      try { peer.msgs[msgName]?.send(payload) } catch {}
    }
  }

  _sendTransferControl (contentHash, state) { this._broadcast('transferControl', { contentHash, state }) }

  // Tell holders our current on-disk have-bytes for a hash we're (re)fetching, so their
  // sender-side bar mirrors our TRUE progress rather than only the bytes they re-serve.
  sendTransferProgress (contentHash, have) { this._broadcast('transferProgress', { contentHash, have }) }

  // Drop a cancel recorded before a scheduler existed when the fetch is
  // abandoned without ever reaching fetchContent (no peer connected) — otherwise the
  // marker would cancel the next fetch of the same content.
  clearCancelPending (contentHash) { this._cancelPending.delete('content:' + contentHash) }

  setContentHashPaths (map) { this._contentHashPaths = map }

  attach (mux) {
    if (this._peers.has(mux)) return this._peers.get(mux)

    const self = this

    // Attach used to be ONE-SHOT and order-sensitive, which silently killed byte transfer for the life
    // of a connection. If the remote has no matching (protocol, id) channel at the moment we attach —
    // it hasn't booted this DID's overlay yet, or is mid-restart — protomux tears our channel down,
    // onclose drops the peer, and NOTHING ever re-created it. The remote attaching a second later did
    // not help: `createChannel` then returns null ("the remote has already closed this one") and there
    // was no path back. Meanwhile the connection stays perfectly healthy for everything else, so
    // corestore keeps replicating and the pointers keep converging while the BYTES never move again —
    // which is precisely the fault seen in production (overlayPeers:0, forever, on a live connection).
    //
    // mux.pair() is protomux's mechanism for exactly this race: it fires whenever the remote REQUESTS
    // this (protocol, id), so a late or restarted peer gets a channel created on demand. Registered
    // once per mux, and left registered after a close so re-pairing keeps working for the connection's
    // whole life.
    if (!this._paired.has(mux)) {
      this._paired.add(mux)
      try {
        mux.pair({ protocol: PROTOCOL, id: this._overlayKey || null }, () => { try { self.attach(mux) } catch {} })
      } catch { this._paired.delete(mux) }
    }

    const channel = mux.createChannel({
      protocol: PROTOCOL,
      id: this._overlayKey || null,
      onopen () {
        const peer = self._peers.get(mux)
        if (peer) self._onOpen(peer)
      },
      onclose () {
        const peer = self._peers.get(mux)
        self._peers.delete(mux)
        // Failover: let any active multi-source fetch reassign this peer's
        // inflight chunks to the remaining peers.
        if (peer) for (const sched of self._schedulers.values()) sched.removePeer(peer)
        // Fail in-flight tree requests now, so a mid-stream disconnect rejects
        // promptly instead of stranding the request (and its buffered pages)
        // until the idle timeout fires.
        if (peer) self._failPendingTrees(peer, new Error('channel closed'))
        // tell the sender-side indicator this peer is gone for every file
        // it was pulling, so a disconnect mid-download clears its row promptly.
        if (peer && self._serveEndCb) {
          for (const [synthPath, from] of peer.authorizedServe) {
            try { self._serveEndCb({ path: synthPath, peer, from }) } catch {}
          }
        }
      }
    })

    if (!channel) return null

    // protomux returns null for a duplicate channel, or one the remote has already closed. Before the
    // pair() registration above this could not be recovered from; now the pair callback will re-drive
    // attach() when the remote asks again, so returning cleanly here is the correct behaviour. It also
    // stops a null-deref on the addMessage calls below, which the consumer's try/catch would have
    // swallowed into a silent "this DID has no byte transfer".
    if (!channel) return null

    const peer = {
      mux, channel, remoteFeedKey: null, remoteSeq: 0, msgs: {},
      pendingTrees: new Map(), // hash → { resolve, reject, timer, timeout, nonce, pages, pageBytes }
      // synthetic serve-paths this peer was authorized for via a
      // GATED _onContentRequest, mapped to the requester profileKey (msg.from).
      // _onChunkNeed serves bytes only for paths in here, so a peer cannot pull
      // bytes by sending chunkNeed/fileRequest directly for a registered path it
      // was never authorized to fetch; the value flows the requester identity to
      // the serve telemetry.
      authorizedServe: new Map()
    }

    peer.msgs.syncState = channel.addMessage({ encoding: messages.syncState, onmessage: (msg) => self._onSyncState(peer, msg) })
    peer.msgs.fileOffer = channel.addMessage({ encoding: messages.fileOffer, onmessage: (msg) => self._onFileOffer(peer, msg) })
    peer.msgs.fileRequest = channel.addMessage({ encoding: messages.fileRequest, onmessage: (msg) => self._onFileRequest(peer, msg) })
    peer.msgs.chunkHashes = channel.addMessage({ encoding: messages.chunkHashes, onmessage: (msg) => self._onChunkHashes(peer, msg) })
    peer.msgs.chunkNeed = channel.addMessage({ encoding: messages.chunkNeed, onmessage: (msg) => self._onChunkNeed(peer, msg) })
    peer.msgs.chunkData = channel.addMessage({ encoding: messages.chunkData, onmessage: (msg) => self._onChunkData(peer, msg) })
    peer.msgs.chunkCancel = channel.addMessage({ encoding: messages.chunkCancel, onmessage: (msg) => self._onChunkCancel(peer, msg) })
    peer.msgs.transferComplete = channel.addMessage({ encoding: messages.transferComplete, onmessage: (msg) => self._onTransferComplete(peer, msg) })
    peer.msgs.conflict = channel.addMessage({ encoding: messages.conflict, onmessage: (msg) => self._onConflictMsg(peer, msg) })

    // 0.5a — tree messages (backward-compatible additions)
    peer.msgs.treeRequest = channel.addMessage({ encoding: messages.treeRequest, onmessage: (msg) => self._onTreeRequest(peer, msg) })
    peer.msgs.treeResponse = channel.addMessage({ encoding: messages.treeResponse, onmessage: (msg) => self._onTreeResponse(peer, msg) })
    peer.msgs.contentRequest = channel.addMessage({ encoding: messages.contentRequest, onmessage: (msg) => self._onContentRequest(peer, msg) })
    // Appended-last slots: ids 0-11 stay fixed for peers without 12/13, which just
    // never register these channels and ignore the frames.
    peer.msgs.transferControl = channel.addMessage({ encoding: messages.transferControl, onmessage: (msg) => self._onTransferControl(peer, msg) })
    peer.msgs.transferProgress = channel.addMessage({ encoding: messages.transferProgress, onmessage: (msg) => self._onTransferProgress(peer, msg) })

    this._peers.set(mux, peer)
    channel.open({ version: VERSION, capabilities: CAP_LOCAL_FILES | CAP_ADAPTIVE_CHUNKS })
    return peer
  }

  // Explicit teardown — unlike an incidental channel close (where the pair callback must SURVIVE so a
  // returning peer can re-pair), detach means we are done with this mux, so drop the pairing too.
  detach (mux) {
    if (this._paired.delete(mux)) {
      try { mux.unpair({ protocol: PROTOCOL, id: this._overlayKey || null }) } catch {}
    }
    const peer = this._peers.get(mux)
    if (!peer) return
    peer.channel.close()
    this._peers.delete(mux)
  }

  setFilePaths (map) { this._filePaths = map }

  broadcastOffer (overlayPath, meta, op) {
    const msg = {
      path: overlayPath,
      contentHash: meta.contentHash || '0'.repeat(64),
      size: meta.size || 0,
      mtime: meta.mtime || 0,
      op
    }
    for (const [, peer] of this._peers) {
      peer.msgs.fileOffer.send(msg)
    }
  }

  requestFile (peer, overlayPath, contentHash, chunksHave) {
    peer.msgs.fileRequest.send({
      path: overlayPath,
      contentHash,
      chunksHave: chunksHave || null
    })
  }

  /**
   * 0.5a: Request a tree by its hash. Returns a promise resolving to
   * the entries array (or [] if the peer doesn't have it).
   *
   * @param {object} peer
   * @param {string} hash - hex tree hash
   * @param {object} [opts]
   * @param {number} [opts.timeout] - override default timeout (ms)
   * @returns {Promise<Array<{kind, exec, name, childHash, size}>>}
   */
  requestTree (peer, hash, opts = {}) {
    return new Promise((resolve, reject) => {
      // If a prior request is already in-flight for the same hash, reject
      // this caller rather than silently overwrite.
      if (peer.pendingTrees.has(hash)) {
        return reject(new Error('tree request already pending for hash: ' + hash))
      }
      // Per-request nonce (per-peer monotonic, non-zero) lets the requester drop
      // pages echoed for a superseded attempt. pages/pageBytes buffer this
      // request's own paged response, so a fresh attempt never inherits an old one.
      const nonce = peer._treeReqSeq = (peer._treeReqSeq || 0) + 1
      const timeout = opts.timeout || this._treeRequestTimeout
      const pending = { resolve, reject, timer: null, timeout, nonce, pages: null, pageBytes: 0 }
      this._armTreeTimeout(peer, hash, pending)
      peer.pendingTrees.set(hash, pending)
      peer.msgs.treeRequest.send({ hash, nonce })
    })
  }

  // Arm (or re-arm) a pending tree request's idle timeout. Every buffered page
  // re-arms it (a large tree on a slow link mustn't time out mid-stream); the
  // request still settles because the accumulator is byte-capped in _onTreeResponse.
  _armTreeTimeout (peer, hash, pending) {
    pending.timer = setTimeout(() => {
      if (peer.pendingTrees.get(hash) === pending) {
        peer.pendingTrees.delete(hash)
        pending.reject(new Error('tree request timed out: ' + hash))
      }
    }, pending.timeout)
  }

  // Reject every in-flight tree request on this peer (channel close / destroy),
  // clearing timers so the promise fails promptly and its buffered pages release.
  _failPendingTrees (peer, err) {
    for (const [hash, pending] of peer.pendingTrees) {
      clearTimeout(pending.timer)
      peer.pendingTrees.delete(hash)
      pending.reject(err)
    }
  }

  /**
   * 0.5a: Request a file by its content hash. Peer looks up any file
   * matching that hash locally and streams chunks via the existing flow
   * (chunkHashes → chunkNeed → chunkData). Returns immediately; chunks
   * arrive asynchronously.
   *
   * Note: receiver must pre-register a target path mapping via
   * _filePaths keyed by the contentHash before calling, OR this method
   * is a no-op on the receive side. Full wiring deferred to 0.5c.
   */
  requestContent (peer, contentHash, chunksHave) {
    peer.msgs.contentRequest.send({
      contentHash,
      chunksHave: chunksHave || null,
      from: this._localProfileKey || ''   // authenticate the asker to the serve gate
    })
  }

  get peerCount () { return this._peers.size }

  destroy () {
    for (const [, peer] of this._peers) {
      this._failPendingTrees(peer, new Error('protocol destroyed'))
      peer.channel.close()
    }
    this._peers.clear()
  }

  // ── Handlers ────────────────────────────────────────────────

  _onOpen (peer) {
    peer.msgs.syncState.send({
      feedKey: this._syncEngine.feedKey,
      localSeq: this._syncEngine.feed.length,
      remoteSeq: 0
    })
  }

  async _onSyncState (peer, msg) {
    peer.remoteFeedKey = msg.feedKey
    peer.remoteSeq = msg.localSeq

    if (!this._autoSync) return

    try {
      await this._offerLocalFiles(peer)
    } catch {
      // ignore — the HyperOverlay facade runs with autoSync off, so this path is dormant
    }
  }

  async _onFileOffer (peer, msg) {
    // In gated mode the overlay flow is contentRequest→scheduler ONLY; gated
    // consumers never act on path-based file-offers (autoSync is off). Ignoring
    // them closes the offer→requestFile→legacy-receive path a peer could drive.
    if (this._serveAuthorizer) return
    if (this._fileOfferCb) this._fileOfferCb(peer, msg)

    if (msg.op === 0) {
      const local = await this._syncEngine._fileIndex.getFile(msg.path)
      if (!local || local.contentHash !== msg.contentHash) {
        const diskPath = this._resolveFilePath(msg.path)
        if (diskPath) {
          this._filePaths.set(msg.path, diskPath)
          this.requestFile(peer, msg.path, msg.contentHash)
        }
      }
    }
  }

  async _onFileRequest (peer, msg) {
    // path-based serve is UNGATED and would bypass the serve gate (the only
    // gated entry point is _onContentRequest). Gated consumers never send
    // fileRequest, so refuse it outright in gated mode.
    if (this._serveAuthorizer) return
    const diskPath = this._filePaths.get(msg.path)
    if (!diskPath) return

    const prepared = await this._transferManager.prepareFile(diskPath, msg.path)
    if (!prepared) return

    if (!await this._transferManager._fileIndex.hasChunkMap(msg.path)) {
      await this._transferManager._fileIndex.putChunkMap(msg.path, prepared.chunks)
    }

    this._sendChunkHashes(peer, msg.path, prepared.tier, prepared.chunks)
  }

  // Send a file's chunk list, paged so no single frame exceeds
  // the Noise transport's 16 MiB-1 atomic-write limit. A list that fits in one
  // frame ships as one (more:0) — identical on the wire to the pre-paging format
  // bar a trailing 0 byte. _onChunkHashes reassembles the pages on the receiver.
  _sendChunkHashes (peer, filePath, tier, chunks) {
    const total = chunks.length
    if (total <= MAX_CHUNKS_PER_MSG) {
      peer.msgs.chunkHashes.send({ path: filePath, tier, chunks, more: 0 })
      return
    }
    for (let i = 0; i < total; i += MAX_CHUNKS_PER_MSG) {
      const more = i + MAX_CHUNKS_PER_MSG < total ? 1 : 0
      peer.msgs.chunkHashes.send({ path: filePath, tier, chunks: chunks.slice(i, i + MAX_CHUNKS_PER_MSG), more })
    }
  }

  // Reassemble a paged chunk list. Returns the full list once
  // the final page (more:0) arrives, or null while pages are still pending. A
  // lone complete frame passes straight through (no copy) — the small-file norm.
  // Pages for a path arrive in order on this channel, so arrival-order
  // concatenation is correct even when several files interleave on it.
  _reassembleChunkHashes (peer, msg) {
    const buffered = peer._chunkHashPages && peer._chunkHashPages.get(msg.path)
    if (!buffered && !msg.more) return msg.chunks
    if (!peer._chunkHashPages) peer._chunkHashPages = new Map()
    let acc = buffered
    if (!acc) { acc = []; peer._chunkHashPages.set(msg.path, acc) }
    for (const ch of msg.chunks) acc.push(ch)
    if (acc.length > MAX_CHUNK_HASHES_ACCUM) {
      // F2: drop an over-long accumulator (adversarial more:1 flood) instead of
      // OOMing — the fetch fails and the scheduler retries/fails over to another peer.
      peer._chunkHashPages.delete(msg.path)
      return null
    }
    if (msg.more) return null
    peer._chunkHashPages.delete(msg.path)
    return acc
  }

  // Upper-bound wire size of one tree entry: kind(1)+exec(1) + string(len-prefix ≤4
  // + utf8 name bytes) + childHash(32) + size varint(≤9, c.uint is 9 bytes for a
  // >4 GiB size).
  _treeEntryWireSize (e) {
    return 1 + 1 + 4 + Buffer.byteLength(e.name || '', 'utf8') + 32 + 9
  }

  // Send a tree's entries, paged so no single frame exceeds the Noise atomic-write
  // limit; every page but the last is more:1. A tree that fits in one frame ships
  // as a single more:0 frame. `nonce` echoes the requester's per-request id.
  _sendTreeResponse (peer, hash, entries, nonce) {
    let page = []
    let bytes = 40 // hash(32) + uint32 count prefix + more/nonce + slack
    for (const e of entries) {
      const sz = this._treeEntryWireSize(e)
      if (page.length && bytes + sz > MAX_TREE_BYTES_PER_MSG) {
        peer.msgs.treeResponse.send({ hash, entries: page, more: 1, nonce })
        page = []
        bytes = 40
      }
      page.push(e)
      bytes += sz
    }
    peer.msgs.treeResponse.send({ hash, entries: page, more: 0, nonce })
  }

  async _onChunkHashes (peer, msg) {
    // Reassemble paged chunkHashes frames before dispatching.
    const chunks = this._reassembleChunkHashes(peer, msg)
    if (chunks === null) {
      // A buffered page is forward progress — keep the scheduler's no-progress
      // watchdog from tripping while a large map streams across several frames.
      const pending = this._schedulers.get(msg.path)
      if (pending && pending.notePageProgress) pending.notePageProgress()
      return
    }
    // Multi-source: if a scheduler owns this path, let it distribute chunk
    // requests across peers instead of the legacy single-peer auto-flow.
    const sched = this._schedulers.get(msg.path)
    if (sched) { Promise.resolve(sched.onChunkHashes(peer, chunks)).catch(() => {}); return }
    // the legacy single-peer receive below writes incoming bytes
    // to _filePaths.get(path) and finalizes a rename OVER it. For a path that
    // maps to one of OUR own files (e.g. a content-register entry), an
    // unsolicited chunkHashes would let a peer overwrite the owner's source
    // file. Gated consumers only ever receive via a scheduler (the branch
    // above), so refuse any other receive.
    if (this._serveAuthorizer) return

    const diskPath = this._filePaths.get(msg.path)
    if (!diskPath) return

    // Empty-file fast path: a 0-byte file has no chunks to fetch, but we
    // still need to materialize it on disk and fire the same completion
    // flow as _onChunkData's "transfer complete" branch — otherwise the
    // requester (e.g. NetworkFetcher.getContent) never resolves and times
    // out. Empty files are common in practice: .keep, __init__.py, build
    // stamps, touch'd markers. Bug symptom before this: node-gyp's
    // postinstall.stamp (0 bytes, hash 5187b7a8…) hung every install.
    if (chunks.length === 0) {
      try {
        fs.mkdirSync(path.dirname(diskPath), { recursive: true })
        fs.writeFileSync(diskPath, Buffer.alloc(0))
      } catch {
        return
      }
      const contentHash = hashChunk(Buffer.alloc(0))
      const stat = fs.statSync(diskPath)
      await this._syncEngine._fileIndex.putFile(msg.path, {
        contentHash, size: 0, mtime: stat.mtimeMs
      })
      await this._syncEngine.logPut(msg.path, {
        contentHash, size: 0, mtime: stat.mtimeMs
      })
      if (peer.remoteFeedKey) {
        await this._syncEngine.markSynced(peer.remoteFeedKey, msg.path, contentHash, this._syncEngine.feed.length - 1)
      }
      peer.msgs.transferComplete.send({ path: msg.path, contentHash })
      if (this._syncedCb) {
        this._syncedCb({ path: msg.path, contentHash, size: 0, direction: 'pull' })
      }
      return
    }

    const needed = this._transferManager.computeNeeded(chunks, new Set())
    if (needed.length === 0) return

    let size = 0
    for (const c of chunks) size += c.length

    // startReceive can throw (fd open, partial recovery) — drop this transfer
    // instead of rejecting protomux's onmessage promise, which would destroy the
    // whole Noise stream.
    try {
      await this._transferManager.startReceive(diskPath, {
        size,
        chunks: chunks.map((c, i) => {
          let offset = 0
          for (let j = 0; j < i; j++) offset += chunks[j].length
          return { hash: c.hash, offset, length: c.length }
        })
      })
    } catch {
      return
    }

    peer.msgs.chunkNeed.send({ path: msg.path, indices: needed })
  }

  async _onChunkNeed (peer, msg) {
    // Serve bytes ONLY for a synthetic path THIS peer was
    // authorized for via a gated _onContentRequest. Blocks a peer pulling bytes
    // by sending chunkNeed directly for a registered path (e.g. a content-
    // register entry) without passing the serve gate.
    if (this._serveAuthorizer && !peer.authorizedServe.has(msg.path)) return
    const diskPath = this._filePaths.get(msg.path)
    if (!diskPath) return

    const fileIndex = this._transferManager._fileIndex
    let chunkMap
    try {
      chunkMap = msg.path.startsWith('content:')
        ? await fileIndex.getChunkMapByHash(msg.path.slice('content:'.length))
        : await fileIndex.getChunkMap(msg.path)
    } catch {
      // A chunk-map lookup that rejects (index I/O error) must not escape: the
      // onmessage dispatch is fire-and-forget, so a throw here becomes an
      // unhandled rejection that tears down the whole Noise/replication stream
      // (the S1 malformed-frame hardening theme). Drop the request — the receiver
      // re-requests and the transfer self-heals.
      return
    }
    if (!chunkMap) return

    // Single-flight serve pump per peer (mirrors hypercore's `receiverBusy`).
    // Enqueue this request; if no pump is running for this peer, drain the queue
    // one job at a time. protomux dispatches every frame of a socket read
    // synchronously, so two chunkNeed frames from the same peer would otherwise
    // start rival serve loops — interleaving sends and admitting N chunks per
    // drain instead of honouring backpressure once (the discipline our 0.2.13
    // serialized pump had, which Ok's inline loop dropped).
    if (!peer._serveQueue) peer._serveQueue = []
    peer._serveQueue.push({ path: msg.path, diskPath, chunkMap, indices: msg.indices })
    if (peer._serving) return
    peer._serving = true
    // Kick the pump fire-and-forget: _onChunkNeed is the onmessage dispatch and
    // must return promptly, NOT stay pending for the whole multi-chunk serve
    // (which, under backpressure, spans the entire transfer). A second chunkNeed
    // from this peer just enqueues above; the single-flight guard (_serving) means
    // this already-running pump picks it up. This is our 0.2.13 contract — Ok's
    // inline drain loop awaited the whole serve, which the unit test rejects.
    this._drainServeQueue(peer)
  }

  // The per-peer single-flight serve pump. Drains _serveQueue one job at a time;
  // detached from _onChunkNeed so the message handler returns immediately.
  async _drainServeQueue (peer) {
    try {
      while (peer._serveQueue.length) {
        if (peer.channel && peer.channel.closed) { peer._serveQueue.length = 0; break }
        try { await this._serveJob(peer, peer._serveQueue.shift()) } catch {}
      }
    } finally {
      peer._serving = false
    }
  }

  // Serve one chunkNeed job: stream the requested chunk indices, honouring
  // stream backpressure — stop producing when send() reports the write buffer is
  // full and resume on drain, so sibling protocols multiplexed on the same Noise
  // stream (Corestore replication, app handshakes; protomux MAX_BACKLOG is
  // Infinity) aren't starved. Runs under the per-peer single-flight guard in
  // _onChunkNeed, so exactly one job streams at a time per peer.
  async _serveJob (peer, job) {
    const { path, diskPath, chunkMap, indices } = job
    // P3: cork the mux so a run of chunkData frames coalesces into fewer stream
    // writes (hypercore's replicator corks around each request/response batch,
    // replicator.js:722/738). Uncork before parking on drain (flush the batch) and
    // in the finally. The `corked` guard keeps cork/uncork balanced across the early
    // returns, and no-ops when the mux stub has no cork (unit tests).
    const mux = peer.mux
    let corked = false
    const cork = () => { if (mux && mux.cork && !corked) { mux.cork(); corked = true } }
    const uncork = () => { if (mux && mux.uncork && corked) { mux.uncork(); corked = false } }
    cork()
    try {
      for (let i = 0; i < indices.length; i++) {
        const index = indices[i]
        if (index >= chunkMap.length) continue
        // P5: skip a chunk the downloader hotswapped away (one-shot).
        if (peer._cancelledServe && peer._cancelledServe.delete(path + '\x00' + index)) continue
        const c = chunkMap[index]
        const data = this._transferManager.readChunk(diskPath, c.offset, c.length)
        if (!data) continue
        const flushed = peer.msgs.chunkData.send({ path, index, data })
        if (this._chunkServeCb) {
          try { this._chunkServeCb({ path, index, bytes: data.length, peer, from: peer.authorizedServe.get(path) || null }) } catch {}
        }
        if (flushed === false && i < indices.length - 1) {
          uncork()   // flush the corked batch before we park on drain
          const alive = await this._waitForDrain(peer)
          if (!alive) return
          if (this._serveAuthorizer && !peer.authorizedServe.has(path)) return
          cork()     // re-cork for the next run
        }
      }
    } finally {
      uncork()
    }
  }

  // One shared drain waiter per peer so concurrent serve loops (the
  // same peer pulling several files) don't each attach listeners and trip the stream's
  // max-listeners warning. Resolves true on drain, false if the channel closes first.
  _waitForDrain (peer) {
    const channel = peer.channel
    const stream = peer.mux && peer.mux.stream
    if (!stream || !channel || channel.closed) return Promise.resolve(false)
    if (channel.drained) return Promise.resolve(true)
    if (peer._drainWait) return peer._drainWait
    peer._drainWait = new Promise((resolve) => {
      let timer = null
      const done = (alive) => {
        if (timer) clearTimeout(timer)
        stream.removeListener('drain', onDrain)
        stream.removeListener('close', onClose)
        peer._drainWait = null
        resolve(alive)
      }
      const onDrain = () => done(!channel.closed)
      const onClose = () => done(false)
      timer = setTimeout(() => done(false), DRAIN_TIMEOUT_MS)
      stream.on('drain', onDrain)
      stream.on('close', onClose)
    })
    return peer._drainWait
  }

  async _onChunkData (peer, msg) {
    // Silent-peer watchdog hook: fire BEFORE any processing so a slow disk
    // doesn't get blamed for a peer-side stall.
    if (this._chunkProgressCb) {
      try {
        this._chunkProgressCb({
          path: msg.path,
          index: msg.index,
          bytes: msg.data ? msg.data.length : 0,
          peer
        })
      } catch {}
    }

    // Multi-source: scheduler owns the write + reschedule + finalize.
    const sched = this._schedulers.get(msg.path)
    if (sched) { Promise.resolve(sched.onChunkData(peer, msg.index, msg.data)).catch(() => {}); return }
    // refuse the legacy single-peer receive (writes+renames over
    // _filePaths.get(path), i.e. potentially our own source file). Gated
    // consumers only receive via a scheduler (above).
    if (this._serveAuthorizer) return

    const diskPath = this._filePaths.get(msg.path)
    if (!diskPath) return

    const result = this._transferManager.writeChunk(diskPath, msg.index, msg.data)

    if (result.ok && this._transferManager.isComplete(diskPath)) {
      const fin = await this._transferManager.finalize(diskPath)
      if (fin.ok) {
        const fileData = fs.readFileSync(diskPath)
        const contentHash = hashChunk(fileData)
        const stat = fs.statSync(diskPath)

        await this._syncEngine._fileIndex.putFile(msg.path, {
          contentHash, size: stat.size, mtime: stat.mtimeMs
        })
        await this._syncEngine.logPut(msg.path, {
          contentHash, size: stat.size, mtime: stat.mtimeMs
        })

        if (peer.remoteFeedKey) {
          await this._syncEngine.markSynced(peer.remoteFeedKey, msg.path, contentHash, this._syncEngine.feed.length - 1)
        }

        peer.msgs.transferComplete.send({ path: msg.path, contentHash })
        if (this._syncedCb) {
          this._syncedCb({ path: msg.path, contentHash, size: stat.size, direction: 'pull' })
        }
      }
    }
  }

  _onChunkCancel (peer, msg) {
    if (msg.indices && msg.indices.length) {
      // P5 hotswap: the downloader reassigned these chunk indices to a faster peer.
      // Stop serving just them (bandwidth save); the rest of the transfer continues.
      // Consumed one-shot in _serveJob.
      if (!peer._cancelledServe) peer._cancelledServe = new Set()
      for (const i of msg.indices) peer._cancelledServe.add(msg.path + '\x00' + i)
      return
    }
    const diskPath = this._filePaths.get(msg.path)
    if (diskPath) this._transferManager.cancel(diskPath)
  }

  // Resolve a serve-message's downloader identity from the authenticated record we kept at
  // serve start (peer.authorizedServe), NOT the message — so a peer can only affect its own
  // ledger row, never spoof another. Returns null `from` when the peer was never authorized.
  _authorizedFrom (peer, contentHash) {
    const synthPath = 'content:' + contentHash
    return { synthPath, from: peer.authorizedServe.get(synthPath) || null }
  }

  // A downloader told us it paused/stopped pulling a hash WE serve.
  _onTransferControl (peer, msg) {
    if (!this._serveControlCb) return
    const { synthPath, from } = this._authorizedFrom(peer, msg.contentHash)
    if (!from) return
    const state = msg.state === CONTROL_STOPPED ? 'stopped' : 'paused'
    try { this._serveControlCb({ path: synthPath, peer, from, state }) } catch {}
  }

  // A downloader reported its on-disk have-bytes for a hash WE serve.
  _onTransferProgress (peer, msg) {
    if (!this._serveProgressCb) return
    const { synthPath, from } = this._authorizedFrom(peer, msg.contentHash)
    if (!from) return
    try { this._serveProgressCb({ path: synthPath, peer, from, have: msg.have || 0 }) } catch {}
  }

  async _onTransferComplete (peer, msg) {
    if (peer.remoteFeedKey) {
      await this._syncEngine.markSynced(peer.remoteFeedKey, msg.path, msg.contentHash, this._syncEngine.feed.length - 1)
    }
    if (this._syncedCb) {
      this._syncedCb({ path: msg.path, contentHash: msg.contentHash, direction: 'push' })
    }
  }

  _onConflictMsg (peer, msg) {
    if (this._conflictCb) this._conflictCb(msg, peer)
  }

  // ── Tree handlers (0.5a) ────────────────────────────────────

  async _onTreeRequest (peer, msg) {
    // ungated metadata oracle (returns fileIndex tree entries to
    // any peer). Gated consumers never use tree-requests; refuse in gated mode.
    if (this._serveAuthorizer) return
    let entries = []
    try {
      const fileIndex = this._syncEngine && this._syncEngine._fileIndex
      if (fileIndex && typeof fileIndex.getTree === 'function') {
        const tree = await fileIndex.getTree(msg.hash)
        if (tree && Array.isArray(tree.entries)) entries = tree.entries
      }
    } catch {
      // fall through — reply with empty entries on lookup failure
    }
    this._sendTreeResponse(peer, msg.hash, entries, msg.nonce || 0)
  }

  _onTreeResponse (peer, msg) {
    const pending = peer.pendingTrees.get(msg.hash)
    if (!pending) return // no request in flight — drop
    // Drop a page echoing a superseded request's nonce (a stale page from a
    // timed-out attempt must not settle a later same-hash retry). nonce 0 = a
    // pre-nonce holder that can't echo it, so fall through (unverifiable,
    // best-effort).
    if (msg.nonce && msg.nonce !== pending.nonce) return
    const settle = (fn, arg) => { clearTimeout(pending.timer); peer.pendingTrees.delete(msg.hash); fn(arg) }
    // Lone complete frame (the small-tree norm) resolves with no accumulation.
    if (!pending.pages && !msg.more) return settle(pending.resolve, msg.entries)
    // Accumulate into THIS request's own buffer, bounding total bytes so a peer
    // streaming pages endlessly rejects the request instead of growing memory
    // without limit.
    if (!pending.pages) pending.pages = []
    for (const e of msg.entries) { pending.pages.push(e); pending.pageBytes += this._treeEntryWireSize(e) }
    if (pending.pageBytes > this._maxTreeResponseBytes) {
      return settle(pending.reject, new Error('tree response too large: ' + msg.hash))
    }
    if (msg.more) {
      // Forward progress — re-arm the idle timeout so a legitimately large
      // multi-page tree on a slow link doesn't time out mid-stream.
      clearTimeout(pending.timer)
      this._armTreeTimeout(peer, msg.hash, pending)
      return
    }
    settle(pending.resolve, pending.pages)
  }

  async _onContentRequest (peer, msg) {
    // serve gate. BEFORE any path resolution or streaming,
    // ask the injected authorizer whether this peer may receive this hash.
    // Deny == silent return (same observable behavior as "I don't hold it"
    // below), so a non-member cannot distinguish denial from absence.
    if (this._serveAuthorizer) {
      let ok = false
      try { ok = await this._serveAuthorizer(peer, msg.from || null, msg.contentHash) } catch { ok = false }
      if (!ok) return
    }

    // Resolve msg.contentHash → an on-disk path. Three lookup paths, in order:
    //  1. Fast path: _contentHashPaths map (warm — just-registered / consumer-set)
    //  2. Content-addressed spool (RESTART-DURABLE): <syncBaseDir>/<contentHash>
    //  3. Slow path: scan fileIndex for any file:* entry with that hash
    let diskPath = this._contentHashPaths.get(msg.contentHash)
    if (diskPath && !fileExists(diskPath)) diskPath = null

    if (!diskPath) {
      // Content-addressed spool fallback. The canonical bytes are stored at
      // <syncBaseDir>/<contentHash> (fetchFile's mirrorSpool option writes them
      // there). The in-memory map is empty after a restart and FileIndex
      // persists no disk path, so resolve the hash straight from the
      // content-addressed spool — a pure function of the hash + dir, like a
      // git object store's blobs/<hash>. This is what lets a node keep serving
      // its spooled files across restarts with no rehydration step.
      for (const baseDir of this._syncBaseDirs) {
        const candidate = path.join(baseDir, msg.contentHash)
        if (fileExists(candidate)) {
          diskPath = candidate
          this._contentHashPaths.set(msg.contentHash, candidate) // warm the map
          break
        }
      }
    }

    if (!diskPath) {
      // Scan fileIndex for a file whose contentHash matches.
      const fileIndex = this._syncEngine && this._syncEngine._fileIndex
      if (fileIndex && typeof fileIndex.listFiles === 'function') {
        try {
          const files = await fileIndex.listFiles()
          for (const f of files) {
            if (f.contentHash === msg.contentHash) {
              diskPath = this._filePaths.get(f.path)
              if ((!diskPath || !fileExists(diskPath)) && f.path) diskPath = f.path
              break
            }
          }
        } catch {
          // fall through
        }
      }
    }

    if (!diskPath) {
      // Don't have it. Silently drop; requester will time out.
      return
    }

    // Use a SYNTHETIC path keyed by contentHash so the requester can
    // pre-register a destPath and the chunk flow routes correctly on
    // both sides. Temporarily populate _filePaths so _onChunkNeed +
    // chunkmap lookups resolve under this synthetic key.
    const syntheticPath = 'content:' + msg.contentHash
    this._filePaths.set(syntheticPath, diskPath)
    peer.authorizedServe.set(syntheticPath, msg.from || null) // _onChunkNeed will now serve this path to THIS peer

    // The chunk map is content-addressed — identical bytes always chunk
    // identically. Reuse one computed at publish (or a prior serve, or before a
    // restart) instead of re-reading the whole file to re-chunk it: re-chunking a
    // multi-GB file adds a full-file-read of latency before the first byte ships.
    const fileIndex = this._transferManager._fileIndex
    let chunks = await fileIndex.getChunkMapByHash(msg.contentHash)
    let tier
    if (chunks) {
      let size = 0
      try { size = fs.statSync(diskPath).size } catch {}
      tier = selectTier(size)
    } else {
      const prepared = await this._transferManager.prepareFile(diskPath, syntheticPath, { byHashOnly: true })
      if (!prepared) {
        this._filePaths.delete(syntheticPath)
        peer.authorizedServe.delete(syntheticPath) // keep the authorize map in lockstep with _filePaths
        return
      }
      chunks = prepared.chunks
      tier = prepared.tier
      // prepareFile persists the hash-keyed map only for large files; ensure it exists
      // for small ones too, since _onChunkNeed resolves content:<hash> by that map.
      if (!await fileIndex.hasChunkMapByHash(msg.contentHash)) await fileIndex.putChunkMapByHash(msg.contentHash, chunks)
    }
    this._sendChunkHashes(peer, syntheticPath, tier, chunks)
    if (this._serveStartCb) {
      let total = 0
      for (const c of chunks) total += c.length || 0
      try { this._serveStartCb({ path: syntheticPath, peer, from: msg.from || null, total }) } catch {}
    }
  }

  // ── Sync logic ──────────────────────────────────────────────

  /**
   * Offer all local files to a peer.
   * Reads from the local feed and sends file-offers for each entry.
   * The peer will request any files it doesn't have.
   */
  async _offerLocalFiles (peer) {
    const feed = this._syncEngine.feed
    if (feed.length === 0) return

    for (let i = 0; i < feed.length; i++) {
      try {
        const entry = await feed.get(i)
        if (!entry) continue

        // Register the file path mapping so _onFileRequest can find it
        if (entry.op === 0) {
          const diskPath = this._resolveFilePath(entry.path)
          if (diskPath) this._filePaths.set(entry.path, diskPath)
        }

        peer.msgs.fileOffer.send({
          path: entry.path,
          contentHash: entry.hash || '0'.repeat(64),
          size: entry.size || 0,
          mtime: entry.mtime || 0,
          op: entry.op
        })
      } catch {
        // skip unreadable entries
      }
    }
  }

  /**
   * Resolve an overlay path to a disk path.
   */
  _resolveFilePath (overlayPath) {
    if (this._filePaths.has(overlayPath)) return this._filePaths.get(overlayPath)
    for (const baseDir of this._syncBaseDirs) {
      return path.join(baseDir, overlayPath)
    }
    return null
  }
}

// True iff `p` is an existing regular file. Used to validate a resolved disk path
// before serving (a cold-restart map entry or an overlay-path masquerading as a
// disk path won't exist) and to probe the content-addressed spool.
function fileExists (p) {
  try { return fs.statSync(p).isFile() } catch { return false }
}
/**
 * SyncEngine — Feed-based change tracking and conflict detection
 *
 * Each peer has a Hypercore feed logging file change operations.
 * When peers connect, feeds replicate automatically (Hypercore handles this).
 * The sync engine diffs feeds since the last-synced sequence number
 * and detects conflicts using three-value comparison:
 *
 *   my current hash vs their current hash vs last-synced hash (common ancestor)
 *
 * Conflict resolution is configurable per sync folder:
 *   prompt | newest-wins | keep-both | skip | mine-wins | theirs-wins
 */

import ReadyResource from 'ready-resource'

// Operation types written to the change feed
const OP_PUT = 0
const OP_DEL = 1

export const STRATEGIES = ['prompt', 'newest-wins', 'keep-both', 'skip', 'mine-wins', 'theirs-wins']

export class SyncEngine extends ReadyResource {
  constructor (fileIndex, corestore, opts = {}) {
    super()
    this._fileIndex = fileIndex
    this._corestore = corestore
    this._feed = null
    this._peerFeeds = new Map() // peerKey hex → Hypercore
    this._conflicts = []       // unresolved conflicts
    this._opts = opts
  }

  async _open () {
    // Local change feed — append-only log of file operations. Encrypted at rest when
    // the caller passes opts.encryptionKey (peer feeds, opened by key in addPeerFeed,
    // stay plaintext — their key is not ours to encrypt under).
    const opts = { name: 'sync-feed', valueEncoding: 'json' }
    if (this._opts.encryptionKey) opts.encryptionKey = this._opts.encryptionKey
    this._feed = this._corestore.get(opts)
    await this._feed.ready()
  }

  async _close () {
    // Peer feeds are managed by corestore, no explicit close needed
  }

  /**
   * The local change feed (Hypercore)
   */
  get feed () { return this._feed }

  /**
   * The local feed's public key as hex
   */
  get feedKey () { return this._feed.key.toString('hex') }

  /**
   * Current unresolved conflicts
   */
  get conflicts () { return this._conflicts }

  // ── Change logging ──────────────────────────────────────────

  /**
   * Log a file put/update operation
   * @param {string} path - file path
   * @param {{ contentHash: string, size: number, mtime: number }} meta
   */
  async logPut (path, meta) {
    await this._feed.append({
      op: OP_PUT,
      path,
      hash: meta.contentHash,
      size: meta.size,
      mtime: meta.mtime,
      rev: meta.rev ?? null, // optional app version (e.g. atproto TID) for rev-based conflict resolution; null for generic callers
      timestamp: Date.now()
    })
  }

  /**
   * Log a file deletion
   * @param {string} path - file path
   */
  async logDel (path) {
    await this._feed.append({
      op: OP_DEL,
      path,
      hash: null,
      size: 0,
      mtime: 0,
      timestamp: Date.now()
    })
  }

  // ── Peer feeds ──────────────────────────────────────────────

  /**
   * Add a remote peer's feed for syncing
   * @param {Buffer|string} key - peer's feed public key
   * @returns {Hypercore} the peer feed
   */
  addPeerFeed (key) {
    const keyHex = typeof key === 'string' ? key : key.toString('hex')
    if (this._peerFeeds.has(keyHex)) return this._peerFeeds.get(keyHex)

    const feed = this._corestore.get({ key: typeof key === 'string' ? Buffer.from(key, 'hex') : key, valueEncoding: 'json' })
    this._peerFeeds.set(keyHex, feed)
    return feed
  }

  /**
   * Get a peer feed by key
   * @param {string} keyHex
   * @returns {Hypercore|null}
   */
  getPeerFeed (keyHex) {
    return this._peerFeeds.get(keyHex) || null
  }

  // ── Feed diffing ────────────────────────────────────────────

  /**
   * Get new operations from a feed since a given sequence number
   * @param {Hypercore} feed
   * @param {number} sinceSeq - start reading from this seq (exclusive)
   * @returns {Array<{ seq: number, op: number, path: string, hash: string|null, size: number, mtime: number }>}
   */
  async getChanges (feed, sinceSeq) {
    await feed.ready()
    await feed.update()

    const changes = []
    const start = sinceSeq + 1
    const end = feed.length

    for (let i = start; i < end; i++) {
      const entry = await feed.get(i)
      changes.push({ seq: i, ...entry })
    }

    return changes
  }

  /**
   * Build a map of latest state per path from a list of changes.
   * Later entries override earlier ones for the same path.
   * @param {Array} changes
   * @returns {Map<string, { seq, op, hash, size, mtime }>}
   */
  buildChangeMap (changes) {
    const map = new Map()
    for (const c of changes) {
      map.set(c.path, { seq: c.seq, op: c.op, hash: c.hash, size: c.size, mtime: c.mtime, rev: c.rev ?? null })
    }
    return map
  }

  // ── Sync diff ───────────────────────────────────────────────

  /**
   * Compute the sync diff between local and a remote peer.
   * Returns actions to take: pull, push, conflict, or skip.
   *
   * @param {string} peerKey - hex key of the remote peer's feed
   * @returns {Array<{ path, action, local?, remote?, ancestor? }>}
   *   action: 'pull' | 'push' | 'conflict' | 'skip' | 'delete-local' | 'delete-remote'
   */
  async diff (peerKey) {
    const peerFeed = this._peerFeeds.get(peerKey)
    if (!peerFeed) throw new Error('Unknown peer: ' + peerKey)

    await peerFeed.ready()
    await peerFeed.update()

    // Get last-synced state for all files with this peer
    const syncStates = await this._fileIndex.listSyncStates(peerKey)
    const syncMap = new Map()
    for (const s of syncStates) {
      syncMap.set(s.path, s)
    }

    // Get my changes since last sync
    const myLastSeq = this._getMaxSyncedSeq(syncStates, 'local')
    const myChanges = await this.getChanges(this._feed, -1) // read all, filter by syncMap
    const myMap = this.buildChangeMap(myChanges)

    // Get their changes since last sync
    const theirChanges = await this.getChanges(peerFeed, -1) // read all
    const theirMap = this.buildChangeMap(theirChanges)

    // Collect all paths mentioned by either side
    const allPaths = new Set([...myMap.keys(), ...theirMap.keys()])

    const actions = []

    for (const path of allPaths) {
      const mine = myMap.get(path)
      const theirs = theirMap.get(path)
      const ancestor = syncMap.get(path)

      const myHash = mine ? mine.hash : null
      const theirHash = theirs ? theirs.hash : null
      const ancestorHash = ancestor ? ancestor.lastHash : null

      // Both have it and hashes match — in sync
      if (myHash && theirHash && myHash === theirHash) {
        actions.push({ path, action: 'skip', local: mine, remote: theirs })
        continue
      }

      // Only they have it (or they changed, I haven't)
      if (theirHash && !mine) {
        actions.push({ path, action: 'pull', remote: theirs })
        continue
      }

      // Only I have it (or I changed, they haven't)
      if (myHash && !theirs) {
        actions.push({ path, action: 'push', local: mine })
        continue
      }

      // Both null — both deleted
      if (!myHash && !theirHash) {
        actions.push({ path, action: 'skip' })
        continue
      }

      // One deleted, one has content
      if (mine && mine.op === OP_DEL && theirHash) {
        // I deleted, they still have it
        if (ancestorHash && ancestorHash === theirHash) {
          // They haven't changed since ancestor — propagate my delete
          actions.push({ path, action: 'delete-remote', local: mine, remote: theirs })
        } else {
          // They changed it after ancestor — conflict
          actions.push({ path, action: 'conflict', local: mine, remote: theirs, ancestor })
        }
        continue
      }

      if (theirs && theirs.op === OP_DEL && myHash) {
        // They deleted, I still have it
        if (ancestorHash && ancestorHash === myHash) {
          // I haven't changed since ancestor — accept their delete
          actions.push({ path, action: 'delete-local', local: mine, remote: theirs })
        } else {
          // I changed it after ancestor — conflict
          actions.push({ path, action: 'conflict', local: mine, remote: theirs, ancestor })
        }
        continue
      }

      // Both have different content — use ancestor to determine conflict
      if (myHash && theirHash && myHash !== theirHash) {
        if (ancestorHash === myHash) {
          // Only they changed — pull
          actions.push({ path, action: 'pull', local: mine, remote: theirs })
        } else if (ancestorHash === theirHash) {
          // Only I changed — push
          actions.push({ path, action: 'push', local: mine, remote: theirs })
        } else {
          // Both changed — conflict
          actions.push({ path, action: 'conflict', local: mine, remote: theirs, ancestor })
        }
        continue
      }

      // Fallback — shouldn't reach here
      actions.push({ path, action: 'skip', local: mine, remote: theirs })
    }

    return actions
  }

  // ── Conflict resolution ─────────────────────────────────────

  /**
   * Resolve a conflict using a strategy
   * @param {{ path, local, remote, ancestor }} conflict
   * @param {string} strategy - one of STRATEGIES
   * @returns {{ action: 'pull'|'push'|'keep-both'|'skip', winner?: 'local'|'remote' }}
   */
  resolveConflict (conflict, strategy) {
    switch (strategy) {
      case 'mine-wins':
        return { action: 'push', winner: 'local' }

      case 'theirs-wins':
        return { action: 'pull', winner: 'remote' }

      case 'newest-wins': {
        const mySeq = conflict.local ? conflict.local.seq : -1
        const theirSeq = conflict.remote ? conflict.remote.seq : -1
        if (mySeq >= theirSeq) {
          return { action: 'push', winner: 'local' }
        }
        return { action: 'pull', winner: 'remote' }
      }

      case 'keep-both':
        return { action: 'keep-both' }

      case 'skip':
        return { action: 'skip' }

      case 'prompt':
      default:
        // Add to conflict queue for user resolution
        this._conflicts.push(conflict)
        return { action: 'skip' }
    }
  }

  /**
   * Resolve a queued conflict by user choice
   * @param {string} path
   * @param {string} choice - 'overwrite' | 'keep-both' | 'skip'
   * @returns {{ action: string } | null}
   */
  resolveQueued (path, choice) {
    const idx = this._conflicts.findIndex(c => c.path === path)
    if (idx === -1) return null

    const conflict = this._conflicts.splice(idx, 1)[0]

    switch (choice) {
      case 'overwrite':
        return { action: 'pull', winner: 'remote', conflict }
      case 'keep-mine':
        return { action: 'push', winner: 'local', conflict }
      case 'keep-both':
        return { action: 'keep-both', conflict }
      case 'skip':
      default:
        return { action: 'skip', conflict }
    }
  }

  /**
   * Update sync state after a successful sync operation
   * @param {string} peerKey
   * @param {string} path
   * @param {string} hash - the hash both sides now agree on
   * @param {number} seq - the sequence number of the operation
   */
  async markSynced (peerKey, path, hash, seq) {
    await this._fileIndex.putSyncState(peerKey, path, {
      lastSeq: seq,
      lastHash: hash
    })
  }

  // ── Internal ────────────────────────────────────────────────

  _getMaxSyncedSeq (syncStates, _side) {
    let max = -1
    for (const s of syncStates) {
      if (s.lastSeq > max) max = s.lastSeq
    }
    return max
  }
}
/**
 * TransferManager — On-demand chunking and streaming file transfer
 *
 * Sender side:
 *   1. Read file from disk
 *   2. Chunk on demand (adaptive FastCDC)
 *   3. Send chunk hashes → peer says which it needs → stream those chunks
 *   4. Release chunks from memory after send
 *
 * Receiver side:
 *   1. Receive chunk hashes, respond with which are missing
 *   2. Receive chunks, write to <file>.overlay-partial
 *   3. Verify each chunk hash
 *   4. Atomic rename on completion
 *
 * No blob storage. Chunks exist in memory during transfer only.
 */

import fs from 'bare-fs'
import path from 'bare-path'
import os from 'bare-os'
import c from 'compact-encoding'
import sodium from 'sodium-universal'
import { chunk as chunkBuffer, chunkStream, hashChunk, createStreamingHasher, selectTier, getTierParams } from './chunker.js'

export const PARTIAL_SUFFIX = '.overlay-partial'

export const partialPathFor = (targetPath) => path.join(path.dirname(targetPath), path.basename(targetPath) + PARTIAL_SUFFIX)

// Resume verification + whole-file hashing run async + yielding so a multi-GB pass
// never blocks the event loop (IPC, pause/cancel, progress stay live).
const VERIFY_YIELD_EVERY = 64
const yieldToLoop = () => new Promise((resolve) => setTimeout(resolve, 0))

// Receive journal: bitmap + whole-file hash snapshot, stored in app-private storage
// (opts.journalDir) keyed by a digest of the destination path — never next to the
// download — so resume is O(1) and the incremental verify continues with no re-read.
const JOURNAL_SUFFIX = '.journal'
const JOURNAL_MAGIC = 0x4d4a5633
const JOURNAL_FLUSH_EVERY = 512
const JOURNAL_FLUSH_INTERVAL_MS = 1500
export const journalNameFor = (targetPath) => hashChunk(Buffer.from(targetPath)) + JOURNAL_SUFFIX

// Block-read prepare — host-adaptive read block size.
const READ_BLOCK_DEFAULT = 8 * 1024 * 1024
const READ_BLOCK_LOW_RAM = 4 * 1024 * 1024
const LOW_RAM_THRESHOLD = 6 * 1024 * 1024 * 1024

// Cap on stashed just-received chunk buffers (per transfer, insert-refusal), and the
// hash pump's yield budget — stash-fed iterations never await, so the pump yields by
// bytes hashed, not chunk count.
const MEM_STASH_BYTES_DEFAULT = 16 * 1024 * 1024
const DRAIN_YIELD_BYTES = 2 * 1024 * 1024

// Emit file blocks >= tier maxSize so chunkStream drains every block
// instead of re-copying a growing pending buffer (64 KiB createReadStream reads force
// ~56x memcpy at tier 3). Owns its fd and closes it on every terminal path: normal
// end, consumer break/throw (for-await calls .return() → finally), read error.
async function * readFileBlocks (filePath, size, blockSize) {
  const fd = await fs.open(filePath, 'r')
  try {
    let pos = 0
    while (pos < size) {
      const want = Math.min(blockSize, size - pos)
      const buf = Buffer.allocUnsafe(want)
      let filled = 0
      while (filled < want) {
        const n = await fs.read(fd, buf, filled, want - filled, pos + filled)
        if (n <= 0) break
        filled += n
      }
      if (filled === 0) break
      pos += filled
      yield filled === buf.length ? buf : buf.subarray(0, filled)
    }
  } finally {
    try { await fs.close(fd) } catch {}
  }
}

function bitmapOf (received, total) {
  const bm = Buffer.alloc((total + 7) >> 3)
  for (const i of received) bm[i >> 3] |= 1 << (i & 7)
  return bm
}

// Drop journals whose partial is gone, are stale, or are corrupt/foreign. Each
// journal self-describes its partial path (read cheaply from the header). Standalone
// (no TransferManager instance) so a boot-time sweep can reclaim orphans without
// the overlay stack being live.
export function cleanupOrphanedJournals (journalDir, maxAge = 7 * 86400000) {
  const cleaned = []
  if (!journalDir) return cleaned
  let entries
  try { entries = fs.readdirSync(journalDir) } catch { return cleaned }
  const now = Date.now()
  for (const entry of entries) {
    if (!entry.endsWith(JOURNAL_SUFFIX)) continue
    const full = path.join(journalDir, entry)
    let remove = false
    try {
      const raw = fs.readFileSync(full)
      const s = { start: 0, end: raw.length, buffer: raw }
      if (c.uint32.decode(s) !== JOURNAL_MAGIC) remove = true
      else {
        c.uint32.decode(s)
        const partialPath = c.string.decode(s)
        if (!fs.existsSync(partialPath)) remove = true
        else if (now - fs.statSync(full).mtimeMs > maxAge) remove = true
      }
    } catch { remove = true }
    if (remove) { try { fs.unlinkSync(full); cleaned.push(full) } catch {} }
  }
  return cleaned
}

export class TransferManager {
  constructor (fileIndex, opts = {}) {
    this._fileIndex = fileIndex
    this._maxConcurrency = opts.maxConcurrency || 4
    this._active = new Map() // path → transfer state
    this._memStashBytes = opts.memStashBytes ?? MEM_STASH_BYTES_DEFAULT
    this._journalDir = opts.journalDir || null
    if (this._journalDir) { try { fs.mkdirSync(this._journalDir, { recursive: true }) } catch {} }
  }

  _journalPathFor (targetPath) {
    if (!this._journalDir) return null
    return path.join(this._journalDir, journalNameFor(targetPath))
  }

  // ── Sender side ─────────────────────────────────────────────

  /**
   * Prepare a file for sending — read from disk, chunk, return hashes.
   * Chunk map is persisted for large files (>1MB).
   *
   * @param {string} filePath - absolute path on disk
   * @param {string} overlayPath - path in the overlay namespace
   * @returns {{ tier: number, chunks: Array<{ hash, offset, length }>, size: number } | null}
   */
  async prepareFile (filePath, overlayPath, opts = {}) {
    // Check file exists and get stats
    let stat
    try {
      stat = fs.statSync(filePath)
    } catch {
      return null
    }

    if (!stat.isFile()) return null

    const mtimeBefore = stat.mtimeMs

    // Check for existing chunk map (skip re-chunking if valid)
    const existing = await this._fileIndex.getChunkMap(overlayPath)
    const fileMeta = await this._fileIndex.getFile(overlayPath)

    if (existing && fileMeta && fileMeta.mtime === mtimeBefore) {
      // Chunk map still valid — file hasn't changed.
      // Backfill the content-addressed alias so the serve path (a
      // different overlay key) reuses this map instead of re-reading the file —
      // covers maps chunked before content-addressed caching existed.
      if (fileMeta.contentHash && stat.size >= 1048576 && !await this._fileIndex.hasChunkMapByHash(fileMeta.contentHash)) {
        await this._fileIndex.putChunkMapByHash(fileMeta.contentHash, existing)
      }
      return {
        tier: selectTier(stat.size),
        chunks: existing,
        size: stat.size,
        contentHash: fileMeta.contentHash // surfaced so the publish path gets the hash from one pass
      }
    }

    // Read in blocks >= tier maxSize and chunk from views (copy:false).
    // Cut points are content-stable across block sizes, so peers still match hashes.
    // size is passed to the hasher so the streaming digest matches hashChunk(buffer).
    const tier = selectTier(stat.size)
    const blockSize = Math.max(
      getTierParams(tier).maxSize,
      os.totalmem() < LOW_RAM_THRESHOLD ? READ_BLOCK_LOW_RAM : READ_BLOCK_DEFAULT
    )
    const fileHasher = createStreamingHasher({ size: stat.size })
    const chunks = []

    for await (const c of chunkStream(readFileBlocks(filePath, stat.size, blockSize), { tier, copy: false })) {
      // honor a publish-cancel signal (per-chunk granularity); on throw the
      // for-await calls readFileBlocks.return() which closes the fd.
      if (opts.signal?.aborted) { const e = new Error('publish aborted'); e.code = 'ECANCELLED'; throw e }
      fileHasher.update(c.data)
      chunks.push({ hash: c.hash, offset: c.offset, length: c.length })
      opts.onProgress?.(c.length)
    }

    // mtime guard — abort if file changed during the streaming read. A
    // vanished source (moved out of the folder mid-read) is just another "changed
    // during read" case — the open fd kept the read alive after a same-volume
    // rename, so it is this path-resolving stat that trips ENOENT. Guard it like
    // the pre-read stat above and re-queue rather than throw.
    let statAfter
    try {
      statAfter = fs.statSync(filePath)
    } catch {
      return null // source vanished during read, caller should re-queue
    }
    if (statAfter.mtimeMs !== mtimeBefore) {
      return null // file changed during read, caller should re-queue
    }

    // Post-read guard: a mid-read shrink can feed the digest fewer bytes than the
    // size baked into its leaf prefix — re-queue instead of persisting a bad hash.
    if (fileHasher.bytes !== stat.size) return null

    // Update file index
    const contentHash = fileHasher.digest()

    // Persist chunk map for large files — both path-keyed (legacy serve lookups)
    // and content-addressed, so the serve path reuses it across keys.
    // byHashOnly (the publish prepareForServe pass) keeps ONLY the
    // content-addressed map — the serve path resolves by hash, so the path-keyed map
    // + putFile under a throwaway overlay key would be dead, never-read state.
    if (stat.size >= 1048576) {
      if (!opts.byHashOnly) await this._fileIndex.putChunkMap(overlayPath, chunks)
      if (!await this._fileIndex.hasChunkMapByHash(contentHash)) await this._fileIndex.putChunkMapByHash(contentHash, chunks)
    }

    if (!opts.byHashOnly) {
      await this._fileIndex.putFile(overlayPath, {
        contentHash,
        size: stat.size,
        mtime: mtimeBefore
      })
    }

    return { tier, chunks, size: stat.size, contentHash }
  }

  /**
   * Read a specific chunk from disk by its offset and length.
   * Returns raw bytes — caller sends them to the peer.
   *
   * @param {string} filePath - absolute path on disk
   * @param {number} offset - byte offset
   * @param {number} length - bytes to read
   * @returns {Buffer | null}
   */
  readChunk (filePath, offset, length) {
    try {
      const fd = fs.openSync(filePath, 'r')
      const buf = Buffer.alloc(length)
      const bytesRead = fs.readSync(fd, buf, 0, length, offset)
      fs.closeSync(fd)
      if (bytesRead !== length) return null
      return buf
    } catch {
      return null
    }
  }

  /**
   * Determine which chunks a peer needs (chunks they don't already have).
   *
   * @param {Array<{ hash, offset, length }>} offered - chunks the sender has
   * @param {Set<string>} peerHas - set of chunk hashes the peer already has
   * @returns {number[]} indices of chunks the peer needs
   */
  computeNeeded (offered, peerHas) {
    const needed = []
    for (let i = 0; i < offered.length; i++) {
      if (!peerHas.has(offered[i].hash)) {
        needed.push(i)
      }
    }
    return needed
  }

  // ── Receiver side ───────────────────────────────────────────

  // Start receiving a file. A same-size partial is resumed: the journal restores the
  // received-set + whole-file hash snapshot in O(1) (no re-read); failing that, an
  // async, yielding re-verify rebuilds them without blocking the event loop.
  // opts.isCancelled aborts a long re-verify; opts.onVerifyProgress(0..1) drives the UI.
  async startReceive (targetPath, meta, opts = {}) {
    const isCancelled = opts.isCancelled || (() => false)
    const dir = path.dirname(targetPath)
    const base = path.basename(targetPath)
    // Visible partial (no leading dot) so an in-progress download shows in the
    // destination folder; the distinct ".overlay-partial" suffix keeps host-app
    // sweeps keyed on a plain ".partial" suffix from deleting a paused overlay
    // partial.
    const partialPath = path.join(dir, base + PARTIAL_SUFFIX)
    const journalPath = this._journalPathFor(targetPath)

    fs.mkdirSync(dir, { recursive: true })

    let resumed = false
    try { resumed = fs.statSync(partialPath).size === meta.size } catch {}

    let received = new Set()
    let hasher = meta.contentHash ? createStreamingHasher({ size: meta.size }) : null
    let hashFrontier = 0

    if (resumed) {
      const j = journalPath ? this._loadJournal(journalPath, partialPath, meta) : null
      if (j) {
        received = j.received
        hashFrontier = j.hashFrontier
        if (meta.contentHash) hasher = createStreamingHasher({ size: meta.size, restore: { state: j.hasherState, bytes: j.hasherBytes } })
      } else {
        const rec = await this._recoverPartialAsync(partialPath, meta, isCancelled, opts.onVerifyProgress)
        received = rec.received; hasher = rec.hasher; hashFrontier = rec.hashFrontier
      }
    } else {
      if (journalPath) { try { fs.unlinkSync(journalPath) } catch {} }
    }

    if (isCancelled()) { const e = new Error('receive cancelled during setup'); e.code = 'ECANCELLED'; throw e }

    // ONE persistent fd for the whole transfer — positioned writeSync/fs.read never
    // move a shared cursor, so a single handle serves the chunk writes, the hash
    // pump's gap read-back, and the journal fsync. Replaces an openSync+closeSync per
    // chunk; 'w+' creates+truncates the fresh partial in the same open.
    const fd = fs.openSync(partialPath, resumed ? 'r+' : 'w+')
    if (!resumed) {
      try { fs.ftruncateSync(fd, meta.size) } catch (err) { try { fs.closeSync(fd) } catch {}; throw err }
    }

    const total = meta.chunks.length
    const state = {
      partialPath,
      targetPath,
      journalPath,
      chunks: meta.chunks,
      received,
      bitmap: bitmapOf(received, total),
      total,
      size: meta.size,
      contentHash: meta.contentHash || null,
      hasher,
      hashFrontier,
      advancePromise: null,
      draining: false,
      sinceFlush: 0,
      lastFlush: 0,
      flushing: false,
      flushPending: false,
      fd,
      // Bounded stash of just-received verified chunk buffers so the hash pump feeds
      // from memory instead of reading the bytes back off disk. Never load-bearing:
      // bytes hit disk first; a miss falls back to read-back.
      memChunks: new Map(),
      memBytes: 0,
      stats: { readbacks: 0, stashHits: 0 }
    }

    // A prior transfer for this path that ended via _fail (stall / all-peers-
    // gone / disk error) leaves its state here with an open fd — close it before
    // overwriting so a retry/resume of the same file can't orphan the fd.
    const prev = this._active.get(targetPath)
    if (prev) this._closeFd(prev)

    this._active.set(targetPath, state)
    // A fresh transfer has nothing worth journaling yet; a resume that recovered
    // chunks persists them now so an immediate re-pause is O(1).
    if (received.size > 0) this._flushJournal(state)
    return state
  }

  // Rebuild the received-set from a same-size partial without blocking the loop:
  // async reads, a yield every VERIFY_YIELD_EVERY chunks, feeding the contiguous
  // prefix into the whole-file hasher inline (single pass, no second read).
  async _recoverPartialAsync (partialPath, meta, isCancelled, onVerifyProgress) {
    const received = new Set()
    const hasher = meta.contentHash ? createStreamingHasher({ size: meta.size }) : null
    let hashFrontier = 0
    const total = meta.chunks.length
    const maxLen = meta.chunks.reduce((m, c) => (c.length > m ? c.length : m), 0)
    const buf = Buffer.allocUnsafe(maxLen)
    let lastPct = -1
    onVerifyProgress?.(0)
    const fd = await fs.open(partialPath, 'r')
    try {
      for (let i = 0; i < total; i++) {
        if (isCancelled()) { const e = new Error('receive recovery cancelled'); e.code = 'ECANCELLED'; throw e }
        const ci = meta.chunks[i]
        let n = 0
        try { n = await fs.read(fd, buf, 0, ci.length, ci.offset) } catch {}
        const view = buf.subarray(0, ci.length)
        if (n === ci.length && hashChunk(view) === ci.hash) {
          received.add(i)
          if (hasher && i === hashFrontier) { hasher.update(view); hashFrontier++ }
        }
        if ((i & (VERIFY_YIELD_EVERY - 1)) === VERIFY_YIELD_EVERY - 1) {
          const pct = Math.floor(((i + 1) / total) * 100)
          if (pct !== lastPct) { lastPct = pct; onVerifyProgress?.(pct / 100) }
          await yieldToLoop()
        }
      }
    } finally {
      try { await fs.close(fd) } catch {}
    }
    onVerifyProgress?.(1)
    return { received, hasher, hashFrontier }
  }

  _encodeJournal (state) {
    const total = state.total
    const bm = state.bitmap
    const ch = state.contentHash ? Buffer.from(state.contentHash, 'hex') : Buffer.alloc(32)
    const snap = state.hasher ? state.hasher.snapshot() : { state: Buffer.alloc(0), bytes: 0 }
    const s = { start: 0, end: 0, buffer: null }
    c.uint32.preencode(s, JOURNAL_MAGIC)
    c.uint32.preencode(s, sodium.crypto_generichash_STATEBYTES)
    c.string.preencode(s, state.partialPath)
    c.uint64.preencode(s, state.size)
    c.uint32.preencode(s, total)
    c.fixed32.preencode(s, ch)
    c.uint32.preencode(s, state.hashFrontier)
    c.uint64.preencode(s, snap.bytes)
    c.buffer.preencode(s, snap.state)
    c.buffer.preencode(s, bm)
    s.buffer = Buffer.allocUnsafe(s.end)
    c.uint32.encode(s, JOURNAL_MAGIC)
    c.uint32.encode(s, sodium.crypto_generichash_STATEBYTES)
    c.string.encode(s, state.partialPath)
    c.uint64.encode(s, state.size)
    c.uint32.encode(s, total)
    c.fixed32.encode(s, ch)
    c.uint32.encode(s, state.hashFrontier)
    c.uint64.encode(s, snap.bytes)
    c.buffer.encode(s, snap.state)
    c.buffer.encode(s, bm)
    return s.buffer
  }

  // Returns the resume state only when the journal is for this partial, binds to
  // this content, and its snapshot is restorable on this libsodium build; else null
  // so the caller falls back to the async re-verify.
  _loadJournal (journalPath, partialPath, meta) {
    let raw
    try { raw = fs.readFileSync(journalPath) } catch { return null }
    try {
      const s = { start: 0, end: raw.length, buffer: raw }
      if (c.uint32.decode(s) !== JOURNAL_MAGIC) return null
      const stateBytes = c.uint32.decode(s)
      const storedPartial = c.string.decode(s)
      const size = c.uint64.decode(s)
      const total = c.uint32.decode(s)
      const ch = c.fixed32.decode(s).toString('hex')
      const hashFrontier = c.uint32.decode(s)
      const hasherBytes = c.uint64.decode(s)
      const hasherState = c.buffer.decode(s)
      const bm = c.buffer.decode(s)
      // F1: never trust a journal we cannot whole-file-verify. Both finalize and the
      // legacy receive path skip the whole-file hash when contentHash is absent, so a
      // FOREIGN journal with a matching size + chunk-count (a stale .overlay-partial
      // for different content the CDC split into the same number of chunks) would be
      // trusted and finalized as merged old+new bytes, undetected. Require a content
      // hash to resume from a journal; without one the caller falls back to
      // _recoverPartialAsync, which re-verifies every chunk by its per-chunk hash.
      if (!meta.contentHash) return null
      if (storedPartial !== partialPath) return null
      if (size !== meta.size || total !== meta.chunks.length) return null
      if (ch !== meta.contentHash) return null
      if (meta.contentHash) {
        if (stateBytes !== sodium.crypto_generichash_STATEBYTES) return null
        if (hasherState.length !== sodium.crypto_generichash_STATEBYTES) return null
      }
      const received = new Set()
      for (let i = 0; i < total; i++) if (bm[i >> 3] & (1 << (i & 7))) received.add(i)
      return { received, hashFrontier, hasherState, hasherBytes }
    } catch { return null }
  }

  // Best-effort async crash-hedge flush: never blocks the receive loop, single-flight
  // (a flush requested while one runs coalesces into one more pass). fsync the partial
  // BEFORE the journal references it so a power-loss can never leave the journal
  // marking a chunk received whose bytes aren't durable.
  _flushJournal (state) {
    if (!state.contentHash || !state.journalPath || state.fd == null) return
    if (state.flushing) { state.flushPending = true; return }
    if (Date.now() - state.lastFlush < JOURNAL_FLUSH_INTERVAL_MS) return
    state.flushing = true
    this._drainFlush(state)
  }

  async _drainFlush (state) {
    do {
      state.flushPending = false
      state.lastFlush = Date.now()
      if (state.fd == null) break
      let buf
      try { buf = this._encodeJournal(state) } catch { break }
      const tmp = state.journalPath + '.tmp'
      try {
        // fsync is best-effort and swallowed on purpose: on Windows, fsync
        // (FlushFileBuffers) on a read-only handle throws EPERM benignly, and we still
        // want the journal to persist so resume stays O(1). The EIO write-back hazard
        // this could mask is already closed upstream — F1 refuses to trust a journal
        // without a contentHash, and the finalize whole-file hash re-verifies the
        // gated flow — so skipping the commit here (former "F3") only regressed Windows.
        try { await fs.fsync(state.fd) } catch {}
        await fs.writeFile(tmp, buf)
        if (state.fd == null) break // paused/cancelled mid-flush — don't re-create the journal
        await fs.rename(tmp, state.journalPath)
      } catch { try { await fs.unlink(tmp) } catch {} }
    } while (state.flushPending && state.fd != null && state.contentHash)
    try { await fs.unlink(state.journalPath + '.tmp') } catch {}
    state.flushing = false
  }

  // Synchronous durable flush for pause/shutdown, where the app may quit before an
  // async flush would land.
  _flushJournalSync (state) {
    if (!state.contentHash || !state.journalPath) return
    let buf
    try { buf = this._encodeJournal(state) } catch { return }
    // fsync swallowed on purpose (Windows read-only-handle EPERM) — see _drainFlush.
    if (state.fd != null) { try { fs.fsyncSync(state.fd) } catch {} }
    try {
      const tmp = state.journalPath + '.tmp'
      fs.writeFileSync(tmp, buf)
      fs.renameSync(tmp, state.journalPath)
      state.lastFlush = Date.now()
    } catch { try { fs.unlinkSync(state.journalPath + '.tmp') } catch {} }
  }

  cleanJournals (maxAge) { return cleanupOrphanedJournals(this._journalDir, maxAge) }

  async _hashWholeFileAsync (filePath, size) {
    const h = createStreamingHasher({ size })
    for await (const buf of readFileBlocks(filePath, size, 8 * 1024 * 1024)) {
      h.update(buf)
      await yieldToLoop()
    }
    return h.digest()
  }

  // Advance the whole-file hash over the contiguous run of received chunks
  // as a single-flight BACKGROUND pump: chunk index order == file offset order, so
  // once chunk `hashFrontier` is present we feed its bytes and move on. The drain is
  // async + yielding so a large contiguous run that suddenly becomes available (a
  // gap filling after a low-frontier resume) cannot block the event loop.
  // Single-flight is `state.draining`, set/cleared SYNCHRONOUSLY inside _drainHash —
  // a stash-fed drain completes without awaiting, so a promise-based gate would stay
  // stale until its microtask clear and block same-macrotask restarts (protomux
  // delivers frame bursts synchronously). advancePromise is only the awaitable
  // handle for finalize/pause; its clear is guarded against nulling a newer drain.
  _advanceHash (state) {
    if (!state.hasher || state.draining) return
    if (!(state.hashFrontier < state.chunks.length && state.received.has(state.hashFrontier))) return
    const p = this._drainHash(state)
    state.advancePromise = p
    p.catch(() => {}).then(() => { if (state.advancePromise === p) state.advancePromise = null })
  }

  async _drainHash (state) {
    state.draining = true
    try {
      let sinceYield = 0
      while (state.fd != null && state.hashFrontier < state.chunks.length && state.received.has(state.hashFrontier)) {
        const i = state.hashFrontier
        const c = state.chunks[i]
        let buf = state.memChunks.get(i)
        if (buf !== undefined) {
          state.memChunks.delete(i)
          state.memBytes -= buf.length
          state.stats.stashHits++
        } else {
          buf = Buffer.alloc(c.length)
          try { await fs.read(state.fd, buf, 0, c.length, c.offset) } catch { return }
          state.stats.readbacks++
        }
        state.hasher.update(buf)
        state.hashFrontier++
        // Yield by BYTE volume: the stash path has no awaits, so a chunk-count
        // cadence would let a gap-fill hash the whole stash synchronously inside
        // the network event that delivered the gap chunk.
        sinceYield += buf.length
        if (sinceYield >= DRAIN_YIELD_BYTES) { sinceYield = 0; await yieldToLoop() }
      }
    } finally {
      state.draining = false
    }
  }

  // Close the persistent fd — MUST run before finalize's rename (an open handle
  // blocks the rename on Windows) — and drop the chunk stash.
  _closeFd (state) {
    if (state.fd != null) {
      try { fs.closeSync(state.fd) } catch {}
      state.fd = null
    }
    state.memChunks.clear()
    state.memBytes = 0
  }

  /**
   * Write a received chunk to the partial file. Verifies the hash.
   *
   * @param {string} targetPath - the final file path (used as key)
   * @param {number} index - chunk index
   * @param {Buffer} data - raw chunk bytes
   * @returns {{ ok: boolean, error?: string }}
   */
  writeChunk (targetPath, index, data) {
    const state = this._active.get(targetPath)
    if (!state) return { ok: false, error: 'no active transfer' }
    if (index < 0 || index >= state.chunks.length) return { ok: false, error: 'index out of range' }
    // Terminal race (chunk landed after close) — codeless so the scheduler
    // re-assigns instead of failing the fetch.
    if (state.fd == null) return { ok: false, error: 'transfer closed' }

    const expected = state.chunks[index]

    // Verify hash
    const actual = hashChunk(data)
    if (actual !== expected.hash) {
      return { ok: false, error: `hash mismatch at index ${index}: expected ${expected.hash.slice(0, 16)}... got ${actual.slice(0, 16)}...` }
    }

    // Verify length
    if (data.length !== expected.length) {
      return { ok: false, error: `length mismatch at index ${index}: expected ${expected.length} got ${data.length}` }
    }

    // Write to partial at the chunk's offset through the persistent fd. A local
    // I/O failure here (ENOSPC / EACCES / EROFS) is fatal and non-retryable —
    // return its code so the scheduler fails the fetch instead of looping over
    // other peers. Loop on a SHORT count: libuv swallows an error after partial
    // progress and returns short without throwing; retrying the remainder
    // surfaces the real coded error instead of leaving a silent hole the
    // stash-fed digest would never see.
    try {
      let written = 0
      while (written < data.length) {
        const n = fs.writeSync(state.fd, data, written, data.length - written, expected.offset + written)
        if (!(n > 0)) { const e = new Error(`short write at index ${index}: ${written}/${data.length} bytes`); e.code = 'EIO'; throw e }
        written += n
      }
    } catch (err) {
      return { ok: false, error: err.message, code: err.code }
    }

    state.received.add(index)
    state.bitmap[index >> 3] |= 1 << (index & 7)
    // Stash only what the pump can consume cleanly: skip when already hashed or
    // stashed, when the pump is mid-drain on exactly this index, or over cap.
    // Copied, not aliased — wire buffers are views into shared network slabs (udx
    // read slab, decrypted in place by secret-stream), so retaining `data` would
    // pin whole slabs and bet the digest on their write-once behavior.
    if (state.hasher && !state.memChunks.has(index) && state.memBytes + data.length <= this._memStashBytes &&
        (index > state.hashFrontier || (index === state.hashFrontier && !state.draining))) {
      state.memChunks.set(index, Buffer.from(data))
      state.memBytes += data.length
    }
    this._advanceHash(state)
    if (++state.sinceFlush >= JOURNAL_FLUSH_EVERY) { state.sinceFlush = 0; this._flushJournal(state) }

    return { ok: true }
  }

  /**
   * Check if a transfer is complete (all chunks received).
   *
   * @param {string} targetPath
   * @returns {boolean}
   */
  isComplete (targetPath) {
    const state = this._active.get(targetPath)
    if (!state) return false
    return state.received.size === state.total
  }

  /**
   * Finalize a completed transfer — atomic rename from partial to target.
   *
   * @param {string} targetPath
   * @returns {{ ok: boolean, error?: string }}
   */
  async finalize (targetPath) {
    const state = this._active.get(targetPath)
    if (!state) return { ok: false, error: 'no active transfer' }
    if (state.received.size !== state.total) {
      return { ok: false, error: `incomplete: ${state.received.size}/${state.total} chunks` }
    }

    // whole-file integrity — computed incrementally by the background hash
    // pump during the transfer. Kick the pump and await it to quiescence, then read
    // the digest (no full re-read). On a mismatch, drop the corrupt partial so a
    // retry starts clean.
    if (state.hasher) {
      this._advanceHash(state)
      while (state.advancePromise) { try { await state.advancePromise } catch {} }
    }
    if (state.contentHash) {
      const actual = state.hashFrontier === state.chunks.length ? state.hasher.digest() : null
      if (actual !== state.contentHash) {
        this._closeFd(state)
        try { fs.unlinkSync(state.partialPath) } catch {}
        if (state.journalPath) { try { fs.unlinkSync(state.journalPath) } catch {} }
        this._active.delete(targetPath)
        return { ok: false, error: 'content-hash mismatch', code: 'EHASHMISMATCH' }
      }
    }
    this._closeFd(state)

    try {
      fs.renameSync(state.partialPath, state.targetPath)
    } catch (err) {
      // Carry the code (routes through the coded local-I/O rethrow) and clear the
      // state — the partial + journal stay for a clean resume-retry.
      this._active.delete(targetPath)
      return { ok: false, error: 'rename failed: ' + err.message, code: err.code }
    }

    if (state.journalPath) { try { fs.unlinkSync(state.journalPath) } catch {} }
    this._active.delete(targetPath)
    return { ok: true }
  }

  // Stop receiving but KEEP the partial on disk so a later startReceive
  // verifies and resumes it. Distinct from cancel(), which unlinks the partial.
  // Drains the in-flight hash advance to the full contiguous frontier first, so the
  // journal captures it and resume stays O(1) (no re-read) and deterministic.
  async pause (targetPath) {
    const state = this._active.get(targetPath)
    if (!state) return
    if (state.hasher) {
      this._advanceHash(state)
      while (state.advancePromise) { try { await state.advancePromise } catch {} }
    }
    this._flushJournalSync(state)
    this._closeFd(state)
    this._active.delete(targetPath)
  }

  /**
   * Cancel an active transfer and clean up the partial file.
   *
   * @param {string} targetPath
   */
  cancel (targetPath) {
    const state = this._active.get(targetPath)
    if (!state) return

    this._closeFd(state)
    try {
      fs.unlinkSync(state.partialPath)
    } catch {
      // partial file may not exist
    }
    if (state.journalPath) { try { fs.unlinkSync(state.journalPath) } catch {} }

    this._active.delete(targetPath)
  }

  /**
   * Get transfer progress for an active transfer.
   *
   * @param {string} targetPath
   * @returns {{ received: number, total: number, percentage: number } | null}
   */
  getProgress (targetPath) {
    const state = this._active.get(targetPath)
    if (!state) return null

    return {
      received: state.received.size,
      total: state.total,
      percentage: state.total > 0 ? Math.round(state.received.size / state.total * 100) : 0
    }
  }

  /**
   * List all active transfers.
   *
   * @returns {Array<{ path: string, received: number, total: number, percentage: number }>}
   */
  listActive () {
    const result = []
    for (const [targetPath, state] of this._active) {
      result.push({
        path: targetPath,
        received: state.received.size,
        total: state.total,
        percentage: state.total > 0 ? Math.round(state.received.size / state.total * 100) : 0
      })
    }
    return result
  }

  /**
   * Clean up stale partial files in a directory (older than maxAge).
   *
   * @param {string} dir - directory to scan
   * @param {number} [maxAge=86400000] - max age in ms (default 24h)
   * @returns {string[]} paths of cleaned files
   */
  cleanPartials (dir, maxAge = 86400000) {
    const cleaned = []
    try {
      const entries = fs.readdirSync(dir)
      const now = Date.now()
      for (const entry of entries) {
        if (!entry.endsWith(PARTIAL_SUFFIX)) continue
        const full = path.join(dir, entry)
        try {
          const stat = fs.statSync(full)
          if (now - stat.mtimeMs > maxAge) {
            fs.unlinkSync(full)
            cleaned.push(full)
          }
        } catch {
          // skip unreadable files
        }
      }
    } catch {
      // directory may not exist
    }
    return cleaned
  }
}
/**
 * Tree — content-addressable directory hashing for hyper-overlay
 *
 * A tree is a canonical serialization of a directory's immediate entries.
 * Each entry is one of: file, subdirectory, symlink. The tree's identity
 * is blake2b-256 of its canonical bytes.
 *
 * Format (P.10 Phase 0.5a):
 *
 *   tree := "pgh-tree-1\n" ||
 *           varint(n_entries) ||
 *           entry[0] || ... || entry[n-1]
 *
 *   entry := u8(kind)  ||           // 0=file, 1=dir, 2=symlink
 *            u8(exec)  ||           // 0 or 1; always 0 for dir/symlink
 *            varint(name_len) || name ||  // UTF-8 NFC, no NUL, no "/"
 *            32_bytes(child_hash)
 *
 *   tree_hash := blake2b-256(tree_bytes)
 *
 * Identity fields (in the hash): kind, exec-bit, name, child_hash.
 * Explicitly excluded: mtime, size, uid/gid, full mode.
 *
 * Entries MUST be sorted byte-wise by name (after NFC normalization).
 * Encoders emit sorted; decoders reject unsorted trees.
 *
 * Symlinks: child_hash = blake2b-256 of the target path bytes (not followed).
 * Empty directories: zero-entry tree, valid, deterministic.
 */

import fs from '#fs'
import path from '#path'
import crypto from 'hypercore-crypto'
import c from 'compact-encoding'

// ── Constants ─────────────────────────────────────────────────

export const MAGIC = Buffer.from('pgh-tree-1\n', 'utf8')
export const KIND_FILE = 0
export const KIND_DIR = 1
export const KIND_SYMLINK = 2

// Windows reserved device names (case-insensitive, with or without extension)
const WINDOWS_RESERVED = new Set([
  'CON', 'PRN', 'AUX', 'NUL',
  'COM1', 'COM2', 'COM3', 'COM4', 'COM5', 'COM6', 'COM7', 'COM8', 'COM9',
  'LPT1', 'LPT2', 'LPT3', 'LPT4', 'LPT5', 'LPT6', 'LPT7', 'LPT8', 'LPT9'
])

// Chars forbidden in filenames on Windows (beyond format-layer rules)
// eslint-disable-next-line no-control-regex
const WINDOWS_FORBIDDEN = /[\\:*?"<>|\x00-\x1f]/

// Default skip list for walkTree — minimal, just obvious platform cruft.
// Consumers that want more (e.g. .git for pgh publish) pass their own.
export const DEFAULT_SKIP = new Set(['.DS_Store', 'Thumbs.db'])

// ── Name validation (format layer) ────────────────────────────

/**
 * Normalize a filename for hashing and validate format-layer rules.
 * Throws on unrepresentable names. Returns the canonical form.
 *
 * @param {string} name - single path component (no "/")
 * @returns {string} NFC-normalized name
 */
export function normalizeName (name) {
  if (typeof name !== 'string') {
    throw new Error('tree entry name must be string, got ' + typeof name)
  }
  if (name.length === 0) {
    throw new Error('tree entry name cannot be empty')
  }
  if (name === '.' || name === '..') {
    throw new Error('tree entry name cannot be "." or ".."')
  }
  if (name.indexOf('/') !== -1) {
    throw new Error('tree entry name cannot contain "/": ' + JSON.stringify(name))
  }
  if (name.indexOf('\x00') !== -1) {
    throw new Error('tree entry name cannot contain NUL: ' + JSON.stringify(name))
  }

  const normalized = name.normalize('NFC')

  // Verify the normalized form is valid UTF-8 that round-trips through
  // Buffer (catches lone surrogates and other malformed sequences).
  const asBytes = Buffer.from(normalized, 'utf8')
  if (asBytes.toString('utf8') !== normalized) {
    throw new Error('tree entry name is not valid UTF-8: ' + JSON.stringify(name))
  }

  return normalized
}

// ── Consumer-policy layer ─────────────────────────────────────

/**
 * Check a name against Windows compatibility rules.
 * Does NOT throw — returns a reason string (or null if the name is OK).
 * Used by pgh publish for strict cross-platform validation.
 *
 * @param {string} name
 * @returns {string|null} reason if incompatible, null if OK
 */
export function windowsIncompatibleReason (name) {
  if (WINDOWS_FORBIDDEN.test(name)) {
    return 'contains Windows-forbidden character (\\:*?"<>| or control code)'
  }
  const trimmed = name.replace(/\.+$/, '').replace(/ +$/, '')
  if (trimmed !== name) {
    return 'trailing "." or space (Windows strips these silently)'
  }
  // Reserved device names apply with any extension: AUX.log, PRN.txt etc.
  const base = name.split('.')[0].toUpperCase()
  if (WINDOWS_RESERVED.has(base)) {
    return `"${base}" is a reserved Windows device name (even with extension)`
  }
  return null
}

// ── Canonical encoding ────────────────────────────────────────

/**
 * Encode a single entry to its canonical byte representation.
 *
 * @param {object} state - compact-encoding state
 * @param {{kind: number, exec: number, name: string, childHash: Buffer|string}} e
 */
function encodeEntry (state, e) {
  if (e.kind !== KIND_FILE && e.kind !== KIND_DIR && e.kind !== KIND_SYMLINK) {
    throw new Error('invalid entry kind: ' + e.kind)
  }
  if (e.exec !== 0 && e.exec !== 1) {
    throw new Error('entry exec must be 0 or 1')
  }
  if (e.kind !== KIND_FILE && e.exec !== 0) {
    throw new Error('non-file entry must have exec=0')
  }

  const hash = toBuffer32(e.childHash)

  c.uint8.encode(state, e.kind)
  c.uint8.encode(state, e.exec)
  c.string.encode(state, e.name) // compact-encoding: varint len + utf8 bytes
  state.buffer.set(hash, state.start)
  state.start += 32
}

function preencodeEntry (state, e) {
  c.uint8.preencode(state, e.kind)
  c.uint8.preencode(state, e.exec)
  c.string.preencode(state, e.name)
  state.end += 32
}

/**
 * Serialize a list of entries to canonical tree bytes.
 * Entries are sorted + normalized as a side-effect of the call.
 *
 * @param {Array<{kind: number, exec: number, name: string, childHash: Buffer|string}>} entries
 * @returns {Buffer}
 */
export function encodeTree (entries) {
  const normalized = entries.map(e => ({
    kind: e.kind,
    exec: e.exec || 0,
    name: normalizeName(e.name),
    childHash: e.childHash
  }))
  normalized.sort(compareEntries)

  // Sanity: no duplicate names after normalization
  for (let i = 1; i < normalized.length; i++) {
    if (normalized[i].name === normalized[i - 1].name) {
      throw new Error('duplicate entry name after NFC normalization: ' + normalized[i].name)
    }
  }

  const state = { start: 0, end: 0, buffer: null }
  state.end += MAGIC.length
  c.uint.preencode(state, normalized.length)
  for (const e of normalized) preencodeEntry(state, e)

  state.buffer = Buffer.alloc(state.end)

  state.buffer.set(MAGIC, state.start)
  state.start += MAGIC.length
  c.uint.encode(state, normalized.length)
  for (const e of normalized) encodeEntry(state, e)

  return state.buffer
}

/**
 * Byte-wise comparator for entry names (after NFC normalization).
 */
function compareEntries (a, b) {
  const ab = Buffer.from(a.name, 'utf8')
  const bb = Buffer.from(b.name, 'utf8')
  return Buffer.compare(ab, bb)
}

/**
 * Decode canonical tree bytes back to an entries list.
 * Rejects unsorted or malformed trees.
 *
 * @param {Buffer} buf
 * @returns {Array<{kind: number, exec: number, name: string, childHash: string}>}
 */
export function decodeTree (buf) {
  if (!Buffer.isBuffer(buf)) throw new Error('decodeTree expects a Buffer')
  if (buf.length < MAGIC.length) throw new Error('tree too short')
  if (buf.compare(MAGIC, 0, MAGIC.length, 0, MAGIC.length) !== 0) {
    throw new Error('tree magic mismatch (expected "pgh-tree-1")')
  }

  const state = { start: MAGIC.length, end: buf.length, buffer: buf }
  const count = c.uint.decode(state)
  const entries = []

  let prevName = null
  for (let i = 0; i < count; i++) {
    const kind = c.uint8.decode(state)
    const exec = c.uint8.decode(state)
    const name = c.string.decode(state)

    if (state.start + 32 > state.end) throw new Error('truncated tree: missing hash bytes')
    const childHash = buf.subarray(state.start, state.start + 32).toString('hex')
    state.start += 32

    // Validate format rules on read — reject invalid trees rather than silently accept
    const normalized = normalizeName(name)
    if (normalized !== name) {
      throw new Error('tree entry name not NFC-normalized: ' + JSON.stringify(name))
    }
    if (kind !== KIND_FILE && exec !== 0) {
      throw new Error('non-file entry with exec=1 is invalid')
    }
    if (prevName !== null) {
      const cmp = Buffer.compare(Buffer.from(prevName, 'utf8'), Buffer.from(name, 'utf8'))
      if (cmp === 0) throw new Error('tree has duplicate entry: ' + name)
      if (cmp > 0) throw new Error('tree entries not sorted: ' + prevName + ' before ' + name)
    }
    prevName = name

    entries.push({ kind, exec, name, childHash })
  }

  if (state.start !== state.end) throw new Error('tree has trailing bytes after entries')

  return entries
}

/**
 * Compute the tree hash of an entries list.
 *
 * @param {Array<{kind: number, exec: number, name: string, childHash: Buffer|string}>} entries
 * @returns {string} hex-encoded blake2b-256
 */
export function computeTreeHash (entries) {
  const buf = encodeTree(entries)
  return crypto.data(buf).toString('hex')
}

// ── Helpers ───────────────────────────────────────────────────

function toBuffer32 (hashOrBuf) {
  if (Buffer.isBuffer(hashOrBuf)) {
    if (hashOrBuf.length !== 32) throw new Error('childHash buffer must be 32 bytes')
    return hashOrBuf
  }
  if (typeof hashOrBuf !== 'string') {
    throw new Error('childHash must be Buffer or hex string')
  }
  if (hashOrBuf.length !== 64) throw new Error('childHash hex string must be 64 chars')
  return Buffer.from(hashOrBuf, 'hex')
}

// ── Filesystem walk ───────────────────────────────────────────

/**
 * Walk a directory recursively, producing tree hashes bottom-up.
 *
 * Returns a nested object keyed by path, where each entry has:
 *   { hash: hex, entries: [{kind, exec, name, childHash, size?}] }
 *
 * File entries carry their content hash as childHash (blake2b of bytes).
 * Dir entries carry the subtree hash.
 * Symlink entries carry blake2b of the link target bytes.
 *
 * @param {string} rootDir - absolute path to scan
 * @param {object} [opts]
 * @param {Set<string>} [opts.skip] - basenames to skip (default: DEFAULT_SKIP)
 * @param {(relPath: string, baseName: string) => boolean} [opts.matcher]
 *   Optional gitignore-style matcher. Returns true to EXCLUDE an entry.
 *   Receives the path relative to rootDir plus the basename. Applies to
 *   all kinds (file/dir/symlink). Used by `pgh publish` for publishIgnore.
 * @returns {Map<string, {hash: string, entries: Array, size: number}>}
 *          map keyed by relative path ('' = root); root tree is at key ''
 */
export function walkTree (rootDir, opts = {}) {
  const skip = opts.skip || DEFAULT_SKIP
  const matcher = opts.matcher || null
  const trees = new Map()

  function visit (relPath) {
    const absPath = relPath === '' ? rootDir : path.join(rootDir, relPath)
    const raw = fs.readdirSync(absPath)
    const entries = []
    let totalSize = 0

    for (const name of raw) {
      if (skip.has(name)) continue

      const entryAbs = path.join(absPath, name)
      const entryRel = relPath === '' ? name : relPath + '/' + name

      if (matcher && matcher(entryRel, name)) continue

      let stat
      try {
        stat = fs.lstatSync(entryAbs)
      } catch {
        continue
      }

      if (stat.isSymbolicLink()) {
        const target = fs.readlinkSync(entryAbs)
        const targetBytes = Buffer.from(target, 'utf8')
        const childHash = crypto.data(targetBytes).toString('hex')
        entries.push({
          kind: KIND_SYMLINK,
          exec: 0,
          name: normalizeName(name),
          childHash,
          size: targetBytes.length,
          linkTarget: target
        })
        totalSize += targetBytes.length
      } else if (stat.isDirectory()) {
        visit(entryRel)
        const sub = trees.get(entryRel)
        if (!sub) continue
        entries.push({
          kind: KIND_DIR,
          exec: 0,
          name: normalizeName(name),
          childHash: sub.hash,
          size: sub.size
        })
        totalSize += sub.size
      } else if (stat.isFile()) {
        const content = fs.readFileSync(entryAbs)
        const childHash = crypto.data(content).toString('hex')
        // Executable bit from stat.mode — works on POSIX; Windows always 0
        const exec = (stat.mode & 0o100) ? 1 : 0
        entries.push({
          kind: KIND_FILE,
          exec,
          name: normalizeName(name),
          childHash,
          size: stat.size
        })
        totalSize += stat.size
      }
      // device files, sockets, fifos: silently skipped (unrepresentable)
    }

    entries.sort(compareEntries)
    const hash = computeTreeHash(entries)
    trees.set(relPath, { hash, entries, size: totalSize })
  }

  visit('')
  return trees
}

// ── Publish-time validation (consumer policy) ─────────────────

/**
 * Scan entries (recursively via walkTree output) for cross-platform-hostile
 * names. Returns an array of {path, name, reason} for every problem found.
 * Does NOT throw — caller decides how to present.
 *
 * @param {Map<string, {hash: string, entries: Array}>} trees - from walkTree
 * @returns {Array<{path: string, name: string, reason: string}>}
 */
export function validateForPublish (trees) {
  const problems = []
  for (const [relPath, tree] of trees) {
    for (const e of tree.entries) {
      const reason = windowsIncompatibleReason(e.name)
      if (reason) {
        problems.push({
          path: relPath ? relPath + '/' + e.name : e.name,
          name: e.name,
          reason
        })
      }
    }
  }
  return problems
}
function isBuffer(value) {
  return Buffer.isBuffer(value) || value instanceof Uint8Array
}

function isEncoding(encoding) {
  return Buffer.isEncoding(encoding)
}

function alloc(size, fill, encoding) {
  return Buffer.alloc(size, fill, encoding)
}

function allocUnsafe(size) {
  return Buffer.allocUnsafe(size)
}

function allocUnsafeSlow(size) {
  return Buffer.allocUnsafeSlow(size)
}

function byteLength(string, encoding) {
  return Buffer.byteLength(string, encoding)
}

function compare(a, b) {
  return Buffer.compare(a, b)
}

function concat(buffers, totalLength) {
  return Buffer.concat(buffers, totalLength)
}

function copy(source, target, targetStart, start, end) {
  return toBuffer(source).copy(target, targetStart, start, end)
}

function equals(a, b) {
  return toBuffer(a).equals(b)
}

function fill(buffer, value, offset, end, encoding) {
  return toBuffer(buffer).fill(value, offset, end, encoding)
}

function from(value, encodingOrOffset, length) {
  return Buffer.from(value, encodingOrOffset, length)
}

function includes(buffer, value, byteOffset, encoding) {
  return toBuffer(buffer).includes(value, byteOffset, encoding)
}

function indexOf(buffer, value, byfeOffset, encoding) {
  return toBuffer(buffer).indexOf(value, byfeOffset, encoding)
}

function lastIndexOf(buffer, value, byteOffset, encoding) {
  return toBuffer(buffer).lastIndexOf(value, byteOffset, encoding)
}

function swap16(buffer) {
  return toBuffer(buffer).swap16()
}

function swap32(buffer) {
  return toBuffer(buffer).swap32()
}

function swap64(buffer) {
  return toBuffer(buffer).swap64()
}

function toBuffer(buffer) {
  if (Buffer.isBuffer(buffer)) return buffer
  return Buffer.from(buffer.buffer, buffer.byteOffset, buffer.byteLength)
}

function toString(buffer, encoding, start, end) {
  return toBuffer(buffer).toString(encoding, start, end)
}

function write(buffer, string, offset, length, encoding) {
  return toBuffer(buffer).write(string, offset, length, encoding)
}

function readDoubleBE(buffer, offset) {
  return toBuffer(buffer).readDoubleBE(offset)
}

function readDoubleLE(buffer, offset) {
  return toBuffer(buffer).readDoubleLE(offset)
}

function readFloatBE(buffer, offset) {
  return toBuffer(buffer).readFloatBE(offset)
}

function readFloatLE(buffer, offset) {
  return toBuffer(buffer).readFloatLE(offset)
}

function readInt32BE(buffer, offset) {
  return toBuffer(buffer).readInt32BE(offset)
}

function readInt32LE(buffer, offset) {
  return toBuffer(buffer).readInt32LE(offset)
}

function readUInt32BE(buffer, offset) {
  return toBuffer(buffer).readUInt32BE(offset)
}

function readUInt32LE(buffer, offset) {
  return toBuffer(buffer).readUInt32LE(offset)
}

function writeDoubleBE(buffer, value, offset) {
  return toBuffer(buffer).writeDoubleBE(value, offset)
}

function writeDoubleLE(buffer, value, offset) {
  return toBuffer(buffer).writeDoubleLE(value, offset)
}

function writeFloatBE(buffer, value, offset) {
  return toBuffer(buffer).writeFloatBE(value, offset)
}

function writeFloatLE(buffer, value, offset) {
  return toBuffer(buffer).writeFloatLE(value, offset)
}

function writeInt32BE(buffer, value, offset) {
  return toBuffer(buffer).writeInt32BE(value, offset)
}

function writeInt32LE(buffer, value, offset) {
  return toBuffer(buffer).writeInt32LE(value, offset)
}

function writeUInt32BE(buffer, value, offset) {
  return toBuffer(buffer).writeUInt32BE(value, offset)
}

function writeUInt32LE(buffer, value, offset) {
  return toBuffer(buffer).writeUInt32LE(value, offset)
}

module.exports = {
  isBuffer,
  isEncoding,
  alloc,
  allocUnsafe,
  allocUnsafeSlow,
  byteLength,
  compare,
  concat,
  copy,
  equals,
  fill,
  from,
  includes,
  indexOf,
  lastIndexOf,
  swap16,
  swap32,
  swap64,
  toBuffer,
  toString,
  write,
  readDoubleBE,
  readDoubleLE,
  readFloatBE,
  readFloatLE,
  readInt32BE,
  readInt32LE,
  readUInt32BE,
  readUInt32LE,
  writeDoubleBE,
  writeDoubleLE,
  writeFloatBE,
  writeFloatLE,
  writeInt32BE,
  writeInt32LE,
  writeUInt32BE,
  writeUInt32LE
}
{
  "name": "b4a",
  "version": "1.8.1",
  "description": "Bridging the gap between buffers and typed arrays",
  "exports": {
    "./package": "./package.json",
    ".": {
      "react-native": "./react-native.js",
      "browser": "./browser.js",
      "default": "./index.js"
    }
  },
  "files": [
    "browser.js",
    "index.js",
    "react-native.js",
    "lib"
  ],
  "scripts": {
    "format": "prettier --write . && lunte --fix",
    "lint": "prettier --check . && lunte",
    "test": "npm run test:bare && npm run test:node",
    "test:bare": "brittle-bare --coverage test.mjs",
    "test:node": "brittle-node --coverage test.mjs"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/b4a.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/b4a/issues"
  },
  "homepage": "https://github.com/holepunchto/b4a#readme",
  "devDependencies": {
    "brittle": "^3.5.2",
    "lunte": "^1.7.0",
    "nanobench": "^3.0.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^2.0.0"
  },
  "peerDependencies": {
    "react-native-b4a": "*"
  },
  "peerDependenciesMeta": {
    "react-native-b4a": {
      "optional": true
    }
  }
}
const ESC = '\x1b'
const CSI = ESC + '['
const SGR = (n) => CSI + n + 'm'

exports.constants = {
  ESC,
  CSI,
  SGR
}

// https://en.wikipedia.org/wiki/ANSI_escape_code#CSI_(Control_Sequence_Introducer)_sequences

exports.cursorHide = CSI + '?25l'
exports.cursorShow = CSI + '?25h'

exports.cursorUp = function cursorUp(n = 1) {
  return CSI + n + 'A'
}

exports.cursorDown = function cursorDown(n = 1) {
  return CSI + n + 'B'
}

exports.cursorForward = function cursorForward(n = 1) {
  return CSI + n + 'C'
}

exports.cursorBack = function cursorBack(n = 1) {
  return CSI + n + 'D'
}

exports.cursorNextLine = function cursorNextLine(n = 1) {
  return CSI + n + 'E'
}

exports.cursorPreviousLine = function cursorPreviousLine(n = 1) {
  return CSI + n + 'F'
}

exports.cursorPosition = function cursorPosition(column, row = 0) {
  if (row === 0) return CSI + (column + 1) + 'G'

  return CSI + (row + 1) + ';' + (column + 1) + 'H'
}

exports.eraseDisplayEnd = CSI + 'J'
exports.eraseDisplayStart = CSI + '1J'
exports.eraseDisplay = CSI + '2J'
exports.eraseLineEnd = CSI + 'K'
exports.eraseLineStart = CSI + '1K'
exports.eraseLine = CSI + '2K'

exports.scrollUp = function scrollUp(n = 1) {
  return CSI + n + 'S'
}

exports.scrollDown = function scrollDown(n = 1) {
  return CSI + n + 'T'
}

// https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_(Select_Graphic_Rendition)_parameters

exports.modifierReset = SGR(0)
exports.modifierBold = SGR(1)
exports.modifierDim = SGR(2)
exports.modifierItalic = SGR(3)
exports.modifierUnderline = SGR(4)
exports.modifierNormal = SGR(22)
exports.modifierNotItalic = SGR(23)
exports.modifierNotUnderline = SGR(24)

// https://en.wikipedia.org/wiki/ANSI_escape_code#Colors

exports.colorBlack = SGR(30)
exports.colorRed = SGR(31)
exports.colorGreen = SGR(32)
exports.colorYellow = SGR(33)
exports.colorBlue = SGR(34)
exports.colorMagenta = SGR(35)
exports.colorCyan = SGR(36)
exports.colorWhite = SGR(37)
exports.colorDefault = SGR(39)
exports.colorBrightBlack = SGR(90)
exports.colorBrightRed = SGR(91)
exports.colorBrightGreen = SGR(92)
exports.colorBrightYellow = SGR(93)
exports.colorBrightBlue = SGR(94)
exports.colorBrightMagenta = SGR(95)
exports.colorBrightCyan = SGR(96)
exports.colorBrightWhite = SGR(97)
{
  "name": "bare-ansi-escapes",
  "version": "2.2.3",
  "description": "Parse and produce ANSI escape sequences",
  "exports": {
    "./package": "./package.json",
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    },
    "./key-decoder": {
      "types": "./key-decoder.d.ts",
      "default": "./key-decoder.js"
    }
  },
  "files": [
    "index.js",
    "index.d.ts",
    "key-decoder.js",
    "key-decoder.d.ts"
  ],
  "scripts": {
    "test": "prettier . --check && bare test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-ansi-escapes.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-ansi-escapes/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-ansi-escapes#readme",
  "dependencies": {
    "bare-stream": "^2.6.5"
  },
  "devDependencies": {
    "bare-buffer": "^3.0.2",
    "brittle": "^3.3.2",
    "prettier": "^3.4.2",
    "prettier-config-standard": "^7.0.0"
  },
  "peerDependencies": {
    "bare-buffer": "*"
  },
  "peerDependenciesMeta": {
    "bare-buffer": {
      "optional": true
    }
  }
}
const inspect = require('bare-inspect')

class AssertionError extends Error {
  constructor(opts = {}) {
    let { message = null, actual, expected, operator } = opts

    if (message === null) {
      message = `${inspect(actual)} ${operator} ${inspect(expected)}`
    }

    super(message)

    this.actual = actual
    this.expected = expected
    this.operator = operator
  }

  get name() {
    return 'AssertionError'
  }

  get code() {
    'ASSERTION'
  }
}

function assertFail(opts, fn) {
  if (opts.message instanceof Error) throw opts.message

  const err = new AssertionError(opts)

  if (Error.captureStackTrace) Error.captureStackTrace(err, fn)

  throw err
}

module.exports = exports = function assert(actual, message) {
  if (actual) return

  assertFail({ message, actual, expected: true, operator: '==' }, assert)
}

exports.AssertionError = AssertionError

exports.fail = function fail(message) {
  if (message === undefined) message = 'Failed'

  assertFail({ message, operator: 'fail' }, fail)
}

exports.ok = function ok(actual, message) {
  if (actual) return

  assertFail({ message, actual, expected: true, operator: '==' }, ok)
}

exports.notOk = function ok(actual, message) {
  if (!actual) return

  assertFail({ message, actual, expected: false, operator: '==' }, ok)
}

exports.equal = function equal(actual, expected, message) {
  if (actual == expected || (actual !== actual && expected !== expected)) {
    return
  }

  assertFail({ message, actual, expected, operator: '==' }, equal)
}

exports.notEqual = function notEqual(actual, expected, message) {
  if (actual != expected && (actual === actual || expected === expected)) {
    return
  }

  assertFail({ message, actual, expected, operator: '!=' }, notEqual)
}

exports.strictEqual = function strictEqual(actual, expected, message) {
  if (Object.is(actual, expected)) return

  assertFail(
    { message, actual, expected, operator: 'strictEqual' },
    strictEqual
  )
}

exports.notStrictEqual = function notStrictEqual(actual, expected, message) {
  if (!Object.is(actual, expected)) return

  assertFail(
    { message, actual, expected, operator: 'notStrictEqual' },
    notStrictEqual
  )
}
{
  "name": "bare-assert",
  "version": "1.2.0",
  "description": "Assertion library for JavaScript",
  "exports": {
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    },
    "./package": "./package.json"
  },
  "files": [
    "index.js",
    "index.d.ts"
  ],
  "scripts": {
    "test": "prettier . --check && bare test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-assert.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-assert/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-assert#readme",
  "dependencies": {
    "bare-inspect": "^3.1.2"
  },
  "devDependencies": {
    "brittle": "^3.1.1",
    "prettier": "^3.4.2",
    "prettier-config-standard": "^7.0.0"
  }
}
const errors = require('./lib/errors')

class EventListener {
  constructor() {
    this.list = []
    this.count = 0
  }

  append(ctx, name, fn, once) {
    this.count++
    ctx.emit('newListener', name, fn) // Emit BEFORE adding
    this.list.push([fn, once])
  }

  prepend(ctx, name, fn, once) {
    this.count++
    ctx.emit('newListener', name, fn) // Emit BEFORE adding
    this.list.unshift([fn, once])
  }

  remove(ctx, name, fn) {
    for (let i = 0, n = this.list.length; i < n; i++) {
      const l = this.list[i]

      if (l[0] === fn) {
        this.list.splice(i, 1)

        if (this.count === 1) delete ctx._events[name]

        ctx.emit('removeListener', name, fn) // Emit AFTER removing

        this.count--
        return
      }
    }
  }

  removeAll(ctx, name) {
    const list = [...this.list]
    this.list = []

    if (this.count === list.length) delete ctx._events[name]

    for (let i = list.length - 1; i >= 0; i--) {
      ctx.emit('removeListener', name, list[i][0]) // Emit AFTER removing
    }

    this.count -= list.length
  }

  emit(ctx, name, ...args) {
    const list = [...this.list]

    for (let i = 0, n = list.length; i < n; i++) {
      const l = list[i]

      if (l[1] === true) this.remove(ctx, name, l[0])

      Reflect.apply(l[0], ctx, args)
    }

    return list.length > 0
  }
}

function appendListener(ctx, name, fn, once) {
  if (ctx._events === undefined) ctx._events = Object.create(null)
  const e = ctx._events[name] || (ctx._events[name] = new EventListener())
  e.append(ctx, name, fn, once)
  return ctx
}

function prependListener(ctx, name, fn, once) {
  if (ctx._events === undefined) ctx._events = Object.create(null)
  const e = ctx._events[name] || (ctx._events[name] = new EventListener())
  e.prepend(ctx, name, fn, once)
  return ctx
}

function removeListener(ctx, name, fn) {
  if (ctx._events === undefined) return ctx
  const e = ctx._events[name]
  if (e !== undefined) e.remove(ctx, name, fn)
  return ctx
}

function throwUnhandledError(...args) {
  let err

  if (args.length > 0) err = args[0]

  if (err instanceof Error === false) err = errors.UNHANDLED_ERROR(err)

  if (Error.captureStackTrace) {
    Error.captureStackTrace(err, exports.prototype.emit)
  }

  queueMicrotask(() => {
    throw err
  })
}

module.exports = exports = class EventEmitter {
  constructor() {
    this._events = Object.create(null)
  }

  addListener(name, fn) {
    return appendListener(this, name, fn, false)
  }

  addOnceListener(name, fn) {
    return appendListener(this, name, fn, true)
  }

  prependListener(name, fn) {
    return prependListener(this, name, fn, false)
  }

  prependOnceListener(name, fn) {
    return prependListener(this, name, fn, true)
  }

  removeListener(name, fn) {
    return removeListener(this, name, fn)
  }

  on(name, fn) {
    return appendListener(this, name, fn, false)
  }

  once(name, fn) {
    return appendListener(this, name, fn, true)
  }

  off(name, fn) {
    return removeListener(this, name, fn)
  }

  emit(name, ...args) {
    if (name === 'error' && this._events !== undefined && this._events.error === undefined) {
      throwUnhandledError(...args)
    }

    if (this._events === undefined) return false
    const e = this._events[name]
    return e === undefined ? false : e.emit(this, name, ...args)
  }

  listeners(name) {
    if (this._events === undefined) return []
    const e = this._events[name]
    return e === undefined ? [] : [...e.list]
  }

  rawListeners(name) {
    if (this._events === undefined) return []
    const e = this._events[name]
    return e === undefined ? [] : e.list.map((l) => l[0])
  }

  eventNames() {
    if (this._events === undefined) return []
    return Reflect.ownKeys(this._events)
  }

  listenerCount(name) {
    if (this._events === undefined) return 0
    const e = this._events[name]
    return e === undefined ? 0 : e.list.length
  }

  getMaxListeners() {
    return EventEmitter.defaultMaxListeners
  }

  setMaxListeners(n) {}

  removeAllListeners(name) {
    if (arguments.length === 0) {
      for (const key of Reflect.ownKeys(this._events)) {
        if (key === 'removeListener') continue
        this.removeAllListeners(key)
      }
      this.removeAllListeners('removeListener')
    } else {
      const e = this._events[name]
      if (e !== undefined) e.removeAll(this, name)
    }
    return this
  }
}

exports.EventEmitter = exports

exports.errors = errors

exports.defaultMaxListeners = 10

exports.on = function on(emitter, name, opts = {}) {
  const { signal } = opts

  if (signal && signal.aborted) {
    throw errors.OPERATION_ABORTED(signal.reason)
  }

  let error = null
  let done = false

  const events = []
  const promises = []

  if (name !== 'error') emitter.on('error', onerror)

  if (signal) signal.addEventListener('abort', onabort)

  emitter.on(name, onevent)

  return {
    next() {
      if (events.length) {
        return Promise.resolve({ value: events.shift(), done: false })
      }

      if (error) {
        const err = error

        error = null

        return Promise.reject(err)
      }

      if (done) return onclose()

      return new Promise((resolve, reject) => promises.push({ resolve, reject }))
    },

    return() {
      return onclose()
    },

    throw(err) {
      return onerror(err)
    },

    [Symbol.asyncIterator]() {
      return this
    }
  }

  function onevent(...args) {
    if (promises.length) {
      promises.shift().resolve({ value: args, done: false })
    } else {
      events.push(args)
    }
  }

  function onerror(err) {
    emitter.off(name, onevent).off('error', onerror)

    if (promises.length) {
      promises.shift().reject(err)
    } else {
      error = err
    }

    return Promise.resolve({ done: true })
  }

  function onabort() {
    signal.removeEventListener('abort', onabort)

    onerror(errors.OPERATION_ABORTED(signal.reason))
  }

  function onclose() {
    emitter.off(name, onevent)

    if (name !== 'error') emitter.off('error', onerror)

    if (signal) signal.removeEventListener('abort', onabort)

    done = true

    if (promises.length) promises.shift().resolve({ done: true })

    return Promise.resolve({ done: true })
  }
}

exports.once = function once(emitter, name, opts = {}) {
  const { signal } = opts

  if (signal && signal.aborted) {
    return Promise.reject(errors.OPERATION_ABORTED(signal.reason))
  }

  return new Promise((resolve, reject) => {
    if (name !== 'error') emitter.on('error', onerror)

    if (signal) signal.addEventListener('abort', onabort)

    emitter.once(name, onevent)

    function onevent(...args) {
      if (name !== 'error') emitter.off('error', onerror)

      if (signal) signal.removeEventListener('abort', onabort)

      resolve(args)
    }

    function onerror(err) {
      emitter.off(name, onevent)

      if (name !== 'error') emitter.off('error', onerror)

      reject(err)
    }

    function onabort() {
      signal.removeEventListener('abort', onabort)

      onerror(errors.OPERATION_ABORTED(signal.reason))
    }
  })
}

exports.forward = function forward(from, to, names, opts = {}) {
  if (typeof names === 'string') names = [names]

  const { emit = to.emit.bind(to) } = opts

  const listeners = names.map(
    (name) =>
      function onevent(...args) {
        emit(name, ...args)
      }
  )

  to.on('newListener', (name) => {
    const i = names.indexOf(name)

    if (i !== -1 && to.listenerCount(name) === 0) {
      from.on(name, listeners[i])
    }
  }).on('removeListener', (name) => {
    const i = names.indexOf(name)

    if (i !== -1 && to.listenerCount(name) === 0) {
      from.off(name, listeners[i])
    }
  })
}

exports.listenerCount = function listenerCount(emitter, name) {
  return emitter.listenerCount(name)
}

exports.getMaxListeners = function getMaxListeners(emitter) {
  if (typeof emitter.getMaxListeners === 'function') {
    return emitter.getMaxListeners()
  }

  return exports.defaultMaxListeners
}

exports.setMaxListeners = function setMaxListeners(n, ...emitters) {
  if (emitters.length === 0) exports.defaultMaxListeners = n
  else {
    for (const emitter of emitters) {
      if (typeof emitter.setMaxListeners === 'function') {
        emitter.setMaxListeners(n)
      }
    }
  }
}
module.exports = class EventEmitterError extends Error {
  constructor(msg, code, fn = EventEmitterError, opts) {
    super(`${code}: ${msg}`, opts)
    this.code = code

    if (Error.captureStackTrace) {
      Error.captureStackTrace(this, fn)
    }
  }

  get name() {
    return 'EventEmitterError'
  }

  static OPERATION_ABORTED(cause, msg = 'Operation aborted') {
    return new EventEmitterError(msg, 'OPERATION_ABORTED', EventEmitterError.OPERATION_ABORTED, {
      cause
    })
  }

  static UNHANDLED_ERROR(cause, msg = 'Unhandled error') {
    return new EventEmitterError(msg, 'UNHANDLED_ERROR', EventEmitterError.UNHANDLED_ERROR, {
      cause
    })
  }
}
{
  "name": "bare-events",
  "version": "2.9.1",
  "description": "Event emitters for JavaScript",
  "exports": {
    "./package": "./package.json",
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    },
    "./global": {
      "types": "./global.d.ts",
      "default": "./global.js"
    },
    "./web": {
      "types": "./web.d.ts",
      "default": "./web.js"
    },
    "./errors": "./lib/errors.js"
  },
  "files": [
    "index.js",
    "index.d.ts",
    "global.js",
    "global.d.ts",
    "web.js",
    "web.d.ts",
    "lib"
  ],
  "scripts": {
    "format": "prettier --write . && lunte --fix",
    "lint": "prettier --check . && lunte",
    "test": "npm run test:bare && npm run test:node",
    "test:bare": "brittle-bare --coverage test.js",
    "test:node": "brittle-node --coverage test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-events.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-events/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-events#readme",
  "devDependencies": {
    "bare-abort-controller": "^1.0.0",
    "brittle": "^3.3.2",
    "lunte": "^1.8.0",
    "prettier": "^3.4.2",
    "prettier-config-holepunch": "^2.0.0",
    "uncaughts": "^1.1.1"
  },
  "peerDependencies": {
    "bare-abort-controller": "*"
  },
  "peerDependenciesMeta": {
    "bare-abort-controller": {
      "optional": true
    }
  }
}
module.exports = require.addon()
const FIFO = require('fast-fifo')
const EventEmitter = require('bare-events')
const path = require('bare-path')
const { isURL, fileURLToPath } = require('bare-url')
const { Readable, Writable } = require('bare-stream')
const binding = require('./binding')
const constants = require('./lib/constants')
const FileError = require('./lib/errors')

const isWindows = Bare.platform === 'win32'

exports.constants = constants

class FileRequest {
  static borrow() {
    if (this._free.length > 0) return this._free.pop()
    return new FileRequest()
  }

  static return(req) {
    if (this._free.length < 32) this._free.push(req.reset())
    else req.destroy()
  }

  constructor() {
    this._reset()
    this._handle = binding.requestInit(this, this._onresult)
  }

  get handle() {
    return this._handle
  }

  retain(value) {
    this._retain = value // Tie the lifetime of `value` to the lifetime of `this`
  }

  reset() {
    if (this._handle === null) return this

    binding.requestReset(this._handle)

    this._reset()

    return this
  }

  destroy() {
    if (this._handle === null) return this

    binding.requestDestroy(this._handle)

    this._reset()
    this._handle = null

    return this
  }

  then(resolve, reject) {
    return this._promise.then(resolve, reject)
  }

  return() {
    if (this._handle === null) return this

    FileRequest.return(this)

    return this
  }

  _reset() {
    this._promise = new Promise((resolve, reject) => {
      this._resolve = resolve
      this._reject = reject
    })
    this._retain = null
  }

  _onresult(err, status) {
    if (err) this._reject(err)
    else this._resolve(status)
  }
}

FileRequest._free = []

function ok(result, cb) {
  if (typeof result === 'function') {
    cb = result
    result = undefined
  }

  if (cb) cb(null, result)
  else return result
}

function fail(err, cb) {
  if (cb) cb(err)
  else throw err
}

function done(err, result, cb) {
  if (typeof result === 'function') {
    cb = result
    result = undefined
  }

  if (err) fail(err, cb)
  else return ok(result, cb)
}

async function open(filepath, flags = 'r', mode = 0o666, cb) {
  if (typeof flags === 'function') {
    cb = flags
    flags = 'r'
    mode = 0o666
  } else if (typeof mode === 'function') {
    cb = mode
    mode = 0o666
  }

  if (typeof flags === 'string') flags = toFlags(flags)
  if (typeof mode === 'string') mode = toMode(mode)

  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  let fd
  let err = null
  try {
    binding.open(req.handle, filepath, flags, mode)

    fd = await req
  } catch (e) {
    err = new FileError(e.message, {
      operation: 'open',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }

  return done(err, fd, cb)
}

function openSync(filepath, flags = 'r', mode = 0o666) {
  if (typeof flags === 'string') flags = toFlags(flags)
  if (typeof mode === 'string') mode = toMode(mode)

  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  try {
    return binding.openSync(req.handle, filepath, flags, mode)
  } catch (e) {
    throw new FileError(e.message, {
      operation: 'open',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }
}

async function close(fd, cb) {
  const req = FileRequest.borrow()

  let err = null
  try {
    binding.close(req.handle, fd)

    await req
  } catch (e) {
    err = new FileError(e.message, { operation: 'close', code: e.code, fd })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function closeSync(fd) {
  const req = FileRequest.borrow()

  try {
    binding.closeSync(req.handle, fd)
  } catch (e) {
    throw new FileError(e.message, { operation: 'close', code: e.code, fd })
  } finally {
    req.return()
  }
}

async function access(filepath, mode = constants.F_OK, cb) {
  if (typeof mode === 'function') {
    cb = mode
    mode = constants.F_OK
  }

  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  let err = null
  try {
    binding.access(req.handle, filepath, mode)

    await req
  } catch (e) {
    err = new FileError(e.message, {
      operation: 'access',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function accessSync(filepath, mode = constants.F_OK) {
  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  try {
    binding.accessSync(req.handle, filepath, mode)
  } catch (e) {
    throw new FileError(e.message, {
      operation: 'access',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }
}

async function exists(filepath, cb) {
  let ok = true
  try {
    await access(filepath)
  } catch {
    ok = false
  }

  return done(null, ok, cb)
}

function existsSync(filepath) {
  try {
    accessSync(filepath)
  } catch {
    return false
  }

  return true
}

async function read(fd, buffer, offset = 0, len = buffer.byteLength - offset, pos = -1, cb) {
  if (typeof offset === 'function') {
    cb = offset
    offset = 0
    len = buffer.byteLength
    pos = -1
  } else if (typeof len === 'function') {
    cb = len
    len = buffer.byteLength - offset
    pos = -1
  } else if (typeof pos === 'function') {
    cb = pos
    pos = -1
  }

  if (typeof pos !== 'number') pos = -1

  const req = FileRequest.borrow()

  let bytes
  let err = null
  try {
    binding.read(req.handle, fd, buffer, offset, len, pos)

    req.retain(buffer)

    bytes = await req
  } catch (e) {
    err = new FileError(e.message, { operation: 'read', code: e.code, fd })
  } finally {
    req.return()
  }

  return done(err, bytes, cb)
}

function readSync(fd, buffer, offset = 0, len = buffer.byteLength - offset, pos = -1) {
  const req = FileRequest.borrow()

  try {
    return binding.readSync(req.handle, fd, buffer, offset, len, pos)
  } catch (e) {
    throw new FileError(e.message, { operation: 'read', code: e.code, fd })
  } finally {
    req.return()
  }
}

async function readv(fd, buffers, pos = -1, cb) {
  if (typeof pos === 'function') {
    cb = pos
    pos = -1
  }

  if (typeof pos !== 'number') pos = -1

  const req = FileRequest.borrow()

  let bytes
  let err = null
  try {
    binding.readv(req.handle, fd, buffers, pos)

    req.retain(buffers)

    bytes = await req
  } catch (e) {
    err = new FileError(e.message, { operation: 'readv', code: e.code, fd })
  } finally {
    req.return()
  }

  return done(err, bytes, cb)
}

function readvSync(fd, buffers, pos = -1) {
  if (typeof pos !== 'number') pos = -1

  const req = FileRequest.borrow()

  try {
    return binding.readvSync(req.handle, fd, buffers, pos)
  } catch (e) {
    throw new FileError(e.message, { operation: 'readv', code: e.code, fd })
  } finally {
    req.return()
  }
}

async function write(fd, data, offset, len, pos = -1, cb) {
  if (typeof data === 'string') {
    let encoding = len
    cb = pos
    pos = offset

    if (typeof pos === 'function') {
      cb = pos
      pos = -1
      encoding = 'utf8'
    } else if (typeof encoding === 'function') {
      cb = encoding
      encoding = 'utf8'
    }

    if (typeof pos === 'string') {
      encoding = pos
      pos = -1
    }

    data = Buffer.from(data, encoding)
    offset = 0
    len = data.byteLength
  } else if (typeof offset === 'function') {
    cb = offset
    offset = 0
    len = data.byteLength
    pos = -1
  } else if (typeof len === 'function') {
    cb = len
    len = data.byteLength - offset
    pos = -1
  } else if (typeof pos === 'function') {
    cb = pos
    pos = -1
  }

  if (typeof offset !== 'number') offset = 0
  if (typeof len !== 'number') len = data.byteLength - offset
  if (typeof pos !== 'number') pos = -1

  const req = FileRequest.borrow()

  let bytes
  let err = null
  try {
    binding.write(req.handle, fd, data, offset, len, pos)

    req.retain(data)

    bytes = await req
  } catch (e) {
    err = new FileError(e.message, { operation: 'write', code: e.code, fd })
  } finally {
    req.return()
  }

  return done(err, bytes, cb)
}

function writeSync(fd, data, offset, len, pos = -1) {
  if (typeof data === 'string') {
    let encoding = len
    pos = offset

    if (typeof pos === 'string') {
      encoding = pos
      pos = -1
    }

    data = Buffer.from(data, encoding)
    offset = 0
    len = data.byteLength
  }

  if (typeof offset !== 'number') offset = 0
  if (typeof len !== 'number') len = data.byteLength - offset
  if (typeof pos !== 'number') pos = -1

  const req = FileRequest.borrow()

  try {
    return binding.writeSync(req.handle, fd, data, offset, len, pos)
  } catch (e) {
    throw new FileError(e.message, { operation: 'write', code: e.code, fd })
  } finally {
    req.return()
  }
}

async function writev(fd, buffers, pos = -1, cb) {
  if (typeof pos === 'function') {
    cb = pos
    pos = -1
  }

  if (typeof pos !== 'number') pos = -1

  const req = FileRequest.borrow()

  let bytes
  let err = null
  try {
    binding.writev(req.handle, fd, buffers, pos)

    req.retain(buffers)

    bytes = await req
  } catch (e) {
    err = new FileError(e.message, { operation: 'writev', code: e.code, fd })
  } finally {
    req.return()
  }

  return done(err, bytes, cb)
}

function writevSync(fd, buffers, pos = -1) {
  if (typeof pos !== 'number') pos = -1

  const req = FileRequest.borrow()

  try {
    return binding.writevSync(req.handle, fd, buffers, pos)
  } catch (e) {
    throw new FileError(e.message, { operation: 'writev', code: e.code, fd })
  } finally {
    req.return()
  }
}

async function stat(filepath, cb) {
  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  let st
  let err = null
  try {
    binding.stat(req.handle, filepath)

    await req

    st = new Stats(...binding.requestResultStat(req.handle))
  } catch (e) {
    err = new FileError(e.message, { operation: 'stat', code: e.code, path: filepath })
  } finally {
    req.return()
  }

  return done(err, st, cb)
}

function statSync(filepath) {
  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  try {
    binding.statSync(req.handle, filepath)

    return new Stats(...binding.requestResultStat(req.handle))
  } catch (e) {
    throw new FileError(e.message, { operation: 'stat', code: e.code, path: filepath })
  } finally {
    req.return()
  }
}

async function lstat(filepath, cb) {
  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  let st
  let err = null
  try {
    binding.lstat(req.handle, filepath)

    await req

    st = new Stats(...binding.requestResultStat(req.handle))
  } catch (e) {
    err = new FileError(e.message, { operation: 'lstat', code: e.code, path: filepath })
  } finally {
    req.return()
  }

  return done(err, st, cb)
}

function lstatSync(filepath) {
  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  try {
    binding.lstatSync(req.handle, filepath)

    return new Stats(...binding.requestResultStat(req.handle))
  } catch (e) {
    throw new FileError(e.message, { operation: 'lstat', code: e.code, path: filepath })
  } finally {
    req.return()
  }
}

async function fstat(fd, cb) {
  const req = FileRequest.borrow()

  let st
  let err = null
  try {
    binding.fstat(req.handle, fd)

    await req

    st = new Stats(...binding.requestResultStat(req.handle))
  } catch (e) {
    err = new FileError(e.message, { operation: 'fstat', code: e.code, fd })
  } finally {
    req.return()
  }

  return done(err, st, cb)
}

function fstatSync(fd) {
  const req = FileRequest.borrow()

  try {
    binding.fstatSync(req.handle, fd)

    return new Stats(...binding.requestResultStat(req.handle))
  } catch (e) {
    throw new FileError(e.message, { operation: 'fstat', code: e.code, fd })
  } finally {
    req.return()
  }
}

async function statfs(filepath, cb) {
  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  let st
  let err = null
  try {
    binding.statfs(req.handle, filepath)

    await req

    st = new StatFs(...binding.requestResultStatfs(req.handle))
  } catch (e) {
    err = new FileError(e.message, { operation: 'statfs', code: e.code, path: filepath })
  } finally {
    req.return()
  }

  return done(err, st, cb)
}

function statfsSync(filepath) {
  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  try {
    binding.statfsSync(req.handle, filepath)

    return new StatFs(...binding.requestResultStatfs(req.handle))
  } catch (e) {
    throw new FileError(e.message, { operation: 'statfs', code: e.code, path: filepath })
  } finally {
    req.return()
  }
}

async function ftruncate(fd, len = 0, cb) {
  if (typeof len === 'function') {
    cb = len
    len = 0
  }

  if (typeof len !== 'number') len = 0

  const req = FileRequest.borrow()

  let err = null
  try {
    binding.ftruncate(req.handle, fd, len)

    await req
  } catch (e) {
    err = new FileError(e.message, { operation: 'ftruncate', code: e.code, fd })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function ftruncateSync(fd, len = 0) {
  if (typeof len !== 'number') len = 0

  const req = FileRequest.borrow()

  try {
    binding.ftruncateSync(req.handle, fd, len)
  } catch (e) {
    throw new FileError(e.message, { operation: 'ftruncate', code: e.code, fd })
  } finally {
    req.return()
  }
}

async function truncate(filepath, len, cb) {
  let fd = -1
  let err

  try {
    fd = await open(filepath, 'r+')

    await ftruncate(fd, len)
  } catch (e) {
    err = e
  } finally {
    if (fd !== -1) await close(fd)
  }

  return done(err, cb)
}

function truncateSync(filepath, len) {
  let fd = -1
  let err

  try {
    fd = openSync(filepath, 'r+')

    ftruncateSync(fd, len)
  } catch (e) {
    err = e
  } finally {
    if (fd !== -1) closeSync(fd)
  }
}

async function chmod(filepath, mode, cb) {
  if (typeof mode === 'string') mode = toMode(mode)

  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  let err = null
  try {
    binding.chmod(req.handle, filepath, mode)

    await req
  } catch (e) {
    err = new FileError(e.message, {
      operation: 'chmod',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function chmodSync(filepath, mode) {
  if (typeof mode === 'string') mode = toMode(mode)

  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  try {
    binding.chmodSync(req.handle, filepath, mode)
  } catch (e) {
    throw new FileError(e.message, {
      operation: 'chmod',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }
}

async function fchmod(fd, mode, cb) {
  if (typeof mode === 'string') mode = toMode(mode)

  const req = FileRequest.borrow()

  let err = null
  try {
    binding.fchmod(req.handle, fd, mode)

    await req
  } catch (e) {
    err = new FileError(e.message, { operation: 'fchmod', code: e.code, fd })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function fchmodSync(fd, mode) {
  if (typeof mode === 'string') mode = toMode(mode)

  const req = FileRequest.borrow()

  try {
    binding.fchmodSync(req.handle, fd, mode)
  } catch (e) {
    throw new FileError(e.message, { operation: 'fchmod', code: e.code, fd })
  } finally {
    req.return()
  }
}

async function chown(filepath, uid, gid, cb) {
  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  let err = null
  try {
    binding.chown(req.handle, filepath, uid, gid)

    await req
  } catch (e) {
    err = new FileError(e.message, { operation: 'chown', code: e.code, path: filepath })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function chownSync(filepath, uid, gid) {
  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  try {
    binding.chownSync(req.handle, filepath, uid, gid)
  } catch (e) {
    throw new FileError(e.message, { operation: 'chownSync', code: e.code, path: filepath })
  } finally {
    req.return()
  }
}

async function lchown(filepath, uid, gid, cb) {
  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  let err = null
  try {
    binding.lchown(req.handle, filepath, uid, gid)

    await req
  } catch (e) {
    err = new FileError(e.message, { operation: 'lchown', code: e.code, path: filepath })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function lchownSync(filepath, uid, gid) {
  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  try {
    binding.lchownSync(req.handle, filepath, uid, gid)
  } catch (e) {
    throw new FileError(e.message, { operation: 'lchownSync', code: e.code, path: filepath })
  } finally {
    req.return()
  }
}

async function fchown(fd, uid, gid, cb) {
  const req = FileRequest.borrow()

  let err = null
  try {
    binding.fchown(req.handle, fd, uid, gid)

    await req
  } catch (e) {
    err = new FileError(e.message, { operation: 'fchown', code: e.code, fd })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function fchownSync(fd, uid, gid) {
  const req = FileRequest.borrow()

  try {
    binding.fchownSync(req.handle, fd, uid, gid)
  } catch (e) {
    throw new FileError(e.message, { operation: 'fchownSync', code: e.code, fd })
  } finally {
    req.return()
  }
}

async function utimes(filepath, atime, mtime, cb) {
  if (typeof atime !== 'number') atime = atime.getTime() / 1000
  if (typeof mtime !== 'number') mtime = mtime.getTime() / 1000

  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  let err = null
  try {
    binding.utimes(req.handle, filepath, atime, mtime)

    await req
  } catch (e) {
    err = new FileError(e.message, { operation: 'utimes', code: e.code, path: filepath })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function utimesSync(filepath, atime, mtime) {
  if (typeof atime !== 'number') atime = atime.getTime() / 1000
  if (typeof mtime !== 'number') mtime = mtime.getTime() / 1000

  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  try {
    binding.utimesSync(req.handle, filepath, atime, mtime)
  } catch (e) {
    throw new FileError(e.message, { operation: 'utimes', code: e.code, path: filepath })
  } finally {
    req.return()
  }
}

async function lutimes(filepath, atime, mtime, cb) {
  if (typeof atime !== 'number') atime = atime.getTime() / 1000
  if (typeof mtime !== 'number') mtime = mtime.getTime() / 1000

  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  let err = null
  try {
    binding.lutimes(req.handle, filepath, atime, mtime)

    await req
  } catch (e) {
    err = new FileError(e.message, { operation: 'lutimes', code: e.code, path: filepath })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function lutimesSync(filepath, atime, mtime) {
  if (typeof atime !== 'number') atime = atime.getTime() / 1000
  if (typeof mtime !== 'number') mtime = mtime.getTime() / 1000

  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  try {
    binding.lutimesSync(req.handle, filepath, atime, mtime)
  } catch (e) {
    throw new FileError(e.message, { operation: 'lutimes', code: e.code, path: filepath })
  } finally {
    req.return()
  }
}

async function futimes(fd, atime, mtime, cb) {
  if (typeof atime !== 'number') atime = atime.getTime() / 1000
  if (typeof mtime !== 'number') mtime = mtime.getTime() / 1000

  const req = FileRequest.borrow()

  let err = null
  try {
    binding.futimes(req.handle, fd, atime, mtime)

    await req
  } catch (e) {
    err = new FileError(e.message, { operation: 'futimes', code: e.code, fd })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function futimesSync(fd, atime, mtime) {
  if (typeof atime !== 'number') atime = atime.getTime() / 1000
  if (typeof mtime !== 'number') mtime = mtime.getTime() / 1000

  const req = FileRequest.borrow()

  try {
    binding.futimesSync(req.handle, fd, atime, mtime)
  } catch (e) {
    throw new FileError(e.message, { operation: 'futimesSync', code: e.code, fd })
  } finally {
    req.return()
  }
}

async function link(src, dst, cb) {
  src = toNamespacedPath(src)
  dst = toNamespacedPath(dst)

  const req = FileRequest.borrow()

  let err = null
  try {
    binding.link(req.handle, src, dst)

    await req
  } catch (e) {
    err = new FileError(e.message, { operation: 'link', code: e.code, path: src, destination: dst })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function linkSync(src, dst) {
  src = toNamespacedPath(src)
  dst = toNamespacedPath(dst)

  const req = FileRequest.borrow()

  try {
    binding.linkSync(req.handle, src, dst)
  } catch (e) {
    throw new FileError(e.message, {
      operation: 'linkSync',
      code: e.code,
      path: src,
      destination: dst
    })
  } finally {
    req.return()
  }
}

async function mkdir(filepath, opts, cb) {
  if (typeof opts === 'function') {
    cb = opts
    opts = { mode: 0o777 }
  }

  if (typeof opts === 'number') opts = { mode: opts }
  else if (!opts) opts = {}

  const mode = typeof opts.mode === 'number' ? opts.mode : 0o777

  filepath = toNamespacedPath(filepath)

  if (opts.recursive) {
    let err = null
    try {
      try {
        await mkdir(filepath, { mode })
      } catch (err) {
        if (err.code !== 'ENOENT') {
          if (!(await stat(filepath)).isDirectory()) throw err
        } else {
          while (filepath.endsWith(path.sep)) filepath = filepath.slice(0, -1)
          const i = filepath.lastIndexOf(path.sep)
          if (i <= 0) throw err

          await mkdir(filepath.slice(0, i), { mode, recursive: true })

          try {
            await mkdir(filepath, { mode })
          } catch (err) {
            if (!(await stat(filepath)).isDirectory()) throw err
          }
        }
      }
    } catch (e) {
      err = e
    }

    return done(err, cb)
  }

  const req = FileRequest.borrow()

  let err = null
  try {
    binding.mkdir(req.handle, filepath, mode)

    await req
  } catch (e) {
    err = new FileError(e.message, {
      operation: 'mkdir',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function mkdirSync(filepath, opts) {
  if (typeof opts === 'number') opts = { mode: opts }
  else if (!opts) opts = {}

  const mode = typeof opts.mode === 'number' ? opts.mode : 0o777

  filepath = toNamespacedPath(filepath)

  if (opts.recursive) {
    try {
      mkdirSync(filepath, { mode })
    } catch (err) {
      if (err.code !== 'ENOENT') {
        if (!statSync(filepath).isDirectory()) throw err
      } else {
        while (filepath.endsWith(path.sep)) filepath = filepath.slice(0, -1)
        const i = filepath.lastIndexOf(path.sep)
        if (i <= 0) throw err

        mkdirSync(filepath.slice(0, i), { mode, recursive: true })

        try {
          mkdirSync(filepath, { mode })
        } catch (err) {
          if (!statSync(filepath).isDirectory()) throw err
        }
      }
    }

    return
  }

  const req = FileRequest.borrow()

  try {
    binding.mkdirSync(req.handle, filepath, mode)
  } catch (e) {
    throw new FileError(e.message, {
      operation: 'mkdir',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }
}

async function mkdtemp(prefix, cb) {
  prefix = toNamespacedPath(prefix)

  const req = FileRequest.borrow()

  let res
  let err = null
  try {
    binding.mkdtemp(req.handle, prefix + 'XXXXXX')

    await req

    res = binding.requestResultPath(req.handle)
  } catch (e) {
    err = new FileError(e.message, { operation: 'mkdtemp', code: e.code, path: prefix })
  } finally {
    req.return()
  }

  return done(err, res, cb)
}

function mkdtempSync(prefix) {
  prefix = toNamespacedPath(prefix)

  const req = FileRequest.borrow()

  try {
    binding.mkdtempSync(req.handle, prefix + 'XXXXXX')

    return binding.requestResultPath(req.handle)
  } catch (e) {
    throw new FileError(e.message, { operation: 'mkdtempSync', code: e.code, path: prefix })
  } finally {
    req.return()
  }
}

async function rmdir(filepath, cb) {
  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  let err = null
  try {
    binding.rmdir(req.handle, filepath)

    await req
  } catch (e) {
    err = new FileError(e.message, {
      operation: 'rmdir',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function rmdirSync(filepath) {
  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  try {
    binding.rmdirSync(req.handle, filepath)
  } catch (e) {
    throw new FileError(e.message, {
      operation: 'rmdir',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }
}

async function rm(filepath, opts, cb) {
  if (typeof opts === 'function') {
    cb = opts
    opts = {}
  }

  if (!opts) opts = {}

  filepath = toNamespacedPath(filepath)

  let err = null
  try {
    const st = await lstat(filepath)

    if (st.isDirectory()) {
      if (opts.recursive) {
        try {
          await rmdir(filepath)
        } catch (err) {
          if (err.code !== 'ENOTEMPTY') throw err

          const files = await readdir(filepath)

          for (const file of files) {
            await rm(filepath + path.sep + file, opts)
          }

          await rmdir(filepath)
        }
      } else {
        throw new FileError('is a directory', {
          operation: 'rm',
          code: 'EISDIR',
          path: filepath
        })
      }
    } else {
      await unlink(filepath)
    }
  } catch (e) {
    if (e.code !== 'ENOENT' || !opts.force) err = e
  }

  return done(err, cb)
}

function rmSync(filepath, opts) {
  if (!opts) opts = {}

  filepath = toNamespacedPath(filepath)

  try {
    const st = lstatSync(filepath)

    if (st.isDirectory()) {
      if (opts.recursive) {
        try {
          rmdirSync(filepath)
        } catch (err) {
          if (err.code !== 'ENOTEMPTY') throw err

          const files = readdirSync(filepath)

          for (const file of files) {
            rmSync(filepath + path.sep + file, opts)
          }

          rmdirSync(filepath)
        }
      } else {
        throw new FileError('is a directory', {
          operation: 'rm',
          code: 'EISDIR',
          path: filepath
        })
      }
    } else {
      unlinkSync(filepath)
    }
  } catch (err) {
    if (err.code !== 'ENOENT' || !opts.force) throw err
  }
}

async function unlink(filepath, cb) {
  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  let err = null
  try {
    binding.unlink(req.handle, filepath)

    await req
  } catch (e) {
    err = new FileError(e.message, {
      operation: 'unlink',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function unlinkSync(filepath) {
  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  try {
    binding.unlinkSync(req.handle, filepath)
  } catch (e) {
    throw new FileError(e.message, {
      operation: 'unlink',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }
}

async function rename(src, dst, cb) {
  src = toNamespacedPath(src)
  dst = toNamespacedPath(dst)

  const req = FileRequest.borrow()

  let err = null
  try {
    binding.rename(req.handle, src, dst)

    await req
  } catch (e) {
    err = new FileError(e.message, {
      operation: 'rename',
      code: e.code,
      path: src,
      destination: dst
    })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function renameSync(src, dst) {
  src = toNamespacedPath(src)
  dst = toNamespacedPath(dst)

  const req = FileRequest.borrow()

  try {
    binding.renameSync(req.handle, src, dst)
  } catch (e) {
    throw new FileError(e.message, {
      operation: 'rename',
      code: e.code,
      path: src,
      destination: dst
    })
  } finally {
    req.return()
  }
}

async function copyFile(src, dst, mode = 0, cb) {
  if (typeof mode === 'function') {
    cb = mode
    mode = 0
  }

  src = toNamespacedPath(src)
  dst = toNamespacedPath(dst)

  const req = FileRequest.borrow()

  let err = null
  try {
    binding.copyfile(req.handle, src, dst, mode)

    await req
  } catch (e) {
    err = new FileError(e.message, {
      operation: 'copyfile',
      code: e.code,
      path: src,
      destination: dst
    })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function copyFileSync(src, dst, mode = 0) {
  src = toNamespacedPath(src)
  dst = toNamespacedPath(dst)

  const req = FileRequest.borrow()

  try {
    binding.copyfileSync(req.handle, src, dst, mode)
  } catch (e) {
    throw new FileError(e.message, {
      operation: 'copyfile',
      code: e.code,
      path: src,
      destination: dst
    })
  } finally {
    req.return()
  }
}

async function cp(src, dst, opts, cb) {
  if (typeof opts === 'function') {
    cb = opts
    opts = {}
  }

  if (!opts) opts = {}

  src = toNamespacedPath(src)
  dst = toNamespacedPath(dst)

  let err = null
  try {
    const st = await lstat(src)

    if (st.isDirectory()) {
      if (opts.recursive !== true) {
        throw new FileError('is a directory', { operation: 'cp', code: 'EISDIR', path: src })
      }

      try {
        await lstat(dst)
      } catch (e) {
        if (e.code !== 'ENOENT') throw e

        await mkdir(dst, { mode: st.mode, recursive: true })
      }

      const dir = await opendir(src)
      for await (const { name } of dir) {
        await cp(path.join(src, name), path.join(dst, name), opts)
      }
    } else if (st.isFile()) {
      await copyFile(src, dst)
      await chmod(dst, st.mode)
    }
  } catch (e) {
    err = e
  }

  return done(err, cb)
}

function cpSync(src, dst, opts = {}) {
  src = toNamespacedPath(src)
  dst = toNamespacedPath(dst)

  const st = lstatSync(src)

  if (st.isDirectory()) {
    if (opts.recursive !== true) {
      throw new FileError('is a directory', { operation: 'cp', code: 'EISDIR', path: src })
    }

    try {
      lstatSync(dst)
    } catch (e) {
      if (e.code !== 'ENOENT') throw e

      mkdirSync(dst, { mode: st.mode, recursive: true })
    }

    const dir = opendirSync(src)
    for (const { name } of dir) {
      cpSync(path.join(src, name), path.join(dst, name), opts)
    }
  } else if (st.isFile()) {
    copyFileSync(src, dst)
    chmodSync(dst, st.mode)
  }
}

async function realpath(filepath, opts, cb) {
  if (typeof opts === 'function') {
    cb = opts
    opts = {}
  }

  if (typeof opts === 'string') opts = { encoding: opts }
  else if (!opts) opts = {}

  const { encoding = 'utf8' } = opts

  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  let res
  let err = null
  try {
    binding.realpath(req.handle, filepath)

    await req

    res = Buffer.from(binding.requestResultString(req.handle))

    if (encoding !== 'buffer') res = res.toString(encoding)
  } catch (e) {
    err = new FileError(e.message, {
      operation: 'realpath',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }

  return done(err, res, cb)
}

function realpathSync(filepath, opts) {
  if (typeof opts === 'string') opts = { encoding: opts }
  else if (!opts) opts = {}

  const { encoding = 'utf8' } = opts

  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  try {
    binding.realpathSync(req.handle, filepath)

    let res = Buffer.from(binding.requestResultString(req.handle))

    if (encoding !== 'buffer') res = res.toString(encoding)

    return res
  } catch (e) {
    throw new FileError(e.message, {
      operation: 'realpath',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }
}

async function readlink(filepath, opts, cb) {
  if (typeof opts === 'function') {
    cb = opts
    opts = {}
  }

  if (typeof opts === 'string') opts = { encoding: opts }
  else if (!opts) opts = {}

  const { encoding = 'utf8' } = opts

  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  let res
  let err = null
  try {
    binding.readlink(req.handle, filepath)

    await req

    res = Buffer.from(binding.requestResultString(req.handle))

    if (encoding !== 'buffer') res = res.toString(encoding)
  } catch (e) {
    err = new FileError(e.message, {
      operation: 'readlink',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }

  return done(err, res, cb)
}

function readlinkSync(filepath, opts) {
  if (typeof opts === 'string') opts = { encoding: opts }
  else if (!opts) opts = {}

  const { encoding = 'utf8' } = opts

  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  try {
    binding.readlinkSync(req.handle, filepath)

    let res = Buffer.from(binding.requestResultString(req.handle))

    if (encoding !== 'buffer') res = res.toString(encoding)

    return res
  } catch (e) {
    throw new FileError(e.message, {
      operation: 'readlink',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }
}

async function fsync(fd, cb) {
  const req = FileRequest.borrow()

  let err = null
  try {
    binding.fsync(req.handle, fd)

    await req
  } catch (e) {
    err = new FileError(e.message, { operation: 'fsync', code: e.code, fd })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function fsyncSync(fd) {
  const req = FileRequest.borrow()

  try {
    binding.fsyncSync(req.handle, fd)
  } catch (e) {
    throw new FileError(e.message, { operation: 'fsyncSync', code: e.code, fd })
  } finally {
    req.return()
  }
}

async function fdatasync(fd, cb) {
  const req = FileRequest.borrow()

  let err = null
  try {
    binding.fdatasync(req.handle, fd)

    await req
  } catch (e) {
    err = new FileError(e.message, { operation: 'fdatasync', code: e.code, fd })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function fdatasyncSync(fd) {
  const req = FileRequest.borrow()

  try {
    binding.fdatasyncSync(req.handle, fd)
  } catch (e) {
    throw new FileError(e.message, { operation: 'fdatasyncSync', code: e.code, fd })
  } finally {
    req.return()
  }
}

function normalizeSymlinkTarget(target, type, filepath) {
  if (isWindows) {
    if (type === constants.UV_FS_SYMLINK_JUNCTION) target = path.resolve(filepath, '..', target)

    if (path.isAbsolute(target)) return path.toNamespacedPath(target)

    return target.replace(/\//g, path.sep)
  }

  return target
}

async function symlink(target, filepath, type, cb) {
  if (typeof type === 'function') {
    cb = type
    type = null
  }

  filepath = toNamespacedPath(filepath)

  if (typeof type === 'string') {
    switch (type) {
      case 'dir':
        type = constants.UV_FS_SYMLINK_DIR
        break
      case 'junction':
        type = constants.UV_FS_SYMLINK_JUNCTION
        break
      case 'file':
      default:
        type = 0
        break
    }
  } else if (typeof type !== 'number') {
    if (isWindows) {
      target = path.resolve(filepath, '..', target)

      try {
        type = (await stat(target)).isDirectory()
          ? constants.UV_FS_SYMLINK_DIR
          : constants.UV_FS_SYMLINK_JUNCTION
      } catch {
        type = 0
      }
    } else {
      type = 0
    }
  }

  target = normalizeSymlinkTarget(target, type, filepath)

  const req = FileRequest.borrow()

  let err = null
  try {
    binding.symlink(req.handle, target, filepath, type)

    await req
  } catch (e) {
    err = new FileError(e.message, {
      operation: 'symlink',
      code: e.code,
      path: target,
      destination: filepath
    })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function symlinkSync(target, filepath, type) {
  filepath = toNamespacedPath(filepath)

  if (typeof type === 'string') {
    switch (type) {
      case 'dir':
        type = constants.UV_FS_SYMLINK_DIR
        break
      case 'junction':
        type = constants.UV_FS_SYMLINK_JUNCTION
        break
      case 'file':
      default:
        type = 0
        break
    }
  } else if (typeof type !== 'number') {
    if (isWindows) {
      target = path.resolve(filepath, '..', target)

      try {
        type = statSync(target).isDirectory()
          ? constants.UV_FS_SYMLINK_DIR
          : constants.UV_FS_SYMLINK_JUNCTION
      } catch {
        type = 0
      }
    } else {
      type = 0
    }
  }

  target = normalizeSymlinkTarget(target, type, filepath)

  const req = FileRequest.borrow()

  try {
    binding.symlinkSync(req.handle, target, filepath, type)
  } catch (e) {
    throw new FileError(e.message, {
      operation: 'symlink',
      code: e.code,
      path: target,
      destination: filepath
    })
  } finally {
    req.return()
  }
}

async function opendir(filepath, opts, cb) {
  if (typeof opts === 'function') {
    cb = opts
    opts = {}
  }

  if (typeof opts === 'string') opts = { encoding: opts }
  else if (!opts) opts = {}

  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  let dir
  let err = null
  try {
    binding.opendir(req.handle, filepath)

    await req

    dir = new Dir(filepath, binding.requestResultDir(req.handle), opts)
  } catch (e) {
    err = new FileError(e.message, {
      operation: 'opendir',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }

  return done(err, dir, cb)
}

function opendirSync(filepath, opts) {
  if (typeof opts === 'string') opts = { encoding: opts }
  else if (!opts) opts = {}

  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  try {
    binding.opendirSync(req.handle, filepath)

    return new Dir(filepath, binding.requestResultDir(req.handle), opts)
  } catch (e) {
    throw new FileError(e.message, {
      operation: 'opendir',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }
}

async function readdir(filepath, opts, cb) {
  if (typeof opts === 'function') {
    cb = opts
    opts = {}
  }

  if (typeof opts === 'string') opts = { encoding: opts }
  else if (!opts) opts = {}

  const { withFileTypes = false, recursive = false } = opts

  filepath = toNamespacedPath(filepath)

  const queue = [filepath]
  let result = []
  let err = null
  try {
    while (queue.length !== 0) {
      const dir = await opendir(queue.pop())

      for await (const entry of dir) {
        const entryPath = path.join(entry.parentPath, entry.name)

        if (withFileTypes) {
          result.push(entry)
        } else {
          result.push(path.relative(filepath, entryPath))
        }

        if (recursive && entry.isDirectory()) queue.push(entryPath)
      }
    }
  } catch (e) {
    result = []
    err = e
  }

  return done(err, result, cb)
}

function readdirSync(filepath, opts) {
  if (typeof opts === 'string') opts = { encoding: opts }
  else if (!opts) opts = {}

  const { withFileTypes = false, recursive = false } = opts

  filepath = toNamespacedPath(filepath)

  const queue = [filepath]
  const result = []

  while (queue.length !== 0) {
    const dir = opendirSync(queue.pop(), opts)

    for (const entry of dir) {
      const entryPath = path.join(entry.parentPath, entry.name)

      if (withFileTypes) {
        result.push(entry)
      } else {
        result.push(path.relative(filepath, entryPath))
      }

      if (recursive && entry.isDirectory()) queue.push(entryPath)
    }
  }

  return result
}

async function readFile(filepath, opts, cb) {
  if (typeof opts === 'function') {
    cb = opts
    opts = {}
  }

  if (typeof opts === 'string') opts = { encoding: opts }
  else if (!opts) opts = {}

  const { encoding = 'buffer' } = opts

  let fd = -1
  let buffer = null
  let err = null
  try {
    fd = await open(filepath, opts.flag || 'r')

    const st = await fstat(fd)

    let len = 0

    if (st.size === 0) {
      const buffers = []

      while (true) {
        buffer = Buffer.allocUnsafe(8192)
        const r = await read(fd, buffer)
        len += r
        if (r === 0) break
        buffers.push(buffer.subarray(0, r))
      }

      buffer = Buffer.concat(buffers)
    } else {
      buffer = Buffer.allocUnsafe(st.size)

      while (true) {
        const r = await read(fd, len ? buffer.subarray(len) : buffer)
        len += r
        if (r === 0 || len === buffer.byteLength) break
      }

      if (len !== buffer.byteLength) buffer = buffer.subarray(0, len)
    }

    if (encoding !== 'buffer') buffer = buffer.toString(encoding)
  } catch (e) {
    err = e
  } finally {
    if (fd !== -1) await close(fd)
  }

  return done(err, buffer, cb)
}

function readFileSync(filepath, opts) {
  if (typeof opts === 'string') opts = { encoding: opts }
  else if (!opts) opts = {}

  const { encoding = 'buffer' } = opts

  let fd = -1
  try {
    fd = openSync(filepath, opts.flag || 'r')

    const st = fstatSync(fd)

    let buffer
    let len = 0

    if (st.size === 0) {
      const buffers = []

      while (true) {
        buffer = Buffer.allocUnsafe(8192)
        const r = readSync(fd, buffer)
        len += r
        if (r === 0) break
        buffers.push(buffer.subarray(0, r))
      }

      buffer = Buffer.concat(buffers)
    } else {
      buffer = Buffer.allocUnsafe(st.size)

      while (true) {
        const r = readSync(fd, len ? buffer.subarray(len) : buffer)
        len += r
        if (r === 0 || len === buffer.byteLength) break
      }

      if (len !== buffer.byteLength) buffer = buffer.subarray(0, len)
    }

    if (encoding !== 'buffer') buffer = buffer.toString(encoding)

    return buffer
  } finally {
    if (fd !== -1) closeSync(fd)
  }
}

async function writeFile(filepath, data, opts, cb) {
  if (typeof opts === 'function') {
    cb = opts
    opts = {}
  }

  if (typeof opts === 'string') opts = { encoding: opts }
  else if (!opts) opts = {}

  if (typeof data === 'string') data = Buffer.from(data, opts.encoding)

  let fd = -1
  let len = 0
  let err = null
  try {
    fd = await open(filepath, opts.flag || 'w', opts.mode || 0o666)

    while (true) {
      len += await write(fd, len ? data.subarray(len) : data)
      if (len === data.byteLength) break
    }
  } catch (e) {
    err = e
  } finally {
    if (fd !== -1) await close(fd)
  }

  return done(err, len, cb)
}

function writeFileSync(filepath, data, opts) {
  if (typeof opts === 'string') opts = { encoding: opts }
  else if (!opts) opts = {}

  if (typeof data === 'string') data = Buffer.from(data, opts.encoding)

  let fd = -1
  try {
    fd = openSync(filepath, opts.flag || 'w', opts.mode || 0o666)

    let len = 0

    while (true) {
      len += writeSync(fd, len ? data.subarray(len) : data)
      if (len === data.byteLength) break
    }
  } finally {
    if (fd !== -1) closeSync(fd)
  }
}

function appendFile(filepath, data, opts, cb) {
  if (typeof opts === 'function') {
    cb = opts
    opts = {}
  }

  if (typeof opts === 'string') opts = { encoding: opts }
  else if (!opts) opts = {}

  if (!opts.flag) opts = { ...opts, flag: 'a' }

  return writeFile(filepath, data, opts, cb)
}

function appendFileSync(filepath, data, opts) {
  if (typeof opts === 'string') opts = { encoding: opts }
  else if (!opts) opts = {}

  if (!opts.flag) opts = { ...opts, flag: 'a' }

  return writeFileSync(filepath, data, opts)
}

function watch(filepath, opts, cb) {
  if (typeof opts === 'function') {
    cb = opts
    opts = {}
  }

  if (typeof opts === 'string') opts = { encoding: opts }
  else if (!opts) opts = {}

  filepath = toNamespacedPath(filepath)

  return new Watcher(filepath, opts, cb)
}

class Stats {
  constructor(
    dev,
    mode,
    nlink,
    uid,
    gid,
    rdev,
    blksize,
    ino,
    size,
    blocks,
    atimeMs,
    mtimeMs,
    ctimeMs,
    birthtimeMs
  ) {
    this.dev = dev
    this.mode = mode
    this.nlink = nlink
    this.uid = uid
    this.gid = gid
    this.rdev = rdev
    this.blksize = blksize
    this.ino = ino
    this.size = size
    this.blocks = blocks
    this.atimeMs = atimeMs
    this.mtimeMs = mtimeMs
    this.ctimeMs = ctimeMs
    this.birthtimeMs = birthtimeMs
    this.atime = new Date(atimeMs)
    this.mtime = new Date(mtimeMs)
    this.ctime = new Date(ctimeMs)
    this.birthtime = new Date(birthtimeMs)
  }

  isDirectory() {
    return (this.mode & constants.S_IFMT) === constants.S_IFDIR
  }

  isFile() {
    return (this.mode & constants.S_IFMT) === constants.S_IFREG
  }

  isBlockDevice() {
    return (this.mode & constants.S_IFMT) === constants.S_IFBLK
  }

  isCharacterDevice() {
    return (this.mode & constants.S_IFMT) === constants.S_IFCHR
  }

  isFIFO() {
    return (this.mode & constants.S_IFMT) === constants.S_IFIFO
  }

  isSymbolicLink() {
    return (this.mode & constants.S_IFMT) === constants.S_IFLNK
  }

  isSocket() {
    return (this.mode & constants.S_IFMT) === constants.S_IFSOCK
  }
}

class StatFs {
  constructor(type, bsize, blocks, bfree, bavail, files, ffree) {
    this.type = type
    this.bsize = bsize
    this.blocks = blocks
    this.bfree = bfree
    this.bavail = bavail
    this.files = files
    this.ffree = ffree
  }
}

class Dir {
  constructor(path, handle, opts = {}) {
    const { encoding = 'utf8', bufferSize = 32 } = opts

    this.path = path

    this._encoding = encoding
    this._capacity = bufferSize
    this._buffer = new FIFO()
    this._ended = false
    this._handle = handle
  }

  async read(cb) {
    if (this._buffer.length) return ok(this._buffer.shift(), cb)
    if (this._ended) return ok(null, cb)

    const req = FileRequest.borrow()

    let entries
    let err = null
    try {
      req.retain(binding.readdir(req.handle, this._handle, this._capacity))

      await req

      entries = binding.requestResultDirents(req.handle)
    } catch (e) {
      err = new FileError(e.message, {
        operation: 'readdir',
        code: e.code,
        path: this.path
      })
    } finally {
      req.return()
    }

    if (err) return fail(err, cb)

    if (entries.length === 0) {
      this._ended = true

      return ok(null, cb)
    }

    for (const entry of entries) {
      let name = Buffer.from(entry.name)

      if (this._encoding !== 'buffer') name = name.toString(this._encoding)

      this._buffer.push(new Dirent(this.path, name, entry.type))
    }

    return ok(this._buffer.shift(), cb)
  }

  readSync() {
    if (this._buffer.length) return this._buffer.shift()
    if (this._ended) return null

    const req = FileRequest.borrow()

    let entries
    try {
      req.retain(binding.readdirSync(req.handle, this._handle, this._capacity))

      entries = binding.requestResultDirents(req.handle)
    } catch (e) {
      throw new FileError(e.message, {
        operation: 'readdir',
        code: e.code,
        path: this.path
      })
    } finally {
      req.return()
    }

    if (entries.length === 0) {
      this._ended = true

      return null
    }

    for (const entry of entries) {
      let name = Buffer.from(entry.name)

      if (this._encoding !== 'buffer') name = name.toString(this._encoding)

      this._buffer.push(new Dirent(this.path, name, entry.type))
    }

    return this._buffer.shift()
  }

  async close(cb) {
    const req = FileRequest.borrow()

    let err = null
    try {
      binding.closedir(req.handle, this._handle)

      await req
    } catch (e) {
      err = new FileError(e.message, {
        operation: 'closedir',
        code: e.code,
        path: this.path
      })
    } finally {
      req.return()
    }

    this._handle = null

    return done(err, cb)
  }

  closeSync() {
    const req = FileRequest.borrow()

    try {
      binding.closedirSync(req.handle, this._handle)
    } catch (e) {
      throw new FileError(e.message, {
        operation: 'closedir',
        code: e.code,
        path: this.path
      })
    } finally {
      req.return()
    }

    this._handle = null
  }

  [Symbol.dispose]() {
    this.closeSync()
  }

  async [Symbol.asyncDispose]() {
    await this.close()
  }

  *[Symbol.iterator]() {
    while (true) {
      const entry = this.readSync()
      if (entry === null) break
      yield entry
    }

    this.closeSync()
  }

  async *[Symbol.asyncIterator]() {
    while (true) {
      const entry = await this.read()
      if (entry === null) break
      yield entry
    }

    await this.close()
  }
}

class Dirent {
  constructor(parentPath, name, type) {
    this.parentPath = parentPath
    this.name = name
    this.type = type
  }

  isFile() {
    return this.type === constants.UV_DIRENT_FILE
  }

  isDirectory() {
    return this.type === constants.UV_DIRENT_DIR
  }

  isSymbolicLink() {
    return this.type === constants.UV_DIRENT_LINK
  }

  isFIFO() {
    return this.type === constants.UV_DIRENT_FIFO
  }

  isSocket() {
    return this.type === constants.UV_DIRENT_SOCKET
  }

  isCharacterDevice() {
    return this.type === constants.UV_DIRENT_CHAR
  }

  isBlockDevice() {
    return this.type === constants.UV_DIRENT_BLOCK
  }
}

class FileReadStream extends Readable {
  constructor(path, opts = {}) {
    const { eagerOpen = true } = opts

    super({ eagerOpen, ...opts })

    this.path = path
    this.fd = typeof opts.fd === 'number' ? opts.fd : -1
    this.flags = opts.flags || 'r'
    this.mode = opts.mode || 0o666

    this._offset = opts.start || 0
    this._missing = 0

    if (opts.length) {
      this._missing = opts.length
    } else if (typeof opts.end === 'number') {
      this._missing = opts.end - this._offset + 1
    } else {
      this._missing = -1
    }
  }

  async _open(cb) {
    let err

    if (this.fd === -1) {
      err = null
      try {
        this.fd = await open(this.path, this.flags, this.mode)
      } catch (e) {
        err = e
      }

      if (err) return cb(err)
    }

    let st
    err = null
    try {
      st = await fstat(this.fd)
    } catch (e) {
      err = e
    }

    if (err) return cb(err)

    if (this._missing === -1) this._missing = st.size

    if (st.size < this._offset) {
      this._offset = st.size
      this._missing = 0
    } else if (st.size < this._offset + this._missing) {
      this._missing = st.size - this._offset
    }

    cb(null)
  }

  async _read(size) {
    if (this._missing <= 0) return this.push(null)

    const data = Buffer.allocUnsafe(Math.min(this._missing, size))

    let len
    let err = null
    try {
      len = await read(this.fd, data, 0, data.byteLength, this._offset)
    } catch (e) {
      err = e
    }

    if (err) return this.destroy(err)

    if (len === 0) return this.push(null)

    if (this._missing < len) len = this._missing

    this._missing -= len
    this._offset += len

    this.push(data.subarray(0, len))
  }

  async _destroy(err, cb) {
    if (this.fd === -1) return cb(err)

    try {
      await close(this.fd)
    } catch (e) {
      err = err || e
    }

    cb(err)
  }
}

class FileWriteStream extends Writable {
  constructor(path, opts = {}) {
    const { eagerOpen = true } = opts

    super({ eagerOpen, ...opts })

    this.path = path
    this.fd = typeof opts.fd === 'number' ? opts.fd : -1
    this.flags = opts.flags || 'w'
    this.mode = opts.mode || 0o666
  }

  async _open(cb) {
    if (this.fd !== -1) return cb(null)

    let err = null
    try {
      this.fd = await open(this.path, this.flags, this.mode)
    } catch (e) {
      err = e
    }

    cb(err)
  }

  async _writev(batch, cb) {
    let err = null
    try {
      await writev(
        this.fd,
        batch.map(({ chunk }) => chunk)
      )
    } catch (e) {
      err = e
    }

    cb(err)
  }

  async _destroy(err, cb) {
    if (this.fd === -1) return cb(err)

    try {
      await close(this.fd)
    } catch (e) {
      err = err || e
    }

    cb(err)
  }
}

class Watcher extends EventEmitter {
  constructor(path, opts, onchange) {
    if (typeof opts === 'function') {
      onchange = opts
      opts = {}
    }

    if (!opts) opts = {}

    const { persistent = true, recursive = false, encoding = 'utf8' } = opts

    super()

    this._closed = false
    this._encoding = encoding
    this._handle = binding.watcherInit(path, recursive, this, this._onevent, this._onclose)

    if (!persistent) this.unref()

    if (onchange) this.on('change', onchange)
  }

  close() {
    if (this._closed) return
    this._closed = true

    binding.watcherClose(this._handle)
  }

  ref() {
    if (this._handle) binding.watcherRef(this._handle)
    return this
  }

  unref() {
    if (this._handle) binding.watcherUnref(this._handle)
    return this
  }

  [Symbol.asyncIterator]() {
    const buffer = []
    let done = false
    let error = null
    let next = null

    this.on('change', (eventType, filename) => {
      if (next) {
        next.resolve({ done: false, value: { eventType, filename } })
        next = null
      } else {
        buffer.push({ eventType, filename })
      }
    })
      .on('error', (err) => {
        done = true
        error = err

        if (next) {
          next.reject(error)
          next = null
        }
      })
      .on('close', () => {
        done = true

        if (next) {
          next.resolve({ done })
          next = null
        }
      })

    return {
      next: () =>
        new Promise((resolve, reject) => {
          if (error) return reject(error)

          if (buffer.length) return resolve({ done: false, value: buffer.shift() })

          if (done) return resolve({ done })

          next = { resolve, reject }
        })
    }
  }

  _onevent(err, events, filename) {
    if (err) {
      this.close()
      this.emit('error', err)
    } else {
      const path =
        this._encoding === 'buffer'
          ? Buffer.from(filename)
          : Buffer.from(filename).toString(this._encoding)

      if (events & binding.UV_RENAME) {
        this.emit('change', 'rename', path)
      }

      if (events & binding.UV_CHANGE) {
        this.emit('change', 'change', path)
      }
    }
  }

  _onclose() {
    this._handle = null

    this.emit('close')
  }
}

exports.access = access
exports.appendFile = appendFile
exports.chmod = chmod
exports.chown = chown
exports.close = close
exports.copyFile = copyFile
exports.cp = cp
exports.exists = exists
exports.fchmod = fchmod
exports.fchown = fchown
exports.fdatasync = fdatasync
exports.fstat = fstat
exports.fsync = fsync
exports.ftruncate = ftruncate
exports.futimes = futimes
exports.lchown = lchown
exports.lutimes = lutimes
exports.link = link
exports.lstat = lstat
exports.mkdir = mkdir
exports.mkdtemp = mkdtemp
exports.open = open
exports.opendir = opendir
exports.read = read
exports.readFile = readFile
exports.readdir = readdir
exports.readlink = readlink
exports.readv = readv
exports.realpath = realpath
exports.rename = rename
exports.rm = rm
exports.rmdir = rmdir
exports.stat = stat
exports.statfs = statfs
exports.symlink = symlink
exports.truncate = truncate
exports.unlink = unlink
exports.utimes = utimes
exports.watch = watch
exports.write = write
exports.writeFile = writeFile
exports.writev = writev

exports.accessSync = accessSync
exports.appendFileSync = appendFileSync
exports.chmodSync = chmodSync
exports.chownSync = chownSync
exports.closeSync = closeSync
exports.copyFileSync = copyFileSync
exports.cpSync = cpSync
exports.existsSync = existsSync
exports.fchmodSync = fchmodSync
exports.fchownSync = fchownSync
exports.fdatasyncSync = fdatasyncSync
exports.fstatSync = fstatSync
exports.fsyncSync = fsyncSync
exports.ftruncateSync = ftruncateSync
exports.futimesSync = futimesSync
exports.lchownSync = lchownSync
exports.lutimesSync = lutimesSync
exports.linkSync = linkSync
exports.lstatSync = lstatSync
exports.mkdirSync = mkdirSync
exports.mkdtempSync = mkdtempSync
exports.openSync = openSync
exports.opendirSync = opendirSync
exports.readFileSync = readFileSync
exports.readSync = readSync
exports.readdirSync = readdirSync
exports.readlinkSync = readlinkSync
exports.readvSync = readvSync
exports.realpathSync = realpathSync
exports.renameSync = renameSync
exports.rmSync = rmSync
exports.rmdirSync = rmdirSync
exports.statSync = statSync
exports.statfsSync = statfsSync
exports.symlinkSync = symlinkSync
exports.truncateSync = truncateSync
exports.unlinkSync = unlinkSync
exports.utimesSync = utimesSync
exports.writeFileSync = writeFileSync
exports.writeSync = writeSync
exports.writevSync = writevSync

exports.promises = require('./promises')

exports.Stats = Stats
exports.StatFs = StatFs
exports.Dir = Dir
exports.Dirent = Dirent
exports.Watcher = Watcher

exports.ReadStream = FileReadStream

exports.createReadStream = function createReadStream(path, opts) {
  return new FileReadStream(path, opts)
}

exports.WriteStream = FileWriteStream

exports.createWriteStream = function createWriteStream(path, opts) {
  return new FileWriteStream(path, opts)
}

function toNamespacedPath(filepath) {
  if (typeof filepath !== 'string') {
    if (isURL(filepath)) filepath = fileURLToPath(filepath)
    else filepath = filepath.toString()
  }

  return path.toNamespacedPath(filepath)
}

function toFlags(flags) {
  switch (flags) {
    case 'r':
      return constants.O_RDONLY
    case 'rs': // Fall through.
    case 'sr':
      return constants.O_RDONLY | constants.O_SYNC
    case 'r+':
      return constants.O_RDWR
    case 'rs+': // Fall through.
    case 'sr+':
      return constants.O_RDWR | constants.O_SYNC

    case 'w':
      return constants.O_TRUNC | constants.O_CREAT | constants.O_WRONLY
    case 'wx': // Fall through.
    case 'xw':
      return constants.O_TRUNC | constants.O_CREAT | constants.O_WRONLY | constants.O_EXCL

    case 'w+':
      return constants.O_TRUNC | constants.O_CREAT | constants.O_RDWR
    case 'wx+': // Fall through.
    case 'xw+':
      return constants.O_TRUNC | constants.O_CREAT | constants.O_RDWR | constants.O_EXCL

    case 'a':
      return constants.O_APPEND | constants.O_CREAT | constants.O_WRONLY
    case 'ax': // Fall through.
    case 'xa':
      return constants.O_APPEND | constants.O_CREAT | constants.O_WRONLY | constants.O_EXCL
    case 'as': // Fall through.
    case 'sa':
      return constants.O_APPEND | constants.O_CREAT | constants.O_WRONLY | constants.O_SYNC

    case 'a+':
      return constants.O_APPEND | constants.O_CREAT | constants.O_RDWR
    case 'ax+': // Fall through.
    case 'xa+':
      return constants.O_APPEND | constants.O_CREAT | constants.O_RDWR | constants.O_EXCL
    case 'as+': // Fall through.
    case 'sa+':
      return constants.O_APPEND | constants.O_CREAT | constants.O_RDWR | constants.O_SYNC
    default:
      return 0
  }
}

function toMode(mode) {
  return parseInt(mode, 8)
}
const binding = require('../binding')

module.exports = {
  O_RDWR: binding.O_RDWR,
  O_RDONLY: binding.O_RDONLY,
  O_WRONLY: binding.O_WRONLY,
  O_CREAT: binding.O_CREAT,
  O_TRUNC: binding.O_TRUNC,
  O_APPEND: binding.O_APPEND,

  F_OK: binding.F_OK || 0,
  R_OK: binding.R_OK || 0,
  W_OK: binding.W_OK || 0,
  X_OK: binding.X_OK || 0,

  S_IFMT: binding.S_IFMT,
  S_IFREG: binding.S_IFREG,
  S_IFDIR: binding.S_IFDIR,
  S_IFCHR: binding.S_IFCHR,
  S_IFLNK: binding.S_IFLNK,
  S_IFBLK: binding.S_IFBLK || 0,
  S_IFIFO: binding.S_IFIFO || 0,
  S_IFSOCK: binding.S_IFSOCK || 0,

  S_IRUSR: binding.S_IRUSR || 0,
  S_IWUSR: binding.S_IWUSR || 0,
  S_IXUSR: binding.S_IXUSR || 0,
  S_IRGRP: binding.S_IRGRP || 0,
  S_IWGRP: binding.S_IWGRP || 0,
  S_IXGRP: binding.S_IXGRP || 0,
  S_IROTH: binding.S_IROTH || 0,
  S_IWOTH: binding.S_IWOTH || 0,
  S_IXOTH: binding.S_IXOTH || 0,

  UV_DIRENT_UNKNOWN: binding.UV_DIRENT_UNKNOWN,
  UV_DIRENT_FILE: binding.UV_DIRENT_FILE,
  UV_DIRENT_DIR: binding.UV_DIRENT_DIR,
  UV_DIRENT_LINK: binding.UV_DIRENT_LINK,
  UV_DIRENT_FIFO: binding.UV_DIRENT_FIFO,
  UV_DIRENT_SOCKET: binding.UV_DIRENT_SOCKET,
  UV_DIRENT_CHAR: binding.UV_DIRENT_CHAR,
  UV_DIRENT_BLOCK: binding.UV_DIRENT_BLOCK,

  COPYFILE_EXCL: binding.UV_FS_COPYFILE_EXCL,
  COPYFILE_FICLONE: binding.UV_FS_COPYFILE_FICLONE,
  COPYFILE_FICLONE_FORCE: binding.UV_FS_COPYFILE_FICLONE_FORCE,
  UV_FS_SYMLINK_DIR: binding.UV_FS_SYMLINK_DIR,
  UV_FS_SYMLINK_JUNCTION: binding.UV_FS_SYMLINK_JUNCTION
}
const os = require('bare-os')

module.exports = class FileError extends Error {
  constructor(msg, opts = {}) {
    const { code, operation = null, path = null, destination = null, fd = -1 } = opts

    if (operation !== null) msg += describe(operation, opts)

    super(`${code}: ${msg}`)

    this.code = code

    if (operation !== null) this.operation = operation
    if (path !== null) this.path = path
    if (destination !== null) this.destination = destination
    if (fd !== -1) this.fd = fd
  }

  get name() {
    return 'FileError'
  }

  // For Node.js compatibility
  get errno() {
    return os.constants.errnos[this.code]
  }

  // For Node.js compatibility
  get syscall() {
    return this.operation
  }

  // For Node.js compatibility
  get dest() {
    return this.destination
  }
}

function describe(operation, opts) {
  const { path = null, destination = null, fd = -1 } = opts

  let result = `, ${operation}`

  if (path !== null) {
    result += ` ${JSON.stringify(path)}`

    if (destination !== null) {
      result += ` -> ${JSON.stringify(destination)}`
    }
  } else if (fd !== -1) {
    result += ` ${fd}`
  }

  return result
}
{
  "name": "bare-fs",
  "version": "4.7.2",
  "description": "Native file system operations for Bare",
  "exports": {
    "./package": "./package.json",
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    },
    "./promises": {
      "types": "./promises.d.ts",
      "default": "./promises.js"
    },
    "./constants": {
      "types": "./lib/constants.d.ts",
      "default": "./lib/constants.js"
    }
  },
  "files": [
    "index.js",
    "index.d.ts",
    "promises.js",
    "promises.d.ts",
    "binding.c",
    "binding.js",
    "CMakeLists.txt",
    "lib",
    "prebuilds"
  ],
  "addon": true,
  "scripts": {
    "format": "prettier --write . && lunte --fix",
    "lint": "prettier --check . && lunte",
    "test": "brittle-bare --coverage test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-fs.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-fs/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-fs#readme",
  "engines": {
    "bare": ">=1.16.0"
  },
  "dependencies": {
    "bare-events": "^2.5.4",
    "bare-path": "^3.0.0",
    "bare-stream": "^2.6.4",
    "bare-url": "^2.2.2",
    "fast-fifo": "^1.3.2"
  },
  "devDependencies": {
    "bare-buffer": "^3.0.2",
    "bare-crypto": "^1.11.2",
    "brittle": "^3.1.1",
    "cmake-bare": "^1.1.7",
    "lunte": "^1.6.0",
    "prettier": "^3.4.1",
    "prettier-config-holepunch": "^2.0.0"
  },
  "peerDependencies": {
    "bare-buffer": "*"
  },
  "peerDependenciesMeta": {
    "bare-buffer": {
      "optional": true
    }
  }
}
const EventEmitter = require('bare-events')
const fs = require('.')

class FileHandle extends EventEmitter {
  constructor(fd) {
    super()

    this.fd = fd
  }

  async close() {
    await fs.close(this.fd)

    this.fd = -1
    this.emit('close')
  }

  async read(buffer, ...args) {
    return {
      bytesRead: await fs.read(this.fd, buffer, ...args),
      buffer
    }
  }

  async readv(buffers, ...args) {
    return {
      bytesRead: await fs.readv(this.fd, buffers, ...args),
      buffers
    }
  }

  async write(buffer, ...args) {
    return {
      bytesWritten: await fs.write(this.fd, buffer, ...args),
      buffer
    }
  }

  async writev(buffers, ...args) {
    return {
      bytesWritten: await fs.writev(this.fd, buffers, ...args),
      buffers
    }
  }

  async stat() {
    return fs.fstat(this.fd)
  }

  async chmod(mode) {
    await fs.fchmod(this.fd, mode)
  }

  async chown(uid, gid) {
    await fs.fchown(this.fd, uid, gid)
  }

  async datasync() {
    return fs.fdatasync(this.fd)
  }

  async sync() {
    return fs.fsync(this.fd)
  }

  async truncate(len) {
    await fs.ftruncate(this.fd, len)
  }

  async utimes(atime, mtime) {
    await fs.futimes(this.fd, atime, mtime)
  }

  createReadStream(opts) {
    return fs.createReadStream(null, { ...opts, fd: this.fd })
  }

  createWriteStream(opts) {
    return fs.createWriteStream(null, { ...opts, fd: this.fd })
  }

  async [Symbol.asyncDispose]() {
    await this.close()
  }
}

exports.open = async function open(filepath, flags, mode) {
  return new FileHandle(await fs.open(filepath, flags, mode))
}

exports.access = fs.access
exports.appendFile = fs.appendFile
exports.chmod = fs.chmod
exports.chown = fs.chown
exports.constants = fs.constants
exports.copyFile = fs.copyFile
exports.cp = fs.cp
exports.lchown = fs.lchown
exports.lutimes = fs.lutimes
exports.link = fs.link
exports.lstat = fs.lstat
exports.mkdir = fs.mkdir
exports.mkdtemp = fs.mkdtemp
exports.opendir = fs.opendir
exports.readFile = fs.readFile
exports.readdir = fs.readdir
exports.readlink = fs.readlink
exports.realpath = fs.realpath
exports.rename = fs.rename
exports.rm = fs.rm
exports.rmdir = fs.rmdir
exports.stat = fs.stat
exports.statfs = fs.statfs
exports.truncate = fs.truncate
exports.symlink = fs.symlink
exports.unlink = fs.unlink
exports.utimes = fs.utimes
exports.watch = fs.watch
exports.writeFile = fs.writeFile
module.exports = require.addon()
const ansiEscapes = require('bare-ansi-escapes')
const getType = require('bare-type')
const binding = require('./binding')

const PLAIN_KEY = /^[a-zA-Z_][a-zA-Z_0-9]*$/

const defaultDepth = 2
const defaultBreakLength = 80
const defaultMaxArrayLength = 40

module.exports = exports = function inspect(value, opts = {}) {
  const {
    colors = false,
    depth = defaultDepth,
    breakLength = defaultBreakLength,
    stylize = defaultStylize(colors)
  } = opts

  const references = new InspectRefMap()

  const tree = inspectValue(value, 0, {
    colors,
    depth,
    breakLength,
    stylize,
    references
  })

  return tree.toString()
}

exports.styles = {
  bigint: ansiEscapes.colorYellow,
  boolean: ansiEscapes.colorYellow,
  date: ansiEscapes.colorMagenta,
  module: ansiEscapes.modifierUnderline,
  name: ansiEscapes.modifierReset,
  null: ansiEscapes.modifierBold,
  number: ansiEscapes.colorYellow,
  regexp: ansiEscapes.colorRed,
  special: ansiEscapes.colorCyan,
  string: ansiEscapes.colorGreen,
  symbol: ansiEscapes.colorGreen,
  undefined: ansiEscapes.colorBrightBlack
}

const styles = exports.styles

function defaultStylize(colors) {
  return function stylize(value, style) {
    const color = colors && styles[style]

    if (color) return color + value + ansiEscapes.modifierReset

    return value
  }
}

class InspectRefMap {
  constructor() {
    this.refs = new WeakMap()
    this.ids = new WeakMap()
    this.nextId = 1
  }

  has(object) {
    return this.refs.has(object)
  }

  get(object) {
    return this.refs.get(object) || null
  }

  set(object, ref) {
    this.refs.set(object, ref)
  }

  id(object) {
    let id = this.ids.get(object)
    if (id) return id

    id = this.nextId++
    this.ids.set(object, id)

    return id
  }
}

class InspectNode {
  constructor(depth, length, opts) {
    const { breakLength = defaultBreakLength, breakAlways = false } = opts

    this.depth = depth
    this.length = length
    this.breakLength = breakLength
    this.breakAlways = breakAlways
  }

  pad(n, string) {
    return string.padStart(n, ' ')
  }

  indent(n, string) {
    return '  '.repeat(n) + string
  }
}

class InspectRef extends InspectNode {
  constructor(depth, opts) {
    super(depth, '[circular *]'.length, opts)

    this.refs = opts.references
    this.count = 0
    this.circular = false
    this.color = opts.colors && styles.special
  }

  get id() {
    return this.refs.id(this)
  }

  increment() {
    return ++this.count
  }

  decrement() {
    return --this.count
  }

  toString(opts = {}) {
    const { offset = 0, pad = 0, indent = 0 } = opts

    let value = this.pad(pad, '[circular *' + this.id + ']')

    if (this.color) value = this.color + value + ansiEscapes.modifierReset

    return offset ? value : this.indent(indent, value)
  }
}

class InspectLeaf extends InspectNode {
  constructor(value, color, depth, opts) {
    const length = value.length

    if (value.includes('\n')) {
      value = value.replaceAll('\n', '\n' + '  '.repeat(depth))

      opts = { ...opts, breakAlways: true }
    }

    super(depth, length, opts)

    this.value = value
    this.color = opts.colors && color
  }

  toString(opts = {}) {
    const { offset = 0, pad = 0, indent = 0 } = opts

    let value = this.pad(pad, this.value)

    if (this.color) value = this.color + value + ansiEscapes.modifierReset

    return offset ? value : this.indent(indent, value)
  }
}

class InspectPair extends InspectNode {
  constructor(delim, left, right, depth, opts) {
    const length = left.length + delim.length + right.length

    if (left.breakAlways || right.breakAlways) {
      opts = { ...opts, breakAlways: true }
    }

    super(depth, length, opts)

    this.delim = delim
    this.left = left
    this.right = right
  }

  toString(opts = {}) {
    const { indent = 0 } = opts

    return this.indent(
      indent,
      this.left +
        this.delim +
        this.right.toString({
          indent,
          offset: this.left.length + this.delim.length
        })
    )
  }
}

class InspectSuspension extends InspectNode {
  constructor(overflow, depth, opts) {
    const label = `... ${overflow} more`

    super(depth, label.length, opts)

    this.label = label
  }

  toString(opts = {}) {
    const { indent = 0 } = opts

    return this.indent(indent, this.label)
  }
}

class InspectSequence extends InspectNode {
  constructor(header, footer, delim, values, ref, depth, opts) {
    const { tabulate = false } = opts

    const length =
      (ref.circular ? '<ref *>'.length + 1 : 0) +
      header.length +
      values.reduce(
        (length, value, i) =>
          length + value.length + (i === 0 ? 0 : delim.length),
        0
      ) +
      footer.length

    if (values.some((value) => value.breakAlways)) {
      opts = { ...opts, breakAlways: true }
    }

    super(depth, length, opts)

    this.header = header
    this.footer = footer
    this.delim = delim
    this.values = values
    this.ref = ref
    this.tabulate = tabulate
  }

  toString(opts = {}) {
    const { offset = 0, indent = 0 } = opts

    const split =
      this.breakAlways ||
      (this.values.length &&
        (offset + this.length > this.breakLength ||
          indent * 2 + this.length > this.breakLength))

    let header = this.header

    if (this.ref.circular) {
      header = '<ref *' + this.ref.id + '> ' + header
    }

    if (this.values.length === 0) {
      header = header.trimEnd()
    }

    if (offset === 0) {
      header = this.indent(indent, header)
    }

    if (split) {
      header = header.trimEnd() + '\n'
    }

    let string = header

    let columns = 1
    let pad = 0

    if (this.tabulate) {
      const widest = this.values.reduce(
        (length, value) =>
          value.breakAlways ? length : Math.max(length, value.length),
        0
      )

      if (widest) {
        columns = Math.max(
          columns,
          Math.floor(
            (this.breakLength - indent * 2) / (widest + this.delim.length)
          )
        )

        if (columns > 1) pad = widest
      }
    }

    for (let i = 0, n = this.values.length, offset = 0; i < n; i++) {
      const value = this.values[i]

      if (split) {
        let part

        if (i % columns === 0 || value.breakAlways) {
          part = value.toString({ indent: indent + 1, pad })
        } else {
          part = value.toString({ pad })
        }

        string += part

        if (i < n - 1) {
          if (i % columns === columns - 1 || this.values[i + 1].breakAlways) {
            string += this.delim.trimEnd() + '\n'
          } else {
            string += this.delim
          }
        }
      } else {
        if (i > 0) string += this.delim

        string += value.toString({ offset })

        offset += value.length
      }
    }

    let footer = this.footer

    if (this.values.length === 0) {
      footer = footer.trimStart()
    }

    if (split) {
      string += '\n' + this.indent(indent, footer.trimStart())
    } else {
      string += footer
    }

    return string
  }
}

function inspectValue(value, depth, opts) {
  const type = getType(value)

  if (type.isUndefined()) return inspectUndefined(depth, opts)
  if (type.isNull()) return inspectNull(depth, opts)
  if (type.isBoolean()) return inspectBoolean(value, depth, opts)
  if (type.isNumber()) return inspectNumber(value, depth, opts)
  if (type.isBigInt()) return inspectBigInt(value, depth, opts)
  if (type.isString()) return inspectString(value, depth, opts)
  if (type.isSymbol()) return inspectSymbol(value, depth, opts)
  if (type.isObject()) return inspectObject(type, value, depth, opts)
  if (type.isFunction()) return inspectFunction(type, value, depth, opts)
  if (type.isExternal()) return inspectExternal(value, opts, opts)
}

function inspectUndefined(depth, opts) {
  return new InspectLeaf('undefined', styles.undefined, depth, opts)
}

function inspectNull(depth, opts) {
  return new InspectLeaf('null', styles.null, depth, opts)
}

function inspectBoolean(value, depth, opts) {
  return new InspectLeaf(value.toString(), styles.boolean, depth, opts)
}

function inspectNumber(value, depth, opts) {
  let string

  if (Object.is(value, -0)) {
    string = '-0'
  } else {
    string = value.toString(10)
  }

  return new InspectLeaf(string, styles.number, depth, opts)
}

function inspectBigInt(value, depth, opts) {
  return new InspectLeaf(value.toString(10) + 'n', styles.bigint, depth, opts)
}

const STRING_ESCAPES =
  /[\ud800-\udbff][\udc00-\udfff]|[\u0000-\u001f'\\\ud800-\udfff]/g

function inspectString(value, depth, opts) {
  // https://tc39.es/ecma262/multipage/structured-data.html#sec-quotejsonstring
  const string = value.replace(STRING_ESCAPES, (match) => {
    if (match.length === 2) return match

    switch (match) {
      case "'":
        return "\\'"
      case '\\':
        return '\\\\'
      case '\b':
        return '\\b'
      case '\t':
        return '\\t'
      case '\n':
        return '\\n'
      case '\f':
        return '\\f'
      case '\r':
        return '\\r'
      default:
        return '\\u' + match.charCodeAt(0).toString(16).padStart(4, '0')
    }
  })

  return new InspectLeaf("'" + string + "'", styles.string, depth, opts)
}

function inspectSymbol(value, depth, opts) {
  return new InspectLeaf(value.toString(), styles.symbol, depth, opts)
}

function inspectKey(value, depth, opts) {
  if (PLAIN_KEY.test(value)) {
    return new InspectLeaf(value, null, depth, opts)
  } else {
    return inspectValue(value, depth, opts)
  }
}

function inspectObject(type, object, depth, opts) {
  const refs = opts.references

  let ref = refs.get(object)
  if (ref === null) {
    ref = new InspectRef(depth, opts)
    refs.set(object, ref)
  } else if (ref.count) {
    ref.circular = true
    return ref
  }

  const maxDepth = typeof opts.depth === 'number' ? opts.depth : Infinity

  if (maxDepth < depth) {
    const constructor = object.constructor

    return new InspectLeaf(
      '[' +
        (constructor && constructor.name ? constructor.name : 'Object') +
        ']',
      styles.special,
      depth,
      opts
    )
  }

  const inspect =
    object[Symbol.for('bare.inspect')] ||
    object[Symbol.for('nodejs.util.inspect.custom')]

  if (typeof inspect === 'function') {
    const value = inspect.call(
      object,
      typeof opts.depth === 'number' ? opts.depth - depth : null,
      {
        colors: opts.colors,
        breakLength: opts.breakLength,
        stylize: opts.stylize
      },
      exports
    )

    if (typeof value === 'object' && value !== null) {
      refs.set(value, ref)
    }

    if (typeof value !== 'string') {
      return inspectValue(value, depth, opts)
    }

    return new InspectLeaf(value, null, depth, opts)
  }

  if (type.isArray()) return inspectArray(object, ref, depth, opts)
  if (type.isDate()) return inspectDate(object, ref, depth, opts)
  if (type.isRegExp()) return inspectRegExp(object, ref, depth, opts)
  if (type.isError()) return inspectError(object, ref, depth, opts)
  if (type.isPromise()) return inspectPromise(object, ref, depth, opts)
  if (type.isMap()) return inspectMap(object, ref, depth, opts)
  if (type.isSet()) return inspectSet(object, ref, depth, opts)
  if (type.isWeakMap()) return inspectWeakMap(object, ref, depth, opts)
  if (type.isWeakSet()) return inspectWeakSet(object, ref, depth, opts)
  if (type.isWeakRef()) return inspectWeakRef(object, ref, depth, opts)
  if (type.isArrayBuffer()) return inspectArrayBuffer(object, ref, depth, opts)
  if (type.isSharedArrayBuffer())
    return inspectSharedArrayBuffer(object, ref, depth, opts)
  if (type.isTypedArray()) return inspectTypedArray(object, ref, depth, opts)
  if (type.isDataView()) return inspectDataView(object, ref, depth, opts)

  ref.increment()

  const values = []

  for (const key in object) {
    if (key === 'constructor') continue

    values.push(
      new InspectPair(
        ': ',
        inspectKey(key, depth + 1, opts),
        inspectValue(object[key], depth + 1, opts),
        depth + 1,
        opts
      )
    )
  }

  ref.decrement()

  let header = '{ '

  const tag = object[Symbol.toStringTag]

  if (tag) header = '[' + tag + '] ' + header

  if (object.constructor) {
    const name = object.constructor.name

    if (name && name !== 'Object') {
      header = object.constructor.name + ' ' + header
    }
  }

  return new InspectSequence(header, ' }', ', ', values, ref, depth, opts)
}

function inspectArray(array, ref, depth, opts) {
  const { maxArrayLength = defaultMaxArrayLength } = opts

  ref.increment()

  const values = []

  let remaining = Math.max(maxArrayLength, 0)

  for (let i = 0, n = array.length; i < n; i++) {
    if (remaining-- === 0) {
      values.push(
        new InspectSuspension(array.length - values.length, depth + 1, {
          ...opts,
          breakAlways: true
        })
      )

      break
    }

    values.push(inspectValue(array[i], depth + 1, opts))
  }

  for (const key of binding.getOwnNonIndexPropertyNames(array)) {
    values.push(
      new InspectPair(
        ': ',
        inspectKey(key, depth + 1, opts),
        inspectValue(array[key], depth + 1, opts),
        depth + 1,
        { ...opts, breakAlways: remaining < 0 }
      )
    )
  }

  ref.decrement()

  let header = '[ '

  if (array.constructor.name !== 'Array') {
    header = array.constructor.name + '(' + array.length + ') ' + header
  }

  return new InspectSequence(header, ' ]', ', ', values, ref, depth, {
    ...opts,
    tabulate: true
  })
}

function inspectDate(date, ref, depth, opts) {
  return new InspectLeaf(date.toISOString(), styles.date, depth, opts)
}

function inspectRegExp(regExp, ref, depth, opts) {
  return new InspectLeaf(regExp.toString(), styles.regexp, depth, opts)
}

function inspectError(error, ref, depth, opts) {
  let header

  if ('stack' in error) {
    header = error.stack

    if (depth > 0) {
      header = header.replaceAll('\n', '\n' + '  '.repeat(depth))
    }
  } else {
    header = error.toString()
  }

  const builtins = ['cause']

  if (error.name === 'AggregateError') {
    builtins.push('errors')
  } else if (error.name === 'SuppressedError') {
    builtins.push('error', 'suppressed')
  }

  const values = []

  for (const key of builtins) {
    if (key in error === false) continue

    values.push(
      new InspectPair(
        ': ',
        new InspectLeaf('[' + key + ']', null, depth + 1, opts),
        inspectValue(error[key], depth + 1, opts),
        depth + 1,
        opts
      )
    )
  }

  for (const key in error) {
    if (key === 'constructor' || builtins.includes(key)) continue

    values.push(
      new InspectPair(
        ': ',
        inspectKey(key, depth + 1, opts),
        inspectValue(error[key], depth + 1, opts),
        depth + 1,
        opts
      )
    )
  }

  if (values.length === 0) return new InspectLeaf(header, null, depth, opts)

  return new InspectSequence(
    header + ' {',
    ' }',
    ', ',
    values,
    ref,
    depth,
    opts
  )
}

function inspectPromise(promise, ref, depth, opts) {
  ref.increment()

  const state = binding.getPromiseState(promise)

  const values = []

  switch (state) {
    case 0: // Pending
      values.push(new InspectLeaf('<pending>', styles.special, depth, opts))
      break

    case 1: // Fulfilled
      values.push(inspectValue(binding.getPromiseResult(promise), depth, opts))
      break

    case 2: // Rejected
      values.push(
        new InspectLeaf('<rejected>', styles.special, depth, opts),
        inspectValue(binding.getPromiseResult(promise), depth, opts)
      )
  }

  ref.decrement()

  const header = promise.constructor.name + ' { '

  return new InspectSequence(header, ' }', ' ', values, ref, depth, opts)
}

function inspectMap(map, ref, depth, opts) {
  const {
    maxArrayLength = defaultMaxArrayLength,
    maxMapLength = maxArrayLength
  } = opts

  ref.increment()

  const values = []

  let remaining = maxMapLength

  for (const entry of map) {
    if (remaining-- === 0) {
      values.push(
        new InspectSuspension(map.size - values.length, depth + 1, {
          ...opts,
          breakAlways: true
        })
      )
      break
    }

    values.push(
      new InspectPair(
        ' => ',
        inspectValue(entry[0], depth + 1, opts),
        inspectValue(entry[1], depth + 1, opts),
        depth + 1,
        opts
      )
    )
  }

  for (const key in map) {
    if (key === 'constructor') continue

    const value = inspectValue(map[key], depth + 1, opts)

    values.push(
      new InspectPair(
        ': ',
        inspectKey(key, depth + 1, opts),
        value,
        depth + 1,
        { ...opts, breakAlways: remaining < 0 }
      )
    )
  }

  ref.decrement()

  const header = map.constructor.name + '(' + map.size + ') { '

  return new InspectSequence(header, ' }', ', ', values, ref, depth, {
    ...opts,
    tabulate: true
  })
}

function inspectSet(set, ref, depth, opts) {
  const {
    maxArrayLength = defaultMaxArrayLength,
    maxSetLength = maxArrayLength
  } = opts

  ref.increment()

  const values = []

  let remaining = maxSetLength

  for (const entry of set) {
    if (remaining-- === 0) {
      values.push(
        new InspectSuspension(set.size - values.length, depth + 1, {
          ...opts,
          breakAlways: true
        })
      )
      break
    }

    values.push(inspectValue(entry, depth + 1, opts))
  }

  for (const key in set) {
    if (key === 'constructor') continue

    const value = inspectValue(set[key], depth + 1, opts)

    values.push(
      new InspectPair(
        ': ',
        inspectKey(key, depth + 1, opts),
        value,
        depth + 1,
        { ...opts, breakAlways: remaining < 0 }
      )
    )
  }

  ref.decrement()

  const header = set.constructor.name + '(' + set.size + ') { '

  return new InspectSequence(header, ' }', ', ', values, ref, depth, {
    ...opts,
    tabulate: true
  })
}

function inspectWeakMap(weakMap, ref, depth, opts) {
  const header = weakMap.constructor.name + ' { '

  return new InspectSequence(
    header,
    ' }',
    ' ',
    [new InspectLeaf('<items unknown>', styles.special, depth + 1, opts)],
    ref,
    depth,
    opts
  )
}

function inspectWeakSet(weakSet, ref, depth, opts) {
  const header = weakSet.constructor.name + ' { '

  return new InspectSequence(
    header,
    ' }',
    ' ',
    [new InspectLeaf('<items unknown>', styles.special, depth + 1, opts)],
    ref,
    depth,
    opts
  )
}

function inspectWeakRef(weakRef, ref, depth, opts) {
  const target = weakRef.deref()

  let value

  if (target === undefined) {
    value = new InspectLeaf('<cleared>', styles.special, depth + 1, opts)
  } else {
    value = inspectValue(target, depth + 1, opts)
  }

  const header = weakRef.constructor.name + ' { '

  return new InspectSequence(header, ' }', ' ', [value], ref, depth, opts)
}

function inspectArrayBuffer(arrayBuffer, ref, depth, opts) {
  ref.increment()

  const values = []

  for (const key of ['byteLength']) {
    values.push(
      new InspectPair(
        ': ',
        inspectKey(key, depth + 1, opts),
        inspectValue(arrayBuffer[key], depth + 1, opts),
        depth + 1,
        opts
      )
    )
  }

  ref.decrement()

  const header = arrayBuffer.constructor.name + ' { '

  return new InspectSequence(header, ' }', ', ', values, ref, depth, opts)
}

function inspectSharedArrayBuffer(sharedArrayBuffer, ref, depth, opts) {
  ref.increment()

  const values = []

  for (const key of ['byteLength']) {
    values.push(
      new InspectPair(
        ': ',
        inspectKey(key, depth + 1, opts),
        inspectValue(sharedArrayBuffer[key], depth + 1, opts),
        depth + 1,
        opts
      )
    )
  }

  ref.decrement()

  const header = sharedArrayBuffer.constructor.name + ' { '

  return new InspectSequence(header, ' }', ', ', values, ref, depth, opts)
}

function inspectTypedArray(typedArray, ref, depth, opts) {
  if (Buffer.isBuffer(typedArray)) {
    return inspectBuffer(typedArray, ref, depth, opts)
  }

  const {
    maxArrayLength = defaultMaxArrayLength,
    maxTypedArrayLength = maxArrayLength
  } = opts

  ref.increment()

  const values = []

  let remaining = Math.max(maxTypedArrayLength, 0)

  for (let i = 0, n = typedArray.length; i < n; i++) {
    if (remaining-- === 0) {
      values.push(
        new InspectSuspension(typedArray.length - values.length, depth + 1, {
          ...opts,
          breakAlways: true
        })
      )

      break
    }

    values.push(inspectValue(typedArray[i], depth + 1, opts))
  }

  for (const key of binding.getOwnNonIndexPropertyNames(typedArray)) {
    values.push(
      new InspectPair(
        ': ',
        inspectKey(key, depth + 1, opts),
        inspectValue(typedArray[key], depth + 1, opts),
        depth + 1,
        { ...opts, breakAlways: remaining < 0 }
      )
    )
  }

  ref.decrement()

  const header = typedArray.constructor.name + '(' + typedArray.length + ') [ '

  return new InspectSequence(header, ' ]', ', ', values, ref, depth, {
    ...opts,
    tabulate: true
  })
}

function inspectBuffer(buffer, ref, depth, opts) {
  const {
    maxArrayLength = defaultMaxArrayLength,
    maxBufferLength = maxArrayLength
  } = opts

  ref.increment()

  const values = []

  let remaining = Math.max(maxBufferLength, 0)

  for (let i = 0, n = buffer.byteLength; i < n; i++) {
    if (remaining-- === 0) {
      values.push(
        new InspectSuspension(buffer.length - values.length, depth + 1, {
          ...opts,
          breakAlways: true
        })
      )

      break
    }

    values.push(
      new InspectLeaf(
        buffer[i].toString(16).padStart(2, '0'),
        null,
        depth + 1,
        opts
      )
    )
  }

  for (const key of binding.getOwnNonIndexPropertyNames(buffer)) {
    values.push(
      new InspectPair(
        ': ',
        inspectKey(key, depth + 1, opts),
        inspectValue(buffer[key], depth + 1, opts),
        depth + 1,
        { ...opts, breakAlways: remaining < 0 }
      )
    )
  }

  ref.decrement()

  return new InspectSequence('<Buffer ', '>', ' ', values, ref, depth, {
    ...opts,
    tabulate: true
  })
}

function inspectDataView(dataView, ref, depth, opts) {
  ref.increment()

  const values = []

  for (const key of ['byteLength', 'byteOffset', 'buffer']) {
    values.push(
      new InspectPair(
        ': ',
        inspectKey(key, depth + 1, opts),
        inspectValue(dataView[key], depth + 1, opts),
        depth + 1,
        opts
      )
    )
  }

  for (const key of binding.getOwnNonIndexPropertyNames(dataView)) {
    values.push(
      new InspectPair(
        ': ',
        inspectKey(key, depth + 1, opts),
        inspectValue(dataView[key], depth + 1, opts),
        depth + 1,
        opts
      )
    )
  }

  ref.decrement()

  const header = dataView.constructor.name + ' { '

  return new InspectSequence(header, ' }', ', ', values, ref, depth, opts)
}

function inspectFunction(type, fn, depth, opts) {
  if (fn.toString().startsWith('class')) return inspectClass(fn, depth, opts)

  let tag = 'function'

  if (type.isGeneratorFunction()) tag = 'generator ' + tag
  if (type.isAsyncFunction()) tag = 'async ' + tag

  return new InspectLeaf(
    '[' + tag + ' ' + (fn.name ? fn.name : '(anonymous)') + ']',
    styles.special,
    depth,
    opts
  )
}

function inspectClass(ctor, depth, opts) {
  return new InspectLeaf(
    '[class ' + (ctor.name ? ctor.name : '(anonymous)') + ']',
    styles.special,
    depth,
    opts
  )
}

function inspectExternal(external, depth, opts) {
  return new InspectLeaf(
    '[external 0x' + binding.getExternal(external).toString(16) + ']',
    styles.special,
    depth,
    opts
  )
}
{
  "name": "bare-inspect",
  "version": "3.1.4",
  "description": "Inspect objects as strings for debugging",
  "exports": {
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    },
    "./package": "./package.json"
  },
  "files": [
    "index.js",
    "index.d.ts",
    "binding.c",
    "binding.js",
    "CMakeLists.txt",
    "prebuilds"
  ],
  "addon": true,
  "scripts": {
    "test": "prettier . --check && bare test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-inspect.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-inspect/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-inspect#readme",
  "engines": {
    "bare": ">=1.18.0"
  },
  "dependencies": {
    "bare-ansi-escapes": "^2.1.0",
    "bare-type": "^1.0.0"
  },
  "devDependencies": {
    "brittle": "^3.3.2",
    "cmake-bare": "^1.1.6",
    "prettier": "^3.4.1",
    "prettier-config-standard": "^7.0.0"
  }
}
module.exports = require.addon()
const binding = require('./binding')
const errors = require('./lib/errors')
const constants = require('./lib/constants')

exports.constants = constants
exports.errors = errors

exports.EOL = binding.platform === 'win32' ? '\r\n' : '\n'

exports.devNull = binding.platform === 'win32' ? '\\\\.\\nul' : '/dev/null'

exports.platform = function platform() {
  return binding.platform
}

exports.arch = function arch() {
  return binding.arch
}

exports.type = binding.type
exports.version = binding.version
exports.release = binding.release
exports.machine = binding.machine
exports.execPath = binding.execPath
exports.pid = binding.pid
exports.ppid = binding.ppid
exports.cwd = binding.cwd
exports.chdir = binding.chdir
exports.tmpdir = binding.tmpdir
exports.homedir = binding.homedir
exports.hostname = binding.hostname
exports.userInfo = binding.userInfo
exports.groupInfo = binding.groupInfo

exports.networkInterfaces = function networkInterfaces() {
  const result = {}

  for (const entry of binding.networkInterfaces()) {
    const { name, ...properties } = entry

    if (result[name]) result[name].push(properties)
    else result[name] = [properties]
  }

  return result
}

exports.kill = function kill(pid, signal = constants.signals.SIGTERM) {
  if (typeof signal === 'string') {
    if (signal in constants.signals === false) {
      throw errors.UNKNOWN_SIGNAL('Unknown signal: ' + signal)
    }

    signal = constants.signals[signal]
  }

  binding.kill(pid, signal)
}

exports.endianness = function endianness() {
  return binding.isLittleEndian ? 'LE' : 'BE'
}

exports.availableParallelism = binding.availableParallelism

exports.cpuUsage = function cpuUsage(previous) {
  const current = binding.cpuUsage()

  if (previous) {
    return {
      user: current.user - previous.user,
      system: current.system - previous.system
    }
  }

  return current
}

exports.threadCpuUsage = function threadCpuUsage(previous) {
  const current = binding.threadCpuUsage()

  if (previous) {
    return {
      user: current.user - previous.user,
      system: current.system - previous.system
    }
  }

  return current
}

exports.resourceUsage = binding.resourceUsage
exports.memoryUsage = binding.memoryUsage
exports.freemem = binding.freemem
exports.totalmem = binding.totalmem
exports.availableMemory = binding.availableMemory
exports.constrainedMemory = binding.constrainedMemory
exports.uptime = binding.uptime
exports.loadavg = binding.loadavg
exports.cpus = binding.cpus

exports.getProcessTitle = binding.getProcessTitle

exports.setProcessTitle = function setProcessTitle(title) {
  if (typeof title !== 'string') title = title.toString()

  if (title.length >= 256) {
    throw errors.TITLE_OVERFLOW('Process title is too long')
  }

  binding.setProcessTitle(title)
}

exports.getPriority = function getPriority(pid = 0) {
  return binding.getPriority(pid)
}

exports.setPriority = function setPriority(pid, priority) {
  if (priority === undefined) {
    priority = pid
    pid = 0
  }

  binding.setPriority(pid, priority)
}

exports.getEnvKeys = binding.getEnvKeys
exports.getEnv = binding.getEnv
exports.hasEnv = binding.hasEnv
exports.setEnv = binding.setEnv
exports.unsetEnv = binding.unsetEnv
const binding = require('../binding')

module.exports = {
  signals: binding.signals,
  errnos: binding.errnos,
  priority: binding.priority
}
module.exports = class OSError extends Error {
  constructor(msg, fn = OSError, code = fn.name) {
    super(`${code}: ${msg}`)
    this.code = code

    if (Error.captureStackTrace) {
      Error.captureStackTrace(this, fn)
    }
  }

  get name() {
    return 'OSError'
  }

  static UNKNOWN_SIGNAL(msg) {
    return new OSError(msg, OSError.UNKNOWN_SIGNAL)
  }

  static TITLE_OVERFLOW(msg) {
    return new OSError(msg, OSError.TITLE_OVERFLOW)
  }
}
{
  "name": "bare-os",
  "version": "3.9.1",
  "description": "Operating system utilities for Bare",
  "exports": {
    "./package": "./package.json",
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    },
    "./constants": {
      "types": "./lib/constants.d.ts",
      "default": "./lib/constants.js"
    },
    "./errors": {
      "types": "./lib/errors.d.ts",
      "default": "./lib/errors.js"
    }
  },
  "files": [
    "index.js",
    "index.d.ts",
    "binding.c",
    "binding.js",
    "CMakeLists.txt",
    "lib",
    "prebuilds"
  ],
  "addon": true,
  "scripts": {
    "format": "prettier --write . && lunte --fix",
    "lint": "prettier --check . && lunte",
    "test": "brittle-bare --coverage test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-os.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-os/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-os#readme",
  "engines": {
    "bare": ">=1.14.0"
  },
  "devDependencies": {
    "brittle": "^3.1.1",
    "cmake-bare": "^1.1.6",
    "lunte": "^1.6.0",
    "prettier": "^3.4.2",
    "prettier-config-holepunch": "^2.0.0"
  }
}
// This export SHOULD NOT be shortened in any way as having the full
// `module.exports = require(...)` statement is crucial for synthesizing
// ESM exports.

if (Bare.platform === 'win32') {
  module.exports = require('./lib/win32')
} else {
  module.exports = require('./lib/posix')
}
module.exports = {
  CHAR_UPPERCASE_A: 0x41,
  CHAR_LOWERCASE_A: 0x61,
  CHAR_UPPERCASE_Z: 0x5a,
  CHAR_LOWERCASE_Z: 0x7a,
  CHAR_DOT: 0x2e,
  CHAR_FORWARD_SLASH: 0x2f,
  CHAR_BACKWARD_SLASH: 0x5c,
  CHAR_COLON: 0x3a,
  CHAR_QUESTION_MARK: 0x3f
}
const os = require('bare-os')

const { normalizeString } = require('./shared')
const { CHAR_DOT, CHAR_FORWARD_SLASH } = require('./constants')

function isPosixPathSeparator(code) {
  return code === CHAR_FORWARD_SLASH
}

exports.win32 = require('./win32')
exports.posix = exports

exports.sep = '/'
exports.delimiter = ':'

exports.resolve = function resolve(...args) {
  let resolvedPath = ''
  let resolvedAbsolute = false

  for (let i = args.length - 1; i >= -1 && !resolvedAbsolute; i--) {
    const path = i >= 0 ? args[i] : os.cwd()

    if (path.length === 0) {
      continue
    }

    resolvedPath = `${path}/${resolvedPath}`
    resolvedAbsolute = path.charCodeAt(0) === CHAR_FORWARD_SLASH
  }

  resolvedPath = normalizeString(resolvedPath, !resolvedAbsolute, '/', isPosixPathSeparator)

  if (resolvedAbsolute) {
    return `/${resolvedPath}`
  }

  return resolvedPath.length > 0 ? resolvedPath : '.'
}

exports.normalize = function normalize(path) {
  if (path.length === 0) return '.'

  const isAbsolute = path.charCodeAt(0) === CHAR_FORWARD_SLASH
  const trailingSeparator = path.charCodeAt(path.length - 1) === CHAR_FORWARD_SLASH

  path = normalizeString(path, !isAbsolute, '/', isPosixPathSeparator)

  if (path.length === 0) {
    if (isAbsolute) return '/'
    return trailingSeparator ? './' : '.'
  }

  if (trailingSeparator) path += '/'

  return isAbsolute ? `/${path}` : path
}

exports.isAbsolute = function isAbsolute(path) {
  return path.length > 0 && path.charCodeAt(0) === CHAR_FORWARD_SLASH
}

exports.join = function join(...args) {
  if (args.length === 0) return '.'
  let joined
  for (let i = 0; i < args.length; ++i) {
    const arg = args[i]
    if (arg.length > 0) {
      if (joined === undefined) joined = arg
      else joined += `/${arg}`
    }
  }
  if (joined === undefined) return '.'
  return exports.normalize(joined)
}

exports.relative = function relative(from, to) {
  if (from === to) return ''

  from = exports.resolve(from)
  to = exports.resolve(to)

  if (from === to) return ''

  const fromStart = 1
  const fromEnd = from.length
  const fromLen = fromEnd - fromStart
  const toStart = 1
  const toLen = to.length - toStart

  const length = fromLen < toLen ? fromLen : toLen
  let lastCommonSep = -1
  let i = 0
  for (; i < length; i++) {
    const fromCode = from.charCodeAt(fromStart + i)
    if (fromCode !== to.charCodeAt(toStart + i)) {
      break
    } else if (fromCode === CHAR_FORWARD_SLASH) {
      lastCommonSep = i
    }
  }
  if (i === length) {
    if (toLen > length) {
      if (to.charCodeAt(toStart + i) === CHAR_FORWARD_SLASH) {
        return to.substring(toStart + i + 1)
      }
      if (i === 0) {
        return to.substring(toStart + i)
      }
    } else if (fromLen > length) {
      if (from.charCodeAt(fromStart + i) === CHAR_FORWARD_SLASH) {
        lastCommonSep = i
      } else if (i === 0) {
        lastCommonSep = 0
      }
    }
  }

  let out = ''
  for (i = fromStart + lastCommonSep + 1; i <= fromEnd; ++i) {
    if (i === fromEnd || from.charCodeAt(i) === CHAR_FORWARD_SLASH) {
      out += out.length === 0 ? '..' : '/..'
    }
  }

  return `${out}${to.substring(toStart + lastCommonSep)}`
}

exports.toNamespacedPath = function toNamespacedPath(path) {
  return path
}

exports.dirname = function dirname(path) {
  if (path.length === 0) return '.'
  const hasRoot = path.charCodeAt(0) === CHAR_FORWARD_SLASH
  let end = -1
  let matchedSlash = true
  for (let i = path.length - 1; i >= 1; --i) {
    if (path.charCodeAt(i) === CHAR_FORWARD_SLASH) {
      if (!matchedSlash) {
        end = i
        break
      }
    } else {
      matchedSlash = false
    }
  }

  if (end === -1) return hasRoot ? '/' : '.'
  if (hasRoot && end === 1) return '//'
  return path.substring(0, end)
}

exports.basename = function basename(path, suffix) {
  let start = 0
  let end = -1
  let matchedSlash = true

  if (suffix !== undefined && suffix.length > 0 && suffix.length <= path.length) {
    if (suffix === path) {
      return ''
    }
    let extIdx = suffix.length - 1
    let firstNonSlashEnd = -1
    for (let i = path.length - 1; i >= 0; --i) {
      const code = path.charCodeAt(i)
      if (code === CHAR_FORWARD_SLASH) {
        if (!matchedSlash) {
          start = i + 1
          break
        }
      } else {
        if (firstNonSlashEnd === -1) {
          matchedSlash = false
          firstNonSlashEnd = i + 1
        }
        if (extIdx >= 0) {
          if (code === suffix.charCodeAt(extIdx)) {
            if (--extIdx === -1) {
              end = i
            }
          } else {
            extIdx = -1
            end = firstNonSlashEnd
          }
        }
      }
    }

    if (start === end) end = firstNonSlashEnd
    else if (end === -1) end = path.length
    return path.substring(start, end)
  }

  for (let i = path.length - 1; i >= 0; --i) {
    if (path.charCodeAt(i) === CHAR_FORWARD_SLASH) {
      if (!matchedSlash) {
        start = i + 1
        break
      }
    } else if (end === -1) {
      matchedSlash = false
      end = i + 1
    }
  }

  if (end === -1) return ''
  return path.substring(start, end)
}

exports.extname = function extname(path) {
  let startDot = -1
  let startPart = 0
  let end = -1
  let matchedSlash = true
  let preDotState = 0
  for (let i = path.length - 1; i >= 0; --i) {
    const code = path.charCodeAt(i)
    if (code === CHAR_FORWARD_SLASH) {
      if (!matchedSlash) {
        startPart = i + 1
        break
      }
      continue
    }
    if (end === -1) {
      matchedSlash = false
      end = i + 1
    }
    if (code === CHAR_DOT) {
      if (startDot === -1) startDot = i
      else if (preDotState !== 1) preDotState = 1
    } else if (startDot !== -1) {
      preDotState = -1
    }
  }

  if (
    startDot === -1 ||
    end === -1 ||
    preDotState === 0 ||
    (preDotState === 1 && startDot === end - 1 && startDot === startPart + 1)
  ) {
    return ''
  }
  return path.substring(startDot, end)
}
const { CHAR_DOT, CHAR_FORWARD_SLASH } = require('./constants')

exports.normalizeString = function normalizeString(
  path,
  allowAboveRoot,
  separator,
  isPathSeparator
) {
  let res = ''
  let lastSegmentLength = 0
  let lastSlash = -1
  let dots = 0
  let code = 0
  for (let i = 0; i <= path.length; ++i) {
    if (i < path.length) {
      code = path.charCodeAt(i)
    } else if (isPathSeparator(code)) {
      break
    } else {
      code = CHAR_FORWARD_SLASH
    }

    if (isPathSeparator(code)) {
      if (lastSlash === i - 1 || dots === 1);
      else if (dots === 2) {
        if (
          res.length < 2 ||
          lastSegmentLength !== 2 ||
          res.charCodeAt(res.length - 1) !== CHAR_DOT ||
          res.charCodeAt(res.length - 2) !== CHAR_DOT
        ) {
          if (res.length > 2) {
            const lastSlashIndex = res.lastIndexOf(separator)
            if (lastSlashIndex === -1) {
              res = ''
              lastSegmentLength = 0
            } else {
              res = res.substring(0, lastSlashIndex)
              lastSegmentLength = res.length - 1 - res.lastIndexOf(separator)
            }
            lastSlash = i
            dots = 0
            continue
          } else if (res.length !== 0) {
            res = ''
            lastSegmentLength = 0
            lastSlash = i
            dots = 0
            continue
          }
        }
        if (allowAboveRoot) {
          res += res.length > 0 ? `${separator}..` : '..'
          lastSegmentLength = 2
        }
      } else {
        if (res.length > 0) {
          res += `${separator}${path.substring(lastSlash + 1, i)}`
        } else {
          res = path.substring(lastSlash + 1, i)
        }
        lastSegmentLength = i - lastSlash - 1
      }
      lastSlash = i
      dots = 0
    } else if (code === CHAR_DOT && dots !== -1) {
      ++dots
    } else {
      dots = -1
    }
  }
  return res
}
const os = require('bare-os')

const { normalizeString } = require('./shared')
const {
  CHAR_UPPERCASE_A,
  CHAR_LOWERCASE_A,
  CHAR_UPPERCASE_Z,
  CHAR_LOWERCASE_Z,
  CHAR_DOT,
  CHAR_FORWARD_SLASH,
  CHAR_BACKWARD_SLASH,
  CHAR_COLON,
  CHAR_QUESTION_MARK
} = require('./constants')

function isWindowsPathSeparator(code) {
  return code === CHAR_FORWARD_SLASH || code === CHAR_BACKWARD_SLASH
}

function isWindowsDeviceRoot(code) {
  return (
    (code >= CHAR_UPPERCASE_A && code <= CHAR_UPPERCASE_Z) ||
    (code >= CHAR_LOWERCASE_A && code <= CHAR_LOWERCASE_Z)
  )
}

exports.posix = require('./posix')
exports.win32 = exports

exports.sep = '\\'
exports.delimiter = ';'

exports.resolve = function resolve(...args) {
  let resolvedDevice = ''
  let resolvedTail = ''
  let resolvedAbsolute = false

  for (let i = args.length - 1; i >= -1; i--) {
    let path
    if (i >= 0) {
      path = args[i]

      if (path.length === 0) continue
    } else if (resolvedDevice.length === 0) {
      path = os.cwd()
    } else {
      path = os.getEnv(`=${resolvedDevice}`) || os.cwd()

      if (
        path === undefined ||
        (path.substring(0, 2).toLowerCase() !== resolvedDevice.toLowerCase() &&
          path.charCodeAt(2) === CHAR_BACKWARD_SLASH)
      ) {
        path = `${resolvedDevice}\\`
      }
    }

    const len = path.length
    let rootEnd = 0
    let device = ''
    let isAbsolute = false
    const code = path.charCodeAt(0)

    if (len === 1) {
      if (isWindowsPathSeparator(code)) {
        rootEnd = 1
        isAbsolute = true
      }
    } else if (isWindowsPathSeparator(code)) {
      isAbsolute = true

      if (isWindowsPathSeparator(path.charCodeAt(1))) {
        let j = 2
        let last = j
        while (j < len && !isWindowsPathSeparator(path.charCodeAt(j))) {
          j++
        }
        if (j < len && j !== last) {
          const firstPart = path.substring(last, j)
          last = j
          while (j < len && isWindowsPathSeparator(path.charCodeAt(j))) {
            j++
          }
          if (j < len && j !== last) {
            last = j
            while (j < len && !isWindowsPathSeparator(path.charCodeAt(j))) {
              j++
            }
            if (j === len || j !== last) {
              device = `\\\\${firstPart}\\${path.substring(last, j)}`
              rootEnd = j
            }
          }
        }
      } else {
        rootEnd = 1
      }
    } else if (isWindowsDeviceRoot(code) && path.charCodeAt(1) === CHAR_COLON) {
      device = path.substring(0, 2)
      rootEnd = 2
      if (len > 2 && isWindowsPathSeparator(path.charCodeAt(2))) {
        isAbsolute = true
        rootEnd = 3
      }
    }

    if (device.length > 0) {
      if (resolvedDevice.length > 0) {
        if (device.toLowerCase() !== resolvedDevice.toLowerCase()) {
          continue
        }
      } else {
        resolvedDevice = device
      }
    }

    if (resolvedAbsolute) {
      if (resolvedDevice.length > 0) {
        break
      }
    } else {
      resolvedTail = `${path.substring(rootEnd)}\\${resolvedTail}`
      resolvedAbsolute = isAbsolute
      if (isAbsolute && resolvedDevice.length > 0) {
        break
      }
    }
  }

  resolvedTail = normalizeString(resolvedTail, !resolvedAbsolute, '\\', isWindowsPathSeparator)

  return resolvedAbsolute
    ? `${resolvedDevice}\\${resolvedTail}`
    : `${resolvedDevice}${resolvedTail}` || '.'
}

exports.normalize = function normalize(path) {
  const len = path.length
  if (len === 0) return '.'
  let rootEnd = 0
  let device
  let isAbsolute = false
  const code = path.charCodeAt(0)

  if (len === 1) {
    return code === CHAR_FORWARD_SLASH ? '\\' : path
  }

  if (isWindowsPathSeparator(code)) {
    isAbsolute = true

    if (isWindowsPathSeparator(path.charCodeAt(1))) {
      let j = 2
      let last = j
      while (j < len && !isWindowsPathSeparator(path.charCodeAt(j))) {
        j++
      }
      if (j < len && j !== last) {
        const firstPart = path.substring(last, j)
        last = j
        while (j < len && isWindowsPathSeparator(path.charCodeAt(j))) {
          j++
        }
        if (j < len && j !== last) {
          last = j
          while (j < len && !isWindowsPathSeparator(path.charCodeAt(j))) {
            j++
          }
          if (j === len) {
            return `\\\\${firstPart}\\${path.substring(last)}\\`
          }
          if (j !== last) {
            device = `\\\\${firstPart}\\${path.substring(last, j)}`
            rootEnd = j
          }
        }
      }
    } else {
      rootEnd = 1
    }
  } else if (isWindowsDeviceRoot(code) && path.charCodeAt(1) === CHAR_COLON) {
    device = path.substring(0, 2)
    rootEnd = 2
    if (len > 2 && isWindowsPathSeparator(path.charCodeAt(2))) {
      isAbsolute = true
      rootEnd = 3
    }
  }

  let tail =
    rootEnd < len
      ? normalizeString(path.substring(rootEnd), !isAbsolute, '\\', isWindowsPathSeparator)
      : ''
  if (tail.length === 0 && !isAbsolute) {
    tail = '.'
  }
  if (tail.length > 0 && isWindowsPathSeparator(path.charCodeAt(len - 1))) {
    tail += '\\'
  }
  if (device === undefined) {
    return isAbsolute ? `\\${tail}` : tail
  }
  return isAbsolute ? `${device}\\${tail}` : `${device}${tail}`
}

exports.isAbsolute = function isAbsolute(path) {
  const len = path.length
  if (len === 0) return false

  const code = path.charCodeAt(0)

  return (
    isWindowsPathSeparator(code) ||
    (len > 2 &&
      isWindowsDeviceRoot(code) &&
      path.charCodeAt(1) === CHAR_COLON &&
      isWindowsPathSeparator(path.charCodeAt(2)))
  )
}

exports.join = function join(...args) {
  if (args.length === 0) return '.'

  let joined
  let firstPart
  for (let i = 0; i < args.length; ++i) {
    const arg = args[i]
    if (arg.length > 0) {
      if (joined === undefined) joined = firstPart = arg
      else joined += `\\${arg}`
    }
  }

  if (joined === undefined) return '.'

  let needsReplace = true
  let slashCount = 0
  if (isWindowsPathSeparator(firstPart.charCodeAt(0))) {
    ++slashCount
    const firstLen = firstPart.length
    if (firstLen > 1 && isWindowsPathSeparator(firstPart.charCodeAt(1))) {
      ++slashCount
      if (firstLen > 2) {
        if (isWindowsPathSeparator(firstPart.charCodeAt(2))) {
          ++slashCount
        } else {
          needsReplace = false
        }
      }
    }
  }
  if (needsReplace) {
    while (slashCount < joined.length && isWindowsPathSeparator(joined.charCodeAt(slashCount))) {
      slashCount++
    }

    if (slashCount >= 2) {
      joined = `\\${joined.substring(slashCount)}`
    }
  }

  return exports.normalize(joined)
}

exports.relative = function relative(from, to) {
  if (from === to) return ''

  const fromOrig = exports.resolve(from)
  const toOrig = exports.resolve(to)

  if (fromOrig === toOrig) return ''

  from = fromOrig.toLowerCase()
  to = toOrig.toLowerCase()

  if (from === to) return ''

  let fromStart = 0
  while (fromStart < from.length && from.charCodeAt(fromStart) === CHAR_BACKWARD_SLASH) {
    fromStart++
  }
  let fromEnd = from.length
  while (fromEnd - 1 > fromStart && from.charCodeAt(fromEnd - 1) === CHAR_BACKWARD_SLASH) {
    fromEnd--
  }
  const fromLen = fromEnd - fromStart

  let toStart = 0
  while (toStart < to.length && to.charCodeAt(toStart) === CHAR_BACKWARD_SLASH) {
    toStart++
  }
  let toEnd = to.length
  while (toEnd - 1 > toStart && to.charCodeAt(toEnd - 1) === CHAR_BACKWARD_SLASH) {
    toEnd--
  }
  const toLen = toEnd - toStart

  const length = fromLen < toLen ? fromLen : toLen
  let lastCommonSep = -1
  let i = 0
  for (; i < length; i++) {
    const fromCode = from.charCodeAt(fromStart + i)
    if (fromCode !== to.charCodeAt(toStart + i)) {
      break
    } else if (fromCode === CHAR_BACKWARD_SLASH) {
      lastCommonSep = i
    }
  }

  if (i !== length) {
    if (lastCommonSep === -1) return toOrig
  } else {
    if (toLen > length) {
      if (to.charCodeAt(toStart + i) === CHAR_BACKWARD_SLASH) {
        return toOrig.substring(toStart + i + 1)
      }
      if (i === 2) {
        return toOrig.substring(toStart + i)
      }
    }
    if (fromLen > length) {
      if (from.charCodeAt(fromStart + i) === CHAR_BACKWARD_SLASH) {
        lastCommonSep = i
      } else if (i === 2) {
        lastCommonSep = 3
      }
    }
    if (lastCommonSep === -1) lastCommonSep = 0
  }

  let out = ''
  for (i = fromStart + lastCommonSep + 1; i <= fromEnd; ++i) {
    if (i === fromEnd || from.charCodeAt(i) === CHAR_BACKWARD_SLASH) {
      out += out.length === 0 ? '..' : '\\..'
    }
  }

  toStart += lastCommonSep

  if (out.length > 0) {
    return `${out}${toOrig.substring(toStart, toEnd)}`
  }
  if (toOrig.charCodeAt(toStart) === CHAR_BACKWARD_SLASH) {
    ++toStart
  }
  return toOrig.substring(toStart, toEnd)
}

exports.toNamespacedPath = function toNamespacedPath(path) {
  if (path.length === 0) return path

  const resolvedPath = exports.resolve(path)

  if (resolvedPath.length <= 2) return path

  if (resolvedPath.charCodeAt(0) === CHAR_BACKWARD_SLASH) {
    if (resolvedPath.charCodeAt(1) === CHAR_BACKWARD_SLASH) {
      const code = resolvedPath.charCodeAt(2)
      if (code !== CHAR_QUESTION_MARK && code !== CHAR_DOT) {
        return `\\\\?\\UNC\\${resolvedPath.substring(2)}`
      }
    }
  } else if (
    isWindowsDeviceRoot(resolvedPath.charCodeAt(0)) &&
    resolvedPath.charCodeAt(1) === CHAR_COLON &&
    resolvedPath.charCodeAt(2) === CHAR_BACKWARD_SLASH
  ) {
    return `\\\\?\\${resolvedPath}`
  }

  return path
}

exports.dirname = function dirname(path) {
  const len = path.length
  if (len === 0) return '.'
  let rootEnd = -1
  let offset = 0
  const code = path.charCodeAt(0)

  if (len === 1) {
    return isWindowsPathSeparator(code) ? path : '.'
  }

  if (isWindowsPathSeparator(code)) {
    rootEnd = offset = 1

    if (isWindowsPathSeparator(path.charCodeAt(1))) {
      let j = 2
      let last = j
      while (j < len && !isWindowsPathSeparator(path.charCodeAt(j))) {
        j++
      }
      if (j < len && j !== last) {
        last = j
        while (j < len && isWindowsPathSeparator(path.charCodeAt(j))) {
          j++
        }
        if (j < len && j !== last) {
          last = j
          while (j < len && !isWindowsPathSeparator(path.charCodeAt(j))) {
            j++
          }
          if (j === len) {
            return path
          }
          if (j !== last) {
            rootEnd = offset = j + 1
          }
        }
      }
    }
  } else if (isWindowsDeviceRoot(code) && path.charCodeAt(1) === CHAR_COLON) {
    rootEnd = len > 2 && isWindowsPathSeparator(path.charCodeAt(2)) ? 3 : 2
    offset = rootEnd
  }

  let end = -1
  let matchedSlash = true
  for (let i = len - 1; i >= offset; --i) {
    if (isWindowsPathSeparator(path.charCodeAt(i))) {
      if (!matchedSlash) {
        end = i
        break
      }
    } else {
      matchedSlash = false
    }
  }

  if (end === -1) {
    if (rootEnd === -1) return '.'

    end = rootEnd
  }
  return path.substring(0, end)
}

exports.basename = function basename(path, suffix) {
  let start = 0
  let end = -1
  let matchedSlash = true

  if (
    path.length >= 2 &&
    isWindowsDeviceRoot(path.charCodeAt(0)) &&
    path.charCodeAt(1) === CHAR_COLON
  ) {
    start = 2
  }

  if (suffix !== undefined && suffix.length > 0 && suffix.length <= path.length) {
    if (suffix === path) return ''
    let extIdx = suffix.length - 1
    let firstNonSlashEnd = -1
    for (let i = path.length - 1; i >= start; --i) {
      const code = path.charCodeAt(i)
      if (isWindowsPathSeparator(code)) {
        if (!matchedSlash) {
          start = i + 1
          break
        }
      } else {
        if (firstNonSlashEnd === -1) {
          matchedSlash = false
          firstNonSlashEnd = i + 1
        }
        if (extIdx >= 0) {
          if (code === suffix.charCodeAt(extIdx)) {
            if (--extIdx === -1) {
              end = i
            }
          } else {
            extIdx = -1
            end = firstNonSlashEnd
          }
        }
      }
    }

    if (start === end) end = firstNonSlashEnd
    else if (end === -1) end = path.length
    return path.substring(start, end)
  }
  for (let i = path.length - 1; i >= start; --i) {
    if (isWindowsPathSeparator(path.charCodeAt(i))) {
      if (!matchedSlash) {
        start = i + 1
        break
      }
    } else if (end === -1) {
      matchedSlash = false
      end = i + 1
    }
  }

  if (end === -1) return ''
  return path.substring(start, end)
}

exports.extname = function extname(path) {
  let start = 0
  let startDot = -1
  let startPart = 0
  let end = -1
  let matchedSlash = true
  let preDotState = 0

  if (
    path.length >= 2 &&
    path.charCodeAt(1) === CHAR_COLON &&
    isWindowsDeviceRoot(path.charCodeAt(0))
  ) {
    start = startPart = 2
  }

  for (let i = path.length - 1; i >= start; --i) {
    const code = path.charCodeAt(i)
    if (isWindowsPathSeparator(code)) {
      if (!matchedSlash) {
        startPart = i + 1
        break
      }
      continue
    }
    if (end === -1) {
      matchedSlash = false
      end = i + 1
    }
    if (code === CHAR_DOT) {
      if (startDot === -1) startDot = i
      else if (preDotState !== 1) preDotState = 1
    } else if (startDot !== -1) {
      preDotState = -1
    }
  }

  if (
    startDot === -1 ||
    end === -1 ||
    preDotState === 0 ||
    (preDotState === 1 && startDot === end - 1 && startDot === startPart + 1)
  ) {
    return ''
  }
  return path.substring(startDot, end)
}
{
  "name": "bare-path",
  "version": "3.0.1",
  "description": "Path manipulation library for JavaScript",
  "exports": {
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    },
    "./package": "./package.json",
    "./posix": "./lib/posix.js",
    "./win32": "./lib/win32.js"
  },
  "files": [
    "index.js",
    "index.d.ts",
    "lib",
    "NOTICE"
  ],
  "scripts": {
    "format": "prettier --write . && lunte --fix",
    "lint": "prettier --check . && lunte",
    "test": "brittle-bare --coverage test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-path.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-path/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-path#readme",
  "dependencies": {
    "bare-os": "^3.0.1"
  },
  "devDependencies": {
    "brittle": "^3.3.2",
    "lunte": "^1.8.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^2.0.0"
  }
}
const b4a = require('b4a')
const stream = require('streamx')
const { ReadableStream, WritableStream } = require('./web')

const defaultEncoding = 'utf8'

module.exports = exports = stream.Stream

exports.pipeline = stream.pipeline

exports.isStream = stream.isStream
exports.isEnding = stream.isEnding
exports.isEnded = stream.isEnded
exports.isFinishing = stream.isFinishing
exports.isFinished = stream.isFinished
exports.isDisturbed = stream.isDisturbed

exports.isErrored = function isErrored(stream) {
  return exports.getStreamError(stream) !== null
}

exports.isReadable = function isReadable(stream) {
  return stream.readable && !stream.destroying && !exports.isEnded(stream)
}

exports.isWritable = function isWritable(stream) {
  return stream.writable && !stream.destroying && !exports.isFinishing(stream)
}

exports.getStreamError = stream.getStreamError

exports.addAbortSignal = function addAbortSignal(signal, stream) {
  function onAbort() {
    stream.destroy(signal.reason)
  }

  if (signal.aborted) onAbort()
  else signal.addEventListener('abort', onAbort)

  return stream
}

exports.Stream = exports

exports.Readable = class Readable extends stream.Readable {
  constructor(opts = {}) {
    super({
      ...opts,
      byteLength: null,
      byteLengthReadable: null,
      map: null,
      mapReadable: null
    })

    if (this._construct) this._open = this._construct

    if (this._read !== stream.Readable.prototype._read) {
      this._read = read.bind(this, this._read)
    }

    if (this._destroy !== stream.Stream.prototype._destroy) {
      this._destroy = destroy.bind(this, this._destroy)
    }
  }

  get closed() {
    return !exports.isReadable(this)
  }

  get errored() {
    return stream.getStreamError(this)
  }

  push(chunk, encoding) {
    if (typeof chunk === 'string') {
      chunk = b4a.from(chunk, encoding || defaultEncoding)
    }

    return super.push(chunk)
  }

  unshift(chunk, encoding) {
    if (typeof chunk === 'string') {
      chunk = b4a.from(chunk, encoding || defaultEncoding)
    }

    super.unshift(chunk)
  }

  static fromWeb(readableStream, opts = {}) {
    const stream = readableStream._stream

    if (opts.encoding) stream.setEncoding(opts.encoding)
    if (opts.signal) exports.addAbortSignal(opts.signal, stream)

    return stream
  }

  static toWeb(readable, opts = {}) {
    return new ReadableStream(readable, opts.strategy)
  }

  async [Symbol.asyncDispose]() {
    if (!this.destroyed) this.destroy()

    await new Promise((resolve) => exports.finished(this, resolve))
  }
}

exports.Writable = class Writable extends stream.Writable {
  constructor(opts = {}) {
    super({
      ...opts,
      byteLength: null,
      byteLengthWritable,
      map: null,
      mapWritable: null
    })

    if (this._construct) this._open = this._construct

    if (this._write !== stream.Writable.prototype._write) {
      this._write = write.bind(this, this._write)
    }

    if (this._destroy !== stream.Stream.prototype._destroy) {
      this._destroy = destroy.bind(this, this._destroy)
    }
  }

  get closed() {
    return !exports.isWritable(this)
  }

  get errored() {
    return stream.getStreamError(this)
  }

  write(chunk, encoding, cb) {
    if (typeof encoding === 'function') {
      cb = encoding
      encoding = null
    }

    if (typeof chunk === 'string') {
      encoding = encoding || defaultEncoding
      chunk = b4a.from(chunk, encoding)
    } else {
      encoding = 'buffer'
    }

    const result = super.write({ chunk, encoding })

    if (cb) stream.Writable.drained(this).then(() => cb(null), cb)

    return result
  }

  end(chunk, encoding, cb) {
    if (typeof chunk === 'function') {
      cb = chunk
      chunk = null
    } else if (typeof encoding === 'function') {
      cb = encoding
      encoding = null
    }

    if (typeof chunk === 'string') {
      encoding = encoding || defaultEncoding
      chunk = b4a.from(chunk, encoding || defaultEncoding)
    } else {
      encoding = 'buffer'
    }

    const result =
      chunk !== undefined && chunk !== null ? super.end({ chunk, encoding }) : super.end()

    if (cb) this.once('finish', () => cb(null))

    return result
  }

  static fromWeb(writableStream, opts = {}) {
    const stream = writableStream._stream

    if (opts.signal) exports.addAbortSignal(opts.signal, stream)

    return stream
  }

  static toWeb(writable) {
    return new WritableStream(writable)
  }

  async [Symbol.asyncDispose]() {
    if (!this.destroyed) this.destroy()

    await new Promise((resolve) => exports.finished(this, resolve))
  }
}

exports.Duplex = class Duplex extends stream.Duplex {
  constructor(opts = {}) {
    super({
      ...opts,
      byteLength: null,
      byteLengthReadable: null,
      byteLengthWritable,
      map: null,
      mapReadable: null,
      mapWritable: null
    })

    if (this._construct) this._open = this._construct

    if (this._read !== stream.Readable.prototype._read) {
      this._read = read.bind(this, this._read)
    }

    if (this._write !== stream.Duplex.prototype._write) {
      this._write = write.bind(this, this._write)
    }

    if (this._destroy !== stream.Stream.prototype._destroy) {
      this._destroy = destroy.bind(this, this._destroy)
    }
  }

  push(chunk, encoding) {
    if (typeof chunk === 'string') {
      chunk = b4a.from(chunk, encoding || defaultEncoding)
    }

    return super.push(chunk)
  }

  unshift(chunk, encoding) {
    if (typeof chunk === 'string') {
      chunk = b4a.from(chunk, encoding || defaultEncoding)
    }

    super.unshift(chunk)
  }

  write(chunk, encoding, cb) {
    if (typeof encoding === 'function') {
      cb = encoding
      encoding = null
    }

    if (typeof chunk === 'string') {
      encoding = encoding || defaultEncoding
      chunk = b4a.from(chunk, encoding)
    } else {
      encoding = 'buffer'
    }

    const result = super.write({ chunk, encoding })

    if (cb) stream.Writable.drained(this).then(() => cb(null), cb)

    return result
  }

  end(chunk, encoding, cb) {
    if (typeof chunk === 'function') {
      cb = chunk
      chunk = null
    } else if (typeof encoding === 'function') {
      cb = encoding
      encoding = null
    }

    if (typeof chunk === 'string') {
      encoding = encoding || defaultEncoding
      chunk = b4a.from(chunk, encoding)
    } else {
      encoding = 'buffer'
    }

    const result =
      chunk !== undefined && chunk !== null ? super.end({ chunk, encoding }) : super.end()

    if (cb) this.once('finish', () => cb(null))

    return result
  }

  static fromWeb({ readable: readableStream, writable: writableStream }, opts) {
    const readable = exports.Readable.fromWeb(readableStream, opts)
    const writable = exports.Writable.fromWeb(writableStream, opts)

    const duplex = new exports.Duplex({
      write(data, encoding, cb) {
        writable.write(data, encoding, cb)
      }
    })

    readable
      .on('data', (data) => duplex.push(data))
      .on('end', () => duplex.push(null))
      .on('error', (err) => duplex.destroy(err))

    writable.on('finish', () => duplex.end()).on('error', (err) => duplex.destroy(err))

    return duplex
  }

  static toWeb(duplex) {
    const readableStream = exports.Readable.toWeb(duplex)
    const writableStream = exports.Writable.toWeb(duplex)

    return { readable: readableStream, writable: writableStream }
  }
}

class DuplexSide extends exports.Duplex {
  constructor(opts) {
    super(opts)

    this._otherSide = null
    this._cb = null
  }

  _read() {
    const cb = this._cb
    if (!cb) return

    this._cb = null
    cb()
  }

  _write(chunk, encoding, cb) {
    this._otherSide.push(chunk, encoding)
    this._otherSide._cb = cb
  }

  _final(cb) {
    this._otherSide.on('end', cb)
    this._otherSide.push(null)
  }
}

exports.duplexPair = function duplexPair(opts) {
  const sideA = new DuplexSide(opts)
  const sideB = new DuplexSide(opts)

  sideA._otherSide = sideB
  sideB._otherSide = sideA

  return [sideA, sideB]
}

exports.Transform = class Transform extends stream.Transform {
  constructor(opts = {}) {
    super({
      ...opts,
      byteLength: null,
      byteLengthReadable: null,
      byteLengthWritable,
      map: null,
      mapReadable: null,
      mapWritable: null
    })

    if (this._transform !== stream.Transform.prototype._transform) {
      this._transform = transform.bind(this, this._transform)
    } else {
      this._transform = passthrough
    }
  }

  push(chunk, encoding) {
    if (typeof chunk === 'string') {
      chunk = b4a.from(chunk, encoding || defaultEncoding)
    }

    return super.push(chunk)
  }

  unshift(chunk, encoding) {
    if (typeof chunk === 'string') {
      chunk = b4a.from(chunk, encoding || defaultEncoding)
    }

    super.unshift(chunk)
  }

  write(chunk, encoding, cb) {
    if (typeof encoding === 'function') {
      cb = encoding
      encoding = null
    }

    if (typeof chunk === 'string') {
      encoding = encoding || defaultEncoding
      chunk = b4a.from(chunk, encoding)
    } else {
      encoding = 'buffer'
    }

    const result = super.write({ chunk, encoding })

    if (cb) stream.Writable.drained(this).then(() => cb(null), cb)

    return result
  }

  end(chunk, encoding, cb) {
    if (typeof chunk === 'function') {
      cb = chunk
      chunk = null
    } else if (typeof encoding === 'function') {
      cb = encoding
      encoding = null
    }

    if (typeof chunk === 'string') {
      encoding = encoding || defaultEncoding
      chunk = b4a.from(chunk, encoding)
    } else {
      encoding = 'buffer'
    }

    const result =
      chunk !== undefined && chunk !== null ? super.end({ chunk, encoding }) : super.end()

    if (cb) this.once('finish', () => cb(null))

    return result
  }
}

exports.PassThrough = class PassThrough extends exports.Transform {}

exports.finished = function finished(stream, opts, cb) {
  if (typeof opts === 'function') {
    cb = opts
    opts = {}
  }

  if (!opts) opts = {}

  const { cleanup = false } = opts

  const done = () => {
    cb(exports.getStreamError(stream, { all: true }))

    if (cleanup) detach()
  }

  const detach = () => {
    stream.off('close', done)
    stream.off('error', noop)
  }

  if (stream.destroyed) {
    done()
  } else {
    stream.on('close', done)
    stream.on('error', noop)
  }

  return detach
}

function read(read, cb) {
  read.call(this, 65536)

  cb(null)
}

function write(write, data, cb) {
  write.call(this, data.chunk, data.encoding, cb)
}

function transform(transform, data, cb) {
  transform.call(this, data.chunk, data.encoding, cb)
}

function destroy(destroy, cb) {
  destroy.call(this, exports.getStreamError(this), cb)
}

function passthrough(data, cb) {
  cb(null, data.chunk)
}

function byteLengthWritable(data) {
  return data.chunk.byteLength
}

function noop() {}
{
  "name": "bare-stream",
  "version": "2.13.3",
  "description": "Streaming data for JavaScript",
  "exports": {
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    },
    "./package": "./package.json",
    "./promises": "./promises.js",
    "./web": {
      "types": "./web.d.ts",
      "default": "./web.js"
    },
    "./global": {
      "types": "./global.d.ts",
      "default": "./global.js"
    }
  },
  "files": [
    "index.js",
    "index.d.ts",
    "promises.js",
    "web.js",
    "web.d.ts",
    "global.js",
    "global.d.ts"
  ],
  "scripts": {
    "format": "prettier --write . && lunte --fix",
    "lint": "prettier --check . && lunte",
    "test": "brittle-bare --coverage test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-stream.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-stream/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-stream#readme",
  "dependencies": {
    "b4a": "^1.8.1",
    "streamx": "^2.25.0",
    "teex": "^1.0.1"
  },
  "devDependencies": {
    "bare-abort-controller": "^1.1.0",
    "bare-buffer": "^3.0.0",
    "bare-events": "^2.5.4",
    "brittle": "^3.5.2",
    "lunte": "^1.6.0",
    "prettier": "^3.3.3",
    "prettier-config-holepunch": "^2.0.0"
  },
  "peerDependencies": {
    "bare-abort-controller": "*",
    "bare-buffer": "*",
    "bare-events": "*"
  },
  "peerDependenciesMeta": {
    "bare-abort-controller": {
      "optional": true
    },
    "bare-buffer": {
      "optional": true
    },
    "bare-events": {
      "optional": true
    }
  }
}
const { Readable, Writable, Transform, getStreamError, isStreamx, isDisturbed } = require('streamx')
const tee = require('teex')

const readableKind = Symbol.for('bare.stream.readable.kind')
const writableKind = Symbol.for('bare.stream.writable.kind')
const transformKind = Symbol.for('bare.stream.transform.kind')

// https://streams.spec.whatwg.org/#readablestreamdefaultreader
exports.ReadableStreamDefaultReader = class ReadableStreamDefaultReader {
  constructor(stream) {
    this._stream = stream
    this._stream._stream.once('close', onclose).once('error', onerror)

    const closed = Promise.withResolvers()

    // Avoid unhandled exceptions
    closed.promise.catch(noop)

    this._closed = closed

    function onclose() {
      closed.resolve()
    }

    function onerror(err) {
      closed.reject(err)
    }
  }

  get closed() {
    return this._closed.promise
  }

  read() {
    const stream = this._stream._stream

    return new Promise((resolve, reject) => {
      const err = getStreamError(stream)

      if (err) return reject(err)

      if (stream.destroyed) {
        return resolve({ value: undefined, done: true })
      }

      const value = stream.read()

      if (value !== null) {
        return resolve({ value, done: false })
      }

      stream.once('readable', onreadable).once('close', onclose).once('error', onerror)

      function onreadable() {
        const value = stream.read()

        ondone(null, value === null ? { value: undefined, done: true } : { value, done: false })
      }

      function onclose() {
        ondone(null, { value: undefined, done: true })
      }

      function onerror(err) {
        ondone(err, null)
      }

      function ondone(err, value) {
        stream.off('readable', onreadable).off('close', onclose).off('error', onerror)

        if (err) reject(err)
        else resolve(value)
      }
    })
  }

  releaseLock() {
    this._closed.reject(new TypeError('Reader was released'))
    this._stream._releaseLock()
    this._stream = null
  }

  cancel(reason = new TypeError('Stream was cancelled')) {
    const stream = this._stream._stream

    if (stream.destroyed) return Promise.resolve()

    return new Promise((resolve) =>
      stream.once('close', resolve).once('error', noop).destroy(reason)
    )
  }
}

// https://streams.spec.whatwg.org/#readablestreamdefaultcontroller
exports.ReadableStreamDefaultController = class ReadableStreamDefaultController {
  constructor(stream) {
    this._stream = stream
  }

  get desiredSize() {
    const stream = this._stream._stream

    return stream._readableState.highWaterMark - stream._readableState.buffered
  }

  enqueue(data) {
    this._stream._stream.push(data)
  }

  close() {
    this._stream._stream.push(null)
  }

  error(err) {
    this._stream._stream.destroy(err)
  }
}

// https://streams.spec.whatwg.org/#readablestream
class ReadableStream {
  static get [readableKind]() {
    return 0 // Compatibility version
  }

  static from(iterable) {
    return new ReadableStream(Readable.from(iterable))
  }

  constructor(underlyingSource = {}, queuingStrategy) {
    if (isStreamx(underlyingSource)) {
      this._stream = underlyingSource
    } else {
      if (queuingStrategy === undefined) {
        queuingStrategy = new exports.CountQueuingStrategy()
      }

      const { start, pull, cancel } = underlyingSource
      const { highWaterMark = 1, size = defaultSize } = queuingStrategy

      this._stream = new Readable({ highWaterMark, byteLength: size })

      const controller = new exports.ReadableStreamDefaultController(this)

      try {
        let starting = Promise.resolve()

        if (start) starting = forwardError(start.call(this, controller), controller)

        if (pull) {
          this._stream._read = this._read.bind(this, starting, pull.bind(this, controller))
        }

        if (cancel) {
          this._stream.once('error', cancel.bind(this))
        }
      } catch (err) {
        controller.error(err)
      }
    }

    this._reader = null
  }

  get [readableKind]() {
    return ReadableStream[readableKind]
  }

  get locked() {
    return this._reader !== null
  }

  getReader() {
    if (this.locked) throw new TypeError('Stream is locked')

    this._reader = new exports.ReadableStreamDefaultReader(this)

    return this._reader
  }

  cancel(reason = new TypeError('Stream was cancelled')) {
    const stream = this._stream

    if (stream.destroyed) return Promise.resolve()

    if (this.locked) return Promise.reject(new TypeError('Stream is locked'))

    return new Promise((resolve) =>
      stream.once('close', resolve).once('error', noop).destroy(reason)
    )
  }

  tee() {
    const [a, b] = tee(this._stream)

    return [new ReadableStream(a), new ReadableStream(b)]
  }

  pipeTo(destination) {
    return new Promise((resolve, reject) =>
      this._stream.pipe(destination._stream, (err) => {
        err ? reject(err) : resolve()
      })
    )
  }

  [Symbol.asyncIterator]() {
    return this._stream[Symbol.asyncIterator]()
  }

  _releaseLock() {
    this._reader = null
  }

  async _read(starting, pull, cb) {
    await starting

    let err = null
    try {
      await pull()
    } catch (e) {
      err = e
    }
    cb(err)
  }
}

function defaultSize() {
  return 1
}

exports.ReadableStream = ReadableStream

// https://streams.spec.whatwg.org/#countqueuingstrategy
exports.CountQueuingStrategy = class CountQueuingStrategy {
  constructor(opts = {}) {
    const { highWaterMark = 1 } = opts

    this.highWaterMark = highWaterMark
  }

  size(chunk) {
    return 1
  }
}

// https://streams.spec.whatwg.org/#bytelengthqueuingstrategy
exports.ByteLengthQueuingStrategy = class ByteLengthQueuingStrategy {
  constructor(opts = {}) {
    const { highWaterMark = 16384 } = opts

    this.highWaterMark = highWaterMark
  }

  size(chunk) {
    return chunk.byteLength
  }
}

exports.isReadableStream = function isReadableStream(value) {
  if (value instanceof ReadableStream) return true

  return (
    typeof value === 'object' &&
    value !== null &&
    value[readableKind] === ReadableStream[readableKind]
  )
}

// https://streams.spec.whatwg.org/#readablestream-errored
exports.isReadableStreamErrored = function isReadableStreamErrored(stream) {
  return getStreamError(stream._stream) !== null
}

// https://streams.spec.whatwg.org/#is-readable-stream-disturbed
exports.isReadableStreamDisturbed = function isReadableStreamDisturbed(stream) {
  return isDisturbed(stream._stream)
}

// https://streams.spec.whatwg.org/#writablestreamdefaultwriter
exports.WritableStreamDefaultWriter = class WritableStreamDefaultWriter {
  constructor(stream) {
    this._stream = stream
    this._stream._stream.once('close', onclose).once('error', onerror)

    const closed = Promise.withResolvers()

    // Avoid unhandled exceptions
    closed.promise.catch(noop)

    this._closed = closed

    function onclose() {
      closed.resolve()
    }

    function onerror(err) {
      closed.reject(err)
    }
  }

  get desiredSize() {
    const stream = this._stream._stream

    return stream._writableState.highWaterMark - stream._writableState.buffered
  }

  get closed() {
    return this._closed.promise
  }

  get ready() {
    const stream = this._stream._stream

    if (getStreamError(stream)) return Promise.reject()

    return Writable.drained(stream).then()
  }

  async write(chunk) {
    const stream = this._stream._stream

    let err = getStreamError(stream)
    if (err) return Promise.reject(err)

    stream.write(chunk)

    await Writable.drained(stream)

    err = getStreamError(stream)
    if (err) return Promise.reject(err)
  }

  releaseLock() {
    this._closed.reject(new TypeError('Writer was released'))
    this._stream._releaseLock()
    this._stream = null
  }

  close() {
    const stream = this._stream._stream

    if (stream.destroyed) return Promise.resolve()

    return new Promise((resolve) => stream.once('close', resolve).end())
  }

  abort(reason = new TypeError('Stream was aborted')) {
    const stream = this._stream._stream

    if (stream.destroyed) return Promise.resolve()

    return new Promise((resolve) => stream.once('close', resolve).destroy(reason))
  }
}

// https://streams.spec.whatwg.org/#writablestreamdefaultcontroller
exports.WritableStreamDefaultController = class WritableStreamDefaultController {
  constructor(stream) {
    this._stream = stream
  }

  error(err) {
    this._stream._stream.destroy(err)
  }
}

// https://streams.spec.whatwg.org/#writablestream
class WritableStream {
  static get [writableKind]() {
    return 0 // Compatibility version
  }

  constructor(underlyingSink = {}, queuingStrategy = {}) {
    if (isStreamx(underlyingSink)) {
      this._stream = underlyingSink
    } else {
      if (queuingStrategy === undefined) {
        queuingStrategy = new exports.CountQueuingStrategy()
      }

      const { start, write, close, abort } = underlyingSink
      const { highWaterMark = 1, size = defaultSize } = queuingStrategy

      this._stream = new Writable({ highWaterMark, byteLength: size })

      const controller = new exports.WritableStreamDefaultController(this)

      this._controller = controller

      try {
        let starting = Promise.resolve()

        if (start) starting = forwardError(start.call(this, controller), controller)

        if (write) {
          this._stream._write = this._write.bind(this, starting, write.bind(this))
        }

        if (close) {
          this._stream._destroy = this._destroy.bind(this, close.call(this))
        }

        if (abort) {
          this._stream.once('error', abort.bind(this))
        }
      } catch (err) {
        controller.error(err)
      }
    }

    this._writer = null
  }

  get [writableKind]() {
    return WritableStream[writableKind]
  }

  get locked() {
    return this._writer !== null
  }

  getWriter() {
    if (this.locked) throw new TypeError('Stream is locked')

    this._writer = new exports.WritableStreamDefaultWriter(this)

    return this._writer
  }

  abort(reason = new TypeError('Stream was aborted')) {
    if (this._stream.destroyed) return Promise.resolve()

    if (this.locked) return Promise.reject(new TypeError('Stream is locked'))

    return new Promise((resolve) => this._stream.once('close', resolve).destroy(reason))
  }

  close() {
    if (this._stream.destroyed) return Promise.resolve()

    if (this.locked) return Promise.reject(new TypeError('Stream is locked'))

    return new Promise((resolve) => this._stream.once('close', resolve).end())
  }

  _releaseLock() {
    this._writer = null
  }

  async _write(starting, write, data, cb) {
    await starting

    let err = null
    try {
      await write(data, this._controller)
    } catch (e) {
      err = e
    }
    cb(err)
  }

  async _destroy(closing, cb) {
    let err = null
    try {
      await closing
    } catch (e) {
      err = e
    }
    cb(err)
  }
}

exports.WritableStream = WritableStream

exports.isWritableStream = function isWritableStream(value) {
  if (value instanceof WritableStream) return true

  return (
    typeof value === 'object' &&
    value !== null &&
    value[writableKind] === WritableStream[writableKind]
  )
}

// https://streams.spec.whatwg.org/#transformstreamdefaultcontroller
exports.TransformStreamDefaultController = class TransformStreamDefaultController {
  constructor(stream) {
    this._stream = stream
  }

  get desiredSize() {
    const stream = this._stream._stream

    return stream._readableState.highWaterMark - stream._readableState.buffered
  }

  enqueue(data) {
    this._stream._stream.push(data)
  }

  error(err) {
    this._stream._stream.destroy(err)
  }

  terminate() {
    const stream = this._stream._stream

    stream.push(null)
    stream.destroy(new TypeError('Stream has been terminated'))
  }
}

// https://streams.spec.whatwg.org/#transformstream
class TransformStream {
  static get [transformKind]() {
    return 0 // Compatibility version
  }

  constructor(transformer = {}, writableStrategy = {}, readableStrategy = {}) {
    if (isStreamx(transformer)) {
      this._stream = transformer
    } else {
      const { start, transform, flush } = transformer

      this._stream = new Transform({ ...writableStrategy, ...readableStrategy })

      const controller = new exports.TransformStreamDefaultController(this)

      this._controller = controller

      try {
        let starting = Promise.resolve()

        if (start) starting = forwardError(start.call(this, controller), controller)

        if (transform) {
          this._stream._transform = this._transform.bind(this, starting, transform.bind(this))
        }

        if (flush) {
          this._stream._flush = this._flush.bind(this, flush.call(this, this._controller))
        }
      } catch (err) {
        controller.error(err)
      }
    }

    this._writable = new WritableStream(this._stream)
    this._readable = new ReadableStream(this._stream)
  }

  get [transformKind]() {
    return TransformStream[transformKind]
  }

  get writable() {
    return this._writable
  }

  get readable() {
    return this._readable
  }

  async _transform(starting, transform, data, cb) {
    await starting

    let err = null
    try {
      await transform(data, this._controller)
    } catch (e) {
      err = e
    }
    cb(err)
  }

  async _flush(flush, cb) {
    let err = null
    try {
      await flush
    } catch (e) {
      err = e
    }
    cb(err)
  }
}

exports.TransformStream = TransformStream

exports.isTransformStream = function isTransformStream(value) {
  if (value instanceof TransformStream) return true

  return (
    typeof value === 'object' &&
    value !== null &&
    value[transformKind] === TransformStream[transformKind]
  )
}

async function forwardError(promise, controller) {
  try {
    await promise
  } catch (err) {
    controller.error(err)
  }
}

function noop() {}
module.exports = require.addon()
const binding = require('./binding')

const t = binding.constants

class Type {
  constructor(type) {
    this._type = type
  }

  isUndefined() {
    return this._type === t.UNDEFINED
  }

  isNull() {
    return this._type === t.NULL
  }

  isBoolean() {
    return this._type === t.BOOLEAN
  }

  isNumber() {
    return (this._type & 0xff) === t.NUMBER
  }

  isInt32() {
    return (this._type & (0xff | t.INT32)) === (t.NUMBER | t.INT32)
  }

  isUint32() {
    return (this._type & (0xff | t.UINT32)) === (t.NUMBER | t.UINT32)
  }

  isString() {
    return this._type === t.STRING
  }

  isSymbol() {
    return this._type === t.SYMBOL
  }

  isObject() {
    return (this._type & 0xff) === t.OBJECT
  }

  isArray() {
    return this._type === (t.OBJECT | t.ARRAY)
  }

  isArguments() {
    return this._type === (t.OBJECT | t.ARGUMENTS)
  }

  isDate() {
    return this._type === (t.OBJECT | t.DATE)
  }

  isRegExp() {
    return this._type === (t.OBJECT | t.REGEXP)
  }

  isError() {
    return this._type === (t.OBJECT | t.ERROR)
  }

  isPromise() {
    return this._type === (t.OBJECT | t.PROMISE)
  }

  isProxy() {
    return this._type === (t.OBJECT | t.PROXY)
  }

  isGenerator() {
    return this._type === (t.OBJECT | t.GENERATOR)
  }

  isMap() {
    return this._type === (t.OBJECT | t.MAP)
  }

  isSet() {
    return this._type === (t.OBJECT | t.SET)
  }

  isWeakMap() {
    return this._type === (t.OBJECT | t.WEAK_MAP)
  }

  isWeakSet() {
    return this._type === (t.OBJECT | t.WEAK_SET)
  }

  isWeakRef() {
    return this._type === (t.OBJECT | t.WEAK_REF)
  }

  isArrayBuffer() {
    return this._type === (t.OBJECT | t.ARRAYBUFFER)
  }

  isSharedArrayBuffer() {
    return this._type === (t.OBJECT | t.SHAREDARRAYBUFFER)
  }

  isTypedArray() {
    return (this._type & 0xffff) === (t.OBJECT | t.TYPEDARRAY)
  }

  isInt8Array() {
    return this._type === (t.OBJECT | t.TYPEDARRAY | t.INT8ARRAY)
  }

  isUint8Array() {
    return this._type === (t.OBJECT | t.TYPEDARRAY | t.UINT8ARRAY)
  }

  isUint8ClampedArray() {
    return this._type === (t.OBJECT | t.TYPEDARRAY | t.UINT8CLAMPEDARRAY)
  }

  isInt16Array() {
    return this._type === (t.OBJECT | t.TYPEDARRAY | t.INT16ARRAY)
  }

  isUint16Array() {
    return this._type === (t.OBJECT | t.TYPEDARRAY | t.UINT16ARRAY)
  }

  isInt32Array() {
    return this._type === (t.OBJECT | t.TYPEDARRAY | t.INT32ARRAY)
  }

  isUint32Array() {
    return this._type === (t.OBJECT | t.TYPEDARRAY | t.UINT32ARRAY)
  }

  isFloat16Array() {
    return this._type === (t.OBJECT | t.TYPEDARRAY | t.FLOAT16ARRAY)
  }

  isFloat32Array() {
    return this._type === (t.OBJECT | t.TYPEDARRAY | t.FLOAT32ARRAY)
  }

  isFloat64Array() {
    return this._type === (t.OBJECT | t.TYPEDARRAY | t.FLOAT64ARRAY)
  }

  isBigInt64Array() {
    return this._type === (t.OBJECT | t.TYPEDARRAY | t.BIGINT64ARRAY)
  }

  isBigUint64Array() {
    return this._type === (t.OBJECT | t.TYPEDARRAY | t.BIGUINT64ARRAY)
  }

  isDataView() {
    return this._type === (t.OBJECT | t.DATAVIEW)
  }

  isModuleNamespace() {
    return this._type === (t.OBJECT | t.MODULE_NAMESPACE)
  }

  isFunction() {
    return (this._type & 0xff) === t.FUNCTION
  }

  isAsyncFunction() {
    return (
      (this._type & (0xff | t.ASYNC_FUNCTION)) ===
      (t.FUNCTION | t.ASYNC_FUNCTION)
    )
  }

  isGeneratorFunction() {
    return (
      (this._type & (0xff | t.GENERATOR_FUNCTION)) ===
      (t.FUNCTION | t.GENERATOR_FUNCTION)
    )
  }

  isExternal() {
    return this._type === t.EXTERNAL
  }

  isBigInt() {
    return this._type === t.BIGINT
  }
}

module.exports = exports = function type(value) {
  switch (typeof value) {
    case 'undefined':
      return new Type(t.UNDEFINED)
    case 'boolean':
      return new Type(t.BOOLEAN)
    case 'number':
      return new Type(
        Number.isSafeInteger(value) ? binding.type(value) : t.NUMBER
      )
    case 'string':
      return new Type(t.STRING)
    case 'symbol':
      return new Type(t.SYMBOL)
    case 'object':
      return new Type(value === null ? t.NULL : binding.type(value))
    case 'function':
      return new Type(binding.type(value))
    case 'bigint':
      return new Type(t.BIGINT)
  }
}

exports.createTag = function createTag(...components) {
  const tag = new Uint32Array(4)
  for (let i = 0; i < 4; i++) tag[i] = components[i] || 0
  return tag
}

exports.addTag = function addTag(object, tag) {
  binding.addTag(object, tag)
}

exports.checkTag = function checkTag(object, tag) {
  return binding.checkTag(object, tag)
}
{
  "name": "bare-type",
  "version": "1.1.0",
  "description": "Cross-realm type predicates for Bare",
  "exports": {
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    },
    "./package": "./package.json"
  },
  "files": [
    "index.js",
    "index.d.ts",
    "binding.c",
    "binding.js",
    "CMakeLists.txt",
    "prebuilds"
  ],
  "addon": true,
  "scripts": {
    "test": "prettier . --check && bare test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-type.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-type/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-type#readme",
  "engines": {
    "bare": ">=1.2.0"
  },
  "devDependencies": {
    "brittle": "^3.4.0",
    "cmake-bare": "^1.1.6",
    "prettier": "^3.4.1",
    "prettier-config-standard": "^7.0.0"
  }
}
module.exports = require.addon()
const path = require('bare-path')
const binding = require('./binding')
const errors = require('./lib/errors')
const URLSearchParams = require('./lib/url-search-params')

const kind = Symbol.for('bare.url.kind')

const isWindows = Bare.platform === 'win32'

class URL {
  static get [kind]() {
    return 0 // Compatibility version
  }

  constructor(input, base, opts = {}) {
    if (arguments.length === 0) throw errors.INVALID_URL()

    input = String(input)

    if (base !== undefined) base = String(base)

    this._components = new Uint32Array(8)

    this._parse(input, base, opts.throw !== false)

    if (this._href) this._params = new URLSearchParams(this.search, this)
  }

  get [kind]() {
    return URL[kind]
  }

  // https://url.spec.whatwg.org/#dom-url-href

  get href() {
    return this._href
  }

  set href(value) {
    this._update(value)

    this._params._parse(this.search)
  }

  // https://url.spec.whatwg.org/#dom-url-protocol

  get protocol() {
    return this._slice(0, this._components[0]) + ':'
  }

  set protocol(value) {
    this._update(this._replace(value.replace(/:+$/, ''), 0, this._components[0]))
  }

  // https://url.spec.whatwg.org/#dom-url-username

  get username() {
    return this._slice(this._components[0] + 3 /* :// */, this._components[1])
  }

  set username(value) {
    if (cannotHaveCredentialsOrPort(this)) {
      return
    }

    if (this.username === '') value += '@'

    this._update(this._replace(value, this._components[0] + 3 /* :// */, this._components[1]))
  }

  // https://url.spec.whatwg.org/#dom-url-password

  get password() {
    return this._href.slice(this._components[1] + 1 /* : */, this._components[2] - 1 /* @ */)
  }

  set password(value) {
    if (cannotHaveCredentialsOrPort(this)) {
      return
    }

    let start = this._components[1] + 1 /* : */
    let end = this._components[2] - 1 /* @ */

    if (this.password === '') {
      value = ':' + value
      start--
    }

    if (this.username === '') {
      value += '@'
      end++
    }

    this._update(this._replace(value, start, end))
  }

  // https://url.spec.whatwg.org/#dom-url-host

  get host() {
    return this._slice(this._components[2], this._components[5])
  }

  set host(value) {
    if (hasOpaquePath(this)) {
      return
    }

    this._update(
      this._replace(value, this._components[2], this._components[value.includes(':') ? 5 : 3])
    )
  }

  // https://url.spec.whatwg.org/#dom-url-hostname

  get hostname() {
    return this._slice(this._components[2], this._components[3])
  }

  set hostname(value) {
    if (hasOpaquePath(this)) {
      return
    }

    this._update(this._replace(value, this._components[2], this._components[3]))
  }

  // https://url.spec.whatwg.org/#dom-url-port

  get port() {
    return this._slice(this._components[3] + 1 /* : */, this._components[5])
  }

  set port(value) {
    if (cannotHaveCredentialsOrPort(this)) {
      return
    }

    let start = this._components[3] + 1 /* : */

    if (this.port === '') {
      value = ':' + value
      start--
    }

    this._update(this._replace(value, start, this._components[5]))
  }

  // https://url.spec.whatwg.org/#dom-url-pathname

  get pathname() {
    return this._slice(this._components[5], this._components[6] - 1 /* ? */)
  }

  set pathname(value) {
    if (hasOpaquePath(this)) {
      return
    }

    if (value[0] !== '/' && value[0] !== '\\') {
      value = '/' + value
    }

    this._update(this._replace(value, this._components[5], this._components[6] - 1 /* ? */))
  }

  // https://url.spec.whatwg.org/#dom-url-search

  get search() {
    return this._slice(this._components[6] - 1 /* ? */, this._components[7] - 1 /* # */)
  }

  set search(value) {
    if (value && value[0] !== '?') value = '?' + value

    this._update(
      this._replace(value, this._components[6] - 1 /* ? */, this._components[7] - 1 /* # */)
    )

    this._params._parse(this.search)
  }

  // https://url.spec.whatwg.org/#dom-url-searchparams

  get searchParams() {
    return this._params
  }

  // https://url.spec.whatwg.org/#dom-url-hash

  get hash() {
    return this._slice(this._components[7] - 1 /* # */)
  }

  set hash(value) {
    if (value && value[0] !== '#') value = '#' + value

    this._update(this._replace(value, this._components[7] - 1 /* # */))
  }

  toString() {
    return this._href
  }

  toJSON() {
    return this._href
  }

  [Symbol.for('bare.inspect')]() {
    return {
      __proto__: { constructor: URL },

      href: this.href,
      protocol: this.protocol,
      username: this.username,
      password: this.password,
      host: this.host,
      hostname: this.hostname,
      port: this.port,
      pathname: this.pathname,
      search: this.search,
      searchParams: this.searchParams,
      hash: this.hash
    }
  }

  _slice(start, end = this._href.length) {
    return this._href.slice(start, end)
  }

  _replace(replacement, start, end = this._href.length) {
    return this._slice(0, start) + replacement + this._slice(end)
  }

  _parse(input, base, shouldThrow) {
    try {
      this._href = binding.parse(
        String(input),
        base ? String(base) : null,
        this._components,
        shouldThrow
      )
    } catch (err) {
      if (err instanceof TypeError) throw err

      throw errors.INVALID_URL(`Invalid URL '${input}'`, input)
    }
  }

  _update(input) {
    try {
      this._parse(input, null, true)
    } catch (err) {
      if (err instanceof TypeError) throw err
    }
  }
}

module.exports = exports = URL

// https://url.spec.whatwg.org/#url-opaque-path
function hasOpaquePath(url) {
  return url.pathname[0] !== '/'
}

// https://url.spec.whatwg.org/#cannot-have-a-username-password-port
function cannotHaveCredentialsOrPort(url) {
  return url.hostname === '' || url.protocol === 'file:'
}

exports.URL = URL
exports.URLSearchParams = URLSearchParams

exports.errors = errors

exports.isURL = function isURL(value) {
  if (value instanceof URL) return true

  return typeof value === 'object' && value !== null && value[kind] === URL[kind]
}

exports.isURLSearchParams = URLSearchParams.isURLSearchParams

// https://url.spec.whatwg.org/#dom-url-parse
exports.parse = function parse(input, base) {
  const url = new URL(input, base, { throw: false })
  return url._href ? url : null
}

// https://url.spec.whatwg.org/#dom-url-canparse
exports.canParse = function canParse(input, base) {
  return binding.canParse(String(input), base ? String(base) : null)
}

exports.fileURLToPath = function fileURLToPath(url) {
  if (typeof url === 'string') {
    url = new URL(url)
  }

  if (url.protocol !== 'file:') {
    throw errors.INVALID_URL_SCHEME('The URL must use the file: protocol')
  }

  if (isWindows) {
    if (/%2f|%5c/i.test(url.pathname)) {
      throw errors.INVALID_FILE_URL_PATH(
        'The file: URL path must not include encoded \\ or / characters'
      )
    }
  } else {
    if (url.hostname) {
      throw errors.INVALID_FILE_URL_HOST("The file: URL host must be 'localhost' or empty")
    }

    if (/%2f/i.test(url.pathname)) {
      throw errors.INVALID_FILE_URL_PATH('The file: URL path must not include encoded / characters')
    }
  }

  if (/%00/i.test(url.pathname)) {
    throw errors.INVALID_FILE_URL_PATH('The file: URL path must not include encoded NUL characters')
  }

  const pathname = path.normalize(decodeURIComponent(url.pathname))

  if (isWindows) {
    if (url.hostname) return '\\\\' + url.hostname + pathname

    const letter = pathname.charCodeAt(1) | 0x20

    if (letter < 0x61 /* a */ || letter > 0x7a /* z */ || pathname.charCodeAt(2) !== 0x3a /* : */) {
      throw errors.INVALID_FILE_URL_PATH('The file: URL path must be absolute')
    }

    return pathname.slice(1)
  }

  return pathname
}

exports.pathToFileURL = function pathToFileURL(pathname) {
  let resolved = path.resolve(pathname)

  if (pathname[pathname.length - 1] === '/') {
    resolved += '/'
  } else if (isWindows && pathname[pathname.length - 1] === '\\') {
    resolved += '\\'
  }

  resolved = resolved
    .replaceAll('%', '%25') // Must be first
    .replaceAll('#', '%23')
    .replaceAll('?', '%3f')
    .replaceAll('\n', '%0a')
    .replaceAll('\r', '%0d')
    .replaceAll('\t', '%09')

  if (!isWindows) {
    resolved = resolved.replaceAll('\\', '%5c')
  }

  return new URL('file:' + resolved)
}

exports.format = function format(parts) {
  const { protocol, auth, host, hostname, port, pathname, search, query, hash, slashes } = parts

  let result = ''

  if (typeof protocol === 'string') {
    result += protocol

    if (protocol[protocol.length - 1] !== ':') {
      result += ':'
    }

    if (slashes === true || /https?|ftp|gopher|file/.test(protocol)) {
      result += '//'
    }
  }

  if (typeof auth === 'string') {
    if (host || hostname) result += auth + '@'
  }

  if (typeof host === 'string') result += host
  else {
    result += hostname

    if (port) result += ':' + port
  }

  if (typeof pathname === 'string' && pathname !== '') {
    if (pathname[0] !== '/') result += '/'
    result += pathname
  }

  if (typeof search === 'string') {
    if (search[0] !== '?') result += '?'
    result += search
  } else if (typeof query === 'object' && query !== null) {
    result += '?' + new URLSearchParams(query)
  }

  if (typeof hash === 'string') {
    if (hash[0] !== '#') result += '#'
    result += hash
  }

  return result
}
module.exports = class URLError extends Error {
  constructor(msg, fn = URLError, code = fn.name) {
    super(`${code}: ${msg}`)

    this.code = code

    if (Error.captureStackTrace) Error.captureStackTrace(this, fn)
  }

  get name() {
    return 'URLError'
  }

  static INVALID_URL(msg, input) {
    const err = new URLError(msg, URLError.INVALID_URL)

    err.input = input

    return err
  }

  static INVALID_URL_SCHEME(msg = 'Invalid URL') {
    return new URLError(msg, URLError.INVALID_URL_SCHEME)
  }

  static INVALID_FILE_URL_HOST(msg = 'Invalid file: URL host') {
    return new URLError(msg, URLError.INVALID_FILE_URL_HOST)
  }

  static INVALID_FILE_URL_PATH(msg = 'Invalid file: URL path') {
    return new URLError(msg, URLError.INVALID_FILE_URL_PATH)
  }
}
const kind = Symbol.for('bare.url.search-params.kind')

class URLSearchParams {
  static _urls = new WeakMap()

  static get [kind]() {
    return 0 // Compatibility version
  }

  // https://url.spec.whatwg.org/#dom-urlsearchparams-urlsearchparams
  constructor(init, url = null) {
    this._params = new Map()

    if (url) URLSearchParams._urls.set(this, url)

    if (typeof init === 'string') {
      this._parse(init)
    } else if (init) {
      for (const [name, value] of typeof init[Symbol.iterator] === 'function'
        ? init
        : Object.entries(init)) {
        this.append(name, value)
      }
    }
  }

  get [kind]() {
    return URLSearchParams[kind]
  }

  // https://url.spec.whatwg.org/#dom-urlsearchparams-size
  get size() {
    return this._params.length
  }

  // https://url.spec.whatwg.org/#dom-urlsearchparams-append
  append(name, value = null) {
    if (value === null) return

    let list = this._params.get(name)

    if (list === undefined) {
      list = []
      this._params.set(name, list)
    }

    list.push(value)

    this._update()
  }

  // https://url.spec.whatwg.org/#dom-urlsearchparams-delete
  delete(name, value = null) {
    if (value === null) this._params.delete(name)
    else {
      let list = this._params.get(name)

      if (list === undefined) return

      list = list.filter((found) => found !== value)

      if (list.length === 0) this._params.delete(name)
      else this._params.set(name, list)
    }

    this._update()
  }

  // https://url.spec.whatwg.org/#dom-urlsearchparams-get
  get(name) {
    const list = this._params.get(name)

    if (list === undefined) return null

    return list[0]
  }

  // https://url.spec.whatwg.org/#dom-urlsearchparams-getall
  getAll(name) {
    const list = this._params.get(name)

    if (list === undefined) return []

    return Array.from(list)
  }

  // https://url.spec.whatwg.org/#dom-urlsearchparams-has
  has(name, value = null) {
    const list = this._params.get(name)

    if (list === undefined) return false

    if (value === null) return true

    return list.includes(value)
  }

  // https://url.spec.whatwg.org/#dom-urlsearchparams-set
  set(name, value = null) {
    if (value === null) this._params.delete(name)
    else this._params.set(name, [value])

    this._update()
  }

  toString() {
    return this._serialize()
  }

  toJSON() {
    return [...this]
  }

  *[Symbol.iterator]() {
    for (const [name, values] of this._params) {
      for (const value of values) yield [name, value]
    }
  }

  [Symbol.for('bare.inspect')]() {
    const object = {
      __proto__: { constructor: URLSearchParams }
    }

    for (const [name, values] of this._params) {
      if (values.length === 1) object[name] = values[0]
      else object[name] = values
    }

    return object
  }

  // https://url.spec.whatwg.org/#concept-urlsearchparams-update
  _update() {
    const url = URLSearchParams._urls.get(this)

    if (url === undefined) return

    url.search = this._serialize()
  }

  // https://url.spec.whatwg.org/#concept-urlencoded-parser
  _parse(input) {
    if (input[0] === '?') input = input.substring(1)

    this._params = new Map()

    for (let sequence of input.split('&')) {
      if (sequence.length === 0) continue

      sequence = sequence.replace(/\+/g, ' ')

      let i = sequence.indexOf('=')
      if (i === -1) i = sequence.length

      const name = decodeURIComponent(sequence.substring(0, i))
      const value = decodeURIComponent(sequence.substring(i + 1, sequence.length))

      let list = this._params.get(name)

      if (list === undefined) {
        list = []
        this._params.set(name, list)
      }

      list.push(value)
    }
  }

  // https://url.spec.whatwg.org/#concept-urlencoded-serializer
  _serialize() {
    let output = ''

    for (let [name, values] of this._params) {
      name = encodeURIComponent(name)

      for (const value of values) {
        if (output) output += '&'

        output += name + '=' + encodeURIComponent(value)
      }
    }

    return output
  }
}

module.exports = exports = URLSearchParams

exports.isURLSearchParams = function isURLSearchParams(value) {
  if (value instanceof URLSearchParams) return true

  return typeof value === 'object' && value !== null && value[kind] === URLSearchParams[kind]
}
{
  "name": "bare-url",
  "version": "2.4.5",
  "description": "WHATWG URL implementation for JavaScript",
  "exports": {
    "./package": "./package.json",
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    },
    "./global": {
      "types": "./global.d.ts",
      "default": "./global.js"
    }
  },
  "files": [
    "index.js",
    "index.d.ts",
    "global.js",
    "global.d.ts",
    "binding.c",
    "binding.js",
    "CMakeLists.txt",
    "lib",
    "prebuilds"
  ],
  "addon": true,
  "scripts": {
    "format": "prettier --write . && lunte --fix",
    "lint": "prettier --check . && lunte",
    "test": "brittle-bare --coverage test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-url.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-url/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-url",
  "dependencies": {
    "bare-path": "^3.0.0"
  },
  "devDependencies": {
    "brittle": "^3.3.2",
    "cmake-bare": "^1.1.6",
    "cmake-fetch": "^1.0.0",
    "lunte": "^1.6.0",
    "prettier": "^3.3.3",
    "prettier-config-holepunch": "^2.0.0"
  }
}
module.exports = require.addon()
const { Transform, Writable } = require('bare-stream')
const binding = require('./binding')
const constants = require('./lib/constants')
const errors = require('./lib/errors')

exports.constants = constants
exports.errors = errors

const MAX_OUTPUT_LENGTH = (Buffer.constants && Buffer.constants.MAX_LENGTH) || 0x100000000

class ZlibState {
  constructor(mode, opts = {}) {
    const {
      flush = constants.Z_NO_FLUSH,
      finishFlush = constants.Z_FINISH,
      chunkSize = constants.Z_DEFAULT_CHUNK,
      level = constants.Z_DEFAULT_LEVEL,
      windowBits = constants.Z_DEFAULT_WINDOWBITS,
      memLevel = constants.Z_DEFAULT_MEMLEVEL,
      strategy = constants.Z_DEFAULT_STRATEGY,
      maxOutputLength = MAX_OUTPUT_LENGTH
    } = opts

    this._mode = mode

    this._flushMode = flush
    this._finishFlushMode = finishFlush
    this._allocations = []

    this._maxOutputLength = maxOutputLength
    this._outputLength = 0

    this._buffer = Buffer.allocUnsafe(chunkSize)

    this._handle = binding.init(
      this._mode,
      this._buffer,
      level,
      windowBits,
      memLevel,
      strategy,
      this,
      this._onalloc,
      this._onfree
    )
  }

  _onalloc(size) {
    const buffer = Buffer.allocUnsafe(size)

    const view = new Uint32Array(buffer.buffer, buffer.byteOffset, buffer.byteLength / 4)

    view[0] = this._allocations.push(view) - 1

    return buffer
  }

  _onfree(id) {
    const last = this._allocations.pop()

    if (last[0] !== id) {
      this._allocations[(last[0] = id)] = last
    }
  }

  reset() {
    this._outputLength = 0

    binding.reset(this._handle)
  }

  *transform(data) {
    binding.load(this._handle, data)

    let available
    do {
      try {
        available = binding.transform(this._handle, this._flushMode)
      } catch (err) {
        throw errors[err.code](err.message)
      }

      const read = this._buffer.length - available

      if (read) {
        this._outputLength += read

        if (this._outputLength > this._maxOutputLength) {
          throw errors.LIMIT_EXCEEDED(
            `Output length exceeded maxOutputLength of ${this._maxOutputLength} bytes`
          )
        }

        const copy = Buffer.allocUnsafe(read)
        copy.set(this._buffer.subarray(0, read))

        yield copy
      }
    } while (available === 0)
  }

  *flush() {
    let available
    do {
      try {
        available = binding.transform(this._handle, this._finishFlushMode)
      } catch (err) {
        throw errors[err.code](err.message)
      }

      const read = this._buffer.length - available

      if (read) {
        this._outputLength += read

        if (this._outputLength > this._maxOutputLength) {
          throw errors.LIMIT_EXCEEDED(
            `Output length exceeded maxOutputLength of ${this._maxOutputLength} bytes`
          )
        }

        const copy = Buffer.allocUnsafe(read)
        copy.set(this._buffer.subarray(0, read))

        yield copy
      }
    } while (available === 0)

    try {
      binding.end(this._handle)
    } catch (err) {
      throw errors[err.code](err.message)
    }

    this._handle = null
  }
}

class ZlibStream extends Transform {
  constructor(mode, opts = {}) {
    const { flush, ...streamOpts } = opts

    super(streamOpts)

    this._state = new ZlibState(mode, opts)
  }

  async flush(mode = constants.Z_FULL_FLUSH, cb) {
    if (typeof mode === 'function') {
      cb = mode
      mode = constants.Z_FULL_FLUSH
    }

    const previousMode = this._state._flushMode

    this._state._flushMode = mode

    await Writable.drained(this)

    this._state._flushMode = previousMode

    if (cb) cb(null) // For Node.js compatibility
  }

  reset() {
    if (this._state === null) {
      throw errors.STREAM_CLOSED('Stream has already closed')
    }

    this._state.reset()
  }

  _transform(data, encoding, cb) {
    let err = null

    try {
      for (const chunk of this._state.transform(data)) {
        this.push(chunk)
      }
    } catch (e) {
      err = e
    }

    cb(err)
  }

  _flush(cb) {
    let err = null

    try {
      for (const chunk of this._state.flush()) {
        this.push(chunk)
      }
    } catch (e) {
      err = e
    }

    this._state = null

    cb(err)
  }
}

function readAsBuffer(stream, buffer, cb) {
  const chunks = []

  stream.on('data', ondata).on('end', onend).on('error', onerror).end(buffer)

  function ondata(chunk) {
    chunks.push(chunk)
  }

  function onend() {
    cb(null, chunks.length === 1 ? chunks[0] : Buffer.concat(chunks))
  }

  function onerror(err) {
    stream.off('end', onend)
    cb(err)
  }
}

function transformToBuffer(mode, buffer, opts) {
  if (typeof buffer === 'string') buffer = Buffer.from(buffer)

  const state = new ZlibState(mode, opts)
  const chunks = []

  for (const chunk of state.transform(buffer)) {
    chunks.push(chunk)
  }

  for (const chunk of state.flush()) {
    chunks.push(chunk)
  }

  return chunks.length === 1 ? chunks[0] : Buffer.concat(chunks)
}

exports.Deflate = class ZlibDeflateStream extends ZlibStream {
  constructor(opts) {
    super(binding.DEFLATE, opts)
  }
}

exports.createDeflate = function createDeflate(opts) {
  return new exports.Deflate(opts)
}

exports.deflate = function deflate(buffer, opts, cb) {
  if (typeof opts === 'function') {
    cb = opts
    opts = {}
  }

  readAsBuffer(new exports.Deflate(opts), buffer, cb)
}

exports.deflateSync = function deflateSync(buffer, opts) {
  return transformToBuffer(binding.DEFLATE, buffer, opts)
}

exports.Inflate = class ZlibInflateStream extends ZlibStream {
  constructor(opts) {
    super(binding.INFLATE, opts)
  }
}

exports.createInflate = function createInflate(opts) {
  return new exports.Inflate(opts)
}

exports.inflate = function inflate(buffer, opts, cb) {
  if (typeof opts === 'function') {
    cb = opts
    opts = {}
  }

  readAsBuffer(new exports.Inflate(opts), buffer, cb)
}

exports.inflateSync = function inflateSync(buffer, opts) {
  return transformToBuffer(binding.INFLATE, buffer, opts)
}

exports.DeflateRaw = class ZlibDeflateRawStream extends ZlibStream {
  constructor(opts) {
    super(binding.DEFLATE_RAW, opts)
  }
}

exports.createDeflateRaw = function createDeflateRaw(opts) {
  return new exports.DeflateRaw(opts)
}

exports.deflateRaw = function deflateRaw(buffer, opts, cb) {
  if (typeof opts === 'function') {
    cb = opts
    opts = {}
  }

  readAsBuffer(new exports.DeflateRaw(opts), buffer, cb)
}

exports.deflateRawSync = function deflateRawSync(buffer, opts) {
  return transformToBuffer(binding.DEFLATE_RAW, buffer, opts)
}

exports.InflateRaw = class ZlibInflateRawStream extends ZlibStream {
  constructor(opts) {
    super(binding.INFLATE_RAW, opts)
  }
}

exports.createInflateRaw = function createInflateRaw(opts) {
  return new exports.InflateRaw(opts)
}

exports.inflateRaw = function inflateRaw(buffer, opts, cb) {
  if (typeof opts === 'function') {
    cb = opts
    opts = {}
  }

  readAsBuffer(new exports.InflateRaw(opts), buffer, cb)
}

exports.inflateRawSync = function inflateRawSync(buffer, opts) {
  return transformToBuffer(binding.INFLATE_RAW, buffer, opts)
}

exports.Gzip = class ZlibGzipStream extends ZlibStream {
  constructor(opts) {
    super(binding.GZIP, opts)
  }
}

exports.createGzip = function createGzip(opts) {
  return new exports.Gzip(opts)
}

exports.gzip = function gzip(buffer, opts, cb) {
  if (typeof opts === 'function') {
    cb = opts
    opts = {}
  }

  readAsBuffer(new exports.Gzip(opts), buffer, cb)
}

exports.gzipSync = function gzipSync(buffer, opts) {
  return transformToBuffer(binding.GZIP, buffer, opts)
}

exports.Gunzip = class ZlibGunzipStream extends ZlibStream {
  constructor(opts) {
    super(binding.GUNZIP, opts)
  }
}

exports.createGunzip = function createGunzip(opts) {
  return new exports.Gunzip(opts)
}

exports.gunzip = function gunzip(buffer, opts, cb) {
  if (typeof opts === 'function') {
    cb = opts
    opts = {}
  }

  readAsBuffer(new exports.Gunzip(opts), buffer, cb)
}

exports.gunzipSync = function gunzipSync(buffer, opts) {
  return transformToBuffer(binding.GUNZIP, buffer, opts)
}
const binding = require('../binding')

module.exports = binding.constants
module.exports = class ZlibError extends Error {
  constructor(msg, code, fn = ZlibError) {
    super(`${code}: ${msg}`)
    this.code = code

    if (Error.captureStackTrace) {
      Error.captureStackTrace(this, fn)
    }
  }

  get name() {
    return 'ZlibError'
  }

  static STREAM_CLOSED(msg) {
    return new ZlibError(msg, 'STREAM_CLOSED', ZlibError.STREAM_CLOSED)
  }

  static STREAM_ERROR(msg) {
    return new ZlibError(msg, 'STREAM_ERROR', ZlibError.STREAM_ERROR)
  }

  static DATA_ERROR(msg) {
    return new ZlibError(msg, 'DATA_ERROR', ZlibError.DATA_ERROR)
  }

  static MEM_ERROR(msg) {
    return new ZlibError(msg, 'MEM_ERROR', ZlibError.MEM_ERROR)
  }

  static BUF_ERROR(msg) {
    return new ZlibError(msg, 'BUF_ERROR', ZlibError.BUF_ERROR)
  }

  static VERSION_ERROR(msg) {
    return new ZlibError(msg, 'VERSION_ERROR', ZlibError.VERSION_ERROR)
  }

  static UNKNOWN_ERROR(msg) {
    return new ZlibError(msg, 'UNKNOWN_ERROR', ZlibError.UNKNOWN_ERROR)
  }

  static LIMIT_EXCEEDED(msg) {
    return new ZlibError(msg, 'LIMIT_EXCEEDED', ZlibError.LIMIT_EXCEEDED)
  }
}
{
  "name": "bare-zlib",
  "version": "1.4.0",
  "description": "Stream-based zlib bindings for JavaScript",
  "exports": {
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    },
    "./package": "./package.json",
    "./constants": {
      "types": "./lib/constants.d.ts",
      "default": "./lib/constants.js"
    },
    "./errors": {
      "types": "./lib/errors.d.ts",
      "default": "./lib/errors.js"
    }
  },
  "files": [
    "index.js",
    "index.d.ts",
    "binding.c",
    "binding.js",
    "CMakeLists.txt",
    "lib",
    "prebuilds"
  ],
  "addon": true,
  "scripts": {
    "format": "prettier --write . && lunte --fix",
    "lint": "prettier --check . && lunte",
    "test": "brittle-bare --coverage test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-zlib.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-zlib/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-zlib",
  "dependencies": {
    "bare-stream": "^2.0.0"
  },
  "devDependencies": {
    "bare-fs": "^3.0.0",
    "brittle": "^3.5.0",
    "cmake-bare": "^1.1.7",
    "cmake-fetch": "^1.0.0",
    "lunte": "^1.6.0",
    "prettier": "^3.5.3",
    "prettier-config-holepunch": "^2.0.0"
  },
  "peerDependencies": {
    "bare-buffer": "*"
  },
  "peerDependenciesMeta": {
    "bare-buffer": {
      "optional": true
    }
  }
}
const b4a = require('b4a')

module.exports = codecs

codecs.ascii = createString('ascii')
codecs.utf8 = createString('utf-8')
codecs.hex = createString('hex')
codecs.base64 = createString('base64')
codecs.ucs2 = createString('ucs2')
codecs.utf16le = createString('utf16le')
codecs.ndjson = createJSON(true)
codecs.json = createJSON(false)
codecs.binary = {
  name: 'binary',
  encode: function encodeBinary (obj) {
    return typeof obj === 'string'
      ? b4a.from(obj, 'utf-8')
      : b4a.toBuffer(obj)
  },
  decode: function decodeBinary (buf) {
    return b4a.toBuffer(buf)
  }
}

function isCompactEncoding (c) {
  return !!(c.encode && c.decode && c.preencode)
}

function fromCompactEncoding (c) {
  return {
    name: 'compact-encoding',
    encode: function encodeWithCompact (value) {
      const state = { start: 0, end: 0, buffer: null, cache: null }
      c.preencode(state, value)
      state.buffer = b4a.allocUnsafe(state.end)
      c.encode(state, value)
      return state.buffer
    },
    decode: function decodeWithCompact (buffer) {
      return c.decode({ start: 0, end: buffer.byteLength, buffer, cache: null })
    }
  }
}

function codecs (fmt, fallback) {
  if (typeof fmt === 'object' && fmt) {
    return isCompactEncoding(fmt) ? fromCompactEncoding(fmt) : fmt
  }

  switch (fmt) {
    case 'ndjson': return codecs.ndjson
    case 'json': return codecs.json
    case 'ascii': return codecs.ascii
    case 'utf-8':
    case 'utf8': return codecs.utf8
    case 'hex': return codecs.hex
    case 'base64': return codecs.base64
    case 'ucs-2':
    case 'ucs2': return codecs.ucs2
    case 'utf16-le':
    case 'utf16le': return codecs.utf16le
  }

  return fallback !== undefined ? fallback : codecs.binary
}

function createJSON (newline) {
  return {
    name: newline ? 'ndjson' : 'json',
    encode: newline ? encodeNDJSON : encodeJSON,
    decode: function decodeJSON (buf) {
      return JSON.parse(b4a.toString(buf))
    }
  }

  function encodeJSON (val) {
    return b4a.from(JSON.stringify(val))
  }

  function encodeNDJSON (val) {
    return b4a.from(JSON.stringify(val) + '\n')
  }
}

function createString (type) {
  return {
    name: type,
    encode: function encodeString (val) {
      if (typeof val !== 'string') val = val.toString()
      return b4a.from(val, type)
    },
    decode: function decodeString (buf) {
      return b4a.toString(buf, type)
    }
  }
}
{
  "name": "codecs",
  "version": "3.1.0",
  "description": "Create an binary encoder/decoder for json, utf-8 or custom types",
  "main": "index.js",
  "dependencies": {
    "b4a": "^1.6.3"
  },
  "devDependencies": {
    "brittle": "^3.2.1",
    "compact-encoding": "^2.11.0",
    "standard": "^17.0.0"
  },
  "scripts": {
    "test": "standard && brittle test.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/mafintosh/codecs.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/mafintosh/codecs/issues"
  },
  "homepage": "https://github.com/mafintosh/codecs"
}
const LE = (exports.LE =
  new Uint8Array(new Uint16Array([0xff]).buffer)[0] === 0xff)

exports.BE = !LE
const b4a = require('b4a')

const { BE } = require('./endian')

exports.state = function (start = 0, end = 0, buffer = null) {
  return { start, end, buffer }
}

const raw = (exports.raw = require('./raw'))

const uint = (exports.uint = {
  preencode(state, n) {
    state.end += n <= 0xfc ? 1 : n <= 0xffff ? 3 : n <= 0xffffffff ? 5 : 9
  },
  encode(state, n) {
    if (n <= 0xfc) uint8.encode(state, n)
    else if (n <= 0xffff) {
      state.buffer[state.start++] = 0xfd
      uint16.encode(state, n)
    } else if (n <= 0xffffffff) {
      state.buffer[state.start++] = 0xfe
      uint32.encode(state, n)
    } else {
      state.buffer[state.start++] = 0xff
      uint64.encode(state, n)
    }
  },
  decode(state) {
    const a = uint8.decode(state)
    if (a <= 0xfc) return a
    if (a === 0xfd) return uint16.decode(state)
    if (a === 0xfe) return uint32.decode(state)
    return uint64.decode(state)
  }
})

const uint8 = (exports.uint8 = {
  preencode(state, n) {
    state.end += 1
  },
  encode(state, n) {
    validateUint(n)
    state.buffer[state.start++] = n
  },
  decode(state) {
    if (state.start >= state.end) throw new Error('Out of bounds')
    return state.buffer[state.start++]
  }
})

const uint16 = (exports.uint16 = {
  preencode(state, n) {
    state.end += 2
  },
  encode(state, n) {
    validateUint(n)
    state.buffer[state.start++] = n
    state.buffer[state.start++] = n >>> 8
  },
  decode(state) {
    if (state.end - state.start < 2) throw new Error('Out of bounds')
    return state.buffer[state.start++] + state.buffer[state.start++] * 0x100
  }
})

const uint24 = (exports.uint24 = {
  preencode(state, n) {
    state.end += 3
  },
  encode(state, n) {
    validateUint(n)
    state.buffer[state.start++] = n
    state.buffer[state.start++] = n >>> 8
    state.buffer[state.start++] = n >>> 16
  },
  decode(state) {
    if (state.end - state.start < 3) throw new Error('Out of bounds')
    return (
      state.buffer[state.start++] +
      state.buffer[state.start++] * 0x100 +
      state.buffer[state.start++] * 0x10000
    )
  }
})

const uint32 = (exports.uint32 = {
  preencode(state, n) {
    state.end += 4
  },
  encode(state, n) {
    validateUint(n)
    state.buffer[state.start++] = n
    state.buffer[state.start++] = n >>> 8
    state.buffer[state.start++] = n >>> 16
    state.buffer[state.start++] = n >>> 24
  },
  decode(state) {
    if (state.end - state.start < 4) throw new Error('Out of bounds')
    return (
      state.buffer[state.start++] +
      state.buffer[state.start++] * 0x100 +
      state.buffer[state.start++] * 0x10000 +
      state.buffer[state.start++] * 0x1000000
    )
  }
})

const uint32be = (exports.uint32be = {
  preencode(state, n) {
    state.end += 4
  },
  encode(state, n) {
    validateUint(n)
    state.buffer[state.start++] = n >>> 24
    state.buffer[state.start++] = n >>> 16
    state.buffer[state.start++] = n >>> 8
    state.buffer[state.start++] = n
  },
  decode(state) {
    if (state.end - state.start < 4) throw new Error('Out of bounds')
    return (
      state.buffer[state.start++] * 0x1000000 +
      state.buffer[state.start++] * 0x10000 +
      state.buffer[state.start++] * 0x100 +
      state.buffer[state.start++]
    )
  }
})

const uint40 = (exports.uint40 = {
  preencode(state, n) {
    state.end += 5
  },
  encode(state, n) {
    validateUint(n)
    const r = Math.floor(n / 0x100)
    uint8.encode(state, n)
    uint32.encode(state, r)
  },
  decode(state) {
    if (state.end - state.start < 5) throw new Error('Out of bounds')
    return uint8.decode(state) + 0x100 * uint32.decode(state)
  }
})

const uint48 = (exports.uint48 = {
  preencode(state, n) {
    state.end += 6
  },
  encode(state, n) {
    validateUint(n)
    const r = Math.floor(n / 0x10000)
    uint16.encode(state, n)
    uint32.encode(state, r)
  },
  decode(state) {
    if (state.end - state.start < 6) throw new Error('Out of bounds')
    return uint16.decode(state) + 0x10000 * uint32.decode(state)
  }
})

const uint56 = (exports.uint56 = {
  preencode(state, n) {
    state.end += 7
  },
  encode(state, n) {
    validateUint(n)
    const r = Math.floor(n / 0x1000000)
    uint24.encode(state, n)
    uint32.encode(state, r)
  },
  decode(state) {
    if (state.end - state.start < 7) throw new Error('Out of bounds')
    return uint24.decode(state) + 0x1000000 * uint32.decode(state)
  }
})

const uint64 = (exports.uint64 = {
  preencode(state, n) {
    state.end += 8
  },
  encode(state, n) {
    validateUint(n)
    const r = Math.floor(n / 0x100000000)
    uint32.encode(state, n)
    uint32.encode(state, r)
  },
  decode(state) {
    if (state.end - state.start < 8) throw new Error('Out of bounds')
    return uint32.decode(state) + 0x100000000 * uint32.decode(state)
  }
})

exports.uint64be = {
  preencode(state, n) {
    state.end += 8
  },
  encode(state, n) {
    validateUint(n)
    const r = Math.floor(n / 0x100000000)
    uint32be.encode(state, r)
    uint32be.encode(state, n)
  },
  decode(state) {
    if (state.end - state.start < 8) throw new Error('Out of bounds')
    return 0x100000000 * uint32be.decode(state) + uint32be.decode(state)
  }
}

const int = (exports.int = zigZagInt(uint))
exports.int8 = zigZagInt(uint8)
exports.int16 = zigZagInt(uint16)
exports.int24 = zigZagInt(uint24)
exports.int32 = zigZagInt(uint32)
exports.int40 = zigZagInt(uint40)
exports.int48 = zigZagInt(uint48)
exports.int56 = zigZagInt(uint56)
exports.int64 = zigZagInt(uint64)

const biguint64 = (exports.biguint64 = {
  preencode(state, n) {
    state.end += 8
  },
  encode(state, n) {
    const view = new DataView(
      state.buffer.buffer,
      state.start + state.buffer.byteOffset,
      8
    )
    view.setBigUint64(0, n, true) // little endian
    state.start += 8
  },
  decode(state) {
    if (state.end - state.start < 8) throw new Error('Out of bounds')
    const view = new DataView(
      state.buffer.buffer,
      state.start + state.buffer.byteOffset,
      8
    )
    const n = view.getBigUint64(0, true) // little endian
    state.start += 8
    return n
  }
})

exports.bigint64 = zigZagBigInt(biguint64)

const biguint = (exports.biguint = {
  preencode(state, n) {
    let len = 0
    for (let m = n; m; m = m >> 64n) len++
    uint.preencode(state, len)
    state.end += 8 * len
  },
  encode(state, n) {
    let len = 0
    for (let m = n; m; m = m >> 64n) len++
    uint.encode(state, len)
    const view = new DataView(
      state.buffer.buffer,
      state.start + state.buffer.byteOffset,
      8 * len
    )
    for (let m = n, i = 0; m; m = m >> 64n, i += 8) {
      view.setBigUint64(i, BigInt.asUintN(64, m), true) // little endian
    }
    state.start += 8 * len
  },
  decode(state) {
    const len = uint.decode(state)
    if (state.end - state.start < 8 * len) throw new Error('Out of bounds')
    const view = new DataView(
      state.buffer.buffer,
      state.start + state.buffer.byteOffset,
      8 * len
    )
    let n = 0n
    for (let i = len - 1; i >= 0; i--)
      n = (n << 64n) + view.getBigUint64(i * 8, true) // little endian
    state.start += 8 * len
    return n
  }
})

exports.bigint = zigZagBigInt(biguint)

exports.lexint = require('./lexint')

exports.float32 = {
  preencode(state, n) {
    state.end += 4
  },
  encode(state, n) {
    const view = new DataView(
      state.buffer.buffer,
      state.start + state.buffer.byteOffset,
      4
    )
    view.setFloat32(0, n, true) // little endian
    state.start += 4
  },
  decode(state) {
    if (state.end - state.start < 4) throw new Error('Out of bounds')
    const view = new DataView(
      state.buffer.buffer,
      state.start + state.buffer.byteOffset,
      4
    )
    const float = view.getFloat32(0, true) // little endian
    state.start += 4
    return float
  }
}

exports.float64 = {
  preencode(state, n) {
    state.end += 8
  },
  encode(state, n) {
    const view = new DataView(
      state.buffer.buffer,
      state.start + state.buffer.byteOffset,
      8
    )
    view.setFloat64(0, n, true) // little endian
    state.start += 8
  },
  decode(state) {
    if (state.end - state.start < 8) throw new Error('Out of bounds')
    const view = new DataView(
      state.buffer.buffer,
      state.start + state.buffer.byteOffset,
      8
    )
    const float = view.getFloat64(0, true) // little endian
    state.start += 8
    return float
  }
}

const buffer = (exports.buffer = {
  preencode(state, b) {
    uint8array.preencode(state, b)
  },
  encode(state, b) {
    uint8array.encode(state, b)
  },
  decode(state) {
    const len = uint.decode(state)
    if (state.end - state.start < len) throw new Error('Out of bounds')
    return state.buffer.subarray(state.start, (state.start += len))
  }
})

exports.optionalBuffer = {
  preencode(state, b) {
    if (b) uint8array.preencode(state, b)
    else state.end++
  },
  encode(state, b) {
    if (b) uint8array.encode(state, b)
    else state.buffer[state.start++] = 0
  },
  decode(state) {
    const len = uint.decode(state)
    if (len === 0) return null
    if (state.end - state.start < len) throw new Error('Out of bounds')
    return state.buffer.subarray(state.start, (state.start += len))
  }
}

exports.binary = {
  ...buffer,
  preencode(state, b) {
    if (typeof b === 'string') utf8.preencode(state, b)
    else buffer.preencode(state, b)
  },
  encode(state, b) {
    if (typeof b === 'string') utf8.encode(state, b)
    else buffer.encode(state, b)
  }
}

exports.arraybuffer = {
  preencode(state, b) {
    uint.preencode(state, b.byteLength)
    state.end += b.byteLength
  },
  encode(state, b) {
    uint.encode(state, b.byteLength)

    const view = new Uint8Array(b)

    state.buffer.set(view, state.start)
    state.start += b.byteLength
  },
  decode(state) {
    const len = uint.decode(state)

    const b = new ArrayBuffer(len)
    const view = new Uint8Array(b)

    view.set(state.buffer.subarray(state.start, (state.start += len)))

    return b
  }
}

function typedarray(TypedArray, swap) {
  const n = TypedArray.BYTES_PER_ELEMENT

  return {
    preencode(state, b) {
      uint.preencode(state, b.length)
      state.end += b.byteLength
    },
    encode(state, b) {
      uint.encode(state, b.length)

      const view = new Uint8Array(b.buffer, b.byteOffset, b.byteLength)

      if (BE && swap) swap(view)

      state.buffer.set(view, state.start)
      state.start += b.byteLength
    },
    decode(state) {
      const len = uint.decode(state)

      let b = state.buffer.subarray(state.start, (state.start += len * n))
      if (b.byteLength !== len * n) throw new Error('Out of bounds')
      if (b.byteOffset % n !== 0) b = new Uint8Array(b)

      if (BE && swap) swap(b)

      return new TypedArray(b.buffer, b.byteOffset, b.byteLength / n)
    }
  }
}

const uint8array = (exports.uint8array = typedarray(Uint8Array))
exports.uint16array = typedarray(Uint16Array, b4a.swap16)
exports.uint32array = typedarray(Uint32Array, b4a.swap32)

exports.int8array = typedarray(Int8Array)
exports.int16array = typedarray(Int16Array, b4a.swap16)
exports.int32array = typedarray(Int32Array, b4a.swap32)

exports.biguint64array = typedarray(BigUint64Array, b4a.swap64)
exports.bigint64array = typedarray(BigInt64Array, b4a.swap64)

exports.float32array = typedarray(Float32Array, b4a.swap32)
exports.float64array = typedarray(Float64Array, b4a.swap64)

function string(encoding) {
  return {
    preencode(state, s) {
      const len = b4a.byteLength(s, encoding)
      uint.preencode(state, len)
      state.end += len
    },
    encode(state, s) {
      const len = b4a.byteLength(s, encoding)
      uint.encode(state, len)
      b4a.write(state.buffer, s, state.start, encoding)
      state.start += len
    },
    decode(state) {
      const len = uint.decode(state)
      if (state.end - state.start < len) throw new Error('Out of bounds')
      return b4a.toString(
        state.buffer,
        encoding,
        state.start,
        (state.start += len)
      )
    },
    fixed(n) {
      return {
        preencode(state) {
          state.end += n
        },
        encode(state, s) {
          b4a.write(state.buffer, s, state.start, n, encoding)
          state.start += n
        },
        decode(state) {
          if (state.end - state.start < n) throw new Error('Out of bounds')
          return b4a.toString(
            state.buffer,
            encoding,
            state.start,
            (state.start += n)
          )
        }
      }
    }
  }
}

const utf8 = (exports.string = exports.utf8 = string('utf-8'))
exports.ascii = string('ascii')
exports.hex = string('hex')
exports.base64 = string('base64')
exports.ucs2 = exports.utf16le = string('utf16le')

exports.bool = {
  preencode(state, b) {
    state.end++
  },
  encode(state, b) {
    state.buffer[state.start++] = b ? 1 : 0
  },
  decode(state) {
    if (state.start >= state.end) throw Error('Out of bounds')
    return state.buffer[state.start++] === 1
  }
}

const fixed = (exports.fixed = function fixed(n) {
  return {
    preencode(state, s) {
      if (s.byteLength !== n) throw new Error('Incorrect buffer size')
      state.end += n
    },
    encode(state, s) {
      state.buffer.set(s, state.start)
      state.start += n
    },
    decode(state) {
      if (state.end - state.start < n) throw new Error('Out of bounds')
      return state.buffer.subarray(state.start, (state.start += n))
    }
  }
})

exports.fixed32 = fixed(32)
exports.fixed64 = fixed(64)

exports.array = function array(enc) {
  return {
    preencode(state, list) {
      uint.preencode(state, list.length)
      for (let i = 0; i < list.length; i++) enc.preencode(state, list[i])
    },
    encode(state, list) {
      uint.encode(state, list.length)
      for (let i = 0; i < list.length; i++) enc.encode(state, list[i])
    },
    decode(state) {
      const len = uint.decode(state)
      if (len > 0x100000) throw new Error('Array is too big')
      const arr = new Array(len)
      for (let i = 0; i < len; i++) arr[i] = enc.decode(state)
      return arr
    }
  }
}

exports.frame = function frame(enc) {
  const dummy = exports.state()

  return {
    preencode(state, m) {
      const end = state.end
      enc.preencode(state, m)
      uint.preencode(state, state.end - end)
    },
    encode(state, m) {
      dummy.end = 0
      enc.preencode(dummy, m)
      uint.encode(state, dummy.end)
      enc.encode(state, m)
    },
    decode(state) {
      const end = state.end
      const len = uint.decode(state)
      state.end = state.start + len
      const m = enc.decode(state)
      state.start = state.end
      state.end = end
      return m
    }
  }
}

exports.date = {
  preencode(state, d) {
    int.preencode(state, d.getTime())
  },
  encode(state, d) {
    int.encode(state, d.getTime())
  },
  decode(state, d) {
    return new Date(int.decode(state))
  }
}

exports.json = {
  preencode(state, v) {
    utf8.preencode(state, JSON.stringify(v))
  },
  encode(state, v) {
    utf8.encode(state, JSON.stringify(v))
  },
  decode(state) {
    return JSON.parse(utf8.decode(state))
  }
}

exports.ndjson = {
  preencode(state, v) {
    utf8.preencode(state, JSON.stringify(v) + '\n')
  },
  encode(state, v) {
    utf8.encode(state, JSON.stringify(v) + '\n')
  },
  decode(state) {
    return JSON.parse(utf8.decode(state))
  }
}

// simple helper for when you want to just express nothing
exports.none = {
  preencode(state, n) {
    // do nothing
  },
  encode(state, n) {
    // do nothing
  },
  decode(state) {
    return null
  }
}

// "any" encoders here for helping just structure any object without schematising it

const anyArray = {
  preencode(state, arr) {
    uint.preencode(state, arr.length)
    for (let i = 0; i < arr.length; i++) {
      any.preencode(state, arr[i])
    }
  },
  encode(state, arr) {
    uint.encode(state, arr.length)
    for (let i = 0; i < arr.length; i++) {
      any.encode(state, arr[i])
    }
  },
  decode(state) {
    const arr = []
    let len = uint.decode(state)
    while (len-- > 0) {
      arr.push(any.decode(state))
    }
    return arr
  }
}

const anyObject = {
  preencode(state, o) {
    const keys = Object.keys(o)
    uint.preencode(state, keys.length)
    for (const key of keys) {
      utf8.preencode(state, key)
      any.preencode(state, o[key])
    }
  },
  encode(state, o) {
    const keys = Object.keys(o)
    uint.encode(state, keys.length)
    for (const key of keys) {
      utf8.encode(state, key)
      any.encode(state, o[key])
    }
  },
  decode(state) {
    let len = uint.decode(state)
    const o = {}
    while (len-- > 0) {
      const key = utf8.decode(state)
      o[key] = any.decode(state)
    }
    return o
  }
}

const anyTypes = [
  exports.none,
  exports.bool,
  exports.string,
  exports.buffer,
  exports.uint,
  exports.int,
  exports.float64,
  anyArray,
  anyObject,
  exports.date
]

const any = (exports.any = {
  preencode(state, o) {
    const t = getType(o)
    uint.preencode(state, t)
    anyTypes[t].preencode(state, o)
  },
  encode(state, o) {
    const t = getType(o)
    uint.encode(state, t)
    anyTypes[t].encode(state, o)
  },
  decode(state) {
    const t = uint.decode(state)
    if (t >= anyTypes.length) throw new Error('Unknown type: ' + t)
    return anyTypes[t].decode(state)
  }
})

const port = (exports.port = uint16)

const address = (host, family) => {
  return {
    preencode(state, m) {
      host.preencode(state, m.host)
      port.preencode(state, m.port)
    },
    encode(state, m) {
      host.encode(state, m.host)
      port.encode(state, m.port)
    },
    decode(state) {
      return {
        host: host.decode(state),
        family,
        port: port.decode(state)
      }
    }
  }
}

const ipv4 = (exports.ipv4 = {
  preencode(state) {
    state.end += 4
  },
  encode(state, string) {
    const start = state.start
    const end = start + 4

    let i = 0

    while (i < string.length) {
      let n = 0
      let c

      while (
        i < string.length &&
        (c = string.charCodeAt(i++)) !== /* . */ 0x2e
      ) {
        n = n * 10 + (c - /* 0 */ 0x30)
      }

      state.buffer[state.start++] = n
    }

    state.start = end
  },
  decode(state) {
    if (state.end - state.start < 4) throw new Error('Out of bounds')
    return (
      state.buffer[state.start++] +
      '.' +
      state.buffer[state.start++] +
      '.' +
      state.buffer[state.start++] +
      '.' +
      state.buffer[state.start++]
    )
  }
})

exports.ipv4Address = address(ipv4, 4)

const ipv6 = (exports.ipv6 = {
  preencode(state) {
    state.end += 16
  },
  encode(state, string) {
    const start = state.start
    const end = start + 16

    let i = 0
    let split = null

    while (i < string.length) {
      let n = 0
      let c

      while (
        i < string.length &&
        (c = string.charCodeAt(i++)) !== /* : */ 0x3a
      ) {
        if (c >= 0x30 && c <= 0x39) n = n * 0x10 + (c - /* 0 */ 0x30)
        else if (c >= 0x41 && c <= 0x46) n = n * 0x10 + (c - /* A */ 0x41 + 10)
        else if (c >= 0x61 && c <= 0x66) n = n * 0x10 + (c - /* a */ 0x61 + 10)
      }

      state.buffer[state.start++] = n >>> 8
      state.buffer[state.start++] = n

      if (i < string.length && string.charCodeAt(i) === /* : */ 0x3a) {
        i++
        split = state.start
      }
    }

    if (split !== null) {
      const offset = end - state.start
      state.buffer
        .copyWithin(split + offset, split)
        .fill(0, split, split + offset)
    }

    state.start = end
  },
  decode(state) {
    if (state.end - state.start < 16) throw new Error('Out of bounds')
    return (
      (
        state.buffer[state.start++] * 256 +
        state.buffer[state.start++]
      ).toString(16) +
      ':' +
      (
        state.buffer[state.start++] * 256 +
        state.buffer[state.start++]
      ).toString(16) +
      ':' +
      (
        state.buffer[state.start++] * 256 +
        state.buffer[state.start++]
      ).toString(16) +
      ':' +
      (
        state.buffer[state.start++] * 256 +
        state.buffer[state.start++]
      ).toString(16) +
      ':' +
      (
        state.buffer[state.start++] * 256 +
        state.buffer[state.start++]
      ).toString(16) +
      ':' +
      (
        state.buffer[state.start++] * 256 +
        state.buffer[state.start++]
      ).toString(16) +
      ':' +
      (
        state.buffer[state.start++] * 256 +
        state.buffer[state.start++]
      ).toString(16) +
      ':' +
      (
        state.buffer[state.start++] * 256 +
        state.buffer[state.start++]
      ).toString(16)
    )
  }
})

exports.ipv6Address = address(ipv6, 6)

const ip = (exports.ip = {
  preencode(state, string) {
    const family = string.includes(':') ? 6 : 4
    uint8.preencode(state, family)
    if (family === 4) ipv4.preencode(state)
    else ipv6.preencode(state)
  },
  encode(state, string) {
    const family = string.includes(':') ? 6 : 4
    uint8.encode(state, family)
    if (family === 4) ipv4.encode(state, string)
    else ipv6.encode(state, string)
  },
  decode(state) {
    const family = uint8.decode(state)
    if (family === 4) return ipv4.decode(state)
    else return ipv6.decode(state)
  }
})

exports.ipAddress = {
  preencode(state, m) {
    ip.preencode(state, m.host)
    port.preencode(state, m.port)
  },
  encode(state, m) {
    ip.encode(state, m.host)
    port.encode(state, m.port)
  },
  decode(state) {
    const family = uint8.decode(state)
    return {
      host: family === 4 ? ipv4.decode(state) : ipv6.decode(state),
      family,
      port: port.decode(state)
    }
  }
}

const record = (exports.record = function (keyEncoding, valueEncoding) {
  return {
    preencode(state, v) {
      const keys = Object.keys(v)
      uint.preencode(state, keys.length)
      for (const k of keys) {
        keyEncoding.preencode(state, k)
        valueEncoding.preencode(state, v[k])
      }
    },
    encode(state, v) {
      const keys = Object.keys(v)
      uint.encode(state, keys.length)
      for (const k of keys) {
        keyEncoding.encode(state, k)
        valueEncoding.encode(state, v[k])
      }
    },
    decode(state) {
      const out = Object.create(null)
      const keys = uint.decode(state)
      for (let i = 0; i < keys; i++) {
        out[keyEncoding.decode(state)] = valueEncoding.decode(state)
      }
      return out
    }
  }
})

exports.stringRecord = record(utf8, utf8)

function getType(o) {
  if (o === null || o === undefined) return 0
  if (typeof o === 'boolean') return 1
  if (typeof o === 'string') return 2
  if (b4a.isBuffer(o)) return 3
  if (typeof o === 'number') {
    if (Number.isInteger(o)) return o >= 0 ? 4 : 5
    return 6
  }
  if (Array.isArray(o)) return 7
  if (o instanceof Date) return 9
  if (typeof o === 'object') return 8

  throw new Error('Unsupported type for ' + o)
}

exports.from = function from(enc) {
  if (typeof enc === 'string') return fromNamed(enc)
  if (enc.preencode) return enc
  if (enc.encodingLength) return fromAbstractEncoder(enc)
  return fromCodec(enc)
}

function fromNamed(enc) {
  switch (enc) {
    case 'ascii':
      return raw.ascii
    case 'utf-8':
    case 'utf8':
      return raw.utf8
    case 'hex':
      return raw.hex
    case 'base64':
      return raw.base64
    case 'utf16-le':
    case 'utf16le':
    case 'ucs-2':
    case 'ucs2':
      return raw.ucs2
    case 'ndjson':
      return raw.ndjson
    case 'json':
      return raw.json
    case 'binary':
    default:
      return raw.binary
  }
}

function fromCodec(enc) {
  let tmpM = null
  let tmpBuf = null

  return {
    preencode(state, m) {
      tmpM = m
      tmpBuf = enc.encode(m)
      state.end += tmpBuf.byteLength
    },
    encode(state, m) {
      raw.encode(state, m === tmpM ? tmpBuf : enc.encode(m))
      tmpM = tmpBuf = null
    },
    decode(state) {
      return enc.decode(raw.decode(state))
    }
  }
}

function fromAbstractEncoder(enc) {
  return {
    preencode(state, m) {
      state.end += enc.encodingLength(m)
    },
    encode(state, m) {
      enc.encode(m, state.buffer, state.start)
      state.start += enc.encode.bytes
    },
    decode(state) {
      const m = enc.decode(state.buffer, state.start, state.end)
      state.start += enc.decode.bytes
      return m
    }
  }
}

exports.encode = function encode(enc, m) {
  const state = exports.state()
  enc.preencode(state, m)
  state.buffer = b4a.allocUnsafe(state.end)
  enc.encode(state, m)
  return state.buffer
}

exports.decode = function decode(enc, buffer) {
  return enc.decode(exports.state(0, buffer.byteLength, buffer))
}

function zigZagInt(enc) {
  return {
    preencode(state, n) {
      enc.preencode(state, zigZagEncodeInt(n))
    },
    encode(state, n) {
      enc.encode(state, zigZagEncodeInt(n))
    },
    decode(state) {
      return zigZagDecodeInt(enc.decode(state))
    }
  }
}

function zigZagDecodeInt(n) {
  return n === 0 ? n : (n & 1) === 0 ? n / 2 : -(n + 1) / 2
}

function zigZagEncodeInt(n) {
  // 0, -1, 1, -2, 2, ...
  return n < 0 ? 2 * -n - 1 : n === 0 ? 0 : 2 * n
}

function zigZagBigInt(enc) {
  return {
    preencode(state, n) {
      enc.preencode(state, zigZagEncodeBigInt(n))
    },
    encode(state, n) {
      enc.encode(state, zigZagEncodeBigInt(n))
    },
    decode(state) {
      return zigZagDecodeBigInt(enc.decode(state))
    }
  }
}

function zigZagDecodeBigInt(n) {
  return n === 0n ? n : (n & 1n) === 0n ? n / 2n : -(n + 1n) / 2n
}

function zigZagEncodeBigInt(n) {
  // 0, -1, 1, -2, 2, ...
  return n < 0n ? 2n * -n - 1n : n === 0n ? 0n : 2n * n
}

function validateUint(n) {
  if (n >= 0 === false /* Handles NaN as well */)
    throw new Error('uint must be positive')
  // Beyond this, Number arithmetic loses precision and silently corrupts the
  // encoding. The zig-zag int codecs route through here too, so this also
  // guards int values whose doubled magnitude exceeds the safe range.
  if (n > Number.MAX_SAFE_INTEGER)
    throw new Error(
      'integer is greater than the maximum safe integer, use biguint/bigint'
    )
}
module.exports = {
  preencode,
  encode,
  decode
}

function preencode(state, num) {
  if (num < 251) {
    state.end++
  } else if (num < 256) {
    state.end += 2
  } else if (num < 0x10000) {
    state.end += 3
  } else if (num < 0x1000000) {
    state.end += 4
  } else if (num < 0x100000000) {
    state.end += 5
  } else {
    state.end++
    const exp = Math.floor(Math.log(num) / Math.log(2)) - 32
    preencode(state, exp)
    state.end += 6
  }
}

function encode(state, num) {
  const max = 251
  const x = num - max

  if (num < max) {
    state.buffer[state.start++] = num
  } else if (num < 256) {
    state.buffer[state.start++] = max
    state.buffer[state.start++] = x
  } else if (num < 0x10000) {
    state.buffer[state.start++] = max + 1
    state.buffer[state.start++] = (x >> 8) & 0xff
    state.buffer[state.start++] = x & 0xff
  } else if (num < 0x1000000) {
    state.buffer[state.start++] = max + 2
    state.buffer[state.start++] = x >> 16
    state.buffer[state.start++] = (x >> 8) & 0xff
    state.buffer[state.start++] = x & 0xff
  } else if (num < 0x100000000) {
    state.buffer[state.start++] = max + 3
    state.buffer[state.start++] = x >> 24
    state.buffer[state.start++] = (x >> 16) & 0xff
    state.buffer[state.start++] = (x >> 8) & 0xff
    state.buffer[state.start++] = x & 0xff
  } else {
    // need to use Math here as bitwise ops are 32 bit
    const exp = Math.floor(Math.log(x) / Math.log(2)) - 32
    state.buffer[state.start++] = 0xff

    encode(state, exp)
    const rem = x / Math.pow(2, exp - 11)

    for (let i = 5; i >= 0; i--) {
      state.buffer[state.start++] = (rem / Math.pow(2, 8 * i)) & 0xff
    }
  }
}

function decode(state) {
  const max = 251

  if (state.end - state.start < 1) throw new Error('Out of bounds')

  const flag = state.buffer[state.start++]

  if (flag < max) return flag

  if (state.end - state.start < flag - max + 1) {
    throw new Error('Out of bounds.')
  }

  if (flag < 252) {
    return state.buffer[state.start++] + max
  }

  if (flag < 253) {
    return (
      (state.buffer[state.start++] << 8) + state.buffer[state.start++] + max
    )
  }

  if (flag < 254) {
    return (
      (state.buffer[state.start++] << 16) +
      (state.buffer[state.start++] << 8) +
      state.buffer[state.start++] +
      max
    )
  }

  // << 24 result may be interpreted as negative
  if (flag < 255) {
    return (
      state.buffer[state.start++] * 0x1000000 +
      (state.buffer[state.start++] << 16) +
      (state.buffer[state.start++] << 8) +
      state.buffer[state.start++] +
      max
    )
  }

  const exp = decode(state)

  if (state.end - state.start < 6) throw new Error('Out of bounds')

  let rem = 0
  for (let i = 5; i >= 0; i--) {
    rem += state.buffer[state.start++] * Math.pow(2, 8 * i)
  }

  return rem * Math.pow(2, exp - 11) + max
}
{
  "name": "compact-encoding",
  "version": "3.2.0",
  "description": "A series of compact encoding schemes for building small and fast parsers and serializers",
  "main": "index.js",
  "files": [
    "endian.js",
    "index.js",
    "lexint.js",
    "raw.js"
  ],
  "dependencies": {
    "b4a": "^1.3.0"
  },
  "devDependencies": {
    "brittle": "^3.0.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^1.0.0"
  },
  "scripts": {
    "format": "prettier . --write",
    "test": "prettier . --check && brittle test.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/holepunchto/compact-encoding.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/compact-encoding/issues"
  },
  "homepage": "https://github.com/holepunchto/compact-encoding"
}
const b4a = require('b4a')

const { BE } = require('./endian')

exports = module.exports = {
  preencode(state, b) {
    state.end += b.byteLength
  },
  encode(state, b) {
    state.buffer.set(b, state.start)
    state.start += b.byteLength
  },
  decode(state) {
    const b = state.buffer.subarray(state.start, state.end)
    state.start = state.end
    return b
  }
}

const buffer = (exports.buffer = {
  preencode(state, b) {
    uint8array.preencode(state, b)
  },
  encode(state, b) {
    uint8array.encode(state, b)
  },
  decode(state) {
    const b = state.buffer.subarray(state.start)
    state.start = state.end
    return b
  }
})

exports.binary = {
  ...buffer,
  preencode(state, b) {
    if (typeof b === 'string') utf8.preencode(state, b)
    else buffer.preencode(state, b)
  },
  encode(state, b) {
    if (typeof b === 'string') utf8.encode(state, b)
    else buffer.encode(state, b)
  }
}

exports.arraybuffer = {
  preencode(state, b) {
    state.end += b.byteLength
  },
  encode(state, b) {
    const view = new Uint8Array(b)

    state.buffer.set(view, state.start)
    state.start += b.byteLength
  },
  decode(state) {
    const b = new ArrayBuffer(state.end - state.start)
    const view = new Uint8Array(b)

    view.set(state.buffer.subarray(state.start))

    state.start = state.end

    return b
  }
}

function typedarray(TypedArray, swap) {
  const n = TypedArray.BYTES_PER_ELEMENT

  return {
    preencode(state, b) {
      state.end += b.byteLength
    },
    encode(state, b) {
      const view = new Uint8Array(b.buffer, b.byteOffset, b.byteLength)

      if (BE && swap) swap(view)

      state.buffer.set(view, state.start)
      state.start += b.byteLength
    },
    decode(state) {
      let b = state.buffer.subarray(state.start)
      if (b.byteOffset % n !== 0) b = new Uint8Array(b)

      if (BE && swap) swap(b)

      state.start = state.end

      return new TypedArray(b.buffer, b.byteOffset, b.byteLength / n)
    }
  }
}

const uint8array = (exports.uint8array = typedarray(Uint8Array))
exports.uint16array = typedarray(Uint16Array, b4a.swap16)
exports.uint32array = typedarray(Uint32Array, b4a.swap32)

exports.int8array = typedarray(Int8Array)
exports.int16array = typedarray(Int16Array, b4a.swap16)
exports.int32array = typedarray(Int32Array, b4a.swap32)

exports.biguint64array = typedarray(BigUint64Array, b4a.swap64)
exports.bigint64array = typedarray(BigInt64Array, b4a.swap64)

exports.float32array = typedarray(Float32Array, b4a.swap32)
exports.float64array = typedarray(Float64Array, b4a.swap64)

function string(encoding) {
  return {
    preencode(state, s) {
      state.end += b4a.byteLength(s, encoding)
    },
    encode(state, s) {
      state.start += b4a.write(state.buffer, s, state.start, encoding)
    },
    decode(state) {
      const s = b4a.toString(state.buffer, encoding, state.start)
      state.start = state.end
      return s
    }
  }
}

const utf8 = (exports.string = exports.utf8 = string('utf-8'))
exports.ascii = string('ascii')
exports.hex = string('hex')
exports.base64 = string('base64')
exports.ucs2 = exports.utf16le = string('utf16le')

exports.array = function array(enc) {
  return {
    preencode(state, list) {
      for (const value of list) enc.preencode(state, value)
    },
    encode(state, list) {
      for (const value of list) enc.encode(state, value)
    },
    decode(state) {
      const arr = []
      while (state.start < state.end) arr.push(enc.decode(state))
      return arr
    }
  }
}

exports.json = {
  preencode(state, v) {
    utf8.preencode(state, JSON.stringify(v))
  },
  encode(state, v) {
    utf8.encode(state, JSON.stringify(v))
  },
  decode(state) {
    return JSON.parse(utf8.decode(state))
  }
}

exports.ndjson = {
  preencode(state, v) {
    utf8.preencode(state, JSON.stringify(v) + '\n')
  },
  encode(state, v) {
    utf8.encode(state, JSON.stringify(v) + '\n')
  },
  decode(state) {
    return JSON.parse(utf8.decode(state))
  }
}
module.exports = function debounce (worker, context = null) {
  debounced.running = null
  return debounced

  async function debounced () {
    if (debounced.running !== null) {
      try {
        await debounced.running
      } catch (_) {
        // ignore - do not fail on old errors
      }
    }

    // another "thread" beat us to it, just piggy pack on that one
    if (debounced.running !== null) return debounced.running

    debounced.running = worker.call(context)

    try {
      return await debounced.running
    } finally {
      debounced.running = null
    }
  }
}
{
  "name": "debounceify",
  "version": "1.1.0",
  "description": "Tiny async debouncer",
  "main": "index.js",
  "dependencies": {},
  "devDependencies": {
    "standard": "^16.0.3",
    "tape": "^5.1.1"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/mafintosh/debounceify.git"
  },
  "scripts": {
    "test": "standard && tape test.js"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/mafintosh/debounceify/issues"
  },
  "homepage": "https://github.com/mafintosh/debounceify"
}
module.exports = require('bare-events')
{
  "name": "events-universal",
  "version": "1.0.1",
  "description": "Universal wrapper for the Node.js events module",
  "exports": {
    "./package": "./package.json",
    ".": {
      "bare": "./bare.js",
      "react-native": "./react-native.js",
      "default": "./default.js"
    }
  },
  "files": [
    "index.js",
    "default.js",
    "bare.js",
    "react-native.js"
  ],
  "scripts": {
    "test": "prettier . --check"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/events-universal.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/events-universal/issues"
  },
  "homepage": "https://github.com/holepunchto/events-universal#readme",
  "dependencies": {
    "bare-events": "^2.7.0"
  },
  "devDependencies": {
    "prettier": "^3.4.2",
    "prettier-config-holepunch": "^1.0.0"
  }
}
module.exports = class FixedFIFO {
  constructor (hwm) {
    if (!(hwm > 0) || ((hwm - 1) & hwm) !== 0) throw new Error('Max size for a FixedFIFO should be a power of two')
    this.buffer = new Array(hwm)
    this.mask = hwm - 1
    this.top = 0
    this.btm = 0
    this.next = null
  }

  clear () {
    this.top = this.btm = 0
    this.next = null
    this.buffer.fill(undefined)
  }

  push (data) {
    if (this.buffer[this.top] !== undefined) return false
    this.buffer[this.top] = data
    this.top = (this.top + 1) & this.mask
    return true
  }

  shift () {
    const last = this.buffer[this.btm]
    if (last === undefined) return undefined
    this.buffer[this.btm] = undefined
    this.btm = (this.btm + 1) & this.mask
    return last
  }

  peek () {
    return this.buffer[this.btm]
  }

  isEmpty () {
    return this.buffer[this.btm] === undefined
  }
}
const FixedFIFO = require('./fixed-size')

module.exports = class FastFIFO {
  constructor (hwm) {
    this.hwm = hwm || 16
    this.head = new FixedFIFO(this.hwm)
    this.tail = this.head
    this.length = 0
  }

  clear () {
    this.head = this.tail
    this.head.clear()
    this.length = 0
  }

  push (val) {
    this.length++
    if (!this.head.push(val)) {
      const prev = this.head
      this.head = prev.next = new FixedFIFO(2 * this.head.buffer.length)
      this.head.push(val)
    }
  }

  shift () {
    if (this.length !== 0) this.length--
    const val = this.tail.shift()
    if (val === undefined && this.tail.next) {
      const next = this.tail.next
      this.tail.next = null
      this.tail = next
      return this.tail.shift()
    }

    return val
  }

  peek () {
    const val = this.tail.peek()
    if (val === undefined && this.tail.next) return this.tail.next.peek()
    return val
  }

  isEmpty () {
    return this.length === 0
  }
}
{
  "name": "fast-fifo",
  "version": "1.3.2",
  "description": "A fast fifo implementation similar to the one powering nextTick in Node.js core",
  "main": "index.js",
  "files": [
    "./index.js",
    "./fixed-size.js"
  ],
  "dependencies": {},
  "devDependencies": {
    "standard": "^17.1.0",
    "brittle": "^3.3.2"
  },
  "scripts": {
    "test": "standard && brittle test.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/mafintosh/fast-fifo.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/mafintosh/fast-fifo/issues"
  },
  "homepage": "https://github.com/mafintosh/fast-fifo"
}
const codecs = require('codecs')
const { Readable } = require('streamx')
const mutexify = require('mutexify/promise')
const b4a = require('b4a')
const safetyCatch = require('safety-catch')
const ReadyResource = require('ready-resource')
const debounce = require('debounceify')
const Rache = require('rache')
const rrp = require('resolve-reject-promise')

const { all: unslabAll } = require('unslab')

const RangeIterator = require('./iterators/range')
const HistoryIterator = require('./iterators/history')
const DiffIterator = require('./iterators/diff')
const LocalBlockIterator = require('./iterators/local')
const Extension = require('./lib/extension')
const { YoloIndex, Node, Header } = require('./lib/messages')
const { BLOCK_NOT_AVAILABLE, DECODING_ERROR } = require('hypercore-errors')

const T = 5
const MIN_KEYS = T - 1
const MAX_CHILDREN = MIN_KEYS * 2 + 1

const SEP = b4a.alloc(1)
const EMPTY = b4a.alloc(0)

class Key {
  constructor(seq, value) {
    this.seq = seq
    this.value = value
  }
}

class Child {
  constructor(seq, offset, value) {
    this.seq = seq
    this.offset = offset
    this.value = value
  }
}

class Cache {
  constructor(rache) {
    this.keys = rache
    this.length = 0
  }

  get(seq) {
    return this.keys.get(seq) || null
  }

  set(seq, key) {
    this.keys.set(seq, key)
    if (seq >= this.length) this.length = seq + 1
  }

  gc(length) {
    // if we need to "work" more than 128 ticks, just bust the cache...
    if (this.length - length > 128) {
      this.keys.clear()
    } else {
      for (let i = length; i < this.length; i++) {
        this.keys.delete(i)
      }
    }

    this.length = length
  }

  clear() {
    this.keys.clear()
  }
}

class Pointers {
  constructor(decoded) {
    this.levels = decoded.levels.map((l) => {
      const children = []
      const keys = []

      for (let i = 0; i < l.keys.length; i++) {
        keys.push(new Key(l.keys[i], null))
      }

      for (let i = 0; i < l.children.length; i += 2) {
        children.push(new Child(l.children[i], l.children[i + 1], null))
      }

      return { keys, children }
    })
  }

  get(i) {
    return this.levels[i]
  }

  hasKey(seq) {
    for (const lvl of this.levels) {
      for (const key of lvl.keys) {
        if (key.seq === seq) return true
      }
    }
    return false
  }
}

function inflate(entry) {
  if (entry.inflated === null) {
    entry.inflated = YoloIndex.decode(entry.index)
    entry.index = null
  }
  return new Pointers(entry.inflated)
}

function deflate(index) {
  const levels = index.map((l) => {
    const keys = []
    const children = []

    for (let i = 0; i < l.value.keys.length; i++) {
      keys.push(l.value.keys[i].seq)
    }

    for (let i = 0; i < l.value.children.length; i++) {
      children.push(l.value.children[i].seq, l.value.children[i].offset)
    }

    return { keys, children }
  })

  return YoloIndex.encode({ levels })
}

class TreeNode {
  constructor(block, keys, children, offset) {
    this.block = block
    this.offset = offset
    this.keys = keys
    this.children = children
    this.changed = false

    this.preload()
  }

  preload() {
    if (this.block === null) return

    const core = getBackingCore(this.block.tree.core)
    if (!core) return

    const bitfield = core.core.bitfield
    const blocks = []

    for (let i = 0; i < this.keys.length; i++) {
      const k = this.keys[i]
      if (k.value) continue
      if (k.seq >= core.signedLength || (bitfield && bitfield.get(k.seq))) continue
      blocks.push(k.seq)
    }
    for (let i = 0; i < this.children.length; i++) {
      const c = this.children[i]
      if (c.value) continue
      if (c.seq >= core.signedLength || (bitfield && bitfield.get(c.seq))) continue
      blocks.push(c.seq)
    }

    if (blocks.length) core.download({ blocks })
  }

  async insertKey(key, value, child, node, encoding, cas) {
    let s = 0
    let e = this.keys.length
    let c

    while (s < e) {
      const mid = (s + e) >> 1
      c = b4a.compare(key.value, await this.getKey(mid))

      if (c === 0) {
        if (cas) {
          const prev = await this.getKeyNode(mid)
          if (!(await cas(prev.final(encoding), node))) return true
        }
        if (!this.block.tree.tree.alwaysDuplicate) {
          const prev = await this.getKeyNode(mid)
          if (sameValue(prev.value, value)) return true
        }
        this.changed = true
        this.keys[mid] = key
        return true
      }

      if (c < 0) e = mid
      else s = mid + 1
    }

    const i = c < 0 ? e : s
    this.keys.splice(i, 0, key)
    if (child) this.children.splice(i + 1, 0, new Child(0, 0, child))
    this.changed = true

    return this.keys.length < MAX_CHILDREN
  }

  removeKey(index) {
    this.keys.splice(index, 1)
    if (this.children.length) {
      this.children[index + 1].seq = 0 // mark as freed
      this.children.splice(index + 1, 1)
    }
    this.changed = true
  }

  async siblings(parent) {
    for (let i = 0; i < parent.children.length; i++) {
      if (parent.children[i].value === this) {
        const [left, right] = await Promise.all([
          i ? parent.getChildNode(i - 1) : null,
          i < parent.children.length - 1 ? parent.getChildNode(i + 1) : null
        ])
        return { left, index: i, right }
      }
    }

    throw new Error('Bad parent')
  }

  merge(node, median) {
    this.changed = true
    this.keys.push(median)
    for (let i = 0; i < node.keys.length; i++) this.keys.push(node.keys[i])
    for (let i = 0; i < node.children.length; i++) this.children.push(node.children[i])
  }

  async split() {
    const len = this.keys.length >> 1
    const right = TreeNode.create(this.block)

    while (right.keys.length < len) right.keys.push(this.keys.pop())
    right.keys.reverse()

    await this.getKey(this.keys.length - 1) // make sure the median is loaded
    const median = this.keys.pop()

    if (this.children.length) {
      while (right.children.length < len + 1) right.children.push(this.children.pop())
      right.children.reverse()
    }

    this.changed = true

    return {
      left: this,
      median,
      right
    }
  }

  getKeyNode(index) {
    return this.block.tree.getBlock(this.keys[index].seq)
  }

  async getChildNode(index) {
    const child = this.children[index]
    if (child.value) return child.value
    const block =
      child.seq === this.block.seq ? this.block : await this.block.tree.getBlock(child.seq)
    return (child.value = block.getTreeNode(child.offset))
  }

  setKey(index, key) {
    this.keys[index] = key
    this.changed = true
  }

  async getKey(index) {
    const key = this.keys[index]
    if (key.value) return key.value
    const k = key.seq === this.block.seq ? this.block.key : await this.block.tree.getKey(key.seq)
    return (key.value = k)
  }

  indexChanges(index, seq) {
    const offset = index.push(null) - 1
    this.changed = false

    for (const child of this.children) {
      if (!child.value || !child.value.changed) continue
      child.seq = seq
      child.offset = child.value.indexChanges(index, seq)
      index[child.offset] = child
    }

    return offset
  }

  updateChildren(seq, block) {
    for (const child of this.children) {
      if (!child.value || child.seq !== seq) continue
      child.value.block = block
      child.value.updateChildren(seq, block)
    }
  }

  static create(block) {
    const node = new TreeNode(block, [], [], 0)
    node.changed = true
    return node
  }
}

class BlockEntry {
  constructor(seq, tree, entry) {
    this.seq = seq
    this.tree = tree
    this.index = null
    this.entry = entry
    this.key = entry.key
    this.value = entry.value
  }

  isTarget(key) {
    return b4a.equals(this.key, key)
  }

  inflate() {
    if (this.index === null) {
      this.index = inflate(this.entry)
    }
  }

  isDeletion() {
    if (this.value !== null) return false

    if (this.index === null) {
      this.index = inflate(this.entry)
    }

    return !this.index.hasKey(this.seq)
  }

  final(encoding) {
    return {
      seq: this.seq,
      key: encoding.key ? encoding.key.decode(this.key) : this.key,
      value: this.value && (encoding.value ? encoding.value.decode(this.value) : this.value)
    }
  }

  getTreeNode(offset) {
    if (this.index === null) {
      this.index = inflate(this.entry)
    }
    const entry = this.index.get(offset)
    return new TreeNode(this, entry.keys, entry.children, offset)
  }
}

class CacheLock {
  constructor() {
    this.map = new Map()
  }

  enter(seq) {
    const pending = this.map.get(seq)

    if (!pending) {
      this.map.set(seq, [])
      return Promise.resolve()
    }

    const { resolve, promise } = rrp()
    pending.push(resolve)
    return promise
  }

  exit(seq) {
    const pending = this.map.get(seq)
    if (!pending.length) {
      this.map.delete(seq)
      return
    }

    pending.pop()()
  }
}

class BatchEntry extends BlockEntry {
  constructor(seq, tree, key, value, index) {
    super(seq, tree, { key, value, index: null, inflated: null })
    this.pendingIndex = index
  }

  isTarget(key) {
    return false
  }

  getTreeNode(offset) {
    return this.pendingIndex[offset].value
  }
}

class Hyperbee extends ReadyResource {
  constructor(core, opts = {}) {
    super()
    // this.feed is now deprecated, and will be this.core going forward
    this.feed = core
    this.core = core

    this.keyEncoding = opts.keyEncoding ? codecs(opts.keyEncoding) : null
    this.valueEncoding = opts.valueEncoding ? codecs(opts.valueEncoding) : null
    this.extension = opts.extension !== false ? getExtension(this, opts) : null
    this.metadata = opts.metadata || null
    this.lock = opts.lock || mutexify()
    this.sep = opts.sep || SEP
    this.readonly = !!opts.readonly
    this.prefix = opts.prefix || null

    // In a future version, this should be false by default
    this.alwaysDuplicate = opts.alwaysDuplicate !== false

    this._unprefixedKeyEncoding = this.keyEncoding
    this._sub = !!this.prefix
    this._checkout = opts.checkout || 0
    this._view = !!opts._view

    this._onappendBound = this._view ? null : this._onappend.bind(this)
    this._ontruncateBound = this._view ? null : this._ontruncate.bind(this)
    this._watchers = this._onappendBound ? [] : null
    this._entryWatchers = this._onappendBound ? [] : null
    this._sessions = opts.sessions !== false

    this._keyCache = null
    this._nodeCache = null
    this._cacheLock = new CacheLock()

    this._batches = []

    if (this._watchers) {
      this.core.on('append', this._onappendBound)
      this.core.on('truncate', this._ontruncateBound)
    }

    if (this.prefix && opts._sub) {
      this.keyEncoding = prefixEncoding(this.prefix, this.keyEncoding)
    }

    this.ready().catch(safetyCatch)
  }

  async _open() {
    if (this.core.opened === false) await this.core.ready()
    // bump the internal ranges since the nodes are small
    if (this._watchers && this.core.replicator.setInflightRange) {
      this.core.replicator.setInflightRange(256, 512)
    }

    // snapshot
    if (this._checkout === -1) this._checkout = Math.max(1, this.core.length)

    const baseCache = Rache.from(this.core.globalCache)
    this._keyCache = new Cache(baseCache)
    this._nodeCache = new Cache(Rache.from(baseCache))
  }

  get version() {
    return Math.max(1, this._checkout || this.core.length)
  }

  get id() {
    return this.core.id
  }

  get key() {
    return this.core.key
  }

  get discoveryKey() {
    return this.core.discoveryKey
  }

  get writable() {
    return this.core.writable
  }

  get readable() {
    return this.core.readable
  }

  replicate(isInitiator, opts) {
    return this.core.replicate(isInitiator, opts)
  }

  update(opts) {
    return this.core.update(opts)
  }

  peek(range, opts) {
    return iteratorPeek(this.createRangeIterator(range, { ...opts, limit: 1 }))
  }

  createRangeIterator(range, opts = {}) {
    // backwards compat range arg
    opts = opts ? { ...opts, ...range } : range

    const extension = opts.extension === false && opts.limit !== 0 ? null : this.extension
    const keyEncoding = opts.keyEncoding ? codecs(opts.keyEncoding) : this.keyEncoding

    if (extension) {
      const { onseq, onwait } = opts
      let version = 0
      let next = 0

      opts = encRange(keyEncoding, {
        ...opts,
        sub: this._sub,
        onseq(seq) {
          if (!version) version = seq + 1
          if (next) next--
          if (onseq) onseq(seq)
        },
        onwait(seq) {
          if (!next) {
            next = Extension.BATCH_SIZE
            extension.iterator(ite.snapshot(version))
          }
          if (onwait) onwait(seq)
        }
      })
    } else {
      opts = encRange(keyEncoding, { ...opts, sub: this._sub })
    }

    const ite = new RangeIterator(
      new Batch(this, this._makeSnapshot(), null, false, opts),
      null,
      opts
    )
    return ite
  }

  createReadStream(range, opts) {
    const signal = (opts && opts.signal) || null
    return iteratorToStream(this.createRangeIterator(range, opts), signal)
  }

  createHistoryStream(opts) {
    const session = opts && opts.live ? this.core.session() : this._makeSnapshot()
    const signal = (opts && opts.signal) || null
    return iteratorToStream(
      new HistoryIterator(new Batch(this, session, null, false, opts), opts),
      signal
    )
  }

  createDiffStream(right, range, opts) {
    if (typeof right === 'number') right = this.checkout(Math.max(1, right), { reuseSession: true })

    // backwards compat range arg
    opts = opts ? { ...opts, ...range } : range

    const signal = (opts && opts.signal) || null

    const keyEncoding = opts && opts.keyEncoding ? codecs(opts.keyEncoding) : this.keyEncoding
    if (keyEncoding) opts = encRange(keyEncoding, { ...opts, sub: this._sub })

    let done
    let closing
    let ite

    const left = this

    const rs = new Readable({
      signal,
      eagerOpen: true,
      async open(cb) {
        try {
          if (right.opened === false) await right.ready()
          if (left.opened === false) await left.ready()
        } catch (err) {
          cb(err)
          return
        }

        if (closing) {
          cb(null)
          return
        }

        if (left.core.closing || right.core.closing) {
          cb(new Error('Bee closed'))
          return
        }

        const snapshot = right.version > left.version ? right._makeSnapshot() : left._makeSnapshot()

        done = cb
        ite = new DiffIterator(
          new Batch(left, snapshot, null, false, opts),
          new Batch(right, snapshot, null, false, opts),
          opts
        )
        ite.open().then(fin, fin)
      },
      read(cb) {
        done = cb
        ite.next().then(push, fin)
      },
      predestroy() {
        if (!ite) {
          closing = Promise.resolve()
        } else {
          closing = ite.close()
          closing.catch(noop)
        }
      },
      destroy(cb) {
        done = cb
        if (!closing) closing = ite.close()
        closing.then(fin, fin)
      }
    })

    return rs

    function fin(err) {
      done(err)
    }

    function push(val) {
      rs.push(val)
      done(null)
    }
  }

  get(key, opts) {
    const b = new Batch(this, this._makeSnapshot(), null, true, opts)
    return b.get(key)
  }

  getBySeq(seq, opts) {
    const b = new Batch(this, this._makeSnapshot(), null, true, opts)
    return b.getBySeq(seq)
  }

  put(key, value, opts) {
    const b = new Batch(this, this.core, null, true, opts)
    return b.put(key, value, opts)
  }

  batch(opts) {
    return new Batch(this, this.core, mutexify(), true, opts)
  }

  del(key, opts) {
    const b = new Batch(this, this.core, null, true, opts)
    return b.del(key, opts)
  }

  watch(range, opts) {
    if (!this._watchers) throw new Error('Can only watch the main bee instance')
    return new Watcher(this, range, opts)
  }

  async getAndWatch(key, opts) {
    if (!this._watchers) throw new Error('Can only watch the main bee instance')

    const watcher = new EntryWatcher(this, key, opts)
    await watcher._debouncedUpdate()

    if (this.closing) {
      await watcher.close()
      throw new Error('Bee closed')
    }

    return watcher
  }

  _onappend() {
    for (const watcher of this._watchers) {
      watcher._onappend()
    }

    for (const watcher of this._entryWatchers) {
      watcher._onappend()
    }
  }

  _ontruncate(length) {
    for (const watcher of this._watchers) {
      watcher._ontruncate()
    }

    for (const watcher of this._entryWatchers) {
      watcher._ontruncate()
    }

    this._nodeCache.gc(length)
    this._keyCache.gc(length)
  }

  _makeSnapshot() {
    if (this._sessions === false) return this.core
    // TODO: better if we could encapsulate this in hypercore in the future
    return this._checkout <= this.core.length || this._checkout <= 1
      ? this.core.snapshot()
      : this.core.session({ snapshot: false })
  }

  async clearUnlinked(options = {}) {
    await this.ready()

    const { gte = 0, lt = this.version - 1, batchSize = 4096, wait = true } = options
    const checkout = this.version

    let prev = this.batch({ wait: false, checkout: gte })
    let b = this.batch({ wait, checkout })

    const iteBatch = this.batch({ wait: false, checkout })
    const ite = new LocalBlockIterator(iteBatch, { gte, lt })
    await ite.open()

    let ticks = 0

    while (true) {
      const data = await ite.next()
      if (!data) break

      if (!(await isLinked(b, data))) {
        if (b.core.closing || this.core.closing || this.closing) break
        await this.core.clear(data.seq)
      }

      const prevNode = await prev.get(data.key, { finalize: false }).catch(toNull)

      if (prevNode && !(await isLinked(b, prevNode))) {
        if (b.core.closing || this.core.closing || this.closing) break
        await this.core.clear(prevNode.seq)
      }

      if (ticks++ >= batchSize) {
        ticks = 0
        await prev.close()
        await b.close()

        prev = this.batch({ wait: false, checkout: gte })
        b = this.batch({ wait, checkout })
      }
    }

    if (b.core.closing || this.core.closing || this.closing) throw new Error('Core is closed')

    await b.close()
    await prev.close()
    await ite.close()
    await iteBatch.close()

    return lt
  }

  checkout(version, opts = {}) {
    if (version === 0) version = 1

    // same as above, just checkout isn't set yet...
    const snap =
      opts.reuseSession || this._sessions === false
        ? this.core
        : version <= this.core.length || version <= 1
          ? this.core.snapshot()
          : this.core.session({ snapshot: false })

    return new Hyperbee(snap, {
      _view: true,
      _sub: false,
      prefix: this.prefix,
      sep: this.sep,
      lock: this.lock,
      checkout: version,
      keyEncoding: opts.keyEncoding || this.keyEncoding,
      valueEncoding: opts.valueEncoding || this.valueEncoding,
      extension: this.extension !== null ? this.extension : false
    })
  }

  snapshot(opts) {
    return this.checkout(
      this.core.opened === false || this._checkout <= 0 ? -1 : Math.max(1, this.version),
      opts
    )
  }

  sub(prefix, opts = {}) {
    let sep = opts.sep || this.sep
    if (!b4a.isBuffer(sep)) sep = b4a.from(sep)

    prefix = b4a.concat([this.prefix || EMPTY, b4a.from(prefix), sep])

    const valueEncoding = codecs(opts.valueEncoding || this.valueEncoding)
    const keyEncoding = codecs(opts.keyEncoding || this._unprefixedKeyEncoding)

    return new Hyperbee(this.core, {
      _view: true,
      _sub: true,
      prefix,
      sep: this.sep,
      lock: this.lock,
      checkout: this._checkout,
      valueEncoding,
      keyEncoding,
      extension: this.extension !== null ? this.extension : false,
      metadata: this.metadata
    })
  }

  async getHeader(opts) {
    const blk = await this.core.get(0, opts)
    try {
      return blk && Header.decode(blk)
    } catch {
      throw DECODING_ERROR()
    }
  }

  async _close() {
    if (!this._view) {
      if (this._keyCache) this._keyCache.clear()
      if (this._nodeCache) this._nodeCache.clear()
    }

    if (this._watchers) {
      this.core.off('append', this._onappendBound)
      this.core.off('truncate', this._ontruncateBound)

      while (this._watchers.length) {
        await this._watchers[this._watchers.length - 1].close()
      }
    }

    if (this._entryWatchers) {
      while (this._entryWatchers.length) {
        await this._entryWatchers[this._entryWatchers.length - 1].close()
      }
    }

    while (this._batches.length) {
      await this._batches[this._batches.length - 1].close()
    }

    return this.core.close()
  }

  static async isHyperbee(core, opts) {
    await core.ready()

    const blk0 = await core.get(0, opts)
    if (blk0 === null) throw BLOCK_NOT_AVAILABLE()

    try {
      return Header.decode(blk0).protocol === 'hyperbee'
    } catch (err) {
      // undecodable
      return false
    }
  }
}

class Batch {
  constructor(tree, core, batchLock, cache, options = {}) {
    this.tree = tree
    // this.feed is now deprecated, and will be this.core going forward
    this.feed = core
    this.core = core
    this.index = tree._batches.push(this) - 1
    this.blocks = cache ? new Map() : null
    this.autoFlush = !batchLock
    this.maxBlocksCached = options.maxBlocksCached || 128
    this.rootSeq = 0
    this.root = null
    this.length = 0
    this.checkout = options.checkout === undefined ? -1 : options.checkout
    this.options = options
    this.locked = null
    this.batchLock = batchLock
    this.onseq = this.options.onseq || noop
    this.appending = null
    this.isSnapshot = this.core !== this.tree.core
    this.shouldUpdate = this.options.update !== false
    this.updating = null
    this.encoding = {
      key: options.keyEncoding ? codecs(options.keyEncoding) : tree.keyEncoding,
      value: options.valueEncoding ? codecs(options.valueEncoding) : tree.valueEncoding
    }
  }

  async ready() {
    if (this.core.opened === false) await this.core.ready()
    if (this.tree.opened === false) await this.tree.ready()
  }

  async lock() {
    if (this.tree.readonly) throw new Error('Hyperbee is marked as read-only')
    if (this.locked === null) this.locked = await this.tree.lock()
  }

  get version() {
    if (this.checkout !== -1) return Math.max(1, this.checkout)
    return Math.max(1, this.tree._checkout || this.core.length + this.length)
  }

  async getRoot(ensureHeader) {
    await this.ready()
    if (ensureHeader) {
      if (this.core.length === 0 && this.core.writable && !this.tree.readonly) {
        await this.core.append(
          Header.encode({
            protocol: 'hyperbee',
            metadata: this.tree.metadata
          })
        )
      }
    }
    if (this.tree._checkout === 0 && this.checkout === -1 && this.shouldUpdate) {
      if (this.updating === null) this.updating = this.core.update()
      await this.updating
    }
    if (this.version < 2) return null
    return (await this.getBlock(this.version - 1)).getTreeNode(0)
  }

  async getKey(seq) {
    await this.tree._cacheLock.enter(seq)

    try {
      const k = this.core.fork === this.tree.core.fork ? this.tree._keyCache.get(seq) : null
      if (k !== null) return k
      const key = (await this._getBlock(seq)).key
      if (this.core.fork === this.tree.core.fork) this.tree._keyCache.set(seq, key)
      return key
    } finally {
      this.tree._cacheLock.exit(seq)
    }
  }

  async _getNode(seq) {
    const cached =
      this.tree._nodeCache !== null && this.core.fork === this.tree.core.fork
        ? this.tree._nodeCache.get(seq)
        : null
    if (cached !== null) return cached
    const entry = await this.core.get(seq, {
      ...this.options,
      valueEncoding: Node
    })
    if (entry === null) throw BLOCK_NOT_AVAILABLE()
    const wrap = copyEntry(entry)
    if (this.core.fork === this.tree.core.fork && this.tree._nodeCache !== null) {
      this.tree._nodeCache.set(seq, wrap)
    }
    return wrap
  }

  async getBlock(seq) {
    if (this.rootSeq === 0) this.rootSeq = seq
    await this.tree._cacheLock.enter(seq)

    try {
      return await this._getBlock(seq)
    } finally {
      this.tree._cacheLock.exit(seq)
    }
  }

  async _getBlock(seq) {
    let b = this.blocks && this.blocks.get(seq)
    if (b) return b
    this.onseq(seq)
    const entry = await this._getNode(seq)
    b = this.blocks && this.blocks.get(seq)
    if (b) return b
    b = new BlockEntry(seq, this, entry)
    if (this.blocks && this.blocks.size - this.length < this.maxBlocksCached) {
      this.blocks.set(seq, b)
    }
    return b
  }

  _onwait(key) {
    this.options.onwait = null
    this.tree.extension.get(this.rootSeq + 1, key)
  }

  _getEncoding(opts) {
    if (!opts) return this.encoding
    return {
      key: opts.keyEncoding ? codecs(opts.keyEncoding) : this.encoding.key,
      value: opts.valueEncoding ? codecs(opts.valueEncoding) : this.encoding.value
    }
  }

  peek(range, opts) {
    return iteratorPeek(this.createRangeIterator(range, { ...opts, limit: 1 }))
  }

  createRangeIterator(range, opts = {}) {
    // backwards compat range arg
    opts = opts ? { ...opts, ...range } : range

    const encoding = this._getEncoding(opts)
    return new RangeIterator(
      this,
      encoding,
      encRange(encoding.key, { ...opts, sub: this.tree._sub })
    )
  }

  createReadStream(range, opts) {
    const signal = (opts && opts.signal) || null
    return iteratorToStream(this.createRangeIterator(range, opts), signal)
  }

  async getBySeq(seq, opts) {
    const encoding = this._getEncoding(opts)

    try {
      const block = (await this.getBlock(seq)).final(encoding)
      return { key: block.key, value: block.value }
    } finally {
      await this._closeSnapshot()
    }
  }

  async get(key, opts) {
    const encoding = this._getEncoding(opts)
    const finalize = opts ? opts.finalize !== false : true

    try {
      return await this._get(key, encoding, finalize)
    } finally {
      await this._closeSnapshot()
    }
  }

  async _get(key, encoding, finalize) {
    key = enc(encoding.key, key)

    if (this.tree.extension !== null && this.options.extension !== false) {
      this.options.onwait = this._onwait.bind(this, key)
    }

    let node = await this.getRoot(false)
    if (!node) return null

    while (true) {
      if (node.block.isTarget(key)) {
        return node.block.isDeletion() ? null : finalize ? node.block.final(encoding) : node.block
      }

      let s = 0
      let e = node.keys.length
      let c

      while (s < e) {
        const mid = (s + e) >> 1

        c = b4a.compare(key, await node.getKey(mid))

        if (c === 0) {
          const block = await this.getBlock(node.keys[mid].seq)
          return finalize ? block.final(encoding) : block
        }

        if (c < 0) e = mid
        else s = mid + 1
      }

      if (!node.children.length) return null

      const i = c < 0 ? e : s
      node = await node.getChildNode(i)
    }
  }

  async links(key, seq) {
    let node = await this.getRoot(false)
    if (!node) return false

    if (node.block.seq === seq) return true

    while (true) {
      if (node.block.isTarget(key)) return false

      let s = 0
      let e = node.keys.length
      let c

      while (s < e) {
        const mid = (s + e) >> 1

        if (node.keys[mid].seq === seq) return true
        c = b4a.compare(key, await node.getKey(mid))

        if (c === 0) return false

        if (c < 0) e = mid
        else s = mid + 1
      }

      if (!node.children.length) return false

      const i = c < 0 ? e : s
      node = await node.getChildNode(i)
      if (node.block.seq === seq) return true
    }
  }

  async put(key, value, opts) {
    const release = this.batchLock ? await this.batchLock() : null

    const cas = (opts && opts.cas) || null
    const encoding = this._getEncoding(opts)

    if (!this.locked) await this.lock()
    if (!release) return this._put(key, value, encoding, cas)

    try {
      return await this._put(key, value, encoding, cas)
    } finally {
      release()
    }
  }

  async _put(key, value, encoding, cas) {
    const newNode = {
      seq: 0,
      key,
      value
    }
    key = enc(encoding.key, key)
    value = enc(encoding.value, value)

    const stack = []

    let root
    let node = (root = await this.getRoot(true))
    if (!node) node = root = TreeNode.create(null)

    const seq = (newNode.seq = this.core.length + this.length)
    const target = new Key(seq, key)

    while (node.children.length) {
      stack.push(node)
      node.changed = true // changed, but compressible

      let s = 0
      let e = node.keys.length
      let c

      while (s < e) {
        const mid = (s + e) >> 1
        c = b4a.compare(target.value, await node.getKey(mid))

        if (c === 0) {
          if (cas) {
            const prev = await node.getKeyNode(mid)
            if (!(await cas(prev.final(encoding), newNode))) return this._unlockMaybe()
          }
          if (!this.tree.alwaysDuplicate) {
            const prev = await node.getKeyNode(mid)
            if (sameValue(prev.value, value)) return this._unlockMaybe()
          }
          node.setKey(mid, target)
          return this._append(root, seq, key, value)
        }

        if (c < 0) e = mid
        else s = mid + 1
      }

      const i = c < 0 ? e : s
      node = await node.getChildNode(i)
    }

    let needsSplit = !(await node.insertKey(target, value, null, newNode, encoding, cas))
    if (!node.changed) return this._unlockMaybe()

    while (needsSplit) {
      const parent = stack.pop()
      const { median, right } = await node.split()

      if (parent) {
        needsSplit = !(await parent.insertKey(median, value, right, null, encoding, null))
        node = parent
      } else {
        root = TreeNode.create(node.block)
        root.changed = true
        root.keys.push(median)
        root.children.push(new Child(0, 0, node), new Child(0, 0, right))
        needsSplit = false
      }
    }

    return this._append(root, seq, key, value)
  }

  async del(key, opts) {
    const release = this.batchLock ? await this.batchLock() : null
    const cas = (opts && opts.cas) || null
    const encoding = this._getEncoding(opts)

    if (!this.locked) await this.lock()
    if (!release) return this._del(key, encoding, cas)

    try {
      return await this._del(key, encoding, cas)
    } finally {
      release()
    }
  }

  async _del(key, encoding, cas) {
    const delNode = {
      seq: 0,
      key,
      value: null
    }

    key = enc(encoding.key, key)

    const stack = []

    let node = await this.getRoot(true)
    if (!node) return this._unlockMaybe()

    const seq = (delNode.seq = this.core.length + this.length)

    while (true) {
      stack.push(node)

      let s = 0
      let e = node.keys.length
      let c

      while (s < e) {
        const mid = (s + e) >> 1
        c = b4a.compare(key, await node.getKey(mid))

        if (c === 0) {
          if (cas) {
            const prev = await node.getKeyNode(mid)
            if (!(await cas(prev.final(encoding), delNode))) return this._unlockMaybe()
          }
          if (node.children.length) await setKeyToNearestLeaf(node, mid, stack)
          else node.removeKey(mid)
          // we mark these as changed late, so we don't rewrite them if it is a 404
          for (const node of stack) node.changed = true
          return this._append(await rebalance(stack), seq, key, null)
        }

        if (c < 0) e = mid
        else s = mid + 1
      }

      if (!node.children.length) return this._unlockMaybe()

      const i = c < 0 ? e : s
      node = await node.getChildNode(i)
    }
  }

  async _closeSnapshot() {
    if (this.isSnapshot) {
      await this.core.close()
      this._finalize()
    }
  }

  async close() {
    if (this.isSnapshot) return this._closeSnapshot()

    this.root = null
    if (this.blocks) this.blocks.clear()
    this.length = 0
    this._unlock()
  }

  destroy() {
    // compat, remove later
    this.close().catch(noop)
  }

  toBlocks() {
    if (this.appending) return this.appending

    const batch = new Array(this.length)

    for (let i = 0; i < this.length; i++) {
      const seq = this.core.length + i
      const { pendingIndex, key, value } = this.blocks.get(seq)

      if (i < this.length - 1) {
        pendingIndex[0] = null
        let j = 0

        while (j < pendingIndex.length) {
          const idx = pendingIndex[j]
          if (idx !== null && idx.seq === seq) {
            idx.offset = j++
            continue
          }
          if (j === pendingIndex.length - 1) pendingIndex.pop()
          else pendingIndex[j] = pendingIndex.pop()
        }
      }

      batch[i] = Node.encode({
        key,
        value,
        index: deflate(pendingIndex)
      })
    }

    this.appending = batch
    return batch
  }

  flush() {
    if (!this.length) return this.close()

    const batch = this.toBlocks()

    this.root = null
    this.blocks.clear()
    this.length = 0

    return this._appendBatch(batch)
  }

  _unlockMaybe() {
    if (this.autoFlush) this._unlock()
  }

  _unlock() {
    const locked = this.locked
    this.locked = null
    if (locked !== null) locked()
    this._finalize()
  }

  _finalize() {
    // technically finalize can be called more than once, so here we just check if we already have been removed
    if (this.index >= this.tree._batches.length || this.tree._batches[this.index] !== this) return
    const top = this.tree._batches.pop()
    if (top === this) return
    top.index = this.index
    this.tree._batches[top.index] = top
  }

  _append(root, seq, key, value) {
    const index = []
    root.indexChanges(index, seq)
    index[0] = new Child(seq, 0, root)

    if (!this.autoFlush) {
      const block = new BatchEntry(seq, this, key, value, index)
      root.block = block
      this.root = root
      this.length++
      this.blocks.set(seq, block)

      root.updateChildren(seq, block)
      return
    }

    return this._appendBatch(
      Node.encode({
        key,
        value,
        index: deflate(index)
      })
    )
  }

  async _appendBatch(raw) {
    try {
      await this.core.append(raw)
    } finally {
      this._unlock()
    }
  }
}

class EntryWatcher extends ReadyResource {
  constructor(bee, key, opts = {}) {
    super()

    this.keyEncoding = opts.keyEncoding || bee.keyEncoding
    this.valueEncoding = opts.valueEncoding || bee.valueEncoding

    this.index = bee._entryWatchers.push(this) - 1
    this.bee = bee

    this.key = key
    this.node = null

    this._forceUpdate = false
    this._debouncedUpdate = debounce(this._processUpdate.bind(this))
  }

  _close() {
    const top = this.bee._entryWatchers.pop()
    if (top !== this) {
      top.index = this.index
      this.bee._entryWatchers[top.index] = top
    }
  }

  _onappend() {
    this._debouncedUpdate()
  }

  _ontruncate() {
    this._forceUpdate = true
    this._debouncedUpdate()
  }

  async _processUpdate() {
    const force = this._forceUpdate
    this._forceUpdate = false

    let newNode
    try {
      newNode = await this.bee.get(this.key, {
        keyEncoding: this.keyEncoding,
        valueEncoding: this.valueEncoding
      })
    } catch (e) {
      if (e.code === 'SNAPSHOT_NOT_AVAILABLE') {
        // There was a truncate event before the get resolved
        // So this handler will run again anyway
        return
      } else if (this.bee.closing) {
        this.close().catch(safetyCatch)
        return
      }
      this.emit('error', e)
      return
    }

    if (force || newNode?.seq !== this.node?.seq) {
      this.node = newNode
      this.emit('update')
    }
  }
}

class Watcher extends ReadyResource {
  constructor(bee, range, opts = {}) {
    super()

    this.keyEncoding = opts.keyEncoding || bee.keyEncoding
    this.valueEncoding = opts.valueEncoding || bee.valueEncoding
    this.index = bee._watchers.push(this) - 1
    this.bee = bee
    this.core = bee.core

    this.latestDiff = 0
    this.range = range
    this.map = opts.map || defaultWatchMap

    this.current = null
    this.previous = null
    this.currentMapped = null
    this.previousMapped = null
    this.stream = null

    this._lock = mutexify()
    this._flowing = false
    this._resolveOnChange = null
    this._differ = opts.differ || defaultDiffer
    this._eager = !!opts.eager
    this._onchange = opts.onchange || null

    this.on('newListener', autoFlowOnUpdate)

    this.ready().catch(safetyCatch)
  }

  async _consume() {
    if (this._flowing) return
    try {
      for await (const _ of this) {
      } // eslint-disable-line
    } catch {}
  }

  async _open() {
    await this.bee.ready()

    const opts = {
      keyEncoding: this.keyEncoding,
      valueEncoding: this.valueEncoding
    }

    // Point from which to start watching
    this.current = this._eager ? this.bee.checkout(1, opts) : this.bee.snapshot(opts)
    await this.current.ready()

    if (this._onchange) {
      if (this._eager) await this._onchange()
      this._consume()
    }
  }

  [Symbol.asyncIterator]() {
    this._flowing = true
    return this
  }

  _ontruncate() {
    this._onappend()
  }

  _onappend() {
    const resolve = this._resolveOnChange
    this._resolveOnChange = null
    if (resolve) resolve()
  }

  async _waitForChanges() {
    if (this.current.version < this.bee.version || this.closing) return

    await new Promise((resolve) => {
      this._resolveOnChange = resolve
    })
  }

  async next() {
    try {
      return await this._next()
    } catch (err) {
      if (this.closing) return { value: undefined, done: true }
      await this.close()
      throw err
    }
  }

  async _next() {
    const release = await this._lock()

    try {
      if (this.closing) return { value: undefined, done: true }

      if (!this.opened) await this.ready()

      while (true) {
        await this._waitForChanges()

        if (this.closing) return { value: undefined, done: true }

        await this._closePrevious()
        this.previous = this.current.snapshot()

        await this._closeCurrent()
        this.current = this.bee.snapshot({
          keyEncoding: this.keyEncoding,
          valueEncoding: this.valueEncoding
        })

        await this.current.ready()
        await this.previous.ready()

        if (this.current.core.fork !== this.previous.core.fork) {
          return await this._yield()
        }

        this.stream = this._differ(this.current, this.previous, this.range)

        try {
          for await (const data of this.stream) {
            // eslint-disable-line
            return await this._yield()
          }
        } finally {
          this.stream = null
        }
      }
    } finally {
      release()
    }
  }

  async _yield() {
    this.currentMapped = this.map(this.current)
    this.previousMapped = this.map(this.previous)

    if (this._onchange) {
      try {
        await this._onchange()
      } catch (err) {
        safetyCatch(err)
      }
    }

    this.emit('update')
    return { done: false, value: [this.currentMapped, this.previousMapped] }
  }

  async return() {
    await this.close()
    return { done: true }
  }

  async _close() {
    const top = this.bee._watchers.pop()
    if (top !== this) {
      top.index = this.index
      this.bee._watchers[top.index] = top
    }

    if (this.stream && !this.stream.destroying) {
      this.stream.destroy()
    }

    this._onappend() // Continue execution being closed

    await this._closeCurrent().catch(safetyCatch)
    await this._closePrevious().catch(safetyCatch)

    const release = await this._lock()
    release()
  }

  destroy() {
    return this.close()
  }

  async _closeCurrent() {
    if (this.currentMapped) await this.currentMapped.close()
    if (this.current) await this.current.close()
    this.current = this.currentMapped = null
  }

  async _closePrevious() {
    if (this.previousMapped) await this.previousMapped.close()
    if (this.previous) await this.previous.close()
    this.previous = this.previousMapped = null
  }
}

function autoFlowOnUpdate(name) {
  if (name === 'update') this._consume()
}

function defaultWatchMap(snapshot) {
  return snapshot
}

async function leafSize(node, goLeft) {
  while (node.children.length) node = await node.getChildNode(goLeft ? 0 : node.children.length - 1)
  return node.keys.length
}

async function setKeyToNearestLeaf(node, index, stack) {
  let [left, right] = await Promise.all([node.getChildNode(index), node.getChildNode(index + 1)])
  const [ls, rs] = await Promise.all([leafSize(left, false), leafSize(right, true)])

  if (ls < rs) {
    // if fewer leaves on the left
    stack.push(right)
    while (right.children.length) stack.push((right = right.children[0].value))
    node.keys[index] = right.keys.shift()
  } else {
    // if fewer leaves on the right
    stack.push(left)
    while (left.children.length) stack.push((left = left.children[left.children.length - 1].value))
    node.keys[index] = left.keys.pop()
  }
}

async function rebalance(stack) {
  const root = stack[0]

  while (stack.length > 1) {
    const node = stack.pop()
    const parent = stack[stack.length - 1]

    if (node.keys.length >= MIN_KEYS) return root

    let { left, index, right } = await node.siblings(parent)

    // maybe borrow from left sibling?
    if (left && left.keys.length > MIN_KEYS) {
      left.changed = true
      node.keys.unshift(parent.keys[index - 1])
      if (left.children.length) node.children.unshift(left.children.pop())
      parent.keys[index - 1] = left.keys.pop()
      return root
    }

    // maybe borrow from right sibling?
    if (right && right.keys.length > MIN_KEYS) {
      right.changed = true
      node.keys.push(parent.keys[index])
      if (right.children.length) node.children.push(right.children.shift())
      parent.keys[index] = right.keys.shift()
      return root
    }

    // merge node with another sibling
    if (left) {
      index--
      right = node
    } else {
      left = node
    }

    left.merge(right, parent.keys[index])
    parent.removeKey(index)
  }

  // check if the tree shrunk
  if (!root.keys.length && root.children.length) return root.getChildNode(0)
  return root
}

function iteratorToStream(ite, signal) {
  let done
  let closing

  const rs = new Readable({
    signal,
    open(cb) {
      done = cb
      ite.open().then(fin, fin)
    },
    read(cb) {
      done = cb
      ite.next().then(push, fin)
    },
    predestroy() {
      closing = ite.close()
      closing.catch(noop)
    },
    destroy(cb) {
      done = cb
      if (!closing) closing = ite.close()
      closing.then(fin, fin)
    }
  })

  return rs

  function fin(err) {
    done(err)
  }

  function push(val) {
    rs.push(val)
    done(null)
  }
}

async function iteratorPeek(ite) {
  try {
    await ite.open()
    return await ite.next()
  } finally {
    await ite.close()
  }
}

function encRange(e, opts) {
  if (!e) return opts

  if (e.encodeRange) {
    const r = e.encodeRange({
      gt: opts.gt,
      gte: opts.gte,
      lt: opts.lt,
      lte: opts.lte
    })
    opts.gt = r.gt
    opts.gte = r.gte
    opts.lt = r.lt
    opts.lte = r.lte
    return opts
  }

  if (opts.gt !== undefined) opts.gt = enc(e, opts.gt)
  if (opts.gte !== undefined) opts.gte = enc(e, opts.gte)
  if (opts.lt !== undefined) opts.lt = enc(e, opts.lt)
  if (opts.lte !== undefined) opts.lte = enc(e, opts.lte)
  if (opts.sub && !opts.gt && !opts.gte) opts.gt = enc(e, SEP)
  if (opts.sub && !opts.lt && !opts.lte) opts.lt = bump(enc(e, EMPTY))

  return opts
}

function bump(key) {
  // key should have been copied by enc above before hitting this
  key[key.length - 1]++
  return key
}

function enc(e, v) {
  if (v === undefined || v === null) return null
  if (e !== null) return e.encode(v)
  if (typeof v === 'string') return b4a.from(v)
  return v
}

function prefixEncoding(prefix, keyEncoding) {
  return {
    encode(key) {
      return b4a.concat([prefix, b4a.isBuffer(key) ? key : enc(keyEncoding, key)])
    },
    decode(key) {
      const sliced = key.slice(prefix.length, key.length)
      return keyEncoding ? keyEncoding.decode(sliced) : sliced
    }
  }
}

function copyEntry(entry) {
  let key = entry.key
  let value = entry.value
  let index = entry.index

  // key, value and index all refer to the same buffer (one hypercore block)
  // If together they are larger than half the buffer's byteLength,
  // this means that they got their own private slab (see Buffer.allocUnsafe docs)
  // so no need to unslab
  const size =
    key.byteLength +
    (value === null ? 0 : value.byteLength) +
    (index === null ? 0 : index.byteLength)
  if (2 * size < key.buffer.byteLength) {
    const [newKey, newValue, newIndex] = unslabAll([entry.key, entry.value, entry.index])
    key = newKey
    value = newValue
    index = newIndex
  }

  return {
    key,
    value,
    index,
    inflated: null
  }
}

function defaultDiffer(currentSnap, previousSnap, opts) {
  return currentSnap.createDiffStream(previousSnap, opts)
}

function toNull() {
  return null
}

function getBackingCore(core) {
  if (core.core) return core
  if (core.getBackingCore) return core.getBackingCore().session
  return null
}

function sameValue(a, b) {
  return a === b || (a !== null && b !== null && b4a.equals(a, b))
}

function noop() {}

function getExtension(db, opts) {
  if (opts.extension === false) return null
  if (opts.extension && opts.extension !== true) return opts.extension
  return Extension.register(db)
}

async function isLinked(batch, block) {
  const seq = block.seq
  block.inflate()

  const keys = [block.key]
  const wait = batch.options.wait

  for (const l of block.index.levels) {
    if (!l.keys.length) continue
    batch.options.wait = false
    try {
      keys.push(await batch.getKey(l.keys[0].seq))
    } catch {}
    batch.options.wait = wait
  }

  for (const k of keys) {
    if (await batch.links(k, seq)) return true
  }

  if (batch.core.closing) throw new Error('Core is closed')

  return false
}

module.exports = Hyperbee
const b4a = require('b4a')

class SubTree {
  constructor(node, parent) {
    this.node = node
    this.parent = parent

    this.isKey = node.children.length === 0
    this.i = this.isKey ? 1 : 0
    this.n = 0

    const child = this.isKey ? null : this.node.children[0]
    this.seq = child !== null ? child.seq : this.node.keys[0].seq
    this.offset = child !== null ? child.offset : 0
  }

  next() {
    this.i++
    this.isKey = (this.i & 1) === 1
    if (!this.isKey && !this.node.children.length) this.i++
    return this.update()
  }

  async bisect(key, incl) {
    let s = 0
    let e = this.node.keys.length
    let c

    while (s < e) {
      const mid = (s + e) >> 1
      c = cmp(key, await this.node.getKey(mid))

      if (c === 0) {
        if (incl) this.i = mid * 2 + 1
        else this.i = mid * 2 + (this.node.children.length ? 2 : 3)
        return true
      }

      if (c < 0) e = mid
      else s = mid + 1
    }

    const i = c < 0 ? e : s
    this.i = 2 * i + (this.node.children.length ? 0 : 1)
    return this.node.children.length === 0
  }

  update() {
    this.isKey = (this.i & 1) === 1
    this.n = this.i >> 1
    if (this.n >= (this.isKey ? this.node.keys.length : this.node.children.length)) return false
    const child = this.isKey ? null : this.node.children[this.n]
    this.seq = child !== null ? child.seq : this.node.keys[this.n].seq
    this.offset = child !== null ? child.offset : 0
    return true
  }

  async key() {
    return this.n < this.node.keys.length
      ? this.node.getKey(this.n)
      : this.parent && this.parent.key()
  }

  async compare(tree) {
    const [a, b] = await Promise.all([this.key(), tree.key()])
    return cmp(a, b)
  }
}

class TreeIterator {
  constructor(batch, opts) {
    this.batch = batch
    this.stack = []
    this.lt = opts.lt || opts.lte || null
    this.lte = !!opts.lte
    this.gt = opts.gt || opts.gte || null
    this.gte = !!opts.gte
    this.seeking = !!this.gt
    this.encoding = opts.encoding || batch.encoding
  }

  async open() {
    const node = await this.batch.getRoot(false)
    if (!node || !node.keys.length) return
    const tree = new SubTree(node, null)
    if (this.seeking && !(await this._seek(tree))) return
    this.stack.push(tree)
  }

  async _seek(tree) {
    const done = await tree.bisect(this.gt, this.gte)
    const oob = !tree.update()
    if (done || oob) {
      this.seeking = false
      if (oob) return false
    }
    return true
  }

  peek() {
    if (!this.stack.length) return null
    return this.stack[this.stack.length - 1]
  }

  skip() {
    if (!this.stack.length) return
    if (!this.stack[this.stack.length - 1].next()) this.stack.pop()
  }

  async nextKey() {
    let n = null
    while (this.stack.length && n === null) n = await this.next()
    if (n === null) return null
    if (!this.lt) return n.final(this.encoding)

    const c = cmp(n.key, this.lt)
    if (this.lte ? c <= 0 : c < 0) return n.final(this.encoding)
    this.stack = []
    return null
  }

  async next() {
    if (!this.stack.length) return null

    const top = this.stack[this.stack.length - 1]
    const { isKey, n, seq } = top

    if (!top.next()) {
      this.stack.pop()
    }

    if (isKey) {
      this.seeking = false
      return this.batch.getBlock(seq)
    }

    const child = await top.node.getChildNode(n)
    top.node.children[n] = null // unlink to save memory
    const tree = new SubTree(child, top)
    if (this.seeking && !(await this._seek(tree))) return null
    this.stack.push(tree)

    return null
  }

  close() {
    return this.batch._closeSnapshot()
  }
}

module.exports = class DiffIterator {
  constructor(left, right, opts = {}) {
    this.left = new TreeIterator(left, opts)
    this.right = new TreeIterator(right, opts)
    this.limit = typeof opts.limit === 'number' ? opts.limit : -1
  }

  async open() {
    await Promise.all([this.left.open(), this.right.open()])
  }

  async next() {
    if (this.limit === 0) return null
    const res = await this._next()
    if (!res || (res.left === null && res.right === null)) return null
    this.limit--
    return res
  }

  async _next() {
    const a = this.left
    const b = this.right

    while (true) {
      const [l, r] = await Promise.all([a.peek(), b.peek()])

      if (!l && !r) return null
      if (!l) return { left: null, right: await b.nextKey() }
      if (!r) return { left: await a.nextKey(), right: null }

      if (l.seq === r.seq && l.isKey === r.isKey && l.offset === r.offset) {
        a.skip()
        b.skip()
        continue
      }

      const c = await l.compare(r)

      if (l.isKey && !r.isKey) {
        await b.next()
        continue
      }

      if (!l.isKey && r.isKey) {
        await a.next()
        continue
      }

      if (l.isKey && r.isKey) {
        if (c === 0) return { left: await a.nextKey(), right: await b.nextKey() }
        if (c < 0) return { left: await a.nextKey(), right: null }
        return { left: null, right: await b.nextKey() }
      }

      if (c === 0) await Promise.all([a.next(), b.next()])
      else if (c < 0) await b.next()
      else await a.next()
    }
  }

  async close() {
    await Promise.all([this.left.close(), this.right.close()])
  }
}

function cmp(a, b) {
  if (!a) return b ? 1 : 0
  if (!b) return a ? -1 : 0
  return b4a.compare(a, b)
}
module.exports = class HistoryIterator {
  constructor(batch, opts = {}) {
    this.batch = batch
    this.options = opts
    this.live = !!opts.live
    this.gte = 0
    this.lt = 0
    this.reverse = !!opts.reverse
    this.limit = typeof opts.limit === 'number' ? opts.limit : -1
    this.encoding = opts.encoding || batch.encoding
    if (this.live && this.reverse) {
      throw new Error('Cannot have both live and reverse enabled')
    }
  }

  async open() {
    await this.batch.getRoot(false) // does the update dance
    this.gte = gte(this.options, this.batch.version)
    this.lt = this.live ? Infinity : lt(this.options, this.batch.version)
  }

  async next() {
    if (this.limit === 0) return null
    if (this.limit > 0) this.limit--

    if (this.gte >= this.lt) return null

    if (this.reverse) {
      if (this.lt <= 1) return null
      return final(await this.batch.getBlock(--this.lt), this.encoding)
    }

    return final(await this.batch.getBlock(this.gte++), this.encoding)
  }

  close() {
    return this.batch._closeSnapshot()
  }
}

function final(node, encoding) {
  const type = node.isDeletion() ? 'del' : 'put'
  return { type, ...node.final(encoding) }
}

function gte(opts, version) {
  if (opts.gt) return (opts.gt < 0 ? opts.gt + version : opts.gt) + 1
  const gte = opts.gte || opts.since || 1
  return gte < 0 ? gte + version : gte
}

function lt(opts, version) {
  if (opts.lte === 0 || opts.lt === 0 || opts.end === 0) return 0
  if (opts.lte) return (opts.lte < 0 ? opts.lte + version : opts.lte) + 1
  const lt = opts.lt || opts.end || version
  return lt < 0 ? lt + version : lt
}
module.exports = class LocalBlocksIterator {
  constructor(batch, opts = {}) {
    this.batch = batch
    this.options = opts
    this.gte = 0
    this.lt = 0
    this.limit = typeof opts.limit === 'number' ? opts.limit : -1
  }

  async open() {
    await this.batch.getRoot(false) // does the update dance
    this.gte = gte(this.options, this.batch.version)
    this.lt = lt(this.options, this.batch.version)
  }

  async next() {
    if (this.limit === 0) return null
    if (this.limit > 0) this.limit--

    while (this.gte < this.lt) {
      try {
        return await this.batch.getBlock(this.gte++)
      } catch {
        continue
      }
    }

    return null
  }

  close() {
    return this.batch._closeSnapshot()
  }
}

function gte(opts, version) {
  if (opts.gt) return (opts.gt < 0 ? opts.gt + version : opts.gt) + 1
  const gte = opts.gte || opts.since || 1
  return gte < 0 ? gte + version : gte
}

function lt(opts, version) {
  if (opts.lte === 0 || opts.lt === 0 || opts.end === 0) return 0
  if (opts.lte) return (opts.lte < 0 ? opts.lte + version : opts.lte) + 1
  const lt = opts.lt || opts.end || version
  return lt < 0 ? lt + version : lt
}
const b4a = require('b4a')

module.exports = class RangeIterator {
  constructor(batch, encoding, opts = {}) {
    this.batch = batch
    this.stack = []
    this.opened = false
    this.encoding = encoding || batch.encoding

    this._limit = typeof opts.limit === 'number' ? opts.limit : -1
    this._gIncl = !opts.gt
    this._gKey = opts.gt || opts.gte || null
    this._lIncl = !opts.lt
    this._lKey = opts.lt || opts.lte || null
    this._reverse = !!opts.reverse
    this._version = 0
    this._checkpoint = opts.checkpoint && opts.checkpoint.length ? opts.checkpoint : null
    this._nexting = false
    this._closed = false
  }

  snapshot(version = this.batch.version) {
    const checkpoint = []
    for (const s of this.stack) {
      let { node, i } = s
      if (this._nexting && s === this.stack[this.stack.length - 1]) {
        i = this._reverse ? i + 1 : i - 1
      }
      if (!node.block) continue
      if (i < 0) continue
      checkpoint.push(node.block.seq, node.offset, i)
    }

    return {
      version,
      gte: this._gIncl ? this._gKey : null,
      gt: this._gIncl ? null : this._gKey,
      lte: this._lIncl ? this._lKey : null,
      lt: this._lIncl ? null : this._lKey,
      limit: this._limit,
      reverse: this._reverse,
      ended: this.opened && !checkpoint.length,
      checkpoint: this.opened ? checkpoint : []
    }
  }

  async open() {
    await this._open()
    this.opened = true
  }

  async _open() {
    if (this._checkpoint) {
      for (let j = 0; j < this._checkpoint.length; j += 3) {
        const seq = this._checkpoint[j]
        const offset = this._checkpoint[j + 1]
        const i = this._checkpoint[j + 2]
        this.stack.push({
          node: (await this.batch.getBlock(seq)).getTreeNode(offset),
          i
        })
      }
      return
    }

    this._nexting = true

    let node = await this.batch.getRoot(false)
    if (!node) {
      this._nexting = false
      return
    }

    const incl = this._reverse ? this._lIncl : this._gIncl
    const start = this._reverse ? this._lKey : this._gKey

    if (!start) {
      this.stack.push({ node, i: this._reverse ? node.keys.length << 1 : 0 })
      this._nexting = false
      this._preloadQuery()
      return
    }

    while (true) {
      const entry = { node, i: this._reverse ? node.keys.length << 1 : 0 }

      let s = 0
      let e = node.keys.length
      let c

      while (s < e) {
        const mid = (s + e) >> 1
        c = b4a.compare(start, await node.getKey(mid))

        if (c === 0) {
          if (incl) entry.i = mid * 2 + 1
          else entry.i = mid * 2 + (this._reverse ? 0 : 2)
          this.stack.push(entry)
          this._nexting = false
          this._preloadQuery()
          return
        }

        if (c < 0) e = mid
        else s = mid + 1
      }

      const i = c < 0 ? e : s
      entry.i = 2 * i + (this._reverse ? -1 : 1)

      if (entry.i >= 0 && entry.i <= node.keys.length << 1) this.stack.push(entry)
      if (!node.children.length) {
        this._nexting = false
        this._preloadQuery()
        return
      }

      node = await node.getChildNode(i)
    }
  }

  _preloadQuery() {
    const max = { nodes: 0, max: 2048 }

    if (this._limit === -1 && !this._reverse && this.stack.length) {
      for (const s of this.stack) {
        const k = (s.i - (s.i & 1)) / 2
        this._preload(s.node, k + 1, this._lKey, max).catch(noop)
      }
    }
  }

  async _preload(node, i, end, max) {
    for (; i < node.keys.length; i++) {
      const key = node.keys[i]
      const block = await this.batch.getBlock(key.seq)
      if (this._closed) return
      const c = end ? b4a.compare(block.key, end) : -1
      if (c >= 0 || max.nodes >= max.max || i >= node.children.length) return
      max.nodes++
      const next = await node.getChildNode(i)
      if (this._closed) return
      this._preload(next, 0, end, max).catch(noop)
    }

    if (node.children.length && max.nodes < max.max) {
      const next = await node.getChildNode(node.children.length - 1)
      if (this._closed) return
      this._preload(next, 0, end, max).catch(noop)
    }
  }

  async next() {
    // TODO: this nexting flag is only needed if someone asks for a snapshot during
    // a lookup (ie the extension, pretty important...).
    // A better solution would be to refactor this so top.i is incremented eagerly
    // to get the current block instead of the way it is done now (++i vs i++)
    this._nexting = true

    const end = this._reverse ? this._gKey : this._lKey
    const incl = this._reverse ? this._gIncl : this._lIncl

    while (this.stack.length && (this._limit === -1 || this._limit > 0)) {
      const top = this.stack[this.stack.length - 1]
      const isKey = (top.i & 1) === 1
      const n = this._reverse ? (top.i < 0 ? top.node.keys.length : top.i-- >> 1) : top.i++ >> 1

      if (!isKey) {
        if (!top.node.children.length) continue
        const node = await top.node.getChildNode(n)
        if (top.node.block.seq < this.batch.core.length) {
          top.node.children[n].value = null // unlink it to save memory
        }
        this.stack.push({ i: this._reverse ? node.keys.length << 1 : 0, node })
        continue
      }

      if (n >= top.node.keys.length) {
        this.stack.pop()
        continue
      }

      const key = top.node.keys[n]
      const block = await this.batch.getBlock(key.seq)
      if (end) {
        const c = b4a.compare(block.key, end)
        if (c === 0 ? !incl : this._reverse ? c < 0 : c > 0) {
          this._limit = 0
          break
        }
      }
      if (this._limit > 0) this._limit--
      this._nexting = false
      return block.final(this.encoding)
    }

    this._nexting = false
    return null
  }

  close() {
    this._closed = true
    return this.batch._closeSnapshot()
  }
}

function noop() {}
const { Extension } = require('./messages')

// const MAX_ACTIVE = 32
const FLUSH_BATCH = 128
const MAX_PASSIVE_BATCH = 2048
const MAX_ACTIVE_BATCH = MAX_PASSIVE_BATCH + FLUSH_BATCH

class Batch {
  constructor(outgoing, from) {
    this.blocks = []
    this.start = 0
    this.end = 0
    this.outgoing = outgoing
    this.from = from
  }

  push(seq) {
    const len = this.blocks.push(seq)
    if (len === 1 || seq < this.start) this.start = seq
    if (len === 1 || seq >= this.end) this.end = seq + 1
    if (len >= FLUSH_BATCH) {
      this.send()
      this.clear()
    }
  }

  send() {
    if (!this.blocks.length) return
    this.outgoing.send(
      Extension.encode({
        cache: { blocks: this.blocks, start: this.start, end: this.end }
      }),
      this.from
    )
  }

  clear() {
    this.start = this.end = 0
    this.blocks = []
  }
}

class HyperbeeExtension {
  constructor(db) {
    this.encoding = null
    this.outgoing = null
    this.db = db
    this.active = 0
  }

  get(version, key) {
    this.outgoing.broadcast(Extension.encode({ get: { version, key } }))
  }

  iterator(snapshot) {
    if (snapshot.ended) return
    if (snapshot.limit === 0) return
    if (snapshot.limit === -1) snapshot.limit = 0
    this.outgoing.broadcast(Extension.encode({ iterator: snapshot }))
  }

  onmessage(buf, from) {
    // TODO: handle max active extension messages
    // this.active++

    const message = decode(buf)
    if (!message) return

    if (message.cache) this.oncache(message.cache, from)
    if (message.get) this.onget(message.get, from)
    if (message.iterator) this.oniterator(message.iterator, from)
  }

  oncache(message, from) {
    if (!message.blocks.length) return
    this.db.core.download(message)
  }

  onget(message, from) {
    if (!message.version || message.version > this.db.version) return

    const b = new Batch(this.outgoing, from)
    const db = this.db.checkout(message.version)

    db.get(message.key, {
      extension: false,
      wait: false,
      update: false,
      onseq
    }).then(done, done)

    function done() {
      db.close().catch(noop)
      b.send()
    }

    function onseq(seq) {
      b.push(seq)
    }
  }

  async oniterator(message, from) {
    if (!message.version || message.version > this.db.version) return

    const b = new Batch(this.outgoing, from)
    const seqs = new Set()

    let skip = message.checkpoint.length
    let work = 0

    const db = this.db.checkout(message.version)
    const ite = db.createRangeIterator({
      ...message,
      wait: false,
      extension: false,
      update: false,
      limit: message.limit === 0 ? -1 : message.limit,
      onseq(seq) {
        if (skip && skip--) return
        if (seqs.has(seq)) return
        work++
        seqs.add(seq)
        b.push(seq)
      }
    })

    try {
      await ite.open()
      // eslint-disable-next-line no-unmodified-loop-condition
      while (work < MAX_ACTIVE_BATCH) {
        if (!(await ite.next())) break
      }
    } catch (_) {
      // do nothing
    } finally {
      ite.close().catch(noop)
      db.close().catch(noop)
      b.send()
    }
  }

  static register(db) {
    const e = new this(db)
    e.outgoing = db.core.registerExtension('hyperbee', e)
    return e
  }
}

HyperbeeExtension.BATCH_SIZE = MAX_PASSIVE_BATCH

module.exports = HyperbeeExtension

function decode(buf) {
  try {
    return Extension.decode(buf)
  } catch (err) {
    return null
  }
}

function noop() {}
// This file is auto generated by the protocol-buffers compiler

/* eslint-disable quotes */
/* eslint-disable indent */
/* eslint-disable no-redeclare */
/* eslint-disable camelcase */
/* eslint-disable no-var */

// Remember to `npm install --save protocol-buffers-encodings`
var encodings = require('protocol-buffers-encodings')
var b4a = require('b4a')
var varint = encodings.varint
var skip = encodings.skip

var YoloIndex = exports.YoloIndex = {
  buffer: true,
  encodingLength: null,
  encode: null,
  decode: null
}

var Header = exports.Header = {
  buffer: true,
  encodingLength: null,
  encode: null,
  decode: null
}

var Node = exports.Node = {
  buffer: true,
  encodingLength: null,
  encode: null,
  decode: null
}

var Extension = exports.Extension = {
  buffer: true,
  encodingLength: null,
  encode: null,
  decode: null
}

defineYoloIndex()
defineHeader()
defineNode()
defineExtension()

function defineYoloIndex () {
  var Level = YoloIndex.Level = {
    buffer: true,
    encodingLength: null,
    encode: null,
    decode: null
  }

  defineLevel()

  function defineLevel () {
    Level.encodingLength = encodingLength
    Level.encode = encode
    Level.decode = decode

    function encodingLength (obj) {
      var length = 0
      if (defined(obj.keys)) {
        var packedLen = 0
        for (var i = 0; i < obj.keys.length; i++) {
          if (!defined(obj.keys[i])) continue
          var len = encodings.varint.encodingLength(obj.keys[i])
          packedLen += len
        }
        if (packedLen) {
          length += 1 + packedLen + varint.encodingLength(packedLen)
        }
      }
      if (defined(obj.children)) {
        var packedLen = 0
        for (var i = 0; i < obj.children.length; i++) {
          if (!defined(obj.children[i])) continue
          var len = encodings.varint.encodingLength(obj.children[i])
          packedLen += len
        }
        if (packedLen) {
          length += 1 + packedLen + varint.encodingLength(packedLen)
        }
      }
      return length
    }

    function encode (obj, buf, offset) {
      if (!offset) offset = 0
      if (!buf) buf = b4a.allocUnsafe(encodingLength(obj))
      var oldOffset = offset
      if (defined(obj.keys)) {
        var packedLen = 0
        for (var i = 0; i < obj.keys.length; i++) {
          if (!defined(obj.keys[i])) continue
          packedLen += encodings.varint.encodingLength(obj.keys[i])
        }
        if (packedLen) {
          buf[offset++] = 10
          varint.encode(packedLen, buf, offset)
          offset += varint.encode.bytes
        }
        for (var i = 0; i < obj.keys.length; i++) {
          if (!defined(obj.keys[i])) continue
          encodings.varint.encode(obj.keys[i], buf, offset)
          offset += encodings.varint.encode.bytes
        }
      }
      if (defined(obj.children)) {
        var packedLen = 0
        for (var i = 0; i < obj.children.length; i++) {
          if (!defined(obj.children[i])) continue
          packedLen += encodings.varint.encodingLength(obj.children[i])
        }
        if (packedLen) {
          buf[offset++] = 18
          varint.encode(packedLen, buf, offset)
          offset += varint.encode.bytes
        }
        for (var i = 0; i < obj.children.length; i++) {
          if (!defined(obj.children[i])) continue
          encodings.varint.encode(obj.children[i], buf, offset)
          offset += encodings.varint.encode.bytes
        }
      }
      encode.bytes = offset - oldOffset
      return buf
    }

    function decode (buf, offset, end) {
      if (!offset) offset = 0
      if (!end) end = buf.length
      if (!(end <= buf.length && offset <= buf.length)) throw new Error("Decoded message is not valid")
      var oldOffset = offset
      var obj = {
        keys: [],
        children: []
      }
      while (true) {
        if (end <= offset) {
          decode.bytes = offset - oldOffset
          return obj
        }
        var prefix = varint.decode(buf, offset)
        offset += varint.decode.bytes
        var tag = prefix >> 3
        switch (tag) {
          case 1:
          var packedEnd = varint.decode(buf, offset)
          offset += varint.decode.bytes
          packedEnd += offset
          while (offset < packedEnd) {
            obj.keys.push(encodings.varint.decode(buf, offset))
            offset += encodings.varint.decode.bytes
          }
          break
          case 2:
          var packedEnd = varint.decode(buf, offset)
          offset += varint.decode.bytes
          packedEnd += offset
          while (offset < packedEnd) {
            obj.children.push(encodings.varint.decode(buf, offset))
            offset += encodings.varint.decode.bytes
          }
          break
          default:
          offset = skip(prefix & 7, buf, offset)
        }
      }
    }
  }

  YoloIndex.encodingLength = encodingLength
  YoloIndex.encode = encode
  YoloIndex.decode = decode

  function encodingLength (obj) {
    var length = 0
    if (defined(obj.levels)) {
      for (var i = 0; i < obj.levels.length; i++) {
        if (!defined(obj.levels[i])) continue
        var len = Level.encodingLength(obj.levels[i])
        length += varint.encodingLength(len)
        length += 1 + len
      }
    }
    return length
  }

  function encode (obj, buf, offset) {
    if (!offset) offset = 0
    if (!buf) buf = b4a.allocUnsafe(encodingLength(obj))
    var oldOffset = offset
    if (defined(obj.levels)) {
      for (var i = 0; i < obj.levels.length; i++) {
        if (!defined(obj.levels[i])) continue
        buf[offset++] = 10
        varint.encode(Level.encodingLength(obj.levels[i]), buf, offset)
        offset += varint.encode.bytes
        Level.encode(obj.levels[i], buf, offset)
        offset += Level.encode.bytes
      }
    }
    encode.bytes = offset - oldOffset
    return buf
  }

  function decode (buf, offset, end) {
    if (!offset) offset = 0
    if (!end) end = buf.length
    if (!(end <= buf.length && offset <= buf.length)) throw new Error("Decoded message is not valid")
    var oldOffset = offset
    var obj = {
      levels: []
    }
    while (true) {
      if (end <= offset) {
        decode.bytes = offset - oldOffset
        return obj
      }
      var prefix = varint.decode(buf, offset)
      offset += varint.decode.bytes
      var tag = prefix >> 3
      switch (tag) {
        case 1:
        var len = varint.decode(buf, offset)
        offset += varint.decode.bytes
        obj.levels.push(Level.decode(buf, offset, offset + len))
        offset += Level.decode.bytes
        break
        default:
        offset = skip(prefix & 7, buf, offset)
      }
    }
  }
}

function defineHeader () {
  var Metadata = Header.Metadata = {
    buffer: true,
    encodingLength: null,
    encode: null,
    decode: null
  }

  defineMetadata()

  function defineMetadata () {
    Metadata.encodingLength = encodingLength
    Metadata.encode = encode
    Metadata.decode = decode

    function encodingLength (obj) {
      var length = 0
      if (defined(obj.contentFeed)) {
        var len = encodings.bytes.encodingLength(obj.contentFeed)
        length += 1 + len
      }
      if (defined(obj.userData)) {
        var len = encodings.bytes.encodingLength(obj.userData)
        length += 1 + len
      }
      return length
    }

    function encode (obj, buf, offset) {
      if (!offset) offset = 0
      if (!buf) buf = b4a.allocUnsafe(encodingLength(obj))
      var oldOffset = offset
      if (defined(obj.contentFeed)) {
        buf[offset++] = 10
        encodings.bytes.encode(obj.contentFeed, buf, offset)
        offset += encodings.bytes.encode.bytes
      }
      if (defined(obj.userData)) {
        buf[offset++] = 18
        encodings.bytes.encode(obj.userData, buf, offset)
        offset += encodings.bytes.encode.bytes
      }
      encode.bytes = offset - oldOffset
      return buf
    }

    function decode (buf, offset, end) {
      if (!offset) offset = 0
      if (!end) end = buf.length
      if (!(end <= buf.length && offset <= buf.length)) throw new Error("Decoded message is not valid")
      var oldOffset = offset
      var obj = {
        contentFeed: null,
        userData: null
      }
      while (true) {
        if (end <= offset) {
          decode.bytes = offset - oldOffset
          return obj
        }
        var prefix = varint.decode(buf, offset)
        offset += varint.decode.bytes
        var tag = prefix >> 3
        switch (tag) {
          case 1:
          obj.contentFeed = encodings.bytes.decode(buf, offset)
          offset += encodings.bytes.decode.bytes
          break
          case 2:
          obj.userData = encodings.bytes.decode(buf, offset)
          offset += encodings.bytes.decode.bytes
          break
          default:
          offset = skip(prefix & 7, buf, offset)
        }
      }
    }
  }

  Header.encodingLength = encodingLength
  Header.encode = encode
  Header.decode = decode

  function encodingLength (obj) {
    var length = 0
    if (!defined(obj.protocol)) throw new Error("protocol is required")
    var len = encodings.string.encodingLength(obj.protocol)
    length += 1 + len
    if (defined(obj.metadata)) {
      var len = Metadata.encodingLength(obj.metadata)
      length += varint.encodingLength(len)
      length += 1 + len
    }
    return length
  }

  function encode (obj, buf, offset) {
    if (!offset) offset = 0
    if (!buf) buf = b4a.allocUnsafe(encodingLength(obj))
    var oldOffset = offset
    if (!defined(obj.protocol)) throw new Error("protocol is required")
    buf[offset++] = 10
    encodings.string.encode(obj.protocol, buf, offset)
    offset += encodings.string.encode.bytes
    if (defined(obj.metadata)) {
      buf[offset++] = 18
      varint.encode(Metadata.encodingLength(obj.metadata), buf, offset)
      offset += varint.encode.bytes
      Metadata.encode(obj.metadata, buf, offset)
      offset += Metadata.encode.bytes
    }
    encode.bytes = offset - oldOffset
    return buf
  }

  function decode (buf, offset, end) {
    if (!offset) offset = 0
    if (!end) end = buf.length
    if (!(end <= buf.length && offset <= buf.length)) throw new Error("Decoded message is not valid")
    var oldOffset = offset
    var obj = {
      protocol: "",
      metadata: null
    }
    var found0 = false
    while (true) {
      if (end <= offset) {
        if (!found0) throw new Error("Decoded message is not valid")
        decode.bytes = offset - oldOffset
        return obj
      }
      var prefix = varint.decode(buf, offset)
      offset += varint.decode.bytes
      var tag = prefix >> 3
      switch (tag) {
        case 1:
        obj.protocol = encodings.string.decode(buf, offset)
        offset += encodings.string.decode.bytes
        found0 = true
        break
        case 2:
        var len = varint.decode(buf, offset)
        offset += varint.decode.bytes
        obj.metadata = Metadata.decode(buf, offset, offset + len)
        offset += Metadata.decode.bytes
        break
        default:
        offset = skip(prefix & 7, buf, offset)
      }
    }
  }
}

function defineNode () {
  Node.encodingLength = encodingLength
  Node.encode = encode
  Node.decode = decode

  function encodingLength (obj) {
    var length = 0
    if (!defined(obj.index)) throw new Error("index is required")
    var len = encodings.bytes.encodingLength(obj.index)
    length += 1 + len
    if (!defined(obj.key)) throw new Error("key is required")
    var len = encodings.bytes.encodingLength(obj.key)
    length += 1 + len
    if (defined(obj.value)) {
      var len = encodings.bytes.encodingLength(obj.value)
      length += 1 + len
    }
    return length
  }

  function encode (obj, buf, offset) {
    if (!offset) offset = 0
    if (!buf) buf = b4a.allocUnsafe(encodingLength(obj))
    var oldOffset = offset
    if (!defined(obj.index)) throw new Error("index is required")
    buf[offset++] = 10
    encodings.bytes.encode(obj.index, buf, offset)
    offset += encodings.bytes.encode.bytes
    if (!defined(obj.key)) throw new Error("key is required")
    buf[offset++] = 18
    encodings.bytes.encode(obj.key, buf, offset)
    offset += encodings.bytes.encode.bytes
    if (defined(obj.value)) {
      buf[offset++] = 26
      encodings.bytes.encode(obj.value, buf, offset)
      offset += encodings.bytes.encode.bytes
    }
    encode.bytes = offset - oldOffset
    return buf
  }

  function decode (buf, offset, end) {
    if (!offset) offset = 0
    if (!end) end = buf.length
    if (!(end <= buf.length && offset <= buf.length)) throw new Error("Decoded message is not valid")
    var oldOffset = offset
    var obj = {
      index: null,
      key: null,
      value: null
    }
    var found0 = false
    var found1 = false
    while (true) {
      if (end <= offset) {
        if (!found0 || !found1) throw new Error("Decoded message is not valid")
        decode.bytes = offset - oldOffset
        return obj
      }
      var prefix = varint.decode(buf, offset)
      offset += varint.decode.bytes
      var tag = prefix >> 3
      switch (tag) {
        case 1:
        obj.index = encodings.bytes.decode(buf, offset)
        offset += encodings.bytes.decode.bytes
        found0 = true
        break
        case 2:
        obj.key = encodings.bytes.decode(buf, offset)
        offset += encodings.bytes.decode.bytes
        found1 = true
        break
        case 3:
        obj.value = encodings.bytes.decode(buf, offset)
        offset += encodings.bytes.decode.bytes
        break
        default:
        offset = skip(prefix & 7, buf, offset)
      }
    }
  }
}

function defineExtension () {
  var Get = Extension.Get = {
    buffer: true,
    encodingLength: null,
    encode: null,
    decode: null
  }

  var Iterator = Extension.Iterator = {
    buffer: true,
    encodingLength: null,
    encode: null,
    decode: null
  }

  var Cache = Extension.Cache = {
    buffer: true,
    encodingLength: null,
    encode: null,
    decode: null
  }

  defineGet()
  defineIterator()
  defineCache()

  function defineGet () {
    Get.encodingLength = encodingLength
    Get.encode = encode
    Get.decode = decode

    function encodingLength (obj) {
      var length = 0
      if (defined(obj.version)) {
        var len = encodings.varint.encodingLength(obj.version)
        length += 1 + len
      }
      if (defined(obj.key)) {
        var len = encodings.bytes.encodingLength(obj.key)
        length += 1 + len
      }
      return length
    }

    function encode (obj, buf, offset) {
      if (!offset) offset = 0
      if (!buf) buf = b4a.allocUnsafe(encodingLength(obj))
      var oldOffset = offset
      if (defined(obj.version)) {
        buf[offset++] = 8
        encodings.varint.encode(obj.version, buf, offset)
        offset += encodings.varint.encode.bytes
      }
      if (defined(obj.key)) {
        buf[offset++] = 18
        encodings.bytes.encode(obj.key, buf, offset)
        offset += encodings.bytes.encode.bytes
      }
      encode.bytes = offset - oldOffset
      return buf
    }

    function decode (buf, offset, end) {
      if (!offset) offset = 0
      if (!end) end = buf.length
      if (!(end <= buf.length && offset <= buf.length)) throw new Error("Decoded message is not valid")
      var oldOffset = offset
      var obj = {
        version: 0,
        key: null
      }
      while (true) {
        if (end <= offset) {
          decode.bytes = offset - oldOffset
          return obj
        }
        var prefix = varint.decode(buf, offset)
        offset += varint.decode.bytes
        var tag = prefix >> 3
        switch (tag) {
          case 1:
          obj.version = encodings.varint.decode(buf, offset)
          offset += encodings.varint.decode.bytes
          break
          case 2:
          obj.key = encodings.bytes.decode(buf, offset)
          offset += encodings.bytes.decode.bytes
          break
          default:
          offset = skip(prefix & 7, buf, offset)
        }
      }
    }
  }

  function defineIterator () {
    Iterator.encodingLength = encodingLength
    Iterator.encode = encode
    Iterator.decode = decode

    function encodingLength (obj) {
      var length = 0
      if (defined(obj.version)) {
        var len = encodings.varint.encodingLength(obj.version)
        length += 1 + len
      }
      if (defined(obj.gte)) {
        var len = encodings.bytes.encodingLength(obj.gte)
        length += 1 + len
      }
      if (defined(obj.gt)) {
        var len = encodings.bytes.encodingLength(obj.gt)
        length += 1 + len
      }
      if (defined(obj.lte)) {
        var len = encodings.bytes.encodingLength(obj.lte)
        length += 1 + len
      }
      if (defined(obj.lt)) {
        var len = encodings.bytes.encodingLength(obj.lt)
        length += 1 + len
      }
      if (defined(obj.limit)) {
        var len = encodings.varint.encodingLength(obj.limit)
        length += 1 + len
      }
      if (defined(obj.reverse)) {
        var len = encodings.bool.encodingLength(obj.reverse)
        length += 1 + len
      }
      if (defined(obj.checkpoint)) {
        var packedLen = 0
        for (var i = 0; i < obj.checkpoint.length; i++) {
          if (!defined(obj.checkpoint[i])) continue
          var len = encodings.varint.encodingLength(obj.checkpoint[i])
          packedLen += len
        }
        if (packedLen) {
          length += 1 + packedLen + varint.encodingLength(packedLen)
        }
      }
      return length
    }

    function encode (obj, buf, offset) {
      if (!offset) offset = 0
      if (!buf) buf = b4a.allocUnsafe(encodingLength(obj))
      var oldOffset = offset
      if (defined(obj.version)) {
        buf[offset++] = 8
        encodings.varint.encode(obj.version, buf, offset)
        offset += encodings.varint.encode.bytes
      }
      if (defined(obj.gte)) {
        buf[offset++] = 18
        encodings.bytes.encode(obj.gte, buf, offset)
        offset += encodings.bytes.encode.bytes
      }
      if (defined(obj.gt)) {
        buf[offset++] = 26
        encodings.bytes.encode(obj.gt, buf, offset)
        offset += encodings.bytes.encode.bytes
      }
      if (defined(obj.lte)) {
        buf[offset++] = 34
        encodings.bytes.encode(obj.lte, buf, offset)
        offset += encodings.bytes.encode.bytes
      }
      if (defined(obj.lt)) {
        buf[offset++] = 42
        encodings.bytes.encode(obj.lt, buf, offset)
        offset += encodings.bytes.encode.bytes
      }
      if (defined(obj.limit)) {
        buf[offset++] = 48
        encodings.varint.encode(obj.limit, buf, offset)
        offset += encodings.varint.encode.bytes
      }
      if (defined(obj.reverse)) {
        buf[offset++] = 56
        encodings.bool.encode(obj.reverse, buf, offset)
        offset += encodings.bool.encode.bytes
      }
      if (defined(obj.checkpoint)) {
        var packedLen = 0
        for (var i = 0; i < obj.checkpoint.length; i++) {
          if (!defined(obj.checkpoint[i])) continue
          packedLen += encodings.varint.encodingLength(obj.checkpoint[i])
        }
        if (packedLen) {
          buf[offset++] = 66
          varint.encode(packedLen, buf, offset)
          offset += varint.encode.bytes
        }
        for (var i = 0; i < obj.checkpoint.length; i++) {
          if (!defined(obj.checkpoint[i])) continue
          encodings.varint.encode(obj.checkpoint[i], buf, offset)
          offset += encodings.varint.encode.bytes
        }
      }
      encode.bytes = offset - oldOffset
      return buf
    }

    function decode (buf, offset, end) {
      if (!offset) offset = 0
      if (!end) end = buf.length
      if (!(end <= buf.length && offset <= buf.length)) throw new Error("Decoded message is not valid")
      var oldOffset = offset
      var obj = {
        version: 0,
        gte: null,
        gt: null,
        lte: null,
        lt: null,
        limit: 0,
        reverse: false,
        checkpoint: []
      }
      while (true) {
        if (end <= offset) {
          decode.bytes = offset - oldOffset
          return obj
        }
        var prefix = varint.decode(buf, offset)
        offset += varint.decode.bytes
        var tag = prefix >> 3
        switch (tag) {
          case 1:
          obj.version = encodings.varint.decode(buf, offset)
          offset += encodings.varint.decode.bytes
          break
          case 2:
          obj.gte = encodings.bytes.decode(buf, offset)
          offset += encodings.bytes.decode.bytes
          break
          case 3:
          obj.gt = encodings.bytes.decode(buf, offset)
          offset += encodings.bytes.decode.bytes
          break
          case 4:
          obj.lte = encodings.bytes.decode(buf, offset)
          offset += encodings.bytes.decode.bytes
          break
          case 5:
          obj.lt = encodings.bytes.decode(buf, offset)
          offset += encodings.bytes.decode.bytes
          break
          case 6:
          obj.limit = encodings.varint.decode(buf, offset)
          offset += encodings.varint.decode.bytes
          break
          case 7:
          obj.reverse = encodings.bool.decode(buf, offset)
          offset += encodings.bool.decode.bytes
          break
          case 8:
          var packedEnd = varint.decode(buf, offset)
          offset += varint.decode.bytes
          packedEnd += offset
          while (offset < packedEnd) {
            obj.checkpoint.push(encodings.varint.decode(buf, offset))
            offset += encodings.varint.decode.bytes
          }
          break
          default:
          offset = skip(prefix & 7, buf, offset)
        }
      }
    }
  }

  function defineCache () {
    Cache.encodingLength = encodingLength
    Cache.encode = encode
    Cache.decode = decode

    function encodingLength (obj) {
      var length = 0
      if (!defined(obj.start)) throw new Error("start is required")
      var len = encodings.varint.encodingLength(obj.start)
      length += 1 + len
      if (!defined(obj.end)) throw new Error("end is required")
      var len = encodings.varint.encodingLength(obj.end)
      length += 1 + len
      if (defined(obj.blocks)) {
        var packedLen = 0
        for (var i = 0; i < obj.blocks.length; i++) {
          if (!defined(obj.blocks[i])) continue
          var len = encodings.varint.encodingLength(obj.blocks[i])
          packedLen += len
        }
        if (packedLen) {
          length += 1 + packedLen + varint.encodingLength(packedLen)
        }
      }
      return length
    }

    function encode (obj, buf, offset) {
      if (!offset) offset = 0
      if (!buf) buf = b4a.allocUnsafe(encodingLength(obj))
      var oldOffset = offset
      if (!defined(obj.start)) throw new Error("start is required")
      buf[offset++] = 8
      encodings.varint.encode(obj.start, buf, offset)
      offset += encodings.varint.encode.bytes
      if (!defined(obj.end)) throw new Error("end is required")
      buf[offset++] = 16
      encodings.varint.encode(obj.end, buf, offset)
      offset += encodings.varint.encode.bytes
      if (defined(obj.blocks)) {
        var packedLen = 0
        for (var i = 0; i < obj.blocks.length; i++) {
          if (!defined(obj.blocks[i])) continue
          packedLen += encodings.varint.encodingLength(obj.blocks[i])
        }
        if (packedLen) {
          buf[offset++] = 26
          varint.encode(packedLen, buf, offset)
          offset += varint.encode.bytes
        }
        for (var i = 0; i < obj.blocks.length; i++) {
          if (!defined(obj.blocks[i])) continue
          encodings.varint.encode(obj.blocks[i], buf, offset)
          offset += encodings.varint.encode.bytes
        }
      }
      encode.bytes = offset - oldOffset
      return buf
    }

    function decode (buf, offset, end) {
      if (!offset) offset = 0
      if (!end) end = buf.length
      if (!(end <= buf.length && offset <= buf.length)) throw new Error("Decoded message is not valid")
      var oldOffset = offset
      var obj = {
        start: 0,
        end: 0,
        blocks: []
      }
      var found0 = false
      var found1 = false
      while (true) {
        if (end <= offset) {
          if (!found0 || !found1) throw new Error("Decoded message is not valid")
          decode.bytes = offset - oldOffset
          return obj
        }
        var prefix = varint.decode(buf, offset)
        offset += varint.decode.bytes
        var tag = prefix >> 3
        switch (tag) {
          case 1:
          obj.start = encodings.varint.decode(buf, offset)
          offset += encodings.varint.decode.bytes
          found0 = true
          break
          case 2:
          obj.end = encodings.varint.decode(buf, offset)
          offset += encodings.varint.decode.bytes
          found1 = true
          break
          case 3:
          var packedEnd = varint.decode(buf, offset)
          offset += varint.decode.bytes
          packedEnd += offset
          while (offset < packedEnd) {
            obj.blocks.push(encodings.varint.decode(buf, offset))
            offset += encodings.varint.decode.bytes
          }
          break
          default:
          offset = skip(prefix & 7, buf, offset)
        }
      }
    }
  }

  Extension.encodingLength = encodingLength
  Extension.encode = encode
  Extension.decode = decode

  function encodingLength (obj) {
    var length = 0
    if (defined(obj.cache)) {
      var len = Cache.encodingLength(obj.cache)
      length += varint.encodingLength(len)
      length += 1 + len
    }
    if (defined(obj.get)) {
      var len = Get.encodingLength(obj.get)
      length += varint.encodingLength(len)
      length += 1 + len
    }
    if (defined(obj.iterator)) {
      var len = Iterator.encodingLength(obj.iterator)
      length += varint.encodingLength(len)
      length += 1 + len
    }
    return length
  }

  function encode (obj, buf, offset) {
    if (!offset) offset = 0
    if (!buf) buf = b4a.allocUnsafe(encodingLength(obj))
    var oldOffset = offset
    if (defined(obj.cache)) {
      buf[offset++] = 10
      varint.encode(Cache.encodingLength(obj.cache), buf, offset)
      offset += varint.encode.bytes
      Cache.encode(obj.cache, buf, offset)
      offset += Cache.encode.bytes
    }
    if (defined(obj.get)) {
      buf[offset++] = 18
      varint.encode(Get.encodingLength(obj.get), buf, offset)
      offset += varint.encode.bytes
      Get.encode(obj.get, buf, offset)
      offset += Get.encode.bytes
    }
    if (defined(obj.iterator)) {
      buf[offset++] = 26
      varint.encode(Iterator.encodingLength(obj.iterator), buf, offset)
      offset += varint.encode.bytes
      Iterator.encode(obj.iterator, buf, offset)
      offset += Iterator.encode.bytes
    }
    encode.bytes = offset - oldOffset
    return buf
  }

  function decode (buf, offset, end) {
    if (!offset) offset = 0
    if (!end) end = buf.length
    if (!(end <= buf.length && offset <= buf.length)) throw new Error("Decoded message is not valid")
    var oldOffset = offset
    var obj = {
      cache: null,
      get: null,
      iterator: null
    }
    while (true) {
      if (end <= offset) {
        decode.bytes = offset - oldOffset
        return obj
      }
      var prefix = varint.decode(buf, offset)
      offset += varint.decode.bytes
      var tag = prefix >> 3
      switch (tag) {
        case 1:
        var len = varint.decode(buf, offset)
        offset += varint.decode.bytes
        obj.cache = Cache.decode(buf, offset, offset + len)
        offset += Cache.decode.bytes
        break
        case 2:
        var len = varint.decode(buf, offset)
        offset += varint.decode.bytes
        obj.get = Get.decode(buf, offset, offset + len)
        offset += Get.decode.bytes
        break
        case 3:
        var len = varint.decode(buf, offset)
        offset += varint.decode.bytes
        obj.iterator = Iterator.decode(buf, offset, offset + len)
        offset += Iterator.decode.bytes
        break
        default:
        offset = skip(prefix & 7, buf, offset)
      }
    }
  }
}

function defined (val) {
  return val !== null && val !== undefined && (typeof val !== 'number' || !isNaN(val))
}
{
  "name": "hyperbee",
  "version": "2.27.3",
  "description": "An append-only B-tree running on a Hypercore.",
  "main": "index.js",
  "files": [
    "index.js",
    "lib/**.js",
    "iterators/**.js"
  ],
  "dependencies": {
    "b4a": "^1.6.0",
    "codecs": "^3.0.0",
    "debounceify": "^1.0.0",
    "hypercore-errors": "^1.0.0",
    "mutexify": "^1.4.0",
    "protocol-buffers-encodings": "^1.2.0",
    "rache": "^1.0.0",
    "ready-resource": "^1.0.0",
    "resolve-reject-promise": "^1.1.0",
    "safety-catch": "^1.0.2",
    "streamx": "^2.12.4",
    "unslab": "^1.2.0"
  },
  "devDependencies": {
    "bare-process": "^4.2.2",
    "brittle": "^3.1.0",
    "hypercore": "^11.0.0",
    "lunte": "^1.0.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^2.0.0",
    "protocol-buffers": "^4.2.0",
    "sub-encoder": "^1.0.6",
    "tree-to-string": "^1.1.1"
  },
  "imports": {
    "process": {
      "bare": "bare-process",
      "default": "process"
    }
  },
  "scripts": {
    "format": "prettier --write .",
    "lint": "prettier --check . && lunte",
    "test": "npm run test:node && npm run test:bare",
    "test:node": "brittle-node test/*.js",
    "test:bare": "brittle-bare test/*.js",
    "protobuf": "protocol-buffers schema.proto -o ./lib/messages.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/holepunchto/hyperbee.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/holepunchto/hyperbee/issues"
  },
  "homepage": "https://github.com/holepunchto/hyperbee"
}
const sodium = require('sodium-universal')
const c = require('compact-encoding')
const b4a = require('b4a')

// https://en.wikipedia.org/wiki/Merkle_tree#Second_preimage_attack
const LEAF_TYPE = b4a.from([0])
const PARENT_TYPE = b4a.from([1])
const ROOT_TYPE = b4a.from([2])

const HYPERCORE = b4a.from('hypercore')

exports.keyPair = function (seed) {
  // key pairs might stay around for a while, so better not to use a default slab to avoid retaining it completely
  const slab = b4a.allocUnsafeSlow(
    sodium.crypto_sign_PUBLICKEYBYTES + sodium.crypto_sign_SECRETKEYBYTES
  )
  const publicKey = slab.subarray(0, sodium.crypto_sign_PUBLICKEYBYTES)
  const secretKey = slab.subarray(sodium.crypto_sign_PUBLICKEYBYTES)

  if (seed) sodium.crypto_sign_seed_keypair(publicKey, secretKey, seed)
  else sodium.crypto_sign_keypair(publicKey, secretKey)

  return {
    publicKey,
    secretKey
  }
}

exports.validateKeyPair = function (keyPair) {
  const pk = b4a.allocUnsafe(sodium.crypto_sign_PUBLICKEYBYTES)
  sodium.crypto_sign_ed25519_sk_to_pk(pk, keyPair.secretKey)
  return b4a.equals(pk, keyPair.publicKey)
}

exports.sign = function (message, secretKey) {
  // Dedicated slab for the signature, to avoid retaining unneeded mem and for security
  const signature = b4a.allocUnsafeSlow(sodium.crypto_sign_BYTES)
  sodium.crypto_sign_detached(signature, message, secretKey)
  return signature
}

exports.verify = function (message, signature, publicKey) {
  if (signature.byteLength !== sodium.crypto_sign_BYTES) return false
  if (publicKey.byteLength !== sodium.crypto_sign_PUBLICKEYBYTES) return false
  return sodium.crypto_sign_verify_detached(signature, message, publicKey)
}

exports.encrypt = function (message, publicKey) {
  const ciphertext = b4a.alloc(message.byteLength + sodium.crypto_box_SEALBYTES)
  sodium.crypto_box_seal(ciphertext, message, publicKey)
  return ciphertext
}

exports.decrypt = function (ciphertext, keyPair) {
  if (ciphertext.byteLength < sodium.crypto_box_SEALBYTES) return null

  const plaintext = b4a.alloc(ciphertext.byteLength - sodium.crypto_box_SEALBYTES)

  if (!sodium.crypto_box_seal_open(plaintext, ciphertext, keyPair.publicKey, keyPair.secretKey)) {
    return null
  }

  return plaintext
}

exports.encryptionKeyPair = function (seed) {
  const publicKey = b4a.alloc(sodium.crypto_box_PUBLICKEYBYTES)
  const secretKey = b4a.alloc(sodium.crypto_box_SECRETKEYBYTES)

  if (seed) {
    sodium.crypto_box_seed_keypair(publicKey, secretKey, seed)
  } else {
    sodium.crypto_box_keypair(publicKey, secretKey)
  }

  return {
    publicKey,
    secretKey
  }
}

exports.data = function (data) {
  const out = b4a.allocUnsafe(32)

  sodium.crypto_generichash_batch(out, [LEAF_TYPE, c.encode(c.uint64, data.byteLength), data])

  return out
}

exports.parent = function (a, b) {
  if (a.index > b.index) {
    const tmp = a
    a = b
    b = tmp
  }

  const out = b4a.allocUnsafe(32)

  sodium.crypto_generichash_batch(out, [
    PARENT_TYPE,
    c.encode(c.uint64, a.size + b.size),
    a.hash,
    b.hash
  ])

  return out
}

exports.tree = function (roots, out) {
  const buffers = new Array(3 * roots.length + 1)
  let j = 0

  buffers[j++] = ROOT_TYPE

  for (let i = 0; i < roots.length; i++) {
    const r = roots[i]
    buffers[j++] = r.hash
    buffers[j++] = c.encode(c.uint64, r.index)
    buffers[j++] = c.encode(c.uint64, r.size)
  }

  if (!out) out = b4a.allocUnsafe(32)
  sodium.crypto_generichash_batch(out, buffers)
  return out
}

exports.hash = function (data, out) {
  if (!out) out = b4a.allocUnsafe(32)
  if (!Array.isArray(data)) data = [data]

  sodium.crypto_generichash_batch(out, data)

  return out
}

exports.randomBytes = function (n) {
  const buf = b4a.allocUnsafe(n)
  sodium.randombytes_buf(buf)
  return buf
}

exports.discoveryKey = function (key) {
  if (!key || key.byteLength !== 32) throw new Error('Must pass a 32 byte buffer')
  // Discovery keys might stay around for a while, so better not to use slab memory (for better gc)
  const digest = b4a.allocUnsafeSlow(32)
  sodium.crypto_generichash(digest, HYPERCORE, key)
  return digest
}

if (sodium.sodium_free) {
  exports.free = function (secureBuf) {
    if (secureBuf.secure) sodium.sodium_free(secureBuf)
  }
} else {
  exports.free = function () {}
}

exports.namespace = function (name, count) {
  const ids = typeof count === 'number' ? range(count) : count

  // Namespaces are long-lived, so better to use a dedicated slab
  const buf = b4a.allocUnsafeSlow(32 * ids.length)

  const list = new Array(ids.length)

  // ns is ephemeral, so default slab
  const ns = b4a.allocUnsafe(33)
  sodium.crypto_generichash(ns.subarray(0, 32), typeof name === 'string' ? b4a.from(name) : name)

  for (let i = 0; i < list.length; i++) {
    list[i] = buf.subarray(32 * i, 32 * i + 32)
    ns[32] = ids[i]
    sodium.crypto_generichash(list[i], ns)
  }

  return list
}

function range(count) {
  const arr = new Array(count)
  for (let i = 0; i < count; i++) arr[i] = i
  return arr
}
{
  "name": "hypercore-crypto",
  "version": "3.7.0",
  "description": "The crypto primitives used in hypercore, extracted into a separate module",
  "main": "index.js",
  "files": [
    "index.js"
  ],
  "dependencies": {
    "b4a": "^1.6.6",
    "compact-encoding": "^3.0.0",
    "sodium-universal": "^5.0.0"
  },
  "devDependencies": {
    "brittle": "^3.5.0",
    "lunte": "^1.0.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^2.0.0"
  },
  "scripts": {
    "format": "prettier --write .",
    "lint": "prettier --check . && lunte",
    "test": "npm run test:node && npm run test:bare",
    "test:node": "brittle-node test.js",
    "test:bare": "brittle-bare test.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/mafintosh/hypercore-crypto.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/mafintosh/hypercore-crypto/issues"
  },
  "homepage": "https://github.com/mafintosh/hypercore-crypto"
}
const IdEnc = require('hypercore-id-encoding')

module.exports = class HypercoreError extends Error {
  constructor (msg, code, fn = HypercoreError, discoveryKey = null) {
    if (discoveryKey) msg = `${msg} (discovery key: ${IdEnc.normalize(discoveryKey)})`
    super(`${code}: ${msg}`)

    this.code = code
    this.discoveryKey = discoveryKey

    if (Error.captureStackTrace) {
      Error.captureStackTrace(this, fn)
    }
  }

  get name () {
    return 'HypercoreError'
  }

  static ASSERTION (msg, discoveryKey = null) { // ERR_ASSERTION is picked up by safety-catch also
    return new HypercoreError(msg, 'ERR_ASSERTION', HypercoreError.ASSERT, discoveryKey)
  }

  static BAD_ARGUMENT (msg, discoveryKey = null) {
    return new HypercoreError(msg, 'BAD_ARGUMENT', HypercoreError.BAD_ARGUMENT, discoveryKey)
  }

  static STORAGE_EMPTY (msg, discoveryKey = null) {
    return new HypercoreError(msg, 'STORAGE_EMPTY', HypercoreError.STORAGE_EMPTY, discoveryKey)
  }

  static STORAGE_CONFLICT (msg, discoveryKey = null) {
    return new HypercoreError(msg, 'STORAGE_CONFLICT', HypercoreError.STORAGE_CONFLICT, discoveryKey)
  }

  static INVALID_SIGNATURE (msg, discoveryKey = null) {
    return new HypercoreError(msg, 'INVALID_SIGNATURE', HypercoreError.INVALID_SIGNATURE, discoveryKey)
  }

  static INVALID_CAPABILITY (msg, discoveryKey = null) {
    return new HypercoreError(msg, 'INVALID_CAPABILITY', HypercoreError.INVALID_CAPABILITY, discoveryKey)
  }

  static INVALID_CHECKSUM (msg = 'Invalid checksum', discoveryKey = null) {
    return new HypercoreError(msg, 'INVALID_CHECKSUM', HypercoreError.INVALID_CHECKSUM, discoveryKey)
  }

  static INVALID_OPERATION (msg, discoveryKey = null) {
    return new HypercoreError(msg, 'INVALID_OPERATION', HypercoreError.INVALID_OPERATION, discoveryKey)
  }

  static INVALID_PROOF (msg = 'Proof not verifiable', discoveryKey = null) {
    return new HypercoreError(msg, 'INVALID_PROOF', HypercoreError.INVALID_PROOF, discoveryKey)
  }

  static BLOCK_NOT_AVAILABLE (msg = 'Block is not available', discoveryKey = null) {
    return new HypercoreError(msg, 'BLOCK_NOT_AVAILABLE', HypercoreError.BLOCK_NOT_AVAILABLE, discoveryKey)
  }

  static SNAPSHOT_NOT_AVAILABLE (msg = 'Snapshot is not available', discoveryKey = null) {
    return new HypercoreError(msg, 'SNAPSHOT_NOT_AVAILABLE', HypercoreError.SNAPSHOT_NOT_AVAILABLE, discoveryKey)
  }

  static REQUEST_CANCELLED (msg = 'Request was cancelled', discoveryKey = null) {
    return new HypercoreError(msg, 'REQUEST_CANCELLED', HypercoreError.REQUEST_CANCELLED, discoveryKey)
  }

  static REQUEST_TIMEOUT (msg = 'Request timed out', discoveryKey = null) {
    return new HypercoreError(msg, 'REQUEST_TIMEOUT', HypercoreError.REQUEST_TIMEOUT, discoveryKey)
  }

  static SESSION_NOT_WRITABLE (msg = 'Session is not writable', discoveryKey = null) {
    return new HypercoreError(msg, 'SESSION_NOT_WRITABLE', HypercoreError.SESSION_NOT_WRITABLE, discoveryKey)
  }

  static SESSION_CLOSED (msg = 'Session is closed', discoveryKey = null) {
    return new HypercoreError(msg, 'SESSION_CLOSED', HypercoreError.SESSION_CLOSED, discoveryKey)
  }

  static BATCH_UNFLUSHED (msg = 'Batch not yet flushed', discoveryKey = null) {
    return new HypercoreError(msg, 'BATCH_UNFLUSHED', HypercoreError.BATCH_UNFLUSHED, discoveryKey)
  }

  static BATCH_ALREADY_EXISTS (msg = 'Batch already exists', discoveryKey = null) {
    return new HypercoreError(msg, 'BATCH_ALREADY_EXISTS', HypercoreError.BATCH_ALREADY_EXISTS, discoveryKey)
  }

  static BATCH_ALREADY_FLUSHED (msg = 'Batch has already been flushed', discoveryKey = null) {
    return new HypercoreError(msg, 'BATCH_ALREADY_FLUSHED', HypercoreError.BATCH_ALREADY_FLUSHED, discoveryKey)
  }

  static OPLOG_CORRUPT (msg = 'Oplog file appears corrupt or out of date', discoveryKey = null) {
    return new HypercoreError(msg, 'OPLOG_CORRUPT', HypercoreError.OPLOG_CORRUPT, discoveryKey)
  }

  static OPLOG_HEADER_OVERFLOW (msg = 'Oplog header exceeds page size', discoveryKey = null) {
    return new HypercoreError(msg, 'OPLOG_HEADER_OVERFLOW', HypercoreError.OPLOG_HEADER_OVERFLOW, discoveryKey)
  }

  static INVALID_OPLOG_VERSION (msg = 'Invalid header version', discoveryKey = null) {
    return new HypercoreError(msg, 'INVALID_OPLOG_VERSION', HypercoreError.INVALID_OPLOG_VERSION, discoveryKey)
  }

  static WRITE_FAILED (msg = 'Write to storage failed', discoveryKey = null) {
    return new HypercoreError(msg, 'WRITE_FAILED', HypercoreError.WRITE_FAILED, discoveryKey)
  }

  static DECODING_ERROR (msg = 'Decoding error', discoveryKey = null) {
    return new HypercoreError(msg, 'DECODING_ERROR', HypercoreError.DECODING_ERROR, discoveryKey)
  }

  static SESSION_MOVED (msg = 'Session moved', discoveryKey = null) {
    return new HypercoreError(msg, 'SESSION_MOVED', HypercoreError.SESSION_MOVED, discoveryKey)
  }
}
{
  "name": "hypercore-errors",
  "version": "1.5.0",
  "description": "Hypercore errors",
  "main": "index.js",
  "files": [
    "index.js"
  ],
  "scripts": {
    "test": "standard && brittle test.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/holepunchto/hypercore-errors.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/hypercore-errors/issues"
  },
  "homepage": "https://github.com/holepunchto/hypercore-errors#readme",
  "devDependencies": {
    "b4a": "^1.6.7",
    "brittle": "^3.1.3",
    "standard": "^17.0.0"
  },
  "dependencies": {
    "hypercore-id-encoding": "^1.3.0"
  }
}
const z32 = require('z32')
const b4a = require('b4a')

module.exports = {
  encode,
  decode,
  normalize,
  isValid
}

function encode (key) {
  if (!b4a.isBuffer(key)) throw new Error('Key must be a Buffer')
  if (key.byteLength !== 32) throw new Error('Key must be 32-bytes long')
  return z32.encode(key)
}

function decode (id) {
  if (b4a.isBuffer(id)) {
    if (id.byteLength !== 32) throw new Error('ID must be 32-bytes long')
    return id
  }
  if (typeof id === 'string') {
    if (id.startsWith('pear://')) id = id.slice(7).split('/')[0]
    if (id.length === 52) return z32.decode(id)
    if (id.length === 64) {
      const buf = b4a.from(id, 'hex')
      if (buf.byteLength === 32) return buf
    }
  }
  throw new Error('Invalid Hypercore key')
}

function normalize (any) {
  return encode(decode(any))
}

function isValid (any) {
  try {
    decode(any)
    return true
  } catch {
    return false
  }
}
{
  "name": "hypercore-id-encoding",
  "version": "1.3.0",
  "description": "Convert Hypercore keys to/from z-base32 or hex",
  "main": "index.js",
  "scripts": {
    "test": "standard && brittle test.js"
  },
  "files": [
    "index.js"
  ],
  "dependencies": {
    "b4a": "^1.5.3",
    "z32": "^1.0.0"
  },
  "devDependencies": {
    "brittle": "^3.1.1",
    "hypercore": "^10.0.0",
    "random-access-memory": "^6.0.0",
    "standard": "^17.1.0"
  },
  "license": "Apache-2.0",
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/hypercore-id-encoding.git"
  },
  "keywords": [],
  "author": "",
  "bugs": {
    "url": "https://github.com/holepunchto/hypercore-id-encoding/issues"
  },
  "homepage": "https://github.com/holepunchto/hypercore-id-encoding#readme"
}
var queueTick = require('queue-tick')

var mutexify = function () {
  var queue = []
  var used = null

  var call = function () {
    used(release)
  }

  var acquire = function (fn) {
    if (used) return queue.push(fn)
    used = fn
    acquire.locked = true
    queueTick(call)
    return 0
  }

  acquire.locked = false

  var release = function (fn, err, value) {
    used = null
    acquire.locked = false
    if (queue.length) acquire(queue.shift())
    if (fn) fn(err, value)
  }

  return acquire
}

module.exports = mutexify
{
  "name": "mutexify",
  "version": "1.4.0",
  "description": "mutex lock for javascript",
  "main": "index.js",
  "dependencies": {
    "queue-tick": "^1.0.0"
  },
  "devDependencies": {
    "standard": "^14.3.3",
    "tape": "^3.0.2"
  },
  "scripts": {
    "test": "tape test.js",
    "posttest": "npm run lint",
    "lint": "standard"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/mafintosh/mutexify.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/mafintosh/mutexify/issues"
  },
  "keywords": [
    "mutex",
    "lock"
  ],
  "homepage": "https://github.com/mafintosh/mutexify"
}
var mutexify = require('.')

var mutexifyPromise = function () {
  var lock = mutexify()

  var acquire = function () {
    return new Promise(lock)
  }

  Object.defineProperty(acquire, 'locked', {
    get: function () { return lock.locked },
    enumerable: true
  })

  return acquire
}

module.exports = mutexifyPromise
var varint = require('varint')
var svarint = require('signed-varint')
var b4a = require('b4a')

exports.make = encoder

exports.name = function (enc) {
  var keys = Object.keys(exports)
  for (var i = 0; i < keys.length; i++) {
    if (exports[keys[i]] === enc) return keys[i]
  }
  return null
}

exports.skip = function (type, buffer, offset) {
  switch (type) {
    case 0:
      varint.decode(buffer, offset)
      return offset + varint.decode.bytes

    case 1:
      return offset + 8

    case 2:
      var len = varint.decode(buffer, offset)
      return offset + varint.decode.bytes + len

    case 3:
    case 4:
      throw new Error('Groups are not supported')

    case 5:
      return offset + 4
  }

  throw new Error('Unknown wire type: ' + type)
}

exports.bytes = encoder(2,
  function encode (val, buffer, offset) {
    var oldOffset = offset
    var len = bufferLength(val)

    varint.encode(len, buffer, offset)
    offset += varint.encode.bytes

    if (b4a.isBuffer(val)) b4a.copy(val, buffer, offset)
    else b4a.write(buffer, val, offset, len)
    offset += len

    encode.bytes = offset - oldOffset
    return buffer
  },
  function decode (buffer, offset) {
    var oldOffset = offset

    var len = varint.decode(buffer, offset)
    offset += varint.decode.bytes

    var val = buffer.subarray(offset, offset + len)
    offset += val.length

    decode.bytes = offset - oldOffset
    return val
  },
  function encodingLength (val) {
    var len = bufferLength(val)
    return varint.encodingLength(len) + len
  }
)

exports.string = encoder(2,
  function encode (val, buffer, offset) {
    var oldOffset = offset
    var len = b4a.byteLength(val)

    varint.encode(len, buffer, offset, 'utf-8')
    offset += varint.encode.bytes

    b4a.write(buffer, val, offset, len)
    offset += len

    encode.bytes = offset - oldOffset
    return buffer
  },
  function decode (buffer, offset) {
    var oldOffset = offset

    var len = varint.decode(buffer, offset)
    offset += varint.decode.bytes

    var val = b4a.toString(buffer, 'utf-8', offset, offset + len)
    offset += len

    decode.bytes = offset - oldOffset
    return val
  },
  function encodingLength (val) {
    var len = b4a.byteLength(val)
    return varint.encodingLength(len) + len
  }
)

exports.bool = encoder(0,
  function encode (val, buffer, offset) {
    buffer[offset] = val ? 1 : 0
    encode.bytes = 1
    return buffer
  },
  function decode (buffer, offset) {
    var bool = buffer[offset] > 0
    decode.bytes = 1
    return bool
  },
  function encodingLength () {
    return 1
  }
)

exports.int32 = encoder(0,
  function encode (val, buffer, offset) {
    varint.encode(val < 0 ? val + 4294967296 : val, buffer, offset)
    encode.bytes = varint.encode.bytes
    return buffer
  },
  function decode (buffer, offset) {
    var val = varint.decode(buffer, offset)
    decode.bytes = varint.decode.bytes
    return val > 2147483647 ? val - 4294967296 : val
  },
  function encodingLength (val) {
    return varint.encodingLength(val < 0 ? val + 4294967296 : val)
  }
)

exports.int64 = encoder(0,
  function encode (val, buffer, offset) {
    if (val < 0) {
      var last = offset + 9
      varint.encode(val * -1, buffer, offset)
      offset += varint.encode.bytes - 1
      buffer[offset] = buffer[offset] | 0x80
      while (offset < last - 1) {
        offset++
        buffer[offset] = 0xff
      }
      buffer[last] = 0x01
      encode.bytes = 10
    } else {
      varint.encode(val, buffer, offset)
      encode.bytes = varint.encode.bytes
    }
    return buffer
  },
  function decode (buffer, offset) {
    var val = varint.decode(buffer, offset)
    if (val >= Math.pow(2, 63)) {
      var limit = 9
      while (buffer[offset + limit - 1] === 0xff) limit--
      limit = limit || 9
      var subset = b4a.allocUnsafe(limit)
      b4a.copy(buffer, subset, 0, offset, offset + limit)
      subset[limit - 1] = subset[limit - 1] & 0x7f
      val = -1 * varint.decode(subset, 0)
      decode.bytes = 10
    } else {
      decode.bytes = varint.decode.bytes
    }
    return val
  },
  function encodingLength (val) {
    return val < 0 ? 10 : varint.encodingLength(val)
  }
)

exports.sint32 =
exports.sint64 = encoder(0,
  svarint.encode,
  svarint.decode,
  svarint.encodingLength
)

exports.uint32 =
exports.uint64 =
exports.enum =
exports.varint = encoder(0,
  varint.encode,
  varint.decode,
  varint.encodingLength
)

// we cannot represent these in javascript so we just use buffers
exports.fixed64 =
exports.sfixed64 = encoder(1,
  function encode (val, buffer, offset) {
    b4a.copy(val, buffer, offset)
    encode.bytes = 8
    return buffer
  },
  function decode (buffer, offset) {
    var val = buffer.subarray(offset, offset + 8)
    decode.bytes = 8
    return val
  },
  function encodingLength () {
    return 8
  }
)

exports.double = encoder(1,
  function encode (val, buffer, offset) {
    b4a.writeDoubleLE(buffer, val, offset)
    encode.bytes = 8
    return buffer
  },
  function decode (buffer, offset) {
    var val = b4a.readDoubleLE(buffer, offset)
    decode.bytes = 8
    return val
  },
  function encodingLength () {
    return 8
  }
)

exports.fixed32 = encoder(5,
  function encode (val, buffer, offset) {
    b4a.writeUInt32LE(buffer, val, offset)
    encode.bytes = 4
    return buffer
  },
  function decode (buffer, offset) {
    var val = b4a.readUInt32LE(buffer, offset)
    decode.bytes = 4
    return val
  },
  function encodingLength () {
    return 4
  }
)

exports.sfixed32 = encoder(5,
  function encode (val, buffer, offset) {
    b4a.writeInt32LE(buffer, val, offset)
    encode.bytes = 4
    return buffer
  },
  function decode (buffer, offset) {
    var val = b4a.readInt32LE(buffer, offset)
    decode.bytes = 4
    return val
  },
  function encodingLength () {
    return 4
  }
)

exports.float = encoder(5,
  function encode (val, buffer, offset) {
    b4a.writeFloatLE(buffer, val, offset)
    encode.bytes = 4
    return buffer
  },
  function decode (buffer, offset) {
    var val = b4a.readFloatLE(buffer, offset)
    decode.bytes = 4
    return val
  },
  function encodingLength () {
    return 4
  }
)

function encoder (type, encode, decode, encodingLength) {
  encode.bytes = decode.bytes = 0

  return {
    type: type,
    encode: encode,
    decode: decode,
    encodingLength: encodingLength
  }
}

function bufferLength (val) {
  return b4a.isBuffer(val) ? val.length : b4a.byteLength(val)
}
{
  "name": "protocol-buffers-encodings",
  "version": "1.2.0",
  "description": "Base encodings for protocol-buffers",
  "main": "index.js",
  "dependencies": {
    "b4a": "^1.6.0",
    "signed-varint": "^2.0.1",
    "varint": "5.0.0"
  },
  "devDependencies": {
    "standard": "^14.3.4",
    "tape": "^5.0.1"
  },
  "scripts": {
    "test": "standard && tape test.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/mafintosh/protocol-buffers-encodings.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/mafintosh/protocol-buffers-encodings/issues"
  },
  "homepage": "https://github.com/mafintosh/protocol-buffers-encodings"
}
{
  "name": "queue-tick",
  "version": "1.0.1",
  "description": "Next tick shim that prefers process.nextTick over queueMicrotask for compat",
  "main": "./process-next-tick.js",
  "browser": "./queue-microtask.js",
  "dependencies": {},
  "devDependencies": {
    "standard": "^16.0.3",
    "tape": "^5.3.1"
  },
  "scripts": {
    "test": "standard && tape test.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/mafintosh/queue-tick.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/mafintosh/queue-tick/issues"
  },
  "homepage": "https://github.com/mafintosh/queue-tick"
}
module.exports = (typeof process !== 'undefined' && typeof process.nextTick === 'function')
  ? process.nextTick.bind(process)
  : require('./queue-microtask')
module.exports = typeof queueMicrotask === 'function' ? queueMicrotask : (fn) => Promise.resolve().then(fn)
class CacheEntry {
  constructor (key, index, map) {
    this.key = key
    this.index = index
    this.map = map
  }
}

class CacheValue {
  constructor (entry, value) {
    this.entry = entry
    this.value = value
  }
}

class Rache {
  constructor ({ maxSize = 65536, parent = null } = {}) {
    this.maxSize = parent?.maxSize || maxSize

    this._array = parent?._array || []
    this._map = new Map()
  }

  static from (cache) {
    return cache ? new this({ parent: cache }) : new this()
  }

  get globalSize () {
    return this._array.length
  }

  get size () {
    return this._map.size
  }

  sub () {
    return new Rache({ parent: this })
  }

  set (key, value) { // ~constant time
    const existing = this._map.get(key)
    if (existing !== undefined) {
      existing.value = value
      return
    }

    if (this._array.length >= this.maxSize) this._gc()

    const entry = new CacheEntry(key, this._array.length, this._map)
    this._array.push(entry)
    const cacheValue = new CacheValue(entry, value)
    this._map.set(key, cacheValue)
  }

  delete (key) {
    const existing = this._map.get(key)
    if (existing === undefined) return false

    this._delete(existing.entry.index)
    return true
  }

  get (key) {
    const existing = this._map.get(key)
    return existing === undefined ? undefined : existing.value
  }

  * [Symbol.iterator] () {
    for (const [key, { value }] of this._map) {
      yield [key, value]
    }
  }

  keys () {
    return this._map.keys()
  }

  * values () {
    for (const { value } of this._map.values()) {
      yield value
    }
  }

  clear () {
    // The entries in map linger on in _array,
    // so on top of clearing the map, we also kill the ref,
    // so that any gc running later on the old map won't interfere
    // (in case a new entry was added with the same key as a cleared entry)

    this._map.clear()
    this._map = new Map()
  }

  destroy () {
    this._map = null
    this._array = null
  }

  _gc () {
    this._delete(Math.floor(Math.random() * this._array.length))
  }

  _delete (index) { // ~constant time
    if (index >= this._array.length) throw new Error('Cannot delete unused index (logic bug?)')

    const head = this._array.pop()
    let removed = head

    if (index < this._array.length) {
      removed = this._array[index]
      head.index = index
      this._array[index] = head
    }

    removed.map.delete(removed.key)
  }
}

module.exports = Rache
{
  "name": "rache",
  "version": "1.0.0",
  "description": "Random-eviction cache",
  "main": "index.js",
  "scripts": {
    "test": "standard && brittle test.js --coverage"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/rache.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/rache/issues"
  },
  "homepage": "https://github.com/holepunchto/rache#readme",
  "files": [
    "index.js"
  ],
  "dependencies": {},
  "devDependencies": {
    "brittle": "^3.5.2",
    "standard": "^17.1.0"
  }
}
const EventEmitter = require('events')

module.exports = class ReadyResource extends EventEmitter {
  constructor () {
    super()

    this.opening = null
    this.closing = null

    this.opened = false
    this.closed = false
  }

  ready () {
    if (this.opening !== null) return this.opening
    this.opening = open(this)
    return this.opening
  }

  close () {
    if (this.closing !== null) return this.closing
    this.closing = close(this)
    return this.closing
  }

  async _open () {
    // add impl here
  }

  async _close () {
    // add impl here
  }
}

async function open (self) {
  // open after close
  if (self.closing !== null) return

  try {
    await self._open()
  } catch (err) {
    self.close() // safe to run in bg
    throw err
  }

  self.opened = true
  self.emit('ready')
}

async function close (self) {
  try {
    if (self.opened === false && self.opening !== null) await self.opening
  } catch {
    // ignore errors on closing
  }
  if (self.opened === true || self.opening === null) await self._close()
  self.closed = true
  self.emit('close')
}
{
  "name": "ready-resource",
  "version": "1.2.0",
  "description": "Modern single resource management",
  "main": "index.js",
  "imports": {
    "events": {
      "bare": "bare-events",
      "default": "events"
    }
  },
  "types": "index.d.ts",
  "files": [
    "index.js",
    "index.d.ts"
  ],
  "scripts": {
    "test": "standard && brittle test.js"
  },
  "devDependencies": {
    "brittle": "^3.1.0",
    "standard": "^17.0.0"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/ready-resource.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/holepunchto/ready-resource/issues"
  },
  "homepage": "https://github.com/holepunchto/ready-resource",
  "dependencies": {
    "bare-events": "^2.2.0"
  }
}
module.exports = require.addon.bind(require)
{
  "name": "require-addon",
  "version": "1.2.0",
  "description": "Import native addons across JavaScript runtimes",
  "exports": {
    "./package": "./package.json",
    ".": {
      "bare": "./lib/bare.js",
      "node": "./lib/node.js",
      "default": "./lib/default.js"
    }
  },
  "imports": {
    "fs": {
      "bare": "bare-fs",
      "default": "fs"
    },
    "path": {
      "bare": "bare-path",
      "default": "path"
    },
    "url": {
      "bare": "bare-url",
      "default": "url"
    }
  },
  "files": [
    "lib"
  ],
  "scripts": {
    "test": "npm run lint && npm run test:bare && npm run test:node",
    "test:bare": "bare test.js",
    "test:node": "node test.js",
    "lint": "prettier --check .",
    "format": "prettier --write ."
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/require-addon.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/require-addon/issues"
  },
  "homepage": "https://github.com/holepunchto/require-addon#readme",
  "engines": {
    "bare": ">=1.10.0"
  },
  "dependencies": {
    "bare-addon-resolve": "^1.3.0"
  },
  "devDependencies": {
    "bare-bundle": "^1.8.1",
    "bare-bundle-evaluate": "^1.1.0",
    "bare-fs": "^4.0.0",
    "bare-path": "^3.0.0",
    "bare-url": "^2.1.0",
    "brittle": "^3.7.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^2.0.0"
  }
}
let tmpResolve = null
let tmpReject = null

if (Promise.withResolvers) {
  module.exports = Promise.withResolvers.bind(Promise)
} else {
  module.exports = function resolveRejectPromise () {
    const promise = new Promise(setTmp)
    const result = { promise, resolve: tmpResolve, reject: tmpReject }
    tmpResolve = tmpReject = null
    return result
  }
}

function setTmp (resolve, reject) {
  tmpResolve = resolve
  tmpReject = reject
}
{
  "name": "resolve-reject-promise",
  "version": "1.1.0",
  "description": "Create an inverted promise with no function allocs",
  "main": "index.js",
  "scripts": {
    "test": "standard"
  },
  "devDependencies": {
    "standard": "^17.1.2"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/mafintosh/resolve-reject-promise.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/mafintosh/resolve-reject-promise/issues"
  },
  "homepage": "https://github.com/mafintosh/resolve-reject-promise"
}
module.exports = safetyCatch

function isActuallyUncaught (err) {
  if (!err) return false
  return err instanceof TypeError ||
    err instanceof SyntaxError ||
    err instanceof ReferenceError ||
    err instanceof EvalError ||
    err instanceof RangeError ||
    err instanceof URIError ||
    err.code === 'ERR_ASSERTION' ||
    err.name === 'AssertionError'
}

function throwErrorNT (err) {
  queueMicrotask(() => { throw err })
}

function safetyCatch (err) {
  if (isActuallyUncaught(err)) {
    throwErrorNT(err)
    throw err
  }
}
{
  "name": "safety-catch",
  "version": "1.0.3",
  "description": "Small module that makes sure your catch, caught an actual error and not a programming mistake or assertion",
  "main": "index.js",
  "dependencies": {},
  "devDependencies": {},
  "repository": {
    "type": "git",
    "url": "https://github.com/mafintosh/safety-catch.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/mafintosh/safety-catch/issues"
  },
  "homepage": "https://github.com/mafintosh/safety-catch"
}
var varint = require('varint')
exports.encode = function encode (v, b, o) {
  v = v >= 0 ? v*2 : v*-2 - 1
  var r = varint.encode(v, b, o)
  encode.bytes = varint.encode.bytes
  return r
}
exports.decode = function decode (b, o) {
  var v = varint.decode(b, o)
  decode.bytes = varint.decode.bytes
  return v & 1 ? (v+1) / -2 : v / 2
}

exports.encodingLength = function (v) {
  return varint.encodingLength(v >= 0 ? v*2 : v*-2 - 1)
}
{
  "name": "signed-varint",
  "description": "efficiently store signed integers in varint",
  "version": "2.0.1",
  "homepage": "https://github.com/dominictarr/signed-varint",
  "repository": {
    "type": "git",
    "url": "git://github.com/dominictarr/signed-varint.git"
  },
  "dependencies": {
    "varint": "~5.0.0"
  },
  "devDependencies": {
    "tape": "~2.12.3"
  },
  "scripts": {
    "test": "node test.js"
  },
  "author": "Dominic Tarr <dominic.tarr@gmail.com> (http://dominictarr.com)",
  "license": "MIT"
}
require.addon = require('require-addon')
module.exports = require.addon('.', __filename)
const assert = require('assert')
const binding = require('./binding')
const { isNode } = require('which-runtime')

const OPTIONAL = Buffer.from(new ArrayBuffer(0))

module.exports = exports = { ...binding }

// memory

exports.sodium_memzero = function (buf) {
  assert(ArrayBuffer.isView(buf), 'buf must be a typed array')

  binding.sodium_memzero(buf)
}

exports.sodium_mlock = function (buf) {
  assert(ArrayBuffer.isView(buf), 'buf must be a typed array')

  const res = binding.sodium_mlock(buf)

  if (res !== 0) throw new Error('memory lock failed')
}

exports.sodium_munlock = function (buf) {
  assert(ArrayBuffer.isView(buf), 'buf must be a typed array')

  const res = binding.sodium_munlock(buf)

  if (res !== 0) throw new Error('memory unlock failed')
}

exports.sodium_malloc = function (size) {
  assert(size >= 0, 'invalid size')

  const buf = Buffer.from(binding.sodium_malloc(size))
  buf.secure = true
  return buf
}

exports.sodium_free = function (buf) {
  if (!buf || !buf.secure) return

  binding.sodium_free(buf.buffer)
}

exports.sodium_mprotect_noaccess = function (buf) {
  const res = binding.sodium_mprotect_noaccess(buf.buffer)

  if (res !== 0) throw new Error('failed to lock buffer')
}

exports.sodium_mprotect_readonly = function (buf) {
  const res = binding.sodium_mprotect_readonly(buf.buffer)

  if (res !== 0) throw new Error('failed to unlock buffer')
}

exports.sodium_mprotect_readwrite = function (buf) {
  const res = binding.sodium_mprotect_readwrite(buf.buffer)

  if (res !== 0) throw new Error('failed to unlock buffer')
}

// crypto_randombytes

exports.randombytes_buf = function (buffer) {
  assert(ArrayBuffer.isView(buffer), 'buffer must be a typed array')

  binding.randombytes_buf(buffer.buffer, buffer.byteOffset, buffer.byteLength)
}

exports.randombytes_buf_deterministic = function (buffer, seed) {
  assert(ArrayBuffer.isView(buffer), 'buffer must be a typed array')
  assert(ArrayBuffer.isView(seed), 'seed must be a typed array')
  assert(
    seed.byteLength === binding.randombytes_SEEDBYTES,
    "seed must be 'randombytes_SEEDBYTES' bytes"
  )

  binding.randombytes_buf_deterministic(
    buffer.buffer,
    buffer.byteOffset,
    buffer.byteLength,

    seed.buffer,
    seed.byteOffset,
    seed.byteLength
  )
}

// sodium_helpers

exports.sodium_memcmp = function (a, b) {
  assert(ArrayBuffer.isView(a), 'a must be a typed array')
  assert(ArrayBuffer.isView(b), 'b must be a typed array')
  assert(a.byteLength === b.byteLength, 'buffers must be of same length')

  return binding.sodium_memcmp(a, b)
}

exports.sodium_add = function (a, b) {
  assert(ArrayBuffer.isView(a), 'a must be a typed array')
  assert(ArrayBuffer.isView(b), 'b must be a typed array')
  assert(a.byteLength === b.byteLength, 'buffers must be of same length')

  binding.sodium_add(a, b)
}

exports.sodium_sub = function (a, b) {
  assert(ArrayBuffer.isView(a), 'a must be a typed array')
  assert(ArrayBuffer.isView(b), 'b must be a typed array')
  assert(a.byteLength === b.byteLength, 'buffers must be of same length')

  binding.sodium_sub(a, b)
}

exports.sodium_compare = function (a, b) {
  assert(ArrayBuffer.isView(a), 'a must be a typed array')
  assert(ArrayBuffer.isView(b), 'b must be a typed array')
  assert(a.byteLength === b.byteLength, 'buffers must be of same length')

  return binding.sodium_compare(a, b)
}

exports.sodium_is_zero = function (buffer, length) {
  if (length === undefined) length = buffer.byteLength

  assert(ArrayBuffer.isView(buffer), 'buffer must be a typed array')
  assert(length >= 0 && length <= buffer.byteLength, 'invalid length')

  return binding.sodium_is_zero(buffer, length)
}

exports.sodium_pad = function (buffer, unpaddedBuflen, blockSize) {
  assert(ArrayBuffer.isView(buffer), 'buffer must be a typed array')
  assert(unpaddedBuflen <= buffer.byteLength, 'unpadded length cannot exceed buffer length')
  assert(blockSize <= buffer.byteLength, 'block size cannot exceed buffer length')
  assert(blockSize >= 1, 'block size must be at least 1 byte')
  assert(
    buffer.byteLength >= unpaddedBuflen + (blockSize - (unpaddedBuflen % blockSize)),
    'buf not long enough'
  )

  return binding.sodium_pad(buffer, unpaddedBuflen, blockSize)
}

exports.sodium_unpad = function (buffer, paddedBuflen, blockSize) {
  assert(ArrayBuffer.isView(buffer), 'buffer must be a typed array')
  assert(paddedBuflen <= buffer.byteLength, 'unpadded length cannot exceed buffer length')
  assert(blockSize <= buffer.byteLength, 'block size cannot exceed buffer length')
  assert(blockSize >= 1, 'block size must be at least 1 byte')

  return binding.sodium_unpad(buffer, paddedBuflen, blockSize)
}

// crypto_sign

exports.crypto_sign_keypair = function (pk, sk) {
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(
    pk.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
    "pk must be 'crypto_sign_PUBLICKEYBYTES' bytes"
  )
  assert(
    sk.byteLength === binding.crypto_sign_SECRETKEYBYTES,
    "sk must be 'crypto_sign_SECRETKEYBYTES' bytes"
  )

  const res = binding.crypto_sign_keypair(pk, sk)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_sign_seed_keypair = function (pk, sk, seed) {
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(ArrayBuffer.isView(seed), 'seed must be a typed array')
  assert(
    pk.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
    "pk must be 'crypto_sign_PUBLICKEYBYTES' bytes"
  )
  assert(
    sk.byteLength === binding.crypto_sign_SECRETKEYBYTES,
    "sk must be 'crypto_sign_SECRETKEYBYTES' bytes"
  )
  assert(
    seed.byteLength === binding.crypto_sign_SEEDBYTES,
    "seed must be 'crypto_sign_SEEDBYTES' bytes"
  )

  const res = binding.crypto_sign_seed_keypair(pk, sk, seed)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_sign = function (sm, m, sk) {
  assert(ArrayBuffer.isView(sm), 'sm must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(
    sm.byteLength === binding.crypto_sign_BYTES + m.byteLength,
    "sm must be 'm.byteLength + crypto_sign_BYTES' bytes"
  )
  assert(
    sk.byteLength === binding.crypto_sign_SECRETKEYBYTES,
    "sk must be 'crypto_sign_SECRETKEYBYTES' bytes"
  )

  const res = binding.crypto_sign(sm, m, sk)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_sign_open = function (m, sm, pk) {
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(sm), 'sm must be a typed array')
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(
    sm.byteLength >= binding.crypto_sign_BYTES,
    "sm must be at least 'crypto_sign_BYTES' bytes"
  )
  assert(
    m.byteLength === sm.byteLength - binding.crypto_sign_BYTES,
    "m must be 'sm.byteLength - crypto_sign_BYTES' bytes"
  )
  assert(
    pk.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
    "pk must be 'crypto_sign_PUBLICKEYBYTES' bytes"
  )

  const res = binding.crypto_sign_open(m, sm, pk)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_sign_open = function (m, sm, pk) {
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(sm), 'sm must be a typed array')
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(
    sm.byteLength >= binding.crypto_sign_BYTES,
    "sm must be at least 'crypto_sign_BYTES' bytes"
  )
  assert(
    m.byteLength === sm.byteLength - binding.crypto_sign_BYTES,
    "m must be 'sm.byteLength - crypto_sign_BYTES' bytes"
  )
  assert(
    pk.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
    "pk must be 'crypto_sign_PUBLICKEYBYTES' bytes"
  )

  return binding.crypto_sign_open(m, sm, pk)
}

exports.crypto_sign_detached = function (sig, m, sk) {
  assert(ArrayBuffer.isView(sig), 'sig must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(sig.byteLength === binding.crypto_sign_BYTES, "sig must be 'crypto_sign_BYTES' bytes")
  assert(
    sk.byteLength === binding.crypto_sign_SECRETKEYBYTES,
    "sk must be 'crypto_sign_SECRETKEYBYTES' bytes"
  )

  const res = binding.crypto_sign_detached(sig, m, sk)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_sign_verify_detached = function (sig, m, pk) {
  assert(ArrayBuffer.isView(sig), 'sig must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(
    sig.byteLength >= binding.crypto_sign_BYTES,
    "sig must be at least 'crypto_sign_BYTES' bytes"
  )
  assert(
    pk.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
    "pk must be 'crypto_sign_PUBLICKEYBYTES' bytes"
  )

  return binding.crypto_sign_verify_detached(
    sig.buffer,
    sig.byteOffset,
    sig.byteLength,

    m.buffer,
    m.byteOffset,
    m.byteLength,

    pk.buffer,
    pk.byteOffset,
    pk.byteLength
  )
}

exports.crypto_sign_ed25519_sk_to_pk = function (pk, sk) {
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(
    pk.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
    "pk must be 'crypto_sign_PUBLICKEYBYTES' bytes"
  )
  assert(
    sk.byteLength === binding.crypto_sign_SECRETKEYBYTES,
    "sk must be 'crypto_sign_SECRETKEYBYTES' bytes"
  )

  const res = binding.crypto_sign_ed25519_sk_to_pk(pk, sk)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_sign_ed25519_pk_to_curve25519 = function (x25519pk, ed25519pk) {
  assert(ArrayBuffer.isView(x25519pk), 'x25519pk must be a typed array')
  assert(ArrayBuffer.isView(ed25519pk), 'ed25519pk must be a typed array')
  assert(
    x25519pk.byteLength === binding.crypto_box_PUBLICKEYBYTES,
    "x25519pk must be 'crypto_box_PUBLICKEYBYTES' bytes"
  )
  assert(
    ed25519pk.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
    "ed25519pk must be 'crypto_sign_PUBLICKEYBYTES' bytes"
  )

  const res = binding.crypto_sign_ed25519_pk_to_curve25519(x25519pk, ed25519pk)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_sign_ed25519_sk_to_curve25519 = function (x25519sk, ed25519sk) {
  assert(ArrayBuffer.isView(x25519sk), 'x25519sk must be a typed array')
  assert(ArrayBuffer.isView(ed25519sk), 'ed25519sk must be a typed array')
  assert(
    x25519sk.byteLength === binding.crypto_box_SECRETKEYBYTES,
    "x25519sk must be 'crypto_box_SECRETKEYBYTES' bytes"
  )

  const edLen = ed25519sk.byteLength

  assert(
    edLen === binding.crypto_sign_SECRETKEYBYTES || edLen === binding.crypto_box_SECRETKEYBYTES,
    "ed25519sk must be 'crypto_sign_SECRETKEYBYTES' or 'crypto_sign_SECRETKEYBYTES - crypto_sign_PUBLICKEYBYTES' bytes"
  )

  const res = binding.crypto_sign_ed25519_sk_to_curve25519(x25519sk, ed25519sk)

  if (res !== 0) throw new Error('status: ' + res)
}

// crypto_box

exports.crypto_box_keypair = function (pk, sk) {
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(
    pk.byteLength === binding.crypto_box_PUBLICKEYBYTES,
    "pk must be 'crypto_box_PUBLICKEYBYTES' bytes"
  )
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(
    sk.byteLength === binding.crypto_box_SECRETKEYBYTES,
    "sk must be 'crypto_box_SECRETKEYBYTES' bytes"
  )

  const res = binding.crypto_box_keypair(pk, sk)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_box_seed_keypair = function (pk, sk, seed) {
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(
    pk.byteLength === binding.crypto_box_PUBLICKEYBYTES,
    "pk must be 'crypto_box_PUBLICKEYBYTES' bytes"
  )
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(
    sk.byteLength === binding.crypto_box_SECRETKEYBYTES,
    "sk must be 'crypto_box_SECRETKEYBYTES' bytes"
  )
  assert(ArrayBuffer.isView(seed), 'seed must be a typed array')
  assert(
    seed.byteLength === binding.crypto_box_SEEDBYTES,
    "seed must be 'crypto_box_SEEDBYTES' bytes"
  )

  const res = binding.crypto_box_seed_keypair(pk, sk, seed)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_box_easy = function (c, m, n, pk, sk) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(
    c.byteLength === m.byteLength + exports.crypto_box_MACBYTES,
    "c must be 'm.byteLength + crypto_box_MACBYTES' bytes"
  )
  assert(n.byteLength === exports.crypto_box_NONCEBYTES, "n must be 'crypto_box_NONCEBYTES' bytes")
  assert(
    pk.byteLength === exports.crypto_box_PUBLICKEYBYTES,
    "pk must be 'crypto_box_PUBLICKEYBYTES' bytes"
  )
  assert(
    sk.byteLength === exports.crypto_box_SECRETKEYBYTES,
    "sk must be 'crypto_box_SECRETKEYBYTES' bytes"
  )

  const res = binding.crypto_box_easy(c, m, n, pk, sk)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_box_detached = function (c, mac, m, n, pk, sk) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(mac), 'mac must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(mac.byteLength === exports.crypto_box_MACBYTES, "mac must be 'crypto_box_MACBYTES' bytes")
  assert(n.byteLength === exports.crypto_box_NONCEBYTES, "n must be 'crypto_box_NONCEBYTES' bytes")
  assert(
    pk.byteLength === exports.crypto_box_PUBLICKEYBYTES,
    "pk must be 'crypto_box_PUBLICKEYBYTES' bytes"
  )
  assert(
    sk.byteLength === exports.crypto_box_SECRETKEYBYTES,
    "sk must be 'crypto_box_SECRETKEYBYTES' bytes"
  )

  const res = binding.crypto_box_detached(c, mac, m, n, pk, sk)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_box_open_easy = function (m, c, n, pk, sk) {
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(
    c.byteLength >= exports.crypto_box_MACBYTES,
    "c must be at least 'crypto_box_MACBYTES' bytes"
  )
  assert(
    m.byteLength === c.byteLength - exports.crypto_box_MACBYTES,
    "m must be 'c.byteLength - crypto_box_MACBYTES' bytes"
  )
  assert(n.byteLength === exports.crypto_box_NONCEBYTES, "n must be 'crypto_box_NONCEBYTES' bytes")
  assert(
    pk.byteLength === exports.crypto_box_PUBLICKEYBYTES,
    "pk must be 'crypto_box_PUBLICKEYBYTES' bytes"
  )
  assert(
    sk.byteLength === exports.crypto_box_SECRETKEYBYTES,
    "sk must be 'crypto_box_SECRETKEYBYTES' bytes"
  )

  return binding.crypto_box_open_easy(m, c, n, pk, sk)
}

exports.crypto_box_open_detached = function (m, c, mac, n, pk, sk) {
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(mac), 'mac must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(m.byteLength === c.byteLength, "m must be 'c.byteLength' bytes")
  assert(mac.byteLength === exports.crypto_box_MACBYTES, "mac must be 'crypto_box_MACBYTES' bytes")
  assert(n.byteLength === exports.crypto_box_NONCEBYTES, "n must be 'crypto_box_NONCEBYTES' bytes")
  assert(
    pk.byteLength === exports.crypto_box_PUBLICKEYBYTES,
    "pk must be 'crypto_box_PUBLICKEYBYTES' bytes"
  )
  assert(
    sk.byteLength === exports.crypto_box_SECRETKEYBYTES,
    "sk must be 'crypto_box_SECRETKEYBYTES' bytes"
  )

  return binding.crypto_box_open_detached(m, c, mac, n, pk, sk)
}

exports.crypto_box_seal = function (c, m, pk) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(
    c.byteLength === m.byteLength + exports.crypto_box_SEALBYTES,
    "c must be 'm.byteLength + crypto_box_SEALBYTES' bytes"
  )
  assert(
    pk.byteLength === exports.crypto_box_PUBLICKEYBYTES,
    "pk must be 'crypto_box_PUBLICKEYBYTES' bytes"
  )

  const res = binding.crypto_box_seal(c, m, pk)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_box_seal_open = function (m, c, pk, sk) {
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(
    c.byteLength >= exports.crypto_box_SEALBYTES,
    "c must be at least 'crypto_box_SEALBYTES' bytes"
  )
  assert(
    m.byteLength === c.byteLength - exports.crypto_box_SEALBYTES,
    "m must be 'c.byteLength - crypto_box_SEALBYTES' bytes"
  )
  assert(
    pk.byteLength === exports.crypto_box_PUBLICKEYBYTES,
    "pk must be 'crypto_box_PUBLICKEYBYTES' bytes"
  )
  assert(
    sk.byteLength === exports.crypto_box_SECRETKEYBYTES,
    "sk must be 'crypto_box_SECRETKEYBYTES' bytes"
  )

  return binding.crypto_box_seal_open(
    m.buffer,
    m.byteOffset,
    m.byteLength,

    c.buffer,
    c.byteOffset,
    c.byteLength,

    pk.buffer,
    pk.byteOffset,
    pk.byteLength,

    sk.buffer,
    sk.byteOffset,
    sk.byteLength
  )
}

// crypto_secretbox

exports.crypto_secretbox_easy = function (c, m, n, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    c.byteLength === m.byteLength + binding.crypto_secretbox_MACBYTES,
    "c must be 'm.byteLength + crypto_secretbox_MACBYTES' bytes"
  )
  assert(
    n.byteLength === binding.crypto_secretbox_NONCEBYTES,
    "n must be 'crypto_secretbox_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_secretbox_KEYBYTES,
    "k must be 'crypto_secretbox_KEYBYTES' bytes"
  )

  const res = binding.crypto_secretbox_easy(c, m, n, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_secretbox_open_easy = function (m, c, n, k) {
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    c.byteLength >= binding.crypto_secretbox_MACBYTES,
    "c must be at least 'crypto_secretbox_MACBYTES' bytes"
  )
  assert(
    m.byteLength === c.byteLength - binding.crypto_secretbox_MACBYTES,
    "m must be 'c.byteLength - crypto_secretbox_MACBYTES' bytes"
  )
  assert(
    n.byteLength === binding.crypto_secretbox_NONCEBYTES,
    "n must be 'crypto_secretbox_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_secretbox_KEYBYTES,
    "k must be 'crypto_secretbox_KEYBYTES' bytes"
  )

  return binding.crypto_secretbox_open_easy(m, c, n, k)
}

exports.crypto_secretbox_detached = function (c, mac, m, n, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(mac), 'mac must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(
    mac.byteLength === binding.crypto_secretbox_MACBYTES,
    "mac must be 'crypto_secretbox_MACBYTES' bytes"
  )
  assert(
    n.byteLength === binding.crypto_secretbox_NONCEBYTES,
    "n must be 'crypto_secretbox_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_secretbox_KEYBYTES,
    "k must be 'crypto_secretbox_KEYBYTES' bytes"
  )

  const res = binding.crypto_secretbox_detached(c, mac, m, n, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_secretbox_open_detached = function (m, c, mac, n, k) {
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(mac), 'mac must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(m.byteLength === c.byteLength, "m must be 'c.byteLength' bytes")
  assert(
    mac.byteLength === binding.crypto_secretbox_MACBYTES,
    "mac must be 'crypto_secretbox_MACBYTES' bytes"
  )
  assert(
    n.byteLength === binding.crypto_secretbox_NONCEBYTES,
    "n must be 'crypto_secretbox_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_secretbox_KEYBYTES,
    "k must be 'crypto_secretbox_KEYBYTES' bytes"
  )

  return binding.crypto_secretbox_open_detached(m, c, mac, n, k)
}

// crypto_generichash

exports.crypto_generichash = function (output, input, key) {
  if (!key) key = OPTIONAL

  assert(ArrayBuffer.isView(output), 'output must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(
    output.byteLength >= binding.crypto_generichash_BYTES_MIN &&
      output.byteLength <= binding.crypto_generichash_BYTES_MAX,
    'output must be between crypto_generichash_BYTES_MIN and crypto_generichash_BYTES_MAX bytes'
  )

  if (key !== OPTIONAL) {
    assert(ArrayBuffer.isView(key), 'key must be a typed array')
    assert(
      key.byteLength >= binding.crypto_generichash_KEYBYTES_MIN &&
        key.byteLength <= binding.crypto_generichash_KEYBYTES_MAX,
      'key must be between crypto_generichash_KEYBYTES_MIN and crypto_generichash_KEYBYTES_MAX bytes'
    )
  }

  const res = binding.crypto_generichash(
    output.buffer,
    output.byteOffset,
    output.byteLength,

    input.buffer,
    input.byteOffset,
    input.byteLength,

    key.buffer,
    key.byteOffset,
    key.byteLength
  )

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_generichash_batch = function (output, batch, key) {
  assert(ArrayBuffer.isView(output), 'output must be a typed array')

  if (isNode || batch.length < 4) {
    const res = binding.crypto_generichash_batch(output, batch, !!key, key || OPTIONAL)
    if (res !== 0) throw new Error('status: ' + res)
  } else {
    const state = Buffer.alloc(binding.crypto_generichash_STATEBYTES)

    exports.crypto_generichash_init(state, key, output.byteLength)

    for (const buf of batch) {
      exports.crypto_generichash_update(state, buf)
    }

    exports.crypto_generichash_final(state, output)
  }
}

exports.crypto_generichash_keygen = function (key) {
  assert(ArrayBuffer.isView(key), 'key must be a typed array')
  assert(
    key.byteLength === binding.crypto_generichash_KEYBYTES,
    "key must be 'crypto_generichash_KEYBYTES' bytes"
  )

  const res = binding.crypto_generichash_keygen(key.buffer, key.byteOffset, key.byteLength)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_generichash_init = function (state, key, outputLength) {
  if (!key) key = OPTIONAL

  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_generichash_STATEBYTES,
    "state must be 'crypto_generichash_STATEBYTES' bytes"
  )

  const res = binding.crypto_generichash_init(
    state.buffer,
    state.byteOffset,
    state.byteLength,

    key.buffer,
    key.byteOffset,
    key.byteLength,

    outputLength
  )

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_generichash_update = function (state, input) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(
    state.byteLength === binding.crypto_generichash_STATEBYTES,
    "state must be 'crypto_generichash_STATEBYTES' bytes"
  )

  const res = binding.crypto_generichash_update(
    state.buffer,
    state.byteOffset,
    state.byteLength,

    input.buffer,
    input.byteOffset,
    input.byteLength
  )

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_generichash_final = function (state, output) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(ArrayBuffer.isView(output), 'output must be a typed array')
  assert(
    state.byteLength === binding.crypto_generichash_STATEBYTES,
    "state must be 'crypto_generichash_STATEBYTES' bytes"
  )

  const res = binding.crypto_generichash_final(
    state.buffer,
    state.byteOffset,
    state.byteLength,

    output.buffer,
    output.byteOffset,
    output.byteLength
  )

  if (res !== 0) throw new Error('status: ' + res)
}

// secretstream

exports.crypto_secretstream_xchacha20poly1305_keygen = function (k) {
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_secretstream_xchacha20poly1305_KEYBYTES,
    "k must be 'crypto_secretstream_xchacha20poly1305_KEYBYTES' bytes"
  )

  binding.crypto_secretstream_xchacha20poly1305_keygen(k.buffer, k.byteOffset, k.byteLength)
}

exports.crypto_secretstream_xchacha20poly1305_init_push = function (state, header, k) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(ArrayBuffer.isView(header), 'header must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    state.byteLength === binding.crypto_secretstream_xchacha20poly1305_STATEBYTES,
    "state must be 'crypto_secretstream_xchacha20poly1305_STATEBYTES' bytes"
  )
  assert(
    header.byteLength === binding.crypto_secretstream_xchacha20poly1305_HEADERBYTES,
    "header must be 'crypto_secretstream_xchacha20poly1305_HEADERBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_secretstream_xchacha20poly1305_KEYBYTES,
    "k must be 'crypto_secretstream_xchacha20poly1305_KEYBYTES' bytes"
  )

  const res = binding.crypto_secretstream_xchacha20poly1305_init_push(
    state.buffer,
    state.byteOffset,
    state.byteLength,

    header.buffer,
    header.byteOffset,
    header.byteLength,

    k.buffer,
    k.byteOffset,
    k.byteLength
  )

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_secretstream_xchacha20poly1305_init_pull = function (state, header, k) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(ArrayBuffer.isView(header), 'header must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    state.byteLength === binding.crypto_secretstream_xchacha20poly1305_STATEBYTES,
    "state must be 'crypto_secretstream_xchacha20poly1305_STATEBYTES' bytes"
  )
  assert(
    header.byteLength === binding.crypto_secretstream_xchacha20poly1305_HEADERBYTES,
    "header must be 'crypto_secretstream_xchacha20poly1305_HEADERBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_secretstream_xchacha20poly1305_KEYBYTES,
    "k must be 'crypto_secretstream_xchacha20poly1305_KEYBYTES' bytes"
  )

  const res = binding.crypto_secretstream_xchacha20poly1305_init_pull(
    state.buffer,
    state.byteOffset,
    state.byteLength,

    header.buffer,
    header.byteOffset,
    header.byteLength,

    k.buffer,
    k.byteOffset,
    k.byteLength
  )

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_secretstream_xchacha20poly1305_push = function (state, c, m, ad, tag) {
  if (!ad) ad = OPTIONAL

  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(
    state.byteLength === binding.crypto_secretstream_xchacha20poly1305_STATEBYTES,
    "state must be 'crypto_secretstream_xchacha20poly1305_STATEBYTES' bytes"
  )
  assert(
    c.byteLength === m.byteLength + binding.crypto_secretstream_xchacha20poly1305_ABYTES,
    "c must be 'm.byteLength + crypto_secretstream_xchacha20poly1305_ABYTES' bytes"
  )

  const res = binding.crypto_secretstream_xchacha20poly1305_push(
    state.buffer,
    state.byteOffset,
    state.byteLength,

    c.buffer,
    c.byteOffset,
    c.byteLength,

    m.buffer,
    m.byteOffset,
    m.byteLength,

    ad.buffer,
    ad.byteOffset,
    ad.byteLength,

    tag
  )

  if (res < 0) throw new Error('push failed')

  return res
}

exports.crypto_secretstream_xchacha20poly1305_pull = function (state, m, tag, c, ad) {
  if (!ad) ad = OPTIONAL

  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_secretstream_xchacha20poly1305_STATEBYTES,
    "state must be 'crypto_secretstream_xchacha20poly1305_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(tag), 'tag must be a typed array')
  assert(tag.byteLength === 1, 'tag must be 1 byte')

  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(
    c.byteLength >= binding.crypto_secretstream_xchacha20poly1305_ABYTES,
    "c must be at least 'crypto_secretstream_xchacha20poly1305_ABYTES' bytes"
  )
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(
    m.byteLength === c.byteLength - binding.crypto_secretstream_xchacha20poly1305_ABYTES,
    "m must be 'c.byteLength - crypto_secretstream_xchacha20poly1305_ABYTES' bytes"
  )

  const res = binding.crypto_secretstream_xchacha20poly1305_pull(
    state.buffer,
    state.byteOffset,
    state.byteLength,

    m.buffer,
    m.byteOffset,
    m.byteLength,

    tag.buffer,
    tag.byteOffset,
    tag.byteLength,

    c.buffer,
    c.byteOffset,
    c.byteLength,

    ad.buffer,
    ad.byteOffset,
    ad.byteLength
  )

  if (res < 0) throw new Error('pull failed')

  return res
}

exports.crypto_secretstream_xchacha20poly1305_rekey = function (state) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_secretstream_xchacha20poly1305_STATEBYTES,
    "state must be 'crypto_secretstream_xchacha20poly1305_STATEBYTES' bytes"
  )

  binding.crypto_secretstream_xchacha20poly1305_rekey(
    state.buffer,
    state.byteOffset,
    state.byteLength
  )
}

// crypto_stream

exports.crypto_stream = function (c, n, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    n.byteLength === binding.crypto_stream_NONCEBYTES,
    "n must be 'crypto_stream_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_KEYBYTES,
    "k must be 'crypto_stream_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream(c, n, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_xor = function (c, m, n, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(
    n.byteLength === binding.crypto_stream_NONCEBYTES,
    "n must be 'crypto_stream_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_KEYBYTES,
    "k must be 'crypto_stream_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_xor(
    c.buffer,
    c.byteOffset,
    c.byteLength,

    m.buffer,
    m.byteOffset,
    m.byteLength,

    n.buffer,
    n.byteOffset,
    n.byteLength,

    k.buffer,
    k.byteOffset,
    k.byteLength
  )

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_chacha20 = function (c, n, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    n.byteLength === binding.crypto_stream_chacha20_NONCEBYTES,
    "n must be 'crypto_stream_chacha20_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_chacha20_KEYBYTES,
    "k must be 'crypto_stream_chacha20_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_chacha20(c, n, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_chacha20_xor = function (c, m, n, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(
    n.byteLength === binding.crypto_stream_chacha20_NONCEBYTES,
    "n must be 'crypto_stream_chacha20_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_chacha20_KEYBYTES,
    "k must be 'crypto_stream_chacha20_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_chacha20_xor(c, m, n, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_chacha20_xor_ic = function (c, m, n, ic, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(
    n.byteLength === binding.crypto_stream_chacha20_NONCEBYTES,
    "n must be 'crypto_stream_chacha20_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_chacha20_KEYBYTES,
    "k must be 'crypto_stream_chacha20_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_chacha20_xor_ic(c, m, n, ic, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_chacha20_ietf = function (c, n, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    n.byteLength === binding.crypto_stream_chacha20_ietf_NONCEBYTES,
    "n must be 'crypto_stream_chacha20_ietf_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_chacha20_ietf_KEYBYTES,
    "k must be 'crypto_stream_chacha20_ietf_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_chacha20_ietf(c, n, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_chacha20_ietf_xor = function (c, m, n, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(
    n.byteLength === binding.crypto_stream_chacha20_ietf_NONCEBYTES,
    "n must be 'crypto_stream_chacha20_ietf_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_chacha20_ietf_KEYBYTES,
    "k must be 'crypto_stream_chacha20_ietf_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_chacha20_ietf_xor(c, m, n, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_chacha20_ietf_xor_ic = function (c, m, n, ic, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(
    n.byteLength === binding.crypto_stream_chacha20_ietf_NONCEBYTES,
    "n must be 'crypto_stream_chacha20_ietf_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_chacha20_ietf_KEYBYTES,
    "k must be 'crypto_stream_chacha20_ietf_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_chacha20_ietf_xor_ic(c, m, n, ic, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_xchacha20 = function (c, n, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    n.byteLength === binding.crypto_stream_xchacha20_NONCEBYTES,
    "n must be 'crypto_stream_xchacha20_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_xchacha20_KEYBYTES,
    "k must be 'crypto_stream_xchacha20_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_xchacha20(c, n, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_xchacha20_xor = function (c, m, n, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(
    n.byteLength === binding.crypto_stream_xchacha20_NONCEBYTES,
    "n must be 'crypto_stream_xchacha20_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_xchacha20_KEYBYTES,
    "k must be 'crypto_stream_xchacha20_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_xchacha20_xor(c, m, n, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_xchacha20_xor_ic = function (c, m, n, ic, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(
    n.byteLength === binding.crypto_stream_xchacha20_NONCEBYTES,
    "n must be 'crypto_stream_xchacha20_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_xchacha20_KEYBYTES,
    "k must be 'crypto_stream_xchacha20_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_xchacha20_xor_ic(c, m, n, ic, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_salsa20 = function (c, n, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    n.byteLength === binding.crypto_stream_salsa20_NONCEBYTES,
    "n must be 'crypto_stream_salsa20_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_salsa20_KEYBYTES,
    "k must be 'crypto_stream_salsa20_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_salsa20(c, n, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_salsa20_xor = function (c, m, n, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(
    n.byteLength === binding.crypto_stream_salsa20_NONCEBYTES,
    "n must be 'crypto_stream_salsa20_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_salsa20_KEYBYTES,
    "k must be 'crypto_stream_salsa20_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_salsa20_xor(c, m, n, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_salsa20_xor_ic = function (c, m, n, ic, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(
    n.byteLength === binding.crypto_stream_salsa20_NONCEBYTES,
    "n must be 'crypto_stream_salsa20_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_salsa20_KEYBYTES,
    "k must be 'crypto_stream_salsa20_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_salsa20_xor_ic(c, m, n, ic, k)

  if (res !== 0) throw new Error('status: ' + res)
}

// crypto_auth

exports.crypto_auth = function (out, input, k) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(out.byteLength === binding.crypto_auth_BYTES, "out must be 'crypto_auth_BYTES' bytes")
  assert(k.byteLength === binding.crypto_auth_KEYBYTES, "k must be 'crypto_auth_KEYBYTES' bytes")

  const res = binding.crypto_auth(out, input, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_auth_verify = function (h, input, k) {
  assert(ArrayBuffer.isView(h), 'h must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(h.byteLength === binding.crypto_auth_BYTES, "h must be 'crypto_auth_BYTES' bytes")
  assert(k.byteLength === binding.crypto_auth_KEYBYTES, "k must be 'crypto_auth_KEYBYTES' bytes")

  return binding.crypto_auth_verify(h, input, k)
}

// crypto_onetimeauth

exports.crypto_onetimeauth = function (out, input, k) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    out.byteLength === binding.crypto_onetimeauth_BYTES,
    "out must be 'crypto_onetimeauth_BYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_onetimeauth_KEYBYTES,
    "k must be 'crypto_onetimeauth_KEYBYTES' bytes"
  )

  const res = binding.crypto_onetimeauth(out, input, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_onetimeauth_init = function (state, k) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    state.byteLength === binding.crypto_onetimeauth_STATEBYTES,
    "state must be 'crypto_onetimeauth_STATEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_onetimeauth_KEYBYTES,
    "k must be 'crypto_onetimeauth_KEYBYTES' bytes"
  )

  const res = binding.crypto_onetimeauth_init(state, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_onetimeauth_update = function (state, input) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(
    state.byteLength === binding.crypto_onetimeauth_STATEBYTES,
    "state must be 'crypto_onetimeauth_STATEBYTES' bytes"
  )

  const res = binding.crypto_onetimeauth_update(state, input)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_onetimeauth_final = function (state, out) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(
    state.byteLength === binding.crypto_onetimeauth_STATEBYTES,
    "state must be 'crypto_onetimeauth_STATEBYTES' bytes"
  )
  assert(
    out.byteLength === binding.crypto_onetimeauth_BYTES,
    "out must be 'crypto_onetimeauth_BYTES' bytes"
  )

  const res = binding.crypto_onetimeauth_final(state, out)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_onetimeauth_verify = function (h, input, k) {
  assert(ArrayBuffer.isView(h), 'h must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    h.byteLength === binding.crypto_onetimeauth_BYTES,
    "h must be 'crypto_onetimeauth_BYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_onetimeauth_KEYBYTES,
    "k must be 'crypto_onetimeauth_KEYBYTES' bytes"
  )

  return binding.crypto_onetimeauth_verify(h, input, k)
}

// crypto_pwhash

exports.crypto_pwhash = function (out, passwd, salt, opslimit, memlimit, alg) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(ArrayBuffer.isView(salt), 'salt must be a typed array')
  assert(
    out.byteLength >= binding.crypto_pwhash_BYTES_MIN,
    "out must be at least 'crypto_pwhash_BYTES_MIN' bytes"
  )
  assert(
    out.byteLength <= binding.crypto_pwhash_BYTES_MAX,
    "out must be at most 'crypto_pwhash_BYTES_MAX' bytes"
  )
  assert(
    salt.byteLength === binding.crypto_pwhash_SALTBYTES,
    "salt must be 'crypto_pwhash_SALTBYTES' bytes"
  )
  assert(
    opslimit >= binding.crypto_pwhash_OPSLIMIT_MIN,
    "opslimit must be at least 'crypto_pwhash_OPSLIMIT_MIN'"
  )
  assert(
    opslimit <= binding.crypto_pwhash_OPSLIMIT_MAX,
    "opslimit must be at most 'crypto_pwhash_OPSLIMIT_MAX'"
  )
  assert(
    memlimit >= binding.crypto_pwhash_MEMLIMIT_MIN,
    "memlimit must be at least 'crypto_pwhash_MEMLIMIT_MIN'"
  )
  assert(
    memlimit <= binding.crypto_pwhash_MEMLIMIT_MAX,
    "memlimit must be at most 'crypto_pwhash_MEMLIMIT_MAX'"
  )
  assert(alg >= 1 && alg <= 2, 'alg must be either Argon2i 1.3 or Argon2id 1.3')

  const res = binding.crypto_pwhash(out, passwd, salt, opslimit, memlimit, alg)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_pwhash_async = function (
  out,
  passwd,
  salt,
  opslimit,
  memlimit,
  alg,
  callback = undefined
) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(ArrayBuffer.isView(salt), 'salt must be a typed array')
  assert(
    out.byteLength >= binding.crypto_pwhash_BYTES_MIN,
    "out must be at least 'crypto_pwhash_BYTES_MIN' bytes"
  )
  assert(
    out.byteLength <= binding.crypto_pwhash_BYTES_MAX,
    "out must be at most 'crypto_pwhash_BYTES_MAX' bytes"
  )
  assert(
    salt.byteLength === binding.crypto_pwhash_SALTBYTES,
    "salt must be 'crypto_pwhash_SALTBYTES' bytes"
  )
  assert(
    opslimit >= binding.crypto_pwhash_OPSLIMIT_MIN,
    "opslimit must be at least 'crypto_pwhash_OPSLIMIT_MIN'"
  )
  assert(
    opslimit <= binding.crypto_pwhash_OPSLIMIT_MAX,
    "opslimit must be at most 'crypto_pwhash_OPSLIMIT_MAX'"
  )
  assert(
    memlimit >= binding.crypto_pwhash_MEMLIMIT_MIN,
    "memlimit must be at least 'crypto_pwhash_MEMLIMIT_MIN'"
  )
  assert(
    memlimit <= binding.crypto_pwhash_MEMLIMIT_MAX,
    "memlimit must be at most 'crypto_pwhash_MEMLIMIT_MAX'"
  )
  assert(alg >= 1 && alg <= 2, 'alg must be either Argon2i 1.3 or Argon2id 1.3')

  const [done, promise] = checkStatus(callback)

  binding.crypto_pwhash_async(
    out.buffer,
    out.byteOffset,
    out.byteLength,

    passwd.buffer,
    passwd.byteOffset,
    passwd.byteLength,

    salt.buffer,
    salt.byteOffset,
    salt.byteLength,

    opslimit,
    memlimit,
    alg,

    done
  )

  return promise
}

exports.crypto_pwhash_str = function (out, passwd, opslimit, memlimit) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(
    out.byteLength === binding.crypto_pwhash_STRBYTES,
    "out must be 'crypto_pwhash_STRBYTES' bytes"
  )
  assert(typeof opslimit === 'number', 'opslimit must be a number')
  assert(
    opslimit >= binding.crypto_pwhash_OPSLIMIT_MIN,
    "opslimit must be at least 'crypto_pwhash_OPSLIMIT_MIN'"
  )
  assert(
    opslimit <= binding.crypto_pwhash_OPSLIMIT_MAX,
    "opslimit must be at most 'crypto_pwhash_OPSLIMIT_MAX'"
  )
  assert(typeof memlimit === 'number', 'memlimit must be a number')
  assert(
    memlimit >= binding.crypto_pwhash_MEMLIMIT_MIN,
    "memlimit must be at least 'crypto_pwhash_MEMLIMIT_MIN'"
  )
  assert(
    memlimit <= binding.crypto_pwhash_MEMLIMIT_MAX,
    "memlimit must be at most 'crypto_pwhash_MEMLIMIT_MAX'"
  )

  const res = binding.crypto_pwhash_str(out, passwd, opslimit, memlimit)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_pwhash_str_async = function (out, passwd, opslimit, memlimit, callback = undefined) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(
    out.byteLength === binding.crypto_pwhash_STRBYTES,
    "out must be 'crypto_pwhash_STRBYTES' bytes"
  )
  assert(passwd.byteLength > 0, 'passwd must not be empty')
  assert(typeof opslimit === 'number', 'opslimit must be a number')
  assert(
    opslimit >= binding.crypto_pwhash_OPSLIMIT_MIN,
    "opslimit must be at least 'crypto_pwhash_OPSLIMIT_MIN'"
  )
  assert(
    opslimit <= binding.crypto_pwhash_OPSLIMIT_MAX,
    "opslimit must be at most 'crypto_pwhash_OPSLIMIT_MAX'"
  )
  assert(typeof memlimit === 'number', 'memlimit must be a number')
  assert(
    memlimit >= binding.crypto_pwhash_MEMLIMIT_MIN,
    "memlimit must be at least 'crypto_pwhash_MEMLIMIT_MIN'"
  )
  assert(
    memlimit <= binding.crypto_pwhash_MEMLIMIT_MAX,
    "memlimit must be at most 'crypto_pwhash_MEMLIMIT_MAX'"
  )

  const [done, promise] = checkStatus(callback)

  binding.crypto_pwhash_str_async(
    out.buffer,
    out.byteOffset,
    out.byteLength,

    passwd.buffer,
    passwd.byteOffset,
    passwd.byteLength,

    opslimit,
    memlimit,

    done
  )

  return promise
}

exports.crypto_pwhash_str_verify = function (str, passwd) {
  assert(ArrayBuffer.isView(str), 'str must be a typed array')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(
    str.byteLength === binding.crypto_pwhash_STRBYTES,
    "str must be 'crypto_pwhash_STRBYTES' bytes"
  )

  return binding.crypto_pwhash_str_verify(str, passwd)
}

exports.crypto_pwhash_str_verify_async = function (str, passwd, callback = undefined) {
  assert(ArrayBuffer.isView(str), 'str must be a typed array')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(
    str.byteLength === binding.crypto_pwhash_STRBYTES,
    "str must be 'crypto_pwhash_STRBYTES' bytes"
  )
  assert(passwd.byteLength > 0, 'passwd must not be empty')

  const [done, promise] = checkStatus(callback, true)

  binding.crypto_pwhash_str_verify_async(
    str.buffer,
    str.byteOffset,
    str.byteLength,

    passwd.buffer,
    passwd.byteOffset,
    passwd.byteLength,

    done
  )

  return promise
}

exports.crypto_pwhash_str_needs_rehash = function (str, opslimit, memlimit) {
  assert(ArrayBuffer.isView(str), 'str must be a typed array')
  assert(
    str.byteLength === binding.crypto_pwhash_STRBYTES,
    "str must be 'crypto_pwhash_STRBYTES' bytes"
  )
  assert(
    opslimit >= binding.crypto_pwhash_OPSLIMIT_MIN,
    "opslimit must be at least 'crypto_pwhash_OPSLIMIT_MIN'"
  )
  assert(
    opslimit <= binding.crypto_pwhash_OPSLIMIT_MAX,
    "opslimit must be at most 'crypto_pwhash_OPSLIMIT_MAX'"
  )
  assert(
    memlimit >= binding.crypto_pwhash_MEMLIMIT_MIN,
    "memlimit must be at least 'crypto_pwhash_MEMLIMIT_MIN'"
  )
  assert(
    memlimit <= binding.crypto_pwhash_MEMLIMIT_MAX,
    "memlimit must be at most 'crypto_pwhash_MEMLIMIT_MAX'"
  )

  return binding.crypto_pwhash_str_needs_rehash(str, opslimit, memlimit)
}

exports.crypto_pwhash_scryptsalsa208sha256 = function (out, passwd, salt, opslimit, memlimit) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(ArrayBuffer.isView(salt), 'salt must be a typed array')
  assert(
    out.byteLength >= binding.crypto_pwhash_scryptsalsa208sha256_BYTES_MIN,
    "out must be at least 'crypto_pwhash_scryptsalsa208sha256_BYTES_MIN' bytes"
  )
  assert(
    out.byteLength <= binding.crypto_pwhash_scryptsalsa208sha256_BYTES_MAX,
    "out must be at most 'crypto_pwhash_scryptsalsa208sha256_BYTES_MAX' bytes"
  )
  assert(
    salt.byteLength === binding.crypto_pwhash_scryptsalsa208sha256_SALTBYTES,
    "salt must be 'crypto_pwhash_scryptsalsa208sha256_SALTBYTES' bytes"
  )
  assert(
    opslimit >= binding.crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MIN,
    "opslimit must be at least 'crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MIN'"
  )
  assert(
    opslimit <= binding.crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MAX,
    "opslimit must be at most 'crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MAX'"
  )
  assert(
    memlimit >= binding.crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MIN,
    "memlimit must be at least 'crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MIN'"
  )
  assert(
    memlimit <= binding.crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MAX,
    "memlimit must be at most 'crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MAX'"
  )

  const res = binding.crypto_pwhash_scryptsalsa208sha256(out, passwd, salt, opslimit, memlimit)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_pwhash_scryptsalsa208sha256_async = function (
  out,
  passwd,
  salt,
  opslimit,
  memlimit,
  callback = undefined
) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(ArrayBuffer.isView(salt), 'salt must be a typed array')
  assert(
    out.byteLength >= binding.crypto_pwhash_scryptsalsa208sha256_BYTES_MIN,
    "out must be at least 'crypto_pwhash_scryptsalsa208sha256_BYTES_MIN' bytes"
  )
  assert(
    out.byteLength <= binding.crypto_pwhash_scryptsalsa208sha256_BYTES_MAX,
    "out must be at most 'crypto_pwhash_scryptsalsa208sha256_BYTES_MAX' bytes"
  )
  assert(passwd.byteLength > 0, 'passwd must not be empty')
  assert(
    salt.byteLength === binding.crypto_pwhash_scryptsalsa208sha256_SALTBYTES,
    "salt must be 'crypto_pwhash_scryptsalsa208sha256_SALTBYTES' bytes"
  )
  assert(
    opslimit >= binding.crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MIN,
    "opslimit must be at least 'crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MIN'"
  )
  assert(
    opslimit <= binding.crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MAX,
    "opslimit must be at most 'crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MAX'"
  )
  assert(
    memlimit >= binding.crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MIN,
    "memlimit must be at least 'crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MIN'"
  )
  assert(
    memlimit <= binding.crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MAX,
    "memlimit must be at most 'crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MAX'"
  )

  const [done, promise] = checkStatus(callback)

  binding.crypto_pwhash_scryptsalsa208sha256_async(
    out.buffer,
    out.byteOffset,
    out.byteLength,

    passwd.buffer,
    passwd.byteOffset,
    passwd.byteLength,

    salt.buffer,
    salt.byteOffset,
    salt.byteLength,

    opslimit,
    memlimit,

    done
  )

  return promise
}

exports.crypto_pwhash_scryptsalsa208sha256_str_async = function (
  out,
  passwd,
  opslimit,
  memlimit,
  callback = undefined
) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(
    out.byteLength === binding.crypto_pwhash_scryptsalsa208sha256_STRBYTES,
    "out must be 'crypto_pwhash_scryptsalsa208sha256_STRBYTES' bytes"
  )
  assert(passwd.byteLength > 0, 'passwd must not be empty')
  assert(
    opslimit >= binding.crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MIN,
    "opslimit must be at least 'crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MIN'"
  )
  assert(
    opslimit <= binding.crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MAX,
    "opslimit must be at most 'crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MAX'"
  )
  assert(
    memlimit >= binding.crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MIN,
    "memlimit must be at least 'crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MIN'"
  )
  assert(
    memlimit <= binding.crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MAX,
    "memlimit must be at most 'crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MAX'"
  )

  const [done, promise] = checkStatus(callback)

  binding.crypto_pwhash_scryptsalsa208sha256_str_async(
    out.buffer,
    out.byteOffset,
    out.byteLength,

    passwd.buffer,
    passwd.byteOffset,
    passwd.byteLength,

    opslimit,
    memlimit,

    done
  )

  return promise
}

exports.crypto_pwhash_scryptsalsa208sha256_str = function (out, passwd, opslimit, memlimit) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(
    out.byteLength === binding.crypto_pwhash_scryptsalsa208sha256_STRBYTES,
    "out must be 'crypto_pwhash_scryptsalsa208sha256_STRBYTES' bytes"
  )
  assert(passwd.byteLength > 0, 'passwd must not be empty')
  assert(
    opslimit >= binding.crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MIN,
    "opslimit must be at least 'crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MIN'"
  )
  assert(
    opslimit <= binding.crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MAX,
    "opslimit must be at most 'crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MAX'"
  )
  assert(
    memlimit >= binding.crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MIN,
    "memlimit must be at least 'crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MIN'"
  )
  assert(
    memlimit <= binding.crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MAX,
    "memlimit must be at most 'crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MAX'"
  )

  const res = binding.crypto_pwhash_scryptsalsa208sha256_str(out, passwd, opslimit, memlimit)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_pwhash_scryptsalsa208sha256_str_verify_async = function (
  str,
  passwd,
  callback = undefined
) {
  assert(ArrayBuffer.isView(str), 'str must be a typed array')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(
    str.byteLength === binding.crypto_pwhash_scryptsalsa208sha256_STRBYTES,
    "str must be 'crypto_pwhash_scryptsalsa208sha256_STRBYTES' bytes"
  )
  assert(passwd.byteLength > 0, 'passwd must not be empty')

  const [done, promise] = checkStatus(callback, true)

  binding.crypto_pwhash_scryptsalsa208sha256_str_verify_async(
    str.buffer,
    str.byteOffset,
    str.byteLength,

    passwd.buffer,
    passwd.byteOffset,
    passwd.byteLength,

    done
  )

  return promise
}

exports.crypto_pwhash_scryptsalsa208sha256_str_verify = function (str, passwd) {
  assert(ArrayBuffer.isView(str), 'str must be a typed array')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(
    str.byteLength === binding.crypto_pwhash_scryptsalsa208sha256_STRBYTES,
    "str must be 'crypto_pwhash_scryptsalsa208sha256_STRBYTES' bytes"
  )
  assert(passwd.byteLength > 0, 'passwd must not be empty')

  return binding.crypto_pwhash_scryptsalsa208sha256_str_verify(str, passwd)
}

exports.crypto_pwhash_scryptsalsa208sha256_str_needs_rehash = function (str, opslimit, memlimit) {
  assert(ArrayBuffer.isView(str), 'str must be a typed array')
  assert(
    str.byteLength === binding.crypto_pwhash_scryptsalsa208sha256_STRBYTES,
    "str must be 'crypto_pwhash_scryptsalsa208sha256_STRBYTES' bytes"
  )
  assert(
    opslimit >= binding.crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MIN,
    "opslimit must be at least 'crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MIN'"
  )
  assert(
    opslimit <= binding.crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MAX,
    "opslimit must be at most 'crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MAX'"
  )
  assert(
    memlimit >= binding.crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MIN,
    "memlimit must be at least 'crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MIN'"
  )
  assert(
    memlimit <= binding.crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MAX,
    "memlimit must be at most 'crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MAX'"
  )

  return binding.crypto_pwhash_scryptsalsa208sha256_str_needs_rehash(str, opslimit, memlimit)
}

// crypto_kx

exports.crypto_kx_keypair = function (pk, sk) {
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(
    pk.byteLength === binding.crypto_kx_PUBLICKEYBYTES,
    "pk must be 'crypto_kx_PUBLICKEYBYTES' bytes"
  )
  assert(
    sk.byteLength === binding.crypto_kx_SECRETKEYBYTES,
    "sk must be 'crypto_kx_SECRETKEYBYTES' bytes"
  )

  const res = binding.crypto_kx_keypair(pk, sk)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_kx_seed_keypair = function (pk, sk, seed) {
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(ArrayBuffer.isView(seed), 'seed must be a typed array')
  assert(
    pk.byteLength === binding.crypto_kx_PUBLICKEYBYTES,
    "pk must be 'crypto_kx_PUBLICKEYBYTES' bytes"
  )
  assert(
    sk.byteLength === binding.crypto_kx_SECRETKEYBYTES,
    "sk must be 'crypto_kx_SECRETKEYBYTES' bytes"
  )
  assert(
    seed.byteLength === binding.crypto_kx_SEEDBYTES,
    "seed must be 'crypto_kx_SEEDBYTES' bytes"
  )

  const res = binding.crypto_kx_seed_keypair(pk, sk, seed)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_kx_client_session_keys = function (rx, tx, clientPk, clientSk, serverPk) {
  if (!rx) rx = undefined
  if (!tx) tx = undefined

  assert(rx || tx, 'at least one session key must be specified')

  if (rx) {
    assert(ArrayBuffer.isView(rx), 'rx must be a typed array')
    assert(
      rx.byteLength === binding.crypto_kx_SESSIONKEYBYTES,
      "rx must be 'crypto_kx_SESSIONKEYBYTES' bytes"
    )
  }

  if (tx) {
    assert(ArrayBuffer.isView(tx), 'tx must be a typed array')
    assert(
      tx.byteLength === binding.crypto_kx_SESSIONKEYBYTES,
      "tx must be 'crypto_kx_SESSIONKEYBYTES' bytes"
    )
  }

  assert(ArrayBuffer.isView(clientPk), 'clientPk must be a typed array')
  assert(ArrayBuffer.isView(clientSk), 'clientSk must be a typed array')
  assert(ArrayBuffer.isView(serverPk), 'serverPk must be a typed array')
  assert(
    clientPk.byteLength === binding.crypto_kx_PUBLICKEYBYTES,
    "clientPk must be 'crypto_kx_PUBLICKEYBYTES' bytes"
  )
  assert(
    clientSk.byteLength === binding.crypto_kx_SECRETKEYBYTES,
    "clientSk must be 'crypto_kx_SECRETKEYBYTES' bytes"
  )
  assert(
    serverPk.byteLength === binding.crypto_kx_PUBLICKEYBYTES,
    "serverPk must be 'crypto_kx_PUBLICKEYBYTES' bytes"
  )

  const res = binding.crypto_kx_client_session_keys(rx, tx, clientPk, clientSk, serverPk)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_kx_server_session_keys = function (rx, tx, serverPk, serverSk, clientPk) {
  if (!rx) rx = undefined
  if (!tx) tx = undefined

  assert(rx || tx, 'at least one session key must be specified')

  if (rx) {
    assert(ArrayBuffer.isView(rx), 'rx must be a typed array')
    assert(
      rx.byteLength === binding.crypto_kx_SESSIONKEYBYTES,
      "rx must be 'crypto_kx_SESSIONKEYBYTES' bytes"
    )
  }

  if (tx) {
    assert(ArrayBuffer.isView(tx), 'tx must be a typed array')
    assert(
      tx.byteLength === binding.crypto_kx_SESSIONKEYBYTES,
      "tx must be 'crypto_kx_SESSIONKEYBYTES' bytes"
    )
  }

  assert(ArrayBuffer.isView(serverPk), 'serverPk must be a typed array')
  assert(ArrayBuffer.isView(serverSk), 'serverSk must be a typed array')
  assert(ArrayBuffer.isView(clientPk), 'clientPk must be a typed array')
  assert(
    serverPk.byteLength === binding.crypto_kx_PUBLICKEYBYTES,
    "serverPk must be 'crypto_kx_PUBLICKEYBYTES' bytes"
  )
  assert(
    serverSk.byteLength === binding.crypto_kx_SECRETKEYBYTES,
    "serverSk must be 'crypto_kx_SECRETKEYBYTES' bytes"
  )
  assert(
    clientPk.byteLength === binding.crypto_kx_PUBLICKEYBYTES,
    "clientPk must be 'crypto_kx_PUBLICKEYBYTES' bytes"
  )

  const res = binding.crypto_kx_server_session_keys(rx, tx, serverPk, serverSk, clientPk)

  if (res !== 0) throw new Error('status: ' + res)
}

// crypto_scalarmult

exports.crypto_scalarmult_base = function (q, n) {
  assert(ArrayBuffer.isView(q), 'q must be a typed array')
  assert(
    q.byteLength === binding.crypto_scalarmult_BYTES,
    "q must be 'crypto_scalarmult_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.crypto_scalarmult_SCALARBYTES,
    "n must be 'crypto_scalarmult_SCALARBYTES' bytes"
  )

  const res = binding.crypto_scalarmult_base(q, n)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_scalarmult = function (q, n, p) {
  assert(ArrayBuffer.isView(q), 'q must be a typed array')
  assert(
    q.byteLength === binding.crypto_scalarmult_BYTES,
    "q must be 'crypto_scalarmult_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.crypto_scalarmult_SCALARBYTES,
    "n must be 'crypto_scalarmult_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(p), 'p must be a typed array')
  assert(
    p.byteLength === binding.crypto_scalarmult_BYTES,
    "p must be 'crypto_scalarmult_BYTES' bytes"
  )

  const res = binding.crypto_scalarmult(q, n, p)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_scalarmult_ed25519_base = function (q, n) {
  assert(ArrayBuffer.isView(q), 'q must be a typed array')
  assert(
    q.byteLength === binding.crypto_scalarmult_ed25519_BYTES,
    "q must be 'crypto_scalarmult_ed25519_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.crypto_scalarmult_ed25519_SCALARBYTES,
    "n must be 'crypto_scalarmult_ed25519_SCALARBYTES' bytes"
  )

  const res = binding.crypto_scalarmult_ed25519_base(q, n)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_scalarmult_ed25519 = function (q, n, p) {
  assert(ArrayBuffer.isView(q), 'q must be a typed array')
  assert(
    q.byteLength === binding.crypto_scalarmult_ed25519_BYTES,
    "q must be 'crypto_scalarmult_ed25519_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.crypto_scalarmult_ed25519_SCALARBYTES,
    "n must be 'crypto_scalarmult_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(p), 'p must be a typed array')
  assert(
    p.byteLength === binding.crypto_scalarmult_ed25519_BYTES,
    "p must be 'crypto_scalarmult_ed25519_BYTES' bytes"
  )

  const res = binding.crypto_scalarmult_ed25519(q, n, p)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_core_ed25519_is_valid_point = function (p) {
  assert(ArrayBuffer.isView(p), 'p must be a typed array')
  assert(
    p.byteLength === binding.crypto_core_ed25519_BYTES,
    "p must be 'crypto_core_ed25519_BYTES' bytes"
  )

  return binding.crypto_core_ed25519_is_valid_point(p)
}

exports.crypto_core_ed25519_from_uniform = function (p, r) {
  assert(ArrayBuffer.isView(p), 'p must be a typed array')
  assert(
    p.byteLength === binding.crypto_core_ed25519_BYTES,
    "p must be 'crypto_core_ed25519_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(r), 'r must be a typed array')
  assert(
    r.byteLength === binding.crypto_core_ed25519_UNIFORMBYTES,
    "r must be 'crypto_core_ed25519_UNIFORMBYTES' bytes"
  )

  const res = binding.crypto_core_ed25519_from_uniform(p, r)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_scalarmult_ed25519_base_noclamp = function (q, n) {
  assert(ArrayBuffer.isView(q), 'q must be a typed array')
  assert(
    q.byteLength === binding.crypto_scalarmult_ed25519_BYTES,
    "q must be 'crypto_scalarmult_ed25519_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.crypto_scalarmult_ed25519_SCALARBYTES,
    "n must be 'crypto_scalarmult_ed25519_SCALARBYTES' bytes"
  )

  const res = binding.crypto_scalarmult_ed25519_base_noclamp(q, n)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_scalarmult_ed25519_noclamp = function (q, n, p) {
  assert(ArrayBuffer.isView(q), 'q must be a typed array')
  assert(
    q.byteLength === binding.crypto_scalarmult_ed25519_BYTES,
    "q must be 'crypto_scalarmult_ed25519_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.crypto_scalarmult_ed25519_SCALARBYTES,
    "n must be 'crypto_scalarmult_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(p), 'p must be a typed array')
  assert(
    p.byteLength === binding.crypto_scalarmult_ed25519_BYTES,
    "p must be 'crypto_scalarmult_ed25519_BYTES' bytes"
  )

  const res = binding.crypto_scalarmult_ed25519_noclamp(q, n, p)

  if (res !== 0) throw new Error('status: ' + res)
}

// crypto_core

exports.crypto_core_ed25519_add = function (r, p, q) {
  assert(ArrayBuffer.isView(r), 'r must be a typed array')
  assert(
    r.byteLength === binding.crypto_core_ed25519_BYTES,
    "r must be 'crypto_core_ed25519_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(p), 'p must be a typed array')
  assert(
    p.byteLength === binding.crypto_core_ed25519_BYTES,
    "p must be 'crypto_core_ed25519_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(q), 'q must be a typed array')
  assert(
    q.byteLength === binding.crypto_core_ed25519_BYTES,
    "q must be 'crypto_core_ed25519_BYTES' bytes"
  )

  const res = binding.crypto_core_ed25519_add(r, p, q)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_core_ed25519_sub = function (r, p, q) {
  assert(ArrayBuffer.isView(r), 'r must be a typed array')
  assert(
    r.byteLength === binding.crypto_core_ed25519_BYTES,
    "r must be 'crypto_core_ed25519_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(p), 'p must be a typed array')
  assert(
    p.byteLength === binding.crypto_core_ed25519_BYTES,
    "p must be 'crypto_core_ed25519_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(q), 'q must be a typed array')
  assert(
    q.byteLength === binding.crypto_core_ed25519_BYTES,
    "q must be 'crypto_core_ed25519_BYTES' bytes"
  )

  const res = binding.crypto_core_ed25519_sub(r, p, q)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_core_ed25519_scalar_random = function (r) {
  assert(ArrayBuffer.isView(r), 'r must be a typed array')
  assert(
    r.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "r must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )

  binding.crypto_core_ed25519_scalar_random(r)
}

exports.crypto_core_ed25519_scalar_reduce = function (r, s) {
  assert(ArrayBuffer.isView(r), 'r must be a typed array')
  assert(
    r.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "r must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(s), 's must be a typed array')
  assert(
    s.byteLength === binding.crypto_core_ed25519_NONREDUCEDSCALARBYTES,
    "s must be 'crypto_core_ed25519_NONREDUCEDSCALARBYTES' bytes"
  )

  binding.crypto_core_ed25519_scalar_reduce(r, s)
}

exports.crypto_core_ed25519_scalar_invert = function (recip, s) {
  assert(ArrayBuffer.isView(recip), 'recip must be a typed array')
  assert(
    recip.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "recip must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(s), 's must be a typed array')
  assert(
    s.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "s must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )

  binding.crypto_core_ed25519_scalar_invert(recip, s)
}

exports.crypto_core_ed25519_scalar_negate = function (neg, s) {
  assert(ArrayBuffer.isView(neg), 'neg must be a typed array')
  assert(
    neg.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "neg must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(s), 's must be a typed array')
  assert(
    s.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "s must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )

  binding.crypto_core_ed25519_scalar_negate(neg, s)
}

exports.crypto_core_ed25519_scalar_complement = function (comp, s) {
  assert(ArrayBuffer.isView(comp), 'comp must be a typed array')
  assert(
    comp.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "comp must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(s), 's must be a typed array')
  assert(
    s.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "s must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )

  binding.crypto_core_ed25519_scalar_complement(comp, s)
}

exports.crypto_core_ed25519_scalar_add = function (z, x, y) {
  assert(ArrayBuffer.isView(z), 'z must be a typed array')
  assert(
    z.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "z must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(x), 'x must be a typed array')
  assert(
    x.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "x must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(y), 'y must be a typed array')
  assert(
    y.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "y must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )

  binding.crypto_core_ed25519_scalar_add(z, x, y)
}

exports.crypto_core_ed25519_scalar_sub = function (z, x, y) {
  assert(ArrayBuffer.isView(z), 'z must be a typed array')
  assert(
    z.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "z must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(x), 'x must be a typed array')
  assert(
    x.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "x must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(y), 'y must be a typed array')
  assert(
    y.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "y must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )

  binding.crypto_core_ed25519_scalar_sub(z, x, y)
}

// crypto_shorthash

exports.crypto_shorthash = function (out, input, k) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(
    out.byteLength === binding.crypto_shorthash_BYTES,
    "out must be 'crypto_shorthash_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_shorthash_KEYBYTES,
    "k must be 'crypto_shorthash_KEYBYTES' bytes"
  )

  const res = binding.crypto_shorthash(out, input, k)

  if (res !== 0) throw new Error('status: ' + res)
}

// crypto_kdf

exports.crypto_kdf_keygen = function (key) {
  assert(ArrayBuffer.isView(key), 'key must be a typed array')
  assert(key.byteLength === binding.crypto_kdf_KEYBYTES, "key must be 'crypto_kdf_KEYBYTES' bytes")

  binding.crypto_kdf_keygen(key)
}

exports.crypto_kdf_derive_from_key = function (subkey, subkeyId, ctx, key) {
  assert(ArrayBuffer.isView(subkey), 'subkey must be a typed array')
  assert(
    subkey.byteLength >= binding.crypto_kdf_BYTES_MIN,
    "subkey must be at least 'crypto_kdf_BYTES_MIN' bytes"
  )
  assert(
    subkey.byteLength <= binding.crypto_kdf_BYTES_MAX,
    "subkey must be at most 'crypto_kdf_BYTES_MAX' bytes"
  )
  assert(ArrayBuffer.isView(ctx), 'ctx must be a typed array')
  assert(
    ctx.byteLength === binding.crypto_kdf_CONTEXTBYTES,
    "ctx must be 'crypto_kdf_CONTEXTBYTES' bytes"
  )
  assert(ArrayBuffer.isView(key), 'key must be a typed array')
  assert(key.byteLength === binding.crypto_kdf_KEYBYTES, "key must be 'crypto_kdf_KEYBYTES' bytes")

  const res = binding.crypto_kdf_derive_from_key(subkey, subkeyId, ctx, key)

  if (res !== 0) throw new Error('status: ' + res)
}

// crypto_hash

exports.crypto_hash = function (out, input) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(out.byteLength === binding.crypto_hash_BYTES, "out must be 'crypto_hash_BYTES' bytes")

  const res = binding.crypto_hash(out, input)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_hash_sha256 = function (out, input) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(
    out.byteLength === binding.crypto_hash_sha256_BYTES,
    "out must be 'crypto_hash_sha256_BYTES' bytes"
  )

  const res = binding.crypto_hash_sha256(out, input)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_hash_sha256_init = function (state) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_hash_sha256_STATEBYTES,
    "state must be 'crypto_hash_sha256_STATEBYTES' bytes"
  )

  const res = binding.crypto_hash_sha256_init(state)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_hash_sha256_update = function (state, input) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(
    state.byteLength === binding.crypto_hash_sha256_STATEBYTES,
    "state must be 'crypto_hash_sha256_STATEBYTES' bytes"
  )

  const res = binding.crypto_hash_sha256_update(state, input)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_hash_sha256_final = function (state, out) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_hash_sha256_STATEBYTES,
    "state must be 'crypto_hash_sha256_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(
    out.byteLength === binding.crypto_hash_sha256_BYTES,
    "out must be 'crypto_hash_sha256_BYTES' bytes"
  )

  const res = binding.crypto_hash_sha256_final(state, out)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_hash_sha512 = function (out, input) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(
    out.byteLength === binding.crypto_hash_sha512_BYTES,
    "out must be 'crypto_hash_sha512_BYTES' bytes"
  )

  const res = binding.crypto_hash_sha512(out, input)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_hash_sha512_init = function (state) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_hash_sha512_STATEBYTES,
    "state must be 'crypto_hash_sha512_STATEBYTES' bytes"
  )

  const res = binding.crypto_hash_sha512_init(state)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_hash_sha512_update = function (state, input) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(
    state.byteLength === binding.crypto_hash_sha512_STATEBYTES,
    "state must be 'crypto_hash_sha512_STATEBYTES' bytes"
  )

  const res = binding.crypto_hash_sha512_update(state, input)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_hash_sha512_final = function (state, out) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_hash_sha512_STATEBYTES,
    "state must be 'crypto_hash_sha512_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(
    out.byteLength === binding.crypto_hash_sha512_BYTES,
    "out must be 'crypto_hash_sha512_BYTES' bytes"
  )

  const res = binding.crypto_hash_sha512_final(state, out)

  if (res !== 0) throw new Error('status: ' + res)
}

// crypto_aead

exports.crypto_aead_xchacha20poly1305_ietf_keygen = function (k) {
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_aead_xchacha20poly1305_ietf_KEYBYTES,
    "k must be 'crypto_aead_xchacha20poly1305_ietf_KEYBYTES' bytes"
  )

  binding.crypto_aead_xchacha20poly1305_ietf_keygen(k)
}

exports.crypto_aead_xchacha20poly1305_ietf_encrypt = function (c, m, ad, nsec, npub, k) {
  if (!ad) ad = undefined

  assert(nsec === null, 'nsec must always be set to null')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(
    c.byteLength === m.byteLength + binding.crypto_aead_xchacha20poly1305_ietf_ABYTES,
    "c must be 'm.byteLength + crypto_aead_xchacha20poly1305_ietf_ABYTES' bytes"
  )
  assert(c.byteLength <= 0xffffffff, 'c.byteLength must be a 32bit integer')
  assert(ArrayBuffer.isView(npub), 'npub must be a typed array')
  assert(
    npub.byteLength === binding.crypto_aead_xchacha20poly1305_ietf_NPUBBYTES,
    "npub must be 'crypto_aead_xchacha20poly1305_ietf_NPUBBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_aead_xchacha20poly1305_ietf_KEYBYTES,
    "k must be 'crypto_aead_xchacha20poly1305_ietf_KEYBYTES' bytes"
  )

  const res = binding.crypto_aead_xchacha20poly1305_ietf_encrypt(c, m, ad, npub, k)

  if (res < 0) throw new Error('could not encrypt data')

  return res
}

exports.crypto_aead_xchacha20poly1305_ietf_decrypt = function (m, nsec, c, ad, npub, k) {
  if (!ad) ad = undefined

  assert(nsec === null, 'nsec must always be set to null')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(
    m.byteLength === c.byteLength - binding.crypto_aead_xchacha20poly1305_ietf_ABYTES,
    "m must be 'c.byteLength - crypto_aead_xchacha20poly1305_ietf_ABYTES' bytes"
  )
  assert(m.byteLength <= 0xffffffff, 'm.byteLength must be a 32bit integer')
  assert(ArrayBuffer.isView(npub), 'npub must be a typed array')
  assert(
    npub.byteLength === binding.crypto_aead_xchacha20poly1305_ietf_NPUBBYTES,
    "npub must be 'crypto_aead_xchacha20poly1305_ietf_NPUBBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_aead_xchacha20poly1305_ietf_KEYBYTES,
    "k must be 'crypto_aead_xchacha20poly1305_ietf_KEYBYTES' bytes"
  )

  const res = binding.crypto_aead_xchacha20poly1305_ietf_decrypt(m, c, ad, npub, k)

  if (res < 0) throw new Error('could not verify data')

  return res
}

exports.crypto_aead_xchacha20poly1305_ietf_encrypt_detached = function (
  c,
  mac,
  m,
  ad,
  nsec,
  npub,
  k
) {
  if (!ad) ad = undefined

  assert(nsec === null, 'nsec must always be set to null')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(ArrayBuffer.isView(mac), 'mac must be a typed array')
  assert(
    mac.byteLength === binding.crypto_aead_xchacha20poly1305_ietf_ABYTES,
    "mac must be 'crypto_aead_xchacha20poly1305_ietf_ABYTES' bytes"
  )
  assert(ArrayBuffer.isView(npub), 'npub must be a typed array')
  assert(
    npub.byteLength === binding.crypto_aead_xchacha20poly1305_ietf_NPUBBYTES,
    "npub must be 'crypto_aead_xchacha20poly1305_ietf_NPUBBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_aead_xchacha20poly1305_ietf_KEYBYTES,
    "k must be 'crypto_aead_xchacha20poly1305_ietf_KEYBYTES' bytes"
  )

  const res = binding.crypto_aead_xchacha20poly1305_ietf_encrypt_detached(c, mac, m, ad, npub, k)

  if (res < 0) throw new Error('could not encrypt data')

  return res
}

exports.crypto_aead_xchacha20poly1305_ietf_decrypt_detached = function (
  m,
  nsec,
  c,
  mac,
  ad,
  npub,
  k
) {
  if (!ad) ad = undefined

  assert(nsec === null, 'nsec must always be set to null')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(m.byteLength === c.byteLength, "m must be 'c.byteLength' bytes")
  assert(ArrayBuffer.isView(mac), 'mac must be a typed array')
  assert(
    mac.byteLength === binding.crypto_aead_xchacha20poly1305_ietf_ABYTES,
    "mac must be 'crypto_aead_xchacha20poly1305_ietf_ABYTES' bytes"
  )
  assert(ArrayBuffer.isView(npub), 'npub must be a typed array')
  assert(
    npub.byteLength === binding.crypto_aead_xchacha20poly1305_ietf_NPUBBYTES,
    "npub must be 'crypto_aead_xchacha20poly1305_ietf_NPUBBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_aead_xchacha20poly1305_ietf_KEYBYTES,
    "k must be 'crypto_aead_xchacha20poly1305_ietf_KEYBYTES' bytes"
  )

  const res = binding.crypto_aead_xchacha20poly1305_ietf_decrypt_detached(m, c, mac, ad, npub, k)

  if (res !== 0) throw new Error('could not verify data')
}

exports.crypto_aead_chacha20poly1305_ietf_keygen = function (k) {
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_aead_chacha20poly1305_ietf_KEYBYTES,
    "k must be 'crypto_aead_chacha20poly1305_ietf_KEYBYTES' bytes"
  )

  binding.crypto_aead_chacha20poly1305_ietf_keygen(k)
}

exports.crypto_aead_chacha20poly1305_ietf_encrypt = function (c, m, ad, nsec, npub, k) {
  if (!ad) ad = undefined

  assert(nsec === null, 'nsec must always be set to null')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(
    c.byteLength === m.byteLength + binding.crypto_aead_chacha20poly1305_ietf_ABYTES,
    "c must be 'm.byteLength + crypto_aead_chacha20poly1305_ietf_ABYTES' bytes"
  )
  assert(c.byteLength <= 0xffffffff, 'c.byteLength must be a 32bit integer')
  assert(ArrayBuffer.isView(npub), 'npub must be a typed array')
  assert(
    npub.byteLength === binding.crypto_aead_chacha20poly1305_ietf_NPUBBYTES,
    "npub must be 'crypto_aead_chacha20poly1305_ietf_NPUBBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_aead_chacha20poly1305_ietf_KEYBYTES,
    "k must be 'crypto_aead_chacha20poly1305_ietf_KEYBYTES' bytes"
  )

  const res = binding.crypto_aead_chacha20poly1305_ietf_encrypt(c, m, ad, npub, k)

  if (res < 0) throw new Error('could not encrypt data')

  return res
}

exports.crypto_aead_chacha20poly1305_ietf_decrypt = function (m, nsec, c, ad, npub, k) {
  if (!ad) ad = undefined

  assert(nsec === null, 'nsec must always be set to null')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(
    m.byteLength === c.byteLength - binding.crypto_aead_chacha20poly1305_ietf_ABYTES,
    "m must be 'c.byteLength - crypto_aead_chacha20poly1305_ietf_ABYTES' bytes"
  )
  assert(m.byteLength <= 0xffffffff, 'm.byteLength must be a 32bit integer')
  assert(ArrayBuffer.isView(npub), 'npub must be a typed array')
  assert(
    npub.byteLength === binding.crypto_aead_chacha20poly1305_ietf_NPUBBYTES,
    "npub must be 'crypto_aead_chacha20poly1305_ietf_NPUBBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_aead_chacha20poly1305_ietf_KEYBYTES,
    "k must be 'crypto_aead_chacha20poly1305_ietf_KEYBYTES' bytes"
  )

  const res = binding.crypto_aead_chacha20poly1305_ietf_decrypt(m, c, ad, npub, k)

  if (res < 0) throw new Error('could not verify data')

  return res
}

exports.crypto_aead_chacha20poly1305_ietf_encrypt_detached = function (
  c,
  mac,
  m,
  ad,
  nsec,
  npub,
  k
) {
  if (!ad) ad = undefined

  assert(nsec === null, 'nsec must always be set to null')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(ArrayBuffer.isView(mac), 'mac must be a typed array')
  assert(
    mac.byteLength === binding.crypto_aead_chacha20poly1305_ietf_ABYTES,
    "mac must be 'crypto_aead_chacha20poly1305_ietf_ABYTES' bytes"
  )
  assert(ArrayBuffer.isView(npub), 'npub must be a typed array')
  assert(
    npub.byteLength === binding.crypto_aead_chacha20poly1305_ietf_NPUBBYTES,
    "npub must be 'crypto_aead_chacha20poly1305_ietf_NPUBBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_aead_chacha20poly1305_ietf_KEYBYTES,
    "k must be 'crypto_aead_chacha20poly1305_ietf_KEYBYTES' bytes"
  )

  const res = binding.crypto_aead_chacha20poly1305_ietf_encrypt_detached(c, mac, m, ad, npub, k)

  if (res < 0) throw new Error('could not encrypt data')

  return res
}

exports.crypto_aead_chacha20poly1305_ietf_decrypt_detached = function (
  m,
  nsec,
  c,
  mac,
  ad,
  npub,
  k
) {
  if (!ad) ad = undefined

  assert(nsec === null, 'nsec must always be set to null')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(m.byteLength === c.byteLength, "m must be 'c.byteLength' bytes")
  assert(ArrayBuffer.isView(mac), 'mac must be a typed array')
  assert(
    mac.byteLength === binding.crypto_aead_chacha20poly1305_ietf_ABYTES,
    "mac must be 'crypto_aead_chacha20poly1305_ietf_ABYTES' bytes"
  )
  assert(ArrayBuffer.isView(npub), 'npub must be a typed array')
  assert(
    npub.byteLength === binding.crypto_aead_chacha20poly1305_ietf_NPUBBYTES,
    "npub must be 'crypto_aead_chacha20poly1305_ietf_NPUBBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_aead_chacha20poly1305_ietf_KEYBYTES,
    "k must be 'crypto_aead_chacha20poly1305_ietf_KEYBYTES' bytes"
  )

  const res = binding.crypto_aead_chacha20poly1305_ietf_decrypt_detached(m, c, mac, ad, npub, k)

  if (res !== 0) throw new Error('could not verify data')
}

// crypto_stream

exports.crypto_stream_xor_wrap_init = function (state, n, k) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.sn_crypto_stream_xor_STATEBYTES,
    "state must be 'sn_crypto_stream_xor_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.crypto_stream_NONCEBYTES,
    "n must be 'crypto_stream_NONCEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_stream_KEYBYTES,
    "k must be 'crypto_stream_KEYBYTES' bytes"
  )

  binding.crypto_stream_xor_wrap_init(state, n, k)
}

exports.crypto_stream_xor_wrap_update = function (state, c, m) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.sn_crypto_stream_xor_STATEBYTES,
    "state must be 'sn_crypto_stream_xor_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")

  binding.crypto_stream_xor_wrap_update(state, c, m)
}

exports.crypto_stream_xor_wrap_final = function (state) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.sn_crypto_stream_xor_STATEBYTES,
    "state must be 'sn_crypto_stream_xor_STATEBYTES' bytes"
  )

  binding.crypto_stream_xor_wrap_final(state)
}

exports.crypto_stream_chacha20_xor_wrap_init = function (state, n, k) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_stream_chacha20_xor_STATEBYTES,
    "state must be 'crypto_stream_chacha20_xor_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.crypto_stream_chacha20_NONCEBYTES,
    "n must be 'crypto_stream_chacha20_NONCEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_stream_chacha20_KEYBYTES,
    "k must be 'crypto_stream_chacha20_KEYBYTES' bytes"
  )

  binding.crypto_stream_chacha20_xor_wrap_init(state, n, k)
}

exports.crypto_stream_chacha20_xor_wrap_update = function (state, c, m) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_stream_chacha20_xor_STATEBYTES,
    "state must be 'crypto_stream_chacha20_xor_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")

  binding.crypto_stream_chacha20_xor_wrap_update(state, c, m)
}

exports.crypto_stream_chacha20_xor_wrap_final = function (state) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_stream_chacha20_xor_STATEBYTES,
    "state must be 'crypto_stream_chacha20_xor_STATEBYTES' bytes"
  )

  binding.crypto_stream_chacha20_xor_wrap_final(state)
}

exports.crypto_stream_chacha20_ietf_xor_wrap_init = function (state, n, k) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_stream_chacha20_ietf_xor_STATEBYTES,
    "state must be 'crypto_stream_chacha20_ietf_xor_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.crypto_stream_chacha20_ietf_NONCEBYTES,
    "n must be 'crypto_stream_chacha20_ietf_NONCEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_stream_chacha20_ietf_KEYBYTES,
    "k must be 'crypto_stream_chacha20_ietf_KEYBYTES' bytes"
  )

  binding.crypto_stream_chacha20_ietf_xor_wrap_init(state, n, k)
}

exports.crypto_stream_chacha20_ietf_xor_wrap_update = function (state, c, m) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_stream_chacha20_ietf_xor_STATEBYTES,
    "state must be 'crypto_stream_chacha20_ietf_xor_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")

  binding.crypto_stream_chacha20_ietf_xor_wrap_update(state, c, m)
}

exports.crypto_stream_chacha20_ietf_xor_wrap_final = function (state) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_stream_chacha20_ietf_xor_STATEBYTES,
    "state must be 'crypto_stream_chacha20_ietf_xor_STATEBYTES' bytes"
  )

  binding.crypto_stream_chacha20_ietf_xor_wrap_final(state)
}

exports.crypto_stream_xchacha20_xor_wrap_init = function (state, n, k) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_stream_xchacha20_xor_STATEBYTES,
    "state must be 'crypto_stream_xchacha20_xor_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.crypto_stream_xchacha20_NONCEBYTES,
    "n must be 'crypto_stream_xchacha20_NONCEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_stream_xchacha20_KEYBYTES,
    "k must be 'crypto_stream_xchacha20_KEYBYTES' bytes"
  )

  binding.crypto_stream_xchacha20_xor_wrap_init(state, n, k)
}

exports.crypto_stream_xchacha20_xor_wrap_update = function (state, c, m) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_stream_xchacha20_xor_STATEBYTES,
    "state must be 'crypto_stream_xchacha20_xor_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")

  binding.crypto_stream_xchacha20_xor_wrap_update(state, c, m)
}

exports.crypto_stream_xchacha20_xor_wrap_final = function (state) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_stream_xchacha20_xor_STATEBYTES,
    "state must be 'crypto_stream_xchacha20_xor_STATEBYTES' bytes"
  )

  binding.crypto_stream_xchacha20_xor_wrap_final(state)
}

exports.crypto_stream_salsa20_xor_wrap_init = function (state, n, k) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_stream_salsa20_xor_STATEBYTES,
    "state must be 'crypto_stream_salsa20_xor_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.crypto_stream_salsa20_NONCEBYTES,
    "n must be 'crypto_stream_salsa20_NONCEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_stream_salsa20_KEYBYTES,
    "k must be 'crypto_stream_salsa20_KEYBYTES' bytes"
  )

  binding.crypto_stream_salsa20_xor_wrap_init(state, n, k)
}

exports.crypto_stream_salsa20_xor_wrap_update = function (state, c, m) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_stream_salsa20_xor_STATEBYTES,
    "state must be 'crypto_stream_salsa20_xor_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")

  binding.crypto_stream_salsa20_xor_wrap_update(state, c, m)
}

exports.crypto_stream_salsa20_xor_wrap_final = function (state) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_stream_salsa20_xor_STATEBYTES,
    "state must be 'crypto_stream_salsa20_xor_STATEBYTES' bytes"
  )

  binding.crypto_stream_salsa20_xor_wrap_final(state)
}

// experimental

exports.extension_tweak_ed25519_base = function (n, p, ns) {
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "n must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(p), 'p must be a typed array')
  assert(
    p.byteLength === binding.extension_tweak_ed25519_BYTES,
    "p must be 'extension_tweak_ed25519_BYTES' bytes"
  )

  binding.extension_tweak_ed25519_base(n, p, ns)
}

exports.extension_tweak_ed25519_sign_detached = function (sig, m, scalar, pk) {
  assert(ArrayBuffer.isView(sig), 'sig must be a typed array')
  assert(sig.byteLength === binding.crypto_sign_BYTES, "sig must be 'crypto_sign_BYTES' bytes")
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(scalar), 'scalar must be a typed array')
  assert(
    scalar.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "scalar must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )

  if (pk) {
    assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
    assert(
      pk.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
      "pk must be 'crypto_sign_PUBLICKEYBYTES' bytes"
    )
  }

  const res = binding.extension_tweak_ed25519_sign_detached(sig, m, scalar, pk)

  if (res !== 0) throw new Error('failed to compute signature')
}

exports.extension_tweak_ed25519_sk_to_scalar = function (n, sk) {
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "n must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(
    sk.byteLength === binding.crypto_sign_SECRETKEYBYTES,
    "sk must be 'crypto_sign_SECRETKEYBYTES' bytes"
  )

  binding.extension_tweak_ed25519_sk_to_scalar(n, sk)
}

exports.extension_tweak_ed25519_scalar = function (scalarOut, scalar, ns) {
  assert(ArrayBuffer.isView(scalarOut), 'scalarOut must be a typed array')
  assert(
    scalarOut.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "scalarOut must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(scalar), 'scalar must be a typed array')
  assert(
    scalar.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "scalar must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )

  binding.extension_tweak_ed25519_scalar(scalarOut, scalar, ns)
}

exports.extension_tweak_ed25519_pk = function (tpk, pk, ns) {
  assert(ArrayBuffer.isView(tpk), 'tpk must be a typed array')
  assert(
    tpk.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
    "tpk must be 'crypto_sign_PUBLICKEYBYTES' bytes"
  )
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(
    pk.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
    "pk must be 'crypto_sign_PUBLICKEYBYTES' bytes"
  )

  const res = binding.extension_tweak_ed25519_pk(tpk, pk, ns)

  if (res !== 0) throw new Error('failed to tweak public key')
}

exports.extension_tweak_ed25519_keypair = function (pk, scalarOut, scalarIn, ns) {
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(
    pk.byteLength === binding.extension_tweak_ed25519_BYTES,
    "pk must be 'extension_tweak_ed25519_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(scalarOut), 'scalarOut must be a typed array')
  assert(
    scalarOut.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "scalarOut must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(scalarIn), 'scalarIn must be a typed array')
  assert(
    scalarIn.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "scalarIn must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )

  binding.extension_tweak_ed25519_keypair(pk, scalarOut, scalarIn, ns)
}

exports.extension_tweak_ed25519_scalar_add = function (scalarOut, scalar, n) {
  assert(ArrayBuffer.isView(scalarOut), 'scalarOut must be a typed array')
  assert(
    scalarOut.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "scalarOut must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(scalar), 'scalar must be a typed array')
  assert(
    scalar.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "scalar must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "n must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )

  binding.extension_tweak_ed25519_scalar_add(scalarOut, scalar, n)
}

exports.extension_tweak_ed25519_pk_add = function (tpk, pk, p) {
  assert(ArrayBuffer.isView(tpk), 'tpk must be a typed array')
  assert(
    tpk.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
    "tpk must be 'crypto_sign_PUBLICKEYBYTES' bytes"
  )
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(
    pk.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
    "pk must be 'crypto_sign_PUBLICKEYBYTES' bytes"
  )
  assert(ArrayBuffer.isView(p), 'p must be a typed array')
  assert(
    p.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
    "p must be 'crypto_sign_PUBLICKEYBYTES' bytes"
  )

  const res = binding.extension_tweak_ed25519_pk_add(tpk, pk, p)

  if (res !== 0) throw new Error('failed to add tweak to public key')
}

exports.extension_tweak_ed25519_keypair_add = function (pk, scalarOut, scalarIn, tweak) {
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(
    pk.byteLength === binding.extension_tweak_ed25519_BYTES,
    "pk must be 'extension_tweak_ed25519_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(scalarOut), 'scalarOut must be a typed array')
  assert(
    scalarOut.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "scalarOut must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(scalarIn), 'scalarIn must be a typed array')
  assert(
    scalarIn.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "scalarIn must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(tweak), 'tweak must be a typed array')
  assert(
    tweak.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "tweak must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )

  const res = binding.extension_tweak_ed25519_keypair_add(pk, scalarOut, scalarIn, tweak)

  if (res !== 0) throw new Error('failed to add tweak to keypair')
}

exports.extension_pbkdf2_sha512_async = function (out, passwd, salt, iter, outlen, callback) {
  assert(
    iter >= binding.extension_pbkdf2_sha512_ITERATIONS_MIN,
    "iter must be at least 'extension_pbkdf2_sha512_ITERATIONS_MIN'"
  )
  assert(
    outlen <= binding.extension_pbkdf2_sha512_BYTES_MAX,
    "outlen must be at most 'extension_pbkdf2_sha512_BYTES_MAX'"
  )
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(out.byteLength >= outlen, "out must be at least 'outlen' bytes")
  assert(out.byteLength > 0, 'out must not be empty')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(passwd.byteLength > 0, 'passwd must not be empty')
  assert(ArrayBuffer.isView(salt), 'salt must be a typed array')
  assert(salt.byteLength > 0, 'salt must not be empty')

  const [done, promise] = checkStatus(callback)

  binding.extension_pbkdf2_sha512_async(
    out.buffer,
    out.byteOffset,
    out.byteLength,

    passwd.buffer,
    passwd.byteOffset,
    passwd.byteLength,

    salt.buffer,
    salt.byteOffset,
    salt.byteLength,

    iter,
    outlen,

    done
  )

  return promise
}

exports.extension_pbkdf2_sha512 = function (out, passwd, salt, iter, outlen) {
  assert(
    iter >= binding.extension_pbkdf2_sha512_ITERATIONS_MIN,
    "iter must be at least 'extension_pbkdf2_sha512_ITERATIONS_MIN'"
  )
  assert(
    outlen <= binding.extension_pbkdf2_sha512_BYTES_MAX,
    "outlen must be at most 'extension_pbkdf2_sha512_BYTES_MAX'"
  )
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(out.byteLength >= outlen, "out must be at least 'outlen' bytes")
  assert(out.byteLength > 0, 'out must not be empty')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(passwd.byteLength > 0, 'passwd must not be empty')
  assert(ArrayBuffer.isView(salt), 'salt must be a typed array')
  assert(salt.byteLength > 0, 'salt must not be empty')

  const res = binding.extension_pbkdf2_sha512(out, passwd, salt, iter, outlen)

  if (res !== 0) throw new Error('failed to add tweak to public key')
}

function checkStatus(callback, booleanResult = false) {
  let done, promise

  if (typeof callback === 'function') {
    done = function (status) {
      if (booleanResult) callback(null, status === 0)
      else if (status === 0) callback(null)
      else callback(new Error('status: ' + status))
    }
  } else {
    promise = new Promise(function (resolve, reject) {
      done = function (status) {
        if (booleanResult) resolve(status === 0)
        else if (status === 0) resolve()
        else reject(new Error('status: ' + status))
      }
    })
  }

  return [done, promise]
}
{
  "name": "sodium-native",
  "version": "5.1.0",
  "description": "Low level bindings for libsodium",
  "main": "index.js",
  "files": [
    "index.js",
    "binding.cc",
    "binding.js",
    "extensions",
    "prebuilds",
    "CMakeLists.txt"
  ],
  "imports": {
    "assert": {
      "bare": "bare-assert",
      "node": "assert"
    }
  },
  "addon": true,
  "dependencies": {
    "bare-assert": "^1.2.0",
    "require-addon": "^1.1.0",
    "which-runtime": "^1.2.1"
  },
  "devDependencies": {
    "bare-compat-napi": "^1.3.5",
    "brittle": "^3.16.2",
    "cmake-bare": "^1.6.1",
    "cmake-fetch": "^1.4.3",
    "cmake-napi": "^1.2.1",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^2.0.0"
  },
  "scripts": {
    "test": "prettier . --check && npm run test:node && npm run test:bare",
    "test:node": "node test/all.js",
    "test:bare": "bare test/all.js"
  },
  "standard": {
    "ignore": [
      "/test/fixtures/*.js"
    ]
  },
  "engines": {
    "bare": ">=1.16.0"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/sodium-native.git"
  },
  "contributors": [
    "Emil Bay <github@tixz.dk> (http://bayes.dk)",
    "Mathias Buus <mathiasbuus@gmail.com> (https://mafinto.sh)",
    "Christophe Diederichs <chm-diederichs@hyperdivision.dk>"
  ],
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/holepunchto/sodium-native/issues"
  },
  "homepage": "https://github.com/holepunchto/sodium-native"
}
module.exports = require('sodium-native')
{
  "name": "sodium-universal",
  "version": "5.0.1",
  "description": "Universal wrapper for sodium-javascript and sodium-native working in Node.js and the Browser",
  "main": "index.js",
  "dependencies": {
    "sodium-native": "^5.0.1"
  },
  "peerDependencies": {
    "sodium-javascript": "~0.8.0"
  },
  "peerDependenciesMeta": {
    "sodium-javascript": {
      "optional": true
    }
  },
  "scripts": {
    "prepublish": "./build-scripts/generate.js"
  },
  "browser": {
    "sodium-native": "sodium-javascript"
  },
  "browserify": {
    "transform": [
      "./build-scripts/transform.js"
    ]
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/sodium-universal.git"
  },
  "keywords": [
    "libsodium",
    "sodium",
    "sodium-native",
    "sodium-javascript",
    "browserify"
  ],
  "contributors": [
    "Emil Bay <github@tixz.dk> (http://bayes.dk)",
    "Mathias Buus <mathiasbuus@gmail.com> (https://mafinto.sh)",
    "Christophe Diederichs <chm-diederichs@hyperdivision.dk>"
  ],
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/holepunchto/sodium-universal/issues"
  },
  "homepage": "https://github.com/holepunchto/sodium-universal#readme"
}
const { EventEmitter } = require('events-universal')
const FIFO = require('fast-fifo')
const TextDecoder = require('text-decoder')

const StreamError = require('./lib/errors.js')

// if we do a future major, expect queue microtask to be there always, for now a bit defensive
const qmt =
  typeof queueMicrotask === 'undefined' ? (fn) => global.process.nextTick(fn) : queueMicrotask

// 29 bits used total (4 from shared, 14 from read, and 11 from write)
const MAX = (1 << 29) - 1

// Shared state
const OPENING = 0b0001
const PREDESTROYING = 0b0010
const DESTROYING = 0b0100
const DESTROYED = 0b1000

const NOT_OPENING = MAX ^ OPENING
const NOT_PREDESTROYING = MAX ^ PREDESTROYING

// Read state (4 bit offset from shared state)
const READ_ACTIVE = 0b00000000000001 << 4
const READ_UPDATING = 0b00000000000010 << 4
const READ_PRIMARY = 0b00000000000100 << 4
const READ_QUEUED = 0b00000000001000 << 4
const READ_RESUMED = 0b00000000010000 << 4
const READ_PIPE_DRAINED = 0b00000000100000 << 4
const READ_ENDING = 0b00000001000000 << 4
const READ_EMIT_DATA = 0b00000010000000 << 4
const READ_EMIT_READABLE = 0b00000100000000 << 4
const READ_EMITTED_READABLE = 0b00001000000000 << 4
const READ_DONE = 0b00010000000000 << 4
const READ_NEXT_TICK = 0b00100000000000 << 4
const READ_NEEDS_PUSH = 0b01000000000000 << 4
const READ_READ_AHEAD = 0b10000000000000 << 4

// Combined read state
const READ_FLOWING = READ_RESUMED | READ_PIPE_DRAINED
const READ_ACTIVE_AND_NEEDS_PUSH = READ_ACTIVE | READ_NEEDS_PUSH
const READ_PRIMARY_AND_ACTIVE = READ_PRIMARY | READ_ACTIVE
const READ_EMIT_READABLE_AND_QUEUED = READ_EMIT_READABLE | READ_QUEUED
const READ_RESUMED_READ_AHEAD = READ_RESUMED | READ_READ_AHEAD

const READ_NOT_ACTIVE = MAX ^ READ_ACTIVE
const READ_NON_PRIMARY = MAX ^ READ_PRIMARY
const READ_NON_PRIMARY_AND_PUSHED = MAX ^ (READ_PRIMARY | READ_NEEDS_PUSH)
const READ_PUSHED = MAX ^ READ_NEEDS_PUSH
const READ_PAUSED = MAX ^ READ_RESUMED
const READ_NOT_QUEUED = MAX ^ (READ_QUEUED | READ_EMITTED_READABLE)
const READ_NOT_ENDING = MAX ^ READ_ENDING
const READ_PIPE_NOT_DRAINED = MAX ^ READ_FLOWING
const READ_NOT_NEXT_TICK = MAX ^ READ_NEXT_TICK
const READ_NOT_UPDATING = MAX ^ READ_UPDATING
const READ_NO_READ_AHEAD = MAX ^ READ_READ_AHEAD
const READ_PAUSED_NO_READ_AHEAD = MAX ^ READ_RESUMED_READ_AHEAD

// Write state (18 bit offset, 4 bit offset from shared state and 14 from read state)
const WRITE_ACTIVE = 0b00000000001 << 18
const WRITE_UPDATING = 0b00000000010 << 18
const WRITE_PRIMARY = 0b00000000100 << 18
const WRITE_QUEUED = 0b00000001000 << 18
const WRITE_UNDRAINED = 0b00000010000 << 18
const WRITE_DONE = 0b00000100000 << 18
const WRITE_EMIT_DRAIN = 0b00001000000 << 18
const WRITE_NEXT_TICK = 0b00010000000 << 18
const WRITE_WRITING = 0b00100000000 << 18
const WRITE_FINISHING = 0b01000000000 << 18
const WRITE_CORKED = 0b10000000000 << 18

const WRITE_NOT_ACTIVE = MAX ^ (WRITE_ACTIVE | WRITE_WRITING)
const WRITE_NON_PRIMARY = MAX ^ WRITE_PRIMARY
const WRITE_NOT_FINISHING = MAX ^ (WRITE_ACTIVE | WRITE_FINISHING)
const WRITE_DRAINED = MAX ^ WRITE_UNDRAINED
const WRITE_NOT_QUEUED = MAX ^ WRITE_QUEUED
const WRITE_NOT_NEXT_TICK = MAX ^ WRITE_NEXT_TICK
const WRITE_NOT_UPDATING = MAX ^ WRITE_UPDATING
const WRITE_NOT_CORKED = MAX ^ WRITE_CORKED

// Combined shared state
const ACTIVE = READ_ACTIVE | WRITE_ACTIVE
const NOT_ACTIVE = MAX ^ ACTIVE
const DONE = READ_DONE | WRITE_DONE
const DESTROY_STATUS = DESTROYING | DESTROYED | PREDESTROYING
const OPEN_STATUS = DESTROY_STATUS | OPENING
const AUTO_DESTROY = DESTROY_STATUS | DONE
const NON_PRIMARY = WRITE_NON_PRIMARY & READ_NON_PRIMARY
const ACTIVE_OR_TICKING = WRITE_NEXT_TICK | READ_NEXT_TICK
const TICKING = ACTIVE_OR_TICKING & NOT_ACTIVE
const IS_OPENING = OPEN_STATUS | TICKING

// Combined shared state and read state
const READ_PRIMARY_STATUS = OPEN_STATUS | READ_ENDING | READ_DONE
const READ_STATUS = OPEN_STATUS | READ_DONE | READ_QUEUED
const READ_ENDING_STATUS = OPEN_STATUS | READ_ENDING | READ_QUEUED
const READ_READABLE_STATUS = OPEN_STATUS | READ_EMIT_READABLE | READ_QUEUED | READ_EMITTED_READABLE
const SHOULD_NOT_READ =
  OPEN_STATUS | READ_ACTIVE | READ_ENDING | READ_DONE | READ_NEEDS_PUSH | READ_READ_AHEAD
const READ_BACKPRESSURE_STATUS = DESTROY_STATUS | READ_ENDING | READ_DONE
const READ_UPDATE_SYNC_STATUS = READ_UPDATING | OPEN_STATUS | READ_NEXT_TICK | READ_PRIMARY
const READ_NEXT_TICK_OR_OPENING = READ_NEXT_TICK | OPENING

// Combined write state
const WRITE_PRIMARY_STATUS = OPEN_STATUS | WRITE_FINISHING | WRITE_DONE
const WRITE_QUEUED_AND_UNDRAINED = WRITE_QUEUED | WRITE_UNDRAINED
const WRITE_QUEUED_AND_ACTIVE = WRITE_QUEUED | WRITE_ACTIVE
const WRITE_DRAIN_STATUS = WRITE_QUEUED | WRITE_UNDRAINED | OPEN_STATUS | WRITE_ACTIVE
const WRITE_STATUS = OPEN_STATUS | WRITE_ACTIVE | WRITE_QUEUED | WRITE_CORKED
const WRITE_PRIMARY_AND_ACTIVE = WRITE_PRIMARY | WRITE_ACTIVE
const WRITE_ACTIVE_AND_WRITING = WRITE_ACTIVE | WRITE_WRITING
const WRITE_FINISHING_STATUS = OPEN_STATUS | WRITE_FINISHING | WRITE_QUEUED_AND_ACTIVE | WRITE_DONE
const WRITE_BACKPRESSURE_STATUS = WRITE_UNDRAINED | DESTROY_STATUS | WRITE_FINISHING | WRITE_DONE
const WRITE_UPDATE_SYNC_STATUS = WRITE_UPDATING | OPEN_STATUS | WRITE_NEXT_TICK | WRITE_PRIMARY
const WRITE_DROP_DATA = WRITE_FINISHING | WRITE_DONE | DESTROY_STATUS

const asyncIterator = Symbol.asyncIterator || Symbol('asyncIterator')

class WritableState {
  constructor(
    stream,
    { highWaterMark = 16384, map = null, mapWritable, byteLength, byteLengthWritable } = {}
  ) {
    this.stream = stream
    this.queue = new FIFO()
    this.highWaterMark = highWaterMark
    this.buffered = 0
    this.error = null
    this.pipeline = null
    this.drains = null // if we add more seldomly used helpers we might them into a subobject so its a single ptr
    this.byteLength = byteLengthWritable || byteLength || defaultByteLength
    this.map = mapWritable || map
    this.afterWrite = afterWrite.bind(this)
    this.afterUpdateNextTick = updateWriteNT.bind(this)
  }

  get ending() {
    return (this.stream._duplexState & WRITE_FINISHING) !== 0
  }

  get ended() {
    return (this.stream._duplexState & WRITE_DONE) !== 0
  }

  push(data) {
    if ((this.stream._duplexState & WRITE_DROP_DATA) !== 0) return false
    if (this.map !== null) data = this.map(data)

    this.buffered += this.byteLength(data)
    this.queue.push(data)

    if (this.buffered < this.highWaterMark) {
      this.stream._duplexState |= WRITE_QUEUED
      return true
    }

    this.stream._duplexState |= WRITE_QUEUED_AND_UNDRAINED
    return false
  }

  shift() {
    const data = this.queue.shift()

    this.buffered -= this.byteLength(data)
    if (this.buffered === 0) this.stream._duplexState &= WRITE_NOT_QUEUED

    return data
  }

  end(data) {
    if (typeof data === 'function') {
      this.stream.once('finish', data)
    } else if (data !== undefined && data !== null) {
      this.push(data)
    }

    this.stream._duplexState = (this.stream._duplexState | WRITE_FINISHING) & WRITE_NON_PRIMARY
  }

  autoBatch(data, cb) {
    const buffer = []
    const stream = this.stream

    buffer.push(data)
    while ((stream._duplexState & WRITE_STATUS) === WRITE_QUEUED_AND_ACTIVE) {
      buffer.push(stream._writableState.shift())
    }

    if ((stream._duplexState & OPEN_STATUS) !== 0) return cb(null)
    stream._writev(buffer, cb)
  }

  update() {
    const stream = this.stream

    stream._duplexState |= WRITE_UPDATING

    do {
      while ((stream._duplexState & WRITE_STATUS) === WRITE_QUEUED) {
        const data = this.shift()
        stream._duplexState |= WRITE_ACTIVE_AND_WRITING
        stream._write(data, this.afterWrite)
      }

      if ((stream._duplexState & WRITE_PRIMARY_AND_ACTIVE) === 0) this.updateNonPrimary()
    } while (this.continueUpdate() === true)

    stream._duplexState &= WRITE_NOT_UPDATING
  }

  updateNonPrimary() {
    const stream = this.stream

    if ((stream._duplexState & WRITE_FINISHING_STATUS) === WRITE_FINISHING) {
      stream._duplexState = stream._duplexState | WRITE_ACTIVE
      stream._final(afterFinal.bind(this))
      return
    }

    if ((stream._duplexState & DESTROY_STATUS) === DESTROYING) {
      if ((stream._duplexState & ACTIVE_OR_TICKING) === 0) {
        stream._duplexState |= ACTIVE
        stream._destroy(afterDestroy.bind(this))
      }
      return
    }

    if ((stream._duplexState & IS_OPENING) === OPENING) {
      stream._duplexState = (stream._duplexState | ACTIVE) & NOT_OPENING
      stream._open(afterOpen.bind(this))
    }
  }

  continueUpdate() {
    if ((this.stream._duplexState & WRITE_NEXT_TICK) === 0) return false
    this.stream._duplexState &= WRITE_NOT_NEXT_TICK
    return true
  }

  updateCallback() {
    if ((this.stream._duplexState & WRITE_UPDATE_SYNC_STATUS) === WRITE_PRIMARY) {
      this.update()
    } else {
      this.updateNextTick()
    }
  }

  updateNextTick() {
    if ((this.stream._duplexState & WRITE_NEXT_TICK) !== 0) return
    this.stream._duplexState |= WRITE_NEXT_TICK
    if ((this.stream._duplexState & WRITE_UPDATING) === 0) qmt(this.afterUpdateNextTick)
  }
}

class ReadableState {
  constructor(
    stream,
    { highWaterMark = 16384, map = null, mapReadable, byteLength, byteLengthReadable } = {}
  ) {
    this.stream = stream
    this.queue = new FIFO()
    this.highWaterMark = highWaterMark === 0 ? 1 : highWaterMark
    this.buffered = 0
    this.readAhead = highWaterMark > 0
    this.error = null
    this.pipeline = null
    this.byteLength = byteLengthReadable || byteLength || defaultByteLength
    this.map = mapReadable || map
    this.pipeTo = null
    this.afterRead = afterRead.bind(this)
    this.afterUpdateNextTick = updateReadNT.bind(this)
  }

  get ending() {
    return (this.stream._duplexState & READ_ENDING) !== 0
  }

  get ended() {
    return (this.stream._duplexState & READ_DONE) !== 0
  }

  pipe(pipeTo, cb) {
    if (this.pipeTo !== null) throw StreamError.BAD_ARGUMENT('Can only pipe to one destination')
    if (typeof cb !== 'function') cb = null

    this.stream._duplexState |= READ_PIPE_DRAINED
    this.pipeTo = pipeTo
    this.pipeline = new Pipeline(this.stream, pipeTo, cb)

    if (cb) this.stream.on('error', noop) // We already error handle this so supress crashes

    if (isStreamx(pipeTo)) {
      pipeTo._writableState.pipeline = this.pipeline
      if (cb) pipeTo.on('error', noop) // We already error handle this so supress crashes
      pipeTo.on('finish', this.pipeline.finished.bind(this.pipeline)) // TODO: just call finished from pipeTo itself
    } else {
      const onerror = this.pipeline.done.bind(this.pipeline, pipeTo)
      const onclose = this.pipeline.done.bind(this.pipeline, pipeTo, null) // onclose has a weird bool arg
      pipeTo.on('error', onerror)
      pipeTo.on('close', onclose)
      pipeTo.on('finish', this.pipeline.finished.bind(this.pipeline))
    }

    pipeTo.on('drain', afterDrain.bind(this))
    this.stream.emit('piping', pipeTo)
    pipeTo.emit('pipe', this.stream)
  }

  push(data) {
    const stream = this.stream

    if (data === null) {
      this.highWaterMark = 0
      stream._duplexState = (stream._duplexState | READ_ENDING) & READ_NON_PRIMARY_AND_PUSHED
      return false
    }

    if (this.map !== null) {
      data = this.map(data)
      if (data === null) {
        stream._duplexState &= READ_PUSHED
        return this.buffered < this.highWaterMark
      }
    }

    this.buffered += this.byteLength(data)
    this.queue.push(data)

    stream._duplexState = (stream._duplexState | READ_QUEUED) & READ_PUSHED

    return this.buffered < this.highWaterMark
  }

  shift() {
    const data = this.queue.shift()

    this.buffered -= this.byteLength(data)
    if (this.buffered === 0) {
      this.stream._duplexState &= READ_NOT_QUEUED
    }

    return data
  }

  unshift(data) {
    const pending = [this.map !== null ? this.map(data) : data]
    while (this.buffered > 0) pending.push(this.shift())

    for (let i = 0; i < pending.length - 1; i++) {
      const data = pending[i]
      this.buffered += this.byteLength(data)
      this.queue.push(data)
    }

    this.push(pending[pending.length - 1])
  }

  read() {
    const stream = this.stream

    if ((stream._duplexState & READ_STATUS) === READ_QUEUED) {
      const data = this.shift()

      if (this.pipeTo !== null && this.pipeTo.write(data) === false) {
        stream._duplexState &= READ_PIPE_NOT_DRAINED
      }

      if ((stream._duplexState & READ_EMIT_DATA) !== 0) {
        stream.emit('data', data)
      }

      return data
    }

    if (this.readAhead === false) {
      stream._duplexState |= READ_READ_AHEAD
      this.updateNextTick()
    }

    return null
  }

  drain() {
    const stream = this.stream

    while (
      (stream._duplexState & READ_STATUS) === READ_QUEUED &&
      (stream._duplexState & READ_FLOWING) !== 0
    ) {
      const data = this.shift()

      if (this.pipeTo !== null && this.pipeTo.write(data) === false) {
        stream._duplexState &= READ_PIPE_NOT_DRAINED
      }

      if ((stream._duplexState & READ_EMIT_DATA) !== 0) {
        stream.emit('data', data)
      }
    }
  }

  update() {
    const stream = this.stream

    stream._duplexState |= READ_UPDATING

    do {
      this.drain()

      while (
        this.buffered < this.highWaterMark &&
        (stream._duplexState & SHOULD_NOT_READ) === READ_READ_AHEAD
      ) {
        stream._duplexState |= READ_ACTIVE_AND_NEEDS_PUSH
        stream._read(this.afterRead)
        this.drain()
      }

      if ((stream._duplexState & READ_READABLE_STATUS) === READ_EMIT_READABLE_AND_QUEUED) {
        stream._duplexState |= READ_EMITTED_READABLE
        stream.emit('readable')
      }

      if ((stream._duplexState & READ_PRIMARY_AND_ACTIVE) === 0) {
        this.updateNonPrimary()
      }
    } while (this.continueUpdate() === true)

    stream._duplexState &= READ_NOT_UPDATING
  }

  updateNonPrimary() {
    const stream = this.stream

    if ((stream._duplexState & READ_ENDING_STATUS) === READ_ENDING) {
      stream._duplexState = (stream._duplexState | READ_DONE) & READ_NOT_ENDING
      stream.emit('end')

      if ((stream._duplexState & AUTO_DESTROY) === DONE) {
        stream._duplexState |= DESTROYING
      }

      if (this.pipeTo !== null) {
        this.pipeTo.end()
      }
    }

    if ((stream._duplexState & DESTROY_STATUS) === DESTROYING) {
      if ((stream._duplexState & ACTIVE_OR_TICKING) === 0) {
        stream._duplexState |= ACTIVE
        stream._destroy(afterDestroy.bind(this))
      }
      return
    }

    if ((stream._duplexState & IS_OPENING) === OPENING) {
      stream._duplexState = (stream._duplexState | ACTIVE) & NOT_OPENING
      stream._open(afterOpen.bind(this))
    }
  }

  continueUpdate() {
    if ((this.stream._duplexState & READ_NEXT_TICK) === 0) return false
    this.stream._duplexState &= READ_NOT_NEXT_TICK
    return true
  }

  updateCallback() {
    if ((this.stream._duplexState & READ_UPDATE_SYNC_STATUS) === READ_PRIMARY) {
      this.update()
    } else {
      this.updateNextTick()
    }
  }

  updateNextTickIfOpen() {
    if ((this.stream._duplexState & READ_NEXT_TICK_OR_OPENING) !== 0) return
    this.stream._duplexState |= READ_NEXT_TICK
    if ((this.stream._duplexState & READ_UPDATING) === 0) qmt(this.afterUpdateNextTick)
  }

  updateNextTick() {
    if ((this.stream._duplexState & READ_NEXT_TICK) !== 0) return
    this.stream._duplexState |= READ_NEXT_TICK
    if ((this.stream._duplexState & READ_UPDATING) === 0) qmt(this.afterUpdateNextTick)
  }
}

class TransformState {
  constructor(stream) {
    this.data = null
    this.afterTransform = afterTransform.bind(stream)
    this.afterFinal = null
  }
}

class Pipeline {
  constructor(src, dst, cb) {
    this.from = src
    this.to = dst
    this.afterPipe = cb
    this.error = null
    this.pipeToFinished = false
  }

  finished() {
    this.pipeToFinished = true
  }

  done(stream, err) {
    if (err) this.error = err

    if (stream === this.to) {
      this.to = null

      if (this.from !== null) {
        if ((this.from._duplexState & READ_DONE) === 0 || !this.pipeToFinished) {
          this.from.destroy(this.error || StreamError.PREMATURE_CLOSE('Writable stream closed'))
        }
        return
      }
    }

    if (stream === this.from) {
      this.from = null

      if (this.to !== null) {
        if ((stream._duplexState & READ_DONE) === 0) {
          this.to.destroy(this.error || StreamError.PREMATURE_CLOSE('Readable stream closed'))
        }
        return
      }
    }

    if (this.afterPipe !== null) this.afterPipe(this.error)
    this.to = this.from = this.afterPipe = null
  }
}

function afterDrain() {
  this.stream._duplexState |= READ_PIPE_DRAINED
  this.updateCallback()
}

function afterFinal(err) {
  const stream = this.stream
  if (err) stream.destroy(err)

  if ((stream._duplexState & DESTROY_STATUS) === 0) {
    stream._duplexState |= WRITE_DONE
    stream.emit('finish')
  }

  if ((stream._duplexState & AUTO_DESTROY) === DONE) {
    stream._duplexState |= DESTROYING
  }

  stream._duplexState &= WRITE_NOT_FINISHING

  // no need to wait the extra tick here, so we short circuit that
  if ((stream._duplexState & WRITE_UPDATING) === 0) {
    this.update()
  } else {
    this.updateNextTick()
  }
}

function afterDestroy(err) {
  const stream = this.stream

  if (!err && !StreamError.isStreamDestroyed(this.error)) err = this.error
  if (err) stream.emit('error', err)

  stream._duplexState |= DESTROYED
  stream.emit('close')

  const rs = stream._readableState
  const ws = stream._writableState

  if (rs !== null && rs.pipeline !== null) {
    rs.pipeline.done(stream, err)
  }

  if (ws !== null) {
    while (ws.drains !== null && ws.drains.length > 0) {
      ws.drains.shift().resolve(false)
    }

    if (ws.pipeline !== null) {
      ws.pipeline.done(stream, err)
    }
  }
}

function afterWrite(err) {
  const stream = this.stream

  if (err) stream.destroy(err)
  stream._duplexState &= WRITE_NOT_ACTIVE

  if (this.drains !== null) tickDrains(this.drains)

  if ((stream._duplexState & WRITE_DRAIN_STATUS) === WRITE_UNDRAINED) {
    stream._duplexState &= WRITE_DRAINED

    if ((stream._duplexState & WRITE_EMIT_DRAIN) === WRITE_EMIT_DRAIN) {
      stream.emit('drain')
    }
  }

  this.updateCallback()
}

function afterRead(err) {
  if (err) this.stream.destroy(err)
  this.stream._duplexState &= READ_NOT_ACTIVE

  if (this.readAhead === false && (this.stream._duplexState & READ_RESUMED) === 0) {
    this.stream._duplexState &= READ_NO_READ_AHEAD
  }

  this.updateCallback()
}

function updateReadNT() {
  if ((this.stream._duplexState & READ_UPDATING) === 0) {
    this.stream._duplexState &= READ_NOT_NEXT_TICK
    this.update()
  }
}

function updateWriteNT() {
  if ((this.stream._duplexState & WRITE_UPDATING) === 0) {
    this.stream._duplexState &= WRITE_NOT_NEXT_TICK
    this.update()
  }
}

function tickDrains(drains) {
  for (let i = 0; i < drains.length; i++) {
    // drains.writes are monotonic, so if one is 0 its always the first one
    if (--drains[i].writes === 0) {
      drains.shift().resolve(true)
      i--
    }
  }
}

function afterOpen(err) {
  const stream = this.stream

  if (err) stream.destroy(err)

  if ((stream._duplexState & DESTROYING) === 0) {
    if ((stream._duplexState & READ_PRIMARY_STATUS) === 0) {
      stream._duplexState |= READ_PRIMARY
    }

    if ((stream._duplexState & WRITE_PRIMARY_STATUS) === 0) {
      stream._duplexState |= WRITE_PRIMARY
    }

    stream.emit('open')
  }

  stream._duplexState &= NOT_ACTIVE

  if (stream._writableState !== null) {
    stream._writableState.updateCallback()
  }

  if (stream._readableState !== null) {
    stream._readableState.updateCallback()
  }
}

function afterTransform(err, data) {
  if (data !== undefined && data !== null) this.push(data)
  this._writableState.afterWrite(err)
}

function newListener(name) {
  if (this._readableState !== null) {
    if (name === 'data') {
      this._duplexState |= READ_EMIT_DATA | READ_RESUMED_READ_AHEAD
      this._readableState.updateNextTick()
    }
    if (name === 'readable') {
      this._duplexState |= READ_EMIT_READABLE
      this._readableState.updateNextTick()
    }
  }

  if (this._writableState !== null) {
    if (name === 'drain') {
      this._duplexState |= WRITE_EMIT_DRAIN
      this._writableState.updateNextTick()
    }
  }
}

class Stream extends EventEmitter {
  constructor(opts) {
    super()

    this._duplexState = 0
    this._readableState = null
    this._writableState = null

    if (opts) {
      if (opts.open) this._open = opts.open
      if (opts.destroy) this._destroy = opts.destroy
      if (opts.predestroy) this._predestroy = opts.predestroy
      if (opts.signal) opts.signal.addEventListener('abort', abort.bind(this))
    }

    this.on('newListener', newListener)
  }

  _open(cb) {
    cb(null)
  }

  _destroy(cb) {
    cb(null)
  }

  _predestroy() {
    // does nothing
  }

  get readable() {
    return this._readableState !== null ? true : undefined
  }

  get writable() {
    return this._writableState !== null ? true : undefined
  }

  get destroyed() {
    return (this._duplexState & DESTROYED) !== 0
  }

  get destroying() {
    return (this._duplexState & DESTROY_STATUS) !== 0
  }

  destroy(err) {
    if ((this._duplexState & DESTROY_STATUS) === 0) {
      if (!err) err = StreamError.STREAM_DESTROYED()
      this._duplexState = (this._duplexState | DESTROYING) & NON_PRIMARY

      if (this._readableState !== null) {
        this._readableState.highWaterMark = 0
        this._readableState.error = err
      }

      if (this._writableState !== null) {
        this._writableState.highWaterMark = 0
        this._writableState.error = err
      }

      this._duplexState |= PREDESTROYING
      this._predestroy()
      this._duplexState &= NOT_PREDESTROYING

      if (this._readableState !== null) {
        this._readableState.updateNextTick()
      }

      if (this._writableState !== null) {
        this._writableState.updateNextTick()
      }
    }
  }
}

class Readable extends Stream {
  constructor(opts) {
    super(opts)

    this._duplexState |= OPENING | WRITE_DONE | READ_READ_AHEAD
    this._readableState = new ReadableState(this, opts)

    if (opts) {
      if (this._readableState.readAhead === false) this._duplexState &= READ_NO_READ_AHEAD
      if (opts.read) this._read = opts.read
      if (opts.eagerOpen) this._readableState.updateNextTick()
      if (opts.encoding) this.setEncoding(opts.encoding)
    }
  }

  static deferred(fn, opts) {
    const out = new PassThrough(opts)

    fn()
      .then((src) => {
        if (src === null) return out.end()
        if (out.destroying) return
        pipeline(src, out, noop)
      })
      .catch((err) => out.destroy(err))

    return out
  }

  setEncoding(encoding) {
    const dec = new TextDecoder(encoding)
    const map = this._readableState.map || echo
    this._readableState.map = mapOrSkip
    return this

    function mapOrSkip(data) {
      const next = dec.push(data)
      return next === '' && (data.byteLength !== 0 || dec.remaining > 0) ? null : map(next)
    }
  }

  _read(cb) {
    cb(null)
  }

  pipe(dest, cb) {
    this._readableState.updateNextTick()
    this._readableState.pipe(dest, cb)
    return dest
  }

  read() {
    this._readableState.updateNextTick()
    return this._readableState.read()
  }

  push(data) {
    this._readableState.updateNextTickIfOpen()
    return this._readableState.push(data)
  }

  unshift(data) {
    this._readableState.updateNextTickIfOpen()
    return this._readableState.unshift(data)
  }

  resume() {
    this._duplexState |= READ_RESUMED_READ_AHEAD
    this._readableState.updateNextTick()
    return this
  }

  pause() {
    this._duplexState &=
      this._readableState.readAhead === false ? READ_PAUSED_NO_READ_AHEAD : READ_PAUSED
    return this
  }

  static _fromAsyncIterator(ite, opts) {
    let destroy

    const rs = new Readable({
      ...opts,
      read(cb) {
        ite.next().then(push).then(cb.bind(null, null)).catch(cb)
      },
      predestroy() {
        destroy = ite.return()
      },
      destroy(cb) {
        if (!destroy) return cb(null)
        destroy.then(cb.bind(null, null)).catch(cb)
      }
    })

    return rs

    function push(data) {
      if (data.done) rs.push(null)
      else rs.push(data.value)
    }
  }

  static from(data, opts) {
    if (isReadStreamx(data)) return data
    if (data[asyncIterator]) return this._fromAsyncIterator(data[asyncIterator](), opts)
    if (!Array.isArray(data)) data = data === undefined ? [] : [data]

    let i = 0
    return new Readable({
      ...opts,
      read(cb) {
        this.push(i === data.length ? null : data[i++])
        cb(null)
      }
    })
  }

  static isBackpressured(rs) {
    return (
      (rs._duplexState & READ_BACKPRESSURE_STATUS) !== 0 ||
      rs._readableState.buffered >= rs._readableState.highWaterMark
    )
  }

  static isPaused(rs) {
    return (rs._duplexState & READ_RESUMED) === 0
  }

  [asyncIterator]() {
    const stream = this

    let error = null
    let promiseResolve = null
    let promiseReject = null

    this.on('error', (err) => {
      error = err
    })
    this.on('readable', onreadable)
    this.on('close', onclose)

    return {
      [asyncIterator]() {
        return this
      },
      next() {
        return new Promise(function (resolve, reject) {
          promiseResolve = resolve
          promiseReject = reject
          const data = stream.read()
          if (data !== null) ondata(data)
          else if ((stream._duplexState & DESTROYED) !== 0) ondata(null)
        })
      },
      return() {
        return destroy(null)
      },
      throw(err) {
        return destroy(err)
      }
    }

    function onreadable() {
      if (promiseResolve !== null) ondata(stream.read())
    }

    function onclose() {
      if (promiseResolve !== null) ondata(null)
    }

    function ondata(data) {
      if (promiseReject === null) return
      if (error) {
        promiseReject(error)
      } else if (data === null && (stream._duplexState & READ_DONE) === 0) {
        promiseReject(StreamError.STREAM_DESTROYED())
      } else {
        promiseResolve({ value: data, done: data === null })
      }
      promiseReject = promiseResolve = null
    }

    function destroy(err) {
      stream.destroy(err)
      return new Promise((resolve, reject) => {
        if (stream._duplexState & DESTROYED) return resolve({ value: undefined, done: true })
        stream.once('close', function () {
          if (err) reject(err)
          else resolve({ value: undefined, done: true })
        })
      })
    }
  }
}

class Writable extends Stream {
  constructor(opts) {
    super(opts)

    this._duplexState |= OPENING | READ_DONE
    this._writableState = new WritableState(this, opts)

    if (opts) {
      if (opts.writev) this._writev = opts.writev
      if (opts.write) this._write = opts.write
      if (opts.final) this._final = opts.final
      if (opts.eagerOpen) this._writableState.updateNextTick()
    }
  }

  cork() {
    this._duplexState |= WRITE_CORKED
  }

  uncork() {
    this._duplexState &= WRITE_NOT_CORKED
    this._writableState.updateNextTick()
  }

  _writev(batch, cb) {
    cb(null)
  }

  _write(data, cb) {
    this._writableState.autoBatch(data, cb)
  }

  _final(cb) {
    cb(null)
  }

  static isBackpressured(ws) {
    return (ws._duplexState & WRITE_BACKPRESSURE_STATUS) !== 0
  }

  static drained(ws) {
    if (ws.destroyed) return Promise.resolve(false)

    const state = ws._writableState
    const pending = isWritev(ws) ? Math.min(1, state.queue.length) : state.queue.length
    const writes = pending + (ws._duplexState & WRITE_WRITING ? 1 : 0)
    if (writes === 0) return Promise.resolve(true)

    if (state.drains === null) state.drains = []
    return new Promise((resolve) => {
      state.drains.push({ writes, resolve })
    })
  }

  write(data) {
    this._writableState.updateNextTick()
    return this._writableState.push(data)
  }

  end(data) {
    this._writableState.updateNextTick()
    this._writableState.end(data)
    return this
  }
}

class Duplex extends Readable {
  // and Writable
  constructor(opts) {
    super(opts)

    this._duplexState = OPENING | (this._duplexState & READ_READ_AHEAD)
    this._writableState = new WritableState(this, opts)

    if (opts) {
      if (opts.writev) this._writev = opts.writev
      if (opts.write) this._write = opts.write
      if (opts.final) this._final = opts.final
    }
  }

  cork() {
    this._duplexState |= WRITE_CORKED
  }

  uncork() {
    this._duplexState &= WRITE_NOT_CORKED
    this._writableState.updateNextTick()
  }

  _writev(batch, cb) {
    cb(null)
  }

  _write(data, cb) {
    this._writableState.autoBatch(data, cb)
  }

  _final(cb) {
    cb(null)
  }

  write(data) {
    this._writableState.updateNextTick()
    return this._writableState.push(data)
  }

  end(data) {
    this._writableState.updateNextTick()
    this._writableState.end(data)
    return this
  }
}

class Transform extends Duplex {
  constructor(opts) {
    super(opts)
    this._transformState = new TransformState(this)

    if (opts) {
      if (opts.transform) this._transform = opts.transform
      if (opts.flush) this._flush = opts.flush
    }
  }

  _write(data, cb) {
    if (this._readableState.buffered >= this._readableState.highWaterMark) {
      this._transformState.data = data
    } else {
      this._transform(data, this._transformState.afterTransform)
    }
  }

  _read(cb) {
    if (this._transformState.data !== null) {
      const data = this._transformState.data
      this._transformState.data = null
      cb(null)
      this._transform(data, this._transformState.afterTransform)
    } else {
      cb(null)
    }
  }

  destroy(err) {
    super.destroy(err)
    if (this._transformState.data !== null) {
      this._transformState.data = null
      this._transformState.afterTransform()
    }
  }

  _transform(data, cb) {
    cb(null, data)
  }

  _flush(cb) {
    cb(null)
  }

  _final(cb) {
    this._transformState.afterFinal = cb
    this._flush(transformAfterFlush.bind(this))
  }
}

class PassThrough extends Transform {}

function transformAfterFlush(err, data) {
  const cb = this._transformState.afterFinal
  if (err) return cb(err)

  if (data !== null && data !== undefined) this.push(data)
  this.push(null)
  cb(null)
}

function pipelinePromise(...streams) {
  return new Promise((resolve, reject) => {
    return pipeline(...streams, (err) => {
      if (err) return reject(err)
      resolve()
    })
  })
}

function pipeline(stream, ...streams) {
  const all = Array.isArray(stream) ? [...stream, ...streams] : [stream, ...streams]
  const done = all.length && typeof all[all.length - 1] === 'function' ? all.pop() : null

  if (all.length < 2) throw StreamError.BAD_ARGUMENT('Pipeline requires at least 2 streams')

  let src = all[0]
  let dest = null
  let error = null

  for (let i = 1; i < all.length; i++) {
    dest = all[i]

    if (isStreamx(src)) {
      src.pipe(dest, onerror)
    } else {
      errorHandle(src, true, i > 1, onerror)
      src.pipe(dest)
    }

    src = dest
  }

  if (done) {
    let fin = false

    const autoDestroy =
      isStreamx(dest) || !!(dest._writableState && dest._writableState.autoDestroy)

    dest.on('error', (err) => {
      if (error === null) error = err
    })

    dest.on('finish', () => {
      fin = true
      if (!autoDestroy) done(error)
    })

    if (autoDestroy) {
      dest.on('close', () => done(error || (fin ? null : StreamError.PREMATURE_CLOSE())))
    }
  }

  return dest

  function errorHandle(s, rd, wr, onerror) {
    s.on('error', onerror)
    s.on('close', onclose)

    function onclose() {
      if (rd && s._readableState && !s._readableState.ended) {
        return onerror(StreamError.PREMATURE_CLOSE())
      }
      if (wr && s._writableState && !s._writableState.ended) {
        return onerror(StreamError.PREMATURE_CLOSE())
      }
    }
  }

  function onerror(err) {
    if (!err || error) return
    error = err

    for (const s of all) {
      s.destroy(err)
    }
  }
}

function echo(s) {
  return s
}

function isStream(stream) {
  return !!stream._readableState || !!stream._writableState
}

function isStreamx(stream) {
  return typeof stream._duplexState === 'number' && isStream(stream)
}

function isEnding(stream) {
  return !!stream._readableState && stream._readableState.ending
}

function isEnded(stream) {
  return !!stream._readableState && stream._readableState.ended
}

function isFinishing(stream) {
  return !!stream._writableState && stream._writableState.ending
}

function isFinished(stream) {
  return !!stream._writableState && stream._writableState.ended
}

function getStreamError(stream, opts = {}) {
  const err =
    (stream._readableState && stream._readableState.error) ||
    (stream._writableState && stream._writableState.error)

  // avoid implicit errors by default
  return !opts.all && StreamError.isStreamDestroyed(err) ? null : err
}

function isReadStreamx(stream) {
  return isStreamx(stream) && stream.readable
}

function isDisturbed(stream) {
  return (
    (stream._duplexState & OPENING) !== OPENING ||
    (stream._duplexState & DESTROYING) === DESTROYING ||
    (stream._duplexState & ACTIVE_OR_TICKING) !== 0
  )
}

function isTypedArray(data) {
  return typeof data === 'object' && data !== null && typeof data.byteLength === 'number'
}

function defaultByteLength(data) {
  return isTypedArray(data) ? data.byteLength : 1024
}

function noop() {}

function abort() {
  this.destroy(StreamError.ABORTED())
}

function isWritev(s) {
  return s._writev !== Writable.prototype._writev && s._writev !== Duplex.prototype._writev
}

module.exports = {
  pipeline,
  pipelinePromise,
  isStream,
  isStreamx,
  isEnding,
  isEnded,
  isFinishing,
  isFinished,
  isDisturbed,
  getStreamError,
  Stream,
  Writable,
  Readable,
  Duplex,
  Transform,
  // Export PassThrough for compatibility with Node.js core's stream module
  PassThrough
}
module.exports = class StreamError extends Error {
  constructor(msg, code, fn = StreamError) {
    super(msg)

    this.code = code

    if (Error.captureStackTrace) {
      Error.captureStackTrace(this, fn)
    }
  }

  static isStreamDestroyed(err) {
    return err && err.code === 'STREAM_DESTROYED'
  }

  static isPrematureClose(err) {
    return err && err.code === 'PREMATURE_CLOSE'
  }

  static isAborted(err) {
    return err && err.code === 'ABORTED'
  }

  static isBadArgument(err) {
    return err && err.code === 'BAD_ARGUMENT'
  }

  get name() {
    return 'StreamError'
  }

  static STREAM_DESTROYED() {
    return new StreamError('Stream was destroyed', 'STREAM_DESTROYED', StreamError.STREAM_DESTROYED)
  }

  static PREMATURE_CLOSE(msg = 'Premature close') {
    return new StreamError(msg, 'PREMATURE_CLOSE', StreamError.PREMATURE_CLOSE)
  }

  static ABORTED() {
    return new StreamError('Stream aborted', 'ABORTED', StreamError.ABORTED)
  }

  static BAD_ARGUMENT(msg = 'Bad argument') {
    return new StreamError(msg, 'BAD_ARGUMENT', StreamError.BAD_ARGUMENT)
  }
}
{
  "name": "streamx",
  "version": "2.28.0",
  "description": "An iteration of the Node.js core streams with a series of improvements",
  "main": "index.js",
  "exports": {
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    },
    "./package": "./package.json",
    "./errors": {
      "types": "./lib/errors.d.ts",
      "default": "./lib/errors"
    }
  },
  "dependencies": {
    "events-universal": "^1.0.0",
    "fast-fifo": "^1.3.2",
    "text-decoder": "^1.1.0"
  },
  "devDependencies": {
    "b4a": "^1.6.6",
    "brittle": "^3.1.1",
    "end-of-stream": "^1.4.4",
    "lunte": "^1.8.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^2.0.0"
  },
  "files": [
    "index.js",
    "index.d.ts",
    "runtime.d.ts",
    "lib"
  ],
  "scripts": {
    "format": "prettier . --write",
    "test": "brittle test/all.js && brittle-bare test/all.js",
    "lint": "prettier . --check && lunte"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/mafintosh/streamx.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/mafintosh/streamx/issues"
  },
  "homepage": "https://github.com/mafintosh/streamx"
}
const { Readable } = require('streamx')

module.exports = function (s, forks = 2) {
  const streams = new Array(forks)
  const status = new Array(forks).fill(true)

  let ended = false

  for (let i = 0; i < forks; i++) {
    streams[i] = new Readable({
      read (cb) {
        const check = !status[i]
        status[i] = true
        if (check && allReadable()) s.resume()
        cb(null)
      }
    })
  }

  s.on('end', function () {
    ended = true
    for (const stream of streams) stream.push(null)
  })

  s.on('error', function (err) {
    for (const stream of streams) stream.destroy(err)
  })

  s.on('close', function () {
    if (ended) return
    for (const stream of streams) stream.destroy()
  })

  s.on('data', function (data) {
    let needsPause = false
    for (let i = 0; i < streams.length; i++) {
      if (!(status[i] = streams[i].push(data))) {
        needsPause = true
      }
    }
    if (needsPause) s.pause()
  })

  return streams

  function allReadable () {
    for (let j = 0; j < status.length; j++) {
      if (!status[j]) return false
    }
    return true
  }
}
{
  "name": "teex",
  "version": "1.0.1",
  "description": "Turn a readable stream into multiple readable streamx streams",
  "main": "index.js",
  "dependencies": {
    "streamx": "^2.12.5"
  },
  "devDependencies": {
    "standard": "^14.3.4",
    "tape": "^5.0.1"
  },
  "scripts": {
    "test": "standard && tape test.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/mafintosh/teex.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/mafintosh/teex/issues"
  },
  "homepage": "https://github.com/mafintosh/teex"
}
const PassThroughDecoder = require('./lib/pass-through-decoder')
const UTF8Decoder = require('./lib/utf8-decoder')

module.exports = class TextDecoder {
  constructor(encoding = 'utf8') {
    this.encoding = normalizeEncoding(encoding)

    switch (this.encoding) {
      case 'utf8':
        this.decoder = new UTF8Decoder()
        break
      case 'utf16le':
      case 'base64':
        throw new Error('Unsupported encoding: ' + this.encoding)
      default:
        this.decoder = new PassThroughDecoder(this.encoding)
    }
  }

  get remaining() {
    return this.decoder.remaining
  }

  push(data) {
    if (typeof data === 'string') return data
    return this.decoder.decode(data)
  }

  // For Node.js compatibility
  write(data) {
    return this.push(data)
  }

  end(data) {
    let result = ''
    if (data) result = this.push(data)
    result += this.decoder.flush()
    return result
  }
}

function normalizeEncoding(encoding) {
  encoding = encoding.toLowerCase()

  switch (encoding) {
    case 'utf8':
    case 'utf-8':
      return 'utf8'
    case 'ucs2':
    case 'ucs-2':
    case 'utf16le':
    case 'utf-16le':
      return 'utf16le'
    case 'latin1':
    case 'binary':
      return 'latin1'
    case 'base64':
    case 'ascii':
    case 'hex':
      return encoding
    default:
      throw new Error('Unknown encoding: ' + encoding)
  }
}
const b4a = require('b4a')

module.exports = class PassThroughDecoder {
  constructor(encoding) {
    this.encoding = encoding
  }

  get remaining() {
    return 0
  }

  decode(data) {
    return b4a.toString(data, this.encoding)
  }

  flush() {
    return ''
  }
}
const b4a = require('b4a')

/**
 * https://encoding.spec.whatwg.org/#utf-8-decoder
 */
module.exports = class UTF8Decoder {
  constructor() {
    this._reset()
  }

  get remaining() {
    return this.bytesSeen
  }

  decode(data) {
    if (data.byteLength === 0) return ''

    if (this.bytesNeeded === 0 && trailingIncomplete(data, 0) === 0) {
      this.bytesSeen = trailingBytesSeen(data)
      return b4a.toString(data, 'utf8')
    }

    let result = ''
    let start = 0

    if (this.bytesNeeded > 0) {
      while (start < data.byteLength) {
        const byte = data[start]

        if (byte < this.lowerBoundary || byte > this.upperBoundary) {
          result += '\ufffd'
          this._reset()
          break
        }

        this.lowerBoundary = 0x80
        this.upperBoundary = 0xbf
        this.codePoint = (this.codePoint << 6) | (byte & 0x3f)
        this.bytesSeen++
        start++

        if (this.bytesSeen === this.bytesNeeded) {
          result += String.fromCodePoint(this.codePoint)
          this._reset()
          break
        }
      }

      if (this.bytesNeeded > 0) return result
    }

    const trailing = trailingIncomplete(data, start)
    const end = data.byteLength - trailing

    if (end > start) result += b4a.toString(data, 'utf8', start, end)

    for (let i = end; i < data.byteLength; i++) {
      const byte = data[i]

      if (this.bytesNeeded === 0) {
        if (byte <= 0x7f) {
          this.bytesSeen = 0
          result += String.fromCharCode(byte)
        } else if (byte >= 0xc2 && byte <= 0xdf) {
          this.bytesNeeded = 2
          this.bytesSeen = 1
          this.codePoint = byte & 0x1f
        } else if (byte >= 0xe0 && byte <= 0xef) {
          if (byte === 0xe0) this.lowerBoundary = 0xa0
          else if (byte === 0xed) this.upperBoundary = 0x9f
          this.bytesNeeded = 3
          this.bytesSeen = 1
          this.codePoint = byte & 0xf
        } else if (byte >= 0xf0 && byte <= 0xf4) {
          if (byte === 0xf0) this.lowerBoundary = 0x90
          else if (byte === 0xf4) this.upperBoundary = 0x8f
          this.bytesNeeded = 4
          this.bytesSeen = 1
          this.codePoint = byte & 0x7
        } else {
          this.bytesSeen = 1
          result += '\ufffd'
        }

        continue
      }

      if (byte < this.lowerBoundary || byte > this.upperBoundary) {
        result += '\ufffd'
        i--
        this._reset()

        continue
      }

      this.lowerBoundary = 0x80
      this.upperBoundary = 0xbf

      this.codePoint = (this.codePoint << 6) | (byte & 0x3f)
      this.bytesSeen++

      if (this.bytesSeen === this.bytesNeeded) {
        result += String.fromCodePoint(this.codePoint)
        this._reset()
      }
    }

    return result
  }

  flush() {
    const result = this.bytesNeeded > 0 ? '\ufffd' : ''
    this._reset()
    return result
  }

  _reset() {
    this.codePoint = 0
    this.bytesNeeded = 0
    this.bytesSeen = 0
    this.lowerBoundary = 0x80
    this.upperBoundary = 0xbf
  }
}

function trailingIncomplete(data, start) {
  const len = data.byteLength
  if (len <= start) return 0

  const limit = Math.max(start, len - 4)

  let i = len - 1
  while (i > limit && (data[i] & 0xc0) === 0x80) i--

  if (i < start) return 0

  const byte = data[i]

  let needed
  if (byte <= 0x7f) return 0
  if (byte >= 0xc2 && byte <= 0xdf) needed = 2
  else if (byte >= 0xe0 && byte <= 0xef) needed = 3
  else if (byte >= 0xf0 && byte <= 0xf4) needed = 4
  else return 0

  const available = len - i
  return available < needed ? available : 0
}

function trailingBytesSeen(data) {
  const len = data.byteLength
  if (len === 0) return 0

  const last = data[len - 1]

  if (last <= 0x7f) return 0
  if ((last & 0xc0) !== 0x80) return 1

  const limit = Math.max(0, len - 4)

  let i = len - 2
  while (i >= limit && (data[i] & 0xc0) === 0x80) i--

  if (i < 0) return 1

  const first = data[i]

  let needed
  if (first >= 0xc2 && first <= 0xdf) needed = 2
  else if (first >= 0xe0 && first <= 0xef) needed = 3
  else if (first >= 0xf0 && first <= 0xf4) needed = 4
  else return 1

  if (len - i !== needed) return 1

  if (needed >= 3) {
    const second = data[i + 1]
    if (first === 0xe0 && second < 0xa0) return 1
    if (first === 0xed && second > 0x9f) return 1
    if (first === 0xf0 && second < 0x90) return 1
    if (first === 0xf4 && second > 0x8f) return 1
  }

  return 0
}
{
  "name": "text-decoder",
  "version": "1.2.7",
  "description": "Streaming text decoder that preserves multibyte Unicode characters",
  "exports": {
    "./package": "./package.json",
    ".": "./index.js"
  },
  "files": [
    "index.js",
    "lib"
  ],
  "scripts": {
    "format": "prettier --write .",
    "lint": "prettier --check . && lunte",
    "test": "npm run test:node && npm run test:bare",
    "test:node": "brittle-node test.js",
    "test:bare": "brittle-bare test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/text-decoder.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/text-decoder/issues"
  },
  "homepage": "https://github.com/holepunchto/text-decoder#readme",
  "dependencies": {
    "b4a": "^1.6.4"
  },
  "devDependencies": {
    "brittle": "^3.3.2",
    "lunte": "^1.3.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^1.0.0"
  }
}
const b4a = require('b4a')

unslab.all = all
unslab.is = is

module.exports = unslab

function unslab (buf) {
  if (buf === null || buf.buffer.byteLength === buf.byteLength) return buf
  const copy = b4a.allocUnsafeSlow(buf.byteLength)
  copy.set(buf, 0)
  return copy
}

function is (buf) {
  return buf.buffer.byteLength !== buf.byteLength
}

function all (list) {
  let size = 0
  for (let i = 0; i < list.length; i++) {
    const buf = list[i]
    size += buf === null || buf.buffer.byteLength === buf.byteLength ? 0 : buf.byteLength
  }

  const copy = b4a.allocUnsafeSlow(size)
  const result = new Array(list.length)

  let offset = 0
  for (let i = 0; i < list.length; i++) {
    let buf = list[i]

    if (buf !== null && buf.buffer.byteLength !== buf.byteLength) {
      copy.set(buf, offset)
      buf = copy.subarray(offset, offset += buf.byteLength)
    }

    result[i] = buf
  }

  return result
}
{
  "name": "unslab",
  "version": "1.3.0",
  "description": "Unslab some slab'ed buffers",
  "main": "index.js",
  "files": [
    "index.js"
  ],
  "dependencies": {
    "b4a": "^1.6.6"
  },
  "devDependencies": {
    "brittle": "^3.5.2",
    "standard": "^17.1.0"
  },
  "scripts": {
    "test": "standard && brittle test.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/holepunchto/unslab.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/unslab/issues"
  },
  "homepage": "https://github.com/holepunchto/unslab"
}
module.exports = read

var MSB = 0x80
  , REST = 0x7F

function read(buf, offset) {
  var res    = 0
    , offset = offset || 0
    , shift  = 0
    , counter = offset
    , b
    , l = buf.length

  do {
    if (counter >= l) {
      read.bytes = 0
      throw new RangeError('Could not decode varint')
    }
    b = buf[counter++]
    res += shift < 28
      ? (b & REST) << shift
      : (b & REST) * Math.pow(2, shift)
    shift += 7
  } while (b >= MSB)

  read.bytes = counter - offset

  return res
}
module.exports = encode

var MSB = 0x80
  , REST = 0x7F
  , MSBALL = ~REST
  , INT = Math.pow(2, 31)

function encode(num, out, offset) {
  out = out || []
  offset = offset || 0
  var oldOffset = offset

  while(num >= INT) {
    out[offset++] = (num & 0xFF) | MSB
    num /= 128
  }
  while(num & MSBALL) {
    out[offset++] = (num & 0xFF) | MSB
    num >>>= 7
  }
  out[offset] = num | 0
  
  encode.bytes = offset - oldOffset + 1
  
  return out
}
module.exports = {
    encode: require('./encode.js')
  , decode: require('./decode.js')
  , encodingLength: require('./length.js')
}

var N1 = Math.pow(2,  7)
var N2 = Math.pow(2, 14)
var N3 = Math.pow(2, 21)
var N4 = Math.pow(2, 28)
var N5 = Math.pow(2, 35)
var N6 = Math.pow(2, 42)
var N7 = Math.pow(2, 49)
var N8 = Math.pow(2, 56)
var N9 = Math.pow(2, 63)

module.exports = function (value) {
  return (
    value < N1 ? 1
  : value < N2 ? 2
  : value < N3 ? 3
  : value < N4 ? 4
  : value < N5 ? 5
  : value < N6 ? 6
  : value < N7 ? 7
  : value < N8 ? 8
  : value < N9 ? 9
  :              10
  )
}
{
  "name": "varint",
  "version": "5.0.0",
  "description": "protobuf-style varint bytes - use msb to create integer values of varying sizes",
  "main": "index.js",
  "scripts": {
    "test": "node test.js"
  },
  "repository": {
    "type": "git",
    "url": "git://github.com/chrisdickinson/varint.git"
  },
  "keywords": [
    "varint",
    "protobuf",
    "encode",
    "decode"
  ],
  "author": "Chris Dickinson <chris@neversaw.us>",
  "license": "MIT",
  "devDependencies": {
    "tape": "~2.12.3"
  }
}
const { runtime, platform, arch, version } =
  typeof Bare !== 'undefined'
    ? {
        runtime: 'bare',
        platform: global.Bare.platform,
        arch: global.Bare.arch,
        version: global.Bare.version
      }
    : typeof process !== 'undefined'
      ? {
          runtime: 'node',
          platform: global.process.platform,
          arch: global.process.arch,
          version: global.process.version
        }
      : typeof Window !== 'undefined'
        ? { runtime: 'browser', platform: 'unknown', arch: 'unknown', version: 'unknown' }
        : { runtime: 'unknown', platform: 'unknown', arch: 'unknown', version: 'unknown' }

exports.runtime = runtime
exports.version = version
exports.platform = platform
exports.arch = arch
exports.isBare = runtime === 'bare'
exports.isBareKit = exports.isBare && typeof BareKit !== 'undefined'
exports.isPear = typeof Pear !== 'undefined'
exports.isNode = runtime === 'node'
exports.isBrowser = runtime === 'browser'
exports.isWindows = platform === 'win32'
exports.isLinux = platform === 'linux'
exports.isMac = platform === 'darwin'
exports.isIOS = platform === 'ios' || platform === 'ios-simulator'
exports.isAndroid = platform === 'android'
exports.isElectron = typeof process !== 'undefined' && !!global.process.versions?.electron
exports.isElectronRenderer = exports.isElectron && global.process.type === 'renderer'
exports.isElectronWorker = exports.isElectron && global.process.type === 'worker'
{
  "name": "which-runtime",
  "version": "1.4.0",
  "description": "Detect if you are in Bare or Node and which os etc",
  "main": "index.js",
  "files": [
    "index.js"
  ],
  "scripts": {
    "format": "prettier --write .",
    "lint": "prettier --check . && lunte .",
    "test": "npm run test:node && npm run test:bare",
    "test:node": "brittle-node test.js",
    "test:bare": "brittle-bare test.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/holepunchto/which-runtime.git"
  },
  "author": "Holepunch Inc.",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/which-runtime/issues"
  },
  "homepage": "https://github.com/holepunchto/which-runtime",
  "devDependencies": {
    "brittle": "^3.0.0",
    "lunte": "^1.4.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^2.0.0"
  }
}
const b4a = require('b4a')

const ALPHABET = 'ybndrfg8ejkmcpqxot1uwisza345h769'
const MIN = 0x31 // 1
const MAX = 0x7a // z
const REVERSE = new Int8Array(1 + MAX - MIN)

REVERSE.fill(-1)

for (let i = 0; i < ALPHABET.length; i++) {
  const v = ALPHABET.charCodeAt(i) - MIN
  REVERSE[v] = i
}

exports.encode = encode
exports.decode = decode
exports.ALPHABET = ALPHABET

function decode (s, out) {
  let pb = 0
  let ps = 0

  const r = s.length & 7
  const q = (s.length - r) / 8

  if (!out) out = b4a.allocUnsafe(Math.ceil(s.length * 5 / 8))

  // 0 5 2 7 4 1 6 3 (+5 mod 8)
  for (let i = 0; i < q; i++) {
    const a = quintet(s, ps++)
    const b = quintet(s, ps++)
    const c = quintet(s, ps++)
    const d = quintet(s, ps++)
    const e = quintet(s, ps++)
    const f = quintet(s, ps++)
    const g = quintet(s, ps++)
    const h = quintet(s, ps++)

    out[pb++] = (a << 3) | (b >>> 2)
    out[pb++] = ((b & 0b11) << 6) | (c << 1) | (d >>> 4)
    out[pb++] = ((d & 0b1111) << 4) | (e >>> 1)
    out[pb++] = ((e & 0b1) << 7) | (f << 2) | (g >>> 3)
    out[pb++] = ((g & 0b111) << 5) | h
  }

  if (r === 0) return out.subarray(0, pb)

  const a = quintet(s, ps++)
  const b = quintet(s, ps++)

  out[pb++] = (a << 3) | (b >>> 2)

  if (r <= 2) return out.subarray(0, pb)

  const c = quintet(s, ps++)
  const d = quintet(s, ps++)

  out[pb++] = ((b & 0b11) << 6) | (c << 1) | (d >>> 4)

  if (r <= 4) return out.subarray(0, pb)

  const e = quintet(s, ps++)

  out[pb++] = ((d & 0b1111) << 4) | (e >>> 1)

  if (r <= 5) return out.subarray(0, pb)

  const f = quintet(s, ps++)
  const g = quintet(s, ps++)

  out[pb++] = ((e & 0b1) << 7) | (f << 2) | (g >>> 3)

  if (r <= 7) return out.subarray(0, pb)

  const h = quintet(s, ps++)

  out[pb++] = ((g & 0b111) << 5) | h

  return out.subarray(0, pb)
}

function encode (buf) {
  if (typeof buf === 'string') buf = b4a.from(buf)

  const max = buf.byteLength * 8

  let s = ''

  for (let p = 0; p < max; p += 5) {
    const i = p >>> 3
    const j = p & 7

    if (j <= 3) {
      s += ALPHABET[(buf[i] >>> (3 - j)) & 0b11111]
      continue
    }

    const of = j - 3
    const h = (buf[i] << of) & 0b11111
    const l = (i >= buf.byteLength ? 0 : buf[i + 1]) >>> (8 - of)

    s += ALPHABET[h | l]
  }

  return s
}

function quintet (s, i) {
  if (i > s.length) {
    return 0
  }

  const v = s.charCodeAt(i)

  if (v < MIN || v > MAX) {
    throw Error('Invalid character in base32 input: "' + s[i] + '" at position ' + i)
  }

  const bits = REVERSE[v - MIN]

  if (bits === -1) {
    throw Error('Invalid character in base32 input: "' + s[i] + '" at position ' + i)
  }

  return bits
}
{
  "name": "z32",
  "version": "1.1.0",
  "description": "Encode & decode z-base32",
  "main": "index.js",
  "dependencies": {
    "b4a": "^1.5.3"
  },
  "devDependencies": {
    "base-x": "^4.0.0",
    "base32": "0.0.7",
    "brittle": "^3.1.3",
    "nanobench": "^3.0.0",
    "rfc4648": "^1.5.2",
    "standard": "^17.0.0"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/mafintosh/z32.git"
  },
  "scripts": {
    "test": "standard && brittle test.js",
    "bench": "node benchmark.js"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/mafintosh/z32/issues"
  },
  "homepage": "https://github.com/mafintosh/z32"
}
{
  "name": "hyper-overlay",
  "version": "0.3.13",
  "description": "Content-addressed P2P file distribution over Protomux — FastCDC chunking, multi-source fetch, resumable transfers, zero blob duplication. Files live on disk; the overlay is a thin index and transfer protocol.",
  "type": "module",
  "main": "index.js",
  "imports": {
    "#fs": {
      "bare": "bare-fs",
      "default": "fs"
    },
    "#path": {
      "bare": "bare-path",
      "default": "path"
    },
    "#os": {
      "bare": "bare-os",
      "default": "os"
    },
    "#zlib": {
      "bare": "bare-zlib",
      "default": "zlib"
    }
  },
  "exports": {
    ".": "./index.js",
    "./overlay": "./lib/overlay.js",
    "./protocol": "./lib/protocol.js",
    "./messages": "./lib/messages.js",
    "./chunker": "./lib/chunker.js",
    "./chunk-scheduler": "./lib/chunk-scheduler.js",
    "./transfer": "./lib/transfer.js",
    "./file-index": "./lib/file-index.js",
    "./sync-engine": "./lib/sync-engine.js",
    "./tree": "./lib/tree.js",
    "./pointer": "./lib/pointer.js",
    "./ignore": "./lib/ignore.js",
    "./manifest": "./lib/manifest.js",
    "./package.json": "./package.json"
  },
  "scripts": {
    "test": "brittle-bare test/*.test.js test/integration/*.test.js",
    "test:node": "brittle-node test/*.test.js"
  },
  "engines": {
    "bare": ">=1.16.0"
  },
  "license": "AGPL-3.0",
  "keywords": [
    "p2p",
    "hypercore",
    "hyperbee",
    "protomux",
    "overlay",
    "content-addressed",
    "deduplication",
    "file-transfer",
    "fastcdc",
    "chunking",
    "multi-source",
    "resumable",
    "bare"
  ],
  "dependencies": {
    "b4a": "^1.0.0",
    "bare-fs": "^4.7.2",
    "bare-os": "^3.8.7",
    "bare-path": "^3.0.0",
    "bare-zlib": "^1.3.3",
    "compact-encoding": "^3.2.0",
    "corestore": "^7.0.0",
    "hyperbee": "^2.0.0",
    "hypercore-crypto": "^3.0.0",
    "protomux": "^3.0.0",
    "ready-resource": "^1.0.0",
    "sodium-universal": "^5.0.1",
    "streamx": "^2.0.0"
  },
  "devDependencies": {
    "brittle": "^3.0.0",
    "hyperswarm": "^4.0.0",
    "@hyperswarm/testnet": "^3.1.4"
  }
}
const b4a = require('b4a')
const Hypercore = require('hypercore')
const EventEmitter = require('events')
const { RangeStream } = require('./lib/ranges.js')
const { DiffStream } = require('./lib/diff.js')
const { ChangesStream } = require('./lib/changes.js')
const NodeCache = require('./lib/cache.js')
const WriteBatch = require('./lib/write.js')
const CoreContext = require('./lib/context.js')
const SessionConfig = require('./lib/session-config.js')
const { inflate, inflateValue } = require('./lib/inflate.js')
const { EMPTY, T } = require('./lib/tree.js')

class Hyperbee extends EventEmitter {
  constructor(store, opts = {}) {
    super()

    const {
      t = T,
      key = null,
      encryption = null,
      getEncryptionProvider = toEncryptionProvider(encryption),
      maxCacheSize = 4096,
      config = new SessionConfig([], 0, true, null),
      activeRequests = config.activeRequests,
      timeout = config.timeout,
      wait = config.wait,
      trace = config.trace,
      core = getRootCore(store, key, getEncryptionProvider),
      context = new CoreContext(
        store,
        core,
        new NodeCache(maxCacheSize),
        core,
        getEncryptionProvider
      ),
      root = null,
      view = false,
      writable = true,
      unbatch = 0,
      autoUpdate = !writable && !view,
      preload = null
    } = opts

    this.store = store
    this.root = root
    this.t = t
    this.context = context
    this.config = config.sub(activeRequests, timeout, wait, trace)
    this.view = view
    this.writable = writable
    this.unbatch = unbatch

    this.autoUpdate = autoUpdate
    this.preload = preload

    this.ready().catch(noop)
  }

  static isHyperbee(bee) {
    return bee instanceof Hyperbee
  }

  head() {
    if (!this.root) return null
    if (this.root === EMPTY) return { length: 0, key: this.context.core.key }
    return { length: this.root.seq + 1, key: this.root.context.core.key }
  }

  get cache() {
    return this.context.cache
  }

  get core() {
    return this.context.core
  }

  get opening() {
    return this.store.opening
  }

  get opened() {
    return this.store.opened
  }

  get closing() {
    return this.store.closing
  }

  get closed() {
    return this.store.closed
  }

  replicate(...opts) {
    return this.store.replicate(...opts)
  }

  _makeView(context, root, writable, unbatch) {
    return new Hyperbee(this.store, {
      t: this.t,
      config: this.config,
      core: context.core,
      activeRequests: [],
      context,
      root,
      view: true,
      writable,
      unbatch,
      autoUpdate: false,
      preload: this.preload
    })
  }

  checkout({ length = this.core.length, key = null, writable = false } = {}) {
    const context = key ? this.context.getContextByKey(key) : this.context
    const root = length === 0 ? EMPTY : context.createTreeNode(0, length - 1, 0, false, null)
    return this._makeView(context, root, writable, 0)
  }

  move({ length = this.core.length, key = null, writable = this.writable } = {}) {
    this.context = key ? this.context.getContextByKey(key) : this.context
    this.writable = writable

    this._setRoot(this._nodeAtSeq(length - 1), true)
  }

  snapshot() {
    return this._makeView(this.context, this.root, false, 0)
  }

  undo(n) {
    return this._makeView(this.context, this.root, true, n)
  }

  write(opts = {}) {
    if (!this.writable) throw new Error('Not writable')
    if (!opts.key) {
      opts.key = this.root ? this.head().key : null
    }
    return new WriteBatch(this, opts)
  }

  update(root = null) {
    if (root === null) {
      if (this.context.lock.locked) return
      root = this._lastNodeInCore()
    }
    this._setRoot(root, true)
  }

  _lastNodeInCore() {
    return this._nodeAtSeq(this.context.core.length - 1)
  }

  _nodeAtSeq(seq) {
    return seq < 0 ? EMPTY : this.context.createTreeNode(0, seq, 0, false, null)
  }

  async ready() {
    if (!this.core.opened) await this.core.ready()

    if (this.root) return
    if (this.preload) await this.preload()
    if (this.root) return

    this._setRoot(this._lastNodeInCore(), false)
    if (this.autoUpdate) {
      this.core.on('append', this.update.bind(this, null))
    }

    this.emit('ready')
  }

  async close() {
    if (!this.root) await this.ready()
    Hypercore.destroyRequests(this.config.activeRequests, null)
    if (this.view) return
    if (this.store.closing) return this.store.close()
    await this.store.close()
    this.emit('close')
  }

  createReadStream(options) {
    return new RangeStream(this, options)
  }

  createDiffStream(right, options) {
    return new DiffStream(this, right, options)
  }

  createChangesStream(options) {
    return new ChangesStream(this, options)
  }

  async peek(range = {}) {
    const rs = new RangeStream(this, { ...range, limit: 1 })
    let entry = null
    for await (const data of rs) entry = data
    return entry
  }

  download(range = {}) {
    const rs = new RangeStream(this, range)
    rs.resume()
    return new Promise((resolve, reject) => {
      rs.once('error', reject)
      rs.once('close', resolve)
    })
  }

  bump(ptr) {
    if (ptr.changed) return ptr.value
    this.context.cache.bump(ptr)
    this.context.cache.gc()
    return ptr.value
  }

  async compat() {
    await this.ready()
    return this.context.getBlockType(0, this.config)
  }

  async inflate(ptr, config) {
    if (!ptr.value) {
      await inflate(ptr, config)
    }
    this.bump(ptr)
    return ptr.value
  }

  async finalizeKeyPointer(key, config) {
    const value = key.value || (await inflateValue(key, config))

    return {
      core: key.context.getCore(key.core),
      offset: key.offset,
      seq: key.seq,
      key: key.key,
      value
    }
  }

  async bootstrap(config) {
    if (!this.root) await this.ready()
    if (this.unbatch) await this._rollback(config)
    return this.root === EMPTY ? null : this.root
  }

  async _rollback(config) {
    const expected = this.unbatch

    let n = expected
    let length = this.root === EMPTY ? 0 : this.root.seq + 1
    let context = this.context

    while (n > 0 && length > 0 && expected === this.unbatch) {
      const seq = length - 1
      const blk = await context.getBlock(seq, 0, config)

      if (!blk.previous) {
        length = 0
        break
      }

      context = await context.getContext(blk.previous.core, config)
      length = blk.previous.seq + 1
      n--
    }

    if (expected === this.unbatch) {
      this.context = context
      this._setRoot(this._nodeAtSeq(length - 1), true)
    }
  }

  _setRoot(root, emit) {
    if (root === null || !root.equivalentTo(this.root)) {
      this.root = root
      if (emit) this.emit('update')
    }
    this.unbatch = 0
  }

  async get(key, opts) {
    const config = this.config.options(opts)

    let ptr = await this.bootstrap(config)
    if (!ptr) return null

    while (true) {
      const v = ptr.value ? this.bump(ptr) : await this.inflate(ptr, config)

      let s = 0
      let e = v.keys.length
      let c = 0

      while (s < e) {
        const mid = (s + e) >> 1
        const m = v.keys.get(mid)

        c = b4a.compare(key, m.key)

        if (c === 0) return this.finalizeKeyPointer(m, config)

        if (c < 0) e = mid
        else s = mid + 1
      }

      if (!v.children.length) return null

      const i = c < 0 ? e : s
      ptr = v.children.get(i)
    }
  }
}

module.exports = Hyperbee

function noop() {}

function toEncryptionProvider(encryption) {
  if (encryption) return (key) => encryption
  return () => null
}

function getRootCore(store, key, getEncryptionProvider) {
  const encryption = getEncryptionProvider(key)

  return key
    ? store.get({ key, encryption, inflightRange: [256, 512] })
    : store.get({ key, name: 'bee', encryption, inflightRange: [256, 512] })
}
module.exports = class NodeCache {
  constructor(maxSize) {
    this.size = 0
    this.maxSize = maxSize
    this.latest = null
    this.retained = 0
    this.byId = new Map()
    this.dirty = []
  }

  oldest() {
    return this.latest ? this.latest.next : null
  }

  empty() {
    while (this.size > 0) {
      const old = this.oldest()
      this.remove(old)
      old.value = null
    }
  }

  flush() {
    while (this.dirty.length > 0) {
      this.dirty[this.dirty.length - 1].unmark()
    }

    this.retained++
    this.gc()
  }

  gc() {
    while (this.size > this.maxSize) {
      const old = this.oldest()
      if (old.retained > this.retained) break
      this.remove(old)
      old.value = null
    }
  }

  bump(node) {
    if (node === this.latest) return

    if (node.next === null && node.prev === null) {
      this.byId.set(cacheId(node), node)
    }

    if (node.prev) this.remove(node)
    this.size++

    if (!this.latest) {
      node.prev = node.next = node
      this.latest = node
    } else {
      node.prev = this.latest
      node.next = this.latest.next
      this.latest.next.prev = node
      this.latest.next = node
      this.latest = node
    }
  }

  get(node) {
    return this.byId.get(cacheId(node)) || null
  }

  remove(node) {
    if (node.prev) {
      this.size--

      if (node === this.latest) {
        this.latest = node.next === node ? null : node.next
      }

      node.prev.next = node.next
      node.next.prev = node.prev
    }

    node.prev = node.next = null

    const id = cacheId(node)
    if (this.byId.get(id) === node) {
      this.byId.delete(id)
    }
  }

  *[Symbol.iterator]() {
    const node = this.latest
    if (node === null) return

    let next = node

    do {
      yield next
      next = next.next
    } while (node !== next)
  }
}

function cacheId(node) {
  const id = node.context.getCore(node.core).id
  return id + '@' + node.seq + '.' + node.offset
}
const { Readable } = require('streamx')
const Hypercore = require('hypercore')

class ChangesStream extends Readable {
  constructor(tree, opts = {}) {
    const { highWaterMark, head = null } = opts
    super({ eagerOpen: true, highWaterMark })

    this.tree = tree

    this.head = head
    this.context = null
    this.config = tree.config.detach(opts)
  }

  async _openp() {
    await this.tree.bootstrap(this.config)
    if (this.head === null) this.head = this.tree.head()
    if (this.head !== null) this.context = this.tree.context.getContextByKey(this.head.key)
  }

  async _readp() {
    if (!this.context || this.head.length === 0) {
      this.push(null)
      return
    }

    const data = {
      head: this.head,
      tail: null,
      batch: []
    }

    const seq = this.head.length - 1
    const blk = await this.context.getBlock(seq, 0, this.config)
    const batchStart = seq - blk.batch.start
    const remaining = new Array(blk.batch.start)

    for (let i = 0; i < remaining.length; i++) {
      remaining[i] = this.context.getBlock(batchStart + i, 0, this.config)
    }

    for (const blk of await Promise.all(remaining)) data.batch.push(blk)
    data.batch.push(blk)

    if (!blk.previous) {
      this.head = null
      this.context = null
      this.push(data)
      return
    }

    this.context = await this.context.getContext(blk.previous.core, this.config)
    this.head = data.tail = { key: this.context.core.key, length: blk.previous.seq + 1 }

    this.push(data)
  }

  async _open(cb) {
    try {
      await this._openp()
    } catch (err) {
      cb(err)
      return
    }

    cb(null)
  }

  async _read(cb) {
    try {
      await this._readp()
    } catch (err) {
      cb(err)
      return
    }

    cb(null)
  }

  _predestroy() {
    Hypercore.destroyRequests(this.config.activeRequests, null)
  }
}

exports.ChangesStream = ChangesStream
// ported from the old protocol buffer impl in old bee

const encodings = require('protocol-buffers-encodings')
const b4a = require('b4a')
const { OP_COHORT, OP_INSERT } = require('./compression')
const varint = encodings.varint
const skip = encodings.skip

exports.t = 5

exports.decode = decodeCompat
exports.encode = encodeCompat

function decodeCompat(buffer, seq) {
  const node = buffer.length > 1 ? decodeNode(buffer) : null
  const index = node ? decodeYoloIndex(node.index) : { levels: [] }
  const morphed = {
    type: 10,
    checkpoint: 0,
    batch: { start: 0, end: 0 },
    previous: seq > 1 ? { core: 0, seq: seq - 1 } : null,
    metadata: null,
    tree: [],
    keys: node ? [{ key: node.key, value: node.value, valuePointer: null }] : [],
    values: [],
    cohorts: [],
    t: exports.t
  }

  for (const lvl of index.levels) {
    const t = { keys: [], children: [] }

    if (lvl.keys.length) {
      const cohort = []
      const offset = morphed.cohorts.push(cohort) - 1

      for (let i = 0; i < lvl.keys.length; i++) {
        const seq = lvl.keys[i]
        cohort.push({ type: OP_INSERT, index: i, pointer: { core: 0, seq, offset: 0 } })
      }

      t.keys.push({ type: OP_COHORT, index: 0, pointer: { core: 0, seq, offset } })
    }

    if (lvl.children.length) {
      const cohort = []
      const offset = morphed.cohorts.push(cohort) - 1

      for (let i = 0; i < lvl.children.length; i += 2) {
        const seq = lvl.children[i]
        const offset = lvl.children[i + 1]
        cohort.push({ type: OP_INSERT, index: i / 2, pointer: { core: 0, seq, offset } })
      }

      t.children.push({ type: OP_COHORT, index: 0, pointer: { core: 0, seq, offset } })
    }

    morphed.tree.push(t)
  }

  return morphed
}

function encodeCompat(node) {
  const index = { levels: [] }

  if (node.tree) {
    for (const t of node.tree) {
      const lvl = { keys: [], children: [] }
      for (const k of t.keys) {
        for (const kk of node.cohorts[k.pointer.offset]) {
          lvl.keys.push(kk.pointer.seq)
        }
      }
      for (const c of t.children) {
        for (const cc of node.cohorts[c.pointer.offset]) {
          lvl.children.push(cc.pointer.seq, cc.pointer.offset)
        }
      }
      index.levels.push(lvl)
    }
  }

  const bufIndex = b4a.allocUnsafe(encodingLengthYoloIndex(index))
  encodeYoloIndex(index, bufIndex, 0)

  const n = {
    key: node.keys[0].key,
    value: node.keys[0].value,
    index: bufIndex
  }

  const buf = b4a.allocUnsafe(encodingLengthNode(n))
  return encodeNode(n, buf, 0)
}

function decodeLevel(buf, offset, end) {
  if (!offset) offset = 0
  if (!end) end = buf.length
  if (!(end <= buf.length && offset <= buf.length)) throw new Error('Decoded message is not valid')

  const oldOffset = offset

  const obj = {
    keys: [],
    children: []
  }

  while (true) {
    if (end <= offset) {
      decodeLevel.bytes = offset - oldOffset
      return obj
    }
    const prefix = varint.decode(buf, offset)
    offset += varint.decode.bytes
    const tag = prefix >> 3
    switch (tag) {
      case 1: {
        let packedEnd = varint.decode(buf, offset)
        offset += varint.decode.bytes
        packedEnd += offset
        while (offset < packedEnd) {
          obj.keys.push(encodings.varint.decode(buf, offset))
          offset += encodings.varint.decode.bytes
        }
        break
      }
      case 2: {
        let packedEnd = varint.decode(buf, offset)
        offset += varint.decode.bytes
        packedEnd += offset
        while (offset < packedEnd) {
          obj.children.push(encodings.varint.decode(buf, offset))
          offset += encodings.varint.decode.bytes
        }
        break
      }
      default: {
        offset = skip(prefix & 7, buf, offset)
      }
    }
  }
}

function encodingLengthLevel(obj) {
  let length = 0
  if (obj.keys) {
    let packedLen = 0
    for (let i = 0; i < obj.keys.length; i++) {
      if (!obj.keys[i]) continue
      const len = encodings.varint.encodingLength(obj.keys[i])
      packedLen += len
    }
    if (packedLen) {
      length += 1 + packedLen + varint.encodingLength(packedLen)
    }
  }
  if (obj.children) {
    let packedLen = 0
    for (let i = 0; i < obj.children.length; i++) {
      if (!obj.children[i]) continue
      const len = encodings.varint.encodingLength(obj.children[i])
      packedLen += len
    }
    if (packedLen) {
      length += 1 + packedLen + varint.encodingLength(packedLen)
    }
  }
  return length
}

function encodeLevel(obj, buf, offset) {
  if (!offset) offset = 0
  const oldOffset = offset
  if (obj.keys) {
    let packedLen = 0
    for (let i = 0; i < obj.keys.length; i++) {
      if (!obj.keys[i]) continue
      packedLen += encodings.varint.encodingLength(obj.keys[i])
    }
    if (packedLen) {
      buf[offset++] = 10
      varint.encode(packedLen, buf, offset)
      offset += varint.encode.bytes
    }
    for (let i = 0; i < obj.keys.length; i++) {
      if (!obj.keys[i]) continue
      encodings.varint.encode(obj.keys[i], buf, offset)
      offset += encodings.varint.encode.bytes
    }
  }
  if (obj.children) {
    let packedLen = 0
    for (let i = 0; i < obj.children.length; i++) {
      if (!obj.children[i]) continue
      packedLen += encodings.varint.encodingLength(obj.children[i])
    }
    if (packedLen) {
      buf[offset++] = 18
      varint.encode(packedLen, buf, offset)
      offset += varint.encode.bytes
    }
    for (let i = 0; i < obj.children.length; i++) {
      if (!obj.children[i]) continue
      encodings.varint.encode(obj.children[i], buf, offset)
      offset += encodings.varint.encode.bytes
    }
  }
  encodeLevel.bytes = offset - oldOffset
  return buf
}

function decodeYoloIndex(buf, offset, end) {
  if (!offset) offset = 0
  if (!end) end = buf.length
  if (!(end <= buf.length && offset <= buf.length)) throw new Error('Decoded message is not valid')
  const oldOffset = offset
  const obj = {
    levels: []
  }
  while (true) {
    if (end <= offset) {
      decodeYoloIndex.bytes = offset - oldOffset
      return obj
    }
    const prefix = varint.decode(buf, offset)
    offset += varint.decode.bytes
    const tag = prefix >> 3
    switch (tag) {
      case 1: {
        const len = varint.decode(buf, offset)
        offset += varint.decode.bytes
        obj.levels.push(decodeLevel(buf, offset, offset + len))
        offset += decodeLevel.bytes
        break
      }
      default: {
        offset = skip(prefix & 7, buf, offset)
      }
    }
  }
}

function encodingLengthYoloIndex(obj) {
  let length = 0
  if (obj.levels) {
    for (let i = 0; i < obj.levels.length; i++) {
      if (!obj.levels[i]) continue
      const len = encodingLengthLevel(obj.levels[i])
      length += varint.encodingLength(len)
      length += 1 + len
    }
  }
  return length
}

function encodeYoloIndex(obj, buf, offset) {
  if (!offset) offset = 0
  const oldOffset = offset
  if (obj.levels) {
    for (let i = 0; i < obj.levels.length; i++) {
      if (!obj.levels[i]) continue
      buf[offset++] = 10
      const len = encodingLengthLevel(obj.levels[i])
      varint.encode(len, buf, offset)
      offset += varint.encode.bytes
      encodeLevel(obj.levels[i], buf, offset)
      offset += encodeLevel.bytes
    }
  }
  encodeYoloIndex.bytes = offset - oldOffset
  return buf
}

function decodeNode(buf, offset, end) {
  if (!offset) offset = 0
  if (!end) end = buf.length
  if (!(end <= buf.length && offset <= buf.length)) throw new Error('Decoded message is not valid')
  const oldOffset = offset
  const obj = {
    index: null,
    key: null,
    value: null
  }
  let found0 = false
  let found1 = false
  while (true) {
    if (end <= offset) {
      if (!found0 || !found1) throw new Error('Decoded message is not valid')
      decodeNode.bytes = offset - oldOffset
      return obj
    }
    const prefix = varint.decode(buf, offset)
    offset += varint.decode.bytes
    const tag = prefix >> 3
    switch (tag) {
      case 1: {
        obj.index = encodings.bytes.decode(buf, offset)
        offset += encodings.bytes.decode.bytes
        found0 = true
        break
      }
      case 2: {
        obj.key = encodings.bytes.decode(buf, offset)
        offset += encodings.bytes.decode.bytes
        found1 = true
        break
      }
      case 3: {
        obj.value = encodings.bytes.decode(buf, offset)
        offset += encodings.bytes.decode.bytes
        break
      }
      default: {
        offset = skip(prefix & 7, buf, offset)
      }
    }
  }
}

function encodingLengthNode(obj) {
  let length = 0
  let len = encodings.bytes.encodingLength(obj.index)
  length += 1 + len
  len = encodings.bytes.encodingLength(obj.key)
  length += 1 + len
  if (obj.value) {
    const len = encodings.bytes.encodingLength(obj.value)
    length += 1 + len
  }
  return length
}

function encodeNode(obj, buf, offset) {
  if (!offset) offset = 0
  const oldOffset = offset
  buf[offset++] = 10
  encodings.bytes.encode(obj.index, buf, offset)
  offset += encodings.bytes.encode.bytes
  buf[offset++] = 18
  encodings.bytes.encode(obj.key, buf, offset)
  offset += encodings.bytes.encode.bytes
  if (obj.value) {
    buf[offset++] = 26
    encodings.bytes.encode(obj.value, buf, offset)
    offset += encodings.bytes.encode.bytes
  }
  encodeNode.bytes = offset - oldOffset
  return buf
}
const OP_SET = 0
const OP_INSERT = 1
const OP_DEL = 2
const OP_COHORT = 3

class DeltaOp {
  constructor(changed, type, index, pointer) {
    this.type = type
    this.index = index
    this.pointer = pointer
    this.changed = changed
  }

  mark() {
    this.changed = true
  }

  unmark() {
    this.changed = false
  }
}

class DeltaCohort extends DeltaOp {
  constructor(changed, pointer, deltas) {
    super(changed, OP_COHORT, 0, pointer)
    this.deltas = deltas
  }
}

class CompressedArray {
  constructor(delta) {
    this.entries = []
    this.delta = delta
    this.uentries = null
    this.updates = 0

    for (const d of delta) {
      if (d.type === OP_COHORT) {
        for (const dd of d.deltas) {
          apply(this.entries, dd.type, dd.index, dd.pointer)
        }
      } else {
        apply(this.entries, d.type, d.index, d.pointer)
      }
    }
  }

  get length() {
    return this.entries.length
  }

  get ulength() {
    return this.uentries ? this.uentries.length : this.entries.length
  }

  reset() {
    if (this.updates > 0 || this.uentries !== null) {
      this.commit()
    }
  }

  commit() {
    const c = new CompressedArray([])

    c.delta = this.delta
    c.entries = this.uentries || this.entries.slice(0)

    this.delta = this.updates === 0 ? this.delta.slice(0) : this.delta.slice(0, -this.updates)
    this.uentries = null
    this.updates = 0

    return c
  }

  _update() {
    if (this.uentries) return
    this.uentries = this.entries.slice(0)
  }

  touch(index, pointer) {
    if (pointer) this.entries[index] = pointer
    else pointer = this.entries[index]

    if (pointer.changedBy === this) return
    this.set(index, pointer)
  }

  get(index) {
    return this.entries[index]
  }

  uget(index) {
    return this.uentries ? this.uentries[index] : this.entries[index]
  }

  push(pointer) {
    if (!this.uentries) this._update()
    this.insert(this.uentries.length, pointer)
  }

  unshift(pointer) {
    this.insert(0, pointer)
  }

  pop() {
    if (!this.uentries) this._update()
    if (this.uentries.length === 0) return
    const head = this.uentries[this.uentries.length - 1]
    this.delete(this.uentries.length - 1)
    return head
  }

  shift() {
    if (!this.uentries) this._update()
    if (this.uentries.length === 0) return
    const tail = this.uentries[0]
    this.delete(0)
    return tail
  }

  _touch(pointer) {
    this.updates++
    if (pointer) pointer.changedBy = this
  }

  insert(index, pointer) {
    if (!this.uentries) this._update()
    if (!insert(this.uentries, index, pointer)) return
    this._touch(pointer)
    this.delta.push(new DeltaOp(true, OP_INSERT, index, pointer))
  }

  delete(index) {
    if (!this.uentries) this._update()
    if (!del(this.uentries, index)) return
    this._touch(null)
    this.delta.push(new DeltaOp(true, OP_DEL, index, null))
  }

  set(index, pointer) {
    if (!this.uentries) this._update()
    if (!set(this.uentries, index, pointer)) return
    this._touch(pointer)
    this.delta.push(new DeltaOp(true, OP_SET, index, pointer))
  }

  flush(max, min) {
    this.updates = 0
    this.uentries = null

    // the raw entries are smaller than the delta, rebase to a single cohort
    if (this.entries.length < this.delta.length) {
      const co = new DeltaCohort(true, null, [])
      for (let i = 0; i < this.entries.length; i++) {
        co.deltas.push(new DeltaOp(true, OP_INSERT, i, this.entries[i]))
      }
      this.delta = [co]
      return this.delta
    }

    let overflow = false
    for (const d of this.delta) {
      if (d.index < 256) continue // has to be uint3, only happens in rebalances/splits
      overflow = true
      break
    }

    if (this.delta.length <= max && !overflow) return this.delta

    const direct = []
    while (this.delta.length && this.delta[this.delta.length - 1].type !== OP_COHORT) {
      direct.push(this.delta.pop())
    }
    direct.reverse()

    const l = direct.length

    if (l > min && l < this.entries.length && !overflow && this.delta.length + l <= max) {
      const co = new DeltaCohort(true, null, [])
      for (const d of direct) {
        co.deltas.push(d)
      }
      this.delta.push(co)
    } else {
      const co = new DeltaCohort(true, null, [])
      for (let i = 0; i < this.entries.length; i++) {
        const d = new DeltaOp(true, OP_INSERT, i, this.entries[i])
        co.deltas.push(d)
      }
      this.delta = [co]
    }

    return this.delta
  }
}

exports.CompressedArray = CompressedArray
exports.DeltaOp = DeltaOp
exports.DeltaCohort = DeltaCohort

exports.OP_SET = OP_SET
exports.OP_INSERT = OP_INSERT
exports.OP_DEL = OP_DEL
exports.OP_COHORT = OP_COHORT

function del(entries, index) {
  if (index >= entries.length) return false
  entries.splice(index, 1)
  return true
}

function insert(entries, index, pointer) {
  if (index >= entries.length + 1) return false
  entries.splice(index, 0, pointer)
  return true
}

function set(entries, index, pointer) {
  if (index >= entries.length) return false
  // if (entries[index] === pointer) return false
  entries[index] = pointer
  return true
}

function apply(entries, type, index, pointer) {
  if (type === OP_INSERT) {
    return insert(entries, index, pointer)
  }
  if (type === OP_DEL) {
    return del(entries, index)
  }
  if (type === OP_SET) {
    return set(entries, index, pointer)
  }
  return false
}
const b4a = require('b4a')
const ScopeLock = require('scope-lock')
const c = require('compact-encoding')
const { BLOCK_NOT_AVAILABLE } = require('hypercore-errors')
const { TreeNodePointer } = require('./tree.js')
const { decodeBlock, TYPE_LATEST } = require('./encoding.js')

const CHECKPOINT_KEY = 'hyperbee/checkpoint'

class CoreContext {
  constructor(
    store,
    local,
    cache,
    core,
    getEncryptionProvider,
    lock = new ScopeLock(),
    other = new Map()
  ) {
    this.store = store
    this.local = local
    this.cache = cache
    this.core = core
    this.getEncryptionProvider = getEncryptionProvider
    this.lock = lock
    this.other = other
    this.length = 0
    this.checkpoint = 0
    this.opened = []
    this.cores = []
    this.changed = false
    this.updateLock = new ScopeLock()
  }

  // TODO: remove, left here for easier debugging for now
  // [Symbol.for('nodejs.util.inspect.custom')]() {
  //   return '[CoreContext]'
  // }

  async update(config) {
    await this.core.ready()

    if (this.length >= this.core.length) return
    await this._inflateCheckpointFromLength(this.core.length, config, true)
  }

  async updateMaybe(config, core) {
    await this.core.ready()

    if (this.hasCore(core)) return

    const buf = await this.core.getUserData(CHECKPOINT_KEY)
    const cached = buf ? c.decode(c.uint, buf) : 0

    if (cached > this.length && cached <= this.core.length) {
      await this._inflateCheckpointFromLength(cached, config, false)
      if (this.hasCore(core)) return
    }

    await this.update(config)
  }

  hasCore(core) {
    return core <= this.opened.length
  }

  async _inflateCheckpointFromLength(length, config, persist) {
    const seq = length - 1
    const buffer = await this.core.get(seq, config)
    if (buffer === null) throw BLOCK_NOT_AVAILABLE()
    const block = decodeBlock(buffer, seq)

    await this._inflateCheckpoint(length, block, config, persist)
  }

  async _inflateCheckpoint(length, block, config, persist) {
    if (length <= this.length) return

    const checkpoint = block.checkpoint
    let cores = null

    if (checkpoint > 0) {
      const seq = checkpoint - 1
      const buffer = await this.core.get(seq, config)
      if (buffer === null) throw BLOCK_NOT_AVAILABLE()
      const block = decodeBlock(buffer, seq)

      cores = block.metadata ? block.metadata.cores : []
    }

    if (length <= this.length) return
    if (!(await this.updateLock.lock())) return

    try {
      if (length <= this.length) return

      if (cores !== null) {
        this.cores = cores
        while (this.opened.length < this.cores.length) this.opened.push(null)
      }

      this.checkpoint = checkpoint
      this.length = length

      if (persist) {
        await this.core.setUserData(CHECKPOINT_KEY, c.encode(c.uint, length))
      }
    } finally {
      this.updateLock.unlock()
    }
  }

  createTreeNode(core, seq, offset, changed, value) {
    const ptr = new TreeNodePointer(this, core, seq, offset, changed, value)
    if (ptr.value || changed) return ptr
    const existing = this.cache.get(ptr)
    if (existing) return existing
    return ptr
  }

  async getBlockType(core, config) {
    const hc = this.getCore(core)
    if (hc.length === 0) return TYPE_LATEST
    const block = await this.getBlock(hc.length - 1, core, config)
    return block.type
  }

  getCoreOffsetLocalByKey(key) {
    if (b4a.equals(key, this.core.key)) return 0

    for (let i = 0; i < this.cores.length; i++) {
      const k = this.cores[i].key
      if (b4a.equals(k, key)) {
        return i + 1
      }
    }

    return -1
  }

  getCoreOffsetLocal(context, core) {
    if (context === this) return core

    const key = core === 0 ? context.core.key : context.cores[core - 1].key
    const offset = this.getCoreOffsetLocalByKey(key)

    if (offset > -1) return offset

    this.changed = true
    this.cores.push({ key, fork: 0, length: 0, treeHash: null })
    this.opened.push(null)

    return this.cores.length
  }

  async getCoreOffset(context, core, config) {
    if (core !== 0 && core - 1 >= context.cores.length) await context.updateMaybe(config, core)
    return this.getCoreOffsetLocal(context, core)
  }

  async getCoreOffsetByKey(key, config) {
    await this.core.ready()

    let offset = this.getCoreOffsetLocalByKey(key)
    if (offset > -1) return offset

    await this.update(config)

    offset = this.getCoreOffsetLocalByKey(key)
    if (offset > -1) return offset

    this.changed = true
    this.cores.push({ key, fork: 0, length: 0, treeHash: null })
    this.opened.push(null)
    return this.cores.length
  }

  getCore(index) {
    if (index === 0) return this.core
    if (index > this.cores.length) throw new Error('Bad core index: ' + index)
    if (this.opened[index - 1] === null) {
      const key = this.cores[index - 1].key
      this.opened[index - 1] = this.store.get({
        key,
        encryption: this.getEncryptionProvider(key),
        inflightRange: [256, 512]
      })
    }
    return this.opened[index - 1]
  }

  getCoreKey(index) {
    if (index === 0) return this.core.key
    if (index > this.cores.length) throw new Error('Bad core index: ' + index)
    if (this.opened[index - 1] !== null && this.opened[index - 1].key) {
      return this.opened[index - 1].key
    }
    return this.cores[index - 1].key
  }

  async getBlock(seq, core, config) {
    if (core !== 0 && core - 1 >= this.cores.length) await this.updateMaybe(config, core)

    const hc = this.getCore(core)
    const buffer = await hc.get(seq, config)
    if (buffer === null) throw BLOCK_NOT_AVAILABLE()

    if (config.trace !== null) config.trace(core, seq)

    const block = decodeBlock(buffer, seq)
    return block
  }

  getLocalContext() {
    return this.getContextByKey(this.local.key)
  }

  getContextByKey(key) {
    if (b4a.equals(key, this.core.key)) return this

    const hex = b4a.toString(key, 'hex')
    if (this.other.has(hex)) return this.other.get(hex)

    const ctx = this._createContext(
      this.store.get({
        key,
        encryption: this.getEncryptionProvider(key),
        inflightRange: [256, 512]
      })
    )
    this.other.set(hex, ctx)
    return ctx
  }

  async getContext(core, config) {
    if (core === 0) return this
    if (core > this.cores.length) await this.updateMaybe(config, core)
    if (core > this.cores.length) throw new Error('Bad core index: ' + core)

    const hex = b4a.toString(this.cores[core - 1].key, 'hex')
    if (this.other.has(hex)) return this.other.get(hex)

    const ctx = this._createContext(this.getCore(core))
    this.other.set(hex, ctx)
    return ctx
  }

  flush() {
    this.changed = false
    return {
      cores: this.cores
    }
  }

  _createContext(core) {
    const store = this.store
    const local = this.local
    const cache = this.cache
    const getEncryptionProvider = this.getEncryptionProvider
    const lock = this.lock
    const other = this.other
    return new CoreContext(store, local, cache, core, getEncryptionProvider, lock, other)
  }
}

module.exports = CoreContext
const { Readable } = require('streamx')
const b4a = require('b4a')
const { RangeIterator } = require('./ranges.js')
const Hypercore = require('hypercore')

class DiffIterator {
  constructor(tree, left, right, opts = {}) {
    const { limit = -1, config = left.config } = opts

    this.tree = tree

    this.left = left
    this.right = right

    this.left.limit = this.right.limit = -1

    this.config = config
    this.limit = limit

    this.nextLeft = null
    this.nextRight = null
  }

  async open() {
    await Promise.all([this.left.open(), this.right.open()])
  }

  async _shiftRight() {
    const right = await this.tree.finalizeKeyPointer(this.nextRight, this.config)
    this.nextRight = null
    if (this.limit > 0) this.limit--
    return { left: null, right }
  }

  async _shiftLeft() {
    const left = await this.tree.finalizeKeyPointer(this.nextLeft, this.config)
    this.nextLeft = null
    if (this.limit > 0) this.limit--
    return { left, right: null }
  }

  _fastForward() {
    while (this.left.stack.length && this.right.stack.length) {
      const a = this.left.stack[this.left.stack.length - 1]
      const b = this.right.stack[this.right.stack.length - 1]

      if (a.offset !== b.offset) return
      if (!isSame(a.node, b.node)) return

      this.left.stack.pop()
      this.right.stack.pop()
    }
  }

  async next() {
    while (this.limit === -1 || this.limit > 0) {
      const leftPromise = this.nextLeft ? null : this.left.nextKey()
      const rightPromise = this.nextRight ? null : this.right.nextKey()

      const [l, r] = await Promise.all([leftPromise, rightPromise])

      if (leftPromise) this.nextLeft = l
      if (rightPromise) this.nextRight = r

      if (!this.nextLeft && !this.nextRight) return null
      if (!this.nextLeft) return this._shiftRight()
      if (!this.nextRight) return this._shiftLeft()

      const cmp = b4a.compare(this.nextLeft.key, this.nextRight.key)

      if (cmp === 0) {
        const leftKey = this.nextLeft
        const rightKey = this.nextRight

        this.nextRight = this.nextLeft = null

        if (isSame(leftKey, rightKey)) {
          this._fastForward()
          continue
        }

        const [left, right] = await Promise.all([
          this.tree.finalizeKeyPointer(leftKey, this.config),
          this.tree.finalizeKeyPointer(rightKey, this.config)
        ])

        if (this.limit > 0) this.limit--
        return { left, right }
      }

      if (cmp < 0) return this._shiftLeft()
      return this._shiftRight()
    }

    return null
  }
}

class DiffStream extends Readable {
  constructor(left, right, options = {}) {
    const { highWaterMark } = options
    super({ eagerOpen: true, highWaterMark })

    this.left = left
    this.right = right

    this.iterator = new DiffIterator(
      left,
      new RangeIterator(left, options),
      new RangeIterator(right, options),
      options
    )
  }

  async _open(cb) {
    try {
      await this.iterator.open()
    } catch (err) {
      cb(err)
      return
    }
    cb(null)
  }

  async _read(cb) {
    try {
      this.push(await this.iterator.next())
    } catch (err) {
      cb(err)
      return
    }
    cb(null)
  }

  _predestroy() {
    Hypercore.destroyRequests(this.iterator.left.config.activeRequests, null)
    Hypercore.destroyRequests(this.iterator.right.config.activeRequests, null)
  }
}

exports.DiffIterator = DiffIterator
exports.DiffStream = DiffStream

function isSame(a, b) {
  if (a.seq !== b.seq || a.offset !== b.offset) return false

  const k1 = a.context.getCoreKey(a.core)
  const k2 = b.context.getCoreKey(b.core)

  return k1 === k2 || b4a.equals(k1, k2) ? true : false
}
const c = require('compact-encoding')
const { getEncoding } = require('../spec/hyperschema')
const { OP_COHORT, OP_INSERT } = require('./compression.js')
const compat = require('./compat.js')
const { T } = require('./tree.js')

const Block0 = getEncoding('@bee/block-0')
const Block1 = getEncoding('@bee/block-1')

const TYPE_COMPAT = 10 // compat with bee1, all blocks starts with 0x10

exports.TYPE_COMPAT = TYPE_COMPAT
exports.TYPE_LATEST = 1

exports.Block = Block1
exports.encodeBlock = encodeBlock
exports.decodeBlock = decodeBlock

function encodeBlock(block) {
  if (block.type === 1) {
    if (block.t === T) block.t = 0
    return c.encode(Block1, block)
  }

  if (block.type === 0) {
    return encodeBlock0(block)
  }

  if (block.type === TYPE_COMPAT) {
    return compat.encode(block)
  }

  throw new Error('Unknown block type: ' + block.type)
}

function decodeBlock(buffer, seq) {
  const state = { start: 0, end: buffer.byteLength, buffer }
  const type = state.end > 0 ? c.uint.decode(state) : 0

  state.start = 0

  if (type === 1) {
    const block = Block1.decode(state)
    if (block.t === 0) block.t = T
    return block
  }

  if (type === 0) {
    return decodeBlock0(state, seq)
  }

  if (type === TYPE_COMPAT) {
    return compat.decode(buffer, seq)
  }

  throw new Error('Unknown block type: ' + type)
}

function dataToKey(d) {
  return { ...d, valuePointer: null }
}

function keyToData(d) {
  return { key: d.key, value: d.value }
}

function decodeBlock0(state, seq) {
  const cohorts = []
  const blk = Block0.decode(state)
  const tree = []

  for (const t of blk.tree) {
    const next = {
      keys: toCohort(seq, t.keys, cohorts),
      children: toCohort(seq, t.children, cohorts)
    }

    tree.push(next)
  }

  return {
    type: 0,
    checkpoint: blk.checkpoint,
    batch: blk.batch,
    previous: blk.previous,
    metadata: { cores: blk.cores },
    tree,
    keys: blk.data.map(dataToKey),
    values: null,
    cohorts,
    t: T
  }
}

function encodeBlock0(blk) {
  const tree = []

  for (const t of blk.tree) {
    tree.push({
      keys: fromCohort(t.keys, blk.cohorts),
      children: fromCohort(t.children, blk.cohorts)
    })
  }

  return c.encode(Block0, {
    type: 0,
    checkpoint: blk.checkpoint,
    batch: blk.batch,
    previous: blk.previous,
    tree,
    data: blk.keys.map(keyToData),
    cores: blk.metadata ? blk.metadata.cores : null
  })
}

function fromCohort(deltas, cohorts) {
  if (deltas.length === 0) return []
  const cohort = cohorts[deltas[0].pointer.offset]
  const pointers = []
  for (const d of cohort) pointers.push(d.pointer)
  return pointers
}

function toCohort(seq, pointers, cohorts) {
  if (!pointers.length) return []

  const cohort = []
  const offset = cohorts.push(cohort) - 1

  for (let i = 0; i < pointers.length; i++) {
    cohort.push({
      type: OP_INSERT,
      index: i,
      pointer: pointers[i]
    })
  }

  return [
    {
      type: OP_COHORT,
      index: 0,
      pointer: {
        core: 0,
        seq,
        offset
      }
    }
  ]
}
const b4a = require('b4a')
const { Pointer, KeyPointer, ValuePointer, TreeNode } = require('./tree.js')
const { DeltaOp, DeltaCohort, OP_COHORT } = require('./compression.js')

exports.inflate = async function inflate(ptr, config) {
  if (ptr.value) return ptr.value
  if (ptr.inflating) return ptr.inflating

  ptr.inflating = _inflate(ptr, config)

  try {
    return await ptr.inflating
  } finally {
    ptr.inflating = null
  }
}

async function _inflate(ptr, config) {
  const [block, context] = await Promise.all([
    ptr.context.getBlock(ptr.seq, ptr.core, config),
    ptr.context.getContext(ptr.core, config)
  ])

  const tree = block.tree[ptr.offset]

  const keys = new Array(tree.keys.length)
  const children = new Array(tree.children.length)

  for (let i = 0; i < keys.length; i++) {
    const d = tree.keys[i]
    keys[i] = inflateKey(context, d, ptr, block, config)
  }

  for (let i = 0; i < children.length; i++) {
    const d = tree.children[i]
    children[i] = inflateChild(context, d, ptr, block, config)
  }

  const [k, c] = await Promise.all([Promise.all(keys), Promise.all(children)])

  const value = new TreeNode(block.t, k, c)
  if (!ptr.value) ptr.value = value
  return ptr.value
}

function inflateKey(context, d, ptr, block, config) {
  if (d.type === OP_COHORT) return inflateKeyCohort(context, d, ptr, block, config)
  return inflateKeyDelta(context, d, ptr, block, config)
}

async function inflateKeyDelta(context, d, ptr, block, config) {
  const k = d.pointer

  if (!k) return new DeltaOp(false, d.type, d.index, null)

  const blk =
    k.seq === ptr.seq && k.core === 0 && ptr.core === 0
      ? block
      : await context.getBlock(k.seq, k.core, config)

  const bk = blk.keys[k.offset]

  let vp = null

  if (bk.valuePointer) {
    const p = bk.valuePointer
    const ctx = await context.getContext(k.core, config)
    vp = new ValuePointer(ctx, p.core, p.seq, p.offset, p.split)
  }

  const kp = new KeyPointer(context, k.core, k.seq, k.offset, false, bk.key, bk.value, vp)
  return new DeltaOp(false, d.type, d.index, kp)
}

exports.inflateValue = async function inflateValue(key, config) {
  if (key.value) return key.value
  if (!key.valuePointer) return null

  const ptr = key.valuePointer

  if (ptr.split === 0) {
    const block = await ptr.context.getBlock(ptr.seq, ptr.core, config)
    return block.values[ptr.offset]
  }

  const blockPromises = new Array(ptr.split + 1)
  for (let i = 0; i < blockPromises.length; i++) {
    blockPromises[i] = ptr.context.getBlock(ptr.seq - ptr.split + i, ptr.core, config)
  }
  const blocks = await Promise.all(blockPromises)
  const splitValue = new Array(blockPromises.length)
  for (let i = 0; i < splitValue.length - 1; i++) {
    splitValue[i] = blocks[i].values[0]
  }
  splitValue[splitValue.length - 1] = blocks[blocks.length - 1].buffer[ptr.offset]
  return b4a.concat(splitValue)
}

async function inflateKeyCohort(context, d, ptr, block, config) {
  const co = d.pointer

  const blk =
    co.seq === ptr.seq && co.core === 0 && ptr.core === 0
      ? block
      : await context.getBlock(co.seq, co.core, config)

  const cohort = blk.cohorts[co.offset]
  const promises = new Array(cohort.length)
  const ctx = await context.getContext(co.core, config)

  for (let i = 0; i < cohort.length; i++) {
    const p = cohort[i]
    const k = inflateKeyDelta(ctx, p, co, blk, config)
    promises[i] = k
  }

  const p = new Pointer(context, co.core, co.seq, co.offset)
  return new DeltaCohort(false, p, await Promise.all(promises))
}

async function inflateChild(context, d, ptr, block, config) {
  if (d.type === OP_COHORT) return inflateChildCohort(context, d, ptr, block, config)
  if (d.pointer && !context.hasCore(d.pointer.core)) {
    await context.updateMaybe(config, d.pointer.core)
  }
  return inflateChildDelta(context, d, ptr, block, config)
}

function inflateChildDelta(context, d, ptr, block, config) {
  const p = d.pointer
  const c = p && context.createTreeNode(p.core, p.seq, p.offset, false, null)
  return new DeltaOp(false, d.type, d.index, c)
}

async function inflateChildCohort(context, d, ptr, block, config) {
  const co = d.pointer

  const blk =
    co.seq === ptr.seq && co.core === 0 && ptr.core === 0
      ? block
      : await context.getBlock(co.seq, co.core, config)

  const cohort = blk.cohorts[co.offset]
  const deltas = new Array(cohort.length)
  const ctx = await context.getContext(co.core, config)

  for (let i = 0; i < cohort.length; i++) {
    const c = cohort[i]
    if (c.pointer && !ctx.hasCore(c.pointer.core)) await ctx.updateMaybe(config, c.pointer.core)
    deltas[i] = inflateChildDelta(ctx, c, co, blk, config)
  }

  const p = new Pointer(context, co.core, co.seq, co.offset)
  return new DeltaCohort(false, p, deltas)
}
const { Readable } = require('streamx')
const Hypercore = require('hypercore')
const b4a = require('b4a')

class RangeIterator {
  constructor(tree, opts = {}) {
    const {
      config = tree.config.detach(opts),
      prefetch = true,
      reverse = false,
      limit = -1,
      gte,
      gt,
      lte,
      lt
    } = opts

    this.tree = tree
    this.root = null
    this.stack = []
    this.config = config
    this.reverse = reverse
    this.limit = limit
    this.start = gte || gt || null
    this.end = lte || lt || null
    this.inclusive = !!(reverse ? lte : gte)
    this.compare = (reverse ? gte : lte) ? 0 : -1
    this.prefetch = prefetch
    this.prefetching = null
  }

  async open() {
    if (!this.root) this.root = await this.tree.bootstrap(this.config)
    if (!this.root) return

    if (this.limit === 0) return

    this.stack.push({ node: this.root, offset: 0 })

    if (this.reverse ? !this.end : !this.start) return

    const offset = this.inclusive ? 0 : 1

    while (true) {
      const top = this.stack[this.stack.length - 1]
      const v = top.node.value
        ? this.tree.bump(top.node)
        : await this.tree.inflate(top.node, this.config)

      for (let i = 0; i < v.keys.length; i++) {
        const j = this.reverse ? v.keys.length - 1 - i : i

        const c = this.reverse
          ? b4a.compare(v.keys.get(j).key, this.end)
          : b4a.compare(this.start, v.keys.get(j).key)

        if (c < 0) break
        top.offset = 2 * i + 1 + (c === 0 ? offset : 1)
      }

      const child = (top.offset & 1) === 0
      const k = top.offset >> 1

      if (!child || k >= v.children.length) break

      const j = this.reverse ? v.children.length - 1 - k : k

      this.stack.push({
        node: v.children.get(j),
        offset: 0
      })

      top.offset++
    }
  }

  async next() {
    const key = await this.nextKey()
    return key === null ? key : this.tree.finalizeKeyPointer(key, this.config)
  }

  async nextKey() {
    while (this.stack.length && (this.limit === -1 || this.limit > 0)) {
      const top = this.stack.pop()
      const v = top.node.value
        ? this.tree.bump(top.node)
        : await this.tree.inflate(top.node, this.config)

      const offset = top.offset++
      const child = (offset & 1) === 0
      const k = offset >> 1

      if (child) {
        this.stack.push(top)
        if (k < v.children.length) {
          const j = this.reverse ? v.children.length - 1 - k : k
          this.stack.push({ node: v.children.get(j), offset: 0 })
        }
        continue
      }

      if (k < v.keys.length) {
        const j = this.reverse ? v.keys.length - 1 - k : k

        const data = v.keys.get(j)

        const c = this.reverse
          ? this.start
            ? b4a.compare(this.start, data.key)
            : -1
          : this.end
            ? b4a.compare(data.key, this.end)
            : -1

        if (c > this.compare) break

        if (this.prefetch && !v.children.length && this.stack.length) {
          this.prefetchNext(v.t - 1)
        }

        this.stack.push(top)
        if (this.limit !== -1) this.limit--
        return data
      }
    }

    return null
  }

  prefetchNext(minKeys) {
    // every sibling covers at least minKeys keys plus the parent separator
    // emitted after it
    const size = minKeys + 1

    let limit = this.limit === -1 ? Infinity : this.limit

    // the current leaf (whose key is being emitted now) covers the rest
    if (limit <= size) return

    const parent = this.stack[this.stack.length - 1]
    const pv = parent.node.value
    if (!pv || pv === this.prefetching) return

    this.prefetching = pv

    for (let i = parent.offset >> 1; i < pv.children.length; i++) {
      const j = this.reverse ? pv.children.length - 1 - i : i

      // If the separator key bounding this child is beyond the range,
      // stop fetching child nodes.
      if (i > 0) {
        const k = this.reverse ? pv.keys.get(j) : pv.keys.get(j - 1)

        const cmp = this.reverse
          ? this.start
            ? b4a.compare(this.start, k.key)
            : -1
          : this.end
            ? b4a.compare(k.key, this.end)
            : -1

        if (cmp > this.compare) return
      }

      const c = pv.children.get(j)
      if (!c.value) this.tree.inflate(c, this.config).catch(noop)

      limit -= size
      if (limit <= 0) break
    }
  }
}

class RangeStream extends Readable {
  constructor(tree, options = {}) {
    const { highWaterMark } = options
    super({ eagerOpen: true, highWaterMark })

    this.tree = tree
    this.iterator = new RangeIterator(tree, options)
  }

  async _open(cb) {
    try {
      await this.iterator.open()
    } catch (err) {
      cb(err)
      return
    }
    cb(null)
  }

  async _read(cb) {
    try {
      this.push(await this.iterator.next())
    } catch (err) {
      cb(err)
      return
    }
    cb(null)
  }

  _predestroy() {
    Hypercore.destroyRequests(this.iterator.config.activeRequests, null)
  }
}

exports.RangeIterator = RangeIterator
exports.RangeStream = RangeStream

function noop() {}
class SessionConfig {
  constructor(activeRequests, timeout, wait, trace) {
    this.activeRequests = activeRequests
    this.timeout = timeout
    this.wait = wait
    this.trace = trace
  }

  sub(activeRequests, timeout, wait, trace) {
    if (
      this.activeRequests === activeRequests &&
      this.timeout === timeout &&
      this.wait === wait &&
      this.trace === trace
    ) {
      return this
    }

    return new SessionConfig(activeRequests, timeout, wait, trace)
  }

  options(opts) {
    if (!opts) return this
    const {
      activeRequests = this.activeRequests,
      timeout = this.timeout,
      wait = this.wait,
      trace = this.trace
    } = opts
    return this.sub(activeRequests, timeout, wait, trace)
  }

  detach(opts) {
    if (!opts) return this.sub([], this.timeout, this.wait, this.trace)
    const {
      activeRequests = [],
      timeout = this.timeout,
      wait = this.wait,
      trace = this.trace
    } = opts
    return this.sub(activeRequests, timeout, wait, trace)
  }
}

module.exports = SessionConfig
const b4a = require('b4a')
const { CompressedArray } = require('./compression.js')

const T = 128 // default degree, only non default degrees are stored on the nodes

const INSERTED = -1
const NEEDS_SPLIT = -2

class ValuePointer {
  constructor(context, core, seq, offset, split) {
    this.context = context

    this.core = core
    this.seq = seq
    this.offset = offset
    this.split = split
  }
}

class Pointer {
  constructor(context, core, seq, offset, changed) {
    this.context = context

    this.core = core
    this.seq = seq
    this.offset = offset
    this.changed = changed
    this.retained = 0

    this.changedBy = null
  }

  mark() {
    this.changed = true
  }

  unmark() {
    this.changed = false
    this.changedBy = null
  }

  // Compare two pointers to see if they point to equivalent positions
  equivalentTo(other) {
    // EMPTY is a special case that can look equivalent to a first entry
    // in a hypercore but actually contains different data.
    if (other === exports.EMPTY) return this === exports.EMPTY
    if (!other) return false

    return (
      this.seq === other.seq &&
      this.offset === other.offset &&
      this.changed === other.changed &&
      this.context === other.context &&
      this.core === other.core
    )
  }

  retain() {
    this.retained = this.context.cache.retained + 1
  }
}

class KeyPointer extends Pointer {
  constructor(context, core, seq, offset, changed, key, value, valuePointer) {
    super(context, core, seq, offset, changed)

    this.key = key
    this.value = value
    this.valuePointer = valuePointer
  }

  // TODO: remove, left here for easier debugging for now
  [Symbol.for('nodejs.util.inspect.custom')]() {
    return `[KeyPointer core=${this.core} seq=${this.seq}, offset=${this.offset}, key="${b4a.toString(this.key)}"]`
  }
}

class TreeNodePointer extends Pointer {
  constructor(context, core, seq, offset, changed, value) {
    super(context, core, seq, offset, false)

    this.value = value

    this.next = null
    this.prev = null

    this.dirty = 0
    this.inflating = null

    if (changed) this.mark()
  }

  mark() {
    if (this.changed) return
    this.changed = true
    this.dirty = this.context.cache.dirty.push(this) - 1
  }

  unmark() {
    if (!this.changed) return

    this.changed = false
    this.changedBy = null

    if (this.value) {
      this.value.keys.reset()
      this.value.children.reset()
    }

    const head = this.context.cache.dirty.pop()
    if (head === this) return

    this.context.cache.dirty[this.dirty] = head
    head.dirty = this.dirty
  }

  commit() {
    const value = new TreeNode(this.value.t, [], [])
    value.keys = this.value.keys.commit()
    value.children = this.value.children.commit()
    const ptr = this.context.createTreeNode(this.core, 0, 0, true, value)
    this.unmark()
    return ptr
  }

  equivalentTo(other) {
    return (
      super.equivalentTo(other) &&
      this.value === other.value &&
      this.prev === other.prev &&
      this.next === other.next
    )
  }

  // TODO: remove, left here for easier debugging for now
  [Symbol.for('nodejs.util.inspect.custom')]() {
    return `[TreeNodePointer core=${this.core} seq=${this.seq} offset=${this.offset} changed=${this.changed}]`
  }
}

class TreeNode {
  constructor(t, keys, children) {
    this.t = t
    this.keys = new CompressedArray(keys)
    this.children = new CompressedArray(children)
  }

  isEmpty() {
    return this.keys.ulength === 0 && this.children.ulength === 0
  }

  insertLeaf(context, key, value) {
    let s = 0
    let e = this.keys.ulength
    let c = 0

    while (s < e) {
      const mid = (s + e) >> 1
      const k = this.keys.uget(mid)

      c = b4a.compare(key, k.key)

      if (c === 0) return mid

      if (c < 0) e = mid
      else s = mid + 1
    }

    const i = c < 0 ? e : s
    const maxKeys = 2 * this.t - 1

    this.keys.insert(i, new KeyPointer(context, 0, 0, 0, true, key, value, null))

    return this.keys.ulength <= maxKeys ? INSERTED : NEEDS_SPLIT
  }

  insertNode(keyPointer, treePointer) {
    let s = 0
    let e = this.keys.ulength
    let c = 0

    while (s < e) {
      const mid = (s + e) >> 1
      const k = this.keys.uget(mid)

      c = b4a.compare(keyPointer.key, k.key)

      if (c === 0) return mid

      if (c < 0) e = mid
      else s = mid + 1
    }

    const i = c < 0 ? e : s
    const maxKeys = 2 * this.t - 1

    this.keys.insert(i, keyPointer)
    this.children.insert(i + 1, treePointer)

    return this.keys.ulength <= maxKeys ? INSERTED : NEEDS_SPLIT
  }

  setValue(context, i, value) {
    this.keys.set(i, new KeyPointer(context, 0, 0, 0, true, this.keys.uget(i).key, value, null))
  }

  removeKey(i) {
    this.keys.delete(i)
    if (this.children.ulength) {
      this.children.delete(i + 1)
    }
  }

  siblings(parent) {
    const pc = parent.children

    for (let i = 0; i < pc.ulength; i++) {
      if (pc.uget(i).value !== this) continue // TODO: move to a seq/offset check instead

      const left = i ? pc.uget(i - 1) : null
      const right = i < pc.ulength - 1 ? pc.uget(i + 1) : null
      return { left, index: i, right }
    }

    // TODO: assert
    throw new Error('Bad parent')
  }

  merge(node, median) {
    const keys = node.keys
    const children = node.children

    this.keys.push(median)

    for (let i = 0; i < keys.ulength; i++) this.keys.push(keys.uget(i))
    for (let i = 0; i < children.ulength; i++) this.children.push(children.uget(i))
  }

  split(context) {
    const len = this.keys.ulength >> 1
    const right = context.createTreeNode(0, 0, 0, true, new TreeNode(this.t, [], []))

    const k = []
    while (k.length < len) k.push(this.keys.pop())
    for (let i = k.length - 1; i >= 0; i--) right.value.keys.push(k[i])

    const median = this.keys.pop()

    if (this.children.ulength) {
      const c = []
      while (c.length < len + 1) c.push(this.children.pop())
      for (let i = c.length - 1; i >= 0; i--) right.value.children.push(c[i])
    }

    return {
      left: this,
      median,
      right
    }
  }
}

exports.T = T
exports.INSERTED = INSERTED
exports.NEEDS_SPLIT = NEEDS_SPLIT

exports.TreeNodePointer = TreeNodePointer
exports.TreeNode = TreeNode
exports.KeyPointer = KeyPointer
exports.ValuePointer = ValuePointer
exports.Pointer = Pointer

exports.EMPTY = new TreeNodePointer(null, 0, 0, 0, false, null)
const b4a = require('b4a')
const { OP_COHORT } = require('./compression.js')
const { encodeBlock, TYPE_COMPAT, TYPE_LATEST } = require('./encoding.js')
const { Pointer, KeyPointer, ValuePointer, TreeNode, INSERTED, NEEDS_SPLIT } = require('./tree.js')
const { inflateValue } = require('./inflate.js')
const compat = require('./compat.js')

const PREFERRED_BLOCK_SIZE = 4096
const INLINE_VALUE_SIZE = 1024
const ESTIMATED_POINTER_SIZE = 12 // conversative, seq=8b, offset=2b, core=2b

module.exports = class WriteBatch {
  constructor(tree, opts = {}) {
    const {
      length = -1,
      key = null,
      autoUpdate = true,
      compat = false,
      type = compat ? TYPE_COMPAT : TYPE_LATEST,
      deltaMax = supportsCompression(type) ? 16 : 0,
      deltaMin = supportsCompression(type) ? 1 : Infinity,
      inlineValueSize = INLINE_VALUE_SIZE,
      preferredBlockSize = PREFERRED_BLOCK_SIZE
    } = opts

    this.tree = tree
    this.config = tree.config.options(opts)
    this.deltaMax = deltaMax
    this.deltaMin = deltaMin
    this.inlineValueSize = inlineValueSize
    this.preferredBlockSize = preferredBlockSize
    this.snapshot = tree.snapshot()
    this.autoUpdate = autoUpdate
    this.length = length
    this.key = key
    this.type = type
    this.hasLock = false
    this.closed = false
    this.applied = 0
    this.root = null
    this.ops = []
  }

  tryPut(key, value) {
    this.checkIfClosed()
    this.ops.push({ put: true, applied: false, key, value })
  }

  tryDelete(key) {
    this.checkIfClosed()
    this.ops.push({ put: false, applied: false, key, value: null })
  }

  tryClear() {
    this.checkIfClosed()
    this.ops = []
    this.length = 0
  }

  _getContext(root) {
    if (!this.key && !root) return this.tree.context
    return this.key ? this.tree.context.getContextByKey(this.key) : root.context
  }

  _getLength(root) {
    if (this.length > -1) return this.length
    return root ? root.seq + 1 : 0
  }

  async lock() {
    if (this.hasLock) return
    await this.tree.context.lock.lock()
    this.hasLock = true
  }

  _unlock() {
    if (!this.hasLock) return
    this.tree.context.lock.unlock()
    this.hasLock = false
  }

  async flush() {
    this.checkIfClosed()
    await this.lock()

    let context = null

    try {
      this.checkIfClosed()

      const ops = this.ops

      const root = await this.tree.bootstrap(this.config)

      const length = this._getLength(root)

      context = this._getContext(root)

      const changed = length === 0
      const seq = length === 0 ? 0 : length - 1
      const t = this.type === TYPE_COMPAT ? compat.t : this.tree.t
      const value = changed ? new TreeNode(t, [], []) : null

      this.length = length
      this.root = context.createTreeNode(0, seq, 0, changed, value)

      for (const op of ops) {
        if (op.put) op.applied = await this._put(op.key, op.value)
        else op.applied = await this._delete(op.key)
        if (op.applied) this.applied++
      }

      await this._flush()
      await this.close()

      if (this.autoUpdate) {
        this.tree._setRoot(this.root, true)
      }
    } finally {
      if (context) context.cache.flush()
      this._unlock()
    }
  }

  close() {
    this.closed = true
    return this.snapshot.close()
  }

  async _put(key, value) {
    const stack = []
    const target = key
    const snap = this.snapshot
    const conf = this.config

    let ptr = this.root

    while (true) {
      const v = await retainAndInflate(ptr, snap, conf)
      if (!v.children.ulength) break

      stack.push(ptr)

      let s = 0
      let e = v.keys.ulength
      let c = 0

      while (s < e) {
        const mid = (s + e) >> 1
        const m = v.keys.uget(mid)

        c = b4a.compare(target, m.key)

        if (c === 0) {
          const existing = await inflateValue(m, conf)
          if (b4a.equals(existing, value)) return false
          v.setValue(this.tree.context, mid, value)
          for (let i = 0; i < stack.length; i++) stack[i].mark()
          return true
        }

        if (c < 0) e = mid
        else s = mid + 1
      }

      const i = c < 0 ? e : s
      ptr = v.children.uget(i)
    }

    const v = await retainAndInflate(ptr, snap, conf)
    let status = v.insertLeaf(this.tree.context, target, value)

    if (status >= 0) {
      // already exists, upsert if changed
      const m = v.keys.uget(status)
      const existing = await inflateValue(m, conf)
      if (b4a.equals(existing, value)) return false
      v.setValue(this.tree.context, status, value)
    }

    ptr.mark()
    for (let i = 0; i < stack.length; i++) stack[i].mark()

    while (status === NEEDS_SPLIT) {
      const v = await retainAndInflate(ptr, snap, conf)
      const parent = stack.pop()
      const { median, right } = v.split(this.tree.context)

      if (parent) {
        const p = await retainAndInflate(parent, snap, conf)
        status = p.insertNode(median, right)
        ptr = parent
      } else {
        const node = new TreeNode(v.t, [], [])
        this.root = this.tree.context.createTreeNode(0, 0, 0, true, node)
        this.root.value.keys.push(median)
        this.root.value.children.push(ptr)
        this.root.value.children.push(right)
        status = INSERTED
      }
    }

    return true
  }

  async _delete(key) {
    let ptr = this.root

    const stack = []

    while (true) {
      const v = await retainAndInflate(ptr, this.snapshot, this.config)
      stack.push(ptr)

      let s = 0
      let e = v.keys.ulength
      let c = 0

      while (s < e) {
        const mid = (s + e) >> 1
        c = b4a.compare(key, v.keys.uget(mid).key)

        if (c === 0) {
          if (v.children.ulength) await this._setKeyToNearestLeaf(v, mid, stack)
          else v.removeKey(mid)
          // we mark these as changed late, so we don't rewrite them if it is a 404
          for (let i = 0; i < stack.length; i++) stack[i].mark()
          this.root = await this._rebalance(stack)
          return true
        }

        if (c < 0) e = mid
        else s = mid + 1
      }

      if (!v.children.ulength) return false

      const i = c < 0 ? e : s
      ptr = v.children.uget(i)
    }

    return false
  }

  async _setKeyToNearestLeaf(v, index, stack) {
    const snap = this.snapshot
    const conf = this.config

    let left = v.children.uget(index)
    let right = v.children.uget(index + 1)

    const [ls, rs] = await Promise.all([this._leafSize(left, false), this._leafSize(right, true)])

    if (ls < rs) {
      // if fewer leaves on the left
      stack.push(right)
      let r = await retainAndInflate(right, snap, conf)
      while (r.children.ulength) {
        right = r.children.uget(0)
        stack.push(right)
        r = await retainAndInflate(right, snap, conf)
      }
      v.keys.set(index, r.keys.shift())
    } else {
      // if fewer leaves on the right
      stack.push(left)
      let l = await retainAndInflate(left, snap, conf)
      while (l.children.ulength) {
        left = l.children.uget(l.children.ulength - 1)
        stack.push(left)
        l = await retainAndInflate(left, snap, conf)
      }
      v.keys.set(index, l.keys.pop())
    }
  }

  async _leafSize(ptr, goLeft) {
    const snap = this.snapshot
    const conf = this.config

    let v = await retainAndInflate(ptr, snap, conf)
    while (v.children.ulength) {
      ptr = v.children.uget(goLeft ? 0 : v.children.ulength - 1)
      v = await retainAndInflate(ptr, snap, conf)
    }
    return v.keys.ulength
  }

  async _rebalance(stack) {
    const root = stack[0]
    const snap = this.snapshot
    const conf = this.config

    while (stack.length > 1) {
      const ptr = stack.pop()
      const parent = stack[stack.length - 1]

      const v = await retainAndInflate(ptr, snap, conf)
      const minKeys = v.t - 1

      if (v.keys.ulength >= minKeys) return root

      const p = await retainAndInflate(parent, snap, conf)

      let { left, index, right } = v.siblings(p)

      let l = left && (await retainAndInflate(left, snap, conf))

      // maybe borrow from left sibling?
      if (l && l.keys.ulength > minKeys) {
        left.mark()
        v.keys.unshift(p.keys.uget(index - 1))
        if (l.children.ulength) v.children.unshift(l.children.pop())
        p.keys.set(index - 1, l.keys.pop())
        return root
      }

      let r = right && (await retainAndInflate(right, snap, conf))

      // maybe borrow from right sibling?
      if (r && r.keys.ulength > minKeys) {
        right.mark()
        v.keys.push(p.keys.uget(index))
        if (r.children.ulength) v.children.push(r.children.shift())
        p.keys.set(index, r.keys.shift())
        return root
      }

      // merge node with another sibling
      if (l) {
        index--
        r = v
        right = ptr
      } else {
        l = v
        left = ptr
      }

      left.mark()
      l.merge(r, p.keys.uget(index))

      parent.mark()
      p.removeKey(index)
    }

    const r = await retainAndInflate(root, snap, conf)
    // check if the tree shrunk
    if (!r.keys.ulength && r.children.ulength) return r.children.uget(0)
    return root
  }

  _shouldInlineValue(k) {
    if (!k.value || !supportsCompression(this.type)) return true
    if (k.valuePointer) return true
    return k.value.byteLength <= this.inlineValueSize
  }

  async _flush() {
    if (!this.root || !this.root.changed) {
      return
    }

    if (!this.root.value) await this.snapshot.inflate(this.root, this.config)

    let update = { size: 0, nodes: [], keys: [], values: [] }
    let minValue = -1

    this.root = this.root.commit()

    // t is global for the tree, so the root's t applies to every node
    const t = this.root.value.t

    const batch = [update]
    const stack = [this.root]
    const values = []

    const context = this.tree.context.getLocalContext()
    const config = this.tree.config

    await context.update(config)

    while (stack.length > 0) {
      const node = stack.pop()

      if (this.type !== TYPE_COMPAT && update.size >= this.preferredBlockSize) {
        update = { size: 0, nodes: [], keys: [], values: [] }
        batch.push(update)
      }

      update.nodes.push(node)
      update.size += getEstimatedNodeSize(node.value)

      const keys = node.value.keys
      const children = node.value.children

      for (let i = 0; i < keys.entries.length; i++) {
        const k = keys.entries[i]
        if (!k.changed) continue

        if (!this._shouldInlineValue(k)) {
          values.push(k)
          k.valuePointer = new ValuePointer(context, 0, 0, 0, 0)

          if (minValue === -1 || minValue < k.value.byteLength) {
            minValue = k.value.byteLength
          }
        }

        update.keys.push(k)
        update.size += getEstimatedKeySize(k)
      }

      for (let i = 0; i < children.entries.length; i++) {
        const c = children.entries[i]
        if (!c.value || !c.changed) continue
        const node = c.commit()
        children.touch(i, node)
        stack.push(node)
      }
    }

    if (this.type === TYPE_COMPAT) await toCompatType(context, batch, this.ops)

    const length = context.core.length

    // if noop and not genesis, bail early
    if (this.applied === 0 && length > 0) return

    if (minValue > -1 && minValue + update.size < this.preferredBlockSize) {
      // TODO: repack the value into the block
    }

    if (values.length) {
      update = { size: 0, nodes: [], keys: [], values: [] }

      for (let i = 0; i < values.length; i++) {
        const k = values[i]

        update.size += getEstimatedValueSize(k)
        update.values.push(k)

        if (i === values.length - 1 || update.size >= this.preferredBlockSize) {
          batch.push(update)
          update = { size: 0, nodes: [], keys: [], values: [] }
        }
      }
    }

    const blocks = new Array(batch.length)

    for (let i = 0; i < batch.length; i++) {
      const update = batch[i]
      const seq = length + batch.length - i - 1

      const block = {
        type: this.type,
        checkpoint: 0,
        batch: { start: batch.length - 1 - i, end: i },
        previous: null,
        metadata: null,
        tree: null,
        keys: null,
        values: null,
        cohorts: null,
        t
      }

      for (const k of update.values) {
        if (block.values === null) block.values = []
        const ptr = k.valuePointer

        ptr.core = 0
        ptr.context = context
        ptr.seq = seq
        ptr.offset = block.values.length
        ptr.split = 0

        block.values.push(k.value)
        k.value = null // unlinked
      }

      for (const k of update.keys) {
        if (block.keys === null) block.keys = []

        k.core = 0
        k.context = context
        k.seq = seq
        k.offset = block.keys.length

        if (k.valuePointer) updateValuePointerContext(k.valuePointer, context)

        k.unmark()
        block.keys.push(k)
      }

      for (const n of update.nodes) {
        if (block.tree === null) block.tree = []

        n.core = 0
        n.context = context
        n.seq = seq
        n.offset = block.tree.length

        const treeDelta = {
          keys: n.value.keys.flush(this.deltaMax, this.deltaMin),
          children: n.value.children.flush(this.deltaMax, this.deltaMin)
        }

        prepareCohorts(context, block, seq, treeDelta.keys, supportsCompression(this.type))
        prepareCohorts(context, block, seq, treeDelta.children, false)

        n.unmark()
        block.tree.push(treeDelta)
      }

      blocks[seq - length] = block
    }

    const buffers = new Array(blocks.length)

    if (blocks.length > 0 && this.length > 0) {
      const core = this.key ? await context.getCoreOffsetByKey(this.key, config) : 0
      blocks[blocks.length - 1].previous = { core, seq: this.length - 1 }
    }

    // TODO: make this transaction safe
    if (context.changed) {
      context.checkpoint = context.core.length + blocks.length
      blocks[blocks.length - 1].metadata = context.flush()
    }

    for (let i = 0; i < blocks.length; i++) {
      blocks[i].checkpoint = context.checkpoint
      buffers[i] = encodeBlock(blocks[i])
    }

    this.checkIfClosed()

    await context.core.append(buffers)

    for (let i = 0; i < batch.length; i++) {
      const update = batch[i]

      for (let j = 0; j < update.nodes.length; j++) {
        const node = update.nodes[j]
        this.snapshot.bump(node)
      }
    }
  }

  checkIfClosed() {
    if (this.closed) {
      throw new Error('Write batch is closed')
    }
  }
}

async function toCompatType(context, batch, ops) {
  const map = new Map()
  let index = 0

  for (const k of batch[0].keys) {
    map.set(b4a.toString(k.key, 'hex'), k)
  }

  for (let i = ops.length - 1; i >= 0; i--) {
    const op = ops[i]
    if (!op.put && !op.applied) continue

    const k = map.get(b4a.toString(op.key, 'hex'))

    const j = index++
    if (j === batch.length) batch.push({ size: 0, nodes: [], keys: [], values: [] })
    batch[j].keys = [k || new KeyPointer(context, 0, 0, 0, false, op.key, op.value, null)]
  }

  // compat doesnt support block 0
  if (context.core.length > 0) return

  const header = b4a.from('0a086879706572626565', 'hex')
  await context.core.append(header)
}

function updateValuePointerContext(valuePointer, context) {
  valuePointer.core = context.getCoreOffsetLocal(valuePointer.context, valuePointer.core)
  valuePointer.context = context
}

// TODO: this isnt right anymore as we compress the delta post flush, but prob ok...
function getEstimatedNodeSize(n) {
  return (
    n.keys.delta.length * ESTIMATED_POINTER_SIZE + n.children.delta.length * ESTIMATED_POINTER_SIZE
  )
}

function getEstimatedKeySize(k) {
  return k.key.byteLength + (k.valuePointer ? ESTIMATED_POINTER_SIZE : 0)
}

function getEstimatedValueSize(k) {
  return k.value.byteLength
}

function prepareCohorts(context, block, seq, deltas, keys) {
  for (const d of deltas) {
    // same below but the delta might be a noop (ie add and the delete) - handle that
    if (keys && d.changed && d.pointer && d.pointer.changed) d.pointer = null

    if (d.pointer && d.pointer.context !== context) {
      const p = d.pointer
      p.core = context.getCoreOffsetLocal(p.context, p.core)
      p.context = context
    }

    if (d.type !== OP_COHORT || !d.changed) continue
    if (block.cohorts === null) block.cohorts = []

    d.unmark()
    d.pointer = new Pointer(context, 0, seq, block.cohorts.length)

    for (const dd of d.deltas) {
      if (keys && dd.changed && dd.pointer && dd.pointer.changed) dd.pointer = null
      dd.unmark()
      const p = dd.pointer
      if (!p) continue
      p.core = context.getCoreOffsetLocal(p.context, p.core)
      p.context = context
    }

    block.cohorts.push(d.deltas)
  }
}

function supportsCompression(type) {
  return type !== TYPE_COMPAT && type !== 0
}

async function retainAndInflate(ptr, snap, conf) {
  if (ptr.value) {
    ptr.retain()
    return ptr.value
  }
  ptr.retain()
  return await snap.inflate(ptr, conf)
}
{
  "name": "hyperbee2",
  "version": "2.13.2",
  "description": "Scalable P2P BTree",
  "main": "index.js",
  "files": [
    "index.js",
    "lib/*.js",
    "spec"
  ],
  "imports": {
    "events": {
      "bare": "bare-events",
      "default": "events"
    }
  },
  "scripts": {
    "format": "prettier . --write",
    "lint": "lunte && prettier . --check",
    "test": "node test/all.js",
    "test:bare": "bare test/all.js",
    "test:generate": "brittle -r test/all.js test/*.js"
  },
  "dependencies": {
    "b4a": "^1.6.7",
    "bare-events": "^2.8.2",
    "compact-encoding": "^3.0.0",
    "hypercore": "^11.29.0",
    "hypercore-errors": "^1.5.0",
    "hyperschema": "^1.14.0",
    "protocol-buffers-encodings": "^1.2.0",
    "scope-lock": "^1.2.4",
    "streamx": "^2.22.1"
  },
  "devDependencies": {
    "brittle": "^3.18.0",
    "corestore": "^7.4.5",
    "lunte": "^1.4.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^2.0.0"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/holepunchto/hyperbee2.git"
  },
  "author": "Holepunch Inc.",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/hyperbee2/issues"
  },
  "homepage": "https://github.com/holepunchto/hyperbee2"
}
// This file is autogenerated by the hyperschema compiler
// Schema Version: 1
/* eslint-disable camelcase */
/* eslint-disable quotes */
/* eslint-disable space-before-function-paren */

const { c } = require('hyperschema/runtime')

const VERSION = 1

// eslint-disable-next-line no-unused-vars
let version = VERSION

// @bee/tree-pointer-0
const encoding0 = {
  preencode(state, m) {
    c.uint.preencode(state, m.core)
    c.uint.preencode(state, m.seq)
    c.uint.preencode(state, m.offset)
  },
  encode(state, m) {
    c.uint.encode(state, m.core)
    c.uint.encode(state, m.seq)
    c.uint.encode(state, m.offset)
  },
  decode(state) {
    const r0 = c.uint.decode(state)
    const r1 = c.uint.decode(state)
    const r2 = c.uint.decode(state)

    return {
      core: r0,
      seq: r1,
      offset: r2
    }
  }
}

// @bee/tree-pointer
const encoding1 = {
  preencode(state, m) {
    state.end++ // flags are fixed size

    if (m.core) c.uint.preencode(state, m.core)
    if (m.seq) c.uint.preencode(state, m.seq)
    if (m.offset) c.uint.preencode(state, m.offset)
  },
  encode(state, m) {
    const flags = (m.core ? 1 : 0) | (m.seq ? 2 : 0) | (m.offset ? 4 : 0)

    c.uint8.encode(state, flags)

    if (m.core) c.uint.encode(state, m.core)
    if (m.seq) c.uint.encode(state, m.seq)
    if (m.offset) c.uint.encode(state, m.offset)
  },
  decode(state) {
    const flags = c.uint8.decode(state)

    return {
      core: (flags & 1) !== 0 ? c.uint.decode(state) : 0,
      seq: (flags & 2) !== 0 ? c.uint.decode(state) : 0,
      offset: (flags & 4) !== 0 ? c.uint.decode(state) : 0
    }
  }
}

// @bee/tree-pointer (inline)
const encoding1_inline = {
  preencode(state, m) {
    if (m.core) c.uint.preencode(state, m.core)
    if (m.seq) c.uint.preencode(state, m.seq)
    if (m.offset) c.uint.preencode(state, m.offset)
  },
  encode(state, m) {
    if (m.core) c.uint.encode(state, m.core)
    if (m.seq) c.uint.encode(state, m.seq)
    if (m.offset) c.uint.encode(state, m.offset)
  },
  decode(state, inlining) {
    const flags = inlining

    return {
      core: (flags & 1) !== 0 ? c.uint.decode(state) : 0,
      seq: (flags & 2) !== 0 ? c.uint.decode(state) : 0,
      offset: (flags & 4) !== 0 ? c.uint.decode(state) : 0
    }
  }
}

// @bee/tree-delta
const encoding2 = {
  preencode(state, m) {
    state.end++ // flags are fixed size

    if (m.index) c.uint8.preencode(state, m.index)
    if (m.pointer) encoding1_inline.preencode(state, m.pointer)
  },
  encode(state, m) {
    let flags = (m.type & 7) | (m.index ? 8 : 0) | (m.pointer ? 16 : 0)
    if (m.pointer) {
      flags |= (m.pointer.core ? 32 : 0) | (m.pointer.seq ? 64 : 0) | (m.pointer.offset ? 128 : 0)
    }

    c.uint8.encode(state, flags)

    if (m.index) c.uint8.encode(state, m.index)
    if (m.pointer) encoding1_inline.encode(state, m.pointer)
  },
  decode(state) {
    const flags = c.uint8.decode(state)

    return {
      type: flags & 7,
      index: (flags & 8) !== 0 ? c.uint8.decode(state) : 0,
      pointer: (flags & 16) !== 0 ? encoding1_inline.decode(state, flags >>> 5) : null
    }
  }
}

// @bee/cohort
const encoding3 = c.array(encoding2)

// @bee/value-pointer
const encoding4 = {
  preencode(state, m) {
    state.end++ // flags are fixed size

    if (m.core) c.uint.preencode(state, m.core)
    if (m.seq) c.uint.preencode(state, m.seq)
    if (m.offset) c.uint.preencode(state, m.offset)
    if (m.split) c.uint.preencode(state, m.split)
  },
  encode(state, m) {
    const flags = (m.core ? 1 : 0) | (m.seq ? 2 : 0) | (m.offset ? 4 : 0) | (m.split ? 8 : 0)

    c.uint8.encode(state, flags)

    if (m.core) c.uint.encode(state, m.core)
    if (m.seq) c.uint.encode(state, m.seq)
    if (m.offset) c.uint.encode(state, m.offset)
    if (m.split) c.uint.encode(state, m.split)
  },
  decode(state) {
    const flags = c.uint8.decode(state)

    return {
      core: (flags & 1) !== 0 ? c.uint.decode(state) : 0,
      seq: (flags & 2) !== 0 ? c.uint.decode(state) : 0,
      offset: (flags & 4) !== 0 ? c.uint.decode(state) : 0,
      split: (flags & 8) !== 0 ? c.uint.decode(state) : 0
    }
  }
}

// @bee/value-pointer (inline)
const encoding4_inline = {
  preencode(state, m) {
    if (m.core) c.uint.preencode(state, m.core)
    if (m.seq) c.uint.preencode(state, m.seq)
    if (m.offset) c.uint.preencode(state, m.offset)
    if (m.split) c.uint.preencode(state, m.split)
  },
  encode(state, m) {
    if (m.core) c.uint.encode(state, m.core)
    if (m.seq) c.uint.encode(state, m.seq)
    if (m.offset) c.uint.encode(state, m.offset)
    if (m.split) c.uint.encode(state, m.split)
  },
  decode(state, inlining) {
    const flags = inlining

    return {
      core: (flags & 1) !== 0 ? c.uint.decode(state) : 0,
      seq: (flags & 2) !== 0 ? c.uint.decode(state) : 0,
      offset: (flags & 4) !== 0 ? c.uint.decode(state) : 0,
      split: (flags & 8) !== 0 ? c.uint.decode(state) : 0
    }
  }
}

// @bee/block-pointer
const encoding5 = {
  preencode(state, m) {
    c.uint.preencode(state, m.core)
    c.uint.preencode(state, m.seq)
  },
  encode(state, m) {
    c.uint.encode(state, m.core)
    c.uint.encode(state, m.seq)
  },
  decode(state) {
    const r0 = c.uint.decode(state)
    const r1 = c.uint.decode(state)

    return {
      core: r0,
      seq: r1
    }
  }
}

// @bee/batch-pointer
const encoding6 = {
  preencode(state, m) {
    c.uint.preencode(state, m.start)
    c.uint.preencode(state, m.end)
  },
  encode(state, m) {
    c.uint.encode(state, m.start)
    c.uint.encode(state, m.end)
  },
  decode(state) {
    const r0 = c.uint.decode(state)
    const r1 = c.uint.decode(state)

    return {
      start: r0,
      end: r1
    }
  }
}

// @bee/tree-0.keys
const encoding7_0 = c.array(encoding0)
// @bee/tree-0.children
const encoding7_1 = encoding7_0

// @bee/tree-0
const encoding7 = {
  preencode(state, m) {
    encoding7_0.preencode(state, m.keys)
    encoding7_1.preencode(state, m.children)
  },
  encode(state, m) {
    encoding7_0.encode(state, m.keys)
    encoding7_1.encode(state, m.children)
  },
  decode(state) {
    const r0 = encoding7_0.decode(state)
    const r1 = encoding7_1.decode(state)

    return {
      keys: r0,
      children: r1
    }
  }
}

// @bee/tree.keys
const encoding8_0 = encoding3
// @bee/tree.children
const encoding8_1 = encoding3

// @bee/tree
const encoding8 = {
  preencode(state, m) {
    encoding8_0.preencode(state, m.keys)
    encoding8_1.preencode(state, m.children)
  },
  encode(state, m) {
    encoding8_0.encode(state, m.keys)
    encoding8_1.encode(state, m.children)
  },
  decode(state) {
    const r0 = encoding8_0.decode(state)
    const r1 = encoding8_1.decode(state)

    return {
      keys: r0,
      children: r1
    }
  }
}

// @bee/data
const encoding9 = {
  preencode(state, m) {
    c.buffer.preencode(state, m.key)
    c.buffer.preencode(state, m.value)
  },
  encode(state, m) {
    c.buffer.encode(state, m.key)
    c.buffer.encode(state, m.value)
  },
  decode(state) {
    const r0 = c.buffer.decode(state)
    const r1 = c.buffer.decode(state)

    return {
      key: r0,
      value: r1
    }
  }
}

// @bee/key
const encoding10 = {
  preencode(state, m) {
    c.buffer.preencode(state, m.key)
    state.end++ // flags are fixed size

    if (m.value) c.buffer.preencode(state, m.value)
    if (m.valuePointer) encoding4_inline.preencode(state, m.valuePointer)
  },
  encode(state, m) {
    let flags = (m.value ? 1 : 0) | (m.valuePointer ? 2 : 0)
    if (m.valuePointer) {
      flags |=
        (m.valuePointer.core ? 4 : 0) |
        (m.valuePointer.seq ? 8 : 0) |
        (m.valuePointer.offset ? 16 : 0) |
        (m.valuePointer.split ? 32 : 0)
    }

    c.buffer.encode(state, m.key)
    c.uint8.encode(state, flags)

    if (m.value) c.buffer.encode(state, m.value)
    if (m.valuePointer) encoding4_inline.encode(state, m.valuePointer)
  },
  decode(state) {
    const r0 = c.buffer.decode(state)
    const flags = c.uint8.decode(state)

    return {
      key: r0,
      value: (flags & 1) !== 0 ? c.buffer.decode(state) : null,
      valuePointer: (flags & 2) !== 0 ? encoding4_inline.decode(state, flags >>> 2) : null
    }
  }
}

// @bee/core
const encoding11 = {
  preencode(state, m) {
    c.fixed32.preencode(state, m.key)
    state.end++ // flags are fixed size

    if (m.fork) c.uint.preencode(state, m.fork)
    if (m.length) c.uint.preencode(state, m.length)
    if (m.treeHash) c.fixed32.preencode(state, m.treeHash)
  },
  encode(state, m) {
    const flags = (m.fork ? 1 : 0) | (m.length ? 2 : 0) | (m.treeHash ? 4 : 0)

    c.fixed32.encode(state, m.key)
    c.uint8.encode(state, flags)

    if (m.fork) c.uint.encode(state, m.fork)
    if (m.length) c.uint.encode(state, m.length)
    if (m.treeHash) c.fixed32.encode(state, m.treeHash)
  },
  decode(state) {
    const r0 = c.fixed32.decode(state)
    const flags = c.uint8.decode(state)

    return {
      key: r0,
      fork: (flags & 1) !== 0 ? c.uint.decode(state) : 0,
      length: (flags & 2) !== 0 ? c.uint.decode(state) : 0,
      treeHash: (flags & 4) !== 0 ? c.fixed32.decode(state) : null
    }
  }
}

// @bee/block-0.tree
const encoding12_4 = c.array(encoding7)
// @bee/block-0.data
const encoding12_5 = c.array(encoding9)
// @bee/block-0.cores
const encoding12_6 = c.array(encoding11)

// @bee/block-0
const encoding12 = {
  preencode(state, m) {
    c.uint.preencode(state, m.type)
    c.uint.preencode(state, m.checkpoint)
    encoding6.preencode(state, m.batch)
    state.end++ // max flag is 8 so always one byte

    if (m.previous) encoding5.preencode(state, m.previous)
    if (m.tree) encoding12_4.preencode(state, m.tree)
    if (m.data) encoding12_5.preencode(state, m.data)
    if (m.cores) encoding12_6.preencode(state, m.cores)
  },
  encode(state, m) {
    const flags = (m.previous ? 1 : 0) | (m.tree ? 2 : 0) | (m.data ? 4 : 0) | (m.cores ? 8 : 0)

    c.uint.encode(state, m.type)
    c.uint.encode(state, m.checkpoint)
    encoding6.encode(state, m.batch)
    c.uint.encode(state, flags)

    if (m.previous) encoding5.encode(state, m.previous)
    if (m.tree) encoding12_4.encode(state, m.tree)
    if (m.data) encoding12_5.encode(state, m.data)
    if (m.cores) encoding12_6.encode(state, m.cores)
  },
  decode(state) {
    const r0 = c.uint.decode(state)
    const r1 = c.uint.decode(state)
    const r2 = encoding6.decode(state)
    const flags = c.uint.decode(state)

    return {
      type: r0,
      checkpoint: r1,
      batch: r2,
      previous: (flags & 1) !== 0 ? encoding5.decode(state) : null,
      tree: (flags & 2) !== 0 ? encoding12_4.decode(state) : null,
      data: (flags & 4) !== 0 ? encoding12_5.decode(state) : null,
      cores: (flags & 8) !== 0 ? encoding12_6.decode(state) : null
    }
  }
}

// @bee/metadata.cores
const encoding13_0 = encoding12_6

// @bee/metadata
const encoding13 = {
  preencode(state, m) {
    encoding13_0.preencode(state, m.cores)
  },
  encode(state, m) {
    encoding13_0.encode(state, m.cores)
  },
  decode(state) {
    const r0 = encoding13_0.decode(state)

    return {
      cores: r0
    }
  }
}

// @bee/block-1.metadata
const encoding14_4 = c.frame(encoding13)
// @bee/block-1.tree
const encoding14_5 = c.array(encoding8)
// @bee/block-1.keys
const encoding14_6 = c.array(encoding10)
// @bee/block-1.values
const encoding14_7 = c.array(c.buffer)
// @bee/block-1.cohorts
const encoding14_8 = c.array(encoding3)

// @bee/block-1
const encoding14 = {
  preencode(state, m) {
    c.uint.preencode(state, m.type)
    c.uint.preencode(state, m.checkpoint)
    encoding6.preencode(state, m.batch)
    state.end++ // max flag is 64 so always one byte

    if (m.previous) encoding5.preencode(state, m.previous)
    if (m.metadata) encoding14_4.preencode(state, m.metadata)
    if (m.tree) encoding14_5.preencode(state, m.tree)
    if (m.keys) encoding14_6.preencode(state, m.keys)
    if (m.values) encoding14_7.preencode(state, m.values)
    if (m.cohorts) encoding14_8.preencode(state, m.cohorts)
    if (m.t) c.uint.preencode(state, m.t)
  },
  encode(state, m) {
    const flags =
      (m.previous ? 1 : 0) |
      (m.metadata ? 2 : 0) |
      (m.tree ? 4 : 0) |
      (m.keys ? 8 : 0) |
      (m.values ? 16 : 0) |
      (m.cohorts ? 32 : 0) |
      (m.t ? 64 : 0)

    c.uint.encode(state, m.type)
    c.uint.encode(state, m.checkpoint)
    encoding6.encode(state, m.batch)
    c.uint.encode(state, flags)

    if (m.previous) encoding5.encode(state, m.previous)
    if (m.metadata) encoding14_4.encode(state, m.metadata)
    if (m.tree) encoding14_5.encode(state, m.tree)
    if (m.keys) encoding14_6.encode(state, m.keys)
    if (m.values) encoding14_7.encode(state, m.values)
    if (m.cohorts) encoding14_8.encode(state, m.cohorts)
    if (m.t) c.uint.encode(state, m.t)
  },
  decode(state) {
    const r0 = c.uint.decode(state)
    const r1 = c.uint.decode(state)
    const r2 = encoding6.decode(state)
    const flags = c.uint.decode(state)

    return {
      type: r0,
      checkpoint: r1,
      batch: r2,
      previous: (flags & 1) !== 0 ? encoding5.decode(state) : null,
      metadata: (flags & 2) !== 0 ? encoding14_4.decode(state) : null,
      tree: (flags & 4) !== 0 ? encoding14_5.decode(state) : null,
      keys: (flags & 8) !== 0 ? encoding14_6.decode(state) : null,
      values: (flags & 16) !== 0 ? encoding14_7.decode(state) : null,
      cohorts: (flags & 32) !== 0 ? encoding14_8.decode(state) : null,
      t: (flags & 64) !== 0 ? c.uint.decode(state) : 0
    }
  }
}

function setVersion(v) {
  version = v
}

function encode(name, value, v = VERSION) {
  version = v
  return c.encode(getEncoding(name), value)
}

function decode(name, buffer, v = VERSION) {
  version = v
  return c.decode(getEncoding(name), buffer)
}

function getEnum(name) {
  switch (name) {
    default:
      throw new Error('Enum not found ' + name)
  }
}

function getEncoding(name) {
  switch (name) {
    case '@bee/tree-pointer-0':
      return encoding0
    case '@bee/tree-pointer':
      return encoding1
    case '@bee/tree-delta':
      return encoding2
    case '@bee/cohort':
      return encoding3
    case '@bee/value-pointer':
      return encoding4
    case '@bee/block-pointer':
      return encoding5
    case '@bee/batch-pointer':
      return encoding6
    case '@bee/tree-0':
      return encoding7
    case '@bee/tree':
      return encoding8
    case '@bee/data':
      return encoding9
    case '@bee/key':
      return encoding10
    case '@bee/core':
      return encoding11
    case '@bee/block-0':
      return encoding12
    case '@bee/metadata':
      return encoding13
    case '@bee/block-1':
      return encoding14
    default:
      throw new Error('Encoder not found ' + name)
  }
}

function getStruct(name, v = VERSION) {
  const enc = getEncoding(name)
  return {
    preencode(state, m) {
      version = v
      enc.preencode(state, m)
    },
    encode(state, m) {
      version = v
      enc.encode(state, m)
    },
    decode(state) {
      version = v
      return enc.decode(state)
    }
  }
}

const resolveStruct = getStruct // compat

module.exports = {
  resolveStruct,
  getStruct,
  getEnum,
  getEncoding,
  encode,
  decode,
  setVersion,
  version
}
const sodium = require('sodium-universal')
const c = require('compact-encoding')
const b4a = require('b4a')

// https://en.wikipedia.org/wiki/Merkle_tree#Second_preimage_attack
const LEAF_TYPE = b4a.from([0])
const PARENT_TYPE = b4a.from([1])
const ROOT_TYPE = b4a.from([2])

const HYPERCORE = b4a.from('hypercore')

exports.keyPair = function (seed) {
  // key pairs might stay around for a while, so better not to use a default slab to avoid retaining it completely
  const slab = b4a.allocUnsafeSlow(
    sodium.crypto_sign_PUBLICKEYBYTES + sodium.crypto_sign_SECRETKEYBYTES
  )
  const publicKey = slab.subarray(0, sodium.crypto_sign_PUBLICKEYBYTES)
  const secretKey = slab.subarray(sodium.crypto_sign_PUBLICKEYBYTES)

  if (seed) sodium.crypto_sign_seed_keypair(publicKey, secretKey, seed)
  else sodium.crypto_sign_keypair(publicKey, secretKey)

  return {
    publicKey,
    secretKey
  }
}

exports.validateKeyPair = function (keyPair) {
  const pk = b4a.allocUnsafe(sodium.crypto_sign_PUBLICKEYBYTES)
  sodium.crypto_sign_ed25519_sk_to_pk(pk, keyPair.secretKey)
  return b4a.equals(pk, keyPair.publicKey)
}

exports.sign = function (message, secretKey) {
  // Dedicated slab for the signature, to avoid retaining unneeded mem and for security
  const signature = b4a.allocUnsafeSlow(sodium.crypto_sign_BYTES)
  sodium.crypto_sign_detached(signature, message, secretKey)
  return signature
}

exports.verify = function (message, signature, publicKey) {
  if (signature.byteLength !== sodium.crypto_sign_BYTES) return false
  if (publicKey.byteLength !== sodium.crypto_sign_PUBLICKEYBYTES) return false
  return sodium.crypto_sign_verify_detached(signature, message, publicKey)
}

exports.encrypt = function (message, publicKey) {
  const ciphertext = b4a.alloc(message.byteLength + sodium.crypto_box_SEALBYTES)
  sodium.crypto_box_seal(ciphertext, message, publicKey)
  return ciphertext
}

exports.decrypt = function (ciphertext, keyPair) {
  if (ciphertext.byteLength < sodium.crypto_box_SEALBYTES) return null

  const plaintext = b4a.alloc(ciphertext.byteLength - sodium.crypto_box_SEALBYTES)

  if (!sodium.crypto_box_seal_open(plaintext, ciphertext, keyPair.publicKey, keyPair.secretKey)) {
    return null
  }

  return plaintext
}

exports.encryptionKeyPair = function (seed) {
  const publicKey = b4a.alloc(sodium.crypto_box_PUBLICKEYBYTES)
  const secretKey = b4a.alloc(sodium.crypto_box_SECRETKEYBYTES)

  if (seed) {
    sodium.crypto_box_seed_keypair(publicKey, secretKey, seed)
  } else {
    sodium.crypto_box_keypair(publicKey, secretKey)
  }

  return {
    publicKey,
    secretKey
  }
}

exports.data = function (data) {
  const out = b4a.allocUnsafe(32)

  sodium.crypto_generichash_batch(out, [LEAF_TYPE, c.encode(c.uint64, data.byteLength), data])

  return out
}

exports.parent = function (a, b) {
  if (a.index > b.index) {
    const tmp = a
    a = b
    b = tmp
  }

  const out = b4a.allocUnsafe(32)

  sodium.crypto_generichash_batch(out, [
    PARENT_TYPE,
    c.encode(c.uint64, a.size + b.size),
    a.hash,
    b.hash
  ])

  return out
}

exports.tree = function (roots, out) {
  const buffers = new Array(3 * roots.length + 1)
  let j = 0

  buffers[j++] = ROOT_TYPE

  for (let i = 0; i < roots.length; i++) {
    const r = roots[i]
    buffers[j++] = r.hash
    buffers[j++] = c.encode(c.uint64, r.index)
    buffers[j++] = c.encode(c.uint64, r.size)
  }

  if (!out) out = b4a.allocUnsafe(32)
  sodium.crypto_generichash_batch(out, buffers)
  return out
}

exports.hash = function (data, out) {
  if (!out) out = b4a.allocUnsafe(32)
  if (!Array.isArray(data)) data = [data]

  sodium.crypto_generichash_batch(out, data)

  return out
}

exports.randomBytes = function (n) {
  const buf = b4a.allocUnsafe(n)
  sodium.randombytes_buf(buf)
  return buf
}

exports.discoveryKey = function (key) {
  if (!key || key.byteLength !== 32) throw new Error('Must pass a 32 byte buffer')
  // Discovery keys might stay around for a while, so better not to use slab memory (for better gc)
  const digest = b4a.allocUnsafeSlow(32)
  sodium.crypto_generichash(digest, HYPERCORE, key)
  return digest
}

if (sodium.sodium_free) {
  exports.free = function (secureBuf) {
    if (secureBuf.secure) sodium.sodium_free(secureBuf)
  }
} else {
  exports.free = function () {}
}

exports.namespace = function (name, count) {
  const ids = typeof count === 'number' ? range(count) : count

  // Namespaces are long-lived, so better to use a dedicated slab
  const buf = b4a.allocUnsafeSlow(32 * ids.length)

  const list = new Array(ids.length)

  // ns is ephemeral, so default slab
  const ns = b4a.allocUnsafe(33)
  sodium.crypto_generichash(ns.subarray(0, 32), typeof name === 'string' ? b4a.from(name) : name)

  for (let i = 0; i < list.length; i++) {
    list[i] = buf.subarray(32 * i, 32 * i + 32)
    ns[32] = ids[i]
    sodium.crypto_generichash(list[i], ns)
  }

  return list
}

function range(count) {
  const arr = new Array(count)
  for (let i = 0; i < count; i++) arr[i] = i
  return arr
}
{
  "name": "hypercore-crypto",
  "version": "3.7.0",
  "description": "The crypto primitives used in hypercore, extracted into a separate module",
  "main": "index.js",
  "files": [
    "index.js"
  ],
  "dependencies": {
    "b4a": "^1.6.6",
    "compact-encoding": "^3.0.0",
    "sodium-universal": "^5.0.0"
  },
  "devDependencies": {
    "brittle": "^3.5.0",
    "lunte": "^1.0.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^2.0.0"
  },
  "scripts": {
    "format": "prettier --write .",
    "lint": "prettier --check . && lunte",
    "test": "npm run test:node && npm run test:bare",
    "test:node": "brittle-node test.js",
    "test:bare": "brittle-bare test.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/mafintosh/hypercore-crypto.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/mafintosh/hypercore-crypto/issues"
  },
  "homepage": "https://github.com/mafintosh/hypercore-crypto"
}
const IdEnc = require('hypercore-id-encoding')

module.exports = class HypercoreError extends Error {
  constructor (msg, code, fn = HypercoreError, discoveryKey = null) {
    if (discoveryKey) msg = `${msg} (discovery key: ${IdEnc.normalize(discoveryKey)})`
    super(`${code}: ${msg}`)

    this.code = code
    this.discoveryKey = discoveryKey

    if (Error.captureStackTrace) {
      Error.captureStackTrace(this, fn)
    }
  }

  get name () {
    return 'HypercoreError'
  }

  static ASSERTION (msg, discoveryKey = null) { // ERR_ASSERTION is picked up by safety-catch also
    return new HypercoreError(msg, 'ERR_ASSERTION', HypercoreError.ASSERT, discoveryKey)
  }

  static BAD_ARGUMENT (msg, discoveryKey = null) {
    return new HypercoreError(msg, 'BAD_ARGUMENT', HypercoreError.BAD_ARGUMENT, discoveryKey)
  }

  static STORAGE_EMPTY (msg, discoveryKey = null) {
    return new HypercoreError(msg, 'STORAGE_EMPTY', HypercoreError.STORAGE_EMPTY, discoveryKey)
  }

  static STORAGE_CONFLICT (msg, discoveryKey = null) {
    return new HypercoreError(msg, 'STORAGE_CONFLICT', HypercoreError.STORAGE_CONFLICT, discoveryKey)
  }

  static INVALID_SIGNATURE (msg, discoveryKey = null) {
    return new HypercoreError(msg, 'INVALID_SIGNATURE', HypercoreError.INVALID_SIGNATURE, discoveryKey)
  }

  static INVALID_CAPABILITY (msg, discoveryKey = null) {
    return new HypercoreError(msg, 'INVALID_CAPABILITY', HypercoreError.INVALID_CAPABILITY, discoveryKey)
  }

  static INVALID_CHECKSUM (msg = 'Invalid checksum', discoveryKey = null) {
    return new HypercoreError(msg, 'INVALID_CHECKSUM', HypercoreError.INVALID_CHECKSUM, discoveryKey)
  }

  static INVALID_OPERATION (msg, discoveryKey = null) {
    return new HypercoreError(msg, 'INVALID_OPERATION', HypercoreError.INVALID_OPERATION, discoveryKey)
  }

  static INVALID_PROOF (msg = 'Proof not verifiable', discoveryKey = null) {
    return new HypercoreError(msg, 'INVALID_PROOF', HypercoreError.INVALID_PROOF, discoveryKey)
  }

  static BLOCK_NOT_AVAILABLE (msg = 'Block is not available', discoveryKey = null) {
    return new HypercoreError(msg, 'BLOCK_NOT_AVAILABLE', HypercoreError.BLOCK_NOT_AVAILABLE, discoveryKey)
  }

  static SNAPSHOT_NOT_AVAILABLE (msg = 'Snapshot is not available', discoveryKey = null) {
    return new HypercoreError(msg, 'SNAPSHOT_NOT_AVAILABLE', HypercoreError.SNAPSHOT_NOT_AVAILABLE, discoveryKey)
  }

  static REQUEST_CANCELLED (msg = 'Request was cancelled', discoveryKey = null) {
    return new HypercoreError(msg, 'REQUEST_CANCELLED', HypercoreError.REQUEST_CANCELLED, discoveryKey)
  }

  static REQUEST_TIMEOUT (msg = 'Request timed out', discoveryKey = null) {
    return new HypercoreError(msg, 'REQUEST_TIMEOUT', HypercoreError.REQUEST_TIMEOUT, discoveryKey)
  }

  static SESSION_NOT_WRITABLE (msg = 'Session is not writable', discoveryKey = null) {
    return new HypercoreError(msg, 'SESSION_NOT_WRITABLE', HypercoreError.SESSION_NOT_WRITABLE, discoveryKey)
  }

  static SESSION_CLOSED (msg = 'Session is closed', discoveryKey = null) {
    return new HypercoreError(msg, 'SESSION_CLOSED', HypercoreError.SESSION_CLOSED, discoveryKey)
  }

  static BATCH_UNFLUSHED (msg = 'Batch not yet flushed', discoveryKey = null) {
    return new HypercoreError(msg, 'BATCH_UNFLUSHED', HypercoreError.BATCH_UNFLUSHED, discoveryKey)
  }

  static BATCH_ALREADY_EXISTS (msg = 'Batch already exists', discoveryKey = null) {
    return new HypercoreError(msg, 'BATCH_ALREADY_EXISTS', HypercoreError.BATCH_ALREADY_EXISTS, discoveryKey)
  }

  static BATCH_ALREADY_FLUSHED (msg = 'Batch has already been flushed', discoveryKey = null) {
    return new HypercoreError(msg, 'BATCH_ALREADY_FLUSHED', HypercoreError.BATCH_ALREADY_FLUSHED, discoveryKey)
  }

  static OPLOG_CORRUPT (msg = 'Oplog file appears corrupt or out of date', discoveryKey = null) {
    return new HypercoreError(msg, 'OPLOG_CORRUPT', HypercoreError.OPLOG_CORRUPT, discoveryKey)
  }

  static OPLOG_HEADER_OVERFLOW (msg = 'Oplog header exceeds page size', discoveryKey = null) {
    return new HypercoreError(msg, 'OPLOG_HEADER_OVERFLOW', HypercoreError.OPLOG_HEADER_OVERFLOW, discoveryKey)
  }

  static INVALID_OPLOG_VERSION (msg = 'Invalid header version', discoveryKey = null) {
    return new HypercoreError(msg, 'INVALID_OPLOG_VERSION', HypercoreError.INVALID_OPLOG_VERSION, discoveryKey)
  }

  static WRITE_FAILED (msg = 'Write to storage failed', discoveryKey = null) {
    return new HypercoreError(msg, 'WRITE_FAILED', HypercoreError.WRITE_FAILED, discoveryKey)
  }

  static DECODING_ERROR (msg = 'Decoding error', discoveryKey = null) {
    return new HypercoreError(msg, 'DECODING_ERROR', HypercoreError.DECODING_ERROR, discoveryKey)
  }

  static SESSION_MOVED (msg = 'Session moved', discoveryKey = null) {
    return new HypercoreError(msg, 'SESSION_MOVED', HypercoreError.SESSION_MOVED, discoveryKey)
  }
}
{
  "name": "hypercore-errors",
  "version": "1.5.0",
  "description": "Hypercore errors",
  "main": "index.js",
  "files": [
    "index.js"
  ],
  "scripts": {
    "test": "standard && brittle test.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/holepunchto/hypercore-errors.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/hypercore-errors/issues"
  },
  "homepage": "https://github.com/holepunchto/hypercore-errors#readme",
  "devDependencies": {
    "b4a": "^1.6.7",
    "brittle": "^3.1.3",
    "standard": "^17.0.0"
  },
  "dependencies": {
    "hypercore-id-encoding": "^1.3.0"
  }
}
const z32 = require('z32')
const b4a = require('b4a')

module.exports = {
  encode,
  decode,
  normalize,
  isValid
}

function encode (key) {
  if (!b4a.isBuffer(key)) throw new Error('Key must be a Buffer')
  if (key.byteLength !== 32) throw new Error('Key must be 32-bytes long')
  return z32.encode(key)
}

function decode (id) {
  if (b4a.isBuffer(id)) {
    if (id.byteLength !== 32) throw new Error('ID must be 32-bytes long')
    return id
  }
  if (typeof id === 'string') {
    if (id.startsWith('pear://')) id = id.slice(7).split('/')[0]
    if (id.length === 52) return z32.decode(id)
    if (id.length === 64) {
      const buf = b4a.from(id, 'hex')
      if (buf.byteLength === 32) return buf
    }
  }
  throw new Error('Invalid Hypercore key')
}

function normalize (any) {
  return encode(decode(any))
}

function isValid (any) {
  try {
    decode(any)
    return true
  } catch {
    return false
  }
}
{
  "name": "hypercore-id-encoding",
  "version": "1.3.0",
  "description": "Convert Hypercore keys to/from z-base32 or hex",
  "main": "index.js",
  "scripts": {
    "test": "standard && brittle test.js"
  },
  "files": [
    "index.js"
  ],
  "dependencies": {
    "b4a": "^1.5.3",
    "z32": "^1.0.0"
  },
  "devDependencies": {
    "brittle": "^3.1.1",
    "hypercore": "^10.0.0",
    "random-access-memory": "^6.0.0",
    "standard": "^17.1.0"
  },
  "license": "Apache-2.0",
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/hypercore-id-encoding.git"
  },
  "keywords": [],
  "author": "",
  "bugs": {
    "url": "https://github.com/holepunchto/hypercore-id-encoding/issues"
  },
  "homepage": "https://github.com/holepunchto/hypercore-id-encoding#readme"
}
function headLegacyMap(m) {
  return {
    version: 2, // version bumped from 1
    cores: m.allocated.cores,
    datas: m.allocated.datas,
    groups: 0,
    seed: m.seed,
    defaultDiscoveryKey: m.defaultDiscoveryKey
  }
}

module.exports = {
  headLegacyMap
}
// This file is autogenerated by the hyperschema compiler
// Schema Version: 2
/* eslint-disable camelcase */
/* eslint-disable quotes */
/* eslint-disable space-before-function-paren */

const { c } = require('hyperschema/runtime')
const external0 = require('../../external.js')

const VERSION = 2

// eslint-disable-next-line no-unused-vars
let version = VERSION

// @corestore/allocated
const encoding0 = {
  preencode(state, m) {
    c.uint.preencode(state, m.cores)
    c.uint.preencode(state, m.datas)
  },
  encode(state, m) {
    c.uint.encode(state, m.cores)
    c.uint.encode(state, m.datas)
  },
  decode(state) {
    const r0 = c.uint.decode(state)
    const r1 = c.uint.decode(state)

    return {
      cores: r0,
      datas: r1
    }
  }
}

// @corestore/head-v1
const encoding1 = {
  preencode(state, m) {
    state.end++ // max flag is 4 so always one byte

    if (m.allocated) encoding0.preencode(state, m.allocated)
    if (m.seed) c.fixed32.preencode(state, m.seed)
    if (m.defaultDiscoveryKey) c.fixed32.preencode(state, m.defaultDiscoveryKey)
  },
  encode(state, m) {
    const flags = (m.allocated ? 1 : 0) | (m.seed ? 2 : 0) | (m.defaultDiscoveryKey ? 4 : 0)

    c.uint.encode(state, flags)

    if (m.allocated) encoding0.encode(state, m.allocated)
    if (m.seed) c.fixed32.encode(state, m.seed)
    if (m.defaultDiscoveryKey) c.fixed32.encode(state, m.defaultDiscoveryKey)
  },
  decode(state) {
    const v = c.uint.decode(state)
    const flags = c.uint.decode(state)

    return {
      version: v,
      allocated: (flags & 1) !== 0 ? encoding0.decode(state) : null,
      seed: (flags & 2) !== 0 ? c.fixed32.decode(state) : null,
      defaultDiscoveryKey: (flags & 4) !== 0 ? c.fixed32.decode(state) : null
    }
  }
}

// @corestore/head-v2
const encoding2 = {
  preencode(state, m) {
    c.uint.preencode(state, m.cores)
    c.uint.preencode(state, m.datas)
    c.uint.preencode(state, m.groups)
    state.end++ // max flag is 2 so always one byte

    if (m.seed) c.fixed32.preencode(state, m.seed)
    if (m.defaultDiscoveryKey) c.fixed32.preencode(state, m.defaultDiscoveryKey)
  },
  encode(state, m) {
    const flags = (m.seed ? 1 : 0) | (m.defaultDiscoveryKey ? 2 : 0)

    c.uint.encode(state, m.cores)
    c.uint.encode(state, m.datas)
    c.uint.encode(state, m.groups)
    c.uint.encode(state, flags)

    if (m.seed) c.fixed32.encode(state, m.seed)
    if (m.defaultDiscoveryKey) c.fixed32.encode(state, m.defaultDiscoveryKey)
  },
  decode(state) {
    const v = c.uint.decode(state)
    const r0 = c.uint.decode(state)
    const r1 = c.uint.decode(state)
    const r2 = c.uint.decode(state)
    const flags = c.uint.decode(state)

    return {
      version: v,
      cores: r0,
      datas: r1,
      groups: r2,
      seed: (flags & 1) !== 0 ? c.fixed32.decode(state) : null,
      defaultDiscoveryKey: (flags & 2) !== 0 ? c.fixed32.decode(state) : null
    }
  }
}

// @corestore/head
const encoding3 = {
  preencode(state, m) {
    c.uint.preencode(state, m.version)
    switch (m.version) {
      case 0:
      case 1:
        encoding1.preencode(state, m)
        break
      case 2:
        encoding2.preencode(state, m)
        break
      default:
        throw new Error('Unsupported version')
    }
  },
  encode(state, m) {
    c.uint.encode(state, m.version)
    switch (m.version) {
      case 0:
      case 1:
        encoding1.encode(state, m)
        break
      case 2:
        encoding2.encode(state, m)
        break
      default:
        throw new Error('Unsupported version')
    }
  },
  decode(state) {
    const start = state.start
    const v = c.uint.decode(state)
    state.start = start
    switch (v) {
      case 0:
      case 1: {
        const decoded = encoding1.decode(state)
        const map = external0.headLegacyMap
        return map(decoded)
      }
      case 2: {
        const decoded = encoding2.decode(state)
        return decoded
      }
      default:
        throw new Error('Unsupported version')
    }
  }
}

// @corestore/alias
const encoding4 = {
  preencode(state, m) {
    c.string.preencode(state, m.name)
    c.fixed32.preencode(state, m.namespace)
  },
  encode(state, m) {
    c.string.encode(state, m.name)
    c.fixed32.encode(state, m.namespace)
  },
  decode(state) {
    const r0 = c.string.decode(state)
    const r1 = c.fixed32.decode(state)

    return {
      name: r0,
      namespace: r1
    }
  }
}

// @corestore/core
const encoding5 = {
  preencode(state, m) {
    c.uint.preencode(state, m.version)
    c.uint.preencode(state, m.corePointer)
    c.uint.preencode(state, m.dataPointer)
    state.end++ // max flag is 1 so always one byte

    if (m.alias) encoding4.preencode(state, m.alias)
  },
  encode(state, m) {
    const flags = m.alias ? 1 : 0

    c.uint.encode(state, m.version)
    c.uint.encode(state, m.corePointer)
    c.uint.encode(state, m.dataPointer)
    c.uint.encode(state, flags)

    if (m.alias) encoding4.encode(state, m.alias)
  },
  decode(state) {
    const r0 = c.uint.decode(state)
    const r1 = c.uint.decode(state)
    const r2 = c.uint.decode(state)
    const flags = c.uint.decode(state)

    return {
      version: r0,
      corePointer: r1,
      dataPointer: r2,
      alias: (flags & 1) !== 0 ? encoding4.decode(state) : null
    }
  }
}

const encoding6_enum = {
  blake2b: 'blake2b'
}

// @core/hashes enum
const encoding6 = {
  preencode(state, m) {
    state.end++ // max enum is 0 so always one byte
  },
  encode(state, m) {
    switch (m) {
      case 'blake2b':
        c.uint.encode(state, 0)
        break
      default:
        throw new Error('Unknown enum')
    }
  },
  decode(state) {
    switch (c.uint.decode(state)) {
      case 0:
        return 'blake2b'
      default:
        return null
    }
  }
}

const encoding7_enum = {
  ed25519: 'ed25519'
}

// @core/signatures enum
const encoding7 = {
  preencode(state, m) {
    state.end++ // max enum is 0 so always one byte
  },
  encode(state, m) {
    switch (m) {
      case 'ed25519':
        c.uint.encode(state, 0)
        break
      default:
        throw new Error('Unknown enum')
    }
  },
  decode(state) {
    switch (c.uint.decode(state)) {
      case 0:
        return 'ed25519'
      default:
        return null
    }
  }
}

// @core/tree-node
const encoding8 = {
  preencode(state, m) {
    c.uint.preencode(state, m.index)
    c.uint.preencode(state, m.size)
    c.fixed32.preencode(state, m.hash)
  },
  encode(state, m) {
    c.uint.encode(state, m.index)
    c.uint.encode(state, m.size)
    c.fixed32.encode(state, m.hash)
  },
  decode(state) {
    const r0 = c.uint.decode(state)
    const r1 = c.uint.decode(state)
    const r2 = c.fixed32.decode(state)

    return {
      index: r0,
      size: r1,
      hash: r2
    }
  }
}

// @core/signer
const encoding9 = {
  preencode(state, m) {
    encoding7.preencode(state, m.signature)
    c.fixed32.preencode(state, m.namespace)
    c.fixed32.preencode(state, m.publicKey)
  },
  encode(state, m) {
    encoding7.encode(state, m.signature)
    c.fixed32.encode(state, m.namespace)
    c.fixed32.encode(state, m.publicKey)
  },
  decode(state) {
    const r0 = encoding7.decode(state)
    const r1 = c.fixed32.decode(state)
    const r2 = c.fixed32.decode(state)

    return {
      signature: r0,
      namespace: r1,
      publicKey: r2
    }
  }
}

// @core/prologue
const encoding10 = {
  preencode(state, m) {
    c.fixed32.preencode(state, m.hash)
    c.uint.preencode(state, m.length)
  },
  encode(state, m) {
    c.fixed32.encode(state, m.hash)
    c.uint.encode(state, m.length)
  },
  decode(state) {
    const r0 = c.fixed32.decode(state)
    const r1 = c.uint.decode(state)

    return {
      hash: r0,
      length: r1
    }
  }
}

// @core/manifest.signers
const encoding11_4 = c.array(encoding9)
// @core/manifest.linked
const encoding11_6 = c.array(c.fixed32)

// @core/manifest
const encoding11 = {
  preencode(state, m) {
    c.uint.preencode(state, m.version)
    state.end++ // max flag is 8 so always one byte
    encoding6.preencode(state, m.hash)
    c.uint.preencode(state, m.quorum)
    encoding11_4.preencode(state, m.signers)

    if (m.prologue) encoding10.preencode(state, m.prologue)
    if (m.linked) encoding11_6.preencode(state, m.linked)
    if (m.userData) c.buffer.preencode(state, m.userData)
  },
  encode(state, m) {
    const flags =
      (m.allowPatch ? 1 : 0) | (m.prologue ? 2 : 0) | (m.linked ? 4 : 0) | (m.userData ? 8 : 0)

    c.uint.encode(state, m.version)
    c.uint.encode(state, flags)
    encoding6.encode(state, m.hash)
    c.uint.encode(state, m.quorum)
    encoding11_4.encode(state, m.signers)

    if (m.prologue) encoding10.encode(state, m.prologue)
    if (m.linked) encoding11_6.encode(state, m.linked)
    if (m.userData) c.buffer.encode(state, m.userData)
  },
  decode(state) {
    const r0 = c.uint.decode(state)
    const flags = c.uint.decode(state)

    return {
      version: r0,
      hash: encoding6.decode(state),
      quorum: c.uint.decode(state),
      allowPatch: (flags & 1) !== 0,
      signers: encoding11_4.decode(state),
      prologue: (flags & 2) !== 0 ? encoding10.decode(state) : null,
      linked: (flags & 4) !== 0 ? encoding11_6.decode(state) : null,
      userData: (flags & 8) !== 0 ? c.buffer.decode(state) : null
    }
  }
}

// @core/keyPair
const encoding12 = {
  preencode(state, m) {
    c.buffer.preencode(state, m.publicKey)
    c.optionalBuffer.preencode(state, m.secretKey)
  },
  encode(state, m) {
    c.buffer.encode(state, m.publicKey)
    c.optionalBuffer.encode(state, m.secretKey)
  },
  decode(state) {
    const r0 = c.buffer.decode(state)
    const r1 = c.optionalBuffer.decode(state)

    return {
      publicKey: r0,
      secretKey: r1
    }
  }
}

// @core/group
const encoding13 = {
  preencode(state, m) {
    state.end++ // max flag is 2 so always one byte

    if (m.key) c.fixed32.preencode(state, m.key)
    if (m.pointer) c.uint.preencode(state, m.pointer)
  },
  encode(state, m) {
    const flags = (m.key ? 1 : 0) | (m.pointer ? 2 : 0)

    c.uint.encode(state, flags)

    if (m.key) c.fixed32.encode(state, m.key)
    if (m.pointer) c.uint.encode(state, m.pointer)
  },
  decode(state) {
    const flags = c.uint.decode(state)

    return {
      key: (flags & 1) !== 0 ? c.fixed32.decode(state) : null,
      pointer: (flags & 2) !== 0 ? c.uint.decode(state) : 0
    }
  }
}

// @core/auth.manifest
const encoding14_2 = c.frame(encoding11)

// @core/auth
const encoding14 = {
  preencode(state, m) {
    c.fixed32.preencode(state, m.key)
    c.fixed32.preencode(state, m.discoveryKey)
    state.end++ // max flag is 4 so always one byte

    if (m.manifest) encoding14_2.preencode(state, m.manifest)
    if (m.keyPair) encoding12.preencode(state, m.keyPair)
    if (m.encryptionKey) c.buffer.preencode(state, m.encryptionKey)
  },
  encode(state, m) {
    const flags = (m.manifest ? 1 : 0) | (m.keyPair ? 2 : 0) | (m.encryptionKey ? 4 : 0)

    c.fixed32.encode(state, m.key)
    c.fixed32.encode(state, m.discoveryKey)
    c.uint.encode(state, flags)

    if (m.manifest) encoding14_2.encode(state, m.manifest)
    if (m.keyPair) encoding12.encode(state, m.keyPair)
    if (m.encryptionKey) c.buffer.encode(state, m.encryptionKey)
  },
  decode(state) {
    const r0 = c.fixed32.decode(state)
    const r1 = c.fixed32.decode(state)
    const flags = c.uint.decode(state)

    return {
      key: r0,
      discoveryKey: r1,
      manifest: (flags & 1) !== 0 ? encoding14_2.decode(state) : null,
      keyPair: (flags & 2) !== 0 ? encoding12.decode(state) : null,
      encryptionKey: (flags & 4) !== 0 ? c.buffer.decode(state) : null
    }
  }
}

// @core/head
const encoding15 = {
  preencode(state, m) {
    c.uint.preencode(state, m.fork)
    c.uint.preencode(state, m.length)
    c.fixed32.preencode(state, m.rootHash)
    c.optionalBuffer.preencode(state, m.signature)
    state.end++ // max flag is 1 so always one byte

    if (version >= 2 && m.timestamp) c.uint64.preencode(state, m.timestamp)
  },
  encode(state, m) {
    const flags = version >= 2 && m.timestamp ? 1 : 0

    c.uint.encode(state, m.fork)
    c.uint.encode(state, m.length)
    c.fixed32.encode(state, m.rootHash)
    c.optionalBuffer.encode(state, m.signature)
    c.uint.encode(state, flags)

    if (version >= 2 && m.timestamp) c.uint64.encode(state, m.timestamp)
  },
  decode(state) {
    const r0 = c.uint.decode(state)
    const r1 = c.uint.decode(state)
    const r2 = c.fixed32.decode(state)
    const r3 = c.optionalBuffer.decode(state)
    const flags = state.start < state.end ? c.uint.decode(state) : 0

    return {
      fork: r0,
      length: r1,
      rootHash: r2,
      signature: r3,
      timestamp: version >= 2 && (flags & 1) !== 0 ? c.uint64.decode(state) : 0
    }
  }
}

// @core/hints
const encoding16 = {
  preencode(state, m) {
    state.end++ // max flag is 4 so always one byte

    if (m.contiguousLength) c.uint.preencode(state, m.contiguousLength)
    if (m.remoteContiguousLength) c.uint.preencode(state, m.remoteContiguousLength)
    if (m.recovering) c.uint.preencode(state, m.recovering)
  },
  encode(state, m) {
    const flags =
      (m.contiguousLength ? 1 : 0) | (m.remoteContiguousLength ? 2 : 0) | (m.recovering ? 4 : 0)

    c.uint.encode(state, flags)

    if (m.contiguousLength) c.uint.encode(state, m.contiguousLength)
    if (m.remoteContiguousLength) c.uint.encode(state, m.remoteContiguousLength)
    if (m.recovering) c.uint.encode(state, m.recovering)
  },
  decode(state) {
    const flags = c.uint.decode(state)

    return {
      contiguousLength: (flags & 1) !== 0 ? c.uint.decode(state) : 0,
      remoteContiguousLength: (flags & 2) !== 0 ? c.uint.decode(state) : 0,
      recovering: (flags & 4) !== 0 ? c.uint.decode(state) : 0
    }
  }
}

// @core/session
const encoding17 = {
  preencode(state, m) {
    c.string.preencode(state, m.name)
    c.uint.preencode(state, m.dataPointer)
  },
  encode(state, m) {
    c.string.encode(state, m.name)
    c.uint.encode(state, m.dataPointer)
  },
  decode(state) {
    const r0 = c.string.decode(state)
    const r1 = c.uint.decode(state)

    return {
      name: r0,
      dataPointer: r1
    }
  }
}

// @core/sessions
const encoding18 = c.array(encoding17)

// @core/dependency
const encoding19 = {
  preencode(state, m) {
    c.uint.preencode(state, m.dataPointer)
    c.uint.preencode(state, m.length)
  },
  encode(state, m) {
    c.uint.encode(state, m.dataPointer)
    c.uint.encode(state, m.length)
  },
  decode(state) {
    const r0 = c.uint.decode(state)
    const r1 = c.uint.decode(state)

    return {
      dataPointer: r0,
      length: r1
    }
  }
}

function setVersion(v) {
  version = v
}

function encode(name, value, v = VERSION) {
  version = v
  return c.encode(getEncoding(name), value)
}

function decode(name, buffer, v = VERSION) {
  version = v
  return c.decode(getEncoding(name), buffer)
}

function getEnum(name) {
  switch (name) {
    case '@core/hashes':
      return encoding6_enum
    case '@core/signatures':
      return encoding7_enum
    default:
      throw new Error('Enum not found ' + name)
  }
}

function getEncoding(name) {
  switch (name) {
    case '@corestore/allocated':
      return encoding0
    case '@corestore/head-v1':
      return encoding1
    case '@corestore/head-v2':
      return encoding2
    case '@corestore/head':
      return encoding3
    case '@corestore/alias':
      return encoding4
    case '@corestore/core':
      return encoding5
    case '@core/hashes':
      return encoding6
    case '@core/signatures':
      return encoding7
    case '@core/tree-node':
      return encoding8
    case '@core/signer':
      return encoding9
    case '@core/prologue':
      return encoding10
    case '@core/manifest':
      return encoding11
    case '@core/keyPair':
      return encoding12
    case '@core/group':
      return encoding13
    case '@core/auth':
      return encoding14
    case '@core/head':
      return encoding15
    case '@core/hints':
      return encoding16
    case '@core/session':
      return encoding17
    case '@core/sessions':
      return encoding18
    case '@core/dependency':
      return encoding19
    default:
      throw new Error('Encoder not found ' + name)
  }
}

function getStruct(name, v = VERSION) {
  const enc = getEncoding(name)
  return {
    preencode(state, m) {
      version = v
      enc.preencode(state, m)
    },
    encode(state, m) {
      version = v
      enc.encode(state, m)
    },
    decode(state) {
      version = v
      return enc.decode(state)
    }
  }
}

const resolveStruct = getStruct // compat

module.exports = {
  resolveStruct,
  getStruct,
  getEnum,
  getEncoding,
  encode,
  decode,
  setVersion,
  version
}
const RocksDB = require('rocksdb-native')
const rrp = require('resolve-reject-promise')
const ScopeLock = require('scope-lock')
const DeviceFile = require('device-file')
const path = require('path')
const fs = require('fs')
const Xache = require('xache')

const View = require('./lib/view.js')

const VERSION = 2
const COLUMN_FAMILY = 'corestore'

const { store, core } = require('./lib/keys.js')

const { CorestoreRX, CorestoreTX, CoreTX, CoreRX } = require('./lib/tx.js')

const {
  createCoreStream,
  createAliasStream,
  createDiscoveryKeyStream,
  createBlockStream,
  createBitfieldStream,
  createUserDataStream,
  createTreeNodeStream,
  createMarkStream,
  createLocalStream,
  createGroupUpdateStream
} = require('./lib/streams.js')

const EMPTY = new View()

class Atom {
  constructor(db) {
    this.db = db
    this.view = new View()
    this.flushedPromise = null
    this.flushing = false
    this.preflushes = []
    this.flushes = []
  }

  preflush(fn) {
    this.preflushes.push(fn)
  }

  onflush(fn) {
    this.flushes.push(fn)
  }

  flushed() {
    if (!this.flushing) return Promise.resolve()
    if (this.flushedPromise !== null) return this.flushedPromise.promise
    this.flushedPromise = rrp()
    return this.flushedPromise.promise
  }

  _resolve() {
    const f = this.flushedPromise
    this.flushedPromise = null
    f.resolve()
  }

  async flush() {
    if (this.flushing) throw new Error('Atom already flushing')
    this.flushing = true

    try {
      const plen = this.preflushes.length
      for (let i = 0; i < plen; i++) this.preflushes[i]()

      await View.flush(this.view.changes, this.db)

      const promises = []
      const len = this.flushes.length // in case of reentry
      for (let i = 0; i < len; i++) promises.push(this.flushes[i]())

      await Promise.all(promises)
    } finally {
      this.view.reset()
      this.flushing = false
      if (this.flushedPromise !== null) this._resolve()
    }
  }
}

class HypercoreStorage {
  constructor(store, db, treeCache, core, view, atom) {
    this.store = store
    this.db = db
    this.core = core
    this.view = view
    this.atom = atom

    this.view.readStart()
    this.treeCache = treeCache
  }

  get readOnly() {
    return this.store.readOnly
  }

  get dependencies() {
    return this.core.dependencies
  }

  getDependencyLength() {
    return this.core.dependencies.length
      ? this.core.dependencies[this.core.dependencies.length - 1].length
      : -1
  }

  getDependency(length) {
    for (let i = this.core.dependencies.length - 1; i >= 0; i--) {
      const dep = this.core.dependencies[i]
      if (dep.length < length) return dep
    }

    return null
  }

  setDependencyHead(dep) {
    if (dep === null) {
      this.core.dependencies = []
      return
    }

    const deps = this.core.dependencies

    for (let i = deps.length - 1; i >= 0; i--) {
      const d = deps[i]

      if (d.dataPointer !== dep.dataPointer) continue

      // check if nothing changed
      if (d.length === dep.length && i === deps.length - 1) return

      this.core = {
        corePointer: this.core.corePointer,
        dataPointer: this.core.dataPointer,
        dependencies: deps.slice(0, i + 1)
      }

      this.core.dependencies[i] = {
        dataPointer: dep.dataPointer,
        length: dep.length
      }
      return // updated head so done
    }

    this.core.dependencies = [
      {
        dataPointer: dep.dataPointer,
        length: dep.length
      }
    ]
  }

  // TODO: this might have to be async if the dependents have changed, but prop ok for now
  updateDependencyLength(length, truncated) {
    const deps = this.core.dependencies

    const i = this.findDependencyIndex(length, truncated)
    if (i === -1) throw new Error('Dependency not found')

    this.core = {
      corePointer: this.core.corePointer,
      dataPointer: this.core.dataPointer,
      dependencies: deps.slice(0, i + 1)
    }

    if (this.core.dependencies[i].length !== length) {
      this.core.dependencies[i] = {
        dataPointer: deps[i].dataPointer,
        length
      }
    }
  }

  findDependencyIndex(length, truncated) {
    const deps = this.core.dependencies

    if (truncated) {
      for (let i = 0; i < deps.length; i++) {
        if (deps[i].length >= length) return i
      }

      return -1
    }

    for (let i = deps.length - 1; i >= 0; i--) {
      if (deps[i].length <= length) return i
    }

    return -1
  }

  get snapshotted() {
    return this.db._snapshot !== null
  }

  snapshot() {
    return new HypercoreStorage(
      this.store,
      this.db.snapshot(),
      this.treeCache,
      this.core,
      this.view.snapshot(),
      this.atom
    )
  }

  compact() {
    return Promise.all([
      this.db.compactRange(core.core(this.core.corePointer), core.core(this.core.corePointer)),
      this.db.compactRange(core.data(this.core.dataPointer), core.data(this.core.dataPointer))
    ])
  }

  atomize(atom) {
    if (this.atom && this.atom !== atom) {
      throw new Error('Cannot atomize and atomized session with a new atom')
    }

    return new HypercoreStorage(
      this.store,
      this.db.session(),
      this.treeCache,
      this.core,
      atom.view,
      atom
    )
  }

  createAtom() {
    return this.store.createAtom()
  }

  createBlockStream(opts) {
    return createBlockStream(this.core, this.db, this.view, opts)
  }

  createTreeNodeStream(opts) {
    return createTreeNodeStream(this.core, this.db, this.view, opts)
  }

  createBitfieldStream(opts) {
    return createBitfieldStream(this.core, this.db, this.view, opts)
  }

  createUserDataStream(opts) {
    return createUserDataStream(this.core, this.db, this.view, opts)
  }

  createMarkStream(opts) {
    return createMarkStream(this.core, this.db, this.view, opts)
  }

  createLocalStream(opts) {
    return createLocalStream(this.core, this.db, this.view, opts)
  }

  async resumeSession(name) {
    const rx = this.read()
    const existingSessionsPromise = rx.getSessions()

    rx.tryFlush()
    const existingSessions = await existingSessionsPromise

    const sessions = existingSessions || []
    const session = getSession(sessions, name, false)

    if (session === null) return null

    const core = {
      corePointer: this.core.corePointer,
      dataPointer: session.dataPointer,
      dependencies: []
    }

    const coreRx = new CoreRX(core, this.db, this.view, this.treeCache, this.store.stats, -1)

    const dependencyPromise = coreRx.getDependency()
    coreRx.tryFlush()

    const dependency = await dependencyPromise
    if (dependency) core.dependencies = this._addDependency(dependency)

    return new HypercoreStorage(
      this.store,
      this.db.session(),
      this.treeCache,
      core,
      this.atom ? this.view : new View(),
      this.atom
    )
  }

  async createSession(name, head) {
    const rx = this.read()

    const existingSessionsPromise = rx.getSessions()
    const existingHeadPromise = rx.getHead()

    rx.tryFlush()

    const [existingSessions, existingHead] = await Promise.all([
      existingSessionsPromise,
      existingHeadPromise
    ])
    if (head === null) head = existingHead

    if (existingHead !== null && head.length > existingHead.length) {
      throw new Error('Invalid head passed, ahead of core')
    }

    const sessions = existingSessions || []
    const session = getSession(sessions, name, true)
    const fresh = session.dataPointer === -1

    if (fresh) {
      session.dataPointer = await this.store._allocData()
    }

    const tx = this.write()

    tx.setSessions(sessions)

    const length = head === null ? 0 : head.length
    const core = {
      corePointer: this.core.corePointer,
      dataPointer: session.dataPointer,
      dependencies: this._addDependency({
        dataPointer: this.core.dataPointer,
        length
      })
    }

    const coreTx = new CoreTX(core, this.db, tx.view, tx.changes)

    if (length > 0) coreTx.setHead(head)
    coreTx.setDependency(core.dependencies[core.dependencies.length - 1])

    if (!fresh) {
      // nuke all existing state...
      coreTx.deleteBlockRange(0, -1)
      coreTx.deleteTreeNodeRange(0, -1)
      coreTx.deleteBitfieldPageRange(0, -1)
    }

    await tx.flush()

    return new HypercoreStorage(
      this.store,
      this.db.session(),
      this.treeCache,
      core,
      this.atom ? this.view : new View(),
      this.atom
    )
  }

  async createAtomicSession(atom, head) {
    const length = head === null ? 0 : head.length
    const core = {
      corePointer: this.core.corePointer,
      dataPointer: this.core.dataPointer,
      dependencies: this._addDependency(null)
    }

    const coreTx = new CoreTX(core, this.db, atom.view, [])

    if (length > 0) coreTx.setHead(head)

    await coreTx.flush()

    return this.atomize(atom)
  }

  _addDependency(dep) {
    const deps = []

    for (let i = 0; i < this.core.dependencies.length; i++) {
      const d = this.core.dependencies[i]

      if (dep !== null && d.length > dep.length) {
        if (d.dataPointer !== dep.dataPointer) {
          deps.push({ dataPointer: d.dataPointer, length: dep.length })
        }
        return deps
      }

      deps.push(d)
    }

    if (
      dep !== null &&
      (deps.length === 0 || deps[deps.length - 1].dataPointer !== dep.dataPointer)
    ) {
      deps.push(dep)
    }
    return deps
  }

  async deleteSessions() {
    const rx = this.read()
    const existingSessionsPromise = rx.getSessions()
    rx.tryFlush()

    const existingSessions = await existingSessionsPromise

    // Remove batches
    const coreTx = this.write()
    coreTx.setSessions([]) // Clear sessions record
    await coreTx.flush()

    const tx = this.db.write({ autoDestroy: true })

    for (const { dataPointer } of existingSessions) {
      const start = core.data(dataPointer)
      const end = core.data(dataPointer + 1)
      tx.tryDeleteRange(start, end)
    }

    await tx.flush()
  }

  read(fork = -1) {
    let treeCache = null
    if (!this.atom) treeCache = this.treeCache

    return new CoreRX(this.core, this.db, this.view, treeCache, this.store.stats, fork)
  }

  write() {
    return new CoreTX(this.core, this.db, this.atom ? this.view : null, [])
  }

  close() {
    if (this.view !== null) {
      this.view.readStop()
      this.view = null
    }

    return this.db.close()
  }

  static async export(ptr, db, { batches = false } = {}) {
    const rx = new CoreRX(ptr, db, EMPTY, null, null, -1)

    const core = {
      head: null,
      auth: null,
      sessions: [],
      data: null
    }

    const sessionsPromise = rx.getSessions()
    const headPromise = rx.getHead()
    const authPromise = rx.getAuth()

    rx.tryFlush()

    const [sessions, head, auth] = await Promise.all([sessionsPromise, headPromise, authPromise])

    core.head = head
    core.auth = { ...auth, keyPair: null }
    if (sessions) core.sessions = sessions.map((s) => s.name)

    const data = []

    data.push(exportData(ptr, db))

    if (batches) {
      for (const { dataPointer } of sessions) {
        data.push(exportData({ dataPointer, dependencies: [] }, db))
      }
    }

    core.data = await Promise.all(data)

    return core
  }

  static isParentStorage(storage, parent) {
    const last = getLastDependency(storage)
    return !!last && last.dataPointer === parent.core.dataPointer
  }
}

class CorestoreStorage {
  constructor(db, opts = {}) {
    const storage = typeof db === 'string' ? db : null

    this.bootstrap = storage !== null
    this.path = storage !== null ? storage : path.join(db.path, '..')
    this.alwaysRecover = !!opts.alwaysRecover
    this.readOnly = !!opts.readOnly
    this.allowBackup = !!opts.allowBackup
    this.deviceFile = null
    this.wait = !!opts.wait

    // tmp sync fix for simplicty since not super deployed yet
    if (this.bootstrap && !this.readOnly) tmpFixStorage(this.path)

    this.id = opts.id || null
    this.view = null
    this.enters = 0
    this.lock = new ScopeLock()
    this.flushing = null
    this.version = 0
    this.migrating = null

    if ((this.bootstrap && !this.readOnly && !this.allowBackup) || this.wait) {
      const corestoreFile = path.join(this.path, 'CORESTORE')

      this.deviceFile = new DeviceFile(corestoreFile, {
        lock: true,
        wait: this.wait,
        data: { id: this.id }
      })
    }

    const dbPath = path.join(this.path, 'db')

    this.stats = {
      treeCache: {
        hits: 0,
        misses: 0,
        parallel: 0,
        skips: 0
      }
    }
    this.rocks = storage === null ? db : new RocksDB(dbPath, { ...opts, lock: this.deviceFile })
    this.db = createColumnFamily(this.rocks, opts)

    const { treeCache = { maxSize: 8192 } } = opts
    this.treeCache = new Xache(treeCache)
  }

  get opened() {
    return this.db.opened
  }

  get closed() {
    return this.db.closed
  }

  async ready() {
    if (this.version === 0) await this._migrateStore()
    return this.db.ready()
  }

  compact(opts) {
    return this.db.compactRange(opts)
  }

  async audit() {
    for await (const { core } of this.createCoreStream()) {
      const coreRx = new CoreRX(core, this.db, EMPTY, this.treeCache, this.stats, -1)
      const authPromise = coreRx.getAuth()

      coreRx.tryFlush()

      const auth = await authPromise

      if (!auth.manifest || auth.manifest.version > 0) continue
      if (auth.manifest.linked === null) continue

      auth.manifest.linked = null
      const coreTx = new CoreTX(core, this.db, null, [])
      coreTx.setAuth(auth)
      await coreTx.flush()
    }
  }

  async deleteCore(ptr) {
    const rx = new CoreRX(ptr, this.db, EMPTY, this.treeCache, this.stats, -1)

    const authPromise = rx.getAuth()
    const sessionsPromise = rx.getSessions()

    rx.tryFlush()

    const auth = await authPromise
    const sessions = await sessionsPromise

    // no core stored here
    if (!auth) return

    const tx = this.db.write({ autoDestroy: true })

    tx.tryDelete(store.core(auth.discoveryKey))

    // clear core
    const start = core.core(ptr.corePointer)
    const end = core.core(ptr.corePointer + 1)
    tx.tryDeleteRange(start, end)

    if (sessions) {
      for (const { dataPointer } of sessions) {
        const start = core.data(dataPointer)
        const end = core.data(dataPointer + 1)
        tx.tryDeleteRange(start, end)
      }
    }

    return tx.flush()
  }

  static isCoreStorage(db) {
    return isCorestoreStorage(db)
  }

  static from(db) {
    if (isCorestoreStorage(db)) return db
    return new this(db)
  }

  async _flush() {
    while (this.enters > 0) {
      await this.lock.lock()
      await this.lock.unlock()
    }
  }

  async _maybeRecover(view) {
    const r = path.join(this.db.path, 'RECOVERING')
    if (!(await fileExists(r))) return
    await this.setRecovering(view)
    await fs.promises.unlink(r)
  }

  _maybeRecoverLater(err) {
    if (!shouldRecover(err) && !this.alwaysRecover) return
    fs.writeFileSync(path.join(this.db.path, 'RECOVERING'), '')
    for (const file of fs.readdirSync(this.db.path)) {
      if (file.endsWith('.log')) fs.unlinkSync(path.join(this.db.path, file))
    }
  }

  // runs pre any other mutation and read
  async _migrateStore() {
    const view = await this._enter()

    try {
      if (this.version === VERSION) return

      await this._preopen

      try {
        await this.db.ready()
      } catch (err) {
        this._maybeRecoverLater(err)
        throw err
      }

      await this._maybeRecover(view)

      const rx = new CorestoreRX(this.db, view)
      const headPromise = rx.getHead()

      rx.tryFlush()
      const head = await headPromise

      const version = head === null ? 0 : head.version
      if (version === VERSION) {
        this.version = VERSION
        return
      }

      const target = { version: VERSION, dryRun: false }

      switch (version) {
        case 0: {
          await require('./migrations/0').store(this, target)
          break
        }
        case 1: {
          // implicit migration on write
          break
        }
        default: {
          throw new Error(
            'Unsupported version: ' + version + ' - you should probably upgrade your dependencies'
          )
        }
      }

      this.version = VERSION
    } finally {
      await this._exit()
    }
  }

  // runs pre the core is returned to the user
  async _migrateCore(core, discoveryKey, version, locked) {
    const view = locked ? this.view : await this._enter()
    try {
      if (version === VERSION) return

      const target = { version: VERSION, dryRun: false }

      switch (version) {
        case 0: {
          await require('./migrations/0').core(core, target)
          break
        }
        case 1: {
          await require('./migrations/1').core(core, target)
          break
        }
        default: {
          throw new Error(
            'Unsupported version: ' + version + ' - you should probably upgrade your dependencies'
          )
        }
      }

      if (locked === false) return

      // if its locked, then move the core state into the memview
      // in case the core is reopened from the memview, pre flush

      const rx = new CorestoreRX(this.db, EMPTY)
      const tx = new CorestoreTX(view)

      const corePromise = rx.getCore(discoveryKey)
      rx.tryFlush()

      tx.putCore(discoveryKey, await corePromise)
      tx.apply()
    } finally {
      if (!locked) await this._exit()
    }
  }

  async _enter() {
    this.enters++
    await this.lock.lock()
    if (this.view === null) this.view = new View()
    return this.view
  }

  async _exit() {
    this.enters--

    if (this.flushing === null) this.flushing = rrp()
    const flushed = this.flushing.promise

    if (this.enters === 0 || this.view.size() > 128) {
      try {
        await View.flush(this.view.changes, this.db)
        this.flushing.resolve()
      } catch (err) {
        this.flushing.reject(err)
      } finally {
        this.flushing = null
        this.view = null
      }
    }

    this.lock.unlock()
    return flushed
  }

  // when used with core catches this isnt transactional for simplicity, HOWEVER, its just a number
  // so worth the tradeoff
  async _allocData() {
    let dataPointer = 0

    const view = await this._enter()
    const tx = new CorestoreTX(view)

    try {
      const head = await this._getHead(view)

      dataPointer = head.datas++

      tx.setHead(head)
      tx.apply()
    } finally {
      await this._exit()
    }

    return dataPointer
  }

  // exposes here so migrations can easily access the head in an init state
  async _getHead(view) {
    const rx = new CorestoreRX(this.db, view)
    const headPromise = rx.getHead()
    rx.tryFlush()

    const head = await headPromise
    return head === null ? initStoreHead() : head
  }

  createAtom() {
    return new Atom(this.db)
  }

  async flush() {
    await this.rocks.flush()
  }

  async close() {
    if (this.db.closed) return
    await this._flush()
    await this.db.close()
    await this.rocks.close()
    this.treeCache.destroy()
    if (this.deviceFile) await this.deviceFile.close()
  }

  async clear() {
    if (this.version === 0) await this._migrateStore()

    const view = await this._enter()
    const tx = new CorestoreTX(view)

    tx.clear()
    tx.apply()

    await this._exit()
  }

  createCoreStream() {
    // TODO: be nice to run the mgiration here also, but too much plumbing atm
    return createCoreStream(this.db, EMPTY)
  }

  createAliasStream(namespace) {
    // TODO: be nice to run the mgiration here also, but too much plumbing atm
    return createAliasStream(this.db, EMPTY, namespace)
  }

  createDiscoveryKeyStream(namespace) {
    return createDiscoveryKeyStream(this.db, EMPTY, namespace)
  }

  createGroupUpdateStream(group, { since, reverse } = {}) {
    return createGroupUpdateStream(this.db, EMPTY, group, { gte: since, reverse })
  }

  async getAlias(alias) {
    if (this.version === 0) await this._migrateStore()

    const rx = new CorestoreRX(this.db, EMPTY)
    const discoveryKeyPromise = rx.getCoreByAlias(alias)
    rx.tryFlush()
    return discoveryKeyPromise
  }

  async getSeed() {
    if (this.version === 0) await this._migrateStore()

    const rx = new CorestoreRX(this.db, EMPTY)
    const headPromise = rx.getHead()

    rx.tryFlush()

    const head = await headPromise
    return head === null ? null : head.seed
  }

  async getGroup(topic) {
    if (this.version === 0) await this._migrateStore()

    const rx = new CorestoreRX(this.db, EMPTY)
    const groupPromise = rx.getGroup(topic)
    rx.tryFlush()
    return groupPromise
  }

  async setSeed(seed, { overwrite = true } = {}) {
    if (this.version === 0) await this._migrateStore()

    const view = await this._enter()
    const tx = new CorestoreTX(view)

    try {
      const rx = new CorestoreRX(this.db, view)
      const headPromise = rx.getHead()

      rx.tryFlush()

      const head = (await headPromise) || initStoreHead()

      if (head.seed === null || overwrite) head.seed = seed
      tx.setHead(head)
      tx.apply()

      return head.seed
    } finally {
      await this._exit()
    }
  }

  async setRecovering(view = null) {
    const recovering = Date.now()
    let keys = []

    for await (const data of this.createDiscoveryKeyStream()) {
      keys.push(data)
      if (keys.length > 16 * 1024) {
        await this._flushRecovering(recovering, view, keys)
        keys = []
      }
    }

    if (keys.length) await this._flushRecovering(recovering, view, keys)
  }

  async _flushRecovering(recovering, view, keys) {
    const own = !view
    if (own) view = await this._enter()
    const tx = new CorestoreTX(view)

    try {
      const infos = await this._getInfos(keys, { auth: false, head: false, hints: true })
      for (const inf of infos) {
        tx.setCoreHints(inf.core, { ...inf.hints, recovering })
      }
      tx.apply()
    } finally {
      if (own) await this._exit()
    }
  }

  async getDefaultDiscoveryKey() {
    if (this.version === 0) await this._migrateStore()

    const rx = new CorestoreRX(this.db, EMPTY)
    const headPromise = rx.getHead()

    rx.tryFlush()

    const head = await headPromise
    return head === null ? null : head.defaultDiscoveryKey
  }

  async setDefaultDiscoveryKey(discoveryKey, { overwrite = true } = {}) {
    if (this.version === 0) await this._migrateStore()

    const view = await this._enter()
    const tx = new CorestoreTX(view)

    try {
      const rx = new CorestoreRX(this.db, view)
      const headPromise = rx.getHead()

      rx.tryFlush()

      const head = (await headPromise) || initStoreHead()

      if (head.defaultDiscoveryKey === null || overwrite) head.defaultDiscoveryKey = discoveryKey
      tx.setHead(head)
      tx.apply()

      return head.defaultDiscoveryKey
    } finally {
      await this._exit()
    }
  }

  async hasCore(discoveryKey, { ifMigrated = false } = {}) {
    if (this.version === 0) await this._migrateStore()

    const rx = new CorestoreRX(this.db, EMPTY)
    const promise = rx.getCore(discoveryKey)

    rx.tryFlush()

    const core = await promise

    if (core === null) return false
    if (core.version === 0 && ifMigrated) return false

    return true
  }

  async getAuth(discoveryKey) {
    if (this.version === 0) await this._migrateStore()

    const rx = new CorestoreRX(this.db, EMPTY)
    const corePromise = rx.getCore(discoveryKey)

    rx.tryFlush()

    const core = await corePromise
    if (core === null) return null

    const read = this.db.read({ autoDestroy: true })
    const authPromise = CoreRX.getAuth(read, core)

    read.tryFlush()

    return authPromise
  }

  async getInfo(discoveryKey, opts) {
    return (await this.getInfos([discoveryKey], opts))[0]
  }

  async getInfos(discoveryKeys, opts) {
    if (this.version === 0) await this._migrateStore()
    return this._getInfos(discoveryKeys, opts)
  }

  async _getInfos(discoveryKeys, { auth = true, head = true, hints = true } = {}) {
    const rx = new CorestoreRX(this.db, EMPTY)
    const corePromises = new Array(discoveryKeys.length)

    for (let i = 0; i < discoveryKeys.length; i++) {
      corePromises[i] = rx.getCore(discoveryKeys[i])
    }

    rx.tryFlush()

    const cores = await Promise.all(corePromises)
    const read = this.db.read({ autoDestroy: true })

    const resultPromises = new Array(cores.length)

    for (let i = 0; i < cores.length; i++) {
      resultPromises[i] = cores[i]
        ? getInfoFromBatch(read, cores[i], discoveryKeys[i], auth, head, hints)
        : null
    }

    read.tryFlush()

    return Promise.all(resultPromises)
  }

  async suspend({ log = noop } = {}) {
    await log('Suspending hypercore storage...')
    await this.db.suspend()
    await log('db suspended')
    if (this.deviceFile) {
      await log('suspend deviceFile')
      await this.deviceFile.suspend()
      await log('deviceFile suspended')
    }
  }

  async resume() {
    if (this.deviceFile) await this.deviceFile.resume()
    await this.db.resume()
  }

  async resumeCore(discoveryKey) {
    if (this.version === 0) await this._migrateStore()

    if (!discoveryKey) {
      discoveryKey = await this.getDefaultDiscoveryKey()
      if (!discoveryKey) return null
    }

    const rx = new CorestoreRX(this.db, EMPTY)
    const corePromise = rx.getCore(discoveryKey)

    rx.tryFlush()
    const core = await corePromise

    if (core === null) return null
    return this._resumeFromPointers(EMPTY, discoveryKey, false, core)
  }

  async export(discoveryKey, opts) {
    const rx = new CorestoreRX(this.db, EMPTY)
    const corePromise = rx.getCore(discoveryKey)

    rx.tryFlush()
    const core = await corePromise
    if (core === null) return null

    let { dataPointer, corePointer } = core

    const ptr = { corePointer, dataPointer, dependencies: [] }

    while (true) {
      const rx = new CoreRX(
        { dataPointer, corePointer: 0, dependencies: [] },
        this.db,
        EMPTY,
        this.treeCache,
        this.stats,
        -1
      )
      const dependencyPromise = rx.getDependency()
      rx.tryFlush()
      const dependency = await dependencyPromise
      if (!dependency) break
      ptr.dependencies.push(dependency)
      dataPointer = dependency.dataPointer
    }

    const session = this.db.session()
    try {
      return await HypercoreStorage.export(ptr, session, opts)
    } finally {
      await session.close()
    }
  }

  async _resumeFromPointers(view, discoveryKey, create, { version, corePointer, dataPointer }) {
    const core = { corePointer, dataPointer, dependencies: [] }

    while (true) {
      const rx = new CoreRX(
        { dataPointer, corePointer: 0, dependencies: [] },
        this.db,
        view,
        this.treeCache,
        this.stats,
        -1
      )
      const dependencyPromise = rx.getDependency()
      rx.tryFlush()
      const dependency = await dependencyPromise
      if (!dependency) break
      core.dependencies.push(dependency)
      dataPointer = dependency.dataPointer
    }

    const result = new HypercoreStorage(this, this.db.session(), this.treeCache, core, EMPTY, null)

    if (version < VERSION) await this._migrateCore(result, discoveryKey, version, create)
    return result
  }

  // not allowed to throw validation errors as its a shared tx!
  async _create(
    view,
    { key, manifest, keyPair, encryptionKey, discoveryKey, alias, userData, core: ptrs }
  ) {
    const rx = new CorestoreRX(this.db, view)
    const tx = new CorestoreTX(view)

    const corePromise = rx.getCore(discoveryKey)
    const headPromise = rx.getHead()

    rx.tryFlush()

    let [core, head] = await Promise.all([corePromise, headPromise])
    if (core) return this._resumeFromPointers(view, discoveryKey, true, core)

    if (head === null) head = initStoreHead()
    if (head.defaultDiscoveryKey === null) head.defaultDiscoveryKey = discoveryKey

    const corePointer = ptrs ? ptrs.corePointer : head.cores++
    const dataPointer = ptrs ? ptrs.dataPointer : head.datas++

    core = { version: VERSION, corePointer, dataPointer, alias }

    tx.setHead(head)
    tx.putCore(discoveryKey, core)
    if (alias) tx.putCoreByAlias(alias, discoveryKey)

    const ptr = { corePointer, dataPointer, dependencies: [] }
    const ctx = new CoreTX(ptr, this.db, view, tx.changes)

    ctx.setAuth({
      key,
      discoveryKey,
      manifest,
      keyPair,
      encryptionKey
    })

    if (userData) {
      for (const { key, value } of userData) {
        ctx.putUserData(key, value)
      }
    }

    tx.apply()

    return new HypercoreStorage(this, this.db.session(), this.treeCache, ptr, EMPTY, null)
  }

  async createCore(data) {
    if (this.version === 0) await this._migrateStore()

    const view = await this._enter()

    try {
      return await this._create(view, data)
    } finally {
      await this._exit()
    }
  }

  // not allowed to throw validation errors as its a shared tx!
  async _createGroup(view, topic) {
    const rx = new CorestoreRX(this.db, view)
    const tx = new CorestoreTX(view)

    const groupPromise = rx.getGroup(topic)
    const headPromise = rx.getHead()

    rx.tryFlush()

    let group = await groupPromise
    if (group !== null) {
      return {
        key: topic,
        pointer: group
      }
    }

    let head = await headPromise
    if (head === null) head = initStoreHead()

    group = head.groups++

    tx.putGroup(topic, group)
    tx.setHead(head)
    tx.apply()

    return {
      key: topic,
      pointer: group
    }
  }

  async createGroup(topic) {
    if (this.version === 0) await this._migrateStore()

    const view = await this._enter()

    try {
      return await this._createGroup(view, topic)
    } finally {
      await this._exit()
    }
  }

  static isParentStorage(storage, parent) {
    return HypercoreStorage.isParentStorage(storage, parent)
  }
}

module.exports = CorestoreStorage

function initStoreHead() {
  return {
    version: 0, // cause we wanna run the migration
    datas: 0,
    cores: 0,
    groups: 0,
    seed: null,
    defaultDiscoveryKey: null
  }
}

function getSession(sessions, name, alloc) {
  for (let i = 0; i < sessions.length; i++) {
    if (sessions[i].name === name) return sessions[i]
  }

  if (!alloc) return null

  const result = { name, dataPointer: -1 }
  sessions.push(result)
  return result
}

function isCorestoreStorage(s) {
  return typeof s === 'object' && !!s && typeof s.setDefaultDiscoveryKey === 'function'
}

function createColumnFamily(db, opts = {}) {
  const {
    tableCacheIndexAndFilterBlocks = true,
    blockCache = true,
    optimizeFiltersForMemory = false,
    blobFileSize = 256 * 1024 * 1024,
    blobGarbageCollectionAgeCutOff = 0.25,
    blobGarbageCollectionForceThreshold = 1.0
  } = opts

  const col = new RocksDB.ColumnFamily(COLUMN_FAMILY, {
    enableBlobFiles: true,
    minBlobSize: 4096,
    blobFileSize,
    enableBlobGarbageCollection: true,
    tableBlockSize: 8192,
    tableCacheIndexAndFilterBlocks,
    tableFormatVersion: 6,
    optimizeFiltersForMemory,
    blockCache,
    blobGarbageCollectionAgeCutOff,
    blobGarbageCollectionForceThreshold
  })

  return db.columnFamily(col)
}

function getLastDependency(storage) {
  return storage.dependencies.length ? storage.dependencies[storage.dependencies.length - 1] : null
}

// TODO: remove in like 3-6 mo
function tmpFixStorage(p) {
  // if CORESTORE file is written, new format
  if (fs.existsSync(path.join(p, 'CORESTORE'))) return

  let files = []

  try {
    files = fs.readdirSync(p)
  } catch {}

  const notRocks = new Set([
    'CORESTORE',
    'primary-key',
    'cores',
    'app-preferences',
    'cache',
    'preferences.json',
    'db',
    'clone',
    'core',
    'notifications'
  ])

  for (const f of files) {
    if (notRocks.has(f)) continue

    try {
      fs.mkdirSync(path.join(p, 'db'))
    } catch {}

    fs.renameSync(path.join(p, f), path.join(p, 'db', f))
  }
}

async function exportData(ptr, db, opts) {
  // just need dataPointer
  const reads = [
    toArray(createBlockStream(ptr, db, EMPTY, opts)),
    toArray(createTreeNodeStream(ptr, db, EMPTY, opts)),
    toArray(createBitfieldStream(ptr, db, EMPTY, opts))
  ]

  const [blocks, tree, bitfield] = await Promise.all(reads)

  return {
    blocks,
    tree,
    bitfield
  }
}

async function toArray(stream) {
  const all = []
  for await (const e of stream) all.push(e)
  return all
}

async function fileExists(path) {
  try {
    await fs.promises.stat(path)
    return true
  } catch {
    return false
  }
}

function shouldRecover(err) {
  return err.message.toLowerCase().indexOf('sequence number is being set backwards') > -1
}

function noop() {}

async function getInfoFromBatch(db, c, discoveryKey, getAuth, getHead, getHints) {
  const authPromise = getAuth ? CoreRX.getAuth(db, c) : null
  const headPromise = getHead ? CoreRX.getHead(db, c) : null
  const hintsPromise = getHints ? CoreRX.getHints(db, c) : null

  // ensure no uncaughts
  if (authPromise) authPromise.catch(noop)
  if (headPromise) headPromise.catch(noop)
  if (hintsPromise) hintsPromise.catch(noop)

  return {
    discoveryKey,
    core: c,
    auth: await authPromise,
    head: await headPromise,
    hints: await hintsPromise
  }
}
const { Readable, getStreamError } = require('streamx')
const { core } = require('./keys')

module.exports = class BlockStream extends Readable {
  constructor(core, db, updates, start, end, reverse) {
    super()

    this.core = core
    this.db = db
    this.updates = updates
    this.start = start
    this.end = end
    this.reverse = reverse === true

    this._drained = true
    this._consumed = 0
    this._stream = null
    this._oncloseBound = this._onclose.bind(this)
    this._maybeDrainBound = this._maybeDrain.bind(this)

    this._update()
  }

  _update() {
    if (this._consumed > this.core.dependencies.length) return

    const deps = this.core.dependencies
    const index = this._findDependencyIndex(deps)

    const curr = index < deps.length ? deps[index] : null
    const prev = index > 0 && index - 1 < deps.length ? deps[index - 1] : null

    const start = prev && prev.length > this.start ? prev.length : this.start
    const end = curr && (this.end === -1 || curr.length < this.end) ? curr.length : this.end

    const ptr = curr ? curr.dataPointer : this.core.dataPointer

    this._makeStream(core.block(ptr, start), core.block(ptr, end))
  }

  _findDependencyIndex(deps) {
    if (!this.reverse) return this._consumed++

    let i = deps.length - this._consumed++
    while (i > 0) {
      if (deps[i - 1].length <= this.end) return i
      i--
      this._consumed++
    }

    return 0
  }

  _predestroy() {
    if (this._stream !== null) this._stream.destroy()
  }

  _read(cb) {
    this._drained = this._onreadable()
    cb(null)
  }

  _maybeDrain() {
    if (this._drained === true) return
    this._drained = this._onreadable()
  }

  _onreadable() {
    if (this._stream === null) {
      this.push(null)
      return true
    }

    let data = this._stream.read()

    if (data === null) return false

    do {
      this.push(data)
      data = this._stream.read()
    } while (data !== null)

    return true
  }

  _onclose() {
    if (this.destroying) return

    const err = getStreamError(this._stream)

    if (err !== null) {
      this.destroy(err)
      return
    }

    // empty the current stream
    if (this._onreadable() === true) this._drained = true

    this._stream = null

    this._update()
    this._maybeDrain()
  }

  _makeStream(start, end) {
    this._stream = this.updates.iterator(this.db, start, end, this.reverse)
    this._stream.on('readable', this._maybeDrainBound)
    this._stream.on('error', noop)
    this._stream.on('close', this._oncloseBound)
  }
}

function noop() {}
const { Readable } = require('streamx')

// used for returned a stream that just errors (during read during teardown)

module.exports = class CloseErrorStream extends Readable {
  constructor(err) {
    super()
    this.error = err
  }

  _open(cb) {
    cb(this.error)
  }
}
const { UINT, STRING } = require('index-encoder')
const c = require('compact-encoding')
const b4a = require('b4a')

const TL_HEAD = 0
const TL_CORE_BY_DKEY = 1
const TL_CORE_BY_ALIAS = 2
const TL_CORE = 3
const TL_DATA = 4
const TL_GROUP_BY_TOPIC = 5
const TL_GROUP = 6

const TL_END = TL_GROUP + 1

const CORE_AUTH = 0
const CORE_SESSIONS = 1
const CORE_GROUP = 2

const DATA_HEAD = 0
const DATA_DEPENDENCY = 1
const DATA_HINTS = 2
const DATA_BLOCK = 3
const DATA_TREE = 4
const DATA_BITFIELD = 5
const DATA_USER_DATA = 6
const DATA_LOCAL = 7
const DATA_MARK = 8

const slab = { buffer: b4a.allocUnsafe(65536), start: 0, end: 0 }

const store = {}
const core = {}
const group = {}

store.clear = function () {
  const state = alloc()
  let start = state.start
  UINT.encode(state, 0)
  const a = state.buffer.subarray(start, state.start)
  start = state.start
  UINT.encode(state, TL_END)
  const b = state.buffer.subarray(start, state.start)
  return [a, b]
}

store.head = function () {
  const state = alloc()
  const start = state.start
  UINT.encode(state, TL_HEAD)
  return state.buffer.subarray(start, state.start)
}

store.core = function (discoveryKey) {
  const state = alloc()
  const start = state.start
  UINT.encode(state, TL_CORE_BY_DKEY)
  c.fixed32.encode(state, discoveryKey)
  return state.buffer.subarray(start, state.start)
}

store.coreStart = function () {
  const state = alloc()
  const start = state.start
  UINT.encode(state, TL_CORE_BY_DKEY)
  return state.buffer.subarray(start, state.start)
}

store.coreEnd = function () {
  const state = alloc()
  const start = state.start
  UINT.encode(state, TL_CORE_BY_DKEY + 1)
  return state.buffer.subarray(start, state.start)
}

store.coreByAlias = function ({ namespace, name }) {
  const state = alloc()
  const start = state.start
  UINT.encode(state, TL_CORE_BY_ALIAS)
  c.fixed32.encode(state, namespace)
  STRING.encode(state, name)
  return state.buffer.subarray(start, state.start)
}

store.coreByAliasStart = function (namespace) {
  const state = alloc()
  const start = state.start
  UINT.encode(state, TL_CORE_BY_ALIAS)
  if (namespace) c.fixed32.encode(state, namespace)
  return state.buffer.subarray(start, state.start)
}

store.coreByAliasEnd = function (namespace) {
  const state = alloc()
  const start = state.start

  if (namespace) {
    UINT.encode(state, TL_CORE_BY_ALIAS)
    c.fixed32.encode(state, namespace)
    state.buffer[state.start++] = 0xff
  } else {
    UINT.encode(state, TL_CORE_BY_ALIAS + 1)
  }

  return state.buffer.subarray(start, state.start)
}

store.alias = function (buffer) {
  const state = { buffer, start: 0, end: buffer.byteLength }
  UINT.decode(state) // ns
  const namespace = c.fixed32.decode(state)
  const name = STRING.decode(state)
  return { namespace, name }
}

store.discoveryKey = function (buffer) {
  const state = { buffer, start: 0, end: buffer.byteLength }
  UINT.decode(state) // ns
  return c.fixed32.decode(state)
}

core.core = function (ptr) {
  const state = alloc()
  const start = state.start
  UINT.encode(state, TL_CORE)
  UINT.encode(state, ptr)
  return state.buffer.subarray(start, state.start)
}

core.data = function (ptr) {
  const state = alloc()
  const start = state.start
  UINT.encode(state, TL_DATA)
  UINT.encode(state, ptr)
  return state.buffer.subarray(start, state.start)
}

core.auth = function (ptr) {
  const state = alloc()
  const start = state.start
  UINT.encode(state, TL_CORE)
  UINT.encode(state, ptr)
  UINT.encode(state, CORE_AUTH)
  return state.buffer.subarray(start, state.start)
}

core.group = function (ptr) {
  const state = alloc()
  const start = state.start
  UINT.encode(state, TL_CORE)
  UINT.encode(state, ptr)
  UINT.encode(state, CORE_GROUP)
  return state.buffer.subarray(start, state.start)
}

core.sessions = function (ptr) {
  const state = alloc()
  const start = state.start
  UINT.encode(state, TL_CORE)
  UINT.encode(state, ptr)
  UINT.encode(state, CORE_SESSIONS)
  return state.buffer.subarray(start, state.start)
}

core.head = function (ptr) {
  const state = alloc()
  const start = state.start
  UINT.encode(state, TL_DATA)
  UINT.encode(state, ptr)
  UINT.encode(state, DATA_HEAD)
  return state.buffer.subarray(start, state.start)
}

core.dependency = function (ptr) {
  const state = alloc()
  const start = state.start
  UINT.encode(state, TL_DATA)
  UINT.encode(state, ptr)
  UINT.encode(state, DATA_DEPENDENCY)
  return state.buffer.subarray(start, state.start)
}

core.hints = function (ptr) {
  const state = alloc()
  const start = state.start
  UINT.encode(state, TL_DATA)
  UINT.encode(state, ptr)
  UINT.encode(state, DATA_HINTS)
  return state.buffer.subarray(start, state.start)
}

core.block = function (ptr, index) {
  const state = alloc()
  const start = state.start
  UINT.encode(state, TL_DATA)
  UINT.encode(state, ptr)
  UINT.encode(state, DATA_BLOCK)
  UINT.encode(state, index)
  return state.buffer.subarray(start, state.start)
}

core.tree = function (ptr, index) {
  const state = alloc()
  const start = state.start
  UINT.encode(state, TL_DATA)
  UINT.encode(state, ptr)
  UINT.encode(state, DATA_TREE)
  UINT.encode(state, index)
  return state.buffer.subarray(start, state.start)
}

core.bitfield = function (ptr, index, type) {
  const state = alloc()
  const start = state.start
  UINT.encode(state, TL_DATA)
  UINT.encode(state, ptr)
  UINT.encode(state, DATA_BITFIELD)
  UINT.encode(state, index)
  UINT.encode(state, type)
  return state.buffer.subarray(start, state.start)
}

core.userData = function (ptr, key) {
  const state = alloc()
  const start = state.start
  UINT.encode(state, TL_DATA)
  UINT.encode(state, ptr)
  UINT.encode(state, DATA_USER_DATA)
  STRING.encode(state, key)
  return state.buffer.subarray(start, state.start)
}

core.userDataEnd = function (ptr) {
  const state = alloc()
  const start = state.start
  UINT.encode(state, TL_DATA)
  UINT.encode(state, ptr)
  UINT.encode(state, DATA_USER_DATA + 1)
  return state.buffer.subarray(start, state.start)
}

core.mark = function (ptr, index) {
  const state = alloc()
  const start = state.start
  UINT.encode(state, TL_DATA)
  UINT.encode(state, ptr)
  UINT.encode(state, DATA_MARK)
  UINT.encode(state, index)
  return state.buffer.subarray(start, state.start)
}

core.local = function (ptr, key) {
  if (key.byteLength > 2048) {
    throw new Error('local keys has an upper limit of 2048 bytes atm')
  }

  const state = alloc()
  const start = state.start
  UINT.encode(state, TL_DATA)
  UINT.encode(state, ptr)
  UINT.encode(state, DATA_LOCAL)

  state.buffer.set(key, state.start)
  state.start += key.byteLength
  return state.buffer.subarray(start, state.start)
}

core.localEnd = function (ptr) {
  const state = alloc()
  const start = state.start
  UINT.encode(state, TL_DATA)
  UINT.encode(state, ptr)
  UINT.encode(state, DATA_LOCAL + 1)
  return state.buffer.subarray(start, state.start)
}

core.blockIndex = function (buffer) {
  const state = { buffer, start: 0, end: buffer.byteLength }
  UINT.decode(state) // ns
  UINT.decode(state) // ptr
  UINT.decode(state) // type
  return UINT.decode(state)
}

core.bitfieldIndexAndType = function (buffer) {
  const state = { buffer, start: 0, end: buffer.byteLength }
  UINT.decode(state) // ns
  UINT.decode(state) // ptr
  UINT.decode(state) // type
  return [UINT.decode(state), UINT.decode(state)]
}

core.userDataKey = function (buffer) {
  const state = { buffer, start: 0, end: buffer.byteLength }
  UINT.decode(state) // ns
  UINT.decode(state) // ptr
  UINT.decode(state) // type
  return STRING.decode(state)
}

core.markKey = function (buffer) {
  const state = { buffer, start: 0, end: buffer.byteLength }
  UINT.decode(state) // ns
  UINT.decode(state) // ptr
  UINT.decode(state) // type
  return UINT.decode(state)
}

core.localKey = function (buffer) {
  const state = { buffer, start: 0, end: buffer.byteLength }
  UINT.decode(state) // ns
  UINT.decode(state) // ptr
  UINT.decode(state) // type
  return state.buffer.subarray(state.start, state.end)
}

group.topic = function (topic) {
  const state = alloc()
  const start = state.start
  UINT.encode(state, TL_GROUP_BY_TOPIC)
  c.fixed32.encode(state, topic)
  return state.buffer.subarray(start, state.start)
}

group.entry = function (group, timestamp, ptr) {
  const state = alloc()
  const start = state.start
  UINT.encode(state, TL_GROUP)
  UINT.encode(state, group)
  c.uint64be.encode(state, timestamp)
  UINT.encode(state, ptr)
  return state.buffer.subarray(start, state.start)
}

module.exports = { store, core, group }

function alloc() {
  if (slab.buffer.byteLength - slab.start < 4096) {
    slab.buffer = b4a.allocUnsafe(slab.buffer.byteLength)
    slab.start = 0
  }
  return slab
}
const b4a = require('b4a')
const BlockDependencyStream = require('./block-dependency-stream.js')
const { core, store, group } = require('./keys.js')
const schema = require('../encoding/spec/hyperschema')

const CORESTORE_CORE = schema.getEncoding('@corestore/core')
const CORE_TREE_NODE = schema.getEncoding('@core/tree-node')
const EMPTY = b4a.alloc(0)

module.exports = {
  createBlockStream,
  createBitfieldStream,
  createUserDataStream,
  createCoreStream,
  createAliasStream,
  createDiscoveryKeyStream,
  createTreeNodeStream,
  createMarkStream,
  createLocalStream,
  createGroupUpdateStream
}

function createCoreStream(db, view) {
  const start = store.coreStart()
  const end = store.coreEnd()

  const ite = view.iterator(db, start, end, false)

  ite._readableState.map = mapCore
  return ite
}

function createDiscoveryKeyStream(db, view, namespace) {
  const start = namespace ? store.coreByAliasStart(namespace) : store.coreStart()
  const end = namespace ? store.coreByAliasEnd(namespace) : store.coreEnd()

  const ite = view.iterator(db, start, end, false)

  ite._readableState.map = namespace ? mapNamespaceDiscoveryKeys : mapAllDiscoveryKeys
  return ite
}

function createAliasStream(db, view, namespace) {
  const start = store.coreByAliasStart(namespace)
  const end = store.coreByAliasEnd(namespace)

  const ite = view.iterator(db, start, end, false)

  ite._readableState.map = mapAlias
  return ite
}

function createGroupUpdateStream(db, view, groupID, { gte = 0, reverse = true } = {}) {
  const start = group.entry(groupID, gte, 0)
  const end = group.entry(groupID + 1, 0, 0)

  const ite = view.iterator(db, start, end, reverse)

  ite._readableState.map = mapGroupUpdate
  return ite
}

function createBlockIterator(ptr, db, view, start, end, reverse) {
  if (ptr.dependencies.length > 0) {
    return new BlockDependencyStream(ptr, db, view, start, end, reverse)
  }

  const s = core.block(ptr.dataPointer, start)
  const e = core.block(ptr.dataPointer, end === -1 ? Infinity : end)
  return view.iterator(db, s, e, reverse)
}

function createBlockStream(
  ptr,
  db,
  view,
  { gt = -1, gte = gt + 1, lte = -1, lt = lte === -1 ? -1 : lte + 1, reverse = false } = {}
) {
  const ite = createBlockIterator(ptr, db, view, gte, lt, reverse)

  ite._readableState.map = mapBlock
  return ite
}

function createBitfieldStream(
  ptr,
  db,
  view,
  { gt = -1, gte = gt + 1, lte = -1, lt = lte === -1 ? -1 : lte + 1, reverse = false } = {}
) {
  const s = core.bitfield(ptr.dataPointer, gte, 0)
  const e = core.bitfield(ptr.dataPointer, lt === -1 ? Infinity : lt, 0)
  const ite = view.iterator(db, s, e, false)

  ite._readableState.map = mapBitfield
  return ite
}

// NOTE: this does not do dependency lookups atm
function createTreeNodeStream(
  ptr,
  db,
  view,
  { gt = -1, gte = gt + 1, lte = -1, lt = lte === -1 ? -1 : lte + 1, reverse = false } = {}
) {
  const s = core.tree(ptr.dataPointer, gte, 0)
  const e = core.tree(ptr.dataPointer, lt === -1 ? Infinity : lt, 0)
  const ite = view.iterator(db, s, e, false)

  ite._readableState.map = mapTreeNode
  return ite
}

function createUserDataStream(
  ptr,
  db,
  view,
  { gt = null, gte = '', lte = null, lt = null, reverse = false } = {}
) {
  if (gt !== null || lte !== null) {
    throw new Error('gt and lte not yet supported for user data streams')
  }

  const s = core.userData(ptr.dataPointer, gte)
  const e = lt === null ? core.userDataEnd(ptr.dataPointer) : core.userData(ptr.dataPointer, lt)
  const ite = view.iterator(db, s, e, false)

  ite._readableState.map = mapUserData
  return ite
}

function createMarkStream(
  ptr,
  db,
  view,
  { gt = -1, gte = gt + 1, lte = -1, lt = lte === -1 ? -1 : lte + 1, reverse = false } = {}
) {
  const s = core.mark(ptr.dataPointer, gte)
  const e = core.mark(ptr.dataPointer, lt === -1 ? Infinity : lt)
  const ite = view.iterator(db, s, e, reverse)

  ite._readableState.map = mapMark
  return ite
}

function createLocalStream(
  ptr,
  db,
  view,
  { gt = null, gte = EMPTY, lte = null, lt = null, reverse = false } = {}
) {
  if (gt !== null || lte !== null) throw new Error('gt and lte not yet supported for local streams')

  const s = core.local(ptr.dataPointer, gte)
  const e = lt === null ? core.localEnd(ptr.dataPointer) : core.local(ptr.dataPointer, lt)
  const ite = view.iterator(db, s, e, false)

  ite._readableState.map = mapLocal
  return ite
}

function mapBitfield(data) {
  const [index, type] = core.bitfieldIndexAndType(data.key)
  if (type !== 0) return null // ignore for now
  return { index, page: data.value }
}

function mapMark(data) {
  const index = core.markKey(data.key)
  return { index, page: data.value }
}

function mapLocal(data) {
  const key = core.localKey(data.key)
  return { key, value: data.value }
}

function mapGroupUpdate(data) {
  return data.value
}

function mapUserData(data) {
  const key = core.userDataKey(data.key)
  return { key, value: data.value }
}

function mapCore(data) {
  const discoveryKey = store.discoveryKey(data.key)
  const core = CORESTORE_CORE.decode({
    start: 0,
    end: data.value.byteLength,
    buffer: data.value
  })
  return { discoveryKey, core }
}

function mapAllDiscoveryKeys(data) {
  return store.discoveryKey(data.key)
}

function mapNamespaceDiscoveryKeys(data) {
  return data.value
}

function mapAlias(data) {
  const alias = store.alias(data.key)
  return { alias, discoveryKey: data.value }
}

function mapBlock(data) {
  return { index: core.blockIndex(data.key), value: data.value }
}

function mapTreeNode(data) {
  return CORE_TREE_NODE.decode({
    start: 0,
    end: data.value.byteLength,
    buffer: data.value
  })
}
const b4a = require('b4a')
const c = require('compact-encoding')
const flat = require('flat-tree')

const schema = require('../encoding/spec/hyperschema')
const { store, core, group } = require('./keys.js')
const View = require('./view.js')

const CORESTORE_HEAD = schema.getEncoding('@corestore/head')
const CORESTORE_CORE = schema.getEncoding('@corestore/core')
const CORESTORE_GROUP = c.uint

const CORE_AUTH = schema.getEncoding('@core/auth')
const CORE_GROUP = schema.getEncoding('@core/group')
const CORE_SESSIONS = schema.getEncoding('@core/sessions')
const CORE_HEAD = schema.getEncoding('@core/head')
const CORE_TREE_NODE = schema.getEncoding('@core/tree-node')
const CORE_DEPENDENCY = schema.getEncoding('@core/dependency')
const CORE_HINTS = schema.getEncoding('@core/hints')

class CoreTX {
  constructor(core, db, view, changes) {
    if (db.snapshotted) throw new Error('Cannot open core tx on snapshot')
    this.core = core
    this.db = db
    this.view = view
    this.changes = changes
  }

  setAuth(auth) {
    this.changes.push([core.auth(this.core.corePointer), encode(CORE_AUTH, auth), null])
  }

  setSessions(sessions) {
    this.changes.push([core.sessions(this.core.corePointer), encode(CORE_SESSIONS, sessions), null])
  }

  setHead(head) {
    this.changes.push([core.head(this.core.dataPointer), encode(CORE_HEAD, head), null])
  }

  deleteHead() {
    this.changes.push([core.head(this.core.dataPointer), null, null])
  }

  setDependency(dep) {
    this.changes.push([core.dependency(this.core.dataPointer), encode(CORE_DEPENDENCY, dep), null])
  }

  setGroup(group) {
    this.changes.push([core.group(this.core.corePointer), encode(CORE_GROUP, group), null])
  }

  deleteGroup() {
    this.changes.push([core.group(this.core.corePointer), null, null])
  }

  setHints(hints) {
    this.changes.push([core.hints(this.core.dataPointer), encode(CORE_HINTS, hints), null])
  }

  putBlock(index, data) {
    this.changes.push([core.block(this.core.dataPointer, index), data, null])
  }

  deleteBlock(index) {
    this.changes.push([core.block(this.core.dataPointer, index), null, null])
  }

  deleteBlockRange(start, end) {
    this.changes.push([
      core.block(this.core.dataPointer, start),
      null,
      core.block(this.core.dataPointer, end === -1 ? Infinity : end)
    ])
  }

  putBitfieldPage(index, data) {
    this.changes.push([core.bitfield(this.core.dataPointer, index, 0), data, null])
  }

  deleteBitfieldPage(index) {
    this.changes.push([core.bitfield(this.core.dataPointer, index, 0), null, null])
  }

  deleteBitfieldPageRange(start, end) {
    this.changes.push([
      core.bitfield(this.core.dataPointer, start, 0),
      null,
      core.bitfield(this.core.dataPointer, end === -1 ? Infinity : end, 0)
    ])
  }

  putTreeNode(node) {
    this.changes.push([
      core.tree(this.core.dataPointer, node.index),
      encode(CORE_TREE_NODE, node),
      null
    ])
  }

  deleteTreeNode(index) {
    this.changes.push([core.tree(this.core.dataPointer, index), null, null])
  }

  deleteTreeNodeRange(start, end) {
    this.changes.push([
      core.tree(this.core.dataPointer, start),
      null,
      core.tree(this.core.dataPointer, end === -1 ? Infinity : end)
    ])
  }

  putUserData(key, value) {
    const buffer = typeof value === 'string' ? b4a.from(value) : value
    this.changes.push([core.userData(this.core.dataPointer, key), buffer, null])
  }

  deleteUserData(key) {
    this.changes.push([core.userData(this.core.dataPointer, key), null, null])
  }

  putMark(index, data) {
    this.changes.push([core.mark(this.core.dataPointer, index), data, null])
  }

  deleteMark(index) {
    this.changes.push([core.mark(this.core.dataPointer, index), null, null])
  }

  deleteMarkRange(start, end) {
    this.changes.push([
      core.mark(this.core.dataPointer, start),
      null,
      core.mark(this.core.dataPointer, end === -1 ? Infinity : end)
    ])
  }

  putLocal(key, value) {
    this.changes.push([core.local(this.core.dataPointer, key), value, null])
  }

  deleteLocal(key) {
    this.changes.push([core.local(this.core.dataPointer, key), null, null])
  }

  deleteLocalRange(start, end) {
    this.changes.push([
      core.local(this.core.dataPointer, start),
      null,
      end === null ? core.localEnd(this.core.dataPointer) : core.local(this.core.dataPointer, end)
    ])
  }

  putGroupUpdate(groupPtr, timestamp, key) {
    this.changes.push([group.entry(groupPtr, timestamp, this.core.corePointer), key, null])
  }

  deleteGroupUpdate(groupPtr, timestamp) {
    this.changes.push([group.entry(groupPtr, timestamp, this.core.corePointer), null, null])
  }

  flush() {
    const changes = this.changes
    if (changes === null) return Promise.resolve(!this.view)

    this.changes = null

    if (this.view) {
      this.view.apply(changes)
      return Promise.resolve(false)
    }

    return View.flush(changes, this.db)
  }
}

class CoreRX {
  constructor(core, db, view, treeCache, stats, fork) {
    this.core = core
    this.read = db.read({ autoDestroy: true })
    this.view = view
    this.treeCache = treeCache
    this.fork = fork
    this.stats = stats

    view.readStart()
  }

  static async getAuth(db, c) {
    return decode(CORE_AUTH, await db.get(core.auth(c.corePointer)))
  }

  async getAuth() {
    return decode(CORE_AUTH, await this.view.get(this.read, core.auth(this.core.corePointer)))
  }

  async getSessions() {
    return decode(
      CORE_SESSIONS,
      await this.view.get(this.read, core.sessions(this.core.corePointer))
    )
  }

  static async getHead(db, c) {
    return decode(CORE_HEAD, await db.get(core.head(c.dataPointer)))
  }

  async getHead() {
    return decode(CORE_HEAD, await this.view.get(this.read, core.head(this.core.dataPointer)))
  }

  async getDependency() {
    return decode(
      CORE_DEPENDENCY,
      await this.view.get(this.read, core.dependency(this.core.dataPointer))
    )
  }

  async getGroup() {
    return await decode(
      CORE_GROUP,
      await this.view.get(this.read, core.group(this.core.corePointer))
    )
  }

  static async getHints(db, c) {
    return decode(CORE_HINTS, await db.get(core.hints(c.dataPointer)))
  }

  async getHints() {
    return decode(CORE_HINTS, await this.view.get(this.read, core.hints(this.core.dataPointer)))
  }

  getBlock(index) {
    const dep = findBlockDependency(this.core.dependencies, index)
    const data = dep === null ? this.core.dataPointer : dep.dataPointer
    return this.view.get(this.read, core.block(data, index))
  }

  getBitfieldPage(index) {
    return this.view.get(this.read, core.bitfield(this.core.dataPointer, index, 0))
  }

  async _resolveTreeCache(key, promise, entry) {
    try {
      const value = decode(CORE_TREE_NODE, await promise)
      const cached = this.treeCache.get(key)

      // preflight is gone; no-op
      if (cached !== entry) return value

      if (value !== null) {
        // replace cached entry with value
        this.treeCache.set(key, { pending: null, value })
      } else {
        // discard non-existing values
        this.treeCache.delete(key)
      }

      return value
    } catch (err) {
      // cleanup
      const cached = this.treeCache.get(key)
      if (cached === entry) {
        this.treeCache.delete(key)
      }

      throw err
    }
  }

  async getTreeNode(index) {
    const dep = findTreeDependency(this.core.dependencies, index)
    const data = dep === null ? this.core.dataPointer : dep.dataPointer

    if (!this.treeCache || this.fork < 0) {
      if (this.stats !== null) this.stats.treeCache.skips++
      // no-cache
      return decode(CORE_TREE_NODE, await this.view.get(this.read, core.tree(data, index)))
    }

    const key = `${data}:${index}:${this.fork}`
    let entry = this.treeCache.get(key)
    if (entry) {
      if (entry.pending) {
        if (this.stats !== null) this.stats.treeCache.parallel++
        return entry.pending
      }

      if (this.stats !== null) this.stats.treeCache.hits++
      return entry.value
    }

    if (this.stats !== null) this.stats.treeCache.misses++

    const promise = this.view.get(this.read, core.tree(data, index))
    entry = { pending: null, value: null }
    entry.pending = this._resolveTreeCache(key, promise, entry)
    this.treeCache.set(key, entry)

    return entry.pending
  }

  async hasTreeNode(index) {
    return (await this.getTreeNode(index)) !== null
  }

  getUserData(key) {
    return this.view.get(this.read, core.userData(this.core.dataPointer, key))
  }

  getMark(index) {
    return this.view.get(this.read, core.mark(this.core.dataPointer, index))
  }

  getLocal(key) {
    return this.view.get(this.read, core.local(this.core.dataPointer, key))
  }

  tryFlush() {
    this.read.tryFlush()
    this._free()
  }

  destroy() {
    this.read.destroy()
    this._free()
  }

  _free() {
    if (this.view === null) return
    this.view.readStop()
    this.view = null
  }
}

class CorestoreTX {
  constructor(view) {
    this.view = view
    this.changes = []
  }

  setHead(head) {
    this.changes.push([store.head(), encode(CORESTORE_HEAD, head), null])
  }

  setCoreHints(c, hints) {
    this.changes.push([core.hints(c.dataPointer), encode(CORE_HINTS, hints), null])
  }

  putCore(discoveryKey, ptr) {
    this.changes.push([store.core(discoveryKey), encode(CORESTORE_CORE, ptr), null])
  }

  putCoreByAlias(alias, discoveryKey) {
    this.changes.push([store.coreByAlias(alias), discoveryKey, null])
  }

  putGroup(topic, groupPtr) {
    this.changes.push([group.topic(topic), encode(CORESTORE_GROUP, groupPtr), null])
  }

  deleteGroup(topic) {
    this.changes.push([group.topic(topic), null, null])
  }

  clear() {
    const [start, end] = store.clear()
    this.changes.push([start, null, end])
  }

  apply() {
    if (this.changes === null) return
    this.view.apply(this.changes)
    this.changes = null
  }
}

class CorestoreRX {
  constructor(db, view) {
    this.read = db.read({ autoDestroy: true })
    this.view = view

    view.readStart()
  }

  async getHeadRaw(db) {
    return await this.view.get(this.read, store.head())
  }

  async getHead() {
    return decode(CORESTORE_HEAD, await this.view.get(this.read, store.head()))
  }

  async getCore(discoveryKey) {
    return decode(CORESTORE_CORE, await this.view.get(this.read, store.core(discoveryKey)))
  }

  getCoreByAlias(alias) {
    return this.view.get(this.read, store.coreByAlias(alias))
  }

  async getGroup(topic) {
    return decode(CORESTORE_GROUP, await this.view.get(this.read, group.topic(topic)))
  }

  tryFlush() {
    this.read.tryFlush()
    this._free()
  }

  destroy() {
    this.read.destroy()
    this._free()
  }

  _free() {
    if (this.view === null) return
    this.view.readStop()
    this.view = null
  }
}

module.exports = { CorestoreTX, CorestoreRX, CoreTX, CoreRX }

function findBlockDependency(dependencies, index) {
  for (let i = 0; i < dependencies.length; i++) {
    const dep = dependencies[i]
    if (index < dep.length) return dep
  }

  return null
}

function findTreeDependency(dependencies, index) {
  for (let i = 0; i < dependencies.length; i++) {
    const dep = dependencies[i]
    if (flat.rightSpan(index) <= (dep.length - 1) * 2) return dep
  }

  return null
}

function decode(enc, buffer) {
  if (buffer === null) return null
  return enc.decode({ start: 0, end: buffer.byteLength, buffer })
}

function encode(enc, m) {
  // TODO: use fancy slab for small messages
  const state = { start: 0, end: 0, buffer: null }
  enc.preencode(state, m)
  state.buffer = b4a.allocUnsafe(state.end)
  enc.encode(state, m)
  return state.buffer
}
const { Readable, getStreamError } = require('streamx')
const CloseErrorStream = require('./close-error-stream.js')
const b4a = require('b4a')

class OverlayStream extends Readable {
  constructor(stream, start, end, reverse, changes, cleared) {
    super()

    this.start = start
    this.end = end
    this.reverse = reverse
    this.changes = changes
    this.cleared = cleared
    this.change = 0
    this.range = 0

    this._stream = stream
    this._drained = false

    this._stream.on('readable', this._drainMaybe.bind(this))
    this._stream.on('error', noop)
    this._stream.on('close', this._onclose.bind(this))
  }

  _drainMaybe() {
    if (this._drained === true) return
    this._drained = this._onreadable()
  }

  _onclose() {
    if (this.destroying) return

    const err = getStreamError(this._stream)

    if (err !== null) {
      this.destroy(err)
      return
    }

    while (this.change < this.changes.length) {
      const c = this.changes[this.change++]
      const key = c[0]
      const value = c[1]

      if (value !== null && this._inRange(key)) this.push({ key, value })
    }

    this.push(null)
    this._stream = null
  }

  _onreadable() {
    let data = this._stream.read()
    if (data === null) return false

    let drained = false

    do {
      if (this._push(data) === true) drained = true
      data = this._stream.read()
    } while (data !== null)

    return drained
  }

  _read(cb) {
    this._drained = this._onreadable()
    cb(null)
  }

  _predestroy() {
    this._stream.destroy()
  }

  _push(entry) {
    const key = entry.key

    while (this.range < this.cleared.length) {
      const c = this.cleared[this.range]

      // we moved past the range
      if (this.reverse ? b4a.compare(key, c[0]) < 0 : b4a.compare(c[2], key) <= 0) {
        this.range++
        continue
      }

      // we didnt move past and are in, drop
      if (b4a.compare(c[0], key) <= 0 && b4a.compare(key, c[2]) < 0) {
        return false
      }

      break
    }

    let updated = false

    while (this.change < this.changes.length) {
      const c = this.changes[this.change]
      const key = c[0]
      const value = typeof c[1] === 'string' ? b4a.from(c[1]) : c[1]
      const cmp = b4a.compare(key, entry.key)

      // same value, if not deleted, return new one
      if (cmp === 0) {
        this.change++
        if (value === null || this._inRange(key) === false) return updated
        this.push({ key, value })
        return true
      }

      // we moved past the change, push it
      if (this.reverse ? cmp > 0 : cmp < 0) {
        this.change++
        if (value === null || this._inRange(key) === false) continue
        this.push({ key, value })
        updated = true
        continue
      }

      this.push(entry)
      return true
    }

    this.push(entry)
    return true
  }

  _inRange(key) {
    return b4a.compare(this.start, key) <= 0 && b4a.compare(key, this.end) < 0
  }
}

class Overlay {
  constructor() {
    this.indexed = 0
    this.changes = null
    this.cleared = null
    this.reverse = false
  }

  update(view, reverse) {
    if (view.indexed === this.indexed) return

    const changes = view.map === null ? [] : [...view.map.values()]
    const cleared = view.cleared === null ? [] : view.cleared.slice(0)

    const cmp = reverse ? cmpChangeReverse : cmpChange

    changes.sort(cmp)
    cleared.sort(cmp)

    this.indexed = view.indexed
    this.changes = changes
    this.cleared = cleared
    this.reverse = reverse
  }

  createStream(stream, start, end, reverse) {
    return new OverlayStream(
      stream,
      start,
      end,
      reverse,
      this.reverse === reverse ? this.changes : reverseArray(this.changes),
      this.reverse === reverse ? this.cleared : reverseArray(this.cleared)
    )
  }
}

class View {
  constructor() {
    this.map = null
    this.indexed = 0
    this.changes = null
    this.cleared = null
    this.overlay = null
    this.snap = null
    this.readers = 0
  }

  snapshot() {
    if (this._attached()) return this.snap.snapshot()

    const snap = new View()

    snap.map = this.map
    snap.indexed = this.indexed
    snap.changes = this.changes
    snap.cleared = this.cleared

    if (this._frozen()) return snap

    this.readers++
    snap.snap = this

    return snap
  }

  readStart() {
    if (this.snap !== null) this.readers++
  }

  readStop() {
    if (this.snap !== null && --this.readers === 0) this.snap.readers--
  }

  size() {
    return this.changes === null ? 0 : this.changes.length
  }

  updated() {
    return this.changes === null
  }

  get(read, key) {
    return this.changes === null ? read.get(key) : this._indexAndGet(read, key)
  }

  reset() {
    this.indexed = 0
    this.snap = this.map = this.changes = this.cleared = this.overlay = null
  }

  iterator(db, start, end, reverse) {
    if (dbClosing(db)) return new CloseErrorStream(new Error('RocksDB session is closed'))

    const stream = db.iterator({ gte: start, lt: end, reverse })
    if (this.changes === null) return stream

    this._index()

    if (this.overlay === null) this.overlay = new Overlay()
    this.overlay.update(this, reverse)
    return this.overlay.createStream(stream, start, end, reverse)
  }

  _indexAndGet(read, key) {
    this._index()
    const change = this.map.get(b4a.toString(key, 'hex'))

    if (change === undefined) {
      return this.cleared === null ? read.get(key) : this._readAndMaybeDrop(read, key)
    }

    return Promise.resolve(change[1])
  }

  async _readAndMaybeDrop(read, key) {
    const cleared = this.cleared // in case its cleared
    const value = await read.get(key)
    if (value === null) return null

    for (let i = 0; i < cleared.length; i++) {
      const c = cleared[i]
      // check if in range
      if (b4a.compare(c[0], key) <= 0 && b4a.compare(key, c[2]) < 0) return null
    }

    return value
  }

  _attached() {
    return this.snap !== null && this.changes === this.snap.changes
  }

  _frozen() {
    return this.changes === null || (this.snap !== null && this.changes !== this.snap.changes)
  }

  _index() {
    // if we are a snap and we are still attached (ie no mutations), simply copy the refs
    if (this._attached()) {
      this.snap._index()
      this.map = this.snap.map
      this.cleared = this.snap.cleared
      this.indexed = this.snap.indexed
      return
    }

    if (this.map === null) this.map = new Map()
    if (this.changes.length === this.indexed) return

    while (this.indexed < this.changes.length) {
      const c = this.changes[this.indexed++]

      if (c[2] === null) this.map.set(b4a.toString(c[0], 'hex'), c)
      else this._indexRange(c)
    }
  }

  _indexRange(range) {
    const s = b4a.toString(range[0], 'hex')
    const e = b4a.toString(range[2], 'hex')

    for (const [key, c] of this.map) {
      if (s <= key && key < e) this.map.set(key, [c[0], null, null])
    }

    if (this.cleared === null) this.cleared = []
    this.cleared.push(range)
  }

  apply(changes) {
    if (this.snap !== null) throw new Error('Illegal to push changes to a snapshot')

    if (this.readers !== 0 && this.changes !== null) {
      this.changes = this.changes.slice(0)
      this.cleared = this.cleared === null ? null : this.cleared.slice(0)
      this.map = this.map === null ? null : new Map([...this.map])
    }

    if (this.changes === null) {
      this.changes = changes
      return
    }

    for (let i = 0; i < changes.length; i++) {
      this.changes.push(changes[i])
    }
  }

  static async flush(changes, db) {
    if (changes === null) return true

    const w = db.write({ autoDestroy: true })

    for (const [start, value, end] of changes) {
      if (end !== null) w.tryDeleteRange(start, end)
      else if (value !== null) w.tryPut(start, value)
      else w.tryDelete(start)
    }

    await w.flush()

    return true
  }
}

module.exports = View

function cmpChange(a, b) {
  const c = b4a.compare(a[0], b[0])
  return c === 0 ? b4a.compare(a[2], b[2]) : c
}

function cmpChangeReverse(a, b) {
  return cmpChange(b, a)
}

function noop() {}

function reverseArray(list) {
  const r = new Array(list.length)
  for (let i = 0; i < list.length; i++) r[r.length - 1 - i] = list[i]
  return r
}

// TODO: expose from rocks instead
function dbClosing(db) {
  return db._state.closing || db._index === -1
}
const fs = require('fs')
const path = require('path')
const { Readable } = require('streamx')
const b4a = require('b4a')
const flat = require('flat-tree')
const crypto = require('hypercore-crypto')
const c = require('compact-encoding')
const m = require('./messages.js')
const View = require('../../lib/view.js')
const { CorestoreTX, CoreTX, CorestoreRX } = require('../../lib/tx.js')

const EMPTY_NODE = b4a.alloc(40)
const EMPTY_PAGE = b4a.alloc(4096)

let TREE_01_SKIP = null
let TREE_04_SKIP = null
let TREE_16_SKIP = null

class CoreListStream extends Readable {
  constructor (storage) {
    super()

    this.storage = storage
    this.stack = []
  }

  async _open (cb) {
    for (const a of await readdir(path.join(this.storage, 'cores'))) {
      for (const b of await readdir(path.join(this.storage, 'cores', a))) {
        for (const dkey of await readdir(path.join(this.storage, 'cores', a, b))) {
          this.stack.push(path.join(this.storage, 'cores', a, b, dkey))
        }
      }
    }

    cb(null)
  }

  async _read (cb) {
    while (true) {
      const next = this.stack.pop()
      if (!next) {
        this.push(null)
        break
      }

      const oplog = path.join(next, 'oplog')
      const result = await readOplog(oplog)
      if (!result) continue

      this.push(result)
      break
    }

    cb(null)
  }
}

function decodeOplogHeader (state) {
  c.uint32.decode(state) // cksum, ignore for now

  const l = c.uint32.decode(state)
  const length = l >> 2
  const headerBit = l & 1
  const partialBit = l & 2

  if (state.end - state.start < length) return null

  const end = state.start + length
  const result = { header: headerBit, partial: partialBit !== 0, byteLength: length + 8, message: null }

  try {
    result.message = m.oplog.header.decode({ start: state.start, end, buffer: state.buffer })
  } catch {
    return null
  }

  state.start = end
  return result
}

function decodeOplogEntry (state) {
  if (state.end - state.start < 8) return null

  c.uint32.decode(state) // cksum, ignore for now

  const l = c.uint32.decode(state)
  const length = l >>> 2
  const headerBit = l & 1
  const partialBit = l & 2

  if (state.end - state.start < length) return null

  const end = state.start + length

  const result = { header: headerBit, partial: partialBit !== 0, byteLength: length + 8, message: null }

  try {
    result.message = m.oplog.entry.decode({ start: state.start, end, buffer: state.buffer })
  } catch {
    return null
  }

  state.start = end

  return result
}

module.exports = { store, core }

async function store (storage, { version, dryRun = true, gc = true }) {
  const stream = new CoreListStream(storage.path)
  const view = new View()

  const tx = new CorestoreTX(view)
  const head = await storage._getHead(view)
  const primaryKeyFile = path.join(storage.path, 'primary-key')

  const primaryKey = await readFile(primaryKeyFile)

  if (!head.seed) head.seed = primaryKey

  for await (const data of stream) {
    const key = data.header.key
    const discoveryKey = crypto.discoveryKey(data.header.key)
    const files = getFiles(data.path)

    if (head.defaultDiscoveryKey === null) head.defaultDiscoveryKey = discoveryKey

    const core = {
      version: 0, // need later migration
      corePointer: head.allocated.cores++,
      dataPointer: head.allocated.datas++,
      alias: null
    }

    const ptr = { version: 0, corePointer: core.corePointer, dataPointer: core.dataPointer, dependencies: [] }
    const ctx = new CoreTX(ptr, storage.db, view, [])
    const userData = new Map()
    const treeNodes = new Map()

    const auth = {
      key,
      discoveryKey,
      manifest: data.header.manifest,
      keyPair: data.header.keyPair,
      encryptionKey: null
    }

    const tree = {
      length: 0,
      fork: 0,
      rootHash: null,
      signature: null
    }

    if (data.header.tree && data.header.tree.length) {
      tree.length = data.header.tree.length
      tree.fork = data.header.tree.fork
      tree.rootHash = data.header.tree.rootHash
      tree.signature = data.header.tree.signature
    }

    for (const { key, value } of data.header.userData) {
      userData.set(key, value)
    }

    for (const e of data.entries) {
      if (e.userData) userData.set(e.userData.key, e.userData.value)

      if (e.treeNodes) {
        for (const node of e.treeNodes) {
          treeNodes.set(node.index, node)
          ctx.putTreeNode(node)
        }
      }

      if (e.treeUpgrade) {
        if (e.treeUpgrade.ancestors !== tree.length) {
          throw new Error('Unflushed truncations not migrate-able atm')
        }

        tree.length = e.treeUpgrade.length
        tree.fork = e.treeUpgrade.fork
        tree.rootHash = null
        tree.signature = e.treeUpgrade.signature
      }
    }

    if (userData.has('corestore/name') && userData.has('corestore/namespace')) {
      core.alias = {
        name: b4a.toString(userData.get('corestore/name')),
        namespace: userData.get('corestore/namespace')
      }
      userData.delete('corestore/name')
      userData.delete('corestore/namespace')
    }

    for (const [key, value] of userData) {
      ctx.putUserData(key, value)
    }

    ctx.setAuth(auth)

    const getTreeNode = (index) => (treeNodes.get(index) || getTreeNodeFromFile(files.tree, index))

    if (tree.length) {
      if (tree.rootHash === null) tree.rootHash = crypto.tree(await getRoots(tree.length, getTreeNode))
      ctx.setHead(tree)
    }

    tx.putCore(discoveryKey, core)
    if (core.alias) tx.putCoreByAlias(core.alias, discoveryKey)

    await ctx.flush()
  }

  head.version = version
  tx.setHead(head)
  tx.apply()

  if (dryRun) return

  await View.flush(view.changes, storage.db)

  if (gc) await rm(primaryKeyFile)
}

class BlockSlicer {
  constructor (filename) {
    this.stream = fs.createReadStream(filename)
    this.closed = new Promise(resolve => this.stream.once('close', resolve))
    this.offset = 0
    this.overflow = null
  }

  async take (offset, size) {
    let buffer = null
    if (offset < this.offset) throw new Error('overread')

    while (true) {
      let data = null

      if (this.overflow) {
        data = this.overflow
        this.overflow = null
      } else {
        data = this.stream.read()

        if (!data) {
          await new Promise(resolve => this.stream.once('readable', resolve))
          continue
        }
      }

      let chunk = null

      if (this.offset === offset || buffer) {
        chunk = data
      } else if (this.offset + data.byteLength > offset) {
        chunk = data.subarray(offset - this.offset)
      }

      this.offset += data.byteLength
      if (!chunk) continue

      if (buffer) buffer = b4a.concat([buffer, chunk])
      else buffer = chunk

      if (buffer.byteLength < size) continue

      const result = buffer.subarray(0, size)
      this.overflow = size === buffer.byteLength ? null : buffer.subarray(result.byteLength)
      this.offset -= (this.overflow ? this.overflow.byteLength : 0)
      return result
    }
  }

  close () {
    this.stream.on('error', noop)
    this.stream.destroy()
    return this.closed
  }
}

class TreeSlicer {
  constructor () {
    this.buffer = null
    this.offset = 0
  }

  get size () {
    return this.buffer === null ? 0 : this.buffer.byteLength
  }

  push (data) {
    if (this.buffer === null) this.buffer = data
    else this.buffer = b4a.concat([this.buffer, data])
    this.offset += data.byteLength
  }

  skip () {
    let skipped = 0

    if (TREE_01_SKIP === null) {
      TREE_16_SKIP = b4a.alloc(16 * 40 * 100)
      TREE_04_SKIP = TREE_16_SKIP.subarray(0, 4 * 40 * 100)
      TREE_01_SKIP = TREE_16_SKIP.subarray(0, 1 * 40 * 100)
    }

    while (true) {
      if (this.buffer.byteLength >= TREE_16_SKIP.byteLength) {
        if (b4a.equals(this.buffer.subarray(0, TREE_16_SKIP.byteLength), TREE_16_SKIP)) {
          this.buffer = this.buffer.subarray(TREE_16_SKIP.byteLength)
          skipped += 1600
          continue
        }
      }

      if (this.buffer.byteLength >= TREE_04_SKIP.byteLength) {
        if (b4a.equals(this.buffer.subarray(0, TREE_04_SKIP.byteLength), TREE_04_SKIP)) {
          this.buffer = this.buffer.subarray(TREE_04_SKIP.byteLength)
          skipped += 400
          continue
        }
      }

      if (this.buffer.byteLength >= TREE_01_SKIP.byteLength) {
        if (b4a.equals(this.buffer.subarray(0, TREE_01_SKIP.byteLength), TREE_01_SKIP)) {
          this.buffer = this.buffer.subarray(TREE_01_SKIP.byteLength)
          skipped += 100
          continue
        }
      }
      break
    }

    return skipped
  }

  take () {
    const len = 40

    if (len <= this.size) {
      const chunk = this.buffer.subarray(0, len)
      this.buffer = this.buffer.subarray(len)
      return chunk
    }

    return null
  }
}

async function core (core, { version, dryRun = true, gc = true }) {
  if (dryRun) return // dryRun mode not supported atm

  const rx = core.read()

  const promises = [rx.getAuth(), rx.getHead()]
  rx.tryFlush()

  const [auth, head] = await Promise.all(promises)

  if (!auth) return

  const dk = b4a.toString(auth.discoveryKey, 'hex')
  const files = getFiles(path.join(core.store.path, 'cores', dk.slice(0, 2), dk.slice(2, 4), dk))

  if (head === null || head.length === 0) {
    await commitCoreMigration(auth, core, version)
    if (gc) await runGC()
    return // no data
  }

  const oplog = await readOplog(files.oplog)
  if (!oplog) {
    const writable = !!auth.keyPair

    if (writable) {
      throw new Error('No oplog available writable core for ' + files.oplog + ', length = ' + (head ? head.length : 0))
    }

    // if not writable, just nuke it to recover, some bad state happened here, prop corruption from earlier versions
    const w = core.write()

    w.deleteBlockRange(0, -1)
    w.deleteTreeNodeRange(0, -1)
    w.deleteBitfieldPageRange(0, -1)
    w.deleteHead()

    await w.flush()

    await commitCoreMigration(auth, core, version)
    if (gc) await runGC()
    return // no data
  }

  const treeData = new TreeSlicer()

  let treeIndex = 0

  if (await exists(files.tree)) {
    for await (const data of fs.createReadStream(files.tree)) {
      treeData.push(data)

      let write = null

      while (true) {
        const skip = treeData.skip()
        treeIndex += skip

        const buf = treeData.take()
        if (buf === null) break

        const index = treeIndex++
        if (b4a.equals(buf, EMPTY_NODE)) continue

        if (write === null) write = core.write()
        write.putTreeNode(decodeTreeNode(index, buf))
      }

      if (write !== null) await write.flush()
    }
  }

  const buf = []
  if (await exists(files.bitfield)) {
    for await (const data of fs.createReadStream(files.bitfield)) {
      buf.push(data)
    }
  }

  let bitfield = b4a.concat(buf)
  if (bitfield.byteLength & 4095) bitfield = b4a.concat([bitfield, b4a.alloc(4096 - (bitfield.byteLength & 4095))])

  const pages = new Map()
  const headerBits = new Map()

  const roots = await getRootsFromStorage(core, head.length)

  for (const e of oplog.entries) {
    if (!e.bitfield) continue

    for (let i = 0; i < e.bitfield.length; i++) {
      headerBits.set(i + e.bitfield.start, !e.bitfield.drop)
    }
  }

  let batch = []

  const cache = new Map()
  const blocks = new BlockSlicer(files.data)

  for (const index of allBits(bitfield)) {
    if (headerBits.get(index) === false) continue
    if (index >= head.length) continue

    setBitInPage(index)

    batch.push(index)
    if (batch.length < 1024) continue

    await writeBlocksBatch()
    continue
  }

  if (batch.length) await writeBlocksBatch()

  await blocks.close()

  const w = core.write()

  for (const [index, bit] of headerBits) {
    if (!bit) continue
    if (index >= head.length) continue

    setBitInPage(index)

    const blk = await getBlockFromFile(files.data, core, index, roots, cache)
    w.putBlock(index, blk)
  }

  for (const [index, page] of pages) {
    w.putBitfieldPage(index, b4a.from(page.buffer, page.byteOffset, page.byteLength))
  }

  await w.flush()

  let contiguousLength = 0
  for await (const data of core.createBlockStream()) {
    if (data.index === contiguousLength) contiguousLength++
    else break
  }

  if (contiguousLength) {
    const w = core.write()
    w.setHints({ contiguousLength })
    await w.flush()
  }

  await commitCoreMigration(auth, core, version)

  if (gc) await runGC()

  async function runGC () {
    await rm(files.path)
    await rmdir(path.join(files.path, '..'))
    await rmdir(path.join(files.path, '../..'))
    await rmdir(path.join(core.store.path, 'cores'))
  }

  function setBitInPage (index) {
    const n = index & 32767
    const p = (index - n) / 32768

    let page = pages.get(p)

    if (!page) {
      page = new Uint32Array(1024)
      pages.set(p, page)
    }

    const o = n & 31
    const b = (n - o) / 32
    const v = 1 << o

    page[b] |= v
  }

  async function writeBlocksBatch () {
    const read = core.read()
    const promises = []
    for (const index of batch) promises.push(getByteRangeFromStorage(read, 2 * index, roots, cache))
    read.tryFlush()

    const r = await Promise.all(promises)
    const tx = core.write()

    for (let i = 0; i < r.length; i++) {
      const index = batch[i]
      const [offset, size] = r[i]

      const blk = await blocks.take(offset, size)
      tx.putBlock(index, blk)
    }

    batch = []
    if (cache.size > 16384) cache.clear()

    await tx.flush()
  }
}

async function commitCoreMigration (auth, core, version) {
  const view = new View()
  const rx = new CorestoreRX(core.db, view)

  const storeCorePromise = rx.getCore(auth.discoveryKey)
  rx.tryFlush()

  const storeCore = await storeCorePromise

  storeCore.version = version

  const tx = new CorestoreTX(view)

  tx.putCore(auth.discoveryKey, storeCore)
  tx.apply()

  await View.flush(view.changes, core.db)
}

async function getBlockFromFile (file, core, index, roots, cache) {
  const rx = core.read()
  const promise = getByteRangeFromStorage(rx, 2 * index, roots, cache)
  rx.tryFlush()
  const [offset, size] = await promise

  return new Promise(function (resolve) {
    readAll(file, size, offset, function (err, buf) {
      if (err) return resolve(null)
      resolve(buf)
    })
  })
}

function getFiles (dir) {
  return {
    path: dir,
    oplog: path.join(dir, 'oplog'),
    data: path.join(dir, 'data'),
    tree: path.join(dir, 'tree'),
    bitfield: path.join(dir, 'bitfield')
  }
}

async function getRootsFromStorage (core, length) {
  const all = []
  const rx = core.read()
  for (const index of flat.fullRoots(2 * length)) {
    all.push(rx.getTreeNode(index))
  }
  rx.tryFlush()
  return Promise.all(all)
}

async function getRoots (length, getTreeNode) {
  const all = []
  for (const index of flat.fullRoots(2 * length)) {
    all.push(await getTreeNode(index))
  }
  return all
}

function getCached (read, cache, index) {
  if (cache.has(index)) return cache.get(index)
  const p = read.getTreeNode(index)
  cache.set(index, p)
  return p
}

async function getByteRangeFromStorage (read, index, roots, cache) {
  const promises = [getCached(read, cache, index), getByteOffsetFromStorage(read, index, roots, cache)]
  const [node, offset] = await Promise.all(promises)
  if (!node) throw new Error('Node not found during migration: ' + index)
  return [offset, node.size]
}

async function getByteOffsetFromStorage (rx, index, roots, cache) {
  if (index === 0) return 0
  if ((index & 1) === 1) index = flat.leftSpan(index)

  let head = 0
  let offset = 0

  for (const node of roots) { // all async ticks happen once we find the root so safe
    head += 2 * ((node.index - head) + 1)

    if (index >= head) {
      offset += node.size
      continue
    }

    const ite = flat.iterator(node.index)
    const promises = []

    while (ite.index !== index) {
      if (index < ite.index) {
        ite.leftChild()
      } else {
        promises.push(getCached(rx, cache, ite.leftChild()))
        ite.sibling()
      }
    }

    const nodes = await Promise.all(promises)
    for (const node of nodes) offset += node.size

    return offset
  }

  throw new Error('Failed to find offset')
}

function decodeTreeNode (index, buf) {
  return { index, size: c.decode(c.uint64, buf), hash: buf.subarray(8) }
}

async function getTreeNodeFromFile (file, index) {
  return new Promise(function (resolve) {
    readAll(file, 40, index * 40, function (err, buf) {
      if (err) return resolve(null)
      resolve(decodeTreeNode(index, buf))
    })
  })
}

function readAll (filename, length, pos, cb) {
  const buf = b4a.alloc(length)

  fs.open(filename, 'r', function (err, fd) {
    if (err) return cb(err)

    let offset = 0

    fs.read(fd, buf, offset, buf.byteLength, pos, function loop (err, read) {
      if (err) return done(err)
      if (read === 0) return done(new Error('Partial read'))
      offset += read
      if (offset === buf.byteLength) return done(null, buf)
      fs.read(fd, offset, buf.byteLength - offset, buf, pos + offset, loop)
    })

    function done (err, value) {
      fs.close(fd, () => cb(err, value))
    }
  })
}

async function readdir (dir) {
  try {
    return await fs.promises.readdir(dir)
  } catch {
    return []
  }
}

async function exists (file) {
  try {
    await fs.promises.stat(file)
    return true
  } catch {
    return false
  }
}

async function readFile (file) {
  try {
    return await fs.promises.readFile(file)
  } catch {
    return null
  }
}

async function rm (dir) {
  try {
    await fs.promises.rm(dir, { recursive: true })
  } catch {}
}

async function rmdir (dir) {
  try {
    await fs.promises.rmdir(dir)
  } catch {}
}

function * allBits (buffer) {
  for (let i = 0; i < buffer.byteLength; i += EMPTY_PAGE.byteLength) {
    const page = buffer.subarray(i, i + EMPTY_NODE.byteLength)
    if (b4a.equals(page, EMPTY_PAGE)) continue

    const view = new Uint32Array(page.buffer, page.byteOffset, EMPTY_PAGE.byteLength / 4)

    for (let j = 0; j < view.length; j++) {
      const n = view[j]
      if (n === 0) continue

      for (let k = 0; k < 32; k++) {
        const m = 1 << k
        if (n & m) yield i * 8 + j * 32 + k
      }
    }
  }
}

function readOplog (oplog) {
  return new Promise(function (resolve) {
    fs.readFile(oplog, function (err, buffer) {
      if (err) return resolve(null)

      const state = { start: 0, end: buffer.byteLength, buffer }
      const headers = [1, 0]

      const h1 = decodeOplogHeader(state)
      state.start = 4096

      const h2 = decodeOplogHeader(state)
      state.start = 4096 * 2

      if (!h1 && !h2) return resolve(null)

      if (h1 && !h2) {
        headers[0] = h1.header
        headers[1] = h1.header
      } else if (!h1 && h2) {
        headers[0] = (h2.header + 1) & 1
        headers[1] = h2.header
      } else {
        headers[0] = h1.header
        headers[1] = h2.header
      }

      const header = (headers[0] + headers[1]) & 1
      const result = { path: path.dirname(oplog), header: null, entries: [] }
      const decoded = []

      result.header = header ? h2.message : h1.message

      if (result.header.external) {
        fs.readFile(path.join(oplog, '../header'), function (err, buffer) {
          if (err) return resolve(null)
          const start = result.header.external.start
          const end = start + result.header.external.length
          result.header = m.oplog.header.decode({ buffer, start, end })
          finish()
        })
        return
      }

      finish()

      function finish () {
        while (true) {
          const entry = decodeOplogEntry(state)
          if (!entry) break
          if (entry.header !== header) break

          decoded.push(entry)
        }

        while (decoded.length > 0 && decoded[decoded.length - 1].partial) decoded.pop()

        for (const e of decoded) {
          result.entries.push(e.message)
        }

        resolve(result)
      }
    })
  })
}

function noop () {}
// needed here for compat, copied from old hypercore, do not change this

const c = require('compact-encoding')
const b4a = require('b4a')

const EMPTY = b4a.alloc(0)
const DEFAULT_NAMESPACE = b4a.from('4144eea531e483d54e0c14f4ca68e0644f355343ff6fcb0f005200e12cd747cb', 'hex')

const hashes = {
  preencode (state, m) {
    state.end++ // small uint
  },
  encode (state, m) {
    if (m === 'blake2b') {
      c.uint.encode(state, 0)
      return
    }

    throw new Error('Unknown hash: ' + m)
  },
  decode (state) {
    const n = c.uint.decode(state)
    if (n === 0) return 'blake2b'
    throw new Error('Unknown hash id: ' + n)
  }
}

const signatures = {
  preencode (state, m) {
    state.end++ // small uint
  },
  encode (state, m) {
    if (m === 'ed25519') {
      c.uint.encode(state, 0)
      return
    }

    throw new Error('Unknown signature: ' + m)
  },
  decode (state) {
    const n = c.uint.decode(state)
    if (n === 0) return 'ed25519'
    throw new Error('Unknown signature id: ' + n)
  }
}

const signer = {
  preencode (state, m) {
    signatures.preencode(state, m.signature)
    c.fixed32.preencode(state, m.namespace)
    c.fixed32.preencode(state, m.publicKey)
  },
  encode (state, m) {
    signatures.encode(state, m.signature)
    c.fixed32.encode(state, m.namespace)
    c.fixed32.encode(state, m.publicKey)
  },
  decode (state) {
    return {
      signature: signatures.decode(state),
      namespace: c.fixed32.decode(state),
      publicKey: c.fixed32.decode(state)
    }
  }
}

const signerArray = c.array(signer)

const prologue = {
  preencode (state, p) {
    c.fixed32.preencode(state, p.hash)
    c.uint.preencode(state, p.length)
  },
  encode (state, p) {
    c.fixed32.encode(state, p.hash)
    c.uint.encode(state, p.length)
  },
  decode (state) {
    return {
      hash: c.fixed32.decode(state),
      length: c.uint.decode(state)
    }
  }
}

const manifestv0 = {
  preencode (state, m) {
    hashes.preencode(state, m.hash)
    state.end++ // type

    if (m.prologue && m.signers.length === 0) {
      c.fixed32.preencode(state, m.prologue.hash)
      return
    }

    if (m.quorum === 1 && m.signers.length === 1 && !m.allowPatch) {
      signer.preencode(state, m.signers[0])
    } else {
      state.end++ // flags
      c.uint.preencode(state, m.quorum)
      signerArray.preencode(state, m.signers)
    }
  },
  encode (state, m) {
    hashes.encode(state, m.hash)

    if (m.prologue && m.signers.length === 0) {
      c.uint.encode(state, 0)
      c.fixed32.encode(state, m.prologue.hash)
      return
    }

    if (m.quorum === 1 && m.signers.length === 1 && !m.allowPatch) {
      c.uint.encode(state, 1)
      signer.encode(state, m.signers[0])
    } else {
      c.uint.encode(state, 2)
      c.uint.encode(state, m.allowPatch ? 1 : 0)
      c.uint.encode(state, m.quorum)
      signerArray.encode(state, m.signers)
    }
  },
  decode (state) {
    const hash = hashes.decode(state)
    const type = c.uint.decode(state)

    if (type > 2) throw new Error('Unknown type: ' + type)

    if (type === 0) {
      return {
        version: 0,
        hash,
        allowPatch: false,
        quorum: 0,
        signers: [],
        prologue: {
          hash: c.fixed32.decode(state),
          length: 0
        }
      }
    }

    if (type === 1) {
      return {
        version: 0,
        hash,
        allowPatch: false,
        quorum: 1,
        signers: [signer.decode(state)],
        prologue: null
      }
    }

    const flags = c.uint.decode(state)

    return {
      version: 0,
      hash,
      allowPatch: (flags & 1) !== 0,
      quorum: c.uint.decode(state),
      signers: signerArray.decode(state),
      prologue: null
    }
  }
}

const manifest = exports.manifest = {
  preencode (state, m) {
    state.end++ // version
    if (m.version === 0) return manifestv0.preencode(state, m)

    state.end++ // flags
    hashes.preencode(state, m.hash)

    c.uint.preencode(state, m.quorum)
    signerArray.preencode(state, m.signers)
    if (m.prologue) prologue.preencode(state, m.prologue)
  },
  encode (state, m) {
    c.uint.encode(state, m.version)
    if (m.version === 0) return manifestv0.encode(state, m)

    c.uint.encode(state, (m.allowPatch ? 1 : 0) | (m.prologue ? 2 : 0) | (m.unencrypted ? 4 : 0))
    hashes.encode(state, m.hash)

    c.uint.encode(state, m.quorum)
    signerArray.encode(state, m.signers)
    if (m.prologue) prologue.encode(state, m.prologue)
  },
  decode (state) {
    const v = c.uint.decode(state)
    if (v === 0) return manifestv0.decode(state)
    if (v !== 1) throw new Error('Unknown version: ' + v)

    const flags = c.uint.decode(state)
    const hash = hashes.decode(state)
    const quorum = c.uint.decode(state)
    const signers = signerArray.decode(state)
    const unencrypted = (flags & 4) !== 0

    return {
      version: 1,
      hash,
      allowPatch: (flags & 1) !== 0,
      quorum,
      signers,
      prologue: (flags & 2) === 0 ? null : prologue.decode(state),
      unencrypted
    }
  }
}

const node = {
  preencode (state, n) {
    c.uint.preencode(state, n.index)
    c.uint.preencode(state, n.size)
    c.fixed32.preencode(state, n.hash)
  },
  encode (state, n) {
    c.uint.encode(state, n.index)
    c.uint.encode(state, n.size)
    c.fixed32.encode(state, n.hash)
  },
  decode (state) {
    return {
      index: c.uint.decode(state),
      size: c.uint.decode(state),
      hash: c.fixed32.decode(state)
    }
  }
}

const nodeArray = c.array(node)

const wire = exports.wire = {}

wire.handshake = {
  preencode (state, m) {
    c.uint.preencode(state, 1)
    c.fixed32.preencode(state, m.capability)
  },
  encode (state, m) {
    c.uint.encode(state, m.seeks ? 1 : 0)
    c.fixed32.encode(state, m.capability)
  },
  decode (state) {
    const flags = c.uint.decode(state)
    return {
      seeks: (flags & 1) !== 0,
      capability: c.fixed32.decode(state)
    }
  }
}

const requestBlock = {
  preencode (state, b) {
    c.uint.preencode(state, b.index)
    c.uint.preencode(state, b.nodes)
  },
  encode (state, b) {
    c.uint.encode(state, b.index)
    c.uint.encode(state, b.nodes)
  },
  decode (state) {
    return {
      index: c.uint.decode(state),
      nodes: c.uint.decode(state)
    }
  }
}

const requestSeek = {
  preencode (state, s) {
    c.uint.preencode(state, s.bytes)
    c.uint.preencode(state, s.padding)
  },
  encode (state, s) {
    c.uint.encode(state, s.bytes)
    c.uint.encode(state, s.padding)
  },
  decode (state) {
    return {
      bytes: c.uint.decode(state),
      padding: c.uint.decode(state)
    }
  }
}

const requestUpgrade = {
  preencode (state, u) {
    c.uint.preencode(state, u.start)
    c.uint.preencode(state, u.length)
  },
  encode (state, u) {
    c.uint.encode(state, u.start)
    c.uint.encode(state, u.length)
  },
  decode (state) {
    return {
      start: c.uint.decode(state),
      length: c.uint.decode(state)
    }
  }
}

wire.request = {
  preencode (state, m) {
    state.end++ // flags
    c.uint.preencode(state, m.id)
    c.uint.preencode(state, m.fork)

    if (m.block) requestBlock.preencode(state, m.block)
    if (m.hash) requestBlock.preencode(state, m.hash)
    if (m.seek) requestSeek.preencode(state, m.seek)
    if (m.upgrade) requestUpgrade.preencode(state, m.upgrade)
    if (m.priority) c.uint.preencode(state, m.priority)
  },
  encode (state, m) {
    const flags = (m.block ? 1 : 0) | (m.hash ? 2 : 0) | (m.seek ? 4 : 0) | (m.upgrade ? 8 : 0) | (m.manifest ? 16 : 0) | (m.priority ? 32 : 0)

    c.uint.encode(state, flags)
    c.uint.encode(state, m.id)
    c.uint.encode(state, m.fork)

    if (m.block) requestBlock.encode(state, m.block)
    if (m.hash) requestBlock.encode(state, m.hash)
    if (m.seek) requestSeek.encode(state, m.seek)
    if (m.upgrade) requestUpgrade.encode(state, m.upgrade)
    if (m.priority) c.uint.encode(state, m.priority)
  },
  decode (state) {
    const flags = c.uint.decode(state)

    return {
      id: c.uint.decode(state),
      fork: c.uint.decode(state),
      block: flags & 1 ? requestBlock.decode(state) : null,
      hash: flags & 2 ? requestBlock.decode(state) : null,
      seek: flags & 4 ? requestSeek.decode(state) : null,
      upgrade: flags & 8 ? requestUpgrade.decode(state) : null,
      manifest: (flags & 16) !== 0,
      priority: flags & 32 ? c.uint.decode(state) : 0
    }
  }
}

wire.cancel = {
  preencode (state, m) {
    c.uint.preencode(state, m.request)
  },
  encode (state, m) {
    c.uint.encode(state, m.request)
  },
  decode (state, m) {
    return {
      request: c.uint.decode(state)
    }
  }
}

const dataUpgrade = {
  preencode (state, u) {
    c.uint.preencode(state, u.start)
    c.uint.preencode(state, u.length)
    nodeArray.preencode(state, u.nodes)
    nodeArray.preencode(state, u.additionalNodes)
    c.optionalBuffer.preencode(state, u.signature)
  },
  encode (state, u) {
    c.uint.encode(state, u.start)
    c.uint.encode(state, u.length)
    nodeArray.encode(state, u.nodes)
    nodeArray.encode(state, u.additionalNodes)
    c.optionalBuffer.encode(state, u.signature)
  },
  decode (state) {
    return {
      start: c.uint.decode(state),
      length: c.uint.decode(state),
      nodes: nodeArray.decode(state),
      additionalNodes: nodeArray.decode(state),
      signature: c.optionalBuffer.decode(state)
    }
  }
}

const dataSeek = {
  preencode (state, s) {
    c.uint.preencode(state, s.bytes)
    nodeArray.preencode(state, s.nodes)
  },
  encode (state, s) {
    c.uint.encode(state, s.bytes)
    nodeArray.encode(state, s.nodes)
  },
  decode (state) {
    return {
      bytes: c.uint.decode(state),
      nodes: nodeArray.decode(state)
    }
  }
}

const dataBlock = {
  preencode (state, b) {
    c.uint.preencode(state, b.index)
    c.optionalBuffer.preencode(state, b.value)
    nodeArray.preencode(state, b.nodes)
  },
  encode (state, b) {
    c.uint.encode(state, b.index)
    c.optionalBuffer.encode(state, b.value)
    nodeArray.encode(state, b.nodes)
  },
  decode (state) {
    return {
      index: c.uint.decode(state),
      value: c.optionalBuffer.decode(state) || EMPTY,
      nodes: nodeArray.decode(state)
    }
  }
}

const dataHash = {
  preencode (state, b) {
    c.uint.preencode(state, b.index)
    nodeArray.preencode(state, b.nodes)
  },
  encode (state, b) {
    c.uint.encode(state, b.index)
    nodeArray.encode(state, b.nodes)
  },
  decode (state) {
    return {
      index: c.uint.decode(state),
      nodes: nodeArray.decode(state)
    }
  }
}

wire.data = {
  preencode (state, m) {
    state.end++ // flags
    c.uint.preencode(state, m.request)
    c.uint.preencode(state, m.fork)

    if (m.block) dataBlock.preencode(state, m.block)
    if (m.hash) dataHash.preencode(state, m.hash)
    if (m.seek) dataSeek.preencode(state, m.seek)
    if (m.upgrade) dataUpgrade.preencode(state, m.upgrade)
    if (m.manifest) manifest.preencode(state, m.manifest)
  },
  encode (state, m) {
    const flags = (m.block ? 1 : 0) | (m.hash ? 2 : 0) | (m.seek ? 4 : 0) | (m.upgrade ? 8 : 0) | (m.manifest ? 16 : 0)

    c.uint.encode(state, flags)
    c.uint.encode(state, m.request)
    c.uint.encode(state, m.fork)

    if (m.block) dataBlock.encode(state, m.block)
    if (m.hash) dataHash.encode(state, m.hash)
    if (m.seek) dataSeek.encode(state, m.seek)
    if (m.upgrade) dataUpgrade.encode(state, m.upgrade)
    if (m.manifest) manifest.encode(state, m.manifest)
  },
  decode (state) {
    const flags = c.uint.decode(state)

    return {
      request: c.uint.decode(state),
      fork: c.uint.decode(state),
      block: flags & 1 ? dataBlock.decode(state) : null,
      hash: flags & 2 ? dataHash.decode(state) : null,
      seek: flags & 4 ? dataSeek.decode(state) : null,
      upgrade: flags & 8 ? dataUpgrade.decode(state) : null,
      manifest: flags & 16 ? manifest.decode(state) : null
    }
  }
}

wire.noData = {
  preencode (state, m) {
    c.uint.preencode(state, m.request)
  },
  encode (state, m) {
    c.uint.encode(state, m.request)
  },
  decode (state, m) {
    return {
      request: c.uint.decode(state)
    }
  }
}

wire.want = {
  preencode (state, m) {
    c.uint.preencode(state, m.start)
    c.uint.preencode(state, m.length)
  },
  encode (state, m) {
    c.uint.encode(state, m.start)
    c.uint.encode(state, m.length)
  },
  decode (state) {
    return {
      start: c.uint.decode(state),
      length: c.uint.decode(state)
    }
  }
}

wire.unwant = {
  preencode (state, m) {
    c.uint.preencode(state, m.start)
    c.uint.preencode(state, m.length)
  },
  encode (state, m) {
    c.uint.encode(state, m.start)
    c.uint.encode(state, m.length)
  },
  decode (state, m) {
    return {
      start: c.uint.decode(state),
      length: c.uint.decode(state)
    }
  }
}

wire.range = {
  preencode (state, m) {
    state.end++ // flags
    c.uint.preencode(state, m.start)
    if (m.length !== 1) c.uint.preencode(state, m.length)
  },
  encode (state, m) {
    c.uint.encode(state, (m.drop ? 1 : 0) | (m.length === 1 ? 2 : 0))
    c.uint.encode(state, m.start)
    if (m.length !== 1) c.uint.encode(state, m.length)
  },
  decode (state) {
    const flags = c.uint.decode(state)

    return {
      drop: (flags & 1) !== 0,
      start: c.uint.decode(state),
      length: (flags & 2) !== 0 ? 1 : c.uint.decode(state)
    }
  }
}

wire.bitfield = {
  preencode (state, m) {
    c.uint.preencode(state, m.start)
    c.uint32array.preencode(state, m.bitfield)
  },
  encode (state, m) {
    c.uint.encode(state, m.start)
    c.uint32array.encode(state, m.bitfield)
  },
  decode (state, m) {
    return {
      start: c.uint.decode(state),
      bitfield: c.uint32array.decode(state)
    }
  }
}

wire.sync = {
  preencode (state, m) {
    state.end++ // flags
    c.uint.preencode(state, m.fork)
    c.uint.preencode(state, m.length)
    c.uint.preencode(state, m.remoteLength)
  },
  encode (state, m) {
    c.uint.encode(state, (m.canUpgrade ? 1 : 0) | (m.uploading ? 2 : 0) | (m.downloading ? 4 : 0) | (m.hasManifest ? 8 : 0))
    c.uint.encode(state, m.fork)
    c.uint.encode(state, m.length)
    c.uint.encode(state, m.remoteLength)
  },
  decode (state) {
    const flags = c.uint.decode(state)

    return {
      fork: c.uint.decode(state),
      length: c.uint.decode(state),
      remoteLength: c.uint.decode(state),
      canUpgrade: (flags & 1) !== 0,
      uploading: (flags & 2) !== 0,
      downloading: (flags & 4) !== 0,
      hasManifest: (flags & 8) !== 0
    }
  }
}

wire.reorgHint = {
  preencode (state, m) {
    c.uint.preencode(state, m.from)
    c.uint.preencode(state, m.to)
    c.uint.preencode(state, m.ancestors)
  },
  encode (state, m) {
    c.uint.encode(state, m.from)
    c.uint.encode(state, m.to)
    c.uint.encode(state, m.ancestors)
  },
  decode (state) {
    return {
      from: c.uint.encode(state),
      to: c.uint.encode(state),
      ancestors: c.uint.encode(state)
    }
  }
}

wire.extension = {
  preencode (state, m) {
    c.string.preencode(state, m.name)
    c.raw.preencode(state, m.message)
  },
  encode (state, m) {
    c.string.encode(state, m.name)
    c.raw.encode(state, m.message)
  },
  decode (state) {
    return {
      name: c.string.decode(state),
      message: c.raw.decode(state)
    }
  }
}

const keyValue = {
  preencode (state, p) {
    c.string.preencode(state, p.key)
    c.optionalBuffer.preencode(state, p.value)
  },
  encode (state, p) {
    c.string.encode(state, p.key)
    c.optionalBuffer.encode(state, p.value)
  },
  decode (state) {
    return {
      key: c.string.decode(state),
      value: c.optionalBuffer.decode(state)
    }
  }
}

const treeUpgrade = {
  preencode (state, u) {
    c.uint.preencode(state, u.fork)
    c.uint.preencode(state, u.ancestors)
    c.uint.preencode(state, u.length)
    c.optionalBuffer.preencode(state, u.signature)
  },
  encode (state, u) {
    c.uint.encode(state, u.fork)
    c.uint.encode(state, u.ancestors)
    c.uint.encode(state, u.length)
    c.optionalBuffer.encode(state, u.signature)
  },
  decode (state) {
    return {
      fork: c.uint.decode(state),
      ancestors: c.uint.decode(state),
      length: c.uint.decode(state),
      signature: c.optionalBuffer.decode(state)
    }
  }
}

const bitfieldUpdate = { // TODO: can maybe be folded into a HAVE later on with the most recent spec
  preencode (state, b) {
    state.end++ // flags
    c.uint.preencode(state, b.start)
    c.uint.preencode(state, b.length)
  },
  encode (state, b) {
    state.buffer[state.start++] = b.drop ? 1 : 0
    c.uint.encode(state, b.start)
    c.uint.encode(state, b.length)
  },
  decode (state) {
    const flags = c.uint.decode(state)
    return {
      drop: (flags & 1) !== 0,
      start: c.uint.decode(state),
      length: c.uint.decode(state)
    }
  }
}

const oplog = exports.oplog = {}

oplog.entry = {
  preencode (state, m) {
    state.end++ // flags
    if (m.userData) keyValue.preencode(state, m.userData)
    if (m.treeNodes) nodeArray.preencode(state, m.treeNodes)
    if (m.treeUpgrade) treeUpgrade.preencode(state, m.treeUpgrade)
    if (m.bitfield) bitfieldUpdate.preencode(state, m.bitfield)
  },
  encode (state, m) {
    const s = state.start++
    let flags = 0

    if (m.userData) {
      flags |= 1
      keyValue.encode(state, m.userData)
    }
    if (m.treeNodes) {
      flags |= 2
      nodeArray.encode(state, m.treeNodes)
    }
    if (m.treeUpgrade) {
      flags |= 4
      treeUpgrade.encode(state, m.treeUpgrade)
    }
    if (m.bitfield) {
      flags |= 8
      bitfieldUpdate.encode(state, m.bitfield)
    }

    state.buffer[s] = flags
  },
  decode (state) {
    const flags = c.uint.decode(state)
    return {
      userData: (flags & 1) !== 0 ? keyValue.decode(state) : null,
      treeNodes: (flags & 2) !== 0 ? nodeArray.decode(state) : null,
      treeUpgrade: (flags & 4) !== 0 ? treeUpgrade.decode(state) : null,
      bitfield: (flags & 8) !== 0 ? bitfieldUpdate.decode(state) : null
    }
  }
}

const keyPair = {
  preencode (state, kp) {
    c.optionalBuffer.preencode(state, kp.publicKey)
    c.optionalBuffer.preencode(state, kp.secretKey)
  },
  encode (state, kp) {
    c.optionalBuffer.encode(state, kp.publicKey)
    c.optionalBuffer.encode(state, kp.secretKey)
  },
  decode (state) {
    return {
      publicKey: c.optionalBuffer.decode(state),
      secretKey: c.optionalBuffer.decode(state)
    }
  }
}

const reorgHint = {
  preencode (state, r) {
    c.uint.preencode(state, r.from)
    c.uint.preencode(state, r.to)
    c.uint.preencode(state, r.ancestors)
  },
  encode (state, r) {
    c.uint.encode(state, r.from)
    c.uint.encode(state, r.to)
    c.uint.encode(state, r.ancestors)
  },
  decode (state) {
    return {
      from: c.uint.decode(state),
      to: c.uint.decode(state),
      ancestors: c.uint.decode(state)
    }
  }
}

const reorgHintArray = c.array(reorgHint)

const hints = {
  preencode (state, h) {
    reorgHintArray.preencode(state, h.reorgs)
    c.uint.preencode(state, h.contiguousLength)
  },
  encode (state, h) {
    reorgHintArray.encode(state, h.reorgs)
    c.uint.encode(state, h.contiguousLength)
  },
  decode (state) {
    return {
      reorgs: reorgHintArray.decode(state),
      contiguousLength: state.start < state.end ? c.uint.decode(state) : 0
    }
  }
}

const treeHeader = {
  preencode (state, t) {
    c.uint.preencode(state, t.fork)
    c.uint.preencode(state, t.length)
    c.optionalBuffer.preencode(state, t.rootHash)
    c.optionalBuffer.preencode(state, t.signature)
  },
  encode (state, t) {
    c.uint.encode(state, t.fork)
    c.uint.encode(state, t.length)
    c.optionalBuffer.encode(state, t.rootHash)
    c.optionalBuffer.encode(state, t.signature)
  },
  decode (state) {
    return {
      fork: c.uint.decode(state),
      length: c.uint.decode(state),
      rootHash: c.optionalBuffer.decode(state),
      signature: c.optionalBuffer.decode(state)
    }
  }
}

const types = {
  preencode (state, t) {
    c.string.preencode(state, t.tree)
    c.string.preencode(state, t.bitfield)
    c.string.preencode(state, t.signer)
  },
  encode (state, t) {
    c.string.encode(state, t.tree)
    c.string.encode(state, t.bitfield)
    c.string.encode(state, t.signer)
  },
  decode (state) {
    return {
      tree: c.string.decode(state),
      bitfield: c.string.decode(state),
      signer: c.string.decode(state)
    }
  }
}

const externalHeader = {
  preencode (state, m) {
    c.uint.preencode(state, m.start)
    c.uint.preencode(state, m.length)
  },
  encode (state, m) {
    c.uint.encode(state, m.start)
    c.uint.encode(state, m.length)
  },
  decode (state) {
    return {
      start: c.uint.decode(state),
      length: c.uint.decode(state)
    }
  }
}

const keyValueArray = c.array(keyValue)

oplog.header = {
  preencode (state, h) {
    state.end += 2 // version + flags
    if (h.external) {
      externalHeader.preencode(state, h.external)
      return
    }
    c.fixed32.preencode(state, h.key)
    if (h.manifest) manifest.preencode(state, h.manifest)
    if (h.keyPair) keyPair.preencode(state, h.keyPair)
    keyValueArray.preencode(state, h.userData)
    treeHeader.preencode(state, h.tree)
    hints.preencode(state, h.hints)
  },
  encode (state, h) {
    c.uint.encode(state, 1)
    if (h.external) {
      c.uint.encode(state, 1) // ONLY set the first big for clarity
      externalHeader.encode(state, h.external)
      return
    }
    c.uint.encode(state, (h.manifest ? 2 : 0) | (h.keyPair ? 4 : 0))
    c.fixed32.encode(state, h.key)
    if (h.manifest) manifest.encode(state, h.manifest)
    if (h.keyPair) keyPair.encode(state, h.keyPair)
    keyValueArray.encode(state, h.userData)
    treeHeader.encode(state, h.tree)
    hints.encode(state, h.hints)
  },
  decode (state) {
    const version = c.uint.decode(state)

    if (version > 1) {
      throw new Error('Invalid header version. Expected <= 1, got ' + version)
    }

    if (version === 0) {
      const old = {
        types: types.decode(state),
        userData: keyValueArray.decode(state),
        tree: treeHeader.decode(state),
        signer: keyPair.decode(state),
        hints: hints.decode(state)
      }

      return {
        external: null,
        key: old.signer.publicKey,
        manifest: {
          version: 0,
          hash: old.types.tree,
          allowPatch: false,
          quorum: 1,
          signers: [{
            signature: old.types.signer,
            namespace: DEFAULT_NAMESPACE,
            publicKey: old.signer.publicKey
          }],
          prologue: null
        },
        keyPair: old.signer.secretKey ? old.signer : null,
        userData: old.userData,
        tree: old.tree,
        hints: old.hints
      }
    }

    const flags = c.uint.decode(state)

    if (flags & 1) {
      return {
        external: externalHeader.decode(state),
        key: null,
        manifest: null,
        keyPair: null,
        userData: null,
        tree: null,
        hints: null
      }
    }

    return {
      external: null,
      key: c.fixed32.decode(state),
      manifest: (flags & 2) !== 0 ? manifest.decode(state) : null,
      keyPair: (flags & 4) !== 0 ? keyPair.decode(state) : null,
      userData: keyValueArray.decode(state),
      tree: treeHeader.decode(state),
      hints: hints.decode(state)
    }
  }
}

const uintArray = c.array(c.uint)

const multisigInput = {
  preencode (state, inp) {
    c.uint.preencode(state, inp.signer)
    c.fixed64.preencode(state, inp.signature)
    c.uint.preencode(state, inp.patch)
  },
  encode (state, inp) {
    c.uint.encode(state, inp.signer)
    c.fixed64.encode(state, inp.signature)
    c.uint.encode(state, inp.patch)
  },
  decode (state) {
    return {
      signer: c.uint.decode(state),
      signature: c.fixed64.decode(state),
      patch: c.uint.decode(state)
    }
  }
}

const patchEncodingv0 = {
  preencode (state, n) {
    c.uint.preencode(state, n.start)
    c.uint.preencode(state, n.length)
    uintArray.preencode(state, n.nodes)
  },
  encode (state, n) {
    c.uint.encode(state, n.start)
    c.uint.encode(state, n.length)
    uintArray.encode(state, n.nodes)
  },
  decode (state) {
    return {
      start: c.uint.decode(state),
      length: c.uint.decode(state),
      nodes: uintArray.decode(state)
    }
  }
}

const multisigInputv0 = {
  preencode (state, n) {
    state.end++
    c.uint.preencode(state, n.signer)
    c.fixed64.preencode(state, n.signature)
    if (n.patch) patchEncodingv0.preencode(state, n.patch)
  },
  encode (state, n) {
    c.uint.encode(state, n.patch ? 1 : 0)
    c.uint.encode(state, n.signer)
    c.fixed64.encode(state, n.signature)
    if (n.patch) patchEncodingv0.encode(state, n.patch)
  },
  decode (state) {
    const flags = c.uint.decode(state)
    return {
      signer: c.uint.decode(state),
      signature: c.fixed64.decode(state),
      patch: (flags & 1) ? patchEncodingv0.decode(state) : null
    }
  }
}

const multisigInputArrayv0 = c.array(multisigInputv0)
const multisigInputArray = c.array(multisigInput)

const compactNode = {
  preencode (state, n) {
    c.uint.preencode(state, n.index)
    c.uint.preencode(state, n.size)
    c.fixed32.preencode(state, n.hash)
  },
  encode (state, n) {
    c.uint.encode(state, n.index)
    c.uint.encode(state, n.size)
    c.fixed32.encode(state, n.hash)
  },
  decode (state) {
    return {
      index: c.uint.decode(state),
      size: c.uint.decode(state),
      hash: c.fixed32.decode(state)
    }
  }
}

const compactNodeArray = c.array(compactNode)

exports.multiSignaturev0 = {
  preencode (state, s) {
    multisigInputArrayv0.preencode(state, s.proofs)
    compactNodeArray.preencode(state, s.patch)
  },
  encode (state, s) {
    multisigInputArrayv0.encode(state, s.proofs)
    compactNodeArray.encode(state, s.patch)
  },
  decode (state) {
    return {
      proofs: multisigInputArrayv0.decode(state),
      patch: compactNodeArray.decode(state)
    }
  }
}

exports.multiSignature = {
  preencode (state, s) {
    multisigInputArray.preencode(state, s.proofs)
    compactNodeArray.preencode(state, s.patch)
  },
  encode (state, s) {
    multisigInputArray.encode(state, s.proofs)
    compactNodeArray.encode(state, s.patch)
  },
  decode (state) {
    return {
      proofs: multisigInputArray.decode(state),
      patch: compactNodeArray.decode(state)
    }
  }
}
const c = require('compact-encoding')
const schema = require('../../encoding/spec/hyperschema')
const { core: coreKey } = require('../../lib/keys.js')
const View = require('../../lib/view.js')

const CORE_AUTH = schema.getEncoding('@core/auth')

const { CorestoreRX, CorestoreTX } = require('../../lib/tx.js')

async function core (core, { version, dryRun = false } = {}) {
  if (dryRun) return // dryRun mode not supported atm

  const rx = core.db.read({ autoDestroy: true })
  const storedAuthPromise = rx.get(coreKey.auth(core.core.corePointer))

  rx.tryFlush()

  const storedAuth = await storedAuthPromise
  if (!storedAuth) return

  const auth = c.decode(CORE_AUTH, storedAuth)
  await commitCoreMigration(auth, core, version)
}

module.exports = { core }

async function commitCoreMigration (auth, core, version) {
  const view = new View()
  const rx = new CorestoreRX(core.db, view)

  const storeCorePromise = rx.getCore(auth.discoveryKey)
  rx.tryFlush()

  const storeCore = await storeCorePromise

  storeCore.version = version

  const tx = new CorestoreTX(view)
  tx.putCore(auth.discoveryKey, storeCore)
  tx.apply()

  await View.flush(view.changes, core.db)
}
{
  "name": "hypercore-storage",
  "version": "3.2.1",
  "main": "index.js",
  "files": [
    "index.js",
    "lib/*.js",
    "encoding/*",
    "migrations/0/*.js",
    "migrations/1/*.js"
  ],
  "scripts": {
    "format": "prettier --write .",
    "lint": "prettier --check . && lunte",
    "test": "npm run test:node && npm run test:bare",
    "test:bare": "brittle-bare test/all.js",
    "test:generate": "brittle -r test/all.js test/*.js",
    "test:node": "brittle-node test/all.js"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "description": "Storage engine for Hypercore",
  "imports": {
    "fs": {
      "bare": "bare-fs",
      "default": "fs"
    },
    "fs/*": {
      "bare": "bare-fs/*",
      "default": "fs/*"
    },
    "os": {
      "bare": "bare-os",
      "default": "os"
    },
    "path": {
      "bare": "bare-path",
      "default": "path"
    }
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/hypercore-storage.git"
  },
  "bugs": {
    "url": "https://github.com/holepunchto/hypercore-storage/issues"
  },
  "homepage": "https://github.com/holepunchto/hypercore-storage#readme",
  "dependencies": {
    "b4a": "^1.6.7",
    "bare-path": "^3.0.0",
    "compact-encoding": "^3.1.0",
    "device-file": "^2.1.2",
    "flat-tree": "^1.12.1",
    "hypercore-crypto": "^3.4.2",
    "hyperschema": "^1.21.0",
    "index-encoder": "^3.3.2",
    "resolve-reject-promise": "^1.0.0",
    "rocksdb-native": "^3.11.0",
    "scope-lock": "^1.2.4",
    "streamx": "^2.21.1",
    "xache": "^1.2.1"
  },
  "devDependencies": {
    "bare-os": "^3.9.1",
    "brittle": "^3.7.0",
    "hypercore-storage-2.9.0": "npm:hypercore-storage@^2.9.0",
    "lunte": "^1.3.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^2.0.0",
    "test-tmp": "^1.3.1"
  }
}
const { EventEmitter } = require('events')
const isOptions = require('is-options')
const crypto = require('hypercore-crypto')
const CoreStorage = require('hypercore-storage')
const c = require('compact-encoding')
const b4a = require('b4a')
const NoiseSecretStream = require('@hyperswarm/secret-stream')
const Protomux = require('protomux')
const id = require('hypercore-id-encoding')
const safetyCatch = require('safety-catch')
const unslab = require('unslab')
const flat = require('flat-tree')
const assert = require('nanoassert')

const { SMALL_WANTS } = require('./lib/feature-flags')
const { UPDATE_COMPAT } = require('./lib/wants')
const MarkBitfield = require('./lib/mark-bitfield')

const inspect = require('./lib/inspect')
const Core = require('./lib/core')
const Info = require('./lib/info')
const Download = require('./lib/download')
const DefaultEncryption = require('./lib/default-encryption')
const caps = require('./lib/caps')
const Replicator = require('./lib/replicator')
const Verifier = require('./lib/verifier')
const { manifestHash, createManifest, encodeManifest } = Verifier
const { ReadStream, WriteStream, ByteStream } = require('./lib/streams')
const { MerkleTree } = require('./lib/merkle-tree')
const { proof, verify } = require('./lib/fully-remote-proof')
const {
  ASSERTION,
  BAD_ARGUMENT,
  SESSION_CLOSED,
  SESSION_MOVED,
  SESSION_NOT_WRITABLE,
  SNAPSHOT_NOT_AVAILABLE,
  DECODING_ERROR,
  REQUEST_CANCELLED,
  INVALID_CHECKSUM
} = require('hypercore-errors')

// Hypercore actually does not have any notion of max/min block sizes
// but we enforce 15mb to ensure smooth replication (each block is transmitted atomically)
const MAX_SUGGESTED_BLOCK_SIZE = 15 * 1024 * 1024

class Hypercore extends EventEmitter {
  constructor(storage, key, opts) {
    super()

    if (isOptions(storage) && !storage.db) {
      opts = storage
      storage = null
      key = opts.key || null
    } else if (isOptions(key)) {
      opts = key
      key = opts.key || null
    }

    if (key && typeof key === 'string') key = id.decode(key)
    if (!opts) opts = {}

    if (!storage) storage = opts.storage

    this.core = null
    this.state = null
    this.encryption = null
    this.extensions = new Map()

    this.valueEncoding = null
    this.encodeBatch = null
    this.activeRequests = []
    this.sessions = null
    this.ongc = null

    this.keyPair = opts.keyPair || null
    this.readable = true
    this.writable = false
    this.exclusive = false
    this.opened = false
    this.closed = false
    this.weak = !!opts.weak
    this.snapshotted = !!opts.snapshot
    this.onseq = opts.onseq || null
    this.onwait = opts.onwait || null
    this.wait = opts.wait !== false
    this.timeout = opts.timeout || 0
    this.preload = null
    this.closing = null
    this.opening = null

    this._readonly = opts.writable === false
    this._preappend = preappend.bind(this)
    this._snapshot = null
    this._findingPeers = 0
    this._active = opts.weak ? !!opts.active : opts.active !== false

    this.waits = 0

    // Mark & Sweep GC
    this._marking = false
    this._marks = null

    this._sessionIndex = -1
    this._stateIndex = -1 // maintained by session state
    this._monitorIndex = -1 // maintained by replication state

    this.opening = this._open(storage, key, opts)
    this.opening.catch(safetyCatch)

    this.on('newListener', maybeAddMonitor)
  }

  [Symbol.for('nodejs.util.inspect.custom')](depth, opts) {
    return inspect(this, depth, opts)
  }

  static MAX_SUGGESTED_BLOCK_SIZE = MAX_SUGGESTED_BLOCK_SIZE

  static DefaultEncryption = DefaultEncryption

  static SMALL_WANTS = SMALL_WANTS

  static enable(flag) {
    const enableCompat = (flag & SMALL_WANTS) === 0
    UPDATE_COMPAT(enableCompat)
  }

  static setRecoveryPeers(peers) {
    Core.setRecoveryPeers(peers)
  }

  static key(manifest, { compat, version, namespace } = {}) {
    if (b4a.isBuffer(manifest)) {
      manifest =
        manifest.byteLength === 32
          ? { version, signers: [{ publicKey: manifest, namespace }] }
          : this.parseManifest(manifest)
    }
    return compat ? manifest.signers[0].publicKey : manifestHash(createManifest(manifest))
  }

  static parseManifest(manifest, key) {
    const parsed = createManifest(manifest)
    if (parsed === null) return null

    if (parsed.quorum > parsed.signers.length) {
      throw BAD_ARGUMENT('Quorum should not be higher than the number of signers')
    }

    if (key && !Verifier.isValidManifest(key, parsed)) {
      throw INVALID_CHECKSUM('Manifest does not hash to provided key')
    }

    return parsed
  }

  static discoveryKey(key) {
    return crypto.discoveryKey(key)
  }

  static blockEncryptionKey(key, encryptionKey) {
    return DefaultEncryption.blockEncryptionKey(key, encryptionKey)
  }

  static getProtocolMuxer(stream) {
    return stream.noiseStream.userData
  }

  static createCore(storage, opts) {
    return new Core(Hypercore.defaultStorage(storage), { autoClose: false, ...opts })
  }

  static createProtocolStream(isInitiator, opts = {}) {
    let outerStream = Protomux.isProtomux(isInitiator)
      ? isInitiator.stream
      : isStream(isInitiator)
        ? isInitiator
        : opts.stream

    let noiseStream = null

    if (outerStream) {
      noiseStream = outerStream.noiseStream
    } else {
      noiseStream = new NoiseSecretStream(isInitiator, null, opts)
      outerStream = noiseStream.rawStream
    }
    if (!noiseStream) throw BAD_ARGUMENT('Invalid stream', this.discoveryKey)

    if (!noiseStream.userData) {
      const protocol = Protomux.from(noiseStream)

      if (opts.keepAlive !== false && noiseStream.keepAlive === 0) {
        noiseStream.setKeepAlive(5000)
      }
      noiseStream.userData = protocol
    }

    if (opts.ondiscoverykey) {
      const ondiscoverykey = createDiscoveryKeyHandler(opts.ondiscoverykey)
      noiseStream.userData.pair({ protocol: 'hypercore/alpha' }, ondiscoverykey)
    }

    return outerStream
  }

  static defaultStorage(storage, opts = {}) {
    if (CoreStorage.isCoreStorage(storage)) return storage

    const directory = storage
    return new CoreStorage(directory, opts)
  }

  static clearRequests(session, err) {
    Replicator.clearRequests(session, err)
  }

  static destroyRequests(session, err) {
    if (isRequestsDestroyed(session)) return
    Replicator.clearRequests(session, err)
    session.push(null) // mark as dead
  }

  static async treeHashFromStorage(session, length = session.length) {
    const roots = await MerkleTree.getRoots(session.state, length)
    return crypto.tree(roots)
  }

  snapshot(opts) {
    return this.session({ ...opts, snapshot: true })
  }

  compact() {
    return this.core.compact()
  }

  session(opts = {}) {
    if (this.closing) {
      // This makes the closing logic a lot easier. If this turns out to be a problem
      // in practice, open an issue and we'll try to make a solution for it.
      throw SESSION_CLOSED('Cannot make sessions on a closing core', this.discoveryKey)
    }
    if (opts.checkout !== undefined && !opts.name && !opts.atom) {
      throw ASSERTION('Checkouts are only supported on atoms or named sessions', this.discoveryKey)
    }

    const wait = opts.wait === false ? false : this.wait
    const writable = opts.writable === undefined ? !this._readonly : opts.writable === true
    const onwait = opts.onwait === undefined ? this.onwait : opts.onwait
    const onseq = opts.onseq === undefined ? this.onseq : opts.onseq
    const timeout = opts.timeout === undefined ? this.timeout : opts.timeout
    const weak = opts.weak === undefined ? this.weak : opts.weak
    const marking = this._marking
    const marks = this._marks
    const Clz = opts.class || Hypercore
    const s = new Clz(null, this.key, {
      ...opts,
      wait,
      onwait,
      onseq,
      timeout,
      writable,
      weak,
      parent: this
    })
    s._marking = marking
    s._marks = marks

    return s
  }

  async setEncryptionKey(key, opts) {
    if (!this.opened) await this.opening
    const encryption = this._getEncryptionProvider({ key, block: !!(opts && opts.block) })
    return this.setEncryption(encryption)
  }

  async setEncryption(encryption) {
    if (!this.opened) await this.opening

    if (encryption === null) {
      this.encryption = encryption
      return
    }

    if (!isEncryptionProvider(encryption)) {
      throw ASSERTION('Provider does not satisfy HypercoreEncryption interface', this.discoveryKey)
    }

    this.encryption = encryption
  }

  async setGroup(topic) {
    if (!this.opened) await this.opening
    return this.core.setGroup(topic)
  }

  setKeyPair(keyPair) {
    this.keyPair = keyPair
  }

  setActive(bool) {
    const active = !!bool
    if (active === this._active || this.closing) return
    this._active = active
    if (!this.opened) return
    this.core.replicator.updateActivity(this._active ? 1 : -1)
  }

  async _open(storage, key, opts) {
    const preload = opts.preload || (opts.parent && opts.parent.preload)

    if (preload) {
      this.sessions = [] // in case someone looks at it like with peers
      this.preload = preload
      opts = { ...opts, ...(await this.preload) }
      this.preload = null
    }

    const parent = opts.parent || null
    const core = opts.core || (parent && parent.core)
    const sessions = opts.sessions || (parent && parent.sessions)
    const ongc = opts.ongc || (parent && parent.ongc)

    if (core) this.core = core
    if (ongc) this.ongc = ongc
    if (sessions) this.sessions = sessions

    if (this.sessions === null) this.sessions = []
    this._sessionIndex = this.sessions.push(this) - 1

    if (this.core === null) initOnce(this, storage, key, opts)
    if (this._monitorIndex === -2) this.core.addMonitor(this)

    try {
      await this._openSession(opts)
    } catch (err) {
      if (this.core.autoClose && this.core.hasSession() === false) await this.core.close()

      if (this.exclusive) this.core.unlockExclusive()

      this.core.removeMonitor(this)
      this._removeSession()

      if (this.state !== null) this.state.removeSession(this)

      this.closed = true
      this.emit('close')
      throw err
    }

    // Setup automatic recovery if in repair mode
    if (this.core._repairMode) {
      const recoverTreeNodeFromPeersBound = this.recoverTreeNodeFromPeers.bind(this)
      this.once('repaired', () => {
        this.off('peer-add', recoverTreeNodeFromPeersBound)
      })
      this.on('peer-add', recoverTreeNodeFromPeersBound)
    }

    this.emit('ready')

    // if we are a weak session the core might have closed...
    if (this.core.closing) this.close().catch(safetyCatch)
  }

  _removeSession() {
    if (this._sessionIndex === -1) return
    const head = this.sessions.pop()
    if (head !== this) this.sessions[(head._sessionIndex = this._sessionIndex)] = head
    this._sessionIndex = -1
    if (this.ongc !== null) this.ongc(this)
  }

  async _openSession(opts) {
    if (this.core.opened === false) await this.core.ready()

    if (opts.group && !this.core.header.group) {
      await this.core.setGroup(opts.group)
    }

    if (this.keyPair === null) this.keyPair = opts.keyPair || this.core.header.keyPair

    const parent = opts.parent || null
    if (parent && parent.encryption) this.encryption = parent.encryption

    const e = getEncryptionOption(opts)
    if (!this.encryption) this.encryption = this._getEncryptionProvider(e)

    this.writable = this._isWritable()

    if (opts.valueEncoding) {
      this.valueEncoding = c.from(opts.valueEncoding)
    }
    if (opts.encodeBatch) {
      this.encodeBatch = opts.encodeBatch
    }

    // one session sets for pushOnly for all
    if (opts.pushOnly === true) {
      this.core.replicator.setPushOnly(true)
    }

    if (parent) {
      if (parent._stateIndex === -1) await parent.ready()
      if (!this.keyPair) this.keyPair = parent.keyPair

      const ps = parent.state

      if (ps) {
        const shouldSnapshot = this.snapshotted && !ps.isSnapshot()
        this.state = shouldSnapshot ? await ps.snapshot() : ps.ref()
      }

      if (this.snapshotted && this.core && !this._snapshot) {
        this._updateSnapshot()
      }
    }

    if (opts.exclusive && opts.writable !== false) {
      this.exclusive = true
      await this.core.lockExclusive()
    }

    const parentState = parent ? parent.state : this.core.state
    const checkout = opts.checkout === undefined ? -1 : opts.checkout
    const state = this.state

    if (opts.atom) {
      this.state = await parentState.createSession(null, false, opts.atom)
      if (state) state.unref()
    } else if (opts.name) {
      // todo: need to make named sessions safe before ready
      // atm we always copy the state in passCapabilities
      this.state = await parentState.createSession(opts.name, !!opts.overwrite, null)
      if (state) state.unref() // ref'ed above in setup session
    }

    if (this.state && checkout !== -1) {
      if (!opts.name && !opts.atom) {
        throw ASSERTION('Checkouts must be named or atomized', this.discoveryKey)
      }
      if (checkout > this.state.length) {
        throw ASSERTION(
          `Invalid checkout ${checkout} for ${opts.name}, length is ${this.state.length}`,
          this.discoveryKey
        )
      }
      if (this.state.prologue && checkout < this.state.prologue.length) {
        throw ASSERTION(
          `Invalid checkout ${checkout} for ${opts.name}, prologue length is ${this.state.prologue.length}`,
          this.discoveryKey
        )
      }
      if (checkout < this.state.length) await this.state.truncate(checkout, this.fork)
    }

    if (this.state === null) {
      this.state = this.core.state.ref()
    }

    this.writable = this._isWritable()

    if (this.snapshotted && this.core) this._updateSnapshot()

    this.state.addSession(this)
    // TODO: we need to rework the core reference flow, as the state and session do not always agree now due to moveTo
    this.core = this.state.core // in case it was wrong...

    if (opts.userData) {
      const tx = this.state.storage.write()
      for (const [key, value] of Object.entries(opts.userData)) {
        tx.putUserData(key, value)
      }
      await tx.flush()
    }

    if (opts.manifest && !this.core.header.manifest) {
      await this.core.setManifest(opts.manifest)
    }

    this.core.replicator.updateActivity(this._active ? 1 : 0)

    this.opened = true
  }

  get replicator() {
    return this.core === null ? null : this.core.replicator
  }

  _getSnapshot() {
    return {
      length: this.state.length,
      byteLength: this.state.byteLength,
      fork: this.state.fork
    }
  }

  _updateSnapshot() {
    const prev = this._snapshot
    const next = (this._snapshot = this._getSnapshot())

    if (!prev) return true
    return prev.length !== next.length || prev.fork !== next.fork
  }

  _isWritable() {
    if (this._readonly) return false
    if (this.state && !this.state.isDefault()) return true
    return !!(this.keyPair && this.keyPair.secretKey)
  }

  close({ error } = {}) {
    if (this.closing) return this.closing

    this.closing = this._close(error || null)
    return this.closing
  }

  clearRequests(activeRequests, error) {
    if (!activeRequests.length) return
    if (this.core) this.core.replicator.clearRequests(activeRequests, error)
  }

  async _close(error) {
    if (this.opened === false) {
      try {
        await this.opening
      } catch (err) {
        if (!this.closed) throw err
      }
    }

    if (this.closed === true) return

    this.core.removeMonitor(this)
    this.state.removeSession(this)
    this._removeSession()

    this.readable = false
    this.writable = false
    this.opened = false

    const gc = []
    for (const ext of this.extensions.values()) {
      if (ext.session === this) gc.push(ext)
    }
    for (const ext of gc) ext.destroy()

    this.core.replicator.findingPeers -= this._findingPeers
    this.core.replicator.clearRequests(this.activeRequests, error)
    this.core.replicator.updateActivity(this._active ? -1 : 0)

    this._findingPeers = 0

    this.state.unref()

    if (this.exclusive) this.core.unlockExclusive()

    if (this.core.hasSession()) {
      // emit "fake" close as this is a session
      this.closed = true
      this.emit('close')
      return
    }

    if (this.core.autoClose) await this.core.close()

    this.closed = true
    this.emit('close')
  }

  async commit(session, opts) {
    await this.ready()
    await session.ready()

    return this.state.commit(session.state, { keyPair: this.keyPair, ...opts })
  }

  replicate(isInitiator, opts = {}) {
    // Only limitation here is that ondiscoverykey doesn't work atm when passing a muxer directly,
    // because it doesn't really make a lot of sense.
    if (Protomux.isProtomux(isInitiator)) return this._attachToMuxer(isInitiator)

    // if same stream is passed twice, ignore the 2nd one before we make sessions etc
    if (isStream(isInitiator) && this._isAttached(isInitiator)) return isInitiator

    const protocolStream = Hypercore.createProtocolStream(isInitiator, opts)
    const noiseStream = protocolStream.noiseStream
    const protocol = noiseStream.userData

    this._attachToMuxer(protocol)

    return protocolStream
  }

  _isAttached(stream) {
    return (
      stream.userData &&
      this.core &&
      this.core.replicator &&
      this.core.replicator.attached(stream.userData)
    )
  }

  _attachToMuxer(mux) {
    if (this.opened) {
      this.core.replicator.attachTo(mux)
    } else {
      this.opening.then(() => this.core.replicator.attachTo(mux), mux.destroy.bind(mux))
    }

    return mux
  }

  get id() {
    return this.core === null ? null : this.core.id
  }

  get key() {
    return this.core === null ? null : this.core.key
  }

  get discoveryKey() {
    return this.core === null ? null : this.core.discoveryKey
  }

  get manifest() {
    return this.core === null ? null : this.core.manifest
  }

  getManifest({ raw = false } = {}) {
    if (this.manifest === null) return null
    return raw ? encodeManifest(this.manifest) : this.manifest
  }

  get length() {
    if (this._snapshot) return this._snapshot.length
    return this.opened === false ? 0 : this.state.length
  }

  get signedLength() {
    return this.opened === false ? 0 : this.state.signedLength()
  }

  /**
   * Deprecated. Use `const { byteLength } = await core.info()`.
   */
  get byteLength() {
    if (this.opened === false) return 0
    if (this._snapshot) return this._snapshot.byteLength
    return this.state.byteLength - this.state.length * this.padding
  }

  get remoteContiguousLength() {
    if (this.opened === false) return 0
    return Math.min(this.core.state.length, this.core.header.hints.remoteContiguousLength)
  }

  get contiguousLength() {
    if (this.opened === false) return 0
    return Math.min(this.core.state.length, this.core.header.hints.contiguousLength)
  }

  get contiguousByteLength() {
    return 0
  }

  get fork() {
    if (this.opened === false) return 0
    return this.state.fork
  }

  get padding() {
    if (this.encryption && this.key && this.manifest) {
      return this.encryption.padding(this.core, this.length)
    }

    return 0
  }

  get peers() {
    return this.opened === false ? [] : this.core.replicator.peers
  }

  get globalCache() {
    return this.opened === false ? null : this.core.globalCache
  }

  get recovering() {
    return this.opened === false ? 0 : this.core.header.hints.recovering
  }

  ready() {
    return this.opening
  }

  async recover() {
    if (this.opened === false) await this.opening
    return this.state.waitForRecovery()
  }

  async setUserData(key, value) {
    if (this.opened === false) await this.opening
    const existing = await this.getUserData(key)
    if (existing && b4a.isBuffer(value) && b4a.equals(existing, value)) return
    await this.state.setUserData(key, value)
  }

  async getUserData(key) {
    if (this.opened === false) await this.opening
    const batch = this.state.storage.read()
    const p = batch.getUserData(key)
    batch.tryFlush()
    return p
  }

  transferSession(core) {
    // todo: validate we can move

    if (this.weak === false) {
      this.core.activeSessions--
      core.activeSessions++
    }

    if (this._monitorIndex >= 0) {
      this.core.removeMonitor(this)
      core.addMonitor(this)
    }

    const old = this.core

    this.core = core

    old.replicator.clearRequests(this.activeRequests, SESSION_MOVED())

    this.emit('migrate', this.key)
  }

  findingPeers() {
    this._findingPeers++
    if (this.core !== null && !this.closing) this.core.replicator.findingPeers++

    let once = true

    return () => {
      if (this.closing || !once) return
      once = false
      this._findingPeers--
      if (this.core !== null && --this.core.replicator.findingPeers === 0) {
        this.core.replicator.queueUpdateAll()
      }
    }
  }

  async info(opts) {
    if (this.opened === false) await this.opening

    return Info.from(this, opts)
  }

  async update(opts) {
    if (this.opened === false) await this.opening
    if (this.closing !== null) return false
    if (this.snapshotted) return false

    if (this.writable && (!opts || opts.force !== true)) return false

    const remoteWait = this._shouldWait(opts, this.core.replicator.findingPeers > 0)

    let upgraded = false

    if (await this.core.replicator.applyPendingReorg()) {
      upgraded = true
    }

    if (!upgraded && remoteWait) {
      const activeRequests = (opts && opts.activeRequests) || this.activeRequests
      if (isRequestsDestroyed(activeRequests)) throw REQUEST_CANCELLED()

      const req = this.core.replicator.addUpgrade(activeRequests)

      try {
        upgraded = await req.promise
      } catch (err) {
        if (isSessionMoved(err)) return this.update(opts)
        throw err
      }
    }

    if (!upgraded) return false
    return true
  }

  async seek(bytes, opts) {
    if (this.opened === false) await this.opening
    if (!isValidIndex(bytes)) throw ASSERTION('seek is invalid', this.discoveryKey)

    const activeRequests = (opts && opts.activeRequests) || this.activeRequests
    if (isRequestsDestroyed(activeRequests)) throw REQUEST_CANCELLED()

    if (this.encryption && !this.core.manifest) {
      const req = this.replicator.addUpgrade(activeRequests)
      try {
        await req.promise
      } catch (err) {
        if (isSessionMoved(err)) return this.seek(bytes, opts)
        throw err
      }
    }

    const s = MerkleTree.seek(this.state, bytes, this.padding)

    const offset = await s.update()
    if (offset) return offset

    if (this.closing !== null) {
      throw SESSION_CLOSED('cannot seek on a closed session', this.discoveryKey)
    }

    if (!this._shouldWait(opts, this.wait)) return null

    if (isRequestsDestroyed(activeRequests)) throw REQUEST_CANCELLED()
    const req = this.core.replicator.addSeek(activeRequests, s)

    const timeout = opts && opts.timeout !== undefined ? opts.timeout : this.timeout
    if (timeout) req.context.setTimeout(req, timeout)

    try {
      return await req.promise
    } catch (err) {
      if (isSessionMoved(err)) return this.seek(bytes, opts)
      throw err
    }
  }

  async has(start, end = start + 1) {
    if (this.opened === false) await this.opening
    if (!isValidIndex(start) || !isValidIndex(end)) {
      throw ASSERTION('has range is invalid', this.discoveryKey)
    }

    if (this.state.isDefault()) {
      if (end === start + 1) return this.core.bitfield.get(start)

      const i = this.core.bitfield.firstUnset(start)
      return i === -1 || i >= end
    }

    if (end === start + 1) {
      const rx = this.state.storage.read()
      const block = rx.getBlock(start)
      rx.tryFlush()

      return (await block) !== null
    }

    let count = 0

    const stream = this.state.storage.createBlockStream({ gte: start, lt: end })
    for await (const block of stream) {
      if (block === null) return false
      count++
    }

    return count === end - start
  }

  async get(index, opts) {
    if (this.opened === false) await this.opening
    if (!isValidIndex(index)) throw ASSERTION('block index is invalid', this.discoveryKey)

    if (this.closing !== null) {
      throw SESSION_CLOSED('cannot get on a closed session', this.discoveryKey)
    }

    const encoding =
      (opts && opts.valueEncoding && c.from(opts.valueEncoding)) || this.valueEncoding

    if (this.onseq !== null) this.onseq(index, this)
    if (this._marking) await this.markBlock(index)

    const req = this._get(index, opts)

    let block = await req
    if (!block) return null

    if (opts && opts.raw) return block

    if (this.encryption && (!opts || opts.decrypt !== false)) {
      // Copy the block as it might be shared with other sessions.
      block = b4a.from(block)

      await this.encryption.decrypt(index, block, this.core)
    }

    return this._decode(encoding, block, index)
  }

  async clear(start, end = start + 1, opts) {
    if (this.opened === false) await this.opening
    if (this.closing !== null) {
      throw SESSION_CLOSED('cannot clear on a closed session', this.discoveryKey)
    }

    if (typeof end === 'object') {
      opts = end
      end = start + 1
    }

    if (!isValidIndex(start) || !isValidIndex(end)) {
      throw ASSERTION('clear range is invalid', this.discoveryKey)
    }

    const cleared = opts && opts.diff ? { blocks: 0 } : null

    if (start >= end) return cleared
    if (start >= this.length) return cleared

    await this.state.clear(start, end, cleared)

    return cleared
  }

  async purge() {
    await this._closeAllSessions(null)
    await this.core.purge()
  }

  async _get(index, opts) {
    const block = await readBlock(this.state.storage.read(), index)

    if (block !== null) return block

    if (this.closing !== null) {
      throw SESSION_CLOSED('cannot get on a closed session', this.discoveryKey)
    }

    // snapshot should check if core has block
    if (this._snapshot !== null) {
      checkSnapshot(this, index)
      const coreBlock = await readBlock(this.core.state.storage.read(), index)

      checkSnapshot(this, index)
      if (coreBlock !== null) return coreBlock
    }

    // lets check the bitfield to see if we got it during the above async calls
    // this is the last resort before replication, so always safe.
    if (this.core.bitfield.get(index)) {
      const coreBlock = await readBlock(this.state.storage.read(), index)
      // TODO: this should not be needed, only needed atm in case we are doing a moveTo during this (we should fix)
      if (coreBlock !== null) return coreBlock
    }

    if (!this._shouldWait(opts, this.wait)) return null

    this.waits++
    if (opts && opts.onwait) opts.onwait(index, this)
    if (this.onwait) this.onwait(index, this)

    const activeRequests = (opts && opts.activeRequests) || this.activeRequests
    if (isRequestsDestroyed(activeRequests)) throw REQUEST_CANCELLED()

    const force = opts ? opts.force === true : false
    const req = this.core.replicator.addBlock(activeRequests, index, force)
    req.snapshot = index < this.length

    const timeout = opts && opts.timeout !== undefined ? opts.timeout : this.timeout
    if (timeout) req.context.setTimeout(req, timeout)

    let replicatedBlock = null

    try {
      replicatedBlock = await req.promise
    } catch (err) {
      if (isSessionMoved(err)) return this._get(index, opts)
      throw err
    }

    if (this._snapshot !== null) checkSnapshot(this, index)
    return maybeUnslab(replicatedBlock)
  }

  _shouldWait(opts, defaultValue) {
    if (opts) {
      if (opts.wait === false) return false
      if (opts.wait === true) return true
    }
    return defaultValue
  }

  _setupMarks() {
    if (this._marks === null) {
      const storage = this.snapshotted ? this.core.state.storage : this.state.storage
      this._marks = new MarkBitfield(storage)
    }
  }

  async markBlock(start, end = start + 1) {
    if (this.opened === false) await this.opening

    this._setupMarks()

    // TODO support as single rocks batch
    const setPromises = []
    for (let i = start; i < end; i++) {
      setPromises.push(this._marks.set(i, true))
    }

    return Promise.all(setPromises)
  }

  async clearMarkings() {
    if (this.opened === false) await this.opening

    this._setupMarks()

    await this._marks.clear()
    this._marks = null
  }

  async startMarking() {
    if (this._marking) {
      throw ASSERTION("Hypercore cannot be gc'ed when already in gc mode", this.discoveryKey)
    }
    if (this.state && this.state.name) {
      throw ASSERTION("Hypercore cannot be gc'ed when a named session", this.discoveryKey)
    }
    if (this.state && this.state.storage.atom) {
      throw ASSERTION("Hypercore cannot be gc'ed when an atomic session", this.discoveryKey)
    }
    if (this.opened === false) await this.opening
    await this.clearMarkings()

    this._marking = true
  }

  async sweep({ batchSize = 1000 } = {}) {
    if (this.opened === false) await this.opening

    assert(!this.snapshotted, 'Cannot sweep a snapshot')

    // No marks - load from storage
    this._setupMarks()

    let clearing = []
    let prevIndex = this.length
    for await (const index of this._marks.createMarkStream({ reverse: true })) {
      if (index + 1 === prevIndex) {
        prevIndex = index
        continue
      }
      clearing.push(this.clear(index + 1, prevIndex))
      if (clearing.length >= batchSize) {
        await Promise.all(clearing)
        clearing = []
      }
      prevIndex = index
    }
    // Clear range from the very start if not marked
    if (prevIndex > 0) clearing.push(this.clear(0, prevIndex))
    await Promise.all(clearing)

    this._marking = false
    await this.clearMarkings()
  }

  createReadStream(opts) {
    return new ReadStream(this, opts)
  }

  createWriteStream() {
    return new WriteStream(this)
  }

  createByteStream(opts) {
    return new ByteStream(this, opts)
  }

  download(range) {
    return new Download(this, range)
  }

  // TODO: get rid of this / deprecate it?
  undownload(range) {
    range.destroy(null)
  }

  // TODO: get rid of this / deprecate it?
  cancel(request) {
    // Do nothing for now
  }

  async truncate(newLength = 0, opts = {}) {
    if (this.opened === false) await this.opening
    if (this.closing) throw SESSION_CLOSED('Cannot append to a closed session', this.discoveryKey)

    const {
      fork = this.state.fork + 1,
      keyPair = this.keyPair,
      signature = null
    } = typeof opts === 'number' ? { fork: opts } : opts

    const isDefault = this.state === this.core.state
    const writable = !this._readonly && !!(signature || (keyPair && keyPair.secretKey))
    if (isDefault && writable === false && (newLength > 0 || fork !== this.state.fork)) {
      throw SESSION_NOT_WRITABLE('cannot append to a non-writable core', this.discoveryKey)
    }

    await this.state.truncate(newLength, fork, { keyPair, signature })

    // TODO: Should propagate from an event triggered by the oplog
    if (this.state === this.core.state) this.core.replicator.updateAll()
  }

  async append(blocks, opts = {}) {
    if (this.opened === false) await this.opening
    if (this.closing) throw SESSION_CLOSED('Cannot append to a closed session', this.discoveryKey)

    const isDefault = this.state === this.core.state
    const defaultKeyPair = this.state.name === null ? this.keyPair : null

    const { keyPair = defaultKeyPair, signature = null, maxLength, postappend = null } = opts
    const writable =
      !isDefault || !!signature || !!(keyPair && keyPair.secretKey) || opts.writable === true

    if (this._readonly || writable === false) {
      throw SESSION_NOT_WRITABLE('cannot append to a readonly core', this.discoveryKey)
    }

    blocks = Array.isArray(blocks) ? blocks : [blocks]

    const preappend = this.encryption && this._preappend

    const buffers = this.encodeBatch !== null ? this.encodeBatch(blocks) : new Array(blocks.length)

    if (this.encodeBatch === null) {
      for (let i = 0; i < blocks.length; i++) {
        buffers[i] = this._encode(this.valueEncoding, blocks[i])
      }
    }
    for (const b of buffers) {
      if (b.byteLength > MAX_SUGGESTED_BLOCK_SIZE) {
        throw BAD_ARGUMENT(
          'Appended block exceeds the maximum suggested block size',
          this.discoveryKey
        )
      }
    }

    return this.state.append(buffers, { keyPair, signature, preappend, postappend, maxLength })
  }

  async signable(length = -1, fork = -1) {
    if (this.opened === false) await this.opening
    if (length === -1) length = this.length
    if (fork === -1) fork = this.fork

    return caps.treeSignable(this.key, await this.treeHash(length), length, fork)
  }

  async treeHash(length = -1) {
    if (this.opened === false) await this.opening
    if (length === -1) length = this.length
    if (length > 0 && !(await this.has(length - 1))) await this.get(length - 1)

    const roots = await MerkleTree.getRoots(this.state, length)
    return crypto.tree(roots)
  }

  async missingNodes(index) {
    if (this.opened === false) await this.opening
    return await MerkleTree.missingNodes(this.core.state, 2 * index, this.core.state.length)
  }

  async proof(opts) {
    if (this.opened === false) await this.opening
    const rx = this.state.storage.read()
    const proofPromise = MerkleTree.proof(this.state, rx, opts)
    const blockPromise = opts && opts.block ? rx.getBlock(opts.block.index) : null
    rx.tryFlush()
    const [proof, block] = await Promise.all([proofPromise, blockPromise])
    const settled = await proof.settle()
    if (block) settled.block.value = block
    return settled
  }

  async applyProof(proof, from) {
    if (this.opened === false) await this.opening
    return this.core.verify(proof, from)
  }

  async verifyFullyRemote(proof) {
    if (this.opened === false) await this.opening
    const batch = await MerkleTree.verifyFullyRemote(this.state, proof)
    await this.core._verifyBatchUpgrade(batch, proof.manifest)
    return batch
  }

  generateRemoteProofForTreeNode(treeNodeIndex) {
    const blockProofIndex = flat.rightSpan(treeNodeIndex) / 2
    return proof(this, {
      index: blockProofIndex,
      // + 1 to length so the block is included
      upgrade: { start: 0, length: blockProofIndex + 1 }
    })
  }

  async recoverFromRemoteProof(remoteProof) {
    this.core.replicator.setPushOnly(true)
    this.core._repairMode = true

    await this.core.state.mutex.lock()

    try {
      const p = await verify(this.core.db, remoteProof)
      if (!p) return false

      const tx = this.core.storage.write()
      for (const node of p.proof.upgrade.nodes) {
        tx.putTreeNode(node)
      }
      await tx.flush()

      const succeed = p.proof.upgrade.nodes.length !== 0
      if (succeed) {
        this.core.replicator.setPushOnly(false)
      }
      return succeed
    } finally {
      this.core.state.mutex.unlock()
    }
  }

  recoverTreeNodeFromPeers() {
    this.core.replicator.setPushOnly(true)

    for (const peer of this.core.replicator.peers) {
      const req = {
        id: 0,
        fork: this.fork,
        upgrade: {
          start: 0,
          length: this.length
        }
      }
      peer.wireRequest.send(req)
    }
  }

  registerExtension(name, handlers = {}) {
    if (this.extensions.has(name)) {
      const ext = this.extensions.get(name)
      ext.handlers = handlers
      ext.encoding = c.from(handlers.encoding || c.buffer)
      ext.session = this
      return ext
    }

    const ext = {
      name,
      handlers,
      encoding: c.from(handlers.encoding || c.buffer),
      session: this,
      send(message, peer) {
        const buffer = c.encode(this.encoding, message)
        peer.extension(name, buffer)
      },
      broadcast(message) {
        const buffer = c.encode(this.encoding, message)
        for (const peer of this.session.peers) {
          peer.extension(name, buffer)
        }
      },
      destroy() {
        for (const peer of this.session.peers) {
          if (peer.extensions.get(name) === ext) peer.extensions.delete(name)
        }
        this.session.extensions.delete(name)
      },
      _onmessage(state, peer) {
        const m = this.encoding.decode(state)
        if (this.handlers.onmessage) this.handlers.onmessage(m, peer)
      }
    }

    this.extensions.set(name, ext)

    if (this.core === null) this._monitorIndex = -2
    else this.core.addMonitor(this)

    for (const peer of this.peers) {
      peer.extensions.set(name, ext)
    }

    return ext
  }

  _encode(enc, val) {
    const state = { start: this.padding, end: this.padding, buffer: null }

    if (b4a.isBuffer(val)) {
      if (state.start === 0) return val
      state.end += val.byteLength
    } else if (enc) {
      enc.preencode(state, val)
    } else {
      val = b4a.from(val)
      if (state.start === 0) return val
      state.end += val.byteLength
    }

    state.buffer = b4a.allocUnsafe(state.end)

    if (enc) enc.encode(state, val)
    else state.buffer.set(val, state.start)

    return state.buffer
  }

  _decode(enc, block, index) {
    if (this.encryption) block = block.subarray(this.encryption.padding(this.core, index))
    try {
      if (enc) return c.decode(enc, block)
    } catch (err) {
      throw DECODING_ERROR(err.message, this.discoveryKey)
    }
    return block
  }

  _getEncryptionProvider(e) {
    if (isEncryptionProvider(e)) return e
    if (!e || !e.key) return null
    return new DefaultEncryption(e.key, this.key, { block: e.block, compat: this.core.compat })
  }
}

module.exports = Hypercore

function isStream(s) {
  return typeof s === 'object' && s && typeof s.pipe === 'function'
}

async function preappend(blocks) {
  const offset = this.state.length
  const fork = this.state.encryptionFork

  for (let i = 0; i < blocks.length; i++) {
    await this.encryption.encrypt(offset + i, blocks[i], fork, this.core)
  }
}

function isValidIndex(index) {
  return index === 0 || index > 0
}

function maybeUnslab(block) {
  // Unslab only when it takes up less then half the slab
  return block !== null && 2 * block.byteLength < block.buffer.byteLength ? unslab(block) : block
}

function checkSnapshot(snapshot, index) {
  if (index >= snapshot.state.snapshotCompatLength) {
    throw SNAPSHOT_NOT_AVAILABLE(
      `snapshot at index ${index} not available (max compat length ${snapshot.state.snapshotCompatLength})`,
      snapshot.discoveryKey
    )
  }
}

function readBlock(rx, index) {
  const promise = rx.getBlock(index)
  rx.tryFlush()
  return promise
}

function initOnce(session, storage, key, opts) {
  if (storage === null) storage = opts.storage || null
  if (key === null) key = opts.key || null

  session.core = new Core(Hypercore.defaultStorage(storage), {
    preopen: opts.preopen,
    eagerUpgrade: opts.eagerUpgrade !== false,
    notDownloadingLinger: opts.notDownloadingLinger,
    allowFork: opts.allowFork !== false,
    allowPush: !!opts.allowPush,
    pushOnly: !!opts.pushOnly,
    alwaysLatestBlock: !!opts.allowLatestBlock,
    inflightRange: opts.inflightRange,
    compat: opts.compat === true,
    force: opts.force,
    createIfMissing: opts.createIfMissing,
    discoveryKey: opts.discoveryKey,
    overwrite: opts.overwrite,
    key,
    keyPair: opts.keyPair,
    legacy: opts.legacy,
    manifest: opts.manifest,
    group: opts.group,
    globalCache: opts.globalCache || null // session is a temp option, not to be relied on unless you know what you are doing (no semver guarantees)
  })
}

function maybeAddMonitor(name) {
  if (name === 'append' || name === 'truncate') return
  if (this._monitorIndex >= 0 || this.closing) return

  if (this.core === null) {
    this._monitorIndex = -2
  } else {
    this.core.addMonitor(this)
  }
}

function isSessionMoved(err) {
  return err.code === 'SESSION_MOVED'
}

function getEncryptionOption(opts) {
  // old style, supported for now but will go away
  if (opts.encryptionKey) return { key: opts.encryptionKey, block: !!opts.isBlockKey }
  if (!opts.encryption) return null
  return b4a.isBuffer(opts.encryption) ? { key: opts.encryption } : opts.encryption
}

function isEncryptionProvider(e) {
  return e && isFunction(e.padding) && isFunction(e.encrypt) && isFunction(e.decrypt)
}

function isFunction(fn) {
  return !!fn && typeof fn === 'function'
}

function createDiscoveryKeyHandler(fn) {
  return ondiscoverykey
  function ondiscoverykey(id) {
    if (!id || id.byteLength !== 32) throw BAD_ARGUMENT('Invalid discovery key')
    return fn(id)
  }
}

function isRequestsDestroyed(activeRequests) {
  // TODO: move this to an object instead so we can store a property in next major
  return activeRequests.length > 0 && activeRequests[0] === null
}
const crypto = require('hypercore-crypto')
const flat = require('flat-tree')
const b4a = require('b4a')
const { MerkleTree } = require('./merkle-tree')

module.exports = async function auditCore(
  core,
  { tree = true, blocks = true, bitfield = true, dryRun = false } = {}
) {
  const length = core.state.length
  const stats = {
    treeNodes: 0,
    blocks: 0,
    bits: 0,
    droppedTreeNodes: 0,
    droppedBlocks: 0,
    droppedBits: 0,
    corrupt: false
  }

  // audit the tree
  if (tree) {
    let tx = null

    const roots = await MerkleTree.getRootsFromStorage(core.state.storage, length)
    const stack = []

    for (const r of roots) {
      if (r === null) {
        if (!dryRun) {
          const storage = core.state.storage
          await storage.store.deleteCore(storage.core)
          return null
        }

        stats.corrupt = true
      }

      stack.push(r)
    }

    stats.treeNodes += roots.length

    while (stack.length > 0) {
      const node = stack.pop()

      if ((node.index & 1) === 0) continue

      const [left, right] = flat.children(node.index)

      const rx = core.state.storage.read()
      const leftNodePromise = rx.getTreeNode(left)
      const rightNodePromise = rx.getTreeNode(right)

      rx.tryFlush()

      const [leftNode, rightNode] = await Promise.all([leftNodePromise, rightNodePromise])

      if (isBadTree(node, leftNode, rightNode)) {
        if (!tx && !stats.corrupt) tx = core.state.storage.write()
        const [l, r] = flat.spans(node.index)
        tx.deleteTreeNodeRange(l, r + 1)
        stats.droppedTreeNodes++
        continue
      }

      if (!leftNode) continue

      stats.treeNodes += 2
      stack.push(leftNode, rightNode)
    }

    if (tx && !dryRun) await tx.flush()
  }

  // audit the blocks
  if (blocks) {
    let tx = null

    for await (const block of core.state.storage.createBlockStream()) {
      if (!core.bitfield.get(block.index)) {
        if (!tx && !stats.corrupt) tx = core.state.storage.write()
        tx.deleteBlock(block.index)
        stats.droppedBlocks++
      }

      const rx = core.state.storage.read()
      const treeNodePromise = rx.getTreeNode(2 * block.index)

      rx.tryFlush()

      const treeNode = await treeNodePromise

      if (isBadBlock(treeNode, block.value)) {
        if (!tx && !stats.corrupt) tx = core.state.storage.write()
        tx.deleteBlock(block.index)
        stats.droppedBlocks++
        continue
      }

      stats.blocks++
    }

    if (tx && !dryRun) await tx.flush()
  }

  if (bitfield) {
    let tx = null

    for (const index of allBits(core.bitfield)) {
      const rx = core.state.storage.read()
      const blockPromise = rx.getBlock(index)

      rx.tryFlush()

      const block = await blockPromise
      if (!block) {
        stats.droppedBits++
        if (dryRun) continue

        if (!tx && !stats.corrupt) tx = core.state.storage.write()

        core.bitfield.set(index, false)

        const page = core.bitfield.getBitfield(index)
        if (page.bitfield) tx.putBitfieldPage(page.index, page.bitfield)
        else tx.deleteBitfieldPage(page.index)
        continue
      }

      stats.bits++
    }

    if (tx && !dryRun) await tx.flush()
  }

  return stats
}

function isBadBlock(node, block) {
  if (!node) return true
  const hash = crypto.data(block)
  return !b4a.equals(hash, node.hash) || node.size !== block.byteLength
}

function isBadTree(parent, left, right) {
  if (!left && !right) return false
  if (!left || !right) return true
  const hash = crypto.parent(left, right)
  return !b4a.equals(hash, parent.hash) || parent.size !== left.size + right.size
}

function* allBits(bitfield) {
  let i = 0
  if (bitfield.get(0)) yield 0
  while (true) {
    i = bitfield.findFirst(true, i + 1)
    if (i === -1) break
    yield i
  }
}
const b4a = require('b4a')
const quickbit = require('./compat').quickbit

module.exports = class BitInterlude {
  constructor() {
    this.ranges = []
  }

  contiguousLength(from) {
    for (const r of this.ranges) {
      if (r.start > from) break
      if (!r.value && r.start <= from) return r.start
    }

    // TODO: be smarter
    while (this.get(from) === true) from++
    return from
  }

  get(index) {
    let start = 0
    let end = this.ranges.length

    while (start < end) {
      const mid = (start + end) >> 1
      const r = this.ranges[mid]

      if (index < r.start) {
        end = mid
        continue
      }

      if (index >= r.end) {
        if (mid === start) break
        start = mid
        continue
      }

      return r.value
    }

    return false
  }

  setRange(start, end, value) {
    if (start === end) return

    let r = null

    for (let i = 0; i < this.ranges.length; i++) {
      r = this.ranges[i]

      // if already inside, stop
      if (r.start <= start && end <= r.end) {
        if (value === r.value) return

        const ranges = mergeRanges(r, { start, end, value })
        this.ranges.splice(i, 1, ...ranges)

        return
      }

      // we wanna overun the interval
      if (start > r.end) {
        continue
      }

      // we overran but this interval is ending after us, move it back
      if (end >= r.start && end <= r.end) {
        r.start = r.value === value ? start : end
        if (r.value !== value) this.ranges.splice(i, 0, { start, end, value })
        return
      }

      // we overran but our start is contained in this interval, move start back
      if (start >= r.start && start <= r.end) {
        if (r.value !== value) {
          r.end = start // Crop current comparison

          // If there's a next range
          if (this.ranges.length - 1 > i) {
            const next = this.ranges[i + 1]
            // If the same, go next
            if (next.value === value) continue
            // Else we overlap so update next range
            if (next.start < end) next.start = end
          }

          this.ranges.splice(++i, 0, { start, end, value })
          return
        }

        start = r.start
      }

      let remove = 0

      for (let j = i; j < this.ranges.length; j++) {
        const n = this.ranges[j]
        if (n.start > end || n.value !== value) break
        if (n.start <= end && n.end > end) end = n.end
        remove++
      }

      this.ranges.splice(i, remove, { start, end, value })
      return
    }

    if (r !== null) {
      if (start <= r.end && end > r.end) {
        r.end = end
        return
      }

      // we never
      if (r.end > start) return
    }

    this.ranges.push({ start, end, value })
  }

  flush(tx, bitfield) {
    if (!this.ranges.length) return []

    let index = this.ranges[0].start
    const final = this.ranges[this.ranges.length - 1].end

    let i = 0

    while (index < final) {
      const page = bitfield.getBitfield(index) // read only
      const pageIndex = page ? page.index : bitfield.getPageIndex(index)

      const buf = b4a.allocUnsafe(bitfield.getPageByteLength())

      if (page) {
        const src = page.bitfield // Uint32Array
        buf.set(b4a.from(src.buffer, src.byteOffset, src.byteLength), 0)
      } else {
        b4a.fill(buf, 0)
      }

      const last = (pageIndex + 1) * (buf.byteLength << 3)
      const offset = pageIndex * (buf.byteLength << 3)

      let hasValue = false

      while (i < this.ranges.length) {
        const { start, end, value } = this.ranges[i]

        if (!hasValue && value) hasValue = true

        const from = start < index ? index : start
        const to = end < last ? end : last

        quickbit.fill(buf, value, from - offset, to - offset)

        index = to

        if (to === last) break

        i++
      }

      if (page || hasValue) tx.putBitfieldPage(pageIndex, buf)
    }

    return this.ranges
  }
}

function mergeRanges(a, b) {
  const ranges = []
  if (a.start < b.start) ranges.push({ start: a.start, end: b.start, value: a.value })
  ranges.push({ start: b.start, end: b.end, value: b.value })
  if (b.end < a.end) ranges.push({ start: b.end, end: a.end, value: a.value })

  return ranges
}
const BigSparseArray = require('big-sparse-array')
const b4a = require('b4a')
const quickbit = require('./compat').quickbit

const BITS_PER_PAGE = 32768
const BYTES_PER_PAGE = BITS_PER_PAGE / 8
const WORDS_PER_PAGE = BYTES_PER_PAGE / 4
const BITS_PER_SEGMENT = 2097152
const BYTES_PER_SEGMENT = BITS_PER_SEGMENT / 8
const WORDS_PER_SEGMENT = BYTES_PER_SEGMENT / 4
const INITIAL_WORDS_PER_SEGMENT = 1024
const PAGES_PER_SEGMENT = BITS_PER_SEGMENT / BITS_PER_PAGE
const SEGMENT_GROWTH_FACTOR = 4

class BitfieldPage {
  constructor(index, segment) {
    this.index = index
    this.offset = index * BYTES_PER_PAGE - segment.offset
    this.bitfield = null
    this.segment = segment

    segment.add(this)
  }

  get tree() {
    return this.segment.tree
  }

  get(index, dirty) {
    return quickbit.get(this.bitfield, index)
  }

  set(index, val) {
    if (quickbit.set(this.bitfield, index, val)) {
      this.tree.update(this.offset * 8 + index)
    }
  }

  setRange(start, end, val) {
    quickbit.fill(this.bitfield, val, start, end)

    let i = Math.floor(start / 128)
    const n = i + Math.ceil((end - start) / 128)

    while (i <= n) this.tree.update(this.offset * 8 + i++ * 128)
  }

  findFirst(val, position) {
    return quickbit.findFirst(this.bitfield, val, position)
  }

  findLast(val, position) {
    return quickbit.findLast(this.bitfield, val, position)
  }

  count(start, length, val) {
    const end = start + length

    let i = start
    let c = 0

    while (length > 0) {
      const l = this.findFirst(val, i)
      if (l === -1 || l >= end) return c

      const h = this.findFirst(!val, l + 1)
      if (h === -1 || h >= end) return c + end - l

      c += h - l
      length -= h - i
      i = h
    }

    return c
  }
}

class BitfieldSegment {
  constructor(index, bitfield) {
    this.index = index
    this.offset = index * BYTES_PER_SEGMENT
    this.tree = quickbit.Index.from(bitfield, BYTES_PER_SEGMENT)
    this.pages = new Array(PAGES_PER_SEGMENT)
  }

  get bitfield() {
    return this.tree.field
  }

  add(page) {
    const i = page.index - this.index * PAGES_PER_SEGMENT
    this.pages[i] = page

    const start = i * WORDS_PER_PAGE
    const end = start + WORDS_PER_PAGE

    if (end >= this.bitfield.length) this.reallocate(end)

    page.bitfield = this.bitfield.subarray(start, end)
  }

  reallocate(length) {
    let target = this.bitfield.length
    while (target < length) target *= SEGMENT_GROWTH_FACTOR

    const bitfield = new Uint32Array(target)
    bitfield.set(this.bitfield)

    this.tree = quickbit.Index.from(bitfield, BYTES_PER_SEGMENT)

    for (let i = 0; i < this.pages.length; i++) {
      const page = this.pages[i]
      if (!page) continue

      const start = i * WORDS_PER_PAGE
      const end = start + WORDS_PER_PAGE

      page.bitfield = bitfield.subarray(start, end)
    }
  }

  findFirst(val, position) {
    position = this.tree.skipFirst(!val, position)

    let j = position & (BITS_PER_PAGE - 1)
    let i = (position - j) / BITS_PER_PAGE

    if (i >= PAGES_PER_SEGMENT) return -1

    while (i < this.pages.length) {
      const p = this.pages[i]

      let index = -1

      if (p) index = p.findFirst(val, j)
      else if (!val) index = j

      if (index !== -1) return i * BITS_PER_PAGE + index

      j = 0
      i++
    }

    return -1
  }

  findLast(val, position) {
    position = this.tree.skipLast(!val, position)

    let j = position & (BITS_PER_PAGE - 1)
    let i = (position - j) / BITS_PER_PAGE

    if (i >= PAGES_PER_SEGMENT) return -1

    while (i >= 0) {
      const p = this.pages[i]

      let index = -1

      if (p) index = p.findLast(val, j)
      else if (!val) index = j

      if (index !== -1) return i * BITS_PER_PAGE + index

      j = BITS_PER_PAGE - 1
      i--
    }

    return -1
  }
}

module.exports = class Bitfield {
  static BITS_PER_PAGE = BITS_PER_PAGE
  static BYTES_PER_PAGE = BYTES_PER_PAGE

  constructor(buffer) {
    this.resumed = !!(buffer && buffer.byteLength >= 0)

    this._pages = new BigSparseArray()
    this._segments = new BigSparseArray()

    const view = this.resumed
      ? new Uint32Array(buffer.buffer, buffer.byteOffset, Math.floor(buffer.byteLength / 4))
      : new Uint32Array(INITIAL_WORDS_PER_SEGMENT)

    for (let i = 0; i < view.length; i += WORDS_PER_SEGMENT) {
      let bitfield = view.subarray(i, i + WORDS_PER_SEGMENT)
      let length = WORDS_PER_SEGMENT

      if (i === 0) {
        length = INITIAL_WORDS_PER_SEGMENT
        while (length < bitfield.length) length *= SEGMENT_GROWTH_FACTOR
      }

      if (bitfield.length !== length) {
        const copy = new Uint32Array(length)
        copy.set(bitfield, 0)
        bitfield = copy
      }

      const segment = new BitfieldSegment(i / WORDS_PER_SEGMENT, bitfield)
      this._segments.set(segment.index, segment)

      for (let j = 0; j < bitfield.length; j += WORDS_PER_PAGE) {
        const page = new BitfieldPage((i + j) / WORDS_PER_PAGE, segment)
        this._pages.set(page.index, page)
      }
    }
  }

  static from(bitfield) {
    return new Bitfield(bitfield.toBuffer(bitfield._pages.maxLength * BITS_PER_PAGE))
  }

  toBuffer(length) {
    const pages = Math.ceil(length / BITS_PER_PAGE)
    const buffer = b4a.allocUnsafe(pages * BYTES_PER_PAGE)

    for (let i = 0; i < pages; i++) {
      const page = this._pages.get(i)
      const offset = i * BYTES_PER_PAGE

      if (page) {
        const buf = b4a.from(
          page.bitfield.buffer,
          page.bitfield.byteOffset,
          page.bitfield.byteLength
        )

        buffer.set(buf, offset)
      } else {
        buffer.fill(0, offset, offset + BYTES_PER_PAGE)
      }
    }

    return buffer
  }

  getBitfield(index) {
    const i = this.getPageIndex(index)

    const p = this._pages.get(i)
    return p || null
  }

  merge(bitfield, length) {
    let i = 0

    while (i < length) {
      const start = bitfield.firstSet(i)
      if (start === -1) break

      i = bitfield.firstUnset(start)

      if (i === -1 || i > length) i = length

      this.setRange(start, i, true)

      if (i >= length) break
    }
  }

  get(index) {
    const j = index & (BITS_PER_PAGE - 1)
    const i = (index - j) / BITS_PER_PAGE

    const p = this._pages.get(i)

    return p ? p.get(j) : false
  }

  getPageByteLength() {
    return BYTES_PER_PAGE
  }

  getPageIndex(index) {
    const j = index & (BITS_PER_PAGE - 1)
    return (index - j) / BITS_PER_PAGE
  }

  getPage(index, create) {
    const i = this.getPageIndex(index)

    let p = this._pages.get(i)

    if (p) return p

    if (!create) return null

    const k = Math.floor(i / PAGES_PER_SEGMENT)
    const s =
      this._segments.get(k) ||
      this._segments.set(
        k,
        new BitfieldSegment(
          k,
          new Uint32Array(k === 0 ? INITIAL_WORDS_PER_SEGMENT : WORDS_PER_SEGMENT)
        )
      )

    p = this._pages.set(i, new BitfieldPage(i, s))

    return p
  }

  set(index, val) {
    const j = index & (BITS_PER_PAGE - 1)
    const i = (index - j) / BITS_PER_PAGE

    let p = this._pages.get(i)

    if (!p && val) {
      const k = Math.floor(i / PAGES_PER_SEGMENT)
      const s =
        this._segments.get(k) ||
        this._segments.set(
          k,
          new BitfieldSegment(
            k,
            new Uint32Array(k === 0 ? INITIAL_WORDS_PER_SEGMENT : WORDS_PER_SEGMENT)
          )
        )

      p = this._pages.set(i, new BitfieldPage(i, s))
    }

    if (p) p.set(j, val)
  }

  setRange(start, end, val) {
    let j = start & (BITS_PER_PAGE - 1)
    let i = (start - j) / BITS_PER_PAGE

    while (start < end) {
      let p = this._pages.get(i)

      if (!p && val) {
        const k = Math.floor(i / PAGES_PER_SEGMENT)
        const s =
          this._segments.get(k) ||
          this._segments.set(
            k,
            new BitfieldSegment(
              k,
              new Uint32Array(k === 0 ? INITIAL_WORDS_PER_SEGMENT : WORDS_PER_SEGMENT)
            )
          )

        p = this._pages.set(i, new BitfieldPage(i, s))
      }

      const offset = i * BITS_PER_PAGE
      const last = Math.min(end - offset, BITS_PER_PAGE)
      const range = last - j

      if (p) p.setRange(j, last, val)

      j = 0
      i++
      start += range
    }
  }

  findFirst(val, position) {
    let j = position & (BITS_PER_SEGMENT - 1)
    let i = (position - j) / BITS_PER_SEGMENT

    while (i < this._segments.maxLength) {
      const s = this._segments.get(i)

      let index = -1

      if (s) index = s.findFirst(val, j)
      else if (!val) index = j

      if (index !== -1) return i * BITS_PER_SEGMENT + index

      j = 0
      i++
    }

    return val ? -1 : this._segments.maxLength * BITS_PER_SEGMENT
  }

  firstSet(position) {
    return this.findFirst(true, position)
  }

  firstUnset(position) {
    return this.findFirst(false, position)
  }

  findLast(val, position) {
    let j = position & (BITS_PER_SEGMENT - 1)
    let i = (position - j) / BITS_PER_SEGMENT

    while (i >= 0) {
      const s = this._segments.get(i)

      let index = -1

      if (s) index = s.findLast(val, j)
      else if (!val) index = j

      if (index !== -1) return i * BITS_PER_SEGMENT + index

      j = BITS_PER_SEGMENT - 1
      i--
    }

    return -1
  }

  lastSet(position) {
    return this.findLast(true, position)
  }

  lastUnset(position) {
    return this.findLast(false, position)
  }

  hasUnset(start, length) {
    const end = start + length

    let j = start & (BITS_PER_SEGMENT - 1)
    let i = (start - j) / BITS_PER_SEGMENT

    while (i < this._segments.maxLength) {
      const s = this._segments.get(i)
      const index = s ? s.findFirst(false, j) : j
      if (index !== -1) return i * BITS_PER_SEGMENT + index < end

      j = 0
      i++

      if (i * BITS_PER_SEGMENT >= end) return false
    }

    return true
  }

  hasSet(start, length) {
    const end = start + length

    let j = start & (BITS_PER_SEGMENT - 1)
    let i = (start - j) / BITS_PER_SEGMENT

    while (i < this._segments.maxLength) {
      const s = this._segments.get(i)

      let index = -1

      if (s) index = s.findFirst(true, j)

      if (index !== -1) return i * BITS_PER_SEGMENT + index < end

      j = 0
      i++

      if (i * BITS_PER_SEGMENT >= end) return false
    }

    return false
  }

  count(start, length, val) {
    let j = start & (BITS_PER_PAGE - 1)
    let i = (start - j) / BITS_PER_PAGE
    let c = 0

    while (length > 0) {
      const p = this._pages.get(i)

      const end = Math.min(j + length, BITS_PER_PAGE)
      const range = end - j

      if (p) c += p.count(j, range, val)
      else if (!val) c += range

      j = 0
      i++
      length -= range
    }

    return c
  }

  countSet(start, length) {
    return this.count(start, length, true)
  }

  countUnset(start, length) {
    return this.count(start, length, false)
  }

  *want(start, length) {
    if (start & 31) {
      const d = start & 31
      start -= d
      length += d
    }

    let j = start & (BITS_PER_SEGMENT - 1)
    let i = (start - j) / BITS_PER_SEGMENT

    while (length > 0) {
      const s = this._segments.get(i)

      if (s) {
        let end = Math.min(j + length, BITS_PER_SEGMENT)
        if (end & 31) end += 32 - (end & 31)

        yield {
          start: i * BITS_PER_SEGMENT + j,
          bitfield: s.bitfield.subarray(j / 32, end / 32)
        }
      }

      j = 0
      i++
      length -= BITS_PER_SEGMENT
    }
  }

  clear(tx) {
    return tx.deleteBitfieldPageRange(0, -1)
  }

  onupdate(ranges) {
    for (const { start, end, value } of ranges) {
      this.setRange(start, end, value)
    }
  }

  static async open(storage, length) {
    if (length === 0) return new Bitfield(storage, null)

    const pages = Math.ceil(length / BITS_PER_PAGE)
    const buffer = b4a.alloc(pages * BYTES_PER_PAGE)
    const stream = storage.createBitfieldStream({ lt: pages })

    for await (const { index, page } of stream) {
      buffer.set(page, index * BYTES_PER_PAGE)
    }

    return new Bitfield(buffer)
  }
}
const crypto = require('hypercore-crypto')
const sodium = require('sodium-universal')
const b4a = require('b4a')
const c = require('compact-encoding')

// TODO: rename this to "crypto" and move everything hashing related etc in here
// Also lets move the tree stuff from hypercore-crypto here

const [
  TREE,
  REPLICATE_INITIATOR,
  REPLICATE_RESPONDER,
  MANIFEST,
  DEFAULT_NAMESPACE,
  DEFAULT_ENCRYPTION
] = crypto.namespace('hypercore', 6)

exports.MANIFEST = MANIFEST
exports.DEFAULT_NAMESPACE = DEFAULT_NAMESPACE
exports.DEFAULT_ENCRYPTION = DEFAULT_ENCRYPTION

exports.replicate = function (isInitiator, key, handshakeHash) {
  const out = b4a.allocUnsafe(32)
  sodium.crypto_generichash_batch(
    out,
    [isInitiator ? REPLICATE_INITIATOR : REPLICATE_RESPONDER, key],
    handshakeHash
  )
  return out
}

exports.treeSignable = function (manifestHash, treeHash, length, fork) {
  const state = { start: 0, end: 112, buffer: b4a.allocUnsafe(112) }
  c.fixed32.encode(state, TREE)
  c.fixed32.encode(state, manifestHash)
  c.fixed32.encode(state, treeHash)
  c.uint64.encode(state, length)
  c.uint64.encode(state, fork)
  return state.buffer
}

exports.treeSignableCompat = function (hash, length, fork, noHeader) {
  const end = noHeader ? 48 : 80
  const state = { start: 0, end, buffer: b4a.allocUnsafe(end) }
  if (!noHeader) c.fixed32.encode(state, TREE) // ultra legacy mode, kill in future major
  c.fixed32.encode(state, hash)
  c.uint64.encode(state, length)
  c.uint64.encode(state, fork)
  return state.buffer
}
// Export the appropriate version of `quickbit-universal` as the plain import
// may resolve to an older version in some environments
let quickbit = require('quickbit-universal')
if (
  typeof quickbit.findFirst !== 'function' ||
  typeof quickbit.findLast !== 'function' ||
  typeof quickbit.clear !== 'function'
) {
  // This should always load the fallback from the locally installed version
  quickbit = require('quickbit-universal/fallback')
}
exports.quickbit = quickbit
const crypto = require('hypercore-crypto')
const flat = require('flat-tree')
const b4a = require('b4a')
const quickbit = require('quickbit-universal')
const Bitfield = require('./bitfield')

const MAX_BATCH_USED = 4 * 1024 * 1024
const MIN_BATCH_USED = 512 * 1024

// just in its own file as its a bit involved

module.exports = copyPrologue

async function copyPrologue(src, dst) {
  const prologue = dst.header.manifest.prologue

  if (src.length < prologue.length || prologue.length === 0) return

  const stack = []
  const roots = flat.fullRoots(prologue.length * 2)
  const batch = { roots, first: true, last: false, contig: 0, used: 0, tree: [], blocks: [] }

  for (let i = 0; i < roots.length; i++) {
    const node = roots[i]
    batch.tree.push(node)
    stack.push(node)
  }

  let lastPage = -1
  let lastBlock = -1

  for await (const data of src.storage.createBlockStream({
    gte: 0,
    lt: prologue.length,
    reverse: true
  })) {
    if (walkTree(stack, data.index * 2, batch) === false) {
      throw new Error('Missing block or tree node for ' + data.index)
    }

    batch.contig = data.index + 1 === lastBlock ? batch.contig + 1 : 1
    lastBlock = data.index

    const page = getBitfieldPage(data.index)
    batch.blocks.push(data)

    if (lastPage !== page) batch.used += 4096
    batch.used += Math.max(data.value.byteLength, 128) // 128 is just a sanity number to avoid mega batches

    // always safe to partially flush so we do that ondemand to reduce memory usage...
    if ((batch.used >= MIN_BATCH_USED && page !== lastPage) || batch.used >= MAX_BATCH_USED) {
      await flushBatch(prologue, src, dst, batch)
    }

    lastPage = page
  }

  if (lastBlock !== 0) batch.contig = 0

  batch.last = true
  await flushBatch(prologue, src, dst, batch)
}

async function flushBatch(prologue, src, dst, batch) {
  const nodePromises = []

  const srcReader = src.storage.read()
  for (const index of batch.tree) {
    nodePromises.push(srcReader.getTreeNode(index))
  }
  srcReader.tryFlush()

  const nodes = await Promise.all(nodePromises)

  const pagePromises = []
  const dstReader = dst.storage.read()

  const headPromise = batch.first ? dstReader.getHead() : null
  if (headPromise) headPromise.catch(noop)

  let lastPage = -1
  for (const { index } of batch.blocks) {
    const page = getBitfieldPage(index)
    if (page === lastPage) continue
    lastPage = page
    pagePromises.push(dstReader.getBitfieldPage(page))
  }

  dstReader.tryFlush()

  const pages = await Promise.all(pagePromises)
  const head = headPromise === null ? null : await headPromise
  const userData = []

  // reads done!

  if (batch.first) {
    const roots = nodes.slice(0, batch.roots.length)

    for (const node of roots) {
      if (!node) throw new Error('Missing nodes for prologue hash')
    }

    const treeHash = crypto.tree(roots)
    if (!b4a.equals(treeHash, prologue.hash)) throw new Error('Prologue does not match source')
  }

  if (batch.first) {
    for await (const data of src.storage.createUserDataStream()) userData.push(data)
  }

  for (let i = 0; i < pages.length; i++) {
    if (!pages[i]) pages[i] = b4a.alloc(4096)
  }

  const tx = dst.storage.write()

  for (const node of nodes) tx.putTreeNode(node)

  lastPage = -1
  let pageIndex = -1

  for (const { index, value } of batch.blocks) {
    const page = getBitfieldPage(index)

    if (page !== lastPage) {
      lastPage = page
      pageIndex++
      // queue the page now, we mutate it below but its the same ref
      tx.putBitfieldPage(pageIndex, pages[pageIndex])
    }

    const pageBuffer = pages[pageIndex]
    quickbit.set(pageBuffer, getBitfieldOffset(index), true)
    tx.putBlock(index, value)
  }

  for (const { key, value } of userData) {
    tx.putUserData(key, value)
  }

  let upgraded = batch.first && !head
  if (upgraded) {
    tx.setHead(prologueToTree(prologue))
  }

  await tx.flush()

  if (upgraded) {
    const roots = nodes.slice(0, batch.roots.length)
    dst.state.setRoots(roots)
    dst.header.tree = prologueToTree(prologue)
  }

  if (userData.length > 0) {
    dst.header.userData = userData.concat(dst.header.userData)
  }

  if (batch.contig) {
    // TODO: we need to persist this somehow
    dst.header.hints.contiguousLength = batch.contig
  }

  let start = 0
  let length = 0

  // update in memory bitfield
  for (const { index } of batch.blocks) {
    if (start === 0 || start - 1 === index) {
      length++
    } else {
      if (length > 0) signalReplicator(dst, upgraded, start, length)
      upgraded = false
      length = 1
    }

    start = index
    dst.bitfield.set(index, true)
  }

  if (length > 0) signalReplicator(dst, upgraded, start, length)

  // unlink
  batch.tree = []
  batch.blocks = []
  batch.first = false
  batch.used = 0
}

function signalReplicator(core, upgraded, start, length) {
  if (upgraded) {
    core.replicator.cork()
    core.replicator.onhave(start, length, false)
    core.replicator.onupgrade()
    core.replicator.uncork()
  } else {
    core.replicator.onhave(start, length, false)
  }
}

function prologueToTree(prologue) {
  return {
    fork: 0,
    length: prologue.length,
    rootHash: prologue.hash,
    signature: null
  }
}

function getBitfieldPage(index) {
  return Math.floor(index / Bitfield.BITS_PER_PAGE)
}

function getBitfieldOffset(index) {
  return index & (Bitfield.BITS_PER_PAGE - 1)
}

function walkTree(stack, target, batch) {
  while (stack.length > 0) {
    const node = stack.pop()

    if ((node & 1) === 0) {
      if (node === target) return true
      continue
    }

    const ite = flat.iterator(node)
    if (!ite.contains(target)) continue

    while ((ite.index & 1) !== 0) {
      const left = ite.leftChild()
      const right = ite.sibling() // is right child

      batch.tree.push(left, right)

      if (ite.contains(target)) stack.push(left)
      else ite.sibling()
    }

    if (ite.index === target) return true
  }

  return false
}

function noop() {}
const crypto = require('hypercore-crypto')
const b4a = require('b4a')
const unslab = require('unslab')
const z32 = require('z32')
const Mutex = require('./mutex')
const { MerkleTree, ReorgBatch } = require('./merkle-tree')
const BitInterlude = require('./bit-interlude')
const Bitfield = require('./bitfield')
const RemoteBitfield = require('./remote-bitfield')
const {
  BAD_ARGUMENT,
  STORAGE_EMPTY,
  STORAGE_CONFLICT,
  INVALID_SIGNATURE,
  INVALID_CHECKSUM
} = require('hypercore-errors')
const Verifier = require('./verifier')
const audit = require('./audit')
const copyPrologue = require('./copy-prologue')
const SessionState = require('./session-state')
const Replicator = require('./replicator')

module.exports = class Core {
  constructor(db, opts = {}) {
    this.db = db
    this.storage = null
    this.replicator = new Replicator(this, opts)
    this.sessionStates = []
    this.monitors = []
    this.activeSessions = 0
    this.gc = 0 // corestore uses this to main a gc strike pool

    this.id = opts.key ? z32.encode(opts.key) : null
    this.key = opts.key || null
    this.discoveryKey = opts.discoveryKey || (opts.key && crypto.discoveryKey(opts.key)) || null
    this.manifest = null
    this.opening = null
    this.closing = null
    this.exclusive = null

    this.preupdate = null
    this.header = null
    this.compat = false
    this.bitfield = null
    this.verifier = null
    this.truncating = 0
    this.skipBitfield = null
    this.globalCache = opts.globalCache || null
    this.autoClose = opts.autoClose !== false
    this.onidle = noop
    this.ongroupupdate = noop

    this.state = null
    this.opened = false
    this.destroyed = false
    this.closed = false

    this.hintsChanged = false
    this.remoteContiguousLengthChanged = false

    this._bitfield = null
    this._verifies = null
    this._verifiesFlushed = null
    this._legacy = !!opts.legacy
    this._repairMode = false

    this.opening = this._open(opts)
    this.opening.catch(noop)
  }

  static setRecoveryPeers(peers) {
    SessionState.RECOVERY_PEERS = peers
  }

  ready() {
    return this.opening
  }

  addMonitor(s) {
    if (s._monitorIndex >= 0) return
    s._monitorIndex = this.monitors.push(s) - 1
  }

  removeMonitor(s) {
    if (s._monitorIndex < 0) return
    const head = this.monitors.pop()
    if (head !== s) this.monitors[(head._monitorIndex = s._monitorIndex)] = head
    s._monitorIndex = -1
  }

  emitRemoteContiguousLength() {
    if (!this.remoteContiguousLengthChanged) return
    this.remoteContiguousLengthChanged = false

    const length = this.header.hints.remoteContiguousLength

    for (let i = this.monitors.length - 1; i >= 0; i--) {
      this.monitors[i].emit('remote-contiguous-length', length)
    }
  }

  emitManifest() {
    for (let i = this.monitors.length - 1; i >= 0; i--) {
      this.monitors[i].emit('manifest')
    }
  }

  createUserDataStream(opts, session = this.state) {
    return session.storage.createUserDataStream(opts)
  }

  allSessions() {
    const sessions = []
    for (const state of this.sessionStates) {
      if (state.sessions.length) sessions.push(...state.sessions)
    }
    return sessions
  }

  hasSession() {
    return this.activeSessions !== 0
  }

  compact() {
    const compacting = []
    for (const s of this.sessionStates) {
      compacting.push(s.storage.compact())
    }
    return Promise.all(compacting)
  }

  checkIfIdle() {
    if (!this.opened || this.destroyed === true || this.hasSession() === true) return
    if (this.replicator.idle() === false) return
    if (this.state === null || this.state.mutex.idle() === false) return
    this.onidle()
  }

  async lockExclusive() {
    if (this.exclusive === null) this.exclusive = new Mutex()
    await this.exclusive.lock()
  }

  unlockExclusive() {
    if (this.exclusive !== null) this.exclusive.unlock()
  }

  async _open(opts) {
    try {
      await this._tryOpen(opts)
    } catch (err) {
      this.onidle()
      throw err
    }

    this.opened = true
  }

  async _tryOpen(opts) {
    if (opts.preopen) await opts.preopen // just a hook to allow exclusive access here...

    let storage = await this.db.resumeCore(this.discoveryKey)

    let overwrite = opts.overwrite === true

    const force = opts.force === true
    const createIfMissing = opts.createIfMissing !== false
    // kill this flag soon
    const legacy = !!opts.legacy

    // default to true for now if no manifest is provided
    let compat = opts.compat === true || (opts.compat !== false && !opts.manifest)

    let header = storage ? parseHeader(await getCoreInfo(storage)) : null

    const hadManifest = !!header && !!header.manifest && !!header.key
    const hadKeyPair = !!header && !!header.keyPair

    if (force && opts.key && header && !b4a.equals(header.key, opts.key)) {
      overwrite = true
    }

    if (!header && opts.discoveryKey && !(opts.key || opts.manifest)) {
      throw STORAGE_EMPTY('No Hypercore is stored here', this.discoveryKey)
    }

    if (!header || overwrite) {
      if (!createIfMissing) {
        throw STORAGE_EMPTY('No Hypercore is stored here', this.discoveryKey)
      }

      if (compat) {
        if (opts.key && opts.keyPair && !b4a.equals(opts.key, opts.keyPair.publicKey)) {
          throw BAD_ARGUMENT('Key must match publicKey when in compat mode', this.discoveryKey)
        }
      }

      const keyPair = opts.keyPair || (opts.key ? null : crypto.keyPair())

      const defaultManifest =
        !opts.manifest &&
        (!!opts.compat || !opts.key || !!(keyPair && b4a.equals(opts.key, keyPair.publicKey)))
      const manifest = defaultManifest
        ? Verifier.defaultSignerManifest(opts.key || keyPair.publicKey)
        : Verifier.createManifest(opts.manifest)

      header = {
        key: opts.key || (compat ? manifest.signers[0].publicKey : Verifier.manifestHash(manifest)),
        manifest,
        keyPair: keyPair
          ? { publicKey: keyPair.publicKey, secretKey: keyPair.secretKey || null }
          : null,
        frozen: false,
        tree: {
          fork: 0,
          length: 0,
          rootHash: null,
          signature: null,
          timestamp: 0
        },
        group: null,
        hints: {
          reorgs: [],
          contiguousLength: 0,
          remoteContiguousLength: 0,
          recovering: 0
        }
      }

      const discoveryKey = opts.discoveryKey || crypto.discoveryKey(header.key)

      storage = await this.db.createCore({
        key: header.key,
        manifest,
        keyPair,
        frozen: false,
        discoveryKey,
        userData: opts.userData || [],
        alias: opts.alias || null
      })
    }

    // unslab the long lived buffers to avoid keeping the slab alive
    header.key = unslab(header.key)

    if (header.tree) {
      header.tree.rootHash = unslab(header.tree.rootHash)
      header.tree.signature = unslab(header.tree.signature)
    }

    if (header.keyPair) {
      header.keyPair.publicKey = unslab(header.keyPair.publicKey)
      header.keyPair.secretKey = unslab(header.keyPair.secretKey)
    }

    if (header.keyPair) {
      header.keyPair.publicKey = unslab(header.keyPair.publicKey)
      header.keyPair.secretKey = unslab(header.keyPair.secretKey)
    }

    if (opts.manifest) {
      const manifest = Verifier.createManifest(opts.manifest)

      // if we provide a manifest and no key, verify that the stored key is the same
      if (!opts.key && !Verifier.isValidManifest(header.key, manifest)) {
        throw STORAGE_CONFLICT('Manifest does not hash to provided key', this.discoveryKey)
      }

      if (!header.manifest) header.manifest = manifest
    }

    if (opts.key && !b4a.equals(header.key, opts.key)) {
      throw STORAGE_CONFLICT('Another Hypercore is stored here', this.discoveryKey)
    }

    // if we signalled compat, but already now this core isn't disable it
    if (compat && header.manifest && !Verifier.isCompat(header.key, header.manifest)) {
      compat = false
    } else if (!compat && header.manifest && Verifier.isCompat(header.key, header.manifest)) {
      compat = true
    }

    const prologue = header.manifest ? header.manifest.prologue : null

    const bitfield = await Bitfield.open(storage, header.tree.length)

    const treeInfo = {
      fork: header.tree.fork,
      length: header.tree.length,
      signature: header.tree.signature,
      roots: await maybeGetRootsFromStorage(storage, header.tree.length),
      prologue
    }

    this._repairMode = !treeInfo.roots.length && treeInfo.length && !overwrite
    // Set push only mode if in repair mode
    if (this._repairMode) {
      this.replicator.setPushOnly(true)
    }

    if (opts.group) {
      if (!header.group) header.group = await this.db.createGroup(opts.group)
      else if (!b4a.equals(header.group.key, opts.group)) {
        throw STORAGE_CONFLICT(
          'Core is already register to group: ' + b4a.toString(header.group.key, 'hex')
        )
      }

      const tx = storage.write()
      tx.setGroup(header.group)
      await tx.flush()
    }

    if (overwrite) {
      const tx = storage.write()
      tx.deleteTreeNodeRange(0, -1)
      tx.deleteBlockRange(0, -1)
      bitfield.clear(tx)
      await tx.flush()
    }

    const len = bitfield.findFirst(false, header.hints.contiguousLength)
    if (header.hints.contiguousLength !== len && !storage.readOnly) {
      header.hints.contiguousLength = len
      const tx = storage.write()
      tx.setHints(header.hints)
      await tx.flush()
    }

    // to unslab
    if (header.manifest) {
      header.manifest = Verifier.createManifest(header.manifest)
      if (!hadManifest || (header.keyPair && !hadKeyPair)) {
        const tx = storage.write()
        tx.setAuth({
          key: header.key,
          discoveryKey: crypto.discoveryKey(header.key),
          manifest: header.manifest,
          keyPair: header.keyPair
        })
        await tx.flush()
      }
    }

    const verifier = header.manifest
      ? new Verifier(header.key, header.manifest, { crypto, legacy })
      : null

    this.storage = storage
    this.header = header
    this.compat = compat
    this.bitfield = bitfield
    this.verifier = verifier
    this.state = new SessionState(this, null, storage, treeInfo, null)

    if (this.key === null) this.key = this.header.key
    if (this.discoveryKey === null) this.discoveryKey = crypto.discoveryKey(this.key)
    if (this.id === null) this.id = z32.encode(this.key)
    if (this.manifest === null) this.manifest = this.header.manifest
  }

  async audit(opts) {
    await this.state.mutex.lock()

    try {
      return await audit(this, opts)
    } finally {
      this.state._unlock()
    }
  }

  async setGroup(group) {
    await this.state.mutex.lock()
    try {
      if (!this.header.group) this.header.group = await this.db.createGroup(group)
      else if (!b4a.equals(this.header.group.key, group)) {
        throw STORAGE_CONFLICT(
          'Core is already register to group: ' + b4a.toString(this.header.group.key, 'hex')
        )
      }

      const tx = this.storage.write()
      tx.setGroup(this.header.group)
      await tx.flush()
    } finally {
      this.state.mutex.unlock()
    }
  }

  async setManifest(manifest) {
    await this.state.mutex.lock()

    try {
      if (manifest && this.header.manifest === null) {
        manifest = Verifier.createManifest(manifest)

        if (!Verifier.isValidManifest(this.header.key, manifest)) {
          throw INVALID_CHECKSUM('Manifest hash does not match', this.discoveryKey)
        }

        const tx = this.state.createWriteBatch()
        this._setManifest(tx, manifest, null)
        await this.state.flush()
      }
    } finally {
      this.state._unlock()
    }
  }

  _setManifest(tx, manifest, keyPair) {
    if (!manifest && b4a.equals(keyPair.publicKey, this.header.key)) {
      manifest = Verifier.defaultSignerManifest(this.header.key)
    }
    if (!manifest) return

    const verifier = new Verifier(this.header.key, manifest, { legacy: this._legacy })

    if (verifier.prologue) this.state.prologue = Object.assign({}, verifier.prologue)

    this.manifest = this.header.manifest = manifest

    tx.setAuth({
      key: this.header.key,
      discoveryKey: this.discoveryKey,
      manifest,
      keyPair: this.header.keyPair
      // TODO: encryptionKey?
    })

    this.compat = verifier.compat
    this.verifier = verifier

    this.replicator.onupgrade()
    this.emitManifest()
  }

  async copyPrologue(src) {
    await this.state.mutex.lock()

    try {
      await src.mutex.lock()
    } catch (err) {
      this.state.mutex.unlock()
      throw err
    }

    try {
      await copyPrologue(src, this)
    } finally {
      src.mutex.unlock()
      this.state.mutex.unlock()
      this.checkIfIdle()
    }
  }

  flushed() {
    return this.state.flushed()
  }

  async _validateCommit(state, treeLength) {
    if (this.state.length > state.length) {
      return false // TODO: partial commit and truncation possible in the future
    }

    if (this.state.length > treeLength) {
      for (const root of this.state.roots) {
        const batchRoot = await MerkleTree.get(state, root.index)
        if (batchRoot.size !== root.size || !b4a.equals(batchRoot.hash, root.hash)) {
          return false
        }
      }
    }

    if (this.verifier === null) {
      return false // easier to assert than upsert
    }

    return true
  }

  _verifyBatchUpgrade(batch, manifest) {
    if (!this.header.manifest) {
      // compat, drop at some point
      if (!manifest) manifest = Verifier.defaultSignerManifest(this.header.key)

      if (
        !manifest ||
        !(
          Verifier.isValidManifest(this.header.key, manifest) ||
          Verifier.isCompat(this.header.key, manifest)
        )
      ) {
        throw INVALID_SIGNATURE('Proof contains an invalid manifest', this.discoveryKey) // TODO: proper error type
      }
    }

    const verifier =
      this.verifier ||
      new Verifier(this.header.key, Verifier.createManifest(manifest), { legacy: this._legacy })

    if (!verifier.verify(batch, batch.signature)) {
      throw INVALID_SIGNATURE('Proof contains an invalid signature', this.discoveryKey)
    }

    return manifest
  }

  async _verifyExclusive({ batch, bitfield, value, manifest }) {
    manifest = this._verifyBatchUpgrade(batch, manifest)

    if (
      !(await this.state._verifyBlock(
        batch,
        bitfield,
        value,
        this.header.manifest ? null : manifest
      ))
    ) {
      return false
    }

    if (!batch.upgraded && bitfield) {
      this.replicator.onhave(bitfield.start, bitfield.length, bitfield.drop)
    }

    return true
  }

  async _verifyShared() {
    if (!this._verifies.length) return false

    await this.state.mutex.lock()

    const tx = this.state.createWriteBatch()

    const verifies = this._verifies
    this._verifies = null
    this._verified = null

    try {
      for (const { batch, bitfield, value } of verifies) {
        if (!batch.commitable()) continue

        if (bitfield) {
          tx.putBlock(bitfield.start, value)
        }
      }

      const bits = new BitInterlude()

      for (let i = 0; i < verifies.length; i++) {
        const { batch, bitfield, manifest } = verifies[i]

        if (!batch.commitable()) {
          verifies[i] = null // signal that we cannot commit this one
          continue
        }

        if (bitfield) {
          bits.setRange(bitfield.start, bitfield.start + 1, true)
        }

        // if we got a manifest AND its strictly a non compat one, lets store it
        if (manifest && this.header.manifest === null) {
          if (!Verifier.isValidManifest(this.header.key, manifest)) {
            throw INVALID_CHECKSUM('Manifest hash does not match', this.discoveryKey)
          }
          this._setManifest(tx, manifest, null)
        }

        if (batch.commitable()) batch.commit(tx)
      }

      const ranges = bits.flush(tx, this.bitfield)

      await this.state.flush()

      for (const { start, end, value } of ranges) {
        this._setBitfieldRanges(start, end, value)
      }

      for (let i = 0; i < verifies.length; i++) {
        const bitfield = verifies[i] && verifies[i].bitfield
        if (bitfield) {
          this.updateContiguousLength(bitfield)
          this.replicator.onhave(bitfield.start, bitfield.length, bitfield.drop)
        }
      }

      if (this.hintsChanged) await this.flushHints()
    } finally {
      this.state._clearActiveBatch()
      this.state.mutex.unlock()
    }

    return verifies[0] !== null
  }

  async flushHints() {
    if (!this.hintsChanged) return
    this.hintsChanged = false // we unset this immediately as a "debounce"

    const tx = this.state.storage.write()

    tx.setHints({
      contiguousLength: this.header.hints.contiguousLength,
      remoteContiguousLength: this.header.hints.remoteContiguousLength,
      recovering: this.header.hints.recovering
    })

    await tx.flush()
  }

  async checkConflict(proof, from) {
    if (this.state.length < proof.upgrade.length || proof.fork !== this.state.fork) {
      // out of date this proof - ignore for now
      return false
    }

    // sanity check -> no manifest, no way to verify
    if (!this.header.manifest) {
      return false
    }

    const batch = MerkleTree.verifyFullyRemote(this.state, proof)

    try {
      this._verifyBatchUpgrade(batch, proof.manifest)
    } catch {
      return true
    }

    const roots = await maybeGetRootsFromStorage(this.storage, proof.upgrade.length)
    const remoteTreeHash = crypto.tree(proof.upgrade.nodes)
    const localTreeHash = crypto.tree(roots)

    try {
      const rx = this.state.storage.read()
      const treeProofPromise = MerkleTree.proof(this.state, rx, {
        block: null,
        hash: null,
        seek: null,
        upgrade: {
          start: 0,
          length: proof.upgrade.length
        }
      })

      rx.tryFlush()

      const treeProof = await treeProofPromise

      const verifyBatch = MerkleTree.verifyFullyRemote(this.state, await treeProof.settle())
      this._verifyBatchUpgrade(verifyBatch, this.header.manifest)
    } catch (err) {
      if (err.code !== 'INVALID_OPERATION') return true

      return !(await this._repairTreeNodes(proof))
    }

    // both proofs are valid, now check if they forked
    if (b4a.equals(localTreeHash, remoteTreeHash)) return false

    await this.state.mutex.lock()

    try {
      const tx = this.state.createWriteBatch()

      this.header.frozen = true

      tx.setAuth({
        key: this.header.key,
        discoveryKey: this.discoveryKey,
        manifest: this.header.manifest,
        keyPair: this.header.keyPair,
        frozen: true
      })

      await this.state.flush()
    } finally {
      this.state.mutex.unlock()
    }

    // tmp log so we can see these
    const id = b4a.toString(this.discoveryKey, 'hex')
    console.log(
      '[hypercore] conflict detected in ' +
        id +
        ' (writable=' +
        !!this.header.keyPair +
        ',quorum=' +
        this.header.manifest.quorum +
        ')'
    )
    await this._onconflict(proof)
    return true
  }

  async _repairTreeNodes(proof) {
    if (!this._repairMode) return false

    await this.state.mutex.lock()

    for (let i = this.monitors.length - 1; i >= 0; i--) {
      this.monitors[i].emit('repairing', proof)
    }

    try {
      const batch = MerkleTree.verifyFullyRemote(this.state, proof)
      this._verifyBatchUpgrade(batch, proof.manifest)
    } catch {
      this.state.mutex.unlock()
      for (let i = this.monitors.length - 1; i >= 0; i--) {
        this.monitors[i].emit('repair-failed')
      }
      return false
    }

    // Attempt to repair local merkle tree with remote roots
    const atom = this.storage.createAtom()
    const atomized = this.storage.atomize(atom)
    const tx = atomized.write()
    for (const node of proof.upgrade.nodes) {
      tx.putTreeNode(node)
    }
    await tx.flush()

    // Try local proof again
    try {
      const rx = atomized.read()
      const treeProofPromise = MerkleTree.proof(this.state, rx, {
        block: null,
        hash: null,
        seek: null,
        upgrade: {
          start: 0,
          length: proof.upgrade.length
        }
      })

      rx.tryFlush()

      const treeProof = await treeProofPromise

      const verifyBatch = MerkleTree.verifyFullyRemote(this.state, await treeProof.settle())
      this._verifyBatchUpgrade(verifyBatch, this.header.manifest)
    } catch (err) {
      this.state.mutex.unlock()
      for (let i = this.monitors.length - 1; i >= 0; i--) {
        this.monitors[i].emit('repair-failed')
      }
      return false
    }

    await atom.flush()

    for (let i = this.monitors.length - 1; i >= 0; i--) {
      this.monitors[i].emit('repaired', proof)
    }
    this.replicator.setPushOnly(false)
    this.state.mutex.unlock()
    return true
  }

  async verifyReorg(proof) {
    const batch = new ReorgBatch(this.state)
    await MerkleTree.reorg(this.state, proof, batch)
    const manifest = this._verifyBatchUpgrade(batch, proof.manifest)

    if (manifest && !this.header.manifest) {
      await this.state.mutex.lock()
      try {
        if (manifest && this.header.manifest === null) {
          const tx = this.state.createWriteBatch()
          this._setManifest(tx, Verifier.createManifest(manifest), null)
          await this.state.flush()
        }
      } finally {
        this.state._unlock()
      }
    }

    return batch
  }

  async verify(proof, from) {
    // We cannot apply "other forks" atm.
    // We should probably still try and they are likely super similar for non upgrades
    // but this is easy atm (and the above layer will just retry)
    if (proof.fork !== this.state.fork) return false

    const batch = await MerkleTree.verify(this.state, proof)

    if (batch.upgraded && batch.length <= this.state.length) {
      await batch.downgrade()
    }

    if (!batch.commitable()) {
      return false
    }

    const value = (proof.block && proof.block.value) || null
    const op = {
      batch,
      bitfield: value && { drop: false, start: proof.block.index, length: 1 },
      value,
      manifest: proof.manifest,
      from
    }

    if (batch.upgraded) {
      return this._verifyExclusive(op)
    }

    if (this._verifies !== null) {
      const verifies = this._verifies
      const i = verifies.push(op)
      await this._verified
      return verifies[i] !== null
    }

    this._verifies = [op]
    this._verified = this._verifyShared()

    return this._verified
  }

  async reorg(batch) {
    if (!batch.commitable()) return false

    this.truncating++

    try {
      await this.state.reorg(batch)
    } finally {
      this.truncating--
    }

    return true
  }

  openSkipBitfield() {
    if (this.skipBitfield !== null) return this.skipBitfield
    this.skipBitfield = new RemoteBitfield()
    const buf = this.bitfield.toBuffer(this.state.length)
    const bitfield = new Uint32Array(buf.buffer, buf.byteOffset, buf.byteLength / 4)
    this.skipBitfield.insert(0, bitfield)
    return this.skipBitfield
  }

  _setBitfieldRanges(start, end, value) {
    this.bitfield.setRange(start, end, value)
    if (this.skipBitfield !== null) this.skipBitfield.setRange(start, end, value)
  }

  close() {
    if (!this.closing) this.closing = this._close()
    return this.closing
  }

  updateContiguousLength(bitfield) {
    const contig = updateContigBatch(this.header.hints.contiguousLength, bitfield, this.bitfield)

    if (contig.length !== -1 && contig.length !== this.header.hints.contiguousLength) {
      this.header.hints.contiguousLength = contig.length
      this.hintsChanged = true
    }
  }

  updateRemoteContiguousLength(length) {
    this.header.hints.remoteContiguousLength = length
    this.hintsChanged = true
    this.remoteContiguousLengthChanged = true
  }

  onappend(tree, bitfield) {
    this.header.tree = tree

    if (!bitfield) {
      this.replicator.onupgrade()
      return
    }

    this.replicator.cork()

    const { start, length, drop } = bitfield

    this._setBitfieldRanges(start, start + length, true)
    this.updateContiguousLength({ start, length, drop: false })

    this.replicator.onupgrade()
    this.replicator.onhave(start, length, drop)
    this.replicator.uncork()
  }

  ontruncate(tree, { start, length }) {
    if (tree) this.header.tree = tree

    this.replicator.cork()

    this.replicator.ontruncate(start, length)
    this.replicator.onhave(start, length, true)
    this.replicator.onupgrade()
    this.replicator.uncork()

    for (const sessionState of this.sessionStates) {
      if (start < sessionState.snapshotCompatLength) sessionState.snapshotCompatLength = start
    }

    this._setBitfieldRanges(start, start + length, false)
    this.updateContiguousLength({ start, length, drop: true })
  }

  async _onconflict(proof) {
    await this.replicator.onconflict()

    for (let i = this.monitors.length - 1; i >= 0; i--) {
      const s = this.monitors[i]
      s.emit('conflict', proof.upgrade.length, proof.fork, proof)
    }

    const err = new Error('Two conflicting signatures exist for length ' + proof.upgrade.length)
    await this.closeAllSessions(err)
  }

  async closeAllSessions(err) {
    // this.sessions modifies itself when a session closes
    // This way we ensure we indeed iterate over all sessions
    const sessions = this.allSessions()

    const all = []
    for (const s of sessions) all.push(s.close({ error: err, force: false })) // force false or else infinite recursion
    await Promise.allSettled(all)
  }

  async destroy() {
    if (this.destroyed === true) return
    this.destroyed = true

    if (this.hasSession() === true) throw new Error('Cannot destroy while sessions are open')

    const weakSessions = this.allSessions()

    if (this.replicator) this.replicator.destroy()
    if (this.state) await this.state.close()

    // close all pending weak sessions...
    for (const s of weakSessions) s.close().catch(noop)
  }

  async _close() {
    if (this.opened === false) await this.opening
    if (this.hasSession() === true) throw new Error('Cannot close while sessions are open')

    if (this.replicator) await this.replicator.close()

    await this.destroy()
    if (this.autoClose) await this.storage.store.close()

    this.closed = true
  }
}

function updateContigBatch(start, upd, bitfield) {
  const end = upd.start + upd.length

  let c = start

  if (upd.drop) {
    // If we dropped a block in the current contig range, "downgrade" it
    if (c > upd.start) {
      c = upd.start
    }
  } else {
    if (c <= end && c >= upd.start) {
      c = end
      while (bitfield.get(c)) c++
    }
  }

  if (c === start) {
    return {
      length: -1
    }
  }

  if (c > start) {
    return {
      length: c
    }
  }

  return {
    length: c
  }
}

function getDefaultTree() {
  return {
    fork: 0,
    length: 0,
    rootHash: null,
    signature: null,
    timestamp: 0
  }
}

function parseHeader(info) {
  if (!info) return null

  return {
    key: info.key,
    manifest: info.manifest,
    external: null,
    keyPair: info.keyPair,
    tree: info.head || getDefaultTree(),
    group: info.group,
    hints: {
      reorgs: [],
      contiguousLength: info.hints ? info.hints.contiguousLength : 0,
      remoteContiguousLength: info.hints ? info.hints.remoteContiguousLength : 0,
      recovering: info.hints ? info.hints.recovering : 0
    }
  }
}

async function maybeGetRootsFromStorage(storage, length) {
  try {
    return length ? await MerkleTree.getRootsFromStorage(storage, length) : []
  } catch (err) {
    if (err.code !== 'INVALID_OPERATION') throw err
    // If INVALID_OPERATION, a root is likely missing so load empty roots to try to recover
    return []
  }
}

function noop() {}

async function getCoreInfo(storage) {
  const r = storage.read()

  const auth = r.getAuth()
  const head = r.getHead()
  const hints = r.getHints()
  const group = r.getGroup()

  r.tryFlush()

  const [authInfo, headInfo, hintsInfo, groupInfo] = await Promise.all([auth, head, hints, group])
  return {
    ...authInfo,
    head: headInfo,
    hints: hintsInfo,
    group: groupInfo
  }
}
const sodium = require('sodium-universal')
const c = require('compact-encoding')
const b4a = require('b4a')
const { DEFAULT_ENCRYPTION } = require('./caps')

const nonce = b4a.alloc(sodium.crypto_stream_NONCEBYTES)

module.exports = class DefaultEncryption {
  static PADDING = 8

  constructor(encryptionKey, hypercoreKey, opts = {}) {
    this.key = encryptionKey
    this.compat = opts.compat === true

    const keys = DefaultEncryption.deriveKeys(encryptionKey, hypercoreKey, opts)

    this.blockKey = keys.block
    this.blindingKey = keys.blinding
  }

  static deriveKeys(encryptionKey, hypercoreKey, { block = false, compat = false } = {}) {
    const subKeys = b4a.alloc(2 * sodium.crypto_stream_KEYBYTES)

    const blockKey = block ? encryptionKey : subKeys.subarray(0, sodium.crypto_stream_KEYBYTES)
    const blindingKey = subKeys.subarray(sodium.crypto_stream_KEYBYTES)

    if (!block) {
      if (compat) {
        sodium.crypto_generichash_batch(blockKey, [encryptionKey], hypercoreKey)
      } else {
        sodium.crypto_generichash_batch(blockKey, [DEFAULT_ENCRYPTION, hypercoreKey, encryptionKey])
      }
    }

    sodium.crypto_generichash(blindingKey, blockKey)

    return {
      blinding: blindingKey,
      block: blockKey
    }
  }

  static blockEncryptionKey(hypercoreKey, encryptionKey) {
    const blockKey = b4a.alloc(sodium.crypto_stream_KEYBYTES)
    sodium.crypto_generichash_batch(blockKey, [DEFAULT_ENCRYPTION, hypercoreKey, encryptionKey])
    return blockKey
  }

  static encrypt(index, block, fork, blockKey, blindingKey) {
    const padding = block.subarray(0, DefaultEncryption.PADDING)
    block = block.subarray(DefaultEncryption.PADDING)

    c.uint64.encode({ start: 0, end: 8, buffer: padding }, fork)
    c.uint64.encode({ start: 0, end: 8, buffer: nonce }, index)

    // Zero out any previous padding.
    nonce.fill(0, 8, 8 + padding.byteLength)

    // Blind the fork ID, possibly risking reusing the nonce on a reorg of the
    // Hypercore. This is fine as the blinding is best-effort and the latest
    // fork ID shared on replication anyway.
    sodium.crypto_stream_xor(padding, padding, nonce, blindingKey)

    nonce.set(padding, 8)

    // The combination of a (blinded) fork ID and a block index is unique for a
    // given Hypercore and is therefore a valid nonce for encrypting the block.
    sodium.crypto_stream_xor(block, block, nonce, blockKey)
  }

  static decrypt(index, block, blockKey) {
    const padding = block.subarray(0, DefaultEncryption.PADDING)
    block = block.subarray(DefaultEncryption.PADDING)

    c.uint64.encode({ start: 0, end: 8, buffer: nonce }, index)

    nonce.set(padding, 8)

    // Decrypt the block using the blinded fork ID.
    sodium.crypto_stream_xor(block, block, nonce, blockKey)
  }

  encrypt(index, block, fork, core) {
    if (core.compat !== this.compat) this._reload(core)
    return DefaultEncryption.encrypt(index, block, fork, this.blockKey, this.blindingKey)
  }

  decrypt(index, block, core) {
    if (core.compat !== this.compat) this._reload(core)
    return DefaultEncryption.decrypt(index, block, this.blockKey)
  }

  padding() {
    return DefaultEncryption.PADDING
  }

  _reload(core) {
    const block = b4a.equals(this.key, this.blockKey)
    const keys = DefaultEncryption.deriveKeys(this.key, core.key, { block, compat: core.compat })

    this.blockKey = keys.blockKey
    this.blindingKey = keys.blindingKey
  }
}
module.exports = class Download {
  constructor(session, range) {
    this.session = session
    this.range = range
    this.request = null
    this.opened = false
    this.opening = this._open()
    this.opening.catch(noop)
  }

  ready() {
    return this.opening
  }

  async _open() {
    if (this.session.opened === false) await this.session.opening
    this._download()
    this.opened = true
  }

  async done() {
    await this.ready()

    try {
      return await this.request.promise
    } catch (err) {
      if (isSessionMoved(err)) return this._download()
      throw err
    }
  }

  _download() {
    const activeRequests = (this.range && this.range.activeRequests) || this.session.activeRequests
    this.request = this.session.core.replicator.addRange(activeRequests, this.range)
    this.request.promise.catch(noop)
    return this.request.promise
  }

  /**
   * Deprecated. Use `range.done()`.
   */
  downloaded() {
    return this.done()
  }

  destroy() {
    this._destroyBackground().catch(noop)
  }

  async _destroyBackground() {
    if (this.opened === false) await this.ready()
    if (this.request.context) this.request.context.detach(this.request)
  }
}

function noop() {}

function isSessionMoved(err) {
  return err.code === 'SESSION_MOVED'
}
module.exports = {
  SMALL_WANTS: 1
}
// this helper is for fully remote proofs, is like in a push notification where no other context exists

const { MerkleTree } = require('./merkle-tree.js')
const messages = require('./messages.js')
const b4a = require('b4a')
const c = require('compact-encoding')
const crypto = require('hypercore-crypto')
const flat = require('flat-tree')

class SlimSession {
  constructor(storage, auth, head, roots) {
    this.fork = head ? head.fork : 0
    this.roots = roots
    this.length = head ? head.length : 0
    this.signature = head ? head.signature : null
    this.ancestors = this.length
    this.byteLength = 0
    this.prologue = auth.manifest.prologue
    this.storage = storage

    for (let i = 0; i < roots.length; i++) this.byteLength += roots[i].size
  }
}

module.exports = { verify, proof }

async function verify(storage, buffer, { referrer = null } = {}) {
  const state = { buffer, start: 0, end: buffer.byteLength }

  const discoveryKey = c.fixed32.decode(state)
  const proof = messages.wire.data.decode(state)

  const result = {
    key: null,
    discoveryKey,
    newer: true,
    length: 0,
    proof,
    block: null
  }

  const core = await storage.resumeCore(discoveryKey)
  if (core === null) return null

  let rx = core.read()
  const authPromise = rx.getAuth()
  const headPromise = rx.getHead()
  const referrerPromise = rx.getUserData('referrer')

  rx.tryFlush()

  const [auth, head, ref] = await Promise.all([authPromise, headPromise, referrerPromise])

  if (auth === null) return null

  if (referrer && (!ref || !b4a.equals(ref, referrer))) return null

  rx = core.read()

  const rootPromises = []

  for (const index of flat.fullRoots(head ? 2 * head.length : 0)) {
    rootPromises.push(rx.getTreeNode(index))
  }

  rx.tryFlush()

  let roots = await Promise.all(rootPromises)
  if (roots.some(isNull)) roots = []

  const length = head ? head.length : 0

  if (!auth.manifest || !auth.manifest.signers.length) return null

  const batch = await MerkleTree.verifyFullyRemote(new SlimSession(core, auth, head, roots), proof)
  const publicKey = auth.manifest.signers[0].publicKey

  let signable = null
  let signature = null

  if (auth.manifest.version === 0) {
    signable = batch.signable(auth.manifest.signers[0].namespace)
    signature = batch.signature
  } else {
    if (batch.signature[0] !== 1) return null
    if (batch.signature[1] !== 0) return null

    signable = batch.signable(auth.key)
    signature = batch.signature.subarray(2, 66)
  }

  if (!crypto.verify(signable, signature, publicKey)) {
    return null
  }

  result.key = auth.key
  result.discoveryKey = discoveryKey
  result.newer = batch.length > length
  result.length = batch.length
  result.block = proof.block

  return result
}

async function proof(sender, { index, block = null, upgrade = null } = {}) {
  const proof = await sender.proof({
    block: block ? { index, nodes: 0 } : null,
    upgrade: upgrade ? upgrade : { start: 0, length: sender.length }
  })

  if (block) proof.block.value = block
  proof.manifest = sender.core.header.manifest

  const state = { buffer: null, start: 0, end: 0 }
  const data = { request: 0, ...proof }

  c.fixed32.preencode(state, sender.discoveryKey)
  messages.wire.data.preencode(state, data)

  state.buffer = b4a.allocUnsafe(state.end)

  c.fixed32.encode(state, sender.discoveryKey)
  messages.wire.data.encode(state, data)

  return state.buffer
}

function isNull(x) {
  return x === null
}
const TICKS = 16

module.exports = class HotswapQueue {
  constructor() {
    this.priorities = [[], [], []]
  }

  *pick(peer) {
    for (let i = 0; i < this.priorities.length; i++) {
      // try first one more than second one etc etc
      let ticks = (this.priorities.length - i) * TICKS
      const queue = this.priorities[i]

      for (let j = 0; j < queue.length; j++) {
        const r = j + Math.floor(Math.random() * queue.length - j)
        const a = queue[j]
        const b = queue[r]

        if (r !== j) {
          queue[(b.hotswap.index = j)] = b
          queue[(a.hotswap.index = r)] = a
        }

        if (hasInflight(b, peer)) continue

        yield b

        if (--ticks <= 0) break
      }
    }
  }

  add(block) {
    if (block.hotswap !== null) this.remove(block)
    if (block.inflight.length === 0 || block.inflight.length >= 3) return

    // TODO: also use other stuff to determine queue prio
    const queue = this.priorities[block.inflight.length - 1]

    const index = queue.push(block) - 1
    block.hotswap = { ref: this, queue, index }
  }

  remove(block) {
    const hotswap = block.hotswap
    if (hotswap === null) return

    block.hotswap = null
    const head = hotswap.queue.pop()
    if (head === block) return
    hotswap.queue[(head.hotswap.index = hotswap.index)] = head
  }
}

function hasInflight(block, peer) {
  for (let j = 0; j < block.inflight.length; j++) {
    if (block.inflight[j].peer === peer) return true
  }
  return false
}
module.exports = class Info {
  constructor(opts = {}) {
    this.key = opts.key
    this.discoveryKey = opts.discoveryKey
    this.length = opts.length || 0
    this.contiguousLength = opts.contiguousLength || 0
    this.byteLength = opts.byteLength || 0
    this.fork = opts.fork || 0
    this.padding = opts.padding || 0
    this.storage = opts.storage || null
  }

  static async from(session, opts = {}) {
    return new Info({
      key: session.key,
      discoveryKey: session.discoveryKey,
      length: session.length,
      contiguousLength: session.contiguousLength,
      byteLength: session.byteLength,
      fork: session.fork,
      padding: session.padding,
      storage: opts.storage ? await this.storage(session) : null
    })
  }

  static async storage(session) {
    const { oplog, tree, blocks, bitfield } = session.core
    try {
      return {
        oplog: await Info.bytesUsed(oplog.storage),
        tree: await Info.bytesUsed(tree.storage),
        blocks: await Info.bytesUsed(blocks.storage),
        bitfield: await Info.bytesUsed(bitfield.storage)
      }
    } catch {
      return null
    }
  }

  static bytesUsed(file) {
    return new Promise((resolve, reject) => {
      file.stat((err, st) => {
        if (err) {
          resolve(0) // prob just file not found (TODO, improve)
        } else if (typeof st.blocks !== 'number') {
          reject(new Error('cannot determine bytes used'))
        } else {
          resolve(st.blocks * 512)
        }
      })
    })
  }
}
const b4a = require('b4a')

module.exports = function (core, depth, opts) {
  let indent = ''
  if (typeof opts.indentationLvl === 'number') {
    while (indent.length < opts.indentationLvl) indent += ' '
  }

  let peers = ''
  const min = Math.min(core.peers.length, 5)

  for (let i = 0; i < min; i++) {
    const peer = core.peers[i]

    peers += `${indent}    Peer(\n`
    peers += `${indent}      remotePublicKey: ${opts.stylize(toHex(peer.remotePublicKey), 'string')}\n`
    peers += `${indent}      remoteLength: ${opts.stylize(peer.remoteLength, 'number')}\n`
    peers += `${indent}      remoteFork: ${opts.stylize(peer.remoteFork, 'number')}\n`
    peers += `${indent}      remoteCanUpgrade: ${opts.stylize(peer.remoteCanUpgrade, 'boolean')}\n`
    peers += `${indent}    )\n`
  }

  if (core.peers.length > 5) {
    peers += `${indent}  ... and ${core.peers.length - 5} more\n`
  }

  if (peers) peers = `[\n${peers}${indent}  ]`
  else peers = `[ ${opts.stylize(0, 'number')} ]`

  return (
    `${core.constructor.name}(\n` +
    `${indent}  id: ${opts.stylize(core.id, 'string')}\n` +
    `${indent}  key: ${opts.stylize(toHex(core.key), 'string')}\n` +
    `${indent}  discoveryKey: ${opts.stylize(toHex(core.discoveryKey), 'string')}\n` +
    `${indent}  opened: ${opts.stylize(core.opened, 'boolean')}\n` +
    `${indent}  closed: ${opts.stylize(core.closed, 'boolean')}\n` +
    `${indent}  snapshotted: ${opts.stylize(core.snapshotted, 'boolean')}\n` +
    `${indent}  writable: ${opts.stylize(core.writable, 'boolean')}\n` +
    `${indent}  length: ${opts.stylize(core.length, 'number')}\n` +
    `${indent}  fork: ${opts.stylize(core.fork, 'number')}\n` +
    `${indent}  sessions: [ ${opts.stylize(core.sessions.length, 'number')} ]\n` +
    `${indent}  activeRequests: [ ${opts.stylize(core.activeRequests.length, 'number')} ]\n` +
    `${indent}  peers: ${peers}\n` +
    `${indent})`
  )
}

function toHex(buf) {
  return buf && b4a.toString(buf, 'hex')
}
const BigSparseArray = require('big-sparse-array')
const { Transform } = require('streamx')
const quickbit = require('./compat').quickbit
const Mutex = require('./mutex')
const b4a = require('b4a')

const BITS_PER_PAGE = 32768
const BYTES_PER_PAGE = BITS_PER_PAGE / 8

class MarkPage {
  constructor() {
    this.bitfield = null
    this.loaded = new Promise((resolve) => {
      this.load = resolve
    })
  }

  setBitfield(bitfield) {
    this.bitfield = bitfield
    this.load()
  }

  get(index) {
    return quickbit.get(this.bitfield, index)
  }

  set(index, val) {
    quickbit.set(this.bitfield, index, val)
  }
}

module.exports = class MarkBitfield {
  static BITS_PER_PAGE = BITS_PER_PAGE
  static BYTES_PER_PAGE = BYTES_PER_PAGE

  constructor(storage) {
    this.storage = storage
    this.mutex = new Mutex()
    this._pages = new BigSparseArray()
  }

  async loadPage(pageIndex) {
    const p = this._pages.set(pageIndex, new MarkPage())
    const rx = this.storage.read()
    const pageBuf = rx.getMark(pageIndex)
    rx.tryFlush()
    const bitfield = (await pageBuf) ?? b4a.alloc(BYTES_PER_PAGE)
    await p.setBitfield(bitfield)
    return p
  }

  async get(index) {
    const j = index & (BITS_PER_PAGE - 1)
    const i = (index - j) / BITS_PER_PAGE

    let p = this._pages.get(i)
    if (!p) p = await this.loadPage(i)

    return p.get(j)
  }

  async set(index, val) {
    const j = index & (BITS_PER_PAGE - 1)
    const i = (index - j) / BITS_PER_PAGE

    let p = this._pages.get(i)

    if (!p && val) p = await this.loadPage(i)

    if (p) {
      await p.loaded
      p.set(j, val)
      await this.mutex.lock()
      const tx = this.storage.write()
      tx.putMark(i, p.bitfield)
      await tx.flush()
      this.mutex.unlock()
    }
  }

  async clear() {
    const tx = this.storage.write()
    tx.deleteMarkRange(0, -1)
    await tx.flush()
    this._pages = new BigSparseArray()
  }

  createMarkStream({ reverse = false } = {}) {
    return this.storage.createMarkStream({ reverse }).pipe(
      new Transform({
        transform({ index, page }, cb) {
          let bitIndex = reverse
            ? quickbit.findLast(page, true, BITS_PER_PAGE)
            : quickbit.findFirst(page, true, 0)
          while (bitIndex !== -1) {
            const blockIndex = index * BITS_PER_PAGE + bitIndex
            this.push(blockIndex)

            // Account for `bitIndex` being either causing infinite loop
            if (bitIndex === 0 && reverse) break

            bitIndex = reverse
              ? quickbit.findLast(page, true, bitIndex - 1)
              : quickbit.findFirst(page, true, bitIndex + 1)
          }

          cb(null)
        }
      })
    )
  }
}
const flat = require('flat-tree')
const crypto = require('hypercore-crypto')
const b4a = require('b4a')
const unslab = require('unslab')
const caps = require('./caps')
const {
  INVALID_PROOF,
  INVALID_CHECKSUM,
  INVALID_OPERATION,
  BAD_ARGUMENT,
  ASSERTION
} = require('hypercore-errors')

class NodeQueue {
  constructor(nodes, extra = null) {
    this.i = 0
    this.nodes = nodes
    this.extra = extra
    this.length = nodes.length + (this.extra === null ? 0 : 1)
  }

  shift(index) {
    if (this.extra !== null && this.extra.index === index) {
      const node = this.extra
      this.extra = null
      this.length--
      return node
    }

    if (this.i >= this.nodes.length) {
      throw INVALID_OPERATION('Expected node ' + index + ', got (nil)')
    }

    const node = this.nodes[this.i++]
    if (node.index !== index) {
      throw INVALID_OPERATION('Expected node ' + index + ', got node ' + node.index)
    }

    this.length--
    return node
  }
}

class MerkleTreeBatch {
  constructor(session) {
    this.fork = session.fork
    this.roots = [...session.roots]
    this.length = session.length
    this.signature = session.signature
    this.ancestors = session.length
    this.byteLength = session.byteLength
    this.prologue = session.prologue
    this.hashCached = null

    this.committed = false
    this.truncated = false
    this.treeLength = session.length
    this.treeFork = session.fork
    this.storage = session.storage
    this.session = session
    this.nodes = []
    this.upgraded = false
  }

  checkout(length, additionalRoots) {
    const roots = []
    let r = 0

    const head = 2 * length - 2
    const gaps = new Set()
    const all = new Map()

    // additional roots is so the original roots can be passed (we mutate the array in appendRoot)
    if (additionalRoots) {
      for (const node of additionalRoots) all.set(node.index, node)
    }

    for (const node of this.nodes) all.set(node.index, node)

    for (const index of flat.fullRoots(head + 2)) {
      const left = flat.leftSpan(index)
      if (left !== 0) gaps.add(left - 1)

      if (r < this.roots.length && this.roots[r].index === index) {
        roots.push(this.roots[r++])
        continue
      }
      const node = all.get(index)
      if (!node) throw new BAD_ARGUMENT('root missing for given length')
      roots.push(node)
    }

    this.roots = roots
    this.length = length
    this.byteLength = totalSize(roots)
    this.hashCached = null
    this.signature = null

    for (let i = 0; i < this.nodes.length; i++) {
      const index = this.nodes[i].index
      if (index <= head && !gaps.has(index)) continue
      const last = this.nodes.pop()
      if (i < this.nodes.length) this.nodes[i--] = last
    }
  }

  prune(length) {
    if (length === 0) return

    const head = 2 * length - 2
    const gaps = new Set()

    // TODO: make a function for this in flat-tree
    for (const index of flat.fullRoots(head + 2)) {
      const left = flat.leftSpan(index)
      if (left !== 0) gaps.add(left - 1)
    }

    for (let i = 0; i < this.nodes.length; i++) {
      const index = this.nodes[i].index
      if (index > head || gaps.has(index)) continue
      const last = this.nodes.pop()
      if (i < this.nodes.length) this.nodes[i--] = last
    }
  }

  clone() {
    const b = new MerkleTreeBatch(this.session)

    b.fork = this.fork
    b.roots = [...this.roots]
    b.length = this.length
    b.byteLength = this.byteLength
    b.signature = this.signature
    b.treeLength = this.treeLength
    b.treeFork = this.treeFork
    b.tree = this.tree
    b.nodes = [...this.nodes]
    b.upgraded = this.upgraded

    return b
  }

  hash() {
    if (this.hashCached === null) this.hashCached = unslab(crypto.tree(this.roots))
    return this.hashCached
  }

  signable(manifestHash) {
    return caps.treeSignable(manifestHash, this.hash(), this.length, this.fork)
  }

  signableCompat(noHeader) {
    return caps.treeSignableCompat(this.hash(), this.length, this.fork, noHeader)
  }

  get(index) {
    if (index >= this.length * 2) {
      return null
    }

    for (const n of this.nodes) {
      if (n.index === index) return n
    }

    return getTreeNodeFromStorage(this.session.storage, index)
  }

  // deprecated, use sssion proof instead
  proof(batch, { block, hash, seek, upgrade }) {
    return generateProof(this.session, batch, block, hash, seek, upgrade)
  }

  verifyUpgrade(proof) {
    const unverified = verifyTree(proof, this.nodes)

    if (!proof.upgrade) throw INVALID_OPERATION('Expected upgrade proof')

    return verifyUpgrade(proof, unverified, this)
  }

  addNodesUnsafe(nodes) {
    for (let i = 0; i < nodes.length; i++) {
      this.nodes.push(nodes[i])
    }
  }

  append(buf) {
    const head = this.length * 2
    const ite = flat.iterator(head)
    const node = blockNode(head, buf)

    this.appendRoot(node, ite)
  }

  appendRoot(node, ite) {
    node = unslabNode(node)
    this.hashCached = null
    this.upgraded = true
    this.length += ite.factor / 2
    this.byteLength += node.size
    this.roots.push(node)
    this.nodes.push(node)

    while (this.roots.length > 1) {
      const a = this.roots[this.roots.length - 1]
      const b = this.roots[this.roots.length - 2]

      // TODO: just have a peek sibling instead? (pretty sure it's always the left sib as well)
      if (ite.sibling() !== b.index) {
        ite.sibling() // unset so it always points to last root
        break
      }

      const node = unslabNode(parentNode(ite.parent(), a, b))
      this.nodes.push(node)
      this.roots.pop()
      this.roots.pop()
      this.roots.push(node)
    }
  }

  commitable() {
    return (
      this.treeFork === this.session.fork &&
      (this.upgraded
        ? this.treeLength === this.session.length
        : this.treeLength <= this.session.length)
    )
  }

  commit(tx) {
    if (tx === undefined) throw INVALID_OPERATION('No database batch was passed')

    if (this.session.core._repairMode) {
      throw ASSERTION('Cannot commit while repair mode is on')
    }

    if (!this.commitable()) {
      throw INVALID_OPERATION('Tree was modified during batch, refusing to commit')
    }

    if (this.upgraded) this._commitUpgrade(tx)

    for (let i = 0; i < this.nodes.length; i++) {
      const node = this.nodes[i]
      tx.putTreeNode(node)
    }

    this.committed = true

    return this
  }

  _commitUpgrade(tx) {
    // TODO: If easy to detect, we should refuse an trunc+append here without a fork id
    // change. Will only happen on user error so mostly to prevent that.

    if (this.ancestors < this.treeLength) {
      tx.deleteTreeNodeRange(this.ancestors * 2, this.treeLength * 2)

      if (this.ancestors > 0) {
        const head = this.ancestors * 2
        const ite = flat.iterator(head - 2)

        while (true) {
          if (ite.contains(head) && ite.index < head) {
            tx.deleteTreeNode(ite.index)
          }
          if (ite.offset === 0) break
          ite.parent()
        }

        this.truncated = true
      }
    }
  }

  seek(bytes, padding) {
    return new ByteSeeker(this, this, bytes, padding)
  }

  byteRange(index) {
    const rx = this.storage.read()
    const range = getByteRange(this, index, rx)
    rx.tryFlush()

    return range
  }

  byteOffset(index) {
    if (index === 2 * this.length) return this.byteLength

    const rx = this.storage.read()
    const offset = getByteOffset(this, index, rx)
    rx.tryFlush()

    return offset
  }

  async downgrade() {
    if (!this.upgraded) return true

    const rx = this.storage.read()
    const nodePromises = []

    for (const r of this.roots) {
      nodePromises.push(rx.getTreeNode(r.index))
    }

    rx.tryFlush()

    const nodes = await Promise.all(nodePromises)

    for (let i = 0; i < this.roots.length; i++) {
      const root = this.roots[i]
      const node = nodes[i]

      if (!node) return false
      if (!b4a.equals(node.hash, root.hash)) return false
    }

    this.upgraded = false
    return true
  }

  async restore(length) {
    if (length === this.length) return this

    const roots = unslabNodes(await MerkleTree.getRootsFromStorage(this.storage, length))

    this.roots = roots
    this.length = length
    this.byteLength = totalSize(roots)
    this.ancestors = length

    for (const node of roots) this.byteLength += node.size

    return this
  }
}

class ReorgBatch extends MerkleTreeBatch {
  constructor(session) {
    super(session)

    this.roots = []
    this.length = 0
    this.byteLength = 0
    this.diff = null
    this.ancestors = 0
    // We set upgraded because reorgs are signed so hit will
    // hit the same code paths (like the treeLength check in commit)
    this.upgraded = true
    this.want = {
      nodes: 0,
      start: 0,
      end: 0
    }
  }

  get finished() {
    return this.want === null
  }

  update(proof) {
    if (this.want === null) return true

    const nodes = []
    const root = verifyTree(proof, nodes)

    if (root === null || !b4a.equals(root.hash, this.diff.hash)) return false

    this.nodes.push(...nodes)
    return this._update(nodes)
  }

  async _update(nodes) {
    const n = new Map()
    for (const node of nodes) n.set(node.index, node)

    let diff = null
    const ite = flat.iterator(this.diff.index)
    const startingDiff = this.diff

    while ((ite.index & 1) !== 0) {
      const left = n.get(ite.leftChild())
      if (!left) break

      const existing = await getTreeNodeFromStorage(this.session.storage, left.index)
      if (!existing || !b4a.equals(existing.hash, left.hash)) {
        diff = left
      } else {
        diff = n.get(ite.sibling())
      }
    }

    if ((this.diff.index & 1) === 0) return true
    if (diff === null) return false
    if (startingDiff !== this.diff) return false

    return this._updateDiffRoot(diff)
  }

  _updateDiffRoot(diff) {
    if (this.want === null) return true

    const spans = flat.spans(diff.index)
    const start = spans[0] / 2
    const end = Math.min(this.treeLength, spans[1] / 2 + 1)
    const len = end - start

    this.ancestors = start
    this.diff = diff

    if ((diff.index & 1) === 0 || this.want.start >= this.treeLength || len <= 0) {
      this.want = null
      return true
    }

    this.want.start = start
    this.want.end = end
    this.want.nodes = log2(spans[1] - spans[0] + 2) - 1

    return false
  }
}

class ByteSeeker {
  constructor(session, bytes, padding = 0) {
    this.session = session
    this.bytes = bytes
    this.padding = padding

    const size = session.byteLength - session.length * padding

    this.start = bytes >= size ? session.length : 0
    this.end = bytes < size ? session.length : 0
  }

  async _seek(bytes) {
    if (!bytes) return [0, 0]

    for (const node of this.session.roots) {
      // all async ticks happen once we find the root so safe
      const size = getUnpaddedSize(node, this.padding, null)

      if (bytes === size) return [flat.rightSpan(node.index) + 2, 0]
      if (bytes > size) {
        bytes -= size
        continue
      }

      const ite = flat.iterator(node.index)

      while ((ite.index & 1) !== 0) {
        const l = await getTreeNodeFromStorage(this.session.storage, ite.leftChild())

        if (l) {
          const size = getUnpaddedSize(l, this.padding, ite)

          if (size === bytes) return [ite.rightSpan() + 2, 0]
          if (size > bytes) continue
          bytes -= size
          ite.sibling()
        } else {
          ite.parent()
          return [ite.index, bytes]
        }
      }

      return [ite.index, bytes]
    }

    return null
  }

  async update() {
    // TODO: combine _seek and this, much simpler
    const res = await this._seek(this.bytes)
    if (!res) return null
    if ((res[0] & 1) === 0) return [res[0] / 2, res[1]]

    const span = flat.spans(res[0])
    this.start = span[0] / 2
    this.end = span[1] / 2 + 1

    return null
  }
}

class TreeProof {
  constructor(session, block, hash, seek, upgrade) {
    this.fork = session.fork
    this.signature = session.signature

    this.block = block
    this.hash = hash
    this.seek = seek
    this.upgrade = upgrade

    this.pending = {
      node: null,
      seek: null,
      upgrade: null,
      additionalUpgrade: null
    }
  }

  async settle() {
    const result = {
      fork: this.fork,
      block: null,
      hash: null,
      seek: null,
      upgrade: null,
      manifest: null
    }

    const [pNode, pSeek, pUpgrade, pAdditional] = await settleProof(this.pending)

    if (this.block) {
      if (pNode === null) throw INVALID_OPERATION('Invalid block request')
      result.block = {
        index: this.block.index,
        value: null, // populated upstream, alloc it here for simplicity
        nodes: pNode
      }
    } else if (this.hash) {
      if (pNode === null) throw INVALID_OPERATION('Invalid block request')
      result.hash = {
        index: this.hash.index,
        nodes: pNode
      }
    }

    if (this.seek && pSeek !== null) {
      result.seek = {
        bytes: this.seek.bytes,
        nodes: pSeek
      }
    }

    if (this.upgrade) {
      result.upgrade = {
        start: this.upgrade.start,
        length: this.upgrade.length,
        nodes: pUpgrade,
        additionalNodes: pAdditional || [],
        signature: this.signature
      }
    }

    return result
  }
}

class MerkleTree {
  static hash(s) {
    return unslab(crypto.tree(s.roots))
  }

  static signable(s, namespace) {
    return caps.treeSignable(namespace, MerkleTree.hash(s), s.length, s.fork)
  }

  static size(roots) {
    return totalSize(roots)
  }

  static span(roots) {
    return totalSpan(roots)
  }

  static getRoots(session, length) {
    return MerkleTree.getRootsFromStorage(session.storage, length)
  }

  static getRootsFromStorage(storage, length) {
    const indexes = flat.fullRoots(2 * length)
    const roots = new Array(indexes.length)
    const rx = storage.read()

    for (let i = 0; i < indexes.length; i++) {
      roots[i] = getTreeNodeOrError(rx, indexes[i])
    }

    rx.tryFlush()

    return Promise.all(roots)
  }

  static async upgradeable(session, length) {
    const indexes = flat.fullRoots(2 * length)
    const roots = new Array(indexes.length)
    const rx = session.storage.read()

    for (let i = 0; i < indexes.length; i++) {
      roots[i] = rx.getTreeNode(indexes[i])
    }

    rx.tryFlush()

    for (const node of await Promise.all(roots)) {
      if (node === null) return false
    }

    return true
  }

  static seek(session, bytes, padding) {
    return new ByteSeeker(session, bytes, padding)
  }

  static get(session, index) {
    return getTreeNodeFromStorage(session.storage, index)
  }

  static async truncate(session, length, batch, fork = batch.fork) {
    const head = length * 2
    const fullRoots = flat.fullRoots(head)

    for (let i = 0; i < fullRoots.length; i++) {
      const root = fullRoots[i]
      if (i < batch.roots.length && batch.roots[i].index === root) continue

      while (batch.roots.length > i) batch.roots.pop()
      batch.roots.push(unslabNode(await getTreeNodeFromStorageOrError(session.storage, root)))
    }

    while (batch.roots.length > fullRoots.length) {
      batch.roots.pop()
    }

    batch.fork = fork
    batch.length = length
    batch.ancestors = length
    batch.byteLength = totalSize(batch.roots)
    batch.upgraded = true

    return batch
  }

  static async reorg(session, proof, batch) {
    let unverified = null

    if (proof.block || proof.hash || proof.seek) {
      unverified = verifyTree(proof, batch.nodes)
    }

    if (!verifyUpgrade(proof, unverified, batch)) {
      throw INVALID_PROOF('Fork proof not verifiable')
    }

    for (const root of batch.roots) {
      const existing = await getTreeNodeFromStorage(session.storage, root.index)
      if (existing && b4a.equals(existing.hash, root.hash)) continue
      batch._updateDiffRoot(root)
      break
    }

    if (batch.diff !== null) {
      await batch._update(batch.nodes)
    } else {
      batch.want = null
      batch.ancestors = batch.length
    }

    return batch
  }

  static verifyFullyRemote(session, proof) {
    // TODO: impl this less hackishly
    const batch = new MerkleTreeBatch(session)

    batch.fork = proof.fork
    batch.roots = []
    batch.length = 0
    batch.ancestors = 0
    batch.byteLength = 0

    let unverified = verifyTree(proof, batch.nodes)

    if (proof.upgrade) {
      if (verifyUpgrade(proof, unverified, batch)) {
        unverified = null
      }
    }

    return batch
  }

  static async verify(session, proof) {
    const batch = new MerkleTreeBatch(session)

    let unverified = verifyTree(proof, batch.nodes)

    if (proof.upgrade) {
      if (verifyUpgrade(proof, unverified, batch)) {
        unverified = null
      }
    }

    if (unverified) {
      const verified = await getTreeNodeFromStorageOrError(session.storage, unverified.index)
      if (!b4a.equals(verified.hash, unverified.hash)) {
        throw INVALID_CHECKSUM('Invalid checksum at node ' + unverified.index)
      }
    }

    return batch
  }

  static proof(session, rx, { block, hash, seek, upgrade }) {
    return generateProof(session, rx, block, hash, seek, upgrade)
  }

  static maxMissingNodes(index, length) {
    const head = 2 * length
    const ite = flat.iterator(index)

    // See iterator.rightSpan()
    const iteRightSpan = ite.index + ite.factor / 2 - 1
    // If the index is not in the current tree, we do not know how many missing nodes there are...
    if (iteRightSpan >= head) return 0

    for (const r of flat.fullRoots(head)) {
      if (r === index) return 0
    }

    // need missing nodes not inclusive of index
    ite.parent()

    let cnt = 0
    while (!ite.contains(head)) {
      cnt++
      ite.parent()
    }

    return cnt
  }

  static async missingNodes(session, index, length) {
    const head = 2 * length
    const ite = flat.iterator(index)

    // See iterator.rightSpan()
    const iteRightSpan = ite.index + ite.factor / 2 - 1
    // If the index is not in the current tree, we do not know how many missing nodes there are...
    if (iteRightSpan >= head) return 0

    let cnt = 0
    // TODO: we could prop use a read batch here and do this in blocks of X for perf
    while (!ite.contains(head) && !(await hasTreeNode(session.storage, ite.index))) {
      cnt++
      if (cnt >= 1024) {
        throw ASSERTION('Bad arguments to missingNodes index=' + index + ' at length=' + length)
      }
      ite.parent()
    }

    return cnt
  }

  static byteOffset(session, index) {
    return getByteOffsetSession(session, index, null)
  }

  static byteRange(session, index) {
    const rx = session.storage.read()
    const offset = getByteOffsetSession(session, index, rx)
    const size = getNodeSize(index, rx)
    rx.tryFlush()
    return Promise.all([offset, size])
  }

  static upgradeLength(proof) {
    const extra = proof.additionalNodes

    if (extra.length) {
      const last = extra[extra.length - 1]
      const index = flat.rightSpan(last.index)
      return index / 2 + 1
    }

    return proof.start + proof.length
  }
}

module.exports = {
  MerkleTreeBatch,
  ReorgBatch,
  MerkleTree
}

async function getNodeSize(index, rx) {
  return (await getTreeNodeOrError(rx, index)).size
}

async function getByteOffsetSession(session, index, rx) {
  if (index === 2 * session.length) return session.byteLength

  const treeNodes =
    rx === null
      ? await getByteOffsetBatchFlush(session.roots, index, session.storage.read())
      : await getByteOffsetBatch(session.roots, index, rx)

  let offset = 0
  for (const node of treeNodes) offset += node.size

  return offset
}

async function getByteOffset(tree, index, rx) {
  if (index === 2 * tree.length) return tree.byteLength

  const treeNodes = await getByteOffsetBatch(tree.roots, index, rx)

  let offset = 0
  for (const node of treeNodes) offset += node.size

  return offset
}

function getByteOffsetBatchFlush(roots, index, rx) {
  const treeNodes = getByteOffsetBatch(roots, index, rx)
  rx.tryFlush()
  return treeNodes
}

function getByteOffsetBatch(roots, index, rx) {
  if ((index & 1) === 1) index = flat.leftSpan(index)

  let head = 0
  let cnt = 0

  const promises = []

  for (const node of roots) {
    // all async ticks happen once we find the root so safe
    head += 2 * (node.index - head + 1)

    if (index >= head) {
      promises.push(node.size)
      continue
    }

    const ite = flat.iterator(node.index)

    while (ite.index !== index) {
      if (++cnt >= 1024) throw ASSERTION('Bad getByteOffsetSession index=' + index)

      if (index < ite.index) {
        ite.leftChild()
      } else {
        promises.push(getTreeNodeOrError(rx, ite.leftChild()))
        ite.sibling()
      }
    }

    return Promise.all(promises)
  }

  throw ASSERTION('Failed to find offset')
}

function getByteRange(tree, index, rx) {
  const head = 2 * tree.length
  if (((index & 1) === 0 ? index : flat.rightSpan(index)) >= head) {
    throw BAD_ARGUMENT('Index is out of bounds')
  }

  const offset = getByteOffset(tree, index, rx)
  const size = getNodeSize(index, rx)

  return Promise.all([offset, size])
}

// All the methods needed for proof verification

function verifyTree({ block, hash, seek }, nodes) {
  const untrustedNode = block
    ? { index: 2 * block.index, value: block.value, nodes: block.nodes }
    : hash
      ? { index: hash.index, value: null, nodes: hash.nodes }
      : null

  if (untrustedNode === null && (!seek || !seek.nodes.length)) return null

  let root = null

  if (seek && seek.nodes.length) {
    const ite = flat.iterator(seek.nodes[0].index)
    const q = new NodeQueue(seek.nodes)

    root = q.shift(ite.index)
    nodes.push(root)

    while (q.length > 0) {
      const node = q.shift(ite.sibling())

      root = parentNode(ite.parent(), root, node)
      nodes.push(node)
      nodes.push(root)
    }
  }

  if (untrustedNode === null) return root

  const ite = flat.iterator(untrustedNode.index)
  const blockHash = untrustedNode.value && blockNode(ite.index, untrustedNode.value)

  const q = new NodeQueue(untrustedNode.nodes, root)

  root = blockHash || q.shift(ite.index)
  nodes.push(root)

  while (q.length > 0) {
    const node = q.shift(ite.sibling())

    root = parentNode(ite.parent(), root, node)
    nodes.push(node)
    nodes.push(root)
  }

  return root
}

function verifyUpgrade({ fork, upgrade }, blockRoot, batch) {
  const prologue = batch.prologue

  if (prologue) {
    const { start, length } = upgrade
    if (start < prologue.length && (start !== 0 || length < prologue.length)) {
      throw INVALID_PROOF('Upgrade does not satisfy prologue')
    }
  }

  const q = new NodeQueue(upgrade.nodes, blockRoot)

  let grow = batch.roots.length > 0
  let i = 0

  const to = 2 * (upgrade.start + upgrade.length)
  const ite = flat.iterator(0)

  for (; ite.fullRoot(to); ite.nextTree()) {
    if (i < batch.roots.length && batch.roots[i].index === ite.index) {
      i++
      continue
    }

    if (grow) {
      grow = false
      const root = ite.index
      if (i < batch.roots.length) {
        ite.seek(batch.roots[batch.roots.length - 1].index)
        while (ite.index !== root) {
          batch.appendRoot(q.shift(ite.sibling()), ite)
        }
        continue
      }
    }

    batch.appendRoot(q.shift(ite.index), ite)
  }

  if (prologue && batch.length === prologue.length) {
    if (!b4a.equals(prologue.hash, batch.hash())) {
      throw INVALID_PROOF('Invalid hash')
    }
  }

  const extra = upgrade.additionalNodes

  ite.seek(batch.roots[batch.roots.length - 1].index)
  i = 0

  while (i < extra.length && extra[i].index === ite.sibling()) {
    batch.appendRoot(extra[i++], ite)
  }

  while (i < extra.length) {
    const node = extra[i++]

    while (node.index !== ite.index) {
      if (ite.factor === 2) throw INVALID_OPERATION('Unexpected node: ' + node.index)
      ite.leftChild()
    }

    batch.appendRoot(node, ite)
    ite.sibling()
  }

  batch.signature = unslab(upgrade.signature)
  batch.fork = fork

  return q.extra === null
}

async function seekFromHead(session, head, bytes, padding) {
  const roots = flat.fullRoots(head)

  for (let i = 0; i < roots.length; i++) {
    const root = roots[i]
    const node = await getTreeNodeFromStorage(session.storage, root)
    const size = getUnpaddedSize(node, padding, null)

    if (bytes === size) return root
    if (bytes > size) {
      bytes -= size
      continue
    }

    return seekTrustedTree(session, root, bytes, padding)
  }

  return head
}

// trust that bytes are within the root tree and find the block at bytes

async function seekTrustedTree(session, root, bytes, padding) {
  if (!bytes) return root

  const ite = flat.iterator(root)
  let cnt = 0

  while ((ite.index & 1) !== 0) {
    if (++cnt >= 1024) throw ASSERTION('Bad seekTrusted bytes=' + bytes + ', paddding=' + padding)
    const l = await getTreeNodeFromStorage(session.storage, ite.leftChild())

    if (l) {
      const size = getUnpaddedSize(l, padding, ite)
      if (size === bytes) return ite.index
      if (size > bytes) continue
      bytes -= size
      ite.sibling()
    } else {
      ite.parent()
      return ite.index
    }
  }

  return ite.index
}

// try to find the block at bytes without trusting that is *is* within the root passed

async function seekUntrustedTree(session, root, bytes, padding) {
  const offset =
    (await getByteOffsetSession(session, root, null)) -
    (padding ? (padding * flat.leftSpan(root)) / 2 : 0)

  if (offset > bytes) throw INVALID_OPERATION('Invalid seek')
  if (offset === bytes) return root

  bytes -= offset

  const node = await getTreeNodeFromStorageOrError(session.storage, root)

  if (getUnpaddedSize(node, padding, null) <= bytes) throw INVALID_OPERATION('Invalid seek')

  return seekTrustedTree(session, root, bytes, padding)
}

// Below is proof production, ie, construct proofs to verify a request
// Note, that all these methods are sync as we can statically infer which nodes
// are needed for the remote to verify given they arguments they passed us

function seekProof(session, rx, seekRoot, root, p) {
  const ite = flat.iterator(seekRoot)
  let cnt = 0

  p.seek = []
  p.seek.push(getTreeNodeOrError(rx, ite.index))

  while (ite.index !== root) {
    if (++cnt >= 1024) throw ASSERTION('Bad seekProof seekRoot=' + seekRoot + ', root=' + root)
    ite.sibling()
    p.seek.push(getTreeNodeOrError(rx, ite.index))
    ite.parent()
  }
}

function blockAndSeekProof(session, rx, node, seek, seekRoot, root, p) {
  if (!node) return seekProof(session, rx, seekRoot, root, p)

  const ite = flat.iterator(node.index)
  let cnt = 0

  p.node = []
  if (!node.value) p.node.push(getTreeNodeOrError(rx, ite.index))

  while (ite.index !== root) {
    if (++cnt >= 1024) {
      throw ASSERTION('Bad blockAndSeekProof seekRoot=' + seekRoot + ', root=' + root)
    }
    ite.sibling()

    if (seek && ite.contains(seekRoot) && ite.index !== seekRoot) {
      seekProof(session, rx, seekRoot, ite.index, p)
    } else {
      p.node.push(getTreeNodeOrError(rx, ite.index))
    }

    ite.parent()
  }
}

function upgradeProof(session, rx, node, seek, from, to, subTree, p) {
  if (from === 0) p.upgrade = []

  for (const ite = flat.iterator(0); ite.fullRoot(to); ite.nextTree()) {
    // check if they already have the node
    if (ite.index + ite.factor / 2 < from) continue

    // connect existing tree
    if (p.upgrade === null && ite.contains(from - 2)) {
      p.upgrade = []

      const root = ite.index
      const target = from - 2
      let cnt = 0

      ite.seek(target)

      while (ite.index !== root) {
        if (++cnt >= 1024) throw ASSERTION('Bad upgradeProof target=' + target + ', root=' + root)

        ite.sibling()
        if (ite.index > target) {
          if (p.node === null && p.seek === null && ite.contains(subTree)) {
            blockAndSeekProof(session, rx, node, seek, subTree, ite.index, p)
          } else {
            p.upgrade.push(getTreeNodeOrError(rx, ite.index))
          }
        }
        ite.parent()
      }

      continue
    }

    if (p.upgrade === null) {
      p.upgrade = []
    }

    // if the subtree included is a child of this tree, include that one
    // instead of a dup node
    if (p.node === null && p.seek === null && ite.contains(subTree)) {
      blockAndSeekProof(session, rx, node, seek, subTree, ite.index, p)
      continue
    }

    // add root (can be optimised since the root might be in tree.roots)
    p.upgrade.push(getTreeNodeOrError(rx, ite.index))
  }
}

function additionalUpgradeProof(session, rx, from, to, p) {
  if (from === 0) p.additionalUpgrade = []

  for (const ite = flat.iterator(0); ite.fullRoot(to); ite.nextTree()) {
    // check if they already have the node
    if (ite.index + ite.factor / 2 < from) continue

    // connect existing tree
    if (p.additionalUpgrade === null && ite.contains(from - 2)) {
      p.additionalUpgrade = []

      const root = ite.index
      const target = from - 2
      let cnt = 0

      ite.seek(target)

      while (ite.index !== root) {
        if (++cnt >= 1024) {
          throw ASSERTION(
            'Bad arguments to additionalUpgradeProof root=' + root + ' target=' + target
          )
        }
        ite.sibling()
        if (ite.index > target) {
          p.additionalUpgrade.push(getTreeNodeOrError(rx, ite.index))
        }
        ite.parent()
      }

      continue
    }

    if (p.additionalUpgrade === null) {
      p.additionalUpgrade = []
    }

    // add root (can be optimised since the root is in tree.roots)
    p.additionalUpgrade.push(getTreeNodeOrError(rx, ite.index))
  }
}

function nodesToRoot(index, nodes, head) {
  const ite = flat.iterator(index)

  for (let i = 0; i < nodes; i++) {
    ite.parent()
    if (ite.contains(head)) throw INVALID_OPERATION('Nodes is out of bounds')
  }

  return ite.index
}

function totalSize(nodes) {
  let s = 0
  for (const node of nodes) s += node.size
  return s
}

function totalSpan(nodes) {
  let s = 0
  for (const node of nodes) s += 2 * (node.index - s + 1)
  return s
}

function blockNode(index, value) {
  return { index, size: value.byteLength, hash: crypto.data(value) }
}

function parentNode(index, a, b) {
  return { index, size: a.size + b.size, hash: crypto.parent(a, b) }
}

function log2(n) {
  let res = 1

  while (n > 2) {
    n /= 2
    res++
  }

  return res
}

function normalizeIndexed(block, hash) {
  if (block) {
    return {
      value: true,
      index: block.index * 2,
      nodes: block.nodes,
      lastIndex: block.index
    }
  }
  if (hash) {
    return {
      value: false,
      index: hash.index,
      nodes: hash.nodes,
      lastIndex: flat.rightSpan(hash.index) / 2
    }
  }
  return null
}

async function getTreeNodeOrError(rx, index) {
  const node = await rx.getTreeNode(index)
  if (node === null) {
    throw INVALID_OPERATION('Expected tree node ' + index + ' from storage, got (nil)')
  }
  return node
}

function getTreeNodeFromStorageOrError(storage, index) {
  const rx = storage.read()
  const p = getTreeNodeOrError(rx, index)
  rx.tryFlush()
  return p
}

function getTreeNodeFromStorage(storage, index) {
  const rx = storage.read()
  const node = rx.getTreeNode(index)
  rx.tryFlush()
  return node
}

function hasTreeNode(storage, index) {
  const rx = storage.read()
  const has = rx.hasTreeNode(index)
  rx.tryFlush()
  return has
}

async function settleProof(p) {
  const result = [
    p.node && Promise.all(p.node),
    p.seek && Promise.all(p.seek),
    p.upgrade && Promise.all(p.upgrade),
    p.additionalUpgrade && Promise.all(p.additionalUpgrade)
  ]

  try {
    return await Promise.all(result)
  } catch (err) {
    if (p.node) await Promise.allSettled(p.node)
    if (p.seek) await Promise.allSettled(p.seek)
    if (p.upgrade) await Promise.allSettled(p.upgrade)
    if (p.additionalUpgrade) await Promise.allSettled(p.additionalUpgrade)
    throw err
  }
}

// tree can be either the merkle tree or a merkle tree batch
async function generateProof(session, rx, block, hash, seek, upgrade) {
  // Important that this does not throw inbetween making the promise arrays
  // and finalise being called, otherwise there will be lingering promises in the background

  if (session.prologue && upgrade) {
    upgrade.start = upgrade.start < session.prologue.length ? 0 : upgrade.start
    upgrade.length =
      upgrade.start < session.prologue.length ? session.prologue.length : upgrade.length
  }

  const head = 2 * session.length
  const from = upgrade ? upgrade.start * 2 : 0
  const to = upgrade ? from + upgrade.length * 2 : head
  const node = normalizeIndexed(block, hash)

  // can't do anything as we have no data...
  if (head === 0) return new TreeProof(session, null, null, null, null)

  if (from >= to || to > head) {
    throw INVALID_OPERATION('Invalid upgrade')
  }
  if (seek && upgrade && node !== null && node.index >= from) {
    throw INVALID_OPERATION('Cannot both do a seek and block/hash request when upgrading')
  }

  let subTree = head

  const p = new TreeProof(session, block, hash, seek, upgrade)

  if (node !== null && (!upgrade || node.lastIndex < upgrade.start)) {
    subTree = nodesToRoot(node.index, node.nodes, to)
    const seekRoot = seek
      ? await seekUntrustedTree(session, subTree, seek.bytes, seek.padding)
      : head
    blockAndSeekProof(session, rx, node, seek, seekRoot, subTree, p.pending)
  } else if ((node || seek) && upgrade) {
    subTree = seek ? await seekFromHead(session, to, seek.bytes, seek.padding) : node.index
  }

  if (upgrade) {
    upgradeProof(session, rx, node, seek, from, to, subTree, p.pending)
    if (head > to) additionalUpgradeProof(session, rx, to, head, p.pending)
  }

  return p
}

function getUnpaddedSize(node, padding, ite) {
  return padding === 0
    ? node.size
    : node.size - padding * (ite ? ite.countLeaves() : flat.countLeaves(node.index))
}

function unslabNodes(nodes) {
  for (const node of nodes) unslabNode(node)
  return nodes
}

function unslabNode(node) {
  if (node === null) return node
  node.hash = unslab(node.hash)
  return node
}
const c = require('compact-encoding')
const { DEFAULT_NAMESPACE } = require('./caps')
const { INVALID_OPLOG_VERSION } = require('hypercore-errors')
const unslab = require('unslab')

const MANIFEST_PATCH = 0b00000001
const MANIFEST_PROLOGUE = 0b00000010
const MANIFEST_LINKED = 0b00000100
const MANIFEST_USER_DATA = 0b00001000

const hashes = {
  preencode(state, m) {
    state.end++ // small uint
  },
  encode(state, m) {
    if (m === 'blake2b') {
      c.uint.encode(state, 0)
      return
    }

    throw new Error('Unknown hash: ' + m)
  },
  decode(state) {
    const n = c.uint.decode(state)
    if (n === 0) return 'blake2b'
    throw new Error('Unknown hash id: ' + n)
  }
}

const signatures = {
  preencode(state, m) {
    state.end++ // small uint
  },
  encode(state, m) {
    if (m === 'ed25519') {
      c.uint.encode(state, 0)
      return
    }

    throw new Error('Unknown signature: ' + m)
  },
  decode(state) {
    const n = c.uint.decode(state)
    if (n === 0) return 'ed25519'
    throw new Error('Unknown signature id: ' + n)
  }
}

const signer = {
  preencode(state, m) {
    signatures.preencode(state, m.signature)
    c.fixed32.preencode(state, m.namespace)
    c.fixed32.preencode(state, m.publicKey)
  },
  encode(state, m) {
    signatures.encode(state, m.signature)
    c.fixed32.encode(state, m.namespace)
    c.fixed32.encode(state, m.publicKey)
  },
  decode(state) {
    return {
      signature: signatures.decode(state),
      namespace: c.fixed32.decode(state),
      publicKey: c.fixed32.decode(state)
    }
  }
}

const signerArray = c.array(signer)

const prologue = {
  preencode(state, p) {
    c.fixed32.preencode(state, p.hash)
    c.uint.preencode(state, p.length)
  },
  encode(state, p) {
    c.fixed32.encode(state, p.hash)
    c.uint.encode(state, p.length)
  },
  decode(state) {
    return {
      hash: c.fixed32.decode(state),
      length: c.uint.decode(state)
    }
  }
}

const manifestv0 = {
  preencode(state, m) {
    hashes.preencode(state, m.hash)
    state.end++ // type

    if (m.prologue && m.signers.length === 0) {
      c.fixed32.preencode(state, m.prologue.hash)
      return
    }

    if (m.quorum === 1 && m.signers.length === 1 && !m.allowPatch) {
      signer.preencode(state, m.signers[0])
    } else {
      state.end++ // flags
      c.uint.preencode(state, m.quorum)
      signerArray.preencode(state, m.signers)
    }
  },
  encode(state, m) {
    hashes.encode(state, m.hash)

    if (m.prologue && m.signers.length === 0) {
      c.uint.encode(state, 0)
      c.fixed32.encode(state, m.prologue.hash)
      return
    }

    if (m.quorum === 1 && m.signers.length === 1 && !m.allowPatch) {
      c.uint.encode(state, 1)
      signer.encode(state, m.signers[0])
    } else {
      c.uint.encode(state, 2)
      c.uint.encode(state, m.allowPatch ? 1 : 0)
      c.uint.encode(state, m.quorum)
      signerArray.encode(state, m.signers)
    }
  },
  decode(state) {
    const hash = hashes.decode(state)
    const type = c.uint.decode(state)

    if (type > 2) throw new Error('Unknown type: ' + type)

    if (type === 0) {
      return {
        version: 0,
        hash,
        allowPatch: false,
        quorum: 0,
        signers: [],
        prologue: {
          hash: c.fixed32.decode(state),
          length: 0
        },
        linked: null,
        userData: null
      }
    }

    if (type === 1) {
      return {
        version: 0,
        hash,
        allowPatch: false,
        quorum: 1,
        signers: [signer.decode(state)],
        prologue: null,
        linked: null,
        userData: null
      }
    }

    const flags = c.uint.decode(state)

    return {
      version: 0,
      hash,
      allowPatch: (flags & 1) !== 0,
      quorum: c.uint.decode(state),
      signers: signerArray.decode(state),
      prologue: null,
      linked: null,
      userData: null
    }
  }
}

const fixed32Array = c.array(c.fixed32)

const manifest = (exports.manifest = {
  preencode(state, m) {
    state.end++ // version

    if (m.version === 0) return manifestv0.preencode(state, m)

    state.end++ // flags
    hashes.preencode(state, m.hash)

    c.uint.preencode(state, m.quorum)
    signerArray.preencode(state, m.signers)

    if (m.prologue) prologue.preencode(state, m.prologue)
    if (m.linked) fixed32Array.preencode(state, m.linked)
    if (m.userData) c.buffer.preencode(state, m.userData)
  },
  encode(state, m) {
    c.uint.encode(state, m.version)

    if (m.version === 0) return manifestv0.encode(state, m)

    let flags = 0
    if (m.allowPatch) flags |= MANIFEST_PATCH
    if (m.prologue) flags |= MANIFEST_PROLOGUE
    if (m.linked) flags |= MANIFEST_LINKED
    if (m.userData) flags |= MANIFEST_USER_DATA

    c.uint.encode(state, flags)
    hashes.encode(state, m.hash)

    c.uint.encode(state, m.quorum)
    signerArray.encode(state, m.signers)

    if (m.prologue) prologue.encode(state, m.prologue)
    if (m.linked) fixed32Array.encode(state, m.linked)
    if (m.userData) c.buffer.encode(state, m.userData)
  },
  decode(state) {
    const version = c.uint.decode(state)

    if (version === 0) return manifestv0.decode(state)
    if (version > 2) throw new Error('Unknown version: ' + version)

    const flags = c.uint.decode(state)
    const hash = hashes.decode(state)
    const quorum = c.uint.decode(state)
    const signers = signerArray.decode(state)

    const hasPatch = (flags & MANIFEST_PATCH) !== 0
    const hasPrologue = (flags & MANIFEST_PROLOGUE) !== 0
    const hasLinked = (flags & MANIFEST_LINKED) !== 0
    const hasUserData = (flags & MANIFEST_USER_DATA) !== 0

    return {
      version,
      hash,
      allowPatch: hasPatch,
      quorum,
      signers,
      prologue: hasPrologue ? prologue.decode(state) : null,
      linked: hasLinked ? fixed32Array.decode(state) : null,
      userData: hasUserData ? c.buffer.decode(state) : null
    }
  }
})

const node = {
  preencode(state, n) {
    c.uint.preencode(state, n.index)
    c.uint.preencode(state, n.size)
    c.fixed32.preencode(state, n.hash)
  },
  encode(state, n) {
    c.uint.encode(state, n.index)
    c.uint.encode(state, n.size)
    c.fixed32.encode(state, n.hash)
  },
  decode(state) {
    return {
      index: c.uint.decode(state),
      size: c.uint.decode(state),
      hash: c.fixed32.decode(state)
    }
  }
}

const nodeArray = c.array(node)

const wire = (exports.wire = {})

wire.handshake = {
  preencode(state, m) {
    c.uint.preencode(state, 1)
    c.fixed32.preencode(state, m.capability)
  },
  encode(state, m) {
    c.uint.encode(state, m.seeks ? 1 : 0)
    c.fixed32.encode(state, m.capability)
  },
  decode(state) {
    const flags = c.uint.decode(state)
    return {
      seeks: (flags & 1) !== 0,
      capability: unslab(c.fixed32.decode(state))
    }
  }
}

const requestBlock = {
  preencode(state, b) {
    c.uint.preencode(state, b.index)
    c.uint.preencode(state, b.nodes)
  },
  encode(state, b) {
    c.uint.encode(state, b.index)
    c.uint.encode(state, b.nodes)
  },
  decode(state) {
    return {
      index: c.uint.decode(state),
      nodes: c.uint.decode(state)
    }
  }
}

const requestSeek = {
  preencode(state, s) {
    c.uint.preencode(state, s.bytes)
    c.uint.preencode(state, s.padding)
  },
  encode(state, s) {
    c.uint.encode(state, s.bytes)
    c.uint.encode(state, s.padding)
  },
  decode(state) {
    return {
      bytes: c.uint.decode(state),
      padding: c.uint.decode(state)
    }
  }
}

const requestUpgrade = {
  preencode(state, u) {
    c.uint.preencode(state, u.start)
    c.uint.preencode(state, u.length)
  },
  encode(state, u) {
    c.uint.encode(state, u.start)
    c.uint.encode(state, u.length)
  },
  decode(state) {
    return {
      start: c.uint.decode(state),
      length: c.uint.decode(state)
    }
  }
}

wire.request = {
  preencode(state, m) {
    state.end++ // flags
    c.uint.preencode(state, m.id)
    c.uint.preencode(state, m.fork)

    if (m.block) requestBlock.preencode(state, m.block)
    if (m.hash) requestBlock.preencode(state, m.hash)
    if (m.seek) requestSeek.preencode(state, m.seek)
    if (m.upgrade) requestUpgrade.preencode(state, m.upgrade)
    if (m.priority) c.uint.preencode(state, m.priority)
  },
  encode(state, m) {
    const flags =
      (m.block ? 1 : 0) |
      (m.hash ? 2 : 0) |
      (m.seek ? 4 : 0) |
      (m.upgrade ? 8 : 0) |
      (m.manifest ? 16 : 0) |
      (m.priority ? 32 : 0)

    c.uint.encode(state, flags)
    c.uint.encode(state, m.id)
    c.uint.encode(state, m.fork)

    if (m.block) requestBlock.encode(state, m.block)
    if (m.hash) requestBlock.encode(state, m.hash)
    if (m.seek) requestSeek.encode(state, m.seek)
    if (m.upgrade) requestUpgrade.encode(state, m.upgrade)
    if (m.priority) c.uint.encode(state, m.priority)
  },
  decode(state) {
    const flags = c.uint.decode(state)

    return {
      id: c.uint.decode(state),
      fork: c.uint.decode(state),
      block: flags & 1 ? requestBlock.decode(state) : null,
      hash: flags & 2 ? requestBlock.decode(state) : null,
      seek: flags & 4 ? requestSeek.decode(state) : null,
      upgrade: flags & 8 ? requestUpgrade.decode(state) : null,
      manifest: (flags & 16) !== 0,
      priority: flags & 32 ? c.uint.decode(state) : 0
    }
  }
}

wire.cancel = {
  preencode(state, m) {
    c.uint.preencode(state, m.request)
  },
  encode(state, m) {
    c.uint.encode(state, m.request)
  },
  decode(state, m) {
    return {
      request: c.uint.decode(state)
    }
  }
}

const dataUpgrade = {
  preencode(state, u) {
    c.uint.preencode(state, u.start)
    c.uint.preencode(state, u.length)
    nodeArray.preencode(state, u.nodes)
    nodeArray.preencode(state, u.additionalNodes)
    c.optionalBuffer.preencode(state, u.signature)
  },
  encode(state, u) {
    c.uint.encode(state, u.start)
    c.uint.encode(state, u.length)
    nodeArray.encode(state, u.nodes)
    nodeArray.encode(state, u.additionalNodes)
    c.optionalBuffer.encode(state, u.signature)
  },
  decode(state) {
    return {
      start: c.uint.decode(state),
      length: c.uint.decode(state),
      nodes: nodeArray.decode(state),
      additionalNodes: nodeArray.decode(state),
      signature: c.optionalBuffer.decode(state)
    }
  }
}

const dataSeek = {
  preencode(state, s) {
    c.uint.preencode(state, s.bytes)
    nodeArray.preencode(state, s.nodes)
  },
  encode(state, s) {
    c.uint.encode(state, s.bytes)
    nodeArray.encode(state, s.nodes)
  },
  decode(state) {
    return {
      bytes: c.uint.decode(state),
      nodes: nodeArray.decode(state)
    }
  }
}

const dataBlock = {
  preencode(state, b) {
    c.uint.preencode(state, b.index)
    c.buffer.preencode(state, b.value)
    nodeArray.preencode(state, b.nodes)
  },
  encode(state, b) {
    c.uint.encode(state, b.index)
    c.buffer.encode(state, b.value)
    nodeArray.encode(state, b.nodes)
  },
  decode(state) {
    return {
      index: c.uint.decode(state),
      value: c.buffer.decode(state),
      nodes: nodeArray.decode(state)
    }
  }
}

const dataHash = {
  preencode(state, b) {
    c.uint.preencode(state, b.index)
    nodeArray.preencode(state, b.nodes)
  },
  encode(state, b) {
    c.uint.encode(state, b.index)
    nodeArray.encode(state, b.nodes)
  },
  decode(state) {
    return {
      index: c.uint.decode(state),
      nodes: nodeArray.decode(state)
    }
  }
}

wire.data = {
  preencode(state, m) {
    state.end++ // flags
    c.uint.preencode(state, m.request)
    c.uint.preencode(state, m.fork)

    if (m.block) dataBlock.preencode(state, m.block)
    if (m.hash) dataHash.preencode(state, m.hash)
    if (m.seek) dataSeek.preencode(state, m.seek)
    if (m.upgrade) dataUpgrade.preencode(state, m.upgrade)
    if (m.manifest) manifest.preencode(state, m.manifest)
  },
  encode(state, m) {
    const flags =
      (m.block ? 1 : 0) |
      (m.hash ? 2 : 0) |
      (m.seek ? 4 : 0) |
      (m.upgrade ? 8 : 0) |
      (m.manifest ? 16 : 0)

    c.uint.encode(state, flags)
    c.uint.encode(state, m.request)
    c.uint.encode(state, m.fork)

    if (m.block) dataBlock.encode(state, m.block)
    if (m.hash) dataHash.encode(state, m.hash)
    if (m.seek) dataSeek.encode(state, m.seek)
    if (m.upgrade) dataUpgrade.encode(state, m.upgrade)
    if (m.manifest) manifest.encode(state, m.manifest)
  },
  decode(state) {
    const flags = c.uint.decode(state)

    return {
      request: c.uint.decode(state),
      fork: c.uint.decode(state),
      block: flags & 1 ? dataBlock.decode(state) : null,
      hash: flags & 2 ? dataHash.decode(state) : null,
      seek: flags & 4 ? dataSeek.decode(state) : null,
      upgrade: flags & 8 ? dataUpgrade.decode(state) : null,
      manifest: flags & 16 ? manifest.decode(state) : null
    }
  }
}

wire.noData = {
  preencode(state, m) {
    c.uint.preencode(state, m.request)
    c.uint.preencode(state, m.reason ? 1 : 0)
    if (m.reason) c.uint.preencode(state, m.reason)
  },
  encode(state, m) {
    c.uint.encode(state, m.request)
    c.uint.encode(state, m.reason ? 1 : 0)
    if (m.reason) c.uint.encode(state, m.reason)
  },
  decode(state, m) {
    const request = c.uint.decode(state)
    const flags = state.start < state.end ? c.uint.decode(state) : 0
    return {
      request,
      reason: flags & 1 ? c.uint.decode(state) : 0
    }
  }
}

// TODO: we should move to having these have a handle like in data, easier since they
// allocate state in practice
wire.want = {
  preencode(state, m) {
    c.uint.preencode(state, m.start)
    c.uint.preencode(state, m.length)

    const flags = m.any ? 1 : 0
    if (flags !== 0) c.uint.preencode(state, flags)
  },
  encode(state, m) {
    c.uint.encode(state, m.start)
    c.uint.encode(state, m.length)

    const flags = m.any ? 1 : 0
    if (flags !== 0) c.uint.encode(state, flags)
  },
  decode(state) {
    const start = c.uint.decode(state)
    const length = c.uint.decode(state)
    const flags = state.start < state.end ? c.uint.decode(state) : 0

    return {
      start,
      length,
      any: (flags & 1) !== 0
    }
  }
}

wire.unwant = {
  preencode(state, m) {
    c.uint.preencode(state, m.start)
    c.uint.preencode(state, m.length)

    const flags = m.any ? 1 : 0
    if (flags !== 0) c.uint.preencode(state, flags)
  },
  encode(state, m) {
    c.uint.encode(state, m.start)
    c.uint.encode(state, m.length)

    const flags = m.any ? 1 : 0
    if (flags !== 0) c.uint.encode(state, flags)
  },
  decode(state, m) {
    const start = c.uint.decode(state)
    const length = c.uint.decode(state)
    const flags = state.start < state.end ? c.uint.decode(state) : 0

    return {
      start,
      length,
      any: (flags & 1) !== 0
    }
  }
}

wire.range = {
  preencode(state, m) {
    state.end++ // flags
    c.uint.preencode(state, m.start)
    if (m.length !== 1) c.uint.preencode(state, m.length)
  },
  encode(state, m) {
    c.uint.encode(state, (m.drop ? 1 : 0) | (m.length === 1 ? 2 : 0))
    c.uint.encode(state, m.start)
    if (m.length !== 1) c.uint.encode(state, m.length)
  },
  decode(state) {
    const flags = c.uint.decode(state)

    return {
      drop: (flags & 1) !== 0,
      start: c.uint.decode(state),
      length: (flags & 2) !== 0 ? 1 : c.uint.decode(state)
    }
  }
}

wire.bitfield = {
  preencode(state, m) {
    c.uint.preencode(state, m.start)
    c.uint32array.preencode(state, m.bitfield)
  },
  encode(state, m) {
    c.uint.encode(state, m.start)
    c.uint32array.encode(state, m.bitfield)
  },
  decode(state, m) {
    return {
      start: c.uint.decode(state),
      bitfield: c.uint32array.decode(state)
    }
  }
}

wire.sync = {
  preencode(state, m) {
    state.end++ // flags
    c.uint.preencode(state, m.fork)
    c.uint.preencode(state, m.length)
    c.uint.preencode(state, m.remoteLength)
  },
  encode(state, m) {
    c.uint.encode(
      state,
      (m.canUpgrade ? 1 : 0) |
        (m.uploading ? 2 : 0) |
        (m.downloading ? 4 : 0) |
        (m.hasManifest ? 8 : 0) |
        (m.allowPush ? 16 : 0)
    )
    c.uint.encode(state, m.fork)
    c.uint.encode(state, m.length)
    c.uint.encode(state, m.remoteLength)
  },
  decode(state) {
    const flags = c.uint.decode(state)

    return {
      fork: c.uint.decode(state),
      length: c.uint.decode(state),
      remoteLength: c.uint.decode(state),
      canUpgrade: (flags & 1) !== 0,
      uploading: (flags & 2) !== 0,
      downloading: (flags & 4) !== 0,
      hasManifest: (flags & 8) !== 0,
      allowPush: (flags & 16) !== 0
    }
  }
}

wire.reorgHint = {
  preencode(state, m) {
    c.uint.preencode(state, m.from)
    c.uint.preencode(state, m.to)
    c.uint.preencode(state, m.ancestors)
  },
  encode(state, m) {
    c.uint.encode(state, m.from)
    c.uint.encode(state, m.to)
    c.uint.encode(state, m.ancestors)
  },
  decode(state) {
    return {
      from: c.uint.decode(state),
      to: c.uint.decode(state),
      ancestors: c.uint.decode(state)
    }
  }
}

wire.extension = {
  preencode(state, m) {
    c.string.preencode(state, m.name)
    c.raw.preencode(state, m.message)
  },
  encode(state, m) {
    c.string.encode(state, m.name)
    c.raw.encode(state, m.message)
  },
  decode(state) {
    return {
      name: c.string.decode(state),
      message: c.raw.decode(state)
    }
  }
}

const keyValue = {
  preencode(state, p) {
    c.string.preencode(state, p.key)
    c.buffer.preencode(state, p.value)
  },
  encode(state, p) {
    c.string.encode(state, p.key)
    c.buffer.encode(state, p.value)
  },
  decode(state) {
    return {
      key: c.string.decode(state),
      value: c.buffer.decode(state)
    }
  }
}

const treeUpgrade = {
  preencode(state, u) {
    c.uint.preencode(state, u.fork)
    c.uint.preencode(state, u.ancestors)
    c.uint.preencode(state, u.length)
    c.buffer.preencode(state, u.signature)
  },
  encode(state, u) {
    c.uint.encode(state, u.fork)
    c.uint.encode(state, u.ancestors)
    c.uint.encode(state, u.length)
    c.buffer.encode(state, u.signature)
  },
  decode(state) {
    return {
      fork: c.uint.decode(state),
      ancestors: c.uint.decode(state),
      length: c.uint.decode(state),
      signature: c.buffer.decode(state)
    }
  }
}

const bitfieldUpdate = {
  // TODO: can maybe be folded into a HAVE later on with the most recent spec
  preencode(state, b) {
    state.end++ // flags
    c.uint.preencode(state, b.start)
    c.uint.preencode(state, b.length)
  },
  encode(state, b) {
    state.buffer[state.start++] = b.drop ? 1 : 0
    c.uint.encode(state, b.start)
    c.uint.encode(state, b.length)
  },
  decode(state) {
    const flags = c.uint.decode(state)
    return {
      drop: (flags & 1) !== 0,
      start: c.uint.decode(state),
      length: c.uint.decode(state)
    }
  }
}

const oplog = (exports.oplog = {})

oplog.entry = {
  preencode(state, m) {
    state.end++ // flags
    if (m.userData) keyValue.preencode(state, m.userData)
    if (m.treeNodes) nodeArray.preencode(state, m.treeNodes)
    if (m.treeUpgrade) treeUpgrade.preencode(state, m.treeUpgrade)
    if (m.bitfield) bitfieldUpdate.preencode(state, m.bitfield)
  },
  encode(state, m) {
    const s = state.start++
    let flags = 0

    if (m.userData) {
      flags |= 1
      keyValue.encode(state, m.userData)
    }
    if (m.treeNodes) {
      flags |= 2
      nodeArray.encode(state, m.treeNodes)
    }
    if (m.treeUpgrade) {
      flags |= 4
      treeUpgrade.encode(state, m.treeUpgrade)
    }
    if (m.bitfield) {
      flags |= 8
      bitfieldUpdate.encode(state, m.bitfield)
    }

    state.buffer[s] = flags
  },
  decode(state) {
    const flags = c.uint.decode(state)
    return {
      userData: (flags & 1) !== 0 ? keyValue.decode(state) : null,
      treeNodes: (flags & 2) !== 0 ? nodeArray.decode(state) : null,
      treeUpgrade: (flags & 4) !== 0 ? treeUpgrade.decode(state) : null,
      bitfield: (flags & 8) !== 0 ? bitfieldUpdate.decode(state) : null
    }
  }
}

const keyPair = {
  preencode(state, kp) {
    c.buffer.preencode(state, kp.publicKey)
    c.buffer.preencode(state, kp.secretKey)
  },
  encode(state, kp) {
    c.buffer.encode(state, kp.publicKey)
    c.buffer.encode(state, kp.secretKey)
  },
  decode(state) {
    return {
      publicKey: c.buffer.decode(state),
      secretKey: c.buffer.decode(state)
    }
  }
}

const reorgHint = {
  preencode(state, r) {
    c.uint.preencode(state, r.from)
    c.uint.preencode(state, r.to)
    c.uint.preencode(state, r.ancestors)
  },
  encode(state, r) {
    c.uint.encode(state, r.from)
    c.uint.encode(state, r.to)
    c.uint.encode(state, r.ancestors)
  },
  decode(state) {
    return {
      from: c.uint.decode(state),
      to: c.uint.decode(state),
      ancestors: c.uint.decode(state)
    }
  }
}

const reorgHintArray = c.array(reorgHint)

const hints = {
  preencode(state, h) {
    reorgHintArray.preencode(state, h.reorgs)
    c.uint.preencode(state, h.contiguousLength)
  },
  encode(state, h) {
    reorgHintArray.encode(state, h.reorgs)
    c.uint.encode(state, h.contiguousLength)
  },
  decode(state) {
    return {
      reorgs: reorgHintArray.decode(state),
      contiguousLength: state.start < state.end ? c.uint.decode(state) : 0
    }
  }
}

const treeHeader = {
  preencode(state, t) {
    c.uint.preencode(state, t.fork)
    c.uint.preencode(state, t.length)
    c.buffer.preencode(state, t.rootHash)
    c.buffer.preencode(state, t.signature)
  },
  encode(state, t) {
    c.uint.encode(state, t.fork)
    c.uint.encode(state, t.length)
    c.buffer.encode(state, t.rootHash)
    c.buffer.encode(state, t.signature)
  },
  decode(state) {
    return {
      fork: c.uint.decode(state),
      length: c.uint.decode(state),
      rootHash: c.buffer.decode(state),
      signature: c.buffer.decode(state)
    }
  }
}

const types = {
  preencode(state, t) {
    c.string.preencode(state, t.tree)
    c.string.preencode(state, t.bitfield)
    c.string.preencode(state, t.signer)
  },
  encode(state, t) {
    c.string.encode(state, t.tree)
    c.string.encode(state, t.bitfield)
    c.string.encode(state, t.signer)
  },
  decode(state) {
    return {
      tree: c.string.decode(state),
      bitfield: c.string.decode(state),
      signer: c.string.decode(state)
    }
  }
}

const externalHeader = {
  preencode(state, m) {
    c.uint.preencode(state, m.start)
    c.uint.preencode(state, m.length)
  },
  encode(state, m) {
    c.uint.encode(state, m.start)
    c.uint.encode(state, m.length)
  },
  decode(state) {
    return {
      start: c.uint.decode(state),
      length: c.uint.decode(state)
    }
  }
}

const keyValueArray = c.array(keyValue)

oplog.header = {
  preencode(state, h) {
    state.end += 2 // version + flags
    if (h.external) {
      externalHeader.preencode(state, h.external)
      return
    }
    c.fixed32.preencode(state, h.key)
    if (h.manifest) manifest.preencode(state, h.manifest)
    if (h.keyPair) keyPair.preencode(state, h.keyPair)
    keyValueArray.preencode(state, h.userData)
    treeHeader.preencode(state, h.tree)
    hints.preencode(state, h.hints)
  },
  encode(state, h) {
    c.uint.encode(state, 1)
    if (h.external) {
      c.uint.encode(state, 1) // ONLY set the first big for clarity
      externalHeader.encode(state, h.external)
      return
    }
    c.uint.encode(state, (h.manifest ? 2 : 0) | (h.keyPair ? 4 : 0))
    c.fixed32.encode(state, h.key)
    if (h.manifest) manifest.encode(state, h.manifest)
    if (h.keyPair) keyPair.encode(state, h.keyPair)
    keyValueArray.encode(state, h.userData)
    treeHeader.encode(state, h.tree)
    hints.encode(state, h.hints)
  },
  decode(state) {
    const version = c.uint.decode(state)

    if (version > 1) {
      throw INVALID_OPLOG_VERSION('Invalid header version. Expected <= 1, got ' + version)
    }

    if (version === 0) {
      const old = {
        types: types.decode(state),
        userData: keyValueArray.decode(state),
        tree: treeHeader.decode(state),
        signer: keyPair.decode(state),
        hints: hints.decode(state)
      }

      return {
        external: null,
        key: old.signer.publicKey,
        manifest: {
          version: 0,
          hash: old.types.tree,
          allowPatch: false,
          quorum: 1,
          signers: [
            {
              signature: old.types.signer,
              namespace: DEFAULT_NAMESPACE,
              publicKey: old.signer.publicKey
            }
          ],
          prologue: null,
          linked: null,
          userData: null
        },
        keyPair: old.signer.secretKey ? old.signer : null,
        userData: old.userData,
        tree: old.tree,
        hints: old.hints
      }
    }

    const flags = c.uint.decode(state)

    if (flags & 1) {
      return {
        external: externalHeader.decode(state),
        key: null,
        manifest: null,
        keyPair: null,
        userData: null,
        tree: null,
        hints: null
      }
    }

    return {
      external: null,
      key: c.fixed32.decode(state),
      manifest: (flags & 2) !== 0 ? manifest.decode(state) : null,
      keyPair: (flags & 4) !== 0 ? keyPair.decode(state) : null,
      userData: keyValueArray.decode(state),
      tree: treeHeader.decode(state),
      hints: hints.decode(state)
    }
  }
}

const uintArray = c.array(c.uint)

const multisigInput = {
  preencode(state, inp) {
    c.uint.preencode(state, inp.signer)
    c.fixed64.preencode(state, inp.signature)
    c.uint.preencode(state, inp.patch)
  },
  encode(state, inp) {
    c.uint.encode(state, inp.signer)
    c.fixed64.encode(state, inp.signature)
    c.uint.encode(state, inp.patch)
  },
  decode(state) {
    return {
      signer: c.uint.decode(state),
      signature: c.fixed64.decode(state),
      patch: c.uint.decode(state)
    }
  }
}

const patchEncodingv0 = {
  preencode(state, n) {
    c.uint.preencode(state, n.start)
    c.uint.preencode(state, n.length)
    uintArray.preencode(state, n.nodes)
  },
  encode(state, n) {
    c.uint.encode(state, n.start)
    c.uint.encode(state, n.length)
    uintArray.encode(state, n.nodes)
  },
  decode(state) {
    return {
      start: c.uint.decode(state),
      length: c.uint.decode(state),
      nodes: uintArray.decode(state)
    }
  }
}

const multisigInputv0 = {
  preencode(state, n) {
    state.end++
    c.uint.preencode(state, n.signer)
    c.fixed64.preencode(state, n.signature)
    if (n.patch) patchEncodingv0.preencode(state, n.patch)
  },
  encode(state, n) {
    c.uint.encode(state, n.patch ? 1 : 0)
    c.uint.encode(state, n.signer)
    c.fixed64.encode(state, n.signature)
    if (n.patch) patchEncodingv0.encode(state, n.patch)
  },
  decode(state) {
    const flags = c.uint.decode(state)
    return {
      signer: c.uint.decode(state),
      signature: c.fixed64.decode(state),
      patch: flags & 1 ? patchEncodingv0.decode(state) : null
    }
  }
}

const multisigInputArrayv0 = c.array(multisigInputv0)
const multisigInputArray = c.array(multisigInput)

const compactNode = {
  preencode(state, n) {
    c.uint.preencode(state, n.index)
    c.uint.preencode(state, n.size)
    c.fixed32.preencode(state, n.hash)
  },
  encode(state, n) {
    c.uint.encode(state, n.index)
    c.uint.encode(state, n.size)
    c.fixed32.encode(state, n.hash)
  },
  decode(state) {
    return {
      index: c.uint.decode(state),
      size: c.uint.decode(state),
      hash: c.fixed32.decode(state)
    }
  }
}

const compactNodeArray = c.array(compactNode)

exports.multiSignaturev0 = {
  preencode(state, s) {
    multisigInputArrayv0.preencode(state, s.proofs)
    compactNodeArray.preencode(state, s.patch)
  },
  encode(state, s) {
    multisigInputArrayv0.encode(state, s.proofs)
    compactNodeArray.encode(state, s.patch)
  },
  decode(state) {
    return {
      proofs: multisigInputArrayv0.decode(state),
      patch: compactNodeArray.decode(state)
    }
  }
}

exports.multiSignature = {
  preencode(state, s) {
    multisigInputArray.preencode(state, s.proofs)
    compactNodeArray.preencode(state, s.patch)
  },
  encode(state, s) {
    multisigInputArray.encode(state, s.proofs)
    compactNodeArray.encode(state, s.patch)
  },
  decode(state) {
    return {
      proofs: multisigInputArray.decode(state),
      patch: compactNodeArray.decode(state)
    }
  }
}
const c = require('compact-encoding')
const b4a = require('b4a')
const flat = require('flat-tree')
const { MerkleTree } = require('./merkle-tree')
const { multiSignature, multiSignaturev0 } = require('./messages')

module.exports = {
  assemblev0,
  assemble,
  inflatev0,
  inflate,
  partialSignature,
  signableLength
}

function inflatev0(data) {
  return c.decode(multiSignaturev0, data)
}

function inflate(data) {
  return c.decode(multiSignature, data)
}

async function partialSignature(
  core,
  signer,
  from,
  to = core.state.length,
  signature = core.state.signature
) {
  if (from > core.state.length) return null
  const nodes = to <= from ? null : await upgradeNodes(core, from, to)

  if (signature.byteLength !== 64) {
    signature = c.decode(multiSignature, signature).proofs[0].signature
  }

  return {
    signer,
    signature,
    patch: nodes ? to - from : 0,
    nodes
  }
}

async function upgradeNodes(core, from, to) {
  const rx = core.state.storage.read()
  let p = null
  try {
    p = await MerkleTree.proof(core.state, rx, { upgrade: { start: from, length: to - from } })
  } catch (err) {
    rx.destroy()
    throw err
  }
  rx.tryFlush()
  return (await p.settle()).upgrade.nodes
}

function signableLength(lengths, quorum) {
  if (quorum <= 0) quorum = 1
  if (quorum > lengths.length) return 0

  return lengths.sort(cmp)[quorum - 1]
}

function cmp(a, b) {
  return b - a
}

function assemblev0(inputs) {
  const proofs = []
  const patch = []

  for (const u of inputs) {
    proofs.push(compressProof(u, patch))
  }

  return c.encode(multiSignaturev0, { proofs, patch })
}

function assemble(inputs) {
  const proofs = []
  const patch = []
  const seen = new Set()

  for (const u of inputs) {
    if (u.nodes) {
      for (const node of u.nodes) {
        if (seen.has(node.index)) continue
        seen.add(node.index)
        patch.push(node)
      }
    }

    proofs.push({
      signer: u.signer,
      signature: u.signature,
      patch: u.patch
    })
  }

  return c.encode(multiSignature, { proofs, patch })
}

function compareNode(a, b) {
  if (a.index !== b.index) return false
  if (a.size !== b.size) return false
  return b4a.equals(a.hash, b.hash)
}

function compressProof(proof, nodes) {
  return {
    signer: proof.signer,
    signature: proof.signature,
    patch: proof.patch ? compressUpgrade(proof, nodes) : null
  }
}

function compressUpgrade(p, nodes) {
  const u = {
    start: flat.rightSpan(p.nodes[p.nodes.length - 1].index) / 2 + 1,
    length: p.patch,
    nodes: []
  }

  for (const node of p.nodes) {
    let present = false
    for (let i = 0; i < nodes.length; i++) {
      if (!compareNode(nodes[i], node)) continue

      u.nodes.push(i)
      present = true
      break
    }

    if (present) continue
    u.nodes.push(nodes.push(node) - 1)
  }

  return u
}
module.exports = class Mutex {
  constructor() {
    this.locked = false
    this.destroyed = false

    this._destroying = null
    this._destroyError = null
    this._queue = []
    this._enqueue = (resolve, reject) => this._queue.push([resolve, reject])
  }

  idle() {
    return this._queue.length === 0 && this.locked === false
  }

  lock() {
    if (this.destroyed) {
      return Promise.reject(this._destroyError || new Error('Mutex has been destroyed'))
    }
    if (this.locked) return new Promise(this._enqueue)
    this.locked = true
    return Promise.resolve()
  }

  unlock() {
    if (!this._queue.length) {
      this.locked = false
      return
    }
    this._queue.shift()[0]()
  }

  destroy(err) {
    if (!this._destroying) {
      this._destroying = this.locked ? this.lock().catch(() => {}) : Promise.resolve()
    }

    this.destroyed = true
    if (err) this._destroyError = err

    if (err) {
      while (this._queue.length) this._queue.shift()[1](err)
    }

    return this._destroying
  }
}
const FIFO = require('fast-fifo')

module.exports = class ReceiverQueue {
  constructor() {
    this.queue = new FIFO()
    this.priority = []
    this.requests = new Map()
    this.length = 0
  }

  push(req) {
    // TODO: use a heap at some point if we wanna support multiple prios
    if (req.priority > 0) this.priority.push(req)
    else this.queue.push(req)

    this.requests.set(req.id, req)
    this.length++
  }

  shift() {
    while (this.priority.length > 0) {
      const msg = this.priority.pop()
      const req = this._processRequest(msg)
      if (req !== null) return req
    }

    while (this.queue.length > 0) {
      const msg = this.queue.shift()
      const req = this._processRequest(msg)
      if (req !== null) return req
    }

    return null
  }

  _processRequest(req) {
    if (req.block || req.hash || req.seek || req.upgrade || req.manifest) {
      this.requests.delete(req.id)
      this.length--
      return req
    }

    return null
  }

  clear() {
    this.queue.clear()
    this.priority = []
    this.length = 0
    this.requests.clear()
  }

  delete(id) {
    const req = this.requests.get(id)
    if (!req) return

    req.block = null
    req.hash = null
    req.seek = null
    req.upgrade = null
    req.manifest = false

    this.requests.delete(id)
    this.length--

    if (this.length === 0) {
      this.queue.clear()
      this.priority = []
    }
  }
}
const BigSparseArray = require('big-sparse-array')
const quickbit = require('./compat').quickbit

const BITS_PER_PAGE = 32768
const BYTES_PER_PAGE = BITS_PER_PAGE / 8
const WORDS_PER_PAGE = BYTES_PER_PAGE / 4
const BITS_PER_SEGMENT = 2097152
const BYTES_PER_SEGMENT = BITS_PER_SEGMENT / 8
const PAGES_PER_SEGMENT = BITS_PER_SEGMENT / BITS_PER_PAGE

class RemoteBitfieldPage {
  constructor(index, bitfield, segment) {
    this.index = index
    this.offset = index * BYTES_PER_PAGE - segment.offset
    this.bitfield = bitfield
    this.segment = segment

    segment.add(this)
  }

  get tree() {
    return this.segment.tree
  }

  get(index) {
    return quickbit.get(this.bitfield, index)
  }

  set(index, val) {
    if (quickbit.set(this.bitfield, index, val)) {
      this.tree.update(this.offset * 8 + index)
    }
  }

  setRange(start, end, val) {
    quickbit.fill(this.bitfield, val, start, end)

    let i = Math.floor(start / 128)
    const n = i + Math.ceil((end - start) / 128)

    while (i <= n) this.tree.update(this.offset * 8 + i++ * 128)
  }

  findFirst(val, position) {
    return quickbit.findFirst(this.bitfield, val, position)
  }

  findLast(val, position) {
    return quickbit.findLast(this.bitfield, val, position)
  }

  insert(start, bitfield) {
    this.bitfield.set(bitfield, start / 32)
    this.segment.refresh()
  }

  clear(start, bitfield) {
    quickbit.clear(this.bitfield, { field: bitfield, offset: start })
  }
}

class RemoteBitfieldSegment {
  constructor(index) {
    this.index = index
    this.offset = index * BYTES_PER_SEGMENT
    this.tree = quickbit.Index.from([], BYTES_PER_SEGMENT)
    this.pages = new Array(PAGES_PER_SEGMENT)
    this.pagesLength = 0
  }

  get chunks() {
    return this.tree.chunks
  }

  refresh() {
    this.tree = quickbit.Index.from(this.tree.chunks, BYTES_PER_SEGMENT)
  }

  add(page) {
    const pageIndex = page.index - this.index * PAGES_PER_SEGMENT
    if (pageIndex >= this.pagesLength) this.pagesLength = pageIndex + 1

    this.pages[pageIndex] = page

    const chunk = { field: page.bitfield, offset: page.offset }

    this.chunks.push(chunk)

    for (let i = this.chunks.length - 2; i >= 0; i--) {
      const prev = this.chunks[i]
      if (prev.offset <= chunk.offset) break
      this.chunks[i] = chunk
      this.chunks[i + 1] = prev
    }
  }

  findFirst(val, position) {
    position = this.tree.skipFirst(!val, position)

    let j = position & (BITS_PER_PAGE - 1)
    let i = (position - j) / BITS_PER_PAGE

    if (i >= PAGES_PER_SEGMENT) return -1

    while (i < this.pagesLength) {
      const p = this.pages[i]

      let index = -1

      if (p) index = p.findFirst(val, j)
      else if (!val) index = j

      if (index !== -1) return i * BITS_PER_PAGE + index

      j = 0
      i++
    }

    return val || this.pagesLength === PAGES_PER_SEGMENT ? -1 : this.pagesLength * BITS_PER_PAGE
  }

  findLast(val, position) {
    position = this.tree.skipLast(!val, position)

    let j = position & (BITS_PER_PAGE - 1)
    let i = (position - j) / BITS_PER_PAGE

    if (i >= PAGES_PER_SEGMENT) return -1

    while (i >= 0) {
      const p = this.pages[i]

      let index = -1

      if (p) index = p.findLast(val, j)
      else if (!val) index = j

      if (index !== -1) return i * BITS_PER_PAGE + index

      j = BITS_PER_PAGE - 1
      i--
    }

    return -1
  }
}

module.exports = class RemoteBitfield {
  static BITS_PER_PAGE = BITS_PER_PAGE
  static BITS_PER_SEGMENT = BITS_PER_SEGMENT

  constructor() {
    this._pages = new BigSparseArray()
    this._segments = new BigSparseArray()
    this._maxSegments = 0
  }

  getBitfield(index) {
    const j = index & (BITS_PER_PAGE - 1)
    const i = (index - j) / BITS_PER_PAGE

    const p = this._pages.get(i)
    return p || null
  }

  get(index) {
    const j = index & (BITS_PER_PAGE - 1)
    const i = (index - j) / BITS_PER_PAGE

    const p = this._pages.get(i)

    return p ? p.get(j) : false
  }

  set(index, val) {
    const j = index & (BITS_PER_PAGE - 1)
    const i = (index - j) / BITS_PER_PAGE

    let p = this._pages.get(i)

    if (!p && val) {
      const k = Math.floor(i / PAGES_PER_SEGMENT)
      const s = this._segments.get(k) || this._segments.set(k, new RemoteBitfieldSegment(k))
      if (this._maxSegments <= k) this._maxSegments = k + 1

      p = this._pages.set(i, new RemoteBitfieldPage(i, new Uint32Array(WORDS_PER_PAGE), s))
    }

    if (p) p.set(j, val)
  }

  setRange(start, end, val) {
    let j = start & (BITS_PER_PAGE - 1)
    let i = (start - j) / BITS_PER_PAGE

    while (start < end) {
      let p = this._pages.get(i)

      if (!p && val) {
        const k = Math.floor(i / PAGES_PER_SEGMENT)
        const s = this._segments.get(k) || this._segments.set(k, new RemoteBitfieldSegment(k))
        if (this._maxSegments <= k) this._maxSegments = k + 1

        p = this._pages.set(i, new RemoteBitfieldPage(i, new Uint32Array(WORDS_PER_PAGE), s))
      }

      const offset = i * BITS_PER_PAGE
      const last = Math.min(end - offset, BITS_PER_PAGE)
      const range = last - j

      if (p) p.setRange(j, last, val)

      j = 0
      i++
      start += range
    }
  }

  findFirst(val, position) {
    let j = position & (BITS_PER_SEGMENT - 1)
    let i = (position - j) / BITS_PER_SEGMENT

    while (i < this._maxSegments) {
      const s = this._segments.get(i)

      let index = -1

      if (s) index = s.findFirst(val, j)
      else if (!val) index = j

      if (index !== -1) return i * BITS_PER_SEGMENT + index

      j = 0
      i++
    }

    // For the val === false case, we always return at least
    // the 'position', also if nothing was found
    return val ? -1 : Math.max(position, this._maxSegments * BITS_PER_SEGMENT)
  }

  firstSet(position) {
    return this.findFirst(true, position)
  }

  firstUnset(position) {
    return this.findFirst(false, position)
  }

  findLast(val, position) {
    let j = position & (BITS_PER_SEGMENT - 1)
    let i = (position - j) / BITS_PER_SEGMENT

    while (i >= 0) {
      const s = this._segments.get(i)

      let index = -1

      if (s) index = s.findLast(val, j)
      else if (!val) index = j

      if (index !== -1) return i * BITS_PER_SEGMENT + index

      j = BITS_PER_SEGMENT - 1
      i--
    }

    return -1
  }

  lastSet(position) {
    return this.findLast(true, position)
  }

  lastUnset(position) {
    return this.findLast(false, position)
  }

  insert(start, bitfield) {
    if (start % 32 !== 0) return false

    let length = bitfield.byteLength * 8

    let j = start & (BITS_PER_PAGE - 1)
    let i = (start - j) / BITS_PER_PAGE

    while (length > 0) {
      let p = this._pages.get(i)

      if (!p) {
        const k = Math.floor(i / PAGES_PER_SEGMENT)
        const s = this._segments.get(k) || this._segments.set(k, new RemoteBitfieldSegment(k))
        if (this._maxSegments <= k) this._maxSegments = k + 1

        p = this._pages.set(i, new RemoteBitfieldPage(i, new Uint32Array(WORDS_PER_PAGE), s))
      }

      const end = Math.min(j + length, BITS_PER_PAGE)
      const range = end - j

      p.insert(j, bitfield.subarray(0, range / 32))

      bitfield = bitfield.subarray(range / 32)

      j = 0
      i++
      length -= range
    }

    return true
  }

  clear(start, bitfield) {
    if (start % 32 !== 0) return false

    let length = bitfield.byteLength * 8

    let j = start & (BITS_PER_PAGE - 1)
    let i = (start - j) / BITS_PER_PAGE

    while (length > 0) {
      let p = this._pages.get(i)

      if (!p) {
        const k = Math.floor(i / PAGES_PER_SEGMENT)
        const s = this._segments.get(k) || this._segments.set(k, new RemoteBitfieldSegment(k))
        if (this._maxSegments <= k) this._maxSegments = k + 1

        p = this._pages.set(i, new RemoteBitfieldPage(i, new Uint32Array(WORDS_PER_PAGE), s))
      }

      const end = Math.min(j + length, BITS_PER_PAGE)
      const range = end - j

      p.clear(j, bitfield.subarray(0, range / 32))

      bitfield = bitfield.subarray(range / 32)

      j = 0
      i++
      length -= range
    }

    return true
  }
}
/* DEV DOCS
  Every hypercore has one Replicator object managing its connections to other peers.
  There is one Peer object per peer connected to the Hypercore.
  Hypercores do not know about other hypercores, so when a peer is connected to multiple cores, there exists one Peer object per core.

  Hypercore indicates block should be downloaded through methods like Replicator.addRange or Replicator.addBlock
  Hypercore calls Replicator.updateActivity every time a hypercore session opens/closes
  Replicator.updateActivity ensures the Hypercore is downloading blocks as expected
  Replicator keeps track of:
    - Which blocks need to be downloaded (Replicator._blocks)
    - Which blocks currently have inflight requests (Replicator._inflight)

  Blocks are requested from remote peers by Peer objects. The flow is:
    - The replicator's updatePeer method gets called
    - The replicator detects whether the Peer can accept more requests (for example by checking if it's maxed out on inflight blocks)
    - The replicator then tells the Peer what to request (e.g. Peer_requestRange or Peer._requestBlock methods)

  The Peer object is responsible for tracking
    - Which blocks does the Peer have available (tracked in remoteBitfield)
    - Which blocks are you actively looking for from this peer (tracked in missingBlocks)
    - How many blocks are currently inflight (tracked in inflight)
  The Peer uses this information to decide which blocks to request from the peer in response to _requestRange requests and the like.
*/

const b4a = require('b4a')
const safetyCatch = require('safety-catch')
const RandomIterator = require('random-array-iterator')
const flatTree = require('flat-tree')
const Mutex = require('./mutex')
const ReceiverQueue = require('./receiver-queue')
const HotswapQueue = require('./hotswap-queue')
const RemoteBitfield = require('./remote-bitfield')
const { MerkleTree } = require('./merkle-tree')
const w = require('./wants')
const { LocalWants, RemoteWants } = w
const {
  REQUEST_CANCELLED,
  REQUEST_TIMEOUT,
  INVALID_CAPABILITY,
  SNAPSHOT_NOT_AVAILABLE,
  ASSERTION
} = require('hypercore-errors')
const m = require('./messages')
const caps = require('./caps')

const DEFAULT_MAX_INFLIGHT = [16, 512]
const SCALE_LATENCY = 50
const NOT_DOWNLOADING_SLACK = (20000 + Math.random() * 20000) | 0
const MAX_PEERS_UPGRADE = 3
const LAST_BLOCKS = 256
const MAX_RANGES = 64
const MIN_PEER_UPDATE_ALL = 40

const NOT_AVAILABLE = 1
const INVALID_REQUEST = 2
const MAX_INVALID_REQUESTS = 64
const MAX_BACKOFFS = MAX_INVALID_REQUESTS / 2

const PRIORITY = {
  NORMAL: 0,
  HIGH: 1,
  VERY_HIGH: 2,
  CANCELLED: 255 // reserved to mark cancellation
}

class Attachable {
  constructor(replicator) {
    this.replicator = replicator
    this.resolved = false
    this.processing = false
    this.refs = []
    this.wants = null
  }

  addWant(w) {
    if (this.wants === null) this.wants = new Set()
    this.wants.add(w)
  }

  removeWant(w) {
    if (this.wants === null) return
    this.wants.delete(w)
  }

  attach(session) {
    const r = {
      context: this,
      session,
      sindex: 0,
      rindex: 0,
      snapshot: true,
      resolve: null,
      reject: null,
      promise: null,
      timeout: null
    }

    r.sindex = session.push(r) - 1
    r.rindex = this.refs.push(r) - 1
    r.promise = new Promise((resolve, reject) => {
      r.resolve = resolve
      r.reject = reject
    })

    return r
  }

  detach(r, err = null) {
    if (r.context !== this) return false

    this._detach(r)
    this._cancel(r, err)
    this.gc()

    return true
  }

  _detach(r) {
    const rh = this.refs.pop()
    const sh = r.session.pop()

    if (r.rindex < this.refs.length) this.refs[(rh.rindex = r.rindex)] = rh
    if (r.sindex < r.session.length) r.session[(sh.sindex = r.sindex)] = sh

    destroyRequestTimeout(r)
    r.context = null

    return r
  }

  gc() {
    if (this.refs.length > 0 || this.processing) return
    this._unref()

    if (this.wants === null) return

    const wants = this.wants
    this.wants = null

    let update = null

    for (const w of wants) {
      if (w.any) {
        const removed = w.wants.removeAnyRange(w.start, w.length, this)
        if (!removed) continue
        if (update === null) update = new Set()
        update.add(w.wants.peer)
        // TODO: should also use a free list for simplicity
        w.wants.peer.wireUnwant.send(removed)
        continue
      }

      if (w.wants.removeBatch(w.start, this)) {
        if (update === null) update = new Set()
        update.add(w.wants.peer)
      }
    }

    if (update === null) return
    for (const peer of update) {
      this.replicator.updatePeer(peer)
    }
  }

  processed() {
    this.processing = false
    this.gc()
  }

  _cancel(r, err) {
    r.reject(err || REQUEST_CANCELLED())
  }

  _unref() {
    // overwrite me
  }

  resolve(val) {
    this.resolved = true
    while (this.refs.length > 0) {
      this._detach(this.refs[this.refs.length - 1]).resolve(val)
    }
    this.gc()
  }

  reject(err) {
    this.resolved = true
    while (this.refs.length > 0) {
      this._detach(this.refs[this.refs.length - 1]).reject(err)
    }
    this.gc()
  }

  setTimeout(r, ms) {
    destroyRequestTimeout(r)
    r.timeout = setTimeout(onrequesttimeout, ms, r)
  }
}

class BlockRequest extends Attachable {
  constructor(replicator, tracker, index, priority, force) {
    super(replicator)

    this.index = index
    this.priority = priority
    this.inflight = []
    this.queued = false
    this.hotswap = null
    this.tracker = tracker
    this.force = force
  }

  _unref() {
    if (this.resolved) return
    this.queued = false

    for (const req of this.inflight) {
      req.peer._cancelRequest(req)
    }

    this.tracker.remove(this.index)
    removeHotswap(this)
  }
}

class RangeBatchRequest extends Attachable {
  constructor(replicator, batch) {
    super(replicator)

    this.batch = batch
    this.index = 0
  }
}

class RangeRequest extends Attachable {
  constructor(replicator, ranges, start, end, linear, blocks) {
    super(replicator)

    this.start = start
    this.end = end
    this.linear = linear
    this.blocks = blocks
    this.ranges = ranges
    this.batches = []

    ranges.push(this)

    if (this.end === -1) {
      this.replicator._alwaysLatestBlock++
    }

    // As passed by the user, immut
    this.userStart = start
    this.userEnd = end
  }

  addBatch(batch) {
    for (let i = 0; i < this.batches.length; i++) {
      const b = this.batches[i]
      if (b.batch === batch) return false
    }
    const b = new RangeBatchRequest(this.replicator, batch)
    b.index = this.batches.length
    this.batches.push(b)
    return true
  }

  removeBatch(batch) {
    const head = this.batches.pop()

    if (head !== batch) {
      head.index = batch.index
      this.batches[head.index] = head
    }

    batch.gc()
  }

  _unref() {
    const rangeIndex = this.ranges.indexOf(this)
    if (rangeIndex === -1) return

    const h = this.ranges.pop()
    if (h !== this) {
      this.ranges[rangeIndex] = h
    }

    if (!this.resolved && this.replicator._updatesRunning) {
      this.replicator._updatesQueued = true
    }

    if (this.end === -1) {
      this.replicator._alwaysLatestBlock--
    }

    while (this.batches.length) {
      this.batches.pop().gc()
    }
  }

  _cancel(r) {
    r.resolve(false)
  }
}

class UpgradeRequest extends Attachable {
  constructor(replicator, fork, length) {
    super(replicator)

    this.fork = fork
    this.length = length
    this.inflight = []
  }

  _unref() {
    if (this.resolved) return
    if (this.replicator.eagerUpgrade === true || this.inflight.length > 0) return
    this.replicator._upgrade = null
  }

  _cancel(r) {
    r.resolve(false)
  }
}

class SeekRequest extends Attachable {
  constructor(replicator, seeks, seeker) {
    super(replicator)

    this.seeker = seeker
    this.inflight = []
    this.seeks = seeks
  }

  _unref() {
    if (this.resolved) return
    if (this.inflight.length > 0) return
    const i = this.seeks.indexOf(this)
    if (i === -1) return
    const h = this.seeks.pop()
    if (i < this.seeks.length) this.seeks[i] = h
  }
}

class ForkRequest extends Attachable {
  constructor(replicator, fork) {
    super(replicator)
    this.fork = fork
    this.inflight = []
    this.batch = null
  }
}

class InflightTracker {
  constructor() {
    this._requests = []
    this._free = []
    this._active = 0
  }

  get idle() {
    return this._active === 0
  }

  *[Symbol.iterator]() {
    for (const req of this._requests) {
      if (req !== null) yield req
    }
  }

  add(req) {
    const id = this._free.length ? this._free.pop() : this._requests.push(null)
    req.id = id
    this._requests[id - 1] = req
    this._active++
    return req
  }

  get(id) {
    return id <= this._requests.length ? this._requests[id - 1] : null
  }

  remove(id, roundtrip) {
    if (id > this._requests.length) return
    if (this._requests[id - 1]) this._active--
    this._requests[id - 1] = null
    if (roundtrip === true) this._free.push(id)
  }

  reusable(id) {
    this._free.push(id)
  }
}

class BlockTracker {
  constructor(replicator) {
    this._replicator = replicator
    this._map = new Map()
  }

  [Symbol.iterator]() {
    return this._map.values()
  }

  isEmpty() {
    return this._map.size === 0
  }

  has(index) {
    return this._map.has(index)
  }

  get(index) {
    return this._map.get(index) || null
  }

  add(index, priority, force) {
    let b = this._map.get(index)
    if (b) return b

    b = new BlockRequest(this._replicator, this, index, priority, force)
    this._map.set(index, b)

    return b
  }

  remove(index) {
    const b = this.get(index)
    this._map.delete(index)
    return b
  }
}

class RoundtripQueue {
  constructor() {
    this.queue = []
    this.tick = 0
  }

  clear() {
    const ids = new Array(this.queue.length)
    for (let i = 0; i < ids.length; i++) {
      ids[i] = this.queue[i][1]
    }

    this.queue = []

    return ids
  }

  add(id) {
    this.queue.push([++this.tick, id])
  }

  flush(tick) {
    let flushed = null

    for (let i = 0; i < this.queue.length; i++) {
      if (this.queue[i][0] > tick) break
      if (flushed === null) flushed = []
      flushed.push(this.queue[i][1])
    }

    if (flushed !== null) this.queue.splice(0, flushed.length)
    return flushed
  }
}

class ProofRequest {
  constructor(msg, proof, block, manifest) {
    this.msg = msg
    this.proof = proof
    this.block = block
    this.manifest = manifest
  }

  async fulfill() {
    if (this.proof === null) return null

    const [proof, block] = await Promise.all([this.proof.settle(), this.block])

    if (this.manifest) proof.manifest = this.manifest
    if (!block && proof.block) return null

    if (block) proof.block.value = block
    return proof
  }
}

class Peer {
  constructor(replicator, protomux, channel, inflightRange) {
    this.core = replicator.core
    this.replicator = replicator
    this.stream = protomux.stream
    this.protomux = protomux
    this.remotePublicKey = this.stream.remotePublicKey
    this.remoteSupportsSeeks = false
    this.inflightRange = inflightRange
    this.fullyDownloadedSignaled = 0

    this.paused = false
    this.removed = false

    this.channel = channel
    this.channel.userData = this

    this.wireSync = this.channel.messages[0]
    this.wireRequest = this.channel.messages[1]
    this.wireCancel = this.channel.messages[2]
    this.wireData = this.channel.messages[3]
    this.wireNoData = this.channel.messages[4]
    this.wireWant = this.channel.messages[5]
    this.wireUnwant = this.channel.messages[6]
    this.wireBitfield = this.channel.messages[7]
    this.wireRange = this.channel.messages[8]
    this.wireExtension = this.channel.messages[9]

    // Same stats as replicator, but for this specific peer
    this.stats = {
      wireSync: { tx: 0, rx: 0 },
      wireRequest: { tx: 0, rx: 0 },
      wireCancel: { tx: 0, rx: 0 },
      wireData: { tx: 0, rx: 0 },
      wireWant: { tx: 0, rx: 0 },
      wireUnwant: { tx: 0, rx: 0 },
      wireBitfield: { tx: 0, rx: 0 },
      wireRange: { tx: 0, rx: 0 },
      wireExtension: { tx: 0, rx: 0 },
      hotswaps: 0,
      invalidData: 0,
      invalidRequests: 0,
      backoffs: 0,
      notAvailableBackoffs: 0
    }

    this.receiverQueue = new ReceiverQueue()
    this.receiverBusy = false

    // most often not used, so made on demand
    this.roundtripQueue = null

    this.inflight = 0
    this.dataProcessing = 0

    this.canUpgrade = true

    this.needsSync = false
    this.syncsProcessing = 0
    this.lastUpgradableLength = 0
    this.lastUpgradableFork = 0

    this._remoteContiguousLength = 0

    // TODO: tweak pipelining so that data sent BEFORE remoteOpened is not cap verified!
    // we might wanna tweak that with some crypto, ie use the cap to encrypt it...
    // or just be aware of that, to only push non leaky data

    this.remoteOpened = false
    this.remoteBitfield = new RemoteBitfield()
    this.missingBlocks = new RemoteBitfield()

    this.pushedLength = 0

    this.remoteFork = 0
    this.remoteLength = 0
    this.remoteCanUpgrade = false
    this.remoteUploading = true
    this.remoteDownloading = true
    this.remoteSynced = false
    this.remoteHasManifest = false
    this.remoteAllowPush = false
    this.remoteRequests = new Map()

    this.segmentsWanted = new Set()
    this.broadcastedNonSparse = false

    this.wants = new LocalWants(this)
    this.remoteWants = new RemoteWants(this)

    this.lengthAcked = 0

    this.extensions = new Map()
    this.lastExtensionSent = ''
    this.lastExtensionRecv = ''

    replicator._ifAvailable++
    replicator._active++
  }

  get remoteContiguousLength() {
    return this.remoteBitfield.findFirst(false, this._remoteContiguousLength)
  }

  getMaxInflight() {
    const stream = this.stream.rawStream
    if (!stream.udx) return Math.min(this.inflightRange[1], this.inflightRange[0] * 3)

    const scale =
      stream.rtt <= SCALE_LATENCY
        ? 1
        : (stream.rtt / SCALE_LATENCY) * Math.min(1, 2 / this.replicator.peers.length)
    return Math.max(
      this.inflightRange[0],
      Math.round(Math.min(this.inflightRange[1], this.inflightRange[0] * scale))
    )
  }

  getMaxHotswapInflight() {
    const inf = this.getMaxInflight()
    return Math.max(16, inf / 2)
  }

  signalUpgrade() {
    if (this._shouldUpdateCanUpgrade() === true) this._updateCanUpgradeAndSync()
    else this.sendSync()
  }

  _markInflight(index) {
    this.missingBlocks.set(index, false)
  }

  broadcastRange(start, length, drop) {
    if (!this.isActive()) return

    if (drop) this._unclearLocalRange(start, length)
    else this._clearLocalRange(start, length)

    const fullyContig = this.core.header.hints.contiguousLength === this.core.state.length

    if (start + LAST_BLOCKS < this.core.state.length && !drop && !fullyContig) {
      if (!this.remoteWants.hasRange(start, length)) return
    }

    let force = false
    if (fullyContig && !drop) {
      start = 0
      length = this.core.state.length

      // Always send the broadcast when we switch from sparse to fully contiguous, so remote knows too
      if (this.fullyDownloadedSignaled < length) {
        this.fullyDownloadedSignaled = length
        force = true
      }
    }

    // TODO: consider also adding early-returns on the drop===true case
    if (!force && !drop) {
      // No need to broadcast if the remote already has this range

      if (this._remoteContiguousLength >= start + length) return

      if (length === 1) {
        if (this.remoteBitfield.get(start)) return
      } else {
        if (this.remoteBitfield.firstUnset(start) >= start + length) return
      }
    }

    this.wireRange.send({
      drop,
      start,
      length
    })
    incrementTx(this.stats.wireRange, this.replicator.stats.wireRange)
  }

  extension(name, message) {
    this.wireExtension.send({ name: name === this.lastExtensionSent ? '' : name, message })
    incrementTx(this.stats.wireExtension, this.replicator.stats.wireExtension)
    this.lastExtensionSent = name
  }

  onextension(message) {
    const name = message.name || this.lastExtensionRecv
    this.lastExtensionRecv = name
    const ext = this.extensions.get(name)
    if (ext) {
      ext._onmessage({ start: 0, end: message.message.byteLength, buffer: message.message }, this)
    }
  }

  sendSync() {
    // Skip syncs if in repair mode
    if (this.core._repairMode) return

    if (this.syncsProcessing !== 0) {
      this.needsSync = true
      return
    }

    if (this.core.state.fork !== this.remoteFork) {
      this.canUpgrade = false
    }

    this.needsSync = false

    this.wireSync.send({
      fork: this.core.state.fork,
      length: this.core.state.length,
      remoteLength: this.core.state.fork === this.remoteFork ? this.remoteLength : 0,
      canUpgrade: this.canUpgrade,
      uploading: true,
      downloading: this.replicator.isDownloading(),
      hasManifest: !!this.core.header.manifest && this.core.compat === false,
      allowPush: this.replicator.isAllowingPush()
    })
    incrementTx(this.stats.wireSync, this.replicator.stats.wireSync)
  }

  onopen({ seeks, capability }) {
    const expected = caps.replicate(
      this.stream.isInitiator === false,
      this.core.key,
      this.stream.handshakeHash
    )

    if (b4a.equals(capability, expected) !== true) {
      // TODO: change this to a rejection instead, less leakage
      throw INVALID_CAPABILITY('Remote sent an invalid replication capability')
    }

    if (this.remoteOpened === true) return
    this.remoteOpened = true
    this.remoteSupportsSeeks = seeks

    this.protomux.cork()

    this.sendSync()

    const contig = Math.min(this.core.state.length, this.core.header.hints.contiguousLength)
    if (contig > 0) {
      this.broadcastRange(0, contig, false)

      if (contig === this.core.state.length) {
        this.broadcastedNonSparse = true
      }
    }

    this.replicator._ifAvailable--
    this.replicator._addPeer(this)

    this.protomux.uncork()

    this.core.checkIfIdle()
  }

  onclose(isRemote) {
    // we might have signalled to the remote that we are done (ie not downloading) and the remote might agree on that
    // if that happens, the channel might be closed by the remote. if so just renegotiate it.
    // TODO: add a CLOSE_REASON to mux to we can make this cleaner...
    const reopen =
      isRemote === true &&
      this.remoteOpened === true &&
      this.remoteDownloading === false &&
      this.remoteUploading === true &&
      this.replicator.downloading === true

    if (this.remoteOpened === false) {
      this.replicator._ifAvailable--
      this.replicator.queueUpdateAll()
      return
    }

    this.remoteOpened = false
    this.removed = true
    this.remoteRequests.clear() // cancel all
    this.receiverQueue.clear()

    if (this.roundtripQueue !== null) {
      for (const id of this.roundtripQueue.clear()) this.replicator._inflight.reusable(id)
    }

    this.replicator._removePeer(this)

    if (reopen) {
      this.replicator._makePeer(this.protomux)
    }
  }

  closeIfIdle() {
    if (this.remoteDownloading === false && this.replicator.isDownloading() === false) {
      // idling, shut it down...
      this.channel.close()
      return true
    }

    return false
  }

  async onsync(msg) {
    const {
      fork,
      length,
      remoteLength,
      canUpgrade,
      uploading,
      downloading,
      hasManifest,
      allowPush
    } = msg

    const lengthChanged = length !== this.remoteLength
    const sameFork = fork === this.core.state.fork

    this.remoteSynced = true
    this.remoteFork = fork
    this.remoteLength = length
    this.remoteCanUpgrade = canUpgrade
    this.remoteUploading = uploading
    this.remoteDownloading = downloading
    this.remoteHasManifest = hasManifest
    this.remoteAllowPush = allowPush

    if (this.closeIfIdle()) return

    this.lengthAcked = sameFork ? remoteLength : 0
    this.syncsProcessing++

    this.replicator._updateFork(this)

    if (this.remoteLength > this.core.state.length && this.lengthAcked === this.core.state.length) {
      if (this.replicator._addUpgradeMaybe() !== null) this._update()
    }

    const upgrade =
      lengthChanged === false || sameFork === false
        ? this.canUpgrade && sameFork
        : await this._canUpgrade(length, fork)

    if (length === this.remoteLength && fork === this.core.state.fork) {
      this.canUpgrade = upgrade
      if (upgrade) {
        this.lastUpgradableFork = fork
        this.lastUpgradableLength = length
      }
    }

    if (--this.syncsProcessing !== 0) return // ie not latest

    if (
      this.needsSync === true ||
      (this.core.state.fork === this.remoteFork && this.core.state.length > this.remoteLength)
    ) {
      this.signalUpgrade()
    }

    this._update()
  }

  _shouldUpdateCanUpgrade() {
    return (
      this.core.state.fork === this.remoteFork &&
      this.core.state.length > this.remoteLength &&
      this.canUpgrade === false &&
      this.syncsProcessing === 0
    )
  }

  async _updateCanUpgradeAndSync() {
    const { length, fork } = this.core.state

    const remoteLength = this.remoteLength
    const remoteFork = this.remoteFork
    const canUpgrade = await this._canUpgrade(this.remoteLength, this.remoteFork)

    if (
      this.syncsProcessing > 0 ||
      length !== this.core.state.length ||
      fork !== this.core.state.fork
    ) {
      return
    }
    if (remoteLength !== this.remoteLength || remoteFork !== this.remoteFork) {
      return
    }
    if (canUpgrade === this.canUpgrade) {
      return
    }

    this.canUpgrade = canUpgrade
    if (canUpgrade) {
      this.lastUpgradableLength = this.remoteLength
      this.lastUpgradableFork = this.remoteFork
    }

    this.sendSync()
  }

  // Safe to call in the background - never fails
  async _canUpgrade(remoteLength, remoteFork) {
    if (remoteFork !== this.core.state.fork) return false

    if (remoteLength === 0) return true
    if (remoteLength >= this.core.state.length) return false

    try {
      // Rely on caching to make sure this is cheap...
      const canUpgrade = await MerkleTree.upgradeable(this.core.state, remoteLength)

      if (remoteFork !== this.core.state.fork) return false

      return canUpgrade
    } catch {
      return false
    }
  }

  async _getProof(batch, msg, pushing) {
    let block = null

    if (msg.block) {
      const index = msg.block.index

      if (!pushing && (msg.fork !== this.core.state.fork || !this.core.bitfield.get(index))) {
        return new ProofRequest(msg, null, null, null)
      }

      block = batch.getBlock(index)
      block.catch(noop)
    }

    const manifest = msg.manifest && !this.core.compat ? this.core.header.manifest : null

    try {
      const proof = await MerkleTree.proof(this.core.state, batch, msg)
      return new ProofRequest(msg, proof, block, manifest)
    } catch (err) {
      batch.destroy()

      this.replicator._oninvalidrequest(err, msg, this)

      if (this.stats.invalidRequests >= MAX_INVALID_REQUESTS) throw err

      await backoff(this.stats.invalidRequests)
      return null
    }
  }

  async onrequest(msg) {
    const size = this.remoteRequests.size
    this.remoteRequests.set(msg.id, msg)

    // if size didnt change -> id overwrite -> old one is deleted, cancel current and re-add
    if (size === this.remoteRequests.size) {
      this._cancel(msg.id)
      this.remoteRequests.set(msg.id, msg)
    }

    if (!this.protomux.drained || this.receiverQueue.length) {
      this.receiverQueue.push(msg)
      return
    }

    if (this.replicator.destroyed) return

    await this._handleRequest(msg)
  }

  oncancel(msg) {
    this._cancel(msg.request)
  }

  _cancel(id) {
    this.remoteRequests.delete(id)
    this.receiverQueue.delete(id)
  }

  ondrain() {
    return this._handleRequests()
  }

  async _handleRequests() {
    if (this.receiverBusy || this.replicator.destroyed) return
    this.receiverBusy = true
    this.protomux.cork()

    while (
      this.remoteOpened &&
      this.protomux.drained &&
      this.receiverQueue.length > 0 &&
      !this.removed &&
      this.core.closing === null
    ) {
      const msg = this.receiverQueue.shift()
      await this._handleRequest(msg)
    }

    this.protomux.uncork()
    this.receiverBusy = false
  }

  async push(index) {
    if (this.core.closing !== null) return
    if (!this.remoteAllowPush) return
    if (!this.remoteDownloading) return
    if (this.core.state.length <= index) return
    if (this.core.state.fork !== this.remoteFork) return
    if (this.remoteBitfield.get(index)) return

    const msg = {
      id: 0,
      fork: this.core.state.fork,
      block: null,
      hash: null,
      seek: null,
      upgrade: null,
      manifest: this.remoteLength === 0,
      priority: 0
    }

    await this.replicator._txLock.lock()
    try {
      const remoteLength = Math.max(this.remoteLength, this.pushedLength)

      msg.block = {
        index,
        nodes: MerkleTree.maxMissingNodes(2 * index, remoteLength)
      }

      if (index >= remoteLength) {
        msg.upgrade = {
          start: remoteLength,
          length: this.core.state.length - remoteLength
        }
      }

      let req = null
      const batch = this.core.storage.read(this.core.state.fork)
      try {
        req = await this._getProof(batch, msg, true)
      } catch (err) {
        this.replicator._oninvalidrequest(err, msg, this)
        return
      }

      if (req === null) return
      batch.tryFlush()

      await this._fulfillRequest(req, true)
    } finally {
      this.replicator._txLock.unlock()
    }
  }

  async _handleRequest(msg) {
    const locked = !!msg.upgrade && this.remoteAllowPush

    if (locked) {
      // ensure contig push proofs
      await this.replicator._txLock.lock()
      const end = msg.upgrade.start + msg.upgrade.length

      if (this.pushedLength < end) {
        msg.upgrade.start = this.pushedLength
        msg.upgrade.length = end - this.pushedLength
      }
    }

    try {
      const batch = this.core.storage.read(this.core.state.fork)

      // TODO: could still be answerable if (index, fork) is an ancestor of the current fork
      const req =
        msg.fork === this.core.state.fork
          ? await this._getProof(batch, msg, false)
          : new ProofRequest(msg, null, null, null)

      if (req === null) {
        this.wireNoData.send({ request: msg.id, reason: INVALID_REQUEST })
        return
      }

      batch.tryFlush()

      await this._fulfillRequest(req, false)
    } finally {
      if (locked) this.replicator._txLock.unlock()
    }
  }

  async _fulfillRequest(req, pushing) {
    if (this.core._repairMode) {
      // if cancelled do not reply
      if (this.remoteRequests.get(req.msg.id) !== req.msg) {
        return
      }

      // sync from now on, so safe to delete from the map
      this.remoteRequests.delete(req.msg.id)

      // If cutting off the fulfillment, ensure promise is caught.
      // While repairing merkle tree nodes can be `nil` so can throw.
      req.fulfill().catch(safetyCatch)
      return
    }

    const proof = await req.fulfill()

    if (!pushing) {
      // if cancelled do not reply
      if (this.remoteRequests.get(req.msg.id) !== req.msg) {
        return
      }

      // sync from now on, so safe to delete from the map
      this.remoteRequests.delete(req.msg.id)
    } else if (!this.remoteAllowPush) {
      // if pushing but remote disabled it, just drop it
      return
    }

    if (!this.isActive() && proof.block !== null) {
      return
    }

    if (proof === null) {
      if (req.msg.manifest && this.core.header.manifest) {
        const manifest = this.core.header.manifest
        this.wireData.send({
          request: req.msg.id,
          fork: this.core.state.fork,
          block: null,
          hash: null,
          seek: null,
          upgrade: null,
          manifest
        })
        incrementTx(this.stats.wireData, this.replicator.stats.wireData)
        return
      }

      this.wireNoData.send({ request: req.msg.id, reason: NOT_AVAILABLE })
      return
    }

    if (proof.block !== null) {
      this.replicator._onupload(proof.block.index, proof.block.value.byteLength, this)
    }

    if (proof.upgrade) {
      const remoteLength = MerkleTree.upgradeLength(proof.upgrade)
      if (remoteLength > this.pushedLength) this.pushedLength = remoteLength
    }

    this.wireData.send({
      request: req.msg.id,
      fork: req.msg.fork,
      block: proof.block,
      hash: proof.hash,
      seek: proof.seek,
      upgrade: proof.upgrade,
      manifest: proof.manifest
    })
    incrementTx(this.stats.wireData, this.replicator.stats.wireData)
  }

  _cancelRequest(req) {
    if (req.priority === PRIORITY.CANCELLED) return
    // mark as cancelled also and avoid re-entry
    req.priority = PRIORITY.CANCELLED

    this.inflight--
    this.replicator._requestDone(req.id, false)

    // clear inflight state
    if (isBlockRequest(req)) this.replicator._unmarkInflight(req.block.index)
    if (isUpgradeRequest(req)) this.replicator._clearInflightUpgrade(req)

    if (this.roundtripQueue === null) this.roundtripQueue = new RoundtripQueue()
    this.roundtripQueue.add(req.id)
    this.wireCancel.send({ request: req.id })
    incrementTx(this.stats.wireCancel, this.replicator.stats.wireCancel)
  }

  _checkIfConflict() {
    this.paused = true

    const length = Math.min(this.core.state.length, this.remoteLength)
    if (length === 0) return // pause and ignore

    this.wireRequest.send({
      id: 0, // TODO: use an more explicit id for this eventually...
      fork: this.remoteFork,
      block: null,
      hash: null,
      seek: null,
      upgrade: {
        start: 0,
        length
      }
    })

    incrementTx(this.stats.wireRequest, this.replicator.stats.wireRequest)
  }

  _unmarkInflightBlockRequest(req, data) {
    if (isBlockRequest(req)) this.replicator._unmarkInflight(req.block.index)
    else if (data.block && !req) this.replicator._unmarkInflight(data.block.index)
  }

  async ondata(data) {
    if (data.request !== 0 && !data.upgrade) return this._handleData(data)

    await this.replicator._rxLock.lock()
    try {
      await this._handleData(data)
    } finally {
      this.replicator._rxLock.unlock()
    }
  }

  async _handleData(data) {
    // always allow a fork conflict proof to be sent
    if (data.request === 0 && data.upgrade && data.upgrade.start === 0) {
      if (await this.core.checkConflict(data, this)) return
      this.paused = false
    }

    const req = data.request > 0 ? this.replicator._inflight.get(data.request) : null
    const reorg = data.fork > this.core.state.fork

    // no push atm, TODO: check if this satisfies another pending request
    // allow reorg pushes tho as those are not written to storage so we'll take all the help we can get
    if (req === null && reorg === false && !this.replicator.allowPush) {
      return
    }

    // Skip accepting data if in repair mode to avoid corruption
    if (this.core._repairMode) {
      this.replicator._onnodata(this, req, NOT_AVAILABLE)
      return
    }

    if (req === null && reorg === false && data.block) {
      // mark this as inflight to avoid parallel requests
      this.replicator._markInflight(data.block.index)
    }

    if (req !== null) {
      if (req.peer !== this) return
      this._onrequestroundtrip(req)
    }

    try {
      if (reorg === true) return await this.replicator._onreorgdata(this, req, data)
    } catch (err) {
      safetyCatch(err)
      this._unmarkInflightBlockRequest(req, data)

      this.paused = true
      this.replicator._oninvaliddata(err, req, data, this)
      return
    }

    this.dataProcessing++
    if (isBlockRequest(req)) this.replicator._markProcessing(req.block.index)

    try {
      if (!this.replicator._matchingRequest(req, data) || !(await this.core.verify(data, this))) {
        this.replicator._onnodata(this, req, 0)
        return
      }
    } catch (err) {
      safetyCatch(err)
      this._unmarkInflightBlockRequest(req, data)

      if (err.code === 'WRITE_FAILED') {
        // For example, we don't want to keep pulling data when storage is full
        // TODO: notify the user somehow
        this.paused = true
        return
      }

      if ((this.core.closing !== null || this.core.closed) && !isCriticalError(err)) return

      if (err.code !== 'INVALID_OPERATION') {
        // might be a fork, verify
        this._checkIfConflict()
      }

      // if push mode, we MIGHT get an occasional bad message
      // no need to mega bail for that. TODO: rate limit instead as a general thing
      if (!this.replicator.allowPush) {
        this.paused = true
      }

      this.replicator._onnodata(this, req, 0)
      this.replicator._oninvaliddata(err, req, data, this)
      return
    } finally {
      if (isBlockRequest(req)) this.replicator._markProcessed(req.block.index)
      this.dataProcessing--
    }

    this.replicator._ondata(this, req, data)

    if (this._shouldUpdateCanUpgrade() === true) {
      this._updateCanUpgradeAndSync()
    }
  }

  onnodata({ request, reason }) {
    const req = request > 0 ? this.replicator._inflight.get(request) : null

    if (req === null || req.peer !== this) {
      this.replicator.queueUpdateAll()
      return
    }

    this._onrequestroundtrip(req)
    this.replicator._onnodata(this, req, reason)
  }

  _onrequestroundtrip(req) {
    if (req.priority === PRIORITY.CANCELLED) return
    // to avoid re-entry we also just mark it as cancelled
    req.priority = PRIORITY.CANCELLED

    this.inflight--
    this.replicator._requestDone(req.id, true)
    if (this.roundtripQueue === null) return
    const flushed = this.roundtripQueue.flush(req.rt)
    if (flushed === null) return
    for (const id of flushed) this.replicator._inflight.reusable(id)
  }

  onwant(range) {
    if (!this.remoteWants.add(range)) return
    this.replicator._onwant(this, range.start, range.length, range.any)
  }

  onunwant(range) {
    this.remoteWants.remove(range)
  }

  onbitfield({ start, bitfield }) {
    if (start < this._remoteContiguousLength) this._remoteContiguousLength = start // bitfield is always the truth
    this.remoteBitfield.insert(start, bitfield)
    this.missingBlocks.insert(start, bitfield)
    this._clearLocalRange(start, bitfield.byteLength * 8)
    this._update()
  }

  _clearLocalRange(start, length) {
    const bitfield = this.core.skipBitfield === null ? this.core.bitfield : this.core.skipBitfield

    if (length === 1) {
      this.missingBlocks.set(start, this._remoteHasBlock(start) && !bitfield.get(start))
      return
    }

    const contig = Math.min(this.core.state.length, this.core.header.hints.contiguousLength)

    if (start + length < contig) {
      this.missingBlocks.setRange(start, contig, false)
      return
    }

    const rem = start & 32767
    if (rem > 0) {
      start -= rem
      length += rem
    }

    const end = start + Math.min(length, this.core.state.length)
    while (start < end) {
      const local = bitfield.getBitfield(start)

      if (local && local.bitfield) {
        this.missingBlocks.clear(start, local.bitfield)
      }

      start += 32768
    }
  }

  _resetMissingBlock(index) {
    const bitfield = this.core.skipBitfield === null ? this.core.bitfield : this.core.skipBitfield
    this.missingBlocks.set(index, this._remoteHasBlock(index) && !bitfield.get(index))
  }

  _unclearLocalRange(start, length) {
    if (length === 1) {
      this._resetMissingBlock(start)
      return
    }

    const rem = start & (RemoteBitfield.BITS_PER_SEGMENT - 1)
    if (rem > 0) {
      start -= rem
      length += rem
    }

    const fixedStart = start

    const end = start + Math.min(length, this.remoteLength)
    while (start < end) {
      const remote = this.remoteBitfield.getBitfield(start)
      if (remote && remote.bitfield) {
        this.missingBlocks.insert(start, remote.bitfield)
      }

      start += RemoteBitfield.BITS_PER_SEGMENT
    }

    this._clearLocalRange(fixedStart, length)
  }

  async onrange({ drop, start, length }) {
    if (this.core.closing !== null) return

    const has = drop === false

    if (drop === true && start < this._remoteContiguousLength) {
      this._remoteContiguousLength = start
    }

    if (start === 0 && drop === false) {
      if (length > this._remoteContiguousLength) {
        this._remoteContiguousLength = length
        if (
          this.remoteFork === this.core.state.fork &&
          length > this.core.header.hints.remoteContiguousLength
        ) {
          this.core.updateRemoteContiguousLength(length)
        }
      }
    } else if (length === 1) {
      const bitfield = this.core.skipBitfield === null ? this.core.bitfield : this.core.skipBitfield
      this.remoteBitfield.set(start, has)
      this.missingBlocks.set(start, has && !bitfield.get(start))
    } else {
      const rangeStart = this.remoteBitfield.findFirst(!has, start)
      const rangeEnd = length + start

      if (rangeStart !== -1 && rangeStart < rangeEnd) {
        this.remoteBitfield.setRange(rangeStart, rangeEnd, has)
        this.missingBlocks.setRange(rangeStart, rangeEnd, has)
        if (has) this._clearLocalRange(rangeStart, rangeEnd - rangeStart)
      }
    }

    if (this.core.hintsChanged) await this.core.flushHints()
    if (drop === false) this._update()

    this.core.emitRemoteContiguousLength()
  }

  onreorghint() {
    // TODO
  }

  _update() {
    // TODO: if this is in a batch or similar it would be better to defer it
    // we could do that with nextTick/microtick mb? (combined with a property on the session to signal read buffer mb)
    this.replicator.updatePeer(this)
  }

  async _onconflict() {
    this.protomux.cork()
    if (this.remoteLength > 0 && this.core.state.fork === this.remoteFork) {
      await this.onrequest({
        id: 0,
        fork: this.core.state.fork,
        block: null,
        hash: null,
        seek: null,
        upgrade: {
          start: 0,
          length: Math.min(this.core.state.length, this.remoteLength)
        }
      })
    }
    this.channel.close()
    this.protomux.uncork()
  }

  _makeRequest(needsUpgrade, priority, minLength) {
    if (needsUpgrade === true && this.replicator._shouldUpgrade(this) === false) {
      return null
    }

    // ensure that the remote has signalled they have the length we request
    if (this.remoteLength < minLength) {
      return null
    }

    if (needsUpgrade === false && this.replicator._autoUpgrade(this) === true) {
      needsUpgrade = true
    }

    return {
      peer: this,
      rt: this.roundtripQueue === null ? 0 : this.roundtripQueue.tick,
      id: 0,
      fork: this.remoteFork,
      block: null,
      hash: null,
      seek: null,
      upgrade:
        needsUpgrade === false
          ? null
          : { start: this.core.state.length, length: this.remoteLength - this.core.state.length },
      // remote manifest check can be removed eventually...
      manifest: this.core.header.manifest === null && this.remoteHasManifest === true,
      priority,
      timestamp: Date.now(),
      elapsed: 0
    }
  }

  _requestManifest() {
    const req = this._makeRequest(false, 0, 0)
    if (req === null) return

    this._send(req)
  }

  _includeLastBlock() {
    if (this.replicator._alwaysLatestBlock === 0) return null

    const index = this.remoteLength - 1

    // only valid if its an OOB get
    if (index < this.core.state.length) return null

    // do the normal checks
    if (!this._remoteHasBlock(index)) return null
    if (!this._canRequest(index, false)) return null

    // atm we only ever do one upgrade request in parallel, if that changes
    // then we should add some check here for inflights to avoid over requesting
    const b = this.replicator._blocks.add(index, PRIORITY.NORMAL, false)
    return b
  }

  _requestUpgrade(u) {
    if (this.replicator.pushOnly) return false

    const req = this._makeRequest(true, 0, 0)
    if (req === null) return false

    const b = this._includeLastBlock()

    if (b) this._sendBlockRequest(req, b)
    else this._send(req)

    return true
  }

  _requestSeek(s) {
    // if replicator is updating the seeks, bail and wait for it to drain
    if (this.replicator._updatingSeeks) return false
    if (this.replicator.pushOnly) return false

    const { length, fork } = this.core.state

    if (fork !== this.remoteFork) return false

    if (s.seeker.start >= length) {
      const req = this._makeRequest(true, 0, 0)

      // We need an upgrade for the seek, if non can be provided, skip
      if (req === null) return false

      req.seek = this.remoteSupportsSeeks
        ? { bytes: s.seeker.bytes, padding: s.seeker.padding }
        : null

      s.inflight.push(req)
      this._send(req)

      return true
    }

    const len = s.seeker.end - s.seeker.start
    const off = s.seeker.start + Math.floor(Math.random() * len)

    for (let i = 0; i < len; i++) {
      let index = off + i
      if (index > s.seeker.end) index -= len

      if (this._remoteHasBlock(index) === false) continue
      if (this.core.bitfield.get(index) === true) continue
      if (!this._canRequest(index, false)) continue

      // Check if this block is currently inflight - if so pick another
      const b = this.replicator._blocks.get(index)
      if (b !== null && b.inflight.length > 0) continue

      // Block is not inflight, but we only want the hash, check if that is inflight
      const h = this.replicator._hashes.add(index, PRIORITY.NORMAL, false)
      if (h.inflight.length > 0) continue

      const req = this._makeRequest(false, h.priority, index + 1)
      if (req === null) continue

      const nodes = flatTree.depth(s.seeker.start + s.seeker.end - 1)

      req.hash = { index: 2 * index, nodes }
      req.seek = this.remoteSupportsSeeks
        ? { bytes: s.seeker.bytes, padding: s.seeker.padding }
        : null

      s.inflight.push(req)
      h.inflight.push(req)
      this._send(req)

      return true
    }

    this._maybeWantAnyRange(s.seeker.start, len, s)
    return false
  }

  _canRequest(index, force) {
    if (!(index >= 0)) throw ASSERTION('bad index to _canRequest: ' + index)

    if (!force && this.replicator.pushOnly) {
      return false
    }

    if (this.remoteLength >= this.core.state.length) {
      return true
    }

    if (index < this.lastUpgradableLength && this.lastUpgradableFork === this.core.state.fork) {
      return true
    }

    if (this.canUpgrade && this.syncsProcessing === 0) {
      return true
    }

    return false
  }

  _remoteHasBlock(index) {
    return index < this._remoteContiguousLength || this.remoteBitfield.get(index) === true
  }

  _sendBlockRequest(req, b) {
    if (this.core._repairMode) return

    req.block = { index: b.index, nodes: 0 }
    this.replicator._markInflight(b.index)

    b.inflight.push(req)
    this.replicator.hotswaps.add(b)
    this._send(req)
  }

  _requestBlock(b) {
    const { length, fork } = this.core.state

    if (this._remoteHasBlock(b.index) === false || fork !== this.remoteFork) {
      if (!this.core.bitfield.get(b.index)) this._maybeWant(b.index, b)
      return false
    }

    if (!this._canRequest(b.index, b.force)) return false

    const req = this._makeRequest(b.index >= length, b.priority, b.index + 1)
    if (req === null) return false

    this._sendBlockRequest(req, b)

    return true
  }

  _maybeWant(index, handle) {
    if (index < this.remoteContiguousLength) return

    const res = this.wants.add(index, handle)
    if (!res) return

    const batch = res.want && res.unwant
    if (batch) this.protomux.cork()

    if (res.unwant) {
      this.wireUnwant.send(res.unwant)
      incrementTx(this.stats.wireUnwant, this.replicator.stats.wireUnwant)
    }
    if (res.want) {
      this.wireWant.send(res.want)
      incrementTx(this.stats.wireWant, this.replicator.stats.wireWant)
    }

    if (batch) this.protomux.uncork()
  }

  _maybeWantAnyRange(start, length, handle) {
    if (handle.wants) {
      for (const w of handle.wants) {
        if ((w.start !== start || w.length !== length) && w.any) {
          const req = this.wants.removeAnyRange(w.start, w.length, handle)
          if (req) this.wireUnwant.send(req)
        }
      }
    }

    if (start + length <= this.remoteContiguousLength) return

    const req = this.wants.addAnyRange(start, length, handle)
    if (req) this.wireWant.send(req)
  }

  _requestRangeBlock(index, length) {
    if (this.core.bitfield.get(index) === true || !this._canRequest(index, false)) return false

    const b = this.replicator._blocks.add(index, PRIORITY.NORMAL, false)
    if (b.inflight.length > 0) {
      this.missingBlocks.set(index, false) // in case we missed some states just set them ondemand, nbd
      return false
    }

    const req = this._makeRequest(index >= length, b.priority, index + 1)

    // If the request cannot be satisfied, dealloc the block request if no one is subscribed to it
    if (req === null) {
      b.gc()
      return false
    }

    this._sendBlockRequest(req, b)

    // Don't think this will ever happen, as the pending queue is drained before the range queue
    // but doesn't hurt to check this explicitly here also.
    if (b.queued) b.queued = false
    return true
  }

  _findNext(i) {
    if (i < this._remoteContiguousLength) {
      if (this.core.skipBitfield === null) this.replicator._openSkipBitfield()
      i = this.core.skipBitfield.findFirst(false, i)
      if (i < this._remoteContiguousLength && i > -1) return i
      i = this._remoteContiguousLength
    }

    return this.missingBlocks.findFirst(true, i)
  }

  _addRangeBatch(r, offset, end) {
    const { WANT_BATCH } = w
    while (offset < end) {
      const next = this.core.bitfield.findFirst(false, offset)
      if (next === -1 || next >= end) return false

      const b = (next - (next & (WANT_BATCH - 1))) / WANT_BATCH

      if (r.addBatch(b)) return true

      offset = (b + 1) * WANT_BATCH
    }

    return false
  }

  _addLastRangeBatch(r, start, end) {
    const { WANT_BATCH } = w

    const next = this.core.bitfield.findLast(false, end - 1)
    if (next === -1 || next < start) return false

    const b = (next - (next & (WANT_BATCH - 1))) / WANT_BATCH

    return r.addBatch(b)
  }

  _populateRangeBatches(r) {
    const { WANT_BATCH } = w
    for (let i = r.batches.length - 1; i >= 0; i--) {
      const b = r.batches[i]
      const minStart = b.batch * WANT_BATCH
      const maxEnd = minStart + WANT_BATCH

      const start = Math.max(r.start, minStart)
      const end = Math.min(r.end === -1 ? maxEnd : r.end, maxEnd)

      if (start < end && this.core.bitfield.hasUnset(start, end - start)) continue

      r.removeBatch(b)
    }

    const end = r.end === -1 ? this.core.state.length : r.end
    if (end <= r.start) return

    // always keep the last batch in the range selected
    if (r.batches.length < 3) this._addLastRangeBatch(r, r.start, end)

    while (r.batches.length < 3) {
      const len = end - r.start
      const off = r.start + (r.linear ? 0 : Math.floor(Math.random() * len))

      if (!this._addRangeBatch(r, off, end) && !this._addRangeBatch(r, r.start, end)) break
    }
  }

  _requestRangeBatch(start, end, length) {
    let tries = this.inflight

    do {
      start = this._findNext(start)
      if (start === -1 || start >= end) break

      if (this._requestRangeBlock(start, length)) return true
      start++
    } while (tries-- > 0)

    return false
  }

  _getValidEnd(maxEnd) {
    // if we can upgrade the remote, or the remote is ahead, then all the remotes blocks are valid
    // otherwise truncate to the last length the remote has acked for us
    const maxLocalLength =
      this.canUpgrade || this.remoteLength >= this.core.state.length
        ? this.core.state.length
        : this.core.state.fork === this.lastUpgradableFork
          ? Math.min(this.lastUpgradableLength, this.core.state.length)
          : 0

    return Math.min(
      maxLocalLength,
      Math.min(maxEnd === -1 ? this.remoteLength : maxEnd, this.remoteLength)
    )
  }

  _requestRange(r) {
    const { WANT_BATCH } = w
    if (this.syncsProcessing > 0) return false

    const { length, fork } = this.core.state

    if (r.blocks) {
      for (let i = r.start; i < r.end; i++) {
        const index = r.blocks[i]
        const has = index < this._remoteContiguousLength || this.missingBlocks.get(index) === true
        if (has === true && this._requestRangeBlock(index, length)) return true
        this._maybeWant(index, r)
      }

      return false
    }

    this._populateRangeBatches(r)

    const end = this._getValidEnd(r.end)

    if (end <= r.start || fork !== this.remoteFork) return false

    for (let i = 0; i < r.batches.length; i++) {
      const b = r.batches[i]

      const batchStart = Math.max(b.batch * WANT_BATCH, r.start)
      const batchEnd = Math.min((b.batch + 1) * WANT_BATCH, end)
      const len = batchEnd - batchStart
      const off = batchStart + (r.linear ? 0 : Math.floor(Math.random() * len))

      if (
        this._requestRangeBatch(off, end, length) ||
        this._requestRangeBatch(batchStart, off, length)
      ) {
        return true
      }

      this._maybeWant(batchStart, b)
    }

    // Here we should consider making an OOB request (ie if the remote says they have a newer block)
    // but the upgrade code already does that, so no big deal for now

    return false
  }

  _requestForkProof(f) {
    if (!this.remoteLength) return
    if (this.replicator.pushOnly) return

    const req = this._makeRequest(false, 0, 0)
    if (req === null) return

    req.upgrade = { start: 0, length: this.remoteLength }
    req.manifest = !this.core.header.manifest

    f.inflight.push(req)
    this._send(req)
  }

  _requestForkRange(f) {
    if (f.fork !== this.remoteFork || f.batch.want === null) return false
    if (this.replicator.pushOnly) return false

    const end = Math.min(f.batch.want.end, this.remoteLength)
    if (end < f.batch.want.start) return false

    const len = end - f.batch.want.start
    const off = f.batch.want.start + Math.floor(Math.random() * len)

    for (let i = 0; i < len; i++) {
      let index = off + i
      if (index >= end) index -= len

      if (this._remoteHasBlock(index) === false) continue

      const req = this._makeRequest(false, 0, 0)
      if (req === null) return false

      req.hash = { index: 2 * index, nodes: f.batch.want.nodes }

      f.inflight.push(req)
      this._send(req)

      return true
    }

    this._maybeWantAnyRange(f.batch.want.start, len, f)
    return false
  }

  isActive() {
    if (this.paused || this.removed || this.core.header.frozen) return false
    return true
  }

  async _send(req) {
    const fork = this.core.state.fork

    this.inflight++
    this.replicator._inflight.add(req)

    if (req.upgrade !== null && req.fork === fork) {
      const u = this.replicator._addUpgrade()
      u.inflight.push(req)
    }

    try {
      if (req.block !== null && req.fork === fork) {
        req.block.nodes = await MerkleTree.missingNodes(
          this.core.state,
          2 * req.block.index,
          this.core.state.length
        )
        if (req.priority === PRIORITY.CANCELLED) return
      }
      if (req.hash !== null && req.fork === fork && req.hash.nodes === 0) {
        req.hash.nodes = await MerkleTree.missingNodes(
          this.core.state,
          req.hash.index,
          this.core.state.length
        )
        if (req.priority === PRIORITY.CANCELLED) return

        // nodes === 0, we already have it, bail
        if (req.hash.nodes === 0 && (req.hash.index & 1) === 0) {
          this.inflight--
          this.replicator._resolveHashLocally(this, req)
          return
        }
      }
    } catch (err) {
      if (this.core.closing !== null) return
      this.stream.destroy(err)
      return
    }

    this.wireRequest.send(req)
    incrementTx(this.stats.wireRequest, this.replicator.stats.wireRequest)
  }
}

module.exports = class Replicator {
  static Peer = Peer // hack to be able to access Peer from outside this module

  constructor(
    core,
    {
      notDownloadingLinger = NOT_DOWNLOADING_SLACK,
      eagerUpgrade = true,
      allowFork = true,
      allowPush = false,
      alwaysLatestBlock = false,
      inflightRange = null
    } = {}
  ) {
    this.core = core
    this.eagerUpgrade = eagerUpgrade
    this.allowFork = allowFork
    this.allowPush = allowPush
    this.ondownloading = null // optional external hook for monitoring downloading status
    this.peers = []
    this.findingPeers = 0 // updatable from the outside
    this.destroyed = false
    this.downloading = false
    this.pushOnly = false
    this.activeSessions = 0

    this.hotswaps = new HotswapQueue()
    this.inflightRange = inflightRange || DEFAULT_MAX_INFLIGHT

    // Note: nodata and unwant not currently tracked
    // tx = transmitted, rx = received
    this.stats = {
      wireSync: { tx: 0, rx: 0 },
      wireRequest: { tx: 0, rx: 0 },
      wireCancel: { tx: 0, rx: 0 },
      wireData: { tx: 0, rx: 0 },
      wireWant: { tx: 0, rx: 0 },
      wireUnwant: { tx: 0, rx: 0 },
      wireBitfield: { tx: 0, rx: 0 },
      wireRange: { tx: 0, rx: 0 },
      wireExtension: { tx: 0, rx: 0 },
      hotswaps: 0,
      invalidData: 0,
      invalidRequests: 0,
      backoffs: 0,
      notAvailableBackoffs: 0
    }

    this._attached = new Set()
    this._inflight = new InflightTracker()
    this._blocks = new BlockTracker(this)
    this._hashes = new BlockTracker(this)

    this._alwaysLatestBlock = alwaysLatestBlock ? 1 : 0
    this._queued = []

    this._seeks = []
    this._upgrade = null
    this._reorgs = []
    this._ranges = []

    this._rxLock = new Mutex()
    this._txLock = new Mutex()

    this._hadPeers = false
    this._active = 0
    this._ifAvailable = 0
    this._updatingSeeks = false
    this._updatesRunning = false
    this._updatesQueued = false
    this._applyingReorg = null
    this._manifestPeer = null
    this._notDownloadingLinger = notDownloadingLinger
    this._notDownloadingTimer = null

    this._updateAllBump = null
    this._updateAllBound = this.updateAll.bind(this)

    const self = this
    this._onstreamclose = onstreamclose

    function onstreamclose() {
      self.detachFrom(this.userData)
    }
  }

  setInflightRange(min, max) {
    const def = this.inflightRange
    this.inflightRange = [min, max]
    for (const peer of this.peers) {
      if (peer.inflightRange !== def) continue
      peer.inflightRange = this.inflightRange
    }
  }

  updateActivity(inc, session) {
    this.activeSessions += inc
    this.setDownloading(this.activeSessions !== 0, session)
  }

  isDownloading() {
    return this.downloading || !this._inflight.idle
  }

  async push(index) {
    const all = []
    for (const peer of this.peers) {
      all.push(peer.push(index))
    }
    await Promise.all(all)
  }

  setAllowPush(allowPush) {
    if (allowPush === this.allowPush) return
    this.allowPush = allowPush
    for (const peer of this.peers) peer.sendSync()
  }

  isAllowingPush() {
    if (!this.allowPush) return false
    if (this._notDownloadingTimer) return false
    return this.downloading
  }

  setPushOnly(pushOnly) {
    if (pushOnly === this.pushOnly) return
    this.pushOnly = pushOnly
  }

  setDownloading(downloading) {
    const allowPush = this.isAllowingPush()
    const linger = this._notDownloadingLinger

    if (this._notDownloadingTimer) {
      clearTimeout(this._notDownloadingTimer)
      this._notDownloadingTimer = null
    }

    if (this.destroyed) return

    if (downloading || linger === 0) {
      if (!this._setDownloadingNow(downloading)) this.signalAllowPush(allowPush)
      return
    }

    this._notDownloadingTimer = setTimeout(setNotDownloadingLater, linger, this)
    if (this._notDownloadingTimer.unref) this._notDownloadingTimer.unref()
    this.signalAllowPush(allowPush)
  }

  signalAllowPush(allowPush) {
    if (allowPush === this.isAllowingPush()) return
    for (const peer of this.peers) peer.signalUpgrade()
  }

  _setDownloadingNow(downloading) {
    this._notDownloadingTimer = null

    if (this.downloading === downloading) return false
    this.downloading = downloading

    if (!downloading && this.isDownloading()) return false

    for (const peer of this.peers) peer.signalUpgrade()

    if (downloading) {
      // restart channel if needed...
      for (const protomux of this._attached) {
        if (!protomux.stream.handshakeHash) continue
        if (protomux.opened({ protocol: 'hypercore/alpha', id: this.core.discoveryKey })) continue
        this._makePeer(protomux, true)
      }
    } else {
      for (const peer of this.peers) peer.closeIfIdle()
    }

    if (this.ondownloading !== null && downloading) this.ondownloading()
    return true
  }

  cork() {
    for (const peer of this.peers) peer.protomux.cork()
  }

  uncork() {
    for (const peer of this.peers) peer.protomux.uncork()
  }

  // Called externally when a range of new blocks has been processed/removed
  onhave(start, length, drop = false) {
    for (const peer of this.peers) peer.broadcastRange(start, length, drop)
  }

  // Called externally when a truncation upgrade has been processed
  ontruncate(newLength, truncated) {
    const notify = []

    for (const blk of this._blocks) {
      if (blk.index < newLength) continue
      notify.push(blk)
    }

    for (const blk of notify) {
      for (const r of blk.refs) {
        if (r.snapshot === false) continue
        blk.detach(r, SNAPSHOT_NOT_AVAILABLE())
      }
    }

    for (const peer of this.peers) {
      peer.fullyDownloadedSignaled = Math.min(newLength, peer.fullyDownloadedSignaled)
      peer._unclearLocalRange(newLength, truncated)
    }
  }

  // Called externally when a upgrade has been processed
  onupgrade() {
    for (const peer of this.peers) peer.signalUpgrade()
    if (this._blocks.isEmpty() === false) this._resolveBlocksLocally()
    if (this._upgrade !== null) this._resolveUpgradeRequest(null)
    if (!this._blocks.isEmpty() || this._ranges.length !== 0 || this._seeks.length !== 0) {
      this._updateNonPrimary(true)
    }
  }

  // Called externally when a conflict has been detected and verified
  async onconflict() {
    const all = []
    for (const peer of this.peers) {
      all.push(peer._onconflict())
    }
    await Promise.allSettled(all)
  }

  async applyPendingReorg() {
    if (this._applyingReorg !== null) {
      await this._applyingReorg
      return true
    }

    for (let i = this._reorgs.length - 1; i >= 0; i--) {
      const f = this._reorgs[i]
      if (f.batch !== null && f.batch.finished) {
        await this._applyReorg(f)
        return true
      }
    }

    return false
  }

  addUpgrade(session) {
    if (this._upgrade !== null) {
      const ref = this._upgrade.attach(session)
      this._checkUpgradeIfAvailable()
      return ref
    }

    const ref = this._addUpgrade().attach(session)

    this.queueUpdateAll()

    return ref
  }

  addBlock(session, index, force) {
    const b = this._blocks.add(index, PRIORITY.HIGH, force)
    const ref = b.attach(session)

    this._queueBlock(b)
    this.queueUpdateAll()

    return ref
  }

  addSeek(session, seeker) {
    const s = new SeekRequest(this, this._seeks, seeker)
    const ref = s.attach(session)

    this._seeks.push(s)
    this.queueUpdateAll()

    return ref
  }

  addRange(
    session,
    { start = 0, end = -1, length = toLength(start, end), blocks = null, linear = false } = {}
  ) {
    if (blocks !== null) {
      // if using blocks, start, end just acts as frames around the blocks array
      start = 0
      end = length = blocks.length
    }

    const r = new RangeRequest(
      this,
      this._ranges,
      start,
      length === -1 ? -1 : start + length,
      linear,
      blocks
    )

    const ref = r.attach(session)

    // Trigger this to see if this is already resolved...
    // Also auto compresses the range based on local bitfield
    clampRange(this.core, r)

    if (r.end !== -1 && r.start >= r.end) {
      this._resolveRangeRequest(r)
      return ref
    }

    this.queueUpdateAll()

    return ref
  }

  cancel(ref) {
    ref.context.detach(ref, null)
  }

  static clearRequests(session, err) {
    if (session.length === 0) return
    if (session.length === 1 && session[0] === null) return

    const updated = new Set()

    while (session.length > 0) {
      const ref = session[session.length - 1]
      updated.add(ref.context.replicator)
      ref.context.detach(ref, err)
    }

    for (const replicator of updated) {
      replicator.queueUpdateAll()
    }
  }

  clearRequests(session, err = null) {
    let cleared = false
    while (session.length > 0) {
      const ref = session[session.length - 1]
      ref.context.detach(ref, err)
      cleared = true
    }

    if (cleared) this.queueUpdateAll()
  }

  _matchingRequest(req, data) {
    if (this.allowPush) {
      return true
    }
    if (data.block !== null && (req.block === null || req.block.index !== data.block.index)) {
      return false
    }
    if (data.hash !== null && (req.hash === null || req.hash.index !== data.hash.index)) {
      return false
    }
    if (data.seek !== null && (req.seek === null || req.seek.bytes !== data.seek.bytes)) {
      return false
    }
    if (data.upgrade !== null && req.upgrade === null) {
      return false
    }
    return req.fork === data.fork
  }

  _addUpgradeMaybe() {
    return this.eagerUpgrade === true ? this._addUpgrade() : this._upgrade
  }

  // TODO: this function is OVER called atm, at each updatePeer/updateAll
  // instead its more efficient to only call it when the conditions in here change - ie on sync/add/remove peer
  // Do this when we have more tests.
  _checkUpgradeIfAvailable() {
    if (this._ifAvailable > 0 && this.peers.length < MAX_PEERS_UPGRADE) return
    if (this._upgrade === null || this._upgrade.refs.length === 0) return
    if (this._hadPeers === false && this.findingPeers > 0) return

    const maxPeers = Math.min(this.peers.length, MAX_PEERS_UPGRADE)

    // check if a peer can upgrade us

    for (let i = 0; i < maxPeers; i++) {
      const peer = this.peers[i]

      if (peer.remoteSynced === false) return

      if (this.core.state.length === 0 && peer.remoteLength > 0) return

      if (peer.remoteLength <= this._upgrade.length || peer.remoteFork !== this._upgrade.fork) {
        continue
      }

      if (peer.syncsProcessing > 0) return

      if (peer.lengthAcked !== this.core.state.length && peer.remoteFork === this.core.state.fork) {
        return
      }

      if (peer.remoteCanUpgrade === true) return
    }

    // check if reorgs in progress...

    if (this._applyingReorg !== null) return

    // TODO: we prob should NOT wait for inflight reorgs here, seems better to just resolve the upgrade
    // and then apply the reorg on the next call in case it's slow - needs some testing in practice

    for (let i = 0; i < this._reorgs.length; i++) {
      const r = this._reorgs[i]
      if (r.inflight.length > 0) return
    }

    // if something is inflight, wait for that first
    if (this._upgrade.inflight.length > 0) return

    // nothing to do, indicate no update avail

    const u = this._upgrade
    this._upgrade = null
    u.resolve(false)
  }

  _addUpgrade() {
    if (this._upgrade !== null) return this._upgrade

    // TODO: needs a reorg: true/false flag to indicate if the user requested a reorg
    this._upgrade = new UpgradeRequest(this, this.core.state.fork, this.core.state.length)

    return this._upgrade
  }

  _addReorg(fork, peer) {
    if (this.allowFork === false) return null

    // TODO: eager gc old reorgs from the same peer
    // not super important because they'll get gc'ed when the request finishes
    // but just spam the remote can do ...

    for (const f of this._reorgs) {
      if (f.fork > fork && f.batch !== null) return null
      if (f.fork === fork) return f
    }

    const f = new ForkRequest(this, fork)

    this._reorgs.push(f)

    // maintain sorted by fork
    let i = this._reorgs.length - 1
    while (i > 0 && this._reorgs[i - 1].fork > fork) {
      this._reorgs[i] = this._reorgs[i - 1]
      this._reorgs[--i] = f
    }

    return f
  }

  _shouldUpgrade(peer) {
    if (this._upgrade !== null && this._upgrade.inflight.length > 0) return false
    return (
      peer.remoteCanUpgrade === true &&
      peer.remoteLength > this.core.state.length &&
      peer.lengthAcked === this.core.state.length
    )
  }

  _autoUpgrade(peer) {
    return (
      this._upgrade !== null &&
      peer.remoteFork === this.core.state.fork &&
      this._shouldUpgrade(peer)
    )
  }

  _addPeer(peer) {
    this._hadPeers = true
    this.peers.push(peer)
    this.updatePeer(peer)
    this._onpeerupdate(true, peer)
  }

  _requestDone(id, roundtrip) {
    this._inflight.remove(id, roundtrip)
    if (this.isDownloading() === true) return
    for (const peer of this.peers) peer.signalUpgrade()
  }

  _removePeer(peer) {
    this.peers.splice(this.peers.indexOf(peer), 1)
    peer.wants.destroy()

    if (this._manifestPeer === peer) this._manifestPeer = null

    let inflight = false
    for (const req of this._inflight) {
      if (req.peer !== peer) continue
      inflight = true
      this._inflight.remove(req.id, true)
      this._clearRequest(peer, req)
    }

    this._onpeerupdate(false, peer)
    if (inflight) {
      this.queueUpdateAll()
    } else {
      this._checkUpgradeIfAvailable()
    }
  }

  _queueBlock(b) {
    if (b.inflight.length > 0 || b.queued === true) return
    b.queued = true
    this._queued.push(b)
  }

  _resolveHashLocally(peer, req) {
    this._requestDone(req.id, false)
    this._resolveBlockRequest(this._hashes, req.hash.index / 2, null, req)
    this.updatePeer(peer)
  }

  // Runs in the background - not allowed to throw
  async _resolveBlocksLocally() {
    // TODO: check if fork compat etc. Requires that we pass down truncation info

    const clear = []
    const blocks = []

    const reader = this.core.storage.read()
    for (const b of this._blocks) {
      if (this.core.bitfield.get(b.index) === false) continue
      blocks.push(this._resolveLocalBlock(b, reader, clear))
    }
    reader.tryFlush()

    await Promise.all(blocks)

    if (!clear.length) return

    // Currently the block tracker does not support deletes during iteration, so we make
    // sure to clear them afterwards.
    for (const b of clear) {
      this._blocks.remove(b.index)
      removeHotswap(b)
    }
  }

  async _resolveLocalBlock(b, reader, resolved) {
    try {
      b.resolve(await reader.getBlock(b.index))
    } catch (err) {
      b.reject(err)
      return
    }

    resolved.push(b)
  }

  _resolveBlockRequest(tracker, index, value, req) {
    const b = tracker.remove(index)
    if (b === null) return false

    removeInflight(b.inflight, req)
    removeHotswap(b)
    b.queued = false

    b.resolve(value)

    if (b.inflight.length > 0) {
      // if anything is still inflight, cancel it
      for (let i = b.inflight.length - 1; i >= 0; i--) {
        const req = b.inflight[i]
        req.peer._cancelRequest(req)
      }
    }

    return true
  }

  _resolveUpgradeRequest(req) {
    if (req !== null) removeInflight(this._upgrade.inflight, req)

    if (
      this.core.state.length === this._upgrade.length &&
      this.core.state.fork === this._upgrade.fork
    ) {
      return false
    }

    const u = this._upgrade
    this._upgrade = null
    u.resolve(true)

    return true
  }

  _resolveRangeRequest(req) {
    req.resolve(true)
    req.gc()
  }

  _clearInflightBlock(tracker, req) {
    const isBlock = tracker === this._blocks
    const index = isBlock === true ? req.block.index : req.hash.index / 2
    const b = tracker.get(index)

    if (b === null || removeInflight(b.inflight, req) === false) return

    if (removeHotswap(b) === true && b.inflight.length > 0) {
      this.hotswaps.add(b)
    }

    if (b.refs.length > 0 && isBlock === true) {
      this._queueBlock(b)
      return
    }

    b.gc()
  }

  _clearInflightUpgrade(req) {
    if (this._upgrade === null || removeInflight(this._upgrade.inflight, req) === false) return
    this._upgrade.gc()
  }

  _clearInflightSeeks(req) {
    for (const s of this._seeks) {
      if (removeInflight(s.inflight, req) === false) continue
      s.gc()
    }
  }

  _clearInflightReorgs(req) {
    for (const r of this._reorgs) {
      removeInflight(r.inflight, req)
    }
  }

  _clearOldReorgs(fork) {
    for (let i = 0; i < this._reorgs.length; i++) {
      const f = this._reorgs[i]
      if (f.fork >= fork) continue
      if (i === this._reorgs.length - 1) this._reorgs.pop()
      else this._reorgs[i] = this._reorgs.pop()
      i--
    }
  }

  _updateRanges(index, limit) {
    index = Math.min(index, this._ranges.length - 1)

    let checked = 0
    let resolved = 0
    let updateAll = false
    let remaining = Math.min(limit, this._ranges.length)

    while (remaining-- > 0 && this._ranges.length > 0) {
      if (index >= this._ranges.length) index = 0

      const r = this._ranges[index]

      clampRange(this.core, r)
      checked++

      if (r.end !== -1 && r.start >= r.end) {
        this._resolveRangeRequest(r)
        resolved++
        // Crossing into the capped window exposes another range to peer scheduling.
        if (this._ranges.length === MAX_RANGES) updateAll = true
      } else {
        index++
      }
    }

    index = this._ranges.length === 0 ? 0 : index % this._ranges.length

    return { checked, resolved, index, updateAll }
  }

  // "slow" updates here - async but not allowed to ever throw
  async _updateNonPrimary(updateAll) {
    this._updatesQueued = true
    if (this._updatesRunning) {
      if (this._inflight.idle || updateAll) this.queueUpdateAll()
      return
    }

    this._updatesRunning = true

    while (this._updatesQueued) {
      this._updatesQueued = false

      let checkedSinceResolve = 0
      let rangeIndex = 0
      const drain = this._inflight.idle || updateAll
      const checkedSeeks = new Set()

      while (true) {
        const limit = Math.min(MAX_RANGES, this._ranges.length - checkedSinceResolve)
        const {
          checked,
          resolved,
          index,
          updateAll: resultUpdateAll
        } = this._updateRanges(rangeIndex, limit)
        rangeIndex = index

        if (resultUpdateAll) updateAll = true

        if (resolved > 0) checkedSinceResolve = 0
        else checkedSinceResolve += checked

        const continueRanges = checked > 0 && checkedSinceResolve < this._ranges.length && drain

        this._updatingSeeks = true

        let checkedSeek = false
        for (const s of this._seeks.slice()) {
          if (checkedSeeks.has(s) || !this._seeks.includes(s)) continue
          checkedSeeks.add(s)
          checkedSeek = true

          let err = null
          let res = null

          try {
            res = await s.seeker.update()
          } catch (error) {
            err = error
          }

          const seekIndex = this._seeks.indexOf(s)
          if (seekIndex === -1 || (!res && !err)) continue

          const h = this._seeks.pop()
          if (h !== s) this._seeks[seekIndex] = h

          if (err) s.reject(err)
          else s.resolve(res)
        }

        this._updatingSeeks = false

        if (
          (!continueRanges || (checkedSeek && this._seeks.length > 0)) &&
          (this._inflight.idle || updateAll)
        ) {
          this.queueUpdateAll()
        }
        if (!continueRanges) break

        await yieldToLoop()
        if (this.destroyed) break
      }

      if (this.destroyed) break
    }

    this._updatesRunning = false
  }

  _clearRequest(peer, req) {
    if (req.block !== null) {
      this._clearInflightBlock(this._blocks, req)
      this._unmarkInflight(req.block.index)
    }

    if (req.hash !== null) {
      this._clearInflightBlock(this._hashes, req)
    }

    if (req.upgrade !== null && this._upgrade !== null) {
      this._clearInflightUpgrade(req)
    }

    if (this._seeks.length > 0) {
      this._clearInflightSeeks(req)
    }

    if (this._reorgs.length > 0) {
      this._clearInflightReorgs(req)
    }
  }

  _onnodata(peer, req, reason) {
    if (reason === NOT_AVAILABLE) {
      peer.stats.notAvailableBackoffs++
      this.stats.notAvailableBackoffs++

      if (peer.stats.notAvailableBackoffs >= MAX_BACKOFFS) {
        peer.paused = true
      }
    }

    if (reason === INVALID_REQUEST) {
      peer.stats.backoffs++
      this.stats.backoffs++

      if (peer.stats.backoffs >= MAX_BACKOFFS) {
        peer.paused = true
      }
    }

    if (req) {
      this._clearRequest(peer, req)
    }

    this.queueUpdateAll()
  }

  _openSkipBitfield() {
    // technically the skip bitfield gets bits cleared if .clear() is called
    // also which might be in inflight also, but that just results in that section being overcalled shortly
    // worst case, so ok for now

    const bitfield = this.core.openSkipBitfield()

    for (const req of this._inflight) {
      if (req.block) bitfield.set(req.block.index, true) // skip
    }
  }

  _markProcessing(index) {
    const b = this._blocks.get(index)
    if (b) {
      b.processing = true
      return
    }

    const h = this._hashes.get(index)
    if (h) h.processing = true
  }

  _markProcessed(index) {
    const b = this._blocks.get(index)
    if (b) return b.processed()

    const h = this._hashes.get(index)
    if (h) h.processed()
  }

  _markInflight(index) {
    if (this.core.skipBitfield !== null) this.core.skipBitfield.set(index, true)
    for (const peer of this.peers) peer._markInflight(index)
  }

  _unmarkInflight(index) {
    if (this.core.skipBitfield !== null) {
      this.core.skipBitfield.set(index, this.core.bitfield.get(index))
    }
    for (const peer of this.peers) peer._resetMissingBlock(index)
  }

  _ondata(peer, req, data) {
    if (req) {
      req.elapsed = Date.now() - req.timestamp
    }

    if (data.block !== null) {
      this._resolveBlockRequest(this._blocks, data.block.index, data.block.value, req)
      this._ondownload(data.block.index, data.block.value.byteLength, peer, req)
    }

    if (data.hash !== null && (data.hash.index & 1) === 0) {
      this._resolveBlockRequest(this._hashes, data.hash.index / 2, null, req)
    }

    if (this._upgrade !== null) {
      this._resolveUpgradeRequest(req)
    }

    if (this._seeks.length > 0) {
      this._clearInflightSeeks(req)
    }

    if (this._reorgs.length > 0) {
      this._clearInflightReorgs(req)
    }

    if (this._manifestPeer === peer && this.core.header.manifest !== null) {
      this._manifestPeer = null
    }

    if (this._seeks.length > 0 || this._ranges.length > 0) {
      this._updateNonPrimary(this._seeks.length > 0)
    }

    this.updatePeer(peer)
  }

  _onwant(peer, start, length, any) {
    if (!peer.isActive()) return

    const contig = Math.min(this.core.state.length, this.core.header.hints.contiguousLength)

    if (start + length < contig || this.core.state.length === contig || (any && start < contig)) {
      peer.wireRange.send({
        drop: false,
        start: 0,
        length: contig
      })
      incrementTx(peer.stats.wireRange, this.stats.wireRange)
      return
    }

    length = Math.min(length, this.core.state.length - start)

    if (any) {
      const bit = this.core.bitfield.firstSet(start)
      if (bit > -1 && bit < start + length) {
        peer.wireRange.send({
          drop: false,
          start: bit,
          length: 1
        })
      }
      return
    }

    for (const msg of this.core.bitfield.want(start, length)) {
      peer.wireBitfield.send(msg)
      incrementTx(peer.stats.wireBitfield, this.stats.wireBitfield)
    }
  }

  async _onreorgdata(peer, req, data) {
    const newBatch = data.upgrade && (await this.core.verifyReorg(data))
    const f = this._addReorg(data.fork, peer)

    if (f === null) {
      this.queueUpdateAll()
      return
    }

    removeInflight(f.inflight, req)

    if (f.batch) {
      await f.batch.update(data)
    } else if (data.upgrade) {
      f.batch = newBatch

      // Remove "older" reorgs in progress as we just verified this one.
      this._clearOldReorgs(f.fork)
    }

    if (f.batch && f.batch.finished) {
      if (this._addUpgradeMaybe() !== null) {
        await this._applyReorg(f)
      }
    }

    this.queueUpdateAll()
  }

  // Never throws, allowed to run in the background
  async _applyReorg(f) {
    // TODO: more optimal here to check if potentially a better reorg
    // is available, ie higher fork, and request that one first.
    // This will request that one after this finishes, which is fine, but we
    // should investigate the complexity in going the other way

    const u = this._upgrade
    const old = this._reorgs

    this._reorgs = [] // clear all as the nodes are against the old tree - easier
    this._applyingReorg = this.core.reorg(f.batch, null) // TODO: null should be the first/last peer?

    try {
      await this._applyingReorg
    } catch (err) {
      this._upgrade = null
      u.reject(err)
    }

    this._applyingReorg = null

    if (this._upgrade !== null) {
      this._resolveUpgradeRequest(null)
    }

    for (const peer of this.peers) this._updateFork(peer)

    // TODO: all the remaining is a tmp workaround until we have a flag/way for ANY_FORK
    for (const r of this._ranges) {
      r.start = r.userStart
      r.end = r.userEnd
    }

    for (const f of old) f.resolve()
    f.resolve()

    this.queueUpdateAll()
  }

  _maybeUpdate() {
    return this._upgrade !== null && this._upgrade.inflight.length === 0
  }

  _maybeRequestManifest() {
    return this.core.header.manifest === null && this._manifestPeer === null
  }

  _updateFork(peer) {
    if (
      this._applyingReorg !== null ||
      this.allowFork === false ||
      peer.remoteFork <= this.core.state.fork
    ) {
      return false
    }

    const f = this._addReorg(peer.remoteFork, peer)

    // TODO: one per peer is better
    if (f !== null && f.batch === null && f.inflight.length === 0) {
      return peer._requestForkProof(f)
    }

    return false
  }

  _updateHotswap(peer) {
    const maxHotswaps = peer.getMaxHotswapInflight()
    if (!peer.isActive() || peer.inflight >= maxHotswaps) return

    for (const b of this.hotswaps.pick(peer)) {
      if (peer._requestBlock(b) === false) continue
      peer.stats.hotswaps++
      peer.replicator.stats.hotswaps++
      if (peer.inflight >= maxHotswaps) break
    }
  }

  _updatePeer(peer) {
    if (!peer.isActive() || peer.inflight >= peer.getMaxInflight() || !peer.remoteUploading) {
      return false
    }

    // Eagerly request the manifest even if the remote length is 0. If not 0 we'll get as part of the upgrade request...
    if (
      this._maybeRequestManifest() === true &&
      peer.remoteLength === 0 &&
      peer.remoteHasManifest === true
    ) {
      this._manifestPeer = peer
      peer._requestManifest()
    }

    for (const s of this._seeks) {
      if (s.inflight.length > 0) continue // TODO: one per peer is better
      if (peer._requestSeek(s) === true) {
        return true
      }
    }

    // Implied that any block in the queue should be requested, no matter how many inflights
    const blks = new RandomIterator(this._queued)

    for (const b of blks) {
      if (b.queued === false || peer._requestBlock(b) === true) {
        b.queued = false
        blks.dequeue()
        return true
      }
    }

    return false
  }

  _updatePeerNonPrimary(peer) {
    if (!peer.isActive() || peer.inflight >= peer.getMaxInflight() || !peer.remoteUploading) {
      return false
    }

    const ranges = new RandomIterator(this._ranges)
    let tried = 0

    for (const r of ranges) {
      if (peer._requestRange(r) === true) {
        return true
      }
      if (++tried >= MAX_RANGES) break
    }

    // Iterate from newest fork to oldest fork...
    for (let i = this._reorgs.length - 1; i >= 0; i--) {
      const f = this._reorgs[i]
      if (f.batch !== null && f.inflight.length === 0 && peer._requestForkRange(f) === true) {
        return true
      }
    }

    if (this._maybeUpdate() === true && peer._requestUpgrade(this._upgrade) === true) {
      return true
    }

    return false
  }

  updatePeer(peer) {
    // Quick shortcut to wait for flushing reorgs - not needed but less waisted requests
    if (this._applyingReorg !== null) return

    while (this._updatePeer(peer) === true);
    while (this._updatePeerNonPrimary(peer) === true);

    if (this.peers.length > 1 && this._blocks.isEmpty() === false) {
      this._updateHotswap(peer)
    }

    this._checkUpgradeIfAvailable()
  }

  updateSubset() {
    return this.updateAll(false)
  }

  updateAll(all = true) {
    // Clear queued scan if a full scan
    if (this._updateAllBump !== null && all) {
      clearTimeout(this._updateAllBump)
      this._updateAllBump = null
    }

    // Quick shortcut to wait for flushing reorgs - not needed but less waisted requests
    if (this._applyingReorg !== null) return

    const peers = new RandomIterator(this.peers)

    const limit = all ? peers.length : MIN_PEER_UPDATE_ALL
    let tried = 0
    for (const peer of peers) {
      tried++
      if (this._updatePeer(peer) === true) {
        peers.requeue()
      }
      if (tried >= limit) break
    }

    // Check if we can skip the non primary check fully
    if (this._maybeUpdate() === false && this._ranges.length === 0 && this._reorgs.length === 0) {
      this._checkUpgradeIfAvailable()
      return
    }

    tried = 0
    for (const peer of peers.restart()) {
      tried++
      if (this._updatePeerNonPrimary(peer) === true) {
        peers.requeue()
      }
      if (tried >= limit) break
    }

    this._checkUpgradeIfAvailable()
  }

  getUpdateAllDelay(peers) {
    return Math.min(3000, Math.max(100, (peers.length - MIN_PEER_UPDATE_ALL) * 5))
  }

  queueUpdateAll() {
    if (this.peers.length < MIN_PEER_UPDATE_ALL) return this.updateAll()

    // Immediately scan subset
    this.updateSubset()

    // Schedule full scan
    if (this._updateAllBump !== null) return //skip if already scheduled
    this._updateAllBump = setTimeout(this._updateAllBound, this.getUpdateAllDelay(this.peers))
  }

  onpeerdestroy() {
    if (--this._active === 0) this.core.checkIfIdle()
  }

  attached(protomux) {
    return this._attached.has(protomux)
  }

  attachTo(protomux) {
    if (this.core.closed) return

    const makePeer = this._makePeer.bind(this, protomux)

    this._attached.add(protomux)
    protomux.pair({ protocol: 'hypercore/alpha', id: this.core.discoveryKey }, makePeer)
    protomux.stream.setMaxListeners(0)
    protomux.stream.on('close', this._onstreamclose)

    this._ifAvailable++
    this._active++

    protomux.stream.opened.then((opened) => {
      this._ifAvailable--
      this._active--

      if (opened && !this.destroyed) makePeer()
      this._checkUpgradeIfAvailable()

      this.core.checkIfIdle()
    })
  }

  detachFrom(protomux) {
    if (this._attached.delete(protomux)) {
      protomux.stream.removeListener('close', this._onstreamclose)
      protomux.unpair({ protocol: 'hypercore/alpha', id: this.core.discoveryKey })
    }
  }

  idle() {
    return this.peers.length === 0 && this._active === 0
  }

  close() {
    const waiting = []

    for (const peer of this.peers) {
      waiting.push(peer.channel.fullyClosed())
    }

    this.destroy()
    return Promise.all(waiting)
  }

  destroy() {
    if (this.destroyed) return
    this.destroyed = true

    if (this._notDownloadingTimer) {
      clearTimeout(this._notDownloadingTimer)
      this._notDownloadingTimer = null
    }

    if (this._updateAllBump) {
      clearTimeout(this._updateAllBump)
      this._updateAllBump = null
    }

    while (this.peers.length) {
      const peer = this.peers[this.peers.length - 1]
      this.detachFrom(peer.protomux)
      peer.channel.close() // peer is removed from array in onclose
    }

    for (const protomux of this._attached) {
      this.detachFrom(protomux)
    }
  }

  _makePeer(protomux) {
    const replicator = this
    if (protomux.opened({ protocol: 'hypercore/alpha', id: this.core.discoveryKey })) {
      return onnochannel()
    }

    const channel = protomux.createChannel({
      userData: null,
      protocol: 'hypercore/alpha',
      aliases: ['hypercore'],
      id: this.core.discoveryKey,
      handshake: m.wire.handshake,
      messages: [
        { encoding: m.wire.sync, onmessage: onwiresync },
        { encoding: m.wire.request, onmessage: onwirerequest },
        { encoding: m.wire.cancel, onmessage: onwirecancel },
        { encoding: m.wire.data, onmessage: onwiredata },
        { encoding: m.wire.noData, onmessage: onwirenodata },
        { encoding: m.wire.want, onmessage: onwirewant },
        { encoding: m.wire.unwant, onmessage: onwireunwant },
        { encoding: m.wire.bitfield, onmessage: onwirebitfield },
        { encoding: m.wire.range, onmessage: onwirerange },
        { encoding: m.wire.extension, onmessage: onwireextension }
      ],
      onopen: onwireopen,
      onclose: onwireclose,
      ondrain: onwiredrain,
      ondestroy: onwiredestroy
    })

    if (channel === null) return onnochannel()

    const peer = new Peer(replicator, protomux, channel, this.inflightRange)
    const stream = protomux.stream

    peer.channel.open({
      seeks: true,
      capability: caps.replicate(stream.isInitiator, this.core.key, stream.handshakeHash)
    })

    return true

    function onnochannel() {
      return false
    }
  }

  _onpeerupdate(added, peer) {
    const name = added ? 'peer-add' : 'peer-remove'
    const sessions = this.core.monitors

    for (let i = sessions.length - 1; i >= 0; i--) {
      sessions[i].emit(name, peer)

      if (added) {
        for (const ext of sessions[i].extensions.values()) {
          peer.extensions.set(ext.name, ext)
        }
      }
    }
  }

  _ondownload(index, byteLength, from, req) {
    const sessions = this.core.monitors

    for (let i = sessions.length - 1; i >= 0; i--) {
      const s = sessions[i]
      s.emit('download', index, byteLength - s.padding, from, req)
    }
  }

  _onupload(index, byteLength, from) {
    const sessions = this.core.monitors

    for (let i = sessions.length - 1; i >= 0; i--) {
      const s = sessions[i]
      s.emit('upload', index, byteLength - s.padding, from)
    }
  }

  _oninvaliddata(err, req, res, from) {
    const sessions = this.core.monitors

    this.stats.invalidData++
    from.stats.invalidData++
    for (let i = 0; i < sessions.length; i++) {
      sessions[i].emit('verification-error', err, req, res, from)
    }
  }

  _oninvalidrequest(err, req, from) {
    const sessions = this.core.monitors

    from.stats.invalidRequests++
    this.stats.invalidRequests++
    for (let i = 0; i < sessions.length; i++) {
      sessions[i].emit('invalid-request', err, req, from)
    }
  }
}

function removeHotswap(block) {
  if (block.hotswap === null) return false
  block.hotswap.ref.remove(block)
  return true
}

function removeInflight(inf, req) {
  const i = inf.indexOf(req)
  if (i === -1) return false
  if (i < inf.length - 1) inf[i] = inf.pop()
  else inf.pop()
  return true
}

function toLength(start, end) {
  return end === -1 ? -1 : end < start ? 0 : end - start
}

function clampRange(core, r) {
  if (r.blocks === null) {
    const start = core.bitfield.firstUnset(r.start)

    if (r.end === -1) r.start = start === -1 ? core.state.length : start
    else if (start === -1 || start >= r.end) r.start = r.end
    else {
      r.start = start

      const end = core.bitfield.lastUnset(r.end - 1)

      if (end === -1 || start >= end + 1) r.end = r.start
      else r.end = end + 1
    }
  } else {
    while (r.start < r.end && core.bitfield.get(r.blocks[r.start])) r.start++
    while (r.start < r.end && core.bitfield.get(r.blocks[r.end - 1])) r.end--
  }
}

function onrequesttimeout(req) {
  if (req.context) req.context.detach(req, REQUEST_TIMEOUT())
}

function destroyRequestTimeout(req) {
  if (req.timeout !== null) {
    clearTimeout(req.timeout)
    req.timeout = null
  }
}

function isCriticalError(err) {
  // TODO: expose .critical or similar on the hypercore errors that are critical (if all not are)
  return err.name === 'HypercoreError'
}

function onwireopen(m, c) {
  return c.userData.onopen(m)
}

function onwireclose(isRemote, c) {
  return c.userData.onclose(isRemote)
}

function onwiredestroy(c) {
  c.userData.replicator.onpeerdestroy()
}

function onwiredrain(c) {
  return c.userData.ondrain()
}

function onwiresync(m, c) {
  incrementRx(c.userData.stats.wireSync, c.userData.replicator.stats.wireSync)
  return c.userData.onsync(m)
}

function onwirerequest(m, c) {
  incrementRx(c.userData.stats.wireRequest, c.userData.replicator.stats.wireRequest)
  return c.userData.onrequest(m)
}

function onwirecancel(m, c) {
  incrementRx(c.userData.stats.wireCancel, c.userData.replicator.stats.wireCancel)
  return c.userData.oncancel(m)
}

function onwiredata(m, c) {
  incrementRx(c.userData.stats.wireData, c.userData.replicator.stats.wireData)
  return c.userData.ondata(m)
}

function onwirenodata(m, c) {
  return c.userData.onnodata(m)
}

function onwirewant(m, c) {
  incrementRx(c.userData.stats.wireWant, c.userData.replicator.stats.wireWant)
  return c.userData.onwant(m)
}

function onwireunwant(m, c) {
  return c.userData.onunwant(m)
}

function onwirebitfield(m, c) {
  incrementRx(c.userData.stats.wireBitfield, c.userData.replicator.stats.wireBitfield)
  return c.userData.onbitfield(m)
}

function onwirerange(m, c) {
  incrementRx(c.userData.stats.wireRange, c.userData.replicator.stats.wireRange)
  return c.userData.onrange(m)
}

function onwireextension(m, c) {
  incrementRx(c.userData.stats.wireExtension, c.userData.replicator.stats.wireExtension)
  return c.userData.onextension(m)
}

function setNotDownloadingLater(repl, session) {
  repl._setDownloadingNow(false, session)
}

function isBlockRequest(req) {
  return req !== null && req.block !== null
}

function isUpgradeRequest(req) {
  return req !== null && req.upgrade !== null
}

function incrementTx(stats1, stats2) {
  stats1.tx++
  stats2.tx++
}

function incrementRx(stats1, stats2) {
  stats1.rx++
  stats2.rx++
}

function noop() {}

function yieldToLoop() {
  return new Promise((resolve) => setImmediate(resolve))
}

function backoff(times) {
  const sleep = times < 2 ? 200 : times < 5 ? 500 : times < 40 ? 1000 : 5000
  return new Promise((resolve) => setTimeout(resolve, sleep))
}
const crypto = require('hypercore-crypto')
const b4a = require('b4a')
const assert = require('nanoassert')
const flat = require('flat-tree')
const quickbit = require('quickbit-universal')
const HypercoreStorage = require('hypercore-storage')

const {
  INVALID_OPERATION,
  INVALID_SIGNATURE,
  ASSERTION,
  SESSION_CLOSED,
  SESSION_NOT_WRITABLE
} = require('hypercore-errors')

const Mutex = require('./mutex')
const Bitfield = require('./bitfield')
const { MerkleTree, MerkleTreeBatch } = require('./merkle-tree')

class SessionState {
  constructor(core, parent, storage, treeInfo, name) {
    this.core = core
    this.index = this.core.sessionStates.push(this) - 1

    this.storage = storage
    this.name = name
    this.sessions = []

    // small hack to close old storages as late as possible.
    // TODO: add a read lock so we can avoid that
    this.lingers = null

    this.parent = parent
    this.atomized = null
    this.mutex = new Mutex()

    // merkle state
    this.roots = treeInfo.roots.length ? treeInfo.roots : []
    this.fork = treeInfo.fork || 0
    this.length = this.roots.length ? MerkleTree.span(this.roots) / 2 : treeInfo.length
    this.byteLength = MerkleTree.size(this.roots)
    this.prologue = treeInfo.prologue || null
    this.signature = treeInfo.signature || null

    this.snapshotCompatLength = this.isSnapshot()
      ? Math.min(this.length, this.core.state.length)
      : -1
    this.lastTruncation = null
    this.groupUpdated = false

    this.active = 0

    this._activeTx = null
    this._pendingBitfield = null

    this.ref()
  }

  static RECOVERY_PEERS = null

  isSnapshot() {
    return this.storage.snapshotted
  }

  isDefault() {
    return this.core.state === this || this.isAtomicDefault()
  }

  isAtomicDefault() {
    return !!this.storage.atom && !!this.parent && this.parent.isDefault()
  }

  createTreeBatch() {
    return new MerkleTreeBatch(this)
  }

  addSession(s) {
    if (s._stateIndex !== -1) return
    s._stateIndex = this.sessions.push(s) - 1
    if (s.weak === false) this.core.activeSessions++
  }

  removeSession(s) {
    if (s._stateIndex === -1) return
    const head = this.sessions.pop()
    if (head !== s) this.sessions[(head._stateIndex = s._stateIndex)] = head
    s._stateIndex = -1
    if (s.weak === false) this.core.activeSessions--
    this.core.checkIfIdle()
  }

  flushedLength() {
    if (this.isDefault() || this.isSnapshot()) return this.length
    const deps = this.storage.dependencies
    if (deps.length) return deps[deps.length - 1].length
    return 0
  }

  signedLength() {
    const l = Math.min(this.flushedLength(), this.core.state.length)
    return this.isSnapshot() && l > this.snapshotCompatLength ? this.snapshotCompatLength : l
  }

  unref() {
    if (--this.active > 0) return
    this.close().catch(noop) // technically async, but only for the last db session
  }

  ref() {
    this.active++
    return this
  }

  hash() {
    return MerkleTree.hash(this)
  }

  setRoots(roots) {
    this.roots = roots
    this.length = MerkleTree.span(roots) / 2
    this.byteLength = MerkleTree.size(roots)
  }

  get encryptionFork() {
    return this.core.header.tree.fork
  }

  async _updateSnapshotStorage(storage) {
    if (!this.atomized || !this.atomized.flushing) return this.treeInfo()
    while (this.atomized.flushing) await this.atomized.flushed()

    let rx = storage.read()
    const headPromise = rx.getHead()
    const authPromise = rx.getAuth()
    const depPromise = rx.getDependency()

    rx.tryFlush()
    const [head, auth, dep] = await Promise.all([headPromise, authPromise, depPromise])

    storage.setDependencyHead(dep)

    const fork = head ? head.fork : 0
    const length = head ? head.length : 0

    rx = storage.read()
    const rootPromises = []
    for (const r of flat.fullRoots(length * 2)) {
      rootPromises.push(rx.getTreeNode(r))
    }

    rx.tryFlush()

    const roots = await Promise.all(rootPromises)

    // dbl check if we are hitting an regression from earler
    for (const root of roots) {
      if (root === null) {
        throw new Error(
          'Bad snapshot from atomized session, id = ' +
            this.core.id +
            ' length = ' +
            length +
            ' fork = ' +
            fork
        )
      }
    }

    return {
      fork,
      roots,
      length,
      prologue: auth.manifest && auth.manifest.prologue,
      signature: head && head.signature
    }
  }

  treeInfo() {
    return {
      fork: this.fork,
      roots: this.roots.slice(),
      length: this.length,
      prologue: this.prologue,
      signature: this.signature
    }
  }

  async close() {
    if (this.index === -1) return

    this.active = 0
    this.mutex.destroy(new Error('Closed')).catch(noop)
    if (this.parent && this.parent.atomized) this.parent.atomized = null

    const closing = this.storage.close()

    const head = this.core.sessionStates.pop()
    if (head !== this) this.core.sessionStates[(head.index = this.index)] = head

    this.index = -1
    this.core.checkIfIdle()

    if (this.lingers !== null) {
      for (const storage of this.lingers) await storage.close()
    }

    return closing
  }

  async snapshot() {
    const storage = this.storage.snapshot()
    const treeInfo = await this._updateSnapshotStorage(storage)

    return new SessionState(this.core, null, storage, treeInfo, this.name)
  }

  updateDependency(tx, length) {
    const dependency = updateDependency(this, length, false)
    if (dependency) tx.setDependency(dependency)
    return dependency
  }

  updateGroupIndex(tx, tree) {
    const { pointer } = this.core.header.group

    tx.deleteGroupUpdate(pointer, this.core.header.tree.timestamp)
    tx.putGroupUpdate(pointer, tree.timestamp, this.core.header.key)

    this.groupUpdated = true
  }

  _clearActiveBatch() {
    this._activeTx = null
  }

  createWriteBatch() {
    assert(!this._activeTx && !this.storage.snapshotted)

    this._activeTx = this.storage.write()
    return this._activeTx
  }

  _unlock() {
    this._clearActiveBatch()
    this.mutex.unlock()
    this.core.checkIfIdle()
  }

  _flushHooks() {
    if (!this.groupUpdated) return
    this.groupUpdated = false
    this.core.ongroupupdate(this.core.header.group.key, {
      key: this.core.key,
      length: this.length,
      fork: this.fork
    })
  }

  async flush() {
    const tx = this._activeTx
    this._activeTx = null

    try {
      if (!(await tx.flush())) return false
    } finally {
      this._clearActiveBatch()
    }

    this.lastTruncation = null
    return true
  }

  _precommit(state) {
    assert(
      state.parent === this && state.storage.core.dataPointer === this.storage.core.dataPointer,
      'Atomic state must flush to parent'
    )
  }

  async _commit() {
    await this.mutex.lock()

    try {
      const bitfield = this._pendingBitfield
      this._pendingBitfield = null
      this.lastTruncation = null
      await this.parent._oncommit(this, bitfield)
    } finally {
      this.mutex.unlock()
    }
  }

  async _oncommit(src, bitfield) {
    await this.mutex.lock()

    try {
      const currLength = this.length

      // load dependency into memory
      const rx = this.storage.read()
      const dependencyPromise = rx.getDependency()

      rx.tryFlush()

      const dependency = await dependencyPromise

      this.fork = src.fork
      this.length = src.length
      this.byteLength = src.byteLength
      this.roots = src.roots.slice()
      this.signature = src.signature

      const tree = {
        fork: this.fork,
        length: this.length,
        rootHash: this.hash(),
        signature: this.signature,
        timestamp: Date.now()
      }

      if (this.isDefault() && this.core.header.group) {
        const tx = this.createWriteBatch()
        this.updateGroupIndex(tx, tree)
        await this.flush()
        this._flushHooks()
      }

      if (dependency) this.storage.setDependencyHead(dependency)

      const b = bitfield

      if (b && b.truncated && b.start < currLength) {
        this.ontruncate(tree, b.start, currLength, true)
        if (!b || b.appends === 0) return
      }

      const append = b ? { start: b.start, length: b.appends, drop: false } : null

      this.onappend(tree, append, true)

      if (this.core.hintsChanged && this.isDefault()) await this.core.flushHints()
    } finally {
      this._unlock()
    }
  }

  async setUserData(key, value) {
    await this.mutex.lock()

    try {
      const tx = this.createWriteBatch()
      tx.putUserData(key, value)

      return await this.flush()
    } finally {
      this._unlock()
    }
  }

  async _verifyBlock(batch, bitfield, value, manifest, from) {
    await this.mutex.lock()

    try {
      if (batch.upgraded && batch.length <= this.length) {
        await batch.downgrade()
      }

      if (!batch.commitable()) {
        return false
      }

      if (this.core.preupdate !== null) {
        await this.core.preupdate(batch, this.core.header.key)
      }

      const tx = this.createWriteBatch()

      if (bitfield) {
        tx.putBlock(bitfield.start, value)
      }

      if (bitfield && this.isDefault()) {
        await storeBitfieldRange(this.storage, tx, bitfield.start, bitfield.start + 1, true)
      }

      if (manifest) this.core._setManifest(tx, manifest, null)

      assert(batch.commitable(), 'Should still be commitable')
      batch.commit(tx)

      const head = {
        fork: batch.fork,
        length: batch.length,
        rootHash: batch.hash(),
        signature: batch.signature,
        timestamp: Date.now()
      }

      if (batch.upgraded) {
        tx.setHead(head)

        if (this.core.header.group) {
          this.updateGroupIndex(tx, head)
        }
      }

      const flushed = await this.flush()

      if (batch.upgraded) {
        this.roots = batch.roots
        this.length = batch.length
        this.byteLength = batch.byteLength
        this.fork = batch.fork
        this.signature = batch.signature

        this.onappend(head, bitfield, flushed)
      }

      if (this.core.hintsChanged && this.isDefault()) await this.core.flushHints()
    } finally {
      this._unlock()
    }

    return true
  }

  async truncate(length, fork, { signature, keyPair } = {}) {
    if (!keyPair && this.isDefault()) keyPair = this.core.header.keyPair

    await this.mutex.lock()

    try {
      if (this.prologue && length < this.prologue.length) {
        throw INVALID_OPERATION('Truncation breaks prologue', this.core.discoveryKey)
      }
      if (length > this.length) {
        throw INVALID_OPERATION(
          'Not a truncation, ' + length + ' must be less or equal to ' + this.length,
          this.core.discoveryKey
        )
      }

      const batch = this.createTreeBatch()
      await MerkleTree.truncate(this, length, batch, fork)

      if (signature) {
        batch.signature = signature
        this.core._verifyBatchUpgrade(batch)
      } else if (keyPair) {
        batch.signature = this.core.verifier.sign(batch, keyPair)
      }

      const tx = this.createWriteBatch()

      // upsert compat manifest
      if (this.core.verifier === null && keyPair) this.core._setManifest(tx, null, keyPair)

      const { dependency, tree, roots } = await this._truncate(tx, batch)

      for (const sessionState of this.core.sessionStates) {
        if (
          sessionState.isSnapshot() &&
          sessionState.name === this.name &&
          length < sessionState.snapshotCompatLength
        ) {
          sessionState.snapshotCompatLength = length
        }
      }

      const flushed = await this.flush()

      this.fork = tree.fork
      this.length = tree.length
      this.byteLength = MerkleTree.size(roots)
      this.roots = roots
      this.signature = tree.signature

      if (dependency) this.storage.setDependencyHead(dependency)

      this.ontruncate(tree, tree.length, batch.treeLength, flushed)

      if (this.core.hintsChanged && this.isDefault()) await this.core.flushHints()
    } finally {
      this._unlock()
    }
  }

  async reorg(batch) {
    await this.mutex.lock()

    const storage = this.createWriteBatch()

    try {
      if (!batch.commitable()) return false

      const { dependency, tree } = await this._truncate(storage, batch)

      const flushed = await this.flush()

      this.fork = batch.fork
      this.length = batch.length
      this.byteLength = batch.byteLength
      this.roots = batch.roots
      this.signature = batch.signature

      if (dependency) this.storage.setDependencyHead(dependency)

      this.ontruncate(tree, batch.ancestors, batch.treeLength, flushed)

      if (this.core.hintsChanged && this.isDefault()) await this.core.flushHints()
    } finally {
      this._unlock()
    }
  }

  async _truncate(storage, batch) {
    storage.deleteBlockRange(batch.ancestors, batch.treeLength)

    assert(batch.commitable(), 'Batch must be commitable')

    const tree = {
      fork: batch.fork,
      length: batch.length,
      rootHash: batch.hash(),
      signature: batch.signature,
      timestamp: Date.now()
    }

    if (this.isDefault() && this.core.header.group) {
      this.updateGroupIndex(storage, tree)
    }

    storage.setHead(tree)
    batch.commit(storage)

    const truncated = batch.length < this.flushedLength()
    const dependency = truncated ? updateDependency(this, batch.length, true) : null

    if (dependency) storage.setDependency(dependency)

    if (this.isDefault()) {
      await storeBitfieldRange(this.storage, storage, batch.ancestors, batch.treeLength, false)
      if (batch.ancestors < this.core.header.hints.contiguousLength) {
        this.core.header.hints.remoteContiguousLength = Math.min(
          batch.length,
          this.core.header.hints.remoteContiguousLength
        )
        storage.setHints({
          contiguousLength: batch.ancestors,
          remoteContiguousLength: this.core.header.hints.remoteContiguousLength
        })
      }
    }

    return { dependency, tree, roots: batch.roots }
  }

  async clear(start, end, cleared) {
    await this.mutex.lock()

    try {
      const tx = this.createWriteBatch()

      const ite = flat.iterator(start * 2)
      while (!isRootIndex(ite.index, this.roots)) {
        const a = ite.index
        const b = ite.sibling()
        ite.parent()

        const [left, right] = flat.spans(b)
        const s = left / 2
        const e = right / 2 + 1
        const has = s <= start && e <= end ? false : this.core.bitfield.hasSet(s, e - s)
        if (has) break

        tx.deleteTreeNode(a)
        tx.deleteTreeNode(b)
      }

      if (this.isDefault()) {
        await storeBitfieldRange(this.storage, tx, start, end, false)
        if (start < this.core.header.hints.contiguousLength) {
          tx.setHints({
            contiguousLength: start,
            remoteContiguousLength: this.core.header.hints.remoteContiguousLength
          })
        }
      }

      if (end - start === 1) tx.deleteBlock(start)
      else tx.deleteBlockRange(start, end)

      const dependency = start < this.flushedLength() ? updateDependency(this, start, true) : null

      const flushed = await this.flush()

      if (dependency) this.storage.setDependencyHead(dependency)

      // todo: atomic event handle
      if (this.isDefault() && flushed) {
        const length = end - start

        this.core.updateContiguousLength({ start, length, drop: true })
        this.core._setBitfieldRanges(start, end, false)
        this.core.replicator.onhave(start, length, true)

        if (this.core.hintsChanged) await this.core.flushHints()
      }
    } finally {
      this._unlock()
    }
  }

  // simple tool to wait for other peers to catch us up
  async waitForRecovery() {
    const length = this.length
    const replicator = this.core.replicator

    while (!this.closing) {
      let peers = getRecoveryPeers(replicator.peers)

      if (peers.length < 1) {
        await new Promise((resolve) => setTimeout(resolve, 5000))
        continue
      }

      let restart = false

      for (let i = 0; i < 32; i++) {
        await new Promise((resolve) => setTimeout(resolve, 250))

        peers = getRecoveryPeers(replicator.peers)

        if (this.closing || !validateRecoveryPeers(peers, this.length)) {
          restart = true
          break
        }
      }

      if (restart) continue

      if (this.length !== length) {
        throw SESSION_NOT_WRITABLE('core recovered during append')
      }

      this.core.header.hints.recovering = 0
      this.core.hintsChanged = true

      await this.mutex.lock()
      try {
        await this.core.flushHints()
        return
      } finally {
        this.mutex.unlock()
      }
    }

    throw SESSION_CLOSED('Cannot recover a closed session', this.discoveryKey)
  }

  async append(values, { signature, keyPair, preappend, postappend, maxLength = -1 } = {}) {
    if (!keyPair && this.isDefault()) keyPair = this.core.header.keyPair

    if (this.isDefault() && this.core.header.hints.recovering) await this.waitForRecovery()

    await this.mutex.lock()

    try {
      if (maxLength >= 0 && this.length + values.length > maxLength) {
        return { length: this.length, byteLength: this.byteLength }
      }

      const tx = this.createWriteBatch()

      // upsert compat manifest
      if (this.core.verifier === null && keyPair) this.core._setManifest(tx, null, keyPair)

      if (preappend) await preappend(values)

      if (!values.length) {
        await this.flush()
        return { length: this.length, byteLength: this.byteLength }
      }

      const batch = this.createTreeBatch()
      for (const val of values) batch.append(val)

      // only multisig can have prologue so signature is always present
      if (this.prologue && batch.length < this.prologue.length) {
        throw INVALID_OPERATION('Append is not consistent with prologue', this.core.discoveryKey)
      }

      if (signature) {
        batch.signature = signature
        this.core._verifyBatchUpgrade(batch)
      } else if (keyPair) {
        batch.signature = this.core.verifier.sign(batch, keyPair)
      }

      batch.commit(tx)

      const tree = {
        fork: batch.fork,
        length: batch.length,
        rootHash: batch.hash(),
        signature: batch.signature,
        timestamp: Date.now()
      }

      tx.setHead(tree)

      if (this.isDefault()) {
        await storeBitfieldRange(this.storage, tx, batch.ancestors, batch.length, true)
        if (this.length === this.core.header.hints.contiguousLength) {
          tx.setHints({
            contiguousLength: this.length + values.length,
            remoteContiguousLength: this.core.header.hints.remoteContiguousLength
          })
        }

        if (this.core.header.group) {
          this.updateGroupIndex(tx, tree)
        }
      }

      for (let i = 0; i < values.length; i++) {
        tx.putBlock(this.length + i, values[i])
      }

      const bitfield = {
        drop: false,
        start: batch.ancestors,
        length: values.length
      }

      const flushed = await this.flush()

      this.fork = batch.fork
      this.roots = batch.roots
      this.length = batch.length
      this.byteLength = batch.byteLength
      this.signature = batch.signature

      if (postappend) await postappend(values)

      this.onappend(tree, bitfield, flushed)

      if (this.core.hintsChanged && this.isDefault()) await this.core.flushHints()

      return { length: this.length, byteLength: this.byteLength }
    } finally {
      this._unlock()
    }
  }

  onappend(tree, bitfield, flushed) {
    if (!flushed) this._updateBitfield(bitfield)
    else if (this.isDefault()) this.core.onappend(tree, bitfield)

    for (let i = this.sessions.length - 1; i >= 0; i--) {
      this.sessions[i].emit('append')
    }

    this._flushHooks()
  }

  ontruncate(tree, to, from, flushed) {
    const bitfield = { start: to, length: from - to, drop: true }

    this.lastTruncation = { from, to }

    if (!flushed) this._updateBitfield(bitfield)
    else if (this.isDefault()) this.core.ontruncate(tree, bitfield)

    for (const sessionState of this.core.sessionStates) {
      if (
        sessionState.isSnapshot() &&
        sessionState.name === this.name &&
        to < sessionState.snapshotCompatLength
      ) {
        sessionState.snapshotCompatLength = to
      }
    }

    for (let i = this.sessions.length - 1; i >= 0; i--) {
      this.sessions[i].emit('truncate', to, tree.fork)
    }

    this._flushHooks()
  }

  _updateBitfield(bitfield, flushed) {
    if (!bitfield) return

    const p = this._pendingBitfield
    const b = bitfield

    if (b.drop) {
      // truncation must be from end
      if (p && b.start + b.length !== p.start + p.appends) {
        throw INVALID_OPERATION('Atomic truncations must be contiguous', this.core.discoveryKey)
      }

      // actual truncation
      if (p === null || b.start < p.start) {
        this._pendingBitfield = { truncated: true, start: b.start, appends: 0 }
        return
      }

      // just clearing batch data
      p.appends = b.start - p.start

      // we cleared the current batch
      if (p.appends === 0) this._pendingBitfield = null

      return
    }

    if (p === null) {
      this._pendingBitfield = { truncated: false, start: b.start, appends: b.length }
      return
    }

    if (b.start !== p.start + p.appends) {
      throw INVALID_OPERATION('Atomic operations must be contiguous', this.core.discoveryKey)
    }

    p.appends += b.length
  }

  async catchup(length) {
    assert(!this.isDefault(), 'Cannot catchup signed state') // TODO: make this check better

    await this.mutex.lock()

    try {
      const origLength = this.length

      let sharedLength = 0
      for (let i = this.storage.dependencies.length - 1; i >= 0; i--) {
        const dep = this.storage.dependencies[i]
        if (dep.dataPointer === this.core.state.storage.core.dataPointer) {
          sharedLength = dep.length
          break
        }
      }

      const tx = this.createWriteBatch()
      const rx = this.core.state.storage.read()
      const rootPromises = []

      for (const root of flat.fullRoots(length * 2)) {
        rootPromises.push(rx.getTreeNode(root))
      }

      rx.tryFlush()

      const roots = await Promise.all(rootPromises)
      const truncating = sharedLength < origLength

      for (const node of roots) {
        if (node === null) {
          throw INVALID_OPERATION(
            'Invalid catchup length, tree nodes not available',
            this.core.discoveryKey
          )
        }
      }

      const fork = truncating ? this.fork + 1 : this.fork

      // overwrite it atm, TODO: keep what we can connect to the tree
      tx.deleteBlockRange(0, -1)
      tx.deleteTreeNodeRange(0, -1)
      tx.deleteBitfieldPageRange(0, -1)

      const tree = {
        fork,
        length,
        rootHash: crypto.tree(roots),
        signature: null,
        timestamp: Date.now()
      }

      // never default state so do not update group index
      tx.setHead(tree)

      // prop a better way to do this
      const dep = updateDependency(this, sharedLength, true)
      dep.length = length

      tx.setDependency(dep)

      const flushed = await this.flush()

      this.storage.setDependencyHead(dep)

      this.fork = tree.fork
      this.roots = roots
      this.length = tree.length
      this.byteLength = MerkleTree.size(roots)

      if (truncating) this.ontruncate(tree, sharedLength, origLength, flushed)
      if (sharedLength < length) this.onappend(tree, null, flushed)

      if (this.core.hintsChanged && this.isDefault()) await this.core.flushHints()
    } finally {
      this._unlock()
    }
  }

  async _overwrite(source, fork, length, treeLength, signature) {
    const blockPromises = []
    const treePromises = []
    const rootPromises = []

    const rx = source.storage.read()

    for (const root of flat.fullRoots(length * 2)) {
      rootPromises.push(rx.getTreeNode(root))
    }

    for (const index of flat.patch(treeLength * 2, length * 2)) {
      treePromises.push(rx.getTreeNode(index))
    }

    for (let i = treeLength; i < length; i++) {
      treePromises.push(rx.getTreeNode(i * 2))
      treePromises.push(rx.getTreeNode(i * 2 + 1))
      blockPromises.push(rx.getBlock(i))
    }

    rx.tryFlush()

    const [blocks, nodes, roots] = await Promise.all([
      Promise.all(blockPromises),
      Promise.all(treePromises),
      Promise.all(rootPromises)
    ])

    if (this.core.destroyed) throw new Error('Core destroyed')

    if (signature) {
      const batch = this.createTreeBatch()
      batch.roots = roots
      batch.length = length

      if (!this.core.verifier.verify(batch, signature)) {
        throw INVALID_SIGNATURE(
          'Signature is not valid over committed tree',
          this.core.discoveryKey
        )
      }
    }

    const tx = this.createWriteBatch()

    // truncate existing tree
    if (treeLength < this.length) {
      tx.deleteBlockRange(treeLength, this.length)
    }

    for (const root of roots) tx.putTreeNode(root)

    // no nodes will be copied in shallow mode
    for (const node of nodes) {
      if (node !== null) tx.putTreeNode(node)
    }

    for (let i = 0; i < blocks.length; i++) {
      assert(blocks[i] !== null, 'has block')
      tx.putBlock(i + treeLength, blocks[i])
    }

    const totalLength = Math.max(length, this.length)

    if (totalLength > treeLength) {
      const firstPage = getBitfieldPage(treeLength)
      const lastPage = getBitfieldPage(totalLength - 1)

      const srx = this.storage.read()
      const bitfieldPagePromise = srx.getBitfieldPage(firstPage)
      srx.tryFlush()

      const bitfieldPage = await bitfieldPagePromise

      let index = treeLength

      for (let i = firstPage; i <= lastPage; i++) {
        const page = b4a.alloc(Bitfield.BYTES_PER_PAGE)
        tx.putBitfieldPage(i, page)

        // copy existing bits in
        if (i === firstPage && bitfieldPage) page.set(bitfieldPage)

        if (index < length) {
          index = fillBitfieldPage(page, index, length, i, true)
          if (index < length) continue
        }

        if (index < this.length) {
          index = fillBitfieldPage(page, index, this.length, i, false)
        }
      }
    }

    const tree = {
      fork,
      length,
      rootHash: crypto.tree(roots),
      signature,
      timestamp: Date.now()
    }

    if (this.isDefault() && this.core.header.group) {
      this.updateGroupIndex(tx, tree)
    }

    const upgraded = treeLength < this.length || this.length < length || tree.fork !== this.fork

    if (upgraded) tx.setHead(tree)

    const flushed = await this.flush()

    this.fork = tree.fork
    this.roots = roots
    this.length = length
    this.byteLength = MerkleTree.size(roots)
    this.signature = signature

    return { tree, flushed }
  }

  static isParent(state, parent) {
    // atomic batches have parent set
    if (state.parent && state.parent === parent) return true

    return HypercoreStorage.isParentStorage(state.storage, parent.storage)
  }

  async commit(state, { signature, keyPair, length = state.length, treeLength = -1 } = {}) {
    assert(
      this.isDefault() || (this.parent && this.parent.isDefault()),
      'Can only commit into default state'
    )

    assert(SessionState.isParent(state, this), 'Cannot commit across cores')

    if (this.core._repairMode) {
      throw ASSERTION('Cannot commit while repair mode is on')
    }

    if (this.core.header.hints.recovering) await this.waitForRecovery()

    let srcLocked = false
    await this.mutex.lock()

    try {
      await state.mutex.lock()
      srcLocked = true

      if (treeLength === -1) treeLength = state.flushedLength()

      if (!(await this.core._validateCommit(state, treeLength))) return null
      if (this.length > length) return null

      if (this.length < length && !signature) {
        if (!keyPair) keyPair = this.core.header.keyPair
        const batch = state.createTreeBatch()
        if (length !== batch.length) await batch.restore(length)
        signature = this.core.verifier.sign(batch, keyPair)
      }

      const { tree, flushed } = await this._overwrite(
        state,
        this.fork,
        length,
        treeLength,
        signature
      )

      // gc blocks from source
      if (treeLength < length) {
        const tx = state.createWriteBatch()

        tx.deleteBlockRange(treeLength, length)
        const dependency = state.updateDependency(tx, length)

        await state.flush(tx)

        if (dependency) state.storage.setDependencyHead(dependency)
      }

      const bitfield = { start: treeLength, length: length - treeLength, drop: false }
      this.onappend(tree, bitfield, flushed)

      return {
        length: this.length,
        byteLength: this.byteLength
      }
    } finally {
      if (srcLocked) state._unlock()
      this._unlock()
    }
  }

  async _getTreeHeadAt(length) {
    if (length === null) return this.treeInfo()

    const head = getDefaultTree()

    head.length = length

    const roots = await MerkleTree.getRootsFromStorage(this.storage, length)
    const rootHash = crypto.tree(roots)

    head.fork = this.fork
    head.rootHash = rootHash

    if (length === this.length) head.signature = this.signature

    return head
  }

  _moveToCore(core, truncated, appended) {
    const head = this.core.sessionStates.pop()
    if (head !== this) this.core.sessionStates[(head.index = this.index)] = head

    this.core = core
    this.index = this.core.sessionStates.push(this) - 1

    for (let i = this.sessions.length - 1; i >= 0; i--) {
      const s = this.sessions[i]
      const manifest = s.manifest
      s.transferSession(this.core)
      if (!manifest && s.manifest) s.emit('manifest')
      if (truncated) s.emit('truncate', truncated.to, truncated.fork)
      if (appended) s.emit('append')
    }
  }

  async moveTo(core, length) {
    const state = core.state

    await this.mutex.lock()

    try {
      // if (state.storage && (await state.storage.resumeSession(this.name)) !== null) {
      //   throw STORAGE_CONFLICT('Batch has already been created')
      // }

      const treeLength = this.length

      let truncated = null
      let appended = false

      if (!this.isSnapshot()) {
        if (this.lingers === null) this.lingers = []
        this.lingers.push(this.storage)

        const resumed = await state.storage.resumeSession(this.name)

        const truncation = length < this.length ? await truncateAndFlush(this, length) : null
        const treeInfo = truncation
          ? truncation.tree
          : resumed
            ? null
            : await state._getTreeHeadAt(this.length)

        const fork = truncation ? this.fork + 1 : this.fork

        // todo: validate treeInfo

        let storage = null

        if (resumed) {
          storage = resumed
        } else {
          treeInfo.fork = fork
          storage = await state.storage.createSession(this.name, treeInfo)
        }

        const roots = await MerkleTree.getRootsFromStorage(storage, length)

        this.storage = storage
        this.prologue = state.prologue
        this.fork = fork
        this.length = length
        this.byteLength = MerkleTree.size(roots)
        this.roots = roots

        if (truncation) {
          const { dependency } = truncation

          if (dependency) this.storage.setDependencyHead(dependency)
          truncated = { to: treeLength, fork }
        }

        if (this.length > treeLength) {
          appended = true
        }
      }

      for (let i = this.core.sessionStates.length - 1; i >= 0; i--) {
        const state = this.core.sessionStates[i]
        if (state === this) continue
        if (state.name === this.name) state._moveToCore(core.core)
      }

      this._moveToCore(core.core, truncated, appended)
    } finally {
      this._unlock()
    }
  }

  async createSession(name, overwrite, atom) {
    await this.mutex.lock()

    try {
      let storage = null
      let treeInfo = null

      if (!atom && !overwrite && this.storage) {
        storage = await this.storage.resumeSession(name)

        if (storage !== null) treeInfo = (await getCoreHead(storage)) || getDefaultTree()
      }

      const length = treeInfo ? treeInfo.length : this.length

      if (storage === null) {
        treeInfo = await this._getTreeHeadAt(length)

        if (atom) {
          storage = await this.storage.createAtomicSession(atom, treeInfo)
        } else {
          storage = await this.storage.createSession(name, treeInfo)
        }
      }

      if (this.atomized && atom) {
        throw new Error('Session already atomized')
      }

      const head = {
        fork: this.fork,
        roots:
          length === this.length
            ? this.roots.slice()
            : await MerkleTree.getRootsFromStorage(storage, length),
        length,
        prologue: this.prologue,
        signature: length === this.length ? this.signature : null
      }

      const state = new SessionState(
        this.core,
        atom ? this : null,
        storage,
        head,
        atom ? this.name : name
      )

      if (atom) {
        this.atomized = atom
        atom.preflush(this._precommit.bind(this, state))
        atom.onflush(state._commit.bind(state))
      }

      return state
    } finally {
      this._unlock()
    }
  }
}

module.exports = SessionState

function noop() {}

function getBitfieldPage(index) {
  return Math.floor(index / Bitfield.BITS_PER_PAGE)
}

function fillBitfieldPage(page, start, end, pageIndex, value) {
  const offset = pageIndex * Bitfield.BITS_PER_PAGE
  const max = offset + Bitfield.BITS_PER_PAGE

  const index = max < end ? max : end

  const from = start - offset
  const to = index - offset

  quickbit.fill(page, value, from, to)

  return index
}

async function storeBitfieldRange(storage, tx, from, to, value) {
  if (from >= to) return

  const firstPage = getBitfieldPage(from)
  const lastPage = getBitfieldPage(to - 1)

  let index = from

  const rx = storage.read()

  const promises = []
  for (let i = firstPage; i <= lastPage; i++) {
    promises.push(rx.getBitfieldPage(i))
  }

  rx.tryFlush()

  const pages = await Promise.all(promises)
  const cnt = lastPage - firstPage + 1

  for (let i = 0; i < cnt; i++) {
    const pageIndex = i + firstPage
    if (!pages[i]) pages[i] = b4a.alloc(Bitfield.BYTES_PER_PAGE)

    index = fillBitfieldPage(pages[i], index, to, pageIndex, value)
    tx.putBitfieldPage(pageIndex, pages[i])
  }
}

async function truncateAndFlush(s, length) {
  const batch = s.createTreeBatch()
  await MerkleTree.truncate(s, length, batch, s.fork)
  const tx = s.createWriteBatch()

  const info = await s._truncate(tx, batch)
  const flushed = await s.flush()

  return {
    tree: info.tree,
    roots: info.roots,
    dependency: info.dependency,
    flushed
  }
}

function updateDependency(state, length, truncated) {
  const i = state.storage.findDependencyIndex(length, truncated)
  if (i === -1) return null // skip default state and overlays of default

  return {
    dataPointer: state.storage.dependencies[i].dataPointer,
    length
  }
}

function getDefaultTree() {
  return {
    fork: 0,
    length: 0,
    rootHash: null,
    signature: null
  }
}

function getCoreHead(storage) {
  const b = storage.read()
  const p = b.getHead()
  b.tryFlush()
  return p
}

function isRootIndex(index, roots) {
  for (const node of roots) {
    if (node.index === index) return true
  }

  return false
}

function getRecoveryPeers(peers) {
  if (!SessionState.RECOVERY_PEERS) return peers

  const actual = []

  for (const p of peers) {
    for (const r of SessionState.RECOVERY_PEERS) {
      if (b4a.equals(p.remotePublicKey, r)) {
        actual.push(p)
        break
      }
    }
  }

  return actual
}

function validateRecoveryPeers(peers, length) {
  if (peers.length < 1) return false

  for (const p of peers) {
    if (p.remoteLength > length) return false
    if (p.remoteLength === 0 && !p.remoteSynced) return false
  }

  return true
}
const { Writable, Readable } = require('streamx')

class ReadStream extends Readable {
  constructor(core, opts = {}) {
    super()

    this.core = core
    this.start = opts.start || 0
    this.end = typeof opts.end === 'number' ? opts.end : -1
    this.snapshot = !opts.live && opts.snapshot !== false
    this.live = this.end === -1 ? !!opts.live : false
    this.wait = typeof opts.wait === 'boolean' ? opts.wait : this.core.wait
    this.timeout = opts.timeout || core.timeout
  }

  _open(cb) {
    this._openP().then(cb, cb)
  }

  _read(cb) {
    this._readP().then(cb, cb)
  }

  async _openP() {
    if (this.end === -1) await this.core.update()
    else await this.core.ready()
    if (this.snapshot && this.end === -1) this.end = this.core.length
  }

  async _readP() {
    const end = this.live ? -1 : this.end === -1 ? this.core.length : this.end
    if (end >= 0 && this.start >= end) {
      this.push(null)
      return
    }

    this.push(await this.core.get(this.start++, { wait: this.wait, timeout: this.timeout }))
  }
}

exports.ReadStream = ReadStream

class WriteStream extends Writable {
  constructor(core) {
    super()
    this.core = core
  }

  _writev(batch, cb) {
    this._writevP(batch).then(cb, cb)
  }

  async _writevP(batch) {
    await this.core.append(batch)
  }
}

exports.WriteStream = WriteStream

class ByteStream extends Readable {
  constructor(core, opts = {}) {
    super()

    this._core = core
    this._index = 0
    this._range = null

    this._byteOffset = opts.byteOffset || 0
    this._byteLength = typeof opts.byteLength === 'number' ? opts.byteLength : -1
    this._prefetch = typeof opts.prefetch === 'number' ? opts.prefetch : 32

    this._applyOffset = this._byteOffset > 0
  }

  _open(cb) {
    this._openp().then(cb, cb)
  }

  _read(cb) {
    this._readp().then(cb, cb)
  }

  async _openp() {
    if (this._byteLength === -1) {
      await this._core.update()
      this._byteLength = Math.max(this._core.byteLength - this._byteOffset, 0)
    }
  }

  async _readp() {
    let data = null

    if (this._byteLength === 0) {
      this.push(null)
      return
    }

    let relativeOffset = 0

    if (this._applyOffset) {
      this._applyOffset = false

      const [block, byteOffset] = await this._core.seek(this._byteOffset)

      this._index = block
      relativeOffset = byteOffset
    }

    this._predownload(this._index + 1)
    data = await this._core.get(this._index++, { valueEncoding: 'binary' })

    if (relativeOffset > 0) data = data.subarray(relativeOffset)

    if (data.byteLength > this._byteLength) data = data.subarray(0, this._byteLength)
    this._byteLength -= data.byteLength

    this.push(data)
    if (this._byteLength === 0) this.push(null)
  }

  _predownload(index) {
    if (this._range) this._range.destroy()
    this._range = this._core.download({ start: index, end: index + this._prefetch, linear: true })
  }

  _destroy(cb) {
    if (this._range) this._range.destroy()
    cb(null)
  }
}

exports.ByteStream = ByteStream
const crypto = require('hypercore-crypto')
const b4a = require('b4a')
const c = require('compact-encoding')
const flat = require('flat-tree')
const { BAD_ARGUMENT } = require('hypercore-errors')
const unslab = require('unslab')

const m = require('./messages')
const multisig = require('./multisig')
const caps = require('./caps')

class Signer {
  constructor(
    manifestHash,
    version,
    index,
    { signature = 'ed25519', publicKey, namespace = caps.DEFAULT_NAMESPACE } = {}
  ) {
    if (!publicKey) throw BAD_ARGUMENT('public key is required for a signer')
    if (signature !== 'ed25519') throw BAD_ARGUMENT('Only Ed25519 signatures are supported')

    this.manifestHash = manifestHash
    this.version = version
    this.signer = index
    this.signature = signature
    this.publicKey = publicKey
    this.namespace = namespace
  }

  _ctx() {
    return this.version === 0 ? this.namespace : this.manifestHash
  }

  verify(batch, signature) {
    return crypto.verify(batch.signable(this._ctx()), signature, this.publicKey)
  }

  sign(batch, keyPair) {
    return crypto.sign(batch.signable(this._ctx()), keyPair.secretKey)
  }
}

class CompatSigner extends Signer {
  constructor(index, signer, legacy) {
    super(null, 0, index, signer)
    this.legacy = legacy
  }

  verify(batch, signature) {
    return crypto.verify(batch.signableCompat(this.legacy), signature, this.publicKey)
  }

  sign(batch, keyPair) {
    return crypto.sign(batch.signableCompat(this.legacy), keyPair.secretKey)
  }
}

module.exports = class Verifier {
  constructor(
    manifestHash,
    manifest,
    { compat = isCompat(manifestHash, manifest), legacy = false } = {}
  ) {
    const self = this

    this.manifestHash = manifestHash
    this.compat = compat || manifest === null
    this.version = this.compat ? 0 : typeof manifest.version === 'number' ? manifest.version : 1
    this.hash = manifest.hash || 'blake2b'
    this.allowPatch = !this.compat && !!manifest.allowPatch
    this.quorum = this.compat ? 1 : defaultQuorum(manifest)

    this.signers = manifest.signers ? manifest.signers.map(createSigner) : []
    this.prologue = this.compat ? null : manifest.prologue || null

    function createSigner(signer, index) {
      return self.compat
        ? new CompatSigner(index, signer, legacy)
        : new Signer(manifestHash, self.version, index, signer)
    }
  }

  _verifyCompat(batch, signature) {
    if (!signature) return false

    if (this.compat || (!this.allowPatch && this.signers.length === 1)) {
      return !!signature && this.signers[0].verify(batch, signature)
    }

    return this._verifyMulti(batch, signature)
  }

  _inflate(signature) {
    if (this.version >= 1) return multisig.inflate(signature)
    const { proofs, patch } = multisig.inflatev0(signature)

    return {
      proofs: proofs.map(proofToVersion1),
      patch
    }
  }

  _verifyMulti(batch, signature) {
    if (!signature || this.quorum === 0) return false

    const { proofs, patch } = this._inflate(signature)
    if (proofs.length < this.quorum) return false

    const tried = new Uint8Array(this.signers.length)
    const nodes = this.allowPatch && patch.length ? toMap(patch) : null

    for (let i = 0; i < this.quorum; i++) {
      const inp = proofs[i]

      let tree = batch

      if (inp.patch && this.allowPatch) {
        tree = batch.clone()

        const upgrade = generateUpgrade(nodes, batch.length, inp.patch)
        const proof = {
          fork: tree.fork,
          block: null,
          hash: null,
          seek: null,
          upgrade,
          manifest: null
        }

        try {
          if (!tree.verifyUpgrade(proof)) return false
        } catch {
          return false
        }
      }

      if (inp.signer >= this.signers.length || tried[inp.signer]) return false
      tried[inp.signer] = 1

      const s = this.signers[inp.signer]
      if (!s.verify(tree, inp.signature)) return false
    }

    return true
  }

  verify(batch, signature) {
    if (this.version === 0) {
      return this._verifyCompat(batch, signature)
    }

    if (this.prologue !== null && batch.length <= this.prologue.length) {
      return batch.length === this.prologue.length && b4a.equals(batch.hash(), this.prologue.hash)
    }

    return this._verifyMulti(batch, signature)
  }

  // TODO: better api for this that is more ... multisig-ey
  sign(batch, keyPair) {
    if (!keyPair || !keyPair.secretKey) throw BAD_ARGUMENT('No key pair was passed')

    for (const s of this.signers) {
      if (b4a.equals(s.publicKey, keyPair.publicKey)) {
        const signature = s.sign(batch, keyPair)
        if (this.signers.length !== 1 || this.version === 0) return signature
        return this.assemble([{ signer: 0, signature, patch: 0, nodes: null }])
      }
    }

    throw BAD_ARGUMENT('Public key is not a declared signer')
  }

  assemble(inputs) {
    return this.version === 0 ? multisig.assemblev0(inputs) : multisig.assemble(inputs)
  }

  static manifestHash(manifest) {
    return manifestHash(manifest)
  }

  static encodeManifest(manifest) {
    return c.encode(m.manifest, manifest)
  }

  static decodeManifest(manifest) {
    return c.decode(m.manifest, manifest)
  }

  static defaultSignerManifest(publicKey) {
    return {
      version: 1,
      hash: 'blake2b',
      allowPatch: false,
      quorum: 1,
      signers: [
        {
          signature: 'ed25519',
          namespace: caps.DEFAULT_NAMESPACE,
          publicKey
        }
      ],
      prologue: null,
      linked: null,
      userData: null
    }
  }

  static fromManifest(manifest, opts) {
    const m = this.createManifest(manifest)
    return new this(manifestHash(m), m, opts)
  }

  static createManifest(inp) {
    if (!inp) return null

    if (b4a.isBuffer(inp)) return Verifier.decodeManifest(inp)

    if (inp.quorum && inp.quorum < 0) throw BAD_ARGUMENT('Quorum cannot be negative')
    if (inp.quorum && inp.signers && inp.quorum > inp.signers.length) {
      throw BAD_ARGUMENT('Quorum should not be higher than the number of signers')
    }

    const manifest = {
      version: getManifestVersion(inp), // defaults to v1
      hash: 'blake2b',
      allowPatch: !!inp.allowPatch,
      quorum: defaultQuorum(inp),
      signers: inp.signers ? inp.signers.map(parseSigner) : [],
      prologue: null,
      linked: null,
      userData: inp.userData || null
    }

    if (inp.hash && inp.hash !== 'blake2b') throw BAD_ARGUMENT('Only Blake2b hashes are supported')

    if (inp.prologue) {
      if (
        !(b4a.isBuffer(inp.prologue.hash) && inp.prologue.hash.byteLength === 32) ||
        !(inp.prologue.length >= 0)
      ) {
        throw BAD_ARGUMENT('Invalid prologue')
      }

      manifest.prologue = inp.prologue
      manifest.prologue.hash = unslab(manifest.prologue.hash)
    }

    if (manifest.userData !== null && manifest.version < 2) {
      throw BAD_ARGUMENT('Invalid field: userData')
    }

    if (inp.linked && inp.linked.length) {
      if (manifest.version < 2) throw BAD_ARGUMENT('Invalid field: linked')

      for (const key of inp.linked) {
        if (!(b4a.isBuffer(key) && key.byteLength === 32)) {
          throw BAD_ARGUMENT('Invalid key')
        }
      }

      manifest.linked = inp.linked
    }

    return manifest
  }

  static isValidManifest(key, manifest) {
    return b4a.equals(key, manifestHash(manifest))
  }

  static isCompat(key, manifest) {
    return isCompat(key, manifest)
  }

  static sign(manifest, batch, keyPair, opts) {
    return Verifier.fromManifest(manifest, opts).sign(batch, keyPair)
  }
}

function toMap(nodes) {
  const m = new Map()
  for (const node of nodes) m.set(node.index, node)
  return m
}

function isCompat(key, manifest) {
  return !!(
    manifest &&
    manifest.signers.length === 1 &&
    b4a.equals(key, manifest.signers[0].publicKey)
  )
}

function defaultQuorum(man) {
  if (typeof man.quorum === 'number') return man.quorum
  if (!man.signers || !man.signers.length) return 0
  return (man.signers.length >> 1) + 1
}

function generateUpgrade(patch, start, length) {
  const upgrade = { start, length, nodes: null, additionalNodes: [], signature: null }

  const from = start * 2
  const to = from + length * 2

  for (const ite = flat.iterator(0); ite.fullRoot(to); ite.nextTree()) {
    if (ite.index + ite.factor / 2 < from) continue

    if (upgrade.nodes === null && ite.contains(from - 2)) {
      upgrade.nodes = []

      const root = ite.index
      const target = from - 2

      ite.seek(target)

      while (ite.index !== root) {
        ite.sibling()
        if (ite.index > target) upgrade.nodes.push(patch.get(ite.index))
        ite.parent()
      }

      continue
    }

    if (upgrade.nodes === null) upgrade.nodes = []
    upgrade.nodes.push(patch.get(ite.index))
  }

  if (upgrade.nodes === null) upgrade.nodes = []
  return upgrade
}

function parseSigner(signer) {
  validateSigner(signer)
  return {
    signature: 'ed25519',
    namespace: unslab(signer.namespace || caps.DEFAULT_NAMESPACE),
    publicKey: unslab(signer.publicKey)
  }
}

function validateSigner(signer) {
  if (!signer || !signer.publicKey) throw BAD_ARGUMENT('Signer missing public key')
  if (signer.signature && signer.signature !== 'ed25519') {
    throw BAD_ARGUMENT('Only Ed25519 signatures are supported')
  }
}

function manifestHash(manifest) {
  const state = { start: 0, end: 32, buffer: null }
  m.manifest.preencode(state, manifest)
  state.buffer = b4a.allocUnsafe(state.end)
  c.raw.encode(state, caps.MANIFEST)
  m.manifest.encode(state, manifest)
  return crypto.hash(state.buffer)
}

function proofToVersion1(proof) {
  return {
    signer: proof.signer,
    signature: proof.signature,
    patch: proof.patch ? proof.patch.length : 0
  }
}

function getManifestVersion(inp) {
  if (typeof inp.version === 'number') return inp.version
  if (inp.linked && inp.linked.length) return 2
  if (inp.userData) return 2
  return 1
}
let COMPAT = true
let BATCH_UINTS = COMPAT ? 65536 : 128
let BATCH_BYTES = BATCH_UINTS * 4
let BATCH = BATCH_BYTES * 8 // in bits
const MAX_REMOTE_BATCHES = 4
const MAX_RANGE = 8 * 256 * 1024
const MIN_RANGE = 32 // 4bit ints
const MAX = 512
const MAX_ANY = 16

class LocalWants {
  constructor(peer) {
    this.destroyed = false
    this.peer = peer
    this.wants = new Map()
    this.free = new Set()
    this.any = null
  }

  add(index, handle) {
    const b = (index - (index & (BATCH - 1))) / BATCH
    return this.addBatch(b, handle)
  }

  addAnyRange(start, length, handle) {
    if (this.destroyed) return null
    if (this.any === null) this.any = []

    if (COMPAT) {
      let r = start & (BATCH - 1)
      start -= r
      length += r
      r = length & (BATCH - 1)
      if (r) length = length - r + BATCH
    }

    for (let i = 0; i < this.any.length; i++) {
      const w = this.any[i]
      if (w.start === start && w.length === length) {
        w.handles.add(handle)
        handle.addWant(w)
        return null
      }
    }

    const w = { wants: this, start, length, any: true, handles: new Set([handle]) }
    this.any.push(w)
    handle.addWant(w)

    return { start, length, any: !COMPAT }
  }

  removeAnyRange(start, length, handle) {
    if (this.any === null) return null

    for (let i = 0; i < this.any.length; i++) {
      const w = this.any[i]
      if (w.start !== start || w.length !== length) continue

      w.handles.delete(handle)
      handle.removeWant(w)
      if (w.handles.size > 0) return null

      const head = this.any.pop()
      if (head !== w) this.any[i] = head
      return { start, length, any: !COMPAT }
    }

    return null
  }

  addBatch(index, handle) {
    if (this.destroyed) return null
    let w = this.wants.get(index)

    if (w) {
      const size = w.handles.size
      w.handles.add(handle)
      if (w.handles.size !== size) {
        handle.addWant(w)
      }
      return null
    }

    // start here is the batch number for simplicity....
    w = { wants: this, start: index, length: 0, any: false, handles: new Set([handle]) }

    if (this.free.has(index)) {
      this.free.delete(index)
      this.wants.set(index, w)
      handle.addWant(w)
      return null
    }

    let unwant = null
    if (this.wants.size + this.free.size === MAX) {
      if (this.free.size === 0) {
        return null
      }
      unwant = this._unwant()
    }

    this.wants.set(index, w)
    handle.addWant(w)

    return {
      want: { start: index * BATCH, length: BATCH, any: false },
      unwant
    }
  }

  remove(index, handle) {
    const b = (index - (index & (BATCH - 1))) / BATCH
    return this.removeBatch(b, handle)
  }

  removeBatch(index, handle) {
    if (this.destroyed) return false

    const w = this.wants.get(index)
    if (!w) {
      return false
    }

    w.handles.delete(handle)
    handle.removeWant(w)

    if (w.handles.size === 0) {
      this.free.add(index)
      this.wants.delete(index)
      return this.free.size === 1 && this.wants.size === MAX - 1
    }

    return false
  }

  destroy() {
    if (this.destroyed) return
    this.destroyed = true

    for (const w of this.wants.values()) {
      for (const handle of w.handles) {
        handle.removeWant(w)
      }
    }

    this.wants.clear()
  }

  _unwant() {
    for (const index of this.free) {
      this.free.delete(index)
      return { start: index * BATCH, length: BATCH, any: false }
    }
    return null
  }
}

class RemoteWants {
  constructor() {
    this.any = null
    this.all = false
    this.size = 0
    this.batches = []
  }

  hasAny(start, length) {
    for (let i = 0; i < this.any.length; i++) {
      const a = this.any[i]
      const e = start + length
      const end = a.start + a.length

      if (a.start <= start && start < end) return true
      if (a.start < e && e <= end) return true
    }

    return this.all
  }

  hasRange(start, length) {
    if (this.any !== null && this.hasAny(start, length)) return true
    if (length === 1) return this.has(start)

    let smallest = -1

    for (let i = 0; i < this.batches.length; i++) {
      const b = this.batches[i]
      if (b.length < smallest || smallest === -1) smallest = b.length
    }

    if (smallest === -1) return this.all

    const r = start & (smallest - 1)
    const end = start + length

    let max = 3

    for (let i = start - r; i < end; i += smallest) {
      if (max-- === 0) return true // just to save work
      if (this.has(i)) return true
    }

    return this.all
  }

  has(index) {
    for (let i = 0; i < this.batches.length; i++) {
      const b = this.batches[i]
      const block = (index - (index & (b.length - 1))) / b.length
      if (b.ranges.has(block)) return true
    }

    return this.all
  }

  add(range) {
    if (range.any) {
      if (this.any === null) this.any = []
      if (this.any.length < MAX_ANY) this.any.push(range)
      else this.all = true
      return true
    }

    if (range.length > MAX_RANGE) return false

    if (!validateBatchRange(range)) {
      this.all = true
      return true
    }

    if (this.size >= MAX) {
      this.all = true
      return true
    }

    for (let i = 0; i < this.batches.length; i++) {
      const b = this.batches[i]
      if (b.length === range.length) {
        b.ranges.add(range.start / range.length)
        this.size++
        return true
      }
    }

    if (this.batches.length >= MAX_REMOTE_BATCHES) {
      this.all = true
      return true
    }

    this.batches.push({
      length: range.length,
      ranges: new Set([range.start / range.length])
    })
    this.size++

    return true
  }

  remove(range) {
    if (range.any) {
      if (this.any === null) return false

      for (let i = 0; i < this.any.length; i++) {
        const a = this.any[i]

        if (a.start === range.start && a.length === range.length) {
          const head = this.any.pop()
          if (head !== a) this.any[i] = head
          return true
        }
      }

      return false
    }

    if (!validateBatchRange(range)) return false

    for (let i = 0; i < this.batches.length; i++) {
      const b = this.batches[i]
      if (b.length !== range.length) continue

      const size = b.ranges.size
      b.ranges.delete(range.start / range.length)
      if (b.ranges.size !== size) this.size--

      if (b.ranges.size === 0) {
        const head = this.batches.pop()
        if (head !== b) this.batches[i] = head
      }

      return true
    }

    return false
  }
}

function UPDATE_COMPAT(isCompat) {
  COMPAT = isCompat
  BATCH_UINTS = COMPAT ? 65536 : 128
  BATCH_BYTES = BATCH_UINTS * 4
  BATCH = BATCH_BYTES * 8 // in bits
  exports.WANT_BATCH = BATCH
}

exports.LocalWants = LocalWants
exports.RemoteWants = RemoteWants
exports.WANT_BATCH = BATCH
exports.UPDATE_COMPAT = UPDATE_COMPAT

function validateBatchRange(range) {
  if (range.length > MAX_RANGE || range.length < MIN_RANGE) {
    return false
  }
  // check if power of two
  if ((range.length & (range.length - 1)) !== 0) {
    return false
  }
  // start must be a multiple of the length
  if (range.start & (range.length - 1)) {
    return false
  }

  return true
}
{
  "name": "hypercore",
  "version": "11.35.4",
  "description": "Hypercore is a secure, distributed append-only log",
  "main": "index.js",
  "scripts": {
    "format": "prettier --write .",
    "lint": "prettier --check . && lunte",
    "test": "brittle test/all.js",
    "test:bare": "bare test/all.js",
    "test:generate": "brittle -r test/all.js test/*.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/hypercore.git"
  },
  "contributors": [
    {
      "name": "Mathias Buus",
      "email": "mathiasbuus@gmail.com",
      "url": "https://mafinto.sh"
    },
    {
      "name": "Andrew Osheroff",
      "email": "andrewosh@gmail.com",
      "url": "https://andrewosh.com"
    }
  ],
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/holepunchto/hypercore/issues"
  },
  "homepage": "https://github.com/holepunchto/hypercore#readme",
  "files": [
    "index.js",
    "errors.js",
    "messages.js",
    "lib/**.js"
  ],
  "imports": {
    "events": {
      "bare": "bare-events",
      "default": "events"
    }
  },
  "dependencies": {
    "@hyperswarm/secret-stream": "^6.0.0",
    "b4a": "^1.1.0",
    "bare-events": "^2.2.0",
    "big-sparse-array": "^1.0.3",
    "compact-encoding": "^3.0.0",
    "fast-fifo": "^1.3.0",
    "flat-tree": "^1.9.0",
    "hypercore-crypto": "^3.2.1",
    "hypercore-errors": "^1.5.0",
    "hypercore-id-encoding": "^1.2.0",
    "hypercore-storage": "^3.2.0",
    "is-options": "^1.0.1",
    "nanoassert": "^2.0.0",
    "protomux": "^3.5.0",
    "quickbit-universal": "^2.2.0",
    "random-array-iterator": "^1.0.0",
    "safety-catch": "^1.0.1",
    "sodium-universal": "^5.0.1",
    "streamx": "^2.12.4",
    "unslab": "^1.3.0",
    "z32": "^1.0.0"
  },
  "devDependencies": {
    "brittle": "^3.0.0",
    "debugging-stream": "^3.1.0",
    "hyperswarm": "^4.3.6",
    "lunte": "^1.3.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^2.0.0",
    "rache": "^1.0.0",
    "range-parser": "^1.2.1",
    "resolve-reject-promise": "^1.1.0",
    "speedometer": "^1.1.0",
    "test-tmp": "^1.0.2",
    "tiny-byte-size": "^1.1.0",
    "udx-native": "^1.6.1",
    "uncaughts": "^1.1.0"
  }
}
const c = require('compact-encoding')
const { getEncoding } = require('./spec/hyperschema')

const HyperdhtAddress = getEncoding('@hyperdht/address')

function decode(buf) {
  const addr = c.decode(HyperdhtAddress, buf)
  return addr
}

function encode(key, nodes) {
  return c.encode(HyperdhtAddress, { key, nodes })
}

module.exports = { decode, encode }
{
  "name": "hyperdht-address",
  "version": "1.1.1",
  "description": "hyperdht-address",
  "main": "index.js",
  "exports": {
    "./package": "./package.json",
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    }
  },
  "files": [
    "package.json",
    "index.js",
    "index.d.ts",
    "spec"
  ],
  "devDependencies": {
    "b4a": "^1.8.0",
    "brittle": "^3.19.0",
    "lunte": "^1.2.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^2.0.0"
  },
  "scripts": {
    "format": "prettier . --write",
    "test": "prettier . --check && lunte && brittle-bare test/index.js",
    "lint": "lunte"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/holepunchto/hyperdht-address.git"
  },
  "author": "Holepunch Inc",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/hyperdht-address/issues"
  },
  "homepage": "https://github.com/holepunchto/hyperdht-address",
  "dependencies": {
    "compact-encoding": "^3.0.0",
    "hyperschema": "^1.20.1"
  }
}
// This file is autogenerated by the hyperschema compiler
// Schema Version: 2
/* eslint-disable camelcase */
/* eslint-disable quotes */
/* eslint-disable space-before-function-paren */

const { c } = require('hyperschema/runtime')

const VERSION = 2

// eslint-disable-next-line no-unused-vars
let version = VERSION

// @hyperdht/address.nodes
const encoding0_1 = c.array(c.ipv4Address)

// @hyperdht/address
const encoding0 = {
  preencode(state, m) {
    c.fixed32.preencode(state, m.key)
    state.end++ // max flag is 1 so always one byte

    if (version >= 2 && m.nodes) encoding0_1.preencode(state, m.nodes)
  },
  encode(state, m) {
    const flags = version >= 2 && m.nodes ? 1 : 0

    c.fixed32.encode(state, m.key)
    c.uint.encode(state, flags)

    if (version >= 2 && m.nodes) encoding0_1.encode(state, m.nodes)
  },
  decode(state) {
    const r0 = c.fixed32.decode(state)
    const flags = state.start < state.end ? c.uint.decode(state) : 0

    return {
      key: r0,
      nodes: version >= 2 && (flags & 1) !== 0 ? encoding0_1.decode(state) : null
    }
  }
}

function setVersion(v) {
  version = v
}

function encode(name, value, v = VERSION) {
  version = v
  return c.encode(getEncoding(name), value)
}

function decode(name, buffer, v = VERSION) {
  version = v
  return c.decode(getEncoding(name), buffer)
}

function getEnum(name) {
  switch (name) {
    default:
      throw new Error('Enum not found ' + name)
  }
}

function getEncoding(name) {
  switch (name) {
    case '@hyperdht/address':
      return encoding0
    default:
      throw new Error('Encoder not found ' + name)
  }
}

function getStruct(name, v = VERSION) {
  const enc = getEncoding(name)
  return {
    preencode(state, m) {
      version = v
      enc.preencode(state, m)
    },
    encode(state, m) {
      version = v
      enc.encode(state, m)
    },
    decode(state) {
      version = v
      return enc.decode(state)
    }
  }
}

const resolveStruct = getStruct // compat

module.exports = {
  resolveStruct,
  getStruct,
  getEnum,
  getEncoding,
  encode,
  decode,
  setVersion,
  version
}
const DHT = require('dht-rpc')
const sodium = require('sodium-universal')
const c = require('compact-encoding')
const b4a = require('b4a')
const safetyCatch = require('safety-catch')
const m = require('./lib/messages')
const SocketPool = require('./lib/socket-pool')
const Persistent = require('./lib/persistent')
const Router = require('./lib/router')
const Cache = require('xache')
const Server = require('./lib/server')
const connect = require('./lib/connect')
const { FIREWALL, BOOTSTRAP_NODES, COMMANDS } = require('./lib/constants')
const { hash, createKeyPair } = require('./lib/crypto')
const RawStreamSet = require('./lib/raw-stream-set')
const ConnectionPool = require('./lib/connection-pool')
const { STREAM_NOT_CONNECTED } = require('./lib/errors')

const DEFAULTS = {
  ...DHT.DEFAULTS,
  connectionKeepAlive: 5000,
  randomPunchInterval: 20000
}

class HyperDHT extends DHT {
  constructor(opts = {}) {
    const port = opts.port || 49737
    const bootstrap = opts.bootstrap || BOOTSTRAP_NODES
    const nodes = opts.nodes || []

    super({ ...opts, port, bootstrap, nodes, filterNode })

    const { router, relayAddresses, persistent } = defaultCacheOpts(opts)

    this.defaultKeyPair = opts.keyPair || createKeyPair(opts.seed)
    this.listening = new Set()
    this.connectionKeepAlive =
      opts.connectionKeepAlive === false
        ? 0
        : opts.connectionKeepAlive || DEFAULTS.connectionKeepAlive

    this.rawStreams = new RawStreamSet(this)
    this.plugins = new Map()

    this._router = new Router(this, router)
    this._socketPool = new SocketPool(this, opts.host || '0.0.0.0')
    this._persistent = null
    this._validatedLocalAddresses = new Map()
    this._relayAddressesCache = new Cache(relayAddresses)

    this._deferRandomPunch = !!opts.deferRandomPunch
    this._lastRandomPunch = this._deferRandomPunch ? Date.now() : 0
    this._connectable = true
    this._randomPunchInterval = opts.randomPunchInterval || DEFAULTS.randomPunchInterval // min 20s between random punches...
    this._randomPunches = 0
    this._randomPunchLimit = 1 // set to one for extra safety for now

    // stats is inherited from dht-rpc so fwd the ones from there
    this.stats = {
      punches: {
        consistent: 0,
        random: 0,
        open: 0,
        tryLater: 0,
        tryLaterRelayedHandshakesAtLeastOnce: 0
      },
      relaying: { attempts: 0, successes: 0, aborts: 0 },
      socketPool: this._socketPool.stats,
      ...this.stats
    }

    this.once('persistent', () => {
      this._persistent = new Persistent(this, persistent)
      for (const plugin of this.plugins.values()) plugin.onpersistent()
    })

    this.on('network-change', () => {
      for (const server of this.listening) server.refresh()
    })

    this.on('network-update', () => {
      if (!this.online) return
      for (const server of this.listening) server.notifyOnline()
    })
  }

  static DEFAULTS = DEFAULTS

  connect(remotePublicKey, opts) {
    return connect(this, remotePublicKey, opts)
  }

  createServer(opts, onconnection) {
    if (typeof opts === 'function') return this.createServer({}, opts)
    if (opts && opts.onconnection) onconnection = opts.onconnection
    const s = new Server(this, opts)
    if (onconnection) s.on('connection', onconnection)
    return s
  }

  pool() {
    return new ConnectionPool(this)
  }

  async resume({ log = noop } = {}) {
    if (this._deferRandomPunch) this._lastRandomPunch = Date.now()
    await super.resume({ log })
    const resuming = []
    for (const server of this.listening) resuming.push(server.resume())
    log('Resuming hyperdht servers')
    await Promise.allSettled(resuming)
    log('Done, hyperdht fully resumed')
  }

  async suspend({ log = noop } = {}) {
    this._connectable = false // just so nothing gets connected during suspension
    const suspending = []
    for (const server of this.listening) suspending.push(server.suspend())
    log('Suspending all hyperdht servers')
    await Promise.allSettled(suspending)
    log('Done, clearing all raw streams')
    await this.rawStreams.clear()
    log('Done, suspending dht-rpc')
    await super.suspend({ log })
    log('Done, clearing raw streams again')
    await this.rawStreams.clear()
    log('Done, hyperdht fully suspended')
    this._connectable = true
  }

  async destroy({ force = false } = {}) {
    if (!force) {
      const closing = []
      for (const server of this.listening) closing.push(server.close())
      await Promise.allSettled(closing)
    }
    this._router.destroy()
    if (this._persistent) this._persistent.destroy()
    for (const plugin of this.plugins.values()) plugin.destroy()
    await this.rawStreams.clear()
    await this._socketPool.destroy()
    await super.destroy()
  }

  async validateLocalAddresses(addresses) {
    const list = []
    const socks = []
    const waiting = []

    for (const addr of addresses) {
      const { host } = addr

      if (this._validatedLocalAddresses.has(host)) {
        if (await this._validatedLocalAddresses.get(host)) {
          list.push(addr)
        }
        continue
      }

      const sock = this.udx.createSocket()
      try {
        sock.bind(0, host)
      } catch {
        this._validatedLocalAddresses.set(host, Promise.resolve(false))
        continue
      }

      socks.push(sock)

      // semi terrible heuristic until we proper fix local connections by racing them to the remote...
      const promise = new Promise((resolve) => {
        sock.on('message', () => resolve(true))
        setTimeout(() => resolve(false), 500)
        sock.trySend(b4a.alloc(1), sock.address().port, addr.host)
      })

      this._validatedLocalAddresses.set(host, promise)
      waiting.push(addr)
    }

    for (const addr of waiting) {
      const { host } = addr
      if (this._validatedLocalAddresses.has(host)) {
        if (await this._validatedLocalAddresses.get(host)) {
          list.push(addr)
        }
        continue
      }
    }

    for (const sock of socks) await sock.close()

    return list
  }

  findPeer(publicKey, opts = {}) {
    const target = opts.hash === false ? publicKey : hash(publicKey)
    opts = { ...opts, map: mapFindPeer }
    return this.query({ target, command: COMMANDS.FIND_PEER, value: null }, opts)
  }

  lookup(target, opts = {}) {
    opts = { ...opts, map: mapLookup }
    return this.query({ target, command: COMMANDS.LOOKUP, value: null }, opts)
  }

  lookupAndUnannounce(target, keyPair, opts = {}) {
    const unannounces = []
    const dht = this
    const userCommit = opts.commit || noop
    const signUnannounce = opts.signUnannounce || Persistent.signUnannounce

    if (this._persistent !== null) {
      // unlink self
      this._persistent.unannounce(target, keyPair.publicKey)
    }

    opts = { ...opts, map, commit }
    return this.query({ target, command: COMMANDS.LOOKUP, value: null }, opts)

    async function commit(reply, dht, query) {
      await Promise.all(unannounces) // can never fail, caught below
      return userCommit(reply, dht, query)
    }

    function map(reply) {
      const data = mapLookup(reply)

      if (!data || !data.token) return data

      let found = data.peers.length >= 20
      for (let i = 0; !found && i < data.peers.length; i++) {
        found = b4a.equals(data.peers[i].publicKey, keyPair.publicKey)
      }

      if (!found) return data

      if (!data.from.id) return data

      unannounces.push(
        dht
          ._requestUnannounce(keyPair, dht, target, data.token, data.from, signUnannounce)
          .catch(safetyCatch)
      )

      return data
    }
  }

  unannounce(target, keyPair, opts = {}) {
    return this.lookupAndUnannounce(target, keyPair, opts).finished()
  }

  announce(target, keyPair, relayAddresses, opts = {}) {
    const signAnnounce = opts.signAnnounce || Persistent.signAnnounce
    const bump = opts.bump || 0

    opts = { ...opts, commit }

    return opts.clear ? this.lookupAndUnannounce(target, keyPair, opts) : this.lookup(target, opts)

    function commit(reply, dht) {
      return dht._requestAnnounce(
        keyPair,
        dht,
        target,
        reply.token,
        reply.from,
        relayAddresses,
        signAnnounce,
        bump
      )
    }
  }

  async immutableGet(target, opts = {}) {
    opts = { ...opts, map: mapImmutable }

    const query = this.query({ target, command: COMMANDS.IMMUTABLE_GET, value: null }, opts)
    const check = b4a.allocUnsafe(32)

    for await (const node of query) {
      const { value } = node
      sodium.crypto_generichash(check, value)
      if (b4a.equals(check, target)) return node
    }

    return null
  }

  async immutablePut(value, opts = {}) {
    const target = b4a.allocUnsafe(32)
    sodium.crypto_generichash(target, value)

    opts = {
      ...opts,
      map: mapImmutable,
      commit(reply, dht) {
        return dht.request(
          { token: reply.token, target, command: COMMANDS.IMMUTABLE_PUT, value },
          reply.from
        )
      }
    }

    const query = this.query({ target, command: COMMANDS.IMMUTABLE_GET, value: null }, opts)
    await query.finished()

    return { hash: target, closestNodes: query.closestNodes }
  }

  async mutableGet(publicKey, opts = {}) {
    let refresh = opts.refresh || null
    let signed = null
    let result = null

    opts = { ...opts, map: mapMutable, commit: refresh ? commit : null }

    const target = b4a.allocUnsafe(32)
    sodium.crypto_generichash(target, publicKey)

    const userSeq = opts.seq || 0
    const query = this.query(
      { target, command: COMMANDS.MUTABLE_GET, value: c.encode(c.uint, userSeq) },
      opts
    )
    const latest = opts.latest !== false

    for await (const node of query) {
      if (result && node.seq <= result.seq) continue
      if (
        node.seq < userSeq ||
        !Persistent.verifyMutable(node.signature, node.seq, node.value, publicKey)
      )
        continue
      if (!latest) return node
      if (!result || node.seq > result.seq) result = node
    }

    return result

    function commit(reply, dht) {
      if (!signed && result && refresh) {
        if (refresh(result)) {
          signed = c.encode(m.mutablePutRequest, {
            publicKey,
            seq: result.seq,
            value: result.value,
            signature: result.signature
          })
        } else {
          refresh = null
        }
      }

      return signed
        ? dht.request(
            { token: reply.token, target, command: COMMANDS.MUTABLE_PUT, value: signed },
            reply.from
          )
        : Promise.resolve(null)
    }
  }

  async mutablePut(keyPair, value, opts = {}) {
    const signMutable = opts.signMutable || Persistent.signMutable

    const target = b4a.allocUnsafe(32)
    sodium.crypto_generichash(target, keyPair.publicKey)

    const seq = opts.seq || 0
    const signature = await signMutable(seq, value, keyPair)

    const signed = c.encode(m.mutablePutRequest, {
      publicKey: keyPair.publicKey,
      seq,
      value,
      signature
    })

    opts = {
      ...opts,
      map: mapMutable,
      commit(reply, dht) {
        return dht.request(
          { token: reply.token, target, command: COMMANDS.MUTABLE_PUT, value: signed },
          reply.from
        )
      }
    }

    // use seq = 0, for the query part here, as we don't care about the actual values
    const query = this.query(
      { target, command: COMMANDS.MUTABLE_GET, value: c.encode(c.uint, 0) },
      opts
    )
    await query.finished()

    return { publicKey: keyPair.publicKey, closestNodes: query.closestNodes, seq, signature }
  }

  onrequest(req) {
    switch (req.command) {
      case COMMANDS.PEER_HANDSHAKE: {
        this._router.onpeerhandshake(req)
        return true
      }
      case COMMANDS.PEER_HOLEPUNCH: {
        this._router.onpeerholepunch(req)
        return true
      }
    }

    if (this._persistent === null || this.id === null) return false

    switch (req.command) {
      case COMMANDS.FIND_PEER: {
        this._persistent.onfindpeer(req)
        return true
      }
      case COMMANDS.LOOKUP: {
        this._persistent.onlookup(req)
        return true
      }
      case COMMANDS.ANNOUNCE: {
        this._persistent.onannounce(req)
        return true
      }
      case COMMANDS.UNANNOUNCE: {
        this._persistent.onunannounce(req)
        return true
      }
      case COMMANDS.MUTABLE_PUT: {
        this._persistent.onmutableput(req)
        return true
      }
      case COMMANDS.MUTABLE_GET: {
        this._persistent.onmutableget(req)
        return true
      }
      case COMMANDS.IMMUTABLE_PUT: {
        this._persistent.onimmutableput(req)
        return true
      }
      case COMMANDS.IMMUTABLE_GET: {
        this._persistent.onimmutableget(req)
        return true
      }
      case COMMANDS.PLUGIN: {
        this._persistent.onplugin(req)
        return true
      }
    }

    return false
  }

  static keyPair(seed) {
    return createKeyPair(seed)
  }

  static hash(data) {
    return hash(data)
  }

  static connectRawStream(encryptedStream, rawStream, remoteId) {
    const stream = encryptedStream.rawStream

    if (!stream.connected) throw STREAM_NOT_CONNECTED()

    rawStream.connect(stream.socket, remoteId, stream.remotePort, stream.remoteHost)
  }

  createRawStream(opts) {
    return this.rawStreams.add(opts)
  }

  async _requestAnnounce(keyPair, dht, target, token, from, relayAddresses, sign, bump) {
    const ann = {
      peer: {
        publicKey: keyPair.publicKey,
        relayAddresses: relayAddresses || []
      },
      refresh: null,
      signature: null,
      bump
    }

    ann.signature = await sign(target, token, from.id, ann, keyPair)

    const value = c.encode(m.announce, ann)

    return dht.request(
      {
        token,
        target,
        command: COMMANDS.ANNOUNCE,
        value
      },
      from
    )
  }

  async _requestUnannounce(keyPair, dht, target, token, from, sign) {
    const unann = {
      peer: {
        publicKey: keyPair.publicKey,
        relayAddresses: []
      },
      signature: null
    }

    unann.signature = await sign(target, token, from.id, unann, keyPair)

    const value = c.encode(m.announce, unann)

    return dht.request(
      {
        token,
        target,
        command: COMMANDS.UNANNOUNCE,
        value
      },
      from
    )
  }

  register(name, plugin) {
    this.plugins.set(name, plugin)
    plugin.onregister(this)
  }
}

HyperDHT.BOOTSTRAP = BOOTSTRAP_NODES
HyperDHT.FIREWALL = FIREWALL

module.exports = HyperDHT

function mapLookup(node) {
  if (!node.value) return null

  try {
    const l = c.decode(m.lookupRawReply, node.value)

    return {
      token: node.token,
      from: node.from,
      to: node.to,
      peers: l.peers,
      bump: l.bump
    }
  } catch {
    return null
  }
}

function mapFindPeer(node) {
  if (!node.value) return null

  try {
    return {
      token: node.token,
      from: node.from,
      to: node.to,
      peer: c.decode(m.peer, node.value)
    }
  } catch {
    return null
  }
}

function mapImmutable(node) {
  if (!node.value) return null

  return {
    token: node.token,
    from: node.from,
    to: node.to,
    value: node.value
  }
}

function mapMutable(node) {
  if (!node.value) return null

  try {
    const { seq, value, signature } = c.decode(m.mutableGetResponse, node.value)

    return {
      token: node.token,
      from: node.from,
      to: node.to,
      seq,
      value,
      signature
    }
  } catch {
    return null
  }
}

function noop() {}

function filterNode(node) {
  // always skip these testnet nodes that got mixed in by accident, until they get updated
  return (
    !(node.port === 49738 && (node.host === '134.209.28.98' || node.host === '167.99.142.185')) &&
    !(node.port === 9400 && node.host === '35.233.47.252') &&
    !(node.host === '150.136.142.116')
  )
}

const defaultMaxSize = 65536
const defaultMaxAge = 20 * 60 * 1000 // 20 minutes

function defaultCacheOpts(opts) {
  const maxSize = opts.maxSize || defaultMaxSize
  const maxAge = opts.maxAge || defaultMaxAge

  return {
    router: {
      forwards: { maxSize, maxAge }
    },
    relayAddresses: { maxSize: Math.min(maxSize, 512), maxAge: 0 },
    persistent: {
      records: { maxSize, maxAge },
      refreshes: { maxSize, maxAge },
      mutables: {
        maxSize: (maxSize / 2) | 0,
        maxAge: opts.maxAge || 48 * 60 * 60 * 1000 // 48 hours
      },
      immutables: {
        maxSize: (maxSize / 2) | 0,
        maxAge: opts.maxAge || 48 * 60 * 60 * 1000 // 48 hours
      },
      bumps: { maxSize, maxAge }
    }
  }
}
const safetyCatch = require('safety-catch')
const c = require('compact-encoding')
const Signal = require('signal-promise')
const { encodeUnslab } = require('./encode')
const Sleeper = require('./sleeper')
const m = require('./messages')
const Persistent = require('./persistent')
const { COMMANDS } = require('./constants')

const MIN_ACTIVE = 3

module.exports = class Announcer {
  constructor(dht, keyPair, target, opts = {}) {
    this.dht = dht
    this.keyPair = keyPair
    this.target = target
    this.relays = []
    this.relayAddresses = []
    this.stopped = false
    this.suspended = false
    this.record = encodeUnslab(m.peer, { publicKey: keyPair.publicKey, relayAddresses: [] })
    this.online = new Signal()

    this._refreshing = false
    this._closestNodes = null
    this._active = null
    this._sleeper = new Sleeper()
    this._resumed = new Signal()
    this._signAnnounce = opts.signAnnounce || Persistent.signAnnounce
    this._signUnannounce = opts.signUnannounce || Persistent.signUnannounce
    this._updating = null
    this._activeQuery = null
    this._unannouncing = null

    this._serverRelays = [new Map(), new Map(), new Map()]
  }

  isRelay(addr) {
    const id = addr.host + ':' + addr.port
    const [a, b, c] = this._serverRelays
    return a.has(id) || b.has(id) || c.has(id)
  }

  async suspend({ log = noop } = {}) {
    if (this.suspended) return
    this.suspended = true

    log('Suspending announcer')

    // Suspend has its own sleep logic
    // so we don't want to hang on this one
    this.online.notify()

    if (this._activeQuery) this._activeQuery.destroy()

    this._sleeper.resume()
    if (this._updating) await this._updating
    log('Suspending announcer (post update)')

    if (this.suspended === false || this.stopped) return

    log('Suspending announcer (pre unannounce)')
    await this._unannounceCurrent()
    log('Suspending announcer (post unannounce)')
  }

  resume() {
    if (!this.suspended) return
    this.suspended = false

    this.refresh()
    this._sleeper.resume()
    this._resumed.notify()
  }

  refresh() {
    if (this.stopped) return
    this._refreshing = true
  }

  async start() {
    if (this.stopped) return
    this._active = this._runUpdate()
    await this._active
    if (this.stopped) return
    this._active = this._background()
  }

  async stop() {
    this.stopped = true
    this.online.notify() // Break out of the _background loop if we're offline
    this._sleeper.resume()
    this._resumed.notify()
    await this._active
    await this._unannounceCurrent()
  }

  async _unannounceCurrent() {
    while (this._unannouncing !== null) await this._unannouncing
    const un = (this._unannouncing = this._unannounceAll(this._serverRelays[2].values()))
    await this._unannouncing
    if (un === this._unannouncing) this._unannouncing = null
  }

  async _background() {
    while (!this.dht.destroyed && !this.stopped) {
      try {
        this._refreshing = false

        // ~5min +-
        for (let i = 0; i < 100 && !this.stopped && !this._refreshing && !this.suspended; i++) {
          const pings = []

          for (const node of this._serverRelays[2].values()) {
            pings.push(this.dht.ping(node))
          }

          const active = await resolved(pings)
          if (active < Math.min(pings.length, MIN_ACTIVE)) {
            this.refresh() // we lost too many relay nodes, retry all
          }

          if (this.stopped) return

          if (!this.suspended && !this._refreshing) await this._sleeper.pause(3000)
        }

        while (!this.stopped && this.suspended) await this._resumed.wait()

        if (!this.stopped) await this._runUpdate()

        while (!this.dht.online && !this.stopped && !this.suspended) {
          // Being offline can make _background repeat very quickly
          // So wait until we're back online
          await this.online.wait()
        }
      } catch (err) {
        safetyCatch(err)
      }
    }
  }

  async _runUpdate() {
    this._updating = this._update()
    await this._updating
    this._updating = null
  }

  async _update() {
    while (this._unannouncing) await this._unannouncing

    this._cycle()

    const q = (this._activeQuery = this.dht.findPeer(this.target, {
      hash: false,
      nodes: this._closestNodes
    }))

    try {
      await q.finished()
    } catch {
      // ignore failures...
    }

    this._activeQuery = null

    if (this.stopped || this.suspended) return

    const ann = []
    const replies = pickBest(q.closestReplies)

    const relays = []
    const relayAddresses = []

    if (!this.dht.firewalled) {
      const addr = this.dht.remoteAddress()
      if (addr) relayAddresses.push(addr)
    }

    for (const msg of replies) {
      ann.push(this._commit(msg, relays, relayAddresses))
    }

    await Promise.allSettled(ann)
    if (this.stopped || this.suspended) return

    this._closestNodes = q.closestNodes
    this.relays = relays
    this.relayAddresses = relayAddresses

    const removed = []
    for (const [key, value] of this._serverRelays[1]) {
      if (!this._serverRelays[2].has(key)) removed.push(value)
    }

    await this._unannounceAll(removed)
  }

  _unannounceAll(relays) {
    const unann = []
    for (const r of relays) unann.push(this._unannounce(r))
    return Promise.allSettled(unann)
  }

  async _unannounce(to) {
    const unann = {
      peer: {
        publicKey: this.keyPair.publicKey,
        relayAddresses: []
      },
      refresh: null,
      signature: null
    }

    const { from, token, value } = await this.dht.request(
      {
        token: null,
        command: COMMANDS.FIND_PEER,
        target: this.target,
        value: null
      },
      to
    )

    if (!token || !from.id || !value) return

    unann.signature = await this._signUnannounce(this.target, token, from.id, unann, this.keyPair)

    await this.dht.request(
      {
        token,
        command: COMMANDS.UNANNOUNCE,
        target: this.target,
        value: c.encode(m.announce, unann)
      },
      to
    )
  }

  async _commit(msg, relays, relayAddresses) {
    const ann = {
      peer: {
        publicKey: this.keyPair.publicKey,
        relayAddresses: []
      },
      refresh: null,
      signature: null
    }

    ann.signature = await this._signAnnounce(this.target, msg.token, msg.from.id, ann, this.keyPair)

    const res = await this.dht.request(
      {
        token: msg.token,
        command: COMMANDS.ANNOUNCE,
        target: this.target,
        value: c.encode(m.announce, ann)
      },
      msg.from
    )

    if (res.error !== 0) return

    if (relayAddresses.length < 3) relayAddresses.push({ host: msg.from.host, port: msg.from.port })
    relays.push({ relayAddress: msg.from, peerAddress: msg.to })

    this._serverRelays[2].set(msg.from.host + ':' + msg.from.port, msg.from)
  }

  _cycle() {
    const tmp = this._serverRelays[0]
    this._serverRelays[0] = this._serverRelays[1]
    this._serverRelays[1] = this._serverRelays[2]
    this._serverRelays[2] = tmp
    tmp.clear()
  }
}

function resolved(ps) {
  let replied = 0
  let ticks = ps.length + 1

  return new Promise((resolve) => {
    for (const p of ps) p.then(push, tick)
    tick()

    function push(v) {
      replied++
      tick()
    }

    function tick() {
      if (--ticks === 0) resolve(replied)
    }
  })
}

function pickBest(replies) {
  // TODO: pick the ones closest to us RTT wise
  return replies.slice(0, 3)
}

function noop() {}
const NoiseSecretStream = require('@hyperswarm/secret-stream')
const b4a = require('b4a')
const relay = require('blind-relay')
const { isReserved, isBogon } = require('bogon')
const safetyCatch = require('safety-catch')
const unslab = require('unslab')
const Semaphore = require('./semaphore')
const NoiseWrap = require('./noise-wrap')
const SecurePayload = require('./secure-payload')
const Holepuncher = require('./holepuncher')
const Sleeper = require('./sleeper')
const {
  clearRelayTimeout,
  confirmDirectUpgrade,
  destroyRelayConnection
} = require('./relay-connection')
const { FIREWALL, ERROR } = require('./constants')
const { unslabbedHash } = require('./crypto')
const {
  CANNOT_HOLEPUNCH,
  HANDSHAKE_INVALID,
  HOLEPUNCH_ABORTED,
  HOLEPUNCH_INVALID,
  HOLEPUNCH_PROBE_TIMEOUT,
  HOLEPUNCH_DOUBLE_RANDOMIZED_NATS,
  PEER_CONNECTION_FAILED,
  PEER_NOT_FOUND,
  REMOTE_ABORTED,
  REMOTE_NOT_HOLEPUNCHABLE,
  REMOTE_NOT_HOLEPUNCHING,
  SERVER_ERROR,
  SERVER_INCOMPATIBLE,
  RELAY_ABORTED,
  SUSPENDED
} = require('./errors')
const { decode } = require('hypercore-id-encoding')
const HyperDHTAddress = require('hyperdht-address')

module.exports = function connect(dht, publicKey, opts = {}) {
  const pool = opts.pool || null

  const { key, nodes: providedNodes } = HyperDHTAddress.decode(
    b4a.isBuffer(publicKey) ? publicKey : decode(publicKey)
  )
  publicKey = key

  if (pool && pool.has(publicKey)) return pool.get(publicKey)

  publicKey = unslab(publicKey)

  opts.relayAddresses = opts.relayAddresses || providedNodes || []
  const keyPair = opts.keyPair || dht.defaultKeyPair
  const relayThrough = selectRelay(opts.relayThrough || null)
  const encryptedSocket = (opts.createSecretStream || defaultCreateSecretStream)(true, null, {
    publicKey: keyPair.publicKey,
    remotePublicKey: publicKey,
    autoStart: false,
    keepAlive: dht.connectionKeepAlive
  })

  // in case a socket is made during suspended state, destroy it immediately
  if (dht.suspended || !dht._connectable) {
    encryptedSocket.destroy(SUSPENDED())
    return encryptedSocket
  }

  if (pool) pool._attachStream(encryptedSocket, false)

  const id = b4a.toString(publicKey, 'hex')
  const c = {
    id,
    dht,
    session: dht.session(),
    relayAddresses: opts.relayAddresses,
    remoteRelayAddresses: [],
    pool,
    round: 0,
    target: unslabbedHash(publicKey),
    remotePublicKey: publicKey,
    reusableSocket: !!opts.reusableSocket,
    handshake: (opts.createHandshake || defaultCreateHandshake)(keyPair, publicKey),
    request: null,
    requesting: false,
    lan: opts.localConnection !== false,
    firewall: FIREWALL.UNKNOWN,
    rawStream: dht.createRawStream({ framed: true, firewall }),
    connect: null,
    query: null,
    puncher: null,
    payload: null,
    passiveConnectTimeout: null,
    serverSocket: null,
    serverAddress: null,
    onsocket: null,
    sleeper: new Sleeper(),
    encryptedSocket,

    // Relay state
    relayTimeout: null,
    relayThrough,
    relayToken: relayThrough ? relay.token() : null,
    relaySocket: null,
    relayClient: null,
    relayPaired: false,
    validUpgrade: true,
    relayKeepAlive: opts.relayKeepAlive || 5000
  }

  // If the raw stream receives an error signal pre connect (ie from the firewall hook), make sure
  // to forward that to the encrypted socket for proper teardown
  c.rawStream.on('error', autoDestroy)
  c.rawStream.once('connect', () => {
    c.rawStream.removeListener('error', autoDestroy)
  })

  encryptedSocket.on('close', function () {
    if (c.passiveConnectTimeout) clearPassiveConnectTimeout(c)
    if (c.query) c.query.destroy()
    if (c.puncher) c.puncher.destroy()
    if (c.rawStream) c.rawStream.destroy()
    c.session.destroy()
    c.sleeper.resume()
  })

  // Safe to run in the background - never throws
  if (dht.suspended) encryptedSocket.destroy(SUSPENDED())
  else connectAndHolepunch(c, opts)

  return encryptedSocket

  function autoDestroy(err) {
    maybeDestroyEncryptedSocket(c, err)
  }

  function firewall(socket, port, host) {
    // Check if the traffic originated from the socket on which we're expecting relay traffic. If so,
    // we haven't hole punched yet and the other side is just sending us traffic through the relay.
    if (c.relaySocket && isRelay(c.relaySocket, socket, port, host)) {
      c.validUpgrade = false
      return false
    }

    c.validUpgrade = true
    if (c.onsocket) {
      c.onsocket(socket, port, host)
    } else {
      c.serverSocket = socket
      c.serverAddress = { port, host }
    }
    return false
  }
}

function isDone(c) {
  // we are destroying or the puncher is connected - done
  if (c.encryptedSocket.destroying || !!(c.puncher && c.puncher.connected)) {
    return true
  }
  // not destroying, but no raw stream - def not done
  if (c.encryptedSocket.rawStream === null) {
    return false
  }
  // we are relayed, but the puncher is not done yet
  if (c.relaySocket && !!(c.puncher && !c.puncher.connected && !c.puncher.destroyed)) {
    return false
  }
  // we are done
  return true
}

async function retryRoute(c, route) {
  const ref = c.dht._socketPool.lookup(route.socket)

  if (!ref) {
    if (route.socket === c.dht.socket) {
      await connectThroughNode(c, route.address, c.dht.socket)
    }
    return
  }

  ref.active()

  try {
    await connectThroughNode(c, route.address, route.socket)
  } catch {
    // if error, just ignore, and continue through the existing strat
  }

  ref.inactive()
}

async function connectAndHolepunch(c, opts) {
  const route = c.reusableSocket ? c.dht._socketPool.routes.get(c.remotePublicKey) : null

  if (route) {
    await retryRoute(c, route)
    if (isDone(c)) return
  }

  await findAndConnect(c, opts)
  if (isDone(c)) return

  if (!c.connect) {
    // TODO: just a quick fix for now, should retry prob
    maybeDestroyEncryptedSocket(c, HANDSHAKE_INVALID())
    return
  }

  await holepunch(c, opts)
}

function getFirstRemoteAddress(addrs, serverAddress) {
  for (const addr of addrs) {
    if (isBogon(addr.host)) continue
    return addr
  }

  return serverAddress
}

async function holepunch(c, opts) {
  let { relayAddress, serverAddress, clientAddress, payload } = c.connect

  const remoteHolepunchable = !!(payload.holepunch && payload.holepunch.relays.length)
  const relayed = diffAddress(serverAddress, relayAddress)

  if (payload.firewall === FIREWALL.OPEN || (relayed && !remoteHolepunchable)) {
    const addr = getFirstRemoteAddress(payload.addresses4, serverAddress)
    if (addr) {
      const socket = c.dht.socket
      c.dht.stats.punches.open++
      c.onsocket(socket, addr.port, addr.host)
      return
    }
    // TODO: check all addresses also obvs
  }

  const onabort = () => {
    c.session.destroy()
    maybeDestroyEncryptedSocket(c, HOLEPUNCH_ABORTED())
  }

  if (c.firewall === FIREWALL.OPEN) {
    c.passiveConnectTimeout = setTimeout(onabort, 10000)
    return
  }

  // Race the LAN shortcut with normal holepunch when possible. A blocked LAN ping
  // should not abort a connection that can still holepunch. If the remote is not
  // holepunchable, the LAN ping is the only remaining path.
  if (c.lan && relayed && clientAddress.host === serverAddress.host) {
    const serverAddresses = payload.addresses4.filter(onlyNonReserved)

    if (serverAddresses.length > 0) {
      const myAddresses = Holepuncher.localAddresses(c.dht.io.serverSocket)
      const addr = Holepuncher.matchAddress(myAddresses, serverAddresses) || serverAddresses[0]

      const socket = c.dht.io.serverSocket
      c.dht.ping(addr).then(onlanconnect, onlanerror)
      if (!remoteHolepunchable) return

      function onlanconnect() {
        // LAN and holepunch completions both converge through onsocket,
        // which claims c.rawStream once and ignores later winners.
        if (isDone(c)) return
        c.onsocket(socket, addr.port, addr.host)
      }

      function onlanerror() {
        if (!remoteHolepunchable) {
          maybeDestroyEncryptedSocket(c, HOLEPUNCH_ABORTED())
        }
      }
    }
  }

  if (!remoteHolepunchable) {
    maybeDestroyEncryptedSocket(c, CANNOT_HOLEPUNCH())
    return
  }

  c.puncher = new Holepuncher(c.dht, c.session, true, payload.firewall)

  c.puncher.onconnect = c.onsocket
  c.puncher.onabort = onabort

  const serverRelay = pickServerRelay(payload.holepunch.relays, relayAddress)

  // Begin holepunching!

  let probe
  try {
    probe = await probeRound(c, opts.fastOpen === false ? null : serverAddress, serverRelay, true)
  } catch (err) {
    destroyPuncher(c)
    // TODO: we should retry here with some of the other relays, bail for now
    maybeDestroyEncryptedSocket(c, err)
    return
  }

  if (isDone(c) || !probe) return
  const { token, peerAddress } = probe

  // If the relay the server picked is the same as the relay the client picked,
  // then we can use the peerAddress that round one indicates the server wants to use.
  // This shaves off a roundtrip if the server chose to reroll its socket due to some NAT
  // issue with the first one it picked (ie mobile nat inconsistencies...).
  // If the relays were different, then the server would not have a UDP session open on this address
  // to the client relay, which round2 uses.
  if (
    !diffAddress(serverRelay.relayAddress, relayAddress) &&
    diffAddress(serverAddress, peerAddress)
  ) {
    serverAddress = peerAddress
    await c.puncher.openSession(serverAddress)
    if (isDone(c)) return
  }

  // TODO: still continue here if a local connection might work, but then do not holepunch...
  if (
    opts.holepunch &&
    !opts.holepunch(
      c.puncher.remoteFirewall,
      c.puncher.nat.firewall,
      c.puncher.remoteAddresses,
      c.puncher.nat.addresses
    )
  ) {
    await abort(c, serverRelay, HOLEPUNCH_ABORTED('Client aborted holepunch'))
    return
  }

  try {
    await roundPunch(c, serverAddress, token, relayAddress, serverRelay, false)
  } catch (err) {
    destroyPuncher(c)
    // TODO: retry with another relay?
    maybeDestroyEncryptedSocket(c, err)
  }
}

async function connectThroughNodes(c, addresses, socket) {
  for (const address of addresses) {
    if (isDone(c) || c.connect) return

    c.remoteRelayAddresses.push(address)
    await connectThroughNode(c, address, socket)
  }
}

async function findAndConnect(c, opts) {
  let attempts = 0
  let relayAddresses =
    opts.relayAddresses && opts.relayAddresses.length ? opts.relayAddresses : null

  if (!relayAddresses) {
    const cachedRelayAddresses = c.dht._relayAddressesCache.get(c.id)
    if (cachedRelayAddresses) relayAddresses = cachedRelayAddresses
  }

  if (c.dht._persistent) {
    // check if we know the route ourself...
    const route = c.dht._router.get(c.target)
    if (route && route.relay !== null) {
      relayAddresses = [{ host: route.relay.host, port: route.relay.port }]
    }
  }

  // 2 is how many parallel connect attempts we want to do, we can make this configurable
  const preConnect = relayAddresses !== null && relayAddresses.length > 0
  const sem = new Semaphore(preConnect ? 3 : 2)
  const signal = sem.signal.bind(sem)
  const tries = relayAddresses !== null ? 2 : 1

  if (preConnect) {
    await sem.wait()
    connectThroughNodes(c, relayAddresses, null).then(signal, signal)
  }

  try {
    for (let i = 0; i < tries && !isDone(c) && !c.connect; i++) {
      c.query = c.dht.findPeer(c.target, {
        hash: false,
        session: c.session,
        nodes: relayAddresses,
        retries: 3
      })

      for await (const data of c.query) {
        await sem.wait()
        if (isDone(c)) return

        if (c.connect) {
          sem.signal()
          break
        }

        // Skip node already run via preConnect
        if (preConnect && relayAddresses && isRelayAddress(relayAddresses, data)) {
          sem.signal()
          continue
        }

        c.remoteRelayAddresses.push(data.from)
        attempts++
        connectThroughNode(c, data.from, null).then(signal, signal)
      }

      relayAddresses = null

      if (attempts > 0) await sem.flush()
    }

    c.query = null
    if (isDone(c)) return

    // flush the semaphore
    await sem.flush()
    if (isDone(c)) return
  } catch (err) {
    c.query = null
    maybeDestroyEncryptedSocket(c, err)
    return
  }

  if (!c.connect) {
    maybeDestroyEncryptedSocket(c, attempts ? PEER_CONNECTION_FAILED() : PEER_NOT_FOUND())
  }
}

async function connectThroughNode(c, address, socket) {
  if (!c.requesting) {
    // If we have a stable server address, send it over now
    const addr = c.dht.remoteAddress()
    const localAddrs = c.lan ? Holepuncher.localAddresses(c.dht.io.serverSocket) : null
    const addresses4 = []

    if (addr) addresses4.push(addr)
    if (localAddrs) addresses4.push(...localAddrs)

    c.firewall = addr ? FIREWALL.OPEN : FIREWALL.UNKNOWN
    c.requesting = true
    c.request = await c.handshake.send({
      error: ERROR.NONE,
      firewall: c.firewall,
      holepunch: null,
      addresses4,
      addresses6: [],
      udx: {
        reusableSocket: c.reusableSocket,
        id: c.rawStream.id,
        seq: 0
      },
      secretStream: {},
      relayThrough: c.relayThrough ? { publicKey: c.relayThrough, token: c.relayToken } : null
    })
    if (isDone(c)) return
  }

  const { serverAddress, clientAddress, relayed, noise } = await c.dht._router.peerHandshake(
    c.target,
    { noise: c.request, socket, session: c.session },
    address
  )
  if (isDone(c) || c.connect) return

  const payload = await c.handshake.recv(noise)
  if (isDone(c) || !payload) return

  if (payload.version !== 1) {
    maybeDestroyEncryptedSocket(c, SERVER_INCOMPATIBLE())
    return
  }
  if (payload.error !== ERROR.NONE) {
    maybeDestroyEncryptedSocket(c, SERVER_ERROR())
    return
  }
  if (!payload.udx) {
    maybeDestroyEncryptedSocket(c, SERVER_ERROR('Server did not send UDX data'))
    return
  }

  const hs = c.handshake.final()

  c.handshake = null
  c.request = null
  c.requesting = false
  c.connect = {
    relayed,
    relayAddress: address,
    clientAddress,
    serverAddress,
    payload
  }

  c.payload = new SecurePayload(hs.holepunchSecret)

  c.onsocket = function (socket, port, host) {
    if (c.rawStream === null) return // Already hole punched

    if (c.rawStream.connected) {
      const rawStream = c.rawStream
      const remoteChanging = rawStream.changeRemote(socket, c.connect.payload.udx.id, port, host)
      confirmDirectUpgrade(c, rawStream, remoteChanging)
    } else {
      // cache the relay addrs for a future reconnect, we prefer the remote one so they
      // can give us the correct ones from their pov
      if (payload.relayAddresses && payload.relayAddresses.length) {
        c.dht._relayAddressesCache.set(c.id, payload.relayAddresses)
      } else if (c.remoteRelayAddresses.length) {
        c.dht._relayAddressesCache.set(c.id, c.remoteRelayAddresses)
      }

      c.rawStream.connect(socket, c.connect.payload.udx.id, port, host)
      c.encryptedSocket.start(c.rawStream, { handshake: hs })
    }

    if (c.reusableSocket && payload.udx.reusableSocket) {
      c.dht._socketPool.routes.add(c.remotePublicKey, c.rawStream)
    }

    if (c.puncher) {
      c.puncher.onabort = noop
      c.puncher.destroy()
    }

    if (c.passiveConnectTimeout) {
      clearPassiveConnectTimeout(c)
    }

    c.rawStream = null
  }

  if (payload.relayThrough || c.relayThrough) {
    relayConnection(c, c.relayThrough, payload, hs)
  }

  if (c.serverSocket) {
    c.onsocket(c.serverSocket, c.serverAddress.port, c.serverAddress.host)
    return
  }

  if (!relayed) {
    c.onsocket(socket || c.dht.socket, address.port, address.host)
  }

  c.session.destroy()
}

async function updateHolepunch(c, peerAddress, relayAddr, payload) {
  const holepunch = await c.dht._router.peerHolepunch(
    c.target,
    {
      id: c.connect.payload.holepunch.id,
      payload: c.payload.encrypt(payload),
      peerAddress,
      socket: c.puncher.socket,
      session: c.session
    },
    relayAddr
  )

  if (isDone(c)) return null

  const remotePayload = c.payload.decrypt(holepunch.payload)
  if (!remotePayload) {
    throw HOLEPUNCH_INVALID()
  }

  const { error, firewall, punching, addresses, remoteToken } = remotePayload

  if (error === ERROR.TRY_LATER && c.relayToken && payload.punching) {
    return {
      tryLater: true,
      ...holepunch,
      payload: remotePayload
    }
  }

  if (error !== ERROR.NONE) {
    throw REMOTE_ABORTED('Remote aborted with error code ' + error)
  }

  const echoed = !!(remoteToken && payload.token && b4a.equals(remoteToken, payload.token))

  c.puncher.updateRemote({
    punching,
    firewall,
    addresses,
    verified: echoed ? peerAddress.host : null
  })

  return {
    tryLater: false,
    ...holepunch,
    payload: remotePayload
  }
}

async function probeRound(c, serverAddress, serverRelay, retry) {
  // Open a quick low ttl session against what we think is the server
  if (serverAddress) await c.puncher.openSession(serverAddress)

  if (isDone(c)) return null

  const reply = await updateHolepunch(c, serverRelay.peerAddress, serverRelay.relayAddress, {
    error: ERROR.NONE,
    firewall: c.puncher.nat.firewall,
    round: c.round++,
    connected: false,
    punching: false,
    addresses: c.puncher.nat.addresses,
    remoteAddress: serverAddress,
    token: null,
    remoteToken: null
  })

  if (isDone(c) || !reply) return null

  const { peerAddress } = reply
  const { address, token } = reply.payload

  c.puncher.nat.add(reply.to, reply.from)

  // Open another quick low ttl session against what the server says their address is,
  // if they haven't said they are random yet
  if (
    c.puncher.remoteFirewall < FIREWALL.RANDOM &&
    address &&
    address.host &&
    address.port &&
    diffAddress(address, serverAddress)
  ) {
    await c.puncher.openSession(address)
    if (isDone(c)) return null
  }

  // If the remote told us they didn't know their nat firewall yet, give them a chance to figure it out
  // They might say this to see if the "fast mode" punch comes through first.
  if (c.puncher.remoteFirewall === FIREWALL.UNKNOWN) {
    await c.sleeper.pause(1000)
    if (isDone(c)) return null
  }

  let stable = await c.puncher.analyze(false)
  if (isDone(c)) return null

  // If the socket seems unstable, try to make it stable by setting the "allowReopen" flag
  // Mostly relevant for mobile networks
  if (!stable) {
    stable = await c.puncher.analyze(true)
    if (isDone(c)) return null
    if (stable) return probeRound(c, serverAddress, serverRelay, false)
  }

  if ((c.puncher.remoteFirewall === FIREWALL.UNKNOWN || !token) && retry) {
    return probeRound(c, serverAddress, serverRelay, false)
  }

  if (
    c.puncher.remoteFirewall === FIREWALL.UNKNOWN ||
    c.puncher.nat.firewall === FIREWALL.UNKNOWN
  ) {
    await abort(c, serverRelay, HOLEPUNCH_PROBE_TIMEOUT())
    return null
  }

  if (c.puncher.remoteFirewall >= FIREWALL.RANDOM && c.puncher.nat.firewall >= FIREWALL.RANDOM) {
    await abort(c, serverRelay, HOLEPUNCH_DOUBLE_RANDOMIZED_NATS())
    return null
  }

  return { token, peerAddress }
}

async function roundPunch(c, serverAddress, remoteToken, clientRelay, serverRelay, delayed) {
  // We are gossiping our final NAT status to the other peer now
  // so make sure we don't update our local view for now as that can make things weird
  c.puncher.nat.freeze()

  const isRandom =
    c.puncher.remoteFirewall >= FIREWALL.RANDOM || c.puncher.nat.firewall >= FIREWALL.RANDOM
  if (isRandom) {
    while (
      c.dht._randomPunches >= c.dht._randomPunchLimit ||
      Date.now() - c.dht._lastRandomPunch < c.dht._randomPunchInterval
    ) {
      // if no relay can help, bail
      if (!c.relayToken) throw HOLEPUNCH_ABORTED()

      if (!delayed) {
        delayed = true
        await updateHolepunch(c, serverAddress, clientRelay, {
          error: ERROR.NONE,
          firewall: c.puncher.nat.firewall,
          round: c.round++,
          connected: false,
          punching: false,
          addresses: c.puncher.nat.addresses,
          remoteAddress: null,
          token: c.payload.token(serverAddress),
          remoteToken
        })
        if (isDone(c)) return
      }

      await tryLater(c)
      if (isDone(c)) return
    }
  }

  // increment now, so we can commit to punching
  if (isRandom) c.dht._randomPunches++

  let reply

  try {
    // if delayed switch to the servers chosen relay - we validated anyway
    reply = await updateHolepunch(
      c,
      delayed ? serverRelay.peerAddress : serverAddress,
      delayed ? serverRelay.relayAddress : clientRelay,
      {
        error: ERROR.NONE,
        firewall: c.puncher.nat.firewall,
        round: c.round++,
        connected: false,
        punching: true,
        addresses: c.puncher.nat.addresses,
        remoteAddress: null,
        token: delayed ? null : c.payload.token(serverAddress),
        remoteToken
      }
    )
  } finally {
    // decrement as punch increments for us
    if (isRandom) c.dht._randomPunches--
  }

  if (isDone(c)) return
  if (!reply) return

  if (reply.tryLater) {
    await tryLater(c)
    if (isDone(c)) return
    return roundPunch(c, serverAddress, remoteToken, clientRelay, serverRelay, true)
  }

  if (!c.puncher.remoteHolepunching) {
    throw REMOTE_NOT_HOLEPUNCHING()
  }

  if (!(await c.puncher.punch())) {
    throw REMOTE_NOT_HOLEPUNCHABLE()
  }
}

async function tryLater(c) {
  if (!c.relayToken) throw HOLEPUNCH_ABORTED()
  await c.sleeper.pause(10000 + Math.round(Math.random() * 10000))
}

function maybeDestroyEncryptedSocket(c, err) {
  if (isDone(c)) return
  if (c.encryptedSocket.rawStream) return
  if (c.relaySocket) return // waiting for the relay
  if (c.puncher && !c.puncher.destroyed) return // waiting for the puncher
  c.session.destroy()
  c.encryptedSocket.destroy(err)
}

async function abort(c, { peerAddress, relayAddress }, err) {
  try {
    await updateHolepunch(c, peerAddress, relayAddress, {
      error: ERROR.ABORTED,
      firewall: FIREWALL.UNKNOWN,
      round: c.round++,
      connected: false,
      punching: false,
      addresses: null,
      remoteAddress: null,
      token: null,
      remoteToken: null
    })
  } catch (err) {
    safetyCatch(err)
  }

  destroyPuncher(c)
  maybeDestroyEncryptedSocket(c, err)
}

function relayConnection(c, relayThrough, payload, hs) {
  let isInitiator
  let publicKey
  let token

  if (payload.relayThrough) {
    isInitiator = false
    publicKey = payload.relayThrough.publicKey
    token = payload.relayThrough.token
  } else {
    isInitiator = true
    publicKey = relayThrough
    token = c.relayToken
  }

  c.relayToken = token
  c.relaySocket = c.dht.connect(publicKey)
  c.relaySocket.setKeepAlive(c.relayKeepAlive)
  c.relayClient = relay.Client.from(c.relaySocket, { id: c.relaySocket.publicKey })
  c.relayTimeout = setTimeout(onabort, 15000, null)

  c.relayClient.pair(isInitiator, token, c.rawStream).on('error', onabort).on('data', ondata)

  function ondata(remoteId) {
    if (c.relayTimeout) clearRelayTimeout(c)
    if (c.rawStream === null) {
      onabort(null)
      return
    }

    c.relayPaired = true

    const { remotePort, remoteHost, socket } = c.relaySocket.rawStream

    c.rawStream
      .on('close', () => destroyRelayConnection(c))
      .connect(socket, remoteId, remotePort, remoteHost)

    c.encryptedSocket.start(c.rawStream, { handshake: hs })
  }

  function onabort(err) {
    destroyRelayConnection(c)
    maybeDestroyEncryptedSocket(c, err || RELAY_ABORTED())
  }
}

function clearPassiveConnectTimeout(c) {
  clearTimeout(c.passiveConnectTimeout)
  c.passiveConnectTimeout = null
}

function destroyPuncher(c) {
  if (c.puncher) c.puncher.destroy()
  c.session.destroy()
}

function pickServerRelay(relays, clientRelay) {
  for (const r of relays) {
    if (!diffAddress(r.relayAddress, clientRelay)) return r
  }
  return relays[0]
}

function diffAddress(a, b) {
  return a.host !== b.host || a.port !== b.port
}

function defaultCreateHandshake(keyPair, remotePublicKey) {
  return new NoiseWrap(keyPair, remotePublicKey)
}

function defaultCreateSecretStream(isInitiator, rawStream, opts) {
  return new NoiseSecretStream(isInitiator, rawStream, opts)
}

function onlyNonReserved(addr) {
  return !isReserved(addr.host)
}

function isRelay(relaySocket, socket, port, host) {
  const stream = relaySocket.rawStream
  if (!stream) return false
  if (stream.socket !== socket) return false
  return port === stream.remotePort && host === stream.remoteHost
}

function selectRelay(relayThrough) {
  if (typeof relayThrough === 'function') relayThrough = relayThrough()
  if (relayThrough === null) return null
  if (Array.isArray(relayThrough))
    return relayThrough[Math.floor(Math.random() * relayThrough.length)]
  return relayThrough
}

function isRelayAddress(relayAddresses, data) {
  for (const node of relayAddresses) {
    if (node.host === data.from.host && node.port === data.from.port) return true
  }

  return false
}

function noop() {}
const EventEmitter = require('events')
const b4a = require('b4a')
const errors = require('./errors')

module.exports = class ConnectionPool extends EventEmitter {
  constructor(dht) {
    super()

    this._dht = dht
    this._servers = new Map()
    this._connecting = new Map()
    this._connections = new Map()
  }

  _attachServer(server) {
    const keyString = b4a.toString(server.publicKey, 'hex')

    this._servers.set(keyString, server)

    server
      .on('close', () => {
        this._servers.delete(keyString)
      })
      .on('connection', (socket) => {
        this._attachStream(socket, true)
      })
  }

  _attachStream(stream, opened) {
    const existing = this.get(stream.remotePublicKey)

    if (existing) {
      const keepNew =
        stream.isInitiator === existing.isInitiator ||
        b4a.compare(stream.publicKey, stream.remotePublicKey) > 0

      if (keepNew) {
        let closed = false

        const onclose = () => {
          closed = true
        }

        existing
          .on('error', noop)
          .on('close', () => {
            if (closed) return

            stream.off('error', noop).off('close', onclose)

            this._attachStream(stream, opened)
          })
          .destroy(errors.DUPLICATE_CONNECTION())

        stream.on('error', noop).on('close', onclose)
      } else {
        stream.on('error', noop).destroy(errors.DUPLICATE_CONNECTION())
      }

      return
    }

    const session = new ConnectionRef(this, stream)

    const keyString = b4a.toString(stream.remotePublicKey, 'hex')

    if (opened) {
      this._connections.set(keyString, session)

      stream.on('close', () => {
        this._connections.delete(keyString)
      })

      this.emit('connection', stream, session)
    } else {
      this._connecting.set(keyString, session)

      stream
        .on('error', noop)
        .on('close', () => {
          if (opened) this._connections.delete(keyString)
          else this._connecting.delete(keyString)
        })
        .on('open', () => {
          opened = true

          this._connecting.delete(keyString)
          this._connections.set(keyString, session)

          stream.off('error', noop)

          this.emit('connection', stream, session)
        })
    }

    return session
  }

  get connecting() {
    return this._connecting.size
  }

  get connections() {
    return this._connections.values()
  }

  has(publicKey) {
    const keyString = b4a.toString(publicKey, 'hex')

    return this._connections.has(keyString) || this._connecting.has(keyString)
  }

  get(publicKey) {
    const keyString = b4a.toString(publicKey, 'hex')

    const existing = this._connections.get(keyString) || this._connecting.get(keyString)

    return existing?._stream || null
  }
}

class ConnectionRef {
  constructor(pool, stream) {
    this._pool = pool
    this._stream = stream
    this._refs = 0
  }

  active() {
    this._refs++
  }

  inactive() {
    this._refs--
  }

  release() {
    this._stream.destroy()
  }
}

function noop() {}
const crypto = require('hypercore-crypto')

const COMMANDS = (exports.COMMANDS = {
  PEER_HANDSHAKE: 0,
  PEER_HOLEPUNCH: 1,
  FIND_PEER: 2,
  LOOKUP: 3,
  ANNOUNCE: 4,
  UNANNOUNCE: 5,
  MUTABLE_PUT: 6,
  MUTABLE_GET: 7,
  IMMUTABLE_PUT: 8,
  IMMUTABLE_GET: 9,
  PLUGIN: 10
})

exports.BOOTSTRAP_NODES = [
  '88.99.3.86@node1.hyperdht.org:49737',
  '142.93.90.113@node2.hyperdht.org:49737',
  '138.68.147.8@node3.hyperdht.org:49737'
]

exports.FIREWALL = {
  UNKNOWN: 0,
  OPEN: 1,
  CONSISTENT: 2,
  RANDOM: 3
}

exports.ERROR = {
  // noise / connection related
  NONE: 0,
  ABORTED: 1,
  VERSION_MISMATCH: 2,
  TRY_LATER: 3,
  // dht related
  SEQ_REUSED: 16,
  SEQ_TOO_LOW: 17
}

const [
  NS_ANNOUNCE,
  NS_UNANNOUNCE,
  NS_MUTABLE_PUT,
  NS_PEER_HANDSHAKE,
  NS_PEER_HOLEPUNCH,
  NS_PLUGIN
] = crypto.namespace('hyperswarm/dht', [
  COMMANDS.ANNOUNCE,
  COMMANDS.UNANNOUNCE,
  COMMANDS.MUTABLE_PUT,
  COMMANDS.PEER_HANDSHAKE,
  COMMANDS.PEER_HOLEPUNCH,
  COMMANDS.PLUGIN
])

exports.NS = {
  ANNOUNCE: NS_ANNOUNCE,
  UNANNOUNCE: NS_UNANNOUNCE,
  MUTABLE_PUT: NS_MUTABLE_PUT,
  PEER_HANDSHAKE: NS_PEER_HANDSHAKE,
  PEER_HOLEPUNCH: NS_PEER_HOLEPUNCH,
  PLUGIN: NS_PLUGIN
}
const sodium = require('sodium-universal')
const b4a = require('b4a')

function hash(data) {
  const out = b4a.allocUnsafe(32)
  sodium.crypto_generichash(out, data)
  return out
}

function unslabbedHash(data) {
  const out = b4a.allocUnsafeSlow(32)
  sodium.crypto_generichash(out, data)
  return out
}

function createKeyPair(seed) {
  const publicKey = b4a.alloc(32)
  const secretKey = b4a.alloc(64)
  if (seed) sodium.crypto_sign_seed_keypair(publicKey, secretKey, seed)
  else sodium.crypto_sign_keypair(publicKey, secretKey)
  return { publicKey, secretKey }
}

module.exports = {
  hash,
  unslabbedHash,
  createKeyPair
}
const b4a = require('b4a')
const cenc = require('compact-encoding')

function encodeUnslab(enc, m) {
  // Faster than unslab(c.encode(enc, data)) because it avoids the mem copy.
  // Makes sense to put in compact-encoding when we need it in other modules too
  const state = cenc.state()
  enc.preencode(state, m)
  state.buffer = b4a.allocUnsafeSlow(state.end)
  enc.encode(state, m)
  return state.buffer
}

module.exports = {
  encodeUnslab
}
module.exports = class DHTError extends Error {
  constructor(msg, code, fn = DHTError) {
    super(`${code}: ${msg}`)
    this.code = code

    if (Error.captureStackTrace) {
      Error.captureStackTrace(this, fn)
    }
  }

  get name() {
    return 'DHTError'
  }

  static BAD_HANDSHAKE_REPLY(msg = 'Bad handshake reply') {
    return new DHTError(msg, 'BAD_HANDSHAKE_REPLY', DHTError.BAD_HANDSHAKE_REPLY)
  }

  static BAD_HOLEPUNCH_REPLY(msg = 'Bad holepunch reply') {
    return new DHTError(msg, 'BAD_HOLEPUNCH_REPLY', DHTError.BAD_HOLEPUNCH_REPLY)
  }

  static HOLEPUNCH_ABORTED(msg = 'Holepunch aborted') {
    return new DHTError(msg, 'HOLEPUNCH_ABORTED', DHTError.HOLEPUNCH_ABORTED)
  }

  static HOLEPUNCH_INVALID(msg = 'Invalid holepunch payload') {
    return new DHTError(msg, 'HOLEPUNCH_INVALID', DHTError.HOLEPUNCH_INVALID)
  }

  static HOLEPUNCH_PROBE_TIMEOUT(msg = 'Holepunching probe did not finish in time') {
    return new DHTError(msg, 'HOLEPUNCH_PROBE_TIMEOUT', DHTError.HOLEPUNCH_PROBE_TIMEOUT)
  }

  static HOLEPUNCH_DOUBLE_RANDOMIZED_NATS(msg = 'Both remote and local NATs are randomized') {
    return new DHTError(
      msg,
      'HOLEPUNCH_DOUBLE_RANDOMIZED_NATS',
      DHTError.HOLEPUNCH_DOUBLE_RANDOMIZED_NATS
    )
  }

  static CANNOT_HOLEPUNCH(msg = 'Cannot holepunch to remote') {
    return new DHTError(msg, 'CANNOT_HOLEPUNCH', DHTError.CANNOT_HOLEPUNCH)
  }

  static REMOTE_NOT_HOLEPUNCHING(msg = 'Remote is not holepunching') {
    return new DHTError(msg, 'REMOTE_NOT_HOLEPUNCHING', DHTError.REMOTE_NOT_HOLEPUNCHING)
  }

  static REMOTE_NOT_HOLEPUNCHABLE(msg = 'Remote is not holepunchable') {
    return new DHTError(msg, 'REMOTE_NOT_HOLEPUNCHABLE', DHTError.REMOTE_NOT_HOLEPUNCHABLE)
  }

  static REMOTE_ABORTED(msg = 'Remote aborted') {
    return new DHTError(msg, 'REMOTE_ABORTED', DHTError.REMOTE_ABORTED)
  }

  static HANDSHAKE_UNFINISHED(msg = 'Handshake did not finish') {
    return new DHTError(msg, 'HANDSHAKE_UNFINISHED', DHTError.HANDSHAKE_UNFINISHED)
  }

  static HANDSHAKE_INVALID(msg = 'Received invalid handshake') {
    return new DHTError(msg, 'HANDSHAKE_INVALID', DHTError.HANDSHAKE_INVALID)
  }

  static ALREADY_LISTENING(msg = 'Already listening') {
    return new DHTError(msg, 'ALREADY_LISTENING', DHTError.ALREADY_LISTENING)
  }

  static KEYPAIR_ALREADY_USED(msg = 'Keypair already used') {
    return new DHTError(msg, 'KEYPAIR_ALREADY_USED', DHTError.KEYPAIR_ALREADY_USED)
  }

  static NODE_DESTROYED(msg = 'Node destroyed') {
    return new DHTError(msg, 'NODE_DESTROYED', DHTError.NODE_DESTROYED)
  }

  static PEER_CONNECTION_FAILED(msg = 'Could not connect to peer') {
    return new DHTError(msg, 'PEER_CONNECTION_FAILED', DHTError.PEER_CONNECTION_FAILED)
  }

  static PEER_NOT_FOUND(msg = 'Peer not found') {
    return new DHTError(msg, 'PEER_NOT_FOUND', DHTError.PEER_NOT_FOUND)
  }

  static STREAM_NOT_CONNECTED(msg = 'Stream is not connected') {
    return new DHTError(msg, 'STREAM_NOT_CONNECTED', DHTError.STREAM_NOT_CONNECTED)
  }

  static SERVER_INCOMPATIBLE(msg = 'Server is using an incompatible version') {
    return new DHTError(msg, 'SERVER_INCOMPATIBLE', DHTError.SERVER_INCOMPATIBLE)
  }

  static SERVER_ERROR(msg = 'Server returned an error') {
    return new DHTError(msg, 'SERVER_ERROR', DHTError.SERVER_ERROR)
  }

  static DUPLICATE_CONNECTION(msg = 'Duplicate connection') {
    return new DHTError(msg, 'DUPLICATE_CONNECTION', DHTError.DUPLICATE_CONNECTION)
  }

  static RELAY_ABORTED(msg = 'Relay aborted') {
    return new DHTError(msg, 'RELAY_ABORTED', DHTError.RELAY_ABORTED)
  }

  static SUSPENDED(msg = 'Suspended') {
    return new DHTError(msg, 'SUSPENDED', DHTError.SUSPENDED)
  }
}
const b4a = require('b4a')
const Nat = require('./nat')
const Sleeper = require('./sleeper')
const { FIREWALL } = require('./constants')

const BIRTHDAY_SOCKETS = 256
const HOLEPUNCH = b4a.from([0])
const HOLEPUNCH_TTL = 5
const DEFAULT_TTL = 64
const MAX_REOPENS = 3

module.exports = class Holepuncher {
  constructor(dht, session, isInitiator, remoteFirewall = FIREWALL.UNKNOWN) {
    const holder = dht._socketPool.acquire()

    this.dht = dht
    this.session = session

    this.nat = new Nat(dht, session, holder.socket)
    this.nat.autoSample()

    this.isInitiator = isInitiator

    // events
    this.onconnect = noop
    this.onabort = noop

    this.punching = false
    this.connected = false
    this.destroyed = false
    this.randomized = false

    // track remote state
    this.remoteFirewall = remoteFirewall
    this.remoteAddresses = []
    this.remoteHolepunching = false

    this._sleeper = new Sleeper()
    this._reopening = null
    this._timeout = null
    this._punching = null
    this._allHolders = []
    this._holder = this._addRef(holder)
  }

  get socket() {
    return this._holder.socket
  }

  updateRemote({ punching, firewall, addresses, verified }) {
    const remoteAddresses = []

    if (addresses) {
      for (const addr of addresses) {
        remoteAddresses.push({
          host: addr.host,
          port: addr.port,
          verified: verified === addr.host || this._isVerified(addr.host)
        })
      }
    }

    this.remoteFirewall = firewall
    this.remoteAddresses = remoteAddresses
    this.remoteHolepunching = punching
  }

  _isVerified(host) {
    for (const addr of this.remoteAddresses) {
      if (addr.verified && addr.host === host) {
        return true
      }
    }
    return false
  }

  ping(addr, socket = this._holder.socket) {
    return holepunch(socket, addr, false)
  }

  openSession(addr, socket = this._holder.socket) {
    return holepunch(socket, addr, true)
  }

  async analyze(allowReopen) {
    await this.nat.analyzing
    if (this._unstable()) {
      if (!allowReopen) return false
      if (!this._reopening) this._reopening = this._reopen()
      return this._reopening
    }
    return true
  }

  _unstable() {
    // TODO!!: We need an additional heuristic here... If we were NOT random in the past we should also do this.
    const firewall = this.nat.firewall
    return (
      (this.remoteFirewall >= FIREWALL.RANDOM && firewall >= FIREWALL.RANDOM) ||
      firewall === FIREWALL.UNKNOWN
    )
  }

  _reset() {
    const prev = this._holder

    this._allHolders.pop()
    this._holder = this._addRef(this.dht._socketPool.acquire())

    prev.release()
    this.nat.destroy()

    this.nat = new Nat(this.dht, this.session, this._holder.socket)
    // TODO: maybe make auto sampling configurable somehow?
    this.nat.autoSample()
  }

  _addRef(ref) {
    this._allHolders.push(ref)
    ref.onholepunchmessage = (msg, rinfo) => this._onholepunchmessage(msg, rinfo, ref)
    return ref
  }

  _onholepunchmessage(_, addr, ref) {
    if (!this.isInitiator) {
      // TODO: we don't need this if we had a way to connect a socket to many hosts
      holepunch(ref.socket, addr, false) // never fails
      return
    }

    if (this.connected) return

    this.connected = true
    this.punching = false

    for (const r of this._allHolders) {
      if (r === ref) continue
      r.release()
    }

    this._allHolders[0] = ref
    while (this._allHolders.length > 1) this._allHolders.pop()

    this._decrementRandomized()
    this.onconnect(ref.socket, addr.port, addr.host)
  }

  _done() {
    return this.destroyed || this.connected
  }

  async _reopen() {
    for (let i = 0; this._unstable() && i < MAX_REOPENS && !this._done() && !this.punching; i++) {
      this._reset()
      await this.nat.analyzing
    }

    return coerceFirewall(this.nat.firewall) === FIREWALL.CONSISTENT
  }

  punch() {
    if (!this._punching) this._punching = this._punch()
    return this._punching
  }

  async _punch() {
    if (this._done() || !this.remoteAddresses.length) return false

    this.punching = true

    // Coerce into consistency for now. Obvs we could make this this more efficient if we use that info
    // but that's seldomly used since those will just use tcp most of the time.

    const local = coerceFirewall(this.nat.firewall)
    const remote = coerceFirewall(this.remoteFirewall)

    // Note that most of these async functions are meant to run in the background
    // which is why we don't await them here and why they are not allowed to throw

    let remoteVerifiedAddress = null
    for (const addr of this.remoteAddresses) {
      if (addr.verified) {
        remoteVerifiedAddress = addr
        break
      }
    }

    if (local === FIREWALL.CONSISTENT && remote === FIREWALL.CONSISTENT) {
      this.dht.stats.punches.consistent++
      this._consistentProbe()
      return true
    }

    if (!remoteVerifiedAddress) return false

    if (local === FIREWALL.CONSISTENT && remote >= FIREWALL.RANDOM) {
      this.dht.stats.punches.random++
      this._incrementRandomized()
      this._randomProbes(remoteVerifiedAddress)
      return true
    }

    if (local >= FIREWALL.RANDOM && remote === FIREWALL.CONSISTENT) {
      this.dht.stats.punches.random++
      this._incrementRandomized()
      await this._openBirthdaySockets(remoteVerifiedAddress)
      if (this.punching) this._keepAliveRandomNat(remoteVerifiedAddress)
      return true
    }

    return false
  }

  // Note that this never throws so it is safe to run in the background
  async _consistentProbe() {
    // Here we do the sleep first because the "fast open" mode in the server just fired a ping
    if (!this.isInitiator) await this._sleeper.pause(1000)

    let tries = 0

    while (this.punching && tries++ < 10) {
      for (const addr of this.remoteAddresses) {
        // only try unverified addresses every 4 ticks
        if (!addr.verified && (tries & 3) !== 0) continue
        await holepunch(this._holder.socket, addr, false)
      }
      if (this.punching) await this._sleeper.pause(1000)
    }

    this._autoDestroy()
  }

  // Note that this never throws so it is safe to run in the background
  async _randomProbes(remoteAddr) {
    let tries = 1750 // ~35s

    while (this.punching && tries-- > 0) {
      const addr = { host: remoteAddr.host, port: randomPort() }
      await holepunch(this._holder.socket, addr, false)
      if (this.punching) await this._sleeper.pause(20)
    }

    this._autoDestroy()
  }

  // Note that this never throws so it is safe to run in the background
  async _keepAliveRandomNat(remoteAddr) {
    let i = 0
    let lowTTLRounds = 1

    // TODO: experiment with this here. We just bursted all the messages in
    // openOtherSockets to ensure the sockets are open, so it's potentially
    // a good idea to slow down for a bit.
    await this._sleeper.pause(100)

    let tries = 1750 // ~35s

    while (this.punching && tries-- > 0) {
      if (i === this._allHolders.length) {
        i = 0
        if (lowTTLRounds > 0) lowTTLRounds--
      }

      await holepunch(this._allHolders[i++].socket, remoteAddr, lowTTLRounds > 0)
      if (this.punching) await this._sleeper.pause(20)
    }

    this._autoDestroy()
  }

  async _openBirthdaySockets(remoteAddr) {
    while (this.punching && this._allHolders.length < BIRTHDAY_SOCKETS) {
      const ref = this._addRef(this.dht._socketPool.acquire())
      await holepunch(ref.socket, remoteAddr, HOLEPUNCH_TTL)
    }
  }

  _autoDestroy() {
    if (!this.connected) this.destroy()
  }

  _incrementRandomized() {
    if (!this.randomized) {
      this.randomized = true
      this.dht._randomPunches++
    }
  }

  _decrementRandomized() {
    if (this.randomized) {
      this.dht._lastRandomPunch = Date.now()
      this.randomized = false
      this.dht._randomPunches--
    }
  }

  destroy() {
    if (this.destroyed) return
    this.destroyed = true
    this.punching = false

    for (const ref of this._allHolders) ref.release()
    this._allHolders = []
    this.nat.destroy()

    if (!this.connected) {
      this._decrementRandomized()
      this.onabort()
    }
  }

  static ping(socket, addr) {
    return holepunch(socket, addr, false)
  }

  static localAddresses(socket) {
    return localAddresses(socket)
  }

  static matchAddress(myAddresses, externalAddresses) {
    return matchAddress(myAddresses, externalAddresses)
  }
}

function holepunch(socket, addr, lowTTL) {
  return socket.send(HOLEPUNCH, addr.port, addr.host, lowTTL ? HOLEPUNCH_TTL : DEFAULT_TTL)
}

function randomPort() {
  return (1000 + Math.random() * 64536) | 0
}

function coerceFirewall(fw) {
  return fw === FIREWALL.OPEN ? FIREWALL.CONSISTENT : fw
}

function localAddresses(socket) {
  const addrs = []
  const { host, port } = socket.address()

  if (host === '127.0.0.1') return [{ host, port }]

  for (const n of socket.udx.networkInterfaces()) {
    if (n.family !== 4 || n.internal) continue

    addrs.push({ host: n.host, port })
  }

  if (addrs.length === 0) {
    addrs.push({ host: '127.0.0.1', port })
  }

  return addrs
}

function matchAddress(localAddresses, remoteLocalAddresses) {
  if (remoteLocalAddresses.length === 0) return null

  let best = { segment: 1, addr: null }

  for (const localAddress of localAddresses) {
    // => 192.168.122.238
    const a = localAddress.host.split('.')

    for (const remoteAddress of remoteLocalAddresses) {
      // => 192.168.0.23
      // => 192.168.122.1
      const b = remoteAddress.host.split('.')

      // Matches 192.*.*.*
      if (a[0] === b[0]) {
        if (best.segment === 1) best = { segment: 2, addr: remoteAddress }

        // Matches 192.168.*.*
        if (a[1] === b[1]) {
          if (best.segment === 2) best = { segment: 3, addr: remoteAddress }

          // Matches 192.168.122.*
          if (a[2] === b[2]) return remoteAddress
        }
      }
    }
  }

  return best.addr
}

function noop() {}
const c = require('compact-encoding')

const ipv4 = {
  ...c.ipv4Address,
  decode(state) {
    const ip = c.ipv4Address.decode(state)
    return {
      host: ip.host,
      port: ip.port
    }
  }
}

const ipv4Array = c.array(ipv4)

const ipv6 = {
  ...c.ipv6Address,
  decode(state) {
    const ip = c.ipv6Address.decode(state)
    return {
      host: ip.host,
      port: ip.port
    }
  }
}

const ipv6Array = c.array(ipv6)

exports.handshake = {
  preencode(state, m) {
    state.end += 1 + 1 + (m.peerAddress ? 6 : 0) + (m.relayAddress ? 6 : 0)
    c.buffer.preencode(state, m.noise)
  },
  encode(state, m) {
    const flags = (m.peerAddress ? 1 : 0) | (m.relayAddress ? 2 : 0)

    c.uint.encode(state, flags)
    c.uint.encode(state, m.mode)
    c.buffer.encode(state, m.noise)

    if (m.peerAddress) ipv4.encode(state, m.peerAddress)
    if (m.relayAddress) ipv4.encode(state, m.relayAddress)
  },
  decode(state) {
    const flags = c.uint.decode(state)

    return {
      mode: c.uint.decode(state),
      noise: c.buffer.decode(state),
      peerAddress: flags & 1 ? ipv4.decode(state) : null,
      relayAddress: flags & 2 ? ipv4.decode(state) : null
    }
  }
}

const relayInfo = {
  preencode(state, m) {
    state.end += 12
  },
  encode(state, m) {
    ipv4.encode(state, m.relayAddress)
    ipv4.encode(state, m.peerAddress)
  },
  decode(state) {
    return {
      relayAddress: ipv4.decode(state),
      peerAddress: ipv4.decode(state)
    }
  }
}

const relayInfoArray = c.array(relayInfo)

const holepunchInfo = {
  preencode(state, m) {
    c.uint.preencode(state, m.id)
    relayInfoArray.preencode(state, m.relays)
  },
  encode(state, m) {
    c.uint.encode(state, m.id)
    relayInfoArray.encode(state, m.relays)
  },
  decode(state) {
    return {
      id: c.uint.decode(state),
      relays: relayInfoArray.decode(state)
    }
  }
}

const udxInfo = {
  preencode(state, m) {
    state.end += 2 // version + features
    c.uint.preencode(state, m.id)
    c.uint.preencode(state, m.seq)
  },
  encode(state, m) {
    c.uint.encode(state, 1)
    c.uint.encode(state, m.reusableSocket ? 1 : 0)
    c.uint.encode(state, m.id)
    c.uint.encode(state, m.seq)
  },
  decode(state) {
    const version = c.uint.decode(state)
    const features = c.uint.decode(state)

    return {
      version,
      reusableSocket: (features & 1) !== 0,
      id: c.uint.decode(state),
      seq: c.uint.decode(state)
    }
  }
}

const secretStreamInfo = {
  preencode(state, m) {
    c.uint.preencode(state, 1)
  },
  encode(state, m) {
    c.uint.encode(state, 1)
  },
  decode(state) {
    return {
      version: c.uint.decode(state)
    }
  }
}

const relayThroughInfo = {
  preencode(state, m) {
    c.uint.preencode(state, 1) // version
    c.uint.preencode(state, 0) // flags
    c.fixed32.preencode(state, m.publicKey)
    c.fixed32.preencode(state, m.token)
  },
  encode(state, m) {
    c.uint.encode(state, 1)
    c.uint.encode(state, 0)
    c.fixed32.encode(state, m.publicKey)
    c.fixed32.encode(state, m.token)
  },
  decode(state) {
    const version = c.uint.decode(state)
    c.uint.decode(state)

    return {
      version,
      publicKey: c.fixed32.decode(state),
      token: c.fixed32.decode(state)
    }
  }
}

exports.noisePayload = {
  preencode(state, m) {
    state.end += 4 // version + flags + error + firewall
    if (m.holepunch) holepunchInfo.preencode(state, m.holepunch)
    if (m.addresses4 && m.addresses4.length) ipv4Array.preencode(state, m.addresses4)
    if (m.addresses6 && m.addresses6.length) ipv6Array.preencode(state, m.addresses6)
    if (m.udx) udxInfo.preencode(state, m.udx)
    if (m.secretStream) secretStreamInfo.preencode(state, m.secretStream)
    if (m.relayThrough) relayThroughInfo.preencode(state, m.relayThrough)
    if (m.relayAddresses) ipv4Array.preencode(state, m.relayAddresses)
  },
  encode(state, m) {
    let flags = 0

    if (m.holepunch) flags |= 1
    if (m.addresses4 && m.addresses4.length) flags |= 2
    if (m.addresses6 && m.addresses6.length) flags |= 4
    if (m.udx) flags |= 8
    if (m.secretStream) flags |= 16
    if (m.relayThrough) flags |= 32
    if (m.relayAddresses) flags |= 64

    c.uint.encode(state, 1) // version
    c.uint.encode(state, flags)
    c.uint.encode(state, m.error)
    c.uint.encode(state, m.firewall)

    if (m.holepunch) holepunchInfo.encode(state, m.holepunch)
    if (m.addresses4 && m.addresses4.length) ipv4Array.encode(state, m.addresses4)
    if (m.addresses6 && m.addresses6.length) ipv6Array.encode(state, m.addresses6)
    if (m.udx) udxInfo.encode(state, m.udx)
    if (m.secretStream) secretStreamInfo.encode(state, m.secretStream)
    if (m.relayThrough) relayThroughInfo.encode(state, m.relayThrough)
    if (m.relayAddresses) ipv4Array.encode(state, m.relayAddresses)
  },
  decode(state) {
    const version = c.uint.decode(state)

    if (version !== 1) {
      // Do not attempt to decode but return this back to the user so they can
      // actually handle it
      return {
        version,
        error: 0,
        firewall: 0,
        holepunch: null,
        addresses4: [],
        addresses6: [],
        udx: null,
        secretStream: null,
        relayThrough: null,
        relayAddresses: null
      }
    }

    const flags = c.uint.decode(state)

    return {
      version,
      error: c.uint.decode(state),
      firewall: c.uint.decode(state),
      holepunch: (flags & 1) !== 0 ? holepunchInfo.decode(state) : null,
      addresses4: (flags & 2) !== 0 ? ipv4Array.decode(state) : [],
      addresses6: (flags & 4) !== 0 ? ipv6Array.decode(state) : [],
      udx: (flags & 8) !== 0 ? udxInfo.decode(state) : null,
      secretStream: (flags & 16) !== 0 ? secretStreamInfo.decode(state) : null,
      relayThrough: (flags & 32) !== 0 ? relayThroughInfo.decode(state) : null,
      relayAddresses: (flags & 64) !== 0 ? ipv4Array.decode(state) : null
    }
  }
}

exports.holepunch = {
  preencode(state, m) {
    state.end += 2
    c.uint.preencode(state, m.id)
    c.buffer.preencode(state, m.payload)
    if (m.peerAddress) ipv4.preencode(state, m.peerAddress)
  },
  encode(state, m) {
    const flags = m.peerAddress ? 1 : 0
    c.uint.encode(state, flags)
    c.uint.encode(state, m.mode)
    c.uint.encode(state, m.id)
    c.buffer.encode(state, m.payload)
    if (m.peerAddress) ipv4.encode(state, m.peerAddress)
  },
  decode(state) {
    const flags = c.uint.decode(state)
    return {
      mode: c.uint.decode(state),
      id: c.uint.decode(state),
      payload: c.buffer.decode(state),
      peerAddress: flags & 1 ? ipv4.decode(state) : null
    }
  }
}

exports.holepunchPayload = {
  preencode(state, m) {
    state.end += 4 // flags + error + firewall + round
    if (m.addresses) ipv4Array.preencode(state, m.addresses)
    if (m.remoteAddress) state.end += 6
    if (m.token) state.end += 32
    if (m.remoteToken) state.end += 32
  },
  encode(state, m) {
    const flags =
      (m.connected ? 1 : 0) |
      (m.punching ? 2 : 0) |
      (m.addresses ? 4 : 0) |
      (m.remoteAddress ? 8 : 0) |
      (m.token ? 16 : 0) |
      (m.remoteToken ? 32 : 0)

    c.uint.encode(state, flags)
    c.uint.encode(state, m.error)
    c.uint.encode(state, m.firewall)
    c.uint.encode(state, m.round)

    if (m.addresses) ipv4Array.encode(state, m.addresses)
    if (m.remoteAddress) ipv4.encode(state, m.remoteAddress)
    if (m.token) c.fixed32.encode(state, m.token)
    if (m.remoteToken) c.fixed32.encode(state, m.remoteToken)
  },
  decode(state) {
    const flags = c.uint.decode(state)

    return {
      error: c.uint.decode(state),
      firewall: c.uint.decode(state),
      round: c.uint.decode(state),
      connected: (flags & 1) !== 0,
      punching: (flags & 2) !== 0,
      addresses: (flags & 4) !== 0 ? ipv4Array.decode(state) : null,
      remoteAddress: (flags & 8) !== 0 ? ipv4.decode(state) : null,
      token: (flags & 16) !== 0 ? c.fixed32.decode(state) : null,
      remoteToken: (flags & 32) !== 0 ? c.fixed32.decode(state) : null
    }
  }
}

const peer = (exports.peer = {
  preencode(state, m) {
    state.end += 32
    ipv4Array.preencode(state, m.relayAddresses)
  },
  encode(state, m) {
    c.fixed32.encode(state, m.publicKey)
    ipv4Array.encode(state, m.relayAddresses)
  },
  decode(state) {
    return {
      publicKey: c.fixed32.decode(state),
      relayAddresses: ipv4Array.decode(state)
    }
  }
})

const peers = (exports.peers = c.array(peer))

const rawPeers = c.array(c.raw)

exports.lookupRawReply = {
  preencode(state, m) {
    rawPeers.preencode(state, m.peers)
    c.uint.preencode(state, m.bump)
  },
  encode(state, m) {
    rawPeers.encode(state, m.peers)
    c.uint.encode(state, m.bump)
  },
  decode(state) {
    return {
      peers: peers.decode(state),
      bump: state.start < state.end ? c.uint.decode(state) : 0
    }
  }
}

exports.announce = {
  preencode(state, m) {
    state.end++ // flags
    if (m.peer) peer.preencode(state, m.peer)
    if (m.refresh) state.end += 32
    if (m.signature) state.end += 64
    if (m.bump) c.uint.preencode(state, m.bump)
  },
  encode(state, m) {
    const flags = (m.peer ? 1 : 0) | (m.refresh ? 2 : 0) | (m.signature ? 4 : 0) | (m.bump ? 8 : 0)
    c.uint.encode(state, flags)
    if (m.peer) peer.encode(state, m.peer)
    if (m.refresh) c.fixed32.encode(state, m.refresh)
    if (m.signature) c.fixed64.encode(state, m.signature)
    if (m.bump) c.uint.encode(state, m.bump)
  },
  decode(state) {
    const flags = c.uint.decode(state)

    return {
      peer: (flags & 1) !== 0 ? peer.decode(state) : null,
      refresh: (flags & 2) !== 0 ? c.fixed32.decode(state) : null,
      signature: (flags & 4) !== 0 ? c.fixed64.decode(state) : null,
      bump: (flags & 8) !== 0 ? c.uint.decode(state) : 0
    }
  }
}

exports.mutableSignable = {
  preencode(state, m) {
    c.uint.preencode(state, m.seq)
    c.buffer.preencode(state, m.value)
  },
  encode(state, m) {
    c.uint.encode(state, m.seq)
    c.buffer.encode(state, m.value)
  },
  decode(state) {
    return {
      seq: c.uint.decode(state),
      value: c.buffer.decode(state)
    }
  }
}

exports.mutablePutRequest = {
  preencode(state, m) {
    c.fixed32.preencode(state, m.publicKey)
    c.uint.preencode(state, m.seq)
    c.buffer.preencode(state, m.value)
    c.fixed64.preencode(state, m.signature)
  },
  encode(state, m) {
    c.fixed32.encode(state, m.publicKey)
    c.uint.encode(state, m.seq)
    c.buffer.encode(state, m.value)
    c.fixed64.encode(state, m.signature)
  },
  decode(state) {
    return {
      publicKey: c.fixed32.decode(state),
      seq: c.uint.decode(state),
      value: c.buffer.decode(state),
      signature: c.fixed64.decode(state)
    }
  }
}

exports.mutableGetResponse = {
  preencode(state, m) {
    c.uint.preencode(state, m.seq)
    c.buffer.preencode(state, m.value)
    c.fixed64.preencode(state, m.signature)
  },
  encode(state, m) {
    c.uint.encode(state, m.seq)
    c.buffer.encode(state, m.value)
    c.fixed64.encode(state, m.signature)
  },
  decode(state) {
    return {
      seq: c.uint.decode(state),
      value: c.buffer.decode(state),
      signature: c.fixed64.decode(state)
    }
  }
}

exports.pluginRequest = {
  preencode(state, m) {
    c.string.preencode(state, m.plugin)
    c.uint.preencode(state, m.version)
    c.uint.preencode(state, m.command)
    state.end++ // max flag is 1 so always one byte

    if (m.value) c.buffer.preencode(state, m.value)
  },
  encode(state, m) {
    const flags = m.value ? 1 : 0

    c.string.encode(state, m.plugin)
    c.uint.encode(state, m.version)
    c.uint.encode(state, m.command)
    c.uint.encode(state, flags)

    if (m.value) c.buffer.encode(state, m.value)
  },
  decode(state) {
    const r0 = c.string.decode(state)
    const r1 = c.uint.decode(state)
    const r2 = c.uint.decode(state)
    const flags = c.uint.decode(state)

    return {
      plugin: r0,
      version: r1,
      command: r2,
      value: (flags & 1) !== 0 ? c.buffer.decode(state) : null
    }
  }
}
const { FIREWALL } = require('../lib/constants')

module.exports = class Nat {
  constructor(dht, session, socket) {
    this._samplesHost = []
    this._samplesFull = []
    this._visited = new Map()
    this._resolve = null
    this._minSamples = 4
    this._autoSampling = false

    this.dht = dht
    this.session = session
    this.socket = socket

    this.sampled = 0
    this.firewall = dht.firewalled ? FIREWALL.UNKNOWN : FIREWALL.OPEN
    this.addresses = null

    this.analyzing = new Promise((resolve) => {
      this._resolve = resolve
    })
  }

  autoSample(retry = true) {
    if (this._autoSampling) return
    this._autoSampling = true

    const self = this
    const socket = this.socket
    const maxPings = this._minSamples

    let skip = this.dht.nodes.length >= 8 ? 5 : 0
    let pending = 0

    // TODO: it would be best to pick the nodes to help us based on latency to us
    // That should reduce connect latency in general. We should investigate tracking that later on.

    // TODO 2: try to pick nodes with different IPs as well, as that'll help multi IP cell connections...
    // If we expose this from the nat sampler then the DHT should be able to help us filter out scams as well...

    for (
      let node = this.dht.nodes.latest;
      node && this.sampled + pending < maxPings;
      node = node.prev
    ) {
      if (skip > 0) {
        skip--
        continue
      }

      const ref = node.host + ':' + node.port

      if (this._visited.has(ref)) continue
      this._visited.set(ref, 1)

      pending++
      this.session.ping(node, { socket, retry: false }).then(onpong, onskip)
    }

    pending++
    onskip()

    function onpong(res) {
      self.add(res.to, res.from)
      onskip()
    }

    function onskip() {
      if (--pending === 0 && self.sampled < self._minSamples) {
        if (retry) {
          self._autoSampling = false
          self.autoSample(false)
          return
        }
        self._resolve()
      }
    }
  }

  destroy() {
    this._autoSampling = true
    this._minSamples = 0
    this._resolve()
  }

  unfreeze() {
    this.frozen = false
    this._updateFirewall()
    this._updateAddresses()
  }

  freeze() {
    this.frozen = true
  }

  _updateFirewall() {
    if (!this.dht.firewalled) {
      this.firewall = FIREWALL.OPEN
      return
    }

    if (this.sampled < 3) return

    const max = this._samplesFull[0].hits

    if (max >= 3) {
      this.firewall = FIREWALL.CONSISTENT
      return
    }

    if (max === 1) {
      this.firewall = FIREWALL.RANDOM
      return
    }

    // else max === 2

    // 1 host, >= 4 total samples ie, 2 bad ones -> random
    if (this._samplesHost.length === 1 && this.sampled > 3) {
      this.firewall = FIREWALL.RANDOM
      return
    }

    // double hit on two different ips -> assume consistent
    if (this._samplesHost.length > 1 && this._samplesFull[1].hits > 1) {
      this.firewall = FIREWALL.CONSISTENT
      return
    }

    // (4 is just means - all the samples we expect) - no decision - assume random
    if (this.sampled > 4) {
      this.firewall = FIREWALL.RANDOM
    }
  }

  _updateAddresses() {
    if (this.firewall === FIREWALL.UNKNOWN) {
      this.addresses = null
      return
    }

    if (this.firewall === FIREWALL.RANDOM) {
      this.addresses = [this._samplesHost[0]]
      return
    }

    if (this.firewall === FIREWALL.CONSISTENT) {
      this.addresses = []
      for (const addr of this._samplesFull) {
        if (addr.hits >= 2 || this.addresses.length < 2) this.addresses.push(addr)
      }
    }
  }

  update() {
    if (this.dht.firewalled && this.firewall === FIREWALL.OPEN) {
      this.firewall = FIREWALL.UNKNOWN
    }
    this._updateFirewall()
    this._updateAddresses()
  }

  add(addr, from) {
    const ref = from.host + ':' + from.port

    if (this._visited.get(ref) === 2) return
    this._visited.set(ref, 2)

    addSample(this._samplesHost, addr.host, 0)
    addSample(this._samplesFull, addr.host, addr.port)

    if ((++this.sampled >= 3 || !this.dht.firewalled) && !this.frozen) {
      this.update()
    }

    if (this.firewall === FIREWALL.CONSISTENT || this.firewall === FIREWALL.OPEN) {
      this._resolve()
    } else if (this.sampled >= this._minSamples) {
      this._resolve()
    }
  }
}

function addSample(samples, host, port) {
  for (let i = 0; i < samples.length; i++) {
    const s = samples[i]

    if (s.port !== port || s.host !== host) continue
    s.hits++

    for (; i > 0; i--) {
      const prev = samples[i - 1]
      if (prev.hits >= s.hits) return
      samples[i - 1] = s
      samples[i] = prev
    }

    return
  }

  samples.push({
    host,
    port,
    hits: 1
  })
}
const NoiseSecretStream = require('@hyperswarm/secret-stream')
const NoiseHandshake = require('noise-handshake')
const curve = require('noise-curve-ed')
const c = require('compact-encoding')
const b4a = require('b4a')
const sodium = require('sodium-universal')
const m = require('./messages')
const { NS } = require('./constants')
const { HANDSHAKE_UNFINISHED } = require('./errors')

const NOISE_PROLOUGE = NS.PEER_HANDSHAKE

module.exports = class NoiseWrap {
  constructor(keyPair, remotePublicKey) {
    this.isInitiator = !!remotePublicKey
    this.remotePublicKey = remotePublicKey
    this.keyPair = keyPair
    this.handshake = new NoiseHandshake('IK', this.isInitiator, keyPair, { curve })
    this.handshake.initialise(NOISE_PROLOUGE, remotePublicKey)
  }

  send(payload) {
    const buf = c.encode(m.noisePayload, payload)
    return this.handshake.send(buf)
  }

  recv(buf) {
    const payload = c.decode(m.noisePayload, this.handshake.recv(buf))
    this.remotePublicKey = b4a.toBuffer(this.handshake.rs)
    return payload
  }

  final() {
    if (!this.handshake.complete) throw HANDSHAKE_UNFINISHED()

    const holepunchSecret = b4a.allocUnsafe(32)

    sodium.crypto_generichash(holepunchSecret, NS.PEER_HOLEPUNCH, this.handshake.hash)

    return {
      isInitiator: this.isInitiator,
      publicKey: this.keyPair.publicKey,
      streamId: this.streamId,
      remotePublicKey: this.remotePublicKey,
      remoteId: NoiseSecretStream.id(this.handshake.hash, !this.isInitiator),
      holepunchSecret,
      hash: b4a.toBuffer(this.handshake.hash),
      rx: b4a.toBuffer(this.handshake.rx),
      tx: b4a.toBuffer(this.handshake.tx)
    }
  }
}
const c = require('compact-encoding')
const sodium = require('sodium-universal')
const RecordCache = require('record-cache')
const Cache = require('xache')
const b4a = require('b4a')
const unslab = require('unslab')

const { encodeUnslab } = require('./encode')
const m = require('./messages')
const { NS, ERROR } = require('./constants')

const EMPTY = b4a.alloc(0)
const TMP = b4a.allocUnsafe(32)
const MAX_BUMP_DRIFT = 60_000

module.exports = class Persistent {
  constructor(dht, opts) {
    this.dht = dht
    this.records = new RecordCache(opts.records)
    this.bumps = new Cache(opts.bumps)
    this.refreshes = new Cache(opts.refreshes)
    this.mutables = new Cache(opts.mutables)
    this.immutables = new Cache(opts.immutables)
  }

  onlookup(req) {
    if (!req.target) return

    const k = b4a.toString(req.target, 'hex')
    const records = this.records.get(k, 20)
    const bump = this.bumps.get(k) || 0
    const fwd = this.dht._router.get(k)

    if (fwd && records.length < 20) records.push(fwd.record)

    req.reply(records.length ? c.encode(m.lookupRawReply, { peers: records, bump }) : null)
  }

  onfindpeer(req) {
    if (!req.target) return
    const fwd = this.dht._router.get(req.target)
    req.reply(fwd ? fwd.record : null)
  }

  unannounce(target, publicKey) {
    const k = b4a.toString(target, 'hex')
    sodium.crypto_generichash(TMP, publicKey)

    if (b4a.equals(TMP, target)) this.dht._router.delete(k)
    this.records.remove(k, publicKey)
  }

  onunannounce(req) {
    if (!req.target || !req.token) return

    const unann = decode(m.announce, req.value)
    if (unann === null) return

    const { peer, signature } = unann
    if (!peer || !signature) return

    const signable = annSignable(req.target, req.token, this.dht.id, unann, NS.UNANNOUNCE)

    if (!sodium.crypto_sign_verify_detached(signature, signable, peer.publicKey)) {
      return
    }

    this.unannounce(req.target, peer.publicKey)
    req.reply(null, { token: false, closerNodes: false })
  }

  _onrefresh(token, req) {
    sodium.crypto_generichash(TMP, token)
    const activeRefresh = b4a.toString(TMP, 'hex')

    const r = this.refreshes.get(activeRefresh)
    if (!r) return

    const { announceSelf, k, record } = r
    const publicKey = record.subarray(0, 32)

    if (announceSelf) {
      this.dht._router.set(k, {
        relay: req.from,
        record,
        onconnect: null,
        onholepunch: null
      })
      this.records.remove(k, publicKey)
    } else {
      this.records.add(k, publicKey, record)
    }

    this.refreshes.delete(activeRefresh)
    this.refreshes.set(b4a.toString(token, 'hex'), r)

    req.reply(null, { token: false, closerNodes: false })
  }

  onannounce(req) {
    if (!req.target || !req.token || !this.dht.id) return

    const ann = decode(m.announce, req.value)
    if (ann === null) return

    const { peer, refresh, signature, bump } = ann

    if (!peer) {
      if (!refresh) return
      this._onrefresh(refresh, req)
      return
    }

    const signable = annSignable(req.target, req.token, this.dht.id, ann, NS.ANNOUNCE)

    if (!signature || !sodium.crypto_sign_verify_detached(signature, signable, peer.publicKey)) {
      return
    }

    // TODO: it would be potentially be more optimal to allow more than 3 addresses here for a findPeer response
    // and only use max 3 for a lookup reply
    if (peer.relayAddresses.length > 3) {
      peer.relayAddresses = peer.relayAddresses.slice(0, 3)
    }

    sodium.crypto_generichash(TMP, peer.publicKey)

    const k = b4a.toString(req.target, 'hex')
    const announceSelf = b4a.equals(TMP, req.target)
    const record = encodeUnslab(m.peer, peer)

    if (announceSelf) {
      this.dht._router.set(k, {
        relay: req.from,
        record,
        onconnect: null,
        onholepunch: null
      })
      this.records.remove(k, peer.publicKey)
    } else {
      const currentBump = this.bumps.get(k) || 0
      if (bump > currentBump && bump <= Date.now() + MAX_BUMP_DRIFT) this.bumps.set(k, bump)
      this.records.add(k, peer.publicKey, record)
    }

    if (refresh) {
      this.refreshes.set(b4a.toString(refresh, 'hex'), { k, record, announceSelf })
    }

    req.reply(null, { token: false, closerNodes: false })
  }

  onmutableget(req) {
    if (!req.target || !req.value) return

    let seq = 0
    try {
      seq = c.decode(c.uint, req.value)
    } catch {
      return
    }

    const k = b4a.toString(req.target, 'hex')
    const value = this.mutables.get(k)

    if (!value) {
      req.reply(null)
      return
    }

    const localSeq = c.decode(c.uint, value)
    req.reply(localSeq < seq ? null : value)
  }

  onmutableput(req) {
    if (!req.target || !req.token || !req.value) return

    const p = decode(m.mutablePutRequest, req.value)
    if (!p) return

    const { publicKey, seq, value, signature } = p

    const hash = b4a.allocUnsafe(32)
    sodium.crypto_generichash(hash, publicKey)
    if (!b4a.equals(hash, req.target)) return

    if (!value || !verifyMutable(signature, seq, value, publicKey)) return

    const k = b4a.toString(hash, 'hex')
    const local = this.mutables.get(k)

    if (local) {
      const existing = c.decode(m.mutableGetResponse, local)
      if (existing.value && existing.seq === seq && b4a.compare(value, existing.value) !== 0) {
        req.error(ERROR.SEQ_REUSED)
        return
      }
      if (seq < existing.seq) {
        req.error(ERROR.SEQ_TOO_LOW)
        return
      }
    }

    this.mutables.set(k, encodeUnslab(m.mutableGetResponse, { seq, value, signature }))
    req.reply(null)
  }

  onimmutableget(req) {
    if (!req.target) return

    const k = b4a.toString(req.target, 'hex')
    const value = this.immutables.get(k)

    req.reply(value || null)
  }

  onimmutableput(req) {
    if (!req.target || !req.token || !req.value) return

    const hash = b4a.alloc(32)
    sodium.crypto_generichash(hash, req.value)
    if (!b4a.equals(hash, req.target)) return

    const k = b4a.toString(hash, 'hex')
    this.immutables.set(k, unslab(req.value))

    req.reply(null)
  }

  onplugin(req) {
    if (!req.value) return

    const plugreq = decode(m.pluginRequest, req.value)
    if (plugreq === null) return

    const p = this.dht.plugins.get(plugreq.plugin)
    if (!p || p.version !== plugreq.version) return

    p.onrequest(plugreq, req)
  }

  destroy() {
    this.records.destroy()
    this.refreshes.destroy()
    this.mutables.destroy()
    this.immutables.destroy()
  }

  static signMutable(seq, value, keyPair) {
    const signable = b4a.allocUnsafe(32 + 32)
    const hash = signable.subarray(32)

    signable.set(NS.MUTABLE_PUT, 0)

    sodium.crypto_generichash(hash, c.encode(m.mutableSignable, { seq, value }))
    return sign(signable, keyPair)
  }

  static verifyMutable(signature, seq, value, publicKey) {
    return verifyMutable(signature, seq, value, publicKey)
  }

  static signAnnounce(target, token, id, ann, keyPair) {
    return sign(annSignable(target, token, id, ann, NS.ANNOUNCE), keyPair)
  }

  static signUnannounce(target, token, id, ann, keyPair) {
    return sign(annSignable(target, token, id, ann, NS.UNANNOUNCE), keyPair)
  }
}

function verifyMutable(signature, seq, value, publicKey) {
  const signable = b4a.allocUnsafe(32 + 32)
  const hash = signable.subarray(32)

  signable.set(NS.MUTABLE_PUT, 0)

  sodium.crypto_generichash(hash, c.encode(m.mutableSignable, { seq, value }))
  return sodium.crypto_sign_verify_detached(signature, signable, publicKey)
}

function annSignable(target, token, id, ann, ns) {
  const signable = b4a.allocUnsafe(32 + 32)
  const hash = signable.subarray(32)

  signable.set(ns, 0)

  sodium.crypto_generichash_batch(hash, [
    target,
    id,
    token,
    c.encode(m.peer, ann.peer), // note that this is the partial encoding of the announce message so we could just use that for perf
    ann.refresh || EMPTY
  ])

  return signable
}

function sign(signable, keyPair) {
  if (keyPair.sign) {
    return keyPair.sign(signable)
  }
  const secretKey = keyPair.secretKey ? keyPair.secretKey : keyPair
  const signature = b4a.allocUnsafe(64)
  sodium.crypto_sign_detached(signature, signable, secretKey)
  return signature
}

function decode(enc, val) {
  try {
    return val && c.decode(enc, val)
  } catch (err) {
    return null
  }
}
module.exports = class RawStreamSet {
  constructor(dht) {
    this._dht = dht

    this._prefix = 16 - 1 // 16 is the default stream-set side in udx
    this._streams = new Map()
  }

  get size() {
    return this._streams.size
  }

  [Symbol.iterator]() {
    return this._streams.values()
  }

  add(opts) {
    const self = this

    // TODO: we should prob have a udx helper for id generation, given the slight complexity
    // of the below. requires a PRNG in udx tho.

    let id = 0

    while (true) {
      id = (Math.random() * 0x100000000) >>> 0

      if (this._streams.has(id & this._prefix)) continue
      break
    }

    // always have ~50% change of rolling a free one
    if (2 * this._streams.size >= this._prefix) {
      // ie 0b11111 = 0b1111 + 1 + 0b1111
      this._prefix = 2 * this._prefix + 1

      // move the prefixes over
      const next = new Map()
      for (const stream of this._streams.values()) {
        next.set(stream.id & this._prefix, stream)
      }
      this._streams = next
    }

    const stream = this._dht.udx.createStream(id, opts)
    this._streams.set(id & this._prefix, stream)

    stream.on('close', onclose)

    return stream

    function onclose() {
      self._streams.delete(id & self._prefix)
    }
  }

  async clear() {
    const destroying = []

    for (const stream of this._streams.values()) {
      destroying.push(new Promise((resolve) => stream.once('close', resolve).destroy()))
    }

    await Promise.allSettled(destroying)
  }
}
const b4a = require('b4a')
const safetyCatch = require('safety-catch')

exports.clearRelayTimeout = clearRelayTimeout
exports.closeRelayConnection = closeRelayConnection
exports.confirmDirectUpgrade = confirmDirectUpgrade
exports.destroyRelayConnection = destroyRelayConnection

function clearRelayTimeout(connectionOrHandshake) {
  clearTimeout(connectionOrHandshake.relayTimeout)
  connectionOrHandshake.relayTimeout = null
}

function closeRelayConnection(connectionOrHandshake) {
  const socket = resetRelayConnection(connectionOrHandshake)

  if (socket) socket.end()
}

function confirmDirectUpgrade(connectionOrHandshake, rawStream, remoteChanging) {
  const cleanup = () => {
    rawStream.off('data', ondirect)
    rawStream.off('message', ondirect)
    rawStream.off('close', cleanup)
  }

  function ondirect() {
    if (!connectionOrHandshake.validUpgrade) {
      // reset, aka assume from direct
      connectionOrHandshake.validUpgrade = true
      return
    }

    cleanup()
    closeRelayConnection(connectionOrHandshake)
  }

  const confirm = () => {
    rawStream.on('data', ondirect)
    rawStream.on('message', ondirect)
    rawStream.once('close', cleanup)

    // Use a raw UDX message to make the peer observe the direct path without
    // writing application data into the secret stream.
    if (rawStream.trySend) rawStream.trySend(b4a.alloc(0))
  }

  if (!remoteChanging) {
    confirm()
    return
  }

  remoteChanging.then(confirm).catch(safetyCatch)
}

function destroyRelayConnection(connectionOrHandshake) {
  const socket = resetRelayConnection(connectionOrHandshake)

  if (socket) socket.destroy()
}

function resetRelayConnection(connectionOrHandshake) {
  if (connectionOrHandshake.relayTimeout) clearRelayTimeout(connectionOrHandshake)

  const socket = connectionOrHandshake.relaySocket
  // Drop relay references so the app connection no longer keeps relay state alive.
  connectionOrHandshake.relayToken = null
  connectionOrHandshake.relaySocket = null
  connectionOrHandshake.relayClient = null

  return socket
}
const c = require('compact-encoding')
const Cache = require('xache')
const safetyCatch = require('safety-catch')
const b4a = require('b4a')
const { handshake, holepunch } = require('./messages')
const { COMMANDS } = require('./constants')
const { BAD_HANDSHAKE_REPLY, BAD_HOLEPUNCH_REPLY } = require('./errors')

const FROM_CLIENT = 0
const FROM_SERVER = 1
const FROM_RELAY = 2
const FROM_SECOND_RELAY = 3
const REPLY = 4

// TODO: While the current design is very trustless in regards to clients/servers trusting the DHT,
// we should add a bunch of rate limits everywhere, especially including here to avoid bad users
// using a DHT node to relay traffic indiscriminately using the connect/holepunch messages.
// That's mostly from an abuse POV as none of the messsages do amplication.

module.exports = class Router {
  constructor(dht, opts) {
    this.dht = dht
    this.forwards = new Cache(opts.forwards)
  }

  set(target, state) {
    if (state.onpeerhandshake) {
      this.forwards.retain(toString(target), state)
    } else {
      this.forwards.set(toString(target), state)
    }
  }

  get(target) {
    return this.forwards.get(toString(target))
  }

  delete(target) {
    this.forwards.delete(toString(target))
  }

  destroy() {
    this.forwards.destroy()
  }

  async peerHandshake(target, { noise, peerAddress, relayAddress, socket, session }, to) {
    const dht = this.dht

    const requestValue = c.encode(handshake, {
      mode: FROM_CLIENT,
      noise,
      peerAddress,
      relayAddress
    })

    const res = await dht.request(
      { command: COMMANDS.PEER_HANDSHAKE, target, value: requestValue },
      to,
      { socket, session }
    )

    const hs = decode(handshake, res.value)
    if (
      !hs ||
      hs.mode !== REPLY ||
      to.host !== res.from.host ||
      to.port !== res.from.port ||
      !hs.noise
    ) {
      throw BAD_HANDSHAKE_REPLY()
    }

    return {
      noise: hs.noise,
      relayed: !!hs.peerAddress,
      serverAddress: hs.peerAddress || to,
      clientAddress: res.to
    }
  }

  async onpeerhandshake(req) {
    const hs = req.value && decode(handshake, req.value)
    if (!hs) return

    const { mode, noise, peerAddress, relayAddress } = hs

    const state = req.target && this.get(req.target)
    const isServer = !!(state && state.onpeerhandshake)
    const relay = state && state.relay

    if (isServer) {
      let reply = null
      try {
        reply = noise && (await state.onpeerhandshake({ noise, peerAddress }, req))
      } catch (e) {
        safetyCatch(e)
        return
      }
      if (!reply || !reply.noise) return
      const opts = { socket: reply.socket, closerNodes: false, token: false }

      switch (mode) {
        case FROM_CLIENT: {
          req.reply(
            c.encode(handshake, { mode: REPLY, noise: reply.noise, peerAddress: null }),
            opts
          )
          return
        }
        case FROM_RELAY: {
          req.relay(
            c.encode(handshake, { mode: FROM_SERVER, noise: reply.noise, peerAddress }),
            req.from,
            opts
          )
          return
        }
        case FROM_SECOND_RELAY: {
          if (!relayAddress) return
          req.relay(
            c.encode(handshake, { mode: FROM_SERVER, noise: reply.noise, peerAddress }),
            relayAddress,
            opts
          )
          return // eslint-disable-line
        }
      }
    } else {
      switch (mode) {
        case FROM_CLIENT: {
          // TODO: if no relay is known route closer to the target instead of timing out
          if (!noise) return
          if (!relay && !relayAddress) {
            // help the user route
            req.reply(null, { token: false, closerNodes: true })
            return
          }
          req.relay(
            c.encode(handshake, {
              mode: FROM_RELAY,
              noise,
              peerAddress: req.from,
              relayAddress: null
            }),
            relayAddress || relay
          )
          return
        }
        case FROM_RELAY: {
          if (!relay || !noise) return
          req.relay(
            c.encode(handshake, {
              mode: FROM_SECOND_RELAY,
              noise,
              peerAddress,
              relayAddress: req.from
            }),
            relay
          )
          return
        }
        case FROM_SERVER: {
          if (!peerAddress || !noise) return
          req.reply(
            c.encode(handshake, { mode: REPLY, noise, peerAddress: req.from, relayAddress: null }),
            { to: peerAddress, closerNodes: false, token: false }
          )
          return // eslint-disable-line
        }
      }
    }
  }

  async peerHolepunch(target, { id, payload, peerAddress, socket, session }, to) {
    const dht = this.dht
    const requestValue = c.encode(holepunch, {
      mode: FROM_CLIENT,
      id,
      payload,
      peerAddress
    })

    const res = await dht.request(
      { command: COMMANDS.PEER_HOLEPUNCH, target, value: requestValue },
      to,
      { socket, session }
    )

    const hp = decode(holepunch, res.value)
    if (!hp || hp.mode !== REPLY || to.host !== res.from.host || to.port !== res.from.port) {
      throw BAD_HOLEPUNCH_REPLY()
    }

    return {
      from: res.from,
      to: res.to,
      payload: hp.payload,
      peerAddress: hp.peerAddress || to
    }
  }

  async onpeerholepunch(req) {
    const hp = req.value && decode(holepunch, req.value)
    if (!hp) return

    const { mode, id, payload, peerAddress } = hp

    const state = req.target && this.get(req.target)
    const isServer = !!(state && state.onpeerholepunch)
    const relay = state && state.relay

    switch (mode) {
      case FROM_CLIENT: {
        if (!peerAddress && !relay) return
        req.relay(
          c.encode(holepunch, { mode: FROM_RELAY, id, payload, peerAddress: req.from }),
          peerAddress || relay
        )
        return
      }
      case FROM_RELAY: {
        if (!isServer || !peerAddress) return
        let reply = null
        try {
          reply = await state.onpeerholepunch({ id, payload, peerAddress }, req)
        } catch (e) {
          safetyCatch(e)
          return
        }
        if (!reply) return
        const opts = { socket: reply.socket, closerNodes: false, token: false }
        req.relay(
          c.encode(holepunch, { mode: FROM_SERVER, id: 0, payload: reply.payload, peerAddress }),
          req.from,
          opts
        )
        return
      }
      case FROM_SERVER: {
        req.reply(c.encode(holepunch, { mode: REPLY, id, payload, peerAddress: req.from }), {
          to: peerAddress,
          closerNodes: false,
          token: false
        })
        return // eslint-disable-line
      }
    }
  }
}

function decode(enc, val) {
  try {
    return c.decode(enc, val)
  } catch {
    return null
  }
}

function toString(t) {
  return typeof t === 'string' ? t : b4a.toString(t, 'hex')
}
const sodium = require('sodium-universal')
const b4a = require('b4a')
const { holepunchPayload } = require('./messages')

module.exports = class HolepunchPayload {
  constructor(holepunchSecret) {
    this._sharedSecret = holepunchSecret
    this._localSecret = b4a.allocUnsafe(32)

    sodium.randombytes_buf(this._localSecret)
  }

  decrypt(buffer) {
    const state = { start: 24, end: buffer.byteLength - 16, buffer }

    if (state.end <= state.start) return null

    const nonce = buffer.subarray(0, 24)
    const msg = state.buffer.subarray(state.start, state.end)
    const cipher = state.buffer.subarray(state.start)

    if (!sodium.crypto_secretbox_open_easy(msg, cipher, nonce, this._sharedSecret)) return null

    try {
      return holepunchPayload.decode(state)
    } catch {
      return null
    }
  }

  encrypt(payload) {
    const state = { start: 24, end: 24, buffer: null }
    holepunchPayload.preencode(state, payload)
    state.buffer = b4a.allocUnsafe(state.end + 16)

    const nonce = state.buffer.subarray(0, 24)
    const msg = state.buffer.subarray(state.start, state.end)
    const cipher = state.buffer.subarray(state.start)

    holepunchPayload.encode(state, payload)
    sodium.randombytes_buf(nonce)
    sodium.crypto_secretbox_easy(cipher, msg, nonce, this._sharedSecret)

    return state.buffer
  }

  token(addr) {
    const out = b4a.allocUnsafe(32)
    sodium.crypto_generichash(out, b4a.from(addr.host), this._localSecret)
    return out
  }
}
const DONE = Promise.resolve(true)
const DESTROYED = Promise.resolve(false)

module.exports = class Semaphore {
  constructor(limit = 1) {
    this.limit = limit
    this.active = 0
    this.waiting = []

    this.flushedPromise = null
    this.flushedResolve = null

    this.destroyed = false

    this._onwait = this._queueWaiting.bind(this)
    this._onflush = this._queueFlushed.bind(this)
  }

  _queueWaiting(resolve) {
    this.waiting.push(resolve)
  }

  _queueFlushed(resolve) {
    this.flushedResolve = resolve
  }

  wait() {
    if (this.destroyed === true) return DESTROYED

    if (this.active < this.limit && this.waiting.length === 0) {
      this.active++
      return DONE
    }

    return new Promise(this._onwait)
  }

  signal() {
    if (this.destroyed === true) return

    this.active--
    while (this.active < this.limit && this.waiting.length > 0 && this.destroyed === false) {
      this.active++
      this.waiting.shift()(true)
    }

    if (this.active === 0 && this.flushedResolve) {
      const resolve = this.flushedResolve
      this.flushedResolve = null
      this.flushedPromise = null
      resolve(true)
    }
  }

  async flush() {
    if (this.destroyed === true) return
    if (this.active === 0) return
    if (this.flushedPromise) return this.flushedPromise
    this.flushedPromise = new Promise(this._onflush)
    return this.flushedPromise
  }

  destroy() {
    this.destroyed = true
    this.active = 0
    while (this.waiting.length) this.waiting.pop()(false)
    if (this.flushedResolve) this.flushedResolve(false)
  }
}
const { EventEmitter } = require('events')
const safetyCatch = require('safety-catch')
const NoiseSecretStream = require('@hyperswarm/secret-stream')
const b4a = require('b4a')
const relay = require('blind-relay')
const NoiseWrap = require('./noise-wrap')
const Announcer = require('./announcer')
const { FIREWALL, ERROR } = require('./constants')
const { unslabbedHash } = require('./crypto')
const SecurePayload = require('./secure-payload')
const Holepuncher = require('./holepuncher')
const {
  clearRelayTimeout,
  confirmDirectUpgrade,
  destroyRelayConnection
} = require('./relay-connection')
const { isPrivate } = require('bogon')
const { ALREADY_LISTENING, NODE_DESTROYED, KEYPAIR_ALREADY_USED } = require('./errors')

const HANDSHAKE_CLEAR_WAIT = 10000
const HANDSHAKE_INITIAL_TIMEOUT = 10000

module.exports = class Server extends EventEmitter {
  constructor(dht, opts = {}) {
    super()

    this.dht = dht
    this.target = null

    this.closed = false
    this.firewall = opts.firewall || (() => false)
    this.holepunch = opts.holepunch || (() => true)
    this.relayThrough = opts.relayThrough || null
    this.relayKeepAlive = opts.relayKeepAlive || 5000
    this.pool = opts.pool || null
    this.createHandshake = opts.createHandshake || defaultCreateHandshake
    this.createSecretStream = opts.createSecretStream || defaultCreateSecretStream
    this.suspended = false
    this.handshakeClearWait = opts.handshakeClearWait || HANDSHAKE_CLEAR_WAIT

    this._shareLocalAddress = opts.shareLocalAddress !== false
    this._reusableSocket = !!opts.reusableSocket
    this._neverPunch = opts.holepunch === false // useful for fully disabling punching
    this._keyPair = null
    this._announcer = null
    this._connects = new Map()
    this._holepunches = []
    this._listening = null
    this._closing = null
  }

  get listening() {
    return this._listening !== null
  }

  get publicKey() {
    return this._keyPair && this._keyPair.publicKey
  }

  get relayAddresses() {
    return this._announcer ? this._announcer.relayAddresses : []
  }

  onconnection(encryptedSocket) {
    this.emit('connection', encryptedSocket)
  }

  async suspend({ log = noop } = {}) {
    log('Suspending hyperdht server')
    if (this._listening !== null) await this._listening
    log('Suspending hyperdht server (post listening)')
    this.suspended = true
    this._clearAll()
    return this._announcer ? this._announcer.suspend({ log }) : Promise.resolve()
  }

  async resume() {
    if (this._listening !== null) await this._listening
    this.suspended = false
    return this._announcer ? this._announcer.resume() : Promise.resolve()
  }

  address() {
    if (!this._keyPair) return null

    return {
      publicKey: this._keyPair.publicKey,
      host: this.dht.host,
      port: this.dht.port
    }
  }

  close() {
    if (this._closing) return this._closing
    this._closing = this._close()
    return this._closing
  }

  _gc() {
    this.dht.listening.delete(this)
    if (this.target) this.dht._router.delete(this.target)
  }

  async _stopListening() {
    try {
      if (this._announcer) await this._announcer.stop()
    } catch {
      // ignore
    }

    this._announcer = null
    this._listening = null
    this._keyPair = null
  }

  async _close() {
    if (this._listening === null) {
      this.closed = true
      this.emit('close')
      return
    }

    try {
      await this._listening
    } catch (err) {
      safetyCatch(err)
    }

    this._gc()
    this._clearAll()

    await this._stopListening()

    this.closed = true
    this.emit('close')
  }

  _clearAll() {
    while (this._holepunches.length > 0) {
      const h = this._holepunches.pop()
      if (h && h.puncher) h.puncher.destroy()
      if (h && h.clearing) clearTimeout(h.clearing)
      if (h && h.prepunching) clearTimeout(h.prepunching)
      if (h && h.rawStream) h.rawStream.destroy()
    }

    this._connects.clear()
  }

  async listen(keyPair = this.dht.defaultKeyPair, opts = {}) {
    if (this._listening !== null) throw ALREADY_LISTENING()
    if (this.dht.destroyed) throw NODE_DESTROYED()

    this._listening = this._listen(keyPair, opts)
    await this._listening
    return this
  }

  async _listen(keyPair, opts) {
    // From now on, the DHT object which created me is responsible for closing me
    this.dht.listening.add(this)

    try {
      await this.dht.bind()
      if (this._closing) return

      for (const s of this.dht.listening) {
        if (s._keyPair && b4a.equals(s._keyPair.publicKey, keyPair.publicKey)) {
          throw KEYPAIR_ALREADY_USED()
        }
      }

      this.target = unslabbedHash(keyPair.publicKey)
      this._keyPair = keyPair
      this._announcer = new Announcer(this.dht, keyPair, this.target, opts)

      this.dht._router.set(this.target, {
        relay: null,
        record: this._announcer.record,
        onpeerhandshake: this._onpeerhandshake.bind(this),
        onpeerholepunch: this._onpeerholepunch.bind(this)
      })

      // warm it up for now
      this._localAddresses().catch(safetyCatch)

      await this._announcer.start()
    } catch (err) {
      await this._stopListening()
      this._gc()
      throw err
    }

    if (this._closing) return
    if (this.suspended) await this._announcer.suspend()

    if (this._closing) return
    if (this.dht.destroyed) throw NODE_DESTROYED()

    if (this.pool) this.pool._attachServer(this)

    this.emit('listening')
  }

  refresh() {
    if (this._announcer && !this.suspended) this._announcer.refresh()
  }

  notifyOnline() {
    if (this._announcer) this._announcer.online.notify()
  }

  _localAddresses() {
    return this.dht.validateLocalAddresses(Holepuncher.localAddresses(this.dht.io.serverSocket))
  }

  async _addHandshake(k, noise, clientAddress, { to: serverAddress, socket }, direct) {
    let id = this._holepunches.indexOf(null)
    if (id === -1) id = this._holepunches.push(null) - 1

    const hs = {
      round: 0,
      reply: null,
      puncher: null,
      payload: null,
      rawStream: null,
      encryptedSocket: null,
      prepunching: null,
      firewalled: true,
      clearing: null,
      replyFromPuncher: false,
      onsocket: null,
      aborted: false,

      // Relay state
      relayTimeout: null,
      relayToken: null,
      relaySocket: null,
      relayClient: null,
      relayPaired: false,
      validUpgrade: true
    }

    this._holepunches[id] = hs

    const handshake = this.createHandshake(this._keyPair, null)

    let remotePayload
    try {
      remotePayload = await handshake.recv(noise)
    } catch (err) {
      safetyCatch(err)
      this._clearLater(hs, id, k)
      return null
    }

    if (this._closing || this.suspended) {
      this._clearLater(hs, id, k)
      return null
    }

    try {
      hs.firewalled = await this.firewall(handshake.remotePublicKey, remotePayload, clientAddress)
    } catch (err) {
      safetyCatch(err)
    }

    if (this._closing || this.suspended || hs.firewalled) {
      this._clearLater(hs, id, k)
      return null
    }

    const error =
      remotePayload.version === 1
        ? remotePayload.udx
          ? ERROR.NONE
          : ERROR.ABORTED
        : ERROR.VERSION_MISMATCH

    const addresses = []
    const ourRemoteAddr = this.dht.remoteAddress()
    const ourLocalAddrs = this._shareLocalAddress ? await this._localAddresses() : null

    if (this._closing || this.suspended) {
      this._clearLater(hs, id, k)
      return null
    }

    if (ourRemoteAddr) addresses.push(ourRemoteAddr)
    if (ourLocalAddrs) addresses.push(...ourLocalAddrs)

    if (error === ERROR.NONE) {
      hs.rawStream = this.dht.createRawStream({
        framed: true,
        firewall(socket, port, host) {
          if (!(port > 0 && port < 65536)) return true

          // Check if the traffic originated from the socket on which we're expecting relay traffic. If so,
          // we haven't hole punched yet and the other side is just sending us traffic through the relay.
          if (hs.relaySocket && isRelay(hs.relaySocket, socket, port, host)) {
            hs.validUpgrade = false
            return false
          }

          hs.onsocket(socket, port, host)
          hs.validUpgrade = true
          return false
        }
      })

      hs.rawStream.on('error', autoDestroy)

      // Handles the case where onsocket is never called, but the stream got setup
      // This can happen on a relayed connection which never connects directly
      // (onsocket is called there only when the direct connection is established)
      const onrawstreamclose = () => {
        if (this._closing) return
        if (hs.puncher) {
          hs.puncher.onabort = noop
          hs.puncher.destroy()
        }
        this._clearLater(hs, id, k)
      }
      hs.rawStream.on('close', onrawstreamclose)

      hs.onsocket = (socket, port, host) => {
        if (hs.rawStream === null) return // Already hole punched

        this._clearLater(hs, id, k)

        if (hs.prepunching) {
          clearTimeout(hs.prepunching)
          hs.prepunching = null
        }

        if (this._reusableSocket && remotePayload.udx.reusableSocket) {
          this.dht._socketPool.routes.add(handshake.remotePublicKey, hs.rawStream)
        }

        hs.rawStream.removeListener('error', autoDestroy)
        hs.rawStream.removeListener('close', onrawstreamclose)

        if (hs.rawStream.connected) {
          const rawStream = hs.rawStream
          const remoteChanging = rawStream.changeRemote(socket, remotePayload.udx.id, port, host)
          confirmDirectUpgrade(hs, rawStream, remoteChanging)
        } else {
          hs.rawStream.connect(socket, remotePayload.udx.id, port, host)
          hs.encryptedSocket = this.createSecretStream(false, hs.rawStream, {
            handshake: h,
            keepAlive: this.dht.connectionKeepAlive
          })

          this.onconnection(hs.encryptedSocket)
        }

        if (hs.puncher) {
          hs.puncher.onabort = noop
          hs.puncher.destroy()
        }

        hs.rawStream = null
      }

      function autoDestroy() {
        if (hs.puncher) hs.puncher.destroy()
      }
    }

    const relayAddresses = this.relayAddresses
    const relayThrough = selectRelay(this.relayThrough)

    if (relayThrough) hs.relayToken = relay.token()

    try {
      hs.reply = await handshake.send({
        error,
        firewall: ourRemoteAddr ? FIREWALL.OPEN : FIREWALL.UNKNOWN,
        holepunch: ourRemoteAddr ? null : { id, relays: this._announcer.relays },
        addresses4: addresses,
        addresses6: null,
        udx: {
          reusableSocket: this._reusableSocket,
          id: hs.rawStream ? hs.rawStream.id : 0,
          seq: 0
        },
        secretStream: {},
        relayThrough: relayThrough ? { publicKey: relayThrough, token: hs.relayToken } : null,
        relayAddresses: relayAddresses.length ? relayAddresses : null
      })
    } catch (err) {
      safetyCatch(err)
      if (hs.rawStream) hs.rawStream.destroy()
      this._clearLater(hs, id, k)
      return null
    }

    if (this._closing || this.suspended) {
      if (hs.rawStream) hs.rawStream.destroy()
      this._clearLater(hs, id, k)
      return null
    }

    const h = handshake.final()

    if (error !== ERROR.NONE) {
      if (hs.rawStream) hs.rawStream.destroy()
      this._clearLater(hs, id, k)
      return hs
    }

    if (remotePayload.firewall === FIREWALL.OPEN || direct) {
      const sock = direct ? socket : this.dht.socket
      this.dht.stats.punches.open++
      hs.onsocket(sock, clientAddress.port, clientAddress.host)
      return hs
    }

    if (relayThrough || remotePayload.relayThrough) {
      this._relayConnection(hs, relayThrough, remotePayload, h)
    }

    const onabort = () => {
      hs.aborted = true
      if (hs.prepunching) clearTimeout(hs.prepunching)
      hs.prepunching = null
      if (hs.puncher) {
        hs.puncher.onabort = noop
        hs.puncher.destroy()
      }
      if (hs.rawStream.destroyed) {
        this._clearLater(hs, id, k)
        return
      }

      hs.rawStream.on('close', () => this._clearLater(hs, id, k))
      if (hs.relayToken === null) hs.rawStream.destroy()
    }

    if (!direct && clientAddress.host === serverAddress.host) {
      const clientAddresses = remotePayload.addresses4.filter((addr) => isPrivate(addr.host))

      if (clientAddresses.length > 0 && this._shareLocalAddress) {
        const myAddresses = await this._localAddresses()
        const addr = Holepuncher.matchAddress(myAddresses, clientAddresses)

        if (addr) {
          hs.payload = new SecurePayload(h.holepunchSecret)
          setupHolepuncher(this.dht, hs, remotePayload.firewall, onabort)
          hs.prepunching = setTimeout(onabort, HANDSHAKE_INITIAL_TIMEOUT)
          return hs
        }
      }
    }

    if (this._closing || this.suspended) {
      this._clearLater(hs, id, k)
      return null
    }

    if (ourRemoteAddr || this._neverPunch) {
      hs.prepunching = setTimeout(onabort, HANDSHAKE_INITIAL_TIMEOUT)
      return hs
    }

    hs.payload = new SecurePayload(h.holepunchSecret)
    setupHolepuncher(this.dht, hs, remotePayload.firewall, onabort)
    hs.replyFromPuncher = true
    hs.prepunching = setTimeout(hs.puncher.destroy.bind(hs.puncher), HANDSHAKE_INITIAL_TIMEOUT)

    return hs
  }

  _clearLater(hs, id, k) {
    if (hs.clearing) return
    hs.clearing = setTimeout(() => this._clear(hs, id, k), this.handshakeClearWait)
    hs.clearing.unref()
  }

  _clear(hs, id, k) {
    if (id >= this._holepunches.length || this._holepunches[id] !== hs) return
    if (hs.clearing) clearTimeout(hs.clearing)

    this._holepunches[id] = null
    while (
      this._holepunches.length > 0 &&
      this._holepunches[this._holepunches.length - 1] === null
    ) {
      this._holepunches.pop()
    }
    this._connects.delete(k)
  }

  async _onpeerhandshake({ noise, peerAddress }, req) {
    const k = b4a.toString(noise, 'hex')

    // The next couple of statements MUST run within the same tick to prevent
    // a malicious peer from flooding us with handshakes.
    let p = this._connects.get(k)
    if (!p) {
      p = this._addHandshake(k, noise, peerAddress || req.from, req, !peerAddress)
      this._connects.set(k, p)
    }

    const h = await p
    if (!h) return null

    if (this._closing !== null || this.suspended) return null

    // Routed handshake replies advertise the socket they are sent from as the
    // server address. Same-LAN prepunch also creates a puncher, but it must
    // keep advertising the normal server socket so the LAN shortcut can open.
    return { socket: h.replyFromPuncher ? h.puncher.socket : null, noise: h.reply }
  }

  async _onpeerholepunch({ id, peerAddress, payload }, req) {
    const h = id < this._holepunches.length ? this._holepunches[id] : null
    if (!h) return null

    if (!peerAddress || this._closing !== null || this.suspended) return null
    if (!h.payload) return this._abort(h) // not opened

    const remotePayload = h.payload.decrypt(payload)
    if (!remotePayload) return null

    const p = h.puncher
    if (!p || !p.socket) return this._abort(h) // not opened

    const isServerRelay = this._announcer.isRelay(req.from)
    const { error, firewall, round, punching, addresses, remoteAddress, remoteToken } =
      remotePayload

    if (error !== ERROR.NONE) {
      // We actually do not need to set the round here, but just do it for consistency.
      if (round >= h.round) h.round = round
      return this._abort(h)
    }

    const token = h.payload.token(peerAddress)
    const echoed = isServerRelay && !!remoteToken && b4a.equals(token, remoteToken)

    // Update our heuristics here
    if (req.socket === p.socket) {
      p.nat.add(req.to, req.from)
    }

    if (round >= h.round) {
      h.round = round
      p.updateRemote({ punching, firewall, addresses, verified: echoed ? peerAddress.host : null })
    }

    // Wait for the analyzer to reach a conclusion...
    let stable = await p.analyze(false)
    if (p.destroyed) return null

    if (!p.remoteHolepunching && !stable) {
      stable = await p.analyze(true)
      if (p.destroyed) return null
      if (!stable) return this._abort(h)
    }

    // Fast mode! If we are consistent and the remote has opened a session to us (remoteAddress)
    // then fire a quick punch back. Note the await here just waits for the udp socket to flush.
    if (
      isConsistent(p.nat.firewall) &&
      remoteAddress &&
      hasSameAddr(p.nat.addresses, remoteAddress)
    ) {
      await p.ping(peerAddress)
      if (p.destroyed) return null
    }

    // Remote said they are punching (or willing to), so we will punch as well.
    // Note that this returns when the punching has STARTED, so no guarantee
    // we will have a connection after this promise etc.
    if (p.remoteHolepunching) {
      // TODO: still continue here if a local connection might work, but then do not holepunch...
      if (!this.holepunch(p.remoteFirewall, p.nat.firewall, p.remoteAddresses, p.nat.addresses)) {
        return p.destroyed ? null : this._abort(h)
      }

      if (h.prepunching) {
        clearTimeout(h.prepunching)
        h.prepunching = null
      }

      if (p.remoteFirewall >= FIREWALL.RANDOM || p.nat.firewall >= FIREWALL.RANDOM) {
        if (
          this.dht._randomPunches >= this.dht._randomPunchLimit ||
          Date.now() - this.dht._lastRandomPunch < this.dht._randomPunchInterval
        ) {
          this.dht.stats.punches.tryLater++
          if (!h.relayToken) return this._abort(h, ERROR.TRY_LATER)
          if (!h.tryLaterCounted) {
            h.tryLaterCounted = true
            this.dht.stats.punches.tryLaterRelayedHandshakesAtLeastOnce++
          }
          return {
            socket: p.socket,
            payload: h.payload.encrypt({
              error: ERROR.TRY_LATER,
              firewall: p.nat.firewall,
              round: h.round,
              connected: p.connected,
              punching: p.punching,
              addresses: p.nat.addresses,
              remoteAddress: null,
              token: isServerRelay ? token : null,
              remoteToken: remotePayload.token
            })
          }
        }
      }

      const punching = await p.punch()
      if (p.destroyed) return null
      if (!punching) return this._abort(h)
    }

    // Freeze that analysis as soon as we have a result we are giving to the other peer
    if (p.nat.firewall !== FIREWALL.UNKNOWN) {
      p.nat.freeze()
    }

    return {
      socket: p.socket,
      payload: h.payload.encrypt({
        error: ERROR.NONE,
        firewall: p.nat.firewall,
        round: h.round,
        connected: p.connected,
        punching: p.punching,
        addresses: p.nat.addresses,
        remoteAddress: null,
        token: isServerRelay ? token : null,
        remoteToken: remotePayload.token
      })
    }
  }

  _abort(h, error = ERROR.ABORTED) {
    if (!h.payload) {
      if (h.puncher) h.puncher.destroy()
      return null
    }

    const payload = h.payload.encrypt({
      error,
      firewall: FIREWALL.UNKNOWN,
      round: h.round,
      connected: false,
      punching: false,
      addresses: null,
      remoteAddress: null,
      token: null,
      remoteToken: null
    })

    if (h.puncher) h.puncher.destroy()

    return { socket: this.dht.socket, payload }
  }

  _relayConnection(hs, relayThrough, remotePayload, h) {
    this.dht.stats.relaying.attempts++

    let isInitiator
    let publicKey
    let token

    if (relayThrough) {
      isInitiator = true
      publicKey = relayThrough
      token = hs.relayToken
    } else {
      isInitiator = false
      publicKey = remotePayload.relayThrough.publicKey
      token = remotePayload.relayThrough.token
    }

    hs.relayToken = token
    hs.relaySocket = this.dht.connect(publicKey)
    hs.relaySocket.setKeepAlive(this.relayKeepAlive)
    hs.relayClient = relay.Client.from(hs.relaySocket, { id: hs.relaySocket.publicKey })
    hs.relayTimeout = setTimeout(onabort, 15000)

    hs.relayClient
      .pair(isInitiator, token, hs.rawStream)
      .on('error', onabort)
      .on('data', (remoteId) => {
        if (hs.relayTimeout) clearRelayTimeout(hs)
        if (hs.rawStream === null) {
          onabort(null)
          return
        }

        hs.relayPaired = true
        this.dht.stats.relaying.successes++

        if (hs.prepunching) clearTimeout(hs.prepunching)
        hs.prepunching = null

        const { remotePort, remoteHost, socket } = hs.relaySocket.rawStream

        hs.rawStream
          .on('close', () => destroyRelayConnection(hs))
          .connect(socket, remoteId, remotePort, remoteHost)

        hs.encryptedSocket = this.createSecretStream(false, hs.rawStream, {
          handshake: h,
          keepAlive: this.dht.connectionKeepAlive
        })

        this.onconnection(hs.encryptedSocket)
      })

    const dht = this.dht
    function onabort() {
      if (!hs.relayPaired) dht.stats.relaying.aborts++
      destroyRelayConnection(hs)
      if (hs.aborted && hs.rawStream) hs.rawStream.destroy()
    }
  }
}

function isConsistent(fw) {
  return fw === FIREWALL.OPEN || fw === FIREWALL.CONSISTENT
}

function hasSameAddr(addrs, other) {
  if (addrs === null) return false

  for (const addr of addrs) {
    if (addr.port === other.port && addr.host === other.host) return true
  }
  return false
}

function defaultCreateHandshake(keyPair, remotePublicKey) {
  return new NoiseWrap(keyPair, remotePublicKey)
}

function defaultCreateSecretStream(isInitiator, rawStream, opts) {
  return new NoiseSecretStream(isInitiator, rawStream, opts)
}

function setupHolepuncher(dht, hs, remoteFirewall, onabort) {
  if (hs.puncher) return

  hs.puncher = new Holepuncher(dht, dht.session(), false, remoteFirewall)
  hs.puncher.onconnect = hs.onsocket
  hs.puncher.onabort = onabort
}

function isRelay(relaySocket, socket, port, host) {
  const stream = relaySocket.rawStream
  if (!stream) return false
  if (stream.socket !== socket) return false
  return port === stream.remotePort && host === stream.remoteHost
}

function selectRelay(relayThrough) {
  if (typeof relayThrough === 'function') relayThrough = relayThrough()
  if (relayThrough === null) return null
  if (Array.isArray(relayThrough)) {
    return relayThrough[Math.floor(Math.random() * relayThrough.length)]
  }
  return relayThrough
}

function noop() {}
module.exports = class Sleeper {
  constructor() {
    this._timeout = null
    this._resolve = null

    this._start = (resolve) => {
      this._resolve = resolve
    }

    this._trigger = () => {
      if (this._resolve === null) return
      const resolve = this._resolve
      this._timeout = null
      this._resolve = null
      resolve()
    }
  }

  pause(ms) {
    const p = new Promise(this._start)
    if (this._timeout !== null) {
      clearTimeout(this._timeout)
      this._trigger()
    }
    this._timeout = setTimeout(this._trigger, ms)
    return p
  }

  resume() {
    if (this._timeout !== null) {
      clearTimeout(this._timeout)
      this._trigger()
    }
  }
}
const b4a = require('b4a')

const LINGER_TIME = 3000

module.exports = class SocketPool {
  constructor(dht, host) {
    this._dht = dht
    this._sockets = new Map()
    this._lingering = new Set() // updated by the ref
    this._host = host

    this.routes = new SocketRoutes(this)

    this.stats = {
      socketsAdded: 0,
      socketsRemoved: 0
    }
  }

  _onmessage(ref, data, address) {
    this._dht.onmessage(ref.socket, data, address)
  }

  _add(ref) {
    this._sockets.set(ref.socket, ref)
    this.stats.socketsAdded++
  }

  _remove(ref) {
    const removed = this._sockets.delete(ref.socket)
    this._lingering.delete(ref)
    if (removed) this.stats.socketsRemoved++
  }

  lookup(socket) {
    return this._sockets.get(socket) || null
  }

  setReusable(socket, bool) {
    const ref = this.lookup(socket)
    if (ref) ref.reusable = bool
  }

  acquire() {
    // TODO: Enable socket reuse
    return new SocketRef(this)
  }

  async destroy() {
    const closing = []

    for (const ref of this._sockets.values()) {
      ref._unlinger()
      closing.push(ref.socket.close())
    }

    await Promise.allSettled(closing)
  }
}

class SocketRoutes {
  constructor(pool) {
    this._pool = pool
    this._routes = new Map()
  }

  add(publicKey, rawStream) {
    if (rawStream.socket) this._onconnect(publicKey, rawStream)
    else rawStream.on('connect', this._onconnect.bind(this, publicKey, rawStream))
  }

  get(publicKey) {
    const id = b4a.toString(publicKey, 'hex')
    const route = this._routes.get(id)
    if (!route) return null
    return route
  }

  _onconnect(publicKey, rawStream) {
    const id = b4a.toString(publicKey, 'hex')
    const socket = rawStream.socket

    let route = this._routes.get(id)

    if (!route) {
      const gc = () => {
        if (this._routes.get(id) === route) this._routes.delete(id)
        socket.removeListener('close', gc)
      }

      route = {
        socket,
        address: { host: rawStream.remoteHost, port: rawStream.remotePort },
        gc
      }

      this._routes.set(id, route)
      socket.on('close', gc)
    }

    this._pool.setReusable(socket, true)

    rawStream.on('error', () => {
      this._pool.setReusable(socket, false)
      if (!route) route = this._routes.get(id)
      if (route && route.socket === socket) route.gc()
    })
  }
}

// TODO: we should just make some "user data" object on udx to allow to attach this info
class SocketRef {
  constructor(pool) {
    this._pool = pool

    // Events
    this.onholepunchmessage = noop

    // Whether it should teardown immediately or wait a bit
    this.reusable = false

    this.socket = pool._dht.udx.createSocket()
    this.socket
      .on('close', this._onclose.bind(this))
      .on('message', this._onmessage.bind(this))
      .on('idle', this._onidle.bind(this))
      .on('busy', this._onbusy.bind(this))
      .bind(0, this._pool._host)

    this._refs = 1
    this._released = false
    this._closed = false

    this._timeout = null
    this._wasBusy = false

    this._pool._add(this)
  }

  _onclose() {
    this._pool._remove(this)
  }

  _onmessage(data, address) {
    if (data.byteLength > 1) {
      this._pool._onmessage(this, data, address)
    } else {
      this.onholepunchmessage(data, address, this)
    }
  }

  _onidle() {
    this._closeMaybe()
  }

  _onbusy() {
    this._wasBusy = true
    this._unlinger()
  }

  _reset() {
    this.onholepunchmessage = noop
  }

  _closeMaybe() {
    if (this._refs === 0 && this.socket.idle && !this._timeout) this._close()
  }

  _lingeringClose() {
    this._pool._lingering.delete(this)
    this._timeout = null
    this._closeMaybe()
  }

  _close() {
    this._unlinger()

    if (this.reusable && this._wasBusy) {
      this._wasBusy = false
      this._pool._lingering.add(this)
      this._timeout = setTimeout(this._lingeringClose.bind(this), LINGER_TIME)
      return
    }

    this._closed = true
    this.socket.close()
  }

  _unlinger() {
    if (this._timeout !== null) {
      clearTimeout(this._timeout)
      this._pool._lingering.delete(this)
      this._timeout = null
    }
  }

  get free() {
    return this._refs === 0
  }

  active() {
    this._refs++
    this._unlinger()
  }

  inactive() {
    this._refs--
    this._closeMaybe()
  }

  address() {
    return this.socket.address()
  }

  release() {
    if (this._released) return

    this._released = true
    this._reset()

    this._refs--
    this._closeMaybe()
  }
}

function noop() {}
{
  "name": "hyperdht",
  "version": "6.34.0",
  "description": "The DHT powering Hyperswarm",
  "main": "index.js",
  "browser": "browser.js",
  "bin": {
    "hyperdht": "./bin.js"
  },
  "files": [
    "index.js",
    "browser.js",
    "testnet.js",
    "bin.js",
    "lib/**.js"
  ],
  "imports": {
    "events": {
      "bare": "bare-events",
      "default": "events"
    },
    "child_process": {
      "bare": "bare-node-child-process",
      "default": "child_process"
    }
  },
  "dependencies": {
    "@hyperswarm/secret-stream": "^6.6.2",
    "b4a": "^1.3.1",
    "bare-events": "^2.2.0",
    "blind-relay": "^1.3.0",
    "bogon": "^1.0.0",
    "compact-encoding": "^3.0.0",
    "dht-rpc": "^6.15.1",
    "hypercore-crypto": "^3.3.0",
    "hypercore-id-encoding": "^1.2.0",
    "hyperdht-address": "^1.0.1",
    "noise-curve-ed": "^2.0.0",
    "noise-handshake": "^4.0.0",
    "record-cache": "^1.1.1",
    "safety-catch": "^1.0.1",
    "signal-promise": "^1.0.3",
    "sodium-universal": "^5.0.1",
    "streamx": "^2.16.1",
    "unslab": "^1.3.0",
    "xache": "^1.1.0"
  },
  "devDependencies": {
    "bare-node-child-process": "^1.0.1",
    "brittle": "^3.0.0",
    "graceful-goodbye": "^1.3.0",
    "newline-decoder": "^1.0.2",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^2.0.0"
  },
  "scripts": {
    "format": "prettier --write .",
    "test": "prettier --check . && node test/all.js",
    "test:bare": "bare test/all.js",
    "test:generate": "brittle -r test/all.js test/*.js",
    "integration": "brittle test/integration/*.js",
    "end-to-end": "brittle test/end-to-end/*.js"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "directories": {
    "lib": "lib",
    "test": "test"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/hyperdht.git"
  },
  "keywords": [],
  "bugs": {
    "url": "https://github.com/holepunchto/hyperdht/issues"
  },
  "homepage": "https://github.com/holepunchto/hyperdht#readme"
}
/**
 * Crypto Manager - Cryptographic operations for Hyperident
 * Uses bare-crypto for all cryptographic functions
 */

import crypto from 'node:crypto'
import sodium from 'sodium-native'

/**
 * CryptoManager class for encryption, decryption, and key derivation
 */
export class CryptoManager {
  /**
   * Generate a cryptographically secure random salt
   * @returns {Buffer} 32-byte random salt
   */
  static generateSalt() {
    return crypto.randomBytes(32)
  }

  /**
   * Derive encryption key from passphrase and salt
   * @param {string} passphrase - User passphrase
   * @param {Buffer} salt - 32-byte salt
   * @returns {Buffer} 32-byte derived key
   */
  // ── vault secret encryption ────────────────────────────────────────────────────────────────────
  //
  // This replaces a scheme that was sha256(passphrase||salt) + repeating-key XOR, unauthenticated
  // (hyper-social #47). Against a BIP39 mnemonic that was not protection: the plaintext is lowercase
  // words from a PUBLIC 2048-word list, ~150-200 bytes against a 32-byte key, so the keystream repeated
  // five or six times and was recoverable WITHOUT the passphrase. A single SHA-256 made guessing the
  // passphrase cheap regardless.
  //
  // The old deriveKey/encryptData/decryptData are REMOVED rather than deprecated. Leaving callable
  // broken crypto beside the fixed version is how it gets reused.
  //
  // Parameters are returned rather than hardcoded at the call site so a vault records the cost it was
  // written with, and the cost can be raised later without breaking existing vaults.
  //
  // INTERACTIVE (64 MB, 2 ops) not MODERATE (256 MB): the phone is a first-class target — favela runs
  // as a FULL NODE on Android (#36) with a measured storage budget already in the hundreds of MB — and
  // a 256 MB argon2 allocation during unlock is a real risk there. The memlimit is recorded per-vault,
  // so raising it later is a per-vault decision, not a migration.
  static kdfParams () {
    return {
      kdf: 'argon2id',
      alg: sodium.crypto_pwhash_ALG_ARGON2ID13,
      opslimit: sodium.crypto_pwhash_OPSLIMIT_INTERACTIVE,
      memlimit: sodium.crypto_pwhash_MEMLIMIT_INTERACTIVE,
      saltBytes: sodium.crypto_pwhash_SALTBYTES
    }
  }

  // Memory-hard KDF. `salt` may be longer than the KDF consumes (generateSalt returns 32 for backward
  // compatibility with existing callers); the first SALTBYTES are used.
  static deriveVaultKey (passphrase, salt, params = CryptoManager.kdfParams()) {
    if (typeof passphrase !== 'string' || passphrase.length === 0) throw new Error('Passphrase must be a non-empty string')
    if (!Buffer.isBuffer(salt) || salt.length < sodium.crypto_pwhash_SALTBYTES) {
      throw new Error('Salt must be a Buffer of at least ' + sodium.crypto_pwhash_SALTBYTES + ' bytes')
    }
    const key = Buffer.alloc(sodium.crypto_secretbox_KEYBYTES)
    sodium.crypto_pwhash(
      key,
      Buffer.from(passphrase, 'utf8'),
      salt.subarray(0, sodium.crypto_pwhash_SALTBYTES),
      params.opslimit, params.memlimit, params.alg
    )
    return key
  }

  // XSalsa20-Poly1305. Returns nonce || ciphertext+MAC, so the box is self-contained and a fresh nonce
  // is used per call — encrypting the same secret twice must not produce identical bytes.
  static encryptSecret (data, passphrase, salt, params = CryptoManager.kdfParams()) {
    if (!Buffer.isBuffer(data)) throw new Error('Data must be a Buffer')
    const key = CryptoManager.deriveVaultKey(passphrase, salt, params)
    const nonce = Buffer.alloc(sodium.crypto_secretbox_NONCEBYTES)
    sodium.randombytes_buf(nonce)
    const ct = Buffer.alloc(data.length + sodium.crypto_secretbox_MACBYTES)
    sodium.crypto_secretbox_easy(ct, data, nonce, key)
    sodium.sodium_memzero(key)
    return Buffer.concat([nonce, ct])
  }

  // THROWS on a wrong passphrase or tampered bytes. The old scheme could not: an unauthenticated cipher
  // returns a different plaintext for a different key and cannot tell you which happened.
  static decryptSecret (box, passphrase, salt, params = CryptoManager.kdfParams()) {
    if (!Buffer.isBuffer(box) || box.length <= sodium.crypto_secretbox_NONCEBYTES + sodium.crypto_secretbox_MACBYTES) {
      throw new Error('Ciphertext is too short to be a valid vault box')
    }
    const nonce = box.subarray(0, sodium.crypto_secretbox_NONCEBYTES)
    const ct = box.subarray(sodium.crypto_secretbox_NONCEBYTES)
    const key = CryptoManager.deriveVaultKey(passphrase, salt, params)
    const out = Buffer.alloc(ct.length - sodium.crypto_secretbox_MACBYTES)
    const ok = sodium.crypto_secretbox_open_easy(out, ct, nonce, key)
    sodium.sodium_memzero(key)
    if (!ok) throw new Error('vault: wrong passphrase, or the vault is corrupt')
    return out
  }

  static hash(data) {
    if (!Buffer.isBuffer(data)) {
      throw new Error('Data must be a Buffer')
    }
    return crypto.createHash('sha256').update(data).digest()
  }

  /**
   * Generate random bytes
   * @param {number} length - Number of bytes to generate
   * @returns {Buffer} Random bytes
   */
  static randomBytes(length) {
    if (typeof length !== 'number' || length < 0) {
      throw new Error('Length must be a non-negative number')
    }
    return crypto.randomBytes(length)
  }

  /**
   * Generate Ed25519 keypair for device
   * @returns {Object} Object with publicKey and privateKey as Buffers
   */
  static generateDeviceKeyPair() {
    const { publicKey, privateKey } = crypto.generateKeyPairSync('ed25519')
    
    return {
      publicKey: publicKey.export({ type: 'spki', format: 'der' }),
      privateKey: privateKey.export({ type: 'pkcs8', format: 'der' })
    }
  }

  /**
   * Convert DER format keys to raw Ed25519 format (32 bytes)
   * @param {Buffer} derKey - DER formatted key
   * @param {string} type - 'public' or 'private'
   * @returns {Buffer} Raw Ed25519 key (32 bytes)
   */
  static derToRawEd25519(derKey, type) {
    if (!Buffer.isBuffer(derKey)) {
      throw new Error('DER key must be a Buffer')
    }
    if (type !== 'public' && type !== 'private') {
      throw new Error('Type must be "public" or "private"')
    }

    if (type === 'public') {
      // For Ed25519 public keys in SPKI format, the raw key is the last 32 bytes
      return derKey.slice(-32)
    } else {
      // For Ed25519 private keys in PKCS8 format, the raw key is at a specific offset
      // PKCS8 Ed25519 private key structure has the 32-byte key at offset -32
      return derKey.slice(-32)
    }
  }

  /**
   * Generate raw Ed25519 keypair using sodium-universal (compatible with keet-identity-key)
   * @returns {Object} Object with publicKey (32 bytes) and secretKey (64 bytes) as Buffers
   */
  static async generateRawDeviceKeyPair() {
    // Import sodium-universal dynamically to avoid issues if not installed
    let sodium;
    try {
      const sodiumModule = await import('sodium-universal');
      sodium = sodiumModule.default;
    } catch (error) {
      throw new Error('sodium-universal is required for device key generation. Run: npm install sodium-universal');
    }
    
    const publicKey = Buffer.alloc(sodium.crypto_sign_PUBLICKEYBYTES);
    const secretKey = Buffer.alloc(sodium.crypto_sign_SECRETKEYBYTES);
    sodium.crypto_sign_keypair(publicKey, secretKey);
    
    return {
      publicKey,
      secretKey  // Note: keet-identity-key uses 'secretKey' not 'privateKey'
    };
  }
}
function isBuffer(value) {
  return Buffer.isBuffer(value) || value instanceof Uint8Array
}

function isEncoding(encoding) {
  return Buffer.isEncoding(encoding)
}

function alloc(size, fill, encoding) {
  return Buffer.alloc(size, fill, encoding)
}

function allocUnsafe(size) {
  return Buffer.allocUnsafe(size)
}

function allocUnsafeSlow(size) {
  return Buffer.allocUnsafeSlow(size)
}

function byteLength(string, encoding) {
  return Buffer.byteLength(string, encoding)
}

function compare(a, b) {
  return Buffer.compare(a, b)
}

function concat(buffers, totalLength) {
  return Buffer.concat(buffers, totalLength)
}

function copy(source, target, targetStart, start, end) {
  return toBuffer(source).copy(target, targetStart, start, end)
}

function equals(a, b) {
  return toBuffer(a).equals(b)
}

function fill(buffer, value, offset, end, encoding) {
  return toBuffer(buffer).fill(value, offset, end, encoding)
}

function from(value, encodingOrOffset, length) {
  return Buffer.from(value, encodingOrOffset, length)
}

function includes(buffer, value, byteOffset, encoding) {
  return toBuffer(buffer).includes(value, byteOffset, encoding)
}

function indexOf(buffer, value, byfeOffset, encoding) {
  return toBuffer(buffer).indexOf(value, byfeOffset, encoding)
}

function lastIndexOf(buffer, value, byteOffset, encoding) {
  return toBuffer(buffer).lastIndexOf(value, byteOffset, encoding)
}

function swap16(buffer) {
  return toBuffer(buffer).swap16()
}

function swap32(buffer) {
  return toBuffer(buffer).swap32()
}

function swap64(buffer) {
  return toBuffer(buffer).swap64()
}

function toBuffer(buffer) {
  if (Buffer.isBuffer(buffer)) return buffer
  return Buffer.from(buffer.buffer, buffer.byteOffset, buffer.byteLength)
}

function toString(buffer, encoding, start, end) {
  return toBuffer(buffer).toString(encoding, start, end)
}

function write(buffer, string, offset, length, encoding) {
  return toBuffer(buffer).write(string, offset, length, encoding)
}

function readDoubleBE(buffer, offset) {
  return toBuffer(buffer).readDoubleBE(offset)
}

function readDoubleLE(buffer, offset) {
  return toBuffer(buffer).readDoubleLE(offset)
}

function readFloatBE(buffer, offset) {
  return toBuffer(buffer).readFloatBE(offset)
}

function readFloatLE(buffer, offset) {
  return toBuffer(buffer).readFloatLE(offset)
}

function readInt32BE(buffer, offset) {
  return toBuffer(buffer).readInt32BE(offset)
}

function readInt32LE(buffer, offset) {
  return toBuffer(buffer).readInt32LE(offset)
}

function readUInt32BE(buffer, offset) {
  return toBuffer(buffer).readUInt32BE(offset)
}

function readUInt32LE(buffer, offset) {
  return toBuffer(buffer).readUInt32LE(offset)
}

function writeDoubleBE(buffer, value, offset) {
  return toBuffer(buffer).writeDoubleBE(value, offset)
}

function writeDoubleLE(buffer, value, offset) {
  return toBuffer(buffer).writeDoubleLE(value, offset)
}

function writeFloatBE(buffer, value, offset) {
  return toBuffer(buffer).writeFloatBE(value, offset)
}

function writeFloatLE(buffer, value, offset) {
  return toBuffer(buffer).writeFloatLE(value, offset)
}

function writeInt32BE(buffer, value, offset) {
  return toBuffer(buffer).writeInt32BE(value, offset)
}

function writeInt32LE(buffer, value, offset) {
  return toBuffer(buffer).writeInt32LE(value, offset)
}

function writeUInt32BE(buffer, value, offset) {
  return toBuffer(buffer).writeUInt32BE(value, offset)
}

function writeUInt32LE(buffer, value, offset) {
  return toBuffer(buffer).writeUInt32LE(value, offset)
}

module.exports = {
  isBuffer,
  isEncoding,
  alloc,
  allocUnsafe,
  allocUnsafeSlow,
  byteLength,
  compare,
  concat,
  copy,
  equals,
  fill,
  from,
  includes,
  indexOf,
  lastIndexOf,
  swap16,
  swap32,
  swap64,
  toBuffer,
  toString,
  write,
  readDoubleBE,
  readDoubleLE,
  readFloatBE,
  readFloatLE,
  readInt32BE,
  readInt32LE,
  readUInt32BE,
  readUInt32LE,
  writeDoubleBE,
  writeDoubleLE,
  writeFloatBE,
  writeFloatLE,
  writeInt32BE,
  writeInt32LE,
  writeUInt32BE,
  writeUInt32LE
}
{
  "name": "b4a",
  "version": "1.8.0",
  "description": "Bridging the gap between buffers and typed arrays",
  "exports": {
    "./package": "./package.json",
    ".": {
      "react-native": "./react-native.js",
      "browser": "./browser.js",
      "default": "./index.js"
    }
  },
  "files": [
    "browser.js",
    "index.js",
    "react-native.js",
    "lib"
  ],
  "scripts": {
    "test": "npm run lint && npm run test:bare && npm run test:node",
    "test:bare": "bare test.mjs",
    "test:node": "node test.mjs",
    "lint": "prettier . --check"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/b4a.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/b4a/issues"
  },
  "homepage": "https://github.com/holepunchto/b4a#readme",
  "devDependencies": {
    "brittle": "^3.5.2",
    "nanobench": "^3.0.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^1.0.0"
  },
  "peerDependencies": {
    "react-native-b4a": "*"
  },
  "peerDependenciesMeta": {
    "react-native-b4a": {
      "optional": true
    }
  }
}
const ESC = '\x1b'
const CSI = ESC + '['
const SGR = (n) => CSI + n + 'm'

exports.constants = {
  ESC,
  CSI,
  SGR
}

// https://en.wikipedia.org/wiki/ANSI_escape_code#CSI_(Control_Sequence_Introducer)_sequences

exports.cursorHide = CSI + '?25l'
exports.cursorShow = CSI + '?25h'

exports.cursorUp = function cursorUp(n = 1) {
  return CSI + n + 'A'
}

exports.cursorDown = function cursorDown(n = 1) {
  return CSI + n + 'B'
}

exports.cursorForward = function cursorForward(n = 1) {
  return CSI + n + 'C'
}

exports.cursorBack = function cursorBack(n = 1) {
  return CSI + n + 'D'
}

exports.cursorNextLine = function cursorNextLine(n = 1) {
  return CSI + n + 'E'
}

exports.cursorPreviousLine = function cursorPreviousLine(n = 1) {
  return CSI + n + 'F'
}

exports.cursorPosition = function cursorPosition(column, row = 0) {
  if (row === 0) return CSI + (column + 1) + 'G'

  return CSI + (row + 1) + ';' + (column + 1) + 'H'
}

exports.eraseDisplayEnd = CSI + 'J'
exports.eraseDisplayStart = CSI + '1J'
exports.eraseDisplay = CSI + '2J'
exports.eraseLineEnd = CSI + 'K'
exports.eraseLineStart = CSI + '1K'
exports.eraseLine = CSI + '2K'

exports.scrollUp = function scrollUp(n = 1) {
  return CSI + n + 'S'
}

exports.scrollDown = function scrollDown(n = 1) {
  return CSI + n + 'T'
}

// https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_(Select_Graphic_Rendition)_parameters

exports.modifierReset = SGR(0)
exports.modifierBold = SGR(1)
exports.modifierDim = SGR(2)
exports.modifierItalic = SGR(3)
exports.modifierUnderline = SGR(4)
exports.modifierNormal = SGR(22)
exports.modifierNotItalic = SGR(23)
exports.modifierNotUnderline = SGR(24)

// https://en.wikipedia.org/wiki/ANSI_escape_code#Colors

exports.colorBlack = SGR(30)
exports.colorRed = SGR(31)
exports.colorGreen = SGR(32)
exports.colorYellow = SGR(33)
exports.colorBlue = SGR(34)
exports.colorMagenta = SGR(35)
exports.colorCyan = SGR(36)
exports.colorWhite = SGR(37)
exports.colorDefault = SGR(39)
exports.colorBrightBlack = SGR(90)
exports.colorBrightRed = SGR(91)
exports.colorBrightGreen = SGR(92)
exports.colorBrightYellow = SGR(93)
exports.colorBrightBlue = SGR(94)
exports.colorBrightMagenta = SGR(95)
exports.colorBrightCyan = SGR(96)
exports.colorBrightWhite = SGR(97)
{
  "name": "bare-ansi-escapes",
  "version": "2.2.3",
  "description": "Parse and produce ANSI escape sequences",
  "exports": {
    "./package": "./package.json",
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    },
    "./key-decoder": {
      "types": "./key-decoder.d.ts",
      "default": "./key-decoder.js"
    }
  },
  "files": [
    "index.js",
    "index.d.ts",
    "key-decoder.js",
    "key-decoder.d.ts"
  ],
  "scripts": {
    "test": "prettier . --check && bare test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-ansi-escapes.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-ansi-escapes/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-ansi-escapes#readme",
  "dependencies": {
    "bare-stream": "^2.6.5"
  },
  "devDependencies": {
    "bare-buffer": "^3.0.2",
    "brittle": "^3.3.2",
    "prettier": "^3.4.2",
    "prettier-config-standard": "^7.0.0"
  },
  "peerDependencies": {
    "bare-buffer": "*"
  },
  "peerDependenciesMeta": {
    "bare-buffer": {
      "optional": true
    }
  }
}
const inspect = require('bare-inspect')

class AssertionError extends Error {
  constructor(opts = {}) {
    let { message = null, actual, expected, operator } = opts

    if (message === null) {
      message = `${inspect(actual)} ${operator} ${inspect(expected)}`
    }

    super(message)

    this.actual = actual
    this.expected = expected
    this.operator = operator
  }

  get name() {
    return 'AssertionError'
  }

  get code() {
    'ASSERTION'
  }
}

function assertFail(opts, fn) {
  if (opts.message instanceof Error) throw opts.message

  const err = new AssertionError(opts)

  if (Error.captureStackTrace) Error.captureStackTrace(err, fn)

  throw err
}

module.exports = exports = function assert(actual, message) {
  if (actual) return

  assertFail({ message, actual, expected: true, operator: '==' }, assert)
}

exports.AssertionError = AssertionError

exports.fail = function fail(message) {
  if (message === undefined) message = 'Failed'

  assertFail({ message, operator: 'fail' }, fail)
}

exports.ok = function ok(actual, message) {
  if (actual) return

  assertFail({ message, actual, expected: true, operator: '==' }, ok)
}

exports.notOk = function ok(actual, message) {
  if (!actual) return

  assertFail({ message, actual, expected: false, operator: '==' }, ok)
}

exports.equal = function equal(actual, expected, message) {
  if (actual == expected || (actual !== actual && expected !== expected)) {
    return
  }

  assertFail({ message, actual, expected, operator: '==' }, equal)
}

exports.notEqual = function notEqual(actual, expected, message) {
  if (actual != expected && (actual === actual || expected === expected)) {
    return
  }

  assertFail({ message, actual, expected, operator: '!=' }, notEqual)
}

exports.strictEqual = function strictEqual(actual, expected, message) {
  if (Object.is(actual, expected)) return

  assertFail(
    { message, actual, expected, operator: 'strictEqual' },
    strictEqual
  )
}

exports.notStrictEqual = function notStrictEqual(actual, expected, message) {
  if (!Object.is(actual, expected)) return

  assertFail(
    { message, actual, expected, operator: 'notStrictEqual' },
    notStrictEqual
  )
}
{
  "name": "bare-assert",
  "version": "1.2.0",
  "description": "Assertion library for JavaScript",
  "exports": {
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    },
    "./package": "./package.json"
  },
  "files": [
    "index.js",
    "index.d.ts"
  ],
  "scripts": {
    "test": "prettier . --check && bare test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-assert.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-assert/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-assert#readme",
  "dependencies": {
    "bare-inspect": "^3.1.2"
  },
  "devDependencies": {
    "brittle": "^3.1.1",
    "prettier": "^3.4.2",
    "prettier-config-standard": "^7.0.0"
  }
}
module.exports = require.addon()
const constants = require('./lib/constants')
const Hash = require('./lib/hash')
const Hmac = require('./lib/hmac')
const { Cipheriv, Decipheriv } = require('./lib/cipher')
const { randomBytes, randomFill, randomUUID } = require('./lib/random')
const pbkdf2 = require('./lib/pbkdf2')
const { generateKeyPair } = require('./lib/key')
const { sign, verify } = require('./lib/signature')

exports.constants = constants

exports.Hash = Hash

exports.createHash = function createHash(algorithm, opts) {
  return new Hash(algorithm, opts)
}

exports.Hmac = Hmac

exports.createHmac = function createHmac(algorithm, key, opts) {
  return new Hmac(algorithm, key, opts)
}

exports.Cipheriv = Cipheriv

exports.createCipheriv = function createCipheriv(algorithm, key, iv, opts) {
  return new Cipheriv(algorithm, key, iv, opts)
}

exports.Decipheriv = Decipheriv

exports.createDecipheriv = function createDecipheriv(algorithm, key, iv, opts) {
  return new Decipheriv(algorithm, key, iv, opts)
}

exports.randomBytes = randomBytes

exports.randomFill = randomFill

// For Node.js compatibility
exports.randomFillSync = function randomFillSync(buffer, offset, size) {
  return exports.randomFill(buffer, offset, size)
}

exports.randomUUID = randomUUID

exports.pbkdf2 = pbkdf2

// For Node.js compatibility
exports.pbkdf2Sync = function pbkdf2Sync(password, salt, iterations, keylen, digest) {
  return exports.pbkdf2(password, salt, iterations, keylen, digest)
}

exports.generateKeyPair = generateKeyPair

exports.sign = sign

exports.verify = verify

// For Node.js compatibility
exports.webcrypto = require('./web')
const { Transform } = require('bare-stream')
const assert = require('bare-assert')
const binding = require('../binding')
const constants = require('./constants')

const {
  cipher: {
    AES128ECB,
    AES128CBC,
    AES128CTR,
    AES128OFB,
    AES256ECB,
    AES256CBC,
    AES256CTR,
    AES256OFB,
    AES128GCM,
    AES256GCM,
    CHACHA20POLY1305,
    XCHACHA20POLY1305
  }
} = constants

class CryptoCipher {
  constructor(algorithm, key, iv, encrypt, opts = {}) {
    const { encoding = 'utf8' } = opts

    if (typeof key === 'string') key = Buffer.from(key, encoding)
    if (typeof iv === 'string') iv = Buffer.from(iv, encoding)

    assert(ArrayBuffer.isView(key))
    assert(ArrayBuffer.isView(iv))

    if (key.byteLength !== binding.cipherKeyLength(algorithm)) {
      throw new RangeError('Invalid key length')
    }

    if (iv.byteLength < binding.cipherIVLength(algorithm)) {
      throw new RangeError('Invalid iv length')
    }

    this._handle = binding.cipherInit(
      algorithm,
      key.buffer,
      key.byteOffset,
      key.byteLength,
      iv.buffer,
      iv.byteOffset,
      iv.byteLength,
      encrypt
    )
  }

  update(data, inputEncoding = 'utf8', outputEncoding) {
    if (this._handle === null) {
      throw new Error('Cipher has already been finalized')
    }

    if (typeof data === 'string') data = Buffer.from(data, inputEncoding)

    assert(ArrayBuffer.isView(data))

    const out = new ArrayBuffer(data.byteLength + binding.cipherBlockSize(this._handle))

    const written = binding.cipherUpdate(
      this._handle,
      data.buffer,
      data.byteOffset,
      data.byteLength,
      out
    )

    const result = Buffer.from(out, 0, written)

    return outputEncoding ? result.toString(outputEncoding) : result
  }

  final(outputEncoding) {
    if (this._handle === null) {
      throw new Error('Cipher has already been finalized')
    }

    const out = new ArrayBuffer(binding.cipherBlockSize(this._handle))

    const written = binding.cipherFinal(this._handle, out)

    this._handle = null

    const result = Buffer.from(out, 0, written)

    return outputEncoding ? result.toString(outputEncoding) : result
  }

  setAutoPadding(pad) {
    if (this._handle === null) {
      throw new Error('Cipher has already been finalized')
    }

    binding.cipherSetPadding(this._handle, pad)
  }
}

class CryptoAuthenticatedCipher {
  constructor(algorithm, key, nonce, opts = {}) {
    const { encoding = 'utf8', authTagLength = 16 } = opts

    if (typeof key === 'string') key = Buffer.from(key, encoding)
    if (typeof nonce === 'string') nonce = Buffer.from(nonce, encoding)

    assert(ArrayBuffer.isView(key))
    assert(ArrayBuffer.isView(nonce))

    if (key.byteLength !== binding.aeadKeyLength(algorithm)) {
      throw new RangeError('Invalid key length')
    }

    if (nonce.byteLength < binding.aeadNonceLength(algorithm)) {
      throw new RangeError('Invalid nonce length')
    }

    this._buffer = []
    this._nonce = nonce
    this._authTag = null
    this._authTagLength = authTagLength
    this._additionalData = null

    this._handle = binding.aeadInit(
      algorithm,
      key.buffer,
      key.byteOffset,
      key.byteLength,
      authTagLength
    )
  }

  update(data, inputEncoding = 'utf8', outputEncoding) {
    if (typeof data === 'string') data = Buffer.from(data, inputEncoding)

    assert(ArrayBuffer.isView(data))

    this._buffer.push(data)

    return outputEncoding ? '' : Buffer.alloc(0)
  }

  setAAD(buffer, opts = {}) {
    const { encoding = 'utf8' } = opts

    if (typeof buffer === 'string') buffer = Buffer.from(buffer, encoding)

    assert(ArrayBuffer.isView(buffer))

    this._additionalData = buffer
  }

  getAuthTag() {
    return this._authTag
  }

  setAuthTag(authTag, encoding) {
    if (typeof authTag === 'string') authTag = Buffer.from(authTag, encoding)

    assert(ArrayBuffer.isView(authTag))

    this._authTag = authTag
  }
}

class CryptoAuthenticatedSeal extends CryptoAuthenticatedCipher {
  final(outputEncoding) {
    if (this._handle === null) {
      throw new Error('Cipher has already been finalized')
    }

    const data = this._buffer.length === 1 ? this._buffer[0] : Buffer.concat(this._buffer)

    const nonce = this._nonce
    const ad = this._additionalData || Buffer.alloc(0)

    const out = new ArrayBuffer(data.byteLength + binding.aeadMaxOverhead(this._handle))

    const written = binding.aeadSeal(
      this._handle,
      data.buffer,
      data.byteOffset,
      data.byteLength,
      nonce.buffer,
      nonce.byteOffset,
      nonce.byteLength,
      ad.buffer,
      ad.byteOffset,
      ad.byteLength,
      out
    )

    this._handle = null

    const cipherLength = written - this._authTagLength

    this._authTag = Buffer.from(out, cipherLength)

    const result = Buffer.from(out, 0, cipherLength)

    return outputEncoding ? result.toString(outputEncoding) : result
  }
}

class CryptoAuthenticatedOpen extends CryptoAuthenticatedCipher {
  final(outputEncoding) {
    if (this._handle === null) {
      throw new Error('Decipher has already been finalized')
    }

    this._buffer.push(this._authTag)

    const data = Buffer.concat(this._buffer)

    const nonce = this._nonce
    const ad = this._additionalData || Buffer.alloc(0)

    const out = new ArrayBuffer(data.byteLength)

    const written = binding.aeadOpen(
      this._handle,
      data.buffer,
      data.byteOffset,
      data.byteLength,
      nonce.buffer,
      nonce.byteOffset,
      nonce.byteLength,
      ad.buffer,
      ad.byteOffset,
      ad.byteLength,
      out
    )

    this._handle = null

    const result = Buffer.from(out, 0, written)

    return outputEncoding ? result.toString(outputEncoding) : result
  }
}

exports.Cipheriv = class CryptoCipheriv extends Transform {
  constructor(algorithm, key, iv, opts = {}) {
    super(opts)

    algorithm = constants.toCipher(algorithm)

    switch (algorithm) {
      case AES128ECB:
      case AES128CBC:
      case AES128CTR:
      case AES128OFB:
      case AES256ECB:
      case AES256CBC:
      case AES256CTR:
      case AES256OFB:
        this._cipher = new CryptoCipher(algorithm, key, iv, true, opts)
        break

      case AES128GCM:
      case AES256GCM:
      case CHACHA20POLY1305:
      case XCHACHA20POLY1305:
        this._cipher = new CryptoAuthenticatedSeal(algorithm, key, iv, opts)
        break
    }
  }

  update(data, inputEncoding, outputEncoding) {
    return this._cipher.update(data, inputEncoding, outputEncoding)
  }

  final(outputEncoding) {
    return this._cipher.final(outputEncoding)
  }

  setAutoPadding(pad) {
    this._cipher.setAutoPadding(pad)

    return this
  }

  setAAD(buffer, opts) {
    this._cipher.setAAD(buffer, opts)

    return this
  }

  getAuthTag() {
    return this._cipher.getAuthTag()
  }

  _transform(data, encoding, cb) {
    this.push(this.update(data))

    cb(null)
  }

  _flush(cb) {
    this.push(this.final())

    cb(null)
  }
}

exports.Decipheriv = class CryptoDeipheriv extends Transform {
  constructor(algorithm, key, iv, opts = {}) {
    super(opts)

    algorithm = constants.toCipher(algorithm)

    switch (algorithm) {
      case AES128ECB:
      case AES128CBC:
      case AES128CTR:
      case AES128OFB:
      case AES256ECB:
      case AES256CBC:
      case AES256CTR:
      case AES256OFB:
        this._cipher = new CryptoCipher(algorithm, key, iv, false, opts)
        break

      case AES128GCM:
      case AES256GCM:
      case CHACHA20POLY1305:
      case XCHACHA20POLY1305:
        this._cipher = new CryptoAuthenticatedOpen(algorithm, key, iv, opts)
        break
    }
  }

  update(data, inputEncoding, outputEncoding) {
    return this._cipher.update(data, inputEncoding, outputEncoding)
  }

  final(outputEncoding) {
    return this._cipher.final(outputEncoding)
  }

  setAutoPadding(pad) {
    this._cipher.setAutoPadding(pad)

    return this
  }

  setAAD(buffer, opts) {
    this._cipher.setAAD(buffer, opts)

    return this
  }

  setAuthTag(authTag, encoding) {
    this._cipher.setAuthTag(authTag, encoding)

    return this
  }

  _transform(data, encoding, cb) {
    this.push(this.update(data))

    cb(null)
  }

  _flush(cb) {
    this.push(this.final())

    cb(null)
  }
}
const { fail } = require('bare-assert')
const binding = require('../binding')
const errors = require('./errors')

module.exports = exports = {
  hash: {
    MD5: binding.MD5,
    SHA1: binding.SHA1,
    SHA256: binding.SHA256,
    SHA384: binding.SHA384,
    SHA512: binding.SHA512,
    BLAKE2B256: binding.BLAKE2B256,
    RIPEMD160: binding.RIPEMD160
  },
  signature: {
    ED25519: binding.ED25519
  },
  cipher: {
    AES128ECB: binding.AES128ECB,
    AES128CBC: binding.AES128CBC,
    AES128CTR: binding.AES128CTR,
    AES128OFB: binding.AES128OFB,
    AES256ECB: binding.AES256ECB,
    AES256CBC: binding.AES256CBC,
    AES256CTR: binding.AES256CTR,
    AES256OFB: binding.AES256OFB,
    AES128GCM: binding.AES128GCM,
    AES256GCM: binding.AES256GCM,
    CHACHA20POLY1305: binding.CHACHA20POLY1305,
    XCHACHA20POLY1305: binding.XCHACHA20POLY1305
  },
  keyType: {
    ED25519: binding.ED25519
  }
}

exports.toHash = function toHash(hash) {
  if (typeof hash === 'number' && !isNaN(hash)) return hash

  if (typeof hash === 'string') {
    hash = hash.replace(/-/g, '')

    if (hash in exports.hash === false) {
      hash = hash.toUpperCase()

      if (hash in exports.hash === false) {
        throw errors.UNKNOWN_HASH(`Unknown hash '${hash}'`)
      }
    }

    return exports.hash[hash]
  }

  fail(`Hash must be a number or string. Received ${isNaN(hash) ? 'NaN' : typeof hash} (${hash})`)
}

exports.toCipher = function toCiper(cipher) {
  if (typeof cipher === 'number' && !isNaN(cipher)) return cipher

  if (typeof cipher === 'string') {
    cipher = cipher.replace(/-/g, '')

    if (cipher in exports.cipher === false) {
      cipher = cipher.toUpperCase()

      if (cipher in exports.cipher === false) {
        throw errors.UNKNOWN_CIPHER(`Unknown cipher '${cipher}'`)
      }
    }

    return exports.cipher[cipher]
  }

  fail(
    `Cipher must be a number or string. Received ${isNaN(cipher) ? 'NaN' : typeof cipher} (${cipher})`
  )
}

exports.toKeyType = function toKeyType(type) {
  if (typeof type === 'number' && !isNaN(type)) return type

  if (typeof type === 'string') {
    type = type.replace(/-/g, '')

    if (type in exports.keyType === false) {
      type = type.toUpperCase()

      if (type in exports.keyType === false) {
        throw errors.UNKNOWN_KEY_TYPE(`Unknown key type '${type}'`)
      }
    }

    return exports.keyType[type]
  }

  fail(
    `Key type must be a number or string. Received ${isNaN(type) ? 'NaN' : typeof type} (${type})`
  )
}
module.exports = class CryptoError extends Error {
  constructor(msg, fn = CryptoError, code = fn.name) {
    super(`${code}: ${msg}`)
    this.code = code

    if (Error.captureStackTrace) {
      Error.captureStackTrace(this, fn)
    }
  }

  get name() {
    return 'CryptoError'
  }

  static UNKNOWN_HASH(msg) {
    return new CryptoError(msg, CryptoError.UNKNOWN_HASH)
  }

  static UNKNOWN_CIPHER(msg) {
    return new CryptoError(msg, CryptoError.UNKNOWN_CIPHER)
  }

  static UNKNOWN_KEY_TYPE(msg) {
    return new CryptoError(msg, CryptoError.UNKNOWN_KEY_TYPE)
  }

  static INVALID_ACCESS(msg) {
    return new CryptoError(msg, CryptoError.INVALID_ACCESS)
  }

  static INVALID_DATA(msg) {
    return new CryptoError(msg, CryptoError.INVALID_DATA)
  }

  static OPERATION_ERROR(msg) {
    return new CryptoError(msg, CryptoError.OPERATION_ERROR)
  }

  static NOT_SUPPORTED(msg) {
    return new CryptoError(msg, CryptoError.NOT_SUPPORTED)
  }
}
const { Transform } = require('bare-stream')
const assert = require('bare-assert')
const binding = require('../binding')
const constants = require('./constants')

const {
  hash: { RIPEMD160 }
} = constants

class CryptoDigest {
  constructor(algorithm) {
    this._handle = binding.digestInit(algorithm)
  }

  update(data) {
    if (this._handle === null) {
      throw new Error('Digest has already been finalized')
    }

    binding.digestUpdate(this._handle, data.buffer, data.byteOffset, data.byteLength)
  }

  final() {
    if (this._handle === null) {
      throw new Error('Digest has already been finalized')
    }

    const result = Buffer.from(binding.digestFinal(this._handle))
    this._handle = null
    return result
  }
}

class CryptoRIPEMD160Digest {
  constructor() {
    this._handle = binding.ripemd160Init()
  }

  update(data) {
    if (this._handle === null) {
      throw new Error('Digest has already been finalized')
    }

    binding.ripemd160Update(this._handle, data.buffer, data.byteOffset, data.byteLength)
  }

  final() {
    if (this._handle === null) {
      throw new Error('Digest has already been finalized')
    }

    const result = Buffer.from(binding.ripemd160Final(this._handle))
    this._handle = null
    return result
  }
}

module.exports = class CryptoHash extends Transform {
  constructor(algorithm, opts = {}) {
    super(opts)

    algorithm = constants.toHash(algorithm)

    switch (algorithm) {
      case RIPEMD160:
        this._digest = new CryptoRIPEMD160Digest()
        break

      default:
        this._digest = new CryptoDigest(algorithm)
        break
    }
  }

  update(data, encoding = 'utf8') {
    if (typeof data === 'string') data = Buffer.from(data, encoding)

    assert(ArrayBuffer.isView(data))

    this._digest.update(data)

    return this
  }

  digest(encoding) {
    const digest = this._digest.final()

    return encoding && encoding !== 'buffer' ? digest.toString(encoding) : digest
  }

  _transform(data, encoding, cb) {
    this.update(data)

    cb(null)
  }

  _flush(cb) {
    this.push(this.digest())

    cb(null)
  }
}
const { Transform } = require('bare-stream')
const assert = require('bare-assert')
const binding = require('../binding')
const constants = require('./constants')

module.exports = class CryptoHmac extends Transform {
  constructor(algorithm, key, opts = {}) {
    super(opts)

    const { encoding = 'utf8' } = opts

    if (typeof key === 'string') key = Buffer.from(key, encoding)

    assert(ArrayBuffer.isView(key))

    this._handle = binding.hmacInit(
      constants.toHash(algorithm),
      key.buffer,
      key.byteOffset,
      key.byteLength
    )
  }

  update(data, encoding = 'utf8') {
    if (this._handle === null) {
      throw new Error('Hmac has already been finalized')
    }

    if (typeof data === 'string') data = Buffer.from(data, encoding)

    assert(ArrayBuffer.isView(data))

    binding.hmacUpdate(this._handle, data.buffer, data.byteOffset, data.byteLength)

    return this
  }

  digest(encoding) {
    if (this._handle === null) {
      throw new Error('Hmac has already been finalized')
    }

    const digest = Buffer.from(binding.hmacFinal(this._handle))
    this._handle = null

    return encoding && encoding !== 'buffer' ? digest.toString(encoding) : digest
  }

  _transform(data, encoding, cb) {
    this.update(data)

    cb(null)
  }

  _flush(cb) {
    this.push(this.digest())

    cb(null)
  }
}
const binding = require('../binding')
const constants = require('./constants')

const {
  keyType: { ED25519 }
} = constants

class CryptoKey {
  constructor(keyType) {
    this._keyType = keyType
  }
}

exports.Key = CryptoKey

class CryptoEd25519Key extends CryptoKey {
  constructor(key) {
    super(ED25519)

    this._key = key
  }

  get asymmetricKeyType() {
    return 'ed25519'
  }
}

class CryptoEd25519PublicKey extends CryptoEd25519Key {
  get type() {
    return 'public'
  }
}

exports.Ed25519PublicKey = CryptoEd25519PublicKey

class CryptoEd25519PrivateKey extends CryptoEd25519Key {
  get type() {
    return 'private'
  }
}

exports.Ed25519PrivateKey = CryptoEd25519PrivateKey

exports.generateKeyPair = function generateKeyPair(type, opts = {}) {
  type = constants.toKeyType(type)

  switch (type) {
    case ED25519: {
      const { publicKey, privateKey } = binding.ed25519GenerateKeypair()

      return {
        publicKey: new CryptoEd25519PublicKey(publicKey),
        privateKey: new CryptoEd25519PrivateKey(privateKey)
      }
    }
  }
}
const assert = require('bare-assert')
const binding = require('../binding')
const constants = require('./constants')

module.exports = function pbkdf2(password, salt, iterations, keylen, digest, cb) {
  if (iterations <= 0) {
    throw new RangeError('iterations is out of range')
  }

  assert(typeof iterations === 'number' && !isNaN(iterations))
  assert(typeof keylen === 'number' && !isNaN(keylen))

  if (typeof password === 'string') password = Buffer.from(password)
  if (typeof salt === 'string') salt = Buffer.from(salt)

  assert(ArrayBuffer.isView(password))
  assert(ArrayBuffer.isView(salt))

  const buffer = Buffer.from(
    binding.pbkdf2(
      password.buffer,
      password.byteOffset,
      password.byteLength,
      salt.buffer,
      salt.byteOffset,
      salt.byteLength,
      iterations,
      constants.toHash(digest),
      keylen
    )
  )

  if (cb) queueMicrotask(() => cb(null, buffer))
  else return buffer
}
const assert = require('bare-assert')
const binding = require('../binding')

exports.randomBytes = function randomBytes(size, cb) {
  assert(typeof size === 'number' && !isNaN(size))

  const buffer = Buffer.allocUnsafe(size)
  exports.randomFill(buffer)
  if (cb) queueMicrotask(() => cb(null, buffer))
  else return buffer
}

exports.randomFill = function randomFill(buffer, offset, size, cb) {
  if (typeof offset === 'function') {
    cb = offset
    offset = undefined
  } else if (typeof size === 'function') {
    cb = size
    size = undefined
  }

  assert(buffer instanceof ArrayBuffer || ArrayBuffer.isView(buffer))
  assert(size === undefined || (typeof size === 'number' && !isNaN(size)))
  assert(offset === undefined || (typeof offset === 'number' && !isNaN(offset)))

  const elementSize = buffer.BYTES_PER_ELEMENT || 1

  if (offset === undefined) offset = 0
  else offset *= elementSize

  if (size === undefined) size = buffer.byteLength - offset
  else size *= elementSize

  if (offset < 0 || offset > buffer.byteLength) {
    throw new RangeError('offset is out of range')
  }

  if (size < 0 || size > buffer.byteLength) {
    throw new RangeError('size is out of range')
  }

  if (offset + size > buffer.byteLength) {
    throw new RangeError('offset + size is out of range')
  }

  let arraybuffer

  if (ArrayBuffer.isView(buffer)) {
    offset += buffer.byteOffset
    arraybuffer = buffer.buffer
  } else {
    arraybuffer = buffer
  }

  binding.randomFill(arraybuffer, offset, size)

  if (cb) queueMicrotask(() => cb(null, buffer))
  else return buffer
}

exports.randomUUID = function randomUUID() {
  const uuid = exports.randomBytes(16)

  uuid[6] = (uuid[6] >>> 4) | 0b01000000
  uuid[8] = (uuid[8] >>> 2) | 0b10000000

  return (
    uuid.subarray(0, 4).toString('hex') +
    '-' +
    uuid.subarray(4, 6).toString('hex') +
    '-' +
    uuid.subarray(6, 8).toString('hex') +
    '-' +
    uuid.subarray(8, 10).toString('hex') +
    '-' +
    uuid.subarray(10, 16).toString('hex')
  )
}
const assert = require('bare-assert')
const binding = require('../binding')
const { Key } = require('./key')
const constants = require('./constants')

const {
  keyType: { ED25519 }
} = constants

exports.sign = function sign(algorithm, data, key) {
  assert(data instanceof ArrayBuffer || ArrayBuffer.isView(data))

  if (ArrayBuffer.isView(data)) {
    data = Buffer.coerce(data)
  } else {
    data = Buffer.from(data)
  }

  assert(key instanceof Key)

  switch (key._keyType) {
    case ED25519:
      return Buffer.from(
        binding.ed25519Sign(data.buffer, data.byteOffset, data.byteLength, key._key)
      )
  }
}

exports.verify = function verify(algorithm, data, key, signature) {
  assert(data instanceof ArrayBuffer || ArrayBuffer.isView(data))

  if (ArrayBuffer.isView(data)) {
    data = Buffer.coerce(data)
  } else {
    data = Buffer.from(data)
  }

  assert(signature instanceof ArrayBuffer || ArrayBuffer.isView(signature))

  if (ArrayBuffer.isView(signature)) {
    signature = Buffer.coerce(signature)
  } else {
    signature = Buffer.from(signature)
  }

  assert(key instanceof Key)

  switch (key._keyType) {
    case ED25519:
      assert(signature.byteLength === 64)

      return binding.ed25519Verify(
        data.buffer,
        data.byteOffset,
        data.byteLength,
        signature.buffer,
        signature.byteOffset,
        key._key
      )
  }
}
const crypto = require('../../..')
const binding = require('../../../binding')
const errors = require('../../errors')
const { Ed25519PublicKey, Ed25519PrivateKey } = require('../../key')
const CryptoKey = require('../crypto-key')

// https://w3c.github.io/webcrypto/#ed25519

// https://w3c.github.io/webcrypto/#ed25519-operations-sign
exports.sign = function sign(algorithm, key, data) {
  if (key.type !== 'private') {
    throw errors.INVALID_ACCESS('Must pass private key for Ed25519 signing')
  }

  const signature = crypto.sign(null, data, key._handle)

  return signature.buffer.slice(0, signature.byteLength)
}

// https://w3c.github.io/webcrypto/#ed25519-operations-verify
exports.verify = function verify(algorithm, key, signature, data) {
  if (key.type !== 'public') {
    throw errors.INVALID_ACCESS('Must pass public key for Ed25519 verification')
  }

  return crypto.verify(null, data, key._handle, signature)
}

// https://w3c.github.io/webcrypto/#ed25519-operations-generate-key
exports.generateKey = function generateKey(algorithm, extractable, usages) {
  for (const usage of usages) {
    if (usage !== 'sign' && usage !== 'verify') {
      throw new SyntaxError(
        `Usage '${usage}' cannot be used for the Ed25519 generateKey() operation`
      )
    }
  }

  const keys = crypto.generateKeyPair('ed25519')

  algorithm = { name: 'Ed25519' }

  return {
    publicKey: new CryptoKey('public', true, algorithm, ['verify'], keys.publicKey),
    privateKey: new CryptoKey('private', extractable, algorithm, ['sign'], keys.privateKey)
  }
}

// https://w3c.github.io/webcrypto/#ed25519-operations-import-key
exports.importKey = function importKey(format, keyData, algorithm, extractable, usages) {
  switch (format) {
    case 'spki':
      for (const usage of usages) {
        if (usage !== 'verify') {
          throw new SyntaxError(
            `Usage '${usage}' cannot be used for the Ed25519 importKey() operation`
          )
        }
      }

      keyData = binding.ed25519FromSPKI(keyData.buffer, keyData.byteOffset, keyData.byteLength)

      return new CryptoKey(
        'public',
        extractable,
        {
          name: 'Ed25519'
        },
        usages,
        new Ed25519PublicKey(keyData)
      )
    case 'pkcs8':
      for (const usage of usages) {
        if (usage !== 'sign') {
          throw new SyntaxError(
            `Usage '${usage}' cannot be used for the Ed25519 importKey() operation`
          )
        }
      }

      keyData = binding.ed25519FromPKCS8(keyData.buffer, keyData.byteOffset, keyData.byteLength)

      return new CryptoKey(
        'private',
        extractable,
        {
          name: 'Ed25519'
        },
        usages,
        new Ed25519PrivateKey(keyData)
      )
    case 'raw':
      for (const usage of usages) {
        if (usage !== 'verify') {
          throw new SyntaxError(
            `Usage '${usage}' cannot be used for the Ed25519 importKey() operation`
          )
        }
      }

      if (keyData.byteLength * 8 !== 256) {
        throw errors.INVALID_DATA('Key must be 256 bits')
      }

      return new CryptoKey(
        'public',
        extractable,
        {
          name: 'Ed25519'
        },
        usages,
        new Ed25519PublicKey(keyData.buffer)
      )
    case 'jwk':
      const jwk = keyData

      if ('d' in jwk) {
        if (usages.some((usage) => usage !== 'sign')) {
          throw new SyntaxError('JWK must be valid for signing')
        }
      } else {
        if (usages.some((usage) => usage !== 'verify')) {
          throw new SyntaxError('JWK must be valid for verification')
        }
      }

      if (jwk.kty !== 'OKP') {
        throw errors.INVALID_DATA('JWK key must be an octet key-pair')
      }

      if (jwk.crv !== 'Ed25519') {
        throw errors.INVALID_DATA('JWK must use the Ed25519 curve')
      }

      if ('alg' in jwk && jwk.alg !== 'Ed25519' && jwk.alg !== 'EdDSA') {
        throw errors.INVALID_DATA('JWK must use the Ed25519 curve')
      }

      if (usages.length && 'use' in jwk && jwk.use !== 'sig') {
        throw errors.INVALID_DATA('JWK cannot be used for signatures')
      }

      if ('ext' in jwk && jwk.ext !== extractable && extractable) {
        throw errors.INVALID_DATA('JWK is not extractable')
      }

      if ('d' in jwk) {
        const key = Buffer.concat([
          Buffer.from(jwk.d, 'base64url'),
          Buffer.from(jwk.x, 'base64url')
        ])

        if (key.byteLength * 8 !== 512) {
          throw errors.INVALID_DATA('Key must be 512 bits')
        }

        return new CryptoKey(
          'private',
          extractable,
          {
            name: 'Ed25519'
          },
          usages,
          new Ed25519PrivateKey(key.buffer)
        )
      }

      const key = Buffer.from(jwk.x, 'base64url')

      if (key.byteLength * 8 !== 256) {
        throw errors.INVALID_DATA('Key must be 256 bits')
      }

      return new CryptoKey(
        'public',
        extractable,
        {
          name: 'Ed25519'
        },
        usages,
        new Ed25519PublicKey(key.buffer)
      )
    default:
      throw errors.NOT_SUPPORTED(
        `Format '${format}' cannot be used for the Ed25519 importKey() operation`
      )
  }
}

// https://w3c.github.io/webcrypto/#ed25519-operations-export-key
exports.exportKey = function exportKey(format, key) {
  const data = key._handle

  switch (format) {
    case 'spki':
      if (key.type !== 'public') {
        throw errors.INVALID_ACCESS(
          `Key of type '${key.type}' cannot be used for the Ed25519 exportKey() operation`
        )
      }

      return binding.ed25519ToSPKI(data._key)
    case 'pkcs8':
      if (key.type !== 'private') {
        throw errors.INVALID_ACCESS(
          `Key of type '${key.type}' cannot be used for the Ed25519 exportKey() operation`
        )
      }

      return binding.ed25519ToPKCS8(data._key)
    case 'raw': {
      if (key.type !== 'public') {
        throw errors.INVALID_ACCESS(
          `Key of type '${key.type}' cannot be used for the Ed25519 exportKey() operation`
        )
      }

      return data._key.slice()
    }
    case 'jwk': {
      const buffer = Buffer.from(data._key)

      if (key.type === 'private') {
        const d = buffer.subarray(0, 32).toString('base64url')
        const x = buffer.subarray(32).toString('base64url')

        return {
          kty: 'OKP',
          alg: 'Ed25519',
          crv: 'Ed25519',
          x,
          d,
          key_ops: key.usages,
          ext: key.extractable
        }
      }

      return {
        kty: 'OKP',
        alg: 'Ed25519',
        crv: 'Ed25519',
        x: buffer.toString('base64url'),
        key_ops: key.usages,
        ext: key.extractable
      }
    }
    default:
      throw errors.NOT_SUPPORTED(
        `Format '${format}' cannot be used for the HMAC exportKey() operation`
      )
  }
}
const crypto = require('../../..')
const errors = require('../../errors')
const CryptoKey = require('../crypto-key')

// https://w3c.github.io/webcrypto/#hmac

// https://w3c.github.io/webcrypto/#hmac-operations-sign
exports.sign = function sign(algorithm, key, data) {
  const digest = crypto.createHmac(key.algorithm.hash.name, key._handle).update(data).digest()

  return digest.buffer.slice(0, digest.byteLength)
}

// https://w3c.github.io/webcrypto/#hmac-operations-verify
exports.verify = function verify(algorithm, key, signature, data) {
  const digest = crypto.createHmac(key.algorithm.hash.name, key._handle).update(data).digest()

  if (ArrayBuffer.isView(signature)) {
    signature = Buffer.coerce(signature)
  } else {
    signature = Buffer.from(signature)
  }

  return signature.equals(digest)
}

// https://w3c.github.io/webcrypto/#hmac-operations-generate-key
exports.generateKey = function generateKey(algorithm, extractable, usages) {
  for (const usage of usages) {
    if (usage !== 'sign' && usage !== 'verify') {
      throw new SyntaxError(`Usage '${usage}' cannot be used for the HMAC generateKey() operation`)
    }
  }

  const { length = exports.getKeyLength(algorithm) } = algorithm.length

  let hash = algorithm.hash

  if (typeof hash === 'string') hash = { name: hash }

  const key = crypto.createHmac(hash.name, crypto.randomBytes(length)).digest()

  return new CryptoKey(
    'secret',
    extractable,
    {
      name: 'HMAC',
      length,
      hash: {
        name: hash.name.toUpperCase()
      }
    },
    usages,
    key
  )
}

// https://w3c.github.io/webcrypto/#hmac-operations-import-key
exports.importKey = function importKey(format, keyData, algorithm, extractable, usages) {
  for (const usage of usages) {
    if (usage !== 'sign' && usage !== 'verify') {
      throw new SyntaxError(`Invalid usage ${usage}`)
    }
  }

  let hash = algorithm.hash

  if (typeof hash === 'string') hash = { name: hash }

  let data

  switch (format) {
    case 'raw':
      data = keyData
      break
    case 'jwk':
      const jwk = keyData

      if (jwk.kty !== 'oct') {
        throw errors.INVALID_DATA('JWK key must be an octet sequence')
      }

      data = Buffer.from(jwk.k, 'base64url')

      switch (hash.name.toLowerCase()) {
        case 'sha-1':
          if (jwk.alg === 'HS1') break
          else throw errors.INVALID_DATA('Invalid JWK key algorithm')
        case 'sha-256':
          if (jwk.alg === 'HS256') break
          else throw errors.INVALID_DATA('Invalid JWK key algorithm')
        case 'sha-384':
          if (jwk.alg === 'HS384') break
          else throw errors.INVALID_DATA('Invalid JWK key algorithm')
        case 'sha-512':
          if (jwk.alg === 'HS512') break
          else throw errors.INVALID_DATA('Invalid JWK key algorithm')
      }

      if (usages.length && 'use' in jwk && jwk.use !== 'sign') {
        throw errors.INVALID_DATA('JWK cannot be used for signing')
      }

      if ('ext' in jwk && jwk.ext !== extractable && extractable) {
        throw errors.INVALID_DATA('JWK is not extractable')
      }
      break
    default:
      throw errors.NOT_SUPPORTED(
        `Format '${format}' cannot be used for the HMAC importKey() operation`
      )
  }

  const length = data.byteLength * 8

  if (length === 0) {
    throw errors.INVALID_DATA('Key cannot be empty')
  }

  return new CryptoKey(
    'secret',
    extractable,
    {
      name: 'HMAC',
      length,
      hash: {
        name: hash.name.toUpperCase()
      }
    },
    usages,
    data
  )
}

// https://w3c.github.io/webcrypto/#hmac-operations-export-key
exports.exportKey = function exportKey(format, key) {
  const data = key._handle

  switch (format) {
    case 'raw':
      return data.buffer.slice(0, data.byteLength)
    case 'jwk': {
      const jwk = {
        kty: 'oct',
        k: data.toString('base64url'),
        alg: null,
        key_ops: key.usages,
        ext: key.extractable
      }

      switch (key.algorithm.hash.name) {
        case 'SHA-1':
          jwk.alg = 'HS1'
          break
        case 'SHA-256':
          jwk.alg = 'HS256'
          break
        case 'SHA-384':
          jwk.alg = 'HS384'
          break
        case 'SHA-512':
          jwk.alg = 'HS512'
          break
      }

      return jwk
    }
    default:
      throw errors.NOT_SUPPORTED(
        `Format '${format}' cannot be used for the HMAC exportKey() operation`
      )
  }
}

// https://w3c.github.io/webcrypto/#hmac-operations-get-key-length
exports.getKeyLength = function getKeyLength(algorithm) {
  const { length, hash } = algorithm

  if (length === undefined) {
    if (hash === 'SHA-1' || hash === 'SHA-256') return 512
    if (hash === 'SHA-512') return 1024

    throw errors.OPERATION_ERROR(`Invalid hash '${hash}'`)
  }

  if (length === 0) {
    throw errors.OPERATION_ERROR(`Invalid length ${length}`)
  }

  return length
}
const crypto = require('../../..')
const errors = require('../../errors')
const CryptoKey = require('../crypto-key')

// https://w3c.github.io/webcrypto/#pbkdf2

// https://w3c.github.io/webcrypto/#pbkdf2-operations-derive-bits
exports.deriveBits = function deriveBits(algorithm, key, length) {
  if (length === undefined || length % 8) {
    throw errors.OPERATION_ERROR('Length must be multiple of 8')
  }

  if (algorithm.iterations === 0) {
    throw errors.OPERATION_ERROR('Iterations must be non-0')
  }

  if (length === 0) {
    return new ArrayBuffer(0)
  }

  let hash = algorithm.hash

  if (typeof hash === 'string') hash = { name: hash }

  const result = crypto.pbkdf2(
    key._handle,
    algorithm.salt,
    algorithm.iterations,
    length / 8,
    hash.name
  )

  return result.buffer
}

// https://w3c.github.io/webcrypto/#pbkdf2-operations-import-key
exports.importKey = function importKey(format, keyData, algorithm, extractable, usages) {
  if (format !== 'raw') {
    throw errors.NOT_SUPPORTED(
      `Format '${format}' cannot be used for the PBKDF2 importKey() operation`
    )
  }

  for (const usage of usages) {
    if (usage !== 'deriveKey' && usage !== 'deriveBits') {
      throw new SyntaxError(`Invalid usage ${usage}`)
    }
  }

  if (extractable) {
    throw new SyntaxError('Extractable must be false')
  }

  return new CryptoKey(
    'secret',
    extractable,
    {
      name: 'PBKDF2'
    },
    usages,
    keyData
  )
}
const crypto = require('../../..')

// https://w3c.github.io/webcrypto/#sha

// https://w3c.github.io/webcrypto/#sha-operations-digest
exports.digest = function digest(name, data) {
  const digest = crypto.createHash(name).update(data).digest()

  return digest.buffer.slice(0, digest.byteLength)
}
// https://w3c.github.io/webcrypto/#cryptokey-interface
module.exports = class CryptoKey {
  constructor(type, extractable, algorithm, usages, handle = null) {
    this._type = type
    this._extractable = extractable
    this._algorithm = algorithm
    this._usages = usages
    this._handle = handle
  }

  // https://w3c.github.io/webcrypto/#dom-cryptokey-type
  get type() {
    return this._type
  }

  // https://w3c.github.io/webcrypto/#dom-cryptokey-extractable
  get extractable() {
    return this._extractable
  }

  // https://w3c.github.io/webcrypto/#dom-cryptokey-algorithm
  get algorithm() {
    return this._algorithm
  }

  // https://w3c.github.io/webcrypto/#dom-cryptokey-usages
  get usages() {
    return this._usages
  }

  [Symbol.for('bare.inspect')]() {
    return {
      __proto__: { constructor: CryptoKey },

      type: this.type,
      extractable: this.extractable,
      algorithm: this.algorithm,
      usages: this.usages
    }
  }
}
{
  "name": "bare-crypto",
  "version": "1.13.4",
  "description": "Cryptographic primitives for JavaScript",
  "exports": {
    "./package": "./package.json",
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    },
    "./web": {
      "types": "./web.d.ts",
      "default": "./web.js"
    },
    "./global": {
      "types": "./global.d.ts",
      "default": "./global.js"
    },
    "./constants": "./lib/constants.js",
    "./errors": "./lib/errors.js"
  },
  "files": [
    "index.js",
    "index.d.ts",
    "web.js",
    "web.d.ts",
    "global.js",
    "global.d.ts",
    "binding.c",
    "binding.js",
    "CMakeLists.txt",
    "lib",
    "prebuilds"
  ],
  "addon": true,
  "scripts": {
    "test": "prettier . --check && bare test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-crypto.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-crypto/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-crypto#readme",
  "dependencies": {
    "bare-assert": "^1.2.0",
    "bare-stream": "^2.6.3"
  },
  "devDependencies": {
    "bare-buffer": "^3.0.1",
    "brittle": "^3.5.0",
    "cmake-bare": "^1.1.6",
    "cmake-fetch": "^1.0.0",
    "prettier": "^3.4.2",
    "prettier-config-holepunch": "^2.0.0"
  },
  "peerDependencies": {
    "bare-buffer": "*"
  },
  "peerDependenciesMeta": {
    "bare-buffer": {
      "optional": true
    }
  }
}
const crypto = require('.')
const errors = require('./lib/errors')
const CryptoKey = require('./lib/web/crypto-key')
const hmac = require('./lib/web/algorithm/hmac')
const pbkdf2 = require('./lib/web/algorithm/pbkdf2')
const ed25519 = require('./lib/web/algorithm/ed25519')
const sha = require('./lib/web/algorithm/sha')

exports.CryptoKey = CryptoKey

// https://w3c.github.io/webcrypto/#Crypto-method-getRandomValues
exports.getRandomValues = function getRandomValues(array) {
  return crypto.randomFillSync(array)
}

// https://w3c.github.io/webcrypto/#dfn-Crypto-method-randomUUID
exports.randomUUID = crypto.randomUUID

// https://w3c.github.io/webcrypto/#subtlecrypto-interface
exports.SubtleCrypto = class SubtleCrypto {
  // https://w3c.github.io/webcrypto/#SubtleCrypto-method-generateKey
  async generateKey(algorithm, extractable, usages) {
    if (typeof algorithm === 'string') algorithm = { name: algorithm }

    switch (algorithm.name.toLowerCase()) {
      case 'hmac':
        return hmac.generateKey(algorithm, extractable, usages)
      case 'ed25519':
        return ed25519.generateKey(algorithm, extractable, usages)
      default:
        throw errors.NOT_SUPPORTED(
          `Algorithm '${algorithm.name}' does not support the generateKey() operation`
        )
    }
  }

  // https://w3c.github.io/webcrypto/#SubtleCrypto-method-importKey
  async importKey(format, keyData, algorithm, extractable, usages) {
    if (typeof algorithm === 'string') algorithm = { name: algorithm }

    switch (format) {
      case 'raw':
      case 'pkcs8':
      case 'spki':
        if (ArrayBuffer.isView(keyData)) {
          keyData = Buffer.from(keyData)
        } else {
          keyData = Buffer.from(keyData.slice())
        }
        break
    }

    switch (algorithm.name.toLowerCase()) {
      case 'hmac':
        return hmac.importKey(format, keyData, algorithm, extractable, usages)
      case 'ed25519':
        return ed25519.importKey(format, keyData, algorithm, extractable, usages)
      case 'pbkdf2':
        return pbkdf2.importKey(format, keyData, algorithm, extractable, usages)
      default:
        throw errors.NOT_SUPPORTED(
          `Algorithm '${algorithm.name}' does not support the importKey() operation`
        )
    }
  }

  // https://w3c.github.io/webcrypto/#SubtleCrypto-method-exportKey
  async exportKey(format, key) {
    if (!key.extractable) {
      throw errors.INVALID_ACCESS('Key is not extractable')
    }

    switch (key.algorithm.name.toLowerCase()) {
      case 'hmac':
        return hmac.exportKey(format, key)
      case 'ed25519':
        return ed25519.exportKey(format, key)
      default:
        throw errors.NOT_SUPPORTED(
          `Algorithm '${key.algorithm.name}' does not support the exportKey() operation`
        )
    }
  }

  // https://w3c.github.io/webcrypto/#SubtleCrypto-method-sign
  async sign(algorithm, key, data) {
    if (typeof algorithm === 'string') algorithm = { name: algorithm }

    if (algorithm.name.toLowerCase() !== key.algorithm.name.toLowerCase()) {
      throw errors.INVALID_ACCESS(`Algorithm '${algorithm.name}' does not match key'`)
    }

    if (!key.usages.includes('sign')) {
      throw errors.INVALID_ACCESS('Key cannot be used for signing')
    }

    switch (algorithm.name.toLowerCase()) {
      case 'hmac':
        return hmac.sign(algorithm, key, data)
      case 'ed25519':
        return ed25519.sign(algorithm, key, data)
      default:
        throw errors.NOT_SUPPORTED(
          `Algorithm '${algorithm.name}' does not support the sign() operation`
        )
    }
  }

  // https://w3c.github.io/webcrypto/#SubtleCrypto-method-verify
  async verify(algorithm, key, signature, data) {
    if (typeof algorithm === 'string') algorithm = { name: algorithm }

    if (algorithm.name.toLowerCase() !== key.algorithm.name.toLowerCase()) {
      throw errors.INVALID_ACCESS(`Algorithm '${algorithm.name}' does not match key'`)
    }

    if (!key.usages.includes('verify')) {
      throw errors.INVALID_ACCESS('Key cannot be used for verification')
    }

    switch (algorithm.name.toLowerCase()) {
      case 'hmac':
        return hmac.verify(algorithm, key, signature, data)
      case 'ed25519':
        return ed25519.verify(algorithm, key, signature, data)
      default:
        throw errors.NOT_SUPPORTED(
          `Algorithm '${algorithm.name}' does not support the verify() operation`
        )
    }
  }

  // https://w3c.github.io/webcrypto/#SubtleCrypto-method-deriveBits
  async deriveBits(algorithm, key, length) {
    if (typeof algorithm === 'string') algorithm = { name: algorithm }

    if (algorithm.name.toLowerCase() !== key.algorithm.name.toLowerCase()) {
      throw errors.INVALID_ACCESS(`Algorithm '${algorithm.name}' does not match key'`)
    }

    if (!key.usages.includes('deriveBits')) {
      throw errors.INVALID_ACCESS('Key cannot be used to derive bits')
    }

    switch (algorithm.name.toLowerCase()) {
      case 'pbkdf2':
        return pbkdf2.deriveBits(algorithm, key, length)
      default:
        throw errors.NOT_SUPPORTED(
          `Algorithm '${algorithm.name}' does not support the deriveBits() operation`
        )
    }
  }

  // https://w3c.github.io/webcrypto/#SubtleCrypto-method-deriveKey
  async deriveKey(algorithm, baseKey, derivedKeyType, extractable, usages) {
    if (typeof algorithm === 'string') algorithm = { name: algorithm }

    if (typeof derivedKeyType === 'string') {
      derivedKeyType = { name: derivedKeyType }
    }

    if (algorithm.name.toLowerCase() !== baseKey.algorithm.name.toLowerCase()) {
      throw errors.INVALID_ACCESS(`Algorithm '${algorithm.name}' does not match key'`)
    }

    if (!baseKey.usages.includes('deriveKey')) {
      throw errors.INVALID_ACCESS('Key cannot be used to derive key')
    }

    let length

    switch (derivedKeyType.name.toLowerCase()) {
      case 'hmac':
        length = hmac.getKeyLength(derivedKeyType)
        break
      default:
        throw errors.NOT_SUPPORTED(
          `Algorithm '${derivedKeyType.name}' does not support the getKeyLength() operation`
        )
    }

    let secret

    switch (algorithm.name.toLowerCase()) {
      case 'pbkdf2':
        secret = pbkdf2.deriveBits(algorithm, baseKey, length)
        break
      default:
        throw errors.NOT_SUPPORTED(
          `Algorithm '${algorithm.name}' does not support the deriveBits() operation`
        )
    }

    return this.importKey('raw', secret, derivedKeyType, extractable, usages)
  }

  // https://w3c.github.io/webcrypto/#SubtleCrypto-method-digest
  async digest(algorithm, data) {
    if (typeof algorithm === 'string') algorithm = { name: algorithm }

    switch (algorithm.name.toLowerCase()) {
      case 'sha-1':
        return sha.digest(crypto.constants.hash.SHA1, data)
      case 'sha-256':
        return sha.digest(crypto.constants.hash.SHA256, data)
      case 'sha-384':
        return sha.digest(crypto.constants.hash.SHA384, data)
      case 'sha-512':
        return sha.digest(crypto.constants.hash.SHA512, data)
      default:
        throw errors.NOT_SUPPORTED(
          `Algorithm '${algorithm.name}' does not support the digest() operation`
        )
    }
  }
}

exports.subtle = new exports.SubtleCrypto()

// https://w3c.github.io/webcrypto/#crypto-interface
exports.Crypto = class Crypto {
  get subtle() {
    return exports.subtle
  }

  getRandomValues(array) {
    return exports.getRandomValues(array)
  }

  randomUUID() {
    return exports.randomUUID()
  }
}
const errors = require('./lib/errors')

class EventListener {
  constructor() {
    this.list = []
    this.count = 0
  }

  append(ctx, name, fn, once) {
    this.count++
    ctx.emit('newListener', name, fn) // Emit BEFORE adding
    this.list.push([fn, once])
  }

  prepend(ctx, name, fn, once) {
    this.count++
    ctx.emit('newListener', name, fn) // Emit BEFORE adding
    this.list.unshift([fn, once])
  }

  remove(ctx, name, fn) {
    for (let i = 0, n = this.list.length; i < n; i++) {
      const l = this.list[i]

      if (l[0] === fn) {
        this.list.splice(i, 1)

        if (this.count === 1) delete ctx._events[name]

        ctx.emit('removeListener', name, fn) // Emit AFTER removing

        this.count--
        return
      }
    }
  }

  removeAll(ctx, name) {
    const list = [...this.list]
    this.list = []

    if (this.count === list.length) delete ctx._events[name]

    for (let i = list.length - 1; i >= 0; i--) {
      ctx.emit('removeListener', name, list[i][0]) // Emit AFTER removing
    }

    this.count -= list.length
  }

  emit(ctx, name, ...args) {
    const list = [...this.list]

    for (let i = 0, n = list.length; i < n; i++) {
      const l = list[i]

      if (l[1] === true) this.remove(ctx, name, l[0])

      Reflect.apply(l[0], ctx, args)
    }

    return list.length > 0
  }
}

function appendListener(ctx, name, fn, once) {
  if (ctx._events === undefined) ctx._events = Object.create(null)
  const e = ctx._events[name] || (ctx._events[name] = new EventListener())
  e.append(ctx, name, fn, once)
  return ctx
}

function prependListener(ctx, name, fn, once) {
  if (ctx._events === undefined) ctx._events = Object.create(null)
  const e = ctx._events[name] || (ctx._events[name] = new EventListener())
  e.prepend(ctx, name, fn, once)
  return ctx
}

function removeListener(ctx, name, fn) {
  if (ctx._events === undefined) return ctx
  const e = ctx._events[name]
  if (e !== undefined) e.remove(ctx, name, fn)
  return ctx
}

function throwUnhandledError(...args) {
  let err

  if (args.length > 0) err = args[0]

  if (err instanceof Error === false) err = errors.UNHANDLED_ERROR(err)

  if (Error.captureStackTrace) {
    Error.captureStackTrace(err, exports.prototype.emit)
  }

  queueMicrotask(() => {
    throw err
  })
}

module.exports = exports = class EventEmitter {
  constructor() {
    this._events = Object.create(null)
  }

  addListener(name, fn) {
    return appendListener(this, name, fn, false)
  }

  addOnceListener(name, fn) {
    return appendListener(this, name, fn, true)
  }

  prependListener(name, fn) {
    return prependListener(this, name, fn, false)
  }

  prependOnceListener(name, fn) {
    return prependListener(this, name, fn, true)
  }

  removeListener(name, fn) {
    return removeListener(this, name, fn)
  }

  on(name, fn) {
    return appendListener(this, name, fn, false)
  }

  once(name, fn) {
    return appendListener(this, name, fn, true)
  }

  off(name, fn) {
    return removeListener(this, name, fn)
  }

  emit(name, ...args) {
    if (name === 'error' && this._events !== undefined && this._events.error === undefined) {
      throwUnhandledError(...args)
    }

    if (this._events === undefined) return false
    const e = this._events[name]
    return e === undefined ? false : e.emit(this, name, ...args)
  }

  listeners(name) {
    if (this._events === undefined) return []
    const e = this._events[name]
    return e === undefined ? [] : [...e.list]
  }

  listenerCount(name) {
    if (this._events === undefined) return 0
    const e = this._events[name]
    return e === undefined ? 0 : e.list.length
  }

  getMaxListeners() {
    return EventEmitter.defaultMaxListeners
  }

  setMaxListeners(n) {}

  removeAllListeners(name) {
    if (arguments.length === 0) {
      for (const key of Reflect.ownKeys(this._events)) {
        if (key === 'removeListener') continue
        this.removeAllListeners(key)
      }
      this.removeAllListeners('removeListener')
    } else {
      const e = this._events[name]
      if (e !== undefined) e.removeAll(this, name)
    }
    return this
  }
}

exports.EventEmitter = exports

exports.errors = errors

exports.defaultMaxListeners = 10

exports.on = function on(emitter, name, opts = {}) {
  const { signal } = opts

  if (signal && signal.aborted) {
    throw errors.OPERATION_ABORTED(signal.reason)
  }

  let error = null
  let done = false

  const events = []
  const promises = []

  if (name !== 'error') emitter.on('error', onerror)

  if (signal) signal.addEventListener('abort', onabort)

  emitter.on(name, onevent)

  return {
    next() {
      if (events.length) {
        return Promise.resolve({ value: events.shift(), done: false })
      }

      if (error) {
        const err = error

        error = null

        return Promise.reject(err)
      }

      if (done) return onclose()

      return new Promise((resolve, reject) => promises.push({ resolve, reject }))
    },

    return() {
      return onclose()
    },

    throw(err) {
      return onerror(err)
    },

    [Symbol.asyncIterator]() {
      return this
    }
  }

  function onevent(...args) {
    if (promises.length) {
      promises.shift().resolve({ value: args, done: false })
    } else {
      events.push(args)
    }
  }

  function onerror(err) {
    emitter.off(name, onevent).off('error', onerror)

    if (promises.length) {
      promises.shift().reject(err)
    } else {
      error = err
    }

    return Promise.resolve({ done: true })
  }

  function onabort() {
    signal.removeEventListener('abort', onabort)

    onerror(errors.OPERATION_ABORTED(signal.reason))
  }

  function onclose() {
    emitter.off(name, onevent)

    if (name !== 'error') emitter.off('error', onerror)

    if (signal) signal.removeEventListener('abort', onabort)

    done = true

    if (promises.length) promises.shift().resolve({ done: true })

    return Promise.resolve({ done: true })
  }
}

exports.once = function once(emitter, name, opts = {}) {
  const { signal } = opts

  if (signal && signal.aborted) {
    return Promise.reject(errors.OPERATION_ABORTED(signal.reason))
  }

  return new Promise((resolve, reject) => {
    if (name !== 'error') emitter.on('error', onerror)

    if (signal) signal.addEventListener('abort', onabort)

    emitter.once(name, onevent)

    function onevent(...args) {
      if (name !== 'error') emitter.off('error', onerror)

      if (signal) signal.removeEventListener('abort', onabort)

      resolve(args)
    }

    function onerror(err) {
      emitter.off(name, onevent)

      if (name !== 'error') emitter.off('error', onerror)

      reject(err)
    }

    function onabort() {
      signal.removeEventListener('abort', onabort)

      onerror(errors.OPERATION_ABORTED(signal.reason))
    }
  })
}

exports.forward = function forward(from, to, names, opts = {}) {
  if (typeof names === 'string') names = [names]

  const { emit = to.emit.bind(to) } = opts

  const listeners = names.map(
    (name) =>
      function onevent(...args) {
        emit(name, ...args)
      }
  )

  to.on('newListener', (name) => {
    const i = names.indexOf(name)

    if (i !== -1 && to.listenerCount(name) === 0) {
      from.on(name, listeners[i])
    }
  }).on('removeListener', (name) => {
    const i = names.indexOf(name)

    if (i !== -1 && to.listenerCount(name) === 0) {
      from.off(name, listeners[i])
    }
  })
}

exports.listenerCount = function listenerCount(emitter, name) {
  return emitter.listenerCount(name)
}

exports.getMaxListeners = function getMaxListeners(emitter) {
  if (typeof emitter.getMaxListeners === 'function') {
    return emitter.getMaxListeners()
  }

  return exports.defaultMaxListeners
}

exports.setMaxListeners = function setMaxListeners(n, ...emitters) {
  if (emitters.length === 0) exports.defaultMaxListeners = n
  else {
    for (const emitter of emitters) {
      if (typeof emitter.setMaxListeners === 'function') {
        emitter.setMaxListeners(n)
      }
    }
  }
}
module.exports = class EventEmitterError extends Error {
  constructor(msg, code, fn = EventEmitterError, opts) {
    super(`${code}: ${msg}`, opts)
    this.code = code

    if (Error.captureStackTrace) {
      Error.captureStackTrace(this, fn)
    }
  }

  get name() {
    return 'EventEmitterError'
  }

  static OPERATION_ABORTED(cause, msg = 'Operation aborted') {
    return new EventEmitterError(msg, 'OPERATION_ABORTED', EventEmitterError.OPERATION_ABORTED, {
      cause
    })
  }

  static UNHANDLED_ERROR(cause, msg = 'Unhandled error') {
    return new EventEmitterError(msg, 'UNHANDLED_ERROR', EventEmitterError.UNHANDLED_ERROR, {
      cause
    })
  }
}
{
  "name": "bare-events",
  "version": "2.8.2",
  "description": "Event emitters for JavaScript",
  "exports": {
    "./package": "./package.json",
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    },
    "./global": {
      "types": "./global.d.ts",
      "default": "./global.js"
    },
    "./web": {
      "types": "./web.d.ts",
      "default": "./web.js"
    },
    "./errors": "./lib/errors.js"
  },
  "files": [
    "index.js",
    "index.d.ts",
    "global.js",
    "global.d.ts",
    "web.js",
    "web.d.ts",
    "lib"
  ],
  "scripts": {
    "test": "npm run lint && npm run test:bare && npm run test:node",
    "test:bare": "bare test.js",
    "test:node": "node test.js",
    "lint": "prettier . --check"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-events.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-events/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-events#readme",
  "devDependencies": {
    "bare-abort-controller": "^1.0.0",
    "brittle": "^3.3.2",
    "prettier": "^3.4.2",
    "prettier-config-holepunch": "^2.0.0",
    "uncaughts": "^1.1.1"
  },
  "peerDependencies": {
    "bare-abort-controller": "*"
  },
  "peerDependenciesMeta": {
    "bare-abort-controller": {
      "optional": true
    }
  }
}
module.exports = require.addon()
const ansiEscapes = require('bare-ansi-escapes')
const getType = require('bare-type')
const binding = require('./binding')

const PLAIN_KEY = /^[a-zA-Z_][a-zA-Z_0-9]*$/

const defaultDepth = 2
const defaultBreakLength = 80
const defaultMaxArrayLength = 40

module.exports = exports = function inspect(value, opts = {}) {
  const {
    colors = false,
    depth = defaultDepth,
    breakLength = defaultBreakLength,
    stylize = defaultStylize(colors)
  } = opts

  const references = new InspectRefMap()

  const tree = inspectValue(value, 0, {
    colors,
    depth,
    breakLength,
    stylize,
    references
  })

  return tree.toString()
}

exports.styles = {
  bigint: ansiEscapes.colorYellow,
  boolean: ansiEscapes.colorYellow,
  date: ansiEscapes.colorMagenta,
  module: ansiEscapes.modifierUnderline,
  name: ansiEscapes.modifierReset,
  null: ansiEscapes.modifierBold,
  number: ansiEscapes.colorYellow,
  regexp: ansiEscapes.colorRed,
  special: ansiEscapes.colorCyan,
  string: ansiEscapes.colorGreen,
  symbol: ansiEscapes.colorGreen,
  undefined: ansiEscapes.colorBrightBlack
}

const styles = exports.styles

function defaultStylize(colors) {
  return function stylize(value, style) {
    const color = colors && styles[style]

    if (color) return color + value + ansiEscapes.modifierReset

    return value
  }
}

class InspectRefMap {
  constructor() {
    this.refs = new WeakMap()
    this.ids = new WeakMap()
    this.nextId = 1
  }

  has(object) {
    return this.refs.has(object)
  }

  get(object) {
    return this.refs.get(object) || null
  }

  set(object, ref) {
    this.refs.set(object, ref)
  }

  id(object) {
    let id = this.ids.get(object)
    if (id) return id

    id = this.nextId++
    this.ids.set(object, id)

    return id
  }
}

class InspectNode {
  constructor(depth, length, opts) {
    const { breakLength = defaultBreakLength, breakAlways = false } = opts

    this.depth = depth
    this.length = length
    this.breakLength = breakLength
    this.breakAlways = breakAlways
  }

  pad(n, string) {
    return string.padStart(n, ' ')
  }

  indent(n, string) {
    return '  '.repeat(n) + string
  }
}

class InspectRef extends InspectNode {
  constructor(depth, opts) {
    super(depth, '[circular *]'.length, opts)

    this.refs = opts.references
    this.count = 0
    this.circular = false
    this.color = opts.colors && styles.special
  }

  get id() {
    return this.refs.id(this)
  }

  increment() {
    return ++this.count
  }

  decrement() {
    return --this.count
  }

  toString(opts = {}) {
    const { offset = 0, pad = 0, indent = 0 } = opts

    let value = this.pad(pad, '[circular *' + this.id + ']')

    if (this.color) value = this.color + value + ansiEscapes.modifierReset

    return offset ? value : this.indent(indent, value)
  }
}

class InspectLeaf extends InspectNode {
  constructor(value, color, depth, opts) {
    const length = value.length

    if (value.includes('\n')) {
      value = value.replaceAll('\n', '\n' + '  '.repeat(depth))

      opts = { ...opts, breakAlways: true }
    }

    super(depth, length, opts)

    this.value = value
    this.color = opts.colors && color
  }

  toString(opts = {}) {
    const { offset = 0, pad = 0, indent = 0 } = opts

    let value = this.pad(pad, this.value)

    if (this.color) value = this.color + value + ansiEscapes.modifierReset

    return offset ? value : this.indent(indent, value)
  }
}

class InspectPair extends InspectNode {
  constructor(delim, left, right, depth, opts) {
    const length = left.length + delim.length + right.length

    if (left.breakAlways || right.breakAlways) {
      opts = { ...opts, breakAlways: true }
    }

    super(depth, length, opts)

    this.delim = delim
    this.left = left
    this.right = right
  }

  toString(opts = {}) {
    const { indent = 0 } = opts

    return this.indent(
      indent,
      this.left +
        this.delim +
        this.right.toString({
          indent,
          offset: this.left.length + this.delim.length
        })
    )
  }
}

class InspectSuspension extends InspectNode {
  constructor(overflow, depth, opts) {
    const label = `... ${overflow} more`

    super(depth, label.length, opts)

    this.label = label
  }

  toString(opts = {}) {
    const { indent = 0 } = opts

    return this.indent(indent, this.label)
  }
}

class InspectSequence extends InspectNode {
  constructor(header, footer, delim, values, ref, depth, opts) {
    const { tabulate = false } = opts

    const length =
      (ref.circular ? '<ref *>'.length + 1 : 0) +
      header.length +
      values.reduce(
        (length, value, i) =>
          length + value.length + (i === 0 ? 0 : delim.length),
        0
      ) +
      footer.length

    if (values.some((value) => value.breakAlways)) {
      opts = { ...opts, breakAlways: true }
    }

    super(depth, length, opts)

    this.header = header
    this.footer = footer
    this.delim = delim
    this.values = values
    this.ref = ref
    this.tabulate = tabulate
  }

  toString(opts = {}) {
    const { offset = 0, indent = 0 } = opts

    const split =
      this.breakAlways ||
      (this.values.length &&
        (offset + this.length > this.breakLength ||
          indent * 2 + this.length > this.breakLength))

    let header = this.header

    if (this.ref.circular) {
      header = '<ref *' + this.ref.id + '> ' + header
    }

    if (this.values.length === 0) {
      header = header.trimEnd()
    }

    if (offset === 0) {
      header = this.indent(indent, header)
    }

    if (split) {
      header = header.trimEnd() + '\n'
    }

    let string = header

    let columns = 1
    let pad = 0

    if (this.tabulate) {
      const widest = this.values.reduce(
        (length, value) =>
          value.breakAlways ? length : Math.max(length, value.length),
        0
      )

      if (widest) {
        columns = Math.max(
          columns,
          Math.floor(
            (this.breakLength - indent * 2) / (widest + this.delim.length)
          )
        )

        if (columns > 1) pad = widest
      }
    }

    for (let i = 0, n = this.values.length, offset = 0; i < n; i++) {
      const value = this.values[i]

      if (split) {
        let part

        if (i % columns === 0 || value.breakAlways) {
          part = value.toString({ indent: indent + 1, pad })
        } else {
          part = value.toString({ pad })
        }

        string += part

        if (i < n - 1) {
          if (i % columns === columns - 1 || this.values[i + 1].breakAlways) {
            string += this.delim.trimEnd() + '\n'
          } else {
            string += this.delim
          }
        }
      } else {
        if (i > 0) string += this.delim

        string += value.toString({ offset })

        offset += value.length
      }
    }

    let footer = this.footer

    if (this.values.length === 0) {
      footer = footer.trimStart()
    }

    if (split) {
      string += '\n' + this.indent(indent, footer.trimStart())
    } else {
      string += footer
    }

    return string
  }
}

function inspectValue(value, depth, opts) {
  const type = getType(value)

  if (type.isUndefined()) return inspectUndefined(depth, opts)
  if (type.isNull()) return inspectNull(depth, opts)
  if (type.isBoolean()) return inspectBoolean(value, depth, opts)
  if (type.isNumber()) return inspectNumber(value, depth, opts)
  if (type.isBigInt()) return inspectBigInt(value, depth, opts)
  if (type.isString()) return inspectString(value, depth, opts)
  if (type.isSymbol()) return inspectSymbol(value, depth, opts)
  if (type.isObject()) return inspectObject(type, value, depth, opts)
  if (type.isFunction()) return inspectFunction(type, value, depth, opts)
  if (type.isExternal()) return inspectExternal(value, opts, opts)
}

function inspectUndefined(depth, opts) {
  return new InspectLeaf('undefined', styles.undefined, depth, opts)
}

function inspectNull(depth, opts) {
  return new InspectLeaf('null', styles.null, depth, opts)
}

function inspectBoolean(value, depth, opts) {
  return new InspectLeaf(value.toString(), styles.boolean, depth, opts)
}

function inspectNumber(value, depth, opts) {
  let string

  if (Object.is(value, -0)) {
    string = '-0'
  } else {
    string = value.toString(10)
  }

  return new InspectLeaf(string, styles.number, depth, opts)
}

function inspectBigInt(value, depth, opts) {
  return new InspectLeaf(value.toString(10) + 'n', styles.bigint, depth, opts)
}

const STRING_ESCAPES =
  /[\ud800-\udbff][\udc00-\udfff]|[\u0000-\u001f'\\\ud800-\udfff]/g

function inspectString(value, depth, opts) {
  // https://tc39.es/ecma262/multipage/structured-data.html#sec-quotejsonstring
  const string = value.replace(STRING_ESCAPES, (match) => {
    if (match.length === 2) return match

    switch (match) {
      case "'":
        return "\\'"
      case '\\':
        return '\\\\'
      case '\b':
        return '\\b'
      case '\t':
        return '\\t'
      case '\n':
        return '\\n'
      case '\f':
        return '\\f'
      case '\r':
        return '\\r'
      default:
        return '\\u' + match.charCodeAt(0).toString(16).padStart(4, '0')
    }
  })

  return new InspectLeaf("'" + string + "'", styles.string, depth, opts)
}

function inspectSymbol(value, depth, opts) {
  return new InspectLeaf(value.toString(), styles.symbol, depth, opts)
}

function inspectKey(value, depth, opts) {
  if (PLAIN_KEY.test(value)) {
    return new InspectLeaf(value, null, depth, opts)
  } else {
    return inspectValue(value, depth, opts)
  }
}

function inspectObject(type, object, depth, opts) {
  const refs = opts.references

  let ref = refs.get(object)
  if (ref === null) {
    ref = new InspectRef(depth, opts)
    refs.set(object, ref)
  } else if (ref.count) {
    ref.circular = true
    return ref
  }

  const maxDepth = typeof opts.depth === 'number' ? opts.depth : Infinity

  if (maxDepth < depth) {
    const constructor = object.constructor

    return new InspectLeaf(
      '[' +
        (constructor && constructor.name ? constructor.name : 'Object') +
        ']',
      styles.special,
      depth,
      opts
    )
  }

  const inspect =
    object[Symbol.for('bare.inspect')] ||
    object[Symbol.for('nodejs.util.inspect.custom')]

  if (typeof inspect === 'function') {
    const value = inspect.call(
      object,
      typeof opts.depth === 'number' ? opts.depth - depth : null,
      {
        colors: opts.colors,
        breakLength: opts.breakLength,
        stylize: opts.stylize
      },
      exports
    )

    if (typeof value === 'object' && value !== null) {
      refs.set(value, ref)
    }

    if (typeof value !== 'string') {
      return inspectValue(value, depth, opts)
    }

    return new InspectLeaf(value, null, depth, opts)
  }

  if (type.isArray()) return inspectArray(object, ref, depth, opts)
  if (type.isDate()) return inspectDate(object, ref, depth, opts)
  if (type.isRegExp()) return inspectRegExp(object, ref, depth, opts)
  if (type.isError()) return inspectError(object, ref, depth, opts)
  if (type.isPromise()) return inspectPromise(object, ref, depth, opts)
  if (type.isMap()) return inspectMap(object, ref, depth, opts)
  if (type.isSet()) return inspectSet(object, ref, depth, opts)
  if (type.isWeakMap()) return inspectWeakMap(object, ref, depth, opts)
  if (type.isWeakSet()) return inspectWeakSet(object, ref, depth, opts)
  if (type.isWeakRef()) return inspectWeakRef(object, ref, depth, opts)
  if (type.isArrayBuffer()) return inspectArrayBuffer(object, ref, depth, opts)
  if (type.isSharedArrayBuffer())
    return inspectSharedArrayBuffer(object, ref, depth, opts)
  if (type.isTypedArray()) return inspectTypedArray(object, ref, depth, opts)
  if (type.isDataView()) return inspectDataView(object, ref, depth, opts)

  ref.increment()

  const values = []

  for (const key in object) {
    if (key === 'constructor') continue

    values.push(
      new InspectPair(
        ': ',
        inspectKey(key, depth + 1, opts),
        inspectValue(object[key], depth + 1, opts),
        depth + 1,
        opts
      )
    )
  }

  ref.decrement()

  let header = '{ '

  const tag = object[Symbol.toStringTag]

  if (tag) header = '[' + tag + '] ' + header

  if (object.constructor) {
    const name = object.constructor.name

    if (name && name !== 'Object') {
      header = object.constructor.name + ' ' + header
    }
  }

  return new InspectSequence(header, ' }', ', ', values, ref, depth, opts)
}

function inspectArray(array, ref, depth, opts) {
  const { maxArrayLength = defaultMaxArrayLength } = opts

  ref.increment()

  const values = []

  let remaining = Math.max(maxArrayLength, 0)

  for (let i = 0, n = array.length; i < n; i++) {
    if (remaining-- === 0) {
      values.push(
        new InspectSuspension(array.length - values.length, depth + 1, {
          ...opts,
          breakAlways: true
        })
      )

      break
    }

    values.push(inspectValue(array[i], depth + 1, opts))
  }

  for (const key of binding.getOwnNonIndexPropertyNames(array)) {
    values.push(
      new InspectPair(
        ': ',
        inspectKey(key, depth + 1, opts),
        inspectValue(array[key], depth + 1, opts),
        depth + 1,
        { ...opts, breakAlways: remaining < 0 }
      )
    )
  }

  ref.decrement()

  let header = '[ '

  if (array.constructor.name !== 'Array') {
    header = array.constructor.name + '(' + array.length + ') ' + header
  }

  return new InspectSequence(header, ' ]', ', ', values, ref, depth, {
    ...opts,
    tabulate: true
  })
}

function inspectDate(date, ref, depth, opts) {
  return new InspectLeaf(date.toISOString(), styles.date, depth, opts)
}

function inspectRegExp(regExp, ref, depth, opts) {
  return new InspectLeaf(regExp.toString(), styles.regexp, depth, opts)
}

function inspectError(error, ref, depth, opts) {
  let header

  if ('stack' in error) {
    header = error.stack

    if (depth > 0) {
      header = header.replaceAll('\n', '\n' + '  '.repeat(depth))
    }
  } else {
    header = error.toString()
  }

  const builtins = ['cause']

  if (error.name === 'AggregateError') {
    builtins.push('errors')
  } else if (error.name === 'SuppressedError') {
    builtins.push('error', 'suppressed')
  }

  const values = []

  for (const key of builtins) {
    if (key in error === false) continue

    values.push(
      new InspectPair(
        ': ',
        new InspectLeaf('[' + key + ']', null, depth + 1, opts),
        inspectValue(error[key], depth + 1, opts),
        depth + 1,
        opts
      )
    )
  }

  for (const key in error) {
    if (key === 'constructor' || builtins.includes(key)) continue

    values.push(
      new InspectPair(
        ': ',
        inspectKey(key, depth + 1, opts),
        inspectValue(error[key], depth + 1, opts),
        depth + 1,
        opts
      )
    )
  }

  if (values.length === 0) return new InspectLeaf(header, null, depth, opts)

  return new InspectSequence(
    header + ' {',
    ' }',
    ', ',
    values,
    ref,
    depth,
    opts
  )
}

function inspectPromise(promise, ref, depth, opts) {
  ref.increment()

  const state = binding.getPromiseState(promise)

  const values = []

  switch (state) {
    case 0: // Pending
      values.push(new InspectLeaf('<pending>', styles.special, depth, opts))
      break

    case 1: // Fulfilled
      values.push(inspectValue(binding.getPromiseResult(promise), depth, opts))
      break

    case 2: // Rejected
      values.push(
        new InspectLeaf('<rejected>', styles.special, depth, opts),
        inspectValue(binding.getPromiseResult(promise), depth, opts)
      )
  }

  ref.decrement()

  const header = promise.constructor.name + ' { '

  return new InspectSequence(header, ' }', ' ', values, ref, depth, opts)
}

function inspectMap(map, ref, depth, opts) {
  const {
    maxArrayLength = defaultMaxArrayLength,
    maxMapLength = maxArrayLength
  } = opts

  ref.increment()

  const values = []

  let remaining = maxMapLength

  for (const entry of map) {
    if (remaining-- === 0) {
      values.push(
        new InspectSuspension(map.size - values.length, depth + 1, {
          ...opts,
          breakAlways: true
        })
      )
      break
    }

    values.push(
      new InspectPair(
        ' => ',
        inspectValue(entry[0], depth + 1, opts),
        inspectValue(entry[1], depth + 1, opts),
        depth + 1,
        opts
      )
    )
  }

  for (const key in map) {
    if (key === 'constructor') continue

    const value = inspectValue(map[key], depth + 1, opts)

    values.push(
      new InspectPair(
        ': ',
        inspectKey(key, depth + 1, opts),
        value,
        depth + 1,
        { ...opts, breakAlways: remaining < 0 }
      )
    )
  }

  ref.decrement()

  const header = map.constructor.name + '(' + map.size + ') { '

  return new InspectSequence(header, ' }', ', ', values, ref, depth, {
    ...opts,
    tabulate: true
  })
}

function inspectSet(set, ref, depth, opts) {
  const {
    maxArrayLength = defaultMaxArrayLength,
    maxSetLength = maxArrayLength
  } = opts

  ref.increment()

  const values = []

  let remaining = maxSetLength

  for (const entry of set) {
    if (remaining-- === 0) {
      values.push(
        new InspectSuspension(set.size - values.length, depth + 1, {
          ...opts,
          breakAlways: true
        })
      )
      break
    }

    values.push(inspectValue(entry, depth + 1, opts))
  }

  for (const key in set) {
    if (key === 'constructor') continue

    const value = inspectValue(set[key], depth + 1, opts)

    values.push(
      new InspectPair(
        ': ',
        inspectKey(key, depth + 1, opts),
        value,
        depth + 1,
        { ...opts, breakAlways: remaining < 0 }
      )
    )
  }

  ref.decrement()

  const header = set.constructor.name + '(' + set.size + ') { '

  return new InspectSequence(header, ' }', ', ', values, ref, depth, {
    ...opts,
    tabulate: true
  })
}

function inspectWeakMap(weakMap, ref, depth, opts) {
  const header = weakMap.constructor.name + ' { '

  return new InspectSequence(
    header,
    ' }',
    ' ',
    [new InspectLeaf('<items unknown>', styles.special, depth + 1, opts)],
    ref,
    depth,
    opts
  )
}

function inspectWeakSet(weakSet, ref, depth, opts) {
  const header = weakSet.constructor.name + ' { '

  return new InspectSequence(
    header,
    ' }',
    ' ',
    [new InspectLeaf('<items unknown>', styles.special, depth + 1, opts)],
    ref,
    depth,
    opts
  )
}

function inspectWeakRef(weakRef, ref, depth, opts) {
  const target = weakRef.deref()

  let value

  if (target === undefined) {
    value = new InspectLeaf('<cleared>', styles.special, depth + 1, opts)
  } else {
    value = inspectValue(target, depth + 1, opts)
  }

  const header = weakRef.constructor.name + ' { '

  return new InspectSequence(header, ' }', ' ', [value], ref, depth, opts)
}

function inspectArrayBuffer(arrayBuffer, ref, depth, opts) {
  ref.increment()

  const values = []

  for (const key of ['byteLength']) {
    values.push(
      new InspectPair(
        ': ',
        inspectKey(key, depth + 1, opts),
        inspectValue(arrayBuffer[key], depth + 1, opts),
        depth + 1,
        opts
      )
    )
  }

  ref.decrement()

  const header = arrayBuffer.constructor.name + ' { '

  return new InspectSequence(header, ' }', ', ', values, ref, depth, opts)
}

function inspectSharedArrayBuffer(sharedArrayBuffer, ref, depth, opts) {
  ref.increment()

  const values = []

  for (const key of ['byteLength']) {
    values.push(
      new InspectPair(
        ': ',
        inspectKey(key, depth + 1, opts),
        inspectValue(sharedArrayBuffer[key], depth + 1, opts),
        depth + 1,
        opts
      )
    )
  }

  ref.decrement()

  const header = sharedArrayBuffer.constructor.name + ' { '

  return new InspectSequence(header, ' }', ', ', values, ref, depth, opts)
}

function inspectTypedArray(typedArray, ref, depth, opts) {
  if (Buffer.isBuffer(typedArray)) {
    return inspectBuffer(typedArray, ref, depth, opts)
  }

  const {
    maxArrayLength = defaultMaxArrayLength,
    maxTypedArrayLength = maxArrayLength
  } = opts

  ref.increment()

  const values = []

  let remaining = Math.max(maxTypedArrayLength, 0)

  for (let i = 0, n = typedArray.length; i < n; i++) {
    if (remaining-- === 0) {
      values.push(
        new InspectSuspension(typedArray.length - values.length, depth + 1, {
          ...opts,
          breakAlways: true
        })
      )

      break
    }

    values.push(inspectValue(typedArray[i], depth + 1, opts))
  }

  for (const key of binding.getOwnNonIndexPropertyNames(typedArray)) {
    values.push(
      new InspectPair(
        ': ',
        inspectKey(key, depth + 1, opts),
        inspectValue(typedArray[key], depth + 1, opts),
        depth + 1,
        { ...opts, breakAlways: remaining < 0 }
      )
    )
  }

  ref.decrement()

  const header = typedArray.constructor.name + '(' + typedArray.length + ') [ '

  return new InspectSequence(header, ' ]', ', ', values, ref, depth, {
    ...opts,
    tabulate: true
  })
}

function inspectBuffer(buffer, ref, depth, opts) {
  const {
    maxArrayLength = defaultMaxArrayLength,
    maxBufferLength = maxArrayLength
  } = opts

  ref.increment()

  const values = []

  let remaining = Math.max(maxBufferLength, 0)

  for (let i = 0, n = buffer.byteLength; i < n; i++) {
    if (remaining-- === 0) {
      values.push(
        new InspectSuspension(buffer.length - values.length, depth + 1, {
          ...opts,
          breakAlways: true
        })
      )

      break
    }

    values.push(
      new InspectLeaf(
        buffer[i].toString(16).padStart(2, '0'),
        null,
        depth + 1,
        opts
      )
    )
  }

  for (const key of binding.getOwnNonIndexPropertyNames(buffer)) {
    values.push(
      new InspectPair(
        ': ',
        inspectKey(key, depth + 1, opts),
        inspectValue(buffer[key], depth + 1, opts),
        depth + 1,
        { ...opts, breakAlways: remaining < 0 }
      )
    )
  }

  ref.decrement()

  return new InspectSequence('<Buffer ', '>', ' ', values, ref, depth, {
    ...opts,
    tabulate: true
  })
}

function inspectDataView(dataView, ref, depth, opts) {
  ref.increment()

  const values = []

  for (const key of ['byteLength', 'byteOffset', 'buffer']) {
    values.push(
      new InspectPair(
        ': ',
        inspectKey(key, depth + 1, opts),
        inspectValue(dataView[key], depth + 1, opts),
        depth + 1,
        opts
      )
    )
  }

  for (const key of binding.getOwnNonIndexPropertyNames(dataView)) {
    values.push(
      new InspectPair(
        ': ',
        inspectKey(key, depth + 1, opts),
        inspectValue(dataView[key], depth + 1, opts),
        depth + 1,
        opts
      )
    )
  }

  ref.decrement()

  const header = dataView.constructor.name + ' { '

  return new InspectSequence(header, ' }', ', ', values, ref, depth, opts)
}

function inspectFunction(type, fn, depth, opts) {
  if (fn.toString().startsWith('class')) return inspectClass(fn, depth, opts)

  let tag = 'function'

  if (type.isGeneratorFunction()) tag = 'generator ' + tag
  if (type.isAsyncFunction()) tag = 'async ' + tag

  return new InspectLeaf(
    '[' + tag + ' ' + (fn.name ? fn.name : '(anonymous)') + ']',
    styles.special,
    depth,
    opts
  )
}

function inspectClass(ctor, depth, opts) {
  return new InspectLeaf(
    '[class ' + (ctor.name ? ctor.name : '(anonymous)') + ']',
    styles.special,
    depth,
    opts
  )
}

function inspectExternal(external, depth, opts) {
  return new InspectLeaf(
    '[external 0x' + binding.getExternal(external).toString(16) + ']',
    styles.special,
    depth,
    opts
  )
}
{
  "name": "bare-inspect",
  "version": "3.1.4",
  "description": "Inspect objects as strings for debugging",
  "exports": {
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    },
    "./package": "./package.json"
  },
  "files": [
    "index.js",
    "index.d.ts",
    "binding.c",
    "binding.js",
    "CMakeLists.txt",
    "prebuilds"
  ],
  "addon": true,
  "scripts": {
    "test": "prettier . --check && bare test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-inspect.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-inspect/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-inspect#readme",
  "engines": {
    "bare": ">=1.18.0"
  },
  "dependencies": {
    "bare-ansi-escapes": "^2.1.0",
    "bare-type": "^1.0.0"
  },
  "devDependencies": {
    "brittle": "^3.3.2",
    "cmake-bare": "^1.1.6",
    "prettier": "^3.4.1",
    "prettier-config-standard": "^7.0.0"
  }
}
const stream = require('streamx')
const { ReadableStream, WritableStream } = require('./web')

const defaultEncoding = 'utf8'

module.exports = exports = stream.Stream

exports.pipeline = stream.pipeline

exports.isStream = stream.isStream
exports.isEnding = stream.isEnding
exports.isEnded = stream.isEnded
exports.isFinishing = stream.isFinishing
exports.isFinished = stream.isFinished
exports.isDisturbed = stream.isDisturbed

exports.isErrored = function isErrored(stream) {
  return exports.getStreamError(stream) !== null
}

exports.isReadable = function isReadable(stream) {
  return stream.readable && !stream.destroying && !exports.isEnded(stream)
}

exports.isWritable = function isWritable(stream) {
  return stream.writable && !stream.destroying && !exports.isFinishing(stream)
}

exports.getStreamError = stream.getStreamError

exports.addAbortSignal = function addAbortSignal(signal, stream) {
  function onAbort() {
    stream.destroy(signal.reason)
  }

  if (signal.aborted) onAbort()
  else signal.addEventListener('abort', onAbort)

  return stream
}

exports.Stream = exports

exports.Readable = class Readable extends stream.Readable {
  constructor(opts = {}) {
    super({
      ...opts,
      byteLength: null,
      byteLengthReadable: null,
      map: null,
      mapReadable: null
    })

    if (this._construct) this._open = this._construct

    if (this._read !== stream.Readable.prototype._read) {
      this._read = read.bind(this, this._read)
    }

    if (this._destroy !== stream.Stream.prototype._destroy) {
      this._destroy = destroy.bind(this, this._destroy)
    }
  }

  get closed() {
    return !exports.isReadable(this)
  }

  get errored() {
    return stream.getStreamError(this)
  }

  push(chunk, encoding) {
    if (typeof chunk === 'string') {
      chunk = Buffer.from(chunk, encoding || defaultEncoding)
    }

    return super.push(chunk)
  }

  unshift(chunk, encoding) {
    if (typeof chunk === 'string') {
      chunk = Buffer.from(chunk, encoding || defaultEncoding)
    }

    super.unshift(chunk)
  }

  static fromWeb(readableStream, opts = {}) {
    const stream = readableStream._stream

    if (opts.encoding) stream.setEncoding(opts.encoding)
    if (opts.signal) exports.addAbortSignal(opts.signal, stream)

    return stream
  }

  static toWeb(readable, opts = {}) {
    return new ReadableStream(readable, opts.strategy)
  }

  async [Symbol.asyncDispose]() {
    if (!this.destroyed) this.destroy()

    await new Promise((resolve) => exports.finished(this, resolve))
  }
}

exports.Writable = class Writable extends stream.Writable {
  constructor(opts = {}) {
    super({
      ...opts,
      byteLength: null,
      byteLengthWritable,
      map: null,
      mapWritable: null
    })

    if (this._construct) this._open = this._construct

    if (this._write !== stream.Writable.prototype._write) {
      this._write = write.bind(this, this._write)
    }

    if (this._destroy !== stream.Stream.prototype._destroy) {
      this._destroy = destroy.bind(this, this._destroy)
    }
  }

  get closed() {
    return !exports.isWritable(this)
  }

  get errored() {
    return stream.getStreamError(this)
  }

  write(chunk, encoding, cb) {
    if (typeof encoding === 'function') {
      cb = encoding
      encoding = null
    }

    if (typeof chunk === 'string') {
      encoding = encoding || defaultEncoding
      chunk = Buffer.from(chunk, encoding)
    } else {
      encoding = 'buffer'
    }

    const result = super.write({ chunk, encoding })

    if (cb) stream.Writable.drained(this).then(() => cb(null), cb)

    return result
  }

  end(chunk, encoding, cb) {
    if (typeof chunk === 'function') {
      cb = chunk
      chunk = null
    } else if (typeof encoding === 'function') {
      cb = encoding
      encoding = null
    }

    if (typeof chunk === 'string') {
      encoding = encoding || defaultEncoding
      chunk = Buffer.from(chunk, encoding || defaultEncoding)
    } else {
      encoding = 'buffer'
    }

    const result =
      chunk !== undefined && chunk !== null ? super.end({ chunk, encoding }) : super.end()

    if (cb) this.once('finish', () => cb(null))

    return result
  }

  static fromWeb(writableStream, opts = {}) {
    const stream = writableStream._stream

    if (opts.signal) exports.addAbortSignal(opts.signal, stream)

    return stream
  }

  static toWeb(writable) {
    return new WritableStream(writable)
  }

  async [Symbol.asyncDispose]() {
    if (!this.destroyed) this.destroy()

    await new Promise((resolve) => exports.finished(this, resolve))
  }
}

exports.Duplex = class Duplex extends stream.Duplex {
  constructor(opts = {}) {
    super({
      ...opts,
      byteLength: null,
      byteLengthReadable: null,
      byteLengthWritable,
      map: null,
      mapReadable: null,
      mapWritable: null
    })

    if (this._construct) this._open = this._construct

    if (this._read !== stream.Readable.prototype._read) {
      this._read = read.bind(this, this._read)
    }

    if (this._write !== stream.Duplex.prototype._write) {
      this._write = write.bind(this, this._write)
    }

    if (this._destroy !== stream.Stream.prototype._destroy) {
      this._destroy = destroy.bind(this, this._destroy)
    }
  }

  push(chunk, encoding) {
    if (typeof chunk === 'string') {
      chunk = Buffer.from(chunk, encoding || defaultEncoding)
    }

    return super.push(chunk)
  }

  unshift(chunk, encoding) {
    if (typeof chunk === 'string') {
      chunk = Buffer.from(chunk, encoding || defaultEncoding)
    }

    super.unshift(chunk)
  }

  write(chunk, encoding, cb) {
    if (typeof encoding === 'function') {
      cb = encoding
      encoding = null
    }

    if (typeof chunk === 'string') {
      encoding = encoding || defaultEncoding
      chunk = Buffer.from(chunk, encoding)
    } else {
      encoding = 'buffer'
    }

    const result = super.write({ chunk, encoding })

    if (cb) stream.Writable.drained(this).then(() => cb(null), cb)

    return result
  }

  end(chunk, encoding, cb) {
    if (typeof chunk === 'function') {
      cb = chunk
      chunk = null
    } else if (typeof encoding === 'function') {
      cb = encoding
      encoding = null
    }

    if (typeof chunk === 'string') {
      encoding = encoding || defaultEncoding
      chunk = Buffer.from(chunk, encoding)
    } else {
      encoding = 'buffer'
    }

    const result =
      chunk !== undefined && chunk !== null ? super.end({ chunk, encoding }) : super.end()

    if (cb) this.once('finish', () => cb(null))

    return result
  }

  static fromWeb({ readable: readableStream, writable: writableStream }, opts) {
    const readable = exports.Readable.fromWeb(readableStream, opts)
    const writable = exports.Readable.fromWeb(writableStream, opts)

    const duplex = new exports.Duplex({
      write(data, encoding, cb) {
        writable.write(data, encoding, cb)
      }
    })

    readable
      .on('data', (data) => duplex.push(data))
      .on('end', () => duplex.push(null))
      .on('error', (err) => duplex.destroy(err))

    writable.on('finish', () => duplex.end()).on('error', (err) => duplex.destroy(err))

    return duplex
  }

  static toWeb(duplex) {
    const readableStream = exports.Readable.toWeb(duplex)
    const writableStream = exports.Writable.toWeb(duplex)

    return { readable: readableStream, writable: writableStream }
  }
}

class DuplexSide extends exports.Duplex {
  constructor(opts) {
    super(opts)

    this._otherSide = null
    this._cb = null
  }

  _read() {
    const cb = this._cb
    if (!cb) return

    this._cb = null
    cb()
  }

  _write(chunk, encoding, cb) {
    this._otherSide.push(chunk, encoding)
    this._otherSide._cb = cb
  }

  _final(cb) {
    this._otherSide.on('end', cb)
    this._otherSide.push(null)
  }
}

exports.duplexPair = function duplexPair(opts) {
  const sideA = new DuplexSide(opts)
  const sideB = new DuplexSide(opts)

  sideA._otherSide = sideB
  sideB._otherSide = sideA

  return [sideA, sideB]
}

exports.Transform = class Transform extends stream.Transform {
  constructor(opts = {}) {
    super({
      ...opts,
      byteLength: null,
      byteLengthReadable: null,
      byteLengthWritable,
      map: null,
      mapReadable: null,
      mapWritable: null
    })

    if (this._transform !== stream.Transform.prototype._transform) {
      this._transform = transform.bind(this, this._transform)
    } else {
      this._transform = passthrough
    }
  }

  push(chunk, encoding) {
    if (typeof chunk === 'string') {
      chunk = Buffer.from(chunk, encoding || defaultEncoding)
    }

    return super.push(chunk)
  }

  unshift(chunk, encoding) {
    if (typeof chunk === 'string') {
      chunk = Buffer.from(chunk, encoding || defaultEncoding)
    }

    super.unshift(chunk)
  }

  write(chunk, encoding, cb) {
    if (typeof encoding === 'function') {
      cb = encoding
      encoding = null
    }

    if (typeof chunk === 'string') {
      encoding = encoding || defaultEncoding
      chunk = Buffer.from(chunk, encoding)
    } else {
      encoding = 'buffer'
    }

    const result = super.write({ chunk, encoding })

    if (cb) stream.Writable.drained(this).then(() => cb(null), cb)

    return result
  }

  end(chunk, encoding, cb) {
    if (typeof chunk === 'function') {
      cb = chunk
      chunk = null
    } else if (typeof encoding === 'function') {
      cb = encoding
      encoding = null
    }

    if (typeof chunk === 'string') {
      encoding = encoding || defaultEncoding
      chunk = Buffer.from(chunk, encoding)
    } else {
      encoding = 'buffer'
    }

    const result =
      chunk !== undefined && chunk !== null ? super.end({ chunk, encoding }) : super.end()

    if (cb) this.once('finish', () => cb(null))

    return result
  }
}

exports.PassThrough = class PassThrough extends exports.Transform {}

exports.finished = function finished(stream, opts, cb) {
  if (typeof opts === 'function') {
    cb = opts
    opts = {}
  }

  if (!opts) opts = {}

  const { cleanup = false } = opts

  const done = () => {
    cb(exports.getStreamError(stream, { all: true }))

    if (cleanup) detach()
  }

  const detach = () => {
    stream.off('close', done)
    stream.off('error', noop)
  }

  if (stream.destroyed) {
    done()
  } else {
    stream.on('close', done)
    stream.on('error', noop)
  }

  return detach
}

function read(read, cb) {
  read.call(this, 65536)

  cb(null)
}

function write(write, data, cb) {
  write.call(this, data.chunk, data.encoding, cb)
}

function transform(transform, data, cb) {
  transform.call(this, data.chunk, data.encoding, cb)
}

function destroy(destroy, cb) {
  destroy.call(this, exports.getStreamError(this), cb)
}

function passthrough(data, cb) {
  cb(null, data.chunk)
}

function byteLengthWritable(data) {
  return data.chunk.byteLength
}

function noop() {}
{
  "name": "bare-stream",
  "version": "2.13.0",
  "description": "Streaming data for JavaScript",
  "exports": {
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    },
    "./package": "./package.json",
    "./promises": "./promises.js",
    "./web": {
      "types": "./web.d.ts",
      "default": "./web.js"
    },
    "./global": "./global.js"
  },
  "files": [
    "index.js",
    "index.d.ts",
    "promises.js",
    "web.js",
    "web.d.ts",
    "global.js"
  ],
  "scripts": {
    "format": "prettier --write . && lunte --fix",
    "lint": "prettier --check . && lunte",
    "test": "brittle-bare --coverage test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-stream.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-stream/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-stream#readme",
  "dependencies": {
    "streamx": "^2.25.0",
    "teex": "^1.0.1"
  },
  "devDependencies": {
    "bare-abort-controller": "^1.1.0",
    "bare-buffer": "^3.0.0",
    "bare-events": "^2.5.4",
    "brittle": "^3.5.2",
    "lunte": "^1.6.0",
    "prettier": "^3.3.3",
    "prettier-config-holepunch": "^2.0.0"
  },
  "peerDependencies": {
    "bare-abort-controller": "*",
    "bare-buffer": "*",
    "bare-events": "*"
  },
  "peerDependenciesMeta": {
    "bare-abort-controller": {
      "optional": true
    },
    "bare-buffer": {
      "optional": true
    },
    "bare-events": {
      "optional": true
    }
  }
}
const { Readable, Writable, Transform, getStreamError, isStreamx, isDisturbed } = require('streamx')
const tee = require('teex')

const readableKind = Symbol.for('bare.stream.readable.kind')
const writableKind = Symbol.for('bare.stream.writable.kind')
const transformKind = Symbol.for('bare.stream.transform.kind')

// https://streams.spec.whatwg.org/#readablestreamdefaultreader
exports.ReadableStreamDefaultReader = class ReadableStreamDefaultReader {
  constructor(stream) {
    this._stream = stream
    this._stream._stream.once('close', onclose).once('error', onerror)

    const closed = Promise.withResolvers()

    // Avoid unhandled exceptions
    closed.promise.catch(noop)

    this._closed = closed

    function onclose() {
      closed.resolve()
    }

    function onerror(err) {
      closed.reject(err)
    }
  }

  get closed() {
    return this._closed.promise
  }

  read() {
    const stream = this._stream._stream

    return new Promise((resolve, reject) => {
      const err = getStreamError(stream)

      if (err) return reject(err)

      if (stream.destroyed) {
        return resolve({ value: undefined, done: true })
      }

      const value = stream.read()

      if (value !== null) {
        return resolve({ value, done: false })
      }

      stream.once('readable', onreadable).once('close', onclose).once('error', onerror)

      function onreadable() {
        const value = stream.read()

        ondone(null, value === null ? { value: undefined, done: true } : { value, done: false })
      }

      function onclose() {
        ondone(null, { value: undefined, done: true })
      }

      function onerror(err) {
        ondone(err, null)
      }

      function ondone(err, value) {
        stream.off('readable', onreadable).off('close', onclose).off('error', onerror)

        if (err) reject(err)
        else resolve(value)
      }
    })
  }

  releaseLock() {
    this._closed.reject(new TypeError('Reader was released'))
    this._stream._releaseLock()
    this._stream = null
  }

  cancel(reason = new TypeError('Stream was cancelled')) {
    const stream = this._stream._stream

    if (stream.destroyed) return Promise.resolve()

    return new Promise((resolve) =>
      stream.once('close', resolve).once('error', noop).destroy(reason)
    )
  }
}

// https://streams.spec.whatwg.org/#readablestreamdefaultcontroller
exports.ReadableStreamDefaultController = class ReadableStreamDefaultController {
  constructor(stream) {
    this._stream = stream
  }

  get desiredSize() {
    const stream = this._stream._stream

    return stream._readableState.highWaterMark - stream._readableState.buffered
  }

  enqueue(data) {
    this._stream._stream.push(data)
  }

  close() {
    this._stream._stream.push(null)
  }

  error(err) {
    this._stream._stream.destroy(err)
  }
}

// https://streams.spec.whatwg.org/#readablestream
class ReadableStream {
  static get [readableKind]() {
    return 0 // Compatibility version
  }

  static from(iterable) {
    return new ReadableStream(Readable.from(iterable))
  }

  constructor(underlyingSource = {}, queuingStrategy) {
    if (isStreamx(underlyingSource)) {
      this._stream = underlyingSource
    } else {
      if (queuingStrategy === undefined) {
        queuingStrategy = new exports.CountQueuingStrategy()
      }

      const { start, pull, cancel } = underlyingSource
      const { highWaterMark = 1, size = defaultSize } = queuingStrategy

      this._stream = new Readable({ highWaterMark, byteLength: size })

      const controller = new exports.ReadableStreamDefaultController(this)

      if (start) {
        this._stream._open = this._open.bind(this, start.call(this, controller))
      }

      if (pull) {
        this._stream._read = this._read.bind(this, pull.bind(this, controller))
      }

      if (cancel) {
        this._stream.once('error', cancel)
      }
    }

    this._reader = null
  }

  get [readableKind]() {
    return ReadableStream[readableKind]
  }

  get locked() {
    return this._reader !== null
  }

  getReader() {
    if (this.locked) throw new TypeError('Stream is locked')

    this._reader = new exports.ReadableStreamDefaultReader(this)

    return this._reader
  }

  cancel(reason = new TypeError('Stream was cancelled')) {
    const stream = this._stream

    if (stream.destroyed) return Promise.resolve()

    if (this.locked) return Promise.reject(new TypeError('Stream is locked'))

    return new Promise((resolve) =>
      stream.once('close', resolve).once('error', noop).destroy(reason)
    )
  }

  tee() {
    const [a, b] = tee(this._stream)

    return [new ReadableStream(a), new ReadableStream(b)]
  }

  pipeTo(destination) {
    return new Promise((resolve, reject) =>
      this._stream.pipe(destination._stream, (err) => {
        err ? reject(err) : resolve()
      })
    )
  }

  [Symbol.asyncIterator]() {
    return this._stream[Symbol.asyncIterator]()
  }

  _releaseLock() {
    this._reader = null
  }

  async _open(starting, cb) {
    let err = null
    try {
      await starting
    } catch (e) {
      err = e
    }
    cb(err)
  }

  async _read(pull, cb) {
    let err = null
    try {
      await pull()
    } catch (e) {
      err = e
    }
    cb(err)
  }
}

function defaultSize() {
  return 1
}

exports.ReadableStream = ReadableStream

// https://streams.spec.whatwg.org/#countqueuingstrategy
exports.CountQueuingStrategy = class CountQueuingStrategy {
  constructor(opts = {}) {
    const { highWaterMark = 1 } = opts

    this.highWaterMark = highWaterMark
  }

  size(chunk) {
    return 1
  }
}

// https://streams.spec.whatwg.org/#bytelengthqueuingstrategy
exports.ByteLengthQueuingStrategy = class ByteLengthQueuingStrategy {
  constructor(opts = {}) {
    const { highWaterMark = 16384 } = opts

    this.highWaterMark = highWaterMark
  }

  size(chunk) {
    return chunk.byteLength
  }
}

exports.isReadableStream = function isReadableStream(value) {
  if (value instanceof ReadableStream) return true

  return (
    typeof value === 'object' &&
    value !== null &&
    value[readableKind] === ReadableStream[readableKind]
  )
}

// https://streams.spec.whatwg.org/#readablestream-errored
exports.isReadableStreamErrored = function isReadableStreamErrored(stream) {
  return getStreamError(stream._stream) !== null
}

// https://streams.spec.whatwg.org/#is-readable-stream-disturbed
exports.isReadableStreamDisturbed = function isReadableStreamDisturbed(stream) {
  return isDisturbed(stream._stream)
}

// https://streams.spec.whatwg.org/#writablestreamdefaultwriter
exports.WritableStreamDefaultWriter = class WritableStreamDefaultWriter {
  constructor(stream) {
    this._stream = stream
    this._stream._stream.once('close', onclose).once('error', onerror)

    const closed = Promise.withResolvers()

    // Avoid unhandled exceptions
    closed.promise.catch(noop)

    this._closed = closed

    function onclose() {
      closed.resolve()
    }

    function onerror(err) {
      closed.reject(err)
    }
  }

  get desiredSize() {
    const stream = this._stream._stream

    return stream._writableState.highWaterMark - stream._writableState.buffered
  }

  get closed() {
    return this._closed.promise
  }

  get ready() {
    const stream = this._stream._stream

    if (getStreamError(stream)) return Promise.reject()

    return Writable.drained(stream).then()
  }

  async write(chunk) {
    const stream = this._stream._stream

    let err = getStreamError(stream)
    if (err) return Promise.reject(err)

    stream.write(chunk)

    await Writable.drained(stream)

    err = getStreamError(stream)
    if (err) return Promise.reject(err)
  }

  releaseLock() {
    this._closed.reject(new TypeError('Writer was released'))
    this._stream._releaseLock()
    this._stream = null
  }

  close() {
    const stream = this._stream._stream

    if (stream.destroyed) return Promise.resolve()

    return new Promise((resolve) => stream.once('close', resolve).end())
  }

  abort(reason = new TypeError('Stream was aborted')) {
    const stream = this._stream._stream

    if (stream.destroyed) return Promise.resolve()

    return new Promise((resolve) => stream.once('close', resolve).destroy(reason))
  }
}

// https://streams.spec.whatwg.org/#writablestreamdefaultcontroller
exports.WritableStreamDefaultController = class WritableStreamDefaultController {
  constructor(stream) {
    this._stream = stream
  }

  error(err) {
    this._stream._stream.destroy(err)
  }
}

// https://streams.spec.whatwg.org/#writablestream
class WritableStream {
  static get [writableKind]() {
    return 0 // Compatibility version
  }

  constructor(underlyingSink = {}, queuingStrategy = {}) {
    if (isStreamx(underlyingSink)) {
      this._stream = underlyingSink
    } else {
      if (queuingStrategy === undefined) {
        queuingStrategy = new exports.CountQueuingStrategy()
      }

      const { start, write, close, abort } = underlyingSink
      const { highWaterMark = 1, size = defaultSize } = queuingStrategy

      this._stream = new Writable({ highWaterMark, byteLength: size })

      this._controller = new exports.WritableStreamDefaultController(this)

      if (start) {
        this._stream._open = this._open.bind(this, start.call(this, this._controller))
      }

      if (write) {
        this._stream._write = this._write.bind(this, write)
      }

      if (close) {
        this._stream._destroy = this._destroy.bind(this, close.call(this))
      }

      if (abort) {
        this._stream.once('error', abort)
      }
    }

    this._writer = null
  }

  get [writableKind]() {
    return WritableStream[writableKind]
  }

  get locked() {
    return this._writer !== null
  }

  getWriter() {
    if (this.locked) throw new TypeError('Stream is locked')

    this._writer = new exports.WritableStreamDefaultWriter(this)

    return this._writer
  }

  abort(reason = new TypeError('Stream was aborted')) {
    if (this._stream.destroyed) return Promise.resolve()

    if (this.locked) return Promise.reject(new TypeError('Stream is locked'))

    return new Promise((resolve) => this._stream.once('close', resolve).destroy(reason))
  }

  close() {
    if (this._stream.destroyed) return Promise.resolve()

    if (this.locked) return Promise.reject(new TypeError('Stream is locked'))

    return new Promise((resolve) => this._stream.once('close', resolve).end())
  }

  _releaseLock() {
    this._writer = null
  }

  async _open(starting, cb) {
    let err = null
    try {
      await starting
    } catch (e) {
      err = e
    }
    cb(err)
  }

  async _write(write, data, cb) {
    let err = null
    try {
      await write(data, this._controller)
    } catch (e) {
      err = e
    }
    cb(err)
  }

  async _destroy(closing, cb) {
    let err = null
    try {
      await closing
    } catch (e) {
      err = e
    }
    cb(err)
  }
}

exports.WritableStream = WritableStream

exports.isWritableStream = function isWritableStream(value) {
  if (value instanceof WritableStream) return true

  return (
    typeof value === 'object' &&
    value !== null &&
    value[writableKind] === WritableStream[writableKind]
  )
}

// https://streams.spec.whatwg.org/#transformstreamdefaultcontroller
exports.TransformStreamDefaultController = class TransformStreamDefaultController {
  constructor(stream) {
    this._stream = stream
  }

  get desiredSize() {
    const stream = this._stream._stream

    return stream._readableState.highWaterMark - stream._readableState.buffered
  }

  enqueue(data) {
    this._stream._stream.push(data)
  }

  error(err) {
    this._stream._stream.destroy(err)
  }

  terminate() {
    const stream = this._stream._stream

    stream.push(null)
    stream.destroy(new TypeError('Stream has been terminated'))
  }
}

// https://streams.spec.whatwg.org/#transformstream
class TransformStream {
  static get [transformKind]() {
    return 0 // Compatibility version
  }

  constructor(transformer = {}, writableStrategy = {}, readableStrategy = {}) {
    if (isStreamx(transformer)) {
      this._stream = transformer
    } else {
      const { start, transform, flush } = transformer

      this._stream = new Transform({ ...writableStrategy, ...readableStrategy })

      this._controller = new exports.TransformStreamDefaultController(this)

      if (start) {
        this._stream._open = this._open.bind(this, start.call(this, this._controller))
      }

      if (transform) {
        this._stream._write = this._transform.bind(this, transform)
      }

      if (flush) {
        this._stream._flush = this._flush.bind(this, flush.call(this, this._controller))
      }
    }

    this._writable = new WritableStream(this._stream)
    this._readable = new ReadableStream(this._stream)
  }

  get [transformKind]() {
    return TransformStream[transformKind]
  }

  get writable() {
    return this._writable
  }

  get readable() {
    return this._readable
  }

  async _open(starting, cb) {
    let err = null
    try {
      await starting
    } catch (e) {
      err = e
    }
    cb(err)
  }

  async _transform(transform, data, cb) {
    let err = null
    try {
      await transform(data, this._controller)
    } catch (e) {
      err = e
    }
    cb(err)
  }

  async _flush(flush, cb) {
    let err = null
    try {
      await flush
    } catch (e) {
      err = e
    }
    cb(err)
  }
}

exports.TransformStream = TransformStream

exports.isTransformStream = function isTransformStream(value) {
  if (value instanceof TransformStream) return true

  return (
    typeof value === 'object' &&
    value !== null &&
    value[transformKind] === TransformStream[transformKind]
  )
}

function noop() {}
module.exports = require.addon()
const binding = require('./binding')

const t = binding.constants

class Type {
  constructor(type) {
    this._type = type
  }

  isUndefined() {
    return this._type === t.UNDEFINED
  }

  isNull() {
    return this._type === t.NULL
  }

  isBoolean() {
    return this._type === t.BOOLEAN
  }

  isNumber() {
    return (this._type & 0xff) === t.NUMBER
  }

  isInt32() {
    return (this._type & (0xff | t.INT32)) === (t.NUMBER | t.INT32)
  }

  isUint32() {
    return (this._type & (0xff | t.UINT32)) === (t.NUMBER | t.UINT32)
  }

  isString() {
    return this._type === t.STRING
  }

  isSymbol() {
    return this._type === t.SYMBOL
  }

  isObject() {
    return (this._type & 0xff) === t.OBJECT
  }

  isArray() {
    return this._type === (t.OBJECT | t.ARRAY)
  }

  isArguments() {
    return this._type === (t.OBJECT | t.ARGUMENTS)
  }

  isDate() {
    return this._type === (t.OBJECT | t.DATE)
  }

  isRegExp() {
    return this._type === (t.OBJECT | t.REGEXP)
  }

  isError() {
    return this._type === (t.OBJECT | t.ERROR)
  }

  isPromise() {
    return this._type === (t.OBJECT | t.PROMISE)
  }

  isProxy() {
    return this._type === (t.OBJECT | t.PROXY)
  }

  isGenerator() {
    return this._type === (t.OBJECT | t.GENERATOR)
  }

  isMap() {
    return this._type === (t.OBJECT | t.MAP)
  }

  isSet() {
    return this._type === (t.OBJECT | t.SET)
  }

  isWeakMap() {
    return this._type === (t.OBJECT | t.WEAK_MAP)
  }

  isWeakSet() {
    return this._type === (t.OBJECT | t.WEAK_SET)
  }

  isWeakRef() {
    return this._type === (t.OBJECT | t.WEAK_REF)
  }

  isArrayBuffer() {
    return this._type === (t.OBJECT | t.ARRAYBUFFER)
  }

  isSharedArrayBuffer() {
    return this._type === (t.OBJECT | t.SHAREDARRAYBUFFER)
  }

  isTypedArray() {
    return (this._type & 0xffff) === (t.OBJECT | t.TYPEDARRAY)
  }

  isInt8Array() {
    return this._type === (t.OBJECT | t.TYPEDARRAY | t.INT8ARRAY)
  }

  isUint8Array() {
    return this._type === (t.OBJECT | t.TYPEDARRAY | t.UINT8ARRAY)
  }

  isUint8ClampedArray() {
    return this._type === (t.OBJECT | t.TYPEDARRAY | t.UINT8CLAMPEDARRAY)
  }

  isInt16Array() {
    return this._type === (t.OBJECT | t.TYPEDARRAY | t.INT16ARRAY)
  }

  isUint16Array() {
    return this._type === (t.OBJECT | t.TYPEDARRAY | t.UINT16ARRAY)
  }

  isInt32Array() {
    return this._type === (t.OBJECT | t.TYPEDARRAY | t.INT32ARRAY)
  }

  isUint32Array() {
    return this._type === (t.OBJECT | t.TYPEDARRAY | t.UINT32ARRAY)
  }

  isFloat16Array() {
    return this._type === (t.OBJECT | t.TYPEDARRAY | t.FLOAT16ARRAY)
  }

  isFloat32Array() {
    return this._type === (t.OBJECT | t.TYPEDARRAY | t.FLOAT32ARRAY)
  }

  isFloat64Array() {
    return this._type === (t.OBJECT | t.TYPEDARRAY | t.FLOAT64ARRAY)
  }

  isBigInt64Array() {
    return this._type === (t.OBJECT | t.TYPEDARRAY | t.BIGINT64ARRAY)
  }

  isBigUint64Array() {
    return this._type === (t.OBJECT | t.TYPEDARRAY | t.BIGUINT64ARRAY)
  }

  isDataView() {
    return this._type === (t.OBJECT | t.DATAVIEW)
  }

  isModuleNamespace() {
    return this._type === (t.OBJECT | t.MODULE_NAMESPACE)
  }

  isFunction() {
    return (this._type & 0xff) === t.FUNCTION
  }

  isAsyncFunction() {
    return (
      (this._type & (0xff | t.ASYNC_FUNCTION)) ===
      (t.FUNCTION | t.ASYNC_FUNCTION)
    )
  }

  isGeneratorFunction() {
    return (
      (this._type & (0xff | t.GENERATOR_FUNCTION)) ===
      (t.FUNCTION | t.GENERATOR_FUNCTION)
    )
  }

  isExternal() {
    return this._type === t.EXTERNAL
  }

  isBigInt() {
    return this._type === t.BIGINT
  }
}

module.exports = exports = function type(value) {
  switch (typeof value) {
    case 'undefined':
      return new Type(t.UNDEFINED)
    case 'boolean':
      return new Type(t.BOOLEAN)
    case 'number':
      return new Type(
        Number.isSafeInteger(value) ? binding.type(value) : t.NUMBER
      )
    case 'string':
      return new Type(t.STRING)
    case 'symbol':
      return new Type(t.SYMBOL)
    case 'object':
      return new Type(value === null ? t.NULL : binding.type(value))
    case 'function':
      return new Type(binding.type(value))
    case 'bigint':
      return new Type(t.BIGINT)
  }
}

exports.createTag = function createTag(...components) {
  const tag = new Uint32Array(4)
  for (let i = 0; i < 4; i++) tag[i] = components[i] || 0
  return tag
}

exports.addTag = function addTag(object, tag) {
  binding.addTag(object, tag)
}

exports.checkTag = function checkTag(object, tag) {
  return binding.checkTag(object, tag)
}
{
  "name": "bare-type",
  "version": "1.1.0",
  "description": "Cross-realm type predicates for Bare",
  "exports": {
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    },
    "./package": "./package.json"
  },
  "files": [
    "index.js",
    "index.d.ts",
    "binding.c",
    "binding.js",
    "CMakeLists.txt",
    "prebuilds"
  ],
  "addon": true,
  "scripts": {
    "test": "prettier . --check && bare test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-type.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-type/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-type#readme",
  "engines": {
    "bare": ">=1.2.0"
  },
  "devDependencies": {
    "brittle": "^3.4.0",
    "cmake-bare": "^1.1.6",
    "prettier": "^3.4.1",
    "prettier-config-standard": "^7.0.0"
  }
}
module.exports = require('bare-events')
{
  "name": "events-universal",
  "version": "1.0.1",
  "description": "Universal wrapper for the Node.js events module",
  "exports": {
    "./package": "./package.json",
    ".": {
      "bare": "./bare.js",
      "react-native": "./react-native.js",
      "default": "./default.js"
    }
  },
  "files": [
    "index.js",
    "default.js",
    "bare.js",
    "react-native.js"
  ],
  "scripts": {
    "test": "prettier . --check"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/events-universal.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/events-universal/issues"
  },
  "homepage": "https://github.com/holepunchto/events-universal#readme",
  "dependencies": {
    "bare-events": "^2.7.0"
  },
  "devDependencies": {
    "prettier": "^3.4.2",
    "prettier-config-holepunch": "^1.0.0"
  }
}
module.exports = class FixedFIFO {
  constructor (hwm) {
    if (!(hwm > 0) || ((hwm - 1) & hwm) !== 0) throw new Error('Max size for a FixedFIFO should be a power of two')
    this.buffer = new Array(hwm)
    this.mask = hwm - 1
    this.top = 0
    this.btm = 0
    this.next = null
  }

  clear () {
    this.top = this.btm = 0
    this.next = null
    this.buffer.fill(undefined)
  }

  push (data) {
    if (this.buffer[this.top] !== undefined) return false
    this.buffer[this.top] = data
    this.top = (this.top + 1) & this.mask
    return true
  }

  shift () {
    const last = this.buffer[this.btm]
    if (last === undefined) return undefined
    this.buffer[this.btm] = undefined
    this.btm = (this.btm + 1) & this.mask
    return last
  }

  peek () {
    return this.buffer[this.btm]
  }

  isEmpty () {
    return this.buffer[this.btm] === undefined
  }
}
const FixedFIFO = require('./fixed-size')

module.exports = class FastFIFO {
  constructor (hwm) {
    this.hwm = hwm || 16
    this.head = new FixedFIFO(this.hwm)
    this.tail = this.head
    this.length = 0
  }

  clear () {
    this.head = this.tail
    this.head.clear()
    this.length = 0
  }

  push (val) {
    this.length++
    if (!this.head.push(val)) {
      const prev = this.head
      this.head = prev.next = new FixedFIFO(2 * this.head.buffer.length)
      this.head.push(val)
    }
  }

  shift () {
    if (this.length !== 0) this.length--
    const val = this.tail.shift()
    if (val === undefined && this.tail.next) {
      const next = this.tail.next
      this.tail.next = null
      this.tail = next
      return this.tail.shift()
    }

    return val
  }

  peek () {
    const val = this.tail.peek()
    if (val === undefined && this.tail.next) return this.tail.next.peek()
    return val
  }

  isEmpty () {
    return this.length === 0
  }
}
{
  "name": "fast-fifo",
  "version": "1.3.2",
  "description": "A fast fifo implementation similar to the one powering nextTick in Node.js core",
  "main": "index.js",
  "files": [
    "./index.js",
    "./fixed-size.js"
  ],
  "dependencies": {},
  "devDependencies": {
    "standard": "^17.1.0",
    "brittle": "^3.3.2"
  },
  "scripts": {
    "test": "standard && brittle test.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/mafintosh/fast-fifo.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/mafintosh/fast-fifo/issues"
  },
  "homepage": "https://github.com/mafintosh/fast-fifo"
}
module.exports = require.addon.bind(require)
{
  "name": "require-addon",
  "version": "1.2.0",
  "description": "Import native addons across JavaScript runtimes",
  "exports": {
    "./package": "./package.json",
    ".": {
      "bare": "./lib/bare.js",
      "node": "./lib/node.js",
      "default": "./lib/default.js"
    }
  },
  "imports": {
    "fs": {
      "bare": "bare-fs",
      "default": "fs"
    },
    "path": {
      "bare": "bare-path",
      "default": "path"
    },
    "url": {
      "bare": "bare-url",
      "default": "url"
    }
  },
  "files": [
    "lib"
  ],
  "scripts": {
    "test": "npm run lint && npm run test:bare && npm run test:node",
    "test:bare": "bare test.js",
    "test:node": "node test.js",
    "lint": "prettier --check .",
    "format": "prettier --write ."
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/require-addon.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/require-addon/issues"
  },
  "homepage": "https://github.com/holepunchto/require-addon#readme",
  "engines": {
    "bare": ">=1.10.0"
  },
  "dependencies": {
    "bare-addon-resolve": "^1.3.0"
  },
  "devDependencies": {
    "bare-bundle": "^1.8.1",
    "bare-bundle-evaluate": "^1.1.0",
    "bare-fs": "^4.0.0",
    "bare-path": "^3.0.0",
    "bare-url": "^2.1.0",
    "brittle": "^3.7.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^2.0.0"
  }
}
require.addon = require('require-addon')
module.exports = require.addon('.', __filename)
const assert = require('assert')
const binding = require('./binding')
const { isNode } = require('which-runtime')

const OPTIONAL = Buffer.from(new ArrayBuffer(0))

module.exports = exports = { ...binding }

// memory

exports.sodium_memzero = function (buf) {
  assert(ArrayBuffer.isView(buf), 'buf must be a typed array')

  binding.sodium_memzero(buf)
}

exports.sodium_mlock = function (buf) {
  assert(ArrayBuffer.isView(buf), 'buf must be a typed array')

  const res = binding.sodium_mlock(buf)

  if (res !== 0) throw new Error('memory lock failed')
}

exports.sodium_munlock = function (buf) {
  assert(ArrayBuffer.isView(buf), 'buf must be a typed array')

  const res = binding.sodium_munlock(buf)

  if (res !== 0) throw new Error('memory unlock failed')
}

exports.sodium_malloc = function (size) {
  assert(size >= 0, 'invalid size')

  const buf = Buffer.from(binding.sodium_malloc(size))
  buf.secure = true
  return buf
}

exports.sodium_free = function (buf) {
  if (!buf || !buf.secure) return

  binding.sodium_free(buf.buffer)
}

exports.sodium_mprotect_noaccess = function (buf) {
  const res = binding.sodium_mprotect_noaccess(buf.buffer)

  if (res !== 0) throw new Error('failed to lock buffer')
}

exports.sodium_mprotect_readonly = function (buf) {
  const res = binding.sodium_mprotect_readonly(buf.buffer)

  if (res !== 0) throw new Error('failed to unlock buffer')
}

exports.sodium_mprotect_readwrite = function (buf) {
  const res = binding.sodium_mprotect_readwrite(buf.buffer)

  if (res !== 0) throw new Error('failed to unlock buffer')
}

// crypto_randombytes

exports.randombytes_buf = function (buffer) {
  assert(ArrayBuffer.isView(buffer), 'buffer must be a typed array')

  binding.randombytes_buf(buffer.buffer, buffer.byteOffset, buffer.byteLength)
}

exports.randombytes_buf_deterministic = function (buffer, seed) {
  assert(ArrayBuffer.isView(buffer), 'buffer must be a typed array')
  assert(ArrayBuffer.isView(seed), 'seed must be a typed array')
  assert(
    seed.byteLength === binding.randombytes_SEEDBYTES,
    "seed must be 'randombytes_SEEDBYTES' bytes"
  )

  binding.randombytes_buf_deterministic(
    buffer.buffer,
    buffer.byteOffset,
    buffer.byteLength,

    seed.buffer,
    seed.byteOffset,
    seed.byteLength
  )
}

// sodium_helpers

exports.sodium_memcmp = function (a, b) {
  assert(ArrayBuffer.isView(a), 'a must be a typed array')
  assert(ArrayBuffer.isView(b), 'b must be a typed array')
  assert(a.byteLength === b.byteLength, 'buffers must be of same length')

  return binding.sodium_memcmp(a, b)
}

exports.sodium_add = function (a, b) {
  assert(ArrayBuffer.isView(a), 'a must be a typed array')
  assert(ArrayBuffer.isView(b), 'b must be a typed array')
  assert(a.byteLength === b.byteLength, 'buffers must be of same length')

  binding.sodium_add(a, b)
}

exports.sodium_sub = function (a, b) {
  assert(ArrayBuffer.isView(a), 'a must be a typed array')
  assert(ArrayBuffer.isView(b), 'b must be a typed array')
  assert(a.byteLength === b.byteLength, 'buffers must be of same length')

  binding.sodium_sub(a, b)
}

exports.sodium_compare = function (a, b) {
  assert(ArrayBuffer.isView(a), 'a must be a typed array')
  assert(ArrayBuffer.isView(b), 'b must be a typed array')
  assert(a.byteLength === b.byteLength, 'buffers must be of same length')

  return binding.sodium_compare(a, b)
}

exports.sodium_is_zero = function (buffer, length) {
  if (length === undefined) length = buffer.byteLength

  assert(ArrayBuffer.isView(buffer), 'buffer must be a typed array')
  assert(length >= 0 && length <= buffer.byteLength, 'invalid length')

  return binding.sodium_is_zero(buffer, length)
}

exports.sodium_pad = function (buffer, unpaddedBuflen, blockSize) {
  assert(ArrayBuffer.isView(buffer), 'buffer must be a typed array')
  assert(unpaddedBuflen <= buffer.byteLength, 'unpadded length cannot exceed buffer length')
  assert(blockSize <= buffer.byteLength, 'block size cannot exceed buffer length')
  assert(blockSize >= 1, 'block size must be at least 1 byte')
  assert(
    buffer.byteLength >= unpaddedBuflen + (blockSize - (unpaddedBuflen % blockSize)),
    'buf not long enough'
  )

  return binding.sodium_pad(buffer, unpaddedBuflen, blockSize)
}

exports.sodium_unpad = function (buffer, paddedBuflen, blockSize) {
  assert(ArrayBuffer.isView(buffer), 'buffer must be a typed array')
  assert(paddedBuflen <= buffer.byteLength, 'unpadded length cannot exceed buffer length')
  assert(blockSize <= buffer.byteLength, 'block size cannot exceed buffer length')
  assert(blockSize >= 1, 'block size must be at least 1 byte')

  return binding.sodium_unpad(buffer, paddedBuflen, blockSize)
}

// crypto_sign

exports.crypto_sign_keypair = function (pk, sk) {
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(
    pk.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
    "pk must be 'crypto_sign_PUBLICKEYBYTES' bytes"
  )
  assert(
    sk.byteLength === binding.crypto_sign_SECRETKEYBYTES,
    "sk must be 'crypto_sign_SECRETKEYBYTES' bytes"
  )

  const res = binding.crypto_sign_keypair(pk, sk)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_sign_seed_keypair = function (pk, sk, seed) {
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(ArrayBuffer.isView(seed), 'seed must be a typed array')
  assert(
    pk.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
    "pk must be 'crypto_sign_PUBLICKEYBYTES' bytes"
  )
  assert(
    sk.byteLength === binding.crypto_sign_SECRETKEYBYTES,
    "sk must be 'crypto_sign_SECRETKEYBYTES' bytes"
  )
  assert(
    seed.byteLength === binding.crypto_sign_SEEDBYTES,
    "seed must be 'crypto_sign_SEEDBYTES' bytes"
  )

  const res = binding.crypto_sign_seed_keypair(pk, sk, seed)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_sign = function (sm, m, sk) {
  assert(ArrayBuffer.isView(sm), 'sm must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(
    sm.byteLength === binding.crypto_sign_BYTES + m.byteLength,
    "sm must be 'm.byteLength + crypto_sign_BYTES' bytes"
  )
  assert(
    sk.byteLength === binding.crypto_sign_SECRETKEYBYTES,
    "sk must be 'crypto_sign_SECRETKEYBYTES' bytes"
  )

  const res = binding.crypto_sign(sm, m, sk)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_sign_open = function (m, sm, pk) {
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(sm), 'sm must be a typed array')
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(
    sm.byteLength >= binding.crypto_sign_BYTES,
    "sm must be at least 'crypto_sign_BYTES' bytes"
  )
  assert(
    m.byteLength === sm.byteLength - binding.crypto_sign_BYTES,
    "m must be 'sm.byteLength - crypto_sign_BYTES' bytes"
  )
  assert(
    pk.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
    "pk must be 'crypto_sign_PUBLICKEYBYTES' bytes"
  )

  const res = binding.crypto_sign_open(m, sm, pk)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_sign_open = function (m, sm, pk) {
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(sm), 'sm must be a typed array')
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(
    sm.byteLength >= binding.crypto_sign_BYTES,
    "sm must be at least 'crypto_sign_BYTES' bytes"
  )
  assert(
    m.byteLength === sm.byteLength - binding.crypto_sign_BYTES,
    "m must be 'sm.byteLength - crypto_sign_BYTES' bytes"
  )
  assert(
    pk.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
    "pk must be 'crypto_sign_PUBLICKEYBYTES' bytes"
  )

  return binding.crypto_sign_open(m, sm, pk)
}

exports.crypto_sign_detached = function (sig, m, sk) {
  assert(ArrayBuffer.isView(sig), 'sig must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(sig.byteLength === binding.crypto_sign_BYTES, "sig must be 'crypto_sign_BYTES' bytes")
  assert(
    sk.byteLength === binding.crypto_sign_SECRETKEYBYTES,
    "sk must be 'crypto_sign_SECRETKEYBYTES' bytes"
  )

  const res = binding.crypto_sign_detached(sig, m, sk)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_sign_verify_detached = function (sig, m, pk) {
  assert(ArrayBuffer.isView(sig), 'sig must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(
    sig.byteLength >= binding.crypto_sign_BYTES,
    "sig must be at least 'crypto_sign_BYTES' bytes"
  )
  assert(
    pk.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
    "pk must be 'crypto_sign_PUBLICKEYBYTES' bytes"
  )

  return binding.crypto_sign_verify_detached(
    sig.buffer,
    sig.byteOffset,
    sig.byteLength,

    m.buffer,
    m.byteOffset,
    m.byteLength,

    pk.buffer,
    pk.byteOffset,
    pk.byteLength
  )
}

exports.crypto_sign_ed25519_sk_to_pk = function (pk, sk) {
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(
    pk.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
    "pk must be 'crypto_sign_PUBLICKEYBYTES' bytes"
  )
  assert(
    sk.byteLength === binding.crypto_sign_SECRETKEYBYTES,
    "sk must be 'crypto_sign_SECRETKEYBYTES' bytes"
  )

  const res = binding.crypto_sign_ed25519_sk_to_pk(pk, sk)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_sign_ed25519_pk_to_curve25519 = function (x25519pk, ed25519pk) {
  assert(ArrayBuffer.isView(x25519pk), 'x25519pk must be a typed array')
  assert(ArrayBuffer.isView(ed25519pk), 'ed25519pk must be a typed array')
  assert(
    x25519pk.byteLength === binding.crypto_box_PUBLICKEYBYTES,
    "x25519pk must be 'crypto_box_PUBLICKEYBYTES' bytes"
  )
  assert(
    ed25519pk.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
    "ed25519pk must be 'crypto_sign_PUBLICKEYBYTES' bytes"
  )

  const res = binding.crypto_sign_ed25519_pk_to_curve25519(x25519pk, ed25519pk)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_sign_ed25519_sk_to_curve25519 = function (x25519sk, ed25519sk) {
  assert(ArrayBuffer.isView(x25519sk), 'x25519sk must be a typed array')
  assert(ArrayBuffer.isView(ed25519sk), 'ed25519sk must be a typed array')
  assert(
    x25519sk.byteLength === binding.crypto_box_SECRETKEYBYTES,
    "x25519sk must be 'crypto_box_SECRETKEYBYTES' bytes"
  )

  const edLen = ed25519sk.byteLength

  assert(
    edLen === binding.crypto_sign_SECRETKEYBYTES || edLen === binding.crypto_box_SECRETKEYBYTES,
    "ed25519sk must be 'crypto_sign_SECRETKEYBYTES' or 'crypto_sign_SECRETKEYBYTES - crypto_sign_PUBLICKEYBYTES' bytes"
  )

  const res = binding.crypto_sign_ed25519_sk_to_curve25519(x25519sk, ed25519sk)

  if (res !== 0) throw new Error('status: ' + res)
}

// crypto_box

exports.crypto_box_keypair = function (pk, sk) {
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(
    pk.byteLength === binding.crypto_box_PUBLICKEYBYTES,
    "pk must be 'crypto_box_PUBLICKEYBYTES' bytes"
  )
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(
    sk.byteLength === binding.crypto_box_SECRETKEYBYTES,
    "sk must be 'crypto_box_SECRETKEYBYTES' bytes"
  )

  const res = binding.crypto_box_keypair(pk, sk)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_box_seed_keypair = function (pk, sk, seed) {
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(
    pk.byteLength === binding.crypto_box_PUBLICKEYBYTES,
    "pk must be 'crypto_box_PUBLICKEYBYTES' bytes"
  )
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(
    sk.byteLength === binding.crypto_box_SECRETKEYBYTES,
    "sk must be 'crypto_box_SECRETKEYBYTES' bytes"
  )
  assert(ArrayBuffer.isView(seed), 'seed must be a typed array')
  assert(
    seed.byteLength === binding.crypto_box_SEEDBYTES,
    "seed must be 'crypto_box_SEEDBYTES' bytes"
  )

  const res = binding.crypto_box_seed_keypair(pk, sk, seed)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_box_easy = function (c, m, n, pk, sk) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(
    c.byteLength === m.byteLength + exports.crypto_box_MACBYTES,
    "c must be 'm.byteLength + crypto_box_MACBYTES' bytes"
  )
  assert(n.byteLength === exports.crypto_box_NONCEBYTES, "n must be 'crypto_box_NONCEBYTES' bytes")
  assert(
    pk.byteLength === exports.crypto_box_PUBLICKEYBYTES,
    "pk must be 'crypto_box_PUBLICKEYBYTES' bytes"
  )
  assert(
    sk.byteLength === exports.crypto_box_SECRETKEYBYTES,
    "sk must be 'crypto_box_SECRETKEYBYTES' bytes"
  )

  const res = binding.crypto_box_easy(c, m, n, pk, sk)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_box_detached = function (c, mac, m, n, pk, sk) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(mac), 'mac must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(mac.byteLength === exports.crypto_box_MACBYTES, "mac must be 'crypto_box_MACBYTES' bytes")
  assert(n.byteLength === exports.crypto_box_NONCEBYTES, "n must be 'crypto_box_NONCEBYTES' bytes")
  assert(
    pk.byteLength === exports.crypto_box_PUBLICKEYBYTES,
    "pk must be 'crypto_box_PUBLICKEYBYTES' bytes"
  )
  assert(
    sk.byteLength === exports.crypto_box_SECRETKEYBYTES,
    "sk must be 'crypto_box_SECRETKEYBYTES' bytes"
  )

  const res = binding.crypto_box_detached(c, mac, m, n, pk, sk)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_box_open_easy = function (m, c, n, pk, sk) {
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(
    c.byteLength >= exports.crypto_box_MACBYTES,
    "c must be at least 'crypto_box_MACBYTES' bytes"
  )
  assert(
    m.byteLength === c.byteLength - exports.crypto_box_MACBYTES,
    "m must be 'c.byteLength - crypto_box_MACBYTES' bytes"
  )
  assert(n.byteLength === exports.crypto_box_NONCEBYTES, "n must be 'crypto_box_NONCEBYTES' bytes")
  assert(
    pk.byteLength === exports.crypto_box_PUBLICKEYBYTES,
    "pk must be 'crypto_box_PUBLICKEYBYTES' bytes"
  )
  assert(
    sk.byteLength === exports.crypto_box_SECRETKEYBYTES,
    "sk must be 'crypto_box_SECRETKEYBYTES' bytes"
  )

  return binding.crypto_box_open_easy(m, c, n, pk, sk)
}

exports.crypto_box_open_detached = function (m, c, mac, n, pk, sk) {
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(mac), 'mac must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(m.byteLength === c.byteLength, "m must be 'c.byteLength' bytes")
  assert(mac.byteLength === exports.crypto_box_MACBYTES, "mac must be 'crypto_box_MACBYTES' bytes")
  assert(n.byteLength === exports.crypto_box_NONCEBYTES, "n must be 'crypto_box_NONCEBYTES' bytes")
  assert(
    pk.byteLength === exports.crypto_box_PUBLICKEYBYTES,
    "pk must be 'crypto_box_PUBLICKEYBYTES' bytes"
  )
  assert(
    sk.byteLength === exports.crypto_box_SECRETKEYBYTES,
    "sk must be 'crypto_box_SECRETKEYBYTES' bytes"
  )

  return binding.crypto_box_open_detached(m, c, mac, n, pk, sk)
}

exports.crypto_box_seal = function (c, m, pk) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(
    c.byteLength === m.byteLength + exports.crypto_box_SEALBYTES,
    "c must be 'm.byteLength + crypto_box_SEALBYTES' bytes"
  )
  assert(
    pk.byteLength === exports.crypto_box_PUBLICKEYBYTES,
    "pk must be 'crypto_box_PUBLICKEYBYTES' bytes"
  )

  const res = binding.crypto_box_seal(c, m, pk)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_box_seal_open = function (m, c, pk, sk) {
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(
    c.byteLength >= exports.crypto_box_SEALBYTES,
    "c must be at least 'crypto_box_SEALBYTES' bytes"
  )
  assert(
    m.byteLength === c.byteLength - exports.crypto_box_SEALBYTES,
    "m must be 'c.byteLength - crypto_box_SEALBYTES' bytes"
  )
  assert(
    pk.byteLength === exports.crypto_box_PUBLICKEYBYTES,
    "pk must be 'crypto_box_PUBLICKEYBYTES' bytes"
  )
  assert(
    sk.byteLength === exports.crypto_box_SECRETKEYBYTES,
    "sk must be 'crypto_box_SECRETKEYBYTES' bytes"
  )

  return binding.crypto_box_seal_open(
    m.buffer,
    m.byteOffset,
    m.byteLength,

    c.buffer,
    c.byteOffset,
    c.byteLength,

    pk.buffer,
    pk.byteOffset,
    pk.byteLength,

    sk.buffer,
    sk.byteOffset,
    sk.byteLength
  )
}

// crypto_secretbox

exports.crypto_secretbox_easy = function (c, m, n, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    c.byteLength === m.byteLength + binding.crypto_secretbox_MACBYTES,
    "c must be 'm.byteLength + crypto_secretbox_MACBYTES' bytes"
  )
  assert(
    n.byteLength === binding.crypto_secretbox_NONCEBYTES,
    "n must be 'crypto_secretbox_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_secretbox_KEYBYTES,
    "k must be 'crypto_secretbox_KEYBYTES' bytes"
  )

  const res = binding.crypto_secretbox_easy(c, m, n, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_secretbox_open_easy = function (m, c, n, k) {
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    c.byteLength >= binding.crypto_secretbox_MACBYTES,
    "c must be at least 'crypto_secretbox_MACBYTES' bytes"
  )
  assert(
    m.byteLength === c.byteLength - binding.crypto_secretbox_MACBYTES,
    "m must be 'c.byteLength - crypto_secretbox_MACBYTES' bytes"
  )
  assert(
    n.byteLength === binding.crypto_secretbox_NONCEBYTES,
    "n must be 'crypto_secretbox_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_secretbox_KEYBYTES,
    "k must be 'crypto_secretbox_KEYBYTES' bytes"
  )

  return binding.crypto_secretbox_open_easy(m, c, n, k)
}

exports.crypto_secretbox_detached = function (c, mac, m, n, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(mac), 'mac must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(
    mac.byteLength === binding.crypto_secretbox_MACBYTES,
    "mac must be 'crypto_secretbox_MACBYTES' bytes"
  )
  assert(
    n.byteLength === binding.crypto_secretbox_NONCEBYTES,
    "n must be 'crypto_secretbox_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_secretbox_KEYBYTES,
    "k must be 'crypto_secretbox_KEYBYTES' bytes"
  )

  const res = binding.crypto_secretbox_detached(c, mac, m, n, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_secretbox_open_detached = function (m, c, mac, n, k) {
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(mac), 'mac must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(m.byteLength === c.byteLength, "m must be 'c.byteLength' bytes")
  assert(
    mac.byteLength === binding.crypto_secretbox_MACBYTES,
    "mac must be 'crypto_secretbox_MACBYTES' bytes"
  )
  assert(
    n.byteLength === binding.crypto_secretbox_NONCEBYTES,
    "n must be 'crypto_secretbox_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_secretbox_KEYBYTES,
    "k must be 'crypto_secretbox_KEYBYTES' bytes"
  )

  return binding.crypto_secretbox_open_detached(m, c, mac, n, k)
}

// crypto_generichash

exports.crypto_generichash = function (output, input, key) {
  if (!key) key = OPTIONAL

  assert(ArrayBuffer.isView(output), 'output must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(
    output.byteLength >= binding.crypto_generichash_BYTES_MIN &&
      output.byteLength <= binding.crypto_generichash_BYTES_MAX,
    'output must be between crypto_generichash_BYTES_MIN and crypto_generichash_BYTES_MAX bytes'
  )

  if (key !== OPTIONAL) {
    assert(ArrayBuffer.isView(key), 'key must be a typed array')
    assert(
      key.byteLength >= binding.crypto_generichash_KEYBYTES_MIN &&
        key.byteLength <= binding.crypto_generichash_KEYBYTES_MAX,
      'key must be between crypto_generichash_KEYBYTES_MIN and crypto_generichash_KEYBYTES_MAX bytes'
    )
  }

  const res = binding.crypto_generichash(
    output.buffer,
    output.byteOffset,
    output.byteLength,

    input.buffer,
    input.byteOffset,
    input.byteLength,

    key.buffer,
    key.byteOffset,
    key.byteLength
  )

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_generichash_batch = function (output, batch, key) {
  assert(ArrayBuffer.isView(output), 'output must be a typed array')

  if (isNode || batch.length < 4) {
    const res = binding.crypto_generichash_batch(output, batch, !!key, key || OPTIONAL)
    if (res !== 0) throw new Error('status: ' + res)
  } else {
    const state = Buffer.alloc(binding.crypto_generichash_STATEBYTES)

    exports.crypto_generichash_init(state, key, output.byteLength)

    for (const buf of batch) {
      exports.crypto_generichash_update(state, buf)
    }

    exports.crypto_generichash_final(state, output)
  }
}

exports.crypto_generichash_keygen = function (key) {
  assert(ArrayBuffer.isView(key), 'key must be a typed array')
  assert(
    key.byteLength === binding.crypto_generichash_KEYBYTES,
    "key must be 'crypto_generichash_KEYBYTES' bytes"
  )

  const res = binding.crypto_generichash_keygen(key.buffer, key.byteOffset, key.byteLength)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_generichash_init = function (state, key, outputLength) {
  if (!key) key = OPTIONAL

  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_generichash_STATEBYTES,
    "state must be 'crypto_generichash_STATEBYTES' bytes"
  )

  const res = binding.crypto_generichash_init(
    state.buffer,
    state.byteOffset,
    state.byteLength,

    key.buffer,
    key.byteOffset,
    key.byteLength,

    outputLength
  )

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_generichash_update = function (state, input) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(
    state.byteLength === binding.crypto_generichash_STATEBYTES,
    "state must be 'crypto_generichash_STATEBYTES' bytes"
  )

  const res = binding.crypto_generichash_update(
    state.buffer,
    state.byteOffset,
    state.byteLength,

    input.buffer,
    input.byteOffset,
    input.byteLength
  )

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_generichash_final = function (state, output) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(ArrayBuffer.isView(output), 'output must be a typed array')
  assert(
    state.byteLength === binding.crypto_generichash_STATEBYTES,
    "state must be 'crypto_generichash_STATEBYTES' bytes"
  )

  const res = binding.crypto_generichash_final(
    state.buffer,
    state.byteOffset,
    state.byteLength,

    output.buffer,
    output.byteOffset,
    output.byteLength
  )

  if (res !== 0) throw new Error('status: ' + res)
}

// secretstream

exports.crypto_secretstream_xchacha20poly1305_keygen = function (k) {
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_secretstream_xchacha20poly1305_KEYBYTES,
    "k must be 'crypto_secretstream_xchacha20poly1305_KEYBYTES' bytes"
  )

  binding.crypto_secretstream_xchacha20poly1305_keygen(k.buffer, k.byteOffset, k.byteLength)
}

exports.crypto_secretstream_xchacha20poly1305_init_push = function (state, header, k) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(ArrayBuffer.isView(header), 'header must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    state.byteLength === binding.crypto_secretstream_xchacha20poly1305_STATEBYTES,
    "state must be 'crypto_secretstream_xchacha20poly1305_STATEBYTES' bytes"
  )
  assert(
    header.byteLength === binding.crypto_secretstream_xchacha20poly1305_HEADERBYTES,
    "header must be 'crypto_secretstream_xchacha20poly1305_HEADERBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_secretstream_xchacha20poly1305_KEYBYTES,
    "k must be 'crypto_secretstream_xchacha20poly1305_KEYBYTES' bytes"
  )

  const res = binding.crypto_secretstream_xchacha20poly1305_init_push(
    state.buffer,
    state.byteOffset,
    state.byteLength,

    header.buffer,
    header.byteOffset,
    header.byteLength,

    k.buffer,
    k.byteOffset,
    k.byteLength
  )

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_secretstream_xchacha20poly1305_init_pull = function (state, header, k) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(ArrayBuffer.isView(header), 'header must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    state.byteLength === binding.crypto_secretstream_xchacha20poly1305_STATEBYTES,
    "state must be 'crypto_secretstream_xchacha20poly1305_STATEBYTES' bytes"
  )
  assert(
    header.byteLength === binding.crypto_secretstream_xchacha20poly1305_HEADERBYTES,
    "header must be 'crypto_secretstream_xchacha20poly1305_HEADERBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_secretstream_xchacha20poly1305_KEYBYTES,
    "k must be 'crypto_secretstream_xchacha20poly1305_KEYBYTES' bytes"
  )

  const res = binding.crypto_secretstream_xchacha20poly1305_init_pull(
    state.buffer,
    state.byteOffset,
    state.byteLength,

    header.buffer,
    header.byteOffset,
    header.byteLength,

    k.buffer,
    k.byteOffset,
    k.byteLength
  )

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_secretstream_xchacha20poly1305_push = function (state, c, m, ad, tag) {
  if (!ad) ad = OPTIONAL

  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(
    state.byteLength === binding.crypto_secretstream_xchacha20poly1305_STATEBYTES,
    "state must be 'crypto_secretstream_xchacha20poly1305_STATEBYTES' bytes"
  )
  assert(
    c.byteLength === m.byteLength + binding.crypto_secretstream_xchacha20poly1305_ABYTES,
    "c must be 'm.byteLength + crypto_secretstream_xchacha20poly1305_ABYTES' bytes"
  )

  const res = binding.crypto_secretstream_xchacha20poly1305_push(
    state.buffer,
    state.byteOffset,
    state.byteLength,

    c.buffer,
    c.byteOffset,
    c.byteLength,

    m.buffer,
    m.byteOffset,
    m.byteLength,

    ad.buffer,
    ad.byteOffset,
    ad.byteLength,

    tag
  )

  if (res < 0) throw new Error('push failed')

  return res
}

exports.crypto_secretstream_xchacha20poly1305_pull = function (state, m, tag, c, ad) {
  if (!ad) ad = OPTIONAL

  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_secretstream_xchacha20poly1305_STATEBYTES,
    "state must be 'crypto_secretstream_xchacha20poly1305_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(tag), 'tag must be a typed array')
  assert(tag.byteLength === 1, 'tag must be 1 byte')

  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(
    c.byteLength >= binding.crypto_secretstream_xchacha20poly1305_ABYTES,
    "c must be at least 'crypto_secretstream_xchacha20poly1305_ABYTES' bytes"
  )
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(
    m.byteLength === c.byteLength - binding.crypto_secretstream_xchacha20poly1305_ABYTES,
    "m must be 'c.byteLength - crypto_secretstream_xchacha20poly1305_ABYTES' bytes"
  )

  const res = binding.crypto_secretstream_xchacha20poly1305_pull(
    state.buffer,
    state.byteOffset,
    state.byteLength,

    m.buffer,
    m.byteOffset,
    m.byteLength,

    tag.buffer,
    tag.byteOffset,
    tag.byteLength,

    c.buffer,
    c.byteOffset,
    c.byteLength,

    ad.buffer,
    ad.byteOffset,
    ad.byteLength
  )

  if (res < 0) throw new Error('pull failed')

  return res
}

exports.crypto_secretstream_xchacha20poly1305_rekey = function (state) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_secretstream_xchacha20poly1305_STATEBYTES,
    "state must be 'crypto_secretstream_xchacha20poly1305_STATEBYTES' bytes"
  )

  binding.crypto_secretstream_xchacha20poly1305_rekey(
    state.buffer,
    state.byteOffset,
    state.byteLength
  )
}

// crypto_stream

exports.crypto_stream = function (c, n, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    n.byteLength === binding.crypto_stream_NONCEBYTES,
    "n must be 'crypto_stream_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_KEYBYTES,
    "k must be 'crypto_stream_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream(c, n, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_xor = function (c, m, n, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(
    n.byteLength === binding.crypto_stream_NONCEBYTES,
    "n must be 'crypto_stream_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_KEYBYTES,
    "k must be 'crypto_stream_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_xor(
    c.buffer,
    c.byteOffset,
    c.byteLength,

    m.buffer,
    m.byteOffset,
    m.byteLength,

    n.buffer,
    n.byteOffset,
    n.byteLength,

    k.buffer,
    k.byteOffset,
    k.byteLength
  )

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_chacha20 = function (c, n, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    n.byteLength === binding.crypto_stream_chacha20_NONCEBYTES,
    "n must be 'crypto_stream_chacha20_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_chacha20_KEYBYTES,
    "k must be 'crypto_stream_chacha20_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_chacha20(c, n, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_chacha20_xor = function (c, m, n, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(
    n.byteLength === binding.crypto_stream_chacha20_NONCEBYTES,
    "n must be 'crypto_stream_chacha20_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_chacha20_KEYBYTES,
    "k must be 'crypto_stream_chacha20_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_chacha20_xor(c, m, n, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_chacha20_xor_ic = function (c, m, n, ic, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(
    n.byteLength === binding.crypto_stream_chacha20_NONCEBYTES,
    "n must be 'crypto_stream_chacha20_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_chacha20_KEYBYTES,
    "k must be 'crypto_stream_chacha20_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_chacha20_xor_ic(c, m, n, ic, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_chacha20_ietf = function (c, n, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    n.byteLength === binding.crypto_stream_chacha20_ietf_NONCEBYTES,
    "n must be 'crypto_stream_chacha20_ietf_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_chacha20_ietf_KEYBYTES,
    "k must be 'crypto_stream_chacha20_ietf_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_chacha20_ietf(c, n, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_chacha20_ietf_xor = function (c, m, n, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(
    n.byteLength === binding.crypto_stream_chacha20_ietf_NONCEBYTES,
    "n must be 'crypto_stream_chacha20_ietf_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_chacha20_ietf_KEYBYTES,
    "k must be 'crypto_stream_chacha20_ietf_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_chacha20_ietf_xor(c, m, n, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_chacha20_ietf_xor_ic = function (c, m, n, ic, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(
    n.byteLength === binding.crypto_stream_chacha20_ietf_NONCEBYTES,
    "n must be 'crypto_stream_chacha20_ietf_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_chacha20_ietf_KEYBYTES,
    "k must be 'crypto_stream_chacha20_ietf_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_chacha20_ietf_xor_ic(c, m, n, ic, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_xchacha20 = function (c, n, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    n.byteLength === binding.crypto_stream_xchacha20_NONCEBYTES,
    "n must be 'crypto_stream_xchacha20_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_xchacha20_KEYBYTES,
    "k must be 'crypto_stream_xchacha20_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_xchacha20(c, n, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_xchacha20_xor = function (c, m, n, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(
    n.byteLength === binding.crypto_stream_xchacha20_NONCEBYTES,
    "n must be 'crypto_stream_xchacha20_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_xchacha20_KEYBYTES,
    "k must be 'crypto_stream_xchacha20_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_xchacha20_xor(c, m, n, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_xchacha20_xor_ic = function (c, m, n, ic, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(
    n.byteLength === binding.crypto_stream_xchacha20_NONCEBYTES,
    "n must be 'crypto_stream_xchacha20_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_xchacha20_KEYBYTES,
    "k must be 'crypto_stream_xchacha20_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_xchacha20_xor_ic(c, m, n, ic, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_salsa20 = function (c, n, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    n.byteLength === binding.crypto_stream_salsa20_NONCEBYTES,
    "n must be 'crypto_stream_salsa20_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_salsa20_KEYBYTES,
    "k must be 'crypto_stream_salsa20_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_salsa20(c, n, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_salsa20_xor = function (c, m, n, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(
    n.byteLength === binding.crypto_stream_salsa20_NONCEBYTES,
    "n must be 'crypto_stream_salsa20_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_salsa20_KEYBYTES,
    "k must be 'crypto_stream_salsa20_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_salsa20_xor(c, m, n, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_salsa20_xor_ic = function (c, m, n, ic, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(
    n.byteLength === binding.crypto_stream_salsa20_NONCEBYTES,
    "n must be 'crypto_stream_salsa20_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_salsa20_KEYBYTES,
    "k must be 'crypto_stream_salsa20_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_salsa20_xor_ic(c, m, n, ic, k)

  if (res !== 0) throw new Error('status: ' + res)
}

// crypto_auth

exports.crypto_auth = function (out, input, k) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(out.byteLength === binding.crypto_auth_BYTES, "out must be 'crypto_auth_BYTES' bytes")
  assert(k.byteLength === binding.crypto_auth_KEYBYTES, "k must be 'crypto_auth_KEYBYTES' bytes")

  const res = binding.crypto_auth(out, input, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_auth_verify = function (h, input, k) {
  assert(ArrayBuffer.isView(h), 'h must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(h.byteLength === binding.crypto_auth_BYTES, "h must be 'crypto_auth_BYTES' bytes")
  assert(k.byteLength === binding.crypto_auth_KEYBYTES, "k must be 'crypto_auth_KEYBYTES' bytes")

  return binding.crypto_auth_verify(h, input, k)
}

// crypto_onetimeauth

exports.crypto_onetimeauth = function (out, input, k) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    out.byteLength === binding.crypto_onetimeauth_BYTES,
    "out must be 'crypto_onetimeauth_BYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_onetimeauth_KEYBYTES,
    "k must be 'crypto_onetimeauth_KEYBYTES' bytes"
  )

  const res = binding.crypto_onetimeauth(out, input, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_onetimeauth_init = function (state, k) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    state.byteLength === binding.crypto_onetimeauth_STATEBYTES,
    "state must be 'crypto_onetimeauth_STATEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_onetimeauth_KEYBYTES,
    "k must be 'crypto_onetimeauth_KEYBYTES' bytes"
  )

  const res = binding.crypto_onetimeauth_init(state, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_onetimeauth_update = function (state, input) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(
    state.byteLength === binding.crypto_onetimeauth_STATEBYTES,
    "state must be 'crypto_onetimeauth_STATEBYTES' bytes"
  )

  const res = binding.crypto_onetimeauth_update(state, input)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_onetimeauth_final = function (state, out) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(
    state.byteLength === binding.crypto_onetimeauth_STATEBYTES,
    "state must be 'crypto_onetimeauth_STATEBYTES' bytes"
  )
  assert(
    out.byteLength === binding.crypto_onetimeauth_BYTES,
    "out must be 'crypto_onetimeauth_BYTES' bytes"
  )

  const res = binding.crypto_onetimeauth_final(state, out)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_onetimeauth_verify = function (h, input, k) {
  assert(ArrayBuffer.isView(h), 'h must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    h.byteLength === binding.crypto_onetimeauth_BYTES,
    "h must be 'crypto_onetimeauth_BYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_onetimeauth_KEYBYTES,
    "k must be 'crypto_onetimeauth_KEYBYTES' bytes"
  )

  return binding.crypto_onetimeauth_verify(h, input, k)
}

// crypto_pwhash

exports.crypto_pwhash = function (out, passwd, salt, opslimit, memlimit, alg) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(ArrayBuffer.isView(salt), 'salt must be a typed array')
  assert(
    out.byteLength >= binding.crypto_pwhash_BYTES_MIN,
    "out must be at least 'crypto_pwhash_BYTES_MIN' bytes"
  )
  assert(
    out.byteLength <= binding.crypto_pwhash_BYTES_MAX,
    "out must be at most 'crypto_pwhash_BYTES_MAX' bytes"
  )
  assert(
    salt.byteLength === binding.crypto_pwhash_SALTBYTES,
    "salt must be 'crypto_pwhash_SALTBYTES' bytes"
  )
  assert(
    opslimit >= binding.crypto_pwhash_OPSLIMIT_MIN,
    "opslimit must be at least 'crypto_pwhash_OPSLIMIT_MIN'"
  )
  assert(
    opslimit <= binding.crypto_pwhash_OPSLIMIT_MAX,
    "opslimit must be at most 'crypto_pwhash_OPSLIMIT_MAX'"
  )
  assert(
    memlimit >= binding.crypto_pwhash_MEMLIMIT_MIN,
    "memlimit must be at least 'crypto_pwhash_MEMLIMIT_MIN'"
  )
  assert(
    memlimit <= binding.crypto_pwhash_MEMLIMIT_MAX,
    "memlimit must be at most 'crypto_pwhash_MEMLIMIT_MAX'"
  )
  assert(alg >= 1 && alg <= 2, 'alg must be either Argon2i 1.3 or Argon2id 1.3')

  const res = binding.crypto_pwhash(out, passwd, salt, opslimit, memlimit, alg)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_pwhash_async = function (
  out,
  passwd,
  salt,
  opslimit,
  memlimit,
  alg,
  callback = undefined
) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(ArrayBuffer.isView(salt), 'salt must be a typed array')
  assert(
    out.byteLength >= binding.crypto_pwhash_BYTES_MIN,
    "out must be at least 'crypto_pwhash_BYTES_MIN' bytes"
  )
  assert(
    out.byteLength <= binding.crypto_pwhash_BYTES_MAX,
    "out must be at most 'crypto_pwhash_BYTES_MAX' bytes"
  )
  assert(
    salt.byteLength === binding.crypto_pwhash_SALTBYTES,
    "salt must be 'crypto_pwhash_SALTBYTES' bytes"
  )
  assert(
    opslimit >= binding.crypto_pwhash_OPSLIMIT_MIN,
    "opslimit must be at least 'crypto_pwhash_OPSLIMIT_MIN'"
  )
  assert(
    opslimit <= binding.crypto_pwhash_OPSLIMIT_MAX,
    "opslimit must be at most 'crypto_pwhash_OPSLIMIT_MAX'"
  )
  assert(
    memlimit >= binding.crypto_pwhash_MEMLIMIT_MIN,
    "memlimit must be at least 'crypto_pwhash_MEMLIMIT_MIN'"
  )
  assert(
    memlimit <= binding.crypto_pwhash_MEMLIMIT_MAX,
    "memlimit must be at most 'crypto_pwhash_MEMLIMIT_MAX'"
  )
  assert(alg >= 1 && alg <= 2, 'alg must be either Argon2i 1.3 or Argon2id 1.3')

  const [done, promise] = checkStatus(callback)

  binding.crypto_pwhash_async(
    out.buffer,
    out.byteOffset,
    out.byteLength,

    passwd.buffer,
    passwd.byteOffset,
    passwd.byteLength,

    salt.buffer,
    salt.byteOffset,
    salt.byteLength,

    opslimit,
    memlimit,
    alg,

    done
  )

  return promise
}

exports.crypto_pwhash_str = function (out, passwd, opslimit, memlimit) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(
    out.byteLength === binding.crypto_pwhash_STRBYTES,
    "out must be 'crypto_pwhash_STRBYTES' bytes"
  )
  assert(typeof opslimit === 'number', 'opslimit must be a number')
  assert(
    opslimit >= binding.crypto_pwhash_OPSLIMIT_MIN,
    "opslimit must be at least 'crypto_pwhash_OPSLIMIT_MIN'"
  )
  assert(
    opslimit <= binding.crypto_pwhash_OPSLIMIT_MAX,
    "opslimit must be at most 'crypto_pwhash_OPSLIMIT_MAX'"
  )
  assert(typeof memlimit === 'number', 'memlimit must be a number')
  assert(
    memlimit >= binding.crypto_pwhash_MEMLIMIT_MIN,
    "memlimit must be at least 'crypto_pwhash_MEMLIMIT_MIN'"
  )
  assert(
    memlimit <= binding.crypto_pwhash_MEMLIMIT_MAX,
    "memlimit must be at most 'crypto_pwhash_MEMLIMIT_MAX'"
  )

  const res = binding.crypto_pwhash_str(out, passwd, opslimit, memlimit)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_pwhash_str_async = function (out, passwd, opslimit, memlimit, callback = undefined) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(
    out.byteLength === binding.crypto_pwhash_STRBYTES,
    "out must be 'crypto_pwhash_STRBYTES' bytes"
  )
  assert(passwd.byteLength > 0, 'passwd must not be empty')
  assert(typeof opslimit === 'number', 'opslimit must be a number')
  assert(
    opslimit >= binding.crypto_pwhash_OPSLIMIT_MIN,
    "opslimit must be at least 'crypto_pwhash_OPSLIMIT_MIN'"
  )
  assert(
    opslimit <= binding.crypto_pwhash_OPSLIMIT_MAX,
    "opslimit must be at most 'crypto_pwhash_OPSLIMIT_MAX'"
  )
  assert(typeof memlimit === 'number', 'memlimit must be a number')
  assert(
    memlimit >= binding.crypto_pwhash_MEMLIMIT_MIN,
    "memlimit must be at least 'crypto_pwhash_MEMLIMIT_MIN'"
  )
  assert(
    memlimit <= binding.crypto_pwhash_MEMLIMIT_MAX,
    "memlimit must be at most 'crypto_pwhash_MEMLIMIT_MAX'"
  )

  const [done, promise] = checkStatus(callback)

  binding.crypto_pwhash_str_async(
    out.buffer,
    out.byteOffset,
    out.byteLength,

    passwd.buffer,
    passwd.byteOffset,
    passwd.byteLength,

    opslimit,
    memlimit,

    done
  )

  return promise
}

exports.crypto_pwhash_str_verify = function (str, passwd) {
  assert(ArrayBuffer.isView(str), 'str must be a typed array')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(
    str.byteLength === binding.crypto_pwhash_STRBYTES,
    "str must be 'crypto_pwhash_STRBYTES' bytes"
  )

  return binding.crypto_pwhash_str_verify(str, passwd)
}

exports.crypto_pwhash_str_verify_async = function (str, passwd, callback = undefined) {
  assert(ArrayBuffer.isView(str), 'str must be a typed array')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(
    str.byteLength === binding.crypto_pwhash_STRBYTES,
    "str must be 'crypto_pwhash_STRBYTES' bytes"
  )
  assert(passwd.byteLength > 0, 'passwd must not be empty')

  const [done, promise] = checkStatus(callback, true)

  binding.crypto_pwhash_str_verify_async(
    str.buffer,
    str.byteOffset,
    str.byteLength,

    passwd.buffer,
    passwd.byteOffset,
    passwd.byteLength,

    done
  )

  return promise
}

exports.crypto_pwhash_str_needs_rehash = function (str, opslimit, memlimit) {
  assert(ArrayBuffer.isView(str), 'str must be a typed array')
  assert(
    str.byteLength === binding.crypto_pwhash_STRBYTES,
    "str must be 'crypto_pwhash_STRBYTES' bytes"
  )
  assert(
    opslimit >= binding.crypto_pwhash_OPSLIMIT_MIN,
    "opslimit must be at least 'crypto_pwhash_OPSLIMIT_MIN'"
  )
  assert(
    opslimit <= binding.crypto_pwhash_OPSLIMIT_MAX,
    "opslimit must be at most 'crypto_pwhash_OPSLIMIT_MAX'"
  )
  assert(
    memlimit >= binding.crypto_pwhash_MEMLIMIT_MIN,
    "memlimit must be at least 'crypto_pwhash_MEMLIMIT_MIN'"
  )
  assert(
    memlimit <= binding.crypto_pwhash_MEMLIMIT_MAX,
    "memlimit must be at most 'crypto_pwhash_MEMLIMIT_MAX'"
  )

  return binding.crypto_pwhash_str_needs_rehash(str, opslimit, memlimit)
}

exports.crypto_pwhash_scryptsalsa208sha256 = function (out, passwd, salt, opslimit, memlimit) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(ArrayBuffer.isView(salt), 'salt must be a typed array')
  assert(
    out.byteLength >= binding.crypto_pwhash_scryptsalsa208sha256_BYTES_MIN,
    "out must be at least 'crypto_pwhash_scryptsalsa208sha256_BYTES_MIN' bytes"
  )
  assert(
    out.byteLength <= binding.crypto_pwhash_scryptsalsa208sha256_BYTES_MAX,
    "out must be at most 'crypto_pwhash_scryptsalsa208sha256_BYTES_MAX' bytes"
  )
  assert(
    salt.byteLength === binding.crypto_pwhash_scryptsalsa208sha256_SALTBYTES,
    "salt must be 'crypto_pwhash_scryptsalsa208sha256_SALTBYTES' bytes"
  )
  assert(
    opslimit >= binding.crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MIN,
    "opslimit must be at least 'crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MIN'"
  )
  assert(
    opslimit <= binding.crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MAX,
    "opslimit must be at most 'crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MAX'"
  )
  assert(
    memlimit >= binding.crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MIN,
    "memlimit must be at least 'crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MIN'"
  )
  assert(
    memlimit <= binding.crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MAX,
    "memlimit must be at most 'crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MAX'"
  )

  const res = binding.crypto_pwhash_scryptsalsa208sha256(out, passwd, salt, opslimit, memlimit)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_pwhash_scryptsalsa208sha256_async = function (
  out,
  passwd,
  salt,
  opslimit,
  memlimit,
  callback = undefined
) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(ArrayBuffer.isView(salt), 'salt must be a typed array')
  assert(
    out.byteLength >= binding.crypto_pwhash_scryptsalsa208sha256_BYTES_MIN,
    "out must be at least 'crypto_pwhash_scryptsalsa208sha256_BYTES_MIN' bytes"
  )
  assert(
    out.byteLength <= binding.crypto_pwhash_scryptsalsa208sha256_BYTES_MAX,
    "out must be at most 'crypto_pwhash_scryptsalsa208sha256_BYTES_MAX' bytes"
  )
  assert(passwd.byteLength > 0, 'passwd must not be empty')
  assert(
    salt.byteLength === binding.crypto_pwhash_scryptsalsa208sha256_SALTBYTES,
    "salt must be 'crypto_pwhash_scryptsalsa208sha256_SALTBYTES' bytes"
  )
  assert(
    opslimit >= binding.crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MIN,
    "opslimit must be at least 'crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MIN'"
  )
  assert(
    opslimit <= binding.crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MAX,
    "opslimit must be at most 'crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MAX'"
  )
  assert(
    memlimit >= binding.crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MIN,
    "memlimit must be at least 'crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MIN'"
  )
  assert(
    memlimit <= binding.crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MAX,
    "memlimit must be at most 'crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MAX'"
  )

  const [done, promise] = checkStatus(callback)

  binding.crypto_pwhash_scryptsalsa208sha256_async(
    out.buffer,
    out.byteOffset,
    out.byteLength,

    passwd.buffer,
    passwd.byteOffset,
    passwd.byteLength,

    salt.buffer,
    salt.byteOffset,
    salt.byteLength,

    opslimit,
    memlimit,

    done
  )

  return promise
}

exports.crypto_pwhash_scryptsalsa208sha256_str_async = function (
  out,
  passwd,
  opslimit,
  memlimit,
  callback = undefined
) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(
    out.byteLength === binding.crypto_pwhash_scryptsalsa208sha256_STRBYTES,
    "out must be 'crypto_pwhash_scryptsalsa208sha256_STRBYTES' bytes"
  )
  assert(passwd.byteLength > 0, 'passwd must not be empty')
  assert(
    opslimit >= binding.crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MIN,
    "opslimit must be at least 'crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MIN'"
  )
  assert(
    opslimit <= binding.crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MAX,
    "opslimit must be at most 'crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MAX'"
  )
  assert(
    memlimit >= binding.crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MIN,
    "memlimit must be at least 'crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MIN'"
  )
  assert(
    memlimit <= binding.crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MAX,
    "memlimit must be at most 'crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MAX'"
  )

  const [done, promise] = checkStatus(callback)

  binding.crypto_pwhash_scryptsalsa208sha256_str_async(
    out.buffer,
    out.byteOffset,
    out.byteLength,

    passwd.buffer,
    passwd.byteOffset,
    passwd.byteLength,

    opslimit,
    memlimit,

    done
  )

  return promise
}

exports.crypto_pwhash_scryptsalsa208sha256_str = function (out, passwd, opslimit, memlimit) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(
    out.byteLength === binding.crypto_pwhash_scryptsalsa208sha256_STRBYTES,
    "out must be 'crypto_pwhash_scryptsalsa208sha256_STRBYTES' bytes"
  )
  assert(passwd.byteLength > 0, 'passwd must not be empty')
  assert(
    opslimit >= binding.crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MIN,
    "opslimit must be at least 'crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MIN'"
  )
  assert(
    opslimit <= binding.crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MAX,
    "opslimit must be at most 'crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MAX'"
  )
  assert(
    memlimit >= binding.crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MIN,
    "memlimit must be at least 'crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MIN'"
  )
  assert(
    memlimit <= binding.crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MAX,
    "memlimit must be at most 'crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MAX'"
  )

  const res = binding.crypto_pwhash_scryptsalsa208sha256_str(out, passwd, opslimit, memlimit)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_pwhash_scryptsalsa208sha256_str_verify_async = function (
  str,
  passwd,
  callback = undefined
) {
  assert(ArrayBuffer.isView(str), 'str must be a typed array')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(
    str.byteLength === binding.crypto_pwhash_scryptsalsa208sha256_STRBYTES,
    "str must be 'crypto_pwhash_scryptsalsa208sha256_STRBYTES' bytes"
  )
  assert(passwd.byteLength > 0, 'passwd must not be empty')

  const [done, promise] = checkStatus(callback, true)

  binding.crypto_pwhash_scryptsalsa208sha256_str_verify_async(
    str.buffer,
    str.byteOffset,
    str.byteLength,

    passwd.buffer,
    passwd.byteOffset,
    passwd.byteLength,

    done
  )

  return promise
}

exports.crypto_pwhash_scryptsalsa208sha256_str_verify = function (str, passwd) {
  assert(ArrayBuffer.isView(str), 'str must be a typed array')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(
    str.byteLength === binding.crypto_pwhash_scryptsalsa208sha256_STRBYTES,
    "str must be 'crypto_pwhash_scryptsalsa208sha256_STRBYTES' bytes"
  )
  assert(passwd.byteLength > 0, 'passwd must not be empty')

  return binding.crypto_pwhash_scryptsalsa208sha256_str_verify(str, passwd)
}

exports.crypto_pwhash_scryptsalsa208sha256_str_needs_rehash = function (str, opslimit, memlimit) {
  assert(ArrayBuffer.isView(str), 'str must be a typed array')
  assert(
    str.byteLength === binding.crypto_pwhash_scryptsalsa208sha256_STRBYTES,
    "str must be 'crypto_pwhash_scryptsalsa208sha256_STRBYTES' bytes"
  )
  assert(
    opslimit >= binding.crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MIN,
    "opslimit must be at least 'crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MIN'"
  )
  assert(
    opslimit <= binding.crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MAX,
    "opslimit must be at most 'crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MAX'"
  )
  assert(
    memlimit >= binding.crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MIN,
    "memlimit must be at least 'crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MIN'"
  )
  assert(
    memlimit <= binding.crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MAX,
    "memlimit must be at most 'crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MAX'"
  )

  return binding.crypto_pwhash_scryptsalsa208sha256_str_needs_rehash(str, opslimit, memlimit)
}

// crypto_kx

exports.crypto_kx_keypair = function (pk, sk) {
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(
    pk.byteLength === binding.crypto_kx_PUBLICKEYBYTES,
    "pk must be 'crypto_kx_PUBLICKEYBYTES' bytes"
  )
  assert(
    sk.byteLength === binding.crypto_kx_SECRETKEYBYTES,
    "sk must be 'crypto_kx_SECRETKEYBYTES' bytes"
  )

  const res = binding.crypto_kx_keypair(pk, sk)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_kx_seed_keypair = function (pk, sk, seed) {
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(ArrayBuffer.isView(seed), 'seed must be a typed array')
  assert(
    pk.byteLength === binding.crypto_kx_PUBLICKEYBYTES,
    "pk must be 'crypto_kx_PUBLICKEYBYTES' bytes"
  )
  assert(
    sk.byteLength === binding.crypto_kx_SECRETKEYBYTES,
    "sk must be 'crypto_kx_SECRETKEYBYTES' bytes"
  )
  assert(
    seed.byteLength === binding.crypto_kx_SEEDBYTES,
    "seed must be 'crypto_kx_SEEDBYTES' bytes"
  )

  const res = binding.crypto_kx_seed_keypair(pk, sk, seed)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_kx_client_session_keys = function (rx, tx, clientPk, clientSk, serverPk) {
  if (!rx) rx = undefined
  if (!tx) tx = undefined

  assert(rx || tx, 'at least one session key must be specified')

  if (rx) {
    assert(ArrayBuffer.isView(rx), 'rx must be a typed array')
    assert(
      rx.byteLength === binding.crypto_kx_SESSIONKEYBYTES,
      "rx must be 'crypto_kx_SESSIONKEYBYTES' bytes"
    )
  }

  if (tx) {
    assert(ArrayBuffer.isView(tx), 'tx must be a typed array')
    assert(
      tx.byteLength === binding.crypto_kx_SESSIONKEYBYTES,
      "tx must be 'crypto_kx_SESSIONKEYBYTES' bytes"
    )
  }

  assert(ArrayBuffer.isView(clientPk), 'clientPk must be a typed array')
  assert(ArrayBuffer.isView(clientSk), 'clientSk must be a typed array')
  assert(ArrayBuffer.isView(serverPk), 'serverPk must be a typed array')
  assert(
    clientPk.byteLength === binding.crypto_kx_PUBLICKEYBYTES,
    "clientPk must be 'crypto_kx_PUBLICKEYBYTES' bytes"
  )
  assert(
    clientSk.byteLength === binding.crypto_kx_SECRETKEYBYTES,
    "clientSk must be 'crypto_kx_SECRETKEYBYTES' bytes"
  )
  assert(
    serverPk.byteLength === binding.crypto_kx_PUBLICKEYBYTES,
    "serverPk must be 'crypto_kx_PUBLICKEYBYTES' bytes"
  )

  const res = binding.crypto_kx_client_session_keys(rx, tx, clientPk, clientSk, serverPk)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_kx_server_session_keys = function (rx, tx, serverPk, serverSk, clientPk) {
  if (!rx) rx = undefined
  if (!tx) tx = undefined

  assert(rx || tx, 'at least one session key must be specified')

  if (rx) {
    assert(ArrayBuffer.isView(rx), 'rx must be a typed array')
    assert(
      rx.byteLength === binding.crypto_kx_SESSIONKEYBYTES,
      "rx must be 'crypto_kx_SESSIONKEYBYTES' bytes"
    )
  }

  if (tx) {
    assert(ArrayBuffer.isView(tx), 'tx must be a typed array')
    assert(
      tx.byteLength === binding.crypto_kx_SESSIONKEYBYTES,
      "tx must be 'crypto_kx_SESSIONKEYBYTES' bytes"
    )
  }

  assert(ArrayBuffer.isView(serverPk), 'serverPk must be a typed array')
  assert(ArrayBuffer.isView(serverSk), 'serverSk must be a typed array')
  assert(ArrayBuffer.isView(clientPk), 'clientPk must be a typed array')
  assert(
    serverPk.byteLength === binding.crypto_kx_PUBLICKEYBYTES,
    "serverPk must be 'crypto_kx_PUBLICKEYBYTES' bytes"
  )
  assert(
    serverSk.byteLength === binding.crypto_kx_SECRETKEYBYTES,
    "serverSk must be 'crypto_kx_SECRETKEYBYTES' bytes"
  )
  assert(
    clientPk.byteLength === binding.crypto_kx_PUBLICKEYBYTES,
    "clientPk must be 'crypto_kx_PUBLICKEYBYTES' bytes"
  )

  const res = binding.crypto_kx_server_session_keys(rx, tx, serverPk, serverSk, clientPk)

  if (res !== 0) throw new Error('status: ' + res)
}

// crypto_scalarmult

exports.crypto_scalarmult_base = function (q, n) {
  assert(ArrayBuffer.isView(q), 'q must be a typed array')
  assert(
    q.byteLength === binding.crypto_scalarmult_BYTES,
    "q must be 'crypto_scalarmult_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.crypto_scalarmult_SCALARBYTES,
    "n must be 'crypto_scalarmult_SCALARBYTES' bytes"
  )

  const res = binding.crypto_scalarmult_base(q, n)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_scalarmult = function (q, n, p) {
  assert(ArrayBuffer.isView(q), 'q must be a typed array')
  assert(
    q.byteLength === binding.crypto_scalarmult_BYTES,
    "q must be 'crypto_scalarmult_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.crypto_scalarmult_SCALARBYTES,
    "n must be 'crypto_scalarmult_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(p), 'p must be a typed array')
  assert(
    p.byteLength === binding.crypto_scalarmult_BYTES,
    "p must be 'crypto_scalarmult_BYTES' bytes"
  )

  const res = binding.crypto_scalarmult(q, n, p)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_scalarmult_ed25519_base = function (q, n) {
  assert(ArrayBuffer.isView(q), 'q must be a typed array')
  assert(
    q.byteLength === binding.crypto_scalarmult_ed25519_BYTES,
    "q must be 'crypto_scalarmult_ed25519_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.crypto_scalarmult_ed25519_SCALARBYTES,
    "n must be 'crypto_scalarmult_ed25519_SCALARBYTES' bytes"
  )

  const res = binding.crypto_scalarmult_ed25519_base(q, n)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_scalarmult_ed25519 = function (q, n, p) {
  assert(ArrayBuffer.isView(q), 'q must be a typed array')
  assert(
    q.byteLength === binding.crypto_scalarmult_ed25519_BYTES,
    "q must be 'crypto_scalarmult_ed25519_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.crypto_scalarmult_ed25519_SCALARBYTES,
    "n must be 'crypto_scalarmult_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(p), 'p must be a typed array')
  assert(
    p.byteLength === binding.crypto_scalarmult_ed25519_BYTES,
    "p must be 'crypto_scalarmult_ed25519_BYTES' bytes"
  )

  const res = binding.crypto_scalarmult_ed25519(q, n, p)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_core_ed25519_is_valid_point = function (p) {
  assert(ArrayBuffer.isView(p), 'p must be a typed array')
  assert(
    p.byteLength === binding.crypto_core_ed25519_BYTES,
    "p must be 'crypto_core_ed25519_BYTES' bytes"
  )

  return binding.crypto_core_ed25519_is_valid_point(p)
}

exports.crypto_core_ed25519_from_uniform = function (p, r) {
  assert(ArrayBuffer.isView(p), 'p must be a typed array')
  assert(
    p.byteLength === binding.crypto_core_ed25519_BYTES,
    "p must be 'crypto_core_ed25519_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(r), 'r must be a typed array')
  assert(
    r.byteLength === binding.crypto_core_ed25519_UNIFORMBYTES,
    "r must be 'crypto_core_ed25519_UNIFORMBYTES' bytes"
  )

  const res = binding.crypto_core_ed25519_from_uniform(p, r)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_scalarmult_ed25519_base_noclamp = function (q, n) {
  assert(ArrayBuffer.isView(q), 'q must be a typed array')
  assert(
    q.byteLength === binding.crypto_scalarmult_ed25519_BYTES,
    "q must be 'crypto_scalarmult_ed25519_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.crypto_scalarmult_ed25519_SCALARBYTES,
    "n must be 'crypto_scalarmult_ed25519_SCALARBYTES' bytes"
  )

  const res = binding.crypto_scalarmult_ed25519_base_noclamp(q, n)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_scalarmult_ed25519_noclamp = function (q, n, p) {
  assert(ArrayBuffer.isView(q), 'q must be a typed array')
  assert(
    q.byteLength === binding.crypto_scalarmult_ed25519_BYTES,
    "q must be 'crypto_scalarmult_ed25519_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.crypto_scalarmult_ed25519_SCALARBYTES,
    "n must be 'crypto_scalarmult_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(p), 'p must be a typed array')
  assert(
    p.byteLength === binding.crypto_scalarmult_ed25519_BYTES,
    "p must be 'crypto_scalarmult_ed25519_BYTES' bytes"
  )

  const res = binding.crypto_scalarmult_ed25519_noclamp(q, n, p)

  if (res !== 0) throw new Error('status: ' + res)
}

// crypto_core

exports.crypto_core_ed25519_add = function (r, p, q) {
  assert(ArrayBuffer.isView(r), 'r must be a typed array')
  assert(
    r.byteLength === binding.crypto_core_ed25519_BYTES,
    "r must be 'crypto_core_ed25519_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(p), 'p must be a typed array')
  assert(
    p.byteLength === binding.crypto_core_ed25519_BYTES,
    "p must be 'crypto_core_ed25519_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(q), 'q must be a typed array')
  assert(
    q.byteLength === binding.crypto_core_ed25519_BYTES,
    "q must be 'crypto_core_ed25519_BYTES' bytes"
  )

  const res = binding.crypto_core_ed25519_add(r, p, q)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_core_ed25519_sub = function (r, p, q) {
  assert(ArrayBuffer.isView(r), 'r must be a typed array')
  assert(
    r.byteLength === binding.crypto_core_ed25519_BYTES,
    "r must be 'crypto_core_ed25519_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(p), 'p must be a typed array')
  assert(
    p.byteLength === binding.crypto_core_ed25519_BYTES,
    "p must be 'crypto_core_ed25519_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(q), 'q must be a typed array')
  assert(
    q.byteLength === binding.crypto_core_ed25519_BYTES,
    "q must be 'crypto_core_ed25519_BYTES' bytes"
  )

  const res = binding.crypto_core_ed25519_sub(r, p, q)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_core_ed25519_scalar_random = function (r) {
  assert(ArrayBuffer.isView(r), 'r must be a typed array')
  assert(
    r.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "r must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )

  binding.crypto_core_ed25519_scalar_random(r)
}

exports.crypto_core_ed25519_scalar_reduce = function (r, s) {
  assert(ArrayBuffer.isView(r), 'r must be a typed array')
  assert(
    r.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "r must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(s), 's must be a typed array')
  assert(
    s.byteLength === binding.crypto_core_ed25519_NONREDUCEDSCALARBYTES,
    "s must be 'crypto_core_ed25519_NONREDUCEDSCALARBYTES' bytes"
  )

  binding.crypto_core_ed25519_scalar_reduce(r, s)
}

exports.crypto_core_ed25519_scalar_invert = function (recip, s) {
  assert(ArrayBuffer.isView(recip), 'recip must be a typed array')
  assert(
    recip.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "recip must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(s), 's must be a typed array')
  assert(
    s.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "s must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )

  binding.crypto_core_ed25519_scalar_invert(recip, s)
}

exports.crypto_core_ed25519_scalar_negate = function (neg, s) {
  assert(ArrayBuffer.isView(neg), 'neg must be a typed array')
  assert(
    neg.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "neg must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(s), 's must be a typed array')
  assert(
    s.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "s must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )

  binding.crypto_core_ed25519_scalar_negate(neg, s)
}

exports.crypto_core_ed25519_scalar_complement = function (comp, s) {
  assert(ArrayBuffer.isView(comp), 'comp must be a typed array')
  assert(
    comp.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "comp must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(s), 's must be a typed array')
  assert(
    s.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "s must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )

  binding.crypto_core_ed25519_scalar_complement(comp, s)
}

exports.crypto_core_ed25519_scalar_add = function (z, x, y) {
  assert(ArrayBuffer.isView(z), 'z must be a typed array')
  assert(
    z.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "z must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(x), 'x must be a typed array')
  assert(
    x.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "x must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(y), 'y must be a typed array')
  assert(
    y.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "y must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )

  binding.crypto_core_ed25519_scalar_add(z, x, y)
}

exports.crypto_core_ed25519_scalar_sub = function (z, x, y) {
  assert(ArrayBuffer.isView(z), 'z must be a typed array')
  assert(
    z.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "z must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(x), 'x must be a typed array')
  assert(
    x.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "x must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(y), 'y must be a typed array')
  assert(
    y.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "y must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )

  binding.crypto_core_ed25519_scalar_sub(z, x, y)
}

// crypto_shorthash

exports.crypto_shorthash = function (out, input, k) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(
    out.byteLength === binding.crypto_shorthash_BYTES,
    "out must be 'crypto_shorthash_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_shorthash_KEYBYTES,
    "k must be 'crypto_shorthash_KEYBYTES' bytes"
  )

  const res = binding.crypto_shorthash(out, input, k)

  if (res !== 0) throw new Error('status: ' + res)
}

// crypto_kdf

exports.crypto_kdf_keygen = function (key) {
  assert(ArrayBuffer.isView(key), 'key must be a typed array')
  assert(key.byteLength === binding.crypto_kdf_KEYBYTES, "key must be 'crypto_kdf_KEYBYTES' bytes")

  binding.crypto_kdf_keygen(key)
}

exports.crypto_kdf_derive_from_key = function (subkey, subkeyId, ctx, key) {
  assert(ArrayBuffer.isView(subkey), 'subkey must be a typed array')
  assert(
    subkey.byteLength >= binding.crypto_kdf_BYTES_MIN,
    "subkey must be at least 'crypto_kdf_BYTES_MIN' bytes"
  )
  assert(
    subkey.byteLength <= binding.crypto_kdf_BYTES_MAX,
    "subkey must be at most 'crypto_kdf_BYTES_MAX' bytes"
  )
  assert(ArrayBuffer.isView(ctx), 'ctx must be a typed array')
  assert(
    ctx.byteLength === binding.crypto_kdf_CONTEXTBYTES,
    "ctx must be 'crypto_kdf_CONTEXTBYTES' bytes"
  )
  assert(ArrayBuffer.isView(key), 'key must be a typed array')
  assert(key.byteLength === binding.crypto_kdf_KEYBYTES, "key must be 'crypto_kdf_KEYBYTES' bytes")

  const res = binding.crypto_kdf_derive_from_key(subkey, subkeyId, ctx, key)

  if (res !== 0) throw new Error('status: ' + res)
}

// crypto_hash

exports.crypto_hash = function (out, input) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(out.byteLength === binding.crypto_hash_BYTES, "out must be 'crypto_hash_BYTES' bytes")

  const res = binding.crypto_hash(out, input)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_hash_sha256 = function (out, input) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(
    out.byteLength === binding.crypto_hash_sha256_BYTES,
    "out must be 'crypto_hash_sha256_BYTES' bytes"
  )

  const res = binding.crypto_hash_sha256(out, input)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_hash_sha256_init = function (state) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_hash_sha256_STATEBYTES,
    "state must be 'crypto_hash_sha256_STATEBYTES' bytes"
  )

  const res = binding.crypto_hash_sha256_init(state)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_hash_sha256_update = function (state, input) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(
    state.byteLength === binding.crypto_hash_sha256_STATEBYTES,
    "state must be 'crypto_hash_sha256_STATEBYTES' bytes"
  )

  const res = binding.crypto_hash_sha256_update(state, input)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_hash_sha256_final = function (state, out) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_hash_sha256_STATEBYTES,
    "state must be 'crypto_hash_sha256_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(
    out.byteLength === binding.crypto_hash_sha256_BYTES,
    "out must be 'crypto_hash_sha256_BYTES' bytes"
  )

  const res = binding.crypto_hash_sha256_final(state, out)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_hash_sha512 = function (out, input) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(
    out.byteLength === binding.crypto_hash_sha512_BYTES,
    "out must be 'crypto_hash_sha512_BYTES' bytes"
  )

  const res = binding.crypto_hash_sha512(out, input)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_hash_sha512_init = function (state) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_hash_sha512_STATEBYTES,
    "state must be 'crypto_hash_sha512_STATEBYTES' bytes"
  )

  const res = binding.crypto_hash_sha512_init(state)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_hash_sha512_update = function (state, input) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(
    state.byteLength === binding.crypto_hash_sha512_STATEBYTES,
    "state must be 'crypto_hash_sha512_STATEBYTES' bytes"
  )

  const res = binding.crypto_hash_sha512_update(state, input)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_hash_sha512_final = function (state, out) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_hash_sha512_STATEBYTES,
    "state must be 'crypto_hash_sha512_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(
    out.byteLength === binding.crypto_hash_sha512_BYTES,
    "out must be 'crypto_hash_sha512_BYTES' bytes"
  )

  const res = binding.crypto_hash_sha512_final(state, out)

  if (res !== 0) throw new Error('status: ' + res)
}

// crypto_aead

exports.crypto_aead_xchacha20poly1305_ietf_keygen = function (k) {
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_aead_xchacha20poly1305_ietf_KEYBYTES,
    "k must be 'crypto_aead_xchacha20poly1305_ietf_KEYBYTES' bytes"
  )

  binding.crypto_aead_xchacha20poly1305_ietf_keygen(k)
}

exports.crypto_aead_xchacha20poly1305_ietf_encrypt = function (c, m, ad, nsec, npub, k) {
  if (!ad) ad = undefined

  assert(nsec === null, 'nsec must always be set to null')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(
    c.byteLength === m.byteLength + binding.crypto_aead_xchacha20poly1305_ietf_ABYTES,
    "c must be 'm.byteLength + crypto_aead_xchacha20poly1305_ietf_ABYTES' bytes"
  )
  assert(c.byteLength <= 0xffffffff, 'c.byteLength must be a 32bit integer')
  assert(ArrayBuffer.isView(npub), 'npub must be a typed array')
  assert(
    npub.byteLength === binding.crypto_aead_xchacha20poly1305_ietf_NPUBBYTES,
    "npub must be 'crypto_aead_xchacha20poly1305_ietf_NPUBBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_aead_xchacha20poly1305_ietf_KEYBYTES,
    "k must be 'crypto_aead_xchacha20poly1305_ietf_KEYBYTES' bytes"
  )

  const res = binding.crypto_aead_xchacha20poly1305_ietf_encrypt(c, m, ad, npub, k)

  if (res < 0) throw new Error('could not encrypt data')

  return res
}

exports.crypto_aead_xchacha20poly1305_ietf_decrypt = function (m, nsec, c, ad, npub, k) {
  if (!ad) ad = undefined

  assert(nsec === null, 'nsec must always be set to null')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(
    m.byteLength === c.byteLength - binding.crypto_aead_xchacha20poly1305_ietf_ABYTES,
    "m must be 'c.byteLength - crypto_aead_xchacha20poly1305_ietf_ABYTES' bytes"
  )
  assert(m.byteLength <= 0xffffffff, 'm.byteLength must be a 32bit integer')
  assert(ArrayBuffer.isView(npub), 'npub must be a typed array')
  assert(
    npub.byteLength === binding.crypto_aead_xchacha20poly1305_ietf_NPUBBYTES,
    "npub must be 'crypto_aead_xchacha20poly1305_ietf_NPUBBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_aead_xchacha20poly1305_ietf_KEYBYTES,
    "k must be 'crypto_aead_xchacha20poly1305_ietf_KEYBYTES' bytes"
  )

  const res = binding.crypto_aead_xchacha20poly1305_ietf_decrypt(m, c, ad, npub, k)

  if (res < 0) throw new Error('could not verify data')

  return res
}

exports.crypto_aead_xchacha20poly1305_ietf_encrypt_detached = function (
  c,
  mac,
  m,
  ad,
  nsec,
  npub,
  k
) {
  if (!ad) ad = undefined

  assert(nsec === null, 'nsec must always be set to null')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(ArrayBuffer.isView(mac), 'mac must be a typed array')
  assert(
    mac.byteLength === binding.crypto_aead_xchacha20poly1305_ietf_ABYTES,
    "mac must be 'crypto_aead_xchacha20poly1305_ietf_ABYTES' bytes"
  )
  assert(ArrayBuffer.isView(npub), 'npub must be a typed array')
  assert(
    npub.byteLength === binding.crypto_aead_xchacha20poly1305_ietf_NPUBBYTES,
    "npub must be 'crypto_aead_xchacha20poly1305_ietf_NPUBBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_aead_xchacha20poly1305_ietf_KEYBYTES,
    "k must be 'crypto_aead_xchacha20poly1305_ietf_KEYBYTES' bytes"
  )

  const res = binding.crypto_aead_xchacha20poly1305_ietf_encrypt_detached(c, mac, m, ad, npub, k)

  if (res < 0) throw new Error('could not encrypt data')

  return res
}

exports.crypto_aead_xchacha20poly1305_ietf_decrypt_detached = function (
  m,
  nsec,
  c,
  mac,
  ad,
  npub,
  k
) {
  if (!ad) ad = undefined

  assert(nsec === null, 'nsec must always be set to null')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(m.byteLength === c.byteLength, "m must be 'c.byteLength' bytes")
  assert(ArrayBuffer.isView(mac), 'mac must be a typed array')
  assert(
    mac.byteLength === binding.crypto_aead_xchacha20poly1305_ietf_ABYTES,
    "mac must be 'crypto_aead_xchacha20poly1305_ietf_ABYTES' bytes"
  )
  assert(ArrayBuffer.isView(npub), 'npub must be a typed array')
  assert(
    npub.byteLength === binding.crypto_aead_xchacha20poly1305_ietf_NPUBBYTES,
    "npub must be 'crypto_aead_xchacha20poly1305_ietf_NPUBBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_aead_xchacha20poly1305_ietf_KEYBYTES,
    "k must be 'crypto_aead_xchacha20poly1305_ietf_KEYBYTES' bytes"
  )

  const res = binding.crypto_aead_xchacha20poly1305_ietf_decrypt_detached(m, c, mac, ad, npub, k)

  if (res !== 0) throw new Error('could not verify data')
}

exports.crypto_aead_chacha20poly1305_ietf_keygen = function (k) {
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_aead_chacha20poly1305_ietf_KEYBYTES,
    "k must be 'crypto_aead_chacha20poly1305_ietf_KEYBYTES' bytes"
  )

  binding.crypto_aead_chacha20poly1305_ietf_keygen(k)
}

exports.crypto_aead_chacha20poly1305_ietf_encrypt = function (c, m, ad, nsec, npub, k) {
  if (!ad) ad = undefined

  assert(nsec === null, 'nsec must always be set to null')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(
    c.byteLength === m.byteLength + binding.crypto_aead_chacha20poly1305_ietf_ABYTES,
    "c must be 'm.byteLength + crypto_aead_chacha20poly1305_ietf_ABYTES' bytes"
  )
  assert(c.byteLength <= 0xffffffff, 'c.byteLength must be a 32bit integer')
  assert(ArrayBuffer.isView(npub), 'npub must be a typed array')
  assert(
    npub.byteLength === binding.crypto_aead_chacha20poly1305_ietf_NPUBBYTES,
    "npub must be 'crypto_aead_chacha20poly1305_ietf_NPUBBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_aead_chacha20poly1305_ietf_KEYBYTES,
    "k must be 'crypto_aead_chacha20poly1305_ietf_KEYBYTES' bytes"
  )

  const res = binding.crypto_aead_chacha20poly1305_ietf_encrypt(c, m, ad, npub, k)

  if (res < 0) throw new Error('could not encrypt data')

  return res
}

exports.crypto_aead_chacha20poly1305_ietf_decrypt = function (m, nsec, c, ad, npub, k) {
  if (!ad) ad = undefined

  assert(nsec === null, 'nsec must always be set to null')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(
    m.byteLength === c.byteLength - binding.crypto_aead_chacha20poly1305_ietf_ABYTES,
    "m must be 'c.byteLength - crypto_aead_chacha20poly1305_ietf_ABYTES' bytes"
  )
  assert(m.byteLength <= 0xffffffff, 'm.byteLength must be a 32bit integer')
  assert(ArrayBuffer.isView(npub), 'npub must be a typed array')
  assert(
    npub.byteLength === binding.crypto_aead_chacha20poly1305_ietf_NPUBBYTES,
    "npub must be 'crypto_aead_chacha20poly1305_ietf_NPUBBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_aead_chacha20poly1305_ietf_KEYBYTES,
    "k must be 'crypto_aead_chacha20poly1305_ietf_KEYBYTES' bytes"
  )

  const res = binding.crypto_aead_chacha20poly1305_ietf_decrypt(m, c, ad, npub, k)

  if (res < 0) throw new Error('could not verify data')

  return res
}

exports.crypto_aead_chacha20poly1305_ietf_encrypt_detached = function (
  c,
  mac,
  m,
  ad,
  nsec,
  npub,
  k
) {
  if (!ad) ad = undefined

  assert(nsec === null, 'nsec must always be set to null')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(ArrayBuffer.isView(mac), 'mac must be a typed array')
  assert(
    mac.byteLength === binding.crypto_aead_chacha20poly1305_ietf_ABYTES,
    "mac must be 'crypto_aead_chacha20poly1305_ietf_ABYTES' bytes"
  )
  assert(ArrayBuffer.isView(npub), 'npub must be a typed array')
  assert(
    npub.byteLength === binding.crypto_aead_chacha20poly1305_ietf_NPUBBYTES,
    "npub must be 'crypto_aead_chacha20poly1305_ietf_NPUBBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_aead_chacha20poly1305_ietf_KEYBYTES,
    "k must be 'crypto_aead_chacha20poly1305_ietf_KEYBYTES' bytes"
  )

  const res = binding.crypto_aead_chacha20poly1305_ietf_encrypt_detached(c, mac, m, ad, npub, k)

  if (res < 0) throw new Error('could not encrypt data')

  return res
}

exports.crypto_aead_chacha20poly1305_ietf_decrypt_detached = function (
  m,
  nsec,
  c,
  mac,
  ad,
  npub,
  k
) {
  if (!ad) ad = undefined

  assert(nsec === null, 'nsec must always be set to null')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(m.byteLength === c.byteLength, "m must be 'c.byteLength' bytes")
  assert(ArrayBuffer.isView(mac), 'mac must be a typed array')
  assert(
    mac.byteLength === binding.crypto_aead_chacha20poly1305_ietf_ABYTES,
    "mac must be 'crypto_aead_chacha20poly1305_ietf_ABYTES' bytes"
  )
  assert(ArrayBuffer.isView(npub), 'npub must be a typed array')
  assert(
    npub.byteLength === binding.crypto_aead_chacha20poly1305_ietf_NPUBBYTES,
    "npub must be 'crypto_aead_chacha20poly1305_ietf_NPUBBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_aead_chacha20poly1305_ietf_KEYBYTES,
    "k must be 'crypto_aead_chacha20poly1305_ietf_KEYBYTES' bytes"
  )

  const res = binding.crypto_aead_chacha20poly1305_ietf_decrypt_detached(m, c, mac, ad, npub, k)

  if (res !== 0) throw new Error('could not verify data')
}

// crypto_stream

exports.crypto_stream_xor_wrap_init = function (state, n, k) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.sn_crypto_stream_xor_STATEBYTES,
    "state must be 'sn_crypto_stream_xor_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.crypto_stream_NONCEBYTES,
    "n must be 'crypto_stream_NONCEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_stream_KEYBYTES,
    "k must be 'crypto_stream_KEYBYTES' bytes"
  )

  binding.crypto_stream_xor_wrap_init(state, n, k)
}

exports.crypto_stream_xor_wrap_update = function (state, c, m) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.sn_crypto_stream_xor_STATEBYTES,
    "state must be 'sn_crypto_stream_xor_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")

  binding.crypto_stream_xor_wrap_update(state, c, m)
}

exports.crypto_stream_xor_wrap_final = function (state) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.sn_crypto_stream_xor_STATEBYTES,
    "state must be 'sn_crypto_stream_xor_STATEBYTES' bytes"
  )

  binding.crypto_stream_xor_wrap_final(state)
}

exports.crypto_stream_chacha20_xor_wrap_init = function (state, n, k) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_stream_chacha20_xor_STATEBYTES,
    "state must be 'crypto_stream_chacha20_xor_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.crypto_stream_chacha20_NONCEBYTES,
    "n must be 'crypto_stream_chacha20_NONCEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_stream_chacha20_KEYBYTES,
    "k must be 'crypto_stream_chacha20_KEYBYTES' bytes"
  )

  binding.crypto_stream_chacha20_xor_wrap_init(state, n, k)
}

exports.crypto_stream_chacha20_xor_wrap_update = function (state, c, m) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_stream_chacha20_xor_STATEBYTES,
    "state must be 'crypto_stream_chacha20_xor_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")

  binding.crypto_stream_chacha20_xor_wrap_update(state, c, m)
}

exports.crypto_stream_chacha20_xor_wrap_final = function (state) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_stream_chacha20_xor_STATEBYTES,
    "state must be 'crypto_stream_chacha20_xor_STATEBYTES' bytes"
  )

  binding.crypto_stream_chacha20_xor_wrap_final(state)
}

exports.crypto_stream_chacha20_ietf_xor_wrap_init = function (state, n, k) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_stream_chacha20_ietf_xor_STATEBYTES,
    "state must be 'crypto_stream_chacha20_ietf_xor_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.crypto_stream_chacha20_ietf_NONCEBYTES,
    "n must be 'crypto_stream_chacha20_ietf_NONCEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_stream_chacha20_ietf_KEYBYTES,
    "k must be 'crypto_stream_chacha20_ietf_KEYBYTES' bytes"
  )

  binding.crypto_stream_chacha20_ietf_xor_wrap_init(state, n, k)
}

exports.crypto_stream_chacha20_ietf_xor_wrap_update = function (state, c, m) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_stream_chacha20_ietf_xor_STATEBYTES,
    "state must be 'crypto_stream_chacha20_ietf_xor_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")

  binding.crypto_stream_chacha20_ietf_xor_wrap_update(state, c, m)
}

exports.crypto_stream_chacha20_ietf_xor_wrap_final = function (state) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_stream_chacha20_ietf_xor_STATEBYTES,
    "state must be 'crypto_stream_chacha20_ietf_xor_STATEBYTES' bytes"
  )

  binding.crypto_stream_chacha20_ietf_xor_wrap_final(state)
}

exports.crypto_stream_xchacha20_xor_wrap_init = function (state, n, k) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_stream_xchacha20_xor_STATEBYTES,
    "state must be 'crypto_stream_xchacha20_xor_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.crypto_stream_xchacha20_NONCEBYTES,
    "n must be 'crypto_stream_xchacha20_NONCEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_stream_xchacha20_KEYBYTES,
    "k must be 'crypto_stream_xchacha20_KEYBYTES' bytes"
  )

  binding.crypto_stream_xchacha20_xor_wrap_init(state, n, k)
}

exports.crypto_stream_xchacha20_xor_wrap_update = function (state, c, m) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_stream_xchacha20_xor_STATEBYTES,
    "state must be 'crypto_stream_xchacha20_xor_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")

  binding.crypto_stream_xchacha20_xor_wrap_update(state, c, m)
}

exports.crypto_stream_xchacha20_xor_wrap_final = function (state) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_stream_xchacha20_xor_STATEBYTES,
    "state must be 'crypto_stream_xchacha20_xor_STATEBYTES' bytes"
  )

  binding.crypto_stream_xchacha20_xor_wrap_final(state)
}

exports.crypto_stream_salsa20_xor_wrap_init = function (state, n, k) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_stream_salsa20_xor_STATEBYTES,
    "state must be 'crypto_stream_salsa20_xor_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.crypto_stream_salsa20_NONCEBYTES,
    "n must be 'crypto_stream_salsa20_NONCEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_stream_salsa20_KEYBYTES,
    "k must be 'crypto_stream_salsa20_KEYBYTES' bytes"
  )

  binding.crypto_stream_salsa20_xor_wrap_init(state, n, k)
}

exports.crypto_stream_salsa20_xor_wrap_update = function (state, c, m) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_stream_salsa20_xor_STATEBYTES,
    "state must be 'crypto_stream_salsa20_xor_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")

  binding.crypto_stream_salsa20_xor_wrap_update(state, c, m)
}

exports.crypto_stream_salsa20_xor_wrap_final = function (state) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_stream_salsa20_xor_STATEBYTES,
    "state must be 'crypto_stream_salsa20_xor_STATEBYTES' bytes"
  )

  binding.crypto_stream_salsa20_xor_wrap_final(state)
}

// experimental

exports.extension_tweak_ed25519_base = function (n, p, ns) {
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "n must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(p), 'p must be a typed array')
  assert(
    p.byteLength === binding.extension_tweak_ed25519_BYTES,
    "p must be 'extension_tweak_ed25519_BYTES' bytes"
  )

  binding.extension_tweak_ed25519_base(n, p, ns)
}

exports.extension_tweak_ed25519_sign_detached = function (sig, m, scalar, pk) {
  assert(ArrayBuffer.isView(sig), 'sig must be a typed array')
  assert(sig.byteLength === binding.crypto_sign_BYTES, "sig must be 'crypto_sign_BYTES' bytes")
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(scalar), 'scalar must be a typed array')
  assert(
    scalar.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "scalar must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )

  if (pk) {
    assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
    assert(
      pk.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
      "pk must be 'crypto_sign_PUBLICKEYBYTES' bytes"
    )
  }

  const res = binding.extension_tweak_ed25519_sign_detached(sig, m, scalar, pk)

  if (res !== 0) throw new Error('failed to compute signature')
}

exports.extension_tweak_ed25519_sk_to_scalar = function (n, sk) {
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "n must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(
    sk.byteLength === binding.crypto_sign_SECRETKEYBYTES,
    "sk must be 'crypto_sign_SECRETKEYBYTES' bytes"
  )

  binding.extension_tweak_ed25519_sk_to_scalar(n, sk)
}

exports.extension_tweak_ed25519_scalar = function (scalarOut, scalar, ns) {
  assert(ArrayBuffer.isView(scalarOut), 'scalarOut must be a typed array')
  assert(
    scalarOut.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "scalarOut must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(scalar), 'scalar must be a typed array')
  assert(
    scalar.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "scalar must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )

  binding.extension_tweak_ed25519_scalar(scalarOut, scalar, ns)
}

exports.extension_tweak_ed25519_pk = function (tpk, pk, ns) {
  assert(ArrayBuffer.isView(tpk), 'tpk must be a typed array')
  assert(
    tpk.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
    "tpk must be 'crypto_sign_PUBLICKEYBYTES' bytes"
  )
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(
    pk.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
    "pk must be 'crypto_sign_PUBLICKEYBYTES' bytes"
  )

  const res = binding.extension_tweak_ed25519_pk(tpk, pk, ns)

  if (res !== 0) throw new Error('failed to tweak public key')
}

exports.extension_tweak_ed25519_keypair = function (pk, scalarOut, scalarIn, ns) {
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(
    pk.byteLength === binding.extension_tweak_ed25519_BYTES,
    "pk must be 'extension_tweak_ed25519_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(scalarOut), 'scalarOut must be a typed array')
  assert(
    scalarOut.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "scalarOut must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(scalarIn), 'scalarIn must be a typed array')
  assert(
    scalarIn.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "scalarIn must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )

  binding.extension_tweak_ed25519_keypair(pk, scalarOut, scalarIn, ns)
}

exports.extension_tweak_ed25519_scalar_add = function (scalarOut, scalar, n) {
  assert(ArrayBuffer.isView(scalarOut), 'scalarOut must be a typed array')
  assert(
    scalarOut.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "scalarOut must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(scalar), 'scalar must be a typed array')
  assert(
    scalar.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "scalar must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "n must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )

  binding.extension_tweak_ed25519_scalar_add(scalarOut, scalar, n)
}

exports.extension_tweak_ed25519_pk_add = function (tpk, pk, p) {
  assert(ArrayBuffer.isView(tpk), 'tpk must be a typed array')
  assert(
    tpk.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
    "tpk must be 'crypto_sign_PUBLICKEYBYTES' bytes"
  )
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(
    pk.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
    "pk must be 'crypto_sign_PUBLICKEYBYTES' bytes"
  )
  assert(ArrayBuffer.isView(p), 'p must be a typed array')
  assert(
    p.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
    "p must be 'crypto_sign_PUBLICKEYBYTES' bytes"
  )

  const res = binding.extension_tweak_ed25519_pk_add(tpk, pk, p)

  if (res !== 0) throw new Error('failed to add tweak to public key')
}

exports.extension_tweak_ed25519_keypair_add = function (pk, scalarOut, scalarIn, tweak) {
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(
    pk.byteLength === binding.extension_tweak_ed25519_BYTES,
    "pk must be 'extension_tweak_ed25519_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(scalarOut), 'scalarOut must be a typed array')
  assert(
    scalarOut.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "scalarOut must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(scalarIn), 'scalarIn must be a typed array')
  assert(
    scalarIn.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "scalarIn must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(tweak), 'tweak must be a typed array')
  assert(
    tweak.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "tweak must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )

  const res = binding.extension_tweak_ed25519_keypair_add(pk, scalarOut, scalarIn, tweak)

  if (res !== 0) throw new Error('failed to add tweak to keypair')
}

exports.extension_pbkdf2_sha512_async = function (out, passwd, salt, iter, outlen, callback) {
  assert(
    iter >= binding.extension_pbkdf2_sha512_ITERATIONS_MIN,
    "iter must be at least 'extension_pbkdf2_sha512_ITERATIONS_MIN'"
  )
  assert(
    outlen <= binding.extension_pbkdf2_sha512_BYTES_MAX,
    "outlen must be at most 'extension_pbkdf2_sha512_BYTES_MAX'"
  )
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(out.byteLength >= outlen, "out must be at least 'outlen' bytes")
  assert(out.byteLength > 0, 'out must not be empty')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(passwd.byteLength > 0, 'passwd must not be empty')
  assert(ArrayBuffer.isView(salt), 'salt must be a typed array')
  assert(salt.byteLength > 0, 'salt must not be empty')

  const [done, promise] = checkStatus(callback)

  binding.extension_pbkdf2_sha512_async(
    out.buffer,
    out.byteOffset,
    out.byteLength,

    passwd.buffer,
    passwd.byteOffset,
    passwd.byteLength,

    salt.buffer,
    salt.byteOffset,
    salt.byteLength,

    iter,
    outlen,

    done
  )

  return promise
}

exports.extension_pbkdf2_sha512 = function (out, passwd, salt, iter, outlen) {
  assert(
    iter >= binding.extension_pbkdf2_sha512_ITERATIONS_MIN,
    "iter must be at least 'extension_pbkdf2_sha512_ITERATIONS_MIN'"
  )
  assert(
    outlen <= binding.extension_pbkdf2_sha512_BYTES_MAX,
    "outlen must be at most 'extension_pbkdf2_sha512_BYTES_MAX'"
  )
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(out.byteLength >= outlen, "out must be at least 'outlen' bytes")
  assert(out.byteLength > 0, 'out must not be empty')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(passwd.byteLength > 0, 'passwd must not be empty')
  assert(ArrayBuffer.isView(salt), 'salt must be a typed array')
  assert(salt.byteLength > 0, 'salt must not be empty')

  const res = binding.extension_pbkdf2_sha512(out, passwd, salt, iter, outlen)

  if (res !== 0) throw new Error('failed to add tweak to public key')
}

function checkStatus(callback, booleanResult = false) {
  let done, promise

  if (typeof callback === 'function') {
    done = function (status) {
      if (booleanResult) callback(null, status === 0)
      else if (status === 0) callback(null)
      else callback(new Error('status: ' + status))
    }
  } else {
    promise = new Promise(function (resolve, reject) {
      done = function (status) {
        if (booleanResult) resolve(status === 0)
        else if (status === 0) resolve()
        else reject(new Error('status: ' + status))
      }
    })
  }

  return [done, promise]
}
{
  "name": "sodium-native",
  "version": "5.1.0",
  "description": "Low level bindings for libsodium",
  "main": "index.js",
  "files": [
    "index.js",
    "binding.cc",
    "binding.js",
    "extensions",
    "prebuilds",
    "CMakeLists.txt"
  ],
  "imports": {
    "assert": {
      "bare": "bare-assert",
      "node": "assert"
    }
  },
  "addon": true,
  "dependencies": {
    "bare-assert": "^1.2.0",
    "require-addon": "^1.1.0",
    "which-runtime": "^1.2.1"
  },
  "devDependencies": {
    "bare-compat-napi": "^1.3.5",
    "brittle": "^3.16.2",
    "cmake-bare": "^1.6.1",
    "cmake-fetch": "^1.4.3",
    "cmake-napi": "^1.2.1",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^2.0.0"
  },
  "scripts": {
    "test": "prettier . --check && npm run test:node && npm run test:bare",
    "test:node": "node test/all.js",
    "test:bare": "bare test/all.js"
  },
  "standard": {
    "ignore": [
      "/test/fixtures/*.js"
    ]
  },
  "engines": {
    "bare": ">=1.16.0"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/sodium-native.git"
  },
  "contributors": [
    "Emil Bay <github@tixz.dk> (http://bayes.dk)",
    "Mathias Buus <mathiasbuus@gmail.com> (https://mafinto.sh)",
    "Christophe Diederichs <chm-diederichs@hyperdivision.dk>"
  ],
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/holepunchto/sodium-native/issues"
  },
  "homepage": "https://github.com/holepunchto/sodium-native"
}
module.exports = require('sodium-native')
{
  "name": "sodium-universal",
  "version": "5.0.1",
  "description": "Universal wrapper for sodium-javascript and sodium-native working in Node.js and the Browser",
  "main": "index.js",
  "dependencies": {
    "sodium-native": "^5.0.1"
  },
  "peerDependencies": {
    "sodium-javascript": "~0.8.0"
  },
  "peerDependenciesMeta": {
    "sodium-javascript": {
      "optional": true
    }
  },
  "scripts": {
    "prepublish": "./build-scripts/generate.js"
  },
  "browser": {
    "sodium-native": "sodium-javascript"
  },
  "browserify": {
    "transform": [
      "./build-scripts/transform.js"
    ]
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/sodium-universal.git"
  },
  "keywords": [
    "libsodium",
    "sodium",
    "sodium-native",
    "sodium-javascript",
    "browserify"
  ],
  "contributors": [
    "Emil Bay <github@tixz.dk> (http://bayes.dk)",
    "Mathias Buus <mathiasbuus@gmail.com> (https://mafinto.sh)",
    "Christophe Diederichs <chm-diederichs@hyperdivision.dk>"
  ],
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/holepunchto/sodium-universal/issues"
  },
  "homepage": "https://github.com/holepunchto/sodium-universal#readme"
}
const { EventEmitter } = require('events-universal')
const STREAM_DESTROYED = new Error('Stream was destroyed')
const PREMATURE_CLOSE = new Error('Premature close')

const FIFO = require('fast-fifo')
const TextDecoder = require('text-decoder')

// if we do a future major, expect queue microtask to be there always, for now a bit defensive
const qmt =
  typeof queueMicrotask === 'undefined' ? (fn) => global.process.nextTick(fn) : queueMicrotask

// 29 bits used total (4 from shared, 14 from read, and 11 from write)
const MAX = (1 << 29) - 1

// Shared state
const OPENING = 0b0001
const PREDESTROYING = 0b0010
const DESTROYING = 0b0100
const DESTROYED = 0b1000

const NOT_OPENING = MAX ^ OPENING
const NOT_PREDESTROYING = MAX ^ PREDESTROYING

// Read state (4 bit offset from shared state)
const READ_ACTIVE = 0b00000000000001 << 4
const READ_UPDATING = 0b00000000000010 << 4
const READ_PRIMARY = 0b00000000000100 << 4
const READ_QUEUED = 0b00000000001000 << 4
const READ_RESUMED = 0b00000000010000 << 4
const READ_PIPE_DRAINED = 0b00000000100000 << 4
const READ_ENDING = 0b00000001000000 << 4
const READ_EMIT_DATA = 0b00000010000000 << 4
const READ_EMIT_READABLE = 0b00000100000000 << 4
const READ_EMITTED_READABLE = 0b00001000000000 << 4
const READ_DONE = 0b00010000000000 << 4
const READ_NEXT_TICK = 0b00100000000000 << 4
const READ_NEEDS_PUSH = 0b01000000000000 << 4
const READ_READ_AHEAD = 0b10000000000000 << 4

// Combined read state
const READ_FLOWING = READ_RESUMED | READ_PIPE_DRAINED
const READ_ACTIVE_AND_NEEDS_PUSH = READ_ACTIVE | READ_NEEDS_PUSH
const READ_PRIMARY_AND_ACTIVE = READ_PRIMARY | READ_ACTIVE
const READ_EMIT_READABLE_AND_QUEUED = READ_EMIT_READABLE | READ_QUEUED
const READ_RESUMED_READ_AHEAD = READ_RESUMED | READ_READ_AHEAD

const READ_NOT_ACTIVE = MAX ^ READ_ACTIVE
const READ_NON_PRIMARY = MAX ^ READ_PRIMARY
const READ_NON_PRIMARY_AND_PUSHED = MAX ^ (READ_PRIMARY | READ_NEEDS_PUSH)
const READ_PUSHED = MAX ^ READ_NEEDS_PUSH
const READ_PAUSED = MAX ^ READ_RESUMED
const READ_NOT_QUEUED = MAX ^ (READ_QUEUED | READ_EMITTED_READABLE)
const READ_NOT_ENDING = MAX ^ READ_ENDING
const READ_PIPE_NOT_DRAINED = MAX ^ READ_FLOWING
const READ_NOT_NEXT_TICK = MAX ^ READ_NEXT_TICK
const READ_NOT_UPDATING = MAX ^ READ_UPDATING
const READ_NO_READ_AHEAD = MAX ^ READ_READ_AHEAD
const READ_PAUSED_NO_READ_AHEAD = MAX ^ READ_RESUMED_READ_AHEAD

// Write state (18 bit offset, 4 bit offset from shared state and 14 from read state)
const WRITE_ACTIVE = 0b00000000001 << 18
const WRITE_UPDATING = 0b00000000010 << 18
const WRITE_PRIMARY = 0b00000000100 << 18
const WRITE_QUEUED = 0b00000001000 << 18
const WRITE_UNDRAINED = 0b00000010000 << 18
const WRITE_DONE = 0b00000100000 << 18
const WRITE_EMIT_DRAIN = 0b00001000000 << 18
const WRITE_NEXT_TICK = 0b00010000000 << 18
const WRITE_WRITING = 0b00100000000 << 18
const WRITE_FINISHING = 0b01000000000 << 18
const WRITE_CORKED = 0b10000000000 << 18

const WRITE_NOT_ACTIVE = MAX ^ (WRITE_ACTIVE | WRITE_WRITING)
const WRITE_NON_PRIMARY = MAX ^ WRITE_PRIMARY
const WRITE_NOT_FINISHING = MAX ^ (WRITE_ACTIVE | WRITE_FINISHING)
const WRITE_DRAINED = MAX ^ WRITE_UNDRAINED
const WRITE_NOT_QUEUED = MAX ^ WRITE_QUEUED
const WRITE_NOT_NEXT_TICK = MAX ^ WRITE_NEXT_TICK
const WRITE_NOT_UPDATING = MAX ^ WRITE_UPDATING
const WRITE_NOT_CORKED = MAX ^ WRITE_CORKED

// Combined shared state
const ACTIVE = READ_ACTIVE | WRITE_ACTIVE
const NOT_ACTIVE = MAX ^ ACTIVE
const DONE = READ_DONE | WRITE_DONE
const DESTROY_STATUS = DESTROYING | DESTROYED | PREDESTROYING
const OPEN_STATUS = DESTROY_STATUS | OPENING
const AUTO_DESTROY = DESTROY_STATUS | DONE
const NON_PRIMARY = WRITE_NON_PRIMARY & READ_NON_PRIMARY
const ACTIVE_OR_TICKING = WRITE_NEXT_TICK | READ_NEXT_TICK
const TICKING = ACTIVE_OR_TICKING & NOT_ACTIVE
const IS_OPENING = OPEN_STATUS | TICKING

// Combined shared state and read state
const READ_PRIMARY_STATUS = OPEN_STATUS | READ_ENDING | READ_DONE
const READ_STATUS = OPEN_STATUS | READ_DONE | READ_QUEUED
const READ_ENDING_STATUS = OPEN_STATUS | READ_ENDING | READ_QUEUED
const READ_READABLE_STATUS = OPEN_STATUS | READ_EMIT_READABLE | READ_QUEUED | READ_EMITTED_READABLE
const SHOULD_NOT_READ =
  OPEN_STATUS | READ_ACTIVE | READ_ENDING | READ_DONE | READ_NEEDS_PUSH | READ_READ_AHEAD
const READ_BACKPRESSURE_STATUS = DESTROY_STATUS | READ_ENDING | READ_DONE
const READ_UPDATE_SYNC_STATUS = READ_UPDATING | OPEN_STATUS | READ_NEXT_TICK | READ_PRIMARY
const READ_NEXT_TICK_OR_OPENING = READ_NEXT_TICK | OPENING

// Combined write state
const WRITE_PRIMARY_STATUS = OPEN_STATUS | WRITE_FINISHING | WRITE_DONE
const WRITE_QUEUED_AND_UNDRAINED = WRITE_QUEUED | WRITE_UNDRAINED
const WRITE_QUEUED_AND_ACTIVE = WRITE_QUEUED | WRITE_ACTIVE
const WRITE_DRAIN_STATUS = WRITE_QUEUED | WRITE_UNDRAINED | OPEN_STATUS | WRITE_ACTIVE
const WRITE_STATUS = OPEN_STATUS | WRITE_ACTIVE | WRITE_QUEUED | WRITE_CORKED
const WRITE_PRIMARY_AND_ACTIVE = WRITE_PRIMARY | WRITE_ACTIVE
const WRITE_ACTIVE_AND_WRITING = WRITE_ACTIVE | WRITE_WRITING
const WRITE_FINISHING_STATUS = OPEN_STATUS | WRITE_FINISHING | WRITE_QUEUED_AND_ACTIVE | WRITE_DONE
const WRITE_BACKPRESSURE_STATUS = WRITE_UNDRAINED | DESTROY_STATUS | WRITE_FINISHING | WRITE_DONE
const WRITE_UPDATE_SYNC_STATUS = WRITE_UPDATING | OPEN_STATUS | WRITE_NEXT_TICK | WRITE_PRIMARY
const WRITE_DROP_DATA = WRITE_FINISHING | WRITE_DONE | DESTROY_STATUS

const asyncIterator = Symbol.asyncIterator || Symbol('asyncIterator')

class WritableState {
  constructor(
    stream,
    { highWaterMark = 16384, map = null, mapWritable, byteLength, byteLengthWritable } = {}
  ) {
    this.stream = stream
    this.queue = new FIFO()
    this.highWaterMark = highWaterMark
    this.buffered = 0
    this.error = null
    this.pipeline = null
    this.drains = null // if we add more seldomly used helpers we might them into a subobject so its a single ptr
    this.byteLength = byteLengthWritable || byteLength || defaultByteLength
    this.map = mapWritable || map
    this.afterWrite = afterWrite.bind(this)
    this.afterUpdateNextTick = updateWriteNT.bind(this)
  }

  get ending() {
    return (this.stream._duplexState & WRITE_FINISHING) !== 0
  }

  get ended() {
    return (this.stream._duplexState & WRITE_DONE) !== 0
  }

  push(data) {
    if ((this.stream._duplexState & WRITE_DROP_DATA) !== 0) return false
    if (this.map !== null) data = this.map(data)

    this.buffered += this.byteLength(data)
    this.queue.push(data)

    if (this.buffered < this.highWaterMark) {
      this.stream._duplexState |= WRITE_QUEUED
      return true
    }

    this.stream._duplexState |= WRITE_QUEUED_AND_UNDRAINED
    return false
  }

  shift() {
    const data = this.queue.shift()

    this.buffered -= this.byteLength(data)
    if (this.buffered === 0) this.stream._duplexState &= WRITE_NOT_QUEUED

    return data
  }

  end(data) {
    if (typeof data === 'function') this.stream.once('finish', data)
    else if (data !== undefined && data !== null) this.push(data)
    this.stream._duplexState = (this.stream._duplexState | WRITE_FINISHING) & WRITE_NON_PRIMARY
  }

  autoBatch(data, cb) {
    const buffer = []
    const stream = this.stream

    buffer.push(data)
    while ((stream._duplexState & WRITE_STATUS) === WRITE_QUEUED_AND_ACTIVE) {
      buffer.push(stream._writableState.shift())
    }

    if ((stream._duplexState & OPEN_STATUS) !== 0) return cb(null)
    stream._writev(buffer, cb)
  }

  update() {
    const stream = this.stream

    stream._duplexState |= WRITE_UPDATING

    do {
      while ((stream._duplexState & WRITE_STATUS) === WRITE_QUEUED) {
        const data = this.shift()
        stream._duplexState |= WRITE_ACTIVE_AND_WRITING
        stream._write(data, this.afterWrite)
      }

      if ((stream._duplexState & WRITE_PRIMARY_AND_ACTIVE) === 0) this.updateNonPrimary()
    } while (this.continueUpdate() === true)

    stream._duplexState &= WRITE_NOT_UPDATING
  }

  updateNonPrimary() {
    const stream = this.stream

    if ((stream._duplexState & WRITE_FINISHING_STATUS) === WRITE_FINISHING) {
      stream._duplexState = stream._duplexState | WRITE_ACTIVE
      stream._final(afterFinal.bind(this))
      return
    }

    if ((stream._duplexState & DESTROY_STATUS) === DESTROYING) {
      if ((stream._duplexState & ACTIVE_OR_TICKING) === 0) {
        stream._duplexState |= ACTIVE
        stream._destroy(afterDestroy.bind(this))
      }
      return
    }

    if ((stream._duplexState & IS_OPENING) === OPENING) {
      stream._duplexState = (stream._duplexState | ACTIVE) & NOT_OPENING
      stream._open(afterOpen.bind(this))
    }
  }

  continueUpdate() {
    if ((this.stream._duplexState & WRITE_NEXT_TICK) === 0) return false
    this.stream._duplexState &= WRITE_NOT_NEXT_TICK
    return true
  }

  updateCallback() {
    if ((this.stream._duplexState & WRITE_UPDATE_SYNC_STATUS) === WRITE_PRIMARY) this.update()
    else this.updateNextTick()
  }

  updateNextTick() {
    if ((this.stream._duplexState & WRITE_NEXT_TICK) !== 0) return
    this.stream._duplexState |= WRITE_NEXT_TICK
    if ((this.stream._duplexState & WRITE_UPDATING) === 0) qmt(this.afterUpdateNextTick)
  }
}

class ReadableState {
  constructor(
    stream,
    { highWaterMark = 16384, map = null, mapReadable, byteLength, byteLengthReadable } = {}
  ) {
    this.stream = stream
    this.queue = new FIFO()
    this.highWaterMark = highWaterMark === 0 ? 1 : highWaterMark
    this.buffered = 0
    this.readAhead = highWaterMark > 0
    this.error = null
    this.pipeline = null
    this.byteLength = byteLengthReadable || byteLength || defaultByteLength
    this.map = mapReadable || map
    this.pipeTo = null
    this.afterRead = afterRead.bind(this)
    this.afterUpdateNextTick = updateReadNT.bind(this)
  }

  get ending() {
    return (this.stream._duplexState & READ_ENDING) !== 0
  }

  get ended() {
    return (this.stream._duplexState & READ_DONE) !== 0
  }

  pipe(pipeTo, cb) {
    if (this.pipeTo !== null) throw new Error('Can only pipe to one destination')
    if (typeof cb !== 'function') cb = null

    this.stream._duplexState |= READ_PIPE_DRAINED
    this.pipeTo = pipeTo
    this.pipeline = new Pipeline(this.stream, pipeTo, cb)

    if (cb) this.stream.on('error', noop) // We already error handle this so supress crashes

    if (isStreamx(pipeTo)) {
      pipeTo._writableState.pipeline = this.pipeline
      if (cb) pipeTo.on('error', noop) // We already error handle this so supress crashes
      pipeTo.on('finish', this.pipeline.finished.bind(this.pipeline)) // TODO: just call finished from pipeTo itself
    } else {
      const onerror = this.pipeline.done.bind(this.pipeline, pipeTo)
      const onclose = this.pipeline.done.bind(this.pipeline, pipeTo, null) // onclose has a weird bool arg
      pipeTo.on('error', onerror)
      pipeTo.on('close', onclose)
      pipeTo.on('finish', this.pipeline.finished.bind(this.pipeline))
    }

    pipeTo.on('drain', afterDrain.bind(this))
    this.stream.emit('piping', pipeTo)
    pipeTo.emit('pipe', this.stream)
  }

  push(data) {
    const stream = this.stream

    if (data === null) {
      this.highWaterMark = 0
      stream._duplexState = (stream._duplexState | READ_ENDING) & READ_NON_PRIMARY_AND_PUSHED
      return false
    }

    if (this.map !== null) {
      data = this.map(data)
      if (data === null) {
        stream._duplexState &= READ_PUSHED
        return this.buffered < this.highWaterMark
      }
    }

    this.buffered += this.byteLength(data)
    this.queue.push(data)

    stream._duplexState = (stream._duplexState | READ_QUEUED) & READ_PUSHED

    return this.buffered < this.highWaterMark
  }

  shift() {
    const data = this.queue.shift()

    this.buffered -= this.byteLength(data)
    if (this.buffered === 0) this.stream._duplexState &= READ_NOT_QUEUED
    return data
  }

  unshift(data) {
    const pending = [this.map !== null ? this.map(data) : data]
    while (this.buffered > 0) pending.push(this.shift())

    for (let i = 0; i < pending.length - 1; i++) {
      const data = pending[i]
      this.buffered += this.byteLength(data)
      this.queue.push(data)
    }

    this.push(pending[pending.length - 1])
  }

  read() {
    const stream = this.stream

    if ((stream._duplexState & READ_STATUS) === READ_QUEUED) {
      const data = this.shift()
      if (this.pipeTo !== null && this.pipeTo.write(data) === false)
        stream._duplexState &= READ_PIPE_NOT_DRAINED
      if ((stream._duplexState & READ_EMIT_DATA) !== 0) stream.emit('data', data)
      return data
    }

    if (this.readAhead === false) {
      stream._duplexState |= READ_READ_AHEAD
      this.updateNextTick()
    }

    return null
  }

  drain() {
    const stream = this.stream

    while (
      (stream._duplexState & READ_STATUS) === READ_QUEUED &&
      (stream._duplexState & READ_FLOWING) !== 0
    ) {
      const data = this.shift()
      if (this.pipeTo !== null && this.pipeTo.write(data) === false)
        stream._duplexState &= READ_PIPE_NOT_DRAINED
      if ((stream._duplexState & READ_EMIT_DATA) !== 0) stream.emit('data', data)
    }
  }

  update() {
    const stream = this.stream

    stream._duplexState |= READ_UPDATING

    do {
      this.drain()

      while (
        this.buffered < this.highWaterMark &&
        (stream._duplexState & SHOULD_NOT_READ) === READ_READ_AHEAD
      ) {
        stream._duplexState |= READ_ACTIVE_AND_NEEDS_PUSH
        stream._read(this.afterRead)
        this.drain()
      }

      if ((stream._duplexState & READ_READABLE_STATUS) === READ_EMIT_READABLE_AND_QUEUED) {
        stream._duplexState |= READ_EMITTED_READABLE
        stream.emit('readable')
      }

      if ((stream._duplexState & READ_PRIMARY_AND_ACTIVE) === 0) this.updateNonPrimary()
    } while (this.continueUpdate() === true)

    stream._duplexState &= READ_NOT_UPDATING
  }

  updateNonPrimary() {
    const stream = this.stream

    if ((stream._duplexState & READ_ENDING_STATUS) === READ_ENDING) {
      stream._duplexState = (stream._duplexState | READ_DONE) & READ_NOT_ENDING
      stream.emit('end')
      if ((stream._duplexState & AUTO_DESTROY) === DONE) stream._duplexState |= DESTROYING
      if (this.pipeTo !== null) this.pipeTo.end()
    }

    if ((stream._duplexState & DESTROY_STATUS) === DESTROYING) {
      if ((stream._duplexState & ACTIVE_OR_TICKING) === 0) {
        stream._duplexState |= ACTIVE
        stream._destroy(afterDestroy.bind(this))
      }
      return
    }

    if ((stream._duplexState & IS_OPENING) === OPENING) {
      stream._duplexState = (stream._duplexState | ACTIVE) & NOT_OPENING
      stream._open(afterOpen.bind(this))
    }
  }

  continueUpdate() {
    if ((this.stream._duplexState & READ_NEXT_TICK) === 0) return false
    this.stream._duplexState &= READ_NOT_NEXT_TICK
    return true
  }

  updateCallback() {
    if ((this.stream._duplexState & READ_UPDATE_SYNC_STATUS) === READ_PRIMARY) this.update()
    else this.updateNextTick()
  }

  updateNextTickIfOpen() {
    if ((this.stream._duplexState & READ_NEXT_TICK_OR_OPENING) !== 0) return
    this.stream._duplexState |= READ_NEXT_TICK
    if ((this.stream._duplexState & READ_UPDATING) === 0) qmt(this.afterUpdateNextTick)
  }

  updateNextTick() {
    if ((this.stream._duplexState & READ_NEXT_TICK) !== 0) return
    this.stream._duplexState |= READ_NEXT_TICK
    if ((this.stream._duplexState & READ_UPDATING) === 0) qmt(this.afterUpdateNextTick)
  }
}

class TransformState {
  constructor(stream) {
    this.data = null
    this.afterTransform = afterTransform.bind(stream)
    this.afterFinal = null
  }
}

class Pipeline {
  constructor(src, dst, cb) {
    this.from = src
    this.to = dst
    this.afterPipe = cb
    this.error = null
    this.pipeToFinished = false
  }

  finished() {
    this.pipeToFinished = true
  }

  done(stream, err) {
    if (err) this.error = err

    if (stream === this.to) {
      this.to = null

      if (this.from !== null) {
        if ((this.from._duplexState & READ_DONE) === 0 || !this.pipeToFinished) {
          this.from.destroy(this.error || new Error('Writable stream closed prematurely'))
        }
        return
      }
    }

    if (stream === this.from) {
      this.from = null

      if (this.to !== null) {
        if ((stream._duplexState & READ_DONE) === 0) {
          this.to.destroy(this.error || new Error('Readable stream closed before ending'))
        }
        return
      }
    }

    if (this.afterPipe !== null) this.afterPipe(this.error)
    this.to = this.from = this.afterPipe = null
  }
}

function afterDrain() {
  this.stream._duplexState |= READ_PIPE_DRAINED
  this.updateCallback()
}

function afterFinal(err) {
  const stream = this.stream
  if (err) stream.destroy(err)
  if ((stream._duplexState & DESTROY_STATUS) === 0) {
    stream._duplexState |= WRITE_DONE
    stream.emit('finish')
  }
  if ((stream._duplexState & AUTO_DESTROY) === DONE) {
    stream._duplexState |= DESTROYING
  }

  stream._duplexState &= WRITE_NOT_FINISHING

  // no need to wait the extra tick here, so we short circuit that
  if ((stream._duplexState & WRITE_UPDATING) === 0) this.update()
  else this.updateNextTick()
}

function afterDestroy(err) {
  const stream = this.stream

  if (!err && this.error !== STREAM_DESTROYED) err = this.error
  if (err) stream.emit('error', err)
  stream._duplexState |= DESTROYED
  stream.emit('close')

  const rs = stream._readableState
  const ws = stream._writableState

  if (rs !== null && rs.pipeline !== null) rs.pipeline.done(stream, err)

  if (ws !== null) {
    while (ws.drains !== null && ws.drains.length > 0) ws.drains.shift().resolve(false)
    if (ws.pipeline !== null) ws.pipeline.done(stream, err)
  }
}

function afterWrite(err) {
  const stream = this.stream

  if (err) stream.destroy(err)
  stream._duplexState &= WRITE_NOT_ACTIVE

  if (this.drains !== null) tickDrains(this.drains)

  if ((stream._duplexState & WRITE_DRAIN_STATUS) === WRITE_UNDRAINED) {
    stream._duplexState &= WRITE_DRAINED
    if ((stream._duplexState & WRITE_EMIT_DRAIN) === WRITE_EMIT_DRAIN) {
      stream.emit('drain')
    }
  }

  this.updateCallback()
}

function afterRead(err) {
  if (err) this.stream.destroy(err)
  this.stream._duplexState &= READ_NOT_ACTIVE
  if (this.readAhead === false && (this.stream._duplexState & READ_RESUMED) === 0)
    this.stream._duplexState &= READ_NO_READ_AHEAD
  this.updateCallback()
}

function updateReadNT() {
  if ((this.stream._duplexState & READ_UPDATING) === 0) {
    this.stream._duplexState &= READ_NOT_NEXT_TICK
    this.update()
  }
}

function updateWriteNT() {
  if ((this.stream._duplexState & WRITE_UPDATING) === 0) {
    this.stream._duplexState &= WRITE_NOT_NEXT_TICK
    this.update()
  }
}

function tickDrains(drains) {
  for (let i = 0; i < drains.length; i++) {
    // drains.writes are monotonic, so if one is 0 its always the first one
    if (--drains[i].writes === 0) {
      drains.shift().resolve(true)
      i--
    }
  }
}

function afterOpen(err) {
  const stream = this.stream

  if (err) stream.destroy(err)

  if ((stream._duplexState & DESTROYING) === 0) {
    if ((stream._duplexState & READ_PRIMARY_STATUS) === 0) stream._duplexState |= READ_PRIMARY
    if ((stream._duplexState & WRITE_PRIMARY_STATUS) === 0) stream._duplexState |= WRITE_PRIMARY
    stream.emit('open')
  }

  stream._duplexState &= NOT_ACTIVE

  if (stream._writableState !== null) {
    stream._writableState.updateCallback()
  }

  if (stream._readableState !== null) {
    stream._readableState.updateCallback()
  }
}

function afterTransform(err, data) {
  if (data !== undefined && data !== null) this.push(data)
  this._writableState.afterWrite(err)
}

function newListener(name) {
  if (this._readableState !== null) {
    if (name === 'data') {
      this._duplexState |= READ_EMIT_DATA | READ_RESUMED_READ_AHEAD
      this._readableState.updateNextTick()
    }
    if (name === 'readable') {
      this._duplexState |= READ_EMIT_READABLE
      this._readableState.updateNextTick()
    }
  }

  if (this._writableState !== null) {
    if (name === 'drain') {
      this._duplexState |= WRITE_EMIT_DRAIN
      this._writableState.updateNextTick()
    }
  }
}

class Stream extends EventEmitter {
  constructor(opts) {
    super()

    this._duplexState = 0
    this._readableState = null
    this._writableState = null

    if (opts) {
      if (opts.open) this._open = opts.open
      if (opts.destroy) this._destroy = opts.destroy
      if (opts.predestroy) this._predestroy = opts.predestroy
      if (opts.signal) {
        opts.signal.addEventListener('abort', abort.bind(this))
      }
    }

    this.on('newListener', newListener)
  }

  _open(cb) {
    cb(null)
  }

  _destroy(cb) {
    cb(null)
  }

  _predestroy() {
    // does nothing
  }

  get readable() {
    return this._readableState !== null ? true : undefined
  }

  get writable() {
    return this._writableState !== null ? true : undefined
  }

  get destroyed() {
    return (this._duplexState & DESTROYED) !== 0
  }

  get destroying() {
    return (this._duplexState & DESTROY_STATUS) !== 0
  }

  destroy(err) {
    if ((this._duplexState & DESTROY_STATUS) === 0) {
      if (!err) err = STREAM_DESTROYED
      this._duplexState = (this._duplexState | DESTROYING) & NON_PRIMARY

      if (this._readableState !== null) {
        this._readableState.highWaterMark = 0
        this._readableState.error = err
      }
      if (this._writableState !== null) {
        this._writableState.highWaterMark = 0
        this._writableState.error = err
      }

      this._duplexState |= PREDESTROYING
      this._predestroy()
      this._duplexState &= NOT_PREDESTROYING

      if (this._readableState !== null) this._readableState.updateNextTick()
      if (this._writableState !== null) this._writableState.updateNextTick()
    }
  }
}

class Readable extends Stream {
  constructor(opts) {
    super(opts)

    this._duplexState |= OPENING | WRITE_DONE | READ_READ_AHEAD
    this._readableState = new ReadableState(this, opts)

    if (opts) {
      if (this._readableState.readAhead === false) this._duplexState &= READ_NO_READ_AHEAD
      if (opts.read) this._read = opts.read
      if (opts.eagerOpen) this._readableState.updateNextTick()
      if (opts.encoding) this.setEncoding(opts.encoding)
    }
  }

  setEncoding(encoding) {
    const dec = new TextDecoder(encoding)
    const map = this._readableState.map || echo
    this._readableState.map = mapOrSkip
    return this

    function mapOrSkip(data) {
      const next = dec.push(data)
      return next === '' && (data.byteLength !== 0 || dec.remaining > 0) ? null : map(next)
    }
  }

  _read(cb) {
    cb(null)
  }

  pipe(dest, cb) {
    this._readableState.updateNextTick()
    this._readableState.pipe(dest, cb)
    return dest
  }

  read() {
    this._readableState.updateNextTick()
    return this._readableState.read()
  }

  push(data) {
    this._readableState.updateNextTickIfOpen()
    return this._readableState.push(data)
  }

  unshift(data) {
    this._readableState.updateNextTickIfOpen()
    return this._readableState.unshift(data)
  }

  resume() {
    this._duplexState |= READ_RESUMED_READ_AHEAD
    this._readableState.updateNextTick()
    return this
  }

  pause() {
    this._duplexState &=
      this._readableState.readAhead === false ? READ_PAUSED_NO_READ_AHEAD : READ_PAUSED
    return this
  }

  static _fromAsyncIterator(ite, opts) {
    let destroy

    const rs = new Readable({
      ...opts,
      read(cb) {
        ite.next().then(push).then(cb.bind(null, null)).catch(cb)
      },
      predestroy() {
        destroy = ite.return()
      },
      destroy(cb) {
        if (!destroy) return cb(null)
        destroy.then(cb.bind(null, null)).catch(cb)
      }
    })

    return rs

    function push(data) {
      if (data.done) rs.push(null)
      else rs.push(data.value)
    }
  }

  static from(data, opts) {
    if (isReadStreamx(data)) return data
    if (data[asyncIterator]) return this._fromAsyncIterator(data[asyncIterator](), opts)
    if (!Array.isArray(data)) data = data === undefined ? [] : [data]

    let i = 0
    return new Readable({
      ...opts,
      read(cb) {
        this.push(i === data.length ? null : data[i++])
        cb(null)
      }
    })
  }

  static isBackpressured(rs) {
    return (
      (rs._duplexState & READ_BACKPRESSURE_STATUS) !== 0 ||
      rs._readableState.buffered >= rs._readableState.highWaterMark
    )
  }

  static isPaused(rs) {
    return (rs._duplexState & READ_RESUMED) === 0
  }

  [asyncIterator]() {
    const stream = this

    let error = null
    let promiseResolve = null
    let promiseReject = null

    this.on('error', (err) => {
      error = err
    })
    this.on('readable', onreadable)
    this.on('close', onclose)

    return {
      [asyncIterator]() {
        return this
      },
      next() {
        return new Promise(function (resolve, reject) {
          promiseResolve = resolve
          promiseReject = reject
          const data = stream.read()
          if (data !== null) ondata(data)
          else if ((stream._duplexState & DESTROYED) !== 0) ondata(null)
        })
      },
      return() {
        return destroy(null)
      },
      throw(err) {
        return destroy(err)
      }
    }

    function onreadable() {
      if (promiseResolve !== null) ondata(stream.read())
    }

    function onclose() {
      if (promiseResolve !== null) ondata(null)
    }

    function ondata(data) {
      if (promiseReject === null) return
      if (error) promiseReject(error)
      else if (data === null && (stream._duplexState & READ_DONE) === 0)
        promiseReject(STREAM_DESTROYED)
      else promiseResolve({ value: data, done: data === null })
      promiseReject = promiseResolve = null
    }

    function destroy(err) {
      stream.destroy(err)
      return new Promise((resolve, reject) => {
        if (stream._duplexState & DESTROYED) return resolve({ value: undefined, done: true })
        stream.once('close', function () {
          if (err) reject(err)
          else resolve({ value: undefined, done: true })
        })
      })
    }
  }
}

class Writable extends Stream {
  constructor(opts) {
    super(opts)

    this._duplexState |= OPENING | READ_DONE
    this._writableState = new WritableState(this, opts)

    if (opts) {
      if (opts.writev) this._writev = opts.writev
      if (opts.write) this._write = opts.write
      if (opts.final) this._final = opts.final
      if (opts.eagerOpen) this._writableState.updateNextTick()
    }
  }

  cork() {
    this._duplexState |= WRITE_CORKED
  }

  uncork() {
    this._duplexState &= WRITE_NOT_CORKED
    this._writableState.updateNextTick()
  }

  _writev(batch, cb) {
    cb(null)
  }

  _write(data, cb) {
    this._writableState.autoBatch(data, cb)
  }

  _final(cb) {
    cb(null)
  }

  static isBackpressured(ws) {
    return (ws._duplexState & WRITE_BACKPRESSURE_STATUS) !== 0
  }

  static drained(ws) {
    if (ws.destroyed) return Promise.resolve(false)
    const state = ws._writableState
    const pending = isWritev(ws) ? Math.min(1, state.queue.length) : state.queue.length
    const writes = pending + (ws._duplexState & WRITE_WRITING ? 1 : 0)
    if (writes === 0) return Promise.resolve(true)
    if (state.drains === null) state.drains = []
    return new Promise((resolve) => {
      state.drains.push({ writes, resolve })
    })
  }

  write(data) {
    this._writableState.updateNextTick()
    return this._writableState.push(data)
  }

  end(data) {
    this._writableState.updateNextTick()
    this._writableState.end(data)
    return this
  }
}

class Duplex extends Readable {
  // and Writable
  constructor(opts) {
    super(opts)

    this._duplexState = OPENING | (this._duplexState & READ_READ_AHEAD)
    this._writableState = new WritableState(this, opts)

    if (opts) {
      if (opts.writev) this._writev = opts.writev
      if (opts.write) this._write = opts.write
      if (opts.final) this._final = opts.final
    }
  }

  cork() {
    this._duplexState |= WRITE_CORKED
  }

  uncork() {
    this._duplexState &= WRITE_NOT_CORKED
    this._writableState.updateNextTick()
  }

  _writev(batch, cb) {
    cb(null)
  }

  _write(data, cb) {
    this._writableState.autoBatch(data, cb)
  }

  _final(cb) {
    cb(null)
  }

  write(data) {
    this._writableState.updateNextTick()
    return this._writableState.push(data)
  }

  end(data) {
    this._writableState.updateNextTick()
    this._writableState.end(data)
    return this
  }
}

class Transform extends Duplex {
  constructor(opts) {
    super(opts)
    this._transformState = new TransformState(this)

    if (opts) {
      if (opts.transform) this._transform = opts.transform
      if (opts.flush) this._flush = opts.flush
    }
  }

  _write(data, cb) {
    if (this._readableState.buffered >= this._readableState.highWaterMark) {
      this._transformState.data = data
    } else {
      this._transform(data, this._transformState.afterTransform)
    }
  }

  _read(cb) {
    if (this._transformState.data !== null) {
      const data = this._transformState.data
      this._transformState.data = null
      cb(null)
      this._transform(data, this._transformState.afterTransform)
    } else {
      cb(null)
    }
  }

  destroy(err) {
    super.destroy(err)
    if (this._transformState.data !== null) {
      this._transformState.data = null
      this._transformState.afterTransform()
    }
  }

  _transform(data, cb) {
    cb(null, data)
  }

  _flush(cb) {
    cb(null)
  }

  _final(cb) {
    this._transformState.afterFinal = cb
    this._flush(transformAfterFlush.bind(this))
  }
}

class PassThrough extends Transform {}

function transformAfterFlush(err, data) {
  const cb = this._transformState.afterFinal
  if (err) return cb(err)
  if (data !== null && data !== undefined) this.push(data)
  this.push(null)
  cb(null)
}

function pipelinePromise(...streams) {
  return new Promise((resolve, reject) => {
    return pipeline(...streams, (err) => {
      if (err) return reject(err)
      resolve()
    })
  })
}

function pipeline(stream, ...streams) {
  const all = Array.isArray(stream) ? [...stream, ...streams] : [stream, ...streams]
  const done = all.length && typeof all[all.length - 1] === 'function' ? all.pop() : null

  if (all.length < 2) throw new Error('Pipeline requires at least 2 streams')

  let src = all[0]
  let dest = null
  let error = null

  for (let i = 1; i < all.length; i++) {
    dest = all[i]

    if (isStreamx(src)) {
      src.pipe(dest, onerror)
    } else {
      errorHandle(src, true, i > 1, onerror)
      src.pipe(dest)
    }

    src = dest
  }

  if (done) {
    let fin = false

    const autoDestroy =
      isStreamx(dest) || !!(dest._writableState && dest._writableState.autoDestroy)

    dest.on('error', (err) => {
      if (error === null) error = err
    })

    dest.on('finish', () => {
      fin = true
      if (!autoDestroy) done(error)
    })

    if (autoDestroy) {
      dest.on('close', () => done(error || (fin ? null : PREMATURE_CLOSE)))
    }
  }

  return dest

  function errorHandle(s, rd, wr, onerror) {
    s.on('error', onerror)
    s.on('close', onclose)

    function onclose() {
      if (rd && s._readableState && !s._readableState.ended) return onerror(PREMATURE_CLOSE)
      if (wr && s._writableState && !s._writableState.ended) return onerror(PREMATURE_CLOSE)
    }
  }

  function onerror(err) {
    if (!err || error) return
    error = err

    for (const s of all) {
      s.destroy(err)
    }
  }
}

function echo(s) {
  return s
}

function isStream(stream) {
  return !!stream._readableState || !!stream._writableState
}

function isStreamx(stream) {
  return typeof stream._duplexState === 'number' && isStream(stream)
}

function isEnding(stream) {
  return !!stream._readableState && stream._readableState.ending
}

function isEnded(stream) {
  return !!stream._readableState && stream._readableState.ended
}

function isFinishing(stream) {
  return !!stream._writableState && stream._writableState.ending
}

function isFinished(stream) {
  return !!stream._writableState && stream._writableState.ended
}

function getStreamError(stream, opts = {}) {
  const err =
    (stream._readableState && stream._readableState.error) ||
    (stream._writableState && stream._writableState.error)

  // avoid implicit errors by default
  return !opts.all && err === STREAM_DESTROYED ? null : err
}

function isReadStreamx(stream) {
  return isStreamx(stream) && stream.readable
}

function isDisturbed(stream) {
  return (
    (stream._duplexState & OPENING) !== OPENING ||
    (stream._duplexState & DESTROYING) === DESTROYING ||
    (stream._duplexState & ACTIVE_OR_TICKING) !== 0
  )
}

function isTypedArray(data) {
  return typeof data === 'object' && data !== null && typeof data.byteLength === 'number'
}

function defaultByteLength(data) {
  return isTypedArray(data) ? data.byteLength : 1024
}

function noop() {}

function abort() {
  this.destroy(new Error('Stream aborted.'))
}

function isWritev(s) {
  return s._writev !== Writable.prototype._writev && s._writev !== Duplex.prototype._writev
}

module.exports = {
  pipeline,
  pipelinePromise,
  isStream,
  isStreamx,
  isEnding,
  isEnded,
  isFinishing,
  isFinished,
  isDisturbed,
  getStreamError,
  Stream,
  Writable,
  Readable,
  Duplex,
  Transform,
  // Export PassThrough for compatibility with Node.js core's stream module
  PassThrough
}
{
  "name": "streamx",
  "version": "2.25.0",
  "description": "An iteration of the Node.js core streams with a series of improvements",
  "main": "index.js",
  "dependencies": {
    "events-universal": "^1.0.0",
    "fast-fifo": "^1.3.2",
    "text-decoder": "^1.1.0"
  },
  "devDependencies": {
    "b4a": "^1.6.6",
    "brittle": "^3.1.1",
    "end-of-stream": "^1.4.4",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^2.0.0"
  },
  "files": [
    "index.js"
  ],
  "scripts": {
    "format": "prettier --write .",
    "test": "prettier --check . && node test/all.js",
    "test:bare": "bare test/all.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/mafintosh/streamx.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/mafintosh/streamx/issues"
  },
  "homepage": "https://github.com/mafintosh/streamx"
}
const { Readable } = require('streamx')

module.exports = function (s, forks = 2) {
  const streams = new Array(forks)
  const status = new Array(forks).fill(true)

  let ended = false

  for (let i = 0; i < forks; i++) {
    streams[i] = new Readable({
      read (cb) {
        const check = !status[i]
        status[i] = true
        if (check && allReadable()) s.resume()
        cb(null)
      }
    })
  }

  s.on('end', function () {
    ended = true
    for (const stream of streams) stream.push(null)
  })

  s.on('error', function (err) {
    for (const stream of streams) stream.destroy(err)
  })

  s.on('close', function () {
    if (ended) return
    for (const stream of streams) stream.destroy()
  })

  s.on('data', function (data) {
    let needsPause = false
    for (let i = 0; i < streams.length; i++) {
      if (!(status[i] = streams[i].push(data))) {
        needsPause = true
      }
    }
    if (needsPause) s.pause()
  })

  return streams

  function allReadable () {
    for (let j = 0; j < status.length; j++) {
      if (!status[j]) return false
    }
    return true
  }
}
{
  "name": "teex",
  "version": "1.0.1",
  "description": "Turn a readable stream into multiple readable streamx streams",
  "main": "index.js",
  "dependencies": {
    "streamx": "^2.12.5"
  },
  "devDependencies": {
    "standard": "^14.3.4",
    "tape": "^5.0.1"
  },
  "scripts": {
    "test": "standard && tape test.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/mafintosh/teex.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/mafintosh/teex/issues"
  },
  "homepage": "https://github.com/mafintosh/teex"
}
const PassThroughDecoder = require('./lib/pass-through-decoder')
const UTF8Decoder = require('./lib/utf8-decoder')

module.exports = class TextDecoder {
  constructor(encoding = 'utf8') {
    this.encoding = normalizeEncoding(encoding)

    switch (this.encoding) {
      case 'utf8':
        this.decoder = new UTF8Decoder()
        break
      case 'utf16le':
      case 'base64':
        throw new Error('Unsupported encoding: ' + this.encoding)
      default:
        this.decoder = new PassThroughDecoder(this.encoding)
    }
  }

  get remaining() {
    return this.decoder.remaining
  }

  push(data) {
    if (typeof data === 'string') return data
    return this.decoder.decode(data)
  }

  // For Node.js compatibility
  write(data) {
    return this.push(data)
  }

  end(data) {
    let result = ''
    if (data) result = this.push(data)
    result += this.decoder.flush()
    return result
  }
}

function normalizeEncoding(encoding) {
  encoding = encoding.toLowerCase()

  switch (encoding) {
    case 'utf8':
    case 'utf-8':
      return 'utf8'
    case 'ucs2':
    case 'ucs-2':
    case 'utf16le':
    case 'utf-16le':
      return 'utf16le'
    case 'latin1':
    case 'binary':
      return 'latin1'
    case 'base64':
    case 'ascii':
    case 'hex':
      return encoding
    default:
      throw new Error('Unknown encoding: ' + encoding)
  }
}
const b4a = require('b4a')

module.exports = class PassThroughDecoder {
  constructor(encoding) {
    this.encoding = encoding
  }

  get remaining() {
    return 0
  }

  decode(data) {
    return b4a.toString(data, this.encoding)
  }

  flush() {
    return ''
  }
}
const b4a = require('b4a')

/**
 * https://encoding.spec.whatwg.org/#utf-8-decoder
 */
module.exports = class UTF8Decoder {
  constructor() {
    this._reset()
  }

  get remaining() {
    return this.bytesSeen
  }

  decode(data) {
    if (data.byteLength === 0) return ''

    if (this.bytesNeeded === 0 && trailingIncomplete(data, 0) === 0) {
      this.bytesSeen = trailingBytesSeen(data)
      return b4a.toString(data, 'utf8')
    }

    let result = ''
    let start = 0

    if (this.bytesNeeded > 0) {
      while (start < data.byteLength) {
        const byte = data[start]

        if (byte < this.lowerBoundary || byte > this.upperBoundary) {
          result += '\ufffd'
          this._reset()
          break
        }

        this.lowerBoundary = 0x80
        this.upperBoundary = 0xbf
        this.codePoint = (this.codePoint << 6) | (byte & 0x3f)
        this.bytesSeen++
        start++

        if (this.bytesSeen === this.bytesNeeded) {
          result += String.fromCodePoint(this.codePoint)
          this._reset()
          break
        }
      }

      if (this.bytesNeeded > 0) return result
    }

    const trailing = trailingIncomplete(data, start)
    const end = data.byteLength - trailing

    if (end > start) result += b4a.toString(data, 'utf8', start, end)

    for (let i = end; i < data.byteLength; i++) {
      const byte = data[i]

      if (this.bytesNeeded === 0) {
        if (byte <= 0x7f) {
          this.bytesSeen = 0
          result += String.fromCharCode(byte)
        } else if (byte >= 0xc2 && byte <= 0xdf) {
          this.bytesNeeded = 2
          this.bytesSeen = 1
          this.codePoint = byte & 0x1f
        } else if (byte >= 0xe0 && byte <= 0xef) {
          if (byte === 0xe0) this.lowerBoundary = 0xa0
          else if (byte === 0xed) this.upperBoundary = 0x9f
          this.bytesNeeded = 3
          this.bytesSeen = 1
          this.codePoint = byte & 0xf
        } else if (byte >= 0xf0 && byte <= 0xf4) {
          if (byte === 0xf0) this.lowerBoundary = 0x90
          else if (byte === 0xf4) this.upperBoundary = 0x8f
          this.bytesNeeded = 4
          this.bytesSeen = 1
          this.codePoint = byte & 0x7
        } else {
          this.bytesSeen = 1
          result += '\ufffd'
        }

        continue
      }

      if (byte < this.lowerBoundary || byte > this.upperBoundary) {
        result += '\ufffd'
        i--
        this._reset()

        continue
      }

      this.lowerBoundary = 0x80
      this.upperBoundary = 0xbf

      this.codePoint = (this.codePoint << 6) | (byte & 0x3f)
      this.bytesSeen++

      if (this.bytesSeen === this.bytesNeeded) {
        result += String.fromCodePoint(this.codePoint)
        this._reset()
      }
    }

    return result
  }

  flush() {
    const result = this.bytesNeeded > 0 ? '\ufffd' : ''
    this._reset()
    return result
  }

  _reset() {
    this.codePoint = 0
    this.bytesNeeded = 0
    this.bytesSeen = 0
    this.lowerBoundary = 0x80
    this.upperBoundary = 0xbf
  }
}

function trailingIncomplete(data, start) {
  const len = data.byteLength
  if (len <= start) return 0

  const limit = Math.max(start, len - 4)

  let i = len - 1
  while (i > limit && (data[i] & 0xc0) === 0x80) i--

  if (i < start) return 0

  const byte = data[i]

  let needed
  if (byte <= 0x7f) return 0
  if (byte >= 0xc2 && byte <= 0xdf) needed = 2
  else if (byte >= 0xe0 && byte <= 0xef) needed = 3
  else if (byte >= 0xf0 && byte <= 0xf4) needed = 4
  else return 0

  const available = len - i
  return available < needed ? available : 0
}

function trailingBytesSeen(data) {
  const len = data.byteLength
  if (len === 0) return 0

  const last = data[len - 1]

  if (last <= 0x7f) return 0
  if ((last & 0xc0) !== 0x80) return 1

  const limit = Math.max(0, len - 4)

  let i = len - 2
  while (i >= limit && (data[i] & 0xc0) === 0x80) i--

  if (i < 0) return 1

  const first = data[i]

  let needed
  if (first >= 0xc2 && first <= 0xdf) needed = 2
  else if (first >= 0xe0 && first <= 0xef) needed = 3
  else if (first >= 0xf0 && first <= 0xf4) needed = 4
  else return 1

  if (len - i !== needed) return 1

  if (needed >= 3) {
    const second = data[i + 1]
    if (first === 0xe0 && second < 0xa0) return 1
    if (first === 0xed && second > 0x9f) return 1
    if (first === 0xf0 && second < 0x90) return 1
    if (first === 0xf4 && second > 0x8f) return 1
  }

  return 0
}
{
  "name": "text-decoder",
  "version": "1.2.7",
  "description": "Streaming text decoder that preserves multibyte Unicode characters",
  "exports": {
    "./package": "./package.json",
    ".": "./index.js"
  },
  "files": [
    "index.js",
    "lib"
  ],
  "scripts": {
    "format": "prettier --write .",
    "lint": "prettier --check . && lunte",
    "test": "npm run test:node && npm run test:bare",
    "test:node": "brittle-node test.js",
    "test:bare": "brittle-bare test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/text-decoder.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/text-decoder/issues"
  },
  "homepage": "https://github.com/holepunchto/text-decoder#readme",
  "dependencies": {
    "b4a": "^1.6.4"
  },
  "devDependencies": {
    "brittle": "^3.3.2",
    "lunte": "^1.3.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^1.0.0"
  }
}
const { runtime, platform, arch } = typeof Bare !== 'undefined'
  ? { runtime: 'bare', platform: global.Bare.platform, arch: global.Bare.arch }
  : typeof process !== 'undefined'
    ? { runtime: 'node', platform: global.process.platform, arch: global.process.arch }
    : typeof Window !== 'undefined'
      ? { runtime: 'browser', platform: 'unknown', arch: 'unknown' }
      : { runtime: 'unknown', platform: 'unknown', arch: 'unknown' }

exports.runtime = runtime
exports.platform = platform
exports.arch = arch
exports.isBare = runtime === 'bare'
exports.isBareKit = exports.isBare && typeof BareKit !== 'undefined'
exports.isPear = typeof Pear !== 'undefined'
exports.isNode = runtime === 'node'
exports.isBrowser = runtime === 'browser'
exports.isWindows = platform === 'win32'
exports.isLinux = platform === 'linux'
exports.isMac = platform === 'darwin'
exports.isIOS = platform === 'ios' || platform === 'ios-simulator'
exports.isAndroid = platform === 'android'
exports.isElectron = typeof process !== 'undefined' && !!global.process.versions?.electron
exports.isElectronRenderer = exports.isElectron && global.process.type === 'renderer'
exports.isElectronWorker = exports.isElectron && global.process.type === 'worker'
{
  "name": "which-runtime",
  "version": "1.3.2",
  "description": "Detect if you are in Bare or Node and which os etc",
  "main": "index.js",
  "files": [
    "index.js"
  ],
  "dependencies": {},
  "devDependencies": {
    "standard": "^17.0.0"
  },
  "scripts": {
    "test": "standard"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/holepunchto/which-runtime.git"
  },
  "author": "Holepunch Inc.",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/which-runtime/issues"
  },
  "homepage": "https://github.com/holepunchto/which-runtime"
}
{
  "name": "hyperident",
  "version": "0.2.6",
  "main": "index.js",
  "bin": {
    "hyperident": "./index.js"
  },
  "type": "module",
  "private": true,
  "license": "Apache-2.0",
  "pgh": {
    "bin": "hyperident",
    "entrypoint": "index.js",
    "type": "terminal",
    "runtime": "bare",
    "upgrade": "pgh://1dd36bdfa9cb7f7d84adbd07f017e7200f7f90a3a546cd0761fac2a1f8a13e86/hyperident"
  },
  "scripts": {
    "dev": "bare index.js",
    "test": "brittle test/*.test.js",
    "socks": "node socks-server.js"
  },
  "dependencies": {
    "b4a": "^1.6.7",
    "bare-crypto": "^1.11.1",
    "bare-fs": "^4.7.2",
    "bare-os": "^2.4.2",
    "bare-path": "^2.1.3",
    "bare-process": "^4.2.2",
    "bare-subprocess": "^3.1.4",
    "hypercore-id-encoding": "^1.3.0",
    "hyperswarm": "^4.12.1",
    "keet-identity-key": "3.2.0",
    "sodium-universal": "^5.0.1"
  },
  "devDependencies": {
    "brittle": "^3.0.0",
    "pear-interface": "^1.0.0"
  },
  "imports": {
    "node:crypto": {
      "bare": "bare-crypto",
      "default": "node:crypto"
    },
    "fs": {
      "bare": "bare-fs",
      "default": "fs"
    },
    "fs/*": {
      "bare": "bare-fs/*",
      "default": "fs/*"
    },
    "node:fs/promises": {
      "bare": "bare-fs/promises",
      "default": "node:fs/promises"
    },
    "path": {
      "bare": "bare-path",
      "default": "path"
    },
    "node:path": {
      "bare": "bare-path",
      "default": "node:path"
    },
    "os": {
      "bare": "bare-os",
      "default": "os"
    },
    "node:os": {
      "bare": "bare-os",
      "default": "node:os"
    },
    "process": {
      "bare": "bare-process",
      "default": "process"
    },
    "child_process": {
      "bare": "bare-subprocess",
      "default": "child_process"
    }
  }
}
{
  "name": "hyperschema",
  "version": "1.23.0",
  "description": "Create registries of declarative compact-encoding schemas",
  "files": [
    "lib/*.js",
    "builder.mjs",
    "builder.cjs",
    "builder.d.ts",
    "runtime.mjs",
    "runtime.cjs",
    "primitives.mjs",
    "primitives.cjs"
  ],
  "exports": {
    "./package": "./package.json",
    ".": {
      "types": "./builder.d.ts",
      "import": "./builder.mjs",
      "default": "./builder.cjs"
    },
    "./runtime": {
      "import": "./runtime.mjs",
      "default": "./runtime.cjs"
    },
    "./primitives": {
      "import": "./primitives.mjs",
      "default": "./primitives.cjs"
    }
  },
  "imports": {
    "fs": {
      "bare": "bare-fs",
      "default": "fs"
    },
    "path": {
      "bare": "bare-path",
      "default": "path"
    }
  },
  "scripts": {
    "format": "prettier . --write",
    "test": "prettier . --check && brittle test/index.js",
    "test:bare": "prettier . --check && bare test/index.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/hyperschema.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/hyperschema/issues"
  },
  "homepage": "https://github.com/holepunchto/hyperschema#readme",
  "dependencies": {
    "bare-fs": "^4.0.1",
    "compact-encoding": "^3.0.0",
    "generate-object-property": "^2.0.0",
    "generate-string": "^1.0.1"
  },
  "devDependencies": {
    "brittle": "^3.7.0",
    "hyperschema-test": "^1.1.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^2.0.0",
    "test-tmp": "^1.3.0"
  }
}
module.exports = {
  c: require('compact-encoding')
}
const { EventEmitter } = require('events')
const { getStreamError } = require('streamx')
const DHT = require('hyperdht')
const spq = require('shuffled-priority-queue')
const b4a = require('b4a')
const unslab = require('unslab')

const PeerInfo = require('./lib/peer-info')
const RetryTimer = require('./lib/retry-timer')
const ConnectionSet = require('./lib/connection-set')
const PeerDiscovery = require('./lib/peer-discovery')

const MAX_PEERS = 64
const MAX_PARALLEL = 3
const MAX_CLIENT_CONNECTIONS = Infinity // TODO: Change
const MAX_SERVER_CONNECTIONS = Infinity

const ERR_MISSING_TOPIC = 'Topic is required and must be a 32-byte buffer'
const ERR_DESTROYED = 'Swarm has been destroyed'
const ERR_DUPLICATE = 'Duplicate connection'
const ERR_FIREWALL = 'Peer is firewalled'

module.exports = class Hyperswarm extends EventEmitter {
  constructor(opts = {}) {
    super()
    const {
      seed,
      relayThrough,
      keyPair = DHT.keyPair(seed),
      maxPeers = MAX_PEERS,
      maxClientConnections = MAX_CLIENT_CONNECTIONS,
      maxServerConnections = MAX_SERVER_CONNECTIONS,
      maxParallel = MAX_PARALLEL,
      firewall = allowAll
    } = opts
    this.keyPair = keyPair

    this.dht =
      opts.dht ||
      new DHT({
        bootstrap: opts.bootstrap,
        nodes: opts.nodes,
        port: opts.port,
        deferRandomPunch: opts.deferRandomPunch,
        randomPunchInterval: opts.randomPunchInterval
      })
    this.server = this.dht.createServer(
      {
        firewall: this._handleFirewall.bind(this),
        relayThrough: this._maybeRelayConnection.bind(this),
        handshakeClearWait: opts.handshakeClearWait
      },
      this._handleServerConnection.bind(this)
    )

    this.destroyed = false
    this.suspended = false
    this.maxPeers = maxPeers
    this.maxClientConnections = maxClientConnections
    this.maxServerConnections = maxServerConnections
    this.maxParallel = maxParallel
    this.relayThrough = relayThrough ? toRelayFunction(relayThrough) : null

    this.connecting = 0
    this.connections = new Set()
    this.peers = new Map()
    this.explicitPeers = new Set()
    this.listening = null
    this.stats = {
      updates: 0,
      connects: {
        client: {
          opened: 0,
          closed: 0,
          attempted: 0
        },
        server: {
          // Note: there is no notion of 'attempts' for server connections
          opened: 0,
          closed: 0
        }
      },
      bannedPeers: 0
    }

    this._discovery = new Map()
    this._timer = new RetryTimer(this._requeue.bind(this), {
      backoffs: opts.backoffs,
      jitter: opts.jitter
    })
    this._queue = spq()

    this._allConnections = new ConnectionSet()
    this._pendingFlushes = []
    this._flushTick = 0

    this._drainingQueue = false
    this._clientConnections = 0
    this._serverConnections = 0
    this._firewall = firewall

    this.dht.on('network-change', this._handleNetworkChange.bind(this))
    this.dht.on('network-update', this._handleNetworkUpdate.bind(this))
    this.on('update', this._handleUpdate)
  }

  _maybeRelayConnection(force) {
    if (!this.relayThrough) return null
    return this.relayThrough(force, this)
  }

  _enqueue(peerInfo) {
    if (peerInfo.queued) return
    peerInfo.queued = true
    peerInfo._flushTick = this._flushTick
    this._queue.add(peerInfo)

    this._attemptClientConnections()
  }

  _requeue(batch) {
    if (this.suspended) return
    for (const peerInfo of batch) {
      peerInfo.waiting = false

      if (
        peerInfo._updatePriority() === false ||
        this._allConnections.has(peerInfo.publicKey) ||
        peerInfo.queued
      ) {
        continue
      }
      peerInfo.queued = true
      peerInfo._flushTick = this._flushTick
      this._queue.add(peerInfo)
    }

    this._attemptClientConnections()
  }

  _flushMaybe(peerInfo) {
    for (let i = 0; i < this._pendingFlushes.length; i++) {
      const flush = this._pendingFlushes[i]
      if (peerInfo._flushTick > flush.tick) continue
      if (--flush.missing > 0) continue
      flush.onflush(true)
      this._pendingFlushes.splice(i--, 1)
    }
  }

  _withinMaxPeers() {
    const e = this.explicitPeers.size
    const factor = e === 0 ? 1 : e < 2 ? 2 : e < 4 ? 3 : 4
    return factor * this._allConnections.size < this.maxPeers
  }

  _flushAllMaybe() {
    if (
      this.connecting > 0 ||
      (this._withinMaxPeers() && this._clientConnections < this.maxClientConnections)
    ) {
      return false
    }

    while (this._pendingFlushes.length) {
      const flush = this._pendingFlushes.pop()
      flush.onflush(true)
    }

    return true
  }

  _shouldConnectExplicit() {
    return !this.destroyed && !this.suspended && this.connecting < this.maxParallel
  }

  _shouldConnect() {
    return (
      !this.destroyed &&
      !this.suspended &&
      this.connecting < this.maxParallel &&
      this._withinMaxPeers() &&
      this._clientConnections < this.maxClientConnections
    )
  }

  _shouldRequeue(peerInfo) {
    if (this.suspended) return false
    if (peerInfo.explicit) return true
    for (const topic of peerInfo.topics) {
      if (this._discovery.has(b4a.toString(topic, 'hex')) && !this.destroyed) {
        return true
      }
    }
    return false
  }

  _connect(peerInfo, queued) {
    if (peerInfo.banned || this._allConnections.has(peerInfo.publicKey)) {
      if (queued) this._flushMaybe(peerInfo)
      return
    }

    // TODO: Support async firewalling at some point.
    if (this._handleFirewall(peerInfo.publicKey, null)) {
      if (queued) this._flushMaybe(peerInfo)
      return
    }

    const relayThrough = this._maybeRelayConnection(peerInfo.forceRelaying)
    const conn = this.dht.connect(peerInfo.publicKey, {
      relayAddresses: peerInfo.relayAddresses,
      keyPair: this.keyPair,
      relayThrough
    })
    this._allConnections.add(conn)

    this.stats.connects.client.attempted++

    this.connecting++
    this._clientConnections++
    let opened = false

    const onerror = (err) => {
      if (this.relayThrough && shouldForceRelaying(err.code)) {
        peerInfo.forceRelaying = true
        // Reset the attempts in order to fast connect to relay
        peerInfo.attempts = 0
      }
    }

    // Removed once a connection is opened
    conn.on('error', onerror)

    conn.on('open', () => {
      opened = true
      this.stats.connects.client.opened++

      this._connectDone()
      this.connections.add(conn)
      conn.removeListener('error', onerror)
      peerInfo._connected()
      peerInfo.client = true
      this.emit('connection', conn, peerInfo)
      if (queued) this._flushMaybe(peerInfo)

      this.emit('update')
    })
    conn.on('close', () => {
      if (!opened) this._connectDone()
      this.stats.connects.client.closed++

      const err = getStreamError(conn)
      if (shouldBan(err)) {
        this._banPeer(peerInfo, true, err)
      }

      this.connections.delete(conn)
      this._allConnections.delete(conn)
      this._clientConnections--
      peerInfo._disconnected()

      peerInfo.waiting = this._shouldRequeue(peerInfo) && this._timer.add(peerInfo)
      this._maybeDeletePeer(peerInfo)

      if (!opened && queued) this._flushMaybe(peerInfo)

      this._attemptClientConnections()

      this.emit('update')
    })

    this.emit('update')
  }

  _connectDone() {
    this.connecting--

    if (this.connecting < this.maxParallel) this._attemptClientConnections()
    if (this.connecting === 0) this._flushAllMaybe()
  }

  // Called when the PeerQueue indicates a connection should be attempted.
  _attemptClientConnections() {
    // Guard against re-entries - unsure if it still needed but doesn't hurt
    if (this._drainingQueue || this.suspended) return
    this._drainingQueue = true

    for (const peerInfo of this.explicitPeers) {
      if (!this._shouldConnectExplicit()) break
      if (
        peerInfo.attempts >= 5 ||
        Date.now() - peerInfo.disconnectedTime < peerInfo.attempts * 1000
      ) {
        continue
      }
      this._connect(peerInfo, false)
    }

    while (this._queue.length && this._shouldConnect()) {
      const peerInfo = this._queue.shift()
      peerInfo.queued = false
      this._connect(peerInfo, true)
    }
    this._drainingQueue = false
    if (this.connecting === 0) this._flushAllMaybe()
  }

  _handleFirewall(remotePublicKey, payload) {
    if (b4a.equals(remotePublicKey, this.keyPair.publicKey)) return true

    let peerInfo = this.peers.get(b4a.toString(remotePublicKey, 'hex'))
    if (peerInfo && peerInfo.banned) return true

    const firewalled = this._firewall(remotePublicKey, payload)
    if (firewalled) {
      if (!peerInfo) peerInfo = this._upsertPeer(remotePublicKey)
      this._banPeer(peerInfo, true, new Error(ERR_FIREWALL))
    }

    return firewalled
  }

  _handleServerConnectionSwap(existing, conn) {
    let closed = false

    existing.on('close', () => {
      if (closed) return

      conn.removeListener('error', noop)
      conn.removeListener('close', onclose)

      this._handleServerConnection(conn)
    })

    conn.on('error', noop)
    conn.on('close', onclose)

    function onclose() {
      closed = true
    }
  }

  // Called when the DHT receives a new server connection.
  _handleServerConnection(conn) {
    if (this.destroyed || this.suspended) {
      // TODO: Investigate why a final server connection can be received after close
      conn.on('error', noop)
      return conn.destroy(ERR_DESTROYED)
    }

    const existing = this._allConnections.get(conn.remotePublicKey)

    if (existing) {
      // If both connections are from the same peer,
      // - pick the new one if the existing stream is already established (has sent and received bytes),
      //   because the other client must have lost that connection and be reconnecting
      // - otherwise, pick the one thats expected to initiate in a tie break
      const existingIsOutdated = existing.rawBytesRead > 0 && existing.rawBytesWritten > 0
      const expectedInitiator = b4a.compare(conn.publicKey, conn.remotePublicKey) > 0
      const keepNew = existingIsOutdated || expectedInitiator === conn.isInitiator

      if (keepNew === false) {
        existing.sendKeepAlive()
        conn.on('error', noop)
        conn.destroy(new Error(ERR_DUPLICATE))
        return
      }

      existing.on('error', noop)
      existing.destroy(new Error(ERR_DUPLICATE))
      this._handleServerConnectionSwap(existing, conn)
      return
    }

    // When reaching here, the connection will always be 'opened' next tick
    this.stats.connects.server.opened++

    const peerInfo = this._upsertPeer(conn.remotePublicKey, null)

    this.connections.add(conn)
    this._allConnections.add(conn)
    this._serverConnections++

    conn.on('close', () => {
      const err = getStreamError(conn)
      if (shouldBan(err)) {
        this._banPeer(peerInfo, true, err)
      }

      this.connections.delete(conn)
      this._allConnections.delete(conn)
      this._serverConnections--
      this.stats.connects.server.closed++

      this._maybeDeletePeer(peerInfo)

      this._attemptClientConnections()

      this.emit('update')
    })
    peerInfo.client = false
    this.emit('connection', conn, peerInfo)

    this.emit('update')
  }

  _upsertPeer(publicKey, relayAddresses) {
    if (b4a.equals(publicKey, this.keyPair.publicKey)) return null
    const keyString = b4a.toString(publicKey, 'hex')
    let peerInfo = this.peers.get(keyString)

    if (peerInfo) {
      peerInfo.relayAddresses = relayAddresses // new is always better
      return peerInfo
    }

    peerInfo = new PeerInfo({
      publicKey,
      relayAddresses
    })

    this.peers.set(keyString, peerInfo)
    return peerInfo
  }

  _handleUpdate() {
    this.stats.updates++
  }

  _maybeDeletePeer(peerInfo) {
    if (!peerInfo.shouldGC()) return

    const hasActiveConn = this._allConnections.has(peerInfo.publicKey)
    if (hasActiveConn) return

    const keyString = b4a.toString(peerInfo.publicKey, 'hex')
    this.peers.delete(keyString)
  }

  /*
   * Called when a peer is actively discovered during a lookup.
   *
   * Three conditions:
   *  1. Not a known peer -- insert into queue
   *  2. A known peer with normal priority -- do nothing
   *  3. A known peer with low priority -- bump priority, because it's been rediscovered
   */
  _handlePeer(peer, topic) {
    const peerInfo = this._upsertPeer(peer.publicKey, peer.relayAddresses)
    if (peerInfo) peerInfo._topic(topic)
    if (!peerInfo || this._allConnections.has(peer.publicKey)) return
    if (!peerInfo.prioritized || peerInfo.server) peerInfo._reset()
    if (peerInfo._updatePriority()) {
      this._enqueue(peerInfo)
    }
  }

  async _handleNetworkUpdate() {
    if (!this.online) return
    this._handleNetworkChange()
  }

  async _handleNetworkChange() {
    if (this.suspended) return

    // prioritize figuring out if existing connections are dead
    for (const conn of this._allConnections) {
      conn.sendKeepAlive()
    }

    const refreshes = []

    for (const discovery of this._discovery.values()) {
      refreshes.push(discovery.refresh())
    }

    await Promise.allSettled(refreshes)
  }

  _banPeer(peerInfo, banned, err) {
    peerInfo.ban(banned)
    this.stats.bannedPeers++
    this.emit('ban', peerInfo, err)
  }

  status(key) {
    return this._discovery.get(b4a.toString(key, 'hex')) || null
  }

  listen() {
    if (!this.listening) {
      if (this.destroyed) throw new Error('Swarm destroyed')
      this.listening = this.server.listen(this.keyPair)
    }
    return this.listening
  }

  // Object that exposes a cancellation method (destroy)
  // TODO: When you rejoin, it should reannounce + bump lookup priority
  join(topic, opts = {}) {
    if (this.destroyed) throw new Error('Swarm destroyed')
    if (!topic) throw new Error(ERR_MISSING_TOPIC)
    topic = unslab(topic)

    const topicString = b4a.toString(topic, 'hex')

    let discovery = this._discovery.get(topicString)

    if (discovery && !discovery.destroyed) {
      return discovery.session(opts)
    }

    discovery = new PeerDiscovery(this, topic, {
      limit: opts.limit,
      wait: discovery ? discovery.destroy() : null,
      suspended: this.suspended,
      onpeer: (peer) => this._handlePeer(peer, topic)
    })
    this._discovery.set(topicString, discovery)
    return discovery.session(opts)
  }

  // Returns a promise
  async leave(topic) {
    if (!topic) throw new Error(ERR_MISSING_TOPIC)
    const topicString = b4a.toString(topic, 'hex')
    if (!this._discovery.has(topicString)) return Promise.resolve()

    const discovery = this._discovery.get(topicString)

    try {
      await discovery.destroy()
    } catch {
      // ignore, prop network
    }

    if (this._discovery.get(topicString) === discovery) {
      this._discovery.delete(topicString)
    }
  }

  joinPeer(publicKey) {
    const peerInfo = this._upsertPeer(publicKey, null)
    if (!peerInfo) return
    if (!this.explicitPeers.has(peerInfo)) {
      peerInfo.explicit = true
      this.explicitPeers.add(peerInfo)
    }
    if (this._allConnections.has(publicKey)) return
    if (peerInfo._updatePriority()) {
      this._enqueue(peerInfo)
    }
  }

  leavePeer(publicKey) {
    const keyString = b4a.toString(publicKey, 'hex')
    if (!this.peers.has(keyString)) return

    const peerInfo = this.peers.get(keyString)
    peerInfo.explicit = false
    this.explicitPeers.delete(peerInfo)
    this._maybeDeletePeer(peerInfo)
  }

  // Returns a promise
  async flush() {
    const allFlushed = [...this._discovery.values()].map((v) => v.flushed())
    await Promise.all(allFlushed)
    if (this._flushAllMaybe()) return true
    const pendingSize = this._allConnections.size - this.connections.size
    if (!this._queue.length && !pendingSize) return true
    return new Promise((resolve) => {
      this._pendingFlushes.push({
        onflush: resolve,
        missing: this._queue.length + pendingSize,
        tick: this._flushTick++
      })
    })
  }

  async clear() {
    const cleared = Promise.allSettled([...this._discovery.values()].map((d) => d.destroy()))
    this._discovery.clear()
    return cleared
  }

  async destroy({ force } = {}) {
    if (this.destroyed && !force) return
    this.destroyed = true

    this._timer.destroy()

    if (!force) await this.clear()

    await this.server.close()

    while (this._pendingFlushes.length) {
      const flush = this._pendingFlushes.pop()
      flush.onflush(false)
    }

    await this.dht.destroy({ force })
  }

  async suspend({ log = noop } = {}) {
    if (this.suspended) return

    const promises = []

    promises.push(this.server.suspend({ log }))

    for (const discovery of this._discovery.values()) {
      promises.push(discovery.suspend({ log }))
    }

    const pending = []
    for (const connection of this._allConnections) {
      connection.destroy()
      pending.push(new Promise((resolve) => connection.on('close', resolve)))
    }

    this.suspended = true

    log('Suspending server and discovery... (' + promises.length + ')')
    await Promise.allSettled(promises)
    log('Done, suspending the dht...')
    await this.dht.suspend({ log })
    log('Done, swarm fully suspended')

    await Promise.all(pending)

    // reset queue
    this._timer.destroy()
    this._timer = new RetryTimer(this._requeue.bind(this), {
      backoffs: this._timer.backoffs,
      jitter: this._timer.jitter
    })
    this._queue = spq()
  }

  async resume({ log = noop } = {}) {
    if (!this.suspended) return

    log('Resuming the dht')
    await this.dht.resume()
    log('Done, resuming the server')
    await this.server.resume()
    log('Done, all discovery')

    for (const discovery of this._discovery.values()) {
      discovery.resume()
    }

    this.suspended = false
    this._attemptClientConnections()
  }

  topics() {
    return this._discovery.values()
  }
}

function noop() {}

function allowAll() {
  return false
}

function shouldForceRelaying(code) {
  return (
    code === 'HOLEPUNCH_ABORTED' ||
    code === 'HOLEPUNCH_DOUBLE_RANDOMIZED_NATS' ||
    code === 'REMOTE_NOT_HOLEPUNCHABLE'
  )
}

function shouldBan() {
  // return !!err && err.name === 'HypercoreError' && err.code === 'INVALID_OPERATION'
  return false
}

function toRelayFunction(relayThrough) {
  return typeof relayThrough === 'function'
    ? relayThrough
    : (force, swarm) => (force || swarm.dht.randomized ? relayThrough : null)
}
module.exports = class BulkTimer {
  constructor(time, fn) {
    this._time = time
    this._fn = fn
    this._interval = null
    this._next = []
    this._pending = []
    this._destroyed = false
  }

  destroy() {
    if (this._destroyed) return
    this._destroyed = true
    clearInterval(this._interval)
    this._interval = null
  }

  _ontick() {
    if (!this._next.length && !this._pending.length) return
    if (this._next.length) this._fn(this._next)
    this._next = this._pending
    this._pending = []
  }

  add(info) {
    if (this._destroyed) return
    if (!this._interval) {
      this._interval = setInterval(this._ontick.bind(this), Math.floor(this._time * 0.66))
    }

    this._pending.push(info)
  }
}
const b4a = require('b4a')

module.exports = class ConnectionSet {
  constructor() {
    this._byPublicKey = new Map()
  }

  [Symbol.iterator]() {
    return this._byPublicKey.values()
  }

  get size() {
    return this._byPublicKey.size
  }

  has(publicKey) {
    return this._byPublicKey.has(toHex(publicKey))
  }

  get(publicKey) {
    return this._byPublicKey.get(toHex(publicKey))
  }

  add(connection) {
    this._byPublicKey.set(b4a.toString(connection.remotePublicKey, 'hex'), connection)
  }

  delete(connection) {
    const keyString = b4a.toString(connection.remotePublicKey, 'hex')
    const existing = this._byPublicKey.get(keyString)
    if (existing !== connection) return
    this._byPublicKey.delete(keyString)
  }
}

function toHex(b) {
  return typeof b === 'string' ? b : b4a.toString(b, 'hex')
}
const safetyCatch = require('safety-catch')
const b4a = require('b4a')

const REFRESH_INTERVAL = 1000 * 60 * 10 // 10 min
const RANDOM_JITTER = 1000 * 60 * 2 // 2 min
const DELAY_GRACE_PERIOD = 1000 * 30 // 30s

const MAX_DISCOVERY_CACHE = 64

module.exports = class PeerDiscovery {
  constructor(
    swarm,
    topic,
    { limit = Infinity, wait = null, suspended = false, onpeer = noop, onerror = safetyCatch }
  ) {
    this.limit = limit
    this.swarm = swarm
    this.topic = topic
    this.isClient = false
    this.isServer = false
    this.destroyed = false
    this.destroying = null
    this.suspended = suspended

    this._sessions = []
    this._clientSessions = 0
    this._serverSessions = 0

    this._onpeer = onpeer
    this._onerror = onerror

    this._discovered = new Set()
    this._activeQuery = null
    this._timer = null
    this._currentRefresh = null
    this._closestNodes = null
    this._firstAnnounce = true
    this._needsUnannounce = false
    this._refreshes = 0
    this._wait = wait
  }

  session({ server = true, client = true, limit = Infinity, onerror = safetyCatch }) {
    if (this.destroyed) throw new Error('PeerDiscovery is destroyed')
    const session = new PeerDiscoverySession(this)
    session.refresh({ server, client, limit }).catch(onerror)
    this._sessions.push(session)
    return session
  }

  _refreshLater(eager) {
    const jitter = Math.round(Math.random() * RANDOM_JITTER)
    const delay = !eager ? REFRESH_INTERVAL + jitter : jitter

    if (this._timer) clearTimeout(this._timer)

    const startTime = Date.now()
    this._timer = setTimeout(() => {
      // If your laptop went to sleep, and is coming back online...
      const overdue = Date.now() - startTime > delay + DELAY_GRACE_PERIOD
      if (overdue) this._refreshLater(true)
      else this.refresh().catch(this._onerror)
    }, delay)
  }

  _isActive() {
    return !this.destroyed && !this.suspended
  }

  // TODO: Allow announce to be an argument to this
  // TODO: Maybe announce should be a setter?
  async _refresh() {
    if (this.suspended) return
    const clock = ++this._refreshes

    if (this._wait) {
      await this._wait
      this._wait = null
      if (clock !== this._refreshes || !this._isActive()) return
    }

    const clear = this.isServer && this._firstAnnounce
    if (clear) this._firstAnnounce = false

    const opts = {
      clear,
      closestNodes: this._closestNodes
    }

    if (this.isServer) {
      await this.swarm.listen()
      // if a parallel refresh is happening, yield to the new one
      if (clock !== this._refreshes || !this._isActive()) return
      this._needsUnannounce = true
    }

    let limit = this.limit

    if (limit < Infinity && limit > 0) {
      for (const id of this._discovered) {
        if (!this.swarm.connections.has(id)) continue
        if (--limit === 0) break
      }
    }

    this._discovered.clear()

    const announcing = this.isServer
    const query = (this._activeQuery = announcing
      ? this.swarm.dht.announce(
          this.topic,
          this.swarm.keyPair,
          this.swarm.server.relayAddresses,
          opts
        )
      : this._needsUnannounce
        ? this.swarm.dht.lookupAndUnannounce(this.topic, this.swarm.keyPair, opts)
        : this.swarm.dht.lookup(this.topic, opts))

    try {
      for await (const data of this._activeQuery) {
        if (!this.isClient || !this._isActive()) continue
        for (const peer of data.peers) {
          if (limit < Infinity) {
            const id = b4a.toString(peer.publicKey, 'hex')

            if (this._discovered.size < MAX_DISCOVERY_CACHE) {
              this._discovered.add(id)
            }

            if (limit === 0) continue

            // there is a chance it has updated a connection during discovery and we go over
            // the limit - thats acceptable to avoid a complexity spiral here.
            if (!this.swarm.connections.has(id)) limit--
          }

          this._onpeer(peer, data)
        }
      }
    } catch (err) {
      if (this._isActive()) throw err
    } finally {
      if (this._activeQuery === query) {
        this._activeQuery = null
        if (!this.destroyed && !this.suspended) this._refreshLater(false)
      }
    }

    // This is set at the very end, when the query completes successfully.
    this._closestNodes = query.closestNodes

    if (clock !== this._refreshes) return

    // In this is the latest query, unannounce has been fulfilled as well
    if (!announcing) this._needsUnannounce = false
  }

  async refresh() {
    if (this.destroyed) throw new Error('PeerDiscovery is destroyed')

    const server = this._serverSessions > 0
    const client = this._clientSessions > 0

    if (this.suspended) return

    if (server === this.isServer && client === this.isClient) {
      if (this._currentRefresh) return this._currentRefresh
      this._currentRefresh = this._refresh()
    } else {
      if (this._activeQuery) this._activeQuery.destroy()
      this.isServer = server
      this.isClient = client
      this._currentRefresh = this._refresh()
    }

    const refresh = this._currentRefresh
    try {
      await refresh
    } catch {
      return false
    } finally {
      if (refresh === this._currentRefresh) {
        this._currentRefresh = null
      }
    }

    return true
  }

  async flushed() {
    if (this.swarm.listening) await this.swarm.listening

    try {
      await this._currentRefresh
      return true
    } catch {
      return false
    }
  }

  async _destroyMaybe() {
    if (this.destroyed) return

    try {
      if (this._sessions.length === 0) await this.swarm.leave(this.topic)
      else if (this._serverSessions === 0 && this._needsUnannounce) await this.refresh()
    } catch (err) {
      // ignore network failures here, as we are tearing down
      safetyCatch(err)
    }
  }

  destroy() {
    if (this.destroying) return this.destroying
    this.destroying = this._destroy()
    return this.destroying
  }

  async _abort(log) {
    const id = log === noop ? '' : b4a.toString(this.topic, 'hex')

    log('Aborting discovery', id)
    if (this._wait) await this._wait
    log('Aborting discovery (post wait)', id)

    if (this._activeQuery) {
      this._activeQuery.destroy()
      this._activeQuery = null
    }
    if (this._timer) {
      clearTimeout(this._timer)
      this._timer = null
    }

    let nodes = this._closestNodes

    if (this._currentRefresh) {
      try {
        await this._currentRefresh
      } catch {
        // If the destroy causes the refresh to fail, suppress it.
      }
    }

    log('Aborting discovery (post refresh)', id)
    if (this._isActive()) return

    if (!nodes) nodes = this._closestNodes
    else if (this._closestNodes !== nodes) {
      const len = nodes.length
      for (const newer of this._closestNodes) {
        if (newer.id && !hasNode(nodes, len, newer)) nodes.push(newer)
      }
    }

    if (this._needsUnannounce) {
      log('Unannouncing discovery', id)
      if (nodes && nodes.length) {
        await this.swarm.dht.unannounce(this.topic, this.swarm.keyPair, {
          closestNodes: nodes,
          onlyClosestNodes: true,
          force: true
        })
      }
      this._needsUnannounce = false
      log('Unannouncing discovery (done)', id)
    }
  }

  _destroy() {
    if (this.destroyed) return
    this.destroyed = true
    return this._abort(noop)
  }

  async suspend({ log = noop } = {}) {
    if (this.suspended) return
    this.suspended = true
    try {
      await this._abort(log)
    } catch {
      // ignore
    }
  }

  resume() {
    if (!this.suspended) return
    this.suspended = false
    this.refresh().catch(noop)
  }
}

class PeerDiscoverySession {
  constructor(discovery) {
    this.discovery = discovery
    this.isClient = false
    this.isServer = false
    this.destroyed = false
  }

  get swarm() {
    return this.discovery.swarm
  }

  get topic() {
    return this.discovery.topic
  }

  async refresh({ client = this.isClient, server = this.isServer, limit = Infinity } = {}) {
    if (this.destroyed) throw new Error('PeerDiscovery is destroyed')
    if (!client && !server) throw new Error('Cannot refresh with neither client nor server option')

    if (client !== this.isClient) {
      this.isClient = client
      this.discovery._clientSessions += client ? 1 : -1
    }

    if (server !== this.isServer) {
      this.isServer = server
      this.discovery._serverSessions += server ? 1 : -1
    }

    this.discovery.limit = limit

    return this.discovery.refresh()
  }

  async flushed() {
    return this.discovery.flushed()
  }

  async destroy() {
    if (this.destroyed) return
    this.destroyed = true

    if (this.isClient) this.discovery._clientSessions--
    if (this.isServer) this.discovery._serverSessions--

    const index = this.discovery._sessions.indexOf(this)
    const head = this.discovery._sessions.pop()

    if (head !== this) this.discovery._sessions[index] = head

    return this.discovery._destroyMaybe()
  }
}

function hasNode(nodes, len, node) {
  for (let i = 0; i < len; i++) {
    const existing = nodes[i]
    if (existing.id && b4a.equals(existing.id, node.id)) return true
  }

  return false
}

function noop() {}
const { EventEmitter } = require('events')
const b4a = require('b4a')
const unslab = require('unslab')

const MIN_CONNECTION_TIME = 15000

const VERY_LOW_PRIORITY = 0
const LOW_PRIORITY = 1
const NORMAL_PRIORITY = 2
const HIGH_PRIORITY = 3
const VERY_HIGH_PRIORITY = 4

module.exports = class PeerInfo extends EventEmitter {
  constructor({ publicKey, relayAddresses }) {
    super()

    this.publicKey = unslab(publicKey)
    this.relayAddresses = relayAddresses

    this.reconnecting = true
    this.proven = false
    this.connectedTime = -1
    this.disconnectedTime = 0
    this.banned = false
    this.tried = false
    this.explicit = false
    this.waiting = false
    this.forceRelaying = false

    // Set by the Swarm
    this.queued = false
    this.client = false
    this.topics = [] // TODO: remove on next major (check with mafintosh for context)

    this.attempts = 0
    this.priority = NORMAL_PRIORITY

    // Used by shuffled-priority-queue
    this._index = 0

    // Used for flush management
    this._flushTick = 0

    // Used for topic multiplexing
    this._seenTopics = new Set()
  }

  get server() {
    return !this.client
  }

  get prioritized() {
    return this.priority >= NORMAL_PRIORITY
  }

  _getPriority() {
    const peerIsStale = this.tried && !this.proven
    if (peerIsStale || this.attempts > 3) return VERY_LOW_PRIORITY
    if (this.attempts === 3) return LOW_PRIORITY
    if (this.attempts === 2) return HIGH_PRIORITY
    if (this.attempts === 1) return VERY_HIGH_PRIORITY
    return NORMAL_PRIORITY
  }

  _connected() {
    this.proven = true
    this.connectedTime = Date.now()
  }

  _disconnected() {
    this.disconnectedTime = Date.now()
    if (this.connectedTime > -1) {
      if (this.disconnectedTime - this.connectedTime >= MIN_CONNECTION_TIME) this.attempts = 0 // fast retry
      this.connectedTime = -1
    }
    this.attempts++
  }

  _deprioritize() {
    this.attempts = 3
  }

  _reset() {
    this.client = false
    this.proven = false
    this.tried = false
    this.attempts = 0
  }

  _updatePriority() {
    if (this.explicit && this.attempts > 3) this._deprioritize()
    if (this.banned || this.queued || this.attempts > 3) return false
    this.priority = this._getPriority()
    return true
  }

  _topic(topic) {
    const topicString = b4a.toString(topic, 'hex')
    if (this._seenTopics.has(topicString)) return
    this._seenTopics.add(topicString)
    this.topics.push(topic)
    this.emit('topic', topic)
  }

  reconnect(val) {
    this.reconnecting = !!val
  }

  ban(val) {
    this.banned = !!val
  }

  shouldGC() {
    return !(this.banned || this.queued || this.explicit || this.waiting)
  }
}
const BulkTimer = require('./bulk-timer')

const BACKOFF_JITTER = 500
const BACKOFF_S = 1000 + Math.round(BACKOFF_JITTER * Math.random())
const BACKOFF_M = 5000 + Math.round(2 * BACKOFF_JITTER * Math.random())
const BACKOFF_L = 15000 + Math.round(4 * BACKOFF_JITTER * Math.random())
const BACKOFF_X = 1000 * 60 * 10 + Math.round(240 * BACKOFF_JITTER * Math.random())

module.exports = class RetryTimer {
  constructor(
    push,
    { backoffs = [BACKOFF_S, BACKOFF_M, BACKOFF_L, BACKOFF_X], jitter = BACKOFF_JITTER } = {}
  ) {
    this.jitter = jitter
    this.backoffs = backoffs

    this._sTimer = new BulkTimer(backoffs[0] + Math.round(jitter * Math.random()), push)
    this._mTimer = new BulkTimer(backoffs[1] + Math.round(jitter * Math.random()), push)
    this._lTimer = new BulkTimer(backoffs[2] + Math.round(jitter * Math.random()), push)
    this._xTimer = new BulkTimer(backoffs[3] + Math.round(jitter * Math.random()), push)
  }

  _selectRetryTimer(peerInfo) {
    if (peerInfo.banned || !peerInfo.reconnecting) return null

    if (peerInfo.attempts > 3) {
      return peerInfo.explicit ? this._xTimer : null
    }

    if (peerInfo.attempts === 0) return this._sTimer
    if (peerInfo.proven) {
      switch (peerInfo.attempts) {
        case 1:
          return this._sTimer
        case 2:
          return this._mTimer
        case 3:
          return this._lTimer
      }
    } else {
      switch (peerInfo.attempts) {
        case 1:
          return this._mTimer
        case 2:
          return this._lTimer
        case 3:
          return this._lTimer
      }
    }

    return null
  }

  add(peerInfo) {
    const timer = this._selectRetryTimer(peerInfo)
    if (!timer) return false

    timer.add(peerInfo)
    return true
  }

  destroy() {
    this._sTimer.destroy()
    this._mTimer.destroy()
    this._lTimer.destroy()
    this._xTimer.destroy()
  }
}
{
  "name": "hyperswarm",
  "version": "4.17.0",
  "description": "A distributed networking stack for connecting peers",
  "files": [
    "index.js",
    "lib/**.js"
  ],
  "imports": {
    "events": {
      "bare": "bare-events",
      "default": "events"
    },
    "process": {
      "bare": "bare-process",
      "default": "process"
    }
  },
  "dependencies": {
    "b4a": "^1.3.1",
    "bare-events": "^2.2.0",
    "hyperdht": "^6.21.0",
    "safety-catch": "^1.0.2",
    "shuffled-priority-queue": "^2.1.0",
    "streamx": "^2.22.1",
    "unslab": "^1.3.0"
  },
  "devDependencies": {
    "bare-process": "^4.2.2",
    "brittle": "^3.0.2",
    "hypercore-crypto": "^3.4.0",
    "lunte": "^1.3.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^2.0.0",
    "which-runtime": "^1.3.2"
  },
  "scripts": {
    "format": "prettier --write .",
    "lint": "prettier --check . && lunte",
    "test": "npm run test:node && npm run test:bare",
    "test:generate": "brittle -r test/all.js test/*.js",
    "test:node": "brittle-node --coverage test/*.js",
    "test:bare": "bare test/all.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/holepunchto/hyperswarm.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "contributors": [
    "David Mark Clements (@davidmarkclem)",
    "Andrew Osheroff (@andrewosh)"
  ],
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/holepunchto/hyperswarm/issues"
  },
  "homepage": "https://github.com/holepunchto/hyperswarm"
}
const b4a = require('b4a')

const EMPTY = b4a.alloc(0)
const MAX = b4a.from([0xff])
const BUFFER = {}

BUFFER.preencode = function (state, buf) {
  if (buf === null) buf = EMPTY

  let i = 0
  let extra = 3

  while ((i = b4a.indexOf(buf, 0x00, i)) > -1) {
    i++
    extra++
  }

  state.end += buf.byteLength + extra
}

BUFFER.encode = function (state, buf) {
  if (buf === null) buf = EMPTY

  state.buffer[state.start++] = 0x00

  let prev = 0
  let i = 0

  while ((i = b4a.indexOf(buf, 0x00, i)) > -1) {
    const slice = buf.subarray(prev, ++i)

    state.buffer.set(slice, state.start)
    state.start += slice.byteLength
    state.buffer[state.start++] = 0x02
    prev = i
  }

  const slice = buf.subarray(prev)

  state.buffer.set(slice, state.start)
  state.start += slice.byteLength

  state.buffer[state.start++] = 0x00
  state.buffer[state.start++] = 0x01
}

BUFFER.decode = function (state) {
  if (state.start >= state.end) throw new Error('Out of bounds')
  if (state.buffer[state.start++] !== 0x00) {
    throw new Error('Invalid start of string')
  }

  let escaped = null

  let prev = state.start
  let i = state.start

  while ((i = b4a.indexOf(state.buffer, 0x00, i)) > -1) {
    const next = ++i < state.end ? state.buffer[i] : 0x00

    i++

    if (next === 0x01) {
      break
    }

    if (next === 0x02) {
      if (escaped === null) escaped = []
      escaped.push(state.buffer.subarray(prev, i - 1))
      prev = i
      continue
    }

    throw new Error('Unknown value in terminator')
  }

  if (i === -1) {
    throw new Error('No terminator found')
  }

  state.start = i
  const last = state.buffer.subarray(prev, i - 2)
  if (escaped === null) return last

  escaped.push(last)
  return b4a.concat(escaped)
}

// TODO: can be optimised a lot

const STRING = {}

STRING.preencode = function (state, str) {
  BUFFER.preencode(state, b4a.from(str || ''))
}

STRING.encode = function (state, str) {
  BUFFER.encode(state, b4a.from(str || ''))
}

STRING.decode = function (state, str) {
  return b4a.toString(BUFFER.decode(state))
}

const UINT = {}

UINT.preencode = function (state, n) {
  state.end += n <= 0xfb ? 1 : n <= 0xffff ? 3 : n <= 0xffffffff ? 5 : n === Infinity ? 1 : 9
}

UINT.encode = function (state, n) {
  if (n === Infinity) {
    state.buffer[state.start++] = 0xff
    return
  }

  if (n <= 0xfb) {
    state.buffer[state.start++] = n
    return
  }

  if (n <= 0xffff) {
    state.buffer[state.start++] = 0xfc
    state.buffer[state.start++] = n >>> 8
    state.buffer[state.start++] = n
    return
  }

  if (n <= 0xffffffff) {
    state.buffer[state.start++] = 0xfd
    encodeUint32(state, n)
    return
  }

  if (Number.isSafeInteger(n)) {
    state.buffer[state.start++] = 0xfe

    const r = Math.floor(n / 0x100000000)
    encodeUint32(state, r)
    encodeUint32(state, n)
    return
  }

  throw new Error('Invalid number ' + n)
}

UINT.decode = function (state) {
  if (state.start >= state.end) throw new Error('Out of bounds')

  const a = state.buffer[state.start++]

  if (a <= 0xfb) return a

  if (a === 0xfc) {
    if (state.end - state.start < 2) throw new Error('Out of bounds')
    return state.buffer[state.start++] * 0x100 + state.buffer[state.start++]
  }

  if (a === 0xfd) {
    return decodeUint32(state)
  }

  if (a === 0xfe) {
    return decodeUint32(state) * 0x100000000 + decodeUint32(state)
  }

  return Infinity
}

const INT = {}

INT.preencode = function (state, n) {
  // One asymmetric condition
  if (n >= -0xff && n < 0) {
    state.end += 2
    return
  }
  n = Math.abs(n)
  state.end += n <= 0xf6 ? 1 : n <= 0xffff ? 3 : n <= 0xffffffff ? 5 : n === Infinity ? 1 : 9
}

INT.encode = function (state, n) {
  if (n === Infinity) {
    state.buffer[state.start++] = 0xff
    return
  }

  if (n === -Infinity) {
    state.buffer[state.start++] = 0x00
    return
  }

  // Negative 64 bit
  if (n < -0xffffffff) {
    if (!Number.isSafeInteger(n)) throw new Error('Invalid number ' + n)
    state.buffer[state.start++] = 0x01

    const r = Math.floor(-n / 0x100000000)
    encodeUint32(state, -r + 0xffffffff)
    encodeUint32(state, -(-n % 0x100000000) + 0xffffffff)
    return
  }

  // Negative 32 bit
  if (n < -0xffff) {
    state.buffer[state.start++] = 0x02

    n += 0xffffffff
    encodeUint32(state, n)
    return
  }

  // Negative 16bit
  if (n < -0xff) {
    state.buffer[state.start++] = 0x03

    n += 0xffff
    state.buffer[state.start++] = n >>> 8
    state.buffer[state.start++] = n
    return
  }

  // Negative 8bit
  if (n < 0) {
    state.buffer[state.start++] = 0x04

    n += 0xff
    state.buffer[state.start++] = n
    return
  }

  // Remainder of 8bit space for positive int
  if (n <= 0xf6) {
    state.buffer[state.start++] = n + 0x05
    return
  }

  // Positive 16bit
  if (n <= 0xffff) {
    state.buffer[state.start++] = 0xfc
    state.buffer[state.start++] = n >>> 8
    state.buffer[state.start++] = n
    return
  }

  // Positive 32bit
  if (n <= 0xffffffff) {
    state.buffer[state.start++] = 0xfd
    encodeUint32(state, n)
    return
  }

  // Positive 64bit
  if (Number.isSafeInteger(n)) {
    state.buffer[state.start++] = 0xfe

    const r = Math.floor(n / 0x100000000)
    encodeUint32(state, r)
    encodeUint32(state, n)
    return
  }

  throw new Error('Invalid number ' + n)
}

INT.decode = function (state) {
  if (state.start >= state.end) throw new Error('Out of bounds')

  const a = state.buffer[state.start++]

  if (a === 0x00) return -Infinity

  if (a === 0x01) {
    // Split (2^64 - 1) into its 32 bit components
    // r * 0x100000000 + n - (2^64 - 1)
    // r * 0x100000000 + n - (0xffffffff * 0x100000000) - 0xffffffff
    // (r - 0xffffffff) * 0x100000000 + n - 0xffffffff
    return (decodeUint32(state) - 0xffffffff) * 0x100000000 + decodeUint32(state) - 0xffffffff
  }

  if (a === 0x02) {
    return decodeUint32(state) - 0xffffffff
  }

  if (a === 0x03) {
    if (state.end - state.start < 2) throw new Error('Out of bounds')
    return state.buffer[state.start++] * 0x100 + state.buffer[state.start++] - 0xffff
  }

  if (a === 0x04) return state.buffer[state.start++] - 0xff

  if (a <= 0xfb) return a - 0x05

  if (a === 0xfc) {
    if (state.end - state.start < 2) throw new Error('Out of bounds')
    return state.buffer[state.start++] * 0x100 + state.buffer[state.start++]
  }

  if (a === 0xfd) {
    return decodeUint32(state)
  }

  if (a === 0xfe) {
    return decodeUint32(state) * 0x100000000 + decodeUint32(state)
  }

  return Infinity
}

const DATE = {}

DATE.preencode = (state, d) => INT.preencode(state, d.getTime())
DATE.encode = (state, d) => INT.encode(state, d.getTime())
DATE.decode = (state) => new Date(INT.decode(state))

const BOOL = {}

BOOL.preencode = (state, b) => UINT.preencode(state, b ? 1 : 0)
BOOL.encode = (state, b) => UINT.encode(state, b ? 1 : 0)
BOOL.decode = (state) => !!UINT.decode(state)

module.exports = class IndexEncoder {
  constructor(encodings, { prefix = -1 } = {}) {
    this.encodings = encodings
    this.prefix = prefix
  }

  static BUFFER = BUFFER
  static STRING = STRING
  static UINT = UINT
  static INT = INT
  static DATE = DATE
  static BOOL = BOOL

  static lookup(c) {
    switch (c) {
      case 'uint':
        return UINT
      case 'uint8':
        return UINT
      case 'uint16':
        return UINT
      case 'uint24':
        return UINT
      case 'uint32':
        return UINT
      case 'uint40':
        return UINT
      case 'uint48':
        return UINT
      case 'uint56':
        return UINT
      case 'uint64':
        return UINT
      case 'int':
        return INT
      case 'int8':
        return INT
      case 'int16':
        return INT
      case 'int24':
        return INT
      case 'int32':
        return INT
      case 'int40':
        return INT
      case 'int48':
        return INT
      case 'int56':
        return INT
      case 'int64':
        return INT
      case 'string':
        return STRING
      case 'utf8':
        return STRING
      case 'ascii':
        return STRING
      case 'hex':
        return STRING
      case 'base64':
        return STRING
      case 'fixed32':
        return BUFFER
      case 'fixed64':
        return BUFFER
      case 'buffer':
        return BUFFER
      case 'date':
        return DATE
      case 'bool':
        return BOOL
    }

    throw new Error('Unknown type')
  }

  encode(keys) {
    return this._encode(keys, false)
  }

  _encode(keys, terminate) {
    if (b4a.isBuffer(keys)) return keys

    const state = { start: 0, end: 0, buffer: null }

    if (this.prefix !== -1) UINT.preencode(state, this.prefix)
    for (let i = 0; i < keys.length; i++) {
      this.encodings[i].preencode(state, keys[i])
    }

    if (terminate && keys.length < this.encodings.length) {
      state.end++
    }

    state.buffer = b4a.allocUnsafe(state.end)

    if (this.prefix !== -1) UINT.encode(state, this.prefix)
    for (let i = 0; i < keys.length; i++) {
      this.encodings[i].encode(state, keys[i])
    }

    if (terminate && keys.length < this.encodings.length) {
      state.buffer[state.start++] = MAX[0]
    }

    return state.buffer
  }

  decode(buffer) {
    const state = { start: 0, end: buffer.byteLength, buffer }
    const result = []

    if (this.prefix !== -1) UINT.decode(state)
    for (const enc of this.encodings) {
      const key = state.start < state.end ? enc.decode(state) : enc === UINT ? 0 : null
      result.push(key)
    }

    return result
  }

  encodeRange({ gt, gte, lt, lte }) {
    const range = {
      gt: gt && this._encode(gt, true),
      gte: gte && this._encode(gte, false),
      lt: lt && this._encode(lt, false),
      lte: lte && this._encode(lte, true)
    }

    if (this.prefix !== -1) {
      if (!gt && !gte) range.gte = encodeUint(this.prefix)
      if (!lt && !lte) range.lt = encodeUint(this.prefix + 1)
    }

    return range
  }
}

function encodeUint(n) {
  const state = { start: 0, end: 0, buffer: null }
  UINT.preencode(state, n)
  state.buffer = b4a.allocUnsafe(state.end)
  UINT.encode(state, n)
  return state.buffer
}

function encodeUint32(state, n) {
  state.buffer[state.start++] = n >>> 24
  state.buffer[state.start++] = n >>> 16
  state.buffer[state.start++] = n >>> 8
  state.buffer[state.start++] = n
}

function decodeUint32(state) {
  if (state.end - state.start < 4) throw new Error('Out of bounds')
  return (
    state.buffer[state.start++] * 0x1000000 +
    state.buffer[state.start++] * 0x10000 +
    state.buffer[state.start++] * 0x100 +
    state.buffer[state.start++]
  )
}
{
  "name": "index-encoder",
  "version": "3.5.0",
  "description": "Encode multiple values into sorted keys",
  "main": "index.js",
  "dependencies": {
    "b4a": "^1.6.4"
  },
  "devDependencies": {
    "brittle": "^3.2.1",
    "hyperbee": "^2.10.5",
    "hypercore": "^11.0.0",
    "lunte": "^1.0.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^2.0.0"
  },
  "scripts": {
    "format": "prettier --write .",
    "lint": "prettier --check . && lunte",
    "test": "npm run test:node && npm run test:bare",
    "test:node": "brittle-node test.js",
    "test:bare": "brittle-bare test.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/holepunchto/index-encoder.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/index-encoder/issues"
  },
  "homepage": "https://github.com/holepunchto/index-encoder"
}
const b4a = require('b4a')

module.exports = function isOptions (opts) {
  return typeof opts === 'object' && opts && !b4a.isBuffer(opts)
}
{
  "name": "is-options",
  "version": "1.0.2",
  "description": "Easily check if input is an options map",
  "main": "index.js",
  "dependencies": {
    "b4a": "^1.1.1"
  },
  "devDependencies": {
    "standard": "^11.0.1",
    "tape": "^4.9.0"
  },
  "scripts": {
    "test": "standard && tape test.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/mafintosh/is-options.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/mafintosh/is-options/issues"
  },
  "homepage": "https://github.com/mafintosh/is-options"
}
const { EventEmitter } = require('events')

module.exports = class RoutingTable extends EventEmitter {
  constructor (id, opts) {
    if (!opts) opts = {}

    super()

    this.id = id
    this.k = opts.k || 20
    this.size = 0
    this.rows = new Array(id.length * 8)
  }

  add (node) {
    const i = this._diff(node.id)

    let row = this.rows[i]

    if (!row) {
      row = this.rows[i] = new Row(this, i)
      this.emit('row', row)
    }

    const len = row.nodes.length
    if (!row.add(node, this.k)) return false

    this.size += row.nodes.length - len
    return true
  }

  remove (id) {
    const i = this._diff(id)
    const row = this.rows[i]
    if (!row) return false
    if (!row.remove(id)) return false
    this.size--
    return true
  }

  get (id) {
    const i = this._diff(id)
    const row = this.rows[i]
    if (!row) return null
    return row.get(id)
  }

  has (id) {
    return this.get(id) !== null
  }

  random () {
    let n = (Math.random() * this.size) | 0

    for (let i = 0; i < this.rows.length; i++) {
      const r = this.rows[i]
      if (!r) continue
      if (n < r.nodes.length) return r.nodes[n]
      n -= r.nodes.length
    }

    return null
  }

  closest (id, k) {
    if (!k) k = this.k

    const result = []
    const d = this._diff(id)

    // push close nodes
    for (let i = d; i >= 0 && result.length < k; i--) this._pushNodes(i, k, result)

    // if we don't have enough close nodes, populate from other rows, re the paper
    for (let i = d + 1; i < this.rows.length && result.length < k; i++) this._pushNodes(i, k, result)

    return result
  }

  _pushNodes (i, k, result) {
    const row = this.rows[i]
    if (!row) return

    const missing = Math.min(k - result.length, row.nodes.length)
    for (let j = 0; j < missing; j++) result.push(row.nodes[j])
  }

  toArray () {
    return this.closest(this.id, Infinity)
  }

  _diff (id) {
    for (let i = 0; i < id.length; i++) {
      const a = id[i]
      const b = this.id[i]

      if (a !== b) return i * 8 + Math.clz32(a ^ b) - 24
    }

    return this.rows.length - 1
  }
}

class Row extends EventEmitter {
  constructor (table, index) {
    super()

    this.data = null // can be used be upstream for whatevs
    this.byteOffset = index >> 3
    this.index = index
    this.table = table
    this.nodes = []
  }

  add (node) {
    const id = node.id

    let l = 0
    let r = this.nodes.length - 1

    while (l <= r) {
      const m = (l + r) >> 1
      const c = this.compare(id, this.nodes[m].id)

      if (c === 0) {
        this.nodes[m] = node
        return true
      }

      if (c < 0) r = m - 1
      else l = m + 1
    }

    if (this.nodes.length >= this.table.k) {
      this.emit('full', node)
      return false
    }

    this.insert(l, node)
    return true
  }

  remove (id) {
    let l = 0
    let r = this.nodes.length - 1

    while (l <= r) {
      const m = (l + r) >> 1
      const c = this.compare(id, this.nodes[m].id)

      if (c === 0) {
        this.splice(m)
        return true
      }

      if (c < 0) r = m - 1
      else l = m + 1
    }

    return false
  }

  get (id) {
    let l = 0
    let r = this.nodes.length - 1

    while (l <= r) {
      const m = (l + r) >> 1
      const node = this.nodes[m]
      const c = this.compare(id, node.id)

      if (c === 0) return node
      if (c < 0) r = m - 1
      else l = m + 1
    }

    return null
  }

  insert (i, node) {
    this.nodes.push(node) // push node or null or whatevs, just trying to not be polymorphic
    for (let j = this.nodes.length - 1; j > i; j--) this.nodes[j] = this.nodes[j - 1]
    this.nodes[i] = node
    this.emit('add', node)
  }

  splice (i) {
    for (; i < this.nodes.length - 1; i++) this.nodes[i] = this.nodes[i + 1]
    this.emit('remove', this.nodes.pop())
  }

  // very likely they diverge after a couple of bytes so a simple impl, like this is prop fastest vs Buffer.compare
  compare (a, b) {
    for (let i = this.byteOffset; i < a.length; i++) {
      const ai = a[i]
      const bi = b[i]
      if (ai === bi) continue
      return ai < bi ? -1 : 1
    }
    return 0
  }
}
{
  "name": "kademlia-routing-table",
  "version": "1.0.6",
  "description": "XOR based routing table used for P2P networks such as a Kademlia DHT.",
  "main": "index.js",
  "files": [
    "index.js"
  ],
  "imports": {
    "events": {
      "bare": "bare-events",
      "default": "events"
    }
  },
  "dependencies": {
    "bare-events": "^2.2.0"
  },
  "devDependencies": {
    "brittle": "^3.3.2",
    "standard": "^17.1.0"
  },
  "scripts": {
    "test": "standard && brittle test.js"
  },
  "keywords": [
    "kademlia",
    "p2p",
    "k-bucket",
    "k-buckets",
    "xor",
    "routing",
    "distributed",
    "systems"
  ],
  "repository": {
    "type": "git",
    "url": "https://github.com/mafintosh/kademlia-routing-table.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/mafintosh/kademlia-routing-table/issues"
  },
  "homepage": "https://github.com/mafintosh/kademlia-routing-table"
}
const c = require('compact-encoding')
const b4a = require('b4a')

const KeyChain = require('./lib/keychain')
const { sign, verify, hash } = require('./lib/crypto')

const {
  ProofEncoding,
  AttestedData,
  AttestedDevice,
  ReceiptEncoding
} = require('./lib/encoding')

const PROOF_VERSION = 1
const KEET_TYPE = 5338

const NS_PROFILE_DISC_ENC = hash(b4a.from('profile discovery'))

module.exports = class IdentityKey {
  constructor (keyChain) {
    this.keyChain = keyChain

    this.identityKeyPair = this.keyChain.get(identityKeyPath(0))
    this.profileDiscoveryKeyPair = this.keyChain.get(discoveryCorePath(0))
  }

  static generateMnemonic () {
    return KeyChain.generateMnemonic()
  }

  static deriveSeed (mnemonic) {
    return KeyChain.deriveSeed(mnemonic)
  }

  get identityPublicKey () {
    return this.identityKeyPair.publicKey
  }

  get profileDiscoveryPublicKey () {
    return this.profileDiscoveryKeyPair.publicKey
  }

  getProfileDiscoveryEncryptionKey () {
    return this.keyChain.getSymmetricKey(encryptionKeyPath(NS_PROFILE_DISC_ENC))
  }

  getEncryptionKey (profileKey) {
    return this.keyChain.getSymmetricKey(encryptionKeyPath(profileKey))
  }

  bootstrap (device) {
    return IdentityKey.bootstrap({ identity: this.identityKeyPair }, device)
  }

  clear () {
    this.keyChain.clear()
    this.identityKeyPair.secretKey.fill(0)
    this.profileDiscoveryKeyPair.secretKey.fill(0)
  }

  static async bootstrap ({ identity, seed, mnemonic }, device) {
    if (!identity) {
      const identityPath = identityKeyPath(0) // accountIndex unused for now
      identity = await KeyChain.from({ seed, mnemonic }, identityPath)
    }

    const proof = {
      version: PROOF_VERSION,
      epoch: Date.now(),
      identity: identity.publicKey,
      chain: []
    }

    return IdentityKey.attestDevice(device, identity, proof)
  }

  static attestDevice (publicKey, parent, proof) {
    if (!proof) return IdentityKey.bootstrap({ identity: parent }, publicKey)

    if (b4a.isBuffer(proof)) {
      proof = c.decode(ProofEncoding, proof)
    }

    if (proof.version === 0) {
      throw new Error('Version 0 proofs are not supported')
    }

    const signable = c.encode(AttestedDevice, {
      epoch: proof.epoch,
      identity: proof.identity,
      device: publicKey
    })

    const signature = sign(signable, parent)

    proof.chain.push({ publicKey, signature })

    return c.encode(ProofEncoding, proof)
  }

  static attestData (attestedData, keyPair, proof) {
    if (attestedData === null) throw new Error('Data is not attestable')

    if (b4a.isBuffer(proof)) {
      proof = c.decode(ProofEncoding, proof)
    }

    if (proof && proof.version === 0) {
      throw new Error('Version 0 proofs are not supported')
    }

    if (!proof) {
      proof = {
        version: PROOF_VERSION,
        epoch: Date.now(),
        identity: keyPair.publicKey,
        chain: []
      }
    }

    const signable = c.encode(AttestedData, {
      epoch: proof.epoch,
      identity: proof.identity,
      data: hash(attestedData)
    })

    proof.data = { signature: sign(signable, keyPair) }

    return c.encode(ProofEncoding, proof)
  }

  static async from ({ seed, mnemonic }) {
    const keyChain = await KeyChain.from({ seed, mnemonic })

    return new IdentityKey(keyChain)
  }

  static verify (proof, attestedData, opts = {}) {
    if (b4a.isBuffer(proof)) {
      proof = c.decode(ProofEncoding, proof)
    }

    if (!validateProof(proof, attestedData, opts)) {
      return null
    }

    const { epoch, identity, chain } = proof

    const candidate = getLastKey(chain) || identity

    if (opts.expectedDevice) {
      if (!b4a.equals(candidate, opts.expectedDevice)) return null
    }

    let parent = identity

    // verify chain
    for (let i = 0; i < chain.length; i++) {
      const { publicKey, signature } = chain[i]

      const signable = c.encode(AttestedDevice, {
        epoch,
        identity,
        device: publicKey
      })

      if (!verify(signable, signature, parent)) {
        return null
      }

      parent = publicKey
    }

    if (proof.data) {
      const signable = c.encode(AttestedData, {
        epoch,
        identity,
        data: hash(attestedData)
      })

      if (!verify(signable, proof.data.signature, parent)) {
        return null
      }
    }

    const receipt = c.encode(ReceiptEncoding, { epoch })

    return {
      receipt,
      identityPublicKey: identity,
      devicePublicKey: candidate
    }
  }
}

function validateProof (proof, attestedData, opts = {}) {
  // version 0 is ignored
  if (proof.version === 0) return false

  // validate version
  if (proof.version > PROOF_VERSION) return false

  // verify epoch
  if (opts.receipt) {
    const { epoch } = c.decode(ReceiptEncoding, opts.receipt)
    if (proof.epoch < epoch) return false
  }

  // verify identity
  if (opts.expectedIdentity) {
    if (!b4a.equals(proof.identity, opts.expectedIdentity)) return false
  }

  return validateAttestedData(proof.data, attestedData)
}

function validateAttestedData (data, attestedData) {
  if (!data && attestedData) return false
  return true
}

function getLastKey (chain) {
  if (!chain.length) return null

  const last = chain[chain.length - 1]
  return last.publicKey
}

// slip48 derivations:
// https://github.com/satoshilabs/slips/blob/master/slip-0048.md

function keyPath (accountIndex, index) {
  // https://github.com/bitcoin/bips/blob/master/bip-0043.mediawiki
  const purpose = 48 // SLIP-48 wallet
  const role = 0 // owner

  return [purpose, KEET_TYPE, role, accountIndex, index]
}

// identityKey -> m/48'/keet'/0'/0'

function identityKeyPath (accountIndex) {
  return keyPath(accountIndex, 0)
}

// identityKey -> m/48'/keet'/0'/1'

function discoveryCorePath (accountIndex) {
  return keyPath(accountIndex, 1)
}

// slip21 derivations
// https://github.com/satoshilabs/slips/blob/master/slip-0021.md

function symmetricPath (...path) {
  const purpose = 'SLIP-0021' // SLIP-21 wallet
  const namespace = 'keet-identity-key' // keet

  return [purpose, namespace, ...path]
}

// encryptionKey  -> m/SLIP-10/keet-identity-key/account/"encryption key"

function encryptionKeyPath (profileKey) {
  return symmetricPath(b4a.toString(profileKey, 'hex'), 'encryption key')
}
const sodium = require('sodium-universal')
const b4a = require('b4a')

module.exports = {
  sign,
  verify,
  hash: blake2b
}

function sign (signable, keyPair) {
  const signature = b4a.alloc(sodium.crypto_sign_BYTES)
  sodium.crypto_sign_detached(signature, signable, keyPair.secretKey)

  return signature
}

function verify (signable, signature, publicKey) {
  return sodium.crypto_sign_verify_detached(signature, signable, publicKey)
}

function blake2b (data, output = b4a.alloc(32)) {
  sodium.crypto_generichash(output, data)
  return output
}
const c = require('compact-encoding')

const ATTESTED_DEVICE = 0
const ATTESTED_DATA = 1

const Epoch = {
  preencode (state, e) {
    c.uint64.preencode(state, Math.floor(e / 1000))
  },
  encode (state, e) {
    c.uint64.encode(state, Math.floor(e / 1000))
  },
  decode (state) {
    return c.uint64.decode(state) * 1000
  }
}

const AttestedDevice = {
  preencode (state, a) {
    c.uint8.preencode(state, ATTESTED_DEVICE)
    Epoch.preencode(state, a.epoch)
    c.fixed32.preencode(state, a.identity)
    c.fixed32.preencode(state, a.device)
  },
  encode (state, a) {
    c.uint8.encode(state, ATTESTED_DEVICE)
    Epoch.encode(state, a.epoch)
    c.fixed32.encode(state, a.identity)
    c.fixed32.encode(state, a.device)
  },
  decode (state) {
    throw new Error('Signed data should only be encoded')
  }
}

const AttestedData = {
  preencode (state, a) {
    c.uint8.preencode(state, ATTESTED_DATA)
    Epoch.preencode(state, a.epoch)
    c.fixed32.preencode(state, a.identity)
    c.fixed32.preencode(state, a.data)
  },
  encode (state, a) {
    c.uint8.encode(state, ATTESTED_DATA)
    Epoch.encode(state, a.epoch)
    c.fixed32.encode(state, a.identity)
    c.fixed32.encode(state, a.data)
  },
  decode (state) {
    throw new Error('Signed data should only be encoded')
  }
}

const DeviceAttestation = {
  preencode (state, a) {
    c.fixed32.preencode(state, a.publicKey)
    c.fixed64.preencode(state, a.signature)
  },
  encode (state, a) {
    c.fixed32.encode(state, a.publicKey)
    c.fixed64.encode(state, a.signature)
  },
  decode (state) {
    return {
      publicKey: c.fixed32.decode(state),
      signature: c.fixed64.decode(state)
    }
  }
}

const DeviceAttestationArray = c.array(DeviceAttestation)

const DataAttestation = {
  preencode (state, a) {
    c.fixed64.preencode(state, a.signature)
  },
  encode (state, a) {
    c.fixed64.encode(state, a.signature)
  },
  decode (state) {
    return {
      signature: c.fixed64.decode(state)
    }
  }
}

const ProofEncoding = {
  preencode (state, proof) {
    c.uint.preencode(state, proof.version)
    Epoch.preencode(state, proof.epoch)
    c.fixed32.preencode(state, proof.identity)
    DeviceAttestationArray.preencode(state, proof.chain)

    let flags = 0
    if (proof.data) flags |= 1
    c.uint.preencode(state, flags)

    if (proof.data) DataAttestation.preencode(state, proof.data)
  },
  encode (state, proof) {
    c.uint.encode(state, proof.version)
    Epoch.encode(state, proof.epoch)
    c.fixed32.encode(state, proof.identity)
    DeviceAttestationArray.encode(state, proof.chain)

    let flags = 0
    if (proof.data) flags |= 1
    c.uint.encode(state, flags)

    if (proof.data) DataAttestation.encode(state, proof.data)
  },
  decode (state) {
    const version = c.uint.decode(state)

    // ignore v0 proofs
    if (version === 0) {
      return {
        version: 0,
        epoch: 0,
        identity: null,
        chain: [],
        data: null
      }
    }

    const epoch = Epoch.decode(state)
    const identity = c.fixed32.decode(state)
    const chain = DeviceAttestationArray.decode(state)

    const flags = c.uint.decode(state)
    const data = flags & 1 ? DataAttestation.decode(state) : null

    return {
      version,
      epoch,
      identity,
      chain,
      data
    }
  }
}

const ReceiptEncoding = {
  preencode (state, receipt) {
    Epoch.preencode(state, receipt.epoch)
  },
  encode (state, receipt) {
    Epoch.encode(state, receipt.epoch)
  },
  decode (state) {
    return {
      epoch: Epoch.decode(state)
    }
  }
}

module.exports = {
  ATTESTED_DEVICE,
  ATTESTED_DATA,
  AttestedDevice,
  AttestedData,
  ProofEncoding,
  ReceiptEncoding
}
const sodium = require('sodium-universal')
const c = require('compact-encoding')
const assert = require('nanoassert')
const b4a = require('b4a')
const { HMAC } = require('sodium-hmac')
const bip39 = require('bip39-mnemonic')

const { sign } = require('./crypto')

const MASTER_CHAIN_CODE = b4a.from('ed25519 seed')
const MASTER_SYMMETRIC_CODE = b4a.from('Symmetric key seed')

const HARDENED_OFFSET = 0x80000000

class KeyChain {
  constructor (seed) {
    this.seed = seed

    this._buffer = b4a.alloc(64)

    this.chainKey = this._buffer.subarray(0, 32)
    this.chainCode = this._buffer.subarray(32)

    this.publicKey = b4a.alloc(sodium.crypto_sign_PUBLICKEYBYTES)
    this.secretKey = b4a.alloc(sodium.crypto_sign_SECRETKEYBYTES)

    this._initialise()
  }

  static generateMnemonic () {
    return bip39.generateMnemonic()
  }

  static deriveSeed (mnemonic) {
    return bip39.mnemonicToSeed(mnemonic)
  }

  static async from ({ mnemonic, seed }, path) {
    assert(mnemonic || seed, 'No mnemonic or seed was passed.')

    if (!seed) seed = await KeyChain.deriveSeed(mnemonic)

    const key = new KeyChain(seed)

    if (!path) return key

    return key.get(path)
  }

  getSymmetricKey (path) {
    const buffer = b4a.alloc(64)

    const chainKey = buffer.subarray(0, 32)
    const key = buffer.subarray(32)

    HMAC.sha512(this.seed, MASTER_SYMMETRIC_CODE, buffer)

    for (const step of path) {
      const label = b4a.alloc(step.length + 1)
      b4a.write(label, step, 1, 'utf8')

      HMAC.sha512(label, chainKey, buffer)
    }

    return key
  }

  get isKeychain () {
    return true
  }

  _initialise () {
    HMAC.sha512(this.seed, MASTER_CHAIN_CODE, this._buffer)
    sodium.crypto_sign_seed_keypair(this.publicKey, this.secretKey, this.chainKey)
  }

  clear () {
    this.secretKey.fill(0)
    this.chainKey.fill(0)
    this.chainCode.fill(0)
  }

  get (path) {
    return this.derive(path)
  }

  sign (signable) {
    return sign(signable, this)
  }

  derive (path) {
    const buffer = b4a.from(this._buffer)

    const chainKey = buffer.subarray(0, 32)
    const chainCode = buffer.subarray(32)

    for (const step of path) {
      const index = ensureHardened(step) // hardened indices are >= 2^31
      HMAC.sha512(encodeDerivationData(chainKey, index), chainCode, buffer)
    }

    return createKeyPair(chainKey)
  }
}

module.exports = KeyChain

function createKeyPair (seed) {
  if (!seed) throw new Error('No seed provided')

  const publicKey = b4a.alloc(sodium.crypto_sign_PUBLICKEYBYTES)
  const secretKey = b4a.alloc(sodium.crypto_sign_SECRETKEYBYTES)

  sodium.crypto_sign_seed_keypair(publicKey, secretKey, seed)

  return {
    publicKey,
    secretKey
  }
}

function encodeDerivationData (key, step) {
  const state = { buffer: b4a.alloc(37), start: 0, end: 37 }

  c.uint8.encode(state, 0)
  c.fixed32.encode(state, key)
  state.buffer.writeUInt32BE(step, state.start)

  state.buffer[33] |= 0x80

  return state.buffer
}

function ensureHardened (n) {
  if (n >= HARDENED_OFFSET) return n
  return n + HARDENED_OFFSET
}
{
  "name": "keet-identity-key",
  "version": "3.2.0",
  "description": "Hierarchical Deterministic Keys for Keet Identities",
  "main": "index.js",
  "scripts": {
    "test": "standard && brittle test/*.js"
  },
  "author": "Holepunch Inc.",
  "license": "Apache-2.0",
  "devDependencies": {
    "brittle": "^3.3.2",
    "hypercore-crypto": "^3.4.0",
    "standard": "^17.1.0"
  },
  "dependencies": {
    "b4a": "^1.6.4",
    "bip39-mnemonic": "^2.2.0",
    "compact-encoding": "^3.0.0",
    "nanoassert": "^2.0.0",
    "sodium-hmac": "^2.0.1",
    "sodium-universal": "^5.0.1"
  },
  "files": [
    "lib/*"
  ],
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/keet-identity-key.git"
  },
  "bugs": {
    "url": "https://github.com/holepunchto/keet-identity-key/issues"
  },
  "homepage": "https://github.com/holepunchto/keet-identity-key#readme"
}
module.exports = assert

class AssertionError extends Error {}
AssertionError.prototype.name = 'AssertionError'

/**
 * Minimal assert function
 * @param  {any} t Value to check if falsy
 * @param  {string=} m Optional assertion error message
 * @throws {AssertionError}
 */
function assert (t, m) {
  if (!t) {
    var err = new AssertionError(m)
    if (Error.captureStackTrace) Error.captureStackTrace(err, assert)
    throw err
  }
}
{
  "name": "nanoassert",
  "version": "2.0.0",
  "description": "Nanoscale assertion module",
  "main": "index.js",
  "dependencies": {},
  "devDependencies": {
    "tape": "^4.9.1"
  },
  "scripts": {
    "test": "tape test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/emilbayes/nanoassert.git"
  },
  "keywords": [
    "assert",
    "unassert",
    "power-assert",
    "tiny",
    "nano",
    "pico"
  ],
  "author": "Emil Bay <github@tixz.dk>",
  "license": "ISC",
  "bugs": {
    "url": "https://github.com/emilbayes/nanoassert/issues"
  },
  "homepage": "https://github.com/emilbayes/nanoassert#readme"
}
module.exports = class NatSampler {
  constructor () {
    this.host = null
    this.port = 0
    this.size = 0

    this._a = null
    this._b = null
    this._threshold = 0
    this._top = 0
    this._samples = []
  }

  add (host, port) {
    const a = this._bump(host, port, 2)
    const b = this._bump(host, 0, 1)

    if (this._samples.length < 32) {
      this.size++
      this._threshold = this.size - (this.size < 4 ? 0 : this.size < 8 ? 1 : this.size < 12 ? 2 : 3)
      this._samples.push(a, b)
      this._top += 2
    } else {
      if (this._top === 32) this._top = 0

      const oa = this._samples[this._top]
      this._samples[this._top++] = a
      oa.hits--

      const ob = this._samples[this._top]
      this._samples[this._top++] = b
      ob.hits--
    }

    if (this._a === null || this._a.hits < a.hits) this._a = a
    if (this._b === null || this._b.hits < b.hits) this._b = b

    if (this._a.hits >= this._threshold) {
      this.host = this._a.host
      this.port = this._a.port
    } else if (this._b.hits >= this._threshold) {
      this.host = this._b.host
      this.port = 0
    } else {
      this.host = null
      this.port = 0
    }

    return a.hits
  }

  _bump (host, port, inc) {
    for (let i = 0; i < 4; i++) {
      const j = (this._top - inc - (2 * i)) & 31
      if (j >= this._samples.length) return { host, port, hits: 1 }
      const s = this._samples[j]
      if (s.port === port && s.host === host) {
        s.hits++
        return s
      }
    }
    return { host, port, hits: 1 }
  }
}
{
  "name": "nat-sampler",
  "version": "1.0.1",
  "description": "Sample addresses to figure out if a host + port is consistent",
  "main": "index.js",
  "dependencies": {},
  "devDependencies": {
    "standard": "^16.0.3",
    "tape": "^5.2.2"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/mafintosh/nat-sampler.git"
  },
  "scripts": {
    "test": "standard && tape test.js"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/mafintosh/nat-sampler/issues"
  },
  "homepage": "https://github.com/mafintosh/nat-sampler"
}
/* eslint-disable camelcase */
const sodium = require('sodium-universal')
const assert = require('nanoassert')
const b4a = require('b4a')

const DHLEN = sodium.crypto_scalarmult_ed25519_BYTES
const PKLEN = sodium.crypto_scalarmult_ed25519_BYTES
const SCALARLEN = sodium.crypto_scalarmult_ed25519_BYTES
const SKLEN = sodium.crypto_sign_SECRETKEYBYTES
const ALG = 'Ed25519'

module.exports = {
  DHLEN,
  PKLEN,
  SCALARLEN,
  SKLEN,
  ALG,
  name: ALG,
  generateKeyPair,
  dh
}

function generateKeyPair (privKey) {
  if (privKey) return generateSeedKeyPair(privKey.subarray(0, 32))

  const keyPair = {}
  keyPair.secretKey = b4a.alloc(SKLEN)
  keyPair.publicKey = b4a.alloc(PKLEN)

  sodium.crypto_sign_keypair(keyPair.publicKey, keyPair.secretKey)
  return keyPair
}

function generateSeedKeyPair (seed) {
  const keyPair = {}
  keyPair.secretKey = b4a.alloc(SKLEN)
  keyPair.publicKey = b4a.alloc(PKLEN)

  sodium.crypto_sign_seed_keypair(keyPair.publicKey, keyPair.secretKey, seed)
  return keyPair
}

function dh (publicKey, { scalar, secretKey }) {
  // tweaked keys expose scalar directly
  if (!scalar) {
    assert(secretKey.byteLength === SKLEN)

    // libsodium stores seed not actual scalar
    const sk = b4a.alloc(64)
    sodium.crypto_hash_sha512(sk, secretKey.subarray(0, 32))
    sk[0] &= 248
    sk[31] &= 127
    sk[31] |= 64

    scalar = sk.subarray(0, 32)
  }

  assert(scalar.byteLength === SCALARLEN)
  assert(publicKey.byteLength === PKLEN)

  const output = b4a.alloc(DHLEN)

  // we clamp if necessary above
  sodium.crypto_scalarmult_ed25519_noclamp(
    output,
    scalar,
    publicKey
  )

  return output
}
{
  "name": "noise-curve-ed",
  "version": "2.1.0",
  "description": "Ed25519 elliptic curve operations for [`noise-handshake`](https://github.com/chm-diederichs/noise-handshake)",
  "main": "index.js",
  "scripts": {
    "test": "npx standard && tape test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/chm-diederichs/noise-curve-ed.git"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "bugs": {
    "url": "https://github.com/chm-diederichs/noise-curve-ed/issues"
  },
  "homepage": "https://github.com/chm-diederichs/noise-curve-ed#readme",
  "dependencies": {
    "b4a": "^1.1.0",
    "nanoassert": "^2.0.0",
    "sodium-universal": "^5.0.0"
  },
  "devDependencies": {
    "noise-handshake": "^3.0.0",
    "standard": "^16.0.3",
    "tape": "^5.2.2"
  }
}
const sodium = require('sodium-universal')
const b4a = require('b4a')

module.exports = class CipherState {
  constructor (key) {
    this.key = key || null
    this.nonce = 0
    this.CIPHER_ALG = 'ChaChaPoly'
  }

  initialiseKey (key) {
    this.key = key
    this.nonce = 0
  }

  setNonce (nonce) {
    this.nonce = nonce
  }

  encrypt (plaintext, ad) {
    if (!this.hasKey) return plaintext
    if (!ad) ad = b4a.alloc(0)

    const ciphertext = encryptWithAD(this.key, this.nonce, ad, plaintext)
    if (ciphertext.length > 65535) throw new Error(`ciphertext length of ${ciphertext.length} exceeds maximum Noise message length of 65535`)
    this.nonce++

    return ciphertext
  }

  decrypt (ciphertext, ad) {
    if (!this.hasKey) return ciphertext
    if (!ad) ad = b4a.alloc(0)
    if (ciphertext.length > 65535) throw new Error(`ciphertext length of ${ciphertext.length} exceeds maximum Noise message length of 65535`)

    const plaintext = decryptWithAD(this.key, this.nonce, ad, ciphertext)
    this.nonce++

    return plaintext
  }

  get hasKey () {
    return this.key !== null
  }

  _clear () {
    sodium.sodium_memzero(this.key)
    this.key = null
    this.nonce = null
  }

  static get MACBYTES () {
    return 16
  }

  static get NONCEBYTES () {
    return 8
  }

  static get KEYBYTES () {
    return 32
  }
}

function encryptWithAD (key, counter, additionalData, plaintext) {
  // for our purposes, additionalData will always be a pubkey so we encode from hex
  if (!b4a.isBuffer(additionalData)) additionalData = b4a.from(additionalData, 'hex')
  if (!b4a.isBuffer(plaintext)) plaintext = b4a.from(plaintext, 'hex')

  const nonce = b4a.alloc(sodium.crypto_aead_chacha20poly1305_ietf_NPUBBYTES)
  const view = new DataView(nonce.buffer, nonce.byteOffset, nonce.byteLength)
  view.setUint32(4, counter, true)

  const ciphertext = b4a.alloc(plaintext.byteLength + sodium.crypto_aead_chacha20poly1305_ietf_ABYTES)

  sodium.crypto_aead_chacha20poly1305_ietf_encrypt(ciphertext, plaintext, additionalData, null, nonce, key)
  return ciphertext
}

function decryptWithAD (key, counter, additionalData, ciphertext) {
  // for our purposes, additionalData will always be a pubkey so we encode from hex
  if (!b4a.isBuffer(additionalData)) additionalData = b4a.from(additionalData, 'hex')
  if (!b4a.isBuffer(ciphertext)) ciphertext = b4a.from(ciphertext, 'hex')

  const nonce = b4a.alloc(sodium.crypto_aead_chacha20poly1305_ietf_NPUBBYTES)
  const view = new DataView(nonce.buffer, nonce.byteOffset, nonce.byteLength)
  view.setUint32(4, counter, true)

  const plaintext = b4a.alloc(ciphertext.byteLength - sodium.crypto_aead_chacha20poly1305_ietf_ABYTES)

  sodium.crypto_aead_chacha20poly1305_ietf_decrypt(plaintext, null, ciphertext, additionalData, nonce, key)
  return plaintext
}
/* eslint-disable camelcase */
const {
  crypto_kx_SEEDBYTES,
  crypto_kx_keypair,
  crypto_kx_seed_keypair,
  crypto_scalarmult_BYTES,
  crypto_scalarmult_SCALARBYTES,
  crypto_scalarmult,
  crypto_scalarmult_base
} = require('sodium-universal')

const assert = require('nanoassert')
const b4a = require('b4a')

const DHLEN = crypto_scalarmult_BYTES
const PKLEN = crypto_scalarmult_BYTES
const SKLEN = crypto_scalarmult_SCALARBYTES
const SEEDLEN = crypto_kx_SEEDBYTES
const ALG = '25519'

module.exports = {
  DHLEN,
  PKLEN,
  SKLEN,
  SEEDLEN,
  ALG,
  generateKeyPair,
  generateSeedKeyPair,
  dh
}

function generateKeyPair (privKey) {
  const keyPair = {}

  keyPair.secretKey = privKey || b4a.alloc(SKLEN)
  keyPair.publicKey = b4a.alloc(PKLEN)

  if (privKey) {
    crypto_scalarmult_base(keyPair.publicKey, keyPair.secretKey)
  } else {
    crypto_kx_keypair(keyPair.publicKey, keyPair.secretKey)
  }

  return keyPair
}

function generateSeedKeyPair (seed) {
  assert(seed.byteLength === SKLEN)

  const keyPair = {}
  keyPair.secretKey = b4a.alloc(SKLEN)
  keyPair.publicKey = b4a.alloc(PKLEN)

  crypto_kx_seed_keypair(keyPair.publicKey, keyPair.secretKey, seed)
  return keyPair
}

function dh (publicKey, { secretKey }) {
  assert(secretKey.byteLength === SKLEN)
  assert(publicKey.byteLength === PKLEN)

  const output = b4a.alloc(DHLEN)

  crypto_scalarmult(
    output,
    secretKey,
    publicKey
  )

  return output
}
const hmacBlake2b = require('./hmac')
const b4a = require('b4a')

const HASHLEN = 64

module.exports = {
  hkdf,
  HASHLEN
}

// HMAC-based Extract-and-Expand KDF
// https://www.ietf.org/rfc/rfc5869.txt

function hkdf (salt, inputKeyMaterial, info = '', length = 2 * HASHLEN) {
  const pseudoRandomKey = hkdfExtract(salt, inputKeyMaterial)
  return hkdfExpand(pseudoRandomKey, info, length)
}

function hkdfExtract (salt, inputKeyMaterial) {
  const hmac = b4a.alloc(HASHLEN)
  return hmacDigest(hmac, salt, inputKeyMaterial)
}

function hkdfExpand (key, info, length) {
  // Put in dedicated slab to avoid keeping shared slab from being gc'ed
  const buffer = b4a.allocUnsafeSlow(length)

  const infoBuf = b4a.from(info)
  let prev = infoBuf

  const result = []
  for (let i = 0; i < length; i += HASHLEN) {
    const pos = b4a.from([(i / HASHLEN) + 1])

    const out = buffer.subarray(i, i + HASHLEN)
    result.push(out)

    prev = hmacDigest(out, key, [prev, infoBuf, pos])
  }

  return result
}

function hmacDigest (out, key, input) {
  hmacBlake2b(out, input, key)
  return out
}
/* eslint-disable camelcase */
const b4a = require('b4a')
const { sodium_memzero, crypto_generichash, crypto_generichash_batch } = require('sodium-universal')

const HASHLEN = 64
const BLOCKLEN = 128
const scratch = b4a.alloc(BLOCKLEN * 3)
const HMACKey = scratch.subarray(BLOCKLEN * 0, BLOCKLEN * 1)
const OuterKeyPad = scratch.subarray(BLOCKLEN * 1, BLOCKLEN * 2)
const InnerKeyPad = scratch.subarray(BLOCKLEN * 2, BLOCKLEN * 3)

// Post-fill is done in the cases where someone caught an exception that
// happened before we were able to clear data at the end

module.exports = function hmac (out, batch, key) {
  if (key.byteLength > BLOCKLEN) {
    crypto_generichash(HMACKey.subarray(0, HASHLEN), key)
    sodium_memzero(HMACKey.subarray(HASHLEN))
  } else {
    // Covers key <= BLOCKLEN
    HMACKey.set(key)
    sodium_memzero(HMACKey.subarray(key.byteLength))
  }

  for (let i = 0; i < HMACKey.byteLength; i++) {
    OuterKeyPad[i] = 0x5c ^ HMACKey[i]
    InnerKeyPad[i] = 0x36 ^ HMACKey[i]
  }
  sodium_memzero(HMACKey)

  crypto_generichash_batch(out, [InnerKeyPad].concat(batch))
  sodium_memzero(InnerKeyPad)
  crypto_generichash_batch(out, [OuterKeyPad, out])
  sodium_memzero(OuterKeyPad)
}

module.exports.BYTES = HASHLEN
module.exports.KEYBYTES = BLOCKLEN
const assert = require('nanoassert')
const b4a = require('b4a')

const SymmetricState = require('./symmetric-state')
const { HASHLEN } = require('./hkdf')

const PRESHARE_IS = Symbol('initiator static key preshared')
const PRESHARE_RS = Symbol('responder static key preshared')

const TOK_PSK = Symbol('psk')

const TOK_S = Symbol('s')
const TOK_E = Symbol('e')

const TOK_ES = Symbol('es')
const TOK_SE = Symbol('se')
const TOK_EE = Symbol('ee')
const TOK_SS = Symbol('ss')

const HANDSHAKES = Object.freeze({
  NN: [
    [TOK_E],
    [TOK_E, TOK_EE]
  ],
  NNpsk0: [
    [TOK_PSK, TOK_E],
    [TOK_E, TOK_EE]
  ],
  XX: [
    [TOK_E],
    [TOK_E, TOK_EE, TOK_S, TOK_ES],
    [TOK_S, TOK_SE]
  ],
  XXpsk0: [
    [TOK_PSK, TOK_E],
    [TOK_E, TOK_EE, TOK_S, TOK_ES],
    [TOK_S, TOK_SE]
  ],
  IK: [
    PRESHARE_RS,
    [TOK_E, TOK_ES, TOK_S, TOK_SS],
    [TOK_E, TOK_EE, TOK_SE]
  ],
  XK: [
    PRESHARE_RS,
    [TOK_E, TOK_ES],
    [TOK_E, TOK_EE],
    [TOK_S, TOK_SE]
  ]
})

class Writer {
  constructor () {
    this.size = 0
    this.buffers = []
  }

  push (b) {
    this.size += b.byteLength
    this.buffers.push(b)
  }

  end () {
    const all = b4a.alloc(this.size)
    let offset = 0
    for (const b of this.buffers) {
      all.set(b, offset)
      offset += b.byteLength
    }
    return all
  }
}

class Reader {
  constructor (buf) {
    this.offset = 0
    this.buffer = buf
  }

  shift (n) {
    const start = this.offset
    const end = this.offset += n
    if (end > this.buffer.byteLength) throw new Error('Insufficient bytes')
    return this.buffer.subarray(start, end)
  }

  end () {
    return this.shift(this.buffer.byteLength - this.offset)
  }
}

module.exports = class NoiseState extends SymmetricState {
  constructor (pattern, initiator, staticKeypair, opts = {}) {
    super(opts)

    this.s = staticKeypair || this.curve.generateKeyPair()
    this.e = null

    this.psk = null
    if (opts && opts.psk) this.psk = opts.psk

    this.re = null
    this.rs = null

    this.pattern = pattern
    this.handshake = HANDSHAKES[this.pattern].slice()

    this.isPskHandshake = !!this.psk && hasPskToken(this.handshake)

    this.protocol = b4a.from([
      'Noise',
      this.pattern,
      this.DH_ALG,
      this.CIPHER_ALG,
      'BLAKE2b'
    ].join('_'))

    this.initiator = initiator
    this.complete = false

    this.rx = null
    this.tx = null
    this.hash = null
  }

  initialise (prologue, remoteStatic) {
    if (this.protocol.byteLength <= HASHLEN) this.digest.set(this.protocol)
    else this.mixHash(this.protocol)

    this.chainingKey = b4a.from(this.digest)

    this.mixHash(prologue)

    while (!Array.isArray(this.handshake[0])) {
      const message = this.handshake.shift()

      // handshake steps should be as arrays, only
      // preshare tokens are provided otherwise
      assert(message === PRESHARE_RS || message === PRESHARE_IS,
        'Unexpected pattern')

      const takeRemoteKey = this.initiator
        ? message === PRESHARE_RS
        : message === PRESHARE_IS

      if (takeRemoteKey) this.rs = remoteStatic

      const key = takeRemoteKey ? this.rs : this.s.publicKey
      assert(key != null, 'Remote pubkey required')

      this.mixHash(key)
    }
  }

  final () {
    const [k1, k2] = this.split()

    this.tx = this.initiator ? k1 : k2
    this.rx = this.initiator ? k2 : k1

    this.complete = true
    this.hash = this.getHandshakeHash()

    this._clear()
  }

  recv (buf) {
    const r = new Reader(buf)

    for (const pattern of this.handshake.shift()) {
      switch (pattern) {
        case TOK_PSK :
          this.mixKeyAndHash(this.psk)
          break

        case TOK_E :
          this.re = r.shift(this.curve.PKLEN)
          this.mixHash(this.re)
          if (this.isPskHandshake) this.mixKeyNormal(this.re)
          break

        case TOK_S : {
          const klen = this.hasKey ? this.curve.PKLEN + 16 : this.curve.PKLEN
          this.rs = this.decryptAndHash(r.shift(klen))
          break
        }

        case TOK_EE :
        case TOK_ES :
        case TOK_SE :
        case TOK_SS : {
          const useStatic = keyPattern(pattern, this.initiator)

          const localKey = useStatic.local ? this.s : this.e
          const remoteKey = useStatic.remote ? this.rs : this.re

          this.mixKey(remoteKey, localKey)
          break
        }

        default :
          throw new Error('Unexpected message')
      }
    }

    const payload = this.decryptAndHash(r.end())

    if (!this.handshake.length) this.final()
    return payload
  }

  send (payload = b4a.alloc(0)) {
    const w = new Writer()

    for (const pattern of this.handshake.shift()) {
      switch (pattern) {
        case TOK_PSK :
          this.mixKeyAndHash(this.psk)
          break

        case TOK_E :
          if (this.e === null) this.e = this.curve.generateKeyPair()
          this.mixHash(this.e.publicKey)
          if (this.isPskHandshake) this.mixKeyNormal(this.e.publicKey)
          w.push(this.e.publicKey)
          break

        case TOK_S :
          w.push(this.encryptAndHash(this.s.publicKey))
          break

        case TOK_ES :
        case TOK_SE :
        case TOK_EE :
        case TOK_SS : {
          const useStatic = keyPattern(pattern, this.initiator)

          const localKey = useStatic.local ? this.s : this.e
          const remoteKey = useStatic.remote ? this.rs : this.re

          this.mixKey(remoteKey, localKey)
          break
        }

        default :
          throw new Error('Unexpected message')
      }
    }

    w.push(this.encryptAndHash(payload))
    const response = w.end()

    if (!this.handshake.length) this.final()
    return response
  }

  _clear () {
    super._clear()

    this.e.secretKey.fill(0)
    this.e.publicKey.fill(0)

    this.re.fill(0)

    this.e = null
    this.re = null
  }
}

function keyPattern (pattern, initiator) {
  const ret = {
    local: false,
    remote: false
  }

  switch (pattern) {
    case TOK_EE:
      return ret

    case TOK_ES:
      ret.local ^= !initiator
      ret.remote ^= initiator
      return ret

    case TOK_SE:
      ret.local ^= initiator
      ret.remote ^= !initiator
      return ret

    case TOK_SS:
      ret.local ^= 1
      ret.remote ^= 1
      return ret
  }
}

function hasPskToken (handshake) {
  return handshake.some(x => {
    return Array.isArray(x) && x.indexOf(TOK_PSK) !== -1
  })
}
{
  "name": "noise-handshake",
  "version": "4.2.0",
  "description": "Noise protocol handshake",
  "main": "noise.js",
  "files": [
    "*.js"
  ],
  "scripts": {
    "test": "standard && brittle test/*.js"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/noise-handshake.git"
  },
  "dependencies": {
    "b4a": "^1.1.0",
    "nanoassert": "^2.0.0",
    "sodium-universal": "^5.0.0"
  },
  "devDependencies": {
    "brittle": "^3.3.2",
    "noise-protocol": "chm-diederichs/noise-protocol.git#xx-ephemeral-key",
    "standard": "^16.0.3"
  }
}
const sodium = require('sodium-universal')
const assert = require('nanoassert')
const b4a = require('b4a')
const CipherState = require('./cipher')
const curve = require('./dh')
const { HASHLEN, hkdf } = require('./hkdf')

module.exports = class SymmetricState extends CipherState {
  constructor (opts = {}) {
    super()

    this.curve = opts.curve || curve
    this.digest = b4a.alloc(HASHLEN)
    this.chainingKey = null
    this.offset = 0

    this.DH_ALG = this.curve.ALG
  }

  mixHash (data) {
    accumulateDigest(this.digest, data)
  }

  mixKeyAndHash (key) {
    const [ck, tempH, tempK] = hkdf(this.chainingKey, key, '', 3 * HASHLEN)
    this.chainingKey = ck
    this.mixHash(tempH)
    this.initialiseKey(tempK.subarray(0, 32))
  }

  mixKeyNormal (key) {
    const [ck, tempK] = hkdf(this.chainingKey, key)
    this.chainingKey = ck
    this.initialiseKey(tempK.subarray(0, 32))
  }

  mixKey (remoteKey, localKey) {
    const dh = this.curve.dh(remoteKey, localKey)
    const hkdfResult = hkdf(this.chainingKey, dh)
    this.chainingKey = hkdfResult[0]
    this.initialiseKey(hkdfResult[1].subarray(0, 32))
  }

  encryptAndHash (plaintext) {
    const ciphertext = this.encrypt(plaintext, this.digest)
    accumulateDigest(this.digest, ciphertext)
    return ciphertext
  }

  decryptAndHash (ciphertext) {
    const plaintext = this.decrypt(ciphertext, this.digest)
    accumulateDigest(this.digest, ciphertext)
    return plaintext
  }

  getHandshakeHash (out) {
    if (!out) return this.getHandshakeHash(b4a.alloc(HASHLEN))
    assert(out.byteLength === HASHLEN, `output must be ${HASHLEN} bytes`)

    out.set(this.digest)
    return out
  }

  split () {
    const res = hkdf(this.chainingKey, b4a.alloc(0))
    return res.map(k => k.subarray(0, 32))
  }

  _clear () {
    super._clear()

    sodium.sodium_memzero(this.digest)
    sodium.sodium_memzero(this.chainingKey)

    this.digest = null
    this.chainingKey = null
    this.offset = null

    this.curve = null
  }

  static get alg () {
    return CipherState.alg + '_BLAKE2b'
  }
}

function accumulateDigest (digest, input) {
  const toHash = b4a.concat([digest, input])
  sodium.crypto_generichash(digest, toHash)
}
var varint = require('varint')
var svarint = require('signed-varint')
var b4a = require('b4a')

exports.make = encoder

exports.name = function (enc) {
  var keys = Object.keys(exports)
  for (var i = 0; i < keys.length; i++) {
    if (exports[keys[i]] === enc) return keys[i]
  }
  return null
}

exports.skip = function (type, buffer, offset) {
  switch (type) {
    case 0:
      varint.decode(buffer, offset)
      return offset + varint.decode.bytes

    case 1:
      return offset + 8

    case 2:
      var len = varint.decode(buffer, offset)
      return offset + varint.decode.bytes + len

    case 3:
    case 4:
      throw new Error('Groups are not supported')

    case 5:
      return offset + 4
  }

  throw new Error('Unknown wire type: ' + type)
}

exports.bytes = encoder(2,
  function encode (val, buffer, offset) {
    var oldOffset = offset
    var len = bufferLength(val)

    varint.encode(len, buffer, offset)
    offset += varint.encode.bytes

    if (b4a.isBuffer(val)) b4a.copy(val, buffer, offset)
    else b4a.write(buffer, val, offset, len)
    offset += len

    encode.bytes = offset - oldOffset
    return buffer
  },
  function decode (buffer, offset) {
    var oldOffset = offset

    var len = varint.decode(buffer, offset)
    offset += varint.decode.bytes

    var val = buffer.subarray(offset, offset + len)
    offset += val.length

    decode.bytes = offset - oldOffset
    return val
  },
  function encodingLength (val) {
    var len = bufferLength(val)
    return varint.encodingLength(len) + len
  }
)

exports.string = encoder(2,
  function encode (val, buffer, offset) {
    var oldOffset = offset
    var len = b4a.byteLength(val)

    varint.encode(len, buffer, offset, 'utf-8')
    offset += varint.encode.bytes

    b4a.write(buffer, val, offset, len)
    offset += len

    encode.bytes = offset - oldOffset
    return buffer
  },
  function decode (buffer, offset) {
    var oldOffset = offset

    var len = varint.decode(buffer, offset)
    offset += varint.decode.bytes

    var val = b4a.toString(buffer, 'utf-8', offset, offset + len)
    offset += len

    decode.bytes = offset - oldOffset
    return val
  },
  function encodingLength (val) {
    var len = b4a.byteLength(val)
    return varint.encodingLength(len) + len
  }
)

exports.bool = encoder(0,
  function encode (val, buffer, offset) {
    buffer[offset] = val ? 1 : 0
    encode.bytes = 1
    return buffer
  },
  function decode (buffer, offset) {
    var bool = buffer[offset] > 0
    decode.bytes = 1
    return bool
  },
  function encodingLength () {
    return 1
  }
)

exports.int32 = encoder(0,
  function encode (val, buffer, offset) {
    varint.encode(val < 0 ? val + 4294967296 : val, buffer, offset)
    encode.bytes = varint.encode.bytes
    return buffer
  },
  function decode (buffer, offset) {
    var val = varint.decode(buffer, offset)
    decode.bytes = varint.decode.bytes
    return val > 2147483647 ? val - 4294967296 : val
  },
  function encodingLength (val) {
    return varint.encodingLength(val < 0 ? val + 4294967296 : val)
  }
)

exports.int64 = encoder(0,
  function encode (val, buffer, offset) {
    if (val < 0) {
      var last = offset + 9
      varint.encode(val * -1, buffer, offset)
      offset += varint.encode.bytes - 1
      buffer[offset] = buffer[offset] | 0x80
      while (offset < last - 1) {
        offset++
        buffer[offset] = 0xff
      }
      buffer[last] = 0x01
      encode.bytes = 10
    } else {
      varint.encode(val, buffer, offset)
      encode.bytes = varint.encode.bytes
    }
    return buffer
  },
  function decode (buffer, offset) {
    var val = varint.decode(buffer, offset)
    if (val >= Math.pow(2, 63)) {
      var limit = 9
      while (buffer[offset + limit - 1] === 0xff) limit--
      limit = limit || 9
      var subset = b4a.allocUnsafe(limit)
      b4a.copy(buffer, subset, 0, offset, offset + limit)
      subset[limit - 1] = subset[limit - 1] & 0x7f
      val = -1 * varint.decode(subset, 0)
      decode.bytes = 10
    } else {
      decode.bytes = varint.decode.bytes
    }
    return val
  },
  function encodingLength (val) {
    return val < 0 ? 10 : varint.encodingLength(val)
  }
)

exports.sint32 =
exports.sint64 = encoder(0,
  svarint.encode,
  svarint.decode,
  svarint.encodingLength
)

exports.uint32 =
exports.uint64 =
exports.enum =
exports.varint = encoder(0,
  varint.encode,
  varint.decode,
  varint.encodingLength
)

// we cannot represent these in javascript so we just use buffers
exports.fixed64 =
exports.sfixed64 = encoder(1,
  function encode (val, buffer, offset) {
    b4a.copy(val, buffer, offset)
    encode.bytes = 8
    return buffer
  },
  function decode (buffer, offset) {
    var val = buffer.subarray(offset, offset + 8)
    decode.bytes = 8
    return val
  },
  function encodingLength () {
    return 8
  }
)

exports.double = encoder(1,
  function encode (val, buffer, offset) {
    b4a.writeDoubleLE(buffer, val, offset)
    encode.bytes = 8
    return buffer
  },
  function decode (buffer, offset) {
    var val = b4a.readDoubleLE(buffer, offset)
    decode.bytes = 8
    return val
  },
  function encodingLength () {
    return 8
  }
)

exports.fixed32 = encoder(5,
  function encode (val, buffer, offset) {
    b4a.writeUInt32LE(buffer, val, offset)
    encode.bytes = 4
    return buffer
  },
  function decode (buffer, offset) {
    var val = b4a.readUInt32LE(buffer, offset)
    decode.bytes = 4
    return val
  },
  function encodingLength () {
    return 4
  }
)

exports.sfixed32 = encoder(5,
  function encode (val, buffer, offset) {
    b4a.writeInt32LE(buffer, val, offset)
    encode.bytes = 4
    return buffer
  },
  function decode (buffer, offset) {
    var val = b4a.readInt32LE(buffer, offset)
    decode.bytes = 4
    return val
  },
  function encodingLength () {
    return 4
  }
)

exports.float = encoder(5,
  function encode (val, buffer, offset) {
    b4a.writeFloatLE(buffer, val, offset)
    encode.bytes = 4
    return buffer
  },
  function decode (buffer, offset) {
    var val = b4a.readFloatLE(buffer, offset)
    decode.bytes = 4
    return val
  },
  function encodingLength () {
    return 4
  }
)

function encoder (type, encode, decode, encodingLength) {
  encode.bytes = decode.bytes = 0

  return {
    type: type,
    encode: encode,
    decode: decode,
    encodingLength: encodingLength
  }
}

function bufferLength (val) {
  return b4a.isBuffer(val) ? val.length : b4a.byteLength(val)
}
{
  "name": "protocol-buffers-encodings",
  "version": "1.2.0",
  "description": "Base encodings for protocol-buffers",
  "main": "index.js",
  "dependencies": {
    "b4a": "^1.6.0",
    "signed-varint": "^2.0.1",
    "varint": "5.0.0"
  },
  "devDependencies": {
    "standard": "^14.3.4",
    "tape": "^5.0.1"
  },
  "scripts": {
    "test": "standard && tape test.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/mafintosh/protocol-buffers-encodings.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/mafintosh/protocol-buffers-encodings/issues"
  },
  "homepage": "https://github.com/mafintosh/protocol-buffers-encodings"
}
const crypto = require('hypercore-crypto')
const Protomux = require('protomux')
const b4a = require('b4a')
const schema = require('./spec/hyperschema')

const [NS_INITATOR, NS_RESPONDER] = crypto.namespace('wakeup', 2)

const Handshake = schema.getEncoding('@wakeup/handshake')
const Announce = schema.getEncoding('@wakeup/announce')
const Lookup = schema.getEncoding('@wakeup/lookup')
const Info = schema.getEncoding('@wakeup/info')

module.exports = class WakeupSwarm {
  constructor(onwakeup = noop, { gcTickTime = 2000 } = {}) {
    this.gcTickTime = gcTickTime

    this.topics = new Map()
    this.topicsGC = new Set()
    this.muxers = new Set()
    this.stats = {
      sessionsOpened: 0,
      sessionsClosed: 0,
      topicsAdded: 0,
      topicsGcd: 0,
      peersAdded: 0,
      peersRemoved: 0,
      wireAnnounce: { rx: 0, tx: 0 },
      wireLookup: { rx: 0, tx: 0 },
      wireInfo: { rx: 0, tx: 0 }
    }

    this.onwakeup = onwakeup

    this._gcInterval = null
    this._gcBound = this._gc.bind(this)
  }

  session(capability, handlers = {}) {
    const id = handlers.discoveryKey || crypto.discoveryKey(capability)
    const active = handlers.active !== false
    const hex = b4a.toString(id, 'hex')

    let w = this.topics.get(hex)

    if (w) return w.addSession(handlers)

    w = new WakeupTopic(this, id, capability, active)

    this.topics.set(hex, w)

    for (const muxer of this.muxers) {
      w._onopen(muxer, true)
    }

    return w.addSession(handlers)
  }

  getSessions(capability, handlers = {}) {
    const id = handlers.discoveryKey || crypto.discoveryKey(capability)
    const hex = b4a.toString(id, 'hex')
    const w = this.topics.get(hex)
    return w ? w.sessions : []
  }

  hasStream(stream, capability, handlers = {}) {
    if (!capability) {
      const noiseStream = stream.noiseStream || stream
      return this.muxers.has(getMuxer(noiseStream))
    }

    const id = handlers.discoveryKey || crypto.discoveryKey(capability)
    const hex = b4a.toString(id, 'hex')
    const t = this.topics.get(hex)

    return t ? t.peersByStream.has(stream) : false
  }

  addStream(stream) {
    const noiseStream = stream.noiseStream || stream

    if (!noiseStream.connected) {
      noiseStream.once('open', this.addStream.bind(this, noiseStream))
      return
    }

    const muxer = getMuxer(noiseStream)
    muxer.pair({ protocol: 'wakeup' }, (id) => this._onpair(id, muxer))

    this.muxers.add(muxer)
    noiseStream.on('close', () => this.muxers.delete(muxer))

    for (const w of this.topics.values()) {
      if (!w.isActive) continue
      w._onopen(muxer, true)
    }
  }

  _onActive(w) {
    for (const m of this.muxers) {
      w._onopen(m, false)
    }
  }

  _addGC(topic) {
    if (topic.destroyed) return
    this.topicsGC.add(topic)
    if (this._gcInterval === null) {
      this._gcInterval = setInterval(this._gcBound, this.gcTickTime)
    }
  }

  _removeGC(topic) {
    this.topicsGC.delete(topic)
    if (this.topicsGC.size === 0 && this._gcInterval) {
      clearInterval(this._gcInterval)
      this._gcInterval = null
    }
  }

  _gc() {
    const destroy = []
    for (const w of this.topicsGC) {
      w.idleTicks++
      if (w.idleTicks >= 5) destroy.push(w)
    }
    for (const w of destroy) w.teardown()
  }

  destroy() {
    if (this._gcInterval) clearInterval(this._gcInterval)
    this._gcInterval = null

    for (const w of this.topics.values()) w.teardown()
  }

  async _onpair(id, stream) {
    const hex = b4a.toString(id, 'hex')
    const w = this.topics.get(hex)
    if (!w || !w.sessions.length) return this.onwakeup(id, stream)
    w._onopen(getMuxer(stream), false)
  }

  registerMetrics(promClient) {
    const self = this
    new promClient.Gauge({
      // eslint-disable-line no-new
      name: 'protomux_wakeup_sessions_opened',
      help: 'The amount of sessions opened by protomux wakeup',
      collect() {
        this.set(self.stats.sessionsOpened)
      }
    })

    new promClient.Gauge({
      // eslint-disable-line no-new
      name: 'protomux_wakeup_sessions_closed',
      help: 'The amount of sessions closed by protomux wakeup',
      collect() {
        this.set(self.stats.sessionsClosed)
      }
    })

    new promClient.Gauge({
      // eslint-disable-line no-new
      name: 'protomux_wakeup_topics_added',
      help: 'The amount of topics added to protomux wakeup',
      collect() {
        this.set(self.stats.topicsAdded)
      }
    })

    new promClient.Gauge({
      // eslint-disable-line no-new
      name: 'protomux_wakeup_topics_gcd',
      help: 'The amount of topics that got garbage collected by protomux wakeup',
      collect() {
        this.set(self.stats.topicsGcd)
      }
    })

    new promClient.Gauge({
      // eslint-disable-line no-new
      name: 'protomux_wakeup_peers_added',
      help: 'The amount of peers added by protomux wakeup, across all topics',
      collect() {
        this.set(self.stats.peersAdded)
      }
    })

    new promClient.Gauge({
      // eslint-disable-line no-new
      name: 'protomux_wakeup_peers_removed',
      help: 'The amount of peers removed by protomux wakeup, across all topics',
      collect() {
        this.set(self.stats.peersRemoved)
      }
    })

    new promClient.Gauge({
      // eslint-disable-line no-new
      name: 'protomux_wakeup_wire_announce_rx',
      help: 'The amount of wire announce messages received by protomux wakeup',
      collect() {
        this.set(self.stats.wireAnnounce.rx)
      }
    })
    new promClient.Gauge({
      // eslint-disable-line no-new
      name: 'protomux_wakeup_wire_announce_tx',
      help: 'The amount of wire announce messages transmitted by protomux wakeup',
      collect() {
        this.set(self.stats.wireAnnounce.tx)
      }
    })
    new promClient.Gauge({
      // eslint-disable-line no-new
      name: 'protomux_wakeup_wire_lookup_rx',
      help: 'The amount of wire lookup messages received by protomux wakeup',
      collect() {
        this.set(self.stats.wireLookup.rx)
      }
    })
    new promClient.Gauge({
      // eslint-disable-line no-new
      name: 'protomux_wakeup_wire_lookup_tx',
      help: 'The amount of wire lookup messages transmitted by protomux wakeup',
      collect() {
        this.set(self.stats.wireLookup.tx)
      }
    })
    new promClient.Gauge({
      // eslint-disable-line no-new
      name: 'protomux_wakeup_wire_info_rx',
      help: 'The amount of wire info messages received by protomux wakeup',
      collect() {
        this.set(self.stats.wireInfo.rx)
      }
    })
    new promClient.Gauge({
      // eslint-disable-line no-new
      name: 'protomux_wakeup_wire_info_tx',
      help: 'The amount of wire info messages transmitted by protomux wakeup',
      collect() {
        this.set(self.stats.wireInfo.rx)
      }
    })
  }
}

class WakeupPeer {
  constructor(topic) {
    this.index = 0
    this.userData = null // for the user
    this.clock = 0 // for the user, v useful to reduce traffic
    this.pending = true
    this.removed = false
    this.topic = topic
    this.channel = null
    this.stream = null
    this.wireLookup = null
    this.wireAnnounce = null
    this.wireInfo = null
    this.active = false
  }

  unlink(list) {
    // note that since we pop here we can iterate in reverse safely in case a peer is removed in the same tick
    const head = list.pop()
    if (head === this) return
    head.index = this.index
    list[head.index] = head
  }
}

class WakeupSession {
  constructor(topic, handlers) {
    this.index = 0
    this.topic = topic
    this.handlers = handlers
    this.isActive = handlers.active !== false
    this.destroyed = false
  }

  get peers() {
    return this.topic.peers
  }

  hasStream(stream) {
    return !!this.getPeer(stream)
  }

  addStream(stream) {
    this.topic.addStream(stream)
  }

  getPeer(stream) {
    return this.topic.peersByStream.get(stream) || null
  }

  broadcastLookup(req) {
    for (const peer of this.topic.pendingPeers) {
      this.lookup(peer, req)
    }
    for (const peer of this.topic.peers) {
      this.lookup(peer, req)
    }
  }

  lookupByStream(stream, req) {
    const peer = this.topic.peersByStream.get(stream)
    if (peer) this.lookup(peer, req)
  }

  lookup(peer, req) {
    peer.topic.state.stats.wireLookup.tx++
    peer.wireLookup.send(req || { hash: null })
  }

  announceByStream(stream, wakeup) {
    const peer = this.topic.peersByStream.get(stream)
    if (peer && !peer.pending) this.announce(peer, wakeup)
  }

  announce(peer, wakeup) {
    peer.topic.state.stats.wireAnnounce.tx++
    peer.wireAnnounce.send(wakeup)
  }

  active() {
    this.isActive = true
    this.topic._bumpActivity()
  }

  inactive() {
    this.isActive = false
    this.topic._bumpActivity()
  }

  destroy() {
    if (this.destroyed) return
    this.destroyed = true
    this.topic.removeSession(this)
  }
}

class WakeupTopic {
  constructor(state, id, capability, active) {
    this.state = state
    this.sessions = []
    this.id = id
    this.capability = capability
    this.peers = []
    this.pendingPeers = []
    this.peersByStream = new Map()
    this.activePeers = 0
    this.isActive = active
    this.idleTicks = 0
    this.gcing = false
    this.destroyed = false

    this.state.stats.topicsAdded++
  }

  addSession(handlers) {
    this.state.stats.sessionsOpened++
    const session = new WakeupSession(this, handlers)
    session.index = this.sessions.length
    this.sessions.push(session)
    this._bumpActivity()
    this._checkGC()
    return session
  }

  removeSession(session) {
    if (this.sessions.length <= session.index) return
    if (this.sessions[session.index] !== session) return

    this.state.stats.sessionsClosed++

    // same as with the peer, this allows us to iterate while removing if iterating backwards
    const head = this.sessions.pop()
    if (head !== session) {
      head.index = session.index
      this.sessions[head.index] = head
    }

    this._bumpActivity()
    this._checkGC()
  }

  _bumpActivity() {
    let isActive = false

    for (let i = this.sessions.length - 1; i >= 0; i--) {
      if (this.sessions[i].isActive) {
        isActive = true
        break
      }
    }

    if (isActive) this.active()
    else this.inactive()
  }

  active() {
    if (this.isActive) return
    this.idleTicks = 0
    this.isActive = true
    this._updateActive(true)
  }

  inactive() {
    if (!this.isActive) return
    this.isActive = false
    this._updateActive(false)
  }

  _updateActive(active) {
    const info = { active }

    for (const peer of this.pendingPeers) {
      peer.topic.state.stats.wireInfo.tx++
      peer.wireInfo.send(info)
    }
    for (const peer of this.peers) {
      peer.topic.state.stats.wireInfo.tx++
      peer.wireInfo.send(info)
    }

    this._checkGC()

    if (active) this.state._onActive(this)
  }

  teardown() {
    if (this.destroyed) return
    this.destroyed = true

    this.state.stats.topicsGcd++

    for (let i = this.peers.length - 1; i >= 0; i--) {
      this.peers[i].channel.close()
    }

    for (let i = this.pendingPeers.length - 1; i >= 0; i--) {
      this.pendingPeers[i].channel.close()
    }

    const hex = b4a.toString(this.id, 'hex')

    this.gcing = false
    this.state.topics.delete(hex)
    this.state._removeGC(this)

    for (let i = this.sessions.length - 1; i >= 0; i--) {
      this.state.stats.sessionsClosed++
      const session = this.sessions[i]
      if (session.handlers.ondestroy) session.handlers.ondestroy(session)
    }
  }

  addStream(stream) {
    this._onopen(getMuxer(stream), false)
  }

  _proveCapabilityTo(stream) {
    return this._makeCapability(stream.isInitiator, stream.handshakeHash)
  }

  _makeCapability(isInitiator, handshakeHash) {
    return crypto.hash([isInitiator ? NS_INITATOR : NS_RESPONDER, this.capability, handshakeHash])
  }

  _addPeer(peer, open) {
    if (
      !b4a.equals(
        open.capability,
        this._makeCapability(!peer.stream.isInitiator, peer.stream.handshakeHash)
      )
    ) {
      peer.channel.close()
      return
    }

    if (peer.pending) {
      peer.unlink(this.pendingPeers)
    }

    peer.active = open.active
    peer.pending = false
    peer.index = this.peers.push(peer) - 1

    if (peer.active) {
      this.activePeers++
      this._checkGC()
    }

    for (let i = this.sessions.length - 1; i >= 0; i--) {
      const session = this.sessions[i]
      const handlers = session.handlers

      if (handlers.onpeeradd) handlers.onpeeradd(peer, session)
      if (peer.active && handlers.onpeeractive) handlers.onpeeractive(peer, session)
    }
  }

  _checkGC() {
    const shouldGC = this.activePeers === 0 && this.sessions.length === 0

    if (shouldGC) {
      if (!this.gcing) {
        this.gcing = true
        this.state._addGC(this)
      }
    } else {
      if (this.gcing) {
        this.gcing = false
        this.state._removeGC(this)
      }
    }
  }

  _removePeer(peer) {
    this.state.stats.peersRemoved++
    peer.removed = true
    this.peersByStream.delete(peer.stream)

    if (peer.pending) {
      peer.unlink(this.pendingPeers)
      return
    }

    const active = peer.active

    if (active) {
      peer.active = false
      this.activePeers--
      this._checkGC()
    }

    peer.unlink(this.peers)

    for (let i = this.sessions.length - 1; i >= 0; i--) {
      const session = this.sessions[i]
      const handlers = session.handlers

      if (active && handlers.onpeerinactive) handlers.onpeerinactive(peer, session)
      if (handlers.onpeerremove) handlers.onpeerremove(peer, session)
    }
  }

  _onannounce(wakeup, peer) {
    for (let i = this.sessions.length - 1; i >= 0; i--) {
      const session = this.sessions[i]
      const handlers = session.handlers

      if (handlers.onannounce) handlers.onannounce(wakeup, peer, session)
    }
  }

  _onlookup(req, peer) {
    for (let i = this.sessions.length - 1; i >= 0; i--) {
      const session = this.sessions[i]
      const handlers = session.handlers

      if (handlers.onlookup) handlers.onlookup(req, peer, session)
    }
  }

  _oninfo(info, peer) {
    if (info.active) {
      if (!peer.active) {
        peer.active = true
        this.activePeers++
        this._checkGC()

        for (let i = this.sessions.length - 1; i >= 0; i--) {
          const session = this.sessions[i]
          const handlers = session.handlers

          if (handlers.onpeeractive) handlers.onpeeractive(peer, session)
        }
      }
    } else {
      if (peer.active) {
        peer.active = false
        this.activePeers--
        this._checkGC()

        for (let i = this.sessions.length - 1; i >= 0; i--) {
          const session = this.sessions[i]
          const handlers = session.handlers

          if (handlers.onpeerinactive) handlers.onpeerinactive(peer, session)
        }
      }
    }
  }

  _onopen(muxer, unique) {
    if (!unique && this.peersByStream.has(muxer.stream)) return

    const peer = new WakeupPeer(this)
    const ch = muxer.createChannel({
      userData: peer,
      protocol: 'wakeup',
      id: this.id,
      handshake: Handshake,
      messages: [
        { encoding: Lookup, onmessage: onlookup },
        { encoding: Announce, onmessage: onannounce },
        { encoding: Info, onmessage: onchannelinfo }
      ],
      onopen: onchannelopen,
      onclose: onchannelclose
    })

    if (!ch) return

    this.state.stats.peersAdded++

    peer.channel = ch
    peer.stream = muxer.stream

    peer.wireLookup = ch.messages[0]
    peer.wireAnnounce = ch.messages[1]
    peer.wireInfo = ch.messages[2]

    peer.index = this.pendingPeers.push(peer) - 1
    this.peersByStream.set(muxer.stream, peer)

    ch.open({
      version: 0,
      capability: this._proveCapabilityTo(muxer.stream),
      active: this.isActive
    })
  }
}

function onchannelopen(open, channel) {
  const peer = channel.userData
  peer.topic._addPeer(peer, open)
}

function onchannelclose(close, channel) {
  const peer = channel.userData
  peer.topic._removePeer(peer)
}

function onlookup(req, channel) {
  const peer = channel.userData
  peer.topic.state.stats.wireLookup.rx++
  peer.topic._onlookup(req, peer)
}

function onannounce(wakeup, channel) {
  const peer = channel.userData
  peer.topic.state.stats.wireAnnounce.rx++
  peer.topic._onannounce(wakeup, peer)
}

function onchannelinfo(info, channel) {
  const peer = channel.userData
  peer.topic.state.stats.wireInfo.rx++
  peer.topic._oninfo(info, peer)
}

function getMuxer(stream) {
  if (Protomux.isProtomux(stream)) return stream
  if (stream.noiseStream.userData) return stream.noiseStream.userData
  const mux = Protomux.from(stream.noiseStream)
  stream.noiseStream.userData = mux
  return mux
}

function noop() {}
{
  "name": "protomux-wakeup",
  "version": "2.9.0",
  "description": "Wakeup protocol over protomux",
  "main": "index.js",
  "dependencies": {
    "b4a": "^1.6.7",
    "hypercore-crypto": "^3.5.0",
    "hyperschema": "^1.10.4",
    "protomux": "^3.10.1"
  },
  "devDependencies": {
    "@hyperswarm/secret-stream": "^6.8.1",
    "brittle": "^3.17.1",
    "lunte": "^1.0.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^2.0.0",
    "prom-client": "^15.1.3"
  },
  "files": [
    "index.js",
    "spec/hyperschema/*.js"
  ],
  "scripts": {
    "format": "prettier --write .",
    "test": "prettier --check . && lunte && brittle test/*.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/holepunchto/protomux-wakeup.git"
  },
  "author": "Holepunch Inc.",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/protomux-wakeup/issues"
  },
  "homepage": "https://github.com/holepunchto/protomux-wakeup"
}
// This file is autogenerated by the hyperschema compiler
// Schema Version: 1
/* eslint-disable camelcase */
/* eslint-disable quotes */

const VERSION = 1
const { c } = require('hyperschema/runtime')

// eslint-disable-next-line no-unused-vars
let version = VERSION

// @wakeup/info
const encoding0 = {
  preencode(state, m) {
    state.end++ // max flag is 1 so always one byte
  },
  encode(state, m) {
    const flags = m.active ? 1 : 0

    c.uint.encode(state, flags)
  },
  decode(state) {
    const flags = c.uint.decode(state)

    return {
      active: (flags & 1) !== 0
    }
  }
}

// @wakeup/handshake
const encoding1 = {
  preencode(state, m) {
    c.uint.preencode(state, m.version)
    c.fixed32.preencode(state, m.capability)
    state.end++ // max flag is 1 so always one byte
  },
  encode(state, m) {
    const flags = m.active ? 1 : 0

    c.uint.encode(state, m.version)
    c.fixed32.encode(state, m.capability)
    c.uint.encode(state, flags)
  },
  decode(state) {
    const r0 = c.uint.decode(state)
    const r1 = c.fixed32.decode(state)
    const flags = c.uint.decode(state)

    return {
      version: r0,
      capability: r1,
      active: (flags & 1) !== 0
    }
  }
}

// @wakeup/writer
const encoding2 = {
  preencode(state, m) {
    c.fixed32.preencode(state, m.key)
    c.uint.preencode(state, m.length)
  },
  encode(state, m) {
    c.fixed32.encode(state, m.key)
    c.uint.encode(state, m.length)
  },
  decode(state) {
    const r0 = c.fixed32.decode(state)
    const r1 = c.uint.decode(state)

    return {
      key: r0,
      length: r1
    }
  }
}

// @wakeup/announce
const encoding3 = c.array(encoding2)

// @wakeup/lookup
const encoding4 = {
  preencode(state, m) {
    state.end++ // max flag is 1 so always one byte

    if (m.hash) c.fixed32.preencode(state, m.hash)
  },
  encode(state, m) {
    const flags = m.hash ? 1 : 0

    c.uint.encode(state, flags)

    if (m.hash) c.fixed32.encode(state, m.hash)
  },
  decode(state) {
    const flags = c.uint.decode(state)

    return {
      hash: (flags & 1) !== 0 ? c.fixed32.decode(state) : null
    }
  }
}

function setVersion(v) {
  version = v
}

function encode(name, value, v = VERSION) {
  version = v
  return c.encode(getEncoding(name), value)
}

function decode(name, buffer, v = VERSION) {
  version = v
  return c.decode(getEncoding(name), buffer)
}

function getEnum(name) {
  switch (name) {
    default:
      throw new Error('Enum not found ' + name)
  }
}

function getEncoding(name) {
  switch (name) {
    case '@wakeup/info':
      return encoding0
    case '@wakeup/handshake':
      return encoding1
    case '@wakeup/writer':
      return encoding2
    case '@wakeup/announce':
      return encoding3
    case '@wakeup/lookup':
      return encoding4
    default:
      throw new Error('Encoder not found ' + name)
  }
}

function getStruct(name, v = VERSION) {
  const enc = getEncoding(name)
  return {
    preencode(state, m) {
      version = v
      enc.preencode(state, m)
    },
    encode(state, m) {
      version = v
      enc.encode(state, m)
    },
    decode(state) {
      version = v
      return enc.decode(state)
    }
  }
}

const resolveStruct = getStruct // compat

module.exports = {
  resolveStruct,
  getStruct,
  getEnum,
  getEncoding,
  encode,
  decode,
  setVersion,
  version
}
const b4a = require('b4a')
const c = require('compact-encoding')
const queueTick = require('queue-tick')
const safetyCatch = require('safety-catch')
const unslab = require('unslab')

const MAX_BUFFERED = 32768
const MAX_BACKLOG = Infinity // TODO: impl "open" backpressure
const MAX_BATCH = 8 * 1024 * 1024

class Channel {
  constructor(
    mux,
    info,
    userData,
    protocol,
    aliases,
    id,
    handshake,
    messages,
    onopen,
    onclose,
    ondestroy,
    ondrain
  ) {
    this.userData = userData
    this.protocol = protocol
    this.aliases = aliases
    this.id = id
    this.handshake = null
    this.messages = []

    this.opened = false
    this.closed = false
    this.destroyed = false

    this.onopen = onopen
    this.onclose = onclose
    this.ondestroy = ondestroy
    this.ondrain = ondrain

    this._handshake = handshake
    this._mux = mux
    this._info = info
    this._localId = 0
    this._remoteId = 0
    this._active = 0
    this._extensions = null

    this._decBound = this._dec.bind(this)
    this._decAndDestroyBound = this._decAndDestroy.bind(this)

    this._openedPromise = null
    this._openedResolve = null

    this._destroyedPromise = null
    this._destroyedResolve = null

    for (const m of messages) this.addMessage(m)
  }

  get drained() {
    return this._mux.drained
  }

  fullyOpened() {
    if (this.opened) return Promise.resolve(true)
    if (this.closed) return Promise.resolve(false)
    if (this._openedPromise) return this._openedPromise

    this._openedPromise = new Promise((resolve) => {
      this._openedResolve = resolve
    })
    return this._openedPromise
  }

  fullyClosed() {
    if (this.destroyed) return Promise.resolve()
    if (this._destroyedPromise) return this._destroyedPromise

    this._destroyedPromise = new Promise((resolve) => {
      this._destroyedResolve = resolve
    })
    return this._destroyedPromise
  }

  open(handshake) {
    const id = this._mux._free.length > 0 ? this._mux._free.pop() : this._mux._local.push(null) - 1

    this._info.opened++
    this._info.lastChannel = this
    this._localId = id + 1
    this._mux._local[id] = this

    if (this._remoteId === 0) {
      this._info.outgoing.push(this._localId)
    }

    const state = { buffer: null, start: 2, end: 2 }

    c.uint.preencode(state, this._localId)
    c.string.preencode(state, this.protocol)
    c.optionalBuffer.preencode(state, this.id)
    if (this._handshake) this._handshake.preencode(state, handshake)

    state.buffer = this._mux._alloc(state.end)

    state.buffer[0] = 0
    state.buffer[1] = 1
    c.uint.encode(state, this._localId)
    c.string.encode(state, this.protocol)
    c.optionalBuffer.encode(state, this.id)
    if (this._handshake) this._handshake.encode(state, handshake)

    this._mux._write0(state.buffer)
  }

  _dec() {
    if (--this._active === 0 && this.closed === true) this._destroy()
  }

  _decAndDestroy(err) {
    this._dec()
    this._mux._safeDestroy(err)
  }

  _fullyOpenSoon() {
    this._mux._remote[this._remoteId - 1].session = this
    queueTick(this._fullyOpenOrDestroy.bind(this))
  }

  _fullyOpenOrDestroy() {
    try {
      this._fullyOpen()
    } catch (err) {
      this._mux._safeDestroyBound(err)
    }
  }

  _fullyOpen() {
    if (this.opened === true || this.closed === true) return

    const remote = this._mux._remote[this._remoteId - 1]

    this.handshake = this._handshake ? this._handshake.decode(remote.state) : null
    this._track(this.onopen(this.handshake, this))

    remote.session = this
    remote.state = null
    if (remote.pending !== null) this._drain(remote)
    if (this._mux._destroying === true) return

    this.opened = true
    this._resolveOpen(true)
  }

  _resolveOpen(opened) {
    if (this._openedResolve !== null) {
      this._openedResolve(opened)
      this._openedResolve = this._openedPromise = null
    }
  }

  _resolveDestroyed() {
    if (this._destroyedResolve !== null) {
      this._destroyedResolve()
      this._destroyedResolve = this._destroyedPromise = null
    }
  }

  _drain(remote) {
    for (let i = 0; i < remote.pending.length; i++) {
      const p = remote.pending[i]
      this._mux._buffered -= byteSize(p.state)
      this._recv(p.type, p.state)
      if (this._mux._destroying === true) return
    }

    remote.pending = null
    this._mux._resumeMaybe()
  }

  _track(p) {
    if (isPromise(p) === true) {
      this._active++
      return p.then(this._decBound, this._decAndDestroyBound)
    }

    return null
  }

  _close(isRemote) {
    if (this.closed === true) return
    this.closed = true

    this._info.opened--
    if (this._info.lastChannel === this) this._info.lastChannel = null

    if (this._remoteId > 0) {
      this._mux._remote[this._remoteId - 1] = null
      this._remoteId = 0
      // If remote has acked, we can reuse the local id now
      // otherwise, we need to wait for the "ack" to arrive
      this._mux._free.push(this._localId - 1)
    }

    this._mux._local[this._localId - 1] = null
    this._localId = 0

    this._mux._gc(this._info)
    this._track(this.onclose(isRemote, this))

    if (this._active === 0) this._destroy()

    this._resolveOpen(false)
  }

  _destroy() {
    if (this.destroyed === true) return
    this.destroyed = true
    this._track(this.ondestroy(this))
    this._resolveDestroyed()
  }

  _recv(type, state) {
    if (type < this.messages.length) {
      const m = this.messages[type]
      const p = m.recv(state, this)
      if (m.autoBatch === true) return p
    }
    return null
  }

  cork() {
    this._mux.cork()
  }

  uncork() {
    this._mux.uncork()
  }

  close() {
    if (this.closed === true) return

    const state = { buffer: null, start: 2, end: 2 }

    c.uint.preencode(state, this._localId)

    state.buffer = this._mux._alloc(state.end)

    state.buffer[0] = 0
    state.buffer[1] = 3
    c.uint.encode(state, this._localId)

    this._close(false)
    this._mux._write0(state.buffer)
  }

  addMessage(opts) {
    if (!opts) return this._skipMessage()

    const type = this.messages.length
    const autoBatch = opts.autoBatch !== false
    const encoding = opts.encoding || c.raw
    const onmessage = opts.onmessage || noop

    const s = this
    const typeLen = encodingLength(c.uint, type)

    const m = {
      type,
      autoBatch,
      encoding,
      onmessage,
      recv(state, session) {
        return session._track(m.onmessage(encoding.decode(state), session))
      },
      send(m, session = s) {
        if (session.closed === true) return false

        const mux = session._mux
        const state = { buffer: null, start: 0, end: typeLen }

        if (mux._batch !== null) {
          encoding.preencode(state, m)
          state.buffer = mux._alloc(state.end)

          c.uint.encode(state, type)
          encoding.encode(state, m)

          mux._pushBatch(session._localId, state.buffer)
          return true
        }

        c.uint.preencode(state, session._localId)
        encoding.preencode(state, m)

        state.buffer = mux._alloc(state.end)

        c.uint.encode(state, session._localId)
        c.uint.encode(state, type)
        encoding.encode(state, m)

        mux.drained = mux.stream.write(state.buffer)

        return mux.drained
      }
    }

    this.messages.push(m)

    return m
  }

  _skipMessage() {
    const type = this.messages.length
    const m = {
      type,
      encoding: c.raw,
      onmessage: noop,
      recv(state, session) {},
      send(m, session) {}
    }

    this.messages.push(m)
    return m
  }
}

module.exports = class Protomux {
  constructor(stream, { alloc } = {}) {
    if (stream.userData === null) stream.userData = this

    this.isProtomux = true
    this.stream = stream
    this.corked = 0
    this.drained = true

    this._alloc =
      alloc || (typeof stream.alloc === 'function' ? stream.alloc.bind(stream) : b4a.allocUnsafe)
    this._safeDestroyBound = this._safeDestroy.bind(this)
    this._uncorkBound = this.uncork.bind(this)

    this._remoteBacklog = 0
    this._buffered = 0
    this._paused = false
    this._remote = []
    this._local = []
    this._free = []
    this._batch = null
    this._batchState = null

    this._infos = new Map()
    this._notify = new Map()
    // stream.destroyed flips asynchronously on streamx-based transports.
    this._destroying = false

    this.stream.on('data', this._ondata.bind(this))
    this.stream.on('drain', this._ondrain.bind(this))
    this.stream.on('end', this._onend.bind(this))
    this.stream.on('error', noop) // we handle this in "close"
    this.stream.on('close', this._shutdown.bind(this))
  }

  static from(stream, opts) {
    if (stream.userData && stream.userData.isProtomux) return stream.userData
    if (stream.isProtomux) return stream
    return new this(stream, opts)
  }

  static isProtomux(mux) {
    return typeof mux === 'object' && mux.isProtomux === true
  }

  *[Symbol.iterator]() {
    for (const session of this._local) {
      if (session !== null) yield session
    }
  }

  isIdle() {
    return this._local.length === this._free.length
  }

  cork() {
    if (++this.corked === 1) {
      this._batch = []
      this._batchState = { buffer: null, start: 0, end: 1 }
    }
  }

  uncork() {
    if (--this.corked === 0) {
      this._sendBatch(this._batch, this._batchState)
      this._batch = null
      this._batchState = null
    }
  }

  getLastChannel({ protocol, id = null }) {
    const key = toKey(protocol, id)
    const info = this._infos.get(key)
    if (info) return info.lastChannel
    return null
  }

  pair({ protocol, id = null }, notify) {
    this._notify.set(toKey(protocol, id), notify)
  }

  unpair({ protocol, id = null }) {
    this._notify.delete(toKey(protocol, id))
  }

  opened({ protocol, id = null }) {
    const key = toKey(protocol, id)
    const info = this._infos.get(key)
    return info ? info.opened > 0 : false
  }

  createChannel({
    userData = null,
    protocol,
    aliases = [],
    id = null,
    unique = true,
    handshake = null,
    messages = [],
    onopen = noop,
    onclose = noop,
    ondestroy = noop,
    ondrain = noop
  }) {
    if (this.stream.destroyed) return null

    const info = this._get(protocol, id, aliases)
    if (unique && info.opened > 0) return null

    if (info.incoming.length === 0) {
      return new Channel(
        this,
        info,
        userData,
        protocol,
        aliases,
        id,
        handshake,
        messages,
        onopen,
        onclose,
        ondestroy,
        ondrain
      )
    }

    this._remoteBacklog--

    const remoteId = info.incoming.shift()
    const r = this._remote[remoteId - 1]
    if (r === null) return null

    const session = new Channel(
      this,
      info,
      userData,
      protocol,
      aliases,
      id,
      handshake,
      messages,
      onopen,
      onclose,
      ondestroy,
      ondrain
    )

    session._remoteId = remoteId
    session._fullyOpenSoon()

    return session
  }

  _pushBatch(localId, buffer) {
    if (this._batchState.end >= MAX_BATCH) {
      this._sendBatch(this._batch, this._batchState)
      this._batch = []
      this._batchState = { buffer: null, start: 0, end: 1 }
    }

    if (this._batch.length === 0 || this._batch[this._batch.length - 1].localId !== localId) {
      this._batchState.end++
      c.uint.preencode(this._batchState, localId)
    }
    c.buffer.preencode(this._batchState, buffer)
    this._batch.push({ localId, buffer })
  }

  _sendBatch(batch, state) {
    if (batch.length === 0) return

    let prev = batch[0].localId

    state.buffer = this._alloc(state.end)
    state.buffer[state.start++] = 0
    state.buffer[state.start++] = 0

    c.uint.encode(state, prev)

    for (let i = 0; i < batch.length; i++) {
      const b = batch[i]
      if (prev !== b.localId) {
        state.buffer[state.start++] = 0
        c.uint.encode(state, (prev = b.localId))
      }
      c.buffer.encode(state, b.buffer)
    }

    this.drained = this.stream.write(state.buffer)
  }

  _get(protocol, id, aliases = []) {
    const key = toKey(protocol, id)

    let info = this._infos.get(key)
    if (info) return info

    info = {
      key,
      protocol,
      aliases: [],
      id,
      pairing: 0,
      opened: 0,
      incoming: [],
      outgoing: [],
      lastChannel: null
    }
    this._infos.set(key, info)

    for (const alias of aliases) {
      const key = toKey(alias, id)
      info.aliases.push(key)

      this._infos.set(key, info)
    }

    return info
  }

  _gc(info) {
    if (info.opened === 0 && info.outgoing.length === 0 && info.incoming.length === 0) {
      this._infos.delete(info.key)

      for (const alias of info.aliases) this._infos.delete(alias)
    }
  }

  _ondata(buffer) {
    if (buffer.byteLength === 0) return // ignore empty frames...
    try {
      const state = { buffer, start: 0, end: buffer.byteLength }
      this._decode(c.uint.decode(state), state)
    } catch (err) {
      this._safeDestroy(err)
    }
  }

  _ondrain() {
    this.drained = true

    for (const s of this._local) {
      if (s !== null) s._track(s.ondrain(s))
    }
  }

  _onend() {
    // TODO: support half open mode for the users who wants that here
    this.stream.end()
  }

  _decode(remoteId, state) {
    const type = c.uint.decode(state)

    if (remoteId === 0) {
      return this._oncontrolsession(type, state)
    }

    const r = remoteId <= this._remote.length ? this._remote[remoteId - 1] : null

    // if the channel is closed ignore - could just be a pipeline message...
    if (r === null) return null

    if (r.pending !== null) {
      this._bufferMessage(r, type, state)
      return null
    }

    return r.session._recv(type, state)
  }

  _oncontrolsession(type, state) {
    switch (type) {
      case 0:
        this._onbatch(state)
        break

      case 1:
        // return the promise back up as this has sideeffects so we can batch reply
        return this._onopensession(state)

      case 2:
        this._onrejectsession(state)
        break

      case 3:
        this._onclosesession(state)
        break
    }

    return null
  }

  _bufferMessage(r, type, { buffer, start, end }) {
    const state = { buffer, start, end } // copy
    r.pending.push({ type, state })
    this._buffered += byteSize(state)
    this._pauseMaybe()
  }

  _pauseMaybe() {
    if (this._paused === true || this._buffered <= MAX_BUFFERED) return
    this._paused = true
    this.stream.pause()
  }

  _resumeMaybe() {
    if (this._paused === false || this._buffered > MAX_BUFFERED) return
    this._paused = false
    this.stream.resume()
  }

  _onbatch(state) {
    const end = state.end
    let remoteId = c.uint.decode(state)

    let waiting = null

    while (state.end > state.start) {
      const len = c.uint.decode(state)
      if (len === 0) {
        remoteId = c.uint.decode(state)
        continue
      }
      state.end = state.start + len
      // if batch contains more than one message, cork it so we reply back with a batch
      if (end !== state.end && waiting === null) {
        waiting = []
        this.cork()
      }
      const p = this._decode(remoteId, state)
      if (waiting !== null && p !== null) waiting.push(p)
      state.start = state.end
      state.end = end
    }

    if (waiting !== null) {
      // the waiting promises are not allowed to throw but we destroy the stream in case we are wrong
      Promise.all(waiting).then(this._uncorkBound, this._safeDestroyBound)
    }
  }

  _onopensession(state) {
    const remoteId = c.uint.decode(state)
    const protocol = c.string.decode(state)
    const id = unslab(c.optionalBuffer.decode(state))

    // remote tried to open the control session - auto reject for now
    // as we can use as an explicit control protocol declaration if we need to
    if (remoteId === 0) {
      this._rejectSession(0)
      return null
    }

    const rid = remoteId - 1
    const info = this._get(protocol, id)

    // allow the remote to grow the ids by one
    if (this._remote.length === rid) {
      this._remote.push(null)
    }

    if (rid >= this._remote.length || this._remote[rid] !== null) {
      throw new Error('Invalid open message')
    }

    if (info.outgoing.length > 0) {
      const localId = info.outgoing.shift()
      const session = this._local[localId - 1]

      if (session === null) {
        // we already closed the channel - ignore
        this._free.push(localId - 1)
        return null
      }

      this._remote[rid] = { state, pending: null, session: null }

      session._remoteId = remoteId
      session._fullyOpen()
      return null
    }

    const copyState = { buffer: state.buffer, start: state.start, end: state.end }
    this._remote[rid] = { state: copyState, pending: [], session: null }

    if (++this._remoteBacklog > MAX_BACKLOG) {
      throw new Error('Remote exceeded backlog')
    }

    info.pairing++
    info.incoming.push(remoteId)

    return this._requestSession(protocol, id, info).catch(this._safeDestroyBound)
  }

  _onrejectsession(state) {
    const localId = c.uint.decode(state)

    // TODO: can be done smarter...
    for (const info of this._infos.values()) {
      const i = info.outgoing.indexOf(localId)
      if (i === -1) continue

      info.outgoing.splice(i, 1)

      const session = this._local[localId - 1]

      this._free.push(localId - 1)
      if (session !== null) session._close(true)

      this._gc(info)
      return
    }

    throw new Error('Invalid reject message')
  }

  _onclosesession(state) {
    const remoteId = c.uint.decode(state)

    if (remoteId === 0) return // ignore

    const rid = remoteId - 1
    const r = rid < this._remote.length ? this._remote[rid] : null

    if (r === null) return

    if (r.session !== null) r.session._close(true)
  }

  async _requestSession(protocol, id, info) {
    const notify = this._notify.get(toKey(protocol, id)) || this._notify.get(toKey(protocol, null))

    if (notify) await notify(id)

    if (--info.pairing > 0) return

    while (info.incoming.length > 0) {
      this._rejectSession(info, info.incoming.shift())
    }

    this._gc(info)
  }

  _rejectSession(info, remoteId) {
    if (remoteId > 0) {
      const r = this._remote[remoteId - 1]

      if (r.pending !== null) {
        for (let i = 0; i < r.pending.length; i++) {
          this._buffered -= byteSize(r.pending[i].state)
        }
      }

      this._remote[remoteId - 1] = null
      this._resumeMaybe()
    }

    const state = { buffer: null, start: 2, end: 2 }

    c.uint.preencode(state, remoteId)

    state.buffer = this._alloc(state.end)

    state.buffer[0] = 0
    state.buffer[1] = 2
    c.uint.encode(state, remoteId)

    this._write0(state.buffer)
  }

  _write0(buffer) {
    if (this._batch !== null) {
      this._pushBatch(0, buffer.subarray(1))
      return
    }

    this.drained = this.stream.write(buffer)
  }

  destroy(err) {
    this._destroying = true
    this.stream.destroy(err)
  }

  _safeDestroy(err) {
    safetyCatch(err)
    this._destroying = true
    this.stream.destroy(err)
  }

  _shutdown() {
    this._destroying = true
    for (const s of this._local) {
      if (s !== null) s._close(true)
    }
  }
}

function noop() {}

function toKey(protocol, id) {
  return protocol + '##' + (id ? b4a.toString(id, 'hex') : '')
}

function byteSize(state) {
  return 512 + (state.end - state.start)
}

function isPromise(p) {
  return !!(p && typeof p.then === 'function')
}

function encodingLength(enc, val) {
  const state = { buffer: null, start: 0, end: 0 }
  enc.preencode(state, val)
  return state.end
}
{
  "name": "protomux",
  "version": "3.11.0",
  "description": "Multiplex multiple message oriented protocols over a stream",
  "main": "index.js",
  "files": [
    "index.js"
  ],
  "dependencies": {
    "b4a": "^1.3.1",
    "compact-encoding": "^3.0.0",
    "queue-tick": "^1.0.0",
    "safety-catch": "^1.0.1",
    "unslab": "^1.3.0"
  },
  "devDependencies": {
    "@hyperswarm/secret-stream": "^6.0.0",
    "brittle": "^3.0.0",
    "lunte": "^1.0.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^2.0.0"
  },
  "scripts": {
    "format": "prettier --write .",
    "lint": "prettier --check . && lunte",
    "test": "npm run test:node && npm run test:bare",
    "test:node": "brittle-node test.js",
    "test:bare": "brittle-bare test.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/holepunchto/protomux.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/holepunchto/protomux/issues"
  },
  "homepage": "https://github.com/holepunchto/protomux"
}
{
  "name": "queue-tick",
  "version": "1.0.1",
  "description": "Next tick shim that prefers process.nextTick over queueMicrotask for compat",
  "main": "./process-next-tick.js",
  "browser": "./queue-microtask.js",
  "dependencies": {},
  "devDependencies": {
    "standard": "^16.0.3",
    "tape": "^5.3.1"
  },
  "scripts": {
    "test": "standard && tape test.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/mafintosh/queue-tick.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/mafintosh/queue-tick/issues"
  },
  "homepage": "https://github.com/mafintosh/queue-tick"
}
module.exports = (typeof process !== 'undefined' && typeof process.nextTick === 'function')
  ? process.nextTick.bind(process)
  : require('./queue-microtask')
module.exports = typeof queueMicrotask === 'function' ? queueMicrotask : (fn) => Promise.resolve().then(fn)
require.addon = require('require-addon')

module.exports = require.addon('.', __filename)
const binding = require('./binding')

exports.get = function get(field, bit) {
  const n = field.byteLength * 8

  if (bit < 0) bit += n
  if (bit < 0 || bit >= n) return false

  return binding.quickbit_napi_get(toBuffer(field), bit) !== 0
}

exports.set = function set(field, bit, value = true) {
  const n = field.byteLength * 8

  if (bit < 0) bit += n
  if (bit < 0 || bit >= n) return false

  return binding.quickbit_napi_set(toBuffer(field), bit, value ? 1 : 0) !== 0
}

exports.fill = function fill(
  field,
  value,
  start = 0,
  end = field.byteLength * 8
) {
  const n = field.byteLength * 8

  if (start < 0) start += n
  if (end < 0) end += n
  if (start < 0 || start >= field.byteLength * 8 || start >= end) return field

  binding.quickbit_napi_fill(toBuffer(field), value ? 1 : 0, start, end)
  return field
}

exports.clear = function clear(field, ...chunks) {
  binding.quickbit_napi_clear(toBuffer(field), chunks.map(toBufferChunk))
}

exports.findFirst = function findFirst(field, value, position = 0) {
  const n = field.byteLength * 8

  if (position < 0) position += n
  if (position < 0) position = 0
  if (position >= n) return -1

  return binding.quickbit_napi_find_first(
    toBuffer(field),
    value ? 1 : 0,
    position
  )
}

exports.findLast = function findLast(
  field,
  value,
  position = field.byteLength * 8 - 1
) {
  const n = field.byteLength * 8

  if (position < 0) position += n
  if (position < 0) return -1
  if (position >= n) position = n - 1

  return binding.quickbit_napi_find_last(
    toBuffer(field),
    value ? 1 : 0,
    position
  )
}

function toBuffer(field) {
  if (field.BYTES_PER_ELEMENT === 1) return field
  return new Uint8Array(field.buffer, field.byteOffset, field.byteLength)
}

function toBufferChunk(chunk) {
  return { field: toBuffer(chunk.field), offset: chunk.offset }
}

class Index {
  static from(fieldOrChunks, byteLength = -1) {
    if (Array.isArray(fieldOrChunks)) {
      return new SparseIndex(fieldOrChunks, byteLength)
    } else {
      return new DenseIndex(fieldOrChunks, byteLength)
    }
  }

  constructor(byteLength) {
    this._byteLength = byteLength
    this.handle = Buffer.allocUnsafe(binding.sizeof_quickbit_index_t)
  }

  get byteLength() {
    return this._byteLength
  }

  skipFirst(value, position = 0) {
    const n = this.byteLength * 8

    if (position < 0) position += n
    if (position < 0) position = 0
    if (position >= n) return n - 1

    return binding.quickbit_napi_skip_first(
      this.handle,
      this.byteLength,
      value ? 1 : 0,
      position
    )
  }

  skipLast(value, position = this.byteLength * 8 - 1) {
    const n = this.byteLength * 8

    if (position < 0) position += n
    if (position < 0) return 0
    if (position >= n) position = n - 1

    return binding.quickbit_napi_skip_last(
      this.handle,
      this.byteLength,
      value ? 1 : 0,
      position
    )
  }
}

exports.Index = Index

class DenseIndex extends Index {
  constructor(field, byteLength) {
    super(byteLength)
    this.field = field

    binding.quickbit_napi_index_init(this.handle, toBuffer(this.field))
  }

  get byteLength() {
    if (this._byteLength !== -1) return this._byteLength
    return this.field.byteLength
  }

  update(bit) {
    const n = this.byteLength * 8

    if (bit < 0) bit += n
    if (bit < 0 || bit >= n) return false

    return (
      binding.quickbit_napi_index_update(
        this.handle,
        toBuffer(this.field),
        bit
      ) !== 0
    )
  }
}

function selectChunk(chunks, offset) {
  for (let i = 0; i < chunks.length; i++) {
    const next = chunks[i]

    const start = next.offset
    const end = next.offset + next.field.byteLength

    if (offset >= start && offset + 16 <= end) {
      return next
    }
  }

  return null
}

class SparseIndex extends Index {
  constructor(chunks, byteLength) {
    super(byteLength)
    this.chunks = chunks

    binding.quickbit_napi_index_init_sparse(
      this.handle,
      this.chunks.map(toBufferChunk)
    )
  }

  get byteLength() {
    if (this._byteLength !== -1) return this._byteLength
    const last = this.chunks[this.chunks.length - 1]
    return last ? last.offset + last.field.byteLength : 0
  }

  update(bit) {
    const n = this.byteLength * 8

    if (bit < 0) bit += n
    if (bit < 0 || bit >= n) return false

    const j = Math.floor(bit / 128)

    const offset = j * 16

    const chunk = selectChunk(this.chunks, offset)

    if (chunk === null) return false

    return (
      binding.quickbit_napi_index_update_sparse(
        this.handle,
        toBuffer(chunk.field),
        chunk.offset,
        bit
      ) !== 0
    )
  }
}
{
  "name": "quickbit-native",
  "version": "2.4.8",
  "description": "libquickbit JavaScript bindings for Node.js",
  "main": "index.js",
  "files": [
    "index.js",
    "macros.h",
    "binding.cc",
    "binding.js",
    "CMakeLists.txt",
    "prebuilds"
  ],
  "addon": true,
  "scripts": {
    "test": "npm run lint && npm run test:bare && npm run test:node",
    "test:bare": "bare test.mjs",
    "test:node": "node test.mjs",
    "lint": "prettier . --check"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/quickbit-native.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/quickbit-native/issues"
  },
  "homepage": "https://github.com/holepunchto/quickbit-native#readme",
  "dependencies": {
    "require-addon": "^1.1.0"
  },
  "devDependencies": {
    "b4a": "^1.6.7",
    "bare-compat-napi": "^1.3.2",
    "brittle": "^3.1.0",
    "cmake-bare": "^1.1.7",
    "cmake-fetch": "^1.1.0",
    "cmake-napi": "^1.0.2",
    "prettier": "^3.5.3",
    "prettier-config-standard": "^7.0.0"
  }
}
const simdle = require('simdle-universal')

const INDEX_LEN = (16 /* root */ + 128 * 16 /* children */) * 2

const get = exports.get = function get (field, bit) {
  const n = field.byteLength * 8

  if (bit < 0) bit += n
  if (bit < 0 || bit >= n) return false

  const m = field.BYTES_PER_ELEMENT * 8

  const offset = bit & (m - 1)
  const i = (bit - offset) / m

  return (field[i] & (1 << offset)) !== 0
}

const set = exports.set = function set (field, bit, value = true) {
  const n = field.byteLength * 8

  if (bit < 0) bit += n
  if (bit < 0 || bit >= n) return false

  const m = field.BYTES_PER_ELEMENT * 8

  const offset = bit & (m - 1)
  const i = (bit - offset) / m
  const mask = 1 << offset

  if (value) {
    if ((field[i] & mask) !== 0) return false
  } else {
    if ((field[i] & mask) === 0) return false
  }

  field[i] ^= mask

  return true
}

exports.fill = function fill (field, value, start = 0, end = field.byteLength * 8) {
  const n = field.byteLength * 8

  if (start < 0) start += n
  if (end < 0) end += n
  if (start < 0 || start >= field.byteLength * 8 || start >= end) return field

  const m = field.BYTES_PER_ELEMENT * 8

  let i, j

  {
    const offset = start & (m - 1)
    i = (start - offset) / m

    if (offset !== 0) {
      let shift = m - offset
      if (end - start < shift) shift = end - start

      const mask = ((1 << shift) - 1) << offset

      if (value) field[i] |= mask
      else field[i] &= ~mask

      i++
    }
  }

  {
    const offset = end & (m - 1)
    j = (end - offset) / m

    if (offset !== 0 && j >= i) {
      const mask = (1 << offset) - 1

      if (value) field[j] |= mask
      else field[j] &= ~mask
    }
  }

  if (i < j) field.fill(value ? (2 ** m) - 1 : 0, i, j)

  return field
}

exports.clear = function clear (field, ...chunks) {
  const n = field.byteLength

  for (const chunk of chunks) {
    if (chunk.offset >= n) continue

    const m = chunk.field.byteLength

    let i = chunk.offset
    let j = 0

    while (((i & 15) !== 0 || (j & 15) !== 0) && i < n && j < m) {
      field[i] = field[i] & ~chunk.field[j]
      i++
      j++
    }

    if (i + 15 < n && j + 15 < m) {
      const len = Math.min(n - (n & 15) - i, m - (m & 15) - j)

      simdle.clear(field.subarray(i, i + len), chunk.field.subarray(j, j + len), field.subarray(i, i + len))
    }

    while (i < n && j < m) {
      field[i] = field[i] & ~chunk.field[j]
      i++
      j++
    }
  }
}

function bitOffset (bit, offset) {
  return !bit ? offset : (INDEX_LEN * 8 / 2) + offset
}

function byteOffset (bit, offset) {
  return !bit ? offset : (INDEX_LEN / 2) + offset
}

exports.findFirst = function findFirst (field, value, position = 0) {
  const n = field.byteLength * 8

  if (position < 0) position += n
  if (position < 0) position = 0
  if (position >= n) return -1

  value = !!value

  for (let i = position; i < n; i++) {
    if (get(field, i) === value) return i
  }

  return -1
}

exports.findLast = function findLast (field, value, position = field.byteLength * 8 - 1) {
  const n = field.byteLength * 8

  if (position < 0) position += n
  if (position < 0) return -1
  if (position >= n) position = n - 1

  value = !!value

  for (let i = position; i >= 0; i--) {
    if (get(field, i) === value) return i
  }

  return -1
}

const Index = exports.Index = class Index {
  static from (fieldOrChunks, byteLength = -1) {
    if (Array.isArray(fieldOrChunks)) {
      return new SparseIndex(fieldOrChunks, byteLength)
    } else {
      return new DenseIndex(fieldOrChunks, byteLength)
    }
  }

  constructor (byteLength) {
    this._byteLength = byteLength
    this.handle = new Uint32Array(INDEX_LEN / 4)
  }

  get byteLength () {
    return this._byteLength
  }

  skipFirst (value, position = 0) {
    const n = this.byteLength * 8

    if (position < 0) position += n
    if (position < 0) position = 0
    if (position >= n) return n - 1

    let i = Math.floor(position / 16384)

    if (i > 127) return position

    while (i <= 127 && get(this.handle, bitOffset(value, i))) {
      i++
    }

    if (i === 128) return n - 1

    let k = i * 16384
    let j = 0

    if (position > k) j = Math.floor((position - k) / 128)

    while (j <= 127 && get(this.handle, bitOffset(value, i * 128 + j + 128))) {
      j++
      k += 128
    }

    if (j === 128 && i !== 127) return this.skipFirst(value, (i + 1) * 16384)

    if (k > position) position = k

    return position < n ? position : n - 1
  }

  skipLast (value, position = this.byteLength * 8 - 1) {
    const n = this.byteLength * 8

    if (position < 0) position += n
    if (position < 0) return 0
    if (position >= n) position = n - 1

    let i = Math.floor(position / 16384)

    if (i > 127) return position

    while (i >= 0 && get(this.handle, bitOffset(value, i))) {
      i--
    }

    if (i === -1) return 0

    let k = ((i + 1) * 16384) - 1
    let j = 127

    if (position < k) j = 128 - Math.ceil((k - position) / 128)

    while (j >= 0 && get(this.handle, bitOffset(value, i * 128 + j + 128))) {
      j--
      k -= 128
    }

    if (j === -1 && i !== 0) return this.skipLast(value, i * 16384 - 1)

    if (k < position) position = k

    return position
  }
}

class DenseIndex extends Index {
  constructor (field, byteLength) {
    super(byteLength)
    this.field = field

    const m = field.BYTES_PER_ELEMENT

    for (let i = 0; i < 128; i++) {
      for (let j = 0; j < 128; j++) {
        const offset = (i * 128 + j) * 16
        let allz = true
        let allo = false

        if (offset + 16 <= this.field.byteLength) {
          const vec = this.field.subarray(offset / m, (offset + 16) / m)

          allz = simdle.allz(vec)
          allo = simdle.allo(vec)
        }

        const k = i * 128 + 128 + j

        set(this.handle, bitOffset(false, k), allz)
        set(this.handle, bitOffset(true, k), allo)
      }

      {
        const offset = byteOffset(false, i * 16 + 16) / 4
        const allo = simdle.allo(this.handle.subarray(offset, offset + 4))

        set(this.handle, bitOffset(false, i), allo)
      }

      {
        const offset = byteOffset(true, i * 16 + 16) / 4
        const allo = simdle.allo(this.handle.subarray(offset, offset + 4))

        set(this.handle, bitOffset(true, i), allo)
      }
    }
  }

  get byteLength () {
    if (this._byteLength !== -1) return this._byteLength
    return this.field.byteLength
  }

  update (bit) {
    const n = this.byteLength * 8

    if (bit < 0) bit += n
    if (bit < 0 || bit >= n) return false

    const m = this.field.BYTES_PER_ELEMENT

    const i = Math.floor(bit / 16384)
    const j = Math.floor(bit / 128)

    const offset = (j * 16) / m
    const vec = this.field.subarray(offset, offset + (16 / m))

    const allz = simdle.allz(vec)
    const allo = simdle.allo(vec)

    let changed = false

    if (set(this.handle, bitOffset(false, 128 + j), allz)) {
      changed = true

      const offset = byteOffset(false, i * 16 + 16) / 4
      const allo = simdle.allo(this.handle.subarray(offset, offset + 4))

      set(this.handle, bitOffset(false, i), allo)
    }

    if (set(this.handle, bitOffset(true, 128 + j), allo)) {
      changed = true

      const offset = byteOffset(true, i * 16 + 16) / 4
      const allo = simdle.allo(this.handle.subarray(offset, offset + 4))

      set(this.handle, bitOffset(true, i), allo)
    }

    return changed
  }
}

function selectChunk (chunks, offset) {
  for (let i = 0; i < chunks.length; i++) {
    const next = chunks[i]

    const start = next.offset
    const end = next.offset + next.field.byteLength

    if (offset >= start && offset + 16 <= end) {
      return next
    }
  }

  return null
}

class SparseIndex extends Index {
  constructor (chunks, byteLength) {
    super(byteLength)
    this.chunks = chunks

    for (let i = 0; i < 128; i++) {
      for (let j = 0; j < 128; j++) {
        const offset = (i * 128 + j) * 16
        let allz = true
        let allo = false

        const chunk = selectChunk(this.chunks, offset)

        if (chunk !== null) {
          const m = chunk.field.BYTES_PER_ELEMENT

          const vec = chunk.field.subarray((offset - chunk.offset) / m, (offset - chunk.offset + 16) / m)

          allz = simdle.allz(vec)
          allo = simdle.allo(vec)
        }

        const k = i * 128 + 128 + j

        set(this.handle, bitOffset(false, k), allz)
        set(this.handle, bitOffset(true, k), allo)
      }

      {
        const offset = byteOffset(false, i * 16 + 16) / 4
        const allo = simdle.allo(this.handle.subarray(offset, offset + 4))

        set(this.handle, bitOffset(false, i), allo)
      }

      {
        const offset = byteOffset(true, i * 16 + 16) / 4
        const allo = simdle.allo(this.handle.subarray(offset, offset + 4))

        set(this.handle, bitOffset(true, i), allo)
      }
    }
  }

  get byteLength () {
    if (this._byteLength !== -1) return this._byteLength
    const last = this.chunks[this.chunks.length - 1]
    return last ? last.offset + last.field.byteLength : 0
  }

  update (bit) {
    const n = this.byteLength * 8

    if (bit < 0) bit += n
    if (bit < 0 || bit >= n) return false

    const i = Math.floor(bit / 16384)
    const j = Math.floor(bit / 128)

    const offset = j * 16

    const chunk = selectChunk(this.chunks, offset)

    if (chunk === null) return false

    const m = chunk.field.BYTES_PER_ELEMENT

    const vec = chunk.field.subarray((offset - chunk.offset) / m, (offset - chunk.offset + 16) / m)

    const allz = simdle.allz(vec)
    const allo = simdle.allo(vec)

    let changed = false

    if (set(this.handle, bitOffset(false, 128 + j), allz)) {
      changed = true

      const offset = byteOffset(false, i * 16 + 16) / 4
      const allo = simdle.allo(this.handle.subarray(offset, offset + 4))

      set(this.handle, bitOffset(false, i), allo)
    }

    if (set(this.handle, bitOffset(true, 128 + j), allo)) {
      changed = true

      const offset = byteOffset(true, i * 16 + 16) / 4
      const allo = simdle.allo(this.handle.subarray(offset, offset + 4))

      set(this.handle, bitOffset(true, i), allo)
    }

    return changed
  }
}
const fallback = require('./fallback')

try {
  const native = require('quickbit-native')

  // These functions are faster in JavaScript
  exports.get = fallback.get
  exports.set = fallback.set
  exports.fill = fallback.fill

  // These functions are faster in C
  exports.clear = native.clear
  exports.findFirst = native.findFirst
  exports.findLast = native.findLast
  exports.Index = native.Index
} catch {
  module.exports = fallback
}
{
  "name": "quickbit-universal",
  "version": "2.2.0",
  "description": "Universal wrapper for libquickbit with a JavaScript fallback",
  "main": "index.js",
  "files": [
    "fallback.js",
    "index.js"
  ],
  "browser": {
    "./index.js": "./fallback.js"
  },
  "scripts": {
    "test": "standard && brittle test.mjs"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/quickbit-universal.git"
  },
  "author": "Kasper Isager Dalsgarð <kasper@funktionel.co>",
  "license": "ISC",
  "bugs": {
    "url": "https://github.com/holepunchto/quickbit-universal/issues"
  },
  "homepage": "https://github.com/holepunchto/quickbit-universal#readme",
  "dependencies": {
    "b4a": "^1.6.0",
    "simdle-universal": "^1.1.0"
  },
  "optionalDependencies": {
    "quickbit-native": "^2.2.0"
  },
  "devDependencies": {
    "brittle": "^3.1.0",
    "standard": "^17.0.0"
  }
}
module.exports = class RandomArrayIterator {
  constructor (values) {
    this.values = values
    this.start = 0
    this.length = this.values.length
  }

  next () {
    if (this.length === 0) {
      if (this.start === 0) return { done: true, value: undefined }
      this.length = this.start
      this.start = 0
    }

    const i = this.start + ((Math.random() * this.length) | 0)
    const j = this.start + --this.length
    const value = this.values[i]

    this.values[i] = this.values[j]
    this.values[j] = value

    return { done: false, value }
  }

  dequeue () {
    this.values[this.start + this.length] = this.values[this.values.length - 1]
    this.values.pop()
  }

  requeue () {
    const i = this.start + this.length
    const value = this.values[i]
    this.values[i] = this.values[this.start]
    this.values[this.start++] = value
  }

  restart () {
    this.start = 0
    this.length = this.values.length
    return this
  }

  [Symbol.iterator] () {
    return this
  }
}
{
  "name": "random-array-iterator",
  "version": "1.0.0",
  "description": "An iterator to iterate an array in random order with controls to requeue or dequeue elements during the iteration",
  "main": "index.js",
  "dependencies": {},
  "devDependencies": {
    "standard": "^16.0.3",
    "tape": "^5.0.1"
  },
  "scripts": {
    "test": "standard && tape test.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/mafintosh/random-array-iterator.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/mafintosh/random-array-iterator/issues"
  },
  "homepage": "https://github.com/mafintosh/random-array-iterator"
}
const rrp = require('resolve-reject-promise')

module.exports = class ReadyGuard {
  constructor() {
    const { resolve, reject, promise } = rrp()

    this.resolve = resolve
    this.reject = reject
    this.promise = promise

    this.entered = false
    this.opened = false
    this.destroyed = false
  }

  ready() {
    return this.promise
  }

  enter() {
    if (this.entered || this.destroyed) return false
    this.entered = true
    return true
  }

  exit() {
    this.opened = true
    this.resolve()
  }

  destroy(err) {
    this.destroyed = true
    if (!this.opened) this.reject(err || new Error('Ready guard destroyed'))
  }
}
{
  "name": "ready-guard",
  "version": "1.0.0",
  "description": "Simple signal to do composite resource lifecycles",
  "main": "index.js",
  "dependencies": {
    "resolve-reject-promise": "^1.1.0"
  },
  "scripts": {
    "format": "prettier --write .",
    "lint": "lunte && prettier --check .",
    "test": "node test.js",
    "test:bare": "bare test.js"
  },
  "files": [
    "index.js"
  ],
  "repository": {
    "type": "git",
    "url": "https://github.com/holepunchto/ready-guard.git"
  },
  "author": "Holepunch Inc.",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/ready-guard/issues"
  },
  "homepage": "https://github.com/holepunchto/ready-guard",
  "devDependencies": {
    "brittle": "^3.19.1",
    "lunte": "^1.6.0",
    "prettier": "^3.8.1",
    "prettier-config-holepunch": "^2.0.0"
  }
}
const EventEmitter = require('events')

module.exports = class ReadyResource extends EventEmitter {
  constructor () {
    super()

    this.opening = null
    this.closing = null

    this.opened = false
    this.closed = false
  }

  ready () {
    if (this.opening !== null) return this.opening
    this.opening = open(this)
    return this.opening
  }

  close () {
    if (this.closing !== null) return this.closing
    this.closing = close(this)
    return this.closing
  }

  async _open () {
    // add impl here
  }

  async _close () {
    // add impl here
  }
}

async function open (self) {
  // open after close
  if (self.closing !== null) return

  try {
    await self._open()
  } catch (err) {
    self.close() // safe to run in bg
    throw err
  }

  self.opened = true
  self.emit('ready')
}

async function close (self) {
  try {
    if (self.opened === false && self.opening !== null) await self.opening
  } catch {
    // ignore errors on closing
  }
  if (self.opened === true || self.opening === null) await self._close()
  self.closed = true
  self.emit('close')
}
{
  "name": "ready-resource",
  "version": "1.2.0",
  "description": "Modern single resource management",
  "main": "index.js",
  "imports": {
    "events": {
      "bare": "bare-events",
      "default": "events"
    }
  },
  "types": "index.d.ts",
  "files": [
    "index.js",
    "index.d.ts"
  ],
  "scripts": {
    "test": "standard && brittle test.js"
  },
  "devDependencies": {
    "brittle": "^3.1.0",
    "standard": "^17.0.0"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/ready-resource.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/holepunchto/ready-resource/issues"
  },
  "homepage": "https://github.com/holepunchto/ready-resource",
  "dependencies": {
    "bare-events": "^2.2.0"
  }
}
const b4a = require('b4a')

var EMPTY = []

module.exports = RecordCache

function RecordSet () {
  this.list = []
  this.map = new Map()
}

RecordSet.prototype.add = function (record, value) {
  var k = toString(record)
  var r = this.map.get(k)
  if (r) return false

  r = {index: this.list.length, record: value || record}
  this.list.push(r)
  this.map.set(k, r)
  return true
}

RecordSet.prototype.remove = function (record) {
  var k = toString(record)
  var r = this.map.get(k)
  if (!r) return false

  swap(this.list, r.index, this.list.length - 1)
  this.list.pop()
  this.map.delete(k)
  return true
}

function RecordStore () {
  this.records = new Map()
  this.size = 0
}

RecordStore.prototype.add = function (name, record, value) {
  var r = this.records.get(name)

  if (!r) {
    r = new RecordSet()
    this.records.set(name, r)
  }

  if (r.add(record, value)) {
    this.size++
    return true
  }

  return false
}

RecordStore.prototype.remove = function (name, record, value) {
  var r = this.records.get(name)
  if (!r) return false

  if (r.remove(record, value)) {
    this.size--
    if (!r.map.size) this.records.delete(name)
    return true
  }

  return false
}

RecordStore.prototype.get = function (name) {
  var r = this.records.get(name)
  return r ? r.list : EMPTY
}

function RecordCache (opts) {
  if (!(this instanceof RecordCache)) return new RecordCache(opts)
  if (!opts) opts = {}

  this.maxSize = opts.maxSize || Infinity
  this.maxAge = opts.maxAge || 0

  this._onstale = opts.onStale || opts.onstale || null
  this._fresh = new RecordStore()
  this._stale = new RecordStore()
  this._interval = null
  this._gced = false

  if (this.maxAge && this.maxAge < Infinity) {
    // 2/3 gives us a span of 0.66-1.33 maxAge or avg maxAge
    var tick = Math.ceil(2 / 3 * this.maxAge)
    this._interval = setInterval(this._gcAuto.bind(this), tick)
    if (this._interval.unref) this._interval.unref()
  }
}

Object.defineProperty(RecordCache.prototype, 'size', {
  get: function () {
    return this._fresh.size + this._stale.size
  }
})

RecordCache.prototype.add = function (name, record, value) {
  this._stale.remove(name, record, value)
  if (this._fresh.add(name, record, value) && this._fresh.size > this.maxSize) {
    this._gc()
  }
}

RecordCache.prototype.remove = function (name, record, value) {
  this._fresh.remove(name, record, value)
  this._stale.remove(name, record, value)
}

RecordCache.prototype.get = function (name, n) {
  var a = this._fresh.get(name)
  var b = this._stale.get(name)
  var aLen = a.length
  var bLen = b.length
  var len = aLen + bLen

  if (n > len || !n) n = len
  var result = new Array(n)

  for (var i = 0; i < n; i++) {
    var j = Math.floor(Math.random() * (aLen + bLen))
    if (j < aLen) {
      result[i] = a[j].record
      swap(a, j, --aLen)
    } else {
      j -= aLen
      result[i] = b[j].record
      swap(b, j, --bLen)
    }
  }

  return result
}

RecordCache.prototype._gcAuto = function () {
  if (!this._gced) this._gc()
  this._gced = false
}

RecordCache.prototype._gc = function () {
  if (this._onstale && this._stale.size > 0) this._onstale(this._stale)
  this._stale = this._fresh
  this._fresh = new RecordStore()
  this._gced = true
}

RecordCache.prototype.clear = function () {
  this._gc()
  this._gc()
}

RecordCache.prototype.destroy = function () {
  this.clear()
  clearInterval(this._interval)
  this._interval = null
}

function toString (record) {
  return b4a.isBuffer(record) ? b4a.toString(record, 'hex') : record
}

function swap (list, a, b) {
  var tmp = list[a]
  tmp.index = b
  list[b].index = a
  list[a] = list[b]
  list[b] = tmp
}
{
  "name": "record-cache",
  "version": "1.2.0",
  "description": "Cache optimised for record like things",
  "main": "index.js",
  "dependencies": {
    "b4a": "^1.3.1"
  },
  "devDependencies": {
    "standard": "^10.0.3",
    "tape": "^4.8.0"
  },
  "scripts": {
    "test": "standard && tape test.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/mafintosh/record-cache.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/mafintosh/record-cache/issues"
  },
  "homepage": "https://github.com/mafintosh/record-cache"
}
module.exports = class RefCounter {
  constructor() {
    this.count = 0

    this._onidle = null
    this._idle = null
  }

  isIdle() {
    return this.count === 0
  }

  idle() {
    if (this.count === 0) return Promise.resolve()
    if (this._idle !== null) return this._idle

    this._idle = new Promise((resolve) => {
      this._onidle = resolve
    })

    return this._idle
  }

  inc() {
    this.count++
  }

  dec() {
    if (--this.count > 0) return

    if (this._onidle !== null) {
      const resolve = this._onidle
      this._idle = null
      this._onidle = null
      resolve()
    }
  }
}
{
  "name": "refcounter",
  "version": "1.0.0",
  "description": "Simple refcounter",
  "main": "index.js",
  "dependencies": {},
  "devDependencies": {},
  "repository": {
    "type": "git",
    "url": "https://github.com/holepunchto/refcounter.git"
  },
  "author": "Holepunch Inc",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/refcounter/issues"
  },
  "homepage": "https://github.com/holepunchto/refcounter"
}
module.exports = require.addon.bind(require)
{
  "name": "require-addon",
  "version": "1.2.0",
  "description": "Import native addons across JavaScript runtimes",
  "exports": {
    "./package": "./package.json",
    ".": {
      "bare": "./lib/bare.js",
      "node": "./lib/node.js",
      "default": "./lib/default.js"
    }
  },
  "imports": {
    "fs": {
      "bare": "bare-fs",
      "default": "fs"
    },
    "path": {
      "bare": "bare-path",
      "default": "path"
    },
    "url": {
      "bare": "bare-url",
      "default": "url"
    }
  },
  "files": [
    "lib"
  ],
  "scripts": {
    "test": "npm run lint && npm run test:bare && npm run test:node",
    "test:bare": "bare test.js",
    "test:node": "node test.js",
    "lint": "prettier --check .",
    "format": "prettier --write ."
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/require-addon.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/require-addon/issues"
  },
  "homepage": "https://github.com/holepunchto/require-addon#readme",
  "engines": {
    "bare": ">=1.10.0"
  },
  "dependencies": {
    "bare-addon-resolve": "^1.3.0"
  },
  "devDependencies": {
    "bare-bundle": "^1.8.1",
    "bare-bundle-evaluate": "^1.1.0",
    "bare-fs": "^4.0.0",
    "bare-path": "^3.0.0",
    "bare-url": "^2.1.0",
    "brittle": "^3.7.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^2.0.0"
  }
}
let tmpResolve = null
let tmpReject = null

if (Promise.withResolvers) {
  module.exports = Promise.withResolvers.bind(Promise)
} else {
  module.exports = function resolveRejectPromise () {
    const promise = new Promise(setTmp)
    const result = { promise, resolve: tmpResolve, reject: tmpReject }
    tmpResolve = tmpReject = null
    return result
  }
}

function setTmp (resolve, reject) {
  tmpResolve = resolve
  tmpReject = reject
}
{
  "name": "resolve-reject-promise",
  "version": "1.1.0",
  "description": "Create an inverted promise with no function allocs",
  "main": "index.js",
  "scripts": {
    "test": "standard"
  },
  "devDependencies": {
    "standard": "^17.1.2"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/mafintosh/resolve-reject-promise.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/mafintosh/resolve-reject-promise/issues"
  },
  "homepage": "https://github.com/mafintosh/resolve-reject-promise"
}
const resources = new Map()

exports.add = function(resource, teardown) {
  resources.set(resource, teardown)
}

exports.remove = function(resource) {
  resources.delete(resource)
}

if (global.Bare) global.Bare.on('exit', run)
else global.process.on('exit', run)

function run() {
  for (const [resource, teardown] of resources) {
    teardown(resource)
  }
}
{
  "name": "resource-on-exit",
  "version": "1.0.0",
  "description": "Register sync teardown handlers for a resource",
  "main": "index.js",
  "dependencies": {},
  "devDependencies": {},
  "repository": {
    "type": "git",
    "url": "https://github.com/holepunchto/bare-teardown.git"
  },
  "author": "Holepunch Inc.",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-teardown/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-teardown"
}
require.addon = require('require-addon')

module.exports = require.addon('.', __filename)
const ColumnFamily = require('./lib/column-family')
const Iterator = require('./lib/iterator')
const Snapshot = require('./lib/snapshot')
const State = require('./lib/state')
const { BloomFilterPolicy, RibbonFilterPolicy } = require('./lib/filter-policy')
const constants = require('./lib/constants')

class RocksDB {
  constructor(path, opts = {}) {
    const {
      columnFamily,
      state = new State(this, path, opts),
      snapshot = null,
      keyEncoding = null,
      valueEncoding = null
    } = opts

    this._state = state
    this._snapshot = snapshot
    this._columnFamily = state.getColumnFamily(columnFamily)
    this._keyEncoding = keyEncoding
    this._valueEncoding = valueEncoding
    this._index = -1

    this._state.addSession(this)
  }

  get opened() {
    return this._state.opened
  }

  get closed() {
    return this.isRoot() ? this._state.closed : this._index === -1
  }

  get path() {
    return this._state.path
  }

  get stats() {
    return this._state.stats
  }

  get snapshotted() {
    return this._snapshot !== null
  }

  get defaultColumnFamily() {
    return this._columnFamily
  }

  session({
    columnFamily = this._columnFamily,
    snapshot = this._snapshot !== null,
    keyEncoding = this._keyEncoding,
    valueEncoding = this._valueEncoding
  } = {}) {
    maybeClosed(this)

    return new RocksDB(null, {
      state: this._state,
      columnFamily,
      snapshot: snapshot ? this._snapshot || new Snapshot(this._state) : null,
      keyEncoding,
      valueEncoding
    })
  }

  columnFamily(name, opts) {
    return this.session({ ...opts, columnFamily: name })
  }

  snapshot() {
    return this.session({ snapshot: true })
  }

  isRoot() {
    return this === this._state.db
  }

  ready() {
    return this._state.ready()
  }

  async close({ force } = {}) {
    if (!this._state.opened) await this._state.ready()

    if (this._index !== -1) this._state.removeSession(this)

    if (force) {
      while (this._state.sessions.length > 0) {
        await this._state.sessions[this._state.sessions.length - 1].close()
      }
    }

    return this.isRoot() ? this._state.close() : Promise.resolve()
  }

  suspend() {
    maybeClosed(this)

    return this._state.suspend()
  }

  resume() {
    maybeClosed(this)

    return this._state.resume()
  }

  isIdle() {
    return this._state.handles.isIdle()
  }

  idle() {
    return this._state.handles.idle()
  }

  iterator(range, opts) {
    maybeClosed(this)

    return new Iterator(this, { ...range, ...opts })
  }

  async *keys(range, opts) {
    for await (const { key } of this.iterator(range, {
      ...opts,
      values: false
    })) {
      yield key
    }
  }

  async peek(range, opts) {
    for await (const value of this.iterator({ ...range, ...opts, limit: 1 })) {
      return value
    }

    return null
  }

  read(opts) {
    maybeClosed(this)

    return this._state.createReadBatch(this, opts)
  }

  write(opts) {
    maybeClosed(this)

    return this._state.createWriteBatch(this, opts)
  }

  flush(opts) {
    maybeClosed(this)

    return this._state.flush(this, opts)
  }

  async get(key, opts) {
    const batch = this.read({ ...opts, capacity: 1, autoDestroy: true })
    const value = batch.get(key)
    batch.tryFlush()
    return value
  }

  async put(key, value, opts) {
    const batch = this.write({ ...opts, capacity: 1, autoDestroy: true })
    batch.tryPut(key, value)
    await batch.flush()
  }

  async delete(key, opts) {
    const batch = this.write({ ...opts, capacity: 1, autoDestroy: true })
    batch.tryDelete(key)
    await batch.flush()
  }

  async deleteRange(start, end, opts) {
    const batch = this.write({ ...opts, capacity: 1, autoDestroy: true })
    batch.tryDeleteRange(start, end)
    await batch.flush()
  }

  async compact(opts = {}) {
    maybeClosed(this)

    await this._state.compact(this, opts)
  }

  async compactRange(start = null, end = null, opts = {}) {
    if (typeof end === 'object' && end !== null) {
      opts = end
      end = null
    } else if (typeof start === 'object' && start !== null) {
      opts = start
      start = null
      end = null
    }

    await this._state.compactRange(this, start, end, opts)
  }

  async approximateSize(start, end, opts = {}) {
    return this._state.approximateSize(this, start, end, opts)
  }

  async currentWalFile() {
    maybeClosed(this)

    return this._state.currentWalFile()
  }

  async getStatsLevel() {
    maybeClosed(this)

    return this._state.getStatsLevel()
  }

  async setStatsLevel(level) {
    maybeClosed(this)

    await this._state.setStatsLevel(level)
  }

  async getProperty(name) {
    maybeClosed(this)

    return this._state.getProperty(name)
  }

  _ref() {
    if (this._snapshot) this._snapshot.ref()
    this._state.handles.inc()
  }

  _unref() {
    if (this._snapshot) this._snapshot.unref()
    this._state.handles.dec()
  }

  diagnostics() {
    return this._state.diagnostics()
  }
}

module.exports = exports = RocksDB

exports.constants = constants

exports.ColumnFamily = ColumnFamily
exports.BloomFilterPolicy = BloomFilterPolicy
exports.RibbonFilterPolicy = RibbonFilterPolicy

function maybeClosed(db) {
  if (db._state.closing || db._index === -1) throw new Error('RocksDB session is closed')
}
const c = require('compact-encoding')
const binding = require('../binding')

const empty = Buffer.alloc(0)
const resolved = Promise.resolve()

class RocksDBBatch {
  constructor(db, opts = {}) {
    const { capacity = 8, autoDestroy = false } = opts

    db._ref()

    this._db = db
    this._destroyed = false
    this._capacity = capacity
    this._operations = []
    this._promises = []

    this._enqueuePromise = this._enqueuePromise.bind(this)

    this._request = null
    this._resolve = null
    this._reject = null

    this._handle = null
    this._buffer = null
    this._autoDestroy = autoDestroy
    this._stats = null

    this._resetStats()

    if (db._state.opened === true) this.ready()
  }

  _reuse(db, opts = {}) {
    const { autoDestroy = false } = opts

    db._ref()

    this._db = db
    this._destroyed = false
    this._autoDestroy = autoDestroy
  }

  _onfinished(err) {
    const resolve = this._resolve
    const reject = this._reject

    if (this._request) this._db._state.io.dec()

    this._operations = []
    this._promises = []
    this._request = null
    this._resolve = null
    this._reject = null

    this._resetStats()

    if (this._autoDestroy === true) this.destroy()

    if (reject !== null && err) reject(err)
    else if (resolve !== null) resolve()
  }

  _resize() {
    if (this._operations.length <= this._capacity) return false

    while (this._operations.length > this._capacity) {
      this._capacity *= 2
    }

    return true
  }

  async ready() {
    if (this._handle !== null) return

    if (this._db._state.opened === false) await this._db._state.ready()

    this._init()
  }

  destroy() {
    if (this._request) throw new Error('Request in progress')
    if (this._destroyed) return

    this._destroyed = true

    if (this._promises.length) this._abort()

    this._db._unref()
    this._onfree()
  }

  _onfree() {
    this._resetStats()
    this._db._state.freeBatch(this, false)
    this._db = null
  }

  _abort() {
    for (let i = 0; i < this._promises.length; i++) {
      const promise = this._promises[i]
      if (promise !== null) promise.reject(new Error('Batch is destroyed'))
    }

    this._onfinished(new Error('Batch is destroyed'))
  }

  _resetStats() {}

  async flush() {
    if (this._request) throw new Error('Request in progress')
    if (this._destroyed) throw new Error('Batch is destroyed')

    this._request = new Promise((resolve, reject) => {
      this._resolve = resolve
      this._reject = reject
    })

    this._flush()

    return this._request
  }

  tryFlush() {
    if (this._request) throw new Error('Request in progress')
    if (this._destroyed) throw new Error('Batch is destroyed')

    this._request = resolved

    this._flush()
  }

  async _flush() {
    if (this._handle === null) await this.ready()

    this._db._state.io.inc()

    if (this._db._state.resumed !== null) {
      const resumed = await this._db._state.waitForResume()

      if (!resumed) {
        if (this._destroyed) {
          this._db._state.io.dec()
        } else {
          this._destroyed = true
          this._abort()
          this._db._unref()
        }
      }
    }
  }

  _enqueuePromise(resolve, reject) {
    this._promises.push({ resolve, reject })
  }

  _encodeKey(k) {
    if (this._db._keyEncoding) return c.encode(this._db._keyEncoding, k)
    if (typeof k === 'string') return Buffer.from(k)
    return k
  }

  _encodeValue(v) {
    if (this._db._valueEncoding) return c.encode(this._db._valueEncoding, v)
    if (v === null) return empty
    if (typeof v === 'string') return Buffer.from(v)
    return v
  }

  _decodeValue(b) {
    if (this._db._valueEncoding) return c.decode(this._db._valueEncoding, b)
    return b
  }
}

exports.ReadBatch = class RocksDBReadBatch extends RocksDBBatch {
  constructor(db, opts = {}) {
    super(db, opts)

    const { asyncIO = false, fillCache = true } = opts

    this._asyncIO = asyncIO
    this._fillCache = fillCache
  }

  _init() {
    this._handle = binding.readInit()
    this._buffer = binding.readBuffer(this._handle, this._capacity)
  }

  _resize() {
    if (super._resize() && this._handle !== null) {
      this._buffer = binding.readBuffer(this._handle, this._capacity)
    }
  }

  async _flush() {
    await super._flush()

    if (this._destroyed) return

    try {
      binding.read(
        this._db._state._handle,
        this._handle,
        this._operations,
        this._db._snapshot ? this._db._snapshot._handle : undefined,
        this._asyncIO,
        this._fillCache,
        this,
        this._onread
      )
    } catch (err) {
      this._db._state.io.dec()
      throw err
    }

    this._db._state.stats.gets += this._stats.gets
    this._db._state.stats.readBatches++
  }

  _onread(errs, values) {
    let applied = true

    for (let i = 0, n = this._promises.length; i < n; i++) {
      const err = errs[i]
      if (err) applied = false

      const promise = this._promises[i]
      if (promise === null) continue

      if (err) promise.reject(err)
      else promise.resolve(values[i] ? this._decodeValue(Buffer.from(values[i])) : null)
    }

    this._onfinished(applied ? null : new AggregateError(errs, 'Batch was not applied'))
  }

  _resetStats() {
    this._stats = { gets: 0 }
  }

  get(key) {
    if (this._request) throw new Error('Request already in progress')
    this._stats.gets++

    const promise = new Promise(this._enqueuePromise)

    this._operations.push(new RocksDBGet(this._encodeKey(key), this._db._columnFamily))

    this._resize()

    return promise
  }
}

exports.WriteBatch = class RocksDBWriteBatch extends RocksDBBatch {
  _init() {
    this._handle = binding.writeInit()
    this._buffer = binding.writeBuffer(this._handle, this._capacity)
  }

  _resize() {
    if (super._resize() && this._handle !== null) {
      this._buffer = binding.writeBuffer(this._handle, this._capacity)
    }
  }

  _onfree() {
    this._resetStats()
    this._db._state.freeBatch(this, true)
    this._db = null
  }

  async _flush() {
    await super._flush()

    if (this._destroyed) return

    try {
      binding.write(this._db._state._handle, this._handle, this._operations, this, this._onwrite)
    } catch (err) {
      this._db._state.io.dec()
      throw err
    }

    this._db._state.stats.puts += this._stats.puts
    this._db._state.stats.deletes += this._stats.deletes
    this._db._state.stats.rangeDeletes += this._stats.rangeDeletes
    this._db._state.stats.writeBatches++
  }

  _onwrite(err) {
    const applied = !err

    for (let i = 0, n = this._promises.length; i < n; i++) {
      const promise = this._promises[i]
      if (promise === null) continue

      if (err) promise.reject(err)
      else promise.resolve()
    }

    this._onfinished(applied ? null : new Error('Batch was not applied', { cause: err }))
  }

  _resetStats() {
    this._stats = { puts: 0, deletes: 0, rangeDeletes: 0 }
  }

  put(key, value) {
    if (this._request) throw new Error('Request already in progress')
    this._stats.puts++

    const promise = new Promise(this._enqueuePromise)

    this._operations.push(
      new RocksDBPut(this._encodeKey(key), this._encodeValue(value), this._db._columnFamily)
    )

    this._resize()

    return promise
  }

  tryPut(key, value) {
    if (this._request) throw new Error('Request already in progress')
    this._stats.puts++

    this._operations.push(
      new RocksDBPut(this._encodeKey(key), this._encodeValue(value), this._db._columnFamily)
    )

    this._promises.push(null)

    this._resize()
  }

  delete(key) {
    if (this._request) throw new Error('Request already in progress')
    this._stats.deletes++

    const promise = new Promise(this._enqueuePromise)

    this._operations.push(new RocksDBDelete(this._encodeKey(key), this._db._columnFamily))

    this._resize()

    return promise
  }

  tryDelete(key) {
    if (this._request) throw new Error('Request already in progress')
    this._stats.deletes++

    this._operations.push(new RocksDBDelete(this._encodeKey(key), this._db._columnFamily))

    this._promises.push(null)

    this._resize()
  }

  deleteRange(start, end) {
    if (this._request) throw new Error('Request already in progress')
    this._stats.rangeDeletes++

    const promise = new Promise(this._enqueuePromise)

    this._operations.push(
      new RocksDBDeleteRange(this._encodeKey(start), this._encodeKey(end), this._db._columnFamily)
    )

    this._resize()

    return promise
  }

  tryDeleteRange(start, end) {
    if (this._request) throw new Error('Request already in progress')
    this._stats.rangeDeletes++

    this._operations.push(
      new RocksDBDeleteRange(this._encodeKey(start), this._encodeKey(end), this._db._columnFamily)
    )

    this._promises.push(null)

    this._resize()
  }
}

class RocksDBGet {
  constructor(key, columnFamily) {
    this.key = key
    this.columnFamily = columnFamily._handle
  }

  get type() {
    return binding.GET
  }
}

class RocksDBPut {
  constructor(key, value, columnFamily) {
    this.key = key
    this.value = value
    this.columnFamily = columnFamily._handle
  }

  get type() {
    return binding.PUT
  }
}

class RocksDBDelete {
  constructor(key, columnFamily) {
    this.key = key
    this.columnFamily = columnFamily._handle
  }

  get type() {
    return binding.DELETE
  }
}

class RocksDBDeleteRange {
  constructor(start, end, columnFamily) {
    this.start = start
    this.end = end
    this.columnFamily = columnFamily._handle
  }

  get type() {
    return binding.DELETE_RANGE
  }
}
const binding = require('../binding')
const constants = require('./constants')
const { BloomFilterPolicy } = require('./filter-policy')

class RocksDBColumnFamily {
  constructor(name, opts = {}) {
    const {
      // Blob options
      enableBlobFiles = false,
      minBlobSize = 0,
      blobFileSize = 0,
      enableBlobGarbageCollection = true,
      // Block table options
      tableBlockSize = 8192,
      tableCacheIndexAndFilterBlocks = true,
      tableFormatVersion = 6,
      optimizeFiltersForMemory = false,
      blockCache = true,
      filterPolicy = new BloomFilterPolicy(10),
      topLevelIndexPinningTier = constants.pinningTier.ALL,
      partitionPinningTier = constants.pinningTier.ALL,
      unpartitionedPinningTier = constants.pinningTier.ALL,
      optimizeFiltersForHits = false,
      numLevels = 7,
      maxWriteBufferNumber = 2,
      blobGarbageCollectionAgeCutOff = 0.25,
      blobGarbageCollectionForceThreshold = 1.0
    } = opts

    this._name = name
    this._flushing = null
    this._options = {
      enableBlobFiles,
      minBlobSize,
      blobFileSize,
      enableBlobGarbageCollection,
      tableBlockSize,
      tableCacheIndexAndFilterBlocks,
      tableFormatVersion,
      optimizeFiltersForMemory,
      blockCache,
      filterPolicy,
      topLevelIndexPinningTier,
      partitionPinningTier,
      unpartitionedPinningTier,
      optimizeFiltersForHits,
      numLevels,
      maxWriteBufferNumber,
      blobGarbageCollectionAgeCutOff,
      blobGarbageCollectionForceThreshold
    }

    const filterPolicyArguments = [0, 0, 0]

    if (filterPolicy !== null) {
      filterPolicyArguments[0] = filterPolicy.type

      switch (filterPolicy.type) {
        case 1: // Bloom filter policy
          filterPolicyArguments[1] = filterPolicy.bitsPerKey
          break
        case 2: // Ribbon filter policy
          filterPolicyArguments[1] = filterPolicy.bloomEquivalentBitsPerKey
          filterPolicyArguments[2] = filterPolicy.bloomBeforeLevel

          break
      }
    }

    this._handle = binding.columnFamilyInit(
      name,
      enableBlobFiles,
      minBlobSize,
      blobFileSize,
      enableBlobGarbageCollection,
      tableBlockSize,
      tableCacheIndexAndFilterBlocks,
      tableFormatVersion,
      optimizeFiltersForMemory,
      blockCache === false,
      ...filterPolicyArguments,
      topLevelIndexPinningTier,
      partitionPinningTier,
      unpartitionedPinningTier,
      optimizeFiltersForHits,
      numLevels,
      maxWriteBufferNumber,
      blobGarbageCollectionAgeCutOff,
      blobGarbageCollectionForceThreshold
    )
  }

  cloneSettings(name) {
    return new RocksDBColumnFamily(name, this._options)
  }

  get name() {
    return this._name
  }

  destroy() {
    if (this._handle === null) return

    binding.columnFamilyDestroy(this._handle)

    this._handle = null
  }
}

module.exports = RocksDBColumnFamily
module.exports = {
  pinningTier: {
    NONE: 0,
    FLUSHED_AND_SIMILAR: 1,
    ALL: 2
  },
  garbageCollectionPolicy: {
    DEFAULT: 0,
    FORCE: 1,
    DISABLE: 2
  },
  bottommostLevelCompaction: {
    NONE: 0,
    SKIP: 1,
    FORCE: 2
  },
  walRecoveryMode: {
    TOLERATE_CORRUPTED_TAIL_RECORDS: 0,
    ABSOLUTE_CONSISTENCY: 1,
    POINT_IN_TIME: 2,
    SKIP_ANY_CORRUPTED_RECORDS: 3
  },
  statsLevel: {
    DISABLE_ALL: 0,
    EXCEPT_HISTOGRAM_OR_TIMERS: 1,
    EXCEPT_TIMERS: 2,
    EXCEPT_DETAILED_TIMERS: 3,
    EXCEPT_TIME_FOR_MUTEX: 4,
    ALL: 5
  },
  walFileType: {
    ARCHIVED: 0,
    ALIVE: 1
  }
}
exports.BloomFilterPolicy = class RocksDBBloomFilterPolicy {
  get type() {
    return 1
  }

  constructor(bitsPerKey) {
    this.bitsPerKey = bitsPerKey
  }
}

exports.RibbonFilterPolicy = class RocksDBRibbonFilterPolicy {
  get type() {
    return 2
  }

  constructor(bloomEquivalentBitsPerKey, bloomBeforeLevel = 0) {
    this.bloomEquivalentBitsPerKey = bloomEquivalentBitsPerKey
    this.bloomBeforeLevel = bloomBeforeLevel
  }
}
const { Readable } = require('streamx')
const c = require('compact-encoding')
const binding = require('../binding')

const empty = Buffer.alloc(0)

module.exports = class RocksDBIterator extends Readable {
  constructor(db, opts = {}) {
    const {
      gt = null,
      gte = null,
      lt = null,
      lte = null,
      reverse = false,
      values = true,
      limit = Infinity,
      capacity = 8
    } = opts

    super()

    db._ref()

    this._db = db

    this._gt = gt ? this._encodeKey(gt) : empty
    this._gte = gte ? this._encodeKey(gte) : empty
    this._lt = lt ? this._encodeKey(lt) : empty
    this._lte = lte ? this._encodeKey(lte) : empty

    this._reverse = reverse
    this._values = values
    this._limit = limit < 0 ? Infinity : limit
    this._capacity = capacity
    this._opened = false

    this._pendingOpen = null
    this._pendingRead = null
    this._pendingDestroy = null

    this._buffer = null
    this._handle = null

    if (this._db._state.opened === true) this.ready()
  }

  _onopen(err) {
    const cb = this._pendingOpen
    this._pendingOpen = null
    this._opened = true
    this._db._state.io.dec()
    cb(err)
  }

  _onread(err, keys, values) {
    const cb = this._pendingRead
    this._pendingRead = null
    this._db._state.io.dec()
    if (err) return cb(err)

    const n = keys.length

    this._limit -= n

    for (let i = 0; i < n; i++) {
      this.push({
        key: this._decodeKey(Buffer.from(keys[i])),
        value: this._values ? this._decodeValue(Buffer.from(values[i])) : null
      })
    }

    if (n < this._capacity) this.push(null)

    cb(null)
  }

  _onclose(err) {
    const cb = this._pendingDestroy
    this._pendingDestroy = null
    this._db._state.io.dec()
    this._db._unref()
    cb(err)
  }

  _resize() {
    if (this._handle !== null) {
      this._buffer = binding.iteratorBuffer(this._handle, this._capacity)
    }
  }

  async ready() {
    if (this._handle !== null) return

    if (this._db._state.opened === false) await this._db._state.ready()

    this._init()
  }

  _init() {
    this._handle = binding.iteratorInit()
    this._buffer = binding.iteratorBuffer(this._handle, this._capacity)
  }

  async _open(cb) {
    await this.ready()

    this._db._state.io.inc()

    if (this._db._state.resumed !== null) {
      const resumed = await this._db._state.waitForResume()

      if (!resumed) {
        this._db._state.io.dec()

        return cb(new Error('RocksDB session is closed'))
      }
    }

    this._pendingOpen = cb

    try {
      binding.iteratorOpen(
        this._db._state._handle,
        this._handle,
        this._db._columnFamily._handle,
        this._gt,
        this._gte,
        this._lt,
        this._lte,
        this._reverse,
        !this._values, // Keys only
        this._db._snapshot ? this._db._snapshot._handle : undefined,
        this,
        this._onopen,
        this._onclose,
        this._onread
      )
    } catch (err) {
      this._db._state.io.dec()

      cb(err)
    }
  }

  async _read(cb) {
    this._db._state.io.inc()

    if (this._db._state.resumed !== null) {
      const resumed = await this._db._state.waitForResume()

      if (!resumed) {
        this._db._state.io.dec()

        return cb(new Error('RocksDB session is closed'))
      }
    }

    this._pendingRead = cb

    try {
      binding.iteratorRead(this._handle, Math.min(this._capacity, this._limit))
    } catch (err) {
      this._db._state.io.dec()

      cb(err)
    }
  }

  async _destroy(cb) {
    await this.ready()

    this._db._state.io.inc()

    if (this._opened === false) {
      this._db._state.io.dec()
      this._db._unref()

      return cb(null)
    }

    this._pendingDestroy = cb

    try {
      binding.iteratorClose(this._handle)
    } catch (err) {
      this._db._state.io.dec()
      this._db._unref()

      cb(err)
    }
  }

  _encodeKey(k) {
    if (this._db._keyEncoding !== null) return c.encode(this._db._keyEncoding, k)
    if (typeof k === 'string') return Buffer.from(k)
    return k
  }

  _decodeKey(b) {
    if (this._db._keyEncoding !== null) return c.decode(this._db._keyEncoding, b)
    return b
  }

  _decodeValue(b) {
    if (this._db._valueEncoding !== null) return c.decode(this._db._valueEncoding, b)
    return b
  }
}
const binding = require('../binding')

module.exports = class RocksDBSnapshot {
  constructor(state) {
    this._state = state

    this._handle = null
    this._refs = 0

    if (state.deferSnapshotInit === false) this._init()
  }

  _init() {
    this._handle = binding.snapshotCreate(this._state._handle)
  }

  ref() {
    this._refs++
  }

  unref() {
    if (--this._refs > 0) return

    if (this._handle === null) return

    binding.snapshotDestroy(this._handle)

    this._handle = null
  }
}
const ReadyResource = require('ready-resource')
const RefCounter = require('refcounter')
const rrp = require('resolve-reject-promise')
const SignalPromise = require('signal-promise')
const c = require('compact-encoding')
const { ReadBatch, WriteBatch } = require('./batch')
const ColumnFamily = require('./column-family')
const binding = require('../binding')
const constants = require('./constants')

const MAX_BATCH_REUSE = 64
const empty = Buffer.alloc(0)

module.exports = class RocksDBState extends ReadyResource {
  constructor(db, path, opts) {
    super()

    const {
      columnFamily = new ColumnFamily('default', opts),
      columnFamilies = [],
      readOnly = false,
      createIfMissing = true,
      createMissingColumnFamilies = true,
      maxBackgroundJobs = 6,
      bytesPerSync = 1048576,
      maxOpenFiles = -1,
      useDirectReads = false,
      avoidUnnecessaryBlockingIO = false,
      skipStatsUpdateOnOpen = false,
      useDirectIOForFlushAndCompaction = false,
      maxFileOpeningThreads = 16,
      lock = null,
      walRecoveryMode = constants.walRecoveryMode.POINT_IN_TIME,
      bestEffortsRecovery = false,
      enableStatistics = false,
      statsLevel = constants.statsLevel.EXCEPT_HISTOGRAM_OR_TIMERS,
      walTtlSeconds = 0,
      walSizeLimitMegabytes = 0,
      avoidFlushDuringShutdown = false,
      walFilterPrefixes = []
    } = opts

    this.path = path
    this.db = db
    this.handles = new RefCounter()
    this.io = new RefCounter()
    this.sessions = []
    this.columnFamilies = [columnFamily]
    this.deferSnapshotInit = true
    this.resumed = null
    this.stats = {
      gets: 0,
      puts: 0,
      deletes: 0,
      rangeDeletes: 0,
      readBatches: 0,
      writeBatches: 0
    }

    this._suspended = false
    this._suspending = false
    this._updating = false
    this._updatingSignal = new SignalPromise()
    this._columnsFlushed = false
    this._lock = lock
    this._readBatches = []
    this._writeBatches = []

    for (const columnFamily of columnFamilies) {
      this.columnFamilies.push(
        typeof columnFamily === 'string' ? new ColumnFamily(columnFamily, opts) : columnFamily
      )
    }

    this._handle = binding.init(
      readOnly,
      createIfMissing,
      createMissingColumnFamilies,
      maxBackgroundJobs,
      bytesPerSync,
      maxOpenFiles,
      useDirectReads,
      avoidUnnecessaryBlockingIO,
      skipStatsUpdateOnOpen,
      useDirectIOForFlushAndCompaction,
      maxFileOpeningThreads,
      walRecoveryMode,
      bestEffortsRecovery,
      enableStatistics,
      statsLevel,
      walTtlSeconds,
      walSizeLimitMegabytes,
      avoidFlushDuringShutdown,
      walFilterPrefixes.map(encodePrefix)
    )
  }

  createReadBatch(db, opts) {
    if (this._readBatches.length === 0) return new ReadBatch(db, opts)
    const batch = this._readBatches.pop()
    batch._reuse(db, opts)
    return batch
  }

  createWriteBatch(db, opts) {
    if (this._writeBatches.length === 0) return new WriteBatch(db, opts)
    const batch = this._writeBatches.pop()
    batch._reuse(db, opts)
    return batch
  }

  freeBatch(batch, writable) {
    if (batch._capacity > 16) return
    const queue = writable ? this._writeBatches : this._readBatches
    if (queue.length >= MAX_BATCH_REUSE) return
    queue.push(batch)
  }

  addSession(db) {
    db._index = this.sessions.push(db) - 1
    if (db._snapshot) db._snapshot.ref()
  }

  removeSession(db) {
    const head = this.sessions.pop()
    if (head !== db) this.sessions[(head._index = db._index)] = head
    db._index = -1
    if (db._snapshot) db._snapshot.unref()
  }

  upsertColumnFamily(c) {
    if (typeof c === 'string') {
      let col = this.getColumnFamilyByName(c)
      if (col) return col
      col = this.columnFamilies[0].cloneSettings(c)
      this.columnFamilies.push(col)
      return col
    }

    if (this.columnFamilies.includes(c)) return c
    this.columnFamilies.push(c)
    return c
  }

  getColumnFamily(c) {
    if (!c) return this.columnFamilies[0]
    if (!this._columnsFlushed) return this.upsertColumnFamily(c)

    if (typeof c !== 'string') return c

    const col = this.getColumnFamilyByName(c)
    if (col === null) throw new Error('Unknown column family')
    return col
  }

  getColumnFamilyByName(name) {
    for (const col of this.columnFamilies) {
      if (col.name === name) return col
    }
    return null
  }

  async _open() {
    await Promise.resolve() // Allow column families to populate if on-demand

    if (this._lock) await this._lock.ready()

    const req = { resolve: null, reject: null, handle: null }

    const promise = new Promise((resolve, reject) => {
      req.resolve = resolve
      req.reject = reject
    })

    this._columnsFlushed = true

    const lock = this._lock === null ? -1 : this._lock.transfer()

    req.handle = binding.open(
      this._handle,
      this,
      this.path,
      this.columnFamilies.map((c) => c._handle),
      lock,
      req,
      onopen
    )

    await promise

    this.deferSnapshotInit = false

    for (const session of this.sessions) {
      if (session._snapshot) session._snapshot._init()
    }

    function onopen(err) {
      if (err) req.reject(err)
      else req.resolve()
    }
  }

  async _close() {
    while (this._updating) await this._updatingSignal.wait()
    if (this.resumed) this.resumed.resolve(false)

    while (!this.io.isIdle()) await this.io.idle()
    while (!this.handles.isIdle()) await this.handles.idle()

    while (this.sessions.length > 0) {
      await this.sessions[this.sessions.length - 1].close()
    }

    for (const columnFamily of this.columnFamilies) columnFamily.destroy()

    const req = { resolve: null, reject: null, handle: null }

    const promise = new Promise((resolve, reject) => {
      req.resolve = resolve
      req.reject = reject
    })

    req.handle = binding.close(this._handle, req, onclose)

    try {
      await promise
    } finally {
      if (this._lock) await this._lock.close()
    }

    function onclose(err) {
      if (err) req.reject(err)
      else req.resolve()
    }
  }

  async flush(db, opts) {
    if (this.opened === false) await this.ready()

    this.io.inc()

    if (this.resumed !== null) {
      const resumed = await this.waitForResume()

      if (!resumed) {
        this.io.dec()

        throw new Error('RocksDB session is closed')
      }
    }

    const req = { resolve: null, reject: null, handle: null }

    const promise = new Promise((resolve, reject) => {
      req.resolve = resolve
      req.reject = reject
    })

    try {
      req.handle = binding.flush(this._handle, db._columnFamily._handle, req, onflush)

      await promise
    } finally {
      this.io.dec()
    }

    function onflush(err) {
      if (err) req.reject(err)
      else req.resolve()
    }
  }

  suspend() {
    this._suspending = true
    return this.update()
  }

  resume() {
    this._suspending = false
    return this.update()
  }

  async update() {
    while (this._updating) await this._updatingSignal.wait()
    if (this._suspending === this._suspended || this.closing) return
    this._updating = true
    try {
      if (this._suspending) await this._suspend()
      else await this._resume()
    } finally {
      this._updating = false
      this._updatingSignal.notify()
    }
  }

  async waitForResume() {
    while (this.resumed !== null) {
      this.io.dec()
      const resumed = await this.resumed.promise
      this.io.inc()
      if (resumed === false) return false
    }
    return true
  }

  async _suspend() {
    if (this._suspended === true) return

    this.resumed = rrp()
    while (!this.io.isIdle()) await this.io.idle()
    this.io.inc()

    const req = { resolve: null, reject: null, handle: null }

    const promise = new Promise((resolve, reject) => {
      req.resolve = resolve
      req.reject = reject
    })

    try {
      req.handle = binding.suspend(this._handle, req, onsuspend)

      await promise

      this._suspended = true
    } finally {
      this.io.dec()
    }

    function onsuspend(err) {
      if (err) req.reject(err)
      else req.resolve()
    }
  }

  async _resume() {
    if (this._suspended === false) return

    const req = { resolve: null, reject: null, handle: null }

    const promise = new Promise((resolve, reject) => {
      req.resolve = resolve
      req.reject = reject
    })

    req.handle = binding.resume(this._handle, req, onresume)

    await promise

    this._suspended = false

    const resumed = this.resumed
    this.resumed = null
    resumed.resolve(true)

    function onresume(err) {
      if (err) req.reject(err)
      else req.resolve()
    }
  }

  async compact(db, opts) {
    if (this.opened === false) await this.ready()

    this.io.inc()

    const {
      exclusive = false,
      blobGarbageCollectionPolicy = constants.garbageCollectionPolicy.DEFAULT,
      blobGarbageCollectionAgeCutoff = 0.25,
      bottommostLevelCompaction = constants.bottommostLevelCompaction.NONE
    } = opts

    const req = { resolve: null, reject: null, handle: null }

    const promise = new Promise((resolve, reject) => {
      req.resolve = resolve
      req.reject = reject
    })

    try {
      req.handle = binding.compact(
        this._handle,
        db._columnFamily._handle,
        exclusive,
        blobGarbageCollectionPolicy,
        blobGarbageCollectionAgeCutoff,
        bottommostLevelCompaction,
        req,
        oncompact
      )

      await promise
    } finally {
      this.io.dec()
    }

    function oncompact(err) {
      if (err) req.reject(err)
      else req.resolve()
    }
  }

  async compactRange(db, start, end, opts) {
    if (this.opened === false) await this.ready()

    this.io.inc()

    const {
      exclusive = false,
      blobGarbageCollectionPolicy = constants.garbageCollectionPolicy.DEFAULT,
      blobGarbageCollectionAgeCutoff = 0.25,
      bottommostLevelCompaction = constants.bottommostLevelCompaction.NONE
    } = opts

    start = this._encodeKey(start)
    end = this._encodeKey(end)

    const req = { resolve: null, reject: null, handle: null, start, end }

    const promise = new Promise((resolve, reject) => {
      req.resolve = resolve
      req.reject = reject
    })

    try {
      req.handle = binding.compactRange(
        this._handle,
        db._columnFamily._handle,
        start,
        end,
        exclusive,
        blobGarbageCollectionPolicy,
        blobGarbageCollectionAgeCutoff,
        bottommostLevelCompaction,
        req,
        oncompactrange
      )

      await promise
    } finally {
      this.io.dec()
    }

    function oncompactrange(err) {
      if (err) req.reject(err)
      else req.resolve()
    }
  }

  async approximateSize(db, start, end, opts) {
    if (this.opened === false) await this.ready()

    this.io.inc()

    const { includeMemtables = false, includeFiles = true, filesSizeErrorMargin = -1 } = opts

    start = this._encodeKey(start)
    end = this._encodeKey(end)

    const req = { resolve: null, reject: null, handle: null, start, end }

    const promise = new Promise((resolve, reject) => {
      req.resolve = resolve
      req.reject = reject
    })

    try {
      req.handle = binding.approximateSize(
        this._handle,
        db._columnFamily._handle,
        start,
        end,
        includeMemtables,
        includeFiles,
        filesSizeErrorMargin,
        req,
        onapproximatesize
      )

      return await promise
    } finally {
      this.io.dec()
    }

    function onapproximatesize(err, result) {
      if (err) req.reject(err)
      else req.resolve(result)
    }
  }

  async currentWalFile() {
    if (this.opened === false) await this.ready()

    this.io.inc()

    const req = { resolve: null, reject: null, handle: null }

    const promise = new Promise((resolve, reject) => {
      req.resolve = resolve
      req.reject = reject
    })

    try {
      req.handle = binding.currentWalFile(this._handle, req, oncurrentwalfile)

      return await promise
    } finally {
      this.io.dec()
    }

    function oncurrentwalfile(err, path, number, type, startSequence, size) {
      if (err) req.reject(err)
      else req.resolve({ path, number, type, startSequence, size })
    }
  }

  async getStatsLevel() {
    if (this.opened === false) await this.ready()

    return binding.statsLevelGet(this._handle)
  }

  async setStatsLevel(level) {
    if (this.opened === false) await this.ready()

    binding.statsLevelSet(this._handle, level)
  }

  async getProperty(name) {
    if (this.opened === false) await this.ready()

    return binding.propertyGet(this._handle, name)
  }

  _encodeKey(k) {
    if (this.db._keyEncoding) return c.encode(this.db._keyEncoding, k)
    if (typeof k === 'string') return Buffer.from(k)
    if (k === null) return empty
    return k
  }

  diagnostics() {
    return {
      suspended: this._suspended,
      suspending: this._suspending,
      updating: this._updating,
      resumedPending: this.resumed !== null,
      io: this.io.count,
      handles: this.handles.count,
      sessions: this.sessions.length
    }
  }
}

function encodePrefix(prefix) {
  if (typeof prefix === 'string') return Buffer.from(prefix)
  return prefix
}
{
  "name": "rocksdb-native",
  "version": "3.17.4",
  "description": "librocksdb bindings for JavaScript",
  "exports": {
    ".": "./index.js",
    "./package": "./package.json"
  },
  "imports": {
    "crypto": {
      "bare": "bare-crypto",
      "default": "crypto"
    },
    "fs": {
      "bare": "bare-fs",
      "default": "fs"
    },
    "path": {
      "bare": "bare-path",
      "default": "path"
    }
  },
  "files": [
    "index.js",
    "binding.cc",
    "binding.js",
    "CMakeLists.txt",
    "lib",
    "prebuilds",
    "!prebuilds/android-*/**/*.node"
  ],
  "addon": true,
  "scripts": {
    "format": "prettier --write . && lunte --fix",
    "lint": "prettier --check . && lunte",
    "test": "npm run test:bare && npm run test:node",
    "test:bare": "brittle-bare --coverage test.js",
    "test:node": "brittle-node --coverage test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/rocksdb-native.git"
  },
  "author": "Holepunch Inc",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/rocksdb-native/issues"
  },
  "homepage": "https://github.com/holepunchto/rocksdb-native",
  "engines": {
    "bare": ">=1.16.0"
  },
  "dependencies": {
    "compact-encoding": "^3.0.0",
    "ready-resource": "^1.0.0",
    "refcounter": "^1.0.0",
    "require-addon": "^1.0.2",
    "resolve-reject-promise": "^1.1.0",
    "signal-promise": "^1.0.3",
    "streamx": "^2.16.1"
  },
  "devDependencies": {
    "bare-compat-napi": "^1.3.0",
    "bare-crypto": "^1.12.0",
    "bare-fs": "^4.5.0",
    "bare-path": "^3.0.0",
    "brittle": "^3.5.0",
    "cmake-bare": "^1.1.14",
    "cmake-fetch": "^1.0.1",
    "cmake-napi": "^1.0.6",
    "fd-lock": "^2.0.0",
    "lunte": "^1.8.0",
    "paparam": "^1.9.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^2.0.0"
  }
}
module.exports = safetyCatch

function isActuallyUncaught (err) {
  if (!err) return false
  return err instanceof TypeError ||
    err instanceof SyntaxError ||
    err instanceof ReferenceError ||
    err instanceof EvalError ||
    err instanceof RangeError ||
    err instanceof URIError ||
    err.code === 'ERR_ASSERTION' ||
    err.name === 'AssertionError'
}

function throwErrorNT (err) {
  queueMicrotask(() => { throw err })
}

function safetyCatch (err) {
  if (isActuallyUncaught(err)) {
    throwErrorNT(err)
    throw err
  }
}
{
  "name": "safety-catch",
  "version": "1.0.3",
  "description": "Small module that makes sure your catch, caught an actual error and not a programming mistake or assertion",
  "main": "index.js",
  "dependencies": {},
  "devDependencies": {},
  "repository": {
    "type": "git",
    "url": "https://github.com/mafintosh/safety-catch.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/mafintosh/safety-catch/issues"
  },
  "homepage": "https://github.com/mafintosh/safety-catch"
}
let tmpResolve = null

module.exports = class ScopeLock {
  constructor ({ debounce = false } = {}) {
    this.debounce = debounce
    this.waiting = []
    this.locked = false
    this.skip = 0
    this.destroyed = false
  }

  flush () {
    if (this.locked === false && this.waiting.length === 0) return Promise.resolve(this.destroyed === false)

    const promise = new Promise(setTmpResolve)
    const resolve = tmpResolve

    tmpResolve = null
    this.waiting.push({ lock: false, resolve })

    return promise
  }

  destroy () {
    this.destroyed = true
  }

  lock () {
    const promise = new Promise(setTmpResolve)
    const resolve = tmpResolve

    tmpResolve = null

    if (this.locked === true) {
      this.waiting.push({ lock: true, resolve })
      return promise
    }

    if (this.destroyed === true) {
      resolve(false)
      return promise
    }

    this.locked = true
    resolve(true)

    return promise
  }

  unlock () {
    if (this.destroyed === true) {
      for (let i = 0; i < this.waiting.length; i++) {
        this.waiting[i].resolve(false)
      }
      this.waiting = []
      this.skip = 0
      this.locked = false
      return
    }

    if (this.skip !== 0) {
      for (let i = 0; i < this.skip; i++) {
        const { lock, resolve } = this.waiting[i]
        resolve(lock === false)
      }

      this.waiting = this.waiting.slice(this.skip)
      this.skip = 0
    }

    while (this.waiting.length > 0 && this.waiting[0].lock === false) {
      this.waiting.shift().resolve(true)
    }

    if (this.waiting.length === 0) {
      this.locked = false
      return
    }

    const { resolve } = this.waiting.shift()
    if (this.debounce === true) this.skip = this.waiting.length

    resolve(true)
  }
}

function setTmpResolve (resolve) {
  tmpResolve = resolve
}
{
  "name": "scope-lock",
  "version": "1.2.4",
  "description": "Some concurrency semantics around entering scopes",
  "main": "index.js",
  "files": [
    "index.js"
  ],
  "devDependencies": {
    "brittle": "^3.7.0",
    "standard": "^17.1.2"
  },
  "scripts": {
    "test": "standard && brittle test.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/holepunchto/scope-lock.git"
  },
  "author": "Holepunch Inc.",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/scope-lock/issues"
  },
  "homepage": "https://github.com/holepunchto/scope-lock"
}
const set = require('unordered-set')

module.exports = opts => new ShuffledPriorityQueue(opts)

class ShuffledPriorityQueue {
  constructor (opts) {
    this.priorities = []
    this.equals = (opts && opts.equals) || null
  }

  get length () {
    return this.priorities.reduce(add, 0)
  }

  [Symbol.iterator] () {
    return new Iterator(this)
  }

  head () {
    for (let i = this.priorities.length - 1; i >= 0; i--) {
      const q = this.priorities[i]
      if (q.length) return shuffle(q, 0)
    }
    return null
  }

  tail () {
    for (let i = 0; i < this.priorities.length; i++) {
      const q = this.priorities[i]
      if (q.length) return shuffle(q, 0)
    }
    return null
  }

  prev (prev) {
    if (!prev) return this.tail()
    return next(this.priorities, prev, 1)
  }

  next (prev) {
    if (!prev) return this.head()
    return next(this.priorities, prev, -1)
  }

  shift () {
    return this.remove(this.head())
  }

  pop () {
    return this.remove(this.tail())
  }

  add (val) {
    const prio = val.priority || 0
    while (prio >= this.priorities.length) this.priorities.push([])
    set.add(this.priorities[prio], val)
    return val
  }

  remove (val) {
    if (!val) return null

    if (val._index === undefined) {
      val = this.find(val)
      if (!val) return null
    }

    return set.remove(this.priorities[val.priority || 0], val)
  }

  has (val) {
    if (val._index === undefined) return this.find(val)
    const priority = val.priority || 0
    if (priority >= this.priorities.length) return false
    return set.has(this.priorities[priority], val)
  }

  find (val) {
    if (val._index !== undefined) return val

    const prio = val.priority || 0
    const qs = this.priorities
    if (prio >= qs.length) return null

    const q = qs[prio]

    for (let i = 0; i < q.length; i++) {
      if (this.equals(q[i], val)) return q[i]
    }

    return null
  }
}

class Iterator {
  constructor (queue) {
    this.prev = null
    this.queue = queue
  }

  next () {
    const next = this.queue.next(this.prev)
    this.prev = next
    return { done: !next, value: next }
  }
}

function shuffle (q, i) {
  const ran = i + Math.floor(Math.random() * (q.length - i))
  set.swap(q, q[ran], q[i])
  return q[i]
}

function next (queues, prev, inc) {
  let i = prev.priority || 0
  let j = (prev._index || 0) + 1

  while (true) {
    if (i < 0 || i >= queues.length) return null
    const q = queues[i]

    if (j >= q.length) {
      i += inc
      j = 0
      continue
    }

    return shuffle(q, j)
  }
}

function add (len, b) {
  return len + b.length
}
{
  "name": "shuffled-priority-queue",
  "version": "2.1.0",
  "description": "A priority queue that shuffles elements with the same priority.",
  "main": "index.js",
  "scripts": {
    "test": "standard && tape test.js"
  },
  "dependencies": {
    "unordered-set": "^2.0.1"
  },
  "devDependencies": {
    "standard": "^12.0.1",
    "tape": "^4.9.1"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/mafintosh/shuffled-priority-queue.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/mafintosh/shuffled-priority-queue/issues"
  },
  "homepage": "https://github.com/mafintosh/shuffled-priority-queue"
}
module.exports = class Signal {
  constructor () {
    this._resolve = null
    this._reject = null
    this._promise = null
    this._bind = bind.bind(this)
    this._onerror = clear.bind(this)
    this._onsuccess = clear.bind(this, null)
    this._timers = new Set()
  }

  wait (max) {
    if (!this._promise) {
      this._promise = new Promise(this._bind)
      this._promise.then(this._onsuccess).catch(this._onerror)
    }
    if (max) return this._sleep(max)
    return this._promise
  }

  _sleep (max) {
    const s = new Promise((resolve, reject) => {
      const done = () => {
        this._timers.delete(state)
        resolve(true)
      }
      const id = setTimeout(done, max)
      const state = { id, resolve, reject }
      this._timers.add(state)
    })

    return s
  }

  notify (err) {
    if (!this._promise) return
    const resolve = this._resolve
    const reject = this._reject
    this._promise = null
    if (err) reject(err)
    else resolve(true)
  }
}

function clear (err) {
  for (const { id, resolve, reject } of this._timers) {
    clearTimeout(id)
    if (err) reject(err)
    else resolve(true)
  }
  this._timers.clear()
}

function bind (resolve, reject) {
  this._resolve = resolve
  this._reject = reject
}
{
  "name": "signal-promise",
  "version": "1.0.3",
  "description": "Simple wait/notify promise with optional max wait time",
  "main": "index.js",
  "dependencies": {},
  "devDependencies": {},
  "repository": {
    "type": "git",
    "url": "https://github.com/mafintosh/signal-promise.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/mafintosh/signal-promise/issues"
  },
  "homepage": "https://github.com/mafintosh/signal-promise"
}
var varint = require('varint')
exports.encode = function encode (v, b, o) {
  v = v >= 0 ? v*2 : v*-2 - 1
  var r = varint.encode(v, b, o)
  encode.bytes = varint.encode.bytes
  return r
}
exports.decode = function decode (b, o) {
  var v = varint.decode(b, o)
  decode.bytes = varint.decode.bytes
  return v & 1 ? (v+1) / -2 : v / 2
}

exports.encodingLength = function (v) {
  return varint.encodingLength(v >= 0 ? v*2 : v*-2 - 1)
}
{
  "name": "signed-varint",
  "description": "efficiently store signed integers in varint",
  "version": "2.0.1",
  "homepage": "https://github.com/dominictarr/signed-varint",
  "repository": {
    "type": "git",
    "url": "git://github.com/dominictarr/signed-varint.git"
  },
  "dependencies": {
    "varint": "~5.0.0"
  },
  "devDependencies": {
    "tape": "~2.12.3"
  },
  "scripts": {
    "test": "node test.js"
  },
  "author": "Dominic Tarr <dominic.tarr@gmail.com> (http://dominictarr.com)",
  "license": "MIT"
}
require.addon = require('require-addon')

module.exports = require.addon('.', __filename)
const binding = require('./binding')
const b4a = require('b4a')

function predicate(u8, u16, u32) {
  return function predicate(buf) {
    if (buf.byteLength % 16 !== 0) {
      throw new Error('Buffer length must be a multiple of 16')
    }

    const n = buf.BYTES_PER_ELEMENT

    if (n === 1) return u8(buf)
    if (n === 2) return u16(buf)
    return u32(buf)
  }
}

function unary(u8, u16, u32) {
  return function unary(buf, result = b4a.allocUnsafe(buf.byteLength)) {
    if (buf.byteLength % 16 !== 0) {
      throw new Error('Buffer length must be a multiple of 16')
    }

    if (buf.byteLength !== result.byteLength) {
      throw new Error('Length of result buffer is insufficient')
    }

    const n = buf.BYTES_PER_ELEMENT

    if (n === 1) u8(buf, result)
    else if (n === 2) u16(buf, result)
    else u32(buf, result)

    return result
  }
}

function binary(u8, u16, u32) {
  return function binary(a, b, result = b4a.allocUnsafe(a.byteLength)) {
    if (a.byteLength % 16 !== 0) {
      throw new Error('Buffer length must be a multiple of 16')
    }

    if (a.byteLength !== b.byteLength || a.byteLength !== result.byteLength) {
      throw new Error('Buffers must be the same length')
    }

    const n = a.BYTES_PER_ELEMENT

    if (n === 1) u8(a, b, result)
    else if (n === 2) u16(a, b, result)
    else u32(a, b, result)

    return result
  }
}

function reduce(u8, u16, u32) {
  return function reduce(buf) {
    if (buf.byteLength % 16 !== 0) {
      throw new Error('Buffer length must be a multiple of 16')
    }

    const n = buf.BYTES_PER_ELEMENT

    if (n === 1) return u8(buf)
    if (n === 2) return u16(buf)
    return u32(buf)
  }
}

exports.allo = predicate(
  binding.simdle_native_allo_v128_u8,
  binding.simdle_native_allo_v128_u16,
  binding.simdle_native_allo_v128_u32
)

exports.allz = predicate(
  binding.simdle_native_allz_v128_u8,
  binding.simdle_native_allz_v128_u16,
  binding.simdle_native_allz_v128_u32
)

exports.and = binary(
  binding.simdle_native_and_v128_u8,
  binding.simdle_native_and_v128_u16,
  binding.simdle_native_and_v128_u32
)

exports.clear = binary(
  binding.simdle_native_clear_v128_u8,
  binding.simdle_native_clear_v128_u16,
  binding.simdle_native_clear_v128_u32
)

exports.clo = unary(
  binding.simdle_native_clo_v128_u8,
  binding.simdle_native_clo_v128_u16,
  binding.simdle_native_clo_v128_u32
)

exports.clz = unary(
  binding.simdle_native_clz_v128_u8,
  binding.simdle_native_clz_v128_u16,
  binding.simdle_native_clz_v128_u32
)

exports.cnt = unary(
  binding.simdle_native_cnt_v128_u8,
  binding.simdle_native_cnt_v128_u16,
  binding.simdle_native_cnt_v128_u32
)

exports.cto = unary(
  binding.simdle_native_cto_v128_u8,
  binding.simdle_native_cto_v128_u16,
  binding.simdle_native_cto_v128_u32
)

exports.ctz = unary(
  binding.simdle_native_ctz_v128_u8,
  binding.simdle_native_ctz_v128_u16,
  binding.simdle_native_ctz_v128_u32
)

exports.not = unary(
  binding.simdle_native_not_v128_u8,
  binding.simdle_native_not_v128_u16,
  binding.simdle_native_not_v128_u32
)

exports.or = binary(
  binding.simdle_native_or_v128_u8,
  binding.simdle_native_or_v128_u16,
  binding.simdle_native_or_v128_u32
)

exports.sum = reduce(
  binding.simdle_native_sum_v128_u8,
  binding.simdle_native_sum_v128_u16,
  binding.simdle_native_sum_v128_u32
)

exports.xor = binary(
  binding.simdle_native_xor_v128_u8,
  binding.simdle_native_xor_v128_u16,
  binding.simdle_native_xor_v128_u32
)
{
  "name": "simdle-native",
  "version": "1.3.9",
  "description": "libsimdle JavaScript bindings for Node.js",
  "main": "index.js",
  "files": [
    "index.js",
    "binding.cc",
    "binding.js",
    "CMakeLists.txt",
    "prebuilds"
  ],
  "addon": true,
  "scripts": {
    "format": "prettier --write .",
    "lint": "prettier --check .",
    "test": "npm run lint && npm run test:bare && npm run test:node",
    "test:bare": "bare test.mjs",
    "test:node": "node test.mjs"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/simdle-native.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/simdle-native/issues"
  },
  "homepage": "https://github.com/holepunchto/simdle-native#readme",
  "dependencies": {
    "b4a": "^1.6.0",
    "require-addon": "^1.1.0"
  },
  "devDependencies": {
    "bare-compat-napi": "^1.3.2",
    "brittle": "^3.1.0",
    "cmake-bare": "^1.1.7",
    "cmake-fetch": "^1.1.0",
    "cmake-napi": "^1.0.2",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^1.0.0"
  }
}
const b4a = require('b4a')
const scalar = require('./scalar')

function view (buf, n) {
  if (n === buf.BYTES_PER_ELEMENT) return buf

  let TypedArray

  if (n === 1) TypedArray = Uint8Array
  else if (n === 2) TypedArray = Uint16Array
  else TypedArray = Uint32Array

  return new TypedArray(buf.buffer, buf.byteOffset, buf.byteLength / n)
}

function unary (u8, u16 = u8, u32 = u16) {
  return function unary (buf, result = b4a.allocUnsafe(buf.byteLength)) {
    if (buf.byteLength % 16 !== 0) {
      throw new Error('Buffer length must be a multiple of 16')
    }

    if (buf.byteLength !== result.byteLength) {
      throw new Error('Length of result buffer is insufficient')
    }

    const n = buf.BYTES_PER_ELEMENT

    if (n === 1) u8(buf, view(result, n))
    else if (n === 2) u16(buf, view(result, n))
    else u32(buf, view(result, n))

    return result
  }
}

function binary (u8, u16 = u8, u32 = u16) {
  return function binary (a, b, result = b4a.allocUnsafe(a.byteLength)) {
    if (a.byteLength % 16 !== 0) {
      throw new Error('Buffer length must be a multiple of 16')
    }

    if (a.byteLength !== b.byteLength || a.byteLength !== result.byteLength) {
      throw new Error('Buffers must be the same length')
    }

    const n = a.BYTES_PER_ELEMENT

    if (n === 1) u8(a, b, view(result, n))
    else if (n === 2) u16(a, b, view(result, n))
    else u32(a, b, view(result, n))

    return result
  }
}

function reduce (u8, u16 = u8, u32 = u16) {
  return function reduce (buf) {
    if (buf.byteLength % 16 !== 0) {
      throw new Error('Buffer length must be a multiple of 16')
    }

    const n = buf.BYTES_PER_ELEMENT

    if (n === 1) return u8(buf)
    if (n === 2) return u16(buf)
    return u32(buf)
  }
}

exports.allo = function allo (buf) {
  if (buf.byteLength % 16 !== 0) {
    throw new Error('Buffer length must be a multiple of 16')
  }

  const m = 2 ** (buf.BYTES_PER_ELEMENT * 8) - 1

  for (let i = 0, n = buf.length; i < n; i++) {
    if (buf[i] !== m) return false
  }

  return true
}

exports.allz = function allz (buf) {
  if (buf.byteLength % 16 !== 0) {
    throw new Error('Buffer length must be a multiple of 16')
  }

  for (let i = 0, n = buf.length; i < n; i++) {
    if (buf[i] !== 0) return false
  }

  return true
}

exports.and = binary(
  (a, b, result) => {
    for (let i = 0, n = result.length; i < n; i++) {
      result[i] = a[i] & b[i]
    }
  }
)

exports.clear = binary(
  (a, b, result) => {
    for (let i = 0, n = result.length; i < n; i++) {
      result[i] = a[i] & ~b[i]
    }
  }
)

exports.clo = unary(
  (buf, result) => {
    for (let i = 0, n = buf.length; i < n; i++) {
      result[i] = 24 - scalar.clo(buf[i])
    }
  },
  (buf, result) => {
    for (let i = 0, n = buf.length; i < n; i++) {
      result[i] = 16 - scalar.clo(buf[i])
    }
  },
  (buf, result) => {
    for (let i = 0, n = buf.length; i < n; i++) {
      result[i] = scalar.clo(buf[i])
    }
  }
)

exports.clz = unary(
  (buf, result) => {
    for (let i = 0, n = buf.length; i < n; i++) {
      result[i] = 24 - scalar.clz(buf[i])
    }
  },
  (buf, result) => {
    for (let i = 0, n = buf.length; i < n; i++) {
      result[i] = 16 - scalar.clz(buf[i])
    }
  },
  (buf, result) => {
    for (let i = 0, n = buf.length; i < n; i++) {
      result[i] = scalar.clz(buf[i])
    }
  }
)

exports.cnt = unary(
  (buf, result) => {
    for (let i = 0, n = buf.length; i < n; i++) {
      result[i] = scalar.cnt(buf[i]) & 0xff
    }
  },
  (buf, result) => {
    for (let i = 0, n = buf.length; i < n; i++) {
      result[i] = scalar.cnt(buf[i]) & 0xffff
    }
  },
  (buf, result) => {
    for (let i = 0, n = buf.length; i < n; i++) {
      result[i] = scalar.cnt(buf[i])
    }
  }
)

exports.cto = unary(
  (buf, result) => {
    for (let i = 0, n = buf.length; i < n; i++) {
      result[i] = Math.min(scalar.cto(buf[i]), 8)
    }
  },
  (buf, result) => {
    for (let i = 0, n = buf.length; i < n; i++) {
      result[i] = Math.min(scalar.cto(buf[i]), 16)
    }
  },
  (buf, result) => {
    for (let i = 0, n = buf.length; i < n; i++) {
      result[i] = scalar.cto(buf[i])
    }
  }
)

exports.ctz = unary(
  (buf, result) => {
    for (let i = 0, n = buf.length; i < n; i++) {
      result[i] = Math.min(scalar.ctz(buf[i]), 8)
    }
  },
  (buf, result) => {
    for (let i = 0, n = buf.length; i < n; i++) {
      result[i] = Math.min(scalar.ctz(buf[i]), 16)
    }
  },
  (buf, result) => {
    for (let i = 0, n = buf.length; i < n; i++) {
      result[i] = scalar.ctz(buf[i])
    }
  }
)

exports.not = unary(
  (buf, result) => {
    for (let i = 0, n = buf.length; i < n; i++) {
      result[i] = ~buf[i]
    }
  }
)

exports.or = binary(
  (a, b, result) => {
    for (let i = 0, n = result.length; i < n; i++) {
      result[i] = a[i] | b[i]
    }
  }
)

exports.sum = reduce(
  (buf) => {
    let result = 0n

    for (let i = 0, n = buf.length; i < n; i++) {
      result += BigInt(buf[i])
    }

    return result
  }
)

exports.xor = binary(
  (a, b, result) => {
    for (let i = 0, n = result.length; i < n; i++) {
      result[i] = a[i] ^ b[i]
    }
  }
)
try {
  module.exports = require('simdle-native')
} catch {
  module.exports = require('./fallback')
}
{
  "name": "simdle-universal",
  "version": "1.1.2",
  "description": "Universal wrapper for libsimdle with a JavaScript fallback",
  "main": "index.js",
  "files": [
    "fallback.js",
    "index.js",
    "scalar.js"
  ],
  "browser": {
    "./index.js": "./fallback.js"
  },
  "scripts": {
    "test": "standard && brittle test.mjs"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/simdle-universal.git"
  },
  "author": "Kasper Isager Dalsgarð <kasper@funktionel.co>",
  "license": "ISC",
  "bugs": {
    "url": "https://github.com/holepunchto/simdle-universal/issues"
  },
  "homepage": "https://github.com/holepunchto/simdle-universal#readme",
  "dependencies": {
    "b4a": "^1.6.0"
  },
  "optionalDependencies": {
    "simdle-native": "^1.1.1"
  },
  "devDependencies": {
    "brittle": "^3.1.0",
    "standard": "^17.0.0"
  }
}
const clz = exports.clz = function clz (n) {
  return Math.clz32(n)
}

exports.clo = function clo (n) {
  return clz(~n)
}

const ctz = exports.ctz = function ctz (n) {
  return 32 - (n === 0 ? 0 : (clz(n & -n) + 1))
}

exports.cto = function cto (n) {
  return ctz(~n)
}

exports.cnt = function cnt (n) {
  n = n - ((n >>> 1) & 0x55555555)
  n = (n & 0x33333333) + ((n >>> 2) & 0x33333333)
  n = (n + (n >>> 4)) & 0x0f0f0f0f
  n = (n * 0x01010101) >>> 24
  return n
}
const sodium = require('sodium-universal')

module.exports = {
  init: sodium.crypto_hash_sha256_init,
  update: sodium.crypto_hash_sha256_update,
  final: sodium.crypto_hash_sha256_final,
  BYTES: sodium.crypto_hash_sha256_BYTES,
  STATEBYTES: sodium.crypto_hash_sha256_STATEBYTES
}
const sodium = require('sodium-universal')

module.exports = {
  init: sodium.crypto_hash_sha512_init,
  update: sodium.crypto_hash_sha512_update,
  final: sodium.crypto_hash_sha512_final,
  BYTES: sodium.crypto_hash_sha512_BYTES,
  STATEBYTES: sodium.crypto_hash_sha512_STATEBYTES
}
const b4a = require('b4a')

const sha256 = require('./hash/sha256')
const sha512 = require('./hash/sha512')

class HMAC {
  constructor (hash, key) {
    this.hash = hash
    this.length = hash.BYTES

    this._blocksize = 2 * hash.BYTES

    this._initialised = false

    this.state = b4a.alloc(hash.STATEBYTES)
    this.pad = b4a.alloc(this._blocksize)

    if (key) this.init(key)
  }

  init (key) {
    if (this._initialised) {
      throw new Error('Already initialised, call final first')
    }

    if (key.length > this._blocksize) {
      this.hash.init(this.state)
      this.hash.update(this.state, key)
      this.hash.final(this.state, this.pad.subarray(0, this.length))
    } else {
      this.pad.set(key)
    }

    bufferByteXor(this.pad, 0x36)

    this.hash.init(this.state)
    this.hash.update(this.state, this.pad)

    bufferByteXor(this.pad, 0x5c ^ 0x36)

    this._initialised = true

    return this
  }

  update (data) {
    if (!this._initialised) {
      throw new Error('First initialise with a key')
    }

    this.hash.update(this.state, data)
    return this
  }

  final (output = b4a.alloc(this.length)) {
    if (!this._initialised) {
      throw new Error('First initialise with a key')
    }

    const out = output.byteLength === this.length
      ? output
      : output.subarray(0, this.hash.BYTES)

    this.hash.final(this.state, out)

    this.hash.init(this.state)
    this.hash.update(this.state, this.pad)
    this.hash.update(this.state, out)
    this.hash.final(this.state, out)

    this.pad.fill(0)
    this.state.fill(0)

    this._initialised = false

    return out
  }

  static sha256 (data, key, output) {
    const hmac = new HMAC(sha256, key)
    return hmac.update(data).final(output)
  }

  static sha512 (data, key, output) {
    const hmac = new HMAC(sha512, key)
    return hmac.update(data).final(output)
  }
}

module.exports = {
  HMAC,
  sha256,
  sha512
}

function bufferByteXor (output, byte) {
  for (let i = 0; i < output.byteLength; i++) {
    output[i] ^= byte
  }

  return output
}
{
  "name": "sodium-hmac",
  "version": "2.1.0",
  "description": "HMAC utility",
  "main": "index.js",
  "scripts": {
    "test": "standard && brittle test/*.js"
  },
  "author": "",
  "license": "Apache-2.0",
  "dependencies": {
    "b4a": "^1.6.6",
    "sodium-universal": "^5.0.1"
  },
  "devDependencies": {
    "brittle": "^3.4.0",
    "standard": "^17.1.0"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/sodium-hmac.git"
  },
  "files": [
    "index.js",
    "hash/*"
  ],
  "bugs": {
    "url": "https://github.com/holepunchto/sodium-hmac/issues"
  },
  "homepage": "https://github.com/holepunchto/sodium-hmac#readme"
}
require.addon = require('require-addon')
module.exports = require.addon('.', __filename)
const assert = require('assert')
const binding = require('./binding')
const { isNode } = require('which-runtime')

const OPTIONAL = Buffer.from(new ArrayBuffer(0))

module.exports = exports = { ...binding }

// memory

exports.sodium_memzero = function (buf) {
  assert(ArrayBuffer.isView(buf), 'buf must be a typed array')

  binding.sodium_memzero(buf)
}

exports.sodium_mlock = function (buf) {
  assert(ArrayBuffer.isView(buf), 'buf must be a typed array')

  const res = binding.sodium_mlock(buf)

  if (res !== 0) throw new Error('memory lock failed')
}

exports.sodium_munlock = function (buf) {
  assert(ArrayBuffer.isView(buf), 'buf must be a typed array')

  const res = binding.sodium_munlock(buf)

  if (res !== 0) throw new Error('memory unlock failed')
}

exports.sodium_malloc = function (size) {
  assert(size >= 0, 'invalid size')

  const buf = Buffer.from(binding.sodium_malloc(size))
  buf.secure = true
  return buf
}

exports.sodium_free = function (buf) {
  if (!buf || !buf.secure) return

  binding.sodium_free(buf.buffer)
}

exports.sodium_mprotect_noaccess = function (buf) {
  const res = binding.sodium_mprotect_noaccess(buf.buffer)

  if (res !== 0) throw new Error('failed to lock buffer')
}

exports.sodium_mprotect_readonly = function (buf) {
  const res = binding.sodium_mprotect_readonly(buf.buffer)

  if (res !== 0) throw new Error('failed to unlock buffer')
}

exports.sodium_mprotect_readwrite = function (buf) {
  const res = binding.sodium_mprotect_readwrite(buf.buffer)

  if (res !== 0) throw new Error('failed to unlock buffer')
}

// crypto_randombytes

exports.randombytes_buf = function (buffer) {
  assert(ArrayBuffer.isView(buffer), 'buffer must be a typed array')

  binding.randombytes_buf(buffer.buffer, buffer.byteOffset, buffer.byteLength)
}

exports.randombytes_buf_deterministic = function (buffer, seed) {
  assert(ArrayBuffer.isView(buffer), 'buffer must be a typed array')
  assert(ArrayBuffer.isView(seed), 'seed must be a typed array')
  assert(
    seed.byteLength === binding.randombytes_SEEDBYTES,
    "seed must be 'randombytes_SEEDBYTES' bytes"
  )

  binding.randombytes_buf_deterministic(
    buffer.buffer,
    buffer.byteOffset,
    buffer.byteLength,

    seed.buffer,
    seed.byteOffset,
    seed.byteLength
  )
}

// sodium_helpers

exports.sodium_memcmp = function (a, b) {
  assert(ArrayBuffer.isView(a), 'a must be a typed array')
  assert(ArrayBuffer.isView(b), 'b must be a typed array')
  assert(a.byteLength === b.byteLength, 'buffers must be of same length')

  return binding.sodium_memcmp(a, b)
}

exports.sodium_add = function (a, b) {
  assert(ArrayBuffer.isView(a), 'a must be a typed array')
  assert(ArrayBuffer.isView(b), 'b must be a typed array')
  assert(a.byteLength === b.byteLength, 'buffers must be of same length')

  binding.sodium_add(a, b)
}

exports.sodium_sub = function (a, b) {
  assert(ArrayBuffer.isView(a), 'a must be a typed array')
  assert(ArrayBuffer.isView(b), 'b must be a typed array')
  assert(a.byteLength === b.byteLength, 'buffers must be of same length')

  binding.sodium_sub(a, b)
}

exports.sodium_compare = function (a, b) {
  assert(ArrayBuffer.isView(a), 'a must be a typed array')
  assert(ArrayBuffer.isView(b), 'b must be a typed array')
  assert(a.byteLength === b.byteLength, 'buffers must be of same length')

  return binding.sodium_compare(a, b)
}

exports.sodium_is_zero = function (buffer, length) {
  if (length === undefined) length = buffer.byteLength

  assert(ArrayBuffer.isView(buffer), 'buffer must be a typed array')
  assert(length >= 0 && length <= buffer.byteLength, 'invalid length')

  return binding.sodium_is_zero(buffer, length)
}

exports.sodium_pad = function (buffer, unpaddedBuflen, blockSize) {
  assert(ArrayBuffer.isView(buffer), 'buffer must be a typed array')
  assert(unpaddedBuflen <= buffer.byteLength, 'unpadded length cannot exceed buffer length')
  assert(blockSize <= buffer.byteLength, 'block size cannot exceed buffer length')
  assert(blockSize >= 1, 'block size must be at least 1 byte')
  assert(
    buffer.byteLength >= unpaddedBuflen + (blockSize - (unpaddedBuflen % blockSize)),
    'buf not long enough'
  )

  return binding.sodium_pad(buffer, unpaddedBuflen, blockSize)
}

exports.sodium_unpad = function (buffer, paddedBuflen, blockSize) {
  assert(ArrayBuffer.isView(buffer), 'buffer must be a typed array')
  assert(paddedBuflen <= buffer.byteLength, 'unpadded length cannot exceed buffer length')
  assert(blockSize <= buffer.byteLength, 'block size cannot exceed buffer length')
  assert(blockSize >= 1, 'block size must be at least 1 byte')

  return binding.sodium_unpad(buffer, paddedBuflen, blockSize)
}

// crypto_sign

exports.crypto_sign_keypair = function (pk, sk) {
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(
    pk.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
    "pk must be 'crypto_sign_PUBLICKEYBYTES' bytes"
  )
  assert(
    sk.byteLength === binding.crypto_sign_SECRETKEYBYTES,
    "sk must be 'crypto_sign_SECRETKEYBYTES' bytes"
  )

  const res = binding.crypto_sign_keypair(pk, sk)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_sign_seed_keypair = function (pk, sk, seed) {
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(ArrayBuffer.isView(seed), 'seed must be a typed array')
  assert(
    pk.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
    "pk must be 'crypto_sign_PUBLICKEYBYTES' bytes"
  )
  assert(
    sk.byteLength === binding.crypto_sign_SECRETKEYBYTES,
    "sk must be 'crypto_sign_SECRETKEYBYTES' bytes"
  )
  assert(
    seed.byteLength === binding.crypto_sign_SEEDBYTES,
    "seed must be 'crypto_sign_SEEDBYTES' bytes"
  )

  const res = binding.crypto_sign_seed_keypair(pk, sk, seed)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_sign = function (sm, m, sk) {
  assert(ArrayBuffer.isView(sm), 'sm must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(
    sm.byteLength === binding.crypto_sign_BYTES + m.byteLength,
    "sm must be 'm.byteLength + crypto_sign_BYTES' bytes"
  )
  assert(
    sk.byteLength === binding.crypto_sign_SECRETKEYBYTES,
    "sk must be 'crypto_sign_SECRETKEYBYTES' bytes"
  )

  const res = binding.crypto_sign(sm, m, sk)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_sign_open = function (m, sm, pk) {
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(sm), 'sm must be a typed array')
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(
    sm.byteLength >= binding.crypto_sign_BYTES,
    "sm must be at least 'crypto_sign_BYTES' bytes"
  )
  assert(
    m.byteLength === sm.byteLength - binding.crypto_sign_BYTES,
    "m must be 'sm.byteLength - crypto_sign_BYTES' bytes"
  )
  assert(
    pk.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
    "pk must be 'crypto_sign_PUBLICKEYBYTES' bytes"
  )

  const res = binding.crypto_sign_open(m, sm, pk)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_sign_open = function (m, sm, pk) {
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(sm), 'sm must be a typed array')
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(
    sm.byteLength >= binding.crypto_sign_BYTES,
    "sm must be at least 'crypto_sign_BYTES' bytes"
  )
  assert(
    m.byteLength === sm.byteLength - binding.crypto_sign_BYTES,
    "m must be 'sm.byteLength - crypto_sign_BYTES' bytes"
  )
  assert(
    pk.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
    "pk must be 'crypto_sign_PUBLICKEYBYTES' bytes"
  )

  return binding.crypto_sign_open(m, sm, pk)
}

exports.crypto_sign_detached = function (sig, m, sk) {
  assert(ArrayBuffer.isView(sig), 'sig must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(sig.byteLength === binding.crypto_sign_BYTES, "sig must be 'crypto_sign_BYTES' bytes")
  assert(
    sk.byteLength === binding.crypto_sign_SECRETKEYBYTES,
    "sk must be 'crypto_sign_SECRETKEYBYTES' bytes"
  )

  const res = binding.crypto_sign_detached(sig, m, sk)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_sign_verify_detached = function (sig, m, pk) {
  assert(ArrayBuffer.isView(sig), 'sig must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(
    sig.byteLength >= binding.crypto_sign_BYTES,
    "sig must be at least 'crypto_sign_BYTES' bytes"
  )
  assert(
    pk.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
    "pk must be 'crypto_sign_PUBLICKEYBYTES' bytes"
  )

  return binding.crypto_sign_verify_detached(
    sig.buffer,
    sig.byteOffset,
    sig.byteLength,

    m.buffer,
    m.byteOffset,
    m.byteLength,

    pk.buffer,
    pk.byteOffset,
    pk.byteLength
  )
}

exports.crypto_sign_ed25519_sk_to_pk = function (pk, sk) {
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(
    pk.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
    "pk must be 'crypto_sign_PUBLICKEYBYTES' bytes"
  )
  assert(
    sk.byteLength === binding.crypto_sign_SECRETKEYBYTES,
    "sk must be 'crypto_sign_SECRETKEYBYTES' bytes"
  )

  const res = binding.crypto_sign_ed25519_sk_to_pk(pk, sk)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_sign_ed25519_pk_to_curve25519 = function (x25519pk, ed25519pk) {
  assert(ArrayBuffer.isView(x25519pk), 'x25519pk must be a typed array')
  assert(ArrayBuffer.isView(ed25519pk), 'ed25519pk must be a typed array')
  assert(
    x25519pk.byteLength === binding.crypto_box_PUBLICKEYBYTES,
    "x25519pk must be 'crypto_box_PUBLICKEYBYTES' bytes"
  )
  assert(
    ed25519pk.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
    "ed25519pk must be 'crypto_sign_PUBLICKEYBYTES' bytes"
  )

  const res = binding.crypto_sign_ed25519_pk_to_curve25519(x25519pk, ed25519pk)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_sign_ed25519_sk_to_curve25519 = function (x25519sk, ed25519sk) {
  assert(ArrayBuffer.isView(x25519sk), 'x25519sk must be a typed array')
  assert(ArrayBuffer.isView(ed25519sk), 'ed25519sk must be a typed array')
  assert(
    x25519sk.byteLength === binding.crypto_box_SECRETKEYBYTES,
    "x25519sk must be 'crypto_box_SECRETKEYBYTES' bytes"
  )

  const edLen = ed25519sk.byteLength

  assert(
    edLen === binding.crypto_sign_SECRETKEYBYTES || edLen === binding.crypto_box_SECRETKEYBYTES,
    "ed25519sk must be 'crypto_sign_SECRETKEYBYTES' or 'crypto_sign_SECRETKEYBYTES - crypto_sign_PUBLICKEYBYTES' bytes"
  )

  const res = binding.crypto_sign_ed25519_sk_to_curve25519(x25519sk, ed25519sk)

  if (res !== 0) throw new Error('status: ' + res)
}

// crypto_box

exports.crypto_box_keypair = function (pk, sk) {
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(
    pk.byteLength === binding.crypto_box_PUBLICKEYBYTES,
    "pk must be 'crypto_box_PUBLICKEYBYTES' bytes"
  )
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(
    sk.byteLength === binding.crypto_box_SECRETKEYBYTES,
    "sk must be 'crypto_box_SECRETKEYBYTES' bytes"
  )

  const res = binding.crypto_box_keypair(pk, sk)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_box_seed_keypair = function (pk, sk, seed) {
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(
    pk.byteLength === binding.crypto_box_PUBLICKEYBYTES,
    "pk must be 'crypto_box_PUBLICKEYBYTES' bytes"
  )
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(
    sk.byteLength === binding.crypto_box_SECRETKEYBYTES,
    "sk must be 'crypto_box_SECRETKEYBYTES' bytes"
  )
  assert(ArrayBuffer.isView(seed), 'seed must be a typed array')
  assert(
    seed.byteLength === binding.crypto_box_SEEDBYTES,
    "seed must be 'crypto_box_SEEDBYTES' bytes"
  )

  const res = binding.crypto_box_seed_keypair(pk, sk, seed)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_box_easy = function (c, m, n, pk, sk) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(
    c.byteLength === m.byteLength + exports.crypto_box_MACBYTES,
    "c must be 'm.byteLength + crypto_box_MACBYTES' bytes"
  )
  assert(n.byteLength === exports.crypto_box_NONCEBYTES, "n must be 'crypto_box_NONCEBYTES' bytes")
  assert(
    pk.byteLength === exports.crypto_box_PUBLICKEYBYTES,
    "pk must be 'crypto_box_PUBLICKEYBYTES' bytes"
  )
  assert(
    sk.byteLength === exports.crypto_box_SECRETKEYBYTES,
    "sk must be 'crypto_box_SECRETKEYBYTES' bytes"
  )

  const res = binding.crypto_box_easy(c, m, n, pk, sk)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_box_detached = function (c, mac, m, n, pk, sk) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(mac), 'mac must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(mac.byteLength === exports.crypto_box_MACBYTES, "mac must be 'crypto_box_MACBYTES' bytes")
  assert(n.byteLength === exports.crypto_box_NONCEBYTES, "n must be 'crypto_box_NONCEBYTES' bytes")
  assert(
    pk.byteLength === exports.crypto_box_PUBLICKEYBYTES,
    "pk must be 'crypto_box_PUBLICKEYBYTES' bytes"
  )
  assert(
    sk.byteLength === exports.crypto_box_SECRETKEYBYTES,
    "sk must be 'crypto_box_SECRETKEYBYTES' bytes"
  )

  const res = binding.crypto_box_detached(c, mac, m, n, pk, sk)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_box_open_easy = function (m, c, n, pk, sk) {
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(
    c.byteLength >= exports.crypto_box_MACBYTES,
    "c must be at least 'crypto_box_MACBYTES' bytes"
  )
  assert(
    m.byteLength === c.byteLength - exports.crypto_box_MACBYTES,
    "m must be 'c.byteLength - crypto_box_MACBYTES' bytes"
  )
  assert(n.byteLength === exports.crypto_box_NONCEBYTES, "n must be 'crypto_box_NONCEBYTES' bytes")
  assert(
    pk.byteLength === exports.crypto_box_PUBLICKEYBYTES,
    "pk must be 'crypto_box_PUBLICKEYBYTES' bytes"
  )
  assert(
    sk.byteLength === exports.crypto_box_SECRETKEYBYTES,
    "sk must be 'crypto_box_SECRETKEYBYTES' bytes"
  )

  return binding.crypto_box_open_easy(m, c, n, pk, sk)
}

exports.crypto_box_open_detached = function (m, c, mac, n, pk, sk) {
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(mac), 'mac must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(m.byteLength === c.byteLength, "m must be 'c.byteLength' bytes")
  assert(mac.byteLength === exports.crypto_box_MACBYTES, "mac must be 'crypto_box_MACBYTES' bytes")
  assert(n.byteLength === exports.crypto_box_NONCEBYTES, "n must be 'crypto_box_NONCEBYTES' bytes")
  assert(
    pk.byteLength === exports.crypto_box_PUBLICKEYBYTES,
    "pk must be 'crypto_box_PUBLICKEYBYTES' bytes"
  )
  assert(
    sk.byteLength === exports.crypto_box_SECRETKEYBYTES,
    "sk must be 'crypto_box_SECRETKEYBYTES' bytes"
  )

  return binding.crypto_box_open_detached(m, c, mac, n, pk, sk)
}

exports.crypto_box_seal = function (c, m, pk) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(
    c.byteLength === m.byteLength + exports.crypto_box_SEALBYTES,
    "c must be 'm.byteLength + crypto_box_SEALBYTES' bytes"
  )
  assert(
    pk.byteLength === exports.crypto_box_PUBLICKEYBYTES,
    "pk must be 'crypto_box_PUBLICKEYBYTES' bytes"
  )

  const res = binding.crypto_box_seal(c, m, pk)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_box_seal_open = function (m, c, pk, sk) {
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(
    c.byteLength >= exports.crypto_box_SEALBYTES,
    "c must be at least 'crypto_box_SEALBYTES' bytes"
  )
  assert(
    m.byteLength === c.byteLength - exports.crypto_box_SEALBYTES,
    "m must be 'c.byteLength - crypto_box_SEALBYTES' bytes"
  )
  assert(
    pk.byteLength === exports.crypto_box_PUBLICKEYBYTES,
    "pk must be 'crypto_box_PUBLICKEYBYTES' bytes"
  )
  assert(
    sk.byteLength === exports.crypto_box_SECRETKEYBYTES,
    "sk must be 'crypto_box_SECRETKEYBYTES' bytes"
  )

  return binding.crypto_box_seal_open(
    m.buffer,
    m.byteOffset,
    m.byteLength,

    c.buffer,
    c.byteOffset,
    c.byteLength,

    pk.buffer,
    pk.byteOffset,
    pk.byteLength,

    sk.buffer,
    sk.byteOffset,
    sk.byteLength
  )
}

// crypto_secretbox

exports.crypto_secretbox_easy = function (c, m, n, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    c.byteLength === m.byteLength + binding.crypto_secretbox_MACBYTES,
    "c must be 'm.byteLength + crypto_secretbox_MACBYTES' bytes"
  )
  assert(
    n.byteLength === binding.crypto_secretbox_NONCEBYTES,
    "n must be 'crypto_secretbox_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_secretbox_KEYBYTES,
    "k must be 'crypto_secretbox_KEYBYTES' bytes"
  )

  const res = binding.crypto_secretbox_easy(c, m, n, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_secretbox_open_easy = function (m, c, n, k) {
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    c.byteLength >= binding.crypto_secretbox_MACBYTES,
    "c must be at least 'crypto_secretbox_MACBYTES' bytes"
  )
  assert(
    m.byteLength === c.byteLength - binding.crypto_secretbox_MACBYTES,
    "m must be 'c.byteLength - crypto_secretbox_MACBYTES' bytes"
  )
  assert(
    n.byteLength === binding.crypto_secretbox_NONCEBYTES,
    "n must be 'crypto_secretbox_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_secretbox_KEYBYTES,
    "k must be 'crypto_secretbox_KEYBYTES' bytes"
  )

  return binding.crypto_secretbox_open_easy(m, c, n, k)
}

exports.crypto_secretbox_detached = function (c, mac, m, n, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(mac), 'mac must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(
    mac.byteLength === binding.crypto_secretbox_MACBYTES,
    "mac must be 'crypto_secretbox_MACBYTES' bytes"
  )
  assert(
    n.byteLength === binding.crypto_secretbox_NONCEBYTES,
    "n must be 'crypto_secretbox_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_secretbox_KEYBYTES,
    "k must be 'crypto_secretbox_KEYBYTES' bytes"
  )

  const res = binding.crypto_secretbox_detached(c, mac, m, n, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_secretbox_open_detached = function (m, c, mac, n, k) {
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(mac), 'mac must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(m.byteLength === c.byteLength, "m must be 'c.byteLength' bytes")
  assert(
    mac.byteLength === binding.crypto_secretbox_MACBYTES,
    "mac must be 'crypto_secretbox_MACBYTES' bytes"
  )
  assert(
    n.byteLength === binding.crypto_secretbox_NONCEBYTES,
    "n must be 'crypto_secretbox_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_secretbox_KEYBYTES,
    "k must be 'crypto_secretbox_KEYBYTES' bytes"
  )

  return binding.crypto_secretbox_open_detached(m, c, mac, n, k)
}

// crypto_generichash

exports.crypto_generichash = function (output, input, key) {
  if (!key) key = OPTIONAL

  assert(ArrayBuffer.isView(output), 'output must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(
    output.byteLength >= binding.crypto_generichash_BYTES_MIN &&
      output.byteLength <= binding.crypto_generichash_BYTES_MAX,
    'output must be between crypto_generichash_BYTES_MIN and crypto_generichash_BYTES_MAX bytes'
  )

  if (key !== OPTIONAL) {
    assert(ArrayBuffer.isView(key), 'key must be a typed array')
    assert(
      key.byteLength >= binding.crypto_generichash_KEYBYTES_MIN &&
        key.byteLength <= binding.crypto_generichash_KEYBYTES_MAX,
      'key must be between crypto_generichash_KEYBYTES_MIN and crypto_generichash_KEYBYTES_MAX bytes'
    )
  }

  const res = binding.crypto_generichash(
    output.buffer,
    output.byteOffset,
    output.byteLength,

    input.buffer,
    input.byteOffset,
    input.byteLength,

    key.buffer,
    key.byteOffset,
    key.byteLength
  )

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_generichash_batch = function (output, batch, key) {
  assert(ArrayBuffer.isView(output), 'output must be a typed array')

  if (isNode || batch.length < 4) {
    const res = binding.crypto_generichash_batch(output, batch, !!key, key || OPTIONAL)
    if (res !== 0) throw new Error('status: ' + res)
  } else {
    const state = Buffer.alloc(binding.crypto_generichash_STATEBYTES)

    exports.crypto_generichash_init(state, key, output.byteLength)

    for (const buf of batch) {
      exports.crypto_generichash_update(state, buf)
    }

    exports.crypto_generichash_final(state, output)
  }
}

exports.crypto_generichash_keygen = function (key) {
  assert(ArrayBuffer.isView(key), 'key must be a typed array')
  assert(
    key.byteLength === binding.crypto_generichash_KEYBYTES,
    "key must be 'crypto_generichash_KEYBYTES' bytes"
  )

  const res = binding.crypto_generichash_keygen(key.buffer, key.byteOffset, key.byteLength)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_generichash_init = function (state, key, outputLength) {
  if (!key) key = OPTIONAL

  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_generichash_STATEBYTES,
    "state must be 'crypto_generichash_STATEBYTES' bytes"
  )

  const res = binding.crypto_generichash_init(
    state.buffer,
    state.byteOffset,
    state.byteLength,

    key.buffer,
    key.byteOffset,
    key.byteLength,

    outputLength
  )

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_generichash_update = function (state, input) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(
    state.byteLength === binding.crypto_generichash_STATEBYTES,
    "state must be 'crypto_generichash_STATEBYTES' bytes"
  )

  const res = binding.crypto_generichash_update(
    state.buffer,
    state.byteOffset,
    state.byteLength,

    input.buffer,
    input.byteOffset,
    input.byteLength
  )

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_generichash_final = function (state, output) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(ArrayBuffer.isView(output), 'output must be a typed array')
  assert(
    state.byteLength === binding.crypto_generichash_STATEBYTES,
    "state must be 'crypto_generichash_STATEBYTES' bytes"
  )

  const res = binding.crypto_generichash_final(
    state.buffer,
    state.byteOffset,
    state.byteLength,

    output.buffer,
    output.byteOffset,
    output.byteLength
  )

  if (res !== 0) throw new Error('status: ' + res)
}

// secretstream

exports.crypto_secretstream_xchacha20poly1305_keygen = function (k) {
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_secretstream_xchacha20poly1305_KEYBYTES,
    "k must be 'crypto_secretstream_xchacha20poly1305_KEYBYTES' bytes"
  )

  binding.crypto_secretstream_xchacha20poly1305_keygen(k.buffer, k.byteOffset, k.byteLength)
}

exports.crypto_secretstream_xchacha20poly1305_init_push = function (state, header, k) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(ArrayBuffer.isView(header), 'header must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    state.byteLength === binding.crypto_secretstream_xchacha20poly1305_STATEBYTES,
    "state must be 'crypto_secretstream_xchacha20poly1305_STATEBYTES' bytes"
  )
  assert(
    header.byteLength === binding.crypto_secretstream_xchacha20poly1305_HEADERBYTES,
    "header must be 'crypto_secretstream_xchacha20poly1305_HEADERBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_secretstream_xchacha20poly1305_KEYBYTES,
    "k must be 'crypto_secretstream_xchacha20poly1305_KEYBYTES' bytes"
  )

  const res = binding.crypto_secretstream_xchacha20poly1305_init_push(
    state.buffer,
    state.byteOffset,
    state.byteLength,

    header.buffer,
    header.byteOffset,
    header.byteLength,

    k.buffer,
    k.byteOffset,
    k.byteLength
  )

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_secretstream_xchacha20poly1305_init_pull = function (state, header, k) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(ArrayBuffer.isView(header), 'header must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    state.byteLength === binding.crypto_secretstream_xchacha20poly1305_STATEBYTES,
    "state must be 'crypto_secretstream_xchacha20poly1305_STATEBYTES' bytes"
  )
  assert(
    header.byteLength === binding.crypto_secretstream_xchacha20poly1305_HEADERBYTES,
    "header must be 'crypto_secretstream_xchacha20poly1305_HEADERBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_secretstream_xchacha20poly1305_KEYBYTES,
    "k must be 'crypto_secretstream_xchacha20poly1305_KEYBYTES' bytes"
  )

  const res = binding.crypto_secretstream_xchacha20poly1305_init_pull(
    state.buffer,
    state.byteOffset,
    state.byteLength,

    header.buffer,
    header.byteOffset,
    header.byteLength,

    k.buffer,
    k.byteOffset,
    k.byteLength
  )

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_secretstream_xchacha20poly1305_push = function (state, c, m, ad, tag) {
  if (!ad) ad = OPTIONAL

  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(
    state.byteLength === binding.crypto_secretstream_xchacha20poly1305_STATEBYTES,
    "state must be 'crypto_secretstream_xchacha20poly1305_STATEBYTES' bytes"
  )
  assert(
    c.byteLength === m.byteLength + binding.crypto_secretstream_xchacha20poly1305_ABYTES,
    "c must be 'm.byteLength + crypto_secretstream_xchacha20poly1305_ABYTES' bytes"
  )

  const res = binding.crypto_secretstream_xchacha20poly1305_push(
    state.buffer,
    state.byteOffset,
    state.byteLength,

    c.buffer,
    c.byteOffset,
    c.byteLength,

    m.buffer,
    m.byteOffset,
    m.byteLength,

    ad.buffer,
    ad.byteOffset,
    ad.byteLength,

    tag
  )

  if (res < 0) throw new Error('push failed')

  return res
}

exports.crypto_secretstream_xchacha20poly1305_pull = function (state, m, tag, c, ad) {
  if (!ad) ad = OPTIONAL

  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_secretstream_xchacha20poly1305_STATEBYTES,
    "state must be 'crypto_secretstream_xchacha20poly1305_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(tag), 'tag must be a typed array')
  assert(tag.byteLength === 1, 'tag must be 1 byte')

  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(
    c.byteLength >= binding.crypto_secretstream_xchacha20poly1305_ABYTES,
    "c must be at least 'crypto_secretstream_xchacha20poly1305_ABYTES' bytes"
  )
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(
    m.byteLength === c.byteLength - binding.crypto_secretstream_xchacha20poly1305_ABYTES,
    "m must be 'c.byteLength - crypto_secretstream_xchacha20poly1305_ABYTES' bytes"
  )

  const res = binding.crypto_secretstream_xchacha20poly1305_pull(
    state.buffer,
    state.byteOffset,
    state.byteLength,

    m.buffer,
    m.byteOffset,
    m.byteLength,

    tag.buffer,
    tag.byteOffset,
    tag.byteLength,

    c.buffer,
    c.byteOffset,
    c.byteLength,

    ad.buffer,
    ad.byteOffset,
    ad.byteLength
  )

  if (res < 0) throw new Error('pull failed')

  return res
}

exports.crypto_secretstream_xchacha20poly1305_rekey = function (state) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_secretstream_xchacha20poly1305_STATEBYTES,
    "state must be 'crypto_secretstream_xchacha20poly1305_STATEBYTES' bytes"
  )

  binding.crypto_secretstream_xchacha20poly1305_rekey(
    state.buffer,
    state.byteOffset,
    state.byteLength
  )
}

// crypto_stream

exports.crypto_stream = function (c, n, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    n.byteLength === binding.crypto_stream_NONCEBYTES,
    "n must be 'crypto_stream_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_KEYBYTES,
    "k must be 'crypto_stream_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream(c, n, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_xor = function (c, m, n, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(
    n.byteLength === binding.crypto_stream_NONCEBYTES,
    "n must be 'crypto_stream_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_KEYBYTES,
    "k must be 'crypto_stream_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_xor(
    c.buffer,
    c.byteOffset,
    c.byteLength,

    m.buffer,
    m.byteOffset,
    m.byteLength,

    n.buffer,
    n.byteOffset,
    n.byteLength,

    k.buffer,
    k.byteOffset,
    k.byteLength
  )

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_chacha20 = function (c, n, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    n.byteLength === binding.crypto_stream_chacha20_NONCEBYTES,
    "n must be 'crypto_stream_chacha20_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_chacha20_KEYBYTES,
    "k must be 'crypto_stream_chacha20_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_chacha20(c, n, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_chacha20_xor = function (c, m, n, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(
    n.byteLength === binding.crypto_stream_chacha20_NONCEBYTES,
    "n must be 'crypto_stream_chacha20_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_chacha20_KEYBYTES,
    "k must be 'crypto_stream_chacha20_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_chacha20_xor(c, m, n, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_chacha20_xor_ic = function (c, m, n, ic, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(
    n.byteLength === binding.crypto_stream_chacha20_NONCEBYTES,
    "n must be 'crypto_stream_chacha20_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_chacha20_KEYBYTES,
    "k must be 'crypto_stream_chacha20_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_chacha20_xor_ic(c, m, n, ic, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_chacha20_ietf = function (c, n, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    n.byteLength === binding.crypto_stream_chacha20_ietf_NONCEBYTES,
    "n must be 'crypto_stream_chacha20_ietf_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_chacha20_ietf_KEYBYTES,
    "k must be 'crypto_stream_chacha20_ietf_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_chacha20_ietf(c, n, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_chacha20_ietf_xor = function (c, m, n, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(
    n.byteLength === binding.crypto_stream_chacha20_ietf_NONCEBYTES,
    "n must be 'crypto_stream_chacha20_ietf_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_chacha20_ietf_KEYBYTES,
    "k must be 'crypto_stream_chacha20_ietf_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_chacha20_ietf_xor(c, m, n, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_chacha20_ietf_xor_ic = function (c, m, n, ic, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(
    n.byteLength === binding.crypto_stream_chacha20_ietf_NONCEBYTES,
    "n must be 'crypto_stream_chacha20_ietf_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_chacha20_ietf_KEYBYTES,
    "k must be 'crypto_stream_chacha20_ietf_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_chacha20_ietf_xor_ic(c, m, n, ic, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_xchacha20 = function (c, n, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    n.byteLength === binding.crypto_stream_xchacha20_NONCEBYTES,
    "n must be 'crypto_stream_xchacha20_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_xchacha20_KEYBYTES,
    "k must be 'crypto_stream_xchacha20_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_xchacha20(c, n, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_xchacha20_xor = function (c, m, n, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(
    n.byteLength === binding.crypto_stream_xchacha20_NONCEBYTES,
    "n must be 'crypto_stream_xchacha20_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_xchacha20_KEYBYTES,
    "k must be 'crypto_stream_xchacha20_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_xchacha20_xor(c, m, n, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_xchacha20_xor_ic = function (c, m, n, ic, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(
    n.byteLength === binding.crypto_stream_xchacha20_NONCEBYTES,
    "n must be 'crypto_stream_xchacha20_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_xchacha20_KEYBYTES,
    "k must be 'crypto_stream_xchacha20_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_xchacha20_xor_ic(c, m, n, ic, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_salsa20 = function (c, n, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    n.byteLength === binding.crypto_stream_salsa20_NONCEBYTES,
    "n must be 'crypto_stream_salsa20_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_salsa20_KEYBYTES,
    "k must be 'crypto_stream_salsa20_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_salsa20(c, n, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_salsa20_xor = function (c, m, n, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(
    n.byteLength === binding.crypto_stream_salsa20_NONCEBYTES,
    "n must be 'crypto_stream_salsa20_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_salsa20_KEYBYTES,
    "k must be 'crypto_stream_salsa20_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_salsa20_xor(c, m, n, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_salsa20_xor_ic = function (c, m, n, ic, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(
    n.byteLength === binding.crypto_stream_salsa20_NONCEBYTES,
    "n must be 'crypto_stream_salsa20_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_salsa20_KEYBYTES,
    "k must be 'crypto_stream_salsa20_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_salsa20_xor_ic(c, m, n, ic, k)

  if (res !== 0) throw new Error('status: ' + res)
}

// crypto_auth

exports.crypto_auth = function (out, input, k) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(out.byteLength === binding.crypto_auth_BYTES, "out must be 'crypto_auth_BYTES' bytes")
  assert(k.byteLength === binding.crypto_auth_KEYBYTES, "k must be 'crypto_auth_KEYBYTES' bytes")

  const res = binding.crypto_auth(out, input, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_auth_verify = function (h, input, k) {
  assert(ArrayBuffer.isView(h), 'h must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(h.byteLength === binding.crypto_auth_BYTES, "h must be 'crypto_auth_BYTES' bytes")
  assert(k.byteLength === binding.crypto_auth_KEYBYTES, "k must be 'crypto_auth_KEYBYTES' bytes")

  return binding.crypto_auth_verify(h, input, k)
}

// crypto_onetimeauth

exports.crypto_onetimeauth = function (out, input, k) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    out.byteLength === binding.crypto_onetimeauth_BYTES,
    "out must be 'crypto_onetimeauth_BYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_onetimeauth_KEYBYTES,
    "k must be 'crypto_onetimeauth_KEYBYTES' bytes"
  )

  const res = binding.crypto_onetimeauth(out, input, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_onetimeauth_init = function (state, k) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    state.byteLength === binding.crypto_onetimeauth_STATEBYTES,
    "state must be 'crypto_onetimeauth_STATEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_onetimeauth_KEYBYTES,
    "k must be 'crypto_onetimeauth_KEYBYTES' bytes"
  )

  const res = binding.crypto_onetimeauth_init(state, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_onetimeauth_update = function (state, input) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(
    state.byteLength === binding.crypto_onetimeauth_STATEBYTES,
    "state must be 'crypto_onetimeauth_STATEBYTES' bytes"
  )

  const res = binding.crypto_onetimeauth_update(state, input)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_onetimeauth_final = function (state, out) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(
    state.byteLength === binding.crypto_onetimeauth_STATEBYTES,
    "state must be 'crypto_onetimeauth_STATEBYTES' bytes"
  )
  assert(
    out.byteLength === binding.crypto_onetimeauth_BYTES,
    "out must be 'crypto_onetimeauth_BYTES' bytes"
  )

  const res = binding.crypto_onetimeauth_final(state, out)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_onetimeauth_verify = function (h, input, k) {
  assert(ArrayBuffer.isView(h), 'h must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    h.byteLength === binding.crypto_onetimeauth_BYTES,
    "h must be 'crypto_onetimeauth_BYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_onetimeauth_KEYBYTES,
    "k must be 'crypto_onetimeauth_KEYBYTES' bytes"
  )

  return binding.crypto_onetimeauth_verify(h, input, k)
}

// crypto_pwhash

exports.crypto_pwhash = function (out, passwd, salt, opslimit, memlimit, alg) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(ArrayBuffer.isView(salt), 'salt must be a typed array')
  assert(
    out.byteLength >= binding.crypto_pwhash_BYTES_MIN,
    "out must be at least 'crypto_pwhash_BYTES_MIN' bytes"
  )
  assert(
    out.byteLength <= binding.crypto_pwhash_BYTES_MAX,
    "out must be at most 'crypto_pwhash_BYTES_MAX' bytes"
  )
  assert(
    salt.byteLength === binding.crypto_pwhash_SALTBYTES,
    "salt must be 'crypto_pwhash_SALTBYTES' bytes"
  )
  assert(
    opslimit >= binding.crypto_pwhash_OPSLIMIT_MIN,
    "opslimit must be at least 'crypto_pwhash_OPSLIMIT_MIN'"
  )
  assert(
    opslimit <= binding.crypto_pwhash_OPSLIMIT_MAX,
    "opslimit must be at most 'crypto_pwhash_OPSLIMIT_MAX'"
  )
  assert(
    memlimit >= binding.crypto_pwhash_MEMLIMIT_MIN,
    "memlimit must be at least 'crypto_pwhash_MEMLIMIT_MIN'"
  )
  assert(
    memlimit <= binding.crypto_pwhash_MEMLIMIT_MAX,
    "memlimit must be at most 'crypto_pwhash_MEMLIMIT_MAX'"
  )
  assert(alg >= 1 && alg <= 2, 'alg must be either Argon2i 1.3 or Argon2id 1.3')

  const res = binding.crypto_pwhash(out, passwd, salt, opslimit, memlimit, alg)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_pwhash_async = function (
  out,
  passwd,
  salt,
  opslimit,
  memlimit,
  alg,
  callback = undefined
) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(ArrayBuffer.isView(salt), 'salt must be a typed array')
  assert(
    out.byteLength >= binding.crypto_pwhash_BYTES_MIN,
    "out must be at least 'crypto_pwhash_BYTES_MIN' bytes"
  )
  assert(
    out.byteLength <= binding.crypto_pwhash_BYTES_MAX,
    "out must be at most 'crypto_pwhash_BYTES_MAX' bytes"
  )
  assert(
    salt.byteLength === binding.crypto_pwhash_SALTBYTES,
    "salt must be 'crypto_pwhash_SALTBYTES' bytes"
  )
  assert(
    opslimit >= binding.crypto_pwhash_OPSLIMIT_MIN,
    "opslimit must be at least 'crypto_pwhash_OPSLIMIT_MIN'"
  )
  assert(
    opslimit <= binding.crypto_pwhash_OPSLIMIT_MAX,
    "opslimit must be at most 'crypto_pwhash_OPSLIMIT_MAX'"
  )
  assert(
    memlimit >= binding.crypto_pwhash_MEMLIMIT_MIN,
    "memlimit must be at least 'crypto_pwhash_MEMLIMIT_MIN'"
  )
  assert(
    memlimit <= binding.crypto_pwhash_MEMLIMIT_MAX,
    "memlimit must be at most 'crypto_pwhash_MEMLIMIT_MAX'"
  )
  assert(alg >= 1 && alg <= 2, 'alg must be either Argon2i 1.3 or Argon2id 1.3')

  const [done, promise] = checkStatus(callback)

  binding.crypto_pwhash_async(
    out.buffer,
    out.byteOffset,
    out.byteLength,

    passwd.buffer,
    passwd.byteOffset,
    passwd.byteLength,

    salt.buffer,
    salt.byteOffset,
    salt.byteLength,

    opslimit,
    memlimit,
    alg,

    done
  )

  return promise
}

exports.crypto_pwhash_str = function (out, passwd, opslimit, memlimit) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(
    out.byteLength === binding.crypto_pwhash_STRBYTES,
    "out must be 'crypto_pwhash_STRBYTES' bytes"
  )
  assert(typeof opslimit === 'number', 'opslimit must be a number')
  assert(
    opslimit >= binding.crypto_pwhash_OPSLIMIT_MIN,
    "opslimit must be at least 'crypto_pwhash_OPSLIMIT_MIN'"
  )
  assert(
    opslimit <= binding.crypto_pwhash_OPSLIMIT_MAX,
    "opslimit must be at most 'crypto_pwhash_OPSLIMIT_MAX'"
  )
  assert(typeof memlimit === 'number', 'memlimit must be a number')
  assert(
    memlimit >= binding.crypto_pwhash_MEMLIMIT_MIN,
    "memlimit must be at least 'crypto_pwhash_MEMLIMIT_MIN'"
  )
  assert(
    memlimit <= binding.crypto_pwhash_MEMLIMIT_MAX,
    "memlimit must be at most 'crypto_pwhash_MEMLIMIT_MAX'"
  )

  const res = binding.crypto_pwhash_str(out, passwd, opslimit, memlimit)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_pwhash_str_async = function (out, passwd, opslimit, memlimit, callback = undefined) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(
    out.byteLength === binding.crypto_pwhash_STRBYTES,
    "out must be 'crypto_pwhash_STRBYTES' bytes"
  )
  assert(passwd.byteLength > 0, 'passwd must not be empty')
  assert(typeof opslimit === 'number', 'opslimit must be a number')
  assert(
    opslimit >= binding.crypto_pwhash_OPSLIMIT_MIN,
    "opslimit must be at least 'crypto_pwhash_OPSLIMIT_MIN'"
  )
  assert(
    opslimit <= binding.crypto_pwhash_OPSLIMIT_MAX,
    "opslimit must be at most 'crypto_pwhash_OPSLIMIT_MAX'"
  )
  assert(typeof memlimit === 'number', 'memlimit must be a number')
  assert(
    memlimit >= binding.crypto_pwhash_MEMLIMIT_MIN,
    "memlimit must be at least 'crypto_pwhash_MEMLIMIT_MIN'"
  )
  assert(
    memlimit <= binding.crypto_pwhash_MEMLIMIT_MAX,
    "memlimit must be at most 'crypto_pwhash_MEMLIMIT_MAX'"
  )

  const [done, promise] = checkStatus(callback)

  binding.crypto_pwhash_str_async(
    out.buffer,
    out.byteOffset,
    out.byteLength,

    passwd.buffer,
    passwd.byteOffset,
    passwd.byteLength,

    opslimit,
    memlimit,

    done
  )

  return promise
}

exports.crypto_pwhash_str_verify = function (str, passwd) {
  assert(ArrayBuffer.isView(str), 'str must be a typed array')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(
    str.byteLength === binding.crypto_pwhash_STRBYTES,
    "str must be 'crypto_pwhash_STRBYTES' bytes"
  )

  return binding.crypto_pwhash_str_verify(str, passwd)
}

exports.crypto_pwhash_str_verify_async = function (str, passwd, callback = undefined) {
  assert(ArrayBuffer.isView(str), 'str must be a typed array')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(
    str.byteLength === binding.crypto_pwhash_STRBYTES,
    "str must be 'crypto_pwhash_STRBYTES' bytes"
  )
  assert(passwd.byteLength > 0, 'passwd must not be empty')

  const [done, promise] = checkStatus(callback, true)

  binding.crypto_pwhash_str_verify_async(
    str.buffer,
    str.byteOffset,
    str.byteLength,

    passwd.buffer,
    passwd.byteOffset,
    passwd.byteLength,

    done
  )

  return promise
}

exports.crypto_pwhash_str_needs_rehash = function (str, opslimit, memlimit) {
  assert(ArrayBuffer.isView(str), 'str must be a typed array')
  assert(
    str.byteLength === binding.crypto_pwhash_STRBYTES,
    "str must be 'crypto_pwhash_STRBYTES' bytes"
  )
  assert(
    opslimit >= binding.crypto_pwhash_OPSLIMIT_MIN,
    "opslimit must be at least 'crypto_pwhash_OPSLIMIT_MIN'"
  )
  assert(
    opslimit <= binding.crypto_pwhash_OPSLIMIT_MAX,
    "opslimit must be at most 'crypto_pwhash_OPSLIMIT_MAX'"
  )
  assert(
    memlimit >= binding.crypto_pwhash_MEMLIMIT_MIN,
    "memlimit must be at least 'crypto_pwhash_MEMLIMIT_MIN'"
  )
  assert(
    memlimit <= binding.crypto_pwhash_MEMLIMIT_MAX,
    "memlimit must be at most 'crypto_pwhash_MEMLIMIT_MAX'"
  )

  return binding.crypto_pwhash_str_needs_rehash(str, opslimit, memlimit)
}

exports.crypto_pwhash_scryptsalsa208sha256 = function (out, passwd, salt, opslimit, memlimit) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(ArrayBuffer.isView(salt), 'salt must be a typed array')
  assert(
    out.byteLength >= binding.crypto_pwhash_scryptsalsa208sha256_BYTES_MIN,
    "out must be at least 'crypto_pwhash_scryptsalsa208sha256_BYTES_MIN' bytes"
  )
  assert(
    out.byteLength <= binding.crypto_pwhash_scryptsalsa208sha256_BYTES_MAX,
    "out must be at most 'crypto_pwhash_scryptsalsa208sha256_BYTES_MAX' bytes"
  )
  assert(
    salt.byteLength === binding.crypto_pwhash_scryptsalsa208sha256_SALTBYTES,
    "salt must be 'crypto_pwhash_scryptsalsa208sha256_SALTBYTES' bytes"
  )
  assert(
    opslimit >= binding.crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MIN,
    "opslimit must be at least 'crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MIN'"
  )
  assert(
    opslimit <= binding.crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MAX,
    "opslimit must be at most 'crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MAX'"
  )
  assert(
    memlimit >= binding.crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MIN,
    "memlimit must be at least 'crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MIN'"
  )
  assert(
    memlimit <= binding.crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MAX,
    "memlimit must be at most 'crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MAX'"
  )

  const res = binding.crypto_pwhash_scryptsalsa208sha256(out, passwd, salt, opslimit, memlimit)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_pwhash_scryptsalsa208sha256_async = function (
  out,
  passwd,
  salt,
  opslimit,
  memlimit,
  callback = undefined
) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(ArrayBuffer.isView(salt), 'salt must be a typed array')
  assert(
    out.byteLength >= binding.crypto_pwhash_scryptsalsa208sha256_BYTES_MIN,
    "out must be at least 'crypto_pwhash_scryptsalsa208sha256_BYTES_MIN' bytes"
  )
  assert(
    out.byteLength <= binding.crypto_pwhash_scryptsalsa208sha256_BYTES_MAX,
    "out must be at most 'crypto_pwhash_scryptsalsa208sha256_BYTES_MAX' bytes"
  )
  assert(passwd.byteLength > 0, 'passwd must not be empty')
  assert(
    salt.byteLength === binding.crypto_pwhash_scryptsalsa208sha256_SALTBYTES,
    "salt must be 'crypto_pwhash_scryptsalsa208sha256_SALTBYTES' bytes"
  )
  assert(
    opslimit >= binding.crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MIN,
    "opslimit must be at least 'crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MIN'"
  )
  assert(
    opslimit <= binding.crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MAX,
    "opslimit must be at most 'crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MAX'"
  )
  assert(
    memlimit >= binding.crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MIN,
    "memlimit must be at least 'crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MIN'"
  )
  assert(
    memlimit <= binding.crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MAX,
    "memlimit must be at most 'crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MAX'"
  )

  const [done, promise] = checkStatus(callback)

  binding.crypto_pwhash_scryptsalsa208sha256_async(
    out.buffer,
    out.byteOffset,
    out.byteLength,

    passwd.buffer,
    passwd.byteOffset,
    passwd.byteLength,

    salt.buffer,
    salt.byteOffset,
    salt.byteLength,

    opslimit,
    memlimit,

    done
  )

  return promise
}

exports.crypto_pwhash_scryptsalsa208sha256_str_async = function (
  out,
  passwd,
  opslimit,
  memlimit,
  callback = undefined
) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(
    out.byteLength === binding.crypto_pwhash_scryptsalsa208sha256_STRBYTES,
    "out must be 'crypto_pwhash_scryptsalsa208sha256_STRBYTES' bytes"
  )
  assert(passwd.byteLength > 0, 'passwd must not be empty')
  assert(
    opslimit >= binding.crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MIN,
    "opslimit must be at least 'crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MIN'"
  )
  assert(
    opslimit <= binding.crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MAX,
    "opslimit must be at most 'crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MAX'"
  )
  assert(
    memlimit >= binding.crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MIN,
    "memlimit must be at least 'crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MIN'"
  )
  assert(
    memlimit <= binding.crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MAX,
    "memlimit must be at most 'crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MAX'"
  )

  const [done, promise] = checkStatus(callback)

  binding.crypto_pwhash_scryptsalsa208sha256_str_async(
    out.buffer,
    out.byteOffset,
    out.byteLength,

    passwd.buffer,
    passwd.byteOffset,
    passwd.byteLength,

    opslimit,
    memlimit,

    done
  )

  return promise
}

exports.crypto_pwhash_scryptsalsa208sha256_str = function (out, passwd, opslimit, memlimit) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(
    out.byteLength === binding.crypto_pwhash_scryptsalsa208sha256_STRBYTES,
    "out must be 'crypto_pwhash_scryptsalsa208sha256_STRBYTES' bytes"
  )
  assert(passwd.byteLength > 0, 'passwd must not be empty')
  assert(
    opslimit >= binding.crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MIN,
    "opslimit must be at least 'crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MIN'"
  )
  assert(
    opslimit <= binding.crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MAX,
    "opslimit must be at most 'crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MAX'"
  )
  assert(
    memlimit >= binding.crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MIN,
    "memlimit must be at least 'crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MIN'"
  )
  assert(
    memlimit <= binding.crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MAX,
    "memlimit must be at most 'crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MAX'"
  )

  const res = binding.crypto_pwhash_scryptsalsa208sha256_str(out, passwd, opslimit, memlimit)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_pwhash_scryptsalsa208sha256_str_verify_async = function (
  str,
  passwd,
  callback = undefined
) {
  assert(ArrayBuffer.isView(str), 'str must be a typed array')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(
    str.byteLength === binding.crypto_pwhash_scryptsalsa208sha256_STRBYTES,
    "str must be 'crypto_pwhash_scryptsalsa208sha256_STRBYTES' bytes"
  )
  assert(passwd.byteLength > 0, 'passwd must not be empty')

  const [done, promise] = checkStatus(callback, true)

  binding.crypto_pwhash_scryptsalsa208sha256_str_verify_async(
    str.buffer,
    str.byteOffset,
    str.byteLength,

    passwd.buffer,
    passwd.byteOffset,
    passwd.byteLength,

    done
  )

  return promise
}

exports.crypto_pwhash_scryptsalsa208sha256_str_verify = function (str, passwd) {
  assert(ArrayBuffer.isView(str), 'str must be a typed array')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(
    str.byteLength === binding.crypto_pwhash_scryptsalsa208sha256_STRBYTES,
    "str must be 'crypto_pwhash_scryptsalsa208sha256_STRBYTES' bytes"
  )
  assert(passwd.byteLength > 0, 'passwd must not be empty')

  return binding.crypto_pwhash_scryptsalsa208sha256_str_verify(str, passwd)
}

exports.crypto_pwhash_scryptsalsa208sha256_str_needs_rehash = function (str, opslimit, memlimit) {
  assert(ArrayBuffer.isView(str), 'str must be a typed array')
  assert(
    str.byteLength === binding.crypto_pwhash_scryptsalsa208sha256_STRBYTES,
    "str must be 'crypto_pwhash_scryptsalsa208sha256_STRBYTES' bytes"
  )
  assert(
    opslimit >= binding.crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MIN,
    "opslimit must be at least 'crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MIN'"
  )
  assert(
    opslimit <= binding.crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MAX,
    "opslimit must be at most 'crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MAX'"
  )
  assert(
    memlimit >= binding.crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MIN,
    "memlimit must be at least 'crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MIN'"
  )
  assert(
    memlimit <= binding.crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MAX,
    "memlimit must be at most 'crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MAX'"
  )

  return binding.crypto_pwhash_scryptsalsa208sha256_str_needs_rehash(str, opslimit, memlimit)
}

// crypto_kx

exports.crypto_kx_keypair = function (pk, sk) {
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(
    pk.byteLength === binding.crypto_kx_PUBLICKEYBYTES,
    "pk must be 'crypto_kx_PUBLICKEYBYTES' bytes"
  )
  assert(
    sk.byteLength === binding.crypto_kx_SECRETKEYBYTES,
    "sk must be 'crypto_kx_SECRETKEYBYTES' bytes"
  )

  const res = binding.crypto_kx_keypair(pk, sk)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_kx_seed_keypair = function (pk, sk, seed) {
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(ArrayBuffer.isView(seed), 'seed must be a typed array')
  assert(
    pk.byteLength === binding.crypto_kx_PUBLICKEYBYTES,
    "pk must be 'crypto_kx_PUBLICKEYBYTES' bytes"
  )
  assert(
    sk.byteLength === binding.crypto_kx_SECRETKEYBYTES,
    "sk must be 'crypto_kx_SECRETKEYBYTES' bytes"
  )
  assert(
    seed.byteLength === binding.crypto_kx_SEEDBYTES,
    "seed must be 'crypto_kx_SEEDBYTES' bytes"
  )

  const res = binding.crypto_kx_seed_keypair(pk, sk, seed)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_kx_client_session_keys = function (rx, tx, clientPk, clientSk, serverPk) {
  if (!rx) rx = undefined
  if (!tx) tx = undefined

  assert(rx || tx, 'at least one session key must be specified')

  if (rx) {
    assert(ArrayBuffer.isView(rx), 'rx must be a typed array')
    assert(
      rx.byteLength === binding.crypto_kx_SESSIONKEYBYTES,
      "rx must be 'crypto_kx_SESSIONKEYBYTES' bytes"
    )
  }

  if (tx) {
    assert(ArrayBuffer.isView(tx), 'tx must be a typed array')
    assert(
      tx.byteLength === binding.crypto_kx_SESSIONKEYBYTES,
      "tx must be 'crypto_kx_SESSIONKEYBYTES' bytes"
    )
  }

  assert(ArrayBuffer.isView(clientPk), 'clientPk must be a typed array')
  assert(ArrayBuffer.isView(clientSk), 'clientSk must be a typed array')
  assert(ArrayBuffer.isView(serverPk), 'serverPk must be a typed array')
  assert(
    clientPk.byteLength === binding.crypto_kx_PUBLICKEYBYTES,
    "clientPk must be 'crypto_kx_PUBLICKEYBYTES' bytes"
  )
  assert(
    clientSk.byteLength === binding.crypto_kx_SECRETKEYBYTES,
    "clientSk must be 'crypto_kx_SECRETKEYBYTES' bytes"
  )
  assert(
    serverPk.byteLength === binding.crypto_kx_PUBLICKEYBYTES,
    "serverPk must be 'crypto_kx_PUBLICKEYBYTES' bytes"
  )

  const res = binding.crypto_kx_client_session_keys(rx, tx, clientPk, clientSk, serverPk)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_kx_server_session_keys = function (rx, tx, serverPk, serverSk, clientPk) {
  if (!rx) rx = undefined
  if (!tx) tx = undefined

  assert(rx || tx, 'at least one session key must be specified')

  if (rx) {
    assert(ArrayBuffer.isView(rx), 'rx must be a typed array')
    assert(
      rx.byteLength === binding.crypto_kx_SESSIONKEYBYTES,
      "rx must be 'crypto_kx_SESSIONKEYBYTES' bytes"
    )
  }

  if (tx) {
    assert(ArrayBuffer.isView(tx), 'tx must be a typed array')
    assert(
      tx.byteLength === binding.crypto_kx_SESSIONKEYBYTES,
      "tx must be 'crypto_kx_SESSIONKEYBYTES' bytes"
    )
  }

  assert(ArrayBuffer.isView(serverPk), 'serverPk must be a typed array')
  assert(ArrayBuffer.isView(serverSk), 'serverSk must be a typed array')
  assert(ArrayBuffer.isView(clientPk), 'clientPk must be a typed array')
  assert(
    serverPk.byteLength === binding.crypto_kx_PUBLICKEYBYTES,
    "serverPk must be 'crypto_kx_PUBLICKEYBYTES' bytes"
  )
  assert(
    serverSk.byteLength === binding.crypto_kx_SECRETKEYBYTES,
    "serverSk must be 'crypto_kx_SECRETKEYBYTES' bytes"
  )
  assert(
    clientPk.byteLength === binding.crypto_kx_PUBLICKEYBYTES,
    "clientPk must be 'crypto_kx_PUBLICKEYBYTES' bytes"
  )

  const res = binding.crypto_kx_server_session_keys(rx, tx, serverPk, serverSk, clientPk)

  if (res !== 0) throw new Error('status: ' + res)
}

// crypto_scalarmult

exports.crypto_scalarmult_base = function (q, n) {
  assert(ArrayBuffer.isView(q), 'q must be a typed array')
  assert(
    q.byteLength === binding.crypto_scalarmult_BYTES,
    "q must be 'crypto_scalarmult_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.crypto_scalarmult_SCALARBYTES,
    "n must be 'crypto_scalarmult_SCALARBYTES' bytes"
  )

  const res = binding.crypto_scalarmult_base(q, n)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_scalarmult = function (q, n, p) {
  assert(ArrayBuffer.isView(q), 'q must be a typed array')
  assert(
    q.byteLength === binding.crypto_scalarmult_BYTES,
    "q must be 'crypto_scalarmult_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.crypto_scalarmult_SCALARBYTES,
    "n must be 'crypto_scalarmult_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(p), 'p must be a typed array')
  assert(
    p.byteLength === binding.crypto_scalarmult_BYTES,
    "p must be 'crypto_scalarmult_BYTES' bytes"
  )

  const res = binding.crypto_scalarmult(q, n, p)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_scalarmult_ed25519_base = function (q, n) {
  assert(ArrayBuffer.isView(q), 'q must be a typed array')
  assert(
    q.byteLength === binding.crypto_scalarmult_ed25519_BYTES,
    "q must be 'crypto_scalarmult_ed25519_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.crypto_scalarmult_ed25519_SCALARBYTES,
    "n must be 'crypto_scalarmult_ed25519_SCALARBYTES' bytes"
  )

  const res = binding.crypto_scalarmult_ed25519_base(q, n)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_scalarmult_ed25519 = function (q, n, p) {
  assert(ArrayBuffer.isView(q), 'q must be a typed array')
  assert(
    q.byteLength === binding.crypto_scalarmult_ed25519_BYTES,
    "q must be 'crypto_scalarmult_ed25519_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.crypto_scalarmult_ed25519_SCALARBYTES,
    "n must be 'crypto_scalarmult_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(p), 'p must be a typed array')
  assert(
    p.byteLength === binding.crypto_scalarmult_ed25519_BYTES,
    "p must be 'crypto_scalarmult_ed25519_BYTES' bytes"
  )

  const res = binding.crypto_scalarmult_ed25519(q, n, p)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_core_ed25519_is_valid_point = function (p) {
  assert(ArrayBuffer.isView(p), 'p must be a typed array')
  assert(
    p.byteLength === binding.crypto_core_ed25519_BYTES,
    "p must be 'crypto_core_ed25519_BYTES' bytes"
  )

  return binding.crypto_core_ed25519_is_valid_point(p)
}

exports.crypto_core_ed25519_from_uniform = function (p, r) {
  assert(ArrayBuffer.isView(p), 'p must be a typed array')
  assert(
    p.byteLength === binding.crypto_core_ed25519_BYTES,
    "p must be 'crypto_core_ed25519_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(r), 'r must be a typed array')
  assert(
    r.byteLength === binding.crypto_core_ed25519_UNIFORMBYTES,
    "r must be 'crypto_core_ed25519_UNIFORMBYTES' bytes"
  )

  const res = binding.crypto_core_ed25519_from_uniform(p, r)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_scalarmult_ed25519_base_noclamp = function (q, n) {
  assert(ArrayBuffer.isView(q), 'q must be a typed array')
  assert(
    q.byteLength === binding.crypto_scalarmult_ed25519_BYTES,
    "q must be 'crypto_scalarmult_ed25519_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.crypto_scalarmult_ed25519_SCALARBYTES,
    "n must be 'crypto_scalarmult_ed25519_SCALARBYTES' bytes"
  )

  const res = binding.crypto_scalarmult_ed25519_base_noclamp(q, n)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_scalarmult_ed25519_noclamp = function (q, n, p) {
  assert(ArrayBuffer.isView(q), 'q must be a typed array')
  assert(
    q.byteLength === binding.crypto_scalarmult_ed25519_BYTES,
    "q must be 'crypto_scalarmult_ed25519_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.crypto_scalarmult_ed25519_SCALARBYTES,
    "n must be 'crypto_scalarmult_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(p), 'p must be a typed array')
  assert(
    p.byteLength === binding.crypto_scalarmult_ed25519_BYTES,
    "p must be 'crypto_scalarmult_ed25519_BYTES' bytes"
  )

  const res = binding.crypto_scalarmult_ed25519_noclamp(q, n, p)

  if (res !== 0) throw new Error('status: ' + res)
}

// crypto_core

exports.crypto_core_ed25519_add = function (r, p, q) {
  assert(ArrayBuffer.isView(r), 'r must be a typed array')
  assert(
    r.byteLength === binding.crypto_core_ed25519_BYTES,
    "r must be 'crypto_core_ed25519_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(p), 'p must be a typed array')
  assert(
    p.byteLength === binding.crypto_core_ed25519_BYTES,
    "p must be 'crypto_core_ed25519_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(q), 'q must be a typed array')
  assert(
    q.byteLength === binding.crypto_core_ed25519_BYTES,
    "q must be 'crypto_core_ed25519_BYTES' bytes"
  )

  const res = binding.crypto_core_ed25519_add(r, p, q)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_core_ed25519_sub = function (r, p, q) {
  assert(ArrayBuffer.isView(r), 'r must be a typed array')
  assert(
    r.byteLength === binding.crypto_core_ed25519_BYTES,
    "r must be 'crypto_core_ed25519_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(p), 'p must be a typed array')
  assert(
    p.byteLength === binding.crypto_core_ed25519_BYTES,
    "p must be 'crypto_core_ed25519_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(q), 'q must be a typed array')
  assert(
    q.byteLength === binding.crypto_core_ed25519_BYTES,
    "q must be 'crypto_core_ed25519_BYTES' bytes"
  )

  const res = binding.crypto_core_ed25519_sub(r, p, q)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_core_ed25519_scalar_random = function (r) {
  assert(ArrayBuffer.isView(r), 'r must be a typed array')
  assert(
    r.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "r must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )

  binding.crypto_core_ed25519_scalar_random(r)
}

exports.crypto_core_ed25519_scalar_reduce = function (r, s) {
  assert(ArrayBuffer.isView(r), 'r must be a typed array')
  assert(
    r.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "r must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(s), 's must be a typed array')
  assert(
    s.byteLength === binding.crypto_core_ed25519_NONREDUCEDSCALARBYTES,
    "s must be 'crypto_core_ed25519_NONREDUCEDSCALARBYTES' bytes"
  )

  binding.crypto_core_ed25519_scalar_reduce(r, s)
}

exports.crypto_core_ed25519_scalar_invert = function (recip, s) {
  assert(ArrayBuffer.isView(recip), 'recip must be a typed array')
  assert(
    recip.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "recip must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(s), 's must be a typed array')
  assert(
    s.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "s must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )

  binding.crypto_core_ed25519_scalar_invert(recip, s)
}

exports.crypto_core_ed25519_scalar_negate = function (neg, s) {
  assert(ArrayBuffer.isView(neg), 'neg must be a typed array')
  assert(
    neg.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "neg must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(s), 's must be a typed array')
  assert(
    s.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "s must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )

  binding.crypto_core_ed25519_scalar_negate(neg, s)
}

exports.crypto_core_ed25519_scalar_complement = function (comp, s) {
  assert(ArrayBuffer.isView(comp), 'comp must be a typed array')
  assert(
    comp.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "comp must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(s), 's must be a typed array')
  assert(
    s.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "s must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )

  binding.crypto_core_ed25519_scalar_complement(comp, s)
}

exports.crypto_core_ed25519_scalar_add = function (z, x, y) {
  assert(ArrayBuffer.isView(z), 'z must be a typed array')
  assert(
    z.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "z must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(x), 'x must be a typed array')
  assert(
    x.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "x must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(y), 'y must be a typed array')
  assert(
    y.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "y must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )

  binding.crypto_core_ed25519_scalar_add(z, x, y)
}

exports.crypto_core_ed25519_scalar_sub = function (z, x, y) {
  assert(ArrayBuffer.isView(z), 'z must be a typed array')
  assert(
    z.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "z must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(x), 'x must be a typed array')
  assert(
    x.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "x must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(y), 'y must be a typed array')
  assert(
    y.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "y must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )

  binding.crypto_core_ed25519_scalar_sub(z, x, y)
}

// crypto_shorthash

exports.crypto_shorthash = function (out, input, k) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(
    out.byteLength === binding.crypto_shorthash_BYTES,
    "out must be 'crypto_shorthash_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_shorthash_KEYBYTES,
    "k must be 'crypto_shorthash_KEYBYTES' bytes"
  )

  const res = binding.crypto_shorthash(out, input, k)

  if (res !== 0) throw new Error('status: ' + res)
}

// crypto_kdf

exports.crypto_kdf_keygen = function (key) {
  assert(ArrayBuffer.isView(key), 'key must be a typed array')
  assert(key.byteLength === binding.crypto_kdf_KEYBYTES, "key must be 'crypto_kdf_KEYBYTES' bytes")

  binding.crypto_kdf_keygen(key)
}

exports.crypto_kdf_derive_from_key = function (subkey, subkeyId, ctx, key) {
  assert(ArrayBuffer.isView(subkey), 'subkey must be a typed array')
  assert(
    subkey.byteLength >= binding.crypto_kdf_BYTES_MIN,
    "subkey must be at least 'crypto_kdf_BYTES_MIN' bytes"
  )
  assert(
    subkey.byteLength <= binding.crypto_kdf_BYTES_MAX,
    "subkey must be at most 'crypto_kdf_BYTES_MAX' bytes"
  )
  assert(ArrayBuffer.isView(ctx), 'ctx must be a typed array')
  assert(
    ctx.byteLength === binding.crypto_kdf_CONTEXTBYTES,
    "ctx must be 'crypto_kdf_CONTEXTBYTES' bytes"
  )
  assert(ArrayBuffer.isView(key), 'key must be a typed array')
  assert(key.byteLength === binding.crypto_kdf_KEYBYTES, "key must be 'crypto_kdf_KEYBYTES' bytes")

  const res = binding.crypto_kdf_derive_from_key(subkey, subkeyId, ctx, key)

  if (res !== 0) throw new Error('status: ' + res)
}

// crypto_hash

exports.crypto_hash = function (out, input) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(out.byteLength === binding.crypto_hash_BYTES, "out must be 'crypto_hash_BYTES' bytes")

  const res = binding.crypto_hash(out, input)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_hash_sha256 = function (out, input) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(
    out.byteLength === binding.crypto_hash_sha256_BYTES,
    "out must be 'crypto_hash_sha256_BYTES' bytes"
  )

  const res = binding.crypto_hash_sha256(out, input)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_hash_sha256_init = function (state) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_hash_sha256_STATEBYTES,
    "state must be 'crypto_hash_sha256_STATEBYTES' bytes"
  )

  const res = binding.crypto_hash_sha256_init(state)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_hash_sha256_update = function (state, input) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(
    state.byteLength === binding.crypto_hash_sha256_STATEBYTES,
    "state must be 'crypto_hash_sha256_STATEBYTES' bytes"
  )

  const res = binding.crypto_hash_sha256_update(state, input)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_hash_sha256_final = function (state, out) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_hash_sha256_STATEBYTES,
    "state must be 'crypto_hash_sha256_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(
    out.byteLength === binding.crypto_hash_sha256_BYTES,
    "out must be 'crypto_hash_sha256_BYTES' bytes"
  )

  const res = binding.crypto_hash_sha256_final(state, out)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_hash_sha512 = function (out, input) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(
    out.byteLength === binding.crypto_hash_sha512_BYTES,
    "out must be 'crypto_hash_sha512_BYTES' bytes"
  )

  const res = binding.crypto_hash_sha512(out, input)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_hash_sha512_init = function (state) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_hash_sha512_STATEBYTES,
    "state must be 'crypto_hash_sha512_STATEBYTES' bytes"
  )

  const res = binding.crypto_hash_sha512_init(state)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_hash_sha512_update = function (state, input) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(
    state.byteLength === binding.crypto_hash_sha512_STATEBYTES,
    "state must be 'crypto_hash_sha512_STATEBYTES' bytes"
  )

  const res = binding.crypto_hash_sha512_update(state, input)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_hash_sha512_final = function (state, out) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_hash_sha512_STATEBYTES,
    "state must be 'crypto_hash_sha512_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(
    out.byteLength === binding.crypto_hash_sha512_BYTES,
    "out must be 'crypto_hash_sha512_BYTES' bytes"
  )

  const res = binding.crypto_hash_sha512_final(state, out)

  if (res !== 0) throw new Error('status: ' + res)
}

// crypto_aead

exports.crypto_aead_xchacha20poly1305_ietf_keygen = function (k) {
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_aead_xchacha20poly1305_ietf_KEYBYTES,
    "k must be 'crypto_aead_xchacha20poly1305_ietf_KEYBYTES' bytes"
  )

  binding.crypto_aead_xchacha20poly1305_ietf_keygen(k)
}

exports.crypto_aead_xchacha20poly1305_ietf_encrypt = function (c, m, ad, nsec, npub, k) {
  if (!ad) ad = undefined

  assert(nsec === null, 'nsec must always be set to null')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(
    c.byteLength === m.byteLength + binding.crypto_aead_xchacha20poly1305_ietf_ABYTES,
    "c must be 'm.byteLength + crypto_aead_xchacha20poly1305_ietf_ABYTES' bytes"
  )
  assert(c.byteLength <= 0xffffffff, 'c.byteLength must be a 32bit integer')
  assert(ArrayBuffer.isView(npub), 'npub must be a typed array')
  assert(
    npub.byteLength === binding.crypto_aead_xchacha20poly1305_ietf_NPUBBYTES,
    "npub must be 'crypto_aead_xchacha20poly1305_ietf_NPUBBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_aead_xchacha20poly1305_ietf_KEYBYTES,
    "k must be 'crypto_aead_xchacha20poly1305_ietf_KEYBYTES' bytes"
  )

  const res = binding.crypto_aead_xchacha20poly1305_ietf_encrypt(c, m, ad, npub, k)

  if (res < 0) throw new Error('could not encrypt data')

  return res
}

exports.crypto_aead_xchacha20poly1305_ietf_decrypt = function (m, nsec, c, ad, npub, k) {
  if (!ad) ad = undefined

  assert(nsec === null, 'nsec must always be set to null')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(
    m.byteLength === c.byteLength - binding.crypto_aead_xchacha20poly1305_ietf_ABYTES,
    "m must be 'c.byteLength - crypto_aead_xchacha20poly1305_ietf_ABYTES' bytes"
  )
  assert(m.byteLength <= 0xffffffff, 'm.byteLength must be a 32bit integer')
  assert(ArrayBuffer.isView(npub), 'npub must be a typed array')
  assert(
    npub.byteLength === binding.crypto_aead_xchacha20poly1305_ietf_NPUBBYTES,
    "npub must be 'crypto_aead_xchacha20poly1305_ietf_NPUBBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_aead_xchacha20poly1305_ietf_KEYBYTES,
    "k must be 'crypto_aead_xchacha20poly1305_ietf_KEYBYTES' bytes"
  )

  const res = binding.crypto_aead_xchacha20poly1305_ietf_decrypt(m, c, ad, npub, k)

  if (res < 0) throw new Error('could not verify data')

  return res
}

exports.crypto_aead_xchacha20poly1305_ietf_encrypt_detached = function (
  c,
  mac,
  m,
  ad,
  nsec,
  npub,
  k
) {
  if (!ad) ad = undefined

  assert(nsec === null, 'nsec must always be set to null')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(ArrayBuffer.isView(mac), 'mac must be a typed array')
  assert(
    mac.byteLength === binding.crypto_aead_xchacha20poly1305_ietf_ABYTES,
    "mac must be 'crypto_aead_xchacha20poly1305_ietf_ABYTES' bytes"
  )
  assert(ArrayBuffer.isView(npub), 'npub must be a typed array')
  assert(
    npub.byteLength === binding.crypto_aead_xchacha20poly1305_ietf_NPUBBYTES,
    "npub must be 'crypto_aead_xchacha20poly1305_ietf_NPUBBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_aead_xchacha20poly1305_ietf_KEYBYTES,
    "k must be 'crypto_aead_xchacha20poly1305_ietf_KEYBYTES' bytes"
  )

  const res = binding.crypto_aead_xchacha20poly1305_ietf_encrypt_detached(c, mac, m, ad, npub, k)

  if (res < 0) throw new Error('could not encrypt data')

  return res
}

exports.crypto_aead_xchacha20poly1305_ietf_decrypt_detached = function (
  m,
  nsec,
  c,
  mac,
  ad,
  npub,
  k
) {
  if (!ad) ad = undefined

  assert(nsec === null, 'nsec must always be set to null')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(m.byteLength === c.byteLength, "m must be 'c.byteLength' bytes")
  assert(ArrayBuffer.isView(mac), 'mac must be a typed array')
  assert(
    mac.byteLength === binding.crypto_aead_xchacha20poly1305_ietf_ABYTES,
    "mac must be 'crypto_aead_xchacha20poly1305_ietf_ABYTES' bytes"
  )
  assert(ArrayBuffer.isView(npub), 'npub must be a typed array')
  assert(
    npub.byteLength === binding.crypto_aead_xchacha20poly1305_ietf_NPUBBYTES,
    "npub must be 'crypto_aead_xchacha20poly1305_ietf_NPUBBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_aead_xchacha20poly1305_ietf_KEYBYTES,
    "k must be 'crypto_aead_xchacha20poly1305_ietf_KEYBYTES' bytes"
  )

  const res = binding.crypto_aead_xchacha20poly1305_ietf_decrypt_detached(m, c, mac, ad, npub, k)

  if (res !== 0) throw new Error('could not verify data')
}

exports.crypto_aead_chacha20poly1305_ietf_keygen = function (k) {
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_aead_chacha20poly1305_ietf_KEYBYTES,
    "k must be 'crypto_aead_chacha20poly1305_ietf_KEYBYTES' bytes"
  )

  binding.crypto_aead_chacha20poly1305_ietf_keygen(k)
}

exports.crypto_aead_chacha20poly1305_ietf_encrypt = function (c, m, ad, nsec, npub, k) {
  if (!ad) ad = undefined

  assert(nsec === null, 'nsec must always be set to null')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(
    c.byteLength === m.byteLength + binding.crypto_aead_chacha20poly1305_ietf_ABYTES,
    "c must be 'm.byteLength + crypto_aead_chacha20poly1305_ietf_ABYTES' bytes"
  )
  assert(c.byteLength <= 0xffffffff, 'c.byteLength must be a 32bit integer')
  assert(ArrayBuffer.isView(npub), 'npub must be a typed array')
  assert(
    npub.byteLength === binding.crypto_aead_chacha20poly1305_ietf_NPUBBYTES,
    "npub must be 'crypto_aead_chacha20poly1305_ietf_NPUBBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_aead_chacha20poly1305_ietf_KEYBYTES,
    "k must be 'crypto_aead_chacha20poly1305_ietf_KEYBYTES' bytes"
  )

  const res = binding.crypto_aead_chacha20poly1305_ietf_encrypt(c, m, ad, npub, k)

  if (res < 0) throw new Error('could not encrypt data')

  return res
}

exports.crypto_aead_chacha20poly1305_ietf_decrypt = function (m, nsec, c, ad, npub, k) {
  if (!ad) ad = undefined

  assert(nsec === null, 'nsec must always be set to null')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(
    m.byteLength === c.byteLength - binding.crypto_aead_chacha20poly1305_ietf_ABYTES,
    "m must be 'c.byteLength - crypto_aead_chacha20poly1305_ietf_ABYTES' bytes"
  )
  assert(m.byteLength <= 0xffffffff, 'm.byteLength must be a 32bit integer')
  assert(ArrayBuffer.isView(npub), 'npub must be a typed array')
  assert(
    npub.byteLength === binding.crypto_aead_chacha20poly1305_ietf_NPUBBYTES,
    "npub must be 'crypto_aead_chacha20poly1305_ietf_NPUBBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_aead_chacha20poly1305_ietf_KEYBYTES,
    "k must be 'crypto_aead_chacha20poly1305_ietf_KEYBYTES' bytes"
  )

  const res = binding.crypto_aead_chacha20poly1305_ietf_decrypt(m, c, ad, npub, k)

  if (res < 0) throw new Error('could not verify data')

  return res
}

exports.crypto_aead_chacha20poly1305_ietf_encrypt_detached = function (
  c,
  mac,
  m,
  ad,
  nsec,
  npub,
  k
) {
  if (!ad) ad = undefined

  assert(nsec === null, 'nsec must always be set to null')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(ArrayBuffer.isView(mac), 'mac must be a typed array')
  assert(
    mac.byteLength === binding.crypto_aead_chacha20poly1305_ietf_ABYTES,
    "mac must be 'crypto_aead_chacha20poly1305_ietf_ABYTES' bytes"
  )
  assert(ArrayBuffer.isView(npub), 'npub must be a typed array')
  assert(
    npub.byteLength === binding.crypto_aead_chacha20poly1305_ietf_NPUBBYTES,
    "npub must be 'crypto_aead_chacha20poly1305_ietf_NPUBBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_aead_chacha20poly1305_ietf_KEYBYTES,
    "k must be 'crypto_aead_chacha20poly1305_ietf_KEYBYTES' bytes"
  )

  const res = binding.crypto_aead_chacha20poly1305_ietf_encrypt_detached(c, mac, m, ad, npub, k)

  if (res < 0) throw new Error('could not encrypt data')

  return res
}

exports.crypto_aead_chacha20poly1305_ietf_decrypt_detached = function (
  m,
  nsec,
  c,
  mac,
  ad,
  npub,
  k
) {
  if (!ad) ad = undefined

  assert(nsec === null, 'nsec must always be set to null')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(m.byteLength === c.byteLength, "m must be 'c.byteLength' bytes")
  assert(ArrayBuffer.isView(mac), 'mac must be a typed array')
  assert(
    mac.byteLength === binding.crypto_aead_chacha20poly1305_ietf_ABYTES,
    "mac must be 'crypto_aead_chacha20poly1305_ietf_ABYTES' bytes"
  )
  assert(ArrayBuffer.isView(npub), 'npub must be a typed array')
  assert(
    npub.byteLength === binding.crypto_aead_chacha20poly1305_ietf_NPUBBYTES,
    "npub must be 'crypto_aead_chacha20poly1305_ietf_NPUBBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_aead_chacha20poly1305_ietf_KEYBYTES,
    "k must be 'crypto_aead_chacha20poly1305_ietf_KEYBYTES' bytes"
  )

  const res = binding.crypto_aead_chacha20poly1305_ietf_decrypt_detached(m, c, mac, ad, npub, k)

  if (res !== 0) throw new Error('could not verify data')
}

// crypto_stream

exports.crypto_stream_xor_wrap_init = function (state, n, k) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.sn_crypto_stream_xor_STATEBYTES,
    "state must be 'sn_crypto_stream_xor_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.crypto_stream_NONCEBYTES,
    "n must be 'crypto_stream_NONCEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_stream_KEYBYTES,
    "k must be 'crypto_stream_KEYBYTES' bytes"
  )

  binding.crypto_stream_xor_wrap_init(state, n, k)
}

exports.crypto_stream_xor_wrap_update = function (state, c, m) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.sn_crypto_stream_xor_STATEBYTES,
    "state must be 'sn_crypto_stream_xor_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")

  binding.crypto_stream_xor_wrap_update(state, c, m)
}

exports.crypto_stream_xor_wrap_final = function (state) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.sn_crypto_stream_xor_STATEBYTES,
    "state must be 'sn_crypto_stream_xor_STATEBYTES' bytes"
  )

  binding.crypto_stream_xor_wrap_final(state)
}

exports.crypto_stream_chacha20_xor_wrap_init = function (state, n, k) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_stream_chacha20_xor_STATEBYTES,
    "state must be 'crypto_stream_chacha20_xor_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.crypto_stream_chacha20_NONCEBYTES,
    "n must be 'crypto_stream_chacha20_NONCEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_stream_chacha20_KEYBYTES,
    "k must be 'crypto_stream_chacha20_KEYBYTES' bytes"
  )

  binding.crypto_stream_chacha20_xor_wrap_init(state, n, k)
}

exports.crypto_stream_chacha20_xor_wrap_update = function (state, c, m) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_stream_chacha20_xor_STATEBYTES,
    "state must be 'crypto_stream_chacha20_xor_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")

  binding.crypto_stream_chacha20_xor_wrap_update(state, c, m)
}

exports.crypto_stream_chacha20_xor_wrap_final = function (state) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_stream_chacha20_xor_STATEBYTES,
    "state must be 'crypto_stream_chacha20_xor_STATEBYTES' bytes"
  )

  binding.crypto_stream_chacha20_xor_wrap_final(state)
}

exports.crypto_stream_chacha20_ietf_xor_wrap_init = function (state, n, k) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_stream_chacha20_ietf_xor_STATEBYTES,
    "state must be 'crypto_stream_chacha20_ietf_xor_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.crypto_stream_chacha20_ietf_NONCEBYTES,
    "n must be 'crypto_stream_chacha20_ietf_NONCEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_stream_chacha20_ietf_KEYBYTES,
    "k must be 'crypto_stream_chacha20_ietf_KEYBYTES' bytes"
  )

  binding.crypto_stream_chacha20_ietf_xor_wrap_init(state, n, k)
}

exports.crypto_stream_chacha20_ietf_xor_wrap_update = function (state, c, m) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_stream_chacha20_ietf_xor_STATEBYTES,
    "state must be 'crypto_stream_chacha20_ietf_xor_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")

  binding.crypto_stream_chacha20_ietf_xor_wrap_update(state, c, m)
}

exports.crypto_stream_chacha20_ietf_xor_wrap_final = function (state) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_stream_chacha20_ietf_xor_STATEBYTES,
    "state must be 'crypto_stream_chacha20_ietf_xor_STATEBYTES' bytes"
  )

  binding.crypto_stream_chacha20_ietf_xor_wrap_final(state)
}

exports.crypto_stream_xchacha20_xor_wrap_init = function (state, n, k) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_stream_xchacha20_xor_STATEBYTES,
    "state must be 'crypto_stream_xchacha20_xor_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.crypto_stream_xchacha20_NONCEBYTES,
    "n must be 'crypto_stream_xchacha20_NONCEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_stream_xchacha20_KEYBYTES,
    "k must be 'crypto_stream_xchacha20_KEYBYTES' bytes"
  )

  binding.crypto_stream_xchacha20_xor_wrap_init(state, n, k)
}

exports.crypto_stream_xchacha20_xor_wrap_update = function (state, c, m) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_stream_xchacha20_xor_STATEBYTES,
    "state must be 'crypto_stream_xchacha20_xor_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")

  binding.crypto_stream_xchacha20_xor_wrap_update(state, c, m)
}

exports.crypto_stream_xchacha20_xor_wrap_final = function (state) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_stream_xchacha20_xor_STATEBYTES,
    "state must be 'crypto_stream_xchacha20_xor_STATEBYTES' bytes"
  )

  binding.crypto_stream_xchacha20_xor_wrap_final(state)
}

exports.crypto_stream_salsa20_xor_wrap_init = function (state, n, k) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_stream_salsa20_xor_STATEBYTES,
    "state must be 'crypto_stream_salsa20_xor_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.crypto_stream_salsa20_NONCEBYTES,
    "n must be 'crypto_stream_salsa20_NONCEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_stream_salsa20_KEYBYTES,
    "k must be 'crypto_stream_salsa20_KEYBYTES' bytes"
  )

  binding.crypto_stream_salsa20_xor_wrap_init(state, n, k)
}

exports.crypto_stream_salsa20_xor_wrap_update = function (state, c, m) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_stream_salsa20_xor_STATEBYTES,
    "state must be 'crypto_stream_salsa20_xor_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")

  binding.crypto_stream_salsa20_xor_wrap_update(state, c, m)
}

exports.crypto_stream_salsa20_xor_wrap_final = function (state) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_stream_salsa20_xor_STATEBYTES,
    "state must be 'crypto_stream_salsa20_xor_STATEBYTES' bytes"
  )

  binding.crypto_stream_salsa20_xor_wrap_final(state)
}

// experimental

exports.extension_tweak_ed25519_base = function (n, p, ns) {
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "n must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(p), 'p must be a typed array')
  assert(
    p.byteLength === binding.extension_tweak_ed25519_BYTES,
    "p must be 'extension_tweak_ed25519_BYTES' bytes"
  )

  binding.extension_tweak_ed25519_base(n, p, ns)
}

exports.extension_tweak_ed25519_sign_detached = function (sig, m, scalar, pk) {
  assert(ArrayBuffer.isView(sig), 'sig must be a typed array')
  assert(sig.byteLength === binding.crypto_sign_BYTES, "sig must be 'crypto_sign_BYTES' bytes")
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(scalar), 'scalar must be a typed array')
  assert(
    scalar.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "scalar must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )

  if (pk) {
    assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
    assert(
      pk.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
      "pk must be 'crypto_sign_PUBLICKEYBYTES' bytes"
    )
  }

  const res = binding.extension_tweak_ed25519_sign_detached(sig, m, scalar, pk)

  if (res !== 0) throw new Error('failed to compute signature')
}

exports.extension_tweak_ed25519_sk_to_scalar = function (n, sk) {
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "n must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(
    sk.byteLength === binding.crypto_sign_SECRETKEYBYTES,
    "sk must be 'crypto_sign_SECRETKEYBYTES' bytes"
  )

  binding.extension_tweak_ed25519_sk_to_scalar(n, sk)
}

exports.extension_tweak_ed25519_scalar = function (scalarOut, scalar, ns) {
  assert(ArrayBuffer.isView(scalarOut), 'scalarOut must be a typed array')
  assert(
    scalarOut.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "scalarOut must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(scalar), 'scalar must be a typed array')
  assert(
    scalar.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "scalar must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )

  binding.extension_tweak_ed25519_scalar(scalarOut, scalar, ns)
}

exports.extension_tweak_ed25519_pk = function (tpk, pk, ns) {
  assert(ArrayBuffer.isView(tpk), 'tpk must be a typed array')
  assert(
    tpk.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
    "tpk must be 'crypto_sign_PUBLICKEYBYTES' bytes"
  )
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(
    pk.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
    "pk must be 'crypto_sign_PUBLICKEYBYTES' bytes"
  )

  const res = binding.extension_tweak_ed25519_pk(tpk, pk, ns)

  if (res !== 0) throw new Error('failed to tweak public key')
}

exports.extension_tweak_ed25519_keypair = function (pk, scalarOut, scalarIn, ns) {
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(
    pk.byteLength === binding.extension_tweak_ed25519_BYTES,
    "pk must be 'extension_tweak_ed25519_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(scalarOut), 'scalarOut must be a typed array')
  assert(
    scalarOut.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "scalarOut must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(scalarIn), 'scalarIn must be a typed array')
  assert(
    scalarIn.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "scalarIn must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )

  binding.extension_tweak_ed25519_keypair(pk, scalarOut, scalarIn, ns)
}

exports.extension_tweak_ed25519_scalar_add = function (scalarOut, scalar, n) {
  assert(ArrayBuffer.isView(scalarOut), 'scalarOut must be a typed array')
  assert(
    scalarOut.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "scalarOut must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(scalar), 'scalar must be a typed array')
  assert(
    scalar.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "scalar must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "n must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )

  binding.extension_tweak_ed25519_scalar_add(scalarOut, scalar, n)
}

exports.extension_tweak_ed25519_pk_add = function (tpk, pk, p) {
  assert(ArrayBuffer.isView(tpk), 'tpk must be a typed array')
  assert(
    tpk.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
    "tpk must be 'crypto_sign_PUBLICKEYBYTES' bytes"
  )
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(
    pk.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
    "pk must be 'crypto_sign_PUBLICKEYBYTES' bytes"
  )
  assert(ArrayBuffer.isView(p), 'p must be a typed array')
  assert(
    p.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
    "p must be 'crypto_sign_PUBLICKEYBYTES' bytes"
  )

  const res = binding.extension_tweak_ed25519_pk_add(tpk, pk, p)

  if (res !== 0) throw new Error('failed to add tweak to public key')
}

exports.extension_tweak_ed25519_keypair_add = function (pk, scalarOut, scalarIn, tweak) {
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(
    pk.byteLength === binding.extension_tweak_ed25519_BYTES,
    "pk must be 'extension_tweak_ed25519_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(scalarOut), 'scalarOut must be a typed array')
  assert(
    scalarOut.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "scalarOut must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(scalarIn), 'scalarIn must be a typed array')
  assert(
    scalarIn.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "scalarIn must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(tweak), 'tweak must be a typed array')
  assert(
    tweak.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "tweak must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )

  const res = binding.extension_tweak_ed25519_keypair_add(pk, scalarOut, scalarIn, tweak)

  if (res !== 0) throw new Error('failed to add tweak to keypair')
}

exports.extension_pbkdf2_sha512_async = function (out, passwd, salt, iter, outlen, callback) {
  assert(
    iter >= binding.extension_pbkdf2_sha512_ITERATIONS_MIN,
    "iter must be at least 'extension_pbkdf2_sha512_ITERATIONS_MIN'"
  )
  assert(
    outlen <= binding.extension_pbkdf2_sha512_BYTES_MAX,
    "outlen must be at most 'extension_pbkdf2_sha512_BYTES_MAX'"
  )
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(out.byteLength >= outlen, "out must be at least 'outlen' bytes")
  assert(out.byteLength > 0, 'out must not be empty')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(passwd.byteLength > 0, 'passwd must not be empty')
  assert(ArrayBuffer.isView(salt), 'salt must be a typed array')
  assert(salt.byteLength > 0, 'salt must not be empty')

  const [done, promise] = checkStatus(callback)

  binding.extension_pbkdf2_sha512_async(
    out.buffer,
    out.byteOffset,
    out.byteLength,

    passwd.buffer,
    passwd.byteOffset,
    passwd.byteLength,

    salt.buffer,
    salt.byteOffset,
    salt.byteLength,

    iter,
    outlen,

    done
  )

  return promise
}

exports.extension_pbkdf2_sha512 = function (out, passwd, salt, iter, outlen) {
  assert(
    iter >= binding.extension_pbkdf2_sha512_ITERATIONS_MIN,
    "iter must be at least 'extension_pbkdf2_sha512_ITERATIONS_MIN'"
  )
  assert(
    outlen <= binding.extension_pbkdf2_sha512_BYTES_MAX,
    "outlen must be at most 'extension_pbkdf2_sha512_BYTES_MAX'"
  )
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(out.byteLength >= outlen, "out must be at least 'outlen' bytes")
  assert(out.byteLength > 0, 'out must not be empty')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(passwd.byteLength > 0, 'passwd must not be empty')
  assert(ArrayBuffer.isView(salt), 'salt must be a typed array')
  assert(salt.byteLength > 0, 'salt must not be empty')

  const res = binding.extension_pbkdf2_sha512(out, passwd, salt, iter, outlen)

  if (res !== 0) throw new Error('failed to add tweak to public key')
}

function checkStatus(callback, booleanResult = false) {
  let done, promise

  if (typeof callback === 'function') {
    done = function (status) {
      if (booleanResult) callback(null, status === 0)
      else if (status === 0) callback(null)
      else callback(new Error('status: ' + status))
    }
  } else {
    promise = new Promise(function (resolve, reject) {
      done = function (status) {
        if (booleanResult) resolve(status === 0)
        else if (status === 0) resolve()
        else reject(new Error('status: ' + status))
      }
    })
  }

  return [done, promise]
}
{
  "name": "sodium-native",
  "version": "5.1.0",
  "description": "Low level bindings for libsodium",
  "main": "index.js",
  "files": [
    "index.js",
    "binding.cc",
    "binding.js",
    "extensions",
    "prebuilds",
    "CMakeLists.txt"
  ],
  "imports": {
    "assert": {
      "bare": "bare-assert",
      "node": "assert"
    }
  },
  "addon": true,
  "dependencies": {
    "bare-assert": "^1.2.0",
    "require-addon": "^1.1.0",
    "which-runtime": "^1.2.1"
  },
  "devDependencies": {
    "bare-compat-napi": "^1.3.5",
    "brittle": "^3.16.2",
    "cmake-bare": "^1.6.1",
    "cmake-fetch": "^1.4.3",
    "cmake-napi": "^1.2.1",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^2.0.0"
  },
  "scripts": {
    "test": "prettier . --check && npm run test:node && npm run test:bare",
    "test:node": "node test/all.js",
    "test:bare": "bare test/all.js"
  },
  "standard": {
    "ignore": [
      "/test/fixtures/*.js"
    ]
  },
  "engines": {
    "bare": ">=1.16.0"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/sodium-native.git"
  },
  "contributors": [
    "Emil Bay <github@tixz.dk> (http://bayes.dk)",
    "Mathias Buus <mathiasbuus@gmail.com> (https://mafinto.sh)",
    "Christophe Diederichs <chm-diederichs@hyperdivision.dk>"
  ],
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/holepunchto/sodium-native/issues"
  },
  "homepage": "https://github.com/holepunchto/sodium-native"
}
const sodium = require('sodium-universal')
const b4a = require('b4a')

const ABYTES = sodium.crypto_secretstream_xchacha20poly1305_ABYTES
const TAG_MESSAGE = sodium.crypto_secretstream_xchacha20poly1305_TAG_MESSAGE
const TAG_FINAL = sodium.crypto_secretstream_xchacha20poly1305_TAG_FINAL
const STATEBYTES = sodium.crypto_secretstream_xchacha20poly1305_STATEBYTES
const HEADERBYTES = sodium.crypto_secretstream_xchacha20poly1305_HEADERBYTES
const KEYBYTES = sodium.crypto_secretstream_xchacha20poly1305_KEYBYTES
const TAG_FINAL_BYTE = b4a.isBuffer(TAG_FINAL) ? TAG_FINAL[0] : TAG_FINAL

const EMPTY = b4a.alloc(0)
const TAG = b4a.alloc(1)

class Push {
  constructor (key, state = b4a.allocUnsafeSlow(STATEBYTES), header = b4a.allocUnsafeSlow(HEADERBYTES)) {
    if (!TAG_FINAL) throw new Error('JavaScript sodium version needs to support crypto_secretstream_xchacha20poly')

    this.key = key
    this.state = state
    this.header = header

    sodium.crypto_secretstream_xchacha20poly1305_init_push(this.state, this.header, this.key)
  }

  next (message, cipher = b4a.allocUnsafe(message.byteLength + ABYTES)) {
    sodium.crypto_secretstream_xchacha20poly1305_push(this.state, cipher, message, null, TAG_MESSAGE)
    return cipher
  }

  final (message = EMPTY, cipher = b4a.allocUnsafe(ABYTES)) {
    sodium.crypto_secretstream_xchacha20poly1305_push(this.state, cipher, message, null, TAG_FINAL)
    return cipher
  }
}

class Pull {
  constructor (key, state = b4a.allocUnsafeSlow(STATEBYTES)) {
    if (!TAG_FINAL) throw new Error('JavaScript sodium version needs to support crypto_secretstream_xchacha20poly')

    this.key = key
    this.state = state
    this.final = false
  }

  init (header) {
    sodium.crypto_secretstream_xchacha20poly1305_init_pull(this.state, header, this.key)
  }

  next (cipher, message = b4a.allocUnsafe(cipher.byteLength - ABYTES)) {
    sodium.crypto_secretstream_xchacha20poly1305_pull(this.state, message, TAG, cipher, null)
    this.final = TAG[0] === TAG_FINAL_BYTE
    return message
  }
}

function keygen (buf = b4a.alloc(KEYBYTES)) {
  sodium.crypto_secretstream_xchacha20poly1305_keygen(buf)
  return buf
}

module.exports = {
  keygen,
  KEYBYTES,
  ABYTES,
  STATEBYTES,
  HEADERBYTES,
  Push,
  Pull
}
{
  "name": "sodium-secretstream",
  "version": "1.2.0",
  "description": "Wraps libsodiums secretstream in a higher level abstraction",
  "main": "index.js",
  "dependencies": {
    "b4a": "^1.1.1",
    "sodium-universal": "^5.0.0"
  },
  "devDependencies": {
    "standard": "^17.0.0"
  },
  "scripts": {
    "test": "standard"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/mafintosh/sodium-secretstream.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/mafintosh/sodium-secretstream/issues"
  },
  "homepage": "https://github.com/mafintosh/sodium-secretstream"
}
module.exports = require('sodium-native')
{
  "name": "sodium-universal",
  "version": "5.0.1",
  "description": "Universal wrapper for sodium-javascript and sodium-native working in Node.js and the Browser",
  "main": "index.js",
  "dependencies": {
    "sodium-native": "^5.0.1"
  },
  "peerDependencies": {
    "sodium-javascript": "~0.8.0"
  },
  "peerDependenciesMeta": {
    "sodium-javascript": {
      "optional": true
    }
  },
  "scripts": {
    "prepublish": "./build-scripts/generate.js"
  },
  "browser": {
    "sodium-native": "sodium-javascript"
  },
  "browserify": {
    "transform": [
      "./build-scripts/transform.js"
    ]
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/sodium-universal.git"
  },
  "keywords": [
    "libsodium",
    "sodium",
    "sodium-native",
    "sodium-javascript",
    "browserify"
  ],
  "contributors": [
    "Emil Bay <github@tixz.dk> (http://bayes.dk)",
    "Mathias Buus <mathiasbuus@gmail.com> (https://mafinto.sh)",
    "Christophe Diederichs <chm-diederichs@hyperdivision.dk>"
  ],
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/holepunchto/sodium-universal/issues"
  },
  "homepage": "https://github.com/holepunchto/sodium-universal#readme"
}
const { EventEmitter } = require('events-universal')
const FIFO = require('fast-fifo')
const TextDecoder = require('text-decoder')

const StreamError = require('./lib/errors.js')

// if we do a future major, expect queue microtask to be there always, for now a bit defensive
const qmt =
  typeof queueMicrotask === 'undefined' ? (fn) => global.process.nextTick(fn) : queueMicrotask

// 29 bits used total (4 from shared, 14 from read, and 11 from write)
const MAX = (1 << 29) - 1

// Shared state
const OPENING = 0b0001
const PREDESTROYING = 0b0010
const DESTROYING = 0b0100
const DESTROYED = 0b1000

const NOT_OPENING = MAX ^ OPENING
const NOT_PREDESTROYING = MAX ^ PREDESTROYING

// Read state (4 bit offset from shared state)
const READ_ACTIVE = 0b00000000000001 << 4
const READ_UPDATING = 0b00000000000010 << 4
const READ_PRIMARY = 0b00000000000100 << 4
const READ_QUEUED = 0b00000000001000 << 4
const READ_RESUMED = 0b00000000010000 << 4
const READ_PIPE_DRAINED = 0b00000000100000 << 4
const READ_ENDING = 0b00000001000000 << 4
const READ_EMIT_DATA = 0b00000010000000 << 4
const READ_EMIT_READABLE = 0b00000100000000 << 4
const READ_EMITTED_READABLE = 0b00001000000000 << 4
const READ_DONE = 0b00010000000000 << 4
const READ_NEXT_TICK = 0b00100000000000 << 4
const READ_NEEDS_PUSH = 0b01000000000000 << 4
const READ_READ_AHEAD = 0b10000000000000 << 4

// Combined read state
const READ_FLOWING = READ_RESUMED | READ_PIPE_DRAINED
const READ_ACTIVE_AND_NEEDS_PUSH = READ_ACTIVE | READ_NEEDS_PUSH
const READ_PRIMARY_AND_ACTIVE = READ_PRIMARY | READ_ACTIVE
const READ_EMIT_READABLE_AND_QUEUED = READ_EMIT_READABLE | READ_QUEUED
const READ_RESUMED_READ_AHEAD = READ_RESUMED | READ_READ_AHEAD

const READ_NOT_ACTIVE = MAX ^ READ_ACTIVE
const READ_NON_PRIMARY = MAX ^ READ_PRIMARY
const READ_NON_PRIMARY_AND_PUSHED = MAX ^ (READ_PRIMARY | READ_NEEDS_PUSH)
const READ_PUSHED = MAX ^ READ_NEEDS_PUSH
const READ_PAUSED = MAX ^ READ_RESUMED
const READ_NOT_QUEUED = MAX ^ (READ_QUEUED | READ_EMITTED_READABLE)
const READ_NOT_ENDING = MAX ^ READ_ENDING
const READ_PIPE_NOT_DRAINED = MAX ^ READ_FLOWING
const READ_NOT_NEXT_TICK = MAX ^ READ_NEXT_TICK
const READ_NOT_UPDATING = MAX ^ READ_UPDATING
const READ_NO_READ_AHEAD = MAX ^ READ_READ_AHEAD
const READ_PAUSED_NO_READ_AHEAD = MAX ^ READ_RESUMED_READ_AHEAD

// Write state (18 bit offset, 4 bit offset from shared state and 14 from read state)
const WRITE_ACTIVE = 0b00000000001 << 18
const WRITE_UPDATING = 0b00000000010 << 18
const WRITE_PRIMARY = 0b00000000100 << 18
const WRITE_QUEUED = 0b00000001000 << 18
const WRITE_UNDRAINED = 0b00000010000 << 18
const WRITE_DONE = 0b00000100000 << 18
const WRITE_EMIT_DRAIN = 0b00001000000 << 18
const WRITE_NEXT_TICK = 0b00010000000 << 18
const WRITE_WRITING = 0b00100000000 << 18
const WRITE_FINISHING = 0b01000000000 << 18
const WRITE_CORKED = 0b10000000000 << 18

const WRITE_NOT_ACTIVE = MAX ^ (WRITE_ACTIVE | WRITE_WRITING)
const WRITE_NON_PRIMARY = MAX ^ WRITE_PRIMARY
const WRITE_NOT_FINISHING = MAX ^ (WRITE_ACTIVE | WRITE_FINISHING)
const WRITE_DRAINED = MAX ^ WRITE_UNDRAINED
const WRITE_NOT_QUEUED = MAX ^ WRITE_QUEUED
const WRITE_NOT_NEXT_TICK = MAX ^ WRITE_NEXT_TICK
const WRITE_NOT_UPDATING = MAX ^ WRITE_UPDATING
const WRITE_NOT_CORKED = MAX ^ WRITE_CORKED

// Combined shared state
const ACTIVE = READ_ACTIVE | WRITE_ACTIVE
const NOT_ACTIVE = MAX ^ ACTIVE
const DONE = READ_DONE | WRITE_DONE
const DESTROY_STATUS = DESTROYING | DESTROYED | PREDESTROYING
const OPEN_STATUS = DESTROY_STATUS | OPENING
const AUTO_DESTROY = DESTROY_STATUS | DONE
const NON_PRIMARY = WRITE_NON_PRIMARY & READ_NON_PRIMARY
const ACTIVE_OR_TICKING = WRITE_NEXT_TICK | READ_NEXT_TICK
const TICKING = ACTIVE_OR_TICKING & NOT_ACTIVE
const IS_OPENING = OPEN_STATUS | TICKING

// Combined shared state and read state
const READ_PRIMARY_STATUS = OPEN_STATUS | READ_ENDING | READ_DONE
const READ_STATUS = OPEN_STATUS | READ_DONE | READ_QUEUED
const READ_ENDING_STATUS = OPEN_STATUS | READ_ENDING | READ_QUEUED
const READ_READABLE_STATUS = OPEN_STATUS | READ_EMIT_READABLE | READ_QUEUED | READ_EMITTED_READABLE
const SHOULD_NOT_READ =
  OPEN_STATUS | READ_ACTIVE | READ_ENDING | READ_DONE | READ_NEEDS_PUSH | READ_READ_AHEAD
const READ_BACKPRESSURE_STATUS = DESTROY_STATUS | READ_ENDING | READ_DONE
const READ_UPDATE_SYNC_STATUS = READ_UPDATING | OPEN_STATUS | READ_NEXT_TICK | READ_PRIMARY
const READ_NEXT_TICK_OR_OPENING = READ_NEXT_TICK | OPENING

// Combined write state
const WRITE_PRIMARY_STATUS = OPEN_STATUS | WRITE_FINISHING | WRITE_DONE
const WRITE_QUEUED_AND_UNDRAINED = WRITE_QUEUED | WRITE_UNDRAINED
const WRITE_QUEUED_AND_ACTIVE = WRITE_QUEUED | WRITE_ACTIVE
const WRITE_DRAIN_STATUS = WRITE_QUEUED | WRITE_UNDRAINED | OPEN_STATUS | WRITE_ACTIVE
const WRITE_STATUS = OPEN_STATUS | WRITE_ACTIVE | WRITE_QUEUED | WRITE_CORKED
const WRITE_PRIMARY_AND_ACTIVE = WRITE_PRIMARY | WRITE_ACTIVE
const WRITE_ACTIVE_AND_WRITING = WRITE_ACTIVE | WRITE_WRITING
const WRITE_FINISHING_STATUS = OPEN_STATUS | WRITE_FINISHING | WRITE_QUEUED_AND_ACTIVE | WRITE_DONE
const WRITE_BACKPRESSURE_STATUS = WRITE_UNDRAINED | DESTROY_STATUS | WRITE_FINISHING | WRITE_DONE
const WRITE_UPDATE_SYNC_STATUS = WRITE_UPDATING | OPEN_STATUS | WRITE_NEXT_TICK | WRITE_PRIMARY
const WRITE_DROP_DATA = WRITE_FINISHING | WRITE_DONE | DESTROY_STATUS

const asyncIterator = Symbol.asyncIterator || Symbol('asyncIterator')

class WritableState {
  constructor(
    stream,
    { highWaterMark = 16384, map = null, mapWritable, byteLength, byteLengthWritable } = {}
  ) {
    this.stream = stream
    this.queue = new FIFO()
    this.highWaterMark = highWaterMark
    this.buffered = 0
    this.error = null
    this.pipeline = null
    this.drains = null // if we add more seldomly used helpers we might them into a subobject so its a single ptr
    this.byteLength = byteLengthWritable || byteLength || defaultByteLength
    this.map = mapWritable || map
    this.afterWrite = afterWrite.bind(this)
    this.afterUpdateNextTick = updateWriteNT.bind(this)
  }

  get ending() {
    return (this.stream._duplexState & WRITE_FINISHING) !== 0
  }

  get ended() {
    return (this.stream._duplexState & WRITE_DONE) !== 0
  }

  push(data) {
    if ((this.stream._duplexState & WRITE_DROP_DATA) !== 0) return false
    if (this.map !== null) data = this.map(data)

    this.buffered += this.byteLength(data)
    this.queue.push(data)

    if (this.buffered < this.highWaterMark) {
      this.stream._duplexState |= WRITE_QUEUED
      return true
    }

    this.stream._duplexState |= WRITE_QUEUED_AND_UNDRAINED
    return false
  }

  shift() {
    const data = this.queue.shift()

    this.buffered -= this.byteLength(data)
    if (this.buffered === 0) this.stream._duplexState &= WRITE_NOT_QUEUED

    return data
  }

  end(data) {
    if (typeof data === 'function') {
      this.stream.once('finish', data)
    } else if (data !== undefined && data !== null) {
      this.push(data)
    }

    this.stream._duplexState = (this.stream._duplexState | WRITE_FINISHING) & WRITE_NON_PRIMARY
  }

  autoBatch(data, cb) {
    const buffer = []
    const stream = this.stream

    buffer.push(data)
    while ((stream._duplexState & WRITE_STATUS) === WRITE_QUEUED_AND_ACTIVE) {
      buffer.push(stream._writableState.shift())
    }

    if ((stream._duplexState & OPEN_STATUS) !== 0) return cb(null)
    stream._writev(buffer, cb)
  }

  update() {
    const stream = this.stream

    stream._duplexState |= WRITE_UPDATING

    do {
      while ((stream._duplexState & WRITE_STATUS) === WRITE_QUEUED) {
        const data = this.shift()
        stream._duplexState |= WRITE_ACTIVE_AND_WRITING
        stream._write(data, this.afterWrite)
      }

      if ((stream._duplexState & WRITE_PRIMARY_AND_ACTIVE) === 0) this.updateNonPrimary()
    } while (this.continueUpdate() === true)

    stream._duplexState &= WRITE_NOT_UPDATING
  }

  updateNonPrimary() {
    const stream = this.stream

    if ((stream._duplexState & WRITE_FINISHING_STATUS) === WRITE_FINISHING) {
      stream._duplexState = stream._duplexState | WRITE_ACTIVE
      stream._final(afterFinal.bind(this))
      return
    }

    if ((stream._duplexState & DESTROY_STATUS) === DESTROYING) {
      if ((stream._duplexState & ACTIVE_OR_TICKING) === 0) {
        stream._duplexState |= ACTIVE
        stream._destroy(afterDestroy.bind(this))
      }
      return
    }

    if ((stream._duplexState & IS_OPENING) === OPENING) {
      stream._duplexState = (stream._duplexState | ACTIVE) & NOT_OPENING
      stream._open(afterOpen.bind(this))
    }
  }

  continueUpdate() {
    if ((this.stream._duplexState & WRITE_NEXT_TICK) === 0) return false
    this.stream._duplexState &= WRITE_NOT_NEXT_TICK
    return true
  }

  updateCallback() {
    if ((this.stream._duplexState & WRITE_UPDATE_SYNC_STATUS) === WRITE_PRIMARY) {
      this.update()
    } else {
      this.updateNextTick()
    }
  }

  updateNextTick() {
    if ((this.stream._duplexState & WRITE_NEXT_TICK) !== 0) return
    this.stream._duplexState |= WRITE_NEXT_TICK
    if ((this.stream._duplexState & WRITE_UPDATING) === 0) qmt(this.afterUpdateNextTick)
  }
}

class ReadableState {
  constructor(
    stream,
    { highWaterMark = 16384, map = null, mapReadable, byteLength, byteLengthReadable } = {}
  ) {
    this.stream = stream
    this.queue = new FIFO()
    this.highWaterMark = highWaterMark === 0 ? 1 : highWaterMark
    this.buffered = 0
    this.readAhead = highWaterMark > 0
    this.error = null
    this.pipeline = null
    this.byteLength = byteLengthReadable || byteLength || defaultByteLength
    this.map = mapReadable || map
    this.pipeTo = null
    this.afterRead = afterRead.bind(this)
    this.afterUpdateNextTick = updateReadNT.bind(this)
  }

  get ending() {
    return (this.stream._duplexState & READ_ENDING) !== 0
  }

  get ended() {
    return (this.stream._duplexState & READ_DONE) !== 0
  }

  pipe(pipeTo, cb) {
    if (this.pipeTo !== null) throw StreamError.BAD_ARGUMENT('Can only pipe to one destination')
    if (typeof cb !== 'function') cb = null

    this.stream._duplexState |= READ_PIPE_DRAINED
    this.pipeTo = pipeTo
    this.pipeline = new Pipeline(this.stream, pipeTo, cb)

    if (cb) this.stream.on('error', noop) // We already error handle this so supress crashes

    if (isStreamx(pipeTo)) {
      pipeTo._writableState.pipeline = this.pipeline
      if (cb) pipeTo.on('error', noop) // We already error handle this so supress crashes
      pipeTo.on('finish', this.pipeline.finished.bind(this.pipeline)) // TODO: just call finished from pipeTo itself
    } else {
      const onerror = this.pipeline.done.bind(this.pipeline, pipeTo)
      const onclose = this.pipeline.done.bind(this.pipeline, pipeTo, null) // onclose has a weird bool arg
      pipeTo.on('error', onerror)
      pipeTo.on('close', onclose)
      pipeTo.on('finish', this.pipeline.finished.bind(this.pipeline))
    }

    pipeTo.on('drain', afterDrain.bind(this))
    this.stream.emit('piping', pipeTo)
    pipeTo.emit('pipe', this.stream)
  }

  push(data) {
    const stream = this.stream

    if (data === null) {
      this.highWaterMark = 0
      stream._duplexState = (stream._duplexState | READ_ENDING) & READ_NON_PRIMARY_AND_PUSHED
      return false
    }

    if (this.map !== null) {
      data = this.map(data)
      if (data === null) {
        stream._duplexState &= READ_PUSHED
        return this.buffered < this.highWaterMark
      }
    }

    this.buffered += this.byteLength(data)
    this.queue.push(data)

    stream._duplexState = (stream._duplexState | READ_QUEUED) & READ_PUSHED

    return this.buffered < this.highWaterMark
  }

  shift() {
    const data = this.queue.shift()

    this.buffered -= this.byteLength(data)
    if (this.buffered === 0) {
      this.stream._duplexState &= READ_NOT_QUEUED
    }

    return data
  }

  unshift(data) {
    const pending = [this.map !== null ? this.map(data) : data]
    while (this.buffered > 0) pending.push(this.shift())

    for (let i = 0; i < pending.length - 1; i++) {
      const data = pending[i]
      this.buffered += this.byteLength(data)
      this.queue.push(data)
    }

    this.push(pending[pending.length - 1])
  }

  read() {
    const stream = this.stream

    if ((stream._duplexState & READ_STATUS) === READ_QUEUED) {
      const data = this.shift()

      if (this.pipeTo !== null && this.pipeTo.write(data) === false) {
        stream._duplexState &= READ_PIPE_NOT_DRAINED
      }

      if ((stream._duplexState & READ_EMIT_DATA) !== 0) {
        stream.emit('data', data)
      }

      return data
    }

    if (this.readAhead === false) {
      stream._duplexState |= READ_READ_AHEAD
      this.updateNextTick()
    }

    return null
  }

  drain() {
    const stream = this.stream

    while (
      (stream._duplexState & READ_STATUS) === READ_QUEUED &&
      (stream._duplexState & READ_FLOWING) !== 0
    ) {
      const data = this.shift()

      if (this.pipeTo !== null && this.pipeTo.write(data) === false) {
        stream._duplexState &= READ_PIPE_NOT_DRAINED
      }

      if ((stream._duplexState & READ_EMIT_DATA) !== 0) {
        stream.emit('data', data)
      }
    }
  }

  update() {
    const stream = this.stream

    stream._duplexState |= READ_UPDATING

    do {
      this.drain()

      while (
        this.buffered < this.highWaterMark &&
        (stream._duplexState & SHOULD_NOT_READ) === READ_READ_AHEAD
      ) {
        stream._duplexState |= READ_ACTIVE_AND_NEEDS_PUSH
        stream._read(this.afterRead)
        this.drain()
      }

      if ((stream._duplexState & READ_READABLE_STATUS) === READ_EMIT_READABLE_AND_QUEUED) {
        stream._duplexState |= READ_EMITTED_READABLE
        stream.emit('readable')
      }

      if ((stream._duplexState & READ_PRIMARY_AND_ACTIVE) === 0) {
        this.updateNonPrimary()
      }
    } while (this.continueUpdate() === true)

    stream._duplexState &= READ_NOT_UPDATING
  }

  updateNonPrimary() {
    const stream = this.stream

    if ((stream._duplexState & READ_ENDING_STATUS) === READ_ENDING) {
      stream._duplexState = (stream._duplexState | READ_DONE) & READ_NOT_ENDING
      stream.emit('end')

      if ((stream._duplexState & AUTO_DESTROY) === DONE) {
        stream._duplexState |= DESTROYING
      }

      if (this.pipeTo !== null) {
        this.pipeTo.end()
      }
    }

    if ((stream._duplexState & DESTROY_STATUS) === DESTROYING) {
      if ((stream._duplexState & ACTIVE_OR_TICKING) === 0) {
        stream._duplexState |= ACTIVE
        stream._destroy(afterDestroy.bind(this))
      }
      return
    }

    if ((stream._duplexState & IS_OPENING) === OPENING) {
      stream._duplexState = (stream._duplexState | ACTIVE) & NOT_OPENING
      stream._open(afterOpen.bind(this))
    }
  }

  continueUpdate() {
    if ((this.stream._duplexState & READ_NEXT_TICK) === 0) return false
    this.stream._duplexState &= READ_NOT_NEXT_TICK
    return true
  }

  updateCallback() {
    if ((this.stream._duplexState & READ_UPDATE_SYNC_STATUS) === READ_PRIMARY) {
      this.update()
    } else {
      this.updateNextTick()
    }
  }

  updateNextTickIfOpen() {
    if ((this.stream._duplexState & READ_NEXT_TICK_OR_OPENING) !== 0) return
    this.stream._duplexState |= READ_NEXT_TICK
    if ((this.stream._duplexState & READ_UPDATING) === 0) qmt(this.afterUpdateNextTick)
  }

  updateNextTick() {
    if ((this.stream._duplexState & READ_NEXT_TICK) !== 0) return
    this.stream._duplexState |= READ_NEXT_TICK
    if ((this.stream._duplexState & READ_UPDATING) === 0) qmt(this.afterUpdateNextTick)
  }
}

class TransformState {
  constructor(stream) {
    this.data = null
    this.afterTransform = afterTransform.bind(stream)
    this.afterFinal = null
  }
}

class Pipeline {
  constructor(src, dst, cb) {
    this.from = src
    this.to = dst
    this.afterPipe = cb
    this.error = null
    this.pipeToFinished = false
  }

  finished() {
    this.pipeToFinished = true
  }

  done(stream, err) {
    if (err) this.error = err

    if (stream === this.to) {
      this.to = null

      if (this.from !== null) {
        if ((this.from._duplexState & READ_DONE) === 0 || !this.pipeToFinished) {
          this.from.destroy(this.error || StreamError.PREMATURE_CLOSE('Writable stream closed'))
        }
        return
      }
    }

    if (stream === this.from) {
      this.from = null

      if (this.to !== null) {
        if ((stream._duplexState & READ_DONE) === 0) {
          this.to.destroy(this.error || StreamError.PREMATURE_CLOSE('Readable stream closed'))
        }
        return
      }
    }

    if (this.afterPipe !== null) this.afterPipe(this.error)
    this.to = this.from = this.afterPipe = null
  }
}

function afterDrain() {
  this.stream._duplexState |= READ_PIPE_DRAINED
  this.updateCallback()
}

function afterFinal(err) {
  const stream = this.stream
  if (err) stream.destroy(err)

  if ((stream._duplexState & DESTROY_STATUS) === 0) {
    stream._duplexState |= WRITE_DONE
    stream.emit('finish')
  }

  if ((stream._duplexState & AUTO_DESTROY) === DONE) {
    stream._duplexState |= DESTROYING
  }

  stream._duplexState &= WRITE_NOT_FINISHING

  // no need to wait the extra tick here, so we short circuit that
  if ((stream._duplexState & WRITE_UPDATING) === 0) {
    this.update()
  } else {
    this.updateNextTick()
  }
}

function afterDestroy(err) {
  const stream = this.stream

  if (!err && !StreamError.isStreamDestroyed(this.error)) err = this.error
  if (err) stream.emit('error', err)

  stream._duplexState |= DESTROYED
  stream.emit('close')

  const rs = stream._readableState
  const ws = stream._writableState

  if (rs !== null && rs.pipeline !== null) {
    rs.pipeline.done(stream, err)
  }

  if (ws !== null) {
    while (ws.drains !== null && ws.drains.length > 0) {
      ws.drains.shift().resolve(false)
    }

    if (ws.pipeline !== null) {
      ws.pipeline.done(stream, err)
    }
  }
}

function afterWrite(err) {
  const stream = this.stream

  if (err) stream.destroy(err)
  stream._duplexState &= WRITE_NOT_ACTIVE

  if (this.drains !== null) tickDrains(this.drains)

  if ((stream._duplexState & WRITE_DRAIN_STATUS) === WRITE_UNDRAINED) {
    stream._duplexState &= WRITE_DRAINED

    if ((stream._duplexState & WRITE_EMIT_DRAIN) === WRITE_EMIT_DRAIN) {
      stream.emit('drain')
    }
  }

  this.updateCallback()
}

function afterRead(err) {
  if (err) this.stream.destroy(err)
  this.stream._duplexState &= READ_NOT_ACTIVE

  if (this.readAhead === false && (this.stream._duplexState & READ_RESUMED) === 0) {
    this.stream._duplexState &= READ_NO_READ_AHEAD
  }

  this.updateCallback()
}

function updateReadNT() {
  if ((this.stream._duplexState & READ_UPDATING) === 0) {
    this.stream._duplexState &= READ_NOT_NEXT_TICK
    this.update()
  }
}

function updateWriteNT() {
  if ((this.stream._duplexState & WRITE_UPDATING) === 0) {
    this.stream._duplexState &= WRITE_NOT_NEXT_TICK
    this.update()
  }
}

function tickDrains(drains) {
  for (let i = 0; i < drains.length; i++) {
    // drains.writes are monotonic, so if one is 0 its always the first one
    if (--drains[i].writes === 0) {
      drains.shift().resolve(true)
      i--
    }
  }
}

function afterOpen(err) {
  const stream = this.stream

  if (err) stream.destroy(err)

  if ((stream._duplexState & DESTROYING) === 0) {
    if ((stream._duplexState & READ_PRIMARY_STATUS) === 0) {
      stream._duplexState |= READ_PRIMARY
    }

    if ((stream._duplexState & WRITE_PRIMARY_STATUS) === 0) {
      stream._duplexState |= WRITE_PRIMARY
    }

    stream.emit('open')
  }

  stream._duplexState &= NOT_ACTIVE

  if (stream._writableState !== null) {
    stream._writableState.updateCallback()
  }

  if (stream._readableState !== null) {
    stream._readableState.updateCallback()
  }
}

function afterTransform(err, data) {
  if (data !== undefined && data !== null) this.push(data)
  this._writableState.afterWrite(err)
}

function newListener(name) {
  if (this._readableState !== null) {
    if (name === 'data') {
      this._duplexState |= READ_EMIT_DATA | READ_RESUMED_READ_AHEAD
      this._readableState.updateNextTick()
    }
    if (name === 'readable') {
      this._duplexState |= READ_EMIT_READABLE
      this._readableState.updateNextTick()
    }
  }

  if (this._writableState !== null) {
    if (name === 'drain') {
      this._duplexState |= WRITE_EMIT_DRAIN
      this._writableState.updateNextTick()
    }
  }
}

class Stream extends EventEmitter {
  constructor(opts) {
    super()

    this._duplexState = 0
    this._readableState = null
    this._writableState = null

    if (opts) {
      if (opts.open) this._open = opts.open
      if (opts.destroy) this._destroy = opts.destroy
      if (opts.predestroy) this._predestroy = opts.predestroy
      if (opts.signal) opts.signal.addEventListener('abort', abort.bind(this))
    }

    this.on('newListener', newListener)
  }

  _open(cb) {
    cb(null)
  }

  _destroy(cb) {
    cb(null)
  }

  _predestroy() {
    // does nothing
  }

  get readable() {
    return this._readableState !== null ? true : undefined
  }

  get writable() {
    return this._writableState !== null ? true : undefined
  }

  get destroyed() {
    return (this._duplexState & DESTROYED) !== 0
  }

  get destroying() {
    return (this._duplexState & DESTROY_STATUS) !== 0
  }

  destroy(err) {
    if ((this._duplexState & DESTROY_STATUS) === 0) {
      if (!err) err = StreamError.STREAM_DESTROYED()
      this._duplexState = (this._duplexState | DESTROYING) & NON_PRIMARY

      if (this._readableState !== null) {
        this._readableState.highWaterMark = 0
        this._readableState.error = err
      }

      if (this._writableState !== null) {
        this._writableState.highWaterMark = 0
        this._writableState.error = err
      }

      this._duplexState |= PREDESTROYING
      this._predestroy()
      this._duplexState &= NOT_PREDESTROYING

      if (this._readableState !== null) {
        this._readableState.updateNextTick()
      }

      if (this._writableState !== null) {
        this._writableState.updateNextTick()
      }
    }
  }
}

class Readable extends Stream {
  constructor(opts) {
    super(opts)

    this._duplexState |= OPENING | WRITE_DONE | READ_READ_AHEAD
    this._readableState = new ReadableState(this, opts)

    if (opts) {
      if (this._readableState.readAhead === false) this._duplexState &= READ_NO_READ_AHEAD
      if (opts.read) this._read = opts.read
      if (opts.eagerOpen) this._readableState.updateNextTick()
      if (opts.encoding) this.setEncoding(opts.encoding)
    }
  }

  static deferred(fn, opts) {
    const out = new PassThrough(opts)

    fn()
      .then((src) => {
        if (src === null) return out.end()
        if (out.destroying) return
        pipeline(src, out, noop)
      })
      .catch((err) => out.destroy(err))

    return out
  }

  setEncoding(encoding) {
    const dec = new TextDecoder(encoding)
    const map = this._readableState.map || echo
    this._readableState.map = mapOrSkip
    return this

    function mapOrSkip(data) {
      const next = dec.push(data)
      return next === '' && (data.byteLength !== 0 || dec.remaining > 0) ? null : map(next)
    }
  }

  _read(cb) {
    cb(null)
  }

  pipe(dest, cb) {
    this._readableState.updateNextTick()
    this._readableState.pipe(dest, cb)
    return dest
  }

  read() {
    this._readableState.updateNextTick()
    return this._readableState.read()
  }

  push(data) {
    this._readableState.updateNextTickIfOpen()
    return this._readableState.push(data)
  }

  unshift(data) {
    this._readableState.updateNextTickIfOpen()
    return this._readableState.unshift(data)
  }

  resume() {
    this._duplexState |= READ_RESUMED_READ_AHEAD
    this._readableState.updateNextTick()
    return this
  }

  pause() {
    this._duplexState &=
      this._readableState.readAhead === false ? READ_PAUSED_NO_READ_AHEAD : READ_PAUSED
    return this
  }

  static _fromAsyncIterator(ite, opts) {
    let destroy

    const rs = new Readable({
      ...opts,
      read(cb) {
        ite.next().then(push).then(cb.bind(null, null)).catch(cb)
      },
      predestroy() {
        destroy = ite.return()
      },
      destroy(cb) {
        if (!destroy) return cb(null)
        destroy.then(cb.bind(null, null)).catch(cb)
      }
    })

    return rs

    function push(data) {
      if (data.done) rs.push(null)
      else rs.push(data.value)
    }
  }

  static from(data, opts) {
    if (isReadStreamx(data)) return data
    if (data[asyncIterator]) return this._fromAsyncIterator(data[asyncIterator](), opts)
    if (!Array.isArray(data)) data = data === undefined ? [] : [data]

    let i = 0
    return new Readable({
      ...opts,
      read(cb) {
        this.push(i === data.length ? null : data[i++])
        cb(null)
      }
    })
  }

  static isBackpressured(rs) {
    return (
      (rs._duplexState & READ_BACKPRESSURE_STATUS) !== 0 ||
      rs._readableState.buffered >= rs._readableState.highWaterMark
    )
  }

  static isPaused(rs) {
    return (rs._duplexState & READ_RESUMED) === 0
  }

  [asyncIterator]() {
    const stream = this

    let error = null
    let promiseResolve = null
    let promiseReject = null

    this.on('error', (err) => {
      error = err
    })
    this.on('readable', onreadable)
    this.on('close', onclose)

    return {
      [asyncIterator]() {
        return this
      },
      next() {
        return new Promise(function (resolve, reject) {
          promiseResolve = resolve
          promiseReject = reject
          const data = stream.read()
          if (data !== null) ondata(data)
          else if ((stream._duplexState & DESTROYED) !== 0) ondata(null)
        })
      },
      return() {
        return destroy(null)
      },
      throw(err) {
        return destroy(err)
      }
    }

    function onreadable() {
      if (promiseResolve !== null) ondata(stream.read())
    }

    function onclose() {
      if (promiseResolve !== null) ondata(null)
    }

    function ondata(data) {
      if (promiseReject === null) return
      if (error) {
        promiseReject(error)
      } else if (data === null && (stream._duplexState & READ_DONE) === 0) {
        promiseReject(StreamError.STREAM_DESTROYED())
      } else {
        promiseResolve({ value: data, done: data === null })
      }
      promiseReject = promiseResolve = null
    }

    function destroy(err) {
      stream.destroy(err)
      return new Promise((resolve, reject) => {
        if (stream._duplexState & DESTROYED) return resolve({ value: undefined, done: true })
        stream.once('close', function () {
          if (err) reject(err)
          else resolve({ value: undefined, done: true })
        })
      })
    }
  }
}

class Writable extends Stream {
  constructor(opts) {
    super(opts)

    this._duplexState |= OPENING | READ_DONE
    this._writableState = new WritableState(this, opts)

    if (opts) {
      if (opts.writev) this._writev = opts.writev
      if (opts.write) this._write = opts.write
      if (opts.final) this._final = opts.final
      if (opts.eagerOpen) this._writableState.updateNextTick()
    }
  }

  cork() {
    this._duplexState |= WRITE_CORKED
  }

  uncork() {
    this._duplexState &= WRITE_NOT_CORKED
    this._writableState.updateNextTick()
  }

  _writev(batch, cb) {
    cb(null)
  }

  _write(data, cb) {
    this._writableState.autoBatch(data, cb)
  }

  _final(cb) {
    cb(null)
  }

  static isBackpressured(ws) {
    return (ws._duplexState & WRITE_BACKPRESSURE_STATUS) !== 0
  }

  static drained(ws) {
    if (ws.destroyed) return Promise.resolve(false)

    const state = ws._writableState
    const pending = isWritev(ws) ? Math.min(1, state.queue.length) : state.queue.length
    const writes = pending + (ws._duplexState & WRITE_WRITING ? 1 : 0)
    if (writes === 0) return Promise.resolve(true)

    if (state.drains === null) state.drains = []
    return new Promise((resolve) => {
      state.drains.push({ writes, resolve })
    })
  }

  write(data) {
    this._writableState.updateNextTick()
    return this._writableState.push(data)
  }

  end(data) {
    this._writableState.updateNextTick()
    this._writableState.end(data)
    return this
  }
}

class Duplex extends Readable {
  // and Writable
  constructor(opts) {
    super(opts)

    this._duplexState = OPENING | (this._duplexState & READ_READ_AHEAD)
    this._writableState = new WritableState(this, opts)

    if (opts) {
      if (opts.writev) this._writev = opts.writev
      if (opts.write) this._write = opts.write
      if (opts.final) this._final = opts.final
    }
  }

  cork() {
    this._duplexState |= WRITE_CORKED
  }

  uncork() {
    this._duplexState &= WRITE_NOT_CORKED
    this._writableState.updateNextTick()
  }

  _writev(batch, cb) {
    cb(null)
  }

  _write(data, cb) {
    this._writableState.autoBatch(data, cb)
  }

  _final(cb) {
    cb(null)
  }

  write(data) {
    this._writableState.updateNextTick()
    return this._writableState.push(data)
  }

  end(data) {
    this._writableState.updateNextTick()
    this._writableState.end(data)
    return this
  }
}

class Transform extends Duplex {
  constructor(opts) {
    super(opts)
    this._transformState = new TransformState(this)

    if (opts) {
      if (opts.transform) this._transform = opts.transform
      if (opts.flush) this._flush = opts.flush
    }
  }

  _write(data, cb) {
    if (this._readableState.buffered >= this._readableState.highWaterMark) {
      this._transformState.data = data
    } else {
      this._transform(data, this._transformState.afterTransform)
    }
  }

  _read(cb) {
    if (this._transformState.data !== null) {
      const data = this._transformState.data
      this._transformState.data = null
      cb(null)
      this._transform(data, this._transformState.afterTransform)
    } else {
      cb(null)
    }
  }

  destroy(err) {
    super.destroy(err)
    if (this._transformState.data !== null) {
      this._transformState.data = null
      this._transformState.afterTransform()
    }
  }

  _transform(data, cb) {
    cb(null, data)
  }

  _flush(cb) {
    cb(null)
  }

  _final(cb) {
    this._transformState.afterFinal = cb
    this._flush(transformAfterFlush.bind(this))
  }
}

class PassThrough extends Transform {}

function transformAfterFlush(err, data) {
  const cb = this._transformState.afterFinal
  if (err) return cb(err)

  if (data !== null && data !== undefined) this.push(data)
  this.push(null)
  cb(null)
}

function pipelinePromise(...streams) {
  return new Promise((resolve, reject) => {
    return pipeline(...streams, (err) => {
      if (err) return reject(err)
      resolve()
    })
  })
}

function pipeline(stream, ...streams) {
  const all = Array.isArray(stream) ? [...stream, ...streams] : [stream, ...streams]
  const done = all.length && typeof all[all.length - 1] === 'function' ? all.pop() : null

  if (all.length < 2) throw StreamError.BAD_ARGUMENT('Pipeline requires at least 2 streams')

  let src = all[0]
  let dest = null
  let error = null

  for (let i = 1; i < all.length; i++) {
    dest = all[i]

    if (isStreamx(src)) {
      src.pipe(dest, onerror)
    } else {
      errorHandle(src, true, i > 1, onerror)
      src.pipe(dest)
    }

    src = dest
  }

  if (done) {
    let fin = false

    const autoDestroy =
      isStreamx(dest) || !!(dest._writableState && dest._writableState.autoDestroy)

    dest.on('error', (err) => {
      if (error === null) error = err
    })

    dest.on('finish', () => {
      fin = true
      if (!autoDestroy) done(error)
    })

    if (autoDestroy) {
      dest.on('close', () => done(error || (fin ? null : StreamError.PREMATURE_CLOSE())))
    }
  }

  return dest

  function errorHandle(s, rd, wr, onerror) {
    s.on('error', onerror)
    s.on('close', onclose)

    function onclose() {
      if (rd && s._readableState && !s._readableState.ended) {
        return onerror(StreamError.PREMATURE_CLOSE())
      }
      if (wr && s._writableState && !s._writableState.ended) {
        return onerror(StreamError.PREMATURE_CLOSE())
      }
    }
  }

  function onerror(err) {
    if (!err || error) return
    error = err

    for (const s of all) {
      s.destroy(err)
    }
  }
}

function echo(s) {
  return s
}

function isStream(stream) {
  return !!stream._readableState || !!stream._writableState
}

function isStreamx(stream) {
  return typeof stream._duplexState === 'number' && isStream(stream)
}

function isEnding(stream) {
  return !!stream._readableState && stream._readableState.ending
}

function isEnded(stream) {
  return !!stream._readableState && stream._readableState.ended
}

function isFinishing(stream) {
  return !!stream._writableState && stream._writableState.ending
}

function isFinished(stream) {
  return !!stream._writableState && stream._writableState.ended
}

function getStreamError(stream, opts = {}) {
  const err =
    (stream._readableState && stream._readableState.error) ||
    (stream._writableState && stream._writableState.error)

  // avoid implicit errors by default
  return !opts.all && StreamError.isStreamDestroyed(err) ? null : err
}

function isReadStreamx(stream) {
  return isStreamx(stream) && stream.readable
}

function isDisturbed(stream) {
  return (
    (stream._duplexState & OPENING) !== OPENING ||
    (stream._duplexState & DESTROYING) === DESTROYING ||
    (stream._duplexState & ACTIVE_OR_TICKING) !== 0
  )
}

function isTypedArray(data) {
  return typeof data === 'object' && data !== null && typeof data.byteLength === 'number'
}

function defaultByteLength(data) {
  return isTypedArray(data) ? data.byteLength : 1024
}

function noop() {}

function abort() {
  this.destroy(StreamError.ABORTED())
}

function isWritev(s) {
  return s._writev !== Writable.prototype._writev && s._writev !== Duplex.prototype._writev
}

module.exports = {
  pipeline,
  pipelinePromise,
  isStream,
  isStreamx,
  isEnding,
  isEnded,
  isFinishing,
  isFinished,
  isDisturbed,
  getStreamError,
  Stream,
  Writable,
  Readable,
  Duplex,
  Transform,
  // Export PassThrough for compatibility with Node.js core's stream module
  PassThrough
}
module.exports = class StreamError extends Error {
  constructor(msg, code, fn = StreamError) {
    super(msg)

    this.code = code

    if (Error.captureStackTrace) {
      Error.captureStackTrace(this, fn)
    }
  }

  static isStreamDestroyed(err) {
    return err && err.code === 'STREAM_DESTROYED'
  }

  static isPrematureClose(err) {
    return err && err.code === 'PREMATURE_CLOSE'
  }

  static isAborted(err) {
    return err && err.code === 'ABORTED'
  }

  static isBadArgument(err) {
    return err && err.code === 'BAD_ARGUMENT'
  }

  get name() {
    return 'StreamError'
  }

  static STREAM_DESTROYED() {
    return new StreamError('Stream was destroyed', 'STREAM_DESTROYED', StreamError.STREAM_DESTROYED)
  }

  static PREMATURE_CLOSE(msg = 'Premature close') {
    return new StreamError(msg, 'PREMATURE_CLOSE', StreamError.PREMATURE_CLOSE)
  }

  static ABORTED() {
    return new StreamError('Stream aborted', 'ABORTED', StreamError.ABORTED)
  }

  static BAD_ARGUMENT(msg = 'Bad argument') {
    return new StreamError(msg, 'BAD_ARGUMENT', StreamError.BAD_ARGUMENT)
  }
}
{
  "name": "streamx",
  "version": "2.28.1",
  "description": "An iteration of the Node.js core streams with a series of improvements",
  "main": "index.js",
  "exports": {
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    },
    "./package": "./package.json",
    "./errors": {
      "types": "./lib/errors.d.ts",
      "default": "./lib/errors"
    }
  },
  "dependencies": {
    "events-universal": "^1.0.0",
    "fast-fifo": "^1.3.2",
    "text-decoder": "^1.1.0"
  },
  "devDependencies": {
    "b4a": "^1.6.6",
    "brittle": "^3.1.1",
    "end-of-stream": "^1.4.4",
    "lunte": "^1.8.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^2.0.0"
  },
  "files": [
    "index.js",
    "index.d.ts",
    "runtime.d.ts",
    "lib"
  ],
  "scripts": {
    "format": "prettier . --write",
    "test": "brittle test/all.js && brittle-bare test/all.js",
    "lint": "prettier . --check && lunte"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/mafintosh/streamx.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/mafintosh/streamx/issues"
  },
  "homepage": "https://github.com/mafintosh/streamx"
}
const { Readable } = require('streamx')

module.exports = function (s, forks = 2) {
  const streams = new Array(forks)
  const status = new Array(forks).fill(true)

  let ended = false

  for (let i = 0; i < forks; i++) {
    streams[i] = new Readable({
      read (cb) {
        const check = !status[i]
        status[i] = true
        if (check && allReadable()) s.resume()
        cb(null)
      }
    })
  }

  s.on('end', function () {
    ended = true
    for (const stream of streams) stream.push(null)
  })

  s.on('error', function (err) {
    for (const stream of streams) stream.destroy(err)
  })

  s.on('close', function () {
    if (ended) return
    for (const stream of streams) stream.destroy()
  })

  s.on('data', function (data) {
    let needsPause = false
    for (let i = 0; i < streams.length; i++) {
      if (!(status[i] = streams[i].push(data))) {
        needsPause = true
      }
    }
    if (needsPause) s.pause()
  })

  return streams

  function allReadable () {
    for (let j = 0; j < status.length; j++) {
      if (!status[j]) return false
    }
    return true
  }
}
{
  "name": "teex",
  "version": "1.0.1",
  "description": "Turn a readable stream into multiple readable streamx streams",
  "main": "index.js",
  "dependencies": {
    "streamx": "^2.12.5"
  },
  "devDependencies": {
    "standard": "^14.3.4",
    "tape": "^5.0.1"
  },
  "scripts": {
    "test": "standard && tape test.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/mafintosh/teex.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/mafintosh/teex/issues"
  },
  "homepage": "https://github.com/mafintosh/teex"
}
const PassThroughDecoder = require('./lib/pass-through-decoder')
const UTF8Decoder = require('./lib/utf8-decoder')

module.exports = class TextDecoder {
  constructor(encoding = 'utf8') {
    this.encoding = normalizeEncoding(encoding)

    switch (this.encoding) {
      case 'utf8':
        this.decoder = new UTF8Decoder()
        break
      case 'utf16le':
      case 'base64':
        throw new Error('Unsupported encoding: ' + this.encoding)
      default:
        this.decoder = new PassThroughDecoder(this.encoding)
    }
  }

  get remaining() {
    return this.decoder.remaining
  }

  push(data) {
    if (typeof data === 'string') return data
    return this.decoder.decode(data)
  }

  // For Node.js compatibility
  write(data) {
    return this.push(data)
  }

  end(data) {
    let result = ''
    if (data) result = this.push(data)
    result += this.decoder.flush()
    return result
  }
}

function normalizeEncoding(encoding) {
  encoding = encoding.toLowerCase()

  switch (encoding) {
    case 'utf8':
    case 'utf-8':
      return 'utf8'
    case 'ucs2':
    case 'ucs-2':
    case 'utf16le':
    case 'utf-16le':
      return 'utf16le'
    case 'latin1':
    case 'binary':
      return 'latin1'
    case 'base64':
    case 'ascii':
    case 'hex':
      return encoding
    default:
      throw new Error('Unknown encoding: ' + encoding)
  }
}
const b4a = require('b4a')

module.exports = class PassThroughDecoder {
  constructor(encoding) {
    this.encoding = encoding
  }

  get remaining() {
    return 0
  }

  decode(data) {
    return b4a.toString(data, this.encoding)
  }

  flush() {
    return ''
  }
}
const b4a = require('b4a')

/**
 * https://encoding.spec.whatwg.org/#utf-8-decoder
 */
module.exports = class UTF8Decoder {
  constructor() {
    this._reset()
  }

  get remaining() {
    return this.bytesSeen
  }

  decode(data) {
    if (data.byteLength === 0) return ''

    if (this.bytesNeeded === 0 && trailingIncomplete(data, 0) === 0) {
      this.bytesSeen = trailingBytesSeen(data)
      return b4a.toString(data, 'utf8')
    }

    let result = ''
    let start = 0

    if (this.bytesNeeded > 0) {
      while (start < data.byteLength) {
        const byte = data[start]

        if (byte < this.lowerBoundary || byte > this.upperBoundary) {
          result += '\ufffd'
          this._reset()
          break
        }

        this.lowerBoundary = 0x80
        this.upperBoundary = 0xbf
        this.codePoint = (this.codePoint << 6) | (byte & 0x3f)
        this.bytesSeen++
        start++

        if (this.bytesSeen === this.bytesNeeded) {
          result += String.fromCodePoint(this.codePoint)
          this._reset()
          break
        }
      }

      if (this.bytesNeeded > 0) return result
    }

    const trailing = trailingIncomplete(data, start)
    const end = data.byteLength - trailing

    if (end > start) result += b4a.toString(data, 'utf8', start, end)

    for (let i = end; i < data.byteLength; i++) {
      const byte = data[i]

      if (this.bytesNeeded === 0) {
        if (byte <= 0x7f) {
          this.bytesSeen = 0
          result += String.fromCharCode(byte)
        } else if (byte >= 0xc2 && byte <= 0xdf) {
          this.bytesNeeded = 2
          this.bytesSeen = 1
          this.codePoint = byte & 0x1f
        } else if (byte >= 0xe0 && byte <= 0xef) {
          if (byte === 0xe0) this.lowerBoundary = 0xa0
          else if (byte === 0xed) this.upperBoundary = 0x9f
          this.bytesNeeded = 3
          this.bytesSeen = 1
          this.codePoint = byte & 0xf
        } else if (byte >= 0xf0 && byte <= 0xf4) {
          if (byte === 0xf0) this.lowerBoundary = 0x90
          else if (byte === 0xf4) this.upperBoundary = 0x8f
          this.bytesNeeded = 4
          this.bytesSeen = 1
          this.codePoint = byte & 0x7
        } else {
          this.bytesSeen = 1
          result += '\ufffd'
        }

        continue
      }

      if (byte < this.lowerBoundary || byte > this.upperBoundary) {
        result += '\ufffd'
        i--
        this._reset()

        continue
      }

      this.lowerBoundary = 0x80
      this.upperBoundary = 0xbf

      this.codePoint = (this.codePoint << 6) | (byte & 0x3f)
      this.bytesSeen++

      if (this.bytesSeen === this.bytesNeeded) {
        result += String.fromCodePoint(this.codePoint)
        this._reset()
      }
    }

    return result
  }

  flush() {
    const result = this.bytesNeeded > 0 ? '\ufffd' : ''
    this._reset()
    return result
  }

  _reset() {
    this.codePoint = 0
    this.bytesNeeded = 0
    this.bytesSeen = 0
    this.lowerBoundary = 0x80
    this.upperBoundary = 0xbf
  }
}

function trailingIncomplete(data, start) {
  const len = data.byteLength
  if (len <= start) return 0

  const limit = Math.max(start, len - 4)

  let i = len - 1
  while (i > limit && (data[i] & 0xc0) === 0x80) i--

  if (i < start) return 0

  const byte = data[i]

  let needed
  if (byte <= 0x7f) return 0
  if (byte >= 0xc2 && byte <= 0xdf) needed = 2
  else if (byte >= 0xe0 && byte <= 0xef) needed = 3
  else if (byte >= 0xf0 && byte <= 0xf4) needed = 4
  else return 0

  const available = len - i
  return available < needed ? available : 0
}

function trailingBytesSeen(data) {
  const len = data.byteLength
  if (len === 0) return 0

  const last = data[len - 1]

  if (last <= 0x7f) return 0
  if ((last & 0xc0) !== 0x80) return 1

  const limit = Math.max(0, len - 4)

  let i = len - 2
  while (i >= limit && (data[i] & 0xc0) === 0x80) i--

  if (i < 0) return 1

  const first = data[i]

  let needed
  if (first >= 0xc2 && first <= 0xdf) needed = 2
  else if (first >= 0xe0 && first <= 0xef) needed = 3
  else if (first >= 0xf0 && first <= 0xf4) needed = 4
  else return 1

  if (len - i !== needed) return 1

  if (needed >= 3) {
    const second = data[i + 1]
    if (first === 0xe0 && second < 0xa0) return 1
    if (first === 0xed && second > 0x9f) return 1
    if (first === 0xf0 && second < 0x90) return 1
    if (first === 0xf4 && second > 0x8f) return 1
  }

  return 0
}
{
  "name": "text-decoder",
  "version": "1.2.7",
  "description": "Streaming text decoder that preserves multibyte Unicode characters",
  "exports": {
    "./package": "./package.json",
    ".": "./index.js"
  },
  "files": [
    "index.js",
    "lib"
  ],
  "scripts": {
    "format": "prettier --write .",
    "lint": "prettier --check . && lunte",
    "test": "npm run test:node && npm run test:bare",
    "test:node": "brittle-node test.js",
    "test:bare": "brittle-bare test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/text-decoder.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/text-decoder/issues"
  },
  "homepage": "https://github.com/holepunchto/text-decoder#readme",
  "dependencies": {
    "b4a": "^1.6.4"
  },
  "devDependencies": {
    "brittle": "^3.3.2",
    "lunte": "^1.3.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^1.0.0"
  }
}
module.exports = class TimeOrderedSet {
  constructor () {
    this.oldest = null
    this.latest = null
    this.length = 0
  }

  has (node) {
    return !!(node.next || node.prev) || node === this.oldest
  }

  add (node) {
    if (this.has(node)) this.remove(node)

    if (!this.latest && !this.oldest) {
      this.latest = this.oldest = node
      node.prev = node.next = null
    } else {
      this.latest.next = node
      node.prev = this.latest
      node.next = null
      this.latest = node
    }

    this.length++

    return node
  }

  remove (node) {
    if (!this.has(node)) return node

    if (this.oldest !== node && this.latest !== node) {
      node.prev.next = node.next
      node.next.prev = node.prev
    } else {
      if (this.oldest === node) {
        this.oldest = node.next
        if (this.oldest) this.oldest.prev = null
      }
      if (this.latest === node) {
        this.latest = node.prev
        if (this.latest) this.latest.next = null
      }
    }

    node.next = node.prev = null
    this.length--

    return node
  }

  toArray ({ limit = Infinity, reverse = false } = {}) {
    const list = []

    if (reverse) {
      let node = this.latest
      while (node && limit--) {
        list.push(node)
        node = node.prev
      }
    } else {
      let node = this.oldest
      while (node && limit--) {
        list.push(node)
        node = node.next
      }
    }

    return list
  }
}
{
  "name": "time-ordered-set",
  "version": "2.0.1",
  "description": "Efficiently maintain a set of nodes ordered by the time they were added to the set",
  "main": "index.js",
  "files": [
    "index.js"
  ],
  "devDependencies": {
    "brittle": "^3.0.0",
    "standard": "^17.1.2"
  },
  "scripts": {
    "test": "standard && brittle test.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/mafintosh/time-ordered-set.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/mafintosh/time-ordered-set/issues"
  },
  "homepage": "https://github.com/mafintosh/time-ordered-set"
}
module.exports = class TimerBrowser {
  constructor (ms, fn, ctx = null, interval = false) {
    this.ms = ms
    this.ontimeout = fn
    this.context = ctx || null
    this.interval = interval
    this.done = false

    this._timer = interval
      ? setInterval(callInterval, ms, this)
      : setTimeout(callTimeout, ms, this)
  }

  unref () {}

  ref () {}

  refresh () {
    if (this.done) return

    if (this.interval) {
      clearInterval(this._timer)
      this._timer = setInterval(callInterval, this.ms, this)
    } else {
      clearTimeout(this._timer)
      this._timer = setTimeout(callTimeout, this.ms, this)
    }
  }

  destroy () {
    this.done = true
    this.ontimeout = null

    if (this.interval) clearInterval(this._timer)
    else clearTimeout(this._timer)
  }

  static once (ms, fn, ctx) {
    return new this(ms, fn, ctx, false)
  }

  static on (ms, fn, ctx) {
    return new this(ms, fn, ctx, true)
  }
}

function callTimeout (self) {
  self.done = true
  self.ontimeout.call(self.context)
}

function callInterval (self) {
  self.ontimeout.call(self.context)
}
module.exports = isNode()
  ? require('./node')
  : require('./browser')

function isNode () {
  const to = setTimeout(function () {}, 1000)
  clearTimeout(to)
  return !!to.refresh
}
module.exports = class Timer {
  constructor (ms, fn, ctx = null, interval = false) {
    this.ms = ms
    this.ontimeout = fn
    this.context = ctx
    this.interval = interval
    this.done = false

    this._timer = interval
      ? setInterval(callInterval, ms, this)
      : setTimeout(callTimeout, ms, this)
  }

  unref () {
    this._timer.unref()
  }

  ref () {
    this._timer.ref()
  }

  refresh () {
    if (this.done !== true) this._timer.refresh()
  }

  destroy () {
    this.done = true
    this.ontimeout = null
    if (this.interval) clearInterval(this._timer)
    else clearTimeout(this._timer)
  }

  static once (ms, fn, ctx) {
    return new this(ms, fn, ctx, false)
  }

  static on (ms, fn, ctx) {
    return new this(ms, fn, ctx, true)
  }
}

function callTimeout (self) {
  self.done = true
  self.ontimeout.call(self.context)
}

function callInterval (self) {
  self.ontimeout.call(self.context)
}
{
  "name": "timeout-refresh",
  "version": "2.0.1",
  "description": "Efficiently refresh a timer",
  "main": "index.js",
  "dependencies": {},
  "devDependencies": {
    "standard": "^16.0.4",
    "tape": "^5.5.2"
  },
  "browser": "./browser.js",
  "scripts": {
    "test": "standard && tape test.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/mafintosh/timeout-refresh.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/mafintosh/timeout-refresh/issues"
  },
  "homepage": "https://github.com/mafintosh/timeout-refresh"
}
require.addon = require('require-addon')

module.exports = require.addon('.', __filename)
// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.

const v4Seg = '(?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])'
const v4Str = `(${v4Seg}[.]){3}${v4Seg}`
const IPv4Pattern = new RegExp(`^${v4Str}$`)

const v6Seg = '(?:[0-9a-fA-F]{1,4})'
const IPv6Pattern = new RegExp(
  '^(' +
    `(?:${v6Seg}:){7}(?:${v6Seg}|:)|` +
    `(?:${v6Seg}:){6}(?:${v4Str}|:${v6Seg}|:)|` +
    `(?:${v6Seg}:){5}(?::${v4Str}|(:${v6Seg}){1,2}|:)|` +
    `(?:${v6Seg}:){4}(?:(:${v6Seg}){0,1}:${v4Str}|(:${v6Seg}){1,3}|:)|` +
    `(?:${v6Seg}:){3}(?:(:${v6Seg}){0,2}:${v4Str}|(:${v6Seg}){1,4}|:)|` +
    `(?:${v6Seg}:){2}(?:(:${v6Seg}){0,3}:${v4Str}|(:${v6Seg}){1,5}|:)|` +
    `(?:${v6Seg}:){1}(?:(:${v6Seg}){0,4}:${v4Str}|(:${v6Seg}){1,6}|:)|` +
    `(?::((?::${v6Seg}){0,5}:${v4Str}|(?::${v6Seg}){1,7}|:))` +
    ')(%[0-9a-zA-Z-.:]{1,})?$'
)

const isIPv4 = (exports.isIPv4 = function isIPv4(host) {
  return IPv4Pattern.test(host)
})

const isIPv6 = (exports.isIPv6 = function isIPv6(host) {
  return IPv6Pattern.test(host)
})

exports.isIP = function isIP(host) {
  if (isIPv4(host)) return 4
  if (isIPv6(host)) return 6
  return 0
}
const events = require('events')
const b4a = require('b4a')
const binding = require('../binding')

module.exports = class NetworkInterfaces extends events.EventEmitter {
  constructor(udx) {
    super()

    this._handle = b4a.alloc(binding.sizeof_udx_napi_interface_event_t)
    this._watching = false
    this._destroying = null

    binding.udx_napi_interface_event_init(
      udx._handle,
      this._handle,
      this,
      this._onevent,
      this._onclose
    )

    this.interfaces = binding.udx_napi_interface_event_get_addrs(this._handle)
  }

  _onclose() {
    this.emit('close')
  }

  _onevent() {
    this.interfaces = binding.udx_napi_interface_event_get_addrs(this._handle)

    this.emit('change', this.interfaces)
  }

  watch() {
    if (this._watching) return this
    this._watching = true

    binding.udx_napi_interface_event_start(this._handle)

    return this
  }

  unwatch() {
    if (!this._watching) return this
    this._watching = false

    binding.udx_napi_interface_event_stop(this._handle)

    return this
  }

  async destroy() {
    if (this._destroying) return this._destroying
    this._destroying = events.once(this, 'close')

    binding.udx_napi_interface_event_close(this._handle)

    return this._destroying
  }

  [Symbol.iterator]() {
    return this.interfaces[Symbol.iterator]()
  }
}
const events = require('events')
const b4a = require('b4a')
const binding = require('../binding')
const ip = require('./ip')

module.exports = class UDXSocket extends events.EventEmitter {
  constructor(udx, opts = {}) {
    super()

    this.udx = udx

    this._handle = b4a.alloc(binding.sizeof_udx_napi_socket_t)
    this._inited = false
    this._host = null
    this._family = 0
    this._ipv6Only = opts.ipv6Only === true
    this._reuseAddress = opts.reuseAddress === true
    this._port = 0
    this._reqs = []
    this._free = []
    this._closing = null
    this._closed = false

    this._view64 = new BigUint64Array(
      this._handle.buffer,
      this._handle.byteOffset,
      this._handle.byteLength >> 3
    )

    this.streams = new Set()

    this.userData = null
  }

  get bound() {
    return this._port !== 0
  }

  get closing() {
    return this._closing !== null
  }

  get idle() {
    return this.streams.size === 0
  }

  get busy() {
    return this.streams.size > 0
  }

  get bytesTransmitted() {
    if (this._inited !== true) return 0
    return Number(this._view64[binding.offsetof_udx_socket_t_bytes_tx >> 3])
  }

  get packetsTransmitted() {
    if (this._inited !== true) return 0
    return Number(this._view64[binding.offsetof_udx_socket_t_packets_tx >> 3])
  }

  get bytesReceived() {
    if (this._inited !== true) return 0
    return Number(this._view64[binding.offsetof_udx_socket_t_bytes_rx >> 3])
  }

  get packetsReceived() {
    if (this._inited !== true) return 0
    return Number(this._view64[binding.offsetof_udx_socket_t_packets_rx >> 3])
  }

  get packetsDroppedByKernel() {
    if (this._inited !== true) return 0
    return Number(this._view64[binding.offsetof_udx_socket_t_packets_dropped_by_kernel >> 3])
  }

  toJSON() {
    return {
      bound: this.bound,
      closing: this.closing,
      streams: this.streams.size,
      address: this.address(),
      ipv6Only: this._ipv6Only,
      reuseAddress: this._reuseAddress,
      idle: this.idle,
      busy: this.busy
    }
  }

  _init() {
    if (this._inited) return

    binding.udx_napi_socket_init(
      this.udx._handle,
      this._handle,
      this,
      this._onsend,
      this._onmessage,
      this._onclose,
      this._reallocMessage
    )

    this._inited = true
  }

  _onsend(id, err) {
    const req = this._reqs[id]

    const onflush = req.onflush

    req.buffer = null
    req.onflush = null

    this._free.push(id)

    onflush(err >= 0)

    // gc the free list
    if (this._free.length >= 16 && this._free.length === this._reqs.length) {
      this._free = []
      this._reqs = []
    }
  }

  _onmessage(len, port, host, family) {
    this.emit('message', this.udx._consumeMessage(len), { host, family, port })
    return this.udx._buffer
  }

  _onclose() {
    this.emit('close')
  }

  _reallocMessage() {
    return this.udx._reallocMessage()
  }

  _onidle() {
    this.emit('idle')
  }

  _onbusy() {
    this.emit('busy')
  }

  _addStream(stream) {
    if (this.streams.has(stream)) return false
    this.streams.add(stream)
    if (this.streams.size === 1) this._onbusy()
    return true
  }

  _removeStream(stream) {
    if (!this.streams.has(stream)) return false
    this.streams.delete(stream)
    const closed = this._closeMaybe()
    if (this.idle && !closed) this._onidle()
    return true
  }

  address() {
    if (!this.bound) return null
    return { host: this._host, family: this._family, port: this._port }
  }

  bind(port, host) {
    if (this.bound) throw new Error('Already bound')
    if (this.closing) throw new Error('Socket is closed')

    if (!port) port = 0

    let flags = 0
    if (this._ipv6Only) flags |= binding.UV_UDP_IPV6ONLY
    if (this._reuseAddress) flags |= binding.UV_UDP_REUSEADDR

    let family

    if (host) {
      family = ip.isIP(host)
      if (!family) throw new Error(`${host} is not a valid IP address`)

      if (!this._inited) this._init()

      this._port = binding.udx_napi_socket_bind(this._handle, port, host, family, flags)
    } else {
      if (!this._inited) this._init()

      try {
        host = '::'
        family = 6
        this._port = binding.udx_napi_socket_bind(this._handle, port, host, family, flags)
      } catch {
        host = '0.0.0.0'
        family = 4
        this._port = binding.udx_napi_socket_bind(this._handle, port, host, family, flags)
      }
    }

    this._host = host
    this._family = family

    this.emit('listening')
  }

  async close() {
    if (this._closing) return this._closing
    this._closing = new Promise((resolve) => this.once('close', resolve))
    this._closeMaybe()
    return this._closing
  }

  _closeMaybe() {
    if (this._closed || this._closing === null) return this._closed

    if (!this._inited) {
      this._closed = true
      this.emit('close')
      return true
    }

    if (this.idle) {
      binding.udx_napi_socket_close(this._handle)
      this._closed = true
    }

    return this._closed
  }

  setTTL(ttl) {
    if (!this._inited) throw new Error('Socket not active')
    binding.udx_napi_socket_set_ttl(this._handle, ttl)
  }

  getRecvBufferSize() {
    if (!this._inited) throw new Error('Socket not active')
    return binding.udx_napi_socket_get_recv_buffer_size(this._handle)
  }

  setRecvBufferSize(size) {
    if (!this._inited) throw new Error('Socket not active')
    return binding.udx_napi_socket_set_recv_buffer_size(this._handle, size)
  }

  getSendBufferSize() {
    if (!this._inited) throw new Error('Socket not active')
    return binding.udx_napi_socket_get_send_buffer_size(this._handle)
  }

  setSendBufferSize(size) {
    if (!this._inited) throw new Error('Socket not active')
    return binding.udx_napi_socket_set_send_buffer_size(this._handle, size)
  }

  addMembership(group, ifaceAddress) {
    if (!this._inited) throw new Error('Socket not active')
    return binding.udx_napi_socket_set_membership(this._handle, group, ifaceAddress || '', true)
  }

  dropMembership(group, ifaceAddress) {
    if (!this._inited) throw new Error('Socket not active')
    return binding.udx_napi_socket_set_membership(this._handle, group, ifaceAddress || '', false)
  }

  async send(buffer, port, host, ttl) {
    if (this.closing) return false

    if (!host) host = '127.0.0.1'

    const family = ip.isIP(host)
    if (!family) throw new Error(`${host} is not a valid IP address`)

    if (!this.bound) this.bind(0)

    const id = this._allocSend()
    const req = this._reqs[id]

    req.buffer = buffer

    const promise = new Promise((resolve) => {
      req.onflush = resolve
    })

    binding.udx_napi_socket_send_ttl(
      this._handle,
      req.handle,
      id,
      buffer,
      port,
      host,
      family,
      ttl || 0
    )

    return promise
  }

  trySend(buffer, port, host, ttl) {
    if (this.closing) return

    if (!host) host = '127.0.0.1'

    const family = ip.isIP(host)
    if (!family) throw new Error(`${host} is not a valid IP address`)

    if (!this.bound) this.bind(0)

    const id = this._allocSend()
    const req = this._reqs[id]

    req.buffer = buffer
    req.onflush = noop

    binding.udx_napi_socket_send_ttl(
      this._handle,
      req.handle,
      id,
      buffer,
      port,
      host,
      family,
      ttl || 0
    )
  }

  _allocSend() {
    if (this._free.length > 0) return this._free.pop()
    const handle = b4a.allocUnsafe(binding.sizeof_udx_socket_send_t)
    return this._reqs.push({ handle, buffer: null, onflush: null }) - 1
  }
}

function noop() {}
const streamx = require('streamx')
const b4a = require('b4a')
const binding = require('../binding')
const ip = require('./ip')

const MAX_PACKET = 2048
const BUFFER_SIZE = 65536 + MAX_PACKET

module.exports = class UDXStream extends streamx.Duplex {
  constructor(udx, id, opts = {}) {
    super({ mapWritable: toBuffer, eagerOpen: true })

    this.udx = udx
    this.socket = null

    this._handle = b4a.alloc(binding.sizeof_udx_napi_stream_t)
    this._view = new Uint32Array(
      this._handle.buffer,
      this._handle.byteOffset,
      this._handle.byteLength >> 2
    )
    this._view16 = new Uint16Array(
      this._handle.buffer,
      this._handle.byteOffset,
      this._handle.byteLength >> 1
    )
    this._view64 = new BigUint64Array(
      this._handle.buffer,
      this._handle.byteOffset,
      this._handle.byteLength >> 3
    )

    this._wreqs = []
    this._wfree = []

    this._sreqs = []
    this._sfree = []
    this._closed = false

    this._flushing = 0
    this._flushes = []

    this._buffer = null
    this._reallocData()

    this._onwrite = null
    this._ondestroy = null
    this._firewall = opts.firewall || firewallAll

    this._remoteChanging = null
    this._previousSocket = null

    this.id = id
    this.remoteId = 0
    this.remoteHost = null
    this.remoteFamily = 0
    this.remotePort = 0

    this.userData = null

    binding.udx_napi_stream_init(
      this.udx._handle,
      this._handle,
      id,
      opts.framed ? 1 : 0,
      this,
      this._ondata,
      this._onend,
      this._ondrain,
      this._onack,
      this._onsend,
      this._onmessage,
      this._onclose,
      this._onfirewall,
      this._onremotechanged,
      this._reallocData,
      this._reallocMessage
    )

    if (opts.seq) binding.udx_napi_stream_set_seq(this._handle, opts.seq)

    binding.udx_napi_stream_recv_start(this._handle, this._buffer)
  }

  get connected() {
    return this.socket !== null
  }

  get mtu() {
    return this._view16[binding.offsetof_udx_stream_t_mtu >> 1]
  }

  get rtt() {
    return this._view[binding.offsetof_udx_stream_t_srtt >> 2]
  }

  get cwnd() {
    return this._view[binding.offsetof_udx_stream_t_cwnd >> 2]
  }

  get rtoCount() {
    return this._view16[binding.offsetof_udx_stream_t_rto_count >> 1]
  }

  get retransmits() {
    return this._view16[binding.offsetof_udx_stream_t_retransmit_count >> 1]
  }

  get bbrState() {
    const view = new Uint8Array(
      this._handle.buffer,
      this._handle.byteOffset,
      this._handle.byteLength
    )
    return view[binding.offsetof_udx_stream_t_bbr_state]
  }

  get bbrBandwidth() {
    return binding.udx_napi_stream_get_bw(this._handle)
  }

  get fastRecoveries() {
    return this._view16[binding.offsetof_udx_stream_t_fast_recovery_count >> 1]
  }

  get inflight() {
    return this._view[binding.offsetof_udx_stream_t_inflight >> 2]
  }

  get bytesTransmitted() {
    return Number(this._view64[binding.offsetof_udx_stream_t_bytes_tx >> 3])
  }

  get packetsTransmitted() {
    return Number(this._view64[binding.offsetof_udx_stream_t_packets_tx >> 3])
  }

  get bytesReceived() {
    return Number(this._view64[binding.offsetof_udx_stream_t_bytes_rx >> 3])
  }

  get packetsReceived() {
    return Number(this._view64[binding.offsetof_udx_stream_t_packets_rx >> 3])
  }

  get localHost() {
    return this.socket ? this.socket.address().host : null
  }

  get localFamily() {
    return this.socket ? this.socket.address().family : 0
  }

  get localPort() {
    return this.socket ? this.socket.address().port : 0
  }

  setInteractive(bool) {
    if (!this._closed) return
    binding.udx_napi_stream_set_mode(this._handle, bool ? 0 : 1)
  }

  connect(socket, remoteId, port, host, opts = {}) {
    if (this._closed) return

    if (this.connected) throw new Error('Already connected')
    if (socket.closing) throw new Error('Socket is closed')

    if (typeof host === 'object') {
      opts = host
      host = null
    }

    if (!host) host = '127.0.0.1'

    const family = ip.isIP(host)
    if (!family) throw new Error(`${host} is not a valid IP address`)
    if (!(port > 0 && port < 65536)) throw new Error(`${port} is not a valid port`)

    if (!socket.bound) socket.bind(0)

    this.remoteId = remoteId
    this.remotePort = port
    this.remoteHost = host
    this.remoteFamily = family
    this.socket = socket

    if (opts.ack) binding.udx_napi_stream_set_ack(this._handle, opts.ack)

    binding.udx_napi_stream_connect(this._handle, socket._handle, remoteId, port, host, family)

    this.socket._addStream(this)

    this.emit('connect')
  }

  changeRemote(socket, remoteId, port, host) {
    if (this._remoteChanging) throw new Error('Remote already changing')

    if (!this.connected) throw new Error('Not yet connected')
    if (socket.closing) throw new Error('Socket is closed')

    if (this.socket.udx !== socket.udx) {
      throw new Error('Cannot change to a socket on another UDX instance')
    }

    if (!host) host = '127.0.0.1'

    const family = ip.isIP(host)
    if (!family) throw new Error(`${host} is not a valid IP address`)
    if (!(port > 0 && port < 65536)) throw new Error(`${port} is not a valid port`)

    if (this.socket !== socket) this._previousSocket = this.socket

    this.remoteId = remoteId
    this.remotePort = port
    this.remoteHost = host
    this.remoteFamily = family
    this.socket = socket

    this._remoteChanging = new Promise((resolve, reject) => {
      const onchanged = () => {
        this.off('close', onclose)
        resolve()
      }

      const onclose = () => {
        this.off('remote-changed', onchanged)
        reject(new Error('Stream is closed'))
      }

      this.once('remote-changed', onchanged).once('close', onclose)
    })

    binding.udx_napi_stream_change_remote(
      this._handle,
      socket._handle,
      remoteId,
      port,
      host,
      family
    )

    this.socket._addStream(this)

    return this._remoteChanging
  }

  relayTo(destination) {
    if (this._closed) return

    binding.udx_napi_stream_relay_to(this._handle, destination._handle)
  }

  async send(buffer) {
    if (!this.connected || this._closed) return false

    const id = this._allocSend()
    const req = this._sreqs[id]

    req.buffer = buffer

    const promise = new Promise((resolve) => {
      req.onflush = resolve
    })

    binding.udx_napi_stream_send(this._handle, req.handle, id, buffer)

    return promise
  }

  trySend(buffer) {
    if (!this.connected || this._closed) return

    const id = this._allocSend()
    const req = this._sreqs[id]

    req.buffer = buffer
    req.onflush = noop

    binding.udx_napi_stream_send(this._handle, req.handle, id, buffer)
  }

  async flush() {
    if ((await streamx.Writable.drained(this)) === false) return false
    if (this.destroying) return false

    const missing = this._wreqs.length - this._wfree.length
    if (missing === 0) return true

    return new Promise((resolve) => {
      this._flushes.push({ flush: this._flushing++, missing, resolve })
    })
  }

  toJSON() {
    return {
      id: this.id,
      connected: this.connected,
      destroying: this.destroying,
      destroyed: this.destroyed,
      remoteId: this.remoteId,
      remoteHost: this.remoteHost,
      remoteFamily: this.remoteFamily,
      remotePort: this.remotePort,
      mtu: this.mtu,
      rtt: this.rtt,
      cwnd: this.cwnd,
      inflight: this.inflight,
      socket: this.socket ? this.socket.toJSON() : null
    }
  }

  _read(cb) {
    cb(null)
  }

  _writeContinue(err) {
    if (this._onwrite === null) return
    const cb = this._onwrite
    this._onwrite = null
    cb(err)
  }

  _destroyContinue(err) {
    if (this._ondestroy === null) return
    const cb = this._ondestroy
    this._ondestroy = null
    cb(err)
  }

  _writev(buffers, cb) {
    if (!this.connected) {
      throw customError('Writing while not connected not currently supported', 'ERR_ASSERTION')
    }

    let drained = true

    if (buffers.length === 1) {
      const id = this._allocWrite(1)
      const req = this._wreqs[id]

      req.flush = this._flushing
      req.buffer = buffers[0]

      drained = binding.udx_napi_stream_write(this._handle, req.handle, id, req.buffer) !== 0
    } else {
      const id = this._allocWrite(nextBatchSize(buffers.length))
      const req = this._wreqs[id]

      req.flush = this._flushing
      req.buffers = buffers

      drained = binding.udx_napi_stream_writev(this._handle, req.handle, id, req.buffers) !== 0
    }

    if (drained) cb(null)
    else this._onwrite = cb
  }

  _final(cb) {
    const id = this._allocWrite(1)
    const req = this._wreqs[id]

    req.flush = this._flushes
    req.buffer = b4a.allocUnsafe(0)

    const drained =
      binding.udx_napi_stream_write_end(this._handle, req.handle, id, req.buffer) !== 0

    if (drained) cb(null)
    else this._onwrite = cb
  }

  _predestroy() {
    if (!this._closed) binding.udx_napi_stream_destroy(this._handle)
    this._closed = true
    this._writeContinue(null)
  }

  _destroy(cb) {
    if (this.connected) this._ondestroy = cb
    else cb(null)
  }

  _ondata(read) {
    this.push(this._consumeData(read))
    return this._buffer
  }

  _onend(read) {
    if (read > 0) this.push(this._consumeData(read))
    this.push(null)
  }

  _ondrain() {
    this._writeContinue(null)
  }

  _flushAck(flush) {
    for (let i = this._flushes.length - 1; i >= 0; i--) {
      const f = this._flushes[i]
      if (f.flush < flush) break
      f.missing--
    }

    while (this._flushes.length > 0 && this._flushes[0].missing === 0) {
      this._flushes.shift().resolve(true)
    }
  }

  _onack(id) {
    const req = this._wreqs[id]

    req.buffers = req.buffer = null
    this._wfree.push(id)

    if (this._flushes.length > 0) this._flushAck(req.flush)

    // gc the free list
    if (this._wfree.length >= 64 && this._wfree.length === this._wreqs.length) {
      this._wfree = []
      this._wreqs = []
    }
  }

  _onsend(id, err) {
    const req = this._sreqs[id]

    const onflush = req.onflush

    req.buffer = null
    req.onflush = null

    this._sfree.push(id)

    onflush(err >= 0)

    // gc the free list
    if (this._sfree.length >= 16 && this._sfree.length === this._sreqs.length) {
      this._sfree = []
      this._sreqs = []
    }
  }

  _onmessage(len) {
    this.emit('message', this.udx._consumeMessage(len))
    return this.udx._buffer
  }

  _onclose(err) {
    this._closed = true

    if (this.socket) {
      this.socket._removeStream(this)
      this.socket = null
    }

    if (this._previousSocket) {
      this._previousSocket._removeStream(this)
      this._previousSocket = null
    }

    // no error, we don't need to do anything
    if (!err) return this._destroyContinue(null)

    if (this._ondestroy === null) this.destroy(err)
    else this._destroyContinue(err)
  }

  _onfirewall(socket, port, host, family) {
    if (port <= 0 || port > 65535) return 1
    return this._firewall(socket, port, host, family) ? 1 : 0
  }

  _onremotechanged() {
    if (this._previousSocket) {
      this._previousSocket._removeStream(this)
      this._previousSocket = null
    }

    this._remoteChanging = null
    this.emit('remote-changed')
  }

  _consumeData(len) {
    const next = this._buffer.subarray(0, len)
    this._buffer = this._buffer.subarray(len)
    if (this._buffer.byteLength < MAX_PACKET) this._reallocData()
    return next
  }

  _reallocData() {
    this._buffer = b4a.allocUnsafe(BUFFER_SIZE)
    return this._buffer
  }

  _reallocMessage() {
    return this.udx._reallocMessage()
  }

  _allocWrite(size) {
    if (this._wfree.length === 0) {
      const handle = b4a.allocUnsafe(binding.udx_napi_stream_write_sizeof(size))
      return (
        this._wreqs.push({
          handle,
          size,
          buffers: null,
          buffer: null,
          flush: 0
        }) - 1
      )
    }

    const free = this._wfree.pop()
    if (size === 1) return free

    const next = this._wreqs[free]
    if (next.size < size) {
      next.handle = b4a.allocUnsafe(binding.udx_napi_stream_write_sizeof(size))
      next.size = size
    }

    return free
  }

  _allocSend() {
    if (this._sfree.length > 0) return this._sfree.pop()
    const handle = b4a.allocUnsafe(binding.sizeof_udx_stream_send_t)
    return this._sreqs.push({ handle, buffer: null, resolve: null, reject: null }) - 1
  }
}

function noop() {}

function toBuffer(data) {
  return typeof data === 'string' ? b4a.from(data) : data
}

function firewallAll(socket, port, host) {
  return true
}

function customError(message, code) {
  const error = new Error(message)
  error.code = code
  return error
}

function nextBatchSize(n) {
  // try to coerce the the writevs into sameish size
  if (n === 1) return 1
  // group all < 8 to the same size, low mem overhead but save some small allocs
  if (n < 8) return 8
  if (n < 16) return 16
  if (n < 32) return 32
  if (n < 64) return 64
  return n
}
const b4a = require('b4a')
const binding = require('../binding')
const ip = require('./ip')
const Socket = require('./socket')
const Stream = require('./stream')
const NetworkInterfaces = require('./network-interfaces')

const MAX_MESSAGE = 4096
const BUFFER_SIZE = 65536 + MAX_MESSAGE

module.exports = class UDX {
  constructor() {
    this._handle = b4a.alloc(binding.sizeof_udx_napi_t)
    this._watchers = new Set()
    this._view64 = new BigUint64Array(
      this._handle.buffer,
      this._handle.byteOffset,
      this._handle.byteLength >> 3
    )

    this._buffer = null
    this._reallocMessage()

    binding.udx_napi_init(this._handle, this._buffer)
  }

  static isIPv4(host) {
    return ip.isIPv4(host)
  }

  static isIPv6(host) {
    return ip.isIPv6(host)
  }

  static isIP(host) {
    return ip.isIP(host)
  }

  get bytesTransmitted() {
    return Number(this._view64[binding.offsetof_udx_t_bytes_tx >> 3])
  }

  get packetsTransmitted() {
    return Number(this._view64[binding.offsetof_udx_t_packets_tx >> 3])
  }

  get bytesReceived() {
    return Number(this._view64[binding.offsetof_udx_t_bytes_rx >> 3])
  }

  get packetsReceived() {
    return Number(this._view64[binding.offsetof_udx_t_packets_rx >> 3])
  }

  get packetsDroppedByKernel() {
    return Number(this._view64[binding.offsetof_udx_t_packets_dropped_by_kernel >> 3])
  }

  _consumeMessage(len) {
    const next = this._buffer.subarray(0, len)
    this._buffer = this._buffer.subarray(len)
    if (this._buffer.byteLength < MAX_MESSAGE) this._reallocMessage()
    return next
  }

  _reallocMessage() {
    // TODO: move reallocation to native
    this._buffer = b4a.allocUnsafe(BUFFER_SIZE)
    return this._buffer
  }

  createSocket(opts) {
    return new Socket(this, opts)
  }

  createStream(id, opts) {
    return new Stream(this, id, opts)
  }

  networkInterfaces() {
    let [watcher = null] = this._watchers
    if (watcher) return watcher.interfaces

    watcher = new NetworkInterfaces(this)
    watcher.destroy()

    return watcher.interfaces
  }

  watchNetworkInterfaces(onchange) {
    const watcher = new NetworkInterfaces(this)

    this._watchers.add(watcher)
    watcher.on('close', () => {
      this._watchers.delete(watcher)
    })

    if (onchange) watcher.on('change', onchange)

    return watcher.watch()
  }

  async lookup(host, opts = {}) {
    const { family = 0 } = opts

    const req = b4a.alloc(binding.sizeof_udx_napi_lookup_t)
    const ctx = {
      req,
      resolve: null,
      reject: null
    }

    const promise = new Promise((resolve, reject) => {
      ctx.resolve = resolve
      ctx.reject = reject
    })

    binding.udx_napi_lookup(this._handle, req, host, family, ctx, onlookup)

    return promise
  }
}

function onlookup(err, host, family) {
  if (err) this.reject(err)
  else this.resolve({ host, family })
}
{
  "name": "udx-native",
  "version": "1.21.1",
  "description": "udx is reliable, multiplexed, and congestion-controlled streams over udp",
  "main": "lib/udx.js",
  "files": [
    "lib",
    "prebuilds",
    "binding.cc",
    "binding.js",
    "CMakeLists.txt"
  ],
  "imports": {
    "events": {
      "bare": "bare-events",
      "default": "events"
    }
  },
  "addon": true,
  "scripts": {
    "format": "prettier --write .",
    "lint": "prettier --check . && lunte",
    "test": "npm run test:node && npm run test:bare",
    "test:node": "brittle-node --coverage test/*.js",
    "test:bare": "brittle-bare --coverage test/*.js",
    "test:all": "brittle test/*.js test/slow/*.js",
    "test:generate": "brittle -r test/all.js test/*.js",
    "bench": "brittle test/bench/*.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/udx-native.git"
  },
  "keywords": [
    "tcp",
    "udp",
    "stream",
    "reliable"
  ],
  "author": "Holepunch Inc.",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/udx-native/issues"
  },
  "homepage": "https://github.com/holepunchto/udx-native#readme",
  "engines": {
    "bare": ">=1.17.4"
  },
  "dependencies": {
    "b4a": "^1.5.0",
    "bare-events": "^2.2.0",
    "require-addon": "^1.1.0",
    "streamx": "^2.22.0"
  },
  "devDependencies": {
    "bare-compat-napi": "^1.3.0",
    "brittle": "^3.1.0",
    "cmake-bare": "^1.1.10",
    "cmake-fetch": "^1.0.1",
    "cmake-napi": "^1.0.5",
    "is-ci": "^3.0.1",
    "lunte": "^1.3.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^2.0.0",
    "tiny-byte-size": "^1.1.0"
  }
}
exports.add = add
exports.has = has
exports.remove = remove
exports.swap = swap

function add (list, item) {
  if (has(list, item)) return item
  item._index = list.length
  list.push(item)
  return item
}

function has (list, item) {
  return item._index < list.length && list[item._index] === item
}

function remove (list, item) {
  if (!has(list, item)) return null

  var last = list.pop()
  if (last !== item) {
    list[item._index] = last
    last._index = item._index
  }

  return item
}

function swap (list, a, b) {
  if (!has(list, a) || !has(list, b)) return
  var tmp = a._index
  a._index = b._index
  list[a._index] = a
  b._index = tmp
  list[b._index] = b
}
{
  "name": "unordered-set",
  "version": "2.0.1",
  "description": "A couple of functions that make it easy to maintain an unordered set as an array in an efficient way",
  "main": "index.js",
  "dependencies": {},
  "devDependencies": {
    "standard": "^6.0.4",
    "tape": "^4.4.0"
  },
  "scripts": {
    "test": "standard && tape test.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/mafintosh/unordered-set.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/mafintosh/unordered-set/issues"
  },
  "homepage": "https://github.com/mafintosh/unordered-set"
}
const b4a = require('b4a')

unslab.all = all
unslab.is = is

module.exports = unslab

function unslab (buf) {
  if (buf === null || buf.buffer.byteLength === buf.byteLength) return buf
  const copy = b4a.allocUnsafeSlow(buf.byteLength)
  copy.set(buf, 0)
  return copy
}

function is (buf) {
  return buf.buffer.byteLength !== buf.byteLength
}

function all (list) {
  let size = 0
  for (let i = 0; i < list.length; i++) {
    const buf = list[i]
    size += buf === null || buf.buffer.byteLength === buf.byteLength ? 0 : buf.byteLength
  }

  const copy = b4a.allocUnsafeSlow(size)
  const result = new Array(list.length)

  let offset = 0
  for (let i = 0; i < list.length; i++) {
    let buf = list[i]

    if (buf !== null && buf.buffer.byteLength !== buf.byteLength) {
      copy.set(buf, offset)
      buf = copy.subarray(offset, offset += buf.byteLength)
    }

    result[i] = buf
  }

  return result
}
{
  "name": "unslab",
  "version": "1.3.0",
  "description": "Unslab some slab'ed buffers",
  "main": "index.js",
  "files": [
    "index.js"
  ],
  "dependencies": {
    "b4a": "^1.6.6"
  },
  "devDependencies": {
    "brittle": "^3.5.2",
    "standard": "^17.1.0"
  },
  "scripts": {
    "test": "standard && brittle test.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/holepunchto/unslab.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/unslab/issues"
  },
  "homepage": "https://github.com/holepunchto/unslab"
}
module.exports = read

var MSB = 0x80
  , REST = 0x7F

function read(buf, offset) {
  var res    = 0
    , offset = offset || 0
    , shift  = 0
    , counter = offset
    , b
    , l = buf.length

  do {
    if (counter >= l) {
      read.bytes = 0
      throw new RangeError('Could not decode varint')
    }
    b = buf[counter++]
    res += shift < 28
      ? (b & REST) << shift
      : (b & REST) * Math.pow(2, shift)
    shift += 7
  } while (b >= MSB)

  read.bytes = counter - offset

  return res
}
module.exports = encode

var MSB = 0x80
  , REST = 0x7F
  , MSBALL = ~REST
  , INT = Math.pow(2, 31)

function encode(num, out, offset) {
  out = out || []
  offset = offset || 0
  var oldOffset = offset

  while(num >= INT) {
    out[offset++] = (num & 0xFF) | MSB
    num /= 128
  }
  while(num & MSBALL) {
    out[offset++] = (num & 0xFF) | MSB
    num >>>= 7
  }
  out[offset] = num | 0
  
  encode.bytes = offset - oldOffset + 1
  
  return out
}
module.exports = {
    encode: require('./encode.js')
  , decode: require('./decode.js')
  , encodingLength: require('./length.js')
}

var N1 = Math.pow(2,  7)
var N2 = Math.pow(2, 14)
var N3 = Math.pow(2, 21)
var N4 = Math.pow(2, 28)
var N5 = Math.pow(2, 35)
var N6 = Math.pow(2, 42)
var N7 = Math.pow(2, 49)
var N8 = Math.pow(2, 56)
var N9 = Math.pow(2, 63)

module.exports = function (value) {
  return (
    value < N1 ? 1
  : value < N2 ? 2
  : value < N3 ? 3
  : value < N4 ? 4
  : value < N5 ? 5
  : value < N6 ? 6
  : value < N7 ? 7
  : value < N8 ? 8
  : value < N9 ? 9
  :              10
  )
}
{
  "name": "varint",
  "version": "5.0.0",
  "description": "protobuf-style varint bytes - use msb to create integer values of varying sizes",
  "main": "index.js",
  "scripts": {
    "test": "node test.js"
  },
  "repository": {
    "type": "git",
    "url": "git://github.com/chrisdickinson/varint.git"
  },
  "keywords": [
    "varint",
    "protobuf",
    "encode",
    "decode"
  ],
  "author": "Chris Dickinson <chris@neversaw.us>",
  "license": "MIT",
  "devDependencies": {
    "tape": "~2.12.3"
  }
}
const { runtime, platform, arch, version } =
  typeof Bare !== 'undefined'
    ? {
        runtime: 'bare',
        platform: global.Bare.platform,
        arch: global.Bare.arch,
        version: global.Bare.version
      }
    : typeof process !== 'undefined'
      ? {
          runtime: 'node',
          platform: global.process.platform,
          arch: global.process.arch,
          version: global.process.version
        }
      : typeof Window !== 'undefined'
        ? { runtime: 'browser', platform: 'unknown', arch: 'unknown', version: 'unknown' }
        : { runtime: 'unknown', platform: 'unknown', arch: 'unknown', version: 'unknown' }

exports.runtime = runtime
exports.version = version
exports.platform = platform
exports.arch = arch
exports.isBare = runtime === 'bare'
exports.isBareKit = exports.isBare && typeof BareKit !== 'undefined'
exports.isPear = typeof Pear !== 'undefined'
exports.isNode = runtime === 'node'
exports.isBrowser = runtime === 'browser'
exports.isWindows = platform === 'win32'
exports.isLinux = platform === 'linux'
exports.isMac = platform === 'darwin'
exports.isIOS = platform === 'ios' || platform === 'ios-simulator'
exports.isAndroid = platform === 'android'
exports.isElectron = typeof process !== 'undefined' && !!global.process.versions?.electron
exports.isElectronRenderer = exports.isElectron && global.process.type === 'renderer'
exports.isElectronWorker = exports.isElectron && global.process.type === 'worker'
{
  "name": "which-runtime",
  "version": "1.4.0",
  "description": "Detect if you are in Bare or Node and which os etc",
  "main": "index.js",
  "files": [
    "index.js"
  ],
  "scripts": {
    "format": "prettier --write .",
    "lint": "prettier --check . && lunte .",
    "test": "npm run test:node && npm run test:bare",
    "test:node": "brittle-node test.js",
    "test:bare": "brittle-bare test.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/holepunchto/which-runtime.git"
  },
  "author": "Holepunch Inc.",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/which-runtime/issues"
  },
  "homepage": "https://github.com/holepunchto/which-runtime",
  "devDependencies": {
    "brittle": "^3.0.0",
    "lunte": "^1.4.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^2.0.0"
  }
}
module.exports = class MaxCache {
  constructor({ maxSize, maxAge, createMap, ongc }) {
    this.maxSize = maxSize
    this.maxAge = maxAge
    this.ongc = ongc || null

    this._createMap = createMap || defaultCreateMap
    this._latest = this._createMap()
    this._oldest = this._createMap()
    this._retained = this._createMap()
    this._gced = false
    this._interval = null

    if (this.maxAge > 0 && this.maxAge < Infinity) {
      const tick = Math.ceil((2 / 3) * this.maxAge)
      this._interval = setInterval(this._gcAuto.bind(this), tick)
      if (this._interval.unref) this._interval.unref()
    }
  }

  get size() {
    return this._latest.size + this._oldest.size + this._retained.size
  }

  *[Symbol.iterator]() {
    for (const it of [this._latest, this._oldest, this._retained]) {
      yield* it
    }
  }

  *keys() {
    for (const it of [this._latest, this._oldest, this._retained]) {
      yield* it.keys()
    }
  }

  *values() {
    for (const it of [this._latest, this._oldest, this._retained]) {
      yield* it.values()
    }
  }

  destroy() {
    this.clear()
    clearInterval(this._interval)
    this._interval = null
  }

  clear() {
    this._gced = true
    this._latest.clear()
    this._oldest.clear()
    this._retained.clear()
  }

  set(k, v) {
    if (this._retained.has(k)) return this
    this._latest.set(k, v)
    this._oldest.delete(k) || this._retained.delete(k)
    if (this._latest.size >= this.maxSize) this._gc()
    return this
  }

  retain(k, v) {
    this._retained.set(k, v)
    this._latest.delete(k) || this._oldest.delete(k)
    return this
  }

  delete(k) {
    return (
      this._latest.delete(k) ||
      this._oldest.delete(k) ||
      this._retained.delete(k)
    )
  }

  has(k) {
    return this._latest.has(k) || this._oldest.has(k) || this._retained.has(k)
  }

  get(k) {
    if (this._latest.has(k)) {
      return this._latest.get(k)
    }

    if (this._oldest.has(k)) {
      const v = this._oldest.get(k)
      this._latest.set(k, v)
      this._oldest.delete(k)
      return v
    }

    if (this._retained.has(k)) {
      return this._retained.get(k)
    }

    return null
  }

  _gcAuto() {
    if (!this._gced) this._gc()
    this._gced = false
  }

  _gc() {
    this._gced = true
    if (this.ongc !== null && this._oldest.size > 0) this.ongc(this._oldest)
    this._oldest = this._latest
    this._latest = this._createMap()
  }
}

function defaultCreateMap() {
  return new Map()
}
{
  "name": "xache",
  "version": "1.3.0",
  "description": "Yet another auto expiring, max sizable cache",
  "main": "index.js",
  "files": [
    "index.js"
  ],
  "devDependencies": {
    "brittle": "^3.3.2",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^1.0.0"
  },
  "scripts": {
    "format": "prettier --write .",
    "test": "prettier --check . && brittle test.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/mafintosh/xache.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/mafintosh/xache/issues"
  },
  "homepage": "https://github.com/mafintosh/xache"
}
const b4a = require('b4a')

const ALPHABET = 'ybndrfg8ejkmcpqxot1uwisza345h769'
const MIN = 0x31 // 1
const MAX = 0x7a // z
const REVERSE = new Int8Array(1 + MAX - MIN)

REVERSE.fill(-1)

for (let i = 0; i < ALPHABET.length; i++) {
  const v = ALPHABET.charCodeAt(i) - MIN
  REVERSE[v] = i
}

exports.encode = encode
exports.decode = decode
exports.ALPHABET = ALPHABET

function decode (s, out) {
  let pb = 0
  let ps = 0

  const r = s.length & 7
  const q = (s.length - r) / 8

  if (!out) out = b4a.allocUnsafe(Math.ceil(s.length * 5 / 8))

  // 0 5 2 7 4 1 6 3 (+5 mod 8)
  for (let i = 0; i < q; i++) {
    const a = quintet(s, ps++)
    const b = quintet(s, ps++)
    const c = quintet(s, ps++)
    const d = quintet(s, ps++)
    const e = quintet(s, ps++)
    const f = quintet(s, ps++)
    const g = quintet(s, ps++)
    const h = quintet(s, ps++)

    out[pb++] = (a << 3) | (b >>> 2)
    out[pb++] = ((b & 0b11) << 6) | (c << 1) | (d >>> 4)
    out[pb++] = ((d & 0b1111) << 4) | (e >>> 1)
    out[pb++] = ((e & 0b1) << 7) | (f << 2) | (g >>> 3)
    out[pb++] = ((g & 0b111) << 5) | h
  }

  if (r === 0) return out.subarray(0, pb)

  const a = quintet(s, ps++)
  const b = quintet(s, ps++)

  out[pb++] = (a << 3) | (b >>> 2)

  if (r <= 2) return out.subarray(0, pb)

  const c = quintet(s, ps++)
  const d = quintet(s, ps++)

  out[pb++] = ((b & 0b11) << 6) | (c << 1) | (d >>> 4)

  if (r <= 4) return out.subarray(0, pb)

  const e = quintet(s, ps++)

  out[pb++] = ((d & 0b1111) << 4) | (e >>> 1)

  if (r <= 5) return out.subarray(0, pb)

  const f = quintet(s, ps++)
  const g = quintet(s, ps++)

  out[pb++] = ((e & 0b1) << 7) | (f << 2) | (g >>> 3)

  if (r <= 7) return out.subarray(0, pb)

  const h = quintet(s, ps++)

  out[pb++] = ((g & 0b111) << 5) | h

  return out.subarray(0, pb)
}

function encode (buf) {
  if (typeof buf === 'string') buf = b4a.from(buf)

  const max = buf.byteLength * 8

  let s = ''

  for (let p = 0; p < max; p += 5) {
    const i = p >>> 3
    const j = p & 7

    if (j <= 3) {
      s += ALPHABET[(buf[i] >>> (3 - j)) & 0b11111]
      continue
    }

    const of = j - 3
    const h = (buf[i] << of) & 0b11111
    const l = (i >= buf.byteLength ? 0 : buf[i + 1]) >>> (8 - of)

    s += ALPHABET[h | l]
  }

  return s
}

function quintet (s, i) {
  if (i > s.length) {
    return 0
  }

  const v = s.charCodeAt(i)

  if (v < MIN || v > MAX) {
    throw Error('Invalid character in base32 input: "' + s[i] + '" at position ' + i)
  }

  const bits = REVERSE[v - MIN]

  if (bits === -1) {
    throw Error('Invalid character in base32 input: "' + s[i] + '" at position ' + i)
  }

  return bits
}
{
  "name": "z32",
  "version": "1.1.0",
  "description": "Encode & decode z-base32",
  "main": "index.js",
  "dependencies": {
    "b4a": "^1.5.3"
  },
  "devDependencies": {
    "base-x": "^4.0.0",
    "base32": "0.0.7",
    "brittle": "^3.1.3",
    "nanobench": "^3.0.0",
    "rfc4648": "^1.5.2",
    "standard": "^17.0.0"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/mafintosh/z32.git"
  },
  "scripts": {
    "test": "standard && brittle test.js",
    "bench": "node benchmark.js"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/mafintosh/z32/issues"
  },
  "homepage": "https://github.com/mafintosh/z32"
}
{
  "name": "favela-peer",
  "version": "0.0.54",
  "description": "The favela daemon \u2014 feeds and documents of markdown records over Hypercore. No atproto, no ActivityPub.",
  "type": "module",
  "scripts": {
    "test": "brittle-bare test/*.test.js",
    "test:media": "brittle-bare test/transcode.spawn.js"
  },
  "imports": {
    "#node:fs": {
      "bare": "bare-fs",
      "default": "fs"
    },
    "#node:path": {
      "bare": "bare-path",
      "default": "path"
    },
    "#node:os": {
      "bare": "bare-os",
      "default": "os"
    },
    "#node:net": {
      "bare": "bare-net",
      "default": "net"
    },
    "#node:process": {
      "bare": "bare-process",
      "default": "process"
    }
  },
  "dependencies": {
    "anproto": "file:../anproto",
    "autobee": "^2.1.0",
    "b4a": "^1.6.0",
    "bare-fs": "^4.8.1",
    "bare-net": "^2.3.3",
    "bare-os": "^3.9.3",
    "bare-path": "^3.1.2",
    "bare-process": "^4.5.1",
    "bare-subprocess": "^6.1.0",
    "bip39-mnemonic": "^2.2.0",
    "blind-pairing": "^2.3.1",
    "compact-encoding": "^3.3.0",
    "corestore": "^7.12.3",
    "favela-identity": "file:../favela-identity",
    "favela-markdown": "file:../favela-markdown",
    "hyper-identity2": "file:../packages/hyper-identity2",
    "hyper-overlay": "file:../hyper-overlay",
    "hypercore-crypto": "^3.7.0",
    "hyperswarm": "^4.17.0",
    "keet-identity-key": "3.2.0",
    "protomux": "^3.11.0",
    "sodium-hmac": "^2.0.1",
    "hyperident": "file:../repos/hyperident"
  },
  "devDependencies": {
    "brittle": "^4.0.2"
  },
  "main": "index.js",
  "bin": {
    "favela-peer": "index.js",
    "favela": "bin/favela.js"
  },
  "pgh": {
    "bin": "favela-peer",
    "upgrade": "pgh://1dd36bdfa9cb7f7d84adbd07f017e7200f7f90a3a546cd0761fac2a1f8a13e86/favela-peer",
    "entrypoint": "index.js",
    "type": "daemon",
    "runtime": "bare",
    "autostart": false,
    "depends": [
      "pgh-sidecar"
    ],
    "defaults": {
      "env": {
        "FAVELA_STORAGE": "${HOME}/.config/favela-peer"
      }
    },
    "service": {
      "requiresNetwork": true
    }
  }
}
// multipart/form-data — enough of it to carry one small file from a no-JS form.
//
// ⛔ Byte-exact throughout. An image is binary, so the body is never turned into a string and back;
// the boundary search, the header split and the part slicing all work on bytes. Decoding to UTF-8
// anywhere in that path silently corrupts every non-text upload, and the corruption looks like a
// broken image rather than an error.
//
// This is deliberately the smallest thing that works: no streaming, no nested multiparts, no
// transfer-encodings. It exists because a browser cannot post a file any other way without
// JavaScript, and the surface's whole point is that reading and writing need none.

import b4a from 'b4a'

const CRLF = b4a.from('\r\n')
const DASH = 0x2d

/**
 * parse(body, contentType) -> { fields, files } | null
 *
 *   fields  name -> string        (utf-8 decoded, because a form field IS text)
 *   files   name -> { filename, contentType, bytes }
 *
 * Returns null when the content type is not multipart or carries no boundary — a caller should
 * treat that as a bad request rather than as an empty form.
 */
export function parse (body, contentType) {
  const m = /boundary=(?:"([^"]+)"|([^;]+))/i.exec(String(contentType || ''))
  if (!m) return null
  const boundary = (m[1] || m[2]).trim()
  if (!boundary) return null

  // Every delimiter in the body is `\r\n--boundary`, except the first, which has no leading CRLF.
  // Prepending one makes the scan uniform instead of special-casing the head.
  const buf = b4a.concat([CRLF, body])
  const delim = b4a.concat([CRLF, b4a.from('--' + boundary)])

  const out = { fields: {}, files: {} }
  let pos = b4a.indexOf(buf, delim)
  if (pos !== 0) return out // not our boundary; nothing to read rather than a guess

  while (pos !== -1) {
    let start = pos + delim.byteLength
    // `--` after the boundary is the terminator.
    if (buf[start] === DASH && buf[start + 1] === DASH) break
    // Otherwise a CRLF, then the part.
    if (buf[start] !== 0x0d || buf[start + 1] !== 0x0a) break
    start += 2

    const next = b4a.indexOf(buf, delim, start)
    if (next === -1) break
    readPart(buf.subarray(start, next), out)
    pos = next
  }

  return out
}

function readPart (part, out) {
  const sep = b4a.indexOf(part, b4a.concat([CRLF, CRLF]))
  if (sep === -1) return
  // Headers are ASCII by definition; the BODY is what must stay bytes.
  const head = b4a.toString(part.subarray(0, sep), 'utf8')
  const bytes = part.subarray(sep + 4)

  const name = /name="([^"]*)"/i.exec(head)
  if (!name) return
  const filename = /filename="([^"]*)"/i.exec(head)

  if (!filename) {
    out.fields[name[1]] = b4a.toString(bytes, 'utf8')
    return
  }
  // ⚠ An empty filename means "no file chosen" — every browser sends the part anyway. Treating it
  // as a zero-byte upload would clear an avatar every time the form was saved.
  if (!filename[1]) return

  const type = /content-type:\s*([^\r\n]+)/i.exec(head)
  out.files[name[1]] = {
    filename: filename[1],
    // ⛔ Recorded, never trusted. The daemon types the file from its magic bytes; this is only here
    // so a caller can report what the browser claimed.
    contentType: type ? type[1].trim() : null,
    bytes
  }
}

export default { parse }
// Pages — pure functions from daemon data to HTML. No I/O, so they are testable without a socket
// or a server, which is where nearly all the behaviour worth asserting lives.
//
// ⛔ Every value that reaches the page is escaped here or rendered by favela-markdown's render(),
// which escapes. Nothing interpolates raw. The one exception is `render()`'s output itself, which is
// HTML by contract and is the only trusted producer in the process.

import { render } from 'favela-markdown/lib/render.js'

const ESC = { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }
/**
 * Stamp a rendered page with a forced theme.
 *
 * ⛔ Applied at SEND time, not threaded through every page function and not held in a module
 * variable. A module-level "current theme" is request state in a shared place, and two interleaved
 * requests would hand one reader the other's setting.
 *
 * ⚠ null is SYSTEM and is the default. A site that picks for you and calls it a preference is worse
 * than one that has none, so an untouched page follows prefers-color-scheme.
 */
export function withTheme (html, theme) {
  if (theme !== 'light' && theme !== 'dark') return html
  return html.replace('<html lang="en">', '<html lang="en" data-theme="' + theme + '">')
}

/**
 * A banner on EVERY page when an update is downloaded and waiting.
 *
 * ⛔ APPLIED AT SEND TIME, exactly like `withTheme`, so no page function has to know this exists and
 * none can forget to render it. A notice that only appears where someone remembered to put it is a
 * notice nobody sees on the page they were actually on.
 *
 * ⛔ STAGED-AND-DIFFERENT ONLY. Staged and identical means the node re-verified the bundle it is
 * already running, which is the ordinary case — announcing that as an update would train people to
 * ignore the banner, and then the real one does nothing.
 *
 * ⚠ It links to settings rather than restarting from here. Restarting is a thing the SHELL does and
 * only the shell knows whether it can; a banner that offered a button the shell cannot honour would
 * be the same defect `parseShell` keeps separate flags to avoid.
 */
/**
 * Is a downloaded update actually waiting to be applied?
 *
 * ⛔ `running === null` MEANS "WE BOOTED THE APK'S OWN BUNDLE", and it is the normal state of a
 * freshly installed app — `chooseBundle` returns `hash: null` for the asset, so the shell has no
 * hash to report. Requiring `running` to be set therefore made the answer NO on exactly the devices
 * that most need it: the phone staged an update correctly and then rendered "Up to date", with no
 * banner, which is what "check now does nothing" looked like from the outside.
 *
 * Anything staged while we are running the asset is by construction not what booted, so it IS
 * waiting. When `running` IS known, the comparison is the real one — staged and identical means the
 * node re-verified what it is already running, which is the ordinary case and not an update.
 */
export function updateWaiting (u) {
  if (!u || !u.staged) return false
  return u.running ? u.staged !== u.running : true
}

export function withUpdateBanner (html, update) {
  const u = update || null
  if (!u || !updateWaiting(u)) return html
  const bar = '<div class="update-bar"><a href="/settings">An update is ready — restart to apply</a></div>'
  // After the opening <body>, so it is the first thing in the document and cannot be pushed below
  // the fold by whatever the page happens to render first.
  return html.includes('<body>') ? html.replace('<body>', '<body>' + bar) : bar + html
}

export const esc = (s) => String(s == null ? '' : s).replace(/[&<>"']/g, (c) => ESC[c])

const short = (k) => esc(String(k || '').slice(0, 8))

/**
 * Who wrote something — three tiers, rendered as three weights.
 *
 *     @alice0        the HANDLE      claimed in a namespace, unique within it, attested
 *     Alice Smith    the SELF-LABEL  what this feed calls itself; unique to nobody
 *     fd9ba4c6       the KEY         the only unforgeable thing on the row
 *
 * ⛔ That is NAMING.md §3's resolution order — handle, then self-label, then key — made visible
 * rather than merely implemented. A reader can see which of the three they are trusting.
 *
 * ⛔ The KEY is always shown, even beside both others. A self-label is a CLAIM by its holder,
 * nothing enforces uniqueness and nothing can; hiding the key behind a name would make
 * impersonation a matter of typing.
 *
 * ⛔ A handle arrives here only if the daemon CONFIRMED it — the claim's identity root matches the
 * feed's own founder. Anyone can claim a name pointing at anyone's feed key, so an unchecked handle
 * in a byline would let a stranger rename someone by typing. `confirmedHandles` refuses it upstream;
 * this function must never reintroduce it by reading a handle from anywhere else.
 */
export function who (author, profiles, { avatar = true } = {}) {
  const p = (profiles && profiles[author]) || null
  /**
   * ⛔ A PLACEHOLDER WHEN THERE IS NO PICTURE, never nothing.
   *
   * The avatar used to be omitted entirely for a feed that had published none, so a list of people
   * indented the ones with pictures and left the rest flush — every row a different shape, and the
   * whole list reading as loose text instead of rows. Post rows never had this problem because the
   * article grid always draws its circle. Mastodon and Bluesky both draw a default face; neither
   * has a row that starts with a name.
   */
  const img = !avatar
    ? ''
    : (p && p.avatar
        ? '<img class="av" src="/avatar/' + esc(p.avatar) + '" alt="">'
        : '<span class="av av-dot"></span>')
  const handle = p && p.handle ? '<span class="handle">@' + esc(p.handle) + '</span> ' : ''
  // The self-label is not bold when a handle is present: two names in one weight would read as one
  // name, and they are different KINDS of claim.
  const label = p && p.name
    ? (handle
        ? '<span class="label">' + esc(p.name) + '</span> '
        : '<span class="handle">' + esc(p.name) + '</span> ')
    : ''
  // ⛔ The key is a LINK, not decoration. It is the only unforgeable thing on the row, so it is both
  // always shown and the way to look someone up.
  // ⛔ Only the NAME links. Wrapping the avatar, the handle, the self-label and the key in one
  // anchor underlined all of it and made three different kinds of claim read as one run-on name.
  // The key is still a link — it is how you look someone up — but it is its own one.
  const name = handle || label
  return '<span class="byline">' +
    '<a href="/u/' + esc(author) + '">' + img + (name || '') + '</a>' +
    (handle && label ? label : '') +
    '<a class="key" href="/u/' + esc(author) + '"><code>' + short(author) + '</code></a>' +
    '</span>'
}

/**
 * A full key, out of the reading flow and never out of reach.
 *
 * ⛔ DEMOTED, NOT DELETED. Five pages printed a 64-character key as body text, where it wraps across
 * the column and outweighs everything a reader came for — the one thing neither Bluesky nor Mastodon
 * does anywhere. But `who()` above says why a key is shown at all: a self-label is a claim, nothing
 * enforces uniqueness, and hiding the key behind a name makes impersonation a matter of typing. That
 * argument holds for the full key too, which is also how you verify a stranger, follow a feed and
 * join a room. So it collapses; it does not disappear.
 *
 * ⚠ `<details>` — no JavaScript. Every other page here works with scripting off and this must too,
 * and a copy button would be the first thing on the surface that did not.
 */
export function keyline (label, key) {
  return '<details class="keyline"><summary>' + esc(label) + '</summary>' +
    '<code>' + esc(key) + '</code></details>'
}

/**
 * What a page says when it has nothing to show.
 *
 * ⛔ "Nothing yet." is the one thing the reader already knows. An empty state has three jobs: name
 * what is empty, say WHY it is empty, and offer the thing that fixes it. Bluesky's empty feed does
 * all three and puts two buttons on the page; ours did none of them.
 *
 * ⚠ `why` is escaped, and actions are {href, label} rather than markup — an empty state is the
 * place a caller is most tempted to pass a sentence with a link in it, and this file has already
 * been bitten by markup arriving through a value.
 */
export function empty (title, why, actions = []) {
  // ⚠ A next step is sometimes a POST — "make a room" is not a page you can navigate to. So an
  // action is {href} OR {post}, and this renders the right element for each. Still no markup
  // arriving through a value: the caller passes a URL and a label, never a fragment.
  const links = actions.length
    ? '<div class="choices">' + actions.map((a) => (a.post
      ? '<form method="post" action="' + esc(a.post) + '"><button>' + esc(a.label) + '</button></form>'
      : '<a class="file" href="' + esc(a.href) + '">' + esc(a.label) + '</a>')).join(' ') + '</div>'
    : ''
  return '<div class="empty"><p class="empty-title">' + esc(title) + '</p>' +
    (why ? '<p class="who">' + esc(why) + '</p>' : '') + links + '</div>'
}

const CSS = `
/* ⛔ ONE colour and ONE spacing scale, as variables. Values scattered through rules is how a page
   ends up with four greys that were each "close enough" at the time. */
/**
 * ⛔ Plain values, not light-dark(). The whole palette used one function, so a browser without it
 * loses EVERY colour at once — and there is no partial failure to notice, the page just renders in
 * initial values. The classic three-block form works everywhere and says the same thing.
 *
 * Light is the base. Dark applies when the reader's device asks for it AND they have not chosen
 * light; an explicit choice of dark applies always. That ordering is what makes the setting a
 * choice rather than a hint.
 */
:root{
  color-scheme:light dark;
  --fg:#16181c; --dim:#5b6570; --line:#e1e4e8; --accent:#1d6fd4;
  --bg:#ffffff; --bg-soft:#f6f8fa;
  /* Edge colours, declared ONCE and indirected through the palette where they follow it. Both the
     2D view and the 3D one read these, so a repost cannot be green in one picture and grey in the
     other after somebody edits a single rule. */
  --edge-reaction:var(--accent); --edge-repost:#2f9c63; --edge-reply:#b96a1f;
  --edge-follow:var(--dim); --edge-authored:var(--dim);
  --s1:.35rem; --s2:.7rem; --s3:1.1rem; --s4:1.8rem;
}
@media (prefers-color-scheme:dark){
  :root:not([data-theme=light]){
    color-scheme:dark;
    --fg:#e7e9ea; --dim:#8b98a5; --line:#2f3336; --accent:#6aa9f0;
    --bg:#0d0e10; --bg-soft:#16181c;
  }
}
:root[data-theme=dark]{
  color-scheme:dark;
  --fg:#e7e9ea; --dim:#8b98a5; --line:#2f3336; --accent:#6aa9f0;
  --bg:#0d0e10; --bg-soft:#16181c;
}
:root[data-theme=light]{color-scheme:light}
body{font:16px/1.6 system-ui,-apple-system,'Segoe UI',sans-serif;max-width:38rem;margin:0 auto;
  padding:var(--s3) var(--s3) 6rem;color:var(--fg);background:var(--bg);min-height:100vh}

/* Navigation reads as one row of peers, not eight loose links. No underline until you reach for it,
   and the page you are on is marked — otherwise the nav tells you nothing about where you are. */
/* ⛔ THE ROW WAS FIXED THREE TIMES BY SHRINKING THE GAP, and wrapped again at eleven items.
   "Eight items on one line at this width" became nine, then ten, each time leaving one orphaned
   link on a second row. A row that must fit N items breaks every time N grows, so the fourth fix is
   not a smaller gap: it is a layout that has no N. Beside the column the nav stacks, and stacking
   cannot wrap however many pages are added.

   ⚠ The ROW IS STILL THE DEFAULT. A phone has no gutter to put a sidebar in, so the row is what
   works everywhere and the sidebar is what a wide viewport earns. */
nav{display:flex;flex-wrap:wrap;gap:.55rem;border-bottom:1px solid var(--line);
  padding-bottom:var(--s2);margin-bottom:var(--s3);font-size:13.5px}
nav a{margin:0;text-decoration:none;color:var(--dim)}
nav a:hover{color:var(--fg);text-decoration:underline}
nav a[aria-current]{color:var(--fg);font-weight:600}

/* ⛔ Beside the column, not above it.
   The reading column stays 38rem — the measured match for Bluesky's and Mastodon's 600px — and the
   nav takes 11rem of the gutter that was empty either side of it. It aligns to the start so a short
   nav does not stretch down a long page, and sticks so it is still there after you scroll, which is
   the behaviour both references have and a top row cannot.

   ⛔ NO BACKTICKS IN THIS BLOCK. It lives in a JS template literal, so one backtick in a comment
   ends the string and the module stops parsing. Third time in this file; a test asserts it, but a
   parse error beats the test to it — the suite cannot import what will not parse.

   ⚠ 62rem, because that is where 11rem + 38rem + the gap first fits without squeezing the column. */
@media (min-width:62rem){
  body{max-width:52rem;display:grid;grid-template-columns:11rem minmax(0,38rem);
    gap:var(--s4);align-items:start}
  nav{flex-direction:column;align-items:stretch;gap:.1rem;border-bottom:0;padding-bottom:0;
    margin-bottom:0;position:sticky;top:var(--s3);font-size:14.5px}
  nav a{padding:.3rem .6rem;border-radius:999px}
  nav a:hover{background:var(--bg-soft);text-decoration:none;color:var(--fg)}
  nav a[aria-current]{background:var(--bg-soft)}
}

/* ⛔ The post is a GRID: a fixed avatar gutter and one content column, so every line of a post
   shares a left edge and the identity never shifts the text. */
/* Measured against mastodon/mastodon components.scss, which is the same structure:
   grid-template-columns: 46px minmax(0, 1fr), an 8px gutter (--thread-margin: 46px + 8px) and
   16px padding. minmax(0,1fr) rather than 1fr is what stops a long unbroken word widening the
   column instead of wrapping. */
/* ⚠ A post scrolled to by fragment sits flush against the top edge otherwise, which reads as the
   page being cut off rather than as an arrival. */
article{scroll-margin-top:1rem;border-top:1px solid var(--line);padding:1rem 0;display:grid;
  grid-template-columns:46px minmax(0,1fr);gap:8px}
.av-col{padding-top:.15rem}
.post-col{min-width:0}
img.av-lg-sm,.av-blank{width:46px;height:46px;border-radius:50%;object-fit:cover;display:block}
.av-blank{background:var(--line)}
/* ⛔ ONE line. The separator and the time used to fall to a second line with a dangling "·",
   because '.byline' was a block-level flex and the text after it wrapped. */
.meta{display:flex;align-items:baseline;flex-wrap:wrap;gap:.4rem;font-size:14px;color:var(--dim);
  margin-bottom:var(--s1)}
.meta time{font-variant-numeric:tabular-nums}
.meta .sep{opacity:.5}

/* ⛔ Monospace ONLY for a key. It is a hash and character-level legibility is the point. Everything
   else that used this class — timestamps, help text, a bio — is PROSE, and setting prose in
   monospace is most of why this page read as terminal output. */
.who{font-size:13px;color:var(--dim)}
code,.key{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:.88em}

/* The byline: three kinds of claim, three weights, and only the handle is a link. Wrapping all of
   it in one anchor underlined the lot and made it read as a single run-on name. */
.byline{display:inline-flex;align-items:baseline;gap:.4rem;font-size:14px;color:var(--dim)}
.byline a{text-decoration:none;color:inherit}
.byline a:hover{text-decoration:underline}
.byline .handle{color:var(--fg);font-weight:600}
.label{font-weight:400;color:var(--dim)}
.byline .key{color:var(--dim);opacity:.75}
.hashtag,.wikilink,.mention{color:var(--accent);text-decoration:none}

form{margin:var(--s3) 0}
textarea,input[type=text]{width:100%;font:inherit;padding:var(--s2);border:1px solid var(--line);
  border-radius:8px;background:transparent;color:inherit}
textarea:focus,input[type=text]:focus{outline:2px solid var(--accent);outline-offset:-1px}
button{font:inherit;padding:.4rem .9rem;border:1px solid var(--line);border-radius:999px;
  background:transparent;color:inherit;cursor:pointer}
button:hover{border-color:var(--dim)}
/* Liked is a STATE, so it has to look different from the offer to like. Colour alone would fail a
   reader who cannot see it; the glyph carries the same information. */
button.liked{font-weight:700;border-color:var(--accent);color:var(--accent)}

/* The composer is not the page. It was a third of the first screen before any content: a tall box,
   a permanent file input and two lines of help that never changed. */
.banner-lg{width:100%;max-height:190px;object-fit:cover;border-radius:10px;border:1px solid var(--line)}
.composer{background:var(--bg-soft);border:1px solid var(--line);border-radius:12px;
  padding:var(--s2);margin-bottom:var(--s4)}
.composer textarea{border:0;padding:var(--s1);min-height:3.2rem;resize:vertical}
.composer textarea:focus{outline:0}
.composer .row{display:flex;align-items:center;justify-content:space-between;gap:var(--s2)}
.composer .post{background:var(--accent);border-color:var(--accent);color:#fff;font-weight:600}
/* ⚠ The help still exists — it says SVG is refused and that the type is sniffed, which people need
   BEFORE they pick a file. It just stops shouting: <details> keeps it one click away. */
.composer details{font-size:13px;color:var(--dim);margin-top:var(--s1)}
.composer summary{cursor:pointer}
/* The @-picker. Anchored to the composer, which is why it is inside it. */
.composer{position:relative}
#at{position:absolute;z-index:5;left:var(--s2);right:var(--s2);top:3.4rem;margin:0;padding:.25rem;
  list-style:none;background:var(--bg);border:1px solid var(--line);border-radius:8px;
  box-shadow:0 6px 24px #0004;max-height:14rem;overflow:auto}
#at li{padding:.35rem .6rem;border-radius:6px;cursor:pointer;font-size:14px}
#at li span{color:var(--dim)}
#at li.on{background:var(--bg-soft)}
/* ⛔ The only unstyled control on the page: the browser draws an OS button and the words "No file
   chosen", and it is most of why the composer read as unfinished. ::file-selector-button styles the
   button and KEEPS the filename — the hidden-input-and-label trick throws that away, and with no
   JavaScript nothing else can tell you which file you picked. */
input[type=file]{font-size:13px;color:var(--dim);max-width:100%}
input[type=file]::file-selector-button{font:inherit;padding:.35rem .8rem;margin-right:.6rem;
  border:1px solid var(--line);border-radius:999px;background:transparent;color:var(--fg);
  cursor:pointer}
input[type=file]::file-selector-button:hover{border-color:var(--dim);background:var(--bg-soft)}
.composer input[type=file]{max-width:14rem}
.composer input[type=file]::file-selector-button{font:inherit;border:1px solid var(--line);
  background:transparent;color:var(--dim);border-radius:999px;padding:.25rem .7rem;margin-right:.5rem;
  cursor:pointer}
.composer .post{border-radius:999px;padding:.45rem 1.2rem}

.bio{margin:var(--s1) 0}
/* A field and its verb belong on one line — stacked, a one-field form looks like a page section. */
.scene{width:100%;height:60vh;min-height:22rem;border:1px solid var(--line);border-radius:8px;
  background:var(--bg-soft);touch-action:none;cursor:grab;overflow:hidden;position:relative}
/* Block-level, or the canvas sits on a text baseline and overflows the box it was sized to fit. */
.scene canvas{display:block}
/* The update notice. Deliberately loud — it is the one thing on the page that expires. */
.update-bar{background:var(--warn-bg,#8a5a00);text-align:center;padding:.5rem}
.update-bar a{color:#fff;text-decoration:none;font-weight:600}
/* Inline state words in the status table — loud enough to find, quiet enough to read past. */
.warn-inline{color:var(--warn,#c88)}
table{border-collapse:collapse;width:100%}
td,th{text-align:left;padding:.25rem .5rem;vertical-align:top}
.ns-list{list-style:none;padding:0}
.ns-list li{margin:.35rem 0}
form.inline{display:inline}
.explorer{display:flex;flex-direction:column;gap:8px}
/* ⚠ Fullscreen paints its own black behind the element, so the ground has to be declared again or
   a light-theme page turns into a light panel floating on black. */
.explorer:fullscreen{background:var(--bg);padding:12px;gap:10px}
.explorer:fullscreen .scene{height:auto;flex:1;min-height:0}
.scene-bar{display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap}
.legend{display:flex;gap:14px;flex-wrap:wrap;font-size:.82rem;color:var(--dim)}
.legend span{display:flex;align-items:center;gap:5px}
.legend i{display:inline-block;width:10px;height:10px;border-radius:50%}
.legend i.actor{background:var(--accent)}
.legend i.record{background:var(--dim)}
.legend i.gap{border:1px solid var(--dim);background:none}
.legend i.link{width:16px;height:0;border-radius:0;border-top:1px solid var(--dim)}
.legend i.weak{width:16px;height:0;border-radius:0;border-top:1px solid var(--dim);opacity:.35}
.legend i.e{width:16px;height:0;border-radius:0;border-top:2px solid var(--dim)}
.legend i.e-reaction{border-top-color:var(--edge-reaction)}
.legend i.e-repost{border-top-color:var(--edge-repost)}
.legend i.e-reply{border-top-color:var(--edge-reply)}
.legend i.e-follow{border-top-color:var(--edge-follow)}
.stats{display:flex;flex-wrap:wrap;gap:6px 18px;margin:0;font-size:.82rem;color:var(--dim)}
/* Each label+count is ONE flex item, or a wrap splits a pair across two lines. And dd carries a
   40px inline-start margin by default, which pushed the last counts off the edge. */
.stats>div{display:flex;gap:6px;align-items:baseline}
.stats dt,.stats dd{margin:0}
.stats b{color:var(--fg);font-variant-numeric:tabular-nums}
/* The hover label. Absolutely placed inside the scene, which is why .scene is positioned. */
.tip{position:absolute;pointer-events:none;background:var(--bg);border:1px solid var(--line);
  border-radius:6px;padding:3px 7px;font-size:.78rem;color:var(--fg);white-space:nowrap;z-index:2}
.scene:active{cursor:grabbing}
svg.graph{width:100%;height:auto;border:1px solid var(--line);border-radius:8px;background:var(--bg-soft)}
svg.graph text{font:11px ui-monospace,monospace;fill:var(--dim)}
svg.graph .e{stroke:var(--dim);stroke-width:1.2;opacity:.55}
svg.graph .e.reaction{stroke:var(--edge-reaction)}
svg.graph .e.repost{stroke:var(--edge-repost)}
svg.graph .e.reply{stroke:var(--edge-reply)}
svg.graph .e.follow{stroke:var(--edge-follow)}
svg.graph .e.authored{stroke:var(--edge-authored);opacity:.3}
/* ⛔ A dashed edge is a DIFFERENT FAILURE, not a faded one. Dash pattern rather than opacity alone,
   so it survives a reader who cannot distinguish the colours. */
svg.graph .gap{stroke-dasharray:4 3;opacity:1}
svg.graph circle.a{fill:var(--accent)}
svg.graph circle.r{fill:var(--dim)}
svg.graph circle.r.gap{fill:none;stroke:var(--dim);stroke-dasharray:2 2}
ul.legend{list-style:none;padding:0;font-size:13px;color:var(--dim)}
ul.legend li{margin:.35rem 0}
.legend .k{display:inline-block;width:26px;border-top:2px solid var(--dim);vertical-align:middle;margin-right:.5rem}
.legend .k.dash{border-top-style:dashed}
.said{margin:var(--s3) 0}
.say{border-top:1px solid var(--line);padding:var(--s2) 0}
.say .meta{margin-bottom:.15rem}
/* A person and the button that acts on them belong on one line. */
li.ask{display:flex;align-items:center;gap:var(--s2);flex-wrap:wrap}
li.ask form{margin:0}
.choices{display:flex;gap:var(--s1);margin:var(--s2) 0}
.choices form{margin:0}
.inline-form{display:flex;gap:var(--s1)}
.inline-form input[type=text]{flex:1}
.inline-form button{white-space:nowrap}
ul.people{list-style:none;padding:0;margin:0}
ul.people li{border-top:1px solid var(--line);padding:var(--s2) 0}

/* ⛔ Actions are RANKED and QUIET. Seven bordered pills read as seven equal controls and dominated
   the post they belonged to. Every mature client renders these as small, dim, borderless things
   that come forward on hover — the post is the content, not its buttons. */
/* ⚠ 18px between actions and 16px above them — mastodon's own numbers (.status__action-bar).
   Ours were 2px and 11px, which is why seven controls read as one cramped cluster rather than as
   seven things you can choose between. */
/* ⛔ ONE toolbar, spread across the row — the shape of mastodon's .status__action-bar.
   The palette used to be pushed to the far edge with margin-left:auto, so four words sat at one end
   and three emoji at the other with a hole between them, and the post read as carrying two
   unrelated sets of controls. It carries one. */
.reactions{display:flex;flex-wrap:wrap;gap:14px;align-items:center;margin-top:1rem;
  justify-content:space-between}
.reactions form{margin:0;display:inline-flex}
.reactions button,.reactions .go{border:0;background:transparent;color:var(--dim);font-size:14px;
  padding:.25rem .3rem;border-radius:6px;text-decoration:none;cursor:pointer}
.reactions button:hover,.reactions .go:hover{color:var(--fg);background:var(--bg-soft)}
.reactions button.liked{color:var(--accent);font-weight:600}
/* ⛔ A flex ITEM is not a flex container. The palette was a span holding inline-block buttons, so
   they wrapped INSIDE it and stacked into a column down the right of every post. */
.reactions .quick{display:inline-flex;gap:10px}
/* Only the ones NOT yet added are dimmed. A reaction you hold is on, and dimming it would say the
   opposite of what it means. */
.reactions .quick button{opacity:.55}
.reactions .quick button.liked{opacity:1}
.reactions .quick button:hover{opacity:1}

.warn{background:var(--bg-soft);border:1px solid var(--accent);color:var(--fg);
  padding:var(--s2);border-radius:8px}
/* An inline note, for a state that is ordinary rather than alarming. */
.flag{font-size:12px;color:var(--dim);border:1px solid var(--line);border-radius:999px;
  padding:.05rem .5rem;white-space:nowrap}
/* ⚠ The full key stays — it is the only unforgeable name on the page. It does not need to be the
   loudest thing on it: dim and small, so it is available rather than shouting. */
/* An empty state is a block with room around it, not a stray sentence where content would be. */
.empty{border:1px solid var(--line);border-radius:12px;padding:var(--s4) var(--s3);
  margin:var(--s3) 0;text-align:center;background:var(--bg-soft)}
.empty-title{font-weight:600;margin:0 0 var(--s1)}
.empty .who{margin:0 auto;max-width:26rem}
.empty .choices{justify-content:center;margin-top:var(--s3)}
.keyline{margin:var(--s1) 0;font-size:12px}
.keyline summary{color:var(--dim);cursor:pointer;list-style:none;width:fit-content}
.keyline summary::-webkit-details-marker{display:none}
/* A disclosure triangle drawn by hand, because the native one is inconsistent across browsers and
   this sits inline with prose rather than heading a section. */
.keyline summary::before{content:'▸ ';display:inline-block;transition:transform .12s}
.keyline[open] summary::before{transform:rotate(90deg)}
.keyline code{display:block;color:var(--dim);word-break:break-all;margin-top:.25rem;
  -webkit-user-select:all;user-select:all}
img.av,.av-dot{width:20px;height:20px;border-radius:50%;vertical-align:-4px;margin-right:.35rem;
  object-fit:cover;flex:none}
.av-dot{display:inline-block;background:var(--line)}
img.av-lg{width:96px;height:96px;border-radius:50%;object-fit:cover}
label{display:block;margin:.4rem 0}
pre{overflow-x:auto;background:var(--bg-soft);padding:var(--s2);border-radius:8px}
table{border-collapse:collapse}td,th{border:1px solid var(--line);padding:.2rem .5rem}
h1,h2,h3{line-height:1.25;margin:var(--s3) 0 var(--s2)}
a{color:var(--accent)}

/* ⛔ An embed is a PREVIEW. Rendered at its natural size a photograph takes over the timeline —
   a 4000px image is not a post, it is a wall — and everything below it stops being reachable.
   Capped by VIEWPORT height rather than pixels so it stays a preview on a phone and on a desktop,
   and \'object-fit:contain\' so nothing is cropped: a preview that hides part of the picture is
   worse than one that is small. The <a> around it opens the full file, with no script. */
a.lightbox{display:block;margin:.7rem 0;line-height:0}

/* The lighttable — several attachments as one block instead of a vertical stack.
   ⛔ GRID, not CSS columns. CSS columns is the only masonry that ships today and it flows
   down-then-across, so three attachments in two columns read 1,3,2 — reordering a sequence the
   author chose. Order is worth more here than the ragged edge masonry would save.
   The cost is object-fit cover, which crops: defensible for a CELL in a grid, where each is explicitly a
   thumbnail and the anchor gives back the whole picture, in a way it was not for a lone image. */
.lighttable{display:grid;grid-template-columns:repeat(auto-fit,minmax(9rem,1fr));gap:.35rem;margin:.7rem 0}
.lighttable a.lightbox{margin:0}
.lighttable .embed{width:100%;aspect-ratio:1;max-height:none;object-fit:cover}
.embed{max-width:100%;height:auto;max-height:60vh;object-fit:contain;border-radius:4px;background:#8881}
video.embed{width:100%}
/* A file we cannot preview is still a file: make the link look like one rather than like prose. */
a.file{display:inline-block;padding:.3rem .6rem;border:1px solid var(--line);border-radius:8px;
  margin:.3rem 0;text-decoration:none;color:inherit}
a.file:hover{border-color:var(--dim)}
embed.doc{width:100%;height:min(70vh,40rem);border:1px solid #8884;border-radius:4px}
`

/**
 * The login page — the ONE route that is not gated, and the only page with JavaScript.
 *
 * ⛔ Reading favela needs no JS. Signing does: an ANProto challenge is answered with an Ed25519
 * signature, and a browser cannot produce one from a form post. So the script here is the whole
 * concession, it runs on this page only, and everything it does is visible in it.
 *
 * The keypair lives in localStorage. That IS the credential — there is no password and no expiry —
 * so clearing site data means asking for a new invite, and a stolen key works until revoked. Both
 * are stated on the page rather than left to be discovered.
 */
export function login (message, unclaimed = false) {
  const warn = message ? '<p class="warn">' + esc(message) + '</p>' : ''
  // ⛔ TOFU. With nobody registered, the first browser to prove a key claims the instance — a fresh
  // install has no way to mint an invite for itself. The offer disappears permanently on the first
  // claim, and it is safe only because this binds localhost: whoever reaches the page first owns it.
  const body = unclaimed
    ? '<h1>Claim this favela</h1>' +
      '<p class="warn">Nobody has signed in yet. The first browser to do so claims this instance ' +
      'permanently — everyone after needs an invite.</p>' +
      '<form id="f"><button>claim</button></form>'
    : '<h1>Sign in</h1>' +
      '<p>Paste a one-time invite. Generate one on the machine running the daemon:</p>' +
      '<pre>favela invite</pre>' +
      '<form id="f"><input type="text" id="token" placeholder="invite token" autocomplete="off">' +
      '<button>redeem</button></form>'
  return layout('sign in', warn + body +
    '<p id="msg" class="who"></p>' +
    '<p class="who">Your browser keeps a key in local storage. It does not expire; ' +
    'clearing site data means asking for a new invite.</p>' +
    '<script type="module">\n' + LOGIN_JS + '\n</script>')
}

// Kept out of the template so the page above stays readable. It does three things: make or reuse a
// keypair, answer a challenge, and post the result.
const LOGIN_JS = `
import { ANProtoClient as an } from '/static/anproto-client.js'
const KEY = 'favela-keypair'
const msg = document.getElementById('msg')

async function keypair () {
  let kp = localStorage.getItem(KEY)
  if (!kp) { kp = await an.gen(); localStorage.setItem(KEY, kp) }
  return kp
}

async function answer () {
  const kp = await keypair()
  const { challenge } = await (await fetch('/auth/challenge', { method: 'POST' })).json()
  return { publicKey: an.getPublicKey(kp), challenge, signature: await an.sign(challenge, kp) }
}

// ⛔ Try the key we already hold, first and silently. A registered browser must not be asked for
// an invite it cannot obtain — that is a lockout, and it is what happens on every surface restart
// because sessions live in memory.
//
// Only runs when a keypair exists; a first visit has none and falls through to claim or redeem.
;(async () => {
  if (!localStorage.getItem(KEY)) return
  try {
    const res = await fetch('/auth/login', {
      method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(await answer())
    })
    if (res.ok) location.href = '/'
    else msg.textContent = 'This browser is not accepted here.'
  } catch {}
})()

document.getElementById('f').onsubmit = async (e) => {
  e.preventDefault()
  msg.textContent = 'signing…'
  try {
    // No token field means this is the TOFU claim — a fresh instance nobody holds yet.
    const el = document.getElementById('token')
    const body = await answer()
    if (el) body.token = el.value.trim()
    const res = await fetch(el ? '/auth/redeem' : '/auth/claim', {
      method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(body)
    })
    if (!res.ok) throw new Error((await res.json()).error || 'refused')
    location.href = '/'
  } catch (err) { msg.textContent = err.message }
}
`

/**
 * A bio squeezed onto one line for a listing.
 *
 * ⚠ It is MARKDOWN, and this does not render it — a list wants a sentence, not a heading and a
 * bullet list. Rendering happens on the profile page where there is room for it.
 */
function oneLine (text, max) {
  const flat = String(text || '').replace(/\s+/g, ' ').trim()
  return flat.length > max ? flat.slice(0, max - 1) + '…' : flat
}

/**
 * ⛔ The nav marks where you ARE. Eight identical underlined links told a reader nothing about which
 * page they were on, which is most of what a navigation bar is for.
 */
// ⛔ `discover` SITS BESIDE `search`, and the pair is deliberate. Search answers "where is this
// person I can name"; discover answers "who is here at all" — and until it existed only the first
// question could be asked, so joining a namespace and then finding nobody was the whole experience.
const NAV_ITEMS = [['/', 'timeline'], ['/rooms', 'rooms'], ['/search', 'search'], ['/discover', 'discover'],
  ['/following', 'following'],
  ['/notifications', 'notifications'], ['/mentions', 'mentions'], ['/knocks', 'knocks'],
  ['/graph', 'graph'], ['/explore', 'explore'], ['/status', 'status'],
  ['/settings', 'settings'], ['/logout', 'sign out']]
const NAV = (title) => '<nav>' + NAV_ITEMS.map(([href, label]) =>
  '<a href="' + href + '"' + (label === title || (title === 'favela' && href === '/') ? ' aria-current="page"' : '') +
  '>' + label + '</a>').join('') + '</nav>'

export function layout (title, body) {
  return '<!doctype html><html lang="en"><head><meta charset="utf-8">' +
    '<meta name="viewport" content="width=device-width,initial-scale=1">' +
    '<title>' + esc(title) + '</title><style>' + CSS + '</style></head><body>' +
    NAV(title) + '<main>' + body + '</main></body></html>'
}

/**
 * How `render` turns `favela://blob/<oid>` into something showable.
 *
 * ⛔ The library has no opinion about URLs — it asks, and this answers. `/blob/<oid>` is a path on
 * THIS surface, which streams the file from the daemon's disk; the bytes got to this machine over
 * the peer's own P2P replication. Nothing about a file crosses a network as HTTP.
 *
 * `files` is oid -> mimeType, resolved before rendering because `render` is synchronous and a type
 * comes from the daemon. An oid absent from it renders as nothing, which is right: we could not
 * establish what it is, and guessing is how a text file becomes an <img>.
 */
export const blobResolver = (files) => ({
  resolveBlob: (oid) => {
    const mimeType = files && files[oid]
    return mimeType ? { src: '/blob/' + oid, mimeType } : null
  }
})

// One record. `render()` produces the body HTML; everything else is escaped here.
/**
 * One record.
 *
 * ⛔ The like button renders its STATE. It used to be a bare `like` on every post whether you had
 * liked it or not, with no way back — so a reader could not tell a liked post from an unliked one,
 * and pressing it twice looked exactly like pressing it once. `row.liked` comes from the daemon,
 * computed from one listing of your own feed for the whole page.
 *
 * ⚠ No COUNT, deliberately. `peer.js` refuses to produce one: you may count what you can SEE, and
 * a number rendered here would be a local artifact that reads as a global total.
 */
export function post (row, profiles, files = null, from = '/') {
  const when = esc((row.createdAt || '').slice(0, 16).replace('T', ' '))
  const byline = who(row.author, profiles, { avatar: false })
  const target = encodeURIComponent(row.author) + '/' + encodeURIComponent(row.collection) + '/' + encodeURIComponent(row.name)
  /**
   * ⛔ Every post is an ANCHOR, and every action carries it.
   *
   * A no-JS action is a form POST and a redirect, so the browser loads the page again and lands at
   * the top — liking the twentieth post threw the reader back to the first. A fragment on the
   * redirect is the whole fix, and it needs no script: the browser scrolls to the post you acted on.
   */
  const anchor = 'p-' + (row.author + '-' + row.collection + '-' + row.name).replace(/[^A-Za-z0-9_-]/g, '')
  const back = from + '#' + anchor
  const liked = row.liked === true
  const mine = new Set(Array.isArray(row.reactions) ? row.reactions : [])

  const button = (value, label, on) =>
    '<form method="post" action="' + (on ? '/unreact' : '/react') + '">' +
    '<input type="hidden" name="target" value="' + esc(target) + '">' +
    '<input type="hidden" name="from" value="' + esc(back) + '">' +
    '<input type="hidden" name="value" value="' + esc(value) + '">' +
    '<button class="' + (on ? 'liked' : '') + '">' + label + '</button></form>'

  // ⛔ Like is offered as itself, not as one emoji among several. It is the one value both target
  // networks carry, and burying it in a palette would make the common act the same weight as a
  // reaction only keet and Mastodon can express.
  const likeBtn = button('like', liked ? '♥ liked' : '♡ like', liked)
  // ⚠ Only reactions I ALREADY hold are shown beside it. Everyone else's need a scan across held
  // feeds, which is per-target work and belongs on a post's own page, not on every timeline row.
  /**
   * ⚠ Held reactions and the palette live in ONE group. Rendering the ones you hold beside the
   * ACTS and the rest pushed right split the emoji across the row, so a 🔥 you had already added
   * looked orphaned in the middle of the controls rather than switched on.
   *
   * Order: what is on, then what is on offer. So adding one does not make it jump.
   */
  const emoji = '<span class="quick">' +
    [...mine].filter((v) => v !== 'like').map((v) => button(v, esc(v), true)).join('') +
    QUICK.filter((v) => !mine.has(v)).map((v) => button(v, v, false)).join('') +
    '</span>'

  const reposted = row.reposted === true
  const repostBtn = '<form method="post" action="' + (reposted ? '/unrepost' : '/repost') + '">' +
    '<input type="hidden" name="target" value="' + esc(target) + '">' +
    '<input type="hidden" name="from" value="' + esc(back) + '">' +
    '<button class="' + (reposted ? 'liked' : '') + '">' + (reposted ? '⇌ reposted' : '⇄ repost') +
    '</button></form>'

  // ⛔ QUOTE is not a button that acts — it is a link to the COMPOSER, prefilled. `record-vocabulary`
  // §3.5: "quotes are just links", so a quote is an ordinary post whose body references the quoted
  // record. There is no quote record to create, and pretending otherwise here would invent one.
  // ⛔ quote and thread NAVIGATE. Styling them as buttons said they act on the post, which they do
  // not — one seeds the composer and the other opens a page.
  const quoteBtn = '<a class="go" href="/?quote=' + encodeURIComponent(target) + '">quote</a>'
  // Every post is a way into its own conversation. The thread page is where replies live, because a
  // timeline that inlined them would stop being a timeline.
  const threadBtn = '<a class="go" href="/t/' + encodeURIComponent(target) + '">thread</a>'

  /**
   * ⛔ TWO COLUMNS — avatar in a gutter, everything else beside it. Every mature client in this
   * shape does it (bsky, mastodon, twitter) and the reason is structural rather than fashionable: a
   * post has one author and many lines, so the identity belongs in a fixed column and the content
   * gets a single consistent left edge. Inline, a 20px avatar pushes the first line of every post
   * to a different indent from the rest of it.
   */
  return '<article id="' + esc(anchor) + '">' +
    '<div class="av-col">' + avatarOf(row.author, profiles) + '</div>' +
    '<div class="post-col">' +
    '<div class="meta">' + byline + '<span class="sep">·</span><time>' + when + '</time></div>' +
    render(row.body || '', blobResolver(files)) +
    '<div class="reactions">' + likeBtn + repostBtn + quoteBtn + threadBtn + emoji +
    '</div></div></article>'
}

// The gutter avatar. ⚠ Separate from `who()`, which renders an INLINE one for lists and bylines —
// two sizes doing two jobs rather than one stretched to cover both.
function avatarOf (author, profiles) {
  const p = (profiles && profiles[author]) || null
  return p && p.avatar
    ? '<a href="/u/' + esc(author) + '"><img class="av-lg-sm" src="/avatar/' + esc(p.avatar) + '" alt=""></a>'
    : '<a href="/u/' + esc(author) + '"><span class="av-blank"></span></a>'
}

/**
 * The @-picker. Vendored and page-local, like the login script — the only other JS this surface
 * ships (`static/README.md` explains why nothing is taken as a dependency here).
 *
 * ⛔ PROGRESSIVE. It inserts `@alice`, which is ordinary text a person could have typed, and the
 * daemon resolves it on the write path either way. With JS off the composer still works; with it on
 * the picker cannot produce a mention the server would not have produced anyway. It is an
 * accelerant, never an authority.
 */

// A small fixed palette. ⚠ Not a picker: an arbitrary-value field is a text channel next to every
// post, and the daemon bounds the value for the same reason.
const QUICK = ['🔥', '😂', '👀']

export function timeline (rows, { unreadable = 0, profiles = null, files = null, message = null, quote = null, mention = null, empty: state = null } = {}) {
  const warn = unreadable
    // ⛔ Never silent. "nothing here" and "nothing here, and N records I hold pointers for but
    // cannot read" are different answers, and the daemon distinguishes them — so the page must too.
    ? '<p class="warn">' + unreadable + ' record(s) could not be read — the log arrived, the bytes did not. ' +
      '<a href="/status">what this means</a></p>'
    : ''
  /**
   * ⛔ TWO KINDS OF EMPTY, and they are not the same problem.
   *
   *   follows 0   there is nothing to read because nothing was asked for. The fix is to find
   *               somebody, and the page says where.
   *   follows N   N feeds were asked for and none of them has arrived yet. The fix is time, or a
   *               look at which ones are missing — and telling this reader to "follow somebody"
   *               would be advice they have already taken.
   *
   * Conflating them is the UI version of drawing a converged edge and a resolved one the same way.
   */
  const nothing = !state
    ? empty('Nothing yet.', null, [])
    : (state.follows === 0
        ? empty('Your timeline is empty.',
          'A timeline is made of the feeds you hold, and you hold none yet. Find somebody, or ' +
          'paste a feed key if you already have one.',
          [{ href: '/search', label: 'search for people' }, { href: '/following', label: 'paste a key' }])
        : empty('Nothing has arrived yet.',
          'You follow ' + state.follows + ' feed(s). None of them has anything this node can read ' +
          'so far — replication takes as long as it takes, and a feed that never arrives shows as ' +
          'not replicated in your list.',
          [{ href: '/following', label: 'see who you follow' }]))
  const body = rows.length ? rows.map((r) => post(r, profiles, files, '/')).join('') : nothing
  return layout('favela', composer(message, quote, mention) + warn + body)
}

export function composer (message, quote = null, mention = null) {
  const note = message ? '<p class="warn">' + esc(message) + '</p>' : ''
  /**
   * ⛔ A quote is PREFILLED TEXT, not a field. `record-vocabulary.md` §3.5 — "quotes are just
   * links" — so quoting produces an ordinary post whose body references the quoted record. There is
   * no quote record and no quote pointer; inventing either here would create a second thing the
   * whole stack would then have to carry.
   *
   * ⚠ The author can edit or delete the line before posting, which is the point: it is their text.
   */
  // ⚠ A mention seeds a real markdown LINK, because that is what a mention IS. The author can edit
  // the label or delete the line; what they cannot do is produce one by typing a bare @name, and
  // pretending otherwise is what `markdown-strip.md` §3.4 refuses.
  const seed = quote ? '> ' + quote + '\n\n' : (mention ? mention + ' ' : '')
  const hint = quote
    ? '<p class="who">Quoting — the link is ordinary text in your post, and you can change it.</p>'
    : (mention ? '<p class="who">The mention is a link in your text. A bare @name is not a mention ' +
        '— it would be a guess about who you meant.</p>' : '')
  // ⛔ multipart, because it carries a file. The file is stored FIRST and its oid appended to the
  // body as `![](favela://blob/<oid>)` — so what is published is an ordinary markdown record and
  // nothing about attachments is special-cased downstream.
  // ⛔ The composer is not the page. A tall box, a permanent file input and two lines of help that
  // never changed took a third of the first screen before any content appeared. The help still
  // EXISTS — it says SVG is refused and that the type is sniffed, which people need before they
  // choose a file — it just stops shouting.
  return note + '<form class="composer" method="post" action="/post" enctype="multipart/form-data">' +
    '<textarea name="body" rows="2" placeholder="write markdown…">' + esc(seed) + '</textarea>' + hint +
    '<div class="row">' +
    '<input type="file" name="file">' +
    '<button class="post">post</button>' +
    '</div>' +
    // ⚠ The list lives INSIDE the composer, so it is positioned against it and disappears with it.
    '<ul id="at" hidden></ul>' +
    '<details><summary>attachments</summary>' +
    'Images, video, PDF or text. The type is read from the file itself, never its name, and SVG is ' +
    'refused.</details>' +
    '</form>' +
    // ⛔ The picker script. It must be EMITTED — the first version defined MENTION_JS and never put
    // it on the page, so the list element existed, the endpoint answered, and nothing ever ran.
    '<script type="module" src="/static/composer.js"></script>'
}

export function search (query, results, unreadable, people = [], profiles = null, files = null, handles = []) {
  const form = '<form method="get" action="/search">' +
    '<input type="text" name="q" value="' + esc(query || '') + '" placeholder="search people and posts">' +
    '<button>search</button></form>'
  const warn = unreadable ? '<p class="warn">' + unreadable + ' record(s) in scope could not be read.</p>' : ''

  // ⛔ People and posts are shown APART. They are different answers to one query — someone typing
  // "stortron" may want the person or the mentions — and interleaving them would force the reader
  // to sort out which is which.
  const found = people.length
    ? '<h2>People</h2><ul>' + people.map((x) =>
      // ⛔ Merged with `profiles` so a person found by their self-label still renders their HANDLE.
      // A search result that showed a different name from the timeline would teach the reader that
      // the two disagree, which they do not.
      '<li>' + who(x.author, { [x.author]: { ...x, ...((profiles && profiles[x.author]) || {}) } }) +
      '</li>').join('') + '</ul>'
    : ''
  // ⚠ Liking from a search used to throw the search away — `/like` always redirected to `/`. The
  // page tells the route where it came from.
  const back = '/search?q=' + encodeURIComponent(query || '')
  /**
   * Handles from the shared namespace.
   *
   * ⛔ The gap this closes: `search` above looks at records and profiles of feeds you HOLD. A peer
   * you have never followed has neither, so a fleet sharing a namespace could not find itself —
   * every handle was resolvable and nothing asked. This is the one result kind that can name
   * somebody you have no copy of, which is exactly what discovery has to do.
   *
   * ⛔ MISMATCH is dropped, never annotated. A name claimed at someone else's feed key is the
   * attack, and a row a person can press "follow" on is the worst possible place to render it.
   * ⚠ `unheld` IS shown, labelled: we cannot check a feed we do not hold, and refusing to show it
   * would make the only feeds findable the ones you already have.
   */
  const nameRows = handles.filter((h) => h.verdict !== 'MISMATCH')
  const names = !nameRows.length ? '' : '<h2>Handles</h2><ul>' + nameRows.map((h) =>
    // ⚠ The handle LINKS to their page. From there you can look without following — search should
    // not force a follow just to see who somebody is.
    '<li><a href="/u/' + esc(h.feed) + '"><b>@' + esc(h.handle) + '</b></a>' +
    (h.verdict === 'confirmed' ? '' : ' <span class="who">[' + esc(h.verdict) + ']</span>') +
    // ⚠ A confirmed handle already names them; the key is what you check them AGAINST, which is a
    // second look rather than the first thing on the row.
    keyline('feed key', h.feed) +
    '<form method="post" action="/follow">' +
    '<input type="hidden" name="feed" value="' + esc(h.feed) + '">' +
    '<button>follow</button></form></li>').join('') + '</ul>'

  const hits = !query ? '' : '<h2>Posts</h2>' +
    (results.length ? results.map((r) => post(r, profiles, files, back)).join('') : '<p>No matches.</p>')
  return layout('search', form + warn + names + found + hits)
}

/**
 * Posts that NAME me.
 *
 * ⛔ Only from feeds this node holds. Someone you have no copy of can name you and you will never
 * know — the structural limit of writing every interaction in its author's own feed. The page says
 * so rather than letting an empty list imply nobody has mentioned you.
 */
export function mentions (rows, profiles = null, files = null) {
  const body = rows.length
    ? rows.map((r) => post(r, profiles, files, '/mentions')).join('')
    : empty('Nobody you hold has named you.',
      'And only a [@name](favela://…) link counts — a bare @name is plain text, never a guess ' +
      'about who was meant.',
      [{ href: '/following', label: 'see who you follow' }])
  const note = rows.length
    ? '<p class="who">Only posts from feeds you hold are searched, and only a ' +
      '<code>[@name](favela://…)</code> link counts — a bare <code>@name</code> is plain text, ' +
      'never a guess about who was meant.</p>'
    : ''
  return layout('mentions', body + note)
}

/**
 * One conversation: the subject, then its replies, then a box to add one.
 *
 * ⛔ `subject` may be null — we hold a reply pointing at a post we do not have. That is a different
 * fact from "no replies yet" and the page says which, rather than rendering an unread subject as an
 * empty conversation.
 *
 * ⚠ Replies are rendered in the order given. `peer.repliesTo` sorts them oldest-first because a
 * thread reads FORWARDS where a timeline reads backwards; re-sorting here would undo that.
 */
export function thread (subject, replies, profiles = null, files = null, target = '') {
  const head = subject
    ? post(subject, profiles, files, '/t/' + encodeURIComponent(target))
    : '<p class="warn">The post being replied to cannot be read — you may not hold that feed.</p>'
  const body = replies.length
    ? replies.map((r) => post(r, profiles, files, '/t/' + encodeURIComponent(target))).join('')
    : '<p class="who">No replies yet.</p>'
  const form = '<form method="post" action="/reply">' +
    '<input type="hidden" name="target" value="' + esc(target) + '">' +
    '<textarea name="body" rows="3" placeholder="reply…"></textarea>' +
    '<button>reply</button></form>'
  return layout('thread', head + '<h2>Replies</h2>' + body + form)
}

/**
 * What other people did that concerns you.
 *
 * ⛔ Derived on read from records you already hold — nothing was delivered to produce it. The page
 * says so, because "no notifications" here means something different from an empty inbox elsewhere:
 * only feeds you HOLD can appear, so a stranger interacting with you is invisible. The doorbell is
 * the exception and it is in the list.
 */
export function notifications (rows, profiles = null) {
  const VERB = {
    reaction: (n) => (n.value === 'like' ? 'liked' : 'reacted ' + esc(n.value || '')) + ' your post',
    repost: () => 'reposted your post',
    reply: () => 'replied to your post',
    mention: () => 'mentioned you',
    follow: () => 'followed you',
    knock: () => 'rang your doorbell'
  }
  const items = rows.length
    ? '<ul class="people">' + rows.map((n) => {
      const verb = (VERB[n.kind] || (() => n.kind))(n)
      const link = n.target
        ? ' <a href="/t/' + encodeURIComponent(n.target.feed + '/' + n.target.collection + '/' + n.target.name) + '">→</a>'
        : ''
      const snippet = n.body ? '<div class="who bio">' + esc(n.body.replace(/\s+/g, ' ').trim().slice(0, 120)) + '</div>' : ''
      return '<li>' + who(n.author, profiles) + ' ' + verb + link + snippet + '</li>'
    }).join('') + '</ul>'
    : empty('No notifications.',
      'These are derived from feeds you already hold — nothing is sent to you. Somebody whose feed ' +
      'you do not hold can interact with you and this will not show it; ringing your doorbell is ' +
      'the one thing they can always do.',
      [{ href: '/knocks', label: 'see who knocked' }, { href: '/following', label: 'see who you follow' }])
  const note = rows.length
    ? '<p class="who">Derived from feeds you already hold — nothing is sent to you. Someone whose ' +
      'feed you do not hold can interact with you and this will not show it; ringing your doorbell ' +
      'is the one thing they can always do.</p>'
    : ''
  return layout('notifications', items + note)
}

/**
 * The rooms this node is in.
 *
 * ⛔ A room key is a CAPABILITY — holding it is what lets you replicate the room at all — so this
 * page shows the key and offers a box to paste one. There is no directory to browse, and that is
 * the model rather than a gap: a room is reached because somebody handed you the way in.
 */
/**
 * The graph — the substrate, drawn.
 *
 * ⛔ Inline SVG with a DETERMINISTIC layout and no script. A force simulation would need JS, and
 * would also move things between reloads, which for a diagnostic is worse than ugly: you could not
 * tell a changed graph from a re-run.
 *
 * ⛔ The STATE is on the edge, which is the whole point (EDGE-GRAPH §4). Solid means we hold it and
 * it converged; dashed means converged but the bytes are not readable; the legend says which
 * failure each one is, because "a dashed edge at ② is a quorum problem and at ③ a byte problem" is
 * the distinction the view exists to make.
 */
export function graph (g) {
  const W = 640
  const actors = g.nodes.filter((n) => n.type === 'actor')
  const records = g.nodes.filter((n) => n.type === 'record')
  const H = Math.max(280, 120 + records.length * 26)

  // Actors down the left, records down the right. ⚠ Deterministic: same input, same picture.
  const pos = new Map()
  actors.forEach((n, i) => pos.set(n.id, { x: 90, y: 60 + i * Math.max(40, (H - 120) / Math.max(1, actors.length - 1 || 1)) }))
  records.forEach((n, i) => pos.set(n.id, { x: W - 190, y: 50 + i * 26 }))

  const short = (id) => esc(id.length > 20 ? id.slice(0, 8) + '…' : id)
  /**
   * ⛔ A PAIR OF EDGES MUST LOOK LIKE TWO. A→B and B→A between the same points draw on top of each
   * other and become one line — which hides precisely the failure EDGE-GRAPH §4.1 says the view
   * exists to catch: "the common bug is one direction landing and the other not". Each direction is
   * bowed to its own side, so a pair reads as two and a lone direction reads as one.
   */
  const bow = (a, b, back) => {
    const mx = (a.x + b.x) / 2; const my = (a.y + b.y) / 2
    const dx = b.x - a.x; const dy = b.y - a.y
    const len = Math.hypot(dx, dy) || 1
    const k = (back ? -1 : 1) * 14
    return 'M' + a.x + ' ' + a.y + ' Q' + (mx - (dy / len) * k) + ' ' + (my + (dx / len) * k) +
      ' ' + b.x + ' ' + b.y
  }
  const seen = new Set()

  const lines = g.edges.map((e) => {
    const a = pos.get(e.from); const b = pos.get(e.to)
    if (!a || !b) return ''
    // The second direction of a pair bows the other way.
    const back = seen.has(e.to + '|' + e.from)
    seen.add(e.from + '|' + e.to)
    const target = g.nodes.find((n) => n.id === e.to)
    // ⛔ The SUBJECT's state decides the line: an edge pointing at a record whose bytes we cannot
    // read is the byte-availability failure, and it must not look like the ones that worked.
    const unresolved = target && target.state === 'converged'
    return '<path d="' + bow(a, b, back) + '" fill="none"' +
      ' class="e ' + esc(e.kind) + (unresolved ? ' gap' : '') + '"><title>' +
      esc(e.kind + (e.value ? ' ' + e.value : '') + ' — ' + (unresolved ? 'bytes NOT readable' : 'converged')) +
      '</title></path>'
  }).join('')

  const dots = [...pos].map(([id, p]) => {
    const n = g.nodes.find((x) => x.id === id)
    const cls = n.type === 'actor' ? 'a' : (n.state === 'converged' ? 'r gap' : 'r')
    return '<circle cx="' + p.x + '" cy="' + p.y + '" r="' + (n.type === 'actor' ? 7 : 4) + '" class="' + cls + '">' +
      '<title>' + esc(id) + '</title></circle>' +
      (n.type === 'actor'
        ? '<text x="' + (p.x - 12) + '" y="' + (p.y + 4) + '" text-anchor="end">' + short(id) + '</text>'
        // ⚠ Records are labelled by their NAME, not their key. The key is the actor's and repeating
        // it on every record of theirs is noise that hides the one part that differs.
        : '<text x="' + (p.x + 10) + '" y="' + (p.y + 4) + '">' + esc(id.split('/').slice(1).join('/')) + '</text>')
  }).join('')

  return layout('graph',
    '<h1>Graph</h1>' +
    '<p class="who">' + esc(String(g.nodes.length)) + ' nodes, ' + esc(String(g.edges.length)) +
    ' edges — everything this node holds. Not the network: a graph of one node\'s view is the only ' +
    'honest one.</p>' +
    '<svg viewBox="0 0 ' + W + ' ' + H + '" class="graph">' + lines + dots + '</svg>' +
    '<ul class="legend">' +
    '<li><span class="k solid"></span> converged — we hold it and it linearized</li>' +
    '<li><span class="k dash"></span> bytes NOT readable — the pointer arrived, the file did not</li>' +
    '<li class="who">projection to other networks: <b>' + esc(g.projection) + '</b> — no bridge is ' +
    'wired to this daemon, and a state we cannot observe is not drawn as one we can</li>' +
    '</ul>')
}

/**
 * The discovery view — a separate PAGE, on purpose.
 *
 * ⛔ EDGE-GRAPH §5: the debug and discovery renderings have opposite duties and "must not be one
 * screen". This one rotates, which is useful for exploring and useless for comparing; the debug
 * view stays deterministic so a diagnosis can be screenshotted.
 *
 * ⛔ It never names a cluster, and there is no export. COMMUNITY-EMERGENCE: "the observation is an
 * intervention" — surfacing a cluster is allowed, naming it is a human act. And §6.4: the
 * aggregation attack's mitigation is "no mechanism, not a permission check".
 */
export function explore () {
  return layout('explore',
    '<h1>Explore</h1>' +
    '<p class="who">Force-directed, and the third axis is TIME — a cluster forming reads as a ' +
    'widening shape. Drag to rotate, scroll to zoom. This is a picture for looking at, not for ' +
    'comparing: the <a href="/graph">graph</a> is the one that holds still.</p>' +
    '<div class="explorer">' +
    '<div class="scene-bar"><div class="legend">' +
    // ⛔ The legend is SERVER-RENDERED and needs no JavaScript. It is the direct answer to "I have
    // no idea what the points are", and an answer that arrives only once a megabyte of WebGL has
    // loaded is not one.
    '<span><i class="actor"></i> a person</span>' +
    '<span><i class="record"></i> a post</span>' +
    '<span><i class="gap"></i> held, bytes never arrived</span>' +
    '<span><i class="e e-reaction"></i> a reaction</span>' +
    '<span><i class="e e-repost"></i> a repost</span>' +
    '<span><i class="e e-reply"></i> a reply</span>' +
    '<span><i class="e e-follow"></i> a follow</span>' +
    // ⚠ Authorship is drawn faint because it is always true and would otherwise bury every social
    // edge. Saying so is the difference between a quiet line and one you assume is a failed link.
    '<span><i class="weak"></i> who wrote it</span>' +
    '</div><button type="button" id="fullscreen">fullscreen</button></div>' +
    '<div id="scene" class="scene"><div class="tip" id="tip" hidden></div></div>' +
    '<dl class="stats" id="stats"></dl>' +
    '</div>' +
    '<p class="who">⛔ It does not name what it finds. Surfacing a cluster is one thing; naming it ' +
    'is a political act and belongs to a person. There is no export, deliberately — a view that ' +
    'combines several peers is the aggregation this model exists to prevent.</p>' +
    '<p class="who">Hover a point to see what it is; click one to open it on a plane in the scene. ' +
    'Scroll in among the points and the nearest posts open by themselves. ' +
    '⚠ Needs JavaScript and ships ~730K of three.js, vendored. Everything else here works without ' +
    'either.</p>' +
    '<script type="module" src="/static/explore.js"></script>')
}

export function rooms (list) {
  const items = list.length
    ? '<ul class="people">' + list.map((r) =>
      '<li><a href="/r/' + esc(r.key) + '"><b>' + esc(r.config.title || 'untitled room') + '</b></a>' +
      '<div class="who">' + esc(String(r.members)) + ' member(s) · <code>' + esc(r.key.slice(0, 16)) + '…</code></div>' +
      '</li>').join('') + '</ul>'
    : empty('No rooms yet.',
      'A room is a place with its own members, and what you write in one is read by them. Make one, ' +
      'or paste a key somebody handed you into the box above.',
      [{ post: '/rooms/create', label: 'make a room' }])
  return layout('rooms',
    '<form class="inline-form" method="post" action="/rooms/join">' +
    '<input type="text" name="key" placeholder="room key (64 hex)"><button>join</button></form>' +
    '<form method="post" action="/rooms/create"><button>make a room</button></form>' +
    items)
}

/**
 * One room.
 *
 * ⛔ Three states, and the middle one is the whole reason rooms are not feeds: you can be IN a room
 * (holding its key, replicating, reading) without being OF it (a member, who may write). Rendering
 * a composer to somebody whose posts apply would silently drop is the "it returned, so it worked"
 * failure this stack refuses everywhere else.
 */
export function room (key, r, profiles = null) {
  const role = r.role
  const title = r.config.title || 'untitled room'
  const who_ = (root) => {
    const p = profiles && Object.values(profiles).find((x) => x && x.root === root)
    return p ? esc(p.handle ? '@' + p.handle : (p.name || root.slice(0, 12))) : '<code>' + esc(root.slice(0, 12)) + '…</code>'
  }
  // ⚠ Not the timeline's two-column article. A room message has no avatar gutter to fill, and an
  // empty column indents every line for nothing.
  const msgs = r.messages.length
    ? '<div class="said">' + r.messages.map((m) =>
        '<div class="say"><div class="meta">' + who_(m.author) + '</div>' +
        render(m.body || '') + '</div>').join('') + '</div>'
    : '<p class="who">Nothing said yet.</p>'

  const compose = role && role !== 'read-only'
    ? '<form method="post" action="/r/' + esc(key) + '/post" class="composer">' +
      '<textarea name="body" rows="2" placeholder="say something…"></textarea>' +
      '<div class="row"><span class="who">posting as ' + esc(role) + '</span>' +
      '<button class="post">send</button></div></form>'
    : (role === 'read-only'
        ? '<p class="who">You are a read-only member here.</p>'
        : '<form method="post" action="/r/' + esc(key) + '/request">' +
          '<button>ask to join</button></form>' +
          '<p class="who">You hold this room and can read it. Being a MEMBER is a decision ' +
          'somebody inside makes — they have to admit you, because a new member cannot let ' +
          'themselves in.</p>')

  // Only an admin sees asks, because only an admin can answer one.
  const asks = role === 'admin' && r.requests && r.requests.length
    ? '<h2>Asking to join</h2><ul class="people">' + r.requests.map((q) =>
      '<li class="ask">' + who_(q.root) +
      '<form method="post" action="/r/' + esc(key) + '/admit">' +
      '<input type="hidden" name="writer" value="' + esc(q.writer) + '">' +
      '<input type="hidden" name="member" value="' + esc(q.root) + '">' +
      '<button>let them in</button></form></li>').join('') + '</ul>'
    : ''

  const members = '<h2>Members</h2><ul class="people">' + r.members.map((m) =>
    '<li>' + who_(m.root) + ' <span class="flag">' + esc(m.role) + '</span></li>').join('') + '</ul>'

  // ⚠ The sentence stays on screen and the key folds under it: what the key DOES is the part a
  // reader needs, and it is also the warning.
  return layout('rooms', '<h1>' + esc(title) + '</h1>' +
    '<p class="who">Anyone with this key can read the room. Hand it over to invite somebody.</p>' +
    keyline('room key', key) +
    compose + asks + msgs + members)
}

export function following (keys, missing, profiles = null) {
  const gap = new Set(missing || [])
  const items = keys.length
    ? '<ul class="people">' + keys.map((k) => {
      const p = (profiles && profiles[k]) || null
      // ⛔ A followed feed we cannot read is NAMED, not silently absent from the list — and it is
      // also why the bio may be missing here while the key is not.
      // ⚠ An INLINE marker, not the block `.warn`. Styling a two-word note as a filled alert box
      // made a normal state — a feed you follow that has not arrived — shout louder than the person
      // it was attached to.
      const warn = gap.has(k) ? ' <span class="flag">not replicated</span>' : ''
      // The same byline the timeline uses: picture, handle, self-label, key. A list of people that
      // rendered differently from every other place would teach the reader the two disagree.
      const bio = p && p.bio
        ? '<div class="who bio">' + esc(oneLine(p.bio, 140)) + '</div>'
        : ''
      return '<li>' + who(k, profiles) + warn + bio + '</li>'
    }).join('') + '</ul>'
    // ⚠ The follow form is already below; the empty state points AT it rather than repeating it.
    : empty('Following nobody.',
      'Everything you read comes from a feed you hold. Search for a person, or paste a feed key ' +
      'into the box below.',
      [{ href: '/search', label: 'search for people' }, { href: '/knocks', label: 'see who knocked' }])
  const form = '<form class="inline-form" method="post" action="/follow">' +
    '<input type="text" name="feed" placeholder="feed key (64 hex)"><button>follow</button></form>'
  return layout('following', form + items)
}

/**
 * The settings page — a display name and a picture.
 *
 * ⛔ These are properties of the ACCOUNT, not of anything published. A name must never appear in a
 * record (NAMING.md §2): names are reclaimable, so one baked into immutable bytes can later denote
 * a different person. They live on the user row, which is the thing allowed to change.
 *
 * No JavaScript. A file input in a plain form is `multipart/form-data`, which the server parses —
 * the login page is the only place a script is unavoidable, because signing needs one.
 */
/**
 * Someone's profile page.
 *
 * ⛔ Shows the FULL key, not the eight-character prefix. A prefix is fine for scanning a timeline
 * and useless for deciding whether two feeds are the same person — which is exactly what you came
 * to this page to do.
 */
export function person (author, profile, posts, files = null, holding = null) {
  const p = profile || {}
  const pic = p.avatar
    ? '<img class="av-lg" src="/avatar/' + esc(p.avatar) + '" alt="">'
    : ''
  // ⛔ The HANDLE is the heading when there is one — it is the only one of the three names that is
  // unique anywhere, and the only one anybody had to prove.
  const name = p.handle
    ? '<h1>@' + esc(p.handle) + '</h1>'
    : (p.name ? '<h1>' + esc(p.name) + '</h1>' : '<h1 class="who">no published name</h1>')
  // ⚠ Two different KINDS of name, so the page says which is which rather than stacking them.
  // A self-label is a CLAIM: nothing enforces uniqueness and two feeds may carry the same one.
  // A handle is claimed in a namespace, unique within it, and attested to this feed's owner.
  const claim = p.handle
    ? '<p class="who">' + (p.name ? 'calls itself ' + esc(p.name) + ' · ' : '') +
      'the handle is claimed in a namespace and confirmed against this feed\'s owner' + '</p>'
    : (p.name ? '<p class="who">a name this feed chose for itself — not a unique handle</p>' : '')
  /**
   * ⛔ Three states, three different offers, and conflating any two of them lies to the reader:
   *
   *   not held   we have no copy of this feed — the page is EMPTY because of that, not because
   *              they never posted. Offer to take a look.
   *   peeked     held to read, no follow record anywhere. Offer to follow.
   *   following  nothing to offer.
   *
   * ⚠ "Nothing from them yet" on a feed we do not hold would be a lie, and it is the same
   * silent-absence failure the daemon refuses at every other layer.
   */
  const h = holding || {}
  // ⚠ Still arriving. Rendering this as "not held" would offer a look that is already happening,
  // and rendering it as held would claim they posted nothing when their feed simply has not landed.
  // The page reloads itself once rather than polling — a feed either arrives quickly or the reader
  // can press again.
  const act = h.opening
    ? '<p class="who">Fetching their feed… <a href="/u/' + esc(author) + '">reload</a></p>' +
      '<meta http-equiv="refresh" content="3">'
    : !h.held
    ? '<p class="who">You do not hold this feed, so there is nothing of theirs here yet — that is ' +
      'not the same as them having posted nothing.</p>' +
      '<form method="post" action="/peek">' +
      '<input type="hidden" name="feed" value="' + esc(author) + '">' +
      '<button>take a look</button></form>' +
      '<p class="who">Copies their feed to this machine and joins their discovery topic, so they ' +
      'can see a peer arrived. It lasts until this daemon restarts.</p>'
    : (h.peeked
        ? '<p class="who">You are reading this without following. Their posts are not in your timeline.</p>' +
          '<form method="post" action="/follow">' +
          '<input type="hidden" name="feed" value="' + esc(author) + '">' +
          '<button>follow</button></form>'
        : '')

  const body = posts.length
    ? posts.map((r) => post(r, { [author]: p }, files, '/u/' + author)).join('')
    : (h.held ? '<p>Nothing from them yet.</p>' : '')
  // ⛔ RENDERED here, unlike the one-line form in a listing. It is a markdown record body like any
  // other, and the profile page is the one place with room for what the author actually wrote.
  const bio = p.bio ? '<div class="bio">' + render(p.bio, blobResolver(files)) + '</div>' : ''
  /**
   * ⛔ The block button says what a block actually DOES, because the honest answer is weaker than
   * the word suggests. A feed is public and replication is the reader's to perform, so this cannot
   * stop them reading you, holding your feed, or writing about you. It stops YOUR client rendering
   * them. A button labelled "block" with nothing said would promise enforcement we cannot deliver.
   */
  /**
   * ⛔ The way to @ somebody. A mention names an identity ROOT and a bare `@name` is never a guess
   * (markdown-strip §3.4), so without this the feature was readable and unwritable: nothing in the
   * interface showed anyone's root, and nobody is going to type one.
   *
   * Same shape as quote — it seeds the composer with ordinary text the author can edit.
   */
  const mentionable = p.root
    ? '<a class="file" href="/?mention=' + encodeURIComponent(p.root) +
      '&handle=' + encodeURIComponent(p.handle || p.name || 'them') + '">@ mention them</a> '
    : ''
  const blocked = h.blocked === true
  const blockBtn = '<form method="post" action="' + (blocked ? '/unblock' : '/block') + '">' +
    '<input type="hidden" name="feed" value="' + esc(author) + '">' +
    '<input type="hidden" name="from" value="' + esc('/u/' + author) + '">' +
    '<button>' + (blocked ? 'unblock' : 'block') + '</button></form>' +
    '<p class="who">' + (blocked
      ? 'You are not rendering their posts. Their feed is still on disk and still replicating.'
      : 'Hides their posts from your timeline, search and mentions. It does not stop them ' +
        'reading your feed or writing about you — nothing here can.') + '</p>'
  // ⚠ When the feed publishes NO name the heading already says so, and the key prefix becomes the
  // only thing identifying whose page this is — so it is shown, and the full key still collapses.
  const named = !!(p.handle || p.name)
  const prefix = named ? '' : '<p class="who"><code>' + short(author) + '…</code></p>'
  return layout(p.name || 'profile', pic + name + claim + bio + prefix +
    keyline('feed key', author) +
    '<div class="choices">' + mentionable + '</div>' + act + blockBtn + body)
}

/**
 * The handle half of settings.
 *
 * ⛔ This page is WHY the whole naming path exists, and it had no form on it — the daemon could
 * claim a name and nothing on the surface could ask it to. Since the CLI has no installed launcher
 * either (`pgh.bin` is spent on the daemon), there was in practice NO way for a person to claim a
 * name at all. A capability with no door is not shipped.
 *
 * ⚠ The namespace key is shown because a namespace is a SUBSCRIPTION, not an authority: this node
 * generated its own on first boot, so two nodes that never exchange keys resolve handles in
 * different worlds. Nobody can share what the page never displays.
 */
export function handleSettings (handle, namespace) {
  const ns = namespace && namespace.key
    ? '<p class="who">Claimed in namespace <code>' + esc(namespace.key.slice(0, 16)) + '…</code> — ' +
      esc(String(namespace.claims == null ? 0 : namespace.claims)) + ' claim(s). ' +
      'Handles resolve only for people on this same namespace.</p>'
    : ''

  if (handle) {
    return '<h2>Handle</h2><p><b>@' + esc(handle) + '</b></p>' +
      // ⚠ Releasing is stated honestly: the number retires with it. NAMING.md forbids recycling a
      // number, because a familiar handle pointing at someone new is the one failure this design
      // can actually cause.
      '<p class="who">Releasing it retires that number for good — reclaiming the same name gives ' +
      'you the next one, never this one again.</p>' +
      '<form method="post" action="/handle">' +
      '<input type="hidden" name="release" value="' + esc(handle) + '">' +
      '<button>release @' + esc(handle) + '</button></form>' + ns
  }

  return '<h2>Handle</h2>' +
    '<p class="who">A name claimed in a namespace, unique within it, and proved to belong to this ' +
    'feed. Different from the display name above, which anyone may also use.</p>' +
    '<form method="post" action="/handle">' +
    '<p><label>Claim a name<br><input type="text" name="name" placeholder="alice"></label></p>' +
    // ⛔ The rules are stated BEFORE the form is submitted, not as an error afterwards. A number is
    // always appended, so nothing can be squatted and no claim is ever refused for being late.
    '<p class="who">You get a number on the end — the first <code>alice</code> is ' +
    '<code>alice0</code>, the next is <code>alice1</code>. So a name is never taken. ' +
    'Letters, digits, dot, dash and underscore; it may not END in a digit, since that is where ' +
    'the number goes.</p>' +
    '<button>claim</button></form>' + ns
}

/**
 * The namespace half of settings — the JOIN FORM and the ceremony around it.
 *
 * ⛔ This exists because the CLI has no door on an installed node: `pgh.bin` is a single string and
 * favela-peer spends it on the daemon, so `favela namespace --join` is unreachable on a machine
 * that installed the app rather than checked it out. The browser is the only surface a person
 * actually has, and a namespace nobody can join is the same as no namespace at all.
 *
 * ⚠ `token` is rendered ONCE, straight from the POST that minted it. It is deliberately not passed
 * through a redirect: a secret in a query string lands in browser history and in any log that keeps
 * request lines.
 */
export function namespaceSettings (namespace, token = null, known = []) {
  const ns = namespace || {}
  if (!ns.key) return ''

  const minted = token
    ? '<p class="warn">Invite: <code>' + esc(token) + '</code><br>' +
      'Shown once and stored nowhere — the log holds only its hash. Give it to the other machine, ' +
      'which redeems it with “ask to carry”.</p>'
    : ''

  // ⛔ Carrying and joining are different facts. A node that only resolves handles here is not
  // broken; it simply cannot admit anyone, and nothing about a key says which you are.
  const status = ns.carrying
    ? '<p class="who">This node carries the log — it can invite and admit.</p>'
    : '<p class="who">This node reads this namespace but does not carry it. ' +
      'Ask an admin for an invite to help carry it.</p>'

  const alone = ns.founded && ns.carrying && (ns.admins || []).length === 1 && !(ns.writers || []).length
    ? '<p class="who">Nobody else has joined yet. Share the key above.</p>'
    : ''

  // ⛔ THE LIST IS THE DOOR. The join form below promises the namespace you leave stays on disk and
  // you can come back to it; without this, coming back meant having written down 64 hex characters.
  // A promise nobody can act on is indistinguishable from losing the thing.
  const others = (known || []).filter((n) => !n.current)
  const held = (known || []).length > 1
    ? '<h3>Namespaces this node holds</h3><ul class="ns-list">' +
      (known || []).map((n) => {
        const label = '<code>' + esc(String(n.key).slice(0, 16)) + '…</code>'
        if (n.current) return '<li>' + label + ' <span class="who">— you are here</span></li>'
        return '<li>' + label +
          ' <form method="post" action="/namespace/join" class="inline">' +
          '<input type="hidden" name="key" value="' + esc(n.key) + '"><button>switch</button></form>' +
          // ⚠ Says what it does and what it does NOT do. "Forget" reads like deletion and is not:
          // the log and every claim in it stay on disk.
          ' <form method="post" action="/namespace/forget" class="inline">' +
          '<input type="hidden" name="key" value="' + esc(n.key) + '">' +
          '<button title="removes it from this list; the log and its claims stay on disk">forget</button></form>' +
          '</li>'
      }).join('') + '</ul>' +
      (others.length ? '<p class="who">Forgetting one removes it from this list only — its log and ' +
        'every handle in it stay on disk, and joining by key brings it back.</p>' : '')
    : ''

  const join = held + '<form method="post" action="/namespace/join">' +
    '<p><label>Join another namespace<br>' +
    '<input type="text" name="key" placeholder="64 hex characters"></label></p>' +
    // ⛔ Said plainly, because it is the one thing a person would fear: joining is not a migration
    // and it destroys nothing.
    '<p class="who">The one you are in now stays on disk — you can come back to it, handles and ' +
    'all. Handles you claimed there will not resolve while you are away.</p>' +
    '<button>join</button></form>'

  let carrying = ''
  if (ns.carrying) {
    const reqs = (ns.requests || []).filter((r) => !(ns.writers || []).some((w) => w.writer === r.writer))
    const asks = reqs.length
      ? '<h3>Nodes asking to carry this log</h3><ul>' + reqs.map((r) =>
          '<li><code>' + esc(r.writer.slice(0, 16)) + '…</code> ' +
          (r.invited ? '<b>invited</b> ' : '<span class="who">no invite — admit by hand</span> ') +
          '<form method="post" action="/namespace/admit">' +
          '<input type="hidden" name="writer" value="' + esc(r.writer) + '">' +
          '<button>admit</button></form></li>').join('') + '</ul>'
      : '<p class="who">Nobody is asking to carry it.</p>'

    const unspent = (ns.invites || []).filter((i) => !i.used)
    const open = unspent.length
      ? '<h3>Unspent invites</h3><ul>' + unspent.map((i) =>
          '<li><code>' + esc(i.hash.slice(0, 16)) + '…</code> ' +
          '<form method="post" action="/namespace/withdraw">' +
          '<input type="hidden" name="hash" value="' + esc(i.hash) + '">' +
          '<button>withdraw</button></form></li>').join('') + '</ul>'
      : ''

    carrying = '<form method="post" action="/namespace/invite"><button>mint an invite</button></form>' +
      // ⚠ Stated, because "single use" and "expires" are the two things people assume and only one
      // is true here. Expiry would need a clock inside apply, which is replayed on every peer.
      '<p class="who">Single use. It never expires; withdraw it instead.</p>' +
      open + asks
  } else {
    carrying = '<form method="post" action="/namespace/request">' +
      '<p><label>Invite token (optional)<br>' +
      '<input type="text" name="token" placeholder="from an admin of this namespace"></label></p>' +
      '<p class="who">Without a token your request waits for an admin to answer it by hand. ' +
      'With one, their node answers it on its own.</p>' +
      '<button>ask to carry this log</button></form>'
  }

  return '<h2>Namespace</h2>' + minted +
    keyline('namespace key', ns.key) + status + alone +
    '<p class="who">' + esc(String((ns.admins || []).length)) + ' admin(s), ' +
    esc(String((ns.writers || []).length)) + ' node(s) carrying, ' +
    esc(String(ns.claims == null ? 0 : ns.claims)) + ' handle(s).</p>' +
    carrying + join
}

/**
 * ⚠ Three states, and SYSTEM is the default. Two states would force everyone who has already told
 * their OS which they want to tell us again, and to keep telling us when they change their mind.
 */
export function themeSettings (current) {
  const opt = (v, label) =>
    '<form method="post" action="/theme">' +
    '<input type="hidden" name="theme" value="' + esc(v) + '">' +
    '<button' + ((current || 'system') === v ? ' class="liked"' : '') + '>' + esc(label) + '</button>' +
    '</form>'
  // One row: three mutually exclusive choices stacked read as three separate settings.
  return '<h2>Appearance</h2><div class="choices">' +
    opt('system', 'system', '') + opt('light', 'light', '') + opt('dark', 'dark', '') +
    '</div><p class="who">System follows your device.</p>' +
    '<p class="who">Stored in a cookie on this browser, so the page arrives in the right colours ' +
    'rather than correcting itself after it loads.</p>'
}


/**
 * Where this identity publishes.
 *
 * ⛔ OFF IS THE DEFAULT AND IT SAYS SO. A person who has never touched this must be able to see, in
 * one glance, that nothing they write leaves favela. An unticked box that could mean "off" or
 * "never configured" is the ambiguity this section exists to remove.
 *
 * ⛔ AND IT NAMES THE COST. Turning bsky on publishes under a public identity on a network that does
 * not forget, and a post cannot be recalled from it. That belongs beside the switch, not in a
 * document — the moment to say it is the moment somebody reaches for it.
 *
 * ⚠ A plain form. Every other control here works without JavaScript and so does this one: tick,
 * submit, reload. A live toggle would be the first thing on this page that needed a script.
 */
export function reachSettings (reach) {
  if (!reach) return ''
  const on = new Set(Array.isArray(reach.networks) ? reach.networks : [])
  // ⛔ ONE FIELD PER NETWORK. `body()` in the router keeps the last value for a repeated name, so
  // two checkboxes sharing one name would collapse into a single answer and silently drop whichever
  // the person ticked first.
  const box = (name, label, why) =>
    '<p><label><input type="checkbox" name="reach-' + esc(name) + '" value="on"' +
    (on.has(name) ? ' checked' : '') + '> ' + esc(label) + '</label>' +
    '<br><span class="who">' + esc(why) + '</span></p>'

  // ⛔ identity.md §5.1 — with no designated credential the projection is WITHHELD rather than
  // published under a guess: "Several without a designation → refuse to project. Silently picking
  // one would publish under an identity the author did not choose." The page has to say so, or an
  // enabled network looks like it works.
  //
  // ⛔ AND IT HAS TO BE SETTABLE HERE. This said "Link one on the machine running the daemon" and
  // that mechanism did not exist: `setReach` accepts a credential, this rendered one, and NOTHING
  // anywhere passed one — no route, no CLI verb. An instruction pointing at a door nobody built is
  // worse than no instruction, because it reads like operator error. Same shape as `ota.js` being
  // copied and never imported.
  //
  // ⚠ It is the ATPROTO credential specifically (§5.1: "designated: 'atproto'"), which is why it
  // says DID rather than "identifier". The fediverse equivalent is not a credential at all — §6
  // makes a bridge a RESOURCE you hold a grant on, obtained by registering with one.
  const cred = '<p><label>Bluesky identity (DID)<br>' +
    '<input type="text" name="credential" value="' + esc(reach.credential || '') + '" ' +
    'placeholder="did:plc:…" autocomplete="off"></label></p>' +
    (reach.credential
      ? '<p class="who">Publishing to Bluesky as <code>' + esc(reach.credential) + '</code></p>'
      : '<p class="warn">No Bluesky identity is designated, so nothing is published there even ' +
        'with the box ticked. Paste a DID this node holds, or leave it empty to keep it off.</p>')

  return '<h2>Publishing</h2>' +
    (on.size === 0
      ? '<p class="who">Nothing you write leaves favela.</p>'
      : '<p class="who">Posts you write in a public room are also sent to the networks ticked below.</p>') +
    '<form method="post" action="/reach">' +
    box('bsky', 'Bluesky', 'Plain text only, up to 300 characters. Longer posts are withheld, not shortened.') +
    box('fediverse', 'Mastodon and the fediverse', 'Keeps headings, lists and links. No length limit.') +
    cred +
    '<p class="who">⛔ A post sent to either network cannot be recalled from it. Turning a network ' +
    'off stops future posts; it does not retract the ones already sent.</p>' +
    '<button>save</button></form>'
}


// ── app updates ──────────────────────────────────────────────────────────────────────────────────
//
// ⛔ RENDERED FROM A FILE THE SHELL WROTE, not from anything this surface knows. favela-surface has no
// idea whether it is inside an Android worklet or on a desktop, and must not learn: the shell writes
// `app-update.json`, the page reads it, and neither knows the other's process model.
//
// ⛔ AND THE CONTROLS FOLLOW THE SHELL'S CAPABILITY, not our own optimism. `ota` and `pair` are
// separate flags for a reason recorded in shell-handoff.js: the page arrives by OTA and the shell by
// APK, so "can service update buttons" is answered by whatever shell is actually running. A page that
// renders a control the shell beneath it cannot honour is worse than one that renders nothing.
export function updateSettings (u) {
  if (!u) return ''
  const short = (h) => (h ? esc(String(h).slice(0, 12)) + '…' : '<span class="who">unknown</span>')
  const checked = u.checkedAt ? '<p class="who">Last checked ' + esc(u.checkedAt) + '</p>' : ''
  // ⚠ The node's own last OTA line, verbatim. It says things no derived field can — "no swarm
  // fetcher", "downloading 0.0.6", "previously failed to boot" — and without it a device could only
  // report that nothing happened.
  const note = u.note ? '<p class="who">' + esc(u.note) + '</p>' : ''
  // ⛔ THE VERSION FIRST, because it is the only line here a person can compare against a release
  // page. Two tree hashes say something differs; they do not answer "is my update installed?", which
  // is the question anyone opening this section actually has.
  const built = u.builtAt ? ' <span class="who">built ' + esc(new Date(Number(u.builtAt)).toISOString()) + '</span>' : ''
  const version = u.version
    ? '<p><strong>Running ' + esc(u.version) + '</strong>' + built + '</p>'
    : '<p class="who">This build does not report its version — it predates the stamp.</p>'

  // ⛔ STAGED AND DIFFERENT is the only state that means "an update is waiting". Staged and IDENTICAL
  // is the ordinary case — the node re-verified the bundle it is already running — and calling that
  // "update available" is what makes a restart button lie.
  const waiting = updateWaiting(u)
  // ⛔ ALWAYS SHOWN, BOTH VALUES, IN EVERY STATE. This used to print `running` alone when it judged
  // itself up to date — so when that judgement was WRONG (it required `running`, which is null on a
  // fresh install) there was no way to see that a bundle had in fact been staged. A page that shows
  // its inputs only when it already agrees with you cannot be used to find out that it is wrong,
  // and several hours went into guessing at a state this line could have printed.
  const facts = '<p class="who">running ' + short(u.running) +
    ' · staged ' + short(u.staged) + '</p>'

  const body = waiting
    ? '<p class="warn">An update is downloaded and will apply on the next start.</p>' + facts +
      (u.ota ? '<p><button id="app-restart">restart now</button></p>' : '<p class="who">This app cannot restart itself; close and reopen it.</p>')
    : '<p>Up to date.</p>' + facts

  return '<h2>App updates</h2>' + version + body + checked + note +
    (u.ota ? '<p><button id="app-check">check now</button> <span id="app-msg" class="who"></span></p>' : '') +
    (u.ota ? '<script type="module">\n' + UPDATE_JS + '\n</script>' : '')
}

// ⛔ THE ONLY THING THIS POSTS IS A VERB. `parseShellMessage` on the other side is an allow-list of
// three names and nothing else — no op passed through to a handler map, which is how a WebView bridge
// turns into a remote-call surface for anything that reaches the page.
const UPDATE_JS = `
const post = (op) => {
  try { window.ReactNativeWebView.postMessage(JSON.stringify({ t: 'app-update', op })) }
  catch { const m = document.getElementById('app-msg'); if (m) m.textContent = 'this app cannot do that' }
}
const check = document.getElementById('app-check')
if (check) check.addEventListener('click', (e) => {
  e.preventDefault()
  const m = document.getElementById('app-msg')
  if (m) m.textContent = 'checking…'
  post('check')
})
const restart = document.getElementById('app-restart')
if (restart) restart.addEventListener('click', (e) => { e.preventDefault(); post('restart') })
`

export function settings (profile, message, handle = null, namespace = null, token = null, theme = null, reach = null, update = null, known = []) {
  const p = profile || {}
  const note = message ? '<p class="warn">' + esc(message) + '</p>' : ''
  const shown = p.avatar
    ? '<p><img class="av-lg" src="/avatar/' + esc(p.avatar) + '" alt="your avatar"></p>'
    : '<p class="who">No picture yet.</p>'
  // The banner is shown at its own width rather than in the avatar's circle — they are different
  // pictures with different shapes, and previewing one as the other teaches the wrong thing.
  const shownBanner = p.banner
    ? '<p><img class="banner-lg" src="/avatar/' + esc(p.banner) + '" alt="your banner"></p>'
    : ''
  return layout('settings', note + updateSettings(update) + reachSettings(reach) +
    '<h1>Settings</h1>' + shown +
    '<form method="post" action="/settings" enctype="multipart/form-data">' +
    '<p><label>Display name<br><input type="text" name="name" value="' + esc(p.name || '') +
    '" placeholder="what people see instead of 64 hex characters"></label></p>' +
    '<p><label>Picture<br><input type="file" name="avatar" accept="image/png,image/jpeg,image/gif,image/webp">' +
    '</label></p>' +
    '<p class="who">png, jpeg, gif or webp, up to 512 KiB. The type is read from the file itself, ' +
    'not its name.</p>' +
    shownBanner +
    '<p><label>Banner<br><input type="file" name="banner" accept="image/png,image/jpeg,image/gif,image/webp">' +
    '</label></p>' +
    '<p class="who">The wide header image. Same formats and cap. Leaving either file empty leaves ' +
    'that picture as it was.</p>' +
    // ⛔ The profile record has had a body slot from the start and nothing ever wrote it, so every
    // screen that wanted to say who somebody IS had a name, a picture and 64 hex characters.
    '<p><label>About you<br><textarea name="bio" rows="3" placeholder="a sentence or two — markdown">' +
    esc(p.bio || '') + '</textarea></label></p>' +
    '<p class="who">Markdown, up to 512 characters. It travels with your feed, so it is what other ' +
    'people see next to your name.</p>' +
    '<button>save</button></form>' +
    handleSettings(handle, namespace) +
    themeSettings(theme) +
    namespaceSettings(namespace, token, known))
}

export function knocks (rows) {
  const items = rows.length
    // ⛔ A stranger who knocked has NO profile — we hold nothing of theirs. The key prefix is the
    // only thing on the row that says who this is, so it stays on screen and only the rest folds.
    ? rows.map((k) => '<li><span class="av av-dot"></span><code>' + short(k.feed) + '…</code>' +
      keyline('full key', k.feed) +
      '<form method="post" action="/admit"><input type="hidden" name="feed" value="' + esc(k.feed) + '">' +
      '<button>follow them</button></form></li>').join('')
    : ''
  // Accepting a knock FOLLOWS them. It does not give them access to your feed, and the button says
  // so — a UI that said "accept" would imply the opposite of what happens.
  const none = rows.length ? '' : empty('Nobody has knocked.',
    'Ringing your doorbell is the one thing somebody can do without you holding their feed. When ' +
    'they do, they appear here and you can choose to follow them.',
    [{ href: '/search', label: 'search for people' }])
  return layout('knocks', rows.length ? '<ul>' + items + '</ul>' : none)
}

export function error (message) {
  return layout('error', '<p class="warn">' + esc(message) + '</p>')
}

// ── onboarding ───────────────────────────────────────────────────────────────────────────────────
//
// ⛔ THREE DOORS, AND THEY ARE NOT INTERCHANGEABLE. Create makes a new identity. Restore rebuilds one
// from its recovery phrase and needs nobody online. Pair copies one from a device that IS online.
// Only restore survives losing every device, and only pair avoids handling the phrase at all — so
// offering two of the three would strand somebody.
//
// ⚠ This is the screen a handset had no way to reach at all: creating an identity used to require
// `hyperident` on a machine with a shell.
export function welcome (message = null) {
  const warn = message ? '<p class="warn">' + esc(message) + '</p>' : ''
  return layout('welcome', warn +
    '<h1>Welcome to favela</h1>' +
    '<p>This node has no identity yet. Everything you post is signed by one, so pick how to get it.</p>' +

    '<h2>Create a new identity</h2>' +
    '<p class="who">You will be shown a 24-word recovery phrase, once. It is the only way back if ' +
    'you lose every device.</p>' +
    '<form method="post" action="/welcome/create">' +
    '<input type="password" name="passphrase" placeholder="passphrase (optional, encrypts the vault)" autocomplete="new-password">' +
    '<button>create</button></form>' +

    '<h2>I already have a recovery phrase</h2>' +
    '<p class="who">Rebuilds an identity you already own. Works with nothing else switched on.</p>' +
    '<form method="post" action="/welcome/restore">' +
    '<textarea name="mnemonic" rows="3" placeholder="your 24 words" autocomplete="off"></textarea>' +
    '<input type="password" name="passphrase" placeholder="passphrase for the new vault (optional)" autocomplete="new-password">' +
    '<button>restore</button></form>' +

    '<h2>Link this device to another</h2>' +
    '<p class="who">Paste an invite from a device you are already signed in on — run ' +
    '<code>favela pair</code> there. Never asks for your recovery phrase.</p>' +
    '<form method="post" action="/welcome/pair">' +
    '<textarea name="invite" rows="2" placeholder="invite" autocomplete="off"></textarea>' +
    '<button>link</button></form>')
}

// ⛔ SHOWN ONCE, AND THAT IS THE POINT. The daemon returns the phrase from the mint and never stores
// it in the clear; there is no route that can show it again. If there were, "a stolen device yields a
// locked vault" would stop being true (hyper-social #48), so the absence is the feature.
export function minted (mnemonic, restored = false) {
  const words = String(mnemonic).split(/\s+/)
  const grid = words.map((w, i) => '<li><span class="who">' + (i + 1) + '</span> ' + esc(w) + '</li>').join('')
  if (restored) {
    return layout('restored', '<h1>Identity restored</h1>' +
      '<p>This device is now signed by your identity. Your recovery phrase has not changed.</p>' +
      '<p><a href="/">continue</a></p>')
  }
  return layout('write this down', '<h1>Write these words down</h1>' +
    '<p class="warn">This is the only time they will be shown. There is no route that can show them ' +
    'again, and nobody — including this node — can recover them for you.</p>' +
    '<ol class="mnemonic">' + grid + '</ol>' +
    '<p><button id="copy">copy</button> <span id="copied" class="who"></span></p>' +
    '<p class="who">Store them somewhere a stolen phone cannot reach. If you set a passphrase, the ' +
    'vault on this device is encrypted with it — but the phrase alone is enough to be you.</p>' +
    '<form method="get" action="/"><button>I have written them down</button></form>' +
    '<script type="module">\n' +
    "const w = " + JSON.stringify(words.join(' ')) + ';\n' +
    "document.getElementById('copy').addEventListener('click', async (e) => {\n" +
    "  e.preventDefault()\n" +
    // ⚠ 127.0.0.1 is a secure context in Chromium, so clipboard works in the WebView. The fallback is
    // not decoration: a viewer that refuses it must still let someone get the words out.
    "  try { await navigator.clipboard.writeText(w); document.getElementById('copied').textContent = 'copied' }\n" +
    "  catch { document.getElementById('copied').textContent = 'copy failed — select the words above' }\n" +
    '})\n</script>')
}

// ── discovery ────────────────────────────────────────────────────────────────────────────────────
//
// ⛔ THE STEP NOBODY COULD TAKE. Joining a namespace resolves handles; it does not make you follow
// anyone, and a timeline is built ONLY from feeds you follow. So a person could join the fleet's
// namespace, search correctly, see a handle, and still have no route from "found them" to "seeing
// them" — there was no follow button anywhere near a search result. Two steps from a timeline with
// no visible path is indistinguishable from a broken network, and that is what it was reported as.
//
// ⛔ AND IT SHOWS EVERYONE BY DEFAULT. `/search` returns without asking the daemon when the box is
// empty — deliberate there, because an empty query is not a request for the whole world. A
// namespace is exactly the scope where "show me everyone" IS the question, so this asks with no
// query and gets `names.list()`.
export function discover (handles, following = [], profiles = {}, ns = null) {
  const held = new Set((following || []).map((f) => (typeof f === 'string' ? f : f && f.feed)).filter(Boolean))
  const rows = (handles || []).filter((h) => h.verdict !== 'MISMATCH')

  const key = ns && ns.key
    ? '<p class="who">Namespace <code>' + esc(String(ns.key).slice(0, 16)) + '…</code></p>'
    : ''

  if (!rows.length) {
    // ⛔ NAMES THE NAMESPACE IN THE EMPTY STATE. "Nobody is here" and "you are in the wrong
    // namespace" look identical otherwise, and the second is the likely one — an invite token and a
    // namespace key are both 64 hex characters, so joining the wrong thing succeeds silently.
    return layout('discover', '<h1>Nobody here yet</h1>' + key +
      '<p>No handles have been claimed in this namespace. If you expected people, check you are in ' +
      'the right one — a namespace key and an invite token are both 64 characters, and joining the ' +
      'wrong one looks like this.</p>' +
      '<p><a href="/settings">namespace settings</a></p>')
  }

  const row = (h) => {
    const p = (profiles || {})[h.feed] || {}
    const name = p.name ? '<strong>' + esc(p.name) + '</strong> ' : ''
    // ⚠ `unheld` is LABELLED, not hidden. It means "this claim exists and I do not hold the feed to
    // check it against" — which is the normal state for everyone you have not followed yet, so
    // hiding it would empty the very page that exists to find them.
    const flag = h.verdict === 'confirmed' ? '' : ' <span class="who">[' + esc(h.verdict) + ']</span>'
    const action = held.has(h.feed)
      ? '<span class="who">following</span>'
      : '<form method="post" action="/follow" class="inline">' +
        '<input type="hidden" name="feed" value="' + esc(h.feed) + '"><button>follow</button></form>'
    // ⛔ LINKED, like search's rows already are (`nameRows` above). Without this the page could show
    // you who exists and offer to follow them, but not let you LOOK at them first — which is the
    // one thing anyone wants before following a stranger.
    return '<li>' + name + '<a href="/u/' + esc(h.feed) + '">@' + esc(h.handle) + '</a>' + flag +
      // ⛔ The key is always shown. A handle is a claim; the key is the unforgeable thing, and this
      // page's whole job is handing someone a feed to follow.
      ' <code class="who">' + esc(String(h.feed).slice(0, 8)) + '</code> ' + action + '</li>'
  }

  return layout('discover', '<h1>People here</h1>' + key +
    '<p class="who">Everyone who has claimed a handle in this namespace. Following one is what puts ' +
    'their posts in your timeline — joining a namespace on its own shows you nothing.</p>' +
    '<ul class="ns-list">' + rows.map(row).join('') + '</ul>')
}

// ── status ───────────────────────────────────────────────────────────────────────────────────────
//
// ⛔ THE PAGE EVERY DEAD-END WARNING SHOULD POINT AT. "1 record could not be read — the log arrived,
// the bytes did not" is accurate and useless on its own: it does not say WHOSE feed, or what would
// fix it, so the only way to find out was to ask someone with a shell. The same was true of every
// OTA failure until settings started printing the daemon's own last line — and that instrument
// found a real bug on its first run, after hours of guessing at it from the outside.
//
// ⚠ It reports STATE, never a verdict. A page that prints a conclusion cannot be used to discover
// the conclusion is wrong, which is exactly how a staged update sat invisible behind "Up to date".
export function status ({ me = null, ns = null, following = [], missing = [], profiles = {}, update = null } = {}) {
  const short = (k) => esc(String(k || '').slice(0, 12))
  const row = (k, v, why) => '<tr><td class="who">' + esc(k) + '</td><td>' + v +
    (why ? '<br><span class="who">' + esc(why) + '</span>' : '') + '</td></tr>'

  const held = (following || []).map((f) => (typeof f === 'string' ? f : f && f.feed)).filter(Boolean)
  const gone = new Set((missing || []).map((f) => (typeof f === 'string' ? f : f && f.feed)).filter(Boolean))

  const feedRows = held.length
    ? held.map((f) => {
      const p = (profiles || {})[f] || {}
      const name = p.name ? esc(p.name) : '<span class="who">no profile yet</span>'
      // ⛔ THE TWO STATES A PERSON ACTUALLY HITS, named in the terms the warnings use.
      const state = gone.has(f)
        ? '<span class="warn-inline">followed, not held</span>'
        : (p.name ? 'held' : '<span class="warn-inline">held, profile not read</span>')
      return '<tr><td><a href="/u/' + esc(f) + '"><code>' + short(f) + '</code></a></td>' +
        '<td>' + name + '</td><td>' + state + '</td></tr>'
    }).join('')
    : '<tr><td colspan="3" class="who">You follow nobody yet — try <a href="/discover">discover</a>.</td></tr>'

  return layout('status',
    '<h1>Status</h1>' +
    '<p class="who">What this node holds, and what it is still waiting for. Every warning elsewhere ' +
    'in favela means one of the states below.</p>' +

    '<h2>This node</h2><table>' +
    row('feed', me && me.feed ? '<code>' + short(me.feed) + '</code>' : '<span class="who">unknown</span>') +
    row('identity', me && me.linked ? 'linked' : '<span class="warn-inline">not linked</span>',
      me && me.linked ? '' : 'This node cannot claim a name or sign until an identity is minted.') +
    '</table>' +

    '<h2>Namespace</h2><table>' +
    row('key', ns && ns.key ? '<code>' + short(ns.key) + '</code>' : '<span class="who">none</span>',
      'Handles resolve only within the namespace you are in.') +
    row('claims', ns ? String(ns.claims == null ? '?' : ns.claims) : '?',
      ns && ns.claims === 0 ? 'Nobody has claimed a handle here — you may be in the wrong namespace.' : '') +
    row('carrying', ns && ns.carrying ? 'yes' : 'no',
      ns && ns.carrying ? '' : 'This node reads the namespace but does not carry its log.') +
    '</table>' +

    '<h2>Feeds</h2>' +
    // ⛔ THE ANSWER TO "the log arrived, the bytes did not". A feed can be followed without being
    // held, and held without its records being readable — three states that look identical from a
    // timeline, and are fixed by completely different things.
    '<p class="who"><b>followed, not held</b> — the feed is in your follow list but has not ' +
    'replicated. It arrives when a peer holding it is reachable.<br>' +
    '<b>held, profile not read</b> — the log arrived and some bytes have not. That is what an ' +
    'unreadable record means; it resolves as the content replicates.</p>' +
    '<table><tr><th>feed</th><th>who</th><th>state</th></tr>' + feedRows + '</table>' +

    (update
      ? '<h2>Updates</h2><table>' +
        row('version', esc(update.version || 'unstamped')) +
        row('running', short(update.running) || '<span class="who">the app\'s own bundle</span>') +
        row('staged', short(update.staged) || '<span class="who">nothing staged</span>') +
        row('last checked', esc(update.checkedAt || 'never')) +
        // ⛔ The daemon's own words, verbatim. It is the only line that distinguishes "cannot reach
        // the swarm" from "nothing published" from "downloading" — three different problems.
        row('last message', update.note ? '<code>' + esc(update.note) + '</code>' : '<span class="who">none</span>') +
        '</table>'
      : ''))
}
// The router — every route this surface serves, and nothing about how it is booted.
//
// ⛔ Split out of `index.js` so a caller can have the ROUTES without starting a SERVER. Importing
// the entrypoint to reach `route` also bound its port, which is wrong for anyone embedding this:
// a bare-kit worklet wants the routes and supplies its own listener, and on a machine already
// running favela-surface the import simply failed with EADDRINUSE. An entrypoint boots; a library
// exports.
//
// ⛔ `route(req, res, ctx)` has NO default context. This file has no opinion about where the daemon
// is — `ctx.client.call(cmd, args) -> Promise` is the whole contract, satisfied by a socket client
// on a desktop and by a direct `dispatch` binding in a worklet, and nothing here can tell.

import os from '#node:os'
import { join } from '#node:path'
import fs from '#node:fs'
import { sessionIdFrom, setCookie, clearCookie } from './session.js'
import * as pages from './pages.js'
import { embeds } from 'favela-markdown/lib/render.js'
import { recordUri } from 'favela-markdown/lib/uri.js'
import { parse as parseMultipart } from './multipart.js'
import b4a from 'b4a'

// ⚠ Both spellings: bare-tcp reports IPv4 loopback as `::ffff:127.0.0.1` when the listener is dual
// stack, and a check that knew only one form would refuse the very machine it is running on.
const LOOPBACK = new Set(['127.0.0.1', '::1', '::ffff:127.0.0.1'])

// ⛔ VENDORED in static/, not taken as a dependency. Depending on hyper-identity to serve four
// browser files pulled a whole multi-writer log engine into this renderer's published
// closure — in a process whose entire boundary is "no storage, no keys, no swarm". See
// static/README.md.
const STATIC_DIR = new URL('../static/', import.meta.url).pathname
const STATIC = new Map()
// ⚠ Read the DIRECTORY. The list was hand-maintained and a file added to static/ without being
// named here is served as a 404 — which for a module script is a page that silently does nothing.
for (const f of (() => { try { return fs.readdirSync(STATIC_DIR).filter((x) => x.endsWith('.js')) } catch { return [] } })()) {
  try { STATIC.set('/static/' + f, fs.readFileSync(STATIC_DIR + f, 'utf8')) } catch {}
}

/**
 * ⛔ Per-RESPONSE, keyed by the response object itself. A module-level "current theme" is request
 * state in a shared place, and two interleaved requests would hand one reader the other's setting.
 * A WeakMap keyed by `res` is scoped to exactly one exchange and needs no cleanup.
 */
const THEME = new WeakMap()
// ⛔ The same per-response shape as THEME, for the same reason: applied once at send time so no page
// has to know the banner exists and none can forget it.
const UPDATE = new WeakMap()

const send = (res, html, status = 200) => {
  res.writeHead(status, { 'content-type': 'text/html; charset=utf-8' })
  res.end(pages.withUpdateBanner(pages.withTheme(html, THEME.get(res) || null), UPDATE.get(res) || null))
}

// The reader's theme, or null for system. A plain cookie — no JS, and the server needs to know it
// before it writes the page or the first paint is the wrong colour.
export function themeFrom (req) {
  const raw = String((req.headers && req.headers.cookie) || '')
  const m = /(?:^|;\s*)favela_theme=(light|dark)(?:;|$)/.exec(raw)
  return m ? m[1] : null
}
const seeOther = (res, to) => { res.writeHead(303, { location: to }); res.end() }

// application/x-www-form-urlencoded, which is what a no-JS form sends.
const body = (req) => new Promise((resolve) => {
  let buf = ''
  req.on('data', (c) => { buf += c.toString() })
  req.on('end', () => {
    const out = {}
    for (const pair of buf.split('&')) {
      if (!pair) continue
      const i = pair.indexOf('=')
      const k = decodeURIComponent((i === -1 ? pair : pair.slice(0, i)).replace(/\+/g, ' '))
      const v = i === -1 ? '' : decodeURIComponent(pair.slice(i + 1).replace(/\+/g, ' '))
      out[k] = v
    }
    resolve(out)
  })
})

/**
 * The settings page, built once and reachable from every route that has to re-render it.
 *
 * ⛔ `whoami` gives this node's own feed key, and the CONFIRMED handle for it comes back through
 * `profiles` like every other byline — the same path, so settings can never show a handle the
 * timeline would not, or the other way round.
 */
async function settingsPage (client, message, token = null, theme = null, appUpdate = null) {
  const me = await client.call('whoami')
  const mine = (await client.call('profiles'))[me.feed] || {}
  const ns = await client.call('namespace')
  // ⚠ Tolerated missing, like `reach`: an older daemon has no `namespaces` command and settings must
  // still render. An empty list makes the section absent rather than claiming this node holds none.
  const known = (await client.call('namespaces').catch(() => ({ namespaces: [] }))).namespaces || []
  // Invites are a separate call: only an admin has any, and the namespace summary is fetched on
  // every settings load by anyone.
  if (ns && ns.carrying) {
    ns.invites = (await client.call('namespace-invites').catch(() => ({ invites: [] }))).invites || []
  }
  // ⚠ Tolerated missing: an older daemon has no `reach` command, and settings must still render.
  // Passing null makes the section absent rather than showing every network as off, which would be
  // a claim this surface cannot support.
  const reach = await client.call('reach').catch(() => null)
  // ⛔ SUPPLIED BY THE EMBEDDER, never read from here. `test/router-import.test.js` asserts this file
  // touches no environment — "an embedder has no env to read" — and it is right: a shell writes
  // `app-update.json`, its own entrypoint knows where, and this file must not learn what a worklet is.
  // Absent on every desktop, where there is no shell and no APK to update, so the section does not
  // render at all rather than being fabricated from what this surface can see.
  let update = null
  try { update = appUpdate ? await appUpdate() : null } catch { /* stays absent rather than guessing */ }
  return pages.settings(await client.call('profile'), message, mine.handle || null, ns, token, theme, reach, update, known)
}

/**
 * A same-site path, or `/`.
 *
 * ⛔ `//evil.com` is a protocol-relative URL and a browser follows it off-site, so "starts with a
 * slash" is not enough on its own. Anything else — an absolute URL, a scheme, a backslash a browser
 * may normalise — falls back to the timeline.
 */
function local (to) {
  const s = String(to == null ? '' : to)
  if (!s.startsWith('/') || s.startsWith('//') || s.startsWith('/\\')) return '/'
  // ⚠ The FRAGMENT survives. It is what returns the reader to the post they acted on instead of the
  // top of the page, and stripping it here would quietly undo that.
  return s
}

// "<feed>/<collection>/<name>", the form the post page emits.
function parseTarget (s) {
  const parts = String(s || '').split('/')
  if (parts.length !== 3) return null
  const [feed, collection, name] = parts.map(decodeURIComponent)
  return feed && collection && name ? { feed, collection, name } : null
}

const json = (res, obj, status = 200) => {
  res.writeHead(status, { 'content-type': 'application/json' })
  res.end(JSON.stringify(obj))
}

// ⛔ Bytes. An image posted through a form is binary, and decoding it to UTF-8 anywhere on this
// path corrupts it silently — the symptom is a broken picture, not an error.
const readBytes = (req, limit) => new Promise((resolve, reject) => {
  const chunks = []
  let n = 0
  req.on('data', (c) => {
    n += c.length
    if (n > limit) return reject(new Error('upload too large'))
    chunks.push(c)
  })
  req.on('end', () => resolve(b4a.concat(chunks)))
})

const readJson = (req) => new Promise((resolve) => {
  let buf = ''
  req.on('data', (c) => { buf += c.toString() })
  req.on('end', () => { try { resolve(JSON.parse(buf)) } catch { resolve({}) } })
})

/**
 * Types for every file the given rows reference: oid -> mimeType.
 *
 * ⛔ Resolved BEFORE rendering, because `render` is synchronous and only the daemon knows what a
 * file is. One call per distinct oid, deduped across the whole page.
 *
 * ⚠ Which is one round trip per file on a timeline. Fine at these sizes and not measured; if a page
 * ever holds hundreds of attachments this is the thing to batch.
 */
// ⚠ A REAL limit, and it belongs to this process rather than to the stack: `parseMultipart` needs
// the whole body in memory to find its boundaries, so an upload is buffered here before anything
// else sees it. The content store behind the daemon chunks and has no ceiling of its own, and the
// daemon takes a path — so removing this means streaming the multipart parse to a temp file rather
// than changing any protocol.
const MAX_UPLOAD = 64 * 1024 * 1024

async function fileTypes (rows, client) {
  const oids = new Set()
  for (const r of rows || []) for (const e of embeds(r.body || '')) oids.add(e.oid)
  const out = {}
  for (const oid of oids) {
    const r = await client.call('file-get', { oid }).catch(() => null)
    // ⛔ An unresolved oid is simply absent, and renders as nothing. A file that has not replicated
    // yet is a missing picture, not a broken page.
    if (r && r.mimeType) out[oid] = r.mimeType
  }
  return out
}

export async function route (req, res, ctx) {
  // Read once, before anything renders — the server has to know the theme before it writes the
  // page, or the first paint is the wrong colour and then corrects itself.
  THEME.set(res, themeFrom(req))
  // ⚠ Per request, because the state changes underneath a long-lived page: a node that stages an
  // update while someone is reading should tell them on their next navigation, not on their next
  // restart. It is a small local file, and absent everywhere there is no shell.
  try { if (ctx.appUpdate) UPDATE.set(res, await ctx.appUpdate()) } catch { /* no shell */ }
  const { client, sessions } = ctx
  const url = new URL(req.url, 'http://localhost')
  const path = url.pathname

  // ── the login surface: the only routes reachable without a session ──────────────────────────
  //
  // ⛔ Vendored crypto is served rather than fetched from a CDN. A page that loaded its signer from
  // someone else's server would let that server mint signatures with your key.
  if (req.method === 'GET' && STATIC.has(path)) {
    res.writeHead(200, { 'content-type': 'text/javascript; charset=utf-8' })
    return res.end(STATIC.get(path))
  }
  if (req.method === 'GET' && path === '/login') {
    // Asked of the daemon, not remembered here: the surface holds no state, and a stale "unclaimed"
    // would offer a claim that then fails.
    const { unclaimed } = await client.call('browser-unclaimed').catch(() => ({ unclaimed: false }))
    return send(res, pages.login(url.searchParams.get('m'), unclaimed))
  }
  if (req.method === 'POST' && path === '/auth/challenge') return json(res, await client.call('browser-challenge'))
  // ⛔ AUTO-LOGIN. A browser that already holds a registered key must be able to get a session back
  // without an invite, or a surface restart locks out its own owner: sessions are in memory, claim
  // is refused once the instance is claimed, and only the daemon's operator can mint an invite.
  //
  // This existed in the daemon (`browser-auth`) from the start and simply had no route. That gap
  // did exactly what it sounds like.
  if (req.method === 'POST' && path === '/auth/login') {
    const body = await readJson(req)
    try {
      await client.call('browser-auth', body)
      res.writeHead(200, { 'content-type': 'application/json', 'set-cookie': setCookie(sessions.create(body.publicKey)) })
      return res.end(JSON.stringify({ ok: true }))
    } catch (err) {
      return json(res, { error: err.message }, 401)
    }
  }

  // ⛔ THE EMBEDDED-WEBVIEW HANDOFF. The session cookie is HttpOnly, so it cannot be handed to a
  // WebView from outside the browser — `document.cookie` cannot write one. It has to arrive on an
  // HTTP response, which is why this route exists at all and why a worklet minting a session is not
  // by itself enough. favela-web reached the same conclusion for the same reason.
  //
  // ⛔ LOOPBACK ONLY. A handoff turns a token in a URL into a signed-in session, and that is only
  // defensible from the machine the surface is running on.
  //
  // ⛔ The token is ONE-TIME (`redeemHandoff` burns it). favela-web's equivalent accepts a token
  // repeatedly and relies on loopback alone — a real gate on a desktop, and not one on Android,
  // where every app on the device shares loopback. A reusable token in a query string is a
  // replayable credential; a burned one is a race someone has to win once.
  if (req.method === 'GET' && path === '/auth/handoff') {
    const remote = req.socket && req.socket.remoteAddress
    if (!LOOPBACK.has(String(remote || ''))) return send(res, pages.error('Not found'), 404)
    const id = sessions.redeemHandoff(url.searchParams.get('token'))
    if (!id) return seeOther(res, '/login?m=' + encodeURIComponent('That sign-in link was already used.'))
    // ⚠ `next` is sanitised rather than trusted: it must be a path on this surface, so `//evil.com`
    // — which a browser reads as a protocol-relative URL — is refused along with anything absolute.
    const raw = url.searchParams.get('next')
    const next = (typeof raw === 'string' && raw.startsWith('/') && !raw.startsWith('//')) ? raw : '/'
    res.writeHead(303, { location: next, 'set-cookie': setCookie(id) })
    return res.end()
  }

  if (req.method === 'POST' && path === '/auth/claim') {
    const body = await readJson(req)
    try {
      await client.call('browser-claim', body)
      res.writeHead(200, { 'content-type': 'application/json', 'set-cookie': setCookie(sessions.create(body.publicKey)) })
      return res.end(JSON.stringify({ ok: true }))
    } catch (err) {
      return json(res, { error: err.message }, 400)
    }
  }
  if (req.method === 'POST' && path === '/auth/redeem') {
    const body = await readJson(req)
    try {
      await client.call('browser-redeem', body)
      res.writeHead(200, { 'content-type': 'application/json', 'set-cookie': setCookie(sessions.create(body.publicKey)) })
      return res.end(JSON.stringify({ ok: true }))
    } catch (err) {
      return json(res, { error: err.message }, 400)
    }
  }
  if (path === '/logout') {
    const id = sessionIdFrom(req.headers.cookie)
    sessions.destroy(id)
    res.writeHead(303, { location: '/login', 'set-cookie': clearCookie() })
    return res.end()
  }

  // ── the gate ────────────────────────────────────────────────────────────────────────────────
  //
  // ⛔ A session is not a credential. It names the key that proved itself at login, and the daemon
  // is asked on EVERY request whether that key is still allowed — so a revoke lands on the next
  // page load rather than when something expires. That is why there is no expiry to configure.
  const publicKey = sessions.keyFor(sessionIdFrom(req.headers.cookie))
  if (!publicKey) return seeOther(res, '/login')

  const standing = await client.call('browser-status', { publicKey })
  if (!standing.known || standing.revoked) {
    sessions.destroyKey(publicKey) // and no other tab keeps working either
    res.writeHead(303, { location: '/login?m=' + encodeURIComponent('This browser was revoked.'), 'set-cookie': clearCookie() })
    return res.end()
  }

  // ── the identity gate ───────────────────────────────────────────────────────────────────────
  //
  // ⛔ A SIGNED-IN BROWSER IS NOT AN IDENTITY. The gate above proves who is at the keyboard; this
  // one asks whether the NODE is anybody yet. They are different questions with different answers,
  // and conflating them is why an unlinked node used to render a whole timeline and only reveal the
  // problem when a name claim failed with the daemon's error passed through.
  //
  // ⚠ Asked every request, like `browser-status` above and for the same reason: linking happens on
  // another route (or another device, via pairing) and a cached "not linked" would strand someone on
  // this page after they had already succeeded.
  // ⚠ `=== false`, NOT falsy. A daemon that does not report `linked` at all must not be treated as
  // unlinked and lock its owner out of their own node — the same tolerance settings applies to a
  // daemon with no `reach` command. The live daemon always returns a boolean; absence means old, and
  // old is not the same as unlinked.
  const identity = await client.call('whoami')
  if (identity.linked === false) {
    if (req.method === 'GET' && path === '/welcome') {
      return send(res, pages.welcome(url.searchParams.get('m')))
    }
    if (req.method === 'POST' && (path === '/welcome/create' || path === '/welcome/restore')) {
      const form = await body(req)
      const restoring = path === '/welcome/restore'
      if (restoring && !(form.mnemonic || '').trim()) {
        return seeOther(res, '/welcome?m=' + encodeURIComponent('paste your recovery phrase'))
      }
      try {
        // ⛔ THE ONLY TIME THE PHRASE EXISTS OUTSIDE THE VAULT. It is rendered straight into the
        // response and never written, cached or logged here — there is deliberately no route that
        // can show it a second time.
        const r = await client.call('mint-identity', {
          passphrase: (form.passphrase || '').trim() || null,
          mnemonic: restoring ? form.mnemonic.trim() : null
        })
        return send(res, pages.minted(r.mnemonic, r.restored))
      } catch (err) {
        // The daemon's message verbatim — it is the one that knows whether the phrase was invalid,
        // the vault already existed, or the passphrase was wrong.
        return seeOther(res, '/welcome?m=' + encodeURIComponent(err.message))
      }
    }
    if (req.method === 'POST' && path === '/welcome/pair') {
      const form = await body(req)
      if (!(form.invite || '').trim()) {
        return seeOther(res, '/welcome?m=' + encodeURIComponent('paste an invite'))
      }
      try {
        await client.call('pair-accept', { invite: form.invite.trim() })
        return seeOther(res, '/')
      } catch (err) {
        return seeOther(res, '/welcome?m=' + encodeURIComponent(err.message))
      }
    }
    // Everything else is unreachable until this node is somebody.
    return seeOther(res, '/welcome')
  }

  if (req.method === 'GET' && path === '/') {
    const rows = await client.call('timeline', { limit: 50 })
    const un = await client.call('unresolved')
    // ⛔ EVERY readable feed's profile, in one call — not just this node's. Asking only for our own
    // is what left other authors rendering as bare hex.
    const profiles = await client.call('profiles')
    // ⛔ `?quote=` is a COMPOSER SEED, not a mode. It becomes editable text in the box and nothing
    // downstream knows a quote happened — which is what "quotes are just links" means in practice.
    const q = url.searchParams.get('quote')
    // ⛔ THE AUTHORITY IS THE IDENTITY ROOT, not the feed key (hyper-social #61 §12 #1). This built
    // `'favela://' + feed + '/' + collection + '/' + name` — a FEED key in the position where a
    // mention carries a ROOT. Both are 64 hex, so nothing downstream could tell which it had, and
    // `isMention` (bare `^favela://`) then classified the whole thing as a mention of a user.
    //
    // A feed key is a LOCATOR; the root is the identity, and it survives device changes and feed
    // migration. `profiles` is already fetched above and carries feed → root for every readable
    // feed, so the resolution costs nothing here.
    //
    // ⚠ If the root is unknown we emit NOTHING rather than falling back to the feed key. A URI goes
    // into a record's bytes and SOCIAL-MODEL §8 forbids rewriting a record you did not author — a
    // wrong address written once is permanent, and an empty composer is recoverable.
    const qt = q ? parseTarget(q) : null
    const qroot = qt && profiles && profiles[qt.feed] ? profiles[qt.feed].root : null
    const quote = qt && qroot ? recordUri({ root: qroot, collection: qt.collection, name: qt.name }) : null
    // ⛔ `[@name](favela://<root>)` — the canonical form. Built here rather than left to the author,
    // because nobody is going to type a 64-character identity root.
    const mr = url.searchParams.get('mention')
    const mh = (url.searchParams.get('handle') || 'them').replace(/[^\w.@-]/g, '')
    const mention = mr && /^[0-9a-f]{64}$/i.test(mr) ? '[@' + mh + '](favela://' + mr + ')' : null
    /**
     * ⛔ An empty timeline has TWO causes and the page cannot tell them apart on its own.
     *
     * Following nobody and following people whose feeds have not arrived are different problems
     * with different fixes, and one sentence for both is the UI version of drawing a converged edge
     * and a resolved one the same way. MEASURED 2026-09-07: stortron follows nobody, its timeline
     * has always been blank, and nothing on the page has ever said why.
     *
     * ⚠ Asked for ONLY when the timeline is empty. It is a scan of our own follow set, and the
     * common path — a timeline with posts in it — must not pay for a message it will not render.
     */
    const emptyState = rows.length ? null : {
      follows: await client.call('following')
        .then((f) => (Array.isArray(f) ? f.length : ((f && f.following) || []).length))
        .catch(() => 0)
    }
    return send(res, pages.timeline(rows, {
      quote, mention, unreadable: un.length, profiles, files: await fileTypes(rows, client),
      message: url.searchParams.get('m'), empty: emptyState
    }))
  }

  if (req.method === 'GET' && path === '/search') {
    const q = url.searchParams.get('q') || ''
    if (!q) return send(res, pages.search('', [], 0, []))
    const r = await client.call('search', { query: q, limit: 25 })
    const profiles = await client.call('profiles')
    // ⛔ The namespace too. `search` only sees records and profiles of feeds this node HOLDS, so a
    // peer you have never followed is invisible to it — which made a shared handle registry
    // unreachable from the one box a person would type a name into.
    const named = await client.call('search-names', { query: q }).catch(() => ({ handles: [] }))
    return send(res, pages.search(q, r.results, r.unreadable, r.people || [], profiles,
      await fileTypes(r.results, client), named.handles || []))
  }

  // One conversation. ⛔ The daemon has had `reply` and `replies` from the beginning and nothing
  // could reach either — a conversation you can only have from a CLI is not shipped.
  if (req.method === 'GET' && path.startsWith('/t/')) {
    const raw = decodeURIComponent(path.slice('/t/'.length))
    const target = parseTarget(raw)
    if (!target) return send(res, pages.error('Not a record'), 404)
    const replies = await client.call('replies', { target })
    // ⚠ The subject may be a post we do NOT hold — we can hold a reply to it and not it. `posts-by`
    // returns what we have of that author, which is empty in that case, and the page says so.
    const subject = (await client.call('posts-by', { feed: target.feed }))
      .find((r) => r.collection === target.collection && r.name === target.name) || null
    const profiles = await client.call('profiles')
    const rows = subject ? [subject, ...replies] : replies
    return send(res, pages.thread(subject, replies, profiles, await fileTypes(rows, client), raw))
  }

  if (req.method === 'GET' && path === '/notifications') {
    const { notifications } = await client.call('notifications')
    return send(res, pages.notifications(notifications, await client.call('profiles')))
  }

  /**
   * Handle suggestions for the composer. JSON, because the only caller is a script.
   *
   * ⛔ CONFIRMED only, and it returns the NAME rather than a root. The picker inserts `@alice` —
   * ordinary text a person could have typed — and the daemon resolves it on the write path exactly
   * as it does for anything typed by hand. So the script is an accelerant and never an authority:
   * with it off, typing the name still works, and with it on it cannot produce a mention the
   * server would not have produced anyway.
   */
  if (req.method === 'GET' && path === '/api/mentions') {
    const q = (url.searchParams.get('q') || '').trim()
    if (!q) return json(res, { people: [] })
    const { handles } = await client.call('search-names', { query: q }).catch(() => ({ handles: [] }))
    const profiles = await client.call('profiles').catch(() => ({}))
    const people = (handles || [])
      .filter((h) => h.verdict === 'confirmed')
      .slice(0, 6)
      .map((h) => ({ handle: h.handle, name: h.name, label: (profiles[h.feed] || {}).name || null }))
    return json(res, { people })
  }

  // JSON for the explorer. ⛔ Same projection as the debug view: two renderings of ONE substrate,
  // which is the claim SOCIAL-MODEL makes about every view. They may disagree about layout, never
  // about facts.
  if (req.method === 'GET' && path === '/api/graph') {
    const g = await client.call('graph')
    /**
     * ⛔ LABELS ARE ATTACHED HERE, NOT IN THE PROJECTION.
     *
     * `graphOf` renders the substrate, and the debug view draws truncated hex on purpose so a
     * diagnosis reads identically on any machine. A name is a display concern of THIS surface, so
     * it travels beside the projection rather than inside it — the two views still cannot disagree
     * about a fact, because the facts are the part that passed through untouched.
     *
     * ⛔ A handle becomes a label only when CONFIRMED. Drawing an unverified one would let anyone
     * claim to be anyone in the single view whose whole job is showing you who is who. A profile
     * name is different and is always shown: it is that feed saying what it calls itself, which is
     * a claim the reader can see is a claim.
     */
    const [profiles, handles] = await Promise.all([
      client.call('profiles').catch(() => ({})),
      client.call('handles').then((r) => (r && r.handles) || []).catch(() => [])
    ])
    const labels = {}
    for (const feed of Object.keys(profiles || {})) {
      const p = profiles[feed] || {}
      // ⚠ The avatar OID, not the bytes. /avatar/<oid> already streams it from the daemon's disk,
      // content-addressed and immutable, so the picture can load it like any other image.
      if (p.name || p.avatar) labels[feed] = { name: p.name || null, avatar: p.avatar || null }
    }
    for (const h of handles) {
      if (!h || h.verdict !== 'confirmed' || !h.feed) continue
      labels[h.feed] = Object.assign({}, labels[h.feed], { handle: h.handle })
    }
    return json(res, Object.assign({}, g, { labels }))
  }

  /**
   * What ONE point is. The answer to "i have no idea what the points are", asked per click rather
   * than shipped for every node — a graph payload carrying every post body would be the timeline
   * again, and most of it is never looked at.
   *
   * ⛔ It reports UNREADABILITY as a fact. A pointer we hold whose bytes never arrived is not a
   * missing post (EDGE-GRAPH §4 ③), and rendering it as an empty one destroys the one distinction
   * this whole view exists to make. `readable:false` with `body:null` — never invented text.
   */
  /**
   * One feed's records, excerpted. The door the explorer's proximity reveal needs.
   *
   * ⛔ ONE SCAN PER FEED, not one per point. Revealing a post as the camera drifts near it would
   * otherwise be a request and a `posts-by` scan for every point that came into range, repeatedly,
   * for a picture that is mostly the same twenty records.
   *
   * ⛔ EXCERPTED. A reveal is a few lines on a small plane; shipping whole bodies to render two
   * hundred characters of them turns this back into the timeline, which is what the graph payload
   * was deliberately kept from being. The full text is one click away through /api/node.
   */
  if (req.method === 'GET' && path === '/api/records') {
    const feed = String(url.searchParams.get('feed') || '')
    if (!/^[0-9a-f]{64}$/i.test(feed)) return json(res, { error: 'not a feed key' }, 404)
    const rows = await client.call('posts-by', { feed }).catch(() => [])
    const records = {}
    for (const r of rows || []) {
      const text = String(r.body || '')
      records[r.collection + '/' + r.name] = {
        excerpt: text.length > 260 ? text.slice(0, 260) + '…' : text,
        createdAt: r.createdAt || null
      }
    }
    return json(res, { feed, records })
  }

  if (req.method === 'GET' && path === '/api/node') {
    const id = String(url.searchParams.get('id') || '')
    if (/^[0-9a-f]{64}$/i.test(id)) {
      const [profiles, handles, holding] = await Promise.all([
        client.call('profiles').catch(() => ({})),
        client.call('handles').then((r) => (r && r.handles) || []).catch(() => []),
        client.call('holding', { feed: id }).catch(() => ({}))
      ])
      const p = (profiles || {})[id] || {}
      const h = handles.find((x) => x && x.feed === id && x.verdict === 'confirmed')
      return json(res, {
        type: 'actor',
        id,
        handle: h ? h.handle : null,
        name: p.name || null,
        bio: p.bio || null,
        avatar: p.avatar || null,
        // ⚠ Why a point can sit there with no edges: we know OF them without holding them.
        holding: holding || {},
        href: '/u/' + id
      })
    }
    const t = parseTarget(id)
    if (!t) return json(res, { error: 'not a node' }, 404)
    /**
     * ⛔ WHO INTERACTED, not just that something did. A line between two points says an interaction
     * happened and cannot say what it was or who made it — so the card is where an interaction
     * becomes readable, and the counts and the people have to reach it.
     *
     * ⚠ Three extra scans, paid ONCE PER CLICK on a record. They are not paid for an actor, which
     * has no target to scan for, and never for the graph payload — putting them there would be a
     * scan per edge on every load of the page.
     */
    const [rows, labels, reactions, reposts, replies] = await Promise.all([
      client.call('posts-by', { feed: t.feed }).catch(() => []),
      client.call('profiles').catch(() => ({})),
      client.call('reactions', { target: t }).then((r) => (r && r.reactions) || []).catch(() => []),
      client.call('reposts', { target: t }).then((r) => (r && r.reposts) || []).catch(() => []),
      client.call('replies', { target: t }).catch(() => [])
    ])
    const row = (rows || []).find((r) => r.collection === t.collection && r.name === t.name) || null
    // ⚠ Named where we can name them. A key is an answer, just a poor one — and inventing a name
    // for a feed with no profile would be worse than showing the key.
    const named = (a) => ({ author: a.author, name: ((labels || {})[a.author] || {}).name || null })
    return json(res, {
      interactions: {
        reactions: reactions.map((r) => Object.assign(named(r), { value: r.value || 'like' })),
        reposts: reposts.map(named),
        replies: Array.isArray(replies) ? replies.length : 0
      },
      type: 'record',
      id,
      author: t.feed,
      authorName: ((labels || {})[t.feed] || {}).name || null,
      collection: t.collection,
      readable: !!row,
      body: row ? (row.body || '') : null,
      createdAt: row ? row.createdAt || null : null,
      href: '/t/' + encodeURIComponent(id)
    })
  }

  if (req.method === 'GET' && path === '/explore') {
    return send(res, pages.explore())
  }

  if (req.method === 'GET' && path === '/graph') {
    return send(res, pages.graph(await client.call('graph')))
  }

  if (req.method === 'GET' && path === '/rooms') {
    const { rooms } = await client.call('rooms')
    return send(res, pages.rooms(rooms))
  }

  if (req.method === 'GET' && path.startsWith('/r/')) {
    const key = path.slice('/r/'.length)
    if (!/^[0-9a-f]{64}$/i.test(key)) return send(res, pages.error('Not a room key'), 404)
    let r
    try { r = await client.call('room', { key }) } catch (err) { return send(res, pages.error(err.message), 404) }
    return send(res, pages.room(key, r, await client.call('profiles')))
  }

  if (req.method === 'GET' && path === '/mentions') {
    const rows = await client.call('mentions')
    const profiles = await client.call('profiles')
    return send(res, pages.mentions(rows, profiles, await fileTypes(rows, client)))
  }

  // ⛔ NO QUERY, DELIBERATELY. `/search` returns without asking the daemon when the box is empty,
  // which is right there — an empty query is not a request for the whole world. A namespace is the
  // one scope where "show me everyone" IS the question, so this asks with none and gets the full
  // claim list. Without it, joining a namespace and then finding nobody was the reported experience.
  // ⛔ WHERE EVERY DEAD-END WARNING SHOULD LEAD. "the log arrived, the bytes did not" says nothing
  // about whose feed or what fixes it, and until now the only way to find out was a shell.
  if (req.method === 'GET' && path === '/status') {
    const [me, ns, following, missing, profiles] = await Promise.all([
      client.call('whoami').catch(() => null),
      client.call('namespace').catch(() => null),
      client.call('following').catch(() => []),
      client.call('missing').catch(() => []),
      client.call('profiles').catch(() => ({}))
    ])
    let update = null
    try { update = ctx.appUpdate ? await ctx.appUpdate() : null } catch { /* no shell */ }
    return send(res, pages.status({ me, ns, following, missing, profiles, update }))
  }

  if (req.method === 'GET' && path === '/discover') {
    const named = await client.call('search-names', {}).catch(() => ({ handles: [] }))
    const ns = await client.call('namespace').catch(() => null)
    // Profiles are only available for feeds this node HOLDS, so most rows here have none — a
    // stranger's display name is unreachable until you follow them. The handle is what identifies
    // them until then, which is why it leads each row.
    const profiles = await client.call('profiles').catch(() => ({}))
    const following = await client.call('following').catch(() => [])
    return send(res, pages.discover(named.handles || [], following, profiles, ns))
  }

  if (req.method === 'GET' && path === '/following') {
    // ⛔ Profiles too. A following list that showed keys could not say WHO you follow, which is the
    // only question that page answers.
    return send(res, pages.following(await client.call('following'), await client.call('missing'),
      await client.call('profiles')))
  }

  // ⚠ Served through the daemon, not from disk: the surface holds no storage, and an avatar may
  // belong to a feed this node replicated rather than one it authored.
  // ⛔ Served through the daemon and streamed FROM DISK. The daemon returns a PATH, not bytes: both
  // processes are on one machine (the control channel is a unix socket), so pushing a file through
  // it as base64 was an encode/decode round trip for nothing. The bytes reached this machine over
  // the peer's own replication, which is where every byte on this machine came from.
  if (req.method === 'GET' && (path.startsWith('/avatar/') || path.startsWith('/blob/'))) {
    const isAvatar = path.startsWith('/avatar/')
    const oid = path.slice((isAvatar ? '/avatar/' : '/blob/').length)
    const r = await client.call(isAvatar ? 'avatar-get' : 'file-get', { oid }).catch(() => null)
    if (!r || !r.path) { res.writeHead(404); return res.end() }
    res.writeHead(200, {
      'content-type': r.mimeType || 'application/octet-stream',
      'content-length': r.size,
      // ⛔ The browser must not second-guess the type. We sniffed the bytes; letting it sniff too is
      // how a text file becomes markup.
      'x-content-type-options': 'nosniff',
      // Content-addressed, so the bytes for an oid can never change.
      'cache-control': 'public, max-age=31536000, immutable'
    })
    return fs.createReadStream(r.path).pipe(res)
  }

  if (req.method === 'GET' && path.startsWith('/u/')) {
    const author = path.slice('/u/'.length)
    if (!/^[0-9a-f]{64}$/i.test(author)) return send(res, pages.error('Not a feed key'), 404)
    // ⛔ Both: `profile` is that feed's own record, `profiles` carries the CONFIRMED handle the
    // daemon attached. Rendering the page from the record alone is what left the handle off it.
    /**
     * ⛔ MERGED, not cherry-picked. This used to copy `handle` across by hand and nothing else, so
     * every field the profiles map gained had to be remembered here — and twice now it was not.
     * #64 §2 records the first time, with `handle` itself: "rendering the page from the record
     * alone is what left the handle off it". `root` was the second, and it silently removed the
     * only way to @ anybody.
     *
     * `profiles` is a superset of `profile` for any feed that has published one; `profile` supplies
     * the empty shape for a feed that has not, so the fallback survives.
     */
    const profile = { ...(await client.call('profile', { feed: author })),
      ...((await client.call('profiles'))[author] || {}) }
    // ⛔ `posts-by`, not a filtered timeline. The timeline excludes PEEKED feeds, so filtering it
    // could never render the page of somebody you had only looked at — which is the case this page
    // exists to serve.
    const rows = await client.call('posts-by', { feed: author })
    const holding = await client.call('holding', { feed: author })
    return send(res, pages.person(author, profile, rows, await fileTypes(rows, client), holding))
  }

  if (req.method === 'GET' && path === '/settings') {
    return send(res, await settingsPage(client, url.searchParams.get('m'), null, themeFrom(req), ctx.appUpdate))
  }

  // ── the namespace ───────────────────────────────────────────────────────────────────────────
  //
  // ⛔ These are the only door on an installed node. `pgh.bin` is one string and favela-peer spends
  // it on the daemon, so `favela namespace --join` cannot be run on a machine that installed the
  // app. A ceremony reachable only from a checkout is a ceremony most people cannot perform.

  // The theme. ⚠ A cookie rather than a stored preference: it must be readable BEFORE the page is
  // written, and it is a property of this browser rather than of the identity.
  if (req.method === 'POST' && path === '/rooms/create') {
    const { key } = await client.call('room-create')
    return seeOther(res, '/r/' + key)
  }
  if (req.method === 'POST' && path === '/rooms/join') {
    const form = await body(req)
    try { await client.call('room-join', { key: String(form.key || '').trim() }) } catch (err) {
      return seeOther(res, '/rooms?m=' + encodeURIComponent(err.message))
    }
    return seeOther(res, '/r/' + String(form.key || '').trim())
  }
  if (req.method === 'POST' && path.startsWith('/r/')) {
    const [, , key, action] = path.split('/')
    if (!/^[0-9a-f]{64}$/i.test(key || '')) return send(res, pages.error('Not a room key'), 404)
    const form = await body(req)
    try {
      if (action === 'post' && String(form.body || '').trim()) await client.call('room-post', { key, body: form.body })
      else if (action === 'request') await client.call('room-request', { key })
      else if (action === 'admit') {
        // ⛔ Both, in this order. Admitting a writer whose identity is not a member would put a
        // machine in the room with nobody behind it — apply refuses that, and doing it here in one
        // press is what makes "let them in" mean what it says.
        if (form.member) await client.call('room-add', { key, member: form.member, role: 'peer' })
        await client.call('room-admit', { key, writer: form.writer })
      }
    } catch (err) {
      return seeOther(res, '/r/' + key + '?m=' + encodeURIComponent(err.message))
    }
    return seeOther(res, '/r/' + key)
  }

  if (req.method === 'POST' && path === '/theme') {
    const form = await body(req)
    const v = String(form.theme || '')
    const attrs = '; Path=/; Max-Age=31536000; SameSite=Lax'
    res.writeHead(303, {
      location: '/settings',
      // "system" CLEARS it. A third value stored as a value would be a setting that shadows the
      // reader's own; absence is what lets prefers-color-scheme through.
      'set-cookie': v === 'light' || v === 'dark'
        ? 'favela_theme=' + v + attrs
        : 'favela_theme=; Path=/; Max-Age=0; SameSite=Lax'
    })
    return res.end()
  }

  // ⛔ REMOVES A LIST ENTRY, NEVER A LOG. The daemon refuses the current namespace and refuses a
  // founded one, and says why in both cases — the message is passed through verbatim because it is
  // the half that knows.
  if (req.method === 'POST' && path === '/namespace/forget') {
    const form = await body(req)
    try {
      await client.call('namespace-forget', { key: (form.key || '').trim() })
    } catch (err) {
      return seeOther(res, '/settings?m=' + encodeURIComponent(err.message))
    }
    return seeOther(res, '/settings')
  }
  if (req.method === 'POST' && path === '/namespace/join') {
    const form = await body(req)
    try {
      await client.call('namespace-join', { key: String(form.key || '').trim() })
    } catch (err) {
      // The daemon's message verbatim — it is the one that knows which rule was broken.
      return seeOther(res, '/settings?m=' + encodeURIComponent(err.message))
    }
    return seeOther(res, '/settings')
  }

  if (req.method === 'POST' && path === '/namespace/request') {
    const form = await body(req)
    const token = String(form.token || '').trim()
    try {
      await client.call('namespace-request', token ? { token } : {})
    } catch (err) {
      return seeOther(res, '/settings?m=' + encodeURIComponent(err.message))
    }
    return seeOther(res, '/settings?m=' + encodeURIComponent(
      token ? 'asked, with your invite' : 'asked — an admin has to answer it by hand'))
  }

  // ⚠ Renders instead of redirecting, on purpose. The token is a secret shown once, and a redirect
  // would have to carry it in the query string — into browser history and every log that keeps
  // request lines.
  if (req.method === 'POST' && path === '/namespace/invite') {
    let minted
    try {
      minted = await client.call('namespace-invite')
    } catch (err) {
      return seeOther(res, '/settings?m=' + encodeURIComponent(err.message))
    }
    return send(res, await settingsPage(client, null, minted.token, themeFrom(req), ctx.appUpdate))
  }

  if (req.method === 'POST' && path === '/namespace/admit') {
    const form = await body(req)
    try {
      await client.call('namespace-admit', { writer: String(form.writer || '').trim() })
    } catch (err) {
      return seeOther(res, '/settings?m=' + encodeURIComponent(err.message))
    }
    return seeOther(res, '/settings')
  }

  if (req.method === 'POST' && path === '/namespace/withdraw') {
    const form = await body(req)
    try {
      await client.call('namespace-uninvite', { hash: String(form.hash || '').trim() })
    } catch (err) {
      return seeOther(res, '/settings?m=' + encodeURIComponent(err.message))
    }
    return seeOther(res, '/settings')
  }

  // Claim or release a handle. ⚠ Its own route rather than a field on the settings form: that form
  // is multipart because of the avatar, and a name claim is an ordinary urlencoded post. Folding
  // them together would make claiming a name depend on the file-upload parser.
  if (req.method === 'POST' && path === '/handle') {
    const form = await body(req)
    try {
      if (form.release) await client.call('release-name', { handle: form.release })
      else if (form.name && form.name.trim()) await client.call('claim-name', { name: form.name.trim() })
      else return seeOther(res, '/settings?m=' + encodeURIComponent('type a name to claim'))
    } catch (err) {
      // ⛔ The daemon's message is passed through verbatim — it is the one that knows why. An
      // unlinked device says so, and a malformed name says which rule it broke.
      return seeOther(res, '/settings?m=' + encodeURIComponent(err.message))
    }
    return seeOther(res, '/settings')
  }

  /**
   * Where this identity publishes.
   *
   * ⛔ The daemon decides, not this page. `set-reach` is gated on an ATTESTED device — the same gate
   * as posting — so a browser that is merely signed in cannot switch a network on. The refusal
   * arrives here as an error and is shown; it is not something to route around.
   *
   * ⚠ An unticked box sends NOTHING, so the absent-means-empty reading is the correct one for a
   * checkbox form: no `reach` field at all means every network off.
   */
  if (req.method === 'POST' && path === '/reach') {
    const form = await body(req)
    // ⚠ One field per network — see reachSettings(). An unchecked box sends nothing at all, so
    // absence is the correct reading of "off" here.
    const networks = ['bsky', 'fediverse'].filter((n) => form['reach-' + n])
    // ⛔ SENT, because it never was. The field has been rendered and accepted by the daemon the
    // whole time with nothing in between — so the page told people to designate an identity
    // somewhere else, and there was no somewhere else.
    //
    // ⚠ An empty box CLEARS it, deliberately: `setReach` treats a falsy credential as null, and
    // withholding is the fail-closed state. Leaving it unsendable would make "stop publishing under
    // this identity" impossible from the only surface that can say it.
    const credential = String(form.credential || '').trim() || null
    try {
      await client.call('set-reach', { networks, credential })
    } catch (err) {
      return seeOther(res, '/settings?m=' + encodeURIComponent(err.message))
    }
    return seeOther(res, '/settings?m=' + encodeURIComponent(
      networks.length ? 'Publishing to ' + networks.join(' and ') + '.' : 'Publishing nowhere but favela.'))
  }

  if (req.method === 'POST' && path === '/settings') {
    // 3 MiB: each picture is capped at 512 KiB by the daemon and the form can now carry TWO, and
    // the slack is so an oversized picture is refused by the daemon with a message about its size
    // rather than by the transport with nothing useful.
    let form
    try {
      form = parseMultipart(await readBytes(req, 3 * 1024 * 1024), req.headers['content-type'])
    } catch (err) {
      return seeOther(res, '/settings?m=' + encodeURIComponent(err.message))
    }
    if (!form) return seeOther(res, '/settings?m=' + encodeURIComponent('expected a form upload'))

    try {
      const set = {}
      if (typeof form.fields.name === 'string') set.name = form.fields.name
      if (typeof form.fields.bio === 'string') set.bio = form.fields.bio
      if (Object.keys(set).length) await client.call('set-profile', set)
      // ⛔ TWO PICTURES, one form. The avatar is the round one; the banner is the wide header.
      // They are independent — submitting the form with only one file set must not clear the other,
      // which is why each is only touched when its own part carries bytes.
      for (const [field, verb] of [['avatar', 'avatar-put'], ['banner', 'banner-put']]) {
        const file = form.files[field]
        if (!file || !file.bytes.length) continue
        // ⛔ A PATH, for the same reason. Written where the daemon can read it; it copies what it
        // keeps, so this temp file is ours to remove.
        const tmp = join(os.tmpdir(), 'favela-upload-' + Date.now() + '-' + Math.random().toString(16).slice(2))
        fs.writeFileSync(tmp, file.bytes)
        try { await client.call(verb, { path: tmp }) } finally { try { fs.unlinkSync(tmp) } catch {} }
      }
    } catch (err) {
      return seeOther(res, '/settings?m=' + encodeURIComponent(err.message))
    }
    return seeOther(res, '/settings')
  }

  if (req.method === 'GET' && path === '/knocks') {
    return send(res, pages.knocks(await client.call('knocks')))
  }

  // Compose. multipart, because it may carry a file.
  //
  // ⛔ The file is stored FIRST and its oid appended to the body as markdown, so what gets published
  // is an ordinary record. Nothing downstream — replication, rendering, projection — has to know
  // that a post had an attachment; `![](favela://blob/<oid>)` is just a link to a file the overlay
  // holds, and every peer resolves it the same way.
  if (req.method === 'POST' && path === '/post') {
    let form
    try {
      form = parseMultipart(await readBytes(req, MAX_UPLOAD), req.headers['content-type'])
    } catch (err) {
      return seeOther(res, '/?m=' + encodeURIComponent(err.message))
    }
    if (!form) return seeOther(res, '/?m=' + encodeURIComponent('expected a form upload'))

    let body = typeof form.fields.body === 'string' ? form.fields.body : ''
    const file = form.files.file
    if (file && file.bytes.length) {
      // ⛔ A PATH over the socket, not bytes — both processes are on this machine and the daemon
      // copies what it keeps, so this temp file is ours to remove.
      const tmp = join(os.tmpdir(), 'favela-post-' + Date.now() + '-' + Math.random().toString(16).slice(2))
      fs.writeFileSync(tmp, file.bytes)
      // ⛔ THE PYRAMID, when the composer built one. The base is the author's own bytes and always
      // level 0; `level-N` parts are the smaller levels the browser rendered at compose time
      // (SOCIAL-MODEL §6.6.1 — encode once at authoring, so bridges SELECT rather than re-encode).
      //
      // ⚠ Absent levels are the ordinary case, not a failure: a browser without the script, an
      // image already smaller than the lowest rung, or a non-image all send exactly one part. That
      // path is today's behaviour and stays byte-identical.
      const tmps = [tmp]
      const levels = [{ path: tmp, w: Number(form.fields['file-w']) || 0, h: Number(form.fields['file-h']) || 0 }]
      for (let n = 0; ; n++) {
        const part = form.files['level-' + n]
        if (!part || !part.bytes.length) break
        const lp = join(os.tmpdir(), 'favela-level-' + Date.now() + '-' + n + '-' + Math.random().toString(16).slice(2))
        fs.writeFileSync(lp, part.bytes)
        tmps.push(lp)
        levels.push({ path: lp, w: Number(form.fields['level-' + n + '-w']) || 0, h: Number(form.fields['level-' + n + '-h']) || 0 })
      }

      let stored
      try {
        stored = levels.length > 1
          ? await client.call('file-put-levels', { levels })
          : await client.call('file-put', { path: tmp })
      } catch (err) {
        return seeOther(res, '/?m=' + encodeURIComponent(err.message))
      } finally {
        for (const t of tmps) { try { fs.unlinkSync(t) } catch {} }
      }
      // The alt text is the filename the browser sent — the only label we have, and it is escaped
      // wherever it is rendered. ⚠ It is a CLAIM by the uploader; the TYPE came from the bytes.
      const alt = String(file.filename || 'file').replace(/[[\]()]/g, '')
      body = (body ? body.trimEnd() + '\n\n' : '') + '![' + alt + '](favela://blob/' + stored.oid + ')'
    }

    if (!body.trim()) return seeOther(res, '/?m=' + encodeURIComponent('a post needs words or a file'))
    await client.call('post', { body })
    return seeOther(res, '/')
  }

  if (req.method === 'POST') {
    const form = await body(req)

    if (path === '/follow') {
      if (/^[0-9a-f]{64}$/i.test(form.feed || '')) await client.call('follow', { feed: form.feed })
      return seeOther(res, '/following')
    }
    if (path === '/admit') {
      if (/^[0-9a-f]{64}$/i.test(form.feed || '')) await client.call('admit', { feed: form.feed })
      return seeOther(res, '/knocks')
    }
    // ⛔ Two routes, because a like has to be reversible from the page that offered it. The daemon
    // has had `unlike` all along and nothing on the surface could reach it, so a like was one-way
    // from a browser — the same "method with no route" shape as `browser-auth`.
    // Hold a feed to READ it. ⚠ Deliberately its own route rather than a silent side effect of
    // visiting /u/<key>: holding replicates their feed to this disk and joins their discovery
    // topic, so it is a decision a person makes, not one a link makes for them.
    if (path === '/peek') {
      if (/^[0-9a-f]{64}$/i.test(form.feed || '')) await client.call('peek', { feed: form.feed })
      return seeOther(res, local(form.from) === '/' ? '/u/' + form.feed : local(form.from))
    }
    if (path === '/reply') {
      const target = parseTarget(form.target)
      const body = String(form.body || '').trim()
      if (!target || !body) return seeOther(res, local('/t/' + encodeURIComponent(String(form.target || ''))))
      await client.call('reply', { target, body })
      return seeOther(res, '/t/' + encodeURIComponent(String(form.target)))
    }
    if (path === '/block' || path === '/unblock') {
      if (/^[0-9a-f]{64}$/i.test(form.feed || '')) {
        try { await client.call(path === '/block' ? 'block' : 'unblock', { feed: form.feed }) } catch (err) {
          return seeOther(res, '/u/' + form.feed + '?m=' + encodeURIComponent(err.message))
        }
      }
      return seeOther(res, local(form.from) === '/' ? '/u/' + form.feed : local(form.from))
    }
    if (path === '/repost' || path === '/unrepost') {
      const target = parseTarget(form.target)
      if (target) await client.call(path === '/repost' ? 'repost' : 'unrepost', { target })
      return seeOther(res, local(form.from))
    }
    if (path === '/react' || path === '/unreact') {
      const target = parseTarget(form.target)
      const value = String(form.value || '').trim()
      // `like` keeps its own daemon command — it is the one value both target networks carry.
      const cmd = value === 'like'
        ? (path === '/react' ? 'like' : 'unlike')
        : (path === '/react' ? 'react' : 'unreact')
      if (target && value) await client.call(cmd, value === 'like' ? { target } : { target, value })
      return seeOther(res, local(form.from))
    }
    if (path === '/like' || path === '/unlike') {
      const target = parseTarget(form.target)
      if (target) await client.call(path === '/like' ? 'like' : 'unlike', { target })
      // ⚠ Back where you came from. It used to always redirect to `/`, so liking a post found in
      // search threw away the search. `local()` refuses anything but a same-site path — an
      // attacker-supplied `from` would otherwise make this an open redirect.
      return seeOther(res, local(form.from))
    }
  }

  return send(res, pages.error('Not found'), 404)
}
export default { route }
// Sessions — in memory, and deliberately nothing more.
//
// ── What a session is and is not ──────────────────────────────────────────────────────────────
//
// It maps an opaque cookie value to the browser public key that proved itself at login. That is
// ALL. It is not a credential, it carries no permissions, and it is never trusted on its own:
// every request re-asks the daemon whether that key is still allowed (`browser-status`).
//
// ⛔ So a revoke lands on the NEXT request, not when something lapses. That is the whole reason
// there is no expiry to configure — the check is live, so an expiry would only add a way to be
// logged out for no reason.
//
// ── Why in memory ─────────────────────────────────────────────────────────────────────────────
//
// The surface holds no storage — that is its boundary, and its test enforces it. So sessions die
// with the process, and a restart means logging in again. That is honest and stated rather than
// worked around: persisting them would make the renderer own durable security state, which is the
// thing it exists not to do.
//
// ⚠ The cookie value is 32 random bytes. It is a bearer token for the length of one process, so it
// is HttpOnly and SameSite=Strict — a page cannot read it, and it does not ride cross-site
// requests. It is not signed, because nothing verifies it but this map.

// ⚠ bare-crypto — NOT the storage layer's crypto package. The surface must not reach into the
// replication stack, and test/boundary.test.js caught the first version of this import. A renderer
// needs random bytes, nothing more.
//
// That test greps source TEXT, so naming the offending package here would trip it too; the guard is
// blunt on purpose and the comment moves instead.
import crypto from 'bare-crypto'

export const COOKIE = 'favela_session'

export class Sessions {
  constructor () {
    this._byId = new Map() // sessionId -> publicKey
    this._handoff = new Map() // one-time token -> sessionId
  }

  /**
   * A ONE-TIME token that can be exchanged for this session's cookie.
   *
   * ⛔ Exists because the session cookie is `HttpOnly`, so it cannot be handed to an embedded
   * WebView from the outside — `document.cookie` cannot write one and no native API portably can.
   * The cookie has to arrive on an HTTP response, which means a route. favela-web reached the same
   * conclusion and for the same reason.
   *
   * ⛔ ONE-TIME, unlike favela-web's, which accepts a token repeatedly and leans on the request
   * coming from loopback. On a desktop that is a real gate; on Android it is not — every app on the
   * device shares loopback, so a token that survives its first use is a replayable credential
   * sitting in a URL. Burning it on redemption costs one `delete`.
   */
  createHandoff (id) {
    if (!id || !this._byId.has(id)) return null
    const token = crypto.randomBytes(32).toString('hex')
    this._handoff.set(token, id)
    return token
  }

  /** Exchange a handoff token for its session id, consuming it. Null if unknown or already used. */
  redeemHandoff (token) {
    if (!token) return null
    const id = this._handoff.get(token)
    if (!id) return null
    this._handoff.delete(token)
    // ⚠ The session may have been destroyed between minting and redeeming — a revoke, or a restart.
    return this._byId.has(id) ? id : null
  }

  /** Start a session for a browser that has just proven its key. */
  create (publicKey) {
    const id = crypto.randomBytes(32).toString('hex')
    this._byId.set(id, publicKey)
    return id
  }

  /** The public key behind a cookie, or null. */
  keyFor (id) {
    if (!id) return null
    return this._byId.get(id) || null
  }

  destroy (id) {
    if (!id) return
    this._byId.delete(id)
    for (const [t, sid] of this._handoff) if (sid === id) this._handoff.delete(t)
  }

  /** Every session for a key — a revoked browser should not keep a second tab alive. */
  destroyKey (publicKey) {
    for (const [id, key] of this._byId) if (key === publicKey) this.destroy(id)
  }

  get size () { return this._byId.size }
}

/**
 * The session id from a Cookie header.
 *
 * ⚠ Parsed strictly: a cookie value is taken up to the first `;` and never unescaped, because the
 * only thing it is ever compared against is a hex string this process generated.
 */
export function sessionIdFrom (cookieHeader) {
  if (typeof cookieHeader !== 'string') return null
  for (const part of cookieHeader.split(';')) {
    const i = part.indexOf('=')
    if (i === -1) continue
    if (part.slice(0, i).trim() !== COOKIE) continue
    const v = part.slice(i + 1).trim()
    return /^[0-9a-f]{64}$/.test(v) ? v : null
  }
  return null
}

export const setCookie = (id) =>
  COOKIE + '=' + id + '; Path=/; HttpOnly; SameSite=Strict'

export const clearCookie = () =>
  COOKIE + '=; Path=/; HttpOnly; SameSite=Strict; Max-Age=0'

export default { Sessions, sessionIdFrom, setCookie, clearCookie, COOKIE }
function isBuffer(value) {
  return Buffer.isBuffer(value) || value instanceof Uint8Array
}

function isEncoding(encoding) {
  return Buffer.isEncoding(encoding)
}

function alloc(size, fill, encoding) {
  return Buffer.alloc(size, fill, encoding)
}

function allocUnsafe(size) {
  return Buffer.allocUnsafe(size)
}

function allocUnsafeSlow(size) {
  return Buffer.allocUnsafeSlow(size)
}

function byteLength(string, encoding) {
  return Buffer.byteLength(string, encoding)
}

function compare(a, b) {
  return Buffer.compare(a, b)
}

function concat(buffers, totalLength) {
  return Buffer.concat(buffers, totalLength)
}

function copy(source, target, targetStart, start, end) {
  return toBuffer(source).copy(target, targetStart, start, end)
}

function equals(a, b) {
  return toBuffer(a).equals(b)
}

function fill(buffer, value, offset, end, encoding) {
  return toBuffer(buffer).fill(value, offset, end, encoding)
}

function from(value, encodingOrOffset, length) {
  return Buffer.from(value, encodingOrOffset, length)
}

function includes(buffer, value, byteOffset, encoding) {
  return toBuffer(buffer).includes(value, byteOffset, encoding)
}

function indexOf(buffer, value, byfeOffset, encoding) {
  return toBuffer(buffer).indexOf(value, byfeOffset, encoding)
}

function lastIndexOf(buffer, value, byteOffset, encoding) {
  return toBuffer(buffer).lastIndexOf(value, byteOffset, encoding)
}

function swap16(buffer) {
  return toBuffer(buffer).swap16()
}

function swap32(buffer) {
  return toBuffer(buffer).swap32()
}

function swap64(buffer) {
  return toBuffer(buffer).swap64()
}

function toBuffer(buffer) {
  if (Buffer.isBuffer(buffer)) return buffer
  return Buffer.from(buffer.buffer, buffer.byteOffset, buffer.byteLength)
}

function toString(buffer, encoding, start, end) {
  return toBuffer(buffer).toString(encoding, start, end)
}

function write(buffer, string, offset, length, encoding) {
  return toBuffer(buffer).write(string, offset, length, encoding)
}

function readDoubleBE(buffer, offset) {
  return toBuffer(buffer).readDoubleBE(offset)
}

function readDoubleLE(buffer, offset) {
  return toBuffer(buffer).readDoubleLE(offset)
}

function readFloatBE(buffer, offset) {
  return toBuffer(buffer).readFloatBE(offset)
}

function readFloatLE(buffer, offset) {
  return toBuffer(buffer).readFloatLE(offset)
}

function readInt32BE(buffer, offset) {
  return toBuffer(buffer).readInt32BE(offset)
}

function readInt32LE(buffer, offset) {
  return toBuffer(buffer).readInt32LE(offset)
}

function readUInt32BE(buffer, offset) {
  return toBuffer(buffer).readUInt32BE(offset)
}

function readUInt32LE(buffer, offset) {
  return toBuffer(buffer).readUInt32LE(offset)
}

function writeDoubleBE(buffer, value, offset) {
  return toBuffer(buffer).writeDoubleBE(value, offset)
}

function writeDoubleLE(buffer, value, offset) {
  return toBuffer(buffer).writeDoubleLE(value, offset)
}

function writeFloatBE(buffer, value, offset) {
  return toBuffer(buffer).writeFloatBE(value, offset)
}

function writeFloatLE(buffer, value, offset) {
  return toBuffer(buffer).writeFloatLE(value, offset)
}

function writeInt32BE(buffer, value, offset) {
  return toBuffer(buffer).writeInt32BE(value, offset)
}

function writeInt32LE(buffer, value, offset) {
  return toBuffer(buffer).writeInt32LE(value, offset)
}

function writeUInt32BE(buffer, value, offset) {
  return toBuffer(buffer).writeUInt32BE(value, offset)
}

function writeUInt32LE(buffer, value, offset) {
  return toBuffer(buffer).writeUInt32LE(value, offset)
}

module.exports = {
  isBuffer,
  isEncoding,
  alloc,
  allocUnsafe,
  allocUnsafeSlow,
  byteLength,
  compare,
  concat,
  copy,
  equals,
  fill,
  from,
  includes,
  indexOf,
  lastIndexOf,
  swap16,
  swap32,
  swap64,
  toBuffer,
  toString,
  write,
  readDoubleBE,
  readDoubleLE,
  readFloatBE,
  readFloatLE,
  readInt32BE,
  readInt32LE,
  readUInt32BE,
  readUInt32LE,
  writeDoubleBE,
  writeDoubleLE,
  writeFloatBE,
  writeFloatLE,
  writeInt32BE,
  writeInt32LE,
  writeUInt32BE,
  writeUInt32LE
}
{
  "name": "b4a",
  "version": "1.8.1",
  "description": "Bridging the gap between buffers and typed arrays",
  "exports": {
    "./package": "./package.json",
    ".": {
      "react-native": "./react-native.js",
      "browser": "./browser.js",
      "default": "./index.js"
    }
  },
  "files": [
    "browser.js",
    "index.js",
    "react-native.js",
    "lib"
  ],
  "scripts": {
    "format": "prettier --write . && lunte --fix",
    "lint": "prettier --check . && lunte",
    "test": "npm run test:bare && npm run test:node",
    "test:bare": "brittle-bare --coverage test.mjs",
    "test:node": "brittle-node --coverage test.mjs"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/b4a.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/b4a/issues"
  },
  "homepage": "https://github.com/holepunchto/b4a#readme",
  "devDependencies": {
    "brittle": "^3.5.2",
    "lunte": "^1.7.0",
    "nanobench": "^3.0.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^2.0.0"
  },
  "peerDependencies": {
    "react-native-b4a": "*"
  },
  "peerDependenciesMeta": {
    "react-native-b4a": {
      "optional": true
    }
  }
}
const ESC = '\x1b'
const CSI = ESC + '['
const SGR = (n) => CSI + n + 'm'

exports.constants = {
  ESC,
  CSI,
  SGR
}

// https://en.wikipedia.org/wiki/ANSI_escape_code#CSI_(Control_Sequence_Introducer)_sequences

exports.cursorHide = CSI + '?25l'
exports.cursorShow = CSI + '?25h'

exports.cursorUp = function cursorUp(n = 1) {
  return CSI + n + 'A'
}

exports.cursorDown = function cursorDown(n = 1) {
  return CSI + n + 'B'
}

exports.cursorForward = function cursorForward(n = 1) {
  return CSI + n + 'C'
}

exports.cursorBack = function cursorBack(n = 1) {
  return CSI + n + 'D'
}

exports.cursorNextLine = function cursorNextLine(n = 1) {
  return CSI + n + 'E'
}

exports.cursorPreviousLine = function cursorPreviousLine(n = 1) {
  return CSI + n + 'F'
}

exports.cursorPosition = function cursorPosition(column, row = 0) {
  if (row === 0) return CSI + (column + 1) + 'G'

  return CSI + (row + 1) + ';' + (column + 1) + 'H'
}

exports.eraseDisplayEnd = CSI + 'J'
exports.eraseDisplayStart = CSI + '1J'
exports.eraseDisplay = CSI + '2J'
exports.eraseLineEnd = CSI + 'K'
exports.eraseLineStart = CSI + '1K'
exports.eraseLine = CSI + '2K'

exports.scrollUp = function scrollUp(n = 1) {
  return CSI + n + 'S'
}

exports.scrollDown = function scrollDown(n = 1) {
  return CSI + n + 'T'
}

// https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_(Select_Graphic_Rendition)_parameters

exports.modifierReset = SGR(0)
exports.modifierBold = SGR(1)
exports.modifierDim = SGR(2)
exports.modifierItalic = SGR(3)
exports.modifierUnderline = SGR(4)
exports.modifierNormal = SGR(22)
exports.modifierNotItalic = SGR(23)
exports.modifierNotUnderline = SGR(24)

// https://en.wikipedia.org/wiki/ANSI_escape_code#Colors

exports.colorBlack = SGR(30)
exports.colorRed = SGR(31)
exports.colorGreen = SGR(32)
exports.colorYellow = SGR(33)
exports.colorBlue = SGR(34)
exports.colorMagenta = SGR(35)
exports.colorCyan = SGR(36)
exports.colorWhite = SGR(37)
exports.colorDefault = SGR(39)
exports.colorBrightBlack = SGR(90)
exports.colorBrightRed = SGR(91)
exports.colorBrightGreen = SGR(92)
exports.colorBrightYellow = SGR(93)
exports.colorBrightBlue = SGR(94)
exports.colorBrightMagenta = SGR(95)
exports.colorBrightCyan = SGR(96)
exports.colorBrightWhite = SGR(97)
{
  "name": "bare-ansi-escapes",
  "version": "2.2.3",
  "description": "Parse and produce ANSI escape sequences",
  "exports": {
    "./package": "./package.json",
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    },
    "./key-decoder": {
      "types": "./key-decoder.d.ts",
      "default": "./key-decoder.js"
    }
  },
  "files": [
    "index.js",
    "index.d.ts",
    "key-decoder.js",
    "key-decoder.d.ts"
  ],
  "scripts": {
    "test": "prettier . --check && bare test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-ansi-escapes.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-ansi-escapes/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-ansi-escapes#readme",
  "dependencies": {
    "bare-stream": "^2.6.5"
  },
  "devDependencies": {
    "bare-buffer": "^3.0.2",
    "brittle": "^3.3.2",
    "prettier": "^3.4.2",
    "prettier-config-standard": "^7.0.0"
  },
  "peerDependencies": {
    "bare-buffer": "*"
  },
  "peerDependenciesMeta": {
    "bare-buffer": {
      "optional": true
    }
  }
}
const inspect = require('bare-inspect')

class AssertionError extends Error {
  constructor(opts = {}) {
    let { message = null, actual, expected, operator } = opts

    if (message === null) {
      message = `${inspect(actual)} ${operator} ${inspect(expected)}`
    }

    super(message)

    this.actual = actual
    this.expected = expected
    this.operator = operator
  }

  get name() {
    return 'AssertionError'
  }

  get code() {
    'ASSERTION'
  }
}

function assertFail(opts, fn) {
  if (opts.message instanceof Error) throw opts.message

  const err = new AssertionError(opts)

  if (Error.captureStackTrace) Error.captureStackTrace(err, fn)

  throw err
}

module.exports = exports = function assert(actual, message) {
  if (actual) return

  assertFail({ message, actual, expected: true, operator: '==' }, assert)
}

exports.AssertionError = AssertionError

exports.fail = function fail(message) {
  if (message === undefined) message = 'Failed'

  assertFail({ message, operator: 'fail' }, fail)
}

exports.ok = function ok(actual, message) {
  if (actual) return

  assertFail({ message, actual, expected: true, operator: '==' }, ok)
}

exports.notOk = function ok(actual, message) {
  if (!actual) return

  assertFail({ message, actual, expected: false, operator: '==' }, ok)
}

exports.equal = function equal(actual, expected, message) {
  if (actual == expected || (actual !== actual && expected !== expected)) {
    return
  }

  assertFail({ message, actual, expected, operator: '==' }, equal)
}

exports.notEqual = function notEqual(actual, expected, message) {
  if (actual != expected && (actual === actual || expected === expected)) {
    return
  }

  assertFail({ message, actual, expected, operator: '!=' }, notEqual)
}

exports.strictEqual = function strictEqual(actual, expected, message) {
  if (Object.is(actual, expected)) return

  assertFail(
    { message, actual, expected, operator: 'strictEqual' },
    strictEqual
  )
}

exports.notStrictEqual = function notStrictEqual(actual, expected, message) {
  if (!Object.is(actual, expected)) return

  assertFail(
    { message, actual, expected, operator: 'notStrictEqual' },
    notStrictEqual
  )
}
{
  "name": "bare-assert",
  "version": "1.2.0",
  "description": "Assertion library for JavaScript",
  "exports": {
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    },
    "./package": "./package.json"
  },
  "files": [
    "index.js",
    "index.d.ts"
  ],
  "scripts": {
    "test": "prettier . --check && bare test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-assert.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-assert/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-assert#readme",
  "dependencies": {
    "bare-inspect": "^3.1.2"
  },
  "devDependencies": {
    "brittle": "^3.1.1",
    "prettier": "^3.4.2",
    "prettier-config-standard": "^7.0.0"
  }
}
module.exports = require.addon()
const constants = require('./lib/constants')
const Hash = require('./lib/hash')
const Hmac = require('./lib/hmac')
const { Cipheriv, Decipheriv } = require('./lib/cipher')
const { randomBytes, randomFill, randomUUID } = require('./lib/random')
const pbkdf2 = require('./lib/pbkdf2')
const { generateKeyPair } = require('./lib/key')
const { sign, verify } = require('./lib/signature')
const { timingSafeEqual } = require('./lib/timing')

exports.constants = constants

exports.Hash = Hash

exports.createHash = function createHash(algorithm, opts) {
  return new Hash(algorithm, opts)
}

exports.Hmac = Hmac

exports.createHmac = function createHmac(algorithm, key, opts) {
  return new Hmac(algorithm, key, opts)
}

exports.Cipheriv = Cipheriv

exports.createCipheriv = function createCipheriv(algorithm, key, iv, opts) {
  return new Cipheriv(algorithm, key, iv, opts)
}

exports.Decipheriv = Decipheriv

exports.createDecipheriv = function createDecipheriv(algorithm, key, iv, opts) {
  return new Decipheriv(algorithm, key, iv, opts)
}

exports.randomBytes = randomBytes

exports.randomFill = randomFill

// For Node.js compatibility
exports.randomFillSync = function randomFillSync(buffer, offset, size) {
  return exports.randomFill(buffer, offset, size)
}

exports.randomUUID = randomUUID

exports.pbkdf2 = pbkdf2

// For Node.js compatibility
exports.pbkdf2Sync = function pbkdf2Sync(password, salt, iterations, keylen, digest) {
  return exports.pbkdf2(password, salt, iterations, keylen, digest)
}

exports.generateKeyPair = generateKeyPair

exports.sign = sign

exports.verify = verify

exports.timingSafeEqual = timingSafeEqual

// For Node.js compatibility
exports.webcrypto = require('./web')
const { Transform } = require('bare-stream')
const assert = require('bare-assert')
const binding = require('../binding')
const constants = require('./constants')

const {
  cipher: {
    AES128ECB,
    AES128CBC,
    AES128CTR,
    AES128OFB,
    AES256ECB,
    AES256CBC,
    AES256CTR,
    AES256OFB,
    AES128GCM,
    AES256GCM,
    CHACHA20POLY1305,
    XCHACHA20POLY1305
  }
} = constants

class CryptoCipher {
  constructor(algorithm, key, iv, encrypt, opts = {}) {
    const { encoding = 'utf8' } = opts

    if (typeof key === 'string') key = Buffer.from(key, encoding)
    if (typeof iv === 'string') iv = Buffer.from(iv, encoding)

    assert(ArrayBuffer.isView(key))
    assert(ArrayBuffer.isView(iv))

    if (key.byteLength !== binding.cipherKeyLength(algorithm)) {
      throw new RangeError('Invalid key length')
    }

    if (iv.byteLength !== binding.cipherIVLength(algorithm)) {
      throw new RangeError('Invalid iv length')
    }

    this._finalized = false

    binding.cipherInit(
      this,
      algorithm,
      key.buffer,
      key.byteOffset,
      key.byteLength,
      iv.buffer,
      iv.byteOffset,
      iv.byteLength,
      encrypt
    )
  }

  update(data, inputEncoding = 'utf8', outputEncoding) {
    if (this._finalized) {
      throw new Error('Cipher has already been finalized')
    }

    if (typeof data === 'string') data = Buffer.from(data, inputEncoding)

    assert(ArrayBuffer.isView(data))

    const out = new ArrayBuffer(data.byteLength + binding.cipherBlockSize(this))

    const written = binding.cipherUpdate(this, data.buffer, data.byteOffset, data.byteLength, out)

    const result = Buffer.from(out, 0, written)

    return outputEncoding ? result.toString(outputEncoding) : result
  }

  final(outputEncoding) {
    if (this._finalized) {
      throw new Error('Cipher has already been finalized')
    }

    const out = new ArrayBuffer(binding.cipherBlockSize(this))

    const written = binding.cipherFinal(this, out)

    this._finalized = true

    const result = Buffer.from(out, 0, written)

    return outputEncoding ? result.toString(outputEncoding) : result
  }

  setAutoPadding(pad) {
    if (this._finalized) {
      throw new Error('Cipher has already been finalized')
    }

    binding.cipherSetPadding(this, pad)
  }
}

class CryptoAuthenticatedCipher {
  constructor(algorithm, key, nonce, opts = {}) {
    const { encoding = 'utf8', authTagLength = 16 } = opts

    if (typeof key === 'string') key = Buffer.from(key, encoding)
    if (typeof nonce === 'string') nonce = Buffer.from(nonce, encoding)

    assert(ArrayBuffer.isView(key))
    assert(ArrayBuffer.isView(nonce))

    if (key.byteLength !== binding.aeadKeyLength(algorithm)) {
      throw new RangeError('Invalid key length')
    }

    if (nonce.byteLength !== binding.aeadNonceLength(algorithm)) {
      throw new RangeError('Invalid nonce length')
    }

    if (authTagLength !== 12 && authTagLength !== 14 && authTagLength !== 16) {
      throw new RangeError('Invalid authentication tag length')
    }

    this._buffer = []
    this._nonce = nonce
    this._authTag = null
    this._authTagLength = authTagLength
    this._additionalData = null
    this._finalized = false

    binding.aeadInit(this, algorithm, key.buffer, key.byteOffset, key.byteLength, authTagLength)
  }

  update(data, inputEncoding = 'utf8', outputEncoding) {
    if (typeof data === 'string') data = Buffer.from(data, inputEncoding)

    assert(ArrayBuffer.isView(data))

    this._buffer.push(data)

    return outputEncoding ? '' : Buffer.alloc(0)
  }

  setAAD(buffer, opts = {}) {
    const { encoding = 'utf8' } = opts

    if (typeof buffer === 'string') buffer = Buffer.from(buffer, encoding)

    assert(ArrayBuffer.isView(buffer))

    this._additionalData = buffer
  }

  getAuthTag() {
    return this._authTag
  }

  setAuthTag(authTag, encoding) {
    if (typeof authTag === 'string') authTag = Buffer.from(authTag, encoding)

    assert(ArrayBuffer.isView(authTag))

    this._authTag = authTag
  }
}

class CryptoAuthenticatedSeal extends CryptoAuthenticatedCipher {
  final(outputEncoding) {
    if (this._finalized) {
      throw new Error('Cipher has already been finalized')
    }

    const data = this._buffer.length === 1 ? this._buffer[0] : Buffer.concat(this._buffer)

    const nonce = this._nonce
    const ad = this._additionalData || Buffer.alloc(0)

    const out = new ArrayBuffer(data.byteLength + binding.aeadMaxOverhead(this))

    const written = binding.aeadSeal(
      this,
      data.buffer,
      data.byteOffset,
      data.byteLength,
      nonce.buffer,
      nonce.byteOffset,
      nonce.byteLength,
      ad.buffer,
      ad.byteOffset,
      ad.byteLength,
      out
    )

    this._finalized = true

    const cipherLength = written - this._authTagLength

    this._authTag = Buffer.from(out, cipherLength)

    const result = Buffer.from(out, 0, cipherLength)

    return outputEncoding ? result.toString(outputEncoding) : result
  }
}

class CryptoAuthenticatedOpen extends CryptoAuthenticatedCipher {
  final(outputEncoding) {
    if (this._finalized) {
      throw new Error('Decipher has already been finalized')
    }

    this._buffer.push(this._authTag)

    const data = Buffer.concat(this._buffer)

    const nonce = this._nonce
    const ad = this._additionalData || Buffer.alloc(0)

    const out = new ArrayBuffer(data.byteLength)

    const written = binding.aeadOpen(
      this,
      data.buffer,
      data.byteOffset,
      data.byteLength,
      nonce.buffer,
      nonce.byteOffset,
      nonce.byteLength,
      ad.buffer,
      ad.byteOffset,
      ad.byteLength,
      out
    )

    this._finalized = true

    const result = Buffer.from(out, 0, written)

    return outputEncoding ? result.toString(outputEncoding) : result
  }
}

exports.Cipheriv = class CryptoCipheriv extends Transform {
  constructor(algorithm, key, iv, opts = {}) {
    super(opts)

    algorithm = constants.toCipher(algorithm)

    switch (algorithm) {
      case AES128ECB:
      case AES128CBC:
      case AES128CTR:
      case AES128OFB:
      case AES256ECB:
      case AES256CBC:
      case AES256CTR:
      case AES256OFB:
        this._cipher = new CryptoCipher(algorithm, key, iv, true, opts)
        break

      case AES128GCM:
      case AES256GCM:
      case CHACHA20POLY1305:
      case XCHACHA20POLY1305:
        this._cipher = new CryptoAuthenticatedSeal(algorithm, key, iv, opts)
        break
    }
  }

  update(data, inputEncoding, outputEncoding) {
    return this._cipher.update(data, inputEncoding, outputEncoding)
  }

  final(outputEncoding) {
    return this._cipher.final(outputEncoding)
  }

  setAutoPadding(pad) {
    this._cipher.setAutoPadding(pad)

    return this
  }

  setAAD(buffer, opts) {
    this._cipher.setAAD(buffer, opts)

    return this
  }

  getAuthTag() {
    return this._cipher.getAuthTag()
  }

  _transform(data, encoding, cb) {
    this.push(this.update(data))

    cb(null)
  }

  _flush(cb) {
    this.push(this.final())

    cb(null)
  }
}

exports.Decipheriv = class CryptoDeipheriv extends Transform {
  constructor(algorithm, key, iv, opts = {}) {
    super(opts)

    algorithm = constants.toCipher(algorithm)

    switch (algorithm) {
      case AES128ECB:
      case AES128CBC:
      case AES128CTR:
      case AES128OFB:
      case AES256ECB:
      case AES256CBC:
      case AES256CTR:
      case AES256OFB:
        this._cipher = new CryptoCipher(algorithm, key, iv, false, opts)
        break

      case AES128GCM:
      case AES256GCM:
      case CHACHA20POLY1305:
      case XCHACHA20POLY1305:
        this._cipher = new CryptoAuthenticatedOpen(algorithm, key, iv, opts)
        break
    }
  }

  update(data, inputEncoding, outputEncoding) {
    return this._cipher.update(data, inputEncoding, outputEncoding)
  }

  final(outputEncoding) {
    return this._cipher.final(outputEncoding)
  }

  setAutoPadding(pad) {
    this._cipher.setAutoPadding(pad)

    return this
  }

  setAAD(buffer, opts) {
    this._cipher.setAAD(buffer, opts)

    return this
  }

  setAuthTag(authTag, encoding) {
    this._cipher.setAuthTag(authTag, encoding)

    return this
  }

  _transform(data, encoding, cb) {
    this.push(this.update(data))

    cb(null)
  }

  _flush(cb) {
    this.push(this.final())

    cb(null)
  }
}
const { fail } = require('bare-assert')
const binding = require('../binding')
const errors = require('./errors')

module.exports = exports = {
  hash: {
    MD5: binding.MD5,
    SHA1: binding.SHA1,
    SHA256: binding.SHA256,
    SHA384: binding.SHA384,
    SHA512: binding.SHA512,
    BLAKE2B256: binding.BLAKE2B256,
    RIPEMD160: binding.RIPEMD160
  },
  signature: {
    ED25519: binding.ED25519
  },
  cipher: {
    AES128ECB: binding.AES128ECB,
    AES128CBC: binding.AES128CBC,
    AES128CTR: binding.AES128CTR,
    AES128OFB: binding.AES128OFB,
    AES256ECB: binding.AES256ECB,
    AES256CBC: binding.AES256CBC,
    AES256CTR: binding.AES256CTR,
    AES256OFB: binding.AES256OFB,
    AES128GCM: binding.AES128GCM,
    AES256GCM: binding.AES256GCM,
    CHACHA20POLY1305: binding.CHACHA20POLY1305,
    XCHACHA20POLY1305: binding.XCHACHA20POLY1305
  },
  keyType: {
    ED25519: binding.ED25519
  }
}

exports.toHash = function toHash(hash) {
  if (typeof hash === 'number' && !isNaN(hash)) return hash

  if (typeof hash === 'string') {
    hash = hash.replace(/-/g, '')

    if (hash in exports.hash === false) {
      hash = hash.toUpperCase()

      if (hash in exports.hash === false) {
        throw errors.UNKNOWN_HASH(`Unknown hash '${hash}'`)
      }
    }

    return exports.hash[hash]
  }

  fail(`Hash must be a number or string. Received ${isNaN(hash) ? 'NaN' : typeof hash} (${hash})`)
}

exports.toCipher = function toCiper(cipher) {
  if (typeof cipher === 'number' && !isNaN(cipher)) return cipher

  if (typeof cipher === 'string') {
    cipher = cipher.replace(/-/g, '')

    if (cipher in exports.cipher === false) {
      cipher = cipher.toUpperCase()

      if (cipher in exports.cipher === false) {
        throw errors.UNKNOWN_CIPHER(`Unknown cipher '${cipher}'`)
      }
    }

    return exports.cipher[cipher]
  }

  fail(
    `Cipher must be a number or string. Received ${isNaN(cipher) ? 'NaN' : typeof cipher} (${cipher})`
  )
}

exports.toKeyType = function toKeyType(type) {
  if (typeof type === 'number' && !isNaN(type)) return type

  if (typeof type === 'string') {
    type = type.replace(/-/g, '')

    if (type in exports.keyType === false) {
      type = type.toUpperCase()

      if (type in exports.keyType === false) {
        throw errors.UNKNOWN_KEY_TYPE(`Unknown key type '${type}'`)
      }
    }

    return exports.keyType[type]
  }

  fail(
    `Key type must be a number or string. Received ${isNaN(type) ? 'NaN' : typeof type} (${type})`
  )
}
module.exports = class CryptoError extends Error {
  constructor(msg, fn = CryptoError, code = fn.name) {
    super(`${code}: ${msg}`)
    this.code = code

    if (Error.captureStackTrace) {
      Error.captureStackTrace(this, fn)
    }
  }

  get name() {
    return 'CryptoError'
  }

  static UNKNOWN_HASH(msg) {
    return new CryptoError(msg, CryptoError.UNKNOWN_HASH)
  }

  static UNKNOWN_CIPHER(msg) {
    return new CryptoError(msg, CryptoError.UNKNOWN_CIPHER)
  }

  static UNKNOWN_KEY_TYPE(msg) {
    return new CryptoError(msg, CryptoError.UNKNOWN_KEY_TYPE)
  }

  static INVALID_ACCESS(msg) {
    return new CryptoError(msg, CryptoError.INVALID_ACCESS)
  }

  static INVALID_DATA(msg) {
    return new CryptoError(msg, CryptoError.INVALID_DATA)
  }

  static OPERATION_ERROR(msg) {
    return new CryptoError(msg, CryptoError.OPERATION_ERROR)
  }

  static NOT_SUPPORTED(msg) {
    return new CryptoError(msg, CryptoError.NOT_SUPPORTED)
  }
}
const { Transform } = require('bare-stream')
const assert = require('bare-assert')
const binding = require('../binding')
const constants = require('./constants')

const {
  hash: { RIPEMD160 }
} = constants

class CryptoDigest {
  constructor(algorithm) {
    this._handle = binding.digestInit(algorithm)
  }

  update(data) {
    if (this._handle === null) {
      throw new Error('Digest has already been finalized')
    }

    binding.digestUpdate(this._handle, data.buffer, data.byteOffset, data.byteLength)
  }

  final() {
    if (this._handle === null) {
      throw new Error('Digest has already been finalized')
    }

    const result = Buffer.from(binding.digestFinal(this._handle))
    this._handle = null
    return result
  }
}

class CryptoRIPEMD160Digest {
  constructor() {
    this._handle = binding.ripemd160Init()
  }

  update(data) {
    if (this._handle === null) {
      throw new Error('Digest has already been finalized')
    }

    binding.ripemd160Update(this._handle, data.buffer, data.byteOffset, data.byteLength)
  }

  final() {
    if (this._handle === null) {
      throw new Error('Digest has already been finalized')
    }

    const result = Buffer.from(binding.ripemd160Final(this._handle))
    this._handle = null
    return result
  }
}

module.exports = class CryptoHash extends Transform {
  constructor(algorithm, opts = {}) {
    super(opts)

    algorithm = constants.toHash(algorithm)

    switch (algorithm) {
      case RIPEMD160:
        this._digest = new CryptoRIPEMD160Digest()
        break

      default:
        this._digest = new CryptoDigest(algorithm)
        break
    }
  }

  update(data, encoding = 'utf8') {
    if (typeof data === 'string') data = Buffer.from(data, encoding)

    assert(ArrayBuffer.isView(data))

    this._digest.update(data)

    return this
  }

  digest(encoding) {
    const digest = this._digest.final()

    return encoding && encoding !== 'buffer' ? digest.toString(encoding) : digest
  }

  _transform(data, encoding, cb) {
    this.update(data)

    cb(null)
  }

  _flush(cb) {
    this.push(this.digest())

    cb(null)
  }
}
const { Transform } = require('bare-stream')
const assert = require('bare-assert')
const binding = require('../binding')
const constants = require('./constants')

module.exports = class CryptoHmac extends Transform {
  constructor(algorithm, key, opts = {}) {
    super(opts)

    const { encoding = 'utf8' } = opts

    if (typeof key === 'string') key = Buffer.from(key, encoding)

    assert(ArrayBuffer.isView(key))

    this._finalized = false

    binding.hmacInit(this, constants.toHash(algorithm), key.buffer, key.byteOffset, key.byteLength)
  }

  update(data, encoding = 'utf8') {
    if (this._finalized) {
      throw new Error('Hmac has already been finalized')
    }

    if (typeof data === 'string') data = Buffer.from(data, encoding)

    assert(ArrayBuffer.isView(data))

    binding.hmacUpdate(this, data.buffer, data.byteOffset, data.byteLength)

    return this
  }

  digest(encoding) {
    if (this._finalized) {
      throw new Error('Hmac has already been finalized')
    }

    const digest = Buffer.from(binding.hmacFinal(this))
    this._finalized = true

    return encoding && encoding !== 'buffer' ? digest.toString(encoding) : digest
  }

  _transform(data, encoding, cb) {
    this.update(data)

    cb(null)
  }

  _flush(cb) {
    this.push(this.digest())

    cb(null)
  }
}
const binding = require('../binding')
const constants = require('./constants')

const {
  keyType: { ED25519 }
} = constants

class CryptoKey {
  constructor(keyType) {
    this._keyType = keyType
  }

  get type() {
    throw new Error('Not implemented')
  }

  get destroyed() {
    return false
  }

  export() {
    throw new Error('Not implemented')
  }

  destroy() {}

  [Symbol.dispose]() {
    this.destroy()
  }
}

exports.Key = CryptoKey

class CryptoEd25519Key extends CryptoKey {
  constructor(key = null) {
    super(ED25519)

    this._destroyed = false

    if (key === null) {
      binding.keyInit(this, new ArrayBuffer(0), 0, 0)
    } else if (ArrayBuffer.isView(key)) {
      binding.keyInit(this, key.buffer, key.byteOffset, key.byteLength)
    } else {
      binding.keyInit(this, key, 0, key.byteLength)
    }
  }

  get asymmetricKeyType() {
    return 'ed25519'
  }

  get destroyed() {
    return this._destroyed
  }

  export() {
    if (this._destroyed) {
      throw new Error('Key has been destroyed')
    }

    return Buffer.from(binding.keyExport(this))
  }

  destroy() {
    if (this._destroyed) return

    binding.keyDestroy(this)

    this._destroyed = true
  }
}

class CryptoEd25519PublicKey extends CryptoEd25519Key {
  get type() {
    return 'public'
  }
}

exports.Ed25519PublicKey = CryptoEd25519PublicKey

class CryptoEd25519PrivateKey extends CryptoEd25519Key {
  get type() {
    return 'private'
  }
}

exports.Ed25519PrivateKey = CryptoEd25519PrivateKey

class CryptoSecretKey extends CryptoKey {
  constructor(key) {
    super()

    this._destroyed = false

    if (ArrayBuffer.isView(key)) {
      binding.keyInit(this, key.buffer, key.byteOffset, key.byteLength)
    } else {
      binding.keyInit(this, key, 0, key.byteLength)
    }
  }

  get type() {
    return 'secret'
  }

  get destroyed() {
    return this._destroyed
  }

  export() {
    if (this._destroyed) {
      throw new Error('Key has been destroyed')
    }

    return Buffer.from(binding.keyExport(this))
  }

  destroy() {
    if (this._destroyed) return

    binding.keyDestroy(this)

    this._destroyed = true
  }
}

exports.SecretKey = CryptoSecretKey

exports.generateKeyPair = function generateKeyPair(type) {
  type = constants.toKeyType(type)

  switch (type) {
    case ED25519: {
      const publicKey = new CryptoEd25519PublicKey()
      const privateKey = new CryptoEd25519PrivateKey()

      binding.ed25519GenerateKeypair(publicKey, privateKey)

      return { publicKey, privateKey }
    }
  }
}
const assert = require('bare-assert')
const binding = require('../binding')
const constants = require('./constants')

module.exports = function pbkdf2(password, salt, iterations, keylen, digest, cb) {
  assert(typeof iterations === 'number' && !isNaN(iterations))

  if (iterations < 1 || iterations > 0xffffffff) {
    throw new RangeError('Invalid iterations')
  }

  assert(typeof keylen === 'number' && !isNaN(keylen))

  if (keylen < 1 || keylen > 0x7fffffff) {
    throw new RangeError('Invalid key length')
  }

  if (typeof password === 'string') password = Buffer.from(password)
  if (typeof salt === 'string') salt = Buffer.from(salt)

  assert(ArrayBuffer.isView(password))
  assert(ArrayBuffer.isView(salt))

  const buffer = Buffer.from(
    binding.pbkdf2(
      password.buffer,
      password.byteOffset,
      password.byteLength,
      salt.buffer,
      salt.byteOffset,
      salt.byteLength,
      iterations,
      constants.toHash(digest),
      keylen
    )
  )

  if (cb) queueMicrotask(() => cb(null, buffer))
  else return buffer
}
const assert = require('bare-assert')
const binding = require('../binding')

exports.randomBytes = function randomBytes(size, cb) {
  assert(typeof size === 'number' && !isNaN(size))

  const buffer = Buffer.allocUnsafe(size)
  exports.randomFill(buffer)
  if (cb) queueMicrotask(() => cb(null, buffer))
  else return buffer
}

exports.randomFill = function randomFill(buffer, offset, size, cb) {
  if (typeof offset === 'function') {
    cb = offset
    offset = undefined
  } else if (typeof size === 'function') {
    cb = size
    size = undefined
  }

  assert(buffer instanceof ArrayBuffer || ArrayBuffer.isView(buffer))
  assert(size === undefined || (typeof size === 'number' && !isNaN(size)))
  assert(offset === undefined || (typeof offset === 'number' && !isNaN(offset)))

  const elementSize = buffer.BYTES_PER_ELEMENT || 1

  if (offset === undefined) offset = 0
  else offset *= elementSize

  if (size === undefined) size = buffer.byteLength - offset
  else size *= elementSize

  if (offset < 0 || offset > buffer.byteLength) {
    throw new RangeError('offset is out of range')
  }

  if (size < 0 || size > buffer.byteLength) {
    throw new RangeError('size is out of range')
  }

  if (offset + size > buffer.byteLength) {
    throw new RangeError('offset + size is out of range')
  }

  let arraybuffer

  if (ArrayBuffer.isView(buffer)) {
    offset += buffer.byteOffset
    arraybuffer = buffer.buffer
  } else {
    arraybuffer = buffer
  }

  binding.randomFill(arraybuffer, offset, size)

  if (cb) queueMicrotask(() => cb(null, buffer))
  else return buffer
}

exports.randomUUID = function randomUUID() {
  const uuid = exports.randomBytes(16)

  uuid[6] = (uuid[6] & 0x0f) | 0x40
  uuid[8] = (uuid[8] & 0x3f) | 0x80

  return (
    uuid.subarray(0, 4).toString('hex') +
    '-' +
    uuid.subarray(4, 6).toString('hex') +
    '-' +
    uuid.subarray(6, 8).toString('hex') +
    '-' +
    uuid.subarray(8, 10).toString('hex') +
    '-' +
    uuid.subarray(10, 16).toString('hex')
  )
}
const assert = require('bare-assert')
const binding = require('../binding')
const { Key } = require('./key')
const constants = require('./constants')

const {
  keyType: { ED25519 }
} = constants

exports.sign = function sign(algorithm, data, key) {
  assert(data instanceof ArrayBuffer || ArrayBuffer.isView(data))

  if (ArrayBuffer.isView(data)) {
    data = Buffer.coerce(data)
  } else {
    data = Buffer.from(data)
  }

  assert(key instanceof Key)

  switch (key._keyType) {
    case ED25519:
      return Buffer.from(binding.ed25519Sign(data.buffer, data.byteOffset, data.byteLength, key))
  }
}

exports.verify = function verify(algorithm, data, key, signature) {
  assert(data instanceof ArrayBuffer || ArrayBuffer.isView(data))

  if (ArrayBuffer.isView(data)) {
    data = Buffer.coerce(data)
  } else {
    data = Buffer.from(data)
  }

  assert(signature instanceof ArrayBuffer || ArrayBuffer.isView(signature))

  if (ArrayBuffer.isView(signature)) {
    signature = Buffer.coerce(signature)
  } else {
    signature = Buffer.from(signature)
  }

  assert(key instanceof Key)

  switch (key._keyType) {
    case ED25519:
      assert(signature.byteLength === 64)

      return binding.ed25519Verify(
        data.buffer,
        data.byteOffset,
        data.byteLength,
        signature.buffer,
        signature.byteOffset,
        key
      )
  }
}
const assert = require('bare-assert')
const binding = require('../binding')

exports.timingSafeEqual = function timingSafeEqual(a, b) {
  assert(a instanceof ArrayBuffer || ArrayBuffer.isView(a))
  assert(b instanceof ArrayBuffer || ArrayBuffer.isView(b))

  let aBuffer, aOffset
  if (ArrayBuffer.isView(a)) {
    aBuffer = a.buffer
    aOffset = a.byteOffset
  } else {
    aBuffer = a
    aOffset = 0
  }

  let bBuffer, bOffset
  if (ArrayBuffer.isView(b)) {
    bBuffer = b.buffer
    bOffset = b.byteOffset
  } else {
    bBuffer = b
    bOffset = 0
  }

  return binding.timingSafeEqual(aBuffer, aOffset, a.byteLength, bBuffer, bOffset, b.byteLength)
}
const crypto = require('../../..')
const binding = require('../../../binding')
const errors = require('../../errors')
const { Ed25519PublicKey, Ed25519PrivateKey } = require('../../key')
const CryptoKey = require('../crypto-key')

// https://w3c.github.io/webcrypto/#ed25519

// https://w3c.github.io/webcrypto/#ed25519-operations-sign
exports.sign = function sign(algorithm, key, data) {
  if (key.type !== 'private') {
    throw errors.INVALID_ACCESS('Must pass private key for Ed25519 signing')
  }

  const signature = crypto.sign(null, data, key._handle)

  return signature.buffer.slice(0, signature.byteLength)
}

// https://w3c.github.io/webcrypto/#ed25519-operations-verify
exports.verify = function verify(algorithm, key, signature, data) {
  if (key.type !== 'public') {
    throw errors.INVALID_ACCESS('Must pass public key for Ed25519 verification')
  }

  return crypto.verify(null, data, key._handle, signature)
}

// https://w3c.github.io/webcrypto/#ed25519-operations-generate-key
exports.generateKey = function generateKey(algorithm, extractable, usages) {
  for (const usage of usages) {
    if (usage !== 'sign' && usage !== 'verify') {
      throw new SyntaxError(
        `Usage '${usage}' cannot be used for the Ed25519 generateKey() operation`
      )
    }
  }

  const keys = crypto.generateKeyPair('ed25519')

  algorithm = { name: 'Ed25519' }

  return {
    publicKey: new CryptoKey('public', true, algorithm, ['verify'], keys.publicKey),
    privateKey: new CryptoKey('private', extractable, algorithm, ['sign'], keys.privateKey)
  }
}

// https://w3c.github.io/webcrypto/#ed25519-operations-import-key
exports.importKey = function importKey(format, keyData, algorithm, extractable, usages) {
  switch (format) {
    case 'spki': {
      for (const usage of usages) {
        if (usage !== 'verify') {
          throw new SyntaxError(
            `Usage '${usage}' cannot be used for the Ed25519 importKey() operation`
          )
        }
      }

      const publicKey = new Ed25519PublicKey()

      binding.ed25519FromSPKI(publicKey, keyData.buffer, keyData.byteOffset, keyData.byteLength)

      return new CryptoKey(
        'public',
        extractable,
        {
          name: 'Ed25519'
        },
        usages,
        publicKey
      )
    }
    case 'pkcs8': {
      for (const usage of usages) {
        if (usage !== 'sign') {
          throw new SyntaxError(
            `Usage '${usage}' cannot be used for the Ed25519 importKey() operation`
          )
        }
      }

      const privateKey = new Ed25519PrivateKey()

      binding.ed25519FromPKCS8(privateKey, keyData.buffer, keyData.byteOffset, keyData.byteLength)

      return new CryptoKey(
        'private',
        extractable,
        {
          name: 'Ed25519'
        },
        usages,
        privateKey
      )
    }
    case 'raw':
      for (const usage of usages) {
        if (usage !== 'verify') {
          throw new SyntaxError(
            `Usage '${usage}' cannot be used for the Ed25519 importKey() operation`
          )
        }
      }

      if (keyData.byteLength * 8 !== 256) {
        throw errors.INVALID_DATA('Key must be 256 bits')
      }

      return new CryptoKey(
        'public',
        extractable,
        {
          name: 'Ed25519'
        },
        usages,
        new Ed25519PublicKey(keyData.buffer)
      )
    case 'jwk': {
      const jwk = keyData

      if ('d' in jwk) {
        if (usages.some((usage) => usage !== 'sign')) {
          throw new SyntaxError('JWK must be valid for signing')
        }
      } else {
        if (usages.some((usage) => usage !== 'verify')) {
          throw new SyntaxError('JWK must be valid for verification')
        }
      }

      if (jwk.kty !== 'OKP') {
        throw errors.INVALID_DATA('JWK key must be an octet key-pair')
      }

      if (jwk.crv !== 'Ed25519') {
        throw errors.INVALID_DATA('JWK must use the Ed25519 curve')
      }

      if ('alg' in jwk && jwk.alg !== 'Ed25519' && jwk.alg !== 'EdDSA') {
        throw errors.INVALID_DATA('JWK must use the Ed25519 curve')
      }

      if (usages.length && 'use' in jwk && jwk.use !== 'sig') {
        throw errors.INVALID_DATA('JWK cannot be used for signatures')
      }

      if ('ext' in jwk && jwk.ext !== extractable && extractable) {
        throw errors.INVALID_DATA('JWK is not extractable')
      }

      if ('d' in jwk) {
        const key = Buffer.concat([
          Buffer.from(jwk.d, 'base64url'),
          Buffer.from(jwk.x, 'base64url')
        ])

        if (key.byteLength * 8 !== 512) {
          throw errors.INVALID_DATA('Key must be 512 bits')
        }

        const handle = new Ed25519PrivateKey(key.buffer)

        key.fill(0)

        return new CryptoKey(
          'private',
          extractable,
          {
            name: 'Ed25519'
          },
          usages,
          handle
        )
      }

      const key = Buffer.from(jwk.x, 'base64url')

      if (key.byteLength * 8 !== 256) {
        throw errors.INVALID_DATA('Key must be 256 bits')
      }

      return new CryptoKey(
        'public',
        extractable,
        {
          name: 'Ed25519'
        },
        usages,
        new Ed25519PublicKey(key.buffer)
      )
    }
    default:
      throw errors.NOT_SUPPORTED(
        `Format '${format}' cannot be used for the Ed25519 importKey() operation`
      )
  }
}

// https://w3c.github.io/webcrypto/#ed25519-operations-export-key
exports.exportKey = function exportKey(format, key) {
  const data = key._handle

  switch (format) {
    case 'spki':
      if (key.type !== 'public') {
        throw errors.INVALID_ACCESS(
          `Key of type '${key.type}' cannot be used for the Ed25519 exportKey() operation`
        )
      }

      return binding.ed25519ToSPKI(data)
    case 'pkcs8':
      if (key.type !== 'private') {
        throw errors.INVALID_ACCESS(
          `Key of type '${key.type}' cannot be used for the Ed25519 exportKey() operation`
        )
      }

      return binding.ed25519ToPKCS8(data)
    case 'raw': {
      if (key.type !== 'public') {
        throw errors.INVALID_ACCESS(
          `Key of type '${key.type}' cannot be used for the Ed25519 exportKey() operation`
        )
      }

      return data.export().buffer
    }
    case 'jwk': {
      const buffer = data.export()

      if (key.type === 'private') {
        const d = buffer.subarray(0, 32).toString('base64url')
        const x = buffer.subarray(32).toString('base64url')

        return {
          kty: 'OKP',
          alg: 'Ed25519',
          crv: 'Ed25519',
          x,
          d,
          key_ops: key.usages,
          ext: key.extractable
        }
      }

      return {
        kty: 'OKP',
        alg: 'Ed25519',
        crv: 'Ed25519',
        x: buffer.toString('base64url'),
        key_ops: key.usages,
        ext: key.extractable
      }
    }
    default:
      throw errors.NOT_SUPPORTED(
        `Format '${format}' cannot be used for the HMAC exportKey() operation`
      )
  }
}
const crypto = require('../../..')
const errors = require('../../errors')
const { SecretKey } = require('../../key')
const CryptoKey = require('../crypto-key')

// https://w3c.github.io/webcrypto/#hmac

// https://w3c.github.io/webcrypto/#hmac-operations-sign
exports.sign = function sign(algorithm, key, data) {
  const material = key._handle.export()

  const digest = crypto.createHmac(key.algorithm.hash.name, material).update(data).digest()

  return digest.buffer.slice(0, digest.byteLength)
}

// https://w3c.github.io/webcrypto/#hmac-operations-verify
exports.verify = function verify(algorithm, key, signature, data) {
  const material = key._handle.export()

  const digest = crypto.createHmac(key.algorithm.hash.name, material).update(data).digest()

  if (ArrayBuffer.isView(signature)) {
    signature = Buffer.coerce(signature)
  } else {
    signature = Buffer.from(signature)
  }

  if (signature.byteLength !== digest.byteLength) return false

  return crypto.timingSafeEqual(signature, digest)
}

// https://w3c.github.io/webcrypto/#hmac-operations-generate-key
exports.generateKey = function generateKey(algorithm, extractable, usages) {
  for (const usage of usages) {
    if (usage !== 'sign' && usage !== 'verify') {
      throw new SyntaxError(`Usage '${usage}' cannot be used for the HMAC generateKey() operation`)
    }
  }

  const length = exports.getKeyLength(algorithm)

  let hash = algorithm.hash

  if (typeof hash === 'string') hash = { name: hash }

  const key = crypto.randomBytes(length / 8)

  const handle = new SecretKey(key)

  key.fill(0)

  return new CryptoKey(
    'secret',
    extractable,
    {
      name: 'HMAC',
      length,
      hash: {
        name: hash.name.toUpperCase()
      }
    },
    usages,
    handle
  )
}

// https://w3c.github.io/webcrypto/#hmac-operations-import-key
exports.importKey = function importKey(format, keyData, algorithm, extractable, usages) {
  for (const usage of usages) {
    if (usage !== 'sign' && usage !== 'verify') {
      throw new SyntaxError(`Invalid usage ${usage}`)
    }
  }

  let hash = algorithm.hash

  if (typeof hash === 'string') hash = { name: hash }

  let data
  let owned = false

  switch (format) {
    case 'raw':
      data = keyData
      break
    case 'jwk': {
      const jwk = keyData

      if (jwk.kty !== 'oct') {
        throw errors.INVALID_DATA('JWK key must be an octet sequence')
      }

      data = Buffer.from(jwk.k, 'base64url')
      owned = true

      switch (hash.name.toLowerCase()) {
        case 'sha-1':
          if (jwk.alg === 'HS1') break
          else throw errors.INVALID_DATA('Invalid JWK key algorithm')
        case 'sha-256':
          if (jwk.alg === 'HS256') break
          else throw errors.INVALID_DATA('Invalid JWK key algorithm')
        case 'sha-384':
          if (jwk.alg === 'HS384') break
          else throw errors.INVALID_DATA('Invalid JWK key algorithm')
        case 'sha-512':
          if (jwk.alg === 'HS512') break
          else throw errors.INVALID_DATA('Invalid JWK key algorithm')
      }

      if (usages.length && 'use' in jwk && jwk.use !== 'sign') {
        throw errors.INVALID_DATA('JWK cannot be used for signing')
      }

      if ('ext' in jwk && jwk.ext !== extractable && extractable) {
        throw errors.INVALID_DATA('JWK is not extractable')
      }
      break
    }
    default:
      throw errors.NOT_SUPPORTED(
        `Format '${format}' cannot be used for the HMAC importKey() operation`
      )
  }

  const length = data.byteLength * 8

  if (length === 0) {
    throw errors.INVALID_DATA('Key cannot be empty')
  }

  const handle = new SecretKey(data)

  if (owned) data.fill(0)

  return new CryptoKey(
    'secret',
    extractable,
    {
      name: 'HMAC',
      length,
      hash: {
        name: hash.name.toUpperCase()
      }
    },
    usages,
    handle
  )
}

// https://w3c.github.io/webcrypto/#hmac-operations-export-key
exports.exportKey = function exportKey(format, key) {
  const data = key._handle.export()

  switch (format) {
    case 'raw':
      return data.buffer.slice(0, data.byteLength)
    case 'jwk': {
      const jwk = {
        kty: 'oct',
        k: data.toString('base64url'),
        alg: null,
        key_ops: key.usages,
        ext: key.extractable
      }

      switch (key.algorithm.hash.name) {
        case 'SHA-1':
          jwk.alg = 'HS1'
          break
        case 'SHA-256':
          jwk.alg = 'HS256'
          break
        case 'SHA-384':
          jwk.alg = 'HS384'
          break
        case 'SHA-512':
          jwk.alg = 'HS512'
          break
      }

      return jwk
    }
    default:
      throw errors.NOT_SUPPORTED(
        `Format '${format}' cannot be used for the HMAC exportKey() operation`
      )
  }
}

// https://w3c.github.io/webcrypto/#hmac-operations-get-key-length
exports.getKeyLength = function getKeyLength(algorithm) {
  const { length } = algorithm

  let { hash } = algorithm

  if (hash !== null && typeof hash === 'object') hash = hash.name

  if (length === undefined) {
    if (hash === 'SHA-1' || hash === 'SHA-256') return 512
    if (hash === 'SHA-384' || hash === 'SHA-512') return 1024

    throw errors.OPERATION_ERROR(`Invalid hash '${hash}'`)
  }

  if (length === 0) {
    throw errors.OPERATION_ERROR(`Invalid length ${length}`)
  }

  return length
}
const crypto = require('../../..')
const errors = require('../../errors')
const { SecretKey } = require('../../key')
const CryptoKey = require('../crypto-key')

// https://w3c.github.io/webcrypto/#pbkdf2

// https://w3c.github.io/webcrypto/#pbkdf2-operations-derive-bits
exports.deriveBits = function deriveBits(algorithm, key, length) {
  if (length === undefined || length % 8) {
    throw errors.OPERATION_ERROR('Length must be multiple of 8')
  }

  if (algorithm.iterations === 0) {
    throw errors.OPERATION_ERROR('Iterations must be non-0')
  }

  if (length === 0) {
    return new ArrayBuffer(0)
  }

  let hash = algorithm.hash

  if (typeof hash === 'string') hash = { name: hash }

  const material = key._handle.export()

  const result = crypto.pbkdf2(
    material,
    algorithm.salt,
    algorithm.iterations,
    length / 8,
    hash.name
  )

  return result.buffer
}

// https://w3c.github.io/webcrypto/#pbkdf2-operations-import-key
exports.importKey = function importKey(format, keyData, algorithm, extractable, usages) {
  if (format !== 'raw') {
    throw errors.NOT_SUPPORTED(
      `Format '${format}' cannot be used for the PBKDF2 importKey() operation`
    )
  }

  for (const usage of usages) {
    if (usage !== 'deriveKey' && usage !== 'deriveBits') {
      throw new SyntaxError(`Invalid usage ${usage}`)
    }
  }

  if (extractable) {
    throw new SyntaxError('Extractable must be false')
  }

  return new CryptoKey(
    'secret',
    extractable,
    {
      name: 'PBKDF2'
    },
    usages,
    new SecretKey(keyData)
  )
}
const crypto = require('../../..')

// https://w3c.github.io/webcrypto/#sha

// https://w3c.github.io/webcrypto/#sha-operations-digest
exports.digest = function digest(name, data) {
  const digest = crypto.createHash(name).update(data).digest()

  return digest.buffer.slice(0, digest.byteLength)
}
// https://w3c.github.io/webcrypto/#cryptokey-interface
module.exports = class CryptoKey {
  constructor(type, extractable, algorithm, usages, handle = null) {
    this._type = type
    this._extractable = extractable
    this._algorithm = algorithm
    this._usages = usages
    this._handle = handle
  }

  // https://w3c.github.io/webcrypto/#dom-cryptokey-type
  get type() {
    return this._type
  }

  // https://w3c.github.io/webcrypto/#dom-cryptokey-extractable
  get extractable() {
    return this._extractable
  }

  // https://w3c.github.io/webcrypto/#dom-cryptokey-algorithm
  get algorithm() {
    return this._algorithm
  }

  // https://w3c.github.io/webcrypto/#dom-cryptokey-usages
  get usages() {
    return this._usages
  }

  get destroyed() {
    return this._handle === null
  }

  destroy() {
    if (this._handle === null) return

    this._handle.destroy()

    this._handle = null
  }

  [Symbol.dispose]() {
    this.destroy()
  }

  [Symbol.for('bare.inspect')]() {
    return {
      __proto__: { constructor: CryptoKey },

      type: this.type,
      extractable: this.extractable,
      algorithm: this.algorithm,
      usages: this.usages
    }
  }
}
{
  "name": "bare-crypto",
  "version": "1.15.3",
  "description": "Cryptographic primitives for JavaScript",
  "exports": {
    "./package": "./package.json",
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    },
    "./web": {
      "types": "./web.d.ts",
      "default": "./web.js"
    },
    "./global": {
      "types": "./global.d.ts",
      "default": "./global.js"
    },
    "./constants": "./lib/constants.js",
    "./errors": "./lib/errors.js"
  },
  "files": [
    "index.js",
    "index.d.ts",
    "web.js",
    "web.d.ts",
    "global.js",
    "global.d.ts",
    "binding.c",
    "binding.js",
    "CMakeLists.txt",
    "lib",
    "prebuilds"
  ],
  "addon": true,
  "scripts": {
    "format": "prettier --write . && lunte --fix",
    "lint": "prettier --check . && lunte",
    "test": "brittle-bare --coverage test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-crypto.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-crypto/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-crypto#readme",
  "dependencies": {
    "bare-assert": "^1.2.0",
    "bare-stream": "^2.6.3"
  },
  "devDependencies": {
    "bare-buffer": "^3.0.1",
    "brittle": "^3.5.0",
    "cmake-bare": "^1.1.6",
    "cmake-fetch": "^1.0.0",
    "lunte": "^1.7.0",
    "prettier": "^3.4.2",
    "prettier-config-holepunch": "^2.0.0"
  },
  "peerDependencies": {
    "bare-buffer": "*"
  },
  "peerDependenciesMeta": {
    "bare-buffer": {
      "optional": true
    }
  }
}
const crypto = require('.')
const errors = require('./lib/errors')
const CryptoKey = require('./lib/web/crypto-key')
const hmac = require('./lib/web/algorithm/hmac')
const pbkdf2 = require('./lib/web/algorithm/pbkdf2')
const ed25519 = require('./lib/web/algorithm/ed25519')
const sha = require('./lib/web/algorithm/sha')

exports.CryptoKey = CryptoKey

// https://w3c.github.io/webcrypto/#Crypto-method-getRandomValues
exports.getRandomValues = function getRandomValues(array) {
  return crypto.randomFillSync(array)
}

// https://w3c.github.io/webcrypto/#dfn-Crypto-method-randomUUID
exports.randomUUID = crypto.randomUUID

// https://w3c.github.io/webcrypto/#subtlecrypto-interface
exports.SubtleCrypto = class SubtleCrypto {
  // https://w3c.github.io/webcrypto/#SubtleCrypto-method-generateKey
  async generateKey(algorithm, extractable, usages) {
    if (typeof algorithm === 'string') algorithm = { name: algorithm }

    switch (algorithm.name.toLowerCase()) {
      case 'hmac':
        return hmac.generateKey(algorithm, extractable, usages)
      case 'ed25519':
        return ed25519.generateKey(algorithm, extractable, usages)
      default:
        throw errors.NOT_SUPPORTED(
          `Algorithm '${algorithm.name}' does not support the generateKey() operation`
        )
    }
  }

  // https://w3c.github.io/webcrypto/#SubtleCrypto-method-importKey
  async importKey(format, keyData, algorithm, extractable, usages) {
    if (typeof algorithm === 'string') algorithm = { name: algorithm }

    switch (format) {
      case 'raw':
      case 'pkcs8':
      case 'spki':
        if (ArrayBuffer.isView(keyData)) {
          keyData = Buffer.from(keyData)
        } else {
          keyData = Buffer.from(keyData.slice())
        }
        break
    }

    switch (algorithm.name.toLowerCase()) {
      case 'hmac':
        return hmac.importKey(format, keyData, algorithm, extractable, usages)
      case 'ed25519':
        return ed25519.importKey(format, keyData, algorithm, extractable, usages)
      case 'pbkdf2':
        return pbkdf2.importKey(format, keyData, algorithm, extractable, usages)
      default:
        throw errors.NOT_SUPPORTED(
          `Algorithm '${algorithm.name}' does not support the importKey() operation`
        )
    }
  }

  // https://w3c.github.io/webcrypto/#SubtleCrypto-method-exportKey
  async exportKey(format, key) {
    if (!key.extractable) {
      throw errors.INVALID_ACCESS('Key is not extractable')
    }

    switch (key.algorithm.name.toLowerCase()) {
      case 'hmac':
        return hmac.exportKey(format, key)
      case 'ed25519':
        return ed25519.exportKey(format, key)
      default:
        throw errors.NOT_SUPPORTED(
          `Algorithm '${key.algorithm.name}' does not support the exportKey() operation`
        )
    }
  }

  // https://w3c.github.io/webcrypto/#SubtleCrypto-method-sign
  async sign(algorithm, key, data) {
    if (typeof algorithm === 'string') algorithm = { name: algorithm }

    if (algorithm.name.toLowerCase() !== key.algorithm.name.toLowerCase()) {
      throw errors.INVALID_ACCESS(`Algorithm '${algorithm.name}' does not match key'`)
    }

    if (!key.usages.includes('sign')) {
      throw errors.INVALID_ACCESS('Key cannot be used for signing')
    }

    switch (algorithm.name.toLowerCase()) {
      case 'hmac':
        return hmac.sign(algorithm, key, data)
      case 'ed25519':
        return ed25519.sign(algorithm, key, data)
      default:
        throw errors.NOT_SUPPORTED(
          `Algorithm '${algorithm.name}' does not support the sign() operation`
        )
    }
  }

  // https://w3c.github.io/webcrypto/#SubtleCrypto-method-verify
  async verify(algorithm, key, signature, data) {
    if (typeof algorithm === 'string') algorithm = { name: algorithm }

    if (algorithm.name.toLowerCase() !== key.algorithm.name.toLowerCase()) {
      throw errors.INVALID_ACCESS(`Algorithm '${algorithm.name}' does not match key'`)
    }

    if (!key.usages.includes('verify')) {
      throw errors.INVALID_ACCESS('Key cannot be used for verification')
    }

    switch (algorithm.name.toLowerCase()) {
      case 'hmac':
        return hmac.verify(algorithm, key, signature, data)
      case 'ed25519':
        return ed25519.verify(algorithm, key, signature, data)
      default:
        throw errors.NOT_SUPPORTED(
          `Algorithm '${algorithm.name}' does not support the verify() operation`
        )
    }
  }

  // https://w3c.github.io/webcrypto/#SubtleCrypto-method-deriveBits
  async deriveBits(algorithm, key, length) {
    if (typeof algorithm === 'string') algorithm = { name: algorithm }

    if (algorithm.name.toLowerCase() !== key.algorithm.name.toLowerCase()) {
      throw errors.INVALID_ACCESS(`Algorithm '${algorithm.name}' does not match key'`)
    }

    if (!key.usages.includes('deriveBits')) {
      throw errors.INVALID_ACCESS('Key cannot be used to derive bits')
    }

    switch (algorithm.name.toLowerCase()) {
      case 'pbkdf2':
        return pbkdf2.deriveBits(algorithm, key, length)
      default:
        throw errors.NOT_SUPPORTED(
          `Algorithm '${algorithm.name}' does not support the deriveBits() operation`
        )
    }
  }

  // https://w3c.github.io/webcrypto/#SubtleCrypto-method-deriveKey
  async deriveKey(algorithm, baseKey, derivedKeyType, extractable, usages) {
    if (typeof algorithm === 'string') algorithm = { name: algorithm }

    if (typeof derivedKeyType === 'string') {
      derivedKeyType = { name: derivedKeyType }
    }

    if (algorithm.name.toLowerCase() !== baseKey.algorithm.name.toLowerCase()) {
      throw errors.INVALID_ACCESS(`Algorithm '${algorithm.name}' does not match key'`)
    }

    if (!baseKey.usages.includes('deriveKey')) {
      throw errors.INVALID_ACCESS('Key cannot be used to derive key')
    }

    let length

    switch (derivedKeyType.name.toLowerCase()) {
      case 'hmac':
        length = hmac.getKeyLength(derivedKeyType)
        break
      default:
        throw errors.NOT_SUPPORTED(
          `Algorithm '${derivedKeyType.name}' does not support the getKeyLength() operation`
        )
    }

    let secret

    switch (algorithm.name.toLowerCase()) {
      case 'pbkdf2':
        secret = pbkdf2.deriveBits(algorithm, baseKey, length)
        break
      default:
        throw errors.NOT_SUPPORTED(
          `Algorithm '${algorithm.name}' does not support the deriveBits() operation`
        )
    }

    return this.importKey('raw', secret, derivedKeyType, extractable, usages)
  }

  // https://w3c.github.io/webcrypto/#SubtleCrypto-method-digest
  async digest(algorithm, data) {
    if (typeof algorithm === 'string') algorithm = { name: algorithm }

    switch (algorithm.name.toLowerCase()) {
      case 'sha-1':
        return sha.digest(crypto.constants.hash.SHA1, data)
      case 'sha-256':
        return sha.digest(crypto.constants.hash.SHA256, data)
      case 'sha-384':
        return sha.digest(crypto.constants.hash.SHA384, data)
      case 'sha-512':
        return sha.digest(crypto.constants.hash.SHA512, data)
      default:
        throw errors.NOT_SUPPORTED(
          `Algorithm '${algorithm.name}' does not support the digest() operation`
        )
    }
  }
}

exports.subtle = new exports.SubtleCrypto()

// https://w3c.github.io/webcrypto/#crypto-interface
exports.Crypto = class Crypto {
  get subtle() {
    return exports.subtle
  }

  getRandomValues(array) {
    return exports.getRandomValues(array)
  }

  randomUUID() {
    return exports.randomUUID()
  }
}
const errors = require('./lib/errors')

class EventListener {
  constructor() {
    this.list = []
    this.count = 0
  }

  append(ctx, name, fn, once) {
    this.count++
    ctx.emit('newListener', name, fn) // Emit BEFORE adding
    this.list.push([fn, once, false])
  }

  prepend(ctx, name, fn, once) {
    this.count++
    ctx.emit('newListener', name, fn) // Emit BEFORE adding
    this.list.unshift([fn, once, false])
  }

  remove(ctx, name, fn) {
    for (let i = this.list.length - 1; i >= 0; i--) {
      if (this.list[i][0] === fn) return this.removeAt(ctx, name, i)
    }
  }

  removeAt(ctx, name, i) {
    const fn = this.list[i][0]

    this.list.splice(i, 1)

    if (this.count === 1) delete ctx._events[name]

    ctx.emit('removeListener', name, fn) // Emit AFTER removing

    this.count--
  }

  removeAll(ctx, name) {
    const list = [...this.list]
    this.list = []

    if (this.count === list.length) delete ctx._events[name]

    for (let i = list.length - 1; i >= 0; i--) {
      ctx.emit('removeListener', name, list[i][0]) // Emit AFTER removing
    }

    this.count -= list.length
  }

  emit(ctx, name, ...args) {
    const list = [...this.list]

    for (let i = 0, n = list.length; i < n; i++) {
      const l = list[i]

      if (l[1] === true) {
        // A reentrant emit may already have fired this listener, in which case
        // it must not be fired again.
        if (l[2] === true) continue

        l[2] = true

        // Remove this entry, rather than the first one with the same listener,
        // as the same listener may have been added more than once.
        const j = this.list.indexOf(l)

        if (j !== -1) this.removeAt(ctx, name, j)
      }

      Reflect.apply(l[0], ctx, args)
    }

    return list.length > 0
  }
}

function appendListener(ctx, name, fn, once) {
  if (ctx._events === undefined) ctx._events = Object.create(null)
  const e = ctx._events[name] || (ctx._events[name] = new EventListener())
  e.append(ctx, name, fn, once)
  return ctx
}

function prependListener(ctx, name, fn, once) {
  if (ctx._events === undefined) ctx._events = Object.create(null)
  const e = ctx._events[name] || (ctx._events[name] = new EventListener())
  e.prepend(ctx, name, fn, once)
  return ctx
}

function removeListener(ctx, name, fn) {
  if (ctx._events === undefined) return ctx
  const e = ctx._events[name]
  if (e !== undefined) e.remove(ctx, name, fn)
  return ctx
}

function throwUnhandledError(...args) {
  let err

  if (args.length > 0) err = args[0]

  if (err instanceof Error === false) {
    err = errors.UNHANDLED_ERROR(err)

    if (Error.captureStackTrace) Error.captureStackTrace(err, exports.prototype.emit)
  }

  queueMicrotask(() => {
    throw err
  })
}

module.exports = exports = class EventEmitter {
  constructor() {
    this._events = Object.create(null)
  }

  addListener(name, fn) {
    return appendListener(this, name, fn, false)
  }

  addOnceListener(name, fn) {
    return appendListener(this, name, fn, true)
  }

  prependListener(name, fn) {
    return prependListener(this, name, fn, false)
  }

  prependOnceListener(name, fn) {
    return prependListener(this, name, fn, true)
  }

  removeListener(name, fn) {
    return removeListener(this, name, fn)
  }

  on(name, fn) {
    return appendListener(this, name, fn, false)
  }

  once(name, fn) {
    return appendListener(this, name, fn, true)
  }

  off(name, fn) {
    return removeListener(this, name, fn)
  }

  emit(name, ...args) {
    if (name === 'error' && (this._events === undefined || this._events.error === undefined)) {
      throwUnhandledError(...args)
    }

    if (this._events === undefined) return false
    const e = this._events[name]
    return e === undefined ? false : e.emit(this, name, ...args)
  }

  listeners(name) {
    if (this._events === undefined) return []
    const e = this._events[name]
    return e === undefined ? [] : e.list.map((l) => l[0])
  }

  rawListeners(name) {
    if (this._events === undefined) return []
    const e = this._events[name]
    return e === undefined ? [] : e.list.map((l) => l[0])
  }

  eventNames() {
    if (this._events === undefined) return []
    return Reflect.ownKeys(this._events)
  }

  listenerCount(name) {
    if (this._events === undefined) return 0
    const e = this._events[name]
    return e === undefined ? 0 : e.list.length
  }

  getMaxListeners() {
    return EventEmitter.defaultMaxListeners
  }

  setMaxListeners(n) {
    return this
  }

  removeAllListeners(name) {
    if (this._events === undefined) return this

    if (arguments.length === 0) {
      for (const key of Reflect.ownKeys(this._events)) {
        if (key === 'removeListener') continue
        this.removeAllListeners(key)
      }
      this.removeAllListeners('removeListener')
    } else {
      const e = this._events[name]
      if (e !== undefined) e.removeAll(this, name)
    }
    return this
  }
}

exports.EventEmitter = exports

exports.errors = errors

exports.defaultMaxListeners = 10

exports.on = function on(emitter, name, opts = {}) {
  const { signal } = opts

  if (signal && signal.aborted) {
    throw errors.OPERATION_ABORTED(signal.reason)
  }

  let error = null
  let done = false

  const events = []
  const promises = []

  if (name !== 'error') emitter.on('error', onerror)

  if (signal) signal.addEventListener('abort', onabort)

  emitter.on(name, onevent)

  return {
    next() {
      if (events.length) {
        return Promise.resolve({ value: events.shift(), done: false })
      }

      if (error) {
        const err = error

        error = null

        return Promise.reject(err)
      }

      if (done) return onclose()

      return new Promise((resolve, reject) => promises.push({ resolve, reject }))
    },

    return() {
      return onclose()
    },

    throw(err) {
      return onerror(err)
    },

    [Symbol.asyncIterator]() {
      return this
    }
  }

  function onevent(...args) {
    if (promises.length) {
      promises.shift().resolve({ value: args, done: false })
    } else {
      events.push(args)
    }
  }

  function onerror(err) {
    if (promises.length) {
      promises.shift().reject(err)
    } else {
      error = err
    }

    return onclose()
  }

  function onabort() {
    return onerror(errors.OPERATION_ABORTED(signal.reason))
  }

  function onclose() {
    emitter.off(name, onevent)

    if (name !== 'error') emitter.off('error', onerror)

    if (signal) signal.removeEventListener('abort', onabort)

    done = true

    const result = { done: true }

    while (promises.length) promises.shift().resolve(result)

    return Promise.resolve(result)
  }
}

exports.once = function once(emitter, name, opts = {}) {
  const { signal } = opts

  if (signal && signal.aborted) {
    return Promise.reject(errors.OPERATION_ABORTED(signal.reason))
  }

  return new Promise((resolve, reject) => {
    if (name !== 'error') emitter.on('error', onerror)

    if (signal) signal.addEventListener('abort', onabort)

    emitter.once(name, onevent)

    function onevent(...args) {
      if (name !== 'error') emitter.off('error', onerror)

      if (signal) signal.removeEventListener('abort', onabort)

      resolve(args)
    }

    function onerror(err) {
      emitter.off(name, onevent)

      if (name !== 'error') emitter.off('error', onerror)

      if (signal) signal.removeEventListener('abort', onabort)

      reject(err)
    }

    function onabort() {
      onerror(errors.OPERATION_ABORTED(signal.reason))
    }
  })
}

exports.forward = function forward(from, to, names, opts = {}) {
  if (Array.isArray(names) === false) names = [names]

  const { emit = to.emit.bind(to) } = opts

  const listeners = names.map(
    (name) =>
      function onevent(...args) {
        emit(name, ...args)
      }
  )

  for (let i = 0, n = names.length; i < n; i++) {
    if (to.listenerCount(names[i]) > 0) from.on(names[i], listeners[i])
  }

  to.on('newListener', (name) => {
    const i = names.indexOf(name)

    if (i !== -1 && to.listenerCount(name) === 0) {
      from.on(name, listeners[i])
    }
  }).on('removeListener', (name) => {
    const i = names.indexOf(name)

    if (i !== -1 && to.listenerCount(name) === 0) {
      from.off(name, listeners[i])
    }
  })
}

exports.listenerCount = function listenerCount(emitter, name) {
  return emitter.listenerCount(name)
}

exports.getMaxListeners = function getMaxListeners(emitter) {
  if (typeof emitter.getMaxListeners === 'function') {
    return emitter.getMaxListeners()
  }

  return exports.defaultMaxListeners
}

exports.setMaxListeners = function setMaxListeners(n, ...emitters) {
  if (emitters.length === 0) exports.defaultMaxListeners = n
  else {
    for (const emitter of emitters) {
      if (typeof emitter.setMaxListeners === 'function') {
        emitter.setMaxListeners(n)
      }
    }
  }
}
module.exports = class EventEmitterError extends Error {
  constructor(msg, fn = EventEmitterError, opts = {}) {
    const { code = fn.name } = opts

    super(`${code}: ${msg}`, opts)

    this.code = code

    if (Error.captureStackTrace) Error.captureStackTrace(this, fn)
  }

  get name() {
    return 'EventEmitterError'
  }

  static OPERATION_ABORTED(cause, msg = 'Operation aborted') {
    return new EventEmitterError(msg, EventEmitterError.OPERATION_ABORTED, { cause })
  }

  static UNHANDLED_ERROR(cause, msg = 'Unhandled error') {
    return new EventEmitterError(msg, EventEmitterError.UNHANDLED_ERROR, { cause })
  }
}
{
  "name": "bare-events",
  "version": "2.9.2",
  "description": "Event emitters for JavaScript",
  "exports": {
    "./package": "./package.json",
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    },
    "./global": {
      "types": "./global.d.ts",
      "default": "./global.js"
    },
    "./web": {
      "types": "./web.d.ts",
      "default": "./web.js"
    },
    "./errors": "./lib/errors.js"
  },
  "files": [
    "index.js",
    "index.d.ts",
    "global.js",
    "global.d.ts",
    "web.js",
    "web.d.ts",
    "lib"
  ],
  "scripts": {
    "format": "prettier --write . && lunte --fix",
    "lint": "prettier --check . && lunte",
    "test": "npm run test:bare && npm run test:node",
    "test:bare": "brittle-bare --coverage test.js",
    "test:node": "brittle-node --coverage test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-events.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-events/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-events#readme",
  "devDependencies": {
    "bare-abort-controller": "^1.0.0",
    "brittle": "^3.3.2",
    "lunte": "^1.8.0",
    "prettier": "^3.4.2",
    "prettier-config-holepunch": "^2.0.0",
    "uncaughts": "^1.1.1"
  },
  "peerDependencies": {
    "bare-abort-controller": "*"
  },
  "peerDependenciesMeta": {
    "bare-abort-controller": {
      "optional": true
    }
  }
}
module.exports = require.addon()
const FIFO = require('fast-fifo')
const EventEmitter = require('bare-events')
const path = require('bare-path')
const { isURL, fileURLToPath } = require('bare-url')
const { Readable, Writable } = require('bare-stream')
const binding = require('./binding')
const constants = require('./lib/constants')
const FileError = require('./lib/errors')

const isWindows = Bare.platform === 'win32'

exports.constants = constants

class FileRequest {
  static borrow() {
    if (this._free.length > 0) return this._free.pop()
    return new FileRequest()
  }

  static return(req) {
    if (this._free.length < 32) this._free.push(req.reset())
    else req.destroy()
  }

  constructor() {
    this._reset()
    this._handle = binding.requestInit(this, this._onresult)
  }

  get handle() {
    return this._handle
  }

  retain(value) {
    this._retain = value // Tie the lifetime of `value` to the lifetime of `this`
  }

  reset() {
    if (this._handle === null) return this

    binding.requestReset(this._handle)

    this._reset()

    return this
  }

  destroy() {
    if (this._handle === null) return this

    binding.requestDestroy(this._handle)

    this._reset()
    this._handle = null

    return this
  }

  then(resolve, reject) {
    return this._promise.then(resolve, reject)
  }

  return() {
    if (this._handle === null) return this

    FileRequest.return(this)

    return this
  }

  _reset() {
    this._promise = new Promise((resolve, reject) => {
      this._resolve = resolve
      this._reject = reject
    })
    this._retain = null
  }

  _onresult(err, status) {
    if (err) this._reject(err)
    else this._resolve(status)
  }
}

FileRequest._free = []

function ok(result, cb) {
  if (typeof result === 'function') {
    cb = result
    result = undefined
  }

  if (cb) cb(null, result)
  else return result
}

function fail(err, cb) {
  if (cb) cb(err)
  else throw err
}

function done(err, result, cb) {
  if (typeof result === 'function') {
    cb = result
    result = undefined
  }

  if (err) fail(err, cb)
  else return ok(result, cb)
}

async function open(filepath, flags = 'r', mode = 0o666, cb) {
  if (typeof flags === 'function') {
    cb = flags
    flags = 'r'
    mode = 0o666
  } else if (typeof mode === 'function') {
    cb = mode
    mode = 0o666
  }

  if (typeof flags === 'string') flags = toFlags(flags)
  if (typeof mode === 'string') mode = toMode(mode)

  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  let fd
  let err = null
  try {
    binding.open(req.handle, filepath, flags, mode)

    fd = await req
  } catch (e) {
    err = new FileError(e.message, {
      operation: 'open',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }

  return done(err, fd, cb)
}

function openSync(filepath, flags = 'r', mode = 0o666) {
  if (typeof flags === 'string') flags = toFlags(flags)
  if (typeof mode === 'string') mode = toMode(mode)

  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  try {
    return binding.openSync(req.handle, filepath, flags, mode)
  } catch (e) {
    throw new FileError(e.message, {
      operation: 'open',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }
}

async function close(fd, cb) {
  const req = FileRequest.borrow()

  let err = null
  try {
    binding.close(req.handle, fd)

    await req
  } catch (e) {
    err = new FileError(e.message, { operation: 'close', code: e.code, fd })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function closeSync(fd) {
  const req = FileRequest.borrow()

  try {
    binding.closeSync(req.handle, fd)
  } catch (e) {
    throw new FileError(e.message, { operation: 'close', code: e.code, fd })
  } finally {
    req.return()
  }
}

async function access(filepath, mode = constants.F_OK, cb) {
  if (typeof mode === 'function') {
    cb = mode
    mode = constants.F_OK
  }

  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  let err = null
  try {
    binding.access(req.handle, filepath, mode)

    await req
  } catch (e) {
    err = new FileError(e.message, {
      operation: 'access',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function accessSync(filepath, mode = constants.F_OK) {
  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  try {
    binding.accessSync(req.handle, filepath, mode)
  } catch (e) {
    throw new FileError(e.message, {
      operation: 'access',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }
}

async function exists(filepath, cb) {
  let ok = true
  try {
    await access(filepath)
  } catch {
    ok = false
  }

  return done(null, ok, cb)
}

function existsSync(filepath) {
  try {
    accessSync(filepath)
  } catch {
    return false
  }

  return true
}

async function read(fd, buffer, offset = 0, len = buffer.byteLength - offset, pos = -1, cb) {
  if (typeof offset === 'function') {
    cb = offset
    offset = 0
    len = buffer.byteLength
    pos = -1
  } else if (typeof len === 'function') {
    cb = len
    len = buffer.byteLength - offset
    pos = -1
  } else if (typeof pos === 'function') {
    cb = pos
    pos = -1
  }

  if (typeof pos !== 'number') pos = -1

  const req = FileRequest.borrow()

  let bytes
  let err = null
  try {
    binding.read(req.handle, fd, buffer, offset, len, pos)

    req.retain(buffer)

    bytes = await req
  } catch (e) {
    err = new FileError(e.message, { operation: 'read', code: e.code, fd })
  } finally {
    req.return()
  }

  return done(err, bytes, cb)
}

function readSync(fd, buffer, offset = 0, len = buffer.byteLength - offset, pos = -1) {
  const req = FileRequest.borrow()

  try {
    return binding.readSync(req.handle, fd, buffer, offset, len, pos)
  } catch (e) {
    throw new FileError(e.message, { operation: 'read', code: e.code, fd })
  } finally {
    req.return()
  }
}

async function readv(fd, buffers, pos = -1, cb) {
  if (typeof pos === 'function') {
    cb = pos
    pos = -1
  }

  if (typeof pos !== 'number') pos = -1

  const req = FileRequest.borrow()

  let bytes
  let err = null
  try {
    binding.readv(req.handle, fd, buffers, pos)

    req.retain(buffers)

    bytes = await req
  } catch (e) {
    err = new FileError(e.message, { operation: 'readv', code: e.code, fd })
  } finally {
    req.return()
  }

  return done(err, bytes, cb)
}

function readvSync(fd, buffers, pos = -1) {
  if (typeof pos !== 'number') pos = -1

  const req = FileRequest.borrow()

  try {
    return binding.readvSync(req.handle, fd, buffers, pos)
  } catch (e) {
    throw new FileError(e.message, { operation: 'readv', code: e.code, fd })
  } finally {
    req.return()
  }
}

async function write(fd, data, offset, len, pos = -1, cb) {
  if (typeof data === 'string') {
    let encoding = len
    cb = pos
    pos = offset

    if (typeof pos === 'function') {
      cb = pos
      pos = -1
      encoding = 'utf8'
    } else if (typeof encoding === 'function') {
      cb = encoding
      encoding = 'utf8'
    }

    if (typeof pos === 'string') {
      encoding = pos
      pos = -1
    }

    data = Buffer.from(data, encoding)
    offset = 0
    len = data.byteLength
  } else if (typeof offset === 'function') {
    cb = offset
    offset = 0
    len = data.byteLength
    pos = -1
  } else if (typeof len === 'function') {
    cb = len
    len = data.byteLength - offset
    pos = -1
  } else if (typeof pos === 'function') {
    cb = pos
    pos = -1
  }

  if (typeof offset !== 'number') offset = 0
  if (typeof len !== 'number') len = data.byteLength - offset
  if (typeof pos !== 'number') pos = -1

  const req = FileRequest.borrow()

  let bytes
  let err = null
  try {
    binding.write(req.handle, fd, data, offset, len, pos)

    req.retain(data)

    bytes = await req
  } catch (e) {
    err = new FileError(e.message, { operation: 'write', code: e.code, fd })
  } finally {
    req.return()
  }

  return done(err, bytes, cb)
}

function writeSync(fd, data, offset, len, pos = -1) {
  if (typeof data === 'string') {
    let encoding = len
    pos = offset

    if (typeof pos === 'string') {
      encoding = pos
      pos = -1
    }

    data = Buffer.from(data, encoding)
    offset = 0
    len = data.byteLength
  }

  if (typeof offset !== 'number') offset = 0
  if (typeof len !== 'number') len = data.byteLength - offset
  if (typeof pos !== 'number') pos = -1

  const req = FileRequest.borrow()

  try {
    return binding.writeSync(req.handle, fd, data, offset, len, pos)
  } catch (e) {
    throw new FileError(e.message, { operation: 'write', code: e.code, fd })
  } finally {
    req.return()
  }
}

async function writev(fd, buffers, pos = -1, cb) {
  if (typeof pos === 'function') {
    cb = pos
    pos = -1
  }

  if (typeof pos !== 'number') pos = -1

  const req = FileRequest.borrow()

  let bytes
  let err = null
  try {
    binding.writev(req.handle, fd, buffers, pos)

    req.retain(buffers)

    bytes = await req
  } catch (e) {
    err = new FileError(e.message, { operation: 'writev', code: e.code, fd })
  } finally {
    req.return()
  }

  return done(err, bytes, cb)
}

function writevSync(fd, buffers, pos = -1) {
  if (typeof pos !== 'number') pos = -1

  const req = FileRequest.borrow()

  try {
    return binding.writevSync(req.handle, fd, buffers, pos)
  } catch (e) {
    throw new FileError(e.message, { operation: 'writev', code: e.code, fd })
  } finally {
    req.return()
  }
}

async function stat(filepath, cb) {
  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  let st
  let err = null
  try {
    binding.stat(req.handle, filepath)

    await req

    st = new Stats(...binding.requestResultStat(req.handle))
  } catch (e) {
    err = new FileError(e.message, { operation: 'stat', code: e.code, path: filepath })
  } finally {
    req.return()
  }

  return done(err, st, cb)
}

function statSync(filepath) {
  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  try {
    binding.statSync(req.handle, filepath)

    return new Stats(...binding.requestResultStat(req.handle))
  } catch (e) {
    throw new FileError(e.message, { operation: 'stat', code: e.code, path: filepath })
  } finally {
    req.return()
  }
}

async function lstat(filepath, cb) {
  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  let st
  let err = null
  try {
    binding.lstat(req.handle, filepath)

    await req

    st = new Stats(...binding.requestResultStat(req.handle))
  } catch (e) {
    err = new FileError(e.message, { operation: 'lstat', code: e.code, path: filepath })
  } finally {
    req.return()
  }

  return done(err, st, cb)
}

function lstatSync(filepath) {
  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  try {
    binding.lstatSync(req.handle, filepath)

    return new Stats(...binding.requestResultStat(req.handle))
  } catch (e) {
    throw new FileError(e.message, { operation: 'lstat', code: e.code, path: filepath })
  } finally {
    req.return()
  }
}

async function fstat(fd, cb) {
  const req = FileRequest.borrow()

  let st
  let err = null
  try {
    binding.fstat(req.handle, fd)

    await req

    st = new Stats(...binding.requestResultStat(req.handle))
  } catch (e) {
    err = new FileError(e.message, { operation: 'fstat', code: e.code, fd })
  } finally {
    req.return()
  }

  return done(err, st, cb)
}

function fstatSync(fd) {
  const req = FileRequest.borrow()

  try {
    binding.fstatSync(req.handle, fd)

    return new Stats(...binding.requestResultStat(req.handle))
  } catch (e) {
    throw new FileError(e.message, { operation: 'fstat', code: e.code, fd })
  } finally {
    req.return()
  }
}

async function statfs(filepath, cb) {
  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  let st
  let err = null
  try {
    binding.statfs(req.handle, filepath)

    await req

    st = new StatFs(...binding.requestResultStatfs(req.handle))
  } catch (e) {
    err = new FileError(e.message, { operation: 'statfs', code: e.code, path: filepath })
  } finally {
    req.return()
  }

  return done(err, st, cb)
}

function statfsSync(filepath) {
  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  try {
    binding.statfsSync(req.handle, filepath)

    return new StatFs(...binding.requestResultStatfs(req.handle))
  } catch (e) {
    throw new FileError(e.message, { operation: 'statfs', code: e.code, path: filepath })
  } finally {
    req.return()
  }
}

async function ftruncate(fd, len = 0, cb) {
  if (typeof len === 'function') {
    cb = len
    len = 0
  }

  if (typeof len !== 'number') len = 0

  const req = FileRequest.borrow()

  let err = null
  try {
    binding.ftruncate(req.handle, fd, len)

    await req
  } catch (e) {
    err = new FileError(e.message, { operation: 'ftruncate', code: e.code, fd })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function ftruncateSync(fd, len = 0) {
  if (typeof len !== 'number') len = 0

  const req = FileRequest.borrow()

  try {
    binding.ftruncateSync(req.handle, fd, len)
  } catch (e) {
    throw new FileError(e.message, { operation: 'ftruncate', code: e.code, fd })
  } finally {
    req.return()
  }
}

async function truncate(filepath, len, cb) {
  let fd = -1
  let err

  try {
    fd = await open(filepath, 'r+')

    await ftruncate(fd, len)
  } catch (e) {
    err = e
  } finally {
    if (fd !== -1) await close(fd)
  }

  return done(err, cb)
}

function truncateSync(filepath, len) {
  let fd = -1
  let err

  try {
    fd = openSync(filepath, 'r+')

    ftruncateSync(fd, len)
  } catch (e) {
    err = e
  } finally {
    if (fd !== -1) closeSync(fd)
  }
}

async function chmod(filepath, mode, cb) {
  if (typeof mode === 'string') mode = toMode(mode)

  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  let err = null
  try {
    binding.chmod(req.handle, filepath, mode)

    await req
  } catch (e) {
    err = new FileError(e.message, {
      operation: 'chmod',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function chmodSync(filepath, mode) {
  if (typeof mode === 'string') mode = toMode(mode)

  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  try {
    binding.chmodSync(req.handle, filepath, mode)
  } catch (e) {
    throw new FileError(e.message, {
      operation: 'chmod',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }
}

async function fchmod(fd, mode, cb) {
  if (typeof mode === 'string') mode = toMode(mode)

  const req = FileRequest.borrow()

  let err = null
  try {
    binding.fchmod(req.handle, fd, mode)

    await req
  } catch (e) {
    err = new FileError(e.message, { operation: 'fchmod', code: e.code, fd })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function fchmodSync(fd, mode) {
  if (typeof mode === 'string') mode = toMode(mode)

  const req = FileRequest.borrow()

  try {
    binding.fchmodSync(req.handle, fd, mode)
  } catch (e) {
    throw new FileError(e.message, { operation: 'fchmod', code: e.code, fd })
  } finally {
    req.return()
  }
}

async function chown(filepath, uid, gid, cb) {
  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  let err = null
  try {
    binding.chown(req.handle, filepath, uid, gid)

    await req
  } catch (e) {
    err = new FileError(e.message, { operation: 'chown', code: e.code, path: filepath })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function chownSync(filepath, uid, gid) {
  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  try {
    binding.chownSync(req.handle, filepath, uid, gid)
  } catch (e) {
    throw new FileError(e.message, { operation: 'chownSync', code: e.code, path: filepath })
  } finally {
    req.return()
  }
}

async function lchown(filepath, uid, gid, cb) {
  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  let err = null
  try {
    binding.lchown(req.handle, filepath, uid, gid)

    await req
  } catch (e) {
    err = new FileError(e.message, { operation: 'lchown', code: e.code, path: filepath })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function lchownSync(filepath, uid, gid) {
  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  try {
    binding.lchownSync(req.handle, filepath, uid, gid)
  } catch (e) {
    throw new FileError(e.message, { operation: 'lchownSync', code: e.code, path: filepath })
  } finally {
    req.return()
  }
}

async function fchown(fd, uid, gid, cb) {
  const req = FileRequest.borrow()

  let err = null
  try {
    binding.fchown(req.handle, fd, uid, gid)

    await req
  } catch (e) {
    err = new FileError(e.message, { operation: 'fchown', code: e.code, fd })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function fchownSync(fd, uid, gid) {
  const req = FileRequest.borrow()

  try {
    binding.fchownSync(req.handle, fd, uid, gid)
  } catch (e) {
    throw new FileError(e.message, { operation: 'fchownSync', code: e.code, fd })
  } finally {
    req.return()
  }
}

async function utimes(filepath, atime, mtime, cb) {
  if (typeof atime !== 'number') atime = atime.getTime() / 1000
  if (typeof mtime !== 'number') mtime = mtime.getTime() / 1000

  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  let err = null
  try {
    binding.utimes(req.handle, filepath, atime, mtime)

    await req
  } catch (e) {
    err = new FileError(e.message, { operation: 'utimes', code: e.code, path: filepath })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function utimesSync(filepath, atime, mtime) {
  if (typeof atime !== 'number') atime = atime.getTime() / 1000
  if (typeof mtime !== 'number') mtime = mtime.getTime() / 1000

  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  try {
    binding.utimesSync(req.handle, filepath, atime, mtime)
  } catch (e) {
    throw new FileError(e.message, { operation: 'utimes', code: e.code, path: filepath })
  } finally {
    req.return()
  }
}

async function lutimes(filepath, atime, mtime, cb) {
  if (typeof atime !== 'number') atime = atime.getTime() / 1000
  if (typeof mtime !== 'number') mtime = mtime.getTime() / 1000

  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  let err = null
  try {
    binding.lutimes(req.handle, filepath, atime, mtime)

    await req
  } catch (e) {
    err = new FileError(e.message, { operation: 'lutimes', code: e.code, path: filepath })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function lutimesSync(filepath, atime, mtime) {
  if (typeof atime !== 'number') atime = atime.getTime() / 1000
  if (typeof mtime !== 'number') mtime = mtime.getTime() / 1000

  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  try {
    binding.lutimesSync(req.handle, filepath, atime, mtime)
  } catch (e) {
    throw new FileError(e.message, { operation: 'lutimes', code: e.code, path: filepath })
  } finally {
    req.return()
  }
}

async function futimes(fd, atime, mtime, cb) {
  if (typeof atime !== 'number') atime = atime.getTime() / 1000
  if (typeof mtime !== 'number') mtime = mtime.getTime() / 1000

  const req = FileRequest.borrow()

  let err = null
  try {
    binding.futimes(req.handle, fd, atime, mtime)

    await req
  } catch (e) {
    err = new FileError(e.message, { operation: 'futimes', code: e.code, fd })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function futimesSync(fd, atime, mtime) {
  if (typeof atime !== 'number') atime = atime.getTime() / 1000
  if (typeof mtime !== 'number') mtime = mtime.getTime() / 1000

  const req = FileRequest.borrow()

  try {
    binding.futimesSync(req.handle, fd, atime, mtime)
  } catch (e) {
    throw new FileError(e.message, { operation: 'futimesSync', code: e.code, fd })
  } finally {
    req.return()
  }
}

async function link(src, dst, cb) {
  src = toNamespacedPath(src)
  dst = toNamespacedPath(dst)

  const req = FileRequest.borrow()

  let err = null
  try {
    binding.link(req.handle, src, dst)

    await req
  } catch (e) {
    err = new FileError(e.message, { operation: 'link', code: e.code, path: src, destination: dst })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function linkSync(src, dst) {
  src = toNamespacedPath(src)
  dst = toNamespacedPath(dst)

  const req = FileRequest.borrow()

  try {
    binding.linkSync(req.handle, src, dst)
  } catch (e) {
    throw new FileError(e.message, {
      operation: 'linkSync',
      code: e.code,
      path: src,
      destination: dst
    })
  } finally {
    req.return()
  }
}

async function mkdir(filepath, opts, cb) {
  if (typeof opts === 'function') {
    cb = opts
    opts = { mode: 0o777 }
  }

  if (typeof opts === 'number') opts = { mode: opts }
  else if (!opts) opts = {}

  const mode = typeof opts.mode === 'number' ? opts.mode : 0o777

  filepath = toNamespacedPath(filepath)

  if (opts.recursive) {
    let err = null
    try {
      try {
        await mkdir(filepath, { mode })
      } catch (err) {
        if (err.code !== 'ENOENT') {
          if (!(await stat(filepath)).isDirectory()) throw err
        } else {
          while (filepath.endsWith(path.sep)) filepath = filepath.slice(0, -1)
          const i = filepath.lastIndexOf(path.sep)
          if (i <= 0) throw err

          await mkdir(filepath.slice(0, i), { mode, recursive: true })

          try {
            await mkdir(filepath, { mode })
          } catch (err) {
            if (!(await stat(filepath)).isDirectory()) throw err
          }
        }
      }
    } catch (e) {
      err = e
    }

    return done(err, cb)
  }

  const req = FileRequest.borrow()

  let err = null
  try {
    binding.mkdir(req.handle, filepath, mode)

    await req
  } catch (e) {
    err = new FileError(e.message, {
      operation: 'mkdir',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function mkdirSync(filepath, opts) {
  if (typeof opts === 'number') opts = { mode: opts }
  else if (!opts) opts = {}

  const mode = typeof opts.mode === 'number' ? opts.mode : 0o777

  filepath = toNamespacedPath(filepath)

  if (opts.recursive) {
    try {
      mkdirSync(filepath, { mode })
    } catch (err) {
      if (err.code !== 'ENOENT') {
        if (!statSync(filepath).isDirectory()) throw err
      } else {
        while (filepath.endsWith(path.sep)) filepath = filepath.slice(0, -1)
        const i = filepath.lastIndexOf(path.sep)
        if (i <= 0) throw err

        mkdirSync(filepath.slice(0, i), { mode, recursive: true })

        try {
          mkdirSync(filepath, { mode })
        } catch (err) {
          if (!statSync(filepath).isDirectory()) throw err
        }
      }
    }

    return
  }

  const req = FileRequest.borrow()

  try {
    binding.mkdirSync(req.handle, filepath, mode)
  } catch (e) {
    throw new FileError(e.message, {
      operation: 'mkdir',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }
}

async function mkdtemp(prefix, cb) {
  prefix = toNamespacedPath(prefix)

  const req = FileRequest.borrow()

  let res
  let err = null
  try {
    binding.mkdtemp(req.handle, prefix + 'XXXXXX')

    await req

    res = binding.requestResultPath(req.handle)
  } catch (e) {
    err = new FileError(e.message, { operation: 'mkdtemp', code: e.code, path: prefix })
  } finally {
    req.return()
  }

  return done(err, res, cb)
}

function mkdtempSync(prefix) {
  prefix = toNamespacedPath(prefix)

  const req = FileRequest.borrow()

  try {
    binding.mkdtempSync(req.handle, prefix + 'XXXXXX')

    return binding.requestResultPath(req.handle)
  } catch (e) {
    throw new FileError(e.message, { operation: 'mkdtempSync', code: e.code, path: prefix })
  } finally {
    req.return()
  }
}

async function rmdir(filepath, cb) {
  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  let err = null
  try {
    binding.rmdir(req.handle, filepath)

    await req
  } catch (e) {
    err = new FileError(e.message, {
      operation: 'rmdir',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function rmdirSync(filepath) {
  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  try {
    binding.rmdirSync(req.handle, filepath)
  } catch (e) {
    throw new FileError(e.message, {
      operation: 'rmdir',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }
}

async function rm(filepath, opts, cb) {
  if (typeof opts === 'function') {
    cb = opts
    opts = {}
  }

  if (!opts) opts = {}

  filepath = toNamespacedPath(filepath)

  let err = null
  try {
    const st = await lstat(filepath)

    if (st.isDirectory()) {
      if (opts.recursive) {
        try {
          await rmdir(filepath)
        } catch (err) {
          if (err.code !== 'ENOTEMPTY') throw err

          const files = await readdir(filepath)

          for (const file of files) {
            await rm(filepath + path.sep + file, opts)
          }

          await rmdir(filepath)
        }
      } else {
        throw new FileError('is a directory', {
          operation: 'rm',
          code: 'EISDIR',
          path: filepath
        })
      }
    } else {
      await unlink(filepath)
    }
  } catch (e) {
    if (e.code !== 'ENOENT' || !opts.force) err = e
  }

  return done(err, cb)
}

function rmSync(filepath, opts) {
  if (!opts) opts = {}

  filepath = toNamespacedPath(filepath)

  try {
    const st = lstatSync(filepath)

    if (st.isDirectory()) {
      if (opts.recursive) {
        try {
          rmdirSync(filepath)
        } catch (err) {
          if (err.code !== 'ENOTEMPTY') throw err

          const files = readdirSync(filepath)

          for (const file of files) {
            rmSync(filepath + path.sep + file, opts)
          }

          rmdirSync(filepath)
        }
      } else {
        throw new FileError('is a directory', {
          operation: 'rm',
          code: 'EISDIR',
          path: filepath
        })
      }
    } else {
      unlinkSync(filepath)
    }
  } catch (err) {
    if (err.code !== 'ENOENT' || !opts.force) throw err
  }
}

async function unlink(filepath, cb) {
  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  let err = null
  try {
    binding.unlink(req.handle, filepath)

    await req
  } catch (e) {
    err = new FileError(e.message, {
      operation: 'unlink',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function unlinkSync(filepath) {
  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  try {
    binding.unlinkSync(req.handle, filepath)
  } catch (e) {
    throw new FileError(e.message, {
      operation: 'unlink',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }
}

async function rename(src, dst, cb) {
  src = toNamespacedPath(src)
  dst = toNamespacedPath(dst)

  const req = FileRequest.borrow()

  let err = null
  try {
    binding.rename(req.handle, src, dst)

    await req
  } catch (e) {
    err = new FileError(e.message, {
      operation: 'rename',
      code: e.code,
      path: src,
      destination: dst
    })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function renameSync(src, dst) {
  src = toNamespacedPath(src)
  dst = toNamespacedPath(dst)

  const req = FileRequest.borrow()

  try {
    binding.renameSync(req.handle, src, dst)
  } catch (e) {
    throw new FileError(e.message, {
      operation: 'rename',
      code: e.code,
      path: src,
      destination: dst
    })
  } finally {
    req.return()
  }
}

async function copyFile(src, dst, mode = 0, cb) {
  if (typeof mode === 'function') {
    cb = mode
    mode = 0
  }

  src = toNamespacedPath(src)
  dst = toNamespacedPath(dst)

  const req = FileRequest.borrow()

  let err = null
  try {
    binding.copyfile(req.handle, src, dst, mode)

    await req
  } catch (e) {
    err = new FileError(e.message, {
      operation: 'copyfile',
      code: e.code,
      path: src,
      destination: dst
    })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function copyFileSync(src, dst, mode = 0) {
  src = toNamespacedPath(src)
  dst = toNamespacedPath(dst)

  const req = FileRequest.borrow()

  try {
    binding.copyfileSync(req.handle, src, dst, mode)
  } catch (e) {
    throw new FileError(e.message, {
      operation: 'copyfile',
      code: e.code,
      path: src,
      destination: dst
    })
  } finally {
    req.return()
  }
}

async function cp(src, dst, opts, cb) {
  if (typeof opts === 'function') {
    cb = opts
    opts = {}
  }

  if (!opts) opts = {}

  src = toNamespacedPath(src)
  dst = toNamespacedPath(dst)

  let err = null
  try {
    const st = await lstat(src)

    if (st.isDirectory()) {
      if (opts.recursive !== true) {
        throw new FileError('is a directory', { operation: 'cp', code: 'EISDIR', path: src })
      }

      try {
        await lstat(dst)
      } catch (e) {
        if (e.code !== 'ENOENT') throw e

        await mkdir(dst, { mode: st.mode, recursive: true })
      }

      const dir = await opendir(src)
      for await (const { name } of dir) {
        await cp(path.join(src, name), path.join(dst, name), opts)
      }
    } else if (st.isFile()) {
      await copyFile(src, dst)
      await chmod(dst, st.mode)
    }
  } catch (e) {
    err = e
  }

  return done(err, cb)
}

function cpSync(src, dst, opts = {}) {
  src = toNamespacedPath(src)
  dst = toNamespacedPath(dst)

  const st = lstatSync(src)

  if (st.isDirectory()) {
    if (opts.recursive !== true) {
      throw new FileError('is a directory', { operation: 'cp', code: 'EISDIR', path: src })
    }

    try {
      lstatSync(dst)
    } catch (e) {
      if (e.code !== 'ENOENT') throw e

      mkdirSync(dst, { mode: st.mode, recursive: true })
    }

    const dir = opendirSync(src)
    for (const { name } of dir) {
      cpSync(path.join(src, name), path.join(dst, name), opts)
    }
  } else if (st.isFile()) {
    copyFileSync(src, dst)
    chmodSync(dst, st.mode)
  }
}

async function realpath(filepath, opts, cb) {
  if (typeof opts === 'function') {
    cb = opts
    opts = {}
  }

  if (typeof opts === 'string') opts = { encoding: opts }
  else if (!opts) opts = {}

  const { encoding = 'utf8' } = opts

  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  let res
  let err = null
  try {
    binding.realpath(req.handle, filepath)

    await req

    res = Buffer.from(binding.requestResultString(req.handle))

    if (encoding !== 'buffer') res = res.toString(encoding)
  } catch (e) {
    err = new FileError(e.message, {
      operation: 'realpath',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }

  return done(err, res, cb)
}

function realpathSync(filepath, opts) {
  if (typeof opts === 'string') opts = { encoding: opts }
  else if (!opts) opts = {}

  const { encoding = 'utf8' } = opts

  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  try {
    binding.realpathSync(req.handle, filepath)

    let res = Buffer.from(binding.requestResultString(req.handle))

    if (encoding !== 'buffer') res = res.toString(encoding)

    return res
  } catch (e) {
    throw new FileError(e.message, {
      operation: 'realpath',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }
}

async function readlink(filepath, opts, cb) {
  if (typeof opts === 'function') {
    cb = opts
    opts = {}
  }

  if (typeof opts === 'string') opts = { encoding: opts }
  else if (!opts) opts = {}

  const { encoding = 'utf8' } = opts

  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  let res
  let err = null
  try {
    binding.readlink(req.handle, filepath)

    await req

    res = Buffer.from(binding.requestResultString(req.handle))

    if (encoding !== 'buffer') res = res.toString(encoding)
  } catch (e) {
    err = new FileError(e.message, {
      operation: 'readlink',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }

  return done(err, res, cb)
}

function readlinkSync(filepath, opts) {
  if (typeof opts === 'string') opts = { encoding: opts }
  else if (!opts) opts = {}

  const { encoding = 'utf8' } = opts

  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  try {
    binding.readlinkSync(req.handle, filepath)

    let res = Buffer.from(binding.requestResultString(req.handle))

    if (encoding !== 'buffer') res = res.toString(encoding)

    return res
  } catch (e) {
    throw new FileError(e.message, {
      operation: 'readlink',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }
}

async function fsync(fd, cb) {
  const req = FileRequest.borrow()

  let err = null
  try {
    binding.fsync(req.handle, fd)

    await req
  } catch (e) {
    err = new FileError(e.message, { operation: 'fsync', code: e.code, fd })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function fsyncSync(fd) {
  const req = FileRequest.borrow()

  try {
    binding.fsyncSync(req.handle, fd)
  } catch (e) {
    throw new FileError(e.message, { operation: 'fsyncSync', code: e.code, fd })
  } finally {
    req.return()
  }
}

async function fdatasync(fd, cb) {
  const req = FileRequest.borrow()

  let err = null
  try {
    binding.fdatasync(req.handle, fd)

    await req
  } catch (e) {
    err = new FileError(e.message, { operation: 'fdatasync', code: e.code, fd })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function fdatasyncSync(fd) {
  const req = FileRequest.borrow()

  try {
    binding.fdatasyncSync(req.handle, fd)
  } catch (e) {
    throw new FileError(e.message, { operation: 'fdatasyncSync', code: e.code, fd })
  } finally {
    req.return()
  }
}

function normalizeSymlinkTarget(target, type, filepath) {
  if (isWindows) {
    if (type === constants.UV_FS_SYMLINK_JUNCTION) target = path.resolve(filepath, '..', target)

    if (path.isAbsolute(target)) return path.toNamespacedPath(target)

    return target.replace(/\//g, path.sep)
  }

  return target
}

async function symlink(target, filepath, type, cb) {
  if (typeof type === 'function') {
    cb = type
    type = null
  }

  filepath = toNamespacedPath(filepath)

  if (typeof type === 'string') {
    switch (type) {
      case 'dir':
        type = constants.UV_FS_SYMLINK_DIR
        break
      case 'junction':
        type = constants.UV_FS_SYMLINK_JUNCTION
        break
      case 'file':
      default:
        type = 0
        break
    }
  } else if (typeof type !== 'number') {
    if (isWindows) {
      target = path.resolve(filepath, '..', target)

      try {
        type = (await stat(target)).isDirectory()
          ? constants.UV_FS_SYMLINK_DIR
          : constants.UV_FS_SYMLINK_JUNCTION
      } catch {
        type = 0
      }
    } else {
      type = 0
    }
  }

  target = normalizeSymlinkTarget(target, type, filepath)

  const req = FileRequest.borrow()

  let err = null
  try {
    binding.symlink(req.handle, target, filepath, type)

    await req
  } catch (e) {
    err = new FileError(e.message, {
      operation: 'symlink',
      code: e.code,
      path: target,
      destination: filepath
    })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function symlinkSync(target, filepath, type) {
  filepath = toNamespacedPath(filepath)

  if (typeof type === 'string') {
    switch (type) {
      case 'dir':
        type = constants.UV_FS_SYMLINK_DIR
        break
      case 'junction':
        type = constants.UV_FS_SYMLINK_JUNCTION
        break
      case 'file':
      default:
        type = 0
        break
    }
  } else if (typeof type !== 'number') {
    if (isWindows) {
      target = path.resolve(filepath, '..', target)

      try {
        type = statSync(target).isDirectory()
          ? constants.UV_FS_SYMLINK_DIR
          : constants.UV_FS_SYMLINK_JUNCTION
      } catch {
        type = 0
      }
    } else {
      type = 0
    }
  }

  target = normalizeSymlinkTarget(target, type, filepath)

  const req = FileRequest.borrow()

  try {
    binding.symlinkSync(req.handle, target, filepath, type)
  } catch (e) {
    throw new FileError(e.message, {
      operation: 'symlink',
      code: e.code,
      path: target,
      destination: filepath
    })
  } finally {
    req.return()
  }
}

async function opendir(filepath, opts, cb) {
  if (typeof opts === 'function') {
    cb = opts
    opts = {}
  }

  if (typeof opts === 'string') opts = { encoding: opts }
  else if (!opts) opts = {}

  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  let dir
  let err = null
  try {
    binding.opendir(req.handle, filepath)

    await req

    dir = new Dir(filepath, binding.requestResultDir(req.handle), opts)
  } catch (e) {
    err = new FileError(e.message, {
      operation: 'opendir',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }

  return done(err, dir, cb)
}

function opendirSync(filepath, opts) {
  if (typeof opts === 'string') opts = { encoding: opts }
  else if (!opts) opts = {}

  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  try {
    binding.opendirSync(req.handle, filepath)

    return new Dir(filepath, binding.requestResultDir(req.handle), opts)
  } catch (e) {
    throw new FileError(e.message, {
      operation: 'opendir',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }
}

async function readdir(filepath, opts, cb) {
  if (typeof opts === 'function') {
    cb = opts
    opts = {}
  }

  if (typeof opts === 'string') opts = { encoding: opts }
  else if (!opts) opts = {}

  const { withFileTypes = false, recursive = false } = opts

  filepath = toNamespacedPath(filepath)

  const queue = [filepath]
  let result = []
  let err = null
  try {
    while (queue.length !== 0) {
      const dir = await opendir(queue.pop())

      for await (const entry of dir) {
        const entryPath = path.join(entry.parentPath, entry.name)

        if (withFileTypes) {
          result.push(entry)
        } else {
          result.push(path.relative(filepath, entryPath))
        }

        if (recursive && entry.isDirectory()) queue.push(entryPath)
      }
    }
  } catch (e) {
    result = []
    err = e
  }

  return done(err, result, cb)
}

function readdirSync(filepath, opts) {
  if (typeof opts === 'string') opts = { encoding: opts }
  else if (!opts) opts = {}

  const { withFileTypes = false, recursive = false } = opts

  filepath = toNamespacedPath(filepath)

  const queue = [filepath]
  const result = []

  while (queue.length !== 0) {
    const dir = opendirSync(queue.pop(), opts)

    for (const entry of dir) {
      const entryPath = path.join(entry.parentPath, entry.name)

      if (withFileTypes) {
        result.push(entry)
      } else {
        result.push(path.relative(filepath, entryPath))
      }

      if (recursive && entry.isDirectory()) queue.push(entryPath)
    }
  }

  return result
}

async function readFile(filepath, opts, cb) {
  if (typeof opts === 'function') {
    cb = opts
    opts = {}
  }

  if (typeof opts === 'string') opts = { encoding: opts }
  else if (!opts) opts = {}

  const { encoding = 'buffer' } = opts

  let fd = -1
  let buffer = null
  let err = null
  try {
    fd = await open(filepath, opts.flag || 'r')

    const st = await fstat(fd)

    let len = 0

    if (st.size === 0) {
      const buffers = []

      while (true) {
        buffer = Buffer.allocUnsafe(8192)
        const r = await read(fd, buffer)
        len += r
        if (r === 0) break
        buffers.push(buffer.subarray(0, r))
      }

      buffer = Buffer.concat(buffers)
    } else {
      buffer = Buffer.allocUnsafe(st.size)

      while (true) {
        const r = await read(fd, len ? buffer.subarray(len) : buffer)
        len += r
        if (r === 0 || len === buffer.byteLength) break
      }

      if (len !== buffer.byteLength) buffer = buffer.subarray(0, len)
    }

    if (encoding !== 'buffer') buffer = buffer.toString(encoding)
  } catch (e) {
    err = e
  } finally {
    if (fd !== -1) await close(fd)
  }

  return done(err, buffer, cb)
}

function readFileSync(filepath, opts) {
  if (typeof opts === 'string') opts = { encoding: opts }
  else if (!opts) opts = {}

  const { encoding = 'buffer' } = opts

  let fd = -1
  try {
    fd = openSync(filepath, opts.flag || 'r')

    const st = fstatSync(fd)

    let buffer
    let len = 0

    if (st.size === 0) {
      const buffers = []

      while (true) {
        buffer = Buffer.allocUnsafe(8192)
        const r = readSync(fd, buffer)
        len += r
        if (r === 0) break
        buffers.push(buffer.subarray(0, r))
      }

      buffer = Buffer.concat(buffers)
    } else {
      buffer = Buffer.allocUnsafe(st.size)

      while (true) {
        const r = readSync(fd, len ? buffer.subarray(len) : buffer)
        len += r
        if (r === 0 || len === buffer.byteLength) break
      }

      if (len !== buffer.byteLength) buffer = buffer.subarray(0, len)
    }

    if (encoding !== 'buffer') buffer = buffer.toString(encoding)

    return buffer
  } finally {
    if (fd !== -1) closeSync(fd)
  }
}

async function writeFile(filepath, data, opts, cb) {
  if (typeof opts === 'function') {
    cb = opts
    opts = {}
  }

  if (typeof opts === 'string') opts = { encoding: opts }
  else if (!opts) opts = {}

  if (typeof data === 'string') data = Buffer.from(data, opts.encoding)

  let fd = -1
  let len = 0
  let err = null
  try {
    fd = await open(filepath, opts.flag || 'w', opts.mode || 0o666)

    while (true) {
      len += await write(fd, len ? data.subarray(len) : data)
      if (len === data.byteLength) break
    }
  } catch (e) {
    err = e
  } finally {
    if (fd !== -1) await close(fd)
  }

  return done(err, len, cb)
}

function writeFileSync(filepath, data, opts) {
  if (typeof opts === 'string') opts = { encoding: opts }
  else if (!opts) opts = {}

  if (typeof data === 'string') data = Buffer.from(data, opts.encoding)

  let fd = -1
  try {
    fd = openSync(filepath, opts.flag || 'w', opts.mode || 0o666)

    let len = 0

    while (true) {
      len += writeSync(fd, len ? data.subarray(len) : data)
      if (len === data.byteLength) break
    }
  } finally {
    if (fd !== -1) closeSync(fd)
  }
}

function appendFile(filepath, data, opts, cb) {
  if (typeof opts === 'function') {
    cb = opts
    opts = {}
  }

  if (typeof opts === 'string') opts = { encoding: opts }
  else if (!opts) opts = {}

  if (!opts.flag) opts = { ...opts, flag: 'a' }

  return writeFile(filepath, data, opts, cb)
}

function appendFileSync(filepath, data, opts) {
  if (typeof opts === 'string') opts = { encoding: opts }
  else if (!opts) opts = {}

  if (!opts.flag) opts = { ...opts, flag: 'a' }

  return writeFileSync(filepath, data, opts)
}

function watch(filepath, opts, cb) {
  if (typeof opts === 'function') {
    cb = opts
    opts = {}
  }

  if (typeof opts === 'string') opts = { encoding: opts }
  else if (!opts) opts = {}

  filepath = toNamespacedPath(filepath)

  return new Watcher(filepath, opts, cb)
}

class Stats {
  constructor(
    dev,
    mode,
    nlink,
    uid,
    gid,
    rdev,
    blksize,
    ino,
    size,
    blocks,
    atimeMs,
    mtimeMs,
    ctimeMs,
    birthtimeMs
  ) {
    this.dev = dev
    this.mode = mode
    this.nlink = nlink
    this.uid = uid
    this.gid = gid
    this.rdev = rdev
    this.blksize = blksize
    this.ino = ino
    this.size = size
    this.blocks = blocks
    this.atimeMs = atimeMs
    this.mtimeMs = mtimeMs
    this.ctimeMs = ctimeMs
    this.birthtimeMs = birthtimeMs
    this.atime = new Date(atimeMs)
    this.mtime = new Date(mtimeMs)
    this.ctime = new Date(ctimeMs)
    this.birthtime = new Date(birthtimeMs)
  }

  isDirectory() {
    return (this.mode & constants.S_IFMT) === constants.S_IFDIR
  }

  isFile() {
    return (this.mode & constants.S_IFMT) === constants.S_IFREG
  }

  isBlockDevice() {
    return (this.mode & constants.S_IFMT) === constants.S_IFBLK
  }

  isCharacterDevice() {
    return (this.mode & constants.S_IFMT) === constants.S_IFCHR
  }

  isFIFO() {
    return (this.mode & constants.S_IFMT) === constants.S_IFIFO
  }

  isSymbolicLink() {
    return (this.mode & constants.S_IFMT) === constants.S_IFLNK
  }

  isSocket() {
    return (this.mode & constants.S_IFMT) === constants.S_IFSOCK
  }
}

class StatFs {
  constructor(type, bsize, blocks, bfree, bavail, files, ffree, frsize) {
    this.type = type
    this.bsize = bsize
    this.blocks = blocks
    this.bfree = bfree
    this.bavail = bavail
    this.files = files
    this.ffree = ffree
    this.frsize = frsize
  }
}

class Dir {
  constructor(path, handle, opts = {}) {
    const { encoding = 'utf8', bufferSize = 32 } = opts

    this.path = path

    this._encoding = encoding
    this._capacity = bufferSize
    this._buffer = new FIFO()
    this._ended = false
    this._handle = handle
  }

  async read(cb) {
    if (this._buffer.length) return ok(this._buffer.shift(), cb)
    if (this._ended) return ok(null, cb)

    const req = FileRequest.borrow()

    let entries
    let err = null
    try {
      req.retain(binding.readdir(req.handle, this._handle, this._capacity))

      await req

      entries = binding.requestResultDirents(req.handle)
    } catch (e) {
      err = new FileError(e.message, {
        operation: 'readdir',
        code: e.code,
        path: this.path
      })
    } finally {
      req.return()
    }

    if (err) return fail(err, cb)

    if (entries.length === 0) {
      this._ended = true

      return ok(null, cb)
    }

    for (const entry of entries) {
      let name = Buffer.from(entry.name)

      if (this._encoding !== 'buffer') name = name.toString(this._encoding)

      this._buffer.push(new Dirent(this.path, name, entry.type))
    }

    return ok(this._buffer.shift(), cb)
  }

  readSync() {
    if (this._buffer.length) return this._buffer.shift()
    if (this._ended) return null

    const req = FileRequest.borrow()

    let entries
    try {
      req.retain(binding.readdirSync(req.handle, this._handle, this._capacity))

      entries = binding.requestResultDirents(req.handle)
    } catch (e) {
      throw new FileError(e.message, {
        operation: 'readdir',
        code: e.code,
        path: this.path
      })
    } finally {
      req.return()
    }

    if (entries.length === 0) {
      this._ended = true

      return null
    }

    for (const entry of entries) {
      let name = Buffer.from(entry.name)

      if (this._encoding !== 'buffer') name = name.toString(this._encoding)

      this._buffer.push(new Dirent(this.path, name, entry.type))
    }

    return this._buffer.shift()
  }

  async close(cb) {
    const req = FileRequest.borrow()

    let err = null
    try {
      binding.closedir(req.handle, this._handle)

      await req
    } catch (e) {
      err = new FileError(e.message, {
        operation: 'closedir',
        code: e.code,
        path: this.path
      })
    } finally {
      req.return()
    }

    this._handle = null

    return done(err, cb)
  }

  closeSync() {
    const req = FileRequest.borrow()

    try {
      binding.closedirSync(req.handle, this._handle)
    } catch (e) {
      throw new FileError(e.message, {
        operation: 'closedir',
        code: e.code,
        path: this.path
      })
    } finally {
      req.return()
    }

    this._handle = null
  }

  [Symbol.dispose]() {
    this.closeSync()
  }

  async [Symbol.asyncDispose]() {
    await this.close()
  }

  *[Symbol.iterator]() {
    while (true) {
      const entry = this.readSync()
      if (entry === null) break
      yield entry
    }

    this.closeSync()
  }

  async *[Symbol.asyncIterator]() {
    while (true) {
      const entry = await this.read()
      if (entry === null) break
      yield entry
    }

    await this.close()
  }
}

class Dirent {
  constructor(parentPath, name, type) {
    this.parentPath = parentPath
    this.name = name
    this.type = type
  }

  isFile() {
    return this.type === constants.UV_DIRENT_FILE
  }

  isDirectory() {
    return this.type === constants.UV_DIRENT_DIR
  }

  isSymbolicLink() {
    return this.type === constants.UV_DIRENT_LINK
  }

  isFIFO() {
    return this.type === constants.UV_DIRENT_FIFO
  }

  isSocket() {
    return this.type === constants.UV_DIRENT_SOCKET
  }

  isCharacterDevice() {
    return this.type === constants.UV_DIRENT_CHAR
  }

  isBlockDevice() {
    return this.type === constants.UV_DIRENT_BLOCK
  }
}

class FileReadStream extends Readable {
  constructor(path, opts = {}) {
    const { eagerOpen = true } = opts

    super({ eagerOpen, ...opts })

    this.path = path
    this.fd = typeof opts.fd === 'number' ? opts.fd : -1
    this.flags = opts.flags || 'r'
    this.mode = opts.mode || 0o666

    this._offset = opts.start || 0
    this._missing = 0

    if (opts.length) {
      this._missing = opts.length
    } else if (typeof opts.end === 'number') {
      this._missing = opts.end - this._offset + 1
    } else {
      this._missing = -1
    }
  }

  async _open(cb) {
    let err

    if (this.fd === -1) {
      err = null
      try {
        this.fd = await open(this.path, this.flags, this.mode)
      } catch (e) {
        err = e
      }

      if (err) return cb(err)
    }

    let st
    err = null
    try {
      st = await fstat(this.fd)
    } catch (e) {
      err = e
    }

    if (err) return cb(err)

    this._isCharacterDevice = st.isCharacterDevice()

    if (this._isCharacterDevice) {
      this._missing = Infinity
    } else {
      if (this._missing === -1) this._missing = st.size

      if (st.size < this._offset) {
        this._offset = st.size
        this._missing = 0
      } else if (st.size < this._offset + this._missing) {
        this._missing = st.size - this._offset
      }
    }

    cb(null)
  }

  async _read(size) {
    if (this._missing <= 0) return this.push(null)

    const data = Buffer.allocUnsafe(Math.min(this._missing, size))

    let len
    let err = null
    try {
      len = await read(
        this.fd,
        data,
        0,
        data.byteLength,
        this._isCharacterDevice ? -1 : this._offset
      )
    } catch (e) {
      err = e
    }

    if (err) return this.destroy(err)

    if (len === 0) return this.push(null)

    if (this._missing < len) len = this._missing

    this._missing -= len
    this._offset += len

    this.push(data.subarray(0, len))
  }

  async _destroy(err, cb) {
    if (this.fd === -1) return cb(err)

    try {
      await close(this.fd)
    } catch (e) {
      err = err || e
    }

    cb(err)
  }
}

class FileWriteStream extends Writable {
  constructor(path, opts = {}) {
    const { eagerOpen = true } = opts

    super({ eagerOpen, ...opts })

    this.path = path
    this.fd = typeof opts.fd === 'number' ? opts.fd : -1
    this.flags = opts.flags || 'w'
    this.mode = opts.mode || 0o666
  }

  async _open(cb) {
    if (this.fd !== -1) return cb(null)

    let err = null
    try {
      this.fd = await open(this.path, this.flags, this.mode)
    } catch (e) {
      err = e
    }

    cb(err)
  }

  async _writev(batch, cb) {
    let err = null
    try {
      await writev(
        this.fd,
        batch.map(({ chunk }) => chunk)
      )
    } catch (e) {
      err = e
    }

    cb(err)
  }

  async _destroy(err, cb) {
    if (this.fd === -1) return cb(err)

    try {
      await close(this.fd)
    } catch (e) {
      err = err || e
    }

    cb(err)
  }
}

class Watcher extends EventEmitter {
  constructor(path, opts, onchange) {
    if (typeof opts === 'function') {
      onchange = opts
      opts = {}
    }

    if (!opts) opts = {}

    const { persistent = true, recursive = false, encoding = 'utf8' } = opts

    super()

    this._closed = false
    this._encoding = encoding
    this._handle = binding.watcherInit(path, recursive, this, this._onevent, this._onclose)

    if (!persistent) this.unref()

    if (onchange) this.on('change', onchange)
  }

  close() {
    if (this._closed) return
    this._closed = true

    binding.watcherClose(this._handle)
  }

  ref() {
    if (this._handle) binding.watcherRef(this._handle)
    return this
  }

  unref() {
    if (this._handle) binding.watcherUnref(this._handle)
    return this
  }

  [Symbol.asyncIterator]() {
    const buffer = []
    let done = false
    let error = null
    let next = null

    this.on('change', (eventType, filename) => {
      if (next) {
        next.resolve({ done: false, value: { eventType, filename } })
        next = null
      } else {
        buffer.push({ eventType, filename })
      }
    })
      .on('error', (err) => {
        done = true
        error = err

        if (next) {
          next.reject(error)
          next = null
        }
      })
      .on('close', () => {
        done = true

        if (next) {
          next.resolve({ done })
          next = null
        }
      })

    return {
      next: () =>
        new Promise((resolve, reject) => {
          if (error) return reject(error)

          if (buffer.length) return resolve({ done: false, value: buffer.shift() })

          if (done) return resolve({ done })

          next = { resolve, reject }
        })
    }
  }

  _onevent(err, events, filename) {
    if (err) {
      this.close()
      this.emit('error', err)
    } else {
      const path =
        this._encoding === 'buffer'
          ? Buffer.from(filename)
          : Buffer.from(filename).toString(this._encoding)

      if (events & binding.UV_RENAME) {
        this.emit('change', 'rename', path)
      }

      if (events & binding.UV_CHANGE) {
        this.emit('change', 'change', path)
      }
    }
  }

  _onclose() {
    this._handle = null

    this.emit('close')
  }
}

exports.access = access
exports.appendFile = appendFile
exports.chmod = chmod
exports.chown = chown
exports.close = close
exports.copyFile = copyFile
exports.cp = cp
exports.exists = exists
exports.fchmod = fchmod
exports.fchown = fchown
exports.fdatasync = fdatasync
exports.fstat = fstat
exports.fsync = fsync
exports.ftruncate = ftruncate
exports.futimes = futimes
exports.lchown = lchown
exports.lutimes = lutimes
exports.link = link
exports.lstat = lstat
exports.mkdir = mkdir
exports.mkdtemp = mkdtemp
exports.open = open
exports.opendir = opendir
exports.read = read
exports.readFile = readFile
exports.readdir = readdir
exports.readlink = readlink
exports.readv = readv
exports.realpath = realpath
exports.rename = rename
exports.rm = rm
exports.rmdir = rmdir
exports.stat = stat
exports.statfs = statfs
exports.symlink = symlink
exports.truncate = truncate
exports.unlink = unlink
exports.utimes = utimes
exports.watch = watch
exports.write = write
exports.writeFile = writeFile
exports.writev = writev

exports.accessSync = accessSync
exports.appendFileSync = appendFileSync
exports.chmodSync = chmodSync
exports.chownSync = chownSync
exports.closeSync = closeSync
exports.copyFileSync = copyFileSync
exports.cpSync = cpSync
exports.existsSync = existsSync
exports.fchmodSync = fchmodSync
exports.fchownSync = fchownSync
exports.fdatasyncSync = fdatasyncSync
exports.fstatSync = fstatSync
exports.fsyncSync = fsyncSync
exports.ftruncateSync = ftruncateSync
exports.futimesSync = futimesSync
exports.lchownSync = lchownSync
exports.lutimesSync = lutimesSync
exports.linkSync = linkSync
exports.lstatSync = lstatSync
exports.mkdirSync = mkdirSync
exports.mkdtempSync = mkdtempSync
exports.openSync = openSync
exports.opendirSync = opendirSync
exports.readFileSync = readFileSync
exports.readSync = readSync
exports.readdirSync = readdirSync
exports.readlinkSync = readlinkSync
exports.readvSync = readvSync
exports.realpathSync = realpathSync
exports.renameSync = renameSync
exports.rmSync = rmSync
exports.rmdirSync = rmdirSync
exports.statSync = statSync
exports.statfsSync = statfsSync
exports.symlinkSync = symlinkSync
exports.truncateSync = truncateSync
exports.unlinkSync = unlinkSync
exports.utimesSync = utimesSync
exports.writeFileSync = writeFileSync
exports.writeSync = writeSync
exports.writevSync = writevSync

exports.promises = require('./promises')

exports.Stats = Stats
exports.StatFs = StatFs
exports.Dir = Dir
exports.Dirent = Dirent
exports.Watcher = Watcher

exports.ReadStream = FileReadStream

exports.createReadStream = function createReadStream(path, opts) {
  return new FileReadStream(path, opts)
}

exports.WriteStream = FileWriteStream

exports.createWriteStream = function createWriteStream(path, opts) {
  return new FileWriteStream(path, opts)
}

function toNamespacedPath(filepath) {
  if (typeof filepath !== 'string') {
    if (isURL(filepath)) filepath = fileURLToPath(filepath)
    else filepath = filepath.toString()
  }

  return path.toNamespacedPath(filepath)
}

function toFlags(flags) {
  switch (flags) {
    case 'r':
      return constants.O_RDONLY
    case 'rs': // Fall through.
    case 'sr':
      return constants.O_RDONLY | constants.O_SYNC
    case 'r+':
      return constants.O_RDWR
    case 'rs+': // Fall through.
    case 'sr+':
      return constants.O_RDWR | constants.O_SYNC

    case 'w':
      return constants.O_TRUNC | constants.O_CREAT | constants.O_WRONLY
    case 'wx': // Fall through.
    case 'xw':
      return constants.O_TRUNC | constants.O_CREAT | constants.O_WRONLY | constants.O_EXCL

    case 'w+':
      return constants.O_TRUNC | constants.O_CREAT | constants.O_RDWR
    case 'wx+': // Fall through.
    case 'xw+':
      return constants.O_TRUNC | constants.O_CREAT | constants.O_RDWR | constants.O_EXCL

    case 'a':
      return constants.O_APPEND | constants.O_CREAT | constants.O_WRONLY
    case 'ax': // Fall through.
    case 'xa':
      return constants.O_APPEND | constants.O_CREAT | constants.O_WRONLY | constants.O_EXCL
    case 'as': // Fall through.
    case 'sa':
      return constants.O_APPEND | constants.O_CREAT | constants.O_WRONLY | constants.O_SYNC

    case 'a+':
      return constants.O_APPEND | constants.O_CREAT | constants.O_RDWR
    case 'ax+': // Fall through.
    case 'xa+':
      return constants.O_APPEND | constants.O_CREAT | constants.O_RDWR | constants.O_EXCL
    case 'as+': // Fall through.
    case 'sa+':
      return constants.O_APPEND | constants.O_CREAT | constants.O_RDWR | constants.O_SYNC
    default:
      return 0
  }
}

function toMode(mode) {
  return parseInt(mode, 8)
}
const binding = require('../binding')

module.exports = {
  O_RDWR: binding.O_RDWR,
  O_RDONLY: binding.O_RDONLY,
  O_WRONLY: binding.O_WRONLY,
  O_CREAT: binding.O_CREAT,
  O_TRUNC: binding.O_TRUNC,
  O_APPEND: binding.O_APPEND,

  F_OK: binding.F_OK || 0,
  R_OK: binding.R_OK || 0,
  W_OK: binding.W_OK || 0,
  X_OK: binding.X_OK || 0,

  S_IFMT: binding.S_IFMT,
  S_IFREG: binding.S_IFREG,
  S_IFDIR: binding.S_IFDIR,
  S_IFCHR: binding.S_IFCHR,
  S_IFLNK: binding.S_IFLNK,
  S_IFBLK: binding.S_IFBLK || 0,
  S_IFIFO: binding.S_IFIFO || 0,
  S_IFSOCK: binding.S_IFSOCK || 0,

  S_IRUSR: binding.S_IRUSR || 0,
  S_IWUSR: binding.S_IWUSR || 0,
  S_IXUSR: binding.S_IXUSR || 0,
  S_IRGRP: binding.S_IRGRP || 0,
  S_IWGRP: binding.S_IWGRP || 0,
  S_IXGRP: binding.S_IXGRP || 0,
  S_IROTH: binding.S_IROTH || 0,
  S_IWOTH: binding.S_IWOTH || 0,
  S_IXOTH: binding.S_IXOTH || 0,

  UV_DIRENT_UNKNOWN: binding.UV_DIRENT_UNKNOWN,
  UV_DIRENT_FILE: binding.UV_DIRENT_FILE,
  UV_DIRENT_DIR: binding.UV_DIRENT_DIR,
  UV_DIRENT_LINK: binding.UV_DIRENT_LINK,
  UV_DIRENT_FIFO: binding.UV_DIRENT_FIFO,
  UV_DIRENT_SOCKET: binding.UV_DIRENT_SOCKET,
  UV_DIRENT_CHAR: binding.UV_DIRENT_CHAR,
  UV_DIRENT_BLOCK: binding.UV_DIRENT_BLOCK,

  COPYFILE_EXCL: binding.UV_FS_COPYFILE_EXCL,
  COPYFILE_FICLONE: binding.UV_FS_COPYFILE_FICLONE,
  COPYFILE_FICLONE_FORCE: binding.UV_FS_COPYFILE_FICLONE_FORCE,
  UV_FS_SYMLINK_DIR: binding.UV_FS_SYMLINK_DIR,
  UV_FS_SYMLINK_JUNCTION: binding.UV_FS_SYMLINK_JUNCTION
}
const binding = require('../binding')

module.exports = class FileError extends Error {
  constructor(msg, opts = {}) {
    const { code, operation = null, path = null, destination = null, fd = -1 } = opts

    if (operation !== null) msg += describe(operation, opts)

    super(`${code}: ${msg}`)

    this.code = code

    if (operation !== null) this.operation = operation
    if (path !== null) this.path = path
    if (destination !== null) this.destination = destination
    if (fd !== -1) this.fd = fd
  }

  get name() {
    return 'FileError'
  }

  // For Node.js compatibility
  get errno() {
    return binding.errnos[this.code]
  }

  // For Node.js compatibility
  get syscall() {
    return this.operation
  }

  // For Node.js compatibility
  get dest() {
    return this.destination
  }
}

function describe(operation, opts) {
  const { path = null, destination = null, fd = -1 } = opts

  let result = `, ${operation}`

  if (path !== null) {
    result += ` ${JSON.stringify(path)}`

    if (destination !== null) {
      result += ` -> ${JSON.stringify(destination)}`
    }
  } else if (fd !== -1) {
    result += ` ${fd}`
  }

  return result
}
{
  "name": "bare-fs",
  "version": "4.8.1",
  "description": "Native file system operations for Bare",
  "exports": {
    "./package": "./package.json",
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    },
    "./promises": {
      "types": "./promises.d.ts",
      "default": "./promises.js"
    },
    "./constants": {
      "types": "./lib/constants.d.ts",
      "default": "./lib/constants.js"
    }
  },
  "files": [
    "index.js",
    "index.d.ts",
    "promises.js",
    "promises.d.ts",
    "binding.c",
    "binding.js",
    "CMakeLists.txt",
    "lib",
    "prebuilds"
  ],
  "addon": true,
  "scripts": {
    "format": "prettier --write . && lunte --fix",
    "lint": "prettier --check . && lunte",
    "test": "brittle-bare --coverage test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-fs.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-fs/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-fs#readme",
  "engines": {
    "bare": ">=1.28.0"
  },
  "dependencies": {
    "bare-events": "^2.5.4",
    "bare-path": "^3.0.0",
    "bare-stream": "^2.6.4",
    "bare-url": "^2.2.2",
    "fast-fifo": "^1.3.2"
  },
  "devDependencies": {
    "bare-buffer": "^3.0.2",
    "bare-crypto": "^1.11.2",
    "brittle": "^3.1.1",
    "cmake-bare": "^1.1.7",
    "lunte": "^1.6.0",
    "prettier": "^3.4.1",
    "prettier-config-holepunch": "^2.0.0"
  },
  "peerDependencies": {
    "bare-buffer": "*"
  },
  "peerDependenciesMeta": {
    "bare-buffer": {
      "optional": true
    }
  }
}
const EventEmitter = require('bare-events')
const fs = require('.')

class FileHandle extends EventEmitter {
  constructor(fd) {
    super()

    this.fd = fd
  }

  async close() {
    await fs.close(this.fd)

    this.fd = -1
    this.emit('close')
  }

  async read(buffer, ...args) {
    return {
      bytesRead: await fs.read(this.fd, buffer, ...args),
      buffer
    }
  }

  async readv(buffers, ...args) {
    return {
      bytesRead: await fs.readv(this.fd, buffers, ...args),
      buffers
    }
  }

  async write(buffer, ...args) {
    return {
      bytesWritten: await fs.write(this.fd, buffer, ...args),
      buffer
    }
  }

  async writev(buffers, ...args) {
    return {
      bytesWritten: await fs.writev(this.fd, buffers, ...args),
      buffers
    }
  }

  async stat() {
    return fs.fstat(this.fd)
  }

  async chmod(mode) {
    await fs.fchmod(this.fd, mode)
  }

  async chown(uid, gid) {
    await fs.fchown(this.fd, uid, gid)
  }

  async datasync() {
    return fs.fdatasync(this.fd)
  }

  async sync() {
    return fs.fsync(this.fd)
  }

  async truncate(len) {
    await fs.ftruncate(this.fd, len)
  }

  async utimes(atime, mtime) {
    await fs.futimes(this.fd, atime, mtime)
  }

  createReadStream(opts) {
    return fs.createReadStream(null, { ...opts, fd: this.fd })
  }

  createWriteStream(opts) {
    return fs.createWriteStream(null, { ...opts, fd: this.fd })
  }

  async [Symbol.asyncDispose]() {
    await this.close()
  }
}

exports.open = async function open(filepath, flags, mode) {
  return new FileHandle(await fs.open(filepath, flags, mode))
}

exports.access = fs.access
exports.appendFile = fs.appendFile
exports.chmod = fs.chmod
exports.chown = fs.chown
exports.constants = fs.constants
exports.copyFile = fs.copyFile
exports.cp = fs.cp
exports.lchown = fs.lchown
exports.lutimes = fs.lutimes
exports.link = fs.link
exports.lstat = fs.lstat
exports.mkdir = fs.mkdir
exports.mkdtemp = fs.mkdtemp
exports.opendir = fs.opendir
exports.readFile = fs.readFile
exports.readdir = fs.readdir
exports.readlink = fs.readlink
exports.realpath = fs.realpath
exports.rename = fs.rename
exports.rm = fs.rm
exports.rmdir = fs.rmdir
exports.stat = fs.stat
exports.statfs = fs.statfs
exports.truncate = fs.truncate
exports.symlink = fs.symlink
exports.unlink = fs.unlink
exports.utimes = fs.utimes
exports.watch = fs.watch
exports.writeFile = fs.writeFile
module.exports = require.addon()
const ansiEscapes = require('bare-ansi-escapes')
const getType = require('bare-type')
const binding = require('./binding')

const defaultDepth = 2
const defaultBreakLength = 80
const defaultMaxArrayLength = 40

module.exports = exports = function inspect(value, opts = {}) {
  const {
    colors = false,
    depth = defaultDepth,
    breakLength = defaultBreakLength,
    stylize = defaultStylize(colors)
  } = opts

  const references = new InspectRefMap()

  const tree = inspectValue(value, 0, {
    colors,
    depth,
    breakLength,
    stylize,
    references
  })

  return tree.toString()
}

exports.styles = {
  bigint: ansiEscapes.colorYellow,
  boolean: ansiEscapes.colorYellow,
  date: ansiEscapes.colorMagenta,
  module: ansiEscapes.modifierUnderline,
  name: ansiEscapes.modifierReset,
  null: ansiEscapes.modifierBold,
  number: ansiEscapes.colorYellow,
  regexp: ansiEscapes.colorRed,
  special: ansiEscapes.colorCyan,
  string: ansiEscapes.colorGreen,
  symbol: ansiEscapes.colorGreen,
  undefined: ansiEscapes.colorBrightBlack
}

const styles = exports.styles

function defaultStylize(colors) {
  return function stylize(value, style) {
    const color = colors && styles[style]

    if (color) return color + value + ansiEscapes.modifierReset

    return value
  }
}

class InspectRefMap {
  constructor() {
    this.refs = new WeakMap()
    this.ids = new WeakMap()
    this.nextId = 1
  }

  has(object) {
    return this.refs.has(object)
  }

  get(object) {
    return this.refs.get(object) || null
  }

  set(object, ref) {
    this.refs.set(object, ref)
  }

  id(object) {
    let id = this.ids.get(object)
    if (id) return id

    id = this.nextId++
    this.ids.set(object, id)

    return id
  }
}

class InspectNode {
  constructor(depth, length, opts) {
    const { breakLength = defaultBreakLength, breakAlways = false } = opts

    this.depth = depth
    this.length = length
    this.breakLength = breakLength
    this.breakAlways = breakAlways
  }

  pad(n, string) {
    return string.padStart(n, ' ')
  }

  indent(n, string) {
    return '  '.repeat(n) + string
  }
}

class InspectRef extends InspectNode {
  constructor(depth, opts) {
    super(depth, '[circular *]'.length, opts)

    this.refs = opts.references
    this.count = 0
    this.circular = false
    this.color = opts.colors && styles.special
  }

  get id() {
    return this.refs.id(this)
  }

  increment() {
    return ++this.count
  }

  decrement() {
    return --this.count
  }

  toString(opts = {}) {
    const { offset = 0, pad = 0, indent = 0 } = opts

    let value = this.pad(pad, '[circular *' + this.id + ']')

    if (this.color) value = this.color + value + ansiEscapes.modifierReset

    return offset ? value : this.indent(indent, value)
  }
}

class InspectLeaf extends InspectNode {
  constructor(value, color, depth, opts) {
    const length = value.length

    if (value.includes('\n')) {
      value = value.replaceAll('\n', '\n' + '  '.repeat(depth))

      opts = { ...opts, breakAlways: true }
    }

    super(depth, length, opts)

    this.value = value
    this.color = opts.colors && color
  }

  toString(opts = {}) {
    const { offset = 0, pad = 0, indent = 0 } = opts

    let value = this.pad(pad, this.value)

    if (this.color) value = this.color + value + ansiEscapes.modifierReset

    return offset ? value : this.indent(indent, value)
  }
}

class InspectPair extends InspectNode {
  constructor(delim, left, right, depth, opts) {
    const length = left.length + delim.length + right.length

    if (left.breakAlways || right.breakAlways) {
      opts = { ...opts, breakAlways: true }
    }

    super(depth, length, opts)

    this.delim = delim
    this.left = left
    this.right = right
  }

  toString(opts = {}) {
    const { indent = 0 } = opts

    return this.indent(
      indent,
      this.left +
        this.delim +
        this.right.toString({
          indent,
          offset: this.left.length + this.delim.length
        })
    )
  }
}

class InspectSuspension extends InspectNode {
  constructor(overflow, depth, opts) {
    const label = `... ${overflow} more`

    super(depth, label.length, opts)

    this.label = label
  }

  toString(opts = {}) {
    const { indent = 0 } = opts

    return this.indent(indent, this.label)
  }
}

class InspectSequence extends InspectNode {
  constructor(header, footer, delim, values, ref, depth, opts) {
    const { tabulate = false } = opts

    const length =
      (ref.circular ? '<ref *>'.length + 1 : 0) +
      header.length +
      values.reduce((length, value, i) => length + value.length + (i === 0 ? 0 : delim.length), 0) +
      footer.length

    if (values.some((value) => value.breakAlways)) {
      opts = { ...opts, breakAlways: true }
    }

    super(depth, length, opts)

    this.header = header
    this.footer = footer
    this.delim = delim
    this.values = values
    this.ref = ref
    this.tabulate = tabulate
  }

  toString(opts = {}) {
    const { offset = 0, indent = 0 } = opts

    const split =
      this.breakAlways ||
      (this.values.length &&
        (offset + this.length > this.breakLength || indent * 2 + this.length > this.breakLength))

    let header = this.header

    if (this.ref.circular) {
      header = '<ref *' + this.ref.id + '> ' + header
    }

    if (this.values.length === 0) {
      header = header.trimEnd()
    }

    if (offset === 0) {
      header = this.indent(indent, header)
    }

    if (split) {
      header = header.trimEnd() + '\n'
    }

    let string = header

    let columns = 1
    let pad = 0

    if (this.tabulate) {
      const widest = this.values.reduce(
        (length, value) => (value.breakAlways ? length : Math.max(length, value.length)),
        0
      )

      if (widest) {
        columns = Math.max(
          columns,
          Math.floor((this.breakLength - indent * 2) / (widest + this.delim.length))
        )

        if (columns > 1) pad = widest
      }
    }

    for (let i = 0, n = this.values.length, offset = 0; i < n; i++) {
      const value = this.values[i]

      if (split) {
        let part

        if (i % columns === 0 || value.breakAlways) {
          part = value.toString({ indent: indent + 1, pad })
        } else {
          part = value.toString({ pad })
        }

        string += part

        if (i < n - 1) {
          if (i % columns === columns - 1 || this.values[i + 1].breakAlways) {
            string += this.delim.trimEnd() + '\n'
          } else {
            string += this.delim
          }
        }
      } else {
        if (i > 0) string += this.delim

        string += value.toString({ offset })

        // TODO Lunte thinks this refers to `const { offset }...` further up
        offset += value.length // eslint-disable-line
      }
    }

    let footer = this.footer

    if (this.values.length === 0) {
      footer = footer.trimStart()
    }

    if (split) {
      string += '\n' + this.indent(indent, footer.trimStart())
    } else {
      string += footer
    }

    return string
  }
}

function inspectValue(value, depth, opts) {
  const type = getType(value)

  if (type.isUndefined()) return inspectUndefined(depth, opts)
  if (type.isNull()) return inspectNull(depth, opts)
  if (type.isBoolean()) return inspectBoolean(value, depth, opts)
  if (type.isNumber()) return inspectNumber(value, depth, opts)
  if (type.isBigInt()) return inspectBigInt(value, depth, opts)
  if (type.isString()) return inspectString(value, depth, opts)
  if (type.isSymbol()) return inspectSymbol(value, depth, opts)
  if (type.isObject()) return inspectObject(type, value, depth, opts)
  if (type.isFunction()) return inspectFunction(type, value, depth, opts)
  if (type.isExternal()) return inspectExternal(value, depth, opts)
}

function inspectUndefined(depth, opts) {
  return new InspectLeaf('undefined', styles.undefined, depth, opts)
}

function inspectNull(depth, opts) {
  return new InspectLeaf('null', styles.null, depth, opts)
}

function inspectBoolean(value, depth, opts) {
  return new InspectLeaf(value.toString(), styles.boolean, depth, opts)
}

function inspectNumber(value, depth, opts) {
  let string

  if (Object.is(value, -0)) {
    string = '-0'
  } else {
    string = value.toString(10)
  }

  return new InspectLeaf(string, styles.number, depth, opts)
}

function inspectBigInt(value, depth, opts) {
  return new InspectLeaf(value.toString(10) + 'n', styles.bigint, depth, opts)
}

const STRING_ESCAPES = /[\ud800-\udbff][\udc00-\udfff]|[\u0000-\u001f'\\\ud800-\udfff]/g

function inspectString(value, depth, opts) {
  // https://tc39.es/ecma262/multipage/structured-data.html#sec-quotejsonstring
  const string = value.replace(STRING_ESCAPES, (match) => {
    if (match.length === 2) return match

    switch (match) {
      case "'":
        return "\\'"
      case '\\':
        return '\\\\'
      case '\b':
        return '\\b'
      case '\t':
        return '\\t'
      case '\n':
        return '\\n'
      case '\f':
        return '\\f'
      case '\r':
        return '\\r'
      default:
        return '\\u' + match.charCodeAt(0).toString(16).padStart(4, '0')
    }
  })

  return new InspectLeaf("'" + string + "'", styles.string, depth, opts)
}

function inspectSymbol(value, depth, opts) {
  return new InspectLeaf(value.toString(), styles.symbol, depth, opts)
}

const PLAIN_KEY = /^[a-zA-Z_][a-zA-Z_0-9]*$/

function inspectKey(value, depth, opts) {
  if (typeof value === 'symbol') {
    return inspectValue(value, depth, opts)
  } else if (PLAIN_KEY.test(value)) {
    return new InspectLeaf(value, null, depth, opts)
  } else {
    return inspectValue(value, depth, opts)
  }
}

function inspectObject(type, object, depth, opts) {
  const refs = opts.references

  let ref = refs.get(object)
  if (ref === null) {
    ref = new InspectRef(depth, opts)
    refs.set(object, ref)
  } else if (ref.count) {
    ref.circular = true
    return ref
  }

  const maxDepth = typeof opts.depth === 'number' ? opts.depth : Infinity

  if (maxDepth < depth) {
    const name = nameOf(object)

    return new InspectLeaf('[' + (name || 'Object') + ']', styles.special, depth, opts)
  }

  const { value: inspect } = attempt(
    () => object[Symbol.for('bare.inspect')] || object[Symbol.for('nodejs.util.inspect.custom')]
  )

  if (typeof inspect === 'function' && isPrototype(object) === false) {
    const { value, threw, error } = attempt(() =>
      inspect.call(
        object,
        typeof opts.depth === 'number' ? opts.depth - depth : null,
        {
          colors: opts.colors,
          breakLength: opts.breakLength,
          stylize: opts.stylize
        },
        exports
      )
    )

    if (threw) {
      return new InspectLeaf(
        '<inspect threw ' + errorLabel(error) + '>',
        styles.special,
        depth,
        opts
      )
    }

    if (typeof value === 'object' && value !== null) {
      refs.set(value, ref)
    }

    if (typeof value !== 'string') {
      return inspectValue(value, depth, opts)
    }

    return new InspectLeaf(value, null, depth, opts)
  }

  if (type.isArray()) return inspectArray(object, ref, depth, opts)
  if (type.isDate()) return inspectDate(object, ref, depth, opts)
  if (type.isRegExp()) return inspectRegExp(object, ref, depth, opts)
  if (type.isError()) return inspectError(object, ref, depth, opts)
  if (type.isPromise()) return inspectPromise(object, ref, depth, opts)
  if (type.isMap()) return inspectMap(object, ref, depth, opts)
  if (type.isSet()) return inspectSet(object, ref, depth, opts)
  if (type.isWeakMap()) return inspectWeakMap(object, ref, depth, opts)
  if (type.isWeakSet()) return inspectWeakSet(object, ref, depth, opts)
  if (type.isWeakRef()) return inspectWeakRef(object, ref, depth, opts)
  if (type.isArrayBuffer()) return inspectArrayBuffer(object, ref, depth, opts)
  if (type.isSharedArrayBuffer()) return inspectSharedArrayBuffer(object, ref, depth, opts)
  if (type.isTypedArray()) return inspectTypedArray(object, ref, depth, opts)
  if (type.isDataView()) return inspectDataView(object, ref, depth, opts)

  ref.increment()

  const values = []

  for (const key of keysOf(object)) {
    if (key === 'constructor') continue

    values.push(
      new InspectPair(
        ': ',
        inspectKey(key, depth + 1, opts),
        inspectProperty(object, key, depth + 1, opts),
        depth + 1,
        opts
      )
    )
  }

  for (const key of symbolsOf(object)) {
    values.push(
      new InspectPair(
        ': ',
        inspectKey(key, depth + 1, opts),
        inspectProperty(object, key, depth + 1, opts),
        depth + 1,
        opts
      )
    )
  }

  ref.decrement()

  let header = '{ '

  const { value: tag } = attempt(() => object[Symbol.toStringTag])

  if (typeof tag === 'string' && tag) header = '[' + tag + '] ' + header

  const name = nameOf(object)

  if (name && name !== 'Object') header = name + ' ' + header

  return new InspectSequence(header, ' }', ', ', values, ref, depth, opts)
}

function inspectArray(array, ref, depth, opts) {
  const { maxArrayLength = defaultMaxArrayLength } = opts

  ref.increment()

  const values = []

  let remaining = Math.max(maxArrayLength, 0)

  for (let i = 0, n = array.length; i < n; i++) {
    if (remaining-- === 0) {
      values.push(
        new InspectSuspension(array.length - values.length, depth + 1, {
          ...opts,
          breakAlways: true
        })
      )

      break
    }

    values.push(inspectProperty(array, i, depth + 1, opts))
  }

  for (const key of nonIndexKeysOf(array)) {
    values.push(
      new InspectPair(
        ': ',
        inspectKey(key, depth + 1, opts),
        inspectProperty(array, key, depth + 1, opts),
        depth + 1,
        { ...opts, breakAlways: remaining < 0 }
      )
    )
  }

  ref.decrement()

  let header = '[ '

  const name = nameOf(array)

  if (name && name !== 'Array') header = name + '(' + array.length + ') ' + header

  return new InspectSequence(header, ' ]', ', ', values, ref, depth, {
    ...opts,
    tabulate: true
  })
}

function inspectProperty(object, key, depth, opts) {
  const descriptor = descriptorOf(object, key)

  if (descriptor === null) {
    return new InspectLeaf('<unreadable>', styles.special, depth, opts)
  }

  if (descriptor === undefined) return inspectUndefined(depth, opts)

  if ('value' in descriptor === false) {
    let label = '[getter/setter]'

    if (descriptor.get === undefined) label = '[setter]'
    else if (descriptor.set === undefined) label = '[getter]'

    return new InspectLeaf(label, styles.special, depth, opts)
  }

  return inspectValue(descriptor.value, depth, opts)
}

function inspectDate(date, ref, depth, opts) {
  const { value: time } = attempt(() => date.getTime())

  if (Number.isNaN(time)) {
    return new InspectLeaf('Invalid Date', styles.date, depth, opts)
  }

  const { value: string } = attempt(() => date.toISOString())

  return new InspectLeaf(string || '[Date]', styles.date, depth, opts)
}

function inspectRegExp(regExp, ref, depth, opts) {
  const { value: string } = attempt(() => regExp.toString())

  return new InspectLeaf(string || '[RegExp]', styles.regexp, depth, opts)
}

function inspectError(error, ref, depth, opts) {
  let header = attempt(() => error.stack).value

  if (typeof header === 'string') {
    // Not all engines put the `<name>: <message>` line at the top of `stack`,
    // so add it when it's missing.
    const title = attempt(() => error.toString()).value

    if (typeof title === 'string' && !header.startsWith(title)) {
      header = title + '\n' + header
    }

    if (depth > 0) {
      header = header.replaceAll('\n', '\n' + '  '.repeat(depth))
    }
  } else {
    header = attempt(() => error.toString()).value

    if (typeof header !== 'string') header = errorLabel(error)
  }

  const builtins = ['cause']

  const { value: name } = attempt(() => error.name)

  if (name === 'AggregateError') {
    builtins.push('errors')
  } else if (name === 'SuppressedError') {
    builtins.push('error', 'suppressed')
  }

  const values = []

  for (const key of builtins) {
    if (descriptorOf(error, key) === undefined) continue

    values.push(
      new InspectPair(
        ': ',
        new InspectLeaf('[' + key + ']', null, depth + 1, opts),
        inspectProperty(error, key, depth + 1, opts),
        depth + 1,
        opts
      )
    )
  }

  for (const key of keysOf(error)) {
    if (key === 'constructor' || builtins.includes(key)) continue

    values.push(
      new InspectPair(
        ': ',
        inspectKey(key, depth + 1, opts),
        inspectProperty(error, key, depth + 1, opts),
        depth + 1,
        opts
      )
    )
  }

  if (values.length === 0) return new InspectLeaf(header, null, depth, opts)

  return new InspectSequence(header + ' {', ' }', ', ', values, ref, depth, opts)
}

function inspectPromise(promise, ref, depth, opts) {
  ref.increment()

  const state = binding.getPromiseState(promise)

  const values = []

  switch (state) {
    case 0: // Pending
      values.push(new InspectLeaf('<pending>', styles.special, depth, opts))
      break

    case 1: // Fulfilled
      values.push(inspectValue(binding.getPromiseResult(promise), depth, opts))
      break

    case 2: // Rejected
      values.push(
        new InspectLeaf('<rejected>', styles.special, depth, opts),
        inspectValue(binding.getPromiseResult(promise), depth, opts)
      )
  }

  ref.decrement()

  const header = (nameOf(promise) || 'Promise') + ' { '

  return new InspectSequence(header, ' }', ' ', values, ref, depth, opts)
}

function inspectMap(map, ref, depth, opts) {
  const { maxArrayLength = defaultMaxArrayLength, maxMapLength = maxArrayLength } = opts

  ref.increment()

  const values = []

  let remaining = maxMapLength

  const { value: size } = attempt(() => map.size)

  attempt(() => {
    for (const entry of map) {
      if (remaining-- === 0) {
        values.push(
          new InspectSuspension(size - values.length, depth + 1, {
            ...opts,
            breakAlways: true
          })
        )
        break
      }

      values.push(
        new InspectPair(
          ' => ',
          inspectValue(entry[0], depth + 1, opts),
          inspectValue(entry[1], depth + 1, opts),
          depth + 1,
          opts
        )
      )
    }
  })

  for (const key of keysOf(map)) {
    if (key === 'constructor') continue

    const value = inspectProperty(map, key, depth + 1, opts)

    values.push(
      new InspectPair(': ', inspectKey(key, depth + 1, opts), value, depth + 1, {
        ...opts,
        breakAlways: remaining < 0
      })
    )
  }

  ref.decrement()

  const header = (nameOf(map) || 'Map') + '(' + size + ') { '

  return new InspectSequence(header, ' }', ', ', values, ref, depth, {
    ...opts,
    tabulate: true
  })
}

function inspectSet(set, ref, depth, opts) {
  const { maxArrayLength = defaultMaxArrayLength, maxSetLength = maxArrayLength } = opts

  ref.increment()

  const values = []

  let remaining = maxSetLength

  const { value: size } = attempt(() => set.size)

  attempt(() => {
    for (const entry of set) {
      if (remaining-- === 0) {
        values.push(
          new InspectSuspension(size - values.length, depth + 1, {
            ...opts,
            breakAlways: true
          })
        )
        break
      }

      values.push(inspectValue(entry, depth + 1, opts))
    }
  })

  for (const key of keysOf(set)) {
    if (key === 'constructor') continue

    const value = inspectProperty(set, key, depth + 1, opts)

    values.push(
      new InspectPair(': ', inspectKey(key, depth + 1, opts), value, depth + 1, {
        ...opts,
        breakAlways: remaining < 0
      })
    )
  }

  ref.decrement()

  const header = (nameOf(set) || 'Set') + '(' + size + ') { '

  return new InspectSequence(header, ' }', ', ', values, ref, depth, {
    ...opts,
    tabulate: true
  })
}

function inspectWeakMap(weakMap, ref, depth, opts) {
  const header = (nameOf(weakMap) || 'WeakMap') + ' { '

  return new InspectSequence(
    header,
    ' }',
    ' ',
    [new InspectLeaf('<items unknown>', styles.special, depth + 1, opts)],
    ref,
    depth,
    opts
  )
}

function inspectWeakSet(weakSet, ref, depth, opts) {
  const header = (nameOf(weakSet) || 'WeakSet') + ' { '

  return new InspectSequence(
    header,
    ' }',
    ' ',
    [new InspectLeaf('<items unknown>', styles.special, depth + 1, opts)],
    ref,
    depth,
    opts
  )
}

function inspectWeakRef(weakRef, ref, depth, opts) {
  const { value: target } = attempt(() => weakRef.deref())

  let value

  if (target === undefined) {
    value = new InspectLeaf('<cleared>', styles.special, depth + 1, opts)
  } else {
    value = inspectValue(target, depth + 1, opts)
  }

  const header = (nameOf(weakRef) || 'WeakRef') + ' { '

  return new InspectSequence(header, ' }', ' ', [value], ref, depth, opts)
}

function inspectArrayBuffer(arrayBuffer, ref, depth, opts) {
  ref.increment()

  const values = []

  for (const key of ['byteLength']) {
    values.push(
      new InspectPair(
        ': ',
        inspectKey(key, depth + 1, opts),
        inspectValue(arrayBuffer[key], depth + 1, opts),
        depth + 1,
        opts
      )
    )
  }

  ref.decrement()

  const header = (nameOf(arrayBuffer) || 'ArrayBuffer') + ' { '

  return new InspectSequence(header, ' }', ', ', values, ref, depth, opts)
}

function inspectSharedArrayBuffer(sharedArrayBuffer, ref, depth, opts) {
  ref.increment()

  const values = []

  for (const key of ['byteLength']) {
    values.push(
      new InspectPair(
        ': ',
        inspectKey(key, depth + 1, opts),
        inspectValue(sharedArrayBuffer[key], depth + 1, opts),
        depth + 1,
        opts
      )
    )
  }

  ref.decrement()

  const header = (nameOf(sharedArrayBuffer) || 'SharedArrayBuffer') + ' { '

  return new InspectSequence(header, ' }', ', ', values, ref, depth, opts)
}

function inspectTypedArray(typedArray, ref, depth, opts) {
  if (Buffer.isBuffer(typedArray)) {
    return inspectBuffer(typedArray, ref, depth, opts)
  }

  const { maxArrayLength = defaultMaxArrayLength, maxTypedArrayLength = maxArrayLength } = opts

  ref.increment()

  const values = []

  let remaining = Math.max(maxTypedArrayLength, 0)

  for (let i = 0, n = typedArray.length; i < n; i++) {
    if (remaining-- === 0) {
      values.push(
        new InspectSuspension(typedArray.length - values.length, depth + 1, {
          ...opts,
          breakAlways: true
        })
      )

      break
    }

    values.push(inspectValue(typedArray[i], depth + 1, opts))
  }

  for (const key of nonIndexKeysOf(typedArray)) {
    values.push(
      new InspectPair(
        ': ',
        inspectKey(key, depth + 1, opts),
        inspectProperty(typedArray, key, depth + 1, opts),
        depth + 1,
        { ...opts, breakAlways: remaining < 0 }
      )
    )
  }

  ref.decrement()

  const header = (nameOf(typedArray) || 'TypedArray') + '(' + typedArray.length + ') [ '

  return new InspectSequence(header, ' ]', ', ', values, ref, depth, {
    ...opts,
    tabulate: true
  })
}

function inspectBuffer(buffer, ref, depth, opts) {
  const { maxArrayLength = defaultMaxArrayLength, maxBufferLength = maxArrayLength } = opts

  ref.increment()

  const values = []

  let remaining = Math.max(maxBufferLength, 0)

  for (let i = 0, n = buffer.byteLength; i < n; i++) {
    if (remaining-- === 0) {
      values.push(
        new InspectSuspension(buffer.length - values.length, depth + 1, {
          ...opts,
          breakAlways: true
        })
      )

      break
    }

    values.push(new InspectLeaf(buffer[i].toString(16).padStart(2, '0'), null, depth + 1, opts))
  }

  for (const key of nonIndexKeysOf(buffer)) {
    values.push(
      new InspectPair(
        ': ',
        inspectKey(key, depth + 1, opts),
        inspectProperty(buffer, key, depth + 1, opts),
        depth + 1,
        { ...opts, breakAlways: remaining < 0 }
      )
    )
  }

  ref.decrement()

  return new InspectSequence('<Buffer ', '>', ' ', values, ref, depth, {
    ...opts,
    tabulate: true
  })
}

function inspectDataView(dataView, ref, depth, opts) {
  ref.increment()

  const values = []

  for (const key of ['byteLength', 'byteOffset', 'buffer']) {
    values.push(
      new InspectPair(
        ': ',
        inspectKey(key, depth + 1, opts),
        inspectValue(dataView[key], depth + 1, opts),
        depth + 1,
        opts
      )
    )
  }

  for (const key of nonIndexKeysOf(dataView)) {
    values.push(
      new InspectPair(
        ': ',
        inspectKey(key, depth + 1, opts),
        inspectProperty(dataView, key, depth + 1, opts),
        depth + 1,
        opts
      )
    )
  }

  ref.decrement()

  const header = (nameOf(dataView) || 'DataView') + ' { '

  return new InspectSequence(header, ' }', ', ', values, ref, depth, opts)
}

function inspectFunction(type, fn, depth, opts) {
  const { value: source } = attempt(() => fn.toString())

  if (typeof source === 'string' && source.startsWith('class')) {
    return inspectClass(fn, depth, opts)
  }

  let tag = 'function'

  if (type.isGeneratorFunction()) tag = 'generator ' + tag
  if (type.isAsyncFunction()) tag = 'async ' + tag

  const { value: name } = attempt(() => fn.name)

  return new InspectLeaf(
    '[' + tag + ' ' + (name ? name : '(anonymous)') + ']',
    styles.special,
    depth,
    opts
  )
}

function inspectClass(ctor, depth, opts) {
  const { value: name } = attempt(() => ctor.name)

  return new InspectLeaf(
    '[class ' + (name ? name : '(anonymous)') + ']',
    styles.special,
    depth,
    opts
  )
}

// Externals are labelled by an opaque, per-process token so that their address
// is never disclosed.
const externalTokens = new WeakMap()

let nextExternalToken = 1

function inspectExternal(external, depth, opts) {
  let token = externalTokens.get(external)

  if (token === undefined) {
    token = nextExternalToken++

    externalTokens.set(external, token)
  }

  return new InspectLeaf('[external #' + token + ']', styles.special, depth, opts)
}

// An inspection must not fail on the value it inspects, so everything read from
// that value is read through one of the helpers below. Values are read for
// their own sake, which is why an accessor is reported rather than called, but
// the labels an inspection puts around them are read as they are written.

function attempt(fn) {
  try {
    return { value: fn(), threw: false, error: null }
  } catch (err) {
    return { value: undefined, threw: true, error: err }
  }
}

// The name a value is labelled with, which is the name of its constructor.
function nameOf(object) {
  const { value: name } = attempt(() => object.constructor.name)

  return typeof name === 'string' ? name : ''
}

// A prototype inherits the custom inspection written for its instances, which
// must not be called on the prototype itself as it is not one of them.
function isPrototype(object) {
  const { value: prototype } = attempt(() => object.constructor.prototype)

  return prototype === object
}

function keysOf(object) {
  const keys = []

  // A partial list is the best that can be had from a value that stops
  // answering halfway through.
  attempt(() => {
    for (const key in object) keys.push(key)
  })

  return keys
}

function symbolsOf(object) {
  const symbols = []

  for (const symbol of attempt(() => Object.getOwnPropertySymbols(object)).value || []) {
    const { value: descriptor, threw } = attempt(() =>
      Object.getOwnPropertyDescriptor(object, symbol)
    )

    if (threw || descriptor === undefined || descriptor.enumerable) symbols.push(symbol)
  }

  return symbols
}

function nonIndexKeysOf(object) {
  return attempt(() => binding.getOwnNonIndexPropertyNames(object)).value || []
}

// Returns the descriptor, `undefined` if the property is absent, or `null` if
// the lookup itself could not be made, as with a proxy that throws.
function descriptorOf(object, key) {
  let target = object

  while (target !== null && target !== undefined) {
    const { value: descriptor, threw } = attempt(() => Object.getOwnPropertyDescriptor(target, key))

    if (threw) return null

    if (descriptor) return descriptor

    const { value: prototype, threw: unreadable } = attempt(() => Object.getPrototypeOf(target))

    if (unreadable) return null

    target = prototype
  }

  return undefined
}

function errorLabel(err) {
  if (err === null || typeof err !== 'object') return 'a non-error'

  const { value: message } = attempt(() => err.message)

  const name = nameOf(err) || 'Error'

  return typeof message === 'string' && message ? name + ': ' + message : name
}
{
  "name": "bare-inspect",
  "version": "3.1.10",
  "description": "Inspect objects as strings for debugging",
  "exports": {
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    },
    "./package": "./package.json"
  },
  "files": [
    "index.js",
    "index.d.ts",
    "binding.c",
    "binding.js",
    "CMakeLists.txt",
    "prebuilds"
  ],
  "addon": true,
  "scripts": {
    "format": "prettier --write . && lunte --fix",
    "lint": "prettier --check . && lunte",
    "test": "brittle-bare --coverage test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-inspect.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-inspect/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-inspect#readme",
  "engines": {
    "bare": ">=1.18.0"
  },
  "dependencies": {
    "bare-ansi-escapes": "^2.1.0",
    "bare-type": "^1.0.0"
  },
  "devDependencies": {
    "brittle": "^3.3.2",
    "cmake-bare": "^1.1.6",
    "lunte": "^1.8.4",
    "prettier": "^3.4.1",
    "prettier-config-holepunch": "^2.0.0"
  }
}
module.exports = require.addon()
const binding = require('./binding')
const errors = require('./lib/errors')
const constants = require('./lib/constants')

exports.constants = constants
exports.errors = errors

exports.EOL = binding.platform === 'win32' ? '\r\n' : '\n'

exports.devNull = binding.platform === 'win32' ? '\\\\.\\nul' : '/dev/null'

exports.platform = function platform() {
  return binding.platform
}

exports.arch = function arch() {
  return binding.arch
}

exports.type = function type() {
  return binding.type()
}

exports.version = function version() {
  return binding.version()
}

exports.release = function release() {
  return binding.release()
}

exports.machine = function machine() {
  return binding.machine()
}

exports.execPath = function execPath() {
  return binding.execPath()
}

exports.pid = function pid() {
  return binding.pid()
}

exports.ppid = function ppid() {
  return binding.ppid()
}

exports.cwd = function cwd() {
  return binding.cwd()
}

exports.chdir = function chdir(dir) {
  validateString(dir, 'Directory')

  binding.chdir(dir)
}

exports.tmpdir = function tmpdir() {
  return binding.tmpdir()
}

exports.homedir = function homedir() {
  return binding.homedir()
}

exports.hostname = function hostname() {
  return binding.hostname()
}

exports.userInfo = function userInfo(uid) {
  if (uid !== undefined) validateInteger(uid, 'User ID')

  return binding.userInfo(uid)
}

exports.groupInfo = function groupInfo(gid) {
  if (gid !== undefined) validateInteger(gid, 'Group ID')

  return binding.groupInfo(gid)
}

exports.networkInterfaces = function networkInterfaces() {
  const result = {}

  for (const entry of binding.networkInterfaces()) {
    const { name, ...properties } = entry

    if (result[name]) result[name].push(properties)
    else result[name] = [properties]
  }

  return result
}

exports.kill = function kill(pid, signal = constants.signals.SIGTERM) {
  validateInteger(pid, 'Process ID')

  if (typeof signal === 'string') {
    if (signal in constants.signals === false) {
      throw errors.UNKNOWN_SIGNAL('Unknown signal: ' + signal)
    }

    signal = constants.signals[signal]
  } else {
    validateInteger(signal, 'Signal')
  }

  binding.kill(pid, signal)
}

exports.endianness = function endianness() {
  return binding.isLittleEndian ? 'LE' : 'BE'
}

exports.availableParallelism = function availableParallelism() {
  return binding.availableParallelism()
}

exports.cpuUsage = function cpuUsage(previous) {
  const current = binding.cpuUsage()

  if (previous) {
    return {
      user: current.user - previous.user,
      system: current.system - previous.system
    }
  }

  return current
}

exports.threadCpuUsage = function threadCpuUsage(previous) {
  const current = binding.threadCpuUsage()

  if (previous) {
    return {
      user: current.user - previous.user,
      system: current.system - previous.system
    }
  }

  return current
}

exports.resourceUsage = function resourceUsage() {
  return binding.resourceUsage()
}

exports.memoryUsage = function memoryUsage() {
  return binding.memoryUsage()
}

exports.freemem = function freemem() {
  return binding.freemem()
}

exports.totalmem = function totalmem() {
  return binding.totalmem()
}

exports.availableMemory = function availableMemory() {
  return binding.availableMemory()
}

exports.constrainedMemory = function constrainedMemory() {
  return binding.constrainedMemory()
}

exports.uptime = function uptime() {
  return binding.uptime()
}

exports.loadavg = function loadavg() {
  return binding.loadavg()
}

exports.cpus = function cpus() {
  return binding.cpus()
}

exports.getProcessTitle = function getProcessTitle() {
  return binding.getProcessTitle()
}

exports.setProcessTitle = function setProcessTitle(title) {
  if (typeof title !== 'string') title = title.toString()

  if (title.length >= 256) {
    throw errors.TITLE_OVERFLOW('Process title is too long')
  }

  binding.setProcessTitle(title)
}

exports.getPriority = function getPriority(pid = 0) {
  validateInteger(pid, 'Process ID')

  return binding.getPriority(pid)
}

exports.setPriority = function setPriority(pid, priority) {
  if (priority === undefined) {
    priority = pid
    pid = 0
  }

  validateInteger(pid, 'Process ID')
  validateInteger(priority, 'Priority')

  binding.setPriority(pid, priority)
}

exports.getEnvKeys = function getEnvKeys() {
  return binding.getEnvKeys()
}

exports.getEnv = function getEnv(name) {
  validateString(name, 'Name')

  return binding.getEnv(name)
}

exports.hasEnv = function hasEnv(name) {
  validateString(name, 'Name')

  return binding.hasEnv(name)
}

exports.setEnv = function setEnv(name, value) {
  validateString(name, 'Name')
  validateString(value, 'Value')

  binding.setEnv(name, value)
}

exports.unsetEnv = function unsetEnv(name) {
  validateString(name, 'Name')

  binding.unsetEnv(name)
}

function validateString(value, name) {
  if (typeof value !== 'string') {
    throw new TypeError(`${name} must be a string. Received type ${typeof value} (${value})`)
  }
}

function validateInteger(value, name) {
  if (!Number.isInteger(value)) {
    throw new TypeError(`${name} must be an integer. Received type ${typeof value} (${value})`)
  }
}
const binding = require('../binding')

module.exports = {
  signals: binding.signals,
  errnos: binding.errnos,
  priority: binding.priority
}
module.exports = class OSError extends Error {
  constructor(msg, fn = OSError, code = fn.name) {
    super(`${code}: ${msg}`)
    this.code = code

    if (Error.captureStackTrace) {
      Error.captureStackTrace(this, fn)
    }
  }

  get name() {
    return 'OSError'
  }

  static UNKNOWN_SIGNAL(msg) {
    return new OSError(msg, OSError.UNKNOWN_SIGNAL)
  }

  static TITLE_OVERFLOW(msg) {
    return new OSError(msg, OSError.TITLE_OVERFLOW)
  }
}
{
  "name": "bare-os",
  "version": "3.9.3",
  "description": "Operating system utilities for Bare",
  "exports": {
    "./package": "./package.json",
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    },
    "./constants": {
      "types": "./lib/constants.d.ts",
      "default": "./lib/constants.js"
    },
    "./errors": {
      "types": "./lib/errors.d.ts",
      "default": "./lib/errors.js"
    }
  },
  "files": [
    "index.js",
    "index.d.ts",
    "binding.c",
    "binding.js",
    "CMakeLists.txt",
    "lib",
    "prebuilds"
  ],
  "addon": true,
  "scripts": {
    "format": "prettier --write . && lunte --fix",
    "lint": "prettier --check . && lunte",
    "test": "brittle-bare --coverage test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-os.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-os/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-os#readme",
  "engines": {
    "bare": ">=1.14.0"
  },
  "devDependencies": {
    "brittle": "^3.1.1",
    "cmake-bare": "^1.1.6",
    "lunte": "^1.6.0",
    "prettier": "^3.4.2",
    "prettier-config-holepunch": "^2.0.0"
  }
}
module.exports = require.addon()
// This export SHOULD NOT be shortened in any way as having the full
// `module.exports = require(...)` statement is crucial for synthesizing
// ESM exports.

if (Bare.platform === 'win32') {
  module.exports = require('./lib/win32')
} else {
  module.exports = require('./lib/posix')
}
module.exports = {
  CHAR_UPPERCASE_A: 0x41,
  CHAR_LOWERCASE_A: 0x61,
  CHAR_UPPERCASE_Z: 0x5a,
  CHAR_LOWERCASE_Z: 0x7a,
  CHAR_DOT: 0x2e,
  CHAR_FORWARD_SLASH: 0x2f,
  CHAR_BACKWARD_SLASH: 0x5c,
  CHAR_COLON: 0x3a,
  CHAR_QUESTION_MARK: 0x3f
}
const binding = require('../binding')

const { normalizeString, validateString } = require('./shared')
const { CHAR_DOT, CHAR_FORWARD_SLASH } = require('./constants')

function isPosixPathSeparator(code) {
  return code === CHAR_FORWARD_SLASH
}

exports.win32 = require('./win32')
exports.posix = exports

exports.sep = '/'
exports.delimiter = ':'

exports.resolve = function resolve(...args) {
  let resolvedPath = ''
  let resolvedAbsolute = false

  for (let i = args.length - 1; i >= -1 && !resolvedAbsolute; i--) {
    const path = i >= 0 ? args[i] : binding.cwd()

    if (i >= 0) validateString(path, `paths[${i}]`)

    if (path.length === 0) {
      continue
    }

    resolvedPath = `${path}/${resolvedPath}`
    resolvedAbsolute = path.charCodeAt(0) === CHAR_FORWARD_SLASH
  }

  resolvedPath = normalizeString(resolvedPath, !resolvedAbsolute, '/', isPosixPathSeparator)

  if (resolvedAbsolute) {
    return `/${resolvedPath}`
  }

  return resolvedPath.length > 0 ? resolvedPath : '.'
}

exports.normalize = function normalize(path) {
  validateString(path, 'path')

  if (path.length === 0) return '.'

  const isAbsolute = path.charCodeAt(0) === CHAR_FORWARD_SLASH
  const trailingSeparator = path.charCodeAt(path.length - 1) === CHAR_FORWARD_SLASH

  path = normalizeString(path, !isAbsolute, '/', isPosixPathSeparator)

  if (path.length === 0) {
    if (isAbsolute) return '/'
    return trailingSeparator ? './' : '.'
  }

  if (trailingSeparator) path += '/'

  return isAbsolute ? `/${path}` : path
}

exports.isAbsolute = function isAbsolute(path) {
  validateString(path, 'path')

  return path.length > 0 && path.charCodeAt(0) === CHAR_FORWARD_SLASH
}

exports.join = function join(...args) {
  if (args.length === 0) return '.'
  let joined
  for (let i = 0; i < args.length; ++i) {
    const arg = args[i]
    validateString(arg, 'path')
    if (arg.length > 0) {
      if (joined === undefined) joined = arg
      else joined += `/${arg}`
    }
  }
  if (joined === undefined) return '.'
  return exports.normalize(joined)
}

exports.relative = function relative(from, to) {
  validateString(from, 'from')
  validateString(to, 'to')

  if (from === to) return ''

  from = exports.resolve(from)
  to = exports.resolve(to)

  if (from === to) return ''

  const fromStart = 1
  const fromEnd = from.length
  const fromLen = fromEnd - fromStart
  const toStart = 1
  const toLen = to.length - toStart

  const length = fromLen < toLen ? fromLen : toLen
  let lastCommonSep = -1
  let i = 0
  for (; i < length; i++) {
    const fromCode = from.charCodeAt(fromStart + i)
    if (fromCode !== to.charCodeAt(toStart + i)) {
      break
    } else if (fromCode === CHAR_FORWARD_SLASH) {
      lastCommonSep = i
    }
  }
  if (i === length) {
    if (toLen > length) {
      if (to.charCodeAt(toStart + i) === CHAR_FORWARD_SLASH) {
        return to.substring(toStart + i + 1)
      }
      if (i === 0) {
        return to.substring(toStart + i)
      }
    } else if (fromLen > length) {
      if (from.charCodeAt(fromStart + i) === CHAR_FORWARD_SLASH) {
        lastCommonSep = i
      } else if (i === 0) {
        lastCommonSep = 0
      }
    }
  }

  let out = ''
  for (i = fromStart + lastCommonSep + 1; i <= fromEnd; ++i) {
    if (i === fromEnd || from.charCodeAt(i) === CHAR_FORWARD_SLASH) {
      out += out.length === 0 ? '..' : '/..'
    }
  }

  return `${out}${to.substring(toStart + lastCommonSep)}`
}

exports.toNamespacedPath = function toNamespacedPath(path) {
  return path
}

exports.dirname = function dirname(path) {
  validateString(path, 'path')

  if (path.length === 0) return '.'
  const hasRoot = path.charCodeAt(0) === CHAR_FORWARD_SLASH
  let end = -1
  let matchedSlash = true
  for (let i = path.length - 1; i >= 1; --i) {
    if (path.charCodeAt(i) === CHAR_FORWARD_SLASH) {
      if (!matchedSlash) {
        end = i
        break
      }
    } else {
      matchedSlash = false
    }
  }

  if (end === -1) return hasRoot ? '/' : '.'
  if (hasRoot && end === 1) return '//'
  return path.substring(0, end)
}

exports.basename = function basename(path, suffix) {
  if (suffix !== undefined) validateString(suffix, 'suffix')
  validateString(path, 'path')

  let start = 0
  let end = -1
  let matchedSlash = true

  if (suffix !== undefined && suffix.length > 0 && suffix.length <= path.length) {
    if (suffix === path) {
      return ''
    }
    let extIdx = suffix.length - 1
    let firstNonSlashEnd = -1
    for (let i = path.length - 1; i >= 0; --i) {
      const code = path.charCodeAt(i)
      if (code === CHAR_FORWARD_SLASH) {
        if (!matchedSlash) {
          start = i + 1
          break
        }
      } else {
        if (firstNonSlashEnd === -1) {
          matchedSlash = false
          firstNonSlashEnd = i + 1
        }
        if (extIdx >= 0) {
          if (code === suffix.charCodeAt(extIdx)) {
            if (--extIdx === -1) {
              end = i
            }
          } else {
            extIdx = -1
            end = firstNonSlashEnd
          }
        }
      }
    }

    if (start === end) end = firstNonSlashEnd
    else if (end === -1) end = path.length
    return path.substring(start, end)
  }

  for (let i = path.length - 1; i >= 0; --i) {
    if (path.charCodeAt(i) === CHAR_FORWARD_SLASH) {
      if (!matchedSlash) {
        start = i + 1
        break
      }
    } else if (end === -1) {
      matchedSlash = false
      end = i + 1
    }
  }

  if (end === -1) return ''
  return path.substring(start, end)
}

exports.extname = function extname(path) {
  validateString(path, 'path')

  let startDot = -1
  let startPart = 0
  let end = -1
  let matchedSlash = true
  let preDotState = 0
  for (let i = path.length - 1; i >= 0; --i) {
    const code = path.charCodeAt(i)
    if (code === CHAR_FORWARD_SLASH) {
      if (!matchedSlash) {
        startPart = i + 1
        break
      }
      continue
    }
    if (end === -1) {
      matchedSlash = false
      end = i + 1
    }
    if (code === CHAR_DOT) {
      if (startDot === -1) startDot = i
      else if (preDotState !== 1) preDotState = 1
    } else if (startDot !== -1) {
      preDotState = -1
    }
  }

  if (
    startDot === -1 ||
    end === -1 ||
    preDotState === 0 ||
    (preDotState === 1 && startDot === end - 1 && startDot === startPart + 1)
  ) {
    return ''
  }
  return path.substring(startDot, end)
}
const { CHAR_DOT, CHAR_FORWARD_SLASH } = require('./constants')

exports.validateString = function validateString(value, name) {
  if (typeof value !== 'string') {
    throw new TypeError(
      `The "${name}" argument must be of type string. Received type ${typeof value}`
    )
  }
}

exports.normalizeString = function normalizeString(
  path,
  allowAboveRoot,
  separator,
  isPathSeparator
) {
  let res = ''
  let lastSegmentLength = 0
  let lastSlash = -1
  let dots = 0
  let code = 0
  for (let i = 0; i <= path.length; ++i) {
    if (i < path.length) {
      code = path.charCodeAt(i)
    } else if (isPathSeparator(code)) {
      break
    } else {
      code = CHAR_FORWARD_SLASH
    }

    if (isPathSeparator(code)) {
      if (lastSlash === i - 1 || dots === 1);
      else if (dots === 2) {
        if (
          res.length < 2 ||
          lastSegmentLength !== 2 ||
          res.charCodeAt(res.length - 1) !== CHAR_DOT ||
          res.charCodeAt(res.length - 2) !== CHAR_DOT
        ) {
          if (res.length > 2) {
            const lastSlashIndex = res.lastIndexOf(separator)
            if (lastSlashIndex === -1) {
              res = ''
              lastSegmentLength = 0
            } else {
              res = res.substring(0, lastSlashIndex)
              lastSegmentLength = res.length - 1 - res.lastIndexOf(separator)
            }
            lastSlash = i
            dots = 0
            continue
          } else if (res.length !== 0) {
            res = ''
            lastSegmentLength = 0
            lastSlash = i
            dots = 0
            continue
          }
        }
        if (allowAboveRoot) {
          res += res.length > 0 ? `${separator}..` : '..'
          lastSegmentLength = 2
        }
      } else {
        if (res.length > 0) {
          res += `${separator}${path.substring(lastSlash + 1, i)}`
        } else {
          res = path.substring(lastSlash + 1, i)
        }
        lastSegmentLength = i - lastSlash - 1
      }
      lastSlash = i
      dots = 0
    } else if (code === CHAR_DOT && dots !== -1) {
      ++dots
    } else {
      dots = -1
    }
  }
  return res
}
const binding = require('../binding')

const { normalizeString, validateString } = require('./shared')
const {
  CHAR_UPPERCASE_A,
  CHAR_LOWERCASE_A,
  CHAR_UPPERCASE_Z,
  CHAR_LOWERCASE_Z,
  CHAR_DOT,
  CHAR_FORWARD_SLASH,
  CHAR_BACKWARD_SLASH,
  CHAR_COLON,
  CHAR_QUESTION_MARK
} = require('./constants')

function isWindowsPathSeparator(code) {
  return code === CHAR_FORWARD_SLASH || code === CHAR_BACKWARD_SLASH
}

function isWindowsDeviceRoot(code) {
  return (
    (code >= CHAR_UPPERCASE_A && code <= CHAR_UPPERCASE_Z) ||
    (code >= CHAR_LOWERCASE_A && code <= CHAR_LOWERCASE_Z)
  )
}

exports.posix = require('./posix')
exports.win32 = exports

exports.sep = '\\'
exports.delimiter = ';'

exports.resolve = function resolve(...args) {
  let resolvedDevice = ''
  let resolvedTail = ''
  let resolvedAbsolute = false

  for (let i = args.length - 1; i >= -1; i--) {
    let path
    if (i >= 0) {
      path = args[i]

      validateString(path, `paths[${i}]`)

      if (path.length === 0) continue
    } else if (resolvedDevice.length === 0) {
      path = binding.cwd()
    } else {
      path = binding.cwd(resolvedDevice)

      if (
        path === undefined ||
        (path.substring(0, 2).toLowerCase() !== resolvedDevice.toLowerCase() &&
          path.charCodeAt(2) === CHAR_BACKWARD_SLASH)
      ) {
        path = `${resolvedDevice}\\`
      }
    }

    const len = path.length
    let rootEnd = 0
    let device = ''
    let isAbsolute = false
    const code = path.charCodeAt(0)

    if (len === 1) {
      if (isWindowsPathSeparator(code)) {
        rootEnd = 1
        isAbsolute = true
      }
    } else if (isWindowsPathSeparator(code)) {
      isAbsolute = true

      if (isWindowsPathSeparator(path.charCodeAt(1))) {
        let j = 2
        let last = j
        while (j < len && !isWindowsPathSeparator(path.charCodeAt(j))) {
          j++
        }
        if (j < len && j !== last) {
          const firstPart = path.substring(last, j)
          last = j
          while (j < len && isWindowsPathSeparator(path.charCodeAt(j))) {
            j++
          }
          if (j < len && j !== last) {
            last = j
            while (j < len && !isWindowsPathSeparator(path.charCodeAt(j))) {
              j++
            }
            if (j === len || j !== last) {
              device = `\\\\${firstPart}\\${path.substring(last, j)}`
              rootEnd = j
            }
          }
        }
      } else {
        rootEnd = 1
      }
    } else if (isWindowsDeviceRoot(code) && path.charCodeAt(1) === CHAR_COLON) {
      device = path.substring(0, 2)
      rootEnd = 2
      if (len > 2 && isWindowsPathSeparator(path.charCodeAt(2))) {
        isAbsolute = true
        rootEnd = 3
      }
    }

    if (device.length > 0) {
      if (resolvedDevice.length > 0) {
        if (device.toLowerCase() !== resolvedDevice.toLowerCase()) {
          continue
        }
      } else {
        resolvedDevice = device
      }
    }

    if (resolvedAbsolute) {
      if (resolvedDevice.length > 0) {
        break
      }
    } else {
      resolvedTail = `${path.substring(rootEnd)}\\${resolvedTail}`
      resolvedAbsolute = isAbsolute
      if (isAbsolute && resolvedDevice.length > 0) {
        break
      }
    }
  }

  resolvedTail = normalizeString(resolvedTail, !resolvedAbsolute, '\\', isWindowsPathSeparator)

  return resolvedAbsolute
    ? `${resolvedDevice}\\${resolvedTail}`
    : `${resolvedDevice}${resolvedTail}` || '.'
}

exports.normalize = function normalize(path) {
  validateString(path, 'path')

  const len = path.length
  if (len === 0) return '.'
  let rootEnd = 0
  let device
  let isAbsolute = false
  const code = path.charCodeAt(0)

  if (len === 1) {
    return code === CHAR_FORWARD_SLASH ? '\\' : path
  }

  if (isWindowsPathSeparator(code)) {
    isAbsolute = true

    if (isWindowsPathSeparator(path.charCodeAt(1))) {
      let j = 2
      let last = j
      while (j < len && !isWindowsPathSeparator(path.charCodeAt(j))) {
        j++
      }
      if (j < len && j !== last) {
        const firstPart = path.substring(last, j)
        last = j
        while (j < len && isWindowsPathSeparator(path.charCodeAt(j))) {
          j++
        }
        if (j < len && j !== last) {
          last = j
          while (j < len && !isWindowsPathSeparator(path.charCodeAt(j))) {
            j++
          }
          if (j === len) {
            return `\\\\${firstPart}\\${path.substring(last)}\\`
          }
          if (j !== last) {
            device = `\\\\${firstPart}\\${path.substring(last, j)}`
            rootEnd = j
          }
        }
      }
    } else {
      rootEnd = 1
    }
  } else if (isWindowsDeviceRoot(code) && path.charCodeAt(1) === CHAR_COLON) {
    device = path.substring(0, 2)
    rootEnd = 2
    if (len > 2 && isWindowsPathSeparator(path.charCodeAt(2))) {
      isAbsolute = true
      rootEnd = 3
    }
  }

  let tail =
    rootEnd < len
      ? normalizeString(path.substring(rootEnd), !isAbsolute, '\\', isWindowsPathSeparator)
      : ''
  if (tail.length === 0 && !isAbsolute) {
    tail = '.'
  }
  if (tail.length > 0 && isWindowsPathSeparator(path.charCodeAt(len - 1))) {
    tail += '\\'
  }
  if (device === undefined) {
    return isAbsolute ? `\\${tail}` : tail
  }
  return isAbsolute ? `${device}\\${tail}` : `${device}${tail}`
}

exports.isAbsolute = function isAbsolute(path) {
  validateString(path, 'path')

  const len = path.length
  if (len === 0) return false

  const code = path.charCodeAt(0)

  return (
    isWindowsPathSeparator(code) ||
    (len > 2 &&
      isWindowsDeviceRoot(code) &&
      path.charCodeAt(1) === CHAR_COLON &&
      isWindowsPathSeparator(path.charCodeAt(2)))
  )
}

exports.join = function join(...args) {
  if (args.length === 0) return '.'

  let joined
  let firstPart
  for (let i = 0; i < args.length; ++i) {
    const arg = args[i]
    validateString(arg, 'path')
    if (arg.length > 0) {
      if (joined === undefined) joined = firstPart = arg
      else joined += `\\${arg}`
    }
  }

  if (joined === undefined) return '.'

  let needsReplace = true
  let slashCount = 0
  if (isWindowsPathSeparator(firstPart.charCodeAt(0))) {
    ++slashCount
    const firstLen = firstPart.length
    if (firstLen > 1 && isWindowsPathSeparator(firstPart.charCodeAt(1))) {
      ++slashCount
      if (firstLen > 2) {
        if (isWindowsPathSeparator(firstPart.charCodeAt(2))) {
          ++slashCount
        } else {
          needsReplace = false
        }
      }
    }
  }
  if (needsReplace) {
    while (slashCount < joined.length && isWindowsPathSeparator(joined.charCodeAt(slashCount))) {
      slashCount++
    }

    if (slashCount >= 2) {
      joined = `\\${joined.substring(slashCount)}`
    }
  }

  return exports.normalize(joined)
}

exports.relative = function relative(from, to) {
  validateString(from, 'from')
  validateString(to, 'to')

  if (from === to) return ''

  const fromOrig = exports.resolve(from)
  const toOrig = exports.resolve(to)

  if (fromOrig === toOrig) return ''

  from = fromOrig.toLowerCase()
  to = toOrig.toLowerCase()

  if (from === to) return ''

  let fromStart = 0
  while (fromStart < from.length && from.charCodeAt(fromStart) === CHAR_BACKWARD_SLASH) {
    fromStart++
  }
  let fromEnd = from.length
  while (fromEnd - 1 > fromStart && from.charCodeAt(fromEnd - 1) === CHAR_BACKWARD_SLASH) {
    fromEnd--
  }
  const fromLen = fromEnd - fromStart

  let toStart = 0
  while (toStart < to.length && to.charCodeAt(toStart) === CHAR_BACKWARD_SLASH) {
    toStart++
  }
  let toEnd = to.length
  while (toEnd - 1 > toStart && to.charCodeAt(toEnd - 1) === CHAR_BACKWARD_SLASH) {
    toEnd--
  }
  const toLen = toEnd - toStart

  const length = fromLen < toLen ? fromLen : toLen
  let lastCommonSep = -1
  let i = 0
  for (; i < length; i++) {
    const fromCode = from.charCodeAt(fromStart + i)
    if (fromCode !== to.charCodeAt(toStart + i)) {
      break
    } else if (fromCode === CHAR_BACKWARD_SLASH) {
      lastCommonSep = i
    }
  }

  if (i !== length) {
    if (lastCommonSep === -1) return toOrig
  } else {
    if (toLen > length) {
      if (to.charCodeAt(toStart + i) === CHAR_BACKWARD_SLASH) {
        return toOrig.substring(toStart + i + 1)
      }
      if (i === 2) {
        return toOrig.substring(toStart + i)
      }
    }
    if (fromLen > length) {
      if (from.charCodeAt(fromStart + i) === CHAR_BACKWARD_SLASH) {
        lastCommonSep = i
      } else if (i === 2) {
        lastCommonSep = 3
      }
    }
    if (lastCommonSep === -1) lastCommonSep = 0
  }

  let out = ''
  for (i = fromStart + lastCommonSep + 1; i <= fromEnd; ++i) {
    if (i === fromEnd || from.charCodeAt(i) === CHAR_BACKWARD_SLASH) {
      out += out.length === 0 ? '..' : '\\..'
    }
  }

  toStart += lastCommonSep

  if (out.length > 0) {
    return `${out}${toOrig.substring(toStart, toEnd)}`
  }
  if (toOrig.charCodeAt(toStart) === CHAR_BACKWARD_SLASH) {
    ++toStart
  }
  return toOrig.substring(toStart, toEnd)
}

exports.toNamespacedPath = function toNamespacedPath(path) {
  if (typeof path !== 'string' || path.length === 0) return path

  const resolvedPath = exports.resolve(path)

  if (resolvedPath.length <= 2) return path

  if (resolvedPath.charCodeAt(0) === CHAR_BACKWARD_SLASH) {
    if (resolvedPath.charCodeAt(1) === CHAR_BACKWARD_SLASH) {
      const code = resolvedPath.charCodeAt(2)
      if (code !== CHAR_QUESTION_MARK && code !== CHAR_DOT) {
        return `\\\\?\\UNC\\${resolvedPath.substring(2)}`
      }
    }
  } else if (
    isWindowsDeviceRoot(resolvedPath.charCodeAt(0)) &&
    resolvedPath.charCodeAt(1) === CHAR_COLON &&
    resolvedPath.charCodeAt(2) === CHAR_BACKWARD_SLASH
  ) {
    return `\\\\?\\${resolvedPath}`
  }

  return path
}

exports.dirname = function dirname(path) {
  validateString(path, 'path')

  const len = path.length
  if (len === 0) return '.'
  let rootEnd = -1
  let offset = 0
  const code = path.charCodeAt(0)

  if (len === 1) {
    return isWindowsPathSeparator(code) ? path : '.'
  }

  if (isWindowsPathSeparator(code)) {
    rootEnd = offset = 1

    if (isWindowsPathSeparator(path.charCodeAt(1))) {
      let j = 2
      let last = j
      while (j < len && !isWindowsPathSeparator(path.charCodeAt(j))) {
        j++
      }
      if (j < len && j !== last) {
        last = j
        while (j < len && isWindowsPathSeparator(path.charCodeAt(j))) {
          j++
        }
        if (j < len && j !== last) {
          last = j
          while (j < len && !isWindowsPathSeparator(path.charCodeAt(j))) {
            j++
          }
          if (j === len) {
            return path
          }
          if (j !== last) {
            rootEnd = offset = j + 1
          }
        }
      }
    }
  } else if (isWindowsDeviceRoot(code) && path.charCodeAt(1) === CHAR_COLON) {
    rootEnd = len > 2 && isWindowsPathSeparator(path.charCodeAt(2)) ? 3 : 2
    offset = rootEnd
  }

  let end = -1
  let matchedSlash = true
  for (let i = len - 1; i >= offset; --i) {
    if (isWindowsPathSeparator(path.charCodeAt(i))) {
      if (!matchedSlash) {
        end = i
        break
      }
    } else {
      matchedSlash = false
    }
  }

  if (end === -1) {
    if (rootEnd === -1) return '.'

    end = rootEnd
  }
  return path.substring(0, end)
}

exports.basename = function basename(path, suffix) {
  if (suffix !== undefined) validateString(suffix, 'suffix')
  validateString(path, 'path')

  let start = 0
  let end = -1
  let matchedSlash = true

  if (
    path.length >= 2 &&
    isWindowsDeviceRoot(path.charCodeAt(0)) &&
    path.charCodeAt(1) === CHAR_COLON
  ) {
    start = 2
  }

  if (suffix !== undefined && suffix.length > 0 && suffix.length <= path.length) {
    if (suffix === path) return ''
    let extIdx = suffix.length - 1
    let firstNonSlashEnd = -1
    for (let i = path.length - 1; i >= start; --i) {
      const code = path.charCodeAt(i)
      if (isWindowsPathSeparator(code)) {
        if (!matchedSlash) {
          start = i + 1
          break
        }
      } else {
        if (firstNonSlashEnd === -1) {
          matchedSlash = false
          firstNonSlashEnd = i + 1
        }
        if (extIdx >= 0) {
          if (code === suffix.charCodeAt(extIdx)) {
            if (--extIdx === -1) {
              end = i
            }
          } else {
            extIdx = -1
            end = firstNonSlashEnd
          }
        }
      }
    }

    if (start === end) end = firstNonSlashEnd
    else if (end === -1) end = path.length
    return path.substring(start, end)
  }
  for (let i = path.length - 1; i >= start; --i) {
    if (isWindowsPathSeparator(path.charCodeAt(i))) {
      if (!matchedSlash) {
        start = i + 1
        break
      }
    } else if (end === -1) {
      matchedSlash = false
      end = i + 1
    }
  }

  if (end === -1) return ''
  return path.substring(start, end)
}

exports.extname = function extname(path) {
  validateString(path, 'path')

  let start = 0
  let startDot = -1
  let startPart = 0
  let end = -1
  let matchedSlash = true
  let preDotState = 0

  if (
    path.length >= 2 &&
    path.charCodeAt(1) === CHAR_COLON &&
    isWindowsDeviceRoot(path.charCodeAt(0))
  ) {
    start = startPart = 2
  }

  for (let i = path.length - 1; i >= start; --i) {
    const code = path.charCodeAt(i)
    if (isWindowsPathSeparator(code)) {
      if (!matchedSlash) {
        startPart = i + 1
        break
      }
      continue
    }
    if (end === -1) {
      matchedSlash = false
      end = i + 1
    }
    if (code === CHAR_DOT) {
      if (startDot === -1) startDot = i
      else if (preDotState !== 1) preDotState = 1
    } else if (startDot !== -1) {
      preDotState = -1
    }
  }

  if (
    startDot === -1 ||
    end === -1 ||
    preDotState === 0 ||
    (preDotState === 1 && startDot === end - 1 && startDot === startPart + 1)
  ) {
    return ''
  }
  return path.substring(startDot, end)
}
{
  "name": "bare-path",
  "version": "3.1.2",
  "description": "Path manipulation library for JavaScript",
  "exports": {
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    },
    "./package": "./package.json",
    "./posix": "./lib/posix.js",
    "./win32": "./lib/win32.js"
  },
  "files": [
    "index.js",
    "index.d.ts",
    "binding.c",
    "binding.js",
    "CMakeLists.txt",
    "lib",
    "prebuilds",
    "NOTICE"
  ],
  "addon": true,
  "scripts": {
    "format": "prettier --write . && lunte --fix",
    "lint": "prettier --check . && lunte",
    "test": "brittle-bare --coverage test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-path.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-path/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-path#readme",
  "devDependencies": {
    "brittle": "^3.3.2",
    "cmake-bare": "^1.1.6",
    "lunte": "^1.8.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^2.0.0"
  }
}
const b4a = require('b4a')
const stream = require('streamx')
const { ReadableStream, WritableStream } = require('./web')

const defaultEncoding = 'utf8'

module.exports = exports = stream.Stream

exports.pipeline = stream.pipeline

exports.isStream = stream.isStream
exports.isEnding = stream.isEnding
exports.isEnded = stream.isEnded
exports.isFinishing = stream.isFinishing
exports.isFinished = stream.isFinished
exports.isDisturbed = stream.isDisturbed

exports.isErrored = function isErrored(stream) {
  return exports.getStreamError(stream) !== null
}

exports.isReadable = function isReadable(stream) {
  return stream.readable && !stream.destroying && !exports.isEnded(stream)
}

exports.isWritable = function isWritable(stream) {
  return stream.writable && !stream.destroying && !exports.isFinishing(stream)
}

exports.getStreamError = stream.getStreamError

exports.addAbortSignal = function addAbortSignal(signal, stream) {
  function onAbort() {
    stream.destroy(signal.reason)
  }

  if (signal.aborted) onAbort()
  else signal.addEventListener('abort', onAbort)

  return stream
}

exports.Stream = exports

exports.Readable = class Readable extends stream.Readable {
  constructor(opts = {}) {
    super({
      ...opts,
      byteLength: null,
      byteLengthReadable: null,
      map: null,
      mapReadable: null
    })

    if (this._construct) this._open = this._construct

    if (this._read !== stream.Readable.prototype._read) {
      this._read = read.bind(this, this._read)
    }

    if (this._destroy !== stream.Stream.prototype._destroy) {
      this._destroy = destroy.bind(this, this._destroy)
    }
  }

  get closed() {
    return !exports.isReadable(this)
  }

  get errored() {
    return stream.getStreamError(this)
  }

  push(chunk, encoding) {
    if (typeof chunk === 'string') {
      chunk = b4a.from(chunk, encoding || defaultEncoding)
    }

    return super.push(chunk)
  }

  unshift(chunk, encoding) {
    if (typeof chunk === 'string') {
      chunk = b4a.from(chunk, encoding || defaultEncoding)
    }

    super.unshift(chunk)
  }

  static fromWeb(readableStream, opts = {}) {
    const stream = readableStream._stream

    if (opts.encoding) stream.setEncoding(opts.encoding)
    if (opts.signal) exports.addAbortSignal(opts.signal, stream)

    return stream
  }

  static toWeb(readable, opts = {}) {
    return new ReadableStream(readable, opts.strategy)
  }

  async [Symbol.asyncDispose]() {
    if (!this.destroyed) this.destroy()

    await new Promise((resolve) => exports.finished(this, resolve))
  }
}

exports.Writable = class Writable extends stream.Writable {
  constructor(opts = {}) {
    super({
      ...opts,
      byteLength: null,
      byteLengthWritable,
      map: null,
      mapWritable: null
    })

    if (this._construct) this._open = this._construct

    if (this._write !== stream.Writable.prototype._write) {
      this._write = write.bind(this, this._write)
    }

    if (this._destroy !== stream.Stream.prototype._destroy) {
      this._destroy = destroy.bind(this, this._destroy)
    }
  }

  get closed() {
    return !exports.isWritable(this)
  }

  get errored() {
    return stream.getStreamError(this)
  }

  write(chunk, encoding, cb) {
    if (typeof encoding === 'function') {
      cb = encoding
      encoding = null
    }

    if (typeof chunk === 'string') {
      encoding = encoding || defaultEncoding
      chunk = b4a.from(chunk, encoding)
    } else {
      encoding = 'buffer'
    }

    const result = super.write({ chunk, encoding })

    if (cb) stream.Writable.drained(this).then(() => cb(null), cb)

    return result
  }

  end(chunk, encoding, cb) {
    if (typeof chunk === 'function') {
      cb = chunk
      chunk = null
    } else if (typeof encoding === 'function') {
      cb = encoding
      encoding = null
    }

    if (typeof chunk === 'string') {
      encoding = encoding || defaultEncoding
      chunk = b4a.from(chunk, encoding || defaultEncoding)
    } else {
      encoding = 'buffer'
    }

    const result =
      chunk !== undefined && chunk !== null ? super.end({ chunk, encoding }) : super.end()

    if (cb) this.once('finish', () => cb(null))

    return result
  }

  static fromWeb(writableStream, opts = {}) {
    const stream = writableStream._stream

    if (opts.signal) exports.addAbortSignal(opts.signal, stream)

    return stream
  }

  static toWeb(writable) {
    return new WritableStream(writable)
  }

  async [Symbol.asyncDispose]() {
    if (!this.destroyed) this.destroy()

    await new Promise((resolve) => exports.finished(this, resolve))
  }
}

exports.Duplex = class Duplex extends stream.Duplex {
  constructor(opts = {}) {
    super({
      ...opts,
      byteLength: null,
      byteLengthReadable: null,
      byteLengthWritable,
      map: null,
      mapReadable: null,
      mapWritable: null
    })

    if (this._construct) this._open = this._construct

    if (this._read !== stream.Readable.prototype._read) {
      this._read = read.bind(this, this._read)
    }

    if (this._write !== stream.Duplex.prototype._write) {
      this._write = write.bind(this, this._write)
    }

    if (this._destroy !== stream.Stream.prototype._destroy) {
      this._destroy = destroy.bind(this, this._destroy)
    }
  }

  push(chunk, encoding) {
    if (typeof chunk === 'string') {
      chunk = b4a.from(chunk, encoding || defaultEncoding)
    }

    return super.push(chunk)
  }

  unshift(chunk, encoding) {
    if (typeof chunk === 'string') {
      chunk = b4a.from(chunk, encoding || defaultEncoding)
    }

    super.unshift(chunk)
  }

  write(chunk, encoding, cb) {
    if (typeof encoding === 'function') {
      cb = encoding
      encoding = null
    }

    if (typeof chunk === 'string') {
      encoding = encoding || defaultEncoding
      chunk = b4a.from(chunk, encoding)
    } else {
      encoding = 'buffer'
    }

    const result = super.write({ chunk, encoding })

    if (cb) stream.Writable.drained(this).then(() => cb(null), cb)

    return result
  }

  end(chunk, encoding, cb) {
    if (typeof chunk === 'function') {
      cb = chunk
      chunk = null
    } else if (typeof encoding === 'function') {
      cb = encoding
      encoding = null
    }

    if (typeof chunk === 'string') {
      encoding = encoding || defaultEncoding
      chunk = b4a.from(chunk, encoding)
    } else {
      encoding = 'buffer'
    }

    const result =
      chunk !== undefined && chunk !== null ? super.end({ chunk, encoding }) : super.end()

    if (cb) this.once('finish', () => cb(null))

    return result
  }

  static fromWeb({ readable: readableStream, writable: writableStream }, opts) {
    const readable = exports.Readable.fromWeb(readableStream, opts)
    const writable = exports.Writable.fromWeb(writableStream, opts)

    const duplex = new exports.Duplex({
      write(data, encoding, cb) {
        writable.write(data, encoding, cb)
      }
    })

    readable
      .on('data', (data) => duplex.push(data))
      .on('end', () => duplex.push(null))
      .on('error', (err) => duplex.destroy(err))

    writable.on('finish', () => duplex.end()).on('error', (err) => duplex.destroy(err))

    return duplex
  }

  static toWeb(duplex) {
    const readableStream = exports.Readable.toWeb(duplex)
    const writableStream = exports.Writable.toWeb(duplex)

    return { readable: readableStream, writable: writableStream }
  }
}

class DuplexSide extends exports.Duplex {
  constructor(opts) {
    super(opts)

    this._otherSide = null
    this._cb = null
  }

  _read() {
    const cb = this._cb
    if (!cb) return

    this._cb = null
    cb()
  }

  _write(chunk, encoding, cb) {
    this._otherSide.push(chunk, encoding)
    this._otherSide._cb = cb
  }

  _final(cb) {
    this._otherSide.on('end', cb)
    this._otherSide.push(null)
  }
}

exports.duplexPair = function duplexPair(opts) {
  const sideA = new DuplexSide(opts)
  const sideB = new DuplexSide(opts)

  sideA._otherSide = sideB
  sideB._otherSide = sideA

  return [sideA, sideB]
}

exports.Transform = class Transform extends stream.Transform {
  constructor(opts = {}) {
    super({
      ...opts,
      byteLength: null,
      byteLengthReadable: null,
      byteLengthWritable,
      map: null,
      mapReadable: null,
      mapWritable: null
    })

    if (this._transform !== stream.Transform.prototype._transform) {
      this._transform = transform.bind(this, this._transform)
    } else {
      this._transform = passthrough
    }
  }

  push(chunk, encoding) {
    if (typeof chunk === 'string') {
      chunk = b4a.from(chunk, encoding || defaultEncoding)
    }

    return super.push(chunk)
  }

  unshift(chunk, encoding) {
    if (typeof chunk === 'string') {
      chunk = b4a.from(chunk, encoding || defaultEncoding)
    }

    super.unshift(chunk)
  }

  write(chunk, encoding, cb) {
    if (typeof encoding === 'function') {
      cb = encoding
      encoding = null
    }

    if (typeof chunk === 'string') {
      encoding = encoding || defaultEncoding
      chunk = b4a.from(chunk, encoding)
    } else {
      encoding = 'buffer'
    }

    const result = super.write({ chunk, encoding })

    if (cb) stream.Writable.drained(this).then(() => cb(null), cb)

    return result
  }

  end(chunk, encoding, cb) {
    if (typeof chunk === 'function') {
      cb = chunk
      chunk = null
    } else if (typeof encoding === 'function') {
      cb = encoding
      encoding = null
    }

    if (typeof chunk === 'string') {
      encoding = encoding || defaultEncoding
      chunk = b4a.from(chunk, encoding)
    } else {
      encoding = 'buffer'
    }

    const result =
      chunk !== undefined && chunk !== null ? super.end({ chunk, encoding }) : super.end()

    if (cb) this.once('finish', () => cb(null))

    return result
  }
}

exports.PassThrough = class PassThrough extends exports.Transform {}

exports.finished = function finished(stream, opts, cb) {
  if (typeof opts === 'function') {
    cb = opts
    opts = {}
  }

  if (!opts) opts = {}

  const { cleanup = false } = opts

  const done = () => {
    cb(exports.getStreamError(stream, { all: true }))

    if (cleanup) detach()
  }

  const detach = () => {
    stream.off('close', done)
    stream.off('error', noop)
  }

  if (stream.destroyed) {
    done()
  } else {
    stream.on('close', done)
    stream.on('error', noop)
  }

  return detach
}

function read(read, cb) {
  read.call(this, 65536)

  cb(null)
}

function write(write, data, cb) {
  write.call(this, data.chunk, data.encoding, cb)
}

function transform(transform, data, cb) {
  transform.call(this, data.chunk, data.encoding, cb)
}

function destroy(destroy, cb) {
  destroy.call(this, exports.getStreamError(this), cb)
}

function passthrough(data, cb) {
  cb(null, data.chunk)
}

function byteLengthWritable(data) {
  return data.chunk.byteLength
}

function noop() {}
{
  "name": "bare-stream",
  "version": "2.13.4",
  "description": "Streaming data for JavaScript",
  "exports": {
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    },
    "./package": "./package.json",
    "./promises": "./promises.js",
    "./web": {
      "types": "./web.d.ts",
      "default": "./web.js"
    },
    "./global": {
      "types": "./global.d.ts",
      "default": "./global.js"
    }
  },
  "files": [
    "index.js",
    "index.d.ts",
    "promises.js",
    "web.js",
    "web.d.ts",
    "global.js",
    "global.d.ts"
  ],
  "scripts": {
    "format": "prettier --write . && lunte --fix",
    "lint": "prettier --check . && lunte",
    "test": "brittle-bare --coverage test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-stream.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-stream/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-stream#readme",
  "dependencies": {
    "b4a": "^1.8.1",
    "streamx": "^2.25.0",
    "teex": "^1.0.1"
  },
  "devDependencies": {
    "bare-abort-controller": "^1.1.0",
    "bare-buffer": "^3.0.0",
    "bare-events": "^2.5.4",
    "brittle": "^3.5.2",
    "lunte": "^1.6.0",
    "prettier": "^3.3.3",
    "prettier-config-holepunch": "^2.0.0"
  },
  "peerDependencies": {
    "bare-abort-controller": "*",
    "bare-buffer": "*",
    "bare-events": "*"
  },
  "peerDependenciesMeta": {
    "bare-abort-controller": {
      "optional": true
    },
    "bare-buffer": {
      "optional": true
    },
    "bare-events": {
      "optional": true
    }
  }
}
const { Readable, Writable, Transform, getStreamError, isStreamx, isDisturbed } = require('streamx')
const tee = require('teex')

const readableKind = Symbol.for('bare.stream.readable.kind')
const writableKind = Symbol.for('bare.stream.writable.kind')
const transformKind = Symbol.for('bare.stream.transform.kind')

// https://streams.spec.whatwg.org/#readablestreamdefaultreader
exports.ReadableStreamDefaultReader = class ReadableStreamDefaultReader {
  constructor(stream) {
    this._stream = stream
    this._stream._stream.once('close', onclose).once('error', onerror)

    const closed = Promise.withResolvers()

    // Avoid unhandled exceptions
    closed.promise.catch(noop)

    this._closed = closed

    function onclose() {
      closed.resolve()
    }

    function onerror(err) {
      closed.reject(err)
    }
  }

  get closed() {
    return this._closed.promise
  }

  read() {
    const stream = this._stream._stream

    return new Promise((resolve, reject) => {
      const err = getStreamError(stream)

      if (err) return reject(err)

      if (stream.destroyed) {
        return resolve({ value: undefined, done: true })
      }

      const value = stream.read()

      if (value !== null) {
        return resolve({ value, done: false })
      }

      stream.once('readable', onreadable).once('close', onclose).once('error', onerror)

      function onreadable() {
        const value = stream.read()

        ondone(null, value === null ? { value: undefined, done: true } : { value, done: false })
      }

      function onclose() {
        ondone(null, { value: undefined, done: true })
      }

      function onerror(err) {
        ondone(err, null)
      }

      function ondone(err, value) {
        stream.off('readable', onreadable).off('close', onclose).off('error', onerror)

        if (err) reject(err)
        else resolve(value)
      }
    })
  }

  releaseLock() {
    this._closed.reject(new TypeError('Reader was released'))
    this._stream._releaseLock()
    this._stream = null
  }

  cancel(reason = new TypeError('Stream was cancelled')) {
    const stream = this._stream._stream

    if (stream.destroyed) return Promise.resolve()

    return new Promise((resolve) =>
      stream.once('close', resolve).once('error', noop).destroy(reason)
    )
  }
}

// https://streams.spec.whatwg.org/#readablestreamdefaultcontroller
exports.ReadableStreamDefaultController = class ReadableStreamDefaultController {
  constructor(stream) {
    this._stream = stream
  }

  get desiredSize() {
    const stream = this._stream._stream

    return stream._readableState.highWaterMark - stream._readableState.buffered
  }

  enqueue(data) {
    this._stream._stream.push(data)
  }

  close() {
    this._stream._stream.push(null)
  }

  error(err) {
    this._stream._stream.destroy(err)
  }
}

// https://streams.spec.whatwg.org/#readablestream
class ReadableStream {
  static get [readableKind]() {
    return 0 // Compatibility version
  }

  static from(iterable) {
    return new ReadableStream(Readable.from(iterable))
  }

  constructor(underlyingSource = {}, queuingStrategy) {
    if (isStreamx(underlyingSource)) {
      this._stream = underlyingSource
    } else {
      if (queuingStrategy === undefined) {
        queuingStrategy = new exports.CountQueuingStrategy()
      }

      const { start, pull, cancel } = underlyingSource
      const { highWaterMark = 1, size = defaultSize } = queuingStrategy

      this._stream = new Readable({ highWaterMark, byteLength: size })

      const controller = new exports.ReadableStreamDefaultController(this)

      try {
        let starting = Promise.resolve()

        if (start) starting = forwardError(start.call(this, controller), controller)

        if (pull) {
          this._stream._read = this._read.bind(this, starting, pull.bind(this, controller))
        }

        if (cancel) {
          this._stream.once('error', cancel.bind(this))
        }
      } catch (err) {
        controller.error(err)
      }
    }

    this._reader = null
  }

  get [readableKind]() {
    return ReadableStream[readableKind]
  }

  get locked() {
    return this._reader !== null
  }

  getReader() {
    if (this.locked) throw new TypeError('Stream is locked')

    this._reader = new exports.ReadableStreamDefaultReader(this)

    return this._reader
  }

  cancel(reason = new TypeError('Stream was cancelled')) {
    const stream = this._stream

    if (stream.destroyed) return Promise.resolve()

    if (this.locked) return Promise.reject(new TypeError('Stream is locked'))

    return new Promise((resolve) =>
      stream.once('close', resolve).once('error', noop).destroy(reason)
    )
  }

  tee() {
    const [a, b] = tee(this._stream)

    return [new ReadableStream(a), new ReadableStream(b)]
  }

  pipeTo(destination) {
    return new Promise((resolve, reject) =>
      this._stream.pipe(destination._stream, (err) => {
        err ? reject(err) : resolve()
      })
    )
  }

  [Symbol.asyncIterator]() {
    return this._stream[Symbol.asyncIterator]()
  }

  _releaseLock() {
    this._reader = null
  }

  async _read(starting, pull, cb) {
    await starting

    let err = null
    try {
      await pull()
    } catch (e) {
      err = e
    }
    cb(err)
  }
}

function defaultSize() {
  return 1
}

exports.ReadableStream = ReadableStream

// https://streams.spec.whatwg.org/#countqueuingstrategy
exports.CountQueuingStrategy = class CountQueuingStrategy {
  constructor(opts = {}) {
    const { highWaterMark = 1 } = opts

    this.highWaterMark = highWaterMark
  }

  size(chunk) {
    return 1
  }
}

// https://streams.spec.whatwg.org/#bytelengthqueuingstrategy
exports.ByteLengthQueuingStrategy = class ByteLengthQueuingStrategy {
  constructor(opts = {}) {
    const { highWaterMark = 16384 } = opts

    this.highWaterMark = highWaterMark
  }

  size(chunk) {
    return chunk.byteLength
  }
}

exports.isReadableStream = function isReadableStream(value) {
  if (value instanceof ReadableStream) return true

  return (
    typeof value === 'object' &&
    value !== null &&
    value[readableKind] === ReadableStream[readableKind]
  )
}

// https://streams.spec.whatwg.org/#readablestream-errored
exports.isReadableStreamErrored = function isReadableStreamErrored(stream) {
  return getStreamError(stream._stream) !== null
}

// https://streams.spec.whatwg.org/#is-readable-stream-disturbed
exports.isReadableStreamDisturbed = function isReadableStreamDisturbed(stream) {
  return isDisturbed(stream._stream)
}

// https://streams.spec.whatwg.org/#writablestreamdefaultwriter
exports.WritableStreamDefaultWriter = class WritableStreamDefaultWriter {
  constructor(stream) {
    this._stream = stream
    this._stream._stream.once('close', onclose).once('error', onerror)

    const closed = Promise.withResolvers()

    // Avoid unhandled exceptions
    closed.promise.catch(noop)

    this._closed = closed

    function onclose() {
      closed.resolve()
    }

    function onerror(err) {
      closed.reject(err)
    }
  }

  get desiredSize() {
    const stream = this._stream._stream

    return stream._writableState.highWaterMark - stream._writableState.buffered
  }

  get closed() {
    return this._closed.promise
  }

  get ready() {
    const stream = this._stream._stream

    if (getStreamError(stream)) return Promise.reject()

    return Writable.drained(stream).then()
  }

  async write(chunk) {
    const stream = this._stream._stream

    let err = getStreamError(stream)
    if (err) return Promise.reject(err)

    stream.write(chunk)

    await Writable.drained(stream)

    err = getStreamError(stream)
    if (err) return Promise.reject(err)
  }

  releaseLock() {
    this._closed.reject(new TypeError('Writer was released'))
    this._stream._releaseLock()
    this._stream = null
  }

  close() {
    const stream = this._stream._stream

    if (stream.destroyed) return Promise.resolve()

    return new Promise((resolve) => stream.once('close', resolve).end())
  }

  abort(reason = new TypeError('Stream was aborted')) {
    const stream = this._stream._stream

    if (stream.destroyed) return Promise.resolve()

    return new Promise((resolve) => stream.once('close', resolve).destroy(reason))
  }
}

// https://streams.spec.whatwg.org/#writablestreamdefaultcontroller
exports.WritableStreamDefaultController = class WritableStreamDefaultController {
  constructor(stream) {
    this._stream = stream
  }

  error(err) {
    this._stream._stream.destroy(err)
  }
}

// https://streams.spec.whatwg.org/#writablestream
class WritableStream {
  static get [writableKind]() {
    return 0 // Compatibility version
  }

  constructor(underlyingSink = {}, queuingStrategy = {}) {
    if (isStreamx(underlyingSink)) {
      this._stream = underlyingSink
    } else {
      if (queuingStrategy === undefined) {
        queuingStrategy = new exports.CountQueuingStrategy()
      }

      const { start, write, close, abort } = underlyingSink
      const { highWaterMark = 1, size = defaultSize } = queuingStrategy

      this._stream = new Writable({ highWaterMark, byteLength: size })

      const controller = new exports.WritableStreamDefaultController(this)

      this._controller = controller

      try {
        let starting = Promise.resolve()

        if (start) starting = forwardError(start.call(this, controller), controller)

        if (write) {
          this._stream._write = this._write.bind(this, starting, write.bind(this))
        }

        if (close) {
          this._stream._destroy = this._destroy.bind(this, close.call(this))
        }

        if (abort) {
          this._stream.once('error', abort.bind(this))
        }
      } catch (err) {
        controller.error(err)
      }
    }

    this._writer = null
  }

  get [writableKind]() {
    return WritableStream[writableKind]
  }

  get locked() {
    return this._writer !== null
  }

  getWriter() {
    if (this.locked) throw new TypeError('Stream is locked')

    this._writer = new exports.WritableStreamDefaultWriter(this)

    return this._writer
  }

  abort(reason = new TypeError('Stream was aborted')) {
    if (this._stream.destroyed) return Promise.resolve()

    if (this.locked) return Promise.reject(new TypeError('Stream is locked'))

    return new Promise((resolve) => this._stream.once('close', resolve).destroy(reason))
  }

  close() {
    if (this._stream.destroyed) return Promise.resolve()

    if (this.locked) return Promise.reject(new TypeError('Stream is locked'))

    return new Promise((resolve) => this._stream.once('close', resolve).end())
  }

  _releaseLock() {
    this._writer = null
  }

  async _write(starting, write, data, cb) {
    await starting

    let err = null
    try {
      await write(data, this._controller)
    } catch (e) {
      err = e
    }
    cb(err)
  }

  async _destroy(closing, cb) {
    let err = null
    try {
      await closing
    } catch (e) {
      err = e
    }
    cb(err)
  }
}

exports.WritableStream = WritableStream

exports.isWritableStream = function isWritableStream(value) {
  if (value instanceof WritableStream) return true

  return (
    typeof value === 'object' &&
    value !== null &&
    value[writableKind] === WritableStream[writableKind]
  )
}

// https://streams.spec.whatwg.org/#transformstreamdefaultcontroller
exports.TransformStreamDefaultController = class TransformStreamDefaultController {
  constructor(stream) {
    this._stream = stream
  }

  get desiredSize() {
    const stream = this._stream._stream

    return stream._readableState.highWaterMark - stream._readableState.buffered
  }

  enqueue(data) {
    this._stream._stream.push(data)
  }

  error(err) {
    this._stream._stream.destroy(err)
  }

  terminate() {
    const stream = this._stream._stream

    stream.push(null)
    stream.destroy(new TypeError('Stream has been terminated'))
  }
}

// https://streams.spec.whatwg.org/#transformstream
class TransformStream {
  static get [transformKind]() {
    return 0 // Compatibility version
  }

  constructor(transformer = {}, writableStrategy = {}, readableStrategy = {}) {
    if (isStreamx(transformer)) {
      this._stream = transformer
    } else {
      const { start, transform, flush } = transformer

      this._stream = new Transform({ ...writableStrategy, ...readableStrategy })

      const controller = new exports.TransformStreamDefaultController(this)

      this._controller = controller

      try {
        let starting = Promise.resolve()

        if (start) starting = forwardError(start.call(this, controller), controller)

        if (transform) {
          this._stream._transform = this._transform.bind(this, starting, transform.bind(this))
        }

        if (flush) {
          this._stream._flush = this._flush.bind(this, flush.call(this, this._controller))
        }
      } catch (err) {
        controller.error(err)
      }
    }

    this._writable = new WritableStream(this._stream)
    this._readable = new ReadableStream(this._stream)
  }

  get [transformKind]() {
    return TransformStream[transformKind]
  }

  get writable() {
    return this._writable
  }

  get readable() {
    return this._readable
  }

  async _transform(starting, transform, data, cb) {
    await starting

    let err = null
    try {
      await transform(data, this._controller)
    } catch (e) {
      err = e
    }
    cb(err)
  }

  async _flush(flush, cb) {
    let err = null
    try {
      await flush
    } catch (e) {
      err = e
    }
    cb(err)
  }
}

exports.TransformStream = TransformStream

exports.isTransformStream = function isTransformStream(value) {
  if (value instanceof TransformStream) return true

  return (
    typeof value === 'object' &&
    value !== null &&
    value[transformKind] === TransformStream[transformKind]
  )
}

async function forwardError(promise, controller) {
  try {
    await promise
  } catch (err) {
    controller.error(err)
  }
}

function noop() {}
module.exports = require.addon()
const binding = require('./binding')

const t = binding.constants

class Type {
  constructor(type) {
    this._type = type
  }

  isUndefined() {
    return this._type === t.UNDEFINED
  }

  isNull() {
    return this._type === t.NULL
  }

  isBoolean() {
    return this._type === t.BOOLEAN
  }

  isNumber() {
    return (this._type & 0xff) === t.NUMBER
  }

  isInt32() {
    return (this._type & (0xff | t.INT32)) === (t.NUMBER | t.INT32)
  }

  isUint32() {
    return (this._type & (0xff | t.UINT32)) === (t.NUMBER | t.UINT32)
  }

  isString() {
    return this._type === t.STRING
  }

  isSymbol() {
    return this._type === t.SYMBOL
  }

  isObject() {
    return (this._type & 0xff) === t.OBJECT
  }

  isArray() {
    return this._type === (t.OBJECT | t.ARRAY)
  }

  isArguments() {
    return this._type === (t.OBJECT | t.ARGUMENTS)
  }

  isDate() {
    return this._type === (t.OBJECT | t.DATE)
  }

  isRegExp() {
    return this._type === (t.OBJECT | t.REGEXP)
  }

  isError() {
    return this._type === (t.OBJECT | t.ERROR)
  }

  isPromise() {
    return this._type === (t.OBJECT | t.PROMISE)
  }

  isProxy() {
    return this._type === (t.OBJECT | t.PROXY)
  }

  isGenerator() {
    return this._type === (t.OBJECT | t.GENERATOR)
  }

  isMap() {
    return this._type === (t.OBJECT | t.MAP)
  }

  isSet() {
    return this._type === (t.OBJECT | t.SET)
  }

  isWeakMap() {
    return this._type === (t.OBJECT | t.WEAK_MAP)
  }

  isWeakSet() {
    return this._type === (t.OBJECT | t.WEAK_SET)
  }

  isWeakRef() {
    return this._type === (t.OBJECT | t.WEAK_REF)
  }

  isArrayBuffer() {
    return this._type === (t.OBJECT | t.ARRAYBUFFER)
  }

  isSharedArrayBuffer() {
    return this._type === (t.OBJECT | t.SHAREDARRAYBUFFER)
  }

  isTypedArray() {
    return (this._type & 0xffff) === (t.OBJECT | t.TYPEDARRAY)
  }

  isInt8Array() {
    return this._type === (t.OBJECT | t.TYPEDARRAY | t.INT8ARRAY)
  }

  isUint8Array() {
    return this._type === (t.OBJECT | t.TYPEDARRAY | t.UINT8ARRAY)
  }

  isUint8ClampedArray() {
    return this._type === (t.OBJECT | t.TYPEDARRAY | t.UINT8CLAMPEDARRAY)
  }

  isInt16Array() {
    return this._type === (t.OBJECT | t.TYPEDARRAY | t.INT16ARRAY)
  }

  isUint16Array() {
    return this._type === (t.OBJECT | t.TYPEDARRAY | t.UINT16ARRAY)
  }

  isInt32Array() {
    return this._type === (t.OBJECT | t.TYPEDARRAY | t.INT32ARRAY)
  }

  isUint32Array() {
    return this._type === (t.OBJECT | t.TYPEDARRAY | t.UINT32ARRAY)
  }

  isFloat16Array() {
    return this._type === (t.OBJECT | t.TYPEDARRAY | t.FLOAT16ARRAY)
  }

  isFloat32Array() {
    return this._type === (t.OBJECT | t.TYPEDARRAY | t.FLOAT32ARRAY)
  }

  isFloat64Array() {
    return this._type === (t.OBJECT | t.TYPEDARRAY | t.FLOAT64ARRAY)
  }

  isBigInt64Array() {
    return this._type === (t.OBJECT | t.TYPEDARRAY | t.BIGINT64ARRAY)
  }

  isBigUint64Array() {
    return this._type === (t.OBJECT | t.TYPEDARRAY | t.BIGUINT64ARRAY)
  }

  isDataView() {
    return this._type === (t.OBJECT | t.DATAVIEW)
  }

  isModuleNamespace() {
    return this._type === (t.OBJECT | t.MODULE_NAMESPACE)
  }

  isFunction() {
    return (this._type & 0xff) === t.FUNCTION
  }

  isAsyncFunction() {
    return (this._type & (0xff | t.ASYNC_FUNCTION)) === (t.FUNCTION | t.ASYNC_FUNCTION)
  }

  isGeneratorFunction() {
    return (this._type & (0xff | t.GENERATOR_FUNCTION)) === (t.FUNCTION | t.GENERATOR_FUNCTION)
  }

  isExternal() {
    return this._type === t.EXTERNAL
  }

  isBigInt() {
    return this._type === t.BIGINT
  }
}

// Classifies a number the way the engine does, but without crossing into the
// addon. A number is an `int32` or a `uint32` if it is an integer within the
// respective range, with negative zero being representable as neither.
function numberType(value) {
  if (!Number.isInteger(value)) return t.NUMBER

  // Negative zero passes both range checks below, so exclude it up front.
  if (value === 0 && 1 / value < 0) return t.NUMBER

  let type = t.NUMBER

  if (value >= -0x80000000 && value <= 0x7fffffff) type |= t.INT32
  if (value >= 0 && value <= 0xffffffff) type |= t.UINT32

  return type
}

module.exports = function type(value) {
  switch (typeof value) {
    case 'undefined':
      return new Type(t.UNDEFINED)
    case 'boolean':
      return new Type(t.BOOLEAN)
    case 'number':
      return new Type(numberType(value))
    case 'string':
      return new Type(t.STRING)
    case 'symbol':
      return new Type(t.SYMBOL)
    case 'object':
      return new Type(value === null ? t.NULL : binding.type(value))
    case 'function':
      return new Type(binding.type(value))
    case 'bigint':
      return new Type(t.BIGINT)
  }
}
{
  "name": "bare-type",
  "version": "1.1.1",
  "description": "Cross-realm type predicates for Bare",
  "exports": {
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    },
    "./package": "./package.json"
  },
  "files": [
    "index.js",
    "index.d.ts",
    "binding.c",
    "binding.js",
    "CMakeLists.txt",
    "prebuilds"
  ],
  "addon": true,
  "scripts": {
    "format": "prettier --write . && lunte --fix",
    "lint": "prettier --check . && lunte",
    "test": "brittle-bare --coverage test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-type.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-type/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-type#readme",
  "engines": {
    "bare": ">=1.2.0"
  },
  "devDependencies": {
    "brittle": "^3.4.0",
    "cmake-bare": "^1.1.6",
    "lunte": "^1.8.4",
    "prettier": "^3.4.1",
    "prettier-config-holepunch": "^2.0.0"
  }
}
module.exports = require.addon()
const path = require('bare-path')
const binding = require('./binding')
const errors = require('./lib/errors')
const URLSearchParams = require('./lib/url-search-params')

const kind = Symbol.for('bare.url.kind')

const isWindows = Bare.platform === 'win32'

// Scratch buffer that the binding writes the parsed component offsets into. A
// single shared buffer is reused across every parse.
//
// The offsets are copied out into fields on the URL immediately after a
// successful parse, so nothing observes the buffer across calls.
const components = new Uint32Array(8)

// The value used for a component that is not present in the URL.
const unset = 0xffffffff

// The schemes the parser treats specially. A URL cannot be switched between a
// special and a non-special scheme, and a backslash only terminates a host for
// the former.
const special = new Set(['ftp', 'file', 'http', 'https', 'ws', 'wss'])

// https://url.spec.whatwg.org/#scheme-start-state
const scheme = /^[a-z][a-z0-9+\-.]*$/

// ASCII tab and newline are removed from input before it is parsed rather than
// percent-encoded like the other C0 controls.
const whitespaceAll = /[\t\n\r]/g

// The characters that terminate a host, and so bound the value the host and
// hostname setters accept.
const hostEnd = /[/\\?#]/
const hostEndOpaque = /[/?#]/

// The delimiters that would let a setter's value escape the component it is
// spliced into. Everything else is left to the reparse, which applies the full
// percent-encode set for the component. As elsewhere in this package, each set
// needs two patterns because test() advances a global pattern's lastIndex.
//
// Credentials are not run through the parser and so keep their tabs and
// newlines, percent-encoded, rather than having them stripped.
const userinfoDelimiter = /[\t\n\r/\\?#@:]/
const userinfoDelimiterAll = /[\t\n\r/\\?#@:]/g

const pathDelimiter = /[?#]/
const pathDelimiterAll = /[?#]/g

// A leading or trailing run of C0 control or space in a value that ends up at
// either end of the href. The parser strips those, but only when parsing a URL
// as a whole, so a setter has to encode its own.
const edges = /^[\u0000-\u0020]+|[\u0000-\u0020]+$/g

const escapes = {
  '\t': '%09',
  '\n': '%0A',
  '\r': '%0D',
  '/': '%2F',
  '\\': '%5C',
  ':': '%3A',
  '?': '%3F',
  '@': '%40',
  '#': '%23'
}

// The characters that pathToFileURL() has to percent-encode itself. A backslash
// is a path separator on Windows and so is left alone there.
const reserved = isWindows ? /[%#?\n\r\t]/ : /[%#?\n\r\t\\]/

class URL {
  static get [kind]() {
    return 0 // Compatibility version
  }

  constructor(input, base, opts = {}) {
    if (arguments.length === 0) throw errors.INVALID_URL()

    input = String(input)

    if (base !== undefined) base = String(base)

    this._href = undefined
    this._schemeEnd = 0
    this._usernameEnd = 0
    this._hostStart = 0
    this._hostEnd = 0
    this._pathStart = 0
    this._queryStart = 0
    this._fragmentStart = 0
    this._params = null

    this._parse(input, base, opts.throw !== false)
  }

  get [kind]() {
    return URL[kind]
  }

  // https://url.spec.whatwg.org/#dom-url-href

  get href() {
    return this._href
  }

  set href(value) {
    // Unlike every other setter, the href setter reports a parse failure rather
    // than leaving the URL untouched.
    this._parse(String(value), null, true)

    if (this._params) this._params._parse(this.search)
  }

  // https://url.spec.whatwg.org/#dom-url-protocol

  get protocol() {
    return this._slice(0, this._schemeEnd) + ':'
  }

  set protocol(value) {
    value = strip(String(value))

    const end = value.indexOf(':')

    if (end !== -1) value = value.slice(0, end)

    value = value.toLowerCase()

    if (!scheme.test(value)) return

    const current = this._slice(0, this._schemeEnd)

    if (special.has(current) !== special.has(value)) return

    if (value === 'file' && (this.username || this.password || this.port)) {
      return
    }

    if (current === 'file' && this._hostStart === this._hostEnd) return

    this._update(this._replace(value, 0, this._schemeEnd))
  }

  // https://url.spec.whatwg.org/#dom-url-username

  get username() {
    return this._slice(this._schemeEnd + 3 /* :// */, this._usernameEnd)
  }

  set username(value) {
    if (cannotHaveCredentialsOrPort(this)) {
      return
    }

    value = encodeUserinfo(String(value))

    if (!hasCredentials(this)) value += '@'

    this._update(this._replace(value, this._schemeEnd + 3 /* :// */, this._usernameEnd))
  }

  // https://url.spec.whatwg.org/#dom-url-password

  get password() {
    return this._href.slice(this._usernameEnd + 1 /* : */, this._hostStart - 1 /* @ */)
  }

  set password(value) {
    if (cannotHaveCredentialsOrPort(this)) {
      return
    }

    value = ':' + encodeUserinfo(String(value))

    let end = this._hostStart - 1 /* @ */

    if (!hasCredentials(this)) {
      value += '@'
      end = this._usernameEnd
    }

    this._update(this._replace(value, this._usernameEnd, end))
  }

  // https://url.spec.whatwg.org/#dom-url-host

  get host() {
    return this._slice(this._hostStart, this._pathStart)
  }

  set host(value) {
    if (hasOpaquePath(this)) {
      return
    }

    const protocol = this._slice(0, this._schemeEnd)

    value = truncateHost(protocol, String(value))

    // An `@` would make the reparse read the value as credentials rather than
    // as a host, so it is rejected outright.
    if (value.includes('@')) return

    const separator = portSeparator(value)

    let end = this._hostEnd

    // A port in the value is parsed separately so that an invalid one leaves
    // the existing port in place rather than rejecting the host along with it.
    if (separator !== -1) {
      // A file URL cannot have a port, so a value carrying one is rejected
      // rather than split.
      if (protocol === 'file') return

      const port = parsePort(value.slice(separator + 1))

      value = value.slice(0, separator)

      if (port !== null) {
        value += port
        end = this._pathStart
      }
    }

    if (value === '' && cannotHaveEmptyHost(protocol)) return

    this._update(this._replace(value, this._hostStart, end))
  }

  // https://url.spec.whatwg.org/#dom-url-hostname

  get hostname() {
    return this._slice(this._hostStart, this._hostEnd)
  }

  set hostname(value) {
    if (hasOpaquePath(this)) {
      return
    }

    const protocol = this._slice(0, this._schemeEnd)

    value = truncateHost(protocol, String(value))

    // A port cannot be set through this setter, and a value that carries one is
    // rejected outright rather than truncated. An `@` would make the reparse
    // read the value as credentials rather than as a host.
    if (value.includes('@') || portSeparator(value) !== -1) return

    if (value === '' && cannotHaveEmptyHost(protocol)) return

    this._update(this._replace(value, this._hostStart, this._hostEnd))
  }

  // https://url.spec.whatwg.org/#dom-url-port

  get port() {
    return this._slice(this._hostEnd + 1 /* : */, this._pathStart)
  }

  set port(value) {
    if (cannotHaveCredentialsOrPort(this)) {
      return
    }

    value = strip(String(value))

    if (value !== '') {
      value = parsePort(value)

      if (value === null) return
    }

    this._update(this._replace(value, this._hostEnd, this._pathStart))
  }

  // https://url.spec.whatwg.org/#dom-url-pathname

  get pathname() {
    return this._slice(this._pathStart, this._queryStart - 1 /* ? */)
  }

  set pathname(value) {
    if (hasOpaquePath(this)) {
      return
    }

    value = encodePath(encodeEdges(String(value)))

    // An empty path is left alone, as only a special scheme is required to have
    // one and the reparse inserts it there.
    if (value !== '' && value[0] !== '/' && value[0] !== '\\') {
      value = '/' + value
    }

    this._update(this._replace(value, this._pathStart, this._queryStart - 1 /* ? */))
  }

  // https://url.spec.whatwg.org/#dom-url-search

  get search() {
    return this._slice(this._queryStart - 1 /* ? */, this._fragmentStart - 1 /* # */)
  }

  set search(value) {
    value = String(value)

    if (value !== '') {
      if (value[0] === '?') value = value.slice(1)

      value = '?' + encodeQuery(encodeEdges(value))
    }

    this._update(
      this._replace(value, this._queryStart - 1 /* ? */, this._fragmentStart - 1 /* # */)
    )
  }

  // https://url.spec.whatwg.org/#dom-url-searchparams

  get searchParams() {
    if (this._params === null) {
      this._params = new URLSearchParams(this.search, this)
    }

    return this._params
  }

  // https://url.spec.whatwg.org/#dom-url-hash

  get hash() {
    return this._slice(this._fragmentStart - 1 /* # */)
  }

  set hash(value) {
    value = String(value)

    // The fragment runs to the end of the URL, so nothing in it can escape into
    // another component and no delimiter needs encoding here.
    if (value !== '') {
      if (value[0] === '#') value = value.slice(1)

      value = '#' + encodeEdges(value)
    }

    this._update(this._replace(value, this._fragmentStart - 1 /* # */))
  }

  toString() {
    return this._href
  }

  toJSON() {
    return this._href
  }

  [Symbol.for('bare.inspect')]() {
    return {
      __proto__: { constructor: URL },

      href: this.href,
      protocol: this.protocol,
      username: this.username,
      password: this.password,
      host: this.host,
      hostname: this.hostname,
      port: this.port,
      pathname: this.pathname,
      search: this.search,
      searchParams: this.searchParams,
      hash: this.hash
    }
  }

  _slice(start, end = this._href.length) {
    return this._href.slice(start, end)
  }

  _replace(replacement, start, end = this._href.length) {
    return this._slice(0, start) + replacement + this._slice(end)
  }

  _parse(input, base, shouldThrow) {
    let href

    try {
      href = binding.parse(input, base || null, components, shouldThrow)
    } catch (err) {
      if (err instanceof TypeError || err.code !== undefined) throw err

      throw errors.INVALID_URL(`Invalid URL '${input}'`, input)
    }

    if (href === undefined) return

    this._href = href
    this._schemeEnd = components[0]
    this._usernameEnd = components[1]
    this._hostStart = components[2]
    this._hostEnd = components[3]
    this._pathStart = components[5]

    const queryStart = components[6]
    const fragmentStart = components[7]

    const end = href.length + 1

    this._queryStart = queryStart === unset ? end : queryStart
    this._fragmentStart = fragmentStart === unset ? end : fragmentStart
  }

  _update(input) {
    try {
      this._parse(input, null, true)
    } catch (err) {
      if (err instanceof TypeError) throw err

      return
    }

    if (this._params) this._params._parse(this.search)
  }
}

module.exports = exports = URL

// https://url.spec.whatwg.org/#url-opaque-path
function hasOpaquePath(url) {
  return url.pathname[0] !== '/'
}

// https://url.spec.whatwg.org/#cannot-have-a-username-password-port
function cannotHaveCredentialsOrPort(url) {
  return url.hostname === '' || url.protocol === 'file:'
}

// Whether the URL carries a userinfo section, and so an `@` before its host.
function hasCredentials(url) {
  return url._hostStart !== url._usernameEnd
}

// Almost no input contains any of these, so it is worth ruling all three out
// before rewriting anything.
function strip(value) {
  if (value.indexOf('\t') === -1 && value.indexOf('\n') === -1 && value.indexOf('\r') === -1) {
    return value
  }

  return value.replace(whitespaceAll, '')
}

// https://url.spec.whatwg.org/#host-state
//
// Host parsing stops at the first character that starts another component, so
// anything from there on is dropped rather than spliced into the host.
function truncateHost(protocol, value) {
  const end = value.search(special.has(protocol) ? hostEnd : hostEndOpaque)

  return end === -1 ? value : value.slice(0, end)
}

// Only a special scheme other than file has to have a host.
function cannotHaveEmptyHost(protocol) {
  return protocol !== 'file' && special.has(protocol)
}

// The index of the colon that separates a host from its port, disregarding the
// colons of an IPv6 address, or -1 if the host carries no port.
function portSeparator(host) {
  return host.indexOf(':', host[0] === '[' ? host.indexOf(']') : 0)
}

// https://url.spec.whatwg.org/#port-state
//
// Parsing stops at the first character that is not a digit, so a value with no
// leading digits, or one that overflows, leaves the port as it was.
function parsePort(value) {
  value = /^\d*/.exec(value)[0]

  if (value === '' || Number(value) > 65535) return null

  return ':' + value
}

function encodeUserinfo(value) {
  if (!userinfoDelimiter.test(value)) return value

  return value.replace(userinfoDelimiterAll, (match) => escapes[match])
}

function encodePath(value) {
  if (!pathDelimiter.test(value)) return value

  return value.replace(pathDelimiterAll, (match) => escapes[match])
}

function encodeQuery(value) {
  if (value.indexOf('#') === -1) return value

  return value.replaceAll('#', '%23')
}

// Percent-encodes a leading or trailing run of C0 control or space, which the
// parser would otherwise strip from a value that lands at either end of the
// href. Every component that can end a URL encodes them anyway, so this only
// brings the encoding forward.
function encodeEdges(value) {
  const len = value.length

  if (len === 0) return value
  if (value.charCodeAt(0) > 0x20 && value.charCodeAt(len - 1) > 0x20) return value

  return value.replace(edges, (match) => {
    let encoded = ''

    for (let i = 0, n = match.length; i < n; i++) {
      encoded += '%' + match.charCodeAt(i).toString(16).padStart(2, '0').toUpperCase()
    }

    return encoded
  })
}

exports.URL = URL
exports.URLSearchParams = URLSearchParams

exports.errors = errors

exports.isURL = function isURL(value) {
  if (value instanceof URL) return true

  return typeof value === 'object' && value !== null && value[kind] === URL[kind]
}

exports.isURLSearchParams = URLSearchParams.isURLSearchParams

// https://url.spec.whatwg.org/#dom-url-parse
exports.parse = function parse(input, base) {
  const url = new URL(input, base, { throw: false })
  return url._href ? url : null
}

// https://url.spec.whatwg.org/#dom-url-canparse
exports.canParse = function canParse(input, base) {
  return binding.canParse(String(input), base ? String(base) : null)
}

exports.fileURLToPath = function fileURLToPath(url) {
  if (typeof url === 'string') {
    url = new URL(url)
  }

  if (url.protocol !== 'file:') {
    throw errors.INVALID_URL_SCHEME('The URL must use the file: protocol')
  }

  if (!isWindows && url.hostname) {
    throw errors.INVALID_FILE_URL_HOST("The file: URL host must be 'localhost' or empty")
  }

  const encoded = url.pathname

  // Every check below looks for a percent encoded sequence, as does the decoding
  // that follows, so a path without any can skip all of them.
  const hasEncoded = encoded.includes('%')

  if (hasEncoded) {
    if (isWindows) {
      if (/%2f|%5c/i.test(encoded)) {
        throw errors.INVALID_FILE_URL_PATH(
          'The file: URL path must not include encoded \\ or / characters'
        )
      }
    } else if (/%2f/i.test(encoded)) {
      throw errors.INVALID_FILE_URL_PATH('The file: URL path must not include encoded / characters')
    }

    if (/%00/i.test(encoded)) {
      throw errors.INVALID_FILE_URL_PATH(
        'The file: URL path must not include encoded NUL characters'
      )
    }
  }

  const pathname = path.normalize(hasEncoded ? decodeURIComponent(encoded) : encoded)

  if (isWindows) {
    if (url.hostname) return '\\\\' + url.hostname + pathname

    const letter = pathname.charCodeAt(1) | 0x20

    if (letter < 0x61 /* a */ || letter > 0x7a /* z */ || pathname.charCodeAt(2) !== 0x3a /* : */) {
      throw errors.INVALID_FILE_URL_PATH('The file: URL path must be absolute')
    }

    return pathname.slice(1)
  }

  return pathname
}

exports.pathToFileURL = function pathToFileURL(pathname) {
  let resolved = path.resolve(pathname)

  if (pathname[pathname.length - 1] === '/') {
    resolved += '/'
  } else if (isWindows && pathname[pathname.length - 1] === '\\') {
    resolved += '\\'
  }

  // Paths hardly ever contain any of these, so one pass to rule them out is
  // cheaper than the six or seven replacements it stands in for.
  if (reserved.test(resolved)) {
    resolved = resolved
      .replaceAll('%', '%25') // Must be first
      .replaceAll('#', '%23')
      .replaceAll('?', '%3f')
      .replaceAll('\n', '%0a')
      .replaceAll('\r', '%0d')
      .replaceAll('\t', '%09')

    if (!isWindows) {
      resolved = resolved.replaceAll('\\', '%5c')
    }
  }

  return new URL('file:' + resolved)
}

exports.format = function format(parts) {
  const { protocol, auth, host, hostname, port, pathname, search, query, hash, slashes } = parts

  let result = ''

  if (typeof protocol === 'string') {
    result += protocol

    if (protocol[protocol.length - 1] !== ':') {
      result += ':'
    }

    if (slashes === true || /https?|ftp|gopher|file/.test(protocol)) {
      result += '//'
    }
  }

  if (typeof auth === 'string') {
    if (host || hostname) result += auth + '@'
  }

  if (typeof host === 'string') result += host
  else {
    result += hostname

    if (port) result += ':' + port
  }

  if (typeof pathname === 'string' && pathname !== '') {
    if (pathname[0] !== '/') result += '/'
    result += pathname
  }

  if (typeof search === 'string') {
    if (search[0] !== '?') result += '?'
    result += search
  } else if (typeof query === 'object' && query !== null) {
    result += '?' + new URLSearchParams(query)
  }

  if (typeof hash === 'string') {
    if (hash[0] !== '#') result += '#'
    result += hash
  }

  return result
}
module.exports = class URLError extends Error {
  constructor(msg, fn = URLError, code = fn.name) {
    super(`${code}: ${msg}`)

    this.code = code

    if (Error.captureStackTrace) Error.captureStackTrace(this, fn)
  }

  get name() {
    return 'URLError'
  }

  static INVALID_URL(msg, input) {
    const err = new URLError(msg, URLError.INVALID_URL)

    err.input = input

    return err
  }

  static INVALID_URL_SCHEME(msg = 'Invalid URL') {
    return new URLError(msg, URLError.INVALID_URL_SCHEME)
  }

  static INVALID_FILE_URL_HOST(msg = 'Invalid file: URL host') {
    return new URLError(msg, URLError.INVALID_FILE_URL_HOST)
  }

  static INVALID_FILE_URL_PATH(msg = 'Invalid file: URL path') {
    return new URLError(msg, URLError.INVALID_FILE_URL_PATH)
  }
}
const kind = Symbol.for('bare.url.search-params.kind')

// The URL each instance writes back to, if any. Kept module private so that the
// setter it drives cannot be pointed at an arbitrary object.
const urls = new WeakMap()

class URLSearchParams {
  static get [kind]() {
    return 0 // Compatibility version
  }

  // https://url.spec.whatwg.org/#dom-urlsearchparams-urlsearchparams
  constructor(init, url = null) {
    this._params = null

    if (url) urls.set(this, url)

    if (typeof init === 'string') {
      this._parse(init)
    } else {
      this._params = new Map()

      if (init) {
        for (const [name, value] of typeof init[Symbol.iterator] === 'function'
          ? init
          : Object.entries(init)) {
          this.append(name, value)
        }
      }
    }
  }

  get [kind]() {
    return URLSearchParams[kind]
  }

  // https://url.spec.whatwg.org/#dom-urlsearchparams-size
  get size() {
    let size = 0

    for (const values of this._params.values()) size += values.length

    return size
  }

  // https://url.spec.whatwg.org/#dom-urlsearchparams-append
  append(name, value = null) {
    if (value === null) return

    let list = this._params.get(name)

    if (list === undefined) {
      list = []
      this._params.set(name, list)
    }

    list.push(value)

    this._update()
  }

  // https://url.spec.whatwg.org/#dom-urlsearchparams-delete
  delete(name, value = null) {
    if (value === null) this._params.delete(name)
    else {
      let list = this._params.get(name)

      if (list === undefined) return

      list = list.filter((found) => found !== value)

      if (list.length === 0) this._params.delete(name)
      else this._params.set(name, list)
    }

    this._update()
  }

  // https://url.spec.whatwg.org/#dom-urlsearchparams-get
  get(name) {
    const list = this._params.get(name)

    if (list === undefined) return null

    return list[0]
  }

  // https://url.spec.whatwg.org/#dom-urlsearchparams-getall
  getAll(name) {
    const list = this._params.get(name)

    if (list === undefined) return []

    return Array.from(list)
  }

  // https://url.spec.whatwg.org/#dom-urlsearchparams-has
  has(name, value = null) {
    const list = this._params.get(name)

    if (list === undefined) return false

    if (value === null) return true

    return list.includes(value)
  }

  // https://url.spec.whatwg.org/#dom-urlsearchparams-set
  set(name, value = null) {
    if (value === null) this._params.delete(name)
    else this._params.set(name, [value])

    this._update()
  }

  toString() {
    return this._serialize()
  }

  toJSON() {
    return [...this]
  }

  *[Symbol.iterator]() {
    for (const [name, values] of this._params) {
      for (const value of values) yield [name, value]
    }
  }

  [Symbol.for('bare.inspect')]() {
    const object = {
      __proto__: { constructor: URLSearchParams }
    }

    for (const [name, values] of this._params) {
      if (values.length === 1) object[name] = values[0]
      else object[name] = values
    }

    return object
  }

  // https://url.spec.whatwg.org/#concept-urlsearchparams-update
  _update() {
    const url = urls.get(this)

    if (url === undefined) return

    url.search = this._serialize()
  }

  // https://url.spec.whatwg.org/#concept-urlencoded-parser
  _parse(input) {
    const params = new Map()

    this._params = params

    const len = input.length

    let start = input.charCodeAt(0) === 0x3f /* ? */ ? 1 : 0

    while (start < len) {
      let end = input.indexOf('&', start)
      if (end === -1) end = len

      if (end !== start) {
        let split = input.indexOf('=', start)
        if (split === -1 || split > end) split = end

        const name = decode(input.slice(start, split))
        const value = split === end ? '' : decode(input.slice(split + 1, end))

        const list = params.get(name)

        if (list === undefined) params.set(name, [value])
        else list.push(value)
      }

      start = end + 1
    }
  }

  // https://url.spec.whatwg.org/#concept-urlencoded-serializer
  _serialize() {
    let result = ''
    let separator = ''

    for (const [name, values] of this._params) {
      // The name is shared by all of its values, so it is only encoded once.
      const encoded = encode(String(name))

      for (const value of values) {
        result += separator + encoded + '=' + encode(String(value))
        separator = '&'
      }
    }

    return result
  }
}

module.exports = exports = URLSearchParams

exports.isURLSearchParams = function isURLSearchParams(value) {
  if (value instanceof URLSearchParams) return true

  return typeof value === 'object' && value !== null && value[kind] === URLSearchParams[kind]
}

// https://url.spec.whatwg.org/#concept-urlencoded-byte-serializer
//
// Everything except ASCII alphanumerics and `*-._` is percent-encoded, and a
// space becomes `+` rather than `%20`. Names and values usually consist entirely
// of characters that are left alone, so a component is tested as a whole before
// any of it is rewritten.
const unencoded = /^[\w*.-]*$/

// As above, but allowing spaces, which need nothing more than turning into `+`.
// Spaces are by far the most common reason for a component to need rewriting at
// all, so recognizing them here spares those components a trip through
// encodeURIComponent().
const spaced = /^[\w*. -]*$/

// The characters that encodeURIComponent() leaves alone but the urlencoded byte
// serializer does not. Two patterns are needed because a global regular
// expression cannot be shared between test() and replace(), as test() advances
// its lastIndex.
const extra = /[!'()~]/
const extraAll = /[!'()~]/g

const escapes = {
  '!': '%21',
  "'": '%27',
  '(': '%28',
  ')': '%29',
  '~': '%7E'
}

// A surrogate pair, or a surrogate that is not part of one and so encodes no
// scalar value. Pairs are matched first so that only the capture group can hold
// a lone surrogate; a lookbehind would say this more directly but isn't
// supported by every engine.
const lone = /[\ud800-\udbff][\udc00-\udfff]|([\ud800-\udbff]|[\udc00-\udfff])/g

function encode(component) {
  if (unencoded.test(component)) return component

  if (spaced.test(component)) return component.replaceAll(' ', '+')

  let encoded

  try {
    encoded = encodeURIComponent(component)
  } catch {
    // encodeURIComponent() rejects lone surrogates, whereas the UTF-8 encoder the
    // serializer is defined in terms of replaces them with U+FFFD.
    encoded = encodeURIComponent(
      component.replace(lone, (match, single) => (single === undefined ? match : '\ufffd'))
    )
  }

  if (encoded.includes('%20')) encoded = encoded.replaceAll('%20', '+')

  if (extra.test(encoded)) encoded = encoded.replace(extraAll, (match) => escapes[match])

  return encoded
}

// https://url.spec.whatwg.org/#percent-decode-after-encoding
function decode(component) {
  if (component.indexOf('+') !== -1) component = component.replaceAll('+', ' ')

  if (component.indexOf('%') === -1) return component

  return decodeURIComponent(component)
}
{
  "name": "bare-url",
  "version": "2.5.4",
  "description": "WHATWG URL implementation for JavaScript",
  "exports": {
    "./package": "./package.json",
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    },
    "./global": {
      "types": "./global.d.ts",
      "default": "./global.js"
    }
  },
  "files": [
    "index.js",
    "index.d.ts",
    "global.js",
    "global.d.ts",
    "binding.c",
    "binding.js",
    "CMakeLists.txt",
    "lib",
    "prebuilds"
  ],
  "addon": true,
  "scripts": {
    "format": "prettier --write . && lunte --fix",
    "lint": "prettier --check . && lunte",
    "test": "brittle-bare --coverage test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-url.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-url/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-url",
  "dependencies": {
    "bare-path": "^3.0.0"
  },
  "devDependencies": {
    "brittle": "^3.3.2",
    "cmake-bare": "^1.8.1",
    "cmake-fetch": "^1.0.0",
    "lunte": "^1.6.0",
    "prettier": "^3.3.3",
    "prettier-config-holepunch": "^2.0.0"
  }
}
module.exports = require('bare-events')
{
  "name": "events-universal",
  "version": "1.0.1",
  "description": "Universal wrapper for the Node.js events module",
  "exports": {
    "./package": "./package.json",
    ".": {
      "bare": "./bare.js",
      "react-native": "./react-native.js",
      "default": "./default.js"
    }
  },
  "files": [
    "index.js",
    "default.js",
    "bare.js",
    "react-native.js"
  ],
  "scripts": {
    "test": "prettier . --check"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/events-universal.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/events-universal/issues"
  },
  "homepage": "https://github.com/holepunchto/events-universal#readme",
  "dependencies": {
    "bare-events": "^2.7.0"
  },
  "devDependencies": {
    "prettier": "^3.4.2",
    "prettier-config-holepunch": "^1.0.0"
  }
}
module.exports = class FixedFIFO {
  constructor (hwm) {
    if (!(hwm > 0) || ((hwm - 1) & hwm) !== 0) throw new Error('Max size for a FixedFIFO should be a power of two')
    this.buffer = new Array(hwm)
    this.mask = hwm - 1
    this.top = 0
    this.btm = 0
    this.next = null
  }

  clear () {
    this.top = this.btm = 0
    this.next = null
    this.buffer.fill(undefined)
  }

  push (data) {
    if (this.buffer[this.top] !== undefined) return false
    this.buffer[this.top] = data
    this.top = (this.top + 1) & this.mask
    return true
  }

  shift () {
    const last = this.buffer[this.btm]
    if (last === undefined) return undefined
    this.buffer[this.btm] = undefined
    this.btm = (this.btm + 1) & this.mask
    return last
  }

  peek () {
    return this.buffer[this.btm]
  }

  isEmpty () {
    return this.buffer[this.btm] === undefined
  }
}
const FixedFIFO = require('./fixed-size')

module.exports = class FastFIFO {
  constructor (hwm) {
    this.hwm = hwm || 16
    this.head = new FixedFIFO(this.hwm)
    this.tail = this.head
    this.length = 0
  }

  clear () {
    this.head = this.tail
    this.head.clear()
    this.length = 0
  }

  push (val) {
    this.length++
    if (!this.head.push(val)) {
      const prev = this.head
      this.head = prev.next = new FixedFIFO(2 * this.head.buffer.length)
      this.head.push(val)
    }
  }

  shift () {
    if (this.length !== 0) this.length--
    const val = this.tail.shift()
    if (val === undefined && this.tail.next) {
      const next = this.tail.next
      this.tail.next = null
      this.tail = next
      return this.tail.shift()
    }

    return val
  }

  peek () {
    const val = this.tail.peek()
    if (val === undefined && this.tail.next) return this.tail.next.peek()
    return val
  }

  isEmpty () {
    return this.length === 0
  }
}
{
  "name": "fast-fifo",
  "version": "1.3.2",
  "description": "A fast fifo implementation similar to the one powering nextTick in Node.js core",
  "main": "index.js",
  "files": [
    "./index.js",
    "./fixed-size.js"
  ],
  "dependencies": {},
  "devDependencies": {
    "standard": "^17.1.0",
    "brittle": "^3.3.2"
  },
  "scripts": {
    "test": "standard && brittle test.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/mafintosh/fast-fifo.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/mafintosh/fast-fifo/issues"
  },
  "homepage": "https://github.com/mafintosh/fast-fifo"
}
// The pinned dialect, in ONE place.
//
// markdown-strip.md §2.1 pins CommonMark 0.31.2 plus exactly six extensions, and the set is CLOSED:
// syntax outside it is literal text, not markup. Both `strip()` (→ text + facets, for bridges) and
// `render()` (→ HTML, for our own surfaces) must parse identically — if they drift, the same post
// says two different things depending on who is looking, which is the whole failure the pinned
// dialect exists to prevent. So the config and the lexical rules live here and are imported, never
// copied.

import { gfmStrikethrough } from 'micromark-extension-gfm-strikethrough'
import { gfmStrikethroughFromMarkdown } from 'mdast-util-gfm-strikethrough'
import { gfmTable } from 'micromark-extension-gfm-table'
import { gfmTableFromMarkdown } from 'mdast-util-gfm-table'
import { gfmFootnote } from 'micromark-extension-gfm-footnote'
import { gfmFootnoteFromMarkdown } from 'mdast-util-gfm-footnote'
import { directive } from 'micromark-extension-directive'
import { directiveFromMarkdown } from 'mdast-util-directive'
import { parseUri } from './uri.js'

export const MICROMARK = {
  // ⛔ singleTilde:false — micromark defaults it TRUE, which would consume `~x~` as strikethrough.
  extensions: [gfmStrikethrough({ singleTilde: false }), gfmTable(), gfmFootnote(), directive()],
  mdastExtensions: [
    gfmStrikethroughFromMarkdown(),
    gfmTableFromMarkdown(),
    gfmFootnoteFromMarkdown(),
    directiveFromMarkdown()
  ]
}

// §3.3 NORMATIVE — favela-web's shipped URL_RE verbatim. Changing it changes records.
export const URL_RE = /\bhttps?:\/\/[^\s<]+[^\s<.,:;!?)\]}'"]/g
export const WIKILINK_RE = /\[\[([^\][|]+)(?:\|([^\][]*))?\]\]/g
export const HASHTAG_RE = /(^|\s)(#([A-Za-z0-9_À-￿]{1,64}))/g

// §3.2 — only http/https may carry a link. Everything else renders as text.
export const isHttp = (uri) => /^https?:\/\//i.test(String(uri || ''))
// §3.4 — a mention names a USER: `favela://<identity-root>`; `did:` is the legacy form.
//
// ⛔ NARROWED. This was bare `/^(favela:\/\/|did:)/`, so it matched EVERY favela URI — including
// `favela://blob/<oid>` (worked around below by telling every caller to test for a blob first) and
// `favela://<root>/<collection>/<name>`, which was NOT worked around because the record form was
// never specified. favela-surface emits one for a quote (router.js:291), so a quote was classified
// as a mention of a user whose identity was actually a feed key. See lib/uri.js.
//
// A mention is now the USER form specifically: a favela URI with no path, or a `did:`.
export const isMention = (uri) => {
  const s = String(uri || '')
  if (/^did:/i.test(s)) return true
  const u = parseUri(s)
  return !!u && u.kind === 'user'
}

// record-vocabulary.md §3.5 — an embedded file: `favela://blob/<oid>`, where `<oid>` is the
// hyper-overlay content hash. A record never contains media; it names a file the overlay holds.
//
// ⛔ This form ALSO matches `isMention`, which is `^favela://`. Every caller must test for a blob
// FIRST — otherwise an embed renders as a mention span and the picture silently disappears. That
// ordering is not a style choice, it is the difference between an image and nothing.
export const BLOB_RE = /^favela:\/\/blob\/([0-9a-f]{8,128})$/i
export const isBlob = (uri) => BLOB_RE.test(String(uri || ''))
export const blobOid = (uri) => {
  const m = BLOB_RE.exec(String(uri || ''))
  return m ? m[1].toLowerCase() : null
}
// render(markdown) -> HTML — the NATIVE projection.
//
// `strip()` is for bridges: it throws formatting away to fit networks that have none. This is the
// opposite, and it is what our own surfaces show. Both parse through `dialect.js` so they can never
// disagree about what the markup means.
//
// ⛔ This is the one function in the package where a defect is an XSS rather than a rendering bug,
// so it is fail-closed in the same way `reach.js` is for privacy:
//   - every text node and every attribute value is escaped;
//   - only http/https may carry an `href` or `src` — anything else renders as its label, or is
//     dropped, and NEVER becomes a link;
//   - raw HTML in the source is emitted as escaped text, never as markup.
//
// ★ Note the spoiler asymmetry, which is the thesis in one node type: `:::spoiler` is REVEALED by
// strip (atproto has no such concept) and CONCEALED here. Same record, two projections, and only
// one of them is lossy — which is why `library.md` §5 asks the composer to say so.

import { fromMarkdown } from 'mdast-util-from-markdown'
import { MICROMARK, WIKILINK_RE, HASHTAG_RE, isHttp, isMention, blobOid } from './dialect.js'

const ESC = { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }
const esc = (s) => String(s == null ? '' : s).replace(/[&<>"']/g, (c) => ESC[c])

// Text runs get the two LEXICAL rules the dialect owns (§3.6, §3.7) — wiki-links and hashtags are
// not structural, so micromark never sees them and they are applied here, after escaping, so a page
// name or a tag can never inject markup.
function inlineText (raw) {
  let out = esc(raw)
  out = out.replace(WIKILINK_RE, (_m, page, label) =>
    '<span class="wikilink" data-page="' + esc(page) + '">' + esc(label !== undefined ? label : page) + '</span>')
  out = out.replace(HASHTAG_RE, (_m, lead, whole, tag) =>
    lead + '<span class="hashtag" data-tag="' + esc(tag) + '">' + esc(whole) + '</span>')
  return out
}

const children = (node, o) => (node.children || []).map((c) => toHtml(c, o)).join('')

/**
 * The images in a paragraph that contains NOTHING ELSE, or null.
 *
 * ⛔ "Nothing else" is strict: any text with content, any link, any video, and this is prose with a
 * picture in it rather than a row of pictures. Grouping those would silently restructure someone's
 * writing, which is a much worse failure than not grouping.
 *
 * Whitespace between images does not count — a composer appending embeds produces exactly that.
 */
function imageRun (node, o) {
  if (!node || node.type !== 'paragraph') return null
  const out = []
  for (const c of node.children || []) {
    if (c.type === 'image') {
      const b = blob(o, c.url || '')
      // ⛔ Only resolved IMAGES group. A video has controls and its own aspect; an unresolved embed
      // renders as nothing and would leave a hole in the grid.
      if (!b || !b.mimeType.startsWith('image/')) return null
      out.push(c)
    } else if (c.type === 'text' && !String(c.value || '').trim()) {
      continue
    } else {
      return null
    }
  }
  return out.length ? out : null
}

/**
 * Walk the top level, folding RUNS of image-only paragraphs into one lighttable.
 *
 * ⛔ A run, not a paragraph: the composer appends one embed per attachment separated by blank lines,
 * so three attachments are three paragraphs and grouping per-paragraph would do nothing at all —
 * which is exactly what it did before this.
 *
 * ⚠ A single image is left alone. One picture is not a grid, and wrapping it in one would change
 * how every existing post renders for no gain.
 */
function topLevel (node, o) {
  const kids = node.children || []
  const out = []
  let i = 0
  while (i < kids.length) {
    const run = []
    let j = i
    for (;;) {
      const imgs = imageRun(kids[j], o)
      if (!imgs) break
      run.push(...imgs)
      j++
    }
    if (run.length > 1) {
      out.push('<div class="lighttable">' + run.map((n) => image(n, o)).join('') + '</div>')
      i = j
    } else {
      out.push(toHtml(kids[i], o))
      i++
    }
  }
  return out.join('')
}

/**
 * An embedded file: `favela://blob/<oid>` (record-vocabulary.md §3.5).
 *
 * ⛔ The library has NO OPINION about the URL scheme. `resolveBlob(oid)` belongs to the surface —
 * an HTTP surface returns a loopback path, a native one returns whatever it can display — and this
 * only escapes what comes back. Baking `http` in here would make a pure library assume a browser,
 * and the surfaces are not all browsers. The bytes themselves never move over HTTP: they arrive
 * P2P through hyper-overlay, and the surface is only saying where it can read them from.
 *
 * ⛔ It is NOT run through `isHttp`. That allowlist guards AUTHOR-supplied URLs, which are the
 * untrusted input it exists for; a resolver is the surface's own code and no author can influence
 * what it returns.
 *
 * Absent resolver, unknown oid, or a type this element cannot show ⇒ nothing, the same fail-closed
 * posture the rest of this file has.
 */
function blob (o, url) {
  const oid = blobOid(url)
  if (!oid) return null
  const r = o && typeof o.resolveBlob === 'function' ? o.resolveBlob(oid) : null
  if (!r || typeof r.src !== 'string' || r.src === '') return null
  return { src: r.src, mimeType: typeof r.mimeType === 'string' ? r.mimeType : '' }
}

function link (node, o) {
  const url = node.url || ''
  // ⛔ BLOB FIRST. `favela://blob/<oid>` also matches `isMention` (which is bare `^favela://`), so
  // testing mentions first turns every embed into a mention span and the file silently vanishes.
  const b = blob(o, url)
  if (b) {
    // A PDF is shown inline because a browser already knows how; anything else is a link. Either
    // way the bytes came P2P and the surface's resolver said where to read them.
    if (b.mimeType === 'application/pdf') {
      return '<embed class="doc" type="application/pdf" src="' + esc(b.src) + '">'
    }
    return '<a class="file" href="' + esc(b.src) + '">' + children(node, o) + '</a>'
  }
  // A mention is OURS to resolve, so it never becomes an href — a browser cannot follow favela://
  // and a surface should not be tempted to try. The target rides in a data attribute.
  if (isMention(url)) {
    return '<span class="mention" data-target="' + esc(url) + '">' + children(node) + '</span>'
  }
  // §3.2 — anything not http/https degrades to its label with NO link. That covers javascript:,
  // data:, vbscript: and every scheme nobody has invented yet, because it is an allowlist.
  if (!isHttp(url)) return children(node)
  return '<a href="' + esc(url) + '" rel="noopener noreferrer nofollow">' + children(node) + '</a>'
}

function image (node, o) {
  const alt = esc(node.alt || '')
  const b = blob(o, node.url || '')
  if (b) {
    if (b.mimeType.startsWith('image/')) {
      // ⛔ A PREVIEW, wrapped in a link to the file itself. An embed rendered at its natural size
      // takes over a timeline — a 4000px photo is not a post, it is a wall — but a preview that
      // cannot be opened has thrown the picture away. The surface constrains `.embed` and the
      // anchor gives the full file back, with no script involved.
      return '<a class="lightbox" href="' + esc(b.src) + '">' +
        '<img class="embed" loading="lazy" src="' + esc(b.src) + '" alt="' + alt + '">' +
        '</a>'
    }
    // ⛔ `![]()` on a video is deliberate, not a mistake: §3.5 has the embed pipeline consuming
    // images in source order, and a video is the same authoring gesture with different bytes.
    if (b.mimeType.startsWith('video/')) {
      return '<video class="embed" controls preload="metadata" src="' + esc(b.src) + '"></video>'
    }
    // A file that is neither is not an image, whatever the author wrote. Dropped, not guessed.
    return ''
  }
  if (!isHttp(node.url || '')) return '' // dropped entirely — an <img> with no usable src is noise
  return '<img src="' + esc(node.url) + '" alt="' + alt + '">'
}

// :::spoiler / :::cw <text> / :::details — the pinned set (library.md §5).
function directive (node, o) {
  const name = node.name || ''
  const inner = children(node, o)
  if (name === 'spoiler') return '<details class="spoiler"><summary>spoiler</summary>' + inner + '</details>'
  if (name === 'cw') {
    const label = (node.attributes && node.attributes.label) || textOf(node.children && node.children[0]) || 'content warning'
    return '<details class="cw"><summary>' + esc(label) + '</summary>' + inner + '</details>'
  }
  if (name === 'details') return '<details>' + inner + '</details>'
  // An unknown directive contributes its CONTENTS, matching strip §4.8 — the dialect is closed, so
  // an unrecognised name is not markup and must not vanish.
  return inner
}

const textOf = (n) => (n && n.type === 'paragraph' ? (n.children || []).map((c) => c.value || '').join('') : '')

function toHtml (node, o) {
  if (!node || typeof node !== 'object') return ''
  switch (node.type) {
    case 'root': return topLevel(node, o)
    case 'paragraph': {
      // ⛔ A paragraph that renders to nothing IS nothing. `image()` drops an unresolvable embed
      // deliberately — an <img> with no usable src is noise — but the paragraph around it used to
      // render anyway, so a post whose only content was a picture produced `<p></p>` and every
      // consumer showed a blank line. Measured against a live feed 2026-09-07.
      const inner = children(node, o)
      return inner.trim() ? '<p>' + inner + '</p>' : ''
    }
    case 'text': return inlineText(node.value)
    case 'strong': return '<strong>' + children(node, o) + '</strong>'
    case 'emphasis': return '<em>' + children(node, o) + '</em>'
    case 'delete': return '<del>' + children(node, o) + '</del>'
    case 'inlineCode': return '<code>' + esc(node.value) + '</code>'
    case 'break': return '<br>'
    case 'thematicBreak': return '<hr>'
    case 'link': return link(node, o)
    case 'image': return image(node, o)
    case 'heading': {
      const d = Math.min(Math.max(node.depth || 1, 1), 6)
      return '<h' + d + '>' + children(node, o) + '</h' + d + '>'
    }
    case 'blockquote': return '<blockquote>' + children(node, o) + '</blockquote>'
    case 'list': return node.ordered ? '<ol>' + children(node, o) + '</ol>' : '<ul>' + children(node, o) + '</ul>'
    case 'listItem': {
      const inner = (node.children || []).map((c) =>
        c.type === 'paragraph' ? children(c, o) : toHtml(c, o)).join('')
      return '<li>' + inner + '</li>'
    }
    case 'code':
      return '<pre><code' + (node.lang ? ' class="language-' + esc(node.lang) + '"' : '') + '>' +
        esc(node.value) + '</code></pre>'
    case 'table': return '<table>' + children(node, o) + '</table>'
    case 'tableRow': {
      const head = node.__head === true
      const cells = (node.children || []).map((c) =>
        head ? '<th>' + children(c, o) + '</th>' : '<td>' + children(c, o) + '</td>').join('')
      return '<tr>' + cells + '</tr>'
    }
    case 'tableCell': return children(node, o)
    case 'footnoteReference': return '<sup class="fnref">' + esc(node.identifier) + '</sup>'
    case 'footnoteDefinition': return '<div class="fn" id="fn-' + esc(node.identifier) + '">' + children(node, o) + '</div>'
    case 'containerDirective':
    case 'leafDirective':
    case 'textDirective': return directive(node, o)
    // ⛔ Raw HTML is emitted as TEXT. The dialect does not include HTML, and rendering it would hand
    // markup authorship to whoever wrote the record.
    case 'html': return esc(node.value)
    default: return children(node, o)
  }
}

/**
 * render(source, { resolveBlob }) -> html
 *
 * `resolveBlob(oid) -> { src, mimeType } | null` is how a surface says where a `favela://blob/<oid>`
 * can be read from and what it is. Omit it and every embed renders as nothing, which is correct for
 * a caller that cannot serve files.
 *
 * TOTAL — never throws. Anything unparseable degrades to escaped text, the same posture `strip` has,
 * because a surface that throws on one bad record shows nothing at all.
 */
export function render (source, opts = {}) {
  const src = String(source == null ? '' : source).replace(/\r\n?/g, '\n')
  let tree
  try {
    tree = fromMarkdown(src, MICROMARK)
  } catch {
    return '<p>' + esc(src) + '</p>'
  }
  // mdast marks the header row only by position, so tag it before the walk.
  for (const node of tree.children || []) {
    if (node.type === 'table' && node.children && node.children[0]) node.children[0].__head = true
  }
  try { return toHtml(tree, opts) } catch { return '<p>' + esc(src) + '</p>' }
}

export default render

/**
 * embeds(source) -> [{ oid, alt, kind }] — every `favela://blob/<oid>` a record references.
 *
 * ⛔ Uses the SAME parse `render` does, not a regular expression. A regex would match a blob URI
 * inside a code fence or an escaped bracket and report a reference the renderer will never make —
 * and the two disagreeing about what a record references is how a surface fetches the wrong files.
 *
 * `kind` is the authoring gesture: `image` for `![…]()`, `link` for `[…]()`. Source order, because
 * §3.5 has the embed pipeline consuming them in it.
 *
 * TOTAL — never throws; an unparseable record has no embeds.
 */
const labelOf = (n) => (n.children || []).map((c) => c.value || labelOf(c)).join('')

export function embeds (source) {
  const src = String(source == null ? '' : source).replace(/\r\n?/g, '\n')
  let tree
  try { tree = fromMarkdown(src, MICROMARK) } catch { return [] }

  const out = []
  const walk = (node) => {
    if (!node || typeof node !== 'object') return
    if (node.type === 'image' || node.type === 'link') {
      const oid = blobOid(node.url || '')
      if (oid) {
        out.push({
          oid,
          // ⛔ Not `textOf`, which only walks a paragraph — a link's label is its own children.
          alt: node.type === 'image' ? (node.alt || '') : labelOf(node),
          kind: node.type === 'image' ? 'image' : 'link'
        })
      }
    }
    for (const c of node.children || []) walk(c)
  }
  try { walk(tree) } catch { return out }
  return out
}
// uri.js — the `favela://` scheme.
//
// ⛔ WHY THIS FILE EXISTS. `isMention` was `/^(favela:\/\/|did:)/` — it matched ANY favela URI. The
// blob form was noticed and worked around by ORDERING: dialect.js:43 says "every caller must test
// for a blob FIRST — otherwise an embed renders as a mention span and the picture silently
// disappears". The RECORD form was never specified, so nobody worked around it, and
// `favela://<x>/<collection>/<name>` — which favela-surface already emits for a quote
// (router.js:291) — was classified as a mention of a user. A quote rendered as a mention of nobody.
//
// A scheme with three forms needs a parse, not a chain of predicates in the right order.
//
// ── The authority (hyper-social #61 §12 #1) ───────────────────────────────────────────────────
//
// The authority was TWO things at once. A mention carries an IDENTITY ROOT (dialect.js:37,
// favela-peer/lib/mentions.js:116). The quote path carries a FEED KEY. Both are 64 hex characters,
// so no reader could tell which it had.
//
// ★ IT IS THE IDENTITY ROOT. One space, everywhere:
//
//   1. The mention form already uses it, and those records are written. Changing that would move
//      every existing mention.
//   2. `NAMING.md` §3 — a name resolves to an identity ROOT, and only then to a room key.
//   3. hyper-social #64 §1 — handle claims anchor to the root. pgh shipped the device-key version
//      first and needed `pgh names reanchor --all --force` to repair it; favela stored something
//      worse still (a manifest-derived writer key, which can never sign).
//   4. #61 §2a — deriving an address from a rotatable locator is the exact failure `favela://`
//      exists to prevent. `roomKey = hash(NS('room-key') || did)` broke because a DID can rotate.
//
// A FEED KEY IS A LOCATOR, not an identity: it says where bytes are, and #61 §12 #3 leaves oplog
// stability under `setLocal` writer rotation unverified. The root says who. Same split as a record's
// name versus its oplog — identity is not the same question as where to fetch.
//
// Resolution root → feed already exists: `profiles()` returns feed → root for feeds you hold, and a
// handle claim binds root → feed and REPLICATES, which is the namespace layer's whole job.
//
// ⚠ PERMISSIVE WHEN READING, STRICT WHEN WRITING. `parseUri` does not police the authority's shape,
// because it must read bytes already written — including a conformance vector that mentions
// `favela://abc123`. The 64-hex requirement is enforced in `recordUri`, where we mint.

const PREFIX = 'favela://'
const HEX64 = /^[0-9a-f]{64}$/i
const OID = /^[0-9a-f]{8,128}$/i
// One path segment, matching favela-peer's `isSafeSeg`: no separator, not a dot entry.
const isSeg = (s) => typeof s === 'string' && s.length > 0 && !s.includes('/') && s !== '.' && s !== '..'

/**
 * Parse a favela URI into exactly one of three kinds, or null.
 *
 * ⛔ BY SHAPE, never by call order:
 *   favela://blob/<oid>                      → blob   (authority is the literal "blob")
 *   favela://<root>                          → user   (no path)
 *   favela://<root>/<collection>/<name>      → record (two path segments)
 *
 * Anything else is null. A URI we cannot classify must not be guessed at — rendering an
 * unrecognised form as the nearest familiar one is how the picture disappeared.
 */
export function parseUri (uri) {
  const s = String(uri == null ? '' : uri)
  if (!s.toLowerCase().startsWith(PREFIX)) return null

  const rest = s.slice(PREFIX.length)
  if (!rest) return null
  const parts = rest.split('/')

  if (parts[0].toLowerCase() === 'blob') {
    if (parts.length !== 2 || !OID.test(parts[1])) return null
    return { kind: 'blob', root: null, collection: null, name: null, oid: parts[1].toLowerCase() }
  }

  if (parts.length === 1) {
    return { kind: 'user', root: parts[0], collection: null, name: null, oid: null }
  }

  if (parts.length === 3) {
    if (!isSeg(parts[1]) || !isSeg(parts[2])) return null
    return { kind: 'record', root: parts[0], collection: parts[1], name: parts[2], oid: null }
  }

  return null
}

export const isUserUri = (uri) => { const u = parseUri(uri); return !!u && u.kind === 'user' }
export const isRecordUri = (uri) => { const u = parseUri(uri); return !!u && u.kind === 'record' }
export const isBlobUri = (uri) => { const u = parseUri(uri); return !!u && u.kind === 'blob' }

export const userUri = (root) => PREFIX + String(root)
export const blobUri = (oid) => PREFIX + 'blob/' + String(oid).toLowerCase()

/**
 * Mint a record URI.
 *
 * ⛔ STRICT ABOUT THE AUTHORITY, deliberately. This is the door a feed key would otherwise walk
 * through — favela-surface's quote path was putting one here — and once a URI is written into a
 * record's bytes it cannot be corrected, because SOCIAL-MODEL §8 forbids rewriting a record you did
 * not author. Refusing at mint time is the only place this can be enforced.
 */
export function recordUri ({ root, collection, name }) {
  if (!HEX64.test(String(root || ''))) {
    throw new Error('recordUri: the authority must be a 64-hex identity root, not a feed key')
  }
  if (!isSeg(collection) || !isSeg(name)) {
    throw new Error('recordUri: collection and name must each be one path segment')
  }
  return PREFIX + String(root).toLowerCase() + '/' + collection + '/' + name
}

export default { parseUri, isUserUri, isRecordUri, isBlobUri, userUri, recordUri, blobUri }
/**
 * Count how often a character (or substring) is used in a string.
 *
 * @param {string} value
 *   Value to search in.
 * @param {string} character
 *   Character (or substring) to look for.
 * @return {number}
 *   Number of times `character` occurred in `value`.
 */
export function ccount(value, character) {
  const source = String(value)

  if (typeof character !== 'string') {
    throw new TypeError('Expected character')
  }

  let count = 0
  let index = source.indexOf(character)

  while (index !== -1) {
    count++
    index = source.indexOf(character, index + character.length)
  }

  return count
}
{
  "name": "ccount",
  "version": "2.0.1",
  "description": "Count how often a character (or substring) is used in a string",
  "license": "MIT",
  "keywords": [
    "character",
    "count",
    "char"
  ],
  "repository": "wooorm/ccount",
  "bugs": "https://github.com/wooorm/ccount/issues",
  "funding": {
    "type": "github",
    "url": "https://github.com/sponsors/wooorm"
  },
  "author": "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)",
  "contributors": [
    "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)"
  ],
  "sideEffects": false,
  "type": "module",
  "main": "index.js",
  "types": "index.d.ts",
  "files": [
    "index.d.ts",
    "index.js"
  ],
  "devDependencies": {
    "@types/tape": "^4.0.0",
    "c8": "^7.0.0",
    "prettier": "^2.0.0",
    "remark-cli": "^10.0.0",
    "remark-preset-wooorm": "^9.0.0",
    "rimraf": "^3.0.0",
    "tape": "^5.0.0",
    "type-coverage": "^2.0.0",
    "typescript": "^4.0.0",
    "xo": "^0.46.0"
  },
  "scripts": {
    "prepublishOnly": "npm run build && npm run format",
    "build": "rimraf \"*.d.ts\" && tsc && type-coverage",
    "format": "remark . -qfo && prettier . -w --loglevel warn && xo --fix",
    "test-api": "node --conditions development test.js",
    "test-coverage": "c8 --check-coverage --branches 100 --functions 100 --lines 100 --statements 100 --reporter lcov npm run test-api",
    "test": "npm run build && npm run format && npm run test-coverage"
  },
  "prettier": {
    "tabWidth": 2,
    "useTabs": false,
    "singleQuote": true,
    "bracketSpacing": false,
    "semi": false,
    "trailingComma": "none"
  },
  "xo": {
    "prettier": true
  },
  "remarkConfig": {
    "plugins": [
      "preset-wooorm"
    ]
  },
  "typeCoverage": {
    "atLeast": 100,
    "detail": true,
    "strict": true,
    "ignoreCatch": true
  }
}
/**
 * Map of named character references from HTML 4.
 *
 * @type {Record<string, string>}
 */
export const characterEntitiesHtml4 = {
  nbsp: ' ',
  iexcl: '¡',
  cent: '¢',
  pound: '£',
  curren: '¤',
  yen: '¥',
  brvbar: '¦',
  sect: '§',
  uml: '¨',
  copy: '©',
  ordf: 'ª',
  laquo: '«',
  not: '¬',
  shy: '­',
  reg: '®',
  macr: '¯',
  deg: '°',
  plusmn: '±',
  sup2: '²',
  sup3: '³',
  acute: '´',
  micro: 'µ',
  para: '¶',
  middot: '·',
  cedil: '¸',
  sup1: '¹',
  ordm: 'º',
  raquo: '»',
  frac14: '¼',
  frac12: '½',
  frac34: '¾',
  iquest: '¿',
  Agrave: 'À',
  Aacute: 'Á',
  Acirc: 'Â',
  Atilde: 'Ã',
  Auml: 'Ä',
  Aring: 'Å',
  AElig: 'Æ',
  Ccedil: 'Ç',
  Egrave: 'È',
  Eacute: 'É',
  Ecirc: 'Ê',
  Euml: 'Ë',
  Igrave: 'Ì',
  Iacute: 'Í',
  Icirc: 'Î',
  Iuml: 'Ï',
  ETH: 'Ð',
  Ntilde: 'Ñ',
  Ograve: 'Ò',
  Oacute: 'Ó',
  Ocirc: 'Ô',
  Otilde: 'Õ',
  Ouml: 'Ö',
  times: '×',
  Oslash: 'Ø',
  Ugrave: 'Ù',
  Uacute: 'Ú',
  Ucirc: 'Û',
  Uuml: 'Ü',
  Yacute: 'Ý',
  THORN: 'Þ',
  szlig: 'ß',
  agrave: 'à',
  aacute: 'á',
  acirc: 'â',
  atilde: 'ã',
  auml: 'ä',
  aring: 'å',
  aelig: 'æ',
  ccedil: 'ç',
  egrave: 'è',
  eacute: 'é',
  ecirc: 'ê',
  euml: 'ë',
  igrave: 'ì',
  iacute: 'í',
  icirc: 'î',
  iuml: 'ï',
  eth: 'ð',
  ntilde: 'ñ',
  ograve: 'ò',
  oacute: 'ó',
  ocirc: 'ô',
  otilde: 'õ',
  ouml: 'ö',
  divide: '÷',
  oslash: 'ø',
  ugrave: 'ù',
  uacute: 'ú',
  ucirc: 'û',
  uuml: 'ü',
  yacute: 'ý',
  thorn: 'þ',
  yuml: 'ÿ',
  fnof: 'ƒ',
  Alpha: 'Α',
  Beta: 'Β',
  Gamma: 'Γ',
  Delta: 'Δ',
  Epsilon: 'Ε',
  Zeta: 'Ζ',
  Eta: 'Η',
  Theta: 'Θ',
  Iota: 'Ι',
  Kappa: 'Κ',
  Lambda: 'Λ',
  Mu: 'Μ',
  Nu: 'Ν',
  Xi: 'Ξ',
  Omicron: 'Ο',
  Pi: 'Π',
  Rho: 'Ρ',
  Sigma: 'Σ',
  Tau: 'Τ',
  Upsilon: 'Υ',
  Phi: 'Φ',
  Chi: 'Χ',
  Psi: 'Ψ',
  Omega: 'Ω',
  alpha: 'α',
  beta: 'β',
  gamma: 'γ',
  delta: 'δ',
  epsilon: 'ε',
  zeta: 'ζ',
  eta: 'η',
  theta: 'θ',
  iota: 'ι',
  kappa: 'κ',
  lambda: 'λ',
  mu: 'μ',
  nu: 'ν',
  xi: 'ξ',
  omicron: 'ο',
  pi: 'π',
  rho: 'ρ',
  sigmaf: 'ς',
  sigma: 'σ',
  tau: 'τ',
  upsilon: 'υ',
  phi: 'φ',
  chi: 'χ',
  psi: 'ψ',
  omega: 'ω',
  thetasym: 'ϑ',
  upsih: 'ϒ',
  piv: 'ϖ',
  bull: '•',
  hellip: '…',
  prime: '′',
  Prime: '″',
  oline: '‾',
  frasl: '⁄',
  weierp: '℘',
  image: 'ℑ',
  real: 'ℜ',
  trade: '™',
  alefsym: 'ℵ',
  larr: '←',
  uarr: '↑',
  rarr: '→',
  darr: '↓',
  harr: '↔',
  crarr: '↵',
  lArr: '⇐',
  uArr: '⇑',
  rArr: '⇒',
  dArr: '⇓',
  hArr: '⇔',
  forall: '∀',
  part: '∂',
  exist: '∃',
  empty: '∅',
  nabla: '∇',
  isin: '∈',
  notin: '∉',
  ni: '∋',
  prod: '∏',
  sum: '∑',
  minus: '−',
  lowast: '∗',
  radic: '√',
  prop: '∝',
  infin: '∞',
  ang: '∠',
  and: '∧',
  or: '∨',
  cap: '∩',
  cup: '∪',
  int: '∫',
  there4: '∴',
  sim: '∼',
  cong: '≅',
  asymp: '≈',
  ne: '≠',
  equiv: '≡',
  le: '≤',
  ge: '≥',
  sub: '⊂',
  sup: '⊃',
  nsub: '⊄',
  sube: '⊆',
  supe: '⊇',
  oplus: '⊕',
  otimes: '⊗',
  perp: '⊥',
  sdot: '⋅',
  lceil: '⌈',
  rceil: '⌉',
  lfloor: '⌊',
  rfloor: '⌋',
  lang: '〈',
  rang: '〉',
  loz: '◊',
  spades: '♠',
  clubs: '♣',
  hearts: '♥',
  diams: '♦',
  quot: '"',
  amp: '&',
  lt: '<',
  gt: '>',
  OElig: 'Œ',
  oelig: 'œ',
  Scaron: 'Š',
  scaron: 'š',
  Yuml: 'Ÿ',
  circ: 'ˆ',
  tilde: '˜',
  ensp: ' ',
  emsp: ' ',
  thinsp: ' ',
  zwnj: '‌',
  zwj: '‍',
  lrm: '‎',
  rlm: '‏',
  ndash: '–',
  mdash: '—',
  lsquo: '‘',
  rsquo: '’',
  sbquo: '‚',
  ldquo: '“',
  rdquo: '”',
  bdquo: '„',
  dagger: '†',
  Dagger: '‡',
  permil: '‰',
  lsaquo: '‹',
  rsaquo: '›',
  euro: '€'
}
{
  "name": "character-entities-html4",
  "version": "2.1.0",
  "description": "Map of named character references from HTML 4",
  "license": "MIT",
  "keywords": [
    "html",
    "html4",
    "entity",
    "entities",
    "character",
    "reference",
    "name",
    "replacement"
  ],
  "repository": "wooorm/character-entities-html4",
  "bugs": "https://github.com/wooorm/character-entities-html4/issues",
  "funding": {
    "type": "github",
    "url": "https://github.com/sponsors/wooorm"
  },
  "author": "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)",
  "contributors": [
    "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)"
  ],
  "sideEffects": false,
  "type": "module",
  "main": "index.js",
  "types": "index.d.ts",
  "files": [
    "index.d.ts",
    "index.js"
  ],
  "devDependencies": {
    "@types/tape": "^4.0.0",
    "bail": "^2.0.0",
    "c8": "^7.0.0",
    "concat-stream": "^2.0.0",
    "prettier": "^2.0.0",
    "remark-cli": "^10.0.0",
    "remark-preset-wooorm": "^9.0.0",
    "rimraf": "^3.0.0",
    "tape": "^5.0.0",
    "type-coverage": "^2.0.0",
    "typescript": "^4.0.0",
    "xo": "^0.46.0"
  },
  "scripts": {
    "prepublishOnly": "npm run build && npm run format",
    "generate": "node build",
    "build": "rimraf \"*.d.ts\" && tsc && type-coverage",
    "format": "remark . -qfo && prettier . -w --loglevel warn && xo --fix",
    "test-api": "node --conditions development test.js",
    "test-coverage": "c8 --check-coverage --branches 100 --functions 100 --lines 100 --statements 100 --reporter lcov npm run test-api",
    "test": "npm run generate && npm run build && npm run format && npm run test-coverage"
  },
  "prettier": {
    "tabWidth": 2,
    "useTabs": false,
    "singleQuote": true,
    "bracketSpacing": false,
    "semi": false,
    "trailingComma": "none"
  },
  "xo": {
    "prettier": true
  },
  "remarkConfig": {
    "plugins": [
      "preset-wooorm"
    ]
  },
  "typeCoverage": {
    "atLeast": 100,
    "detail": true,
    "strict": true,
    "ignoreCatch": true
  }
}
/**
 * List of legacy HTML named character references that don’t need a trailing semicolon.
 *
 * @type {Array<string>}
 */
export const characterEntitiesLegacy = [
  'AElig',
  'AMP',
  'Aacute',
  'Acirc',
  'Agrave',
  'Aring',
  'Atilde',
  'Auml',
  'COPY',
  'Ccedil',
  'ETH',
  'Eacute',
  'Ecirc',
  'Egrave',
  'Euml',
  'GT',
  'Iacute',
  'Icirc',
  'Igrave',
  'Iuml',
  'LT',
  'Ntilde',
  'Oacute',
  'Ocirc',
  'Ograve',
  'Oslash',
  'Otilde',
  'Ouml',
  'QUOT',
  'REG',
  'THORN',
  'Uacute',
  'Ucirc',
  'Ugrave',
  'Uuml',
  'Yacute',
  'aacute',
  'acirc',
  'acute',
  'aelig',
  'agrave',
  'amp',
  'aring',
  'atilde',
  'auml',
  'brvbar',
  'ccedil',
  'cedil',
  'cent',
  'copy',
  'curren',
  'deg',
  'divide',
  'eacute',
  'ecirc',
  'egrave',
  'eth',
  'euml',
  'frac12',
  'frac14',
  'frac34',
  'gt',
  'iacute',
  'icirc',
  'iexcl',
  'igrave',
  'iquest',
  'iuml',
  'laquo',
  'lt',
  'macr',
  'micro',
  'middot',
  'nbsp',
  'not',
  'ntilde',
  'oacute',
  'ocirc',
  'ograve',
  'ordf',
  'ordm',
  'oslash',
  'otilde',
  'ouml',
  'para',
  'plusmn',
  'pound',
  'quot',
  'raquo',
  'reg',
  'sect',
  'shy',
  'sup1',
  'sup2',
  'sup3',
  'szlig',
  'thorn',
  'times',
  'uacute',
  'ucirc',
  'ugrave',
  'uml',
  'uuml',
  'yacute',
  'yen',
  'yuml'
]
{
  "name": "character-entities-legacy",
  "version": "3.0.0",
  "description": "List of legacy HTML named character references that don’t need a trailing semicolon",
  "license": "MIT",
  "keywords": [
    "html",
    "entity",
    "entities",
    "character",
    "reference",
    "name"
  ],
  "repository": "wooorm/character-entities-legacy",
  "bugs": "https://github.com/wooorm/character-entities-legacy/issues",
  "funding": {
    "type": "github",
    "url": "https://github.com/sponsors/wooorm"
  },
  "author": "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)",
  "contributors": [
    "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)"
  ],
  "sideEffects": false,
  "type": "module",
  "main": "index.js",
  "types": "index.d.ts",
  "files": [
    "index.d.ts",
    "index.js"
  ],
  "devDependencies": {
    "@types/tape": "^4.0.0",
    "bail": "^2.0.0",
    "c8": "^7.0.0",
    "concat-stream": "^2.0.0",
    "prettier": "^2.0.0",
    "remark-cli": "^10.0.0",
    "remark-preset-wooorm": "^9.0.0",
    "rimraf": "^3.0.0",
    "tape": "^5.0.0",
    "type-coverage": "^2.0.0",
    "typescript": "^4.0.0",
    "xo": "^0.45.0"
  },
  "scripts": {
    "generate": "node build",
    "prepublishOnly": "npm run build && npm run format",
    "build": "rimraf \"*.d.ts\" && tsc && type-coverage",
    "format": "remark . -qfo && prettier . -w --loglevel warn && xo --fix",
    "test-api": "node --conditions development test.js",
    "test-coverage": "c8 --check-coverage --branches 100 --functions 100 --lines 100 --statements 100 --reporter lcov npm run test-api",
    "test": "npm run generate && npm run build && npm run format && npm run test-coverage"
  },
  "prettier": {
    "tabWidth": 2,
    "useTabs": false,
    "singleQuote": true,
    "bracketSpacing": false,
    "semi": false,
    "trailingComma": "none"
  },
  "xo": {
    "prettier": true
  },
  "remarkConfig": {
    "plugins": [
      "preset-wooorm"
    ]
  },
  "typeCoverage": {
    "atLeast": 100,
    "detail": true,
    "strict": true,
    "ignoreCatch": true
  }
}
/**
 * Map of named character references.
 *
 * @type {Record<string, string>}
 */
export const characterEntities = {
  AElig: 'Æ',
  AMP: '&',
  Aacute: 'Á',
  Abreve: 'Ă',
  Acirc: 'Â',
  Acy: 'А',
  Afr: '𝔄',
  Agrave: 'À',
  Alpha: 'Α',
  Amacr: 'Ā',
  And: '⩓',
  Aogon: 'Ą',
  Aopf: '𝔸',
  ApplyFunction: '⁡',
  Aring: 'Å',
  Ascr: '𝒜',
  Assign: '≔',
  Atilde: 'Ã',
  Auml: 'Ä',
  Backslash: '∖',
  Barv: '⫧',
  Barwed: '⌆',
  Bcy: 'Б',
  Because: '∵',
  Bernoullis: 'ℬ',
  Beta: 'Β',
  Bfr: '𝔅',
  Bopf: '𝔹',
  Breve: '˘',
  Bscr: 'ℬ',
  Bumpeq: '≎',
  CHcy: 'Ч',
  COPY: '©',
  Cacute: 'Ć',
  Cap: '⋒',
  CapitalDifferentialD: 'ⅅ',
  Cayleys: 'ℭ',
  Ccaron: 'Č',
  Ccedil: 'Ç',
  Ccirc: 'Ĉ',
  Cconint: '∰',
  Cdot: 'Ċ',
  Cedilla: '¸',
  CenterDot: '·',
  Cfr: 'ℭ',
  Chi: 'Χ',
  CircleDot: '⊙',
  CircleMinus: '⊖',
  CirclePlus: '⊕',
  CircleTimes: '⊗',
  ClockwiseContourIntegral: '∲',
  CloseCurlyDoubleQuote: '”',
  CloseCurlyQuote: '’',
  Colon: '∷',
  Colone: '⩴',
  Congruent: '≡',
  Conint: '∯',
  ContourIntegral: '∮',
  Copf: 'ℂ',
  Coproduct: '∐',
  CounterClockwiseContourIntegral: '∳',
  Cross: '⨯',
  Cscr: '𝒞',
  Cup: '⋓',
  CupCap: '≍',
  DD: 'ⅅ',
  DDotrahd: '⤑',
  DJcy: 'Ђ',
  DScy: 'Ѕ',
  DZcy: 'Џ',
  Dagger: '‡',
  Darr: '↡',
  Dashv: '⫤',
  Dcaron: 'Ď',
  Dcy: 'Д',
  Del: '∇',
  Delta: 'Δ',
  Dfr: '𝔇',
  DiacriticalAcute: '´',
  DiacriticalDot: '˙',
  DiacriticalDoubleAcute: '˝',
  DiacriticalGrave: '`',
  DiacriticalTilde: '˜',
  Diamond: '⋄',
  DifferentialD: 'ⅆ',
  Dopf: '𝔻',
  Dot: '¨',
  DotDot: '⃜',
  DotEqual: '≐',
  DoubleContourIntegral: '∯',
  DoubleDot: '¨',
  DoubleDownArrow: '⇓',
  DoubleLeftArrow: '⇐',
  DoubleLeftRightArrow: '⇔',
  DoubleLeftTee: '⫤',
  DoubleLongLeftArrow: '⟸',
  DoubleLongLeftRightArrow: '⟺',
  DoubleLongRightArrow: '⟹',
  DoubleRightArrow: '⇒',
  DoubleRightTee: '⊨',
  DoubleUpArrow: '⇑',
  DoubleUpDownArrow: '⇕',
  DoubleVerticalBar: '∥',
  DownArrow: '↓',
  DownArrowBar: '⤓',
  DownArrowUpArrow: '⇵',
  DownBreve: '̑',
  DownLeftRightVector: '⥐',
  DownLeftTeeVector: '⥞',
  DownLeftVector: '↽',
  DownLeftVectorBar: '⥖',
  DownRightTeeVector: '⥟',
  DownRightVector: '⇁',
  DownRightVectorBar: '⥗',
  DownTee: '⊤',
  DownTeeArrow: '↧',
  Downarrow: '⇓',
  Dscr: '𝒟',
  Dstrok: 'Đ',
  ENG: 'Ŋ',
  ETH: 'Ð',
  Eacute: 'É',
  Ecaron: 'Ě',
  Ecirc: 'Ê',
  Ecy: 'Э',
  Edot: 'Ė',
  Efr: '𝔈',
  Egrave: 'È',
  Element: '∈',
  Emacr: 'Ē',
  EmptySmallSquare: '◻',
  EmptyVerySmallSquare: '▫',
  Eogon: 'Ę',
  Eopf: '𝔼',
  Epsilon: 'Ε',
  Equal: '⩵',
  EqualTilde: '≂',
  Equilibrium: '⇌',
  Escr: 'ℰ',
  Esim: '⩳',
  Eta: 'Η',
  Euml: 'Ë',
  Exists: '∃',
  ExponentialE: 'ⅇ',
  Fcy: 'Ф',
  Ffr: '𝔉',
  FilledSmallSquare: '◼',
  FilledVerySmallSquare: '▪',
  Fopf: '𝔽',
  ForAll: '∀',
  Fouriertrf: 'ℱ',
  Fscr: 'ℱ',
  GJcy: 'Ѓ',
  GT: '>',
  Gamma: 'Γ',
  Gammad: 'Ϝ',
  Gbreve: 'Ğ',
  Gcedil: 'Ģ',
  Gcirc: 'Ĝ',
  Gcy: 'Г',
  Gdot: 'Ġ',
  Gfr: '𝔊',
  Gg: '⋙',
  Gopf: '𝔾',
  GreaterEqual: '≥',
  GreaterEqualLess: '⋛',
  GreaterFullEqual: '≧',
  GreaterGreater: '⪢',
  GreaterLess: '≷',
  GreaterSlantEqual: '⩾',
  GreaterTilde: '≳',
  Gscr: '𝒢',
  Gt: '≫',
  HARDcy: 'Ъ',
  Hacek: 'ˇ',
  Hat: '^',
  Hcirc: 'Ĥ',
  Hfr: 'ℌ',
  HilbertSpace: 'ℋ',
  Hopf: 'ℍ',
  HorizontalLine: '─',
  Hscr: 'ℋ',
  Hstrok: 'Ħ',
  HumpDownHump: '≎',
  HumpEqual: '≏',
  IEcy: 'Е',
  IJlig: 'Ĳ',
  IOcy: 'Ё',
  Iacute: 'Í',
  Icirc: 'Î',
  Icy: 'И',
  Idot: 'İ',
  Ifr: 'ℑ',
  Igrave: 'Ì',
  Im: 'ℑ',
  Imacr: 'Ī',
  ImaginaryI: 'ⅈ',
  Implies: '⇒',
  Int: '∬',
  Integral: '∫',
  Intersection: '⋂',
  InvisibleComma: '⁣',
  InvisibleTimes: '⁢',
  Iogon: 'Į',
  Iopf: '𝕀',
  Iota: 'Ι',
  Iscr: 'ℐ',
  Itilde: 'Ĩ',
  Iukcy: 'І',
  Iuml: 'Ï',
  Jcirc: 'Ĵ',
  Jcy: 'Й',
  Jfr: '𝔍',
  Jopf: '𝕁',
  Jscr: '𝒥',
  Jsercy: 'Ј',
  Jukcy: 'Є',
  KHcy: 'Х',
  KJcy: 'Ќ',
  Kappa: 'Κ',
  Kcedil: 'Ķ',
  Kcy: 'К',
  Kfr: '𝔎',
  Kopf: '𝕂',
  Kscr: '𝒦',
  LJcy: 'Љ',
  LT: '<',
  Lacute: 'Ĺ',
  Lambda: 'Λ',
  Lang: '⟪',
  Laplacetrf: 'ℒ',
  Larr: '↞',
  Lcaron: 'Ľ',
  Lcedil: 'Ļ',
  Lcy: 'Л',
  LeftAngleBracket: '⟨',
  LeftArrow: '←',
  LeftArrowBar: '⇤',
  LeftArrowRightArrow: '⇆',
  LeftCeiling: '⌈',
  LeftDoubleBracket: '⟦',
  LeftDownTeeVector: '⥡',
  LeftDownVector: '⇃',
  LeftDownVectorBar: '⥙',
  LeftFloor: '⌊',
  LeftRightArrow: '↔',
  LeftRightVector: '⥎',
  LeftTee: '⊣',
  LeftTeeArrow: '↤',
  LeftTeeVector: '⥚',
  LeftTriangle: '⊲',
  LeftTriangleBar: '⧏',
  LeftTriangleEqual: '⊴',
  LeftUpDownVector: '⥑',
  LeftUpTeeVector: '⥠',
  LeftUpVector: '↿',
  LeftUpVectorBar: '⥘',
  LeftVector: '↼',
  LeftVectorBar: '⥒',
  Leftarrow: '⇐',
  Leftrightarrow: '⇔',
  LessEqualGreater: '⋚',
  LessFullEqual: '≦',
  LessGreater: '≶',
  LessLess: '⪡',
  LessSlantEqual: '⩽',
  LessTilde: '≲',
  Lfr: '𝔏',
  Ll: '⋘',
  Lleftarrow: '⇚',
  Lmidot: 'Ŀ',
  LongLeftArrow: '⟵',
  LongLeftRightArrow: '⟷',
  LongRightArrow: '⟶',
  Longleftarrow: '⟸',
  Longleftrightarrow: '⟺',
  Longrightarrow: '⟹',
  Lopf: '𝕃',
  LowerLeftArrow: '↙',
  LowerRightArrow: '↘',
  Lscr: 'ℒ',
  Lsh: '↰',
  Lstrok: 'Ł',
  Lt: '≪',
  Map: '⤅',
  Mcy: 'М',
  MediumSpace: ' ',
  Mellintrf: 'ℳ',
  Mfr: '𝔐',
  MinusPlus: '∓',
  Mopf: '𝕄',
  Mscr: 'ℳ',
  Mu: 'Μ',
  NJcy: 'Њ',
  Nacute: 'Ń',
  Ncaron: 'Ň',
  Ncedil: 'Ņ',
  Ncy: 'Н',
  NegativeMediumSpace: '​',
  NegativeThickSpace: '​',
  NegativeThinSpace: '​',
  NegativeVeryThinSpace: '​',
  NestedGreaterGreater: '≫',
  NestedLessLess: '≪',
  NewLine: '\n',
  Nfr: '𝔑',
  NoBreak: '⁠',
  NonBreakingSpace: ' ',
  Nopf: 'ℕ',
  Not: '⫬',
  NotCongruent: '≢',
  NotCupCap: '≭',
  NotDoubleVerticalBar: '∦',
  NotElement: '∉',
  NotEqual: '≠',
  NotEqualTilde: '≂̸',
  NotExists: '∄',
  NotGreater: '≯',
  NotGreaterEqual: '≱',
  NotGreaterFullEqual: '≧̸',
  NotGreaterGreater: '≫̸',
  NotGreaterLess: '≹',
  NotGreaterSlantEqual: '⩾̸',
  NotGreaterTilde: '≵',
  NotHumpDownHump: '≎̸',
  NotHumpEqual: '≏̸',
  NotLeftTriangle: '⋪',
  NotLeftTriangleBar: '⧏̸',
  NotLeftTriangleEqual: '⋬',
  NotLess: '≮',
  NotLessEqual: '≰',
  NotLessGreater: '≸',
  NotLessLess: '≪̸',
  NotLessSlantEqual: '⩽̸',
  NotLessTilde: '≴',
  NotNestedGreaterGreater: '⪢̸',
  NotNestedLessLess: '⪡̸',
  NotPrecedes: '⊀',
  NotPrecedesEqual: '⪯̸',
  NotPrecedesSlantEqual: '⋠',
  NotReverseElement: '∌',
  NotRightTriangle: '⋫',
  NotRightTriangleBar: '⧐̸',
  NotRightTriangleEqual: '⋭',
  NotSquareSubset: '⊏̸',
  NotSquareSubsetEqual: '⋢',
  NotSquareSuperset: '⊐̸',
  NotSquareSupersetEqual: '⋣',
  NotSubset: '⊂⃒',
  NotSubsetEqual: '⊈',
  NotSucceeds: '⊁',
  NotSucceedsEqual: '⪰̸',
  NotSucceedsSlantEqual: '⋡',
  NotSucceedsTilde: '≿̸',
  NotSuperset: '⊃⃒',
  NotSupersetEqual: '⊉',
  NotTilde: '≁',
  NotTildeEqual: '≄',
  NotTildeFullEqual: '≇',
  NotTildeTilde: '≉',
  NotVerticalBar: '∤',
  Nscr: '𝒩',
  Ntilde: 'Ñ',
  Nu: 'Ν',
  OElig: 'Œ',
  Oacute: 'Ó',
  Ocirc: 'Ô',
  Ocy: 'О',
  Odblac: 'Ő',
  Ofr: '𝔒',
  Ograve: 'Ò',
  Omacr: 'Ō',
  Omega: 'Ω',
  Omicron: 'Ο',
  Oopf: '𝕆',
  OpenCurlyDoubleQuote: '“',
  OpenCurlyQuote: '‘',
  Or: '⩔',
  Oscr: '𝒪',
  Oslash: 'Ø',
  Otilde: 'Õ',
  Otimes: '⨷',
  Ouml: 'Ö',
  OverBar: '‾',
  OverBrace: '⏞',
  OverBracket: '⎴',
  OverParenthesis: '⏜',
  PartialD: '∂',
  Pcy: 'П',
  Pfr: '𝔓',
  Phi: 'Φ',
  Pi: 'Π',
  PlusMinus: '±',
  Poincareplane: 'ℌ',
  Popf: 'ℙ',
  Pr: '⪻',
  Precedes: '≺',
  PrecedesEqual: '⪯',
  PrecedesSlantEqual: '≼',
  PrecedesTilde: '≾',
  Prime: '″',
  Product: '∏',
  Proportion: '∷',
  Proportional: '∝',
  Pscr: '𝒫',
  Psi: 'Ψ',
  QUOT: '"',
  Qfr: '𝔔',
  Qopf: 'ℚ',
  Qscr: '𝒬',
  RBarr: '⤐',
  REG: '®',
  Racute: 'Ŕ',
  Rang: '⟫',
  Rarr: '↠',
  Rarrtl: '⤖',
  Rcaron: 'Ř',
  Rcedil: 'Ŗ',
  Rcy: 'Р',
  Re: 'ℜ',
  ReverseElement: '∋',
  ReverseEquilibrium: '⇋',
  ReverseUpEquilibrium: '⥯',
  Rfr: 'ℜ',
  Rho: 'Ρ',
  RightAngleBracket: '⟩',
  RightArrow: '→',
  RightArrowBar: '⇥',
  RightArrowLeftArrow: '⇄',
  RightCeiling: '⌉',
  RightDoubleBracket: '⟧',
  RightDownTeeVector: '⥝',
  RightDownVector: '⇂',
  RightDownVectorBar: '⥕',
  RightFloor: '⌋',
  RightTee: '⊢',
  RightTeeArrow: '↦',
  RightTeeVector: '⥛',
  RightTriangle: '⊳',
  RightTriangleBar: '⧐',
  RightTriangleEqual: '⊵',
  RightUpDownVector: '⥏',
  RightUpTeeVector: '⥜',
  RightUpVector: '↾',
  RightUpVectorBar: '⥔',
  RightVector: '⇀',
  RightVectorBar: '⥓',
  Rightarrow: '⇒',
  Ropf: 'ℝ',
  RoundImplies: '⥰',
  Rrightarrow: '⇛',
  Rscr: 'ℛ',
  Rsh: '↱',
  RuleDelayed: '⧴',
  SHCHcy: 'Щ',
  SHcy: 'Ш',
  SOFTcy: 'Ь',
  Sacute: 'Ś',
  Sc: '⪼',
  Scaron: 'Š',
  Scedil: 'Ş',
  Scirc: 'Ŝ',
  Scy: 'С',
  Sfr: '𝔖',
  ShortDownArrow: '↓',
  ShortLeftArrow: '←',
  ShortRightArrow: '→',
  ShortUpArrow: '↑',
  Sigma: 'Σ',
  SmallCircle: '∘',
  Sopf: '𝕊',
  Sqrt: '√',
  Square: '□',
  SquareIntersection: '⊓',
  SquareSubset: '⊏',
  SquareSubsetEqual: '⊑',
  SquareSuperset: '⊐',
  SquareSupersetEqual: '⊒',
  SquareUnion: '⊔',
  Sscr: '𝒮',
  Star: '⋆',
  Sub: '⋐',
  Subset: '⋐',
  SubsetEqual: '⊆',
  Succeeds: '≻',
  SucceedsEqual: '⪰',
  SucceedsSlantEqual: '≽',
  SucceedsTilde: '≿',
  SuchThat: '∋',
  Sum: '∑',
  Sup: '⋑',
  Superset: '⊃',
  SupersetEqual: '⊇',
  Supset: '⋑',
  THORN: 'Þ',
  TRADE: '™',
  TSHcy: 'Ћ',
  TScy: 'Ц',
  Tab: '\t',
  Tau: 'Τ',
  Tcaron: 'Ť',
  Tcedil: 'Ţ',
  Tcy: 'Т',
  Tfr: '𝔗',
  Therefore: '∴',
  Theta: 'Θ',
  ThickSpace: '  ',
  ThinSpace: ' ',
  Tilde: '∼',
  TildeEqual: '≃',
  TildeFullEqual: '≅',
  TildeTilde: '≈',
  Topf: '𝕋',
  TripleDot: '⃛',
  Tscr: '𝒯',
  Tstrok: 'Ŧ',
  Uacute: 'Ú',
  Uarr: '↟',
  Uarrocir: '⥉',
  Ubrcy: 'Ў',
  Ubreve: 'Ŭ',
  Ucirc: 'Û',
  Ucy: 'У',
  Udblac: 'Ű',
  Ufr: '𝔘',
  Ugrave: 'Ù',
  Umacr: 'Ū',
  UnderBar: '_',
  UnderBrace: '⏟',
  UnderBracket: '⎵',
  UnderParenthesis: '⏝',
  Union: '⋃',
  UnionPlus: '⊎',
  Uogon: 'Ų',
  Uopf: '𝕌',
  UpArrow: '↑',
  UpArrowBar: '⤒',
  UpArrowDownArrow: '⇅',
  UpDownArrow: '↕',
  UpEquilibrium: '⥮',
  UpTee: '⊥',
  UpTeeArrow: '↥',
  Uparrow: '⇑',
  Updownarrow: '⇕',
  UpperLeftArrow: '↖',
  UpperRightArrow: '↗',
  Upsi: 'ϒ',
  Upsilon: 'Υ',
  Uring: 'Ů',
  Uscr: '𝒰',
  Utilde: 'Ũ',
  Uuml: 'Ü',
  VDash: '⊫',
  Vbar: '⫫',
  Vcy: 'В',
  Vdash: '⊩',
  Vdashl: '⫦',
  Vee: '⋁',
  Verbar: '‖',
  Vert: '‖',
  VerticalBar: '∣',
  VerticalLine: '|',
  VerticalSeparator: '❘',
  VerticalTilde: '≀',
  VeryThinSpace: ' ',
  Vfr: '𝔙',
  Vopf: '𝕍',
  Vscr: '𝒱',
  Vvdash: '⊪',
  Wcirc: 'Ŵ',
  Wedge: '⋀',
  Wfr: '𝔚',
  Wopf: '𝕎',
  Wscr: '𝒲',
  Xfr: '𝔛',
  Xi: 'Ξ',
  Xopf: '𝕏',
  Xscr: '𝒳',
  YAcy: 'Я',
  YIcy: 'Ї',
  YUcy: 'Ю',
  Yacute: 'Ý',
  Ycirc: 'Ŷ',
  Ycy: 'Ы',
  Yfr: '𝔜',
  Yopf: '𝕐',
  Yscr: '𝒴',
  Yuml: 'Ÿ',
  ZHcy: 'Ж',
  Zacute: 'Ź',
  Zcaron: 'Ž',
  Zcy: 'З',
  Zdot: 'Ż',
  ZeroWidthSpace: '​',
  Zeta: 'Ζ',
  Zfr: 'ℨ',
  Zopf: 'ℤ',
  Zscr: '𝒵',
  aacute: 'á',
  abreve: 'ă',
  ac: '∾',
  acE: '∾̳',
  acd: '∿',
  acirc: 'â',
  acute: '´',
  acy: 'а',
  aelig: 'æ',
  af: '⁡',
  afr: '𝔞',
  agrave: 'à',
  alefsym: 'ℵ',
  aleph: 'ℵ',
  alpha: 'α',
  amacr: 'ā',
  amalg: '⨿',
  amp: '&',
  and: '∧',
  andand: '⩕',
  andd: '⩜',
  andslope: '⩘',
  andv: '⩚',
  ang: '∠',
  ange: '⦤',
  angle: '∠',
  angmsd: '∡',
  angmsdaa: '⦨',
  angmsdab: '⦩',
  angmsdac: '⦪',
  angmsdad: '⦫',
  angmsdae: '⦬',
  angmsdaf: '⦭',
  angmsdag: '⦮',
  angmsdah: '⦯',
  angrt: '∟',
  angrtvb: '⊾',
  angrtvbd: '⦝',
  angsph: '∢',
  angst: 'Å',
  angzarr: '⍼',
  aogon: 'ą',
  aopf: '𝕒',
  ap: '≈',
  apE: '⩰',
  apacir: '⩯',
  ape: '≊',
  apid: '≋',
  apos: "'",
  approx: '≈',
  approxeq: '≊',
  aring: 'å',
  ascr: '𝒶',
  ast: '*',
  asymp: '≈',
  asympeq: '≍',
  atilde: 'ã',
  auml: 'ä',
  awconint: '∳',
  awint: '⨑',
  bNot: '⫭',
  backcong: '≌',
  backepsilon: '϶',
  backprime: '‵',
  backsim: '∽',
  backsimeq: '⋍',
  barvee: '⊽',
  barwed: '⌅',
  barwedge: '⌅',
  bbrk: '⎵',
  bbrktbrk: '⎶',
  bcong: '≌',
  bcy: 'б',
  bdquo: '„',
  becaus: '∵',
  because: '∵',
  bemptyv: '⦰',
  bepsi: '϶',
  bernou: 'ℬ',
  beta: 'β',
  beth: 'ℶ',
  between: '≬',
  bfr: '𝔟',
  bigcap: '⋂',
  bigcirc: '◯',
  bigcup: '⋃',
  bigodot: '⨀',
  bigoplus: '⨁',
  bigotimes: '⨂',
  bigsqcup: '⨆',
  bigstar: '★',
  bigtriangledown: '▽',
  bigtriangleup: '△',
  biguplus: '⨄',
  bigvee: '⋁',
  bigwedge: '⋀',
  bkarow: '⤍',
  blacklozenge: '⧫',
  blacksquare: '▪',
  blacktriangle: '▴',
  blacktriangledown: '▾',
  blacktriangleleft: '◂',
  blacktriangleright: '▸',
  blank: '␣',
  blk12: '▒',
  blk14: '░',
  blk34: '▓',
  block: '█',
  bne: '=⃥',
  bnequiv: '≡⃥',
  bnot: '⌐',
  bopf: '𝕓',
  bot: '⊥',
  bottom: '⊥',
  bowtie: '⋈',
  boxDL: '╗',
  boxDR: '╔',
  boxDl: '╖',
  boxDr: '╓',
  boxH: '═',
  boxHD: '╦',
  boxHU: '╩',
  boxHd: '╤',
  boxHu: '╧',
  boxUL: '╝',
  boxUR: '╚',
  boxUl: '╜',
  boxUr: '╙',
  boxV: '║',
  boxVH: '╬',
  boxVL: '╣',
  boxVR: '╠',
  boxVh: '╫',
  boxVl: '╢',
  boxVr: '╟',
  boxbox: '⧉',
  boxdL: '╕',
  boxdR: '╒',
  boxdl: '┐',
  boxdr: '┌',
  boxh: '─',
  boxhD: '╥',
  boxhU: '╨',
  boxhd: '┬',
  boxhu: '┴',
  boxminus: '⊟',
  boxplus: '⊞',
  boxtimes: '⊠',
  boxuL: '╛',
  boxuR: '╘',
  boxul: '┘',
  boxur: '└',
  boxv: '│',
  boxvH: '╪',
  boxvL: '╡',
  boxvR: '╞',
  boxvh: '┼',
  boxvl: '┤',
  boxvr: '├',
  bprime: '‵',
  breve: '˘',
  brvbar: '¦',
  bscr: '𝒷',
  bsemi: '⁏',
  bsim: '∽',
  bsime: '⋍',
  bsol: '\\',
  bsolb: '⧅',
  bsolhsub: '⟈',
  bull: '•',
  bullet: '•',
  bump: '≎',
  bumpE: '⪮',
  bumpe: '≏',
  bumpeq: '≏',
  cacute: 'ć',
  cap: '∩',
  capand: '⩄',
  capbrcup: '⩉',
  capcap: '⩋',
  capcup: '⩇',
  capdot: '⩀',
  caps: '∩︀',
  caret: '⁁',
  caron: 'ˇ',
  ccaps: '⩍',
  ccaron: 'č',
  ccedil: 'ç',
  ccirc: 'ĉ',
  ccups: '⩌',
  ccupssm: '⩐',
  cdot: 'ċ',
  cedil: '¸',
  cemptyv: '⦲',
  cent: '¢',
  centerdot: '·',
  cfr: '𝔠',
  chcy: 'ч',
  check: '✓',
  checkmark: '✓',
  chi: 'χ',
  cir: '○',
  cirE: '⧃',
  circ: 'ˆ',
  circeq: '≗',
  circlearrowleft: '↺',
  circlearrowright: '↻',
  circledR: '®',
  circledS: 'Ⓢ',
  circledast: '⊛',
  circledcirc: '⊚',
  circleddash: '⊝',
  cire: '≗',
  cirfnint: '⨐',
  cirmid: '⫯',
  cirscir: '⧂',
  clubs: '♣',
  clubsuit: '♣',
  colon: ':',
  colone: '≔',
  coloneq: '≔',
  comma: ',',
  commat: '@',
  comp: '∁',
  compfn: '∘',
  complement: '∁',
  complexes: 'ℂ',
  cong: '≅',
  congdot: '⩭',
  conint: '∮',
  copf: '𝕔',
  coprod: '∐',
  copy: '©',
  copysr: '℗',
  crarr: '↵',
  cross: '✗',
  cscr: '𝒸',
  csub: '⫏',
  csube: '⫑',
  csup: '⫐',
  csupe: '⫒',
  ctdot: '⋯',
  cudarrl: '⤸',
  cudarrr: '⤵',
  cuepr: '⋞',
  cuesc: '⋟',
  cularr: '↶',
  cularrp: '⤽',
  cup: '∪',
  cupbrcap: '⩈',
  cupcap: '⩆',
  cupcup: '⩊',
  cupdot: '⊍',
  cupor: '⩅',
  cups: '∪︀',
  curarr: '↷',
  curarrm: '⤼',
  curlyeqprec: '⋞',
  curlyeqsucc: '⋟',
  curlyvee: '⋎',
  curlywedge: '⋏',
  curren: '¤',
  curvearrowleft: '↶',
  curvearrowright: '↷',
  cuvee: '⋎',
  cuwed: '⋏',
  cwconint: '∲',
  cwint: '∱',
  cylcty: '⌭',
  dArr: '⇓',
  dHar: '⥥',
  dagger: '†',
  daleth: 'ℸ',
  darr: '↓',
  dash: '‐',
  dashv: '⊣',
  dbkarow: '⤏',
  dblac: '˝',
  dcaron: 'ď',
  dcy: 'д',
  dd: 'ⅆ',
  ddagger: '‡',
  ddarr: '⇊',
  ddotseq: '⩷',
  deg: '°',
  delta: 'δ',
  demptyv: '⦱',
  dfisht: '⥿',
  dfr: '𝔡',
  dharl: '⇃',
  dharr: '⇂',
  diam: '⋄',
  diamond: '⋄',
  diamondsuit: '♦',
  diams: '♦',
  die: '¨',
  digamma: 'ϝ',
  disin: '⋲',
  div: '÷',
  divide: '÷',
  divideontimes: '⋇',
  divonx: '⋇',
  djcy: 'ђ',
  dlcorn: '⌞',
  dlcrop: '⌍',
  dollar: '$',
  dopf: '𝕕',
  dot: '˙',
  doteq: '≐',
  doteqdot: '≑',
  dotminus: '∸',
  dotplus: '∔',
  dotsquare: '⊡',
  doublebarwedge: '⌆',
  downarrow: '↓',
  downdownarrows: '⇊',
  downharpoonleft: '⇃',
  downharpoonright: '⇂',
  drbkarow: '⤐',
  drcorn: '⌟',
  drcrop: '⌌',
  dscr: '𝒹',
  dscy: 'ѕ',
  dsol: '⧶',
  dstrok: 'đ',
  dtdot: '⋱',
  dtri: '▿',
  dtrif: '▾',
  duarr: '⇵',
  duhar: '⥯',
  dwangle: '⦦',
  dzcy: 'џ',
  dzigrarr: '⟿',
  eDDot: '⩷',
  eDot: '≑',
  eacute: 'é',
  easter: '⩮',
  ecaron: 'ě',
  ecir: '≖',
  ecirc: 'ê',
  ecolon: '≕',
  ecy: 'э',
  edot: 'ė',
  ee: 'ⅇ',
  efDot: '≒',
  efr: '𝔢',
  eg: '⪚',
  egrave: 'è',
  egs: '⪖',
  egsdot: '⪘',
  el: '⪙',
  elinters: '⏧',
  ell: 'ℓ',
  els: '⪕',
  elsdot: '⪗',
  emacr: 'ē',
  empty: '∅',
  emptyset: '∅',
  emptyv: '∅',
  emsp13: ' ',
  emsp14: ' ',
  emsp: ' ',
  eng: 'ŋ',
  ensp: ' ',
  eogon: 'ę',
  eopf: '𝕖',
  epar: '⋕',
  eparsl: '⧣',
  eplus: '⩱',
  epsi: 'ε',
  epsilon: 'ε',
  epsiv: 'ϵ',
  eqcirc: '≖',
  eqcolon: '≕',
  eqsim: '≂',
  eqslantgtr: '⪖',
  eqslantless: '⪕',
  equals: '=',
  equest: '≟',
  equiv: '≡',
  equivDD: '⩸',
  eqvparsl: '⧥',
  erDot: '≓',
  erarr: '⥱',
  escr: 'ℯ',
  esdot: '≐',
  esim: '≂',
  eta: 'η',
  eth: 'ð',
  euml: 'ë',
  euro: '€',
  excl: '!',
  exist: '∃',
  expectation: 'ℰ',
  exponentiale: 'ⅇ',
  fallingdotseq: '≒',
  fcy: 'ф',
  female: '♀',
  ffilig: 'ﬃ',
  fflig: 'ﬀ',
  ffllig: 'ﬄ',
  ffr: '𝔣',
  filig: 'ﬁ',
  fjlig: 'fj',
  flat: '♭',
  fllig: 'ﬂ',
  fltns: '▱',
  fnof: 'ƒ',
  fopf: '𝕗',
  forall: '∀',
  fork: '⋔',
  forkv: '⫙',
  fpartint: '⨍',
  frac12: '½',
  frac13: '⅓',
  frac14: '¼',
  frac15: '⅕',
  frac16: '⅙',
  frac18: '⅛',
  frac23: '⅔',
  frac25: '⅖',
  frac34: '¾',
  frac35: '⅗',
  frac38: '⅜',
  frac45: '⅘',
  frac56: '⅚',
  frac58: '⅝',
  frac78: '⅞',
  frasl: '⁄',
  frown: '⌢',
  fscr: '𝒻',
  gE: '≧',
  gEl: '⪌',
  gacute: 'ǵ',
  gamma: 'γ',
  gammad: 'ϝ',
  gap: '⪆',
  gbreve: 'ğ',
  gcirc: 'ĝ',
  gcy: 'г',
  gdot: 'ġ',
  ge: '≥',
  gel: '⋛',
  geq: '≥',
  geqq: '≧',
  geqslant: '⩾',
  ges: '⩾',
  gescc: '⪩',
  gesdot: '⪀',
  gesdoto: '⪂',
  gesdotol: '⪄',
  gesl: '⋛︀',
  gesles: '⪔',
  gfr: '𝔤',
  gg: '≫',
  ggg: '⋙',
  gimel: 'ℷ',
  gjcy: 'ѓ',
  gl: '≷',
  glE: '⪒',
  gla: '⪥',
  glj: '⪤',
  gnE: '≩',
  gnap: '⪊',
  gnapprox: '⪊',
  gne: '⪈',
  gneq: '⪈',
  gneqq: '≩',
  gnsim: '⋧',
  gopf: '𝕘',
  grave: '`',
  gscr: 'ℊ',
  gsim: '≳',
  gsime: '⪎',
  gsiml: '⪐',
  gt: '>',
  gtcc: '⪧',
  gtcir: '⩺',
  gtdot: '⋗',
  gtlPar: '⦕',
  gtquest: '⩼',
  gtrapprox: '⪆',
  gtrarr: '⥸',
  gtrdot: '⋗',
  gtreqless: '⋛',
  gtreqqless: '⪌',
  gtrless: '≷',
  gtrsim: '≳',
  gvertneqq: '≩︀',
  gvnE: '≩︀',
  hArr: '⇔',
  hairsp: ' ',
  half: '½',
  hamilt: 'ℋ',
  hardcy: 'ъ',
  harr: '↔',
  harrcir: '⥈',
  harrw: '↭',
  hbar: 'ℏ',
  hcirc: 'ĥ',
  hearts: '♥',
  heartsuit: '♥',
  hellip: '…',
  hercon: '⊹',
  hfr: '𝔥',
  hksearow: '⤥',
  hkswarow: '⤦',
  hoarr: '⇿',
  homtht: '∻',
  hookleftarrow: '↩',
  hookrightarrow: '↪',
  hopf: '𝕙',
  horbar: '―',
  hscr: '𝒽',
  hslash: 'ℏ',
  hstrok: 'ħ',
  hybull: '⁃',
  hyphen: '‐',
  iacute: 'í',
  ic: '⁣',
  icirc: 'î',
  icy: 'и',
  iecy: 'е',
  iexcl: '¡',
  iff: '⇔',
  ifr: '𝔦',
  igrave: 'ì',
  ii: 'ⅈ',
  iiiint: '⨌',
  iiint: '∭',
  iinfin: '⧜',
  iiota: '℩',
  ijlig: 'ĳ',
  imacr: 'ī',
  image: 'ℑ',
  imagline: 'ℐ',
  imagpart: 'ℑ',
  imath: 'ı',
  imof: '⊷',
  imped: 'Ƶ',
  in: '∈',
  incare: '℅',
  infin: '∞',
  infintie: '⧝',
  inodot: 'ı',
  int: '∫',
  intcal: '⊺',
  integers: 'ℤ',
  intercal: '⊺',
  intlarhk: '⨗',
  intprod: '⨼',
  iocy: 'ё',
  iogon: 'į',
  iopf: '𝕚',
  iota: 'ι',
  iprod: '⨼',
  iquest: '¿',
  iscr: '𝒾',
  isin: '∈',
  isinE: '⋹',
  isindot: '⋵',
  isins: '⋴',
  isinsv: '⋳',
  isinv: '∈',
  it: '⁢',
  itilde: 'ĩ',
  iukcy: 'і',
  iuml: 'ï',
  jcirc: 'ĵ',
  jcy: 'й',
  jfr: '𝔧',
  jmath: 'ȷ',
  jopf: '𝕛',
  jscr: '𝒿',
  jsercy: 'ј',
  jukcy: 'є',
  kappa: 'κ',
  kappav: 'ϰ',
  kcedil: 'ķ',
  kcy: 'к',
  kfr: '𝔨',
  kgreen: 'ĸ',
  khcy: 'х',
  kjcy: 'ќ',
  kopf: '𝕜',
  kscr: '𝓀',
  lAarr: '⇚',
  lArr: '⇐',
  lAtail: '⤛',
  lBarr: '⤎',
  lE: '≦',
  lEg: '⪋',
  lHar: '⥢',
  lacute: 'ĺ',
  laemptyv: '⦴',
  lagran: 'ℒ',
  lambda: 'λ',
  lang: '⟨',
  langd: '⦑',
  langle: '⟨',
  lap: '⪅',
  laquo: '«',
  larr: '←',
  larrb: '⇤',
  larrbfs: '⤟',
  larrfs: '⤝',
  larrhk: '↩',
  larrlp: '↫',
  larrpl: '⤹',
  larrsim: '⥳',
  larrtl: '↢',
  lat: '⪫',
  latail: '⤙',
  late: '⪭',
  lates: '⪭︀',
  lbarr: '⤌',
  lbbrk: '❲',
  lbrace: '{',
  lbrack: '[',
  lbrke: '⦋',
  lbrksld: '⦏',
  lbrkslu: '⦍',
  lcaron: 'ľ',
  lcedil: 'ļ',
  lceil: '⌈',
  lcub: '{',
  lcy: 'л',
  ldca: '⤶',
  ldquo: '“',
  ldquor: '„',
  ldrdhar: '⥧',
  ldrushar: '⥋',
  ldsh: '↲',
  le: '≤',
  leftarrow: '←',
  leftarrowtail: '↢',
  leftharpoondown: '↽',
  leftharpoonup: '↼',
  leftleftarrows: '⇇',
  leftrightarrow: '↔',
  leftrightarrows: '⇆',
  leftrightharpoons: '⇋',
  leftrightsquigarrow: '↭',
  leftthreetimes: '⋋',
  leg: '⋚',
  leq: '≤',
  leqq: '≦',
  leqslant: '⩽',
  les: '⩽',
  lescc: '⪨',
  lesdot: '⩿',
  lesdoto: '⪁',
  lesdotor: '⪃',
  lesg: '⋚︀',
  lesges: '⪓',
  lessapprox: '⪅',
  lessdot: '⋖',
  lesseqgtr: '⋚',
  lesseqqgtr: '⪋',
  lessgtr: '≶',
  lesssim: '≲',
  lfisht: '⥼',
  lfloor: '⌊',
  lfr: '𝔩',
  lg: '≶',
  lgE: '⪑',
  lhard: '↽',
  lharu: '↼',
  lharul: '⥪',
  lhblk: '▄',
  ljcy: 'љ',
  ll: '≪',
  llarr: '⇇',
  llcorner: '⌞',
  llhard: '⥫',
  lltri: '◺',
  lmidot: 'ŀ',
  lmoust: '⎰',
  lmoustache: '⎰',
  lnE: '≨',
  lnap: '⪉',
  lnapprox: '⪉',
  lne: '⪇',
  lneq: '⪇',
  lneqq: '≨',
  lnsim: '⋦',
  loang: '⟬',
  loarr: '⇽',
  lobrk: '⟦',
  longleftarrow: '⟵',
  longleftrightarrow: '⟷',
  longmapsto: '⟼',
  longrightarrow: '⟶',
  looparrowleft: '↫',
  looparrowright: '↬',
  lopar: '⦅',
  lopf: '𝕝',
  loplus: '⨭',
  lotimes: '⨴',
  lowast: '∗',
  lowbar: '_',
  loz: '◊',
  lozenge: '◊',
  lozf: '⧫',
  lpar: '(',
  lparlt: '⦓',
  lrarr: '⇆',
  lrcorner: '⌟',
  lrhar: '⇋',
  lrhard: '⥭',
  lrm: '‎',
  lrtri: '⊿',
  lsaquo: '‹',
  lscr: '𝓁',
  lsh: '↰',
  lsim: '≲',
  lsime: '⪍',
  lsimg: '⪏',
  lsqb: '[',
  lsquo: '‘',
  lsquor: '‚',
  lstrok: 'ł',
  lt: '<',
  ltcc: '⪦',
  ltcir: '⩹',
  ltdot: '⋖',
  lthree: '⋋',
  ltimes: '⋉',
  ltlarr: '⥶',
  ltquest: '⩻',
  ltrPar: '⦖',
  ltri: '◃',
  ltrie: '⊴',
  ltrif: '◂',
  lurdshar: '⥊',
  luruhar: '⥦',
  lvertneqq: '≨︀',
  lvnE: '≨︀',
  mDDot: '∺',
  macr: '¯',
  male: '♂',
  malt: '✠',
  maltese: '✠',
  map: '↦',
  mapsto: '↦',
  mapstodown: '↧',
  mapstoleft: '↤',
  mapstoup: '↥',
  marker: '▮',
  mcomma: '⨩',
  mcy: 'м',
  mdash: '—',
  measuredangle: '∡',
  mfr: '𝔪',
  mho: '℧',
  micro: 'µ',
  mid: '∣',
  midast: '*',
  midcir: '⫰',
  middot: '·',
  minus: '−',
  minusb: '⊟',
  minusd: '∸',
  minusdu: '⨪',
  mlcp: '⫛',
  mldr: '…',
  mnplus: '∓',
  models: '⊧',
  mopf: '𝕞',
  mp: '∓',
  mscr: '𝓂',
  mstpos: '∾',
  mu: 'μ',
  multimap: '⊸',
  mumap: '⊸',
  nGg: '⋙̸',
  nGt: '≫⃒',
  nGtv: '≫̸',
  nLeftarrow: '⇍',
  nLeftrightarrow: '⇎',
  nLl: '⋘̸',
  nLt: '≪⃒',
  nLtv: '≪̸',
  nRightarrow: '⇏',
  nVDash: '⊯',
  nVdash: '⊮',
  nabla: '∇',
  nacute: 'ń',
  nang: '∠⃒',
  nap: '≉',
  napE: '⩰̸',
  napid: '≋̸',
  napos: 'ŉ',
  napprox: '≉',
  natur: '♮',
  natural: '♮',
  naturals: 'ℕ',
  nbsp: ' ',
  nbump: '≎̸',
  nbumpe: '≏̸',
  ncap: '⩃',
  ncaron: 'ň',
  ncedil: 'ņ',
  ncong: '≇',
  ncongdot: '⩭̸',
  ncup: '⩂',
  ncy: 'н',
  ndash: '–',
  ne: '≠',
  neArr: '⇗',
  nearhk: '⤤',
  nearr: '↗',
  nearrow: '↗',
  nedot: '≐̸',
  nequiv: '≢',
  nesear: '⤨',
  nesim: '≂̸',
  nexist: '∄',
  nexists: '∄',
  nfr: '𝔫',
  ngE: '≧̸',
  nge: '≱',
  ngeq: '≱',
  ngeqq: '≧̸',
  ngeqslant: '⩾̸',
  nges: '⩾̸',
  ngsim: '≵',
  ngt: '≯',
  ngtr: '≯',
  nhArr: '⇎',
  nharr: '↮',
  nhpar: '⫲',
  ni: '∋',
  nis: '⋼',
  nisd: '⋺',
  niv: '∋',
  njcy: 'њ',
  nlArr: '⇍',
  nlE: '≦̸',
  nlarr: '↚',
  nldr: '‥',
  nle: '≰',
  nleftarrow: '↚',
  nleftrightarrow: '↮',
  nleq: '≰',
  nleqq: '≦̸',
  nleqslant: '⩽̸',
  nles: '⩽̸',
  nless: '≮',
  nlsim: '≴',
  nlt: '≮',
  nltri: '⋪',
  nltrie: '⋬',
  nmid: '∤',
  nopf: '𝕟',
  not: '¬',
  notin: '∉',
  notinE: '⋹̸',
  notindot: '⋵̸',
  notinva: '∉',
  notinvb: '⋷',
  notinvc: '⋶',
  notni: '∌',
  notniva: '∌',
  notnivb: '⋾',
  notnivc: '⋽',
  npar: '∦',
  nparallel: '∦',
  nparsl: '⫽⃥',
  npart: '∂̸',
  npolint: '⨔',
  npr: '⊀',
  nprcue: '⋠',
  npre: '⪯̸',
  nprec: '⊀',
  npreceq: '⪯̸',
  nrArr: '⇏',
  nrarr: '↛',
  nrarrc: '⤳̸',
  nrarrw: '↝̸',
  nrightarrow: '↛',
  nrtri: '⋫',
  nrtrie: '⋭',
  nsc: '⊁',
  nsccue: '⋡',
  nsce: '⪰̸',
  nscr: '𝓃',
  nshortmid: '∤',
  nshortparallel: '∦',
  nsim: '≁',
  nsime: '≄',
  nsimeq: '≄',
  nsmid: '∤',
  nspar: '∦',
  nsqsube: '⋢',
  nsqsupe: '⋣',
  nsub: '⊄',
  nsubE: '⫅̸',
  nsube: '⊈',
  nsubset: '⊂⃒',
  nsubseteq: '⊈',
  nsubseteqq: '⫅̸',
  nsucc: '⊁',
  nsucceq: '⪰̸',
  nsup: '⊅',
  nsupE: '⫆̸',
  nsupe: '⊉',
  nsupset: '⊃⃒',
  nsupseteq: '⊉',
  nsupseteqq: '⫆̸',
  ntgl: '≹',
  ntilde: 'ñ',
  ntlg: '≸',
  ntriangleleft: '⋪',
  ntrianglelefteq: '⋬',
  ntriangleright: '⋫',
  ntrianglerighteq: '⋭',
  nu: 'ν',
  num: '#',
  numero: '№',
  numsp: ' ',
  nvDash: '⊭',
  nvHarr: '⤄',
  nvap: '≍⃒',
  nvdash: '⊬',
  nvge: '≥⃒',
  nvgt: '>⃒',
  nvinfin: '⧞',
  nvlArr: '⤂',
  nvle: '≤⃒',
  nvlt: '<⃒',
  nvltrie: '⊴⃒',
  nvrArr: '⤃',
  nvrtrie: '⊵⃒',
  nvsim: '∼⃒',
  nwArr: '⇖',
  nwarhk: '⤣',
  nwarr: '↖',
  nwarrow: '↖',
  nwnear: '⤧',
  oS: 'Ⓢ',
  oacute: 'ó',
  oast: '⊛',
  ocir: '⊚',
  ocirc: 'ô',
  ocy: 'о',
  odash: '⊝',
  odblac: 'ő',
  odiv: '⨸',
  odot: '⊙',
  odsold: '⦼',
  oelig: 'œ',
  ofcir: '⦿',
  ofr: '𝔬',
  ogon: '˛',
  ograve: 'ò',
  ogt: '⧁',
  ohbar: '⦵',
  ohm: 'Ω',
  oint: '∮',
  olarr: '↺',
  olcir: '⦾',
  olcross: '⦻',
  oline: '‾',
  olt: '⧀',
  omacr: 'ō',
  omega: 'ω',
  omicron: 'ο',
  omid: '⦶',
  ominus: '⊖',
  oopf: '𝕠',
  opar: '⦷',
  operp: '⦹',
  oplus: '⊕',
  or: '∨',
  orarr: '↻',
  ord: '⩝',
  order: 'ℴ',
  orderof: 'ℴ',
  ordf: 'ª',
  ordm: 'º',
  origof: '⊶',
  oror: '⩖',
  orslope: '⩗',
  orv: '⩛',
  oscr: 'ℴ',
  oslash: 'ø',
  osol: '⊘',
  otilde: 'õ',
  otimes: '⊗',
  otimesas: '⨶',
  ouml: 'ö',
  ovbar: '⌽',
  par: '∥',
  para: '¶',
  parallel: '∥',
  parsim: '⫳',
  parsl: '⫽',
  part: '∂',
  pcy: 'п',
  percnt: '%',
  period: '.',
  permil: '‰',
  perp: '⊥',
  pertenk: '‱',
  pfr: '𝔭',
  phi: 'φ',
  phiv: 'ϕ',
  phmmat: 'ℳ',
  phone: '☎',
  pi: 'π',
  pitchfork: '⋔',
  piv: 'ϖ',
  planck: 'ℏ',
  planckh: 'ℎ',
  plankv: 'ℏ',
  plus: '+',
  plusacir: '⨣',
  plusb: '⊞',
  pluscir: '⨢',
  plusdo: '∔',
  plusdu: '⨥',
  pluse: '⩲',
  plusmn: '±',
  plussim: '⨦',
  plustwo: '⨧',
  pm: '±',
  pointint: '⨕',
  popf: '𝕡',
  pound: '£',
  pr: '≺',
  prE: '⪳',
  prap: '⪷',
  prcue: '≼',
  pre: '⪯',
  prec: '≺',
  precapprox: '⪷',
  preccurlyeq: '≼',
  preceq: '⪯',
  precnapprox: '⪹',
  precneqq: '⪵',
  precnsim: '⋨',
  precsim: '≾',
  prime: '′',
  primes: 'ℙ',
  prnE: '⪵',
  prnap: '⪹',
  prnsim: '⋨',
  prod: '∏',
  profalar: '⌮',
  profline: '⌒',
  profsurf: '⌓',
  prop: '∝',
  propto: '∝',
  prsim: '≾',
  prurel: '⊰',
  pscr: '𝓅',
  psi: 'ψ',
  puncsp: ' ',
  qfr: '𝔮',
  qint: '⨌',
  qopf: '𝕢',
  qprime: '⁗',
  qscr: '𝓆',
  quaternions: 'ℍ',
  quatint: '⨖',
  quest: '?',
  questeq: '≟',
  quot: '"',
  rAarr: '⇛',
  rArr: '⇒',
  rAtail: '⤜',
  rBarr: '⤏',
  rHar: '⥤',
  race: '∽̱',
  racute: 'ŕ',
  radic: '√',
  raemptyv: '⦳',
  rang: '⟩',
  rangd: '⦒',
  range: '⦥',
  rangle: '⟩',
  raquo: '»',
  rarr: '→',
  rarrap: '⥵',
  rarrb: '⇥',
  rarrbfs: '⤠',
  rarrc: '⤳',
  rarrfs: '⤞',
  rarrhk: '↪',
  rarrlp: '↬',
  rarrpl: '⥅',
  rarrsim: '⥴',
  rarrtl: '↣',
  rarrw: '↝',
  ratail: '⤚',
  ratio: '∶',
  rationals: 'ℚ',
  rbarr: '⤍',
  rbbrk: '❳',
  rbrace: '}',
  rbrack: ']',
  rbrke: '⦌',
  rbrksld: '⦎',
  rbrkslu: '⦐',
  rcaron: 'ř',
  rcedil: 'ŗ',
  rceil: '⌉',
  rcub: '}',
  rcy: 'р',
  rdca: '⤷',
  rdldhar: '⥩',
  rdquo: '”',
  rdquor: '”',
  rdsh: '↳',
  real: 'ℜ',
  realine: 'ℛ',
  realpart: 'ℜ',
  reals: 'ℝ',
  rect: '▭',
  reg: '®',
  rfisht: '⥽',
  rfloor: '⌋',
  rfr: '𝔯',
  rhard: '⇁',
  rharu: '⇀',
  rharul: '⥬',
  rho: 'ρ',
  rhov: 'ϱ',
  rightarrow: '→',
  rightarrowtail: '↣',
  rightharpoondown: '⇁',
  rightharpoonup: '⇀',
  rightleftarrows: '⇄',
  rightleftharpoons: '⇌',
  rightrightarrows: '⇉',
  rightsquigarrow: '↝',
  rightthreetimes: '⋌',
  ring: '˚',
  risingdotseq: '≓',
  rlarr: '⇄',
  rlhar: '⇌',
  rlm: '‏',
  rmoust: '⎱',
  rmoustache: '⎱',
  rnmid: '⫮',
  roang: '⟭',
  roarr: '⇾',
  robrk: '⟧',
  ropar: '⦆',
  ropf: '𝕣',
  roplus: '⨮',
  rotimes: '⨵',
  rpar: ')',
  rpargt: '⦔',
  rppolint: '⨒',
  rrarr: '⇉',
  rsaquo: '›',
  rscr: '𝓇',
  rsh: '↱',
  rsqb: ']',
  rsquo: '’',
  rsquor: '’',
  rthree: '⋌',
  rtimes: '⋊',
  rtri: '▹',
  rtrie: '⊵',
  rtrif: '▸',
  rtriltri: '⧎',
  ruluhar: '⥨',
  rx: '℞',
  sacute: 'ś',
  sbquo: '‚',
  sc: '≻',
  scE: '⪴',
  scap: '⪸',
  scaron: 'š',
  sccue: '≽',
  sce: '⪰',
  scedil: 'ş',
  scirc: 'ŝ',
  scnE: '⪶',
  scnap: '⪺',
  scnsim: '⋩',
  scpolint: '⨓',
  scsim: '≿',
  scy: 'с',
  sdot: '⋅',
  sdotb: '⊡',
  sdote: '⩦',
  seArr: '⇘',
  searhk: '⤥',
  searr: '↘',
  searrow: '↘',
  sect: '§',
  semi: ';',
  seswar: '⤩',
  setminus: '∖',
  setmn: '∖',
  sext: '✶',
  sfr: '𝔰',
  sfrown: '⌢',
  sharp: '♯',
  shchcy: 'щ',
  shcy: 'ш',
  shortmid: '∣',
  shortparallel: '∥',
  shy: '­',
  sigma: 'σ',
  sigmaf: 'ς',
  sigmav: 'ς',
  sim: '∼',
  simdot: '⩪',
  sime: '≃',
  simeq: '≃',
  simg: '⪞',
  simgE: '⪠',
  siml: '⪝',
  simlE: '⪟',
  simne: '≆',
  simplus: '⨤',
  simrarr: '⥲',
  slarr: '←',
  smallsetminus: '∖',
  smashp: '⨳',
  smeparsl: '⧤',
  smid: '∣',
  smile: '⌣',
  smt: '⪪',
  smte: '⪬',
  smtes: '⪬︀',
  softcy: 'ь',
  sol: '/',
  solb: '⧄',
  solbar: '⌿',
  sopf: '𝕤',
  spades: '♠',
  spadesuit: '♠',
  spar: '∥',
  sqcap: '⊓',
  sqcaps: '⊓︀',
  sqcup: '⊔',
  sqcups: '⊔︀',
  sqsub: '⊏',
  sqsube: '⊑',
  sqsubset: '⊏',
  sqsubseteq: '⊑',
  sqsup: '⊐',
  sqsupe: '⊒',
  sqsupset: '⊐',
  sqsupseteq: '⊒',
  squ: '□',
  square: '□',
  squarf: '▪',
  squf: '▪',
  srarr: '→',
  sscr: '𝓈',
  ssetmn: '∖',
  ssmile: '⌣',
  sstarf: '⋆',
  star: '☆',
  starf: '★',
  straightepsilon: 'ϵ',
  straightphi: 'ϕ',
  strns: '¯',
  sub: '⊂',
  subE: '⫅',
  subdot: '⪽',
  sube: '⊆',
  subedot: '⫃',
  submult: '⫁',
  subnE: '⫋',
  subne: '⊊',
  subplus: '⪿',
  subrarr: '⥹',
  subset: '⊂',
  subseteq: '⊆',
  subseteqq: '⫅',
  subsetneq: '⊊',
  subsetneqq: '⫋',
  subsim: '⫇',
  subsub: '⫕',
  subsup: '⫓',
  succ: '≻',
  succapprox: '⪸',
  succcurlyeq: '≽',
  succeq: '⪰',
  succnapprox: '⪺',
  succneqq: '⪶',
  succnsim: '⋩',
  succsim: '≿',
  sum: '∑',
  sung: '♪',
  sup1: '¹',
  sup2: '²',
  sup3: '³',
  sup: '⊃',
  supE: '⫆',
  supdot: '⪾',
  supdsub: '⫘',
  supe: '⊇',
  supedot: '⫄',
  suphsol: '⟉',
  suphsub: '⫗',
  suplarr: '⥻',
  supmult: '⫂',
  supnE: '⫌',
  supne: '⊋',
  supplus: '⫀',
  supset: '⊃',
  supseteq: '⊇',
  supseteqq: '⫆',
  supsetneq: '⊋',
  supsetneqq: '⫌',
  supsim: '⫈',
  supsub: '⫔',
  supsup: '⫖',
  swArr: '⇙',
  swarhk: '⤦',
  swarr: '↙',
  swarrow: '↙',
  swnwar: '⤪',
  szlig: 'ß',
  target: '⌖',
  tau: 'τ',
  tbrk: '⎴',
  tcaron: 'ť',
  tcedil: 'ţ',
  tcy: 'т',
  tdot: '⃛',
  telrec: '⌕',
  tfr: '𝔱',
  there4: '∴',
  therefore: '∴',
  theta: 'θ',
  thetasym: 'ϑ',
  thetav: 'ϑ',
  thickapprox: '≈',
  thicksim: '∼',
  thinsp: ' ',
  thkap: '≈',
  thksim: '∼',
  thorn: 'þ',
  tilde: '˜',
  times: '×',
  timesb: '⊠',
  timesbar: '⨱',
  timesd: '⨰',
  tint: '∭',
  toea: '⤨',
  top: '⊤',
  topbot: '⌶',
  topcir: '⫱',
  topf: '𝕥',
  topfork: '⫚',
  tosa: '⤩',
  tprime: '‴',
  trade: '™',
  triangle: '▵',
  triangledown: '▿',
  triangleleft: '◃',
  trianglelefteq: '⊴',
  triangleq: '≜',
  triangleright: '▹',
  trianglerighteq: '⊵',
  tridot: '◬',
  trie: '≜',
  triminus: '⨺',
  triplus: '⨹',
  trisb: '⧍',
  tritime: '⨻',
  trpezium: '⏢',
  tscr: '𝓉',
  tscy: 'ц',
  tshcy: 'ћ',
  tstrok: 'ŧ',
  twixt: '≬',
  twoheadleftarrow: '↞',
  twoheadrightarrow: '↠',
  uArr: '⇑',
  uHar: '⥣',
  uacute: 'ú',
  uarr: '↑',
  ubrcy: 'ў',
  ubreve: 'ŭ',
  ucirc: 'û',
  ucy: 'у',
  udarr: '⇅',
  udblac: 'ű',
  udhar: '⥮',
  ufisht: '⥾',
  ufr: '𝔲',
  ugrave: 'ù',
  uharl: '↿',
  uharr: '↾',
  uhblk: '▀',
  ulcorn: '⌜',
  ulcorner: '⌜',
  ulcrop: '⌏',
  ultri: '◸',
  umacr: 'ū',
  uml: '¨',
  uogon: 'ų',
  uopf: '𝕦',
  uparrow: '↑',
  updownarrow: '↕',
  upharpoonleft: '↿',
  upharpoonright: '↾',
  uplus: '⊎',
  upsi: 'υ',
  upsih: 'ϒ',
  upsilon: 'υ',
  upuparrows: '⇈',
  urcorn: '⌝',
  urcorner: '⌝',
  urcrop: '⌎',
  uring: 'ů',
  urtri: '◹',
  uscr: '𝓊',
  utdot: '⋰',
  utilde: 'ũ',
  utri: '▵',
  utrif: '▴',
  uuarr: '⇈',
  uuml: 'ü',
  uwangle: '⦧',
  vArr: '⇕',
  vBar: '⫨',
  vBarv: '⫩',
  vDash: '⊨',
  vangrt: '⦜',
  varepsilon: 'ϵ',
  varkappa: 'ϰ',
  varnothing: '∅',
  varphi: 'ϕ',
  varpi: 'ϖ',
  varpropto: '∝',
  varr: '↕',
  varrho: 'ϱ',
  varsigma: 'ς',
  varsubsetneq: '⊊︀',
  varsubsetneqq: '⫋︀',
  varsupsetneq: '⊋︀',
  varsupsetneqq: '⫌︀',
  vartheta: 'ϑ',
  vartriangleleft: '⊲',
  vartriangleright: '⊳',
  vcy: 'в',
  vdash: '⊢',
  vee: '∨',
  veebar: '⊻',
  veeeq: '≚',
  vellip: '⋮',
  verbar: '|',
  vert: '|',
  vfr: '𝔳',
  vltri: '⊲',
  vnsub: '⊂⃒',
  vnsup: '⊃⃒',
  vopf: '𝕧',
  vprop: '∝',
  vrtri: '⊳',
  vscr: '𝓋',
  vsubnE: '⫋︀',
  vsubne: '⊊︀',
  vsupnE: '⫌︀',
  vsupne: '⊋︀',
  vzigzag: '⦚',
  wcirc: 'ŵ',
  wedbar: '⩟',
  wedge: '∧',
  wedgeq: '≙',
  weierp: '℘',
  wfr: '𝔴',
  wopf: '𝕨',
  wp: '℘',
  wr: '≀',
  wreath: '≀',
  wscr: '𝓌',
  xcap: '⋂',
  xcirc: '◯',
  xcup: '⋃',
  xdtri: '▽',
  xfr: '𝔵',
  xhArr: '⟺',
  xharr: '⟷',
  xi: 'ξ',
  xlArr: '⟸',
  xlarr: '⟵',
  xmap: '⟼',
  xnis: '⋻',
  xodot: '⨀',
  xopf: '𝕩',
  xoplus: '⨁',
  xotime: '⨂',
  xrArr: '⟹',
  xrarr: '⟶',
  xscr: '𝓍',
  xsqcup: '⨆',
  xuplus: '⨄',
  xutri: '△',
  xvee: '⋁',
  xwedge: '⋀',
  yacute: 'ý',
  yacy: 'я',
  ycirc: 'ŷ',
  ycy: 'ы',
  yen: '¥',
  yfr: '𝔶',
  yicy: 'ї',
  yopf: '𝕪',
  yscr: '𝓎',
  yucy: 'ю',
  yuml: 'ÿ',
  zacute: 'ź',
  zcaron: 'ž',
  zcy: 'з',
  zdot: 'ż',
  zeetrf: 'ℨ',
  zeta: 'ζ',
  zfr: '𝔷',
  zhcy: 'ж',
  zigrarr: '⇝',
  zopf: '𝕫',
  zscr: '𝓏',
  zwj: '‍',
  zwnj: '‌'
}
{
  "name": "character-entities",
  "version": "2.0.2",
  "description": "Map of named character references",
  "license": "MIT",
  "keywords": [
    "html",
    "entity",
    "entities",
    "character",
    "reference",
    "name",
    "replacement"
  ],
  "repository": "wooorm/character-entities",
  "bugs": "https://github.com/wooorm/character-entities/issues",
  "funding": {
    "type": "github",
    "url": "https://github.com/sponsors/wooorm"
  },
  "author": "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)",
  "contributors": [
    "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)"
  ],
  "sideEffects": false,
  "type": "module",
  "main": "index.js",
  "types": "index.d.ts",
  "files": [
    "index.d.ts",
    "index.js"
  ],
  "devDependencies": {
    "@types/tape": "^4.0.0",
    "bail": "^2.0.0",
    "c8": "^7.0.0",
    "concat-stream": "^2.0.0",
    "prettier": "^2.0.0",
    "remark-cli": "^10.0.0",
    "remark-preset-wooorm": "^9.0.0",
    "rimraf": "^3.0.0",
    "tape": "^5.0.0",
    "type-coverage": "^2.0.0",
    "typescript": "^4.0.0",
    "xo": "^0.50.0"
  },
  "scripts": {
    "generate": "node build",
    "prepublishOnly": "npm run build && npm run format",
    "build": "rimraf \"*.d.ts\" && tsc && type-coverage",
    "format": "remark . -qfo && prettier . -w --loglevel warn && xo --fix",
    "test-api": "node --conditions development test.js",
    "test-coverage": "c8 --check-coverage --branches 100 --functions 100 --lines 100 --statements 100 --reporter lcov npm run test-api",
    "test": "npm run generate && npm run build && npm run format && npm run test-coverage"
  },
  "prettier": {
    "tabWidth": 2,
    "useTabs": false,
    "singleQuote": true,
    "bracketSpacing": false,
    "semi": false,
    "trailingComma": "none"
  },
  "xo": {
    "prettier": true
  },
  "remarkConfig": {
    "plugins": [
      "preset-wooorm"
    ]
  },
  "typeCoverage": {
    "atLeast": 100,
    "detail": true,
    "strict": true,
    "ignoreCatch": true
  }
}
/**
 * Map of invalid numeric character references to their replacements, according to HTML.
 *
 * @type {Record<number, string>}
 */
export const characterReferenceInvalid = {
  0: '�',
  128: '€',
  130: '‚',
  131: 'ƒ',
  132: '„',
  133: '…',
  134: '†',
  135: '‡',
  136: 'ˆ',
  137: '‰',
  138: 'Š',
  139: '‹',
  140: 'Œ',
  142: 'Ž',
  145: '‘',
  146: '’',
  147: '“',
  148: '”',
  149: '•',
  150: '–',
  151: '—',
  152: '˜',
  153: '™',
  154: 'š',
  155: '›',
  156: 'œ',
  158: 'ž',
  159: 'Ÿ'
}
{
  "name": "character-reference-invalid",
  "version": "2.0.1",
  "description": "Map of invalid numeric character references to their replacements, according to HTML",
  "license": "MIT",
  "keywords": [
    "html",
    "entity",
    "numeric",
    "character",
    "reference",
    "replacement",
    "invalid",
    "name"
  ],
  "repository": "wooorm/character-reference-invalid",
  "bugs": "https://github.com/wooorm/character-reference-invalid/issues",
  "funding": {
    "type": "github",
    "url": "https://github.com/sponsors/wooorm"
  },
  "author": "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)",
  "contributors": [
    "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)"
  ],
  "sideEffects": false,
  "type": "module",
  "main": "index.js",
  "types": "index.d.ts",
  "files": [
    "index.d.ts",
    "index.js"
  ],
  "devDependencies": {
    "@types/tape": "^4.0.0",
    "bail": "^2.0.0",
    "c8": "^7.0.0",
    "concat-stream": "^2.0.0",
    "hast-util-select": "^5.0.0",
    "hast-util-to-string": "^2.0.0",
    "prettier": "^2.0.0",
    "rehype-parse": "^8.0.0",
    "remark-cli": "^10.0.0",
    "remark-preset-wooorm": "^9.0.0",
    "rimraf": "^3.0.0",
    "tape": "^5.0.0",
    "type-coverage": "^2.0.0",
    "typescript": "^4.0.0",
    "unified": "^10.0.0",
    "xo": "^0.45.0"
  },
  "scripts": {
    "prepublishOnly": "npm run build && npm run format",
    "generate": "node build",
    "build": "rimraf \"*.d.ts\" && tsc && type-coverage",
    "format": "remark . -qfo && prettier . -w --loglevel warn && xo --fix",
    "test-api": "node --conditions development test.js",
    "test-coverage": "c8 --check-coverage --branches 100 --functions 100 --lines 100 --statements 100 --reporter lcov npm run test-api",
    "test": "npm run generate && npm run build && npm run format && npm run test-coverage"
  },
  "prettier": {
    "tabWidth": 2,
    "useTabs": false,
    "singleQuote": true,
    "bracketSpacing": false,
    "semi": false,
    "trailingComma": "none"
  },
  "xo": {
    "prettier": true
  },
  "remarkConfig": {
    "plugins": [
      "preset-wooorm"
    ]
  },
  "typeCoverage": {
    "atLeast": 100,
    "detail": true,
    "strict": true,
    "ignoreCatch": true
  }
}
import {characterEntities} from 'character-entities'

// To do: next major: use `Object.hasOwn`.
const own = {}.hasOwnProperty

/**
 * Decode a single character reference (without the `&` or `;`).
 * You probably only need this when you’re building parsers yourself that follow
 * different rules compared to HTML.
 * This is optimized to be tiny in browsers.
 *
 * @param {string} value
 *   `notin` (named), `#123` (deci), `#x123` (hexa).
 * @returns {string|false}
 *   Decoded reference.
 */
export function decodeNamedCharacterReference(value) {
  return own.call(characterEntities, value) ? characterEntities[value] : false
}
{
  "author": "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)",
  "bugs": "https://github.com/wooorm/decode-named-character-reference/issues",
  "contributors": [
    "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)"
  ],
  "dependencies": {
    "character-entities": "^2.0.0"
  },
  "description": "Decode named character references",
  "devDependencies": {
    "@types/node": "^25.0.0",
    "c8": "^10.0.0",
    "prettier": "^3.0.0",
    "remark-cli": "^12.0.0",
    "remark-preset-wooorm": "^11.0.0",
    "type-coverage": "^2.0.0",
    "typescript": "^5.0.0",
    "xo": "^1.0.0"
  },
  "exports": {
    "convex": "./index.js",
    "deno": "./index.js",
    "edge-light": "./index.js",
    "react-native": "./index.js",
    "worker": "./index.js",
    "workerd": "./index.js",
    "browser": "./index.dom.js",
    "default": "./index.js"
  },
  "files": [
    "index.d.ts.map",
    "index.d.ts",
    "index.dom.d.ts.map",
    "index.dom.d.ts",
    "index.dom.js",
    "index.js"
  ],
  "funding": {
    "type": "github",
    "url": "https://github.com/sponsors/wooorm"
  },
  "keywords": [
    "character",
    "decode",
    "named",
    "references"
  ],
  "license": "MIT",
  "main#": "to do: next major: remove field",
  "main": "index.js",
  "name": "decode-named-character-reference",
  "prettier": {
    "bracketSpacing": false,
    "semi": false,
    "singleQuote": true,
    "tabWidth": 2,
    "trailingComma": "none",
    "useTabs": false
  },
  "remarkConfig": {
    "plugins": [
      "remark-preset-wooorm"
    ]
  },
  "repository": "wooorm/decode-named-character-reference",
  "scripts": {
    "build": "tsc --build --clean && tsc --build && type-coverage",
    "format": "remark --frail --output --quiet -- . && prettier --log-level warn --write -- . && xo --fix",
    "test-api": "node --conditions development test.js",
    "test-coverage": "c8 --100 --reporter lcov -- npm run test-api",
    "test": "npm run build && npm run format && npm run test-coverage"
  },
  "sideEffects": false,
  "typeCoverage": {
    "atLeast": 100,
    "ignoreCatch": true
  },
  "types#": "to do: next major: remove field",
  "types": "index.d.ts",
  "type": "module",
  "version": "1.3.0",
  "xo": {
    "prettier": true,
    "rules": {
      "unicorn/prefer-code-point": "off"
    },
    "space": true
  }
}
export function deprecate(fn) {
  return fn
}

export function equal() {}

export function ok() {}

export function unreachable() {}
{
  "name": "devlop",
  "version": "1.1.0",
  "description": "Do things in development and nothing otherwise",
  "license": "MIT",
  "keywords": [
    "assert",
    "deprecate",
    "develop",
    "development"
  ],
  "repository": "wooorm/devlop",
  "bugs": "https://github.com/wooorm/devlop/issues",
  "funding": {
    "type": "github",
    "url": "https://github.com/sponsors/wooorm"
  },
  "author": "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)",
  "contributors": [
    "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)"
  ],
  "sideEffects": false,
  "type": "module",
  "exports": {
    "types": "./lib/development.d.ts",
    "development": "./lib/development.js",
    "default": "./lib/default.js"
  },
  "files": [
    "lib/"
  ],
  "dependencies": {
    "dequal": "^2.0.0"
  },
  "devDependencies": {
    "@rollup/plugin-node-resolve": "^15.1.0",
    "@rollup/plugin-terser": "^0.4.3",
    "@types/node": "^20.0.0",
    "c8": "^8.0.0",
    "esbuild": "^0.18.0",
    "prettier": "^2.0.0",
    "remark-cli": "^11.0.0",
    "remark-preset-wooorm": "^9.0.0",
    "type-coverage": "^2.0.0",
    "typescript": "^5.0.0",
    "xo": "^0.54.0"
  },
  "scripts": {
    "prepack": "npm run build && npm run format",
    "build": "tsc --build --clean && tsc --build && type-coverage",
    "format": "remark . -qfo && prettier . -w --loglevel warn && xo --fix",
    "test-api-development": "node --conditions development test-development.js",
    "test-api-default": "node test-default.js",
    "test-api": "npm run test-api-development && npm run test-api-default",
    "test-coverage": "c8 --100 --reporter lcov npm run test-api",
    "test": "npm run build && npm run format && npm run test-coverage"
  },
  "prettier": {
    "bracketSpacing": false,
    "semi": false,
    "singleQuote": true,
    "tabWidth": 2,
    "trailingComma": "none",
    "useTabs": false
  },
  "remarkConfig": {
    "plugins": [
      "remark-preset-wooorm"
    ]
  },
  "typeCoverage": {
    "atLeast": 100,
    "detail": true,
    "ignoreCatch": true,
    "strict": true
  },
  "xo": {
    "prettier": true
  }
}
/**
 * Check if the given character code, or the character code at the first
 * character, is alphabetical.
 *
 * @param {string|number} character
 * @returns {boolean} Whether `character` is alphabetical.
 */
export function isAlphabetical(character) {
  const code =
    typeof character === 'string' ? character.charCodeAt(0) : character

  return (
    (code >= 97 && code <= 122) /* a-z */ ||
    (code >= 65 && code <= 90) /* A-Z */
  )
}
{
  "name": "is-alphabetical",
  "version": "2.0.1",
  "description": "Check if a character is alphabetical",
  "license": "MIT",
  "keywords": [
    "string",
    "character",
    "char",
    "code",
    "alphabetical"
  ],
  "repository": "wooorm/is-alphabetical",
  "bugs": "https://github.com/wooorm/is-alphabetical/issues",
  "funding": {
    "type": "github",
    "url": "https://github.com/sponsors/wooorm"
  },
  "author": "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)",
  "contributors": [
    "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)"
  ],
  "sideEffects": false,
  "type": "module",
  "main": "index.js",
  "types": "index.d.ts",
  "files": [
    "index.d.ts",
    "index.js"
  ],
  "devDependencies": {
    "@types/tape": "^4.0.0",
    "c8": "^7.0.0",
    "prettier": "^2.0.0",
    "remark-cli": "^10.0.0",
    "remark-preset-wooorm": "^9.0.0",
    "rimraf": "^3.0.0",
    "tape": "^5.0.0",
    "type-coverage": "^2.0.0",
    "typescript": "^4.0.0",
    "xo": "^0.46.0"
  },
  "scripts": {
    "prepublishOnly": "npm run build && npm run format",
    "build": "rimraf \"*.d.ts\" && tsc && type-coverage",
    "format": "remark . -qfo && prettier . -w --loglevel warn && xo --fix",
    "test-api": "node --conditions development test.js",
    "test-coverage": "c8 --check-coverage --branches 100 --functions 100 --lines 100 --statements 100 --reporter lcov npm run test-api",
    "test": "npm run build && npm run format && npm run test-coverage"
  },
  "prettier": {
    "tabWidth": 2,
    "useTabs": false,
    "singleQuote": true,
    "bracketSpacing": false,
    "semi": false,
    "trailingComma": "none"
  },
  "xo": {
    "prettier": true
  },
  "remarkConfig": {
    "plugins": [
      "preset-wooorm"
    ]
  },
  "typeCoverage": {
    "atLeast": 100,
    "detail": true,
    "strict": true,
    "ignoreCatch": true
  }
}
import {isAlphabetical} from 'is-alphabetical'
import {isDecimal} from 'is-decimal'

/**
 * Check if the given character code, or the character code at the first
 * character, is alphanumerical.
 *
 * @param {string|number} character
 * @returns {boolean} Whether `character` is alphanumerical.
 */
export function isAlphanumerical(character) {
  return isAlphabetical(character) || isDecimal(character)
}
{
  "name": "is-alphanumerical",
  "version": "2.0.1",
  "description": "Check if a character is alphanumerical",
  "license": "MIT",
  "keywords": [
    "string",
    "character",
    "char",
    "code",
    "alphabetical",
    "numerical",
    "alphanumerical"
  ],
  "repository": "wooorm/is-alphanumerical",
  "bugs": "https://github.com/wooorm/is-alphanumerical/issues",
  "funding": {
    "type": "github",
    "url": "https://github.com/sponsors/wooorm"
  },
  "author": "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)",
  "contributors": [
    "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)"
  ],
  "sideEffects": false,
  "type": "module",
  "main": "index.js",
  "types": "index.d.ts",
  "files": [
    "index.d.ts",
    "index.js"
  ],
  "dependencies": {
    "is-alphabetical": "^2.0.0",
    "is-decimal": "^2.0.0"
  },
  "devDependencies": {
    "@types/tape": "^4.0.0",
    "c8": "^7.0.0",
    "prettier": "^2.0.0",
    "remark-cli": "^10.0.0",
    "remark-preset-wooorm": "^9.0.0",
    "rimraf": "^3.0.0",
    "tape": "^5.0.0",
    "type-coverage": "^2.0.0",
    "typescript": "^4.0.0",
    "xo": "^0.46.0"
  },
  "scripts": {
    "prepublishOnly": "npm run build && npm run format",
    "build": "rimraf \"*.d.ts\" && tsc && type-coverage",
    "format": "remark . -qfo && prettier . -w --loglevel warn && xo --fix",
    "test-api": "node --conditions development test.js",
    "test-coverage": "c8 --check-coverage --branches 100 --functions 100 --lines 100 --statements 100 --reporter lcov npm run test-api",
    "test": "npm run build && npm run format && npm run test-coverage"
  },
  "prettier": {
    "tabWidth": 2,
    "useTabs": false,
    "singleQuote": true,
    "bracketSpacing": false,
    "semi": false,
    "trailingComma": "none"
  },
  "xo": {
    "prettier": true
  },
  "remarkConfig": {
    "plugins": [
      "preset-wooorm"
    ]
  },
  "typeCoverage": {
    "atLeast": 100,
    "detail": true,
    "strict": true,
    "ignoreCatch": true
  }
}
/**
 * Check if the given character code, or the character code at the first
 * character, is decimal.
 *
 * @param {string|number} character
 * @returns {boolean} Whether `character` is a decimal
 */
export function isDecimal(character) {
  const code =
    typeof character === 'string' ? character.charCodeAt(0) : character

  return code >= 48 && code <= 57 /* 0-9 */
}
{
  "name": "is-decimal",
  "version": "2.0.1",
  "description": "Check if a character is decimal",
  "license": "MIT",
  "keywords": [
    "string",
    "character",
    "char",
    "code",
    "decimal"
  ],
  "repository": "wooorm/is-decimal",
  "bugs": "https://github.com/wooorm/is-decimal/issues",
  "funding": {
    "type": "github",
    "url": "https://github.com/sponsors/wooorm"
  },
  "author": "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)",
  "contributors": [
    "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)"
  ],
  "sideEffects": false,
  "type": "module",
  "main": "index.js",
  "types": "index.d.ts",
  "files": [
    "index.d.ts",
    "index.js"
  ],
  "devDependencies": {
    "@types/tape": "^4.0.0",
    "c8": "^7.0.0",
    "prettier": "^2.0.0",
    "remark-cli": "^10.0.0",
    "remark-preset-wooorm": "^9.0.0",
    "rimraf": "^3.0.0",
    "tape": "^5.0.0",
    "type-coverage": "^2.0.0",
    "typescript": "^4.0.0",
    "xo": "^0.46.0"
  },
  "scripts": {
    "prepublishOnly": "npm run build && npm run format",
    "build": "rimraf \"*.d.ts\" && tsc && type-coverage",
    "format": "remark . -qfo && prettier . -w --loglevel warn && xo --fix",
    "test-api": "node --conditions development test.js",
    "test-coverage": "c8 --check-coverage --branches 100 --functions 100 --lines 100 --statements 100 --reporter lcov npm run test-api",
    "test": "npm run build && npm run format && npm run test-coverage"
  },
  "prettier": {
    "tabWidth": 2,
    "useTabs": false,
    "singleQuote": true,
    "bracketSpacing": false,
    "semi": false,
    "trailingComma": "none"
  },
  "xo": {
    "prettier": true
  },
  "remarkConfig": {
    "plugins": [
      "preset-wooorm"
    ]
  },
  "typeCoverage": {
    "atLeast": 100,
    "detail": true,
    "strict": true,
    "ignoreCatch": true
  }
}
/**
 * Check if the given character code, or the character code at the first
 * character, is hexadecimal.
 *
 * @param {string|number} character
 * @returns {boolean} Whether `character` is hexadecimal
 */
export function isHexadecimal(character) {
  const code =
    typeof character === 'string' ? character.charCodeAt(0) : character

  return (
    (code >= 97 /* a */ && code <= 102) /* z */ ||
    (code >= 65 /* A */ && code <= 70) /* Z */ ||
    (code >= 48 /* A */ && code <= 57) /* Z */
  )
}
{
  "name": "is-hexadecimal",
  "version": "2.0.1",
  "description": "Check if a character is hexadecimal",
  "license": "MIT",
  "keywords": [
    "string",
    "character",
    "char",
    "code",
    "hexadecimal"
  ],
  "repository": "wooorm/is-hexadecimal",
  "bugs": "https://github.com/wooorm/is-hexadecimal/issues",
  "funding": {
    "type": "github",
    "url": "https://github.com/sponsors/wooorm"
  },
  "author": "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)",
  "contributors": [
    "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)"
  ],
  "sideEffects": false,
  "type": "module",
  "main": "index.js",
  "types": "index.d.ts",
  "files": [
    "index.d.ts",
    "index.js"
  ],
  "devDependencies": {
    "@types/tape": "^4.0.0",
    "c8": "^7.0.0",
    "prettier": "^2.0.0",
    "remark-cli": "^10.0.0",
    "remark-preset-wooorm": "^9.0.0",
    "rimraf": "^3.0.0",
    "tape": "^5.0.0",
    "type-coverage": "^2.0.0",
    "typescript": "^4.0.0",
    "xo": "^0.46.0"
  },
  "scripts": {
    "prepublishOnly": "npm run build && npm run format",
    "build": "rimraf \"*.d.ts\" && tsc && type-coverage",
    "format": "remark . -qfo && prettier . -w --loglevel warn && xo --fix",
    "test-api": "node --conditions development test.js",
    "test-coverage": "c8 --check-coverage --branches 100 --functions 100 --lines 100 --statements 100 --reporter lcov npm run test-api",
    "test": "npm run build && npm run format && npm run test-coverage"
  },
  "prettier": {
    "tabWidth": 2,
    "useTabs": false,
    "singleQuote": true,
    "bracketSpacing": false,
    "semi": false,
    "trailingComma": "none"
  },
  "xo": {
    "prettier": true
  },
  "remarkConfig": {
    "plugins": [
      "preset-wooorm"
    ]
  },
  "typeCoverage": {
    "atLeast": 100,
    "detail": true,
    "strict": true,
    "ignoreCatch": true
  }
}
/**
 * Get the count of the longest repeating streak of `substring` in `value`.
 *
 * @param {string} value
 *   Content to search in.
 * @param {string} substring
 *   Substring to look for, typically one character.
 * @returns {number}
 *   Count of most frequent adjacent `substring`s in `value`.
 */
export function longestStreak(value, substring) {
  const source = String(value)
  let index = source.indexOf(substring)
  let expected = index
  let count = 0
  let max = 0

  if (typeof substring !== 'string') {
    throw new TypeError('Expected substring')
  }

  while (index !== -1) {
    if (index === expected) {
      if (++count > max) {
        max = count
      }
    } else {
      count = 1
    }

    expected = index + substring.length
    index = source.indexOf(substring, expected)
  }

  return max
}
{
  "name": "longest-streak",
  "version": "3.1.0",
  "description": "Count the longest repeating streak of a substring",
  "license": "MIT",
  "keywords": [
    "count",
    "length",
    "longest",
    "repeating",
    "streak",
    "substring",
    "character"
  ],
  "repository": "wooorm/longest-streak",
  "bugs": "https://github.com/wooorm/longest-streak/issues",
  "funding": {
    "type": "github",
    "url": "https://github.com/sponsors/wooorm"
  },
  "author": "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)",
  "contributors": [
    "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)"
  ],
  "sideEffects": false,
  "type": "module",
  "main": "index.js",
  "types": "index.d.ts",
  "files": [
    "index.d.ts",
    "index.js"
  ],
  "devDependencies": {
    "@types/node": "^18.0.0",
    "c8": "^7.0.0",
    "prettier": "^2.0.0",
    "remark-cli": "^11.0.0",
    "remark-preset-wooorm": "^9.0.0",
    "type-coverage": "^2.0.0",
    "typescript": "^4.0.0",
    "xo": "^0.52.0"
  },
  "scripts": {
    "prepack": "npm run build && npm run format",
    "build": "tsc --build --clean && tsc --build && type-coverage",
    "format": "remark . -qfo && prettier . -w --loglevel warn && xo --fix",
    "test-api": "node --conditions development test.js",
    "test-coverage": "c8 --check-coverage --100 --reporter lcov npm run test-api",
    "test": "npm run build && npm run format && npm run test-coverage"
  },
  "prettier": {
    "tabWidth": 2,
    "useTabs": false,
    "singleQuote": true,
    "bracketSpacing": false,
    "semi": false,
    "trailingComma": "none"
  },
  "xo": {
    "prettier": true
  },
  "remarkConfig": {
    "plugins": [
      "preset-wooorm"
    ]
  },
  "typeCoverage": {
    "atLeast": 100,
    "detail": true,
    "strict": true,
    "ignoreCatch": true
  }
}
// To do: next major: remove.
/**
 * @typedef {Options} MarkdownTableOptions
 *   Configuration.
 */

/**
 * @typedef Options
 *   Configuration.
 * @property {boolean | null | undefined} [alignDelimiters=true]
 *   Whether to align the delimiters (default: `true`);
 *   they are aligned by default:
 *
 *   ```markdown
 *   | Alpha | B     |
 *   | ----- | ----- |
 *   | C     | Delta |
 *   ```
 *
 *   Pass `false` to make them staggered:
 *
 *   ```markdown
 *   | Alpha | B |
 *   | - | - |
 *   | C | Delta |
 *   ```
 * @property {ReadonlyArray<string | null | undefined> | string | null | undefined} [align]
 *   How to align columns (default: `''`);
 *   one style for all columns or styles for their respective columns;
 *   each style is either `'l'` (left), `'r'` (right), or `'c'` (center);
 *   other values are treated as `''`, which doesn’t place the colon in the
 *   alignment row but does align left;
 *   *only the lowercased first character is used, so `Right` is fine.*
 * @property {boolean | null | undefined} [delimiterEnd=true]
 *   Whether to end each row with the delimiter (default: `true`).
 *
 *   > 👉 **Note**: please don’t use this: it could create fragile structures
 *   > that aren’t understandable to some markdown parsers.
 *
 *   When `true`, there are ending delimiters:
 *
 *   ```markdown
 *   | Alpha | B     |
 *   | ----- | ----- |
 *   | C     | Delta |
 *   ```
 *
 *   When `false`, there are no ending delimiters:
 *
 *   ```markdown
 *   | Alpha | B
 *   | ----- | -----
 *   | C     | Delta
 *   ```
 * @property {boolean | null | undefined} [delimiterStart=true]
 *   Whether to begin each row with the delimiter (default: `true`).
 *
 *   > 👉 **Note**: please don’t use this: it could create fragile structures
 *   > that aren’t understandable to some markdown parsers.
 *
 *   When `true`, there are starting delimiters:
 *
 *   ```markdown
 *   | Alpha | B     |
 *   | ----- | ----- |
 *   | C     | Delta |
 *   ```
 *
 *   When `false`, there are no starting delimiters:
 *
 *   ```markdown
 *   Alpha | B     |
 *   ----- | ----- |
 *   C     | Delta |
 *   ```
 * @property {boolean | null | undefined} [padding=true]
 *   Whether to add a space of padding between delimiters and cells
 *   (default: `true`).
 *
 *   When `true`, there is padding:
 *
 *   ```markdown
 *   | Alpha | B     |
 *   | ----- | ----- |
 *   | C     | Delta |
 *   ```
 *
 *   When `false`, there is no padding:
 *
 *   ```markdown
 *   |Alpha|B    |
 *   |-----|-----|
 *   |C    |Delta|
 *   ```
 * @property {((value: string) => number) | null | undefined} [stringLength]
 *   Function to detect the length of table cell content (optional);
 *   this is used when aligning the delimiters (`|`) between table cells;
 *   full-width characters and emoji mess up delimiter alignment when viewing
 *   the markdown source;
 *   to fix this, you can pass this function,
 *   which receives the cell content and returns its “visible” size;
 *   note that what is and isn’t visible depends on where the text is displayed.
 *
 *   Without such a function, the following:
 *
 *   ```js
 *   markdownTable([
 *     ['Alpha', 'Bravo'],
 *     ['中文', 'Charlie'],
 *     ['👩‍❤️‍👩', 'Delta']
 *   ])
 *   ```
 *
 *   Yields:
 *
 *   ```markdown
 *   | Alpha | Bravo |
 *   | - | - |
 *   | 中文 | Charlie |
 *   | 👩‍❤️‍👩 | Delta |
 *   ```
 *
 *   With [`string-width`](https://github.com/sindresorhus/string-width):
 *
 *   ```js
 *   import stringWidth from 'string-width'
 *
 *   markdownTable(
 *     [
 *       ['Alpha', 'Bravo'],
 *       ['中文', 'Charlie'],
 *       ['👩‍❤️‍👩', 'Delta']
 *     ],
 *     {stringLength: stringWidth}
 *   )
 *   ```
 *
 *   Yields:
 *
 *   ```markdown
 *   | Alpha | Bravo   |
 *   | ----- | ------- |
 *   | 中文  | Charlie |
 *   | 👩‍❤️‍👩    | Delta   |
 *   ```
 */

/**
 * @param {string} value
 *   Cell value.
 * @returns {number}
 *   Cell size.
 */
function defaultStringLength(value) {
  return value.length
}

/**
 * Generate a markdown
 * ([GFM](https://docs.github.com/en/github/writing-on-github/working-with-advanced-formatting/organizing-information-with-tables))
 * table.
 *
 * @param {ReadonlyArray<ReadonlyArray<string | null | undefined>>} table
 *   Table data (matrix of strings).
 * @param {Readonly<Options> | null | undefined} [options]
 *   Configuration (optional).
 * @returns {string}
 *   Result.
 */
export function markdownTable(table, options) {
  const settings = options || {}
  // To do: next major: change to spread.
  const align = (settings.align || []).concat()
  const stringLength = settings.stringLength || defaultStringLength
  /** @type {Array<number>} Character codes as symbols for alignment per column. */
  const alignments = []
  /** @type {Array<Array<string>>} Cells per row. */
  const cellMatrix = []
  /** @type {Array<Array<number>>} Sizes of each cell per row. */
  const sizeMatrix = []
  /** @type {Array<number>} */
  const longestCellByColumn = []
  let mostCellsPerRow = 0
  let rowIndex = -1

  // This is a superfluous loop if we don’t align delimiters, but otherwise we’d
  // do superfluous work when aligning, so optimize for aligning.
  while (++rowIndex < table.length) {
    /** @type {Array<string>} */
    const row = []
    /** @type {Array<number>} */
    const sizes = []
    let columnIndex = -1

    if (table[rowIndex].length > mostCellsPerRow) {
      mostCellsPerRow = table[rowIndex].length
    }

    while (++columnIndex < table[rowIndex].length) {
      const cell = serialize(table[rowIndex][columnIndex])

      if (settings.alignDelimiters !== false) {
        const size = stringLength(cell)
        sizes[columnIndex] = size

        if (
          longestCellByColumn[columnIndex] === undefined ||
          size > longestCellByColumn[columnIndex]
        ) {
          longestCellByColumn[columnIndex] = size
        }
      }

      row.push(cell)
    }

    cellMatrix[rowIndex] = row
    sizeMatrix[rowIndex] = sizes
  }

  // Figure out which alignments to use.
  let columnIndex = -1

  if (typeof align === 'object' && 'length' in align) {
    while (++columnIndex < mostCellsPerRow) {
      alignments[columnIndex] = toAlignment(align[columnIndex])
    }
  } else {
    const code = toAlignment(align)

    while (++columnIndex < mostCellsPerRow) {
      alignments[columnIndex] = code
    }
  }

  // Inject the alignment row.
  columnIndex = -1
  /** @type {Array<string>} */
  const row = []
  /** @type {Array<number>} */
  const sizes = []

  while (++columnIndex < mostCellsPerRow) {
    const code = alignments[columnIndex]
    let before = ''
    let after = ''

    if (code === 99 /* `c` */) {
      before = ':'
      after = ':'
    } else if (code === 108 /* `l` */) {
      before = ':'
    } else if (code === 114 /* `r` */) {
      after = ':'
    }

    // There *must* be at least one hyphen-minus in each alignment cell.
    let size =
      settings.alignDelimiters === false
        ? 1
        : Math.max(
            1,
            longestCellByColumn[columnIndex] - before.length - after.length
          )

    const cell = before + '-'.repeat(size) + after

    if (settings.alignDelimiters !== false) {
      size = before.length + size + after.length

      if (size > longestCellByColumn[columnIndex]) {
        longestCellByColumn[columnIndex] = size
      }

      sizes[columnIndex] = size
    }

    row[columnIndex] = cell
  }

  // Inject the alignment row.
  cellMatrix.splice(1, 0, row)
  sizeMatrix.splice(1, 0, sizes)

  rowIndex = -1
  /** @type {Array<string>} */
  const lines = []

  while (++rowIndex < cellMatrix.length) {
    const row = cellMatrix[rowIndex]
    const sizes = sizeMatrix[rowIndex]
    columnIndex = -1
    /** @type {Array<string>} */
    const line = []

    while (++columnIndex < mostCellsPerRow) {
      const cell = row[columnIndex] || ''
      let before = ''
      let after = ''

      if (settings.alignDelimiters !== false) {
        const size =
          longestCellByColumn[columnIndex] - (sizes[columnIndex] || 0)
        const code = alignments[columnIndex]

        if (code === 114 /* `r` */) {
          before = ' '.repeat(size)
        } else if (code === 99 /* `c` */) {
          if (size % 2) {
            before = ' '.repeat(size / 2 + 0.5)
            after = ' '.repeat(size / 2 - 0.5)
          } else {
            before = ' '.repeat(size / 2)
            after = before
          }
        } else {
          after = ' '.repeat(size)
        }
      }

      if (settings.delimiterStart !== false && !columnIndex) {
        line.push('|')
      }

      if (
        settings.padding !== false &&
        // Don’t add the opening space if we’re not aligning and the cell is
        // empty: there will be a closing space.
        !(settings.alignDelimiters === false && cell === '') &&
        (settings.delimiterStart !== false || columnIndex)
      ) {
        line.push(' ')
      }

      if (settings.alignDelimiters !== false) {
        line.push(before)
      }

      line.push(cell)

      if (settings.alignDelimiters !== false) {
        line.push(after)
      }

      if (settings.padding !== false) {
        line.push(' ')
      }

      if (
        settings.delimiterEnd !== false ||
        columnIndex !== mostCellsPerRow - 1
      ) {
        line.push('|')
      }
    }

    lines.push(
      settings.delimiterEnd === false
        ? line.join('').replace(/ +$/, '')
        : line.join('')
    )
  }

  return lines.join('\n')
}

/**
 * @param {string | null | undefined} [value]
 *   Value to serialize.
 * @returns {string}
 *   Result.
 */
function serialize(value) {
  return value === null || value === undefined ? '' : String(value)
}

/**
 * @param {string | null | undefined} value
 *   Value.
 * @returns {number}
 *   Alignment.
 */
function toAlignment(value) {
  const code = typeof value === 'string' ? value.codePointAt(0) : 0

  return code === 67 /* `C` */ || code === 99 /* `c` */
    ? 99 /* `c` */
    : code === 76 /* `L` */ || code === 108 /* `l` */
      ? 108 /* `l` */
      : code === 82 /* `R` */ || code === 114 /* `r` */
        ? 114 /* `r` */
        : 0
}
{
  "author": "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)",
  "bugs": "https://github.com/wooorm/markdown-table/issues",
  "contributors": [
    "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)"
  ],
  "description": "Generate a markdown (GFM) table",
  "devDependencies": {
    "@types/node": "^22.0.0",
    "c8": "^10.0.0",
    "chalk": "^5.0.0",
    "prettier": "^3.0.0",
    "remark-cli": "^12.0.0",
    "remark-preset-wooorm": "^10.0.0",
    "strip-ansi": "^7.0.0",
    "type-coverage": "^2.0.0",
    "typescript": "^5.0.0",
    "xo": "^0.59.0"
  },
  "files": [
    "index.d.ts.map",
    "index.d.ts",
    "index.js"
  ],
  "funding": {
    "type": "github",
    "url": "https://github.com/sponsors/wooorm"
  },
  "keywords": [
    "align",
    "markdown",
    "rows",
    "table",
    "tabular",
    "text"
  ],
  "license": "MIT",
  "main": "index.js",
  "name": "markdown-table",
  "prettier": {
    "bracketSpacing": false,
    "semi": false,
    "singleQuote": true,
    "tabWidth": 2,
    "trailingComma": "none",
    "useTabs": false
  },
  "remarkConfig": {
    "plugins": [
      "remark-preset-wooorm"
    ]
  },
  "repository": "wooorm/markdown-table",
  "scripts": {
    "build": "tsc --build --clean && tsc --build && type-coverage",
    "format": "remark --frail --output --quiet -- . && prettier --log-level warn --write -- . && xo --fix",
    "test-api": "node --conditions development test.js",
    "test-coverage": "c8 --100 --check-coverage --reporter lcov -- npm run test-api",
    "test": "npm run build && npm run format && npm run test-coverage"
  },
  "sideEffects": false,
  "typeCoverage": {
    "atLeast": 100,
    "detail": true,
    "strict": true
  },
  "types": "index.d.ts",
  "type": "module",
  "version": "3.0.4",
  "xo": {
    "prettier": true,
    "rules": {
      "complexity": "off",
      "unicorn/prefer-switch": "off"
    }
  }
}
// Note: types exposed from `index.d.ts`.
export {directiveFromMarkdown, directiveToMarkdown} from './lib/index.js'
/**
 * @import {Directives, LeafDirective, TextDirective, ToMarkdownOptions} from 'mdast-util-directive'
 * @import {
 *   CompileContext,
 *   Extension as FromMarkdownExtension,
 *   Handle as FromMarkdownHandle,
 *   Token
 * } from 'mdast-util-from-markdown'
 * @import {
 *   ConstructName,
 *   Handle as ToMarkdownHandle,
 *   Options as ToMarkdownExtension,
 *   State
 * } from 'mdast-util-to-markdown'
 * @import {Nodes, Paragraph} from 'mdast'
 */

import {ccount} from 'ccount'
import {ok as assert} from 'devlop'
import {parseEntities} from 'parse-entities'
import {stringifyEntitiesLight} from 'stringify-entities'
import {visitParents} from 'unist-util-visit-parents'

const own = {}.hasOwnProperty

/** @type {Readonly<ToMarkdownOptions>} */
const emptyOptions = {}

const shortcut = /^[^\t\n\r "#'.<=>`}]+$/
const unquoted = /^[^\t\n\r "'<=>`}]+$/

/**
 * Create an extension for `mdast-util-from-markdown` to enable directives in
 * markdown.
 *
 * @returns {FromMarkdownExtension}
 *   Extension for `mdast-util-from-markdown` to enable directives.
 */
export function directiveFromMarkdown() {
  return {
    canContainEols: ['textDirective'],
    enter: {
      directiveContainer: enterContainer,
      directiveContainerAttributes: enterAttributes,
      directiveContainerLabel: enterContainerLabel,

      directiveLeaf: enterLeaf,
      directiveLeafAttributes: enterAttributes,

      directiveText: enterText,
      directiveTextAttributes: enterAttributes
    },
    exit: {
      directiveContainer: exit,
      directiveContainerAttributeClassValue: exitAttributeClassValue,
      directiveContainerAttributeIdValue: exitAttributeIdValue,
      directiveContainerAttributeName: exitAttributeName,
      directiveContainerAttributeValue: exitAttributeValue,
      directiveContainerAttributes: exitAttributes,
      directiveContainerLabel: exitContainerLabel,
      directiveContainerName: exitName,

      directiveLeaf: exit,
      directiveLeafAttributeClassValue: exitAttributeClassValue,
      directiveLeafAttributeIdValue: exitAttributeIdValue,
      directiveLeafAttributeName: exitAttributeName,
      directiveLeafAttributeValue: exitAttributeValue,
      directiveLeafAttributes: exitAttributes,
      directiveLeafName: exitName,

      directiveText: exit,
      directiveTextAttributeClassValue: exitAttributeClassValue,
      directiveTextAttributeIdValue: exitAttributeIdValue,
      directiveTextAttributeName: exitAttributeName,
      directiveTextAttributeValue: exitAttributeValue,
      directiveTextAttributes: exitAttributes,
      directiveTextName: exitName
    }
  }
}

/**
 * Create an extension for `mdast-util-to-markdown` to enable directives in
 * markdown.
 *
 * @param {Readonly<ToMarkdownOptions> | null | undefined} [options]
 *   Configuration (optional).
 * @returns {ToMarkdownExtension}
 *   Extension for `mdast-util-to-markdown` to enable directives.
 */
export function directiveToMarkdown(options) {
  const settings = options || emptyOptions

  if (
    settings.quote !== '"' &&
    settings.quote !== "'" &&
    settings.quote !== null &&
    settings.quote !== undefined
  ) {
    throw new Error(
      'Invalid quote `' + settings.quote + '`, expected `\'` or `"`'
    )
  }

  handleDirective.peek = peekDirective

  return {
    handlers: {
      containerDirective: handleDirective,
      leafDirective: handleDirective,
      textDirective: handleDirective
    },
    unsafe: [
      {
        character: '\r',
        inConstruct: ['leafDirectiveLabel', 'containerDirectiveLabel']
      },
      {
        character: '\n',
        inConstruct: ['leafDirectiveLabel', 'containerDirectiveLabel']
      },
      {
        before: '[^:]',
        character: ':',
        after: '[A-Za-z]',
        inConstruct: ['phrasing']
      },
      {atBreak: true, character: ':', after: ':'}
    ]
  }

  /**
   * @type {ToMarkdownHandle}
   * @param {Directives} node
   */
  function handleDirective(node, _, state, info) {
    const tracker = state.createTracker(info)
    const sequence = fence(node)
    const exit = state.enter(node.type)
    let value = tracker.move(sequence + (node.name || ''))
    /** @type {LeafDirective | Paragraph | TextDirective | undefined} */
    let label

    if (node.type === 'containerDirective') {
      const head = (node.children || [])[0]
      label = inlineDirectiveLabel(head) ? head : undefined
    } else {
      label = node
    }

    if (label && label.children && label.children.length > 0) {
      const exit = state.enter('label')
      /** @type {ConstructName} */
      const labelType = `${node.type}Label`
      const subexit = state.enter(labelType)
      value += tracker.move('[')
      value += tracker.move(
        state.containerPhrasing(label, {
          ...tracker.current(),
          before: value,
          after: ']'
        })
      )
      value += tracker.move(']')
      subexit()
      exit()
    }

    value += tracker.move(attributes(node, state))

    if (node.type === 'containerDirective') {
      const head = (node.children || [])[0]
      let shallow = node

      if (inlineDirectiveLabel(head)) {
        shallow = Object.assign({}, node, {children: node.children.slice(1)})
      }

      if (shallow && shallow.children && shallow.children.length > 0) {
        value += tracker.move('\n')
        value += tracker.move(state.containerFlow(shallow, tracker.current()))
      }

      value += tracker.move('\n' + sequence)
    }

    exit()
    return value
  }

  /**
   * @param {Directives} node
   * @param {State} state
   * @returns {string}
   */
  function attributes(node, state) {
    const attributes = node.attributes || {}
    /** @type {Array<string>} */
    const values = []
    /** @type {string | undefined} */
    let classesFull
    /** @type {string | undefined} */
    let classes
    /** @type {string | undefined} */
    let id
    /** @type {string} */
    let key

    for (key in attributes) {
      if (
        own.call(attributes, key) &&
        attributes[key] !== undefined &&
        attributes[key] !== null
      ) {
        const value = String(attributes[key])

        // To do: next major:
        // Do not reorder `id` and `class` attributes when they do not turn into
        // shortcuts.
        // Additionally, join shortcuts: `#a .b.c d="e"` -> `#a.b.c d="e"`
        if (key === 'id') {
          id =
            settings.preferShortcut !== false && shortcut.test(value)
              ? '#' + value
              : quoted('id', value, node, state)
        } else if (key === 'class') {
          const list = value.split(/[\t\n\r ]+/g)
          /** @type {Array<string>} */
          const classesFullList = []
          /** @type {Array<string>} */
          const classesList = []
          let index = -1

          while (++index < list.length) {
            ;(settings.preferShortcut !== false && shortcut.test(list[index])
              ? classesList
              : classesFullList
            ).push(list[index])
          }

          classesFull =
            classesFullList.length > 0
              ? quoted('class', classesFullList.join(' '), node, state)
              : ''
          classes = classesList.length > 0 ? '.' + classesList.join('.') : ''
        } else {
          values.push(quoted(key, value, node, state))
        }
      }
    }

    if (classesFull) {
      values.unshift(classesFull)
    }

    if (classes) {
      values.unshift(classes)
    }

    if (id) {
      values.unshift(id)
    }

    return values.length > 0 ? '{' + values.join(' ') + '}' : ''
  }

  /**
   * @param {string} key
   * @param {string} value
   * @param {Directives} node
   * @param {State} state
   * @returns {string}
   */
  function quoted(key, value, node, state) {
    if (settings.collapseEmptyAttributes !== false && !value) return key

    if (settings.preferUnquoted && unquoted.test(value)) {
      return key + '=' + value
    }

    // If the alternative is less common than `quote`, switch.
    const preferred = settings.quote || state.options.quote || '"'
    const alternative = preferred === '"' ? "'" : '"'
    // If the alternative is less common than `quote`, switch.
    const appliedQuote =
      settings.quoteSmart &&
      ccount(value, preferred) > ccount(value, alternative)
        ? alternative
        : preferred
    const subset =
      node.type === 'textDirective'
        ? [appliedQuote]
        : [appliedQuote, '\n', '\r']

    return (
      key +
      '=' +
      appliedQuote +
      stringifyEntitiesLight(value, {subset}) +
      appliedQuote
    )
  }
}

/**
 * @this {CompileContext}
 * @type {FromMarkdownHandle}
 */
function enterContainer(token) {
  enter.call(this, 'containerDirective', token)
}

/**
 * @this {CompileContext}
 * @type {FromMarkdownHandle}
 */
function enterLeaf(token) {
  enter.call(this, 'leafDirective', token)
}

/**
 * @this {CompileContext}
 * @type {FromMarkdownHandle}
 */
function enterText(token) {
  enter.call(this, 'textDirective', token)
}

/**
 * @this {CompileContext}
 * @param {Directives['type']} type
 * @param {Token} token
 */
function enter(type, token) {
  this.enter({type, name: '', attributes: {}, children: []}, token)
}

/**
 * @this {CompileContext}
 * @param {Token} token
 */
function exitName(token) {
  const node = this.stack[this.stack.length - 1]
  assert(
    node.type === 'containerDirective' ||
      node.type === 'leafDirective' ||
      node.type === 'textDirective'
  )
  node.name = this.sliceSerialize(token)
}

/**
 * @this {CompileContext}
 * @type {FromMarkdownHandle}
 */
function enterContainerLabel(token) {
  this.enter(
    {type: 'paragraph', data: {directiveLabel: true}, children: []},
    token
  )
}

/**
 * @this {CompileContext}
 * @type {FromMarkdownHandle}
 */
function exitContainerLabel(token) {
  this.exit(token)
}

/**
 * @this {CompileContext}
 * @type {FromMarkdownHandle}
 */
function enterAttributes() {
  this.data.directiveAttributes = []
  this.buffer() // Capture EOLs
}

/**
 * @this {CompileContext}
 * @type {FromMarkdownHandle}
 */
function exitAttributeIdValue(token) {
  const list = this.data.directiveAttributes
  assert(list, 'expected `directiveAttributes`')
  list.push([
    'id',
    parseEntities(this.sliceSerialize(token), {attribute: true})
  ])
}

/**
 * @this {CompileContext}
 * @type {FromMarkdownHandle}
 */
function exitAttributeClassValue(token) {
  const list = this.data.directiveAttributes
  assert(list, 'expected `directiveAttributes`')
  list.push([
    'class',
    parseEntities(this.sliceSerialize(token), {attribute: true})
  ])
}

/**
 * @this {CompileContext}
 * @type {FromMarkdownHandle}
 */
function exitAttributeValue(token) {
  const list = this.data.directiveAttributes
  assert(list, 'expected `directiveAttributes`')
  list[list.length - 1][1] = parseEntities(this.sliceSerialize(token), {
    attribute: true
  })
}

/**
 * @this {CompileContext}
 * @type {FromMarkdownHandle}
 */
function exitAttributeName(token) {
  const list = this.data.directiveAttributes
  assert(list, 'expected `directiveAttributes`')

  // Attribute names in CommonMark are significantly limited, so character
  // references can’t exist.
  list.push([this.sliceSerialize(token), ''])
}

/**
 * @this {CompileContext}
 * @type {FromMarkdownHandle}
 */
function exitAttributes() {
  const list = this.data.directiveAttributes
  assert(list, 'expected `directiveAttributes`')
  /** @type {Record<string, string>} */
  const cleaned = {}
  let index = -1

  while (++index < list.length) {
    const attribute = list[index]

    if (attribute[0] === 'class' && cleaned.class) {
      cleaned.class += ' ' + attribute[1]
    } else {
      cleaned[attribute[0]] = attribute[1]
    }
  }

  this.data.directiveAttributes = undefined
  this.resume() // Drop EOLs
  const node = this.stack[this.stack.length - 1]
  assert(
    node.type === 'containerDirective' ||
      node.type === 'leafDirective' ||
      node.type === 'textDirective'
  )
  node.attributes = cleaned
}

/**
 * @this {CompileContext}
 * @type {FromMarkdownHandle}
 */
function exit(token) {
  this.exit(token)
}

/** @type {ToMarkdownHandle} */
function peekDirective() {
  return ':'
}

/**
 * @param {Nodes} node
 * @returns {node is Paragraph & {data: {directiveLabel: true}}}
 */
function inlineDirectiveLabel(node) {
  return Boolean(
    node && node.type === 'paragraph' && node.data && node.data.directiveLabel
  )
}

/**
 * @param {Directives} node
 * @returns {string}
 */
function fence(node) {
  let size = 0

  if (node.type === 'containerDirective') {
    visitParents(node, function (node, parents) {
      if (node.type === 'containerDirective') {
        let index = parents.length
        let nesting = 0

        while (index--) {
          if (parents[index].type === 'containerDirective') {
            nesting++
          }
        }

        if (nesting > size) size = nesting
      }
    })
    size += 3
  } else if (node.type === 'leafDirective') {
    size = 2
  } else {
    size = 1
  }

  return ':'.repeat(size)
}
{
  "author": "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)",
  "bugs": "https://github.com/syntax-tree/mdast-util-directive/issues",
  "contributors": [
    "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)"
  ],
  "dependencies": {
    "@types/mdast": "^4.0.0",
    "@types/unist": "^3.0.0",
    "ccount": "^2.0.0",
    "devlop": "^1.0.0",
    "mdast-util-from-markdown": "^2.0.0",
    "mdast-util-to-markdown": "^2.0.0",
    "parse-entities": "^4.0.0",
    "stringify-entities": "^4.0.0",
    "unist-util-visit-parents": "^6.0.0"
  },
  "description": "mdast extension to parse and serialize generic directives (`:cite[smith04]`)",
  "devDependencies": {
    "@types/node": "^22.0.0",
    "c8": "^10.0.0",
    "micromark-extension-directive": "^3.0.0",
    "prettier": "^3.0.0",
    "remark-cli": "^12.0.0",
    "remark-preset-wooorm": "^10.0.0",
    "type-coverage": "^2.0.0",
    "typescript": "^5.0.0",
    "unist-util-remove-position": "^5.0.0",
    "xo": "^0.60.0"
  },
  "exports": "./index.js",
  "files": [
    "index.d.ts",
    "index.js",
    "lib/"
  ],
  "funding": {
    "type": "opencollective",
    "url": "https://opencollective.com/unified"
  },
  "keywords": [
    "container",
    "directive",
    "extension",
    "generic",
    "markdown",
    "markup",
    "mdast-util",
    "mdast",
    "unist",
    "utility",
    "util"
  ],
  "license": "MIT",
  "name": "mdast-util-directive",
  "prettier": {
    "bracketSpacing": false,
    "semi": false,
    "singleQuote": true,
    "tabWidth": 2,
    "trailingComma": "none",
    "useTabs": false
  },
  "remarkConfig": {
    "plugins": [
      "remark-preset-wooorm"
    ]
  },
  "repository": "syntax-tree/mdast-util-directive",
  "scripts": {
    "build": "tsc --build --clean && tsc --build && type-coverage",
    "format": "remark --frail --output --quiet -- . && prettier --log-level warn --write -- . && xo --fix",
    "test-api-dev": "node --conditions development test.js",
    "test-api-prod": "node --conditions production test.js",
    "test-api": "npm run test-api-dev && npm run test-api-prod",
    "test-coverage": "c8 --100 --reporter lcov -- npm run test-api",
    "test": "npm run build && npm run format && npm run test-coverage"
  },
  "sideEffects": false,
  "typeCoverage": {
    "atLeast": 100,
    "strict": true
  },
  "type": "module",
  "version": "3.1.0",
  "xo": {
    "overrides": [
      {
        "files": [
          "**/*.d.ts"
        ],
        "rules": {
          "@typescript-eslint/array-type": [
            "error",
            {
              "default": "generic"
            }
          ],
          "@typescript-eslint/ban-types": [
            "error",
            {
              "extendDefaults": true
            }
          ],
          "@typescript-eslint/consistent-type-definitions": [
            "error",
            "interface"
          ]
        }
      }
    ],
    "prettier": true,
    "rules": {
      "unicorn/prefer-at": "off"
    }
  }
}
// Note: types exported from `index.d.ts`.
export { fromMarkdown } from './lib/index.js';/**
 * @import {
 *   Break,
 *   Blockquote,
 *   Code,
 *   Definition,
 *   Emphasis,
 *   Heading,
 *   Html,
 *   Image,
 *   InlineCode,
 *   Link,
 *   ListItem,
 *   List,
 *   Nodes,
 *   Paragraph,
 *   PhrasingContent,
 *   ReferenceType,
 *   Root,
 *   Strong,
 *   Text,
 *   ThematicBreak
 * } from 'mdast'
 * @import {
 *   Encoding,
 *   Event,
 *   Token,
 *   Value
 * } from 'micromark-util-types'
 * @import {Point} from 'unist'
 * @import {
 *   CompileContext,
 *   CompileData,
 *   Config,
 *   Extension,
 *   Handle,
 *   OnEnterError,
 *   Options
 * } from './types.js'
 */

import { toString } from 'mdast-util-to-string';
import { parse, postprocess, preprocess } from 'micromark';
import { decodeNumericCharacterReference } from 'micromark-util-decode-numeric-character-reference';
import { decodeString } from 'micromark-util-decode-string';
import { normalizeIdentifier } from 'micromark-util-normalize-identifier';
import { decodeNamedCharacterReference } from 'decode-named-character-reference';
import { stringifyPosition } from 'unist-util-stringify-position';
const own = {}.hasOwnProperty;

/**
 * Turn markdown into a syntax tree.
 *
 * @overload
 * @param {Value} value
 * @param {Encoding | null | undefined} [encoding]
 * @param {Options | null | undefined} [options]
 * @returns {Root}
 *
 * @overload
 * @param {Value} value
 * @param {Options | null | undefined} [options]
 * @returns {Root}
 *
 * @param {Value} value
 *   Markdown to parse.
 * @param {Encoding | Options | null | undefined} [encoding]
 *   Character encoding for when `value` is `Buffer`.
 * @param {Options | null | undefined} [options]
 *   Configuration.
 * @returns {Root}
 *   mdast tree.
 */
export function fromMarkdown(value, encoding, options) {
  if (encoding && typeof encoding === 'object') {
    options = encoding;
    encoding = undefined;
  }
  return compiler(options)(postprocess(parse(options).document().write(preprocess()(value, encoding, true))));
}

/**
 * Note this compiler only understand complete buffering, not streaming.
 *
 * @param {Options | null | undefined} [options]
 */
function compiler(options) {
  /** @type {Config} */
  const config = {
    transforms: [],
    canContainEols: ['emphasis', 'fragment', 'heading', 'paragraph', 'strong'],
    enter: {
      autolink: opener(link),
      autolinkProtocol: onenterdata,
      autolinkEmail: onenterdata,
      atxHeading: opener(heading),
      blockQuote: opener(blockQuote),
      characterEscape: onenterdata,
      characterReference: onenterdata,
      codeFenced: opener(codeFlow),
      codeFencedFenceInfo: buffer,
      codeFencedFenceMeta: buffer,
      codeIndented: opener(codeFlow, buffer),
      codeText: opener(codeText, buffer),
      codeTextData: onenterdata,
      data: onenterdata,
      codeFlowValue: onenterdata,
      definition: opener(definition),
      definitionDestinationString: buffer,
      definitionLabelString: buffer,
      definitionTitleString: buffer,
      emphasis: opener(emphasis),
      hardBreakEscape: opener(hardBreak),
      hardBreakTrailing: opener(hardBreak),
      htmlFlow: opener(html, buffer),
      htmlFlowData: onenterdata,
      htmlText: opener(html, buffer),
      htmlTextData: onenterdata,
      image: opener(image),
      label: buffer,
      link: opener(link),
      listItem: opener(listItem),
      listItemValue: onenterlistitemvalue,
      listOrdered: opener(list, onenterlistordered),
      listUnordered: opener(list),
      paragraph: opener(paragraph),
      reference: onenterreference,
      referenceString: buffer,
      resourceDestinationString: buffer,
      resourceTitleString: buffer,
      setextHeading: opener(heading),
      strong: opener(strong),
      thematicBreak: opener(thematicBreak)
    },
    exit: {
      atxHeading: closer(),
      atxHeadingSequence: onexitatxheadingsequence,
      autolink: closer(),
      autolinkEmail: onexitautolinkemail,
      autolinkProtocol: onexitautolinkprotocol,
      blockQuote: closer(),
      characterEscapeValue: onexitdata,
      characterReferenceMarkerHexadecimal: onexitcharacterreferencemarker,
      characterReferenceMarkerNumeric: onexitcharacterreferencemarker,
      characterReferenceValue: onexitcharacterreferencevalue,
      characterReference: onexitcharacterreference,
      codeFenced: closer(onexitcodefenced),
      codeFencedFence: onexitcodefencedfence,
      codeFencedFenceInfo: onexitcodefencedfenceinfo,
      codeFencedFenceMeta: onexitcodefencedfencemeta,
      codeFlowValue: onexitdata,
      codeIndented: closer(onexitcodeindented),
      codeText: closer(onexitcodetext),
      codeTextData: onexitdata,
      data: onexitdata,
      definition: closer(),
      definitionDestinationString: onexitdefinitiondestinationstring,
      definitionLabelString: onexitdefinitionlabelstring,
      definitionTitleString: onexitdefinitiontitlestring,
      emphasis: closer(),
      hardBreakEscape: closer(onexithardbreak),
      hardBreakTrailing: closer(onexithardbreak),
      htmlFlow: closer(onexithtmlflow),
      htmlFlowData: onexitdata,
      htmlText: closer(onexithtmltext),
      htmlTextData: onexitdata,
      image: closer(onexitimage),
      label: onexitlabel,
      labelText: onexitlabeltext,
      lineEnding: onexitlineending,
      link: closer(onexitlink),
      listItem: closer(),
      listOrdered: closer(),
      listUnordered: closer(),
      paragraph: closer(),
      referenceString: onexitreferencestring,
      resourceDestinationString: onexitresourcedestinationstring,
      resourceTitleString: onexitresourcetitlestring,
      resource: onexitresource,
      setextHeading: closer(onexitsetextheading),
      setextHeadingLineSequence: onexitsetextheadinglinesequence,
      setextHeadingText: onexitsetextheadingtext,
      strong: closer(),
      thematicBreak: closer()
    }
  };
  configure(config, (options || {}).mdastExtensions || []);

  /** @type {CompileData} */
  const data = {};
  return compile;

  /**
   * Turn micromark events into an mdast tree.
   *
   * @param {Array<Event>} events
   *   Events.
   * @returns {Root}
   *   mdast tree.
   */
  function compile(events) {
    /** @type {Root} */
    let tree = {
      type: 'root',
      children: []
    };
    /** @type {Omit<CompileContext, 'sliceSerialize'>} */
    const context = {
      stack: [tree],
      tokenStack: [],
      config,
      enter,
      exit,
      buffer,
      resume,
      data
    };
    /** @type {Array<number>} */
    const listStack = [];
    let index = -1;
    while (++index < events.length) {
      // We preprocess lists to add `listItem` tokens, and to infer whether
      // items the list itself are spread out.
      if (events[index][1].type === "listOrdered" || events[index][1].type === "listUnordered") {
        if (events[index][0] === 'enter') {
          listStack.push(index);
        } else {
          const tail = listStack.pop();
          index = prepareList(events, tail, index);
        }
      }
    }
    index = -1;
    while (++index < events.length) {
      const handler = config[events[index][0]];
      if (own.call(handler, events[index][1].type)) {
        handler[events[index][1].type].call(Object.assign({
          sliceSerialize: events[index][2].sliceSerialize
        }, context), events[index][1]);
      }
    }

    // Handle tokens still being open.
    if (context.tokenStack.length > 0) {
      const tail = context.tokenStack[context.tokenStack.length - 1];
      const handler = tail[1] || defaultOnError;
      handler.call(context, undefined, tail[0]);
    }

    // Figure out `root` position.
    tree.position = {
      start: point(events.length > 0 ? events[0][1].start : {
        line: 1,
        column: 1,
        offset: 0
      }),
      end: point(events.length > 0 ? events[events.length - 2][1].end : {
        line: 1,
        column: 1,
        offset: 0
      })
    };

    // Call transforms.
    index = -1;
    while (++index < config.transforms.length) {
      tree = config.transforms[index](tree) || tree;
    }
    return tree;
  }

  /**
   * @param {Array<Event>} events
   * @param {number} start
   * @param {number} length
   * @returns {number}
   */
  function prepareList(events, start, length) {
    let index = start - 1;
    let containerBalance = -1;
    let listSpread = false;
    /** @type {Token | undefined} */
    let listItem;
    /** @type {number | undefined} */
    let lineIndex;
    /** @type {number | undefined} */
    let firstBlankLineIndex;
    /** @type {boolean | undefined} */
    let atMarker;
    while (++index <= length) {
      const event = events[index];
      switch (event[1].type) {
        case "listUnordered":
        case "listOrdered":
        case "blockQuote":
          {
            if (event[0] === 'enter') {
              containerBalance++;
            } else {
              containerBalance--;
            }
            atMarker = undefined;
            break;
          }
        case "lineEndingBlank":
          {
            if (event[0] === 'enter') {
              if (listItem && !atMarker && !containerBalance && !firstBlankLineIndex) {
                firstBlankLineIndex = index;
              }
              atMarker = undefined;
            }
            break;
          }
        case "linePrefix":
        case "listItemValue":
        case "listItemMarker":
        case "listItemPrefix":
        case "listItemPrefixWhitespace":
          {
            // Empty.

            break;
          }
        default:
          {
            atMarker = undefined;
          }
      }
      if (!containerBalance && event[0] === 'enter' && event[1].type === "listItemPrefix" || containerBalance === -1 && event[0] === 'exit' && (event[1].type === "listUnordered" || event[1].type === "listOrdered")) {
        if (listItem) {
          let tailIndex = index;
          lineIndex = undefined;
          while (tailIndex--) {
            const tailEvent = events[tailIndex];
            if (tailEvent[1].type === "lineEnding" || tailEvent[1].type === "lineEndingBlank") {
              if (tailEvent[0] === 'exit') continue;
              if (lineIndex) {
                events[lineIndex][1].type = "lineEndingBlank";
                listSpread = true;
              }
              tailEvent[1].type = "lineEnding";
              lineIndex = tailIndex;
            } else if (tailEvent[1].type === "linePrefix" || tailEvent[1].type === "blockQuotePrefix" || tailEvent[1].type === "blockQuotePrefixWhitespace" || tailEvent[1].type === "blockQuoteMarker" || tailEvent[1].type === "listItemIndent") {
              // Empty
            } else {
              break;
            }
          }
          if (firstBlankLineIndex && (!lineIndex || firstBlankLineIndex < lineIndex)) {
            listItem._spread = true;
          }

          // Fix position.
          listItem.end = Object.assign({}, lineIndex ? events[lineIndex][1].start : event[1].end);
          events.splice(lineIndex || index, 0, ['exit', listItem, event[2]]);
          index++;
          length++;
        }

        // Create a new list item.
        if (event[1].type === "listItemPrefix") {
          /** @type {Token} */
          const item = {
            type: 'listItem',
            _spread: false,
            start: Object.assign({}, event[1].start),
            // @ts-expect-error: we’ll add `end` in a second.
            end: undefined
          };
          listItem = item;
          events.splice(index, 0, ['enter', item, event[2]]);
          index++;
          length++;
          firstBlankLineIndex = undefined;
          atMarker = true;
        }
      }
    }
    events[start][1]._spread = listSpread;
    return length;
  }

  /**
   * Create an opener handle.
   *
   * @param {(token: Token) => Nodes} create
   *   Create a node.
   * @param {Handle | undefined} [and]
   *   Optional function to also run.
   * @returns {Handle}
   *   Handle.
   */
  function opener(create, and) {
    return open;

    /**
     * @this {CompileContext}
     * @param {Token} token
     * @returns {undefined}
     */
    function open(token) {
      enter.call(this, create(token), token);
      if (and) and.call(this, token);
    }
  }

  /**
   * @type {CompileContext['buffer']}
   */
  function buffer() {
    this.stack.push({
      type: 'fragment',
      children: []
    });
  }

  /**
   * @type {CompileContext['enter']}
   */
  function enter(node, token, errorHandler) {
    const parent = this.stack[this.stack.length - 1];
    /** @type {Array<Nodes>} */
    const siblings = parent.children;
    siblings.push(node);
    this.stack.push(node);
    this.tokenStack.push([token, errorHandler || undefined]);
    node.position = {
      start: point(token.start),
      // @ts-expect-error: `end` will be patched later.
      end: undefined
    };
  }

  /**
   * Create a closer handle.
   *
   * @param {Handle | undefined} [and]
   *   Optional function to also run.
   * @returns {Handle}
   *   Handle.
   */
  function closer(and) {
    return close;

    /**
     * @this {CompileContext}
     * @param {Token} token
     * @returns {undefined}
     */
    function close(token) {
      if (and) and.call(this, token);
      exit.call(this, token);
    }
  }

  /**
   * @type {CompileContext['exit']}
   */
  function exit(token, onExitError) {
    const node = this.stack.pop();
    const open = this.tokenStack.pop();
    if (!open) {
      throw new Error('Cannot close `' + token.type + '` (' + stringifyPosition({
        start: token.start,
        end: token.end
      }) + '): it’s not open');
    } else if (open[0].type !== token.type) {
      if (onExitError) {
        onExitError.call(this, token, open[0]);
      } else {
        const handler = open[1] || defaultOnError;
        handler.call(this, token, open[0]);
      }
    }
    node.position.end = point(token.end);
  }

  /**
   * @type {CompileContext['resume']}
   */
  function resume() {
    return toString(this.stack.pop());
  }

  //
  // Handlers.
  //

  /**
   * @this {CompileContext}
   * @type {Handle}
   */
  function onenterlistordered() {
    this.data.expectingFirstListItemValue = true;
  }

  /**
   * @this {CompileContext}
   * @type {Handle}
   */
  function onenterlistitemvalue(token) {
    if (this.data.expectingFirstListItemValue) {
      const ancestor = this.stack[this.stack.length - 2];
      ancestor.start = Number.parseInt(this.sliceSerialize(token), 10);
      this.data.expectingFirstListItemValue = undefined;
    }
  }

  /**
   * @this {CompileContext}
   * @type {Handle}
   */
  function onexitcodefencedfenceinfo() {
    const data = this.resume();
    const node = this.stack[this.stack.length - 1];
    node.lang = data;
  }

  /**
   * @this {CompileContext}
   * @type {Handle}
   */
  function onexitcodefencedfencemeta() {
    const data = this.resume();
    const node = this.stack[this.stack.length - 1];
    node.meta = data;
  }

  /**
   * @this {CompileContext}
   * @type {Handle}
   */
  function onexitcodefencedfence() {
    // Exit if this is the closing fence.
    if (this.data.flowCodeInside) return;
    this.buffer();
    this.data.flowCodeInside = true;
  }

  /**
   * @this {CompileContext}
   * @type {Handle}
   */
  function onexitcodefenced() {
    const data = this.resume();
    const node = this.stack[this.stack.length - 1];
    node.value = data.replace(/^(\r?\n|\r)|(\r?\n|\r)$/g, '');
    this.data.flowCodeInside = undefined;
  }

  /**
   * @this {CompileContext}
   * @type {Handle}
   */
  function onexitcodeindented() {
    const data = this.resume();
    const node = this.stack[this.stack.length - 1];
    node.value = data.replace(/(\r?\n|\r)$/g, '');
  }

  /**
   * @this {CompileContext}
   * @type {Handle}
   */
  function onexitdefinitionlabelstring(token) {
    const label = this.resume();
    const node = this.stack[this.stack.length - 1];
    node.label = label;
    node.identifier = normalizeIdentifier(this.sliceSerialize(token)).toLowerCase();
  }

  /**
   * @this {CompileContext}
   * @type {Handle}
   */
  function onexitdefinitiontitlestring() {
    const data = this.resume();
    const node = this.stack[this.stack.length - 1];
    node.title = data;
  }

  /**
   * @this {CompileContext}
   * @type {Handle}
   */
  function onexitdefinitiondestinationstring() {
    const data = this.resume();
    const node = this.stack[this.stack.length - 1];
    node.url = data;
  }

  /**
   * @this {CompileContext}
   * @type {Handle}
   */
  function onexitatxheadingsequence(token) {
    const node = this.stack[this.stack.length - 1];
    if (!node.depth) {
      const depth = this.sliceSerialize(token).length;
      node.depth = depth;
    }
  }

  /**
   * @this {CompileContext}
   * @type {Handle}
   */
  function onexitsetextheadingtext() {
    this.data.setextHeadingSlurpLineEnding = true;
  }

  /**
   * @this {CompileContext}
   * @type {Handle}
   */
  function onexitsetextheadinglinesequence(token) {
    const node = this.stack[this.stack.length - 1];
    node.depth = this.sliceSerialize(token).codePointAt(0) === 61 ? 1 : 2;
  }

  /**
   * @this {CompileContext}
   * @type {Handle}
   */
  function onexitsetextheading() {
    this.data.setextHeadingSlurpLineEnding = undefined;
  }

  /**
   * @this {CompileContext}
   * @type {Handle}
   */

  function onenterdata(token) {
    const node = this.stack[this.stack.length - 1];
    /** @type {Array<Nodes>} */
    const siblings = node.children;
    let tail = siblings[siblings.length - 1];
    if (!tail || tail.type !== 'text') {
      // Add a new text node.
      tail = text();
      tail.position = {
        start: point(token.start),
        // @ts-expect-error: we’ll add `end` later.
        end: undefined
      };
      siblings.push(tail);
    }
    this.stack.push(tail);
  }

  /**
   * @this {CompileContext}
   * @type {Handle}
   */

  function onexitdata(token) {
    const tail = this.stack.pop();
    tail.value += this.sliceSerialize(token);
    tail.position.end = point(token.end);
  }

  /**
   * @this {CompileContext}
   * @type {Handle}
   */

  function onexitlineending(token) {
    const context = this.stack[this.stack.length - 1];
    // If we’re at a hard break, include the line ending in there.
    if (this.data.atHardBreak) {
      const tail = context.children[context.children.length - 1];
      tail.position.end = point(token.end);
      this.data.atHardBreak = undefined;
      return;
    }
    if (!this.data.setextHeadingSlurpLineEnding && config.canContainEols.includes(context.type)) {
      onenterdata.call(this, token);
      onexitdata.call(this, token);
    }
  }

  /**
   * @this {CompileContext}
   * @type {Handle}
   */

  function onexithardbreak() {
    this.data.atHardBreak = true;
  }

  /**
   * @this {CompileContext}
   * @type {Handle}
   */

  function onexithtmlflow() {
    const data = this.resume();
    const node = this.stack[this.stack.length - 1];
    node.value = data;
  }

  /**
   * @this {CompileContext}
   * @type {Handle}
   */

  function onexithtmltext() {
    const data = this.resume();
    const node = this.stack[this.stack.length - 1];
    node.value = data;
  }

  /**
   * @this {CompileContext}
   * @type {Handle}
   */

  function onexitcodetext() {
    const data = this.resume();
    const node = this.stack[this.stack.length - 1];
    node.value = data;
  }

  /**
   * @this {CompileContext}
   * @type {Handle}
   */

  function onexitlink() {
    const node = this.stack[this.stack.length - 1];
    // Note: there are also `identifier` and `label` fields on this link node!
    // These are used / cleaned here.

    // To do: clean.
    if (this.data.inReference) {
      /** @type {ReferenceType} */
      const referenceType = this.data.referenceType || 'shortcut';
      node.type += 'Reference';
      // @ts-expect-error: mutate.
      node.referenceType = referenceType;
      // @ts-expect-error: mutate.
      delete node.url;
      delete node.title;
    } else {
      // @ts-expect-error: mutate.
      delete node.identifier;
      // @ts-expect-error: mutate.
      delete node.label;
    }
    this.data.referenceType = undefined;
  }

  /**
   * @this {CompileContext}
   * @type {Handle}
   */

  function onexitimage() {
    const node = this.stack[this.stack.length - 1];
    // Note: there are also `identifier` and `label` fields on this link node!
    // These are used / cleaned here.

    // To do: clean.
    if (this.data.inReference) {
      /** @type {ReferenceType} */
      const referenceType = this.data.referenceType || 'shortcut';
      node.type += 'Reference';
      // @ts-expect-error: mutate.
      node.referenceType = referenceType;
      // @ts-expect-error: mutate.
      delete node.url;
      delete node.title;
    } else {
      // @ts-expect-error: mutate.
      delete node.identifier;
      // @ts-expect-error: mutate.
      delete node.label;
    }
    this.data.referenceType = undefined;
  }

  /**
   * @this {CompileContext}
   * @type {Handle}
   */

  function onexitlabeltext(token) {
    const string = this.sliceSerialize(token);
    const ancestor = this.stack[this.stack.length - 2];
    // @ts-expect-error: stash this on the node, as it might become a reference
    // later.
    ancestor.label = decodeString(string);
    // @ts-expect-error: same as above.
    ancestor.identifier = normalizeIdentifier(string).toLowerCase();
  }

  /**
   * @this {CompileContext}
   * @type {Handle}
   */

  function onexitlabel() {
    const fragment = this.stack[this.stack.length - 1];
    const value = this.resume();
    const node = this.stack[this.stack.length - 1];
    // Assume a reference.
    this.data.inReference = true;
    if (node.type === 'link') {
      /** @type {Array<PhrasingContent>} */
      const children = fragment.children;
      node.children = children;
    } else {
      node.alt = value;
    }
  }

  /**
   * @this {CompileContext}
   * @type {Handle}
   */

  function onexitresourcedestinationstring() {
    const data = this.resume();
    const node = this.stack[this.stack.length - 1];
    node.url = data;
  }

  /**
   * @this {CompileContext}
   * @type {Handle}
   */

  function onexitresourcetitlestring() {
    const data = this.resume();
    const node = this.stack[this.stack.length - 1];
    node.title = data;
  }

  /**
   * @this {CompileContext}
   * @type {Handle}
   */

  function onexitresource() {
    this.data.inReference = undefined;
  }

  /**
   * @this {CompileContext}
   * @type {Handle}
   */

  function onenterreference() {
    this.data.referenceType = 'collapsed';
  }

  /**
   * @this {CompileContext}
   * @type {Handle}
   */

  function onexitreferencestring(token) {
    const label = this.resume();
    const node = this.stack[this.stack.length - 1];
    // @ts-expect-error: stash this on the node, as it might become a reference
    // later.
    node.label = label;
    // @ts-expect-error: same as above.
    node.identifier = normalizeIdentifier(this.sliceSerialize(token)).toLowerCase();
    this.data.referenceType = 'full';
  }

  /**
   * @this {CompileContext}
   * @type {Handle}
   */

  function onexitcharacterreferencemarker(token) {
    this.data.characterReferenceType = token.type;
  }

  /**
   * @this {CompileContext}
   * @type {Handle}
   */
  function onexitcharacterreferencevalue(token) {
    const data = this.sliceSerialize(token);
    const type = this.data.characterReferenceType;
    /** @type {string} */
    let value;
    if (type) {
      value = decodeNumericCharacterReference(data, type === "characterReferenceMarkerNumeric" ? 10 : 16);
      this.data.characterReferenceType = undefined;
    } else {
      const result = decodeNamedCharacterReference(data);
      value = result;
    }
    const tail = this.stack[this.stack.length - 1];
    tail.value += value;
  }

  /**
   * @this {CompileContext}
   * @type {Handle}
   */
  function onexitcharacterreference(token) {
    const tail = this.stack.pop();
    tail.position.end = point(token.end);
  }

  /**
   * @this {CompileContext}
   * @type {Handle}
   */
  function onexitautolinkprotocol(token) {
    onexitdata.call(this, token);
    const node = this.stack[this.stack.length - 1];
    node.url = this.sliceSerialize(token);
  }

  /**
   * @this {CompileContext}
   * @type {Handle}
   */
  function onexitautolinkemail(token) {
    onexitdata.call(this, token);
    const node = this.stack[this.stack.length - 1];
    node.url = 'mailto:' + this.sliceSerialize(token);
  }

  //
  // Creaters.
  //

  /** @returns {Blockquote} */
  function blockQuote() {
    return {
      type: 'blockquote',
      children: []
    };
  }

  /** @returns {Code} */
  function codeFlow() {
    return {
      type: 'code',
      lang: null,
      meta: null,
      value: ''
    };
  }

  /** @returns {InlineCode} */
  function codeText() {
    return {
      type: 'inlineCode',
      value: ''
    };
  }

  /** @returns {Definition} */
  function definition() {
    return {
      type: 'definition',
      identifier: '',
      label: null,
      title: null,
      url: ''
    };
  }

  /** @returns {Emphasis} */
  function emphasis() {
    return {
      type: 'emphasis',
      children: []
    };
  }

  /** @returns {Heading} */
  function heading() {
    return {
      type: 'heading',
      // @ts-expect-error `depth` will be set later.
      depth: 0,
      children: []
    };
  }

  /** @returns {Break} */
  function hardBreak() {
    return {
      type: 'break'
    };
  }

  /** @returns {Html} */
  function html() {
    return {
      type: 'html',
      value: ''
    };
  }

  /** @returns {Image} */
  function image() {
    return {
      type: 'image',
      title: null,
      url: '',
      alt: null
    };
  }

  /** @returns {Link} */
  function link() {
    return {
      type: 'link',
      title: null,
      url: '',
      children: []
    };
  }

  /**
   * @param {Token} token
   * @returns {List}
   */
  function list(token) {
    return {
      type: 'list',
      ordered: token.type === 'listOrdered',
      start: null,
      spread: token._spread,
      children: []
    };
  }

  /**
   * @param {Token} token
   * @returns {ListItem}
   */
  function listItem(token) {
    return {
      type: 'listItem',
      spread: token._spread,
      checked: null,
      children: []
    };
  }

  /** @returns {Paragraph} */
  function paragraph() {
    return {
      type: 'paragraph',
      children: []
    };
  }

  /** @returns {Strong} */
  function strong() {
    return {
      type: 'strong',
      children: []
    };
  }

  /** @returns {Text} */
  function text() {
    return {
      type: 'text',
      value: ''
    };
  }

  /** @returns {ThematicBreak} */
  function thematicBreak() {
    return {
      type: 'thematicBreak'
    };
  }
}

/**
 * Copy a point-like value.
 *
 * @param {Point} d
 *   Point-like value.
 * @returns {Point}
 *   unist point.
 */
function point(d) {
  return {
    line: d.line,
    column: d.column,
    offset: d.offset
  };
}

/**
 * @param {Config} combined
 * @param {Array<Array<Extension> | Extension>} extensions
 * @returns {undefined}
 */
function configure(combined, extensions) {
  let index = -1;
  while (++index < extensions.length) {
    const value = extensions[index];
    if (Array.isArray(value)) {
      configure(combined, value);
    } else {
      extension(combined, value);
    }
  }
}

/**
 * @param {Config} combined
 * @param {Extension} extension
 * @returns {undefined}
 */
function extension(combined, extension) {
  /** @type {keyof Extension} */
  let key;
  for (key in extension) {
    if (own.call(extension, key)) {
      switch (key) {
        case 'canContainEols':
          {
            const right = extension[key];
            if (right) {
              combined[key].push(...right);
            }
            break;
          }
        case 'transforms':
          {
            const right = extension[key];
            if (right) {
              combined[key].push(...right);
            }
            break;
          }
        case 'enter':
        case 'exit':
          {
            const right = extension[key];
            if (right) {
              Object.assign(combined[key], right);
            }
            break;
          }
        // No default
      }
    }
  }
}

/** @type {OnEnterError} */
function defaultOnError(left, right) {
  if (left) {
    throw new Error('Cannot close `' + left.type + '` (' + stringifyPosition({
      start: left.start,
      end: left.end
    }) + '): a different token (`' + right.type + '`, ' + stringifyPosition({
      start: right.start,
      end: right.end
    }) + ') is open');
  } else {
    throw new Error('Cannot close document, a token (`' + right.type + '`, ' + stringifyPosition({
      start: right.start,
      end: right.end
    }) + ') is still open');
  }
}{
  "author": "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)",
  "bugs": "https://github.com/syntax-tree/mdast-util-from-markdown/issues",
  "contributors": [
    "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)"
  ],
  "dependencies": {
    "@types/mdast": "^4.0.0",
    "@types/unist": "^3.0.0",
    "decode-named-character-reference": "^1.0.0",
    "devlop": "^1.0.0",
    "mdast-util-to-string": "^4.0.0",
    "micromark": "^4.0.0",
    "micromark-util-decode-numeric-character-reference": "^2.0.0",
    "micromark-util-decode-string": "^2.0.0",
    "micromark-util-normalize-identifier": "^2.0.0",
    "micromark-util-symbol": "^2.0.0",
    "micromark-util-types": "^2.0.0",
    "unist-util-stringify-position": "^4.0.0"
  },
  "description": "mdast utility to parse markdown",
  "devDependencies": {
    "@types/node": "^25.0.0",
    "c8": "^10.0.0",
    "commonmark.json": "^0.31.0",
    "esbuild": "^0.27.0",
    "gzip-size-cli": "^5.0.0",
    "hast-util-from-html": "^2.0.0",
    "hast-util-to-html": "^9.0.0",
    "mdast-util-to-hast": "^13.0.0",
    "micromark-build": "^2.0.0",
    "prettier": "^3.0.0",
    "remark-cli": "^12.0.0",
    "remark-preset-wooorm": "^11.0.0",
    "terser": "^5.0.0",
    "type-coverage": "^2.0.0",
    "typescript": "^5.0.0",
    "xo": "^1.0.0"
  },
  "exports": {
    "development": "./dev/index.js",
    "default": "./index.js"
  },
  "files": [
    "dev/",
    "lib/",
    "index.d.ts",
    "index.js"
  ],
  "funding": {
    "type": "opencollective",
    "url": "https://opencollective.com/unified"
  },
  "keywords": [
    "ast",
    "markdown",
    "markup",
    "mdast-util",
    "mdast",
    "parse",
    "syntax",
    "tree",
    "unist",
    "utility",
    "util"
  ],
  "license": "MIT",
  "name": "mdast-util-from-markdown",
  "prettier": {
    "bracketSpacing": false,
    "semi": false,
    "singleQuote": true,
    "tabWidth": 2,
    "trailingComma": "none",
    "useTabs": false
  },
  "remarkConfig": {
    "plugins": [
      "remark-preset-wooorm"
    ]
  },
  "repository": "syntax-tree/mdast-util-from-markdown",
  "scripts": {
    "build": "tsc --build --clean && tsc --build && type-coverage && micromark-build && esbuild . --bundle --minify | terser | gzip-size --raw",
    "format": "remark --frail --quiet --output -- . && prettier --log-level warn --write -- . && xo --fix",
    "test-api-dev": "node --conditions development test/index.js",
    "test-api-prod": "node --conditions production test/index.js",
    "test-api": "npm run test-api-dev && npm run test-api-prod",
    "test-coverage": "c8 --100 --reporter lcov -- npm run test-api",
    "test": "npm run build && npm run format && npm run test-coverage"
  },
  "sideEffects": false,
  "typeCoverage": {
    "atLeast": 100,
    "strict": true
  },
  "type": "module",
  "version": "2.0.3"
}
// Note: extra types exported from `index.d.ts`.
export {gfmFootnoteFromMarkdown, gfmFootnoteToMarkdown} from './lib/index.js'
/**
 * @import {
 *   CompileContext,
 *   Extension as FromMarkdownExtension,
 *   Handle as FromMarkdownHandle
 * } from 'mdast-util-from-markdown'
 * @import {ToMarkdownOptions} from 'mdast-util-gfm-footnote'
 * @import {
 *   Handle as ToMarkdownHandle,
 *   Map,
 *   Options as ToMarkdownExtension
 * } from 'mdast-util-to-markdown'
 * @import {FootnoteDefinition, FootnoteReference} from 'mdast'
 */

import {ok as assert} from 'devlop'
import {normalizeIdentifier} from 'micromark-util-normalize-identifier'

footnoteReference.peek = footnoteReferencePeek

/**
 * @this {CompileContext}
 * @type {FromMarkdownHandle}
 */
function enterFootnoteCallString() {
  this.buffer()
}

/**
 * @this {CompileContext}
 * @type {FromMarkdownHandle}
 */
function enterFootnoteCall(token) {
  this.enter({type: 'footnoteReference', identifier: '', label: ''}, token)
}

/**
 * @this {CompileContext}
 * @type {FromMarkdownHandle}
 */
function enterFootnoteDefinitionLabelString() {
  this.buffer()
}

/**
 * @this {CompileContext}
 * @type {FromMarkdownHandle}
 */
function enterFootnoteDefinition(token) {
  this.enter(
    {type: 'footnoteDefinition', identifier: '', label: '', children: []},
    token
  )
}

/**
 * @this {CompileContext}
 * @type {FromMarkdownHandle}
 */
function exitFootnoteCallString(token) {
  const label = this.resume()
  const node = this.stack[this.stack.length - 1]
  assert(node.type === 'footnoteReference')
  node.identifier = normalizeIdentifier(
    this.sliceSerialize(token)
  ).toLowerCase()
  node.label = label
}

/**
 * @this {CompileContext}
 * @type {FromMarkdownHandle}
 */
function exitFootnoteCall(token) {
  this.exit(token)
}

/**
 * @this {CompileContext}
 * @type {FromMarkdownHandle}
 */
function exitFootnoteDefinitionLabelString(token) {
  const label = this.resume()
  const node = this.stack[this.stack.length - 1]
  assert(node.type === 'footnoteDefinition')
  node.identifier = normalizeIdentifier(
    this.sliceSerialize(token)
  ).toLowerCase()
  node.label = label
}

/**
 * @this {CompileContext}
 * @type {FromMarkdownHandle}
 */
function exitFootnoteDefinition(token) {
  this.exit(token)
}

/** @type {ToMarkdownHandle} */
function footnoteReferencePeek() {
  return '['
}

/**
 * @type {ToMarkdownHandle}
 * @param {FootnoteReference} node
 */
function footnoteReference(node, _, state, info) {
  const tracker = state.createTracker(info)
  let value = tracker.move('[^')
  const exit = state.enter('footnoteReference')
  const subexit = state.enter('reference')
  value += tracker.move(
    state.safe(state.associationId(node), {after: ']', before: value})
  )
  subexit()
  exit()
  value += tracker.move(']')
  return value
}

/**
 * Create an extension for `mdast-util-from-markdown` to enable GFM footnotes
 * in markdown.
 *
 * @returns {FromMarkdownExtension}
 *   Extension for `mdast-util-from-markdown`.
 */
export function gfmFootnoteFromMarkdown() {
  return {
    enter: {
      gfmFootnoteCallString: enterFootnoteCallString,
      gfmFootnoteCall: enterFootnoteCall,
      gfmFootnoteDefinitionLabelString: enterFootnoteDefinitionLabelString,
      gfmFootnoteDefinition: enterFootnoteDefinition
    },
    exit: {
      gfmFootnoteCallString: exitFootnoteCallString,
      gfmFootnoteCall: exitFootnoteCall,
      gfmFootnoteDefinitionLabelString: exitFootnoteDefinitionLabelString,
      gfmFootnoteDefinition: exitFootnoteDefinition
    }
  }
}

/**
 * Create an extension for `mdast-util-to-markdown` to enable GFM footnotes
 * in markdown.
 *
 * @param {ToMarkdownOptions | null | undefined} [options]
 *   Configuration (optional).
 * @returns {ToMarkdownExtension}
 *   Extension for `mdast-util-to-markdown`.
 */
export function gfmFootnoteToMarkdown(options) {
  // To do: next major: change default.
  let firstLineBlank = false

  if (options && options.firstLineBlank) {
    firstLineBlank = true
  }

  return {
    handlers: {footnoteDefinition, footnoteReference},
    // This is on by default already.
    unsafe: [{character: '[', inConstruct: ['label', 'phrasing', 'reference']}]
  }

  /**
   * @type {ToMarkdownHandle}
   * @param {FootnoteDefinition} node
   */
  function footnoteDefinition(node, _, state, info) {
    const tracker = state.createTracker(info)
    let value = tracker.move('[^')
    const exit = state.enter('footnoteDefinition')
    const subexit = state.enter('label')
    value += tracker.move(
      state.safe(state.associationId(node), {before: value, after: ']'})
    )
    subexit()

    value += tracker.move(']:')

    if (node.children && node.children.length > 0) {
      tracker.shift(4)

      value += tracker.move(
        (firstLineBlank ? '\n' : ' ') +
          state.indentLines(
            state.containerFlow(node, tracker.current()),
            firstLineBlank ? mapAll : mapExceptFirst
          )
      )
    }

    exit()

    return value
  }
}

/** @type {Map} */
function mapExceptFirst(line, index, blank) {
  return index === 0 ? line : mapAll(line, index, blank)
}

/** @type {Map} */
function mapAll(line, index, blank) {
  return (blank ? '' : '    ') + line
}
{
  "author": "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)",
  "bugs": "https://github.com/syntax-tree/mdast-util-gfm-footnote/issues",
  "contributors": [
    "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)"
  ],
  "dependencies": {
    "@types/mdast": "^4.0.0",
    "devlop": "^1.1.0",
    "mdast-util-from-markdown": "^2.0.0",
    "mdast-util-to-markdown": "^2.0.0",
    "micromark-util-normalize-identifier": "^2.0.0"
  },
  "description": "mdast extension to parse and serialize GFM footnotes",
  "devDependencies": {
    "@types/node": "^22.0.0",
    "c8": "^10.0.0",
    "micromark-extension-gfm-footnote": "^2.0.0",
    "prettier": "^3.0.0",
    "remark-cli": "^12.0.0",
    "remark-preset-wooorm": "^11.0.0",
    "type-coverage": "^2.0.0",
    "typescript": "^5.0.0",
    "xo": "^0.60.0"
  },
  "exports": "./index.js",
  "files": [
    "index.d.ts",
    "index.js",
    "lib/"
  ],
  "funding": {
    "type": "opencollective",
    "url": "https://opencollective.com/unified"
  },
  "keywords": [
    "footnote",
    "gfm",
    "markdown",
    "markup",
    "mdast-util",
    "mdast",
    "note",
    "unist",
    "utility",
    "util"
  ],
  "license": "MIT",
  "name": "mdast-util-gfm-footnote",
  "prettier": {
    "bracketSpacing": false,
    "semi": false,
    "singleQuote": true,
    "tabWidth": 2,
    "trailingComma": "none",
    "useTabs": false
  },
  "remarkConfig": {
    "plugins": [
      "remark-preset-wooorm"
    ]
  },
  "repository": "syntax-tree/mdast-util-gfm-footnote",
  "scripts": {
    "build": "tsc --build --clean && tsc --build && type-coverage",
    "format": "remark --frail --output --quiet -- . && prettier --log-level warn --write -- . && xo --fix",
    "test-api-dev": "node --conditions development test.js",
    "test-api-prod": "node --conditions production test.js",
    "test-api": "npm run test-api-dev && npm run test-api-prod",
    "test-coverage": "c8 --100 --reporter lcov npm run test-api",
    "test": "npm run build && npm run format && npm run test-coverage"
  },
  "sideEffects": false,
  "typeCoverage": {
    "atLeast": 100,
    "strict": true
  },
  "type": "module",
  "version": "2.1.0",
  "xo": {
    "overrides": [
      {
        "files": [
          "**/*.d.ts"
        ],
        "rules": {
          "@typescript-eslint/array-type": [
            "error",
            {
              "default": "generic"
            }
          ],
          "@typescript-eslint/ban-types": [
            "error",
            {
              "extendDefaults": true
            }
          ],
          "@typescript-eslint/consistent-type-definitions": [
            "error",
            "interface"
          ]
        }
      }
    ],
    "prettier": true,
    "rules": {
      "unicorn/prefer-at": "off"
    }
  }
}
// Note: extra types in `index.d.ts`.
export {
  gfmStrikethroughFromMarkdown,
  gfmStrikethroughToMarkdown
} from './lib/index.js'
/**
 * @typedef {import('mdast').Delete} Delete
 *
 * @typedef {import('mdast-util-from-markdown').CompileContext} CompileContext
 * @typedef {import('mdast-util-from-markdown').Extension} FromMarkdownExtension
 * @typedef {import('mdast-util-from-markdown').Handle} FromMarkdownHandle
 *
 * @typedef {import('mdast-util-to-markdown').ConstructName} ConstructName
 * @typedef {import('mdast-util-to-markdown').Handle} ToMarkdownHandle
 * @typedef {import('mdast-util-to-markdown').Options} ToMarkdownExtension
 */

/**
 * List of constructs that occur in phrasing (paragraphs, headings), but cannot
 * contain strikethrough.
 * So they sort of cancel each other out.
 * Note: could use a better name.
 *
 * Note: keep in sync with: <https://github.com/syntax-tree/mdast-util-to-markdown/blob/8ce8dbf/lib/unsafe.js#L14>
 *
 * @type {Array<ConstructName>}
 */
const constructsWithoutStrikethrough = [
  'autolink',
  'destinationLiteral',
  'destinationRaw',
  'reference',
  'titleQuote',
  'titleApostrophe'
]

handleDelete.peek = peekDelete

/**
 * Create an extension for `mdast-util-from-markdown` to enable GFM
 * strikethrough in markdown.
 *
 * @returns {FromMarkdownExtension}
 *   Extension for `mdast-util-from-markdown` to enable GFM strikethrough.
 */
export function gfmStrikethroughFromMarkdown() {
  return {
    canContainEols: ['delete'],
    enter: {strikethrough: enterStrikethrough},
    exit: {strikethrough: exitStrikethrough}
  }
}

/**
 * Create an extension for `mdast-util-to-markdown` to enable GFM
 * strikethrough in markdown.
 *
 * @returns {ToMarkdownExtension}
 *   Extension for `mdast-util-to-markdown` to enable GFM strikethrough.
 */
export function gfmStrikethroughToMarkdown() {
  return {
    unsafe: [
      {
        character: '~',
        inConstruct: 'phrasing',
        notInConstruct: constructsWithoutStrikethrough
      }
    ],
    handlers: {delete: handleDelete}
  }
}

/**
 * @this {CompileContext}
 * @type {FromMarkdownHandle}
 */
function enterStrikethrough(token) {
  this.enter({type: 'delete', children: []}, token)
}

/**
 * @this {CompileContext}
 * @type {FromMarkdownHandle}
 */
function exitStrikethrough(token) {
  this.exit(token)
}

/**
 * @type {ToMarkdownHandle}
 * @param {Delete} node
 */
function handleDelete(node, _, state, info) {
  const tracker = state.createTracker(info)
  const exit = state.enter('strikethrough')
  let value = tracker.move('~~')
  value += state.containerPhrasing(node, {
    ...tracker.current(),
    before: value,
    after: '~'
  })
  value += tracker.move('~~')
  exit()
  return value
}

/** @type {ToMarkdownHandle} */
function peekDelete() {
  return '~'
}
{
  "name": "mdast-util-gfm-strikethrough",
  "version": "2.0.0",
  "description": "mdast extension to parse and serialize GFM strikethrough",
  "license": "MIT",
  "keywords": [
    "unist",
    "mdast",
    "mdast-util",
    "util",
    "utility",
    "markdown",
    "markup",
    "strikethrough",
    "strike",
    "through",
    "del",
    "delete",
    "deletion",
    "gfm"
  ],
  "repository": "syntax-tree/mdast-util-gfm-strikethrough",
  "bugs": "https://github.com/syntax-tree/mdast-util-gfm-strikethrough/issues",
  "funding": {
    "type": "opencollective",
    "url": "https://opencollective.com/unified"
  },
  "author": "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)",
  "contributors": [
    "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)"
  ],
  "sideEffects": false,
  "type": "module",
  "exports": "./index.js",
  "files": [
    "lib/",
    "index.d.ts",
    "index.js"
  ],
  "dependencies": {
    "@types/mdast": "^4.0.0",
    "mdast-util-from-markdown": "^2.0.0",
    "mdast-util-to-markdown": "^2.0.0"
  },
  "devDependencies": {
    "@types/node": "^20.0.0",
    "c8": "^8.0.0",
    "micromark-extension-gfm-strikethrough": "^2.0.0",
    "prettier": "^2.0.0",
    "remark-cli": "^11.0.0",
    "remark-preset-wooorm": "^9.0.0",
    "type-coverage": "^2.0.0",
    "typescript": "^5.0.0",
    "unist-util-remove-position": "^5.0.0",
    "xo": "^0.54.0"
  },
  "scripts": {
    "prepack": "npm run build && npm run format",
    "build": "tsc --build --clean && tsc --build && type-coverage",
    "format": "remark . -qfo && prettier . -w --loglevel warn && xo --fix",
    "test-api-prod": "node --conditions production test.js",
    "test-api-dev": "node --conditions development test.js",
    "test-api": "npm run test-api-dev && npm run test-api-prod",
    "test-coverage": "c8 --100 --reporter lcov npm run test-api",
    "test": "npm run build && npm run format && npm run test-coverage"
  },
  "prettier": {
    "bracketSpacing": false,
    "semi": false,
    "singleQuote": true,
    "tabWidth": 2,
    "trailingComma": "none",
    "useTabs": false
  },
  "remarkConfig": {
    "plugins": [
      "remark-preset-wooorm"
    ]
  },
  "typeCoverage": {
    "atLeast": 100,
    "detail": true,
    "ignoreCatch": true,
    "strict": true
  },
  "xo": {
    "overrides": [
      {
        "files": [
          "**/*.ts"
        ],
        "rules": {
          "@typescript-eslint/consistent-type-definitions": "off"
        }
      }
    ],
    "prettier": true
  }
}
// Note: types exposed from `index.d.ts`.
export {gfmTableFromMarkdown, gfmTableToMarkdown} from './lib/index.js'
/**
 * @typedef {import('mdast').InlineCode} InlineCode
 * @typedef {import('mdast').Table} Table
 * @typedef {import('mdast').TableCell} TableCell
 * @typedef {import('mdast').TableRow} TableRow
 *
 * @typedef {import('markdown-table').Options} MarkdownTableOptions
 *
 * @typedef {import('mdast-util-from-markdown').CompileContext} CompileContext
 * @typedef {import('mdast-util-from-markdown').Extension} FromMarkdownExtension
 * @typedef {import('mdast-util-from-markdown').Handle} FromMarkdownHandle
 *
 * @typedef {import('mdast-util-to-markdown').Options} ToMarkdownExtension
 * @typedef {import('mdast-util-to-markdown').Handle} ToMarkdownHandle
 * @typedef {import('mdast-util-to-markdown').State} State
 * @typedef {import('mdast-util-to-markdown').Info} Info
 */

/**
 * @typedef Options
 *   Configuration.
 * @property {boolean | null | undefined} [tableCellPadding=true]
 *   Whether to add a space of padding between delimiters and cells (default:
 *   `true`).
 * @property {boolean | null | undefined} [tablePipeAlign=true]
 *   Whether to align the delimiters (default: `true`).
 * @property {MarkdownTableOptions['stringLength'] | null | undefined} [stringLength]
 *   Function to detect the length of table cell content, used when aligning
 *   the delimiters between cells (optional).
 */

import {ok as assert} from 'devlop'
import {markdownTable} from 'markdown-table'
import {defaultHandlers} from 'mdast-util-to-markdown'

/**
 * Create an extension for `mdast-util-from-markdown` to enable GFM tables in
 * markdown.
 *
 * @returns {FromMarkdownExtension}
 *   Extension for `mdast-util-from-markdown` to enable GFM tables.
 */
export function gfmTableFromMarkdown() {
  return {
    enter: {
      table: enterTable,
      tableData: enterCell,
      tableHeader: enterCell,
      tableRow: enterRow
    },
    exit: {
      codeText: exitCodeText,
      table: exitTable,
      tableData: exit,
      tableHeader: exit,
      tableRow: exit
    }
  }
}

/**
 * @this {CompileContext}
 * @type {FromMarkdownHandle}
 */
function enterTable(token) {
  const align = token._align
  assert(align, 'expected `_align` on table')
  this.enter(
    {
      type: 'table',
      align: align.map(function (d) {
        return d === 'none' ? null : d
      }),
      children: []
    },
    token
  )
  this.data.inTable = true
}

/**
 * @this {CompileContext}
 * @type {FromMarkdownHandle}
 */
function exitTable(token) {
  this.exit(token)
  this.data.inTable = undefined
}

/**
 * @this {CompileContext}
 * @type {FromMarkdownHandle}
 */
function enterRow(token) {
  this.enter({type: 'tableRow', children: []}, token)
}

/**
 * @this {CompileContext}
 * @type {FromMarkdownHandle}
 */
function exit(token) {
  this.exit(token)
}

/**
 * @this {CompileContext}
 * @type {FromMarkdownHandle}
 */
function enterCell(token) {
  this.enter({type: 'tableCell', children: []}, token)
}

// Overwrite the default code text data handler to unescape escaped pipes when
// they are in tables.
/**
 * @this {CompileContext}
 * @type {FromMarkdownHandle}
 */
function exitCodeText(token) {
  let value = this.resume()

  if (this.data.inTable) {
    value = value.replace(/\\([\\|])/g, replace)
  }

  const node = this.stack[this.stack.length - 1]
  assert(node.type === 'inlineCode')
  node.value = value
  this.exit(token)
}

/**
 * @param {string} $0
 * @param {string} $1
 * @returns {string}
 */
function replace($0, $1) {
  // Pipes work, backslashes don’t (but can’t escape pipes).
  return $1 === '|' ? $1 : $0
}

/**
 * Create an extension for `mdast-util-to-markdown` to enable GFM tables in
 * markdown.
 *
 * @param {Options | null | undefined} [options]
 *   Configuration.
 * @returns {ToMarkdownExtension}
 *   Extension for `mdast-util-to-markdown` to enable GFM tables.
 */
export function gfmTableToMarkdown(options) {
  const settings = options || {}
  const padding = settings.tableCellPadding
  const alignDelimiters = settings.tablePipeAlign
  const stringLength = settings.stringLength
  const around = padding ? ' ' : '|'

  return {
    unsafe: [
      {character: '\r', inConstruct: 'tableCell'},
      {character: '\n', inConstruct: 'tableCell'},
      // A pipe, when followed by a tab or space (padding), or a dash or colon
      // (unpadded delimiter row), could result in a table.
      {atBreak: true, character: '|', after: '[\t :-]'},
      // A pipe in a cell must be encoded.
      {character: '|', inConstruct: 'tableCell'},
      // A colon must be followed by a dash, in which case it could start a
      // delimiter row.
      {atBreak: true, character: ':', after: '-'},
      // A delimiter row can also start with a dash, when followed by more
      // dashes, a colon, or a pipe.
      // This is a stricter version than the built in check for lists, thematic
      // breaks, and setex heading underlines though:
      // <https://github.com/syntax-tree/mdast-util-to-markdown/blob/51a2038/lib/unsafe.js#L57>
      {atBreak: true, character: '-', after: '[:|-]'}
    ],
    handlers: {
      inlineCode: inlineCodeWithTable,
      table: handleTable,
      tableCell: handleTableCell,
      tableRow: handleTableRow
    }
  }

  /**
   * @type {ToMarkdownHandle}
   * @param {Table} node
   */
  function handleTable(node, _, state, info) {
    return serializeData(handleTableAsData(node, state, info), node.align)
  }

  /**
   * This function isn’t really used normally, because we handle rows at the
   * table level.
   * But, if someone passes in a table row, this ensures we make somewhat sense.
   *
   * @type {ToMarkdownHandle}
   * @param {TableRow} node
   */
  function handleTableRow(node, _, state, info) {
    const row = handleTableRowAsData(node, state, info)
    const value = serializeData([row])
    // `markdown-table` will always add an align row
    return value.slice(0, value.indexOf('\n'))
  }

  /**
   * @type {ToMarkdownHandle}
   * @param {TableCell} node
   */
  function handleTableCell(node, _, state, info) {
    const exit = state.enter('tableCell')
    const subexit = state.enter('phrasing')
    const value = state.containerPhrasing(node, {
      ...info,
      before: around,
      after: around
    })
    subexit()
    exit()
    return value
  }

  /**
   * @param {Array<Array<string>>} matrix
   * @param {Array<string | null | undefined> | null | undefined} [align]
   */
  function serializeData(matrix, align) {
    return markdownTable(matrix, {
      align,
      // @ts-expect-error: `markdown-table` types should support `null`.
      alignDelimiters,
      // @ts-expect-error: `markdown-table` types should support `null`.
      padding,
      // @ts-expect-error: `markdown-table` types should support `null`.
      stringLength
    })
  }

  /**
   * @param {Table} node
   * @param {State} state
   * @param {Info} info
   */
  function handleTableAsData(node, state, info) {
    const children = node.children
    let index = -1
    /** @type {Array<Array<string>>} */
    const result = []
    const subexit = state.enter('table')

    while (++index < children.length) {
      result[index] = handleTableRowAsData(children[index], state, info)
    }

    subexit()

    return result
  }

  /**
   * @param {TableRow} node
   * @param {State} state
   * @param {Info} info
   */
  function handleTableRowAsData(node, state, info) {
    const children = node.children
    let index = -1
    /** @type {Array<string>} */
    const result = []
    const subexit = state.enter('tableRow')

    while (++index < children.length) {
      // Note: the positional info as used here is incorrect.
      // Making it correct would be impossible due to aligning cells?
      // And it would need copy/pasting `markdown-table` into this project.
      result[index] = handleTableCell(children[index], node, state, info)
    }

    subexit()

    return result
  }

  /**
   * @type {ToMarkdownHandle}
   * @param {InlineCode} node
   */
  function inlineCodeWithTable(node, parent, state) {
    let value = defaultHandlers.inlineCode(node, parent, state)

    if (state.stack.includes('tableCell')) {
      value = value.replace(/\|/g, '\\$&')
    }

    return value
  }
}
{
  "name": "mdast-util-gfm-table",
  "version": "2.0.0",
  "description": "mdast extension to parse and serialize GFM tables",
  "license": "MIT",
  "keywords": [
    "unist",
    "mdast",
    "mdast-util",
    "util",
    "utility",
    "markdown",
    "markup",
    "table",
    "row",
    "column",
    "cell",
    "tabular",
    "gfm"
  ],
  "repository": "syntax-tree/mdast-util-gfm-table",
  "bugs": "https://github.com/syntax-tree/mdast-util-gfm-table/issues",
  "funding": {
    "type": "opencollective",
    "url": "https://opencollective.com/unified"
  },
  "author": "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)",
  "contributors": [
    "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)"
  ],
  "sideEffects": false,
  "type": "module",
  "exports": "./index.js",
  "files": [
    "lib/",
    "index.d.ts",
    "index.js"
  ],
  "dependencies": {
    "@types/mdast": "^4.0.0",
    "devlop": "^1.0.0",
    "markdown-table": "^3.0.0",
    "mdast-util-from-markdown": "^2.0.0",
    "mdast-util-to-markdown": "^2.0.0"
  },
  "devDependencies": {
    "@types/node": "^20.0.0",
    "c8": "^8.0.0",
    "micromark-extension-gfm-table": "^2.0.0",
    "prettier": "^2.0.0",
    "remark-cli": "^11.0.0",
    "remark-preset-wooorm": "^9.0.0",
    "string-width": "^6.0.0",
    "type-coverage": "^2.0.0",
    "typescript": "^5.0.0",
    "unist-util-remove-position": "^5.0.0",
    "xo": "^0.54.0"
  },
  "scripts": {
    "prepack": "npm run build && npm run format",
    "build": "tsc --build --clean && tsc --build && type-coverage",
    "format": "remark . -qfo && prettier . -w --loglevel warn && xo --fix",
    "test-api-prod": "node --conditions production test.js",
    "test-api-dev": "node --conditions development test.js",
    "test-api": "npm run test-api-dev && npm run test-api-prod",
    "test-coverage": "c8 --100 --reporter lcov npm run test-api",
    "test": "npm run build && npm run format && npm run test-coverage"
  },
  "prettier": {
    "bracketSpacing": false,
    "semi": false,
    "singleQuote": true,
    "tabWidth": 2,
    "trailingComma": "none",
    "useTabs": false
  },
  "remarkConfig": {
    "plugins": [
      "remark-preset-wooorm"
    ]
  },
  "typeCoverage": {
    "atLeast": 100,
    "detail": true,
    "ignoreCatch": true,
    "strict": true
  },
  "xo": {
    "overrides": [
      {
        "files": [
          "**/*.ts"
        ],
        "rules": {
          "@typescript-eslint/consistent-type-definitions": "off"
        }
      }
    ],
    "prettier": true
  }
}
export {phrasing} from './lib/index.js'
/**
 * @typedef {import('mdast').Html} Html
 * @typedef {import('mdast').PhrasingContent} PhrasingContent
 */

import {convert} from 'unist-util-is'

/**
 * Check if the given value is *phrasing content*.
 *
 * > 👉 **Note**: Excludes `html`, which can be both phrasing or flow.
 *
 * @param node
 *   Thing to check, typically `Node`.
 * @returns
 *   Whether `value` is phrasing content.
 */

export const phrasing =
  /** @type {(node?: unknown) => node is Exclude<PhrasingContent, Html>} */
  (
    convert([
      'break',
      'delete',
      'emphasis',
      // To do: next major: removed since footnotes were added to GFM.
      'footnote',
      'footnoteReference',
      'image',
      'imageReference',
      'inlineCode',
      // Enabled by `mdast-util-math`:
      'inlineMath',
      'link',
      'linkReference',
      // Enabled by `mdast-util-mdx`:
      'mdxJsxTextElement',
      // Enabled by `mdast-util-mdx`:
      'mdxTextExpression',
      'strong',
      'text',
      // Enabled by `mdast-util-directive`:
      'textDirective'
    ])
  )
{
  "name": "mdast-util-phrasing",
  "version": "4.1.0",
  "description": "mdast utility to check if a node is phrasing content",
  "license": "MIT",
  "keywords": [
    "unist",
    "mdast",
    "mdast=util",
    "util",
    "utility",
    "markdown",
    "phrasing"
  ],
  "repository": "syntax-tree/mdast-util-phrasing",
  "bugs": "https://github.com/syntax-tree/mdast-util-phrasing/issues",
  "funding": {
    "type": "opencollective",
    "url": "https://opencollective.com/unified"
  },
  "author": "Victor Felder <victor@draft.li> (https://draft.li)",
  "contributors": [
    "Victor Felder <victor@draft.li> (https://draft.li)",
    "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)"
  ],
  "sideEffects": false,
  "type": "module",
  "exports": "./index.js",
  "files": [
    "lib/",
    "index.d.ts",
    "index.js"
  ],
  "dependencies": {
    "@types/mdast": "^4.0.0",
    "unist-util-is": "^6.0.0"
  },
  "devDependencies": {
    "@types/node": "^20.0.0",
    "c8": "^9.0.0",
    "prettier": "^3.0.0",
    "remark-cli": "^11.0.0",
    "remark-preset-wooorm": "^9.0.0",
    "type-coverage": "^2.0.0",
    "typescript": "^5.0.0",
    "xo": "^0.56.0"
  },
  "scripts": {
    "prepack": "npm run build && npm run format",
    "build": "tsc --build --clean && tsc --build && type-coverage",
    "format": "remark . -qfo && prettier . -w --log-level warn && xo --fix",
    "test-api": "node --conditions development test.js",
    "test-coverage": "c8 --100 --reporter lcov npm run test-api",
    "test": "npm run build && npm run format && npm run test-coverage"
  },
  "prettier": {
    "bracketSpacing": false,
    "semi": false,
    "singleQuote": true,
    "tabWidth": 2,
    "trailingComma": "none",
    "useTabs": false
  },
  "remarkConfig": {
    "plugins": [
      "remark-preset-wooorm"
    ]
  },
  "typeCoverage": {
    "atLeast": 100,
    "detail": true,
    "ignoreCatch": true,
    "strict": true
  },
  "xo": {
    "prettier": true
  }
}
// Note: extra types exposed from `index.d.ts`.
export {toMarkdown} from './lib/index.js'
export {handle as defaultHandlers} from './lib/handle/index.js'
/**
 * @import {Options, State} from './types.js'
 */

const own = {}.hasOwnProperty

/**
 * @param {State} base
 * @param {Options} extension
 * @returns {State}
 */
export function configure(base, extension) {
  let index = -1
  /** @type {keyof Options} */
  let key

  // First do subextensions.
  if (extension.extensions) {
    while (++index < extension.extensions.length) {
      configure(base, extension.extensions[index])
    }
  }

  for (key in extension) {
    if (own.call(extension, key)) {
      switch (key) {
        case 'extensions': {
          // Empty.
          break
        }

        /* c8 ignore next 4 */
        case 'unsafe': {
          list(base[key], extension[key])
          break
        }

        case 'join': {
          list(base[key], extension[key])
          break
        }

        case 'handlers': {
          map(base[key], extension[key])
          break
        }

        default: {
          // @ts-expect-error: matches.
          base.options[key] = extension[key]
        }
      }
    }
  }

  return base
}

/**
 * @template T
 * @param {Array<T>} left
 * @param {Array<T> | null | undefined} right
 */
function list(left, right) {
  if (right) {
    left.push(...right)
  }
}

/**
 * @template T
 * @param {Record<string, T>} left
 * @param {Record<string, T> | null | undefined} right
 */
function map(left, right) {
  if (right) {
    Object.assign(left, right)
  }
}
/**
 * @import {Blockquote, Parents} from 'mdast'
 * @import {Info, Map, State} from 'mdast-util-to-markdown'
 */

/**
 * @param {Blockquote} node
 * @param {Parents | undefined} _
 * @param {State} state
 * @param {Info} info
 * @returns {string}
 */
export function blockquote(node, _, state, info) {
  const exit = state.enter('blockquote')
  const tracker = state.createTracker(info)
  tracker.move('> ')
  tracker.shift(2)
  const value = state.indentLines(
    state.containerFlow(node, tracker.current()),
    map
  )
  exit()
  return value
}

/** @type {Map} */
function map(line, _, blank) {
  return '>' + (blank ? '' : ' ') + line
}
/**
 * @import {Break, Parents} from 'mdast'
 * @import {Info, State} from 'mdast-util-to-markdown'
 */

import {patternInScope} from '../util/pattern-in-scope.js'

/**
 * @param {Break} _
 * @param {Parents | undefined} _1
 * @param {State} state
 * @param {Info} info
 * @returns {string}
 */
export function hardBreak(_, _1, state, info) {
  let index = -1

  while (++index < state.unsafe.length) {
    // If we can’t put eols in this construct (setext headings, tables), use a
    // space instead.
    if (
      state.unsafe[index].character === '\n' &&
      patternInScope(state.stack, state.unsafe[index])
    ) {
      return /[ \t]/.test(info.before) ? '' : ' '
    }
  }

  return '\\\n'
}
/**
 * @import {Info, Map, State} from 'mdast-util-to-markdown'
 * @import {Code, Parents} from 'mdast'
 */

import {longestStreak} from 'longest-streak'
import {formatCodeAsIndented} from '../util/format-code-as-indented.js'
import {checkFence} from '../util/check-fence.js'

/**
 * @param {Code} node
 * @param {Parents | undefined} _
 * @param {State} state
 * @param {Info} info
 * @returns {string}
 */
export function code(node, _, state, info) {
  const marker = checkFence(state)
  const raw = node.value || ''
  const suffix = marker === '`' ? 'GraveAccent' : 'Tilde'

  if (formatCodeAsIndented(node, state)) {
    const exit = state.enter('codeIndented')
    const value = state.indentLines(raw, map)
    exit()
    return value
  }

  const tracker = state.createTracker(info)
  const sequence = marker.repeat(Math.max(longestStreak(raw, marker) + 1, 3))
  const exit = state.enter('codeFenced')
  let value = tracker.move(sequence)

  if (node.lang) {
    const subexit = state.enter(`codeFencedLang${suffix}`)
    value += tracker.move(
      state.safe(node.lang, {
        before: value,
        after: ' ',
        encode: ['`'],
        ...tracker.current()
      })
    )
    subexit()
  }

  if (node.lang && node.meta) {
    const subexit = state.enter(`codeFencedMeta${suffix}`)
    value += tracker.move(' ')
    value += tracker.move(
      state.safe(node.meta, {
        before: value,
        after: '\n',
        encode: ['`'],
        ...tracker.current()
      })
    )
    subexit()
  }

  value += tracker.move('\n')

  if (raw) {
    value += tracker.move(raw + '\n')
  }

  value += tracker.move(sequence)
  exit()
  return value
}

/** @type {Map} */
function map(line, _, blank) {
  return (blank ? '' : '    ') + line
}
/**
 * @import {Info, State} from 'mdast-util-to-markdown'
 * @import {Definition, Parents} from 'mdast'
 */

import {checkQuote} from '../util/check-quote.js'

/**
 * @param {Definition} node
 * @param {Parents | undefined} _
 * @param {State} state
 * @param {Info} info
 * @returns {string}
 */
export function definition(node, _, state, info) {
  const quote = checkQuote(state)
  const suffix = quote === '"' ? 'Quote' : 'Apostrophe'
  const exit = state.enter('definition')
  let subexit = state.enter('label')
  const tracker = state.createTracker(info)
  let value = tracker.move('[')
  value += tracker.move(
    state.safe(state.associationId(node), {
      before: value,
      after: ']',
      ...tracker.current()
    })
  )
  value += tracker.move(']: ')

  subexit()

  if (
    // If there’s no url, or…
    !node.url ||
    // If there are control characters or whitespace.
    /[\0- \u007F]/.test(node.url)
  ) {
    subexit = state.enter('destinationLiteral')
    value += tracker.move('<')
    value += tracker.move(
      state.safe(node.url, {before: value, after: '>', ...tracker.current()})
    )
    value += tracker.move('>')
  } else {
    // No whitespace, raw is prettier.
    subexit = state.enter('destinationRaw')
    value += tracker.move(
      state.safe(node.url, {
        before: value,
        after: node.title ? ' ' : '\n',
        ...tracker.current()
      })
    )
  }

  subexit()

  if (node.title) {
    subexit = state.enter(`title${suffix}`)
    value += tracker.move(' ' + quote)
    value += tracker.move(
      state.safe(node.title, {
        before: value,
        after: quote,
        ...tracker.current()
      })
    )
    value += tracker.move(quote)
    subexit()
  }

  exit()

  return value
}
/**
 * @import {Info, State} from 'mdast-util-to-markdown'
 * @import {Emphasis, Parents} from 'mdast'
 */

import {checkEmphasis} from '../util/check-emphasis.js'
import {encodeCharacterReference} from '../util/encode-character-reference.js'
import {encodeInfo} from '../util/encode-info.js'

emphasis.peek = emphasisPeek

/**
 * @param {Emphasis} node
 * @param {Parents | undefined} _
 * @param {State} state
 * @param {Info} info
 * @returns {string}
 */
export function emphasis(node, _, state, info) {
  const marker = checkEmphasis(state)
  const exit = state.enter('emphasis')
  const tracker = state.createTracker(info)
  const before = tracker.move(marker)

  let between = tracker.move(
    state.containerPhrasing(node, {
      after: marker,
      before,
      ...tracker.current()
    })
  )
  const betweenHead = between.charCodeAt(0)
  const open = encodeInfo(
    info.before.charCodeAt(info.before.length - 1),
    betweenHead,
    marker
  )

  if (open.inside) {
    between = encodeCharacterReference(betweenHead) + between.slice(1)
  }

  const betweenTail = between.charCodeAt(between.length - 1)
  const close = encodeInfo(info.after.charCodeAt(0), betweenTail, marker)

  if (close.inside) {
    between = between.slice(0, -1) + encodeCharacterReference(betweenTail)
  }

  const after = tracker.move(marker)

  exit()

  state.attentionEncodeSurroundingInfo = {
    after: close.outside,
    before: open.outside
  }
  return before + between + after
}

/**
 * @param {Emphasis} _
 * @param {Parents | undefined} _1
 * @param {State} state
 * @returns {string}
 */
function emphasisPeek(_, _1, state) {
  return state.options.emphasis || '*'
}
/**
 * @import {Info, State} from 'mdast-util-to-markdown'
 * @import {Heading, Parents} from 'mdast'
 */

import {encodeCharacterReference} from '../util/encode-character-reference.js'
import {formatHeadingAsSetext} from '../util/format-heading-as-setext.js'

/**
 * @param {Heading} node
 * @param {Parents | undefined} _
 * @param {State} state
 * @param {Info} info
 * @returns {string}
 */
export function heading(node, _, state, info) {
  const rank = Math.max(Math.min(6, node.depth || 1), 1)
  const tracker = state.createTracker(info)

  if (formatHeadingAsSetext(node, state)) {
    const exit = state.enter('headingSetext')
    const subexit = state.enter('phrasing')
    const value = state.containerPhrasing(node, {
      ...tracker.current(),
      before: '\n',
      after: '\n'
    })
    subexit()
    exit()

    return (
      value +
      '\n' +
      (rank === 1 ? '=' : '-').repeat(
        // The whole size…
        value.length -
          // Minus the position of the character after the last EOL (or
          // 0 if there is none)…
          (Math.max(value.lastIndexOf('\r'), value.lastIndexOf('\n')) + 1)
      )
    )
  }

  const sequence = '#'.repeat(rank)
  const exit = state.enter('headingAtx')
  const subexit = state.enter('phrasing')

  // Note: for proper tracking, we should reset the output positions when there
  // is no content returned, because then the space is not output.
  // Practically, in that case, there is no content, so it doesn’t matter that
  // we’ve tracked one too many characters.
  tracker.move(sequence + ' ')

  let value = state.containerPhrasing(node, {
    before: '# ',
    after: '\n',
    ...tracker.current()
  })

  if (/^[\t ]/.test(value)) {
    // To do: what effect has the character reference on tracking?
    value = encodeCharacterReference(value.charCodeAt(0)) + value.slice(1)
  }

  value = value ? sequence + ' ' + value : sequence

  if (state.options.closeAtx) {
    value += ' ' + sequence
  }

  subexit()
  exit()

  return value
}
/**
 * @import {Html} from 'mdast'
 */

html.peek = htmlPeek

/**
 * @param {Html} node
 * @returns {string}
 */
export function html(node) {
  return node.value || ''
}

/**
 * @returns {string}
 */
function htmlPeek() {
  return '<'
}
/**
 * @import {Info, State} from 'mdast-util-to-markdown'
 * @import {ImageReference, Parents} from 'mdast'
 */

imageReference.peek = imageReferencePeek

/**
 * @param {ImageReference} node
 * @param {Parents | undefined} _
 * @param {State} state
 * @param {Info} info
 * @returns {string}
 */
export function imageReference(node, _, state, info) {
  const type = node.referenceType
  const exit = state.enter('imageReference')
  let subexit = state.enter('label')
  const tracker = state.createTracker(info)
  let value = tracker.move('![')
  const alt = state.safe(node.alt, {
    before: value,
    after: ']',
    ...tracker.current()
  })
  value += tracker.move(alt + '][')

  subexit()
  // Hide the fact that we’re in phrasing, because escapes don’t work.
  const stack = state.stack
  state.stack = []
  subexit = state.enter('reference')
  // Note: for proper tracking, we should reset the output positions when we end
  // up making a `shortcut` reference, because then there is no brace output.
  // Practically, in that case, there is no content, so it doesn’t matter that
  // we’ve tracked one too many characters.
  const reference = state.safe(state.associationId(node), {
    before: value,
    after: ']',
    ...tracker.current()
  })
  subexit()
  state.stack = stack
  exit()

  if (type === 'full' || !alt || alt !== reference) {
    value += tracker.move(reference + ']')
  } else if (type === 'shortcut') {
    // Remove the unwanted `[`.
    value = value.slice(0, -1)
  } else {
    value += tracker.move(']')
  }

  return value
}

/**
 * @returns {string}
 */
function imageReferencePeek() {
  return '!'
}
/**
 * @import {Info, State} from 'mdast-util-to-markdown'
 * @import {Image, Parents} from 'mdast'
 */

import {checkQuote} from '../util/check-quote.js'

image.peek = imagePeek

/**
 * @param {Image} node
 * @param {Parents | undefined} _
 * @param {State} state
 * @param {Info} info
 * @returns {string}
 */
export function image(node, _, state, info) {
  const quote = checkQuote(state)
  const suffix = quote === '"' ? 'Quote' : 'Apostrophe'
  const exit = state.enter('image')
  let subexit = state.enter('label')
  const tracker = state.createTracker(info)
  let value = tracker.move('![')
  value += tracker.move(
    state.safe(node.alt, {before: value, after: ']', ...tracker.current()})
  )
  value += tracker.move('](')

  subexit()

  if (
    // If there’s no url but there is a title…
    (!node.url && node.title) ||
    // If there are control characters or whitespace.
    /[\0- \u007F]/.test(node.url)
  ) {
    subexit = state.enter('destinationLiteral')
    value += tracker.move('<')
    value += tracker.move(
      state.safe(node.url, {before: value, after: '>', ...tracker.current()})
    )
    value += tracker.move('>')
  } else {
    // No whitespace, raw is prettier.
    subexit = state.enter('destinationRaw')
    value += tracker.move(
      state.safe(node.url, {
        before: value,
        after: node.title ? ' ' : ')',
        ...tracker.current()
      })
    )
  }

  subexit()

  if (node.title) {
    subexit = state.enter(`title${suffix}`)
    value += tracker.move(' ' + quote)
    value += tracker.move(
      state.safe(node.title, {
        before: value,
        after: quote,
        ...tracker.current()
      })
    )
    value += tracker.move(quote)
    subexit()
  }

  value += tracker.move(')')
  exit()

  return value
}

/**
 * @returns {string}
 */
function imagePeek() {
  return '!'
}
import {blockquote} from './blockquote.js'
import {hardBreak} from './break.js'
import {code} from './code.js'
import {definition} from './definition.js'
import {emphasis} from './emphasis.js'
import {heading} from './heading.js'
import {html} from './html.js'
import {image} from './image.js'
import {imageReference} from './image-reference.js'
import {inlineCode} from './inline-code.js'
import {link} from './link.js'
import {linkReference} from './link-reference.js'
import {list} from './list.js'
import {listItem} from './list-item.js'
import {paragraph} from './paragraph.js'
import {root} from './root.js'
import {strong} from './strong.js'
import {text} from './text.js'
import {thematicBreak} from './thematic-break.js'

/**
 * Default (CommonMark) handlers.
 */
export const handle = {
  blockquote,
  break: hardBreak,
  code,
  definition,
  emphasis,
  hardBreak,
  heading,
  html,
  image,
  imageReference,
  inlineCode,
  link,
  linkReference,
  list,
  listItem,
  paragraph,
  root,
  strong,
  text,
  thematicBreak
}
/**
 * @import {State} from 'mdast-util-to-markdown'
 * @import {InlineCode, Parents} from 'mdast'
 */

inlineCode.peek = inlineCodePeek

/**
 * @param {InlineCode} node
 * @param {Parents | undefined} _
 * @param {State} state
 * @returns {string}
 */
export function inlineCode(node, _, state) {
  let value = node.value || ''
  let sequence = '`'
  let index = -1

  // If there is a single grave accent on its own in the code, use a fence of
  // two.
  // If there are two in a row, use one.
  while (new RegExp('(^|[^`])' + sequence + '([^`]|$)').test(value)) {
    sequence += '`'
  }

  // If this is not just spaces or eols (tabs don’t count), and either the
  // first or last character are a space, eol, or tick, then pad with spaces.
  if (
    /[^ \r\n]/.test(value) &&
    ((/^[ \r\n]/.test(value) && /[ \r\n]$/.test(value)) || /^`|`$/.test(value))
  ) {
    value = ' ' + value + ' '
  }

  // We have a potential problem: certain characters after eols could result in
  // blocks being seen.
  // For example, if someone injected the string `'\n# b'`, then that would
  // result in an ATX heading.
  // We can’t escape characters in `inlineCode`, but because eols are
  // transformed to spaces when going from markdown to HTML anyway, we can swap
  // them out.
  while (++index < state.unsafe.length) {
    const pattern = state.unsafe[index]
    const expression = state.compilePattern(pattern)
    /** @type {RegExpExecArray | null} */
    let match

    // Only look for `atBreak`s.
    // Btw: note that `atBreak` patterns will always start the regex at LF or
    // CR.
    if (!pattern.atBreak) continue

    while ((match = expression.exec(value))) {
      let position = match.index

      // Support CRLF (patterns only look for one of the characters).
      if (
        value.charCodeAt(position) === 10 /* `\n` */ &&
        value.charCodeAt(position - 1) === 13 /* `\r` */
      ) {
        position--
      }

      value = value.slice(0, position) + ' ' + value.slice(match.index + 1)
    }
  }

  return sequence + value + sequence
}

/**
 * @returns {string}
 */
function inlineCodePeek() {
  return '`'
}
/**
 * @import {Info, State} from 'mdast-util-to-markdown'
 * @import {LinkReference, Parents} from 'mdast'
 */

linkReference.peek = linkReferencePeek

/**
 * @param {LinkReference} node
 * @param {Parents | undefined} _
 * @param {State} state
 * @param {Info} info
 * @returns {string}
 */
export function linkReference(node, _, state, info) {
  const type = node.referenceType
  const exit = state.enter('linkReference')
  let subexit = state.enter('label')
  const tracker = state.createTracker(info)
  let value = tracker.move('[')
  const text = state.containerPhrasing(node, {
    before: value,
    after: ']',
    ...tracker.current()
  })
  value += tracker.move(text + '][')

  subexit()
  // Hide the fact that we’re in phrasing, because escapes don’t work.
  const stack = state.stack
  state.stack = []
  subexit = state.enter('reference')
  // Note: for proper tracking, we should reset the output positions when we end
  // up making a `shortcut` reference, because then there is no brace output.
  // Practically, in that case, there is no content, so it doesn’t matter that
  // we’ve tracked one too many characters.
  const reference = state.safe(state.associationId(node), {
    before: value,
    after: ']',
    ...tracker.current()
  })
  subexit()
  state.stack = stack
  exit()

  if (type === 'full' || !text || text !== reference) {
    value += tracker.move(reference + ']')
  } else if (type === 'shortcut') {
    // Remove the unwanted `[`.
    value = value.slice(0, -1)
  } else {
    value += tracker.move(']')
  }

  return value
}

/**
 * @returns {string}
 */
function linkReferencePeek() {
  return '['
}
/**
 * @import {Info, State} from 'mdast-util-to-markdown'
 * @import {Link, Parents} from 'mdast'
 * @import {Exit} from '../types.js'
 */

import {checkQuote} from '../util/check-quote.js'
import {formatLinkAsAutolink} from '../util/format-link-as-autolink.js'

link.peek = linkPeek

/**
 * @param {Link} node
 * @param {Parents | undefined} _
 * @param {State} state
 * @param {Info} info
 * @returns {string}
 */
export function link(node, _, state, info) {
  const quote = checkQuote(state)
  const suffix = quote === '"' ? 'Quote' : 'Apostrophe'
  const tracker = state.createTracker(info)
  /** @type {Exit} */
  let exit
  /** @type {Exit} */
  let subexit

  if (formatLinkAsAutolink(node, state)) {
    // Hide the fact that we’re in phrasing, because escapes don’t work.
    const stack = state.stack
    state.stack = []
    exit = state.enter('autolink')
    let value = tracker.move('<')
    value += tracker.move(
      state.containerPhrasing(node, {
        before: value,
        after: '>',
        ...tracker.current()
      })
    )
    value += tracker.move('>')
    exit()
    state.stack = stack
    return value
  }

  exit = state.enter('link')
  subexit = state.enter('label')
  let value = tracker.move('[')
  value += tracker.move(
    state.containerPhrasing(node, {
      before: value,
      after: '](',
      ...tracker.current()
    })
  )
  value += tracker.move('](')
  subexit()

  if (
    // If there’s no url but there is a title…
    (!node.url && node.title) ||
    // If there are control characters or whitespace.
    /[\0- \u007F]/.test(node.url)
  ) {
    subexit = state.enter('destinationLiteral')
    value += tracker.move('<')
    value += tracker.move(
      state.safe(node.url, {before: value, after: '>', ...tracker.current()})
    )
    value += tracker.move('>')
  } else {
    // No whitespace, raw is prettier.
    subexit = state.enter('destinationRaw')
    value += tracker.move(
      state.safe(node.url, {
        before: value,
        after: node.title ? ' ' : ')',
        ...tracker.current()
      })
    )
  }

  subexit()

  if (node.title) {
    subexit = state.enter(`title${suffix}`)
    value += tracker.move(' ' + quote)
    value += tracker.move(
      state.safe(node.title, {
        before: value,
        after: quote,
        ...tracker.current()
      })
    )
    value += tracker.move(quote)
    subexit()
  }

  value += tracker.move(')')

  exit()
  return value
}

/**
 * @param {Link} node
 * @param {Parents | undefined} _
 * @param {State} state
 * @returns {string}
 */
function linkPeek(node, _, state) {
  return formatLinkAsAutolink(node, state) ? '<' : '['
}
/**
 * @import {Info, Map, State} from 'mdast-util-to-markdown'
 * @import {ListItem, Parents} from 'mdast'
 */

import {checkBullet} from '../util/check-bullet.js'
import {checkListItemIndent} from '../util/check-list-item-indent.js'

/**
 * @param {ListItem} node
 * @param {Parents | undefined} parent
 * @param {State} state
 * @param {Info} info
 * @returns {string}
 */
export function listItem(node, parent, state, info) {
  const listItemIndent = checkListItemIndent(state)
  let bullet = state.bulletCurrent || checkBullet(state)

  // Add the marker value for ordered lists.
  if (parent && parent.type === 'list' && parent.ordered) {
    bullet =
      (typeof parent.start === 'number' && parent.start > -1
        ? parent.start
        : 1) +
      (state.options.incrementListMarker === false
        ? 0
        : parent.children.indexOf(node)) +
      bullet
  }

  let size = bullet.length + 1

  if (
    listItemIndent === 'tab' ||
    (listItemIndent === 'mixed' &&
      ((parent && parent.type === 'list' && parent.spread) || node.spread))
  ) {
    size = Math.ceil(size / 4) * 4
  }

  const tracker = state.createTracker(info)
  tracker.move(bullet + ' '.repeat(size - bullet.length))
  tracker.shift(size)
  const exit = state.enter('listItem')
  const value = state.indentLines(
    state.containerFlow(node, tracker.current()),
    map
  )
  exit()

  return value

  /** @type {Map} */
  function map(line, index, blank) {
    if (index) {
      return (blank ? '' : ' '.repeat(size)) + line
    }

    return (blank ? bullet : bullet + ' '.repeat(size - bullet.length)) + line
  }
}
/**
 * @import {Info, State} from 'mdast-util-to-markdown'
 * @import {List, Parents} from 'mdast'
 */

import {checkBullet} from '../util/check-bullet.js'
import {checkBulletOther} from '../util/check-bullet-other.js'
import {checkBulletOrdered} from '../util/check-bullet-ordered.js'
import {checkRule} from '../util/check-rule.js'

/**
 * @param {List} node
 * @param {Parents | undefined} parent
 * @param {State} state
 * @param {Info} info
 * @returns {string}
 */
export function list(node, parent, state, info) {
  const exit = state.enter('list')
  const bulletCurrent = state.bulletCurrent
  /** @type {string} */
  let bullet = node.ordered ? checkBulletOrdered(state) : checkBullet(state)
  /** @type {string} */
  const bulletOther = node.ordered
    ? bullet === '.'
      ? ')'
      : '.'
    : checkBulletOther(state)
  let useDifferentMarker =
    parent && state.bulletLastUsed ? bullet === state.bulletLastUsed : false

  if (!node.ordered) {
    const firstListItem = node.children ? node.children[0] : undefined

    // If there’s an empty first list item directly in two list items,
    // we have to use a different bullet:
    //
    // ```markdown
    // * - *
    // ```
    //
    // …because otherwise it would become one big thematic break.
    if (
      // Bullet could be used as a thematic break marker:
      (bullet === '*' || bullet === '-') &&
      // Empty first list item:
      firstListItem &&
      (!firstListItem.children || !firstListItem.children[0]) &&
      // Directly in two other list items:
      state.stack[state.stack.length - 1] === 'list' &&
      state.stack[state.stack.length - 2] === 'listItem' &&
      state.stack[state.stack.length - 3] === 'list' &&
      state.stack[state.stack.length - 4] === 'listItem' &&
      // That are each the first child.
      state.indexStack[state.indexStack.length - 1] === 0 &&
      state.indexStack[state.indexStack.length - 2] === 0 &&
      state.indexStack[state.indexStack.length - 3] === 0
    ) {
      useDifferentMarker = true
    }

    // If there’s a thematic break at the start of the first list item,
    // we have to use a different bullet:
    //
    // ```markdown
    // * ---
    // ```
    //
    // …because otherwise it would become one big thematic break.
    if (checkRule(state) === bullet && firstListItem) {
      let index = -1

      while (++index < node.children.length) {
        const item = node.children[index]

        if (
          item &&
          item.type === 'listItem' &&
          item.children &&
          item.children[0] &&
          item.children[0].type === 'thematicBreak'
        ) {
          useDifferentMarker = true
          break
        }
      }
    }
  }

  if (useDifferentMarker) {
    bullet = bulletOther
  }

  state.bulletCurrent = bullet
  const value = state.containerFlow(node, info)
  state.bulletLastUsed = bullet
  state.bulletCurrent = bulletCurrent
  exit()
  return value
}
/**
 * @import {Info, State} from 'mdast-util-to-markdown'
 * @import {Paragraph, Parents} from 'mdast'
 */

/**
 * @param {Paragraph} node
 * @param {Parents | undefined} _
 * @param {State} state
 * @param {Info} info
 * @returns {string}
 */
export function paragraph(node, _, state, info) {
  const exit = state.enter('paragraph')
  const subexit = state.enter('phrasing')
  const value = state.containerPhrasing(node, info)
  subexit()
  exit()
  return value
}
/**
 * @import {Info, State} from 'mdast-util-to-markdown'
 * @import {Parents, Root} from 'mdast'
 */

import {phrasing} from 'mdast-util-phrasing'

/**
 * @param {Root} node
 * @param {Parents | undefined} _
 * @param {State} state
 * @param {Info} info
 * @returns {string}
 */
export function root(node, _, state, info) {
  // Note: `html` nodes are ambiguous.
  const hasPhrasing = node.children.some(function (d) {
    return phrasing(d)
  })

  const container = hasPhrasing ? state.containerPhrasing : state.containerFlow
  return container.call(state, node, info)
}
/**
 * @import {Info, State} from 'mdast-util-to-markdown'
 * @import {Parents, Strong} from 'mdast'
 */

import {checkStrong} from '../util/check-strong.js'
import {encodeCharacterReference} from '../util/encode-character-reference.js'
import {encodeInfo} from '../util/encode-info.js'

strong.peek = strongPeek

/**
 * @param {Strong} node
 * @param {Parents | undefined} _
 * @param {State} state
 * @param {Info} info
 * @returns {string}
 */
export function strong(node, _, state, info) {
  const marker = checkStrong(state)
  const exit = state.enter('strong')
  const tracker = state.createTracker(info)
  const before = tracker.move(marker + marker)

  let between = tracker.move(
    state.containerPhrasing(node, {
      after: marker,
      before,
      ...tracker.current()
    })
  )
  const betweenHead = between.charCodeAt(0)
  const open = encodeInfo(
    info.before.charCodeAt(info.before.length - 1),
    betweenHead,
    marker
  )

  if (open.inside) {
    between = encodeCharacterReference(betweenHead) + between.slice(1)
  }

  const betweenTail = between.charCodeAt(between.length - 1)
  const close = encodeInfo(info.after.charCodeAt(0), betweenTail, marker)

  if (close.inside) {
    between = between.slice(0, -1) + encodeCharacterReference(betweenTail)
  }

  const after = tracker.move(marker + marker)

  exit()

  state.attentionEncodeSurroundingInfo = {
    after: close.outside,
    before: open.outside
  }
  return before + between + after
}

/**
 * @param {Strong} _
 * @param {Parents | undefined} _1
 * @param {State} state
 * @returns {string}
 */
function strongPeek(_, _1, state) {
  return state.options.strong || '*'
}
/**
 * @import {Info, State} from 'mdast-util-to-markdown'
 * @import {Parents, Text} from 'mdast'
 */

/**
 * @param {Text} node
 * @param {Parents | undefined} _
 * @param {State} state
 * @param {Info} info
 * @returns {string}
 */
export function text(node, _, state, info) {
  return state.safe(node.value, info)
}
/**
 * @import {State} from 'mdast-util-to-markdown'
 * @import {Parents, ThematicBreak} from 'mdast'
 */

import {checkRuleRepetition} from '../util/check-rule-repetition.js'
import {checkRule} from '../util/check-rule.js'

/**
 * @param {ThematicBreak} _
 * @param {Parents | undefined} _1
 * @param {State} state
 * @returns {string}
 */
export function thematicBreak(_, _1, state) {
  const value = (
    checkRule(state) + (state.options.ruleSpaces ? ' ' : '')
  ).repeat(checkRuleRepetition(state))

  return state.options.ruleSpaces ? value.slice(0, -1) : value
}
/**
 * @import {Info, Join, Options, SafeConfig, State} from 'mdast-util-to-markdown'
 * @import {Nodes} from 'mdast'
 * @import {Enter, FlowParents, PhrasingParents, TrackFields} from './types.js'
 */

import {zwitch} from 'zwitch'
import {configure} from './configure.js'
import {handle as handlers} from './handle/index.js'
import {join} from './join.js'
import {unsafe} from './unsafe.js'
import {association} from './util/association.js'
import {compilePattern} from './util/compile-pattern.js'
import {containerPhrasing} from './util/container-phrasing.js'
import {containerFlow} from './util/container-flow.js'
import {indentLines} from './util/indent-lines.js'
import {safe} from './util/safe.js'
import {track} from './util/track.js'

/**
 * Turn an mdast syntax tree into markdown.
 *
 * @param {Nodes} tree
 *   Tree to serialize.
 * @param {Options | null | undefined} [options]
 *   Configuration (optional).
 * @returns {string}
 *   Serialized markdown representing `tree`.
 */
export function toMarkdown(tree, options) {
  const settings = options || {}
  /** @type {State} */
  const state = {
    associationId: association,
    containerPhrasing: containerPhrasingBound,
    containerFlow: containerFlowBound,
    createTracker: track,
    compilePattern,
    enter,
    // @ts-expect-error: GFM / frontmatter are typed in `mdast` but not defined
    // here.
    handlers: {...handlers},
    // @ts-expect-error: add `handle` in a second.
    handle: undefined,
    indentLines,
    indexStack: [],
    join: [...join],
    options: {},
    safe: safeBound,
    stack: [],
    unsafe: [...unsafe]
  }

  configure(state, settings)

  if (state.options.tightDefinitions) {
    state.join.push(joinDefinition)
  }

  state.handle = zwitch('type', {
    invalid,
    unknown,
    handlers: state.handlers
  })

  let result = state.handle(tree, undefined, state, {
    before: '\n',
    after: '\n',
    now: {line: 1, column: 1},
    lineShift: 0
  })

  if (
    result &&
    result.charCodeAt(result.length - 1) !== 10 &&
    result.charCodeAt(result.length - 1) !== 13
  ) {
    result += '\n'
  }

  return result

  /** @type {Enter} */
  function enter(name) {
    state.stack.push(name)
    return exit

    /**
     * @returns {undefined}
     */
    function exit() {
      state.stack.pop()
    }
  }
}

/**
 * @param {unknown} value
 * @returns {never}
 */
function invalid(value) {
  throw new Error('Cannot handle value `' + value + '`, expected node')
}

/**
 * @param {unknown} value
 * @returns {never}
 */
function unknown(value) {
  // Always a node.
  const node = /** @type {Nodes} */ (value)
  throw new Error('Cannot handle unknown node `' + node.type + '`')
}

/** @type {Join} */
function joinDefinition(left, right) {
  // No blank line between adjacent definitions.
  if (left.type === 'definition' && left.type === right.type) {
    return 0
  }
}

/**
 * Serialize the children of a parent that contains phrasing children.
 *
 * These children will be joined flush together.
 *
 * @this {State}
 *   Info passed around about the current state.
 * @param {PhrasingParents} parent
 *   Parent of flow nodes.
 * @param {Info} info
 *   Info on where we are in the document we are generating.
 * @returns {string}
 *   Serialized children, joined together.
 */
function containerPhrasingBound(parent, info) {
  return containerPhrasing(parent, this, info)
}

/**
 * Serialize the children of a parent that contains flow children.
 *
 * These children will typically be joined by blank lines.
 * What they are joined by exactly is defined by `Join` functions.
 *
 * @this {State}
 *   Info passed around about the current state.
 * @param {FlowParents} parent
 *   Parent of flow nodes.
 * @param {TrackFields} info
 *   Info on where we are in the document we are generating.
 * @returns {string}
 *   Serialized children, joined by (blank) lines.
 */
function containerFlowBound(parent, info) {
  return containerFlow(parent, this, info)
}

/**
 * Make a string safe for embedding in markdown constructs.
 *
 * In markdown, almost all punctuation characters can, in certain cases,
 * result in something.
 * Whether they do is highly subjective to where they happen and in what
 * they happen.
 *
 * To solve this, `mdast-util-to-markdown` tracks:
 *
 * * Characters before and after something;
 * * What “constructs” we are in.
 *
 * This information is then used by this function to escape or encode
 * special characters.
 *
 * @this {State}
 *   Info passed around about the current state.
 * @param {string | null | undefined} value
 *   Raw value to make safe.
 * @param {SafeConfig} config
 *   Configuration.
 * @returns {string}
 *   Serialized markdown safe for embedding.
 */
function safeBound(value, config) {
  return safe(this, value, config)
}
/**
 * @import {Join} from 'mdast-util-to-markdown'
 */

import {formatCodeAsIndented} from './util/format-code-as-indented.js'
import {formatHeadingAsSetext} from './util/format-heading-as-setext.js'

/** @type {Array<Join>} */
export const join = [joinDefaults]

/** @type {Join} */
function joinDefaults(left, right, parent, state) {
  // Indented code after list or another indented code.
  if (
    right.type === 'code' &&
    formatCodeAsIndented(right, state) &&
    (left.type === 'list' ||
      (left.type === right.type && formatCodeAsIndented(left, state)))
  ) {
    return false
  }

  // Join children of a list or an item.
  // In which case, `parent` has a `spread` field.
  if ('spread' in parent && typeof parent.spread === 'boolean') {
    if (
      left.type === 'paragraph' &&
      // Two paragraphs.
      (left.type === right.type ||
        right.type === 'definition' ||
        // Paragraph followed by a setext heading.
        (right.type === 'heading' && formatHeadingAsSetext(right, state)))
    ) {
      return
    }

    return parent.spread ? 1 : 0
  }
}
/**
 * @import {ConstructName, Unsafe} from 'mdast-util-to-markdown'
 */

/**
 * List of constructs that occur in phrasing (paragraphs, headings), but cannot
 * contain things like attention (emphasis, strong), images, or links.
 * So they sort of cancel each other out.
 * Note: could use a better name.
 *
 * @type {Array<ConstructName>}
 */
const fullPhrasingSpans = [
  'autolink',
  'destinationLiteral',
  'destinationRaw',
  'reference',
  'titleQuote',
  'titleApostrophe'
]

/** @type {Array<Unsafe>} */
export const unsafe = [
  {character: '\t', after: '[\\r\\n]', inConstruct: 'phrasing'},
  {character: '\t', before: '[\\r\\n]', inConstruct: 'phrasing'},
  {
    character: '\t',
    inConstruct: ['codeFencedLangGraveAccent', 'codeFencedLangTilde']
  },
  {
    character: '\r',
    inConstruct: [
      'codeFencedLangGraveAccent',
      'codeFencedLangTilde',
      'codeFencedMetaGraveAccent',
      'codeFencedMetaTilde',
      'destinationLiteral',
      'headingAtx'
    ]
  },
  {
    character: '\n',
    inConstruct: [
      'codeFencedLangGraveAccent',
      'codeFencedLangTilde',
      'codeFencedMetaGraveAccent',
      'codeFencedMetaTilde',
      'destinationLiteral',
      'headingAtx'
    ]
  },
  {character: ' ', after: '[\\r\\n]', inConstruct: 'phrasing'},
  {character: ' ', before: '[\\r\\n]', inConstruct: 'phrasing'},
  {
    character: ' ',
    inConstruct: ['codeFencedLangGraveAccent', 'codeFencedLangTilde']
  },
  // An exclamation mark can start an image, if it is followed by a link or
  // a link reference.
  {
    character: '!',
    after: '\\[',
    inConstruct: 'phrasing',
    notInConstruct: fullPhrasingSpans
  },
  // A quote can break out of a title.
  {character: '"', inConstruct: 'titleQuote'},
  // A number sign could start an ATX heading if it starts a line.
  {atBreak: true, character: '#'},
  {character: '#', inConstruct: 'headingAtx', after: '(?:[\r\n]|$)'},
  // Dollar sign and percentage are not used in markdown.
  // An ampersand could start a character reference.
  {character: '&', after: '[#A-Za-z]', inConstruct: 'phrasing'},
  // An apostrophe can break out of a title.
  {character: "'", inConstruct: 'titleApostrophe'},
  // A left paren could break out of a destination raw.
  {character: '(', inConstruct: 'destinationRaw'},
  // A left paren followed by `]` could make something into a link or image.
  {
    before: '\\]',
    character: '(',
    inConstruct: 'phrasing',
    notInConstruct: fullPhrasingSpans
  },
  // A right paren could start a list item or break out of a destination
  // raw.
  {atBreak: true, before: '\\d+', character: ')'},
  {character: ')', inConstruct: 'destinationRaw'},
  // An asterisk can start thematic breaks, list items, emphasis, strong.
  {atBreak: true, character: '*', after: '(?:[ \t\r\n*])'},
  {character: '*', inConstruct: 'phrasing', notInConstruct: fullPhrasingSpans},
  // A plus sign could start a list item.
  {atBreak: true, character: '+', after: '(?:[ \t\r\n])'},
  // A dash can start thematic breaks, list items, and setext heading
  // underlines.
  {atBreak: true, character: '-', after: '(?:[ \t\r\n-])'},
  // A dot could start a list item.
  {atBreak: true, before: '\\d+', character: '.', after: '(?:[ \t\r\n]|$)'},
  // Slash, colon, and semicolon are not used in markdown for constructs.
  // A less than can start html (flow or text) or an autolink.
  // HTML could start with an exclamation mark (declaration, cdata, comment),
  // slash (closing tag), question mark (instruction), or a letter (tag).
  // An autolink also starts with a letter.
  // Finally, it could break out of a destination literal.
  {atBreak: true, character: '<', after: '[!/?A-Za-z]'},
  {
    character: '<',
    after: '[!/?A-Za-z]',
    inConstruct: 'phrasing',
    notInConstruct: fullPhrasingSpans
  },
  {character: '<', inConstruct: 'destinationLiteral'},
  // An equals to can start setext heading underlines.
  {atBreak: true, character: '='},
  // A greater than can start block quotes and it can break out of a
  // destination literal.
  {atBreak: true, character: '>'},
  {character: '>', inConstruct: 'destinationLiteral'},
  // Question mark and at sign are not used in markdown for constructs.
  // A left bracket can start definitions, references, labels,
  {atBreak: true, character: '['},
  {character: '[', inConstruct: 'phrasing', notInConstruct: fullPhrasingSpans},
  {character: '[', inConstruct: ['label', 'reference']},
  // A backslash can start an escape (when followed by punctuation) or a
  // hard break (when followed by an eol).
  // Note: typical escapes are handled in `safe`!
  {character: '\\', after: '[\\r\\n]', inConstruct: 'phrasing'},
  // A right bracket can exit labels.
  {character: ']', inConstruct: ['label', 'reference']},
  // Caret is not used in markdown for constructs.
  // An underscore can start emphasis, strong, or a thematic break.
  {atBreak: true, character: '_'},
  {character: '_', inConstruct: 'phrasing', notInConstruct: fullPhrasingSpans},
  // A grave accent can start code (fenced or text), or it can break out of
  // a grave accent code fence.
  {atBreak: true, character: '`'},
  {
    character: '`',
    inConstruct: ['codeFencedLangGraveAccent', 'codeFencedMetaGraveAccent']
  },
  {character: '`', inConstruct: 'phrasing', notInConstruct: fullPhrasingSpans},
  // Left brace, vertical bar, right brace are not used in markdown for
  // constructs.
  // A tilde can start code (fenced).
  {atBreak: true, character: '~'}
]
/**
 * @import {AssociationId} from '../types.js'
 */

import {decodeString} from 'micromark-util-decode-string'

/**
 * Get an identifier from an association to match it to others.
 *
 * Associations are nodes that match to something else through an ID:
 * <https://github.com/syntax-tree/mdast#association>.
 *
 * The `label` of an association is the string value: character escapes and
 * references work, and casing is intact.
 * The `identifier` is used to match one association to another:
 * controversially, character escapes and references don’t work in this
 * matching: `&copy;` does not match `©`, and `\+` does not match `+`.
 *
 * But casing is ignored (and whitespace) is trimmed and collapsed: ` A\nb`
 * matches `a b`.
 * So, we do prefer the label when figuring out how we’re going to serialize:
 * it has whitespace, casing, and we can ignore most useless character
 * escapes and all character references.
 *
 * @type {AssociationId}
 */
export function association(node) {
  if (node.label || !node.identifier) {
    return node.label || ''
  }

  return decodeString(node.identifier)
}
/**
 * @import {Options, State} from 'mdast-util-to-markdown'
 */

/**
 * @param {State} state
 * @returns {Exclude<Options['bulletOrdered'], null | undefined>}
 */
export function checkBulletOrdered(state) {
  const marker = state.options.bulletOrdered || '.'

  if (marker !== '.' && marker !== ')') {
    throw new Error(
      'Cannot serialize items with `' +
        marker +
        '` for `options.bulletOrdered`, expected `.` or `)`'
    )
  }

  return marker
}
/**
 * @import {Options, State} from 'mdast-util-to-markdown'
 */

import {checkBullet} from './check-bullet.js'

/**
 * @param {State} state
 * @returns {Exclude<Options['bullet'], null | undefined>}
 */
export function checkBulletOther(state) {
  const bullet = checkBullet(state)
  const bulletOther = state.options.bulletOther

  if (!bulletOther) {
    return bullet === '*' ? '-' : '*'
  }

  if (bulletOther !== '*' && bulletOther !== '+' && bulletOther !== '-') {
    throw new Error(
      'Cannot serialize items with `' +
        bulletOther +
        '` for `options.bulletOther`, expected `*`, `+`, or `-`'
    )
  }

  if (bulletOther === bullet) {
    throw new Error(
      'Expected `bullet` (`' +
        bullet +
        '`) and `bulletOther` (`' +
        bulletOther +
        '`) to be different'
    )
  }

  return bulletOther
}
/**
 * @import {Options, State} from 'mdast-util-to-markdown'
 */

/**
 * @param {State} state
 * @returns {Exclude<Options['bullet'], null | undefined>}
 */
export function checkBullet(state) {
  const marker = state.options.bullet || '*'

  if (marker !== '*' && marker !== '+' && marker !== '-') {
    throw new Error(
      'Cannot serialize items with `' +
        marker +
        '` for `options.bullet`, expected `*`, `+`, or `-`'
    )
  }

  return marker
}
/**
 * @import {Options, State} from 'mdast-util-to-markdown'
 */

/**
 * @param {State} state
 * @returns {Exclude<Options['emphasis'], null | undefined>}
 */
export function checkEmphasis(state) {
  const marker = state.options.emphasis || '*'

  if (marker !== '*' && marker !== '_') {
    throw new Error(
      'Cannot serialize emphasis with `' +
        marker +
        '` for `options.emphasis`, expected `*`, or `_`'
    )
  }

  return marker
}
/**
 * @import {Options, State} from 'mdast-util-to-markdown'
 */

/**
 * @param {State} state
 * @returns {Exclude<Options['fence'], null | undefined>}
 */
export function checkFence(state) {
  const marker = state.options.fence || '`'

  if (marker !== '`' && marker !== '~') {
    throw new Error(
      'Cannot serialize code with `' +
        marker +
        '` for `options.fence`, expected `` ` `` or `~`'
    )
  }

  return marker
}
/**
 * @import {Options, State} from 'mdast-util-to-markdown'
 */

/**
 * @param {State} state
 * @returns {Exclude<Options['listItemIndent'], null | undefined>}
 */
export function checkListItemIndent(state) {
  const style = state.options.listItemIndent || 'one'

  if (style !== 'tab' && style !== 'one' && style !== 'mixed') {
    throw new Error(
      'Cannot serialize items with `' +
        style +
        '` for `options.listItemIndent`, expected `tab`, `one`, or `mixed`'
    )
  }

  return style
}
/**
 * @import {Options, State} from 'mdast-util-to-markdown'
 */

/**
 * @param {State} state
 * @returns {Exclude<Options['quote'], null | undefined>}
 */
export function checkQuote(state) {
  const marker = state.options.quote || '"'

  if (marker !== '"' && marker !== "'") {
    throw new Error(
      'Cannot serialize title with `' +
        marker +
        '` for `options.quote`, expected `"`, or `\'`'
    )
  }

  return marker
}
/**
 * @import {Options, State} from 'mdast-util-to-markdown'
 */

/**
 * @param {State} state
 * @returns {Exclude<Options['ruleRepetition'], null | undefined>}
 */
export function checkRuleRepetition(state) {
  const repetition = state.options.ruleRepetition || 3

  if (repetition < 3) {
    throw new Error(
      'Cannot serialize rules with repetition `' +
        repetition +
        '` for `options.ruleRepetition`, expected `3` or more'
    )
  }

  return repetition
}
/**
 * @import {Options, State} from 'mdast-util-to-markdown'
 */

/**
 * @param {State} state
 * @returns {Exclude<Options['rule'], null | undefined>}
 */
export function checkRule(state) {
  const marker = state.options.rule || '*'

  if (marker !== '*' && marker !== '-' && marker !== '_') {
    throw new Error(
      'Cannot serialize rules with `' +
        marker +
        '` for `options.rule`, expected `*`, `-`, or `_`'
    )
  }

  return marker
}
/**
 * @import {Options, State} from 'mdast-util-to-markdown'
 */

/**
 * @param {State} state
 * @returns {Exclude<Options['strong'], null | undefined>}
 */
export function checkStrong(state) {
  const marker = state.options.strong || '*'

  if (marker !== '*' && marker !== '_') {
    throw new Error(
      'Cannot serialize strong with `' +
        marker +
        '` for `options.strong`, expected `*`, or `_`'
    )
  }

  return marker
}
/**
 * @import {CompilePattern} from '../types.js'
 */

/**
 * @type {CompilePattern}
 */
export function compilePattern(pattern) {
  if (!pattern._compiled) {
    const before =
      (pattern.atBreak ? '[\\r\\n][\\t ]*' : '') +
      (pattern.before ? '(?:' + pattern.before + ')' : '')

    pattern._compiled = new RegExp(
      (before ? '(' + before + ')' : '') +
        (/[|\\{}()[\]^$+*?.-]/.test(pattern.character) ? '\\' : '') +
        pattern.character +
        (pattern.after ? '(?:' + pattern.after + ')' : ''),
      'g'
    )
  }

  return pattern._compiled
}
/**
 * @import {State} from 'mdast-util-to-markdown'
 * @import {FlowChildren, FlowParents, TrackFields} from '../types.js'
 */

/**
 * @param {FlowParents} parent
 *   Parent of flow nodes.
 * @param {State} state
 *   Info passed around about the current state.
 * @param {TrackFields} info
 *   Info on where we are in the document we are generating.
 * @returns {string}
 *   Serialized children, joined by (blank) lines.
 */
export function containerFlow(parent, state, info) {
  const indexStack = state.indexStack
  const children = parent.children || []
  const tracker = state.createTracker(info)
  /** @type {Array<string>} */
  const results = []
  let index = -1

  indexStack.push(-1)

  while (++index < children.length) {
    const child = children[index]

    indexStack[indexStack.length - 1] = index

    results.push(
      tracker.move(
        state.handle(child, parent, state, {
          before: '\n',
          after: '\n',
          ...tracker.current()
        })
      )
    )

    if (child.type !== 'list') {
      state.bulletLastUsed = undefined
    }

    if (index < children.length - 1) {
      results.push(
        tracker.move(between(child, children[index + 1], parent, state))
      )
    }
  }

  indexStack.pop()

  return results.join('')
}

/**
 * @param {FlowChildren} left
 * @param {FlowChildren} right
 * @param {FlowParents} parent
 * @param {State} state
 * @returns {string}
 */
function between(left, right, parent, state) {
  let index = state.join.length

  while (index--) {
    const result = state.join[index](left, right, parent, state)

    if (result === true || result === 1) {
      break
    }

    if (typeof result === 'number') {
      return '\n'.repeat(1 + result)
    }

    if (result === false) {
      return '\n\n<!---->\n\n'
    }
  }

  return '\n\n'
}
/**
 * @import {Handle, Info, State} from 'mdast-util-to-markdown'
 * @import {PhrasingParents} from '../types.js'
 */

import {encodeCharacterReference} from './encode-character-reference.js'

/**
 * Serialize the children of a parent that contains phrasing children.
 *
 * These children will be joined flush together.
 *
 * @param {PhrasingParents} parent
 *   Parent of flow nodes.
 * @param {State} state
 *   Info passed around about the current state.
 * @param {Info} info
 *   Info on where we are in the document we are generating.
 * @returns {string}
 *   Serialized children, joined together.
 */
export function containerPhrasing(parent, state, info) {
  const indexStack = state.indexStack
  const children = parent.children || []
  /** @type {Array<string>} */
  const results = []
  let index = -1
  let before = info.before
  /** @type {string | undefined} */
  let encodeAfter

  indexStack.push(-1)
  let tracker = state.createTracker(info)

  while (++index < children.length) {
    const child = children[index]
    /** @type {string} */
    let after

    indexStack[indexStack.length - 1] = index

    if (index + 1 < children.length) {
      /** @type {Handle} */
      // @ts-expect-error: hush, it’s actually a `zwitch`.
      let handle = state.handle.handlers[children[index + 1].type]
      /** @type {Handle} */
      // @ts-expect-error: hush, it’s actually a `zwitch`.
      if (handle && handle.peek) handle = handle.peek
      after = handle
        ? handle(children[index + 1], parent, state, {
            before: '',
            after: '',
            ...tracker.current()
          }).charAt(0)
        : ''
    } else {
      after = info.after
    }

    // In some cases, html (text) can be found in phrasing right after an eol.
    // When we’d serialize that, in most cases that would be seen as html
    // (flow).
    // As we can’t escape or so to prevent it from happening, we take a somewhat
    // reasonable approach: replace that eol with a space.
    // See: <https://github.com/syntax-tree/mdast-util-to-markdown/issues/15>
    if (
      results.length > 0 &&
      (before === '\r' || before === '\n') &&
      child.type === 'html'
    ) {
      results[results.length - 1] = results[results.length - 1].replace(
        /(\r?\n|\r)$/,
        ' '
      )
      before = ' '

      // To do: does this work to reset tracker?
      tracker = state.createTracker(info)
      tracker.move(results.join(''))
    }

    let value = state.handle(child, parent, state, {
      ...tracker.current(),
      after,
      before
    })

    // If we had to encode the first character after the previous node and it’s
    // still the same character,
    // encode it.
    if (encodeAfter && encodeAfter === value.slice(0, 1)) {
      value =
        encodeCharacterReference(encodeAfter.charCodeAt(0)) + value.slice(1)
    }

    const encodingInfo = state.attentionEncodeSurroundingInfo
    state.attentionEncodeSurroundingInfo = undefined
    encodeAfter = undefined

    // If we have to encode the first character before the current node and
    // it’s still the same character,
    // encode it.
    if (encodingInfo) {
      if (
        results.length > 0 &&
        encodingInfo.before &&
        before === results[results.length - 1].slice(-1)
      ) {
        results[results.length - 1] =
          results[results.length - 1].slice(0, -1) +
          encodeCharacterReference(before.charCodeAt(0))
      }

      if (encodingInfo.after) encodeAfter = after
    }

    tracker.move(value)
    results.push(value)
    before = value.slice(-1)
  }

  indexStack.pop()

  return results.join('')
}
/**
 * Encode a code point as a character reference.
 *
 * @param {number} code
 *   Code point to encode.
 * @returns {string}
 *   Encoded character reference.
 */
export function encodeCharacterReference(code) {
  return '&#x' + code.toString(16).toUpperCase() + ';'
}
/**
 * @import {EncodeSides} from '../types.js'
 */

import {classifyCharacter} from 'micromark-util-classify-character'

/**
 * Check whether to encode (as a character reference) the characters
 * surrounding an attention run.
 *
 * Which characters are around an attention run influence whether it works or
 * not.
 *
 * See <https://github.com/orgs/syntax-tree/discussions/60> for more info.
 * See this markdown in a particular renderer to see what works:
 *
 * ```markdown
 * |                         | A (letter inside) | B (punctuation inside) | C (whitespace inside) | D (nothing inside) |
 * | ----------------------- | ----------------- | ---------------------- | --------------------- | ------------------ |
 * | 1 (letter outside)      | x*y*z             | x*.*z                  | x* *z                 | x**z               |
 * | 2 (punctuation outside) | .*y*.             | .*.*.                  | .* *.                 | .**.               |
 * | 3 (whitespace outside)  | x *y* z           | x *.* z                | x * * z               | x ** z             |
 * | 4 (nothing outside)     | *x*               | *.*                    | * *                   | **                 |
 * ```
 *
 * @param {number} outside
 *   Code point on the outer side of the run.
 * @param {number} inside
 *   Code point on the inner side of the run.
 * @param {'*' | '_'} marker
 *   Marker of the run.
 *   Underscores are handled more strictly (they form less often) than
 *   asterisks.
 * @returns {EncodeSides}
 *   Whether to encode characters.
 */
// Important: punctuation must never be encoded.
// Punctuation is solely used by markdown constructs.
// And by encoding itself.
// Encoding them will break constructs or double encode things.
export function encodeInfo(outside, inside, marker) {
  const outsideKind = classifyCharacter(outside)
  const insideKind = classifyCharacter(inside)

  // Letter outside:
  if (outsideKind === undefined) {
    return insideKind === undefined
      ? // Letter inside:
        // we have to encode *both* letters for `_` as it is looser.
        // it already forms for `*` (and GFMs `~`).
        marker === '_'
        ? {inside: true, outside: true}
        : {inside: false, outside: false}
      : insideKind === 1
        ? // Whitespace inside: encode both (letter, whitespace).
          {inside: true, outside: true}
        : // Punctuation inside: encode outer (letter)
          {inside: false, outside: true}
  }

  // Whitespace outside:
  if (outsideKind === 1) {
    return insideKind === undefined
      ? // Letter inside: already forms.
        {inside: false, outside: false}
      : insideKind === 1
        ? // Whitespace inside: encode both (whitespace).
          {inside: true, outside: true}
        : // Punctuation inside: already forms.
          {inside: false, outside: false}
  }

  // Punctuation outside:
  return insideKind === undefined
    ? // Letter inside: already forms.
      {inside: false, outside: false}
    : insideKind === 1
      ? // Whitespace inside: encode inner (whitespace).
        {inside: true, outside: false}
      : // Punctuation inside: already forms.
        {inside: false, outside: false}
}
/**
 * @import {State} from 'mdast-util-to-markdown'
 * @import {Code} from 'mdast'
 */

/**
 * @param {Code} node
 * @param {State} state
 * @returns {boolean}
 */
export function formatCodeAsIndented(node, state) {
  return Boolean(
    state.options.fences === false &&
      node.value &&
      // If there’s no info…
      !node.lang &&
      // And there’s a non-whitespace character…
      /[^ \r\n]/.test(node.value) &&
      // And the value doesn’t start or end in a blank…
      !/^[\t ]*(?:[\r\n]|$)|(?:^|[\r\n])[\t ]*$/.test(node.value)
  )
}
/**
 * @import {State} from 'mdast-util-to-markdown'
 * @import {Heading} from 'mdast'
 */

import {EXIT, visit} from 'unist-util-visit'
import {toString} from 'mdast-util-to-string'

/**
 * @param {Heading} node
 * @param {State} state
 * @returns {boolean}
 */
export function formatHeadingAsSetext(node, state) {
  let literalWithBreak = false

  // Look for literals with a line break.
  // Note that this also
  visit(node, function (node) {
    if (
      ('value' in node && /\r?\n|\r/.test(node.value)) ||
      node.type === 'break'
    ) {
      literalWithBreak = true
      return EXIT
    }
  })

  return Boolean(
    (!node.depth || node.depth < 3) &&
      toString(node) &&
      (state.options.setext || literalWithBreak)
  )
}
/**
 * @import {State} from 'mdast-util-to-markdown'
 * @import {Link} from 'mdast'
 */

import {toString} from 'mdast-util-to-string'

/**
 * @param {Link} node
 * @param {State} state
 * @returns {boolean}
 */
export function formatLinkAsAutolink(node, state) {
  const raw = toString(node)

  return Boolean(
    !state.options.resourceLink &&
      // If there’s a url…
      node.url &&
      // And there’s a no title…
      !node.title &&
      // And the content of `node` is a single text node…
      node.children &&
      node.children.length === 1 &&
      node.children[0].type === 'text' &&
      // And if the url is the same as the content…
      (raw === node.url || 'mailto:' + raw === node.url) &&
      // And that starts w/ a protocol…
      /^[a-z][a-z+.-]+:/i.test(node.url) &&
      // And that doesn’t contain ASCII control codes (character escapes and
      // references don’t work), space, or angle brackets…
      !/[\0- <>\u007F]/.test(node.url)
  )
}
/**
 * @import {IndentLines} from '../types.js'
 */

const eol = /\r?\n|\r/g

/**
 * @type {IndentLines}
 */
export function indentLines(value, map) {
  /** @type {Array<string>} */
  const result = []
  let start = 0
  let line = 0
  /** @type {RegExpExecArray | null} */
  let match

  while ((match = eol.exec(value))) {
    one(value.slice(start, match.index))
    result.push(match[0])
    start = match.index + match[0].length
    line++
  }

  one(value.slice(start))

  return result.join('')

  /**
   * @param {string} value
   */
  function one(value) {
    result.push(map(value, line, !value))
  }
}
/**
 * @import {ConstructName, Unsafe} from 'mdast-util-to-markdown'
 */

/**
 * @param {Array<ConstructName>} stack
 * @param {Unsafe} pattern
 * @returns {boolean}
 */
export function patternInScope(stack, pattern) {
  return (
    listInScope(stack, pattern.inConstruct, true) &&
    !listInScope(stack, pattern.notInConstruct, false)
  )
}

/**
 * @param {Array<ConstructName>} stack
 * @param {Unsafe['inConstruct']} list
 * @param {boolean} none
 * @returns {boolean}
 */
function listInScope(stack, list, none) {
  if (typeof list === 'string') {
    list = [list]
  }

  if (!list || list.length === 0) {
    return none
  }

  let index = -1

  while (++index < list.length) {
    if (stack.includes(list[index])) {
      return true
    }
  }

  return false
}
/**
 * @import {SafeConfig, State} from 'mdast-util-to-markdown'
 */

import {encodeCharacterReference} from './encode-character-reference.js'
import {patternInScope} from './pattern-in-scope.js'

/**
 * Make a string safe for embedding in markdown constructs.
 *
 * In markdown, almost all punctuation characters can, in certain cases,
 * result in something.
 * Whether they do is highly subjective to where they happen and in what
 * they happen.
 *
 * To solve this, `mdast-util-to-markdown` tracks:
 *
 * * Characters before and after something;
 * * What “constructs” we are in.
 *
 * This information is then used by this function to escape or encode
 * special characters.
 *
 * @param {State} state
 *   Info passed around about the current state.
 * @param {string | null | undefined} input
 *   Raw value to make safe.
 * @param {SafeConfig} config
 *   Configuration.
 * @returns {string}
 *   Serialized markdown safe for embedding.
 */
export function safe(state, input, config) {
  const value = (config.before || '') + (input || '') + (config.after || '')
  /** @type {Array<number>} */
  const positions = []
  /** @type {Array<string>} */
  const result = []
  /** @type {Record<number, {before: boolean, after: boolean}>} */
  const infos = {}
  let index = -1

  while (++index < state.unsafe.length) {
    const pattern = state.unsafe[index]

    if (!patternInScope(state.stack, pattern)) {
      continue
    }

    const expression = state.compilePattern(pattern)
    /** @type {RegExpExecArray | null} */
    let match

    while ((match = expression.exec(value))) {
      const before = 'before' in pattern || Boolean(pattern.atBreak)
      const after = 'after' in pattern
      const position = match.index + (before ? match[1].length : 0)

      if (positions.includes(position)) {
        if (infos[position].before && !before) {
          infos[position].before = false
        }

        if (infos[position].after && !after) {
          infos[position].after = false
        }
      } else {
        positions.push(position)
        infos[position] = {before, after}
      }
    }
  }

  positions.sort(numerical)

  let start = config.before ? config.before.length : 0
  const end = value.length - (config.after ? config.after.length : 0)
  index = -1

  while (++index < positions.length) {
    const position = positions[index]

    // Character before or after matched:
    if (position < start || position >= end) {
      continue
    }

    // If this character is supposed to be escaped because it has a condition on
    // the next character, and the next character is definitly being escaped,
    // then skip this escape.
    if (
      (position + 1 < end &&
        positions[index + 1] === position + 1 &&
        infos[position].after &&
        !infos[position + 1].before &&
        !infos[position + 1].after) ||
      (positions[index - 1] === position - 1 &&
        infos[position].before &&
        !infos[position - 1].before &&
        !infos[position - 1].after)
    ) {
      continue
    }

    if (start !== position) {
      // If we have to use a character reference, an ampersand would be more
      // correct, but as backslashes only care about punctuation, either will
      // do the trick
      result.push(escapeBackslashes(value.slice(start, position), '\\'))
    }

    start = position

    if (
      /[!-/:-@[-`{-~]/.test(value.charAt(position)) &&
      (!config.encode || !config.encode.includes(value.charAt(position)))
    ) {
      // Character escape.
      result.push('\\')
    } else {
      // Character reference.
      result.push(encodeCharacterReference(value.charCodeAt(position)))
      start++
    }
  }

  result.push(escapeBackslashes(value.slice(start, end), config.after))

  return result.join('')
}

/**
 * @param {number} a
 * @param {number} b
 * @returns {number}
 */
function numerical(a, b) {
  return a - b
}

/**
 * @param {string} value
 * @param {string} after
 * @returns {string}
 */
function escapeBackslashes(value, after) {
  const expression = /\\(?=[!-/:-@[-`{-~])/g
  /** @type {Array<number>} */
  const positions = []
  /** @type {Array<string>} */
  const results = []
  const whole = value + after
  let index = -1
  let start = 0
  /** @type {RegExpExecArray | null} */
  let match

  while ((match = expression.exec(whole))) {
    positions.push(match.index)
  }

  while (++index < positions.length) {
    if (start !== positions[index]) {
      results.push(value.slice(start, positions[index]))
    }

    results.push('\\')
    start = positions[index]
  }

  results.push(value.slice(start))

  return results.join('')
}
/**
 * @import {CreateTracker, TrackCurrent, TrackMove, TrackShift} from '../types.js'
 */

/**
 * Track positional info in the output.
 *
 * @type {CreateTracker}
 */
export function track(config) {
  // Defaults are used to prevent crashes when older utilities somehow activate
  // this code.
  /* c8 ignore next 5 */
  const options = config || {}
  const now = options.now || {}
  let lineShift = options.lineShift || 0
  let line = now.line || 1
  let column = now.column || 1

  return {move, current, shift}

  /**
   * Get the current tracked info.
   *
   * @type {TrackCurrent}
   */
  function current() {
    return {now: {line, column}, lineShift}
  }

  /**
   * Define an increased line shift (the typical indent for lines).
   *
   * @type {TrackShift}
   */
  function shift(value) {
    lineShift += value
  }

  /**
   * Move past some generated markdown.
   *
   * @type {TrackMove}
   */
  function move(input) {
    // eslint-disable-next-line unicorn/prefer-default-parameters
    const value = input || ''
    const chunks = value.split(/\r?\n|\r/g)
    const tail = chunks[chunks.length - 1]
    line += chunks.length - 1
    column =
      chunks.length === 1 ? column + tail.length : 1 + tail.length + lineShift
    return value
  }
}
{
  "author": "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)",
  "bugs": "https://github.com/syntax-tree/mdast-util-to-markdown/issues",
  "contributors": [
    "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)"
  ],
  "dependencies": {
    "@types/mdast": "^4.0.0",
    "@types/unist": "^3.0.0",
    "longest-streak": "^3.0.0",
    "mdast-util-phrasing": "^4.0.0",
    "mdast-util-to-string": "^4.0.0",
    "micromark-util-classify-character": "^2.0.0",
    "micromark-util-decode-string": "^2.0.0",
    "unist-util-visit": "^5.0.0",
    "zwitch": "^2.0.0"
  },
  "description": "mdast utility to serialize markdown",
  "devDependencies": {
    "@types/node": "^22.0.0",
    "c8": "^10.0.0",
    "mdast-util-from-markdown": "^2.0.0",
    "prettier": "^3.0.0",
    "remark-cli": "^12.0.0",
    "remark-preset-wooorm": "^10.0.0",
    "type-coverage": "^2.0.0",
    "typescript": "^5.0.0",
    "unist-util-remove-position": "^5.0.0",
    "xo": "^0.59.0"
  },
  "exports": "./index.js",
  "files": [
    "lib/",
    "index.d.ts",
    "index.js"
  ],
  "funding": {
    "type": "opencollective",
    "url": "https://opencollective.com/unified"
  },
  "keywords": [
    "ast",
    "compile",
    "markdown",
    "markup",
    "mdast-util",
    "mdast",
    "serialize",
    "stringify",
    "syntax",
    "tree",
    "unist",
    "utility",
    "util"
  ],
  "license": "MIT",
  "name": "mdast-util-to-markdown",
  "prettier": {
    "bracketSpacing": false,
    "semi": false,
    "singleQuote": true,
    "tabWidth": 2,
    "trailingComma": "none",
    "useTabs": false
  },
  "remarkConfig": {
    "plugins": [
      "remark-preset-wooorm"
    ]
  },
  "repository": "syntax-tree/mdast-util-to-markdown",
  "version": "2.1.2",
  "scripts": {
    "prepack": "npm run build && npm run format",
    "build": "tsc --build --clean && tsc --build && type-coverage",
    "format": "remark . -qfo && prettier . -w --log-level warn && xo --fix",
    "test-api": "node --conditions development test/index.js",
    "test-coverage": "c8 --100 --reporter lcov npm run test-api",
    "test": "npm run build && npm run format && npm run test-coverage"
  },
  "sideEffects": false,
  "typeCoverage": {
    "atLeast": 100,
    "ignoreFiles": [
      "lib/types.d.ts"
    ],
    "strict": true
  },
  "type": "module",
  "xo": {
    "overrides": [
      {
        "files": [
          "**/*.d.ts"
        ],
        "rules": {
          "@typescript-eslint/array-type": [
            "error",
            {
              "default": "generic"
            }
          ],
          "@typescript-eslint/ban-types": [
            "error",
            {
              "extendDefaults": true
            }
          ],
          "@typescript-eslint/consistent-type-definitions": [
            "error",
            "interface"
          ]
        }
      },
      {
        "files": [
          "test/**/*.js"
        ],
        "rules": {
          "max-depth": "off",
          "no-await-in-loop": "off"
        }
      }
    ],
    "prettier": true,
    "rules": {
      "complexity": "off",
      "unicorn/prefer-at": "off",
      "unicorn/prefer-code-point": "off"
    }
  }
}
/**
 * @typedef {import('./lib/index.js').Options} Options
 */

export {toString} from './lib/index.js'
/**
 * @typedef {import('mdast').Nodes} Nodes
 *
 * @typedef Options
 *   Configuration (optional).
 * @property {boolean | null | undefined} [includeImageAlt=true]
 *   Whether to use `alt` for `image`s (default: `true`).
 * @property {boolean | null | undefined} [includeHtml=true]
 *   Whether to use `value` of HTML (default: `true`).
 */

/** @type {Options} */
const emptyOptions = {}

/**
 * Get the text content of a node or list of nodes.
 *
 * Prefers the node’s plain-text fields, otherwise serializes its children,
 * and if the given value is an array, serialize the nodes in it.
 *
 * @param {unknown} [value]
 *   Thing to serialize, typically `Node`.
 * @param {Options | null | undefined} [options]
 *   Configuration (optional).
 * @returns {string}
 *   Serialized `value`.
 */
export function toString(value, options) {
  const settings = options || emptyOptions
  const includeImageAlt =
    typeof settings.includeImageAlt === 'boolean'
      ? settings.includeImageAlt
      : true
  const includeHtml =
    typeof settings.includeHtml === 'boolean' ? settings.includeHtml : true

  return one(value, includeImageAlt, includeHtml)
}

/**
 * One node or several nodes.
 *
 * @param {unknown} value
 *   Thing to serialize.
 * @param {boolean} includeImageAlt
 *   Include image `alt`s.
 * @param {boolean} includeHtml
 *   Include HTML.
 * @returns {string}
 *   Serialized node.
 */
function one(value, includeImageAlt, includeHtml) {
  if (node(value)) {
    if ('value' in value) {
      return value.type === 'html' && !includeHtml ? '' : value.value
    }

    if (includeImageAlt && 'alt' in value && value.alt) {
      return value.alt
    }

    if ('children' in value) {
      return all(value.children, includeImageAlt, includeHtml)
    }
  }

  if (Array.isArray(value)) {
    return all(value, includeImageAlt, includeHtml)
  }

  return ''
}

/**
 * Serialize a list of nodes.
 *
 * @param {Array<unknown>} values
 *   Thing to serialize.
 * @param {boolean} includeImageAlt
 *   Include image `alt`s.
 * @param {boolean} includeHtml
 *   Include HTML.
 * @returns {string}
 *   Serialized nodes.
 */
function all(values, includeImageAlt, includeHtml) {
  /** @type {Array<string>} */
  const result = []
  let index = -1

  while (++index < values.length) {
    result[index] = one(values[index], includeImageAlt, includeHtml)
  }

  return result.join('')
}

/**
 * Check if `value` looks like a node.
 *
 * @param {unknown} value
 *   Thing.
 * @returns {value is Nodes}
 *   Whether `value` is a node.
 */
function node(value) {
  return Boolean(value && typeof value === 'object')
}
{
  "name": "mdast-util-to-string",
  "version": "4.0.0",
  "description": "mdast utility to get the plain text content of a node",
  "license": "MIT",
  "keywords": [
    "unist",
    "mdast",
    "mdast-util",
    "util",
    "utility",
    "markdown",
    "node",
    "string",
    "serialize"
  ],
  "repository": "syntax-tree/mdast-util-to-string",
  "bugs": "https://github.com/syntax-tree/mdast-util-to-string/issues",
  "funding": {
    "type": "opencollective",
    "url": "https://opencollective.com/unified"
  },
  "author": "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)",
  "contributors": [
    "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)"
  ],
  "sideEffects": false,
  "type": "module",
  "exports": "./index.js",
  "files": [
    "lib/",
    "index.d.ts",
    "index.js"
  ],
  "dependencies": {
    "@types/mdast": "^4.0.0"
  },
  "devDependencies": {
    "@types/node": "^20.0.0",
    "c8": "^8.0.0",
    "prettier": "^2.0.0",
    "remark-cli": "^11.0.0",
    "remark-preset-wooorm": "^9.0.0",
    "type-coverage": "^2.0.0",
    "typescript": "^5.0.0",
    "xo": "^0.54.0"
  },
  "scripts": {
    "prepack": "npm run build && npm run format",
    "build": "tsc --build --clean && tsc --build && type-coverage",
    "format": "remark . -qfo && prettier . -w --loglevel warn && xo --fix",
    "test-api": "node --conditions development test.js",
    "test-coverage": "c8 --100 --reporter lcov npm run test-api",
    "test": "npm run build && npm run format && npm run test-coverage"
  },
  "prettier": {
    "bracketSpacing": false,
    "semi": false,
    "singleQuote": true,
    "tabWidth": 2,
    "trailingComma": "none",
    "useTabs": false
  },
  "remarkConfig": {
    "plugins": [
      "remark-preset-wooorm"
    ]
  },
  "typeCoverage": {
    "atLeast": 100,
    "detail": true,
    "ignoreCatch": true,
    "strict": true
  },
  "xo": {
    "prettier": true
  }
}
export { attention } from './lib/attention.js';
export { autolink } from './lib/autolink.js';
export { blankLine } from './lib/blank-line.js';
export { blockQuote } from './lib/block-quote.js';
export { characterEscape } from './lib/character-escape.js';
export { characterReference } from './lib/character-reference.js';
export { codeFenced } from './lib/code-fenced.js';
export { codeIndented } from './lib/code-indented.js';
export { codeText } from './lib/code-text.js';
export { content } from './lib/content.js';
export { definition } from './lib/definition.js';
export { hardBreakEscape } from './lib/hard-break-escape.js';
export { headingAtx } from './lib/heading-atx.js';
export { htmlFlow } from './lib/html-flow.js';
export { htmlText } from './lib/html-text.js';
export { labelEnd } from './lib/label-end.js';
export { labelStartImage } from './lib/label-start-image.js';
export { labelStartLink } from './lib/label-start-link.js';
export { lineEnding } from './lib/line-ending.js';
export { list } from './lib/list.js';
export { setextUnderline } from './lib/setext-underline.js';
export { thematicBreak } from './lib/thematic-break.js';/**
 * @import {
 *   Code,
 *   Construct,
 *   Event,
 *   Point,
 *   Resolver,
 *   State,
 *   TokenizeContext,
 *   Tokenizer,
 *   Token
 * } from 'micromark-util-types'
 */

import { push, splice } from 'micromark-util-chunked';
import { classifyCharacter } from 'micromark-util-classify-character';
import { resolveAll } from 'micromark-util-resolve-all';
/** @type {Construct} */
export const attention = {
  name: 'attention',
  resolveAll: resolveAllAttention,
  tokenize: tokenizeAttention
};

/**
 * Take all events and resolve attention to emphasis or strong.
 *
 * @type {Resolver}
 */
// eslint-disable-next-line complexity
function resolveAllAttention(events, context) {
  let index = -1;
  /** @type {number} */
  let open;
  /** @type {Token} */
  let group;
  /** @type {Token} */
  let text;
  /** @type {Token} */
  let openingSequence;
  /** @type {Token} */
  let closingSequence;
  /** @type {number} */
  let use;
  /** @type {Array<Event>} */
  let nextEvents;
  /** @type {number} */
  let offset;

  // Walk through all events.
  //
  // Note: performance of this is fine on an mb of normal markdown, but it’s
  // a bottleneck for malicious stuff.
  while (++index < events.length) {
    // Find a token that can close.
    if (events[index][0] === 'enter' && events[index][1].type === 'attentionSequence' && events[index][1]._close) {
      open = index;

      // Now walk back to find an opener.
      while (open--) {
        // Find a token that can open the closer.
        if (events[open][0] === 'exit' && events[open][1].type === 'attentionSequence' && events[open][1]._open &&
        // If the markers are the same:
        context.sliceSerialize(events[open][1]).charCodeAt(0) === context.sliceSerialize(events[index][1]).charCodeAt(0)) {
          // If the opening can close or the closing can open,
          // and the close size *is not* a multiple of three,
          // but the sum of the opening and closing size *is* multiple of three,
          // then don’t match.
          if ((events[open][1]._close || events[index][1]._open) && (events[index][1].end.offset - events[index][1].start.offset) % 3 && !((events[open][1].end.offset - events[open][1].start.offset + events[index][1].end.offset - events[index][1].start.offset) % 3)) {
            continue;
          }

          // Number of markers to use from the sequence.
          use = events[open][1].end.offset - events[open][1].start.offset > 1 && events[index][1].end.offset - events[index][1].start.offset > 1 ? 2 : 1;
          const start = {
            ...events[open][1].end
          };
          const end = {
            ...events[index][1].start
          };
          movePoint(start, -use);
          movePoint(end, use);
          openingSequence = {
            type: use > 1 ? "strongSequence" : "emphasisSequence",
            start,
            end: {
              ...events[open][1].end
            }
          };
          closingSequence = {
            type: use > 1 ? "strongSequence" : "emphasisSequence",
            start: {
              ...events[index][1].start
            },
            end
          };
          text = {
            type: use > 1 ? "strongText" : "emphasisText",
            start: {
              ...events[open][1].end
            },
            end: {
              ...events[index][1].start
            }
          };
          group = {
            type: use > 1 ? "strong" : "emphasis",
            start: {
              ...openingSequence.start
            },
            end: {
              ...closingSequence.end
            }
          };
          events[open][1].end = {
            ...openingSequence.start
          };
          events[index][1].start = {
            ...closingSequence.end
          };
          nextEvents = [];

          // If there are more markers in the opening, add them before.
          if (events[open][1].end.offset - events[open][1].start.offset) {
            nextEvents = push(nextEvents, [['enter', events[open][1], context], ['exit', events[open][1], context]]);
          }

          // Opening.
          nextEvents = push(nextEvents, [['enter', group, context], ['enter', openingSequence, context], ['exit', openingSequence, context], ['enter', text, context]]);

          // Always populated by defaults.

          // Between.
          nextEvents = push(nextEvents, resolveAll(context.parser.constructs.insideSpan.null, events.slice(open + 1, index), context));

          // Closing.
          nextEvents = push(nextEvents, [['exit', text, context], ['enter', closingSequence, context], ['exit', closingSequence, context], ['exit', group, context]]);

          // If there are more markers in the closing, add them after.
          if (events[index][1].end.offset - events[index][1].start.offset) {
            offset = 2;
            nextEvents = push(nextEvents, [['enter', events[index][1], context], ['exit', events[index][1], context]]);
          } else {
            offset = 0;
          }
          splice(events, open - 1, index - open + 3, nextEvents);
          index = open + nextEvents.length - offset - 2;
          break;
        }
      }
    }
  }

  // Remove remaining sequences.
  index = -1;
  while (++index < events.length) {
    if (events[index][1].type === 'attentionSequence') {
      events[index][1].type = 'data';
    }
  }
  return events;
}

/**
 * @this {TokenizeContext}
 *   Context.
 * @type {Tokenizer}
 */
function tokenizeAttention(effects, ok) {
  const attentionMarkers = this.parser.constructs.attentionMarkers.null;
  const previous = this.previous;
  const before = classifyCharacter(previous);

  /** @type {NonNullable<Code>} */
  let marker;
  return start;

  /**
   * Before a sequence.
   *
   * ```markdown
   * > | **
   *     ^
   * ```
   *
   * @type {State}
   */
  function start(code) {
    marker = code;
    effects.enter('attentionSequence');
    return inside(code);
  }

  /**
   * In a sequence.
   *
   * ```markdown
   * > | **
   *     ^^
   * ```
   *
   * @type {State}
   */
  function inside(code) {
    if (code === marker) {
      effects.consume(code);
      return inside;
    }
    const token = effects.exit('attentionSequence');

    // To do: next major: move this to resolver, just like `markdown-rs`.
    const after = classifyCharacter(code);

    // Always populated by defaults.

    const open = !after || after === 2 && before || attentionMarkers.includes(code);
    const close = !before || before === 2 && after || attentionMarkers.includes(previous);
    token._open = Boolean(marker === 42 ? open : open && (before || !close));
    token._close = Boolean(marker === 42 ? close : close && (after || !open));
    return ok(code);
  }
}

/**
 * Move a point a bit.
 *
 * Note: `move` only works inside lines! It’s not possible to move past other
 * chunks (replacement characters, tabs, or line endings).
 *
 * @param {Point} point
 *   Point.
 * @param {number} offset
 *   Amount to move.
 * @returns {undefined}
 *   Nothing.
 */
function movePoint(point, offset) {
  point.column += offset;
  point.offset += offset;
  point._bufferIndex += offset;
}/**
 * @import {
 *   Construct,
 *   State,
 *   TokenizeContext,
 *   Tokenizer
 * } from 'micromark-util-types'
 */

import { asciiAlphanumeric, asciiAlpha, asciiAtext, asciiControl } from 'micromark-util-character';
/** @type {Construct} */
export const autolink = {
  name: 'autolink',
  tokenize: tokenizeAutolink
};

/**
 * @this {TokenizeContext}
 *   Context.
 * @type {Tokenizer}
 */
function tokenizeAutolink(effects, ok, nok) {
  let size = 0;
  return start;

  /**
   * Start of an autolink.
   *
   * ```markdown
   * > | a<https://example.com>b
   *      ^
   * > | a<user@example.com>b
   *      ^
   * ```
   *
   * @type {State}
   */
  function start(code) {
    effects.enter("autolink");
    effects.enter("autolinkMarker");
    effects.consume(code);
    effects.exit("autolinkMarker");
    effects.enter("autolinkProtocol");
    return open;
  }

  /**
   * After `<`, at protocol or atext.
   *
   * ```markdown
   * > | a<https://example.com>b
   *       ^
   * > | a<user@example.com>b
   *       ^
   * ```
   *
   * @type {State}
   */
  function open(code) {
    if (asciiAlpha(code)) {
      effects.consume(code);
      return schemeOrEmailAtext;
    }
    if (code === 64) {
      return nok(code);
    }
    return emailAtext(code);
  }

  /**
   * At second byte of protocol or atext.
   *
   * ```markdown
   * > | a<https://example.com>b
   *        ^
   * > | a<user@example.com>b
   *        ^
   * ```
   *
   * @type {State}
   */
  function schemeOrEmailAtext(code) {
    // ASCII alphanumeric and `+`, `-`, and `.`.
    if (code === 43 || code === 45 || code === 46 || asciiAlphanumeric(code)) {
      // Count the previous alphabetical from `open` too.
      size = 1;
      return schemeInsideOrEmailAtext(code);
    }
    return emailAtext(code);
  }

  /**
   * In ambiguous protocol or atext.
   *
   * ```markdown
   * > | a<https://example.com>b
   *        ^
   * > | a<user@example.com>b
   *        ^
   * ```
   *
   * @type {State}
   */
  function schemeInsideOrEmailAtext(code) {
    if (code === 58) {
      effects.consume(code);
      size = 0;
      return urlInside;
    }

    // ASCII alphanumeric and `+`, `-`, and `.`.
    if ((code === 43 || code === 45 || code === 46 || asciiAlphanumeric(code)) && size++ < 32) {
      effects.consume(code);
      return schemeInsideOrEmailAtext;
    }
    size = 0;
    return emailAtext(code);
  }

  /**
   * After protocol, in URL.
   *
   * ```markdown
   * > | a<https://example.com>b
   *             ^
   * ```
   *
   * @type {State}
   */
  function urlInside(code) {
    if (code === 62) {
      effects.exit("autolinkProtocol");
      effects.enter("autolinkMarker");
      effects.consume(code);
      effects.exit("autolinkMarker");
      effects.exit("autolink");
      return ok;
    }

    // ASCII control, space, or `<`.
    if (code === null || code === 32 || code === 60 || asciiControl(code)) {
      return nok(code);
    }
    effects.consume(code);
    return urlInside;
  }

  /**
   * In email atext.
   *
   * ```markdown
   * > | a<user.name@example.com>b
   *              ^
   * ```
   *
   * @type {State}
   */
  function emailAtext(code) {
    if (code === 64) {
      effects.consume(code);
      return emailAtSignOrDot;
    }
    if (asciiAtext(code)) {
      effects.consume(code);
      return emailAtext;
    }
    return nok(code);
  }

  /**
   * In label, after at-sign or dot.
   *
   * ```markdown
   * > | a<user.name@example.com>b
   *                 ^       ^
   * ```
   *
   * @type {State}
   */
  function emailAtSignOrDot(code) {
    return asciiAlphanumeric(code) ? emailLabel(code) : nok(code);
  }

  /**
   * In label, where `.` and `>` are allowed.
   *
   * ```markdown
   * > | a<user.name@example.com>b
   *                   ^
   * ```
   *
   * @type {State}
   */
  function emailLabel(code) {
    if (code === 46) {
      effects.consume(code);
      size = 0;
      return emailAtSignOrDot;
    }
    if (code === 62) {
      // Exit, then change the token type.
      effects.exit("autolinkProtocol").type = "autolinkEmail";
      effects.enter("autolinkMarker");
      effects.consume(code);
      effects.exit("autolinkMarker");
      effects.exit("autolink");
      return ok;
    }
    return emailValue(code);
  }

  /**
   * In label, where `.` and `>` are *not* allowed.
   *
   * Though, this is also used in `emailLabel` to parse other values.
   *
   * ```markdown
   * > | a<user.name@ex-ample.com>b
   *                    ^
   * ```
   *
   * @type {State}
   */
  function emailValue(code) {
    // ASCII alphanumeric or `-`.
    if ((code === 45 || asciiAlphanumeric(code)) && size++ < 63) {
      const next = code === 45 ? emailValue : emailLabel;
      effects.consume(code);
      return next;
    }
    return nok(code);
  }
}/**
 * @import {
 *   Construct,
 *   State,
 *   TokenizeContext,
 *   Tokenizer
 * } from 'micromark-util-types'
 */

import { factorySpace } from 'micromark-factory-space';
import { markdownLineEnding, markdownSpace } from 'micromark-util-character';
/** @type {Construct} */
export const blankLine = {
  partial: true,
  tokenize: tokenizeBlankLine
};

/**
 * @this {TokenizeContext}
 *   Context.
 * @type {Tokenizer}
 */
function tokenizeBlankLine(effects, ok, nok) {
  return start;

  /**
   * Start of blank line.
   *
   * > 👉 **Note**: `␠` represents a space character.
   *
   * ```markdown
   * > | ␠␠␊
   *     ^
   * > | ␊
   *     ^
   * ```
   *
   * @type {State}
   */
  function start(code) {
    return markdownSpace(code) ? factorySpace(effects, after, "linePrefix")(code) : after(code);
  }

  /**
   * At eof/eol, after optional whitespace.
   *
   * > 👉 **Note**: `␠` represents a space character.
   *
   * ```markdown
   * > | ␠␠␊
   *       ^
   * > | ␊
   *     ^
   * ```
   *
   * @type {State}
   */
  function after(code) {
    return code === null || markdownLineEnding(code) ? ok(code) : nok(code);
  }
}/**
 * @import {
 *   Construct,
 *   Exiter,
 *   State,
 *   TokenizeContext,
 *   Tokenizer
 * } from 'micromark-util-types'
 */

import { factorySpace } from 'micromark-factory-space';
import { markdownSpace } from 'micromark-util-character';
/** @type {Construct} */
export const blockQuote = {
  continuation: {
    tokenize: tokenizeBlockQuoteContinuation
  },
  exit,
  name: 'blockQuote',
  tokenize: tokenizeBlockQuoteStart
};

/**
 * @this {TokenizeContext}
 *   Context.
 * @type {Tokenizer}
 */
function tokenizeBlockQuoteStart(effects, ok, nok) {
  const self = this;
  return start;

  /**
   * Start of block quote.
   *
   * ```markdown
   * > | > a
   *     ^
   * ```
   *
   * @type {State}
   */
  function start(code) {
    if (code === 62) {
      const state = self.containerState;
      if (!state.open) {
        effects.enter("blockQuote", {
          _container: true
        });
        state.open = true;
      }
      effects.enter("blockQuotePrefix");
      effects.enter("blockQuoteMarker");
      effects.consume(code);
      effects.exit("blockQuoteMarker");
      return after;
    }
    return nok(code);
  }

  /**
   * After `>`, before optional whitespace.
   *
   * ```markdown
   * > | > a
   *      ^
   * ```
   *
   * @type {State}
   */
  function after(code) {
    if (markdownSpace(code)) {
      effects.enter("blockQuotePrefixWhitespace");
      effects.consume(code);
      effects.exit("blockQuotePrefixWhitespace");
      effects.exit("blockQuotePrefix");
      return ok;
    }
    effects.exit("blockQuotePrefix");
    return ok(code);
  }
}

/**
 * Start of block quote continuation.
 *
 * ```markdown
 *   | > a
 * > | > b
 *     ^
 * ```
 *
 * @this {TokenizeContext}
 *   Context.
 * @type {Tokenizer}
 */
function tokenizeBlockQuoteContinuation(effects, ok, nok) {
  const self = this;
  return contStart;

  /**
   * Start of block quote continuation.
   *
   * Also used to parse the first block quote opening.
   *
   * ```markdown
   *   | > a
   * > | > b
   *     ^
   * ```
   *
   * @type {State}
   */
  function contStart(code) {
    if (markdownSpace(code)) {
      // Always populated by defaults.

      return factorySpace(effects, contBefore, "linePrefix", self.parser.constructs.disable.null.includes('codeIndented') ? undefined : 4)(code);
    }
    return contBefore(code);
  }

  /**
   * At `>`, after optional whitespace.
   *
   * Also used to parse the first block quote opening.
   *
   * ```markdown
   *   | > a
   * > | > b
   *     ^
   * ```
   *
   * @type {State}
   */
  function contBefore(code) {
    return effects.attempt(blockQuote, ok, nok)(code);
  }
}

/** @type {Exiter} */
function exit(effects) {
  effects.exit("blockQuote");
}/**
 * @import {
 *   Construct,
 *   State,
 *   TokenizeContext,
 *   Tokenizer
 * } from 'micromark-util-types'
 */

import { asciiPunctuation } from 'micromark-util-character';
/** @type {Construct} */
export const characterEscape = {
  name: 'characterEscape',
  tokenize: tokenizeCharacterEscape
};

/**
 * @this {TokenizeContext}
 *   Context.
 * @type {Tokenizer}
 */
function tokenizeCharacterEscape(effects, ok, nok) {
  return start;

  /**
   * Start of character escape.
   *
   * ```markdown
   * > | a\*b
   *      ^
   * ```
   *
   * @type {State}
   */
  function start(code) {
    effects.enter("characterEscape");
    effects.enter("escapeMarker");
    effects.consume(code);
    effects.exit("escapeMarker");
    return inside;
  }

  /**
   * After `\`, at punctuation.
   *
   * ```markdown
   * > | a\*b
   *       ^
   * ```
   *
   * @type {State}
   */
  function inside(code) {
    // ASCII punctuation.
    if (asciiPunctuation(code)) {
      effects.enter("characterEscapeValue");
      effects.consume(code);
      effects.exit("characterEscapeValue");
      effects.exit("characterEscape");
      return ok;
    }
    return nok(code);
  }
}/**
 * @import {
 *   Code,
 *   Construct,
 *   State,
 *   TokenizeContext,
 *   Tokenizer
 * } from 'micromark-util-types'
 */

import { decodeNamedCharacterReference } from 'decode-named-character-reference';
import { asciiAlphanumeric, asciiDigit, asciiHexDigit } from 'micromark-util-character';
/** @type {Construct} */
export const characterReference = {
  name: 'characterReference',
  tokenize: tokenizeCharacterReference
};

/**
 * @this {TokenizeContext}
 *   Context.
 * @type {Tokenizer}
 */
function tokenizeCharacterReference(effects, ok, nok) {
  const self = this;
  let size = 0;
  /** @type {number} */
  let max;
  /** @type {(code: Code) => boolean} */
  let test;
  return start;

  /**
   * Start of character reference.
   *
   * ```markdown
   * > | a&amp;b
   *      ^
   * > | a&#123;b
   *      ^
   * > | a&#x9;b
   *      ^
   * ```
   *
   * @type {State}
   */
  function start(code) {
    effects.enter("characterReference");
    effects.enter("characterReferenceMarker");
    effects.consume(code);
    effects.exit("characterReferenceMarker");
    return open;
  }

  /**
   * After `&`, at `#` for numeric references or alphanumeric for named
   * references.
   *
   * ```markdown
   * > | a&amp;b
   *       ^
   * > | a&#123;b
   *       ^
   * > | a&#x9;b
   *       ^
   * ```
   *
   * @type {State}
   */
  function open(code) {
    if (code === 35) {
      effects.enter("characterReferenceMarkerNumeric");
      effects.consume(code);
      effects.exit("characterReferenceMarkerNumeric");
      return numeric;
    }
    effects.enter("characterReferenceValue");
    max = 31;
    test = asciiAlphanumeric;
    return value(code);
  }

  /**
   * After `#`, at `x` for hexadecimals or digit for decimals.
   *
   * ```markdown
   * > | a&#123;b
   *        ^
   * > | a&#x9;b
   *        ^
   * ```
   *
   * @type {State}
   */
  function numeric(code) {
    if (code === 88 || code === 120) {
      effects.enter("characterReferenceMarkerHexadecimal");
      effects.consume(code);
      effects.exit("characterReferenceMarkerHexadecimal");
      effects.enter("characterReferenceValue");
      max = 6;
      test = asciiHexDigit;
      return value;
    }
    effects.enter("characterReferenceValue");
    max = 7;
    test = asciiDigit;
    return value(code);
  }

  /**
   * After markers (`&#x`, `&#`, or `&`), in value, before `;`.
   *
   * The character reference kind defines what and how many characters are
   * allowed.
   *
   * ```markdown
   * > | a&amp;b
   *       ^^^
   * > | a&#123;b
   *        ^^^
   * > | a&#x9;b
   *         ^
   * ```
   *
   * @type {State}
   */
  function value(code) {
    if (code === 59 && size) {
      const token = effects.exit("characterReferenceValue");
      if (test === asciiAlphanumeric && !decodeNamedCharacterReference(self.sliceSerialize(token))) {
        return nok(code);
      }

      // To do: `markdown-rs` uses a different name:
      // `CharacterReferenceMarkerSemi`.
      effects.enter("characterReferenceMarker");
      effects.consume(code);
      effects.exit("characterReferenceMarker");
      effects.exit("characterReference");
      return ok;
    }
    if (test(code) && size++ < max) {
      effects.consume(code);
      return value;
    }
    return nok(code);
  }
}/**
 * @import {
 *   Code,
 *   Construct,
 *   State,
 *   TokenizeContext,
 *   Tokenizer
 * } from 'micromark-util-types'
 */

import { factorySpace } from 'micromark-factory-space';
import { markdownLineEnding, markdownSpace } from 'micromark-util-character';
/** @type {Construct} */
const nonLazyContinuation = {
  partial: true,
  tokenize: tokenizeNonLazyContinuation
};

/** @type {Construct} */
export const codeFenced = {
  concrete: true,
  name: 'codeFenced',
  tokenize: tokenizeCodeFenced
};

/**
 * @this {TokenizeContext}
 *   Context.
 * @type {Tokenizer}
 */
function tokenizeCodeFenced(effects, ok, nok) {
  const self = this;
  /** @type {Construct} */
  const closeStart = {
    partial: true,
    tokenize: tokenizeCloseStart
  };
  let initialPrefix = 0;
  let sizeOpen = 0;
  /** @type {NonNullable<Code>} */
  let marker;
  return start;

  /**
   * Start of code.
   *
   * ```markdown
   * > | ~~~js
   *     ^
   *   | alert(1)
   *   | ~~~
   * ```
   *
   * @type {State}
   */
  function start(code) {
    // To do: parse whitespace like `markdown-rs`.
    return beforeSequenceOpen(code);
  }

  /**
   * In opening fence, after prefix, at sequence.
   *
   * ```markdown
   * > | ~~~js
   *     ^
   *   | alert(1)
   *   | ~~~
   * ```
   *
   * @type {State}
   */
  function beforeSequenceOpen(code) {
    const tail = self.events[self.events.length - 1];
    initialPrefix = tail && tail[1].type === "linePrefix" ? tail[2].sliceSerialize(tail[1], true).length : 0;
    marker = code;
    effects.enter("codeFenced");
    effects.enter("codeFencedFence");
    effects.enter("codeFencedFenceSequence");
    return sequenceOpen(code);
  }

  /**
   * In opening fence sequence.
   *
   * ```markdown
   * > | ~~~js
   *      ^
   *   | alert(1)
   *   | ~~~
   * ```
   *
   * @type {State}
   */
  function sequenceOpen(code) {
    if (code === marker) {
      sizeOpen++;
      effects.consume(code);
      return sequenceOpen;
    }
    if (sizeOpen < 3) {
      return nok(code);
    }
    effects.exit("codeFencedFenceSequence");
    return markdownSpace(code) ? factorySpace(effects, infoBefore, "whitespace")(code) : infoBefore(code);
  }

  /**
   * In opening fence, after the sequence (and optional whitespace), before info.
   *
   * ```markdown
   * > | ~~~js
   *        ^
   *   | alert(1)
   *   | ~~~
   * ```
   *
   * @type {State}
   */
  function infoBefore(code) {
    if (code === null || markdownLineEnding(code)) {
      effects.exit("codeFencedFence");
      return self.interrupt ? ok(code) : effects.check(nonLazyContinuation, atNonLazyBreak, after)(code);
    }
    effects.enter("codeFencedFenceInfo");
    effects.enter("chunkString", {
      contentType: "string"
    });
    return info(code);
  }

  /**
   * In info.
   *
   * ```markdown
   * > | ~~~js
   *        ^
   *   | alert(1)
   *   | ~~~
   * ```
   *
   * @type {State}
   */
  function info(code) {
    if (code === null || markdownLineEnding(code)) {
      effects.exit("chunkString");
      effects.exit("codeFencedFenceInfo");
      return infoBefore(code);
    }
    if (markdownSpace(code)) {
      effects.exit("chunkString");
      effects.exit("codeFencedFenceInfo");
      return factorySpace(effects, metaBefore, "whitespace")(code);
    }
    if (code === 96 && code === marker) {
      return nok(code);
    }
    effects.consume(code);
    return info;
  }

  /**
   * In opening fence, after info and whitespace, before meta.
   *
   * ```markdown
   * > | ~~~js eval
   *           ^
   *   | alert(1)
   *   | ~~~
   * ```
   *
   * @type {State}
   */
  function metaBefore(code) {
    if (code === null || markdownLineEnding(code)) {
      return infoBefore(code);
    }
    effects.enter("codeFencedFenceMeta");
    effects.enter("chunkString", {
      contentType: "string"
    });
    return meta(code);
  }

  /**
   * In meta.
   *
   * ```markdown
   * > | ~~~js eval
   *           ^
   *   | alert(1)
   *   | ~~~
   * ```
   *
   * @type {State}
   */
  function meta(code) {
    if (code === null || markdownLineEnding(code)) {
      effects.exit("chunkString");
      effects.exit("codeFencedFenceMeta");
      return infoBefore(code);
    }
    if (code === 96 && code === marker) {
      return nok(code);
    }
    effects.consume(code);
    return meta;
  }

  /**
   * At eol/eof in code, before a non-lazy closing fence or content.
   *
   * ```markdown
   * > | ~~~js
   *          ^
   * > | alert(1)
   *             ^
   *   | ~~~
   * ```
   *
   * @type {State}
   */
  function atNonLazyBreak(code) {
    return effects.attempt(closeStart, after, contentBefore)(code);
  }

  /**
   * Before code content, not a closing fence, at eol.
   *
   * ```markdown
   *   | ~~~js
   * > | alert(1)
   *             ^
   *   | ~~~
   * ```
   *
   * @type {State}
   */
  function contentBefore(code) {
    effects.enter("lineEnding");
    effects.consume(code);
    effects.exit("lineEnding");
    return contentStart;
  }

  /**
   * Before code content, not a closing fence.
   *
   * ```markdown
   *   | ~~~js
   * > | alert(1)
   *     ^
   *   | ~~~
   * ```
   *
   * @type {State}
   */
  function contentStart(code) {
    return initialPrefix > 0 && markdownSpace(code) ? factorySpace(effects, beforeContentChunk, "linePrefix", initialPrefix + 1)(code) : beforeContentChunk(code);
  }

  /**
   * Before code content, after optional prefix.
   *
   * ```markdown
   *   | ~~~js
   * > | alert(1)
   *     ^
   *   | ~~~
   * ```
   *
   * @type {State}
   */
  function beforeContentChunk(code) {
    if (code === null || markdownLineEnding(code)) {
      return effects.check(nonLazyContinuation, atNonLazyBreak, after)(code);
    }
    effects.enter("codeFlowValue");
    return contentChunk(code);
  }

  /**
   * In code content.
   *
   * ```markdown
   *   | ~~~js
   * > | alert(1)
   *     ^^^^^^^^
   *   | ~~~
   * ```
   *
   * @type {State}
   */
  function contentChunk(code) {
    if (code === null || markdownLineEnding(code)) {
      effects.exit("codeFlowValue");
      return beforeContentChunk(code);
    }
    effects.consume(code);
    return contentChunk;
  }

  /**
   * After code.
   *
   * ```markdown
   *   | ~~~js
   *   | alert(1)
   * > | ~~~
   *        ^
   * ```
   *
   * @type {State}
   */
  function after(code) {
    effects.exit("codeFenced");
    return ok(code);
  }

  /**
   * @this {TokenizeContext}
   *   Context.
   * @type {Tokenizer}
   */
  function tokenizeCloseStart(effects, ok, nok) {
    let size = 0;
    return startBefore;

    /**
     *
     *
     * @type {State}
     */
    function startBefore(code) {
      effects.enter("lineEnding");
      effects.consume(code);
      effects.exit("lineEnding");
      return start;
    }

    /**
     * Before closing fence, at optional whitespace.
     *
     * ```markdown
     *   | ~~~js
     *   | alert(1)
     * > | ~~~
     *     ^
     * ```
     *
     * @type {State}
     */
    function start(code) {
      // Always populated by defaults.

      // To do: `enter` here or in next state?
      effects.enter("codeFencedFence");
      return markdownSpace(code) ? factorySpace(effects, beforeSequenceClose, "linePrefix", self.parser.constructs.disable.null.includes('codeIndented') ? undefined : 4)(code) : beforeSequenceClose(code);
    }

    /**
     * In closing fence, after optional whitespace, at sequence.
     *
     * ```markdown
     *   | ~~~js
     *   | alert(1)
     * > | ~~~
     *     ^
     * ```
     *
     * @type {State}
     */
    function beforeSequenceClose(code) {
      if (code === marker) {
        effects.enter("codeFencedFenceSequence");
        return sequenceClose(code);
      }
      return nok(code);
    }

    /**
     * In closing fence sequence.
     *
     * ```markdown
     *   | ~~~js
     *   | alert(1)
     * > | ~~~
     *     ^
     * ```
     *
     * @type {State}
     */
    function sequenceClose(code) {
      if (code === marker) {
        size++;
        effects.consume(code);
        return sequenceClose;
      }
      if (size >= sizeOpen) {
        effects.exit("codeFencedFenceSequence");
        return markdownSpace(code) ? factorySpace(effects, sequenceCloseAfter, "whitespace")(code) : sequenceCloseAfter(code);
      }
      return nok(code);
    }

    /**
     * After closing fence sequence, after optional whitespace.
     *
     * ```markdown
     *   | ~~~js
     *   | alert(1)
     * > | ~~~
     *        ^
     * ```
     *
     * @type {State}
     */
    function sequenceCloseAfter(code) {
      if (code === null || markdownLineEnding(code)) {
        effects.exit("codeFencedFence");
        return ok(code);
      }
      return nok(code);
    }
  }
}

/**
 * @this {TokenizeContext}
 *   Context.
 * @type {Tokenizer}
 */
function tokenizeNonLazyContinuation(effects, ok, nok) {
  const self = this;
  return start;

  /**
   *
   *
   * @type {State}
   */
  function start(code) {
    if (code === null) {
      return nok(code);
    }
    effects.enter("lineEnding");
    effects.consume(code);
    effects.exit("lineEnding");
    return lineStart;
  }

  /**
   *
   *
   * @type {State}
   */
  function lineStart(code) {
    return self.parser.lazy[self.now().line] ? nok(code) : ok(code);
  }
}/**
 * @import {
 *   Construct,
 *   State,
 *   TokenizeContext,
 *   Tokenizer
 * } from 'micromark-util-types'
 */

import { factorySpace } from 'micromark-factory-space';
import { markdownLineEnding, markdownSpace } from 'micromark-util-character';
/** @type {Construct} */
export const codeIndented = {
  name: 'codeIndented',
  tokenize: tokenizeCodeIndented
};

/** @type {Construct} */
const furtherStart = {
  partial: true,
  tokenize: tokenizeFurtherStart
};

/**
 * @this {TokenizeContext}
 *   Context.
 * @type {Tokenizer}
 */
function tokenizeCodeIndented(effects, ok, nok) {
  const self = this;
  return start;

  /**
   * Start of code (indented).
   *
   * > **Parsing note**: it is not needed to check if this first line is a
   * > filled line (that it has a non-whitespace character), because blank lines
   * > are parsed already, so we never run into that.
   *
   * ```markdown
   * > |     aaa
   *     ^
   * ```
   *
   * @type {State}
   */
  function start(code) {
    // To do: manually check if interrupting like `markdown-rs`.

    effects.enter("codeIndented");
    // To do: use an improved `space_or_tab` function like `markdown-rs`,
    // so that we can drop the next state.
    return factorySpace(effects, afterPrefix, "linePrefix", 4 + 1)(code);
  }

  /**
   * At start, after 1 or 4 spaces.
   *
   * ```markdown
   * > |     aaa
   *         ^
   * ```
   *
   * @type {State}
   */
  function afterPrefix(code) {
    const tail = self.events[self.events.length - 1];
    return tail && tail[1].type === "linePrefix" && tail[2].sliceSerialize(tail[1], true).length >= 4 ? atBreak(code) : nok(code);
  }

  /**
   * At a break.
   *
   * ```markdown
   * > |     aaa
   *         ^  ^
   * ```
   *
   * @type {State}
   */
  function atBreak(code) {
    if (code === null) {
      return after(code);
    }
    if (markdownLineEnding(code)) {
      return effects.attempt(furtherStart, atBreak, after)(code);
    }
    effects.enter("codeFlowValue");
    return inside(code);
  }

  /**
   * In code content.
   *
   * ```markdown
   * > |     aaa
   *         ^^^^
   * ```
   *
   * @type {State}
   */
  function inside(code) {
    if (code === null || markdownLineEnding(code)) {
      effects.exit("codeFlowValue");
      return atBreak(code);
    }
    effects.consume(code);
    return inside;
  }

  /** @type {State} */
  function after(code) {
    effects.exit("codeIndented");
    // To do: allow interrupting like `markdown-rs`.
    // Feel free to interrupt.
    // tokenizer.interrupt = false
    return ok(code);
  }
}

/**
 * @this {TokenizeContext}
 *   Context.
 * @type {Tokenizer}
 */
function tokenizeFurtherStart(effects, ok, nok) {
  const self = this;
  return furtherStart;

  /**
   * At eol, trying to parse another indent.
   *
   * ```markdown
   * > |     aaa
   *            ^
   *   |     bbb
   * ```
   *
   * @type {State}
   */
  function furtherStart(code) {
    // To do: improve `lazy` / `pierce` handling.
    // If this is a lazy line, it can’t be code.
    if (self.parser.lazy[self.now().line]) {
      return nok(code);
    }
    if (markdownLineEnding(code)) {
      effects.enter("lineEnding");
      effects.consume(code);
      effects.exit("lineEnding");
      return furtherStart;
    }

    // To do: the code here in `micromark-js` is a bit different from
    // `markdown-rs` because there it can attempt spaces.
    // We can’t yet.
    //
    // To do: use an improved `space_or_tab` function like `markdown-rs`,
    // so that we can drop the next state.
    return factorySpace(effects, afterPrefix, "linePrefix", 4 + 1)(code);
  }

  /**
   * At start, after 1 or 4 spaces.
   *
   * ```markdown
   * > |     aaa
   *         ^
   * ```
   *
   * @type {State}
   */
  function afterPrefix(code) {
    const tail = self.events[self.events.length - 1];
    return tail && tail[1].type === "linePrefix" && tail[2].sliceSerialize(tail[1], true).length >= 4 ? ok(code) : markdownLineEnding(code) ? furtherStart(code) : nok(code);
  }
}/**
 * @import {
 *   Construct,
 *   Previous,
 *   Resolver,
 *   State,
 *   TokenizeContext,
 *   Tokenizer,
 *   Token
 * } from 'micromark-util-types'
 */

import { markdownLineEnding } from 'micromark-util-character';
/** @type {Construct} */
export const codeText = {
  name: 'codeText',
  previous,
  resolve: resolveCodeText,
  tokenize: tokenizeCodeText
};

// To do: next major: don’t resolve, like `markdown-rs`.
/** @type {Resolver} */
function resolveCodeText(events) {
  let tailExitIndex = events.length - 4;
  let headEnterIndex = 3;
  /** @type {number} */
  let index;
  /** @type {number | undefined} */
  let enter;

  // If we start and end with an EOL or a space.
  if ((events[headEnterIndex][1].type === "lineEnding" || events[headEnterIndex][1].type === 'space') && (events[tailExitIndex][1].type === "lineEnding" || events[tailExitIndex][1].type === 'space')) {
    index = headEnterIndex;

    // And we have data.
    while (++index < tailExitIndex) {
      if (events[index][1].type === "codeTextData") {
        // Then we have padding.
        events[headEnterIndex][1].type = "codeTextPadding";
        events[tailExitIndex][1].type = "codeTextPadding";
        headEnterIndex += 2;
        tailExitIndex -= 2;
        break;
      }
    }
  }

  // Merge adjacent spaces and data.
  index = headEnterIndex - 1;
  tailExitIndex++;
  while (++index <= tailExitIndex) {
    if (enter === undefined) {
      if (index !== tailExitIndex && events[index][1].type !== "lineEnding") {
        enter = index;
      }
    } else if (index === tailExitIndex || events[index][1].type === "lineEnding") {
      events[enter][1].type = "codeTextData";
      if (index !== enter + 2) {
        events[enter][1].end = events[index - 1][1].end;
        events.splice(enter + 2, index - enter - 2);
        tailExitIndex -= index - enter - 2;
        index = enter + 2;
      }
      enter = undefined;
    }
  }
  return events;
}

/**
 * @this {TokenizeContext}
 *   Context.
 * @type {Previous}
 */
function previous(code) {
  // If there is a previous code, there will always be a tail.
  return code !== 96 || this.events[this.events.length - 1][1].type === "characterEscape";
}

/**
 * @this {TokenizeContext}
 *   Context.
 * @type {Tokenizer}
 */
function tokenizeCodeText(effects, ok, nok) {
  const self = this;
  let sizeOpen = 0;
  /** @type {number} */
  let size;
  /** @type {Token} */
  let token;
  return start;

  /**
   * Start of code (text).
   *
   * ```markdown
   * > | `a`
   *     ^
   * > | \`a`
   *      ^
   * ```
   *
   * @type {State}
   */
  function start(code) {
    effects.enter("codeText");
    effects.enter("codeTextSequence");
    return sequenceOpen(code);
  }

  /**
   * In opening sequence.
   *
   * ```markdown
   * > | `a`
   *     ^
   * ```
   *
   * @type {State}
   */
  function sequenceOpen(code) {
    if (code === 96) {
      effects.consume(code);
      sizeOpen++;
      return sequenceOpen;
    }
    effects.exit("codeTextSequence");
    return between(code);
  }

  /**
   * Between something and something else.
   *
   * ```markdown
   * > | `a`
   *      ^^
   * ```
   *
   * @type {State}
   */
  function between(code) {
    // EOF.
    if (code === null) {
      return nok(code);
    }

    // To do: next major: don’t do spaces in resolve, but when compiling,
    // like `markdown-rs`.
    // Tabs don’t work, and virtual spaces don’t make sense.
    if (code === 32) {
      effects.enter('space');
      effects.consume(code);
      effects.exit('space');
      return between;
    }

    // Closing fence? Could also be data.
    if (code === 96) {
      token = effects.enter("codeTextSequence");
      size = 0;
      return sequenceClose(code);
    }
    if (markdownLineEnding(code)) {
      effects.enter("lineEnding");
      effects.consume(code);
      effects.exit("lineEnding");
      return between;
    }

    // Data.
    effects.enter("codeTextData");
    return data(code);
  }

  /**
   * In data.
   *
   * ```markdown
   * > | `a`
   *      ^
   * ```
   *
   * @type {State}
   */
  function data(code) {
    if (code === null || code === 32 || code === 96 || markdownLineEnding(code)) {
      effects.exit("codeTextData");
      return between(code);
    }
    effects.consume(code);
    return data;
  }

  /**
   * In closing sequence.
   *
   * ```markdown
   * > | `a`
   *       ^
   * ```
   *
   * @type {State}
   */
  function sequenceClose(code) {
    // More.
    if (code === 96) {
      effects.consume(code);
      size++;
      return sequenceClose;
    }

    // Done!
    if (size === sizeOpen) {
      effects.exit("codeTextSequence");
      effects.exit("codeText");
      return ok(code);
    }

    // More or less accents: mark as data.
    token.type = "codeTextData";
    return data(code);
  }
}/**
 * @import {
 *   Construct,
 *   Resolver,
 *   State,
 *   TokenizeContext,
 *   Tokenizer,
 *   Token
 * } from 'micromark-util-types'
 */

import { factorySpace } from 'micromark-factory-space';
import { markdownLineEnding } from 'micromark-util-character';
import { subtokenize } from 'micromark-util-subtokenize';
/**
 * No name because it must not be turned off.
 * @type {Construct}
 */
export const content = {
  resolve: resolveContent,
  tokenize: tokenizeContent
};

/** @type {Construct} */
const continuationConstruct = {
  partial: true,
  tokenize: tokenizeContinuation
};

/**
 * Content is transparent: it’s parsed right now. That way, definitions are also
 * parsed right now: before text in paragraphs (specifically, media) are parsed.
 *
 * @type {Resolver}
 */
function resolveContent(events) {
  subtokenize(events);
  return events;
}

/**
 * @this {TokenizeContext}
 *   Context.
 * @type {Tokenizer}
 */
function tokenizeContent(effects, ok) {
  /** @type {Token | undefined} */
  let previous;
  return chunkStart;

  /**
   * Before a content chunk.
   *
   * ```markdown
   * > | abc
   *     ^
   * ```
   *
   * @type {State}
   */
  function chunkStart(code) {
    effects.enter("content");
    previous = effects.enter("chunkContent", {
      contentType: "content"
    });
    return chunkInside(code);
  }

  /**
   * In a content chunk.
   *
   * ```markdown
   * > | abc
   *     ^^^
   * ```
   *
   * @type {State}
   */
  function chunkInside(code) {
    if (code === null) {
      return contentEnd(code);
    }

    // To do: in `markdown-rs`, each line is parsed on its own, and everything
    // is stitched together resolving.
    if (markdownLineEnding(code)) {
      return effects.check(continuationConstruct, contentContinue, contentEnd)(code);
    }

    // Data.
    effects.consume(code);
    return chunkInside;
  }

  /**
   *
   *
   * @type {State}
   */
  function contentEnd(code) {
    effects.exit("chunkContent");
    effects.exit("content");
    return ok(code);
  }

  /**
   *
   *
   * @type {State}
   */
  function contentContinue(code) {
    effects.consume(code);
    effects.exit("chunkContent");
    previous.next = effects.enter("chunkContent", {
      contentType: "content",
      previous
    });
    previous = previous.next;
    return chunkInside;
  }
}

/**
 * @this {TokenizeContext}
 *   Context.
 * @type {Tokenizer}
 */
function tokenizeContinuation(effects, ok, nok) {
  const self = this;
  return startLookahead;

  /**
   *
   *
   * @type {State}
   */
  function startLookahead(code) {
    effects.exit("chunkContent");
    effects.enter("lineEnding");
    effects.consume(code);
    effects.exit("lineEnding");
    return factorySpace(effects, prefixed, "linePrefix");
  }

  /**
   *
   *
   * @type {State}
   */
  function prefixed(code) {
    if (code === null || markdownLineEnding(code)) {
      return nok(code);
    }

    // Always populated by defaults.

    const tail = self.events[self.events.length - 1];
    if (!self.parser.constructs.disable.null.includes('codeIndented') && tail && tail[1].type === "linePrefix" && tail[2].sliceSerialize(tail[1], true).length >= 4) {
      return ok(code);
    }
    return effects.interrupt(self.parser.constructs.flow, nok, ok)(code);
  }
}/**
 * @import {
 *   Construct,
 *   State,
 *   TokenizeContext,
 *   Tokenizer
 * } from 'micromark-util-types'
 */

import { factoryDestination } from 'micromark-factory-destination';
import { factoryLabel } from 'micromark-factory-label';
import { factorySpace } from 'micromark-factory-space';
import { factoryTitle } from 'micromark-factory-title';
import { factoryWhitespace } from 'micromark-factory-whitespace';
import { markdownLineEndingOrSpace, markdownLineEnding, markdownSpace } from 'micromark-util-character';
import { normalizeIdentifier } from 'micromark-util-normalize-identifier';
/** @type {Construct} */
export const definition = {
  name: 'definition',
  tokenize: tokenizeDefinition
};

/** @type {Construct} */
const titleBefore = {
  partial: true,
  tokenize: tokenizeTitleBefore
};

/**
 * @this {TokenizeContext}
 *   Context.
 * @type {Tokenizer}
 */
function tokenizeDefinition(effects, ok, nok) {
  const self = this;
  /** @type {string} */
  let identifier;
  return start;

  /**
   * At start of a definition.
   *
   * ```markdown
   * > | [a]: b "c"
   *     ^
   * ```
   *
   * @type {State}
   */
  function start(code) {
    // Do not interrupt paragraphs (but do follow definitions).
    // To do: do `interrupt` the way `markdown-rs` does.
    // To do: parse whitespace the way `markdown-rs` does.
    effects.enter("definition");
    return before(code);
  }

  /**
   * After optional whitespace, at `[`.
   *
   * ```markdown
   * > | [a]: b "c"
   *     ^
   * ```
   *
   * @type {State}
   */
  function before(code) {
    // To do: parse whitespace the way `markdown-rs` does.

    return factoryLabel.call(self, effects, labelAfter,
    // Note: we don’t need to reset the way `markdown-rs` does.
    nok, "definitionLabel", "definitionLabelMarker", "definitionLabelString")(code);
  }

  /**
   * After label.
   *
   * ```markdown
   * > | [a]: b "c"
   *        ^
   * ```
   *
   * @type {State}
   */
  function labelAfter(code) {
    identifier = normalizeIdentifier(self.sliceSerialize(self.events[self.events.length - 1][1]).slice(1, -1));
    if (code === 58) {
      effects.enter("definitionMarker");
      effects.consume(code);
      effects.exit("definitionMarker");
      return markerAfter;
    }
    return nok(code);
  }

  /**
   * After marker.
   *
   * ```markdown
   * > | [a]: b "c"
   *         ^
   * ```
   *
   * @type {State}
   */
  function markerAfter(code) {
    // Note: whitespace is optional.
    return markdownLineEndingOrSpace(code) ? factoryWhitespace(effects, destinationBefore)(code) : destinationBefore(code);
  }

  /**
   * Before destination.
   *
   * ```markdown
   * > | [a]: b "c"
   *          ^
   * ```
   *
   * @type {State}
   */
  function destinationBefore(code) {
    return factoryDestination(effects, destinationAfter,
    // Note: we don’t need to reset the way `markdown-rs` does.
    nok, "definitionDestination", "definitionDestinationLiteral", "definitionDestinationLiteralMarker", "definitionDestinationRaw", "definitionDestinationString")(code);
  }

  /**
   * After destination.
   *
   * ```markdown
   * > | [a]: b "c"
   *           ^
   * ```
   *
   * @type {State}
   */
  function destinationAfter(code) {
    return effects.attempt(titleBefore, after, after)(code);
  }

  /**
   * After definition.
   *
   * ```markdown
   * > | [a]: b
   *           ^
   * > | [a]: b "c"
   *               ^
   * ```
   *
   * @type {State}
   */
  function after(code) {
    return markdownSpace(code) ? factorySpace(effects, afterWhitespace, "whitespace")(code) : afterWhitespace(code);
  }

  /**
   * After definition, after optional whitespace.
   *
   * ```markdown
   * > | [a]: b
   *           ^
   * > | [a]: b "c"
   *               ^
   * ```
   *
   * @type {State}
   */
  function afterWhitespace(code) {
    if (code === null || markdownLineEnding(code)) {
      effects.exit("definition");

      // Note: we don’t care about uniqueness.
      // It’s likely that that doesn’t happen very frequently.
      // It is more likely that it wastes precious time.
      self.parser.defined.push(identifier);

      // To do: `markdown-rs` interrupt.
      // // You’d be interrupting.
      // tokenizer.interrupt = true
      return ok(code);
    }
    return nok(code);
  }
}

/**
 * @this {TokenizeContext}
 *   Context.
 * @type {Tokenizer}
 */
function tokenizeTitleBefore(effects, ok, nok) {
  return titleBefore;

  /**
   * After destination, at whitespace.
   *
   * ```markdown
   * > | [a]: b
   *           ^
   * > | [a]: b "c"
   *           ^
   * ```
   *
   * @type {State}
   */
  function titleBefore(code) {
    return markdownLineEndingOrSpace(code) ? factoryWhitespace(effects, beforeMarker)(code) : nok(code);
  }

  /**
   * At title.
   *
   * ```markdown
   *   | [a]: b
   * > | "c"
   *     ^
   * ```
   *
   * @type {State}
   */
  function beforeMarker(code) {
    return factoryTitle(effects, titleAfter, nok, "definitionTitle", "definitionTitleMarker", "definitionTitleString")(code);
  }

  /**
   * After title.
   *
   * ```markdown
   * > | [a]: b "c"
   *               ^
   * ```
   *
   * @type {State}
   */
  function titleAfter(code) {
    return markdownSpace(code) ? factorySpace(effects, titleAfterOptionalWhitespace, "whitespace")(code) : titleAfterOptionalWhitespace(code);
  }

  /**
   * After title, after optional whitespace.
   *
   * ```markdown
   * > | [a]: b "c"
   *               ^
   * ```
   *
   * @type {State}
   */
  function titleAfterOptionalWhitespace(code) {
    return code === null || markdownLineEnding(code) ? ok(code) : nok(code);
  }
}/**
 * @import {
 *   Construct,
 *   State,
 *   TokenizeContext,
 *   Tokenizer
 * } from 'micromark-util-types'
 */

import { markdownLineEnding } from 'micromark-util-character';
/** @type {Construct} */
export const hardBreakEscape = {
  name: 'hardBreakEscape',
  tokenize: tokenizeHardBreakEscape
};

/**
 * @this {TokenizeContext}
 *   Context.
 * @type {Tokenizer}
 */
function tokenizeHardBreakEscape(effects, ok, nok) {
  return start;

  /**
   * Start of a hard break (escape).
   *
   * ```markdown
   * > | a\
   *      ^
   *   | b
   * ```
   *
   * @type {State}
   */
  function start(code) {
    effects.enter("hardBreakEscape");
    effects.consume(code);
    return after;
  }

  /**
   * After `\`, at eol.
   *
   * ```markdown
   * > | a\
   *       ^
   *   | b
   * ```
   *
   *  @type {State}
   */
  function after(code) {
    if (markdownLineEnding(code)) {
      effects.exit("hardBreakEscape");
      return ok(code);
    }
    return nok(code);
  }
}/**
 * @import {
 *   Construct,
 *   Resolver,
 *   State,
 *   TokenizeContext,
 *   Tokenizer,
 *   Token
 * } from 'micromark-util-types'
 */

import { factorySpace } from 'micromark-factory-space';
import { markdownLineEndingOrSpace, markdownLineEnding, markdownSpace } from 'micromark-util-character';
import { splice } from 'micromark-util-chunked';
/** @type {Construct} */
export const headingAtx = {
  name: 'headingAtx',
  resolve: resolveHeadingAtx,
  tokenize: tokenizeHeadingAtx
};

/** @type {Resolver} */
function resolveHeadingAtx(events, context) {
  let contentEnd = events.length - 2;
  let contentStart = 3;
  /** @type {Token} */
  let content;
  /** @type {Token} */
  let text;

  // Prefix whitespace, part of the opening.
  if (events[contentStart][1].type === "whitespace") {
    contentStart += 2;
  }

  // Suffix whitespace, part of the closing.
  if (contentEnd - 2 > contentStart && events[contentEnd][1].type === "whitespace") {
    contentEnd -= 2;
  }
  if (events[contentEnd][1].type === "atxHeadingSequence" && (contentStart === contentEnd - 1 || contentEnd - 4 > contentStart && events[contentEnd - 2][1].type === "whitespace")) {
    contentEnd -= contentStart + 1 === contentEnd ? 2 : 4;
  }
  if (contentEnd > contentStart) {
    content = {
      type: "atxHeadingText",
      start: events[contentStart][1].start,
      end: events[contentEnd][1].end
    };
    text = {
      type: "chunkText",
      start: events[contentStart][1].start,
      end: events[contentEnd][1].end,
      contentType: "text"
    };
    splice(events, contentStart, contentEnd - contentStart + 1, [['enter', content, context], ['enter', text, context], ['exit', text, context], ['exit', content, context]]);
  }
  return events;
}

/**
 * @this {TokenizeContext}
 *   Context.
 * @type {Tokenizer}
 */
function tokenizeHeadingAtx(effects, ok, nok) {
  let size = 0;
  return start;

  /**
   * Start of a heading (atx).
   *
   * ```markdown
   * > | ## aa
   *     ^
   * ```
   *
   * @type {State}
   */
  function start(code) {
    // To do: parse indent like `markdown-rs`.
    effects.enter("atxHeading");
    return before(code);
  }

  /**
   * After optional whitespace, at `#`.
   *
   * ```markdown
   * > | ## aa
   *     ^
   * ```
   *
   * @type {State}
   */
  function before(code) {
    effects.enter("atxHeadingSequence");
    return sequenceOpen(code);
  }

  /**
   * In opening sequence.
   *
   * ```markdown
   * > | ## aa
   *     ^
   * ```
   *
   * @type {State}
   */
  function sequenceOpen(code) {
    if (code === 35 && size++ < 6) {
      effects.consume(code);
      return sequenceOpen;
    }

    // Always at least one `#`.
    if (code === null || markdownLineEndingOrSpace(code)) {
      effects.exit("atxHeadingSequence");
      return atBreak(code);
    }
    return nok(code);
  }

  /**
   * After something, before something else.
   *
   * ```markdown
   * > | ## aa
   *       ^
   * ```
   *
   * @type {State}
   */
  function atBreak(code) {
    if (code === 35) {
      effects.enter("atxHeadingSequence");
      return sequenceFurther(code);
    }
    if (code === null || markdownLineEnding(code)) {
      effects.exit("atxHeading");
      // To do: interrupt like `markdown-rs`.
      // // Feel free to interrupt.
      // tokenizer.interrupt = false
      return ok(code);
    }
    if (markdownSpace(code)) {
      return factorySpace(effects, atBreak, "whitespace")(code);
    }

    // To do: generate `data` tokens, add the `text` token later.
    // Needs edit map, see: `markdown.rs`.
    effects.enter("atxHeadingText");
    return data(code);
  }

  /**
   * In further sequence (after whitespace).
   *
   * Could be normal “visible” hashes in the heading or a final sequence.
   *
   * ```markdown
   * > | ## aa ##
   *           ^
   * ```
   *
   * @type {State}
   */
  function sequenceFurther(code) {
    if (code === 35) {
      effects.consume(code);
      return sequenceFurther;
    }
    effects.exit("atxHeadingSequence");
    return atBreak(code);
  }

  /**
   * In text.
   *
   * ```markdown
   * > | ## aa
   *        ^
   * ```
   *
   * @type {State}
   */
  function data(code) {
    if (code === null || code === 35 || markdownLineEndingOrSpace(code)) {
      effects.exit("atxHeadingText");
      return atBreak(code);
    }
    effects.consume(code);
    return data;
  }
}/**
 * @import {
 *   Code,
 *   Construct,
 *   Resolver,
 *   State,
 *   TokenizeContext,
 *   Tokenizer
 * } from 'micromark-util-types'
 */

import { asciiAlphanumeric, asciiAlpha, markdownLineEndingOrSpace, markdownLineEnding, markdownSpace } from 'micromark-util-character';
import { htmlBlockNames, htmlRawNames } from 'micromark-util-html-tag-name';
import { blankLine } from './blank-line.js';

/** @type {Construct} */
export const htmlFlow = {
  concrete: true,
  name: 'htmlFlow',
  resolveTo: resolveToHtmlFlow,
  tokenize: tokenizeHtmlFlow
};

/** @type {Construct} */
const blankLineBefore = {
  partial: true,
  tokenize: tokenizeBlankLineBefore
};
const nonLazyContinuationStart = {
  partial: true,
  tokenize: tokenizeNonLazyContinuationStart
};

/** @type {Resolver} */
function resolveToHtmlFlow(events) {
  let index = events.length;
  while (index--) {
    if (events[index][0] === 'enter' && events[index][1].type === "htmlFlow") {
      break;
    }
  }
  if (index > 1 && events[index - 2][1].type === "linePrefix") {
    // Add the prefix start to the HTML token.
    events[index][1].start = events[index - 2][1].start;
    // Add the prefix start to the HTML line token.
    events[index + 1][1].start = events[index - 2][1].start;
    // Remove the line prefix.
    events.splice(index - 2, 2);
  }
  return events;
}

/**
 * @this {TokenizeContext}
 *   Context.
 * @type {Tokenizer}
 */
function tokenizeHtmlFlow(effects, ok, nok) {
  const self = this;
  /** @type {number} */
  let marker;
  /** @type {boolean} */
  let closingTag;
  /** @type {string} */
  let buffer;
  /** @type {number} */
  let index;
  /** @type {Code} */
  let markerB;
  return start;

  /**
   * Start of HTML (flow).
   *
   * ```markdown
   * > | <x />
   *     ^
   * ```
   *
   * @type {State}
   */
  function start(code) {
    // To do: parse indent like `markdown-rs`.
    return before(code);
  }

  /**
   * At `<`, after optional whitespace.
   *
   * ```markdown
   * > | <x />
   *     ^
   * ```
   *
   * @type {State}
   */
  function before(code) {
    effects.enter("htmlFlow");
    effects.enter("htmlFlowData");
    effects.consume(code);
    return open;
  }

  /**
   * After `<`, at tag name or other stuff.
   *
   * ```markdown
   * > | <x />
   *      ^
   * > | <!doctype>
   *      ^
   * > | <!--xxx-->
   *      ^
   * ```
   *
   * @type {State}
   */
  function open(code) {
    if (code === 33) {
      effects.consume(code);
      return declarationOpen;
    }
    if (code === 47) {
      effects.consume(code);
      closingTag = true;
      return tagCloseStart;
    }
    if (code === 63) {
      effects.consume(code);
      marker = 3;
      // To do:
      // tokenizer.concrete = true
      // To do: use `markdown-rs` style interrupt.
      // While we’re in an instruction instead of a declaration, we’re on a `?`
      // right now, so we do need to search for `>`, similar to declarations.
      return self.interrupt ? ok : continuationDeclarationInside;
    }

    // ASCII alphabetical.
    if (asciiAlpha(code)) {
      // Always the case.
      effects.consume(code);
      buffer = String.fromCharCode(code);
      return tagName;
    }
    return nok(code);
  }

  /**
   * After `<!`, at declaration, comment, or CDATA.
   *
   * ```markdown
   * > | <!doctype>
   *       ^
   * > | <!--xxx-->
   *       ^
   * > | <![CDATA[>&<]]>
   *       ^
   * ```
   *
   * @type {State}
   */
  function declarationOpen(code) {
    if (code === 45) {
      effects.consume(code);
      marker = 2;
      return commentOpenInside;
    }
    if (code === 91) {
      effects.consume(code);
      marker = 5;
      index = 0;
      return cdataOpenInside;
    }

    // ASCII alphabetical.
    if (asciiAlpha(code)) {
      effects.consume(code);
      marker = 4;
      // // Do not form containers.
      // tokenizer.concrete = true
      return self.interrupt ? ok : continuationDeclarationInside;
    }
    return nok(code);
  }

  /**
   * After `<!-`, inside a comment, at another `-`.
   *
   * ```markdown
   * > | <!--xxx-->
   *        ^
   * ```
   *
   * @type {State}
   */
  function commentOpenInside(code) {
    if (code === 45) {
      effects.consume(code);
      // // Do not form containers.
      // tokenizer.concrete = true
      return self.interrupt ? ok : continuationDeclarationInside;
    }
    return nok(code);
  }

  /**
   * After `<![`, inside CDATA, expecting `CDATA[`.
   *
   * ```markdown
   * > | <![CDATA[>&<]]>
   *        ^^^^^^
   * ```
   *
   * @type {State}
   */
  function cdataOpenInside(code) {
    const value = "CDATA[";
    if (code === value.charCodeAt(index++)) {
      effects.consume(code);
      if (index === value.length) {
        // // Do not form containers.
        // tokenizer.concrete = true
        return self.interrupt ? ok : continuation;
      }
      return cdataOpenInside;
    }
    return nok(code);
  }

  /**
   * After `</`, in closing tag, at tag name.
   *
   * ```markdown
   * > | </x>
   *       ^
   * ```
   *
   * @type {State}
   */
  function tagCloseStart(code) {
    if (asciiAlpha(code)) {
      // Always the case.
      effects.consume(code);
      buffer = String.fromCharCode(code);
      return tagName;
    }
    return nok(code);
  }

  /**
   * In tag name.
   *
   * ```markdown
   * > | <ab>
   *      ^^
   * > | </ab>
   *       ^^
   * ```
   *
   * @type {State}
   */
  function tagName(code) {
    if (code === null || code === 47 || code === 62 || markdownLineEndingOrSpace(code)) {
      const slash = code === 47;
      const name = buffer.toLowerCase();
      if (!slash && !closingTag && htmlRawNames.includes(name)) {
        marker = 1;
        // // Do not form containers.
        // tokenizer.concrete = true
        return self.interrupt ? ok(code) : continuation(code);
      }
      if (htmlBlockNames.includes(buffer.toLowerCase())) {
        marker = 6;
        if (slash) {
          effects.consume(code);
          return basicSelfClosing;
        }

        // // Do not form containers.
        // tokenizer.concrete = true
        return self.interrupt ? ok(code) : continuation(code);
      }
      marker = 7;
      // Do not support complete HTML when interrupting.
      return self.interrupt && !self.parser.lazy[self.now().line] ? nok(code) : closingTag ? completeClosingTagAfter(code) : completeAttributeNameBefore(code);
    }

    // ASCII alphanumerical and `-`.
    if (code === 45 || asciiAlphanumeric(code)) {
      effects.consume(code);
      buffer += String.fromCharCode(code);
      return tagName;
    }
    return nok(code);
  }

  /**
   * After closing slash of a basic tag name.
   *
   * ```markdown
   * > | <div/>
   *          ^
   * ```
   *
   * @type {State}
   */
  function basicSelfClosing(code) {
    if (code === 62) {
      effects.consume(code);
      // // Do not form containers.
      // tokenizer.concrete = true
      return self.interrupt ? ok : continuation;
    }
    return nok(code);
  }

  /**
   * After closing slash of a complete tag name.
   *
   * ```markdown
   * > | <x/>
   *        ^
   * ```
   *
   * @type {State}
   */
  function completeClosingTagAfter(code) {
    if (markdownSpace(code)) {
      effects.consume(code);
      return completeClosingTagAfter;
    }
    return completeEnd(code);
  }

  /**
   * At an attribute name.
   *
   * At first, this state is used after a complete tag name, after whitespace,
   * where it expects optional attributes or the end of the tag.
   * It is also reused after attributes, when expecting more optional
   * attributes.
   *
   * ```markdown
   * > | <a />
   *        ^
   * > | <a :b>
   *        ^
   * > | <a _b>
   *        ^
   * > | <a b>
   *        ^
   * > | <a >
   *        ^
   * ```
   *
   * @type {State}
   */
  function completeAttributeNameBefore(code) {
    if (code === 47) {
      effects.consume(code);
      return completeEnd;
    }

    // ASCII alphanumerical and `:` and `_`.
    if (code === 58 || code === 95 || asciiAlpha(code)) {
      effects.consume(code);
      return completeAttributeName;
    }
    if (markdownSpace(code)) {
      effects.consume(code);
      return completeAttributeNameBefore;
    }
    return completeEnd(code);
  }

  /**
   * In attribute name.
   *
   * ```markdown
   * > | <a :b>
   *         ^
   * > | <a _b>
   *         ^
   * > | <a b>
   *         ^
   * ```
   *
   * @type {State}
   */
  function completeAttributeName(code) {
    // ASCII alphanumerical and `-`, `.`, `:`, and `_`.
    if (code === 45 || code === 46 || code === 58 || code === 95 || asciiAlphanumeric(code)) {
      effects.consume(code);
      return completeAttributeName;
    }
    return completeAttributeNameAfter(code);
  }

  /**
   * After attribute name, at an optional initializer, the end of the tag, or
   * whitespace.
   *
   * ```markdown
   * > | <a b>
   *         ^
   * > | <a b=c>
   *         ^
   * ```
   *
   * @type {State}
   */
  function completeAttributeNameAfter(code) {
    if (code === 61) {
      effects.consume(code);
      return completeAttributeValueBefore;
    }
    if (markdownSpace(code)) {
      effects.consume(code);
      return completeAttributeNameAfter;
    }
    return completeAttributeNameBefore(code);
  }

  /**
   * Before unquoted, double quoted, or single quoted attribute value, allowing
   * whitespace.
   *
   * ```markdown
   * > | <a b=c>
   *          ^
   * > | <a b="c">
   *          ^
   * ```
   *
   * @type {State}
   */
  function completeAttributeValueBefore(code) {
    if (code === null || code === 60 || code === 61 || code === 62 || code === 96) {
      return nok(code);
    }
    if (code === 34 || code === 39) {
      effects.consume(code);
      markerB = code;
      return completeAttributeValueQuoted;
    }
    if (markdownSpace(code)) {
      effects.consume(code);
      return completeAttributeValueBefore;
    }
    return completeAttributeValueUnquoted(code);
  }

  /**
   * In double or single quoted attribute value.
   *
   * ```markdown
   * > | <a b="c">
   *           ^
   * > | <a b='c'>
   *           ^
   * ```
   *
   * @type {State}
   */
  function completeAttributeValueQuoted(code) {
    if (code === markerB) {
      effects.consume(code);
      markerB = null;
      return completeAttributeValueQuotedAfter;
    }
    if (code === null || markdownLineEnding(code)) {
      return nok(code);
    }
    effects.consume(code);
    return completeAttributeValueQuoted;
  }

  /**
   * In unquoted attribute value.
   *
   * ```markdown
   * > | <a b=c>
   *          ^
   * ```
   *
   * @type {State}
   */
  function completeAttributeValueUnquoted(code) {
    if (code === null || code === 34 || code === 39 || code === 47 || code === 60 || code === 61 || code === 62 || code === 96 || markdownLineEndingOrSpace(code)) {
      return completeAttributeNameAfter(code);
    }
    effects.consume(code);
    return completeAttributeValueUnquoted;
  }

  /**
   * After double or single quoted attribute value, before whitespace or the
   * end of the tag.
   *
   * ```markdown
   * > | <a b="c">
   *            ^
   * ```
   *
   * @type {State}
   */
  function completeAttributeValueQuotedAfter(code) {
    if (code === 47 || code === 62 || markdownSpace(code)) {
      return completeAttributeNameBefore(code);
    }
    return nok(code);
  }

  /**
   * In certain circumstances of a complete tag where only an `>` is allowed.
   *
   * ```markdown
   * > | <a b="c">
   *             ^
   * ```
   *
   * @type {State}
   */
  function completeEnd(code) {
    if (code === 62) {
      effects.consume(code);
      return completeAfter;
    }
    return nok(code);
  }

  /**
   * After `>` in a complete tag.
   *
   * ```markdown
   * > | <x>
   *        ^
   * ```
   *
   * @type {State}
   */
  function completeAfter(code) {
    if (code === null || markdownLineEnding(code)) {
      // // Do not form containers.
      // tokenizer.concrete = true
      return continuation(code);
    }
    if (markdownSpace(code)) {
      effects.consume(code);
      return completeAfter;
    }
    return nok(code);
  }

  /**
   * In continuation of any HTML kind.
   *
   * ```markdown
   * > | <!--xxx-->
   *          ^
   * ```
   *
   * @type {State}
   */
  function continuation(code) {
    if (code === 45 && marker === 2) {
      effects.consume(code);
      return continuationCommentInside;
    }
    if (code === 60 && marker === 1) {
      effects.consume(code);
      return continuationRawTagOpen;
    }
    if (code === 62 && marker === 4) {
      effects.consume(code);
      return continuationClose;
    }
    if (code === 63 && marker === 3) {
      effects.consume(code);
      return continuationDeclarationInside;
    }
    if (code === 93 && marker === 5) {
      effects.consume(code);
      return continuationCdataInside;
    }
    if (markdownLineEnding(code) && (marker === 6 || marker === 7)) {
      effects.exit("htmlFlowData");
      return effects.check(blankLineBefore, continuationAfter, continuationStart)(code);
    }
    if (code === null || markdownLineEnding(code)) {
      effects.exit("htmlFlowData");
      return continuationStart(code);
    }
    effects.consume(code);
    return continuation;
  }

  /**
   * In continuation, at eol.
   *
   * ```markdown
   * > | <x>
   *        ^
   *   | asd
   * ```
   *
   * @type {State}
   */
  function continuationStart(code) {
    return effects.check(nonLazyContinuationStart, continuationStartNonLazy, continuationAfter)(code);
  }

  /**
   * In continuation, at eol, before non-lazy content.
   *
   * ```markdown
   * > | <x>
   *        ^
   *   | asd
   * ```
   *
   * @type {State}
   */
  function continuationStartNonLazy(code) {
    effects.enter("lineEnding");
    effects.consume(code);
    effects.exit("lineEnding");
    return continuationBefore;
  }

  /**
   * In continuation, before non-lazy content.
   *
   * ```markdown
   *   | <x>
   * > | asd
   *     ^
   * ```
   *
   * @type {State}
   */
  function continuationBefore(code) {
    if (code === null || markdownLineEnding(code)) {
      return continuationStart(code);
    }
    effects.enter("htmlFlowData");
    return continuation(code);
  }

  /**
   * In comment continuation, after one `-`, expecting another.
   *
   * ```markdown
   * > | <!--xxx-->
   *             ^
   * ```
   *
   * @type {State}
   */
  function continuationCommentInside(code) {
    if (code === 45) {
      effects.consume(code);
      return continuationDeclarationInside;
    }
    return continuation(code);
  }

  /**
   * In raw continuation, after `<`, at `/`.
   *
   * ```markdown
   * > | <script>console.log(1)</script>
   *                            ^
   * ```
   *
   * @type {State}
   */
  function continuationRawTagOpen(code) {
    if (code === 47) {
      effects.consume(code);
      buffer = '';
      return continuationRawEndTag;
    }
    return continuation(code);
  }

  /**
   * In raw continuation, after `</`, in a raw tag name.
   *
   * ```markdown
   * > | <script>console.log(1)</script>
   *                             ^^^^^^
   * ```
   *
   * @type {State}
   */
  function continuationRawEndTag(code) {
    if (code === 62) {
      const name = buffer.toLowerCase();
      if (htmlRawNames.includes(name)) {
        effects.consume(code);
        return continuationClose;
      }
      return continuation(code);
    }
    if (asciiAlpha(code) && buffer.length < 8) {
      // Always the case.
      effects.consume(code);
      buffer += String.fromCharCode(code);
      return continuationRawEndTag;
    }
    return continuation(code);
  }

  /**
   * In cdata continuation, after `]`, expecting `]>`.
   *
   * ```markdown
   * > | <![CDATA[>&<]]>
   *                  ^
   * ```
   *
   * @type {State}
   */
  function continuationCdataInside(code) {
    if (code === 93) {
      effects.consume(code);
      return continuationDeclarationInside;
    }
    return continuation(code);
  }

  /**
   * In declaration or instruction continuation, at `>`.
   *
   * ```markdown
   * > | <!-->
   *         ^
   * > | <?>
   *       ^
   * > | <!q>
   *        ^
   * > | <!--ab-->
   *             ^
   * > | <![CDATA[>&<]]>
   *                   ^
   * ```
   *
   * @type {State}
   */
  function continuationDeclarationInside(code) {
    if (code === 62) {
      effects.consume(code);
      return continuationClose;
    }

    // More dashes.
    if (code === 45 && marker === 2) {
      effects.consume(code);
      return continuationDeclarationInside;
    }
    return continuation(code);
  }

  /**
   * In closed continuation: everything we get until the eol/eof is part of it.
   *
   * ```markdown
   * > | <!doctype>
   *               ^
   * ```
   *
   * @type {State}
   */
  function continuationClose(code) {
    if (code === null || markdownLineEnding(code)) {
      effects.exit("htmlFlowData");
      return continuationAfter(code);
    }
    effects.consume(code);
    return continuationClose;
  }

  /**
   * Done.
   *
   * ```markdown
   * > | <!doctype>
   *               ^
   * ```
   *
   * @type {State}
   */
  function continuationAfter(code) {
    effects.exit("htmlFlow");
    // // Feel free to interrupt.
    // tokenizer.interrupt = false
    // // No longer concrete.
    // tokenizer.concrete = false
    return ok(code);
  }
}

/**
 * @this {TokenizeContext}
 *   Context.
 * @type {Tokenizer}
 */
function tokenizeNonLazyContinuationStart(effects, ok, nok) {
  const self = this;
  return start;

  /**
   * At eol, before continuation.
   *
   * ```markdown
   * > | * ```js
   *            ^
   *   | b
   * ```
   *
   * @type {State}
   */
  function start(code) {
    if (markdownLineEnding(code)) {
      effects.enter("lineEnding");
      effects.consume(code);
      effects.exit("lineEnding");
      return after;
    }
    return nok(code);
  }

  /**
   * A continuation.
   *
   * ```markdown
   *   | * ```js
   * > | b
   *     ^
   * ```
   *
   * @type {State}
   */
  function after(code) {
    return self.parser.lazy[self.now().line] ? nok(code) : ok(code);
  }
}

/**
 * @this {TokenizeContext}
 *   Context.
 * @type {Tokenizer}
 */
function tokenizeBlankLineBefore(effects, ok, nok) {
  return start;

  /**
   * Before eol, expecting blank line.
   *
   * ```markdown
   * > | <div>
   *          ^
   *   |
   * ```
   *
   * @type {State}
   */
  function start(code) {
    effects.enter("lineEnding");
    effects.consume(code);
    effects.exit("lineEnding");
    return effects.attempt(blankLine, ok, nok);
  }
}/**
 * @import {
 *   Code,
 *   Construct,
 *   State,
 *   TokenizeContext,
 *   Tokenizer
 * } from 'micromark-util-types'
 */

import { factorySpace } from 'micromark-factory-space';
import { asciiAlphanumeric, asciiAlpha, markdownLineEndingOrSpace, markdownLineEnding, markdownSpace } from 'micromark-util-character';
/** @type {Construct} */
export const htmlText = {
  name: 'htmlText',
  tokenize: tokenizeHtmlText
};

/**
 * @this {TokenizeContext}
 *   Context.
 * @type {Tokenizer}
 */
function tokenizeHtmlText(effects, ok, nok) {
  const self = this;
  /** @type {NonNullable<Code> | undefined} */
  let marker;
  /** @type {number} */
  let index;
  /** @type {State} */
  let returnState;
  return start;

  /**
   * Start of HTML (text).
   *
   * ```markdown
   * > | a <b> c
   *       ^
   * ```
   *
   * @type {State}
   */
  function start(code) {
    effects.enter("htmlText");
    effects.enter("htmlTextData");
    effects.consume(code);
    return open;
  }

  /**
   * After `<`, at tag name or other stuff.
   *
   * ```markdown
   * > | a <b> c
   *        ^
   * > | a <!doctype> c
   *        ^
   * > | a <!--b--> c
   *        ^
   * ```
   *
   * @type {State}
   */
  function open(code) {
    if (code === 33) {
      effects.consume(code);
      return declarationOpen;
    }
    if (code === 47) {
      effects.consume(code);
      return tagCloseStart;
    }
    if (code === 63) {
      effects.consume(code);
      return instruction;
    }

    // ASCII alphabetical.
    if (asciiAlpha(code)) {
      effects.consume(code);
      return tagOpen;
    }
    return nok(code);
  }

  /**
   * After `<!`, at declaration, comment, or CDATA.
   *
   * ```markdown
   * > | a <!doctype> c
   *         ^
   * > | a <!--b--> c
   *         ^
   * > | a <![CDATA[>&<]]> c
   *         ^
   * ```
   *
   * @type {State}
   */
  function declarationOpen(code) {
    if (code === 45) {
      effects.consume(code);
      return commentOpenInside;
    }
    if (code === 91) {
      effects.consume(code);
      index = 0;
      return cdataOpenInside;
    }
    if (asciiAlpha(code)) {
      effects.consume(code);
      return declaration;
    }
    return nok(code);
  }

  /**
   * In a comment, after `<!-`, at another `-`.
   *
   * ```markdown
   * > | a <!--b--> c
   *          ^
   * ```
   *
   * @type {State}
   */
  function commentOpenInside(code) {
    if (code === 45) {
      effects.consume(code);
      return commentEnd;
    }
    return nok(code);
  }

  /**
   * In comment.
   *
   * ```markdown
   * > | a <!--b--> c
   *           ^
   * ```
   *
   * @type {State}
   */
  function comment(code) {
    if (code === null) {
      return nok(code);
    }
    if (code === 45) {
      effects.consume(code);
      return commentClose;
    }
    if (markdownLineEnding(code)) {
      returnState = comment;
      return lineEndingBefore(code);
    }
    effects.consume(code);
    return comment;
  }

  /**
   * In comment, after `-`.
   *
   * ```markdown
   * > | a <!--b--> c
   *             ^
   * ```
   *
   * @type {State}
   */
  function commentClose(code) {
    if (code === 45) {
      effects.consume(code);
      return commentEnd;
    }
    return comment(code);
  }

  /**
   * In comment, after `--`.
   *
   * ```markdown
   * > | a <!--b--> c
   *              ^
   * ```
   *
   * @type {State}
   */
  function commentEnd(code) {
    return code === 62 ? end(code) : code === 45 ? commentClose(code) : comment(code);
  }

  /**
   * After `<![`, in CDATA, expecting `CDATA[`.
   *
   * ```markdown
   * > | a <![CDATA[>&<]]> b
   *          ^^^^^^
   * ```
   *
   * @type {State}
   */
  function cdataOpenInside(code) {
    const value = "CDATA[";
    if (code === value.charCodeAt(index++)) {
      effects.consume(code);
      return index === value.length ? cdata : cdataOpenInside;
    }
    return nok(code);
  }

  /**
   * In CDATA.
   *
   * ```markdown
   * > | a <![CDATA[>&<]]> b
   *                ^^^
   * ```
   *
   * @type {State}
   */
  function cdata(code) {
    if (code === null) {
      return nok(code);
    }
    if (code === 93) {
      effects.consume(code);
      return cdataClose;
    }
    if (markdownLineEnding(code)) {
      returnState = cdata;
      return lineEndingBefore(code);
    }
    effects.consume(code);
    return cdata;
  }

  /**
   * In CDATA, after `]`, at another `]`.
   *
   * ```markdown
   * > | a <![CDATA[>&<]]> b
   *                    ^
   * ```
   *
   * @type {State}
   */
  function cdataClose(code) {
    if (code === 93) {
      effects.consume(code);
      return cdataEnd;
    }
    return cdata(code);
  }

  /**
   * In CDATA, after `]]`, at `>`.
   *
   * ```markdown
   * > | a <![CDATA[>&<]]> b
   *                     ^
   * ```
   *
   * @type {State}
   */
  function cdataEnd(code) {
    if (code === 62) {
      return end(code);
    }
    if (code === 93) {
      effects.consume(code);
      return cdataEnd;
    }
    return cdata(code);
  }

  /**
   * In declaration.
   *
   * ```markdown
   * > | a <!b> c
   *          ^
   * ```
   *
   * @type {State}
   */
  function declaration(code) {
    if (code === null || code === 62) {
      return end(code);
    }
    if (markdownLineEnding(code)) {
      returnState = declaration;
      return lineEndingBefore(code);
    }
    effects.consume(code);
    return declaration;
  }

  /**
   * In instruction.
   *
   * ```markdown
   * > | a <?b?> c
   *         ^
   * ```
   *
   * @type {State}
   */
  function instruction(code) {
    if (code === null) {
      return nok(code);
    }
    if (code === 63) {
      effects.consume(code);
      return instructionClose;
    }
    if (markdownLineEnding(code)) {
      returnState = instruction;
      return lineEndingBefore(code);
    }
    effects.consume(code);
    return instruction;
  }

  /**
   * In instruction, after `?`, at `>`.
   *
   * ```markdown
   * > | a <?b?> c
   *           ^
   * ```
   *
   * @type {State}
   */
  function instructionClose(code) {
    return code === 62 ? end(code) : instruction(code);
  }

  /**
   * After `</`, in closing tag, at tag name.
   *
   * ```markdown
   * > | a </b> c
   *         ^
   * ```
   *
   * @type {State}
   */
  function tagCloseStart(code) {
    // ASCII alphabetical.
    if (asciiAlpha(code)) {
      effects.consume(code);
      return tagClose;
    }
    return nok(code);
  }

  /**
   * After `</x`, in a tag name.
   *
   * ```markdown
   * > | a </b> c
   *          ^
   * ```
   *
   * @type {State}
   */
  function tagClose(code) {
    // ASCII alphanumerical and `-`.
    if (code === 45 || asciiAlphanumeric(code)) {
      effects.consume(code);
      return tagClose;
    }
    return tagCloseBetween(code);
  }

  /**
   * In closing tag, after tag name.
   *
   * ```markdown
   * > | a </b> c
   *          ^
   * ```
   *
   * @type {State}
   */
  function tagCloseBetween(code) {
    if (markdownLineEnding(code)) {
      returnState = tagCloseBetween;
      return lineEndingBefore(code);
    }
    if (markdownSpace(code)) {
      effects.consume(code);
      return tagCloseBetween;
    }
    return end(code);
  }

  /**
   * After `<x`, in opening tag name.
   *
   * ```markdown
   * > | a <b> c
   *         ^
   * ```
   *
   * @type {State}
   */
  function tagOpen(code) {
    // ASCII alphanumerical and `-`.
    if (code === 45 || asciiAlphanumeric(code)) {
      effects.consume(code);
      return tagOpen;
    }
    if (code === 47 || code === 62 || markdownLineEndingOrSpace(code)) {
      return tagOpenBetween(code);
    }
    return nok(code);
  }

  /**
   * In opening tag, after tag name.
   *
   * ```markdown
   * > | a <b> c
   *         ^
   * ```
   *
   * @type {State}
   */
  function tagOpenBetween(code) {
    if (code === 47) {
      effects.consume(code);
      return end;
    }

    // ASCII alphabetical and `:` and `_`.
    if (code === 58 || code === 95 || asciiAlpha(code)) {
      effects.consume(code);
      return tagOpenAttributeName;
    }
    if (markdownLineEnding(code)) {
      returnState = tagOpenBetween;
      return lineEndingBefore(code);
    }
    if (markdownSpace(code)) {
      effects.consume(code);
      return tagOpenBetween;
    }
    return end(code);
  }

  /**
   * In attribute name.
   *
   * ```markdown
   * > | a <b c> d
   *          ^
   * ```
   *
   * @type {State}
   */
  function tagOpenAttributeName(code) {
    // ASCII alphabetical and `-`, `.`, `:`, and `_`.
    if (code === 45 || code === 46 || code === 58 || code === 95 || asciiAlphanumeric(code)) {
      effects.consume(code);
      return tagOpenAttributeName;
    }
    return tagOpenAttributeNameAfter(code);
  }

  /**
   * After attribute name, before initializer, the end of the tag, or
   * whitespace.
   *
   * ```markdown
   * > | a <b c> d
   *           ^
   * ```
   *
   * @type {State}
   */
  function tagOpenAttributeNameAfter(code) {
    if (code === 61) {
      effects.consume(code);
      return tagOpenAttributeValueBefore;
    }
    if (markdownLineEnding(code)) {
      returnState = tagOpenAttributeNameAfter;
      return lineEndingBefore(code);
    }
    if (markdownSpace(code)) {
      effects.consume(code);
      return tagOpenAttributeNameAfter;
    }
    return tagOpenBetween(code);
  }

  /**
   * Before unquoted, double quoted, or single quoted attribute value, allowing
   * whitespace.
   *
   * ```markdown
   * > | a <b c=d> e
   *            ^
   * ```
   *
   * @type {State}
   */
  function tagOpenAttributeValueBefore(code) {
    if (code === null || code === 60 || code === 61 || code === 62 || code === 96) {
      return nok(code);
    }
    if (code === 34 || code === 39) {
      effects.consume(code);
      marker = code;
      return tagOpenAttributeValueQuoted;
    }
    if (markdownLineEnding(code)) {
      returnState = tagOpenAttributeValueBefore;
      return lineEndingBefore(code);
    }
    if (markdownSpace(code)) {
      effects.consume(code);
      return tagOpenAttributeValueBefore;
    }
    effects.consume(code);
    return tagOpenAttributeValueUnquoted;
  }

  /**
   * In double or single quoted attribute value.
   *
   * ```markdown
   * > | a <b c="d"> e
   *             ^
   * ```
   *
   * @type {State}
   */
  function tagOpenAttributeValueQuoted(code) {
    if (code === marker) {
      effects.consume(code);
      marker = undefined;
      return tagOpenAttributeValueQuotedAfter;
    }
    if (code === null) {
      return nok(code);
    }
    if (markdownLineEnding(code)) {
      returnState = tagOpenAttributeValueQuoted;
      return lineEndingBefore(code);
    }
    effects.consume(code);
    return tagOpenAttributeValueQuoted;
  }

  /**
   * In unquoted attribute value.
   *
   * ```markdown
   * > | a <b c=d> e
   *            ^
   * ```
   *
   * @type {State}
   */
  function tagOpenAttributeValueUnquoted(code) {
    if (code === null || code === 34 || code === 39 || code === 60 || code === 61 || code === 96) {
      return nok(code);
    }
    if (code === 47 || code === 62 || markdownLineEndingOrSpace(code)) {
      return tagOpenBetween(code);
    }
    effects.consume(code);
    return tagOpenAttributeValueUnquoted;
  }

  /**
   * After double or single quoted attribute value, before whitespace or the end
   * of the tag.
   *
   * ```markdown
   * > | a <b c="d"> e
   *               ^
   * ```
   *
   * @type {State}
   */
  function tagOpenAttributeValueQuotedAfter(code) {
    if (code === 47 || code === 62 || markdownLineEndingOrSpace(code)) {
      return tagOpenBetween(code);
    }
    return nok(code);
  }

  /**
   * In certain circumstances of a tag where only an `>` is allowed.
   *
   * ```markdown
   * > | a <b c="d"> e
   *               ^
   * ```
   *
   * @type {State}
   */
  function end(code) {
    if (code === 62) {
      effects.consume(code);
      effects.exit("htmlTextData");
      effects.exit("htmlText");
      return ok;
    }
    return nok(code);
  }

  /**
   * At eol.
   *
   * > 👉 **Note**: we can’t have blank lines in text, so no need to worry about
   * > empty tokens.
   *
   * ```markdown
   * > | a <!--a
   *            ^
   *   | b-->
   * ```
   *
   * @type {State}
   */
  function lineEndingBefore(code) {
    effects.exit("htmlTextData");
    effects.enter("lineEnding");
    effects.consume(code);
    effects.exit("lineEnding");
    return lineEndingAfter;
  }

  /**
   * After eol, at optional whitespace.
   *
   * > 👉 **Note**: we can’t have blank lines in text, so no need to worry about
   * > empty tokens.
   *
   * ```markdown
   *   | a <!--a
   * > | b-->
   *     ^
   * ```
   *
   * @type {State}
   */
  function lineEndingAfter(code) {
    // Always populated by defaults.

    return markdownSpace(code) ? factorySpace(effects, lineEndingAfterPrefix, "linePrefix", self.parser.constructs.disable.null.includes('codeIndented') ? undefined : 4)(code) : lineEndingAfterPrefix(code);
  }

  /**
   * After eol, after optional whitespace.
   *
   * > 👉 **Note**: we can’t have blank lines in text, so no need to worry about
   * > empty tokens.
   *
   * ```markdown
   *   | a <!--a
   * > | b-->
   *     ^
   * ```
   *
   * @type {State}
   */
  function lineEndingAfterPrefix(code) {
    effects.enter("htmlTextData");
    return returnState(code);
  }
}/**
 * @import {
 *   Construct,
 *   Event,
 *   Resolver,
 *   State,
 *   TokenizeContext,
 *   Tokenizer,
 *   Token
 * } from 'micromark-util-types'
 */

import { factoryDestination } from 'micromark-factory-destination';
import { factoryLabel } from 'micromark-factory-label';
import { factoryTitle } from 'micromark-factory-title';
import { factoryWhitespace } from 'micromark-factory-whitespace';
import { markdownLineEndingOrSpace } from 'micromark-util-character';
import { push, splice } from 'micromark-util-chunked';
import { normalizeIdentifier } from 'micromark-util-normalize-identifier';
import { resolveAll } from 'micromark-util-resolve-all';
/** @type {Construct} */
export const labelEnd = {
  name: 'labelEnd',
  resolveAll: resolveAllLabelEnd,
  resolveTo: resolveToLabelEnd,
  tokenize: tokenizeLabelEnd
};

/** @type {Construct} */
const resourceConstruct = {
  tokenize: tokenizeResource
};
/** @type {Construct} */
const referenceFullConstruct = {
  tokenize: tokenizeReferenceFull
};
/** @type {Construct} */
const referenceCollapsedConstruct = {
  tokenize: tokenizeReferenceCollapsed
};

/** @type {Resolver} */
function resolveAllLabelEnd(events) {
  let index = -1;
  /** @type {Array<Event>} */
  const newEvents = [];
  while (++index < events.length) {
    const token = events[index][1];
    newEvents.push(events[index]);
    if (token.type === "labelImage" || token.type === "labelLink" || token.type === "labelEnd") {
      // Remove the marker.
      const offset = token.type === "labelImage" ? 4 : 2;
      token.type = "data";
      index += offset;
    }
  }

  // If the events are equal, we don't have to copy newEvents to events
  if (events.length !== newEvents.length) {
    splice(events, 0, events.length, newEvents);
  }
  return events;
}

/** @type {Resolver} */
function resolveToLabelEnd(events, context) {
  let index = events.length;
  let offset = 0;
  /** @type {Token} */
  let token;
  /** @type {number | undefined} */
  let open;
  /** @type {number | undefined} */
  let close;
  /** @type {Array<Event>} */
  let media;

  // Find an opening.
  while (index--) {
    token = events[index][1];
    if (open) {
      // If we see another link, or inactive link label, we’ve been here before.
      if (token.type === "link" || token.type === "labelLink" && token._inactive) {
        break;
      }

      // Mark other link openings as inactive, as we can’t have links in
      // links.
      if (events[index][0] === 'enter' && token.type === "labelLink") {
        token._inactive = true;
      }
    } else if (close) {
      if (events[index][0] === 'enter' && (token.type === "labelImage" || token.type === "labelLink") && !token._balanced) {
        open = index;
        if (token.type !== "labelLink") {
          offset = 2;
          break;
        }
      }
    } else if (token.type === "labelEnd") {
      close = index;
    }
  }
  const group = {
    type: events[open][1].type === "labelLink" ? "link" : "image",
    start: {
      ...events[open][1].start
    },
    end: {
      ...events[events.length - 1][1].end
    }
  };
  const label = {
    type: "label",
    start: {
      ...events[open][1].start
    },
    end: {
      ...events[close][1].end
    }
  };
  const text = {
    type: "labelText",
    start: {
      ...events[open + offset + 2][1].end
    },
    end: {
      ...events[close - 2][1].start
    }
  };
  media = [['enter', group, context], ['enter', label, context]];

  // Opening marker.
  media = push(media, events.slice(open + 1, open + offset + 3));

  // Text open.
  media = push(media, [['enter', text, context]]);

  // Always populated by defaults.

  // Between.
  media = push(media, resolveAll(context.parser.constructs.insideSpan.null, events.slice(open + offset + 4, close - 3), context));

  // Text close, marker close, label close.
  media = push(media, [['exit', text, context], events[close - 2], events[close - 1], ['exit', label, context]]);

  // Reference, resource, or so.
  media = push(media, events.slice(close + 1));

  // Media close.
  media = push(media, [['exit', group, context]]);
  splice(events, open, events.length, media);
  return events;
}

/**
 * @this {TokenizeContext}
 *   Context.
 * @type {Tokenizer}
 */
function tokenizeLabelEnd(effects, ok, nok) {
  const self = this;
  let index = self.events.length;
  /** @type {Token} */
  let labelStart;
  /** @type {boolean} */
  let defined;

  // Find an opening.
  while (index--) {
    if ((self.events[index][1].type === "labelImage" || self.events[index][1].type === "labelLink") && !self.events[index][1]._balanced) {
      labelStart = self.events[index][1];
      break;
    }
  }
  return start;

  /**
   * Start of label end.
   *
   * ```markdown
   * > | [a](b) c
   *       ^
   * > | [a][b] c
   *       ^
   * > | [a][] b
   *       ^
   * > | [a] b
   * ```
   *
   * @type {State}
   */
  function start(code) {
    // If there is not an okay opening.
    if (!labelStart) {
      return nok(code);
    }

    // If the corresponding label (link) start is marked as inactive,
    // it means we’d be wrapping a link, like this:
    //
    // ```markdown
    // > | a [b [c](d) e](f) g.
    //                  ^
    // ```
    //
    // We can’t have that, so it’s just balanced brackets.
    if (labelStart._inactive) {
      return labelEndNok(code);
    }
    defined = self.parser.defined.includes(normalizeIdentifier(self.sliceSerialize({
      start: labelStart.end,
      end: self.now()
    })));
    effects.enter("labelEnd");
    effects.enter("labelMarker");
    effects.consume(code);
    effects.exit("labelMarker");
    effects.exit("labelEnd");
    return after;
  }

  /**
   * After `]`.
   *
   * ```markdown
   * > | [a](b) c
   *       ^
   * > | [a][b] c
   *       ^
   * > | [a][] b
   *       ^
   * > | [a] b
   *       ^
   * ```
   *
   * @type {State}
   */
  function after(code) {
    // Note: `markdown-rs` also parses GFM footnotes here, which for us is in
    // an extension.

    // Resource (`[asd](fgh)`)?
    if (code === 40) {
      return effects.attempt(resourceConstruct, labelEndOk, defined ? labelEndOk : labelEndNok)(code);
    }

    // Full (`[asd][fgh]`) or collapsed (`[asd][]`) reference?
    if (code === 91) {
      return effects.attempt(referenceFullConstruct, labelEndOk, defined ? referenceNotFull : labelEndNok)(code);
    }

    // Shortcut (`[asd]`) reference?
    return defined ? labelEndOk(code) : labelEndNok(code);
  }

  /**
   * After `]`, at `[`, but not at a full reference.
   *
   * > 👉 **Note**: we only get here if the label is defined.
   *
   * ```markdown
   * > | [a][] b
   *        ^
   * > | [a] b
   *        ^
   * ```
   *
   * @type {State}
   */
  function referenceNotFull(code) {
    return effects.attempt(referenceCollapsedConstruct, labelEndOk, labelEndNok)(code);
  }

  /**
   * Done, we found something.
   *
   * ```markdown
   * > | [a](b) c
   *           ^
   * > | [a][b] c
   *           ^
   * > | [a][] b
   *          ^
   * > | [a] b
   *        ^
   * ```
   *
   * @type {State}
   */
  function labelEndOk(code) {
    // Note: `markdown-rs` does a bunch of stuff here.
    return ok(code);
  }

  /**
   * Done, it’s nothing.
   *
   * There was an okay opening, but we didn’t match anything.
   *
   * ```markdown
   * > | [a](b c
   *        ^
   * > | [a][b c
   *        ^
   * > | [a] b
   *        ^
   * ```
   *
   * @type {State}
   */
  function labelEndNok(code) {
    labelStart._balanced = true;
    return nok(code);
  }
}

/**
 * @this {TokenizeContext}
 *   Context.
 * @type {Tokenizer}
 */
function tokenizeResource(effects, ok, nok) {
  return resourceStart;

  /**
   * At a resource.
   *
   * ```markdown
   * > | [a](b) c
   *        ^
   * ```
   *
   * @type {State}
   */
  function resourceStart(code) {
    effects.enter("resource");
    effects.enter("resourceMarker");
    effects.consume(code);
    effects.exit("resourceMarker");
    return resourceBefore;
  }

  /**
   * In resource, after `(`, at optional whitespace.
   *
   * ```markdown
   * > | [a](b) c
   *         ^
   * ```
   *
   * @type {State}
   */
  function resourceBefore(code) {
    return markdownLineEndingOrSpace(code) ? factoryWhitespace(effects, resourceOpen)(code) : resourceOpen(code);
  }

  /**
   * In resource, after optional whitespace, at `)` or a destination.
   *
   * ```markdown
   * > | [a](b) c
   *         ^
   * ```
   *
   * @type {State}
   */
  function resourceOpen(code) {
    if (code === 41) {
      return resourceEnd(code);
    }
    return factoryDestination(effects, resourceDestinationAfter, resourceDestinationMissing, "resourceDestination", "resourceDestinationLiteral", "resourceDestinationLiteralMarker", "resourceDestinationRaw", "resourceDestinationString", 32)(code);
  }

  /**
   * In resource, after destination, at optional whitespace.
   *
   * ```markdown
   * > | [a](b) c
   *          ^
   * ```
   *
   * @type {State}
   */
  function resourceDestinationAfter(code) {
    return markdownLineEndingOrSpace(code) ? factoryWhitespace(effects, resourceBetween)(code) : resourceEnd(code);
  }

  /**
   * At invalid destination.
   *
   * ```markdown
   * > | [a](<<) b
   *         ^
   * ```
   *
   * @type {State}
   */
  function resourceDestinationMissing(code) {
    return nok(code);
  }

  /**
   * In resource, after destination and whitespace, at `(` or title.
   *
   * ```markdown
   * > | [a](b ) c
   *           ^
   * ```
   *
   * @type {State}
   */
  function resourceBetween(code) {
    if (code === 34 || code === 39 || code === 40) {
      return factoryTitle(effects, resourceTitleAfter, nok, "resourceTitle", "resourceTitleMarker", "resourceTitleString")(code);
    }
    return resourceEnd(code);
  }

  /**
   * In resource, after title, at optional whitespace.
   *
   * ```markdown
   * > | [a](b "c") d
   *              ^
   * ```
   *
   * @type {State}
   */
  function resourceTitleAfter(code) {
    return markdownLineEndingOrSpace(code) ? factoryWhitespace(effects, resourceEnd)(code) : resourceEnd(code);
  }

  /**
   * In resource, at `)`.
   *
   * ```markdown
   * > | [a](b) d
   *          ^
   * ```
   *
   * @type {State}
   */
  function resourceEnd(code) {
    if (code === 41) {
      effects.enter("resourceMarker");
      effects.consume(code);
      effects.exit("resourceMarker");
      effects.exit("resource");
      return ok;
    }
    return nok(code);
  }
}

/**
 * @this {TokenizeContext}
 *   Context.
 * @type {Tokenizer}
 */
function tokenizeReferenceFull(effects, ok, nok) {
  const self = this;
  return referenceFull;

  /**
   * In a reference (full), at the `[`.
   *
   * ```markdown
   * > | [a][b] d
   *        ^
   * ```
   *
   * @type {State}
   */
  function referenceFull(code) {
    return factoryLabel.call(self, effects, referenceFullAfter, referenceFullMissing, "reference", "referenceMarker", "referenceString")(code);
  }

  /**
   * In a reference (full), after `]`.
   *
   * ```markdown
   * > | [a][b] d
   *          ^
   * ```
   *
   * @type {State}
   */
  function referenceFullAfter(code) {
    return self.parser.defined.includes(normalizeIdentifier(self.sliceSerialize(self.events[self.events.length - 1][1]).slice(1, -1))) ? ok(code) : nok(code);
  }

  /**
   * In reference (full) that was missing.
   *
   * ```markdown
   * > | [a][b d
   *        ^
   * ```
   *
   * @type {State}
   */
  function referenceFullMissing(code) {
    return nok(code);
  }
}

/**
 * @this {TokenizeContext}
 *   Context.
 * @type {Tokenizer}
 */
function tokenizeReferenceCollapsed(effects, ok, nok) {
  return referenceCollapsedStart;

  /**
   * In reference (collapsed), at `[`.
   *
   * > 👉 **Note**: we only get here if the label is defined.
   *
   * ```markdown
   * > | [a][] d
   *        ^
   * ```
   *
   * @type {State}
   */
  function referenceCollapsedStart(code) {
    // We only attempt a collapsed label if there’s a `[`.

    effects.enter("reference");
    effects.enter("referenceMarker");
    effects.consume(code);
    effects.exit("referenceMarker");
    return referenceCollapsedOpen;
  }

  /**
   * In reference (collapsed), at `]`.
   *
   * > 👉 **Note**: we only get here if the label is defined.
   *
   * ```markdown
   * > | [a][] d
   *         ^
   * ```
   *
   *  @type {State}
   */
  function referenceCollapsedOpen(code) {
    if (code === 93) {
      effects.enter("referenceMarker");
      effects.consume(code);
      effects.exit("referenceMarker");
      effects.exit("reference");
      return ok;
    }
    return nok(code);
  }
}/**
 * @import {
 *   Construct,
 *   State,
 *   TokenizeContext,
 *   Tokenizer
 * } from 'micromark-util-types'
 */

import { labelEnd } from './label-end.js';

/** @type {Construct} */
export const labelStartImage = {
  name: 'labelStartImage',
  resolveAll: labelEnd.resolveAll,
  tokenize: tokenizeLabelStartImage
};

/**
 * @this {TokenizeContext}
 *   Context.
 * @type {Tokenizer}
 */
function tokenizeLabelStartImage(effects, ok, nok) {
  const self = this;
  return start;

  /**
   * Start of label (image) start.
   *
   * ```markdown
   * > | a ![b] c
   *       ^
   * ```
   *
   * @type {State}
   */
  function start(code) {
    effects.enter("labelImage");
    effects.enter("labelImageMarker");
    effects.consume(code);
    effects.exit("labelImageMarker");
    return open;
  }

  /**
   * After `!`, at `[`.
   *
   * ```markdown
   * > | a ![b] c
   *        ^
   * ```
   *
   * @type {State}
   */
  function open(code) {
    if (code === 91) {
      effects.enter("labelMarker");
      effects.consume(code);
      effects.exit("labelMarker");
      effects.exit("labelImage");
      return after;
    }
    return nok(code);
  }

  /**
   * After `![`.
   *
   * ```markdown
   * > | a ![b] c
   *         ^
   * ```
   *
   * This is needed in because, when GFM footnotes are enabled, images never
   * form when started with a `^`.
   * Instead, links form:
   *
   * ```markdown
   * ![^a](b)
   *
   * ![^a][b]
   *
   * [b]: c
   * ```
   *
   * ```html
   * <p>!<a href=\"b\">^a</a></p>
   * <p>!<a href=\"c\">^a</a></p>
   * ```
   *
   * @type {State}
   */
  function after(code) {
    // To do: use a new field to do this, this is still needed for
    // `micromark-extension-gfm-footnote`, but the `label-start-link`
    // behavior isn’t.
    // Hidden footnotes hook.
    /* c8 ignore next 3 */
    return code === 94 && '_hiddenFootnoteSupport' in self.parser.constructs ? nok(code) : ok(code);
  }
}/**
 * @import {
 *   Construct,
 *   State,
 *   TokenizeContext,
 *   Tokenizer
 * } from 'micromark-util-types'
 */

import { labelEnd } from './label-end.js';

/** @type {Construct} */
export const labelStartLink = {
  name: 'labelStartLink',
  resolveAll: labelEnd.resolveAll,
  tokenize: tokenizeLabelStartLink
};

/**
 * @this {TokenizeContext}
 *   Context.
 * @type {Tokenizer}
 */
function tokenizeLabelStartLink(effects, ok, nok) {
  const self = this;
  return start;

  /**
   * Start of label (link) start.
   *
   * ```markdown
   * > | a [b] c
   *       ^
   * ```
   *
   * @type {State}
   */
  function start(code) {
    effects.enter("labelLink");
    effects.enter("labelMarker");
    effects.consume(code);
    effects.exit("labelMarker");
    effects.exit("labelLink");
    return after;
  }

  /** @type {State} */
  function after(code) {
    // To do: this isn’t needed in `micromark-extension-gfm-footnote`,
    // remove.
    // Hidden footnotes hook.
    /* c8 ignore next 3 */
    return code === 94 && '_hiddenFootnoteSupport' in self.parser.constructs ? nok(code) : ok(code);
  }
}/**
 * @import {
 *   Construct,
 *   State,
 *   TokenizeContext,
 *   Tokenizer
 * } from 'micromark-util-types'
 */

import { factorySpace } from 'micromark-factory-space';
import { markdownLineEnding } from 'micromark-util-character';
/** @type {Construct} */
export const lineEnding = {
  name: 'lineEnding',
  tokenize: tokenizeLineEnding
};

/**
 * @this {TokenizeContext}
 *   Context.
 * @type {Tokenizer}
 */
function tokenizeLineEnding(effects, ok) {
  return start;

  /** @type {State} */
  function start(code) {
    effects.enter("lineEnding");
    effects.consume(code);
    effects.exit("lineEnding");
    return factorySpace(effects, ok, "linePrefix");
  }
}/**
 * @import {
 *   Code,
 *   Construct,
 *   Exiter,
 *   State,
 *   TokenizeContext,
 *   Tokenizer
 * } from 'micromark-util-types'
 */

import { factorySpace } from 'micromark-factory-space';
import { asciiDigit, markdownSpace } from 'micromark-util-character';
import { blankLine } from './blank-line.js';
import { thematicBreak } from './thematic-break.js';

/** @type {Construct} */
export const list = {
  continuation: {
    tokenize: tokenizeListContinuation
  },
  exit: tokenizeListEnd,
  name: 'list',
  tokenize: tokenizeListStart
};

/** @type {Construct} */
const listItemPrefixWhitespaceConstruct = {
  partial: true,
  tokenize: tokenizeListItemPrefixWhitespace
};

/** @type {Construct} */
const indentConstruct = {
  partial: true,
  tokenize: tokenizeIndent
};

// To do: `markdown-rs` parses list items on their own and later stitches them
// together.

/**
 * @this {TokenizeContext}
 *   Context.
 * @type {Tokenizer}
 */
function tokenizeListStart(effects, ok, nok) {
  const self = this;
  const tail = self.events[self.events.length - 1];
  let initialSize = tail && tail[1].type === "linePrefix" ? tail[2].sliceSerialize(tail[1], true).length : 0;
  let size = 0;
  return start;

  /** @type {State} */
  function start(code) {
    const kind = self.containerState.type || (code === 42 || code === 43 || code === 45 ? "listUnordered" : "listOrdered");
    if (kind === "listUnordered" ? !self.containerState.marker || code === self.containerState.marker : asciiDigit(code)) {
      if (!self.containerState.type) {
        self.containerState.type = kind;
        effects.enter(kind, {
          _container: true
        });
      }
      if (kind === "listUnordered") {
        effects.enter("listItemPrefix");
        return code === 42 || code === 45 ? effects.check(thematicBreak, nok, atMarker)(code) : atMarker(code);
      }
      if (!self.interrupt || code === 49) {
        effects.enter("listItemPrefix");
        effects.enter("listItemValue");
        return inside(code);
      }
    }
    return nok(code);
  }

  /** @type {State} */
  function inside(code) {
    if (asciiDigit(code) && ++size < 10) {
      effects.consume(code);
      return inside;
    }
    if ((!self.interrupt || size < 2) && (self.containerState.marker ? code === self.containerState.marker : code === 41 || code === 46)) {
      effects.exit("listItemValue");
      return atMarker(code);
    }
    return nok(code);
  }

  /**
   * @type {State}
   **/
  function atMarker(code) {
    effects.enter("listItemMarker");
    effects.consume(code);
    effects.exit("listItemMarker");
    self.containerState.marker = self.containerState.marker || code;
    return effects.check(blankLine,
    // Can’t be empty when interrupting.
    self.interrupt ? nok : onBlank, effects.attempt(listItemPrefixWhitespaceConstruct, endOfPrefix, otherPrefix));
  }

  /** @type {State} */
  function onBlank(code) {
    self.containerState.initialBlankLine = true;
    initialSize++;
    return endOfPrefix(code);
  }

  /** @type {State} */
  function otherPrefix(code) {
    if (markdownSpace(code)) {
      effects.enter("listItemPrefixWhitespace");
      effects.consume(code);
      effects.exit("listItemPrefixWhitespace");
      return endOfPrefix;
    }
    return nok(code);
  }

  /** @type {State} */
  function endOfPrefix(code) {
    self.containerState.size = initialSize + self.sliceSerialize(effects.exit("listItemPrefix"), true).length;
    return ok(code);
  }
}

/**
 * @this {TokenizeContext}
 *   Context.
 * @type {Tokenizer}
 */
function tokenizeListContinuation(effects, ok, nok) {
  const self = this;
  self.containerState._closeFlow = undefined;
  return effects.check(blankLine, onBlank, notBlank);

  /** @type {State} */
  function onBlank(code) {
    self.containerState.furtherBlankLines = self.containerState.furtherBlankLines || self.containerState.initialBlankLine;

    // We have a blank line.
    // Still, try to consume at most the items size.
    return factorySpace(effects, ok, "listItemIndent", self.containerState.size + 1)(code);
  }

  /** @type {State} */
  function notBlank(code) {
    if (self.containerState.furtherBlankLines || !markdownSpace(code)) {
      self.containerState.furtherBlankLines = undefined;
      self.containerState.initialBlankLine = undefined;
      return notInCurrentItem(code);
    }
    self.containerState.furtherBlankLines = undefined;
    self.containerState.initialBlankLine = undefined;
    return effects.attempt(indentConstruct, ok, notInCurrentItem)(code);
  }

  /** @type {State} */
  function notInCurrentItem(code) {
    // While we do continue, we signal that the flow should be closed.
    self.containerState._closeFlow = true;
    // As we’re closing flow, we’re no longer interrupting.
    self.interrupt = undefined;
    // Always populated by defaults.

    return factorySpace(effects, effects.attempt(list, ok, nok), "linePrefix", self.parser.constructs.disable.null.includes('codeIndented') ? undefined : 4)(code);
  }
}

/**
 * @this {TokenizeContext}
 *   Context.
 * @type {Tokenizer}
 */
function tokenizeIndent(effects, ok, nok) {
  const self = this;
  return factorySpace(effects, afterPrefix, "listItemIndent", self.containerState.size + 1);

  /** @type {State} */
  function afterPrefix(code) {
    const tail = self.events[self.events.length - 1];
    return tail && tail[1].type === "listItemIndent" && tail[2].sliceSerialize(tail[1], true).length === self.containerState.size ? ok(code) : nok(code);
  }
}

/**
 * @this {TokenizeContext}
 *   Context.
 * @type {Exiter}
 */
function tokenizeListEnd(effects) {
  effects.exit(this.containerState.type);
}

/**
 * @this {TokenizeContext}
 *   Context.
 * @type {Tokenizer}
 */
function tokenizeListItemPrefixWhitespace(effects, ok, nok) {
  const self = this;

  // Always populated by defaults.

  return factorySpace(effects, afterPrefix, "listItemPrefixWhitespace", self.parser.constructs.disable.null.includes('codeIndented') ? undefined : 4 + 1);

  /** @type {State} */
  function afterPrefix(code) {
    const tail = self.events[self.events.length - 1];
    return !markdownSpace(code) && tail && tail[1].type === "listItemPrefixWhitespace" ? ok(code) : nok(code);
  }
}/**
 * @import {
 *   Code,
 *   Construct,
 *   Resolver,
 *   State,
 *   TokenizeContext,
 *   Tokenizer
 * } from 'micromark-util-types'
 */

import { factorySpace } from 'micromark-factory-space';
import { markdownLineEnding, markdownSpace } from 'micromark-util-character';
/** @type {Construct} */
export const setextUnderline = {
  name: 'setextUnderline',
  resolveTo: resolveToSetextUnderline,
  tokenize: tokenizeSetextUnderline
};

/** @type {Resolver} */
function resolveToSetextUnderline(events, context) {
  // To do: resolve like `markdown-rs`.
  let index = events.length;
  /** @type {number | undefined} */
  let content;
  /** @type {number | undefined} */
  let text;
  /** @type {number | undefined} */
  let definition;

  // Find the opening of the content.
  // It’ll always exist: we don’t tokenize if it isn’t there.
  while (index--) {
    if (events[index][0] === 'enter') {
      if (events[index][1].type === "content") {
        content = index;
        break;
      }
      if (events[index][1].type === "paragraph") {
        text = index;
      }
    }
    // Exit
    else {
      if (events[index][1].type === "content") {
        // Remove the content end (if needed we’ll add it later)
        events.splice(index, 1);
      }
      if (!definition && events[index][1].type === "definition") {
        definition = index;
      }
    }
  }
  const heading = {
    type: "setextHeading",
    start: {
      ...events[content][1].start
    },
    end: {
      ...events[events.length - 1][1].end
    }
  };

  // Change the paragraph to setext heading text.
  events[text][1].type = "setextHeadingText";

  // If we have definitions in the content, we’ll keep on having content,
  // but we need move it.
  if (definition) {
    events.splice(text, 0, ['enter', heading, context]);
    events.splice(definition + 1, 0, ['exit', events[content][1], context]);
    events[content][1].end = {
      ...events[definition][1].end
    };
  } else {
    events[content][1] = heading;
  }

  // Add the heading exit at the end.
  events.push(['exit', heading, context]);
  return events;
}

/**
 * @this {TokenizeContext}
 *   Context.
 * @type {Tokenizer}
 */
function tokenizeSetextUnderline(effects, ok, nok) {
  const self = this;
  /** @type {NonNullable<Code>} */
  let marker;
  return start;

  /**
   * At start of heading (setext) underline.
   *
   * ```markdown
   *   | aa
   * > | ==
   *     ^
   * ```
   *
   * @type {State}
   */
  function start(code) {
    let index = self.events.length;
    /** @type {boolean | undefined} */
    let paragraph;
    // Find an opening.
    while (index--) {
      // Skip enter/exit of line ending, line prefix, and content.
      // We can now either have a definition or a paragraph.
      if (self.events[index][1].type !== "lineEnding" && self.events[index][1].type !== "linePrefix" && self.events[index][1].type !== "content") {
        paragraph = self.events[index][1].type === "paragraph";
        break;
      }
    }

    // To do: handle lazy/pierce like `markdown-rs`.
    // To do: parse indent like `markdown-rs`.
    if (!self.parser.lazy[self.now().line] && (self.interrupt || paragraph)) {
      effects.enter("setextHeadingLine");
      marker = code;
      return before(code);
    }
    return nok(code);
  }

  /**
   * After optional whitespace, at `-` or `=`.
   *
   * ```markdown
   *   | aa
   * > | ==
   *     ^
   * ```
   *
   * @type {State}
   */
  function before(code) {
    effects.enter("setextHeadingLineSequence");
    return inside(code);
  }

  /**
   * In sequence.
   *
   * ```markdown
   *   | aa
   * > | ==
   *     ^
   * ```
   *
   * @type {State}
   */
  function inside(code) {
    if (code === marker) {
      effects.consume(code);
      return inside;
    }
    effects.exit("setextHeadingLineSequence");
    return markdownSpace(code) ? factorySpace(effects, after, "lineSuffix")(code) : after(code);
  }

  /**
   * After sequence, after optional whitespace.
   *
   * ```markdown
   *   | aa
   * > | ==
   *       ^
   * ```
   *
   * @type {State}
   */
  function after(code) {
    if (code === null || markdownLineEnding(code)) {
      effects.exit("setextHeadingLine");
      return ok(code);
    }
    return nok(code);
  }
}/**
 * @import {
 *   Code,
 *   Construct,
 *   State,
 *   TokenizeContext,
 *   Tokenizer
 * } from 'micromark-util-types'
 */

import { factorySpace } from 'micromark-factory-space';
import { markdownLineEnding, markdownSpace } from 'micromark-util-character';
/** @type {Construct} */
export const thematicBreak = {
  name: 'thematicBreak',
  tokenize: tokenizeThematicBreak
};

/**
 * @this {TokenizeContext}
 *   Context.
 * @type {Tokenizer}
 */
function tokenizeThematicBreak(effects, ok, nok) {
  let size = 0;
  /** @type {NonNullable<Code>} */
  let marker;
  return start;

  /**
   * Start of thematic break.
   *
   * ```markdown
   * > | ***
   *     ^
   * ```
   *
   * @type {State}
   */
  function start(code) {
    effects.enter("thematicBreak");
    // To do: parse indent like `markdown-rs`.
    return before(code);
  }

  /**
   * After optional whitespace, at marker.
   *
   * ```markdown
   * > | ***
   *     ^
   * ```
   *
   * @type {State}
   */
  function before(code) {
    marker = code;
    return atBreak(code);
  }

  /**
   * After something, before something else.
   *
   * ```markdown
   * > | ***
   *     ^
   * ```
   *
   * @type {State}
   */
  function atBreak(code) {
    if (code === marker) {
      effects.enter("thematicBreakSequence");
      return sequence(code);
    }
    if (size >= 3 && (code === null || markdownLineEnding(code))) {
      effects.exit("thematicBreak");
      return ok(code);
    }
    return nok(code);
  }

  /**
   * In sequence.
   *
   * ```markdown
   * > | ***
   *     ^
   * ```
   *
   * @type {State}
   */
  function sequence(code) {
    if (code === marker) {
      effects.consume(code);
      size++;
      return sequence;
    }
    effects.exit("thematicBreakSequence");
    return markdownSpace(code) ? factorySpace(effects, atBreak, "whitespace")(code) : atBreak(code);
  }
}{
  "name": "micromark-core-commonmark",
  "version": "2.0.3",
  "description": "The CommonMark markdown constructs",
  "license": "MIT",
  "keywords": [
    "micromark",
    "core",
    "commonmark"
  ],
  "repository": "https://github.com/micromark/micromark/tree/main/packages/micromark-core-commonmark",
  "bugs": "https://github.com/micromark/micromark/issues",
  "funding": [
    {
      "type": "GitHub Sponsors",
      "url": "https://github.com/sponsors/unifiedjs"
    },
    {
      "type": "OpenCollective",
      "url": "https://opencollective.com/unified"
    }
  ],
  "author": "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)",
  "contributors": [
    "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)"
  ],
  "sideEffects": false,
  "type": "module",
  "files": [
    "dev/",
    "lib/",
    "index.d.ts.map",
    "index.d.ts",
    "index.js"
  ],
  "exports": {
    "development": "./dev/index.js",
    "default": "./index.js"
  },
  "dependencies": {
    "decode-named-character-reference": "^1.0.0",
    "devlop": "^1.0.0",
    "micromark-factory-destination": "^2.0.0",
    "micromark-factory-label": "^2.0.0",
    "micromark-factory-space": "^2.0.0",
    "micromark-factory-title": "^2.0.0",
    "micromark-factory-whitespace": "^2.0.0",
    "micromark-util-character": "^2.0.0",
    "micromark-util-chunked": "^2.0.0",
    "micromark-util-classify-character": "^2.0.0",
    "micromark-util-html-tag-name": "^2.0.0",
    "micromark-util-normalize-identifier": "^2.0.0",
    "micromark-util-resolve-all": "^2.0.0",
    "micromark-util-subtokenize": "^2.0.0",
    "micromark-util-symbol": "^2.0.0",
    "micromark-util-types": "^2.0.0"
  },
  "scripts": {
    "build": "micromark-build"
  },
  "xo": {
    "envs": [
      "shared-node-browser"
    ],
    "prettier": true,
    "rules": {
      "logical-assignment-operators": "off",
      "max-depth": "off",
      "unicorn/no-this-assignment": "off",
      "unicorn/prefer-at": "off",
      "unicorn/prefer-code-point": "off"
    }
  }
}
// Note: more types exported from `index.d.ts`.
export { directive } from './lib/syntax.js';
export { directiveHtml } from './lib/html.js';/**
 * @import {Construct, State, Token, TokenizeContext, Tokenizer} from 'micromark-util-types'
 */

import { factorySpace } from 'micromark-factory-space';
import { markdownLineEnding } from 'micromark-util-character';
import { factoryAttributes } from './factory-attributes.js';
import { factoryLabel } from './factory-label.js';
import { factoryName } from './factory-name.js';

/** @type {Construct} */
export const directiveContainer = {
  tokenize: tokenizeDirectiveContainer,
  concrete: true
};
const label = {
  tokenize: tokenizeLabel,
  partial: true
};
const attributes = {
  tokenize: tokenizeAttributes,
  partial: true
};
const nonLazyLine = {
  tokenize: tokenizeNonLazyLine,
  partial: true
};

/**
 * @this {TokenizeContext}
 * @type {Tokenizer}
 */
function tokenizeDirectiveContainer(effects, ok, nok) {
  const self = this;
  const tail = self.events[self.events.length - 1];
  const initialSize = tail && tail[1].type === "linePrefix" ? tail[2].sliceSerialize(tail[1], true).length : 0;
  let sizeOpen = 0;
  /** @type {Token} */
  let previous;
  return start;

  /** @type {State} */
  function start(code) {
    effects.enter('directiveContainer');
    effects.enter('directiveContainerFence');
    effects.enter('directiveContainerSequence');
    return sequenceOpen(code);
  }

  /** @type {State} */
  function sequenceOpen(code) {
    if (code === 58) {
      effects.consume(code);
      sizeOpen++;
      return sequenceOpen;
    }
    if (sizeOpen < 3) {
      return nok(code);
    }
    effects.exit('directiveContainerSequence');
    return factoryName.call(self, effects, afterName, nok, 'directiveContainerName')(code);
  }

  /** @type {State} */
  function afterName(code) {
    return code === 91 ? effects.attempt(label, afterLabel, afterLabel)(code) : afterLabel(code);
  }

  /** @type {State} */
  function afterLabel(code) {
    return code === 123 ? effects.attempt(attributes, afterAttributes, afterAttributes)(code) : afterAttributes(code);
  }

  /** @type {State} */
  function afterAttributes(code) {
    return factorySpace(effects, openAfter, "whitespace")(code);
  }

  /** @type {State} */
  function openAfter(code) {
    effects.exit('directiveContainerFence');
    if (code === null) {
      return after(code);
    }
    if (markdownLineEnding(code)) {
      if (self.interrupt) {
        return ok(code);
      }
      return effects.attempt(nonLazyLine, contentStart, after)(code);
    }
    return nok(code);
  }

  /** @type {State} */
  function contentStart(code) {
    if (code === null) {
      return after(code);
    }
    if (markdownLineEnding(code)) {
      return effects.check(nonLazyLine, emptyContentNonLazyLineAfter, after)(code);
    }
    effects.enter('directiveContainerContent');
    return lineStart(code);
  }

  /** @type {State} */
  function lineStart(code) {
    return effects.attempt({
      tokenize: tokenizeClosingFence,
      partial: true
    }, afterContent, initialSize ? factorySpace(effects, chunkStart, "linePrefix", initialSize + 1) : chunkStart)(code);
  }

  /** @type {State} */
  function chunkStart(code) {
    if (code === null) {
      return afterContent(code);
    }
    if (markdownLineEnding(code)) {
      return effects.check(nonLazyLine, chunkNonLazyStart, afterContent)(code);
    }
    return chunkNonLazyStart(code);
  }

  /** @type {State} */
  function contentContinue(code) {
    if (code === null) {
      const t = effects.exit("chunkDocument");
      self.parser.lazy[t.start.line] = false;
      return afterContent(code);
    }
    if (markdownLineEnding(code)) {
      return effects.check(nonLazyLine, nonLazyLineAfter, lineAfter)(code);
    }
    effects.consume(code);
    return contentContinue;
  }

  /** @type {State} */
  function chunkNonLazyStart(code) {
    const token = effects.enter("chunkDocument", {
      contentType: "document",
      previous
    });
    if (previous) previous.next = token;
    previous = token;
    return contentContinue(code);
  }

  /** @type {State} */
  function emptyContentNonLazyLineAfter(code) {
    effects.enter('directiveContainerContent');
    return lineStart(code);
  }

  /** @type {State} */
  function nonLazyLineAfter(code) {
    effects.consume(code);
    const t = effects.exit("chunkDocument");
    self.parser.lazy[t.start.line] = false;
    return lineStart;
  }

  /** @type {State} */
  function lineAfter(code) {
    const t = effects.exit("chunkDocument");
    self.parser.lazy[t.start.line] = false;
    return afterContent(code);
  }

  /** @type {State} */
  function afterContent(code) {
    effects.exit('directiveContainerContent');
    return after(code);
  }

  /** @type {State} */
  function after(code) {
    effects.exit('directiveContainer');
    return ok(code);
  }

  /**
   * @this {TokenizeContext}
   * @type {Tokenizer}
   */
  function tokenizeClosingFence(effects, ok, nok) {
    let size = 0;
    return factorySpace(effects, closingPrefixAfter, "linePrefix", self.parser.constructs.disable.null.includes('codeIndented') ? undefined : 4);

    /** @type {State} */
    function closingPrefixAfter(code) {
      effects.enter('directiveContainerFence');
      effects.enter('directiveContainerSequence');
      return closingSequence(code);
    }

    /** @type {State} */
    function closingSequence(code) {
      if (code === 58) {
        effects.consume(code);
        size++;
        return closingSequence;
      }
      if (size < sizeOpen) return nok(code);
      effects.exit('directiveContainerSequence');
      return factorySpace(effects, closingSequenceEnd, "whitespace")(code);
    }

    /** @type {State} */
    function closingSequenceEnd(code) {
      if (code === null || markdownLineEnding(code)) {
        effects.exit('directiveContainerFence');
        return ok(code);
      }
      return nok(code);
    }
  }
}

/**
 * @this {TokenizeContext}
 * @type {Tokenizer}
 */
function tokenizeLabel(effects, ok, nok) {
  // Always a `[`
  return factoryLabel(effects, ok, nok, 'directiveContainerLabel', 'directiveContainerLabelMarker', 'directiveContainerLabelString', true);
}

/**
 * @this {TokenizeContext}
 * @type {Tokenizer}
 */
function tokenizeAttributes(effects, ok, nok) {
  // Always a `{`
  return factoryAttributes(effects, ok, nok, 'directiveContainerAttributes', 'directiveContainerAttributesMarker', 'directiveContainerAttribute', 'directiveContainerAttributeId', 'directiveContainerAttributeClass', 'directiveContainerAttributeName', 'directiveContainerAttributeInitializerMarker', 'directiveContainerAttributeValueLiteral', 'directiveContainerAttributeValue', 'directiveContainerAttributeValueMarker', 'directiveContainerAttributeValueData', true);
}

/**
 * @this {TokenizeContext}
 * @type {Tokenizer}
 */
function tokenizeNonLazyLine(effects, ok, nok) {
  const self = this;
  return start;

  /** @type {State} */
  function start(code) {
    effects.enter("lineEnding");
    effects.consume(code);
    effects.exit("lineEnding");
    return lineStart;
  }

  /** @type {State} */
  function lineStart(code) {
    return self.parser.lazy[self.now().line] ? nok(code) : ok(code);
  }
}/**
 * @import {Construct, State, TokenizeContext, Tokenizer} from 'micromark-util-types'
 */

import { factorySpace } from 'micromark-factory-space';
import { markdownLineEnding } from 'micromark-util-character';
import { factoryAttributes } from './factory-attributes.js';
import { factoryLabel } from './factory-label.js';
import { factoryName } from './factory-name.js';

/** @type {Construct} */
export const directiveLeaf = {
  tokenize: tokenizeDirectiveLeaf
};
const label = {
  tokenize: tokenizeLabel,
  partial: true
};
const attributes = {
  tokenize: tokenizeAttributes,
  partial: true
};

/**
 * @this {TokenizeContext}
 * @type {Tokenizer}
 */
function tokenizeDirectiveLeaf(effects, ok, nok) {
  const self = this;
  return start;

  /** @type {State} */
  function start(code) {
    effects.enter('directiveLeaf');
    effects.enter('directiveLeafSequence');
    effects.consume(code);
    return inStart;
  }

  /** @type {State} */
  function inStart(code) {
    if (code === 58) {
      effects.consume(code);
      effects.exit('directiveLeafSequence');
      return factoryName.call(self, effects, afterName, nok, 'directiveLeafName');
    }
    return nok(code);
  }

  /** @type {State} */
  function afterName(code) {
    return code === 91 ? effects.attempt(label, afterLabel, afterLabel)(code) : afterLabel(code);
  }

  /** @type {State} */
  function afterLabel(code) {
    return code === 123 ? effects.attempt(attributes, afterAttributes, afterAttributes)(code) : afterAttributes(code);
  }

  /** @type {State} */
  function afterAttributes(code) {
    return factorySpace(effects, end, "whitespace")(code);
  }

  /** @type {State} */
  function end(code) {
    if (code === null || markdownLineEnding(code)) {
      effects.exit('directiveLeaf');
      return ok(code);
    }
    return nok(code);
  }
}

/**
 * @this {TokenizeContext}
 * @type {Tokenizer}
 */
function tokenizeLabel(effects, ok, nok) {
  // Always a `[`
  return factoryLabel(effects, ok, nok, 'directiveLeafLabel', 'directiveLeafLabelMarker', 'directiveLeafLabelString', true);
}

/**
 * @this {TokenizeContext}
 * @type {Tokenizer}
 */
function tokenizeAttributes(effects, ok, nok) {
  // Always a `{`
  return factoryAttributes(effects, ok, nok, 'directiveLeafAttributes', 'directiveLeafAttributesMarker', 'directiveLeafAttribute', 'directiveLeafAttributeId', 'directiveLeafAttributeClass', 'directiveLeafAttributeName', 'directiveLeafAttributeInitializerMarker', 'directiveLeafAttributeValueLiteral', 'directiveLeafAttributeValue', 'directiveLeafAttributeValueMarker', 'directiveLeafAttributeValueData', true);
}/**
 * @import {Construct, Previous, State, TokenizeContext, Tokenizer} from 'micromark-util-types'
 */

import { factoryAttributes } from './factory-attributes.js';
import { factoryLabel } from './factory-label.js';
import { factoryName } from './factory-name.js';

/** @type {Construct} */
export const directiveText = {
  tokenize: tokenizeDirectiveText,
  previous
};
const label = {
  tokenize: tokenizeLabel,
  partial: true
};
const attributes = {
  tokenize: tokenizeAttributes,
  partial: true
};

/**
 * @this {TokenizeContext}
 * @type {Previous}
 */
function previous(code) {
  // If there is a previous code, there will always be a tail.
  return code !== 58 || this.events[this.events.length - 1][1].type === "characterEscape";
}

/**
 * @this {TokenizeContext}
 * @type {Tokenizer}
 */
function tokenizeDirectiveText(effects, ok, nok) {
  const self = this;
  return start;

  /** @type {State} */
  function start(code) {
    effects.enter('directiveText');
    effects.enter('directiveTextMarker');
    effects.consume(code);
    effects.exit('directiveTextMarker');
    return factoryName.call(self, effects, afterName, nok, 'directiveTextName');
  }

  /** @type {State} */
  function afterName(code) {
    return code === 58 ? nok(code) : code === 91 ? effects.attempt(label, afterLabel, afterLabel)(code) : afterLabel(code);
  }

  /** @type {State} */
  function afterLabel(code) {
    return code === 123 ? effects.attempt(attributes, afterAttributes, afterAttributes)(code) : afterAttributes(code);
  }

  /** @type {State} */
  function afterAttributes(code) {
    effects.exit('directiveText');
    return ok(code);
  }
}

/**
 * @this {TokenizeContext}
 * @type {Tokenizer}
 */
function tokenizeLabel(effects, ok, nok) {
  // Always a `[`
  return factoryLabel(effects, ok, nok, 'directiveTextLabel', 'directiveTextLabelMarker', 'directiveTextLabelString');
}

/**
 * @this {TokenizeContext}
 * @type {Tokenizer}
 */
function tokenizeAttributes(effects, ok, nok) {
  // Always a `{`
  return factoryAttributes(effects, ok, nok, 'directiveTextAttributes', 'directiveTextAttributesMarker', 'directiveTextAttribute', 'directiveTextAttributeId', 'directiveTextAttributeClass', 'directiveTextAttributeName', 'directiveTextAttributeInitializerMarker', 'directiveTextAttributeValueLiteral', 'directiveTextAttributeValue', 'directiveTextAttributeValueMarker', 'directiveTextAttributeValueData');
}/**
 * @import {Code, Effects, State, TokenType} from 'micromark-util-types'
 */

import { factorySpace } from 'micromark-factory-space';
import { factoryWhitespace } from 'micromark-factory-whitespace';
import { markdownLineEnding, markdownLineEndingOrSpace, markdownSpace, unicodePunctuation, unicodeWhitespace } from 'micromark-util-character';
/**
 * @param {Effects} effects
 * @param {State} ok
 * @param {State} nok
 * @param {TokenType} attributesType
 * @param {TokenType} attributesMarkerType
 * @param {TokenType} attributeType
 * @param {TokenType} attributeIdType
 * @param {TokenType} attributeClassType
 * @param {TokenType} attributeNameType
 * @param {TokenType} attributeInitializerType
 * @param {TokenType} attributeValueLiteralType
 * @param {TokenType} attributeValueType
 * @param {TokenType} attributeValueMarker
 * @param {TokenType} attributeValueData
 * @param {boolean | undefined} [disallowEol=false]
 */
export function factoryAttributes(effects, ok, nok, attributesType, attributesMarkerType, attributeType, attributeIdType, attributeClassType, attributeNameType, attributeInitializerType, attributeValueLiteralType, attributeValueType, attributeValueMarker, attributeValueData, disallowEol) {
  /** @type {TokenType} */
  let type;
  /** @type {Code | undefined} */
  let marker;
  return start;

  /** @type {State} */
  function start(code) {
    effects.enter(attributesType);
    effects.enter(attributesMarkerType);
    effects.consume(code);
    effects.exit(attributesMarkerType);
    return between;
  }

  /** @type {State} */
  function between(code) {
    if (code === 35) {
      type = attributeIdType;
      return shortcutStart(code);
    }
    if (code === 46) {
      type = attributeClassType;
      return shortcutStart(code);
    }
    if (disallowEol && markdownSpace(code)) {
      return factorySpace(effects, between, "whitespace")(code);
    }
    if (!disallowEol && markdownLineEndingOrSpace(code)) {
      return factoryWhitespace(effects, between)(code);
    }
    if (code === null || markdownLineEnding(code) || unicodeWhitespace(code) || unicodePunctuation(code) && code !== 45 && code !== 95) {
      return end(code);
    }
    effects.enter(attributeType);
    effects.enter(attributeNameType);
    effects.consume(code);
    return name;
  }

  /** @type {State} */
  function shortcutStart(code) {
    // Assume it’s registered.
    const markerType = /** @type {TokenType} */type + 'Marker';
    effects.enter(attributeType);
    effects.enter(type);
    effects.enter(markerType);
    effects.consume(code);
    effects.exit(markerType);
    return shortcutStartAfter;
  }

  /** @type {State} */
  function shortcutStartAfter(code) {
    if (code === null || code === 34 || code === 35 || code === 39 || code === 46 || code === 60 || code === 61 || code === 62 || code === 96 || code === 125 || markdownLineEndingOrSpace(code)) {
      return nok(code);
    }

    // Assume it’s registered.
    const valueType = /** @type {TokenType} */type + 'Value';
    effects.enter(valueType);
    effects.consume(code);
    return shortcut;
  }

  /** @type {State} */
  function shortcut(code) {
    if (code === null || code === 34 || code === 39 || code === 60 || code === 61 || code === 62 || code === 96) {
      return nok(code);
    }
    if (code === 35 || code === 46 || code === 125 || markdownLineEndingOrSpace(code)) {
      // Assume it’s registered.
      const valueType = /** @type {TokenType} */type + 'Value';
      effects.exit(valueType);
      effects.exit(type);
      effects.exit(attributeType);
      return between(code);
    }
    effects.consume(code);
    return shortcut;
  }

  /** @type {State} */
  function name(code) {
    if (code === null || markdownLineEnding(code) || unicodeWhitespace(code) || unicodePunctuation(code) && code !== 45 && code !== 46 && code !== 58 && code !== 95) {
      effects.exit(attributeNameType);
      if (disallowEol && markdownSpace(code)) {
        return factorySpace(effects, nameAfter, "whitespace")(code);
      }
      if (!disallowEol && markdownLineEndingOrSpace(code)) {
        return factoryWhitespace(effects, nameAfter)(code);
      }
      return nameAfter(code);
    }
    effects.consume(code);
    return name;
  }

  /** @type {State} */
  function nameAfter(code) {
    if (code === 61) {
      effects.enter(attributeInitializerType);
      effects.consume(code);
      effects.exit(attributeInitializerType);
      return valueBefore;
    }

    // Attribute w/o value.
    effects.exit(attributeType);
    return between(code);
  }

  /** @type {State} */
  function valueBefore(code) {
    if (code === null || code === 60 || code === 61 || code === 62 || code === 96 || code === 125 || disallowEol && markdownLineEnding(code)) {
      return nok(code);
    }
    if (code === 34 || code === 39) {
      effects.enter(attributeValueLiteralType);
      effects.enter(attributeValueMarker);
      effects.consume(code);
      effects.exit(attributeValueMarker);
      marker = code;
      return valueQuotedStart;
    }
    if (disallowEol && markdownSpace(code)) {
      return factorySpace(effects, valueBefore, "whitespace")(code);
    }
    if (!disallowEol && markdownLineEndingOrSpace(code)) {
      return factoryWhitespace(effects, valueBefore)(code);
    }
    effects.enter(attributeValueType);
    effects.enter(attributeValueData);
    effects.consume(code);
    marker = undefined;
    return valueUnquoted;
  }

  /** @type {State} */
  function valueUnquoted(code) {
    if (code === null || code === 34 || code === 39 || code === 60 || code === 61 || code === 62 || code === 96) {
      return nok(code);
    }
    if (code === 125 || markdownLineEndingOrSpace(code)) {
      effects.exit(attributeValueData);
      effects.exit(attributeValueType);
      effects.exit(attributeType);
      return between(code);
    }
    effects.consume(code);
    return valueUnquoted;
  }

  /** @type {State} */
  function valueQuotedStart(code) {
    if (code === marker) {
      effects.enter(attributeValueMarker);
      effects.consume(code);
      effects.exit(attributeValueMarker);
      effects.exit(attributeValueLiteralType);
      effects.exit(attributeType);
      return valueQuotedAfter;
    }
    effects.enter(attributeValueType);
    return valueQuotedBetween(code);
  }

  /** @type {State} */
  function valueQuotedBetween(code) {
    if (code === marker) {
      effects.exit(attributeValueType);
      return valueQuotedStart(code);
    }
    if (code === null) {
      return nok(code);
    }

    // Note: blank lines can’t exist in content.
    if (markdownLineEnding(code)) {
      return disallowEol ? nok(code) : factoryWhitespace(effects, valueQuotedBetween)(code);
    }
    effects.enter(attributeValueData);
    effects.consume(code);
    return valueQuoted;
  }

  /** @type {State} */
  function valueQuoted(code) {
    if (code === marker || code === null || markdownLineEnding(code)) {
      effects.exit(attributeValueData);
      return valueQuotedBetween(code);
    }
    effects.consume(code);
    return valueQuoted;
  }

  /** @type {State} */
  function valueQuotedAfter(code) {
    return code === 125 || markdownLineEndingOrSpace(code) ? between(code) : end(code);
  }

  /** @type {State} */
  function end(code) {
    if (code === 125) {
      effects.enter(attributesMarkerType);
      effects.consume(code);
      effects.exit(attributesMarkerType);
      effects.exit(attributesType);
      return ok;
    }
    return nok(code);
  }
}/**
 * @import {Code, Effects, State, Token, TokenType} from 'micromark-util-types'
 */

import { markdownLineEnding } from 'micromark-util-character';
// This is a fork of:
// <https://github.com/micromark/micromark/tree/main/packages/micromark-factory-label>
// to allow empty labels, balanced brackets (such as for nested directives),
// text instead of strings, and optionally disallows EOLs.

/**
 * @param {Effects} effects
 * @param {State} ok
 * @param {State} nok
 * @param {TokenType} type
 * @param {TokenType} markerType
 * @param {TokenType} stringType
 * @param {boolean | undefined} [disallowEol=false]
 */
export function factoryLabel(effects, ok, nok, type, markerType, stringType, disallowEol) {
  let size = 0;
  let balance = 0;
  /** @type {Token | undefined} */
  let previous;
  return start;

  /** @type {State} */
  function start(code) {
    effects.enter(type);
    effects.enter(markerType);
    effects.consume(code);
    effects.exit(markerType);
    return afterStart;
  }

  /** @type {State} */
  function afterStart(code) {
    if (code === 93) {
      effects.enter(markerType);
      effects.consume(code);
      effects.exit(markerType);
      effects.exit(type);
      return ok;
    }
    effects.enter(stringType);
    return lineStart(code);
  }

  /** @type {State} */
  function lineStart(code) {
    if (code === 93 && !balance) {
      return atClosingBrace(code);
    }
    const token = effects.enter("chunkText", {
      _contentTypeTextTrailing: true,
      contentType: "text",
      previous
    });
    if (previous) previous.next = token;
    previous = token;
    return data(code);
  }

  /** @type {State} */
  function data(code) {
    if (code === null || size > 999) {
      return nok(code);
    }
    if (code === 91 && ++balance > 32) {
      return nok(code);
    }
    if (code === 93 && !balance--) {
      effects.exit("chunkText");
      return atClosingBrace(code);
    }
    if (markdownLineEnding(code)) {
      if (disallowEol) {
        return nok(code);
      }
      effects.consume(code);
      effects.exit("chunkText");
      return lineStart;
    }
    effects.consume(code);
    return code === 92 ? dataEscape : data;
  }

  /** @type {State} */
  function dataEscape(code) {
    if (code === 91 || code === 92 || code === 93) {
      effects.consume(code);
      size++;
      return data;
    }
    return data(code);
  }

  /** @type {State} */
  function atClosingBrace(code) {
    effects.exit(stringType);
    effects.enter(markerType);
    effects.consume(code);
    effects.exit(markerType);
    effects.exit(type);
    return ok;
  }
}/**
 * @import {Code, Effects, State, TokenizeContext, TokenType} from 'micromark-util-types'
 */

import { markdownLineEnding, unicodePunctuation, unicodeWhitespace } from 'micromark-util-character';
/**
 * @this {TokenizeContext}
 * @param {Effects} effects
 * @param {State} ok
 * @param {State} nok
 * @param {TokenType} type
 */
export function factoryName(effects, ok, nok, type) {
  const self = this;
  return start;

  /** @type {State} */
  function start(code) {
    if (code === null || markdownLineEnding(code) || unicodePunctuation(code) || unicodeWhitespace(code)) {
      return nok(code);
    }
    effects.enter(type);
    effects.consume(code);
    return name;
  }

  /** @type {State} */
  function name(code) {
    if (code === null || markdownLineEnding(code) || unicodeWhitespace(code) || unicodePunctuation(code) && code !== 45 && code !== 95) {
      effects.exit(type);
      return self.previous === 45 || self.previous === 95 ? nok(code) : ok(code);
    }
    effects.consume(code);
    return name;
  }
}/**
 * @import {Directive, HtmlOptions} from 'micromark-extension-directive'
 * @import {CompileContext, Handle as MicromarkHandle, HtmlExtension} from 'micromark-util-types'
 */

import { parseEntities } from 'parse-entities';
const own = {}.hasOwnProperty;

/**
 * Create an extension for `micromark` to support directives when serializing
 * to HTML.
 *
 * @param {HtmlOptions | null | undefined} [options={}]
 *   Configuration (default: `{}`).
 * @returns {HtmlExtension}
 *   Extension for `micromark` that can be passed in `htmlExtensions`, to
 *   support directives when serializing to HTML.
 */
export function directiveHtml(options) {
  const options_ = options || {};
  return {
    enter: {
      directiveContainer() {
        enter.call(this, 'containerDirective');
      },
      directiveContainerAttributes: enterAttributes,
      directiveContainerLabel: enterLabel,
      directiveContainerContent() {
        this.buffer();
      },
      directiveLeaf() {
        enter.call(this, 'leafDirective');
      },
      directiveLeafAttributes: enterAttributes,
      directiveLeafLabel: enterLabel,
      directiveText() {
        enter.call(this, 'textDirective');
      },
      directiveTextAttributes: enterAttributes,
      directiveTextLabel: enterLabel
    },
    exit: {
      directiveContainer: exit,
      directiveContainerAttributeClassValue: exitAttributeClassValue,
      directiveContainerAttributeIdValue: exitAttributeIdValue,
      directiveContainerAttributeName: exitAttributeName,
      directiveContainerAttributeValue: exitAttributeValue,
      directiveContainerAttributes: exitAttributes,
      directiveContainerContent: exitContainerContent,
      directiveContainerFence: exitContainerFence,
      directiveContainerLabel: exitLabel,
      directiveContainerName: exitName,
      directiveLeaf: exit,
      directiveLeafAttributeClassValue: exitAttributeClassValue,
      directiveLeafAttributeIdValue: exitAttributeIdValue,
      directiveLeafAttributeName: exitAttributeName,
      directiveLeafAttributeValue: exitAttributeValue,
      directiveLeafAttributes: exitAttributes,
      directiveLeafLabel: exitLabel,
      directiveLeafName: exitName,
      directiveText: exit,
      directiveTextAttributeClassValue: exitAttributeClassValue,
      directiveTextAttributeIdValue: exitAttributeIdValue,
      directiveTextAttributeName: exitAttributeName,
      directiveTextAttributeValue: exitAttributeValue,
      directiveTextAttributes: exitAttributes,
      directiveTextLabel: exitLabel,
      directiveTextName: exitName
    }
  };

  /**
   * @this {CompileContext}
   * @param {Directive['type']} type
   */
  function enter(type) {
    let stack = this.getData('directiveStack');
    if (!stack) this.setData('directiveStack', stack = []);
    stack.push({
      type,
      name: ''
    });
  }

  /**
   * @this {CompileContext}
   * @type {MicromarkHandle}
   */
  function exitName(token) {
    const stack = this.getData('directiveStack');
    stack[stack.length - 1].name = this.sliceSerialize(token);
  }

  /**
   * @this {CompileContext}
   * @type {MicromarkHandle}
   */
  function enterLabel() {
    this.buffer();
  }

  /**
   * @this {CompileContext}
   * @type {MicromarkHandle}
   */
  function exitLabel() {
    const data = this.resume();
    const stack = this.getData('directiveStack');
    stack[stack.length - 1].label = data;
  }

  /**
   * @this {CompileContext}
   * @type {MicromarkHandle}
   */
  function enterAttributes() {
    this.buffer();
    this.setData('directiveAttributes', []);
  }

  /**
   * @this {CompileContext}
   * @type {MicromarkHandle}
   */
  function exitAttributeIdValue(token) {
    const attributes = this.getData('directiveAttributes');
    attributes.push(['id', parseEntities(this.sliceSerialize(token), {
      attribute: true
    })]);
  }

  /**
   * @this {CompileContext}
   * @type {MicromarkHandle}
   */
  function exitAttributeClassValue(token) {
    const attributes = this.getData('directiveAttributes');
    attributes.push(['class', parseEntities(this.sliceSerialize(token), {
      attribute: true
    })]);
  }

  /**
   * @this {CompileContext}
   * @type {MicromarkHandle}
   */
  function exitAttributeName(token) {
    // Attribute names in CommonMark are significantly limited, so character
    // references can’t exist.
    const attributes = this.getData('directiveAttributes');
    attributes.push([this.sliceSerialize(token), '']);
  }

  /**
   * @this {CompileContext}
   * @type {MicromarkHandle}
   */
  function exitAttributeValue(token) {
    const attributes = this.getData('directiveAttributes');
    attributes[attributes.length - 1][1] = parseEntities(this.sliceSerialize(token), {
      attribute: true
    });
  }

  /**
   * @this {CompileContext}
   * @type {MicromarkHandle}
   */
  function exitAttributes() {
    const stack = this.getData('directiveStack');
    const attributes = this.getData('directiveAttributes');
    /** @type {Record<string, string>} */
    const cleaned = {};
    let index = -1;
    while (++index < attributes.length) {
      const attribute = attributes[index];
      if (attribute[0] === 'class' && cleaned.class) {
        cleaned.class += ' ' + attribute[1];
      } else {
        cleaned[attribute[0]] = attribute[1];
      }
    }
    this.resume();
    this.setData('directiveAttributes');
    stack[stack.length - 1].attributes = cleaned;
  }

  /**
   * @this {CompileContext}
   * @type {MicromarkHandle}
   */
  function exitContainerContent() {
    const data = this.resume();
    const stack = this.getData('directiveStack');
    stack[stack.length - 1].content = data;
  }

  /**
   * @this {CompileContext}
   * @type {MicromarkHandle}
   */
  function exitContainerFence() {
    const stack = this.getData('directiveStack');
    const directive = stack[stack.length - 1];
    if (!directive._fenceCount) directive._fenceCount = 0;
    directive._fenceCount++;
    if (directive._fenceCount === 1) this.setData('slurpOneLineEnding', true);
  }

  /**
   * @this {CompileContext}
   * @type {MicromarkHandle}
   */
  function exit() {
    const stack = this.getData('directiveStack');
    const directive = stack.pop();
    /** @type {boolean | undefined} */
    let found;
    /** @type {boolean | undefined} */
    let result;
    if (own.call(options_, directive.name)) {
      result = options_[directive.name].call(this, directive);
      found = result !== false;
    }
    if (!found && own.call(options_, '*')) {
      result = options_['*'].call(this, directive);
      found = result !== false;
    }
    if (!found && directive.type !== 'textDirective') {
      this.setData('slurpOneLineEnding', true);
    }
  }
}/**
 * @import {Extension} from 'micromark-util-types'
 */

import { directiveContainer } from './directive-container.js';
import { directiveLeaf } from './directive-leaf.js';
import { directiveText } from './directive-text.js';

/**
 * Create an extension for `micromark` to enable directive syntax.
 *
 * @returns {Extension}
 *   Extension for `micromark` that can be passed in `extensions`, to
 *   enable directive syntax.
 */
export function directive() {
  return {
    text: {
      [58]: directiveText
    },
    flow: {
      [58]: [directiveContainer, directiveLeaf]
    }
  };
}{
  "author": "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)",
  "bugs": "https://github.com/micromark/micromark-extension-directive/issues",
  "contributors": [
    "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)"
  ],
  "dependencies": {
    "devlop": "^1.0.0",
    "micromark-factory-space": "^2.0.0",
    "micromark-factory-whitespace": "^2.0.0",
    "micromark-util-character": "^2.0.0",
    "micromark-util-symbol": "^2.0.0",
    "micromark-util-types": "^2.0.0",
    "parse-entities": "^4.0.0"
  },
  "description": "micromark extension to support generic directives (`:cite[smith04]`)",
  "devDependencies": {
    "@types/node": "^22.0.0",
    "c8": "^10.0.0",
    "html-void-elements": "^3.0.0",
    "micromark": "^4.0.0",
    "micromark-build": "^2.0.0",
    "prettier": "^3.0.0",
    "remark-cli": "^12.0.0",
    "remark-preset-wooorm": "^11.0.0",
    "type-coverage": "^2.0.0",
    "typescript": "^5.0.0",
    "xo": "^0.60.0"
  },
  "exports": {
    "development": "./dev/index.js",
    "default": "./index.js"
  },
  "files": [
    "dev/",
    "index.d.ts",
    "index.js",
    "lib/"
  ],
  "funding": {
    "type": "opencollective",
    "url": "https://opencollective.com/unified"
  },
  "keywords": [
    "container",
    "directive",
    "extension",
    "generic",
    "markdown",
    "micromark-extension",
    "micromark",
    "unified"
  ],
  "license": "MIT",
  "name": "micromark-extension-directive",
  "prettier": {
    "bracketSpacing": false,
    "semi": false,
    "singleQuote": true,
    "tabWidth": 2,
    "trailingComma": "none",
    "useTabs": false
  },
  "remarkConfig": {
    "plugins": [
      "remark-preset-wooorm"
    ]
  },
  "repository": "micromark/micromark-extension-directive",
  "scripts": {
    "build": "tsc --build --clean && tsc --build && type-coverage && micromark-build",
    "format": "remark --frail --output --quiet -- . && prettier --log-level warn --write -- . && xo --fix",
    "test-api-prod": "node --conditions production test/index.js",
    "test-api-dev": "node --conditions development test/index.js",
    "test-api": "npm run test-api-dev && npm run test-api-prod",
    "test-coverage": "c8 --100 --reporter lcov -- npm run test-api",
    "test": "npm run build && npm run format && npm run test-coverage"
  },
  "sideEffects": false,
  "typeCoverage": {
    "atLeast": 100,
    "strict": true
  },
  "type": "module",
  "version": "4.0.0",
  "xo": {
    "overrides": [
      {
        "files": [
          "**/*.d.ts"
        ],
        "rules": {
          "@typescript-eslint/array-type": [
            "error",
            {
              "default": "generic"
            }
          ],
          "@typescript-eslint/ban-types": [
            "error",
            {
              "extendDefaults": true
            }
          ],
          "@typescript-eslint/consistent-indexed-object-style": [
            "error",
            "index-signature"
          ],
          "@typescript-eslint/consistent-type-definitions": [
            "error",
            "interface"
          ]
        }
      }
    ],
    "prettier": true,
    "rules": {
      "logical-assignment-operators": "off",
      "max-params": "off",
      "unicorn/no-this-assignment": "off",
      "unicorn/prefer-at": "off"
    }
  }
}
// Note: types are exported from `dev/index.d.ts`.
export { gfmFootnote } from './lib/syntax.js';
export { gfmFootnoteHtml, defaultBackLabel } from './lib/html.js';/**
 * @import {HtmlOptions as Options} from 'micromark-extension-gfm-footnote'
 * @import {HtmlExtension} from 'micromark-util-types'
 */

import { normalizeIdentifier } from 'micromark-util-normalize-identifier';
import { sanitizeUri } from 'micromark-util-sanitize-uri';
const own = {}.hasOwnProperty;

/** @type {Options} */
const emptyOptions = {};

/**
 * Generate the default label that GitHub uses on backreferences.
 *
 * @param {number} referenceIndex
 *   Index of the definition in the order that they are first referenced,
 *   0-indexed.
 * @param {number} rereferenceIndex
 *   Index of calls to the same definition, 0-indexed.
 * @returns {string}
 *   Default label.
 */
export function defaultBackLabel(referenceIndex, rereferenceIndex) {
  return 'Back to reference ' + (referenceIndex + 1) + (rereferenceIndex > 1 ? '-' + rereferenceIndex : '');
}

/**
 * Create an extension for `micromark` to support GFM footnotes when
 * serializing to HTML.
 *
 * @param {Options | null | undefined} [options={}]
 *   Configuration (optional).
 * @returns {HtmlExtension}
 *   Extension for `micromark` that can be passed in `htmlExtensions` to
 *   support GFM footnotes when serializing to HTML.
 */
export function gfmFootnoteHtml(options) {
  const config = options || emptyOptions;
  const label = config.label || 'Footnotes';
  const labelTagName = config.labelTagName || 'h2';
  const labelAttributes = config.labelAttributes === null || config.labelAttributes === undefined ? 'class="sr-only"' : config.labelAttributes;
  const backLabel = config.backLabel || defaultBackLabel;
  const clobberPrefix = config.clobberPrefix === null || config.clobberPrefix === undefined ? 'user-content-' : config.clobberPrefix;
  return {
    enter: {
      gfmFootnoteDefinition() {
        const stack = this.getData('tightStack');
        stack.push(false);
      },
      gfmFootnoteDefinitionLabelString() {
        this.buffer();
      },
      gfmFootnoteCallString() {
        this.buffer();
      }
    },
    exit: {
      gfmFootnoteDefinition() {
        let definitions = this.getData('gfmFootnoteDefinitions');
        const footnoteStack = this.getData('gfmFootnoteDefinitionStack');
        const tightStack = this.getData('tightStack');
        const current = footnoteStack.pop();
        const value = this.resume();
        if (!definitions) {
          this.setData('gfmFootnoteDefinitions', definitions = {});
        }
        if (!own.call(definitions, current)) definitions[current] = value;
        tightStack.pop();
        this.setData('slurpOneLineEnding', true);
        // “Hack” to prevent a line ending from showing up if we’re in a definition in
        // an empty list item.
        this.setData('lastWasTag');
      },
      gfmFootnoteDefinitionLabelString(token) {
        let footnoteStack = this.getData('gfmFootnoteDefinitionStack');
        if (!footnoteStack) {
          this.setData('gfmFootnoteDefinitionStack', footnoteStack = []);
        }
        footnoteStack.push(normalizeIdentifier(this.sliceSerialize(token)));
        this.resume(); // Drop the label.
        this.buffer(); // Get ready for a value.
      },
      gfmFootnoteCallString(token) {
        let calls = this.getData('gfmFootnoteCallOrder');
        let counts = this.getData('gfmFootnoteCallCounts');
        const id = normalizeIdentifier(this.sliceSerialize(token));
        /** @type {number} */
        let counter;
        this.resume();
        if (!calls) this.setData('gfmFootnoteCallOrder', calls = []);
        if (!counts) this.setData('gfmFootnoteCallCounts', counts = {});
        const index = calls.indexOf(id);
        const safeId = sanitizeUri(id.toLowerCase());
        if (index === -1) {
          calls.push(id);
          counts[id] = 1;
          counter = calls.length;
        } else {
          counts[id]++;
          counter = index + 1;
        }
        const reuseCounter = counts[id];
        this.tag('<sup><a href="#' + clobberPrefix + 'fn-' + safeId + '" id="' + clobberPrefix + 'fnref-' + safeId + (reuseCounter > 1 ? '-' + reuseCounter : '') + '" data-footnote-ref="" aria-describedby="footnote-label">' + String(counter) + '</a></sup>');
      },
      null() {
        const calls = this.getData('gfmFootnoteCallOrder') || [];
        const counts = this.getData('gfmFootnoteCallCounts') || {};
        const definitions = this.getData('gfmFootnoteDefinitions') || {};
        let index = -1;
        if (calls.length > 0) {
          this.lineEndingIfNeeded();
          this.tag('<section data-footnotes="" class="footnotes"><' + labelTagName + ' id="footnote-label"' + (labelAttributes ? ' ' + labelAttributes : '') + '>');
          this.raw(this.encode(label));
          this.tag('</' + labelTagName + '>');
          this.lineEndingIfNeeded();
          this.tag('<ol>');
        }
        while (++index < calls.length) {
          // Called definitions are always defined.
          const id = calls[index];
          const safeId = sanitizeUri(id.toLowerCase());
          let referenceIndex = 0;
          /** @type {Array<string>} */
          const references = [];
          while (++referenceIndex <= counts[id]) {
            references.push('<a href="#' + clobberPrefix + 'fnref-' + safeId + (referenceIndex > 1 ? '-' + referenceIndex : '') + '" data-footnote-backref="" aria-label="' + this.encode(typeof backLabel === 'string' ? backLabel : backLabel(index, referenceIndex)) + '" class="data-footnote-backref">↩' + (referenceIndex > 1 ? '<sup>' + referenceIndex + '</sup>' : '') + '</a>');
          }
          const reference = references.join(' ');
          let injected = false;
          this.lineEndingIfNeeded();
          this.tag('<li id="' + clobberPrefix + 'fn-' + safeId + '">');
          this.lineEndingIfNeeded();
          this.tag(definitions[id].replace(/<\/p>(?:\r?\n|\r)?$/, function ($0) {
            injected = true;
            return ' ' + reference + $0;
          }));
          if (!injected) {
            this.lineEndingIfNeeded();
            this.tag(reference);
          }
          this.lineEndingIfNeeded();
          this.tag('</li>');
        }
        if (calls.length > 0) {
          this.lineEndingIfNeeded();
          this.tag('</ol>');
          this.lineEndingIfNeeded();
          this.tag('</section>');
        }
      }
    }
  };
}/**
 * @import {Event, Exiter, Extension, Resolver, State, Token, TokenizeContext, Tokenizer} from 'micromark-util-types'
 */

import { blankLine } from 'micromark-core-commonmark';
import { factorySpace } from 'micromark-factory-space';
import { markdownLineEndingOrSpace } from 'micromark-util-character';
import { normalizeIdentifier } from 'micromark-util-normalize-identifier';
const indent = {
  tokenize: tokenizeIndent,
  partial: true
};

// To do: micromark should support a `_hiddenGfmFootnoteSupport`, which only
// affects label start (image).
// That will let us drop `tokenizePotentialGfmFootnote*`.
// It currently has a `_hiddenFootnoteSupport`, which affects that and more.
// That can be removed when `micromark-extension-footnote` is archived.

/**
 * Create an extension for `micromark` to enable GFM footnote syntax.
 *
 * @returns {Extension}
 *   Extension for `micromark` that can be passed in `extensions` to
 *   enable GFM footnote syntax.
 */
export function gfmFootnote() {
  /** @type {Extension} */
  return {
    document: {
      [91]: {
        name: 'gfmFootnoteDefinition',
        tokenize: tokenizeDefinitionStart,
        continuation: {
          tokenize: tokenizeDefinitionContinuation
        },
        exit: gfmFootnoteDefinitionEnd
      }
    },
    text: {
      [91]: {
        name: 'gfmFootnoteCall',
        tokenize: tokenizeGfmFootnoteCall
      },
      [93]: {
        name: 'gfmPotentialFootnoteCall',
        add: 'after',
        tokenize: tokenizePotentialGfmFootnoteCall,
        resolveTo: resolveToPotentialGfmFootnoteCall
      }
    }
  };
}

// To do: remove after micromark update.
/**
 * @this {TokenizeContext}
 * @type {Tokenizer}
 */
function tokenizePotentialGfmFootnoteCall(effects, ok, nok) {
  const self = this;
  let index = self.events.length;
  const defined = self.parser.gfmFootnotes || (self.parser.gfmFootnotes = []);
  /** @type {Token} */
  let labelStart;

  // Find an opening.
  while (index--) {
    const token = self.events[index][1];
    if (token.type === "labelImage") {
      labelStart = token;
      break;
    }

    // Exit if we’ve walked far enough.
    if (token.type === 'gfmFootnoteCall' || token.type === "labelLink" || token.type === "label" || token.type === "image" || token.type === "link") {
      break;
    }
  }
  return start;

  /**
   * @type {State}
   */
  function start(code) {
    if (!labelStart || !labelStart._balanced) {
      return nok(code);
    }
    const id = normalizeIdentifier(self.sliceSerialize({
      start: labelStart.end,
      end: self.now()
    }));
    if (id.codePointAt(0) !== 94 || !defined.includes(id.slice(1))) {
      return nok(code);
    }
    effects.enter('gfmFootnoteCallLabelMarker');
    effects.consume(code);
    effects.exit('gfmFootnoteCallLabelMarker');
    return ok(code);
  }
}

// To do: remove after micromark update.
/** @type {Resolver} */
function resolveToPotentialGfmFootnoteCall(events, context) {
  let index = events.length;
  /** @type {Token | undefined} */
  let labelStart;

  // Find an opening.
  while (index--) {
    if (events[index][1].type === "labelImage" && events[index][0] === 'enter') {
      labelStart = events[index][1];
      break;
    }
  }
  // Change the `labelImageMarker` to a `data`.
  events[index + 1][1].type = "data";
  events[index + 3][1].type = 'gfmFootnoteCallLabelMarker';

  // The whole (without `!`):
  /** @type {Token} */
  const call = {
    type: 'gfmFootnoteCall',
    start: Object.assign({}, events[index + 3][1].start),
    end: Object.assign({}, events[events.length - 1][1].end)
  };
  // The `^` marker
  /** @type {Token} */
  const marker = {
    type: 'gfmFootnoteCallMarker',
    start: Object.assign({}, events[index + 3][1].end),
    end: Object.assign({}, events[index + 3][1].end)
  };
  // Increment the end 1 character.
  marker.end.column++;
  marker.end.offset++;
  marker.end._bufferIndex++;
  /** @type {Token} */
  const string = {
    type: 'gfmFootnoteCallString',
    start: Object.assign({}, marker.end),
    end: Object.assign({}, events[events.length - 1][1].start)
  };
  /** @type {Token} */
  const chunk = {
    type: "chunkString",
    contentType: 'string',
    start: Object.assign({}, string.start),
    end: Object.assign({}, string.end)
  };

  /** @type {Array<Event>} */
  const replacement = [
  // Take the `labelImageMarker` (now `data`, the `!`)
  events[index + 1], events[index + 2], ['enter', call, context],
  // The `[`
  events[index + 3], events[index + 4],
  // The `^`.
  ['enter', marker, context], ['exit', marker, context],
  // Everything in between.
  ['enter', string, context], ['enter', chunk, context], ['exit', chunk, context], ['exit', string, context],
  // The ending (`]`, properly parsed and labelled).
  events[events.length - 2], events[events.length - 1], ['exit', call, context]];
  events.splice(index, events.length - index + 1, ...replacement);
  return events;
}

/**
 * @this {TokenizeContext}
 * @type {Tokenizer}
 */
function tokenizeGfmFootnoteCall(effects, ok, nok) {
  const self = this;
  const defined = self.parser.gfmFootnotes || (self.parser.gfmFootnotes = []);
  let size = 0;
  /** @type {boolean} */
  let data;

  // Note: the implementation of `markdown-rs` is different, because it houses
  // core *and* extensions in one project.
  // Therefore, it can include footnote logic inside `label-end`.
  // We can’t do that, but luckily, we can parse footnotes in a simpler way than
  // needed for labels.
  return start;

  /**
   * Start of footnote label.
   *
   * ```markdown
   * > | a [^b] c
   *       ^
   * ```
   *
   * @type {State}
   */
  function start(code) {
    effects.enter('gfmFootnoteCall');
    effects.enter('gfmFootnoteCallLabelMarker');
    effects.consume(code);
    effects.exit('gfmFootnoteCallLabelMarker');
    return callStart;
  }

  /**
   * After `[`, at `^`.
   *
   * ```markdown
   * > | a [^b] c
   *        ^
   * ```
   *
   * @type {State}
   */
  function callStart(code) {
    if (code !== 94) return nok(code);
    effects.enter('gfmFootnoteCallMarker');
    effects.consume(code);
    effects.exit('gfmFootnoteCallMarker');
    effects.enter('gfmFootnoteCallString');
    effects.enter('chunkString').contentType = 'string';
    return callData;
  }

  /**
   * In label.
   *
   * ```markdown
   * > | a [^b] c
   *         ^
   * ```
   *
   * @type {State}
   */
  function callData(code) {
    if (
    // Too long.
    size > 999 ||
    // Closing brace with nothing.
    code === 93 && !data ||
    // Space or tab is not supported by GFM for some reason.
    // `\n` and `[` not being supported makes sense.
    code === null || code === 91 || markdownLineEndingOrSpace(code)) {
      return nok(code);
    }
    if (code === 93) {
      effects.exit('chunkString');
      const token = effects.exit('gfmFootnoteCallString');
      if (!defined.includes(normalizeIdentifier(self.sliceSerialize(token)))) {
        return nok(code);
      }
      effects.enter('gfmFootnoteCallLabelMarker');
      effects.consume(code);
      effects.exit('gfmFootnoteCallLabelMarker');
      effects.exit('gfmFootnoteCall');
      return ok;
    }
    if (!markdownLineEndingOrSpace(code)) {
      data = true;
    }
    size++;
    effects.consume(code);
    return code === 92 ? callEscape : callData;
  }

  /**
   * On character after escape.
   *
   * ```markdown
   * > | a [^b\c] d
   *           ^
   * ```
   *
   * @type {State}
   */
  function callEscape(code) {
    if (code === 91 || code === 92 || code === 93) {
      effects.consume(code);
      size++;
      return callData;
    }
    return callData(code);
  }
}

/**
 * @this {TokenizeContext}
 * @type {Tokenizer}
 */
function tokenizeDefinitionStart(effects, ok, nok) {
  const self = this;
  const defined = self.parser.gfmFootnotes || (self.parser.gfmFootnotes = []);
  /** @type {string} */
  let identifier;
  let size = 0;
  /** @type {boolean | undefined} */
  let data;
  return start;

  /**
   * Start of GFM footnote definition.
   *
   * ```markdown
   * > | [^a]: b
   *     ^
   * ```
   *
   * @type {State}
   */
  function start(code) {
    effects.enter('gfmFootnoteDefinition')._container = true;
    effects.enter('gfmFootnoteDefinitionLabel');
    effects.enter('gfmFootnoteDefinitionLabelMarker');
    effects.consume(code);
    effects.exit('gfmFootnoteDefinitionLabelMarker');
    return labelAtMarker;
  }

  /**
   * In label, at caret.
   *
   * ```markdown
   * > | [^a]: b
   *      ^
   * ```
   *
   * @type {State}
   */
  function labelAtMarker(code) {
    if (code === 94) {
      effects.enter('gfmFootnoteDefinitionMarker');
      effects.consume(code);
      effects.exit('gfmFootnoteDefinitionMarker');
      effects.enter('gfmFootnoteDefinitionLabelString');
      effects.enter('chunkString').contentType = 'string';
      return labelInside;
    }
    return nok(code);
  }

  /**
   * In label.
   *
   * > 👉 **Note**: `cmark-gfm` prevents whitespace from occurring in footnote
   * > definition labels.
   *
   * ```markdown
   * > | [^a]: b
   *       ^
   * ```
   *
   * @type {State}
   */
  function labelInside(code) {
    if (
    // Too long.
    size > 999 ||
    // Closing brace with nothing.
    code === 93 && !data ||
    // Space or tab is not supported by GFM for some reason.
    // `\n` and `[` not being supported makes sense.
    code === null || code === 91 || markdownLineEndingOrSpace(code)) {
      return nok(code);
    }
    if (code === 93) {
      effects.exit('chunkString');
      const token = effects.exit('gfmFootnoteDefinitionLabelString');
      identifier = normalizeIdentifier(self.sliceSerialize(token));
      effects.enter('gfmFootnoteDefinitionLabelMarker');
      effects.consume(code);
      effects.exit('gfmFootnoteDefinitionLabelMarker');
      effects.exit('gfmFootnoteDefinitionLabel');
      return labelAfter;
    }
    if (!markdownLineEndingOrSpace(code)) {
      data = true;
    }
    size++;
    effects.consume(code);
    return code === 92 ? labelEscape : labelInside;
  }

  /**
   * After `\`, at a special character.
   *
   * > 👉 **Note**: `cmark-gfm` currently does not support escaped brackets:
   * > <https://github.com/github/cmark-gfm/issues/240>
   *
   * ```markdown
   * > | [^a\*b]: c
   *         ^
   * ```
   *
   * @type {State}
   */
  function labelEscape(code) {
    if (code === 91 || code === 92 || code === 93) {
      effects.consume(code);
      size++;
      return labelInside;
    }
    return labelInside(code);
  }

  /**
   * After definition label.
   *
   * ```markdown
   * > | [^a]: b
   *         ^
   * ```
   *
   * @type {State}
   */
  function labelAfter(code) {
    if (code === 58) {
      effects.enter('definitionMarker');
      effects.consume(code);
      effects.exit('definitionMarker');
      if (!defined.includes(identifier)) {
        defined.push(identifier);
      }

      // Any whitespace after the marker is eaten, forming indented code
      // is not possible.
      // No space is also fine, just like a block quote marker.
      return factorySpace(effects, whitespaceAfter, 'gfmFootnoteDefinitionWhitespace');
    }
    return nok(code);
  }

  /**
   * After definition prefix.
   *
   * ```markdown
   * > | [^a]: b
   *           ^
   * ```
   *
   * @type {State}
   */
  function whitespaceAfter(code) {
    // `markdown-rs` has a wrapping token for the prefix that is closed here.
    return ok(code);
  }
}

/**
 * @this {TokenizeContext}
 * @type {Tokenizer}
 */
function tokenizeDefinitionContinuation(effects, ok, nok) {
  /// Start of footnote definition continuation.
  ///
  /// ```markdown
  ///   | [^a]: b
  /// > |     c
  ///     ^
  /// ```
  //
  // Either a blank line, which is okay, or an indented thing.
  return effects.check(blankLine, ok, effects.attempt(indent, ok, nok));
}

/** @type {Exiter} */
function gfmFootnoteDefinitionEnd(effects) {
  effects.exit('gfmFootnoteDefinition');
}

/**
 * @this {TokenizeContext}
 * @type {Tokenizer}
 */
function tokenizeIndent(effects, ok, nok) {
  const self = this;
  return factorySpace(effects, afterPrefix, 'gfmFootnoteDefinitionIndent', 4 + 1);

  /**
   * @type {State}
   */
  function afterPrefix(code) {
    const tail = self.events[self.events.length - 1];
    return tail && tail[1].type === 'gfmFootnoteDefinitionIndent' && tail[2].sliceSerialize(tail[1], true).length === 4 ? ok(code) : nok(code);
  }
}{
  "name": "micromark-extension-gfm-footnote",
  "version": "2.1.0",
  "description": "micromark extension to support GFM footnotes",
  "license": "MIT",
  "keywords": [
    "micromark",
    "micromark-extension",
    "gfm",
    "footnote",
    "note",
    "definition",
    "markdown",
    "unified"
  ],
  "repository": "micromark/micromark-extension-gfm-footnote",
  "bugs": "https://github.com/micromark/micromark-extension-gfm-footnote/issues",
  "funding": {
    "type": "opencollective",
    "url": "https://opencollective.com/unified"
  },
  "author": "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)",
  "contributors": [
    "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)"
  ],
  "sideEffects": false,
  "type": "module",
  "files": [
    "dev/",
    "lib/",
    "index.d.ts",
    "index.js"
  ],
  "exports": {
    "development": "./dev/index.js",
    "default": "./index.js"
  },
  "dependencies": {
    "devlop": "^1.0.0",
    "micromark-core-commonmark": "^2.0.0",
    "micromark-factory-space": "^2.0.0",
    "micromark-util-character": "^2.0.0",
    "micromark-util-normalize-identifier": "^2.0.0",
    "micromark-util-sanitize-uri": "^2.0.0",
    "micromark-util-symbol": "^2.0.0",
    "micromark-util-types": "^2.0.0"
  },
  "devDependencies": {
    "@types/node": "^20.0.0",
    "c8": "^10.0.0",
    "create-gfm-fixtures": "^1.0.0",
    "micromark": "^4.0.0",
    "micromark-build": "^2.0.0",
    "prettier": "^3.0.0",
    "remark-cli": "^12.0.0",
    "remark-preset-wooorm": "^10.0.0",
    "type-coverage": "^2.0.0",
    "typescript": "^5.0.0",
    "xo": "^0.58.0"
  },
  "scripts": {
    "prepack": "npm run build && npm run format",
    "build": "tsc --build --clean && tsc --build && type-coverage && micromark-build",
    "format": "remark . -qfo && prettier . -w --log-level warn && xo --fix",
    "test-api-prod": "node --conditions production test/index.js",
    "test-api-dev": "node --conditions development test/index.js",
    "test-api": "npm run test-api-dev && npm run test-api-prod",
    "test-coverage": "c8 --100 --reporter lcov npm run test-api",
    "test": "npm run build && npm run format && npm run test-coverage"
  },
  "prettier": {
    "bracketSpacing": false,
    "semi": false,
    "singleQuote": true,
    "tabWidth": 2,
    "trailingComma": "none",
    "useTabs": false
  },
  "remarkConfig": {
    "plugins": [
      "remark-preset-wooorm"
    ]
  },
  "typeCoverage": {
    "atLeast": 100,
    "detail": true,
    "ignoreCatch": true,
    "strict": true
  },
  "xo": {
    "prettier": true,
    "rules": {
      "logical-assignment-operators": "off",
      "unicorn/no-this-assignment": "off",
      "unicorn/prefer-at": "off",
      "unicorn/prefer-string-replace-all": "off"
    },
    "overrides": [
      {
        "files": [
          "**/*.d.ts"
        ],
        "rules": {
          "@typescript-eslint/array-type": [
            "error",
            {
              "default": "generic"
            }
          ],
          "@typescript-eslint/ban-types": [
            "error",
            {
              "extendDefaults": true
            }
          ],
          "@typescript-eslint/consistent-type-definitions": [
            "error",
            "interface"
          ]
        }
      },
      {
        "files": [
          "test/**/*.js"
        ],
        "rules": {
          "no-await-in-loop": 0
        }
      }
    ]
  }
}
// Note: more types exposed from `index.d.ts`.
export { gfmStrikethroughHtml } from './lib/html.js';
export { gfmStrikethrough } from './lib/syntax.js';/**
 * @import {HtmlExtension} from 'micromark-util-types'
 */

/**
 * Create an HTML extension for `micromark` to support GFM strikethrough when
 * serializing to HTML.
 *
 * @returns {HtmlExtension}
 *   Extension for `micromark` that can be passed in `htmlExtensions`, to
 *   support GFM strikethrough when serializing to HTML.
 */
export function gfmStrikethroughHtml() {
  return {
    enter: {
      strikethrough() {
        this.tag('<del>');
      }
    },
    exit: {
      strikethrough() {
        this.tag('</del>');
      }
    }
  };
}/**
 * @import {Options} from 'micromark-extension-gfm-strikethrough'
 * @import {Event, Extension, Resolver, State, Token, TokenizeContext, Tokenizer} from 'micromark-util-types'
 */

import { splice } from 'micromark-util-chunked';
import { classifyCharacter } from 'micromark-util-classify-character';
import { resolveAll } from 'micromark-util-resolve-all';
/**
 * Create an extension for `micromark` to enable GFM strikethrough syntax.
 *
 * @param {Options | null | undefined} [options={}]
 *   Configuration.
 * @returns {Extension}
 *   Extension for `micromark` that can be passed in `extensions`, to
 *   enable GFM strikethrough syntax.
 */
export function gfmStrikethrough(options) {
  const options_ = options || {};
  let single = options_.singleTilde;
  const tokenizer = {
    name: 'strikethrough',
    tokenize: tokenizeStrikethrough,
    resolveAll: resolveAllStrikethrough
  };
  if (single === null || single === undefined) {
    single = true;
  }
  return {
    text: {
      [126]: tokenizer
    },
    insideSpan: {
      null: [tokenizer]
    },
    attentionMarkers: {
      null: [126]
    }
  };

  /**
   * Take events and resolve strikethrough.
   *
   * @type {Resolver}
   */
  function resolveAllStrikethrough(events, context) {
    let index = -1;

    // Walk through all events.
    while (++index < events.length) {
      // Find a token that can close.
      if (events[index][0] === 'enter' && events[index][1].type === 'strikethroughSequenceTemporary' && events[index][1]._close) {
        let open = index;

        // Now walk back to find an opener.
        while (open--) {
          // Find a token that can open the closer.
          if (events[open][0] === 'exit' && events[open][1].type === 'strikethroughSequenceTemporary' && events[open][1]._open &&
          // If the sizes are the same:
          events[index][1].end.offset - events[index][1].start.offset === events[open][1].end.offset - events[open][1].start.offset) {
            events[index][1].type = 'strikethroughSequence';
            events[open][1].type = 'strikethroughSequence';

            /** @type {Token} */
            const strikethrough = {
              type: 'strikethrough',
              start: Object.assign({}, events[open][1].start),
              end: Object.assign({}, events[index][1].end)
            };

            /** @type {Token} */
            const text = {
              type: 'strikethroughText',
              start: Object.assign({}, events[open][1].end),
              end: Object.assign({}, events[index][1].start)
            };

            // Opening.
            /** @type {Array<Event>} */
            const nextEvents = [['enter', strikethrough, context], ['enter', events[open][1], context], ['exit', events[open][1], context], ['enter', text, context]];
            const insideSpan = context.parser.constructs.insideSpan.null;
            if (insideSpan) {
              // Between.
              splice(nextEvents, nextEvents.length, 0, resolveAll(insideSpan, events.slice(open + 1, index), context));
            }

            // Closing.
            splice(nextEvents, nextEvents.length, 0, [['exit', text, context], ['enter', events[index][1], context], ['exit', events[index][1], context], ['exit', strikethrough, context]]);
            splice(events, open - 1, index - open + 3, nextEvents);
            index = open + nextEvents.length - 2;
            break;
          }
        }
      }
    }
    index = -1;
    while (++index < events.length) {
      if (events[index][1].type === 'strikethroughSequenceTemporary') {
        events[index][1].type = "data";
      }
    }
    return events;
  }

  /**
   * @this {TokenizeContext}
   * @type {Tokenizer}
   */
  function tokenizeStrikethrough(effects, ok, nok) {
    const previous = this.previous;
    const events = this.events;
    let size = 0;
    return start;

    /** @type {State} */
    function start(code) {
      if (previous === 126 && events[events.length - 1][1].type !== "characterEscape") {
        return nok(code);
      }
      effects.enter('strikethroughSequenceTemporary');
      return more(code);
    }

    /** @type {State} */
    function more(code) {
      const before = classifyCharacter(previous);
      if (code === 126) {
        // If this is the third marker, exit.
        if (size > 1) return nok(code);
        effects.consume(code);
        size++;
        return more;
      }
      if (size < 2 && !single) return nok(code);
      const token = effects.exit('strikethroughSequenceTemporary');
      const after = classifyCharacter(code);
      token._open = !after || after === 2 && Boolean(before);
      token._close = !before || before === 2 && Boolean(after);
      return ok(code);
    }
  }
}{
  "name": "micromark-extension-gfm-strikethrough",
  "version": "2.1.0",
  "description": "micromark extension to support GFM strikethrough",
  "license": "MIT",
  "keywords": [
    "micromark",
    "micromark-extension",
    "strikethrough",
    "strike",
    "through",
    "del",
    "delete",
    "deletion",
    "gfm",
    "markdown",
    "unified"
  ],
  "repository": "micromark/micromark-extension-gfm-strikethrough",
  "bugs": "https://github.com/micromark/micromark-extension-gfm-strikethrough/issues",
  "funding": {
    "type": "opencollective",
    "url": "https://opencollective.com/unified"
  },
  "author": "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)",
  "contributors": [
    "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)"
  ],
  "sideEffects": false,
  "type": "module",
  "files": [
    "dev/",
    "lib/",
    "index.d.ts",
    "index.js"
  ],
  "exports": {
    "development": "./dev/index.js",
    "default": "./index.js"
  },
  "dependencies": {
    "devlop": "^1.0.0",
    "micromark-util-chunked": "^2.0.0",
    "micromark-util-classify-character": "^2.0.0",
    "micromark-util-resolve-all": "^2.0.0",
    "micromark-util-symbol": "^2.0.0",
    "micromark-util-types": "^2.0.0"
  },
  "devDependencies": {
    "@types/node": "^20.0.0",
    "c8": "^10.0.0",
    "create-gfm-fixtures": "^1.0.0",
    "micromark": "^4.0.0",
    "micromark-build": "^2.0.0",
    "prettier": "^3.0.0",
    "remark-cli": "^12.0.0",
    "remark-preset-wooorm": "^10.0.0",
    "tape": "^5.0.0",
    "type-coverage": "^2.0.0",
    "typescript": "^5.0.0",
    "xo": "^0.58.0"
  },
  "scripts": {
    "prepack": "npm run build && npm run format",
    "build": "tsc --build --clean && tsc --build && type-coverage && micromark-build",
    "format": "remark . -qfo && prettier . -w --log-level warn && xo --fix",
    "test-api-prod": "node --conditions production test/index.js",
    "test-api-dev": "node --conditions development test/index.js",
    "test-api": "npm run test-api-dev && npm run test-api-prod",
    "test-coverage": "c8 --100 --reporter lcov npm run test-api",
    "test": "npm run build && npm run format && npm run test-coverage"
  },
  "prettier": {
    "bracketSpacing": false,
    "semi": false,
    "singleQuote": true,
    "tabWidth": 2,
    "trailingComma": "none",
    "useTabs": false
  },
  "remarkConfig": {
    "plugins": [
      "remark-preset-wooorm"
    ]
  },
  "typeCoverage": {
    "atLeast": 100,
    "detail": true,
    "ignoreCatch": true,
    "strict": true
  },
  "xo": {
    "prettier": true,
    "rules": {
      "logical-assignment-operators": "off",
      "max-depth": "off",
      "unicorn/prefer-at": "off"
    },
    "overrides": [
      {
        "files": "**/*.ts",
        "rules": {
          "@typescript-eslint/array-type": [
            "error",
            {
              "default": "generic"
            }
          ],
          "@typescript-eslint/ban-types": [
            "error",
            {
              "extendDefaults": true
            }
          ],
          "@typescript-eslint/consistent-type-definitions": [
            "error",
            "interface"
          ]
        }
      },
      {
        "files": "test/**/*.js",
        "rules": {
          "no-await-in-loop": 0
        }
      }
    ]
  }
}
export { gfmTableHtml } from './lib/html.js';
export { gfmTable } from './lib/syntax.js';/**
 * @import {Event} from 'micromark-util-types'
 */

// Port of `edit_map.rs` from `markdown-rs`.
// This should move to `markdown-js` later.

// Deal with several changes in events, batching them together.
//
// Preferably, changes should be kept to a minimum.
// Sometimes, it’s needed to change the list of events, because parsing can be
// messy, and it helps to expose a cleaner interface of events to the compiler
// and other users.
// It can also help to merge many adjacent similar events.
// And, in other cases, it’s needed to parse subcontent: pass some events
// through another tokenizer and inject the result.

/**
 * @typedef {[number, number, Array<Event>]} Change
 * @typedef {[number, number, number]} Jump
 */

/**
 * Tracks a bunch of edits.
 */
export class EditMap {
  /**
   * Create a new edit map.
   */
  constructor() {
    /**
     * Record of changes.
     *
     * @type {Array<Change>}
     */
    this.map = [];
  }

  /**
   * Create an edit: a remove and/or add at a certain place.
   *
   * @param {number} index
   * @param {number} remove
   * @param {Array<Event>} add
   * @returns {undefined}
   */
  add(index, remove, add) {
    addImplementation(this, index, remove, add);
  }

  // To do: add this when moving to `micromark`.
  // /**
  //  * Create an edit: but insert `add` before existing additions.
  //  *
  //  * @param {number} index
  //  * @param {number} remove
  //  * @param {Array<Event>} add
  //  * @returns {undefined}
  //  */
  // addBefore(index, remove, add) {
  //   addImplementation(this, index, remove, add, true)
  // }

  /**
   * Done, change the events.
   *
   * @param {Array<Event>} events
   * @returns {undefined}
   */
  consume(events) {
    this.map.sort(function (a, b) {
      return a[0] - b[0];
    });

    /* c8 ignore next 3 -- `resolve` is never called without tables, so without edits. */
    if (this.map.length === 0) {
      return;
    }

    // To do: if links are added in events, like they are in `markdown-rs`,
    // this is needed.
    // // Calculate jumps: where items in the current list move to.
    // /** @type {Array<Jump>} */
    // const jumps = []
    // let index = 0
    // let addAcc = 0
    // let removeAcc = 0
    // while (index < this.map.length) {
    //   const [at, remove, add] = this.map[index]
    //   removeAcc += remove
    //   addAcc += add.length
    //   jumps.push([at, removeAcc, addAcc])
    //   index += 1
    // }
    //
    // . shiftLinks(events, jumps)

    let index = this.map.length;
    /** @type {Array<Array<Event>>} */
    const vecs = [];
    while (index > 0) {
      index -= 1;
      vecs.push(events.slice(this.map[index][0] + this.map[index][1]), this.map[index][2]);

      // Truncate rest.
      events.length = this.map[index][0];
    }
    vecs.push(events.slice());
    events.length = 0;
    let slice = vecs.pop();
    while (slice) {
      for (const element of slice) {
        events.push(element);
      }
      slice = vecs.pop();
    }

    // Truncate everything.
    this.map.length = 0;
  }
}

/**
 * Create an edit.
 *
 * @param {EditMap} editMap
 * @param {number} at
 * @param {number} remove
 * @param {Array<Event>} add
 * @returns {undefined}
 */
function addImplementation(editMap, at, remove, add) {
  let index = 0;

  /* c8 ignore next 3 -- `resolve` is never called without tables, so without edits. */
  if (remove === 0 && add.length === 0) {
    return;
  }
  while (index < editMap.map.length) {
    if (editMap.map[index][0] === at) {
      editMap.map[index][1] += remove;

      // To do: before not used by tables, use when moving to micromark.
      // if (before) {
      //   add.push(...editMap.map[index][2])
      //   editMap.map[index][2] = add
      // } else {
      editMap.map[index][2].push(...add);
      // }

      return;
    }
    index += 1;
  }
  editMap.map.push([at, remove, add]);
}

// /**
//  * Shift `previous` and `next` links according to `jumps`.
//  *
//  * This fixes links in case there are events removed or added between them.
//  *
//  * @param {Array<Event>} events
//  * @param {Array<Jump>} jumps
//  */
// function shiftLinks(events, jumps) {
//   let jumpIndex = 0
//   let index = 0
//   let add = 0
//   let rm = 0

//   while (index < events.length) {
//     const rmCurr = rm

//     while (jumpIndex < jumps.length && jumps[jumpIndex][0] <= index) {
//       add = jumps[jumpIndex][2]
//       rm = jumps[jumpIndex][1]
//       jumpIndex += 1
//     }

//     // Ignore items that will be removed.
//     if (rm > rmCurr) {
//       index += rm - rmCurr
//     } else {
//       // ?
//       // if let Some(link) = &events[index].link {
//       //     if let Some(next) = link.next {
//       //         events[next].link.as_mut().unwrap().previous = Some(index + add - rm);
//       //         while jumpIndex < jumps.len() && jumps[jumpIndex].0 <= next {
//       //             add = jumps[jumpIndex].2;
//       //             rm = jumps[jumpIndex].1;
//       //             jumpIndex += 1;
//       //         }
//       //         events[index].link.as_mut().unwrap().next = Some(next + add - rm);
//       //         index = next;
//       //         continue;
//       //     }
//       // }
//       index += 1
//     }
//   }
// }/**
 * @import {HtmlExtension} from 'micromark-util-types'
 */

const alignment = {
  none: '',
  left: ' align="left"',
  right: ' align="right"',
  center: ' align="center"'
};

// To do: micromark@5: use `infer` here, when all events are exposed.

/**
 * Create an HTML extension for `micromark` to support GitHub tables when
 * serializing to HTML.
 *
 * @returns {HtmlExtension}
 *   Extension for `micromark` that can be passed in `htmlExtensions` to
 *   support GitHub tables when serializing to HTML.
 */
export function gfmTableHtml() {
  return {
    enter: {
      table(token) {
        const tableAlign = token._align;
        this.lineEndingIfNeeded();
        this.tag('<table>');
        this.setData('tableAlign', tableAlign);
      },
      tableBody() {
        this.tag('<tbody>');
      },
      tableData() {
        const tableAlign = this.getData('tableAlign');
        const tableColumn = this.getData('tableColumn');
        const align = alignment[tableAlign[tableColumn]];
        if (align === undefined) {
          // Capture results to ignore them.
          this.buffer();
        } else {
          this.lineEndingIfNeeded();
          this.tag('<td' + align + '>');
        }
      },
      tableHead() {
        this.lineEndingIfNeeded();
        this.tag('<thead>');
      },
      tableHeader() {
        const tableAlign = this.getData('tableAlign');
        const tableColumn = this.getData('tableColumn');
        const align = alignment[tableAlign[tableColumn]];
        this.lineEndingIfNeeded();
        this.tag('<th' + align + '>');
      },
      tableRow() {
        this.setData('tableColumn', 0);
        this.lineEndingIfNeeded();
        this.tag('<tr>');
      }
    },
    exit: {
      // Overwrite the default code text data handler to unescape escaped pipes when
      // they are in tables.
      codeTextData(token) {
        let value = this.sliceSerialize(token);
        if (this.getData('tableAlign')) {
          value = value.replace(/\\([\\|])/g, replace);
        }
        this.raw(this.encode(value));
      },
      table() {
        this.setData('tableAlign');
        // Note: we don’t set `slurpAllLineEndings` anymore, in delimiter rows,
        // but we do need to reset it to match a funky newline GH generates for
        // list items combined with tables.
        this.setData('slurpAllLineEndings');
        this.lineEndingIfNeeded();
        this.tag('</table>');
      },
      tableBody() {
        this.lineEndingIfNeeded();
        this.tag('</tbody>');
      },
      tableData() {
        const tableAlign = this.getData('tableAlign');
        const tableColumn = this.getData('tableColumn');
        if (tableColumn in tableAlign) {
          this.tag('</td>');
          this.setData('tableColumn', tableColumn + 1);
        } else {
          // Stop capturing.
          this.resume();
        }
      },
      tableHead() {
        this.lineEndingIfNeeded();
        this.tag('</thead>');
      },
      tableHeader() {
        const tableColumn = this.getData('tableColumn');
        this.tag('</th>');
        this.setData('tableColumn', tableColumn + 1);
      },
      tableRow() {
        const tableAlign = this.getData('tableAlign');
        let tableColumn = this.getData('tableColumn');
        while (tableColumn < tableAlign.length) {
          this.lineEndingIfNeeded();
          this.tag('<td' + alignment[tableAlign[tableColumn]] + '></td>');
          tableColumn++;
        }
        this.setData('tableColumn', tableColumn);
        this.lineEndingIfNeeded();
        this.tag('</tr>');
      }
    }
  };
}

/**
 * @param {string} $0
 * @param {string} $1
 * @returns {string}
 */
function replace($0, $1) {
  // Pipes work, backslashes don’t (but can’t escape pipes).
  return $1 === '|' ? $1 : $0;
}/**
 * @import {Event} from 'micromark-util-types'
 */

/**
 * @typedef {'center' | 'left' | 'none' | 'right'} Align
 */

/**
 * Figure out the alignment of a GFM table.
 *
 * @param {Readonly<Array<Event>>} events
 *   List of events.
 * @param {number} index
 *   Table enter event.
 * @returns {Array<Align>}
 *   List of aligns.
 */
export function gfmTableAlign(events, index) {
  let inDelimiterRow = false;
  /** @type {Array<Align>} */
  const align = [];
  while (index < events.length) {
    const event = events[index];
    if (inDelimiterRow) {
      if (event[0] === 'enter') {
        // Start of alignment value: set a new column.
        // To do: `markdown-rs` uses `tableDelimiterCellValue`.
        if (event[1].type === 'tableContent') {
          align.push(events[index + 1][1].type === 'tableDelimiterMarker' ? 'left' : 'none');
        }
      }
      // Exits:
      // End of alignment value: change the column.
      // To do: `markdown-rs` uses `tableDelimiterCellValue`.
      else if (event[1].type === 'tableContent') {
        if (events[index - 1][1].type === 'tableDelimiterMarker') {
          const alignIndex = align.length - 1;
          align[alignIndex] = align[alignIndex] === 'left' ? 'center' : 'right';
        }
      }
      // Done!
      else if (event[1].type === 'tableDelimiterRow') {
        break;
      }
    } else if (event[0] === 'enter' && event[1].type === 'tableDelimiterRow') {
      inDelimiterRow = true;
    }
    index += 1;
  }
  return align;
}/**
 * @import {Event, Extension, Point, Resolver, State, Token, TokenizeContext, Tokenizer} from 'micromark-util-types'
 */

/**
 * @typedef {[number, number, number, number]} Range
 *   Cell info.
 *
 * @typedef {0 | 1 | 2 | 3} RowKind
 *   Where we are: `1` for head row, `2` for delimiter row, `3` for body row.
 */

import { factorySpace } from 'micromark-factory-space';
import { markdownLineEnding, markdownLineEndingOrSpace, markdownSpace } from 'micromark-util-character';
import { EditMap } from './edit-map.js';
import { gfmTableAlign } from './infer.js';

/**
 * Create an HTML extension for `micromark` to support GitHub tables syntax.
 *
 * @returns {Extension}
 *   Extension for `micromark` that can be passed in `extensions` to enable GFM
 *   table syntax.
 */
export function gfmTable() {
  return {
    flow: {
      null: {
        name: 'table',
        tokenize: tokenizeTable,
        resolveAll: resolveTable
      }
    }
  };
}

/**
 * @this {TokenizeContext}
 * @type {Tokenizer}
 */
function tokenizeTable(effects, ok, nok) {
  const self = this;
  let size = 0;
  let sizeB = 0;
  /** @type {boolean | undefined} */
  let seen;
  return start;

  /**
   * Start of a GFM table.
   *
   * If there is a valid table row or table head before, then we try to parse
   * another row.
   * Otherwise, we try to parse a head.
   *
   * ```markdown
   * > | | a |
   *     ^
   *   | | - |
   * > | | b |
   *     ^
   * ```
   * @type {State}
   */
  function start(code) {
    let index = self.events.length - 1;
    while (index > -1) {
      const type = self.events[index][1].type;
      if (type === "lineEnding" ||
      // Note: markdown-rs uses `whitespace` instead of `linePrefix`
      type === "linePrefix") index--;else break;
    }
    const tail = index > -1 ? self.events[index][1].type : null;
    const next = tail === 'tableHead' || tail === 'tableRow' ? bodyRowStart : headRowBefore;

    // Don’t allow lazy body rows.
    if (next === bodyRowStart && self.parser.lazy[self.now().line]) {
      return nok(code);
    }
    return next(code);
  }

  /**
   * Before table head row.
   *
   * ```markdown
   * > | | a |
   *     ^
   *   | | - |
   *   | | b |
   * ```
   *
   * @type {State}
   */
  function headRowBefore(code) {
    effects.enter('tableHead');
    effects.enter('tableRow');
    return headRowStart(code);
  }

  /**
   * Before table head row, after whitespace.
   *
   * ```markdown
   * > | | a |
   *     ^
   *   | | - |
   *   | | b |
   * ```
   *
   * @type {State}
   */
  function headRowStart(code) {
    if (code === 124) {
      return headRowBreak(code);
    }

    // To do: micromark-js should let us parse our own whitespace in extensions,
    // like `markdown-rs`:
    //
    // ```js
    // // 4+ spaces.
    // if (markdownSpace(code)) {
    //   return nok(code)
    // }
    // ```

    seen = true;
    // Count the first character, that isn’t a pipe, double.
    sizeB += 1;
    return headRowBreak(code);
  }

  /**
   * At break in table head row.
   *
   * ```markdown
   * > | | a |
   *     ^
   *       ^
   *         ^
   *   | | - |
   *   | | b |
   * ```
   *
   * @type {State}
   */
  function headRowBreak(code) {
    if (code === null) {
      // Note: in `markdown-rs`, we need to reset, in `micromark-js` we don‘t.
      return nok(code);
    }
    if (markdownLineEnding(code)) {
      // If anything other than one pipe (ignoring whitespace) was used, it’s fine.
      if (sizeB > 1) {
        sizeB = 0;
        // To do: check if this works.
        // Feel free to interrupt:
        self.interrupt = true;
        effects.exit('tableRow');
        effects.enter("lineEnding");
        effects.consume(code);
        effects.exit("lineEnding");
        return headDelimiterStart;
      }

      // Note: in `markdown-rs`, we need to reset, in `micromark-js` we don‘t.
      return nok(code);
    }
    if (markdownSpace(code)) {
      // To do: check if this is fine.
      // effects.attempt(State::Next(StateName::GfmTableHeadRowBreak), State::Nok)
      // State::Retry(space_or_tab(tokenizer))
      return factorySpace(effects, headRowBreak, "whitespace")(code);
    }
    sizeB += 1;
    if (seen) {
      seen = false;
      // Header cell count.
      size += 1;
    }
    if (code === 124) {
      effects.enter('tableCellDivider');
      effects.consume(code);
      effects.exit('tableCellDivider');
      // Whether a delimiter was seen.
      seen = true;
      return headRowBreak;
    }

    // Anything else is cell data.
    effects.enter("data");
    return headRowData(code);
  }

  /**
   * In table head row data.
   *
   * ```markdown
   * > | | a |
   *       ^
   *   | | - |
   *   | | b |
   * ```
   *
   * @type {State}
   */
  function headRowData(code) {
    if (code === null || code === 124 || markdownLineEndingOrSpace(code)) {
      effects.exit("data");
      return headRowBreak(code);
    }
    effects.consume(code);
    return code === 92 ? headRowEscape : headRowData;
  }

  /**
   * In table head row escape.
   *
   * ```markdown
   * > | | a\-b |
   *         ^
   *   | | ---- |
   *   | | c    |
   * ```
   *
   * @type {State}
   */
  function headRowEscape(code) {
    if (code === 92 || code === 124) {
      effects.consume(code);
      return headRowData;
    }
    return headRowData(code);
  }

  /**
   * Before delimiter row.
   *
   * ```markdown
   *   | | a |
   * > | | - |
   *     ^
   *   | | b |
   * ```
   *
   * @type {State}
   */
  function headDelimiterStart(code) {
    // Reset `interrupt`.
    self.interrupt = false;

    // Note: in `markdown-rs`, we need to handle piercing here too.
    if (self.parser.lazy[self.now().line]) {
      return nok(code);
    }
    effects.enter('tableDelimiterRow');
    // Track if we’ve seen a `:` or `|`.
    seen = false;
    if (markdownSpace(code)) {
      return factorySpace(effects, headDelimiterBefore, "linePrefix", self.parser.constructs.disable.null.includes('codeIndented') ? undefined : 4)(code);
    }
    return headDelimiterBefore(code);
  }

  /**
   * Before delimiter row, after optional whitespace.
   *
   * Reused when a `|` is found later, to parse another cell.
   *
   * ```markdown
   *   | | a |
   * > | | - |
   *     ^
   *   | | b |
   * ```
   *
   * @type {State}
   */
  function headDelimiterBefore(code) {
    if (code === 45 || code === 58) {
      return headDelimiterValueBefore(code);
    }
    if (code === 124) {
      seen = true;
      // If we start with a pipe, we open a cell marker.
      effects.enter('tableCellDivider');
      effects.consume(code);
      effects.exit('tableCellDivider');
      return headDelimiterCellBefore;
    }

    // More whitespace / empty row not allowed at start.
    return headDelimiterNok(code);
  }

  /**
   * After `|`, before delimiter cell.
   *
   * ```markdown
   *   | | a |
   * > | | - |
   *      ^
   * ```
   *
   * @type {State}
   */
  function headDelimiterCellBefore(code) {
    if (markdownSpace(code)) {
      return factorySpace(effects, headDelimiterValueBefore, "whitespace")(code);
    }
    return headDelimiterValueBefore(code);
  }

  /**
   * Before delimiter cell value.
   *
   * ```markdown
   *   | | a |
   * > | | - |
   *       ^
   * ```
   *
   * @type {State}
   */
  function headDelimiterValueBefore(code) {
    // Align: left.
    if (code === 58) {
      sizeB += 1;
      seen = true;
      effects.enter('tableDelimiterMarker');
      effects.consume(code);
      effects.exit('tableDelimiterMarker');
      return headDelimiterLeftAlignmentAfter;
    }

    // Align: none.
    if (code === 45) {
      sizeB += 1;
      // To do: seems weird that this *isn’t* left aligned, but that state is used?
      return headDelimiterLeftAlignmentAfter(code);
    }
    if (code === null || markdownLineEnding(code)) {
      return headDelimiterCellAfter(code);
    }
    return headDelimiterNok(code);
  }

  /**
   * After delimiter cell left alignment marker.
   *
   * ```markdown
   *   | | a  |
   * > | | :- |
   *        ^
   * ```
   *
   * @type {State}
   */
  function headDelimiterLeftAlignmentAfter(code) {
    if (code === 45) {
      effects.enter('tableDelimiterFiller');
      return headDelimiterFiller(code);
    }

    // Anything else is not ok after the left-align colon.
    return headDelimiterNok(code);
  }

  /**
   * In delimiter cell filler.
   *
   * ```markdown
   *   | | a |
   * > | | - |
   *       ^
   * ```
   *
   * @type {State}
   */
  function headDelimiterFiller(code) {
    if (code === 45) {
      effects.consume(code);
      return headDelimiterFiller;
    }

    // Align is `center` if it was `left`, `right` otherwise.
    if (code === 58) {
      seen = true;
      effects.exit('tableDelimiterFiller');
      effects.enter('tableDelimiterMarker');
      effects.consume(code);
      effects.exit('tableDelimiterMarker');
      return headDelimiterRightAlignmentAfter;
    }
    effects.exit('tableDelimiterFiller');
    return headDelimiterRightAlignmentAfter(code);
  }

  /**
   * After delimiter cell right alignment marker.
   *
   * ```markdown
   *   | |  a |
   * > | | -: |
   *         ^
   * ```
   *
   * @type {State}
   */
  function headDelimiterRightAlignmentAfter(code) {
    if (markdownSpace(code)) {
      return factorySpace(effects, headDelimiterCellAfter, "whitespace")(code);
    }
    return headDelimiterCellAfter(code);
  }

  /**
   * After delimiter cell.
   *
   * ```markdown
   *   | |  a |
   * > | | -: |
   *          ^
   * ```
   *
   * @type {State}
   */
  function headDelimiterCellAfter(code) {
    if (code === 124) {
      return headDelimiterBefore(code);
    }
    if (code === null || markdownLineEnding(code)) {
      // Exit when:
      // * there was no `:` or `|` at all (it’s a thematic break or setext
      //   underline instead)
      // * the header cell count is not the delimiter cell count
      if (!seen || size !== sizeB) {
        return headDelimiterNok(code);
      }

      // Note: in markdown-rs`, a reset is needed here.
      effects.exit('tableDelimiterRow');
      effects.exit('tableHead');
      // To do: in `markdown-rs`, resolvers need to be registered manually.
      // effects.register_resolver(ResolveName::GfmTable)
      return ok(code);
    }
    return headDelimiterNok(code);
  }

  /**
   * In delimiter row, at a disallowed byte.
   *
   * ```markdown
   *   | | a |
   * > | | x |
   *       ^
   * ```
   *
   * @type {State}
   */
  function headDelimiterNok(code) {
    // Note: in `markdown-rs`, we need to reset, in `micromark-js` we don‘t.
    return nok(code);
  }

  /**
   * Before table body row.
   *
   * ```markdown
   *   | | a |
   *   | | - |
   * > | | b |
   *     ^
   * ```
   *
   * @type {State}
   */
  function bodyRowStart(code) {
    // Note: in `markdown-rs` we need to manually take care of a prefix,
    // but in `micromark-js` that is done for us, so if we’re here, we’re
    // never at whitespace.
    effects.enter('tableRow');
    return bodyRowBreak(code);
  }

  /**
   * At break in table body row.
   *
   * ```markdown
   *   | | a |
   *   | | - |
   * > | | b |
   *     ^
   *       ^
   *         ^
   * ```
   *
   * @type {State}
   */
  function bodyRowBreak(code) {
    if (code === 124) {
      effects.enter('tableCellDivider');
      effects.consume(code);
      effects.exit('tableCellDivider');
      return bodyRowBreak;
    }
    if (code === null || markdownLineEnding(code)) {
      effects.exit('tableRow');
      return ok(code);
    }
    if (markdownSpace(code)) {
      return factorySpace(effects, bodyRowBreak, "whitespace")(code);
    }

    // Anything else is cell content.
    effects.enter("data");
    return bodyRowData(code);
  }

  /**
   * In table body row data.
   *
   * ```markdown
   *   | | a |
   *   | | - |
   * > | | b |
   *       ^
   * ```
   *
   * @type {State}
   */
  function bodyRowData(code) {
    if (code === null || code === 124 || markdownLineEndingOrSpace(code)) {
      effects.exit("data");
      return bodyRowBreak(code);
    }
    effects.consume(code);
    return code === 92 ? bodyRowEscape : bodyRowData;
  }

  /**
   * In table body row escape.
   *
   * ```markdown
   *   | | a    |
   *   | | ---- |
   * > | | b\-c |
   *         ^
   * ```
   *
   * @type {State}
   */
  function bodyRowEscape(code) {
    if (code === 92 || code === 124) {
      effects.consume(code);
      return bodyRowData;
    }
    return bodyRowData(code);
  }
}

/** @type {Resolver} */

function resolveTable(events, context) {
  let index = -1;
  let inFirstCellAwaitingPipe = true;
  /** @type {RowKind} */
  let rowKind = 0;
  /** @type {Range} */
  let lastCell = [0, 0, 0, 0];
  /** @type {Range} */
  let cell = [0, 0, 0, 0];
  let afterHeadAwaitingFirstBodyRow = false;
  let lastTableEnd = 0;
  /** @type {Token | undefined} */
  let currentTable;
  /** @type {Token | undefined} */
  let currentBody;
  /** @type {Token | undefined} */
  let currentCell;
  const map = new EditMap();
  while (++index < events.length) {
    const event = events[index];
    const token = event[1];
    if (event[0] === 'enter') {
      // Start of head.
      if (token.type === 'tableHead') {
        afterHeadAwaitingFirstBodyRow = false;

        // Inject previous (body end and) table end.
        if (lastTableEnd !== 0) {
          flushTableEnd(map, context, lastTableEnd, currentTable, currentBody);
          currentBody = undefined;
          lastTableEnd = 0;
        }

        // Inject table start.
        currentTable = {
          type: 'table',
          start: Object.assign({}, token.start),
          // Note: correct end is set later.
          end: Object.assign({}, token.end)
        };
        map.add(index, 0, [['enter', currentTable, context]]);
      } else if (token.type === 'tableRow' || token.type === 'tableDelimiterRow') {
        inFirstCellAwaitingPipe = true;
        currentCell = undefined;
        lastCell = [0, 0, 0, 0];
        cell = [0, index + 1, 0, 0];

        // Inject table body start.
        if (afterHeadAwaitingFirstBodyRow) {
          afterHeadAwaitingFirstBodyRow = false;
          currentBody = {
            type: 'tableBody',
            start: Object.assign({}, token.start),
            // Note: correct end is set later.
            end: Object.assign({}, token.end)
          };
          map.add(index, 0, [['enter', currentBody, context]]);
        }
        rowKind = token.type === 'tableDelimiterRow' ? 2 : currentBody ? 3 : 1;
      }
      // Cell data.
      else if (rowKind && (token.type === "data" || token.type === 'tableDelimiterMarker' || token.type === 'tableDelimiterFiller')) {
        inFirstCellAwaitingPipe = false;

        // First value in cell.
        if (cell[2] === 0) {
          if (lastCell[1] !== 0) {
            cell[0] = cell[1];
            currentCell = flushCell(map, context, lastCell, rowKind, undefined, currentCell);
            lastCell = [0, 0, 0, 0];
          }
          cell[2] = index;
        }
      } else if (token.type === 'tableCellDivider') {
        if (inFirstCellAwaitingPipe) {
          inFirstCellAwaitingPipe = false;
        } else {
          if (lastCell[1] !== 0) {
            cell[0] = cell[1];
            currentCell = flushCell(map, context, lastCell, rowKind, undefined, currentCell);
          }
          lastCell = cell;
          cell = [lastCell[1], index, 0, 0];
        }
      }
    }
    // Exit events.
    else if (token.type === 'tableHead') {
      afterHeadAwaitingFirstBodyRow = true;
      lastTableEnd = index;
    } else if (token.type === 'tableRow' || token.type === 'tableDelimiterRow') {
      lastTableEnd = index;
      if (lastCell[1] !== 0) {
        cell[0] = cell[1];
        currentCell = flushCell(map, context, lastCell, rowKind, index, currentCell);
      } else if (cell[1] !== 0) {
        currentCell = flushCell(map, context, cell, rowKind, index, currentCell);
      }
      rowKind = 0;
    } else if (rowKind && (token.type === "data" || token.type === 'tableDelimiterMarker' || token.type === 'tableDelimiterFiller')) {
      cell[3] = index;
    }
  }
  if (lastTableEnd !== 0) {
    flushTableEnd(map, context, lastTableEnd, currentTable, currentBody);
  }
  map.consume(context.events);

  // To do: move this into `html`, when events are exposed there.
  // That’s what `markdown-rs` does.
  // That needs updates to `mdast-util-gfm-table`.
  index = -1;
  while (++index < context.events.length) {
    const event = context.events[index];
    if (event[0] === 'enter' && event[1].type === 'table') {
      event[1]._align = gfmTableAlign(context.events, index);
    }
  }
  return events;
}

/**
 * Generate a cell.
 *
 * @param {EditMap} map
 * @param {Readonly<TokenizeContext>} context
 * @param {Readonly<Range>} range
 * @param {RowKind} rowKind
 * @param {number | undefined} rowEnd
 * @param {Token | undefined} previousCell
 * @returns {Token | undefined}
 */
// eslint-disable-next-line max-params
function flushCell(map, context, range, rowKind, rowEnd, previousCell) {
  // `markdown-rs` uses:
  // rowKind === 2 ? 'tableDelimiterCell' : 'tableCell'
  const groupName = rowKind === 1 ? 'tableHeader' : rowKind === 2 ? 'tableDelimiter' : 'tableData';
  // `markdown-rs` uses:
  // rowKind === 2 ? 'tableDelimiterCellValue' : 'tableCellText'
  const valueName = 'tableContent';

  // Insert an exit for the previous cell, if there is one.
  //
  // ```markdown
  // > | | aa | bb | cc |
  //          ^-- exit
  //           ^^^^-- this cell
  // ```
  if (range[0] !== 0) {
    previousCell.end = Object.assign({}, getPoint(context.events, range[0]));
    map.add(range[0], 0, [['exit', previousCell, context]]);
  }

  // Insert enter of this cell.
  //
  // ```markdown
  // > | | aa | bb | cc |
  //           ^-- enter
  //           ^^^^-- this cell
  // ```
  const now = getPoint(context.events, range[1]);
  previousCell = {
    type: groupName,
    start: Object.assign({}, now),
    // Note: correct end is set later.
    end: Object.assign({}, now)
  };
  map.add(range[1], 0, [['enter', previousCell, context]]);

  // Insert text start at first data start and end at last data end, and
  // remove events between.
  //
  // ```markdown
  // > | | aa | bb | cc |
  //            ^-- enter
  //             ^-- exit
  //           ^^^^-- this cell
  // ```
  if (range[2] !== 0) {
    const relatedStart = getPoint(context.events, range[2]);
    const relatedEnd = getPoint(context.events, range[3]);
    /** @type {Token} */
    const valueToken = {
      type: valueName,
      start: Object.assign({}, relatedStart),
      end: Object.assign({}, relatedEnd)
    };
    map.add(range[2], 0, [['enter', valueToken, context]]);
    if (rowKind !== 2) {
      // Fix positional info on remaining events
      const start = context.events[range[2]];
      const end = context.events[range[3]];
      start[1].end = Object.assign({}, end[1].end);
      start[1].type = "chunkText";
      start[1].contentType = "text";

      // Remove if needed.
      if (range[3] > range[2] + 1) {
        const a = range[2] + 1;
        const b = range[3] - range[2] - 1;
        map.add(a, b, []);
      }
    }
    map.add(range[3] + 1, 0, [['exit', valueToken, context]]);
  }

  // Insert an exit for the last cell, if at the row end.
  //
  // ```markdown
  // > | | aa | bb | cc |
  //                    ^-- exit
  //               ^^^^^^-- this cell (the last one contains two “between” parts)
  // ```
  if (rowEnd !== undefined) {
    previousCell.end = Object.assign({}, getPoint(context.events, rowEnd));
    map.add(rowEnd, 0, [['exit', previousCell, context]]);
    previousCell = undefined;
  }
  return previousCell;
}

/**
 * Generate table end (and table body end).
 *
 * @param {Readonly<EditMap>} map
 * @param {Readonly<TokenizeContext>} context
 * @param {number} index
 * @param {Token} table
 * @param {Token | undefined} tableBody
 */
// eslint-disable-next-line max-params
function flushTableEnd(map, context, index, table, tableBody) {
  /** @type {Array<Event>} */
  const exits = [];
  const related = getPoint(context.events, index);
  if (tableBody) {
    tableBody.end = Object.assign({}, related);
    exits.push(['exit', tableBody, context]);
  }
  table.end = Object.assign({}, related);
  exits.push(['exit', table, context]);
  map.add(index + 1, 0, exits);
}

/**
 * @param {Readonly<Array<Event>>} events
 * @param {number} index
 * @returns {Readonly<Point>}
 */
function getPoint(events, index) {
  const event = events[index];
  const side = event[0] === 'enter' ? 'start' : 'end';
  return event[1][side];
}{
  "author": "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)",
  "bugs": "https://github.com/micromark/micromark-extension-gfm-table/issues",
  "contributors": [
    "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)"
  ],
  "dependencies": {
    "devlop": "^1.0.0",
    "micromark-factory-space": "^2.0.0",
    "micromark-util-character": "^2.0.0",
    "micromark-util-symbol": "^2.0.0",
    "micromark-util-types": "^2.0.0"
  },
  "description": "micromark extension to support GFM tables",
  "devDependencies": {
    "@types/node": "^22.0.0",
    "c8": "^10.0.0",
    "create-gfm-fixtures": "^2.0.0",
    "micromark": "^4.0.0",
    "micromark-build": "^2.0.0",
    "prettier": "^3.0.0",
    "remark-cli": "^12.0.0",
    "remark-preset-wooorm": "^10.0.0",
    "type-coverage": "^2.0.0",
    "typescript": "^5.0.0",
    "xo": "^0.60.0"
  },
  "exports": {
    "development": "./dev/index.js",
    "default": "./index.js"
  },
  "files": [
    "dev/",
    "index.d.ts",
    "index.js",
    "lib/"
  ],
  "funding": {
    "type": "opencollective",
    "url": "https://opencollective.com/unified"
  },
  "keywords": [
    "cell",
    "column",
    "gfm",
    "markdown",
    "micromark-extension",
    "micromark",
    "row",
    "table",
    "tabular",
    "unified"
  ],
  "license": "MIT",
  "name": "micromark-extension-gfm-table",
  "prettier": {
    "bracketSpacing": false,
    "semi": false,
    "singleQuote": true,
    "tabWidth": 2,
    "trailingComma": "none",
    "useTabs": false
  },
  "remarkConfig": {
    "plugins": [
      "remark-preset-wooorm"
    ]
  },
  "repository": "micromark/micromark-extension-gfm-table",
  "scripts": {
    "build": "tsc --build --clean && tsc --build && type-coverage && micromark-build",
    "format": "remark --frail --quiet --output -- . && prettier --log-level warn --write -- . && xo --fix",
    "test-api-dev": "node --conditions development test/index.js",
    "test-api-prod": "node --conditions production test/index.js",
    "test-api": "npm run test-api-dev && npm run test-api-prod",
    "test-coverage": "c8 --100 --reporter lcov -- npm run test-api",
    "test": "npm run build && npm run format && npm run test-coverage"
  },
  "sideEffects": false,
  "typeCoverage": {
    "atLeast": 100,
    "strict": true
  },
  "type": "module",
  "version": "2.1.1",
  "xo": {
    "overrides": [
      {
        "files": [
          "**/*.d.ts"
        ],
        "rules": {
          "@typescript-eslint/array-type": [
            "error",
            {
              "default": "generic"
            }
          ],
          "@typescript-eslint/ban-types": [
            "error",
            {
              "extendDefaults": true
            }
          ],
          "@typescript-eslint/consistent-type-definitions": [
            "error",
            "interface"
          ]
        }
      },
      {
        "files": [
          "test/**/*.js"
        ],
        "rules": {
          "no-await-in-loop": "off"
        }
      }
    ],
    "prettier": true,
    "rules": {
      "complexity": "off",
      "max-depth": "off",
      "unicorn/no-this-assignment": "off",
      "unicorn/prefer-string-replace-all": "off"
    }
  }
}
/**
 * @import {Effects, State, TokenType} from 'micromark-util-types'
 */

import { asciiControl, markdownLineEndingOrSpace, markdownLineEnding } from 'micromark-util-character';
/**
 * Parse destinations.
 *
 * ###### Examples
 *
 * ```markdown
 * <a>
 * <a\>b>
 * <a b>
 * <a)>
 * a
 * a\)b
 * a(b)c
 * a(b)
 * ```
 *
 * @param {Effects} effects
 *   Context.
 * @param {State} ok
 *   State switched to when successful.
 * @param {State} nok
 *   State switched to when unsuccessful.
 * @param {TokenType} type
 *   Type for whole (`<a>` or `b`).
 * @param {TokenType} literalType
 *   Type when enclosed (`<a>`).
 * @param {TokenType} literalMarkerType
 *   Type for enclosing (`<` and `>`).
 * @param {TokenType} rawType
 *   Type when not enclosed (`b`).
 * @param {TokenType} stringType
 *   Type for the value (`a` or `b`).
 * @param {number | undefined} [max=Infinity]
 *   Depth of nested parens (inclusive).
 * @returns {State}
 *   Start state.
 */
export function factoryDestination(effects, ok, nok, type, literalType, literalMarkerType, rawType, stringType, max) {
  const limit = max || Number.POSITIVE_INFINITY;
  let balance = 0;
  return start;

  /**
   * Start of destination.
   *
   * ```markdown
   * > | <aa>
   *     ^
   * > | aa
   *     ^
   * ```
   *
   * @type {State}
   */
  function start(code) {
    if (code === 60) {
      effects.enter(type);
      effects.enter(literalType);
      effects.enter(literalMarkerType);
      effects.consume(code);
      effects.exit(literalMarkerType);
      return enclosedBefore;
    }

    // ASCII control, space, closing paren.
    if (code === null || code === 32 || code === 41 || asciiControl(code)) {
      return nok(code);
    }
    effects.enter(type);
    effects.enter(rawType);
    effects.enter(stringType);
    effects.enter("chunkString", {
      contentType: "string"
    });
    return raw(code);
  }

  /**
   * After `<`, at an enclosed destination.
   *
   * ```markdown
   * > | <aa>
   *      ^
   * ```
   *
   * @type {State}
   */
  function enclosedBefore(code) {
    if (code === 62) {
      effects.enter(literalMarkerType);
      effects.consume(code);
      effects.exit(literalMarkerType);
      effects.exit(literalType);
      effects.exit(type);
      return ok;
    }
    effects.enter(stringType);
    effects.enter("chunkString", {
      contentType: "string"
    });
    return enclosed(code);
  }

  /**
   * In enclosed destination.
   *
   * ```markdown
   * > | <aa>
   *      ^
   * ```
   *
   * @type {State}
   */
  function enclosed(code) {
    if (code === 62) {
      effects.exit("chunkString");
      effects.exit(stringType);
      return enclosedBefore(code);
    }
    if (code === null || code === 60 || markdownLineEnding(code)) {
      return nok(code);
    }
    effects.consume(code);
    return code === 92 ? enclosedEscape : enclosed;
  }

  /**
   * After `\`, at a special character.
   *
   * ```markdown
   * > | <a\*a>
   *        ^
   * ```
   *
   * @type {State}
   */
  function enclosedEscape(code) {
    if (code === 60 || code === 62 || code === 92) {
      effects.consume(code);
      return enclosed;
    }
    return enclosed(code);
  }

  /**
   * In raw destination.
   *
   * ```markdown
   * > | aa
   *     ^
   * ```
   *
   * @type {State}
   */
  function raw(code) {
    if (!balance && (code === null || code === 41 || markdownLineEndingOrSpace(code))) {
      effects.exit("chunkString");
      effects.exit(stringType);
      effects.exit(rawType);
      effects.exit(type);
      return ok(code);
    }
    if (balance < limit && code === 40) {
      effects.consume(code);
      balance++;
      return raw;
    }
    if (code === 41) {
      effects.consume(code);
      balance--;
      return raw;
    }

    // ASCII control (but *not* `\0`) and space and `(`.
    // Note: in `markdown-rs`, `\0` exists in codes, in `micromark-js` it
    // doesn’t.
    if (code === null || code === 32 || code === 40 || asciiControl(code)) {
      return nok(code);
    }
    effects.consume(code);
    return code === 92 ? rawEscape : raw;
  }

  /**
   * After `\`, at special character.
   *
   * ```markdown
   * > | a\*a
   *       ^
   * ```
   *
   * @type {State}
   */
  function rawEscape(code) {
    if (code === 40 || code === 41 || code === 92) {
      effects.consume(code);
      return raw;
    }
    return raw(code);
  }
}{
  "name": "micromark-factory-destination",
  "version": "2.0.1",
  "description": "micromark factory to parse destinations (found in resources, definitions)",
  "license": "MIT",
  "keywords": [
    "micromark",
    "factory",
    "destination"
  ],
  "repository": "https://github.com/micromark/micromark/tree/main/packages/micromark-factory-destination",
  "bugs": "https://github.com/micromark/micromark/issues",
  "funding": [
    {
      "type": "GitHub Sponsors",
      "url": "https://github.com/sponsors/unifiedjs"
    },
    {
      "type": "OpenCollective",
      "url": "https://opencollective.com/unified"
    }
  ],
  "author": "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)",
  "contributors": [
    "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)"
  ],
  "sideEffects": false,
  "type": "module",
  "files": [
    "dev/",
    "index.d.ts.map",
    "index.d.ts",
    "index.js"
  ],
  "exports": {
    "development": "./dev/index.js",
    "default": "./index.js"
  },
  "dependencies": {
    "micromark-util-character": "^2.0.0",
    "micromark-util-symbol": "^2.0.0",
    "micromark-util-types": "^2.0.0"
  },
  "scripts": {
    "build": "micromark-build"
  },
  "xo": {
    "envs": [
      "shared-node-browser"
    ],
    "prettier": true,
    "rules": {
      "max-params": "off",
      "unicorn/prefer-code-point": "off"
    }
  }
}
/**
 * @import {
 *   Effects,
 *   State,
 *   TokenizeContext,
 *   TokenType
 * } from 'micromark-util-types'
 */

import { markdownLineEnding, markdownSpace } from 'micromark-util-character';
/**
 * Parse labels.
 *
 * > 👉 **Note**: labels in markdown are capped at 999 characters in the string.
 *
 * ###### Examples
 *
 * ```markdown
 * [a]
 * [a
 * b]
 * [a\]b]
 * ```
 *
 * @this {TokenizeContext}
 *   Tokenize context.
 * @param {Effects} effects
 *   Context.
 * @param {State} ok
 *   State switched to when successful.
 * @param {State} nok
 *   State switched to when unsuccessful.
 * @param {TokenType} type
 *   Type of the whole label (`[a]`).
 * @param {TokenType} markerType
 *   Type for the markers (`[` and `]`).
 * @param {TokenType} stringType
 *   Type for the identifier (`a`).
 * @returns {State}
 *   Start state.
 */
export function factoryLabel(effects, ok, nok, type, markerType, stringType) {
  const self = this;
  let size = 0;
  /** @type {boolean} */
  let seen;
  return start;

  /**
   * Start of label.
   *
   * ```markdown
   * > | [a]
   *     ^
   * ```
   *
   * @type {State}
   */
  function start(code) {
    effects.enter(type);
    effects.enter(markerType);
    effects.consume(code);
    effects.exit(markerType);
    effects.enter(stringType);
    return atBreak;
  }

  /**
   * In label, at something, before something else.
   *
   * ```markdown
   * > | [a]
   *      ^
   * ```
   *
   * @type {State}
   */
  function atBreak(code) {
    if (size > 999 || code === null || code === 91 || code === 93 && !seen ||
    // To do: remove in the future once we’ve switched from
    // `micromark-extension-footnote` to `micromark-extension-gfm-footnote`,
    // which doesn’t need this.
    // Hidden footnotes hook.
    /* c8 ignore next 3 */
    code === 94 && !size && '_hiddenFootnoteSupport' in self.parser.constructs) {
      return nok(code);
    }
    if (code === 93) {
      effects.exit(stringType);
      effects.enter(markerType);
      effects.consume(code);
      effects.exit(markerType);
      effects.exit(type);
      return ok;
    }

    // To do: indent? Link chunks and EOLs together?
    if (markdownLineEnding(code)) {
      effects.enter("lineEnding");
      effects.consume(code);
      effects.exit("lineEnding");
      return atBreak;
    }
    effects.enter("chunkString", {
      contentType: "string"
    });
    return labelInside(code);
  }

  /**
   * In label, in text.
   *
   * ```markdown
   * > | [a]
   *      ^
   * ```
   *
   * @type {State}
   */
  function labelInside(code) {
    if (code === null || code === 91 || code === 93 || markdownLineEnding(code) || size++ > 999) {
      effects.exit("chunkString");
      return atBreak(code);
    }
    effects.consume(code);
    if (!seen) seen = !markdownSpace(code);
    return code === 92 ? labelEscape : labelInside;
  }

  /**
   * After `\`, at a special character.
   *
   * ```markdown
   * > | [a\*a]
   *        ^
   * ```
   *
   * @type {State}
   */
  function labelEscape(code) {
    if (code === 91 || code === 92 || code === 93) {
      effects.consume(code);
      size++;
      return labelInside;
    }
    return labelInside(code);
  }
}{
  "name": "micromark-factory-label",
  "version": "2.0.1",
  "description": "micromark factory to parse labels (found in media, definitions)",
  "license": "MIT",
  "keywords": [
    "micromark",
    "factory",
    "label"
  ],
  "repository": "https://github.com/micromark/micromark/tree/main/packages/micromark-factory-label",
  "bugs": "https://github.com/micromark/micromark/issues",
  "funding": [
    {
      "type": "GitHub Sponsors",
      "url": "https://github.com/sponsors/unifiedjs"
    },
    {
      "type": "OpenCollective",
      "url": "https://opencollective.com/unified"
    }
  ],
  "author": "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)",
  "contributors": [
    "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)"
  ],
  "sideEffects": false,
  "type": "module",
  "files": [
    "dev/",
    "index.d.ts.map",
    "index.d.ts",
    "index.js"
  ],
  "exports": {
    "development": "./dev/index.js",
    "default": "./index.js"
  },
  "dependencies": {
    "devlop": "^1.0.0",
    "micromark-util-character": "^2.0.0",
    "micromark-util-symbol": "^2.0.0",
    "micromark-util-types": "^2.0.0"
  },
  "scripts": {
    "build": "micromark-build"
  },
  "xo": {
    "envs": [
      "shared-node-browser"
    ],
    "prettier": true,
    "rules": {
      "logical-assignment-operators": "off",
      "max-params": "off",
      "unicorn/no-this-assignment": "off",
      "unicorn/prefer-code-point": "off"
    }
  }
}
/**
 * @import {Effects, State, TokenType} from 'micromark-util-types'
 */

import { markdownSpace } from 'micromark-util-character';

// To do: implement `spaceOrTab`, `spaceOrTabMinMax`, `spaceOrTabWithOptions`.

/**
 * Parse spaces and tabs.
 *
 * There is no `nok` parameter:
 *
 * *   spaces in markdown are often optional, in which case this factory can be
 *     used and `ok` will be switched to whether spaces were found or not
 * *   one line ending or space can be detected with `markdownSpace(code)` right
 *     before using `factorySpace`
 *
 * ###### Examples
 *
 * Where `␉` represents a tab (plus how much it expands) and `␠` represents a
 * single space.
 *
 * ```markdown
 * ␉
 * ␠␠␠␠
 * ␉␠
 * ```
 *
 * @param {Effects} effects
 *   Context.
 * @param {State} ok
 *   State switched to when successful.
 * @param {TokenType} type
 *   Type (`' \t'`).
 * @param {number | undefined} [max=Infinity]
 *   Max (exclusive).
 * @returns {State}
 *   Start state.
 */
export function factorySpace(effects, ok, type, max) {
  const limit = max ? max - 1 : Number.POSITIVE_INFINITY;
  let size = 0;
  return start;

  /** @type {State} */
  function start(code) {
    if (markdownSpace(code)) {
      effects.enter(type);
      return prefix(code);
    }
    return ok(code);
  }

  /** @type {State} */
  function prefix(code) {
    if (markdownSpace(code) && size++ < limit) {
      effects.consume(code);
      return prefix;
    }
    effects.exit(type);
    return ok(code);
  }
}{
  "name": "micromark-factory-space",
  "version": "2.0.1",
  "description": "micromark factory to parse markdown space (found in lots of places)",
  "license": "MIT",
  "keywords": [
    "micromark",
    "factory",
    "space"
  ],
  "repository": "https://github.com/micromark/micromark/tree/main/packages/micromark-factory-space",
  "bugs": "https://github.com/micromark/micromark/issues",
  "funding": [
    {
      "type": "GitHub Sponsors",
      "url": "https://github.com/sponsors/unifiedjs"
    },
    {
      "type": "OpenCollective",
      "url": "https://opencollective.com/unified"
    }
  ],
  "author": "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)",
  "contributors": [
    "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)"
  ],
  "sideEffects": false,
  "type": "module",
  "files": [
    "dev/",
    "index.d.ts.map",
    "index.d.ts",
    "index.js"
  ],
  "exports": {
    "development": "./dev/index.js",
    "default": "./index.js"
  },
  "dependencies": {
    "micromark-util-character": "^2.0.0",
    "micromark-util-types": "^2.0.0"
  },
  "scripts": {
    "build": "micromark-build"
  },
  "xo": {
    "envs": [
      "shared-node-browser"
    ],
    "prettier": true,
    "rules": {
      "unicorn/prefer-code-point": "off"
    }
  }
}
/**
 * @import {
 *   Code,
 *   Effects,
 *   State,
 *   TokenType
 * } from 'micromark-util-types'
 */

import { factorySpace } from 'micromark-factory-space';
import { markdownLineEnding } from 'micromark-util-character';
/**
 * Parse titles.
 *
 * ###### Examples
 *
 * ```markdown
 * "a"
 * 'b'
 * (c)
 * "a
 * b"
 * 'a
 *     b'
 * (a\)b)
 * ```
 *
 * @param {Effects} effects
 *   Context.
 * @param {State} ok
 *   State switched to when successful.
 * @param {State} nok
 *   State switched to when unsuccessful.
 * @param {TokenType} type
 *   Type of the whole title (`"a"`, `'b'`, `(c)`).
 * @param {TokenType} markerType
 *   Type for the markers (`"`, `'`, `(`, and `)`).
 * @param {TokenType} stringType
 *   Type for the value (`a`).
 * @returns {State}
 *   Start state.
 */
export function factoryTitle(effects, ok, nok, type, markerType, stringType) {
  /** @type {NonNullable<Code>} */
  let marker;
  return start;

  /**
   * Start of title.
   *
   * ```markdown
   * > | "a"
   *     ^
   * ```
   *
   * @type {State}
   */
  function start(code) {
    if (code === 34 || code === 39 || code === 40) {
      effects.enter(type);
      effects.enter(markerType);
      effects.consume(code);
      effects.exit(markerType);
      marker = code === 40 ? 41 : code;
      return begin;
    }
    return nok(code);
  }

  /**
   * After opening marker.
   *
   * This is also used at the closing marker.
   *
   * ```markdown
   * > | "a"
   *      ^
   * ```
   *
   * @type {State}
   */
  function begin(code) {
    if (code === marker) {
      effects.enter(markerType);
      effects.consume(code);
      effects.exit(markerType);
      effects.exit(type);
      return ok;
    }
    effects.enter(stringType);
    return atBreak(code);
  }

  /**
   * At something, before something else.
   *
   * ```markdown
   * > | "a"
   *      ^
   * ```
   *
   * @type {State}
   */
  function atBreak(code) {
    if (code === marker) {
      effects.exit(stringType);
      return begin(marker);
    }
    if (code === null) {
      return nok(code);
    }

    // Note: blank lines can’t exist in content.
    if (markdownLineEnding(code)) {
      // To do: use `space_or_tab_eol_with_options`, connect.
      effects.enter("lineEnding");
      effects.consume(code);
      effects.exit("lineEnding");
      return factorySpace(effects, atBreak, "linePrefix");
    }
    effects.enter("chunkString", {
      contentType: "string"
    });
    return inside(code);
  }

  /**
   *
   *
   * @type {State}
   */
  function inside(code) {
    if (code === marker || code === null || markdownLineEnding(code)) {
      effects.exit("chunkString");
      return atBreak(code);
    }
    effects.consume(code);
    return code === 92 ? escape : inside;
  }

  /**
   * After `\`, at a special character.
   *
   * ```markdown
   * > | "a\*b"
   *      ^
   * ```
   *
   * @type {State}
   */
  function escape(code) {
    if (code === marker || code === 92) {
      effects.consume(code);
      return inside;
    }
    return inside(code);
  }
}{
  "name": "micromark-factory-title",
  "version": "2.0.1",
  "description": "micromark factory to parse markdown titles (found in resources, definitions)",
  "license": "MIT",
  "keywords": [
    "micromark",
    "factory",
    "title"
  ],
  "repository": "https://github.com/micromark/micromark/tree/main/packages/micromark-factory-title",
  "bugs": "https://github.com/micromark/micromark/issues",
  "funding": [
    {
      "type": "GitHub Sponsors",
      "url": "https://github.com/sponsors/unifiedjs"
    },
    {
      "type": "OpenCollective",
      "url": "https://opencollective.com/unified"
    }
  ],
  "author": "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)",
  "contributors": [
    "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)"
  ],
  "sideEffects": false,
  "type": "module",
  "files": [
    "dev/",
    "index.d.ts.map",
    "index.d.ts",
    "index.js"
  ],
  "exports": {
    "development": "./dev/index.js",
    "default": "./index.js"
  },
  "dependencies": {
    "micromark-factory-space": "^2.0.0",
    "micromark-util-character": "^2.0.0",
    "micromark-util-symbol": "^2.0.0",
    "micromark-util-types": "^2.0.0"
  },
  "scripts": {
    "build": "micromark-build"
  },
  "xo": {
    "envs": [
      "shared-node-browser"
    ],
    "prettier": true,
    "rules": {
      "max-params": "off",
      "unicorn/prefer-code-point": "off"
    }
  }
}
/**
 * @import {Effects, State} from 'micromark-util-types'
 */

import { factorySpace } from 'micromark-factory-space';
import { markdownLineEnding, markdownSpace } from 'micromark-util-character';
/**
 * Parse spaces and tabs.
 *
 * There is no `nok` parameter:
 *
 * *   line endings or spaces in markdown are often optional, in which case this
 *     factory can be used and `ok` will be switched to whether spaces were found
 *     or not
 * *   one line ending or space can be detected with
 *     `markdownLineEndingOrSpace(code)` right before using `factoryWhitespace`
 *
 * @param {Effects} effects
 *   Context.
 * @param {State} ok
 *   State switched to when successful.
 * @returns {State}
 *   Start state.
 */
export function factoryWhitespace(effects, ok) {
  /** @type {boolean} */
  let seen;
  return start;

  /** @type {State} */
  function start(code) {
    if (markdownLineEnding(code)) {
      effects.enter("lineEnding");
      effects.consume(code);
      effects.exit("lineEnding");
      seen = true;
      return start;
    }
    if (markdownSpace(code)) {
      return factorySpace(effects, start, seen ? "linePrefix" : "lineSuffix")(code);
    }
    return ok(code);
  }
}{
  "name": "micromark-factory-whitespace",
  "version": "2.0.1",
  "description": "micromark factory to parse markdown whitespace (found in lots of places)",
  "license": "MIT",
  "keywords": [
    "micromark",
    "factory",
    "whitespace"
  ],
  "repository": "https://github.com/micromark/micromark/tree/main/packages/micromark-factory-whitespace",
  "bugs": "https://github.com/micromark/micromark/issues",
  "funding": [
    {
      "type": "GitHub Sponsors",
      "url": "https://github.com/sponsors/unifiedjs"
    },
    {
      "type": "OpenCollective",
      "url": "https://opencollective.com/unified"
    }
  ],
  "author": "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)",
  "contributors": [
    "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)"
  ],
  "sideEffects": false,
  "type": "module",
  "files": [
    "dev/",
    "index.d.ts.map",
    "index.d.ts",
    "index.js"
  ],
  "exports": {
    "development": "./dev/index.js",
    "default": "./index.js"
  },
  "dependencies": {
    "micromark-factory-space": "^2.0.0",
    "micromark-util-character": "^2.0.0",
    "micromark-util-symbol": "^2.0.0",
    "micromark-util-types": "^2.0.0"
  },
  "scripts": {
    "build": "micromark-build"
  },
  "xo": {
    "envs": [
      "shared-node-browser"
    ],
    "prettier": true,
    "rules": {
      "unicorn/prefer-code-point": "off"
    }
  }
}
/**
 * @import {Code} from 'micromark-util-types'
 */

/**
 * Check whether the character code represents an ASCII alpha (`a` through `z`,
 * case insensitive).
 *
 * An **ASCII alpha** is an ASCII upper alpha or ASCII lower alpha.
 *
 * An **ASCII upper alpha** is a character in the inclusive range U+0041 (`A`)
 * to U+005A (`Z`).
 *
 * An **ASCII lower alpha** is a character in the inclusive range U+0061 (`a`)
 * to U+007A (`z`).
 *
 * @param code
 *   Code.
 * @returns {boolean}
 *   Whether it matches.
 */
export const asciiAlpha = regexCheck(/[A-Za-z]/);

/**
 * Check whether the character code represents an ASCII alphanumeric (`a`
 * through `z`, case insensitive, or `0` through `9`).
 *
 * An **ASCII alphanumeric** is an ASCII digit (see `asciiDigit`) or ASCII alpha
 * (see `asciiAlpha`).
 *
 * @param code
 *   Code.
 * @returns {boolean}
 *   Whether it matches.
 */
export const asciiAlphanumeric = regexCheck(/[\dA-Za-z]/);

/**
 * Check whether the character code represents an ASCII atext.
 *
 * atext is an ASCII alphanumeric (see `asciiAlphanumeric`), or a character in
 * the inclusive ranges U+0023 NUMBER SIGN (`#`) to U+0027 APOSTROPHE (`'`),
 * U+002A ASTERISK (`*`), U+002B PLUS SIGN (`+`), U+002D DASH (`-`), U+002F
 * SLASH (`/`), U+003D EQUALS TO (`=`), U+003F QUESTION MARK (`?`), U+005E
 * CARET (`^`) to U+0060 GRAVE ACCENT (`` ` ``), or U+007B LEFT CURLY BRACE
 * (`{`) to U+007E TILDE (`~`).
 *
 * See:
 * **\[RFC5322]**:
 * [Internet Message Format](https://tools.ietf.org/html/rfc5322).
 * P. Resnick.
 * IETF.
 *
 * @param code
 *   Code.
 * @returns {boolean}
 *   Whether it matches.
 */
export const asciiAtext = regexCheck(/[#-'*+\--9=?A-Z^-~]/);

/**
 * Check whether a character code is an ASCII control character.
 *
 * An **ASCII control** is a character in the inclusive range U+0000 NULL (NUL)
 * to U+001F (US), or U+007F (DEL).
 *
 * @param {Code} code
 *   Code.
 * @returns {boolean}
 *   Whether it matches.
 */
export function asciiControl(code) {
  return (
    // Special whitespace codes (which have negative values), C0 and Control
    // character DEL
    code !== null && (code < 32 || code === 127)
  );
}

/**
 * Check whether the character code represents an ASCII digit (`0` through `9`).
 *
 * An **ASCII digit** is a character in the inclusive range U+0030 (`0`) to
 * U+0039 (`9`).
 *
 * @param code
 *   Code.
 * @returns {boolean}
 *   Whether it matches.
 */
export const asciiDigit = regexCheck(/\d/);

/**
 * Check whether the character code represents an ASCII hex digit (`a` through
 * `f`, case insensitive, or `0` through `9`).
 *
 * An **ASCII hex digit** is an ASCII digit (see `asciiDigit`), ASCII upper hex
 * digit, or an ASCII lower hex digit.
 *
 * An **ASCII upper hex digit** is a character in the inclusive range U+0041
 * (`A`) to U+0046 (`F`).
 *
 * An **ASCII lower hex digit** is a character in the inclusive range U+0061
 * (`a`) to U+0066 (`f`).
 *
 * @param code
 *   Code.
 * @returns {boolean}
 *   Whether it matches.
 */
export const asciiHexDigit = regexCheck(/[\dA-Fa-f]/);

/**
 * Check whether the character code represents ASCII punctuation.
 *
 * An **ASCII punctuation** is a character in the inclusive ranges U+0021
 * EXCLAMATION MARK (`!`) to U+002F SLASH (`/`), U+003A COLON (`:`) to U+0040 AT
 * SIGN (`@`), U+005B LEFT SQUARE BRACKET (`[`) to U+0060 GRAVE ACCENT
 * (`` ` ``), or U+007B LEFT CURLY BRACE (`{`) to U+007E TILDE (`~`).
 *
 * @param code
 *   Code.
 * @returns {boolean}
 *   Whether it matches.
 */
export const asciiPunctuation = regexCheck(/[!-/:-@[-`{-~]/);

/**
 * Check whether a character code is a markdown line ending.
 *
 * A **markdown line ending** is the virtual characters M-0003 CARRIAGE RETURN
 * LINE FEED (CRLF), M-0004 LINE FEED (LF) and M-0005 CARRIAGE RETURN (CR).
 *
 * In micromark, the actual character U+000A LINE FEED (LF) and U+000D CARRIAGE
 * RETURN (CR) are replaced by these virtual characters depending on whether
 * they occurred together.
 *
 * @param {Code} code
 *   Code.
 * @returns {boolean}
 *   Whether it matches.
 */
export function markdownLineEnding(code) {
  return code !== null && code < -2;
}

/**
 * Check whether a character code is a markdown line ending (see
 * `markdownLineEnding`) or markdown space (see `markdownSpace`).
 *
 * @param {Code} code
 *   Code.
 * @returns {boolean}
 *   Whether it matches.
 */
export function markdownLineEndingOrSpace(code) {
  return code !== null && (code < 0 || code === 32);
}

/**
 * Check whether a character code is a markdown space.
 *
 * A **markdown space** is the concrete character U+0020 SPACE (SP) and the
 * virtual characters M-0001 VIRTUAL SPACE (VS) and M-0002 HORIZONTAL TAB (HT).
 *
 * In micromark, the actual character U+0009 CHARACTER TABULATION (HT) is
 * replaced by one M-0002 HORIZONTAL TAB (HT) and between 0 and 3 M-0001 VIRTUAL
 * SPACE (VS) characters, depending on the column at which the tab occurred.
 *
 * @param {Code} code
 *   Code.
 * @returns {boolean}
 *   Whether it matches.
 */
export function markdownSpace(code) {
  return code === -2 || code === -1 || code === 32;
}

// Size note: removing ASCII from the regex and using `asciiPunctuation` here
// In fact adds to the bundle size.
/**
 * Check whether the character code represents Unicode punctuation.
 *
 * A **Unicode punctuation** is a character in the Unicode `Pc` (Punctuation,
 * Connector), `Pd` (Punctuation, Dash), `Pe` (Punctuation, Close), `Pf`
 * (Punctuation, Final quote), `Pi` (Punctuation, Initial quote), `Po`
 * (Punctuation, Other), or `Ps` (Punctuation, Open) categories, or an ASCII
 * punctuation (see `asciiPunctuation`).
 *
 * See:
 * **\[UNICODE]**:
 * [The Unicode Standard](https://www.unicode.org/versions/).
 * Unicode Consortium.
 *
 * @param code
 *   Code.
 * @returns
 *   Whether it matches.
 */
export const unicodePunctuation = regexCheck(/\p{P}|\p{S}/u);

/**
 * Check whether the character code represents Unicode whitespace.
 *
 * Note that this does handle micromark specific markdown whitespace characters.
 * See `markdownLineEndingOrSpace` to check that.
 *
 * A **Unicode whitespace** is a character in the Unicode `Zs` (Separator,
 * Space) category, or U+0009 CHARACTER TABULATION (HT), U+000A LINE FEED (LF),
 * U+000C (FF), or U+000D CARRIAGE RETURN (CR) (**\[UNICODE]**).
 *
 * See:
 * **\[UNICODE]**:
 * [The Unicode Standard](https://www.unicode.org/versions/).
 * Unicode Consortium.
 *
 * @param code
 *   Code.
 * @returns
 *   Whether it matches.
 */
export const unicodeWhitespace = regexCheck(/\s/);

/**
 * Create a code check from a regex.
 *
 * @param {RegExp} regex
 *   Expression.
 * @returns {(code: Code) => boolean}
 *   Check.
 */
function regexCheck(regex) {
  return check;

  /**
   * Check whether a code matches the bound regex.
   *
   * @param {Code} code
   *   Character code.
   * @returns {boolean}
   *   Whether the character code matches the bound regex.
   */
  function check(code) {
    return code !== null && code > -1 && regex.test(String.fromCharCode(code));
  }
}{
  "name": "micromark-util-character",
  "version": "2.1.1",
  "description": "micromark utility to handle character codes",
  "license": "MIT",
  "keywords": [
    "micromark",
    "util",
    "utility",
    "character"
  ],
  "repository": "https://github.com/micromark/micromark/tree/main/packages/micromark-util-character",
  "bugs": "https://github.com/micromark/micromark/issues",
  "funding": [
    {
      "type": "GitHub Sponsors",
      "url": "https://github.com/sponsors/unifiedjs"
    },
    {
      "type": "OpenCollective",
      "url": "https://opencollective.com/unified"
    }
  ],
  "author": "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)",
  "contributors": [
    "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)"
  ],
  "sideEffects": false,
  "type": "module",
  "files": [
    "dev/",
    "lib/",
    "index.d.ts.map",
    "index.d.ts",
    "index.js"
  ],
  "exports": {
    "development": "./dev/index.js",
    "default": "./index.js"
  },
  "dependencies": {
    "micromark-util-symbol": "^2.0.0",
    "micromark-util-types": "^2.0.0"
  },
  "scripts": {
    "build": "micromark-build"
  },
  "xo": {
    "envs": [
      "shared-node-browser"
    ],
    "prettier": true,
    "rules": {
      "unicorn/prefer-code-point": "off"
    }
  }
}
/**
 * Like `Array#splice`, but smarter for giant arrays.
 *
 * `Array#splice` takes all items to be inserted as individual argument which
 * causes a stack overflow in V8 when trying to insert 100k items for instance.
 *
 * Otherwise, this does not return the removed items, and takes `items` as an
 * array instead of rest parameters.
 *
 * @template {unknown} T
 *   Item type.
 * @param {Array<T>} list
 *   List to operate on.
 * @param {number} start
 *   Index to remove/insert at (can be negative).
 * @param {number} remove
 *   Number of items to remove.
 * @param {Array<T>} items
 *   Items to inject into `list`.
 * @returns {undefined}
 *   Nothing.
 */
export function splice(list, start, remove, items) {
  const end = list.length;
  let chunkStart = 0;
  /** @type {Array<unknown>} */
  let parameters;

  // Make start between zero and `end` (included).
  if (start < 0) {
    start = -start > end ? 0 : end + start;
  } else {
    start = start > end ? end : start;
  }
  remove = remove > 0 ? remove : 0;

  // No need to chunk the items if there’s only a couple (10k) items.
  if (items.length < 10000) {
    parameters = Array.from(items);
    parameters.unshift(start, remove);
    // @ts-expect-error Hush, it’s fine.
    list.splice(...parameters);
  } else {
    // Delete `remove` items starting from `start`
    if (remove) list.splice(start, remove);

    // Insert the items in chunks to not cause stack overflows.
    while (chunkStart < items.length) {
      parameters = items.slice(chunkStart, chunkStart + 10000);
      parameters.unshift(start, 0);
      // @ts-expect-error Hush, it’s fine.
      list.splice(...parameters);
      chunkStart += 10000;
      start += 10000;
    }
  }
}

/**
 * Append `items` (an array) at the end of `list` (another array).
 * When `list` was empty, returns `items` instead.
 *
 * This prevents a potentially expensive operation when `list` is empty,
 * and adds items in batches to prevent V8 from hanging.
 *
 * @template {unknown} T
 *   Item type.
 * @param {Array<T>} list
 *   List to operate on.
 * @param {Array<T>} items
 *   Items to add to `list`.
 * @returns {Array<T>}
 *   Either `list` or `items`.
 */
export function push(list, items) {
  if (list.length > 0) {
    splice(list, list.length, 0, items);
    return list;
  }
  return items;
}{
  "name": "micromark-util-chunked",
  "version": "2.0.1",
  "description": "micromark utility to splice and push with giant arrays",
  "license": "MIT",
  "keywords": [
    "micromark",
    "util",
    "utility",
    "chunk",
    "splice",
    "push"
  ],
  "repository": "https://github.com/micromark/micromark/tree/main/packages/micromark-util-chunked",
  "bugs": "https://github.com/micromark/micromark/issues",
  "funding": [
    {
      "type": "GitHub Sponsors",
      "url": "https://github.com/sponsors/unifiedjs"
    },
    {
      "type": "OpenCollective",
      "url": "https://opencollective.com/unified"
    }
  ],
  "author": "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)",
  "contributors": [
    "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)"
  ],
  "sideEffects": false,
  "type": "module",
  "files": [
    "dev/",
    "index.d.ts.map",
    "index.d.ts",
    "index.js"
  ],
  "exports": {
    "development": "./dev/index.js",
    "default": "./index.js"
  },
  "dependencies": {
    "micromark-util-symbol": "^2.0.0"
  },
  "scripts": {
    "build": "micromark-build"
  },
  "xo": {
    "envs": [
      "shared-node-browser"
    ],
    "prettier": true,
    "rules": {
      "unicorn/prefer-code-point": "off"
    }
  }
}
/**
 * @import {Code} from 'micromark-util-types'
 */

import { markdownLineEndingOrSpace, unicodePunctuation, unicodeWhitespace } from 'micromark-util-character';
/**
 * Classify whether a code represents whitespace, punctuation, or something
 * else.
 *
 * Used for attention (emphasis, strong), whose sequences can open or close
 * based on the class of surrounding characters.
 *
 * > 👉 **Note**: eof (`null`) is seen as whitespace.
 *
 * @param {Code} code
 *   Code.
 * @returns {typeof constants.characterGroupWhitespace | typeof constants.characterGroupPunctuation | undefined}
 *   Group.
 */
export function classifyCharacter(code) {
  if (code === null || markdownLineEndingOrSpace(code) || unicodeWhitespace(code)) {
    return 1;
  }
  if (unicodePunctuation(code)) {
    return 2;
  }
}{
  "name": "micromark-util-classify-character",
  "version": "2.0.1",
  "description": "micromark utility to classify whether a character is whitespace or punctuation",
  "license": "MIT",
  "keywords": [
    "micromark",
    "util",
    "utility",
    "attention",
    "classify",
    "character"
  ],
  "repository": "https://github.com/micromark/micromark/tree/main/packages/micromark-util-classify-character",
  "bugs": "https://github.com/micromark/micromark/issues",
  "funding": [
    {
      "type": "GitHub Sponsors",
      "url": "https://github.com/sponsors/unifiedjs"
    },
    {
      "type": "OpenCollective",
      "url": "https://opencollective.com/unified"
    }
  ],
  "author": "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)",
  "contributors": [
    "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)"
  ],
  "sideEffects": false,
  "type": "module",
  "files": [
    "dev/",
    "index.d.ts.map",
    "index.d.ts",
    "index.js"
  ],
  "exports": {
    "development": "./dev/index.js",
    "default": "./index.js"
  },
  "dependencies": {
    "micromark-util-character": "^2.0.0",
    "micromark-util-symbol": "^2.0.0",
    "micromark-util-types": "^2.0.0"
  },
  "scripts": {
    "build": "micromark-build"
  },
  "xo": {
    "envs": [
      "shared-node-browser"
    ],
    "prettier": true,
    "rules": {
      "unicorn/prefer-code-point": "off"
    }
  }
}
/**
 * @import {
 *   Extension,
 *   Handles,
 *   HtmlExtension,
 *   NormalizedExtension
 * } from 'micromark-util-types'
 */

import {splice} from 'micromark-util-chunked'

const hasOwnProperty = {}.hasOwnProperty

/**
 * Combine multiple syntax extensions into one.
 *
 * @param {ReadonlyArray<Extension>} extensions
 *   List of syntax extensions.
 * @returns {NormalizedExtension}
 *   A single combined extension.
 */
export function combineExtensions(extensions) {
  /** @type {NormalizedExtension} */
  const all = {}
  let index = -1

  while (++index < extensions.length) {
    syntaxExtension(all, extensions[index])
  }

  return all
}

/**
 * Merge `extension` into `all`.
 *
 * @param {NormalizedExtension} all
 *   Extension to merge into.
 * @param {Extension} extension
 *   Extension to merge.
 * @returns {undefined}
 *   Nothing.
 */
function syntaxExtension(all, extension) {
  /** @type {keyof Extension} */
  let hook

  for (hook in extension) {
    const maybe = hasOwnProperty.call(all, hook) ? all[hook] : undefined
    /** @type {Record<string, unknown>} */
    const left = maybe || (all[hook] = {})
    /** @type {Record<string, unknown> | undefined} */
    const right = extension[hook]
    /** @type {string} */
    let code

    if (right) {
      for (code in right) {
        if (!hasOwnProperty.call(left, code)) left[code] = []
        const value = right[code]
        constructs(
          // @ts-expect-error Looks like a list.
          left[code],
          Array.isArray(value) ? value : value ? [value] : []
        )
      }
    }
  }
}

/**
 * Merge `list` into `existing` (both lists of constructs).
 * Mutates `existing`.
 *
 * @param {Array<unknown>} existing
 *   List of constructs to merge into.
 * @param {Array<unknown>} list
 *   List of constructs to merge.
 * @returns {undefined}
 *   Nothing.
 */
function constructs(existing, list) {
  let index = -1
  /** @type {Array<unknown>} */
  const before = []

  while (++index < list.length) {
    // @ts-expect-error Looks like an object.
    ;(list[index].add === 'after' ? existing : before).push(list[index])
  }

  splice(existing, 0, 0, before)
}

/**
 * Combine multiple HTML extensions into one.
 *
 * @param {ReadonlyArray<HtmlExtension>} htmlExtensions
 *   List of HTML extensions.
 * @returns {HtmlExtension}
 *   Single combined HTML extension.
 */
export function combineHtmlExtensions(htmlExtensions) {
  /** @type {HtmlExtension} */
  const handlers = {}
  let index = -1

  while (++index < htmlExtensions.length) {
    htmlExtension(handlers, htmlExtensions[index])
  }

  return handlers
}

/**
 * Merge `extension` into `all`.
 *
 * @param {HtmlExtension} all
 *   Extension to merge into.
 * @param {HtmlExtension} extension
 *   Extension to merge.
 * @returns {undefined}
 *   Nothing.
 */
function htmlExtension(all, extension) {
  /** @type {keyof HtmlExtension} */
  let hook

  for (hook in extension) {
    const maybe = hasOwnProperty.call(all, hook) ? all[hook] : undefined
    const left = maybe || (all[hook] = {})
    const right = extension[hook]
    /** @type {keyof Handles} */
    let type

    if (right) {
      for (type in right) {
        // @ts-expect-error assume document vs regular handler are managed correctly.
        left[type] = right[type]
      }
    }
  }
}
{
  "name": "micromark-util-combine-extensions",
  "version": "2.0.1",
  "description": "micromark utility to combine syntax or html extensions",
  "license": "MIT",
  "keywords": [
    "micromark",
    "util",
    "utility",
    "extension",
    "combine",
    "merge"
  ],
  "repository": "https://github.com/micromark/micromark/tree/main/packages/micromark-util-combine-extensions",
  "bugs": "https://github.com/micromark/micromark/issues",
  "funding": [
    {
      "type": "GitHub Sponsors",
      "url": "https://github.com/sponsors/unifiedjs"
    },
    {
      "type": "OpenCollective",
      "url": "https://opencollective.com/unified"
    }
  ],
  "author": "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)",
  "contributors": [
    "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)"
  ],
  "sideEffects": false,
  "type": "module",
  "files": [
    "index.d.ts.map",
    "index.d.ts",
    "index.js"
  ],
  "exports": "./index.js",
  "dependencies": {
    "micromark-util-chunked": "^2.0.0",
    "micromark-util-types": "^2.0.0"
  },
  "xo": {
    "envs": [
      "shared-node-browser"
    ],
    "prettier": true,
    "rules": {
      "guard-for-in": "off",
      "unicorn/prefer-code-point": "off"
    }
  }
}
/**
 * Turn the number (in string form as either hexa- or plain decimal) coming from
 * a numeric character reference into a character.
 *
 * Sort of like `String.fromCodePoint(Number.parseInt(value, base))`, but makes
 * non-characters and control characters safe.
 *
 * @param {string} value
 *   Value to decode.
 * @param {number} base
 *   Numeric base.
 * @returns {string}
 *   Character.
 */
export function decodeNumericCharacterReference(value, base) {
  const code = Number.parseInt(value, base);
  if (
  // C0 except for HT, LF, FF, CR, space.
  code < 9 || code === 11 || code > 13 && code < 32 ||
  // Control character (DEL) of C0, and C1 controls.
  code > 126 && code < 160 ||
  // Lone high surrogates and low surrogates.
  code > 55_295 && code < 57_344 ||
  // Noncharacters.
  code > 64_975 && code < 65_008 || /* eslint-disable no-bitwise */
  (code & 65_535) === 65_535 || (code & 65_535) === 65_534 || /* eslint-enable no-bitwise */
  // Out of range
  code > 1_114_111) {
    return "\uFFFD";
  }
  return String.fromCodePoint(code);
}{
  "name": "micromark-util-decode-numeric-character-reference",
  "version": "2.0.2",
  "description": "micromark utility to decode numeric character references",
  "license": "MIT",
  "keywords": [
    "micromark",
    "util",
    "utility",
    "decode",
    "numeric",
    "number",
    "character",
    "reference"
  ],
  "repository": "https://github.com/micromark/micromark/tree/main/packages/micromark-util-decode-numeric-character-reference",
  "bugs": "https://github.com/micromark/micromark/issues",
  "funding": [
    {
      "type": "GitHub Sponsors",
      "url": "https://github.com/sponsors/unifiedjs"
    },
    {
      "type": "OpenCollective",
      "url": "https://opencollective.com/unified"
    }
  ],
  "author": "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)",
  "contributors": [
    "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)"
  ],
  "sideEffects": false,
  "type": "module",
  "files": [
    "dev/",
    "index.d.ts.map",
    "index.d.ts",
    "index.js"
  ],
  "exports": {
    "development": "./dev/index.js",
    "default": "./index.js"
  },
  "dependencies": {
    "micromark-util-symbol": "^2.0.0"
  },
  "scripts": {
    "build": "micromark-build"
  },
  "xo": {
    "envs": [
      "shared-node-browser"
    ],
    "prettier": true,
    "rules": {
      "unicorn/prefer-code-point": "off"
    }
  }
}
import { decodeNamedCharacterReference } from 'decode-named-character-reference';
import { decodeNumericCharacterReference } from 'micromark-util-decode-numeric-character-reference';
const characterEscapeOrReference = /\\([!-/:-@[-`{-~])|&(#(?:\d{1,7}|x[\da-f]{1,6})|[\da-z]{1,31});/gi;

/**
 * Decode markdown strings (which occur in places such as fenced code info
 * strings, destinations, labels, and titles).
 *
 * The “string” content type allows character escapes and -references.
 * This decodes those.
 *
 * @param {string} value
 *   Value to decode.
 * @returns {string}
 *   Decoded value.
 */
export function decodeString(value) {
  return value.replace(characterEscapeOrReference, decode);
}

/**
 * @param {string} $0
 *   Match.
 * @param {string} $1
 *   Character escape.
 * @param {string} $2
 *   Character reference.
 * @returns {string}
 *   Decoded value
 */
function decode($0, $1, $2) {
  if ($1) {
    // Escape.
    return $1;
  }

  // Reference.
  const head = $2.charCodeAt(0);
  if (head === 35) {
    const head = $2.charCodeAt(1);
    const hex = head === 120 || head === 88;
    return decodeNumericCharacterReference($2.slice(hex ? 2 : 1), hex ? 16 : 10);
  }
  return decodeNamedCharacterReference($2) || $0;
}{
  "name": "micromark-util-decode-string",
  "version": "2.0.1",
  "description": "micromark utility to decode markdown strings",
  "license": "MIT",
  "keywords": [
    "micromark",
    "util",
    "utility",
    "decode",
    "character",
    "reference",
    "escape",
    "string"
  ],
  "repository": "https://github.com/micromark/micromark/tree/main/packages/micromark-util-decode-string",
  "bugs": "https://github.com/micromark/micromark/issues",
  "funding": [
    {
      "type": "GitHub Sponsors",
      "url": "https://github.com/sponsors/unifiedjs"
    },
    {
      "type": "OpenCollective",
      "url": "https://opencollective.com/unified"
    }
  ],
  "author": "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)",
  "contributors": [
    "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)"
  ],
  "sideEffects": false,
  "type": "module",
  "files": [
    "dev/",
    "index.d.ts.map",
    "index.d.ts",
    "index.js"
  ],
  "exports": {
    "development": "./dev/index.js",
    "default": "./index.js"
  },
  "dependencies": {
    "micromark-util-character": "^2.0.0",
    "micromark-util-decode-numeric-character-reference": "^2.0.0",
    "micromark-util-symbol": "^2.0.0",
    "decode-named-character-reference": "^1.0.0"
  },
  "scripts": {
    "build": "micromark-build"
  },
  "xo": {
    "envs": [
      "shared-node-browser"
    ],
    "prettier": true,
    "rules": {
      "unicorn/prefer-code-point": "off",
      "unicorn/prefer-string-replace-all": "off"
    }
  }
}
const characterReferences = {'"': 'quot', '&': 'amp', '<': 'lt', '>': 'gt'}

/**
 * Encode only the dangerous HTML characters.
 *
 * This ensures that certain characters which have special meaning in HTML are
 * dealt with.
 * Technically, we can skip `>` and `"` in many cases, but CM includes them.
 *
 * @param {string} value
 *   Value to encode.
 * @returns {string}
 *   Encoded value.
 */
export function encode(value) {
  return value.replace(/["&<>]/g, replace)

  /**
   * @param {string} value
   *   Value to replace.
   * @returns {string}
   *   Encoded value.
   */
  function replace(value) {
    return (
      '&' +
      characterReferences[
        /** @type {keyof typeof characterReferences} */ (value)
      ] +
      ';'
    )
  }
}
{
  "name": "micromark-util-encode",
  "version": "2.0.1",
  "description": "micromark utility to encode dangerous html characters",
  "license": "MIT",
  "keywords": [
    "micromark",
    "util",
    "utility",
    "html",
    "encode"
  ],
  "repository": "https://github.com/micromark/micromark/tree/main/packages/micromark-util-encode",
  "bugs": "https://github.com/micromark/micromark/issues",
  "funding": [
    {
      "type": "GitHub Sponsors",
      "url": "https://github.com/sponsors/unifiedjs"
    },
    {
      "type": "OpenCollective",
      "url": "https://opencollective.com/unified"
    }
  ],
  "author": "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)",
  "contributors": [
    "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)"
  ],
  "sideEffects": false,
  "type": "module",
  "files": [
    "index.d.ts.map",
    "index.d.ts",
    "index.js"
  ],
  "exports": "./index.js",
  "xo": {
    "envs": [
      "shared-node-browser"
    ],
    "prettier": true,
    "rules": {
      "unicorn/prefer-string-replace-all": "off",
      "unicorn/prefer-code-point": "off"
    }
  }
}
/**
 * List of lowercase HTML “block” tag names.
 *
 * The list, when parsing HTML (flow), results in more relaxed rules (condition
 * 6).
 * Because they are known blocks, the HTML-like syntax doesn’t have to be
 * strictly parsed.
 * For tag names not in this list, a more strict algorithm (condition 7) is used
 * to detect whether the HTML-like syntax is seen as HTML (flow) or not.
 *
 * This is copied from:
 * <https://spec.commonmark.org/0.30/#html-blocks>.
 *
 * > 👉 **Note**: `search` was added in `CommonMark@0.31`.
 */
export const htmlBlockNames = [
  'address',
  'article',
  'aside',
  'base',
  'basefont',
  'blockquote',
  'body',
  'caption',
  'center',
  'col',
  'colgroup',
  'dd',
  'details',
  'dialog',
  'dir',
  'div',
  'dl',
  'dt',
  'fieldset',
  'figcaption',
  'figure',
  'footer',
  'form',
  'frame',
  'frameset',
  'h1',
  'h2',
  'h3',
  'h4',
  'h5',
  'h6',
  'head',
  'header',
  'hr',
  'html',
  'iframe',
  'legend',
  'li',
  'link',
  'main',
  'menu',
  'menuitem',
  'nav',
  'noframes',
  'ol',
  'optgroup',
  'option',
  'p',
  'param',
  'search',
  'section',
  'summary',
  'table',
  'tbody',
  'td',
  'tfoot',
  'th',
  'thead',
  'title',
  'tr',
  'track',
  'ul'
]

/**
 * List of lowercase HTML “raw” tag names.
 *
 * The list, when parsing HTML (flow), results in HTML that can include lines
 * without exiting, until a closing tag also in this list is found (condition
 * 1).
 *
 * This module is copied from:
 * <https://spec.commonmark.org/0.30/#html-blocks>.
 *
 * > 👉 **Note**: `textarea` was added in `CommonMark@0.30`.
 */
export const htmlRawNames = ['pre', 'script', 'style', 'textarea']
{
  "name": "micromark-util-html-tag-name",
  "version": "2.0.1",
  "description": "micromark utility with list of html tag names",
  "license": "MIT",
  "keywords": [
    "micromark",
    "util",
    "utility",
    "html",
    "tag",
    "name"
  ],
  "repository": "https://github.com/micromark/micromark/tree/main/packages/micromark-util-html-tag-name",
  "bugs": "https://github.com/micromark/micromark/issues",
  "funding": [
    {
      "type": "GitHub Sponsors",
      "url": "https://github.com/sponsors/unifiedjs"
    },
    {
      "type": "OpenCollective",
      "url": "https://opencollective.com/unified"
    }
  ],
  "author": "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)",
  "contributors": [
    "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)"
  ],
  "sideEffects": false,
  "type": "module",
  "files": [
    "index.d.ts.map",
    "index.d.ts",
    "index.js"
  ],
  "exports": "./index.js",
  "xo": {
    "envs": [
      "shared-node-browser"
    ],
    "prettier": true,
    "rules": {
      "unicorn/prefer-code-point": "off"
    }
  }
}
/**
 * Normalize an identifier (as found in references, definitions).
 *
 * Collapses markdown whitespace, trim, and then lower- and uppercase.
 *
 * Some characters are considered “uppercase”, such as U+03F4 (`ϴ`), but if their
 * lowercase counterpart (U+03B8 (`θ`)) is uppercased will result in a different
 * uppercase character (U+0398 (`Θ`)).
 * So, to get a canonical form, we perform both lower- and uppercase.
 *
 * Using uppercase last makes sure keys will never interact with default
 * prototypal values (such as `constructor`): nothing in the prototype of
 * `Object` is uppercase.
 *
 * @param {string} value
 *   Identifier to normalize.
 * @returns {string}
 *   Normalized identifier.
 */
export function normalizeIdentifier(value) {
  return value
  // Collapse markdown whitespace.
  .replace(/[\t\n\r ]+/g, " ")
  // Trim.
  .replace(/^ | $/g, '')
  // Some characters are considered “uppercase”, but if their lowercase
  // counterpart is uppercased will result in a different uppercase
  // character.
  // Hence, to get that form, we perform both lower- and uppercase.
  // Upper case makes sure keys will not interact with default prototypal
  // methods: no method is uppercase.
  .toLowerCase().toUpperCase();
}{
  "name": "micromark-util-normalize-identifier",
  "version": "2.0.1",
  "description": "micromark utility normalize identifiers (as found in references, definitions)",
  "license": "MIT",
  "keywords": [
    "micromark",
    "util",
    "utility",
    "normalize",
    "id",
    "identifier"
  ],
  "repository": "https://github.com/micromark/micromark/tree/main/packages/micromark-util-normalize-identifier",
  "bugs": "https://github.com/micromark/micromark/issues",
  "funding": [
    {
      "type": "GitHub Sponsors",
      "url": "https://github.com/sponsors/unifiedjs"
    },
    {
      "type": "OpenCollective",
      "url": "https://opencollective.com/unified"
    }
  ],
  "author": "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)",
  "contributors": [
    "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)"
  ],
  "sideEffects": false,
  "type": "module",
  "files": [
    "dev/",
    "index.d.ts.map",
    "index.d.ts",
    "index.js"
  ],
  "exports": {
    "development": "./dev/index.js",
    "default": "./index.js"
  },
  "dependencies": {
    "micromark-util-symbol": "^2.0.0"
  },
  "scripts": {
    "build": "micromark-build"
  },
  "xo": {
    "envs": [
      "shared-node-browser"
    ],
    "prettier": true,
    "rules": {
      "unicorn/prefer-code-point": "off",
      "unicorn/prefer-string-replace-all": "off"
    }
  }
}
/**
 * @import {Event, Resolver, TokenizeContext} from 'micromark-util-types'
 */

/**
 * Call all `resolveAll`s.
 *
 * @param {ReadonlyArray<{resolveAll?: Resolver | undefined}>} constructs
 *   List of constructs, optionally with `resolveAll`s.
 * @param {Array<Event>} events
 *   List of events.
 * @param {TokenizeContext} context
 *   Context used by `tokenize`.
 * @returns {Array<Event>}
 *   Changed events.
 */
export function resolveAll(constructs, events, context) {
  /** @type {Array<Resolver>} */
  const called = []
  let index = -1

  while (++index < constructs.length) {
    const resolve = constructs[index].resolveAll

    if (resolve && !called.includes(resolve)) {
      events = resolve(events, context)
      called.push(resolve)
    }
  }

  return events
}
{
  "name": "micromark-util-resolve-all",
  "version": "2.0.1",
  "description": "micromark utility to resolve subtokens",
  "license": "MIT",
  "keywords": [
    "micromark",
    "util",
    "utility",
    "resolve"
  ],
  "repository": "https://github.com/micromark/micromark/tree/main/packages/micromark-util-resolve-all",
  "bugs": "https://github.com/micromark/micromark/issues",
  "funding": [
    {
      "type": "GitHub Sponsors",
      "url": "https://github.com/sponsors/unifiedjs"
    },
    {
      "type": "OpenCollective",
      "url": "https://opencollective.com/unified"
    }
  ],
  "author": "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)",
  "contributors": [
    "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)"
  ],
  "sideEffects": false,
  "type": "module",
  "files": [
    "index.d.ts.map",
    "index.d.ts",
    "index.js"
  ],
  "exports": "./index.js",
  "dependencies": {
    "micromark-util-types": "^2.0.0"
  },
  "xo": {
    "envs": [
      "shared-node-browser"
    ],
    "prettier": true,
    "rules": {
      "unicorn/prefer-code-point": "off"
    }
  }
}
import { asciiAlphanumeric } from 'micromark-util-character';
import { encode } from 'micromark-util-encode';
/**
 * Make a value safe for injection as a URL.
 *
 * This encodes unsafe characters with percent-encoding and skips already
 * encoded sequences (see `normalizeUri`).
 * Further unsafe characters are encoded as character references (see
 * `micromark-util-encode`).
 *
 * A regex of allowed protocols can be given, in which case the URL is
 * sanitized.
 * For example, `/^(https?|ircs?|mailto|xmpp)$/i` can be used for `a[href]`, or
 * `/^https?$/i` for `img[src]` (this is what `github.com` allows).
 * If the URL includes an unknown protocol (one not matched by `protocol`, such
 * as a dangerous example, `javascript:`), the value is ignored.
 *
 * @param {string | null | undefined} url
 *   URI to sanitize.
 * @param {RegExp | null | undefined} [protocol]
 *   Allowed protocols.
 * @returns {string}
 *   Sanitized URI.
 */
export function sanitizeUri(url, protocol) {
  const value = encode(normalizeUri(url || ''));
  if (!protocol) {
    return value;
  }
  const colon = value.indexOf(':');
  const questionMark = value.indexOf('?');
  const numberSign = value.indexOf('#');
  const slash = value.indexOf('/');
  if (
  // If there is no protocol, it’s relative.
  colon < 0 ||
  // If the first colon is after a `?`, `#`, or `/`, it’s not a protocol.
  slash > -1 && colon > slash || questionMark > -1 && colon > questionMark || numberSign > -1 && colon > numberSign ||
  // It is a protocol, it should be allowed.
  protocol.test(value.slice(0, colon))) {
    return value;
  }
  return '';
}

/**
 * Normalize a URL.
 *
 * Encode unsafe characters with percent-encoding, skipping already encoded
 * sequences.
 *
 * @param {string} value
 *   URI to normalize.
 * @returns {string}
 *   Normalized URI.
 */
export function normalizeUri(value) {
  /** @type {Array<string>} */
  const result = [];
  let index = -1;
  let start = 0;
  let skip = 0;
  while (++index < value.length) {
    const code = value.charCodeAt(index);
    /** @type {string} */
    let replace = '';

    // A correct percent encoded value.
    if (code === 37 && asciiAlphanumeric(value.charCodeAt(index + 1)) && asciiAlphanumeric(value.charCodeAt(index + 2))) {
      skip = 2;
    }
    // ASCII.
    else if (code < 128) {
      if (!/[!#$&-;=?-Z_a-z~]/.test(String.fromCharCode(code))) {
        replace = String.fromCharCode(code);
      }
    }
    // Astral.
    else if (code > 55_295 && code < 57_344) {
      const next = value.charCodeAt(index + 1);

      // A correct surrogate pair.
      if (code < 56_320 && next > 56_319 && next < 57_344) {
        replace = String.fromCharCode(code, next);
        skip = 1;
      }
      // Lone surrogate.
      else {
        replace = "\uFFFD";
      }
    }
    // Unicode.
    else {
      replace = String.fromCharCode(code);
    }
    if (replace) {
      result.push(value.slice(start, index), encodeURIComponent(replace));
      start = index + skip + 1;
      replace = '';
    }
    if (skip) {
      index += skip;
      skip = 0;
    }
  }
  return result.join('') + value.slice(start);
}{
  "name": "micromark-util-sanitize-uri",
  "version": "2.0.1",
  "description": "micromark utility to sanitize urls",
  "license": "MIT",
  "keywords": [
    "micromark",
    "util",
    "utility",
    "sanitize",
    "clear",
    "url"
  ],
  "repository": "https://github.com/micromark/micromark/tree/main/packages/micromark-util-sanitize-uri",
  "bugs": "https://github.com/micromark/micromark/issues",
  "funding": [
    {
      "type": "GitHub Sponsors",
      "url": "https://github.com/sponsors/unifiedjs"
    },
    {
      "type": "OpenCollective",
      "url": "https://opencollective.com/unified"
    }
  ],
  "author": "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)",
  "contributors": [
    "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)"
  ],
  "sideEffects": false,
  "type": "module",
  "files": [
    "dev/",
    "index.d.ts.map",
    "index.d.ts",
    "index.js"
  ],
  "exports": {
    "development": "./dev/index.js",
    "default": "./index.js"
  },
  "dependencies": {
    "micromark-util-character": "^2.0.0",
    "micromark-util-encode": "^2.0.0",
    "micromark-util-symbol": "^2.0.0"
  },
  "scripts": {
    "build": "micromark-build"
  },
  "xo": {
    "envs": [
      "shared-node-browser"
    ],
    "prettier": true,
    "rules": {
      "unicorn/prefer-code-point": "off"
    }
  }
}
/**
 * @import {Chunk, Event, Token} from 'micromark-util-types'
 */

import { splice } from 'micromark-util-chunked';
import { SpliceBuffer } from './lib/splice-buffer.js';

// Hidden API exposed for testing.
export { SpliceBuffer } from './lib/splice-buffer.js';

/**
 * Tokenize subcontent.
 *
 * @param {Array<Event>} eventsArray
 *   List of events.
 * @returns {boolean}
 *   Whether subtokens were found.
 */
// eslint-disable-next-line complexity
export function subtokenize(eventsArray) {
  /** @type {Record<string, number>} */
  const jumps = {};
  let index = -1;
  /** @type {Event} */
  let event;
  /** @type {number | undefined} */
  let lineIndex;
  /** @type {number} */
  let otherIndex;
  /** @type {Event} */
  let otherEvent;
  /** @type {Array<Event>} */
  let parameters;
  /** @type {Array<Event>} */
  let subevents;
  /** @type {boolean | undefined} */
  let more;
  const events = new SpliceBuffer(eventsArray);
  while (++index < events.length) {
    while (index in jumps) {
      index = jumps[index];
    }
    event = events.get(index);

    // Add a hook for the GFM tasklist extension, which needs to know if text
    // is in the first content of a list item.
    if (index && event[1].type === "chunkFlow" && events.get(index - 1)[1].type === "listItemPrefix") {
      subevents = event[1]._tokenizer.events;
      otherIndex = 0;
      if (otherIndex < subevents.length && subevents[otherIndex][1].type === "lineEndingBlank") {
        otherIndex += 2;
      }
      if (otherIndex < subevents.length && subevents[otherIndex][1].type === "content") {
        while (++otherIndex < subevents.length) {
          if (subevents[otherIndex][1].type === "content") {
            break;
          }
          if (subevents[otherIndex][1].type === "chunkText") {
            subevents[otherIndex][1]._isInFirstContentOfListItem = true;
            otherIndex++;
          }
        }
      }
    }

    // Enter.
    if (event[0] === 'enter') {
      if (event[1].contentType) {
        Object.assign(jumps, subcontent(events, index));
        index = jumps[index];
        more = true;
      }
    }
    // Exit.
    else if (event[1]._container) {
      otherIndex = index;
      lineIndex = undefined;
      while (otherIndex--) {
        otherEvent = events.get(otherIndex);
        if (otherEvent[1].type === "lineEnding" || otherEvent[1].type === "lineEndingBlank") {
          if (otherEvent[0] === 'enter') {
            if (lineIndex) {
              events.get(lineIndex)[1].type = "lineEndingBlank";
            }
            otherEvent[1].type = "lineEnding";
            lineIndex = otherIndex;
          }
        } else if (otherEvent[1].type === "linePrefix" || otherEvent[1].type === "listItemIndent") {
          // Move past.
        } else {
          break;
        }
      }
      if (lineIndex) {
        // Fix position.
        event[1].end = {
          ...events.get(lineIndex)[1].start
        };

        // Switch container exit w/ line endings.
        parameters = events.slice(lineIndex, index);
        parameters.unshift(event);
        events.splice(lineIndex, index - lineIndex + 1, parameters);
      }
    }
  }

  // The changes to the `events` buffer must be copied back into the eventsArray
  splice(eventsArray, 0, Number.POSITIVE_INFINITY, events.slice(0));
  return !more;
}

/**
 * Tokenize embedded tokens.
 *
 * @param {SpliceBuffer<Event>} events
 *   Events.
 * @param {number} eventIndex
 *   Index.
 * @returns {Record<string, number>}
 *   Gaps.
 */
function subcontent(events, eventIndex) {
  const token = events.get(eventIndex)[1];
  const context = events.get(eventIndex)[2];
  let startPosition = eventIndex - 1;
  /** @type {Array<number>} */
  const startPositions = [];
  let tokenizer = token._tokenizer;
  if (!tokenizer) {
    tokenizer = context.parser[token.contentType](token.start);
    if (token._contentTypeTextTrailing) {
      tokenizer._contentTypeTextTrailing = true;
    }
  }
  const childEvents = tokenizer.events;
  /** @type {Array<[number, number]>} */
  const jumps = [];
  /** @type {Record<string, number>} */
  const gaps = {};
  /** @type {Array<Chunk>} */
  let stream;
  /** @type {Token | undefined} */
  let previous;
  let index = -1;
  /** @type {Token | undefined} */
  let current = token;
  let adjust = 0;
  let start = 0;
  const breaks = [start];

  // Loop forward through the linked tokens to pass them in order to the
  // subtokenizer.
  while (current) {
    // Find the position of the event for this token.
    while (events.get(++startPosition)[1] !== current) {
      // Empty.
    }
    startPositions.push(startPosition);
    if (!current._tokenizer) {
      stream = context.sliceStream(current);
      if (!current.next) {
        stream.push(null);
      }
      if (previous) {
        tokenizer.defineSkip(current.start);
      }
      if (current._isInFirstContentOfListItem) {
        tokenizer._gfmTasklistFirstContentOfListItem = true;
      }
      tokenizer.write(stream);
      if (current._isInFirstContentOfListItem) {
        tokenizer._gfmTasklistFirstContentOfListItem = undefined;
      }
    }

    // Unravel the next token.
    previous = current;
    current = current.next;
  }

  // Now, loop back through all events (and linked tokens), to figure out which
  // parts belong where.
  current = token;
  while (++index < childEvents.length) {
    if (
    // Find a void token that includes a break.
    childEvents[index][0] === 'exit' && childEvents[index - 1][0] === 'enter' && childEvents[index][1].type === childEvents[index - 1][1].type && childEvents[index][1].start.line !== childEvents[index][1].end.line) {
      start = index + 1;
      breaks.push(start);
      // Help GC.
      current._tokenizer = undefined;
      current.previous = undefined;
      current = current.next;
    }
  }

  // Help GC.
  tokenizer.events = [];

  // If there’s one more token (which is the cases for lines that end in an
  // EOF), that’s perfect: the last point we found starts it.
  // If there isn’t then make sure any remaining content is added to it.
  if (current) {
    // Help GC.
    current._tokenizer = undefined;
    current.previous = undefined;
  } else {
    breaks.pop();
  }

  // Now splice the events from the subtokenizer into the current events,
  // moving back to front so that splice indices aren’t affected.
  index = breaks.length;
  while (index--) {
    const slice = childEvents.slice(breaks[index], breaks[index + 1]);
    const start = startPositions.pop();
    jumps.push([start, start + slice.length - 1]);
    events.splice(start, 2, slice);
  }
  jumps.reverse();
  index = -1;
  while (++index < jumps.length) {
    gaps[adjust + jumps[index][0]] = adjust + jumps[index][1];
    adjust += jumps[index][1] - jumps[index][0] - 1;
  }
  return gaps;
}/**
 * Some of the internal operations of micromark do lots of editing
 * operations on very large arrays. This runs into problems with two
 * properties of most circa-2020 JavaScript interpreters:
 *
 *  - Array-length modifications at the high end of an array (push/pop) are
 *    expected to be common and are implemented in (amortized) time
 *    proportional to the number of elements added or removed, whereas
 *    other operations (shift/unshift and splice) are much less efficient.
 *  - Function arguments are passed on the stack, so adding tens of thousands
 *    of elements to an array with `arr.push(...newElements)` will frequently
 *    cause stack overflows. (see <https://stackoverflow.com/questions/22123769/rangeerror-maximum-call-stack-size-exceeded-why>)
 *
 * SpliceBuffers are an implementation of gap buffers, which are a
 * generalization of the "queue made of two stacks" idea. The splice buffer
 * maintains a cursor, and moving the cursor has cost proportional to the
 * distance the cursor moves, but inserting, deleting, or splicing in
 * new information at the cursor is as efficient as the push/pop operation.
 * This allows for an efficient sequence of splices (or pushes, pops, shifts,
 * or unshifts) as long such edits happen at the same part of the array or
 * generally sweep through the array from the beginning to the end.
 *
 * The interface for splice buffers also supports large numbers of inputs by
 * passing a single array argument rather passing multiple arguments on the
 * function call stack.
 *
 * @template T
 *   Item type.
 */
export class SpliceBuffer {
  /**
   * @param {ReadonlyArray<T> | null | undefined} [initial]
   *   Initial items (optional).
   * @returns
   *   Splice buffer.
   */
  constructor(initial) {
    /** @type {Array<T>} */
    this.left = initial ? [...initial] : [];
    /** @type {Array<T>} */
    this.right = [];
  }

  /**
   * Array access;
   * does not move the cursor.
   *
   * @param {number} index
   *   Index.
   * @return {T}
   *   Item.
   */
  get(index) {
    if (index < 0 || index >= this.left.length + this.right.length) {
      throw new RangeError('Cannot access index `' + index + '` in a splice buffer of size `' + (this.left.length + this.right.length) + '`');
    }
    if (index < this.left.length) return this.left[index];
    return this.right[this.right.length - index + this.left.length - 1];
  }

  /**
   * The length of the splice buffer, one greater than the largest index in the
   * array.
   */
  get length() {
    return this.left.length + this.right.length;
  }

  /**
   * Remove and return `list[0]`;
   * moves the cursor to `0`.
   *
   * @returns {T | undefined}
   *   Item, optional.
   */
  shift() {
    this.setCursor(0);
    return this.right.pop();
  }

  /**
   * Slice the buffer to get an array;
   * does not move the cursor.
   *
   * @param {number} start
   *   Start.
   * @param {number | null | undefined} [end]
   *   End (optional).
   * @returns {Array<T>}
   *   Array of items.
   */
  slice(start, end) {
    /** @type {number} */
    const stop = end === null || end === undefined ? Number.POSITIVE_INFINITY : end;
    if (stop < this.left.length) {
      return this.left.slice(start, stop);
    }
    if (start > this.left.length) {
      return this.right.slice(this.right.length - stop + this.left.length, this.right.length - start + this.left.length).reverse();
    }
    return this.left.slice(start).concat(this.right.slice(this.right.length - stop + this.left.length).reverse());
  }

  /**
   * Mimics the behavior of Array.prototype.splice() except for the change of
   * interface necessary to avoid segfaults when patching in very large arrays.
   *
   * This operation moves cursor is moved to `start` and results in the cursor
   * placed after any inserted items.
   *
   * @param {number} start
   *   Start;
   *   zero-based index at which to start changing the array;
   *   negative numbers count backwards from the end of the array and values
   *   that are out-of bounds are clamped to the appropriate end of the array.
   * @param {number | null | undefined} [deleteCount=0]
   *   Delete count (default: `0`);
   *   maximum number of elements to delete, starting from start.
   * @param {Array<T> | null | undefined} [items=[]]
   *   Items to include in place of the deleted items (default: `[]`).
   * @return {Array<T>}
   *   Any removed items.
   */
  splice(start, deleteCount, items) {
    /** @type {number} */
    const count = deleteCount || 0;
    this.setCursor(Math.trunc(start));
    const removed = this.right.splice(this.right.length - count, Number.POSITIVE_INFINITY);
    if (items) chunkedPush(this.left, items);
    return removed.reverse();
  }

  /**
   * Remove and return the highest-numbered item in the array, so
   * `list[list.length - 1]`;
   * Moves the cursor to `length`.
   *
   * @returns {T | undefined}
   *   Item, optional.
   */
  pop() {
    this.setCursor(Number.POSITIVE_INFINITY);
    return this.left.pop();
  }

  /**
   * Inserts a single item to the high-numbered side of the array;
   * moves the cursor to `length`.
   *
   * @param {T} item
   *   Item.
   * @returns {undefined}
   *   Nothing.
   */
  push(item) {
    this.setCursor(Number.POSITIVE_INFINITY);
    this.left.push(item);
  }

  /**
   * Inserts many items to the high-numbered side of the array.
   * Moves the cursor to `length`.
   *
   * @param {Array<T>} items
   *   Items.
   * @returns {undefined}
   *   Nothing.
   */
  pushMany(items) {
    this.setCursor(Number.POSITIVE_INFINITY);
    chunkedPush(this.left, items);
  }

  /**
   * Inserts a single item to the low-numbered side of the array;
   * Moves the cursor to `0`.
   *
   * @param {T} item
   *   Item.
   * @returns {undefined}
   *   Nothing.
   */
  unshift(item) {
    this.setCursor(0);
    this.right.push(item);
  }

  /**
   * Inserts many items to the low-numbered side of the array;
   * moves the cursor to `0`.
   *
   * @param {Array<T>} items
   *   Items.
   * @returns {undefined}
   *   Nothing.
   */
  unshiftMany(items) {
    this.setCursor(0);
    chunkedPush(this.right, items.reverse());
  }

  /**
   * Move the cursor to a specific position in the array. Requires
   * time proportional to the distance moved.
   *
   * If `n < 0`, the cursor will end up at the beginning.
   * If `n > length`, the cursor will end up at the end.
   *
   * @param {number} n
   *   Position.
   * @return {undefined}
   *   Nothing.
   */
  setCursor(n) {
    if (n === this.left.length || n > this.left.length && this.right.length === 0 || n < 0 && this.left.length === 0) return;
    if (n < this.left.length) {
      // Move cursor to the this.left
      const removed = this.left.splice(n, Number.POSITIVE_INFINITY);
      chunkedPush(this.right, removed.reverse());
    } else {
      // Move cursor to the this.right
      const removed = this.right.splice(this.left.length + this.right.length - n, Number.POSITIVE_INFINITY);
      chunkedPush(this.left, removed.reverse());
    }
  }
}

/**
 * Avoid stack overflow by pushing items onto the stack in segments
 *
 * @template T
 *   Item type.
 * @param {Array<T>} list
 *   List to inject into.
 * @param {ReadonlyArray<T>} right
 *   Items to inject.
 * @return {undefined}
 *   Nothing.
 */
function chunkedPush(list, right) {
  /** @type {number} */
  let chunkStart = 0;
  if (right.length < 10000) {
    list.push(...right);
  } else {
    while (chunkStart < right.length) {
      list.push(...right.slice(chunkStart, chunkStart + 10000));
      chunkStart += 10000;
    }
  }
}{
  "name": "micromark-util-subtokenize",
  "version": "2.1.0",
  "description": "micromark utility to tokenize subtokens",
  "license": "MIT",
  "keywords": [
    "micromark",
    "util",
    "utility",
    "tokenize"
  ],
  "repository": "https://github.com/micromark/micromark/tree/main/packages/micromark-util-subtokenize",
  "bugs": "https://github.com/micromark/micromark/issues",
  "funding": [
    {
      "type": "GitHub Sponsors",
      "url": "https://github.com/sponsors/unifiedjs"
    },
    {
      "type": "OpenCollective",
      "url": "https://opencollective.com/unified"
    }
  ],
  "author": "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)",
  "contributors": [
    "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)"
  ],
  "sideEffects": false,
  "type": "module",
  "files": [
    "dev/",
    "lib/",
    "index.d.ts.map",
    "index.d.ts",
    "index.js"
  ],
  "exports": {
    "development": "./dev/index.js",
    "default": "./index.js"
  },
  "dependencies": {
    "devlop": "^1.0.0",
    "micromark-util-chunked": "^2.0.0",
    "micromark-util-symbol": "^2.0.0",
    "micromark-util-types": "^2.0.0"
  },
  "scripts": {
    "build": "micromark-build"
  },
  "xo": {
    "envs": [
      "shared-node-browser"
    ],
    "prettier": true,
    "rules": {
      "max-depth": "off",
      "unicorn/prefer-code-point": "off"
    }
  }
}
/**
 * @import {Encoding, Value} from 'micromark-util-types'
 */

/**
 * @typedef {import('micromark-util-types').Options} Options
 */

import { compile } from './lib/compile.js';
import { parse } from './lib/parse.js';
import { postprocess } from './lib/postprocess.js';
import { preprocess } from './lib/preprocess.js';
export { compile } from './lib/compile.js';
export { parse } from './lib/parse.js';
export { postprocess } from './lib/postprocess.js';
export { preprocess } from './lib/preprocess.js';

/**
 * Compile markdown to HTML.
 *
 * > Note: which encodings are supported depends on the engine.
 * > For info on Node.js, see:
 * > <https://nodejs.org/api/util.html#whatwg-supported-encodings>.
 *
 * @overload
 * @param {Value} value
 *   Markdown to parse (`string` or `Uint8Array`).
 * @param {Encoding | null | undefined} encoding
 *   Character encoding to understand `value` as when it’s a `Uint8Array`
 *   (`string`, default: `'utf8'`).
 * @param {Options | null | undefined} [options]
 *   Configuration.
 * @returns {string}
 *   Compiled HTML.
 *
 * @overload
 * @param {Value} value
 *   Markdown to parse (`string` or `Uint8Array`).
 * @param {Options | null | undefined} [options]
 *   Configuration.
 * @returns {string}
 *   Compiled HTML.
 *
 * @param {Value} value
 *   Markdown to parse (`string` or `Uint8Array`).
 * @param {Encoding | Options | null | undefined} [encoding]
 *   Character encoding to understand `value` as when it’s a `Uint8Array`
 *   (`string`, default: `'utf8'`).
 * @param {Options | null | undefined} [options]
 *   Configuration.
 * @returns {string}
 *   Compiled HTML.
 */
export function micromark(value, encoding, options) {
  if (typeof encoding !== 'string') {
    options = encoding;
    encoding = undefined;
  }
  return compile(options)(postprocess(parse(options).document().write(preprocess()(value, encoding, true))));
}/**
 * While micromark is a lexer/tokenizer, the common case of going from markdown
 * to html is currently built in as this module, even though the parts can be
 * used separately to build ASTs, CSTs, or many other output formats.
 *
 * Having an HTML compiler built in is useful because it allows us to check for
 * compliancy to CommonMark, the de facto norm of markdown, specified in roughly
 * 600 input/output cases.
 *
 * This module has an interface that accepts lists of events instead of the
 * whole at once, however, because markdown can’t be truly streaming, we buffer
 * events before processing and outputting the final result.
 */

/**
 * @import {
 *   CompileContext,
 *   CompileData,
 *   CompileOptions,
 *   Compile,
 *   Definition,
 *   Event,
 *   Handle,
 *   HtmlExtension,
 *   LineEnding,
 *   NormalizedHtmlExtension,
 *   Token
 * } from 'micromark-util-types'
 */

/**
 * @typedef Media
 * @property {boolean | undefined} [image]
 * @property {string | undefined} [labelId]
 * @property {string | undefined} [label]
 * @property {string | undefined} [referenceId]
 * @property {string | undefined} [destination]
 * @property {string | undefined} [title]
 */

import { decodeNamedCharacterReference } from 'decode-named-character-reference';
import { push } from 'micromark-util-chunked';
import { combineHtmlExtensions } from 'micromark-util-combine-extensions';
import { decodeNumericCharacterReference } from 'micromark-util-decode-numeric-character-reference';
import { encode as _encode } from 'micromark-util-encode';
import { normalizeIdentifier } from 'micromark-util-normalize-identifier';
import { sanitizeUri } from 'micromark-util-sanitize-uri';
const hasOwnProperty = {}.hasOwnProperty;

/**
 * These two are allowlists of safe protocols for full URLs in respectively the
 * `href` (on `<a>`) and `src` (on `<img>`) attributes.
 * They are based on what is allowed on GitHub,
 * <https://github.com/syntax-tree/hast-util-sanitize/blob/9275b21/lib/github.json#L31>
 */
const protocolHref = /^(https?|ircs?|mailto|xmpp)$/i;
const protocolSource = /^https?$/i;

/**
 * @param {CompileOptions | null | undefined} [options]
 * @returns {Compile}
 */
export function compile(options) {
  const settings = options || {};

  /**
   * Tags is needed because according to markdown, links and emphasis and
   * whatnot can exist in images, however, as HTML doesn’t allow content in
   * images, the tags are ignored in the `alt` attribute, but the content
   * remains.
   *
   * @type {boolean | undefined}
   */
  let tags = true;

  /**
   * An object to track identifiers to media (URLs and titles) defined with
   * definitions.
   *
   * @type {Record<string, Definition>}
   */
  const definitions = {};

  /**
   * A lot of the handlers need to capture some of the output data, modify it
   * somehow, and then deal with it.
   * We do that by tracking a stack of buffers, that can be opened (with
   * `buffer`) and closed (with `resume`) to access them.
   *
   * @type {Array<Array<string>>}
   */
  const buffers = [[]];

  /**
   * As we can have links in images and the other way around, where the deepest
   * ones are closed first, we need to track which one we’re in.
   *
   * @type {Array<Media>}
   */
  const mediaStack = [];

  /**
   * Same as `mediaStack` for tightness, which is specific to lists.
   * We need to track if we’re currently in a tight or loose container.
   *
   * @type {Array<boolean>}
   */
  const tightStack = [];

  /** @type {HtmlExtension} */
  const defaultHandlers = {
    enter: {
      blockQuote: onenterblockquote,
      codeFenced: onentercodefenced,
      codeFencedFenceInfo: buffer,
      codeFencedFenceMeta: buffer,
      codeIndented: onentercodeindented,
      codeText: onentercodetext,
      content: onentercontent,
      definition: onenterdefinition,
      definitionDestinationString: onenterdefinitiondestinationstring,
      definitionLabelString: buffer,
      definitionTitleString: buffer,
      emphasis: onenteremphasis,
      htmlFlow: onenterhtmlflow,
      htmlText: onenterhtml,
      image: onenterimage,
      label: buffer,
      link: onenterlink,
      listItemMarker: onenterlistitemmarker,
      listItemValue: onenterlistitemvalue,
      listOrdered: onenterlistordered,
      listUnordered: onenterlistunordered,
      paragraph: onenterparagraph,
      reference: buffer,
      resource: onenterresource,
      resourceDestinationString: onenterresourcedestinationstring,
      resourceTitleString: buffer,
      setextHeading: onentersetextheading,
      strong: onenterstrong
    },
    exit: {
      atxHeading: onexitatxheading,
      atxHeadingSequence: onexitatxheadingsequence,
      autolinkEmail: onexitautolinkemail,
      autolinkProtocol: onexitautolinkprotocol,
      blockQuote: onexitblockquote,
      characterEscapeValue: onexitdata,
      characterReferenceMarkerHexadecimal: onexitcharacterreferencemarker,
      characterReferenceMarkerNumeric: onexitcharacterreferencemarker,
      characterReferenceValue: onexitcharacterreferencevalue,
      codeFenced: onexitflowcode,
      codeFencedFence: onexitcodefencedfence,
      codeFencedFenceInfo: onexitcodefencedfenceinfo,
      codeFencedFenceMeta: onresumedrop,
      codeFlowValue: onexitcodeflowvalue,
      codeIndented: onexitflowcode,
      codeText: onexitcodetext,
      codeTextData: onexitdata,
      data: onexitdata,
      definition: onexitdefinition,
      definitionDestinationString: onexitdefinitiondestinationstring,
      definitionLabelString: onexitdefinitionlabelstring,
      definitionTitleString: onexitdefinitiontitlestring,
      emphasis: onexitemphasis,
      hardBreakEscape: onexithardbreak,
      hardBreakTrailing: onexithardbreak,
      htmlFlow: onexithtml,
      htmlFlowData: onexitdata,
      htmlText: onexithtml,
      htmlTextData: onexitdata,
      image: onexitmedia,
      label: onexitlabel,
      labelText: onexitlabeltext,
      lineEnding: onexitlineending,
      link: onexitmedia,
      listOrdered: onexitlistordered,
      listUnordered: onexitlistunordered,
      paragraph: onexitparagraph,
      reference: onresumedrop,
      referenceString: onexitreferencestring,
      resource: onresumedrop,
      resourceDestinationString: onexitresourcedestinationstring,
      resourceTitleString: onexitresourcetitlestring,
      setextHeading: onexitsetextheading,
      setextHeadingLineSequence: onexitsetextheadinglinesequence,
      setextHeadingText: onexitsetextheadingtext,
      strong: onexitstrong,
      thematicBreak: onexitthematicbreak
    }
  };

  /**
   * Combine the HTML extensions with the default handlers.
   * An HTML extension is an object whose fields are either `enter` or `exit`
   * (reflecting whether a token is entered or exited).
   * The values at such objects are names of tokens mapping to handlers.
   * Handlers are called, respectively when a token is opener or closed, with
   * that token, and a context as `this`.
   */
  const handlers = /** @type {NormalizedHtmlExtension} */
  combineHtmlExtensions([defaultHandlers, ...(settings.htmlExtensions || [])]);

  /**
   * Handlers do often need to keep track of some state.
   * That state is provided here as a key-value store (an object).
   *
   * @type {CompileData}
   */
  const data = {
    definitions,
    tightStack
  };

  /**
   * The context for handlers references a couple of useful functions.
   * In handlers from extensions, those can be accessed at `this`.
   * For the handlers here, they can be accessed directly.
   *
   * @type {Omit<CompileContext, 'sliceSerialize'>}
   */
  const context = {
    buffer,
    encode,
    getData,
    lineEndingIfNeeded,
    options: settings,
    raw,
    resume,
    setData,
    tag
  };

  /**
   * Generally, micromark copies line endings (`'\r'`, `'\n'`, `'\r\n'`) in the
   * markdown document over to the compiled HTML.
   * In some cases, such as `> a`, CommonMark requires that extra line endings
   * are added: `<blockquote>\n<p>a</p>\n</blockquote>`.
   * This variable hold the default line ending when given (or `undefined`),
   * and in the latter case will be updated to the first found line ending if
   * there is one.
   */
  let lineEndingStyle = settings.defaultLineEnding;

  // Return the function that handles a slice of events.
  return compile;

  /**
   * Deal w/ a slice of events.
   * Return either the empty string if there’s nothing of note to return, or the
   * result when done.
   *
   * @param {ReadonlyArray<Event>} events
   * @returns {string}
   */
  function compile(events) {
    let index = -1;
    let start = 0;
    /** @type {Array<number>} */
    const listStack = [];
    // As definitions can come after references, we need to figure out the media
    // (urls and titles) defined by them before handling the references.
    // So, we do sort of what HTML does: put metadata at the start (in head), and
    // then put content after (`body`).
    /** @type {Array<Event>} */
    let head = [];
    /** @type {Array<Event>} */
    let body = [];
    while (++index < events.length) {
      // Figure out the line ending style used in the document.
      if (!lineEndingStyle && (events[index][1].type === "lineEnding" || events[index][1].type === "lineEndingBlank")) {
        lineEndingStyle = /** @type {LineEnding} */
        events[index][2].sliceSerialize(events[index][1]);
      }

      // Preprocess lists to infer whether the list is loose or not.
      if (events[index][1].type === "listOrdered" || events[index][1].type === "listUnordered") {
        if (events[index][0] === 'enter') {
          listStack.push(index);
        } else {
          prepareList(events.slice(listStack.pop(), index));
        }
      }

      // Move definitions to the front.
      if (events[index][1].type === "definition") {
        if (events[index][0] === 'enter') {
          body = push(body, events.slice(start, index));
          start = index;
        } else {
          head = push(head, events.slice(start, index + 1));
          start = index + 1;
        }
      }
    }
    head = push(head, body);
    head = push(head, events.slice(start));
    index = -1;
    const result = head;

    // Handle the start of the document, if defined.
    if (handlers.enter.null) {
      handlers.enter.null.call(context);
    }

    // Handle all events.
    while (++index < events.length) {
      const handles = handlers[result[index][0]];
      const kind = result[index][1].type;
      const handle = handles[kind];
      if (hasOwnProperty.call(handles, kind) && handle) {
        handle.call({
          sliceSerialize: result[index][2].sliceSerialize,
          ...context
        }, result[index][1]);
      }
    }

    // Handle the end of the document, if defined.
    if (handlers.exit.null) {
      handlers.exit.null.call(context);
    }
    return buffers[0].join('');
  }

  /**
   * Figure out whether lists are loose or not.
   *
   * @param {ReadonlyArray<Event>} slice
   * @returns {undefined}
   */
  function prepareList(slice) {
    const length = slice.length;
    let index = 0; // Skip open.
    let containerBalance = 0;
    let loose = false;
    /** @type {boolean | undefined} */
    let atMarker;
    while (++index < length) {
      const event = slice[index];
      if (event[1]._container) {
        atMarker = undefined;
        if (event[0] === 'enter') {
          containerBalance++;
        } else {
          containerBalance--;
        }
      } else switch (event[1].type) {
        case "listItemPrefix":
          {
            if (event[0] === 'exit') {
              atMarker = true;
            }
            break;
          }
        case "linePrefix":
          {
            // Ignore

            break;
          }
        case "lineEndingBlank":
          {
            if (event[0] === 'enter' && !containerBalance) {
              if (atMarker) {
                atMarker = undefined;
              } else {
                loose = true;
              }
            }
            break;
          }
        default:
          {
            atMarker = undefined;
          }
      }
    }
    slice[0][1]._loose = loose;
  }

  /**
   * @type {CompileContext['setData']}
   */
  function setData(key, value) {
    // @ts-expect-error: assume `value` is omitted (`undefined` is passed) only
    // if allowed.
    data[key] = value;
  }

  /**
   * @type {CompileContext['getData']}
   */
  function getData(key) {
    return data[key];
  }

  /** @type {CompileContext['buffer']} */
  function buffer() {
    buffers.push([]);
  }

  /** @type {CompileContext['resume']} */
  function resume() {
    const buf = buffers.pop();
    return buf.join('');
  }

  /** @type {CompileContext['tag']} */
  function tag(value) {
    if (!tags) return;
    setData('lastWasTag', true);
    buffers[buffers.length - 1].push(value);
  }

  /** @type {CompileContext['raw']} */
  function raw(value) {
    setData('lastWasTag');
    buffers[buffers.length - 1].push(value);
  }

  /**
   * Output an extra line ending.
   *
   * @returns {undefined}
   */
  function lineEnding() {
    raw(lineEndingStyle || '\n');
  }

  /** @type {CompileContext['lineEndingIfNeeded']} */
  function lineEndingIfNeeded() {
    const buffer = buffers[buffers.length - 1];
    const slice = buffer[buffer.length - 1];
    const previous = slice ? slice.charCodeAt(slice.length - 1) : null;
    if (previous === 10 || previous === 13 || previous === null) {
      return;
    }
    lineEnding();
  }

  /** @type {CompileContext['encode']} */
  function encode(value) {
    return getData('ignoreEncode') ? value : _encode(value);
  }

  //
  // Handlers.
  //

  /**
   * @returns {undefined}
   */
  function onresumedrop() {
    resume();
  }

  /**
   * @this {CompileContext}
   * @type {Handle}
   */
  function onenterlistordered(token) {
    tightStack.push(!token._loose);
    lineEndingIfNeeded();
    tag('<ol');
    setData('expectFirstItem', true);
  }

  /**
   * @this {CompileContext}
   * @type {Handle}
   */
  function onenterlistunordered(token) {
    tightStack.push(!token._loose);
    lineEndingIfNeeded();
    tag('<ul');
    setData('expectFirstItem', true);
  }

  /**
   * @this {CompileContext}
   * @type {Handle}
   */
  function onenterlistitemvalue(token) {
    if (getData('expectFirstItem')) {
      const value = Number.parseInt(this.sliceSerialize(token), 10);
      if (value !== 1) {
        tag(' start="' + encode(String(value)) + '"');
      }
    }
  }

  /**
   * @returns {undefined}
   */
  function onenterlistitemmarker() {
    if (getData('expectFirstItem')) {
      tag('>');
    } else {
      onexitlistitem();
    }
    lineEndingIfNeeded();
    tag('<li>');
    setData('expectFirstItem');
    // “Hack” to prevent a line ending from showing up if the item is empty.
    setData('lastWasTag');
  }

  /**
   * @returns {undefined}
   */
  function onexitlistordered() {
    onexitlistitem();
    tightStack.pop();
    lineEnding();
    tag('</ol>');
  }

  /**
   * @returns {undefined}
   */
  function onexitlistunordered() {
    onexitlistitem();
    tightStack.pop();
    lineEnding();
    tag('</ul>');
  }

  /**
   * @returns {undefined}
   */
  function onexitlistitem() {
    if (getData('lastWasTag') && !getData('slurpAllLineEndings')) {
      lineEndingIfNeeded();
    }
    tag('</li>');
    setData('slurpAllLineEndings');
  }

  /**
   * @this {CompileContext}
   * @type {Handle}
   */
  function onenterblockquote() {
    tightStack.push(false);
    lineEndingIfNeeded();
    tag('<blockquote>');
  }

  /**
   * @this {CompileContext}
   * @type {Handle}
   */
  function onexitblockquote() {
    tightStack.pop();
    lineEndingIfNeeded();
    tag('</blockquote>');
    setData('slurpAllLineEndings');
  }

  /**
   * @this {CompileContext}
   * @type {Handle}
   */
  function onenterparagraph() {
    if (!tightStack[tightStack.length - 1]) {
      lineEndingIfNeeded();
      tag('<p>');
    }
    setData('slurpAllLineEndings');
  }

  /**
   * @this {CompileContext}
   * @type {Handle}
   */
  function onexitparagraph() {
    if (tightStack[tightStack.length - 1]) {
      setData('slurpAllLineEndings', true);
    } else {
      tag('</p>');
    }
  }

  /**
   * @this {CompileContext}
   * @type {Handle}
   */
  function onentercodefenced() {
    lineEndingIfNeeded();
    tag('<pre><code');
    setData('fencesCount', 0);
  }

  /**
   * @this {CompileContext}
   * @type {Handle}
   */
  function onexitcodefencedfenceinfo() {
    const value = resume();
    tag(' class="language-' + value + '"');
  }

  /**
   * @this {CompileContext}
   * @type {Handle}
   */
  function onexitcodefencedfence() {
    const count = getData('fencesCount') || 0;
    if (!count) {
      tag('>');
      setData('slurpOneLineEnding', true);
    }
    setData('fencesCount', count + 1);
  }

  /**
   * @this {CompileContext}
   * @type {Handle}
   */
  function onentercodeindented() {
    lineEndingIfNeeded();
    tag('<pre><code>');
  }

  /**
   * @this {CompileContext}
   * @type {Handle}
   */
  function onexitflowcode() {
    const count = getData('fencesCount');

    // One special case is if we are inside a container, and the fenced code was
    // not closed (meaning it runs to the end).
    // In that case, the following line ending, is considered *outside* the
    // fenced code and block quote by micromark, but CM wants to treat that
    // ending as part of the code.
    if (count !== undefined && count < 2 && data.tightStack.length > 0 && !getData('lastWasTag')) {
      lineEnding();
    }

    // But in most cases, it’s simpler: when we’ve seen some data, emit an extra
    // line ending when needed.
    if (getData('flowCodeSeenData')) {
      lineEndingIfNeeded();
    }
    tag('</code></pre>');
    if (count !== undefined && count < 2) lineEndingIfNeeded();
    setData('flowCodeSeenData');
    setData('fencesCount');
    setData('slurpOneLineEnding');
  }

  /**
   * @this {CompileContext}
   * @type {Handle}
   */
  function onenterimage() {
    mediaStack.push({
      image: true
    });
    tags = undefined; // Disallow tags.
  }

  /**
   * @this {CompileContext}
   * @type {Handle}
   */
  function onenterlink() {
    mediaStack.push({});
  }

  /**
   * @this {CompileContext}
   * @type {Handle}
   */
  function onexitlabeltext(token) {
    mediaStack[mediaStack.length - 1].labelId = this.sliceSerialize(token);
  }

  /**
   * @this {CompileContext}
   * @type {Handle}
   */
  function onexitlabel() {
    mediaStack[mediaStack.length - 1].label = resume();
  }

  /**
   * @this {CompileContext}
   * @type {Handle}
   */
  function onexitreferencestring(token) {
    mediaStack[mediaStack.length - 1].referenceId = this.sliceSerialize(token);
  }

  /**
   * @this {CompileContext}
   * @type {Handle}
   */
  function onenterresource() {
    buffer(); // We can have line endings in the resource, ignore them.
    mediaStack[mediaStack.length - 1].destination = '';
  }

  /**
   * @this {CompileContext}
   * @type {Handle}
   */
  function onenterresourcedestinationstring() {
    buffer();
    // Ignore encoding the result, as we’ll first percent encode the url and
    // encode manually after.
    setData('ignoreEncode', true);
  }

  /**
   * @this {CompileContext}
   * @type {Handle}
   */
  function onexitresourcedestinationstring() {
    mediaStack[mediaStack.length - 1].destination = resume();
    setData('ignoreEncode');
  }

  /**
   * @this {CompileContext}
   * @type {Handle}
   */
  function onexitresourcetitlestring() {
    mediaStack[mediaStack.length - 1].title = resume();
  }

  /**
   * @this {CompileContext}
   * @type {Handle}
   */
  function onexitmedia() {
    let index = mediaStack.length - 1; // Skip current.
    const media = mediaStack[index];
    const id = media.referenceId || media.labelId;
    const context = media.destination === undefined ? definitions[normalizeIdentifier(id)] : media;
    tags = true;
    while (index--) {
      if (mediaStack[index].image) {
        tags = undefined;
        break;
      }
    }
    if (media.image) {
      tag('<img src="' + sanitizeUri(context.destination, settings.allowDangerousProtocol ? undefined : protocolSource) + '" alt="');
      raw(media.label);
      tag('"');
    } else {
      tag('<a href="' + sanitizeUri(context.destination, settings.allowDangerousProtocol ? undefined : protocolHref) + '"');
    }
    tag(context.title ? ' title="' + context.title + '"' : '');
    if (media.image) {
      tag(' />');
    } else {
      tag('>');
      raw(media.label);
      tag('</a>');
    }
    mediaStack.pop();
  }

  /**
   * @this {CompileContext}
   * @type {Handle}
   */
  function onenterdefinition() {
    buffer();
    mediaStack.push({});
  }

  /**
   * @this {CompileContext}
   * @type {Handle}
   */
  function onexitdefinitionlabelstring(token) {
    // Discard label, use the source content instead.
    resume();
    mediaStack[mediaStack.length - 1].labelId = this.sliceSerialize(token);
  }

  /**
   * @this {CompileContext}
   * @type {Handle}
   */
  function onenterdefinitiondestinationstring() {
    buffer();
    setData('ignoreEncode', true);
  }

  /**
   * @this {CompileContext}
   * @type {Handle}
   */
  function onexitdefinitiondestinationstring() {
    mediaStack[mediaStack.length - 1].destination = resume();
    setData('ignoreEncode');
  }

  /**
   * @this {CompileContext}
   * @type {Handle}
   */
  function onexitdefinitiontitlestring() {
    mediaStack[mediaStack.length - 1].title = resume();
  }

  /**
   * @this {CompileContext}
   * @type {Handle}
   */
  function onexitdefinition() {
    const media = mediaStack[mediaStack.length - 1];
    const id = normalizeIdentifier(media.labelId);
    resume();
    if (!hasOwnProperty.call(definitions, id)) {
      definitions[id] = mediaStack[mediaStack.length - 1];
    }
    mediaStack.pop();
  }

  /**
   * @this {CompileContext}
   * @type {Handle}
   */
  function onentercontent() {
    setData('slurpAllLineEndings', true);
  }

  /**
   * @this {CompileContext}
   * @type {Handle}
   */
  function onexitatxheadingsequence(token) {
    // Exit for further sequences.
    if (getData('headingRank')) return;
    setData('headingRank', this.sliceSerialize(token).length);
    lineEndingIfNeeded();
    tag('<h' + getData('headingRank') + '>');
  }

  /**
   * @this {CompileContext}
   * @type {Handle}
   */
  function onentersetextheading() {
    buffer();
    setData('slurpAllLineEndings');
  }

  /**
   * @this {CompileContext}
   * @type {Handle}
   */
  function onexitsetextheadingtext() {
    setData('slurpAllLineEndings', true);
  }

  /**
   * @this {CompileContext}
   * @type {Handle}
   */
  function onexitatxheading() {
    tag('</h' + getData('headingRank') + '>');
    setData('headingRank');
  }

  /**
   * @this {CompileContext}
   * @type {Handle}
   */
  function onexitsetextheadinglinesequence(token) {
    setData('headingRank', this.sliceSerialize(token).charCodeAt(0) === 61 ? 1 : 2);
  }

  /**
   * @this {CompileContext}
   * @type {Handle}
   */
  function onexitsetextheading() {
    const value = resume();
    lineEndingIfNeeded();
    tag('<h' + getData('headingRank') + '>');
    raw(value);
    tag('</h' + getData('headingRank') + '>');
    setData('slurpAllLineEndings');
    setData('headingRank');
  }

  /**
   * @this {CompileContext}
   * @type {Handle}
   */
  function onexitdata(token) {
    raw(encode(this.sliceSerialize(token)));
  }

  /**
   * @this {CompileContext}
   * @type {Handle}
   */
  function onexitlineending(token) {
    if (getData('slurpAllLineEndings')) {
      return;
    }
    if (getData('slurpOneLineEnding')) {
      setData('slurpOneLineEnding');
      return;
    }
    if (getData('inCodeText')) {
      raw(' ');
      return;
    }
    raw(encode(this.sliceSerialize(token)));
  }

  /**
   * @this {CompileContext}
   * @type {Handle}
   */
  function onexitcodeflowvalue(token) {
    raw(encode(this.sliceSerialize(token)));
    setData('flowCodeSeenData', true);
  }

  /**
   * @this {CompileContext}
   * @type {Handle}
   */
  function onexithardbreak() {
    tag('<br />');
  }

  /**
   * @returns {undefined}
   */
  function onenterhtmlflow() {
    lineEndingIfNeeded();
    onenterhtml();
  }

  /**
   * @returns {undefined}
   */
  function onexithtml() {
    setData('ignoreEncode');
  }

  /**
   * @returns {undefined}
   */
  function onenterhtml() {
    if (settings.allowDangerousHtml) {
      setData('ignoreEncode', true);
    }
  }

  /**
   * @returns {undefined}
   */
  function onenteremphasis() {
    tag('<em>');
  }

  /**
   * @returns {undefined}
   */
  function onenterstrong() {
    tag('<strong>');
  }

  /**
   * @returns {undefined}
   */
  function onentercodetext() {
    setData('inCodeText', true);
    tag('<code>');
  }

  /**
   * @returns {undefined}
   */
  function onexitcodetext() {
    setData('inCodeText');
    tag('</code>');
  }

  /**
   * @returns {undefined}
   */
  function onexitemphasis() {
    tag('</em>');
  }

  /**
   * @returns {undefined}
   */
  function onexitstrong() {
    tag('</strong>');
  }

  /**
   * @returns {undefined}
   */
  function onexitthematicbreak() {
    lineEndingIfNeeded();
    tag('<hr />');
  }

  /**
   * @this {CompileContext}
   * @param {Token} token
   * @returns {undefined}
   */
  function onexitcharacterreferencemarker(token) {
    setData('characterReferenceType', token.type);
  }

  /**
   * @this {CompileContext}
   * @type {Handle}
   */
  function onexitcharacterreferencevalue(token) {
    const value = this.sliceSerialize(token);
    const decoded = getData('characterReferenceType') ? decodeNumericCharacterReference(value, getData('characterReferenceType') === "characterReferenceMarkerNumeric" ? 10 : 16) : decodeNamedCharacterReference(value);

    // `decodeNamedCharacterReference` can return `false` for invalid named
    // character references,
    // but everything we’ve tokenized is valid.
    raw(encode(/** @type {string} */decoded));
    setData('characterReferenceType');
  }

  /**
   * @this {CompileContext}
   * @type {Handle}
   */
  function onexitautolinkprotocol(token) {
    const uri = this.sliceSerialize(token);
    tag('<a href="' + sanitizeUri(uri, settings.allowDangerousProtocol ? undefined : protocolHref) + '">');
    raw(encode(uri));
    tag('</a>');
  }

  /**
   * @this {CompileContext}
   * @type {Handle}
   */
  function onexitautolinkemail(token) {
    const uri = this.sliceSerialize(token);
    tag('<a href="' + sanitizeUri('mailto:' + uri) + '">');
    raw(encode(uri));
    tag('</a>');
  }
}/**
 * @import {Extension} from 'micromark-util-types'
 */

import { attention, autolink, blockQuote, characterEscape, characterReference, codeFenced, codeIndented, codeText, definition, hardBreakEscape, headingAtx, htmlFlow, htmlText, labelEnd, labelStartImage, labelStartLink, lineEnding, list, setextUnderline, thematicBreak } from 'micromark-core-commonmark';
import { resolver as resolveText } from './initialize/text.js';

/** @satisfies {Extension['document']} */
export const document = {
  [42]: list,
  [43]: list,
  [45]: list,
  [48]: list,
  [49]: list,
  [50]: list,
  [51]: list,
  [52]: list,
  [53]: list,
  [54]: list,
  [55]: list,
  [56]: list,
  [57]: list,
  [62]: blockQuote
};

/** @satisfies {Extension['contentInitial']} */
export const contentInitial = {
  [91]: definition
};

/** @satisfies {Extension['flowInitial']} */
export const flowInitial = {
  [-2]: codeIndented,
  [-1]: codeIndented,
  [32]: codeIndented
};

/** @satisfies {Extension['flow']} */
export const flow = {
  [35]: headingAtx,
  [42]: thematicBreak,
  [45]: [setextUnderline, thematicBreak],
  [60]: htmlFlow,
  [61]: setextUnderline,
  [95]: thematicBreak,
  [96]: codeFenced,
  [126]: codeFenced
};

/** @satisfies {Extension['string']} */
export const string = {
  [38]: characterReference,
  [92]: characterEscape
};

/** @satisfies {Extension['text']} */
export const text = {
  [-5]: lineEnding,
  [-4]: lineEnding,
  [-3]: lineEnding,
  [33]: labelStartImage,
  [38]: characterReference,
  [42]: attention,
  [60]: [autolink, htmlText],
  [91]: labelStartLink,
  [92]: [hardBreakEscape, characterEscape],
  [93]: labelEnd,
  [95]: attention,
  [96]: codeText
};

/** @satisfies {Extension['insideSpan']} */
export const insideSpan = {
  null: [attention, resolveText]
};

/** @satisfies {Extension['attentionMarkers']} */
export const attentionMarkers = {
  null: [42, 95]
};

/** @satisfies {Extension['disable']} */
export const disable = {
  null: []
};/**
 * @import {
 *   Chunk,
 *   Code,
 *   ConstructRecord,
 *   Construct,
 *   Effects,
 *   InitialConstruct,
 *   ParseContext,
 *   Point,
 *   State,
 *   TokenizeContext,
 *   Token
 * } from 'micromark-util-types'
 */

/**
 * @callback Restore
 *   Restore the state.
 * @returns {undefined}
 *   Nothing.
 *
 * @typedef Info
 *   Info.
 * @property {Restore} restore
 *   Restore.
 * @property {number} from
 *   From.
 *
 * @callback ReturnHandle
 *   Handle a successful run.
 * @param {Construct} construct
 *   Construct.
 * @param {Info} info
 *   Info.
 * @returns {undefined}
 *   Nothing.
 */

import { markdownLineEnding } from 'micromark-util-character';
import { push, splice } from 'micromark-util-chunked';
import { resolveAll } from 'micromark-util-resolve-all';
/**
 * Create a tokenizer.
 * Tokenizers deal with one type of data (e.g., containers, flow, text).
 * The parser is the object dealing with it all.
 * `initialize` works like other constructs, except that only its `tokenize`
 * function is used, in which case it doesn’t receive an `ok` or `nok`.
 * `from` can be given to set the point before the first character, although
 * when further lines are indented, they must be set with `defineSkip`.
 *
 * @param {ParseContext} parser
 *   Parser.
 * @param {InitialConstruct} initialize
 *   Construct.
 * @param {Omit<Point, '_bufferIndex' | '_index'> | undefined} [from]
 *   Point (optional).
 * @returns {TokenizeContext}
 *   Context.
 */
export function createTokenizer(parser, initialize, from) {
  /** @type {Point} */
  let point = {
    _bufferIndex: -1,
    _index: 0,
    line: from && from.line || 1,
    column: from && from.column || 1,
    offset: from && from.offset || 0
  };
  /** @type {Record<string, number>} */
  const columnStart = {};
  /** @type {Array<Construct>} */
  const resolveAllConstructs = [];
  /** @type {Array<Chunk>} */
  let chunks = [];
  /** @type {Array<Token>} */
  let stack = [];
  /** @type {boolean | undefined} */
  let consumed = true;

  /**
   * Tools used for tokenizing.
   *
   * @type {Effects}
   */
  const effects = {
    attempt: constructFactory(onsuccessfulconstruct),
    check: constructFactory(onsuccessfulcheck),
    consume,
    enter,
    exit,
    interrupt: constructFactory(onsuccessfulcheck, {
      interrupt: true
    })
  };

  /**
   * State and tools for resolving and serializing.
   *
   * @type {TokenizeContext}
   */
  const context = {
    code: null,
    containerState: {},
    defineSkip,
    events: [],
    now,
    parser,
    previous: null,
    sliceSerialize,
    sliceStream,
    write
  };

  /**
   * The state function.
   *
   * @type {State | undefined}
   */
  let state = initialize.tokenize.call(context, effects);

  /**
   * Track which character we expect to be consumed, to catch bugs.
   *
   * @type {Code}
   */
  let expectedCode;
  if (initialize.resolveAll) {
    resolveAllConstructs.push(initialize);
  }
  return context;

  /** @type {TokenizeContext['write']} */
  function write(slice) {
    chunks = push(chunks, slice);
    main();

    // Exit if we’re not done, resolve might change stuff.
    if (chunks[chunks.length - 1] !== null) {
      return [];
    }
    addResult(initialize, 0);

    // Otherwise, resolve, and exit.
    context.events = resolveAll(resolveAllConstructs, context.events, context);
    return context.events;
  }

  //
  // Tools.
  //

  /** @type {TokenizeContext['sliceSerialize']} */
  function sliceSerialize(token, expandTabs) {
    return serializeChunks(sliceStream(token), expandTabs);
  }

  /** @type {TokenizeContext['sliceStream']} */
  function sliceStream(token) {
    return sliceChunks(chunks, token);
  }

  /** @type {TokenizeContext['now']} */
  function now() {
    // This is a hot path, so we clone manually instead of `Object.assign({}, point)`
    const {
      _bufferIndex,
      _index,
      line,
      column,
      offset
    } = point;
    return {
      _bufferIndex,
      _index,
      line,
      column,
      offset
    };
  }

  /** @type {TokenizeContext['defineSkip']} */
  function defineSkip(value) {
    columnStart[value.line] = value.column;
    accountForPotentialSkip();
  }

  //
  // State management.
  //

  /**
   * Main loop (note that `_index` and `_bufferIndex` in `point` are modified by
   * `consume`).
   * Here is where we walk through the chunks, which either include strings of
   * several characters, or numerical character codes.
   * The reason to do this in a loop instead of a call is so the stack can
   * drain.
   *
   * @returns {undefined}
   *   Nothing.
   */
  function main() {
    /** @type {number} */
    let chunkIndex;
    while (point._index < chunks.length) {
      const chunk = chunks[point._index];

      // If we’re in a buffer chunk, loop through it.
      if (typeof chunk === 'string') {
        chunkIndex = point._index;
        if (point._bufferIndex < 0) {
          point._bufferIndex = 0;
        }
        while (point._index === chunkIndex && point._bufferIndex < chunk.length) {
          go(chunk.charCodeAt(point._bufferIndex));
        }
      } else {
        go(chunk);
      }
    }
  }

  /**
   * Deal with one code.
   *
   * @param {Code} code
   *   Code.
   * @returns {undefined}
   *   Nothing.
   */
  function go(code) {
    consumed = undefined;
    expectedCode = code;
    state = state(code);
  }

  /** @type {Effects['consume']} */
  function consume(code) {
    if (markdownLineEnding(code)) {
      point.line++;
      point.column = 1;
      point.offset += code === -3 ? 2 : 1;
      accountForPotentialSkip();
    } else if (code !== -1) {
      point.column++;
      point.offset++;
    }

    // Not in a string chunk.
    if (point._bufferIndex < 0) {
      point._index++;
    } else {
      point._bufferIndex++;

      // At end of string chunk.
      if (point._bufferIndex ===
      // Points w/ non-negative `_bufferIndex` reference
      // strings.
      /** @type {string} */
      chunks[point._index].length) {
        point._bufferIndex = -1;
        point._index++;
      }
    }

    // Expose the previous character.
    context.previous = code;

    // Mark as consumed.
    consumed = true;
  }

  /** @type {Effects['enter']} */
  function enter(type, fields) {
    /** @type {Token} */
    // @ts-expect-error Patch instead of assign required fields to help GC.
    const token = fields || {};
    token.type = type;
    token.start = now();
    context.events.push(['enter', token, context]);
    stack.push(token);
    return token;
  }

  /** @type {Effects['exit']} */
  function exit(type) {
    const token = stack.pop();
    token.end = now();
    context.events.push(['exit', token, context]);
    return token;
  }

  /**
   * Use results.
   *
   * @type {ReturnHandle}
   */
  function onsuccessfulconstruct(construct, info) {
    addResult(construct, info.from);
  }

  /**
   * Discard results.
   *
   * @type {ReturnHandle}
   */
  function onsuccessfulcheck(_, info) {
    info.restore();
  }

  /**
   * Factory to attempt/check/interrupt.
   *
   * @param {ReturnHandle} onreturn
   *   Callback.
   * @param {{interrupt?: boolean | undefined} | undefined} [fields]
   *   Fields.
   */
  function constructFactory(onreturn, fields) {
    return hook;

    /**
     * Handle either an object mapping codes to constructs, a list of
     * constructs, or a single construct.
     *
     * @param {Array<Construct> | ConstructRecord | Construct} constructs
     *   Constructs.
     * @param {State} returnState
     *   State.
     * @param {State | undefined} [bogusState]
     *   State.
     * @returns {State}
     *   State.
     */
    function hook(constructs, returnState, bogusState) {
      /** @type {ReadonlyArray<Construct>} */
      let listOfConstructs;
      /** @type {number} */
      let constructIndex;
      /** @type {Construct} */
      let currentConstruct;
      /** @type {Info} */
      let info;
      return Array.isArray(constructs) ? /* c8 ignore next 1 */
      handleListOfConstructs(constructs) : 'tokenize' in constructs ?
      // Looks like a construct.
      handleListOfConstructs([(/** @type {Construct} */constructs)]) : handleMapOfConstructs(constructs);

      /**
       * Handle a list of construct.
       *
       * @param {ConstructRecord} map
       *   Constructs.
       * @returns {State}
       *   State.
       */
      function handleMapOfConstructs(map) {
        return start;

        /** @type {State} */
        function start(code) {
          const left = code !== null && map[code];
          const all = code !== null && map.null;
          const list = [
          // To do: add more extension tests.
          /* c8 ignore next 2 */
          ...(Array.isArray(left) ? left : left ? [left] : []), ...(Array.isArray(all) ? all : all ? [all] : [])];
          return handleListOfConstructs(list)(code);
        }
      }

      /**
       * Handle a list of construct.
       *
       * @param {ReadonlyArray<Construct>} list
       *   Constructs.
       * @returns {State}
       *   State.
       */
      function handleListOfConstructs(list) {
        listOfConstructs = list;
        constructIndex = 0;
        if (list.length === 0) {
          return bogusState;
        }
        return handleConstruct(list[constructIndex]);
      }

      /**
       * Handle a single construct.
       *
       * @param {Construct} construct
       *   Construct.
       * @returns {State}
       *   State.
       */
      function handleConstruct(construct) {
        return start;

        /** @type {State} */
        function start(code) {
          // To do: not needed to store if there is no bogus state, probably?
          // Currently doesn’t work because `inspect` in document does a check
          // w/o a bogus, which doesn’t make sense. But it does seem to help perf
          // by not storing.
          info = store();
          currentConstruct = construct;
          if (!construct.partial) {
            context.currentConstruct = construct;
          }

          // Always populated by defaults.

          if (construct.name && context.parser.constructs.disable.null.includes(construct.name)) {
            return nok(code);
          }
          return construct.tokenize.call(
          // If we do have fields, create an object w/ `context` as its
          // prototype.
          // This allows a “live binding”, which is needed for `interrupt`.
          fields ? Object.assign(Object.create(context), fields) : context, effects, ok, nok)(code);
        }
      }

      /** @type {State} */
      function ok(code) {
        consumed = true;
        onreturn(currentConstruct, info);
        return returnState;
      }

      /** @type {State} */
      function nok(code) {
        consumed = true;
        info.restore();
        if (++constructIndex < listOfConstructs.length) {
          return handleConstruct(listOfConstructs[constructIndex]);
        }
        return bogusState;
      }
    }
  }

  /**
   * @param {Construct} construct
   *   Construct.
   * @param {number} from
   *   From.
   * @returns {undefined}
   *   Nothing.
   */
  function addResult(construct, from) {
    if (construct.resolveAll && !resolveAllConstructs.includes(construct)) {
      resolveAllConstructs.push(construct);
    }
    if (construct.resolve) {
      splice(context.events, from, context.events.length - from, construct.resolve(context.events.slice(from), context));
    }
    if (construct.resolveTo) {
      context.events = construct.resolveTo(context.events, context);
    }
  }

  /**
   * Store state.
   *
   * @returns {Info}
   *   Info.
   */
  function store() {
    const startPoint = now();
    const startPrevious = context.previous;
    const startCurrentConstruct = context.currentConstruct;
    const startEventsIndex = context.events.length;
    const startStack = Array.from(stack);
    return {
      from: startEventsIndex,
      restore
    };

    /**
     * Restore state.
     *
     * @returns {undefined}
     *   Nothing.
     */
    function restore() {
      point = startPoint;
      context.previous = startPrevious;
      context.currentConstruct = startCurrentConstruct;
      context.events.length = startEventsIndex;
      stack = startStack;
      accountForPotentialSkip();
    }
  }

  /**
   * Move the current point a bit forward in the line when it’s on a column
   * skip.
   *
   * @returns {undefined}
   *   Nothing.
   */
  function accountForPotentialSkip() {
    if (point.line in columnStart && point.column < 2) {
      point.column = columnStart[point.line];
      point.offset += columnStart[point.line] - 1;
    }
  }
}

/**
 * Get the chunks from a slice of chunks in the range of a token.
 *
 * @param {ReadonlyArray<Chunk>} chunks
 *   Chunks.
 * @param {Pick<Token, 'end' | 'start'>} token
 *   Token.
 * @returns {Array<Chunk>}
 *   Chunks.
 */
function sliceChunks(chunks, token) {
  const startIndex = token.start._index;
  const startBufferIndex = token.start._bufferIndex;
  const endIndex = token.end._index;
  const endBufferIndex = token.end._bufferIndex;
  /** @type {Array<Chunk>} */
  let view;
  if (startIndex === endIndex) {
    // @ts-expect-error `_bufferIndex` is used on string chunks.
    view = [chunks[startIndex].slice(startBufferIndex, endBufferIndex)];
  } else {
    view = chunks.slice(startIndex, endIndex);
    if (startBufferIndex > -1) {
      const head = view[0];
      if (typeof head === 'string') {
        view[0] = head.slice(startBufferIndex);
        /* c8 ignore next 4 -- used to be used, no longer */
      } else {
        view.shift();
      }
    }
    if (endBufferIndex > 0) {
      // @ts-expect-error `_bufferIndex` is used on string chunks.
      view.push(chunks[endIndex].slice(0, endBufferIndex));
    }
  }
  return view;
}

/**
 * Get the string value of a slice of chunks.
 *
 * @param {ReadonlyArray<Chunk>} chunks
 *   Chunks.
 * @param {boolean | undefined} [expandTabs=false]
 *   Whether to expand tabs (default: `false`).
 * @returns {string}
 *   Result.
 */
function serializeChunks(chunks, expandTabs) {
  let index = -1;
  /** @type {Array<string>} */
  const result = [];
  /** @type {boolean | undefined} */
  let atTab;
  while (++index < chunks.length) {
    const chunk = chunks[index];
    /** @type {string} */
    let value;
    if (typeof chunk === 'string') {
      value = chunk;
    } else switch (chunk) {
      case -5:
        {
          value = "\r";
          break;
        }
      case -4:
        {
          value = "\n";
          break;
        }
      case -3:
        {
          value = "\r" + "\n";
          break;
        }
      case -2:
        {
          value = expandTabs ? " " : "\t";
          break;
        }
      case -1:
        {
          if (!expandTabs && atTab) continue;
          value = " ";
          break;
        }
      default:
        {
          // Currently only replacement character.
          value = String.fromCharCode(chunk);
        }
    }
    atTab = chunk === -2;
    result.push(value);
  }
  return result.join('');
}/**
 * @import {
 *   InitialConstruct,
 *   Initializer,
 *   State,
 *   TokenizeContext,
 *   Token
 * } from 'micromark-util-types'
 */

import { factorySpace } from 'micromark-factory-space';
import { markdownLineEnding } from 'micromark-util-character';
/** @type {InitialConstruct} */
export const content = {
  tokenize: initializeContent
};

/**
 * @this {TokenizeContext}
 *   Context.
 * @type {Initializer}
 *   Content.
 */
function initializeContent(effects) {
  const contentStart = effects.attempt(this.parser.constructs.contentInitial, afterContentStartConstruct, paragraphInitial);
  /** @type {Token} */
  let previous;
  return contentStart;

  /** @type {State} */
  function afterContentStartConstruct(code) {
    if (code === null) {
      effects.consume(code);
      return;
    }
    effects.enter("lineEnding");
    effects.consume(code);
    effects.exit("lineEnding");
    return factorySpace(effects, contentStart, "linePrefix");
  }

  /** @type {State} */
  function paragraphInitial(code) {
    effects.enter("paragraph");
    return lineStart(code);
  }

  /** @type {State} */
  function lineStart(code) {
    const token = effects.enter("chunkText", {
      contentType: "text",
      previous
    });
    if (previous) {
      previous.next = token;
    }
    previous = token;
    return data(code);
  }

  /** @type {State} */
  function data(code) {
    if (code === null) {
      effects.exit("chunkText");
      effects.exit("paragraph");
      effects.consume(code);
      return;
    }
    if (markdownLineEnding(code)) {
      effects.consume(code);
      effects.exit("chunkText");
      return lineStart;
    }

    // Data.
    effects.consume(code);
    return data;
  }
}/**
 * @import {
 *   Construct,
 *   ContainerState,
 *   InitialConstruct,
 *   Initializer,
 *   Point,
 *   State,
 *   TokenizeContext,
 *   Tokenizer,
 *   Token
 * } from 'micromark-util-types'
 */

/**
 * @typedef {[Construct, ContainerState]} StackItem
 *   Construct and its state.
 */

import { factorySpace } from 'micromark-factory-space';
import { markdownLineEnding } from 'micromark-util-character';
import { splice } from 'micromark-util-chunked';
/** @type {InitialConstruct} */
export const document = {
  tokenize: initializeDocument
};

/** @type {Construct} */
const containerConstruct = {
  tokenize: tokenizeContainer
};

/**
 * @this {TokenizeContext}
 *   Self.
 * @type {Initializer}
 *   Initializer.
 */
function initializeDocument(effects) {
  const self = this;
  /** @type {Array<StackItem>} */
  const stack = [];
  let continued = 0;
  /** @type {TokenizeContext | undefined} */
  let childFlow;
  /** @type {Token | undefined} */
  let childToken;
  /** @type {number} */
  let lineStartOffset;
  return start;

  /** @type {State} */
  function start(code) {
    // First we iterate through the open blocks, starting with the root
    // document, and descending through last children down to the last open
    // block.
    // Each block imposes a condition that the line must satisfy if the block is
    // to remain open.
    // For example, a block quote requires a `>` character.
    // A paragraph requires a non-blank line.
    // In this phase we may match all or just some of the open blocks.
    // But we cannot close unmatched blocks yet, because we may have a lazy
    // continuation line.
    if (continued < stack.length) {
      const item = stack[continued];
      self.containerState = item[1];
      return effects.attempt(item[0].continuation, documentContinue, checkNewContainers)(code);
    }

    // Done.
    return checkNewContainers(code);
  }

  /** @type {State} */
  function documentContinue(code) {
    continued++;

    // Note: this field is called `_closeFlow` but it also closes containers.
    // Perhaps a good idea to rename it but it’s already used in the wild by
    // extensions.
    if (self.containerState._closeFlow) {
      self.containerState._closeFlow = undefined;
      if (childFlow) {
        closeFlow();
      }

      // Note: this algorithm for moving events around is similar to the
      // algorithm when dealing with lazy lines in `writeToChild`.
      const indexBeforeExits = self.events.length;
      let indexBeforeFlow = indexBeforeExits;
      /** @type {Point | undefined} */
      let point;

      // Find the flow chunk.
      while (indexBeforeFlow--) {
        if (self.events[indexBeforeFlow][0] === 'exit' && self.events[indexBeforeFlow][1].type === "chunkFlow") {
          point = self.events[indexBeforeFlow][1].end;
          break;
        }
      }
      exitContainers(continued);

      // Fix positions.
      let index = indexBeforeExits;
      while (index < self.events.length) {
        self.events[index][1].end = {
          ...point
        };
        index++;
      }

      // Inject the exits earlier (they’re still also at the end).
      splice(self.events, indexBeforeFlow + 1, 0, self.events.slice(indexBeforeExits));

      // Discard the duplicate exits.
      self.events.length = index;
      return checkNewContainers(code);
    }
    return start(code);
  }

  /** @type {State} */
  function checkNewContainers(code) {
    // Next, after consuming the continuation markers for existing blocks, we
    // look for new block starts (e.g. `>` for a block quote).
    // If we encounter a new block start, we close any blocks unmatched in
    // step 1 before creating the new block as a child of the last matched
    // block.
    if (continued === stack.length) {
      // No need to `check` whether there’s a container, of `exitContainers`
      // would be moot.
      // We can instead immediately `attempt` to parse one.
      if (!childFlow) {
        return documentContinued(code);
      }

      // If we have concrete content, such as block HTML or fenced code,
      // we can’t have containers “pierce” into them, so we can immediately
      // start.
      if (childFlow.currentConstruct && childFlow.currentConstruct.concrete) {
        return flowStart(code);
      }

      // If we do have flow, it could still be a blank line,
      // but we’d be interrupting it w/ a new container if there’s a current
      // construct.
      // To do: next major: remove `_gfmTableDynamicInterruptHack` (no longer
      // needed in micromark-extension-gfm-table@1.0.6).
      self.interrupt = Boolean(childFlow.currentConstruct && !childFlow._gfmTableDynamicInterruptHack);
    }

    // Check if there is a new container.
    self.containerState = {};
    return effects.check(containerConstruct, thereIsANewContainer, thereIsNoNewContainer)(code);
  }

  /** @type {State} */
  function thereIsANewContainer(code) {
    if (childFlow) closeFlow();
    exitContainers(continued);
    return documentContinued(code);
  }

  /** @type {State} */
  function thereIsNoNewContainer(code) {
    self.parser.lazy[self.now().line] = continued !== stack.length;
    lineStartOffset = self.now().offset;
    return flowStart(code);
  }

  /** @type {State} */
  function documentContinued(code) {
    // Try new containers.
    self.containerState = {};
    return effects.attempt(containerConstruct, containerContinue, flowStart)(code);
  }

  /** @type {State} */
  function containerContinue(code) {
    continued++;
    stack.push([self.currentConstruct, self.containerState]);
    // Try another.
    return documentContinued(code);
  }

  /** @type {State} */
  function flowStart(code) {
    if (code === null) {
      if (childFlow) closeFlow();
      exitContainers(0);
      effects.consume(code);
      return;
    }
    childFlow = childFlow || self.parser.flow(self.now());
    effects.enter("chunkFlow", {
      _tokenizer: childFlow,
      contentType: "flow",
      previous: childToken
    });
    return flowContinue(code);
  }

  /** @type {State} */
  function flowContinue(code) {
    if (code === null) {
      writeToChild(effects.exit("chunkFlow"), true);
      exitContainers(0);
      effects.consume(code);
      return;
    }
    if (markdownLineEnding(code)) {
      effects.consume(code);
      writeToChild(effects.exit("chunkFlow"));
      // Get ready for the next line.
      continued = 0;
      self.interrupt = undefined;
      return start;
    }
    effects.consume(code);
    return flowContinue;
  }

  /**
   * @param {Token} token
   *   Token.
   * @param {boolean | undefined} [endOfFile]
   *   Whether the token is at the end of the file (default: `false`).
   * @returns {undefined}
   *   Nothing.
   */
  function writeToChild(token, endOfFile) {
    const stream = self.sliceStream(token);
    if (endOfFile) stream.push(null);
    token.previous = childToken;
    if (childToken) childToken.next = token;
    childToken = token;
    childFlow.defineSkip(token.start);
    childFlow.write(stream);

    // Alright, so we just added a lazy line:
    //
    // ```markdown
    // > a
    // b.
    //
    // Or:
    //
    // > ~~~c
    // d
    //
    // Or:
    //
    // > | e |
    // f
    // ```
    //
    // The construct in the second example (fenced code) does not accept lazy
    // lines, so it marked itself as done at the end of its first line, and
    // then the content construct parses `d`.
    // Most constructs in markdown match on the first line: if the first line
    // forms a construct, a non-lazy line can’t “unmake” it.
    //
    // The construct in the third example is potentially a GFM table, and
    // those are *weird*.
    // It *could* be a table, from the first line, if the following line
    // matches a condition.
    // In this case, that second line is lazy, which “unmakes” the first line
    // and turns the whole into one content block.
    //
    // We’ve now parsed the non-lazy and the lazy line, and can figure out
    // whether the lazy line started a new flow block.
    // If it did, we exit the current containers between the two flow blocks.
    if (self.parser.lazy[token.start.line]) {
      let index = childFlow.events.length;
      while (index--) {
        if (
        // The token starts before the line ending…
        childFlow.events[index][1].start.offset < lineStartOffset && (
        // …and either is not ended yet…
        !childFlow.events[index][1].end ||
        // …or ends after it.
        childFlow.events[index][1].end.offset > lineStartOffset)) {
          // Exit: there’s still something open, which means it’s a lazy line
          // part of something.
          return;
        }
      }

      // Note: this algorithm for moving events around is similar to the
      // algorithm when closing flow in `documentContinue`.
      const indexBeforeExits = self.events.length;
      let indexBeforeFlow = indexBeforeExits;
      /** @type {boolean | undefined} */
      let seen;
      /** @type {Point | undefined} */
      let point;

      // Find the previous chunk (the one before the lazy line).
      while (indexBeforeFlow--) {
        if (self.events[indexBeforeFlow][0] === 'exit' && self.events[indexBeforeFlow][1].type === "chunkFlow") {
          if (seen) {
            point = self.events[indexBeforeFlow][1].end;
            break;
          }
          seen = true;
        }
      }
      exitContainers(continued);

      // Fix positions.
      index = indexBeforeExits;
      while (index < self.events.length) {
        self.events[index][1].end = {
          ...point
        };
        index++;
      }

      // Inject the exits earlier (they’re still also at the end).
      splice(self.events, indexBeforeFlow + 1, 0, self.events.slice(indexBeforeExits));

      // Discard the duplicate exits.
      self.events.length = index;
    }
  }

  /**
   * @param {number} size
   *   Size.
   * @returns {undefined}
   *   Nothing.
   */
  function exitContainers(size) {
    let index = stack.length;

    // Exit open containers.
    while (index-- > size) {
      const entry = stack[index];
      self.containerState = entry[1];
      entry[0].exit.call(self, effects);
    }
    stack.length = size;
  }
  function closeFlow() {
    childFlow.write([null]);
    childToken = undefined;
    childFlow = undefined;
    self.containerState._closeFlow = undefined;
  }
}

/**
 * @this {TokenizeContext}
 *   Context.
 * @type {Tokenizer}
 *   Tokenizer.
 */
function tokenizeContainer(effects, ok, nok) {
  // Always populated by defaults.

  return factorySpace(effects, effects.attempt(this.parser.constructs.document, ok, nok), "linePrefix", this.parser.constructs.disable.null.includes('codeIndented') ? undefined : 4);
}/**
 * @import {
 *   InitialConstruct,
 *   Initializer,
 *   State,
 *   TokenizeContext
 * } from 'micromark-util-types'
 */

import { blankLine, content } from 'micromark-core-commonmark';
import { factorySpace } from 'micromark-factory-space';
import { markdownLineEnding } from 'micromark-util-character';
/** @type {InitialConstruct} */
export const flow = {
  tokenize: initializeFlow
};

/**
 * @this {TokenizeContext}
 *   Self.
 * @type {Initializer}
 *   Initializer.
 */
function initializeFlow(effects) {
  const self = this;
  const initial = effects.attempt(
  // Try to parse a blank line.
  blankLine, atBlankEnding,
  // Try to parse initial flow (essentially, only code).
  effects.attempt(this.parser.constructs.flowInitial, afterConstruct, factorySpace(effects, effects.attempt(this.parser.constructs.flow, afterConstruct, effects.attempt(content, afterConstruct)), "linePrefix")));
  return initial;

  /** @type {State} */
  function atBlankEnding(code) {
    if (code === null) {
      effects.consume(code);
      return;
    }
    effects.enter("lineEndingBlank");
    effects.consume(code);
    effects.exit("lineEndingBlank");
    self.currentConstruct = undefined;
    return initial;
  }

  /** @type {State} */
  function afterConstruct(code) {
    if (code === null) {
      effects.consume(code);
      return;
    }
    effects.enter("lineEnding");
    effects.consume(code);
    effects.exit("lineEnding");
    self.currentConstruct = undefined;
    return initial;
  }
}/**
 * @import {
 *   Code,
 *   InitialConstruct,
 *   Initializer,
 *   Resolver,
 *   State,
 *   TokenizeContext
 * } from 'micromark-util-types'
 */

export const resolver = {
  resolveAll: createResolver()
};
export const string = initializeFactory('string');
export const text = initializeFactory('text');

/**
 * @param {'string' | 'text'} field
 *   Field.
 * @returns {InitialConstruct}
 *   Construct.
 */
function initializeFactory(field) {
  return {
    resolveAll: createResolver(field === 'text' ? resolveAllLineSuffixes : undefined),
    tokenize: initializeText
  };

  /**
   * @this {TokenizeContext}
   *   Context.
   * @type {Initializer}
   */
  function initializeText(effects) {
    const self = this;
    const constructs = this.parser.constructs[field];
    const text = effects.attempt(constructs, start, notText);
    return start;

    /** @type {State} */
    function start(code) {
      return atBreak(code) ? text(code) : notText(code);
    }

    /** @type {State} */
    function notText(code) {
      if (code === null) {
        effects.consume(code);
        return;
      }
      effects.enter("data");
      effects.consume(code);
      return data;
    }

    /** @type {State} */
    function data(code) {
      if (atBreak(code)) {
        effects.exit("data");
        return text(code);
      }

      // Data.
      effects.consume(code);
      return data;
    }

    /**
     * @param {Code} code
     *   Code.
     * @returns {boolean}
     *   Whether the code is a break.
     */
    function atBreak(code) {
      if (code === null) {
        return true;
      }
      const list = constructs[code];
      let index = -1;
      if (list) {
        // Always populated by defaults.

        while (++index < list.length) {
          const item = list[index];
          if (!item.previous || item.previous.call(self, self.previous)) {
            return true;
          }
        }
      }
      return false;
    }
  }
}

/**
 * @param {Resolver | undefined} [extraResolver]
 *   Resolver.
 * @returns {Resolver}
 *   Resolver.
 */
function createResolver(extraResolver) {
  return resolveAllText;

  /** @type {Resolver} */
  function resolveAllText(events, context) {
    let index = -1;
    /** @type {number | undefined} */
    let enter;

    // A rather boring computation (to merge adjacent `data` events) which
    // improves mm performance by 29%.
    while (++index <= events.length) {
      if (enter === undefined) {
        if (events[index] && events[index][1].type === "data") {
          enter = index;
          index++;
        }
      } else if (!events[index] || events[index][1].type !== "data") {
        // Don’t do anything if there is one data token.
        if (index !== enter + 2) {
          events[enter][1].end = events[index - 1][1].end;
          events.splice(enter + 2, index - enter - 2);
          index = enter + 2;
        }
        enter = undefined;
      }
    }
    return extraResolver ? extraResolver(events, context) : events;
  }
}

/**
 * A rather ugly set of instructions which again looks at chunks in the input
 * stream.
 * The reason to do this here is that it is *much* faster to parse in reverse.
 * And that we can’t hook into `null` to split the line suffix before an EOF.
 * To do: figure out if we can make this into a clean utility, or even in core.
 * As it will be useful for GFMs literal autolink extension (and maybe even
 * tables?)
 *
 * @type {Resolver}
 */
function resolveAllLineSuffixes(events, context) {
  let eventIndex = 0; // Skip first.

  while (++eventIndex <= events.length) {
    if ((eventIndex === events.length || events[eventIndex][1].type === "lineEnding") && events[eventIndex - 1][1].type === "data") {
      const data = events[eventIndex - 1][1];
      const chunks = context.sliceStream(data);
      let index = chunks.length;
      let bufferIndex = -1;
      let size = 0;
      /** @type {boolean | undefined} */
      let tabs;
      while (index--) {
        const chunk = chunks[index];
        if (typeof chunk === 'string') {
          bufferIndex = chunk.length;
          while (chunk.charCodeAt(bufferIndex - 1) === 32) {
            size++;
            bufferIndex--;
          }
          if (bufferIndex) break;
          bufferIndex = -1;
        }
        // Number
        else if (chunk === -2) {
          tabs = true;
          size++;
        } else if (chunk === -1) {
          // Empty
        } else {
          // Replacement character, exit.
          index++;
          break;
        }
      }

      // Allow final trailing whitespace.
      if (context._contentTypeTextTrailing && eventIndex === events.length) {
        size = 0;
      }
      if (size) {
        const token = {
          type: eventIndex === events.length || tabs || size < 2 ? "lineSuffix" : "hardBreakTrailing",
          start: {
            _bufferIndex: index ? bufferIndex : data.start._bufferIndex + bufferIndex,
            _index: data.start._index + index,
            line: data.end.line,
            column: data.end.column - size,
            offset: data.end.offset - size
          },
          end: {
            ...data.end
          }
        };
        data.end = {
          ...token.start
        };
        if (data.start.offset === data.end.offset) {
          Object.assign(data, token);
        } else {
          events.splice(eventIndex, 0, ['enter', token, context], ['exit', token, context]);
          eventIndex += 2;
        }
      }
      eventIndex++;
    }
  }
  return events;
}/**
 * @import {
 *   Create,
 *   FullNormalizedExtension,
 *   InitialConstruct,
 *   ParseContext,
 *   ParseOptions
 * } from 'micromark-util-types'
 */

import { combineExtensions } from 'micromark-util-combine-extensions';
import { content } from './initialize/content.js';
import { document } from './initialize/document.js';
import { flow } from './initialize/flow.js';
import { string, text } from './initialize/text.js';
import * as defaultConstructs from './constructs.js';
import { createTokenizer } from './create-tokenizer.js';

/**
 * @param {ParseOptions | null | undefined} [options]
 *   Configuration (optional).
 * @returns {ParseContext}
 *   Parser.
 */
export function parse(options) {
  const settings = options || {};
  const constructs = /** @type {FullNormalizedExtension} */
  combineExtensions([defaultConstructs, ...(settings.extensions || [])]);

  /** @type {ParseContext} */
  const parser = {
    constructs,
    content: create(content),
    defined: [],
    document: create(document),
    flow: create(flow),
    lazy: {},
    string: create(string),
    text: create(text)
  };
  return parser;

  /**
   * @param {InitialConstruct} initial
   *   Construct to start with.
   * @returns {Create}
   *   Create a tokenizer.
   */
  function create(initial) {
    return creator;
    /** @type {Create} */
    function creator(from) {
      return createTokenizer(parser, initial, from);
    }
  }
}/**
 * @import {Event} from 'micromark-util-types'
 */

import { subtokenize } from 'micromark-util-subtokenize';

/**
 * @param {Array<Event>} events
 *   Events.
 * @returns {Array<Event>}
 *   Events.
 */
export function postprocess(events) {
  while (!subtokenize(events)) {
    // Empty
  }
  return events;
}/**
 * @import {Chunk, Code, Encoding, Value} from 'micromark-util-types'
 */

/**
 * @callback Preprocessor
 *   Preprocess a value.
 * @param {Value} value
 *   Value.
 * @param {Encoding | null | undefined} [encoding]
 *   Encoding when `value` is a typed array (optional).
 * @param {boolean | null | undefined} [end=false]
 *   Whether this is the last chunk (default: `false`).
 * @returns {Array<Chunk>}
 *   Chunks.
 */

const search = /[\0\t\n\r]/g;

/**
 * @returns {Preprocessor}
 *   Preprocess a value.
 */
export function preprocess() {
  let column = 1;
  let buffer = '';
  /** @type {boolean | undefined} */
  let start = true;
  /** @type {boolean | undefined} */
  let atCarriageReturn;
  return preprocessor;

  /** @type {Preprocessor} */
  // eslint-disable-next-line complexity
  function preprocessor(value, encoding, end) {
    /** @type {Array<Chunk>} */
    const chunks = [];
    /** @type {RegExpMatchArray | null} */
    let match;
    /** @type {number} */
    let next;
    /** @type {number} */
    let startPosition;
    /** @type {number} */
    let endPosition;
    /** @type {Code} */
    let code;
    value = buffer + (typeof value === 'string' ? value.toString() : new TextDecoder(encoding || undefined).decode(value));
    startPosition = 0;
    buffer = '';
    if (start) {
      // To do: `markdown-rs` actually parses BOMs (byte order mark).
      if (value.charCodeAt(0) === 65279) {
        startPosition++;
      }
      start = undefined;
    }
    while (startPosition < value.length) {
      search.lastIndex = startPosition;
      match = search.exec(value);
      endPosition = match && match.index !== undefined ? match.index : value.length;
      code = value.charCodeAt(endPosition);
      if (!match) {
        buffer = value.slice(startPosition);
        break;
      }
      if (code === 10 && startPosition === endPosition && atCarriageReturn) {
        chunks.push(-3);
        atCarriageReturn = undefined;
      } else {
        if (atCarriageReturn) {
          chunks.push(-5);
          atCarriageReturn = undefined;
        }
        if (startPosition < endPosition) {
          chunks.push(value.slice(startPosition, endPosition));
          column += endPosition - startPosition;
        }
        switch (code) {
          case 0:
            {
              chunks.push(65533);
              column++;
              break;
            }
          case 9:
            {
              next = Math.ceil(column / 4) * 4;
              chunks.push(-2);
              while (column++ < next) chunks.push(-1);
              break;
            }
          case 10:
            {
              chunks.push(-4);
              column = 1;
              break;
            }
          default:
            {
              atCarriageReturn = true;
              column = 1;
            }
        }
      }
      startPosition = endPosition + 1;
    }
    if (end) {
      if (atCarriageReturn) chunks.push(-5);
      if (buffer) chunks.push(buffer);
      chunks.push(null);
    }
    return chunks;
  }
}{
  "name": "micromark",
  "version": "4.0.2",
  "description": "small commonmark compliant markdown parser with positional info and concrete tokens",
  "license": "MIT",
  "keywords": [
    "commonmark",
    "compiler",
    "gfm",
    "html",
    "lexer",
    "markdown",
    "markup",
    "md",
    "unified",
    "parse",
    "parser",
    "plugin",
    "process",
    "remark",
    "render",
    "renderer",
    "token",
    "tokenizer"
  ],
  "repository": "https://github.com/micromark/micromark/tree/main/packages/micromark",
  "bugs": "https://github.com/micromark/micromark/issues",
  "funding": [
    {
      "type": "GitHub Sponsors",
      "url": "https://github.com/sponsors/unifiedjs"
    },
    {
      "type": "OpenCollective",
      "url": "https://opencollective.com/unified"
    }
  ],
  "author": "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)",
  "contributors": [
    "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)"
  ],
  "sideEffects": false,
  "type": "module",
  "files": [
    "dev/",
    "lib/",
    "index.d.ts.map",
    "index.d.ts",
    "index.js",
    "stream.d.ts.map",
    "stream.d.ts",
    "stream.js"
  ],
  "exports": {
    ".": {
      "development": "./dev/index.js",
      "default": "./index.js"
    },
    "./stream": {
      "development": "./dev/stream.js",
      "default": "./stream.js"
    }
  },
  "dependencies": {
    "@types/debug": "^4.0.0",
    "debug": "^4.0.0",
    "decode-named-character-reference": "^1.0.0",
    "devlop": "^1.0.0",
    "micromark-core-commonmark": "^2.0.0",
    "micromark-factory-space": "^2.0.0",
    "micromark-util-character": "^2.0.0",
    "micromark-util-chunked": "^2.0.0",
    "micromark-util-combine-extensions": "^2.0.0",
    "micromark-util-decode-numeric-character-reference": "^2.0.0",
    "micromark-util-encode": "^2.0.0",
    "micromark-util-normalize-identifier": "^2.0.0",
    "micromark-util-resolve-all": "^2.0.0",
    "micromark-util-sanitize-uri": "^2.0.0",
    "micromark-util-subtokenize": "^2.0.0",
    "micromark-util-symbol": "^2.0.0",
    "micromark-util-types": "^2.0.0"
  },
  "scripts": {
    "build": "micromark-build"
  },
  "xo": {
    "envs": [
      "shared-node-browser"
    ],
    "prettier": true,
    "rules": {
      "logical-assignment-operators": "off",
      "max-depth": "off",
      "unicorn/no-this-assignment": "off",
      "unicorn/prefer-at": "off",
      "unicorn/prefer-code-point": "off",
      "unicorn/prefer-event-target": "off"
    }
  }
}
// Note: more types exposed from `index.d.ts`.
// To do: refactor to include type parameters in JS.
export {parseEntities} from './lib/index.js'
/**
 * @import {Point} from 'unist'
 * @import {Options} from '../index.js'
 */

import {characterEntitiesLegacy} from 'character-entities-legacy'
import {characterReferenceInvalid} from 'character-reference-invalid'
import {isDecimal} from 'is-decimal'
import {isHexadecimal} from 'is-hexadecimal'
import {isAlphanumerical} from 'is-alphanumerical'
import {decodeNamedCharacterReference} from 'decode-named-character-reference'

// Warning messages.
const messages = [
  '',
  /* 1: Non terminated (named) */
  'Named character references must be terminated by a semicolon',
  /* 2: Non terminated (numeric) */
  'Numeric character references must be terminated by a semicolon',
  /* 3: Empty (named) */
  'Named character references cannot be empty',
  /* 4: Empty (numeric) */
  'Numeric character references cannot be empty',
  /* 5: Unknown (named) */
  'Named character references must be known',
  /* 6: Disallowed (numeric) */
  'Numeric character references cannot be disallowed',
  /* 7: Prohibited (numeric) */
  'Numeric character references cannot be outside the permissible Unicode range'
]

/**
 * Parse HTML character references.
 *
 * @param {string} value
 * @param {Readonly<Options> | null | undefined} [options]
 */
export function parseEntities(value, options) {
  const settings = options || {}
  const additional =
    typeof settings.additional === 'string'
      ? settings.additional.charCodeAt(0)
      : settings.additional
  /** @type {Array<string>} */
  const result = []
  let index = 0
  let lines = -1
  let queue = ''
  /** @type {Point | undefined} */
  let point
  /** @type {Array<number>|undefined} */
  let indent

  if (settings.position) {
    if ('start' in settings.position || 'indent' in settings.position) {
      // @ts-expect-error: points don’t have indent.
      indent = settings.position.indent
      // @ts-expect-error: points don’t have indent.
      point = settings.position.start
    } else {
      point = settings.position
    }
  }

  let line = (point ? point.line : 0) || 1
  let column = (point ? point.column : 0) || 1

  // Cache the current point.
  let previous = now()
  /** @type {number|undefined} */
  let character

  // Ensure the algorithm walks over the first character (inclusive).
  index--

  while (++index <= value.length) {
    // If the previous character was a newline.
    if (character === 10 /* `\n` */) {
      column = (indent ? indent[lines] : 0) || 1
    }

    character = value.charCodeAt(index)

    if (character === 38 /* `&` */) {
      const following = value.charCodeAt(index + 1)

      // The behavior depends on the identity of the next character.
      if (
        following === 9 /* `\t` */ ||
        following === 10 /* `\n` */ ||
        following === 12 /* `\f` */ ||
        following === 32 /* ` ` */ ||
        following === 38 /* `&` */ ||
        following === 60 /* `<` */ ||
        Number.isNaN(following) ||
        (additional && following === additional)
      ) {
        // Not a character reference.
        // No characters are consumed, and nothing is returned.
        // This is not an error, either.
        queue += String.fromCharCode(character)
        column++
        continue
      }

      const start = index + 1
      let begin = start
      let end = start
      /** @type {string} */
      let type

      if (following === 35 /* `#` */) {
        // Numerical reference.
        end = ++begin

        // The behavior further depends on the next character.
        const following = value.charCodeAt(end)

        if (following === 88 /* `X` */ || following === 120 /* `x` */) {
          // ASCII hexadecimal digits.
          type = 'hexadecimal'
          end = ++begin
        } else {
          // ASCII decimal digits.
          type = 'decimal'
        }
      } else {
        // Named reference.
        type = 'named'
      }

      let characterReferenceCharacters = ''
      let characterReference = ''
      let characters = ''
      // Each type of character reference accepts different characters.
      // This test is used to detect whether a reference has ended (as the semicolon
      // is not strictly needed).
      const test =
        type === 'named'
          ? isAlphanumerical
          : type === 'decimal'
            ? isDecimal
            : isHexadecimal

      end--

      while (++end <= value.length) {
        const following = value.charCodeAt(end)

        if (!test(following)) {
          break
        }

        characters += String.fromCharCode(following)

        // Check if we can match a legacy named reference.
        // If so, we cache that as the last viable named reference.
        // This ensures we do not need to walk backwards later.
        if (type === 'named' && characterEntitiesLegacy.includes(characters)) {
          characterReferenceCharacters = characters
          // @ts-expect-error: always able to decode.
          characterReference = decodeNamedCharacterReference(characters)
        }
      }

      let terminated = value.charCodeAt(end) === 59 /* `;` */

      if (terminated) {
        end++

        const namedReference =
          type === 'named' ? decodeNamedCharacterReference(characters) : false

        if (namedReference) {
          characterReferenceCharacters = characters
          characterReference = namedReference
        }
      }

      let diff = 1 + end - start
      let reference = ''

      if (!terminated && settings.nonTerminated === false) {
        // Empty.
      } else if (!characters) {
        // An empty (possible) reference is valid, unless it’s numeric (thus an
        // ampersand followed by an octothorp).
        if (type !== 'named') {
          warning(4 /* Empty (numeric) */, diff)
        }
      } else if (type === 'named') {
        // An ampersand followed by anything unknown, and not terminated, is
        // invalid.
        if (terminated && !characterReference) {
          warning(5 /* Unknown (named) */, 1)
        } else {
          // If there’s something after an named reference which is not known,
          // cap the reference.
          if (characterReferenceCharacters !== characters) {
            end = begin + characterReferenceCharacters.length
            diff = 1 + end - begin
            terminated = false
          }

          // If the reference is not terminated, warn.
          if (!terminated) {
            const reason = characterReferenceCharacters
              ? 1 /* Non terminated (named) */
              : 3 /* Empty (named) */

            if (settings.attribute) {
              const following = value.charCodeAt(end)

              if (following === 61 /* `=` */) {
                warning(reason, diff)
                characterReference = ''
              } else if (isAlphanumerical(following)) {
                characterReference = ''
              } else {
                warning(reason, diff)
              }
            } else {
              warning(reason, diff)
            }
          }
        }

        reference = characterReference
      } else {
        if (!terminated) {
          // All nonterminated numeric references are not rendered, and emit a
          // warning.
          warning(2 /* Non terminated (numeric) */, diff)
        }

        // When terminated and numerical, parse as either hexadecimal or
        // decimal.
        let referenceCode = Number.parseInt(
          characters,
          type === 'hexadecimal' ? 16 : 10
        )

        // Emit a warning when the parsed number is prohibited, and replace with
        // replacement character.
        if (prohibited(referenceCode)) {
          warning(7 /* Prohibited (numeric) */, diff)
          reference = String.fromCharCode(65533 /* `�` */)
        } else if (referenceCode in characterReferenceInvalid) {
          // Emit a warning when the parsed number is disallowed, and replace by
          // an alternative.
          warning(6 /* Disallowed (numeric) */, diff)
          reference = characterReferenceInvalid[referenceCode]
        } else {
          // Parse the number.
          let output = ''

          // Emit a warning when the parsed number should not be used.
          if (disallowed(referenceCode)) {
            warning(6 /* Disallowed (numeric) */, diff)
          }

          // Serialize the number.
          if (referenceCode > 0xffff) {
            referenceCode -= 0x10000
            output += String.fromCharCode(
              (referenceCode >>> (10 & 0x3ff)) | 0xd800
            )
            referenceCode = 0xdc00 | (referenceCode & 0x3ff)
          }

          reference = output + String.fromCharCode(referenceCode)
        }
      }

      // Found it!
      // First eat the queued characters as normal text, then eat a reference.
      if (reference) {
        flush()

        previous = now()
        index = end - 1
        column += end - start + 1
        result.push(reference)
        const next = now()
        next.offset++

        if (settings.reference) {
          settings.reference.call(
            settings.referenceContext || undefined,
            reference,
            {start: previous, end: next},
            value.slice(start - 1, end)
          )
        }

        previous = next
      } else {
        // If we could not find a reference, queue the checked characters (as
        // normal characters), and move the pointer to their end.
        // This is possible because we can be certain neither newlines nor
        // ampersands are included.
        characters = value.slice(start - 1, end)
        queue += characters
        column += characters.length
        index = end - 1
      }
    } else {
      // Handle anything other than an ampersand, including newlines and EOF.
      if (character === 10 /* `\n` */) {
        line++
        lines++
        column = 0
      }

      if (Number.isNaN(character)) {
        flush()
      } else {
        queue += String.fromCharCode(character)
        column++
      }
    }
  }

  // Return the reduced nodes.
  return result.join('')

  // Get current position.
  function now() {
    return {
      line,
      column,
      offset: index + ((point ? point.offset : 0) || 0)
    }
  }

  /**
   * Handle the warning.
   *
   * @param {1|2|3|4|5|6|7} code
   * @param {number} offset
   */
  function warning(code, offset) {
    /** @type {ReturnType<now>} */
    let position

    if (settings.warning) {
      position = now()
      position.column += offset
      position.offset += offset

      settings.warning.call(
        settings.warningContext || undefined,
        messages[code],
        position,
        code
      )
    }
  }

  /**
   * Flush `queue` (normal text).
   * Macro invoked before each reference and at the end of `value`.
   * Does nothing when `queue` is empty.
   */
  function flush() {
    if (queue) {
      result.push(queue)

      if (settings.text) {
        settings.text.call(settings.textContext || undefined, queue, {
          start: previous,
          end: now()
        })
      }

      queue = ''
    }
  }
}

/**
 * Check if `character` is outside the permissible unicode range.
 *
 * @param {number} code
 * @returns {boolean}
 */
function prohibited(code) {
  return (code >= 0xd800 && code <= 0xdfff) || code > 0x10ffff
}

/**
 * Check if `character` is disallowed.
 *
 * @param {number} code
 * @returns {boolean}
 */
function disallowed(code) {
  return (
    (code >= 0x0001 && code <= 0x0008) ||
    code === 0x000b ||
    (code >= 0x000d && code <= 0x001f) ||
    (code >= 0x007f && code <= 0x009f) ||
    (code >= 0xfdd0 && code <= 0xfdef) ||
    (code & 0xffff) === 0xffff ||
    (code & 0xffff) === 0xfffe
  )
}
{
  "name": "parse-entities",
  "version": "4.0.2",
  "description": "Parse HTML character references",
  "license": "MIT",
  "keywords": [
    "parse",
    "html",
    "character",
    "reference",
    "entity",
    "entities"
  ],
  "repository": "wooorm/parse-entities",
  "bugs": "https://github.com/wooorm/parse-entities/issues",
  "funding": {
    "type": "github",
    "url": "https://github.com/sponsors/wooorm"
  },
  "author": "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)",
  "contributors": [
    "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)"
  ],
  "sideEffects": false,
  "type": "module",
  "main": "index.js",
  "types": "index.d.ts",
  "files": [
    "lib/",
    "index.d.ts",
    "index.js"
  ],
  "dependencies": {
    "@types/unist": "^2.0.0",
    "character-entities-legacy": "^3.0.0",
    "character-reference-invalid": "^2.0.0",
    "decode-named-character-reference": "^1.0.0",
    "is-alphanumerical": "^2.0.0",
    "is-decimal": "^2.0.0",
    "is-hexadecimal": "^2.0.0"
  },
  "devDependencies": {
    "@types/node": "^22.0.0",
    "c8": "^10.0.0",
    "prettier": "^3.0.0",
    "remark-cli": "^12.0.0",
    "remark-preset-wooorm": "^10.0.0",
    "type-coverage": "^2.0.0",
    "typescript": "^5.0.0",
    "xo": "^0.60.0"
  },
  "scripts": {
    "prepack": "npm run build && npm run format",
    "build": "tsc --build --clean && tsc --build && type-coverage",
    "format": "remark . -qfo && prettier . -w --log-level warn && xo --fix",
    "test-api": "node --conditions development test.js",
    "test-coverage": "c8 --check-coverage --100 --reporter lcov npm run test-api",
    "test": "npm run build && npm run format && npm run test-coverage"
  },
  "prettier": {
    "tabWidth": 2,
    "useTabs": false,
    "singleQuote": true,
    "bracketSpacing": false,
    "semi": false,
    "trailingComma": "none"
  },
  "xo": {
    "prettier": true,
    "rules": {
      "@typescript-eslint/consistent-type-definitions": "off",
      "@typescript-eslint/ban-types": "off",
      "complexity": "off",
      "max-depth": "off",
      "no-bitwise": "off",
      "unicorn/numeric-separators-style": "off",
      "unicorn/prefer-code-point": "off"
    }
  },
  "remarkConfig": {
    "plugins": [
      "remark-preset-wooorm"
    ]
  },
  "typeCoverage": {
    "atLeast": 100,
    "detail": true,
    "strict": true,
    "ignoreCatch": true
  }
}
/**
 * @typedef {import('./lib/index.js').LightOptions} LightOptions
 * @typedef {import('./lib/index.js').Options} Options
 */

export * from './lib/index.js'
/**
 * List of legacy (that don’t need a trailing `;`) named references which could,
 * depending on what follows them, turn into a different meaning
 *
 * @type {Array<string>}
 */
export const dangerous = [
  'cent',
  'copy',
  'divide',
  'gt',
  'lt',
  'not',
  'para',
  'times'
]
/**
 * @typedef CoreOptions
 * @property {ReadonlyArray<string>} [subset=[]]
 *   Whether to only escape the given subset of characters.
 * @property {boolean} [escapeOnly=false]
 *   Whether to only escape possibly dangerous characters.
 *   Those characters are `"`, `&`, `'`, `<`, `>`, and `` ` ``.
 *
 * @typedef FormatOptions
 * @property {(code: number, next: number, options: CoreWithFormatOptions) => string} format
 *   Format strategy.
 *
 * @typedef {CoreOptions & FormatOptions & import('./util/format-smart.js').FormatSmartOptions} CoreWithFormatOptions
 */

const defaultSubsetRegex = /["&'<>`]/g
const surrogatePairsRegex = /[\uD800-\uDBFF][\uDC00-\uDFFF]/g
const controlCharactersRegex =
  // eslint-disable-next-line no-control-regex, unicorn/no-hex-escape
  /[\x01-\t\v\f\x0E-\x1F\x7F\x81\x8D\x8F\x90\x9D\xA0-\uFFFF]/g
const regexEscapeRegex = /[|\\{}()[\]^$+*?.]/g

/** @type {WeakMap<ReadonlyArray<string>, RegExp>} */
const subsetToRegexCache = new WeakMap()

/**
 * Encode certain characters in `value`.
 *
 * @param {string} value
 * @param {CoreWithFormatOptions} options
 * @returns {string}
 */
export function core(value, options) {
  value = value.replace(
    options.subset
      ? charactersToExpressionCached(options.subset)
      : defaultSubsetRegex,
    basic
  )

  if (options.subset || options.escapeOnly) {
    return value
  }

  return (
    value
      // Surrogate pairs.
      .replace(surrogatePairsRegex, surrogate)
      // BMP control characters (C0 except for LF, CR, SP; DEL; and some more
      // non-ASCII ones).
      .replace(controlCharactersRegex, basic)
  )

  /**
   * @param {string} pair
   * @param {number} index
   * @param {string} all
   */
  function surrogate(pair, index, all) {
    return options.format(
      (pair.charCodeAt(0) - 0xd800) * 0x400 +
        pair.charCodeAt(1) -
        0xdc00 +
        0x10000,
      all.charCodeAt(index + 2),
      options
    )
  }

  /**
   * @param {string} character
   * @param {number} index
   * @param {string} all
   */
  function basic(character, index, all) {
    return options.format(
      character.charCodeAt(0),
      all.charCodeAt(index + 1),
      options
    )
  }
}

/**
 * A wrapper function that caches the result of `charactersToExpression` with a WeakMap.
 * This can improve performance when tooling calls `charactersToExpression` repeatedly
 * with the same subset.
 *
 * @param {ReadonlyArray<string>} subset
 * @returns {RegExp}
 */
function charactersToExpressionCached(subset) {
  let cached = subsetToRegexCache.get(subset)

  if (!cached) {
    cached = charactersToExpression(subset)
    subsetToRegexCache.set(subset, cached)
  }

  return cached
}

/**
 * @param {ReadonlyArray<string>} subset
 * @returns {RegExp}
 */
function charactersToExpression(subset) {
  /** @type {Array<string>} */
  const groups = []
  let index = -1

  while (++index < subset.length) {
    groups.push(subset[index].replace(regexEscapeRegex, '\\$&'))
  }

  return new RegExp('(?:' + groups.join('|') + ')', 'g')
}
/**
 * @typedef {import('./core.js').CoreOptions & import('./util/format-smart.js').FormatSmartOptions} Options
 * @typedef {import('./core.js').CoreOptions} LightOptions
 */

import {core} from './core.js'
import {formatSmart} from './util/format-smart.js'
import {formatBasic} from './util/format-basic.js'

/**
 * Encode special characters in `value`.
 *
 * @param {string} value
 *   Value to encode.
 * @param {Options} [options]
 *   Configuration.
 * @returns {string}
 *   Encoded value.
 */
export function stringifyEntities(value, options) {
  return core(value, Object.assign({format: formatSmart}, options))
}

/**
 * Encode special characters in `value` as hexadecimals.
 *
 * @param {string} value
 *   Value to encode.
 * @param {LightOptions} [options]
 *   Configuration.
 * @returns {string}
 *   Encoded value.
 */
export function stringifyEntitiesLight(value, options) {
  return core(value, Object.assign({format: formatBasic}, options))
}
/**
 * The smallest way to encode a character.
 *
 * @param {number} code
 * @returns {string}
 */
export function formatBasic(code) {
  return '&#x' + code.toString(16).toUpperCase() + ';'
}
/**
 * @typedef FormatSmartOptions
 * @property {boolean} [useNamedReferences=false]
 *   Prefer named character references (`&amp;`) where possible.
 * @property {boolean} [useShortestReferences=false]
 *   Prefer the shortest possible reference, if that results in less bytes.
 *   **Note**: `useNamedReferences` can be omitted when using `useShortestReferences`.
 * @property {boolean} [omitOptionalSemicolons=false]
 *   Whether to omit semicolons when possible.
 *   **Note**: This creates what HTML calls “parse errors” but is otherwise still valid HTML — don’t use this except when building a minifier.
 *   Omitting semicolons is possible for certain named and numeric references in some cases.
 * @property {boolean} [attribute=false]
 *   Create character references which don’t fail in attributes.
 *   **Note**: `attribute` only applies when operating dangerously with
 *   `omitOptionalSemicolons: true`.
 */

import {toHexadecimal} from './to-hexadecimal.js'
import {toDecimal} from './to-decimal.js'
import {toNamed} from './to-named.js'

/**
 * Configurable ways to encode a character yielding pretty or small results.
 *
 * @param {number} code
 * @param {number} next
 * @param {FormatSmartOptions} options
 * @returns {string}
 */
export function formatSmart(code, next, options) {
  let numeric = toHexadecimal(code, next, options.omitOptionalSemicolons)
  /** @type {string|undefined} */
  let named

  if (options.useNamedReferences || options.useShortestReferences) {
    named = toNamed(
      code,
      next,
      options.omitOptionalSemicolons,
      options.attribute
    )
  }

  // Use the shortest numeric reference when requested.
  // A simple algorithm would use decimal for all code points under 100, as
  // those are shorter than hexadecimal:
  //
  // * `&#99;` vs `&#x63;` (decimal shorter)
  // * `&#100;` vs `&#x64;` (equal)
  //
  // However, because we take `next` into consideration when `omit` is used,
  // And it would be possible that decimals are shorter on bigger values as
  // well if `next` is hexadecimal but not decimal, we instead compare both.
  if (
    (options.useShortestReferences || !named) &&
    options.useShortestReferences
  ) {
    const decimal = toDecimal(code, next, options.omitOptionalSemicolons)

    if (decimal.length < numeric.length) {
      numeric = decimal
    }
  }

  return named &&
    (!options.useShortestReferences || named.length < numeric.length)
    ? named
    : numeric
}
const decimalRegex = /\d/

/**
 * Configurable ways to encode characters as decimal references.
 *
 * @param {number} code
 * @param {number} next
 * @param {boolean|undefined} omit
 * @returns {string}
 */
export function toDecimal(code, next, omit) {
  const value = '&#' + String(code)
  return omit && next && !decimalRegex.test(String.fromCharCode(next))
    ? value
    : value + ';'
}
const hexadecimalRegex = /[\dA-Fa-f]/

/**
 * Configurable ways to encode characters as hexadecimal references.
 *
 * @param {number} code
 * @param {number} next
 * @param {boolean|undefined} omit
 * @returns {string}
 */
export function toHexadecimal(code, next, omit) {
  const value = '&#x' + code.toString(16).toUpperCase()
  return omit && next && !hexadecimalRegex.test(String.fromCharCode(next))
    ? value
    : value + ';'
}
import {characterEntitiesLegacy} from 'character-entities-legacy'
import {characterEntitiesHtml4} from 'character-entities-html4'
import {dangerous} from '../constant/dangerous.js'

const own = {}.hasOwnProperty

/**
 * `characterEntitiesHtml4` but inverted.
 *
 * @type {Record<string, string>}
 */
const characters = {}

/** @type {string} */
let key

for (key in characterEntitiesHtml4) {
  if (own.call(characterEntitiesHtml4, key)) {
    characters[characterEntitiesHtml4[key]] = key
  }
}

const notAlphanumericRegex = /[^\dA-Za-z]/

/**
 * Configurable ways to encode characters as named references.
 *
 * @param {number} code
 * @param {number} next
 * @param {boolean|undefined} omit
 * @param {boolean|undefined} attribute
 * @returns {string}
 */
export function toNamed(code, next, omit, attribute) {
  const character = String.fromCharCode(code)

  if (own.call(characters, character)) {
    const name = characters[character]
    const value = '&' + name

    if (
      omit &&
      characterEntitiesLegacy.includes(name) &&
      !dangerous.includes(name) &&
      (!attribute ||
        (next &&
          next !== 61 /* `=` */ &&
          notAlphanumericRegex.test(String.fromCharCode(next))))
    ) {
      return value
    }

    return value + ';'
  }

  return ''
}
{
  "name": "stringify-entities",
  "version": "4.0.4",
  "description": "Serialize (encode) HTML character references",
  "license": "MIT",
  "keywords": [
    "stringify",
    "encode",
    "escape",
    "html",
    "character",
    "reference",
    "entity",
    "entities"
  ],
  "repository": "wooorm/stringify-entities",
  "bugs": "https://github.com/wooorm/stringify-entities/issues",
  "funding": {
    "type": "github",
    "url": "https://github.com/sponsors/wooorm"
  },
  "author": "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)",
  "contributors": [
    "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)"
  ],
  "sideEffects": false,
  "type": "module",
  "main": "index.js",
  "types": "index.d.ts",
  "files": [
    "lib/",
    "index.d.ts",
    "index.js"
  ],
  "dependencies": {
    "character-entities-html4": "^2.0.0",
    "character-entities-legacy": "^3.0.0"
  },
  "devDependencies": {
    "@types/node": "^20.0.0",
    "c8": "^9.0.0",
    "character-entities": "^2.0.0",
    "prettier": "^3.0.0",
    "remark-cli": "^11.0.0",
    "remark-preset-wooorm": "^9.0.0",
    "type-coverage": "^2.0.0",
    "typescript": "^5.0.0",
    "xo": "^0.58.0"
  },
  "scripts": {
    "prepack": "npm run build && npm run format",
    "generate": "node --conditions development build.js",
    "build": "tsc --build --clean && tsc --build && type-coverage",
    "format": "remark . -qfo && prettier . -w --log-level warn && xo --fix",
    "test-api": "node --conditions development test.js",
    "test-coverage": "c8 --check-coverage --100 --reporter lcov npm run test-api",
    "test": "npm run generate && npm run build && npm run format && npm run test-coverage"
  },
  "prettier": {
    "tabWidth": 2,
    "useTabs": false,
    "singleQuote": true,
    "bracketSpacing": false,
    "semi": false,
    "trailingComma": "none"
  },
  "xo": {
    "prettier": true,
    "rules": {
      "unicorn/prefer-code-point": "off",
      "unicorn/prefer-string-replace-all": "off",
      "unicorn/numeric-separators-style": "off"
    }
  },
  "remarkConfig": {
    "plugins": [
      "preset-wooorm"
    ]
  },
  "typeCoverage": {
    "atLeast": 100,
    "detail": true,
    "strict": true,
    "ignoreCatch": true
  }
}
/**
 * @typedef {import('./lib/index.js').Check} Check
 * @typedef {import('./lib/index.js').Test} Test
 * @typedef {import('./lib/index.js').TestFunction} TestFunction
 */

export {is, convert} from './lib/index.js'
/**
 * @import {Node, Parent} from 'unist'
 */

/**
 * @template Fn
 * @template Fallback
 * @typedef {Fn extends (value: any) => value is infer Thing ? Thing : Fallback} Predicate
 */

/**
 * @callback Check
 *   Check that an arbitrary value is a node.
 * @param {unknown} this
 *   The given context.
 * @param {unknown} [node]
 *   Anything (typically a node).
 * @param {number | null | undefined} [index]
 *   The node’s position in its parent.
 * @param {Parent | null | undefined} [parent]
 *   The node’s parent.
 * @returns {boolean}
 *   Whether this is a node and passes a test.
 *
 * @typedef {Record<string, unknown> | Node} Props
 *   Object to check for equivalence.
 *
 *   Note: `Node` is included as it is common but is not indexable.
 *
 * @typedef {Array<Props | TestFunction | string> | ReadonlyArray<Props | TestFunction | string> | Props | TestFunction | string | null | undefined} Test
 *   Check for an arbitrary node.
 *
 * @callback TestFunction
 *   Check if a node passes a test.
 * @param {unknown} this
 *   The given context.
 * @param {Node} node
 *   A node.
 * @param {number | undefined} [index]
 *   The node’s position in its parent.
 * @param {Parent | undefined} [parent]
 *   The node’s parent.
 * @returns {boolean | undefined | void}
 *   Whether this node passes the test.
 *
 *   Note: `void` is included until TS sees no return as `undefined`.
 */

/**
 * Check if `node` is a `Node` and whether it passes the given test.
 *
 * @param {unknown} node
 *   Thing to check, typically `Node`.
 * @param {Test} test
 *   A check for a specific node.
 * @param {number | null | undefined} index
 *   The node’s position in its parent.
 * @param {Parent | null | undefined} parent
 *   The node’s parent.
 * @param {unknown} context
 *   Context object (`this`) to pass to `test` functions.
 * @returns {boolean}
 *   Whether `node` is a node and passes a test.
 */
export const is =
  // Note: overloads in JSDoc can’t yet use different `@template`s.
  /**
   * @type {(
   *   (<Condition extends ReadonlyArray<string>>(node: unknown, test: Condition, index?: number | null | undefined, parent?: Parent | null | undefined, context?: unknown) => node is Node & {type: Condition[number]}) &
   *   (<Condition extends Array<string>>(node: unknown, test: Condition, index?: number | null | undefined, parent?: Parent | null | undefined, context?: unknown) => node is Node & {type: Condition[number]}) &
   *   (<Condition extends string>(node: unknown, test: Condition, index?: number | null | undefined, parent?: Parent | null | undefined, context?: unknown) => node is Node & {type: Condition}) &
   *   (<Condition extends Props>(node: unknown, test: Condition, index?: number | null | undefined, parent?: Parent | null | undefined, context?: unknown) => node is Node & Condition) &
   *   (<Condition extends TestFunction>(node: unknown, test: Condition, index?: number | null | undefined, parent?: Parent | null | undefined, context?: unknown) => node is Node & Predicate<Condition, Node>) &
   *   ((node?: null | undefined) => false) &
   *   ((node: unknown, test?: null | undefined, index?: number | null | undefined, parent?: Parent | null | undefined, context?: unknown) => node is Node) &
   *   ((node: unknown, test?: Test, index?: number | null | undefined, parent?: Parent | null | undefined, context?: unknown) => boolean)
   * )}
   */
  (
    /**
     * @param {unknown} [node]
     * @param {Test} [test]
     * @param {number | null | undefined} [index]
     * @param {Parent | null | undefined} [parent]
     * @param {unknown} [context]
     * @returns {boolean}
     */
    // eslint-disable-next-line max-params
    function (node, test, index, parent, context) {
      const check = convert(test)

      if (
        index !== undefined &&
        index !== null &&
        (typeof index !== 'number' ||
          index < 0 ||
          index === Number.POSITIVE_INFINITY)
      ) {
        throw new Error('Expected positive finite index')
      }

      if (
        parent !== undefined &&
        parent !== null &&
        (!is(parent) || !parent.children)
      ) {
        throw new Error('Expected parent node')
      }

      if (
        (parent === undefined || parent === null) !==
        (index === undefined || index === null)
      ) {
        throw new Error('Expected both parent and index')
      }

      return looksLikeANode(node)
        ? check.call(context, node, index, parent)
        : false
    }
  )

/**
 * Generate an assertion from a test.
 *
 * Useful if you’re going to test many nodes, for example when creating a
 * utility where something else passes a compatible test.
 *
 * The created function is a bit faster because it expects valid input only:
 * a `node`, `index`, and `parent`.
 *
 * @param {Test} test
 *   *   when nullish, checks if `node` is a `Node`.
 *   *   when `string`, works like passing `(node) => node.type === test`.
 *   *   when `function` checks if function passed the node is true.
 *   *   when `object`, checks that all keys in test are in node, and that they have (strictly) equal values.
 *   *   when `array`, checks if any one of the subtests pass.
 * @returns {Check}
 *   An assertion.
 */
export const convert =
  // Note: overloads in JSDoc can’t yet use different `@template`s.
  /**
   * @type {(
   *   (<Condition extends string>(test: Condition) => (node: unknown, index?: number | null | undefined, parent?: Parent | null | undefined, context?: unknown) => node is Node & {type: Condition}) &
   *   (<Condition extends Props>(test: Condition) => (node: unknown, index?: number | null | undefined, parent?: Parent | null | undefined, context?: unknown) => node is Node & Condition) &
   *   (<Condition extends TestFunction>(test: Condition) => (node: unknown, index?: number | null | undefined, parent?: Parent | null | undefined, context?: unknown) => node is Node & Predicate<Condition, Node>) &
   *   ((test?: null | undefined) => (node?: unknown, index?: number | null | undefined, parent?: Parent | null | undefined, context?: unknown) => node is Node) &
   *   ((test?: Test) => Check)
   * )}
   */
  (
    /**
     * @param {Test} [test]
     * @returns {Check}
     */
    function (test) {
      if (test === null || test === undefined) {
        return ok
      }

      if (typeof test === 'function') {
        return castFactory(test)
      }

      if (typeof test === 'object') {
        return Array.isArray(test)
          ? anyFactory(test)
          : // Cast because `ReadonlyArray` goes into the above but `isArray`
            // narrows to `Array`.
            propertiesFactory(/** @type {Props} */ (test))
      }

      if (typeof test === 'string') {
        return typeFactory(test)
      }

      throw new Error('Expected function, string, or object as test')
    }
  )

/**
 * @param {Array<Props | TestFunction | string>} tests
 * @returns {Check}
 */
function anyFactory(tests) {
  /** @type {Array<Check>} */
  const checks = []
  let index = -1

  while (++index < tests.length) {
    checks[index] = convert(tests[index])
  }

  return castFactory(any)

  /**
   * @this {unknown}
   * @type {TestFunction}
   */
  function any(...parameters) {
    let index = -1

    while (++index < checks.length) {
      if (checks[index].apply(this, parameters)) return true
    }

    return false
  }
}

/**
 * Turn an object into a test for a node with a certain fields.
 *
 * @param {Props} check
 * @returns {Check}
 */
function propertiesFactory(check) {
  const checkAsRecord = /** @type {Record<string, unknown>} */ (check)

  return castFactory(all)

  /**
   * @param {Node} node
   * @returns {boolean}
   */
  function all(node) {
    const nodeAsRecord = /** @type {Record<string, unknown>} */ (
      /** @type {unknown} */ (node)
    )

    /** @type {string} */
    let key

    for (key in check) {
      if (nodeAsRecord[key] !== checkAsRecord[key]) return false
    }

    return true
  }
}

/**
 * Turn a string into a test for a node with a certain type.
 *
 * @param {string} check
 * @returns {Check}
 */
function typeFactory(check) {
  return castFactory(type)

  /**
   * @param {Node} node
   */
  function type(node) {
    return node && node.type === check
  }
}

/**
 * Turn a custom test into a test for a node that passes that test.
 *
 * @param {TestFunction} testFunction
 * @returns {Check}
 */
function castFactory(testFunction) {
  return check

  /**
   * @this {unknown}
   * @type {Check}
   */
  function check(value, index, parent) {
    return Boolean(
      looksLikeANode(value) &&
        testFunction.call(
          this,
          value,
          typeof index === 'number' ? index : undefined,
          parent || undefined
        )
    )
  }
}

function ok() {
  return true
}

/**
 * @param {unknown} value
 * @returns {value is Node}
 */
function looksLikeANode(value) {
  return value !== null && typeof value === 'object' && 'type' in value
}
{
  "name": "unist-util-is",
  "version": "6.0.1",
  "description": "unist utility to check if a node passes a test",
  "license": "MIT",
  "keywords": [
    "unist",
    "unist-util",
    "util",
    "utility",
    "tree",
    "node",
    "is",
    "equal",
    "check",
    "test",
    "type"
  ],
  "repository": "syntax-tree/unist-util-is",
  "bugs": "https://github.com/syntax-tree/unist-util-is/issues",
  "funding": {
    "type": "opencollective",
    "url": "https://opencollective.com/unified"
  },
  "author": "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)",
  "contributors": [
    "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)",
    "Christian Murphy <christian.murphy.42@gmail.com>",
    "Lucas Brandstaetter <lucas@brandstaetter.tech> (https://github.com/Roang-zero1)"
  ],
  "sideEffects": false,
  "type": "module",
  "exports": "./index.js",
  "files": [
    "lib/",
    "index.d.ts.map",
    "index.d.ts",
    "index.js"
  ],
  "dependencies": {
    "@types/unist": "^3.0.0"
  },
  "devDependencies": {
    "@types/mdast": "^4.0.0",
    "@types/node": "^24.0.0",
    "c8": "^10.0.0",
    "prettier": "^3.0.0",
    "remark-cli": "^12.0.0",
    "remark-preset-wooorm": "^11.0.0",
    "tsd": "^0.33.0",
    "type-coverage": "^2.0.0",
    "typescript": "^5.0.0",
    "xo": "^0.58.0"
  },
  "scripts": {
    "prepack": "npm run build && npm run format",
    "build": "tsc --build --clean && tsc --build && tsd && type-coverage",
    "format": "remark . -qfo && prettier . -w --log-level warn && xo --fix",
    "test-api": "node --conditions development test/index.js",
    "test-coverage": "c8 --100 --reporter lcov npm run test-api",
    "test": "npm run build && npm run format && npm run test-coverage"
  },
  "prettier": {
    "bracketSpacing": false,
    "semi": false,
    "singleQuote": true,
    "tabWidth": 2,
    "trailingComma": "none",
    "useTabs": false
  },
  "remarkConfig": {
    "plugins": [
      "remark-preset-wooorm"
    ]
  },
  "typeCoverage": {
    "atLeast": 100,
    "detail": true,
    "#": "needed `any`s",
    "ignoreFiles": [
      "lib/index.d.ts"
    ],
    "ignoreCatch": true,
    "strict": true
  },
  "xo": {
    "overrides": [
      {
        "files": [
          "**/*.ts"
        ],
        "rules": {
          "@typescript-eslint/consistent-type-definitions": "off",
          "@typescript-eslint/no-unnecessary-type-arguments": "off",
          "@typescript-eslint/no-unsafe-argument": "off",
          "@typescript-eslint/no-unsafe-assignment": "off",
          "import/no-extraneous-dependencies": "off"
        }
      }
    ],
    "prettier": true
  }
}
export {stringifyPosition} from './lib/index.js'
/**
 * @typedef {import('unist').Node} Node
 * @typedef {import('unist').Point} Point
 * @typedef {import('unist').Position} Position
 */

/**
 * @typedef NodeLike
 * @property {string} type
 * @property {PositionLike | null | undefined} [position]
 *
 * @typedef PointLike
 * @property {number | null | undefined} [line]
 * @property {number | null | undefined} [column]
 * @property {number | null | undefined} [offset]
 *
 * @typedef PositionLike
 * @property {PointLike | null | undefined} [start]
 * @property {PointLike | null | undefined} [end]
 */

/**
 * Serialize the positional info of a point, position (start and end points),
 * or node.
 *
 * @param {Node | NodeLike | Point | PointLike | Position | PositionLike | null | undefined} [value]
 *   Node, position, or point.
 * @returns {string}
 *   Pretty printed positional info of a node (`string`).
 *
 *   In the format of a range `ls:cs-le:ce` (when given `node` or `position`)
 *   or a point `l:c` (when given `point`), where `l` stands for line, `c` for
 *   column, `s` for `start`, and `e` for end.
 *   An empty string (`''`) is returned if the given value is neither `node`,
 *   `position`, nor `point`.
 */
export function stringifyPosition(value) {
  // Nothing.
  if (!value || typeof value !== 'object') {
    return ''
  }

  // Node.
  if ('position' in value || 'type' in value) {
    return position(value.position)
  }

  // Position.
  if ('start' in value || 'end' in value) {
    return position(value)
  }

  // Point.
  if ('line' in value || 'column' in value) {
    return point(value)
  }

  // ?
  return ''
}

/**
 * @param {Point | PointLike | null | undefined} point
 * @returns {string}
 */
function point(point) {
  return index(point && point.line) + ':' + index(point && point.column)
}

/**
 * @param {Position | PositionLike | null | undefined} pos
 * @returns {string}
 */
function position(pos) {
  return point(pos && pos.start) + '-' + point(pos && pos.end)
}

/**
 * @param {number | null | undefined} value
 * @returns {number}
 */
function index(value) {
  return value && typeof value === 'number' ? value : 1
}
{
  "name": "unist-util-stringify-position",
  "version": "4.0.0",
  "description": "unist utility to serialize a node, position, or point as a human readable location",
  "license": "MIT",
  "keywords": [
    "unist",
    "unist-util",
    "util",
    "utility",
    "position",
    "location",
    "point",
    "node",
    "stringify",
    "tostring"
  ],
  "repository": "syntax-tree/unist-util-stringify-position",
  "bugs": "https://github.com/syntax-tree/unist-util-stringify-position/issues",
  "funding": {
    "type": "opencollective",
    "url": "https://opencollective.com/unified"
  },
  "author": "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)",
  "contributors": [
    "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)"
  ],
  "sideEffects": false,
  "type": "module",
  "exports": "./index.js",
  "files": [
    "lib/",
    "index.d.ts",
    "index.js"
  ],
  "dependencies": {
    "@types/unist": "^3.0.0"
  },
  "devDependencies": {
    "@types/mdast": "^4.0.0",
    "@types/node": "^20.0.0",
    "c8": "^8.0.0",
    "prettier": "^2.0.0",
    "remark-cli": "^11.0.0",
    "remark-preset-wooorm": "^9.0.0",
    "type-coverage": "^2.0.0",
    "typescript": "^5.0.0",
    "xo": "^0.54.0"
  },
  "scripts": {
    "prepack": "npm run build && npm run format",
    "build": "tsc --build --clean && tsc --build && type-coverage",
    "format": "remark . -qfo && prettier . -w --loglevel warn && xo --fix",
    "test-api": "node --conditions development test.js",
    "test-coverage": "c8 --100 --reporter lcov npm run test-api",
    "test": "npm run build && npm run format && npm run test-coverage"
  },
  "prettier": {
    "bracketSpacing": false,
    "semi": false,
    "singleQuote": true,
    "tabWidth": 2,
    "trailingComma": "none",
    "useTabs": false
  },
  "remarkConfig": {
    "plugins": [
      "remark-preset-wooorm"
    ]
  },
  "typeCoverage": {
    "atLeast": 100,
    "detail": true,
    "ignoreCatch": true,
    "strict": true
  },
  "xo": {
    "prettier": true
  }
}
// Note: types exported from `index.d.ts`
export {CONTINUE, EXIT, SKIP, visitParents} from './lib/index.js'
/**
 * @param {string} d
 * @returns {string}
 */
export function color(d) {
  return '\u001B[33m' + d + '\u001B[39m'
}
/**
 * @import {Node as UnistNode, Parent as UnistParent} from 'unist'
 */

/**
 * @typedef {Exclude<import('unist-util-is').Test, undefined> | undefined} Test
 *   Test from `unist-util-is`.
 *
 *   Note: we have remove and add `undefined`, because otherwise when generating
 *   automatic `.d.ts` files, TS tries to flatten paths from a local perspective,
 *   which doesn’t work when publishing on npm.
 */

/**
 * @typedef {(
 *   Fn extends (value: any) => value is infer Thing
 *   ? Thing
 *   : Fallback
 * )} Predicate
 *   Get the value of a type guard `Fn`.
 * @template Fn
 *   Value; typically function that is a type guard (such as `(x): x is Y`).
 * @template Fallback
 *   Value to yield if `Fn` is not a type guard.
 */

/**
 * @typedef {(
 *   Check extends null | undefined // No test.
 *   ? Value
 *   : Value extends {type: Check} // String (type) test.
 *   ? Value
 *   : Value extends Check // Partial test.
 *   ? Value
 *   : Check extends Function // Function test.
 *   ? Predicate<Check, Value> extends Value
 *     ? Predicate<Check, Value>
 *     : never
 *   : never // Some other test?
 * )} MatchesOne
 *   Check whether a node matches a primitive check in the type system.
 * @template Value
 *   Value; typically unist `Node`.
 * @template Check
 *   Value; typically `unist-util-is`-compatible test, but not arrays.
 */

/**
 * @typedef {(
 *   Check extends ReadonlyArray<infer T>
 *   ? MatchesOne<Value, T>
 *   : Check extends Array<infer T>
 *   ? MatchesOne<Value, T>
 *   : MatchesOne<Value, Check>
 * )} Matches
 *   Check whether a node matches a check in the type system.
 * @template Value
 *   Value; typically unist `Node`.
 * @template Check
 *   Value; typically `unist-util-is`-compatible test.
 */

/**
 * @typedef {0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10} Uint
 *   Number; capped reasonably.
 */

/**
 * @typedef {I extends 0 ? 1 : I extends 1 ? 2 : I extends 2 ? 3 : I extends 3 ? 4 : I extends 4 ? 5 : I extends 5 ? 6 : I extends 6 ? 7 : I extends 7 ? 8 : I extends 8 ? 9 : 10} Increment
 *   Increment a number in the type system.
 * @template {Uint} [I=0]
 *   Index.
 */

/**
 * @typedef {(
 *   Node extends UnistParent
 *   ? Node extends {children: Array<infer Children>}
 *     ? Child extends Children ? Node : never
 *     : never
 *   : never
 * )} InternalParent
 *   Collect nodes that can be parents of `Child`.
 * @template {UnistNode} Node
 *   All node types in a tree.
 * @template {UnistNode} Child
 *   Node to search for.
 */

/**
 * @typedef {InternalParent<InclusiveDescendant<Tree>, Child>} Parent
 *   Collect nodes in `Tree` that can be parents of `Child`.
 * @template {UnistNode} Tree
 *   All node types in a tree.
 * @template {UnistNode} Child
 *   Node to search for.
 */

/**
 * @typedef {(
 *   Depth extends Max
 *   ? never
 *   :
 *     | InternalParent<Node, Child>
 *     | InternalAncestor<Node, InternalParent<Node, Child>, Max, Increment<Depth>>
 * )} InternalAncestor
 *   Collect nodes in `Tree` that can be ancestors of `Child`.
 * @template {UnistNode} Node
 *   All node types in a tree.
 * @template {UnistNode} Child
 *   Node to search for.
 * @template {Uint} [Max=10]
 *   Max; searches up to this depth.
 * @template {Uint} [Depth=0]
 *   Current depth.
 */

/**
 * @typedef {InternalAncestor<InclusiveDescendant<Tree>, Child>} Ancestor
 *   Collect nodes in `Tree` that can be ancestors of `Child`.
 * @template {UnistNode} Tree
 *   All node types in a tree.
 * @template {UnistNode} Child
 *   Node to search for.
 */

/**
 * @typedef {(
 *   Tree extends UnistParent
 *     ? Depth extends Max
 *       ? Tree
 *       : Tree | InclusiveDescendant<Tree['children'][number], Max, Increment<Depth>>
 *     : Tree
 * )} InclusiveDescendant
 *   Collect all (inclusive) descendants of `Tree`.
 *
 *   > 👉 **Note**: for performance reasons, this seems to be the fastest way to
 *   > recurse without actually running into an infinite loop, which the
 *   > previous version did.
 *   >
 *   > Practically, a max of `2` is typically enough assuming a `Root` is
 *   > passed, but it doesn’t improve performance.
 *   > It gets higher with `List > ListItem > Table > TableRow > TableCell`.
 *   > Using up to `10` doesn’t hurt or help either.
 * @template {UnistNode} Tree
 *   Tree type.
 * @template {Uint} [Max=10]
 *   Max; searches up to this depth.
 * @template {Uint} [Depth=0]
 *   Current depth.
 */

/**
 * @typedef {'skip' | boolean} Action
 *   Union of the action types.
 *
 * @typedef {number} Index
 *   Move to the sibling at `index` next (after node itself is completely
 *   traversed).
 *
 *   Useful if mutating the tree, such as removing the node the visitor is
 *   currently on, or any of its previous siblings.
 *   Results less than 0 or greater than or equal to `children.length` stop
 *   traversing the parent.
 *
 * @typedef {[(Action | null | undefined | void)?, (Index | null | undefined)?]} ActionTuple
 *   List with one or two values, the first an action, the second an index.
 *
 * @typedef {Action | ActionTuple | Index | null | undefined | void} VisitorResult
 *   Any value that can be returned from a visitor.
 */

/**
 * @callback Visitor
 *   Handle a node (matching `test`, if given).
 *
 *   Visitors are free to transform `node`.
 *   They can also transform the parent of node (the last of `ancestors`).
 *
 *   Replacing `node` itself, if `SKIP` is not returned, still causes its
 *   descendants to be walked (which is a bug).
 *
 *   When adding or removing previous siblings of `node` (or next siblings, in
 *   case of reverse), the `Visitor` should return a new `Index` to specify the
 *   sibling to traverse after `node` is traversed.
 *   Adding or removing next siblings of `node` (or previous siblings, in case
 *   of reverse) is handled as expected without needing to return a new `Index`.
 *
 *   Removing the children property of an ancestor still results in them being
 *   traversed.
 * @param {Visited} node
 *   Found node.
 * @param {Array<VisitedParents>} ancestors
 *   Ancestors of `node`.
 * @returns {VisitorResult}
 *   What to do next.
 *
 *   An `Index` is treated as a tuple of `[CONTINUE, Index]`.
 *   An `Action` is treated as a tuple of `[Action]`.
 *
 *   Passing a tuple back only makes sense if the `Action` is `SKIP`.
 *   When the `Action` is `EXIT`, that action can be returned.
 *   When the `Action` is `CONTINUE`, `Index` can be returned.
 * @template {UnistNode} [Visited=UnistNode]
 *   Visited node type.
 * @template {UnistParent} [VisitedParents=UnistParent]
 *   Ancestor type.
 */

/**
 * @typedef {Visitor<Matches<InclusiveDescendant<Tree>, Check>, Ancestor<Tree, Matches<InclusiveDescendant<Tree>, Check>>>} BuildVisitor
 *   Build a typed `Visitor` function from a tree and a test.
 *
 *   It will infer which values are passed as `node` and which as `parents`.
 * @template {UnistNode} [Tree=UnistNode]
 *   Tree type.
 * @template {Test} [Check=Test]
 *   Test type.
 */

import {convert} from 'unist-util-is'
import {color} from 'unist-util-visit-parents/do-not-use-color'

/** @type {Readonly<ActionTuple>} */
const empty = []

/**
 * Continue traversing as normal.
 */
export const CONTINUE = true

/**
 * Stop traversing immediately.
 */
export const EXIT = false

/**
 * Do not traverse this node’s children.
 */
export const SKIP = 'skip'

/**
 * Visit nodes, with ancestral information.
 *
 * This algorithm performs *depth-first* *tree traversal* in *preorder*
 * (**NLR**) or if `reverse` is given, in *reverse preorder* (**NRL**).
 *
 * You can choose for which nodes `visitor` is called by passing a `test`.
 * For complex tests, you should test yourself in `visitor`, as it will be
 * faster and will have improved type information.
 *
 * Walking the tree is an intensive task.
 * Make use of the return values of the visitor when possible.
 * Instead of walking a tree multiple times, walk it once, use `unist-util-is`
 * to check if a node matches, and then perform different operations.
 *
 * You can change the tree.
 * See `Visitor` for more info.
 *
 * @overload
 * @param {Tree} tree
 * @param {Check} check
 * @param {BuildVisitor<Tree, Check>} visitor
 * @param {boolean | null | undefined} [reverse]
 * @returns {undefined}
 *
 * @overload
 * @param {Tree} tree
 * @param {BuildVisitor<Tree>} visitor
 * @param {boolean | null | undefined} [reverse]
 * @returns {undefined}
 *
 * @param {UnistNode} tree
 *   Tree to traverse.
 * @param {Visitor | Test} test
 *   `unist-util-is`-compatible test
 * @param {Visitor | boolean | null | undefined} [visitor]
 *   Handle each node.
 * @param {boolean | null | undefined} [reverse]
 *   Traverse in reverse preorder (NRL) instead of the default preorder (NLR).
 * @returns {undefined}
 *   Nothing.
 *
 * @template {UnistNode} Tree
 *   Node type.
 * @template {Test} Check
 *   `unist-util-is`-compatible test.
 */
export function visitParents(tree, test, visitor, reverse) {
  /** @type {Test} */
  let check

  if (typeof test === 'function' && typeof visitor !== 'function') {
    reverse = visitor
    // @ts-expect-error no visitor given, so `visitor` is test.
    visitor = test
  } else {
    // @ts-expect-error visitor given, so `test` isn’t a visitor.
    check = test
  }

  const is = convert(check)
  const step = reverse ? -1 : 1

  factory(tree, undefined, [])()

  /**
   * @param {UnistNode} node
   * @param {number | undefined} index
   * @param {Array<UnistParent>} parents
   */
  function factory(node, index, parents) {
    const value = /** @type {Record<string, unknown>} */ (
      node && typeof node === 'object' ? node : {}
    )

    if (typeof value.type === 'string') {
      const name =
        // `hast`
        typeof value.tagName === 'string'
          ? value.tagName
          : // `xast`
            typeof value.name === 'string'
            ? value.name
            : undefined

      Object.defineProperty(visit, 'name', {
        value:
          'node (' + color(node.type + (name ? '<' + name + '>' : '')) + ')'
      })
    }

    return visit

    function visit() {
      /** @type {Readonly<ActionTuple>} */
      let result = empty
      /** @type {Readonly<ActionTuple>} */
      let subresult
      /** @type {number} */
      let offset
      /** @type {Array<UnistParent>} */
      let grandparents

      if (!test || is(node, index, parents[parents.length - 1] || undefined)) {
        // @ts-expect-error: `visitor` is now a visitor.
        result = toResult(visitor(node, parents))

        if (result[0] === EXIT) {
          return result
        }
      }

      if ('children' in node && node.children) {
        const nodeAsParent = /** @type {UnistParent} */ (node)

        if (nodeAsParent.children && result[0] !== SKIP) {
          offset = (reverse ? nodeAsParent.children.length : -1) + step
          grandparents = parents.concat(nodeAsParent)

          while (offset > -1 && offset < nodeAsParent.children.length) {
            const child = nodeAsParent.children[offset]

            subresult = factory(child, offset, grandparents)()

            if (subresult[0] === EXIT) {
              return subresult
            }

            offset =
              typeof subresult[1] === 'number' ? subresult[1] : offset + step
          }
        }
      }

      return result
    }
  }
}

/**
 * Turn a return value into a clean result.
 *
 * @param {VisitorResult} value
 *   Valid return values from visitors.
 * @returns {Readonly<ActionTuple>}
 *   Clean result.
 */
function toResult(value) {
  if (Array.isArray(value)) {
    return value
  }

  if (typeof value === 'number') {
    return [CONTINUE, value]
  }

  return value === null || value === undefined ? empty : [value]
}
{
  "name": "unist-util-visit-parents",
  "version": "6.0.2",
  "description": "unist utility to recursively walk over nodes, with ancestral information",
  "license": "MIT",
  "keywords": [
    "unist",
    "unist-util",
    "util",
    "utility",
    "tree",
    "ast",
    "visit",
    "traverse",
    "walk",
    "check",
    "parent",
    "parents"
  ],
  "repository": "syntax-tree/unist-util-visit-parents",
  "bugs": "https://github.com/syntax-tree/unist-util-visit-parents/issues",
  "funding": {
    "type": "opencollective",
    "url": "https://opencollective.com/unified"
  },
  "author": "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)",
  "contributors": [
    "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)"
  ],
  "sideEffects": false,
  "type": "module",
  "exports": {
    ".": "./index.js",
    "./do-not-use-color": {
      "node": "./lib/color.node.js",
      "default": "./lib/color.js"
    }
  },
  "types": "index.d.ts",
  "files": [
    "lib/",
    "index.d.ts.map",
    "index.d.ts",
    "index.js"
  ],
  "dependencies": {
    "@types/unist": "^3.0.0",
    "unist-util-is": "^6.0.0"
  },
  "devDependencies": {
    "@types/hast": "^3.0.0",
    "@types/mdast": "^4.0.0",
    "@types/node": "^24.0.0",
    "@types/xast": "^2.0.0",
    "c8": "^10.0.0",
    "mdast-util-from-markdown": "^2.0.0",
    "mdast-util-gfm": "^3.0.0",
    "micromark-extension-gfm": "^3.0.0",
    "prettier": "^3.0.0",
    "remark-cli": "^12.0.0",
    "remark-preset-wooorm": "^10.0.0",
    "strip-ansi": "^7.0.0",
    "tsd": "^0.33.0",
    "type-coverage": "^2.0.0",
    "typescript": "^5.0.0",
    "xo": "^0.58.0"
  },
  "scripts": {
    "prepack": "npm run build && npm run format",
    "build": "tsc --build --clean && tsc --build && tsd && type-coverage",
    "format": "remark . -qfo && prettier . -w --log-level warn && xo --fix",
    "test-api": "node --conditions development test.js",
    "test-coverage": "c8 --100 --reporter lcov npm run test-api",
    "test": "npm run build && npm run format && npm run test-coverage"
  },
  "prettier": {
    "bracketSpacing": false,
    "semi": false,
    "singleQuote": true,
    "tabWidth": 2,
    "trailingComma": "none",
    "useTabs": false
  },
  "remarkConfig": {
    "plugins": [
      "remark-preset-wooorm"
    ]
  },
  "typeCoverage": {
    "atLeast": 100,
    "detail": true,
    "#": "needed `any`s",
    "ignoreFiles": [
      "lib/index.d.ts"
    ],
    "ignoreCatch": true,
    "strict": true
  },
  "xo": {
    "prettier": true,
    "rules": {
      "unicorn/prefer-at": "off",
      "unicorn/prefer-string-replace-all": "off"
    }
  }
}
// Note: types exported from `index.d.ts`
export {CONTINUE, EXIT, SKIP, visit} from './lib/index.js'
/**
 * @import {Node as UnistNode, Parent as UnistParent} from 'unist'
 * @import {VisitorResult} from 'unist-util-visit-parents'
 */

/**
 * @typedef {Exclude<import('unist-util-is').Test, undefined> | undefined} Test
 *   Test from `unist-util-is`.
 *
 *   Note: we have remove and add `undefined`, because otherwise when generating
 *   automatic `.d.ts` files, TS tries to flatten paths from a local perspective,
 *   which doesn’t work when publishing on npm.
 */

// To do: use types from `unist-util-visit-parents` when it’s released.

/**
 * @typedef {(
 *   Fn extends (value: any) => value is infer Thing
 *   ? Thing
 *   : Fallback
 * )} Predicate
 *   Get the value of a type guard `Fn`.
 * @template Fn
 *   Value; typically function that is a type guard (such as `(x): x is Y`).
 * @template Fallback
 *   Value to yield if `Fn` is not a type guard.
 */

/**
 * @typedef {(
 *   Check extends null | undefined // No test.
 *   ? Value
 *   : Value extends {type: Check} // String (type) test.
 *   ? Value
 *   : Value extends Check // Partial test.
 *   ? Value
 *   : Check extends Function // Function test.
 *   ? Predicate<Check, Value> extends Value
 *     ? Predicate<Check, Value>
 *     : never
 *   : never // Some other test?
 * )} MatchesOne
 *   Check whether a node matches a primitive check in the type system.
 * @template Value
 *   Value; typically unist `Node`.
 * @template Check
 *   Value; typically `unist-util-is`-compatible test, but not arrays.
 */

/**
 * @typedef {(
 *   Check extends ReadonlyArray<any>
 *   ? MatchesOne<Value, Check[number]>
 *   : MatchesOne<Value, Check>
 * )} Matches
 *   Check whether a node matches a check in the type system.
 * @template Value
 *   Value; typically unist `Node`.
 * @template Check
 *   Value; typically `unist-util-is`-compatible test.
 */

/**
 * @typedef {0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10} Uint
 *   Number; capped reasonably.
 */

/**
 * @typedef {I extends 0 ? 1 : I extends 1 ? 2 : I extends 2 ? 3 : I extends 3 ? 4 : I extends 4 ? 5 : I extends 5 ? 6 : I extends 6 ? 7 : I extends 7 ? 8 : I extends 8 ? 9 : 10} Increment
 *   Increment a number in the type system.
 * @template {Uint} [I=0]
 *   Index.
 */

/**
 * @typedef {(
 *   Node extends UnistParent
 *   ? Node extends {children: Array<infer Children>}
 *     ? Child extends Children ? Node : never
 *     : never
 *   : never
 * )} InternalParent
 *   Collect nodes that can be parents of `Child`.
 * @template {UnistNode} Node
 *   All node types in a tree.
 * @template {UnistNode} Child
 *   Node to search for.
 */

/**
 * @typedef {InternalParent<InclusiveDescendant<Tree>, Child>} Parent
 *   Collect nodes in `Tree` that can be parents of `Child`.
 * @template {UnistNode} Tree
 *   All node types in a tree.
 * @template {UnistNode} Child
 *   Node to search for.
 */

/**
 * @typedef {(
 *   Depth extends Max
 *   ? never
 *   :
 *     | InternalParent<Node, Child>
 *     | InternalAncestor<Node, InternalParent<Node, Child>, Max, Increment<Depth>>
 * )} InternalAncestor
 *   Collect nodes in `Tree` that can be ancestors of `Child`.
 * @template {UnistNode} Node
 *   All node types in a tree.
 * @template {UnistNode} Child
 *   Node to search for.
 * @template {Uint} [Max=10]
 *   Max; searches up to this depth.
 * @template {Uint} [Depth=0]
 *   Current depth.
 */

/**
 * @typedef {(
 *   Tree extends UnistParent
 *     ? Depth extends Max
 *       ? Tree
 *       : Tree | InclusiveDescendant<Tree['children'][number], Max, Increment<Depth>>
 *     : Tree
 * )} InclusiveDescendant
 *   Collect all (inclusive) descendants of `Tree`.
 *
 *   > 👉 **Note**: for performance reasons, this seems to be the fastest way to
 *   > recurse without actually running into an infinite loop, which the
 *   > previous version did.
 *   >
 *   > Practically, a max of `2` is typically enough assuming a `Root` is
 *   > passed, but it doesn’t improve performance.
 *   > It gets higher with `List > ListItem > Table > TableRow > TableCell`.
 *   > Using up to `10` doesn’t hurt or help either.
 * @template {UnistNode} Tree
 *   Tree type.
 * @template {Uint} [Max=10]
 *   Max; searches up to this depth.
 * @template {Uint} [Depth=0]
 *   Current depth.
 */

/**
 * @callback Visitor
 *   Handle a node (matching `test`, if given).
 *
 *   Visitors are free to transform `node`.
 *   They can also transform `parent`.
 *
 *   Replacing `node` itself, if `SKIP` is not returned, still causes its
 *   descendants to be walked (which is a bug).
 *
 *   When adding or removing previous siblings of `node` (or next siblings, in
 *   case of reverse), the `Visitor` should return a new `Index` to specify the
 *   sibling to traverse after `node` is traversed.
 *   Adding or removing next siblings of `node` (or previous siblings, in case
 *   of reverse) is handled as expected without needing to return a new `Index`.
 *
 *   Removing the children property of `parent` still results in them being
 *   traversed.
 * @param {Visited} node
 *   Found node.
 * @param {Visited extends UnistNode ? number | undefined : never} index
 *   Index of `node` in `parent`.
 * @param {Ancestor extends UnistParent ? Ancestor | undefined : never} parent
 *   Parent of `node`.
 * @returns {VisitorResult}
 *   What to do next.
 *
 *   An `Index` is treated as a tuple of `[CONTINUE, Index]`.
 *   An `Action` is treated as a tuple of `[Action]`.
 *
 *   Passing a tuple back only makes sense if the `Action` is `SKIP`.
 *   When the `Action` is `EXIT`, that action can be returned.
 *   When the `Action` is `CONTINUE`, `Index` can be returned.
 * @template {UnistNode} [Visited=UnistNode]
 *   Visited node type.
 * @template {UnistParent} [Ancestor=UnistParent]
 *   Ancestor type.
 */

/**
 * @typedef {Visitor<Visited, Parent<Ancestor, Visited>>} BuildVisitorFromMatch
 *   Build a typed `Visitor` function from a node and all possible parents.
 *
 *   It will infer which values are passed as `node` and which as `parent`.
 * @template {UnistNode} Visited
 *   Node type.
 * @template {UnistParent} Ancestor
 *   Parent type.
 */

/**
 * @typedef {(
 *   BuildVisitorFromMatch<
 *     Matches<Descendant, Check>,
 *     Extract<Descendant, UnistParent>
 *   >
 * )} BuildVisitorFromDescendants
 *   Build a typed `Visitor` function from a list of descendants and a test.
 *
 *   It will infer which values are passed as `node` and which as `parent`.
 * @template {UnistNode} Descendant
 *   Node type.
 * @template {Test} Check
 *   Test type.
 */

/**
 * @typedef {(
 *   BuildVisitorFromDescendants<
 *     InclusiveDescendant<Tree>,
 *     Check
 *   >
 * )} BuildVisitor
 *   Build a typed `Visitor` function from a tree and a test.
 *
 *   It will infer which values are passed as `node` and which as `parent`.
 * @template {UnistNode} [Tree=UnistNode]
 *   Node type.
 * @template {Test} [Check=Test]
 *   Test type.
 */

import {visitParents} from 'unist-util-visit-parents'

export {CONTINUE, EXIT, SKIP} from 'unist-util-visit-parents'

/**
 * Visit nodes.
 *
 * This algorithm performs *depth-first* *tree traversal* in *preorder*
 * (**NLR**) or if `reverse` is given, in *reverse preorder* (**NRL**).
 *
 * You can choose for which nodes `visitor` is called by passing a `test`.
 * For complex tests, you should test yourself in `visitor`, as it will be
 * faster and will have improved type information.
 *
 * Walking the tree is an intensive task.
 * Make use of the return values of the visitor when possible.
 * Instead of walking a tree multiple times, walk it once, use `unist-util-is`
 * to check if a node matches, and then perform different operations.
 *
 * You can change the tree.
 * See `Visitor` for more info.
 *
 * @overload
 * @param {Tree} tree
 * @param {Check} check
 * @param {BuildVisitor<Tree, Check>} visitor
 * @param {boolean | null | undefined} [reverse]
 * @returns {undefined}
 *
 * @overload
 * @param {Tree} tree
 * @param {BuildVisitor<Tree>} visitor
 * @param {boolean | null | undefined} [reverse]
 * @returns {undefined}
 *
 * @param {UnistNode} tree
 *   Tree to traverse.
 * @param {Visitor | Test} testOrVisitor
 *   `unist-util-is`-compatible test (optional, omit to pass a visitor).
 * @param {Visitor | boolean | null | undefined} [visitorOrReverse]
 *   Handle each node (when test is omitted, pass `reverse`).
 * @param {boolean | null | undefined} [maybeReverse=false]
 *   Traverse in reverse preorder (NRL) instead of the default preorder (NLR).
 * @returns {undefined}
 *   Nothing.
 *
 * @template {UnistNode} Tree
 *   Node type.
 * @template {Test} Check
 *   `unist-util-is`-compatible test.
 */
export function visit(tree, testOrVisitor, visitorOrReverse, maybeReverse) {
  /** @type {boolean | null | undefined} */
  let reverse
  /** @type {Test} */
  let test
  /** @type {Visitor} */
  let visitor

  if (
    typeof testOrVisitor === 'function' &&
    typeof visitorOrReverse !== 'function'
  ) {
    test = undefined
    visitor = testOrVisitor
    reverse = visitorOrReverse
  } else {
    // @ts-expect-error: assume the overload with test was given.
    test = testOrVisitor
    // @ts-expect-error: assume the overload with test was given.
    visitor = visitorOrReverse
    reverse = maybeReverse
  }

  visitParents(tree, test, overload, reverse)

  /**
   * @param {UnistNode} node
   * @param {Array<UnistParent>} parents
   */
  function overload(node, parents) {
    const parent = parents[parents.length - 1]
    const index = parent ? parent.children.indexOf(node) : undefined
    return visitor(node, index, parent)
  }
}
{
  "name": "unist-util-visit",
  "version": "5.1.0",
  "description": "unist utility to visit nodes",
  "license": "MIT",
  "keywords": [
    "unist",
    "unist-util",
    "util",
    "utility",
    "remark",
    "retext",
    "rehype",
    "mdast",
    "hast",
    "xast",
    "nlcst",
    "natural",
    "language",
    "markdown",
    "html",
    "xml",
    "tree",
    "ast",
    "node",
    "visit",
    "walk"
  ],
  "repository": "syntax-tree/unist-util-visit",
  "bugs": "https://github.com/syntax-tree/unist-util-visit/issues",
  "funding": {
    "type": "opencollective",
    "url": "https://opencollective.com/unified"
  },
  "author": "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)",
  "contributors": [
    "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)",
    "Eugene Sharygin <eush77@gmail.com>",
    "Richard Gibson <richard.gibson@gmail.com>"
  ],
  "sideEffects": false,
  "type": "module",
  "exports": "./index.js",
  "files": [
    "lib/",
    "index.d.ts.map",
    "index.d.ts",
    "index.js"
  ],
  "dependencies": {
    "@types/unist": "^3.0.0",
    "unist-util-is": "^6.0.0",
    "unist-util-visit-parents": "^6.0.0"
  },
  "devDependencies": {
    "@types/mdast": "^4.0.0",
    "@types/node": "^25.0.0",
    "c8": "^10.0.0",
    "mdast-util-from-markdown": "^2.0.0",
    "mdast-util-gfm": "^3.0.0",
    "micromark-extension-gfm": "^3.0.0",
    "prettier": "^3.0.0",
    "remark-cli": "^12.0.0",
    "remark-preset-wooorm": "^11.0.0",
    "tsd": "^0.33.0",
    "type-coverage": "^2.0.0",
    "typescript": "^5.0.0",
    "xo": "^1.0.0"
  },
  "scripts": {
    "prepack": "npm run build && npm run format",
    "build": "tsc --build --clean && tsc --build && tsd && type-coverage",
    "format": "remark . -qfo && prettier . -w --log-level warn && xo --fix",
    "test-api": "node --conditions development test.js",
    "test-coverage": "c8 --100 --reporter lcov npm run test-api",
    "test": "npm run build && npm run format && npm run test-coverage"
  },
  "prettier": {
    "bracketSpacing": false,
    "semi": false,
    "singleQuote": true,
    "tabWidth": 2,
    "trailingComma": "none",
    "useTabs": false
  },
  "remarkConfig": {
    "plugins": [
      "remark-preset-wooorm"
    ]
  },
  "typeCoverage": {
    "atLeast": 100,
    "detail": true,
    "#": "needed `any`s",
    "ignoreFiles": [
      "lib/index.d.ts"
    ],
    "ignoreCatch": true,
    "strict": true
  },
  "xo": {
    "space": true,
    "prettier": true,
    "rules": {
      "unicorn/prefer-at": "off"
    }
  }
}
/**
 * @callback Handler
 *   Handle a value, with a certain ID field set to a certain value.
 *   The ID field is passed to `zwitch`, and it’s value is this function’s
 *   place on the `handlers` record.
 * @param {...any} parameters
 *   Arbitrary parameters passed to the zwitch.
 *   The first will be an object with a certain ID field set to a certain value.
 * @returns {any}
 *   Anything!
 */

/**
 * @callback UnknownHandler
 *   Handle values that do have a certain ID field, but it’s set to a value
 *   that is not listed in the `handlers` record.
 * @param {unknown} value
 *   An object with a certain ID field set to an unknown value.
 * @param {...any} rest
 *   Arbitrary parameters passed to the zwitch.
 * @returns {any}
 *   Anything!
 */

/**
 * @callback InvalidHandler
 *   Handle values that do not have a certain ID field.
 * @param {unknown} value
 *   Any unknown value.
 * @param {...any} rest
 *   Arbitrary parameters passed to the zwitch.
 * @returns {void|null|undefined|never}
 *   This should crash or return nothing.
 */

/**
 * @template {InvalidHandler} [Invalid=InvalidHandler]
 * @template {UnknownHandler} [Unknown=UnknownHandler]
 * @template {Record<string, Handler>} [Handlers=Record<string, Handler>]
 * @typedef Options
 *   Configuration (required).
 * @property {Invalid} [invalid]
 *   Handler to use for invalid values.
 * @property {Unknown} [unknown]
 *   Handler to use for unknown values.
 * @property {Handlers} [handlers]
 *   Handlers to use.
 */

const own = {}.hasOwnProperty

/**
 * Handle values based on a field.
 *
 * @template {InvalidHandler} [Invalid=InvalidHandler]
 * @template {UnknownHandler} [Unknown=UnknownHandler]
 * @template {Record<string, Handler>} [Handlers=Record<string, Handler>]
 * @param {string} key
 *   Field to switch on.
 * @param {Options<Invalid, Unknown, Handlers>} [options]
 *   Configuration (required).
 * @returns {{unknown: Unknown, invalid: Invalid, handlers: Handlers, (...parameters: Parameters<Handlers[keyof Handlers]>): ReturnType<Handlers[keyof Handlers]>, (...parameters: Parameters<Unknown>): ReturnType<Unknown>}}
 */
export function zwitch(key, options) {
  const settings = options || {}

  /**
   * Handle one value.
   *
   * Based on the bound `key`, a respective handler will be called.
   * If `value` is not an object, or doesn’t have a `key` property, the special
   * “invalid” handler will be called.
   * If `value` has an unknown `key`, the special “unknown” handler will be
   * called.
   *
   * All arguments, and the context object, are passed through to the handler,
   * and it’s result is returned.
   *
   * @this {unknown}
   *   Any context object.
   * @param {unknown} [value]
   *   Any value.
   * @param {...unknown} parameters
   *   Arbitrary parameters passed to the zwitch.
   * @property {Handler} invalid
   *   Handle for values that do not have a certain ID field.
   * @property {Handler} unknown
   *   Handle values that do have a certain ID field, but it’s set to a value
   *   that is not listed in the `handlers` record.
   * @property {Handlers} handlers
   *   Record of handlers.
   * @returns {unknown}
   *   Anything.
   */
  function one(value, ...parameters) {
    /** @type {Handler|undefined} */
    let fn = one.invalid
    const handlers = one.handlers

    if (value && own.call(value, key)) {
      // @ts-expect-error Indexable.
      const id = String(value[key])
      // @ts-expect-error Indexable.
      fn = own.call(handlers, id) ? handlers[id] : one.unknown
    }

    if (fn) {
      return fn.call(this, value, ...parameters)
    }
  }

  one.handlers = settings.handlers || {}
  one.invalid = settings.invalid
  one.unknown = settings.unknown

  // @ts-expect-error: matches!
  return one
}
{
  "name": "zwitch",
  "version": "2.0.4",
  "description": "Handle values based on a property",
  "license": "MIT",
  "keywords": [
    "handle",
    "switch",
    "property"
  ],
  "repository": "wooorm/zwitch",
  "bugs": "https://github.com/wooorm/zwitch/issues",
  "funding": {
    "type": "github",
    "url": "https://github.com/sponsors/wooorm"
  },
  "author": "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)",
  "contributors": [
    "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)"
  ],
  "sideEffects": false,
  "type": "module",
  "main": "index.js",
  "types": "index.d.ts",
  "files": [
    "index.d.ts",
    "index.js"
  ],
  "devDependencies": {
    "@types/node": "^18.0.0",
    "c8": "^7.0.0",
    "prettier": "^2.0.0",
    "remark-cli": "^11.0.0",
    "remark-preset-wooorm": "^9.0.0",
    "tsd": "^0.24.0",
    "type-coverage": "^2.0.0",
    "typescript": "^4.0.0",
    "xo": "^0.52.0"
  },
  "scripts": {
    "prepack": "npm run build && npm run format",
    "build": "tsc --build --clean && tsc --build && tsd && type-coverage",
    "format": "remark . -qfo && prettier . -w --loglevel warn && xo --fix",
    "test-api": "node --conditions development test.js",
    "test-coverage": "c8 --check-coverage --100 --reporter lcov npm run test-api",
    "test": "npm run build && npm run format && npm run test-coverage"
  },
  "prettier": {
    "tabWidth": 2,
    "useTabs": false,
    "singleQuote": true,
    "bracketSpacing": false,
    "semi": false,
    "trailingComma": "none"
  },
  "xo": {
    "prettier": true
  },
  "remarkConfig": {
    "plugins": [
      "preset-wooorm"
    ]
  },
  "typeCoverage": {
    "atLeast": 100,
    "detail": true,
    "strict": true,
    "ignoreFiles": [
      "index.d.ts"
    ]
  }
}
{
  "name": "favela-markdown",
  "version": "0.2.0",
  "description": "The P2P-native markdown record layer for favela. A post is a markdown document; atproto and ActivityPub are projections derived from it on read.",
  "type": "module",
  "license": "MIT",
  "dependencies": {
    "mdast-util-directive": "^3.1.0",
    "mdast-util-from-markdown": "^2.0.3",
    "mdast-util-gfm-footnote": "^2.1.0",
    "mdast-util-gfm-strikethrough": "^2.0.0",
    "mdast-util-gfm-table": "^2.0.0",
    "micromark-extension-directive": "^4.0.0",
    "micromark-extension-gfm-footnote": "^2.1.0",
    "micromark-extension-gfm-strikethrough": "^2.1.0",
    "micromark-extension-gfm-table": "^2.1.1",
    "bare-fs": "^4.8.1",
    "bare-path": "^3.1.2",
    "bare-os": "^3.9.3"
  },
  "scripts": {
    "test": "brittle-bare test/*.test.js"
  },
  "bin": {
    "favela-markdown": "bin/favela-markdown.js"
  },
  "imports": {
    "#node:fs": {
      "bare": "bare-fs",
      "default": "fs"
    },
    "#node:path": {
      "bare": "bare-path",
      "default": "path"
    },
    "#node:os": {
      "bare": "bare-os",
      "default": "os"
    }
  }
}
const { EventEmitter } = require('events-universal')
const FIFO = require('fast-fifo')
const TextDecoder = require('text-decoder')

const StreamError = require('./lib/errors.js')

// if we do a future major, expect queue microtask to be there always, for now a bit defensive
const qmt =
  typeof queueMicrotask === 'undefined' ? (fn) => global.process.nextTick(fn) : queueMicrotask

// 29 bits used total (4 from shared, 14 from read, and 11 from write)
const MAX = (1 << 29) - 1

// Shared state
const OPENING = 0b0001
const PREDESTROYING = 0b0010
const DESTROYING = 0b0100
const DESTROYED = 0b1000

const NOT_OPENING = MAX ^ OPENING
const NOT_PREDESTROYING = MAX ^ PREDESTROYING

// Read state (4 bit offset from shared state)
const READ_ACTIVE = 0b00000000000001 << 4
const READ_UPDATING = 0b00000000000010 << 4
const READ_PRIMARY = 0b00000000000100 << 4
const READ_QUEUED = 0b00000000001000 << 4
const READ_RESUMED = 0b00000000010000 << 4
const READ_PIPE_DRAINED = 0b00000000100000 << 4
const READ_ENDING = 0b00000001000000 << 4
const READ_EMIT_DATA = 0b00000010000000 << 4
const READ_EMIT_READABLE = 0b00000100000000 << 4
const READ_EMITTED_READABLE = 0b00001000000000 << 4
const READ_DONE = 0b00010000000000 << 4
const READ_NEXT_TICK = 0b00100000000000 << 4
const READ_NEEDS_PUSH = 0b01000000000000 << 4
const READ_READ_AHEAD = 0b10000000000000 << 4

// Combined read state
const READ_FLOWING = READ_RESUMED | READ_PIPE_DRAINED
const READ_ACTIVE_AND_NEEDS_PUSH = READ_ACTIVE | READ_NEEDS_PUSH
const READ_PRIMARY_AND_ACTIVE = READ_PRIMARY | READ_ACTIVE
const READ_EMIT_READABLE_AND_QUEUED = READ_EMIT_READABLE | READ_QUEUED
const READ_RESUMED_READ_AHEAD = READ_RESUMED | READ_READ_AHEAD

const READ_NOT_ACTIVE = MAX ^ READ_ACTIVE
const READ_NON_PRIMARY = MAX ^ READ_PRIMARY
const READ_NON_PRIMARY_AND_PUSHED = MAX ^ (READ_PRIMARY | READ_NEEDS_PUSH)
const READ_PUSHED = MAX ^ READ_NEEDS_PUSH
const READ_PAUSED = MAX ^ READ_RESUMED
const READ_NOT_QUEUED = MAX ^ (READ_QUEUED | READ_EMITTED_READABLE)
const READ_NOT_ENDING = MAX ^ READ_ENDING
const READ_PIPE_NOT_DRAINED = MAX ^ READ_FLOWING
const READ_NOT_NEXT_TICK = MAX ^ READ_NEXT_TICK
const READ_NOT_UPDATING = MAX ^ READ_UPDATING
const READ_NO_READ_AHEAD = MAX ^ READ_READ_AHEAD
const READ_PAUSED_NO_READ_AHEAD = MAX ^ READ_RESUMED_READ_AHEAD

// Write state (18 bit offset, 4 bit offset from shared state and 14 from read state)
const WRITE_ACTIVE = 0b00000000001 << 18
const WRITE_UPDATING = 0b00000000010 << 18
const WRITE_PRIMARY = 0b00000000100 << 18
const WRITE_QUEUED = 0b00000001000 << 18
const WRITE_UNDRAINED = 0b00000010000 << 18
const WRITE_DONE = 0b00000100000 << 18
const WRITE_EMIT_DRAIN = 0b00001000000 << 18
const WRITE_NEXT_TICK = 0b00010000000 << 18
const WRITE_WRITING = 0b00100000000 << 18
const WRITE_FINISHING = 0b01000000000 << 18
const WRITE_CORKED = 0b10000000000 << 18

const WRITE_NOT_ACTIVE = MAX ^ (WRITE_ACTIVE | WRITE_WRITING)
const WRITE_NON_PRIMARY = MAX ^ WRITE_PRIMARY
const WRITE_NOT_FINISHING = MAX ^ (WRITE_ACTIVE | WRITE_FINISHING)
const WRITE_DRAINED = MAX ^ WRITE_UNDRAINED
const WRITE_NOT_QUEUED = MAX ^ WRITE_QUEUED
const WRITE_NOT_NEXT_TICK = MAX ^ WRITE_NEXT_TICK
const WRITE_NOT_UPDATING = MAX ^ WRITE_UPDATING
const WRITE_NOT_CORKED = MAX ^ WRITE_CORKED

// Combined shared state
const ACTIVE = READ_ACTIVE | WRITE_ACTIVE
const NOT_ACTIVE = MAX ^ ACTIVE
const DONE = READ_DONE | WRITE_DONE
const DESTROY_STATUS = DESTROYING | DESTROYED | PREDESTROYING
const OPEN_STATUS = DESTROY_STATUS | OPENING
const AUTO_DESTROY = DESTROY_STATUS | DONE
const NON_PRIMARY = WRITE_NON_PRIMARY & READ_NON_PRIMARY
const ACTIVE_OR_TICKING = WRITE_NEXT_TICK | READ_NEXT_TICK
const TICKING = ACTIVE_OR_TICKING & NOT_ACTIVE
const IS_OPENING = OPEN_STATUS | TICKING

// Combined shared state and read state
const READ_PRIMARY_STATUS = OPEN_STATUS | READ_ENDING | READ_DONE
const READ_STATUS = OPEN_STATUS | READ_DONE | READ_QUEUED
const READ_ENDING_STATUS = OPEN_STATUS | READ_ENDING | READ_QUEUED
const READ_READABLE_STATUS = OPEN_STATUS | READ_EMIT_READABLE | READ_QUEUED | READ_EMITTED_READABLE
const SHOULD_NOT_READ =
  OPEN_STATUS | READ_ACTIVE | READ_ENDING | READ_DONE | READ_NEEDS_PUSH | READ_READ_AHEAD
const READ_BACKPRESSURE_STATUS = DESTROY_STATUS | READ_ENDING | READ_DONE
const READ_UPDATE_SYNC_STATUS = READ_UPDATING | OPEN_STATUS | READ_NEXT_TICK | READ_PRIMARY
const READ_NEXT_TICK_OR_OPENING = READ_NEXT_TICK | OPENING

// Combined write state
const WRITE_PRIMARY_STATUS = OPEN_STATUS | WRITE_FINISHING | WRITE_DONE
const WRITE_QUEUED_AND_UNDRAINED = WRITE_QUEUED | WRITE_UNDRAINED
const WRITE_QUEUED_AND_ACTIVE = WRITE_QUEUED | WRITE_ACTIVE
const WRITE_DRAIN_STATUS = WRITE_QUEUED | WRITE_UNDRAINED | OPEN_STATUS | WRITE_ACTIVE
const WRITE_STATUS = OPEN_STATUS | WRITE_ACTIVE | WRITE_QUEUED | WRITE_CORKED
const WRITE_PRIMARY_AND_ACTIVE = WRITE_PRIMARY | WRITE_ACTIVE
const WRITE_ACTIVE_AND_WRITING = WRITE_ACTIVE | WRITE_WRITING
const WRITE_FINISHING_STATUS = OPEN_STATUS | WRITE_FINISHING | WRITE_QUEUED_AND_ACTIVE | WRITE_DONE
const WRITE_BACKPRESSURE_STATUS = WRITE_UNDRAINED | DESTROY_STATUS | WRITE_FINISHING | WRITE_DONE
const WRITE_UPDATE_SYNC_STATUS = WRITE_UPDATING | OPEN_STATUS | WRITE_NEXT_TICK | WRITE_PRIMARY
const WRITE_DROP_DATA = WRITE_FINISHING | WRITE_DONE | DESTROY_STATUS

const asyncIterator = Symbol.asyncIterator || Symbol('asyncIterator')

class WritableState {
  constructor(
    stream,
    { highWaterMark = 16384, map = null, mapWritable, byteLength, byteLengthWritable } = {}
  ) {
    this.stream = stream
    this.queue = new FIFO()
    this.highWaterMark = highWaterMark
    this.buffered = 0
    this.error = null
    this.pipeline = null
    this.drains = null // if we add more seldomly used helpers we might them into a subobject so its a single ptr
    this.byteLength = byteLengthWritable || byteLength || defaultByteLength
    this.map = mapWritable || map
    this.afterWrite = afterWrite.bind(this)
    this.afterUpdateNextTick = updateWriteNT.bind(this)
  }

  get ending() {
    return (this.stream._duplexState & WRITE_FINISHING) !== 0
  }

  get ended() {
    return (this.stream._duplexState & WRITE_DONE) !== 0
  }

  push(data) {
    if ((this.stream._duplexState & WRITE_DROP_DATA) !== 0) return false
    if (this.map !== null) data = this.map(data)

    this.buffered += this.byteLength(data)
    this.queue.push(data)

    if (this.buffered < this.highWaterMark) {
      this.stream._duplexState |= WRITE_QUEUED
      return true
    }

    this.stream._duplexState |= WRITE_QUEUED_AND_UNDRAINED
    return false
  }

  shift() {
    const data = this.queue.shift()

    this.buffered -= this.byteLength(data)
    if (this.buffered === 0) this.stream._duplexState &= WRITE_NOT_QUEUED

    return data
  }

  end(data) {
    if (typeof data === 'function') {
      this.stream.once('finish', data)
    } else if (data !== undefined && data !== null) {
      this.push(data)
    }

    this.stream._duplexState = (this.stream._duplexState | WRITE_FINISHING) & WRITE_NON_PRIMARY
  }

  autoBatch(data, cb) {
    const buffer = []
    const stream = this.stream

    buffer.push(data)
    while ((stream._duplexState & WRITE_STATUS) === WRITE_QUEUED_AND_ACTIVE) {
      buffer.push(stream._writableState.shift())
    }

    if ((stream._duplexState & OPEN_STATUS) !== 0) return cb(null)
    stream._writev(buffer, cb)
  }

  update() {
    const stream = this.stream

    stream._duplexState |= WRITE_UPDATING

    do {
      while ((stream._duplexState & WRITE_STATUS) === WRITE_QUEUED) {
        const data = this.shift()
        stream._duplexState |= WRITE_ACTIVE_AND_WRITING
        stream._write(data, this.afterWrite)
      }

      if ((stream._duplexState & WRITE_PRIMARY_AND_ACTIVE) === 0) this.updateNonPrimary()
    } while (this.continueUpdate() === true)

    stream._duplexState &= WRITE_NOT_UPDATING
  }

  updateNonPrimary() {
    const stream = this.stream

    if ((stream._duplexState & WRITE_FINISHING_STATUS) === WRITE_FINISHING) {
      stream._duplexState = stream._duplexState | WRITE_ACTIVE
      stream._final(afterFinal.bind(this))
      return
    }

    if ((stream._duplexState & DESTROY_STATUS) === DESTROYING) {
      if ((stream._duplexState & ACTIVE_OR_TICKING) === 0) {
        stream._duplexState |= ACTIVE
        stream._destroy(afterDestroy.bind(this))
      }
      return
    }

    if ((stream._duplexState & IS_OPENING) === OPENING) {
      stream._duplexState = (stream._duplexState | ACTIVE) & NOT_OPENING
      stream._open(afterOpen.bind(this))
    }
  }

  continueUpdate() {
    if ((this.stream._duplexState & WRITE_NEXT_TICK) === 0) return false
    this.stream._duplexState &= WRITE_NOT_NEXT_TICK
    return true
  }

  updateCallback() {
    if ((this.stream._duplexState & WRITE_UPDATE_SYNC_STATUS) === WRITE_PRIMARY) {
      this.update()
    } else {
      this.updateNextTick()
    }
  }

  updateNextTick() {
    if ((this.stream._duplexState & WRITE_NEXT_TICK) !== 0) return
    this.stream._duplexState |= WRITE_NEXT_TICK
    if ((this.stream._duplexState & WRITE_UPDATING) === 0) qmt(this.afterUpdateNextTick)
  }
}

class ReadableState {
  constructor(
    stream,
    { highWaterMark = 16384, map = null, mapReadable, byteLength, byteLengthReadable } = {}
  ) {
    this.stream = stream
    this.queue = new FIFO()
    this.highWaterMark = highWaterMark === 0 ? 1 : highWaterMark
    this.buffered = 0
    this.readAhead = highWaterMark > 0
    this.error = null
    this.pipeline = null
    this.byteLength = byteLengthReadable || byteLength || defaultByteLength
    this.map = mapReadable || map
    this.pipeTo = null
    this.afterRead = afterRead.bind(this)
    this.afterUpdateNextTick = updateReadNT.bind(this)
  }

  get ending() {
    return (this.stream._duplexState & READ_ENDING) !== 0
  }

  get ended() {
    return (this.stream._duplexState & READ_DONE) !== 0
  }

  pipe(pipeTo, cb) {
    if (this.pipeTo !== null) throw StreamError.BAD_ARGUMENT('Can only pipe to one destination')
    if (typeof cb !== 'function') cb = null

    this.stream._duplexState |= READ_PIPE_DRAINED
    this.pipeTo = pipeTo
    this.pipeline = new Pipeline(this.stream, pipeTo, cb)

    if (cb) this.stream.on('error', noop) // We already error handle this so supress crashes

    if (isStreamx(pipeTo)) {
      pipeTo._writableState.pipeline = this.pipeline
      if (cb) pipeTo.on('error', noop) // We already error handle this so supress crashes
      pipeTo.on('finish', this.pipeline.finished.bind(this.pipeline)) // TODO: just call finished from pipeTo itself
    } else {
      const onerror = this.pipeline.done.bind(this.pipeline, pipeTo)
      const onclose = this.pipeline.done.bind(this.pipeline, pipeTo, null) // onclose has a weird bool arg
      pipeTo.on('error', onerror)
      pipeTo.on('close', onclose)
      pipeTo.on('finish', this.pipeline.finished.bind(this.pipeline))
    }

    pipeTo.on('drain', afterDrain.bind(this))
    this.stream.emit('piping', pipeTo)
    pipeTo.emit('pipe', this.stream)
  }

  push(data) {
    const stream = this.stream

    if (data === null) {
      this.highWaterMark = 0
      stream._duplexState = (stream._duplexState | READ_ENDING) & READ_NON_PRIMARY_AND_PUSHED
      return false
    }

    if (this.map !== null) {
      data = this.map(data)
      if (data === null) {
        stream._duplexState &= READ_PUSHED
        return this.buffered < this.highWaterMark
      }
    }

    this.buffered += this.byteLength(data)
    this.queue.push(data)

    stream._duplexState = (stream._duplexState | READ_QUEUED) & READ_PUSHED

    return this.buffered < this.highWaterMark
  }

  shift() {
    const data = this.queue.shift()

    this.buffered -= this.byteLength(data)
    if (this.buffered === 0) {
      this.stream._duplexState &= READ_NOT_QUEUED
    }

    return data
  }

  unshift(data) {
    const pending = [this.map !== null ? this.map(data) : data]
    while (this.buffered > 0) pending.push(this.shift())

    for (let i = 0; i < pending.length - 1; i++) {
      const data = pending[i]
      this.buffered += this.byteLength(data)
      this.queue.push(data)
    }

    this.push(pending[pending.length - 1])
  }

  read() {
    const stream = this.stream

    if ((stream._duplexState & READ_STATUS) === READ_QUEUED) {
      const data = this.shift()

      if (this.pipeTo !== null && this.pipeTo.write(data) === false) {
        stream._duplexState &= READ_PIPE_NOT_DRAINED
      }

      if ((stream._duplexState & READ_EMIT_DATA) !== 0) {
        stream.emit('data', data)
      }

      return data
    }

    if (this.readAhead === false) {
      stream._duplexState |= READ_READ_AHEAD
      this.updateNextTick()
    }

    return null
  }

  drain() {
    const stream = this.stream

    while (
      (stream._duplexState & READ_STATUS) === READ_QUEUED &&
      (stream._duplexState & READ_FLOWING) !== 0
    ) {
      const data = this.shift()

      if (this.pipeTo !== null && this.pipeTo.write(data) === false) {
        stream._duplexState &= READ_PIPE_NOT_DRAINED
      }

      if ((stream._duplexState & READ_EMIT_DATA) !== 0) {
        stream.emit('data', data)
      }
    }
  }

  update() {
    const stream = this.stream

    stream._duplexState |= READ_UPDATING

    do {
      this.drain()

      while (
        this.buffered < this.highWaterMark &&
        (stream._duplexState & SHOULD_NOT_READ) === READ_READ_AHEAD
      ) {
        stream._duplexState |= READ_ACTIVE_AND_NEEDS_PUSH
        stream._read(this.afterRead)
        this.drain()
      }

      if ((stream._duplexState & READ_READABLE_STATUS) === READ_EMIT_READABLE_AND_QUEUED) {
        stream._duplexState |= READ_EMITTED_READABLE
        stream.emit('readable')
      }

      if ((stream._duplexState & READ_PRIMARY_AND_ACTIVE) === 0) {
        this.updateNonPrimary()
      }
    } while (this.continueUpdate() === true)

    stream._duplexState &= READ_NOT_UPDATING
  }

  updateNonPrimary() {
    const stream = this.stream

    if ((stream._duplexState & READ_ENDING_STATUS) === READ_ENDING) {
      stream._duplexState = (stream._duplexState | READ_DONE) & READ_NOT_ENDING
      stream.emit('end')

      if ((stream._duplexState & AUTO_DESTROY) === DONE) {
        stream._duplexState |= DESTROYING
      }

      if (this.pipeTo !== null) {
        this.pipeTo.end()
      }
    }

    if ((stream._duplexState & DESTROY_STATUS) === DESTROYING) {
      if ((stream._duplexState & ACTIVE_OR_TICKING) === 0) {
        stream._duplexState |= ACTIVE
        stream._destroy(afterDestroy.bind(this))
      }
      return
    }

    if ((stream._duplexState & IS_OPENING) === OPENING) {
      stream._duplexState = (stream._duplexState | ACTIVE) & NOT_OPENING
      stream._open(afterOpen.bind(this))
    }
  }

  continueUpdate() {
    if ((this.stream._duplexState & READ_NEXT_TICK) === 0) return false
    this.stream._duplexState &= READ_NOT_NEXT_TICK
    return true
  }

  updateCallback() {
    if ((this.stream._duplexState & READ_UPDATE_SYNC_STATUS) === READ_PRIMARY) {
      this.update()
    } else {
      this.updateNextTick()
    }
  }

  updateNextTickIfOpen() {
    if ((this.stream._duplexState & READ_NEXT_TICK_OR_OPENING) !== 0) return
    this.stream._duplexState |= READ_NEXT_TICK
    if ((this.stream._duplexState & READ_UPDATING) === 0) qmt(this.afterUpdateNextTick)
  }

  updateNextTick() {
    if ((this.stream._duplexState & READ_NEXT_TICK) !== 0) return
    this.stream._duplexState |= READ_NEXT_TICK
    if ((this.stream._duplexState & READ_UPDATING) === 0) qmt(this.afterUpdateNextTick)
  }
}

class TransformState {
  constructor(stream) {
    this.data = null
    this.afterTransform = afterTransform.bind(stream)
    this.afterFinal = null
  }
}

class Pipeline {
  constructor(src, dst, cb) {
    this.from = src
    this.to = dst
    this.afterPipe = cb
    this.error = null
    this.pipeToFinished = false
  }

  finished() {
    this.pipeToFinished = true
  }

  done(stream, err) {
    if (err) this.error = err

    if (stream === this.to) {
      this.to = null

      if (this.from !== null) {
        if ((this.from._duplexState & READ_DONE) === 0 || !this.pipeToFinished) {
          this.from.destroy(this.error || StreamError.PREMATURE_CLOSE('Writable stream closed'))
        }
        return
      }
    }

    if (stream === this.from) {
      this.from = null

      if (this.to !== null) {
        if ((stream._duplexState & READ_DONE) === 0) {
          this.to.destroy(this.error || StreamError.PREMATURE_CLOSE('Readable stream closed'))
        }
        return
      }
    }

    if (this.afterPipe !== null) this.afterPipe(this.error)
    this.to = this.from = this.afterPipe = null
  }
}

function afterDrain() {
  this.stream._duplexState |= READ_PIPE_DRAINED
  this.updateCallback()
}

function afterFinal(err) {
  const stream = this.stream
  if (err) stream.destroy(err)

  if ((stream._duplexState & DESTROY_STATUS) === 0) {
    stream._duplexState |= WRITE_DONE
    stream.emit('finish')
  }

  if ((stream._duplexState & AUTO_DESTROY) === DONE) {
    stream._duplexState |= DESTROYING
  }

  stream._duplexState &= WRITE_NOT_FINISHING

  // no need to wait the extra tick here, so we short circuit that
  if ((stream._duplexState & WRITE_UPDATING) === 0) {
    this.update()
  } else {
    this.updateNextTick()
  }
}

function afterDestroy(err) {
  const stream = this.stream

  if (!err && !StreamError.isStreamDestroyed(this.error)) err = this.error
  if (err) stream.emit('error', err)

  stream._duplexState |= DESTROYED
  stream.emit('close')

  const rs = stream._readableState
  const ws = stream._writableState

  if (rs !== null && rs.pipeline !== null) {
    rs.pipeline.done(stream, err)
  }

  if (ws !== null) {
    while (ws.drains !== null && ws.drains.length > 0) {
      ws.drains.shift().resolve(false)
    }

    if (ws.pipeline !== null) {
      ws.pipeline.done(stream, err)
    }
  }
}

function afterWrite(err) {
  const stream = this.stream

  if (err) stream.destroy(err)
  stream._duplexState &= WRITE_NOT_ACTIVE

  if (this.drains !== null) tickDrains(this.drains)

  if ((stream._duplexState & WRITE_DRAIN_STATUS) === WRITE_UNDRAINED) {
    stream._duplexState &= WRITE_DRAINED

    if ((stream._duplexState & WRITE_EMIT_DRAIN) === WRITE_EMIT_DRAIN) {
      stream.emit('drain')
    }
  }

  this.updateCallback()
}

function afterRead(err) {
  if (err) this.stream.destroy(err)
  this.stream._duplexState &= READ_NOT_ACTIVE

  if (this.readAhead === false && (this.stream._duplexState & READ_RESUMED) === 0) {
    this.stream._duplexState &= READ_NO_READ_AHEAD
  }

  this.updateCallback()
}

function updateReadNT() {
  if ((this.stream._duplexState & READ_UPDATING) === 0) {
    this.stream._duplexState &= READ_NOT_NEXT_TICK
    this.update()
  }
}

function updateWriteNT() {
  if ((this.stream._duplexState & WRITE_UPDATING) === 0) {
    this.stream._duplexState &= WRITE_NOT_NEXT_TICK
    this.update()
  }
}

function tickDrains(drains) {
  for (let i = 0; i < drains.length; i++) {
    // drains.writes are monotonic, so if one is 0 its always the first one
    if (--drains[i].writes === 0) {
      drains.shift().resolve(true)
      i--
    }
  }
}

function afterOpen(err) {
  const stream = this.stream

  if (err) stream.destroy(err)

  if ((stream._duplexState & DESTROYING) === 0) {
    if ((stream._duplexState & READ_PRIMARY_STATUS) === 0) {
      stream._duplexState |= READ_PRIMARY
    }

    if ((stream._duplexState & WRITE_PRIMARY_STATUS) === 0) {
      stream._duplexState |= WRITE_PRIMARY
    }

    stream.emit('open')
  }

  stream._duplexState &= NOT_ACTIVE

  if (stream._writableState !== null) {
    stream._writableState.updateCallback()
  }

  if (stream._readableState !== null) {
    stream._readableState.updateCallback()
  }
}

function afterTransform(err, data) {
  if (data !== undefined && data !== null) this.push(data)
  this._writableState.afterWrite(err)
}

function newListener(name) {
  if (this._readableState !== null) {
    if (name === 'data') {
      this._duplexState |= READ_EMIT_DATA | READ_RESUMED_READ_AHEAD
      this._readableState.updateNextTick()
    }
    if (name === 'readable') {
      this._duplexState |= READ_EMIT_READABLE
      this._readableState.updateNextTick()
    }
  }

  if (this._writableState !== null) {
    if (name === 'drain') {
      this._duplexState |= WRITE_EMIT_DRAIN
      this._writableState.updateNextTick()
    }
  }
}

class Stream extends EventEmitter {
  constructor(opts) {
    super()

    this._duplexState = 0
    this._readableState = null
    this._writableState = null

    if (opts) {
      if (opts.open) this._open = opts.open
      if (opts.destroy) this._destroy = opts.destroy
      if (opts.predestroy) this._predestroy = opts.predestroy
      if (opts.signal) opts.signal.addEventListener('abort', abort.bind(this))
    }

    this.on('newListener', newListener)
  }

  _open(cb) {
    cb(null)
  }

  _destroy(cb) {
    cb(null)
  }

  _predestroy() {
    // does nothing
  }

  get readable() {
    return this._readableState !== null ? true : undefined
  }

  get writable() {
    return this._writableState !== null ? true : undefined
  }

  get destroyed() {
    return (this._duplexState & DESTROYED) !== 0
  }

  get destroying() {
    return (this._duplexState & DESTROY_STATUS) !== 0
  }

  destroy(err) {
    if ((this._duplexState & DESTROY_STATUS) === 0) {
      if (!err) err = StreamError.STREAM_DESTROYED()
      this._duplexState = (this._duplexState | DESTROYING) & NON_PRIMARY

      if (this._readableState !== null) {
        this._readableState.highWaterMark = 0
        this._readableState.error = err
      }

      if (this._writableState !== null) {
        this._writableState.highWaterMark = 0
        this._writableState.error = err
      }

      this._duplexState |= PREDESTROYING
      this._predestroy()
      this._duplexState &= NOT_PREDESTROYING

      if (this._readableState !== null) {
        this._readableState.updateNextTick()
      }

      if (this._writableState !== null) {
        this._writableState.updateNextTick()
      }
    }
  }
}

class Readable extends Stream {
  constructor(opts) {
    super(opts)

    this._duplexState |= OPENING | WRITE_DONE | READ_READ_AHEAD
    this._readableState = new ReadableState(this, opts)

    if (opts) {
      if (this._readableState.readAhead === false) this._duplexState &= READ_NO_READ_AHEAD
      if (opts.read) this._read = opts.read
      if (opts.eagerOpen) this._readableState.updateNextTick()
      if (opts.encoding) this.setEncoding(opts.encoding)
    }
  }

  static deferred(fn, opts) {
    const out = new PassThrough(opts)

    fn()
      .then((src) => {
        if (src === null) return out.end()
        if (out.destroying) return
        pipeline(src, out, noop)
      })
      .catch((err) => out.destroy(err))

    return out
  }

  setEncoding(encoding) {
    const dec = new TextDecoder(encoding)
    const map = this._readableState.map || echo
    this._readableState.map = mapOrSkip
    return this

    function mapOrSkip(data) {
      const next = dec.push(data)
      return next === '' && (data.byteLength !== 0 || dec.remaining > 0) ? null : map(next)
    }
  }

  _read(cb) {
    cb(null)
  }

  pipe(dest, cb) {
    this._readableState.updateNextTick()
    this._readableState.pipe(dest, cb)
    return dest
  }

  read() {
    this._readableState.updateNextTick()
    return this._readableState.read()
  }

  push(data) {
    this._readableState.updateNextTickIfOpen()
    return this._readableState.push(data)
  }

  unshift(data) {
    this._readableState.updateNextTickIfOpen()
    return this._readableState.unshift(data)
  }

  resume() {
    this._duplexState |= READ_RESUMED_READ_AHEAD
    this._readableState.updateNextTick()
    return this
  }

  pause() {
    this._duplexState &=
      this._readableState.readAhead === false ? READ_PAUSED_NO_READ_AHEAD : READ_PAUSED
    return this
  }

  static _fromAsyncIterator(ite, opts) {
    let destroy

    const rs = new Readable({
      ...opts,
      read(cb) {
        ite.next().then(push).then(cb.bind(null, null)).catch(cb)
      },
      predestroy() {
        destroy = ite.return()
      },
      destroy(cb) {
        if (!destroy) return cb(null)
        destroy.then(cb.bind(null, null)).catch(cb)
      }
    })

    return rs

    function push(data) {
      if (data.done) rs.push(null)
      else rs.push(data.value)
    }
  }

  static from(data, opts) {
    if (isReadStreamx(data)) return data
    if (data[asyncIterator]) return this._fromAsyncIterator(data[asyncIterator](), opts)
    if (!Array.isArray(data)) data = data === undefined ? [] : [data]

    let i = 0
    return new Readable({
      ...opts,
      read(cb) {
        this.push(i === data.length ? null : data[i++])
        cb(null)
      }
    })
  }

  static isBackpressured(rs) {
    return (
      (rs._duplexState & READ_BACKPRESSURE_STATUS) !== 0 ||
      rs._readableState.buffered >= rs._readableState.highWaterMark
    )
  }

  static isPaused(rs) {
    return (rs._duplexState & READ_RESUMED) === 0
  }

  [asyncIterator]() {
    const stream = this

    let error = null
    let promiseResolve = null
    let promiseReject = null

    this.on('error', (err) => {
      error = err
    })
    this.on('readable', onreadable)
    this.on('close', onclose)

    return {
      [asyncIterator]() {
        return this
      },
      next() {
        return new Promise(function (resolve, reject) {
          promiseResolve = resolve
          promiseReject = reject
          const data = stream.read()
          if (data !== null) ondata(data)
          else if ((stream._duplexState & DESTROYED) !== 0) ondata(null)
        })
      },
      return() {
        return destroy(null)
      },
      throw(err) {
        return destroy(err)
      }
    }

    function onreadable() {
      if (promiseResolve !== null) ondata(stream.read())
    }

    function onclose() {
      if (promiseResolve !== null) ondata(null)
    }

    function ondata(data) {
      if (promiseReject === null) return
      if (error) {
        promiseReject(error)
      } else if (data === null && (stream._duplexState & READ_DONE) === 0) {
        promiseReject(StreamError.STREAM_DESTROYED())
      } else {
        promiseResolve({ value: data, done: data === null })
      }
      promiseReject = promiseResolve = null
    }

    function destroy(err) {
      stream.destroy(err)
      return new Promise((resolve, reject) => {
        if (stream._duplexState & DESTROYED) return resolve({ value: undefined, done: true })
        stream.once('close', function () {
          if (err) reject(err)
          else resolve({ value: undefined, done: true })
        })
      })
    }
  }
}

class Writable extends Stream {
  constructor(opts) {
    super(opts)

    this._duplexState |= OPENING | READ_DONE
    this._writableState = new WritableState(this, opts)

    if (opts) {
      if (opts.writev) this._writev = opts.writev
      if (opts.write) this._write = opts.write
      if (opts.final) this._final = opts.final
      if (opts.eagerOpen) this._writableState.updateNextTick()
    }
  }

  cork() {
    this._duplexState |= WRITE_CORKED
  }

  uncork() {
    this._duplexState &= WRITE_NOT_CORKED
    this._writableState.updateNextTick()
  }

  _writev(batch, cb) {
    cb(null)
  }

  _write(data, cb) {
    this._writableState.autoBatch(data, cb)
  }

  _final(cb) {
    cb(null)
  }

  static isBackpressured(ws) {
    return (ws._duplexState & WRITE_BACKPRESSURE_STATUS) !== 0
  }

  static drained(ws) {
    if (ws.destroyed) return Promise.resolve(false)

    const state = ws._writableState
    const pending = isWritev(ws) ? Math.min(1, state.queue.length) : state.queue.length
    const writes = pending + (ws._duplexState & WRITE_WRITING ? 1 : 0)
    if (writes === 0) return Promise.resolve(true)

    if (state.drains === null) state.drains = []
    return new Promise((resolve) => {
      state.drains.push({ writes, resolve })
    })
  }

  write(data) {
    this._writableState.updateNextTick()
    return this._writableState.push(data)
  }

  end(data) {
    this._writableState.updateNextTick()
    this._writableState.end(data)
    return this
  }
}

class Duplex extends Readable {
  // and Writable
  constructor(opts) {
    super(opts)

    this._duplexState = OPENING | (this._duplexState & READ_READ_AHEAD)
    this._writableState = new WritableState(this, opts)

    if (opts) {
      if (opts.writev) this._writev = opts.writev
      if (opts.write) this._write = opts.write
      if (opts.final) this._final = opts.final
    }
  }

  cork() {
    this._duplexState |= WRITE_CORKED
  }

  uncork() {
    this._duplexState &= WRITE_NOT_CORKED
    this._writableState.updateNextTick()
  }

  _writev(batch, cb) {
    cb(null)
  }

  _write(data, cb) {
    this._writableState.autoBatch(data, cb)
  }

  _final(cb) {
    cb(null)
  }

  write(data) {
    this._writableState.updateNextTick()
    return this._writableState.push(data)
  }

  end(data) {
    this._writableState.updateNextTick()
    this._writableState.end(data)
    return this
  }
}

class Transform extends Duplex {
  constructor(opts) {
    super(opts)
    this._transformState = new TransformState(this)

    if (opts) {
      if (opts.transform) this._transform = opts.transform
      if (opts.flush) this._flush = opts.flush
    }
  }

  _write(data, cb) {
    if (this._readableState.buffered >= this._readableState.highWaterMark) {
      this._transformState.data = data
    } else {
      this._transform(data, this._transformState.afterTransform)
    }
  }

  _read(cb) {
    if (this._transformState.data !== null) {
      const data = this._transformState.data
      this._transformState.data = null
      cb(null)
      this._transform(data, this._transformState.afterTransform)
    } else {
      cb(null)
    }
  }

  destroy(err) {
    super.destroy(err)
    if (this._transformState.data !== null) {
      this._transformState.data = null
      this._transformState.afterTransform()
    }
  }

  _transform(data, cb) {
    cb(null, data)
  }

  _flush(cb) {
    cb(null)
  }

  _final(cb) {
    this._transformState.afterFinal = cb
    this._flush(transformAfterFlush.bind(this))
  }
}

class PassThrough extends Transform {}

function transformAfterFlush(err, data) {
  const cb = this._transformState.afterFinal
  if (err) return cb(err)

  if (data !== null && data !== undefined) this.push(data)
  this.push(null)
  cb(null)
}

function pipelinePromise(...streams) {
  return new Promise((resolve, reject) => {
    return pipeline(...streams, (err) => {
      if (err) return reject(err)
      resolve()
    })
  })
}

function pipeline(stream, ...streams) {
  const all = Array.isArray(stream) ? [...stream, ...streams] : [stream, ...streams]
  const done = all.length && typeof all[all.length - 1] === 'function' ? all.pop() : null

  if (all.length < 2) throw StreamError.BAD_ARGUMENT('Pipeline requires at least 2 streams')

  let src = all[0]
  let dest = null
  let error = null

  for (let i = 1; i < all.length; i++) {
    dest = all[i]

    if (isStreamx(src)) {
      src.pipe(dest, onerror)
    } else {
      errorHandle(src, true, i > 1, onerror)
      src.pipe(dest)
    }

    src = dest
  }

  if (done) {
    let fin = false

    const autoDestroy =
      isStreamx(dest) || !!(dest._writableState && dest._writableState.autoDestroy)

    dest.on('error', (err) => {
      if (error === null) error = err
    })

    dest.on('finish', () => {
      fin = true
      if (!autoDestroy) done(error)
    })

    if (autoDestroy) {
      dest.on('close', () => done(error || (fin ? null : StreamError.PREMATURE_CLOSE())))
    }
  }

  return dest

  function errorHandle(s, rd, wr, onerror) {
    s.on('error', onerror)
    s.on('close', onclose)

    function onclose() {
      if (rd && s._readableState && !s._readableState.ended) {
        return onerror(StreamError.PREMATURE_CLOSE())
      }
      if (wr && s._writableState && !s._writableState.ended) {
        return onerror(StreamError.PREMATURE_CLOSE())
      }
    }
  }

  function onerror(err) {
    if (!err || error) return
    error = err

    for (const s of all) {
      s.destroy(err)
    }
  }
}

function echo(s) {
  return s
}

function isStream(stream) {
  return !!stream._readableState || !!stream._writableState
}

function isStreamx(stream) {
  return typeof stream._duplexState === 'number' && isStream(stream)
}

function isEnding(stream) {
  return !!stream._readableState && stream._readableState.ending
}

function isEnded(stream) {
  return !!stream._readableState && stream._readableState.ended
}

function isFinishing(stream) {
  return !!stream._writableState && stream._writableState.ending
}

function isFinished(stream) {
  return !!stream._writableState && stream._writableState.ended
}

function getStreamError(stream, opts = {}) {
  const err =
    (stream._readableState && stream._readableState.error) ||
    (stream._writableState && stream._writableState.error)

  // avoid implicit errors by default
  return !opts.all && StreamError.isStreamDestroyed(err) ? null : err
}

function isReadStreamx(stream) {
  return isStreamx(stream) && stream.readable
}

function isDisturbed(stream) {
  return (
    (stream._duplexState & OPENING) !== OPENING ||
    (stream._duplexState & DESTROYING) === DESTROYING ||
    (stream._duplexState & ACTIVE_OR_TICKING) !== 0
  )
}

function isTypedArray(data) {
  return typeof data === 'object' && data !== null && typeof data.byteLength === 'number'
}

function defaultByteLength(data) {
  return isTypedArray(data) ? data.byteLength : 1024
}

function noop() {}

function abort() {
  this.destroy(StreamError.ABORTED())
}

function isWritev(s) {
  return s._writev !== Writable.prototype._writev && s._writev !== Duplex.prototype._writev
}

module.exports = {
  pipeline,
  pipelinePromise,
  isStream,
  isStreamx,
  isEnding,
  isEnded,
  isFinishing,
  isFinished,
  isDisturbed,
  getStreamError,
  Stream,
  Writable,
  Readable,
  Duplex,
  Transform,
  // Export PassThrough for compatibility with Node.js core's stream module
  PassThrough
}
module.exports = class StreamError extends Error {
  constructor(msg, code, fn = StreamError) {
    super(msg)

    this.code = code

    if (Error.captureStackTrace) {
      Error.captureStackTrace(this, fn)
    }
  }

  static isStreamDestroyed(err) {
    return err && err.code === 'STREAM_DESTROYED'
  }

  static isPrematureClose(err) {
    return err && err.code === 'PREMATURE_CLOSE'
  }

  static isAborted(err) {
    return err && err.code === 'ABORTED'
  }

  static isBadArgument(err) {
    return err && err.code === 'BAD_ARGUMENT'
  }

  get name() {
    return 'StreamError'
  }

  static STREAM_DESTROYED() {
    return new StreamError('Stream was destroyed', 'STREAM_DESTROYED', StreamError.STREAM_DESTROYED)
  }

  static PREMATURE_CLOSE(msg = 'Premature close') {
    return new StreamError(msg, 'PREMATURE_CLOSE', StreamError.PREMATURE_CLOSE)
  }

  static ABORTED() {
    return new StreamError('Stream aborted', 'ABORTED', StreamError.ABORTED)
  }

  static BAD_ARGUMENT(msg = 'Bad argument') {
    return new StreamError(msg, 'BAD_ARGUMENT', StreamError.BAD_ARGUMENT)
  }
}
{
  "name": "streamx",
  "version": "2.28.1",
  "description": "An iteration of the Node.js core streams with a series of improvements",
  "main": "index.js",
  "exports": {
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    },
    "./package": "./package.json",
    "./errors": {
      "types": "./lib/errors.d.ts",
      "default": "./lib/errors"
    }
  },
  "dependencies": {
    "events-universal": "^1.0.0",
    "fast-fifo": "^1.3.2",
    "text-decoder": "^1.1.0"
  },
  "devDependencies": {
    "b4a": "^1.6.6",
    "brittle": "^3.1.1",
    "end-of-stream": "^1.4.4",
    "lunte": "^1.8.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^2.0.0"
  },
  "files": [
    "index.js",
    "index.d.ts",
    "runtime.d.ts",
    "lib"
  ],
  "scripts": {
    "format": "prettier . --write",
    "test": "brittle test/all.js && brittle-bare test/all.js",
    "lint": "prettier . --check && lunte"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/mafintosh/streamx.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/mafintosh/streamx/issues"
  },
  "homepage": "https://github.com/mafintosh/streamx"
}
const { Readable } = require('streamx')

module.exports = function (s, forks = 2) {
  const streams = new Array(forks)
  const status = new Array(forks).fill(true)

  let ended = false

  for (let i = 0; i < forks; i++) {
    streams[i] = new Readable({
      read (cb) {
        const check = !status[i]
        status[i] = true
        if (check && allReadable()) s.resume()
        cb(null)
      }
    })
  }

  s.on('end', function () {
    ended = true
    for (const stream of streams) stream.push(null)
  })

  s.on('error', function (err) {
    for (const stream of streams) stream.destroy(err)
  })

  s.on('close', function () {
    if (ended) return
    for (const stream of streams) stream.destroy()
  })

  s.on('data', function (data) {
    let needsPause = false
    for (let i = 0; i < streams.length; i++) {
      if (!(status[i] = streams[i].push(data))) {
        needsPause = true
      }
    }
    if (needsPause) s.pause()
  })

  return streams

  function allReadable () {
    for (let j = 0; j < status.length; j++) {
      if (!status[j]) return false
    }
    return true
  }
}
{
  "name": "teex",
  "version": "1.0.1",
  "description": "Turn a readable stream into multiple readable streamx streams",
  "main": "index.js",
  "dependencies": {
    "streamx": "^2.12.5"
  },
  "devDependencies": {
    "standard": "^14.3.4",
    "tape": "^5.0.1"
  },
  "scripts": {
    "test": "standard && tape test.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/mafintosh/teex.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/mafintosh/teex/issues"
  },
  "homepage": "https://github.com/mafintosh/teex"
}
const PassThroughDecoder = require('./lib/pass-through-decoder')
const UTF8Decoder = require('./lib/utf8-decoder')

module.exports = class TextDecoder {
  constructor(encoding = 'utf8') {
    this.encoding = normalizeEncoding(encoding)

    switch (this.encoding) {
      case 'utf8':
        this.decoder = new UTF8Decoder()
        break
      case 'utf16le':
      case 'base64':
        throw new Error('Unsupported encoding: ' + this.encoding)
      default:
        this.decoder = new PassThroughDecoder(this.encoding)
    }
  }

  get remaining() {
    return this.decoder.remaining
  }

  push(data) {
    if (typeof data === 'string') return data
    return this.decoder.decode(data)
  }

  // For Node.js compatibility
  write(data) {
    return this.push(data)
  }

  end(data) {
    let result = ''
    if (data) result = this.push(data)
    result += this.decoder.flush()
    return result
  }
}

function normalizeEncoding(encoding) {
  encoding = encoding.toLowerCase()

  switch (encoding) {
    case 'utf8':
    case 'utf-8':
      return 'utf8'
    case 'ucs2':
    case 'ucs-2':
    case 'utf16le':
    case 'utf-16le':
      return 'utf16le'
    case 'latin1':
    case 'binary':
      return 'latin1'
    case 'base64':
    case 'ascii':
    case 'hex':
      return encoding
    default:
      throw new Error('Unknown encoding: ' + encoding)
  }
}
const b4a = require('b4a')

module.exports = class PassThroughDecoder {
  constructor(encoding) {
    this.encoding = encoding
  }

  get remaining() {
    return 0
  }

  decode(data) {
    return b4a.toString(data, this.encoding)
  }

  flush() {
    return ''
  }
}
const b4a = require('b4a')

/**
 * https://encoding.spec.whatwg.org/#utf-8-decoder
 */
module.exports = class UTF8Decoder {
  constructor() {
    this._reset()
  }

  get remaining() {
    return this.bytesSeen
  }

  decode(data) {
    if (data.byteLength === 0) return ''

    if (this.bytesNeeded === 0 && trailingIncomplete(data, 0) === 0) {
      this.bytesSeen = trailingBytesSeen(data)
      return b4a.toString(data, 'utf8')
    }

    let result = ''
    let start = 0

    if (this.bytesNeeded > 0) {
      while (start < data.byteLength) {
        const byte = data[start]

        if (byte < this.lowerBoundary || byte > this.upperBoundary) {
          result += '\ufffd'
          this._reset()
          break
        }

        this.lowerBoundary = 0x80
        this.upperBoundary = 0xbf
        this.codePoint = (this.codePoint << 6) | (byte & 0x3f)
        this.bytesSeen++
        start++

        if (this.bytesSeen === this.bytesNeeded) {
          result += String.fromCodePoint(this.codePoint)
          this._reset()
          break
        }
      }

      if (this.bytesNeeded > 0) return result
    }

    const trailing = trailingIncomplete(data, start)
    const end = data.byteLength - trailing

    if (end > start) result += b4a.toString(data, 'utf8', start, end)

    for (let i = end; i < data.byteLength; i++) {
      const byte = data[i]

      if (this.bytesNeeded === 0) {
        if (byte <= 0x7f) {
          this.bytesSeen = 0
          result += String.fromCharCode(byte)
        } else if (byte >= 0xc2 && byte <= 0xdf) {
          this.bytesNeeded = 2
          this.bytesSeen = 1
          this.codePoint = byte & 0x1f
        } else if (byte >= 0xe0 && byte <= 0xef) {
          if (byte === 0xe0) this.lowerBoundary = 0xa0
          else if (byte === 0xed) this.upperBoundary = 0x9f
          this.bytesNeeded = 3
          this.bytesSeen = 1
          this.codePoint = byte & 0xf
        } else if (byte >= 0xf0 && byte <= 0xf4) {
          if (byte === 0xf0) this.lowerBoundary = 0x90
          else if (byte === 0xf4) this.upperBoundary = 0x8f
          this.bytesNeeded = 4
          this.bytesSeen = 1
          this.codePoint = byte & 0x7
        } else {
          this.bytesSeen = 1
          result += '\ufffd'
        }

        continue
      }

      if (byte < this.lowerBoundary || byte > this.upperBoundary) {
        result += '\ufffd'
        i--
        this._reset()

        continue
      }

      this.lowerBoundary = 0x80
      this.upperBoundary = 0xbf

      this.codePoint = (this.codePoint << 6) | (byte & 0x3f)
      this.bytesSeen++

      if (this.bytesSeen === this.bytesNeeded) {
        result += String.fromCodePoint(this.codePoint)
        this._reset()
      }
    }

    return result
  }

  flush() {
    const result = this.bytesNeeded > 0 ? '\ufffd' : ''
    this._reset()
    return result
  }

  _reset() {
    this.codePoint = 0
    this.bytesNeeded = 0
    this.bytesSeen = 0
    this.lowerBoundary = 0x80
    this.upperBoundary = 0xbf
  }
}

function trailingIncomplete(data, start) {
  const len = data.byteLength
  if (len <= start) return 0

  const limit = Math.max(start, len - 4)

  let i = len - 1
  while (i > limit && (data[i] & 0xc0) === 0x80) i--

  if (i < start) return 0

  const byte = data[i]

  let needed
  if (byte <= 0x7f) return 0
  if (byte >= 0xc2 && byte <= 0xdf) needed = 2
  else if (byte >= 0xe0 && byte <= 0xef) needed = 3
  else if (byte >= 0xf0 && byte <= 0xf4) needed = 4
  else return 0

  const available = len - i
  return available < needed ? available : 0
}

function trailingBytesSeen(data) {
  const len = data.byteLength
  if (len === 0) return 0

  const last = data[len - 1]

  if (last <= 0x7f) return 0
  if ((last & 0xc0) !== 0x80) return 1

  const limit = Math.max(0, len - 4)

  let i = len - 2
  while (i >= limit && (data[i] & 0xc0) === 0x80) i--

  if (i < 0) return 1

  const first = data[i]

  let needed
  if (first >= 0xc2 && first <= 0xdf) needed = 2
  else if (first >= 0xe0 && first <= 0xef) needed = 3
  else if (first >= 0xf0 && first <= 0xf4) needed = 4
  else return 1

  if (len - i !== needed) return 1

  if (needed >= 3) {
    const second = data[i + 1]
    if (first === 0xe0 && second < 0xa0) return 1
    if (first === 0xed && second > 0x9f) return 1
    if (first === 0xf0 && second < 0x90) return 1
    if (first === 0xf4 && second > 0x8f) return 1
  }

  return 0
}
{
  "name": "text-decoder",
  "version": "1.2.7",
  "description": "Streaming text decoder that preserves multibyte Unicode characters",
  "exports": {
    "./package": "./package.json",
    ".": "./index.js"
  },
  "files": [
    "index.js",
    "lib"
  ],
  "scripts": {
    "format": "prettier --write .",
    "lint": "prettier --check . && lunte",
    "test": "npm run test:node && npm run test:bare",
    "test:node": "brittle-node test.js",
    "test:bare": "brittle-bare test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/text-decoder.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/text-decoder/issues"
  },
  "homepage": "https://github.com/holepunchto/text-decoder#readme",
  "dependencies": {
    "b4a": "^1.6.4"
  },
  "devDependencies": {
    "brittle": "^3.3.2",
    "lunte": "^1.3.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^1.0.0"
  }
}
{
  "name": "favela-surface",
  "version": "0.0.59",
  "description": "The favela renderer \u2014 server-side HTML over the daemon's socket. No storage, no keys, no swarm.",
  "type": "module",
  "main": "index.js",
  "bin": {
    "favela-surface": "index.js"
  },
  "scripts": {
    "test": "brittle-bare test/*.test.js"
  },
  "imports": {
    "#node:net": {
      "bare": "bare-net",
      "default": "net"
    },
    "#node:os": {
      "bare": "bare-os",
      "default": "os"
    },
    "#node:path": {
      "bare": "bare-path",
      "default": "path"
    },
    "#node:process": {
      "bare": "bare-process",
      "default": "process"
    },
    "#node:fs": {
      "bare": "bare-fs",
      "default": "fs"
    }
  },
  "dependencies": {
    "favela-markdown": "file:../favela-markdown",
    "bare-http1": "^4.0.7",
    "bare-net": "^2.3.3",
    "bare-os": "^3.9.3",
    "bare-path": "^3.1.2",
    "bare-process": "^4.5.1",
    "bare-fs": "^4.8.1",
    "bare-crypto": "^1.11.1"
  },
  "devDependencies": {
    "brittle": "^4.0.2"
  },
  "pgh": {
    "bin": "favela-surface",
    "entrypoint": "index.js",
    "type": "daemon",
    "runtime": "bare",
    "upgrade": "pgh://1dd36bdfa9cb7f7d84adbd07f017e7200f7f90a3a546cd0761fac2a1f8a13e86/favela-surface",
    "autostart": false,
    "depends": [
      "pgh-sidecar"
    ],
    "defaults": {
      "env": {
        "FAVELA_SURFACE_PORT": "4042",
        "FAVELA_SOCK": "${HOME}/.config/favela-peer/favela.sock"
      }
    },
    "service": {
      "requiresNetwork": false
    }
  }
}
/**
 * Tree — content-addressable directory hashing for hyper-overlay
 *
 * A tree is a canonical serialization of a directory's immediate entries.
 * Each entry is one of: file, subdirectory, symlink. The tree's identity
 * is blake2b-256 of its canonical bytes.
 *
 * Format (P.10 Phase 0.5a):
 *
 *   tree := "pgh-tree-1\n" ||
 *           varint(n_entries) ||
 *           entry[0] || ... || entry[n-1]
 *
 *   entry := u8(kind)  ||           // 0=file, 1=dir, 2=symlink
 *            u8(exec)  ||           // 0 or 1; always 0 for dir/symlink
 *            varint(name_len) || name ||  // UTF-8 NFC, no NUL, no "/"
 *            32_bytes(child_hash)
 *
 *   tree_hash := blake2b-256(tree_bytes)
 *
 * Identity fields (in the hash): kind, exec-bit, name, child_hash.
 * Explicitly excluded: mtime, size, uid/gid, full mode.
 *
 * Entries MUST be sorted byte-wise by name (after NFC normalization).
 * Encoders emit sorted; decoders reject unsorted trees.
 *
 * Symlinks: child_hash = blake2b-256 of the target path bytes (not followed).
 * Empty directories: zero-entry tree, valid, deterministic.
 */

import fs from '#fs'
import path from '#path'
import crypto from 'hypercore-crypto'
import c from 'compact-encoding'

// ── Constants ─────────────────────────────────────────────────

export const MAGIC = Buffer.from('pgh-tree-1\n', 'utf8')
export const KIND_FILE = 0
export const KIND_DIR = 1
export const KIND_SYMLINK = 2

// Windows reserved device names (case-insensitive, with or without extension)
const WINDOWS_RESERVED = new Set([
  'CON', 'PRN', 'AUX', 'NUL',
  'COM1', 'COM2', 'COM3', 'COM4', 'COM5', 'COM6', 'COM7', 'COM8', 'COM9',
  'LPT1', 'LPT2', 'LPT3', 'LPT4', 'LPT5', 'LPT6', 'LPT7', 'LPT8', 'LPT9'
])

// Chars forbidden in filenames on Windows (beyond format-layer rules)
// eslint-disable-next-line no-control-regex
const WINDOWS_FORBIDDEN = /[\\:*?"<>|\x00-\x1f]/

// Default skip list for walkTree — minimal, just obvious platform cruft.
// Consumers that want more (e.g. .git for pgh publish) pass their own.
export const DEFAULT_SKIP = new Set(['.DS_Store', 'Thumbs.db'])

// ── Name validation (format layer) ────────────────────────────

/**
 * Normalize a filename for hashing and validate format-layer rules.
 * Throws on unrepresentable names. Returns the canonical form.
 *
 * @param {string} name - single path component (no "/")
 * @returns {string} NFC-normalized name
 */
export function normalizeName (name) {
  if (typeof name !== 'string') {
    throw new Error('tree entry name must be string, got ' + typeof name)
  }
  if (name.length === 0) {
    throw new Error('tree entry name cannot be empty')
  }
  if (name === '.' || name === '..') {
    throw new Error('tree entry name cannot be "." or ".."')
  }
  if (name.indexOf('/') !== -1) {
    throw new Error('tree entry name cannot contain "/": ' + JSON.stringify(name))
  }
  if (name.indexOf('\x00') !== -1) {
    throw new Error('tree entry name cannot contain NUL: ' + JSON.stringify(name))
  }

  const normalized = name.normalize('NFC')

  // Verify the normalized form is valid UTF-8 that round-trips through
  // Buffer (catches lone surrogates and other malformed sequences).
  const asBytes = Buffer.from(normalized, 'utf8')
  if (asBytes.toString('utf8') !== normalized) {
    throw new Error('tree entry name is not valid UTF-8: ' + JSON.stringify(name))
  }

  return normalized
}

// ── Consumer-policy layer ─────────────────────────────────────

/**
 * Check a name against Windows compatibility rules.
 * Does NOT throw — returns a reason string (or null if the name is OK).
 * Used by pgh publish for strict cross-platform validation.
 *
 * @param {string} name
 * @returns {string|null} reason if incompatible, null if OK
 */
export function windowsIncompatibleReason (name) {
  if (WINDOWS_FORBIDDEN.test(name)) {
    return 'contains Windows-forbidden character (\\:*?"<>| or control code)'
  }
  const trimmed = name.replace(/\.+$/, '').replace(/ +$/, '')
  if (trimmed !== name) {
    return 'trailing "." or space (Windows strips these silently)'
  }
  // Reserved device names apply with any extension: AUX.log, PRN.txt etc.
  const base = name.split('.')[0].toUpperCase()
  if (WINDOWS_RESERVED.has(base)) {
    return `"${base}" is a reserved Windows device name (even with extension)`
  }
  return null
}

// ── Canonical encoding ────────────────────────────────────────

/**
 * Encode a single entry to its canonical byte representation.
 *
 * @param {object} state - compact-encoding state
 * @param {{kind: number, exec: number, name: string, childHash: Buffer|string}} e
 */
function encodeEntry (state, e) {
  if (e.kind !== KIND_FILE && e.kind !== KIND_DIR && e.kind !== KIND_SYMLINK) {
    throw new Error('invalid entry kind: ' + e.kind)
  }
  if (e.exec !== 0 && e.exec !== 1) {
    throw new Error('entry exec must be 0 or 1')
  }
  if (e.kind !== KIND_FILE && e.exec !== 0) {
    throw new Error('non-file entry must have exec=0')
  }

  const hash = toBuffer32(e.childHash)

  c.uint8.encode(state, e.kind)
  c.uint8.encode(state, e.exec)
  c.string.encode(state, e.name) // compact-encoding: varint len + utf8 bytes
  state.buffer.set(hash, state.start)
  state.start += 32
}

function preencodeEntry (state, e) {
  c.uint8.preencode(state, e.kind)
  c.uint8.preencode(state, e.exec)
  c.string.preencode(state, e.name)
  state.end += 32
}

/**
 * Serialize a list of entries to canonical tree bytes.
 * Entries are sorted + normalized as a side-effect of the call.
 *
 * @param {Array<{kind: number, exec: number, name: string, childHash: Buffer|string}>} entries
 * @returns {Buffer}
 */
export function encodeTree (entries) {
  const normalized = entries.map(e => ({
    kind: e.kind,
    exec: e.exec || 0,
    name: normalizeName(e.name),
    childHash: e.childHash
  }))
  normalized.sort(compareEntries)

  // Sanity: no duplicate names after normalization
  for (let i = 1; i < normalized.length; i++) {
    if (normalized[i].name === normalized[i - 1].name) {
      throw new Error('duplicate entry name after NFC normalization: ' + normalized[i].name)
    }
  }

  const state = { start: 0, end: 0, buffer: null }
  state.end += MAGIC.length
  c.uint.preencode(state, normalized.length)
  for (const e of normalized) preencodeEntry(state, e)

  state.buffer = Buffer.alloc(state.end)

  state.buffer.set(MAGIC, state.start)
  state.start += MAGIC.length
  c.uint.encode(state, normalized.length)
  for (const e of normalized) encodeEntry(state, e)

  return state.buffer
}

/**
 * Byte-wise comparator for entry names (after NFC normalization).
 */
function compareEntries (a, b) {
  const ab = Buffer.from(a.name, 'utf8')
  const bb = Buffer.from(b.name, 'utf8')
  return Buffer.compare(ab, bb)
}

/**
 * Decode canonical tree bytes back to an entries list.
 * Rejects unsorted or malformed trees.
 *
 * @param {Buffer} buf
 * @returns {Array<{kind: number, exec: number, name: string, childHash: string}>}
 */
export function decodeTree (buf) {
  if (!Buffer.isBuffer(buf)) throw new Error('decodeTree expects a Buffer')
  if (buf.length < MAGIC.length) throw new Error('tree too short')
  if (buf.compare(MAGIC, 0, MAGIC.length, 0, MAGIC.length) !== 0) {
    throw new Error('tree magic mismatch (expected "pgh-tree-1")')
  }

  const state = { start: MAGIC.length, end: buf.length, buffer: buf }
  const count = c.uint.decode(state)
  const entries = []

  let prevName = null
  for (let i = 0; i < count; i++) {
    const kind = c.uint8.decode(state)
    const exec = c.uint8.decode(state)
    const name = c.string.decode(state)

    if (state.start + 32 > state.end) throw new Error('truncated tree: missing hash bytes')
    const childHash = buf.subarray(state.start, state.start + 32).toString('hex')
    state.start += 32

    // Validate format rules on read — reject invalid trees rather than silently accept
    const normalized = normalizeName(name)
    if (normalized !== name) {
      throw new Error('tree entry name not NFC-normalized: ' + JSON.stringify(name))
    }
    if (kind !== KIND_FILE && exec !== 0) {
      throw new Error('non-file entry with exec=1 is invalid')
    }
    if (prevName !== null) {
      const cmp = Buffer.compare(Buffer.from(prevName, 'utf8'), Buffer.from(name, 'utf8'))
      if (cmp === 0) throw new Error('tree has duplicate entry: ' + name)
      if (cmp > 0) throw new Error('tree entries not sorted: ' + prevName + ' before ' + name)
    }
    prevName = name

    entries.push({ kind, exec, name, childHash })
  }

  if (state.start !== state.end) throw new Error('tree has trailing bytes after entries')

  return entries
}

/**
 * Compute the tree hash of an entries list.
 *
 * @param {Array<{kind: number, exec: number, name: string, childHash: Buffer|string}>} entries
 * @returns {string} hex-encoded blake2b-256
 */
export function computeTreeHash (entries) {
  const buf = encodeTree(entries)
  return crypto.data(buf).toString('hex')
}

// ── Helpers ───────────────────────────────────────────────────

function toBuffer32 (hashOrBuf) {
  if (Buffer.isBuffer(hashOrBuf)) {
    if (hashOrBuf.length !== 32) throw new Error('childHash buffer must be 32 bytes')
    return hashOrBuf
  }
  if (typeof hashOrBuf !== 'string') {
    throw new Error('childHash must be Buffer or hex string')
  }
  if (hashOrBuf.length !== 64) throw new Error('childHash hex string must be 64 chars')
  return Buffer.from(hashOrBuf, 'hex')
}

// ── Filesystem walk ───────────────────────────────────────────

/**
 * Walk a directory recursively, producing tree hashes bottom-up.
 *
 * Returns a nested object keyed by path, where each entry has:
 *   { hash: hex, entries: [{kind, exec, name, childHash, size?}] }
 *
 * File entries carry their content hash as childHash (blake2b of bytes).
 * Dir entries carry the subtree hash.
 * Symlink entries carry blake2b of the link target bytes.
 *
 * @param {string} rootDir - absolute path to scan
 * @param {object} [opts]
 * @param {Set<string>} [opts.skip] - basenames to skip (default: DEFAULT_SKIP)
 * @param {(relPath: string, baseName: string) => boolean} [opts.matcher]
 *   Optional gitignore-style matcher. Returns true to EXCLUDE an entry.
 *   Receives the path relative to rootDir plus the basename. Applies to
 *   all kinds (file/dir/symlink). Used by `pgh publish` for publishIgnore.
 * @returns {Map<string, {hash: string, entries: Array, size: number}>}
 *          map keyed by relative path ('' = root); root tree is at key ''
 */
export function walkTree (rootDir, opts = {}) {
  const skip = opts.skip || DEFAULT_SKIP
  const matcher = opts.matcher || null
  const trees = new Map()

  function visit (relPath) {
    const absPath = relPath === '' ? rootDir : path.join(rootDir, relPath)
    const raw = fs.readdirSync(absPath)
    const entries = []
    let totalSize = 0

    for (const name of raw) {
      if (skip.has(name)) continue

      const entryAbs = path.join(absPath, name)
      const entryRel = relPath === '' ? name : relPath + '/' + name

      if (matcher && matcher(entryRel, name)) continue

      let stat
      try {
        stat = fs.lstatSync(entryAbs)
      } catch {
        continue
      }

      if (stat.isSymbolicLink()) {
        const target = fs.readlinkSync(entryAbs)
        const targetBytes = Buffer.from(target, 'utf8')
        const childHash = crypto.data(targetBytes).toString('hex')
        entries.push({
          kind: KIND_SYMLINK,
          exec: 0,
          name: normalizeName(name),
          childHash,
          size: targetBytes.length,
          linkTarget: target
        })
        totalSize += targetBytes.length
      } else if (stat.isDirectory()) {
        visit(entryRel)
        const sub = trees.get(entryRel)
        if (!sub) continue
        entries.push({
          kind: KIND_DIR,
          exec: 0,
          name: normalizeName(name),
          childHash: sub.hash,
          size: sub.size
        })
        totalSize += sub.size
      } else if (stat.isFile()) {
        const content = fs.readFileSync(entryAbs)
        const childHash = crypto.data(content).toString('hex')
        // Executable bit from stat.mode — works on POSIX; Windows always 0
        const exec = (stat.mode & 0o100) ? 1 : 0
        entries.push({
          kind: KIND_FILE,
          exec,
          name: normalizeName(name),
          childHash,
          size: stat.size
        })
        totalSize += stat.size
      }
      // device files, sockets, fifos: silently skipped (unrepresentable)
    }

    entries.sort(compareEntries)
    const hash = computeTreeHash(entries)
    trees.set(relPath, { hash, entries, size: totalSize })
  }

  visit('')
  return trees
}

// ── Publish-time validation (consumer policy) ─────────────────

/**
 * Scan entries (recursively via walkTree output) for cross-platform-hostile
 * names. Returns an array of {path, name, reason} for every problem found.
 * Does NOT throw — caller decides how to present.
 *
 * @param {Map<string, {hash: string, entries: Array}>} trees - from walkTree
 * @returns {Array<{path: string, name: string, reason: string}>}
 */
export function validateForPublish (trees) {
  const problems = []
  for (const [relPath, tree] of trees) {
    for (const e of tree.entries) {
      const reason = windowsIncompatibleReason(e.name)
      if (reason) {
        problems.push({
          path: relPath ? relPath + '/' + e.name : e.name,
          name: e.name,
          reason
        })
      }
    }
  }
  return problems
}
function isBuffer(value) {
  return Buffer.isBuffer(value) || value instanceof Uint8Array
}

function isEncoding(encoding) {
  return Buffer.isEncoding(encoding)
}

function alloc(size, fill, encoding) {
  return Buffer.alloc(size, fill, encoding)
}

function allocUnsafe(size) {
  return Buffer.allocUnsafe(size)
}

function allocUnsafeSlow(size) {
  return Buffer.allocUnsafeSlow(size)
}

function byteLength(string, encoding) {
  return Buffer.byteLength(string, encoding)
}

function compare(a, b) {
  return Buffer.compare(a, b)
}

function concat(buffers, totalLength) {
  return Buffer.concat(buffers, totalLength)
}

function copy(source, target, targetStart, start, end) {
  return toBuffer(source).copy(target, targetStart, start, end)
}

function equals(a, b) {
  return toBuffer(a).equals(b)
}

function fill(buffer, value, offset, end, encoding) {
  return toBuffer(buffer).fill(value, offset, end, encoding)
}

function from(value, encodingOrOffset, length) {
  return Buffer.from(value, encodingOrOffset, length)
}

function includes(buffer, value, byteOffset, encoding) {
  return toBuffer(buffer).includes(value, byteOffset, encoding)
}

function indexOf(buffer, value, byfeOffset, encoding) {
  return toBuffer(buffer).indexOf(value, byfeOffset, encoding)
}

function lastIndexOf(buffer, value, byteOffset, encoding) {
  return toBuffer(buffer).lastIndexOf(value, byteOffset, encoding)
}

function swap16(buffer) {
  return toBuffer(buffer).swap16()
}

function swap32(buffer) {
  return toBuffer(buffer).swap32()
}

function swap64(buffer) {
  return toBuffer(buffer).swap64()
}

function toBuffer(buffer) {
  if (Buffer.isBuffer(buffer)) return buffer
  return Buffer.from(buffer.buffer, buffer.byteOffset, buffer.byteLength)
}

function toString(buffer, encoding, start, end) {
  return toBuffer(buffer).toString(encoding, start, end)
}

function write(buffer, string, offset, length, encoding) {
  return toBuffer(buffer).write(string, offset, length, encoding)
}

function readDoubleBE(buffer, offset) {
  return toBuffer(buffer).readDoubleBE(offset)
}

function readDoubleLE(buffer, offset) {
  return toBuffer(buffer).readDoubleLE(offset)
}

function readFloatBE(buffer, offset) {
  return toBuffer(buffer).readFloatBE(offset)
}

function readFloatLE(buffer, offset) {
  return toBuffer(buffer).readFloatLE(offset)
}

function readInt32BE(buffer, offset) {
  return toBuffer(buffer).readInt32BE(offset)
}

function readInt32LE(buffer, offset) {
  return toBuffer(buffer).readInt32LE(offset)
}

function readUInt32BE(buffer, offset) {
  return toBuffer(buffer).readUInt32BE(offset)
}

function readUInt32LE(buffer, offset) {
  return toBuffer(buffer).readUInt32LE(offset)
}

function writeDoubleBE(buffer, value, offset) {
  return toBuffer(buffer).writeDoubleBE(value, offset)
}

function writeDoubleLE(buffer, value, offset) {
  return toBuffer(buffer).writeDoubleLE(value, offset)
}

function writeFloatBE(buffer, value, offset) {
  return toBuffer(buffer).writeFloatBE(value, offset)
}

function writeFloatLE(buffer, value, offset) {
  return toBuffer(buffer).writeFloatLE(value, offset)
}

function writeInt32BE(buffer, value, offset) {
  return toBuffer(buffer).writeInt32BE(value, offset)
}

function writeInt32LE(buffer, value, offset) {
  return toBuffer(buffer).writeInt32LE(value, offset)
}

function writeUInt32BE(buffer, value, offset) {
  return toBuffer(buffer).writeUInt32BE(value, offset)
}

function writeUInt32LE(buffer, value, offset) {
  return toBuffer(buffer).writeUInt32LE(value, offset)
}

module.exports = {
  isBuffer,
  isEncoding,
  alloc,
  allocUnsafe,
  allocUnsafeSlow,
  byteLength,
  compare,
  concat,
  copy,
  equals,
  fill,
  from,
  includes,
  indexOf,
  lastIndexOf,
  swap16,
  swap32,
  swap64,
  toBuffer,
  toString,
  write,
  readDoubleBE,
  readDoubleLE,
  readFloatBE,
  readFloatLE,
  readInt32BE,
  readInt32LE,
  readUInt32BE,
  readUInt32LE,
  writeDoubleBE,
  writeDoubleLE,
  writeFloatBE,
  writeFloatLE,
  writeInt32BE,
  writeInt32LE,
  writeUInt32BE,
  writeUInt32LE
}
{
  "name": "b4a",
  "version": "1.8.1",
  "description": "Bridging the gap between buffers and typed arrays",
  "exports": {
    "./package": "./package.json",
    ".": {
      "react-native": "./react-native.js",
      "browser": "./browser.js",
      "default": "./index.js"
    }
  },
  "files": [
    "browser.js",
    "index.js",
    "react-native.js",
    "lib"
  ],
  "scripts": {
    "format": "prettier --write . && lunte --fix",
    "lint": "prettier --check . && lunte",
    "test": "npm run test:bare && npm run test:node",
    "test:bare": "brittle-bare --coverage test.mjs",
    "test:node": "brittle-node --coverage test.mjs"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/b4a.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/b4a/issues"
  },
  "homepage": "https://github.com/holepunchto/b4a#readme",
  "devDependencies": {
    "brittle": "^3.5.2",
    "lunte": "^1.7.0",
    "nanobench": "^3.0.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^2.0.0"
  },
  "peerDependencies": {
    "react-native-b4a": "*"
  },
  "peerDependenciesMeta": {
    "react-native-b4a": {
      "optional": true
    }
  }
}
const ESC = '\x1b'
const CSI = ESC + '['
const SGR = (n) => CSI + n + 'm'

exports.constants = {
  ESC,
  CSI,
  SGR
}

// https://en.wikipedia.org/wiki/ANSI_escape_code#CSI_(Control_Sequence_Introducer)_sequences

exports.cursorHide = CSI + '?25l'
exports.cursorShow = CSI + '?25h'

exports.cursorUp = function cursorUp(n = 1) {
  return CSI + n + 'A'
}

exports.cursorDown = function cursorDown(n = 1) {
  return CSI + n + 'B'
}

exports.cursorForward = function cursorForward(n = 1) {
  return CSI + n + 'C'
}

exports.cursorBack = function cursorBack(n = 1) {
  return CSI + n + 'D'
}

exports.cursorNextLine = function cursorNextLine(n = 1) {
  return CSI + n + 'E'
}

exports.cursorPreviousLine = function cursorPreviousLine(n = 1) {
  return CSI + n + 'F'
}

exports.cursorPosition = function cursorPosition(column, row = 0) {
  if (row === 0) return CSI + (column + 1) + 'G'

  return CSI + (row + 1) + ';' + (column + 1) + 'H'
}

exports.eraseDisplayEnd = CSI + 'J'
exports.eraseDisplayStart = CSI + '1J'
exports.eraseDisplay = CSI + '2J'
exports.eraseLineEnd = CSI + 'K'
exports.eraseLineStart = CSI + '1K'
exports.eraseLine = CSI + '2K'

exports.scrollUp = function scrollUp(n = 1) {
  return CSI + n + 'S'
}

exports.scrollDown = function scrollDown(n = 1) {
  return CSI + n + 'T'
}

// https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_(Select_Graphic_Rendition)_parameters

exports.modifierReset = SGR(0)
exports.modifierBold = SGR(1)
exports.modifierDim = SGR(2)
exports.modifierItalic = SGR(3)
exports.modifierUnderline = SGR(4)
exports.modifierNormal = SGR(22)
exports.modifierNotItalic = SGR(23)
exports.modifierNotUnderline = SGR(24)

// https://en.wikipedia.org/wiki/ANSI_escape_code#Colors

exports.colorBlack = SGR(30)
exports.colorRed = SGR(31)
exports.colorGreen = SGR(32)
exports.colorYellow = SGR(33)
exports.colorBlue = SGR(34)
exports.colorMagenta = SGR(35)
exports.colorCyan = SGR(36)
exports.colorWhite = SGR(37)
exports.colorDefault = SGR(39)
exports.colorBrightBlack = SGR(90)
exports.colorBrightRed = SGR(91)
exports.colorBrightGreen = SGR(92)
exports.colorBrightYellow = SGR(93)
exports.colorBrightBlue = SGR(94)
exports.colorBrightMagenta = SGR(95)
exports.colorBrightCyan = SGR(96)
exports.colorBrightWhite = SGR(97)
{
  "name": "bare-ansi-escapes",
  "version": "2.2.3",
  "description": "Parse and produce ANSI escape sequences",
  "exports": {
    "./package": "./package.json",
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    },
    "./key-decoder": {
      "types": "./key-decoder.d.ts",
      "default": "./key-decoder.js"
    }
  },
  "files": [
    "index.js",
    "index.d.ts",
    "key-decoder.js",
    "key-decoder.d.ts"
  ],
  "scripts": {
    "test": "prettier . --check && bare test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-ansi-escapes.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-ansi-escapes/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-ansi-escapes#readme",
  "dependencies": {
    "bare-stream": "^2.6.5"
  },
  "devDependencies": {
    "bare-buffer": "^3.0.2",
    "brittle": "^3.3.2",
    "prettier": "^3.4.2",
    "prettier-config-standard": "^7.0.0"
  },
  "peerDependencies": {
    "bare-buffer": "*"
  },
  "peerDependenciesMeta": {
    "bare-buffer": {
      "optional": true
    }
  }
}
const inspect = require('bare-inspect')

class AssertionError extends Error {
  constructor(opts = {}) {
    let { message = null, actual, expected, operator } = opts

    if (message === null) {
      message = `${inspect(actual)} ${operator} ${inspect(expected)}`
    }

    super(message)

    this.actual = actual
    this.expected = expected
    this.operator = operator
  }

  get name() {
    return 'AssertionError'
  }

  get code() {
    'ASSERTION'
  }
}

function assertFail(opts, fn) {
  if (opts.message instanceof Error) throw opts.message

  const err = new AssertionError(opts)

  if (Error.captureStackTrace) Error.captureStackTrace(err, fn)

  throw err
}

module.exports = exports = function assert(actual, message) {
  if (actual) return

  assertFail({ message, actual, expected: true, operator: '==' }, assert)
}

exports.AssertionError = AssertionError

exports.fail = function fail(message) {
  if (message === undefined) message = 'Failed'

  assertFail({ message, operator: 'fail' }, fail)
}

exports.ok = function ok(actual, message) {
  if (actual) return

  assertFail({ message, actual, expected: true, operator: '==' }, ok)
}

exports.notOk = function ok(actual, message) {
  if (!actual) return

  assertFail({ message, actual, expected: false, operator: '==' }, ok)
}

exports.equal = function equal(actual, expected, message) {
  if (actual == expected || (actual !== actual && expected !== expected)) {
    return
  }

  assertFail({ message, actual, expected, operator: '==' }, equal)
}

exports.notEqual = function notEqual(actual, expected, message) {
  if (actual != expected && (actual === actual || expected === expected)) {
    return
  }

  assertFail({ message, actual, expected, operator: '!=' }, notEqual)
}

exports.strictEqual = function strictEqual(actual, expected, message) {
  if (Object.is(actual, expected)) return

  assertFail(
    { message, actual, expected, operator: 'strictEqual' },
    strictEqual
  )
}

exports.notStrictEqual = function notStrictEqual(actual, expected, message) {
  if (!Object.is(actual, expected)) return

  assertFail(
    { message, actual, expected, operator: 'notStrictEqual' },
    notStrictEqual
  )
}
{
  "name": "bare-assert",
  "version": "1.2.0",
  "description": "Assertion library for JavaScript",
  "exports": {
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    },
    "./package": "./package.json"
  },
  "files": [
    "index.js",
    "index.d.ts"
  ],
  "scripts": {
    "test": "prettier . --check && bare test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-assert.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-assert/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-assert#readme",
  "dependencies": {
    "bare-inspect": "^3.1.2"
  },
  "devDependencies": {
    "brittle": "^3.1.1",
    "prettier": "^3.4.2",
    "prettier-config-standard": "^7.0.0"
  }
}
const errors = require('./lib/errors')

class EventListener {
  constructor() {
    this.list = []
    this.count = 0
  }

  append(ctx, name, fn, once) {
    this.count++
    ctx.emit('newListener', name, fn) // Emit BEFORE adding
    this.list.push([fn, once])
  }

  prepend(ctx, name, fn, once) {
    this.count++
    ctx.emit('newListener', name, fn) // Emit BEFORE adding
    this.list.unshift([fn, once])
  }

  remove(ctx, name, fn) {
    for (let i = 0, n = this.list.length; i < n; i++) {
      const l = this.list[i]

      if (l[0] === fn) {
        this.list.splice(i, 1)

        if (this.count === 1) delete ctx._events[name]

        ctx.emit('removeListener', name, fn) // Emit AFTER removing

        this.count--
        return
      }
    }
  }

  removeAll(ctx, name) {
    const list = [...this.list]
    this.list = []

    if (this.count === list.length) delete ctx._events[name]

    for (let i = list.length - 1; i >= 0; i--) {
      ctx.emit('removeListener', name, list[i][0]) // Emit AFTER removing
    }

    this.count -= list.length
  }

  emit(ctx, name, ...args) {
    const list = [...this.list]

    for (let i = 0, n = list.length; i < n; i++) {
      const l = list[i]

      if (l[1] === true) this.remove(ctx, name, l[0])

      Reflect.apply(l[0], ctx, args)
    }

    return list.length > 0
  }
}

function appendListener(ctx, name, fn, once) {
  if (ctx._events === undefined) ctx._events = Object.create(null)
  const e = ctx._events[name] || (ctx._events[name] = new EventListener())
  e.append(ctx, name, fn, once)
  return ctx
}

function prependListener(ctx, name, fn, once) {
  if (ctx._events === undefined) ctx._events = Object.create(null)
  const e = ctx._events[name] || (ctx._events[name] = new EventListener())
  e.prepend(ctx, name, fn, once)
  return ctx
}

function removeListener(ctx, name, fn) {
  if (ctx._events === undefined) return ctx
  const e = ctx._events[name]
  if (e !== undefined) e.remove(ctx, name, fn)
  return ctx
}

function throwUnhandledError(...args) {
  let err

  if (args.length > 0) err = args[0]

  if (err instanceof Error === false) err = errors.UNHANDLED_ERROR(err)

  if (Error.captureStackTrace) {
    Error.captureStackTrace(err, exports.prototype.emit)
  }

  queueMicrotask(() => {
    throw err
  })
}

module.exports = exports = class EventEmitter {
  constructor() {
    this._events = Object.create(null)
  }

  addListener(name, fn) {
    return appendListener(this, name, fn, false)
  }

  addOnceListener(name, fn) {
    return appendListener(this, name, fn, true)
  }

  prependListener(name, fn) {
    return prependListener(this, name, fn, false)
  }

  prependOnceListener(name, fn) {
    return prependListener(this, name, fn, true)
  }

  removeListener(name, fn) {
    return removeListener(this, name, fn)
  }

  on(name, fn) {
    return appendListener(this, name, fn, false)
  }

  once(name, fn) {
    return appendListener(this, name, fn, true)
  }

  off(name, fn) {
    return removeListener(this, name, fn)
  }

  emit(name, ...args) {
    if (name === 'error' && this._events !== undefined && this._events.error === undefined) {
      throwUnhandledError(...args)
    }

    if (this._events === undefined) return false
    const e = this._events[name]
    return e === undefined ? false : e.emit(this, name, ...args)
  }

  listeners(name) {
    if (this._events === undefined) return []
    const e = this._events[name]
    return e === undefined ? [] : [...e.list]
  }

  rawListeners(name) {
    if (this._events === undefined) return []
    const e = this._events[name]
    return e === undefined ? [] : e.list.map((l) => l[0])
  }

  eventNames() {
    if (this._events === undefined) return []
    return Reflect.ownKeys(this._events)
  }

  listenerCount(name) {
    if (this._events === undefined) return 0
    const e = this._events[name]
    return e === undefined ? 0 : e.list.length
  }

  getMaxListeners() {
    return EventEmitter.defaultMaxListeners
  }

  setMaxListeners(n) {}

  removeAllListeners(name) {
    if (arguments.length === 0) {
      for (const key of Reflect.ownKeys(this._events)) {
        if (key === 'removeListener') continue
        this.removeAllListeners(key)
      }
      this.removeAllListeners('removeListener')
    } else {
      const e = this._events[name]
      if (e !== undefined) e.removeAll(this, name)
    }
    return this
  }
}

exports.EventEmitter = exports

exports.errors = errors

exports.defaultMaxListeners = 10

exports.on = function on(emitter, name, opts = {}) {
  const { signal } = opts

  if (signal && signal.aborted) {
    throw errors.OPERATION_ABORTED(signal.reason)
  }

  let error = null
  let done = false

  const events = []
  const promises = []

  if (name !== 'error') emitter.on('error', onerror)

  if (signal) signal.addEventListener('abort', onabort)

  emitter.on(name, onevent)

  return {
    next() {
      if (events.length) {
        return Promise.resolve({ value: events.shift(), done: false })
      }

      if (error) {
        const err = error

        error = null

        return Promise.reject(err)
      }

      if (done) return onclose()

      return new Promise((resolve, reject) => promises.push({ resolve, reject }))
    },

    return() {
      return onclose()
    },

    throw(err) {
      return onerror(err)
    },

    [Symbol.asyncIterator]() {
      return this
    }
  }

  function onevent(...args) {
    if (promises.length) {
      promises.shift().resolve({ value: args, done: false })
    } else {
      events.push(args)
    }
  }

  function onerror(err) {
    emitter.off(name, onevent).off('error', onerror)

    if (promises.length) {
      promises.shift().reject(err)
    } else {
      error = err
    }

    return Promise.resolve({ done: true })
  }

  function onabort() {
    signal.removeEventListener('abort', onabort)

    onerror(errors.OPERATION_ABORTED(signal.reason))
  }

  function onclose() {
    emitter.off(name, onevent)

    if (name !== 'error') emitter.off('error', onerror)

    if (signal) signal.removeEventListener('abort', onabort)

    done = true

    if (promises.length) promises.shift().resolve({ done: true })

    return Promise.resolve({ done: true })
  }
}

exports.once = function once(emitter, name, opts = {}) {
  const { signal } = opts

  if (signal && signal.aborted) {
    return Promise.reject(errors.OPERATION_ABORTED(signal.reason))
  }

  return new Promise((resolve, reject) => {
    if (name !== 'error') emitter.on('error', onerror)

    if (signal) signal.addEventListener('abort', onabort)

    emitter.once(name, onevent)

    function onevent(...args) {
      if (name !== 'error') emitter.off('error', onerror)

      if (signal) signal.removeEventListener('abort', onabort)

      resolve(args)
    }

    function onerror(err) {
      emitter.off(name, onevent)

      if (name !== 'error') emitter.off('error', onerror)

      reject(err)
    }

    function onabort() {
      signal.removeEventListener('abort', onabort)

      onerror(errors.OPERATION_ABORTED(signal.reason))
    }
  })
}

exports.forward = function forward(from, to, names, opts = {}) {
  if (typeof names === 'string') names = [names]

  const { emit = to.emit.bind(to) } = opts

  const listeners = names.map(
    (name) =>
      function onevent(...args) {
        emit(name, ...args)
      }
  )

  to.on('newListener', (name) => {
    const i = names.indexOf(name)

    if (i !== -1 && to.listenerCount(name) === 0) {
      from.on(name, listeners[i])
    }
  }).on('removeListener', (name) => {
    const i = names.indexOf(name)

    if (i !== -1 && to.listenerCount(name) === 0) {
      from.off(name, listeners[i])
    }
  })
}

exports.listenerCount = function listenerCount(emitter, name) {
  return emitter.listenerCount(name)
}

exports.getMaxListeners = function getMaxListeners(emitter) {
  if (typeof emitter.getMaxListeners === 'function') {
    return emitter.getMaxListeners()
  }

  return exports.defaultMaxListeners
}

exports.setMaxListeners = function setMaxListeners(n, ...emitters) {
  if (emitters.length === 0) exports.defaultMaxListeners = n
  else {
    for (const emitter of emitters) {
      if (typeof emitter.setMaxListeners === 'function') {
        emitter.setMaxListeners(n)
      }
    }
  }
}
module.exports = class EventEmitterError extends Error {
  constructor(msg, code, fn = EventEmitterError, opts) {
    super(`${code}: ${msg}`, opts)
    this.code = code

    if (Error.captureStackTrace) {
      Error.captureStackTrace(this, fn)
    }
  }

  get name() {
    return 'EventEmitterError'
  }

  static OPERATION_ABORTED(cause, msg = 'Operation aborted') {
    return new EventEmitterError(msg, 'OPERATION_ABORTED', EventEmitterError.OPERATION_ABORTED, {
      cause
    })
  }

  static UNHANDLED_ERROR(cause, msg = 'Unhandled error') {
    return new EventEmitterError(msg, 'UNHANDLED_ERROR', EventEmitterError.UNHANDLED_ERROR, {
      cause
    })
  }
}
{
  "name": "bare-events",
  "version": "2.9.1",
  "description": "Event emitters for JavaScript",
  "exports": {
    "./package": "./package.json",
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    },
    "./global": {
      "types": "./global.d.ts",
      "default": "./global.js"
    },
    "./web": {
      "types": "./web.d.ts",
      "default": "./web.js"
    },
    "./errors": "./lib/errors.js"
  },
  "files": [
    "index.js",
    "index.d.ts",
    "global.js",
    "global.d.ts",
    "web.js",
    "web.d.ts",
    "lib"
  ],
  "scripts": {
    "format": "prettier --write . && lunte --fix",
    "lint": "prettier --check . && lunte",
    "test": "npm run test:bare && npm run test:node",
    "test:bare": "brittle-bare --coverage test.js",
    "test:node": "brittle-node --coverage test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-events.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-events/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-events#readme",
  "devDependencies": {
    "bare-abort-controller": "^1.0.0",
    "brittle": "^3.3.2",
    "lunte": "^1.8.0",
    "prettier": "^3.4.2",
    "prettier-config-holepunch": "^2.0.0",
    "uncaughts": "^1.1.1"
  },
  "peerDependencies": {
    "bare-abort-controller": "*"
  },
  "peerDependenciesMeta": {
    "bare-abort-controller": {
      "optional": true
    }
  }
}
module.exports = require.addon()
const FIFO = require('fast-fifo')
const EventEmitter = require('bare-events')
const path = require('bare-path')
const { isURL, fileURLToPath } = require('bare-url')
const { Readable, Writable } = require('bare-stream')
const binding = require('./binding')
const constants = require('./lib/constants')
const FileError = require('./lib/errors')

const isWindows = Bare.platform === 'win32'

exports.constants = constants

class FileRequest {
  static borrow() {
    if (this._free.length > 0) return this._free.pop()
    return new FileRequest()
  }

  static return(req) {
    if (this._free.length < 32) this._free.push(req.reset())
    else req.destroy()
  }

  constructor() {
    this._reset()
    this._handle = binding.requestInit(this, this._onresult)
  }

  get handle() {
    return this._handle
  }

  retain(value) {
    this._retain = value // Tie the lifetime of `value` to the lifetime of `this`
  }

  reset() {
    if (this._handle === null) return this

    binding.requestReset(this._handle)

    this._reset()

    return this
  }

  destroy() {
    if (this._handle === null) return this

    binding.requestDestroy(this._handle)

    this._reset()
    this._handle = null

    return this
  }

  then(resolve, reject) {
    return this._promise.then(resolve, reject)
  }

  return() {
    if (this._handle === null) return this

    FileRequest.return(this)

    return this
  }

  _reset() {
    this._promise = new Promise((resolve, reject) => {
      this._resolve = resolve
      this._reject = reject
    })
    this._retain = null
  }

  _onresult(err, status) {
    if (err) this._reject(err)
    else this._resolve(status)
  }
}

FileRequest._free = []

function ok(result, cb) {
  if (typeof result === 'function') {
    cb = result
    result = undefined
  }

  if (cb) cb(null, result)
  else return result
}

function fail(err, cb) {
  if (cb) cb(err)
  else throw err
}

function done(err, result, cb) {
  if (typeof result === 'function') {
    cb = result
    result = undefined
  }

  if (err) fail(err, cb)
  else return ok(result, cb)
}

async function open(filepath, flags = 'r', mode = 0o666, cb) {
  if (typeof flags === 'function') {
    cb = flags
    flags = 'r'
    mode = 0o666
  } else if (typeof mode === 'function') {
    cb = mode
    mode = 0o666
  }

  if (typeof flags === 'string') flags = toFlags(flags)
  if (typeof mode === 'string') mode = toMode(mode)

  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  let fd
  let err = null
  try {
    binding.open(req.handle, filepath, flags, mode)

    fd = await req
  } catch (e) {
    err = new FileError(e.message, {
      operation: 'open',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }

  return done(err, fd, cb)
}

function openSync(filepath, flags = 'r', mode = 0o666) {
  if (typeof flags === 'string') flags = toFlags(flags)
  if (typeof mode === 'string') mode = toMode(mode)

  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  try {
    return binding.openSync(req.handle, filepath, flags, mode)
  } catch (e) {
    throw new FileError(e.message, {
      operation: 'open',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }
}

async function close(fd, cb) {
  const req = FileRequest.borrow()

  let err = null
  try {
    binding.close(req.handle, fd)

    await req
  } catch (e) {
    err = new FileError(e.message, { operation: 'close', code: e.code, fd })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function closeSync(fd) {
  const req = FileRequest.borrow()

  try {
    binding.closeSync(req.handle, fd)
  } catch (e) {
    throw new FileError(e.message, { operation: 'close', code: e.code, fd })
  } finally {
    req.return()
  }
}

async function access(filepath, mode = constants.F_OK, cb) {
  if (typeof mode === 'function') {
    cb = mode
    mode = constants.F_OK
  }

  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  let err = null
  try {
    binding.access(req.handle, filepath, mode)

    await req
  } catch (e) {
    err = new FileError(e.message, {
      operation: 'access',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function accessSync(filepath, mode = constants.F_OK) {
  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  try {
    binding.accessSync(req.handle, filepath, mode)
  } catch (e) {
    throw new FileError(e.message, {
      operation: 'access',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }
}

async function exists(filepath, cb) {
  let ok = true
  try {
    await access(filepath)
  } catch {
    ok = false
  }

  return done(null, ok, cb)
}

function existsSync(filepath) {
  try {
    accessSync(filepath)
  } catch {
    return false
  }

  return true
}

async function read(fd, buffer, offset = 0, len = buffer.byteLength - offset, pos = -1, cb) {
  if (typeof offset === 'function') {
    cb = offset
    offset = 0
    len = buffer.byteLength
    pos = -1
  } else if (typeof len === 'function') {
    cb = len
    len = buffer.byteLength - offset
    pos = -1
  } else if (typeof pos === 'function') {
    cb = pos
    pos = -1
  }

  if (typeof pos !== 'number') pos = -1

  const req = FileRequest.borrow()

  let bytes
  let err = null
  try {
    binding.read(req.handle, fd, buffer, offset, len, pos)

    req.retain(buffer)

    bytes = await req
  } catch (e) {
    err = new FileError(e.message, { operation: 'read', code: e.code, fd })
  } finally {
    req.return()
  }

  return done(err, bytes, cb)
}

function readSync(fd, buffer, offset = 0, len = buffer.byteLength - offset, pos = -1) {
  const req = FileRequest.borrow()

  try {
    return binding.readSync(req.handle, fd, buffer, offset, len, pos)
  } catch (e) {
    throw new FileError(e.message, { operation: 'read', code: e.code, fd })
  } finally {
    req.return()
  }
}

async function readv(fd, buffers, pos = -1, cb) {
  if (typeof pos === 'function') {
    cb = pos
    pos = -1
  }

  if (typeof pos !== 'number') pos = -1

  const req = FileRequest.borrow()

  let bytes
  let err = null
  try {
    binding.readv(req.handle, fd, buffers, pos)

    req.retain(buffers)

    bytes = await req
  } catch (e) {
    err = new FileError(e.message, { operation: 'readv', code: e.code, fd })
  } finally {
    req.return()
  }

  return done(err, bytes, cb)
}

function readvSync(fd, buffers, pos = -1) {
  if (typeof pos !== 'number') pos = -1

  const req = FileRequest.borrow()

  try {
    return binding.readvSync(req.handle, fd, buffers, pos)
  } catch (e) {
    throw new FileError(e.message, { operation: 'readv', code: e.code, fd })
  } finally {
    req.return()
  }
}

async function write(fd, data, offset, len, pos = -1, cb) {
  if (typeof data === 'string') {
    let encoding = len
    cb = pos
    pos = offset

    if (typeof pos === 'function') {
      cb = pos
      pos = -1
      encoding = 'utf8'
    } else if (typeof encoding === 'function') {
      cb = encoding
      encoding = 'utf8'
    }

    if (typeof pos === 'string') {
      encoding = pos
      pos = -1
    }

    data = Buffer.from(data, encoding)
    offset = 0
    len = data.byteLength
  } else if (typeof offset === 'function') {
    cb = offset
    offset = 0
    len = data.byteLength
    pos = -1
  } else if (typeof len === 'function') {
    cb = len
    len = data.byteLength - offset
    pos = -1
  } else if (typeof pos === 'function') {
    cb = pos
    pos = -1
  }

  if (typeof offset !== 'number') offset = 0
  if (typeof len !== 'number') len = data.byteLength - offset
  if (typeof pos !== 'number') pos = -1

  const req = FileRequest.borrow()

  let bytes
  let err = null
  try {
    binding.write(req.handle, fd, data, offset, len, pos)

    req.retain(data)

    bytes = await req
  } catch (e) {
    err = new FileError(e.message, { operation: 'write', code: e.code, fd })
  } finally {
    req.return()
  }

  return done(err, bytes, cb)
}

function writeSync(fd, data, offset, len, pos = -1) {
  if (typeof data === 'string') {
    let encoding = len
    pos = offset

    if (typeof pos === 'string') {
      encoding = pos
      pos = -1
    }

    data = Buffer.from(data, encoding)
    offset = 0
    len = data.byteLength
  }

  if (typeof offset !== 'number') offset = 0
  if (typeof len !== 'number') len = data.byteLength - offset
  if (typeof pos !== 'number') pos = -1

  const req = FileRequest.borrow()

  try {
    return binding.writeSync(req.handle, fd, data, offset, len, pos)
  } catch (e) {
    throw new FileError(e.message, { operation: 'write', code: e.code, fd })
  } finally {
    req.return()
  }
}

async function writev(fd, buffers, pos = -1, cb) {
  if (typeof pos === 'function') {
    cb = pos
    pos = -1
  }

  if (typeof pos !== 'number') pos = -1

  const req = FileRequest.borrow()

  let bytes
  let err = null
  try {
    binding.writev(req.handle, fd, buffers, pos)

    req.retain(buffers)

    bytes = await req
  } catch (e) {
    err = new FileError(e.message, { operation: 'writev', code: e.code, fd })
  } finally {
    req.return()
  }

  return done(err, bytes, cb)
}

function writevSync(fd, buffers, pos = -1) {
  if (typeof pos !== 'number') pos = -1

  const req = FileRequest.borrow()

  try {
    return binding.writevSync(req.handle, fd, buffers, pos)
  } catch (e) {
    throw new FileError(e.message, { operation: 'writev', code: e.code, fd })
  } finally {
    req.return()
  }
}

async function stat(filepath, cb) {
  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  let st
  let err = null
  try {
    binding.stat(req.handle, filepath)

    await req

    st = new Stats(...binding.requestResultStat(req.handle))
  } catch (e) {
    err = new FileError(e.message, { operation: 'stat', code: e.code, path: filepath })
  } finally {
    req.return()
  }

  return done(err, st, cb)
}

function statSync(filepath) {
  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  try {
    binding.statSync(req.handle, filepath)

    return new Stats(...binding.requestResultStat(req.handle))
  } catch (e) {
    throw new FileError(e.message, { operation: 'stat', code: e.code, path: filepath })
  } finally {
    req.return()
  }
}

async function lstat(filepath, cb) {
  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  let st
  let err = null
  try {
    binding.lstat(req.handle, filepath)

    await req

    st = new Stats(...binding.requestResultStat(req.handle))
  } catch (e) {
    err = new FileError(e.message, { operation: 'lstat', code: e.code, path: filepath })
  } finally {
    req.return()
  }

  return done(err, st, cb)
}

function lstatSync(filepath) {
  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  try {
    binding.lstatSync(req.handle, filepath)

    return new Stats(...binding.requestResultStat(req.handle))
  } catch (e) {
    throw new FileError(e.message, { operation: 'lstat', code: e.code, path: filepath })
  } finally {
    req.return()
  }
}

async function fstat(fd, cb) {
  const req = FileRequest.borrow()

  let st
  let err = null
  try {
    binding.fstat(req.handle, fd)

    await req

    st = new Stats(...binding.requestResultStat(req.handle))
  } catch (e) {
    err = new FileError(e.message, { operation: 'fstat', code: e.code, fd })
  } finally {
    req.return()
  }

  return done(err, st, cb)
}

function fstatSync(fd) {
  const req = FileRequest.borrow()

  try {
    binding.fstatSync(req.handle, fd)

    return new Stats(...binding.requestResultStat(req.handle))
  } catch (e) {
    throw new FileError(e.message, { operation: 'fstat', code: e.code, fd })
  } finally {
    req.return()
  }
}

async function statfs(filepath, cb) {
  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  let st
  let err = null
  try {
    binding.statfs(req.handle, filepath)

    await req

    st = new StatFs(...binding.requestResultStatfs(req.handle))
  } catch (e) {
    err = new FileError(e.message, { operation: 'statfs', code: e.code, path: filepath })
  } finally {
    req.return()
  }

  return done(err, st, cb)
}

function statfsSync(filepath) {
  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  try {
    binding.statfsSync(req.handle, filepath)

    return new StatFs(...binding.requestResultStatfs(req.handle))
  } catch (e) {
    throw new FileError(e.message, { operation: 'statfs', code: e.code, path: filepath })
  } finally {
    req.return()
  }
}

async function ftruncate(fd, len = 0, cb) {
  if (typeof len === 'function') {
    cb = len
    len = 0
  }

  if (typeof len !== 'number') len = 0

  const req = FileRequest.borrow()

  let err = null
  try {
    binding.ftruncate(req.handle, fd, len)

    await req
  } catch (e) {
    err = new FileError(e.message, { operation: 'ftruncate', code: e.code, fd })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function ftruncateSync(fd, len = 0) {
  if (typeof len !== 'number') len = 0

  const req = FileRequest.borrow()

  try {
    binding.ftruncateSync(req.handle, fd, len)
  } catch (e) {
    throw new FileError(e.message, { operation: 'ftruncate', code: e.code, fd })
  } finally {
    req.return()
  }
}

async function truncate(filepath, len, cb) {
  let fd = -1
  let err

  try {
    fd = await open(filepath, 'r+')

    await ftruncate(fd, len)
  } catch (e) {
    err = e
  } finally {
    if (fd !== -1) await close(fd)
  }

  return done(err, cb)
}

function truncateSync(filepath, len) {
  let fd = -1
  let err

  try {
    fd = openSync(filepath, 'r+')

    ftruncateSync(fd, len)
  } catch (e) {
    err = e
  } finally {
    if (fd !== -1) closeSync(fd)
  }
}

async function chmod(filepath, mode, cb) {
  if (typeof mode === 'string') mode = toMode(mode)

  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  let err = null
  try {
    binding.chmod(req.handle, filepath, mode)

    await req
  } catch (e) {
    err = new FileError(e.message, {
      operation: 'chmod',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function chmodSync(filepath, mode) {
  if (typeof mode === 'string') mode = toMode(mode)

  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  try {
    binding.chmodSync(req.handle, filepath, mode)
  } catch (e) {
    throw new FileError(e.message, {
      operation: 'chmod',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }
}

async function fchmod(fd, mode, cb) {
  if (typeof mode === 'string') mode = toMode(mode)

  const req = FileRequest.borrow()

  let err = null
  try {
    binding.fchmod(req.handle, fd, mode)

    await req
  } catch (e) {
    err = new FileError(e.message, { operation: 'fchmod', code: e.code, fd })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function fchmodSync(fd, mode) {
  if (typeof mode === 'string') mode = toMode(mode)

  const req = FileRequest.borrow()

  try {
    binding.fchmodSync(req.handle, fd, mode)
  } catch (e) {
    throw new FileError(e.message, { operation: 'fchmod', code: e.code, fd })
  } finally {
    req.return()
  }
}

async function chown(filepath, uid, gid, cb) {
  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  let err = null
  try {
    binding.chown(req.handle, filepath, uid, gid)

    await req
  } catch (e) {
    err = new FileError(e.message, { operation: 'chown', code: e.code, path: filepath })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function chownSync(filepath, uid, gid) {
  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  try {
    binding.chownSync(req.handle, filepath, uid, gid)
  } catch (e) {
    throw new FileError(e.message, { operation: 'chownSync', code: e.code, path: filepath })
  } finally {
    req.return()
  }
}

async function lchown(filepath, uid, gid, cb) {
  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  let err = null
  try {
    binding.lchown(req.handle, filepath, uid, gid)

    await req
  } catch (e) {
    err = new FileError(e.message, { operation: 'lchown', code: e.code, path: filepath })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function lchownSync(filepath, uid, gid) {
  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  try {
    binding.lchownSync(req.handle, filepath, uid, gid)
  } catch (e) {
    throw new FileError(e.message, { operation: 'lchownSync', code: e.code, path: filepath })
  } finally {
    req.return()
  }
}

async function fchown(fd, uid, gid, cb) {
  const req = FileRequest.borrow()

  let err = null
  try {
    binding.fchown(req.handle, fd, uid, gid)

    await req
  } catch (e) {
    err = new FileError(e.message, { operation: 'fchown', code: e.code, fd })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function fchownSync(fd, uid, gid) {
  const req = FileRequest.borrow()

  try {
    binding.fchownSync(req.handle, fd, uid, gid)
  } catch (e) {
    throw new FileError(e.message, { operation: 'fchownSync', code: e.code, fd })
  } finally {
    req.return()
  }
}

async function utimes(filepath, atime, mtime, cb) {
  if (typeof atime !== 'number') atime = atime.getTime() / 1000
  if (typeof mtime !== 'number') mtime = mtime.getTime() / 1000

  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  let err = null
  try {
    binding.utimes(req.handle, filepath, atime, mtime)

    await req
  } catch (e) {
    err = new FileError(e.message, { operation: 'utimes', code: e.code, path: filepath })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function utimesSync(filepath, atime, mtime) {
  if (typeof atime !== 'number') atime = atime.getTime() / 1000
  if (typeof mtime !== 'number') mtime = mtime.getTime() / 1000

  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  try {
    binding.utimesSync(req.handle, filepath, atime, mtime)
  } catch (e) {
    throw new FileError(e.message, { operation: 'utimes', code: e.code, path: filepath })
  } finally {
    req.return()
  }
}

async function lutimes(filepath, atime, mtime, cb) {
  if (typeof atime !== 'number') atime = atime.getTime() / 1000
  if (typeof mtime !== 'number') mtime = mtime.getTime() / 1000

  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  let err = null
  try {
    binding.lutimes(req.handle, filepath, atime, mtime)

    await req
  } catch (e) {
    err = new FileError(e.message, { operation: 'lutimes', code: e.code, path: filepath })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function lutimesSync(filepath, atime, mtime) {
  if (typeof atime !== 'number') atime = atime.getTime() / 1000
  if (typeof mtime !== 'number') mtime = mtime.getTime() / 1000

  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  try {
    binding.lutimesSync(req.handle, filepath, atime, mtime)
  } catch (e) {
    throw new FileError(e.message, { operation: 'lutimes', code: e.code, path: filepath })
  } finally {
    req.return()
  }
}

async function futimes(fd, atime, mtime, cb) {
  if (typeof atime !== 'number') atime = atime.getTime() / 1000
  if (typeof mtime !== 'number') mtime = mtime.getTime() / 1000

  const req = FileRequest.borrow()

  let err = null
  try {
    binding.futimes(req.handle, fd, atime, mtime)

    await req
  } catch (e) {
    err = new FileError(e.message, { operation: 'futimes', code: e.code, fd })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function futimesSync(fd, atime, mtime) {
  if (typeof atime !== 'number') atime = atime.getTime() / 1000
  if (typeof mtime !== 'number') mtime = mtime.getTime() / 1000

  const req = FileRequest.borrow()

  try {
    binding.futimesSync(req.handle, fd, atime, mtime)
  } catch (e) {
    throw new FileError(e.message, { operation: 'futimesSync', code: e.code, fd })
  } finally {
    req.return()
  }
}

async function link(src, dst, cb) {
  src = toNamespacedPath(src)
  dst = toNamespacedPath(dst)

  const req = FileRequest.borrow()

  let err = null
  try {
    binding.link(req.handle, src, dst)

    await req
  } catch (e) {
    err = new FileError(e.message, { operation: 'link', code: e.code, path: src, destination: dst })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function linkSync(src, dst) {
  src = toNamespacedPath(src)
  dst = toNamespacedPath(dst)

  const req = FileRequest.borrow()

  try {
    binding.linkSync(req.handle, src, dst)
  } catch (e) {
    throw new FileError(e.message, {
      operation: 'linkSync',
      code: e.code,
      path: src,
      destination: dst
    })
  } finally {
    req.return()
  }
}

async function mkdir(filepath, opts, cb) {
  if (typeof opts === 'function') {
    cb = opts
    opts = { mode: 0o777 }
  }

  if (typeof opts === 'number') opts = { mode: opts }
  else if (!opts) opts = {}

  const mode = typeof opts.mode === 'number' ? opts.mode : 0o777

  filepath = toNamespacedPath(filepath)

  if (opts.recursive) {
    let err = null
    try {
      try {
        await mkdir(filepath, { mode })
      } catch (err) {
        if (err.code !== 'ENOENT') {
          if (!(await stat(filepath)).isDirectory()) throw err
        } else {
          while (filepath.endsWith(path.sep)) filepath = filepath.slice(0, -1)
          const i = filepath.lastIndexOf(path.sep)
          if (i <= 0) throw err

          await mkdir(filepath.slice(0, i), { mode, recursive: true })

          try {
            await mkdir(filepath, { mode })
          } catch (err) {
            if (!(await stat(filepath)).isDirectory()) throw err
          }
        }
      }
    } catch (e) {
      err = e
    }

    return done(err, cb)
  }

  const req = FileRequest.borrow()

  let err = null
  try {
    binding.mkdir(req.handle, filepath, mode)

    await req
  } catch (e) {
    err = new FileError(e.message, {
      operation: 'mkdir',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function mkdirSync(filepath, opts) {
  if (typeof opts === 'number') opts = { mode: opts }
  else if (!opts) opts = {}

  const mode = typeof opts.mode === 'number' ? opts.mode : 0o777

  filepath = toNamespacedPath(filepath)

  if (opts.recursive) {
    try {
      mkdirSync(filepath, { mode })
    } catch (err) {
      if (err.code !== 'ENOENT') {
        if (!statSync(filepath).isDirectory()) throw err
      } else {
        while (filepath.endsWith(path.sep)) filepath = filepath.slice(0, -1)
        const i = filepath.lastIndexOf(path.sep)
        if (i <= 0) throw err

        mkdirSync(filepath.slice(0, i), { mode, recursive: true })

        try {
          mkdirSync(filepath, { mode })
        } catch (err) {
          if (!statSync(filepath).isDirectory()) throw err
        }
      }
    }

    return
  }

  const req = FileRequest.borrow()

  try {
    binding.mkdirSync(req.handle, filepath, mode)
  } catch (e) {
    throw new FileError(e.message, {
      operation: 'mkdir',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }
}

async function mkdtemp(prefix, cb) {
  prefix = toNamespacedPath(prefix)

  const req = FileRequest.borrow()

  let res
  let err = null
  try {
    binding.mkdtemp(req.handle, prefix + 'XXXXXX')

    await req

    res = binding.requestResultPath(req.handle)
  } catch (e) {
    err = new FileError(e.message, { operation: 'mkdtemp', code: e.code, path: prefix })
  } finally {
    req.return()
  }

  return done(err, res, cb)
}

function mkdtempSync(prefix) {
  prefix = toNamespacedPath(prefix)

  const req = FileRequest.borrow()

  try {
    binding.mkdtempSync(req.handle, prefix + 'XXXXXX')

    return binding.requestResultPath(req.handle)
  } catch (e) {
    throw new FileError(e.message, { operation: 'mkdtempSync', code: e.code, path: prefix })
  } finally {
    req.return()
  }
}

async function rmdir(filepath, cb) {
  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  let err = null
  try {
    binding.rmdir(req.handle, filepath)

    await req
  } catch (e) {
    err = new FileError(e.message, {
      operation: 'rmdir',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function rmdirSync(filepath) {
  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  try {
    binding.rmdirSync(req.handle, filepath)
  } catch (e) {
    throw new FileError(e.message, {
      operation: 'rmdir',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }
}

async function rm(filepath, opts, cb) {
  if (typeof opts === 'function') {
    cb = opts
    opts = {}
  }

  if (!opts) opts = {}

  filepath = toNamespacedPath(filepath)

  let err = null
  try {
    const st = await lstat(filepath)

    if (st.isDirectory()) {
      if (opts.recursive) {
        try {
          await rmdir(filepath)
        } catch (err) {
          if (err.code !== 'ENOTEMPTY') throw err

          const files = await readdir(filepath)

          for (const file of files) {
            await rm(filepath + path.sep + file, opts)
          }

          await rmdir(filepath)
        }
      } else {
        throw new FileError('is a directory', {
          operation: 'rm',
          code: 'EISDIR',
          path: filepath
        })
      }
    } else {
      await unlink(filepath)
    }
  } catch (e) {
    if (e.code !== 'ENOENT' || !opts.force) err = e
  }

  return done(err, cb)
}

function rmSync(filepath, opts) {
  if (!opts) opts = {}

  filepath = toNamespacedPath(filepath)

  try {
    const st = lstatSync(filepath)

    if (st.isDirectory()) {
      if (opts.recursive) {
        try {
          rmdirSync(filepath)
        } catch (err) {
          if (err.code !== 'ENOTEMPTY') throw err

          const files = readdirSync(filepath)

          for (const file of files) {
            rmSync(filepath + path.sep + file, opts)
          }

          rmdirSync(filepath)
        }
      } else {
        throw new FileError('is a directory', {
          operation: 'rm',
          code: 'EISDIR',
          path: filepath
        })
      }
    } else {
      unlinkSync(filepath)
    }
  } catch (err) {
    if (err.code !== 'ENOENT' || !opts.force) throw err
  }
}

async function unlink(filepath, cb) {
  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  let err = null
  try {
    binding.unlink(req.handle, filepath)

    await req
  } catch (e) {
    err = new FileError(e.message, {
      operation: 'unlink',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function unlinkSync(filepath) {
  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  try {
    binding.unlinkSync(req.handle, filepath)
  } catch (e) {
    throw new FileError(e.message, {
      operation: 'unlink',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }
}

async function rename(src, dst, cb) {
  src = toNamespacedPath(src)
  dst = toNamespacedPath(dst)

  const req = FileRequest.borrow()

  let err = null
  try {
    binding.rename(req.handle, src, dst)

    await req
  } catch (e) {
    err = new FileError(e.message, {
      operation: 'rename',
      code: e.code,
      path: src,
      destination: dst
    })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function renameSync(src, dst) {
  src = toNamespacedPath(src)
  dst = toNamespacedPath(dst)

  const req = FileRequest.borrow()

  try {
    binding.renameSync(req.handle, src, dst)
  } catch (e) {
    throw new FileError(e.message, {
      operation: 'rename',
      code: e.code,
      path: src,
      destination: dst
    })
  } finally {
    req.return()
  }
}

async function copyFile(src, dst, mode = 0, cb) {
  if (typeof mode === 'function') {
    cb = mode
    mode = 0
  }

  src = toNamespacedPath(src)
  dst = toNamespacedPath(dst)

  const req = FileRequest.borrow()

  let err = null
  try {
    binding.copyfile(req.handle, src, dst, mode)

    await req
  } catch (e) {
    err = new FileError(e.message, {
      operation: 'copyfile',
      code: e.code,
      path: src,
      destination: dst
    })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function copyFileSync(src, dst, mode = 0) {
  src = toNamespacedPath(src)
  dst = toNamespacedPath(dst)

  const req = FileRequest.borrow()

  try {
    binding.copyfileSync(req.handle, src, dst, mode)
  } catch (e) {
    throw new FileError(e.message, {
      operation: 'copyfile',
      code: e.code,
      path: src,
      destination: dst
    })
  } finally {
    req.return()
  }
}

async function cp(src, dst, opts, cb) {
  if (typeof opts === 'function') {
    cb = opts
    opts = {}
  }

  if (!opts) opts = {}

  src = toNamespacedPath(src)
  dst = toNamespacedPath(dst)

  let err = null
  try {
    const st = await lstat(src)

    if (st.isDirectory()) {
      if (opts.recursive !== true) {
        throw new FileError('is a directory', { operation: 'cp', code: 'EISDIR', path: src })
      }

      try {
        await lstat(dst)
      } catch (e) {
        if (e.code !== 'ENOENT') throw e

        await mkdir(dst, { mode: st.mode, recursive: true })
      }

      const dir = await opendir(src)
      for await (const { name } of dir) {
        await cp(path.join(src, name), path.join(dst, name), opts)
      }
    } else if (st.isFile()) {
      await copyFile(src, dst)
      await chmod(dst, st.mode)
    }
  } catch (e) {
    err = e
  }

  return done(err, cb)
}

function cpSync(src, dst, opts = {}) {
  src = toNamespacedPath(src)
  dst = toNamespacedPath(dst)

  const st = lstatSync(src)

  if (st.isDirectory()) {
    if (opts.recursive !== true) {
      throw new FileError('is a directory', { operation: 'cp', code: 'EISDIR', path: src })
    }

    try {
      lstatSync(dst)
    } catch (e) {
      if (e.code !== 'ENOENT') throw e

      mkdirSync(dst, { mode: st.mode, recursive: true })
    }

    const dir = opendirSync(src)
    for (const { name } of dir) {
      cpSync(path.join(src, name), path.join(dst, name), opts)
    }
  } else if (st.isFile()) {
    copyFileSync(src, dst)
    chmodSync(dst, st.mode)
  }
}

async function realpath(filepath, opts, cb) {
  if (typeof opts === 'function') {
    cb = opts
    opts = {}
  }

  if (typeof opts === 'string') opts = { encoding: opts }
  else if (!opts) opts = {}

  const { encoding = 'utf8' } = opts

  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  let res
  let err = null
  try {
    binding.realpath(req.handle, filepath)

    await req

    res = Buffer.from(binding.requestResultString(req.handle))

    if (encoding !== 'buffer') res = res.toString(encoding)
  } catch (e) {
    err = new FileError(e.message, {
      operation: 'realpath',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }

  return done(err, res, cb)
}

function realpathSync(filepath, opts) {
  if (typeof opts === 'string') opts = { encoding: opts }
  else if (!opts) opts = {}

  const { encoding = 'utf8' } = opts

  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  try {
    binding.realpathSync(req.handle, filepath)

    let res = Buffer.from(binding.requestResultString(req.handle))

    if (encoding !== 'buffer') res = res.toString(encoding)

    return res
  } catch (e) {
    throw new FileError(e.message, {
      operation: 'realpath',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }
}

async function readlink(filepath, opts, cb) {
  if (typeof opts === 'function') {
    cb = opts
    opts = {}
  }

  if (typeof opts === 'string') opts = { encoding: opts }
  else if (!opts) opts = {}

  const { encoding = 'utf8' } = opts

  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  let res
  let err = null
  try {
    binding.readlink(req.handle, filepath)

    await req

    res = Buffer.from(binding.requestResultString(req.handle))

    if (encoding !== 'buffer') res = res.toString(encoding)
  } catch (e) {
    err = new FileError(e.message, {
      operation: 'readlink',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }

  return done(err, res, cb)
}

function readlinkSync(filepath, opts) {
  if (typeof opts === 'string') opts = { encoding: opts }
  else if (!opts) opts = {}

  const { encoding = 'utf8' } = opts

  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  try {
    binding.readlinkSync(req.handle, filepath)

    let res = Buffer.from(binding.requestResultString(req.handle))

    if (encoding !== 'buffer') res = res.toString(encoding)

    return res
  } catch (e) {
    throw new FileError(e.message, {
      operation: 'readlink',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }
}

async function fsync(fd, cb) {
  const req = FileRequest.borrow()

  let err = null
  try {
    binding.fsync(req.handle, fd)

    await req
  } catch (e) {
    err = new FileError(e.message, { operation: 'fsync', code: e.code, fd })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function fsyncSync(fd) {
  const req = FileRequest.borrow()

  try {
    binding.fsyncSync(req.handle, fd)
  } catch (e) {
    throw new FileError(e.message, { operation: 'fsyncSync', code: e.code, fd })
  } finally {
    req.return()
  }
}

async function fdatasync(fd, cb) {
  const req = FileRequest.borrow()

  let err = null
  try {
    binding.fdatasync(req.handle, fd)

    await req
  } catch (e) {
    err = new FileError(e.message, { operation: 'fdatasync', code: e.code, fd })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function fdatasyncSync(fd) {
  const req = FileRequest.borrow()

  try {
    binding.fdatasyncSync(req.handle, fd)
  } catch (e) {
    throw new FileError(e.message, { operation: 'fdatasyncSync', code: e.code, fd })
  } finally {
    req.return()
  }
}

function normalizeSymlinkTarget(target, type, filepath) {
  if (isWindows) {
    if (type === constants.UV_FS_SYMLINK_JUNCTION) target = path.resolve(filepath, '..', target)

    if (path.isAbsolute(target)) return path.toNamespacedPath(target)

    return target.replace(/\//g, path.sep)
  }

  return target
}

async function symlink(target, filepath, type, cb) {
  if (typeof type === 'function') {
    cb = type
    type = null
  }

  filepath = toNamespacedPath(filepath)

  if (typeof type === 'string') {
    switch (type) {
      case 'dir':
        type = constants.UV_FS_SYMLINK_DIR
        break
      case 'junction':
        type = constants.UV_FS_SYMLINK_JUNCTION
        break
      case 'file':
      default:
        type = 0
        break
    }
  } else if (typeof type !== 'number') {
    if (isWindows) {
      target = path.resolve(filepath, '..', target)

      try {
        type = (await stat(target)).isDirectory()
          ? constants.UV_FS_SYMLINK_DIR
          : constants.UV_FS_SYMLINK_JUNCTION
      } catch {
        type = 0
      }
    } else {
      type = 0
    }
  }

  target = normalizeSymlinkTarget(target, type, filepath)

  const req = FileRequest.borrow()

  let err = null
  try {
    binding.symlink(req.handle, target, filepath, type)

    await req
  } catch (e) {
    err = new FileError(e.message, {
      operation: 'symlink',
      code: e.code,
      path: target,
      destination: filepath
    })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function symlinkSync(target, filepath, type) {
  filepath = toNamespacedPath(filepath)

  if (typeof type === 'string') {
    switch (type) {
      case 'dir':
        type = constants.UV_FS_SYMLINK_DIR
        break
      case 'junction':
        type = constants.UV_FS_SYMLINK_JUNCTION
        break
      case 'file':
      default:
        type = 0
        break
    }
  } else if (typeof type !== 'number') {
    if (isWindows) {
      target = path.resolve(filepath, '..', target)

      try {
        type = statSync(target).isDirectory()
          ? constants.UV_FS_SYMLINK_DIR
          : constants.UV_FS_SYMLINK_JUNCTION
      } catch {
        type = 0
      }
    } else {
      type = 0
    }
  }

  target = normalizeSymlinkTarget(target, type, filepath)

  const req = FileRequest.borrow()

  try {
    binding.symlinkSync(req.handle, target, filepath, type)
  } catch (e) {
    throw new FileError(e.message, {
      operation: 'symlink',
      code: e.code,
      path: target,
      destination: filepath
    })
  } finally {
    req.return()
  }
}

async function opendir(filepath, opts, cb) {
  if (typeof opts === 'function') {
    cb = opts
    opts = {}
  }

  if (typeof opts === 'string') opts = { encoding: opts }
  else if (!opts) opts = {}

  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  let dir
  let err = null
  try {
    binding.opendir(req.handle, filepath)

    await req

    dir = new Dir(filepath, binding.requestResultDir(req.handle), opts)
  } catch (e) {
    err = new FileError(e.message, {
      operation: 'opendir',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }

  return done(err, dir, cb)
}

function opendirSync(filepath, opts) {
  if (typeof opts === 'string') opts = { encoding: opts }
  else if (!opts) opts = {}

  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  try {
    binding.opendirSync(req.handle, filepath)

    return new Dir(filepath, binding.requestResultDir(req.handle), opts)
  } catch (e) {
    throw new FileError(e.message, {
      operation: 'opendir',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }
}

async function readdir(filepath, opts, cb) {
  if (typeof opts === 'function') {
    cb = opts
    opts = {}
  }

  if (typeof opts === 'string') opts = { encoding: opts }
  else if (!opts) opts = {}

  const { withFileTypes = false, recursive = false } = opts

  filepath = toNamespacedPath(filepath)

  const queue = [filepath]
  let result = []
  let err = null
  try {
    while (queue.length !== 0) {
      const dir = await opendir(queue.pop())

      for await (const entry of dir) {
        const entryPath = path.join(entry.parentPath, entry.name)

        if (withFileTypes) {
          result.push(entry)
        } else {
          result.push(path.relative(filepath, entryPath))
        }

        if (recursive && entry.isDirectory()) queue.push(entryPath)
      }
    }
  } catch (e) {
    result = []
    err = e
  }

  return done(err, result, cb)
}

function readdirSync(filepath, opts) {
  if (typeof opts === 'string') opts = { encoding: opts }
  else if (!opts) opts = {}

  const { withFileTypes = false, recursive = false } = opts

  filepath = toNamespacedPath(filepath)

  const queue = [filepath]
  const result = []

  while (queue.length !== 0) {
    const dir = opendirSync(queue.pop(), opts)

    for (const entry of dir) {
      const entryPath = path.join(entry.parentPath, entry.name)

      if (withFileTypes) {
        result.push(entry)
      } else {
        result.push(path.relative(filepath, entryPath))
      }

      if (recursive && entry.isDirectory()) queue.push(entryPath)
    }
  }

  return result
}

async function readFile(filepath, opts, cb) {
  if (typeof opts === 'function') {
    cb = opts
    opts = {}
  }

  if (typeof opts === 'string') opts = { encoding: opts }
  else if (!opts) opts = {}

  const { encoding = 'buffer' } = opts

  let fd = -1
  let buffer = null
  let err = null
  try {
    fd = await open(filepath, opts.flag || 'r')

    const st = await fstat(fd)

    let len = 0

    if (st.size === 0) {
      const buffers = []

      while (true) {
        buffer = Buffer.allocUnsafe(8192)
        const r = await read(fd, buffer)
        len += r
        if (r === 0) break
        buffers.push(buffer.subarray(0, r))
      }

      buffer = Buffer.concat(buffers)
    } else {
      buffer = Buffer.allocUnsafe(st.size)

      while (true) {
        const r = await read(fd, len ? buffer.subarray(len) : buffer)
        len += r
        if (r === 0 || len === buffer.byteLength) break
      }

      if (len !== buffer.byteLength) buffer = buffer.subarray(0, len)
    }

    if (encoding !== 'buffer') buffer = buffer.toString(encoding)
  } catch (e) {
    err = e
  } finally {
    if (fd !== -1) await close(fd)
  }

  return done(err, buffer, cb)
}

function readFileSync(filepath, opts) {
  if (typeof opts === 'string') opts = { encoding: opts }
  else if (!opts) opts = {}

  const { encoding = 'buffer' } = opts

  let fd = -1
  try {
    fd = openSync(filepath, opts.flag || 'r')

    const st = fstatSync(fd)

    let buffer
    let len = 0

    if (st.size === 0) {
      const buffers = []

      while (true) {
        buffer = Buffer.allocUnsafe(8192)
        const r = readSync(fd, buffer)
        len += r
        if (r === 0) break
        buffers.push(buffer.subarray(0, r))
      }

      buffer = Buffer.concat(buffers)
    } else {
      buffer = Buffer.allocUnsafe(st.size)

      while (true) {
        const r = readSync(fd, len ? buffer.subarray(len) : buffer)
        len += r
        if (r === 0 || len === buffer.byteLength) break
      }

      if (len !== buffer.byteLength) buffer = buffer.subarray(0, len)
    }

    if (encoding !== 'buffer') buffer = buffer.toString(encoding)

    return buffer
  } finally {
    if (fd !== -1) closeSync(fd)
  }
}

async function writeFile(filepath, data, opts, cb) {
  if (typeof opts === 'function') {
    cb = opts
    opts = {}
  }

  if (typeof opts === 'string') opts = { encoding: opts }
  else if (!opts) opts = {}

  if (typeof data === 'string') data = Buffer.from(data, opts.encoding)

  let fd = -1
  let len = 0
  let err = null
  try {
    fd = await open(filepath, opts.flag || 'w', opts.mode || 0o666)

    while (true) {
      len += await write(fd, len ? data.subarray(len) : data)
      if (len === data.byteLength) break
    }
  } catch (e) {
    err = e
  } finally {
    if (fd !== -1) await close(fd)
  }

  return done(err, len, cb)
}

function writeFileSync(filepath, data, opts) {
  if (typeof opts === 'string') opts = { encoding: opts }
  else if (!opts) opts = {}

  if (typeof data === 'string') data = Buffer.from(data, opts.encoding)

  let fd = -1
  try {
    fd = openSync(filepath, opts.flag || 'w', opts.mode || 0o666)

    let len = 0

    while (true) {
      len += writeSync(fd, len ? data.subarray(len) : data)
      if (len === data.byteLength) break
    }
  } finally {
    if (fd !== -1) closeSync(fd)
  }
}

function appendFile(filepath, data, opts, cb) {
  if (typeof opts === 'function') {
    cb = opts
    opts = {}
  }

  if (typeof opts === 'string') opts = { encoding: opts }
  else if (!opts) opts = {}

  if (!opts.flag) opts = { ...opts, flag: 'a' }

  return writeFile(filepath, data, opts, cb)
}

function appendFileSync(filepath, data, opts) {
  if (typeof opts === 'string') opts = { encoding: opts }
  else if (!opts) opts = {}

  if (!opts.flag) opts = { ...opts, flag: 'a' }

  return writeFileSync(filepath, data, opts)
}

function watch(filepath, opts, cb) {
  if (typeof opts === 'function') {
    cb = opts
    opts = {}
  }

  if (typeof opts === 'string') opts = { encoding: opts }
  else if (!opts) opts = {}

  filepath = toNamespacedPath(filepath)

  return new Watcher(filepath, opts, cb)
}

class Stats {
  constructor(
    dev,
    mode,
    nlink,
    uid,
    gid,
    rdev,
    blksize,
    ino,
    size,
    blocks,
    atimeMs,
    mtimeMs,
    ctimeMs,
    birthtimeMs
  ) {
    this.dev = dev
    this.mode = mode
    this.nlink = nlink
    this.uid = uid
    this.gid = gid
    this.rdev = rdev
    this.blksize = blksize
    this.ino = ino
    this.size = size
    this.blocks = blocks
    this.atimeMs = atimeMs
    this.mtimeMs = mtimeMs
    this.ctimeMs = ctimeMs
    this.birthtimeMs = birthtimeMs
    this.atime = new Date(atimeMs)
    this.mtime = new Date(mtimeMs)
    this.ctime = new Date(ctimeMs)
    this.birthtime = new Date(birthtimeMs)
  }

  isDirectory() {
    return (this.mode & constants.S_IFMT) === constants.S_IFDIR
  }

  isFile() {
    return (this.mode & constants.S_IFMT) === constants.S_IFREG
  }

  isBlockDevice() {
    return (this.mode & constants.S_IFMT) === constants.S_IFBLK
  }

  isCharacterDevice() {
    return (this.mode & constants.S_IFMT) === constants.S_IFCHR
  }

  isFIFO() {
    return (this.mode & constants.S_IFMT) === constants.S_IFIFO
  }

  isSymbolicLink() {
    return (this.mode & constants.S_IFMT) === constants.S_IFLNK
  }

  isSocket() {
    return (this.mode & constants.S_IFMT) === constants.S_IFSOCK
  }
}

class StatFs {
  constructor(type, bsize, blocks, bfree, bavail, files, ffree) {
    this.type = type
    this.bsize = bsize
    this.blocks = blocks
    this.bfree = bfree
    this.bavail = bavail
    this.files = files
    this.ffree = ffree
  }
}

class Dir {
  constructor(path, handle, opts = {}) {
    const { encoding = 'utf8', bufferSize = 32 } = opts

    this.path = path

    this._encoding = encoding
    this._capacity = bufferSize
    this._buffer = new FIFO()
    this._ended = false
    this._handle = handle
  }

  async read(cb) {
    if (this._buffer.length) return ok(this._buffer.shift(), cb)
    if (this._ended) return ok(null, cb)

    const req = FileRequest.borrow()

    let entries
    let err = null
    try {
      req.retain(binding.readdir(req.handle, this._handle, this._capacity))

      await req

      entries = binding.requestResultDirents(req.handle)
    } catch (e) {
      err = new FileError(e.message, {
        operation: 'readdir',
        code: e.code,
        path: this.path
      })
    } finally {
      req.return()
    }

    if (err) return fail(err, cb)

    if (entries.length === 0) {
      this._ended = true

      return ok(null, cb)
    }

    for (const entry of entries) {
      let name = Buffer.from(entry.name)

      if (this._encoding !== 'buffer') name = name.toString(this._encoding)

      this._buffer.push(new Dirent(this.path, name, entry.type))
    }

    return ok(this._buffer.shift(), cb)
  }

  readSync() {
    if (this._buffer.length) return this._buffer.shift()
    if (this._ended) return null

    const req = FileRequest.borrow()

    let entries
    try {
      req.retain(binding.readdirSync(req.handle, this._handle, this._capacity))

      entries = binding.requestResultDirents(req.handle)
    } catch (e) {
      throw new FileError(e.message, {
        operation: 'readdir',
        code: e.code,
        path: this.path
      })
    } finally {
      req.return()
    }

    if (entries.length === 0) {
      this._ended = true

      return null
    }

    for (const entry of entries) {
      let name = Buffer.from(entry.name)

      if (this._encoding !== 'buffer') name = name.toString(this._encoding)

      this._buffer.push(new Dirent(this.path, name, entry.type))
    }

    return this._buffer.shift()
  }

  async close(cb) {
    const req = FileRequest.borrow()

    let err = null
    try {
      binding.closedir(req.handle, this._handle)

      await req
    } catch (e) {
      err = new FileError(e.message, {
        operation: 'closedir',
        code: e.code,
        path: this.path
      })
    } finally {
      req.return()
    }

    this._handle = null

    return done(err, cb)
  }

  closeSync() {
    const req = FileRequest.borrow()

    try {
      binding.closedirSync(req.handle, this._handle)
    } catch (e) {
      throw new FileError(e.message, {
        operation: 'closedir',
        code: e.code,
        path: this.path
      })
    } finally {
      req.return()
    }

    this._handle = null
  }

  [Symbol.dispose]() {
    this.closeSync()
  }

  async [Symbol.asyncDispose]() {
    await this.close()
  }

  *[Symbol.iterator]() {
    while (true) {
      const entry = this.readSync()
      if (entry === null) break
      yield entry
    }

    this.closeSync()
  }

  async *[Symbol.asyncIterator]() {
    while (true) {
      const entry = await this.read()
      if (entry === null) break
      yield entry
    }

    await this.close()
  }
}

class Dirent {
  constructor(parentPath, name, type) {
    this.parentPath = parentPath
    this.name = name
    this.type = type
  }

  isFile() {
    return this.type === constants.UV_DIRENT_FILE
  }

  isDirectory() {
    return this.type === constants.UV_DIRENT_DIR
  }

  isSymbolicLink() {
    return this.type === constants.UV_DIRENT_LINK
  }

  isFIFO() {
    return this.type === constants.UV_DIRENT_FIFO
  }

  isSocket() {
    return this.type === constants.UV_DIRENT_SOCKET
  }

  isCharacterDevice() {
    return this.type === constants.UV_DIRENT_CHAR
  }

  isBlockDevice() {
    return this.type === constants.UV_DIRENT_BLOCK
  }
}

class FileReadStream extends Readable {
  constructor(path, opts = {}) {
    const { eagerOpen = true } = opts

    super({ eagerOpen, ...opts })

    this.path = path
    this.fd = typeof opts.fd === 'number' ? opts.fd : -1
    this.flags = opts.flags || 'r'
    this.mode = opts.mode || 0o666

    this._offset = opts.start || 0
    this._missing = 0

    if (opts.length) {
      this._missing = opts.length
    } else if (typeof opts.end === 'number') {
      this._missing = opts.end - this._offset + 1
    } else {
      this._missing = -1
    }
  }

  async _open(cb) {
    let err

    if (this.fd === -1) {
      err = null
      try {
        this.fd = await open(this.path, this.flags, this.mode)
      } catch (e) {
        err = e
      }

      if (err) return cb(err)
    }

    let st
    err = null
    try {
      st = await fstat(this.fd)
    } catch (e) {
      err = e
    }

    if (err) return cb(err)

    if (this._missing === -1) this._missing = st.size

    if (st.size < this._offset) {
      this._offset = st.size
      this._missing = 0
    } else if (st.size < this._offset + this._missing) {
      this._missing = st.size - this._offset
    }

    cb(null)
  }

  async _read(size) {
    if (this._missing <= 0) return this.push(null)

    const data = Buffer.allocUnsafe(Math.min(this._missing, size))

    let len
    let err = null
    try {
      len = await read(this.fd, data, 0, data.byteLength, this._offset)
    } catch (e) {
      err = e
    }

    if (err) return this.destroy(err)

    if (len === 0) return this.push(null)

    if (this._missing < len) len = this._missing

    this._missing -= len
    this._offset += len

    this.push(data.subarray(0, len))
  }

  async _destroy(err, cb) {
    if (this.fd === -1) return cb(err)

    try {
      await close(this.fd)
    } catch (e) {
      err = err || e
    }

    cb(err)
  }
}

class FileWriteStream extends Writable {
  constructor(path, opts = {}) {
    const { eagerOpen = true } = opts

    super({ eagerOpen, ...opts })

    this.path = path
    this.fd = typeof opts.fd === 'number' ? opts.fd : -1
    this.flags = opts.flags || 'w'
    this.mode = opts.mode || 0o666
  }

  async _open(cb) {
    if (this.fd !== -1) return cb(null)

    let err = null
    try {
      this.fd = await open(this.path, this.flags, this.mode)
    } catch (e) {
      err = e
    }

    cb(err)
  }

  async _writev(batch, cb) {
    let err = null
    try {
      await writev(
        this.fd,
        batch.map(({ chunk }) => chunk)
      )
    } catch (e) {
      err = e
    }

    cb(err)
  }

  async _destroy(err, cb) {
    if (this.fd === -1) return cb(err)

    try {
      await close(this.fd)
    } catch (e) {
      err = err || e
    }

    cb(err)
  }
}

class Watcher extends EventEmitter {
  constructor(path, opts, onchange) {
    if (typeof opts === 'function') {
      onchange = opts
      opts = {}
    }

    if (!opts) opts = {}

    const { persistent = true, recursive = false, encoding = 'utf8' } = opts

    super()

    this._closed = false
    this._encoding = encoding
    this._handle = binding.watcherInit(path, recursive, this, this._onevent, this._onclose)

    if (!persistent) this.unref()

    if (onchange) this.on('change', onchange)
  }

  close() {
    if (this._closed) return
    this._closed = true

    binding.watcherClose(this._handle)
  }

  ref() {
    if (this._handle) binding.watcherRef(this._handle)
    return this
  }

  unref() {
    if (this._handle) binding.watcherUnref(this._handle)
    return this
  }

  [Symbol.asyncIterator]() {
    const buffer = []
    let done = false
    let error = null
    let next = null

    this.on('change', (eventType, filename) => {
      if (next) {
        next.resolve({ done: false, value: { eventType, filename } })
        next = null
      } else {
        buffer.push({ eventType, filename })
      }
    })
      .on('error', (err) => {
        done = true
        error = err

        if (next) {
          next.reject(error)
          next = null
        }
      })
      .on('close', () => {
        done = true

        if (next) {
          next.resolve({ done })
          next = null
        }
      })

    return {
      next: () =>
        new Promise((resolve, reject) => {
          if (error) return reject(error)

          if (buffer.length) return resolve({ done: false, value: buffer.shift() })

          if (done) return resolve({ done })

          next = { resolve, reject }
        })
    }
  }

  _onevent(err, events, filename) {
    if (err) {
      this.close()
      this.emit('error', err)
    } else {
      const path =
        this._encoding === 'buffer'
          ? Buffer.from(filename)
          : Buffer.from(filename).toString(this._encoding)

      if (events & binding.UV_RENAME) {
        this.emit('change', 'rename', path)
      }

      if (events & binding.UV_CHANGE) {
        this.emit('change', 'change', path)
      }
    }
  }

  _onclose() {
    this._handle = null

    this.emit('close')
  }
}

exports.access = access
exports.appendFile = appendFile
exports.chmod = chmod
exports.chown = chown
exports.close = close
exports.copyFile = copyFile
exports.cp = cp
exports.exists = exists
exports.fchmod = fchmod
exports.fchown = fchown
exports.fdatasync = fdatasync
exports.fstat = fstat
exports.fsync = fsync
exports.ftruncate = ftruncate
exports.futimes = futimes
exports.lchown = lchown
exports.lutimes = lutimes
exports.link = link
exports.lstat = lstat
exports.mkdir = mkdir
exports.mkdtemp = mkdtemp
exports.open = open
exports.opendir = opendir
exports.read = read
exports.readFile = readFile
exports.readdir = readdir
exports.readlink = readlink
exports.readv = readv
exports.realpath = realpath
exports.rename = rename
exports.rm = rm
exports.rmdir = rmdir
exports.stat = stat
exports.statfs = statfs
exports.symlink = symlink
exports.truncate = truncate
exports.unlink = unlink
exports.utimes = utimes
exports.watch = watch
exports.write = write
exports.writeFile = writeFile
exports.writev = writev

exports.accessSync = accessSync
exports.appendFileSync = appendFileSync
exports.chmodSync = chmodSync
exports.chownSync = chownSync
exports.closeSync = closeSync
exports.copyFileSync = copyFileSync
exports.cpSync = cpSync
exports.existsSync = existsSync
exports.fchmodSync = fchmodSync
exports.fchownSync = fchownSync
exports.fdatasyncSync = fdatasyncSync
exports.fstatSync = fstatSync
exports.fsyncSync = fsyncSync
exports.ftruncateSync = ftruncateSync
exports.futimesSync = futimesSync
exports.lchownSync = lchownSync
exports.lutimesSync = lutimesSync
exports.linkSync = linkSync
exports.lstatSync = lstatSync
exports.mkdirSync = mkdirSync
exports.mkdtempSync = mkdtempSync
exports.openSync = openSync
exports.opendirSync = opendirSync
exports.readFileSync = readFileSync
exports.readSync = readSync
exports.readdirSync = readdirSync
exports.readlinkSync = readlinkSync
exports.readvSync = readvSync
exports.realpathSync = realpathSync
exports.renameSync = renameSync
exports.rmSync = rmSync
exports.rmdirSync = rmdirSync
exports.statSync = statSync
exports.statfsSync = statfsSync
exports.symlinkSync = symlinkSync
exports.truncateSync = truncateSync
exports.unlinkSync = unlinkSync
exports.utimesSync = utimesSync
exports.writeFileSync = writeFileSync
exports.writeSync = writeSync
exports.writevSync = writevSync

exports.promises = require('./promises')

exports.Stats = Stats
exports.StatFs = StatFs
exports.Dir = Dir
exports.Dirent = Dirent
exports.Watcher = Watcher

exports.ReadStream = FileReadStream

exports.createReadStream = function createReadStream(path, opts) {
  return new FileReadStream(path, opts)
}

exports.WriteStream = FileWriteStream

exports.createWriteStream = function createWriteStream(path, opts) {
  return new FileWriteStream(path, opts)
}

function toNamespacedPath(filepath) {
  if (typeof filepath !== 'string') {
    if (isURL(filepath)) filepath = fileURLToPath(filepath)
    else filepath = filepath.toString()
  }

  return path.toNamespacedPath(filepath)
}

function toFlags(flags) {
  switch (flags) {
    case 'r':
      return constants.O_RDONLY
    case 'rs': // Fall through.
    case 'sr':
      return constants.O_RDONLY | constants.O_SYNC
    case 'r+':
      return constants.O_RDWR
    case 'rs+': // Fall through.
    case 'sr+':
      return constants.O_RDWR | constants.O_SYNC

    case 'w':
      return constants.O_TRUNC | constants.O_CREAT | constants.O_WRONLY
    case 'wx': // Fall through.
    case 'xw':
      return constants.O_TRUNC | constants.O_CREAT | constants.O_WRONLY | constants.O_EXCL

    case 'w+':
      return constants.O_TRUNC | constants.O_CREAT | constants.O_RDWR
    case 'wx+': // Fall through.
    case 'xw+':
      return constants.O_TRUNC | constants.O_CREAT | constants.O_RDWR | constants.O_EXCL

    case 'a':
      return constants.O_APPEND | constants.O_CREAT | constants.O_WRONLY
    case 'ax': // Fall through.
    case 'xa':
      return constants.O_APPEND | constants.O_CREAT | constants.O_WRONLY | constants.O_EXCL
    case 'as': // Fall through.
    case 'sa':
      return constants.O_APPEND | constants.O_CREAT | constants.O_WRONLY | constants.O_SYNC

    case 'a+':
      return constants.O_APPEND | constants.O_CREAT | constants.O_RDWR
    case 'ax+': // Fall through.
    case 'xa+':
      return constants.O_APPEND | constants.O_CREAT | constants.O_RDWR | constants.O_EXCL
    case 'as+': // Fall through.
    case 'sa+':
      return constants.O_APPEND | constants.O_CREAT | constants.O_RDWR | constants.O_SYNC
    default:
      return 0
  }
}

function toMode(mode) {
  return parseInt(mode, 8)
}
const binding = require('../binding')

module.exports = {
  O_RDWR: binding.O_RDWR,
  O_RDONLY: binding.O_RDONLY,
  O_WRONLY: binding.O_WRONLY,
  O_CREAT: binding.O_CREAT,
  O_TRUNC: binding.O_TRUNC,
  O_APPEND: binding.O_APPEND,

  F_OK: binding.F_OK || 0,
  R_OK: binding.R_OK || 0,
  W_OK: binding.W_OK || 0,
  X_OK: binding.X_OK || 0,

  S_IFMT: binding.S_IFMT,
  S_IFREG: binding.S_IFREG,
  S_IFDIR: binding.S_IFDIR,
  S_IFCHR: binding.S_IFCHR,
  S_IFLNK: binding.S_IFLNK,
  S_IFBLK: binding.S_IFBLK || 0,
  S_IFIFO: binding.S_IFIFO || 0,
  S_IFSOCK: binding.S_IFSOCK || 0,

  S_IRUSR: binding.S_IRUSR || 0,
  S_IWUSR: binding.S_IWUSR || 0,
  S_IXUSR: binding.S_IXUSR || 0,
  S_IRGRP: binding.S_IRGRP || 0,
  S_IWGRP: binding.S_IWGRP || 0,
  S_IXGRP: binding.S_IXGRP || 0,
  S_IROTH: binding.S_IROTH || 0,
  S_IWOTH: binding.S_IWOTH || 0,
  S_IXOTH: binding.S_IXOTH || 0,

  UV_DIRENT_UNKNOWN: binding.UV_DIRENT_UNKNOWN,
  UV_DIRENT_FILE: binding.UV_DIRENT_FILE,
  UV_DIRENT_DIR: binding.UV_DIRENT_DIR,
  UV_DIRENT_LINK: binding.UV_DIRENT_LINK,
  UV_DIRENT_FIFO: binding.UV_DIRENT_FIFO,
  UV_DIRENT_SOCKET: binding.UV_DIRENT_SOCKET,
  UV_DIRENT_CHAR: binding.UV_DIRENT_CHAR,
  UV_DIRENT_BLOCK: binding.UV_DIRENT_BLOCK,

  COPYFILE_EXCL: binding.UV_FS_COPYFILE_EXCL,
  COPYFILE_FICLONE: binding.UV_FS_COPYFILE_FICLONE,
  COPYFILE_FICLONE_FORCE: binding.UV_FS_COPYFILE_FICLONE_FORCE,
  UV_FS_SYMLINK_DIR: binding.UV_FS_SYMLINK_DIR,
  UV_FS_SYMLINK_JUNCTION: binding.UV_FS_SYMLINK_JUNCTION
}
const os = require('bare-os')

module.exports = class FileError extends Error {
  constructor(msg, opts = {}) {
    const { code, operation = null, path = null, destination = null, fd = -1 } = opts

    if (operation !== null) msg += describe(operation, opts)

    super(`${code}: ${msg}`)

    this.code = code

    if (operation !== null) this.operation = operation
    if (path !== null) this.path = path
    if (destination !== null) this.destination = destination
    if (fd !== -1) this.fd = fd
  }

  get name() {
    return 'FileError'
  }

  // For Node.js compatibility
  get errno() {
    return os.constants.errnos[this.code]
  }

  // For Node.js compatibility
  get syscall() {
    return this.operation
  }

  // For Node.js compatibility
  get dest() {
    return this.destination
  }
}

function describe(operation, opts) {
  const { path = null, destination = null, fd = -1 } = opts

  let result = `, ${operation}`

  if (path !== null) {
    result += ` ${JSON.stringify(path)}`

    if (destination !== null) {
      result += ` -> ${JSON.stringify(destination)}`
    }
  } else if (fd !== -1) {
    result += ` ${fd}`
  }

  return result
}
{
  "name": "bare-fs",
  "version": "4.7.2",
  "description": "Native file system operations for Bare",
  "exports": {
    "./package": "./package.json",
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    },
    "./promises": {
      "types": "./promises.d.ts",
      "default": "./promises.js"
    },
    "./constants": {
      "types": "./lib/constants.d.ts",
      "default": "./lib/constants.js"
    }
  },
  "files": [
    "index.js",
    "index.d.ts",
    "promises.js",
    "promises.d.ts",
    "binding.c",
    "binding.js",
    "CMakeLists.txt",
    "lib",
    "prebuilds"
  ],
  "addon": true,
  "scripts": {
    "format": "prettier --write . && lunte --fix",
    "lint": "prettier --check . && lunte",
    "test": "brittle-bare --coverage test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-fs.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-fs/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-fs#readme",
  "engines": {
    "bare": ">=1.16.0"
  },
  "dependencies": {
    "bare-events": "^2.5.4",
    "bare-path": "^3.0.0",
    "bare-stream": "^2.6.4",
    "bare-url": "^2.2.2",
    "fast-fifo": "^1.3.2"
  },
  "devDependencies": {
    "bare-buffer": "^3.0.2",
    "bare-crypto": "^1.11.2",
    "brittle": "^3.1.1",
    "cmake-bare": "^1.1.7",
    "lunte": "^1.6.0",
    "prettier": "^3.4.1",
    "prettier-config-holepunch": "^2.0.0"
  },
  "peerDependencies": {
    "bare-buffer": "*"
  },
  "peerDependenciesMeta": {
    "bare-buffer": {
      "optional": true
    }
  }
}
const EventEmitter = require('bare-events')
const fs = require('.')

class FileHandle extends EventEmitter {
  constructor(fd) {
    super()

    this.fd = fd
  }

  async close() {
    await fs.close(this.fd)

    this.fd = -1
    this.emit('close')
  }

  async read(buffer, ...args) {
    return {
      bytesRead: await fs.read(this.fd, buffer, ...args),
      buffer
    }
  }

  async readv(buffers, ...args) {
    return {
      bytesRead: await fs.readv(this.fd, buffers, ...args),
      buffers
    }
  }

  async write(buffer, ...args) {
    return {
      bytesWritten: await fs.write(this.fd, buffer, ...args),
      buffer
    }
  }

  async writev(buffers, ...args) {
    return {
      bytesWritten: await fs.writev(this.fd, buffers, ...args),
      buffers
    }
  }

  async stat() {
    return fs.fstat(this.fd)
  }

  async chmod(mode) {
    await fs.fchmod(this.fd, mode)
  }

  async chown(uid, gid) {
    await fs.fchown(this.fd, uid, gid)
  }

  async datasync() {
    return fs.fdatasync(this.fd)
  }

  async sync() {
    return fs.fsync(this.fd)
  }

  async truncate(len) {
    await fs.ftruncate(this.fd, len)
  }

  async utimes(atime, mtime) {
    await fs.futimes(this.fd, atime, mtime)
  }

  createReadStream(opts) {
    return fs.createReadStream(null, { ...opts, fd: this.fd })
  }

  createWriteStream(opts) {
    return fs.createWriteStream(null, { ...opts, fd: this.fd })
  }

  async [Symbol.asyncDispose]() {
    await this.close()
  }
}

exports.open = async function open(filepath, flags, mode) {
  return new FileHandle(await fs.open(filepath, flags, mode))
}

exports.access = fs.access
exports.appendFile = fs.appendFile
exports.chmod = fs.chmod
exports.chown = fs.chown
exports.constants = fs.constants
exports.copyFile = fs.copyFile
exports.cp = fs.cp
exports.lchown = fs.lchown
exports.lutimes = fs.lutimes
exports.link = fs.link
exports.lstat = fs.lstat
exports.mkdir = fs.mkdir
exports.mkdtemp = fs.mkdtemp
exports.opendir = fs.opendir
exports.readFile = fs.readFile
exports.readdir = fs.readdir
exports.readlink = fs.readlink
exports.realpath = fs.realpath
exports.rename = fs.rename
exports.rm = fs.rm
exports.rmdir = fs.rmdir
exports.stat = fs.stat
exports.statfs = fs.statfs
exports.truncate = fs.truncate
exports.symlink = fs.symlink
exports.unlink = fs.unlink
exports.utimes = fs.utimes
exports.watch = fs.watch
exports.writeFile = fs.writeFile
module.exports = require.addon()
const ansiEscapes = require('bare-ansi-escapes')
const getType = require('bare-type')
const binding = require('./binding')

const PLAIN_KEY = /^[a-zA-Z_][a-zA-Z_0-9]*$/

const defaultDepth = 2
const defaultBreakLength = 80
const defaultMaxArrayLength = 40

module.exports = exports = function inspect(value, opts = {}) {
  const {
    colors = false,
    depth = defaultDepth,
    breakLength = defaultBreakLength,
    stylize = defaultStylize(colors)
  } = opts

  const references = new InspectRefMap()

  const tree = inspectValue(value, 0, {
    colors,
    depth,
    breakLength,
    stylize,
    references
  })

  return tree.toString()
}

exports.styles = {
  bigint: ansiEscapes.colorYellow,
  boolean: ansiEscapes.colorYellow,
  date: ansiEscapes.colorMagenta,
  module: ansiEscapes.modifierUnderline,
  name: ansiEscapes.modifierReset,
  null: ansiEscapes.modifierBold,
  number: ansiEscapes.colorYellow,
  regexp: ansiEscapes.colorRed,
  special: ansiEscapes.colorCyan,
  string: ansiEscapes.colorGreen,
  symbol: ansiEscapes.colorGreen,
  undefined: ansiEscapes.colorBrightBlack
}

const styles = exports.styles

function defaultStylize(colors) {
  return function stylize(value, style) {
    const color = colors && styles[style]

    if (color) return color + value + ansiEscapes.modifierReset

    return value
  }
}

class InspectRefMap {
  constructor() {
    this.refs = new WeakMap()
    this.ids = new WeakMap()
    this.nextId = 1
  }

  has(object) {
    return this.refs.has(object)
  }

  get(object) {
    return this.refs.get(object) || null
  }

  set(object, ref) {
    this.refs.set(object, ref)
  }

  id(object) {
    let id = this.ids.get(object)
    if (id) return id

    id = this.nextId++
    this.ids.set(object, id)

    return id
  }
}

class InspectNode {
  constructor(depth, length, opts) {
    const { breakLength = defaultBreakLength, breakAlways = false } = opts

    this.depth = depth
    this.length = length
    this.breakLength = breakLength
    this.breakAlways = breakAlways
  }

  pad(n, string) {
    return string.padStart(n, ' ')
  }

  indent(n, string) {
    return '  '.repeat(n) + string
  }
}

class InspectRef extends InspectNode {
  constructor(depth, opts) {
    super(depth, '[circular *]'.length, opts)

    this.refs = opts.references
    this.count = 0
    this.circular = false
    this.color = opts.colors && styles.special
  }

  get id() {
    return this.refs.id(this)
  }

  increment() {
    return ++this.count
  }

  decrement() {
    return --this.count
  }

  toString(opts = {}) {
    const { offset = 0, pad = 0, indent = 0 } = opts

    let value = this.pad(pad, '[circular *' + this.id + ']')

    if (this.color) value = this.color + value + ansiEscapes.modifierReset

    return offset ? value : this.indent(indent, value)
  }
}

class InspectLeaf extends InspectNode {
  constructor(value, color, depth, opts) {
    const length = value.length

    if (value.includes('\n')) {
      value = value.replaceAll('\n', '\n' + '  '.repeat(depth))

      opts = { ...opts, breakAlways: true }
    }

    super(depth, length, opts)

    this.value = value
    this.color = opts.colors && color
  }

  toString(opts = {}) {
    const { offset = 0, pad = 0, indent = 0 } = opts

    let value = this.pad(pad, this.value)

    if (this.color) value = this.color + value + ansiEscapes.modifierReset

    return offset ? value : this.indent(indent, value)
  }
}

class InspectPair extends InspectNode {
  constructor(delim, left, right, depth, opts) {
    const length = left.length + delim.length + right.length

    if (left.breakAlways || right.breakAlways) {
      opts = { ...opts, breakAlways: true }
    }

    super(depth, length, opts)

    this.delim = delim
    this.left = left
    this.right = right
  }

  toString(opts = {}) {
    const { indent = 0 } = opts

    return this.indent(
      indent,
      this.left +
        this.delim +
        this.right.toString({
          indent,
          offset: this.left.length + this.delim.length
        })
    )
  }
}

class InspectSuspension extends InspectNode {
  constructor(overflow, depth, opts) {
    const label = `... ${overflow} more`

    super(depth, label.length, opts)

    this.label = label
  }

  toString(opts = {}) {
    const { indent = 0 } = opts

    return this.indent(indent, this.label)
  }
}

class InspectSequence extends InspectNode {
  constructor(header, footer, delim, values, ref, depth, opts) {
    const { tabulate = false } = opts

    const length =
      (ref.circular ? '<ref *>'.length + 1 : 0) +
      header.length +
      values.reduce(
        (length, value, i) =>
          length + value.length + (i === 0 ? 0 : delim.length),
        0
      ) +
      footer.length

    if (values.some((value) => value.breakAlways)) {
      opts = { ...opts, breakAlways: true }
    }

    super(depth, length, opts)

    this.header = header
    this.footer = footer
    this.delim = delim
    this.values = values
    this.ref = ref
    this.tabulate = tabulate
  }

  toString(opts = {}) {
    const { offset = 0, indent = 0 } = opts

    const split =
      this.breakAlways ||
      (this.values.length &&
        (offset + this.length > this.breakLength ||
          indent * 2 + this.length > this.breakLength))

    let header = this.header

    if (this.ref.circular) {
      header = '<ref *' + this.ref.id + '> ' + header
    }

    if (this.values.length === 0) {
      header = header.trimEnd()
    }

    if (offset === 0) {
      header = this.indent(indent, header)
    }

    if (split) {
      header = header.trimEnd() + '\n'
    }

    let string = header

    let columns = 1
    let pad = 0

    if (this.tabulate) {
      const widest = this.values.reduce(
        (length, value) =>
          value.breakAlways ? length : Math.max(length, value.length),
        0
      )

      if (widest) {
        columns = Math.max(
          columns,
          Math.floor(
            (this.breakLength - indent * 2) / (widest + this.delim.length)
          )
        )

        if (columns > 1) pad = widest
      }
    }

    for (let i = 0, n = this.values.length, offset = 0; i < n; i++) {
      const value = this.values[i]

      if (split) {
        let part

        if (i % columns === 0 || value.breakAlways) {
          part = value.toString({ indent: indent + 1, pad })
        } else {
          part = value.toString({ pad })
        }

        string += part

        if (i < n - 1) {
          if (i % columns === columns - 1 || this.values[i + 1].breakAlways) {
            string += this.delim.trimEnd() + '\n'
          } else {
            string += this.delim
          }
        }
      } else {
        if (i > 0) string += this.delim

        string += value.toString({ offset })

        offset += value.length
      }
    }

    let footer = this.footer

    if (this.values.length === 0) {
      footer = footer.trimStart()
    }

    if (split) {
      string += '\n' + this.indent(indent, footer.trimStart())
    } else {
      string += footer
    }

    return string
  }
}

function inspectValue(value, depth, opts) {
  const type = getType(value)

  if (type.isUndefined()) return inspectUndefined(depth, opts)
  if (type.isNull()) return inspectNull(depth, opts)
  if (type.isBoolean()) return inspectBoolean(value, depth, opts)
  if (type.isNumber()) return inspectNumber(value, depth, opts)
  if (type.isBigInt()) return inspectBigInt(value, depth, opts)
  if (type.isString()) return inspectString(value, depth, opts)
  if (type.isSymbol()) return inspectSymbol(value, depth, opts)
  if (type.isObject()) return inspectObject(type, value, depth, opts)
  if (type.isFunction()) return inspectFunction(type, value, depth, opts)
  if (type.isExternal()) return inspectExternal(value, opts, opts)
}

function inspectUndefined(depth, opts) {
  return new InspectLeaf('undefined', styles.undefined, depth, opts)
}

function inspectNull(depth, opts) {
  return new InspectLeaf('null', styles.null, depth, opts)
}

function inspectBoolean(value, depth, opts) {
  return new InspectLeaf(value.toString(), styles.boolean, depth, opts)
}

function inspectNumber(value, depth, opts) {
  let string

  if (Object.is(value, -0)) {
    string = '-0'
  } else {
    string = value.toString(10)
  }

  return new InspectLeaf(string, styles.number, depth, opts)
}

function inspectBigInt(value, depth, opts) {
  return new InspectLeaf(value.toString(10) + 'n', styles.bigint, depth, opts)
}

const STRING_ESCAPES =
  /[\ud800-\udbff][\udc00-\udfff]|[\u0000-\u001f'\\\ud800-\udfff]/g

function inspectString(value, depth, opts) {
  // https://tc39.es/ecma262/multipage/structured-data.html#sec-quotejsonstring
  const string = value.replace(STRING_ESCAPES, (match) => {
    if (match.length === 2) return match

    switch (match) {
      case "'":
        return "\\'"
      case '\\':
        return '\\\\'
      case '\b':
        return '\\b'
      case '\t':
        return '\\t'
      case '\n':
        return '\\n'
      case '\f':
        return '\\f'
      case '\r':
        return '\\r'
      default:
        return '\\u' + match.charCodeAt(0).toString(16).padStart(4, '0')
    }
  })

  return new InspectLeaf("'" + string + "'", styles.string, depth, opts)
}

function inspectSymbol(value, depth, opts) {
  return new InspectLeaf(value.toString(), styles.symbol, depth, opts)
}

function inspectKey(value, depth, opts) {
  if (PLAIN_KEY.test(value)) {
    return new InspectLeaf(value, null, depth, opts)
  } else {
    return inspectValue(value, depth, opts)
  }
}

function inspectObject(type, object, depth, opts) {
  const refs = opts.references

  let ref = refs.get(object)
  if (ref === null) {
    ref = new InspectRef(depth, opts)
    refs.set(object, ref)
  } else if (ref.count) {
    ref.circular = true
    return ref
  }

  const maxDepth = typeof opts.depth === 'number' ? opts.depth : Infinity

  if (maxDepth < depth) {
    const constructor = object.constructor

    return new InspectLeaf(
      '[' +
        (constructor && constructor.name ? constructor.name : 'Object') +
        ']',
      styles.special,
      depth,
      opts
    )
  }

  const inspect =
    object[Symbol.for('bare.inspect')] ||
    object[Symbol.for('nodejs.util.inspect.custom')]

  if (typeof inspect === 'function') {
    const value = inspect.call(
      object,
      typeof opts.depth === 'number' ? opts.depth - depth : null,
      {
        colors: opts.colors,
        breakLength: opts.breakLength,
        stylize: opts.stylize
      },
      exports
    )

    if (typeof value === 'object' && value !== null) {
      refs.set(value, ref)
    }

    if (typeof value !== 'string') {
      return inspectValue(value, depth, opts)
    }

    return new InspectLeaf(value, null, depth, opts)
  }

  if (type.isArray()) return inspectArray(object, ref, depth, opts)
  if (type.isDate()) return inspectDate(object, ref, depth, opts)
  if (type.isRegExp()) return inspectRegExp(object, ref, depth, opts)
  if (type.isError()) return inspectError(object, ref, depth, opts)
  if (type.isPromise()) return inspectPromise(object, ref, depth, opts)
  if (type.isMap()) return inspectMap(object, ref, depth, opts)
  if (type.isSet()) return inspectSet(object, ref, depth, opts)
  if (type.isWeakMap()) return inspectWeakMap(object, ref, depth, opts)
  if (type.isWeakSet()) return inspectWeakSet(object, ref, depth, opts)
  if (type.isWeakRef()) return inspectWeakRef(object, ref, depth, opts)
  if (type.isArrayBuffer()) return inspectArrayBuffer(object, ref, depth, opts)
  if (type.isSharedArrayBuffer())
    return inspectSharedArrayBuffer(object, ref, depth, opts)
  if (type.isTypedArray()) return inspectTypedArray(object, ref, depth, opts)
  if (type.isDataView()) return inspectDataView(object, ref, depth, opts)

  ref.increment()

  const values = []

  for (const key in object) {
    if (key === 'constructor') continue

    values.push(
      new InspectPair(
        ': ',
        inspectKey(key, depth + 1, opts),
        inspectValue(object[key], depth + 1, opts),
        depth + 1,
        opts
      )
    )
  }

  ref.decrement()

  let header = '{ '

  const tag = object[Symbol.toStringTag]

  if (tag) header = '[' + tag + '] ' + header

  if (object.constructor) {
    const name = object.constructor.name

    if (name && name !== 'Object') {
      header = object.constructor.name + ' ' + header
    }
  }

  return new InspectSequence(header, ' }', ', ', values, ref, depth, opts)
}

function inspectArray(array, ref, depth, opts) {
  const { maxArrayLength = defaultMaxArrayLength } = opts

  ref.increment()

  const values = []

  let remaining = Math.max(maxArrayLength, 0)

  for (let i = 0, n = array.length; i < n; i++) {
    if (remaining-- === 0) {
      values.push(
        new InspectSuspension(array.length - values.length, depth + 1, {
          ...opts,
          breakAlways: true
        })
      )

      break
    }

    values.push(inspectValue(array[i], depth + 1, opts))
  }

  for (const key of binding.getOwnNonIndexPropertyNames(array)) {
    values.push(
      new InspectPair(
        ': ',
        inspectKey(key, depth + 1, opts),
        inspectValue(array[key], depth + 1, opts),
        depth + 1,
        { ...opts, breakAlways: remaining < 0 }
      )
    )
  }

  ref.decrement()

  let header = '[ '

  if (array.constructor.name !== 'Array') {
    header = array.constructor.name + '(' + array.length + ') ' + header
  }

  return new InspectSequence(header, ' ]', ', ', values, ref, depth, {
    ...opts,
    tabulate: true
  })
}

function inspectDate(date, ref, depth, opts) {
  return new InspectLeaf(date.toISOString(), styles.date, depth, opts)
}

function inspectRegExp(regExp, ref, depth, opts) {
  return new InspectLeaf(regExp.toString(), styles.regexp, depth, opts)
}

function inspectError(error, ref, depth, opts) {
  let header

  if ('stack' in error) {
    header = error.stack

    if (depth > 0) {
      header = header.replaceAll('\n', '\n' + '  '.repeat(depth))
    }
  } else {
    header = error.toString()
  }

  const builtins = ['cause']

  if (error.name === 'AggregateError') {
    builtins.push('errors')
  } else if (error.name === 'SuppressedError') {
    builtins.push('error', 'suppressed')
  }

  const values = []

  for (const key of builtins) {
    if (key in error === false) continue

    values.push(
      new InspectPair(
        ': ',
        new InspectLeaf('[' + key + ']', null, depth + 1, opts),
        inspectValue(error[key], depth + 1, opts),
        depth + 1,
        opts
      )
    )
  }

  for (const key in error) {
    if (key === 'constructor' || builtins.includes(key)) continue

    values.push(
      new InspectPair(
        ': ',
        inspectKey(key, depth + 1, opts),
        inspectValue(error[key], depth + 1, opts),
        depth + 1,
        opts
      )
    )
  }

  if (values.length === 0) return new InspectLeaf(header, null, depth, opts)

  return new InspectSequence(
    header + ' {',
    ' }',
    ', ',
    values,
    ref,
    depth,
    opts
  )
}

function inspectPromise(promise, ref, depth, opts) {
  ref.increment()

  const state = binding.getPromiseState(promise)

  const values = []

  switch (state) {
    case 0: // Pending
      values.push(new InspectLeaf('<pending>', styles.special, depth, opts))
      break

    case 1: // Fulfilled
      values.push(inspectValue(binding.getPromiseResult(promise), depth, opts))
      break

    case 2: // Rejected
      values.push(
        new InspectLeaf('<rejected>', styles.special, depth, opts),
        inspectValue(binding.getPromiseResult(promise), depth, opts)
      )
  }

  ref.decrement()

  const header = promise.constructor.name + ' { '

  return new InspectSequence(header, ' }', ' ', values, ref, depth, opts)
}

function inspectMap(map, ref, depth, opts) {
  const {
    maxArrayLength = defaultMaxArrayLength,
    maxMapLength = maxArrayLength
  } = opts

  ref.increment()

  const values = []

  let remaining = maxMapLength

  for (const entry of map) {
    if (remaining-- === 0) {
      values.push(
        new InspectSuspension(map.size - values.length, depth + 1, {
          ...opts,
          breakAlways: true
        })
      )
      break
    }

    values.push(
      new InspectPair(
        ' => ',
        inspectValue(entry[0], depth + 1, opts),
        inspectValue(entry[1], depth + 1, opts),
        depth + 1,
        opts
      )
    )
  }

  for (const key in map) {
    if (key === 'constructor') continue

    const value = inspectValue(map[key], depth + 1, opts)

    values.push(
      new InspectPair(
        ': ',
        inspectKey(key, depth + 1, opts),
        value,
        depth + 1,
        { ...opts, breakAlways: remaining < 0 }
      )
    )
  }

  ref.decrement()

  const header = map.constructor.name + '(' + map.size + ') { '

  return new InspectSequence(header, ' }', ', ', values, ref, depth, {
    ...opts,
    tabulate: true
  })
}

function inspectSet(set, ref, depth, opts) {
  const {
    maxArrayLength = defaultMaxArrayLength,
    maxSetLength = maxArrayLength
  } = opts

  ref.increment()

  const values = []

  let remaining = maxSetLength

  for (const entry of set) {
    if (remaining-- === 0) {
      values.push(
        new InspectSuspension(set.size - values.length, depth + 1, {
          ...opts,
          breakAlways: true
        })
      )
      break
    }

    values.push(inspectValue(entry, depth + 1, opts))
  }

  for (const key in set) {
    if (key === 'constructor') continue

    const value = inspectValue(set[key], depth + 1, opts)

    values.push(
      new InspectPair(
        ': ',
        inspectKey(key, depth + 1, opts),
        value,
        depth + 1,
        { ...opts, breakAlways: remaining < 0 }
      )
    )
  }

  ref.decrement()

  const header = set.constructor.name + '(' + set.size + ') { '

  return new InspectSequence(header, ' }', ', ', values, ref, depth, {
    ...opts,
    tabulate: true
  })
}

function inspectWeakMap(weakMap, ref, depth, opts) {
  const header = weakMap.constructor.name + ' { '

  return new InspectSequence(
    header,
    ' }',
    ' ',
    [new InspectLeaf('<items unknown>', styles.special, depth + 1, opts)],
    ref,
    depth,
    opts
  )
}

function inspectWeakSet(weakSet, ref, depth, opts) {
  const header = weakSet.constructor.name + ' { '

  return new InspectSequence(
    header,
    ' }',
    ' ',
    [new InspectLeaf('<items unknown>', styles.special, depth + 1, opts)],
    ref,
    depth,
    opts
  )
}

function inspectWeakRef(weakRef, ref, depth, opts) {
  const target = weakRef.deref()

  let value

  if (target === undefined) {
    value = new InspectLeaf('<cleared>', styles.special, depth + 1, opts)
  } else {
    value = inspectValue(target, depth + 1, opts)
  }

  const header = weakRef.constructor.name + ' { '

  return new InspectSequence(header, ' }', ' ', [value], ref, depth, opts)
}

function inspectArrayBuffer(arrayBuffer, ref, depth, opts) {
  ref.increment()

  const values = []

  for (const key of ['byteLength']) {
    values.push(
      new InspectPair(
        ': ',
        inspectKey(key, depth + 1, opts),
        inspectValue(arrayBuffer[key], depth + 1, opts),
        depth + 1,
        opts
      )
    )
  }

  ref.decrement()

  const header = arrayBuffer.constructor.name + ' { '

  return new InspectSequence(header, ' }', ', ', values, ref, depth, opts)
}

function inspectSharedArrayBuffer(sharedArrayBuffer, ref, depth, opts) {
  ref.increment()

  const values = []

  for (const key of ['byteLength']) {
    values.push(
      new InspectPair(
        ': ',
        inspectKey(key, depth + 1, opts),
        inspectValue(sharedArrayBuffer[key], depth + 1, opts),
        depth + 1,
        opts
      )
    )
  }

  ref.decrement()

  const header = sharedArrayBuffer.constructor.name + ' { '

  return new InspectSequence(header, ' }', ', ', values, ref, depth, opts)
}

function inspectTypedArray(typedArray, ref, depth, opts) {
  if (Buffer.isBuffer(typedArray)) {
    return inspectBuffer(typedArray, ref, depth, opts)
  }

  const {
    maxArrayLength = defaultMaxArrayLength,
    maxTypedArrayLength = maxArrayLength
  } = opts

  ref.increment()

  const values = []

  let remaining = Math.max(maxTypedArrayLength, 0)

  for (let i = 0, n = typedArray.length; i < n; i++) {
    if (remaining-- === 0) {
      values.push(
        new InspectSuspension(typedArray.length - values.length, depth + 1, {
          ...opts,
          breakAlways: true
        })
      )

      break
    }

    values.push(inspectValue(typedArray[i], depth + 1, opts))
  }

  for (const key of binding.getOwnNonIndexPropertyNames(typedArray)) {
    values.push(
      new InspectPair(
        ': ',
        inspectKey(key, depth + 1, opts),
        inspectValue(typedArray[key], depth + 1, opts),
        depth + 1,
        { ...opts, breakAlways: remaining < 0 }
      )
    )
  }

  ref.decrement()

  const header = typedArray.constructor.name + '(' + typedArray.length + ') [ '

  return new InspectSequence(header, ' ]', ', ', values, ref, depth, {
    ...opts,
    tabulate: true
  })
}

function inspectBuffer(buffer, ref, depth, opts) {
  const {
    maxArrayLength = defaultMaxArrayLength,
    maxBufferLength = maxArrayLength
  } = opts

  ref.increment()

  const values = []

  let remaining = Math.max(maxBufferLength, 0)

  for (let i = 0, n = buffer.byteLength; i < n; i++) {
    if (remaining-- === 0) {
      values.push(
        new InspectSuspension(buffer.length - values.length, depth + 1, {
          ...opts,
          breakAlways: true
        })
      )

      break
    }

    values.push(
      new InspectLeaf(
        buffer[i].toString(16).padStart(2, '0'),
        null,
        depth + 1,
        opts
      )
    )
  }

  for (const key of binding.getOwnNonIndexPropertyNames(buffer)) {
    values.push(
      new InspectPair(
        ': ',
        inspectKey(key, depth + 1, opts),
        inspectValue(buffer[key], depth + 1, opts),
        depth + 1,
        { ...opts, breakAlways: remaining < 0 }
      )
    )
  }

  ref.decrement()

  return new InspectSequence('<Buffer ', '>', ' ', values, ref, depth, {
    ...opts,
    tabulate: true
  })
}

function inspectDataView(dataView, ref, depth, opts) {
  ref.increment()

  const values = []

  for (const key of ['byteLength', 'byteOffset', 'buffer']) {
    values.push(
      new InspectPair(
        ': ',
        inspectKey(key, depth + 1, opts),
        inspectValue(dataView[key], depth + 1, opts),
        depth + 1,
        opts
      )
    )
  }

  for (const key of binding.getOwnNonIndexPropertyNames(dataView)) {
    values.push(
      new InspectPair(
        ': ',
        inspectKey(key, depth + 1, opts),
        inspectValue(dataView[key], depth + 1, opts),
        depth + 1,
        opts
      )
    )
  }

  ref.decrement()

  const header = dataView.constructor.name + ' { '

  return new InspectSequence(header, ' }', ', ', values, ref, depth, opts)
}

function inspectFunction(type, fn, depth, opts) {
  if (fn.toString().startsWith('class')) return inspectClass(fn, depth, opts)

  let tag = 'function'

  if (type.isGeneratorFunction()) tag = 'generator ' + tag
  if (type.isAsyncFunction()) tag = 'async ' + tag

  return new InspectLeaf(
    '[' + tag + ' ' + (fn.name ? fn.name : '(anonymous)') + ']',
    styles.special,
    depth,
    opts
  )
}

function inspectClass(ctor, depth, opts) {
  return new InspectLeaf(
    '[class ' + (ctor.name ? ctor.name : '(anonymous)') + ']',
    styles.special,
    depth,
    opts
  )
}

function inspectExternal(external, depth, opts) {
  return new InspectLeaf(
    '[external 0x' + binding.getExternal(external).toString(16) + ']',
    styles.special,
    depth,
    opts
  )
}
{
  "name": "bare-inspect",
  "version": "3.1.4",
  "description": "Inspect objects as strings for debugging",
  "exports": {
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    },
    "./package": "./package.json"
  },
  "files": [
    "index.js",
    "index.d.ts",
    "binding.c",
    "binding.js",
    "CMakeLists.txt",
    "prebuilds"
  ],
  "addon": true,
  "scripts": {
    "test": "prettier . --check && bare test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-inspect.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-inspect/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-inspect#readme",
  "engines": {
    "bare": ">=1.18.0"
  },
  "dependencies": {
    "bare-ansi-escapes": "^2.1.0",
    "bare-type": "^1.0.0"
  },
  "devDependencies": {
    "brittle": "^3.3.2",
    "cmake-bare": "^1.1.6",
    "prettier": "^3.4.1",
    "prettier-config-standard": "^7.0.0"
  }
}
module.exports = require.addon()
const binding = require('./binding')
const errors = require('./lib/errors')
const constants = require('./lib/constants')

exports.constants = constants
exports.errors = errors

exports.EOL = binding.platform === 'win32' ? '\r\n' : '\n'

exports.devNull = binding.platform === 'win32' ? '\\\\.\\nul' : '/dev/null'

exports.platform = function platform() {
  return binding.platform
}

exports.arch = function arch() {
  return binding.arch
}

exports.type = binding.type
exports.version = binding.version
exports.release = binding.release
exports.machine = binding.machine
exports.execPath = binding.execPath
exports.pid = binding.pid
exports.ppid = binding.ppid
exports.cwd = binding.cwd
exports.chdir = binding.chdir
exports.tmpdir = binding.tmpdir
exports.homedir = binding.homedir
exports.hostname = binding.hostname
exports.userInfo = binding.userInfo
exports.groupInfo = binding.groupInfo

exports.networkInterfaces = function networkInterfaces() {
  const result = {}

  for (const entry of binding.networkInterfaces()) {
    const { name, ...properties } = entry

    if (result[name]) result[name].push(properties)
    else result[name] = [properties]
  }

  return result
}

exports.kill = function kill(pid, signal = constants.signals.SIGTERM) {
  if (typeof signal === 'string') {
    if (signal in constants.signals === false) {
      throw errors.UNKNOWN_SIGNAL('Unknown signal: ' + signal)
    }

    signal = constants.signals[signal]
  }

  binding.kill(pid, signal)
}

exports.endianness = function endianness() {
  return binding.isLittleEndian ? 'LE' : 'BE'
}

exports.availableParallelism = binding.availableParallelism

exports.cpuUsage = function cpuUsage(previous) {
  const current = binding.cpuUsage()

  if (previous) {
    return {
      user: current.user - previous.user,
      system: current.system - previous.system
    }
  }

  return current
}

exports.threadCpuUsage = function threadCpuUsage(previous) {
  const current = binding.threadCpuUsage()

  if (previous) {
    return {
      user: current.user - previous.user,
      system: current.system - previous.system
    }
  }

  return current
}

exports.resourceUsage = binding.resourceUsage
exports.memoryUsage = binding.memoryUsage
exports.freemem = binding.freemem
exports.totalmem = binding.totalmem
exports.availableMemory = binding.availableMemory
exports.constrainedMemory = binding.constrainedMemory
exports.uptime = binding.uptime
exports.loadavg = binding.loadavg
exports.cpus = binding.cpus

exports.getProcessTitle = binding.getProcessTitle

exports.setProcessTitle = function setProcessTitle(title) {
  if (typeof title !== 'string') title = title.toString()

  if (title.length >= 256) {
    throw errors.TITLE_OVERFLOW('Process title is too long')
  }

  binding.setProcessTitle(title)
}

exports.getPriority = function getPriority(pid = 0) {
  return binding.getPriority(pid)
}

exports.setPriority = function setPriority(pid, priority) {
  if (priority === undefined) {
    priority = pid
    pid = 0
  }

  binding.setPriority(pid, priority)
}

exports.getEnvKeys = binding.getEnvKeys
exports.getEnv = binding.getEnv
exports.hasEnv = binding.hasEnv
exports.setEnv = binding.setEnv
exports.unsetEnv = binding.unsetEnv
const binding = require('../binding')

module.exports = {
  signals: binding.signals,
  errnos: binding.errnos,
  priority: binding.priority
}
module.exports = class OSError extends Error {
  constructor(msg, fn = OSError, code = fn.name) {
    super(`${code}: ${msg}`)
    this.code = code

    if (Error.captureStackTrace) {
      Error.captureStackTrace(this, fn)
    }
  }

  get name() {
    return 'OSError'
  }

  static UNKNOWN_SIGNAL(msg) {
    return new OSError(msg, OSError.UNKNOWN_SIGNAL)
  }

  static TITLE_OVERFLOW(msg) {
    return new OSError(msg, OSError.TITLE_OVERFLOW)
  }
}
{
  "name": "bare-os",
  "version": "3.9.1",
  "description": "Operating system utilities for Bare",
  "exports": {
    "./package": "./package.json",
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    },
    "./constants": {
      "types": "./lib/constants.d.ts",
      "default": "./lib/constants.js"
    },
    "./errors": {
      "types": "./lib/errors.d.ts",
      "default": "./lib/errors.js"
    }
  },
  "files": [
    "index.js",
    "index.d.ts",
    "binding.c",
    "binding.js",
    "CMakeLists.txt",
    "lib",
    "prebuilds"
  ],
  "addon": true,
  "scripts": {
    "format": "prettier --write . && lunte --fix",
    "lint": "prettier --check . && lunte",
    "test": "brittle-bare --coverage test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-os.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-os/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-os#readme",
  "engines": {
    "bare": ">=1.14.0"
  },
  "devDependencies": {
    "brittle": "^3.1.1",
    "cmake-bare": "^1.1.6",
    "lunte": "^1.6.0",
    "prettier": "^3.4.2",
    "prettier-config-holepunch": "^2.0.0"
  }
}
// This export SHOULD NOT be shortened in any way as having the full
// `module.exports = require(...)` statement is crucial for synthesizing
// ESM exports.

if (Bare.platform === 'win32') {
  module.exports = require('./lib/win32')
} else {
  module.exports = require('./lib/posix')
}
module.exports = {
  CHAR_UPPERCASE_A: 0x41,
  CHAR_LOWERCASE_A: 0x61,
  CHAR_UPPERCASE_Z: 0x5a,
  CHAR_LOWERCASE_Z: 0x7a,
  CHAR_DOT: 0x2e,
  CHAR_FORWARD_SLASH: 0x2f,
  CHAR_BACKWARD_SLASH: 0x5c,
  CHAR_COLON: 0x3a,
  CHAR_QUESTION_MARK: 0x3f
}
const os = require('bare-os')

const { normalizeString } = require('./shared')
const { CHAR_DOT, CHAR_FORWARD_SLASH } = require('./constants')

function isPosixPathSeparator(code) {
  return code === CHAR_FORWARD_SLASH
}

exports.win32 = require('./win32')
exports.posix = exports

exports.sep = '/'
exports.delimiter = ':'

exports.resolve = function resolve(...args) {
  let resolvedPath = ''
  let resolvedAbsolute = false

  for (let i = args.length - 1; i >= -1 && !resolvedAbsolute; i--) {
    const path = i >= 0 ? args[i] : os.cwd()

    if (path.length === 0) {
      continue
    }

    resolvedPath = `${path}/${resolvedPath}`
    resolvedAbsolute = path.charCodeAt(0) === CHAR_FORWARD_SLASH
  }

  resolvedPath = normalizeString(resolvedPath, !resolvedAbsolute, '/', isPosixPathSeparator)

  if (resolvedAbsolute) {
    return `/${resolvedPath}`
  }

  return resolvedPath.length > 0 ? resolvedPath : '.'
}

exports.normalize = function normalize(path) {
  if (path.length === 0) return '.'

  const isAbsolute = path.charCodeAt(0) === CHAR_FORWARD_SLASH
  const trailingSeparator = path.charCodeAt(path.length - 1) === CHAR_FORWARD_SLASH

  path = normalizeString(path, !isAbsolute, '/', isPosixPathSeparator)

  if (path.length === 0) {
    if (isAbsolute) return '/'
    return trailingSeparator ? './' : '.'
  }

  if (trailingSeparator) path += '/'

  return isAbsolute ? `/${path}` : path
}

exports.isAbsolute = function isAbsolute(path) {
  return path.length > 0 && path.charCodeAt(0) === CHAR_FORWARD_SLASH
}

exports.join = function join(...args) {
  if (args.length === 0) return '.'
  let joined
  for (let i = 0; i < args.length; ++i) {
    const arg = args[i]
    if (arg.length > 0) {
      if (joined === undefined) joined = arg
      else joined += `/${arg}`
    }
  }
  if (joined === undefined) return '.'
  return exports.normalize(joined)
}

exports.relative = function relative(from, to) {
  if (from === to) return ''

  from = exports.resolve(from)
  to = exports.resolve(to)

  if (from === to) return ''

  const fromStart = 1
  const fromEnd = from.length
  const fromLen = fromEnd - fromStart
  const toStart = 1
  const toLen = to.length - toStart

  const length = fromLen < toLen ? fromLen : toLen
  let lastCommonSep = -1
  let i = 0
  for (; i < length; i++) {
    const fromCode = from.charCodeAt(fromStart + i)
    if (fromCode !== to.charCodeAt(toStart + i)) {
      break
    } else if (fromCode === CHAR_FORWARD_SLASH) {
      lastCommonSep = i
    }
  }
  if (i === length) {
    if (toLen > length) {
      if (to.charCodeAt(toStart + i) === CHAR_FORWARD_SLASH) {
        return to.substring(toStart + i + 1)
      }
      if (i === 0) {
        return to.substring(toStart + i)
      }
    } else if (fromLen > length) {
      if (from.charCodeAt(fromStart + i) === CHAR_FORWARD_SLASH) {
        lastCommonSep = i
      } else if (i === 0) {
        lastCommonSep = 0
      }
    }
  }

  let out = ''
  for (i = fromStart + lastCommonSep + 1; i <= fromEnd; ++i) {
    if (i === fromEnd || from.charCodeAt(i) === CHAR_FORWARD_SLASH) {
      out += out.length === 0 ? '..' : '/..'
    }
  }

  return `${out}${to.substring(toStart + lastCommonSep)}`
}

exports.toNamespacedPath = function toNamespacedPath(path) {
  return path
}

exports.dirname = function dirname(path) {
  if (path.length === 0) return '.'
  const hasRoot = path.charCodeAt(0) === CHAR_FORWARD_SLASH
  let end = -1
  let matchedSlash = true
  for (let i = path.length - 1; i >= 1; --i) {
    if (path.charCodeAt(i) === CHAR_FORWARD_SLASH) {
      if (!matchedSlash) {
        end = i
        break
      }
    } else {
      matchedSlash = false
    }
  }

  if (end === -1) return hasRoot ? '/' : '.'
  if (hasRoot && end === 1) return '//'
  return path.substring(0, end)
}

exports.basename = function basename(path, suffix) {
  let start = 0
  let end = -1
  let matchedSlash = true

  if (suffix !== undefined && suffix.length > 0 && suffix.length <= path.length) {
    if (suffix === path) {
      return ''
    }
    let extIdx = suffix.length - 1
    let firstNonSlashEnd = -1
    for (let i = path.length - 1; i >= 0; --i) {
      const code = path.charCodeAt(i)
      if (code === CHAR_FORWARD_SLASH) {
        if (!matchedSlash) {
          start = i + 1
          break
        }
      } else {
        if (firstNonSlashEnd === -1) {
          matchedSlash = false
          firstNonSlashEnd = i + 1
        }
        if (extIdx >= 0) {
          if (code === suffix.charCodeAt(extIdx)) {
            if (--extIdx === -1) {
              end = i
            }
          } else {
            extIdx = -1
            end = firstNonSlashEnd
          }
        }
      }
    }

    if (start === end) end = firstNonSlashEnd
    else if (end === -1) end = path.length
    return path.substring(start, end)
  }

  for (let i = path.length - 1; i >= 0; --i) {
    if (path.charCodeAt(i) === CHAR_FORWARD_SLASH) {
      if (!matchedSlash) {
        start = i + 1
        break
      }
    } else if (end === -1) {
      matchedSlash = false
      end = i + 1
    }
  }

  if (end === -1) return ''
  return path.substring(start, end)
}

exports.extname = function extname(path) {
  let startDot = -1
  let startPart = 0
  let end = -1
  let matchedSlash = true
  let preDotState = 0
  for (let i = path.length - 1; i >= 0; --i) {
    const code = path.charCodeAt(i)
    if (code === CHAR_FORWARD_SLASH) {
      if (!matchedSlash) {
        startPart = i + 1
        break
      }
      continue
    }
    if (end === -1) {
      matchedSlash = false
      end = i + 1
    }
    if (code === CHAR_DOT) {
      if (startDot === -1) startDot = i
      else if (preDotState !== 1) preDotState = 1
    } else if (startDot !== -1) {
      preDotState = -1
    }
  }

  if (
    startDot === -1 ||
    end === -1 ||
    preDotState === 0 ||
    (preDotState === 1 && startDot === end - 1 && startDot === startPart + 1)
  ) {
    return ''
  }
  return path.substring(startDot, end)
}
const { CHAR_DOT, CHAR_FORWARD_SLASH } = require('./constants')

exports.normalizeString = function normalizeString(
  path,
  allowAboveRoot,
  separator,
  isPathSeparator
) {
  let res = ''
  let lastSegmentLength = 0
  let lastSlash = -1
  let dots = 0
  let code = 0
  for (let i = 0; i <= path.length; ++i) {
    if (i < path.length) {
      code = path.charCodeAt(i)
    } else if (isPathSeparator(code)) {
      break
    } else {
      code = CHAR_FORWARD_SLASH
    }

    if (isPathSeparator(code)) {
      if (lastSlash === i - 1 || dots === 1);
      else if (dots === 2) {
        if (
          res.length < 2 ||
          lastSegmentLength !== 2 ||
          res.charCodeAt(res.length - 1) !== CHAR_DOT ||
          res.charCodeAt(res.length - 2) !== CHAR_DOT
        ) {
          if (res.length > 2) {
            const lastSlashIndex = res.lastIndexOf(separator)
            if (lastSlashIndex === -1) {
              res = ''
              lastSegmentLength = 0
            } else {
              res = res.substring(0, lastSlashIndex)
              lastSegmentLength = res.length - 1 - res.lastIndexOf(separator)
            }
            lastSlash = i
            dots = 0
            continue
          } else if (res.length !== 0) {
            res = ''
            lastSegmentLength = 0
            lastSlash = i
            dots = 0
            continue
          }
        }
        if (allowAboveRoot) {
          res += res.length > 0 ? `${separator}..` : '..'
          lastSegmentLength = 2
        }
      } else {
        if (res.length > 0) {
          res += `${separator}${path.substring(lastSlash + 1, i)}`
        } else {
          res = path.substring(lastSlash + 1, i)
        }
        lastSegmentLength = i - lastSlash - 1
      }
      lastSlash = i
      dots = 0
    } else if (code === CHAR_DOT && dots !== -1) {
      ++dots
    } else {
      dots = -1
    }
  }
  return res
}
const os = require('bare-os')

const { normalizeString } = require('./shared')
const {
  CHAR_UPPERCASE_A,
  CHAR_LOWERCASE_A,
  CHAR_UPPERCASE_Z,
  CHAR_LOWERCASE_Z,
  CHAR_DOT,
  CHAR_FORWARD_SLASH,
  CHAR_BACKWARD_SLASH,
  CHAR_COLON,
  CHAR_QUESTION_MARK
} = require('./constants')

function isWindowsPathSeparator(code) {
  return code === CHAR_FORWARD_SLASH || code === CHAR_BACKWARD_SLASH
}

function isWindowsDeviceRoot(code) {
  return (
    (code >= CHAR_UPPERCASE_A && code <= CHAR_UPPERCASE_Z) ||
    (code >= CHAR_LOWERCASE_A && code <= CHAR_LOWERCASE_Z)
  )
}

exports.posix = require('./posix')
exports.win32 = exports

exports.sep = '\\'
exports.delimiter = ';'

exports.resolve = function resolve(...args) {
  let resolvedDevice = ''
  let resolvedTail = ''
  let resolvedAbsolute = false

  for (let i = args.length - 1; i >= -1; i--) {
    let path
    if (i >= 0) {
      path = args[i]

      if (path.length === 0) continue
    } else if (resolvedDevice.length === 0) {
      path = os.cwd()
    } else {
      path = os.getEnv(`=${resolvedDevice}`) || os.cwd()

      if (
        path === undefined ||
        (path.substring(0, 2).toLowerCase() !== resolvedDevice.toLowerCase() &&
          path.charCodeAt(2) === CHAR_BACKWARD_SLASH)
      ) {
        path = `${resolvedDevice}\\`
      }
    }

    const len = path.length
    let rootEnd = 0
    let device = ''
    let isAbsolute = false
    const code = path.charCodeAt(0)

    if (len === 1) {
      if (isWindowsPathSeparator(code)) {
        rootEnd = 1
        isAbsolute = true
      }
    } else if (isWindowsPathSeparator(code)) {
      isAbsolute = true

      if (isWindowsPathSeparator(path.charCodeAt(1))) {
        let j = 2
        let last = j
        while (j < len && !isWindowsPathSeparator(path.charCodeAt(j))) {
          j++
        }
        if (j < len && j !== last) {
          const firstPart = path.substring(last, j)
          last = j
          while (j < len && isWindowsPathSeparator(path.charCodeAt(j))) {
            j++
          }
          if (j < len && j !== last) {
            last = j
            while (j < len && !isWindowsPathSeparator(path.charCodeAt(j))) {
              j++
            }
            if (j === len || j !== last) {
              device = `\\\\${firstPart}\\${path.substring(last, j)}`
              rootEnd = j
            }
          }
        }
      } else {
        rootEnd = 1
      }
    } else if (isWindowsDeviceRoot(code) && path.charCodeAt(1) === CHAR_COLON) {
      device = path.substring(0, 2)
      rootEnd = 2
      if (len > 2 && isWindowsPathSeparator(path.charCodeAt(2))) {
        isAbsolute = true
        rootEnd = 3
      }
    }

    if (device.length > 0) {
      if (resolvedDevice.length > 0) {
        if (device.toLowerCase() !== resolvedDevice.toLowerCase()) {
          continue
        }
      } else {
        resolvedDevice = device
      }
    }

    if (resolvedAbsolute) {
      if (resolvedDevice.length > 0) {
        break
      }
    } else {
      resolvedTail = `${path.substring(rootEnd)}\\${resolvedTail}`
      resolvedAbsolute = isAbsolute
      if (isAbsolute && resolvedDevice.length > 0) {
        break
      }
    }
  }

  resolvedTail = normalizeString(resolvedTail, !resolvedAbsolute, '\\', isWindowsPathSeparator)

  return resolvedAbsolute
    ? `${resolvedDevice}\\${resolvedTail}`
    : `${resolvedDevice}${resolvedTail}` || '.'
}

exports.normalize = function normalize(path) {
  const len = path.length
  if (len === 0) return '.'
  let rootEnd = 0
  let device
  let isAbsolute = false
  const code = path.charCodeAt(0)

  if (len === 1) {
    return code === CHAR_FORWARD_SLASH ? '\\' : path
  }

  if (isWindowsPathSeparator(code)) {
    isAbsolute = true

    if (isWindowsPathSeparator(path.charCodeAt(1))) {
      let j = 2
      let last = j
      while (j < len && !isWindowsPathSeparator(path.charCodeAt(j))) {
        j++
      }
      if (j < len && j !== last) {
        const firstPart = path.substring(last, j)
        last = j
        while (j < len && isWindowsPathSeparator(path.charCodeAt(j))) {
          j++
        }
        if (j < len && j !== last) {
          last = j
          while (j < len && !isWindowsPathSeparator(path.charCodeAt(j))) {
            j++
          }
          if (j === len) {
            return `\\\\${firstPart}\\${path.substring(last)}\\`
          }
          if (j !== last) {
            device = `\\\\${firstPart}\\${path.substring(last, j)}`
            rootEnd = j
          }
        }
      }
    } else {
      rootEnd = 1
    }
  } else if (isWindowsDeviceRoot(code) && path.charCodeAt(1) === CHAR_COLON) {
    device = path.substring(0, 2)
    rootEnd = 2
    if (len > 2 && isWindowsPathSeparator(path.charCodeAt(2))) {
      isAbsolute = true
      rootEnd = 3
    }
  }

  let tail =
    rootEnd < len
      ? normalizeString(path.substring(rootEnd), !isAbsolute, '\\', isWindowsPathSeparator)
      : ''
  if (tail.length === 0 && !isAbsolute) {
    tail = '.'
  }
  if (tail.length > 0 && isWindowsPathSeparator(path.charCodeAt(len - 1))) {
    tail += '\\'
  }
  if (device === undefined) {
    return isAbsolute ? `\\${tail}` : tail
  }
  return isAbsolute ? `${device}\\${tail}` : `${device}${tail}`
}

exports.isAbsolute = function isAbsolute(path) {
  const len = path.length
  if (len === 0) return false

  const code = path.charCodeAt(0)

  return (
    isWindowsPathSeparator(code) ||
    (len > 2 &&
      isWindowsDeviceRoot(code) &&
      path.charCodeAt(1) === CHAR_COLON &&
      isWindowsPathSeparator(path.charCodeAt(2)))
  )
}

exports.join = function join(...args) {
  if (args.length === 0) return '.'

  let joined
  let firstPart
  for (let i = 0; i < args.length; ++i) {
    const arg = args[i]
    if (arg.length > 0) {
      if (joined === undefined) joined = firstPart = arg
      else joined += `\\${arg}`
    }
  }

  if (joined === undefined) return '.'

  let needsReplace = true
  let slashCount = 0
  if (isWindowsPathSeparator(firstPart.charCodeAt(0))) {
    ++slashCount
    const firstLen = firstPart.length
    if (firstLen > 1 && isWindowsPathSeparator(firstPart.charCodeAt(1))) {
      ++slashCount
      if (firstLen > 2) {
        if (isWindowsPathSeparator(firstPart.charCodeAt(2))) {
          ++slashCount
        } else {
          needsReplace = false
        }
      }
    }
  }
  if (needsReplace) {
    while (slashCount < joined.length && isWindowsPathSeparator(joined.charCodeAt(slashCount))) {
      slashCount++
    }

    if (slashCount >= 2) {
      joined = `\\${joined.substring(slashCount)}`
    }
  }

  return exports.normalize(joined)
}

exports.relative = function relative(from, to) {
  if (from === to) return ''

  const fromOrig = exports.resolve(from)
  const toOrig = exports.resolve(to)

  if (fromOrig === toOrig) return ''

  from = fromOrig.toLowerCase()
  to = toOrig.toLowerCase()

  if (from === to) return ''

  let fromStart = 0
  while (fromStart < from.length && from.charCodeAt(fromStart) === CHAR_BACKWARD_SLASH) {
    fromStart++
  }
  let fromEnd = from.length
  while (fromEnd - 1 > fromStart && from.charCodeAt(fromEnd - 1) === CHAR_BACKWARD_SLASH) {
    fromEnd--
  }
  const fromLen = fromEnd - fromStart

  let toStart = 0
  while (toStart < to.length && to.charCodeAt(toStart) === CHAR_BACKWARD_SLASH) {
    toStart++
  }
  let toEnd = to.length
  while (toEnd - 1 > toStart && to.charCodeAt(toEnd - 1) === CHAR_BACKWARD_SLASH) {
    toEnd--
  }
  const toLen = toEnd - toStart

  const length = fromLen < toLen ? fromLen : toLen
  let lastCommonSep = -1
  let i = 0
  for (; i < length; i++) {
    const fromCode = from.charCodeAt(fromStart + i)
    if (fromCode !== to.charCodeAt(toStart + i)) {
      break
    } else if (fromCode === CHAR_BACKWARD_SLASH) {
      lastCommonSep = i
    }
  }

  if (i !== length) {
    if (lastCommonSep === -1) return toOrig
  } else {
    if (toLen > length) {
      if (to.charCodeAt(toStart + i) === CHAR_BACKWARD_SLASH) {
        return toOrig.substring(toStart + i + 1)
      }
      if (i === 2) {
        return toOrig.substring(toStart + i)
      }
    }
    if (fromLen > length) {
      if (from.charCodeAt(fromStart + i) === CHAR_BACKWARD_SLASH) {
        lastCommonSep = i
      } else if (i === 2) {
        lastCommonSep = 3
      }
    }
    if (lastCommonSep === -1) lastCommonSep = 0
  }

  let out = ''
  for (i = fromStart + lastCommonSep + 1; i <= fromEnd; ++i) {
    if (i === fromEnd || from.charCodeAt(i) === CHAR_BACKWARD_SLASH) {
      out += out.length === 0 ? '..' : '\\..'
    }
  }

  toStart += lastCommonSep

  if (out.length > 0) {
    return `${out}${toOrig.substring(toStart, toEnd)}`
  }
  if (toOrig.charCodeAt(toStart) === CHAR_BACKWARD_SLASH) {
    ++toStart
  }
  return toOrig.substring(toStart, toEnd)
}

exports.toNamespacedPath = function toNamespacedPath(path) {
  if (path.length === 0) return path

  const resolvedPath = exports.resolve(path)

  if (resolvedPath.length <= 2) return path

  if (resolvedPath.charCodeAt(0) === CHAR_BACKWARD_SLASH) {
    if (resolvedPath.charCodeAt(1) === CHAR_BACKWARD_SLASH) {
      const code = resolvedPath.charCodeAt(2)
      if (code !== CHAR_QUESTION_MARK && code !== CHAR_DOT) {
        return `\\\\?\\UNC\\${resolvedPath.substring(2)}`
      }
    }
  } else if (
    isWindowsDeviceRoot(resolvedPath.charCodeAt(0)) &&
    resolvedPath.charCodeAt(1) === CHAR_COLON &&
    resolvedPath.charCodeAt(2) === CHAR_BACKWARD_SLASH
  ) {
    return `\\\\?\\${resolvedPath}`
  }

  return path
}

exports.dirname = function dirname(path) {
  const len = path.length
  if (len === 0) return '.'
  let rootEnd = -1
  let offset = 0
  const code = path.charCodeAt(0)

  if (len === 1) {
    return isWindowsPathSeparator(code) ? path : '.'
  }

  if (isWindowsPathSeparator(code)) {
    rootEnd = offset = 1

    if (isWindowsPathSeparator(path.charCodeAt(1))) {
      let j = 2
      let last = j
      while (j < len && !isWindowsPathSeparator(path.charCodeAt(j))) {
        j++
      }
      if (j < len && j !== last) {
        last = j
        while (j < len && isWindowsPathSeparator(path.charCodeAt(j))) {
          j++
        }
        if (j < len && j !== last) {
          last = j
          while (j < len && !isWindowsPathSeparator(path.charCodeAt(j))) {
            j++
          }
          if (j === len) {
            return path
          }
          if (j !== last) {
            rootEnd = offset = j + 1
          }
        }
      }
    }
  } else if (isWindowsDeviceRoot(code) && path.charCodeAt(1) === CHAR_COLON) {
    rootEnd = len > 2 && isWindowsPathSeparator(path.charCodeAt(2)) ? 3 : 2
    offset = rootEnd
  }

  let end = -1
  let matchedSlash = true
  for (let i = len - 1; i >= offset; --i) {
    if (isWindowsPathSeparator(path.charCodeAt(i))) {
      if (!matchedSlash) {
        end = i
        break
      }
    } else {
      matchedSlash = false
    }
  }

  if (end === -1) {
    if (rootEnd === -1) return '.'

    end = rootEnd
  }
  return path.substring(0, end)
}

exports.basename = function basename(path, suffix) {
  let start = 0
  let end = -1
  let matchedSlash = true

  if (
    path.length >= 2 &&
    isWindowsDeviceRoot(path.charCodeAt(0)) &&
    path.charCodeAt(1) === CHAR_COLON
  ) {
    start = 2
  }

  if (suffix !== undefined && suffix.length > 0 && suffix.length <= path.length) {
    if (suffix === path) return ''
    let extIdx = suffix.length - 1
    let firstNonSlashEnd = -1
    for (let i = path.length - 1; i >= start; --i) {
      const code = path.charCodeAt(i)
      if (isWindowsPathSeparator(code)) {
        if (!matchedSlash) {
          start = i + 1
          break
        }
      } else {
        if (firstNonSlashEnd === -1) {
          matchedSlash = false
          firstNonSlashEnd = i + 1
        }
        if (extIdx >= 0) {
          if (code === suffix.charCodeAt(extIdx)) {
            if (--extIdx === -1) {
              end = i
            }
          } else {
            extIdx = -1
            end = firstNonSlashEnd
          }
        }
      }
    }

    if (start === end) end = firstNonSlashEnd
    else if (end === -1) end = path.length
    return path.substring(start, end)
  }
  for (let i = path.length - 1; i >= start; --i) {
    if (isWindowsPathSeparator(path.charCodeAt(i))) {
      if (!matchedSlash) {
        start = i + 1
        break
      }
    } else if (end === -1) {
      matchedSlash = false
      end = i + 1
    }
  }

  if (end === -1) return ''
  return path.substring(start, end)
}

exports.extname = function extname(path) {
  let start = 0
  let startDot = -1
  let startPart = 0
  let end = -1
  let matchedSlash = true
  let preDotState = 0

  if (
    path.length >= 2 &&
    path.charCodeAt(1) === CHAR_COLON &&
    isWindowsDeviceRoot(path.charCodeAt(0))
  ) {
    start = startPart = 2
  }

  for (let i = path.length - 1; i >= start; --i) {
    const code = path.charCodeAt(i)
    if (isWindowsPathSeparator(code)) {
      if (!matchedSlash) {
        startPart = i + 1
        break
      }
      continue
    }
    if (end === -1) {
      matchedSlash = false
      end = i + 1
    }
    if (code === CHAR_DOT) {
      if (startDot === -1) startDot = i
      else if (preDotState !== 1) preDotState = 1
    } else if (startDot !== -1) {
      preDotState = -1
    }
  }

  if (
    startDot === -1 ||
    end === -1 ||
    preDotState === 0 ||
    (preDotState === 1 && startDot === end - 1 && startDot === startPart + 1)
  ) {
    return ''
  }
  return path.substring(startDot, end)
}
{
  "name": "bare-path",
  "version": "3.0.1",
  "description": "Path manipulation library for JavaScript",
  "exports": {
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    },
    "./package": "./package.json",
    "./posix": "./lib/posix.js",
    "./win32": "./lib/win32.js"
  },
  "files": [
    "index.js",
    "index.d.ts",
    "lib",
    "NOTICE"
  ],
  "scripts": {
    "format": "prettier --write . && lunte --fix",
    "lint": "prettier --check . && lunte",
    "test": "brittle-bare --coverage test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-path.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-path/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-path#readme",
  "dependencies": {
    "bare-os": "^3.0.1"
  },
  "devDependencies": {
    "brittle": "^3.3.2",
    "lunte": "^1.8.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^2.0.0"
  }
}
const b4a = require('b4a')
const stream = require('streamx')
const { ReadableStream, WritableStream } = require('./web')

const defaultEncoding = 'utf8'

module.exports = exports = stream.Stream

exports.pipeline = stream.pipeline

exports.isStream = stream.isStream
exports.isEnding = stream.isEnding
exports.isEnded = stream.isEnded
exports.isFinishing = stream.isFinishing
exports.isFinished = stream.isFinished
exports.isDisturbed = stream.isDisturbed

exports.isErrored = function isErrored(stream) {
  return exports.getStreamError(stream) !== null
}

exports.isReadable = function isReadable(stream) {
  return stream.readable && !stream.destroying && !exports.isEnded(stream)
}

exports.isWritable = function isWritable(stream) {
  return stream.writable && !stream.destroying && !exports.isFinishing(stream)
}

exports.getStreamError = stream.getStreamError

exports.addAbortSignal = function addAbortSignal(signal, stream) {
  function onAbort() {
    stream.destroy(signal.reason)
  }

  if (signal.aborted) onAbort()
  else signal.addEventListener('abort', onAbort)

  return stream
}

exports.Stream = exports

exports.Readable = class Readable extends stream.Readable {
  constructor(opts = {}) {
    super({
      ...opts,
      byteLength: null,
      byteLengthReadable: null,
      map: null,
      mapReadable: null
    })

    if (this._construct) this._open = this._construct

    if (this._read !== stream.Readable.prototype._read) {
      this._read = read.bind(this, this._read)
    }

    if (this._destroy !== stream.Stream.prototype._destroy) {
      this._destroy = destroy.bind(this, this._destroy)
    }
  }

  get closed() {
    return !exports.isReadable(this)
  }

  get errored() {
    return stream.getStreamError(this)
  }

  push(chunk, encoding) {
    if (typeof chunk === 'string') {
      chunk = b4a.from(chunk, encoding || defaultEncoding)
    }

    return super.push(chunk)
  }

  unshift(chunk, encoding) {
    if (typeof chunk === 'string') {
      chunk = b4a.from(chunk, encoding || defaultEncoding)
    }

    super.unshift(chunk)
  }

  static fromWeb(readableStream, opts = {}) {
    const stream = readableStream._stream

    if (opts.encoding) stream.setEncoding(opts.encoding)
    if (opts.signal) exports.addAbortSignal(opts.signal, stream)

    return stream
  }

  static toWeb(readable, opts = {}) {
    return new ReadableStream(readable, opts.strategy)
  }

  async [Symbol.asyncDispose]() {
    if (!this.destroyed) this.destroy()

    await new Promise((resolve) => exports.finished(this, resolve))
  }
}

exports.Writable = class Writable extends stream.Writable {
  constructor(opts = {}) {
    super({
      ...opts,
      byteLength: null,
      byteLengthWritable,
      map: null,
      mapWritable: null
    })

    if (this._construct) this._open = this._construct

    if (this._write !== stream.Writable.prototype._write) {
      this._write = write.bind(this, this._write)
    }

    if (this._destroy !== stream.Stream.prototype._destroy) {
      this._destroy = destroy.bind(this, this._destroy)
    }
  }

  get closed() {
    return !exports.isWritable(this)
  }

  get errored() {
    return stream.getStreamError(this)
  }

  write(chunk, encoding, cb) {
    if (typeof encoding === 'function') {
      cb = encoding
      encoding = null
    }

    if (typeof chunk === 'string') {
      encoding = encoding || defaultEncoding
      chunk = b4a.from(chunk, encoding)
    } else {
      encoding = 'buffer'
    }

    const result = super.write({ chunk, encoding })

    if (cb) stream.Writable.drained(this).then(() => cb(null), cb)

    return result
  }

  end(chunk, encoding, cb) {
    if (typeof chunk === 'function') {
      cb = chunk
      chunk = null
    } else if (typeof encoding === 'function') {
      cb = encoding
      encoding = null
    }

    if (typeof chunk === 'string') {
      encoding = encoding || defaultEncoding
      chunk = b4a.from(chunk, encoding || defaultEncoding)
    } else {
      encoding = 'buffer'
    }

    const result =
      chunk !== undefined && chunk !== null ? super.end({ chunk, encoding }) : super.end()

    if (cb) this.once('finish', () => cb(null))

    return result
  }

  static fromWeb(writableStream, opts = {}) {
    const stream = writableStream._stream

    if (opts.signal) exports.addAbortSignal(opts.signal, stream)

    return stream
  }

  static toWeb(writable) {
    return new WritableStream(writable)
  }

  async [Symbol.asyncDispose]() {
    if (!this.destroyed) this.destroy()

    await new Promise((resolve) => exports.finished(this, resolve))
  }
}

exports.Duplex = class Duplex extends stream.Duplex {
  constructor(opts = {}) {
    super({
      ...opts,
      byteLength: null,
      byteLengthReadable: null,
      byteLengthWritable,
      map: null,
      mapReadable: null,
      mapWritable: null
    })

    if (this._construct) this._open = this._construct

    if (this._read !== stream.Readable.prototype._read) {
      this._read = read.bind(this, this._read)
    }

    if (this._write !== stream.Duplex.prototype._write) {
      this._write = write.bind(this, this._write)
    }

    if (this._destroy !== stream.Stream.prototype._destroy) {
      this._destroy = destroy.bind(this, this._destroy)
    }
  }

  push(chunk, encoding) {
    if (typeof chunk === 'string') {
      chunk = b4a.from(chunk, encoding || defaultEncoding)
    }

    return super.push(chunk)
  }

  unshift(chunk, encoding) {
    if (typeof chunk === 'string') {
      chunk = b4a.from(chunk, encoding || defaultEncoding)
    }

    super.unshift(chunk)
  }

  write(chunk, encoding, cb) {
    if (typeof encoding === 'function') {
      cb = encoding
      encoding = null
    }

    if (typeof chunk === 'string') {
      encoding = encoding || defaultEncoding
      chunk = b4a.from(chunk, encoding)
    } else {
      encoding = 'buffer'
    }

    const result = super.write({ chunk, encoding })

    if (cb) stream.Writable.drained(this).then(() => cb(null), cb)

    return result
  }

  end(chunk, encoding, cb) {
    if (typeof chunk === 'function') {
      cb = chunk
      chunk = null
    } else if (typeof encoding === 'function') {
      cb = encoding
      encoding = null
    }

    if (typeof chunk === 'string') {
      encoding = encoding || defaultEncoding
      chunk = b4a.from(chunk, encoding)
    } else {
      encoding = 'buffer'
    }

    const result =
      chunk !== undefined && chunk !== null ? super.end({ chunk, encoding }) : super.end()

    if (cb) this.once('finish', () => cb(null))

    return result
  }

  static fromWeb({ readable: readableStream, writable: writableStream }, opts) {
    const readable = exports.Readable.fromWeb(readableStream, opts)
    const writable = exports.Writable.fromWeb(writableStream, opts)

    const duplex = new exports.Duplex({
      write(data, encoding, cb) {
        writable.write(data, encoding, cb)
      }
    })

    readable
      .on('data', (data) => duplex.push(data))
      .on('end', () => duplex.push(null))
      .on('error', (err) => duplex.destroy(err))

    writable.on('finish', () => duplex.end()).on('error', (err) => duplex.destroy(err))

    return duplex
  }

  static toWeb(duplex) {
    const readableStream = exports.Readable.toWeb(duplex)
    const writableStream = exports.Writable.toWeb(duplex)

    return { readable: readableStream, writable: writableStream }
  }
}

class DuplexSide extends exports.Duplex {
  constructor(opts) {
    super(opts)

    this._otherSide = null
    this._cb = null
  }

  _read() {
    const cb = this._cb
    if (!cb) return

    this._cb = null
    cb()
  }

  _write(chunk, encoding, cb) {
    this._otherSide.push(chunk, encoding)
    this._otherSide._cb = cb
  }

  _final(cb) {
    this._otherSide.on('end', cb)
    this._otherSide.push(null)
  }
}

exports.duplexPair = function duplexPair(opts) {
  const sideA = new DuplexSide(opts)
  const sideB = new DuplexSide(opts)

  sideA._otherSide = sideB
  sideB._otherSide = sideA

  return [sideA, sideB]
}

exports.Transform = class Transform extends stream.Transform {
  constructor(opts = {}) {
    super({
      ...opts,
      byteLength: null,
      byteLengthReadable: null,
      byteLengthWritable,
      map: null,
      mapReadable: null,
      mapWritable: null
    })

    if (this._transform !== stream.Transform.prototype._transform) {
      this._transform = transform.bind(this, this._transform)
    } else {
      this._transform = passthrough
    }
  }

  push(chunk, encoding) {
    if (typeof chunk === 'string') {
      chunk = b4a.from(chunk, encoding || defaultEncoding)
    }

    return super.push(chunk)
  }

  unshift(chunk, encoding) {
    if (typeof chunk === 'string') {
      chunk = b4a.from(chunk, encoding || defaultEncoding)
    }

    super.unshift(chunk)
  }

  write(chunk, encoding, cb) {
    if (typeof encoding === 'function') {
      cb = encoding
      encoding = null
    }

    if (typeof chunk === 'string') {
      encoding = encoding || defaultEncoding
      chunk = b4a.from(chunk, encoding)
    } else {
      encoding = 'buffer'
    }

    const result = super.write({ chunk, encoding })

    if (cb) stream.Writable.drained(this).then(() => cb(null), cb)

    return result
  }

  end(chunk, encoding, cb) {
    if (typeof chunk === 'function') {
      cb = chunk
      chunk = null
    } else if (typeof encoding === 'function') {
      cb = encoding
      encoding = null
    }

    if (typeof chunk === 'string') {
      encoding = encoding || defaultEncoding
      chunk = b4a.from(chunk, encoding)
    } else {
      encoding = 'buffer'
    }

    const result =
      chunk !== undefined && chunk !== null ? super.end({ chunk, encoding }) : super.end()

    if (cb) this.once('finish', () => cb(null))

    return result
  }
}

exports.PassThrough = class PassThrough extends exports.Transform {}

exports.finished = function finished(stream, opts, cb) {
  if (typeof opts === 'function') {
    cb = opts
    opts = {}
  }

  if (!opts) opts = {}

  const { cleanup = false } = opts

  const done = () => {
    cb(exports.getStreamError(stream, { all: true }))

    if (cleanup) detach()
  }

  const detach = () => {
    stream.off('close', done)
    stream.off('error', noop)
  }

  if (stream.destroyed) {
    done()
  } else {
    stream.on('close', done)
    stream.on('error', noop)
  }

  return detach
}

function read(read, cb) {
  read.call(this, 65536)

  cb(null)
}

function write(write, data, cb) {
  write.call(this, data.chunk, data.encoding, cb)
}

function transform(transform, data, cb) {
  transform.call(this, data.chunk, data.encoding, cb)
}

function destroy(destroy, cb) {
  destroy.call(this, exports.getStreamError(this), cb)
}

function passthrough(data, cb) {
  cb(null, data.chunk)
}

function byteLengthWritable(data) {
  return data.chunk.byteLength
}

function noop() {}
{
  "name": "bare-stream",
  "version": "2.13.3",
  "description": "Streaming data for JavaScript",
  "exports": {
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    },
    "./package": "./package.json",
    "./promises": "./promises.js",
    "./web": {
      "types": "./web.d.ts",
      "default": "./web.js"
    },
    "./global": {
      "types": "./global.d.ts",
      "default": "./global.js"
    }
  },
  "files": [
    "index.js",
    "index.d.ts",
    "promises.js",
    "web.js",
    "web.d.ts",
    "global.js",
    "global.d.ts"
  ],
  "scripts": {
    "format": "prettier --write . && lunte --fix",
    "lint": "prettier --check . && lunte",
    "test": "brittle-bare --coverage test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-stream.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-stream/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-stream#readme",
  "dependencies": {
    "b4a": "^1.8.1",
    "streamx": "^2.25.0",
    "teex": "^1.0.1"
  },
  "devDependencies": {
    "bare-abort-controller": "^1.1.0",
    "bare-buffer": "^3.0.0",
    "bare-events": "^2.5.4",
    "brittle": "^3.5.2",
    "lunte": "^1.6.0",
    "prettier": "^3.3.3",
    "prettier-config-holepunch": "^2.0.0"
  },
  "peerDependencies": {
    "bare-abort-controller": "*",
    "bare-buffer": "*",
    "bare-events": "*"
  },
  "peerDependenciesMeta": {
    "bare-abort-controller": {
      "optional": true
    },
    "bare-buffer": {
      "optional": true
    },
    "bare-events": {
      "optional": true
    }
  }
}
const { Readable, Writable, Transform, getStreamError, isStreamx, isDisturbed } = require('streamx')
const tee = require('teex')

const readableKind = Symbol.for('bare.stream.readable.kind')
const writableKind = Symbol.for('bare.stream.writable.kind')
const transformKind = Symbol.for('bare.stream.transform.kind')

// https://streams.spec.whatwg.org/#readablestreamdefaultreader
exports.ReadableStreamDefaultReader = class ReadableStreamDefaultReader {
  constructor(stream) {
    this._stream = stream
    this._stream._stream.once('close', onclose).once('error', onerror)

    const closed = Promise.withResolvers()

    // Avoid unhandled exceptions
    closed.promise.catch(noop)

    this._closed = closed

    function onclose() {
      closed.resolve()
    }

    function onerror(err) {
      closed.reject(err)
    }
  }

  get closed() {
    return this._closed.promise
  }

  read() {
    const stream = this._stream._stream

    return new Promise((resolve, reject) => {
      const err = getStreamError(stream)

      if (err) return reject(err)

      if (stream.destroyed) {
        return resolve({ value: undefined, done: true })
      }

      const value = stream.read()

      if (value !== null) {
        return resolve({ value, done: false })
      }

      stream.once('readable', onreadable).once('close', onclose).once('error', onerror)

      function onreadable() {
        const value = stream.read()

        ondone(null, value === null ? { value: undefined, done: true } : { value, done: false })
      }

      function onclose() {
        ondone(null, { value: undefined, done: true })
      }

      function onerror(err) {
        ondone(err, null)
      }

      function ondone(err, value) {
        stream.off('readable', onreadable).off('close', onclose).off('error', onerror)

        if (err) reject(err)
        else resolve(value)
      }
    })
  }

  releaseLock() {
    this._closed.reject(new TypeError('Reader was released'))
    this._stream._releaseLock()
    this._stream = null
  }

  cancel(reason = new TypeError('Stream was cancelled')) {
    const stream = this._stream._stream

    if (stream.destroyed) return Promise.resolve()

    return new Promise((resolve) =>
      stream.once('close', resolve).once('error', noop).destroy(reason)
    )
  }
}

// https://streams.spec.whatwg.org/#readablestreamdefaultcontroller
exports.ReadableStreamDefaultController = class ReadableStreamDefaultController {
  constructor(stream) {
    this._stream = stream
  }

  get desiredSize() {
    const stream = this._stream._stream

    return stream._readableState.highWaterMark - stream._readableState.buffered
  }

  enqueue(data) {
    this._stream._stream.push(data)
  }

  close() {
    this._stream._stream.push(null)
  }

  error(err) {
    this._stream._stream.destroy(err)
  }
}

// https://streams.spec.whatwg.org/#readablestream
class ReadableStream {
  static get [readableKind]() {
    return 0 // Compatibility version
  }

  static from(iterable) {
    return new ReadableStream(Readable.from(iterable))
  }

  constructor(underlyingSource = {}, queuingStrategy) {
    if (isStreamx(underlyingSource)) {
      this._stream = underlyingSource
    } else {
      if (queuingStrategy === undefined) {
        queuingStrategy = new exports.CountQueuingStrategy()
      }

      const { start, pull, cancel } = underlyingSource
      const { highWaterMark = 1, size = defaultSize } = queuingStrategy

      this._stream = new Readable({ highWaterMark, byteLength: size })

      const controller = new exports.ReadableStreamDefaultController(this)

      try {
        let starting = Promise.resolve()

        if (start) starting = forwardError(start.call(this, controller), controller)

        if (pull) {
          this._stream._read = this._read.bind(this, starting, pull.bind(this, controller))
        }

        if (cancel) {
          this._stream.once('error', cancel.bind(this))
        }
      } catch (err) {
        controller.error(err)
      }
    }

    this._reader = null
  }

  get [readableKind]() {
    return ReadableStream[readableKind]
  }

  get locked() {
    return this._reader !== null
  }

  getReader() {
    if (this.locked) throw new TypeError('Stream is locked')

    this._reader = new exports.ReadableStreamDefaultReader(this)

    return this._reader
  }

  cancel(reason = new TypeError('Stream was cancelled')) {
    const stream = this._stream

    if (stream.destroyed) return Promise.resolve()

    if (this.locked) return Promise.reject(new TypeError('Stream is locked'))

    return new Promise((resolve) =>
      stream.once('close', resolve).once('error', noop).destroy(reason)
    )
  }

  tee() {
    const [a, b] = tee(this._stream)

    return [new ReadableStream(a), new ReadableStream(b)]
  }

  pipeTo(destination) {
    return new Promise((resolve, reject) =>
      this._stream.pipe(destination._stream, (err) => {
        err ? reject(err) : resolve()
      })
    )
  }

  [Symbol.asyncIterator]() {
    return this._stream[Symbol.asyncIterator]()
  }

  _releaseLock() {
    this._reader = null
  }

  async _read(starting, pull, cb) {
    await starting

    let err = null
    try {
      await pull()
    } catch (e) {
      err = e
    }
    cb(err)
  }
}

function defaultSize() {
  return 1
}

exports.ReadableStream = ReadableStream

// https://streams.spec.whatwg.org/#countqueuingstrategy
exports.CountQueuingStrategy = class CountQueuingStrategy {
  constructor(opts = {}) {
    const { highWaterMark = 1 } = opts

    this.highWaterMark = highWaterMark
  }

  size(chunk) {
    return 1
  }
}

// https://streams.spec.whatwg.org/#bytelengthqueuingstrategy
exports.ByteLengthQueuingStrategy = class ByteLengthQueuingStrategy {
  constructor(opts = {}) {
    const { highWaterMark = 16384 } = opts

    this.highWaterMark = highWaterMark
  }

  size(chunk) {
    return chunk.byteLength
  }
}

exports.isReadableStream = function isReadableStream(value) {
  if (value instanceof ReadableStream) return true

  return (
    typeof value === 'object' &&
    value !== null &&
    value[readableKind] === ReadableStream[readableKind]
  )
}

// https://streams.spec.whatwg.org/#readablestream-errored
exports.isReadableStreamErrored = function isReadableStreamErrored(stream) {
  return getStreamError(stream._stream) !== null
}

// https://streams.spec.whatwg.org/#is-readable-stream-disturbed
exports.isReadableStreamDisturbed = function isReadableStreamDisturbed(stream) {
  return isDisturbed(stream._stream)
}

// https://streams.spec.whatwg.org/#writablestreamdefaultwriter
exports.WritableStreamDefaultWriter = class WritableStreamDefaultWriter {
  constructor(stream) {
    this._stream = stream
    this._stream._stream.once('close', onclose).once('error', onerror)

    const closed = Promise.withResolvers()

    // Avoid unhandled exceptions
    closed.promise.catch(noop)

    this._closed = closed

    function onclose() {
      closed.resolve()
    }

    function onerror(err) {
      closed.reject(err)
    }
  }

  get desiredSize() {
    const stream = this._stream._stream

    return stream._writableState.highWaterMark - stream._writableState.buffered
  }

  get closed() {
    return this._closed.promise
  }

  get ready() {
    const stream = this._stream._stream

    if (getStreamError(stream)) return Promise.reject()

    return Writable.drained(stream).then()
  }

  async write(chunk) {
    const stream = this._stream._stream

    let err = getStreamError(stream)
    if (err) return Promise.reject(err)

    stream.write(chunk)

    await Writable.drained(stream)

    err = getStreamError(stream)
    if (err) return Promise.reject(err)
  }

  releaseLock() {
    this._closed.reject(new TypeError('Writer was released'))
    this._stream._releaseLock()
    this._stream = null
  }

  close() {
    const stream = this._stream._stream

    if (stream.destroyed) return Promise.resolve()

    return new Promise((resolve) => stream.once('close', resolve).end())
  }

  abort(reason = new TypeError('Stream was aborted')) {
    const stream = this._stream._stream

    if (stream.destroyed) return Promise.resolve()

    return new Promise((resolve) => stream.once('close', resolve).destroy(reason))
  }
}

// https://streams.spec.whatwg.org/#writablestreamdefaultcontroller
exports.WritableStreamDefaultController = class WritableStreamDefaultController {
  constructor(stream) {
    this._stream = stream
  }

  error(err) {
    this._stream._stream.destroy(err)
  }
}

// https://streams.spec.whatwg.org/#writablestream
class WritableStream {
  static get [writableKind]() {
    return 0 // Compatibility version
  }

  constructor(underlyingSink = {}, queuingStrategy = {}) {
    if (isStreamx(underlyingSink)) {
      this._stream = underlyingSink
    } else {
      if (queuingStrategy === undefined) {
        queuingStrategy = new exports.CountQueuingStrategy()
      }

      const { start, write, close, abort } = underlyingSink
      const { highWaterMark = 1, size = defaultSize } = queuingStrategy

      this._stream = new Writable({ highWaterMark, byteLength: size })

      const controller = new exports.WritableStreamDefaultController(this)

      this._controller = controller

      try {
        let starting = Promise.resolve()

        if (start) starting = forwardError(start.call(this, controller), controller)

        if (write) {
          this._stream._write = this._write.bind(this, starting, write.bind(this))
        }

        if (close) {
          this._stream._destroy = this._destroy.bind(this, close.call(this))
        }

        if (abort) {
          this._stream.once('error', abort.bind(this))
        }
      } catch (err) {
        controller.error(err)
      }
    }

    this._writer = null
  }

  get [writableKind]() {
    return WritableStream[writableKind]
  }

  get locked() {
    return this._writer !== null
  }

  getWriter() {
    if (this.locked) throw new TypeError('Stream is locked')

    this._writer = new exports.WritableStreamDefaultWriter(this)

    return this._writer
  }

  abort(reason = new TypeError('Stream was aborted')) {
    if (this._stream.destroyed) return Promise.resolve()

    if (this.locked) return Promise.reject(new TypeError('Stream is locked'))

    return new Promise((resolve) => this._stream.once('close', resolve).destroy(reason))
  }

  close() {
    if (this._stream.destroyed) return Promise.resolve()

    if (this.locked) return Promise.reject(new TypeError('Stream is locked'))

    return new Promise((resolve) => this._stream.once('close', resolve).end())
  }

  _releaseLock() {
    this._writer = null
  }

  async _write(starting, write, data, cb) {
    await starting

    let err = null
    try {
      await write(data, this._controller)
    } catch (e) {
      err = e
    }
    cb(err)
  }

  async _destroy(closing, cb) {
    let err = null
    try {
      await closing
    } catch (e) {
      err = e
    }
    cb(err)
  }
}

exports.WritableStream = WritableStream

exports.isWritableStream = function isWritableStream(value) {
  if (value instanceof WritableStream) return true

  return (
    typeof value === 'object' &&
    value !== null &&
    value[writableKind] === WritableStream[writableKind]
  )
}

// https://streams.spec.whatwg.org/#transformstreamdefaultcontroller
exports.TransformStreamDefaultController = class TransformStreamDefaultController {
  constructor(stream) {
    this._stream = stream
  }

  get desiredSize() {
    const stream = this._stream._stream

    return stream._readableState.highWaterMark - stream._readableState.buffered
  }

  enqueue(data) {
    this._stream._stream.push(data)
  }

  error(err) {
    this._stream._stream.destroy(err)
  }

  terminate() {
    const stream = this._stream._stream

    stream.push(null)
    stream.destroy(new TypeError('Stream has been terminated'))
  }
}

// https://streams.spec.whatwg.org/#transformstream
class TransformStream {
  static get [transformKind]() {
    return 0 // Compatibility version
  }

  constructor(transformer = {}, writableStrategy = {}, readableStrategy = {}) {
    if (isStreamx(transformer)) {
      this._stream = transformer
    } else {
      const { start, transform, flush } = transformer

      this._stream = new Transform({ ...writableStrategy, ...readableStrategy })

      const controller = new exports.TransformStreamDefaultController(this)

      this._controller = controller

      try {
        let starting = Promise.resolve()

        if (start) starting = forwardError(start.call(this, controller), controller)

        if (transform) {
          this._stream._transform = this._transform.bind(this, starting, transform.bind(this))
        }

        if (flush) {
          this._stream._flush = this._flush.bind(this, flush.call(this, this._controller))
        }
      } catch (err) {
        controller.error(err)
      }
    }

    this._writable = new WritableStream(this._stream)
    this._readable = new ReadableStream(this._stream)
  }

  get [transformKind]() {
    return TransformStream[transformKind]
  }

  get writable() {
    return this._writable
  }

  get readable() {
    return this._readable
  }

  async _transform(starting, transform, data, cb) {
    await starting

    let err = null
    try {
      await transform(data, this._controller)
    } catch (e) {
      err = e
    }
    cb(err)
  }

  async _flush(flush, cb) {
    let err = null
    try {
      await flush
    } catch (e) {
      err = e
    }
    cb(err)
  }
}

exports.TransformStream = TransformStream

exports.isTransformStream = function isTransformStream(value) {
  if (value instanceof TransformStream) return true

  return (
    typeof value === 'object' &&
    value !== null &&
    value[transformKind] === TransformStream[transformKind]
  )
}

async function forwardError(promise, controller) {
  try {
    await promise
  } catch (err) {
    controller.error(err)
  }
}

function noop() {}
module.exports = require.addon()
const binding = require('./binding')

const t = binding.constants

class Type {
  constructor(type) {
    this._type = type
  }

  isUndefined() {
    return this._type === t.UNDEFINED
  }

  isNull() {
    return this._type === t.NULL
  }

  isBoolean() {
    return this._type === t.BOOLEAN
  }

  isNumber() {
    return (this._type & 0xff) === t.NUMBER
  }

  isInt32() {
    return (this._type & (0xff | t.INT32)) === (t.NUMBER | t.INT32)
  }

  isUint32() {
    return (this._type & (0xff | t.UINT32)) === (t.NUMBER | t.UINT32)
  }

  isString() {
    return this._type === t.STRING
  }

  isSymbol() {
    return this._type === t.SYMBOL
  }

  isObject() {
    return (this._type & 0xff) === t.OBJECT
  }

  isArray() {
    return this._type === (t.OBJECT | t.ARRAY)
  }

  isArguments() {
    return this._type === (t.OBJECT | t.ARGUMENTS)
  }

  isDate() {
    return this._type === (t.OBJECT | t.DATE)
  }

  isRegExp() {
    return this._type === (t.OBJECT | t.REGEXP)
  }

  isError() {
    return this._type === (t.OBJECT | t.ERROR)
  }

  isPromise() {
    return this._type === (t.OBJECT | t.PROMISE)
  }

  isProxy() {
    return this._type === (t.OBJECT | t.PROXY)
  }

  isGenerator() {
    return this._type === (t.OBJECT | t.GENERATOR)
  }

  isMap() {
    return this._type === (t.OBJECT | t.MAP)
  }

  isSet() {
    return this._type === (t.OBJECT | t.SET)
  }

  isWeakMap() {
    return this._type === (t.OBJECT | t.WEAK_MAP)
  }

  isWeakSet() {
    return this._type === (t.OBJECT | t.WEAK_SET)
  }

  isWeakRef() {
    return this._type === (t.OBJECT | t.WEAK_REF)
  }

  isArrayBuffer() {
    return this._type === (t.OBJECT | t.ARRAYBUFFER)
  }

  isSharedArrayBuffer() {
    return this._type === (t.OBJECT | t.SHAREDARRAYBUFFER)
  }

  isTypedArray() {
    return (this._type & 0xffff) === (t.OBJECT | t.TYPEDARRAY)
  }

  isInt8Array() {
    return this._type === (t.OBJECT | t.TYPEDARRAY | t.INT8ARRAY)
  }

  isUint8Array() {
    return this._type === (t.OBJECT | t.TYPEDARRAY | t.UINT8ARRAY)
  }

  isUint8ClampedArray() {
    return this._type === (t.OBJECT | t.TYPEDARRAY | t.UINT8CLAMPEDARRAY)
  }

  isInt16Array() {
    return this._type === (t.OBJECT | t.TYPEDARRAY | t.INT16ARRAY)
  }

  isUint16Array() {
    return this._type === (t.OBJECT | t.TYPEDARRAY | t.UINT16ARRAY)
  }

  isInt32Array() {
    return this._type === (t.OBJECT | t.TYPEDARRAY | t.INT32ARRAY)
  }

  isUint32Array() {
    return this._type === (t.OBJECT | t.TYPEDARRAY | t.UINT32ARRAY)
  }

  isFloat16Array() {
    return this._type === (t.OBJECT | t.TYPEDARRAY | t.FLOAT16ARRAY)
  }

  isFloat32Array() {
    return this._type === (t.OBJECT | t.TYPEDARRAY | t.FLOAT32ARRAY)
  }

  isFloat64Array() {
    return this._type === (t.OBJECT | t.TYPEDARRAY | t.FLOAT64ARRAY)
  }

  isBigInt64Array() {
    return this._type === (t.OBJECT | t.TYPEDARRAY | t.BIGINT64ARRAY)
  }

  isBigUint64Array() {
    return this._type === (t.OBJECT | t.TYPEDARRAY | t.BIGUINT64ARRAY)
  }

  isDataView() {
    return this._type === (t.OBJECT | t.DATAVIEW)
  }

  isModuleNamespace() {
    return this._type === (t.OBJECT | t.MODULE_NAMESPACE)
  }

  isFunction() {
    return (this._type & 0xff) === t.FUNCTION
  }

  isAsyncFunction() {
    return (
      (this._type & (0xff | t.ASYNC_FUNCTION)) ===
      (t.FUNCTION | t.ASYNC_FUNCTION)
    )
  }

  isGeneratorFunction() {
    return (
      (this._type & (0xff | t.GENERATOR_FUNCTION)) ===
      (t.FUNCTION | t.GENERATOR_FUNCTION)
    )
  }

  isExternal() {
    return this._type === t.EXTERNAL
  }

  isBigInt() {
    return this._type === t.BIGINT
  }
}

module.exports = exports = function type(value) {
  switch (typeof value) {
    case 'undefined':
      return new Type(t.UNDEFINED)
    case 'boolean':
      return new Type(t.BOOLEAN)
    case 'number':
      return new Type(
        Number.isSafeInteger(value) ? binding.type(value) : t.NUMBER
      )
    case 'string':
      return new Type(t.STRING)
    case 'symbol':
      return new Type(t.SYMBOL)
    case 'object':
      return new Type(value === null ? t.NULL : binding.type(value))
    case 'function':
      return new Type(binding.type(value))
    case 'bigint':
      return new Type(t.BIGINT)
  }
}

exports.createTag = function createTag(...components) {
  const tag = new Uint32Array(4)
  for (let i = 0; i < 4; i++) tag[i] = components[i] || 0
  return tag
}

exports.addTag = function addTag(object, tag) {
  binding.addTag(object, tag)
}

exports.checkTag = function checkTag(object, tag) {
  return binding.checkTag(object, tag)
}
{
  "name": "bare-type",
  "version": "1.1.0",
  "description": "Cross-realm type predicates for Bare",
  "exports": {
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    },
    "./package": "./package.json"
  },
  "files": [
    "index.js",
    "index.d.ts",
    "binding.c",
    "binding.js",
    "CMakeLists.txt",
    "prebuilds"
  ],
  "addon": true,
  "scripts": {
    "test": "prettier . --check && bare test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-type.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-type/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-type#readme",
  "engines": {
    "bare": ">=1.2.0"
  },
  "devDependencies": {
    "brittle": "^3.4.0",
    "cmake-bare": "^1.1.6",
    "prettier": "^3.4.1",
    "prettier-config-standard": "^7.0.0"
  }
}
module.exports = require.addon()
const path = require('bare-path')
const binding = require('./binding')
const errors = require('./lib/errors')
const URLSearchParams = require('./lib/url-search-params')

const kind = Symbol.for('bare.url.kind')

const isWindows = Bare.platform === 'win32'

class URL {
  static get [kind]() {
    return 0 // Compatibility version
  }

  constructor(input, base, opts = {}) {
    if (arguments.length === 0) throw errors.INVALID_URL()

    input = String(input)

    if (base !== undefined) base = String(base)

    this._components = new Uint32Array(8)

    this._parse(input, base, opts.throw !== false)

    if (this._href) this._params = new URLSearchParams(this.search, this)
  }

  get [kind]() {
    return URL[kind]
  }

  // https://url.spec.whatwg.org/#dom-url-href

  get href() {
    return this._href
  }

  set href(value) {
    this._update(value)

    this._params._parse(this.search)
  }

  // https://url.spec.whatwg.org/#dom-url-protocol

  get protocol() {
    return this._slice(0, this._components[0]) + ':'
  }

  set protocol(value) {
    this._update(this._replace(value.replace(/:+$/, ''), 0, this._components[0]))
  }

  // https://url.spec.whatwg.org/#dom-url-username

  get username() {
    return this._slice(this._components[0] + 3 /* :// */, this._components[1])
  }

  set username(value) {
    if (cannotHaveCredentialsOrPort(this)) {
      return
    }

    if (this.username === '') value += '@'

    this._update(this._replace(value, this._components[0] + 3 /* :// */, this._components[1]))
  }

  // https://url.spec.whatwg.org/#dom-url-password

  get password() {
    return this._href.slice(this._components[1] + 1 /* : */, this._components[2] - 1 /* @ */)
  }

  set password(value) {
    if (cannotHaveCredentialsOrPort(this)) {
      return
    }

    let start = this._components[1] + 1 /* : */
    let end = this._components[2] - 1 /* @ */

    if (this.password === '') {
      value = ':' + value
      start--
    }

    if (this.username === '') {
      value += '@'
      end++
    }

    this._update(this._replace(value, start, end))
  }

  // https://url.spec.whatwg.org/#dom-url-host

  get host() {
    return this._slice(this._components[2], this._components[5])
  }

  set host(value) {
    if (hasOpaquePath(this)) {
      return
    }

    this._update(
      this._replace(value, this._components[2], this._components[value.includes(':') ? 5 : 3])
    )
  }

  // https://url.spec.whatwg.org/#dom-url-hostname

  get hostname() {
    return this._slice(this._components[2], this._components[3])
  }

  set hostname(value) {
    if (hasOpaquePath(this)) {
      return
    }

    this._update(this._replace(value, this._components[2], this._components[3]))
  }

  // https://url.spec.whatwg.org/#dom-url-port

  get port() {
    return this._slice(this._components[3] + 1 /* : */, this._components[5])
  }

  set port(value) {
    if (cannotHaveCredentialsOrPort(this)) {
      return
    }

    let start = this._components[3] + 1 /* : */

    if (this.port === '') {
      value = ':' + value
      start--
    }

    this._update(this._replace(value, start, this._components[5]))
  }

  // https://url.spec.whatwg.org/#dom-url-pathname

  get pathname() {
    return this._slice(this._components[5], this._components[6] - 1 /* ? */)
  }

  set pathname(value) {
    if (hasOpaquePath(this)) {
      return
    }

    if (value[0] !== '/' && value[0] !== '\\') {
      value = '/' + value
    }

    this._update(this._replace(value, this._components[5], this._components[6] - 1 /* ? */))
  }

  // https://url.spec.whatwg.org/#dom-url-search

  get search() {
    return this._slice(this._components[6] - 1 /* ? */, this._components[7] - 1 /* # */)
  }

  set search(value) {
    if (value && value[0] !== '?') value = '?' + value

    this._update(
      this._replace(value, this._components[6] - 1 /* ? */, this._components[7] - 1 /* # */)
    )

    this._params._parse(this.search)
  }

  // https://url.spec.whatwg.org/#dom-url-searchparams

  get searchParams() {
    return this._params
  }

  // https://url.spec.whatwg.org/#dom-url-hash

  get hash() {
    return this._slice(this._components[7] - 1 /* # */)
  }

  set hash(value) {
    if (value && value[0] !== '#') value = '#' + value

    this._update(this._replace(value, this._components[7] - 1 /* # */))
  }

  toString() {
    return this._href
  }

  toJSON() {
    return this._href
  }

  [Symbol.for('bare.inspect')]() {
    return {
      __proto__: { constructor: URL },

      href: this.href,
      protocol: this.protocol,
      username: this.username,
      password: this.password,
      host: this.host,
      hostname: this.hostname,
      port: this.port,
      pathname: this.pathname,
      search: this.search,
      searchParams: this.searchParams,
      hash: this.hash
    }
  }

  _slice(start, end = this._href.length) {
    return this._href.slice(start, end)
  }

  _replace(replacement, start, end = this._href.length) {
    return this._slice(0, start) + replacement + this._slice(end)
  }

  _parse(input, base, shouldThrow) {
    try {
      this._href = binding.parse(
        String(input),
        base ? String(base) : null,
        this._components,
        shouldThrow
      )
    } catch (err) {
      if (err instanceof TypeError) throw err

      throw errors.INVALID_URL(`Invalid URL '${input}'`, input)
    }
  }

  _update(input) {
    try {
      this._parse(input, null, true)
    } catch (err) {
      if (err instanceof TypeError) throw err
    }
  }
}

module.exports = exports = URL

// https://url.spec.whatwg.org/#url-opaque-path
function hasOpaquePath(url) {
  return url.pathname[0] !== '/'
}

// https://url.spec.whatwg.org/#cannot-have-a-username-password-port
function cannotHaveCredentialsOrPort(url) {
  return url.hostname === '' || url.protocol === 'file:'
}

exports.URL = URL
exports.URLSearchParams = URLSearchParams

exports.errors = errors

exports.isURL = function isURL(value) {
  if (value instanceof URL) return true

  return typeof value === 'object' && value !== null && value[kind] === URL[kind]
}

exports.isURLSearchParams = URLSearchParams.isURLSearchParams

// https://url.spec.whatwg.org/#dom-url-parse
exports.parse = function parse(input, base) {
  const url = new URL(input, base, { throw: false })
  return url._href ? url : null
}

// https://url.spec.whatwg.org/#dom-url-canparse
exports.canParse = function canParse(input, base) {
  return binding.canParse(String(input), base ? String(base) : null)
}

exports.fileURLToPath = function fileURLToPath(url) {
  if (typeof url === 'string') {
    url = new URL(url)
  }

  if (url.protocol !== 'file:') {
    throw errors.INVALID_URL_SCHEME('The URL must use the file: protocol')
  }

  if (isWindows) {
    if (/%2f|%5c/i.test(url.pathname)) {
      throw errors.INVALID_FILE_URL_PATH(
        'The file: URL path must not include encoded \\ or / characters'
      )
    }
  } else {
    if (url.hostname) {
      throw errors.INVALID_FILE_URL_HOST("The file: URL host must be 'localhost' or empty")
    }

    if (/%2f/i.test(url.pathname)) {
      throw errors.INVALID_FILE_URL_PATH('The file: URL path must not include encoded / characters')
    }
  }

  if (/%00/i.test(url.pathname)) {
    throw errors.INVALID_FILE_URL_PATH('The file: URL path must not include encoded NUL characters')
  }

  const pathname = path.normalize(decodeURIComponent(url.pathname))

  if (isWindows) {
    if (url.hostname) return '\\\\' + url.hostname + pathname

    const letter = pathname.charCodeAt(1) | 0x20

    if (letter < 0x61 /* a */ || letter > 0x7a /* z */ || pathname.charCodeAt(2) !== 0x3a /* : */) {
      throw errors.INVALID_FILE_URL_PATH('The file: URL path must be absolute')
    }

    return pathname.slice(1)
  }

  return pathname
}

exports.pathToFileURL = function pathToFileURL(pathname) {
  let resolved = path.resolve(pathname)

  if (pathname[pathname.length - 1] === '/') {
    resolved += '/'
  } else if (isWindows && pathname[pathname.length - 1] === '\\') {
    resolved += '\\'
  }

  resolved = resolved
    .replaceAll('%', '%25') // Must be first
    .replaceAll('#', '%23')
    .replaceAll('?', '%3f')
    .replaceAll('\n', '%0a')
    .replaceAll('\r', '%0d')
    .replaceAll('\t', '%09')

  if (!isWindows) {
    resolved = resolved.replaceAll('\\', '%5c')
  }

  return new URL('file:' + resolved)
}

exports.format = function format(parts) {
  const { protocol, auth, host, hostname, port, pathname, search, query, hash, slashes } = parts

  let result = ''

  if (typeof protocol === 'string') {
    result += protocol

    if (protocol[protocol.length - 1] !== ':') {
      result += ':'
    }

    if (slashes === true || /https?|ftp|gopher|file/.test(protocol)) {
      result += '//'
    }
  }

  if (typeof auth === 'string') {
    if (host || hostname) result += auth + '@'
  }

  if (typeof host === 'string') result += host
  else {
    result += hostname

    if (port) result += ':' + port
  }

  if (typeof pathname === 'string' && pathname !== '') {
    if (pathname[0] !== '/') result += '/'
    result += pathname
  }

  if (typeof search === 'string') {
    if (search[0] !== '?') result += '?'
    result += search
  } else if (typeof query === 'object' && query !== null) {
    result += '?' + new URLSearchParams(query)
  }

  if (typeof hash === 'string') {
    if (hash[0] !== '#') result += '#'
    result += hash
  }

  return result
}
module.exports = class URLError extends Error {
  constructor(msg, fn = URLError, code = fn.name) {
    super(`${code}: ${msg}`)

    this.code = code

    if (Error.captureStackTrace) Error.captureStackTrace(this, fn)
  }

  get name() {
    return 'URLError'
  }

  static INVALID_URL(msg, input) {
    const err = new URLError(msg, URLError.INVALID_URL)

    err.input = input

    return err
  }

  static INVALID_URL_SCHEME(msg = 'Invalid URL') {
    return new URLError(msg, URLError.INVALID_URL_SCHEME)
  }

  static INVALID_FILE_URL_HOST(msg = 'Invalid file: URL host') {
    return new URLError(msg, URLError.INVALID_FILE_URL_HOST)
  }

  static INVALID_FILE_URL_PATH(msg = 'Invalid file: URL path') {
    return new URLError(msg, URLError.INVALID_FILE_URL_PATH)
  }
}
const kind = Symbol.for('bare.url.search-params.kind')

class URLSearchParams {
  static _urls = new WeakMap()

  static get [kind]() {
    return 0 // Compatibility version
  }

  // https://url.spec.whatwg.org/#dom-urlsearchparams-urlsearchparams
  constructor(init, url = null) {
    this._params = new Map()

    if (url) URLSearchParams._urls.set(this, url)

    if (typeof init === 'string') {
      this._parse(init)
    } else if (init) {
      for (const [name, value] of typeof init[Symbol.iterator] === 'function'
        ? init
        : Object.entries(init)) {
        this.append(name, value)
      }
    }
  }

  get [kind]() {
    return URLSearchParams[kind]
  }

  // https://url.spec.whatwg.org/#dom-urlsearchparams-size
  get size() {
    return this._params.length
  }

  // https://url.spec.whatwg.org/#dom-urlsearchparams-append
  append(name, value = null) {
    if (value === null) return

    let list = this._params.get(name)

    if (list === undefined) {
      list = []
      this._params.set(name, list)
    }

    list.push(value)

    this._update()
  }

  // https://url.spec.whatwg.org/#dom-urlsearchparams-delete
  delete(name, value = null) {
    if (value === null) this._params.delete(name)
    else {
      let list = this._params.get(name)

      if (list === undefined) return

      list = list.filter((found) => found !== value)

      if (list.length === 0) this._params.delete(name)
      else this._params.set(name, list)
    }

    this._update()
  }

  // https://url.spec.whatwg.org/#dom-urlsearchparams-get
  get(name) {
    const list = this._params.get(name)

    if (list === undefined) return null

    return list[0]
  }

  // https://url.spec.whatwg.org/#dom-urlsearchparams-getall
  getAll(name) {
    const list = this._params.get(name)

    if (list === undefined) return []

    return Array.from(list)
  }

  // https://url.spec.whatwg.org/#dom-urlsearchparams-has
  has(name, value = null) {
    const list = this._params.get(name)

    if (list === undefined) return false

    if (value === null) return true

    return list.includes(value)
  }

  // https://url.spec.whatwg.org/#dom-urlsearchparams-set
  set(name, value = null) {
    if (value === null) this._params.delete(name)
    else this._params.set(name, [value])

    this._update()
  }

  toString() {
    return this._serialize()
  }

  toJSON() {
    return [...this]
  }

  *[Symbol.iterator]() {
    for (const [name, values] of this._params) {
      for (const value of values) yield [name, value]
    }
  }

  [Symbol.for('bare.inspect')]() {
    const object = {
      __proto__: { constructor: URLSearchParams }
    }

    for (const [name, values] of this._params) {
      if (values.length === 1) object[name] = values[0]
      else object[name] = values
    }

    return object
  }

  // https://url.spec.whatwg.org/#concept-urlsearchparams-update
  _update() {
    const url = URLSearchParams._urls.get(this)

    if (url === undefined) return

    url.search = this._serialize()
  }

  // https://url.spec.whatwg.org/#concept-urlencoded-parser
  _parse(input) {
    if (input[0] === '?') input = input.substring(1)

    this._params = new Map()

    for (let sequence of input.split('&')) {
      if (sequence.length === 0) continue

      sequence = sequence.replace(/\+/g, ' ')

      let i = sequence.indexOf('=')
      if (i === -1) i = sequence.length

      const name = decodeURIComponent(sequence.substring(0, i))
      const value = decodeURIComponent(sequence.substring(i + 1, sequence.length))

      let list = this._params.get(name)

      if (list === undefined) {
        list = []
        this._params.set(name, list)
      }

      list.push(value)
    }
  }

  // https://url.spec.whatwg.org/#concept-urlencoded-serializer
  _serialize() {
    let output = ''

    for (let [name, values] of this._params) {
      name = encodeURIComponent(name)

      for (const value of values) {
        if (output) output += '&'

        output += name + '=' + encodeURIComponent(value)
      }
    }

    return output
  }
}

module.exports = exports = URLSearchParams

exports.isURLSearchParams = function isURLSearchParams(value) {
  if (value instanceof URLSearchParams) return true

  return typeof value === 'object' && value !== null && value[kind] === URLSearchParams[kind]
}
{
  "name": "bare-url",
  "version": "2.4.5",
  "description": "WHATWG URL implementation for JavaScript",
  "exports": {
    "./package": "./package.json",
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    },
    "./global": {
      "types": "./global.d.ts",
      "default": "./global.js"
    }
  },
  "files": [
    "index.js",
    "index.d.ts",
    "global.js",
    "global.d.ts",
    "binding.c",
    "binding.js",
    "CMakeLists.txt",
    "lib",
    "prebuilds"
  ],
  "addon": true,
  "scripts": {
    "format": "prettier --write . && lunte --fix",
    "lint": "prettier --check . && lunte",
    "test": "brittle-bare --coverage test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-url.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-url/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-url",
  "dependencies": {
    "bare-path": "^3.0.0"
  },
  "devDependencies": {
    "brittle": "^3.3.2",
    "cmake-bare": "^1.1.6",
    "cmake-fetch": "^1.0.0",
    "lunte": "^1.6.0",
    "prettier": "^3.3.3",
    "prettier-config-holepunch": "^2.0.0"
  }
}
const LE = (exports.LE =
  new Uint8Array(new Uint16Array([0xff]).buffer)[0] === 0xff)

exports.BE = !LE
const b4a = require('b4a')

const { BE } = require('./endian')

exports.state = function (start = 0, end = 0, buffer = null) {
  return { start, end, buffer }
}

const raw = (exports.raw = require('./raw'))

const uint = (exports.uint = {
  preencode(state, n) {
    state.end += n <= 0xfc ? 1 : n <= 0xffff ? 3 : n <= 0xffffffff ? 5 : 9
  },
  encode(state, n) {
    if (n <= 0xfc) uint8.encode(state, n)
    else if (n <= 0xffff) {
      state.buffer[state.start++] = 0xfd
      uint16.encode(state, n)
    } else if (n <= 0xffffffff) {
      state.buffer[state.start++] = 0xfe
      uint32.encode(state, n)
    } else {
      state.buffer[state.start++] = 0xff
      uint64.encode(state, n)
    }
  },
  decode(state) {
    const a = uint8.decode(state)
    if (a <= 0xfc) return a
    if (a === 0xfd) return uint16.decode(state)
    if (a === 0xfe) return uint32.decode(state)
    return uint64.decode(state)
  }
})

const uint8 = (exports.uint8 = {
  preencode(state, n) {
    state.end += 1
  },
  encode(state, n) {
    validateUint(n)
    state.buffer[state.start++] = n
  },
  decode(state) {
    if (state.start >= state.end) throw new Error('Out of bounds')
    return state.buffer[state.start++]
  }
})

const uint16 = (exports.uint16 = {
  preencode(state, n) {
    state.end += 2
  },
  encode(state, n) {
    validateUint(n)
    state.buffer[state.start++] = n
    state.buffer[state.start++] = n >>> 8
  },
  decode(state) {
    if (state.end - state.start < 2) throw new Error('Out of bounds')
    return state.buffer[state.start++] + state.buffer[state.start++] * 0x100
  }
})

const uint24 = (exports.uint24 = {
  preencode(state, n) {
    state.end += 3
  },
  encode(state, n) {
    validateUint(n)
    state.buffer[state.start++] = n
    state.buffer[state.start++] = n >>> 8
    state.buffer[state.start++] = n >>> 16
  },
  decode(state) {
    if (state.end - state.start < 3) throw new Error('Out of bounds')
    return (
      state.buffer[state.start++] +
      state.buffer[state.start++] * 0x100 +
      state.buffer[state.start++] * 0x10000
    )
  }
})

const uint32 = (exports.uint32 = {
  preencode(state, n) {
    state.end += 4
  },
  encode(state, n) {
    validateUint(n)
    state.buffer[state.start++] = n
    state.buffer[state.start++] = n >>> 8
    state.buffer[state.start++] = n >>> 16
    state.buffer[state.start++] = n >>> 24
  },
  decode(state) {
    if (state.end - state.start < 4) throw new Error('Out of bounds')
    return (
      state.buffer[state.start++] +
      state.buffer[state.start++] * 0x100 +
      state.buffer[state.start++] * 0x10000 +
      state.buffer[state.start++] * 0x1000000
    )
  }
})

const uint32be = (exports.uint32be = {
  preencode(state, n) {
    state.end += 4
  },
  encode(state, n) {
    validateUint(n)
    state.buffer[state.start++] = n >>> 24
    state.buffer[state.start++] = n >>> 16
    state.buffer[state.start++] = n >>> 8
    state.buffer[state.start++] = n
  },
  decode(state) {
    if (state.end - state.start < 4) throw new Error('Out of bounds')
    return (
      state.buffer[state.start++] * 0x1000000 +
      state.buffer[state.start++] * 0x10000 +
      state.buffer[state.start++] * 0x100 +
      state.buffer[state.start++]
    )
  }
})

const uint40 = (exports.uint40 = {
  preencode(state, n) {
    state.end += 5
  },
  encode(state, n) {
    validateUint(n)
    const r = Math.floor(n / 0x100)
    uint8.encode(state, n)
    uint32.encode(state, r)
  },
  decode(state) {
    if (state.end - state.start < 5) throw new Error('Out of bounds')
    return uint8.decode(state) + 0x100 * uint32.decode(state)
  }
})

const uint48 = (exports.uint48 = {
  preencode(state, n) {
    state.end += 6
  },
  encode(state, n) {
    validateUint(n)
    const r = Math.floor(n / 0x10000)
    uint16.encode(state, n)
    uint32.encode(state, r)
  },
  decode(state) {
    if (state.end - state.start < 6) throw new Error('Out of bounds')
    return uint16.decode(state) + 0x10000 * uint32.decode(state)
  }
})

const uint56 = (exports.uint56 = {
  preencode(state, n) {
    state.end += 7
  },
  encode(state, n) {
    validateUint(n)
    const r = Math.floor(n / 0x1000000)
    uint24.encode(state, n)
    uint32.encode(state, r)
  },
  decode(state) {
    if (state.end - state.start < 7) throw new Error('Out of bounds')
    return uint24.decode(state) + 0x1000000 * uint32.decode(state)
  }
})

const uint64 = (exports.uint64 = {
  preencode(state, n) {
    state.end += 8
  },
  encode(state, n) {
    validateUint(n)
    const r = Math.floor(n / 0x100000000)
    uint32.encode(state, n)
    uint32.encode(state, r)
  },
  decode(state) {
    if (state.end - state.start < 8) throw new Error('Out of bounds')
    return uint32.decode(state) + 0x100000000 * uint32.decode(state)
  }
})

exports.uint64be = {
  preencode(state, n) {
    state.end += 8
  },
  encode(state, n) {
    validateUint(n)
    const r = Math.floor(n / 0x100000000)
    uint32be.encode(state, r)
    uint32be.encode(state, n)
  },
  decode(state) {
    if (state.end - state.start < 8) throw new Error('Out of bounds')
    return 0x100000000 * uint32be.decode(state) + uint32be.decode(state)
  }
}

const int = (exports.int = zigZagInt(uint))
exports.int8 = zigZagInt(uint8)
exports.int16 = zigZagInt(uint16)
exports.int24 = zigZagInt(uint24)
exports.int32 = zigZagInt(uint32)
exports.int40 = zigZagInt(uint40)
exports.int48 = zigZagInt(uint48)
exports.int56 = zigZagInt(uint56)
exports.int64 = zigZagInt(uint64)

const biguint64 = (exports.biguint64 = {
  preencode(state, n) {
    state.end += 8
  },
  encode(state, n) {
    const view = new DataView(
      state.buffer.buffer,
      state.start + state.buffer.byteOffset,
      8
    )
    view.setBigUint64(0, n, true) // little endian
    state.start += 8
  },
  decode(state) {
    if (state.end - state.start < 8) throw new Error('Out of bounds')
    const view = new DataView(
      state.buffer.buffer,
      state.start + state.buffer.byteOffset,
      8
    )
    const n = view.getBigUint64(0, true) // little endian
    state.start += 8
    return n
  }
})

exports.bigint64 = zigZagBigInt(biguint64)

const biguint = (exports.biguint = {
  preencode(state, n) {
    let len = 0
    for (let m = n; m; m = m >> 64n) len++
    uint.preencode(state, len)
    state.end += 8 * len
  },
  encode(state, n) {
    let len = 0
    for (let m = n; m; m = m >> 64n) len++
    uint.encode(state, len)
    const view = new DataView(
      state.buffer.buffer,
      state.start + state.buffer.byteOffset,
      8 * len
    )
    for (let m = n, i = 0; m; m = m >> 64n, i += 8) {
      view.setBigUint64(i, BigInt.asUintN(64, m), true) // little endian
    }
    state.start += 8 * len
  },
  decode(state) {
    const len = uint.decode(state)
    if (state.end - state.start < 8 * len) throw new Error('Out of bounds')
    const view = new DataView(
      state.buffer.buffer,
      state.start + state.buffer.byteOffset,
      8 * len
    )
    let n = 0n
    for (let i = len - 1; i >= 0; i--)
      n = (n << 64n) + view.getBigUint64(i * 8, true) // little endian
    state.start += 8 * len
    return n
  }
})

exports.bigint = zigZagBigInt(biguint)

exports.lexint = require('./lexint')

exports.float32 = {
  preencode(state, n) {
    state.end += 4
  },
  encode(state, n) {
    const view = new DataView(
      state.buffer.buffer,
      state.start + state.buffer.byteOffset,
      4
    )
    view.setFloat32(0, n, true) // little endian
    state.start += 4
  },
  decode(state) {
    if (state.end - state.start < 4) throw new Error('Out of bounds')
    const view = new DataView(
      state.buffer.buffer,
      state.start + state.buffer.byteOffset,
      4
    )
    const float = view.getFloat32(0, true) // little endian
    state.start += 4
    return float
  }
}

exports.float64 = {
  preencode(state, n) {
    state.end += 8
  },
  encode(state, n) {
    const view = new DataView(
      state.buffer.buffer,
      state.start + state.buffer.byteOffset,
      8
    )
    view.setFloat64(0, n, true) // little endian
    state.start += 8
  },
  decode(state) {
    if (state.end - state.start < 8) throw new Error('Out of bounds')
    const view = new DataView(
      state.buffer.buffer,
      state.start + state.buffer.byteOffset,
      8
    )
    const float = view.getFloat64(0, true) // little endian
    state.start += 8
    return float
  }
}

const buffer = (exports.buffer = {
  preencode(state, b) {
    uint8array.preencode(state, b)
  },
  encode(state, b) {
    uint8array.encode(state, b)
  },
  decode(state) {
    const len = uint.decode(state)
    if (state.end - state.start < len) throw new Error('Out of bounds')
    return state.buffer.subarray(state.start, (state.start += len))
  }
})

exports.optionalBuffer = {
  preencode(state, b) {
    if (b) uint8array.preencode(state, b)
    else state.end++
  },
  encode(state, b) {
    if (b) uint8array.encode(state, b)
    else state.buffer[state.start++] = 0
  },
  decode(state) {
    const len = uint.decode(state)
    if (len === 0) return null
    if (state.end - state.start < len) throw new Error('Out of bounds')
    return state.buffer.subarray(state.start, (state.start += len))
  }
}

exports.binary = {
  ...buffer,
  preencode(state, b) {
    if (typeof b === 'string') utf8.preencode(state, b)
    else buffer.preencode(state, b)
  },
  encode(state, b) {
    if (typeof b === 'string') utf8.encode(state, b)
    else buffer.encode(state, b)
  }
}

exports.arraybuffer = {
  preencode(state, b) {
    uint.preencode(state, b.byteLength)
    state.end += b.byteLength
  },
  encode(state, b) {
    uint.encode(state, b.byteLength)

    const view = new Uint8Array(b)

    state.buffer.set(view, state.start)
    state.start += b.byteLength
  },
  decode(state) {
    const len = uint.decode(state)

    const b = new ArrayBuffer(len)
    const view = new Uint8Array(b)

    view.set(state.buffer.subarray(state.start, (state.start += len)))

    return b
  }
}

function typedarray(TypedArray, swap) {
  const n = TypedArray.BYTES_PER_ELEMENT

  return {
    preencode(state, b) {
      uint.preencode(state, b.length)
      state.end += b.byteLength
    },
    encode(state, b) {
      uint.encode(state, b.length)

      const view = new Uint8Array(b.buffer, b.byteOffset, b.byteLength)

      if (BE && swap) swap(view)

      state.buffer.set(view, state.start)
      state.start += b.byteLength
    },
    decode(state) {
      const len = uint.decode(state)

      let b = state.buffer.subarray(state.start, (state.start += len * n))
      if (b.byteLength !== len * n) throw new Error('Out of bounds')
      if (b.byteOffset % n !== 0) b = new Uint8Array(b)

      if (BE && swap) swap(b)

      return new TypedArray(b.buffer, b.byteOffset, b.byteLength / n)
    }
  }
}

const uint8array = (exports.uint8array = typedarray(Uint8Array))
exports.uint16array = typedarray(Uint16Array, b4a.swap16)
exports.uint32array = typedarray(Uint32Array, b4a.swap32)

exports.int8array = typedarray(Int8Array)
exports.int16array = typedarray(Int16Array, b4a.swap16)
exports.int32array = typedarray(Int32Array, b4a.swap32)

exports.biguint64array = typedarray(BigUint64Array, b4a.swap64)
exports.bigint64array = typedarray(BigInt64Array, b4a.swap64)

exports.float32array = typedarray(Float32Array, b4a.swap32)
exports.float64array = typedarray(Float64Array, b4a.swap64)

function string(encoding) {
  return {
    preencode(state, s) {
      const len = b4a.byteLength(s, encoding)
      uint.preencode(state, len)
      state.end += len
    },
    encode(state, s) {
      const len = b4a.byteLength(s, encoding)
      uint.encode(state, len)
      b4a.write(state.buffer, s, state.start, encoding)
      state.start += len
    },
    decode(state) {
      const len = uint.decode(state)
      if (state.end - state.start < len) throw new Error('Out of bounds')
      return b4a.toString(
        state.buffer,
        encoding,
        state.start,
        (state.start += len)
      )
    },
    fixed(n) {
      return {
        preencode(state) {
          state.end += n
        },
        encode(state, s) {
          b4a.write(state.buffer, s, state.start, n, encoding)
          state.start += n
        },
        decode(state) {
          if (state.end - state.start < n) throw new Error('Out of bounds')
          return b4a.toString(
            state.buffer,
            encoding,
            state.start,
            (state.start += n)
          )
        }
      }
    }
  }
}

const utf8 = (exports.string = exports.utf8 = string('utf-8'))
exports.ascii = string('ascii')
exports.hex = string('hex')
exports.base64 = string('base64')
exports.ucs2 = exports.utf16le = string('utf16le')

exports.bool = {
  preencode(state, b) {
    state.end++
  },
  encode(state, b) {
    state.buffer[state.start++] = b ? 1 : 0
  },
  decode(state) {
    if (state.start >= state.end) throw Error('Out of bounds')
    return state.buffer[state.start++] === 1
  }
}

const fixed = (exports.fixed = function fixed(n) {
  return {
    preencode(state, s) {
      if (s.byteLength !== n) throw new Error('Incorrect buffer size')
      state.end += n
    },
    encode(state, s) {
      state.buffer.set(s, state.start)
      state.start += n
    },
    decode(state) {
      if (state.end - state.start < n) throw new Error('Out of bounds')
      return state.buffer.subarray(state.start, (state.start += n))
    }
  }
})

exports.fixed32 = fixed(32)
exports.fixed64 = fixed(64)

exports.array = function array(enc) {
  return {
    preencode(state, list) {
      uint.preencode(state, list.length)
      for (let i = 0; i < list.length; i++) enc.preencode(state, list[i])
    },
    encode(state, list) {
      uint.encode(state, list.length)
      for (let i = 0; i < list.length; i++) enc.encode(state, list[i])
    },
    decode(state) {
      const len = uint.decode(state)
      if (len > 0x100000) throw new Error('Array is too big')
      const arr = new Array(len)
      for (let i = 0; i < len; i++) arr[i] = enc.decode(state)
      return arr
    }
  }
}

exports.frame = function frame(enc) {
  const dummy = exports.state()

  return {
    preencode(state, m) {
      const end = state.end
      enc.preencode(state, m)
      uint.preencode(state, state.end - end)
    },
    encode(state, m) {
      dummy.end = 0
      enc.preencode(dummy, m)
      uint.encode(state, dummy.end)
      enc.encode(state, m)
    },
    decode(state) {
      const end = state.end
      const len = uint.decode(state)
      state.end = state.start + len
      const m = enc.decode(state)
      state.start = state.end
      state.end = end
      return m
    }
  }
}

exports.date = {
  preencode(state, d) {
    int.preencode(state, d.getTime())
  },
  encode(state, d) {
    int.encode(state, d.getTime())
  },
  decode(state, d) {
    return new Date(int.decode(state))
  }
}

exports.json = {
  preencode(state, v) {
    utf8.preencode(state, JSON.stringify(v))
  },
  encode(state, v) {
    utf8.encode(state, JSON.stringify(v))
  },
  decode(state) {
    return JSON.parse(utf8.decode(state))
  }
}

exports.ndjson = {
  preencode(state, v) {
    utf8.preencode(state, JSON.stringify(v) + '\n')
  },
  encode(state, v) {
    utf8.encode(state, JSON.stringify(v) + '\n')
  },
  decode(state) {
    return JSON.parse(utf8.decode(state))
  }
}

// simple helper for when you want to just express nothing
exports.none = {
  preencode(state, n) {
    // do nothing
  },
  encode(state, n) {
    // do nothing
  },
  decode(state) {
    return null
  }
}

// "any" encoders here for helping just structure any object without schematising it

const anyArray = {
  preencode(state, arr) {
    uint.preencode(state, arr.length)
    for (let i = 0; i < arr.length; i++) {
      any.preencode(state, arr[i])
    }
  },
  encode(state, arr) {
    uint.encode(state, arr.length)
    for (let i = 0; i < arr.length; i++) {
      any.encode(state, arr[i])
    }
  },
  decode(state) {
    const arr = []
    let len = uint.decode(state)
    while (len-- > 0) {
      arr.push(any.decode(state))
    }
    return arr
  }
}

const anyObject = {
  preencode(state, o) {
    const keys = Object.keys(o)
    uint.preencode(state, keys.length)
    for (const key of keys) {
      utf8.preencode(state, key)
      any.preencode(state, o[key])
    }
  },
  encode(state, o) {
    const keys = Object.keys(o)
    uint.encode(state, keys.length)
    for (const key of keys) {
      utf8.encode(state, key)
      any.encode(state, o[key])
    }
  },
  decode(state) {
    let len = uint.decode(state)
    const o = {}
    while (len-- > 0) {
      const key = utf8.decode(state)
      o[key] = any.decode(state)
    }
    return o
  }
}

const anyTypes = [
  exports.none,
  exports.bool,
  exports.string,
  exports.buffer,
  exports.uint,
  exports.int,
  exports.float64,
  anyArray,
  anyObject,
  exports.date
]

const any = (exports.any = {
  preencode(state, o) {
    const t = getType(o)
    uint.preencode(state, t)
    anyTypes[t].preencode(state, o)
  },
  encode(state, o) {
    const t = getType(o)
    uint.encode(state, t)
    anyTypes[t].encode(state, o)
  },
  decode(state) {
    const t = uint.decode(state)
    if (t >= anyTypes.length) throw new Error('Unknown type: ' + t)
    return anyTypes[t].decode(state)
  }
})

const port = (exports.port = uint16)

const address = (host, family) => {
  return {
    preencode(state, m) {
      host.preencode(state, m.host)
      port.preencode(state, m.port)
    },
    encode(state, m) {
      host.encode(state, m.host)
      port.encode(state, m.port)
    },
    decode(state) {
      return {
        host: host.decode(state),
        family,
        port: port.decode(state)
      }
    }
  }
}

const ipv4 = (exports.ipv4 = {
  preencode(state) {
    state.end += 4
  },
  encode(state, string) {
    const start = state.start
    const end = start + 4

    let i = 0

    while (i < string.length) {
      let n = 0
      let c

      while (
        i < string.length &&
        (c = string.charCodeAt(i++)) !== /* . */ 0x2e
      ) {
        n = n * 10 + (c - /* 0 */ 0x30)
      }

      state.buffer[state.start++] = n
    }

    state.start = end
  },
  decode(state) {
    if (state.end - state.start < 4) throw new Error('Out of bounds')
    return (
      state.buffer[state.start++] +
      '.' +
      state.buffer[state.start++] +
      '.' +
      state.buffer[state.start++] +
      '.' +
      state.buffer[state.start++]
    )
  }
})

exports.ipv4Address = address(ipv4, 4)

const ipv6 = (exports.ipv6 = {
  preencode(state) {
    state.end += 16
  },
  encode(state, string) {
    const start = state.start
    const end = start + 16

    let i = 0
    let split = null

    while (i < string.length) {
      let n = 0
      let c

      while (
        i < string.length &&
        (c = string.charCodeAt(i++)) !== /* : */ 0x3a
      ) {
        if (c >= 0x30 && c <= 0x39) n = n * 0x10 + (c - /* 0 */ 0x30)
        else if (c >= 0x41 && c <= 0x46) n = n * 0x10 + (c - /* A */ 0x41 + 10)
        else if (c >= 0x61 && c <= 0x66) n = n * 0x10 + (c - /* a */ 0x61 + 10)
      }

      state.buffer[state.start++] = n >>> 8
      state.buffer[state.start++] = n

      if (i < string.length && string.charCodeAt(i) === /* : */ 0x3a) {
        i++
        split = state.start
      }
    }

    if (split !== null) {
      const offset = end - state.start
      state.buffer
        .copyWithin(split + offset, split)
        .fill(0, split, split + offset)
    }

    state.start = end
  },
  decode(state) {
    if (state.end - state.start < 16) throw new Error('Out of bounds')
    return (
      (
        state.buffer[state.start++] * 256 +
        state.buffer[state.start++]
      ).toString(16) +
      ':' +
      (
        state.buffer[state.start++] * 256 +
        state.buffer[state.start++]
      ).toString(16) +
      ':' +
      (
        state.buffer[state.start++] * 256 +
        state.buffer[state.start++]
      ).toString(16) +
      ':' +
      (
        state.buffer[state.start++] * 256 +
        state.buffer[state.start++]
      ).toString(16) +
      ':' +
      (
        state.buffer[state.start++] * 256 +
        state.buffer[state.start++]
      ).toString(16) +
      ':' +
      (
        state.buffer[state.start++] * 256 +
        state.buffer[state.start++]
      ).toString(16) +
      ':' +
      (
        state.buffer[state.start++] * 256 +
        state.buffer[state.start++]
      ).toString(16) +
      ':' +
      (
        state.buffer[state.start++] * 256 +
        state.buffer[state.start++]
      ).toString(16)
    )
  }
})

exports.ipv6Address = address(ipv6, 6)

const ip = (exports.ip = {
  preencode(state, string) {
    const family = string.includes(':') ? 6 : 4
    uint8.preencode(state, family)
    if (family === 4) ipv4.preencode(state)
    else ipv6.preencode(state)
  },
  encode(state, string) {
    const family = string.includes(':') ? 6 : 4
    uint8.encode(state, family)
    if (family === 4) ipv4.encode(state, string)
    else ipv6.encode(state, string)
  },
  decode(state) {
    const family = uint8.decode(state)
    if (family === 4) return ipv4.decode(state)
    else return ipv6.decode(state)
  }
})

exports.ipAddress = {
  preencode(state, m) {
    ip.preencode(state, m.host)
    port.preencode(state, m.port)
  },
  encode(state, m) {
    ip.encode(state, m.host)
    port.encode(state, m.port)
  },
  decode(state) {
    const family = uint8.decode(state)
    return {
      host: family === 4 ? ipv4.decode(state) : ipv6.decode(state),
      family,
      port: port.decode(state)
    }
  }
}

const record = (exports.record = function (keyEncoding, valueEncoding) {
  return {
    preencode(state, v) {
      const keys = Object.keys(v)
      uint.preencode(state, keys.length)
      for (const k of keys) {
        keyEncoding.preencode(state, k)
        valueEncoding.preencode(state, v[k])
      }
    },
    encode(state, v) {
      const keys = Object.keys(v)
      uint.encode(state, keys.length)
      for (const k of keys) {
        keyEncoding.encode(state, k)
        valueEncoding.encode(state, v[k])
      }
    },
    decode(state) {
      const out = Object.create(null)
      const keys = uint.decode(state)
      for (let i = 0; i < keys; i++) {
        out[keyEncoding.decode(state)] = valueEncoding.decode(state)
      }
      return out
    }
  }
})

exports.stringRecord = record(utf8, utf8)

function getType(o) {
  if (o === null || o === undefined) return 0
  if (typeof o === 'boolean') return 1
  if (typeof o === 'string') return 2
  if (b4a.isBuffer(o)) return 3
  if (typeof o === 'number') {
    if (Number.isInteger(o)) return o >= 0 ? 4 : 5
    return 6
  }
  if (Array.isArray(o)) return 7
  if (o instanceof Date) return 9
  if (typeof o === 'object') return 8

  throw new Error('Unsupported type for ' + o)
}

exports.from = function from(enc) {
  if (typeof enc === 'string') return fromNamed(enc)
  if (enc.preencode) return enc
  if (enc.encodingLength) return fromAbstractEncoder(enc)
  return fromCodec(enc)
}

function fromNamed(enc) {
  switch (enc) {
    case 'ascii':
      return raw.ascii
    case 'utf-8':
    case 'utf8':
      return raw.utf8
    case 'hex':
      return raw.hex
    case 'base64':
      return raw.base64
    case 'utf16-le':
    case 'utf16le':
    case 'ucs-2':
    case 'ucs2':
      return raw.ucs2
    case 'ndjson':
      return raw.ndjson
    case 'json':
      return raw.json
    case 'binary':
    default:
      return raw.binary
  }
}

function fromCodec(enc) {
  let tmpM = null
  let tmpBuf = null

  return {
    preencode(state, m) {
      tmpM = m
      tmpBuf = enc.encode(m)
      state.end += tmpBuf.byteLength
    },
    encode(state, m) {
      raw.encode(state, m === tmpM ? tmpBuf : enc.encode(m))
      tmpM = tmpBuf = null
    },
    decode(state) {
      return enc.decode(raw.decode(state))
    }
  }
}

function fromAbstractEncoder(enc) {
  return {
    preencode(state, m) {
      state.end += enc.encodingLength(m)
    },
    encode(state, m) {
      enc.encode(m, state.buffer, state.start)
      state.start += enc.encode.bytes
    },
    decode(state) {
      const m = enc.decode(state.buffer, state.start, state.end)
      state.start += enc.decode.bytes
      return m
    }
  }
}

exports.encode = function encode(enc, m) {
  const state = exports.state()
  enc.preencode(state, m)
  state.buffer = b4a.allocUnsafe(state.end)
  enc.encode(state, m)
  return state.buffer
}

exports.decode = function decode(enc, buffer) {
  return enc.decode(exports.state(0, buffer.byteLength, buffer))
}

function zigZagInt(enc) {
  return {
    preencode(state, n) {
      enc.preencode(state, zigZagEncodeInt(n))
    },
    encode(state, n) {
      enc.encode(state, zigZagEncodeInt(n))
    },
    decode(state) {
      return zigZagDecodeInt(enc.decode(state))
    }
  }
}

function zigZagDecodeInt(n) {
  return n === 0 ? n : (n & 1) === 0 ? n / 2 : -(n + 1) / 2
}

function zigZagEncodeInt(n) {
  // 0, -1, 1, -2, 2, ...
  return n < 0 ? 2 * -n - 1 : n === 0 ? 0 : 2 * n
}

function zigZagBigInt(enc) {
  return {
    preencode(state, n) {
      enc.preencode(state, zigZagEncodeBigInt(n))
    },
    encode(state, n) {
      enc.encode(state, zigZagEncodeBigInt(n))
    },
    decode(state) {
      return zigZagDecodeBigInt(enc.decode(state))
    }
  }
}

function zigZagDecodeBigInt(n) {
  return n === 0n ? n : (n & 1n) === 0n ? n / 2n : -(n + 1n) / 2n
}

function zigZagEncodeBigInt(n) {
  // 0, -1, 1, -2, 2, ...
  return n < 0n ? 2n * -n - 1n : n === 0n ? 0n : 2n * n
}

function validateUint(n) {
  if (n >= 0 === false /* Handles NaN as well */)
    throw new Error('uint must be positive')
  // Beyond this, Number arithmetic loses precision and silently corrupts the
  // encoding. The zig-zag int codecs route through here too, so this also
  // guards int values whose doubled magnitude exceeds the safe range.
  if (n > Number.MAX_SAFE_INTEGER)
    throw new Error(
      'integer is greater than the maximum safe integer, use biguint/bigint'
    )
}
module.exports = {
  preencode,
  encode,
  decode
}

function preencode(state, num) {
  if (num < 251) {
    state.end++
  } else if (num < 256) {
    state.end += 2
  } else if (num < 0x10000) {
    state.end += 3
  } else if (num < 0x1000000) {
    state.end += 4
  } else if (num < 0x100000000) {
    state.end += 5
  } else {
    state.end++
    const exp = Math.floor(Math.log(num) / Math.log(2)) - 32
    preencode(state, exp)
    state.end += 6
  }
}

function encode(state, num) {
  const max = 251
  const x = num - max

  if (num < max) {
    state.buffer[state.start++] = num
  } else if (num < 256) {
    state.buffer[state.start++] = max
    state.buffer[state.start++] = x
  } else if (num < 0x10000) {
    state.buffer[state.start++] = max + 1
    state.buffer[state.start++] = (x >> 8) & 0xff
    state.buffer[state.start++] = x & 0xff
  } else if (num < 0x1000000) {
    state.buffer[state.start++] = max + 2
    state.buffer[state.start++] = x >> 16
    state.buffer[state.start++] = (x >> 8) & 0xff
    state.buffer[state.start++] = x & 0xff
  } else if (num < 0x100000000) {
    state.buffer[state.start++] = max + 3
    state.buffer[state.start++] = x >> 24
    state.buffer[state.start++] = (x >> 16) & 0xff
    state.buffer[state.start++] = (x >> 8) & 0xff
    state.buffer[state.start++] = x & 0xff
  } else {
    // need to use Math here as bitwise ops are 32 bit
    const exp = Math.floor(Math.log(x) / Math.log(2)) - 32
    state.buffer[state.start++] = 0xff

    encode(state, exp)
    const rem = x / Math.pow(2, exp - 11)

    for (let i = 5; i >= 0; i--) {
      state.buffer[state.start++] = (rem / Math.pow(2, 8 * i)) & 0xff
    }
  }
}

function decode(state) {
  const max = 251

  if (state.end - state.start < 1) throw new Error('Out of bounds')

  const flag = state.buffer[state.start++]

  if (flag < max) return flag

  if (state.end - state.start < flag - max + 1) {
    throw new Error('Out of bounds.')
  }

  if (flag < 252) {
    return state.buffer[state.start++] + max
  }

  if (flag < 253) {
    return (
      (state.buffer[state.start++] << 8) + state.buffer[state.start++] + max
    )
  }

  if (flag < 254) {
    return (
      (state.buffer[state.start++] << 16) +
      (state.buffer[state.start++] << 8) +
      state.buffer[state.start++] +
      max
    )
  }

  // << 24 result may be interpreted as negative
  if (flag < 255) {
    return (
      state.buffer[state.start++] * 0x1000000 +
      (state.buffer[state.start++] << 16) +
      (state.buffer[state.start++] << 8) +
      state.buffer[state.start++] +
      max
    )
  }

  const exp = decode(state)

  if (state.end - state.start < 6) throw new Error('Out of bounds')

  let rem = 0
  for (let i = 5; i >= 0; i--) {
    rem += state.buffer[state.start++] * Math.pow(2, 8 * i)
  }

  return rem * Math.pow(2, exp - 11) + max
}
{
  "name": "compact-encoding",
  "version": "3.2.0",
  "description": "A series of compact encoding schemes for building small and fast parsers and serializers",
  "main": "index.js",
  "files": [
    "endian.js",
    "index.js",
    "lexint.js",
    "raw.js"
  ],
  "dependencies": {
    "b4a": "^1.3.0"
  },
  "devDependencies": {
    "brittle": "^3.0.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^1.0.0"
  },
  "scripts": {
    "format": "prettier . --write",
    "test": "prettier . --check && brittle test.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/holepunchto/compact-encoding.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/compact-encoding/issues"
  },
  "homepage": "https://github.com/holepunchto/compact-encoding"
}
const b4a = require('b4a')

const { BE } = require('./endian')

exports = module.exports = {
  preencode(state, b) {
    state.end += b.byteLength
  },
  encode(state, b) {
    state.buffer.set(b, state.start)
    state.start += b.byteLength
  },
  decode(state) {
    const b = state.buffer.subarray(state.start, state.end)
    state.start = state.end
    return b
  }
}

const buffer = (exports.buffer = {
  preencode(state, b) {
    uint8array.preencode(state, b)
  },
  encode(state, b) {
    uint8array.encode(state, b)
  },
  decode(state) {
    const b = state.buffer.subarray(state.start)
    state.start = state.end
    return b
  }
})

exports.binary = {
  ...buffer,
  preencode(state, b) {
    if (typeof b === 'string') utf8.preencode(state, b)
    else buffer.preencode(state, b)
  },
  encode(state, b) {
    if (typeof b === 'string') utf8.encode(state, b)
    else buffer.encode(state, b)
  }
}

exports.arraybuffer = {
  preencode(state, b) {
    state.end += b.byteLength
  },
  encode(state, b) {
    const view = new Uint8Array(b)

    state.buffer.set(view, state.start)
    state.start += b.byteLength
  },
  decode(state) {
    const b = new ArrayBuffer(state.end - state.start)
    const view = new Uint8Array(b)

    view.set(state.buffer.subarray(state.start))

    state.start = state.end

    return b
  }
}

function typedarray(TypedArray, swap) {
  const n = TypedArray.BYTES_PER_ELEMENT

  return {
    preencode(state, b) {
      state.end += b.byteLength
    },
    encode(state, b) {
      const view = new Uint8Array(b.buffer, b.byteOffset, b.byteLength)

      if (BE && swap) swap(view)

      state.buffer.set(view, state.start)
      state.start += b.byteLength
    },
    decode(state) {
      let b = state.buffer.subarray(state.start)
      if (b.byteOffset % n !== 0) b = new Uint8Array(b)

      if (BE && swap) swap(b)

      state.start = state.end

      return new TypedArray(b.buffer, b.byteOffset, b.byteLength / n)
    }
  }
}

const uint8array = (exports.uint8array = typedarray(Uint8Array))
exports.uint16array = typedarray(Uint16Array, b4a.swap16)
exports.uint32array = typedarray(Uint32Array, b4a.swap32)

exports.int8array = typedarray(Int8Array)
exports.int16array = typedarray(Int16Array, b4a.swap16)
exports.int32array = typedarray(Int32Array, b4a.swap32)

exports.biguint64array = typedarray(BigUint64Array, b4a.swap64)
exports.bigint64array = typedarray(BigInt64Array, b4a.swap64)

exports.float32array = typedarray(Float32Array, b4a.swap32)
exports.float64array = typedarray(Float64Array, b4a.swap64)

function string(encoding) {
  return {
    preencode(state, s) {
      state.end += b4a.byteLength(s, encoding)
    },
    encode(state, s) {
      state.start += b4a.write(state.buffer, s, state.start, encoding)
    },
    decode(state) {
      const s = b4a.toString(state.buffer, encoding, state.start)
      state.start = state.end
      return s
    }
  }
}

const utf8 = (exports.string = exports.utf8 = string('utf-8'))
exports.ascii = string('ascii')
exports.hex = string('hex')
exports.base64 = string('base64')
exports.ucs2 = exports.utf16le = string('utf16le')

exports.array = function array(enc) {
  return {
    preencode(state, list) {
      for (const value of list) enc.preencode(state, value)
    },
    encode(state, list) {
      for (const value of list) enc.encode(state, value)
    },
    decode(state) {
      const arr = []
      while (state.start < state.end) arr.push(enc.decode(state))
      return arr
    }
  }
}

exports.json = {
  preencode(state, v) {
    utf8.preencode(state, JSON.stringify(v))
  },
  encode(state, v) {
    utf8.encode(state, JSON.stringify(v))
  },
  decode(state) {
    return JSON.parse(utf8.decode(state))
  }
}

exports.ndjson = {
  preencode(state, v) {
    utf8.preencode(state, JSON.stringify(v) + '\n')
  },
  encode(state, v) {
    utf8.encode(state, JSON.stringify(v) + '\n')
  },
  decode(state) {
    return JSON.parse(utf8.decode(state))
  }
}
module.exports = require('bare-events')
{
  "name": "events-universal",
  "version": "1.0.1",
  "description": "Universal wrapper for the Node.js events module",
  "exports": {
    "./package": "./package.json",
    ".": {
      "bare": "./bare.js",
      "react-native": "./react-native.js",
      "default": "./default.js"
    }
  },
  "files": [
    "index.js",
    "default.js",
    "bare.js",
    "react-native.js"
  ],
  "scripts": {
    "test": "prettier . --check"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/events-universal.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/events-universal/issues"
  },
  "homepage": "https://github.com/holepunchto/events-universal#readme",
  "dependencies": {
    "bare-events": "^2.7.0"
  },
  "devDependencies": {
    "prettier": "^3.4.2",
    "prettier-config-holepunch": "^1.0.0"
  }
}
module.exports = class FixedFIFO {
  constructor (hwm) {
    if (!(hwm > 0) || ((hwm - 1) & hwm) !== 0) throw new Error('Max size for a FixedFIFO should be a power of two')
    this.buffer = new Array(hwm)
    this.mask = hwm - 1
    this.top = 0
    this.btm = 0
    this.next = null
  }

  clear () {
    this.top = this.btm = 0
    this.next = null
    this.buffer.fill(undefined)
  }

  push (data) {
    if (this.buffer[this.top] !== undefined) return false
    this.buffer[this.top] = data
    this.top = (this.top + 1) & this.mask
    return true
  }

  shift () {
    const last = this.buffer[this.btm]
    if (last === undefined) return undefined
    this.buffer[this.btm] = undefined
    this.btm = (this.btm + 1) & this.mask
    return last
  }

  peek () {
    return this.buffer[this.btm]
  }

  isEmpty () {
    return this.buffer[this.btm] === undefined
  }
}
const FixedFIFO = require('./fixed-size')

module.exports = class FastFIFO {
  constructor (hwm) {
    this.hwm = hwm || 16
    this.head = new FixedFIFO(this.hwm)
    this.tail = this.head
    this.length = 0
  }

  clear () {
    this.head = this.tail
    this.head.clear()
    this.length = 0
  }

  push (val) {
    this.length++
    if (!this.head.push(val)) {
      const prev = this.head
      this.head = prev.next = new FixedFIFO(2 * this.head.buffer.length)
      this.head.push(val)
    }
  }

  shift () {
    if (this.length !== 0) this.length--
    const val = this.tail.shift()
    if (val === undefined && this.tail.next) {
      const next = this.tail.next
      this.tail.next = null
      this.tail = next
      return this.tail.shift()
    }

    return val
  }

  peek () {
    const val = this.tail.peek()
    if (val === undefined && this.tail.next) return this.tail.next.peek()
    return val
  }

  isEmpty () {
    return this.length === 0
  }
}
{
  "name": "fast-fifo",
  "version": "1.3.2",
  "description": "A fast fifo implementation similar to the one powering nextTick in Node.js core",
  "main": "index.js",
  "files": [
    "./index.js",
    "./fixed-size.js"
  ],
  "dependencies": {},
  "devDependencies": {
    "standard": "^17.1.0",
    "brittle": "^3.3.2"
  },
  "scripts": {
    "test": "standard && brittle test.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/mafintosh/fast-fifo.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/mafintosh/fast-fifo/issues"
  },
  "homepage": "https://github.com/mafintosh/fast-fifo"
}
const sodium = require('sodium-universal')
const c = require('compact-encoding')
const b4a = require('b4a')

// https://en.wikipedia.org/wiki/Merkle_tree#Second_preimage_attack
const LEAF_TYPE = b4a.from([0])
const PARENT_TYPE = b4a.from([1])
const ROOT_TYPE = b4a.from([2])

const HYPERCORE = b4a.from('hypercore')

exports.keyPair = function (seed) {
  // key pairs might stay around for a while, so better not to use a default slab to avoid retaining it completely
  const slab = b4a.allocUnsafeSlow(
    sodium.crypto_sign_PUBLICKEYBYTES + sodium.crypto_sign_SECRETKEYBYTES
  )
  const publicKey = slab.subarray(0, sodium.crypto_sign_PUBLICKEYBYTES)
  const secretKey = slab.subarray(sodium.crypto_sign_PUBLICKEYBYTES)

  if (seed) sodium.crypto_sign_seed_keypair(publicKey, secretKey, seed)
  else sodium.crypto_sign_keypair(publicKey, secretKey)

  return {
    publicKey,
    secretKey
  }
}

exports.validateKeyPair = function (keyPair) {
  const pk = b4a.allocUnsafe(sodium.crypto_sign_PUBLICKEYBYTES)
  sodium.crypto_sign_ed25519_sk_to_pk(pk, keyPair.secretKey)
  return b4a.equals(pk, keyPair.publicKey)
}

exports.sign = function (message, secretKey) {
  // Dedicated slab for the signature, to avoid retaining unneeded mem and for security
  const signature = b4a.allocUnsafeSlow(sodium.crypto_sign_BYTES)
  sodium.crypto_sign_detached(signature, message, secretKey)
  return signature
}

exports.verify = function (message, signature, publicKey) {
  if (signature.byteLength !== sodium.crypto_sign_BYTES) return false
  if (publicKey.byteLength !== sodium.crypto_sign_PUBLICKEYBYTES) return false
  return sodium.crypto_sign_verify_detached(signature, message, publicKey)
}

exports.encrypt = function (message, publicKey) {
  const ciphertext = b4a.alloc(message.byteLength + sodium.crypto_box_SEALBYTES)
  sodium.crypto_box_seal(ciphertext, message, publicKey)
  return ciphertext
}

exports.decrypt = function (ciphertext, keyPair) {
  if (ciphertext.byteLength < sodium.crypto_box_SEALBYTES) return null

  const plaintext = b4a.alloc(ciphertext.byteLength - sodium.crypto_box_SEALBYTES)

  if (!sodium.crypto_box_seal_open(plaintext, ciphertext, keyPair.publicKey, keyPair.secretKey)) {
    return null
  }

  return plaintext
}

exports.encryptionKeyPair = function (seed) {
  const publicKey = b4a.alloc(sodium.crypto_box_PUBLICKEYBYTES)
  const secretKey = b4a.alloc(sodium.crypto_box_SECRETKEYBYTES)

  if (seed) {
    sodium.crypto_box_seed_keypair(publicKey, secretKey, seed)
  } else {
    sodium.crypto_box_keypair(publicKey, secretKey)
  }

  return {
    publicKey,
    secretKey
  }
}

exports.data = function (data) {
  const out = b4a.allocUnsafe(32)

  sodium.crypto_generichash_batch(out, [LEAF_TYPE, c.encode(c.uint64, data.byteLength), data])

  return out
}

exports.parent = function (a, b) {
  if (a.index > b.index) {
    const tmp = a
    a = b
    b = tmp
  }

  const out = b4a.allocUnsafe(32)

  sodium.crypto_generichash_batch(out, [
    PARENT_TYPE,
    c.encode(c.uint64, a.size + b.size),
    a.hash,
    b.hash
  ])

  return out
}

exports.tree = function (roots, out) {
  const buffers = new Array(3 * roots.length + 1)
  let j = 0

  buffers[j++] = ROOT_TYPE

  for (let i = 0; i < roots.length; i++) {
    const r = roots[i]
    buffers[j++] = r.hash
    buffers[j++] = c.encode(c.uint64, r.index)
    buffers[j++] = c.encode(c.uint64, r.size)
  }

  if (!out) out = b4a.allocUnsafe(32)
  sodium.crypto_generichash_batch(out, buffers)
  return out
}

exports.hash = function (data, out) {
  if (!out) out = b4a.allocUnsafe(32)
  if (!Array.isArray(data)) data = [data]

  sodium.crypto_generichash_batch(out, data)

  return out
}

exports.randomBytes = function (n) {
  const buf = b4a.allocUnsafe(n)
  sodium.randombytes_buf(buf)
  return buf
}

exports.discoveryKey = function (key) {
  if (!key || key.byteLength !== 32) throw new Error('Must pass a 32 byte buffer')
  // Discovery keys might stay around for a while, so better not to use slab memory (for better gc)
  const digest = b4a.allocUnsafeSlow(32)
  sodium.crypto_generichash(digest, HYPERCORE, key)
  return digest
}

if (sodium.sodium_free) {
  exports.free = function (secureBuf) {
    if (secureBuf.secure) sodium.sodium_free(secureBuf)
  }
} else {
  exports.free = function () {}
}

exports.namespace = function (name, count) {
  const ids = typeof count === 'number' ? range(count) : count

  // Namespaces are long-lived, so better to use a dedicated slab
  const buf = b4a.allocUnsafeSlow(32 * ids.length)

  const list = new Array(ids.length)

  // ns is ephemeral, so default slab
  const ns = b4a.allocUnsafe(33)
  sodium.crypto_generichash(ns.subarray(0, 32), typeof name === 'string' ? b4a.from(name) : name)

  for (let i = 0; i < list.length; i++) {
    list[i] = buf.subarray(32 * i, 32 * i + 32)
    ns[32] = ids[i]
    sodium.crypto_generichash(list[i], ns)
  }

  return list
}

function range(count) {
  const arr = new Array(count)
  for (let i = 0; i < count; i++) arr[i] = i
  return arr
}
{
  "name": "hypercore-crypto",
  "version": "3.7.0",
  "description": "The crypto primitives used in hypercore, extracted into a separate module",
  "main": "index.js",
  "files": [
    "index.js"
  ],
  "dependencies": {
    "b4a": "^1.6.6",
    "compact-encoding": "^3.0.0",
    "sodium-universal": "^5.0.0"
  },
  "devDependencies": {
    "brittle": "^3.5.0",
    "lunte": "^1.0.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^2.0.0"
  },
  "scripts": {
    "format": "prettier --write .",
    "lint": "prettier --check . && lunte",
    "test": "npm run test:node && npm run test:bare",
    "test:node": "brittle-node test.js",
    "test:bare": "brittle-bare test.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/mafintosh/hypercore-crypto.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/mafintosh/hypercore-crypto/issues"
  },
  "homepage": "https://github.com/mafintosh/hypercore-crypto"
}
module.exports = require.addon.bind(require)
{
  "name": "require-addon",
  "version": "1.2.0",
  "description": "Import native addons across JavaScript runtimes",
  "exports": {
    "./package": "./package.json",
    ".": {
      "bare": "./lib/bare.js",
      "node": "./lib/node.js",
      "default": "./lib/default.js"
    }
  },
  "imports": {
    "fs": {
      "bare": "bare-fs",
      "default": "fs"
    },
    "path": {
      "bare": "bare-path",
      "default": "path"
    },
    "url": {
      "bare": "bare-url",
      "default": "url"
    }
  },
  "files": [
    "lib"
  ],
  "scripts": {
    "test": "npm run lint && npm run test:bare && npm run test:node",
    "test:bare": "bare test.js",
    "test:node": "node test.js",
    "lint": "prettier --check .",
    "format": "prettier --write ."
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/require-addon.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/require-addon/issues"
  },
  "homepage": "https://github.com/holepunchto/require-addon#readme",
  "engines": {
    "bare": ">=1.10.0"
  },
  "dependencies": {
    "bare-addon-resolve": "^1.3.0"
  },
  "devDependencies": {
    "bare-bundle": "^1.8.1",
    "bare-bundle-evaluate": "^1.1.0",
    "bare-fs": "^4.0.0",
    "bare-path": "^3.0.0",
    "bare-url": "^2.1.0",
    "brittle": "^3.7.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^2.0.0"
  }
}
require.addon = require('require-addon')
module.exports = require.addon('.', __filename)
const assert = require('assert')
const binding = require('./binding')
const { isNode } = require('which-runtime')

const OPTIONAL = Buffer.from(new ArrayBuffer(0))

module.exports = exports = { ...binding }

// memory

exports.sodium_memzero = function (buf) {
  assert(ArrayBuffer.isView(buf), 'buf must be a typed array')

  binding.sodium_memzero(buf)
}

exports.sodium_mlock = function (buf) {
  assert(ArrayBuffer.isView(buf), 'buf must be a typed array')

  const res = binding.sodium_mlock(buf)

  if (res !== 0) throw new Error('memory lock failed')
}

exports.sodium_munlock = function (buf) {
  assert(ArrayBuffer.isView(buf), 'buf must be a typed array')

  const res = binding.sodium_munlock(buf)

  if (res !== 0) throw new Error('memory unlock failed')
}

exports.sodium_malloc = function (size) {
  assert(size >= 0, 'invalid size')

  const buf = Buffer.from(binding.sodium_malloc(size))
  buf.secure = true
  return buf
}

exports.sodium_free = function (buf) {
  if (!buf || !buf.secure) return

  binding.sodium_free(buf.buffer)
}

exports.sodium_mprotect_noaccess = function (buf) {
  const res = binding.sodium_mprotect_noaccess(buf.buffer)

  if (res !== 0) throw new Error('failed to lock buffer')
}

exports.sodium_mprotect_readonly = function (buf) {
  const res = binding.sodium_mprotect_readonly(buf.buffer)

  if (res !== 0) throw new Error('failed to unlock buffer')
}

exports.sodium_mprotect_readwrite = function (buf) {
  const res = binding.sodium_mprotect_readwrite(buf.buffer)

  if (res !== 0) throw new Error('failed to unlock buffer')
}

// crypto_randombytes

exports.randombytes_buf = function (buffer) {
  assert(ArrayBuffer.isView(buffer), 'buffer must be a typed array')

  binding.randombytes_buf(buffer.buffer, buffer.byteOffset, buffer.byteLength)
}

exports.randombytes_buf_deterministic = function (buffer, seed) {
  assert(ArrayBuffer.isView(buffer), 'buffer must be a typed array')
  assert(ArrayBuffer.isView(seed), 'seed must be a typed array')
  assert(
    seed.byteLength === binding.randombytes_SEEDBYTES,
    "seed must be 'randombytes_SEEDBYTES' bytes"
  )

  binding.randombytes_buf_deterministic(
    buffer.buffer,
    buffer.byteOffset,
    buffer.byteLength,

    seed.buffer,
    seed.byteOffset,
    seed.byteLength
  )
}

// sodium_helpers

exports.sodium_memcmp = function (a, b) {
  assert(ArrayBuffer.isView(a), 'a must be a typed array')
  assert(ArrayBuffer.isView(b), 'b must be a typed array')
  assert(a.byteLength === b.byteLength, 'buffers must be of same length')

  return binding.sodium_memcmp(a, b)
}

exports.sodium_add = function (a, b) {
  assert(ArrayBuffer.isView(a), 'a must be a typed array')
  assert(ArrayBuffer.isView(b), 'b must be a typed array')
  assert(a.byteLength === b.byteLength, 'buffers must be of same length')

  binding.sodium_add(a, b)
}

exports.sodium_sub = function (a, b) {
  assert(ArrayBuffer.isView(a), 'a must be a typed array')
  assert(ArrayBuffer.isView(b), 'b must be a typed array')
  assert(a.byteLength === b.byteLength, 'buffers must be of same length')

  binding.sodium_sub(a, b)
}

exports.sodium_compare = function (a, b) {
  assert(ArrayBuffer.isView(a), 'a must be a typed array')
  assert(ArrayBuffer.isView(b), 'b must be a typed array')
  assert(a.byteLength === b.byteLength, 'buffers must be of same length')

  return binding.sodium_compare(a, b)
}

exports.sodium_is_zero = function (buffer, length) {
  if (length === undefined) length = buffer.byteLength

  assert(ArrayBuffer.isView(buffer), 'buffer must be a typed array')
  assert(length >= 0 && length <= buffer.byteLength, 'invalid length')

  return binding.sodium_is_zero(buffer, length)
}

exports.sodium_pad = function (buffer, unpaddedBuflen, blockSize) {
  assert(ArrayBuffer.isView(buffer), 'buffer must be a typed array')
  assert(unpaddedBuflen <= buffer.byteLength, 'unpadded length cannot exceed buffer length')
  assert(blockSize <= buffer.byteLength, 'block size cannot exceed buffer length')
  assert(blockSize >= 1, 'block size must be at least 1 byte')
  assert(
    buffer.byteLength >= unpaddedBuflen + (blockSize - (unpaddedBuflen % blockSize)),
    'buf not long enough'
  )

  return binding.sodium_pad(buffer, unpaddedBuflen, blockSize)
}

exports.sodium_unpad = function (buffer, paddedBuflen, blockSize) {
  assert(ArrayBuffer.isView(buffer), 'buffer must be a typed array')
  assert(paddedBuflen <= buffer.byteLength, 'unpadded length cannot exceed buffer length')
  assert(blockSize <= buffer.byteLength, 'block size cannot exceed buffer length')
  assert(blockSize >= 1, 'block size must be at least 1 byte')

  return binding.sodium_unpad(buffer, paddedBuflen, blockSize)
}

// crypto_sign

exports.crypto_sign_keypair = function (pk, sk) {
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(
    pk.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
    "pk must be 'crypto_sign_PUBLICKEYBYTES' bytes"
  )
  assert(
    sk.byteLength === binding.crypto_sign_SECRETKEYBYTES,
    "sk must be 'crypto_sign_SECRETKEYBYTES' bytes"
  )

  const res = binding.crypto_sign_keypair(pk, sk)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_sign_seed_keypair = function (pk, sk, seed) {
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(ArrayBuffer.isView(seed), 'seed must be a typed array')
  assert(
    pk.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
    "pk must be 'crypto_sign_PUBLICKEYBYTES' bytes"
  )
  assert(
    sk.byteLength === binding.crypto_sign_SECRETKEYBYTES,
    "sk must be 'crypto_sign_SECRETKEYBYTES' bytes"
  )
  assert(
    seed.byteLength === binding.crypto_sign_SEEDBYTES,
    "seed must be 'crypto_sign_SEEDBYTES' bytes"
  )

  const res = binding.crypto_sign_seed_keypair(pk, sk, seed)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_sign = function (sm, m, sk) {
  assert(ArrayBuffer.isView(sm), 'sm must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(
    sm.byteLength === binding.crypto_sign_BYTES + m.byteLength,
    "sm must be 'm.byteLength + crypto_sign_BYTES' bytes"
  )
  assert(
    sk.byteLength === binding.crypto_sign_SECRETKEYBYTES,
    "sk must be 'crypto_sign_SECRETKEYBYTES' bytes"
  )

  const res = binding.crypto_sign(sm, m, sk)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_sign_open = function (m, sm, pk) {
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(sm), 'sm must be a typed array')
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(
    sm.byteLength >= binding.crypto_sign_BYTES,
    "sm must be at least 'crypto_sign_BYTES' bytes"
  )
  assert(
    m.byteLength === sm.byteLength - binding.crypto_sign_BYTES,
    "m must be 'sm.byteLength - crypto_sign_BYTES' bytes"
  )
  assert(
    pk.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
    "pk must be 'crypto_sign_PUBLICKEYBYTES' bytes"
  )

  const res = binding.crypto_sign_open(m, sm, pk)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_sign_open = function (m, sm, pk) {
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(sm), 'sm must be a typed array')
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(
    sm.byteLength >= binding.crypto_sign_BYTES,
    "sm must be at least 'crypto_sign_BYTES' bytes"
  )
  assert(
    m.byteLength === sm.byteLength - binding.crypto_sign_BYTES,
    "m must be 'sm.byteLength - crypto_sign_BYTES' bytes"
  )
  assert(
    pk.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
    "pk must be 'crypto_sign_PUBLICKEYBYTES' bytes"
  )

  return binding.crypto_sign_open(m, sm, pk)
}

exports.crypto_sign_detached = function (sig, m, sk) {
  assert(ArrayBuffer.isView(sig), 'sig must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(sig.byteLength === binding.crypto_sign_BYTES, "sig must be 'crypto_sign_BYTES' bytes")
  assert(
    sk.byteLength === binding.crypto_sign_SECRETKEYBYTES,
    "sk must be 'crypto_sign_SECRETKEYBYTES' bytes"
  )

  const res = binding.crypto_sign_detached(sig, m, sk)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_sign_verify_detached = function (sig, m, pk) {
  assert(ArrayBuffer.isView(sig), 'sig must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(
    sig.byteLength >= binding.crypto_sign_BYTES,
    "sig must be at least 'crypto_sign_BYTES' bytes"
  )
  assert(
    pk.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
    "pk must be 'crypto_sign_PUBLICKEYBYTES' bytes"
  )

  return binding.crypto_sign_verify_detached(
    sig.buffer,
    sig.byteOffset,
    sig.byteLength,

    m.buffer,
    m.byteOffset,
    m.byteLength,

    pk.buffer,
    pk.byteOffset,
    pk.byteLength
  )
}

exports.crypto_sign_ed25519_sk_to_pk = function (pk, sk) {
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(
    pk.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
    "pk must be 'crypto_sign_PUBLICKEYBYTES' bytes"
  )
  assert(
    sk.byteLength === binding.crypto_sign_SECRETKEYBYTES,
    "sk must be 'crypto_sign_SECRETKEYBYTES' bytes"
  )

  const res = binding.crypto_sign_ed25519_sk_to_pk(pk, sk)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_sign_ed25519_pk_to_curve25519 = function (x25519pk, ed25519pk) {
  assert(ArrayBuffer.isView(x25519pk), 'x25519pk must be a typed array')
  assert(ArrayBuffer.isView(ed25519pk), 'ed25519pk must be a typed array')
  assert(
    x25519pk.byteLength === binding.crypto_box_PUBLICKEYBYTES,
    "x25519pk must be 'crypto_box_PUBLICKEYBYTES' bytes"
  )
  assert(
    ed25519pk.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
    "ed25519pk must be 'crypto_sign_PUBLICKEYBYTES' bytes"
  )

  const res = binding.crypto_sign_ed25519_pk_to_curve25519(x25519pk, ed25519pk)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_sign_ed25519_sk_to_curve25519 = function (x25519sk, ed25519sk) {
  assert(ArrayBuffer.isView(x25519sk), 'x25519sk must be a typed array')
  assert(ArrayBuffer.isView(ed25519sk), 'ed25519sk must be a typed array')
  assert(
    x25519sk.byteLength === binding.crypto_box_SECRETKEYBYTES,
    "x25519sk must be 'crypto_box_SECRETKEYBYTES' bytes"
  )

  const edLen = ed25519sk.byteLength

  assert(
    edLen === binding.crypto_sign_SECRETKEYBYTES || edLen === binding.crypto_box_SECRETKEYBYTES,
    "ed25519sk must be 'crypto_sign_SECRETKEYBYTES' or 'crypto_sign_SECRETKEYBYTES - crypto_sign_PUBLICKEYBYTES' bytes"
  )

  const res = binding.crypto_sign_ed25519_sk_to_curve25519(x25519sk, ed25519sk)

  if (res !== 0) throw new Error('status: ' + res)
}

// crypto_box

exports.crypto_box_keypair = function (pk, sk) {
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(
    pk.byteLength === binding.crypto_box_PUBLICKEYBYTES,
    "pk must be 'crypto_box_PUBLICKEYBYTES' bytes"
  )
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(
    sk.byteLength === binding.crypto_box_SECRETKEYBYTES,
    "sk must be 'crypto_box_SECRETKEYBYTES' bytes"
  )

  const res = binding.crypto_box_keypair(pk, sk)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_box_seed_keypair = function (pk, sk, seed) {
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(
    pk.byteLength === binding.crypto_box_PUBLICKEYBYTES,
    "pk must be 'crypto_box_PUBLICKEYBYTES' bytes"
  )
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(
    sk.byteLength === binding.crypto_box_SECRETKEYBYTES,
    "sk must be 'crypto_box_SECRETKEYBYTES' bytes"
  )
  assert(ArrayBuffer.isView(seed), 'seed must be a typed array')
  assert(
    seed.byteLength === binding.crypto_box_SEEDBYTES,
    "seed must be 'crypto_box_SEEDBYTES' bytes"
  )

  const res = binding.crypto_box_seed_keypair(pk, sk, seed)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_box_easy = function (c, m, n, pk, sk) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(
    c.byteLength === m.byteLength + exports.crypto_box_MACBYTES,
    "c must be 'm.byteLength + crypto_box_MACBYTES' bytes"
  )
  assert(n.byteLength === exports.crypto_box_NONCEBYTES, "n must be 'crypto_box_NONCEBYTES' bytes")
  assert(
    pk.byteLength === exports.crypto_box_PUBLICKEYBYTES,
    "pk must be 'crypto_box_PUBLICKEYBYTES' bytes"
  )
  assert(
    sk.byteLength === exports.crypto_box_SECRETKEYBYTES,
    "sk must be 'crypto_box_SECRETKEYBYTES' bytes"
  )

  const res = binding.crypto_box_easy(c, m, n, pk, sk)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_box_detached = function (c, mac, m, n, pk, sk) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(mac), 'mac must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(mac.byteLength === exports.crypto_box_MACBYTES, "mac must be 'crypto_box_MACBYTES' bytes")
  assert(n.byteLength === exports.crypto_box_NONCEBYTES, "n must be 'crypto_box_NONCEBYTES' bytes")
  assert(
    pk.byteLength === exports.crypto_box_PUBLICKEYBYTES,
    "pk must be 'crypto_box_PUBLICKEYBYTES' bytes"
  )
  assert(
    sk.byteLength === exports.crypto_box_SECRETKEYBYTES,
    "sk must be 'crypto_box_SECRETKEYBYTES' bytes"
  )

  const res = binding.crypto_box_detached(c, mac, m, n, pk, sk)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_box_open_easy = function (m, c, n, pk, sk) {
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(
    c.byteLength >= exports.crypto_box_MACBYTES,
    "c must be at least 'crypto_box_MACBYTES' bytes"
  )
  assert(
    m.byteLength === c.byteLength - exports.crypto_box_MACBYTES,
    "m must be 'c.byteLength - crypto_box_MACBYTES' bytes"
  )
  assert(n.byteLength === exports.crypto_box_NONCEBYTES, "n must be 'crypto_box_NONCEBYTES' bytes")
  assert(
    pk.byteLength === exports.crypto_box_PUBLICKEYBYTES,
    "pk must be 'crypto_box_PUBLICKEYBYTES' bytes"
  )
  assert(
    sk.byteLength === exports.crypto_box_SECRETKEYBYTES,
    "sk must be 'crypto_box_SECRETKEYBYTES' bytes"
  )

  return binding.crypto_box_open_easy(m, c, n, pk, sk)
}

exports.crypto_box_open_detached = function (m, c, mac, n, pk, sk) {
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(mac), 'mac must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(m.byteLength === c.byteLength, "m must be 'c.byteLength' bytes")
  assert(mac.byteLength === exports.crypto_box_MACBYTES, "mac must be 'crypto_box_MACBYTES' bytes")
  assert(n.byteLength === exports.crypto_box_NONCEBYTES, "n must be 'crypto_box_NONCEBYTES' bytes")
  assert(
    pk.byteLength === exports.crypto_box_PUBLICKEYBYTES,
    "pk must be 'crypto_box_PUBLICKEYBYTES' bytes"
  )
  assert(
    sk.byteLength === exports.crypto_box_SECRETKEYBYTES,
    "sk must be 'crypto_box_SECRETKEYBYTES' bytes"
  )

  return binding.crypto_box_open_detached(m, c, mac, n, pk, sk)
}

exports.crypto_box_seal = function (c, m, pk) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(
    c.byteLength === m.byteLength + exports.crypto_box_SEALBYTES,
    "c must be 'm.byteLength + crypto_box_SEALBYTES' bytes"
  )
  assert(
    pk.byteLength === exports.crypto_box_PUBLICKEYBYTES,
    "pk must be 'crypto_box_PUBLICKEYBYTES' bytes"
  )

  const res = binding.crypto_box_seal(c, m, pk)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_box_seal_open = function (m, c, pk, sk) {
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(
    c.byteLength >= exports.crypto_box_SEALBYTES,
    "c must be at least 'crypto_box_SEALBYTES' bytes"
  )
  assert(
    m.byteLength === c.byteLength - exports.crypto_box_SEALBYTES,
    "m must be 'c.byteLength - crypto_box_SEALBYTES' bytes"
  )
  assert(
    pk.byteLength === exports.crypto_box_PUBLICKEYBYTES,
    "pk must be 'crypto_box_PUBLICKEYBYTES' bytes"
  )
  assert(
    sk.byteLength === exports.crypto_box_SECRETKEYBYTES,
    "sk must be 'crypto_box_SECRETKEYBYTES' bytes"
  )

  return binding.crypto_box_seal_open(
    m.buffer,
    m.byteOffset,
    m.byteLength,

    c.buffer,
    c.byteOffset,
    c.byteLength,

    pk.buffer,
    pk.byteOffset,
    pk.byteLength,

    sk.buffer,
    sk.byteOffset,
    sk.byteLength
  )
}

// crypto_secretbox

exports.crypto_secretbox_easy = function (c, m, n, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    c.byteLength === m.byteLength + binding.crypto_secretbox_MACBYTES,
    "c must be 'm.byteLength + crypto_secretbox_MACBYTES' bytes"
  )
  assert(
    n.byteLength === binding.crypto_secretbox_NONCEBYTES,
    "n must be 'crypto_secretbox_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_secretbox_KEYBYTES,
    "k must be 'crypto_secretbox_KEYBYTES' bytes"
  )

  const res = binding.crypto_secretbox_easy(c, m, n, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_secretbox_open_easy = function (m, c, n, k) {
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    c.byteLength >= binding.crypto_secretbox_MACBYTES,
    "c must be at least 'crypto_secretbox_MACBYTES' bytes"
  )
  assert(
    m.byteLength === c.byteLength - binding.crypto_secretbox_MACBYTES,
    "m must be 'c.byteLength - crypto_secretbox_MACBYTES' bytes"
  )
  assert(
    n.byteLength === binding.crypto_secretbox_NONCEBYTES,
    "n must be 'crypto_secretbox_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_secretbox_KEYBYTES,
    "k must be 'crypto_secretbox_KEYBYTES' bytes"
  )

  return binding.crypto_secretbox_open_easy(m, c, n, k)
}

exports.crypto_secretbox_detached = function (c, mac, m, n, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(mac), 'mac must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(
    mac.byteLength === binding.crypto_secretbox_MACBYTES,
    "mac must be 'crypto_secretbox_MACBYTES' bytes"
  )
  assert(
    n.byteLength === binding.crypto_secretbox_NONCEBYTES,
    "n must be 'crypto_secretbox_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_secretbox_KEYBYTES,
    "k must be 'crypto_secretbox_KEYBYTES' bytes"
  )

  const res = binding.crypto_secretbox_detached(c, mac, m, n, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_secretbox_open_detached = function (m, c, mac, n, k) {
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(mac), 'mac must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(m.byteLength === c.byteLength, "m must be 'c.byteLength' bytes")
  assert(
    mac.byteLength === binding.crypto_secretbox_MACBYTES,
    "mac must be 'crypto_secretbox_MACBYTES' bytes"
  )
  assert(
    n.byteLength === binding.crypto_secretbox_NONCEBYTES,
    "n must be 'crypto_secretbox_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_secretbox_KEYBYTES,
    "k must be 'crypto_secretbox_KEYBYTES' bytes"
  )

  return binding.crypto_secretbox_open_detached(m, c, mac, n, k)
}

// crypto_generichash

exports.crypto_generichash = function (output, input, key) {
  if (!key) key = OPTIONAL

  assert(ArrayBuffer.isView(output), 'output must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(
    output.byteLength >= binding.crypto_generichash_BYTES_MIN &&
      output.byteLength <= binding.crypto_generichash_BYTES_MAX,
    'output must be between crypto_generichash_BYTES_MIN and crypto_generichash_BYTES_MAX bytes'
  )

  if (key !== OPTIONAL) {
    assert(ArrayBuffer.isView(key), 'key must be a typed array')
    assert(
      key.byteLength >= binding.crypto_generichash_KEYBYTES_MIN &&
        key.byteLength <= binding.crypto_generichash_KEYBYTES_MAX,
      'key must be between crypto_generichash_KEYBYTES_MIN and crypto_generichash_KEYBYTES_MAX bytes'
    )
  }

  const res = binding.crypto_generichash(
    output.buffer,
    output.byteOffset,
    output.byteLength,

    input.buffer,
    input.byteOffset,
    input.byteLength,

    key.buffer,
    key.byteOffset,
    key.byteLength
  )

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_generichash_batch = function (output, batch, key) {
  assert(ArrayBuffer.isView(output), 'output must be a typed array')

  if (isNode || batch.length < 4) {
    const res = binding.crypto_generichash_batch(output, batch, !!key, key || OPTIONAL)
    if (res !== 0) throw new Error('status: ' + res)
  } else {
    const state = Buffer.alloc(binding.crypto_generichash_STATEBYTES)

    exports.crypto_generichash_init(state, key, output.byteLength)

    for (const buf of batch) {
      exports.crypto_generichash_update(state, buf)
    }

    exports.crypto_generichash_final(state, output)
  }
}

exports.crypto_generichash_keygen = function (key) {
  assert(ArrayBuffer.isView(key), 'key must be a typed array')
  assert(
    key.byteLength === binding.crypto_generichash_KEYBYTES,
    "key must be 'crypto_generichash_KEYBYTES' bytes"
  )

  const res = binding.crypto_generichash_keygen(key.buffer, key.byteOffset, key.byteLength)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_generichash_init = function (state, key, outputLength) {
  if (!key) key = OPTIONAL

  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_generichash_STATEBYTES,
    "state must be 'crypto_generichash_STATEBYTES' bytes"
  )

  const res = binding.crypto_generichash_init(
    state.buffer,
    state.byteOffset,
    state.byteLength,

    key.buffer,
    key.byteOffset,
    key.byteLength,

    outputLength
  )

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_generichash_update = function (state, input) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(
    state.byteLength === binding.crypto_generichash_STATEBYTES,
    "state must be 'crypto_generichash_STATEBYTES' bytes"
  )

  const res = binding.crypto_generichash_update(
    state.buffer,
    state.byteOffset,
    state.byteLength,

    input.buffer,
    input.byteOffset,
    input.byteLength
  )

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_generichash_final = function (state, output) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(ArrayBuffer.isView(output), 'output must be a typed array')
  assert(
    state.byteLength === binding.crypto_generichash_STATEBYTES,
    "state must be 'crypto_generichash_STATEBYTES' bytes"
  )

  const res = binding.crypto_generichash_final(
    state.buffer,
    state.byteOffset,
    state.byteLength,

    output.buffer,
    output.byteOffset,
    output.byteLength
  )

  if (res !== 0) throw new Error('status: ' + res)
}

// secretstream

exports.crypto_secretstream_xchacha20poly1305_keygen = function (k) {
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_secretstream_xchacha20poly1305_KEYBYTES,
    "k must be 'crypto_secretstream_xchacha20poly1305_KEYBYTES' bytes"
  )

  binding.crypto_secretstream_xchacha20poly1305_keygen(k.buffer, k.byteOffset, k.byteLength)
}

exports.crypto_secretstream_xchacha20poly1305_init_push = function (state, header, k) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(ArrayBuffer.isView(header), 'header must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    state.byteLength === binding.crypto_secretstream_xchacha20poly1305_STATEBYTES,
    "state must be 'crypto_secretstream_xchacha20poly1305_STATEBYTES' bytes"
  )
  assert(
    header.byteLength === binding.crypto_secretstream_xchacha20poly1305_HEADERBYTES,
    "header must be 'crypto_secretstream_xchacha20poly1305_HEADERBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_secretstream_xchacha20poly1305_KEYBYTES,
    "k must be 'crypto_secretstream_xchacha20poly1305_KEYBYTES' bytes"
  )

  const res = binding.crypto_secretstream_xchacha20poly1305_init_push(
    state.buffer,
    state.byteOffset,
    state.byteLength,

    header.buffer,
    header.byteOffset,
    header.byteLength,

    k.buffer,
    k.byteOffset,
    k.byteLength
  )

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_secretstream_xchacha20poly1305_init_pull = function (state, header, k) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(ArrayBuffer.isView(header), 'header must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    state.byteLength === binding.crypto_secretstream_xchacha20poly1305_STATEBYTES,
    "state must be 'crypto_secretstream_xchacha20poly1305_STATEBYTES' bytes"
  )
  assert(
    header.byteLength === binding.crypto_secretstream_xchacha20poly1305_HEADERBYTES,
    "header must be 'crypto_secretstream_xchacha20poly1305_HEADERBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_secretstream_xchacha20poly1305_KEYBYTES,
    "k must be 'crypto_secretstream_xchacha20poly1305_KEYBYTES' bytes"
  )

  const res = binding.crypto_secretstream_xchacha20poly1305_init_pull(
    state.buffer,
    state.byteOffset,
    state.byteLength,

    header.buffer,
    header.byteOffset,
    header.byteLength,

    k.buffer,
    k.byteOffset,
    k.byteLength
  )

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_secretstream_xchacha20poly1305_push = function (state, c, m, ad, tag) {
  if (!ad) ad = OPTIONAL

  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(
    state.byteLength === binding.crypto_secretstream_xchacha20poly1305_STATEBYTES,
    "state must be 'crypto_secretstream_xchacha20poly1305_STATEBYTES' bytes"
  )
  assert(
    c.byteLength === m.byteLength + binding.crypto_secretstream_xchacha20poly1305_ABYTES,
    "c must be 'm.byteLength + crypto_secretstream_xchacha20poly1305_ABYTES' bytes"
  )

  const res = binding.crypto_secretstream_xchacha20poly1305_push(
    state.buffer,
    state.byteOffset,
    state.byteLength,

    c.buffer,
    c.byteOffset,
    c.byteLength,

    m.buffer,
    m.byteOffset,
    m.byteLength,

    ad.buffer,
    ad.byteOffset,
    ad.byteLength,

    tag
  )

  if (res < 0) throw new Error('push failed')

  return res
}

exports.crypto_secretstream_xchacha20poly1305_pull = function (state, m, tag, c, ad) {
  if (!ad) ad = OPTIONAL

  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_secretstream_xchacha20poly1305_STATEBYTES,
    "state must be 'crypto_secretstream_xchacha20poly1305_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(tag), 'tag must be a typed array')
  assert(tag.byteLength === 1, 'tag must be 1 byte')

  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(
    c.byteLength >= binding.crypto_secretstream_xchacha20poly1305_ABYTES,
    "c must be at least 'crypto_secretstream_xchacha20poly1305_ABYTES' bytes"
  )
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(
    m.byteLength === c.byteLength - binding.crypto_secretstream_xchacha20poly1305_ABYTES,
    "m must be 'c.byteLength - crypto_secretstream_xchacha20poly1305_ABYTES' bytes"
  )

  const res = binding.crypto_secretstream_xchacha20poly1305_pull(
    state.buffer,
    state.byteOffset,
    state.byteLength,

    m.buffer,
    m.byteOffset,
    m.byteLength,

    tag.buffer,
    tag.byteOffset,
    tag.byteLength,

    c.buffer,
    c.byteOffset,
    c.byteLength,

    ad.buffer,
    ad.byteOffset,
    ad.byteLength
  )

  if (res < 0) throw new Error('pull failed')

  return res
}

exports.crypto_secretstream_xchacha20poly1305_rekey = function (state) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_secretstream_xchacha20poly1305_STATEBYTES,
    "state must be 'crypto_secretstream_xchacha20poly1305_STATEBYTES' bytes"
  )

  binding.crypto_secretstream_xchacha20poly1305_rekey(
    state.buffer,
    state.byteOffset,
    state.byteLength
  )
}

// crypto_stream

exports.crypto_stream = function (c, n, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    n.byteLength === binding.crypto_stream_NONCEBYTES,
    "n must be 'crypto_stream_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_KEYBYTES,
    "k must be 'crypto_stream_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream(c, n, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_xor = function (c, m, n, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(
    n.byteLength === binding.crypto_stream_NONCEBYTES,
    "n must be 'crypto_stream_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_KEYBYTES,
    "k must be 'crypto_stream_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_xor(
    c.buffer,
    c.byteOffset,
    c.byteLength,

    m.buffer,
    m.byteOffset,
    m.byteLength,

    n.buffer,
    n.byteOffset,
    n.byteLength,

    k.buffer,
    k.byteOffset,
    k.byteLength
  )

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_chacha20 = function (c, n, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    n.byteLength === binding.crypto_stream_chacha20_NONCEBYTES,
    "n must be 'crypto_stream_chacha20_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_chacha20_KEYBYTES,
    "k must be 'crypto_stream_chacha20_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_chacha20(c, n, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_chacha20_xor = function (c, m, n, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(
    n.byteLength === binding.crypto_stream_chacha20_NONCEBYTES,
    "n must be 'crypto_stream_chacha20_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_chacha20_KEYBYTES,
    "k must be 'crypto_stream_chacha20_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_chacha20_xor(c, m, n, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_chacha20_xor_ic = function (c, m, n, ic, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(
    n.byteLength === binding.crypto_stream_chacha20_NONCEBYTES,
    "n must be 'crypto_stream_chacha20_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_chacha20_KEYBYTES,
    "k must be 'crypto_stream_chacha20_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_chacha20_xor_ic(c, m, n, ic, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_chacha20_ietf = function (c, n, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    n.byteLength === binding.crypto_stream_chacha20_ietf_NONCEBYTES,
    "n must be 'crypto_stream_chacha20_ietf_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_chacha20_ietf_KEYBYTES,
    "k must be 'crypto_stream_chacha20_ietf_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_chacha20_ietf(c, n, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_chacha20_ietf_xor = function (c, m, n, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(
    n.byteLength === binding.crypto_stream_chacha20_ietf_NONCEBYTES,
    "n must be 'crypto_stream_chacha20_ietf_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_chacha20_ietf_KEYBYTES,
    "k must be 'crypto_stream_chacha20_ietf_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_chacha20_ietf_xor(c, m, n, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_chacha20_ietf_xor_ic = function (c, m, n, ic, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(
    n.byteLength === binding.crypto_stream_chacha20_ietf_NONCEBYTES,
    "n must be 'crypto_stream_chacha20_ietf_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_chacha20_ietf_KEYBYTES,
    "k must be 'crypto_stream_chacha20_ietf_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_chacha20_ietf_xor_ic(c, m, n, ic, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_xchacha20 = function (c, n, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    n.byteLength === binding.crypto_stream_xchacha20_NONCEBYTES,
    "n must be 'crypto_stream_xchacha20_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_xchacha20_KEYBYTES,
    "k must be 'crypto_stream_xchacha20_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_xchacha20(c, n, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_xchacha20_xor = function (c, m, n, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(
    n.byteLength === binding.crypto_stream_xchacha20_NONCEBYTES,
    "n must be 'crypto_stream_xchacha20_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_xchacha20_KEYBYTES,
    "k must be 'crypto_stream_xchacha20_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_xchacha20_xor(c, m, n, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_xchacha20_xor_ic = function (c, m, n, ic, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(
    n.byteLength === binding.crypto_stream_xchacha20_NONCEBYTES,
    "n must be 'crypto_stream_xchacha20_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_xchacha20_KEYBYTES,
    "k must be 'crypto_stream_xchacha20_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_xchacha20_xor_ic(c, m, n, ic, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_salsa20 = function (c, n, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    n.byteLength === binding.crypto_stream_salsa20_NONCEBYTES,
    "n must be 'crypto_stream_salsa20_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_salsa20_KEYBYTES,
    "k must be 'crypto_stream_salsa20_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_salsa20(c, n, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_salsa20_xor = function (c, m, n, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(
    n.byteLength === binding.crypto_stream_salsa20_NONCEBYTES,
    "n must be 'crypto_stream_salsa20_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_salsa20_KEYBYTES,
    "k must be 'crypto_stream_salsa20_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_salsa20_xor(c, m, n, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_salsa20_xor_ic = function (c, m, n, ic, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(
    n.byteLength === binding.crypto_stream_salsa20_NONCEBYTES,
    "n must be 'crypto_stream_salsa20_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_salsa20_KEYBYTES,
    "k must be 'crypto_stream_salsa20_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_salsa20_xor_ic(c, m, n, ic, k)

  if (res !== 0) throw new Error('status: ' + res)
}

// crypto_auth

exports.crypto_auth = function (out, input, k) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(out.byteLength === binding.crypto_auth_BYTES, "out must be 'crypto_auth_BYTES' bytes")
  assert(k.byteLength === binding.crypto_auth_KEYBYTES, "k must be 'crypto_auth_KEYBYTES' bytes")

  const res = binding.crypto_auth(out, input, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_auth_verify = function (h, input, k) {
  assert(ArrayBuffer.isView(h), 'h must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(h.byteLength === binding.crypto_auth_BYTES, "h must be 'crypto_auth_BYTES' bytes")
  assert(k.byteLength === binding.crypto_auth_KEYBYTES, "k must be 'crypto_auth_KEYBYTES' bytes")

  return binding.crypto_auth_verify(h, input, k)
}

// crypto_onetimeauth

exports.crypto_onetimeauth = function (out, input, k) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    out.byteLength === binding.crypto_onetimeauth_BYTES,
    "out must be 'crypto_onetimeauth_BYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_onetimeauth_KEYBYTES,
    "k must be 'crypto_onetimeauth_KEYBYTES' bytes"
  )

  const res = binding.crypto_onetimeauth(out, input, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_onetimeauth_init = function (state, k) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    state.byteLength === binding.crypto_onetimeauth_STATEBYTES,
    "state must be 'crypto_onetimeauth_STATEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_onetimeauth_KEYBYTES,
    "k must be 'crypto_onetimeauth_KEYBYTES' bytes"
  )

  const res = binding.crypto_onetimeauth_init(state, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_onetimeauth_update = function (state, input) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(
    state.byteLength === binding.crypto_onetimeauth_STATEBYTES,
    "state must be 'crypto_onetimeauth_STATEBYTES' bytes"
  )

  const res = binding.crypto_onetimeauth_update(state, input)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_onetimeauth_final = function (state, out) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(
    state.byteLength === binding.crypto_onetimeauth_STATEBYTES,
    "state must be 'crypto_onetimeauth_STATEBYTES' bytes"
  )
  assert(
    out.byteLength === binding.crypto_onetimeauth_BYTES,
    "out must be 'crypto_onetimeauth_BYTES' bytes"
  )

  const res = binding.crypto_onetimeauth_final(state, out)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_onetimeauth_verify = function (h, input, k) {
  assert(ArrayBuffer.isView(h), 'h must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    h.byteLength === binding.crypto_onetimeauth_BYTES,
    "h must be 'crypto_onetimeauth_BYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_onetimeauth_KEYBYTES,
    "k must be 'crypto_onetimeauth_KEYBYTES' bytes"
  )

  return binding.crypto_onetimeauth_verify(h, input, k)
}

// crypto_pwhash

exports.crypto_pwhash = function (out, passwd, salt, opslimit, memlimit, alg) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(ArrayBuffer.isView(salt), 'salt must be a typed array')
  assert(
    out.byteLength >= binding.crypto_pwhash_BYTES_MIN,
    "out must be at least 'crypto_pwhash_BYTES_MIN' bytes"
  )
  assert(
    out.byteLength <= binding.crypto_pwhash_BYTES_MAX,
    "out must be at most 'crypto_pwhash_BYTES_MAX' bytes"
  )
  assert(
    salt.byteLength === binding.crypto_pwhash_SALTBYTES,
    "salt must be 'crypto_pwhash_SALTBYTES' bytes"
  )
  assert(
    opslimit >= binding.crypto_pwhash_OPSLIMIT_MIN,
    "opslimit must be at least 'crypto_pwhash_OPSLIMIT_MIN'"
  )
  assert(
    opslimit <= binding.crypto_pwhash_OPSLIMIT_MAX,
    "opslimit must be at most 'crypto_pwhash_OPSLIMIT_MAX'"
  )
  assert(
    memlimit >= binding.crypto_pwhash_MEMLIMIT_MIN,
    "memlimit must be at least 'crypto_pwhash_MEMLIMIT_MIN'"
  )
  assert(
    memlimit <= binding.crypto_pwhash_MEMLIMIT_MAX,
    "memlimit must be at most 'crypto_pwhash_MEMLIMIT_MAX'"
  )
  assert(alg >= 1 && alg <= 2, 'alg must be either Argon2i 1.3 or Argon2id 1.3')

  const res = binding.crypto_pwhash(out, passwd, salt, opslimit, memlimit, alg)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_pwhash_async = function (
  out,
  passwd,
  salt,
  opslimit,
  memlimit,
  alg,
  callback = undefined
) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(ArrayBuffer.isView(salt), 'salt must be a typed array')
  assert(
    out.byteLength >= binding.crypto_pwhash_BYTES_MIN,
    "out must be at least 'crypto_pwhash_BYTES_MIN' bytes"
  )
  assert(
    out.byteLength <= binding.crypto_pwhash_BYTES_MAX,
    "out must be at most 'crypto_pwhash_BYTES_MAX' bytes"
  )
  assert(
    salt.byteLength === binding.crypto_pwhash_SALTBYTES,
    "salt must be 'crypto_pwhash_SALTBYTES' bytes"
  )
  assert(
    opslimit >= binding.crypto_pwhash_OPSLIMIT_MIN,
    "opslimit must be at least 'crypto_pwhash_OPSLIMIT_MIN'"
  )
  assert(
    opslimit <= binding.crypto_pwhash_OPSLIMIT_MAX,
    "opslimit must be at most 'crypto_pwhash_OPSLIMIT_MAX'"
  )
  assert(
    memlimit >= binding.crypto_pwhash_MEMLIMIT_MIN,
    "memlimit must be at least 'crypto_pwhash_MEMLIMIT_MIN'"
  )
  assert(
    memlimit <= binding.crypto_pwhash_MEMLIMIT_MAX,
    "memlimit must be at most 'crypto_pwhash_MEMLIMIT_MAX'"
  )
  assert(alg >= 1 && alg <= 2, 'alg must be either Argon2i 1.3 or Argon2id 1.3')

  const [done, promise] = checkStatus(callback)

  binding.crypto_pwhash_async(
    out.buffer,
    out.byteOffset,
    out.byteLength,

    passwd.buffer,
    passwd.byteOffset,
    passwd.byteLength,

    salt.buffer,
    salt.byteOffset,
    salt.byteLength,

    opslimit,
    memlimit,
    alg,

    done
  )

  return promise
}

exports.crypto_pwhash_str = function (out, passwd, opslimit, memlimit) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(
    out.byteLength === binding.crypto_pwhash_STRBYTES,
    "out must be 'crypto_pwhash_STRBYTES' bytes"
  )
  assert(typeof opslimit === 'number', 'opslimit must be a number')
  assert(
    opslimit >= binding.crypto_pwhash_OPSLIMIT_MIN,
    "opslimit must be at least 'crypto_pwhash_OPSLIMIT_MIN'"
  )
  assert(
    opslimit <= binding.crypto_pwhash_OPSLIMIT_MAX,
    "opslimit must be at most 'crypto_pwhash_OPSLIMIT_MAX'"
  )
  assert(typeof memlimit === 'number', 'memlimit must be a number')
  assert(
    memlimit >= binding.crypto_pwhash_MEMLIMIT_MIN,
    "memlimit must be at least 'crypto_pwhash_MEMLIMIT_MIN'"
  )
  assert(
    memlimit <= binding.crypto_pwhash_MEMLIMIT_MAX,
    "memlimit must be at most 'crypto_pwhash_MEMLIMIT_MAX'"
  )

  const res = binding.crypto_pwhash_str(out, passwd, opslimit, memlimit)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_pwhash_str_async = function (out, passwd, opslimit, memlimit, callback = undefined) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(
    out.byteLength === binding.crypto_pwhash_STRBYTES,
    "out must be 'crypto_pwhash_STRBYTES' bytes"
  )
  assert(passwd.byteLength > 0, 'passwd must not be empty')
  assert(typeof opslimit === 'number', 'opslimit must be a number')
  assert(
    opslimit >= binding.crypto_pwhash_OPSLIMIT_MIN,
    "opslimit must be at least 'crypto_pwhash_OPSLIMIT_MIN'"
  )
  assert(
    opslimit <= binding.crypto_pwhash_OPSLIMIT_MAX,
    "opslimit must be at most 'crypto_pwhash_OPSLIMIT_MAX'"
  )
  assert(typeof memlimit === 'number', 'memlimit must be a number')
  assert(
    memlimit >= binding.crypto_pwhash_MEMLIMIT_MIN,
    "memlimit must be at least 'crypto_pwhash_MEMLIMIT_MIN'"
  )
  assert(
    memlimit <= binding.crypto_pwhash_MEMLIMIT_MAX,
    "memlimit must be at most 'crypto_pwhash_MEMLIMIT_MAX'"
  )

  const [done, promise] = checkStatus(callback)

  binding.crypto_pwhash_str_async(
    out.buffer,
    out.byteOffset,
    out.byteLength,

    passwd.buffer,
    passwd.byteOffset,
    passwd.byteLength,

    opslimit,
    memlimit,

    done
  )

  return promise
}

exports.crypto_pwhash_str_verify = function (str, passwd) {
  assert(ArrayBuffer.isView(str), 'str must be a typed array')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(
    str.byteLength === binding.crypto_pwhash_STRBYTES,
    "str must be 'crypto_pwhash_STRBYTES' bytes"
  )

  return binding.crypto_pwhash_str_verify(str, passwd)
}

exports.crypto_pwhash_str_verify_async = function (str, passwd, callback = undefined) {
  assert(ArrayBuffer.isView(str), 'str must be a typed array')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(
    str.byteLength === binding.crypto_pwhash_STRBYTES,
    "str must be 'crypto_pwhash_STRBYTES' bytes"
  )
  assert(passwd.byteLength > 0, 'passwd must not be empty')

  const [done, promise] = checkStatus(callback, true)

  binding.crypto_pwhash_str_verify_async(
    str.buffer,
    str.byteOffset,
    str.byteLength,

    passwd.buffer,
    passwd.byteOffset,
    passwd.byteLength,

    done
  )

  return promise
}

exports.crypto_pwhash_str_needs_rehash = function (str, opslimit, memlimit) {
  assert(ArrayBuffer.isView(str), 'str must be a typed array')
  assert(
    str.byteLength === binding.crypto_pwhash_STRBYTES,
    "str must be 'crypto_pwhash_STRBYTES' bytes"
  )
  assert(
    opslimit >= binding.crypto_pwhash_OPSLIMIT_MIN,
    "opslimit must be at least 'crypto_pwhash_OPSLIMIT_MIN'"
  )
  assert(
    opslimit <= binding.crypto_pwhash_OPSLIMIT_MAX,
    "opslimit must be at most 'crypto_pwhash_OPSLIMIT_MAX'"
  )
  assert(
    memlimit >= binding.crypto_pwhash_MEMLIMIT_MIN,
    "memlimit must be at least 'crypto_pwhash_MEMLIMIT_MIN'"
  )
  assert(
    memlimit <= binding.crypto_pwhash_MEMLIMIT_MAX,
    "memlimit must be at most 'crypto_pwhash_MEMLIMIT_MAX'"
  )

  return binding.crypto_pwhash_str_needs_rehash(str, opslimit, memlimit)
}

exports.crypto_pwhash_scryptsalsa208sha256 = function (out, passwd, salt, opslimit, memlimit) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(ArrayBuffer.isView(salt), 'salt must be a typed array')
  assert(
    out.byteLength >= binding.crypto_pwhash_scryptsalsa208sha256_BYTES_MIN,
    "out must be at least 'crypto_pwhash_scryptsalsa208sha256_BYTES_MIN' bytes"
  )
  assert(
    out.byteLength <= binding.crypto_pwhash_scryptsalsa208sha256_BYTES_MAX,
    "out must be at most 'crypto_pwhash_scryptsalsa208sha256_BYTES_MAX' bytes"
  )
  assert(
    salt.byteLength === binding.crypto_pwhash_scryptsalsa208sha256_SALTBYTES,
    "salt must be 'crypto_pwhash_scryptsalsa208sha256_SALTBYTES' bytes"
  )
  assert(
    opslimit >= binding.crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MIN,
    "opslimit must be at least 'crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MIN'"
  )
  assert(
    opslimit <= binding.crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MAX,
    "opslimit must be at most 'crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MAX'"
  )
  assert(
    memlimit >= binding.crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MIN,
    "memlimit must be at least 'crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MIN'"
  )
  assert(
    memlimit <= binding.crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MAX,
    "memlimit must be at most 'crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MAX'"
  )

  const res = binding.crypto_pwhash_scryptsalsa208sha256(out, passwd, salt, opslimit, memlimit)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_pwhash_scryptsalsa208sha256_async = function (
  out,
  passwd,
  salt,
  opslimit,
  memlimit,
  callback = undefined
) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(ArrayBuffer.isView(salt), 'salt must be a typed array')
  assert(
    out.byteLength >= binding.crypto_pwhash_scryptsalsa208sha256_BYTES_MIN,
    "out must be at least 'crypto_pwhash_scryptsalsa208sha256_BYTES_MIN' bytes"
  )
  assert(
    out.byteLength <= binding.crypto_pwhash_scryptsalsa208sha256_BYTES_MAX,
    "out must be at most 'crypto_pwhash_scryptsalsa208sha256_BYTES_MAX' bytes"
  )
  assert(passwd.byteLength > 0, 'passwd must not be empty')
  assert(
    salt.byteLength === binding.crypto_pwhash_scryptsalsa208sha256_SALTBYTES,
    "salt must be 'crypto_pwhash_scryptsalsa208sha256_SALTBYTES' bytes"
  )
  assert(
    opslimit >= binding.crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MIN,
    "opslimit must be at least 'crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MIN'"
  )
  assert(
    opslimit <= binding.crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MAX,
    "opslimit must be at most 'crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MAX'"
  )
  assert(
    memlimit >= binding.crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MIN,
    "memlimit must be at least 'crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MIN'"
  )
  assert(
    memlimit <= binding.crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MAX,
    "memlimit must be at most 'crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MAX'"
  )

  const [done, promise] = checkStatus(callback)

  binding.crypto_pwhash_scryptsalsa208sha256_async(
    out.buffer,
    out.byteOffset,
    out.byteLength,

    passwd.buffer,
    passwd.byteOffset,
    passwd.byteLength,

    salt.buffer,
    salt.byteOffset,
    salt.byteLength,

    opslimit,
    memlimit,

    done
  )

  return promise
}

exports.crypto_pwhash_scryptsalsa208sha256_str_async = function (
  out,
  passwd,
  opslimit,
  memlimit,
  callback = undefined
) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(
    out.byteLength === binding.crypto_pwhash_scryptsalsa208sha256_STRBYTES,
    "out must be 'crypto_pwhash_scryptsalsa208sha256_STRBYTES' bytes"
  )
  assert(passwd.byteLength > 0, 'passwd must not be empty')
  assert(
    opslimit >= binding.crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MIN,
    "opslimit must be at least 'crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MIN'"
  )
  assert(
    opslimit <= binding.crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MAX,
    "opslimit must be at most 'crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MAX'"
  )
  assert(
    memlimit >= binding.crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MIN,
    "memlimit must be at least 'crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MIN'"
  )
  assert(
    memlimit <= binding.crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MAX,
    "memlimit must be at most 'crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MAX'"
  )

  const [done, promise] = checkStatus(callback)

  binding.crypto_pwhash_scryptsalsa208sha256_str_async(
    out.buffer,
    out.byteOffset,
    out.byteLength,

    passwd.buffer,
    passwd.byteOffset,
    passwd.byteLength,

    opslimit,
    memlimit,

    done
  )

  return promise
}

exports.crypto_pwhash_scryptsalsa208sha256_str = function (out, passwd, opslimit, memlimit) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(
    out.byteLength === binding.crypto_pwhash_scryptsalsa208sha256_STRBYTES,
    "out must be 'crypto_pwhash_scryptsalsa208sha256_STRBYTES' bytes"
  )
  assert(passwd.byteLength > 0, 'passwd must not be empty')
  assert(
    opslimit >= binding.crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MIN,
    "opslimit must be at least 'crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MIN'"
  )
  assert(
    opslimit <= binding.crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MAX,
    "opslimit must be at most 'crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MAX'"
  )
  assert(
    memlimit >= binding.crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MIN,
    "memlimit must be at least 'crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MIN'"
  )
  assert(
    memlimit <= binding.crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MAX,
    "memlimit must be at most 'crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MAX'"
  )

  const res = binding.crypto_pwhash_scryptsalsa208sha256_str(out, passwd, opslimit, memlimit)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_pwhash_scryptsalsa208sha256_str_verify_async = function (
  str,
  passwd,
  callback = undefined
) {
  assert(ArrayBuffer.isView(str), 'str must be a typed array')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(
    str.byteLength === binding.crypto_pwhash_scryptsalsa208sha256_STRBYTES,
    "str must be 'crypto_pwhash_scryptsalsa208sha256_STRBYTES' bytes"
  )
  assert(passwd.byteLength > 0, 'passwd must not be empty')

  const [done, promise] = checkStatus(callback, true)

  binding.crypto_pwhash_scryptsalsa208sha256_str_verify_async(
    str.buffer,
    str.byteOffset,
    str.byteLength,

    passwd.buffer,
    passwd.byteOffset,
    passwd.byteLength,

    done
  )

  return promise
}

exports.crypto_pwhash_scryptsalsa208sha256_str_verify = function (str, passwd) {
  assert(ArrayBuffer.isView(str), 'str must be a typed array')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(
    str.byteLength === binding.crypto_pwhash_scryptsalsa208sha256_STRBYTES,
    "str must be 'crypto_pwhash_scryptsalsa208sha256_STRBYTES' bytes"
  )
  assert(passwd.byteLength > 0, 'passwd must not be empty')

  return binding.crypto_pwhash_scryptsalsa208sha256_str_verify(str, passwd)
}

exports.crypto_pwhash_scryptsalsa208sha256_str_needs_rehash = function (str, opslimit, memlimit) {
  assert(ArrayBuffer.isView(str), 'str must be a typed array')
  assert(
    str.byteLength === binding.crypto_pwhash_scryptsalsa208sha256_STRBYTES,
    "str must be 'crypto_pwhash_scryptsalsa208sha256_STRBYTES' bytes"
  )
  assert(
    opslimit >= binding.crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MIN,
    "opslimit must be at least 'crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MIN'"
  )
  assert(
    opslimit <= binding.crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MAX,
    "opslimit must be at most 'crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MAX'"
  )
  assert(
    memlimit >= binding.crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MIN,
    "memlimit must be at least 'crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MIN'"
  )
  assert(
    memlimit <= binding.crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MAX,
    "memlimit must be at most 'crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MAX'"
  )

  return binding.crypto_pwhash_scryptsalsa208sha256_str_needs_rehash(str, opslimit, memlimit)
}

// crypto_kx

exports.crypto_kx_keypair = function (pk, sk) {
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(
    pk.byteLength === binding.crypto_kx_PUBLICKEYBYTES,
    "pk must be 'crypto_kx_PUBLICKEYBYTES' bytes"
  )
  assert(
    sk.byteLength === binding.crypto_kx_SECRETKEYBYTES,
    "sk must be 'crypto_kx_SECRETKEYBYTES' bytes"
  )

  const res = binding.crypto_kx_keypair(pk, sk)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_kx_seed_keypair = function (pk, sk, seed) {
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(ArrayBuffer.isView(seed), 'seed must be a typed array')
  assert(
    pk.byteLength === binding.crypto_kx_PUBLICKEYBYTES,
    "pk must be 'crypto_kx_PUBLICKEYBYTES' bytes"
  )
  assert(
    sk.byteLength === binding.crypto_kx_SECRETKEYBYTES,
    "sk must be 'crypto_kx_SECRETKEYBYTES' bytes"
  )
  assert(
    seed.byteLength === binding.crypto_kx_SEEDBYTES,
    "seed must be 'crypto_kx_SEEDBYTES' bytes"
  )

  const res = binding.crypto_kx_seed_keypair(pk, sk, seed)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_kx_client_session_keys = function (rx, tx, clientPk, clientSk, serverPk) {
  if (!rx) rx = undefined
  if (!tx) tx = undefined

  assert(rx || tx, 'at least one session key must be specified')

  if (rx) {
    assert(ArrayBuffer.isView(rx), 'rx must be a typed array')
    assert(
      rx.byteLength === binding.crypto_kx_SESSIONKEYBYTES,
      "rx must be 'crypto_kx_SESSIONKEYBYTES' bytes"
    )
  }

  if (tx) {
    assert(ArrayBuffer.isView(tx), 'tx must be a typed array')
    assert(
      tx.byteLength === binding.crypto_kx_SESSIONKEYBYTES,
      "tx must be 'crypto_kx_SESSIONKEYBYTES' bytes"
    )
  }

  assert(ArrayBuffer.isView(clientPk), 'clientPk must be a typed array')
  assert(ArrayBuffer.isView(clientSk), 'clientSk must be a typed array')
  assert(ArrayBuffer.isView(serverPk), 'serverPk must be a typed array')
  assert(
    clientPk.byteLength === binding.crypto_kx_PUBLICKEYBYTES,
    "clientPk must be 'crypto_kx_PUBLICKEYBYTES' bytes"
  )
  assert(
    clientSk.byteLength === binding.crypto_kx_SECRETKEYBYTES,
    "clientSk must be 'crypto_kx_SECRETKEYBYTES' bytes"
  )
  assert(
    serverPk.byteLength === binding.crypto_kx_PUBLICKEYBYTES,
    "serverPk must be 'crypto_kx_PUBLICKEYBYTES' bytes"
  )

  const res = binding.crypto_kx_client_session_keys(rx, tx, clientPk, clientSk, serverPk)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_kx_server_session_keys = function (rx, tx, serverPk, serverSk, clientPk) {
  if (!rx) rx = undefined
  if (!tx) tx = undefined

  assert(rx || tx, 'at least one session key must be specified')

  if (rx) {
    assert(ArrayBuffer.isView(rx), 'rx must be a typed array')
    assert(
      rx.byteLength === binding.crypto_kx_SESSIONKEYBYTES,
      "rx must be 'crypto_kx_SESSIONKEYBYTES' bytes"
    )
  }

  if (tx) {
    assert(ArrayBuffer.isView(tx), 'tx must be a typed array')
    assert(
      tx.byteLength === binding.crypto_kx_SESSIONKEYBYTES,
      "tx must be 'crypto_kx_SESSIONKEYBYTES' bytes"
    )
  }

  assert(ArrayBuffer.isView(serverPk), 'serverPk must be a typed array')
  assert(ArrayBuffer.isView(serverSk), 'serverSk must be a typed array')
  assert(ArrayBuffer.isView(clientPk), 'clientPk must be a typed array')
  assert(
    serverPk.byteLength === binding.crypto_kx_PUBLICKEYBYTES,
    "serverPk must be 'crypto_kx_PUBLICKEYBYTES' bytes"
  )
  assert(
    serverSk.byteLength === binding.crypto_kx_SECRETKEYBYTES,
    "serverSk must be 'crypto_kx_SECRETKEYBYTES' bytes"
  )
  assert(
    clientPk.byteLength === binding.crypto_kx_PUBLICKEYBYTES,
    "clientPk must be 'crypto_kx_PUBLICKEYBYTES' bytes"
  )

  const res = binding.crypto_kx_server_session_keys(rx, tx, serverPk, serverSk, clientPk)

  if (res !== 0) throw new Error('status: ' + res)
}

// crypto_scalarmult

exports.crypto_scalarmult_base = function (q, n) {
  assert(ArrayBuffer.isView(q), 'q must be a typed array')
  assert(
    q.byteLength === binding.crypto_scalarmult_BYTES,
    "q must be 'crypto_scalarmult_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.crypto_scalarmult_SCALARBYTES,
    "n must be 'crypto_scalarmult_SCALARBYTES' bytes"
  )

  const res = binding.crypto_scalarmult_base(q, n)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_scalarmult = function (q, n, p) {
  assert(ArrayBuffer.isView(q), 'q must be a typed array')
  assert(
    q.byteLength === binding.crypto_scalarmult_BYTES,
    "q must be 'crypto_scalarmult_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.crypto_scalarmult_SCALARBYTES,
    "n must be 'crypto_scalarmult_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(p), 'p must be a typed array')
  assert(
    p.byteLength === binding.crypto_scalarmult_BYTES,
    "p must be 'crypto_scalarmult_BYTES' bytes"
  )

  const res = binding.crypto_scalarmult(q, n, p)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_scalarmult_ed25519_base = function (q, n) {
  assert(ArrayBuffer.isView(q), 'q must be a typed array')
  assert(
    q.byteLength === binding.crypto_scalarmult_ed25519_BYTES,
    "q must be 'crypto_scalarmult_ed25519_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.crypto_scalarmult_ed25519_SCALARBYTES,
    "n must be 'crypto_scalarmult_ed25519_SCALARBYTES' bytes"
  )

  const res = binding.crypto_scalarmult_ed25519_base(q, n)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_scalarmult_ed25519 = function (q, n, p) {
  assert(ArrayBuffer.isView(q), 'q must be a typed array')
  assert(
    q.byteLength === binding.crypto_scalarmult_ed25519_BYTES,
    "q must be 'crypto_scalarmult_ed25519_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.crypto_scalarmult_ed25519_SCALARBYTES,
    "n must be 'crypto_scalarmult_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(p), 'p must be a typed array')
  assert(
    p.byteLength === binding.crypto_scalarmult_ed25519_BYTES,
    "p must be 'crypto_scalarmult_ed25519_BYTES' bytes"
  )

  const res = binding.crypto_scalarmult_ed25519(q, n, p)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_core_ed25519_is_valid_point = function (p) {
  assert(ArrayBuffer.isView(p), 'p must be a typed array')
  assert(
    p.byteLength === binding.crypto_core_ed25519_BYTES,
    "p must be 'crypto_core_ed25519_BYTES' bytes"
  )

  return binding.crypto_core_ed25519_is_valid_point(p)
}

exports.crypto_core_ed25519_from_uniform = function (p, r) {
  assert(ArrayBuffer.isView(p), 'p must be a typed array')
  assert(
    p.byteLength === binding.crypto_core_ed25519_BYTES,
    "p must be 'crypto_core_ed25519_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(r), 'r must be a typed array')
  assert(
    r.byteLength === binding.crypto_core_ed25519_UNIFORMBYTES,
    "r must be 'crypto_core_ed25519_UNIFORMBYTES' bytes"
  )

  const res = binding.crypto_core_ed25519_from_uniform(p, r)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_scalarmult_ed25519_base_noclamp = function (q, n) {
  assert(ArrayBuffer.isView(q), 'q must be a typed array')
  assert(
    q.byteLength === binding.crypto_scalarmult_ed25519_BYTES,
    "q must be 'crypto_scalarmult_ed25519_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.crypto_scalarmult_ed25519_SCALARBYTES,
    "n must be 'crypto_scalarmult_ed25519_SCALARBYTES' bytes"
  )

  const res = binding.crypto_scalarmult_ed25519_base_noclamp(q, n)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_scalarmult_ed25519_noclamp = function (q, n, p) {
  assert(ArrayBuffer.isView(q), 'q must be a typed array')
  assert(
    q.byteLength === binding.crypto_scalarmult_ed25519_BYTES,
    "q must be 'crypto_scalarmult_ed25519_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.crypto_scalarmult_ed25519_SCALARBYTES,
    "n must be 'crypto_scalarmult_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(p), 'p must be a typed array')
  assert(
    p.byteLength === binding.crypto_scalarmult_ed25519_BYTES,
    "p must be 'crypto_scalarmult_ed25519_BYTES' bytes"
  )

  const res = binding.crypto_scalarmult_ed25519_noclamp(q, n, p)

  if (res !== 0) throw new Error('status: ' + res)
}

// crypto_core

exports.crypto_core_ed25519_add = function (r, p, q) {
  assert(ArrayBuffer.isView(r), 'r must be a typed array')
  assert(
    r.byteLength === binding.crypto_core_ed25519_BYTES,
    "r must be 'crypto_core_ed25519_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(p), 'p must be a typed array')
  assert(
    p.byteLength === binding.crypto_core_ed25519_BYTES,
    "p must be 'crypto_core_ed25519_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(q), 'q must be a typed array')
  assert(
    q.byteLength === binding.crypto_core_ed25519_BYTES,
    "q must be 'crypto_core_ed25519_BYTES' bytes"
  )

  const res = binding.crypto_core_ed25519_add(r, p, q)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_core_ed25519_sub = function (r, p, q) {
  assert(ArrayBuffer.isView(r), 'r must be a typed array')
  assert(
    r.byteLength === binding.crypto_core_ed25519_BYTES,
    "r must be 'crypto_core_ed25519_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(p), 'p must be a typed array')
  assert(
    p.byteLength === binding.crypto_core_ed25519_BYTES,
    "p must be 'crypto_core_ed25519_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(q), 'q must be a typed array')
  assert(
    q.byteLength === binding.crypto_core_ed25519_BYTES,
    "q must be 'crypto_core_ed25519_BYTES' bytes"
  )

  const res = binding.crypto_core_ed25519_sub(r, p, q)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_core_ed25519_scalar_random = function (r) {
  assert(ArrayBuffer.isView(r), 'r must be a typed array')
  assert(
    r.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "r must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )

  binding.crypto_core_ed25519_scalar_random(r)
}

exports.crypto_core_ed25519_scalar_reduce = function (r, s) {
  assert(ArrayBuffer.isView(r), 'r must be a typed array')
  assert(
    r.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "r must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(s), 's must be a typed array')
  assert(
    s.byteLength === binding.crypto_core_ed25519_NONREDUCEDSCALARBYTES,
    "s must be 'crypto_core_ed25519_NONREDUCEDSCALARBYTES' bytes"
  )

  binding.crypto_core_ed25519_scalar_reduce(r, s)
}

exports.crypto_core_ed25519_scalar_invert = function (recip, s) {
  assert(ArrayBuffer.isView(recip), 'recip must be a typed array')
  assert(
    recip.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "recip must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(s), 's must be a typed array')
  assert(
    s.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "s must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )

  binding.crypto_core_ed25519_scalar_invert(recip, s)
}

exports.crypto_core_ed25519_scalar_negate = function (neg, s) {
  assert(ArrayBuffer.isView(neg), 'neg must be a typed array')
  assert(
    neg.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "neg must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(s), 's must be a typed array')
  assert(
    s.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "s must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )

  binding.crypto_core_ed25519_scalar_negate(neg, s)
}

exports.crypto_core_ed25519_scalar_complement = function (comp, s) {
  assert(ArrayBuffer.isView(comp), 'comp must be a typed array')
  assert(
    comp.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "comp must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(s), 's must be a typed array')
  assert(
    s.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "s must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )

  binding.crypto_core_ed25519_scalar_complement(comp, s)
}

exports.crypto_core_ed25519_scalar_add = function (z, x, y) {
  assert(ArrayBuffer.isView(z), 'z must be a typed array')
  assert(
    z.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "z must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(x), 'x must be a typed array')
  assert(
    x.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "x must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(y), 'y must be a typed array')
  assert(
    y.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "y must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )

  binding.crypto_core_ed25519_scalar_add(z, x, y)
}

exports.crypto_core_ed25519_scalar_sub = function (z, x, y) {
  assert(ArrayBuffer.isView(z), 'z must be a typed array')
  assert(
    z.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "z must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(x), 'x must be a typed array')
  assert(
    x.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "x must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(y), 'y must be a typed array')
  assert(
    y.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "y must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )

  binding.crypto_core_ed25519_scalar_sub(z, x, y)
}

// crypto_shorthash

exports.crypto_shorthash = function (out, input, k) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(
    out.byteLength === binding.crypto_shorthash_BYTES,
    "out must be 'crypto_shorthash_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_shorthash_KEYBYTES,
    "k must be 'crypto_shorthash_KEYBYTES' bytes"
  )

  const res = binding.crypto_shorthash(out, input, k)

  if (res !== 0) throw new Error('status: ' + res)
}

// crypto_kdf

exports.crypto_kdf_keygen = function (key) {
  assert(ArrayBuffer.isView(key), 'key must be a typed array')
  assert(key.byteLength === binding.crypto_kdf_KEYBYTES, "key must be 'crypto_kdf_KEYBYTES' bytes")

  binding.crypto_kdf_keygen(key)
}

exports.crypto_kdf_derive_from_key = function (subkey, subkeyId, ctx, key) {
  assert(ArrayBuffer.isView(subkey), 'subkey must be a typed array')
  assert(
    subkey.byteLength >= binding.crypto_kdf_BYTES_MIN,
    "subkey must be at least 'crypto_kdf_BYTES_MIN' bytes"
  )
  assert(
    subkey.byteLength <= binding.crypto_kdf_BYTES_MAX,
    "subkey must be at most 'crypto_kdf_BYTES_MAX' bytes"
  )
  assert(ArrayBuffer.isView(ctx), 'ctx must be a typed array')
  assert(
    ctx.byteLength === binding.crypto_kdf_CONTEXTBYTES,
    "ctx must be 'crypto_kdf_CONTEXTBYTES' bytes"
  )
  assert(ArrayBuffer.isView(key), 'key must be a typed array')
  assert(key.byteLength === binding.crypto_kdf_KEYBYTES, "key must be 'crypto_kdf_KEYBYTES' bytes")

  const res = binding.crypto_kdf_derive_from_key(subkey, subkeyId, ctx, key)

  if (res !== 0) throw new Error('status: ' + res)
}

// crypto_hash

exports.crypto_hash = function (out, input) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(out.byteLength === binding.crypto_hash_BYTES, "out must be 'crypto_hash_BYTES' bytes")

  const res = binding.crypto_hash(out, input)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_hash_sha256 = function (out, input) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(
    out.byteLength === binding.crypto_hash_sha256_BYTES,
    "out must be 'crypto_hash_sha256_BYTES' bytes"
  )

  const res = binding.crypto_hash_sha256(out, input)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_hash_sha256_init = function (state) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_hash_sha256_STATEBYTES,
    "state must be 'crypto_hash_sha256_STATEBYTES' bytes"
  )

  const res = binding.crypto_hash_sha256_init(state)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_hash_sha256_update = function (state, input) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(
    state.byteLength === binding.crypto_hash_sha256_STATEBYTES,
    "state must be 'crypto_hash_sha256_STATEBYTES' bytes"
  )

  const res = binding.crypto_hash_sha256_update(state, input)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_hash_sha256_final = function (state, out) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_hash_sha256_STATEBYTES,
    "state must be 'crypto_hash_sha256_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(
    out.byteLength === binding.crypto_hash_sha256_BYTES,
    "out must be 'crypto_hash_sha256_BYTES' bytes"
  )

  const res = binding.crypto_hash_sha256_final(state, out)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_hash_sha512 = function (out, input) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(
    out.byteLength === binding.crypto_hash_sha512_BYTES,
    "out must be 'crypto_hash_sha512_BYTES' bytes"
  )

  const res = binding.crypto_hash_sha512(out, input)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_hash_sha512_init = function (state) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_hash_sha512_STATEBYTES,
    "state must be 'crypto_hash_sha512_STATEBYTES' bytes"
  )

  const res = binding.crypto_hash_sha512_init(state)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_hash_sha512_update = function (state, input) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(
    state.byteLength === binding.crypto_hash_sha512_STATEBYTES,
    "state must be 'crypto_hash_sha512_STATEBYTES' bytes"
  )

  const res = binding.crypto_hash_sha512_update(state, input)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_hash_sha512_final = function (state, out) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_hash_sha512_STATEBYTES,
    "state must be 'crypto_hash_sha512_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(
    out.byteLength === binding.crypto_hash_sha512_BYTES,
    "out must be 'crypto_hash_sha512_BYTES' bytes"
  )

  const res = binding.crypto_hash_sha512_final(state, out)

  if (res !== 0) throw new Error('status: ' + res)
}

// crypto_aead

exports.crypto_aead_xchacha20poly1305_ietf_keygen = function (k) {
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_aead_xchacha20poly1305_ietf_KEYBYTES,
    "k must be 'crypto_aead_xchacha20poly1305_ietf_KEYBYTES' bytes"
  )

  binding.crypto_aead_xchacha20poly1305_ietf_keygen(k)
}

exports.crypto_aead_xchacha20poly1305_ietf_encrypt = function (c, m, ad, nsec, npub, k) {
  if (!ad) ad = undefined

  assert(nsec === null, 'nsec must always be set to null')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(
    c.byteLength === m.byteLength + binding.crypto_aead_xchacha20poly1305_ietf_ABYTES,
    "c must be 'm.byteLength + crypto_aead_xchacha20poly1305_ietf_ABYTES' bytes"
  )
  assert(c.byteLength <= 0xffffffff, 'c.byteLength must be a 32bit integer')
  assert(ArrayBuffer.isView(npub), 'npub must be a typed array')
  assert(
    npub.byteLength === binding.crypto_aead_xchacha20poly1305_ietf_NPUBBYTES,
    "npub must be 'crypto_aead_xchacha20poly1305_ietf_NPUBBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_aead_xchacha20poly1305_ietf_KEYBYTES,
    "k must be 'crypto_aead_xchacha20poly1305_ietf_KEYBYTES' bytes"
  )

  const res = binding.crypto_aead_xchacha20poly1305_ietf_encrypt(c, m, ad, npub, k)

  if (res < 0) throw new Error('could not encrypt data')

  return res
}

exports.crypto_aead_xchacha20poly1305_ietf_decrypt = function (m, nsec, c, ad, npub, k) {
  if (!ad) ad = undefined

  assert(nsec === null, 'nsec must always be set to null')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(
    m.byteLength === c.byteLength - binding.crypto_aead_xchacha20poly1305_ietf_ABYTES,
    "m must be 'c.byteLength - crypto_aead_xchacha20poly1305_ietf_ABYTES' bytes"
  )
  assert(m.byteLength <= 0xffffffff, 'm.byteLength must be a 32bit integer')
  assert(ArrayBuffer.isView(npub), 'npub must be a typed array')
  assert(
    npub.byteLength === binding.crypto_aead_xchacha20poly1305_ietf_NPUBBYTES,
    "npub must be 'crypto_aead_xchacha20poly1305_ietf_NPUBBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_aead_xchacha20poly1305_ietf_KEYBYTES,
    "k must be 'crypto_aead_xchacha20poly1305_ietf_KEYBYTES' bytes"
  )

  const res = binding.crypto_aead_xchacha20poly1305_ietf_decrypt(m, c, ad, npub, k)

  if (res < 0) throw new Error('could not verify data')

  return res
}

exports.crypto_aead_xchacha20poly1305_ietf_encrypt_detached = function (
  c,
  mac,
  m,
  ad,
  nsec,
  npub,
  k
) {
  if (!ad) ad = undefined

  assert(nsec === null, 'nsec must always be set to null')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(ArrayBuffer.isView(mac), 'mac must be a typed array')
  assert(
    mac.byteLength === binding.crypto_aead_xchacha20poly1305_ietf_ABYTES,
    "mac must be 'crypto_aead_xchacha20poly1305_ietf_ABYTES' bytes"
  )
  assert(ArrayBuffer.isView(npub), 'npub must be a typed array')
  assert(
    npub.byteLength === binding.crypto_aead_xchacha20poly1305_ietf_NPUBBYTES,
    "npub must be 'crypto_aead_xchacha20poly1305_ietf_NPUBBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_aead_xchacha20poly1305_ietf_KEYBYTES,
    "k must be 'crypto_aead_xchacha20poly1305_ietf_KEYBYTES' bytes"
  )

  const res = binding.crypto_aead_xchacha20poly1305_ietf_encrypt_detached(c, mac, m, ad, npub, k)

  if (res < 0) throw new Error('could not encrypt data')

  return res
}

exports.crypto_aead_xchacha20poly1305_ietf_decrypt_detached = function (
  m,
  nsec,
  c,
  mac,
  ad,
  npub,
  k
) {
  if (!ad) ad = undefined

  assert(nsec === null, 'nsec must always be set to null')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(m.byteLength === c.byteLength, "m must be 'c.byteLength' bytes")
  assert(ArrayBuffer.isView(mac), 'mac must be a typed array')
  assert(
    mac.byteLength === binding.crypto_aead_xchacha20poly1305_ietf_ABYTES,
    "mac must be 'crypto_aead_xchacha20poly1305_ietf_ABYTES' bytes"
  )
  assert(ArrayBuffer.isView(npub), 'npub must be a typed array')
  assert(
    npub.byteLength === binding.crypto_aead_xchacha20poly1305_ietf_NPUBBYTES,
    "npub must be 'crypto_aead_xchacha20poly1305_ietf_NPUBBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_aead_xchacha20poly1305_ietf_KEYBYTES,
    "k must be 'crypto_aead_xchacha20poly1305_ietf_KEYBYTES' bytes"
  )

  const res = binding.crypto_aead_xchacha20poly1305_ietf_decrypt_detached(m, c, mac, ad, npub, k)

  if (res !== 0) throw new Error('could not verify data')
}

exports.crypto_aead_chacha20poly1305_ietf_keygen = function (k) {
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_aead_chacha20poly1305_ietf_KEYBYTES,
    "k must be 'crypto_aead_chacha20poly1305_ietf_KEYBYTES' bytes"
  )

  binding.crypto_aead_chacha20poly1305_ietf_keygen(k)
}

exports.crypto_aead_chacha20poly1305_ietf_encrypt = function (c, m, ad, nsec, npub, k) {
  if (!ad) ad = undefined

  assert(nsec === null, 'nsec must always be set to null')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(
    c.byteLength === m.byteLength + binding.crypto_aead_chacha20poly1305_ietf_ABYTES,
    "c must be 'm.byteLength + crypto_aead_chacha20poly1305_ietf_ABYTES' bytes"
  )
  assert(c.byteLength <= 0xffffffff, 'c.byteLength must be a 32bit integer')
  assert(ArrayBuffer.isView(npub), 'npub must be a typed array')
  assert(
    npub.byteLength === binding.crypto_aead_chacha20poly1305_ietf_NPUBBYTES,
    "npub must be 'crypto_aead_chacha20poly1305_ietf_NPUBBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_aead_chacha20poly1305_ietf_KEYBYTES,
    "k must be 'crypto_aead_chacha20poly1305_ietf_KEYBYTES' bytes"
  )

  const res = binding.crypto_aead_chacha20poly1305_ietf_encrypt(c, m, ad, npub, k)

  if (res < 0) throw new Error('could not encrypt data')

  return res
}

exports.crypto_aead_chacha20poly1305_ietf_decrypt = function (m, nsec, c, ad, npub, k) {
  if (!ad) ad = undefined

  assert(nsec === null, 'nsec must always be set to null')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(
    m.byteLength === c.byteLength - binding.crypto_aead_chacha20poly1305_ietf_ABYTES,
    "m must be 'c.byteLength - crypto_aead_chacha20poly1305_ietf_ABYTES' bytes"
  )
  assert(m.byteLength <= 0xffffffff, 'm.byteLength must be a 32bit integer')
  assert(ArrayBuffer.isView(npub), 'npub must be a typed array')
  assert(
    npub.byteLength === binding.crypto_aead_chacha20poly1305_ietf_NPUBBYTES,
    "npub must be 'crypto_aead_chacha20poly1305_ietf_NPUBBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_aead_chacha20poly1305_ietf_KEYBYTES,
    "k must be 'crypto_aead_chacha20poly1305_ietf_KEYBYTES' bytes"
  )

  const res = binding.crypto_aead_chacha20poly1305_ietf_decrypt(m, c, ad, npub, k)

  if (res < 0) throw new Error('could not verify data')

  return res
}

exports.crypto_aead_chacha20poly1305_ietf_encrypt_detached = function (
  c,
  mac,
  m,
  ad,
  nsec,
  npub,
  k
) {
  if (!ad) ad = undefined

  assert(nsec === null, 'nsec must always be set to null')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(ArrayBuffer.isView(mac), 'mac must be a typed array')
  assert(
    mac.byteLength === binding.crypto_aead_chacha20poly1305_ietf_ABYTES,
    "mac must be 'crypto_aead_chacha20poly1305_ietf_ABYTES' bytes"
  )
  assert(ArrayBuffer.isView(npub), 'npub must be a typed array')
  assert(
    npub.byteLength === binding.crypto_aead_chacha20poly1305_ietf_NPUBBYTES,
    "npub must be 'crypto_aead_chacha20poly1305_ietf_NPUBBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_aead_chacha20poly1305_ietf_KEYBYTES,
    "k must be 'crypto_aead_chacha20poly1305_ietf_KEYBYTES' bytes"
  )

  const res = binding.crypto_aead_chacha20poly1305_ietf_encrypt_detached(c, mac, m, ad, npub, k)

  if (res < 0) throw new Error('could not encrypt data')

  return res
}

exports.crypto_aead_chacha20poly1305_ietf_decrypt_detached = function (
  m,
  nsec,
  c,
  mac,
  ad,
  npub,
  k
) {
  if (!ad) ad = undefined

  assert(nsec === null, 'nsec must always be set to null')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(m.byteLength === c.byteLength, "m must be 'c.byteLength' bytes")
  assert(ArrayBuffer.isView(mac), 'mac must be a typed array')
  assert(
    mac.byteLength === binding.crypto_aead_chacha20poly1305_ietf_ABYTES,
    "mac must be 'crypto_aead_chacha20poly1305_ietf_ABYTES' bytes"
  )
  assert(ArrayBuffer.isView(npub), 'npub must be a typed array')
  assert(
    npub.byteLength === binding.crypto_aead_chacha20poly1305_ietf_NPUBBYTES,
    "npub must be 'crypto_aead_chacha20poly1305_ietf_NPUBBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_aead_chacha20poly1305_ietf_KEYBYTES,
    "k must be 'crypto_aead_chacha20poly1305_ietf_KEYBYTES' bytes"
  )

  const res = binding.crypto_aead_chacha20poly1305_ietf_decrypt_detached(m, c, mac, ad, npub, k)

  if (res !== 0) throw new Error('could not verify data')
}

// crypto_stream

exports.crypto_stream_xor_wrap_init = function (state, n, k) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.sn_crypto_stream_xor_STATEBYTES,
    "state must be 'sn_crypto_stream_xor_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.crypto_stream_NONCEBYTES,
    "n must be 'crypto_stream_NONCEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_stream_KEYBYTES,
    "k must be 'crypto_stream_KEYBYTES' bytes"
  )

  binding.crypto_stream_xor_wrap_init(state, n, k)
}

exports.crypto_stream_xor_wrap_update = function (state, c, m) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.sn_crypto_stream_xor_STATEBYTES,
    "state must be 'sn_crypto_stream_xor_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")

  binding.crypto_stream_xor_wrap_update(state, c, m)
}

exports.crypto_stream_xor_wrap_final = function (state) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.sn_crypto_stream_xor_STATEBYTES,
    "state must be 'sn_crypto_stream_xor_STATEBYTES' bytes"
  )

  binding.crypto_stream_xor_wrap_final(state)
}

exports.crypto_stream_chacha20_xor_wrap_init = function (state, n, k) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_stream_chacha20_xor_STATEBYTES,
    "state must be 'crypto_stream_chacha20_xor_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.crypto_stream_chacha20_NONCEBYTES,
    "n must be 'crypto_stream_chacha20_NONCEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_stream_chacha20_KEYBYTES,
    "k must be 'crypto_stream_chacha20_KEYBYTES' bytes"
  )

  binding.crypto_stream_chacha20_xor_wrap_init(state, n, k)
}

exports.crypto_stream_chacha20_xor_wrap_update = function (state, c, m) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_stream_chacha20_xor_STATEBYTES,
    "state must be 'crypto_stream_chacha20_xor_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")

  binding.crypto_stream_chacha20_xor_wrap_update(state, c, m)
}

exports.crypto_stream_chacha20_xor_wrap_final = function (state) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_stream_chacha20_xor_STATEBYTES,
    "state must be 'crypto_stream_chacha20_xor_STATEBYTES' bytes"
  )

  binding.crypto_stream_chacha20_xor_wrap_final(state)
}

exports.crypto_stream_chacha20_ietf_xor_wrap_init = function (state, n, k) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_stream_chacha20_ietf_xor_STATEBYTES,
    "state must be 'crypto_stream_chacha20_ietf_xor_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.crypto_stream_chacha20_ietf_NONCEBYTES,
    "n must be 'crypto_stream_chacha20_ietf_NONCEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_stream_chacha20_ietf_KEYBYTES,
    "k must be 'crypto_stream_chacha20_ietf_KEYBYTES' bytes"
  )

  binding.crypto_stream_chacha20_ietf_xor_wrap_init(state, n, k)
}

exports.crypto_stream_chacha20_ietf_xor_wrap_update = function (state, c, m) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_stream_chacha20_ietf_xor_STATEBYTES,
    "state must be 'crypto_stream_chacha20_ietf_xor_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")

  binding.crypto_stream_chacha20_ietf_xor_wrap_update(state, c, m)
}

exports.crypto_stream_chacha20_ietf_xor_wrap_final = function (state) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_stream_chacha20_ietf_xor_STATEBYTES,
    "state must be 'crypto_stream_chacha20_ietf_xor_STATEBYTES' bytes"
  )

  binding.crypto_stream_chacha20_ietf_xor_wrap_final(state)
}

exports.crypto_stream_xchacha20_xor_wrap_init = function (state, n, k) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_stream_xchacha20_xor_STATEBYTES,
    "state must be 'crypto_stream_xchacha20_xor_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.crypto_stream_xchacha20_NONCEBYTES,
    "n must be 'crypto_stream_xchacha20_NONCEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_stream_xchacha20_KEYBYTES,
    "k must be 'crypto_stream_xchacha20_KEYBYTES' bytes"
  )

  binding.crypto_stream_xchacha20_xor_wrap_init(state, n, k)
}

exports.crypto_stream_xchacha20_xor_wrap_update = function (state, c, m) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_stream_xchacha20_xor_STATEBYTES,
    "state must be 'crypto_stream_xchacha20_xor_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")

  binding.crypto_stream_xchacha20_xor_wrap_update(state, c, m)
}

exports.crypto_stream_xchacha20_xor_wrap_final = function (state) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_stream_xchacha20_xor_STATEBYTES,
    "state must be 'crypto_stream_xchacha20_xor_STATEBYTES' bytes"
  )

  binding.crypto_stream_xchacha20_xor_wrap_final(state)
}

exports.crypto_stream_salsa20_xor_wrap_init = function (state, n, k) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_stream_salsa20_xor_STATEBYTES,
    "state must be 'crypto_stream_salsa20_xor_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.crypto_stream_salsa20_NONCEBYTES,
    "n must be 'crypto_stream_salsa20_NONCEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_stream_salsa20_KEYBYTES,
    "k must be 'crypto_stream_salsa20_KEYBYTES' bytes"
  )

  binding.crypto_stream_salsa20_xor_wrap_init(state, n, k)
}

exports.crypto_stream_salsa20_xor_wrap_update = function (state, c, m) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_stream_salsa20_xor_STATEBYTES,
    "state must be 'crypto_stream_salsa20_xor_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")

  binding.crypto_stream_salsa20_xor_wrap_update(state, c, m)
}

exports.crypto_stream_salsa20_xor_wrap_final = function (state) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_stream_salsa20_xor_STATEBYTES,
    "state must be 'crypto_stream_salsa20_xor_STATEBYTES' bytes"
  )

  binding.crypto_stream_salsa20_xor_wrap_final(state)
}

// experimental

exports.extension_tweak_ed25519_base = function (n, p, ns) {
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "n must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(p), 'p must be a typed array')
  assert(
    p.byteLength === binding.extension_tweak_ed25519_BYTES,
    "p must be 'extension_tweak_ed25519_BYTES' bytes"
  )

  binding.extension_tweak_ed25519_base(n, p, ns)
}

exports.extension_tweak_ed25519_sign_detached = function (sig, m, scalar, pk) {
  assert(ArrayBuffer.isView(sig), 'sig must be a typed array')
  assert(sig.byteLength === binding.crypto_sign_BYTES, "sig must be 'crypto_sign_BYTES' bytes")
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(scalar), 'scalar must be a typed array')
  assert(
    scalar.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "scalar must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )

  if (pk) {
    assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
    assert(
      pk.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
      "pk must be 'crypto_sign_PUBLICKEYBYTES' bytes"
    )
  }

  const res = binding.extension_tweak_ed25519_sign_detached(sig, m, scalar, pk)

  if (res !== 0) throw new Error('failed to compute signature')
}

exports.extension_tweak_ed25519_sk_to_scalar = function (n, sk) {
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "n must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(
    sk.byteLength === binding.crypto_sign_SECRETKEYBYTES,
    "sk must be 'crypto_sign_SECRETKEYBYTES' bytes"
  )

  binding.extension_tweak_ed25519_sk_to_scalar(n, sk)
}

exports.extension_tweak_ed25519_scalar = function (scalarOut, scalar, ns) {
  assert(ArrayBuffer.isView(scalarOut), 'scalarOut must be a typed array')
  assert(
    scalarOut.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "scalarOut must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(scalar), 'scalar must be a typed array')
  assert(
    scalar.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "scalar must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )

  binding.extension_tweak_ed25519_scalar(scalarOut, scalar, ns)
}

exports.extension_tweak_ed25519_pk = function (tpk, pk, ns) {
  assert(ArrayBuffer.isView(tpk), 'tpk must be a typed array')
  assert(
    tpk.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
    "tpk must be 'crypto_sign_PUBLICKEYBYTES' bytes"
  )
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(
    pk.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
    "pk must be 'crypto_sign_PUBLICKEYBYTES' bytes"
  )

  const res = binding.extension_tweak_ed25519_pk(tpk, pk, ns)

  if (res !== 0) throw new Error('failed to tweak public key')
}

exports.extension_tweak_ed25519_keypair = function (pk, scalarOut, scalarIn, ns) {
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(
    pk.byteLength === binding.extension_tweak_ed25519_BYTES,
    "pk must be 'extension_tweak_ed25519_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(scalarOut), 'scalarOut must be a typed array')
  assert(
    scalarOut.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "scalarOut must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(scalarIn), 'scalarIn must be a typed array')
  assert(
    scalarIn.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "scalarIn must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )

  binding.extension_tweak_ed25519_keypair(pk, scalarOut, scalarIn, ns)
}

exports.extension_tweak_ed25519_scalar_add = function (scalarOut, scalar, n) {
  assert(ArrayBuffer.isView(scalarOut), 'scalarOut must be a typed array')
  assert(
    scalarOut.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "scalarOut must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(scalar), 'scalar must be a typed array')
  assert(
    scalar.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "scalar must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "n must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )

  binding.extension_tweak_ed25519_scalar_add(scalarOut, scalar, n)
}

exports.extension_tweak_ed25519_pk_add = function (tpk, pk, p) {
  assert(ArrayBuffer.isView(tpk), 'tpk must be a typed array')
  assert(
    tpk.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
    "tpk must be 'crypto_sign_PUBLICKEYBYTES' bytes"
  )
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(
    pk.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
    "pk must be 'crypto_sign_PUBLICKEYBYTES' bytes"
  )
  assert(ArrayBuffer.isView(p), 'p must be a typed array')
  assert(
    p.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
    "p must be 'crypto_sign_PUBLICKEYBYTES' bytes"
  )

  const res = binding.extension_tweak_ed25519_pk_add(tpk, pk, p)

  if (res !== 0) throw new Error('failed to add tweak to public key')
}

exports.extension_tweak_ed25519_keypair_add = function (pk, scalarOut, scalarIn, tweak) {
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(
    pk.byteLength === binding.extension_tweak_ed25519_BYTES,
    "pk must be 'extension_tweak_ed25519_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(scalarOut), 'scalarOut must be a typed array')
  assert(
    scalarOut.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "scalarOut must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(scalarIn), 'scalarIn must be a typed array')
  assert(
    scalarIn.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "scalarIn must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(tweak), 'tweak must be a typed array')
  assert(
    tweak.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "tweak must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )

  const res = binding.extension_tweak_ed25519_keypair_add(pk, scalarOut, scalarIn, tweak)

  if (res !== 0) throw new Error('failed to add tweak to keypair')
}

exports.extension_pbkdf2_sha512_async = function (out, passwd, salt, iter, outlen, callback) {
  assert(
    iter >= binding.extension_pbkdf2_sha512_ITERATIONS_MIN,
    "iter must be at least 'extension_pbkdf2_sha512_ITERATIONS_MIN'"
  )
  assert(
    outlen <= binding.extension_pbkdf2_sha512_BYTES_MAX,
    "outlen must be at most 'extension_pbkdf2_sha512_BYTES_MAX'"
  )
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(out.byteLength >= outlen, "out must be at least 'outlen' bytes")
  assert(out.byteLength > 0, 'out must not be empty')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(passwd.byteLength > 0, 'passwd must not be empty')
  assert(ArrayBuffer.isView(salt), 'salt must be a typed array')
  assert(salt.byteLength > 0, 'salt must not be empty')

  const [done, promise] = checkStatus(callback)

  binding.extension_pbkdf2_sha512_async(
    out.buffer,
    out.byteOffset,
    out.byteLength,

    passwd.buffer,
    passwd.byteOffset,
    passwd.byteLength,

    salt.buffer,
    salt.byteOffset,
    salt.byteLength,

    iter,
    outlen,

    done
  )

  return promise
}

exports.extension_pbkdf2_sha512 = function (out, passwd, salt, iter, outlen) {
  assert(
    iter >= binding.extension_pbkdf2_sha512_ITERATIONS_MIN,
    "iter must be at least 'extension_pbkdf2_sha512_ITERATIONS_MIN'"
  )
  assert(
    outlen <= binding.extension_pbkdf2_sha512_BYTES_MAX,
    "outlen must be at most 'extension_pbkdf2_sha512_BYTES_MAX'"
  )
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(out.byteLength >= outlen, "out must be at least 'outlen' bytes")
  assert(out.byteLength > 0, 'out must not be empty')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(passwd.byteLength > 0, 'passwd must not be empty')
  assert(ArrayBuffer.isView(salt), 'salt must be a typed array')
  assert(salt.byteLength > 0, 'salt must not be empty')

  const res = binding.extension_pbkdf2_sha512(out, passwd, salt, iter, outlen)

  if (res !== 0) throw new Error('failed to add tweak to public key')
}

function checkStatus(callback, booleanResult = false) {
  let done, promise

  if (typeof callback === 'function') {
    done = function (status) {
      if (booleanResult) callback(null, status === 0)
      else if (status === 0) callback(null)
      else callback(new Error('status: ' + status))
    }
  } else {
    promise = new Promise(function (resolve, reject) {
      done = function (status) {
        if (booleanResult) resolve(status === 0)
        else if (status === 0) resolve()
        else reject(new Error('status: ' + status))
      }
    })
  }

  return [done, promise]
}
{
  "name": "sodium-native",
  "version": "5.1.0",
  "description": "Low level bindings for libsodium",
  "main": "index.js",
  "files": [
    "index.js",
    "binding.cc",
    "binding.js",
    "extensions",
    "prebuilds",
    "CMakeLists.txt"
  ],
  "imports": {
    "assert": {
      "bare": "bare-assert",
      "node": "assert"
    }
  },
  "addon": true,
  "dependencies": {
    "bare-assert": "^1.2.0",
    "require-addon": "^1.1.0",
    "which-runtime": "^1.2.1"
  },
  "devDependencies": {
    "bare-compat-napi": "^1.3.5",
    "brittle": "^3.16.2",
    "cmake-bare": "^1.6.1",
    "cmake-fetch": "^1.4.3",
    "cmake-napi": "^1.2.1",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^2.0.0"
  },
  "scripts": {
    "test": "prettier . --check && npm run test:node && npm run test:bare",
    "test:node": "node test/all.js",
    "test:bare": "bare test/all.js"
  },
  "standard": {
    "ignore": [
      "/test/fixtures/*.js"
    ]
  },
  "engines": {
    "bare": ">=1.16.0"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/sodium-native.git"
  },
  "contributors": [
    "Emil Bay <github@tixz.dk> (http://bayes.dk)",
    "Mathias Buus <mathiasbuus@gmail.com> (https://mafinto.sh)",
    "Christophe Diederichs <chm-diederichs@hyperdivision.dk>"
  ],
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/holepunchto/sodium-native/issues"
  },
  "homepage": "https://github.com/holepunchto/sodium-native"
}
module.exports = require('sodium-native')
{
  "name": "sodium-universal",
  "version": "5.0.1",
  "description": "Universal wrapper for sodium-javascript and sodium-native working in Node.js and the Browser",
  "main": "index.js",
  "dependencies": {
    "sodium-native": "^5.0.1"
  },
  "peerDependencies": {
    "sodium-javascript": "~0.8.0"
  },
  "peerDependenciesMeta": {
    "sodium-javascript": {
      "optional": true
    }
  },
  "scripts": {
    "prepublish": "./build-scripts/generate.js"
  },
  "browser": {
    "sodium-native": "sodium-javascript"
  },
  "browserify": {
    "transform": [
      "./build-scripts/transform.js"
    ]
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/sodium-universal.git"
  },
  "keywords": [
    "libsodium",
    "sodium",
    "sodium-native",
    "sodium-javascript",
    "browserify"
  ],
  "contributors": [
    "Emil Bay <github@tixz.dk> (http://bayes.dk)",
    "Mathias Buus <mathiasbuus@gmail.com> (https://mafinto.sh)",
    "Christophe Diederichs <chm-diederichs@hyperdivision.dk>"
  ],
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/holepunchto/sodium-universal/issues"
  },
  "homepage": "https://github.com/holepunchto/sodium-universal#readme"
}
const { EventEmitter } = require('events-universal')
const FIFO = require('fast-fifo')
const TextDecoder = require('text-decoder')

const StreamError = require('./lib/errors.js')

// if we do a future major, expect queue microtask to be there always, for now a bit defensive
const qmt =
  typeof queueMicrotask === 'undefined' ? (fn) => global.process.nextTick(fn) : queueMicrotask

// 29 bits used total (4 from shared, 14 from read, and 11 from write)
const MAX = (1 << 29) - 1

// Shared state
const OPENING = 0b0001
const PREDESTROYING = 0b0010
const DESTROYING = 0b0100
const DESTROYED = 0b1000

const NOT_OPENING = MAX ^ OPENING
const NOT_PREDESTROYING = MAX ^ PREDESTROYING

// Read state (4 bit offset from shared state)
const READ_ACTIVE = 0b00000000000001 << 4
const READ_UPDATING = 0b00000000000010 << 4
const READ_PRIMARY = 0b00000000000100 << 4
const READ_QUEUED = 0b00000000001000 << 4
const READ_RESUMED = 0b00000000010000 << 4
const READ_PIPE_DRAINED = 0b00000000100000 << 4
const READ_ENDING = 0b00000001000000 << 4
const READ_EMIT_DATA = 0b00000010000000 << 4
const READ_EMIT_READABLE = 0b00000100000000 << 4
const READ_EMITTED_READABLE = 0b00001000000000 << 4
const READ_DONE = 0b00010000000000 << 4
const READ_NEXT_TICK = 0b00100000000000 << 4
const READ_NEEDS_PUSH = 0b01000000000000 << 4
const READ_READ_AHEAD = 0b10000000000000 << 4

// Combined read state
const READ_FLOWING = READ_RESUMED | READ_PIPE_DRAINED
const READ_ACTIVE_AND_NEEDS_PUSH = READ_ACTIVE | READ_NEEDS_PUSH
const READ_PRIMARY_AND_ACTIVE = READ_PRIMARY | READ_ACTIVE
const READ_EMIT_READABLE_AND_QUEUED = READ_EMIT_READABLE | READ_QUEUED
const READ_RESUMED_READ_AHEAD = READ_RESUMED | READ_READ_AHEAD

const READ_NOT_ACTIVE = MAX ^ READ_ACTIVE
const READ_NON_PRIMARY = MAX ^ READ_PRIMARY
const READ_NON_PRIMARY_AND_PUSHED = MAX ^ (READ_PRIMARY | READ_NEEDS_PUSH)
const READ_PUSHED = MAX ^ READ_NEEDS_PUSH
const READ_PAUSED = MAX ^ READ_RESUMED
const READ_NOT_QUEUED = MAX ^ (READ_QUEUED | READ_EMITTED_READABLE)
const READ_NOT_ENDING = MAX ^ READ_ENDING
const READ_PIPE_NOT_DRAINED = MAX ^ READ_FLOWING
const READ_NOT_NEXT_TICK = MAX ^ READ_NEXT_TICK
const READ_NOT_UPDATING = MAX ^ READ_UPDATING
const READ_NO_READ_AHEAD = MAX ^ READ_READ_AHEAD
const READ_PAUSED_NO_READ_AHEAD = MAX ^ READ_RESUMED_READ_AHEAD

// Write state (18 bit offset, 4 bit offset from shared state and 14 from read state)
const WRITE_ACTIVE = 0b00000000001 << 18
const WRITE_UPDATING = 0b00000000010 << 18
const WRITE_PRIMARY = 0b00000000100 << 18
const WRITE_QUEUED = 0b00000001000 << 18
const WRITE_UNDRAINED = 0b00000010000 << 18
const WRITE_DONE = 0b00000100000 << 18
const WRITE_EMIT_DRAIN = 0b00001000000 << 18
const WRITE_NEXT_TICK = 0b00010000000 << 18
const WRITE_WRITING = 0b00100000000 << 18
const WRITE_FINISHING = 0b01000000000 << 18
const WRITE_CORKED = 0b10000000000 << 18

const WRITE_NOT_ACTIVE = MAX ^ (WRITE_ACTIVE | WRITE_WRITING)
const WRITE_NON_PRIMARY = MAX ^ WRITE_PRIMARY
const WRITE_NOT_FINISHING = MAX ^ (WRITE_ACTIVE | WRITE_FINISHING)
const WRITE_DRAINED = MAX ^ WRITE_UNDRAINED
const WRITE_NOT_QUEUED = MAX ^ WRITE_QUEUED
const WRITE_NOT_NEXT_TICK = MAX ^ WRITE_NEXT_TICK
const WRITE_NOT_UPDATING = MAX ^ WRITE_UPDATING
const WRITE_NOT_CORKED = MAX ^ WRITE_CORKED

// Combined shared state
const ACTIVE = READ_ACTIVE | WRITE_ACTIVE
const NOT_ACTIVE = MAX ^ ACTIVE
const DONE = READ_DONE | WRITE_DONE
const DESTROY_STATUS = DESTROYING | DESTROYED | PREDESTROYING
const OPEN_STATUS = DESTROY_STATUS | OPENING
const AUTO_DESTROY = DESTROY_STATUS | DONE
const NON_PRIMARY = WRITE_NON_PRIMARY & READ_NON_PRIMARY
const ACTIVE_OR_TICKING = WRITE_NEXT_TICK | READ_NEXT_TICK
const TICKING = ACTIVE_OR_TICKING & NOT_ACTIVE
const IS_OPENING = OPEN_STATUS | TICKING

// Combined shared state and read state
const READ_PRIMARY_STATUS = OPEN_STATUS | READ_ENDING | READ_DONE
const READ_STATUS = OPEN_STATUS | READ_DONE | READ_QUEUED
const READ_ENDING_STATUS = OPEN_STATUS | READ_ENDING | READ_QUEUED
const READ_READABLE_STATUS = OPEN_STATUS | READ_EMIT_READABLE | READ_QUEUED | READ_EMITTED_READABLE
const SHOULD_NOT_READ =
  OPEN_STATUS | READ_ACTIVE | READ_ENDING | READ_DONE | READ_NEEDS_PUSH | READ_READ_AHEAD
const READ_BACKPRESSURE_STATUS = DESTROY_STATUS | READ_ENDING | READ_DONE
const READ_UPDATE_SYNC_STATUS = READ_UPDATING | OPEN_STATUS | READ_NEXT_TICK | READ_PRIMARY
const READ_NEXT_TICK_OR_OPENING = READ_NEXT_TICK | OPENING

// Combined write state
const WRITE_PRIMARY_STATUS = OPEN_STATUS | WRITE_FINISHING | WRITE_DONE
const WRITE_QUEUED_AND_UNDRAINED = WRITE_QUEUED | WRITE_UNDRAINED
const WRITE_QUEUED_AND_ACTIVE = WRITE_QUEUED | WRITE_ACTIVE
const WRITE_DRAIN_STATUS = WRITE_QUEUED | WRITE_UNDRAINED | OPEN_STATUS | WRITE_ACTIVE
const WRITE_STATUS = OPEN_STATUS | WRITE_ACTIVE | WRITE_QUEUED | WRITE_CORKED
const WRITE_PRIMARY_AND_ACTIVE = WRITE_PRIMARY | WRITE_ACTIVE
const WRITE_ACTIVE_AND_WRITING = WRITE_ACTIVE | WRITE_WRITING
const WRITE_FINISHING_STATUS = OPEN_STATUS | WRITE_FINISHING | WRITE_QUEUED_AND_ACTIVE | WRITE_DONE
const WRITE_BACKPRESSURE_STATUS = WRITE_UNDRAINED | DESTROY_STATUS | WRITE_FINISHING | WRITE_DONE
const WRITE_UPDATE_SYNC_STATUS = WRITE_UPDATING | OPEN_STATUS | WRITE_NEXT_TICK | WRITE_PRIMARY
const WRITE_DROP_DATA = WRITE_FINISHING | WRITE_DONE | DESTROY_STATUS

const asyncIterator = Symbol.asyncIterator || Symbol('asyncIterator')

class WritableState {
  constructor(
    stream,
    { highWaterMark = 16384, map = null, mapWritable, byteLength, byteLengthWritable } = {}
  ) {
    this.stream = stream
    this.queue = new FIFO()
    this.highWaterMark = highWaterMark
    this.buffered = 0
    this.error = null
    this.pipeline = null
    this.drains = null // if we add more seldomly used helpers we might them into a subobject so its a single ptr
    this.byteLength = byteLengthWritable || byteLength || defaultByteLength
    this.map = mapWritable || map
    this.afterWrite = afterWrite.bind(this)
    this.afterUpdateNextTick = updateWriteNT.bind(this)
  }

  get ending() {
    return (this.stream._duplexState & WRITE_FINISHING) !== 0
  }

  get ended() {
    return (this.stream._duplexState & WRITE_DONE) !== 0
  }

  push(data) {
    if ((this.stream._duplexState & WRITE_DROP_DATA) !== 0) return false
    if (this.map !== null) data = this.map(data)

    this.buffered += this.byteLength(data)
    this.queue.push(data)

    if (this.buffered < this.highWaterMark) {
      this.stream._duplexState |= WRITE_QUEUED
      return true
    }

    this.stream._duplexState |= WRITE_QUEUED_AND_UNDRAINED
    return false
  }

  shift() {
    const data = this.queue.shift()

    this.buffered -= this.byteLength(data)
    if (this.buffered === 0) this.stream._duplexState &= WRITE_NOT_QUEUED

    return data
  }

  end(data) {
    if (typeof data === 'function') {
      this.stream.once('finish', data)
    } else if (data !== undefined && data !== null) {
      this.push(data)
    }

    this.stream._duplexState = (this.stream._duplexState | WRITE_FINISHING) & WRITE_NON_PRIMARY
  }

  autoBatch(data, cb) {
    const buffer = []
    const stream = this.stream

    buffer.push(data)
    while ((stream._duplexState & WRITE_STATUS) === WRITE_QUEUED_AND_ACTIVE) {
      buffer.push(stream._writableState.shift())
    }

    if ((stream._duplexState & OPEN_STATUS) !== 0) return cb(null)
    stream._writev(buffer, cb)
  }

  update() {
    const stream = this.stream

    stream._duplexState |= WRITE_UPDATING

    do {
      while ((stream._duplexState & WRITE_STATUS) === WRITE_QUEUED) {
        const data = this.shift()
        stream._duplexState |= WRITE_ACTIVE_AND_WRITING
        stream._write(data, this.afterWrite)
      }

      if ((stream._duplexState & WRITE_PRIMARY_AND_ACTIVE) === 0) this.updateNonPrimary()
    } while (this.continueUpdate() === true)

    stream._duplexState &= WRITE_NOT_UPDATING
  }

  updateNonPrimary() {
    const stream = this.stream

    if ((stream._duplexState & WRITE_FINISHING_STATUS) === WRITE_FINISHING) {
      stream._duplexState = stream._duplexState | WRITE_ACTIVE
      stream._final(afterFinal.bind(this))
      return
    }

    if ((stream._duplexState & DESTROY_STATUS) === DESTROYING) {
      if ((stream._duplexState & ACTIVE_OR_TICKING) === 0) {
        stream._duplexState |= ACTIVE
        stream._destroy(afterDestroy.bind(this))
      }
      return
    }

    if ((stream._duplexState & IS_OPENING) === OPENING) {
      stream._duplexState = (stream._duplexState | ACTIVE) & NOT_OPENING
      stream._open(afterOpen.bind(this))
    }
  }

  continueUpdate() {
    if ((this.stream._duplexState & WRITE_NEXT_TICK) === 0) return false
    this.stream._duplexState &= WRITE_NOT_NEXT_TICK
    return true
  }

  updateCallback() {
    if ((this.stream._duplexState & WRITE_UPDATE_SYNC_STATUS) === WRITE_PRIMARY) {
      this.update()
    } else {
      this.updateNextTick()
    }
  }

  updateNextTick() {
    if ((this.stream._duplexState & WRITE_NEXT_TICK) !== 0) return
    this.stream._duplexState |= WRITE_NEXT_TICK
    if ((this.stream._duplexState & WRITE_UPDATING) === 0) qmt(this.afterUpdateNextTick)
  }
}

class ReadableState {
  constructor(
    stream,
    { highWaterMark = 16384, map = null, mapReadable, byteLength, byteLengthReadable } = {}
  ) {
    this.stream = stream
    this.queue = new FIFO()
    this.highWaterMark = highWaterMark === 0 ? 1 : highWaterMark
    this.buffered = 0
    this.readAhead = highWaterMark > 0
    this.error = null
    this.pipeline = null
    this.byteLength = byteLengthReadable || byteLength || defaultByteLength
    this.map = mapReadable || map
    this.pipeTo = null
    this.afterRead = afterRead.bind(this)
    this.afterUpdateNextTick = updateReadNT.bind(this)
  }

  get ending() {
    return (this.stream._duplexState & READ_ENDING) !== 0
  }

  get ended() {
    return (this.stream._duplexState & READ_DONE) !== 0
  }

  pipe(pipeTo, cb) {
    if (this.pipeTo !== null) throw StreamError.BAD_ARGUMENT('Can only pipe to one destination')
    if (typeof cb !== 'function') cb = null

    this.stream._duplexState |= READ_PIPE_DRAINED
    this.pipeTo = pipeTo
    this.pipeline = new Pipeline(this.stream, pipeTo, cb)

    if (cb) this.stream.on('error', noop) // We already error handle this so supress crashes

    if (isStreamx(pipeTo)) {
      pipeTo._writableState.pipeline = this.pipeline
      if (cb) pipeTo.on('error', noop) // We already error handle this so supress crashes
      pipeTo.on('finish', this.pipeline.finished.bind(this.pipeline)) // TODO: just call finished from pipeTo itself
    } else {
      const onerror = this.pipeline.done.bind(this.pipeline, pipeTo)
      const onclose = this.pipeline.done.bind(this.pipeline, pipeTo, null) // onclose has a weird bool arg
      pipeTo.on('error', onerror)
      pipeTo.on('close', onclose)
      pipeTo.on('finish', this.pipeline.finished.bind(this.pipeline))
    }

    pipeTo.on('drain', afterDrain.bind(this))
    this.stream.emit('piping', pipeTo)
    pipeTo.emit('pipe', this.stream)
  }

  push(data) {
    const stream = this.stream

    if (data === null) {
      this.highWaterMark = 0
      stream._duplexState = (stream._duplexState | READ_ENDING) & READ_NON_PRIMARY_AND_PUSHED
      return false
    }

    if (this.map !== null) {
      data = this.map(data)
      if (data === null) {
        stream._duplexState &= READ_PUSHED
        return this.buffered < this.highWaterMark
      }
    }

    this.buffered += this.byteLength(data)
    this.queue.push(data)

    stream._duplexState = (stream._duplexState | READ_QUEUED) & READ_PUSHED

    return this.buffered < this.highWaterMark
  }

  shift() {
    const data = this.queue.shift()

    this.buffered -= this.byteLength(data)
    if (this.buffered === 0) {
      this.stream._duplexState &= READ_NOT_QUEUED
    }

    return data
  }

  unshift(data) {
    const pending = [this.map !== null ? this.map(data) : data]
    while (this.buffered > 0) pending.push(this.shift())

    for (let i = 0; i < pending.length - 1; i++) {
      const data = pending[i]
      this.buffered += this.byteLength(data)
      this.queue.push(data)
    }

    this.push(pending[pending.length - 1])
  }

  read() {
    const stream = this.stream

    if ((stream._duplexState & READ_STATUS) === READ_QUEUED) {
      const data = this.shift()

      if (this.pipeTo !== null && this.pipeTo.write(data) === false) {
        stream._duplexState &= READ_PIPE_NOT_DRAINED
      }

      if ((stream._duplexState & READ_EMIT_DATA) !== 0) {
        stream.emit('data', data)
      }

      return data
    }

    if (this.readAhead === false) {
      stream._duplexState |= READ_READ_AHEAD
      this.updateNextTick()
    }

    return null
  }

  drain() {
    const stream = this.stream

    while (
      (stream._duplexState & READ_STATUS) === READ_QUEUED &&
      (stream._duplexState & READ_FLOWING) !== 0
    ) {
      const data = this.shift()

      if (this.pipeTo !== null && this.pipeTo.write(data) === false) {
        stream._duplexState &= READ_PIPE_NOT_DRAINED
      }

      if ((stream._duplexState & READ_EMIT_DATA) !== 0) {
        stream.emit('data', data)
      }
    }
  }

  update() {
    const stream = this.stream

    stream._duplexState |= READ_UPDATING

    do {
      this.drain()

      while (
        this.buffered < this.highWaterMark &&
        (stream._duplexState & SHOULD_NOT_READ) === READ_READ_AHEAD
      ) {
        stream._duplexState |= READ_ACTIVE_AND_NEEDS_PUSH
        stream._read(this.afterRead)
        this.drain()
      }

      if ((stream._duplexState & READ_READABLE_STATUS) === READ_EMIT_READABLE_AND_QUEUED) {
        stream._duplexState |= READ_EMITTED_READABLE
        stream.emit('readable')
      }

      if ((stream._duplexState & READ_PRIMARY_AND_ACTIVE) === 0) {
        this.updateNonPrimary()
      }
    } while (this.continueUpdate() === true)

    stream._duplexState &= READ_NOT_UPDATING
  }

  updateNonPrimary() {
    const stream = this.stream

    if ((stream._duplexState & READ_ENDING_STATUS) === READ_ENDING) {
      stream._duplexState = (stream._duplexState | READ_DONE) & READ_NOT_ENDING
      stream.emit('end')

      if ((stream._duplexState & AUTO_DESTROY) === DONE) {
        stream._duplexState |= DESTROYING
      }

      if (this.pipeTo !== null) {
        this.pipeTo.end()
      }
    }

    if ((stream._duplexState & DESTROY_STATUS) === DESTROYING) {
      if ((stream._duplexState & ACTIVE_OR_TICKING) === 0) {
        stream._duplexState |= ACTIVE
        stream._destroy(afterDestroy.bind(this))
      }
      return
    }

    if ((stream._duplexState & IS_OPENING) === OPENING) {
      stream._duplexState = (stream._duplexState | ACTIVE) & NOT_OPENING
      stream._open(afterOpen.bind(this))
    }
  }

  continueUpdate() {
    if ((this.stream._duplexState & READ_NEXT_TICK) === 0) return false
    this.stream._duplexState &= READ_NOT_NEXT_TICK
    return true
  }

  updateCallback() {
    if ((this.stream._duplexState & READ_UPDATE_SYNC_STATUS) === READ_PRIMARY) {
      this.update()
    } else {
      this.updateNextTick()
    }
  }

  updateNextTickIfOpen() {
    if ((this.stream._duplexState & READ_NEXT_TICK_OR_OPENING) !== 0) return
    this.stream._duplexState |= READ_NEXT_TICK
    if ((this.stream._duplexState & READ_UPDATING) === 0) qmt(this.afterUpdateNextTick)
  }

  updateNextTick() {
    if ((this.stream._duplexState & READ_NEXT_TICK) !== 0) return
    this.stream._duplexState |= READ_NEXT_TICK
    if ((this.stream._duplexState & READ_UPDATING) === 0) qmt(this.afterUpdateNextTick)
  }
}

class TransformState {
  constructor(stream) {
    this.data = null
    this.afterTransform = afterTransform.bind(stream)
    this.afterFinal = null
  }
}

class Pipeline {
  constructor(src, dst, cb) {
    this.from = src
    this.to = dst
    this.afterPipe = cb
    this.error = null
    this.pipeToFinished = false
  }

  finished() {
    this.pipeToFinished = true
  }

  done(stream, err) {
    if (err) this.error = err

    if (stream === this.to) {
      this.to = null

      if (this.from !== null) {
        if ((this.from._duplexState & READ_DONE) === 0 || !this.pipeToFinished) {
          this.from.destroy(this.error || StreamError.PREMATURE_CLOSE('Writable stream closed'))
        }
        return
      }
    }

    if (stream === this.from) {
      this.from = null

      if (this.to !== null) {
        if ((stream._duplexState & READ_DONE) === 0) {
          this.to.destroy(this.error || StreamError.PREMATURE_CLOSE('Readable stream closed'))
        }
        return
      }
    }

    if (this.afterPipe !== null) this.afterPipe(this.error)
    this.to = this.from = this.afterPipe = null
  }
}

function afterDrain() {
  this.stream._duplexState |= READ_PIPE_DRAINED
  this.updateCallback()
}

function afterFinal(err) {
  const stream = this.stream
  if (err) stream.destroy(err)

  if ((stream._duplexState & DESTROY_STATUS) === 0) {
    stream._duplexState |= WRITE_DONE
    stream.emit('finish')
  }

  if ((stream._duplexState & AUTO_DESTROY) === DONE) {
    stream._duplexState |= DESTROYING
  }

  stream._duplexState &= WRITE_NOT_FINISHING

  // no need to wait the extra tick here, so we short circuit that
  if ((stream._duplexState & WRITE_UPDATING) === 0) {
    this.update()
  } else {
    this.updateNextTick()
  }
}

function afterDestroy(err) {
  const stream = this.stream

  if (!err && !StreamError.isStreamDestroyed(this.error)) err = this.error
  if (err) stream.emit('error', err)

  stream._duplexState |= DESTROYED
  stream.emit('close')

  const rs = stream._readableState
  const ws = stream._writableState

  if (rs !== null && rs.pipeline !== null) {
    rs.pipeline.done(stream, err)
  }

  if (ws !== null) {
    while (ws.drains !== null && ws.drains.length > 0) {
      ws.drains.shift().resolve(false)
    }

    if (ws.pipeline !== null) {
      ws.pipeline.done(stream, err)
    }
  }
}

function afterWrite(err) {
  const stream = this.stream

  if (err) stream.destroy(err)
  stream._duplexState &= WRITE_NOT_ACTIVE

  if (this.drains !== null) tickDrains(this.drains)

  if ((stream._duplexState & WRITE_DRAIN_STATUS) === WRITE_UNDRAINED) {
    stream._duplexState &= WRITE_DRAINED

    if ((stream._duplexState & WRITE_EMIT_DRAIN) === WRITE_EMIT_DRAIN) {
      stream.emit('drain')
    }
  }

  this.updateCallback()
}

function afterRead(err) {
  if (err) this.stream.destroy(err)
  this.stream._duplexState &= READ_NOT_ACTIVE

  if (this.readAhead === false && (this.stream._duplexState & READ_RESUMED) === 0) {
    this.stream._duplexState &= READ_NO_READ_AHEAD
  }

  this.updateCallback()
}

function updateReadNT() {
  if ((this.stream._duplexState & READ_UPDATING) === 0) {
    this.stream._duplexState &= READ_NOT_NEXT_TICK
    this.update()
  }
}

function updateWriteNT() {
  if ((this.stream._duplexState & WRITE_UPDATING) === 0) {
    this.stream._duplexState &= WRITE_NOT_NEXT_TICK
    this.update()
  }
}

function tickDrains(drains) {
  for (let i = 0; i < drains.length; i++) {
    // drains.writes are monotonic, so if one is 0 its always the first one
    if (--drains[i].writes === 0) {
      drains.shift().resolve(true)
      i--
    }
  }
}

function afterOpen(err) {
  const stream = this.stream

  if (err) stream.destroy(err)

  if ((stream._duplexState & DESTROYING) === 0) {
    if ((stream._duplexState & READ_PRIMARY_STATUS) === 0) {
      stream._duplexState |= READ_PRIMARY
    }

    if ((stream._duplexState & WRITE_PRIMARY_STATUS) === 0) {
      stream._duplexState |= WRITE_PRIMARY
    }

    stream.emit('open')
  }

  stream._duplexState &= NOT_ACTIVE

  if (stream._writableState !== null) {
    stream._writableState.updateCallback()
  }

  if (stream._readableState !== null) {
    stream._readableState.updateCallback()
  }
}

function afterTransform(err, data) {
  if (data !== undefined && data !== null) this.push(data)
  this._writableState.afterWrite(err)
}

function newListener(name) {
  if (this._readableState !== null) {
    if (name === 'data') {
      this._duplexState |= READ_EMIT_DATA | READ_RESUMED_READ_AHEAD
      this._readableState.updateNextTick()
    }
    if (name === 'readable') {
      this._duplexState |= READ_EMIT_READABLE
      this._readableState.updateNextTick()
    }
  }

  if (this._writableState !== null) {
    if (name === 'drain') {
      this._duplexState |= WRITE_EMIT_DRAIN
      this._writableState.updateNextTick()
    }
  }
}

class Stream extends EventEmitter {
  constructor(opts) {
    super()

    this._duplexState = 0
    this._readableState = null
    this._writableState = null

    if (opts) {
      if (opts.open) this._open = opts.open
      if (opts.destroy) this._destroy = opts.destroy
      if (opts.predestroy) this._predestroy = opts.predestroy
      if (opts.signal) opts.signal.addEventListener('abort', abort.bind(this))
    }

    this.on('newListener', newListener)
  }

  _open(cb) {
    cb(null)
  }

  _destroy(cb) {
    cb(null)
  }

  _predestroy() {
    // does nothing
  }

  get readable() {
    return this._readableState !== null ? true : undefined
  }

  get writable() {
    return this._writableState !== null ? true : undefined
  }

  get destroyed() {
    return (this._duplexState & DESTROYED) !== 0
  }

  get destroying() {
    return (this._duplexState & DESTROY_STATUS) !== 0
  }

  destroy(err) {
    if ((this._duplexState & DESTROY_STATUS) === 0) {
      if (!err) err = StreamError.STREAM_DESTROYED()
      this._duplexState = (this._duplexState | DESTROYING) & NON_PRIMARY

      if (this._readableState !== null) {
        this._readableState.highWaterMark = 0
        this._readableState.error = err
      }

      if (this._writableState !== null) {
        this._writableState.highWaterMark = 0
        this._writableState.error = err
      }

      this._duplexState |= PREDESTROYING
      this._predestroy()
      this._duplexState &= NOT_PREDESTROYING

      if (this._readableState !== null) {
        this._readableState.updateNextTick()
      }

      if (this._writableState !== null) {
        this._writableState.updateNextTick()
      }
    }
  }
}

class Readable extends Stream {
  constructor(opts) {
    super(opts)

    this._duplexState |= OPENING | WRITE_DONE | READ_READ_AHEAD
    this._readableState = new ReadableState(this, opts)

    if (opts) {
      if (this._readableState.readAhead === false) this._duplexState &= READ_NO_READ_AHEAD
      if (opts.read) this._read = opts.read
      if (opts.eagerOpen) this._readableState.updateNextTick()
      if (opts.encoding) this.setEncoding(opts.encoding)
    }
  }

  static deferred(fn, opts) {
    const out = new PassThrough(opts)

    fn()
      .then((src) => {
        if (src === null) return out.end()
        if (out.destroying) return
        pipeline(src, out, noop)
      })
      .catch((err) => out.destroy(err))

    return out
  }

  setEncoding(encoding) {
    const dec = new TextDecoder(encoding)
    const map = this._readableState.map || echo
    this._readableState.map = mapOrSkip
    return this

    function mapOrSkip(data) {
      const next = dec.push(data)
      return next === '' && (data.byteLength !== 0 || dec.remaining > 0) ? null : map(next)
    }
  }

  _read(cb) {
    cb(null)
  }

  pipe(dest, cb) {
    this._readableState.updateNextTick()
    this._readableState.pipe(dest, cb)
    return dest
  }

  read() {
    this._readableState.updateNextTick()
    return this._readableState.read()
  }

  push(data) {
    this._readableState.updateNextTickIfOpen()
    return this._readableState.push(data)
  }

  unshift(data) {
    this._readableState.updateNextTickIfOpen()
    return this._readableState.unshift(data)
  }

  resume() {
    this._duplexState |= READ_RESUMED_READ_AHEAD
    this._readableState.updateNextTick()
    return this
  }

  pause() {
    this._duplexState &=
      this._readableState.readAhead === false ? READ_PAUSED_NO_READ_AHEAD : READ_PAUSED
    return this
  }

  static _fromAsyncIterator(ite, opts) {
    let destroy

    const rs = new Readable({
      ...opts,
      read(cb) {
        ite.next().then(push).then(cb.bind(null, null)).catch(cb)
      },
      predestroy() {
        destroy = ite.return()
      },
      destroy(cb) {
        if (!destroy) return cb(null)
        destroy.then(cb.bind(null, null)).catch(cb)
      }
    })

    return rs

    function push(data) {
      if (data.done) rs.push(null)
      else rs.push(data.value)
    }
  }

  static from(data, opts) {
    if (isReadStreamx(data)) return data
    if (data[asyncIterator]) return this._fromAsyncIterator(data[asyncIterator](), opts)
    if (!Array.isArray(data)) data = data === undefined ? [] : [data]

    let i = 0
    return new Readable({
      ...opts,
      read(cb) {
        this.push(i === data.length ? null : data[i++])
        cb(null)
      }
    })
  }

  static isBackpressured(rs) {
    return (
      (rs._duplexState & READ_BACKPRESSURE_STATUS) !== 0 ||
      rs._readableState.buffered >= rs._readableState.highWaterMark
    )
  }

  static isPaused(rs) {
    return (rs._duplexState & READ_RESUMED) === 0
  }

  [asyncIterator]() {
    const stream = this

    let error = null
    let promiseResolve = null
    let promiseReject = null

    this.on('error', (err) => {
      error = err
    })
    this.on('readable', onreadable)
    this.on('close', onclose)

    return {
      [asyncIterator]() {
        return this
      },
      next() {
        return new Promise(function (resolve, reject) {
          promiseResolve = resolve
          promiseReject = reject
          const data = stream.read()
          if (data !== null) ondata(data)
          else if ((stream._duplexState & DESTROYED) !== 0) ondata(null)
        })
      },
      return() {
        return destroy(null)
      },
      throw(err) {
        return destroy(err)
      }
    }

    function onreadable() {
      if (promiseResolve !== null) ondata(stream.read())
    }

    function onclose() {
      if (promiseResolve !== null) ondata(null)
    }

    function ondata(data) {
      if (promiseReject === null) return
      if (error) {
        promiseReject(error)
      } else if (data === null && (stream._duplexState & READ_DONE) === 0) {
        promiseReject(StreamError.STREAM_DESTROYED())
      } else {
        promiseResolve({ value: data, done: data === null })
      }
      promiseReject = promiseResolve = null
    }

    function destroy(err) {
      stream.destroy(err)
      return new Promise((resolve, reject) => {
        if (stream._duplexState & DESTROYED) return resolve({ value: undefined, done: true })
        stream.once('close', function () {
          if (err) reject(err)
          else resolve({ value: undefined, done: true })
        })
      })
    }
  }
}

class Writable extends Stream {
  constructor(opts) {
    super(opts)

    this._duplexState |= OPENING | READ_DONE
    this._writableState = new WritableState(this, opts)

    if (opts) {
      if (opts.writev) this._writev = opts.writev
      if (opts.write) this._write = opts.write
      if (opts.final) this._final = opts.final
      if (opts.eagerOpen) this._writableState.updateNextTick()
    }
  }

  cork() {
    this._duplexState |= WRITE_CORKED
  }

  uncork() {
    this._duplexState &= WRITE_NOT_CORKED
    this._writableState.updateNextTick()
  }

  _writev(batch, cb) {
    cb(null)
  }

  _write(data, cb) {
    this._writableState.autoBatch(data, cb)
  }

  _final(cb) {
    cb(null)
  }

  static isBackpressured(ws) {
    return (ws._duplexState & WRITE_BACKPRESSURE_STATUS) !== 0
  }

  static drained(ws) {
    if (ws.destroyed) return Promise.resolve(false)

    const state = ws._writableState
    const pending = isWritev(ws) ? Math.min(1, state.queue.length) : state.queue.length
    const writes = pending + (ws._duplexState & WRITE_WRITING ? 1 : 0)
    if (writes === 0) return Promise.resolve(true)

    if (state.drains === null) state.drains = []
    return new Promise((resolve) => {
      state.drains.push({ writes, resolve })
    })
  }

  write(data) {
    this._writableState.updateNextTick()
    return this._writableState.push(data)
  }

  end(data) {
    this._writableState.updateNextTick()
    this._writableState.end(data)
    return this
  }
}

class Duplex extends Readable {
  // and Writable
  constructor(opts) {
    super(opts)

    this._duplexState = OPENING | (this._duplexState & READ_READ_AHEAD)
    this._writableState = new WritableState(this, opts)

    if (opts) {
      if (opts.writev) this._writev = opts.writev
      if (opts.write) this._write = opts.write
      if (opts.final) this._final = opts.final
    }
  }

  cork() {
    this._duplexState |= WRITE_CORKED
  }

  uncork() {
    this._duplexState &= WRITE_NOT_CORKED
    this._writableState.updateNextTick()
  }

  _writev(batch, cb) {
    cb(null)
  }

  _write(data, cb) {
    this._writableState.autoBatch(data, cb)
  }

  _final(cb) {
    cb(null)
  }

  write(data) {
    this._writableState.updateNextTick()
    return this._writableState.push(data)
  }

  end(data) {
    this._writableState.updateNextTick()
    this._writableState.end(data)
    return this
  }
}

class Transform extends Duplex {
  constructor(opts) {
    super(opts)
    this._transformState = new TransformState(this)

    if (opts) {
      if (opts.transform) this._transform = opts.transform
      if (opts.flush) this._flush = opts.flush
    }
  }

  _write(data, cb) {
    if (this._readableState.buffered >= this._readableState.highWaterMark) {
      this._transformState.data = data
    } else {
      this._transform(data, this._transformState.afterTransform)
    }
  }

  _read(cb) {
    if (this._transformState.data !== null) {
      const data = this._transformState.data
      this._transformState.data = null
      cb(null)
      this._transform(data, this._transformState.afterTransform)
    } else {
      cb(null)
    }
  }

  destroy(err) {
    super.destroy(err)
    if (this._transformState.data !== null) {
      this._transformState.data = null
      this._transformState.afterTransform()
    }
  }

  _transform(data, cb) {
    cb(null, data)
  }

  _flush(cb) {
    cb(null)
  }

  _final(cb) {
    this._transformState.afterFinal = cb
    this._flush(transformAfterFlush.bind(this))
  }
}

class PassThrough extends Transform {}

function transformAfterFlush(err, data) {
  const cb = this._transformState.afterFinal
  if (err) return cb(err)

  if (data !== null && data !== undefined) this.push(data)
  this.push(null)
  cb(null)
}

function pipelinePromise(...streams) {
  return new Promise((resolve, reject) => {
    return pipeline(...streams, (err) => {
      if (err) return reject(err)
      resolve()
    })
  })
}

function pipeline(stream, ...streams) {
  const all = Array.isArray(stream) ? [...stream, ...streams] : [stream, ...streams]
  const done = all.length && typeof all[all.length - 1] === 'function' ? all.pop() : null

  if (all.length < 2) throw StreamError.BAD_ARGUMENT('Pipeline requires at least 2 streams')

  let src = all[0]
  let dest = null
  let error = null

  for (let i = 1; i < all.length; i++) {
    dest = all[i]

    if (isStreamx(src)) {
      src.pipe(dest, onerror)
    } else {
      errorHandle(src, true, i > 1, onerror)
      src.pipe(dest)
    }

    src = dest
  }

  if (done) {
    let fin = false

    const autoDestroy =
      isStreamx(dest) || !!(dest._writableState && dest._writableState.autoDestroy)

    dest.on('error', (err) => {
      if (error === null) error = err
    })

    dest.on('finish', () => {
      fin = true
      if (!autoDestroy) done(error)
    })

    if (autoDestroy) {
      dest.on('close', () => done(error || (fin ? null : StreamError.PREMATURE_CLOSE())))
    }
  }

  return dest

  function errorHandle(s, rd, wr, onerror) {
    s.on('error', onerror)
    s.on('close', onclose)

    function onclose() {
      if (rd && s._readableState && !s._readableState.ended) {
        return onerror(StreamError.PREMATURE_CLOSE())
      }
      if (wr && s._writableState && !s._writableState.ended) {
        return onerror(StreamError.PREMATURE_CLOSE())
      }
    }
  }

  function onerror(err) {
    if (!err || error) return
    error = err

    for (const s of all) {
      s.destroy(err)
    }
  }
}

function echo(s) {
  return s
}

function isStream(stream) {
  return !!stream._readableState || !!stream._writableState
}

function isStreamx(stream) {
  return typeof stream._duplexState === 'number' && isStream(stream)
}

function isEnding(stream) {
  return !!stream._readableState && stream._readableState.ending
}

function isEnded(stream) {
  return !!stream._readableState && stream._readableState.ended
}

function isFinishing(stream) {
  return !!stream._writableState && stream._writableState.ending
}

function isFinished(stream) {
  return !!stream._writableState && stream._writableState.ended
}

function getStreamError(stream, opts = {}) {
  const err =
    (stream._readableState && stream._readableState.error) ||
    (stream._writableState && stream._writableState.error)

  // avoid implicit errors by default
  return !opts.all && StreamError.isStreamDestroyed(err) ? null : err
}

function isReadStreamx(stream) {
  return isStreamx(stream) && stream.readable
}

function isDisturbed(stream) {
  return (
    (stream._duplexState & OPENING) !== OPENING ||
    (stream._duplexState & DESTROYING) === DESTROYING ||
    (stream._duplexState & ACTIVE_OR_TICKING) !== 0
  )
}

function isTypedArray(data) {
  return typeof data === 'object' && data !== null && typeof data.byteLength === 'number'
}

function defaultByteLength(data) {
  return isTypedArray(data) ? data.byteLength : 1024
}

function noop() {}

function abort() {
  this.destroy(StreamError.ABORTED())
}

function isWritev(s) {
  return s._writev !== Writable.prototype._writev && s._writev !== Duplex.prototype._writev
}

module.exports = {
  pipeline,
  pipelinePromise,
  isStream,
  isStreamx,
  isEnding,
  isEnded,
  isFinishing,
  isFinished,
  isDisturbed,
  getStreamError,
  Stream,
  Writable,
  Readable,
  Duplex,
  Transform,
  // Export PassThrough for compatibility with Node.js core's stream module
  PassThrough
}
module.exports = class StreamError extends Error {
  constructor(msg, code, fn = StreamError) {
    super(msg)

    this.code = code

    if (Error.captureStackTrace) {
      Error.captureStackTrace(this, fn)
    }
  }

  static isStreamDestroyed(err) {
    return err && err.code === 'STREAM_DESTROYED'
  }

  static isPrematureClose(err) {
    return err && err.code === 'PREMATURE_CLOSE'
  }

  static isAborted(err) {
    return err && err.code === 'ABORTED'
  }

  static isBadArgument(err) {
    return err && err.code === 'BAD_ARGUMENT'
  }

  get name() {
    return 'StreamError'
  }

  static STREAM_DESTROYED() {
    return new StreamError('Stream was destroyed', 'STREAM_DESTROYED', StreamError.STREAM_DESTROYED)
  }

  static PREMATURE_CLOSE(msg = 'Premature close') {
    return new StreamError(msg, 'PREMATURE_CLOSE', StreamError.PREMATURE_CLOSE)
  }

  static ABORTED() {
    return new StreamError('Stream aborted', 'ABORTED', StreamError.ABORTED)
  }

  static BAD_ARGUMENT(msg = 'Bad argument') {
    return new StreamError(msg, 'BAD_ARGUMENT', StreamError.BAD_ARGUMENT)
  }
}
{
  "name": "streamx",
  "version": "2.28.0",
  "description": "An iteration of the Node.js core streams with a series of improvements",
  "main": "index.js",
  "exports": {
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    },
    "./package": "./package.json",
    "./errors": {
      "types": "./lib/errors.d.ts",
      "default": "./lib/errors"
    }
  },
  "dependencies": {
    "events-universal": "^1.0.0",
    "fast-fifo": "^1.3.2",
    "text-decoder": "^1.1.0"
  },
  "devDependencies": {
    "b4a": "^1.6.6",
    "brittle": "^3.1.1",
    "end-of-stream": "^1.4.4",
    "lunte": "^1.8.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^2.0.0"
  },
  "files": [
    "index.js",
    "index.d.ts",
    "runtime.d.ts",
    "lib"
  ],
  "scripts": {
    "format": "prettier . --write",
    "test": "brittle test/all.js && brittle-bare test/all.js",
    "lint": "prettier . --check && lunte"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/mafintosh/streamx.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/mafintosh/streamx/issues"
  },
  "homepage": "https://github.com/mafintosh/streamx"
}
const { Readable } = require('streamx')

module.exports = function (s, forks = 2) {
  const streams = new Array(forks)
  const status = new Array(forks).fill(true)

  let ended = false

  for (let i = 0; i < forks; i++) {
    streams[i] = new Readable({
      read (cb) {
        const check = !status[i]
        status[i] = true
        if (check && allReadable()) s.resume()
        cb(null)
      }
    })
  }

  s.on('end', function () {
    ended = true
    for (const stream of streams) stream.push(null)
  })

  s.on('error', function (err) {
    for (const stream of streams) stream.destroy(err)
  })

  s.on('close', function () {
    if (ended) return
    for (const stream of streams) stream.destroy()
  })

  s.on('data', function (data) {
    let needsPause = false
    for (let i = 0; i < streams.length; i++) {
      if (!(status[i] = streams[i].push(data))) {
        needsPause = true
      }
    }
    if (needsPause) s.pause()
  })

  return streams

  function allReadable () {
    for (let j = 0; j < status.length; j++) {
      if (!status[j]) return false
    }
    return true
  }
}
{
  "name": "teex",
  "version": "1.0.1",
  "description": "Turn a readable stream into multiple readable streamx streams",
  "main": "index.js",
  "dependencies": {
    "streamx": "^2.12.5"
  },
  "devDependencies": {
    "standard": "^14.3.4",
    "tape": "^5.0.1"
  },
  "scripts": {
    "test": "standard && tape test.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/mafintosh/teex.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/mafintosh/teex/issues"
  },
  "homepage": "https://github.com/mafintosh/teex"
}
const PassThroughDecoder = require('./lib/pass-through-decoder')
const UTF8Decoder = require('./lib/utf8-decoder')

module.exports = class TextDecoder {
  constructor(encoding = 'utf8') {
    this.encoding = normalizeEncoding(encoding)

    switch (this.encoding) {
      case 'utf8':
        this.decoder = new UTF8Decoder()
        break
      case 'utf16le':
      case 'base64':
        throw new Error('Unsupported encoding: ' + this.encoding)
      default:
        this.decoder = new PassThroughDecoder(this.encoding)
    }
  }

  get remaining() {
    return this.decoder.remaining
  }

  push(data) {
    if (typeof data === 'string') return data
    return this.decoder.decode(data)
  }

  // For Node.js compatibility
  write(data) {
    return this.push(data)
  }

  end(data) {
    let result = ''
    if (data) result = this.push(data)
    result += this.decoder.flush()
    return result
  }
}

function normalizeEncoding(encoding) {
  encoding = encoding.toLowerCase()

  switch (encoding) {
    case 'utf8':
    case 'utf-8':
      return 'utf8'
    case 'ucs2':
    case 'ucs-2':
    case 'utf16le':
    case 'utf-16le':
      return 'utf16le'
    case 'latin1':
    case 'binary':
      return 'latin1'
    case 'base64':
    case 'ascii':
    case 'hex':
      return encoding
    default:
      throw new Error('Unknown encoding: ' + encoding)
  }
}
const b4a = require('b4a')

module.exports = class PassThroughDecoder {
  constructor(encoding) {
    this.encoding = encoding
  }

  get remaining() {
    return 0
  }

  decode(data) {
    return b4a.toString(data, this.encoding)
  }

  flush() {
    return ''
  }
}
const b4a = require('b4a')

/**
 * https://encoding.spec.whatwg.org/#utf-8-decoder
 */
module.exports = class UTF8Decoder {
  constructor() {
    this._reset()
  }

  get remaining() {
    return this.bytesSeen
  }

  decode(data) {
    if (data.byteLength === 0) return ''

    if (this.bytesNeeded === 0 && trailingIncomplete(data, 0) === 0) {
      this.bytesSeen = trailingBytesSeen(data)
      return b4a.toString(data, 'utf8')
    }

    let result = ''
    let start = 0

    if (this.bytesNeeded > 0) {
      while (start < data.byteLength) {
        const byte = data[start]

        if (byte < this.lowerBoundary || byte > this.upperBoundary) {
          result += '\ufffd'
          this._reset()
          break
        }

        this.lowerBoundary = 0x80
        this.upperBoundary = 0xbf
        this.codePoint = (this.codePoint << 6) | (byte & 0x3f)
        this.bytesSeen++
        start++

        if (this.bytesSeen === this.bytesNeeded) {
          result += String.fromCodePoint(this.codePoint)
          this._reset()
          break
        }
      }

      if (this.bytesNeeded > 0) return result
    }

    const trailing = trailingIncomplete(data, start)
    const end = data.byteLength - trailing

    if (end > start) result += b4a.toString(data, 'utf8', start, end)

    for (let i = end; i < data.byteLength; i++) {
      const byte = data[i]

      if (this.bytesNeeded === 0) {
        if (byte <= 0x7f) {
          this.bytesSeen = 0
          result += String.fromCharCode(byte)
        } else if (byte >= 0xc2 && byte <= 0xdf) {
          this.bytesNeeded = 2
          this.bytesSeen = 1
          this.codePoint = byte & 0x1f
        } else if (byte >= 0xe0 && byte <= 0xef) {
          if (byte === 0xe0) this.lowerBoundary = 0xa0
          else if (byte === 0xed) this.upperBoundary = 0x9f
          this.bytesNeeded = 3
          this.bytesSeen = 1
          this.codePoint = byte & 0xf
        } else if (byte >= 0xf0 && byte <= 0xf4) {
          if (byte === 0xf0) this.lowerBoundary = 0x90
          else if (byte === 0xf4) this.upperBoundary = 0x8f
          this.bytesNeeded = 4
          this.bytesSeen = 1
          this.codePoint = byte & 0x7
        } else {
          this.bytesSeen = 1
          result += '\ufffd'
        }

        continue
      }

      if (byte < this.lowerBoundary || byte > this.upperBoundary) {
        result += '\ufffd'
        i--
        this._reset()

        continue
      }

      this.lowerBoundary = 0x80
      this.upperBoundary = 0xbf

      this.codePoint = (this.codePoint << 6) | (byte & 0x3f)
      this.bytesSeen++

      if (this.bytesSeen === this.bytesNeeded) {
        result += String.fromCodePoint(this.codePoint)
        this._reset()
      }
    }

    return result
  }

  flush() {
    const result = this.bytesNeeded > 0 ? '\ufffd' : ''
    this._reset()
    return result
  }

  _reset() {
    this.codePoint = 0
    this.bytesNeeded = 0
    this.bytesSeen = 0
    this.lowerBoundary = 0x80
    this.upperBoundary = 0xbf
  }
}

function trailingIncomplete(data, start) {
  const len = data.byteLength
  if (len <= start) return 0

  const limit = Math.max(start, len - 4)

  let i = len - 1
  while (i > limit && (data[i] & 0xc0) === 0x80) i--

  if (i < start) return 0

  const byte = data[i]

  let needed
  if (byte <= 0x7f) return 0
  if (byte >= 0xc2 && byte <= 0xdf) needed = 2
  else if (byte >= 0xe0 && byte <= 0xef) needed = 3
  else if (byte >= 0xf0 && byte <= 0xf4) needed = 4
  else return 0

  const available = len - i
  return available < needed ? available : 0
}

function trailingBytesSeen(data) {
  const len = data.byteLength
  if (len === 0) return 0

  const last = data[len - 1]

  if (last <= 0x7f) return 0
  if ((last & 0xc0) !== 0x80) return 1

  const limit = Math.max(0, len - 4)

  let i = len - 2
  while (i >= limit && (data[i] & 0xc0) === 0x80) i--

  if (i < 0) return 1

  const first = data[i]

  let needed
  if (first >= 0xc2 && first <= 0xdf) needed = 2
  else if (first >= 0xe0 && first <= 0xef) needed = 3
  else if (first >= 0xf0 && first <= 0xf4) needed = 4
  else return 1

  if (len - i !== needed) return 1

  if (needed >= 3) {
    const second = data[i + 1]
    if (first === 0xe0 && second < 0xa0) return 1
    if (first === 0xed && second > 0x9f) return 1
    if (first === 0xf0 && second < 0x90) return 1
    if (first === 0xf4 && second > 0x8f) return 1
  }

  return 0
}
{
  "name": "text-decoder",
  "version": "1.2.7",
  "description": "Streaming text decoder that preserves multibyte Unicode characters",
  "exports": {
    "./package": "./package.json",
    ".": "./index.js"
  },
  "files": [
    "index.js",
    "lib"
  ],
  "scripts": {
    "format": "prettier --write .",
    "lint": "prettier --check . && lunte",
    "test": "npm run test:node && npm run test:bare",
    "test:node": "brittle-node test.js",
    "test:bare": "brittle-bare test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/text-decoder.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/text-decoder/issues"
  },
  "homepage": "https://github.com/holepunchto/text-decoder#readme",
  "dependencies": {
    "b4a": "^1.6.4"
  },
  "devDependencies": {
    "brittle": "^3.3.2",
    "lunte": "^1.3.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^1.0.0"
  }
}
const { runtime, platform, arch, version } =
  typeof Bare !== 'undefined'
    ? {
        runtime: 'bare',
        platform: global.Bare.platform,
        arch: global.Bare.arch,
        version: global.Bare.version
      }
    : typeof process !== 'undefined'
      ? {
          runtime: 'node',
          platform: global.process.platform,
          arch: global.process.arch,
          version: global.process.version
        }
      : typeof Window !== 'undefined'
        ? { runtime: 'browser', platform: 'unknown', arch: 'unknown', version: 'unknown' }
        : { runtime: 'unknown', platform: 'unknown', arch: 'unknown', version: 'unknown' }

exports.runtime = runtime
exports.version = version
exports.platform = platform
exports.arch = arch
exports.isBare = runtime === 'bare'
exports.isBareKit = exports.isBare && typeof BareKit !== 'undefined'
exports.isPear = typeof Pear !== 'undefined'
exports.isNode = runtime === 'node'
exports.isBrowser = runtime === 'browser'
exports.isWindows = platform === 'win32'
exports.isLinux = platform === 'linux'
exports.isMac = platform === 'darwin'
exports.isIOS = platform === 'ios' || platform === 'ios-simulator'
exports.isAndroid = platform === 'android'
exports.isElectron = typeof process !== 'undefined' && !!global.process.versions?.electron
exports.isElectronRenderer = exports.isElectron && global.process.type === 'renderer'
exports.isElectronWorker = exports.isElectron && global.process.type === 'worker'
{
  "name": "which-runtime",
  "version": "1.4.0",
  "description": "Detect if you are in Bare or Node and which os etc",
  "main": "index.js",
  "files": [
    "index.js"
  ],
  "scripts": {
    "format": "prettier --write .",
    "lint": "prettier --check . && lunte .",
    "test": "npm run test:node && npm run test:bare",
    "test:node": "brittle-node test.js",
    "test:bare": "brittle-bare test.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/holepunchto/which-runtime.git"
  },
  "author": "Holepunch Inc.",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/which-runtime/issues"
  },
  "homepage": "https://github.com/holepunchto/which-runtime",
  "devDependencies": {
    "brittle": "^3.0.0",
    "lunte": "^1.4.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^2.0.0"
  }
}
{
  "name": "hyper-overlay",
  "version": "0.3.13",
  "description": "Content-addressed P2P file distribution over Protomux — FastCDC chunking, multi-source fetch, resumable transfers, zero blob duplication. Files live on disk; the overlay is a thin index and transfer protocol.",
  "type": "module",
  "main": "index.js",
  "imports": {
    "#fs": {
      "bare": "bare-fs",
      "default": "fs"
    },
    "#path": {
      "bare": "bare-path",
      "default": "path"
    },
    "#os": {
      "bare": "bare-os",
      "default": "os"
    },
    "#zlib": {
      "bare": "bare-zlib",
      "default": "zlib"
    }
  },
  "exports": {
    ".": "./index.js",
    "./overlay": "./lib/overlay.js",
    "./protocol": "./lib/protocol.js",
    "./messages": "./lib/messages.js",
    "./chunker": "./lib/chunker.js",
    "./chunk-scheduler": "./lib/chunk-scheduler.js",
    "./transfer": "./lib/transfer.js",
    "./file-index": "./lib/file-index.js",
    "./sync-engine": "./lib/sync-engine.js",
    "./tree": "./lib/tree.js",
    "./pointer": "./lib/pointer.js",
    "./ignore": "./lib/ignore.js",
    "./manifest": "./lib/manifest.js",
    "./package.json": "./package.json"
  },
  "scripts": {
    "test": "brittle-bare test/*.test.js test/integration/*.test.js",
    "test:node": "brittle-node test/*.test.js"
  },
  "engines": {
    "bare": ">=1.16.0"
  },
  "license": "AGPL-3.0",
  "keywords": [
    "p2p",
    "hypercore",
    "hyperbee",
    "protomux",
    "overlay",
    "content-addressed",
    "deduplication",
    "file-transfer",
    "fastcdc",
    "chunking",
    "multi-source",
    "resumable",
    "bare"
  ],
  "dependencies": {
    "b4a": "^1.0.0",
    "bare-fs": "^4.7.2",
    "bare-os": "^3.8.7",
    "bare-path": "^3.0.0",
    "bare-zlib": "^1.3.3",
    "compact-encoding": "^3.2.0",
    "corestore": "^7.0.0",
    "hyperbee": "^2.0.0",
    "hypercore-crypto": "^3.0.0",
    "protomux": "^3.0.0",
    "ready-resource": "^1.0.0",
    "sodium-universal": "^5.0.1",
    "streamx": "^2.0.0"
  },
  "devDependencies": {
    "brittle": "^3.0.0",
    "hyperswarm": "^4.0.0",
    "@hyperswarm/testnet": "^3.1.4"
  }
}
const sodium = require('sodium-universal')
const c = require('compact-encoding')
const b4a = require('b4a')

// https://en.wikipedia.org/wiki/Merkle_tree#Second_preimage_attack
const LEAF_TYPE = b4a.from([0])
const PARENT_TYPE = b4a.from([1])
const ROOT_TYPE = b4a.from([2])

const HYPERCORE = b4a.from('hypercore')

exports.keyPair = function (seed) {
  // key pairs might stay around for a while, so better not to use a default slab to avoid retaining it completely
  const slab = b4a.allocUnsafeSlow(
    sodium.crypto_sign_PUBLICKEYBYTES + sodium.crypto_sign_SECRETKEYBYTES
  )
  const publicKey = slab.subarray(0, sodium.crypto_sign_PUBLICKEYBYTES)
  const secretKey = slab.subarray(sodium.crypto_sign_PUBLICKEYBYTES)

  if (seed) sodium.crypto_sign_seed_keypair(publicKey, secretKey, seed)
  else sodium.crypto_sign_keypair(publicKey, secretKey)

  return {
    publicKey,
    secretKey
  }
}

exports.validateKeyPair = function (keyPair) {
  const pk = b4a.allocUnsafe(sodium.crypto_sign_PUBLICKEYBYTES)
  sodium.crypto_sign_ed25519_sk_to_pk(pk, keyPair.secretKey)
  return b4a.equals(pk, keyPair.publicKey)
}

exports.sign = function (message, secretKey) {
  // Dedicated slab for the signature, to avoid retaining unneeded mem and for security
  const signature = b4a.allocUnsafeSlow(sodium.crypto_sign_BYTES)
  sodium.crypto_sign_detached(signature, message, secretKey)
  return signature
}

exports.verify = function (message, signature, publicKey) {
  if (signature.byteLength !== sodium.crypto_sign_BYTES) return false
  if (publicKey.byteLength !== sodium.crypto_sign_PUBLICKEYBYTES) return false
  return sodium.crypto_sign_verify_detached(signature, message, publicKey)
}

exports.encrypt = function (message, publicKey) {
  const ciphertext = b4a.alloc(message.byteLength + sodium.crypto_box_SEALBYTES)
  sodium.crypto_box_seal(ciphertext, message, publicKey)
  return ciphertext
}

exports.decrypt = function (ciphertext, keyPair) {
  if (ciphertext.byteLength < sodium.crypto_box_SEALBYTES) return null

  const plaintext = b4a.alloc(ciphertext.byteLength - sodium.crypto_box_SEALBYTES)

  if (!sodium.crypto_box_seal_open(plaintext, ciphertext, keyPair.publicKey, keyPair.secretKey)) {
    return null
  }

  return plaintext
}

exports.encryptionKeyPair = function (seed) {
  const publicKey = b4a.alloc(sodium.crypto_box_PUBLICKEYBYTES)
  const secretKey = b4a.alloc(sodium.crypto_box_SECRETKEYBYTES)

  if (seed) {
    sodium.crypto_box_seed_keypair(publicKey, secretKey, seed)
  } else {
    sodium.crypto_box_keypair(publicKey, secretKey)
  }

  return {
    publicKey,
    secretKey
  }
}

exports.data = function (data) {
  const out = b4a.allocUnsafe(32)

  sodium.crypto_generichash_batch(out, [LEAF_TYPE, c.encode(c.uint64, data.byteLength), data])

  return out
}

exports.parent = function (a, b) {
  if (a.index > b.index) {
    const tmp = a
    a = b
    b = tmp
  }

  const out = b4a.allocUnsafe(32)

  sodium.crypto_generichash_batch(out, [
    PARENT_TYPE,
    c.encode(c.uint64, a.size + b.size),
    a.hash,
    b.hash
  ])

  return out
}

exports.tree = function (roots, out) {
  const buffers = new Array(3 * roots.length + 1)
  let j = 0

  buffers[j++] = ROOT_TYPE

  for (let i = 0; i < roots.length; i++) {
    const r = roots[i]
    buffers[j++] = r.hash
    buffers[j++] = c.encode(c.uint64, r.index)
    buffers[j++] = c.encode(c.uint64, r.size)
  }

  if (!out) out = b4a.allocUnsafe(32)
  sodium.crypto_generichash_batch(out, buffers)
  return out
}

exports.hash = function (data, out) {
  if (!out) out = b4a.allocUnsafe(32)
  if (!Array.isArray(data)) data = [data]

  sodium.crypto_generichash_batch(out, data)

  return out
}

exports.randomBytes = function (n) {
  const buf = b4a.allocUnsafe(n)
  sodium.randombytes_buf(buf)
  return buf
}

exports.discoveryKey = function (key) {
  if (!key || key.byteLength !== 32) throw new Error('Must pass a 32 byte buffer')
  // Discovery keys might stay around for a while, so better not to use slab memory (for better gc)
  const digest = b4a.allocUnsafeSlow(32)
  sodium.crypto_generichash(digest, HYPERCORE, key)
  return digest
}

if (sodium.sodium_free) {
  exports.free = function (secureBuf) {
    if (secureBuf.secure) sodium.sodium_free(secureBuf)
  }
} else {
  exports.free = function () {}
}

exports.namespace = function (name, count) {
  const ids = typeof count === 'number' ? range(count) : count

  // Namespaces are long-lived, so better to use a dedicated slab
  const buf = b4a.allocUnsafeSlow(32 * ids.length)

  const list = new Array(ids.length)

  // ns is ephemeral, so default slab
  const ns = b4a.allocUnsafe(33)
  sodium.crypto_generichash(ns.subarray(0, 32), typeof name === 'string' ? b4a.from(name) : name)

  for (let i = 0; i < list.length; i++) {
    list[i] = buf.subarray(32 * i, 32 * i + 32)
    ns[32] = ids[i]
    sodium.crypto_generichash(list[i], ns)
  }

  return list
}

function range(count) {
  const arr = new Array(count)
  for (let i = 0; i < count; i++) arr[i] = i
  return arr
}
{
  "name": "hypercore-crypto",
  "version": "3.7.0",
  "description": "The crypto primitives used in hypercore, extracted into a separate module",
  "main": "index.js",
  "files": [
    "index.js"
  ],
  "dependencies": {
    "b4a": "^1.6.6",
    "compact-encoding": "^3.0.0",
    "sodium-universal": "^5.0.0"
  },
  "devDependencies": {
    "brittle": "^3.5.0",
    "lunte": "^1.0.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^2.0.0"
  },
  "scripts": {
    "format": "prettier --write .",
    "lint": "prettier --check . && lunte",
    "test": "npm run test:node && npm run test:bare",
    "test:node": "brittle-node test.js",
    "test:bare": "brittle-bare test.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/mafintosh/hypercore-crypto.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/mafintosh/hypercore-crypto/issues"
  },
  "homepage": "https://github.com/mafintosh/hypercore-crypto"
}
const z32 = require('z32')
const b4a = require('b4a')

module.exports = {
  encode,
  decode,
  normalize,
  isValid
}

function encode (key) {
  if (!b4a.isBuffer(key)) throw new Error('Key must be a Buffer')
  if (key.byteLength !== 32) throw new Error('Key must be 32-bytes long')
  return z32.encode(key)
}

function decode (id) {
  if (b4a.isBuffer(id)) {
    if (id.byteLength !== 32) throw new Error('ID must be 32-bytes long')
    return id
  }
  if (typeof id === 'string') {
    if (id.startsWith('pear://')) id = id.slice(7).split('/')[0]
    if (id.length === 52) return z32.decode(id)
    if (id.length === 64) {
      const buf = b4a.from(id, 'hex')
      if (buf.byteLength === 32) return buf
    }
  }
  throw new Error('Invalid Hypercore key')
}

function normalize (any) {
  return encode(decode(any))
}

function isValid (any) {
  try {
    decode(any)
    return true
  } catch {
    return false
  }
}
{
  "name": "hypercore-id-encoding",
  "version": "1.3.0",
  "description": "Convert Hypercore keys to/from z-base32 or hex",
  "main": "index.js",
  "scripts": {
    "test": "standard && brittle test.js"
  },
  "files": [
    "index.js"
  ],
  "dependencies": {
    "b4a": "^1.5.3",
    "z32": "^1.0.0"
  },
  "devDependencies": {
    "brittle": "^3.1.1",
    "hypercore": "^10.0.0",
    "random-access-memory": "^6.0.0",
    "standard": "^17.1.0"
  },
  "license": "Apache-2.0",
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/hypercore-id-encoding.git"
  },
  "keywords": [],
  "author": "",
  "bugs": {
    "url": "https://github.com/holepunchto/hypercore-id-encoding/issues"
  },
  "homepage": "https://github.com/holepunchto/hypercore-id-encoding#readme"
}
const c = require('compact-encoding')
const { getEncoding } = require('./spec/hyperschema')

const HyperdhtAddress = getEncoding('@hyperdht/address')

function decode(buf) {
  const addr = c.decode(HyperdhtAddress, buf)
  return addr
}

function encode(key, nodes) {
  return c.encode(HyperdhtAddress, { key, nodes })
}

module.exports = { decode, encode }
{
  "name": "hyperdht-address",
  "version": "1.1.1",
  "description": "hyperdht-address",
  "main": "index.js",
  "exports": {
    "./package": "./package.json",
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    }
  },
  "files": [
    "package.json",
    "index.js",
    "index.d.ts",
    "spec"
  ],
  "devDependencies": {
    "b4a": "^1.8.0",
    "brittle": "^3.19.0",
    "lunte": "^1.2.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^2.0.0"
  },
  "scripts": {
    "format": "prettier . --write",
    "test": "prettier . --check && lunte && brittle-bare test/index.js",
    "lint": "lunte"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/holepunchto/hyperdht-address.git"
  },
  "author": "Holepunch Inc",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/hyperdht-address/issues"
  },
  "homepage": "https://github.com/holepunchto/hyperdht-address",
  "dependencies": {
    "compact-encoding": "^3.0.0",
    "hyperschema": "^1.20.1"
  }
}
// This file is autogenerated by the hyperschema compiler
// Schema Version: 2
/* eslint-disable camelcase */
/* eslint-disable quotes */
/* eslint-disable space-before-function-paren */

const { c } = require('hyperschema/runtime')

const VERSION = 2

// eslint-disable-next-line no-unused-vars
let version = VERSION

// @hyperdht/address.nodes
const encoding0_1 = c.array(c.ipv4Address)

// @hyperdht/address
const encoding0 = {
  preencode(state, m) {
    c.fixed32.preencode(state, m.key)
    state.end++ // max flag is 1 so always one byte

    if (version >= 2 && m.nodes) encoding0_1.preencode(state, m.nodes)
  },
  encode(state, m) {
    const flags = version >= 2 && m.nodes ? 1 : 0

    c.fixed32.encode(state, m.key)
    c.uint.encode(state, flags)

    if (version >= 2 && m.nodes) encoding0_1.encode(state, m.nodes)
  },
  decode(state) {
    const r0 = c.fixed32.decode(state)
    const flags = state.start < state.end ? c.uint.decode(state) : 0

    return {
      key: r0,
      nodes: version >= 2 && (flags & 1) !== 0 ? encoding0_1.decode(state) : null
    }
  }
}

function setVersion(v) {
  version = v
}

function encode(name, value, v = VERSION) {
  version = v
  return c.encode(getEncoding(name), value)
}

function decode(name, buffer, v = VERSION) {
  version = v
  return c.decode(getEncoding(name), buffer)
}

function getEnum(name) {
  switch (name) {
    default:
      throw new Error('Enum not found ' + name)
  }
}

function getEncoding(name) {
  switch (name) {
    case '@hyperdht/address':
      return encoding0
    default:
      throw new Error('Encoder not found ' + name)
  }
}

function getStruct(name, v = VERSION) {
  const enc = getEncoding(name)
  return {
    preencode(state, m) {
      version = v
      enc.preencode(state, m)
    },
    encode(state, m) {
      version = v
      enc.encode(state, m)
    },
    decode(state) {
      version = v
      return enc.decode(state)
    }
  }
}

const resolveStruct = getStruct // compat

module.exports = {
  resolveStruct,
  getStruct,
  getEnum,
  getEncoding,
  encode,
  decode,
  setVersion,
  version
}
const DHT = require('dht-rpc')
const sodium = require('sodium-universal')
const c = require('compact-encoding')
const b4a = require('b4a')
const safetyCatch = require('safety-catch')
const m = require('./lib/messages')
const SocketPool = require('./lib/socket-pool')
const Persistent = require('./lib/persistent')
const Router = require('./lib/router')
const Cache = require('xache')
const Server = require('./lib/server')
const connect = require('./lib/connect')
const { FIREWALL, BOOTSTRAP_NODES, COMMANDS } = require('./lib/constants')
const { hash, createKeyPair } = require('./lib/crypto')
const RawStreamSet = require('./lib/raw-stream-set')
const ConnectionPool = require('./lib/connection-pool')
const { STREAM_NOT_CONNECTED } = require('./lib/errors')

const DEFAULTS = {
  ...DHT.DEFAULTS,
  connectionKeepAlive: 5000,
  randomPunchInterval: 20000
}

class HyperDHT extends DHT {
  constructor(opts = {}) {
    const port = opts.port || 49737
    const bootstrap = opts.bootstrap || BOOTSTRAP_NODES
    const nodes = opts.nodes || []

    super({ ...opts, port, bootstrap, nodes, filterNode })

    const { router, relayAddresses, persistent } = defaultCacheOpts(opts)

    this.defaultKeyPair = opts.keyPair || createKeyPair(opts.seed)
    this.listening = new Set()
    this.connectionKeepAlive =
      opts.connectionKeepAlive === false
        ? 0
        : opts.connectionKeepAlive || DEFAULTS.connectionKeepAlive

    this.rawStreams = new RawStreamSet(this)
    this.plugins = new Map()

    this._router = new Router(this, router)
    this._socketPool = new SocketPool(this, opts.host || '0.0.0.0')
    this._persistent = null
    this._validatedLocalAddresses = new Map()
    this._relayAddressesCache = new Cache(relayAddresses)

    this._deferRandomPunch = !!opts.deferRandomPunch
    this._lastRandomPunch = this._deferRandomPunch ? Date.now() : 0
    this._connectable = true
    this._randomPunchInterval = opts.randomPunchInterval || DEFAULTS.randomPunchInterval // min 20s between random punches...
    this._randomPunches = 0
    this._randomPunchLimit = 1 // set to one for extra safety for now

    // stats is inherited from dht-rpc so fwd the ones from there
    this.stats = {
      punches: {
        consistent: 0,
        random: 0,
        open: 0,
        tryLater: 0,
        tryLaterRelayedHandshakesAtLeastOnce: 0
      },
      relaying: { attempts: 0, successes: 0, aborts: 0 },
      socketPool: this._socketPool.stats,
      ...this.stats
    }

    this.once('persistent', () => {
      this._persistent = new Persistent(this, persistent)
      for (const plugin of this.plugins.values()) plugin.onpersistent()
    })

    this.on('network-change', () => {
      for (const server of this.listening) server.refresh()
    })

    this.on('network-update', () => {
      if (!this.online) return
      for (const server of this.listening) server.notifyOnline()
    })
  }

  static DEFAULTS = DEFAULTS

  connect(remotePublicKey, opts) {
    return connect(this, remotePublicKey, opts)
  }

  createServer(opts, onconnection) {
    if (typeof opts === 'function') return this.createServer({}, opts)
    if (opts && opts.onconnection) onconnection = opts.onconnection
    const s = new Server(this, opts)
    if (onconnection) s.on('connection', onconnection)
    return s
  }

  pool() {
    return new ConnectionPool(this)
  }

  async resume({ log = noop } = {}) {
    if (this._deferRandomPunch) this._lastRandomPunch = Date.now()
    await super.resume({ log })
    const resuming = []
    for (const server of this.listening) resuming.push(server.resume())
    log('Resuming hyperdht servers')
    await Promise.allSettled(resuming)
    log('Done, hyperdht fully resumed')
  }

  async suspend({ log = noop } = {}) {
    this._connectable = false // just so nothing gets connected during suspension
    const suspending = []
    for (const server of this.listening) suspending.push(server.suspend())
    log('Suspending all hyperdht servers')
    await Promise.allSettled(suspending)
    log('Done, clearing all raw streams')
    await this.rawStreams.clear()
    log('Done, suspending dht-rpc')
    await super.suspend({ log })
    log('Done, clearing raw streams again')
    await this.rawStreams.clear()
    log('Done, hyperdht fully suspended')
    this._connectable = true
  }

  async destroy({ force = false } = {}) {
    if (!force) {
      const closing = []
      for (const server of this.listening) closing.push(server.close())
      await Promise.allSettled(closing)
    }
    this._router.destroy()
    if (this._persistent) this._persistent.destroy()
    for (const plugin of this.plugins.values()) plugin.destroy()
    await this.rawStreams.clear()
    await this._socketPool.destroy()
    await super.destroy()
  }

  async validateLocalAddresses(addresses) {
    const list = []
    const socks = []
    const waiting = []

    for (const addr of addresses) {
      const { host } = addr

      if (this._validatedLocalAddresses.has(host)) {
        if (await this._validatedLocalAddresses.get(host)) {
          list.push(addr)
        }
        continue
      }

      const sock = this.udx.createSocket()
      try {
        sock.bind(0, host)
      } catch {
        this._validatedLocalAddresses.set(host, Promise.resolve(false))
        continue
      }

      socks.push(sock)

      // semi terrible heuristic until we proper fix local connections by racing them to the remote...
      const promise = new Promise((resolve) => {
        sock.on('message', () => resolve(true))
        setTimeout(() => resolve(false), 500)
        sock.trySend(b4a.alloc(1), sock.address().port, addr.host)
      })

      this._validatedLocalAddresses.set(host, promise)
      waiting.push(addr)
    }

    for (const addr of waiting) {
      const { host } = addr
      if (this._validatedLocalAddresses.has(host)) {
        if (await this._validatedLocalAddresses.get(host)) {
          list.push(addr)
        }
        continue
      }
    }

    for (const sock of socks) await sock.close()

    return list
  }

  findPeer(publicKey, opts = {}) {
    const target = opts.hash === false ? publicKey : hash(publicKey)
    opts = { ...opts, map: mapFindPeer }
    return this.query({ target, command: COMMANDS.FIND_PEER, value: null }, opts)
  }

  lookup(target, opts = {}) {
    opts = { ...opts, map: mapLookup }
    return this.query({ target, command: COMMANDS.LOOKUP, value: null }, opts)
  }

  lookupAndUnannounce(target, keyPair, opts = {}) {
    const unannounces = []
    const dht = this
    const userCommit = opts.commit || noop
    const signUnannounce = opts.signUnannounce || Persistent.signUnannounce

    if (this._persistent !== null) {
      // unlink self
      this._persistent.unannounce(target, keyPair.publicKey)
    }

    opts = { ...opts, map, commit }
    return this.query({ target, command: COMMANDS.LOOKUP, value: null }, opts)

    async function commit(reply, dht, query) {
      await Promise.all(unannounces) // can never fail, caught below
      return userCommit(reply, dht, query)
    }

    function map(reply) {
      const data = mapLookup(reply)

      if (!data || !data.token) return data

      let found = data.peers.length >= 20
      for (let i = 0; !found && i < data.peers.length; i++) {
        found = b4a.equals(data.peers[i].publicKey, keyPair.publicKey)
      }

      if (!found) return data

      if (!data.from.id) return data

      unannounces.push(
        dht
          ._requestUnannounce(keyPair, dht, target, data.token, data.from, signUnannounce)
          .catch(safetyCatch)
      )

      return data
    }
  }

  unannounce(target, keyPair, opts = {}) {
    return this.lookupAndUnannounce(target, keyPair, opts).finished()
  }

  announce(target, keyPair, relayAddresses, opts = {}) {
    const signAnnounce = opts.signAnnounce || Persistent.signAnnounce
    const bump = opts.bump || 0

    opts = { ...opts, commit }

    return opts.clear ? this.lookupAndUnannounce(target, keyPair, opts) : this.lookup(target, opts)

    function commit(reply, dht) {
      return dht._requestAnnounce(
        keyPair,
        dht,
        target,
        reply.token,
        reply.from,
        relayAddresses,
        signAnnounce,
        bump
      )
    }
  }

  async immutableGet(target, opts = {}) {
    opts = { ...opts, map: mapImmutable }

    const query = this.query({ target, command: COMMANDS.IMMUTABLE_GET, value: null }, opts)
    const check = b4a.allocUnsafe(32)

    for await (const node of query) {
      const { value } = node
      sodium.crypto_generichash(check, value)
      if (b4a.equals(check, target)) return node
    }

    return null
  }

  async immutablePut(value, opts = {}) {
    const target = b4a.allocUnsafe(32)
    sodium.crypto_generichash(target, value)

    opts = {
      ...opts,
      map: mapImmutable,
      commit(reply, dht) {
        return dht.request(
          { token: reply.token, target, command: COMMANDS.IMMUTABLE_PUT, value },
          reply.from
        )
      }
    }

    const query = this.query({ target, command: COMMANDS.IMMUTABLE_GET, value: null }, opts)
    await query.finished()

    return { hash: target, closestNodes: query.closestNodes }
  }

  async mutableGet(publicKey, opts = {}) {
    let refresh = opts.refresh || null
    let signed = null
    let result = null

    opts = { ...opts, map: mapMutable, commit: refresh ? commit : null }

    const target = b4a.allocUnsafe(32)
    sodium.crypto_generichash(target, publicKey)

    const userSeq = opts.seq || 0
    const query = this.query(
      { target, command: COMMANDS.MUTABLE_GET, value: c.encode(c.uint, userSeq) },
      opts
    )
    const latest = opts.latest !== false

    for await (const node of query) {
      if (result && node.seq <= result.seq) continue
      if (
        node.seq < userSeq ||
        !Persistent.verifyMutable(node.signature, node.seq, node.value, publicKey)
      )
        continue
      if (!latest) return node
      if (!result || node.seq > result.seq) result = node
    }

    return result

    function commit(reply, dht) {
      if (!signed && result && refresh) {
        if (refresh(result)) {
          signed = c.encode(m.mutablePutRequest, {
            publicKey,
            seq: result.seq,
            value: result.value,
            signature: result.signature
          })
        } else {
          refresh = null
        }
      }

      return signed
        ? dht.request(
            { token: reply.token, target, command: COMMANDS.MUTABLE_PUT, value: signed },
            reply.from
          )
        : Promise.resolve(null)
    }
  }

  async mutablePut(keyPair, value, opts = {}) {
    const signMutable = opts.signMutable || Persistent.signMutable

    const target = b4a.allocUnsafe(32)
    sodium.crypto_generichash(target, keyPair.publicKey)

    const seq = opts.seq || 0
    const signature = await signMutable(seq, value, keyPair)

    const signed = c.encode(m.mutablePutRequest, {
      publicKey: keyPair.publicKey,
      seq,
      value,
      signature
    })

    opts = {
      ...opts,
      map: mapMutable,
      commit(reply, dht) {
        return dht.request(
          { token: reply.token, target, command: COMMANDS.MUTABLE_PUT, value: signed },
          reply.from
        )
      }
    }

    // use seq = 0, for the query part here, as we don't care about the actual values
    const query = this.query(
      { target, command: COMMANDS.MUTABLE_GET, value: c.encode(c.uint, 0) },
      opts
    )
    await query.finished()

    return { publicKey: keyPair.publicKey, closestNodes: query.closestNodes, seq, signature }
  }

  onrequest(req) {
    switch (req.command) {
      case COMMANDS.PEER_HANDSHAKE: {
        this._router.onpeerhandshake(req)
        return true
      }
      case COMMANDS.PEER_HOLEPUNCH: {
        this._router.onpeerholepunch(req)
        return true
      }
    }

    if (this._persistent === null || this.id === null) return false

    switch (req.command) {
      case COMMANDS.FIND_PEER: {
        this._persistent.onfindpeer(req)
        return true
      }
      case COMMANDS.LOOKUP: {
        this._persistent.onlookup(req)
        return true
      }
      case COMMANDS.ANNOUNCE: {
        this._persistent.onannounce(req)
        return true
      }
      case COMMANDS.UNANNOUNCE: {
        this._persistent.onunannounce(req)
        return true
      }
      case COMMANDS.MUTABLE_PUT: {
        this._persistent.onmutableput(req)
        return true
      }
      case COMMANDS.MUTABLE_GET: {
        this._persistent.onmutableget(req)
        return true
      }
      case COMMANDS.IMMUTABLE_PUT: {
        this._persistent.onimmutableput(req)
        return true
      }
      case COMMANDS.IMMUTABLE_GET: {
        this._persistent.onimmutableget(req)
        return true
      }
      case COMMANDS.PLUGIN: {
        this._persistent.onplugin(req)
        return true
      }
    }

    return false
  }

  static keyPair(seed) {
    return createKeyPair(seed)
  }

  static hash(data) {
    return hash(data)
  }

  static connectRawStream(encryptedStream, rawStream, remoteId) {
    const stream = encryptedStream.rawStream

    if (!stream.connected) throw STREAM_NOT_CONNECTED()

    rawStream.connect(stream.socket, remoteId, stream.remotePort, stream.remoteHost)
  }

  createRawStream(opts) {
    return this.rawStreams.add(opts)
  }

  async _requestAnnounce(keyPair, dht, target, token, from, relayAddresses, sign, bump) {
    const ann = {
      peer: {
        publicKey: keyPair.publicKey,
        relayAddresses: relayAddresses || []
      },
      refresh: null,
      signature: null,
      bump
    }

    ann.signature = await sign(target, token, from.id, ann, keyPair)

    const value = c.encode(m.announce, ann)

    return dht.request(
      {
        token,
        target,
        command: COMMANDS.ANNOUNCE,
        value
      },
      from
    )
  }

  async _requestUnannounce(keyPair, dht, target, token, from, sign) {
    const unann = {
      peer: {
        publicKey: keyPair.publicKey,
        relayAddresses: []
      },
      signature: null
    }

    unann.signature = await sign(target, token, from.id, unann, keyPair)

    const value = c.encode(m.announce, unann)

    return dht.request(
      {
        token,
        target,
        command: COMMANDS.UNANNOUNCE,
        value
      },
      from
    )
  }

  register(name, plugin) {
    this.plugins.set(name, plugin)
    plugin.onregister(this)
  }
}

HyperDHT.BOOTSTRAP = BOOTSTRAP_NODES
HyperDHT.FIREWALL = FIREWALL

module.exports = HyperDHT

function mapLookup(node) {
  if (!node.value) return null

  try {
    const l = c.decode(m.lookupRawReply, node.value)

    return {
      token: node.token,
      from: node.from,
      to: node.to,
      peers: l.peers,
      bump: l.bump
    }
  } catch {
    return null
  }
}

function mapFindPeer(node) {
  if (!node.value) return null

  try {
    return {
      token: node.token,
      from: node.from,
      to: node.to,
      peer: c.decode(m.peer, node.value)
    }
  } catch {
    return null
  }
}

function mapImmutable(node) {
  if (!node.value) return null

  return {
    token: node.token,
    from: node.from,
    to: node.to,
    value: node.value
  }
}

function mapMutable(node) {
  if (!node.value) return null

  try {
    const { seq, value, signature } = c.decode(m.mutableGetResponse, node.value)

    return {
      token: node.token,
      from: node.from,
      to: node.to,
      seq,
      value,
      signature
    }
  } catch {
    return null
  }
}

function noop() {}

function filterNode(node) {
  // always skip these testnet nodes that got mixed in by accident, until they get updated
  return (
    !(node.port === 49738 && (node.host === '134.209.28.98' || node.host === '167.99.142.185')) &&
    !(node.port === 9400 && node.host === '35.233.47.252') &&
    !(node.host === '150.136.142.116')
  )
}

const defaultMaxSize = 65536
const defaultMaxAge = 20 * 60 * 1000 // 20 minutes

function defaultCacheOpts(opts) {
  const maxSize = opts.maxSize || defaultMaxSize
  const maxAge = opts.maxAge || defaultMaxAge

  return {
    router: {
      forwards: { maxSize, maxAge }
    },
    relayAddresses: { maxSize: Math.min(maxSize, 512), maxAge: 0 },
    persistent: {
      records: { maxSize, maxAge },
      refreshes: { maxSize, maxAge },
      mutables: {
        maxSize: (maxSize / 2) | 0,
        maxAge: opts.maxAge || 48 * 60 * 60 * 1000 // 48 hours
      },
      immutables: {
        maxSize: (maxSize / 2) | 0,
        maxAge: opts.maxAge || 48 * 60 * 60 * 1000 // 48 hours
      },
      bumps: { maxSize, maxAge }
    }
  }
}
const safetyCatch = require('safety-catch')
const c = require('compact-encoding')
const Signal = require('signal-promise')
const { encodeUnslab } = require('./encode')
const Sleeper = require('./sleeper')
const m = require('./messages')
const Persistent = require('./persistent')
const { COMMANDS } = require('./constants')

const MIN_ACTIVE = 3

module.exports = class Announcer {
  constructor(dht, keyPair, target, opts = {}) {
    this.dht = dht
    this.keyPair = keyPair
    this.target = target
    this.relays = []
    this.relayAddresses = []
    this.stopped = false
    this.suspended = false
    this.record = encodeUnslab(m.peer, { publicKey: keyPair.publicKey, relayAddresses: [] })
    this.online = new Signal()

    this._refreshing = false
    this._closestNodes = null
    this._active = null
    this._sleeper = new Sleeper()
    this._resumed = new Signal()
    this._signAnnounce = opts.signAnnounce || Persistent.signAnnounce
    this._signUnannounce = opts.signUnannounce || Persistent.signUnannounce
    this._updating = null
    this._activeQuery = null
    this._unannouncing = null

    this._serverRelays = [new Map(), new Map(), new Map()]
  }

  isRelay(addr) {
    const id = addr.host + ':' + addr.port
    const [a, b, c] = this._serverRelays
    return a.has(id) || b.has(id) || c.has(id)
  }

  async suspend({ log = noop } = {}) {
    if (this.suspended) return
    this.suspended = true

    log('Suspending announcer')

    // Suspend has its own sleep logic
    // so we don't want to hang on this one
    this.online.notify()

    if (this._activeQuery) this._activeQuery.destroy()

    this._sleeper.resume()
    if (this._updating) await this._updating
    log('Suspending announcer (post update)')

    if (this.suspended === false || this.stopped) return

    log('Suspending announcer (pre unannounce)')
    await this._unannounceCurrent()
    log('Suspending announcer (post unannounce)')
  }

  resume() {
    if (!this.suspended) return
    this.suspended = false

    this.refresh()
    this._sleeper.resume()
    this._resumed.notify()
  }

  refresh() {
    if (this.stopped) return
    this._refreshing = true
  }

  async start() {
    if (this.stopped) return
    this._active = this._runUpdate()
    await this._active
    if (this.stopped) return
    this._active = this._background()
  }

  async stop() {
    this.stopped = true
    this.online.notify() // Break out of the _background loop if we're offline
    this._sleeper.resume()
    this._resumed.notify()
    await this._active
    await this._unannounceCurrent()
  }

  async _unannounceCurrent() {
    while (this._unannouncing !== null) await this._unannouncing
    const un = (this._unannouncing = this._unannounceAll(this._serverRelays[2].values()))
    await this._unannouncing
    if (un === this._unannouncing) this._unannouncing = null
  }

  async _background() {
    while (!this.dht.destroyed && !this.stopped) {
      try {
        this._refreshing = false

        // ~5min +-
        for (let i = 0; i < 100 && !this.stopped && !this._refreshing && !this.suspended; i++) {
          const pings = []

          for (const node of this._serverRelays[2].values()) {
            pings.push(this.dht.ping(node))
          }

          const active = await resolved(pings)
          if (active < Math.min(pings.length, MIN_ACTIVE)) {
            this.refresh() // we lost too many relay nodes, retry all
          }

          if (this.stopped) return

          if (!this.suspended && !this._refreshing) await this._sleeper.pause(3000)
        }

        while (!this.stopped && this.suspended) await this._resumed.wait()

        if (!this.stopped) await this._runUpdate()

        while (!this.dht.online && !this.stopped && !this.suspended) {
          // Being offline can make _background repeat very quickly
          // So wait until we're back online
          await this.online.wait()
        }
      } catch (err) {
        safetyCatch(err)
      }
    }
  }

  async _runUpdate() {
    this._updating = this._update()
    await this._updating
    this._updating = null
  }

  async _update() {
    while (this._unannouncing) await this._unannouncing

    this._cycle()

    const q = (this._activeQuery = this.dht.findPeer(this.target, {
      hash: false,
      nodes: this._closestNodes
    }))

    try {
      await q.finished()
    } catch {
      // ignore failures...
    }

    this._activeQuery = null

    if (this.stopped || this.suspended) return

    const ann = []
    const replies = pickBest(q.closestReplies)

    const relays = []
    const relayAddresses = []

    if (!this.dht.firewalled) {
      const addr = this.dht.remoteAddress()
      if (addr) relayAddresses.push(addr)
    }

    for (const msg of replies) {
      ann.push(this._commit(msg, relays, relayAddresses))
    }

    await Promise.allSettled(ann)
    if (this.stopped || this.suspended) return

    this._closestNodes = q.closestNodes
    this.relays = relays
    this.relayAddresses = relayAddresses

    const removed = []
    for (const [key, value] of this._serverRelays[1]) {
      if (!this._serverRelays[2].has(key)) removed.push(value)
    }

    await this._unannounceAll(removed)
  }

  _unannounceAll(relays) {
    const unann = []
    for (const r of relays) unann.push(this._unannounce(r))
    return Promise.allSettled(unann)
  }

  async _unannounce(to) {
    const unann = {
      peer: {
        publicKey: this.keyPair.publicKey,
        relayAddresses: []
      },
      refresh: null,
      signature: null
    }

    const { from, token, value } = await this.dht.request(
      {
        token: null,
        command: COMMANDS.FIND_PEER,
        target: this.target,
        value: null
      },
      to
    )

    if (!token || !from.id || !value) return

    unann.signature = await this._signUnannounce(this.target, token, from.id, unann, this.keyPair)

    await this.dht.request(
      {
        token,
        command: COMMANDS.UNANNOUNCE,
        target: this.target,
        value: c.encode(m.announce, unann)
      },
      to
    )
  }

  async _commit(msg, relays, relayAddresses) {
    const ann = {
      peer: {
        publicKey: this.keyPair.publicKey,
        relayAddresses: []
      },
      refresh: null,
      signature: null
    }

    ann.signature = await this._signAnnounce(this.target, msg.token, msg.from.id, ann, this.keyPair)

    const res = await this.dht.request(
      {
        token: msg.token,
        command: COMMANDS.ANNOUNCE,
        target: this.target,
        value: c.encode(m.announce, ann)
      },
      msg.from
    )

    if (res.error !== 0) return

    if (relayAddresses.length < 3) relayAddresses.push({ host: msg.from.host, port: msg.from.port })
    relays.push({ relayAddress: msg.from, peerAddress: msg.to })

    this._serverRelays[2].set(msg.from.host + ':' + msg.from.port, msg.from)
  }

  _cycle() {
    const tmp = this._serverRelays[0]
    this._serverRelays[0] = this._serverRelays[1]
    this._serverRelays[1] = this._serverRelays[2]
    this._serverRelays[2] = tmp
    tmp.clear()
  }
}

function resolved(ps) {
  let replied = 0
  let ticks = ps.length + 1

  return new Promise((resolve) => {
    for (const p of ps) p.then(push, tick)
    tick()

    function push(v) {
      replied++
      tick()
    }

    function tick() {
      if (--ticks === 0) resolve(replied)
    }
  })
}

function pickBest(replies) {
  // TODO: pick the ones closest to us RTT wise
  return replies.slice(0, 3)
}

function noop() {}
const NoiseSecretStream = require('@hyperswarm/secret-stream')
const b4a = require('b4a')
const relay = require('blind-relay')
const { isReserved, isBogon } = require('bogon')
const safetyCatch = require('safety-catch')
const unslab = require('unslab')
const Semaphore = require('./semaphore')
const NoiseWrap = require('./noise-wrap')
const SecurePayload = require('./secure-payload')
const Holepuncher = require('./holepuncher')
const Sleeper = require('./sleeper')
const {
  clearRelayTimeout,
  confirmDirectUpgrade,
  destroyRelayConnection
} = require('./relay-connection')
const { FIREWALL, ERROR } = require('./constants')
const { unslabbedHash } = require('./crypto')
const {
  CANNOT_HOLEPUNCH,
  HANDSHAKE_INVALID,
  HOLEPUNCH_ABORTED,
  HOLEPUNCH_INVALID,
  HOLEPUNCH_PROBE_TIMEOUT,
  HOLEPUNCH_DOUBLE_RANDOMIZED_NATS,
  PEER_CONNECTION_FAILED,
  PEER_NOT_FOUND,
  REMOTE_ABORTED,
  REMOTE_NOT_HOLEPUNCHABLE,
  REMOTE_NOT_HOLEPUNCHING,
  SERVER_ERROR,
  SERVER_INCOMPATIBLE,
  RELAY_ABORTED,
  SUSPENDED
} = require('./errors')
const { decode } = require('hypercore-id-encoding')
const HyperDHTAddress = require('hyperdht-address')

module.exports = function connect(dht, publicKey, opts = {}) {
  const pool = opts.pool || null

  const { key, nodes: providedNodes } = HyperDHTAddress.decode(
    b4a.isBuffer(publicKey) ? publicKey : decode(publicKey)
  )
  publicKey = key

  if (pool && pool.has(publicKey)) return pool.get(publicKey)

  publicKey = unslab(publicKey)

  opts.relayAddresses = opts.relayAddresses || providedNodes || []
  const keyPair = opts.keyPair || dht.defaultKeyPair
  const relayThrough = selectRelay(opts.relayThrough || null)
  const encryptedSocket = (opts.createSecretStream || defaultCreateSecretStream)(true, null, {
    publicKey: keyPair.publicKey,
    remotePublicKey: publicKey,
    autoStart: false,
    keepAlive: dht.connectionKeepAlive
  })

  // in case a socket is made during suspended state, destroy it immediately
  if (dht.suspended || !dht._connectable) {
    encryptedSocket.destroy(SUSPENDED())
    return encryptedSocket
  }

  if (pool) pool._attachStream(encryptedSocket, false)

  const id = b4a.toString(publicKey, 'hex')
  const c = {
    id,
    dht,
    session: dht.session(),
    relayAddresses: opts.relayAddresses,
    remoteRelayAddresses: [],
    pool,
    round: 0,
    target: unslabbedHash(publicKey),
    remotePublicKey: publicKey,
    reusableSocket: !!opts.reusableSocket,
    handshake: (opts.createHandshake || defaultCreateHandshake)(keyPair, publicKey),
    request: null,
    requesting: false,
    lan: opts.localConnection !== false,
    firewall: FIREWALL.UNKNOWN,
    rawStream: dht.createRawStream({ framed: true, firewall }),
    connect: null,
    query: null,
    puncher: null,
    payload: null,
    passiveConnectTimeout: null,
    serverSocket: null,
    serverAddress: null,
    onsocket: null,
    sleeper: new Sleeper(),
    encryptedSocket,

    // Relay state
    relayTimeout: null,
    relayThrough,
    relayToken: relayThrough ? relay.token() : null,
    relaySocket: null,
    relayClient: null,
    relayPaired: false,
    validUpgrade: true,
    relayKeepAlive: opts.relayKeepAlive || 5000
  }

  // If the raw stream receives an error signal pre connect (ie from the firewall hook), make sure
  // to forward that to the encrypted socket for proper teardown
  c.rawStream.on('error', autoDestroy)
  c.rawStream.once('connect', () => {
    c.rawStream.removeListener('error', autoDestroy)
  })

  encryptedSocket.on('close', function () {
    if (c.passiveConnectTimeout) clearPassiveConnectTimeout(c)
    if (c.query) c.query.destroy()
    if (c.puncher) c.puncher.destroy()
    if (c.rawStream) c.rawStream.destroy()
    c.session.destroy()
    c.sleeper.resume()
  })

  // Safe to run in the background - never throws
  if (dht.suspended) encryptedSocket.destroy(SUSPENDED())
  else connectAndHolepunch(c, opts)

  return encryptedSocket

  function autoDestroy(err) {
    maybeDestroyEncryptedSocket(c, err)
  }

  function firewall(socket, port, host) {
    // Check if the traffic originated from the socket on which we're expecting relay traffic. If so,
    // we haven't hole punched yet and the other side is just sending us traffic through the relay.
    if (c.relaySocket && isRelay(c.relaySocket, socket, port, host)) {
      c.validUpgrade = false
      return false
    }

    c.validUpgrade = true
    if (c.onsocket) {
      c.onsocket(socket, port, host)
    } else {
      c.serverSocket = socket
      c.serverAddress = { port, host }
    }
    return false
  }
}

function isDone(c) {
  // we are destroying or the puncher is connected - done
  if (c.encryptedSocket.destroying || !!(c.puncher && c.puncher.connected)) {
    return true
  }
  // not destroying, but no raw stream - def not done
  if (c.encryptedSocket.rawStream === null) {
    return false
  }
  // we are relayed, but the puncher is not done yet
  if (c.relaySocket && !!(c.puncher && !c.puncher.connected && !c.puncher.destroyed)) {
    return false
  }
  // we are done
  return true
}

async function retryRoute(c, route) {
  const ref = c.dht._socketPool.lookup(route.socket)

  if (!ref) {
    if (route.socket === c.dht.socket) {
      await connectThroughNode(c, route.address, c.dht.socket)
    }
    return
  }

  ref.active()

  try {
    await connectThroughNode(c, route.address, route.socket)
  } catch {
    // if error, just ignore, and continue through the existing strat
  }

  ref.inactive()
}

async function connectAndHolepunch(c, opts) {
  const route = c.reusableSocket ? c.dht._socketPool.routes.get(c.remotePublicKey) : null

  if (route) {
    await retryRoute(c, route)
    if (isDone(c)) return
  }

  await findAndConnect(c, opts)
  if (isDone(c)) return

  if (!c.connect) {
    // TODO: just a quick fix for now, should retry prob
    maybeDestroyEncryptedSocket(c, HANDSHAKE_INVALID())
    return
  }

  await holepunch(c, opts)
}

function getFirstRemoteAddress(addrs, serverAddress) {
  for (const addr of addrs) {
    if (isBogon(addr.host)) continue
    return addr
  }

  return serverAddress
}

async function holepunch(c, opts) {
  let { relayAddress, serverAddress, clientAddress, payload } = c.connect

  const remoteHolepunchable = !!(payload.holepunch && payload.holepunch.relays.length)
  const relayed = diffAddress(serverAddress, relayAddress)

  if (payload.firewall === FIREWALL.OPEN || (relayed && !remoteHolepunchable)) {
    const addr = getFirstRemoteAddress(payload.addresses4, serverAddress)
    if (addr) {
      const socket = c.dht.socket
      c.dht.stats.punches.open++
      c.onsocket(socket, addr.port, addr.host)
      return
    }
    // TODO: check all addresses also obvs
  }

  const onabort = () => {
    c.session.destroy()
    maybeDestroyEncryptedSocket(c, HOLEPUNCH_ABORTED())
  }

  if (c.firewall === FIREWALL.OPEN) {
    c.passiveConnectTimeout = setTimeout(onabort, 10000)
    return
  }

  // Race the LAN shortcut with normal holepunch when possible. A blocked LAN ping
  // should not abort a connection that can still holepunch. If the remote is not
  // holepunchable, the LAN ping is the only remaining path.
  if (c.lan && relayed && clientAddress.host === serverAddress.host) {
    const serverAddresses = payload.addresses4.filter(onlyNonReserved)

    if (serverAddresses.length > 0) {
      const myAddresses = Holepuncher.localAddresses(c.dht.io.serverSocket)
      const addr = Holepuncher.matchAddress(myAddresses, serverAddresses) || serverAddresses[0]

      const socket = c.dht.io.serverSocket
      c.dht.ping(addr).then(onlanconnect, onlanerror)
      if (!remoteHolepunchable) return

      function onlanconnect() {
        // LAN and holepunch completions both converge through onsocket,
        // which claims c.rawStream once and ignores later winners.
        if (isDone(c)) return
        c.onsocket(socket, addr.port, addr.host)
      }

      function onlanerror() {
        if (!remoteHolepunchable) {
          maybeDestroyEncryptedSocket(c, HOLEPUNCH_ABORTED())
        }
      }
    }
  }

  if (!remoteHolepunchable) {
    maybeDestroyEncryptedSocket(c, CANNOT_HOLEPUNCH())
    return
  }

  c.puncher = new Holepuncher(c.dht, c.session, true, payload.firewall)

  c.puncher.onconnect = c.onsocket
  c.puncher.onabort = onabort

  const serverRelay = pickServerRelay(payload.holepunch.relays, relayAddress)

  // Begin holepunching!

  let probe
  try {
    probe = await probeRound(c, opts.fastOpen === false ? null : serverAddress, serverRelay, true)
  } catch (err) {
    destroyPuncher(c)
    // TODO: we should retry here with some of the other relays, bail for now
    maybeDestroyEncryptedSocket(c, err)
    return
  }

  if (isDone(c) || !probe) return
  const { token, peerAddress } = probe

  // If the relay the server picked is the same as the relay the client picked,
  // then we can use the peerAddress that round one indicates the server wants to use.
  // This shaves off a roundtrip if the server chose to reroll its socket due to some NAT
  // issue with the first one it picked (ie mobile nat inconsistencies...).
  // If the relays were different, then the server would not have a UDP session open on this address
  // to the client relay, which round2 uses.
  if (
    !diffAddress(serverRelay.relayAddress, relayAddress) &&
    diffAddress(serverAddress, peerAddress)
  ) {
    serverAddress = peerAddress
    await c.puncher.openSession(serverAddress)
    if (isDone(c)) return
  }

  // TODO: still continue here if a local connection might work, but then do not holepunch...
  if (
    opts.holepunch &&
    !opts.holepunch(
      c.puncher.remoteFirewall,
      c.puncher.nat.firewall,
      c.puncher.remoteAddresses,
      c.puncher.nat.addresses
    )
  ) {
    await abort(c, serverRelay, HOLEPUNCH_ABORTED('Client aborted holepunch'))
    return
  }

  try {
    await roundPunch(c, serverAddress, token, relayAddress, serverRelay, false)
  } catch (err) {
    destroyPuncher(c)
    // TODO: retry with another relay?
    maybeDestroyEncryptedSocket(c, err)
  }
}

async function connectThroughNodes(c, addresses, socket) {
  for (const address of addresses) {
    if (isDone(c) || c.connect) return

    c.remoteRelayAddresses.push(address)
    await connectThroughNode(c, address, socket)
  }
}

async function findAndConnect(c, opts) {
  let attempts = 0
  let relayAddresses =
    opts.relayAddresses && opts.relayAddresses.length ? opts.relayAddresses : null

  if (!relayAddresses) {
    const cachedRelayAddresses = c.dht._relayAddressesCache.get(c.id)
    if (cachedRelayAddresses) relayAddresses = cachedRelayAddresses
  }

  if (c.dht._persistent) {
    // check if we know the route ourself...
    const route = c.dht._router.get(c.target)
    if (route && route.relay !== null) {
      relayAddresses = [{ host: route.relay.host, port: route.relay.port }]
    }
  }

  // 2 is how many parallel connect attempts we want to do, we can make this configurable
  const preConnect = relayAddresses !== null && relayAddresses.length > 0
  const sem = new Semaphore(preConnect ? 3 : 2)
  const signal = sem.signal.bind(sem)
  const tries = relayAddresses !== null ? 2 : 1

  if (preConnect) {
    await sem.wait()
    connectThroughNodes(c, relayAddresses, null).then(signal, signal)
  }

  try {
    for (let i = 0; i < tries && !isDone(c) && !c.connect; i++) {
      c.query = c.dht.findPeer(c.target, {
        hash: false,
        session: c.session,
        nodes: relayAddresses,
        retries: 3
      })

      for await (const data of c.query) {
        await sem.wait()
        if (isDone(c)) return

        if (c.connect) {
          sem.signal()
          break
        }

        // Skip node already run via preConnect
        if (preConnect && relayAddresses && isRelayAddress(relayAddresses, data)) {
          sem.signal()
          continue
        }

        c.remoteRelayAddresses.push(data.from)
        attempts++
        connectThroughNode(c, data.from, null).then(signal, signal)
      }

      relayAddresses = null

      if (attempts > 0) await sem.flush()
    }

    c.query = null
    if (isDone(c)) return

    // flush the semaphore
    await sem.flush()
    if (isDone(c)) return
  } catch (err) {
    c.query = null
    maybeDestroyEncryptedSocket(c, err)
    return
  }

  if (!c.connect) {
    maybeDestroyEncryptedSocket(c, attempts ? PEER_CONNECTION_FAILED() : PEER_NOT_FOUND())
  }
}

async function connectThroughNode(c, address, socket) {
  if (!c.requesting) {
    // If we have a stable server address, send it over now
    const addr = c.dht.remoteAddress()
    const localAddrs = c.lan ? Holepuncher.localAddresses(c.dht.io.serverSocket) : null
    const addresses4 = []

    if (addr) addresses4.push(addr)
    if (localAddrs) addresses4.push(...localAddrs)

    c.firewall = addr ? FIREWALL.OPEN : FIREWALL.UNKNOWN
    c.requesting = true
    c.request = await c.handshake.send({
      error: ERROR.NONE,
      firewall: c.firewall,
      holepunch: null,
      addresses4,
      addresses6: [],
      udx: {
        reusableSocket: c.reusableSocket,
        id: c.rawStream.id,
        seq: 0
      },
      secretStream: {},
      relayThrough: c.relayThrough ? { publicKey: c.relayThrough, token: c.relayToken } : null
    })
    if (isDone(c)) return
  }

  const { serverAddress, clientAddress, relayed, noise } = await c.dht._router.peerHandshake(
    c.target,
    { noise: c.request, socket, session: c.session },
    address
  )
  if (isDone(c) || c.connect) return

  const payload = await c.handshake.recv(noise)
  if (isDone(c) || !payload) return

  if (payload.version !== 1) {
    maybeDestroyEncryptedSocket(c, SERVER_INCOMPATIBLE())
    return
  }
  if (payload.error !== ERROR.NONE) {
    maybeDestroyEncryptedSocket(c, SERVER_ERROR())
    return
  }
  if (!payload.udx) {
    maybeDestroyEncryptedSocket(c, SERVER_ERROR('Server did not send UDX data'))
    return
  }

  const hs = c.handshake.final()

  c.handshake = null
  c.request = null
  c.requesting = false
  c.connect = {
    relayed,
    relayAddress: address,
    clientAddress,
    serverAddress,
    payload
  }

  c.payload = new SecurePayload(hs.holepunchSecret)

  c.onsocket = function (socket, port, host) {
    if (c.rawStream === null) return // Already hole punched

    if (c.rawStream.connected) {
      const rawStream = c.rawStream
      const remoteChanging = rawStream.changeRemote(socket, c.connect.payload.udx.id, port, host)
      confirmDirectUpgrade(c, rawStream, remoteChanging)
    } else {
      // cache the relay addrs for a future reconnect, we prefer the remote one so they
      // can give us the correct ones from their pov
      if (payload.relayAddresses && payload.relayAddresses.length) {
        c.dht._relayAddressesCache.set(c.id, payload.relayAddresses)
      } else if (c.remoteRelayAddresses.length) {
        c.dht._relayAddressesCache.set(c.id, c.remoteRelayAddresses)
      }

      c.rawStream.connect(socket, c.connect.payload.udx.id, port, host)
      c.encryptedSocket.start(c.rawStream, { handshake: hs })
    }

    if (c.reusableSocket && payload.udx.reusableSocket) {
      c.dht._socketPool.routes.add(c.remotePublicKey, c.rawStream)
    }

    if (c.puncher) {
      c.puncher.onabort = noop
      c.puncher.destroy()
    }

    if (c.passiveConnectTimeout) {
      clearPassiveConnectTimeout(c)
    }

    c.rawStream = null
  }

  if (payload.relayThrough || c.relayThrough) {
    relayConnection(c, c.relayThrough, payload, hs)
  }

  if (c.serverSocket) {
    c.onsocket(c.serverSocket, c.serverAddress.port, c.serverAddress.host)
    return
  }

  if (!relayed) {
    c.onsocket(socket || c.dht.socket, address.port, address.host)
  }

  c.session.destroy()
}

async function updateHolepunch(c, peerAddress, relayAddr, payload) {
  const holepunch = await c.dht._router.peerHolepunch(
    c.target,
    {
      id: c.connect.payload.holepunch.id,
      payload: c.payload.encrypt(payload),
      peerAddress,
      socket: c.puncher.socket,
      session: c.session
    },
    relayAddr
  )

  if (isDone(c)) return null

  const remotePayload = c.payload.decrypt(holepunch.payload)
  if (!remotePayload) {
    throw HOLEPUNCH_INVALID()
  }

  const { error, firewall, punching, addresses, remoteToken } = remotePayload

  if (error === ERROR.TRY_LATER && c.relayToken && payload.punching) {
    return {
      tryLater: true,
      ...holepunch,
      payload: remotePayload
    }
  }

  if (error !== ERROR.NONE) {
    throw REMOTE_ABORTED('Remote aborted with error code ' + error)
  }

  const echoed = !!(remoteToken && payload.token && b4a.equals(remoteToken, payload.token))

  c.puncher.updateRemote({
    punching,
    firewall,
    addresses,
    verified: echoed ? peerAddress.host : null
  })

  return {
    tryLater: false,
    ...holepunch,
    payload: remotePayload
  }
}

async function probeRound(c, serverAddress, serverRelay, retry) {
  // Open a quick low ttl session against what we think is the server
  if (serverAddress) await c.puncher.openSession(serverAddress)

  if (isDone(c)) return null

  const reply = await updateHolepunch(c, serverRelay.peerAddress, serverRelay.relayAddress, {
    error: ERROR.NONE,
    firewall: c.puncher.nat.firewall,
    round: c.round++,
    connected: false,
    punching: false,
    addresses: c.puncher.nat.addresses,
    remoteAddress: serverAddress,
    token: null,
    remoteToken: null
  })

  if (isDone(c) || !reply) return null

  const { peerAddress } = reply
  const { address, token } = reply.payload

  c.puncher.nat.add(reply.to, reply.from)

  // Open another quick low ttl session against what the server says their address is,
  // if they haven't said they are random yet
  if (
    c.puncher.remoteFirewall < FIREWALL.RANDOM &&
    address &&
    address.host &&
    address.port &&
    diffAddress(address, serverAddress)
  ) {
    await c.puncher.openSession(address)
    if (isDone(c)) return null
  }

  // If the remote told us they didn't know their nat firewall yet, give them a chance to figure it out
  // They might say this to see if the "fast mode" punch comes through first.
  if (c.puncher.remoteFirewall === FIREWALL.UNKNOWN) {
    await c.sleeper.pause(1000)
    if (isDone(c)) return null
  }

  let stable = await c.puncher.analyze(false)
  if (isDone(c)) return null

  // If the socket seems unstable, try to make it stable by setting the "allowReopen" flag
  // Mostly relevant for mobile networks
  if (!stable) {
    stable = await c.puncher.analyze(true)
    if (isDone(c)) return null
    if (stable) return probeRound(c, serverAddress, serverRelay, false)
  }

  if ((c.puncher.remoteFirewall === FIREWALL.UNKNOWN || !token) && retry) {
    return probeRound(c, serverAddress, serverRelay, false)
  }

  if (
    c.puncher.remoteFirewall === FIREWALL.UNKNOWN ||
    c.puncher.nat.firewall === FIREWALL.UNKNOWN
  ) {
    await abort(c, serverRelay, HOLEPUNCH_PROBE_TIMEOUT())
    return null
  }

  if (c.puncher.remoteFirewall >= FIREWALL.RANDOM && c.puncher.nat.firewall >= FIREWALL.RANDOM) {
    await abort(c, serverRelay, HOLEPUNCH_DOUBLE_RANDOMIZED_NATS())
    return null
  }

  return { token, peerAddress }
}

async function roundPunch(c, serverAddress, remoteToken, clientRelay, serverRelay, delayed) {
  // We are gossiping our final NAT status to the other peer now
  // so make sure we don't update our local view for now as that can make things weird
  c.puncher.nat.freeze()

  const isRandom =
    c.puncher.remoteFirewall >= FIREWALL.RANDOM || c.puncher.nat.firewall >= FIREWALL.RANDOM
  if (isRandom) {
    while (
      c.dht._randomPunches >= c.dht._randomPunchLimit ||
      Date.now() - c.dht._lastRandomPunch < c.dht._randomPunchInterval
    ) {
      // if no relay can help, bail
      if (!c.relayToken) throw HOLEPUNCH_ABORTED()

      if (!delayed) {
        delayed = true
        await updateHolepunch(c, serverAddress, clientRelay, {
          error: ERROR.NONE,
          firewall: c.puncher.nat.firewall,
          round: c.round++,
          connected: false,
          punching: false,
          addresses: c.puncher.nat.addresses,
          remoteAddress: null,
          token: c.payload.token(serverAddress),
          remoteToken
        })
        if (isDone(c)) return
      }

      await tryLater(c)
      if (isDone(c)) return
    }
  }

  // increment now, so we can commit to punching
  if (isRandom) c.dht._randomPunches++

  let reply

  try {
    // if delayed switch to the servers chosen relay - we validated anyway
    reply = await updateHolepunch(
      c,
      delayed ? serverRelay.peerAddress : serverAddress,
      delayed ? serverRelay.relayAddress : clientRelay,
      {
        error: ERROR.NONE,
        firewall: c.puncher.nat.firewall,
        round: c.round++,
        connected: false,
        punching: true,
        addresses: c.puncher.nat.addresses,
        remoteAddress: null,
        token: delayed ? null : c.payload.token(serverAddress),
        remoteToken
      }
    )
  } finally {
    // decrement as punch increments for us
    if (isRandom) c.dht._randomPunches--
  }

  if (isDone(c)) return
  if (!reply) return

  if (reply.tryLater) {
    await tryLater(c)
    if (isDone(c)) return
    return roundPunch(c, serverAddress, remoteToken, clientRelay, serverRelay, true)
  }

  if (!c.puncher.remoteHolepunching) {
    throw REMOTE_NOT_HOLEPUNCHING()
  }

  if (!(await c.puncher.punch())) {
    throw REMOTE_NOT_HOLEPUNCHABLE()
  }
}

async function tryLater(c) {
  if (!c.relayToken) throw HOLEPUNCH_ABORTED()
  await c.sleeper.pause(10000 + Math.round(Math.random() * 10000))
}

function maybeDestroyEncryptedSocket(c, err) {
  if (isDone(c)) return
  if (c.encryptedSocket.rawStream) return
  if (c.relaySocket) return // waiting for the relay
  if (c.puncher && !c.puncher.destroyed) return // waiting for the puncher
  c.session.destroy()
  c.encryptedSocket.destroy(err)
}

async function abort(c, { peerAddress, relayAddress }, err) {
  try {
    await updateHolepunch(c, peerAddress, relayAddress, {
      error: ERROR.ABORTED,
      firewall: FIREWALL.UNKNOWN,
      round: c.round++,
      connected: false,
      punching: false,
      addresses: null,
      remoteAddress: null,
      token: null,
      remoteToken: null
    })
  } catch (err) {
    safetyCatch(err)
  }

  destroyPuncher(c)
  maybeDestroyEncryptedSocket(c, err)
}

function relayConnection(c, relayThrough, payload, hs) {
  let isInitiator
  let publicKey
  let token

  if (payload.relayThrough) {
    isInitiator = false
    publicKey = payload.relayThrough.publicKey
    token = payload.relayThrough.token
  } else {
    isInitiator = true
    publicKey = relayThrough
    token = c.relayToken
  }

  c.relayToken = token
  c.relaySocket = c.dht.connect(publicKey)
  c.relaySocket.setKeepAlive(c.relayKeepAlive)
  c.relayClient = relay.Client.from(c.relaySocket, { id: c.relaySocket.publicKey })
  c.relayTimeout = setTimeout(onabort, 15000, null)

  c.relayClient.pair(isInitiator, token, c.rawStream).on('error', onabort).on('data', ondata)

  function ondata(remoteId) {
    if (c.relayTimeout) clearRelayTimeout(c)
    if (c.rawStream === null) {
      onabort(null)
      return
    }

    c.relayPaired = true

    const { remotePort, remoteHost, socket } = c.relaySocket.rawStream

    c.rawStream
      .on('close', () => destroyRelayConnection(c))
      .connect(socket, remoteId, remotePort, remoteHost)

    c.encryptedSocket.start(c.rawStream, { handshake: hs })
  }

  function onabort(err) {
    destroyRelayConnection(c)
    maybeDestroyEncryptedSocket(c, err || RELAY_ABORTED())
  }
}

function clearPassiveConnectTimeout(c) {
  clearTimeout(c.passiveConnectTimeout)
  c.passiveConnectTimeout = null
}

function destroyPuncher(c) {
  if (c.puncher) c.puncher.destroy()
  c.session.destroy()
}

function pickServerRelay(relays, clientRelay) {
  for (const r of relays) {
    if (!diffAddress(r.relayAddress, clientRelay)) return r
  }
  return relays[0]
}

function diffAddress(a, b) {
  return a.host !== b.host || a.port !== b.port
}

function defaultCreateHandshake(keyPair, remotePublicKey) {
  return new NoiseWrap(keyPair, remotePublicKey)
}

function defaultCreateSecretStream(isInitiator, rawStream, opts) {
  return new NoiseSecretStream(isInitiator, rawStream, opts)
}

function onlyNonReserved(addr) {
  return !isReserved(addr.host)
}

function isRelay(relaySocket, socket, port, host) {
  const stream = relaySocket.rawStream
  if (!stream) return false
  if (stream.socket !== socket) return false
  return port === stream.remotePort && host === stream.remoteHost
}

function selectRelay(relayThrough) {
  if (typeof relayThrough === 'function') relayThrough = relayThrough()
  if (relayThrough === null) return null
  if (Array.isArray(relayThrough))
    return relayThrough[Math.floor(Math.random() * relayThrough.length)]
  return relayThrough
}

function isRelayAddress(relayAddresses, data) {
  for (const node of relayAddresses) {
    if (node.host === data.from.host && node.port === data.from.port) return true
  }

  return false
}

function noop() {}
const EventEmitter = require('events')
const b4a = require('b4a')
const errors = require('./errors')

module.exports = class ConnectionPool extends EventEmitter {
  constructor(dht) {
    super()

    this._dht = dht
    this._servers = new Map()
    this._connecting = new Map()
    this._connections = new Map()
  }

  _attachServer(server) {
    const keyString = b4a.toString(server.publicKey, 'hex')

    this._servers.set(keyString, server)

    server
      .on('close', () => {
        this._servers.delete(keyString)
      })
      .on('connection', (socket) => {
        this._attachStream(socket, true)
      })
  }

  _attachStream(stream, opened) {
    const existing = this.get(stream.remotePublicKey)

    if (existing) {
      const keepNew =
        stream.isInitiator === existing.isInitiator ||
        b4a.compare(stream.publicKey, stream.remotePublicKey) > 0

      if (keepNew) {
        let closed = false

        const onclose = () => {
          closed = true
        }

        existing
          .on('error', noop)
          .on('close', () => {
            if (closed) return

            stream.off('error', noop).off('close', onclose)

            this._attachStream(stream, opened)
          })
          .destroy(errors.DUPLICATE_CONNECTION())

        stream.on('error', noop).on('close', onclose)
      } else {
        stream.on('error', noop).destroy(errors.DUPLICATE_CONNECTION())
      }

      return
    }

    const session = new ConnectionRef(this, stream)

    const keyString = b4a.toString(stream.remotePublicKey, 'hex')

    if (opened) {
      this._connections.set(keyString, session)

      stream.on('close', () => {
        this._connections.delete(keyString)
      })

      this.emit('connection', stream, session)
    } else {
      this._connecting.set(keyString, session)

      stream
        .on('error', noop)
        .on('close', () => {
          if (opened) this._connections.delete(keyString)
          else this._connecting.delete(keyString)
        })
        .on('open', () => {
          opened = true

          this._connecting.delete(keyString)
          this._connections.set(keyString, session)

          stream.off('error', noop)

          this.emit('connection', stream, session)
        })
    }

    return session
  }

  get connecting() {
    return this._connecting.size
  }

  get connections() {
    return this._connections.values()
  }

  has(publicKey) {
    const keyString = b4a.toString(publicKey, 'hex')

    return this._connections.has(keyString) || this._connecting.has(keyString)
  }

  get(publicKey) {
    const keyString = b4a.toString(publicKey, 'hex')

    const existing = this._connections.get(keyString) || this._connecting.get(keyString)

    return existing?._stream || null
  }
}

class ConnectionRef {
  constructor(pool, stream) {
    this._pool = pool
    this._stream = stream
    this._refs = 0
  }

  active() {
    this._refs++
  }

  inactive() {
    this._refs--
  }

  release() {
    this._stream.destroy()
  }
}

function noop() {}
const crypto = require('hypercore-crypto')

const COMMANDS = (exports.COMMANDS = {
  PEER_HANDSHAKE: 0,
  PEER_HOLEPUNCH: 1,
  FIND_PEER: 2,
  LOOKUP: 3,
  ANNOUNCE: 4,
  UNANNOUNCE: 5,
  MUTABLE_PUT: 6,
  MUTABLE_GET: 7,
  IMMUTABLE_PUT: 8,
  IMMUTABLE_GET: 9,
  PLUGIN: 10
})

exports.BOOTSTRAP_NODES = [
  '88.99.3.86@node1.hyperdht.org:49737',
  '142.93.90.113@node2.hyperdht.org:49737',
  '138.68.147.8@node3.hyperdht.org:49737'
]

exports.FIREWALL = {
  UNKNOWN: 0,
  OPEN: 1,
  CONSISTENT: 2,
  RANDOM: 3
}

exports.ERROR = {
  // noise / connection related
  NONE: 0,
  ABORTED: 1,
  VERSION_MISMATCH: 2,
  TRY_LATER: 3,
  // dht related
  SEQ_REUSED: 16,
  SEQ_TOO_LOW: 17
}

const [
  NS_ANNOUNCE,
  NS_UNANNOUNCE,
  NS_MUTABLE_PUT,
  NS_PEER_HANDSHAKE,
  NS_PEER_HOLEPUNCH,
  NS_PLUGIN
] = crypto.namespace('hyperswarm/dht', [
  COMMANDS.ANNOUNCE,
  COMMANDS.UNANNOUNCE,
  COMMANDS.MUTABLE_PUT,
  COMMANDS.PEER_HANDSHAKE,
  COMMANDS.PEER_HOLEPUNCH,
  COMMANDS.PLUGIN
])

exports.NS = {
  ANNOUNCE: NS_ANNOUNCE,
  UNANNOUNCE: NS_UNANNOUNCE,
  MUTABLE_PUT: NS_MUTABLE_PUT,
  PEER_HANDSHAKE: NS_PEER_HANDSHAKE,
  PEER_HOLEPUNCH: NS_PEER_HOLEPUNCH,
  PLUGIN: NS_PLUGIN
}
const sodium = require('sodium-universal')
const b4a = require('b4a')

function hash(data) {
  const out = b4a.allocUnsafe(32)
  sodium.crypto_generichash(out, data)
  return out
}

function unslabbedHash(data) {
  const out = b4a.allocUnsafeSlow(32)
  sodium.crypto_generichash(out, data)
  return out
}

function createKeyPair(seed) {
  const publicKey = b4a.alloc(32)
  const secretKey = b4a.alloc(64)
  if (seed) sodium.crypto_sign_seed_keypair(publicKey, secretKey, seed)
  else sodium.crypto_sign_keypair(publicKey, secretKey)
  return { publicKey, secretKey }
}

module.exports = {
  hash,
  unslabbedHash,
  createKeyPair
}
const b4a = require('b4a')
const cenc = require('compact-encoding')

function encodeUnslab(enc, m) {
  // Faster than unslab(c.encode(enc, data)) because it avoids the mem copy.
  // Makes sense to put in compact-encoding when we need it in other modules too
  const state = cenc.state()
  enc.preencode(state, m)
  state.buffer = b4a.allocUnsafeSlow(state.end)
  enc.encode(state, m)
  return state.buffer
}

module.exports = {
  encodeUnslab
}
module.exports = class DHTError extends Error {
  constructor(msg, code, fn = DHTError) {
    super(`${code}: ${msg}`)
    this.code = code

    if (Error.captureStackTrace) {
      Error.captureStackTrace(this, fn)
    }
  }

  get name() {
    return 'DHTError'
  }

  static BAD_HANDSHAKE_REPLY(msg = 'Bad handshake reply') {
    return new DHTError(msg, 'BAD_HANDSHAKE_REPLY', DHTError.BAD_HANDSHAKE_REPLY)
  }

  static BAD_HOLEPUNCH_REPLY(msg = 'Bad holepunch reply') {
    return new DHTError(msg, 'BAD_HOLEPUNCH_REPLY', DHTError.BAD_HOLEPUNCH_REPLY)
  }

  static HOLEPUNCH_ABORTED(msg = 'Holepunch aborted') {
    return new DHTError(msg, 'HOLEPUNCH_ABORTED', DHTError.HOLEPUNCH_ABORTED)
  }

  static HOLEPUNCH_INVALID(msg = 'Invalid holepunch payload') {
    return new DHTError(msg, 'HOLEPUNCH_INVALID', DHTError.HOLEPUNCH_INVALID)
  }

  static HOLEPUNCH_PROBE_TIMEOUT(msg = 'Holepunching probe did not finish in time') {
    return new DHTError(msg, 'HOLEPUNCH_PROBE_TIMEOUT', DHTError.HOLEPUNCH_PROBE_TIMEOUT)
  }

  static HOLEPUNCH_DOUBLE_RANDOMIZED_NATS(msg = 'Both remote and local NATs are randomized') {
    return new DHTError(
      msg,
      'HOLEPUNCH_DOUBLE_RANDOMIZED_NATS',
      DHTError.HOLEPUNCH_DOUBLE_RANDOMIZED_NATS
    )
  }

  static CANNOT_HOLEPUNCH(msg = 'Cannot holepunch to remote') {
    return new DHTError(msg, 'CANNOT_HOLEPUNCH', DHTError.CANNOT_HOLEPUNCH)
  }

  static REMOTE_NOT_HOLEPUNCHING(msg = 'Remote is not holepunching') {
    return new DHTError(msg, 'REMOTE_NOT_HOLEPUNCHING', DHTError.REMOTE_NOT_HOLEPUNCHING)
  }

  static REMOTE_NOT_HOLEPUNCHABLE(msg = 'Remote is not holepunchable') {
    return new DHTError(msg, 'REMOTE_NOT_HOLEPUNCHABLE', DHTError.REMOTE_NOT_HOLEPUNCHABLE)
  }

  static REMOTE_ABORTED(msg = 'Remote aborted') {
    return new DHTError(msg, 'REMOTE_ABORTED', DHTError.REMOTE_ABORTED)
  }

  static HANDSHAKE_UNFINISHED(msg = 'Handshake did not finish') {
    return new DHTError(msg, 'HANDSHAKE_UNFINISHED', DHTError.HANDSHAKE_UNFINISHED)
  }

  static HANDSHAKE_INVALID(msg = 'Received invalid handshake') {
    return new DHTError(msg, 'HANDSHAKE_INVALID', DHTError.HANDSHAKE_INVALID)
  }

  static ALREADY_LISTENING(msg = 'Already listening') {
    return new DHTError(msg, 'ALREADY_LISTENING', DHTError.ALREADY_LISTENING)
  }

  static KEYPAIR_ALREADY_USED(msg = 'Keypair already used') {
    return new DHTError(msg, 'KEYPAIR_ALREADY_USED', DHTError.KEYPAIR_ALREADY_USED)
  }

  static NODE_DESTROYED(msg = 'Node destroyed') {
    return new DHTError(msg, 'NODE_DESTROYED', DHTError.NODE_DESTROYED)
  }

  static PEER_CONNECTION_FAILED(msg = 'Could not connect to peer') {
    return new DHTError(msg, 'PEER_CONNECTION_FAILED', DHTError.PEER_CONNECTION_FAILED)
  }

  static PEER_NOT_FOUND(msg = 'Peer not found') {
    return new DHTError(msg, 'PEER_NOT_FOUND', DHTError.PEER_NOT_FOUND)
  }

  static STREAM_NOT_CONNECTED(msg = 'Stream is not connected') {
    return new DHTError(msg, 'STREAM_NOT_CONNECTED', DHTError.STREAM_NOT_CONNECTED)
  }

  static SERVER_INCOMPATIBLE(msg = 'Server is using an incompatible version') {
    return new DHTError(msg, 'SERVER_INCOMPATIBLE', DHTError.SERVER_INCOMPATIBLE)
  }

  static SERVER_ERROR(msg = 'Server returned an error') {
    return new DHTError(msg, 'SERVER_ERROR', DHTError.SERVER_ERROR)
  }

  static DUPLICATE_CONNECTION(msg = 'Duplicate connection') {
    return new DHTError(msg, 'DUPLICATE_CONNECTION', DHTError.DUPLICATE_CONNECTION)
  }

  static RELAY_ABORTED(msg = 'Relay aborted') {
    return new DHTError(msg, 'RELAY_ABORTED', DHTError.RELAY_ABORTED)
  }

  static SUSPENDED(msg = 'Suspended') {
    return new DHTError(msg, 'SUSPENDED', DHTError.SUSPENDED)
  }
}
const b4a = require('b4a')
const Nat = require('./nat')
const Sleeper = require('./sleeper')
const { FIREWALL } = require('./constants')

const BIRTHDAY_SOCKETS = 256
const HOLEPUNCH = b4a.from([0])
const HOLEPUNCH_TTL = 5
const DEFAULT_TTL = 64
const MAX_REOPENS = 3

module.exports = class Holepuncher {
  constructor(dht, session, isInitiator, remoteFirewall = FIREWALL.UNKNOWN) {
    const holder = dht._socketPool.acquire()

    this.dht = dht
    this.session = session

    this.nat = new Nat(dht, session, holder.socket)
    this.nat.autoSample()

    this.isInitiator = isInitiator

    // events
    this.onconnect = noop
    this.onabort = noop

    this.punching = false
    this.connected = false
    this.destroyed = false
    this.randomized = false

    // track remote state
    this.remoteFirewall = remoteFirewall
    this.remoteAddresses = []
    this.remoteHolepunching = false

    this._sleeper = new Sleeper()
    this._reopening = null
    this._timeout = null
    this._punching = null
    this._allHolders = []
    this._holder = this._addRef(holder)
  }

  get socket() {
    return this._holder.socket
  }

  updateRemote({ punching, firewall, addresses, verified }) {
    const remoteAddresses = []

    if (addresses) {
      for (const addr of addresses) {
        remoteAddresses.push({
          host: addr.host,
          port: addr.port,
          verified: verified === addr.host || this._isVerified(addr.host)
        })
      }
    }

    this.remoteFirewall = firewall
    this.remoteAddresses = remoteAddresses
    this.remoteHolepunching = punching
  }

  _isVerified(host) {
    for (const addr of this.remoteAddresses) {
      if (addr.verified && addr.host === host) {
        return true
      }
    }
    return false
  }

  ping(addr, socket = this._holder.socket) {
    return holepunch(socket, addr, false)
  }

  openSession(addr, socket = this._holder.socket) {
    return holepunch(socket, addr, true)
  }

  async analyze(allowReopen) {
    await this.nat.analyzing
    if (this._unstable()) {
      if (!allowReopen) return false
      if (!this._reopening) this._reopening = this._reopen()
      return this._reopening
    }
    return true
  }

  _unstable() {
    // TODO!!: We need an additional heuristic here... If we were NOT random in the past we should also do this.
    const firewall = this.nat.firewall
    return (
      (this.remoteFirewall >= FIREWALL.RANDOM && firewall >= FIREWALL.RANDOM) ||
      firewall === FIREWALL.UNKNOWN
    )
  }

  _reset() {
    const prev = this._holder

    this._allHolders.pop()
    this._holder = this._addRef(this.dht._socketPool.acquire())

    prev.release()
    this.nat.destroy()

    this.nat = new Nat(this.dht, this.session, this._holder.socket)
    // TODO: maybe make auto sampling configurable somehow?
    this.nat.autoSample()
  }

  _addRef(ref) {
    this._allHolders.push(ref)
    ref.onholepunchmessage = (msg, rinfo) => this._onholepunchmessage(msg, rinfo, ref)
    return ref
  }

  _onholepunchmessage(_, addr, ref) {
    if (!this.isInitiator) {
      // TODO: we don't need this if we had a way to connect a socket to many hosts
      holepunch(ref.socket, addr, false) // never fails
      return
    }

    if (this.connected) return

    this.connected = true
    this.punching = false

    for (const r of this._allHolders) {
      if (r === ref) continue
      r.release()
    }

    this._allHolders[0] = ref
    while (this._allHolders.length > 1) this._allHolders.pop()

    this._decrementRandomized()
    this.onconnect(ref.socket, addr.port, addr.host)
  }

  _done() {
    return this.destroyed || this.connected
  }

  async _reopen() {
    for (let i = 0; this._unstable() && i < MAX_REOPENS && !this._done() && !this.punching; i++) {
      this._reset()
      await this.nat.analyzing
    }

    return coerceFirewall(this.nat.firewall) === FIREWALL.CONSISTENT
  }

  punch() {
    if (!this._punching) this._punching = this._punch()
    return this._punching
  }

  async _punch() {
    if (this._done() || !this.remoteAddresses.length) return false

    this.punching = true

    // Coerce into consistency for now. Obvs we could make this this more efficient if we use that info
    // but that's seldomly used since those will just use tcp most of the time.

    const local = coerceFirewall(this.nat.firewall)
    const remote = coerceFirewall(this.remoteFirewall)

    // Note that most of these async functions are meant to run in the background
    // which is why we don't await them here and why they are not allowed to throw

    let remoteVerifiedAddress = null
    for (const addr of this.remoteAddresses) {
      if (addr.verified) {
        remoteVerifiedAddress = addr
        break
      }
    }

    if (local === FIREWALL.CONSISTENT && remote === FIREWALL.CONSISTENT) {
      this.dht.stats.punches.consistent++
      this._consistentProbe()
      return true
    }

    if (!remoteVerifiedAddress) return false

    if (local === FIREWALL.CONSISTENT && remote >= FIREWALL.RANDOM) {
      this.dht.stats.punches.random++
      this._incrementRandomized()
      this._randomProbes(remoteVerifiedAddress)
      return true
    }

    if (local >= FIREWALL.RANDOM && remote === FIREWALL.CONSISTENT) {
      this.dht.stats.punches.random++
      this._incrementRandomized()
      await this._openBirthdaySockets(remoteVerifiedAddress)
      if (this.punching) this._keepAliveRandomNat(remoteVerifiedAddress)
      return true
    }

    return false
  }

  // Note that this never throws so it is safe to run in the background
  async _consistentProbe() {
    // Here we do the sleep first because the "fast open" mode in the server just fired a ping
    if (!this.isInitiator) await this._sleeper.pause(1000)

    let tries = 0

    while (this.punching && tries++ < 10) {
      for (const addr of this.remoteAddresses) {
        // only try unverified addresses every 4 ticks
        if (!addr.verified && (tries & 3) !== 0) continue
        await holepunch(this._holder.socket, addr, false)
      }
      if (this.punching) await this._sleeper.pause(1000)
    }

    this._autoDestroy()
  }

  // Note that this never throws so it is safe to run in the background
  async _randomProbes(remoteAddr) {
    let tries = 1750 // ~35s

    while (this.punching && tries-- > 0) {
      const addr = { host: remoteAddr.host, port: randomPort() }
      await holepunch(this._holder.socket, addr, false)
      if (this.punching) await this._sleeper.pause(20)
    }

    this._autoDestroy()
  }

  // Note that this never throws so it is safe to run in the background
  async _keepAliveRandomNat(remoteAddr) {
    let i = 0
    let lowTTLRounds = 1

    // TODO: experiment with this here. We just bursted all the messages in
    // openOtherSockets to ensure the sockets are open, so it's potentially
    // a good idea to slow down for a bit.
    await this._sleeper.pause(100)

    let tries = 1750 // ~35s

    while (this.punching && tries-- > 0) {
      if (i === this._allHolders.length) {
        i = 0
        if (lowTTLRounds > 0) lowTTLRounds--
      }

      await holepunch(this._allHolders[i++].socket, remoteAddr, lowTTLRounds > 0)
      if (this.punching) await this._sleeper.pause(20)
    }

    this._autoDestroy()
  }

  async _openBirthdaySockets(remoteAddr) {
    while (this.punching && this._allHolders.length < BIRTHDAY_SOCKETS) {
      const ref = this._addRef(this.dht._socketPool.acquire())
      await holepunch(ref.socket, remoteAddr, HOLEPUNCH_TTL)
    }
  }

  _autoDestroy() {
    if (!this.connected) this.destroy()
  }

  _incrementRandomized() {
    if (!this.randomized) {
      this.randomized = true
      this.dht._randomPunches++
    }
  }

  _decrementRandomized() {
    if (this.randomized) {
      this.dht._lastRandomPunch = Date.now()
      this.randomized = false
      this.dht._randomPunches--
    }
  }

  destroy() {
    if (this.destroyed) return
    this.destroyed = true
    this.punching = false

    for (const ref of this._allHolders) ref.release()
    this._allHolders = []
    this.nat.destroy()

    if (!this.connected) {
      this._decrementRandomized()
      this.onabort()
    }
  }

  static ping(socket, addr) {
    return holepunch(socket, addr, false)
  }

  static localAddresses(socket) {
    return localAddresses(socket)
  }

  static matchAddress(myAddresses, externalAddresses) {
    return matchAddress(myAddresses, externalAddresses)
  }
}

function holepunch(socket, addr, lowTTL) {
  return socket.send(HOLEPUNCH, addr.port, addr.host, lowTTL ? HOLEPUNCH_TTL : DEFAULT_TTL)
}

function randomPort() {
  return (1000 + Math.random() * 64536) | 0
}

function coerceFirewall(fw) {
  return fw === FIREWALL.OPEN ? FIREWALL.CONSISTENT : fw
}

function localAddresses(socket) {
  const addrs = []
  const { host, port } = socket.address()

  if (host === '127.0.0.1') return [{ host, port }]

  for (const n of socket.udx.networkInterfaces()) {
    if (n.family !== 4 || n.internal) continue

    addrs.push({ host: n.host, port })
  }

  if (addrs.length === 0) {
    addrs.push({ host: '127.0.0.1', port })
  }

  return addrs
}

function matchAddress(localAddresses, remoteLocalAddresses) {
  if (remoteLocalAddresses.length === 0) return null

  let best = { segment: 1, addr: null }

  for (const localAddress of localAddresses) {
    // => 192.168.122.238
    const a = localAddress.host.split('.')

    for (const remoteAddress of remoteLocalAddresses) {
      // => 192.168.0.23
      // => 192.168.122.1
      const b = remoteAddress.host.split('.')

      // Matches 192.*.*.*
      if (a[0] === b[0]) {
        if (best.segment === 1) best = { segment: 2, addr: remoteAddress }

        // Matches 192.168.*.*
        if (a[1] === b[1]) {
          if (best.segment === 2) best = { segment: 3, addr: remoteAddress }

          // Matches 192.168.122.*
          if (a[2] === b[2]) return remoteAddress
        }
      }
    }
  }

  return best.addr
}

function noop() {}
const c = require('compact-encoding')

const ipv4 = {
  ...c.ipv4Address,
  decode(state) {
    const ip = c.ipv4Address.decode(state)
    return {
      host: ip.host,
      port: ip.port
    }
  }
}

const ipv4Array = c.array(ipv4)

const ipv6 = {
  ...c.ipv6Address,
  decode(state) {
    const ip = c.ipv6Address.decode(state)
    return {
      host: ip.host,
      port: ip.port
    }
  }
}

const ipv6Array = c.array(ipv6)

exports.handshake = {
  preencode(state, m) {
    state.end += 1 + 1 + (m.peerAddress ? 6 : 0) + (m.relayAddress ? 6 : 0)
    c.buffer.preencode(state, m.noise)
  },
  encode(state, m) {
    const flags = (m.peerAddress ? 1 : 0) | (m.relayAddress ? 2 : 0)

    c.uint.encode(state, flags)
    c.uint.encode(state, m.mode)
    c.buffer.encode(state, m.noise)

    if (m.peerAddress) ipv4.encode(state, m.peerAddress)
    if (m.relayAddress) ipv4.encode(state, m.relayAddress)
  },
  decode(state) {
    const flags = c.uint.decode(state)

    return {
      mode: c.uint.decode(state),
      noise: c.buffer.decode(state),
      peerAddress: flags & 1 ? ipv4.decode(state) : null,
      relayAddress: flags & 2 ? ipv4.decode(state) : null
    }
  }
}

const relayInfo = {
  preencode(state, m) {
    state.end += 12
  },
  encode(state, m) {
    ipv4.encode(state, m.relayAddress)
    ipv4.encode(state, m.peerAddress)
  },
  decode(state) {
    return {
      relayAddress: ipv4.decode(state),
      peerAddress: ipv4.decode(state)
    }
  }
}

const relayInfoArray = c.array(relayInfo)

const holepunchInfo = {
  preencode(state, m) {
    c.uint.preencode(state, m.id)
    relayInfoArray.preencode(state, m.relays)
  },
  encode(state, m) {
    c.uint.encode(state, m.id)
    relayInfoArray.encode(state, m.relays)
  },
  decode(state) {
    return {
      id: c.uint.decode(state),
      relays: relayInfoArray.decode(state)
    }
  }
}

const udxInfo = {
  preencode(state, m) {
    state.end += 2 // version + features
    c.uint.preencode(state, m.id)
    c.uint.preencode(state, m.seq)
  },
  encode(state, m) {
    c.uint.encode(state, 1)
    c.uint.encode(state, m.reusableSocket ? 1 : 0)
    c.uint.encode(state, m.id)
    c.uint.encode(state, m.seq)
  },
  decode(state) {
    const version = c.uint.decode(state)
    const features = c.uint.decode(state)

    return {
      version,
      reusableSocket: (features & 1) !== 0,
      id: c.uint.decode(state),
      seq: c.uint.decode(state)
    }
  }
}

const secretStreamInfo = {
  preencode(state, m) {
    c.uint.preencode(state, 1)
  },
  encode(state, m) {
    c.uint.encode(state, 1)
  },
  decode(state) {
    return {
      version: c.uint.decode(state)
    }
  }
}

const relayThroughInfo = {
  preencode(state, m) {
    c.uint.preencode(state, 1) // version
    c.uint.preencode(state, 0) // flags
    c.fixed32.preencode(state, m.publicKey)
    c.fixed32.preencode(state, m.token)
  },
  encode(state, m) {
    c.uint.encode(state, 1)
    c.uint.encode(state, 0)
    c.fixed32.encode(state, m.publicKey)
    c.fixed32.encode(state, m.token)
  },
  decode(state) {
    const version = c.uint.decode(state)
    c.uint.decode(state)

    return {
      version,
      publicKey: c.fixed32.decode(state),
      token: c.fixed32.decode(state)
    }
  }
}

exports.noisePayload = {
  preencode(state, m) {
    state.end += 4 // version + flags + error + firewall
    if (m.holepunch) holepunchInfo.preencode(state, m.holepunch)
    if (m.addresses4 && m.addresses4.length) ipv4Array.preencode(state, m.addresses4)
    if (m.addresses6 && m.addresses6.length) ipv6Array.preencode(state, m.addresses6)
    if (m.udx) udxInfo.preencode(state, m.udx)
    if (m.secretStream) secretStreamInfo.preencode(state, m.secretStream)
    if (m.relayThrough) relayThroughInfo.preencode(state, m.relayThrough)
    if (m.relayAddresses) ipv4Array.preencode(state, m.relayAddresses)
  },
  encode(state, m) {
    let flags = 0

    if (m.holepunch) flags |= 1
    if (m.addresses4 && m.addresses4.length) flags |= 2
    if (m.addresses6 && m.addresses6.length) flags |= 4
    if (m.udx) flags |= 8
    if (m.secretStream) flags |= 16
    if (m.relayThrough) flags |= 32
    if (m.relayAddresses) flags |= 64

    c.uint.encode(state, 1) // version
    c.uint.encode(state, flags)
    c.uint.encode(state, m.error)
    c.uint.encode(state, m.firewall)

    if (m.holepunch) holepunchInfo.encode(state, m.holepunch)
    if (m.addresses4 && m.addresses4.length) ipv4Array.encode(state, m.addresses4)
    if (m.addresses6 && m.addresses6.length) ipv6Array.encode(state, m.addresses6)
    if (m.udx) udxInfo.encode(state, m.udx)
    if (m.secretStream) secretStreamInfo.encode(state, m.secretStream)
    if (m.relayThrough) relayThroughInfo.encode(state, m.relayThrough)
    if (m.relayAddresses) ipv4Array.encode(state, m.relayAddresses)
  },
  decode(state) {
    const version = c.uint.decode(state)

    if (version !== 1) {
      // Do not attempt to decode but return this back to the user so they can
      // actually handle it
      return {
        version,
        error: 0,
        firewall: 0,
        holepunch: null,
        addresses4: [],
        addresses6: [],
        udx: null,
        secretStream: null,
        relayThrough: null,
        relayAddresses: null
      }
    }

    const flags = c.uint.decode(state)

    return {
      version,
      error: c.uint.decode(state),
      firewall: c.uint.decode(state),
      holepunch: (flags & 1) !== 0 ? holepunchInfo.decode(state) : null,
      addresses4: (flags & 2) !== 0 ? ipv4Array.decode(state) : [],
      addresses6: (flags & 4) !== 0 ? ipv6Array.decode(state) : [],
      udx: (flags & 8) !== 0 ? udxInfo.decode(state) : null,
      secretStream: (flags & 16) !== 0 ? secretStreamInfo.decode(state) : null,
      relayThrough: (flags & 32) !== 0 ? relayThroughInfo.decode(state) : null,
      relayAddresses: (flags & 64) !== 0 ? ipv4Array.decode(state) : null
    }
  }
}

exports.holepunch = {
  preencode(state, m) {
    state.end += 2
    c.uint.preencode(state, m.id)
    c.buffer.preencode(state, m.payload)
    if (m.peerAddress) ipv4.preencode(state, m.peerAddress)
  },
  encode(state, m) {
    const flags = m.peerAddress ? 1 : 0
    c.uint.encode(state, flags)
    c.uint.encode(state, m.mode)
    c.uint.encode(state, m.id)
    c.buffer.encode(state, m.payload)
    if (m.peerAddress) ipv4.encode(state, m.peerAddress)
  },
  decode(state) {
    const flags = c.uint.decode(state)
    return {
      mode: c.uint.decode(state),
      id: c.uint.decode(state),
      payload: c.buffer.decode(state),
      peerAddress: flags & 1 ? ipv4.decode(state) : null
    }
  }
}

exports.holepunchPayload = {
  preencode(state, m) {
    state.end += 4 // flags + error + firewall + round
    if (m.addresses) ipv4Array.preencode(state, m.addresses)
    if (m.remoteAddress) state.end += 6
    if (m.token) state.end += 32
    if (m.remoteToken) state.end += 32
  },
  encode(state, m) {
    const flags =
      (m.connected ? 1 : 0) |
      (m.punching ? 2 : 0) |
      (m.addresses ? 4 : 0) |
      (m.remoteAddress ? 8 : 0) |
      (m.token ? 16 : 0) |
      (m.remoteToken ? 32 : 0)

    c.uint.encode(state, flags)
    c.uint.encode(state, m.error)
    c.uint.encode(state, m.firewall)
    c.uint.encode(state, m.round)

    if (m.addresses) ipv4Array.encode(state, m.addresses)
    if (m.remoteAddress) ipv4.encode(state, m.remoteAddress)
    if (m.token) c.fixed32.encode(state, m.token)
    if (m.remoteToken) c.fixed32.encode(state, m.remoteToken)
  },
  decode(state) {
    const flags = c.uint.decode(state)

    return {
      error: c.uint.decode(state),
      firewall: c.uint.decode(state),
      round: c.uint.decode(state),
      connected: (flags & 1) !== 0,
      punching: (flags & 2) !== 0,
      addresses: (flags & 4) !== 0 ? ipv4Array.decode(state) : null,
      remoteAddress: (flags & 8) !== 0 ? ipv4.decode(state) : null,
      token: (flags & 16) !== 0 ? c.fixed32.decode(state) : null,
      remoteToken: (flags & 32) !== 0 ? c.fixed32.decode(state) : null
    }
  }
}

const peer = (exports.peer = {
  preencode(state, m) {
    state.end += 32
    ipv4Array.preencode(state, m.relayAddresses)
  },
  encode(state, m) {
    c.fixed32.encode(state, m.publicKey)
    ipv4Array.encode(state, m.relayAddresses)
  },
  decode(state) {
    return {
      publicKey: c.fixed32.decode(state),
      relayAddresses: ipv4Array.decode(state)
    }
  }
})

const peers = (exports.peers = c.array(peer))

const rawPeers = c.array(c.raw)

exports.lookupRawReply = {
  preencode(state, m) {
    rawPeers.preencode(state, m.peers)
    c.uint.preencode(state, m.bump)
  },
  encode(state, m) {
    rawPeers.encode(state, m.peers)
    c.uint.encode(state, m.bump)
  },
  decode(state) {
    return {
      peers: peers.decode(state),
      bump: state.start < state.end ? c.uint.decode(state) : 0
    }
  }
}

exports.announce = {
  preencode(state, m) {
    state.end++ // flags
    if (m.peer) peer.preencode(state, m.peer)
    if (m.refresh) state.end += 32
    if (m.signature) state.end += 64
    if (m.bump) c.uint.preencode(state, m.bump)
  },
  encode(state, m) {
    const flags = (m.peer ? 1 : 0) | (m.refresh ? 2 : 0) | (m.signature ? 4 : 0) | (m.bump ? 8 : 0)
    c.uint.encode(state, flags)
    if (m.peer) peer.encode(state, m.peer)
    if (m.refresh) c.fixed32.encode(state, m.refresh)
    if (m.signature) c.fixed64.encode(state, m.signature)
    if (m.bump) c.uint.encode(state, m.bump)
  },
  decode(state) {
    const flags = c.uint.decode(state)

    return {
      peer: (flags & 1) !== 0 ? peer.decode(state) : null,
      refresh: (flags & 2) !== 0 ? c.fixed32.decode(state) : null,
      signature: (flags & 4) !== 0 ? c.fixed64.decode(state) : null,
      bump: (flags & 8) !== 0 ? c.uint.decode(state) : 0
    }
  }
}

exports.mutableSignable = {
  preencode(state, m) {
    c.uint.preencode(state, m.seq)
    c.buffer.preencode(state, m.value)
  },
  encode(state, m) {
    c.uint.encode(state, m.seq)
    c.buffer.encode(state, m.value)
  },
  decode(state) {
    return {
      seq: c.uint.decode(state),
      value: c.buffer.decode(state)
    }
  }
}

exports.mutablePutRequest = {
  preencode(state, m) {
    c.fixed32.preencode(state, m.publicKey)
    c.uint.preencode(state, m.seq)
    c.buffer.preencode(state, m.value)
    c.fixed64.preencode(state, m.signature)
  },
  encode(state, m) {
    c.fixed32.encode(state, m.publicKey)
    c.uint.encode(state, m.seq)
    c.buffer.encode(state, m.value)
    c.fixed64.encode(state, m.signature)
  },
  decode(state) {
    return {
      publicKey: c.fixed32.decode(state),
      seq: c.uint.decode(state),
      value: c.buffer.decode(state),
      signature: c.fixed64.decode(state)
    }
  }
}

exports.mutableGetResponse = {
  preencode(state, m) {
    c.uint.preencode(state, m.seq)
    c.buffer.preencode(state, m.value)
    c.fixed64.preencode(state, m.signature)
  },
  encode(state, m) {
    c.uint.encode(state, m.seq)
    c.buffer.encode(state, m.value)
    c.fixed64.encode(state, m.signature)
  },
  decode(state) {
    return {
      seq: c.uint.decode(state),
      value: c.buffer.decode(state),
      signature: c.fixed64.decode(state)
    }
  }
}

exports.pluginRequest = {
  preencode(state, m) {
    c.string.preencode(state, m.plugin)
    c.uint.preencode(state, m.version)
    c.uint.preencode(state, m.command)
    state.end++ // max flag is 1 so always one byte

    if (m.value) c.buffer.preencode(state, m.value)
  },
  encode(state, m) {
    const flags = m.value ? 1 : 0

    c.string.encode(state, m.plugin)
    c.uint.encode(state, m.version)
    c.uint.encode(state, m.command)
    c.uint.encode(state, flags)

    if (m.value) c.buffer.encode(state, m.value)
  },
  decode(state) {
    const r0 = c.string.decode(state)
    const r1 = c.uint.decode(state)
    const r2 = c.uint.decode(state)
    const flags = c.uint.decode(state)

    return {
      plugin: r0,
      version: r1,
      command: r2,
      value: (flags & 1) !== 0 ? c.buffer.decode(state) : null
    }
  }
}
const { FIREWALL } = require('../lib/constants')

module.exports = class Nat {
  constructor(dht, session, socket) {
    this._samplesHost = []
    this._samplesFull = []
    this._visited = new Map()
    this._resolve = null
    this._minSamples = 4
    this._autoSampling = false

    this.dht = dht
    this.session = session
    this.socket = socket

    this.sampled = 0
    this.firewall = dht.firewalled ? FIREWALL.UNKNOWN : FIREWALL.OPEN
    this.addresses = null

    this.analyzing = new Promise((resolve) => {
      this._resolve = resolve
    })
  }

  autoSample(retry = true) {
    if (this._autoSampling) return
    this._autoSampling = true

    const self = this
    const socket = this.socket
    const maxPings = this._minSamples

    let skip = this.dht.nodes.length >= 8 ? 5 : 0
    let pending = 0

    // TODO: it would be best to pick the nodes to help us based on latency to us
    // That should reduce connect latency in general. We should investigate tracking that later on.

    // TODO 2: try to pick nodes with different IPs as well, as that'll help multi IP cell connections...
    // If we expose this from the nat sampler then the DHT should be able to help us filter out scams as well...

    for (
      let node = this.dht.nodes.latest;
      node && this.sampled + pending < maxPings;
      node = node.prev
    ) {
      if (skip > 0) {
        skip--
        continue
      }

      const ref = node.host + ':' + node.port

      if (this._visited.has(ref)) continue
      this._visited.set(ref, 1)

      pending++
      this.session.ping(node, { socket, retry: false }).then(onpong, onskip)
    }

    pending++
    onskip()

    function onpong(res) {
      self.add(res.to, res.from)
      onskip()
    }

    function onskip() {
      if (--pending === 0 && self.sampled < self._minSamples) {
        if (retry) {
          self._autoSampling = false
          self.autoSample(false)
          return
        }
        self._resolve()
      }
    }
  }

  destroy() {
    this._autoSampling = true
    this._minSamples = 0
    this._resolve()
  }

  unfreeze() {
    this.frozen = false
    this._updateFirewall()
    this._updateAddresses()
  }

  freeze() {
    this.frozen = true
  }

  _updateFirewall() {
    if (!this.dht.firewalled) {
      this.firewall = FIREWALL.OPEN
      return
    }

    if (this.sampled < 3) return

    const max = this._samplesFull[0].hits

    if (max >= 3) {
      this.firewall = FIREWALL.CONSISTENT
      return
    }

    if (max === 1) {
      this.firewall = FIREWALL.RANDOM
      return
    }

    // else max === 2

    // 1 host, >= 4 total samples ie, 2 bad ones -> random
    if (this._samplesHost.length === 1 && this.sampled > 3) {
      this.firewall = FIREWALL.RANDOM
      return
    }

    // double hit on two different ips -> assume consistent
    if (this._samplesHost.length > 1 && this._samplesFull[1].hits > 1) {
      this.firewall = FIREWALL.CONSISTENT
      return
    }

    // (4 is just means - all the samples we expect) - no decision - assume random
    if (this.sampled > 4) {
      this.firewall = FIREWALL.RANDOM
    }
  }

  _updateAddresses() {
    if (this.firewall === FIREWALL.UNKNOWN) {
      this.addresses = null
      return
    }

    if (this.firewall === FIREWALL.RANDOM) {
      this.addresses = [this._samplesHost[0]]
      return
    }

    if (this.firewall === FIREWALL.CONSISTENT) {
      this.addresses = []
      for (const addr of this._samplesFull) {
        if (addr.hits >= 2 || this.addresses.length < 2) this.addresses.push(addr)
      }
    }
  }

  update() {
    if (this.dht.firewalled && this.firewall === FIREWALL.OPEN) {
      this.firewall = FIREWALL.UNKNOWN
    }
    this._updateFirewall()
    this._updateAddresses()
  }

  add(addr, from) {
    const ref = from.host + ':' + from.port

    if (this._visited.get(ref) === 2) return
    this._visited.set(ref, 2)

    addSample(this._samplesHost, addr.host, 0)
    addSample(this._samplesFull, addr.host, addr.port)

    if ((++this.sampled >= 3 || !this.dht.firewalled) && !this.frozen) {
      this.update()
    }

    if (this.firewall === FIREWALL.CONSISTENT || this.firewall === FIREWALL.OPEN) {
      this._resolve()
    } else if (this.sampled >= this._minSamples) {
      this._resolve()
    }
  }
}

function addSample(samples, host, port) {
  for (let i = 0; i < samples.length; i++) {
    const s = samples[i]

    if (s.port !== port || s.host !== host) continue
    s.hits++

    for (; i > 0; i--) {
      const prev = samples[i - 1]
      if (prev.hits >= s.hits) return
      samples[i - 1] = s
      samples[i] = prev
    }

    return
  }

  samples.push({
    host,
    port,
    hits: 1
  })
}
const NoiseSecretStream = require('@hyperswarm/secret-stream')
const NoiseHandshake = require('noise-handshake')
const curve = require('noise-curve-ed')
const c = require('compact-encoding')
const b4a = require('b4a')
const sodium = require('sodium-universal')
const m = require('./messages')
const { NS } = require('./constants')
const { HANDSHAKE_UNFINISHED } = require('./errors')

const NOISE_PROLOUGE = NS.PEER_HANDSHAKE

module.exports = class NoiseWrap {
  constructor(keyPair, remotePublicKey) {
    this.isInitiator = !!remotePublicKey
    this.remotePublicKey = remotePublicKey
    this.keyPair = keyPair
    this.handshake = new NoiseHandshake('IK', this.isInitiator, keyPair, { curve })
    this.handshake.initialise(NOISE_PROLOUGE, remotePublicKey)
  }

  send(payload) {
    const buf = c.encode(m.noisePayload, payload)
    return this.handshake.send(buf)
  }

  recv(buf) {
    const payload = c.decode(m.noisePayload, this.handshake.recv(buf))
    this.remotePublicKey = b4a.toBuffer(this.handshake.rs)
    return payload
  }

  final() {
    if (!this.handshake.complete) throw HANDSHAKE_UNFINISHED()

    const holepunchSecret = b4a.allocUnsafe(32)

    sodium.crypto_generichash(holepunchSecret, NS.PEER_HOLEPUNCH, this.handshake.hash)

    return {
      isInitiator: this.isInitiator,
      publicKey: this.keyPair.publicKey,
      streamId: this.streamId,
      remotePublicKey: this.remotePublicKey,
      remoteId: NoiseSecretStream.id(this.handshake.hash, !this.isInitiator),
      holepunchSecret,
      hash: b4a.toBuffer(this.handshake.hash),
      rx: b4a.toBuffer(this.handshake.rx),
      tx: b4a.toBuffer(this.handshake.tx)
    }
  }
}
const c = require('compact-encoding')
const sodium = require('sodium-universal')
const RecordCache = require('record-cache')
const Cache = require('xache')
const b4a = require('b4a')
const unslab = require('unslab')

const { encodeUnslab } = require('./encode')
const m = require('./messages')
const { NS, ERROR } = require('./constants')

const EMPTY = b4a.alloc(0)
const TMP = b4a.allocUnsafe(32)
const MAX_BUMP_DRIFT = 60_000

module.exports = class Persistent {
  constructor(dht, opts) {
    this.dht = dht
    this.records = new RecordCache(opts.records)
    this.bumps = new Cache(opts.bumps)
    this.refreshes = new Cache(opts.refreshes)
    this.mutables = new Cache(opts.mutables)
    this.immutables = new Cache(opts.immutables)
  }

  onlookup(req) {
    if (!req.target) return

    const k = b4a.toString(req.target, 'hex')
    const records = this.records.get(k, 20)
    const bump = this.bumps.get(k) || 0
    const fwd = this.dht._router.get(k)

    if (fwd && records.length < 20) records.push(fwd.record)

    req.reply(records.length ? c.encode(m.lookupRawReply, { peers: records, bump }) : null)
  }

  onfindpeer(req) {
    if (!req.target) return
    const fwd = this.dht._router.get(req.target)
    req.reply(fwd ? fwd.record : null)
  }

  unannounce(target, publicKey) {
    const k = b4a.toString(target, 'hex')
    sodium.crypto_generichash(TMP, publicKey)

    if (b4a.equals(TMP, target)) this.dht._router.delete(k)
    this.records.remove(k, publicKey)
  }

  onunannounce(req) {
    if (!req.target || !req.token) return

    const unann = decode(m.announce, req.value)
    if (unann === null) return

    const { peer, signature } = unann
    if (!peer || !signature) return

    const signable = annSignable(req.target, req.token, this.dht.id, unann, NS.UNANNOUNCE)

    if (!sodium.crypto_sign_verify_detached(signature, signable, peer.publicKey)) {
      return
    }

    this.unannounce(req.target, peer.publicKey)
    req.reply(null, { token: false, closerNodes: false })
  }

  _onrefresh(token, req) {
    sodium.crypto_generichash(TMP, token)
    const activeRefresh = b4a.toString(TMP, 'hex')

    const r = this.refreshes.get(activeRefresh)
    if (!r) return

    const { announceSelf, k, record } = r
    const publicKey = record.subarray(0, 32)

    if (announceSelf) {
      this.dht._router.set(k, {
        relay: req.from,
        record,
        onconnect: null,
        onholepunch: null
      })
      this.records.remove(k, publicKey)
    } else {
      this.records.add(k, publicKey, record)
    }

    this.refreshes.delete(activeRefresh)
    this.refreshes.set(b4a.toString(token, 'hex'), r)

    req.reply(null, { token: false, closerNodes: false })
  }

  onannounce(req) {
    if (!req.target || !req.token || !this.dht.id) return

    const ann = decode(m.announce, req.value)
    if (ann === null) return

    const { peer, refresh, signature, bump } = ann

    if (!peer) {
      if (!refresh) return
      this._onrefresh(refresh, req)
      return
    }

    const signable = annSignable(req.target, req.token, this.dht.id, ann, NS.ANNOUNCE)

    if (!signature || !sodium.crypto_sign_verify_detached(signature, signable, peer.publicKey)) {
      return
    }

    // TODO: it would be potentially be more optimal to allow more than 3 addresses here for a findPeer response
    // and only use max 3 for a lookup reply
    if (peer.relayAddresses.length > 3) {
      peer.relayAddresses = peer.relayAddresses.slice(0, 3)
    }

    sodium.crypto_generichash(TMP, peer.publicKey)

    const k = b4a.toString(req.target, 'hex')
    const announceSelf = b4a.equals(TMP, req.target)
    const record = encodeUnslab(m.peer, peer)

    if (announceSelf) {
      this.dht._router.set(k, {
        relay: req.from,
        record,
        onconnect: null,
        onholepunch: null
      })
      this.records.remove(k, peer.publicKey)
    } else {
      const currentBump = this.bumps.get(k) || 0
      if (bump > currentBump && bump <= Date.now() + MAX_BUMP_DRIFT) this.bumps.set(k, bump)
      this.records.add(k, peer.publicKey, record)
    }

    if (refresh) {
      this.refreshes.set(b4a.toString(refresh, 'hex'), { k, record, announceSelf })
    }

    req.reply(null, { token: false, closerNodes: false })
  }

  onmutableget(req) {
    if (!req.target || !req.value) return

    let seq = 0
    try {
      seq = c.decode(c.uint, req.value)
    } catch {
      return
    }

    const k = b4a.toString(req.target, 'hex')
    const value = this.mutables.get(k)

    if (!value) {
      req.reply(null)
      return
    }

    const localSeq = c.decode(c.uint, value)
    req.reply(localSeq < seq ? null : value)
  }

  onmutableput(req) {
    if (!req.target || !req.token || !req.value) return

    const p = decode(m.mutablePutRequest, req.value)
    if (!p) return

    const { publicKey, seq, value, signature } = p

    const hash = b4a.allocUnsafe(32)
    sodium.crypto_generichash(hash, publicKey)
    if (!b4a.equals(hash, req.target)) return

    if (!value || !verifyMutable(signature, seq, value, publicKey)) return

    const k = b4a.toString(hash, 'hex')
    const local = this.mutables.get(k)

    if (local) {
      const existing = c.decode(m.mutableGetResponse, local)
      if (existing.value && existing.seq === seq && b4a.compare(value, existing.value) !== 0) {
        req.error(ERROR.SEQ_REUSED)
        return
      }
      if (seq < existing.seq) {
        req.error(ERROR.SEQ_TOO_LOW)
        return
      }
    }

    this.mutables.set(k, encodeUnslab(m.mutableGetResponse, { seq, value, signature }))
    req.reply(null)
  }

  onimmutableget(req) {
    if (!req.target) return

    const k = b4a.toString(req.target, 'hex')
    const value = this.immutables.get(k)

    req.reply(value || null)
  }

  onimmutableput(req) {
    if (!req.target || !req.token || !req.value) return

    const hash = b4a.alloc(32)
    sodium.crypto_generichash(hash, req.value)
    if (!b4a.equals(hash, req.target)) return

    const k = b4a.toString(hash, 'hex')
    this.immutables.set(k, unslab(req.value))

    req.reply(null)
  }

  onplugin(req) {
    if (!req.value) return

    const plugreq = decode(m.pluginRequest, req.value)
    if (plugreq === null) return

    const p = this.dht.plugins.get(plugreq.plugin)
    if (!p || p.version !== plugreq.version) return

    p.onrequest(plugreq, req)
  }

  destroy() {
    this.records.destroy()
    this.refreshes.destroy()
    this.mutables.destroy()
    this.immutables.destroy()
  }

  static signMutable(seq, value, keyPair) {
    const signable = b4a.allocUnsafe(32 + 32)
    const hash = signable.subarray(32)

    signable.set(NS.MUTABLE_PUT, 0)

    sodium.crypto_generichash(hash, c.encode(m.mutableSignable, { seq, value }))
    return sign(signable, keyPair)
  }

  static verifyMutable(signature, seq, value, publicKey) {
    return verifyMutable(signature, seq, value, publicKey)
  }

  static signAnnounce(target, token, id, ann, keyPair) {
    return sign(annSignable(target, token, id, ann, NS.ANNOUNCE), keyPair)
  }

  static signUnannounce(target, token, id, ann, keyPair) {
    return sign(annSignable(target, token, id, ann, NS.UNANNOUNCE), keyPair)
  }
}

function verifyMutable(signature, seq, value, publicKey) {
  const signable = b4a.allocUnsafe(32 + 32)
  const hash = signable.subarray(32)

  signable.set(NS.MUTABLE_PUT, 0)

  sodium.crypto_generichash(hash, c.encode(m.mutableSignable, { seq, value }))
  return sodium.crypto_sign_verify_detached(signature, signable, publicKey)
}

function annSignable(target, token, id, ann, ns) {
  const signable = b4a.allocUnsafe(32 + 32)
  const hash = signable.subarray(32)

  signable.set(ns, 0)

  sodium.crypto_generichash_batch(hash, [
    target,
    id,
    token,
    c.encode(m.peer, ann.peer), // note that this is the partial encoding of the announce message so we could just use that for perf
    ann.refresh || EMPTY
  ])

  return signable
}

function sign(signable, keyPair) {
  if (keyPair.sign) {
    return keyPair.sign(signable)
  }
  const secretKey = keyPair.secretKey ? keyPair.secretKey : keyPair
  const signature = b4a.allocUnsafe(64)
  sodium.crypto_sign_detached(signature, signable, secretKey)
  return signature
}

function decode(enc, val) {
  try {
    return val && c.decode(enc, val)
  } catch (err) {
    return null
  }
}
module.exports = class RawStreamSet {
  constructor(dht) {
    this._dht = dht

    this._prefix = 16 - 1 // 16 is the default stream-set side in udx
    this._streams = new Map()
  }

  get size() {
    return this._streams.size
  }

  [Symbol.iterator]() {
    return this._streams.values()
  }

  add(opts) {
    const self = this

    // TODO: we should prob have a udx helper for id generation, given the slight complexity
    // of the below. requires a PRNG in udx tho.

    let id = 0

    while (true) {
      id = (Math.random() * 0x100000000) >>> 0

      if (this._streams.has(id & this._prefix)) continue
      break
    }

    // always have ~50% change of rolling a free one
    if (2 * this._streams.size >= this._prefix) {
      // ie 0b11111 = 0b1111 + 1 + 0b1111
      this._prefix = 2 * this._prefix + 1

      // move the prefixes over
      const next = new Map()
      for (const stream of this._streams.values()) {
        next.set(stream.id & this._prefix, stream)
      }
      this._streams = next
    }

    const stream = this._dht.udx.createStream(id, opts)
    this._streams.set(id & this._prefix, stream)

    stream.on('close', onclose)

    return stream

    function onclose() {
      self._streams.delete(id & self._prefix)
    }
  }

  async clear() {
    const destroying = []

    for (const stream of this._streams.values()) {
      destroying.push(new Promise((resolve) => stream.once('close', resolve).destroy()))
    }

    await Promise.allSettled(destroying)
  }
}
const b4a = require('b4a')
const safetyCatch = require('safety-catch')

exports.clearRelayTimeout = clearRelayTimeout
exports.closeRelayConnection = closeRelayConnection
exports.confirmDirectUpgrade = confirmDirectUpgrade
exports.destroyRelayConnection = destroyRelayConnection

function clearRelayTimeout(connectionOrHandshake) {
  clearTimeout(connectionOrHandshake.relayTimeout)
  connectionOrHandshake.relayTimeout = null
}

function closeRelayConnection(connectionOrHandshake) {
  const socket = resetRelayConnection(connectionOrHandshake)

  if (socket) socket.end()
}

function confirmDirectUpgrade(connectionOrHandshake, rawStream, remoteChanging) {
  const cleanup = () => {
    rawStream.off('data', ondirect)
    rawStream.off('message', ondirect)
    rawStream.off('close', cleanup)
  }

  function ondirect() {
    if (!connectionOrHandshake.validUpgrade) {
      // reset, aka assume from direct
      connectionOrHandshake.validUpgrade = true
      return
    }

    cleanup()
    closeRelayConnection(connectionOrHandshake)
  }

  const confirm = () => {
    rawStream.on('data', ondirect)
    rawStream.on('message', ondirect)
    rawStream.once('close', cleanup)

    // Use a raw UDX message to make the peer observe the direct path without
    // writing application data into the secret stream.
    if (rawStream.trySend) rawStream.trySend(b4a.alloc(0))
  }

  if (!remoteChanging) {
    confirm()
    return
  }

  remoteChanging.then(confirm).catch(safetyCatch)
}

function destroyRelayConnection(connectionOrHandshake) {
  const socket = resetRelayConnection(connectionOrHandshake)

  if (socket) socket.destroy()
}

function resetRelayConnection(connectionOrHandshake) {
  if (connectionOrHandshake.relayTimeout) clearRelayTimeout(connectionOrHandshake)

  const socket = connectionOrHandshake.relaySocket
  // Drop relay references so the app connection no longer keeps relay state alive.
  connectionOrHandshake.relayToken = null
  connectionOrHandshake.relaySocket = null
  connectionOrHandshake.relayClient = null

  return socket
}
const c = require('compact-encoding')
const Cache = require('xache')
const safetyCatch = require('safety-catch')
const b4a = require('b4a')
const { handshake, holepunch } = require('./messages')
const { COMMANDS } = require('./constants')
const { BAD_HANDSHAKE_REPLY, BAD_HOLEPUNCH_REPLY } = require('./errors')

const FROM_CLIENT = 0
const FROM_SERVER = 1
const FROM_RELAY = 2
const FROM_SECOND_RELAY = 3
const REPLY = 4

// TODO: While the current design is very trustless in regards to clients/servers trusting the DHT,
// we should add a bunch of rate limits everywhere, especially including here to avoid bad users
// using a DHT node to relay traffic indiscriminately using the connect/holepunch messages.
// That's mostly from an abuse POV as none of the messsages do amplication.

module.exports = class Router {
  constructor(dht, opts) {
    this.dht = dht
    this.forwards = new Cache(opts.forwards)
  }

  set(target, state) {
    if (state.onpeerhandshake) {
      this.forwards.retain(toString(target), state)
    } else {
      this.forwards.set(toString(target), state)
    }
  }

  get(target) {
    return this.forwards.get(toString(target))
  }

  delete(target) {
    this.forwards.delete(toString(target))
  }

  destroy() {
    this.forwards.destroy()
  }

  async peerHandshake(target, { noise, peerAddress, relayAddress, socket, session }, to) {
    const dht = this.dht

    const requestValue = c.encode(handshake, {
      mode: FROM_CLIENT,
      noise,
      peerAddress,
      relayAddress
    })

    const res = await dht.request(
      { command: COMMANDS.PEER_HANDSHAKE, target, value: requestValue },
      to,
      { socket, session }
    )

    const hs = decode(handshake, res.value)
    if (
      !hs ||
      hs.mode !== REPLY ||
      to.host !== res.from.host ||
      to.port !== res.from.port ||
      !hs.noise
    ) {
      throw BAD_HANDSHAKE_REPLY()
    }

    return {
      noise: hs.noise,
      relayed: !!hs.peerAddress,
      serverAddress: hs.peerAddress || to,
      clientAddress: res.to
    }
  }

  async onpeerhandshake(req) {
    const hs = req.value && decode(handshake, req.value)
    if (!hs) return

    const { mode, noise, peerAddress, relayAddress } = hs

    const state = req.target && this.get(req.target)
    const isServer = !!(state && state.onpeerhandshake)
    const relay = state && state.relay

    if (isServer) {
      let reply = null
      try {
        reply = noise && (await state.onpeerhandshake({ noise, peerAddress }, req))
      } catch (e) {
        safetyCatch(e)
        return
      }
      if (!reply || !reply.noise) return
      const opts = { socket: reply.socket, closerNodes: false, token: false }

      switch (mode) {
        case FROM_CLIENT: {
          req.reply(
            c.encode(handshake, { mode: REPLY, noise: reply.noise, peerAddress: null }),
            opts
          )
          return
        }
        case FROM_RELAY: {
          req.relay(
            c.encode(handshake, { mode: FROM_SERVER, noise: reply.noise, peerAddress }),
            req.from,
            opts
          )
          return
        }
        case FROM_SECOND_RELAY: {
          if (!relayAddress) return
          req.relay(
            c.encode(handshake, { mode: FROM_SERVER, noise: reply.noise, peerAddress }),
            relayAddress,
            opts
          )
          return // eslint-disable-line
        }
      }
    } else {
      switch (mode) {
        case FROM_CLIENT: {
          // TODO: if no relay is known route closer to the target instead of timing out
          if (!noise) return
          if (!relay && !relayAddress) {
            // help the user route
            req.reply(null, { token: false, closerNodes: true })
            return
          }
          req.relay(
            c.encode(handshake, {
              mode: FROM_RELAY,
              noise,
              peerAddress: req.from,
              relayAddress: null
            }),
            relayAddress || relay
          )
          return
        }
        case FROM_RELAY: {
          if (!relay || !noise) return
          req.relay(
            c.encode(handshake, {
              mode: FROM_SECOND_RELAY,
              noise,
              peerAddress,
              relayAddress: req.from
            }),
            relay
          )
          return
        }
        case FROM_SERVER: {
          if (!peerAddress || !noise) return
          req.reply(
            c.encode(handshake, { mode: REPLY, noise, peerAddress: req.from, relayAddress: null }),
            { to: peerAddress, closerNodes: false, token: false }
          )
          return // eslint-disable-line
        }
      }
    }
  }

  async peerHolepunch(target, { id, payload, peerAddress, socket, session }, to) {
    const dht = this.dht
    const requestValue = c.encode(holepunch, {
      mode: FROM_CLIENT,
      id,
      payload,
      peerAddress
    })

    const res = await dht.request(
      { command: COMMANDS.PEER_HOLEPUNCH, target, value: requestValue },
      to,
      { socket, session }
    )

    const hp = decode(holepunch, res.value)
    if (!hp || hp.mode !== REPLY || to.host !== res.from.host || to.port !== res.from.port) {
      throw BAD_HOLEPUNCH_REPLY()
    }

    return {
      from: res.from,
      to: res.to,
      payload: hp.payload,
      peerAddress: hp.peerAddress || to
    }
  }

  async onpeerholepunch(req) {
    const hp = req.value && decode(holepunch, req.value)
    if (!hp) return

    const { mode, id, payload, peerAddress } = hp

    const state = req.target && this.get(req.target)
    const isServer = !!(state && state.onpeerholepunch)
    const relay = state && state.relay

    switch (mode) {
      case FROM_CLIENT: {
        if (!peerAddress && !relay) return
        req.relay(
          c.encode(holepunch, { mode: FROM_RELAY, id, payload, peerAddress: req.from }),
          peerAddress || relay
        )
        return
      }
      case FROM_RELAY: {
        if (!isServer || !peerAddress) return
        let reply = null
        try {
          reply = await state.onpeerholepunch({ id, payload, peerAddress }, req)
        } catch (e) {
          safetyCatch(e)
          return
        }
        if (!reply) return
        const opts = { socket: reply.socket, closerNodes: false, token: false }
        req.relay(
          c.encode(holepunch, { mode: FROM_SERVER, id: 0, payload: reply.payload, peerAddress }),
          req.from,
          opts
        )
        return
      }
      case FROM_SERVER: {
        req.reply(c.encode(holepunch, { mode: REPLY, id, payload, peerAddress: req.from }), {
          to: peerAddress,
          closerNodes: false,
          token: false
        })
        return // eslint-disable-line
      }
    }
  }
}

function decode(enc, val) {
  try {
    return c.decode(enc, val)
  } catch {
    return null
  }
}

function toString(t) {
  return typeof t === 'string' ? t : b4a.toString(t, 'hex')
}
const sodium = require('sodium-universal')
const b4a = require('b4a')
const { holepunchPayload } = require('./messages')

module.exports = class HolepunchPayload {
  constructor(holepunchSecret) {
    this._sharedSecret = holepunchSecret
    this._localSecret = b4a.allocUnsafe(32)

    sodium.randombytes_buf(this._localSecret)
  }

  decrypt(buffer) {
    const state = { start: 24, end: buffer.byteLength - 16, buffer }

    if (state.end <= state.start) return null

    const nonce = buffer.subarray(0, 24)
    const msg = state.buffer.subarray(state.start, state.end)
    const cipher = state.buffer.subarray(state.start)

    if (!sodium.crypto_secretbox_open_easy(msg, cipher, nonce, this._sharedSecret)) return null

    try {
      return holepunchPayload.decode(state)
    } catch {
      return null
    }
  }

  encrypt(payload) {
    const state = { start: 24, end: 24, buffer: null }
    holepunchPayload.preencode(state, payload)
    state.buffer = b4a.allocUnsafe(state.end + 16)

    const nonce = state.buffer.subarray(0, 24)
    const msg = state.buffer.subarray(state.start, state.end)
    const cipher = state.buffer.subarray(state.start)

    holepunchPayload.encode(state, payload)
    sodium.randombytes_buf(nonce)
    sodium.crypto_secretbox_easy(cipher, msg, nonce, this._sharedSecret)

    return state.buffer
  }

  token(addr) {
    const out = b4a.allocUnsafe(32)
    sodium.crypto_generichash(out, b4a.from(addr.host), this._localSecret)
    return out
  }
}
const DONE = Promise.resolve(true)
const DESTROYED = Promise.resolve(false)

module.exports = class Semaphore {
  constructor(limit = 1) {
    this.limit = limit
    this.active = 0
    this.waiting = []

    this.flushedPromise = null
    this.flushedResolve = null

    this.destroyed = false

    this._onwait = this._queueWaiting.bind(this)
    this._onflush = this._queueFlushed.bind(this)
  }

  _queueWaiting(resolve) {
    this.waiting.push(resolve)
  }

  _queueFlushed(resolve) {
    this.flushedResolve = resolve
  }

  wait() {
    if (this.destroyed === true) return DESTROYED

    if (this.active < this.limit && this.waiting.length === 0) {
      this.active++
      return DONE
    }

    return new Promise(this._onwait)
  }

  signal() {
    if (this.destroyed === true) return

    this.active--
    while (this.active < this.limit && this.waiting.length > 0 && this.destroyed === false) {
      this.active++
      this.waiting.shift()(true)
    }

    if (this.active === 0 && this.flushedResolve) {
      const resolve = this.flushedResolve
      this.flushedResolve = null
      this.flushedPromise = null
      resolve(true)
    }
  }

  async flush() {
    if (this.destroyed === true) return
    if (this.active === 0) return
    if (this.flushedPromise) return this.flushedPromise
    this.flushedPromise = new Promise(this._onflush)
    return this.flushedPromise
  }

  destroy() {
    this.destroyed = true
    this.active = 0
    while (this.waiting.length) this.waiting.pop()(false)
    if (this.flushedResolve) this.flushedResolve(false)
  }
}
const { EventEmitter } = require('events')
const safetyCatch = require('safety-catch')
const NoiseSecretStream = require('@hyperswarm/secret-stream')
const b4a = require('b4a')
const relay = require('blind-relay')
const NoiseWrap = require('./noise-wrap')
const Announcer = require('./announcer')
const { FIREWALL, ERROR } = require('./constants')
const { unslabbedHash } = require('./crypto')
const SecurePayload = require('./secure-payload')
const Holepuncher = require('./holepuncher')
const {
  clearRelayTimeout,
  confirmDirectUpgrade,
  destroyRelayConnection
} = require('./relay-connection')
const { isPrivate } = require('bogon')
const { ALREADY_LISTENING, NODE_DESTROYED, KEYPAIR_ALREADY_USED } = require('./errors')

const HANDSHAKE_CLEAR_WAIT = 10000
const HANDSHAKE_INITIAL_TIMEOUT = 10000

module.exports = class Server extends EventEmitter {
  constructor(dht, opts = {}) {
    super()

    this.dht = dht
    this.target = null

    this.closed = false
    this.firewall = opts.firewall || (() => false)
    this.holepunch = opts.holepunch || (() => true)
    this.relayThrough = opts.relayThrough || null
    this.relayKeepAlive = opts.relayKeepAlive || 5000
    this.pool = opts.pool || null
    this.createHandshake = opts.createHandshake || defaultCreateHandshake
    this.createSecretStream = opts.createSecretStream || defaultCreateSecretStream
    this.suspended = false
    this.handshakeClearWait = opts.handshakeClearWait || HANDSHAKE_CLEAR_WAIT

    this._shareLocalAddress = opts.shareLocalAddress !== false
    this._reusableSocket = !!opts.reusableSocket
    this._neverPunch = opts.holepunch === false // useful for fully disabling punching
    this._keyPair = null
    this._announcer = null
    this._connects = new Map()
    this._holepunches = []
    this._listening = null
    this._closing = null
  }

  get listening() {
    return this._listening !== null
  }

  get publicKey() {
    return this._keyPair && this._keyPair.publicKey
  }

  get relayAddresses() {
    return this._announcer ? this._announcer.relayAddresses : []
  }

  onconnection(encryptedSocket) {
    this.emit('connection', encryptedSocket)
  }

  async suspend({ log = noop } = {}) {
    log('Suspending hyperdht server')
    if (this._listening !== null) await this._listening
    log('Suspending hyperdht server (post listening)')
    this.suspended = true
    this._clearAll()
    return this._announcer ? this._announcer.suspend({ log }) : Promise.resolve()
  }

  async resume() {
    if (this._listening !== null) await this._listening
    this.suspended = false
    return this._announcer ? this._announcer.resume() : Promise.resolve()
  }

  address() {
    if (!this._keyPair) return null

    return {
      publicKey: this._keyPair.publicKey,
      host: this.dht.host,
      port: this.dht.port
    }
  }

  close() {
    if (this._closing) return this._closing
    this._closing = this._close()
    return this._closing
  }

  _gc() {
    this.dht.listening.delete(this)
    if (this.target) this.dht._router.delete(this.target)
  }

  async _stopListening() {
    try {
      if (this._announcer) await this._announcer.stop()
    } catch {
      // ignore
    }

    this._announcer = null
    this._listening = null
    this._keyPair = null
  }

  async _close() {
    if (this._listening === null) {
      this.closed = true
      this.emit('close')
      return
    }

    try {
      await this._listening
    } catch (err) {
      safetyCatch(err)
    }

    this._gc()
    this._clearAll()

    await this._stopListening()

    this.closed = true
    this.emit('close')
  }

  _clearAll() {
    while (this._holepunches.length > 0) {
      const h = this._holepunches.pop()
      if (h && h.puncher) h.puncher.destroy()
      if (h && h.clearing) clearTimeout(h.clearing)
      if (h && h.prepunching) clearTimeout(h.prepunching)
      if (h && h.rawStream) h.rawStream.destroy()
    }

    this._connects.clear()
  }

  async listen(keyPair = this.dht.defaultKeyPair, opts = {}) {
    if (this._listening !== null) throw ALREADY_LISTENING()
    if (this.dht.destroyed) throw NODE_DESTROYED()

    this._listening = this._listen(keyPair, opts)
    await this._listening
    return this
  }

  async _listen(keyPair, opts) {
    // From now on, the DHT object which created me is responsible for closing me
    this.dht.listening.add(this)

    try {
      await this.dht.bind()
      if (this._closing) return

      for (const s of this.dht.listening) {
        if (s._keyPair && b4a.equals(s._keyPair.publicKey, keyPair.publicKey)) {
          throw KEYPAIR_ALREADY_USED()
        }
      }

      this.target = unslabbedHash(keyPair.publicKey)
      this._keyPair = keyPair
      this._announcer = new Announcer(this.dht, keyPair, this.target, opts)

      this.dht._router.set(this.target, {
        relay: null,
        record: this._announcer.record,
        onpeerhandshake: this._onpeerhandshake.bind(this),
        onpeerholepunch: this._onpeerholepunch.bind(this)
      })

      // warm it up for now
      this._localAddresses().catch(safetyCatch)

      await this._announcer.start()
    } catch (err) {
      await this._stopListening()
      this._gc()
      throw err
    }

    if (this._closing) return
    if (this.suspended) await this._announcer.suspend()

    if (this._closing) return
    if (this.dht.destroyed) throw NODE_DESTROYED()

    if (this.pool) this.pool._attachServer(this)

    this.emit('listening')
  }

  refresh() {
    if (this._announcer && !this.suspended) this._announcer.refresh()
  }

  notifyOnline() {
    if (this._announcer) this._announcer.online.notify()
  }

  _localAddresses() {
    return this.dht.validateLocalAddresses(Holepuncher.localAddresses(this.dht.io.serverSocket))
  }

  async _addHandshake(k, noise, clientAddress, { to: serverAddress, socket }, direct) {
    let id = this._holepunches.indexOf(null)
    if (id === -1) id = this._holepunches.push(null) - 1

    const hs = {
      round: 0,
      reply: null,
      puncher: null,
      payload: null,
      rawStream: null,
      encryptedSocket: null,
      prepunching: null,
      firewalled: true,
      clearing: null,
      replyFromPuncher: false,
      onsocket: null,
      aborted: false,

      // Relay state
      relayTimeout: null,
      relayToken: null,
      relaySocket: null,
      relayClient: null,
      relayPaired: false,
      validUpgrade: true
    }

    this._holepunches[id] = hs

    const handshake = this.createHandshake(this._keyPair, null)

    let remotePayload
    try {
      remotePayload = await handshake.recv(noise)
    } catch (err) {
      safetyCatch(err)
      this._clearLater(hs, id, k)
      return null
    }

    if (this._closing || this.suspended) {
      this._clearLater(hs, id, k)
      return null
    }

    try {
      hs.firewalled = await this.firewall(handshake.remotePublicKey, remotePayload, clientAddress)
    } catch (err) {
      safetyCatch(err)
    }

    if (this._closing || this.suspended || hs.firewalled) {
      this._clearLater(hs, id, k)
      return null
    }

    const error =
      remotePayload.version === 1
        ? remotePayload.udx
          ? ERROR.NONE
          : ERROR.ABORTED
        : ERROR.VERSION_MISMATCH

    const addresses = []
    const ourRemoteAddr = this.dht.remoteAddress()
    const ourLocalAddrs = this._shareLocalAddress ? await this._localAddresses() : null

    if (this._closing || this.suspended) {
      this._clearLater(hs, id, k)
      return null
    }

    if (ourRemoteAddr) addresses.push(ourRemoteAddr)
    if (ourLocalAddrs) addresses.push(...ourLocalAddrs)

    if (error === ERROR.NONE) {
      hs.rawStream = this.dht.createRawStream({
        framed: true,
        firewall(socket, port, host) {
          if (!(port > 0 && port < 65536)) return true

          // Check if the traffic originated from the socket on which we're expecting relay traffic. If so,
          // we haven't hole punched yet and the other side is just sending us traffic through the relay.
          if (hs.relaySocket && isRelay(hs.relaySocket, socket, port, host)) {
            hs.validUpgrade = false
            return false
          }

          hs.onsocket(socket, port, host)
          hs.validUpgrade = true
          return false
        }
      })

      hs.rawStream.on('error', autoDestroy)

      // Handles the case where onsocket is never called, but the stream got setup
      // This can happen on a relayed connection which never connects directly
      // (onsocket is called there only when the direct connection is established)
      const onrawstreamclose = () => {
        if (this._closing) return
        if (hs.puncher) {
          hs.puncher.onabort = noop
          hs.puncher.destroy()
        }
        this._clearLater(hs, id, k)
      }
      hs.rawStream.on('close', onrawstreamclose)

      hs.onsocket = (socket, port, host) => {
        if (hs.rawStream === null) return // Already hole punched

        this._clearLater(hs, id, k)

        if (hs.prepunching) {
          clearTimeout(hs.prepunching)
          hs.prepunching = null
        }

        if (this._reusableSocket && remotePayload.udx.reusableSocket) {
          this.dht._socketPool.routes.add(handshake.remotePublicKey, hs.rawStream)
        }

        hs.rawStream.removeListener('error', autoDestroy)
        hs.rawStream.removeListener('close', onrawstreamclose)

        if (hs.rawStream.connected) {
          const rawStream = hs.rawStream
          const remoteChanging = rawStream.changeRemote(socket, remotePayload.udx.id, port, host)
          confirmDirectUpgrade(hs, rawStream, remoteChanging)
        } else {
          hs.rawStream.connect(socket, remotePayload.udx.id, port, host)
          hs.encryptedSocket = this.createSecretStream(false, hs.rawStream, {
            handshake: h,
            keepAlive: this.dht.connectionKeepAlive
          })

          this.onconnection(hs.encryptedSocket)
        }

        if (hs.puncher) {
          hs.puncher.onabort = noop
          hs.puncher.destroy()
        }

        hs.rawStream = null
      }

      function autoDestroy() {
        if (hs.puncher) hs.puncher.destroy()
      }
    }

    const relayAddresses = this.relayAddresses
    const relayThrough = selectRelay(this.relayThrough)

    if (relayThrough) hs.relayToken = relay.token()

    try {
      hs.reply = await handshake.send({
        error,
        firewall: ourRemoteAddr ? FIREWALL.OPEN : FIREWALL.UNKNOWN,
        holepunch: ourRemoteAddr ? null : { id, relays: this._announcer.relays },
        addresses4: addresses,
        addresses6: null,
        udx: {
          reusableSocket: this._reusableSocket,
          id: hs.rawStream ? hs.rawStream.id : 0,
          seq: 0
        },
        secretStream: {},
        relayThrough: relayThrough ? { publicKey: relayThrough, token: hs.relayToken } : null,
        relayAddresses: relayAddresses.length ? relayAddresses : null
      })
    } catch (err) {
      safetyCatch(err)
      if (hs.rawStream) hs.rawStream.destroy()
      this._clearLater(hs, id, k)
      return null
    }

    if (this._closing || this.suspended) {
      if (hs.rawStream) hs.rawStream.destroy()
      this._clearLater(hs, id, k)
      return null
    }

    const h = handshake.final()

    if (error !== ERROR.NONE) {
      if (hs.rawStream) hs.rawStream.destroy()
      this._clearLater(hs, id, k)
      return hs
    }

    if (remotePayload.firewall === FIREWALL.OPEN || direct) {
      const sock = direct ? socket : this.dht.socket
      this.dht.stats.punches.open++
      hs.onsocket(sock, clientAddress.port, clientAddress.host)
      return hs
    }

    if (relayThrough || remotePayload.relayThrough) {
      this._relayConnection(hs, relayThrough, remotePayload, h)
    }

    const onabort = () => {
      hs.aborted = true
      if (hs.prepunching) clearTimeout(hs.prepunching)
      hs.prepunching = null
      if (hs.puncher) {
        hs.puncher.onabort = noop
        hs.puncher.destroy()
      }
      if (hs.rawStream.destroyed) {
        this._clearLater(hs, id, k)
        return
      }

      hs.rawStream.on('close', () => this._clearLater(hs, id, k))
      if (hs.relayToken === null) hs.rawStream.destroy()
    }

    if (!direct && clientAddress.host === serverAddress.host) {
      const clientAddresses = remotePayload.addresses4.filter((addr) => isPrivate(addr.host))

      if (clientAddresses.length > 0 && this._shareLocalAddress) {
        const myAddresses = await this._localAddresses()
        const addr = Holepuncher.matchAddress(myAddresses, clientAddresses)

        if (addr) {
          hs.payload = new SecurePayload(h.holepunchSecret)
          setupHolepuncher(this.dht, hs, remotePayload.firewall, onabort)
          hs.prepunching = setTimeout(onabort, HANDSHAKE_INITIAL_TIMEOUT)
          return hs
        }
      }
    }

    if (this._closing || this.suspended) {
      this._clearLater(hs, id, k)
      return null
    }

    if (ourRemoteAddr || this._neverPunch) {
      hs.prepunching = setTimeout(onabort, HANDSHAKE_INITIAL_TIMEOUT)
      return hs
    }

    hs.payload = new SecurePayload(h.holepunchSecret)
    setupHolepuncher(this.dht, hs, remotePayload.firewall, onabort)
    hs.replyFromPuncher = true
    hs.prepunching = setTimeout(hs.puncher.destroy.bind(hs.puncher), HANDSHAKE_INITIAL_TIMEOUT)

    return hs
  }

  _clearLater(hs, id, k) {
    if (hs.clearing) return
    hs.clearing = setTimeout(() => this._clear(hs, id, k), this.handshakeClearWait)
    hs.clearing.unref()
  }

  _clear(hs, id, k) {
    if (id >= this._holepunches.length || this._holepunches[id] !== hs) return
    if (hs.clearing) clearTimeout(hs.clearing)

    this._holepunches[id] = null
    while (
      this._holepunches.length > 0 &&
      this._holepunches[this._holepunches.length - 1] === null
    ) {
      this._holepunches.pop()
    }
    this._connects.delete(k)
  }

  async _onpeerhandshake({ noise, peerAddress }, req) {
    const k = b4a.toString(noise, 'hex')

    // The next couple of statements MUST run within the same tick to prevent
    // a malicious peer from flooding us with handshakes.
    let p = this._connects.get(k)
    if (!p) {
      p = this._addHandshake(k, noise, peerAddress || req.from, req, !peerAddress)
      this._connects.set(k, p)
    }

    const h = await p
    if (!h) return null

    if (this._closing !== null || this.suspended) return null

    // Routed handshake replies advertise the socket they are sent from as the
    // server address. Same-LAN prepunch also creates a puncher, but it must
    // keep advertising the normal server socket so the LAN shortcut can open.
    return { socket: h.replyFromPuncher ? h.puncher.socket : null, noise: h.reply }
  }

  async _onpeerholepunch({ id, peerAddress, payload }, req) {
    const h = id < this._holepunches.length ? this._holepunches[id] : null
    if (!h) return null

    if (!peerAddress || this._closing !== null || this.suspended) return null
    if (!h.payload) return this._abort(h) // not opened

    const remotePayload = h.payload.decrypt(payload)
    if (!remotePayload) return null

    const p = h.puncher
    if (!p || !p.socket) return this._abort(h) // not opened

    const isServerRelay = this._announcer.isRelay(req.from)
    const { error, firewall, round, punching, addresses, remoteAddress, remoteToken } =
      remotePayload

    if (error !== ERROR.NONE) {
      // We actually do not need to set the round here, but just do it for consistency.
      if (round >= h.round) h.round = round
      return this._abort(h)
    }

    const token = h.payload.token(peerAddress)
    const echoed = isServerRelay && !!remoteToken && b4a.equals(token, remoteToken)

    // Update our heuristics here
    if (req.socket === p.socket) {
      p.nat.add(req.to, req.from)
    }

    if (round >= h.round) {
      h.round = round
      p.updateRemote({ punching, firewall, addresses, verified: echoed ? peerAddress.host : null })
    }

    // Wait for the analyzer to reach a conclusion...
    let stable = await p.analyze(false)
    if (p.destroyed) return null

    if (!p.remoteHolepunching && !stable) {
      stable = await p.analyze(true)
      if (p.destroyed) return null
      if (!stable) return this._abort(h)
    }

    // Fast mode! If we are consistent and the remote has opened a session to us (remoteAddress)
    // then fire a quick punch back. Note the await here just waits for the udp socket to flush.
    if (
      isConsistent(p.nat.firewall) &&
      remoteAddress &&
      hasSameAddr(p.nat.addresses, remoteAddress)
    ) {
      await p.ping(peerAddress)
      if (p.destroyed) return null
    }

    // Remote said they are punching (or willing to), so we will punch as well.
    // Note that this returns when the punching has STARTED, so no guarantee
    // we will have a connection after this promise etc.
    if (p.remoteHolepunching) {
      // TODO: still continue here if a local connection might work, but then do not holepunch...
      if (!this.holepunch(p.remoteFirewall, p.nat.firewall, p.remoteAddresses, p.nat.addresses)) {
        return p.destroyed ? null : this._abort(h)
      }

      if (h.prepunching) {
        clearTimeout(h.prepunching)
        h.prepunching = null
      }

      if (p.remoteFirewall >= FIREWALL.RANDOM || p.nat.firewall >= FIREWALL.RANDOM) {
        if (
          this.dht._randomPunches >= this.dht._randomPunchLimit ||
          Date.now() - this.dht._lastRandomPunch < this.dht._randomPunchInterval
        ) {
          this.dht.stats.punches.tryLater++
          if (!h.relayToken) return this._abort(h, ERROR.TRY_LATER)
          if (!h.tryLaterCounted) {
            h.tryLaterCounted = true
            this.dht.stats.punches.tryLaterRelayedHandshakesAtLeastOnce++
          }
          return {
            socket: p.socket,
            payload: h.payload.encrypt({
              error: ERROR.TRY_LATER,
              firewall: p.nat.firewall,
              round: h.round,
              connected: p.connected,
              punching: p.punching,
              addresses: p.nat.addresses,
              remoteAddress: null,
              token: isServerRelay ? token : null,
              remoteToken: remotePayload.token
            })
          }
        }
      }

      const punching = await p.punch()
      if (p.destroyed) return null
      if (!punching) return this._abort(h)
    }

    // Freeze that analysis as soon as we have a result we are giving to the other peer
    if (p.nat.firewall !== FIREWALL.UNKNOWN) {
      p.nat.freeze()
    }

    return {
      socket: p.socket,
      payload: h.payload.encrypt({
        error: ERROR.NONE,
        firewall: p.nat.firewall,
        round: h.round,
        connected: p.connected,
        punching: p.punching,
        addresses: p.nat.addresses,
        remoteAddress: null,
        token: isServerRelay ? token : null,
        remoteToken: remotePayload.token
      })
    }
  }

  _abort(h, error = ERROR.ABORTED) {
    if (!h.payload) {
      if (h.puncher) h.puncher.destroy()
      return null
    }

    const payload = h.payload.encrypt({
      error,
      firewall: FIREWALL.UNKNOWN,
      round: h.round,
      connected: false,
      punching: false,
      addresses: null,
      remoteAddress: null,
      token: null,
      remoteToken: null
    })

    if (h.puncher) h.puncher.destroy()

    return { socket: this.dht.socket, payload }
  }

  _relayConnection(hs, relayThrough, remotePayload, h) {
    this.dht.stats.relaying.attempts++

    let isInitiator
    let publicKey
    let token

    if (relayThrough) {
      isInitiator = true
      publicKey = relayThrough
      token = hs.relayToken
    } else {
      isInitiator = false
      publicKey = remotePayload.relayThrough.publicKey
      token = remotePayload.relayThrough.token
    }

    hs.relayToken = token
    hs.relaySocket = this.dht.connect(publicKey)
    hs.relaySocket.setKeepAlive(this.relayKeepAlive)
    hs.relayClient = relay.Client.from(hs.relaySocket, { id: hs.relaySocket.publicKey })
    hs.relayTimeout = setTimeout(onabort, 15000)

    hs.relayClient
      .pair(isInitiator, token, hs.rawStream)
      .on('error', onabort)
      .on('data', (remoteId) => {
        if (hs.relayTimeout) clearRelayTimeout(hs)
        if (hs.rawStream === null) {
          onabort(null)
          return
        }

        hs.relayPaired = true
        this.dht.stats.relaying.successes++

        if (hs.prepunching) clearTimeout(hs.prepunching)
        hs.prepunching = null

        const { remotePort, remoteHost, socket } = hs.relaySocket.rawStream

        hs.rawStream
          .on('close', () => destroyRelayConnection(hs))
          .connect(socket, remoteId, remotePort, remoteHost)

        hs.encryptedSocket = this.createSecretStream(false, hs.rawStream, {
          handshake: h,
          keepAlive: this.dht.connectionKeepAlive
        })

        this.onconnection(hs.encryptedSocket)
      })

    const dht = this.dht
    function onabort() {
      if (!hs.relayPaired) dht.stats.relaying.aborts++
      destroyRelayConnection(hs)
      if (hs.aborted && hs.rawStream) hs.rawStream.destroy()
    }
  }
}

function isConsistent(fw) {
  return fw === FIREWALL.OPEN || fw === FIREWALL.CONSISTENT
}

function hasSameAddr(addrs, other) {
  if (addrs === null) return false

  for (const addr of addrs) {
    if (addr.port === other.port && addr.host === other.host) return true
  }
  return false
}

function defaultCreateHandshake(keyPair, remotePublicKey) {
  return new NoiseWrap(keyPair, remotePublicKey)
}

function defaultCreateSecretStream(isInitiator, rawStream, opts) {
  return new NoiseSecretStream(isInitiator, rawStream, opts)
}

function setupHolepuncher(dht, hs, remoteFirewall, onabort) {
  if (hs.puncher) return

  hs.puncher = new Holepuncher(dht, dht.session(), false, remoteFirewall)
  hs.puncher.onconnect = hs.onsocket
  hs.puncher.onabort = onabort
}

function isRelay(relaySocket, socket, port, host) {
  const stream = relaySocket.rawStream
  if (!stream) return false
  if (stream.socket !== socket) return false
  return port === stream.remotePort && host === stream.remoteHost
}

function selectRelay(relayThrough) {
  if (typeof relayThrough === 'function') relayThrough = relayThrough()
  if (relayThrough === null) return null
  if (Array.isArray(relayThrough)) {
    return relayThrough[Math.floor(Math.random() * relayThrough.length)]
  }
  return relayThrough
}

function noop() {}
module.exports = class Sleeper {
  constructor() {
    this._timeout = null
    this._resolve = null

    this._start = (resolve) => {
      this._resolve = resolve
    }

    this._trigger = () => {
      if (this._resolve === null) return
      const resolve = this._resolve
      this._timeout = null
      this._resolve = null
      resolve()
    }
  }

  pause(ms) {
    const p = new Promise(this._start)
    if (this._timeout !== null) {
      clearTimeout(this._timeout)
      this._trigger()
    }
    this._timeout = setTimeout(this._trigger, ms)
    return p
  }

  resume() {
    if (this._timeout !== null) {
      clearTimeout(this._timeout)
      this._trigger()
    }
  }
}
const b4a = require('b4a')

const LINGER_TIME = 3000

module.exports = class SocketPool {
  constructor(dht, host) {
    this._dht = dht
    this._sockets = new Map()
    this._lingering = new Set() // updated by the ref
    this._host = host

    this.routes = new SocketRoutes(this)

    this.stats = {
      socketsAdded: 0,
      socketsRemoved: 0
    }
  }

  _onmessage(ref, data, address) {
    this._dht.onmessage(ref.socket, data, address)
  }

  _add(ref) {
    this._sockets.set(ref.socket, ref)
    this.stats.socketsAdded++
  }

  _remove(ref) {
    const removed = this._sockets.delete(ref.socket)
    this._lingering.delete(ref)
    if (removed) this.stats.socketsRemoved++
  }

  lookup(socket) {
    return this._sockets.get(socket) || null
  }

  setReusable(socket, bool) {
    const ref = this.lookup(socket)
    if (ref) ref.reusable = bool
  }

  acquire() {
    // TODO: Enable socket reuse
    return new SocketRef(this)
  }

  async destroy() {
    const closing = []

    for (const ref of this._sockets.values()) {
      ref._unlinger()
      closing.push(ref.socket.close())
    }

    await Promise.allSettled(closing)
  }
}

class SocketRoutes {
  constructor(pool) {
    this._pool = pool
    this._routes = new Map()
  }

  add(publicKey, rawStream) {
    if (rawStream.socket) this._onconnect(publicKey, rawStream)
    else rawStream.on('connect', this._onconnect.bind(this, publicKey, rawStream))
  }

  get(publicKey) {
    const id = b4a.toString(publicKey, 'hex')
    const route = this._routes.get(id)
    if (!route) return null
    return route
  }

  _onconnect(publicKey, rawStream) {
    const id = b4a.toString(publicKey, 'hex')
    const socket = rawStream.socket

    let route = this._routes.get(id)

    if (!route) {
      const gc = () => {
        if (this._routes.get(id) === route) this._routes.delete(id)
        socket.removeListener('close', gc)
      }

      route = {
        socket,
        address: { host: rawStream.remoteHost, port: rawStream.remotePort },
        gc
      }

      this._routes.set(id, route)
      socket.on('close', gc)
    }

    this._pool.setReusable(socket, true)

    rawStream.on('error', () => {
      this._pool.setReusable(socket, false)
      if (!route) route = this._routes.get(id)
      if (route && route.socket === socket) route.gc()
    })
  }
}

// TODO: we should just make some "user data" object on udx to allow to attach this info
class SocketRef {
  constructor(pool) {
    this._pool = pool

    // Events
    this.onholepunchmessage = noop

    // Whether it should teardown immediately or wait a bit
    this.reusable = false

    this.socket = pool._dht.udx.createSocket()
    this.socket
      .on('close', this._onclose.bind(this))
      .on('message', this._onmessage.bind(this))
      .on('idle', this._onidle.bind(this))
      .on('busy', this._onbusy.bind(this))
      .bind(0, this._pool._host)

    this._refs = 1
    this._released = false
    this._closed = false

    this._timeout = null
    this._wasBusy = false

    this._pool._add(this)
  }

  _onclose() {
    this._pool._remove(this)
  }

  _onmessage(data, address) {
    if (data.byteLength > 1) {
      this._pool._onmessage(this, data, address)
    } else {
      this.onholepunchmessage(data, address, this)
    }
  }

  _onidle() {
    this._closeMaybe()
  }

  _onbusy() {
    this._wasBusy = true
    this._unlinger()
  }

  _reset() {
    this.onholepunchmessage = noop
  }

  _closeMaybe() {
    if (this._refs === 0 && this.socket.idle && !this._timeout) this._close()
  }

  _lingeringClose() {
    this._pool._lingering.delete(this)
    this._timeout = null
    this._closeMaybe()
  }

  _close() {
    this._unlinger()

    if (this.reusable && this._wasBusy) {
      this._wasBusy = false
      this._pool._lingering.add(this)
      this._timeout = setTimeout(this._lingeringClose.bind(this), LINGER_TIME)
      return
    }

    this._closed = true
    this.socket.close()
  }

  _unlinger() {
    if (this._timeout !== null) {
      clearTimeout(this._timeout)
      this._pool._lingering.delete(this)
      this._timeout = null
    }
  }

  get free() {
    return this._refs === 0
  }

  active() {
    this._refs++
    this._unlinger()
  }

  inactive() {
    this._refs--
    this._closeMaybe()
  }

  address() {
    return this.socket.address()
  }

  release() {
    if (this._released) return

    this._released = true
    this._reset()

    this._refs--
    this._closeMaybe()
  }
}

function noop() {}
{
  "name": "hyperdht",
  "version": "6.34.0",
  "description": "The DHT powering Hyperswarm",
  "main": "index.js",
  "browser": "browser.js",
  "bin": {
    "hyperdht": "./bin.js"
  },
  "files": [
    "index.js",
    "browser.js",
    "testnet.js",
    "bin.js",
    "lib/**.js"
  ],
  "imports": {
    "events": {
      "bare": "bare-events",
      "default": "events"
    },
    "child_process": {
      "bare": "bare-node-child-process",
      "default": "child_process"
    }
  },
  "dependencies": {
    "@hyperswarm/secret-stream": "^6.6.2",
    "b4a": "^1.3.1",
    "bare-events": "^2.2.0",
    "blind-relay": "^1.3.0",
    "bogon": "^1.0.0",
    "compact-encoding": "^3.0.0",
    "dht-rpc": "^6.15.1",
    "hypercore-crypto": "^3.3.0",
    "hypercore-id-encoding": "^1.2.0",
    "hyperdht-address": "^1.0.1",
    "noise-curve-ed": "^2.0.0",
    "noise-handshake": "^4.0.0",
    "record-cache": "^1.1.1",
    "safety-catch": "^1.0.1",
    "signal-promise": "^1.0.3",
    "sodium-universal": "^5.0.1",
    "streamx": "^2.16.1",
    "unslab": "^1.3.0",
    "xache": "^1.1.0"
  },
  "devDependencies": {
    "bare-node-child-process": "^1.0.1",
    "brittle": "^3.0.0",
    "graceful-goodbye": "^1.3.0",
    "newline-decoder": "^1.0.2",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^2.0.0"
  },
  "scripts": {
    "format": "prettier --write .",
    "test": "prettier --check . && node test/all.js",
    "test:bare": "bare test/all.js",
    "test:generate": "brittle -r test/all.js test/*.js",
    "integration": "brittle test/integration/*.js",
    "end-to-end": "brittle test/end-to-end/*.js"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "directories": {
    "lib": "lib",
    "test": "test"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/hyperdht.git"
  },
  "keywords": [],
  "bugs": {
    "url": "https://github.com/holepunchto/hyperdht/issues"
  },
  "homepage": "https://github.com/holepunchto/hyperdht#readme"
}
{
  "name": "hyperschema",
  "version": "1.23.0",
  "description": "Create registries of declarative compact-encoding schemas",
  "files": [
    "lib/*.js",
    "builder.mjs",
    "builder.cjs",
    "builder.d.ts",
    "runtime.mjs",
    "runtime.cjs",
    "primitives.mjs",
    "primitives.cjs"
  ],
  "exports": {
    "./package": "./package.json",
    ".": {
      "types": "./builder.d.ts",
      "import": "./builder.mjs",
      "default": "./builder.cjs"
    },
    "./runtime": {
      "import": "./runtime.mjs",
      "default": "./runtime.cjs"
    },
    "./primitives": {
      "import": "./primitives.mjs",
      "default": "./primitives.cjs"
    }
  },
  "imports": {
    "fs": {
      "bare": "bare-fs",
      "default": "fs"
    },
    "path": {
      "bare": "bare-path",
      "default": "path"
    }
  },
  "scripts": {
    "format": "prettier . --write",
    "test": "prettier . --check && brittle test/index.js",
    "test:bare": "prettier . --check && bare test/index.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/hyperschema.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/hyperschema/issues"
  },
  "homepage": "https://github.com/holepunchto/hyperschema#readme",
  "dependencies": {
    "bare-fs": "^4.0.1",
    "compact-encoding": "^3.0.0",
    "generate-object-property": "^2.0.0",
    "generate-string": "^1.0.1"
  },
  "devDependencies": {
    "brittle": "^3.7.0",
    "hyperschema-test": "^1.1.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^2.0.0",
    "test-tmp": "^1.3.0"
  }
}
module.exports = {
  c: require('compact-encoding')
}
const { EventEmitter } = require('events')
const { getStreamError } = require('streamx')
const DHT = require('hyperdht')
const spq = require('shuffled-priority-queue')
const b4a = require('b4a')
const unslab = require('unslab')

const PeerInfo = require('./lib/peer-info')
const RetryTimer = require('./lib/retry-timer')
const ConnectionSet = require('./lib/connection-set')
const PeerDiscovery = require('./lib/peer-discovery')

const MAX_PEERS = 64
const MAX_PARALLEL = 3
const MAX_CLIENT_CONNECTIONS = Infinity // TODO: Change
const MAX_SERVER_CONNECTIONS = Infinity

const ERR_MISSING_TOPIC = 'Topic is required and must be a 32-byte buffer'
const ERR_DESTROYED = 'Swarm has been destroyed'
const ERR_DUPLICATE = 'Duplicate connection'
const ERR_FIREWALL = 'Peer is firewalled'

module.exports = class Hyperswarm extends EventEmitter {
  constructor(opts = {}) {
    super()
    const {
      seed,
      relayThrough,
      keyPair = DHT.keyPair(seed),
      maxPeers = MAX_PEERS,
      maxClientConnections = MAX_CLIENT_CONNECTIONS,
      maxServerConnections = MAX_SERVER_CONNECTIONS,
      maxParallel = MAX_PARALLEL,
      firewall = allowAll
    } = opts
    this.keyPair = keyPair

    this.dht =
      opts.dht ||
      new DHT({
        bootstrap: opts.bootstrap,
        nodes: opts.nodes,
        port: opts.port,
        deferRandomPunch: opts.deferRandomPunch,
        randomPunchInterval: opts.randomPunchInterval
      })
    this.server = this.dht.createServer(
      {
        firewall: this._handleFirewall.bind(this),
        relayThrough: this._maybeRelayConnection.bind(this),
        handshakeClearWait: opts.handshakeClearWait
      },
      this._handleServerConnection.bind(this)
    )

    this.destroyed = false
    this.suspended = false
    this.maxPeers = maxPeers
    this.maxClientConnections = maxClientConnections
    this.maxServerConnections = maxServerConnections
    this.maxParallel = maxParallel
    this.relayThrough = relayThrough ? toRelayFunction(relayThrough) : null

    this.connecting = 0
    this.connections = new Set()
    this.peers = new Map()
    this.explicitPeers = new Set()
    this.listening = null
    this.stats = {
      updates: 0,
      connects: {
        client: {
          opened: 0,
          closed: 0,
          attempted: 0
        },
        server: {
          // Note: there is no notion of 'attempts' for server connections
          opened: 0,
          closed: 0
        }
      },
      bannedPeers: 0
    }

    this._discovery = new Map()
    this._timer = new RetryTimer(this._requeue.bind(this), {
      backoffs: opts.backoffs,
      jitter: opts.jitter
    })
    this._queue = spq()

    this._allConnections = new ConnectionSet()
    this._pendingFlushes = []
    this._flushTick = 0

    this._drainingQueue = false
    this._clientConnections = 0
    this._serverConnections = 0
    this._firewall = firewall

    this.dht.on('network-change', this._handleNetworkChange.bind(this))
    this.dht.on('network-update', this._handleNetworkUpdate.bind(this))
    this.on('update', this._handleUpdate)
  }

  _maybeRelayConnection(force) {
    if (!this.relayThrough) return null
    return this.relayThrough(force, this)
  }

  _enqueue(peerInfo) {
    if (peerInfo.queued) return
    peerInfo.queued = true
    peerInfo._flushTick = this._flushTick
    this._queue.add(peerInfo)

    this._attemptClientConnections()
  }

  _requeue(batch) {
    if (this.suspended) return
    for (const peerInfo of batch) {
      peerInfo.waiting = false

      if (
        peerInfo._updatePriority() === false ||
        this._allConnections.has(peerInfo.publicKey) ||
        peerInfo.queued
      ) {
        continue
      }
      peerInfo.queued = true
      peerInfo._flushTick = this._flushTick
      this._queue.add(peerInfo)
    }

    this._attemptClientConnections()
  }

  _flushMaybe(peerInfo) {
    for (let i = 0; i < this._pendingFlushes.length; i++) {
      const flush = this._pendingFlushes[i]
      if (peerInfo._flushTick > flush.tick) continue
      if (--flush.missing > 0) continue
      flush.onflush(true)
      this._pendingFlushes.splice(i--, 1)
    }
  }

  _withinMaxPeers() {
    const e = this.explicitPeers.size
    const factor = e === 0 ? 1 : e < 2 ? 2 : e < 4 ? 3 : 4
    return factor * this._allConnections.size < this.maxPeers
  }

  _flushAllMaybe() {
    if (
      this.connecting > 0 ||
      (this._withinMaxPeers() && this._clientConnections < this.maxClientConnections)
    ) {
      return false
    }

    while (this._pendingFlushes.length) {
      const flush = this._pendingFlushes.pop()
      flush.onflush(true)
    }

    return true
  }

  _shouldConnectExplicit() {
    return !this.destroyed && !this.suspended && this.connecting < this.maxParallel
  }

  _shouldConnect() {
    return (
      !this.destroyed &&
      !this.suspended &&
      this.connecting < this.maxParallel &&
      this._withinMaxPeers() &&
      this._clientConnections < this.maxClientConnections
    )
  }

  _shouldRequeue(peerInfo) {
    if (this.suspended) return false
    if (peerInfo.explicit) return true
    for (const topic of peerInfo.topics) {
      if (this._discovery.has(b4a.toString(topic, 'hex')) && !this.destroyed) {
        return true
      }
    }
    return false
  }

  _connect(peerInfo, queued) {
    if (peerInfo.banned || this._allConnections.has(peerInfo.publicKey)) {
      if (queued) this._flushMaybe(peerInfo)
      return
    }

    // TODO: Support async firewalling at some point.
    if (this._handleFirewall(peerInfo.publicKey, null)) {
      if (queued) this._flushMaybe(peerInfo)
      return
    }

    const relayThrough = this._maybeRelayConnection(peerInfo.forceRelaying)
    const conn = this.dht.connect(peerInfo.publicKey, {
      relayAddresses: peerInfo.relayAddresses,
      keyPair: this.keyPair,
      relayThrough
    })
    this._allConnections.add(conn)

    this.stats.connects.client.attempted++

    this.connecting++
    this._clientConnections++
    let opened = false

    const onerror = (err) => {
      if (this.relayThrough && shouldForceRelaying(err.code)) {
        peerInfo.forceRelaying = true
        // Reset the attempts in order to fast connect to relay
        peerInfo.attempts = 0
      }
    }

    // Removed once a connection is opened
    conn.on('error', onerror)

    conn.on('open', () => {
      opened = true
      this.stats.connects.client.opened++

      this._connectDone()
      this.connections.add(conn)
      conn.removeListener('error', onerror)
      peerInfo._connected()
      peerInfo.client = true
      this.emit('connection', conn, peerInfo)
      if (queued) this._flushMaybe(peerInfo)

      this.emit('update')
    })
    conn.on('close', () => {
      if (!opened) this._connectDone()
      this.stats.connects.client.closed++

      const err = getStreamError(conn)
      if (shouldBan(err)) {
        this._banPeer(peerInfo, true, err)
      }

      this.connections.delete(conn)
      this._allConnections.delete(conn)
      this._clientConnections--
      peerInfo._disconnected()

      peerInfo.waiting = this._shouldRequeue(peerInfo) && this._timer.add(peerInfo)
      this._maybeDeletePeer(peerInfo)

      if (!opened && queued) this._flushMaybe(peerInfo)

      this._attemptClientConnections()

      this.emit('update')
    })

    this.emit('update')
  }

  _connectDone() {
    this.connecting--

    if (this.connecting < this.maxParallel) this._attemptClientConnections()
    if (this.connecting === 0) this._flushAllMaybe()
  }

  // Called when the PeerQueue indicates a connection should be attempted.
  _attemptClientConnections() {
    // Guard against re-entries - unsure if it still needed but doesn't hurt
    if (this._drainingQueue || this.suspended) return
    this._drainingQueue = true

    for (const peerInfo of this.explicitPeers) {
      if (!this._shouldConnectExplicit()) break
      if (
        peerInfo.attempts >= 5 ||
        Date.now() - peerInfo.disconnectedTime < peerInfo.attempts * 1000
      ) {
        continue
      }
      this._connect(peerInfo, false)
    }

    while (this._queue.length && this._shouldConnect()) {
      const peerInfo = this._queue.shift()
      peerInfo.queued = false
      this._connect(peerInfo, true)
    }
    this._drainingQueue = false
    if (this.connecting === 0) this._flushAllMaybe()
  }

  _handleFirewall(remotePublicKey, payload) {
    if (b4a.equals(remotePublicKey, this.keyPair.publicKey)) return true

    let peerInfo = this.peers.get(b4a.toString(remotePublicKey, 'hex'))
    if (peerInfo && peerInfo.banned) return true

    const firewalled = this._firewall(remotePublicKey, payload)
    if (firewalled) {
      if (!peerInfo) peerInfo = this._upsertPeer(remotePublicKey)
      this._banPeer(peerInfo, true, new Error(ERR_FIREWALL))
    }

    return firewalled
  }

  _handleServerConnectionSwap(existing, conn) {
    let closed = false

    existing.on('close', () => {
      if (closed) return

      conn.removeListener('error', noop)
      conn.removeListener('close', onclose)

      this._handleServerConnection(conn)
    })

    conn.on('error', noop)
    conn.on('close', onclose)

    function onclose() {
      closed = true
    }
  }

  // Called when the DHT receives a new server connection.
  _handleServerConnection(conn) {
    if (this.destroyed || this.suspended) {
      // TODO: Investigate why a final server connection can be received after close
      conn.on('error', noop)
      return conn.destroy(ERR_DESTROYED)
    }

    const existing = this._allConnections.get(conn.remotePublicKey)

    if (existing) {
      // If both connections are from the same peer,
      // - pick the new one if the existing stream is already established (has sent and received bytes),
      //   because the other client must have lost that connection and be reconnecting
      // - otherwise, pick the one thats expected to initiate in a tie break
      const existingIsOutdated = existing.rawBytesRead > 0 && existing.rawBytesWritten > 0
      const expectedInitiator = b4a.compare(conn.publicKey, conn.remotePublicKey) > 0
      const keepNew = existingIsOutdated || expectedInitiator === conn.isInitiator

      if (keepNew === false) {
        existing.sendKeepAlive()
        conn.on('error', noop)
        conn.destroy(new Error(ERR_DUPLICATE))
        return
      }

      existing.on('error', noop)
      existing.destroy(new Error(ERR_DUPLICATE))
      this._handleServerConnectionSwap(existing, conn)
      return
    }

    // When reaching here, the connection will always be 'opened' next tick
    this.stats.connects.server.opened++

    const peerInfo = this._upsertPeer(conn.remotePublicKey, null)

    this.connections.add(conn)
    this._allConnections.add(conn)
    this._serverConnections++

    conn.on('close', () => {
      const err = getStreamError(conn)
      if (shouldBan(err)) {
        this._banPeer(peerInfo, true, err)
      }

      this.connections.delete(conn)
      this._allConnections.delete(conn)
      this._serverConnections--
      this.stats.connects.server.closed++

      this._maybeDeletePeer(peerInfo)

      this._attemptClientConnections()

      this.emit('update')
    })
    peerInfo.client = false
    this.emit('connection', conn, peerInfo)

    this.emit('update')
  }

  _upsertPeer(publicKey, relayAddresses) {
    if (b4a.equals(publicKey, this.keyPair.publicKey)) return null
    const keyString = b4a.toString(publicKey, 'hex')
    let peerInfo = this.peers.get(keyString)

    if (peerInfo) {
      peerInfo.relayAddresses = relayAddresses // new is always better
      return peerInfo
    }

    peerInfo = new PeerInfo({
      publicKey,
      relayAddresses
    })

    this.peers.set(keyString, peerInfo)
    return peerInfo
  }

  _handleUpdate() {
    this.stats.updates++
  }

  _maybeDeletePeer(peerInfo) {
    if (!peerInfo.shouldGC()) return

    const hasActiveConn = this._allConnections.has(peerInfo.publicKey)
    if (hasActiveConn) return

    const keyString = b4a.toString(peerInfo.publicKey, 'hex')
    this.peers.delete(keyString)
  }

  /*
   * Called when a peer is actively discovered during a lookup.
   *
   * Three conditions:
   *  1. Not a known peer -- insert into queue
   *  2. A known peer with normal priority -- do nothing
   *  3. A known peer with low priority -- bump priority, because it's been rediscovered
   */
  _handlePeer(peer, topic) {
    const peerInfo = this._upsertPeer(peer.publicKey, peer.relayAddresses)
    if (peerInfo) peerInfo._topic(topic)
    if (!peerInfo || this._allConnections.has(peer.publicKey)) return
    if (!peerInfo.prioritized || peerInfo.server) peerInfo._reset()
    if (peerInfo._updatePriority()) {
      this._enqueue(peerInfo)
    }
  }

  async _handleNetworkUpdate() {
    if (!this.online) return
    this._handleNetworkChange()
  }

  async _handleNetworkChange() {
    if (this.suspended) return

    // prioritize figuring out if existing connections are dead
    for (const conn of this._allConnections) {
      conn.sendKeepAlive()
    }

    const refreshes = []

    for (const discovery of this._discovery.values()) {
      refreshes.push(discovery.refresh())
    }

    await Promise.allSettled(refreshes)
  }

  _banPeer(peerInfo, banned, err) {
    peerInfo.ban(banned)
    this.stats.bannedPeers++
    this.emit('ban', peerInfo, err)
  }

  status(key) {
    return this._discovery.get(b4a.toString(key, 'hex')) || null
  }

  listen() {
    if (!this.listening) {
      if (this.destroyed) throw new Error('Swarm destroyed')
      this.listening = this.server.listen(this.keyPair)
    }
    return this.listening
  }

  // Object that exposes a cancellation method (destroy)
  // TODO: When you rejoin, it should reannounce + bump lookup priority
  join(topic, opts = {}) {
    if (this.destroyed) throw new Error('Swarm destroyed')
    if (!topic) throw new Error(ERR_MISSING_TOPIC)
    topic = unslab(topic)

    const topicString = b4a.toString(topic, 'hex')

    let discovery = this._discovery.get(topicString)

    if (discovery && !discovery.destroyed) {
      return discovery.session(opts)
    }

    discovery = new PeerDiscovery(this, topic, {
      limit: opts.limit,
      wait: discovery ? discovery.destroy() : null,
      suspended: this.suspended,
      onpeer: (peer) => this._handlePeer(peer, topic)
    })
    this._discovery.set(topicString, discovery)
    return discovery.session(opts)
  }

  // Returns a promise
  async leave(topic) {
    if (!topic) throw new Error(ERR_MISSING_TOPIC)
    const topicString = b4a.toString(topic, 'hex')
    if (!this._discovery.has(topicString)) return Promise.resolve()

    const discovery = this._discovery.get(topicString)

    try {
      await discovery.destroy()
    } catch {
      // ignore, prop network
    }

    if (this._discovery.get(topicString) === discovery) {
      this._discovery.delete(topicString)
    }
  }

  joinPeer(publicKey) {
    const peerInfo = this._upsertPeer(publicKey, null)
    if (!peerInfo) return
    if (!this.explicitPeers.has(peerInfo)) {
      peerInfo.explicit = true
      this.explicitPeers.add(peerInfo)
    }
    if (this._allConnections.has(publicKey)) return
    if (peerInfo._updatePriority()) {
      this._enqueue(peerInfo)
    }
  }

  leavePeer(publicKey) {
    const keyString = b4a.toString(publicKey, 'hex')
    if (!this.peers.has(keyString)) return

    const peerInfo = this.peers.get(keyString)
    peerInfo.explicit = false
    this.explicitPeers.delete(peerInfo)
    this._maybeDeletePeer(peerInfo)
  }

  // Returns a promise
  async flush() {
    const allFlushed = [...this._discovery.values()].map((v) => v.flushed())
    await Promise.all(allFlushed)
    if (this._flushAllMaybe()) return true
    const pendingSize = this._allConnections.size - this.connections.size
    if (!this._queue.length && !pendingSize) return true
    return new Promise((resolve) => {
      this._pendingFlushes.push({
        onflush: resolve,
        missing: this._queue.length + pendingSize,
        tick: this._flushTick++
      })
    })
  }

  async clear() {
    const cleared = Promise.allSettled([...this._discovery.values()].map((d) => d.destroy()))
    this._discovery.clear()
    return cleared
  }

  async destroy({ force } = {}) {
    if (this.destroyed && !force) return
    this.destroyed = true

    this._timer.destroy()

    if (!force) await this.clear()

    await this.server.close()

    while (this._pendingFlushes.length) {
      const flush = this._pendingFlushes.pop()
      flush.onflush(false)
    }

    await this.dht.destroy({ force })
  }

  async suspend({ log = noop } = {}) {
    if (this.suspended) return

    const promises = []

    promises.push(this.server.suspend({ log }))

    for (const discovery of this._discovery.values()) {
      promises.push(discovery.suspend({ log }))
    }

    const pending = []
    for (const connection of this._allConnections) {
      connection.destroy()
      pending.push(new Promise((resolve) => connection.on('close', resolve)))
    }

    this.suspended = true

    log('Suspending server and discovery... (' + promises.length + ')')
    await Promise.allSettled(promises)
    log('Done, suspending the dht...')
    await this.dht.suspend({ log })
    log('Done, swarm fully suspended')

    await Promise.all(pending)

    // reset queue
    this._timer.destroy()
    this._timer = new RetryTimer(this._requeue.bind(this), {
      backoffs: this._timer.backoffs,
      jitter: this._timer.jitter
    })
    this._queue = spq()
  }

  async resume({ log = noop } = {}) {
    if (!this.suspended) return

    log('Resuming the dht')
    await this.dht.resume()
    log('Done, resuming the server')
    await this.server.resume()
    log('Done, all discovery')

    for (const discovery of this._discovery.values()) {
      discovery.resume()
    }

    this.suspended = false
    this._attemptClientConnections()
  }

  topics() {
    return this._discovery.values()
  }
}

function noop() {}

function allowAll() {
  return false
}

function shouldForceRelaying(code) {
  return (
    code === 'HOLEPUNCH_ABORTED' ||
    code === 'HOLEPUNCH_DOUBLE_RANDOMIZED_NATS' ||
    code === 'REMOTE_NOT_HOLEPUNCHABLE'
  )
}

function shouldBan() {
  // return !!err && err.name === 'HypercoreError' && err.code === 'INVALID_OPERATION'
  return false
}

function toRelayFunction(relayThrough) {
  return typeof relayThrough === 'function'
    ? relayThrough
    : (force, swarm) => (force || swarm.dht.randomized ? relayThrough : null)
}
module.exports = class BulkTimer {
  constructor(time, fn) {
    this._time = time
    this._fn = fn
    this._interval = null
    this._next = []
    this._pending = []
    this._destroyed = false
  }

  destroy() {
    if (this._destroyed) return
    this._destroyed = true
    clearInterval(this._interval)
    this._interval = null
  }

  _ontick() {
    if (!this._next.length && !this._pending.length) return
    if (this._next.length) this._fn(this._next)
    this._next = this._pending
    this._pending = []
  }

  add(info) {
    if (this._destroyed) return
    if (!this._interval) {
      this._interval = setInterval(this._ontick.bind(this), Math.floor(this._time * 0.66))
    }

    this._pending.push(info)
  }
}
const b4a = require('b4a')

module.exports = class ConnectionSet {
  constructor() {
    this._byPublicKey = new Map()
  }

  [Symbol.iterator]() {
    return this._byPublicKey.values()
  }

  get size() {
    return this._byPublicKey.size
  }

  has(publicKey) {
    return this._byPublicKey.has(toHex(publicKey))
  }

  get(publicKey) {
    return this._byPublicKey.get(toHex(publicKey))
  }

  add(connection) {
    this._byPublicKey.set(b4a.toString(connection.remotePublicKey, 'hex'), connection)
  }

  delete(connection) {
    const keyString = b4a.toString(connection.remotePublicKey, 'hex')
    const existing = this._byPublicKey.get(keyString)
    if (existing !== connection) return
    this._byPublicKey.delete(keyString)
  }
}

function toHex(b) {
  return typeof b === 'string' ? b : b4a.toString(b, 'hex')
}
const safetyCatch = require('safety-catch')
const b4a = require('b4a')

const REFRESH_INTERVAL = 1000 * 60 * 10 // 10 min
const RANDOM_JITTER = 1000 * 60 * 2 // 2 min
const DELAY_GRACE_PERIOD = 1000 * 30 // 30s

const MAX_DISCOVERY_CACHE = 64

module.exports = class PeerDiscovery {
  constructor(
    swarm,
    topic,
    { limit = Infinity, wait = null, suspended = false, onpeer = noop, onerror = safetyCatch }
  ) {
    this.limit = limit
    this.swarm = swarm
    this.topic = topic
    this.isClient = false
    this.isServer = false
    this.destroyed = false
    this.destroying = null
    this.suspended = suspended

    this._sessions = []
    this._clientSessions = 0
    this._serverSessions = 0

    this._onpeer = onpeer
    this._onerror = onerror

    this._discovered = new Set()
    this._activeQuery = null
    this._timer = null
    this._currentRefresh = null
    this._closestNodes = null
    this._firstAnnounce = true
    this._needsUnannounce = false
    this._refreshes = 0
    this._wait = wait
  }

  session({ server = true, client = true, limit = Infinity, onerror = safetyCatch }) {
    if (this.destroyed) throw new Error('PeerDiscovery is destroyed')
    const session = new PeerDiscoverySession(this)
    session.refresh({ server, client, limit }).catch(onerror)
    this._sessions.push(session)
    return session
  }

  _refreshLater(eager) {
    const jitter = Math.round(Math.random() * RANDOM_JITTER)
    const delay = !eager ? REFRESH_INTERVAL + jitter : jitter

    if (this._timer) clearTimeout(this._timer)

    const startTime = Date.now()
    this._timer = setTimeout(() => {
      // If your laptop went to sleep, and is coming back online...
      const overdue = Date.now() - startTime > delay + DELAY_GRACE_PERIOD
      if (overdue) this._refreshLater(true)
      else this.refresh().catch(this._onerror)
    }, delay)
  }

  _isActive() {
    return !this.destroyed && !this.suspended
  }

  // TODO: Allow announce to be an argument to this
  // TODO: Maybe announce should be a setter?
  async _refresh() {
    if (this.suspended) return
    const clock = ++this._refreshes

    if (this._wait) {
      await this._wait
      this._wait = null
      if (clock !== this._refreshes || !this._isActive()) return
    }

    const clear = this.isServer && this._firstAnnounce
    if (clear) this._firstAnnounce = false

    const opts = {
      clear,
      closestNodes: this._closestNodes
    }

    if (this.isServer) {
      await this.swarm.listen()
      // if a parallel refresh is happening, yield to the new one
      if (clock !== this._refreshes || !this._isActive()) return
      this._needsUnannounce = true
    }

    let limit = this.limit

    if (limit < Infinity && limit > 0) {
      for (const id of this._discovered) {
        if (!this.swarm.connections.has(id)) continue
        if (--limit === 0) break
      }
    }

    this._discovered.clear()

    const announcing = this.isServer
    const query = (this._activeQuery = announcing
      ? this.swarm.dht.announce(
          this.topic,
          this.swarm.keyPair,
          this.swarm.server.relayAddresses,
          opts
        )
      : this._needsUnannounce
        ? this.swarm.dht.lookupAndUnannounce(this.topic, this.swarm.keyPair, opts)
        : this.swarm.dht.lookup(this.topic, opts))

    try {
      for await (const data of this._activeQuery) {
        if (!this.isClient || !this._isActive()) continue
        for (const peer of data.peers) {
          if (limit < Infinity) {
            const id = b4a.toString(peer.publicKey, 'hex')

            if (this._discovered.size < MAX_DISCOVERY_CACHE) {
              this._discovered.add(id)
            }

            if (limit === 0) continue

            // there is a chance it has updated a connection during discovery and we go over
            // the limit - thats acceptable to avoid a complexity spiral here.
            if (!this.swarm.connections.has(id)) limit--
          }

          this._onpeer(peer, data)
        }
      }
    } catch (err) {
      if (this._isActive()) throw err
    } finally {
      if (this._activeQuery === query) {
        this._activeQuery = null
        if (!this.destroyed && !this.suspended) this._refreshLater(false)
      }
    }

    // This is set at the very end, when the query completes successfully.
    this._closestNodes = query.closestNodes

    if (clock !== this._refreshes) return

    // In this is the latest query, unannounce has been fulfilled as well
    if (!announcing) this._needsUnannounce = false
  }

  async refresh() {
    if (this.destroyed) throw new Error('PeerDiscovery is destroyed')

    const server = this._serverSessions > 0
    const client = this._clientSessions > 0

    if (this.suspended) return

    if (server === this.isServer && client === this.isClient) {
      if (this._currentRefresh) return this._currentRefresh
      this._currentRefresh = this._refresh()
    } else {
      if (this._activeQuery) this._activeQuery.destroy()
      this.isServer = server
      this.isClient = client
      this._currentRefresh = this._refresh()
    }

    const refresh = this._currentRefresh
    try {
      await refresh
    } catch {
      return false
    } finally {
      if (refresh === this._currentRefresh) {
        this._currentRefresh = null
      }
    }

    return true
  }

  async flushed() {
    if (this.swarm.listening) await this.swarm.listening

    try {
      await this._currentRefresh
      return true
    } catch {
      return false
    }
  }

  async _destroyMaybe() {
    if (this.destroyed) return

    try {
      if (this._sessions.length === 0) await this.swarm.leave(this.topic)
      else if (this._serverSessions === 0 && this._needsUnannounce) await this.refresh()
    } catch (err) {
      // ignore network failures here, as we are tearing down
      safetyCatch(err)
    }
  }

  destroy() {
    if (this.destroying) return this.destroying
    this.destroying = this._destroy()
    return this.destroying
  }

  async _abort(log) {
    const id = log === noop ? '' : b4a.toString(this.topic, 'hex')

    log('Aborting discovery', id)
    if (this._wait) await this._wait
    log('Aborting discovery (post wait)', id)

    if (this._activeQuery) {
      this._activeQuery.destroy()
      this._activeQuery = null
    }
    if (this._timer) {
      clearTimeout(this._timer)
      this._timer = null
    }

    let nodes = this._closestNodes

    if (this._currentRefresh) {
      try {
        await this._currentRefresh
      } catch {
        // If the destroy causes the refresh to fail, suppress it.
      }
    }

    log('Aborting discovery (post refresh)', id)
    if (this._isActive()) return

    if (!nodes) nodes = this._closestNodes
    else if (this._closestNodes !== nodes) {
      const len = nodes.length
      for (const newer of this._closestNodes) {
        if (newer.id && !hasNode(nodes, len, newer)) nodes.push(newer)
      }
    }

    if (this._needsUnannounce) {
      log('Unannouncing discovery', id)
      if (nodes && nodes.length) {
        await this.swarm.dht.unannounce(this.topic, this.swarm.keyPair, {
          closestNodes: nodes,
          onlyClosestNodes: true,
          force: true
        })
      }
      this._needsUnannounce = false
      log('Unannouncing discovery (done)', id)
    }
  }

  _destroy() {
    if (this.destroyed) return
    this.destroyed = true
    return this._abort(noop)
  }

  async suspend({ log = noop } = {}) {
    if (this.suspended) return
    this.suspended = true
    try {
      await this._abort(log)
    } catch {
      // ignore
    }
  }

  resume() {
    if (!this.suspended) return
    this.suspended = false
    this.refresh().catch(noop)
  }
}

class PeerDiscoverySession {
  constructor(discovery) {
    this.discovery = discovery
    this.isClient = false
    this.isServer = false
    this.destroyed = false
  }

  get swarm() {
    return this.discovery.swarm
  }

  get topic() {
    return this.discovery.topic
  }

  async refresh({ client = this.isClient, server = this.isServer, limit = Infinity } = {}) {
    if (this.destroyed) throw new Error('PeerDiscovery is destroyed')
    if (!client && !server) throw new Error('Cannot refresh with neither client nor server option')

    if (client !== this.isClient) {
      this.isClient = client
      this.discovery._clientSessions += client ? 1 : -1
    }

    if (server !== this.isServer) {
      this.isServer = server
      this.discovery._serverSessions += server ? 1 : -1
    }

    this.discovery.limit = limit

    return this.discovery.refresh()
  }

  async flushed() {
    return this.discovery.flushed()
  }

  async destroy() {
    if (this.destroyed) return
    this.destroyed = true

    if (this.isClient) this.discovery._clientSessions--
    if (this.isServer) this.discovery._serverSessions--

    const index = this.discovery._sessions.indexOf(this)
    const head = this.discovery._sessions.pop()

    if (head !== this) this.discovery._sessions[index] = head

    return this.discovery._destroyMaybe()
  }
}

function hasNode(nodes, len, node) {
  for (let i = 0; i < len; i++) {
    const existing = nodes[i]
    if (existing.id && b4a.equals(existing.id, node.id)) return true
  }

  return false
}

function noop() {}
const { EventEmitter } = require('events')
const b4a = require('b4a')
const unslab = require('unslab')

const MIN_CONNECTION_TIME = 15000

const VERY_LOW_PRIORITY = 0
const LOW_PRIORITY = 1
const NORMAL_PRIORITY = 2
const HIGH_PRIORITY = 3
const VERY_HIGH_PRIORITY = 4

module.exports = class PeerInfo extends EventEmitter {
  constructor({ publicKey, relayAddresses }) {
    super()

    this.publicKey = unslab(publicKey)
    this.relayAddresses = relayAddresses

    this.reconnecting = true
    this.proven = false
    this.connectedTime = -1
    this.disconnectedTime = 0
    this.banned = false
    this.tried = false
    this.explicit = false
    this.waiting = false
    this.forceRelaying = false

    // Set by the Swarm
    this.queued = false
    this.client = false
    this.topics = [] // TODO: remove on next major (check with mafintosh for context)

    this.attempts = 0
    this.priority = NORMAL_PRIORITY

    // Used by shuffled-priority-queue
    this._index = 0

    // Used for flush management
    this._flushTick = 0

    // Used for topic multiplexing
    this._seenTopics = new Set()
  }

  get server() {
    return !this.client
  }

  get prioritized() {
    return this.priority >= NORMAL_PRIORITY
  }

  _getPriority() {
    const peerIsStale = this.tried && !this.proven
    if (peerIsStale || this.attempts > 3) return VERY_LOW_PRIORITY
    if (this.attempts === 3) return LOW_PRIORITY
    if (this.attempts === 2) return HIGH_PRIORITY
    if (this.attempts === 1) return VERY_HIGH_PRIORITY
    return NORMAL_PRIORITY
  }

  _connected() {
    this.proven = true
    this.connectedTime = Date.now()
  }

  _disconnected() {
    this.disconnectedTime = Date.now()
    if (this.connectedTime > -1) {
      if (this.disconnectedTime - this.connectedTime >= MIN_CONNECTION_TIME) this.attempts = 0 // fast retry
      this.connectedTime = -1
    }
    this.attempts++
  }

  _deprioritize() {
    this.attempts = 3
  }

  _reset() {
    this.client = false
    this.proven = false
    this.tried = false
    this.attempts = 0
  }

  _updatePriority() {
    if (this.explicit && this.attempts > 3) this._deprioritize()
    if (this.banned || this.queued || this.attempts > 3) return false
    this.priority = this._getPriority()
    return true
  }

  _topic(topic) {
    const topicString = b4a.toString(topic, 'hex')
    if (this._seenTopics.has(topicString)) return
    this._seenTopics.add(topicString)
    this.topics.push(topic)
    this.emit('topic', topic)
  }

  reconnect(val) {
    this.reconnecting = !!val
  }

  ban(val) {
    this.banned = !!val
  }

  shouldGC() {
    return !(this.banned || this.queued || this.explicit || this.waiting)
  }
}
const BulkTimer = require('./bulk-timer')

const BACKOFF_JITTER = 500
const BACKOFF_S = 1000 + Math.round(BACKOFF_JITTER * Math.random())
const BACKOFF_M = 5000 + Math.round(2 * BACKOFF_JITTER * Math.random())
const BACKOFF_L = 15000 + Math.round(4 * BACKOFF_JITTER * Math.random())
const BACKOFF_X = 1000 * 60 * 10 + Math.round(240 * BACKOFF_JITTER * Math.random())

module.exports = class RetryTimer {
  constructor(
    push,
    { backoffs = [BACKOFF_S, BACKOFF_M, BACKOFF_L, BACKOFF_X], jitter = BACKOFF_JITTER } = {}
  ) {
    this.jitter = jitter
    this.backoffs = backoffs

    this._sTimer = new BulkTimer(backoffs[0] + Math.round(jitter * Math.random()), push)
    this._mTimer = new BulkTimer(backoffs[1] + Math.round(jitter * Math.random()), push)
    this._lTimer = new BulkTimer(backoffs[2] + Math.round(jitter * Math.random()), push)
    this._xTimer = new BulkTimer(backoffs[3] + Math.round(jitter * Math.random()), push)
  }

  _selectRetryTimer(peerInfo) {
    if (peerInfo.banned || !peerInfo.reconnecting) return null

    if (peerInfo.attempts > 3) {
      return peerInfo.explicit ? this._xTimer : null
    }

    if (peerInfo.attempts === 0) return this._sTimer
    if (peerInfo.proven) {
      switch (peerInfo.attempts) {
        case 1:
          return this._sTimer
        case 2:
          return this._mTimer
        case 3:
          return this._lTimer
      }
    } else {
      switch (peerInfo.attempts) {
        case 1:
          return this._mTimer
        case 2:
          return this._lTimer
        case 3:
          return this._lTimer
      }
    }

    return null
  }

  add(peerInfo) {
    const timer = this._selectRetryTimer(peerInfo)
    if (!timer) return false

    timer.add(peerInfo)
    return true
  }

  destroy() {
    this._sTimer.destroy()
    this._mTimer.destroy()
    this._lTimer.destroy()
    this._xTimer.destroy()
  }
}
{
  "name": "hyperswarm",
  "version": "4.17.0",
  "description": "A distributed networking stack for connecting peers",
  "files": [
    "index.js",
    "lib/**.js"
  ],
  "imports": {
    "events": {
      "bare": "bare-events",
      "default": "events"
    },
    "process": {
      "bare": "bare-process",
      "default": "process"
    }
  },
  "dependencies": {
    "b4a": "^1.3.1",
    "bare-events": "^2.2.0",
    "hyperdht": "^6.21.0",
    "safety-catch": "^1.0.2",
    "shuffled-priority-queue": "^2.1.0",
    "streamx": "^2.22.1",
    "unslab": "^1.3.0"
  },
  "devDependencies": {
    "bare-process": "^4.2.2",
    "brittle": "^3.0.2",
    "hypercore-crypto": "^3.4.0",
    "lunte": "^1.3.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^2.0.0",
    "which-runtime": "^1.3.2"
  },
  "scripts": {
    "format": "prettier --write .",
    "lint": "prettier --check . && lunte",
    "test": "npm run test:node && npm run test:bare",
    "test:generate": "brittle -r test/all.js test/*.js",
    "test:node": "brittle-node --coverage test/*.js",
    "test:bare": "bare test/all.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/holepunchto/hyperswarm.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "contributors": [
    "David Mark Clements (@davidmarkclem)",
    "Andrew Osheroff (@andrewosh)"
  ],
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/holepunchto/hyperswarm/issues"
  },
  "homepage": "https://github.com/holepunchto/hyperswarm"
}
const { EventEmitter } = require('events')

module.exports = class RoutingTable extends EventEmitter {
  constructor (id, opts) {
    if (!opts) opts = {}

    super()

    this.id = id
    this.k = opts.k || 20
    this.size = 0
    this.rows = new Array(id.length * 8)
  }

  add (node) {
    const i = this._diff(node.id)

    let row = this.rows[i]

    if (!row) {
      row = this.rows[i] = new Row(this, i)
      this.emit('row', row)
    }

    const len = row.nodes.length
    if (!row.add(node, this.k)) return false

    this.size += row.nodes.length - len
    return true
  }

  remove (id) {
    const i = this._diff(id)
    const row = this.rows[i]
    if (!row) return false
    if (!row.remove(id)) return false
    this.size--
    return true
  }

  get (id) {
    const i = this._diff(id)
    const row = this.rows[i]
    if (!row) return null
    return row.get(id)
  }

  has (id) {
    return this.get(id) !== null
  }

  random () {
    let n = (Math.random() * this.size) | 0

    for (let i = 0; i < this.rows.length; i++) {
      const r = this.rows[i]
      if (!r) continue
      if (n < r.nodes.length) return r.nodes[n]
      n -= r.nodes.length
    }

    return null
  }

  closest (id, k) {
    if (!k) k = this.k

    const result = []
    const d = this._diff(id)

    // push close nodes
    for (let i = d; i >= 0 && result.length < k; i--) this._pushNodes(i, k, result)

    // if we don't have enough close nodes, populate from other rows, re the paper
    for (let i = d + 1; i < this.rows.length && result.length < k; i++) this._pushNodes(i, k, result)

    return result
  }

  _pushNodes (i, k, result) {
    const row = this.rows[i]
    if (!row) return

    const missing = Math.min(k - result.length, row.nodes.length)
    for (let j = 0; j < missing; j++) result.push(row.nodes[j])
  }

  toArray () {
    return this.closest(this.id, Infinity)
  }

  _diff (id) {
    for (let i = 0; i < id.length; i++) {
      const a = id[i]
      const b = this.id[i]

      if (a !== b) return i * 8 + Math.clz32(a ^ b) - 24
    }

    return this.rows.length - 1
  }
}

class Row extends EventEmitter {
  constructor (table, index) {
    super()

    this.data = null // can be used be upstream for whatevs
    this.byteOffset = index >> 3
    this.index = index
    this.table = table
    this.nodes = []
  }

  add (node) {
    const id = node.id

    let l = 0
    let r = this.nodes.length - 1

    while (l <= r) {
      const m = (l + r) >> 1
      const c = this.compare(id, this.nodes[m].id)

      if (c === 0) {
        this.nodes[m] = node
        return true
      }

      if (c < 0) r = m - 1
      else l = m + 1
    }

    if (this.nodes.length >= this.table.k) {
      this.emit('full', node)
      return false
    }

    this.insert(l, node)
    return true
  }

  remove (id) {
    let l = 0
    let r = this.nodes.length - 1

    while (l <= r) {
      const m = (l + r) >> 1
      const c = this.compare(id, this.nodes[m].id)

      if (c === 0) {
        this.splice(m)
        return true
      }

      if (c < 0) r = m - 1
      else l = m + 1
    }

    return false
  }

  get (id) {
    let l = 0
    let r = this.nodes.length - 1

    while (l <= r) {
      const m = (l + r) >> 1
      const node = this.nodes[m]
      const c = this.compare(id, node.id)

      if (c === 0) return node
      if (c < 0) r = m - 1
      else l = m + 1
    }

    return null
  }

  insert (i, node) {
    this.nodes.push(node) // push node or null or whatevs, just trying to not be polymorphic
    for (let j = this.nodes.length - 1; j > i; j--) this.nodes[j] = this.nodes[j - 1]
    this.nodes[i] = node
    this.emit('add', node)
  }

  splice (i) {
    for (; i < this.nodes.length - 1; i++) this.nodes[i] = this.nodes[i + 1]
    this.emit('remove', this.nodes.pop())
  }

  // very likely they diverge after a couple of bytes so a simple impl, like this is prop fastest vs Buffer.compare
  compare (a, b) {
    for (let i = this.byteOffset; i < a.length; i++) {
      const ai = a[i]
      const bi = b[i]
      if (ai === bi) continue
      return ai < bi ? -1 : 1
    }
    return 0
  }
}
{
  "name": "kademlia-routing-table",
  "version": "1.0.6",
  "description": "XOR based routing table used for P2P networks such as a Kademlia DHT.",
  "main": "index.js",
  "files": [
    "index.js"
  ],
  "imports": {
    "events": {
      "bare": "bare-events",
      "default": "events"
    }
  },
  "dependencies": {
    "bare-events": "^2.2.0"
  },
  "devDependencies": {
    "brittle": "^3.3.2",
    "standard": "^17.1.0"
  },
  "scripts": {
    "test": "standard && brittle test.js"
  },
  "keywords": [
    "kademlia",
    "p2p",
    "k-bucket",
    "k-buckets",
    "xor",
    "routing",
    "distributed",
    "systems"
  ],
  "repository": {
    "type": "git",
    "url": "https://github.com/mafintosh/kademlia-routing-table.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/mafintosh/kademlia-routing-table/issues"
  },
  "homepage": "https://github.com/mafintosh/kademlia-routing-table"
}
module.exports = assert

class AssertionError extends Error {}
AssertionError.prototype.name = 'AssertionError'

/**
 * Minimal assert function
 * @param  {any} t Value to check if falsy
 * @param  {string=} m Optional assertion error message
 * @throws {AssertionError}
 */
function assert (t, m) {
  if (!t) {
    var err = new AssertionError(m)
    if (Error.captureStackTrace) Error.captureStackTrace(err, assert)
    throw err
  }
}
{
  "name": "nanoassert",
  "version": "2.0.0",
  "description": "Nanoscale assertion module",
  "main": "index.js",
  "dependencies": {},
  "devDependencies": {
    "tape": "^4.9.1"
  },
  "scripts": {
    "test": "tape test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/emilbayes/nanoassert.git"
  },
  "keywords": [
    "assert",
    "unassert",
    "power-assert",
    "tiny",
    "nano",
    "pico"
  ],
  "author": "Emil Bay <github@tixz.dk>",
  "license": "ISC",
  "bugs": {
    "url": "https://github.com/emilbayes/nanoassert/issues"
  },
  "homepage": "https://github.com/emilbayes/nanoassert#readme"
}
module.exports = class NatSampler {
  constructor () {
    this.host = null
    this.port = 0
    this.size = 0

    this._a = null
    this._b = null
    this._threshold = 0
    this._top = 0
    this._samples = []
  }

  add (host, port) {
    const a = this._bump(host, port, 2)
    const b = this._bump(host, 0, 1)

    if (this._samples.length < 32) {
      this.size++
      this._threshold = this.size - (this.size < 4 ? 0 : this.size < 8 ? 1 : this.size < 12 ? 2 : 3)
      this._samples.push(a, b)
      this._top += 2
    } else {
      if (this._top === 32) this._top = 0

      const oa = this._samples[this._top]
      this._samples[this._top++] = a
      oa.hits--

      const ob = this._samples[this._top]
      this._samples[this._top++] = b
      ob.hits--
    }

    if (this._a === null || this._a.hits < a.hits) this._a = a
    if (this._b === null || this._b.hits < b.hits) this._b = b

    if (this._a.hits >= this._threshold) {
      this.host = this._a.host
      this.port = this._a.port
    } else if (this._b.hits >= this._threshold) {
      this.host = this._b.host
      this.port = 0
    } else {
      this.host = null
      this.port = 0
    }

    return a.hits
  }

  _bump (host, port, inc) {
    for (let i = 0; i < 4; i++) {
      const j = (this._top - inc - (2 * i)) & 31
      if (j >= this._samples.length) return { host, port, hits: 1 }
      const s = this._samples[j]
      if (s.port === port && s.host === host) {
        s.hits++
        return s
      }
    }
    return { host, port, hits: 1 }
  }
}
{
  "name": "nat-sampler",
  "version": "1.0.1",
  "description": "Sample addresses to figure out if a host + port is consistent",
  "main": "index.js",
  "dependencies": {},
  "devDependencies": {
    "standard": "^16.0.3",
    "tape": "^5.2.2"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/mafintosh/nat-sampler.git"
  },
  "scripts": {
    "test": "standard && tape test.js"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/mafintosh/nat-sampler/issues"
  },
  "homepage": "https://github.com/mafintosh/nat-sampler"
}
/* eslint-disable camelcase */
const sodium = require('sodium-universal')
const assert = require('nanoassert')
const b4a = require('b4a')

const DHLEN = sodium.crypto_scalarmult_ed25519_BYTES
const PKLEN = sodium.crypto_scalarmult_ed25519_BYTES
const SCALARLEN = sodium.crypto_scalarmult_ed25519_BYTES
const SKLEN = sodium.crypto_sign_SECRETKEYBYTES
const ALG = 'Ed25519'

module.exports = {
  DHLEN,
  PKLEN,
  SCALARLEN,
  SKLEN,
  ALG,
  name: ALG,
  generateKeyPair,
  dh
}

function generateKeyPair (privKey) {
  if (privKey) return generateSeedKeyPair(privKey.subarray(0, 32))

  const keyPair = {}
  keyPair.secretKey = b4a.alloc(SKLEN)
  keyPair.publicKey = b4a.alloc(PKLEN)

  sodium.crypto_sign_keypair(keyPair.publicKey, keyPair.secretKey)
  return keyPair
}

function generateSeedKeyPair (seed) {
  const keyPair = {}
  keyPair.secretKey = b4a.alloc(SKLEN)
  keyPair.publicKey = b4a.alloc(PKLEN)

  sodium.crypto_sign_seed_keypair(keyPair.publicKey, keyPair.secretKey, seed)
  return keyPair
}

function dh (publicKey, { scalar, secretKey }) {
  // tweaked keys expose scalar directly
  if (!scalar) {
    assert(secretKey.byteLength === SKLEN)

    // libsodium stores seed not actual scalar
    const sk = b4a.alloc(64)
    sodium.crypto_hash_sha512(sk, secretKey.subarray(0, 32))
    sk[0] &= 248
    sk[31] &= 127
    sk[31] |= 64

    scalar = sk.subarray(0, 32)
  }

  assert(scalar.byteLength === SCALARLEN)
  assert(publicKey.byteLength === PKLEN)

  const output = b4a.alloc(DHLEN)

  // we clamp if necessary above
  sodium.crypto_scalarmult_ed25519_noclamp(
    output,
    scalar,
    publicKey
  )

  return output
}
{
  "name": "noise-curve-ed",
  "version": "2.1.0",
  "description": "Ed25519 elliptic curve operations for [`noise-handshake`](https://github.com/chm-diederichs/noise-handshake)",
  "main": "index.js",
  "scripts": {
    "test": "npx standard && tape test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/chm-diederichs/noise-curve-ed.git"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "bugs": {
    "url": "https://github.com/chm-diederichs/noise-curve-ed/issues"
  },
  "homepage": "https://github.com/chm-diederichs/noise-curve-ed#readme",
  "dependencies": {
    "b4a": "^1.1.0",
    "nanoassert": "^2.0.0",
    "sodium-universal": "^5.0.0"
  },
  "devDependencies": {
    "noise-handshake": "^3.0.0",
    "standard": "^16.0.3",
    "tape": "^5.2.2"
  }
}
const sodium = require('sodium-universal')
const b4a = require('b4a')

module.exports = class CipherState {
  constructor (key) {
    this.key = key || null
    this.nonce = 0
    this.CIPHER_ALG = 'ChaChaPoly'
  }

  initialiseKey (key) {
    this.key = key
    this.nonce = 0
  }

  setNonce (nonce) {
    this.nonce = nonce
  }

  encrypt (plaintext, ad) {
    if (!this.hasKey) return plaintext
    if (!ad) ad = b4a.alloc(0)

    const ciphertext = encryptWithAD(this.key, this.nonce, ad, plaintext)
    if (ciphertext.length > 65535) throw new Error(`ciphertext length of ${ciphertext.length} exceeds maximum Noise message length of 65535`)
    this.nonce++

    return ciphertext
  }

  decrypt (ciphertext, ad) {
    if (!this.hasKey) return ciphertext
    if (!ad) ad = b4a.alloc(0)
    if (ciphertext.length > 65535) throw new Error(`ciphertext length of ${ciphertext.length} exceeds maximum Noise message length of 65535`)

    const plaintext = decryptWithAD(this.key, this.nonce, ad, ciphertext)
    this.nonce++

    return plaintext
  }

  get hasKey () {
    return this.key !== null
  }

  _clear () {
    sodium.sodium_memzero(this.key)
    this.key = null
    this.nonce = null
  }

  static get MACBYTES () {
    return 16
  }

  static get NONCEBYTES () {
    return 8
  }

  static get KEYBYTES () {
    return 32
  }
}

function encryptWithAD (key, counter, additionalData, plaintext) {
  // for our purposes, additionalData will always be a pubkey so we encode from hex
  if (!b4a.isBuffer(additionalData)) additionalData = b4a.from(additionalData, 'hex')
  if (!b4a.isBuffer(plaintext)) plaintext = b4a.from(plaintext, 'hex')

  const nonce = b4a.alloc(sodium.crypto_aead_chacha20poly1305_ietf_NPUBBYTES)
  const view = new DataView(nonce.buffer, nonce.byteOffset, nonce.byteLength)
  view.setUint32(4, counter, true)

  const ciphertext = b4a.alloc(plaintext.byteLength + sodium.crypto_aead_chacha20poly1305_ietf_ABYTES)

  sodium.crypto_aead_chacha20poly1305_ietf_encrypt(ciphertext, plaintext, additionalData, null, nonce, key)
  return ciphertext
}

function decryptWithAD (key, counter, additionalData, ciphertext) {
  // for our purposes, additionalData will always be a pubkey so we encode from hex
  if (!b4a.isBuffer(additionalData)) additionalData = b4a.from(additionalData, 'hex')
  if (!b4a.isBuffer(ciphertext)) ciphertext = b4a.from(ciphertext, 'hex')

  const nonce = b4a.alloc(sodium.crypto_aead_chacha20poly1305_ietf_NPUBBYTES)
  const view = new DataView(nonce.buffer, nonce.byteOffset, nonce.byteLength)
  view.setUint32(4, counter, true)

  const plaintext = b4a.alloc(ciphertext.byteLength - sodium.crypto_aead_chacha20poly1305_ietf_ABYTES)

  sodium.crypto_aead_chacha20poly1305_ietf_decrypt(plaintext, null, ciphertext, additionalData, nonce, key)
  return plaintext
}
/* eslint-disable camelcase */
const {
  crypto_kx_SEEDBYTES,
  crypto_kx_keypair,
  crypto_kx_seed_keypair,
  crypto_scalarmult_BYTES,
  crypto_scalarmult_SCALARBYTES,
  crypto_scalarmult,
  crypto_scalarmult_base
} = require('sodium-universal')

const assert = require('nanoassert')
const b4a = require('b4a')

const DHLEN = crypto_scalarmult_BYTES
const PKLEN = crypto_scalarmult_BYTES
const SKLEN = crypto_scalarmult_SCALARBYTES
const SEEDLEN = crypto_kx_SEEDBYTES
const ALG = '25519'

module.exports = {
  DHLEN,
  PKLEN,
  SKLEN,
  SEEDLEN,
  ALG,
  generateKeyPair,
  generateSeedKeyPair,
  dh
}

function generateKeyPair (privKey) {
  const keyPair = {}

  keyPair.secretKey = privKey || b4a.alloc(SKLEN)
  keyPair.publicKey = b4a.alloc(PKLEN)

  if (privKey) {
    crypto_scalarmult_base(keyPair.publicKey, keyPair.secretKey)
  } else {
    crypto_kx_keypair(keyPair.publicKey, keyPair.secretKey)
  }

  return keyPair
}

function generateSeedKeyPair (seed) {
  assert(seed.byteLength === SKLEN)

  const keyPair = {}
  keyPair.secretKey = b4a.alloc(SKLEN)
  keyPair.publicKey = b4a.alloc(PKLEN)

  crypto_kx_seed_keypair(keyPair.publicKey, keyPair.secretKey, seed)
  return keyPair
}

function dh (publicKey, { secretKey }) {
  assert(secretKey.byteLength === SKLEN)
  assert(publicKey.byteLength === PKLEN)

  const output = b4a.alloc(DHLEN)

  crypto_scalarmult(
    output,
    secretKey,
    publicKey
  )

  return output
}
const hmacBlake2b = require('./hmac')
const b4a = require('b4a')

const HASHLEN = 64

module.exports = {
  hkdf,
  HASHLEN
}

// HMAC-based Extract-and-Expand KDF
// https://www.ietf.org/rfc/rfc5869.txt

function hkdf (salt, inputKeyMaterial, info = '', length = 2 * HASHLEN) {
  const pseudoRandomKey = hkdfExtract(salt, inputKeyMaterial)
  return hkdfExpand(pseudoRandomKey, info, length)
}

function hkdfExtract (salt, inputKeyMaterial) {
  const hmac = b4a.alloc(HASHLEN)
  return hmacDigest(hmac, salt, inputKeyMaterial)
}

function hkdfExpand (key, info, length) {
  // Put in dedicated slab to avoid keeping shared slab from being gc'ed
  const buffer = b4a.allocUnsafeSlow(length)

  const infoBuf = b4a.from(info)
  let prev = infoBuf

  const result = []
  for (let i = 0; i < length; i += HASHLEN) {
    const pos = b4a.from([(i / HASHLEN) + 1])

    const out = buffer.subarray(i, i + HASHLEN)
    result.push(out)

    prev = hmacDigest(out, key, [prev, infoBuf, pos])
  }

  return result
}

function hmacDigest (out, key, input) {
  hmacBlake2b(out, input, key)
  return out
}
/* eslint-disable camelcase */
const b4a = require('b4a')
const { sodium_memzero, crypto_generichash, crypto_generichash_batch } = require('sodium-universal')

const HASHLEN = 64
const BLOCKLEN = 128
const scratch = b4a.alloc(BLOCKLEN * 3)
const HMACKey = scratch.subarray(BLOCKLEN * 0, BLOCKLEN * 1)
const OuterKeyPad = scratch.subarray(BLOCKLEN * 1, BLOCKLEN * 2)
const InnerKeyPad = scratch.subarray(BLOCKLEN * 2, BLOCKLEN * 3)

// Post-fill is done in the cases where someone caught an exception that
// happened before we were able to clear data at the end

module.exports = function hmac (out, batch, key) {
  if (key.byteLength > BLOCKLEN) {
    crypto_generichash(HMACKey.subarray(0, HASHLEN), key)
    sodium_memzero(HMACKey.subarray(HASHLEN))
  } else {
    // Covers key <= BLOCKLEN
    HMACKey.set(key)
    sodium_memzero(HMACKey.subarray(key.byteLength))
  }

  for (let i = 0; i < HMACKey.byteLength; i++) {
    OuterKeyPad[i] = 0x5c ^ HMACKey[i]
    InnerKeyPad[i] = 0x36 ^ HMACKey[i]
  }
  sodium_memzero(HMACKey)

  crypto_generichash_batch(out, [InnerKeyPad].concat(batch))
  sodium_memzero(InnerKeyPad)
  crypto_generichash_batch(out, [OuterKeyPad, out])
  sodium_memzero(OuterKeyPad)
}

module.exports.BYTES = HASHLEN
module.exports.KEYBYTES = BLOCKLEN
const assert = require('nanoassert')
const b4a = require('b4a')

const SymmetricState = require('./symmetric-state')
const { HASHLEN } = require('./hkdf')

const PRESHARE_IS = Symbol('initiator static key preshared')
const PRESHARE_RS = Symbol('responder static key preshared')

const TOK_PSK = Symbol('psk')

const TOK_S = Symbol('s')
const TOK_E = Symbol('e')

const TOK_ES = Symbol('es')
const TOK_SE = Symbol('se')
const TOK_EE = Symbol('ee')
const TOK_SS = Symbol('ss')

const HANDSHAKES = Object.freeze({
  NN: [
    [TOK_E],
    [TOK_E, TOK_EE]
  ],
  NNpsk0: [
    [TOK_PSK, TOK_E],
    [TOK_E, TOK_EE]
  ],
  XX: [
    [TOK_E],
    [TOK_E, TOK_EE, TOK_S, TOK_ES],
    [TOK_S, TOK_SE]
  ],
  XXpsk0: [
    [TOK_PSK, TOK_E],
    [TOK_E, TOK_EE, TOK_S, TOK_ES],
    [TOK_S, TOK_SE]
  ],
  IK: [
    PRESHARE_RS,
    [TOK_E, TOK_ES, TOK_S, TOK_SS],
    [TOK_E, TOK_EE, TOK_SE]
  ],
  XK: [
    PRESHARE_RS,
    [TOK_E, TOK_ES],
    [TOK_E, TOK_EE],
    [TOK_S, TOK_SE]
  ]
})

class Writer {
  constructor () {
    this.size = 0
    this.buffers = []
  }

  push (b) {
    this.size += b.byteLength
    this.buffers.push(b)
  }

  end () {
    const all = b4a.alloc(this.size)
    let offset = 0
    for (const b of this.buffers) {
      all.set(b, offset)
      offset += b.byteLength
    }
    return all
  }
}

class Reader {
  constructor (buf) {
    this.offset = 0
    this.buffer = buf
  }

  shift (n) {
    const start = this.offset
    const end = this.offset += n
    if (end > this.buffer.byteLength) throw new Error('Insufficient bytes')
    return this.buffer.subarray(start, end)
  }

  end () {
    return this.shift(this.buffer.byteLength - this.offset)
  }
}

module.exports = class NoiseState extends SymmetricState {
  constructor (pattern, initiator, staticKeypair, opts = {}) {
    super(opts)

    this.s = staticKeypair || this.curve.generateKeyPair()
    this.e = null

    this.psk = null
    if (opts && opts.psk) this.psk = opts.psk

    this.re = null
    this.rs = null

    this.pattern = pattern
    this.handshake = HANDSHAKES[this.pattern].slice()

    this.isPskHandshake = !!this.psk && hasPskToken(this.handshake)

    this.protocol = b4a.from([
      'Noise',
      this.pattern,
      this.DH_ALG,
      this.CIPHER_ALG,
      'BLAKE2b'
    ].join('_'))

    this.initiator = initiator
    this.complete = false

    this.rx = null
    this.tx = null
    this.hash = null
  }

  initialise (prologue, remoteStatic) {
    if (this.protocol.byteLength <= HASHLEN) this.digest.set(this.protocol)
    else this.mixHash(this.protocol)

    this.chainingKey = b4a.from(this.digest)

    this.mixHash(prologue)

    while (!Array.isArray(this.handshake[0])) {
      const message = this.handshake.shift()

      // handshake steps should be as arrays, only
      // preshare tokens are provided otherwise
      assert(message === PRESHARE_RS || message === PRESHARE_IS,
        'Unexpected pattern')

      const takeRemoteKey = this.initiator
        ? message === PRESHARE_RS
        : message === PRESHARE_IS

      if (takeRemoteKey) this.rs = remoteStatic

      const key = takeRemoteKey ? this.rs : this.s.publicKey
      assert(key != null, 'Remote pubkey required')

      this.mixHash(key)
    }
  }

  final () {
    const [k1, k2] = this.split()

    this.tx = this.initiator ? k1 : k2
    this.rx = this.initiator ? k2 : k1

    this.complete = true
    this.hash = this.getHandshakeHash()

    this._clear()
  }

  recv (buf) {
    const r = new Reader(buf)

    for (const pattern of this.handshake.shift()) {
      switch (pattern) {
        case TOK_PSK :
          this.mixKeyAndHash(this.psk)
          break

        case TOK_E :
          this.re = r.shift(this.curve.PKLEN)
          this.mixHash(this.re)
          if (this.isPskHandshake) this.mixKeyNormal(this.re)
          break

        case TOK_S : {
          const klen = this.hasKey ? this.curve.PKLEN + 16 : this.curve.PKLEN
          this.rs = this.decryptAndHash(r.shift(klen))
          break
        }

        case TOK_EE :
        case TOK_ES :
        case TOK_SE :
        case TOK_SS : {
          const useStatic = keyPattern(pattern, this.initiator)

          const localKey = useStatic.local ? this.s : this.e
          const remoteKey = useStatic.remote ? this.rs : this.re

          this.mixKey(remoteKey, localKey)
          break
        }

        default :
          throw new Error('Unexpected message')
      }
    }

    const payload = this.decryptAndHash(r.end())

    if (!this.handshake.length) this.final()
    return payload
  }

  send (payload = b4a.alloc(0)) {
    const w = new Writer()

    for (const pattern of this.handshake.shift()) {
      switch (pattern) {
        case TOK_PSK :
          this.mixKeyAndHash(this.psk)
          break

        case TOK_E :
          if (this.e === null) this.e = this.curve.generateKeyPair()
          this.mixHash(this.e.publicKey)
          if (this.isPskHandshake) this.mixKeyNormal(this.e.publicKey)
          w.push(this.e.publicKey)
          break

        case TOK_S :
          w.push(this.encryptAndHash(this.s.publicKey))
          break

        case TOK_ES :
        case TOK_SE :
        case TOK_EE :
        case TOK_SS : {
          const useStatic = keyPattern(pattern, this.initiator)

          const localKey = useStatic.local ? this.s : this.e
          const remoteKey = useStatic.remote ? this.rs : this.re

          this.mixKey(remoteKey, localKey)
          break
        }

        default :
          throw new Error('Unexpected message')
      }
    }

    w.push(this.encryptAndHash(payload))
    const response = w.end()

    if (!this.handshake.length) this.final()
    return response
  }

  _clear () {
    super._clear()

    this.e.secretKey.fill(0)
    this.e.publicKey.fill(0)

    this.re.fill(0)

    this.e = null
    this.re = null
  }
}

function keyPattern (pattern, initiator) {
  const ret = {
    local: false,
    remote: false
  }

  switch (pattern) {
    case TOK_EE:
      return ret

    case TOK_ES:
      ret.local ^= !initiator
      ret.remote ^= initiator
      return ret

    case TOK_SE:
      ret.local ^= initiator
      ret.remote ^= !initiator
      return ret

    case TOK_SS:
      ret.local ^= 1
      ret.remote ^= 1
      return ret
  }
}

function hasPskToken (handshake) {
  return handshake.some(x => {
    return Array.isArray(x) && x.indexOf(TOK_PSK) !== -1
  })
}
{
  "name": "noise-handshake",
  "version": "4.2.0",
  "description": "Noise protocol handshake",
  "main": "noise.js",
  "files": [
    "*.js"
  ],
  "scripts": {
    "test": "standard && brittle test/*.js"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/noise-handshake.git"
  },
  "dependencies": {
    "b4a": "^1.1.0",
    "nanoassert": "^2.0.0",
    "sodium-universal": "^5.0.0"
  },
  "devDependencies": {
    "brittle": "^3.3.2",
    "noise-protocol": "chm-diederichs/noise-protocol.git#xx-ephemeral-key",
    "standard": "^16.0.3"
  }
}
const sodium = require('sodium-universal')
const assert = require('nanoassert')
const b4a = require('b4a')
const CipherState = require('./cipher')
const curve = require('./dh')
const { HASHLEN, hkdf } = require('./hkdf')

module.exports = class SymmetricState extends CipherState {
  constructor (opts = {}) {
    super()

    this.curve = opts.curve || curve
    this.digest = b4a.alloc(HASHLEN)
    this.chainingKey = null
    this.offset = 0

    this.DH_ALG = this.curve.ALG
  }

  mixHash (data) {
    accumulateDigest(this.digest, data)
  }

  mixKeyAndHash (key) {
    const [ck, tempH, tempK] = hkdf(this.chainingKey, key, '', 3 * HASHLEN)
    this.chainingKey = ck
    this.mixHash(tempH)
    this.initialiseKey(tempK.subarray(0, 32))
  }

  mixKeyNormal (key) {
    const [ck, tempK] = hkdf(this.chainingKey, key)
    this.chainingKey = ck
    this.initialiseKey(tempK.subarray(0, 32))
  }

  mixKey (remoteKey, localKey) {
    const dh = this.curve.dh(remoteKey, localKey)
    const hkdfResult = hkdf(this.chainingKey, dh)
    this.chainingKey = hkdfResult[0]
    this.initialiseKey(hkdfResult[1].subarray(0, 32))
  }

  encryptAndHash (plaintext) {
    const ciphertext = this.encrypt(plaintext, this.digest)
    accumulateDigest(this.digest, ciphertext)
    return ciphertext
  }

  decryptAndHash (ciphertext) {
    const plaintext = this.decrypt(ciphertext, this.digest)
    accumulateDigest(this.digest, ciphertext)
    return plaintext
  }

  getHandshakeHash (out) {
    if (!out) return this.getHandshakeHash(b4a.alloc(HASHLEN))
    assert(out.byteLength === HASHLEN, `output must be ${HASHLEN} bytes`)

    out.set(this.digest)
    return out
  }

  split () {
    const res = hkdf(this.chainingKey, b4a.alloc(0))
    return res.map(k => k.subarray(0, 32))
  }

  _clear () {
    super._clear()

    sodium.sodium_memzero(this.digest)
    sodium.sodium_memzero(this.chainingKey)

    this.digest = null
    this.chainingKey = null
    this.offset = null

    this.curve = null
  }

  static get alg () {
    return CipherState.alg + '_BLAKE2b'
  }
}

function accumulateDigest (digest, input) {
  const toHash = b4a.concat([digest, input])
  sodium.crypto_generichash(digest, toHash)
}
/**
 * pgh-runtime — per-app OTA updater.
 *
 * An installed app imports this module and instantiates a PghRuntime
 * with its own upgrade source, a store handle, and a fetcher. The
 * runtime periodically checks for a newer release and, when found,
 * materializes the new tree into the store and emits events. The app
 * decides when to actually apply the update (typically on next restart).
 *
 * Mirrors pear-runtime-updater's shape (~127 LOC) but uses hyper-overlay
 * tree content-addressing instead of Hyperdrive.
 *
 * Usage:
 *
 *   import { PghRuntime } from 'pgh-runtime'
 *   const runtime = new PghRuntime({
 *     name: 'my-app',
 *     currentTreeHash: process.env.PGH_CURRENT_TREE_HASH,
 *     fetcher,
 *     store,
 *     interval: 60 * 60 * 1000
 *   })
 *   runtime.on('updating', () => console.log('downloading...'))
 *   runtime.on('updated', (info) => console.log('ready:', info.newTreeHash))
 *   runtime.on('error', (err) => console.error(err))
 *   await runtime.start()
 *
 * Expected interfaces:
 *
 *   fetcher:
 *     async getLatestRelease(name) -> { treeHash, version, ... } | null
 *     async getTree(hash)          -> { entries: [...], size }
 *     async getContent(contentHash)-> Buffer
 *
 *   store (TreeStore-compatible):
 *     init()
 *     hasTree(hash) -> boolean
 *     materialize(hash, entries, { fetchContent, fetchTree, source? })
 *
 * The runtime is transport-agnostic — any fetcher conforming to that
 * shape works. See pgh's NetworkFetcher for the Hyperswarm implementation.
 */

import EventEmitter from '#node:events'

import { computeTreeHash } from 'hyper-overlay/tree'

const DEFAULT_INTERVAL = 30 * 60 * 1000 // 30 min

export class PghRuntime extends EventEmitter {
  /**
   * @param {object} opts
   * @param {string} opts.name                 app name
   * @param {string} [opts.currentTreeHash]    hex hash of the tree this app currently runs at
   * @param {object} opts.fetcher              { getTree, getContent, getLatestRelease(name) }
   * @param {object} opts.store                TreeStore-compatible (hasTree / materialize / init)
   * @param {number} [opts.interval]           ms between polls (default 30m; 0 = no polling)
   * @param {boolean} [opts.startImmediately]  do an initial check on start (default true)
   * @param {string} [opts.host]               host label for per-arch prebuild trees (e.g. "linux-x64")
   * @param {function} [opts.materialize]      injected materializer ({ store, appName, rootTreeHash, deps, host })
   *                                           — pgh's node materializer or any compatible fn. Omit for store-only.
   */
  constructor (opts = {}) {
    super()
    if (!opts.name) throw new Error('PghRuntime requires opts.name')
    if (!opts.fetcher) throw new Error('PghRuntime requires opts.fetcher')
    if (!opts.store) throw new Error('PghRuntime requires opts.store')

    this.name = opts.name
    this.currentTreeHash = opts.currentTreeHash || null
    this.fetcher = opts.fetcher
    this.store = opts.store
    this.interval = opts.interval != null ? opts.interval : DEFAULT_INTERVAL
    this.startImmediately = opts.startImmediately !== false
    this.host = opts.host || null
    this.materialize = typeof opts.materialize === 'function' ? opts.materialize : null

    this.latest = null
    this.updating = false
    this.updated = false
    this.newTreeHash = null

    this._timer = null
    this._started = false
  }

  async start () {
    if (this._started) return
    this._started = true
    if (this.startImmediately) await this._check()
    if (this.interval > 0) {
      this._timer = setInterval(() => { this._check().catch(e => this.emit('error', e)) }, this.interval)
      if (this._timer.unref) this._timer.unref()
    }
  }

  async stop () {
    if (!this._started) return
    this._started = false
    if (this._timer) { clearInterval(this._timer); this._timer = null }
  }

  /**
   * Force an immediate check. Returns the release entry if a new tree
   * was materialized, null otherwise.
   */
  async checkNow () {
    return this._check()
  }

  async _check () {
    let release
    try {
      release = await this.fetcher.getLatestRelease(this.name)
    } catch (err) {
      this.emit('error', err)
      return null
    }

    if (!release) return null

    // Nothing to do if we already run this tree.
    if (this.currentTreeHash && release.treeHash === this.currentTreeHash) {
      this.latest = release
      return null
    }
    // We've already staged this tree; don't re-emit.
    if (this.updated && release.treeHash === this.newTreeHash) {
      return null
    }

    this.latest = release
    this.updating = true
    this.emit('updating', release)

    try {
      // Full RELEASE install — root tree PLUS its dependency closure (and,
      // when a materializer is injected, the wired install layout). Using
      // installTree alone here fetched only the root tree → a broken install
      // for any app with deps (see installRelease).
      const result = await installRelease(this.store, release, this.fetcher, {
        host: this.host,
        materialize: this.materialize,
        appName: this.name,
        source: 'pgh-runtime:' + this.name
      })
      this.newTreeHash = release.treeHash
      this.updating = false
      this.updated = true
      this.emit('updated', {
        name: this.name,
        newTreeHash: release.treeHash,
        release,
        materialized: result.materialized,
        skipped: result.skipped
      })
      return release
    } catch (err) {
      this.updating = false
      this.emit('error', err)
      return null
    }
  }

  /**
   * Descriptor for the staged update. Apply via the app's own mechanism
   * (pgh app update <name> --tree <hash> is the usual CLI path).
   */
  readyToApply () {
    if (!this.updated) return null
    return {
      name: this.name,
      previousTreeHash: this.currentTreeHash,
      newTreeHash: this.newTreeHash,
      release: this.latest
    }
  }
}

/**
 * Walk a tree from the root, fetching each subtree and materializing
 * content into the store. Inlined here so pgh-runtime has no dependency
 * on the pgh CLI package — any store + fetcher that conform to the
 * expected shapes work.
 *
 * @param {object} store   — TreeStore-compatible
 * @param {string} rootHash
 * @param {object} fetcher — { getTree, getContent }
 * @param {object} [opts]
 * @returns {Promise<{ materialized: number, skipped: number }>}
 */
export async function installTree (store, rootHash, fetcher, opts = {}) {
  store.init()

  if (store.hasTree(rootHash)) {
    return { materialized: 0, skipped: 1 }
  }

  const tree = await fetcher.getTree(rootHash)
  if (!tree || !Array.isArray(tree.entries)) {
    throw new Error('fetcher returned invalid tree for ' + rootHash)
  }

  // Content-address verification: entries MUST hash to rootHash. A P2P peer
  // can answer with empty/wrong entries to signal "I don't have this tree";
  // without this check that silently materializes an empty/partial dir. Let
  // the caller retry on another peer (EHASHMISMATCH). Matches pgh's installTree.
  const actualRootHash = computeTreeHash(tree.entries)
  if (actualRootHash !== rootHash) {
    throw Object.assign(
      new Error('root tree-hash mismatch: expected ' + rootHash.slice(0, 12) + '…, got ' + actualRootHash.slice(0, 12) + '… (peer may not have this tree)'),
      { code: 'EHASHMISMATCH', expected: rootHash, actual: actualRootHash }
    )
  }

  // Single self-contained materialize — store.materialize recursively inlines
  // subtree content via fetchTree/fetchContent. Subtree hashes do NOT get their
  // own dependencies/<subhash>/ dir (that would duplicate every file N times
  // across levels); dedup happens at the top-level tree layer. `skipPath` skips
  // foreign subtrees (e.g. by-arch/<other-host>/). Mirrors pgh's tree-install.js
  // — the manual subtree recursion this replaced created redundant store dirs
  // and could not honor skipPath.
  await store.materialize(rootHash, tree.entries, {
    fetchContent: fetcher.getContent.bind(fetcher),
    fetchTree: async (h) => {
      const t = await fetcher.getTree(h)
      return t.entries
    },
    source: opts.source,
    skipPath: opts.skipPath
  })

  return { materialized: 1, skipped: 0 }
}

/**
 * Install a full RELEASE — the root tree PLUS its dependency closure.
 *
 * `installTree` fetches a single tree and its nested subdirectories, but an
 * app's node_modules dependencies are SEPARATE content-addressed trees listed
 * in the release's `deps` array (each with its own tree hash and a per-host
 * prebuild tree). Fetching only the root tree therefore yields a BROKEN
 * install — the deps are never fetched and node_modules is never wired. (This
 * is exactly why pear-git-v3 constructs a PghRuntime but refuses to call
 * checkNow(): "pgh-runtime only fetches root trees, not dep closures, so using
 * it here would produce a broken install.")
 *
 * installRelease closes that gap:
 *   1. fetch the FULL closure (root + every dep.tree + prebuilds[host]) into
 *      the store via installTree, then
 *   2. hand the closure to a materializer that wires the install layout
 *      (hardlink deps + symlink node_modules).
 *
 * The materializer is INJECTED (`opts.materialize`) — same dependency-injection
 * philosophy as fetcher/store — so pgh-runtime stays free of any dependency on
 * the pgh CLI's materializer. pgh's node materializer
 * (`materialize({ store, appName, rootTreeHash, deps, host })`) is the canonical
 * one; any function with that shape works. Without a materializer the closure
 * is fetched into the store but the install layout is not wired (store-only).
 *
 * @param {object} store     TreeStore-compatible (init / hasTree / materialize)
 * @param {object} release   { treeHash, deps?: [{ name, version, tree, prebuilds }], name? }
 * @param {object} fetcher   { getTree, getContent }
 * @param {object} [opts]    { host?, appName?, materialize?, source? }
 * @returns {Promise<{ closure: number, materialized: number, skipped: number, install: any }>}
 */
export async function installRelease (store, release, fetcher, opts = {}) {
  if (!release || !release.treeHash) {
    throw new Error('installRelease requires release.treeHash')
  }
  store.init()

  const host = opts.host || null
  const deps = Array.isArray(release.deps) ? release.deps : []

  // Build the full closure: root tree + each dep's tree + its per-host prebuild.
  const closureHashes = [release.treeHash]
  for (const dep of deps) {
    if (dep.tree) closureHashes.push(dep.tree)
    if (host && dep.prebuilds && dep.prebuilds[host]) closureHashes.push(dep.prebuilds[host])
  }

  const source = opts.source || ('pgh-runtime:' + (release.name || 'app'))
  let materialized = 0
  let skipped = 0
  for (const hash of closureHashes) {
    const r = await installTree(store, hash, fetcher, { source, skipPath: opts.skipPath })
    materialized += r.materialized
    skipped += r.skipped
  }

  let install = null
  if (typeof opts.materialize === 'function') {
    install = await opts.materialize({
      store,
      appName: opts.appName || release.name,
      rootTreeHash: release.treeHash,
      deps,
      host
    })
  }

  return { closure: closureHashes.length, materialized, skipped, install }
}
const errors = require('./lib/errors')

class EventListener {
  constructor() {
    this.list = []
    this.count = 0
  }

  append(ctx, name, fn, once) {
    this.count++
    ctx.emit('newListener', name, fn) // Emit BEFORE adding
    this.list.push([fn, once])
  }

  prepend(ctx, name, fn, once) {
    this.count++
    ctx.emit('newListener', name, fn) // Emit BEFORE adding
    this.list.unshift([fn, once])
  }

  remove(ctx, name, fn) {
    for (let i = 0, n = this.list.length; i < n; i++) {
      const l = this.list[i]

      if (l[0] === fn) {
        this.list.splice(i, 1)

        if (this.count === 1) delete ctx._events[name]

        ctx.emit('removeListener', name, fn) // Emit AFTER removing

        this.count--
        return
      }
    }
  }

  removeAll(ctx, name) {
    const list = [...this.list]
    this.list = []

    if (this.count === list.length) delete ctx._events[name]

    for (let i = list.length - 1; i >= 0; i--) {
      ctx.emit('removeListener', name, list[i][0]) // Emit AFTER removing
    }

    this.count -= list.length
  }

  emit(ctx, name, ...args) {
    const list = [...this.list]

    for (let i = 0, n = list.length; i < n; i++) {
      const l = list[i]

      if (l[1] === true) this.remove(ctx, name, l[0])

      Reflect.apply(l[0], ctx, args)
    }

    return list.length > 0
  }
}

function appendListener(ctx, name, fn, once) {
  if (ctx._events === undefined) ctx._events = Object.create(null)
  const e = ctx._events[name] || (ctx._events[name] = new EventListener())
  e.append(ctx, name, fn, once)
  return ctx
}

function prependListener(ctx, name, fn, once) {
  if (ctx._events === undefined) ctx._events = Object.create(null)
  const e = ctx._events[name] || (ctx._events[name] = new EventListener())
  e.prepend(ctx, name, fn, once)
  return ctx
}

function removeListener(ctx, name, fn) {
  if (ctx._events === undefined) return ctx
  const e = ctx._events[name]
  if (e !== undefined) e.remove(ctx, name, fn)
  return ctx
}

function throwUnhandledError(...args) {
  let err

  if (args.length > 0) err = args[0]

  if (err instanceof Error === false) err = errors.UNHANDLED_ERROR(err)

  if (Error.captureStackTrace) {
    Error.captureStackTrace(err, exports.prototype.emit)
  }

  queueMicrotask(() => {
    throw err
  })
}

module.exports = exports = class EventEmitter {
  constructor() {
    this._events = Object.create(null)
  }

  addListener(name, fn) {
    return appendListener(this, name, fn, false)
  }

  addOnceListener(name, fn) {
    return appendListener(this, name, fn, true)
  }

  prependListener(name, fn) {
    return prependListener(this, name, fn, false)
  }

  prependOnceListener(name, fn) {
    return prependListener(this, name, fn, true)
  }

  removeListener(name, fn) {
    return removeListener(this, name, fn)
  }

  on(name, fn) {
    return appendListener(this, name, fn, false)
  }

  once(name, fn) {
    return appendListener(this, name, fn, true)
  }

  off(name, fn) {
    return removeListener(this, name, fn)
  }

  emit(name, ...args) {
    if (name === 'error' && this._events !== undefined && this._events.error === undefined) {
      throwUnhandledError(...args)
    }

    if (this._events === undefined) return false
    const e = this._events[name]
    return e === undefined ? false : e.emit(this, name, ...args)
  }

  listeners(name) {
    if (this._events === undefined) return []
    const e = this._events[name]
    return e === undefined ? [] : [...e.list]
  }

  rawListeners(name) {
    if (this._events === undefined) return []
    const e = this._events[name]
    return e === undefined ? [] : e.list.map((l) => l[0])
  }

  eventNames() {
    if (this._events === undefined) return []
    return Reflect.ownKeys(this._events)
  }

  listenerCount(name) {
    if (this._events === undefined) return 0
    const e = this._events[name]
    return e === undefined ? 0 : e.list.length
  }

  getMaxListeners() {
    return EventEmitter.defaultMaxListeners
  }

  setMaxListeners(n) {}

  removeAllListeners(name) {
    if (arguments.length === 0) {
      for (const key of Reflect.ownKeys(this._events)) {
        if (key === 'removeListener') continue
        this.removeAllListeners(key)
      }
      this.removeAllListeners('removeListener')
    } else {
      const e = this._events[name]
      if (e !== undefined) e.removeAll(this, name)
    }
    return this
  }
}

exports.EventEmitter = exports

exports.errors = errors

exports.defaultMaxListeners = 10

exports.on = function on(emitter, name, opts = {}) {
  const { signal } = opts

  if (signal && signal.aborted) {
    throw errors.OPERATION_ABORTED(signal.reason)
  }

  let error = null
  let done = false

  const events = []
  const promises = []

  if (name !== 'error') emitter.on('error', onerror)

  if (signal) signal.addEventListener('abort', onabort)

  emitter.on(name, onevent)

  return {
    next() {
      if (events.length) {
        return Promise.resolve({ value: events.shift(), done: false })
      }

      if (error) {
        const err = error

        error = null

        return Promise.reject(err)
      }

      if (done) return onclose()

      return new Promise((resolve, reject) => promises.push({ resolve, reject }))
    },

    return() {
      return onclose()
    },

    throw(err) {
      return onerror(err)
    },

    [Symbol.asyncIterator]() {
      return this
    }
  }

  function onevent(...args) {
    if (promises.length) {
      promises.shift().resolve({ value: args, done: false })
    } else {
      events.push(args)
    }
  }

  function onerror(err) {
    emitter.off(name, onevent).off('error', onerror)

    if (promises.length) {
      promises.shift().reject(err)
    } else {
      error = err
    }

    return Promise.resolve({ done: true })
  }

  function onabort() {
    signal.removeEventListener('abort', onabort)

    onerror(errors.OPERATION_ABORTED(signal.reason))
  }

  function onclose() {
    emitter.off(name, onevent)

    if (name !== 'error') emitter.off('error', onerror)

    if (signal) signal.removeEventListener('abort', onabort)

    done = true

    if (promises.length) promises.shift().resolve({ done: true })

    return Promise.resolve({ done: true })
  }
}

exports.once = function once(emitter, name, opts = {}) {
  const { signal } = opts

  if (signal && signal.aborted) {
    return Promise.reject(errors.OPERATION_ABORTED(signal.reason))
  }

  return new Promise((resolve, reject) => {
    if (name !== 'error') emitter.on('error', onerror)

    if (signal) signal.addEventListener('abort', onabort)

    emitter.once(name, onevent)

    function onevent(...args) {
      if (name !== 'error') emitter.off('error', onerror)

      if (signal) signal.removeEventListener('abort', onabort)

      resolve(args)
    }

    function onerror(err) {
      emitter.off(name, onevent)

      if (name !== 'error') emitter.off('error', onerror)

      reject(err)
    }

    function onabort() {
      signal.removeEventListener('abort', onabort)

      onerror(errors.OPERATION_ABORTED(signal.reason))
    }
  })
}

exports.forward = function forward(from, to, names, opts = {}) {
  if (typeof names === 'string') names = [names]

  const { emit = to.emit.bind(to) } = opts

  const listeners = names.map(
    (name) =>
      function onevent(...args) {
        emit(name, ...args)
      }
  )

  to.on('newListener', (name) => {
    const i = names.indexOf(name)

    if (i !== -1 && to.listenerCount(name) === 0) {
      from.on(name, listeners[i])
    }
  }).on('removeListener', (name) => {
    const i = names.indexOf(name)

    if (i !== -1 && to.listenerCount(name) === 0) {
      from.off(name, listeners[i])
    }
  })
}

exports.listenerCount = function listenerCount(emitter, name) {
  return emitter.listenerCount(name)
}

exports.getMaxListeners = function getMaxListeners(emitter) {
  if (typeof emitter.getMaxListeners === 'function') {
    return emitter.getMaxListeners()
  }

  return exports.defaultMaxListeners
}

exports.setMaxListeners = function setMaxListeners(n, ...emitters) {
  if (emitters.length === 0) exports.defaultMaxListeners = n
  else {
    for (const emitter of emitters) {
      if (typeof emitter.setMaxListeners === 'function') {
        emitter.setMaxListeners(n)
      }
    }
  }
}
module.exports = class EventEmitterError extends Error {
  constructor(msg, code, fn = EventEmitterError, opts) {
    super(`${code}: ${msg}`, opts)
    this.code = code

    if (Error.captureStackTrace) {
      Error.captureStackTrace(this, fn)
    }
  }

  get name() {
    return 'EventEmitterError'
  }

  static OPERATION_ABORTED(cause, msg = 'Operation aborted') {
    return new EventEmitterError(msg, 'OPERATION_ABORTED', EventEmitterError.OPERATION_ABORTED, {
      cause
    })
  }

  static UNHANDLED_ERROR(cause, msg = 'Unhandled error') {
    return new EventEmitterError(msg, 'UNHANDLED_ERROR', EventEmitterError.UNHANDLED_ERROR, {
      cause
    })
  }
}
{
  "name": "bare-events",
  "version": "2.9.1",
  "description": "Event emitters for JavaScript",
  "exports": {
    "./package": "./package.json",
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    },
    "./global": {
      "types": "./global.d.ts",
      "default": "./global.js"
    },
    "./web": {
      "types": "./web.d.ts",
      "default": "./web.js"
    },
    "./errors": "./lib/errors.js"
  },
  "files": [
    "index.js",
    "index.d.ts",
    "global.js",
    "global.d.ts",
    "web.js",
    "web.d.ts",
    "lib"
  ],
  "scripts": {
    "format": "prettier --write . && lunte --fix",
    "lint": "prettier --check . && lunte",
    "test": "npm run test:bare && npm run test:node",
    "test:bare": "brittle-bare --coverage test.js",
    "test:node": "brittle-node --coverage test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-events.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-events/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-events#readme",
  "devDependencies": {
    "bare-abort-controller": "^1.0.0",
    "brittle": "^3.3.2",
    "lunte": "^1.8.0",
    "prettier": "^3.4.2",
    "prettier-config-holepunch": "^2.0.0",
    "uncaughts": "^1.1.1"
  },
  "peerDependencies": {
    "bare-abort-controller": "*"
  },
  "peerDependenciesMeta": {
    "bare-abort-controller": {
      "optional": true
    }
  }
}
/**
 * Tree — content-addressable directory hashing for hyper-overlay
 *
 * A tree is a canonical serialization of a directory's immediate entries.
 * Each entry is one of: file, subdirectory, symlink. The tree's identity
 * is blake2b-256 of its canonical bytes.
 *
 * Format (P.10 Phase 0.5a):
 *
 *   tree := "pgh-tree-1\n" ||
 *           varint(n_entries) ||
 *           entry[0] || ... || entry[n-1]
 *
 *   entry := u8(kind)  ||           // 0=file, 1=dir, 2=symlink
 *            u8(exec)  ||           // 0 or 1; always 0 for dir/symlink
 *            varint(name_len) || name ||  // UTF-8 NFC, no NUL, no "/"
 *            32_bytes(child_hash)
 *
 *   tree_hash := blake2b-256(tree_bytes)
 *
 * Identity fields (in the hash): kind, exec-bit, name, child_hash.
 * Explicitly excluded: mtime, size, uid/gid, full mode.
 *
 * Entries MUST be sorted byte-wise by name (after NFC normalization).
 * Encoders emit sorted; decoders reject unsorted trees.
 *
 * Symlinks: child_hash = blake2b-256 of the target path bytes (not followed).
 * Empty directories: zero-entry tree, valid, deterministic.
 */

import fs from '#fs'
import path from '#path'
import crypto from 'hypercore-crypto'
import c from 'compact-encoding'

// ── Constants ─────────────────────────────────────────────────

export const MAGIC = Buffer.from('pgh-tree-1\n', 'utf8')
export const KIND_FILE = 0
export const KIND_DIR = 1
export const KIND_SYMLINK = 2

// Windows reserved device names (case-insensitive, with or without extension)
const WINDOWS_RESERVED = new Set([
  'CON', 'PRN', 'AUX', 'NUL',
  'COM1', 'COM2', 'COM3', 'COM4', 'COM5', 'COM6', 'COM7', 'COM8', 'COM9',
  'LPT1', 'LPT2', 'LPT3', 'LPT4', 'LPT5', 'LPT6', 'LPT7', 'LPT8', 'LPT9'
])

// Chars forbidden in filenames on Windows (beyond format-layer rules)
// eslint-disable-next-line no-control-regex
const WINDOWS_FORBIDDEN = /[\\:*?"<>|\x00-\x1f]/

// Default skip list for walkTree — minimal, just obvious platform cruft.
// Consumers that want more (e.g. .git for pgh publish) pass their own.
export const DEFAULT_SKIP = new Set(['.DS_Store', 'Thumbs.db'])

// ── Name validation (format layer) ────────────────────────────

/**
 * Normalize a filename for hashing and validate format-layer rules.
 * Throws on unrepresentable names. Returns the canonical form.
 *
 * @param {string} name - single path component (no "/")
 * @returns {string} NFC-normalized name
 */
export function normalizeName (name) {
  if (typeof name !== 'string') {
    throw new Error('tree entry name must be string, got ' + typeof name)
  }
  if (name.length === 0) {
    throw new Error('tree entry name cannot be empty')
  }
  if (name === '.' || name === '..') {
    throw new Error('tree entry name cannot be "." or ".."')
  }
  if (name.indexOf('/') !== -1) {
    throw new Error('tree entry name cannot contain "/": ' + JSON.stringify(name))
  }
  if (name.indexOf('\x00') !== -1) {
    throw new Error('tree entry name cannot contain NUL: ' + JSON.stringify(name))
  }

  const normalized = name.normalize('NFC')

  // Verify the normalized form is valid UTF-8 that round-trips through
  // Buffer (catches lone surrogates and other malformed sequences).
  const asBytes = Buffer.from(normalized, 'utf8')
  if (asBytes.toString('utf8') !== normalized) {
    throw new Error('tree entry name is not valid UTF-8: ' + JSON.stringify(name))
  }

  return normalized
}

// ── Consumer-policy layer ─────────────────────────────────────

/**
 * Check a name against Windows compatibility rules.
 * Does NOT throw — returns a reason string (or null if the name is OK).
 * Used by pgh publish for strict cross-platform validation.
 *
 * @param {string} name
 * @returns {string|null} reason if incompatible, null if OK
 */
export function windowsIncompatibleReason (name) {
  if (WINDOWS_FORBIDDEN.test(name)) {
    return 'contains Windows-forbidden character (\\:*?"<>| or control code)'
  }
  const trimmed = name.replace(/\.+$/, '').replace(/ +$/, '')
  if (trimmed !== name) {
    return 'trailing "." or space (Windows strips these silently)'
  }
  // Reserved device names apply with any extension: AUX.log, PRN.txt etc.
  const base = name.split('.')[0].toUpperCase()
  if (WINDOWS_RESERVED.has(base)) {
    return `"${base}" is a reserved Windows device name (even with extension)`
  }
  return null
}

// ── Canonical encoding ────────────────────────────────────────

/**
 * Encode a single entry to its canonical byte representation.
 *
 * @param {object} state - compact-encoding state
 * @param {{kind: number, exec: number, name: string, childHash: Buffer|string}} e
 */
function encodeEntry (state, e) {
  if (e.kind !== KIND_FILE && e.kind !== KIND_DIR && e.kind !== KIND_SYMLINK) {
    throw new Error('invalid entry kind: ' + e.kind)
  }
  if (e.exec !== 0 && e.exec !== 1) {
    throw new Error('entry exec must be 0 or 1')
  }
  if (e.kind !== KIND_FILE && e.exec !== 0) {
    throw new Error('non-file entry must have exec=0')
  }

  const hash = toBuffer32(e.childHash)

  c.uint8.encode(state, e.kind)
  c.uint8.encode(state, e.exec)
  c.string.encode(state, e.name) // compact-encoding: varint len + utf8 bytes
  state.buffer.set(hash, state.start)
  state.start += 32
}

function preencodeEntry (state, e) {
  c.uint8.preencode(state, e.kind)
  c.uint8.preencode(state, e.exec)
  c.string.preencode(state, e.name)
  state.end += 32
}

/**
 * Serialize a list of entries to canonical tree bytes.
 * Entries are sorted + normalized as a side-effect of the call.
 *
 * @param {Array<{kind: number, exec: number, name: string, childHash: Buffer|string}>} entries
 * @returns {Buffer}
 */
export function encodeTree (entries) {
  const normalized = entries.map(e => ({
    kind: e.kind,
    exec: e.exec || 0,
    name: normalizeName(e.name),
    childHash: e.childHash
  }))
  normalized.sort(compareEntries)

  // Sanity: no duplicate names after normalization
  for (let i = 1; i < normalized.length; i++) {
    if (normalized[i].name === normalized[i - 1].name) {
      throw new Error('duplicate entry name after NFC normalization: ' + normalized[i].name)
    }
  }

  const state = { start: 0, end: 0, buffer: null }
  state.end += MAGIC.length
  c.uint.preencode(state, normalized.length)
  for (const e of normalized) preencodeEntry(state, e)

  state.buffer = Buffer.alloc(state.end)

  state.buffer.set(MAGIC, state.start)
  state.start += MAGIC.length
  c.uint.encode(state, normalized.length)
  for (const e of normalized) encodeEntry(state, e)

  return state.buffer
}

/**
 * Byte-wise comparator for entry names (after NFC normalization).
 */
function compareEntries (a, b) {
  const ab = Buffer.from(a.name, 'utf8')
  const bb = Buffer.from(b.name, 'utf8')
  return Buffer.compare(ab, bb)
}

/**
 * Decode canonical tree bytes back to an entries list.
 * Rejects unsorted or malformed trees.
 *
 * @param {Buffer} buf
 * @returns {Array<{kind: number, exec: number, name: string, childHash: string}>}
 */
export function decodeTree (buf) {
  if (!Buffer.isBuffer(buf)) throw new Error('decodeTree expects a Buffer')
  if (buf.length < MAGIC.length) throw new Error('tree too short')
  if (buf.compare(MAGIC, 0, MAGIC.length, 0, MAGIC.length) !== 0) {
    throw new Error('tree magic mismatch (expected "pgh-tree-1")')
  }

  const state = { start: MAGIC.length, end: buf.length, buffer: buf }
  const count = c.uint.decode(state)
  const entries = []

  let prevName = null
  for (let i = 0; i < count; i++) {
    const kind = c.uint8.decode(state)
    const exec = c.uint8.decode(state)
    const name = c.string.decode(state)

    if (state.start + 32 > state.end) throw new Error('truncated tree: missing hash bytes')
    const childHash = buf.subarray(state.start, state.start + 32).toString('hex')
    state.start += 32

    // Validate format rules on read — reject invalid trees rather than silently accept
    const normalized = normalizeName(name)
    if (normalized !== name) {
      throw new Error('tree entry name not NFC-normalized: ' + JSON.stringify(name))
    }
    if (kind !== KIND_FILE && exec !== 0) {
      throw new Error('non-file entry with exec=1 is invalid')
    }
    if (prevName !== null) {
      const cmp = Buffer.compare(Buffer.from(prevName, 'utf8'), Buffer.from(name, 'utf8'))
      if (cmp === 0) throw new Error('tree has duplicate entry: ' + name)
      if (cmp > 0) throw new Error('tree entries not sorted: ' + prevName + ' before ' + name)
    }
    prevName = name

    entries.push({ kind, exec, name, childHash })
  }

  if (state.start !== state.end) throw new Error('tree has trailing bytes after entries')

  return entries
}

/**
 * Compute the tree hash of an entries list.
 *
 * @param {Array<{kind: number, exec: number, name: string, childHash: Buffer|string}>} entries
 * @returns {string} hex-encoded blake2b-256
 */
export function computeTreeHash (entries) {
  const buf = encodeTree(entries)
  return crypto.data(buf).toString('hex')
}

// ── Helpers ───────────────────────────────────────────────────

function toBuffer32 (hashOrBuf) {
  if (Buffer.isBuffer(hashOrBuf)) {
    if (hashOrBuf.length !== 32) throw new Error('childHash buffer must be 32 bytes')
    return hashOrBuf
  }
  if (typeof hashOrBuf !== 'string') {
    throw new Error('childHash must be Buffer or hex string')
  }
  if (hashOrBuf.length !== 64) throw new Error('childHash hex string must be 64 chars')
  return Buffer.from(hashOrBuf, 'hex')
}

// ── Filesystem walk ───────────────────────────────────────────

/**
 * Walk a directory recursively, producing tree hashes bottom-up.
 *
 * Returns a nested object keyed by path, where each entry has:
 *   { hash: hex, entries: [{kind, exec, name, childHash, size?}] }
 *
 * File entries carry their content hash as childHash (blake2b of bytes).
 * Dir entries carry the subtree hash.
 * Symlink entries carry blake2b of the link target bytes.
 *
 * @param {string} rootDir - absolute path to scan
 * @param {object} [opts]
 * @param {Set<string>} [opts.skip] - basenames to skip (default: DEFAULT_SKIP)
 * @param {(relPath: string, baseName: string) => boolean} [opts.matcher]
 *   Optional gitignore-style matcher. Returns true to EXCLUDE an entry.
 *   Receives the path relative to rootDir plus the basename. Applies to
 *   all kinds (file/dir/symlink). Used by `pgh publish` for publishIgnore.
 * @returns {Map<string, {hash: string, entries: Array, size: number}>}
 *          map keyed by relative path ('' = root); root tree is at key ''
 */
export function walkTree (rootDir, opts = {}) {
  const skip = opts.skip || DEFAULT_SKIP
  const matcher = opts.matcher || null
  const trees = new Map()

  function visit (relPath) {
    const absPath = relPath === '' ? rootDir : path.join(rootDir, relPath)
    const raw = fs.readdirSync(absPath)
    const entries = []
    let totalSize = 0

    for (const name of raw) {
      if (skip.has(name)) continue

      const entryAbs = path.join(absPath, name)
      const entryRel = relPath === '' ? name : relPath + '/' + name

      if (matcher && matcher(entryRel, name)) continue

      let stat
      try {
        stat = fs.lstatSync(entryAbs)
      } catch {
        continue
      }

      if (stat.isSymbolicLink()) {
        const target = fs.readlinkSync(entryAbs)
        const targetBytes = Buffer.from(target, 'utf8')
        const childHash = crypto.data(targetBytes).toString('hex')
        entries.push({
          kind: KIND_SYMLINK,
          exec: 0,
          name: normalizeName(name),
          childHash,
          size: targetBytes.length,
          linkTarget: target
        })
        totalSize += targetBytes.length
      } else if (stat.isDirectory()) {
        visit(entryRel)
        const sub = trees.get(entryRel)
        if (!sub) continue
        entries.push({
          kind: KIND_DIR,
          exec: 0,
          name: normalizeName(name),
          childHash: sub.hash,
          size: sub.size
        })
        totalSize += sub.size
      } else if (stat.isFile()) {
        const content = fs.readFileSync(entryAbs)
        const childHash = crypto.data(content).toString('hex')
        // Executable bit from stat.mode — works on POSIX; Windows always 0
        const exec = (stat.mode & 0o100) ? 1 : 0
        entries.push({
          kind: KIND_FILE,
          exec,
          name: normalizeName(name),
          childHash,
          size: stat.size
        })
        totalSize += stat.size
      }
      // device files, sockets, fifos: silently skipped (unrepresentable)
    }

    entries.sort(compareEntries)
    const hash = computeTreeHash(entries)
    trees.set(relPath, { hash, entries, size: totalSize })
  }

  visit('')
  return trees
}

// ── Publish-time validation (consumer policy) ─────────────────

/**
 * Scan entries (recursively via walkTree output) for cross-platform-hostile
 * names. Returns an array of {path, name, reason} for every problem found.
 * Does NOT throw — caller decides how to present.
 *
 * @param {Map<string, {hash: string, entries: Array}>} trees - from walkTree
 * @returns {Array<{path: string, name: string, reason: string}>}
 */
export function validateForPublish (trees) {
  const problems = []
  for (const [relPath, tree] of trees) {
    for (const e of tree.entries) {
      const reason = windowsIncompatibleReason(e.name)
      if (reason) {
        problems.push({
          path: relPath ? relPath + '/' + e.name : e.name,
          name: e.name,
          reason
        })
      }
    }
  }
  return problems
}
function isBuffer(value) {
  return Buffer.isBuffer(value) || value instanceof Uint8Array
}

function isEncoding(encoding) {
  return Buffer.isEncoding(encoding)
}

function alloc(size, fill, encoding) {
  return Buffer.alloc(size, fill, encoding)
}

function allocUnsafe(size) {
  return Buffer.allocUnsafe(size)
}

function allocUnsafeSlow(size) {
  return Buffer.allocUnsafeSlow(size)
}

function byteLength(string, encoding) {
  return Buffer.byteLength(string, encoding)
}

function compare(a, b) {
  return Buffer.compare(a, b)
}

function concat(buffers, totalLength) {
  return Buffer.concat(buffers, totalLength)
}

function copy(source, target, targetStart, start, end) {
  return toBuffer(source).copy(target, targetStart, start, end)
}

function equals(a, b) {
  return toBuffer(a).equals(b)
}

function fill(buffer, value, offset, end, encoding) {
  return toBuffer(buffer).fill(value, offset, end, encoding)
}

function from(value, encodingOrOffset, length) {
  return Buffer.from(value, encodingOrOffset, length)
}

function includes(buffer, value, byteOffset, encoding) {
  return toBuffer(buffer).includes(value, byteOffset, encoding)
}

function indexOf(buffer, value, byfeOffset, encoding) {
  return toBuffer(buffer).indexOf(value, byfeOffset, encoding)
}

function lastIndexOf(buffer, value, byteOffset, encoding) {
  return toBuffer(buffer).lastIndexOf(value, byteOffset, encoding)
}

function swap16(buffer) {
  return toBuffer(buffer).swap16()
}

function swap32(buffer) {
  return toBuffer(buffer).swap32()
}

function swap64(buffer) {
  return toBuffer(buffer).swap64()
}

function toBuffer(buffer) {
  if (Buffer.isBuffer(buffer)) return buffer
  return Buffer.from(buffer.buffer, buffer.byteOffset, buffer.byteLength)
}

function toString(buffer, encoding, start, end) {
  return toBuffer(buffer).toString(encoding, start, end)
}

function write(buffer, string, offset, length, encoding) {
  return toBuffer(buffer).write(string, offset, length, encoding)
}

function readDoubleBE(buffer, offset) {
  return toBuffer(buffer).readDoubleBE(offset)
}

function readDoubleLE(buffer, offset) {
  return toBuffer(buffer).readDoubleLE(offset)
}

function readFloatBE(buffer, offset) {
  return toBuffer(buffer).readFloatBE(offset)
}

function readFloatLE(buffer, offset) {
  return toBuffer(buffer).readFloatLE(offset)
}

function readInt32BE(buffer, offset) {
  return toBuffer(buffer).readInt32BE(offset)
}

function readInt32LE(buffer, offset) {
  return toBuffer(buffer).readInt32LE(offset)
}

function readUInt32BE(buffer, offset) {
  return toBuffer(buffer).readUInt32BE(offset)
}

function readUInt32LE(buffer, offset) {
  return toBuffer(buffer).readUInt32LE(offset)
}

function writeDoubleBE(buffer, value, offset) {
  return toBuffer(buffer).writeDoubleBE(value, offset)
}

function writeDoubleLE(buffer, value, offset) {
  return toBuffer(buffer).writeDoubleLE(value, offset)
}

function writeFloatBE(buffer, value, offset) {
  return toBuffer(buffer).writeFloatBE(value, offset)
}

function writeFloatLE(buffer, value, offset) {
  return toBuffer(buffer).writeFloatLE(value, offset)
}

function writeInt32BE(buffer, value, offset) {
  return toBuffer(buffer).writeInt32BE(value, offset)
}

function writeInt32LE(buffer, value, offset) {
  return toBuffer(buffer).writeInt32LE(value, offset)
}

function writeUInt32BE(buffer, value, offset) {
  return toBuffer(buffer).writeUInt32BE(value, offset)
}

function writeUInt32LE(buffer, value, offset) {
  return toBuffer(buffer).writeUInt32LE(value, offset)
}

module.exports = {
  isBuffer,
  isEncoding,
  alloc,
  allocUnsafe,
  allocUnsafeSlow,
  byteLength,
  compare,
  concat,
  copy,
  equals,
  fill,
  from,
  includes,
  indexOf,
  lastIndexOf,
  swap16,
  swap32,
  swap64,
  toBuffer,
  toString,
  write,
  readDoubleBE,
  readDoubleLE,
  readFloatBE,
  readFloatLE,
  readInt32BE,
  readInt32LE,
  readUInt32BE,
  readUInt32LE,
  writeDoubleBE,
  writeDoubleLE,
  writeFloatBE,
  writeFloatLE,
  writeInt32BE,
  writeInt32LE,
  writeUInt32BE,
  writeUInt32LE
}
{
  "name": "b4a",
  "version": "1.8.1",
  "description": "Bridging the gap between buffers and typed arrays",
  "exports": {
    "./package": "./package.json",
    ".": {
      "react-native": "./react-native.js",
      "browser": "./browser.js",
      "default": "./index.js"
    }
  },
  "files": [
    "browser.js",
    "index.js",
    "react-native.js",
    "lib"
  ],
  "scripts": {
    "format": "prettier --write . && lunte --fix",
    "lint": "prettier --check . && lunte",
    "test": "npm run test:bare && npm run test:node",
    "test:bare": "brittle-bare --coverage test.mjs",
    "test:node": "brittle-node --coverage test.mjs"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/b4a.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/b4a/issues"
  },
  "homepage": "https://github.com/holepunchto/b4a#readme",
  "devDependencies": {
    "brittle": "^3.5.2",
    "lunte": "^1.7.0",
    "nanobench": "^3.0.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^2.0.0"
  },
  "peerDependencies": {
    "react-native-b4a": "*"
  },
  "peerDependenciesMeta": {
    "react-native-b4a": {
      "optional": true
    }
  }
}
const ESC = '\x1b'
const CSI = ESC + '['
const SGR = (n) => CSI + n + 'm'

exports.constants = {
  ESC,
  CSI,
  SGR
}

// https://en.wikipedia.org/wiki/ANSI_escape_code#CSI_(Control_Sequence_Introducer)_sequences

exports.cursorHide = CSI + '?25l'
exports.cursorShow = CSI + '?25h'

exports.cursorUp = function cursorUp(n = 1) {
  return CSI + n + 'A'
}

exports.cursorDown = function cursorDown(n = 1) {
  return CSI + n + 'B'
}

exports.cursorForward = function cursorForward(n = 1) {
  return CSI + n + 'C'
}

exports.cursorBack = function cursorBack(n = 1) {
  return CSI + n + 'D'
}

exports.cursorNextLine = function cursorNextLine(n = 1) {
  return CSI + n + 'E'
}

exports.cursorPreviousLine = function cursorPreviousLine(n = 1) {
  return CSI + n + 'F'
}

exports.cursorPosition = function cursorPosition(column, row = 0) {
  if (row === 0) return CSI + (column + 1) + 'G'

  return CSI + (row + 1) + ';' + (column + 1) + 'H'
}

exports.eraseDisplayEnd = CSI + 'J'
exports.eraseDisplayStart = CSI + '1J'
exports.eraseDisplay = CSI + '2J'
exports.eraseLineEnd = CSI + 'K'
exports.eraseLineStart = CSI + '1K'
exports.eraseLine = CSI + '2K'

exports.scrollUp = function scrollUp(n = 1) {
  return CSI + n + 'S'
}

exports.scrollDown = function scrollDown(n = 1) {
  return CSI + n + 'T'
}

// https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_(Select_Graphic_Rendition)_parameters

exports.modifierReset = SGR(0)
exports.modifierBold = SGR(1)
exports.modifierDim = SGR(2)
exports.modifierItalic = SGR(3)
exports.modifierUnderline = SGR(4)
exports.modifierNormal = SGR(22)
exports.modifierNotItalic = SGR(23)
exports.modifierNotUnderline = SGR(24)

// https://en.wikipedia.org/wiki/ANSI_escape_code#Colors

exports.colorBlack = SGR(30)
exports.colorRed = SGR(31)
exports.colorGreen = SGR(32)
exports.colorYellow = SGR(33)
exports.colorBlue = SGR(34)
exports.colorMagenta = SGR(35)
exports.colorCyan = SGR(36)
exports.colorWhite = SGR(37)
exports.colorDefault = SGR(39)
exports.colorBrightBlack = SGR(90)
exports.colorBrightRed = SGR(91)
exports.colorBrightGreen = SGR(92)
exports.colorBrightYellow = SGR(93)
exports.colorBrightBlue = SGR(94)
exports.colorBrightMagenta = SGR(95)
exports.colorBrightCyan = SGR(96)
exports.colorBrightWhite = SGR(97)
{
  "name": "bare-ansi-escapes",
  "version": "2.2.3",
  "description": "Parse and produce ANSI escape sequences",
  "exports": {
    "./package": "./package.json",
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    },
    "./key-decoder": {
      "types": "./key-decoder.d.ts",
      "default": "./key-decoder.js"
    }
  },
  "files": [
    "index.js",
    "index.d.ts",
    "key-decoder.js",
    "key-decoder.d.ts"
  ],
  "scripts": {
    "test": "prettier . --check && bare test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-ansi-escapes.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-ansi-escapes/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-ansi-escapes#readme",
  "dependencies": {
    "bare-stream": "^2.6.5"
  },
  "devDependencies": {
    "bare-buffer": "^3.0.2",
    "brittle": "^3.3.2",
    "prettier": "^3.4.2",
    "prettier-config-standard": "^7.0.0"
  },
  "peerDependencies": {
    "bare-buffer": "*"
  },
  "peerDependenciesMeta": {
    "bare-buffer": {
      "optional": true
    }
  }
}
const inspect = require('bare-inspect')

class AssertionError extends Error {
  constructor(opts = {}) {
    let { message = null, actual, expected, operator } = opts

    if (message === null) {
      message = `${inspect(actual)} ${operator} ${inspect(expected)}`
    }

    super(message)

    this.actual = actual
    this.expected = expected
    this.operator = operator
  }

  get name() {
    return 'AssertionError'
  }

  get code() {
    'ASSERTION'
  }
}

function assertFail(opts, fn) {
  if (opts.message instanceof Error) throw opts.message

  const err = new AssertionError(opts)

  if (Error.captureStackTrace) Error.captureStackTrace(err, fn)

  throw err
}

module.exports = exports = function assert(actual, message) {
  if (actual) return

  assertFail({ message, actual, expected: true, operator: '==' }, assert)
}

exports.AssertionError = AssertionError

exports.fail = function fail(message) {
  if (message === undefined) message = 'Failed'

  assertFail({ message, operator: 'fail' }, fail)
}

exports.ok = function ok(actual, message) {
  if (actual) return

  assertFail({ message, actual, expected: true, operator: '==' }, ok)
}

exports.notOk = function ok(actual, message) {
  if (!actual) return

  assertFail({ message, actual, expected: false, operator: '==' }, ok)
}

exports.equal = function equal(actual, expected, message) {
  if (actual == expected || (actual !== actual && expected !== expected)) {
    return
  }

  assertFail({ message, actual, expected, operator: '==' }, equal)
}

exports.notEqual = function notEqual(actual, expected, message) {
  if (actual != expected && (actual === actual || expected === expected)) {
    return
  }

  assertFail({ message, actual, expected, operator: '!=' }, notEqual)
}

exports.strictEqual = function strictEqual(actual, expected, message) {
  if (Object.is(actual, expected)) return

  assertFail(
    { message, actual, expected, operator: 'strictEqual' },
    strictEqual
  )
}

exports.notStrictEqual = function notStrictEqual(actual, expected, message) {
  if (!Object.is(actual, expected)) return

  assertFail(
    { message, actual, expected, operator: 'notStrictEqual' },
    notStrictEqual
  )
}
{
  "name": "bare-assert",
  "version": "1.2.0",
  "description": "Assertion library for JavaScript",
  "exports": {
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    },
    "./package": "./package.json"
  },
  "files": [
    "index.js",
    "index.d.ts"
  ],
  "scripts": {
    "test": "prettier . --check && bare test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-assert.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-assert/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-assert#readme",
  "dependencies": {
    "bare-inspect": "^3.1.2"
  },
  "devDependencies": {
    "brittle": "^3.1.1",
    "prettier": "^3.4.2",
    "prettier-config-standard": "^7.0.0"
  }
}
const errors = require('./lib/errors')

class EventListener {
  constructor() {
    this.list = []
    this.count = 0
  }

  append(ctx, name, fn, once) {
    this.count++
    ctx.emit('newListener', name, fn) // Emit BEFORE adding
    this.list.push([fn, once])
  }

  prepend(ctx, name, fn, once) {
    this.count++
    ctx.emit('newListener', name, fn) // Emit BEFORE adding
    this.list.unshift([fn, once])
  }

  remove(ctx, name, fn) {
    for (let i = 0, n = this.list.length; i < n; i++) {
      const l = this.list[i]

      if (l[0] === fn) {
        this.list.splice(i, 1)

        if (this.count === 1) delete ctx._events[name]

        ctx.emit('removeListener', name, fn) // Emit AFTER removing

        this.count--
        return
      }
    }
  }

  removeAll(ctx, name) {
    const list = [...this.list]
    this.list = []

    if (this.count === list.length) delete ctx._events[name]

    for (let i = list.length - 1; i >= 0; i--) {
      ctx.emit('removeListener', name, list[i][0]) // Emit AFTER removing
    }

    this.count -= list.length
  }

  emit(ctx, name, ...args) {
    const list = [...this.list]

    for (let i = 0, n = list.length; i < n; i++) {
      const l = list[i]

      if (l[1] === true) this.remove(ctx, name, l[0])

      Reflect.apply(l[0], ctx, args)
    }

    return list.length > 0
  }
}

function appendListener(ctx, name, fn, once) {
  if (ctx._events === undefined) ctx._events = Object.create(null)
  const e = ctx._events[name] || (ctx._events[name] = new EventListener())
  e.append(ctx, name, fn, once)
  return ctx
}

function prependListener(ctx, name, fn, once) {
  if (ctx._events === undefined) ctx._events = Object.create(null)
  const e = ctx._events[name] || (ctx._events[name] = new EventListener())
  e.prepend(ctx, name, fn, once)
  return ctx
}

function removeListener(ctx, name, fn) {
  if (ctx._events === undefined) return ctx
  const e = ctx._events[name]
  if (e !== undefined) e.remove(ctx, name, fn)
  return ctx
}

function throwUnhandledError(...args) {
  let err

  if (args.length > 0) err = args[0]

  if (err instanceof Error === false) err = errors.UNHANDLED_ERROR(err)

  if (Error.captureStackTrace) {
    Error.captureStackTrace(err, exports.prototype.emit)
  }

  queueMicrotask(() => {
    throw err
  })
}

module.exports = exports = class EventEmitter {
  constructor() {
    this._events = Object.create(null)
  }

  addListener(name, fn) {
    return appendListener(this, name, fn, false)
  }

  addOnceListener(name, fn) {
    return appendListener(this, name, fn, true)
  }

  prependListener(name, fn) {
    return prependListener(this, name, fn, false)
  }

  prependOnceListener(name, fn) {
    return prependListener(this, name, fn, true)
  }

  removeListener(name, fn) {
    return removeListener(this, name, fn)
  }

  on(name, fn) {
    return appendListener(this, name, fn, false)
  }

  once(name, fn) {
    return appendListener(this, name, fn, true)
  }

  off(name, fn) {
    return removeListener(this, name, fn)
  }

  emit(name, ...args) {
    if (name === 'error' && this._events !== undefined && this._events.error === undefined) {
      throwUnhandledError(...args)
    }

    if (this._events === undefined) return false
    const e = this._events[name]
    return e === undefined ? false : e.emit(this, name, ...args)
  }

  listeners(name) {
    if (this._events === undefined) return []
    const e = this._events[name]
    return e === undefined ? [] : [...e.list]
  }

  rawListeners(name) {
    if (this._events === undefined) return []
    const e = this._events[name]
    return e === undefined ? [] : e.list.map((l) => l[0])
  }

  eventNames() {
    if (this._events === undefined) return []
    return Reflect.ownKeys(this._events)
  }

  listenerCount(name) {
    if (this._events === undefined) return 0
    const e = this._events[name]
    return e === undefined ? 0 : e.list.length
  }

  getMaxListeners() {
    return EventEmitter.defaultMaxListeners
  }

  setMaxListeners(n) {}

  removeAllListeners(name) {
    if (arguments.length === 0) {
      for (const key of Reflect.ownKeys(this._events)) {
        if (key === 'removeListener') continue
        this.removeAllListeners(key)
      }
      this.removeAllListeners('removeListener')
    } else {
      const e = this._events[name]
      if (e !== undefined) e.removeAll(this, name)
    }
    return this
  }
}

exports.EventEmitter = exports

exports.errors = errors

exports.defaultMaxListeners = 10

exports.on = function on(emitter, name, opts = {}) {
  const { signal } = opts

  if (signal && signal.aborted) {
    throw errors.OPERATION_ABORTED(signal.reason)
  }

  let error = null
  let done = false

  const events = []
  const promises = []

  if (name !== 'error') emitter.on('error', onerror)

  if (signal) signal.addEventListener('abort', onabort)

  emitter.on(name, onevent)

  return {
    next() {
      if (events.length) {
        return Promise.resolve({ value: events.shift(), done: false })
      }

      if (error) {
        const err = error

        error = null

        return Promise.reject(err)
      }

      if (done) return onclose()

      return new Promise((resolve, reject) => promises.push({ resolve, reject }))
    },

    return() {
      return onclose()
    },

    throw(err) {
      return onerror(err)
    },

    [Symbol.asyncIterator]() {
      return this
    }
  }

  function onevent(...args) {
    if (promises.length) {
      promises.shift().resolve({ value: args, done: false })
    } else {
      events.push(args)
    }
  }

  function onerror(err) {
    emitter.off(name, onevent).off('error', onerror)

    if (promises.length) {
      promises.shift().reject(err)
    } else {
      error = err
    }

    return Promise.resolve({ done: true })
  }

  function onabort() {
    signal.removeEventListener('abort', onabort)

    onerror(errors.OPERATION_ABORTED(signal.reason))
  }

  function onclose() {
    emitter.off(name, onevent)

    if (name !== 'error') emitter.off('error', onerror)

    if (signal) signal.removeEventListener('abort', onabort)

    done = true

    if (promises.length) promises.shift().resolve({ done: true })

    return Promise.resolve({ done: true })
  }
}

exports.once = function once(emitter, name, opts = {}) {
  const { signal } = opts

  if (signal && signal.aborted) {
    return Promise.reject(errors.OPERATION_ABORTED(signal.reason))
  }

  return new Promise((resolve, reject) => {
    if (name !== 'error') emitter.on('error', onerror)

    if (signal) signal.addEventListener('abort', onabort)

    emitter.once(name, onevent)

    function onevent(...args) {
      if (name !== 'error') emitter.off('error', onerror)

      if (signal) signal.removeEventListener('abort', onabort)

      resolve(args)
    }

    function onerror(err) {
      emitter.off(name, onevent)

      if (name !== 'error') emitter.off('error', onerror)

      reject(err)
    }

    function onabort() {
      signal.removeEventListener('abort', onabort)

      onerror(errors.OPERATION_ABORTED(signal.reason))
    }
  })
}

exports.forward = function forward(from, to, names, opts = {}) {
  if (typeof names === 'string') names = [names]

  const { emit = to.emit.bind(to) } = opts

  const listeners = names.map(
    (name) =>
      function onevent(...args) {
        emit(name, ...args)
      }
  )

  to.on('newListener', (name) => {
    const i = names.indexOf(name)

    if (i !== -1 && to.listenerCount(name) === 0) {
      from.on(name, listeners[i])
    }
  }).on('removeListener', (name) => {
    const i = names.indexOf(name)

    if (i !== -1 && to.listenerCount(name) === 0) {
      from.off(name, listeners[i])
    }
  })
}

exports.listenerCount = function listenerCount(emitter, name) {
  return emitter.listenerCount(name)
}

exports.getMaxListeners = function getMaxListeners(emitter) {
  if (typeof emitter.getMaxListeners === 'function') {
    return emitter.getMaxListeners()
  }

  return exports.defaultMaxListeners
}

exports.setMaxListeners = function setMaxListeners(n, ...emitters) {
  if (emitters.length === 0) exports.defaultMaxListeners = n
  else {
    for (const emitter of emitters) {
      if (typeof emitter.setMaxListeners === 'function') {
        emitter.setMaxListeners(n)
      }
    }
  }
}
module.exports = class EventEmitterError extends Error {
  constructor(msg, code, fn = EventEmitterError, opts) {
    super(`${code}: ${msg}`, opts)
    this.code = code

    if (Error.captureStackTrace) {
      Error.captureStackTrace(this, fn)
    }
  }

  get name() {
    return 'EventEmitterError'
  }

  static OPERATION_ABORTED(cause, msg = 'Operation aborted') {
    return new EventEmitterError(msg, 'OPERATION_ABORTED', EventEmitterError.OPERATION_ABORTED, {
      cause
    })
  }

  static UNHANDLED_ERROR(cause, msg = 'Unhandled error') {
    return new EventEmitterError(msg, 'UNHANDLED_ERROR', EventEmitterError.UNHANDLED_ERROR, {
      cause
    })
  }
}
{
  "name": "bare-events",
  "version": "2.9.1",
  "description": "Event emitters for JavaScript",
  "exports": {
    "./package": "./package.json",
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    },
    "./global": {
      "types": "./global.d.ts",
      "default": "./global.js"
    },
    "./web": {
      "types": "./web.d.ts",
      "default": "./web.js"
    },
    "./errors": "./lib/errors.js"
  },
  "files": [
    "index.js",
    "index.d.ts",
    "global.js",
    "global.d.ts",
    "web.js",
    "web.d.ts",
    "lib"
  ],
  "scripts": {
    "format": "prettier --write . && lunte --fix",
    "lint": "prettier --check . && lunte",
    "test": "npm run test:bare && npm run test:node",
    "test:bare": "brittle-bare --coverage test.js",
    "test:node": "brittle-node --coverage test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-events.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-events/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-events#readme",
  "devDependencies": {
    "bare-abort-controller": "^1.0.0",
    "brittle": "^3.3.2",
    "lunte": "^1.8.0",
    "prettier": "^3.4.2",
    "prettier-config-holepunch": "^2.0.0",
    "uncaughts": "^1.1.1"
  },
  "peerDependencies": {
    "bare-abort-controller": "*"
  },
  "peerDependenciesMeta": {
    "bare-abort-controller": {
      "optional": true
    }
  }
}
module.exports = require.addon()
const FIFO = require('fast-fifo')
const EventEmitter = require('bare-events')
const path = require('bare-path')
const { isURL, fileURLToPath } = require('bare-url')
const { Readable, Writable } = require('bare-stream')
const binding = require('./binding')
const constants = require('./lib/constants')
const FileError = require('./lib/errors')

const isWindows = Bare.platform === 'win32'

exports.constants = constants

class FileRequest {
  static borrow() {
    if (this._free.length > 0) return this._free.pop()
    return new FileRequest()
  }

  static return(req) {
    if (this._free.length < 32) this._free.push(req.reset())
    else req.destroy()
  }

  constructor() {
    this._reset()
    this._handle = binding.requestInit(this, this._onresult)
  }

  get handle() {
    return this._handle
  }

  retain(value) {
    this._retain = value // Tie the lifetime of `value` to the lifetime of `this`
  }

  reset() {
    if (this._handle === null) return this

    binding.requestReset(this._handle)

    this._reset()

    return this
  }

  destroy() {
    if (this._handle === null) return this

    binding.requestDestroy(this._handle)

    this._reset()
    this._handle = null

    return this
  }

  then(resolve, reject) {
    return this._promise.then(resolve, reject)
  }

  return() {
    if (this._handle === null) return this

    FileRequest.return(this)

    return this
  }

  _reset() {
    this._promise = new Promise((resolve, reject) => {
      this._resolve = resolve
      this._reject = reject
    })
    this._retain = null
  }

  _onresult(err, status) {
    if (err) this._reject(err)
    else this._resolve(status)
  }
}

FileRequest._free = []

function ok(result, cb) {
  if (typeof result === 'function') {
    cb = result
    result = undefined
  }

  if (cb) cb(null, result)
  else return result
}

function fail(err, cb) {
  if (cb) cb(err)
  else throw err
}

function done(err, result, cb) {
  if (typeof result === 'function') {
    cb = result
    result = undefined
  }

  if (err) fail(err, cb)
  else return ok(result, cb)
}

async function open(filepath, flags = 'r', mode = 0o666, cb) {
  if (typeof flags === 'function') {
    cb = flags
    flags = 'r'
    mode = 0o666
  } else if (typeof mode === 'function') {
    cb = mode
    mode = 0o666
  }

  if (typeof flags === 'string') flags = toFlags(flags)
  if (typeof mode === 'string') mode = toMode(mode)

  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  let fd
  let err = null
  try {
    binding.open(req.handle, filepath, flags, mode)

    fd = await req
  } catch (e) {
    err = new FileError(e.message, {
      operation: 'open',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }

  return done(err, fd, cb)
}

function openSync(filepath, flags = 'r', mode = 0o666) {
  if (typeof flags === 'string') flags = toFlags(flags)
  if (typeof mode === 'string') mode = toMode(mode)

  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  try {
    return binding.openSync(req.handle, filepath, flags, mode)
  } catch (e) {
    throw new FileError(e.message, {
      operation: 'open',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }
}

async function close(fd, cb) {
  const req = FileRequest.borrow()

  let err = null
  try {
    binding.close(req.handle, fd)

    await req
  } catch (e) {
    err = new FileError(e.message, { operation: 'close', code: e.code, fd })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function closeSync(fd) {
  const req = FileRequest.borrow()

  try {
    binding.closeSync(req.handle, fd)
  } catch (e) {
    throw new FileError(e.message, { operation: 'close', code: e.code, fd })
  } finally {
    req.return()
  }
}

async function access(filepath, mode = constants.F_OK, cb) {
  if (typeof mode === 'function') {
    cb = mode
    mode = constants.F_OK
  }

  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  let err = null
  try {
    binding.access(req.handle, filepath, mode)

    await req
  } catch (e) {
    err = new FileError(e.message, {
      operation: 'access',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function accessSync(filepath, mode = constants.F_OK) {
  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  try {
    binding.accessSync(req.handle, filepath, mode)
  } catch (e) {
    throw new FileError(e.message, {
      operation: 'access',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }
}

async function exists(filepath, cb) {
  let ok = true
  try {
    await access(filepath)
  } catch {
    ok = false
  }

  return done(null, ok, cb)
}

function existsSync(filepath) {
  try {
    accessSync(filepath)
  } catch {
    return false
  }

  return true
}

async function read(fd, buffer, offset = 0, len = buffer.byteLength - offset, pos = -1, cb) {
  if (typeof offset === 'function') {
    cb = offset
    offset = 0
    len = buffer.byteLength
    pos = -1
  } else if (typeof len === 'function') {
    cb = len
    len = buffer.byteLength - offset
    pos = -1
  } else if (typeof pos === 'function') {
    cb = pos
    pos = -1
  }

  if (typeof pos !== 'number') pos = -1

  const req = FileRequest.borrow()

  let bytes
  let err = null
  try {
    binding.read(req.handle, fd, buffer, offset, len, pos)

    req.retain(buffer)

    bytes = await req
  } catch (e) {
    err = new FileError(e.message, { operation: 'read', code: e.code, fd })
  } finally {
    req.return()
  }

  return done(err, bytes, cb)
}

function readSync(fd, buffer, offset = 0, len = buffer.byteLength - offset, pos = -1) {
  const req = FileRequest.borrow()

  try {
    return binding.readSync(req.handle, fd, buffer, offset, len, pos)
  } catch (e) {
    throw new FileError(e.message, { operation: 'read', code: e.code, fd })
  } finally {
    req.return()
  }
}

async function readv(fd, buffers, pos = -1, cb) {
  if (typeof pos === 'function') {
    cb = pos
    pos = -1
  }

  if (typeof pos !== 'number') pos = -1

  const req = FileRequest.borrow()

  let bytes
  let err = null
  try {
    binding.readv(req.handle, fd, buffers, pos)

    req.retain(buffers)

    bytes = await req
  } catch (e) {
    err = new FileError(e.message, { operation: 'readv', code: e.code, fd })
  } finally {
    req.return()
  }

  return done(err, bytes, cb)
}

function readvSync(fd, buffers, pos = -1) {
  if (typeof pos !== 'number') pos = -1

  const req = FileRequest.borrow()

  try {
    return binding.readvSync(req.handle, fd, buffers, pos)
  } catch (e) {
    throw new FileError(e.message, { operation: 'readv', code: e.code, fd })
  } finally {
    req.return()
  }
}

async function write(fd, data, offset, len, pos = -1, cb) {
  if (typeof data === 'string') {
    let encoding = len
    cb = pos
    pos = offset

    if (typeof pos === 'function') {
      cb = pos
      pos = -1
      encoding = 'utf8'
    } else if (typeof encoding === 'function') {
      cb = encoding
      encoding = 'utf8'
    }

    if (typeof pos === 'string') {
      encoding = pos
      pos = -1
    }

    data = Buffer.from(data, encoding)
    offset = 0
    len = data.byteLength
  } else if (typeof offset === 'function') {
    cb = offset
    offset = 0
    len = data.byteLength
    pos = -1
  } else if (typeof len === 'function') {
    cb = len
    len = data.byteLength - offset
    pos = -1
  } else if (typeof pos === 'function') {
    cb = pos
    pos = -1
  }

  if (typeof offset !== 'number') offset = 0
  if (typeof len !== 'number') len = data.byteLength - offset
  if (typeof pos !== 'number') pos = -1

  const req = FileRequest.borrow()

  let bytes
  let err = null
  try {
    binding.write(req.handle, fd, data, offset, len, pos)

    req.retain(data)

    bytes = await req
  } catch (e) {
    err = new FileError(e.message, { operation: 'write', code: e.code, fd })
  } finally {
    req.return()
  }

  return done(err, bytes, cb)
}

function writeSync(fd, data, offset, len, pos = -1) {
  if (typeof data === 'string') {
    let encoding = len
    pos = offset

    if (typeof pos === 'string') {
      encoding = pos
      pos = -1
    }

    data = Buffer.from(data, encoding)
    offset = 0
    len = data.byteLength
  }

  if (typeof offset !== 'number') offset = 0
  if (typeof len !== 'number') len = data.byteLength - offset
  if (typeof pos !== 'number') pos = -1

  const req = FileRequest.borrow()

  try {
    return binding.writeSync(req.handle, fd, data, offset, len, pos)
  } catch (e) {
    throw new FileError(e.message, { operation: 'write', code: e.code, fd })
  } finally {
    req.return()
  }
}

async function writev(fd, buffers, pos = -1, cb) {
  if (typeof pos === 'function') {
    cb = pos
    pos = -1
  }

  if (typeof pos !== 'number') pos = -1

  const req = FileRequest.borrow()

  let bytes
  let err = null
  try {
    binding.writev(req.handle, fd, buffers, pos)

    req.retain(buffers)

    bytes = await req
  } catch (e) {
    err = new FileError(e.message, { operation: 'writev', code: e.code, fd })
  } finally {
    req.return()
  }

  return done(err, bytes, cb)
}

function writevSync(fd, buffers, pos = -1) {
  if (typeof pos !== 'number') pos = -1

  const req = FileRequest.borrow()

  try {
    return binding.writevSync(req.handle, fd, buffers, pos)
  } catch (e) {
    throw new FileError(e.message, { operation: 'writev', code: e.code, fd })
  } finally {
    req.return()
  }
}

async function stat(filepath, cb) {
  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  let st
  let err = null
  try {
    binding.stat(req.handle, filepath)

    await req

    st = new Stats(...binding.requestResultStat(req.handle))
  } catch (e) {
    err = new FileError(e.message, { operation: 'stat', code: e.code, path: filepath })
  } finally {
    req.return()
  }

  return done(err, st, cb)
}

function statSync(filepath) {
  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  try {
    binding.statSync(req.handle, filepath)

    return new Stats(...binding.requestResultStat(req.handle))
  } catch (e) {
    throw new FileError(e.message, { operation: 'stat', code: e.code, path: filepath })
  } finally {
    req.return()
  }
}

async function lstat(filepath, cb) {
  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  let st
  let err = null
  try {
    binding.lstat(req.handle, filepath)

    await req

    st = new Stats(...binding.requestResultStat(req.handle))
  } catch (e) {
    err = new FileError(e.message, { operation: 'lstat', code: e.code, path: filepath })
  } finally {
    req.return()
  }

  return done(err, st, cb)
}

function lstatSync(filepath) {
  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  try {
    binding.lstatSync(req.handle, filepath)

    return new Stats(...binding.requestResultStat(req.handle))
  } catch (e) {
    throw new FileError(e.message, { operation: 'lstat', code: e.code, path: filepath })
  } finally {
    req.return()
  }
}

async function fstat(fd, cb) {
  const req = FileRequest.borrow()

  let st
  let err = null
  try {
    binding.fstat(req.handle, fd)

    await req

    st = new Stats(...binding.requestResultStat(req.handle))
  } catch (e) {
    err = new FileError(e.message, { operation: 'fstat', code: e.code, fd })
  } finally {
    req.return()
  }

  return done(err, st, cb)
}

function fstatSync(fd) {
  const req = FileRequest.borrow()

  try {
    binding.fstatSync(req.handle, fd)

    return new Stats(...binding.requestResultStat(req.handle))
  } catch (e) {
    throw new FileError(e.message, { operation: 'fstat', code: e.code, fd })
  } finally {
    req.return()
  }
}

async function statfs(filepath, cb) {
  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  let st
  let err = null
  try {
    binding.statfs(req.handle, filepath)

    await req

    st = new StatFs(...binding.requestResultStatfs(req.handle))
  } catch (e) {
    err = new FileError(e.message, { operation: 'statfs', code: e.code, path: filepath })
  } finally {
    req.return()
  }

  return done(err, st, cb)
}

function statfsSync(filepath) {
  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  try {
    binding.statfsSync(req.handle, filepath)

    return new StatFs(...binding.requestResultStatfs(req.handle))
  } catch (e) {
    throw new FileError(e.message, { operation: 'statfs', code: e.code, path: filepath })
  } finally {
    req.return()
  }
}

async function ftruncate(fd, len = 0, cb) {
  if (typeof len === 'function') {
    cb = len
    len = 0
  }

  if (typeof len !== 'number') len = 0

  const req = FileRequest.borrow()

  let err = null
  try {
    binding.ftruncate(req.handle, fd, len)

    await req
  } catch (e) {
    err = new FileError(e.message, { operation: 'ftruncate', code: e.code, fd })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function ftruncateSync(fd, len = 0) {
  if (typeof len !== 'number') len = 0

  const req = FileRequest.borrow()

  try {
    binding.ftruncateSync(req.handle, fd, len)
  } catch (e) {
    throw new FileError(e.message, { operation: 'ftruncate', code: e.code, fd })
  } finally {
    req.return()
  }
}

async function truncate(filepath, len, cb) {
  let fd = -1
  let err

  try {
    fd = await open(filepath, 'r+')

    await ftruncate(fd, len)
  } catch (e) {
    err = e
  } finally {
    if (fd !== -1) await close(fd)
  }

  return done(err, cb)
}

function truncateSync(filepath, len) {
  let fd = -1
  let err

  try {
    fd = openSync(filepath, 'r+')

    ftruncateSync(fd, len)
  } catch (e) {
    err = e
  } finally {
    if (fd !== -1) closeSync(fd)
  }
}

async function chmod(filepath, mode, cb) {
  if (typeof mode === 'string') mode = toMode(mode)

  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  let err = null
  try {
    binding.chmod(req.handle, filepath, mode)

    await req
  } catch (e) {
    err = new FileError(e.message, {
      operation: 'chmod',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function chmodSync(filepath, mode) {
  if (typeof mode === 'string') mode = toMode(mode)

  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  try {
    binding.chmodSync(req.handle, filepath, mode)
  } catch (e) {
    throw new FileError(e.message, {
      operation: 'chmod',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }
}

async function fchmod(fd, mode, cb) {
  if (typeof mode === 'string') mode = toMode(mode)

  const req = FileRequest.borrow()

  let err = null
  try {
    binding.fchmod(req.handle, fd, mode)

    await req
  } catch (e) {
    err = new FileError(e.message, { operation: 'fchmod', code: e.code, fd })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function fchmodSync(fd, mode) {
  if (typeof mode === 'string') mode = toMode(mode)

  const req = FileRequest.borrow()

  try {
    binding.fchmodSync(req.handle, fd, mode)
  } catch (e) {
    throw new FileError(e.message, { operation: 'fchmod', code: e.code, fd })
  } finally {
    req.return()
  }
}

async function chown(filepath, uid, gid, cb) {
  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  let err = null
  try {
    binding.chown(req.handle, filepath, uid, gid)

    await req
  } catch (e) {
    err = new FileError(e.message, { operation: 'chown', code: e.code, path: filepath })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function chownSync(filepath, uid, gid) {
  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  try {
    binding.chownSync(req.handle, filepath, uid, gid)
  } catch (e) {
    throw new FileError(e.message, { operation: 'chownSync', code: e.code, path: filepath })
  } finally {
    req.return()
  }
}

async function lchown(filepath, uid, gid, cb) {
  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  let err = null
  try {
    binding.lchown(req.handle, filepath, uid, gid)

    await req
  } catch (e) {
    err = new FileError(e.message, { operation: 'lchown', code: e.code, path: filepath })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function lchownSync(filepath, uid, gid) {
  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  try {
    binding.lchownSync(req.handle, filepath, uid, gid)
  } catch (e) {
    throw new FileError(e.message, { operation: 'lchownSync', code: e.code, path: filepath })
  } finally {
    req.return()
  }
}

async function fchown(fd, uid, gid, cb) {
  const req = FileRequest.borrow()

  let err = null
  try {
    binding.fchown(req.handle, fd, uid, gid)

    await req
  } catch (e) {
    err = new FileError(e.message, { operation: 'fchown', code: e.code, fd })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function fchownSync(fd, uid, gid) {
  const req = FileRequest.borrow()

  try {
    binding.fchownSync(req.handle, fd, uid, gid)
  } catch (e) {
    throw new FileError(e.message, { operation: 'fchownSync', code: e.code, fd })
  } finally {
    req.return()
  }
}

async function utimes(filepath, atime, mtime, cb) {
  if (typeof atime !== 'number') atime = atime.getTime() / 1000
  if (typeof mtime !== 'number') mtime = mtime.getTime() / 1000

  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  let err = null
  try {
    binding.utimes(req.handle, filepath, atime, mtime)

    await req
  } catch (e) {
    err = new FileError(e.message, { operation: 'utimes', code: e.code, path: filepath })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function utimesSync(filepath, atime, mtime) {
  if (typeof atime !== 'number') atime = atime.getTime() / 1000
  if (typeof mtime !== 'number') mtime = mtime.getTime() / 1000

  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  try {
    binding.utimesSync(req.handle, filepath, atime, mtime)
  } catch (e) {
    throw new FileError(e.message, { operation: 'utimes', code: e.code, path: filepath })
  } finally {
    req.return()
  }
}

async function lutimes(filepath, atime, mtime, cb) {
  if (typeof atime !== 'number') atime = atime.getTime() / 1000
  if (typeof mtime !== 'number') mtime = mtime.getTime() / 1000

  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  let err = null
  try {
    binding.lutimes(req.handle, filepath, atime, mtime)

    await req
  } catch (e) {
    err = new FileError(e.message, { operation: 'lutimes', code: e.code, path: filepath })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function lutimesSync(filepath, atime, mtime) {
  if (typeof atime !== 'number') atime = atime.getTime() / 1000
  if (typeof mtime !== 'number') mtime = mtime.getTime() / 1000

  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  try {
    binding.lutimesSync(req.handle, filepath, atime, mtime)
  } catch (e) {
    throw new FileError(e.message, { operation: 'lutimes', code: e.code, path: filepath })
  } finally {
    req.return()
  }
}

async function futimes(fd, atime, mtime, cb) {
  if (typeof atime !== 'number') atime = atime.getTime() / 1000
  if (typeof mtime !== 'number') mtime = mtime.getTime() / 1000

  const req = FileRequest.borrow()

  let err = null
  try {
    binding.futimes(req.handle, fd, atime, mtime)

    await req
  } catch (e) {
    err = new FileError(e.message, { operation: 'futimes', code: e.code, fd })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function futimesSync(fd, atime, mtime) {
  if (typeof atime !== 'number') atime = atime.getTime() / 1000
  if (typeof mtime !== 'number') mtime = mtime.getTime() / 1000

  const req = FileRequest.borrow()

  try {
    binding.futimesSync(req.handle, fd, atime, mtime)
  } catch (e) {
    throw new FileError(e.message, { operation: 'futimesSync', code: e.code, fd })
  } finally {
    req.return()
  }
}

async function link(src, dst, cb) {
  src = toNamespacedPath(src)
  dst = toNamespacedPath(dst)

  const req = FileRequest.borrow()

  let err = null
  try {
    binding.link(req.handle, src, dst)

    await req
  } catch (e) {
    err = new FileError(e.message, { operation: 'link', code: e.code, path: src, destination: dst })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function linkSync(src, dst) {
  src = toNamespacedPath(src)
  dst = toNamespacedPath(dst)

  const req = FileRequest.borrow()

  try {
    binding.linkSync(req.handle, src, dst)
  } catch (e) {
    throw new FileError(e.message, {
      operation: 'linkSync',
      code: e.code,
      path: src,
      destination: dst
    })
  } finally {
    req.return()
  }
}

async function mkdir(filepath, opts, cb) {
  if (typeof opts === 'function') {
    cb = opts
    opts = { mode: 0o777 }
  }

  if (typeof opts === 'number') opts = { mode: opts }
  else if (!opts) opts = {}

  const mode = typeof opts.mode === 'number' ? opts.mode : 0o777

  filepath = toNamespacedPath(filepath)

  if (opts.recursive) {
    let err = null
    try {
      try {
        await mkdir(filepath, { mode })
      } catch (err) {
        if (err.code !== 'ENOENT') {
          if (!(await stat(filepath)).isDirectory()) throw err
        } else {
          while (filepath.endsWith(path.sep)) filepath = filepath.slice(0, -1)
          const i = filepath.lastIndexOf(path.sep)
          if (i <= 0) throw err

          await mkdir(filepath.slice(0, i), { mode, recursive: true })

          try {
            await mkdir(filepath, { mode })
          } catch (err) {
            if (!(await stat(filepath)).isDirectory()) throw err
          }
        }
      }
    } catch (e) {
      err = e
    }

    return done(err, cb)
  }

  const req = FileRequest.borrow()

  let err = null
  try {
    binding.mkdir(req.handle, filepath, mode)

    await req
  } catch (e) {
    err = new FileError(e.message, {
      operation: 'mkdir',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function mkdirSync(filepath, opts) {
  if (typeof opts === 'number') opts = { mode: opts }
  else if (!opts) opts = {}

  const mode = typeof opts.mode === 'number' ? opts.mode : 0o777

  filepath = toNamespacedPath(filepath)

  if (opts.recursive) {
    try {
      mkdirSync(filepath, { mode })
    } catch (err) {
      if (err.code !== 'ENOENT') {
        if (!statSync(filepath).isDirectory()) throw err
      } else {
        while (filepath.endsWith(path.sep)) filepath = filepath.slice(0, -1)
        const i = filepath.lastIndexOf(path.sep)
        if (i <= 0) throw err

        mkdirSync(filepath.slice(0, i), { mode, recursive: true })

        try {
          mkdirSync(filepath, { mode })
        } catch (err) {
          if (!statSync(filepath).isDirectory()) throw err
        }
      }
    }

    return
  }

  const req = FileRequest.borrow()

  try {
    binding.mkdirSync(req.handle, filepath, mode)
  } catch (e) {
    throw new FileError(e.message, {
      operation: 'mkdir',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }
}

async function mkdtemp(prefix, cb) {
  prefix = toNamespacedPath(prefix)

  const req = FileRequest.borrow()

  let res
  let err = null
  try {
    binding.mkdtemp(req.handle, prefix + 'XXXXXX')

    await req

    res = binding.requestResultPath(req.handle)
  } catch (e) {
    err = new FileError(e.message, { operation: 'mkdtemp', code: e.code, path: prefix })
  } finally {
    req.return()
  }

  return done(err, res, cb)
}

function mkdtempSync(prefix) {
  prefix = toNamespacedPath(prefix)

  const req = FileRequest.borrow()

  try {
    binding.mkdtempSync(req.handle, prefix + 'XXXXXX')

    return binding.requestResultPath(req.handle)
  } catch (e) {
    throw new FileError(e.message, { operation: 'mkdtempSync', code: e.code, path: prefix })
  } finally {
    req.return()
  }
}

async function rmdir(filepath, cb) {
  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  let err = null
  try {
    binding.rmdir(req.handle, filepath)

    await req
  } catch (e) {
    err = new FileError(e.message, {
      operation: 'rmdir',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function rmdirSync(filepath) {
  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  try {
    binding.rmdirSync(req.handle, filepath)
  } catch (e) {
    throw new FileError(e.message, {
      operation: 'rmdir',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }
}

async function rm(filepath, opts, cb) {
  if (typeof opts === 'function') {
    cb = opts
    opts = {}
  }

  if (!opts) opts = {}

  filepath = toNamespacedPath(filepath)

  let err = null
  try {
    const st = await lstat(filepath)

    if (st.isDirectory()) {
      if (opts.recursive) {
        try {
          await rmdir(filepath)
        } catch (err) {
          if (err.code !== 'ENOTEMPTY') throw err

          const files = await readdir(filepath)

          for (const file of files) {
            await rm(filepath + path.sep + file, opts)
          }

          await rmdir(filepath)
        }
      } else {
        throw new FileError('is a directory', {
          operation: 'rm',
          code: 'EISDIR',
          path: filepath
        })
      }
    } else {
      await unlink(filepath)
    }
  } catch (e) {
    if (e.code !== 'ENOENT' || !opts.force) err = e
  }

  return done(err, cb)
}

function rmSync(filepath, opts) {
  if (!opts) opts = {}

  filepath = toNamespacedPath(filepath)

  try {
    const st = lstatSync(filepath)

    if (st.isDirectory()) {
      if (opts.recursive) {
        try {
          rmdirSync(filepath)
        } catch (err) {
          if (err.code !== 'ENOTEMPTY') throw err

          const files = readdirSync(filepath)

          for (const file of files) {
            rmSync(filepath + path.sep + file, opts)
          }

          rmdirSync(filepath)
        }
      } else {
        throw new FileError('is a directory', {
          operation: 'rm',
          code: 'EISDIR',
          path: filepath
        })
      }
    } else {
      unlinkSync(filepath)
    }
  } catch (err) {
    if (err.code !== 'ENOENT' || !opts.force) throw err
  }
}

async function unlink(filepath, cb) {
  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  let err = null
  try {
    binding.unlink(req.handle, filepath)

    await req
  } catch (e) {
    err = new FileError(e.message, {
      operation: 'unlink',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function unlinkSync(filepath) {
  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  try {
    binding.unlinkSync(req.handle, filepath)
  } catch (e) {
    throw new FileError(e.message, {
      operation: 'unlink',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }
}

async function rename(src, dst, cb) {
  src = toNamespacedPath(src)
  dst = toNamespacedPath(dst)

  const req = FileRequest.borrow()

  let err = null
  try {
    binding.rename(req.handle, src, dst)

    await req
  } catch (e) {
    err = new FileError(e.message, {
      operation: 'rename',
      code: e.code,
      path: src,
      destination: dst
    })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function renameSync(src, dst) {
  src = toNamespacedPath(src)
  dst = toNamespacedPath(dst)

  const req = FileRequest.borrow()

  try {
    binding.renameSync(req.handle, src, dst)
  } catch (e) {
    throw new FileError(e.message, {
      operation: 'rename',
      code: e.code,
      path: src,
      destination: dst
    })
  } finally {
    req.return()
  }
}

async function copyFile(src, dst, mode = 0, cb) {
  if (typeof mode === 'function') {
    cb = mode
    mode = 0
  }

  src = toNamespacedPath(src)
  dst = toNamespacedPath(dst)

  const req = FileRequest.borrow()

  let err = null
  try {
    binding.copyfile(req.handle, src, dst, mode)

    await req
  } catch (e) {
    err = new FileError(e.message, {
      operation: 'copyfile',
      code: e.code,
      path: src,
      destination: dst
    })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function copyFileSync(src, dst, mode = 0) {
  src = toNamespacedPath(src)
  dst = toNamespacedPath(dst)

  const req = FileRequest.borrow()

  try {
    binding.copyfileSync(req.handle, src, dst, mode)
  } catch (e) {
    throw new FileError(e.message, {
      operation: 'copyfile',
      code: e.code,
      path: src,
      destination: dst
    })
  } finally {
    req.return()
  }
}

async function cp(src, dst, opts, cb) {
  if (typeof opts === 'function') {
    cb = opts
    opts = {}
  }

  if (!opts) opts = {}

  src = toNamespacedPath(src)
  dst = toNamespacedPath(dst)

  let err = null
  try {
    const st = await lstat(src)

    if (st.isDirectory()) {
      if (opts.recursive !== true) {
        throw new FileError('is a directory', { operation: 'cp', code: 'EISDIR', path: src })
      }

      try {
        await lstat(dst)
      } catch (e) {
        if (e.code !== 'ENOENT') throw e

        await mkdir(dst, { mode: st.mode, recursive: true })
      }

      const dir = await opendir(src)
      for await (const { name } of dir) {
        await cp(path.join(src, name), path.join(dst, name), opts)
      }
    } else if (st.isFile()) {
      await copyFile(src, dst)
      await chmod(dst, st.mode)
    }
  } catch (e) {
    err = e
  }

  return done(err, cb)
}

function cpSync(src, dst, opts = {}) {
  src = toNamespacedPath(src)
  dst = toNamespacedPath(dst)

  const st = lstatSync(src)

  if (st.isDirectory()) {
    if (opts.recursive !== true) {
      throw new FileError('is a directory', { operation: 'cp', code: 'EISDIR', path: src })
    }

    try {
      lstatSync(dst)
    } catch (e) {
      if (e.code !== 'ENOENT') throw e

      mkdirSync(dst, { mode: st.mode, recursive: true })
    }

    const dir = opendirSync(src)
    for (const { name } of dir) {
      cpSync(path.join(src, name), path.join(dst, name), opts)
    }
  } else if (st.isFile()) {
    copyFileSync(src, dst)
    chmodSync(dst, st.mode)
  }
}

async function realpath(filepath, opts, cb) {
  if (typeof opts === 'function') {
    cb = opts
    opts = {}
  }

  if (typeof opts === 'string') opts = { encoding: opts }
  else if (!opts) opts = {}

  const { encoding = 'utf8' } = opts

  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  let res
  let err = null
  try {
    binding.realpath(req.handle, filepath)

    await req

    res = Buffer.from(binding.requestResultString(req.handle))

    if (encoding !== 'buffer') res = res.toString(encoding)
  } catch (e) {
    err = new FileError(e.message, {
      operation: 'realpath',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }

  return done(err, res, cb)
}

function realpathSync(filepath, opts) {
  if (typeof opts === 'string') opts = { encoding: opts }
  else if (!opts) opts = {}

  const { encoding = 'utf8' } = opts

  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  try {
    binding.realpathSync(req.handle, filepath)

    let res = Buffer.from(binding.requestResultString(req.handle))

    if (encoding !== 'buffer') res = res.toString(encoding)

    return res
  } catch (e) {
    throw new FileError(e.message, {
      operation: 'realpath',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }
}

async function readlink(filepath, opts, cb) {
  if (typeof opts === 'function') {
    cb = opts
    opts = {}
  }

  if (typeof opts === 'string') opts = { encoding: opts }
  else if (!opts) opts = {}

  const { encoding = 'utf8' } = opts

  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  let res
  let err = null
  try {
    binding.readlink(req.handle, filepath)

    await req

    res = Buffer.from(binding.requestResultString(req.handle))

    if (encoding !== 'buffer') res = res.toString(encoding)
  } catch (e) {
    err = new FileError(e.message, {
      operation: 'readlink',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }

  return done(err, res, cb)
}

function readlinkSync(filepath, opts) {
  if (typeof opts === 'string') opts = { encoding: opts }
  else if (!opts) opts = {}

  const { encoding = 'utf8' } = opts

  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  try {
    binding.readlinkSync(req.handle, filepath)

    let res = Buffer.from(binding.requestResultString(req.handle))

    if (encoding !== 'buffer') res = res.toString(encoding)

    return res
  } catch (e) {
    throw new FileError(e.message, {
      operation: 'readlink',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }
}

async function fsync(fd, cb) {
  const req = FileRequest.borrow()

  let err = null
  try {
    binding.fsync(req.handle, fd)

    await req
  } catch (e) {
    err = new FileError(e.message, { operation: 'fsync', code: e.code, fd })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function fsyncSync(fd) {
  const req = FileRequest.borrow()

  try {
    binding.fsyncSync(req.handle, fd)
  } catch (e) {
    throw new FileError(e.message, { operation: 'fsyncSync', code: e.code, fd })
  } finally {
    req.return()
  }
}

async function fdatasync(fd, cb) {
  const req = FileRequest.borrow()

  let err = null
  try {
    binding.fdatasync(req.handle, fd)

    await req
  } catch (e) {
    err = new FileError(e.message, { operation: 'fdatasync', code: e.code, fd })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function fdatasyncSync(fd) {
  const req = FileRequest.borrow()

  try {
    binding.fdatasyncSync(req.handle, fd)
  } catch (e) {
    throw new FileError(e.message, { operation: 'fdatasyncSync', code: e.code, fd })
  } finally {
    req.return()
  }
}

function normalizeSymlinkTarget(target, type, filepath) {
  if (isWindows) {
    if (type === constants.UV_FS_SYMLINK_JUNCTION) target = path.resolve(filepath, '..', target)

    if (path.isAbsolute(target)) return path.toNamespacedPath(target)

    return target.replace(/\//g, path.sep)
  }

  return target
}

async function symlink(target, filepath, type, cb) {
  if (typeof type === 'function') {
    cb = type
    type = null
  }

  filepath = toNamespacedPath(filepath)

  if (typeof type === 'string') {
    switch (type) {
      case 'dir':
        type = constants.UV_FS_SYMLINK_DIR
        break
      case 'junction':
        type = constants.UV_FS_SYMLINK_JUNCTION
        break
      case 'file':
      default:
        type = 0
        break
    }
  } else if (typeof type !== 'number') {
    if (isWindows) {
      target = path.resolve(filepath, '..', target)

      try {
        type = (await stat(target)).isDirectory()
          ? constants.UV_FS_SYMLINK_DIR
          : constants.UV_FS_SYMLINK_JUNCTION
      } catch {
        type = 0
      }
    } else {
      type = 0
    }
  }

  target = normalizeSymlinkTarget(target, type, filepath)

  const req = FileRequest.borrow()

  let err = null
  try {
    binding.symlink(req.handle, target, filepath, type)

    await req
  } catch (e) {
    err = new FileError(e.message, {
      operation: 'symlink',
      code: e.code,
      path: target,
      destination: filepath
    })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function symlinkSync(target, filepath, type) {
  filepath = toNamespacedPath(filepath)

  if (typeof type === 'string') {
    switch (type) {
      case 'dir':
        type = constants.UV_FS_SYMLINK_DIR
        break
      case 'junction':
        type = constants.UV_FS_SYMLINK_JUNCTION
        break
      case 'file':
      default:
        type = 0
        break
    }
  } else if (typeof type !== 'number') {
    if (isWindows) {
      target = path.resolve(filepath, '..', target)

      try {
        type = statSync(target).isDirectory()
          ? constants.UV_FS_SYMLINK_DIR
          : constants.UV_FS_SYMLINK_JUNCTION
      } catch {
        type = 0
      }
    } else {
      type = 0
    }
  }

  target = normalizeSymlinkTarget(target, type, filepath)

  const req = FileRequest.borrow()

  try {
    binding.symlinkSync(req.handle, target, filepath, type)
  } catch (e) {
    throw new FileError(e.message, {
      operation: 'symlink',
      code: e.code,
      path: target,
      destination: filepath
    })
  } finally {
    req.return()
  }
}

async function opendir(filepath, opts, cb) {
  if (typeof opts === 'function') {
    cb = opts
    opts = {}
  }

  if (typeof opts === 'string') opts = { encoding: opts }
  else if (!opts) opts = {}

  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  let dir
  let err = null
  try {
    binding.opendir(req.handle, filepath)

    await req

    dir = new Dir(filepath, binding.requestResultDir(req.handle), opts)
  } catch (e) {
    err = new FileError(e.message, {
      operation: 'opendir',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }

  return done(err, dir, cb)
}

function opendirSync(filepath, opts) {
  if (typeof opts === 'string') opts = { encoding: opts }
  else if (!opts) opts = {}

  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  try {
    binding.opendirSync(req.handle, filepath)

    return new Dir(filepath, binding.requestResultDir(req.handle), opts)
  } catch (e) {
    throw new FileError(e.message, {
      operation: 'opendir',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }
}

async function readdir(filepath, opts, cb) {
  if (typeof opts === 'function') {
    cb = opts
    opts = {}
  }

  if (typeof opts === 'string') opts = { encoding: opts }
  else if (!opts) opts = {}

  const { withFileTypes = false, recursive = false } = opts

  filepath = toNamespacedPath(filepath)

  const queue = [filepath]
  let result = []
  let err = null
  try {
    while (queue.length !== 0) {
      const dir = await opendir(queue.pop())

      for await (const entry of dir) {
        const entryPath = path.join(entry.parentPath, entry.name)

        if (withFileTypes) {
          result.push(entry)
        } else {
          result.push(path.relative(filepath, entryPath))
        }

        if (recursive && entry.isDirectory()) queue.push(entryPath)
      }
    }
  } catch (e) {
    result = []
    err = e
  }

  return done(err, result, cb)
}

function readdirSync(filepath, opts) {
  if (typeof opts === 'string') opts = { encoding: opts }
  else if (!opts) opts = {}

  const { withFileTypes = false, recursive = false } = opts

  filepath = toNamespacedPath(filepath)

  const queue = [filepath]
  const result = []

  while (queue.length !== 0) {
    const dir = opendirSync(queue.pop(), opts)

    for (const entry of dir) {
      const entryPath = path.join(entry.parentPath, entry.name)

      if (withFileTypes) {
        result.push(entry)
      } else {
        result.push(path.relative(filepath, entryPath))
      }

      if (recursive && entry.isDirectory()) queue.push(entryPath)
    }
  }

  return result
}

async function readFile(filepath, opts, cb) {
  if (typeof opts === 'function') {
    cb = opts
    opts = {}
  }

  if (typeof opts === 'string') opts = { encoding: opts }
  else if (!opts) opts = {}

  const { encoding = 'buffer' } = opts

  let fd = -1
  let buffer = null
  let err = null
  try {
    fd = await open(filepath, opts.flag || 'r')

    const st = await fstat(fd)

    let len = 0

    if (st.size === 0) {
      const buffers = []

      while (true) {
        buffer = Buffer.allocUnsafe(8192)
        const r = await read(fd, buffer)
        len += r
        if (r === 0) break
        buffers.push(buffer.subarray(0, r))
      }

      buffer = Buffer.concat(buffers)
    } else {
      buffer = Buffer.allocUnsafe(st.size)

      while (true) {
        const r = await read(fd, len ? buffer.subarray(len) : buffer)
        len += r
        if (r === 0 || len === buffer.byteLength) break
      }

      if (len !== buffer.byteLength) buffer = buffer.subarray(0, len)
    }

    if (encoding !== 'buffer') buffer = buffer.toString(encoding)
  } catch (e) {
    err = e
  } finally {
    if (fd !== -1) await close(fd)
  }

  return done(err, buffer, cb)
}

function readFileSync(filepath, opts) {
  if (typeof opts === 'string') opts = { encoding: opts }
  else if (!opts) opts = {}

  const { encoding = 'buffer' } = opts

  let fd = -1
  try {
    fd = openSync(filepath, opts.flag || 'r')

    const st = fstatSync(fd)

    let buffer
    let len = 0

    if (st.size === 0) {
      const buffers = []

      while (true) {
        buffer = Buffer.allocUnsafe(8192)
        const r = readSync(fd, buffer)
        len += r
        if (r === 0) break
        buffers.push(buffer.subarray(0, r))
      }

      buffer = Buffer.concat(buffers)
    } else {
      buffer = Buffer.allocUnsafe(st.size)

      while (true) {
        const r = readSync(fd, len ? buffer.subarray(len) : buffer)
        len += r
        if (r === 0 || len === buffer.byteLength) break
      }

      if (len !== buffer.byteLength) buffer = buffer.subarray(0, len)
    }

    if (encoding !== 'buffer') buffer = buffer.toString(encoding)

    return buffer
  } finally {
    if (fd !== -1) closeSync(fd)
  }
}

async function writeFile(filepath, data, opts, cb) {
  if (typeof opts === 'function') {
    cb = opts
    opts = {}
  }

  if (typeof opts === 'string') opts = { encoding: opts }
  else if (!opts) opts = {}

  if (typeof data === 'string') data = Buffer.from(data, opts.encoding)

  let fd = -1
  let len = 0
  let err = null
  try {
    fd = await open(filepath, opts.flag || 'w', opts.mode || 0o666)

    while (true) {
      len += await write(fd, len ? data.subarray(len) : data)
      if (len === data.byteLength) break
    }
  } catch (e) {
    err = e
  } finally {
    if (fd !== -1) await close(fd)
  }

  return done(err, len, cb)
}

function writeFileSync(filepath, data, opts) {
  if (typeof opts === 'string') opts = { encoding: opts }
  else if (!opts) opts = {}

  if (typeof data === 'string') data = Buffer.from(data, opts.encoding)

  let fd = -1
  try {
    fd = openSync(filepath, opts.flag || 'w', opts.mode || 0o666)

    let len = 0

    while (true) {
      len += writeSync(fd, len ? data.subarray(len) : data)
      if (len === data.byteLength) break
    }
  } finally {
    if (fd !== -1) closeSync(fd)
  }
}

function appendFile(filepath, data, opts, cb) {
  if (typeof opts === 'function') {
    cb = opts
    opts = {}
  }

  if (typeof opts === 'string') opts = { encoding: opts }
  else if (!opts) opts = {}

  if (!opts.flag) opts = { ...opts, flag: 'a' }

  return writeFile(filepath, data, opts, cb)
}

function appendFileSync(filepath, data, opts) {
  if (typeof opts === 'string') opts = { encoding: opts }
  else if (!opts) opts = {}

  if (!opts.flag) opts = { ...opts, flag: 'a' }

  return writeFileSync(filepath, data, opts)
}

function watch(filepath, opts, cb) {
  if (typeof opts === 'function') {
    cb = opts
    opts = {}
  }

  if (typeof opts === 'string') opts = { encoding: opts }
  else if (!opts) opts = {}

  filepath = toNamespacedPath(filepath)

  return new Watcher(filepath, opts, cb)
}

class Stats {
  constructor(
    dev,
    mode,
    nlink,
    uid,
    gid,
    rdev,
    blksize,
    ino,
    size,
    blocks,
    atimeMs,
    mtimeMs,
    ctimeMs,
    birthtimeMs
  ) {
    this.dev = dev
    this.mode = mode
    this.nlink = nlink
    this.uid = uid
    this.gid = gid
    this.rdev = rdev
    this.blksize = blksize
    this.ino = ino
    this.size = size
    this.blocks = blocks
    this.atimeMs = atimeMs
    this.mtimeMs = mtimeMs
    this.ctimeMs = ctimeMs
    this.birthtimeMs = birthtimeMs
    this.atime = new Date(atimeMs)
    this.mtime = new Date(mtimeMs)
    this.ctime = new Date(ctimeMs)
    this.birthtime = new Date(birthtimeMs)
  }

  isDirectory() {
    return (this.mode & constants.S_IFMT) === constants.S_IFDIR
  }

  isFile() {
    return (this.mode & constants.S_IFMT) === constants.S_IFREG
  }

  isBlockDevice() {
    return (this.mode & constants.S_IFMT) === constants.S_IFBLK
  }

  isCharacterDevice() {
    return (this.mode & constants.S_IFMT) === constants.S_IFCHR
  }

  isFIFO() {
    return (this.mode & constants.S_IFMT) === constants.S_IFIFO
  }

  isSymbolicLink() {
    return (this.mode & constants.S_IFMT) === constants.S_IFLNK
  }

  isSocket() {
    return (this.mode & constants.S_IFMT) === constants.S_IFSOCK
  }
}

class StatFs {
  constructor(type, bsize, blocks, bfree, bavail, files, ffree) {
    this.type = type
    this.bsize = bsize
    this.blocks = blocks
    this.bfree = bfree
    this.bavail = bavail
    this.files = files
    this.ffree = ffree
  }
}

class Dir {
  constructor(path, handle, opts = {}) {
    const { encoding = 'utf8', bufferSize = 32 } = opts

    this.path = path

    this._encoding = encoding
    this._capacity = bufferSize
    this._buffer = new FIFO()
    this._ended = false
    this._handle = handle
  }

  async read(cb) {
    if (this._buffer.length) return ok(this._buffer.shift(), cb)
    if (this._ended) return ok(null, cb)

    const req = FileRequest.borrow()

    let entries
    let err = null
    try {
      req.retain(binding.readdir(req.handle, this._handle, this._capacity))

      await req

      entries = binding.requestResultDirents(req.handle)
    } catch (e) {
      err = new FileError(e.message, {
        operation: 'readdir',
        code: e.code,
        path: this.path
      })
    } finally {
      req.return()
    }

    if (err) return fail(err, cb)

    if (entries.length === 0) {
      this._ended = true

      return ok(null, cb)
    }

    for (const entry of entries) {
      let name = Buffer.from(entry.name)

      if (this._encoding !== 'buffer') name = name.toString(this._encoding)

      this._buffer.push(new Dirent(this.path, name, entry.type))
    }

    return ok(this._buffer.shift(), cb)
  }

  readSync() {
    if (this._buffer.length) return this._buffer.shift()
    if (this._ended) return null

    const req = FileRequest.borrow()

    let entries
    try {
      req.retain(binding.readdirSync(req.handle, this._handle, this._capacity))

      entries = binding.requestResultDirents(req.handle)
    } catch (e) {
      throw new FileError(e.message, {
        operation: 'readdir',
        code: e.code,
        path: this.path
      })
    } finally {
      req.return()
    }

    if (entries.length === 0) {
      this._ended = true

      return null
    }

    for (const entry of entries) {
      let name = Buffer.from(entry.name)

      if (this._encoding !== 'buffer') name = name.toString(this._encoding)

      this._buffer.push(new Dirent(this.path, name, entry.type))
    }

    return this._buffer.shift()
  }

  async close(cb) {
    const req = FileRequest.borrow()

    let err = null
    try {
      binding.closedir(req.handle, this._handle)

      await req
    } catch (e) {
      err = new FileError(e.message, {
        operation: 'closedir',
        code: e.code,
        path: this.path
      })
    } finally {
      req.return()
    }

    this._handle = null

    return done(err, cb)
  }

  closeSync() {
    const req = FileRequest.borrow()

    try {
      binding.closedirSync(req.handle, this._handle)
    } catch (e) {
      throw new FileError(e.message, {
        operation: 'closedir',
        code: e.code,
        path: this.path
      })
    } finally {
      req.return()
    }

    this._handle = null
  }

  [Symbol.dispose]() {
    this.closeSync()
  }

  async [Symbol.asyncDispose]() {
    await this.close()
  }

  *[Symbol.iterator]() {
    while (true) {
      const entry = this.readSync()
      if (entry === null) break
      yield entry
    }

    this.closeSync()
  }

  async *[Symbol.asyncIterator]() {
    while (true) {
      const entry = await this.read()
      if (entry === null) break
      yield entry
    }

    await this.close()
  }
}

class Dirent {
  constructor(parentPath, name, type) {
    this.parentPath = parentPath
    this.name = name
    this.type = type
  }

  isFile() {
    return this.type === constants.UV_DIRENT_FILE
  }

  isDirectory() {
    return this.type === constants.UV_DIRENT_DIR
  }

  isSymbolicLink() {
    return this.type === constants.UV_DIRENT_LINK
  }

  isFIFO() {
    return this.type === constants.UV_DIRENT_FIFO
  }

  isSocket() {
    return this.type === constants.UV_DIRENT_SOCKET
  }

  isCharacterDevice() {
    return this.type === constants.UV_DIRENT_CHAR
  }

  isBlockDevice() {
    return this.type === constants.UV_DIRENT_BLOCK
  }
}

class FileReadStream extends Readable {
  constructor(path, opts = {}) {
    const { eagerOpen = true } = opts

    super({ eagerOpen, ...opts })

    this.path = path
    this.fd = typeof opts.fd === 'number' ? opts.fd : -1
    this.flags = opts.flags || 'r'
    this.mode = opts.mode || 0o666

    this._offset = opts.start || 0
    this._missing = 0

    if (opts.length) {
      this._missing = opts.length
    } else if (typeof opts.end === 'number') {
      this._missing = opts.end - this._offset + 1
    } else {
      this._missing = -1
    }
  }

  async _open(cb) {
    let err

    if (this.fd === -1) {
      err = null
      try {
        this.fd = await open(this.path, this.flags, this.mode)
      } catch (e) {
        err = e
      }

      if (err) return cb(err)
    }

    let st
    err = null
    try {
      st = await fstat(this.fd)
    } catch (e) {
      err = e
    }

    if (err) return cb(err)

    if (this._missing === -1) this._missing = st.size

    if (st.size < this._offset) {
      this._offset = st.size
      this._missing = 0
    } else if (st.size < this._offset + this._missing) {
      this._missing = st.size - this._offset
    }

    cb(null)
  }

  async _read(size) {
    if (this._missing <= 0) return this.push(null)

    const data = Buffer.allocUnsafe(Math.min(this._missing, size))

    let len
    let err = null
    try {
      len = await read(this.fd, data, 0, data.byteLength, this._offset)
    } catch (e) {
      err = e
    }

    if (err) return this.destroy(err)

    if (len === 0) return this.push(null)

    if (this._missing < len) len = this._missing

    this._missing -= len
    this._offset += len

    this.push(data.subarray(0, len))
  }

  async _destroy(err, cb) {
    if (this.fd === -1) return cb(err)

    try {
      await close(this.fd)
    } catch (e) {
      err = err || e
    }

    cb(err)
  }
}

class FileWriteStream extends Writable {
  constructor(path, opts = {}) {
    const { eagerOpen = true } = opts

    super({ eagerOpen, ...opts })

    this.path = path
    this.fd = typeof opts.fd === 'number' ? opts.fd : -1
    this.flags = opts.flags || 'w'
    this.mode = opts.mode || 0o666
  }

  async _open(cb) {
    if (this.fd !== -1) return cb(null)

    let err = null
    try {
      this.fd = await open(this.path, this.flags, this.mode)
    } catch (e) {
      err = e
    }

    cb(err)
  }

  async _writev(batch, cb) {
    let err = null
    try {
      await writev(
        this.fd,
        batch.map(({ chunk }) => chunk)
      )
    } catch (e) {
      err = e
    }

    cb(err)
  }

  async _destroy(err, cb) {
    if (this.fd === -1) return cb(err)

    try {
      await close(this.fd)
    } catch (e) {
      err = err || e
    }

    cb(err)
  }
}

class Watcher extends EventEmitter {
  constructor(path, opts, onchange) {
    if (typeof opts === 'function') {
      onchange = opts
      opts = {}
    }

    if (!opts) opts = {}

    const { persistent = true, recursive = false, encoding = 'utf8' } = opts

    super()

    this._closed = false
    this._encoding = encoding
    this._handle = binding.watcherInit(path, recursive, this, this._onevent, this._onclose)

    if (!persistent) this.unref()

    if (onchange) this.on('change', onchange)
  }

  close() {
    if (this._closed) return
    this._closed = true

    binding.watcherClose(this._handle)
  }

  ref() {
    if (this._handle) binding.watcherRef(this._handle)
    return this
  }

  unref() {
    if (this._handle) binding.watcherUnref(this._handle)
    return this
  }

  [Symbol.asyncIterator]() {
    const buffer = []
    let done = false
    let error = null
    let next = null

    this.on('change', (eventType, filename) => {
      if (next) {
        next.resolve({ done: false, value: { eventType, filename } })
        next = null
      } else {
        buffer.push({ eventType, filename })
      }
    })
      .on('error', (err) => {
        done = true
        error = err

        if (next) {
          next.reject(error)
          next = null
        }
      })
      .on('close', () => {
        done = true

        if (next) {
          next.resolve({ done })
          next = null
        }
      })

    return {
      next: () =>
        new Promise((resolve, reject) => {
          if (error) return reject(error)

          if (buffer.length) return resolve({ done: false, value: buffer.shift() })

          if (done) return resolve({ done })

          next = { resolve, reject }
        })
    }
  }

  _onevent(err, events, filename) {
    if (err) {
      this.close()
      this.emit('error', err)
    } else {
      const path =
        this._encoding === 'buffer'
          ? Buffer.from(filename)
          : Buffer.from(filename).toString(this._encoding)

      if (events & binding.UV_RENAME) {
        this.emit('change', 'rename', path)
      }

      if (events & binding.UV_CHANGE) {
        this.emit('change', 'change', path)
      }
    }
  }

  _onclose() {
    this._handle = null

    this.emit('close')
  }
}

exports.access = access
exports.appendFile = appendFile
exports.chmod = chmod
exports.chown = chown
exports.close = close
exports.copyFile = copyFile
exports.cp = cp
exports.exists = exists
exports.fchmod = fchmod
exports.fchown = fchown
exports.fdatasync = fdatasync
exports.fstat = fstat
exports.fsync = fsync
exports.ftruncate = ftruncate
exports.futimes = futimes
exports.lchown = lchown
exports.lutimes = lutimes
exports.link = link
exports.lstat = lstat
exports.mkdir = mkdir
exports.mkdtemp = mkdtemp
exports.open = open
exports.opendir = opendir
exports.read = read
exports.readFile = readFile
exports.readdir = readdir
exports.readlink = readlink
exports.readv = readv
exports.realpath = realpath
exports.rename = rename
exports.rm = rm
exports.rmdir = rmdir
exports.stat = stat
exports.statfs = statfs
exports.symlink = symlink
exports.truncate = truncate
exports.unlink = unlink
exports.utimes = utimes
exports.watch = watch
exports.write = write
exports.writeFile = writeFile
exports.writev = writev

exports.accessSync = accessSync
exports.appendFileSync = appendFileSync
exports.chmodSync = chmodSync
exports.chownSync = chownSync
exports.closeSync = closeSync
exports.copyFileSync = copyFileSync
exports.cpSync = cpSync
exports.existsSync = existsSync
exports.fchmodSync = fchmodSync
exports.fchownSync = fchownSync
exports.fdatasyncSync = fdatasyncSync
exports.fstatSync = fstatSync
exports.fsyncSync = fsyncSync
exports.ftruncateSync = ftruncateSync
exports.futimesSync = futimesSync
exports.lchownSync = lchownSync
exports.lutimesSync = lutimesSync
exports.linkSync = linkSync
exports.lstatSync = lstatSync
exports.mkdirSync = mkdirSync
exports.mkdtempSync = mkdtempSync
exports.openSync = openSync
exports.opendirSync = opendirSync
exports.readFileSync = readFileSync
exports.readSync = readSync
exports.readdirSync = readdirSync
exports.readlinkSync = readlinkSync
exports.readvSync = readvSync
exports.realpathSync = realpathSync
exports.renameSync = renameSync
exports.rmSync = rmSync
exports.rmdirSync = rmdirSync
exports.statSync = statSync
exports.statfsSync = statfsSync
exports.symlinkSync = symlinkSync
exports.truncateSync = truncateSync
exports.unlinkSync = unlinkSync
exports.utimesSync = utimesSync
exports.writeFileSync = writeFileSync
exports.writeSync = writeSync
exports.writevSync = writevSync

exports.promises = require('./promises')

exports.Stats = Stats
exports.StatFs = StatFs
exports.Dir = Dir
exports.Dirent = Dirent
exports.Watcher = Watcher

exports.ReadStream = FileReadStream

exports.createReadStream = function createReadStream(path, opts) {
  return new FileReadStream(path, opts)
}

exports.WriteStream = FileWriteStream

exports.createWriteStream = function createWriteStream(path, opts) {
  return new FileWriteStream(path, opts)
}

function toNamespacedPath(filepath) {
  if (typeof filepath !== 'string') {
    if (isURL(filepath)) filepath = fileURLToPath(filepath)
    else filepath = filepath.toString()
  }

  return path.toNamespacedPath(filepath)
}

function toFlags(flags) {
  switch (flags) {
    case 'r':
      return constants.O_RDONLY
    case 'rs': // Fall through.
    case 'sr':
      return constants.O_RDONLY | constants.O_SYNC
    case 'r+':
      return constants.O_RDWR
    case 'rs+': // Fall through.
    case 'sr+':
      return constants.O_RDWR | constants.O_SYNC

    case 'w':
      return constants.O_TRUNC | constants.O_CREAT | constants.O_WRONLY
    case 'wx': // Fall through.
    case 'xw':
      return constants.O_TRUNC | constants.O_CREAT | constants.O_WRONLY | constants.O_EXCL

    case 'w+':
      return constants.O_TRUNC | constants.O_CREAT | constants.O_RDWR
    case 'wx+': // Fall through.
    case 'xw+':
      return constants.O_TRUNC | constants.O_CREAT | constants.O_RDWR | constants.O_EXCL

    case 'a':
      return constants.O_APPEND | constants.O_CREAT | constants.O_WRONLY
    case 'ax': // Fall through.
    case 'xa':
      return constants.O_APPEND | constants.O_CREAT | constants.O_WRONLY | constants.O_EXCL
    case 'as': // Fall through.
    case 'sa':
      return constants.O_APPEND | constants.O_CREAT | constants.O_WRONLY | constants.O_SYNC

    case 'a+':
      return constants.O_APPEND | constants.O_CREAT | constants.O_RDWR
    case 'ax+': // Fall through.
    case 'xa+':
      return constants.O_APPEND | constants.O_CREAT | constants.O_RDWR | constants.O_EXCL
    case 'as+': // Fall through.
    case 'sa+':
      return constants.O_APPEND | constants.O_CREAT | constants.O_RDWR | constants.O_SYNC
    default:
      return 0
  }
}

function toMode(mode) {
  return parseInt(mode, 8)
}
const binding = require('../binding')

module.exports = {
  O_RDWR: binding.O_RDWR,
  O_RDONLY: binding.O_RDONLY,
  O_WRONLY: binding.O_WRONLY,
  O_CREAT: binding.O_CREAT,
  O_TRUNC: binding.O_TRUNC,
  O_APPEND: binding.O_APPEND,

  F_OK: binding.F_OK || 0,
  R_OK: binding.R_OK || 0,
  W_OK: binding.W_OK || 0,
  X_OK: binding.X_OK || 0,

  S_IFMT: binding.S_IFMT,
  S_IFREG: binding.S_IFREG,
  S_IFDIR: binding.S_IFDIR,
  S_IFCHR: binding.S_IFCHR,
  S_IFLNK: binding.S_IFLNK,
  S_IFBLK: binding.S_IFBLK || 0,
  S_IFIFO: binding.S_IFIFO || 0,
  S_IFSOCK: binding.S_IFSOCK || 0,

  S_IRUSR: binding.S_IRUSR || 0,
  S_IWUSR: binding.S_IWUSR || 0,
  S_IXUSR: binding.S_IXUSR || 0,
  S_IRGRP: binding.S_IRGRP || 0,
  S_IWGRP: binding.S_IWGRP || 0,
  S_IXGRP: binding.S_IXGRP || 0,
  S_IROTH: binding.S_IROTH || 0,
  S_IWOTH: binding.S_IWOTH || 0,
  S_IXOTH: binding.S_IXOTH || 0,

  UV_DIRENT_UNKNOWN: binding.UV_DIRENT_UNKNOWN,
  UV_DIRENT_FILE: binding.UV_DIRENT_FILE,
  UV_DIRENT_DIR: binding.UV_DIRENT_DIR,
  UV_DIRENT_LINK: binding.UV_DIRENT_LINK,
  UV_DIRENT_FIFO: binding.UV_DIRENT_FIFO,
  UV_DIRENT_SOCKET: binding.UV_DIRENT_SOCKET,
  UV_DIRENT_CHAR: binding.UV_DIRENT_CHAR,
  UV_DIRENT_BLOCK: binding.UV_DIRENT_BLOCK,

  COPYFILE_EXCL: binding.UV_FS_COPYFILE_EXCL,
  COPYFILE_FICLONE: binding.UV_FS_COPYFILE_FICLONE,
  COPYFILE_FICLONE_FORCE: binding.UV_FS_COPYFILE_FICLONE_FORCE,
  UV_FS_SYMLINK_DIR: binding.UV_FS_SYMLINK_DIR,
  UV_FS_SYMLINK_JUNCTION: binding.UV_FS_SYMLINK_JUNCTION
}
const os = require('bare-os')

module.exports = class FileError extends Error {
  constructor(msg, opts = {}) {
    const { code, operation = null, path = null, destination = null, fd = -1 } = opts

    if (operation !== null) msg += describe(operation, opts)

    super(`${code}: ${msg}`)

    this.code = code

    if (operation !== null) this.operation = operation
    if (path !== null) this.path = path
    if (destination !== null) this.destination = destination
    if (fd !== -1) this.fd = fd
  }

  get name() {
    return 'FileError'
  }

  // For Node.js compatibility
  get errno() {
    return os.constants.errnos[this.code]
  }

  // For Node.js compatibility
  get syscall() {
    return this.operation
  }

  // For Node.js compatibility
  get dest() {
    return this.destination
  }
}

function describe(operation, opts) {
  const { path = null, destination = null, fd = -1 } = opts

  let result = `, ${operation}`

  if (path !== null) {
    result += ` ${JSON.stringify(path)}`

    if (destination !== null) {
      result += ` -> ${JSON.stringify(destination)}`
    }
  } else if (fd !== -1) {
    result += ` ${fd}`
  }

  return result
}
{
  "name": "bare-fs",
  "version": "4.7.2",
  "description": "Native file system operations for Bare",
  "exports": {
    "./package": "./package.json",
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    },
    "./promises": {
      "types": "./promises.d.ts",
      "default": "./promises.js"
    },
    "./constants": {
      "types": "./lib/constants.d.ts",
      "default": "./lib/constants.js"
    }
  },
  "files": [
    "index.js",
    "index.d.ts",
    "promises.js",
    "promises.d.ts",
    "binding.c",
    "binding.js",
    "CMakeLists.txt",
    "lib",
    "prebuilds"
  ],
  "addon": true,
  "scripts": {
    "format": "prettier --write . && lunte --fix",
    "lint": "prettier --check . && lunte",
    "test": "brittle-bare --coverage test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-fs.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-fs/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-fs#readme",
  "engines": {
    "bare": ">=1.16.0"
  },
  "dependencies": {
    "bare-events": "^2.5.4",
    "bare-path": "^3.0.0",
    "bare-stream": "^2.6.4",
    "bare-url": "^2.2.2",
    "fast-fifo": "^1.3.2"
  },
  "devDependencies": {
    "bare-buffer": "^3.0.2",
    "bare-crypto": "^1.11.2",
    "brittle": "^3.1.1",
    "cmake-bare": "^1.1.7",
    "lunte": "^1.6.0",
    "prettier": "^3.4.1",
    "prettier-config-holepunch": "^2.0.0"
  },
  "peerDependencies": {
    "bare-buffer": "*"
  },
  "peerDependenciesMeta": {
    "bare-buffer": {
      "optional": true
    }
  }
}
const EventEmitter = require('bare-events')
const fs = require('.')

class FileHandle extends EventEmitter {
  constructor(fd) {
    super()

    this.fd = fd
  }

  async close() {
    await fs.close(this.fd)

    this.fd = -1
    this.emit('close')
  }

  async read(buffer, ...args) {
    return {
      bytesRead: await fs.read(this.fd, buffer, ...args),
      buffer
    }
  }

  async readv(buffers, ...args) {
    return {
      bytesRead: await fs.readv(this.fd, buffers, ...args),
      buffers
    }
  }

  async write(buffer, ...args) {
    return {
      bytesWritten: await fs.write(this.fd, buffer, ...args),
      buffer
    }
  }

  async writev(buffers, ...args) {
    return {
      bytesWritten: await fs.writev(this.fd, buffers, ...args),
      buffers
    }
  }

  async stat() {
    return fs.fstat(this.fd)
  }

  async chmod(mode) {
    await fs.fchmod(this.fd, mode)
  }

  async chown(uid, gid) {
    await fs.fchown(this.fd, uid, gid)
  }

  async datasync() {
    return fs.fdatasync(this.fd)
  }

  async sync() {
    return fs.fsync(this.fd)
  }

  async truncate(len) {
    await fs.ftruncate(this.fd, len)
  }

  async utimes(atime, mtime) {
    await fs.futimes(this.fd, atime, mtime)
  }

  createReadStream(opts) {
    return fs.createReadStream(null, { ...opts, fd: this.fd })
  }

  createWriteStream(opts) {
    return fs.createWriteStream(null, { ...opts, fd: this.fd })
  }

  async [Symbol.asyncDispose]() {
    await this.close()
  }
}

exports.open = async function open(filepath, flags, mode) {
  return new FileHandle(await fs.open(filepath, flags, mode))
}

exports.access = fs.access
exports.appendFile = fs.appendFile
exports.chmod = fs.chmod
exports.chown = fs.chown
exports.constants = fs.constants
exports.copyFile = fs.copyFile
exports.cp = fs.cp
exports.lchown = fs.lchown
exports.lutimes = fs.lutimes
exports.link = fs.link
exports.lstat = fs.lstat
exports.mkdir = fs.mkdir
exports.mkdtemp = fs.mkdtemp
exports.opendir = fs.opendir
exports.readFile = fs.readFile
exports.readdir = fs.readdir
exports.readlink = fs.readlink
exports.realpath = fs.realpath
exports.rename = fs.rename
exports.rm = fs.rm
exports.rmdir = fs.rmdir
exports.stat = fs.stat
exports.statfs = fs.statfs
exports.truncate = fs.truncate
exports.symlink = fs.symlink
exports.unlink = fs.unlink
exports.utimes = fs.utimes
exports.watch = fs.watch
exports.writeFile = fs.writeFile
module.exports = require.addon()
const ansiEscapes = require('bare-ansi-escapes')
const getType = require('bare-type')
const binding = require('./binding')

const PLAIN_KEY = /^[a-zA-Z_][a-zA-Z_0-9]*$/

const defaultDepth = 2
const defaultBreakLength = 80
const defaultMaxArrayLength = 40

module.exports = exports = function inspect(value, opts = {}) {
  const {
    colors = false,
    depth = defaultDepth,
    breakLength = defaultBreakLength,
    stylize = defaultStylize(colors)
  } = opts

  const references = new InspectRefMap()

  const tree = inspectValue(value, 0, {
    colors,
    depth,
    breakLength,
    stylize,
    references
  })

  return tree.toString()
}

exports.styles = {
  bigint: ansiEscapes.colorYellow,
  boolean: ansiEscapes.colorYellow,
  date: ansiEscapes.colorMagenta,
  module: ansiEscapes.modifierUnderline,
  name: ansiEscapes.modifierReset,
  null: ansiEscapes.modifierBold,
  number: ansiEscapes.colorYellow,
  regexp: ansiEscapes.colorRed,
  special: ansiEscapes.colorCyan,
  string: ansiEscapes.colorGreen,
  symbol: ansiEscapes.colorGreen,
  undefined: ansiEscapes.colorBrightBlack
}

const styles = exports.styles

function defaultStylize(colors) {
  return function stylize(value, style) {
    const color = colors && styles[style]

    if (color) return color + value + ansiEscapes.modifierReset

    return value
  }
}

class InspectRefMap {
  constructor() {
    this.refs = new WeakMap()
    this.ids = new WeakMap()
    this.nextId = 1
  }

  has(object) {
    return this.refs.has(object)
  }

  get(object) {
    return this.refs.get(object) || null
  }

  set(object, ref) {
    this.refs.set(object, ref)
  }

  id(object) {
    let id = this.ids.get(object)
    if (id) return id

    id = this.nextId++
    this.ids.set(object, id)

    return id
  }
}

class InspectNode {
  constructor(depth, length, opts) {
    const { breakLength = defaultBreakLength, breakAlways = false } = opts

    this.depth = depth
    this.length = length
    this.breakLength = breakLength
    this.breakAlways = breakAlways
  }

  pad(n, string) {
    return string.padStart(n, ' ')
  }

  indent(n, string) {
    return '  '.repeat(n) + string
  }
}

class InspectRef extends InspectNode {
  constructor(depth, opts) {
    super(depth, '[circular *]'.length, opts)

    this.refs = opts.references
    this.count = 0
    this.circular = false
    this.color = opts.colors && styles.special
  }

  get id() {
    return this.refs.id(this)
  }

  increment() {
    return ++this.count
  }

  decrement() {
    return --this.count
  }

  toString(opts = {}) {
    const { offset = 0, pad = 0, indent = 0 } = opts

    let value = this.pad(pad, '[circular *' + this.id + ']')

    if (this.color) value = this.color + value + ansiEscapes.modifierReset

    return offset ? value : this.indent(indent, value)
  }
}

class InspectLeaf extends InspectNode {
  constructor(value, color, depth, opts) {
    const length = value.length

    if (value.includes('\n')) {
      value = value.replaceAll('\n', '\n' + '  '.repeat(depth))

      opts = { ...opts, breakAlways: true }
    }

    super(depth, length, opts)

    this.value = value
    this.color = opts.colors && color
  }

  toString(opts = {}) {
    const { offset = 0, pad = 0, indent = 0 } = opts

    let value = this.pad(pad, this.value)

    if (this.color) value = this.color + value + ansiEscapes.modifierReset

    return offset ? value : this.indent(indent, value)
  }
}

class InspectPair extends InspectNode {
  constructor(delim, left, right, depth, opts) {
    const length = left.length + delim.length + right.length

    if (left.breakAlways || right.breakAlways) {
      opts = { ...opts, breakAlways: true }
    }

    super(depth, length, opts)

    this.delim = delim
    this.left = left
    this.right = right
  }

  toString(opts = {}) {
    const { indent = 0 } = opts

    return this.indent(
      indent,
      this.left +
        this.delim +
        this.right.toString({
          indent,
          offset: this.left.length + this.delim.length
        })
    )
  }
}

class InspectSuspension extends InspectNode {
  constructor(overflow, depth, opts) {
    const label = `... ${overflow} more`

    super(depth, label.length, opts)

    this.label = label
  }

  toString(opts = {}) {
    const { indent = 0 } = opts

    return this.indent(indent, this.label)
  }
}

class InspectSequence extends InspectNode {
  constructor(header, footer, delim, values, ref, depth, opts) {
    const { tabulate = false } = opts

    const length =
      (ref.circular ? '<ref *>'.length + 1 : 0) +
      header.length +
      values.reduce(
        (length, value, i) =>
          length + value.length + (i === 0 ? 0 : delim.length),
        0
      ) +
      footer.length

    if (values.some((value) => value.breakAlways)) {
      opts = { ...opts, breakAlways: true }
    }

    super(depth, length, opts)

    this.header = header
    this.footer = footer
    this.delim = delim
    this.values = values
    this.ref = ref
    this.tabulate = tabulate
  }

  toString(opts = {}) {
    const { offset = 0, indent = 0 } = opts

    const split =
      this.breakAlways ||
      (this.values.length &&
        (offset + this.length > this.breakLength ||
          indent * 2 + this.length > this.breakLength))

    let header = this.header

    if (this.ref.circular) {
      header = '<ref *' + this.ref.id + '> ' + header
    }

    if (this.values.length === 0) {
      header = header.trimEnd()
    }

    if (offset === 0) {
      header = this.indent(indent, header)
    }

    if (split) {
      header = header.trimEnd() + '\n'
    }

    let string = header

    let columns = 1
    let pad = 0

    if (this.tabulate) {
      const widest = this.values.reduce(
        (length, value) =>
          value.breakAlways ? length : Math.max(length, value.length),
        0
      )

      if (widest) {
        columns = Math.max(
          columns,
          Math.floor(
            (this.breakLength - indent * 2) / (widest + this.delim.length)
          )
        )

        if (columns > 1) pad = widest
      }
    }

    for (let i = 0, n = this.values.length, offset = 0; i < n; i++) {
      const value = this.values[i]

      if (split) {
        let part

        if (i % columns === 0 || value.breakAlways) {
          part = value.toString({ indent: indent + 1, pad })
        } else {
          part = value.toString({ pad })
        }

        string += part

        if (i < n - 1) {
          if (i % columns === columns - 1 || this.values[i + 1].breakAlways) {
            string += this.delim.trimEnd() + '\n'
          } else {
            string += this.delim
          }
        }
      } else {
        if (i > 0) string += this.delim

        string += value.toString({ offset })

        offset += value.length
      }
    }

    let footer = this.footer

    if (this.values.length === 0) {
      footer = footer.trimStart()
    }

    if (split) {
      string += '\n' + this.indent(indent, footer.trimStart())
    } else {
      string += footer
    }

    return string
  }
}

function inspectValue(value, depth, opts) {
  const type = getType(value)

  if (type.isUndefined()) return inspectUndefined(depth, opts)
  if (type.isNull()) return inspectNull(depth, opts)
  if (type.isBoolean()) return inspectBoolean(value, depth, opts)
  if (type.isNumber()) return inspectNumber(value, depth, opts)
  if (type.isBigInt()) return inspectBigInt(value, depth, opts)
  if (type.isString()) return inspectString(value, depth, opts)
  if (type.isSymbol()) return inspectSymbol(value, depth, opts)
  if (type.isObject()) return inspectObject(type, value, depth, opts)
  if (type.isFunction()) return inspectFunction(type, value, depth, opts)
  if (type.isExternal()) return inspectExternal(value, opts, opts)
}

function inspectUndefined(depth, opts) {
  return new InspectLeaf('undefined', styles.undefined, depth, opts)
}

function inspectNull(depth, opts) {
  return new InspectLeaf('null', styles.null, depth, opts)
}

function inspectBoolean(value, depth, opts) {
  return new InspectLeaf(value.toString(), styles.boolean, depth, opts)
}

function inspectNumber(value, depth, opts) {
  let string

  if (Object.is(value, -0)) {
    string = '-0'
  } else {
    string = value.toString(10)
  }

  return new InspectLeaf(string, styles.number, depth, opts)
}

function inspectBigInt(value, depth, opts) {
  return new InspectLeaf(value.toString(10) + 'n', styles.bigint, depth, opts)
}

const STRING_ESCAPES =
  /[\ud800-\udbff][\udc00-\udfff]|[\u0000-\u001f'\\\ud800-\udfff]/g

function inspectString(value, depth, opts) {
  // https://tc39.es/ecma262/multipage/structured-data.html#sec-quotejsonstring
  const string = value.replace(STRING_ESCAPES, (match) => {
    if (match.length === 2) return match

    switch (match) {
      case "'":
        return "\\'"
      case '\\':
        return '\\\\'
      case '\b':
        return '\\b'
      case '\t':
        return '\\t'
      case '\n':
        return '\\n'
      case '\f':
        return '\\f'
      case '\r':
        return '\\r'
      default:
        return '\\u' + match.charCodeAt(0).toString(16).padStart(4, '0')
    }
  })

  return new InspectLeaf("'" + string + "'", styles.string, depth, opts)
}

function inspectSymbol(value, depth, opts) {
  return new InspectLeaf(value.toString(), styles.symbol, depth, opts)
}

function inspectKey(value, depth, opts) {
  if (PLAIN_KEY.test(value)) {
    return new InspectLeaf(value, null, depth, opts)
  } else {
    return inspectValue(value, depth, opts)
  }
}

function inspectObject(type, object, depth, opts) {
  const refs = opts.references

  let ref = refs.get(object)
  if (ref === null) {
    ref = new InspectRef(depth, opts)
    refs.set(object, ref)
  } else if (ref.count) {
    ref.circular = true
    return ref
  }

  const maxDepth = typeof opts.depth === 'number' ? opts.depth : Infinity

  if (maxDepth < depth) {
    const constructor = object.constructor

    return new InspectLeaf(
      '[' +
        (constructor && constructor.name ? constructor.name : 'Object') +
        ']',
      styles.special,
      depth,
      opts
    )
  }

  const inspect =
    object[Symbol.for('bare.inspect')] ||
    object[Symbol.for('nodejs.util.inspect.custom')]

  if (typeof inspect === 'function') {
    const value = inspect.call(
      object,
      typeof opts.depth === 'number' ? opts.depth - depth : null,
      {
        colors: opts.colors,
        breakLength: opts.breakLength,
        stylize: opts.stylize
      },
      exports
    )

    if (typeof value === 'object' && value !== null) {
      refs.set(value, ref)
    }

    if (typeof value !== 'string') {
      return inspectValue(value, depth, opts)
    }

    return new InspectLeaf(value, null, depth, opts)
  }

  if (type.isArray()) return inspectArray(object, ref, depth, opts)
  if (type.isDate()) return inspectDate(object, ref, depth, opts)
  if (type.isRegExp()) return inspectRegExp(object, ref, depth, opts)
  if (type.isError()) return inspectError(object, ref, depth, opts)
  if (type.isPromise()) return inspectPromise(object, ref, depth, opts)
  if (type.isMap()) return inspectMap(object, ref, depth, opts)
  if (type.isSet()) return inspectSet(object, ref, depth, opts)
  if (type.isWeakMap()) return inspectWeakMap(object, ref, depth, opts)
  if (type.isWeakSet()) return inspectWeakSet(object, ref, depth, opts)
  if (type.isWeakRef()) return inspectWeakRef(object, ref, depth, opts)
  if (type.isArrayBuffer()) return inspectArrayBuffer(object, ref, depth, opts)
  if (type.isSharedArrayBuffer())
    return inspectSharedArrayBuffer(object, ref, depth, opts)
  if (type.isTypedArray()) return inspectTypedArray(object, ref, depth, opts)
  if (type.isDataView()) return inspectDataView(object, ref, depth, opts)

  ref.increment()

  const values = []

  for (const key in object) {
    if (key === 'constructor') continue

    values.push(
      new InspectPair(
        ': ',
        inspectKey(key, depth + 1, opts),
        inspectValue(object[key], depth + 1, opts),
        depth + 1,
        opts
      )
    )
  }

  ref.decrement()

  let header = '{ '

  const tag = object[Symbol.toStringTag]

  if (tag) header = '[' + tag + '] ' + header

  if (object.constructor) {
    const name = object.constructor.name

    if (name && name !== 'Object') {
      header = object.constructor.name + ' ' + header
    }
  }

  return new InspectSequence(header, ' }', ', ', values, ref, depth, opts)
}

function inspectArray(array, ref, depth, opts) {
  const { maxArrayLength = defaultMaxArrayLength } = opts

  ref.increment()

  const values = []

  let remaining = Math.max(maxArrayLength, 0)

  for (let i = 0, n = array.length; i < n; i++) {
    if (remaining-- === 0) {
      values.push(
        new InspectSuspension(array.length - values.length, depth + 1, {
          ...opts,
          breakAlways: true
        })
      )

      break
    }

    values.push(inspectValue(array[i], depth + 1, opts))
  }

  for (const key of binding.getOwnNonIndexPropertyNames(array)) {
    values.push(
      new InspectPair(
        ': ',
        inspectKey(key, depth + 1, opts),
        inspectValue(array[key], depth + 1, opts),
        depth + 1,
        { ...opts, breakAlways: remaining < 0 }
      )
    )
  }

  ref.decrement()

  let header = '[ '

  if (array.constructor.name !== 'Array') {
    header = array.constructor.name + '(' + array.length + ') ' + header
  }

  return new InspectSequence(header, ' ]', ', ', values, ref, depth, {
    ...opts,
    tabulate: true
  })
}

function inspectDate(date, ref, depth, opts) {
  return new InspectLeaf(date.toISOString(), styles.date, depth, opts)
}

function inspectRegExp(regExp, ref, depth, opts) {
  return new InspectLeaf(regExp.toString(), styles.regexp, depth, opts)
}

function inspectError(error, ref, depth, opts) {
  let header

  if ('stack' in error) {
    header = error.stack

    if (depth > 0) {
      header = header.replaceAll('\n', '\n' + '  '.repeat(depth))
    }
  } else {
    header = error.toString()
  }

  const builtins = ['cause']

  if (error.name === 'AggregateError') {
    builtins.push('errors')
  } else if (error.name === 'SuppressedError') {
    builtins.push('error', 'suppressed')
  }

  const values = []

  for (const key of builtins) {
    if (key in error === false) continue

    values.push(
      new InspectPair(
        ': ',
        new InspectLeaf('[' + key + ']', null, depth + 1, opts),
        inspectValue(error[key], depth + 1, opts),
        depth + 1,
        opts
      )
    )
  }

  for (const key in error) {
    if (key === 'constructor' || builtins.includes(key)) continue

    values.push(
      new InspectPair(
        ': ',
        inspectKey(key, depth + 1, opts),
        inspectValue(error[key], depth + 1, opts),
        depth + 1,
        opts
      )
    )
  }

  if (values.length === 0) return new InspectLeaf(header, null, depth, opts)

  return new InspectSequence(
    header + ' {',
    ' }',
    ', ',
    values,
    ref,
    depth,
    opts
  )
}

function inspectPromise(promise, ref, depth, opts) {
  ref.increment()

  const state = binding.getPromiseState(promise)

  const values = []

  switch (state) {
    case 0: // Pending
      values.push(new InspectLeaf('<pending>', styles.special, depth, opts))
      break

    case 1: // Fulfilled
      values.push(inspectValue(binding.getPromiseResult(promise), depth, opts))
      break

    case 2: // Rejected
      values.push(
        new InspectLeaf('<rejected>', styles.special, depth, opts),
        inspectValue(binding.getPromiseResult(promise), depth, opts)
      )
  }

  ref.decrement()

  const header = promise.constructor.name + ' { '

  return new InspectSequence(header, ' }', ' ', values, ref, depth, opts)
}

function inspectMap(map, ref, depth, opts) {
  const {
    maxArrayLength = defaultMaxArrayLength,
    maxMapLength = maxArrayLength
  } = opts

  ref.increment()

  const values = []

  let remaining = maxMapLength

  for (const entry of map) {
    if (remaining-- === 0) {
      values.push(
        new InspectSuspension(map.size - values.length, depth + 1, {
          ...opts,
          breakAlways: true
        })
      )
      break
    }

    values.push(
      new InspectPair(
        ' => ',
        inspectValue(entry[0], depth + 1, opts),
        inspectValue(entry[1], depth + 1, opts),
        depth + 1,
        opts
      )
    )
  }

  for (const key in map) {
    if (key === 'constructor') continue

    const value = inspectValue(map[key], depth + 1, opts)

    values.push(
      new InspectPair(
        ': ',
        inspectKey(key, depth + 1, opts),
        value,
        depth + 1,
        { ...opts, breakAlways: remaining < 0 }
      )
    )
  }

  ref.decrement()

  const header = map.constructor.name + '(' + map.size + ') { '

  return new InspectSequence(header, ' }', ', ', values, ref, depth, {
    ...opts,
    tabulate: true
  })
}

function inspectSet(set, ref, depth, opts) {
  const {
    maxArrayLength = defaultMaxArrayLength,
    maxSetLength = maxArrayLength
  } = opts

  ref.increment()

  const values = []

  let remaining = maxSetLength

  for (const entry of set) {
    if (remaining-- === 0) {
      values.push(
        new InspectSuspension(set.size - values.length, depth + 1, {
          ...opts,
          breakAlways: true
        })
      )
      break
    }

    values.push(inspectValue(entry, depth + 1, opts))
  }

  for (const key in set) {
    if (key === 'constructor') continue

    const value = inspectValue(set[key], depth + 1, opts)

    values.push(
      new InspectPair(
        ': ',
        inspectKey(key, depth + 1, opts),
        value,
        depth + 1,
        { ...opts, breakAlways: remaining < 0 }
      )
    )
  }

  ref.decrement()

  const header = set.constructor.name + '(' + set.size + ') { '

  return new InspectSequence(header, ' }', ', ', values, ref, depth, {
    ...opts,
    tabulate: true
  })
}

function inspectWeakMap(weakMap, ref, depth, opts) {
  const header = weakMap.constructor.name + ' { '

  return new InspectSequence(
    header,
    ' }',
    ' ',
    [new InspectLeaf('<items unknown>', styles.special, depth + 1, opts)],
    ref,
    depth,
    opts
  )
}

function inspectWeakSet(weakSet, ref, depth, opts) {
  const header = weakSet.constructor.name + ' { '

  return new InspectSequence(
    header,
    ' }',
    ' ',
    [new InspectLeaf('<items unknown>', styles.special, depth + 1, opts)],
    ref,
    depth,
    opts
  )
}

function inspectWeakRef(weakRef, ref, depth, opts) {
  const target = weakRef.deref()

  let value

  if (target === undefined) {
    value = new InspectLeaf('<cleared>', styles.special, depth + 1, opts)
  } else {
    value = inspectValue(target, depth + 1, opts)
  }

  const header = weakRef.constructor.name + ' { '

  return new InspectSequence(header, ' }', ' ', [value], ref, depth, opts)
}

function inspectArrayBuffer(arrayBuffer, ref, depth, opts) {
  ref.increment()

  const values = []

  for (const key of ['byteLength']) {
    values.push(
      new InspectPair(
        ': ',
        inspectKey(key, depth + 1, opts),
        inspectValue(arrayBuffer[key], depth + 1, opts),
        depth + 1,
        opts
      )
    )
  }

  ref.decrement()

  const header = arrayBuffer.constructor.name + ' { '

  return new InspectSequence(header, ' }', ', ', values, ref, depth, opts)
}

function inspectSharedArrayBuffer(sharedArrayBuffer, ref, depth, opts) {
  ref.increment()

  const values = []

  for (const key of ['byteLength']) {
    values.push(
      new InspectPair(
        ': ',
        inspectKey(key, depth + 1, opts),
        inspectValue(sharedArrayBuffer[key], depth + 1, opts),
        depth + 1,
        opts
      )
    )
  }

  ref.decrement()

  const header = sharedArrayBuffer.constructor.name + ' { '

  return new InspectSequence(header, ' }', ', ', values, ref, depth, opts)
}

function inspectTypedArray(typedArray, ref, depth, opts) {
  if (Buffer.isBuffer(typedArray)) {
    return inspectBuffer(typedArray, ref, depth, opts)
  }

  const {
    maxArrayLength = defaultMaxArrayLength,
    maxTypedArrayLength = maxArrayLength
  } = opts

  ref.increment()

  const values = []

  let remaining = Math.max(maxTypedArrayLength, 0)

  for (let i = 0, n = typedArray.length; i < n; i++) {
    if (remaining-- === 0) {
      values.push(
        new InspectSuspension(typedArray.length - values.length, depth + 1, {
          ...opts,
          breakAlways: true
        })
      )

      break
    }

    values.push(inspectValue(typedArray[i], depth + 1, opts))
  }

  for (const key of binding.getOwnNonIndexPropertyNames(typedArray)) {
    values.push(
      new InspectPair(
        ': ',
        inspectKey(key, depth + 1, opts),
        inspectValue(typedArray[key], depth + 1, opts),
        depth + 1,
        { ...opts, breakAlways: remaining < 0 }
      )
    )
  }

  ref.decrement()

  const header = typedArray.constructor.name + '(' + typedArray.length + ') [ '

  return new InspectSequence(header, ' ]', ', ', values, ref, depth, {
    ...opts,
    tabulate: true
  })
}

function inspectBuffer(buffer, ref, depth, opts) {
  const {
    maxArrayLength = defaultMaxArrayLength,
    maxBufferLength = maxArrayLength
  } = opts

  ref.increment()

  const values = []

  let remaining = Math.max(maxBufferLength, 0)

  for (let i = 0, n = buffer.byteLength; i < n; i++) {
    if (remaining-- === 0) {
      values.push(
        new InspectSuspension(buffer.length - values.length, depth + 1, {
          ...opts,
          breakAlways: true
        })
      )

      break
    }

    values.push(
      new InspectLeaf(
        buffer[i].toString(16).padStart(2, '0'),
        null,
        depth + 1,
        opts
      )
    )
  }

  for (const key of binding.getOwnNonIndexPropertyNames(buffer)) {
    values.push(
      new InspectPair(
        ': ',
        inspectKey(key, depth + 1, opts),
        inspectValue(buffer[key], depth + 1, opts),
        depth + 1,
        { ...opts, breakAlways: remaining < 0 }
      )
    )
  }

  ref.decrement()

  return new InspectSequence('<Buffer ', '>', ' ', values, ref, depth, {
    ...opts,
    tabulate: true
  })
}

function inspectDataView(dataView, ref, depth, opts) {
  ref.increment()

  const values = []

  for (const key of ['byteLength', 'byteOffset', 'buffer']) {
    values.push(
      new InspectPair(
        ': ',
        inspectKey(key, depth + 1, opts),
        inspectValue(dataView[key], depth + 1, opts),
        depth + 1,
        opts
      )
    )
  }

  for (const key of binding.getOwnNonIndexPropertyNames(dataView)) {
    values.push(
      new InspectPair(
        ': ',
        inspectKey(key, depth + 1, opts),
        inspectValue(dataView[key], depth + 1, opts),
        depth + 1,
        opts
      )
    )
  }

  ref.decrement()

  const header = dataView.constructor.name + ' { '

  return new InspectSequence(header, ' }', ', ', values, ref, depth, opts)
}

function inspectFunction(type, fn, depth, opts) {
  if (fn.toString().startsWith('class')) return inspectClass(fn, depth, opts)

  let tag = 'function'

  if (type.isGeneratorFunction()) tag = 'generator ' + tag
  if (type.isAsyncFunction()) tag = 'async ' + tag

  return new InspectLeaf(
    '[' + tag + ' ' + (fn.name ? fn.name : '(anonymous)') + ']',
    styles.special,
    depth,
    opts
  )
}

function inspectClass(ctor, depth, opts) {
  return new InspectLeaf(
    '[class ' + (ctor.name ? ctor.name : '(anonymous)') + ']',
    styles.special,
    depth,
    opts
  )
}

function inspectExternal(external, depth, opts) {
  return new InspectLeaf(
    '[external 0x' + binding.getExternal(external).toString(16) + ']',
    styles.special,
    depth,
    opts
  )
}
{
  "name": "bare-inspect",
  "version": "3.1.4",
  "description": "Inspect objects as strings for debugging",
  "exports": {
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    },
    "./package": "./package.json"
  },
  "files": [
    "index.js",
    "index.d.ts",
    "binding.c",
    "binding.js",
    "CMakeLists.txt",
    "prebuilds"
  ],
  "addon": true,
  "scripts": {
    "test": "prettier . --check && bare test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-inspect.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-inspect/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-inspect#readme",
  "engines": {
    "bare": ">=1.18.0"
  },
  "dependencies": {
    "bare-ansi-escapes": "^2.1.0",
    "bare-type": "^1.0.0"
  },
  "devDependencies": {
    "brittle": "^3.3.2",
    "cmake-bare": "^1.1.6",
    "prettier": "^3.4.1",
    "prettier-config-standard": "^7.0.0"
  }
}
module.exports = require.addon()
const binding = require('./binding')
const errors = require('./lib/errors')
const constants = require('./lib/constants')

exports.constants = constants
exports.errors = errors

exports.EOL = binding.platform === 'win32' ? '\r\n' : '\n'

exports.devNull = binding.platform === 'win32' ? '\\\\.\\nul' : '/dev/null'

exports.platform = function platform() {
  return binding.platform
}

exports.arch = function arch() {
  return binding.arch
}

exports.type = binding.type
exports.version = binding.version
exports.release = binding.release
exports.machine = binding.machine
exports.execPath = binding.execPath
exports.pid = binding.pid
exports.ppid = binding.ppid
exports.cwd = binding.cwd
exports.chdir = binding.chdir
exports.tmpdir = binding.tmpdir
exports.homedir = binding.homedir
exports.hostname = binding.hostname
exports.userInfo = binding.userInfo
exports.groupInfo = binding.groupInfo

exports.networkInterfaces = function networkInterfaces() {
  const result = {}

  for (const entry of binding.networkInterfaces()) {
    const { name, ...properties } = entry

    if (result[name]) result[name].push(properties)
    else result[name] = [properties]
  }

  return result
}

exports.kill = function kill(pid, signal = constants.signals.SIGTERM) {
  if (typeof signal === 'string') {
    if (signal in constants.signals === false) {
      throw errors.UNKNOWN_SIGNAL('Unknown signal: ' + signal)
    }

    signal = constants.signals[signal]
  }

  binding.kill(pid, signal)
}

exports.endianness = function endianness() {
  return binding.isLittleEndian ? 'LE' : 'BE'
}

exports.availableParallelism = binding.availableParallelism

exports.cpuUsage = function cpuUsage(previous) {
  const current = binding.cpuUsage()

  if (previous) {
    return {
      user: current.user - previous.user,
      system: current.system - previous.system
    }
  }

  return current
}

exports.threadCpuUsage = function threadCpuUsage(previous) {
  const current = binding.threadCpuUsage()

  if (previous) {
    return {
      user: current.user - previous.user,
      system: current.system - previous.system
    }
  }

  return current
}

exports.resourceUsage = binding.resourceUsage
exports.memoryUsage = binding.memoryUsage
exports.freemem = binding.freemem
exports.totalmem = binding.totalmem
exports.availableMemory = binding.availableMemory
exports.constrainedMemory = binding.constrainedMemory
exports.uptime = binding.uptime
exports.loadavg = binding.loadavg
exports.cpus = binding.cpus

exports.getProcessTitle = binding.getProcessTitle

exports.setProcessTitle = function setProcessTitle(title) {
  if (typeof title !== 'string') title = title.toString()

  if (title.length >= 256) {
    throw errors.TITLE_OVERFLOW('Process title is too long')
  }

  binding.setProcessTitle(title)
}

exports.getPriority = function getPriority(pid = 0) {
  return binding.getPriority(pid)
}

exports.setPriority = function setPriority(pid, priority) {
  if (priority === undefined) {
    priority = pid
    pid = 0
  }

  binding.setPriority(pid, priority)
}

exports.getEnvKeys = binding.getEnvKeys
exports.getEnv = binding.getEnv
exports.hasEnv = binding.hasEnv
exports.setEnv = binding.setEnv
exports.unsetEnv = binding.unsetEnv
const binding = require('../binding')

module.exports = {
  signals: binding.signals,
  errnos: binding.errnos,
  priority: binding.priority
}
module.exports = class OSError extends Error {
  constructor(msg, fn = OSError, code = fn.name) {
    super(`${code}: ${msg}`)
    this.code = code

    if (Error.captureStackTrace) {
      Error.captureStackTrace(this, fn)
    }
  }

  get name() {
    return 'OSError'
  }

  static UNKNOWN_SIGNAL(msg) {
    return new OSError(msg, OSError.UNKNOWN_SIGNAL)
  }

  static TITLE_OVERFLOW(msg) {
    return new OSError(msg, OSError.TITLE_OVERFLOW)
  }
}
{
  "name": "bare-os",
  "version": "3.9.1",
  "description": "Operating system utilities for Bare",
  "exports": {
    "./package": "./package.json",
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    },
    "./constants": {
      "types": "./lib/constants.d.ts",
      "default": "./lib/constants.js"
    },
    "./errors": {
      "types": "./lib/errors.d.ts",
      "default": "./lib/errors.js"
    }
  },
  "files": [
    "index.js",
    "index.d.ts",
    "binding.c",
    "binding.js",
    "CMakeLists.txt",
    "lib",
    "prebuilds"
  ],
  "addon": true,
  "scripts": {
    "format": "prettier --write . && lunte --fix",
    "lint": "prettier --check . && lunte",
    "test": "brittle-bare --coverage test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-os.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-os/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-os#readme",
  "engines": {
    "bare": ">=1.14.0"
  },
  "devDependencies": {
    "brittle": "^3.1.1",
    "cmake-bare": "^1.1.6",
    "lunte": "^1.6.0",
    "prettier": "^3.4.2",
    "prettier-config-holepunch": "^2.0.0"
  }
}
// This export SHOULD NOT be shortened in any way as having the full
// `module.exports = require(...)` statement is crucial for synthesizing
// ESM exports.

if (Bare.platform === 'win32') {
  module.exports = require('./lib/win32')
} else {
  module.exports = require('./lib/posix')
}
module.exports = {
  CHAR_UPPERCASE_A: 0x41,
  CHAR_LOWERCASE_A: 0x61,
  CHAR_UPPERCASE_Z: 0x5a,
  CHAR_LOWERCASE_Z: 0x7a,
  CHAR_DOT: 0x2e,
  CHAR_FORWARD_SLASH: 0x2f,
  CHAR_BACKWARD_SLASH: 0x5c,
  CHAR_COLON: 0x3a,
  CHAR_QUESTION_MARK: 0x3f
}
const os = require('bare-os')

const { normalizeString } = require('./shared')
const { CHAR_DOT, CHAR_FORWARD_SLASH } = require('./constants')

function isPosixPathSeparator(code) {
  return code === CHAR_FORWARD_SLASH
}

exports.win32 = require('./win32')
exports.posix = exports

exports.sep = '/'
exports.delimiter = ':'

exports.resolve = function resolve(...args) {
  let resolvedPath = ''
  let resolvedAbsolute = false

  for (let i = args.length - 1; i >= -1 && !resolvedAbsolute; i--) {
    const path = i >= 0 ? args[i] : os.cwd()

    if (path.length === 0) {
      continue
    }

    resolvedPath = `${path}/${resolvedPath}`
    resolvedAbsolute = path.charCodeAt(0) === CHAR_FORWARD_SLASH
  }

  resolvedPath = normalizeString(resolvedPath, !resolvedAbsolute, '/', isPosixPathSeparator)

  if (resolvedAbsolute) {
    return `/${resolvedPath}`
  }

  return resolvedPath.length > 0 ? resolvedPath : '.'
}

exports.normalize = function normalize(path) {
  if (path.length === 0) return '.'

  const isAbsolute = path.charCodeAt(0) === CHAR_FORWARD_SLASH
  const trailingSeparator = path.charCodeAt(path.length - 1) === CHAR_FORWARD_SLASH

  path = normalizeString(path, !isAbsolute, '/', isPosixPathSeparator)

  if (path.length === 0) {
    if (isAbsolute) return '/'
    return trailingSeparator ? './' : '.'
  }

  if (trailingSeparator) path += '/'

  return isAbsolute ? `/${path}` : path
}

exports.isAbsolute = function isAbsolute(path) {
  return path.length > 0 && path.charCodeAt(0) === CHAR_FORWARD_SLASH
}

exports.join = function join(...args) {
  if (args.length === 0) return '.'
  let joined
  for (let i = 0; i < args.length; ++i) {
    const arg = args[i]
    if (arg.length > 0) {
      if (joined === undefined) joined = arg
      else joined += `/${arg}`
    }
  }
  if (joined === undefined) return '.'
  return exports.normalize(joined)
}

exports.relative = function relative(from, to) {
  if (from === to) return ''

  from = exports.resolve(from)
  to = exports.resolve(to)

  if (from === to) return ''

  const fromStart = 1
  const fromEnd = from.length
  const fromLen = fromEnd - fromStart
  const toStart = 1
  const toLen = to.length - toStart

  const length = fromLen < toLen ? fromLen : toLen
  let lastCommonSep = -1
  let i = 0
  for (; i < length; i++) {
    const fromCode = from.charCodeAt(fromStart + i)
    if (fromCode !== to.charCodeAt(toStart + i)) {
      break
    } else if (fromCode === CHAR_FORWARD_SLASH) {
      lastCommonSep = i
    }
  }
  if (i === length) {
    if (toLen > length) {
      if (to.charCodeAt(toStart + i) === CHAR_FORWARD_SLASH) {
        return to.substring(toStart + i + 1)
      }
      if (i === 0) {
        return to.substring(toStart + i)
      }
    } else if (fromLen > length) {
      if (from.charCodeAt(fromStart + i) === CHAR_FORWARD_SLASH) {
        lastCommonSep = i
      } else if (i === 0) {
        lastCommonSep = 0
      }
    }
  }

  let out = ''
  for (i = fromStart + lastCommonSep + 1; i <= fromEnd; ++i) {
    if (i === fromEnd || from.charCodeAt(i) === CHAR_FORWARD_SLASH) {
      out += out.length === 0 ? '..' : '/..'
    }
  }

  return `${out}${to.substring(toStart + lastCommonSep)}`
}

exports.toNamespacedPath = function toNamespacedPath(path) {
  return path
}

exports.dirname = function dirname(path) {
  if (path.length === 0) return '.'
  const hasRoot = path.charCodeAt(0) === CHAR_FORWARD_SLASH
  let end = -1
  let matchedSlash = true
  for (let i = path.length - 1; i >= 1; --i) {
    if (path.charCodeAt(i) === CHAR_FORWARD_SLASH) {
      if (!matchedSlash) {
        end = i
        break
      }
    } else {
      matchedSlash = false
    }
  }

  if (end === -1) return hasRoot ? '/' : '.'
  if (hasRoot && end === 1) return '//'
  return path.substring(0, end)
}

exports.basename = function basename(path, suffix) {
  let start = 0
  let end = -1
  let matchedSlash = true

  if (suffix !== undefined && suffix.length > 0 && suffix.length <= path.length) {
    if (suffix === path) {
      return ''
    }
    let extIdx = suffix.length - 1
    let firstNonSlashEnd = -1
    for (let i = path.length - 1; i >= 0; --i) {
      const code = path.charCodeAt(i)
      if (code === CHAR_FORWARD_SLASH) {
        if (!matchedSlash) {
          start = i + 1
          break
        }
      } else {
        if (firstNonSlashEnd === -1) {
          matchedSlash = false
          firstNonSlashEnd = i + 1
        }
        if (extIdx >= 0) {
          if (code === suffix.charCodeAt(extIdx)) {
            if (--extIdx === -1) {
              end = i
            }
          } else {
            extIdx = -1
            end = firstNonSlashEnd
          }
        }
      }
    }

    if (start === end) end = firstNonSlashEnd
    else if (end === -1) end = path.length
    return path.substring(start, end)
  }

  for (let i = path.length - 1; i >= 0; --i) {
    if (path.charCodeAt(i) === CHAR_FORWARD_SLASH) {
      if (!matchedSlash) {
        start = i + 1
        break
      }
    } else if (end === -1) {
      matchedSlash = false
      end = i + 1
    }
  }

  if (end === -1) return ''
  return path.substring(start, end)
}

exports.extname = function extname(path) {
  let startDot = -1
  let startPart = 0
  let end = -1
  let matchedSlash = true
  let preDotState = 0
  for (let i = path.length - 1; i >= 0; --i) {
    const code = path.charCodeAt(i)
    if (code === CHAR_FORWARD_SLASH) {
      if (!matchedSlash) {
        startPart = i + 1
        break
      }
      continue
    }
    if (end === -1) {
      matchedSlash = false
      end = i + 1
    }
    if (code === CHAR_DOT) {
      if (startDot === -1) startDot = i
      else if (preDotState !== 1) preDotState = 1
    } else if (startDot !== -1) {
      preDotState = -1
    }
  }

  if (
    startDot === -1 ||
    end === -1 ||
    preDotState === 0 ||
    (preDotState === 1 && startDot === end - 1 && startDot === startPart + 1)
  ) {
    return ''
  }
  return path.substring(startDot, end)
}
const { CHAR_DOT, CHAR_FORWARD_SLASH } = require('./constants')

exports.normalizeString = function normalizeString(
  path,
  allowAboveRoot,
  separator,
  isPathSeparator
) {
  let res = ''
  let lastSegmentLength = 0
  let lastSlash = -1
  let dots = 0
  let code = 0
  for (let i = 0; i <= path.length; ++i) {
    if (i < path.length) {
      code = path.charCodeAt(i)
    } else if (isPathSeparator(code)) {
      break
    } else {
      code = CHAR_FORWARD_SLASH
    }

    if (isPathSeparator(code)) {
      if (lastSlash === i - 1 || dots === 1);
      else if (dots === 2) {
        if (
          res.length < 2 ||
          lastSegmentLength !== 2 ||
          res.charCodeAt(res.length - 1) !== CHAR_DOT ||
          res.charCodeAt(res.length - 2) !== CHAR_DOT
        ) {
          if (res.length > 2) {
            const lastSlashIndex = res.lastIndexOf(separator)
            if (lastSlashIndex === -1) {
              res = ''
              lastSegmentLength = 0
            } else {
              res = res.substring(0, lastSlashIndex)
              lastSegmentLength = res.length - 1 - res.lastIndexOf(separator)
            }
            lastSlash = i
            dots = 0
            continue
          } else if (res.length !== 0) {
            res = ''
            lastSegmentLength = 0
            lastSlash = i
            dots = 0
            continue
          }
        }
        if (allowAboveRoot) {
          res += res.length > 0 ? `${separator}..` : '..'
          lastSegmentLength = 2
        }
      } else {
        if (res.length > 0) {
          res += `${separator}${path.substring(lastSlash + 1, i)}`
        } else {
          res = path.substring(lastSlash + 1, i)
        }
        lastSegmentLength = i - lastSlash - 1
      }
      lastSlash = i
      dots = 0
    } else if (code === CHAR_DOT && dots !== -1) {
      ++dots
    } else {
      dots = -1
    }
  }
  return res
}
const os = require('bare-os')

const { normalizeString } = require('./shared')
const {
  CHAR_UPPERCASE_A,
  CHAR_LOWERCASE_A,
  CHAR_UPPERCASE_Z,
  CHAR_LOWERCASE_Z,
  CHAR_DOT,
  CHAR_FORWARD_SLASH,
  CHAR_BACKWARD_SLASH,
  CHAR_COLON,
  CHAR_QUESTION_MARK
} = require('./constants')

function isWindowsPathSeparator(code) {
  return code === CHAR_FORWARD_SLASH || code === CHAR_BACKWARD_SLASH
}

function isWindowsDeviceRoot(code) {
  return (
    (code >= CHAR_UPPERCASE_A && code <= CHAR_UPPERCASE_Z) ||
    (code >= CHAR_LOWERCASE_A && code <= CHAR_LOWERCASE_Z)
  )
}

exports.posix = require('./posix')
exports.win32 = exports

exports.sep = '\\'
exports.delimiter = ';'

exports.resolve = function resolve(...args) {
  let resolvedDevice = ''
  let resolvedTail = ''
  let resolvedAbsolute = false

  for (let i = args.length - 1; i >= -1; i--) {
    let path
    if (i >= 0) {
      path = args[i]

      if (path.length === 0) continue
    } else if (resolvedDevice.length === 0) {
      path = os.cwd()
    } else {
      path = os.getEnv(`=${resolvedDevice}`) || os.cwd()

      if (
        path === undefined ||
        (path.substring(0, 2).toLowerCase() !== resolvedDevice.toLowerCase() &&
          path.charCodeAt(2) === CHAR_BACKWARD_SLASH)
      ) {
        path = `${resolvedDevice}\\`
      }
    }

    const len = path.length
    let rootEnd = 0
    let device = ''
    let isAbsolute = false
    const code = path.charCodeAt(0)

    if (len === 1) {
      if (isWindowsPathSeparator(code)) {
        rootEnd = 1
        isAbsolute = true
      }
    } else if (isWindowsPathSeparator(code)) {
      isAbsolute = true

      if (isWindowsPathSeparator(path.charCodeAt(1))) {
        let j = 2
        let last = j
        while (j < len && !isWindowsPathSeparator(path.charCodeAt(j))) {
          j++
        }
        if (j < len && j !== last) {
          const firstPart = path.substring(last, j)
          last = j
          while (j < len && isWindowsPathSeparator(path.charCodeAt(j))) {
            j++
          }
          if (j < len && j !== last) {
            last = j
            while (j < len && !isWindowsPathSeparator(path.charCodeAt(j))) {
              j++
            }
            if (j === len || j !== last) {
              device = `\\\\${firstPart}\\${path.substring(last, j)}`
              rootEnd = j
            }
          }
        }
      } else {
        rootEnd = 1
      }
    } else if (isWindowsDeviceRoot(code) && path.charCodeAt(1) === CHAR_COLON) {
      device = path.substring(0, 2)
      rootEnd = 2
      if (len > 2 && isWindowsPathSeparator(path.charCodeAt(2))) {
        isAbsolute = true
        rootEnd = 3
      }
    }

    if (device.length > 0) {
      if (resolvedDevice.length > 0) {
        if (device.toLowerCase() !== resolvedDevice.toLowerCase()) {
          continue
        }
      } else {
        resolvedDevice = device
      }
    }

    if (resolvedAbsolute) {
      if (resolvedDevice.length > 0) {
        break
      }
    } else {
      resolvedTail = `${path.substring(rootEnd)}\\${resolvedTail}`
      resolvedAbsolute = isAbsolute
      if (isAbsolute && resolvedDevice.length > 0) {
        break
      }
    }
  }

  resolvedTail = normalizeString(resolvedTail, !resolvedAbsolute, '\\', isWindowsPathSeparator)

  return resolvedAbsolute
    ? `${resolvedDevice}\\${resolvedTail}`
    : `${resolvedDevice}${resolvedTail}` || '.'
}

exports.normalize = function normalize(path) {
  const len = path.length
  if (len === 0) return '.'
  let rootEnd = 0
  let device
  let isAbsolute = false
  const code = path.charCodeAt(0)

  if (len === 1) {
    return code === CHAR_FORWARD_SLASH ? '\\' : path
  }

  if (isWindowsPathSeparator(code)) {
    isAbsolute = true

    if (isWindowsPathSeparator(path.charCodeAt(1))) {
      let j = 2
      let last = j
      while (j < len && !isWindowsPathSeparator(path.charCodeAt(j))) {
        j++
      }
      if (j < len && j !== last) {
        const firstPart = path.substring(last, j)
        last = j
        while (j < len && isWindowsPathSeparator(path.charCodeAt(j))) {
          j++
        }
        if (j < len && j !== last) {
          last = j
          while (j < len && !isWindowsPathSeparator(path.charCodeAt(j))) {
            j++
          }
          if (j === len) {
            return `\\\\${firstPart}\\${path.substring(last)}\\`
          }
          if (j !== last) {
            device = `\\\\${firstPart}\\${path.substring(last, j)}`
            rootEnd = j
          }
        }
      }
    } else {
      rootEnd = 1
    }
  } else if (isWindowsDeviceRoot(code) && path.charCodeAt(1) === CHAR_COLON) {
    device = path.substring(0, 2)
    rootEnd = 2
    if (len > 2 && isWindowsPathSeparator(path.charCodeAt(2))) {
      isAbsolute = true
      rootEnd = 3
    }
  }

  let tail =
    rootEnd < len
      ? normalizeString(path.substring(rootEnd), !isAbsolute, '\\', isWindowsPathSeparator)
      : ''
  if (tail.length === 0 && !isAbsolute) {
    tail = '.'
  }
  if (tail.length > 0 && isWindowsPathSeparator(path.charCodeAt(len - 1))) {
    tail += '\\'
  }
  if (device === undefined) {
    return isAbsolute ? `\\${tail}` : tail
  }
  return isAbsolute ? `${device}\\${tail}` : `${device}${tail}`
}

exports.isAbsolute = function isAbsolute(path) {
  const len = path.length
  if (len === 0) return false

  const code = path.charCodeAt(0)

  return (
    isWindowsPathSeparator(code) ||
    (len > 2 &&
      isWindowsDeviceRoot(code) &&
      path.charCodeAt(1) === CHAR_COLON &&
      isWindowsPathSeparator(path.charCodeAt(2)))
  )
}

exports.join = function join(...args) {
  if (args.length === 0) return '.'

  let joined
  let firstPart
  for (let i = 0; i < args.length; ++i) {
    const arg = args[i]
    if (arg.length > 0) {
      if (joined === undefined) joined = firstPart = arg
      else joined += `\\${arg}`
    }
  }

  if (joined === undefined) return '.'

  let needsReplace = true
  let slashCount = 0
  if (isWindowsPathSeparator(firstPart.charCodeAt(0))) {
    ++slashCount
    const firstLen = firstPart.length
    if (firstLen > 1 && isWindowsPathSeparator(firstPart.charCodeAt(1))) {
      ++slashCount
      if (firstLen > 2) {
        if (isWindowsPathSeparator(firstPart.charCodeAt(2))) {
          ++slashCount
        } else {
          needsReplace = false
        }
      }
    }
  }
  if (needsReplace) {
    while (slashCount < joined.length && isWindowsPathSeparator(joined.charCodeAt(slashCount))) {
      slashCount++
    }

    if (slashCount >= 2) {
      joined = `\\${joined.substring(slashCount)}`
    }
  }

  return exports.normalize(joined)
}

exports.relative = function relative(from, to) {
  if (from === to) return ''

  const fromOrig = exports.resolve(from)
  const toOrig = exports.resolve(to)

  if (fromOrig === toOrig) return ''

  from = fromOrig.toLowerCase()
  to = toOrig.toLowerCase()

  if (from === to) return ''

  let fromStart = 0
  while (fromStart < from.length && from.charCodeAt(fromStart) === CHAR_BACKWARD_SLASH) {
    fromStart++
  }
  let fromEnd = from.length
  while (fromEnd - 1 > fromStart && from.charCodeAt(fromEnd - 1) === CHAR_BACKWARD_SLASH) {
    fromEnd--
  }
  const fromLen = fromEnd - fromStart

  let toStart = 0
  while (toStart < to.length && to.charCodeAt(toStart) === CHAR_BACKWARD_SLASH) {
    toStart++
  }
  let toEnd = to.length
  while (toEnd - 1 > toStart && to.charCodeAt(toEnd - 1) === CHAR_BACKWARD_SLASH) {
    toEnd--
  }
  const toLen = toEnd - toStart

  const length = fromLen < toLen ? fromLen : toLen
  let lastCommonSep = -1
  let i = 0
  for (; i < length; i++) {
    const fromCode = from.charCodeAt(fromStart + i)
    if (fromCode !== to.charCodeAt(toStart + i)) {
      break
    } else if (fromCode === CHAR_BACKWARD_SLASH) {
      lastCommonSep = i
    }
  }

  if (i !== length) {
    if (lastCommonSep === -1) return toOrig
  } else {
    if (toLen > length) {
      if (to.charCodeAt(toStart + i) === CHAR_BACKWARD_SLASH) {
        return toOrig.substring(toStart + i + 1)
      }
      if (i === 2) {
        return toOrig.substring(toStart + i)
      }
    }
    if (fromLen > length) {
      if (from.charCodeAt(fromStart + i) === CHAR_BACKWARD_SLASH) {
        lastCommonSep = i
      } else if (i === 2) {
        lastCommonSep = 3
      }
    }
    if (lastCommonSep === -1) lastCommonSep = 0
  }

  let out = ''
  for (i = fromStart + lastCommonSep + 1; i <= fromEnd; ++i) {
    if (i === fromEnd || from.charCodeAt(i) === CHAR_BACKWARD_SLASH) {
      out += out.length === 0 ? '..' : '\\..'
    }
  }

  toStart += lastCommonSep

  if (out.length > 0) {
    return `${out}${toOrig.substring(toStart, toEnd)}`
  }
  if (toOrig.charCodeAt(toStart) === CHAR_BACKWARD_SLASH) {
    ++toStart
  }
  return toOrig.substring(toStart, toEnd)
}

exports.toNamespacedPath = function toNamespacedPath(path) {
  if (path.length === 0) return path

  const resolvedPath = exports.resolve(path)

  if (resolvedPath.length <= 2) return path

  if (resolvedPath.charCodeAt(0) === CHAR_BACKWARD_SLASH) {
    if (resolvedPath.charCodeAt(1) === CHAR_BACKWARD_SLASH) {
      const code = resolvedPath.charCodeAt(2)
      if (code !== CHAR_QUESTION_MARK && code !== CHAR_DOT) {
        return `\\\\?\\UNC\\${resolvedPath.substring(2)}`
      }
    }
  } else if (
    isWindowsDeviceRoot(resolvedPath.charCodeAt(0)) &&
    resolvedPath.charCodeAt(1) === CHAR_COLON &&
    resolvedPath.charCodeAt(2) === CHAR_BACKWARD_SLASH
  ) {
    return `\\\\?\\${resolvedPath}`
  }

  return path
}

exports.dirname = function dirname(path) {
  const len = path.length
  if (len === 0) return '.'
  let rootEnd = -1
  let offset = 0
  const code = path.charCodeAt(0)

  if (len === 1) {
    return isWindowsPathSeparator(code) ? path : '.'
  }

  if (isWindowsPathSeparator(code)) {
    rootEnd = offset = 1

    if (isWindowsPathSeparator(path.charCodeAt(1))) {
      let j = 2
      let last = j
      while (j < len && !isWindowsPathSeparator(path.charCodeAt(j))) {
        j++
      }
      if (j < len && j !== last) {
        last = j
        while (j < len && isWindowsPathSeparator(path.charCodeAt(j))) {
          j++
        }
        if (j < len && j !== last) {
          last = j
          while (j < len && !isWindowsPathSeparator(path.charCodeAt(j))) {
            j++
          }
          if (j === len) {
            return path
          }
          if (j !== last) {
            rootEnd = offset = j + 1
          }
        }
      }
    }
  } else if (isWindowsDeviceRoot(code) && path.charCodeAt(1) === CHAR_COLON) {
    rootEnd = len > 2 && isWindowsPathSeparator(path.charCodeAt(2)) ? 3 : 2
    offset = rootEnd
  }

  let end = -1
  let matchedSlash = true
  for (let i = len - 1; i >= offset; --i) {
    if (isWindowsPathSeparator(path.charCodeAt(i))) {
      if (!matchedSlash) {
        end = i
        break
      }
    } else {
      matchedSlash = false
    }
  }

  if (end === -1) {
    if (rootEnd === -1) return '.'

    end = rootEnd
  }
  return path.substring(0, end)
}

exports.basename = function basename(path, suffix) {
  let start = 0
  let end = -1
  let matchedSlash = true

  if (
    path.length >= 2 &&
    isWindowsDeviceRoot(path.charCodeAt(0)) &&
    path.charCodeAt(1) === CHAR_COLON
  ) {
    start = 2
  }

  if (suffix !== undefined && suffix.length > 0 && suffix.length <= path.length) {
    if (suffix === path) return ''
    let extIdx = suffix.length - 1
    let firstNonSlashEnd = -1
    for (let i = path.length - 1; i >= start; --i) {
      const code = path.charCodeAt(i)
      if (isWindowsPathSeparator(code)) {
        if (!matchedSlash) {
          start = i + 1
          break
        }
      } else {
        if (firstNonSlashEnd === -1) {
          matchedSlash = false
          firstNonSlashEnd = i + 1
        }
        if (extIdx >= 0) {
          if (code === suffix.charCodeAt(extIdx)) {
            if (--extIdx === -1) {
              end = i
            }
          } else {
            extIdx = -1
            end = firstNonSlashEnd
          }
        }
      }
    }

    if (start === end) end = firstNonSlashEnd
    else if (end === -1) end = path.length
    return path.substring(start, end)
  }
  for (let i = path.length - 1; i >= start; --i) {
    if (isWindowsPathSeparator(path.charCodeAt(i))) {
      if (!matchedSlash) {
        start = i + 1
        break
      }
    } else if (end === -1) {
      matchedSlash = false
      end = i + 1
    }
  }

  if (end === -1) return ''
  return path.substring(start, end)
}

exports.extname = function extname(path) {
  let start = 0
  let startDot = -1
  let startPart = 0
  let end = -1
  let matchedSlash = true
  let preDotState = 0

  if (
    path.length >= 2 &&
    path.charCodeAt(1) === CHAR_COLON &&
    isWindowsDeviceRoot(path.charCodeAt(0))
  ) {
    start = startPart = 2
  }

  for (let i = path.length - 1; i >= start; --i) {
    const code = path.charCodeAt(i)
    if (isWindowsPathSeparator(code)) {
      if (!matchedSlash) {
        startPart = i + 1
        break
      }
      continue
    }
    if (end === -1) {
      matchedSlash = false
      end = i + 1
    }
    if (code === CHAR_DOT) {
      if (startDot === -1) startDot = i
      else if (preDotState !== 1) preDotState = 1
    } else if (startDot !== -1) {
      preDotState = -1
    }
  }

  if (
    startDot === -1 ||
    end === -1 ||
    preDotState === 0 ||
    (preDotState === 1 && startDot === end - 1 && startDot === startPart + 1)
  ) {
    return ''
  }
  return path.substring(startDot, end)
}
{
  "name": "bare-path",
  "version": "3.0.1",
  "description": "Path manipulation library for JavaScript",
  "exports": {
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    },
    "./package": "./package.json",
    "./posix": "./lib/posix.js",
    "./win32": "./lib/win32.js"
  },
  "files": [
    "index.js",
    "index.d.ts",
    "lib",
    "NOTICE"
  ],
  "scripts": {
    "format": "prettier --write . && lunte --fix",
    "lint": "prettier --check . && lunte",
    "test": "brittle-bare --coverage test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-path.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-path/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-path#readme",
  "dependencies": {
    "bare-os": "^3.0.1"
  },
  "devDependencies": {
    "brittle": "^3.3.2",
    "lunte": "^1.8.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^2.0.0"
  }
}
const b4a = require('b4a')
const stream = require('streamx')
const { ReadableStream, WritableStream } = require('./web')

const defaultEncoding = 'utf8'

module.exports = exports = stream.Stream

exports.pipeline = stream.pipeline

exports.isStream = stream.isStream
exports.isEnding = stream.isEnding
exports.isEnded = stream.isEnded
exports.isFinishing = stream.isFinishing
exports.isFinished = stream.isFinished
exports.isDisturbed = stream.isDisturbed

exports.isErrored = function isErrored(stream) {
  return exports.getStreamError(stream) !== null
}

exports.isReadable = function isReadable(stream) {
  return stream.readable && !stream.destroying && !exports.isEnded(stream)
}

exports.isWritable = function isWritable(stream) {
  return stream.writable && !stream.destroying && !exports.isFinishing(stream)
}

exports.getStreamError = stream.getStreamError

exports.addAbortSignal = function addAbortSignal(signal, stream) {
  function onAbort() {
    stream.destroy(signal.reason)
  }

  if (signal.aborted) onAbort()
  else signal.addEventListener('abort', onAbort)

  return stream
}

exports.Stream = exports

exports.Readable = class Readable extends stream.Readable {
  constructor(opts = {}) {
    super({
      ...opts,
      byteLength: null,
      byteLengthReadable: null,
      map: null,
      mapReadable: null
    })

    if (this._construct) this._open = this._construct

    if (this._read !== stream.Readable.prototype._read) {
      this._read = read.bind(this, this._read)
    }

    if (this._destroy !== stream.Stream.prototype._destroy) {
      this._destroy = destroy.bind(this, this._destroy)
    }
  }

  get closed() {
    return !exports.isReadable(this)
  }

  get errored() {
    return stream.getStreamError(this)
  }

  push(chunk, encoding) {
    if (typeof chunk === 'string') {
      chunk = b4a.from(chunk, encoding || defaultEncoding)
    }

    return super.push(chunk)
  }

  unshift(chunk, encoding) {
    if (typeof chunk === 'string') {
      chunk = b4a.from(chunk, encoding || defaultEncoding)
    }

    super.unshift(chunk)
  }

  static fromWeb(readableStream, opts = {}) {
    const stream = readableStream._stream

    if (opts.encoding) stream.setEncoding(opts.encoding)
    if (opts.signal) exports.addAbortSignal(opts.signal, stream)

    return stream
  }

  static toWeb(readable, opts = {}) {
    return new ReadableStream(readable, opts.strategy)
  }

  async [Symbol.asyncDispose]() {
    if (!this.destroyed) this.destroy()

    await new Promise((resolve) => exports.finished(this, resolve))
  }
}

exports.Writable = class Writable extends stream.Writable {
  constructor(opts = {}) {
    super({
      ...opts,
      byteLength: null,
      byteLengthWritable,
      map: null,
      mapWritable: null
    })

    if (this._construct) this._open = this._construct

    if (this._write !== stream.Writable.prototype._write) {
      this._write = write.bind(this, this._write)
    }

    if (this._destroy !== stream.Stream.prototype._destroy) {
      this._destroy = destroy.bind(this, this._destroy)
    }
  }

  get closed() {
    return !exports.isWritable(this)
  }

  get errored() {
    return stream.getStreamError(this)
  }

  write(chunk, encoding, cb) {
    if (typeof encoding === 'function') {
      cb = encoding
      encoding = null
    }

    if (typeof chunk === 'string') {
      encoding = encoding || defaultEncoding
      chunk = b4a.from(chunk, encoding)
    } else {
      encoding = 'buffer'
    }

    const result = super.write({ chunk, encoding })

    if (cb) stream.Writable.drained(this).then(() => cb(null), cb)

    return result
  }

  end(chunk, encoding, cb) {
    if (typeof chunk === 'function') {
      cb = chunk
      chunk = null
    } else if (typeof encoding === 'function') {
      cb = encoding
      encoding = null
    }

    if (typeof chunk === 'string') {
      encoding = encoding || defaultEncoding
      chunk = b4a.from(chunk, encoding || defaultEncoding)
    } else {
      encoding = 'buffer'
    }

    const result =
      chunk !== undefined && chunk !== null ? super.end({ chunk, encoding }) : super.end()

    if (cb) this.once('finish', () => cb(null))

    return result
  }

  static fromWeb(writableStream, opts = {}) {
    const stream = writableStream._stream

    if (opts.signal) exports.addAbortSignal(opts.signal, stream)

    return stream
  }

  static toWeb(writable) {
    return new WritableStream(writable)
  }

  async [Symbol.asyncDispose]() {
    if (!this.destroyed) this.destroy()

    await new Promise((resolve) => exports.finished(this, resolve))
  }
}

exports.Duplex = class Duplex extends stream.Duplex {
  constructor(opts = {}) {
    super({
      ...opts,
      byteLength: null,
      byteLengthReadable: null,
      byteLengthWritable,
      map: null,
      mapReadable: null,
      mapWritable: null
    })

    if (this._construct) this._open = this._construct

    if (this._read !== stream.Readable.prototype._read) {
      this._read = read.bind(this, this._read)
    }

    if (this._write !== stream.Duplex.prototype._write) {
      this._write = write.bind(this, this._write)
    }

    if (this._destroy !== stream.Stream.prototype._destroy) {
      this._destroy = destroy.bind(this, this._destroy)
    }
  }

  push(chunk, encoding) {
    if (typeof chunk === 'string') {
      chunk = b4a.from(chunk, encoding || defaultEncoding)
    }

    return super.push(chunk)
  }

  unshift(chunk, encoding) {
    if (typeof chunk === 'string') {
      chunk = b4a.from(chunk, encoding || defaultEncoding)
    }

    super.unshift(chunk)
  }

  write(chunk, encoding, cb) {
    if (typeof encoding === 'function') {
      cb = encoding
      encoding = null
    }

    if (typeof chunk === 'string') {
      encoding = encoding || defaultEncoding
      chunk = b4a.from(chunk, encoding)
    } else {
      encoding = 'buffer'
    }

    const result = super.write({ chunk, encoding })

    if (cb) stream.Writable.drained(this).then(() => cb(null), cb)

    return result
  }

  end(chunk, encoding, cb) {
    if (typeof chunk === 'function') {
      cb = chunk
      chunk = null
    } else if (typeof encoding === 'function') {
      cb = encoding
      encoding = null
    }

    if (typeof chunk === 'string') {
      encoding = encoding || defaultEncoding
      chunk = b4a.from(chunk, encoding)
    } else {
      encoding = 'buffer'
    }

    const result =
      chunk !== undefined && chunk !== null ? super.end({ chunk, encoding }) : super.end()

    if (cb) this.once('finish', () => cb(null))

    return result
  }

  static fromWeb({ readable: readableStream, writable: writableStream }, opts) {
    const readable = exports.Readable.fromWeb(readableStream, opts)
    const writable = exports.Writable.fromWeb(writableStream, opts)

    const duplex = new exports.Duplex({
      write(data, encoding, cb) {
        writable.write(data, encoding, cb)
      }
    })

    readable
      .on('data', (data) => duplex.push(data))
      .on('end', () => duplex.push(null))
      .on('error', (err) => duplex.destroy(err))

    writable.on('finish', () => duplex.end()).on('error', (err) => duplex.destroy(err))

    return duplex
  }

  static toWeb(duplex) {
    const readableStream = exports.Readable.toWeb(duplex)
    const writableStream = exports.Writable.toWeb(duplex)

    return { readable: readableStream, writable: writableStream }
  }
}

class DuplexSide extends exports.Duplex {
  constructor(opts) {
    super(opts)

    this._otherSide = null
    this._cb = null
  }

  _read() {
    const cb = this._cb
    if (!cb) return

    this._cb = null
    cb()
  }

  _write(chunk, encoding, cb) {
    this._otherSide.push(chunk, encoding)
    this._otherSide._cb = cb
  }

  _final(cb) {
    this._otherSide.on('end', cb)
    this._otherSide.push(null)
  }
}

exports.duplexPair = function duplexPair(opts) {
  const sideA = new DuplexSide(opts)
  const sideB = new DuplexSide(opts)

  sideA._otherSide = sideB
  sideB._otherSide = sideA

  return [sideA, sideB]
}

exports.Transform = class Transform extends stream.Transform {
  constructor(opts = {}) {
    super({
      ...opts,
      byteLength: null,
      byteLengthReadable: null,
      byteLengthWritable,
      map: null,
      mapReadable: null,
      mapWritable: null
    })

    if (this._transform !== stream.Transform.prototype._transform) {
      this._transform = transform.bind(this, this._transform)
    } else {
      this._transform = passthrough
    }
  }

  push(chunk, encoding) {
    if (typeof chunk === 'string') {
      chunk = b4a.from(chunk, encoding || defaultEncoding)
    }

    return super.push(chunk)
  }

  unshift(chunk, encoding) {
    if (typeof chunk === 'string') {
      chunk = b4a.from(chunk, encoding || defaultEncoding)
    }

    super.unshift(chunk)
  }

  write(chunk, encoding, cb) {
    if (typeof encoding === 'function') {
      cb = encoding
      encoding = null
    }

    if (typeof chunk === 'string') {
      encoding = encoding || defaultEncoding
      chunk = b4a.from(chunk, encoding)
    } else {
      encoding = 'buffer'
    }

    const result = super.write({ chunk, encoding })

    if (cb) stream.Writable.drained(this).then(() => cb(null), cb)

    return result
  }

  end(chunk, encoding, cb) {
    if (typeof chunk === 'function') {
      cb = chunk
      chunk = null
    } else if (typeof encoding === 'function') {
      cb = encoding
      encoding = null
    }

    if (typeof chunk === 'string') {
      encoding = encoding || defaultEncoding
      chunk = b4a.from(chunk, encoding)
    } else {
      encoding = 'buffer'
    }

    const result =
      chunk !== undefined && chunk !== null ? super.end({ chunk, encoding }) : super.end()

    if (cb) this.once('finish', () => cb(null))

    return result
  }
}

exports.PassThrough = class PassThrough extends exports.Transform {}

exports.finished = function finished(stream, opts, cb) {
  if (typeof opts === 'function') {
    cb = opts
    opts = {}
  }

  if (!opts) opts = {}

  const { cleanup = false } = opts

  const done = () => {
    cb(exports.getStreamError(stream, { all: true }))

    if (cleanup) detach()
  }

  const detach = () => {
    stream.off('close', done)
    stream.off('error', noop)
  }

  if (stream.destroyed) {
    done()
  } else {
    stream.on('close', done)
    stream.on('error', noop)
  }

  return detach
}

function read(read, cb) {
  read.call(this, 65536)

  cb(null)
}

function write(write, data, cb) {
  write.call(this, data.chunk, data.encoding, cb)
}

function transform(transform, data, cb) {
  transform.call(this, data.chunk, data.encoding, cb)
}

function destroy(destroy, cb) {
  destroy.call(this, exports.getStreamError(this), cb)
}

function passthrough(data, cb) {
  cb(null, data.chunk)
}

function byteLengthWritable(data) {
  return data.chunk.byteLength
}

function noop() {}
{
  "name": "bare-stream",
  "version": "2.13.3",
  "description": "Streaming data for JavaScript",
  "exports": {
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    },
    "./package": "./package.json",
    "./promises": "./promises.js",
    "./web": {
      "types": "./web.d.ts",
      "default": "./web.js"
    },
    "./global": {
      "types": "./global.d.ts",
      "default": "./global.js"
    }
  },
  "files": [
    "index.js",
    "index.d.ts",
    "promises.js",
    "web.js",
    "web.d.ts",
    "global.js",
    "global.d.ts"
  ],
  "scripts": {
    "format": "prettier --write . && lunte --fix",
    "lint": "prettier --check . && lunte",
    "test": "brittle-bare --coverage test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-stream.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-stream/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-stream#readme",
  "dependencies": {
    "b4a": "^1.8.1",
    "streamx": "^2.25.0",
    "teex": "^1.0.1"
  },
  "devDependencies": {
    "bare-abort-controller": "^1.1.0",
    "bare-buffer": "^3.0.0",
    "bare-events": "^2.5.4",
    "brittle": "^3.5.2",
    "lunte": "^1.6.0",
    "prettier": "^3.3.3",
    "prettier-config-holepunch": "^2.0.0"
  },
  "peerDependencies": {
    "bare-abort-controller": "*",
    "bare-buffer": "*",
    "bare-events": "*"
  },
  "peerDependenciesMeta": {
    "bare-abort-controller": {
      "optional": true
    },
    "bare-buffer": {
      "optional": true
    },
    "bare-events": {
      "optional": true
    }
  }
}
const { Readable, Writable, Transform, getStreamError, isStreamx, isDisturbed } = require('streamx')
const tee = require('teex')

const readableKind = Symbol.for('bare.stream.readable.kind')
const writableKind = Symbol.for('bare.stream.writable.kind')
const transformKind = Symbol.for('bare.stream.transform.kind')

// https://streams.spec.whatwg.org/#readablestreamdefaultreader
exports.ReadableStreamDefaultReader = class ReadableStreamDefaultReader {
  constructor(stream) {
    this._stream = stream
    this._stream._stream.once('close', onclose).once('error', onerror)

    const closed = Promise.withResolvers()

    // Avoid unhandled exceptions
    closed.promise.catch(noop)

    this._closed = closed

    function onclose() {
      closed.resolve()
    }

    function onerror(err) {
      closed.reject(err)
    }
  }

  get closed() {
    return this._closed.promise
  }

  read() {
    const stream = this._stream._stream

    return new Promise((resolve, reject) => {
      const err = getStreamError(stream)

      if (err) return reject(err)

      if (stream.destroyed) {
        return resolve({ value: undefined, done: true })
      }

      const value = stream.read()

      if (value !== null) {
        return resolve({ value, done: false })
      }

      stream.once('readable', onreadable).once('close', onclose).once('error', onerror)

      function onreadable() {
        const value = stream.read()

        ondone(null, value === null ? { value: undefined, done: true } : { value, done: false })
      }

      function onclose() {
        ondone(null, { value: undefined, done: true })
      }

      function onerror(err) {
        ondone(err, null)
      }

      function ondone(err, value) {
        stream.off('readable', onreadable).off('close', onclose).off('error', onerror)

        if (err) reject(err)
        else resolve(value)
      }
    })
  }

  releaseLock() {
    this._closed.reject(new TypeError('Reader was released'))
    this._stream._releaseLock()
    this._stream = null
  }

  cancel(reason = new TypeError('Stream was cancelled')) {
    const stream = this._stream._stream

    if (stream.destroyed) return Promise.resolve()

    return new Promise((resolve) =>
      stream.once('close', resolve).once('error', noop).destroy(reason)
    )
  }
}

// https://streams.spec.whatwg.org/#readablestreamdefaultcontroller
exports.ReadableStreamDefaultController = class ReadableStreamDefaultController {
  constructor(stream) {
    this._stream = stream
  }

  get desiredSize() {
    const stream = this._stream._stream

    return stream._readableState.highWaterMark - stream._readableState.buffered
  }

  enqueue(data) {
    this._stream._stream.push(data)
  }

  close() {
    this._stream._stream.push(null)
  }

  error(err) {
    this._stream._stream.destroy(err)
  }
}

// https://streams.spec.whatwg.org/#readablestream
class ReadableStream {
  static get [readableKind]() {
    return 0 // Compatibility version
  }

  static from(iterable) {
    return new ReadableStream(Readable.from(iterable))
  }

  constructor(underlyingSource = {}, queuingStrategy) {
    if (isStreamx(underlyingSource)) {
      this._stream = underlyingSource
    } else {
      if (queuingStrategy === undefined) {
        queuingStrategy = new exports.CountQueuingStrategy()
      }

      const { start, pull, cancel } = underlyingSource
      const { highWaterMark = 1, size = defaultSize } = queuingStrategy

      this._stream = new Readable({ highWaterMark, byteLength: size })

      const controller = new exports.ReadableStreamDefaultController(this)

      try {
        let starting = Promise.resolve()

        if (start) starting = forwardError(start.call(this, controller), controller)

        if (pull) {
          this._stream._read = this._read.bind(this, starting, pull.bind(this, controller))
        }

        if (cancel) {
          this._stream.once('error', cancel.bind(this))
        }
      } catch (err) {
        controller.error(err)
      }
    }

    this._reader = null
  }

  get [readableKind]() {
    return ReadableStream[readableKind]
  }

  get locked() {
    return this._reader !== null
  }

  getReader() {
    if (this.locked) throw new TypeError('Stream is locked')

    this._reader = new exports.ReadableStreamDefaultReader(this)

    return this._reader
  }

  cancel(reason = new TypeError('Stream was cancelled')) {
    const stream = this._stream

    if (stream.destroyed) return Promise.resolve()

    if (this.locked) return Promise.reject(new TypeError('Stream is locked'))

    return new Promise((resolve) =>
      stream.once('close', resolve).once('error', noop).destroy(reason)
    )
  }

  tee() {
    const [a, b] = tee(this._stream)

    return [new ReadableStream(a), new ReadableStream(b)]
  }

  pipeTo(destination) {
    return new Promise((resolve, reject) =>
      this._stream.pipe(destination._stream, (err) => {
        err ? reject(err) : resolve()
      })
    )
  }

  [Symbol.asyncIterator]() {
    return this._stream[Symbol.asyncIterator]()
  }

  _releaseLock() {
    this._reader = null
  }

  async _read(starting, pull, cb) {
    await starting

    let err = null
    try {
      await pull()
    } catch (e) {
      err = e
    }
    cb(err)
  }
}

function defaultSize() {
  return 1
}

exports.ReadableStream = ReadableStream

// https://streams.spec.whatwg.org/#countqueuingstrategy
exports.CountQueuingStrategy = class CountQueuingStrategy {
  constructor(opts = {}) {
    const { highWaterMark = 1 } = opts

    this.highWaterMark = highWaterMark
  }

  size(chunk) {
    return 1
  }
}

// https://streams.spec.whatwg.org/#bytelengthqueuingstrategy
exports.ByteLengthQueuingStrategy = class ByteLengthQueuingStrategy {
  constructor(opts = {}) {
    const { highWaterMark = 16384 } = opts

    this.highWaterMark = highWaterMark
  }

  size(chunk) {
    return chunk.byteLength
  }
}

exports.isReadableStream = function isReadableStream(value) {
  if (value instanceof ReadableStream) return true

  return (
    typeof value === 'object' &&
    value !== null &&
    value[readableKind] === ReadableStream[readableKind]
  )
}

// https://streams.spec.whatwg.org/#readablestream-errored
exports.isReadableStreamErrored = function isReadableStreamErrored(stream) {
  return getStreamError(stream._stream) !== null
}

// https://streams.spec.whatwg.org/#is-readable-stream-disturbed
exports.isReadableStreamDisturbed = function isReadableStreamDisturbed(stream) {
  return isDisturbed(stream._stream)
}

// https://streams.spec.whatwg.org/#writablestreamdefaultwriter
exports.WritableStreamDefaultWriter = class WritableStreamDefaultWriter {
  constructor(stream) {
    this._stream = stream
    this._stream._stream.once('close', onclose).once('error', onerror)

    const closed = Promise.withResolvers()

    // Avoid unhandled exceptions
    closed.promise.catch(noop)

    this._closed = closed

    function onclose() {
      closed.resolve()
    }

    function onerror(err) {
      closed.reject(err)
    }
  }

  get desiredSize() {
    const stream = this._stream._stream

    return stream._writableState.highWaterMark - stream._writableState.buffered
  }

  get closed() {
    return this._closed.promise
  }

  get ready() {
    const stream = this._stream._stream

    if (getStreamError(stream)) return Promise.reject()

    return Writable.drained(stream).then()
  }

  async write(chunk) {
    const stream = this._stream._stream

    let err = getStreamError(stream)
    if (err) return Promise.reject(err)

    stream.write(chunk)

    await Writable.drained(stream)

    err = getStreamError(stream)
    if (err) return Promise.reject(err)
  }

  releaseLock() {
    this._closed.reject(new TypeError('Writer was released'))
    this._stream._releaseLock()
    this._stream = null
  }

  close() {
    const stream = this._stream._stream

    if (stream.destroyed) return Promise.resolve()

    return new Promise((resolve) => stream.once('close', resolve).end())
  }

  abort(reason = new TypeError('Stream was aborted')) {
    const stream = this._stream._stream

    if (stream.destroyed) return Promise.resolve()

    return new Promise((resolve) => stream.once('close', resolve).destroy(reason))
  }
}

// https://streams.spec.whatwg.org/#writablestreamdefaultcontroller
exports.WritableStreamDefaultController = class WritableStreamDefaultController {
  constructor(stream) {
    this._stream = stream
  }

  error(err) {
    this._stream._stream.destroy(err)
  }
}

// https://streams.spec.whatwg.org/#writablestream
class WritableStream {
  static get [writableKind]() {
    return 0 // Compatibility version
  }

  constructor(underlyingSink = {}, queuingStrategy = {}) {
    if (isStreamx(underlyingSink)) {
      this._stream = underlyingSink
    } else {
      if (queuingStrategy === undefined) {
        queuingStrategy = new exports.CountQueuingStrategy()
      }

      const { start, write, close, abort } = underlyingSink
      const { highWaterMark = 1, size = defaultSize } = queuingStrategy

      this._stream = new Writable({ highWaterMark, byteLength: size })

      const controller = new exports.WritableStreamDefaultController(this)

      this._controller = controller

      try {
        let starting = Promise.resolve()

        if (start) starting = forwardError(start.call(this, controller), controller)

        if (write) {
          this._stream._write = this._write.bind(this, starting, write.bind(this))
        }

        if (close) {
          this._stream._destroy = this._destroy.bind(this, close.call(this))
        }

        if (abort) {
          this._stream.once('error', abort.bind(this))
        }
      } catch (err) {
        controller.error(err)
      }
    }

    this._writer = null
  }

  get [writableKind]() {
    return WritableStream[writableKind]
  }

  get locked() {
    return this._writer !== null
  }

  getWriter() {
    if (this.locked) throw new TypeError('Stream is locked')

    this._writer = new exports.WritableStreamDefaultWriter(this)

    return this._writer
  }

  abort(reason = new TypeError('Stream was aborted')) {
    if (this._stream.destroyed) return Promise.resolve()

    if (this.locked) return Promise.reject(new TypeError('Stream is locked'))

    return new Promise((resolve) => this._stream.once('close', resolve).destroy(reason))
  }

  close() {
    if (this._stream.destroyed) return Promise.resolve()

    if (this.locked) return Promise.reject(new TypeError('Stream is locked'))

    return new Promise((resolve) => this._stream.once('close', resolve).end())
  }

  _releaseLock() {
    this._writer = null
  }

  async _write(starting, write, data, cb) {
    await starting

    let err = null
    try {
      await write(data, this._controller)
    } catch (e) {
      err = e
    }
    cb(err)
  }

  async _destroy(closing, cb) {
    let err = null
    try {
      await closing
    } catch (e) {
      err = e
    }
    cb(err)
  }
}

exports.WritableStream = WritableStream

exports.isWritableStream = function isWritableStream(value) {
  if (value instanceof WritableStream) return true

  return (
    typeof value === 'object' &&
    value !== null &&
    value[writableKind] === WritableStream[writableKind]
  )
}

// https://streams.spec.whatwg.org/#transformstreamdefaultcontroller
exports.TransformStreamDefaultController = class TransformStreamDefaultController {
  constructor(stream) {
    this._stream = stream
  }

  get desiredSize() {
    const stream = this._stream._stream

    return stream._readableState.highWaterMark - stream._readableState.buffered
  }

  enqueue(data) {
    this._stream._stream.push(data)
  }

  error(err) {
    this._stream._stream.destroy(err)
  }

  terminate() {
    const stream = this._stream._stream

    stream.push(null)
    stream.destroy(new TypeError('Stream has been terminated'))
  }
}

// https://streams.spec.whatwg.org/#transformstream
class TransformStream {
  static get [transformKind]() {
    return 0 // Compatibility version
  }

  constructor(transformer = {}, writableStrategy = {}, readableStrategy = {}) {
    if (isStreamx(transformer)) {
      this._stream = transformer
    } else {
      const { start, transform, flush } = transformer

      this._stream = new Transform({ ...writableStrategy, ...readableStrategy })

      const controller = new exports.TransformStreamDefaultController(this)

      this._controller = controller

      try {
        let starting = Promise.resolve()

        if (start) starting = forwardError(start.call(this, controller), controller)

        if (transform) {
          this._stream._transform = this._transform.bind(this, starting, transform.bind(this))
        }

        if (flush) {
          this._stream._flush = this._flush.bind(this, flush.call(this, this._controller))
        }
      } catch (err) {
        controller.error(err)
      }
    }

    this._writable = new WritableStream(this._stream)
    this._readable = new ReadableStream(this._stream)
  }

  get [transformKind]() {
    return TransformStream[transformKind]
  }

  get writable() {
    return this._writable
  }

  get readable() {
    return this._readable
  }

  async _transform(starting, transform, data, cb) {
    await starting

    let err = null
    try {
      await transform(data, this._controller)
    } catch (e) {
      err = e
    }
    cb(err)
  }

  async _flush(flush, cb) {
    let err = null
    try {
      await flush
    } catch (e) {
      err = e
    }
    cb(err)
  }
}

exports.TransformStream = TransformStream

exports.isTransformStream = function isTransformStream(value) {
  if (value instanceof TransformStream) return true

  return (
    typeof value === 'object' &&
    value !== null &&
    value[transformKind] === TransformStream[transformKind]
  )
}

async function forwardError(promise, controller) {
  try {
    await promise
  } catch (err) {
    controller.error(err)
  }
}

function noop() {}
module.exports = require.addon()
const binding = require('./binding')

const t = binding.constants

class Type {
  constructor(type) {
    this._type = type
  }

  isUndefined() {
    return this._type === t.UNDEFINED
  }

  isNull() {
    return this._type === t.NULL
  }

  isBoolean() {
    return this._type === t.BOOLEAN
  }

  isNumber() {
    return (this._type & 0xff) === t.NUMBER
  }

  isInt32() {
    return (this._type & (0xff | t.INT32)) === (t.NUMBER | t.INT32)
  }

  isUint32() {
    return (this._type & (0xff | t.UINT32)) === (t.NUMBER | t.UINT32)
  }

  isString() {
    return this._type === t.STRING
  }

  isSymbol() {
    return this._type === t.SYMBOL
  }

  isObject() {
    return (this._type & 0xff) === t.OBJECT
  }

  isArray() {
    return this._type === (t.OBJECT | t.ARRAY)
  }

  isArguments() {
    return this._type === (t.OBJECT | t.ARGUMENTS)
  }

  isDate() {
    return this._type === (t.OBJECT | t.DATE)
  }

  isRegExp() {
    return this._type === (t.OBJECT | t.REGEXP)
  }

  isError() {
    return this._type === (t.OBJECT | t.ERROR)
  }

  isPromise() {
    return this._type === (t.OBJECT | t.PROMISE)
  }

  isProxy() {
    return this._type === (t.OBJECT | t.PROXY)
  }

  isGenerator() {
    return this._type === (t.OBJECT | t.GENERATOR)
  }

  isMap() {
    return this._type === (t.OBJECT | t.MAP)
  }

  isSet() {
    return this._type === (t.OBJECT | t.SET)
  }

  isWeakMap() {
    return this._type === (t.OBJECT | t.WEAK_MAP)
  }

  isWeakSet() {
    return this._type === (t.OBJECT | t.WEAK_SET)
  }

  isWeakRef() {
    return this._type === (t.OBJECT | t.WEAK_REF)
  }

  isArrayBuffer() {
    return this._type === (t.OBJECT | t.ARRAYBUFFER)
  }

  isSharedArrayBuffer() {
    return this._type === (t.OBJECT | t.SHAREDARRAYBUFFER)
  }

  isTypedArray() {
    return (this._type & 0xffff) === (t.OBJECT | t.TYPEDARRAY)
  }

  isInt8Array() {
    return this._type === (t.OBJECT | t.TYPEDARRAY | t.INT8ARRAY)
  }

  isUint8Array() {
    return this._type === (t.OBJECT | t.TYPEDARRAY | t.UINT8ARRAY)
  }

  isUint8ClampedArray() {
    return this._type === (t.OBJECT | t.TYPEDARRAY | t.UINT8CLAMPEDARRAY)
  }

  isInt16Array() {
    return this._type === (t.OBJECT | t.TYPEDARRAY | t.INT16ARRAY)
  }

  isUint16Array() {
    return this._type === (t.OBJECT | t.TYPEDARRAY | t.UINT16ARRAY)
  }

  isInt32Array() {
    return this._type === (t.OBJECT | t.TYPEDARRAY | t.INT32ARRAY)
  }

  isUint32Array() {
    return this._type === (t.OBJECT | t.TYPEDARRAY | t.UINT32ARRAY)
  }

  isFloat16Array() {
    return this._type === (t.OBJECT | t.TYPEDARRAY | t.FLOAT16ARRAY)
  }

  isFloat32Array() {
    return this._type === (t.OBJECT | t.TYPEDARRAY | t.FLOAT32ARRAY)
  }

  isFloat64Array() {
    return this._type === (t.OBJECT | t.TYPEDARRAY | t.FLOAT64ARRAY)
  }

  isBigInt64Array() {
    return this._type === (t.OBJECT | t.TYPEDARRAY | t.BIGINT64ARRAY)
  }

  isBigUint64Array() {
    return this._type === (t.OBJECT | t.TYPEDARRAY | t.BIGUINT64ARRAY)
  }

  isDataView() {
    return this._type === (t.OBJECT | t.DATAVIEW)
  }

  isModuleNamespace() {
    return this._type === (t.OBJECT | t.MODULE_NAMESPACE)
  }

  isFunction() {
    return (this._type & 0xff) === t.FUNCTION
  }

  isAsyncFunction() {
    return (
      (this._type & (0xff | t.ASYNC_FUNCTION)) ===
      (t.FUNCTION | t.ASYNC_FUNCTION)
    )
  }

  isGeneratorFunction() {
    return (
      (this._type & (0xff | t.GENERATOR_FUNCTION)) ===
      (t.FUNCTION | t.GENERATOR_FUNCTION)
    )
  }

  isExternal() {
    return this._type === t.EXTERNAL
  }

  isBigInt() {
    return this._type === t.BIGINT
  }
}

module.exports = exports = function type(value) {
  switch (typeof value) {
    case 'undefined':
      return new Type(t.UNDEFINED)
    case 'boolean':
      return new Type(t.BOOLEAN)
    case 'number':
      return new Type(
        Number.isSafeInteger(value) ? binding.type(value) : t.NUMBER
      )
    case 'string':
      return new Type(t.STRING)
    case 'symbol':
      return new Type(t.SYMBOL)
    case 'object':
      return new Type(value === null ? t.NULL : binding.type(value))
    case 'function':
      return new Type(binding.type(value))
    case 'bigint':
      return new Type(t.BIGINT)
  }
}

exports.createTag = function createTag(...components) {
  const tag = new Uint32Array(4)
  for (let i = 0; i < 4; i++) tag[i] = components[i] || 0
  return tag
}

exports.addTag = function addTag(object, tag) {
  binding.addTag(object, tag)
}

exports.checkTag = function checkTag(object, tag) {
  return binding.checkTag(object, tag)
}
{
  "name": "bare-type",
  "version": "1.1.0",
  "description": "Cross-realm type predicates for Bare",
  "exports": {
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    },
    "./package": "./package.json"
  },
  "files": [
    "index.js",
    "index.d.ts",
    "binding.c",
    "binding.js",
    "CMakeLists.txt",
    "prebuilds"
  ],
  "addon": true,
  "scripts": {
    "test": "prettier . --check && bare test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-type.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-type/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-type#readme",
  "engines": {
    "bare": ">=1.2.0"
  },
  "devDependencies": {
    "brittle": "^3.4.0",
    "cmake-bare": "^1.1.6",
    "prettier": "^3.4.1",
    "prettier-config-standard": "^7.0.0"
  }
}
module.exports = require.addon()
const path = require('bare-path')
const binding = require('./binding')
const errors = require('./lib/errors')
const URLSearchParams = require('./lib/url-search-params')

const kind = Symbol.for('bare.url.kind')

const isWindows = Bare.platform === 'win32'

class URL {
  static get [kind]() {
    return 0 // Compatibility version
  }

  constructor(input, base, opts = {}) {
    if (arguments.length === 0) throw errors.INVALID_URL()

    input = String(input)

    if (base !== undefined) base = String(base)

    this._components = new Uint32Array(8)

    this._parse(input, base, opts.throw !== false)

    if (this._href) this._params = new URLSearchParams(this.search, this)
  }

  get [kind]() {
    return URL[kind]
  }

  // https://url.spec.whatwg.org/#dom-url-href

  get href() {
    return this._href
  }

  set href(value) {
    this._update(value)

    this._params._parse(this.search)
  }

  // https://url.spec.whatwg.org/#dom-url-protocol

  get protocol() {
    return this._slice(0, this._components[0]) + ':'
  }

  set protocol(value) {
    this._update(this._replace(value.replace(/:+$/, ''), 0, this._components[0]))
  }

  // https://url.spec.whatwg.org/#dom-url-username

  get username() {
    return this._slice(this._components[0] + 3 /* :// */, this._components[1])
  }

  set username(value) {
    if (cannotHaveCredentialsOrPort(this)) {
      return
    }

    if (this.username === '') value += '@'

    this._update(this._replace(value, this._components[0] + 3 /* :// */, this._components[1]))
  }

  // https://url.spec.whatwg.org/#dom-url-password

  get password() {
    return this._href.slice(this._components[1] + 1 /* : */, this._components[2] - 1 /* @ */)
  }

  set password(value) {
    if (cannotHaveCredentialsOrPort(this)) {
      return
    }

    let start = this._components[1] + 1 /* : */
    let end = this._components[2] - 1 /* @ */

    if (this.password === '') {
      value = ':' + value
      start--
    }

    if (this.username === '') {
      value += '@'
      end++
    }

    this._update(this._replace(value, start, end))
  }

  // https://url.spec.whatwg.org/#dom-url-host

  get host() {
    return this._slice(this._components[2], this._components[5])
  }

  set host(value) {
    if (hasOpaquePath(this)) {
      return
    }

    this._update(
      this._replace(value, this._components[2], this._components[value.includes(':') ? 5 : 3])
    )
  }

  // https://url.spec.whatwg.org/#dom-url-hostname

  get hostname() {
    return this._slice(this._components[2], this._components[3])
  }

  set hostname(value) {
    if (hasOpaquePath(this)) {
      return
    }

    this._update(this._replace(value, this._components[2], this._components[3]))
  }

  // https://url.spec.whatwg.org/#dom-url-port

  get port() {
    return this._slice(this._components[3] + 1 /* : */, this._components[5])
  }

  set port(value) {
    if (cannotHaveCredentialsOrPort(this)) {
      return
    }

    let start = this._components[3] + 1 /* : */

    if (this.port === '') {
      value = ':' + value
      start--
    }

    this._update(this._replace(value, start, this._components[5]))
  }

  // https://url.spec.whatwg.org/#dom-url-pathname

  get pathname() {
    return this._slice(this._components[5], this._components[6] - 1 /* ? */)
  }

  set pathname(value) {
    if (hasOpaquePath(this)) {
      return
    }

    if (value[0] !== '/' && value[0] !== '\\') {
      value = '/' + value
    }

    this._update(this._replace(value, this._components[5], this._components[6] - 1 /* ? */))
  }

  // https://url.spec.whatwg.org/#dom-url-search

  get search() {
    return this._slice(this._components[6] - 1 /* ? */, this._components[7] - 1 /* # */)
  }

  set search(value) {
    if (value && value[0] !== '?') value = '?' + value

    this._update(
      this._replace(value, this._components[6] - 1 /* ? */, this._components[7] - 1 /* # */)
    )

    this._params._parse(this.search)
  }

  // https://url.spec.whatwg.org/#dom-url-searchparams

  get searchParams() {
    return this._params
  }

  // https://url.spec.whatwg.org/#dom-url-hash

  get hash() {
    return this._slice(this._components[7] - 1 /* # */)
  }

  set hash(value) {
    if (value && value[0] !== '#') value = '#' + value

    this._update(this._replace(value, this._components[7] - 1 /* # */))
  }

  toString() {
    return this._href
  }

  toJSON() {
    return this._href
  }

  [Symbol.for('bare.inspect')]() {
    return {
      __proto__: { constructor: URL },

      href: this.href,
      protocol: this.protocol,
      username: this.username,
      password: this.password,
      host: this.host,
      hostname: this.hostname,
      port: this.port,
      pathname: this.pathname,
      search: this.search,
      searchParams: this.searchParams,
      hash: this.hash
    }
  }

  _slice(start, end = this._href.length) {
    return this._href.slice(start, end)
  }

  _replace(replacement, start, end = this._href.length) {
    return this._slice(0, start) + replacement + this._slice(end)
  }

  _parse(input, base, shouldThrow) {
    try {
      this._href = binding.parse(
        String(input),
        base ? String(base) : null,
        this._components,
        shouldThrow
      )
    } catch (err) {
      if (err instanceof TypeError) throw err

      throw errors.INVALID_URL(`Invalid URL '${input}'`, input)
    }
  }

  _update(input) {
    try {
      this._parse(input, null, true)
    } catch (err) {
      if (err instanceof TypeError) throw err
    }
  }
}

module.exports = exports = URL

// https://url.spec.whatwg.org/#url-opaque-path
function hasOpaquePath(url) {
  return url.pathname[0] !== '/'
}

// https://url.spec.whatwg.org/#cannot-have-a-username-password-port
function cannotHaveCredentialsOrPort(url) {
  return url.hostname === '' || url.protocol === 'file:'
}

exports.URL = URL
exports.URLSearchParams = URLSearchParams

exports.errors = errors

exports.isURL = function isURL(value) {
  if (value instanceof URL) return true

  return typeof value === 'object' && value !== null && value[kind] === URL[kind]
}

exports.isURLSearchParams = URLSearchParams.isURLSearchParams

// https://url.spec.whatwg.org/#dom-url-parse
exports.parse = function parse(input, base) {
  const url = new URL(input, base, { throw: false })
  return url._href ? url : null
}

// https://url.spec.whatwg.org/#dom-url-canparse
exports.canParse = function canParse(input, base) {
  return binding.canParse(String(input), base ? String(base) : null)
}

exports.fileURLToPath = function fileURLToPath(url) {
  if (typeof url === 'string') {
    url = new URL(url)
  }

  if (url.protocol !== 'file:') {
    throw errors.INVALID_URL_SCHEME('The URL must use the file: protocol')
  }

  if (isWindows) {
    if (/%2f|%5c/i.test(url.pathname)) {
      throw errors.INVALID_FILE_URL_PATH(
        'The file: URL path must not include encoded \\ or / characters'
      )
    }
  } else {
    if (url.hostname) {
      throw errors.INVALID_FILE_URL_HOST("The file: URL host must be 'localhost' or empty")
    }

    if (/%2f/i.test(url.pathname)) {
      throw errors.INVALID_FILE_URL_PATH('The file: URL path must not include encoded / characters')
    }
  }

  if (/%00/i.test(url.pathname)) {
    throw errors.INVALID_FILE_URL_PATH('The file: URL path must not include encoded NUL characters')
  }

  const pathname = path.normalize(decodeURIComponent(url.pathname))

  if (isWindows) {
    if (url.hostname) return '\\\\' + url.hostname + pathname

    const letter = pathname.charCodeAt(1) | 0x20

    if (letter < 0x61 /* a */ || letter > 0x7a /* z */ || pathname.charCodeAt(2) !== 0x3a /* : */) {
      throw errors.INVALID_FILE_URL_PATH('The file: URL path must be absolute')
    }

    return pathname.slice(1)
  }

  return pathname
}

exports.pathToFileURL = function pathToFileURL(pathname) {
  let resolved = path.resolve(pathname)

  if (pathname[pathname.length - 1] === '/') {
    resolved += '/'
  } else if (isWindows && pathname[pathname.length - 1] === '\\') {
    resolved += '\\'
  }

  resolved = resolved
    .replaceAll('%', '%25') // Must be first
    .replaceAll('#', '%23')
    .replaceAll('?', '%3f')
    .replaceAll('\n', '%0a')
    .replaceAll('\r', '%0d')
    .replaceAll('\t', '%09')

  if (!isWindows) {
    resolved = resolved.replaceAll('\\', '%5c')
  }

  return new URL('file:' + resolved)
}

exports.format = function format(parts) {
  const { protocol, auth, host, hostname, port, pathname, search, query, hash, slashes } = parts

  let result = ''

  if (typeof protocol === 'string') {
    result += protocol

    if (protocol[protocol.length - 1] !== ':') {
      result += ':'
    }

    if (slashes === true || /https?|ftp|gopher|file/.test(protocol)) {
      result += '//'
    }
  }

  if (typeof auth === 'string') {
    if (host || hostname) result += auth + '@'
  }

  if (typeof host === 'string') result += host
  else {
    result += hostname

    if (port) result += ':' + port
  }

  if (typeof pathname === 'string' && pathname !== '') {
    if (pathname[0] !== '/') result += '/'
    result += pathname
  }

  if (typeof search === 'string') {
    if (search[0] !== '?') result += '?'
    result += search
  } else if (typeof query === 'object' && query !== null) {
    result += '?' + new URLSearchParams(query)
  }

  if (typeof hash === 'string') {
    if (hash[0] !== '#') result += '#'
    result += hash
  }

  return result
}
module.exports = class URLError extends Error {
  constructor(msg, fn = URLError, code = fn.name) {
    super(`${code}: ${msg}`)

    this.code = code

    if (Error.captureStackTrace) Error.captureStackTrace(this, fn)
  }

  get name() {
    return 'URLError'
  }

  static INVALID_URL(msg, input) {
    const err = new URLError(msg, URLError.INVALID_URL)

    err.input = input

    return err
  }

  static INVALID_URL_SCHEME(msg = 'Invalid URL') {
    return new URLError(msg, URLError.INVALID_URL_SCHEME)
  }

  static INVALID_FILE_URL_HOST(msg = 'Invalid file: URL host') {
    return new URLError(msg, URLError.INVALID_FILE_URL_HOST)
  }

  static INVALID_FILE_URL_PATH(msg = 'Invalid file: URL path') {
    return new URLError(msg, URLError.INVALID_FILE_URL_PATH)
  }
}
const kind = Symbol.for('bare.url.search-params.kind')

class URLSearchParams {
  static _urls = new WeakMap()

  static get [kind]() {
    return 0 // Compatibility version
  }

  // https://url.spec.whatwg.org/#dom-urlsearchparams-urlsearchparams
  constructor(init, url = null) {
    this._params = new Map()

    if (url) URLSearchParams._urls.set(this, url)

    if (typeof init === 'string') {
      this._parse(init)
    } else if (init) {
      for (const [name, value] of typeof init[Symbol.iterator] === 'function'
        ? init
        : Object.entries(init)) {
        this.append(name, value)
      }
    }
  }

  get [kind]() {
    return URLSearchParams[kind]
  }

  // https://url.spec.whatwg.org/#dom-urlsearchparams-size
  get size() {
    return this._params.length
  }

  // https://url.spec.whatwg.org/#dom-urlsearchparams-append
  append(name, value = null) {
    if (value === null) return

    let list = this._params.get(name)

    if (list === undefined) {
      list = []
      this._params.set(name, list)
    }

    list.push(value)

    this._update()
  }

  // https://url.spec.whatwg.org/#dom-urlsearchparams-delete
  delete(name, value = null) {
    if (value === null) this._params.delete(name)
    else {
      let list = this._params.get(name)

      if (list === undefined) return

      list = list.filter((found) => found !== value)

      if (list.length === 0) this._params.delete(name)
      else this._params.set(name, list)
    }

    this._update()
  }

  // https://url.spec.whatwg.org/#dom-urlsearchparams-get
  get(name) {
    const list = this._params.get(name)

    if (list === undefined) return null

    return list[0]
  }

  // https://url.spec.whatwg.org/#dom-urlsearchparams-getall
  getAll(name) {
    const list = this._params.get(name)

    if (list === undefined) return []

    return Array.from(list)
  }

  // https://url.spec.whatwg.org/#dom-urlsearchparams-has
  has(name, value = null) {
    const list = this._params.get(name)

    if (list === undefined) return false

    if (value === null) return true

    return list.includes(value)
  }

  // https://url.spec.whatwg.org/#dom-urlsearchparams-set
  set(name, value = null) {
    if (value === null) this._params.delete(name)
    else this._params.set(name, [value])

    this._update()
  }

  toString() {
    return this._serialize()
  }

  toJSON() {
    return [...this]
  }

  *[Symbol.iterator]() {
    for (const [name, values] of this._params) {
      for (const value of values) yield [name, value]
    }
  }

  [Symbol.for('bare.inspect')]() {
    const object = {
      __proto__: { constructor: URLSearchParams }
    }

    for (const [name, values] of this._params) {
      if (values.length === 1) object[name] = values[0]
      else object[name] = values
    }

    return object
  }

  // https://url.spec.whatwg.org/#concept-urlsearchparams-update
  _update() {
    const url = URLSearchParams._urls.get(this)

    if (url === undefined) return

    url.search = this._serialize()
  }

  // https://url.spec.whatwg.org/#concept-urlencoded-parser
  _parse(input) {
    if (input[0] === '?') input = input.substring(1)

    this._params = new Map()

    for (let sequence of input.split('&')) {
      if (sequence.length === 0) continue

      sequence = sequence.replace(/\+/g, ' ')

      let i = sequence.indexOf('=')
      if (i === -1) i = sequence.length

      const name = decodeURIComponent(sequence.substring(0, i))
      const value = decodeURIComponent(sequence.substring(i + 1, sequence.length))

      let list = this._params.get(name)

      if (list === undefined) {
        list = []
        this._params.set(name, list)
      }

      list.push(value)
    }
  }

  // https://url.spec.whatwg.org/#concept-urlencoded-serializer
  _serialize() {
    let output = ''

    for (let [name, values] of this._params) {
      name = encodeURIComponent(name)

      for (const value of values) {
        if (output) output += '&'

        output += name + '=' + encodeURIComponent(value)
      }
    }

    return output
  }
}

module.exports = exports = URLSearchParams

exports.isURLSearchParams = function isURLSearchParams(value) {
  if (value instanceof URLSearchParams) return true

  return typeof value === 'object' && value !== null && value[kind] === URLSearchParams[kind]
}
{
  "name": "bare-url",
  "version": "2.4.5",
  "description": "WHATWG URL implementation for JavaScript",
  "exports": {
    "./package": "./package.json",
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    },
    "./global": {
      "types": "./global.d.ts",
      "default": "./global.js"
    }
  },
  "files": [
    "index.js",
    "index.d.ts",
    "global.js",
    "global.d.ts",
    "binding.c",
    "binding.js",
    "CMakeLists.txt",
    "lib",
    "prebuilds"
  ],
  "addon": true,
  "scripts": {
    "format": "prettier --write . && lunte --fix",
    "lint": "prettier --check . && lunte",
    "test": "brittle-bare --coverage test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-url.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-url/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-url",
  "dependencies": {
    "bare-path": "^3.0.0"
  },
  "devDependencies": {
    "brittle": "^3.3.2",
    "cmake-bare": "^1.1.6",
    "cmake-fetch": "^1.0.0",
    "lunte": "^1.6.0",
    "prettier": "^3.3.3",
    "prettier-config-holepunch": "^2.0.0"
  }
}
const LE = (exports.LE =
  new Uint8Array(new Uint16Array([0xff]).buffer)[0] === 0xff)

exports.BE = !LE
const b4a = require('b4a')

const { BE } = require('./endian')

exports.state = function (start = 0, end = 0, buffer = null) {
  return { start, end, buffer }
}

const raw = (exports.raw = require('./raw'))

const uint = (exports.uint = {
  preencode(state, n) {
    state.end += n <= 0xfc ? 1 : n <= 0xffff ? 3 : n <= 0xffffffff ? 5 : 9
  },
  encode(state, n) {
    if (n <= 0xfc) uint8.encode(state, n)
    else if (n <= 0xffff) {
      state.buffer[state.start++] = 0xfd
      uint16.encode(state, n)
    } else if (n <= 0xffffffff) {
      state.buffer[state.start++] = 0xfe
      uint32.encode(state, n)
    } else {
      state.buffer[state.start++] = 0xff
      uint64.encode(state, n)
    }
  },
  decode(state) {
    const a = uint8.decode(state)
    if (a <= 0xfc) return a
    if (a === 0xfd) return uint16.decode(state)
    if (a === 0xfe) return uint32.decode(state)
    return uint64.decode(state)
  }
})

const uint8 = (exports.uint8 = {
  preencode(state, n) {
    state.end += 1
  },
  encode(state, n) {
    validateUint(n)
    state.buffer[state.start++] = n
  },
  decode(state) {
    if (state.start >= state.end) throw new Error('Out of bounds')
    return state.buffer[state.start++]
  }
})

const uint16 = (exports.uint16 = {
  preencode(state, n) {
    state.end += 2
  },
  encode(state, n) {
    validateUint(n)
    state.buffer[state.start++] = n
    state.buffer[state.start++] = n >>> 8
  },
  decode(state) {
    if (state.end - state.start < 2) throw new Error('Out of bounds')
    return state.buffer[state.start++] + state.buffer[state.start++] * 0x100
  }
})

const uint24 = (exports.uint24 = {
  preencode(state, n) {
    state.end += 3
  },
  encode(state, n) {
    validateUint(n)
    state.buffer[state.start++] = n
    state.buffer[state.start++] = n >>> 8
    state.buffer[state.start++] = n >>> 16
  },
  decode(state) {
    if (state.end - state.start < 3) throw new Error('Out of bounds')
    return (
      state.buffer[state.start++] +
      state.buffer[state.start++] * 0x100 +
      state.buffer[state.start++] * 0x10000
    )
  }
})

const uint32 = (exports.uint32 = {
  preencode(state, n) {
    state.end += 4
  },
  encode(state, n) {
    validateUint(n)
    state.buffer[state.start++] = n
    state.buffer[state.start++] = n >>> 8
    state.buffer[state.start++] = n >>> 16
    state.buffer[state.start++] = n >>> 24
  },
  decode(state) {
    if (state.end - state.start < 4) throw new Error('Out of bounds')
    return (
      state.buffer[state.start++] +
      state.buffer[state.start++] * 0x100 +
      state.buffer[state.start++] * 0x10000 +
      state.buffer[state.start++] * 0x1000000
    )
  }
})

const uint32be = (exports.uint32be = {
  preencode(state, n) {
    state.end += 4
  },
  encode(state, n) {
    validateUint(n)
    state.buffer[state.start++] = n >>> 24
    state.buffer[state.start++] = n >>> 16
    state.buffer[state.start++] = n >>> 8
    state.buffer[state.start++] = n
  },
  decode(state) {
    if (state.end - state.start < 4) throw new Error('Out of bounds')
    return (
      state.buffer[state.start++] * 0x1000000 +
      state.buffer[state.start++] * 0x10000 +
      state.buffer[state.start++] * 0x100 +
      state.buffer[state.start++]
    )
  }
})

const uint40 = (exports.uint40 = {
  preencode(state, n) {
    state.end += 5
  },
  encode(state, n) {
    validateUint(n)
    const r = Math.floor(n / 0x100)
    uint8.encode(state, n)
    uint32.encode(state, r)
  },
  decode(state) {
    if (state.end - state.start < 5) throw new Error('Out of bounds')
    return uint8.decode(state) + 0x100 * uint32.decode(state)
  }
})

const uint48 = (exports.uint48 = {
  preencode(state, n) {
    state.end += 6
  },
  encode(state, n) {
    validateUint(n)
    const r = Math.floor(n / 0x10000)
    uint16.encode(state, n)
    uint32.encode(state, r)
  },
  decode(state) {
    if (state.end - state.start < 6) throw new Error('Out of bounds')
    return uint16.decode(state) + 0x10000 * uint32.decode(state)
  }
})

const uint56 = (exports.uint56 = {
  preencode(state, n) {
    state.end += 7
  },
  encode(state, n) {
    validateUint(n)
    const r = Math.floor(n / 0x1000000)
    uint24.encode(state, n)
    uint32.encode(state, r)
  },
  decode(state) {
    if (state.end - state.start < 7) throw new Error('Out of bounds')
    return uint24.decode(state) + 0x1000000 * uint32.decode(state)
  }
})

const uint64 = (exports.uint64 = {
  preencode(state, n) {
    state.end += 8
  },
  encode(state, n) {
    validateUint(n)
    const r = Math.floor(n / 0x100000000)
    uint32.encode(state, n)
    uint32.encode(state, r)
  },
  decode(state) {
    if (state.end - state.start < 8) throw new Error('Out of bounds')
    return uint32.decode(state) + 0x100000000 * uint32.decode(state)
  }
})

exports.uint64be = {
  preencode(state, n) {
    state.end += 8
  },
  encode(state, n) {
    validateUint(n)
    const r = Math.floor(n / 0x100000000)
    uint32be.encode(state, r)
    uint32be.encode(state, n)
  },
  decode(state) {
    if (state.end - state.start < 8) throw new Error('Out of bounds')
    return 0x100000000 * uint32be.decode(state) + uint32be.decode(state)
  }
}

const int = (exports.int = zigZagInt(uint))
exports.int8 = zigZagInt(uint8)
exports.int16 = zigZagInt(uint16)
exports.int24 = zigZagInt(uint24)
exports.int32 = zigZagInt(uint32)
exports.int40 = zigZagInt(uint40)
exports.int48 = zigZagInt(uint48)
exports.int56 = zigZagInt(uint56)
exports.int64 = zigZagInt(uint64)

const biguint64 = (exports.biguint64 = {
  preencode(state, n) {
    state.end += 8
  },
  encode(state, n) {
    const view = new DataView(
      state.buffer.buffer,
      state.start + state.buffer.byteOffset,
      8
    )
    view.setBigUint64(0, n, true) // little endian
    state.start += 8
  },
  decode(state) {
    if (state.end - state.start < 8) throw new Error('Out of bounds')
    const view = new DataView(
      state.buffer.buffer,
      state.start + state.buffer.byteOffset,
      8
    )
    const n = view.getBigUint64(0, true) // little endian
    state.start += 8
    return n
  }
})

exports.bigint64 = zigZagBigInt(biguint64)

const biguint = (exports.biguint = {
  preencode(state, n) {
    let len = 0
    for (let m = n; m; m = m >> 64n) len++
    uint.preencode(state, len)
    state.end += 8 * len
  },
  encode(state, n) {
    let len = 0
    for (let m = n; m; m = m >> 64n) len++
    uint.encode(state, len)
    const view = new DataView(
      state.buffer.buffer,
      state.start + state.buffer.byteOffset,
      8 * len
    )
    for (let m = n, i = 0; m; m = m >> 64n, i += 8) {
      view.setBigUint64(i, BigInt.asUintN(64, m), true) // little endian
    }
    state.start += 8 * len
  },
  decode(state) {
    const len = uint.decode(state)
    if (state.end - state.start < 8 * len) throw new Error('Out of bounds')
    const view = new DataView(
      state.buffer.buffer,
      state.start + state.buffer.byteOffset,
      8 * len
    )
    let n = 0n
    for (let i = len - 1; i >= 0; i--)
      n = (n << 64n) + view.getBigUint64(i * 8, true) // little endian
    state.start += 8 * len
    return n
  }
})

exports.bigint = zigZagBigInt(biguint)

exports.lexint = require('./lexint')

exports.float32 = {
  preencode(state, n) {
    state.end += 4
  },
  encode(state, n) {
    const view = new DataView(
      state.buffer.buffer,
      state.start + state.buffer.byteOffset,
      4
    )
    view.setFloat32(0, n, true) // little endian
    state.start += 4
  },
  decode(state) {
    if (state.end - state.start < 4) throw new Error('Out of bounds')
    const view = new DataView(
      state.buffer.buffer,
      state.start + state.buffer.byteOffset,
      4
    )
    const float = view.getFloat32(0, true) // little endian
    state.start += 4
    return float
  }
}

exports.float64 = {
  preencode(state, n) {
    state.end += 8
  },
  encode(state, n) {
    const view = new DataView(
      state.buffer.buffer,
      state.start + state.buffer.byteOffset,
      8
    )
    view.setFloat64(0, n, true) // little endian
    state.start += 8
  },
  decode(state) {
    if (state.end - state.start < 8) throw new Error('Out of bounds')
    const view = new DataView(
      state.buffer.buffer,
      state.start + state.buffer.byteOffset,
      8
    )
    const float = view.getFloat64(0, true) // little endian
    state.start += 8
    return float
  }
}

const buffer = (exports.buffer = {
  preencode(state, b) {
    uint8array.preencode(state, b)
  },
  encode(state, b) {
    uint8array.encode(state, b)
  },
  decode(state) {
    const len = uint.decode(state)
    if (state.end - state.start < len) throw new Error('Out of bounds')
    return state.buffer.subarray(state.start, (state.start += len))
  }
})

exports.optionalBuffer = {
  preencode(state, b) {
    if (b) uint8array.preencode(state, b)
    else state.end++
  },
  encode(state, b) {
    if (b) uint8array.encode(state, b)
    else state.buffer[state.start++] = 0
  },
  decode(state) {
    const len = uint.decode(state)
    if (len === 0) return null
    if (state.end - state.start < len) throw new Error('Out of bounds')
    return state.buffer.subarray(state.start, (state.start += len))
  }
}

exports.binary = {
  ...buffer,
  preencode(state, b) {
    if (typeof b === 'string') utf8.preencode(state, b)
    else buffer.preencode(state, b)
  },
  encode(state, b) {
    if (typeof b === 'string') utf8.encode(state, b)
    else buffer.encode(state, b)
  }
}

exports.arraybuffer = {
  preencode(state, b) {
    uint.preencode(state, b.byteLength)
    state.end += b.byteLength
  },
  encode(state, b) {
    uint.encode(state, b.byteLength)

    const view = new Uint8Array(b)

    state.buffer.set(view, state.start)
    state.start += b.byteLength
  },
  decode(state) {
    const len = uint.decode(state)

    const b = new ArrayBuffer(len)
    const view = new Uint8Array(b)

    view.set(state.buffer.subarray(state.start, (state.start += len)))

    return b
  }
}

function typedarray(TypedArray, swap) {
  const n = TypedArray.BYTES_PER_ELEMENT

  return {
    preencode(state, b) {
      uint.preencode(state, b.length)
      state.end += b.byteLength
    },
    encode(state, b) {
      uint.encode(state, b.length)

      const view = new Uint8Array(b.buffer, b.byteOffset, b.byteLength)

      if (BE && swap) swap(view)

      state.buffer.set(view, state.start)
      state.start += b.byteLength
    },
    decode(state) {
      const len = uint.decode(state)

      let b = state.buffer.subarray(state.start, (state.start += len * n))
      if (b.byteLength !== len * n) throw new Error('Out of bounds')
      if (b.byteOffset % n !== 0) b = new Uint8Array(b)

      if (BE && swap) swap(b)

      return new TypedArray(b.buffer, b.byteOffset, b.byteLength / n)
    }
  }
}

const uint8array = (exports.uint8array = typedarray(Uint8Array))
exports.uint16array = typedarray(Uint16Array, b4a.swap16)
exports.uint32array = typedarray(Uint32Array, b4a.swap32)

exports.int8array = typedarray(Int8Array)
exports.int16array = typedarray(Int16Array, b4a.swap16)
exports.int32array = typedarray(Int32Array, b4a.swap32)

exports.biguint64array = typedarray(BigUint64Array, b4a.swap64)
exports.bigint64array = typedarray(BigInt64Array, b4a.swap64)

exports.float32array = typedarray(Float32Array, b4a.swap32)
exports.float64array = typedarray(Float64Array, b4a.swap64)

function string(encoding) {
  return {
    preencode(state, s) {
      const len = b4a.byteLength(s, encoding)
      uint.preencode(state, len)
      state.end += len
    },
    encode(state, s) {
      const len = b4a.byteLength(s, encoding)
      uint.encode(state, len)
      b4a.write(state.buffer, s, state.start, encoding)
      state.start += len
    },
    decode(state) {
      const len = uint.decode(state)
      if (state.end - state.start < len) throw new Error('Out of bounds')
      return b4a.toString(
        state.buffer,
        encoding,
        state.start,
        (state.start += len)
      )
    },
    fixed(n) {
      return {
        preencode(state) {
          state.end += n
        },
        encode(state, s) {
          b4a.write(state.buffer, s, state.start, n, encoding)
          state.start += n
        },
        decode(state) {
          if (state.end - state.start < n) throw new Error('Out of bounds')
          return b4a.toString(
            state.buffer,
            encoding,
            state.start,
            (state.start += n)
          )
        }
      }
    }
  }
}

const utf8 = (exports.string = exports.utf8 = string('utf-8'))
exports.ascii = string('ascii')
exports.hex = string('hex')
exports.base64 = string('base64')
exports.ucs2 = exports.utf16le = string('utf16le')

exports.bool = {
  preencode(state, b) {
    state.end++
  },
  encode(state, b) {
    state.buffer[state.start++] = b ? 1 : 0
  },
  decode(state) {
    if (state.start >= state.end) throw Error('Out of bounds')
    return state.buffer[state.start++] === 1
  }
}

const fixed = (exports.fixed = function fixed(n) {
  return {
    preencode(state, s) {
      if (s.byteLength !== n) throw new Error('Incorrect buffer size')
      state.end += n
    },
    encode(state, s) {
      state.buffer.set(s, state.start)
      state.start += n
    },
    decode(state) {
      if (state.end - state.start < n) throw new Error('Out of bounds')
      return state.buffer.subarray(state.start, (state.start += n))
    }
  }
})

exports.fixed32 = fixed(32)
exports.fixed64 = fixed(64)

exports.array = function array(enc) {
  return {
    preencode(state, list) {
      uint.preencode(state, list.length)
      for (let i = 0; i < list.length; i++) enc.preencode(state, list[i])
    },
    encode(state, list) {
      uint.encode(state, list.length)
      for (let i = 0; i < list.length; i++) enc.encode(state, list[i])
    },
    decode(state) {
      const len = uint.decode(state)
      if (len > 0x100000) throw new Error('Array is too big')
      const arr = new Array(len)
      for (let i = 0; i < len; i++) arr[i] = enc.decode(state)
      return arr
    }
  }
}

exports.frame = function frame(enc) {
  const dummy = exports.state()

  return {
    preencode(state, m) {
      const end = state.end
      enc.preencode(state, m)
      uint.preencode(state, state.end - end)
    },
    encode(state, m) {
      dummy.end = 0
      enc.preencode(dummy, m)
      uint.encode(state, dummy.end)
      enc.encode(state, m)
    },
    decode(state) {
      const end = state.end
      const len = uint.decode(state)
      state.end = state.start + len
      const m = enc.decode(state)
      state.start = state.end
      state.end = end
      return m
    }
  }
}

exports.date = {
  preencode(state, d) {
    int.preencode(state, d.getTime())
  },
  encode(state, d) {
    int.encode(state, d.getTime())
  },
  decode(state, d) {
    return new Date(int.decode(state))
  }
}

exports.json = {
  preencode(state, v) {
    utf8.preencode(state, JSON.stringify(v))
  },
  encode(state, v) {
    utf8.encode(state, JSON.stringify(v))
  },
  decode(state) {
    return JSON.parse(utf8.decode(state))
  }
}

exports.ndjson = {
  preencode(state, v) {
    utf8.preencode(state, JSON.stringify(v) + '\n')
  },
  encode(state, v) {
    utf8.encode(state, JSON.stringify(v) + '\n')
  },
  decode(state) {
    return JSON.parse(utf8.decode(state))
  }
}

// simple helper for when you want to just express nothing
exports.none = {
  preencode(state, n) {
    // do nothing
  },
  encode(state, n) {
    // do nothing
  },
  decode(state) {
    return null
  }
}

// "any" encoders here for helping just structure any object without schematising it

const anyArray = {
  preencode(state, arr) {
    uint.preencode(state, arr.length)
    for (let i = 0; i < arr.length; i++) {
      any.preencode(state, arr[i])
    }
  },
  encode(state, arr) {
    uint.encode(state, arr.length)
    for (let i = 0; i < arr.length; i++) {
      any.encode(state, arr[i])
    }
  },
  decode(state) {
    const arr = []
    let len = uint.decode(state)
    while (len-- > 0) {
      arr.push(any.decode(state))
    }
    return arr
  }
}

const anyObject = {
  preencode(state, o) {
    const keys = Object.keys(o)
    uint.preencode(state, keys.length)
    for (const key of keys) {
      utf8.preencode(state, key)
      any.preencode(state, o[key])
    }
  },
  encode(state, o) {
    const keys = Object.keys(o)
    uint.encode(state, keys.length)
    for (const key of keys) {
      utf8.encode(state, key)
      any.encode(state, o[key])
    }
  },
  decode(state) {
    let len = uint.decode(state)
    const o = {}
    while (len-- > 0) {
      const key = utf8.decode(state)
      o[key] = any.decode(state)
    }
    return o
  }
}

const anyTypes = [
  exports.none,
  exports.bool,
  exports.string,
  exports.buffer,
  exports.uint,
  exports.int,
  exports.float64,
  anyArray,
  anyObject,
  exports.date
]

const any = (exports.any = {
  preencode(state, o) {
    const t = getType(o)
    uint.preencode(state, t)
    anyTypes[t].preencode(state, o)
  },
  encode(state, o) {
    const t = getType(o)
    uint.encode(state, t)
    anyTypes[t].encode(state, o)
  },
  decode(state) {
    const t = uint.decode(state)
    if (t >= anyTypes.length) throw new Error('Unknown type: ' + t)
    return anyTypes[t].decode(state)
  }
})

const port = (exports.port = uint16)

const address = (host, family) => {
  return {
    preencode(state, m) {
      host.preencode(state, m.host)
      port.preencode(state, m.port)
    },
    encode(state, m) {
      host.encode(state, m.host)
      port.encode(state, m.port)
    },
    decode(state) {
      return {
        host: host.decode(state),
        family,
        port: port.decode(state)
      }
    }
  }
}

const ipv4 = (exports.ipv4 = {
  preencode(state) {
    state.end += 4
  },
  encode(state, string) {
    const start = state.start
    const end = start + 4

    let i = 0

    while (i < string.length) {
      let n = 0
      let c

      while (
        i < string.length &&
        (c = string.charCodeAt(i++)) !== /* . */ 0x2e
      ) {
        n = n * 10 + (c - /* 0 */ 0x30)
      }

      state.buffer[state.start++] = n
    }

    state.start = end
  },
  decode(state) {
    if (state.end - state.start < 4) throw new Error('Out of bounds')
    return (
      state.buffer[state.start++] +
      '.' +
      state.buffer[state.start++] +
      '.' +
      state.buffer[state.start++] +
      '.' +
      state.buffer[state.start++]
    )
  }
})

exports.ipv4Address = address(ipv4, 4)

const ipv6 = (exports.ipv6 = {
  preencode(state) {
    state.end += 16
  },
  encode(state, string) {
    const start = state.start
    const end = start + 16

    let i = 0
    let split = null

    while (i < string.length) {
      let n = 0
      let c

      while (
        i < string.length &&
        (c = string.charCodeAt(i++)) !== /* : */ 0x3a
      ) {
        if (c >= 0x30 && c <= 0x39) n = n * 0x10 + (c - /* 0 */ 0x30)
        else if (c >= 0x41 && c <= 0x46) n = n * 0x10 + (c - /* A */ 0x41 + 10)
        else if (c >= 0x61 && c <= 0x66) n = n * 0x10 + (c - /* a */ 0x61 + 10)
      }

      state.buffer[state.start++] = n >>> 8
      state.buffer[state.start++] = n

      if (i < string.length && string.charCodeAt(i) === /* : */ 0x3a) {
        i++
        split = state.start
      }
    }

    if (split !== null) {
      const offset = end - state.start
      state.buffer
        .copyWithin(split + offset, split)
        .fill(0, split, split + offset)
    }

    state.start = end
  },
  decode(state) {
    if (state.end - state.start < 16) throw new Error('Out of bounds')
    return (
      (
        state.buffer[state.start++] * 256 +
        state.buffer[state.start++]
      ).toString(16) +
      ':' +
      (
        state.buffer[state.start++] * 256 +
        state.buffer[state.start++]
      ).toString(16) +
      ':' +
      (
        state.buffer[state.start++] * 256 +
        state.buffer[state.start++]
      ).toString(16) +
      ':' +
      (
        state.buffer[state.start++] * 256 +
        state.buffer[state.start++]
      ).toString(16) +
      ':' +
      (
        state.buffer[state.start++] * 256 +
        state.buffer[state.start++]
      ).toString(16) +
      ':' +
      (
        state.buffer[state.start++] * 256 +
        state.buffer[state.start++]
      ).toString(16) +
      ':' +
      (
        state.buffer[state.start++] * 256 +
        state.buffer[state.start++]
      ).toString(16) +
      ':' +
      (
        state.buffer[state.start++] * 256 +
        state.buffer[state.start++]
      ).toString(16)
    )
  }
})

exports.ipv6Address = address(ipv6, 6)

const ip = (exports.ip = {
  preencode(state, string) {
    const family = string.includes(':') ? 6 : 4
    uint8.preencode(state, family)
    if (family === 4) ipv4.preencode(state)
    else ipv6.preencode(state)
  },
  encode(state, string) {
    const family = string.includes(':') ? 6 : 4
    uint8.encode(state, family)
    if (family === 4) ipv4.encode(state, string)
    else ipv6.encode(state, string)
  },
  decode(state) {
    const family = uint8.decode(state)
    if (family === 4) return ipv4.decode(state)
    else return ipv6.decode(state)
  }
})

exports.ipAddress = {
  preencode(state, m) {
    ip.preencode(state, m.host)
    port.preencode(state, m.port)
  },
  encode(state, m) {
    ip.encode(state, m.host)
    port.encode(state, m.port)
  },
  decode(state) {
    const family = uint8.decode(state)
    return {
      host: family === 4 ? ipv4.decode(state) : ipv6.decode(state),
      family,
      port: port.decode(state)
    }
  }
}

const record = (exports.record = function (keyEncoding, valueEncoding) {
  return {
    preencode(state, v) {
      const keys = Object.keys(v)
      uint.preencode(state, keys.length)
      for (const k of keys) {
        keyEncoding.preencode(state, k)
        valueEncoding.preencode(state, v[k])
      }
    },
    encode(state, v) {
      const keys = Object.keys(v)
      uint.encode(state, keys.length)
      for (const k of keys) {
        keyEncoding.encode(state, k)
        valueEncoding.encode(state, v[k])
      }
    },
    decode(state) {
      const out = Object.create(null)
      const keys = uint.decode(state)
      for (let i = 0; i < keys; i++) {
        out[keyEncoding.decode(state)] = valueEncoding.decode(state)
      }
      return out
    }
  }
})

exports.stringRecord = record(utf8, utf8)

function getType(o) {
  if (o === null || o === undefined) return 0
  if (typeof o === 'boolean') return 1
  if (typeof o === 'string') return 2
  if (b4a.isBuffer(o)) return 3
  if (typeof o === 'number') {
    if (Number.isInteger(o)) return o >= 0 ? 4 : 5
    return 6
  }
  if (Array.isArray(o)) return 7
  if (o instanceof Date) return 9
  if (typeof o === 'object') return 8

  throw new Error('Unsupported type for ' + o)
}

exports.from = function from(enc) {
  if (typeof enc === 'string') return fromNamed(enc)
  if (enc.preencode) return enc
  if (enc.encodingLength) return fromAbstractEncoder(enc)
  return fromCodec(enc)
}

function fromNamed(enc) {
  switch (enc) {
    case 'ascii':
      return raw.ascii
    case 'utf-8':
    case 'utf8':
      return raw.utf8
    case 'hex':
      return raw.hex
    case 'base64':
      return raw.base64
    case 'utf16-le':
    case 'utf16le':
    case 'ucs-2':
    case 'ucs2':
      return raw.ucs2
    case 'ndjson':
      return raw.ndjson
    case 'json':
      return raw.json
    case 'binary':
    default:
      return raw.binary
  }
}

function fromCodec(enc) {
  let tmpM = null
  let tmpBuf = null

  return {
    preencode(state, m) {
      tmpM = m
      tmpBuf = enc.encode(m)
      state.end += tmpBuf.byteLength
    },
    encode(state, m) {
      raw.encode(state, m === tmpM ? tmpBuf : enc.encode(m))
      tmpM = tmpBuf = null
    },
    decode(state) {
      return enc.decode(raw.decode(state))
    }
  }
}

function fromAbstractEncoder(enc) {
  return {
    preencode(state, m) {
      state.end += enc.encodingLength(m)
    },
    encode(state, m) {
      enc.encode(m, state.buffer, state.start)
      state.start += enc.encode.bytes
    },
    decode(state) {
      const m = enc.decode(state.buffer, state.start, state.end)
      state.start += enc.decode.bytes
      return m
    }
  }
}

exports.encode = function encode(enc, m) {
  const state = exports.state()
  enc.preencode(state, m)
  state.buffer = b4a.allocUnsafe(state.end)
  enc.encode(state, m)
  return state.buffer
}

exports.decode = function decode(enc, buffer) {
  return enc.decode(exports.state(0, buffer.byteLength, buffer))
}

function zigZagInt(enc) {
  return {
    preencode(state, n) {
      enc.preencode(state, zigZagEncodeInt(n))
    },
    encode(state, n) {
      enc.encode(state, zigZagEncodeInt(n))
    },
    decode(state) {
      return zigZagDecodeInt(enc.decode(state))
    }
  }
}

function zigZagDecodeInt(n) {
  return n === 0 ? n : (n & 1) === 0 ? n / 2 : -(n + 1) / 2
}

function zigZagEncodeInt(n) {
  // 0, -1, 1, -2, 2, ...
  return n < 0 ? 2 * -n - 1 : n === 0 ? 0 : 2 * n
}

function zigZagBigInt(enc) {
  return {
    preencode(state, n) {
      enc.preencode(state, zigZagEncodeBigInt(n))
    },
    encode(state, n) {
      enc.encode(state, zigZagEncodeBigInt(n))
    },
    decode(state) {
      return zigZagDecodeBigInt(enc.decode(state))
    }
  }
}

function zigZagDecodeBigInt(n) {
  return n === 0n ? n : (n & 1n) === 0n ? n / 2n : -(n + 1n) / 2n
}

function zigZagEncodeBigInt(n) {
  // 0, -1, 1, -2, 2, ...
  return n < 0n ? 2n * -n - 1n : n === 0n ? 0n : 2n * n
}

function validateUint(n) {
  if (n >= 0 === false /* Handles NaN as well */)
    throw new Error('uint must be positive')
  // Beyond this, Number arithmetic loses precision and silently corrupts the
  // encoding. The zig-zag int codecs route through here too, so this also
  // guards int values whose doubled magnitude exceeds the safe range.
  if (n > Number.MAX_SAFE_INTEGER)
    throw new Error(
      'integer is greater than the maximum safe integer, use biguint/bigint'
    )
}
module.exports = {
  preencode,
  encode,
  decode
}

function preencode(state, num) {
  if (num < 251) {
    state.end++
  } else if (num < 256) {
    state.end += 2
  } else if (num < 0x10000) {
    state.end += 3
  } else if (num < 0x1000000) {
    state.end += 4
  } else if (num < 0x100000000) {
    state.end += 5
  } else {
    state.end++
    const exp = Math.floor(Math.log(num) / Math.log(2)) - 32
    preencode(state, exp)
    state.end += 6
  }
}

function encode(state, num) {
  const max = 251
  const x = num - max

  if (num < max) {
    state.buffer[state.start++] = num
  } else if (num < 256) {
    state.buffer[state.start++] = max
    state.buffer[state.start++] = x
  } else if (num < 0x10000) {
    state.buffer[state.start++] = max + 1
    state.buffer[state.start++] = (x >> 8) & 0xff
    state.buffer[state.start++] = x & 0xff
  } else if (num < 0x1000000) {
    state.buffer[state.start++] = max + 2
    state.buffer[state.start++] = x >> 16
    state.buffer[state.start++] = (x >> 8) & 0xff
    state.buffer[state.start++] = x & 0xff
  } else if (num < 0x100000000) {
    state.buffer[state.start++] = max + 3
    state.buffer[state.start++] = x >> 24
    state.buffer[state.start++] = (x >> 16) & 0xff
    state.buffer[state.start++] = (x >> 8) & 0xff
    state.buffer[state.start++] = x & 0xff
  } else {
    // need to use Math here as bitwise ops are 32 bit
    const exp = Math.floor(Math.log(x) / Math.log(2)) - 32
    state.buffer[state.start++] = 0xff

    encode(state, exp)
    const rem = x / Math.pow(2, exp - 11)

    for (let i = 5; i >= 0; i--) {
      state.buffer[state.start++] = (rem / Math.pow(2, 8 * i)) & 0xff
    }
  }
}

function decode(state) {
  const max = 251

  if (state.end - state.start < 1) throw new Error('Out of bounds')

  const flag = state.buffer[state.start++]

  if (flag < max) return flag

  if (state.end - state.start < flag - max + 1) {
    throw new Error('Out of bounds.')
  }

  if (flag < 252) {
    return state.buffer[state.start++] + max
  }

  if (flag < 253) {
    return (
      (state.buffer[state.start++] << 8) + state.buffer[state.start++] + max
    )
  }

  if (flag < 254) {
    return (
      (state.buffer[state.start++] << 16) +
      (state.buffer[state.start++] << 8) +
      state.buffer[state.start++] +
      max
    )
  }

  // << 24 result may be interpreted as negative
  if (flag < 255) {
    return (
      state.buffer[state.start++] * 0x1000000 +
      (state.buffer[state.start++] << 16) +
      (state.buffer[state.start++] << 8) +
      state.buffer[state.start++] +
      max
    )
  }

  const exp = decode(state)

  if (state.end - state.start < 6) throw new Error('Out of bounds')

  let rem = 0
  for (let i = 5; i >= 0; i--) {
    rem += state.buffer[state.start++] * Math.pow(2, 8 * i)
  }

  return rem * Math.pow(2, exp - 11) + max
}
{
  "name": "compact-encoding",
  "version": "3.2.0",
  "description": "A series of compact encoding schemes for building small and fast parsers and serializers",
  "main": "index.js",
  "files": [
    "endian.js",
    "index.js",
    "lexint.js",
    "raw.js"
  ],
  "dependencies": {
    "b4a": "^1.3.0"
  },
  "devDependencies": {
    "brittle": "^3.0.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^1.0.0"
  },
  "scripts": {
    "format": "prettier . --write",
    "test": "prettier . --check && brittle test.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/holepunchto/compact-encoding.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/compact-encoding/issues"
  },
  "homepage": "https://github.com/holepunchto/compact-encoding"
}
const b4a = require('b4a')

const { BE } = require('./endian')

exports = module.exports = {
  preencode(state, b) {
    state.end += b.byteLength
  },
  encode(state, b) {
    state.buffer.set(b, state.start)
    state.start += b.byteLength
  },
  decode(state) {
    const b = state.buffer.subarray(state.start, state.end)
    state.start = state.end
    return b
  }
}

const buffer = (exports.buffer = {
  preencode(state, b) {
    uint8array.preencode(state, b)
  },
  encode(state, b) {
    uint8array.encode(state, b)
  },
  decode(state) {
    const b = state.buffer.subarray(state.start)
    state.start = state.end
    return b
  }
})

exports.binary = {
  ...buffer,
  preencode(state, b) {
    if (typeof b === 'string') utf8.preencode(state, b)
    else buffer.preencode(state, b)
  },
  encode(state, b) {
    if (typeof b === 'string') utf8.encode(state, b)
    else buffer.encode(state, b)
  }
}

exports.arraybuffer = {
  preencode(state, b) {
    state.end += b.byteLength
  },
  encode(state, b) {
    const view = new Uint8Array(b)

    state.buffer.set(view, state.start)
    state.start += b.byteLength
  },
  decode(state) {
    const b = new ArrayBuffer(state.end - state.start)
    const view = new Uint8Array(b)

    view.set(state.buffer.subarray(state.start))

    state.start = state.end

    return b
  }
}

function typedarray(TypedArray, swap) {
  const n = TypedArray.BYTES_PER_ELEMENT

  return {
    preencode(state, b) {
      state.end += b.byteLength
    },
    encode(state, b) {
      const view = new Uint8Array(b.buffer, b.byteOffset, b.byteLength)

      if (BE && swap) swap(view)

      state.buffer.set(view, state.start)
      state.start += b.byteLength
    },
    decode(state) {
      let b = state.buffer.subarray(state.start)
      if (b.byteOffset % n !== 0) b = new Uint8Array(b)

      if (BE && swap) swap(b)

      state.start = state.end

      return new TypedArray(b.buffer, b.byteOffset, b.byteLength / n)
    }
  }
}

const uint8array = (exports.uint8array = typedarray(Uint8Array))
exports.uint16array = typedarray(Uint16Array, b4a.swap16)
exports.uint32array = typedarray(Uint32Array, b4a.swap32)

exports.int8array = typedarray(Int8Array)
exports.int16array = typedarray(Int16Array, b4a.swap16)
exports.int32array = typedarray(Int32Array, b4a.swap32)

exports.biguint64array = typedarray(BigUint64Array, b4a.swap64)
exports.bigint64array = typedarray(BigInt64Array, b4a.swap64)

exports.float32array = typedarray(Float32Array, b4a.swap32)
exports.float64array = typedarray(Float64Array, b4a.swap64)

function string(encoding) {
  return {
    preencode(state, s) {
      state.end += b4a.byteLength(s, encoding)
    },
    encode(state, s) {
      state.start += b4a.write(state.buffer, s, state.start, encoding)
    },
    decode(state) {
      const s = b4a.toString(state.buffer, encoding, state.start)
      state.start = state.end
      return s
    }
  }
}

const utf8 = (exports.string = exports.utf8 = string('utf-8'))
exports.ascii = string('ascii')
exports.hex = string('hex')
exports.base64 = string('base64')
exports.ucs2 = exports.utf16le = string('utf16le')

exports.array = function array(enc) {
  return {
    preencode(state, list) {
      for (const value of list) enc.preencode(state, value)
    },
    encode(state, list) {
      for (const value of list) enc.encode(state, value)
    },
    decode(state) {
      const arr = []
      while (state.start < state.end) arr.push(enc.decode(state))
      return arr
    }
  }
}

exports.json = {
  preencode(state, v) {
    utf8.preencode(state, JSON.stringify(v))
  },
  encode(state, v) {
    utf8.encode(state, JSON.stringify(v))
  },
  decode(state) {
    return JSON.parse(utf8.decode(state))
  }
}

exports.ndjson = {
  preencode(state, v) {
    utf8.preencode(state, JSON.stringify(v) + '\n')
  },
  encode(state, v) {
    utf8.encode(state, JSON.stringify(v) + '\n')
  },
  decode(state) {
    return JSON.parse(utf8.decode(state))
  }
}
module.exports = require('bare-events')
{
  "name": "events-universal",
  "version": "1.0.1",
  "description": "Universal wrapper for the Node.js events module",
  "exports": {
    "./package": "./package.json",
    ".": {
      "bare": "./bare.js",
      "react-native": "./react-native.js",
      "default": "./default.js"
    }
  },
  "files": [
    "index.js",
    "default.js",
    "bare.js",
    "react-native.js"
  ],
  "scripts": {
    "test": "prettier . --check"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/events-universal.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/events-universal/issues"
  },
  "homepage": "https://github.com/holepunchto/events-universal#readme",
  "dependencies": {
    "bare-events": "^2.7.0"
  },
  "devDependencies": {
    "prettier": "^3.4.2",
    "prettier-config-holepunch": "^1.0.0"
  }
}
module.exports = class FixedFIFO {
  constructor (hwm) {
    if (!(hwm > 0) || ((hwm - 1) & hwm) !== 0) throw new Error('Max size for a FixedFIFO should be a power of two')
    this.buffer = new Array(hwm)
    this.mask = hwm - 1
    this.top = 0
    this.btm = 0
    this.next = null
  }

  clear () {
    this.top = this.btm = 0
    this.next = null
    this.buffer.fill(undefined)
  }

  push (data) {
    if (this.buffer[this.top] !== undefined) return false
    this.buffer[this.top] = data
    this.top = (this.top + 1) & this.mask
    return true
  }

  shift () {
    const last = this.buffer[this.btm]
    if (last === undefined) return undefined
    this.buffer[this.btm] = undefined
    this.btm = (this.btm + 1) & this.mask
    return last
  }

  peek () {
    return this.buffer[this.btm]
  }

  isEmpty () {
    return this.buffer[this.btm] === undefined
  }
}
const FixedFIFO = require('./fixed-size')

module.exports = class FastFIFO {
  constructor (hwm) {
    this.hwm = hwm || 16
    this.head = new FixedFIFO(this.hwm)
    this.tail = this.head
    this.length = 0
  }

  clear () {
    this.head = this.tail
    this.head.clear()
    this.length = 0
  }

  push (val) {
    this.length++
    if (!this.head.push(val)) {
      const prev = this.head
      this.head = prev.next = new FixedFIFO(2 * this.head.buffer.length)
      this.head.push(val)
    }
  }

  shift () {
    if (this.length !== 0) this.length--
    const val = this.tail.shift()
    if (val === undefined && this.tail.next) {
      const next = this.tail.next
      this.tail.next = null
      this.tail = next
      return this.tail.shift()
    }

    return val
  }

  peek () {
    const val = this.tail.peek()
    if (val === undefined && this.tail.next) return this.tail.next.peek()
    return val
  }

  isEmpty () {
    return this.length === 0
  }
}
{
  "name": "fast-fifo",
  "version": "1.3.2",
  "description": "A fast fifo implementation similar to the one powering nextTick in Node.js core",
  "main": "index.js",
  "files": [
    "./index.js",
    "./fixed-size.js"
  ],
  "dependencies": {},
  "devDependencies": {
    "standard": "^17.1.0",
    "brittle": "^3.3.2"
  },
  "scripts": {
    "test": "standard && brittle test.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/mafintosh/fast-fifo.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/mafintosh/fast-fifo/issues"
  },
  "homepage": "https://github.com/mafintosh/fast-fifo"
}
const sodium = require('sodium-universal')
const c = require('compact-encoding')
const b4a = require('b4a')

// https://en.wikipedia.org/wiki/Merkle_tree#Second_preimage_attack
const LEAF_TYPE = b4a.from([0])
const PARENT_TYPE = b4a.from([1])
const ROOT_TYPE = b4a.from([2])

const HYPERCORE = b4a.from('hypercore')

exports.keyPair = function (seed) {
  // key pairs might stay around for a while, so better not to use a default slab to avoid retaining it completely
  const slab = b4a.allocUnsafeSlow(
    sodium.crypto_sign_PUBLICKEYBYTES + sodium.crypto_sign_SECRETKEYBYTES
  )
  const publicKey = slab.subarray(0, sodium.crypto_sign_PUBLICKEYBYTES)
  const secretKey = slab.subarray(sodium.crypto_sign_PUBLICKEYBYTES)

  if (seed) sodium.crypto_sign_seed_keypair(publicKey, secretKey, seed)
  else sodium.crypto_sign_keypair(publicKey, secretKey)

  return {
    publicKey,
    secretKey
  }
}

exports.validateKeyPair = function (keyPair) {
  const pk = b4a.allocUnsafe(sodium.crypto_sign_PUBLICKEYBYTES)
  sodium.crypto_sign_ed25519_sk_to_pk(pk, keyPair.secretKey)
  return b4a.equals(pk, keyPair.publicKey)
}

exports.sign = function (message, secretKey) {
  // Dedicated slab for the signature, to avoid retaining unneeded mem and for security
  const signature = b4a.allocUnsafeSlow(sodium.crypto_sign_BYTES)
  sodium.crypto_sign_detached(signature, message, secretKey)
  return signature
}

exports.verify = function (message, signature, publicKey) {
  if (signature.byteLength !== sodium.crypto_sign_BYTES) return false
  if (publicKey.byteLength !== sodium.crypto_sign_PUBLICKEYBYTES) return false
  return sodium.crypto_sign_verify_detached(signature, message, publicKey)
}

exports.encrypt = function (message, publicKey) {
  const ciphertext = b4a.alloc(message.byteLength + sodium.crypto_box_SEALBYTES)
  sodium.crypto_box_seal(ciphertext, message, publicKey)
  return ciphertext
}

exports.decrypt = function (ciphertext, keyPair) {
  if (ciphertext.byteLength < sodium.crypto_box_SEALBYTES) return null

  const plaintext = b4a.alloc(ciphertext.byteLength - sodium.crypto_box_SEALBYTES)

  if (!sodium.crypto_box_seal_open(plaintext, ciphertext, keyPair.publicKey, keyPair.secretKey)) {
    return null
  }

  return plaintext
}

exports.encryptionKeyPair = function (seed) {
  const publicKey = b4a.alloc(sodium.crypto_box_PUBLICKEYBYTES)
  const secretKey = b4a.alloc(sodium.crypto_box_SECRETKEYBYTES)

  if (seed) {
    sodium.crypto_box_seed_keypair(publicKey, secretKey, seed)
  } else {
    sodium.crypto_box_keypair(publicKey, secretKey)
  }

  return {
    publicKey,
    secretKey
  }
}

exports.data = function (data) {
  const out = b4a.allocUnsafe(32)

  sodium.crypto_generichash_batch(out, [LEAF_TYPE, c.encode(c.uint64, data.byteLength), data])

  return out
}

exports.parent = function (a, b) {
  if (a.index > b.index) {
    const tmp = a
    a = b
    b = tmp
  }

  const out = b4a.allocUnsafe(32)

  sodium.crypto_generichash_batch(out, [
    PARENT_TYPE,
    c.encode(c.uint64, a.size + b.size),
    a.hash,
    b.hash
  ])

  return out
}

exports.tree = function (roots, out) {
  const buffers = new Array(3 * roots.length + 1)
  let j = 0

  buffers[j++] = ROOT_TYPE

  for (let i = 0; i < roots.length; i++) {
    const r = roots[i]
    buffers[j++] = r.hash
    buffers[j++] = c.encode(c.uint64, r.index)
    buffers[j++] = c.encode(c.uint64, r.size)
  }

  if (!out) out = b4a.allocUnsafe(32)
  sodium.crypto_generichash_batch(out, buffers)
  return out
}

exports.hash = function (data, out) {
  if (!out) out = b4a.allocUnsafe(32)
  if (!Array.isArray(data)) data = [data]

  sodium.crypto_generichash_batch(out, data)

  return out
}

exports.randomBytes = function (n) {
  const buf = b4a.allocUnsafe(n)
  sodium.randombytes_buf(buf)
  return buf
}

exports.discoveryKey = function (key) {
  if (!key || key.byteLength !== 32) throw new Error('Must pass a 32 byte buffer')
  // Discovery keys might stay around for a while, so better not to use slab memory (for better gc)
  const digest = b4a.allocUnsafeSlow(32)
  sodium.crypto_generichash(digest, HYPERCORE, key)
  return digest
}

if (sodium.sodium_free) {
  exports.free = function (secureBuf) {
    if (secureBuf.secure) sodium.sodium_free(secureBuf)
  }
} else {
  exports.free = function () {}
}

exports.namespace = function (name, count) {
  const ids = typeof count === 'number' ? range(count) : count

  // Namespaces are long-lived, so better to use a dedicated slab
  const buf = b4a.allocUnsafeSlow(32 * ids.length)

  const list = new Array(ids.length)

  // ns is ephemeral, so default slab
  const ns = b4a.allocUnsafe(33)
  sodium.crypto_generichash(ns.subarray(0, 32), typeof name === 'string' ? b4a.from(name) : name)

  for (let i = 0; i < list.length; i++) {
    list[i] = buf.subarray(32 * i, 32 * i + 32)
    ns[32] = ids[i]
    sodium.crypto_generichash(list[i], ns)
  }

  return list
}

function range(count) {
  const arr = new Array(count)
  for (let i = 0; i < count; i++) arr[i] = i
  return arr
}
{
  "name": "hypercore-crypto",
  "version": "3.7.0",
  "description": "The crypto primitives used in hypercore, extracted into a separate module",
  "main": "index.js",
  "files": [
    "index.js"
  ],
  "dependencies": {
    "b4a": "^1.6.6",
    "compact-encoding": "^3.0.0",
    "sodium-universal": "^5.0.0"
  },
  "devDependencies": {
    "brittle": "^3.5.0",
    "lunte": "^1.0.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^2.0.0"
  },
  "scripts": {
    "format": "prettier --write .",
    "lint": "prettier --check . && lunte",
    "test": "npm run test:node && npm run test:bare",
    "test:node": "brittle-node test.js",
    "test:bare": "brittle-bare test.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/mafintosh/hypercore-crypto.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/mafintosh/hypercore-crypto/issues"
  },
  "homepage": "https://github.com/mafintosh/hypercore-crypto"
}
module.exports = require.addon.bind(require)
{
  "name": "require-addon",
  "version": "1.2.0",
  "description": "Import native addons across JavaScript runtimes",
  "exports": {
    "./package": "./package.json",
    ".": {
      "bare": "./lib/bare.js",
      "node": "./lib/node.js",
      "default": "./lib/default.js"
    }
  },
  "imports": {
    "fs": {
      "bare": "bare-fs",
      "default": "fs"
    },
    "path": {
      "bare": "bare-path",
      "default": "path"
    },
    "url": {
      "bare": "bare-url",
      "default": "url"
    }
  },
  "files": [
    "lib"
  ],
  "scripts": {
    "test": "npm run lint && npm run test:bare && npm run test:node",
    "test:bare": "bare test.js",
    "test:node": "node test.js",
    "lint": "prettier --check .",
    "format": "prettier --write ."
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/require-addon.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/require-addon/issues"
  },
  "homepage": "https://github.com/holepunchto/require-addon#readme",
  "engines": {
    "bare": ">=1.10.0"
  },
  "dependencies": {
    "bare-addon-resolve": "^1.3.0"
  },
  "devDependencies": {
    "bare-bundle": "^1.8.1",
    "bare-bundle-evaluate": "^1.1.0",
    "bare-fs": "^4.0.0",
    "bare-path": "^3.0.0",
    "bare-url": "^2.1.0",
    "brittle": "^3.7.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^2.0.0"
  }
}
require.addon = require('require-addon')
module.exports = require.addon('.', __filename)
const assert = require('assert')
const binding = require('./binding')
const { isNode } = require('which-runtime')

const OPTIONAL = Buffer.from(new ArrayBuffer(0))

module.exports = exports = { ...binding }

// memory

exports.sodium_memzero = function (buf) {
  assert(ArrayBuffer.isView(buf), 'buf must be a typed array')

  binding.sodium_memzero(buf)
}

exports.sodium_mlock = function (buf) {
  assert(ArrayBuffer.isView(buf), 'buf must be a typed array')

  const res = binding.sodium_mlock(buf)

  if (res !== 0) throw new Error('memory lock failed')
}

exports.sodium_munlock = function (buf) {
  assert(ArrayBuffer.isView(buf), 'buf must be a typed array')

  const res = binding.sodium_munlock(buf)

  if (res !== 0) throw new Error('memory unlock failed')
}

exports.sodium_malloc = function (size) {
  assert(size >= 0, 'invalid size')

  const buf = Buffer.from(binding.sodium_malloc(size))
  buf.secure = true
  return buf
}

exports.sodium_free = function (buf) {
  if (!buf || !buf.secure) return

  binding.sodium_free(buf.buffer)
}

exports.sodium_mprotect_noaccess = function (buf) {
  const res = binding.sodium_mprotect_noaccess(buf.buffer)

  if (res !== 0) throw new Error('failed to lock buffer')
}

exports.sodium_mprotect_readonly = function (buf) {
  const res = binding.sodium_mprotect_readonly(buf.buffer)

  if (res !== 0) throw new Error('failed to unlock buffer')
}

exports.sodium_mprotect_readwrite = function (buf) {
  const res = binding.sodium_mprotect_readwrite(buf.buffer)

  if (res !== 0) throw new Error('failed to unlock buffer')
}

// crypto_randombytes

exports.randombytes_buf = function (buffer) {
  assert(ArrayBuffer.isView(buffer), 'buffer must be a typed array')

  binding.randombytes_buf(buffer.buffer, buffer.byteOffset, buffer.byteLength)
}

exports.randombytes_buf_deterministic = function (buffer, seed) {
  assert(ArrayBuffer.isView(buffer), 'buffer must be a typed array')
  assert(ArrayBuffer.isView(seed), 'seed must be a typed array')
  assert(
    seed.byteLength === binding.randombytes_SEEDBYTES,
    "seed must be 'randombytes_SEEDBYTES' bytes"
  )

  binding.randombytes_buf_deterministic(
    buffer.buffer,
    buffer.byteOffset,
    buffer.byteLength,

    seed.buffer,
    seed.byteOffset,
    seed.byteLength
  )
}

// sodium_helpers

exports.sodium_memcmp = function (a, b) {
  assert(ArrayBuffer.isView(a), 'a must be a typed array')
  assert(ArrayBuffer.isView(b), 'b must be a typed array')
  assert(a.byteLength === b.byteLength, 'buffers must be of same length')

  return binding.sodium_memcmp(a, b)
}

exports.sodium_add = function (a, b) {
  assert(ArrayBuffer.isView(a), 'a must be a typed array')
  assert(ArrayBuffer.isView(b), 'b must be a typed array')
  assert(a.byteLength === b.byteLength, 'buffers must be of same length')

  binding.sodium_add(a, b)
}

exports.sodium_sub = function (a, b) {
  assert(ArrayBuffer.isView(a), 'a must be a typed array')
  assert(ArrayBuffer.isView(b), 'b must be a typed array')
  assert(a.byteLength === b.byteLength, 'buffers must be of same length')

  binding.sodium_sub(a, b)
}

exports.sodium_compare = function (a, b) {
  assert(ArrayBuffer.isView(a), 'a must be a typed array')
  assert(ArrayBuffer.isView(b), 'b must be a typed array')
  assert(a.byteLength === b.byteLength, 'buffers must be of same length')

  return binding.sodium_compare(a, b)
}

exports.sodium_is_zero = function (buffer, length) {
  if (length === undefined) length = buffer.byteLength

  assert(ArrayBuffer.isView(buffer), 'buffer must be a typed array')
  assert(length >= 0 && length <= buffer.byteLength, 'invalid length')

  return binding.sodium_is_zero(buffer, length)
}

exports.sodium_pad = function (buffer, unpaddedBuflen, blockSize) {
  assert(ArrayBuffer.isView(buffer), 'buffer must be a typed array')
  assert(unpaddedBuflen <= buffer.byteLength, 'unpadded length cannot exceed buffer length')
  assert(blockSize <= buffer.byteLength, 'block size cannot exceed buffer length')
  assert(blockSize >= 1, 'block size must be at least 1 byte')
  assert(
    buffer.byteLength >= unpaddedBuflen + (blockSize - (unpaddedBuflen % blockSize)),
    'buf not long enough'
  )

  return binding.sodium_pad(buffer, unpaddedBuflen, blockSize)
}

exports.sodium_unpad = function (buffer, paddedBuflen, blockSize) {
  assert(ArrayBuffer.isView(buffer), 'buffer must be a typed array')
  assert(paddedBuflen <= buffer.byteLength, 'unpadded length cannot exceed buffer length')
  assert(blockSize <= buffer.byteLength, 'block size cannot exceed buffer length')
  assert(blockSize >= 1, 'block size must be at least 1 byte')

  return binding.sodium_unpad(buffer, paddedBuflen, blockSize)
}

// crypto_sign

exports.crypto_sign_keypair = function (pk, sk) {
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(
    pk.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
    "pk must be 'crypto_sign_PUBLICKEYBYTES' bytes"
  )
  assert(
    sk.byteLength === binding.crypto_sign_SECRETKEYBYTES,
    "sk must be 'crypto_sign_SECRETKEYBYTES' bytes"
  )

  const res = binding.crypto_sign_keypair(pk, sk)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_sign_seed_keypair = function (pk, sk, seed) {
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(ArrayBuffer.isView(seed), 'seed must be a typed array')
  assert(
    pk.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
    "pk must be 'crypto_sign_PUBLICKEYBYTES' bytes"
  )
  assert(
    sk.byteLength === binding.crypto_sign_SECRETKEYBYTES,
    "sk must be 'crypto_sign_SECRETKEYBYTES' bytes"
  )
  assert(
    seed.byteLength === binding.crypto_sign_SEEDBYTES,
    "seed must be 'crypto_sign_SEEDBYTES' bytes"
  )

  const res = binding.crypto_sign_seed_keypair(pk, sk, seed)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_sign = function (sm, m, sk) {
  assert(ArrayBuffer.isView(sm), 'sm must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(
    sm.byteLength === binding.crypto_sign_BYTES + m.byteLength,
    "sm must be 'm.byteLength + crypto_sign_BYTES' bytes"
  )
  assert(
    sk.byteLength === binding.crypto_sign_SECRETKEYBYTES,
    "sk must be 'crypto_sign_SECRETKEYBYTES' bytes"
  )

  const res = binding.crypto_sign(sm, m, sk)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_sign_open = function (m, sm, pk) {
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(sm), 'sm must be a typed array')
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(
    sm.byteLength >= binding.crypto_sign_BYTES,
    "sm must be at least 'crypto_sign_BYTES' bytes"
  )
  assert(
    m.byteLength === sm.byteLength - binding.crypto_sign_BYTES,
    "m must be 'sm.byteLength - crypto_sign_BYTES' bytes"
  )
  assert(
    pk.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
    "pk must be 'crypto_sign_PUBLICKEYBYTES' bytes"
  )

  const res = binding.crypto_sign_open(m, sm, pk)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_sign_open = function (m, sm, pk) {
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(sm), 'sm must be a typed array')
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(
    sm.byteLength >= binding.crypto_sign_BYTES,
    "sm must be at least 'crypto_sign_BYTES' bytes"
  )
  assert(
    m.byteLength === sm.byteLength - binding.crypto_sign_BYTES,
    "m must be 'sm.byteLength - crypto_sign_BYTES' bytes"
  )
  assert(
    pk.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
    "pk must be 'crypto_sign_PUBLICKEYBYTES' bytes"
  )

  return binding.crypto_sign_open(m, sm, pk)
}

exports.crypto_sign_detached = function (sig, m, sk) {
  assert(ArrayBuffer.isView(sig), 'sig must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(sig.byteLength === binding.crypto_sign_BYTES, "sig must be 'crypto_sign_BYTES' bytes")
  assert(
    sk.byteLength === binding.crypto_sign_SECRETKEYBYTES,
    "sk must be 'crypto_sign_SECRETKEYBYTES' bytes"
  )

  const res = binding.crypto_sign_detached(sig, m, sk)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_sign_verify_detached = function (sig, m, pk) {
  assert(ArrayBuffer.isView(sig), 'sig must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(
    sig.byteLength >= binding.crypto_sign_BYTES,
    "sig must be at least 'crypto_sign_BYTES' bytes"
  )
  assert(
    pk.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
    "pk must be 'crypto_sign_PUBLICKEYBYTES' bytes"
  )

  return binding.crypto_sign_verify_detached(
    sig.buffer,
    sig.byteOffset,
    sig.byteLength,

    m.buffer,
    m.byteOffset,
    m.byteLength,

    pk.buffer,
    pk.byteOffset,
    pk.byteLength
  )
}

exports.crypto_sign_ed25519_sk_to_pk = function (pk, sk) {
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(
    pk.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
    "pk must be 'crypto_sign_PUBLICKEYBYTES' bytes"
  )
  assert(
    sk.byteLength === binding.crypto_sign_SECRETKEYBYTES,
    "sk must be 'crypto_sign_SECRETKEYBYTES' bytes"
  )

  const res = binding.crypto_sign_ed25519_sk_to_pk(pk, sk)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_sign_ed25519_pk_to_curve25519 = function (x25519pk, ed25519pk) {
  assert(ArrayBuffer.isView(x25519pk), 'x25519pk must be a typed array')
  assert(ArrayBuffer.isView(ed25519pk), 'ed25519pk must be a typed array')
  assert(
    x25519pk.byteLength === binding.crypto_box_PUBLICKEYBYTES,
    "x25519pk must be 'crypto_box_PUBLICKEYBYTES' bytes"
  )
  assert(
    ed25519pk.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
    "ed25519pk must be 'crypto_sign_PUBLICKEYBYTES' bytes"
  )

  const res = binding.crypto_sign_ed25519_pk_to_curve25519(x25519pk, ed25519pk)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_sign_ed25519_sk_to_curve25519 = function (x25519sk, ed25519sk) {
  assert(ArrayBuffer.isView(x25519sk), 'x25519sk must be a typed array')
  assert(ArrayBuffer.isView(ed25519sk), 'ed25519sk must be a typed array')
  assert(
    x25519sk.byteLength === binding.crypto_box_SECRETKEYBYTES,
    "x25519sk must be 'crypto_box_SECRETKEYBYTES' bytes"
  )

  const edLen = ed25519sk.byteLength

  assert(
    edLen === binding.crypto_sign_SECRETKEYBYTES || edLen === binding.crypto_box_SECRETKEYBYTES,
    "ed25519sk must be 'crypto_sign_SECRETKEYBYTES' or 'crypto_sign_SECRETKEYBYTES - crypto_sign_PUBLICKEYBYTES' bytes"
  )

  const res = binding.crypto_sign_ed25519_sk_to_curve25519(x25519sk, ed25519sk)

  if (res !== 0) throw new Error('status: ' + res)
}

// crypto_box

exports.crypto_box_keypair = function (pk, sk) {
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(
    pk.byteLength === binding.crypto_box_PUBLICKEYBYTES,
    "pk must be 'crypto_box_PUBLICKEYBYTES' bytes"
  )
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(
    sk.byteLength === binding.crypto_box_SECRETKEYBYTES,
    "sk must be 'crypto_box_SECRETKEYBYTES' bytes"
  )

  const res = binding.crypto_box_keypair(pk, sk)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_box_seed_keypair = function (pk, sk, seed) {
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(
    pk.byteLength === binding.crypto_box_PUBLICKEYBYTES,
    "pk must be 'crypto_box_PUBLICKEYBYTES' bytes"
  )
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(
    sk.byteLength === binding.crypto_box_SECRETKEYBYTES,
    "sk must be 'crypto_box_SECRETKEYBYTES' bytes"
  )
  assert(ArrayBuffer.isView(seed), 'seed must be a typed array')
  assert(
    seed.byteLength === binding.crypto_box_SEEDBYTES,
    "seed must be 'crypto_box_SEEDBYTES' bytes"
  )

  const res = binding.crypto_box_seed_keypair(pk, sk, seed)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_box_easy = function (c, m, n, pk, sk) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(
    c.byteLength === m.byteLength + exports.crypto_box_MACBYTES,
    "c must be 'm.byteLength + crypto_box_MACBYTES' bytes"
  )
  assert(n.byteLength === exports.crypto_box_NONCEBYTES, "n must be 'crypto_box_NONCEBYTES' bytes")
  assert(
    pk.byteLength === exports.crypto_box_PUBLICKEYBYTES,
    "pk must be 'crypto_box_PUBLICKEYBYTES' bytes"
  )
  assert(
    sk.byteLength === exports.crypto_box_SECRETKEYBYTES,
    "sk must be 'crypto_box_SECRETKEYBYTES' bytes"
  )

  const res = binding.crypto_box_easy(c, m, n, pk, sk)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_box_detached = function (c, mac, m, n, pk, sk) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(mac), 'mac must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(mac.byteLength === exports.crypto_box_MACBYTES, "mac must be 'crypto_box_MACBYTES' bytes")
  assert(n.byteLength === exports.crypto_box_NONCEBYTES, "n must be 'crypto_box_NONCEBYTES' bytes")
  assert(
    pk.byteLength === exports.crypto_box_PUBLICKEYBYTES,
    "pk must be 'crypto_box_PUBLICKEYBYTES' bytes"
  )
  assert(
    sk.byteLength === exports.crypto_box_SECRETKEYBYTES,
    "sk must be 'crypto_box_SECRETKEYBYTES' bytes"
  )

  const res = binding.crypto_box_detached(c, mac, m, n, pk, sk)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_box_open_easy = function (m, c, n, pk, sk) {
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(
    c.byteLength >= exports.crypto_box_MACBYTES,
    "c must be at least 'crypto_box_MACBYTES' bytes"
  )
  assert(
    m.byteLength === c.byteLength - exports.crypto_box_MACBYTES,
    "m must be 'c.byteLength - crypto_box_MACBYTES' bytes"
  )
  assert(n.byteLength === exports.crypto_box_NONCEBYTES, "n must be 'crypto_box_NONCEBYTES' bytes")
  assert(
    pk.byteLength === exports.crypto_box_PUBLICKEYBYTES,
    "pk must be 'crypto_box_PUBLICKEYBYTES' bytes"
  )
  assert(
    sk.byteLength === exports.crypto_box_SECRETKEYBYTES,
    "sk must be 'crypto_box_SECRETKEYBYTES' bytes"
  )

  return binding.crypto_box_open_easy(m, c, n, pk, sk)
}

exports.crypto_box_open_detached = function (m, c, mac, n, pk, sk) {
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(mac), 'mac must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(m.byteLength === c.byteLength, "m must be 'c.byteLength' bytes")
  assert(mac.byteLength === exports.crypto_box_MACBYTES, "mac must be 'crypto_box_MACBYTES' bytes")
  assert(n.byteLength === exports.crypto_box_NONCEBYTES, "n must be 'crypto_box_NONCEBYTES' bytes")
  assert(
    pk.byteLength === exports.crypto_box_PUBLICKEYBYTES,
    "pk must be 'crypto_box_PUBLICKEYBYTES' bytes"
  )
  assert(
    sk.byteLength === exports.crypto_box_SECRETKEYBYTES,
    "sk must be 'crypto_box_SECRETKEYBYTES' bytes"
  )

  return binding.crypto_box_open_detached(m, c, mac, n, pk, sk)
}

exports.crypto_box_seal = function (c, m, pk) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(
    c.byteLength === m.byteLength + exports.crypto_box_SEALBYTES,
    "c must be 'm.byteLength + crypto_box_SEALBYTES' bytes"
  )
  assert(
    pk.byteLength === exports.crypto_box_PUBLICKEYBYTES,
    "pk must be 'crypto_box_PUBLICKEYBYTES' bytes"
  )

  const res = binding.crypto_box_seal(c, m, pk)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_box_seal_open = function (m, c, pk, sk) {
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(
    c.byteLength >= exports.crypto_box_SEALBYTES,
    "c must be at least 'crypto_box_SEALBYTES' bytes"
  )
  assert(
    m.byteLength === c.byteLength - exports.crypto_box_SEALBYTES,
    "m must be 'c.byteLength - crypto_box_SEALBYTES' bytes"
  )
  assert(
    pk.byteLength === exports.crypto_box_PUBLICKEYBYTES,
    "pk must be 'crypto_box_PUBLICKEYBYTES' bytes"
  )
  assert(
    sk.byteLength === exports.crypto_box_SECRETKEYBYTES,
    "sk must be 'crypto_box_SECRETKEYBYTES' bytes"
  )

  return binding.crypto_box_seal_open(
    m.buffer,
    m.byteOffset,
    m.byteLength,

    c.buffer,
    c.byteOffset,
    c.byteLength,

    pk.buffer,
    pk.byteOffset,
    pk.byteLength,

    sk.buffer,
    sk.byteOffset,
    sk.byteLength
  )
}

// crypto_secretbox

exports.crypto_secretbox_easy = function (c, m, n, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    c.byteLength === m.byteLength + binding.crypto_secretbox_MACBYTES,
    "c must be 'm.byteLength + crypto_secretbox_MACBYTES' bytes"
  )
  assert(
    n.byteLength === binding.crypto_secretbox_NONCEBYTES,
    "n must be 'crypto_secretbox_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_secretbox_KEYBYTES,
    "k must be 'crypto_secretbox_KEYBYTES' bytes"
  )

  const res = binding.crypto_secretbox_easy(c, m, n, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_secretbox_open_easy = function (m, c, n, k) {
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    c.byteLength >= binding.crypto_secretbox_MACBYTES,
    "c must be at least 'crypto_secretbox_MACBYTES' bytes"
  )
  assert(
    m.byteLength === c.byteLength - binding.crypto_secretbox_MACBYTES,
    "m must be 'c.byteLength - crypto_secretbox_MACBYTES' bytes"
  )
  assert(
    n.byteLength === binding.crypto_secretbox_NONCEBYTES,
    "n must be 'crypto_secretbox_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_secretbox_KEYBYTES,
    "k must be 'crypto_secretbox_KEYBYTES' bytes"
  )

  return binding.crypto_secretbox_open_easy(m, c, n, k)
}

exports.crypto_secretbox_detached = function (c, mac, m, n, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(mac), 'mac must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(
    mac.byteLength === binding.crypto_secretbox_MACBYTES,
    "mac must be 'crypto_secretbox_MACBYTES' bytes"
  )
  assert(
    n.byteLength === binding.crypto_secretbox_NONCEBYTES,
    "n must be 'crypto_secretbox_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_secretbox_KEYBYTES,
    "k must be 'crypto_secretbox_KEYBYTES' bytes"
  )

  const res = binding.crypto_secretbox_detached(c, mac, m, n, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_secretbox_open_detached = function (m, c, mac, n, k) {
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(mac), 'mac must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(m.byteLength === c.byteLength, "m must be 'c.byteLength' bytes")
  assert(
    mac.byteLength === binding.crypto_secretbox_MACBYTES,
    "mac must be 'crypto_secretbox_MACBYTES' bytes"
  )
  assert(
    n.byteLength === binding.crypto_secretbox_NONCEBYTES,
    "n must be 'crypto_secretbox_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_secretbox_KEYBYTES,
    "k must be 'crypto_secretbox_KEYBYTES' bytes"
  )

  return binding.crypto_secretbox_open_detached(m, c, mac, n, k)
}

// crypto_generichash

exports.crypto_generichash = function (output, input, key) {
  if (!key) key = OPTIONAL

  assert(ArrayBuffer.isView(output), 'output must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(
    output.byteLength >= binding.crypto_generichash_BYTES_MIN &&
      output.byteLength <= binding.crypto_generichash_BYTES_MAX,
    'output must be between crypto_generichash_BYTES_MIN and crypto_generichash_BYTES_MAX bytes'
  )

  if (key !== OPTIONAL) {
    assert(ArrayBuffer.isView(key), 'key must be a typed array')
    assert(
      key.byteLength >= binding.crypto_generichash_KEYBYTES_MIN &&
        key.byteLength <= binding.crypto_generichash_KEYBYTES_MAX,
      'key must be between crypto_generichash_KEYBYTES_MIN and crypto_generichash_KEYBYTES_MAX bytes'
    )
  }

  const res = binding.crypto_generichash(
    output.buffer,
    output.byteOffset,
    output.byteLength,

    input.buffer,
    input.byteOffset,
    input.byteLength,

    key.buffer,
    key.byteOffset,
    key.byteLength
  )

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_generichash_batch = function (output, batch, key) {
  assert(ArrayBuffer.isView(output), 'output must be a typed array')

  if (isNode || batch.length < 4) {
    const res = binding.crypto_generichash_batch(output, batch, !!key, key || OPTIONAL)
    if (res !== 0) throw new Error('status: ' + res)
  } else {
    const state = Buffer.alloc(binding.crypto_generichash_STATEBYTES)

    exports.crypto_generichash_init(state, key, output.byteLength)

    for (const buf of batch) {
      exports.crypto_generichash_update(state, buf)
    }

    exports.crypto_generichash_final(state, output)
  }
}

exports.crypto_generichash_keygen = function (key) {
  assert(ArrayBuffer.isView(key), 'key must be a typed array')
  assert(
    key.byteLength === binding.crypto_generichash_KEYBYTES,
    "key must be 'crypto_generichash_KEYBYTES' bytes"
  )

  const res = binding.crypto_generichash_keygen(key.buffer, key.byteOffset, key.byteLength)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_generichash_init = function (state, key, outputLength) {
  if (!key) key = OPTIONAL

  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_generichash_STATEBYTES,
    "state must be 'crypto_generichash_STATEBYTES' bytes"
  )

  const res = binding.crypto_generichash_init(
    state.buffer,
    state.byteOffset,
    state.byteLength,

    key.buffer,
    key.byteOffset,
    key.byteLength,

    outputLength
  )

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_generichash_update = function (state, input) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(
    state.byteLength === binding.crypto_generichash_STATEBYTES,
    "state must be 'crypto_generichash_STATEBYTES' bytes"
  )

  const res = binding.crypto_generichash_update(
    state.buffer,
    state.byteOffset,
    state.byteLength,

    input.buffer,
    input.byteOffset,
    input.byteLength
  )

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_generichash_final = function (state, output) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(ArrayBuffer.isView(output), 'output must be a typed array')
  assert(
    state.byteLength === binding.crypto_generichash_STATEBYTES,
    "state must be 'crypto_generichash_STATEBYTES' bytes"
  )

  const res = binding.crypto_generichash_final(
    state.buffer,
    state.byteOffset,
    state.byteLength,

    output.buffer,
    output.byteOffset,
    output.byteLength
  )

  if (res !== 0) throw new Error('status: ' + res)
}

// secretstream

exports.crypto_secretstream_xchacha20poly1305_keygen = function (k) {
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_secretstream_xchacha20poly1305_KEYBYTES,
    "k must be 'crypto_secretstream_xchacha20poly1305_KEYBYTES' bytes"
  )

  binding.crypto_secretstream_xchacha20poly1305_keygen(k.buffer, k.byteOffset, k.byteLength)
}

exports.crypto_secretstream_xchacha20poly1305_init_push = function (state, header, k) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(ArrayBuffer.isView(header), 'header must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    state.byteLength === binding.crypto_secretstream_xchacha20poly1305_STATEBYTES,
    "state must be 'crypto_secretstream_xchacha20poly1305_STATEBYTES' bytes"
  )
  assert(
    header.byteLength === binding.crypto_secretstream_xchacha20poly1305_HEADERBYTES,
    "header must be 'crypto_secretstream_xchacha20poly1305_HEADERBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_secretstream_xchacha20poly1305_KEYBYTES,
    "k must be 'crypto_secretstream_xchacha20poly1305_KEYBYTES' bytes"
  )

  const res = binding.crypto_secretstream_xchacha20poly1305_init_push(
    state.buffer,
    state.byteOffset,
    state.byteLength,

    header.buffer,
    header.byteOffset,
    header.byteLength,

    k.buffer,
    k.byteOffset,
    k.byteLength
  )

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_secretstream_xchacha20poly1305_init_pull = function (state, header, k) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(ArrayBuffer.isView(header), 'header must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    state.byteLength === binding.crypto_secretstream_xchacha20poly1305_STATEBYTES,
    "state must be 'crypto_secretstream_xchacha20poly1305_STATEBYTES' bytes"
  )
  assert(
    header.byteLength === binding.crypto_secretstream_xchacha20poly1305_HEADERBYTES,
    "header must be 'crypto_secretstream_xchacha20poly1305_HEADERBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_secretstream_xchacha20poly1305_KEYBYTES,
    "k must be 'crypto_secretstream_xchacha20poly1305_KEYBYTES' bytes"
  )

  const res = binding.crypto_secretstream_xchacha20poly1305_init_pull(
    state.buffer,
    state.byteOffset,
    state.byteLength,

    header.buffer,
    header.byteOffset,
    header.byteLength,

    k.buffer,
    k.byteOffset,
    k.byteLength
  )

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_secretstream_xchacha20poly1305_push = function (state, c, m, ad, tag) {
  if (!ad) ad = OPTIONAL

  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(
    state.byteLength === binding.crypto_secretstream_xchacha20poly1305_STATEBYTES,
    "state must be 'crypto_secretstream_xchacha20poly1305_STATEBYTES' bytes"
  )
  assert(
    c.byteLength === m.byteLength + binding.crypto_secretstream_xchacha20poly1305_ABYTES,
    "c must be 'm.byteLength + crypto_secretstream_xchacha20poly1305_ABYTES' bytes"
  )

  const res = binding.crypto_secretstream_xchacha20poly1305_push(
    state.buffer,
    state.byteOffset,
    state.byteLength,

    c.buffer,
    c.byteOffset,
    c.byteLength,

    m.buffer,
    m.byteOffset,
    m.byteLength,

    ad.buffer,
    ad.byteOffset,
    ad.byteLength,

    tag
  )

  if (res < 0) throw new Error('push failed')

  return res
}

exports.crypto_secretstream_xchacha20poly1305_pull = function (state, m, tag, c, ad) {
  if (!ad) ad = OPTIONAL

  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_secretstream_xchacha20poly1305_STATEBYTES,
    "state must be 'crypto_secretstream_xchacha20poly1305_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(tag), 'tag must be a typed array')
  assert(tag.byteLength === 1, 'tag must be 1 byte')

  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(
    c.byteLength >= binding.crypto_secretstream_xchacha20poly1305_ABYTES,
    "c must be at least 'crypto_secretstream_xchacha20poly1305_ABYTES' bytes"
  )
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(
    m.byteLength === c.byteLength - binding.crypto_secretstream_xchacha20poly1305_ABYTES,
    "m must be 'c.byteLength - crypto_secretstream_xchacha20poly1305_ABYTES' bytes"
  )

  const res = binding.crypto_secretstream_xchacha20poly1305_pull(
    state.buffer,
    state.byteOffset,
    state.byteLength,

    m.buffer,
    m.byteOffset,
    m.byteLength,

    tag.buffer,
    tag.byteOffset,
    tag.byteLength,

    c.buffer,
    c.byteOffset,
    c.byteLength,

    ad.buffer,
    ad.byteOffset,
    ad.byteLength
  )

  if (res < 0) throw new Error('pull failed')

  return res
}

exports.crypto_secretstream_xchacha20poly1305_rekey = function (state) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_secretstream_xchacha20poly1305_STATEBYTES,
    "state must be 'crypto_secretstream_xchacha20poly1305_STATEBYTES' bytes"
  )

  binding.crypto_secretstream_xchacha20poly1305_rekey(
    state.buffer,
    state.byteOffset,
    state.byteLength
  )
}

// crypto_stream

exports.crypto_stream = function (c, n, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    n.byteLength === binding.crypto_stream_NONCEBYTES,
    "n must be 'crypto_stream_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_KEYBYTES,
    "k must be 'crypto_stream_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream(c, n, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_xor = function (c, m, n, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(
    n.byteLength === binding.crypto_stream_NONCEBYTES,
    "n must be 'crypto_stream_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_KEYBYTES,
    "k must be 'crypto_stream_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_xor(
    c.buffer,
    c.byteOffset,
    c.byteLength,

    m.buffer,
    m.byteOffset,
    m.byteLength,

    n.buffer,
    n.byteOffset,
    n.byteLength,

    k.buffer,
    k.byteOffset,
    k.byteLength
  )

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_chacha20 = function (c, n, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    n.byteLength === binding.crypto_stream_chacha20_NONCEBYTES,
    "n must be 'crypto_stream_chacha20_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_chacha20_KEYBYTES,
    "k must be 'crypto_stream_chacha20_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_chacha20(c, n, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_chacha20_xor = function (c, m, n, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(
    n.byteLength === binding.crypto_stream_chacha20_NONCEBYTES,
    "n must be 'crypto_stream_chacha20_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_chacha20_KEYBYTES,
    "k must be 'crypto_stream_chacha20_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_chacha20_xor(c, m, n, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_chacha20_xor_ic = function (c, m, n, ic, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(
    n.byteLength === binding.crypto_stream_chacha20_NONCEBYTES,
    "n must be 'crypto_stream_chacha20_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_chacha20_KEYBYTES,
    "k must be 'crypto_stream_chacha20_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_chacha20_xor_ic(c, m, n, ic, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_chacha20_ietf = function (c, n, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    n.byteLength === binding.crypto_stream_chacha20_ietf_NONCEBYTES,
    "n must be 'crypto_stream_chacha20_ietf_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_chacha20_ietf_KEYBYTES,
    "k must be 'crypto_stream_chacha20_ietf_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_chacha20_ietf(c, n, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_chacha20_ietf_xor = function (c, m, n, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(
    n.byteLength === binding.crypto_stream_chacha20_ietf_NONCEBYTES,
    "n must be 'crypto_stream_chacha20_ietf_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_chacha20_ietf_KEYBYTES,
    "k must be 'crypto_stream_chacha20_ietf_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_chacha20_ietf_xor(c, m, n, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_chacha20_ietf_xor_ic = function (c, m, n, ic, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(
    n.byteLength === binding.crypto_stream_chacha20_ietf_NONCEBYTES,
    "n must be 'crypto_stream_chacha20_ietf_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_chacha20_ietf_KEYBYTES,
    "k must be 'crypto_stream_chacha20_ietf_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_chacha20_ietf_xor_ic(c, m, n, ic, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_xchacha20 = function (c, n, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    n.byteLength === binding.crypto_stream_xchacha20_NONCEBYTES,
    "n must be 'crypto_stream_xchacha20_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_xchacha20_KEYBYTES,
    "k must be 'crypto_stream_xchacha20_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_xchacha20(c, n, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_xchacha20_xor = function (c, m, n, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(
    n.byteLength === binding.crypto_stream_xchacha20_NONCEBYTES,
    "n must be 'crypto_stream_xchacha20_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_xchacha20_KEYBYTES,
    "k must be 'crypto_stream_xchacha20_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_xchacha20_xor(c, m, n, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_xchacha20_xor_ic = function (c, m, n, ic, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(
    n.byteLength === binding.crypto_stream_xchacha20_NONCEBYTES,
    "n must be 'crypto_stream_xchacha20_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_xchacha20_KEYBYTES,
    "k must be 'crypto_stream_xchacha20_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_xchacha20_xor_ic(c, m, n, ic, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_salsa20 = function (c, n, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    n.byteLength === binding.crypto_stream_salsa20_NONCEBYTES,
    "n must be 'crypto_stream_salsa20_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_salsa20_KEYBYTES,
    "k must be 'crypto_stream_salsa20_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_salsa20(c, n, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_salsa20_xor = function (c, m, n, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(
    n.byteLength === binding.crypto_stream_salsa20_NONCEBYTES,
    "n must be 'crypto_stream_salsa20_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_salsa20_KEYBYTES,
    "k must be 'crypto_stream_salsa20_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_salsa20_xor(c, m, n, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_salsa20_xor_ic = function (c, m, n, ic, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(
    n.byteLength === binding.crypto_stream_salsa20_NONCEBYTES,
    "n must be 'crypto_stream_salsa20_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_salsa20_KEYBYTES,
    "k must be 'crypto_stream_salsa20_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_salsa20_xor_ic(c, m, n, ic, k)

  if (res !== 0) throw new Error('status: ' + res)
}

// crypto_auth

exports.crypto_auth = function (out, input, k) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(out.byteLength === binding.crypto_auth_BYTES, "out must be 'crypto_auth_BYTES' bytes")
  assert(k.byteLength === binding.crypto_auth_KEYBYTES, "k must be 'crypto_auth_KEYBYTES' bytes")

  const res = binding.crypto_auth(out, input, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_auth_verify = function (h, input, k) {
  assert(ArrayBuffer.isView(h), 'h must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(h.byteLength === binding.crypto_auth_BYTES, "h must be 'crypto_auth_BYTES' bytes")
  assert(k.byteLength === binding.crypto_auth_KEYBYTES, "k must be 'crypto_auth_KEYBYTES' bytes")

  return binding.crypto_auth_verify(h, input, k)
}

// crypto_onetimeauth

exports.crypto_onetimeauth = function (out, input, k) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    out.byteLength === binding.crypto_onetimeauth_BYTES,
    "out must be 'crypto_onetimeauth_BYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_onetimeauth_KEYBYTES,
    "k must be 'crypto_onetimeauth_KEYBYTES' bytes"
  )

  const res = binding.crypto_onetimeauth(out, input, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_onetimeauth_init = function (state, k) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    state.byteLength === binding.crypto_onetimeauth_STATEBYTES,
    "state must be 'crypto_onetimeauth_STATEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_onetimeauth_KEYBYTES,
    "k must be 'crypto_onetimeauth_KEYBYTES' bytes"
  )

  const res = binding.crypto_onetimeauth_init(state, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_onetimeauth_update = function (state, input) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(
    state.byteLength === binding.crypto_onetimeauth_STATEBYTES,
    "state must be 'crypto_onetimeauth_STATEBYTES' bytes"
  )

  const res = binding.crypto_onetimeauth_update(state, input)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_onetimeauth_final = function (state, out) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(
    state.byteLength === binding.crypto_onetimeauth_STATEBYTES,
    "state must be 'crypto_onetimeauth_STATEBYTES' bytes"
  )
  assert(
    out.byteLength === binding.crypto_onetimeauth_BYTES,
    "out must be 'crypto_onetimeauth_BYTES' bytes"
  )

  const res = binding.crypto_onetimeauth_final(state, out)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_onetimeauth_verify = function (h, input, k) {
  assert(ArrayBuffer.isView(h), 'h must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    h.byteLength === binding.crypto_onetimeauth_BYTES,
    "h must be 'crypto_onetimeauth_BYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_onetimeauth_KEYBYTES,
    "k must be 'crypto_onetimeauth_KEYBYTES' bytes"
  )

  return binding.crypto_onetimeauth_verify(h, input, k)
}

// crypto_pwhash

exports.crypto_pwhash = function (out, passwd, salt, opslimit, memlimit, alg) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(ArrayBuffer.isView(salt), 'salt must be a typed array')
  assert(
    out.byteLength >= binding.crypto_pwhash_BYTES_MIN,
    "out must be at least 'crypto_pwhash_BYTES_MIN' bytes"
  )
  assert(
    out.byteLength <= binding.crypto_pwhash_BYTES_MAX,
    "out must be at most 'crypto_pwhash_BYTES_MAX' bytes"
  )
  assert(
    salt.byteLength === binding.crypto_pwhash_SALTBYTES,
    "salt must be 'crypto_pwhash_SALTBYTES' bytes"
  )
  assert(
    opslimit >= binding.crypto_pwhash_OPSLIMIT_MIN,
    "opslimit must be at least 'crypto_pwhash_OPSLIMIT_MIN'"
  )
  assert(
    opslimit <= binding.crypto_pwhash_OPSLIMIT_MAX,
    "opslimit must be at most 'crypto_pwhash_OPSLIMIT_MAX'"
  )
  assert(
    memlimit >= binding.crypto_pwhash_MEMLIMIT_MIN,
    "memlimit must be at least 'crypto_pwhash_MEMLIMIT_MIN'"
  )
  assert(
    memlimit <= binding.crypto_pwhash_MEMLIMIT_MAX,
    "memlimit must be at most 'crypto_pwhash_MEMLIMIT_MAX'"
  )
  assert(alg >= 1 && alg <= 2, 'alg must be either Argon2i 1.3 or Argon2id 1.3')

  const res = binding.crypto_pwhash(out, passwd, salt, opslimit, memlimit, alg)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_pwhash_async = function (
  out,
  passwd,
  salt,
  opslimit,
  memlimit,
  alg,
  callback = undefined
) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(ArrayBuffer.isView(salt), 'salt must be a typed array')
  assert(
    out.byteLength >= binding.crypto_pwhash_BYTES_MIN,
    "out must be at least 'crypto_pwhash_BYTES_MIN' bytes"
  )
  assert(
    out.byteLength <= binding.crypto_pwhash_BYTES_MAX,
    "out must be at most 'crypto_pwhash_BYTES_MAX' bytes"
  )
  assert(
    salt.byteLength === binding.crypto_pwhash_SALTBYTES,
    "salt must be 'crypto_pwhash_SALTBYTES' bytes"
  )
  assert(
    opslimit >= binding.crypto_pwhash_OPSLIMIT_MIN,
    "opslimit must be at least 'crypto_pwhash_OPSLIMIT_MIN'"
  )
  assert(
    opslimit <= binding.crypto_pwhash_OPSLIMIT_MAX,
    "opslimit must be at most 'crypto_pwhash_OPSLIMIT_MAX'"
  )
  assert(
    memlimit >= binding.crypto_pwhash_MEMLIMIT_MIN,
    "memlimit must be at least 'crypto_pwhash_MEMLIMIT_MIN'"
  )
  assert(
    memlimit <= binding.crypto_pwhash_MEMLIMIT_MAX,
    "memlimit must be at most 'crypto_pwhash_MEMLIMIT_MAX'"
  )
  assert(alg >= 1 && alg <= 2, 'alg must be either Argon2i 1.3 or Argon2id 1.3')

  const [done, promise] = checkStatus(callback)

  binding.crypto_pwhash_async(
    out.buffer,
    out.byteOffset,
    out.byteLength,

    passwd.buffer,
    passwd.byteOffset,
    passwd.byteLength,

    salt.buffer,
    salt.byteOffset,
    salt.byteLength,

    opslimit,
    memlimit,
    alg,

    done
  )

  return promise
}

exports.crypto_pwhash_str = function (out, passwd, opslimit, memlimit) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(
    out.byteLength === binding.crypto_pwhash_STRBYTES,
    "out must be 'crypto_pwhash_STRBYTES' bytes"
  )
  assert(typeof opslimit === 'number', 'opslimit must be a number')
  assert(
    opslimit >= binding.crypto_pwhash_OPSLIMIT_MIN,
    "opslimit must be at least 'crypto_pwhash_OPSLIMIT_MIN'"
  )
  assert(
    opslimit <= binding.crypto_pwhash_OPSLIMIT_MAX,
    "opslimit must be at most 'crypto_pwhash_OPSLIMIT_MAX'"
  )
  assert(typeof memlimit === 'number', 'memlimit must be a number')
  assert(
    memlimit >= binding.crypto_pwhash_MEMLIMIT_MIN,
    "memlimit must be at least 'crypto_pwhash_MEMLIMIT_MIN'"
  )
  assert(
    memlimit <= binding.crypto_pwhash_MEMLIMIT_MAX,
    "memlimit must be at most 'crypto_pwhash_MEMLIMIT_MAX'"
  )

  const res = binding.crypto_pwhash_str(out, passwd, opslimit, memlimit)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_pwhash_str_async = function (out, passwd, opslimit, memlimit, callback = undefined) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(
    out.byteLength === binding.crypto_pwhash_STRBYTES,
    "out must be 'crypto_pwhash_STRBYTES' bytes"
  )
  assert(passwd.byteLength > 0, 'passwd must not be empty')
  assert(typeof opslimit === 'number', 'opslimit must be a number')
  assert(
    opslimit >= binding.crypto_pwhash_OPSLIMIT_MIN,
    "opslimit must be at least 'crypto_pwhash_OPSLIMIT_MIN'"
  )
  assert(
    opslimit <= binding.crypto_pwhash_OPSLIMIT_MAX,
    "opslimit must be at most 'crypto_pwhash_OPSLIMIT_MAX'"
  )
  assert(typeof memlimit === 'number', 'memlimit must be a number')
  assert(
    memlimit >= binding.crypto_pwhash_MEMLIMIT_MIN,
    "memlimit must be at least 'crypto_pwhash_MEMLIMIT_MIN'"
  )
  assert(
    memlimit <= binding.crypto_pwhash_MEMLIMIT_MAX,
    "memlimit must be at most 'crypto_pwhash_MEMLIMIT_MAX'"
  )

  const [done, promise] = checkStatus(callback)

  binding.crypto_pwhash_str_async(
    out.buffer,
    out.byteOffset,
    out.byteLength,

    passwd.buffer,
    passwd.byteOffset,
    passwd.byteLength,

    opslimit,
    memlimit,

    done
  )

  return promise
}

exports.crypto_pwhash_str_verify = function (str, passwd) {
  assert(ArrayBuffer.isView(str), 'str must be a typed array')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(
    str.byteLength === binding.crypto_pwhash_STRBYTES,
    "str must be 'crypto_pwhash_STRBYTES' bytes"
  )

  return binding.crypto_pwhash_str_verify(str, passwd)
}

exports.crypto_pwhash_str_verify_async = function (str, passwd, callback = undefined) {
  assert(ArrayBuffer.isView(str), 'str must be a typed array')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(
    str.byteLength === binding.crypto_pwhash_STRBYTES,
    "str must be 'crypto_pwhash_STRBYTES' bytes"
  )
  assert(passwd.byteLength > 0, 'passwd must not be empty')

  const [done, promise] = checkStatus(callback, true)

  binding.crypto_pwhash_str_verify_async(
    str.buffer,
    str.byteOffset,
    str.byteLength,

    passwd.buffer,
    passwd.byteOffset,
    passwd.byteLength,

    done
  )

  return promise
}

exports.crypto_pwhash_str_needs_rehash = function (str, opslimit, memlimit) {
  assert(ArrayBuffer.isView(str), 'str must be a typed array')
  assert(
    str.byteLength === binding.crypto_pwhash_STRBYTES,
    "str must be 'crypto_pwhash_STRBYTES' bytes"
  )
  assert(
    opslimit >= binding.crypto_pwhash_OPSLIMIT_MIN,
    "opslimit must be at least 'crypto_pwhash_OPSLIMIT_MIN'"
  )
  assert(
    opslimit <= binding.crypto_pwhash_OPSLIMIT_MAX,
    "opslimit must be at most 'crypto_pwhash_OPSLIMIT_MAX'"
  )
  assert(
    memlimit >= binding.crypto_pwhash_MEMLIMIT_MIN,
    "memlimit must be at least 'crypto_pwhash_MEMLIMIT_MIN'"
  )
  assert(
    memlimit <= binding.crypto_pwhash_MEMLIMIT_MAX,
    "memlimit must be at most 'crypto_pwhash_MEMLIMIT_MAX'"
  )

  return binding.crypto_pwhash_str_needs_rehash(str, opslimit, memlimit)
}

exports.crypto_pwhash_scryptsalsa208sha256 = function (out, passwd, salt, opslimit, memlimit) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(ArrayBuffer.isView(salt), 'salt must be a typed array')
  assert(
    out.byteLength >= binding.crypto_pwhash_scryptsalsa208sha256_BYTES_MIN,
    "out must be at least 'crypto_pwhash_scryptsalsa208sha256_BYTES_MIN' bytes"
  )
  assert(
    out.byteLength <= binding.crypto_pwhash_scryptsalsa208sha256_BYTES_MAX,
    "out must be at most 'crypto_pwhash_scryptsalsa208sha256_BYTES_MAX' bytes"
  )
  assert(
    salt.byteLength === binding.crypto_pwhash_scryptsalsa208sha256_SALTBYTES,
    "salt must be 'crypto_pwhash_scryptsalsa208sha256_SALTBYTES' bytes"
  )
  assert(
    opslimit >= binding.crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MIN,
    "opslimit must be at least 'crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MIN'"
  )
  assert(
    opslimit <= binding.crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MAX,
    "opslimit must be at most 'crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MAX'"
  )
  assert(
    memlimit >= binding.crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MIN,
    "memlimit must be at least 'crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MIN'"
  )
  assert(
    memlimit <= binding.crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MAX,
    "memlimit must be at most 'crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MAX'"
  )

  const res = binding.crypto_pwhash_scryptsalsa208sha256(out, passwd, salt, opslimit, memlimit)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_pwhash_scryptsalsa208sha256_async = function (
  out,
  passwd,
  salt,
  opslimit,
  memlimit,
  callback = undefined
) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(ArrayBuffer.isView(salt), 'salt must be a typed array')
  assert(
    out.byteLength >= binding.crypto_pwhash_scryptsalsa208sha256_BYTES_MIN,
    "out must be at least 'crypto_pwhash_scryptsalsa208sha256_BYTES_MIN' bytes"
  )
  assert(
    out.byteLength <= binding.crypto_pwhash_scryptsalsa208sha256_BYTES_MAX,
    "out must be at most 'crypto_pwhash_scryptsalsa208sha256_BYTES_MAX' bytes"
  )
  assert(passwd.byteLength > 0, 'passwd must not be empty')
  assert(
    salt.byteLength === binding.crypto_pwhash_scryptsalsa208sha256_SALTBYTES,
    "salt must be 'crypto_pwhash_scryptsalsa208sha256_SALTBYTES' bytes"
  )
  assert(
    opslimit >= binding.crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MIN,
    "opslimit must be at least 'crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MIN'"
  )
  assert(
    opslimit <= binding.crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MAX,
    "opslimit must be at most 'crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MAX'"
  )
  assert(
    memlimit >= binding.crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MIN,
    "memlimit must be at least 'crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MIN'"
  )
  assert(
    memlimit <= binding.crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MAX,
    "memlimit must be at most 'crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MAX'"
  )

  const [done, promise] = checkStatus(callback)

  binding.crypto_pwhash_scryptsalsa208sha256_async(
    out.buffer,
    out.byteOffset,
    out.byteLength,

    passwd.buffer,
    passwd.byteOffset,
    passwd.byteLength,

    salt.buffer,
    salt.byteOffset,
    salt.byteLength,

    opslimit,
    memlimit,

    done
  )

  return promise
}

exports.crypto_pwhash_scryptsalsa208sha256_str_async = function (
  out,
  passwd,
  opslimit,
  memlimit,
  callback = undefined
) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(
    out.byteLength === binding.crypto_pwhash_scryptsalsa208sha256_STRBYTES,
    "out must be 'crypto_pwhash_scryptsalsa208sha256_STRBYTES' bytes"
  )
  assert(passwd.byteLength > 0, 'passwd must not be empty')
  assert(
    opslimit >= binding.crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MIN,
    "opslimit must be at least 'crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MIN'"
  )
  assert(
    opslimit <= binding.crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MAX,
    "opslimit must be at most 'crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MAX'"
  )
  assert(
    memlimit >= binding.crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MIN,
    "memlimit must be at least 'crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MIN'"
  )
  assert(
    memlimit <= binding.crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MAX,
    "memlimit must be at most 'crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MAX'"
  )

  const [done, promise] = checkStatus(callback)

  binding.crypto_pwhash_scryptsalsa208sha256_str_async(
    out.buffer,
    out.byteOffset,
    out.byteLength,

    passwd.buffer,
    passwd.byteOffset,
    passwd.byteLength,

    opslimit,
    memlimit,

    done
  )

  return promise
}

exports.crypto_pwhash_scryptsalsa208sha256_str = function (out, passwd, opslimit, memlimit) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(
    out.byteLength === binding.crypto_pwhash_scryptsalsa208sha256_STRBYTES,
    "out must be 'crypto_pwhash_scryptsalsa208sha256_STRBYTES' bytes"
  )
  assert(passwd.byteLength > 0, 'passwd must not be empty')
  assert(
    opslimit >= binding.crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MIN,
    "opslimit must be at least 'crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MIN'"
  )
  assert(
    opslimit <= binding.crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MAX,
    "opslimit must be at most 'crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MAX'"
  )
  assert(
    memlimit >= binding.crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MIN,
    "memlimit must be at least 'crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MIN'"
  )
  assert(
    memlimit <= binding.crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MAX,
    "memlimit must be at most 'crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MAX'"
  )

  const res = binding.crypto_pwhash_scryptsalsa208sha256_str(out, passwd, opslimit, memlimit)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_pwhash_scryptsalsa208sha256_str_verify_async = function (
  str,
  passwd,
  callback = undefined
) {
  assert(ArrayBuffer.isView(str), 'str must be a typed array')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(
    str.byteLength === binding.crypto_pwhash_scryptsalsa208sha256_STRBYTES,
    "str must be 'crypto_pwhash_scryptsalsa208sha256_STRBYTES' bytes"
  )
  assert(passwd.byteLength > 0, 'passwd must not be empty')

  const [done, promise] = checkStatus(callback, true)

  binding.crypto_pwhash_scryptsalsa208sha256_str_verify_async(
    str.buffer,
    str.byteOffset,
    str.byteLength,

    passwd.buffer,
    passwd.byteOffset,
    passwd.byteLength,

    done
  )

  return promise
}

exports.crypto_pwhash_scryptsalsa208sha256_str_verify = function (str, passwd) {
  assert(ArrayBuffer.isView(str), 'str must be a typed array')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(
    str.byteLength === binding.crypto_pwhash_scryptsalsa208sha256_STRBYTES,
    "str must be 'crypto_pwhash_scryptsalsa208sha256_STRBYTES' bytes"
  )
  assert(passwd.byteLength > 0, 'passwd must not be empty')

  return binding.crypto_pwhash_scryptsalsa208sha256_str_verify(str, passwd)
}

exports.crypto_pwhash_scryptsalsa208sha256_str_needs_rehash = function (str, opslimit, memlimit) {
  assert(ArrayBuffer.isView(str), 'str must be a typed array')
  assert(
    str.byteLength === binding.crypto_pwhash_scryptsalsa208sha256_STRBYTES,
    "str must be 'crypto_pwhash_scryptsalsa208sha256_STRBYTES' bytes"
  )
  assert(
    opslimit >= binding.crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MIN,
    "opslimit must be at least 'crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MIN'"
  )
  assert(
    opslimit <= binding.crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MAX,
    "opslimit must be at most 'crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MAX'"
  )
  assert(
    memlimit >= binding.crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MIN,
    "memlimit must be at least 'crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MIN'"
  )
  assert(
    memlimit <= binding.crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MAX,
    "memlimit must be at most 'crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MAX'"
  )

  return binding.crypto_pwhash_scryptsalsa208sha256_str_needs_rehash(str, opslimit, memlimit)
}

// crypto_kx

exports.crypto_kx_keypair = function (pk, sk) {
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(
    pk.byteLength === binding.crypto_kx_PUBLICKEYBYTES,
    "pk must be 'crypto_kx_PUBLICKEYBYTES' bytes"
  )
  assert(
    sk.byteLength === binding.crypto_kx_SECRETKEYBYTES,
    "sk must be 'crypto_kx_SECRETKEYBYTES' bytes"
  )

  const res = binding.crypto_kx_keypair(pk, sk)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_kx_seed_keypair = function (pk, sk, seed) {
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(ArrayBuffer.isView(seed), 'seed must be a typed array')
  assert(
    pk.byteLength === binding.crypto_kx_PUBLICKEYBYTES,
    "pk must be 'crypto_kx_PUBLICKEYBYTES' bytes"
  )
  assert(
    sk.byteLength === binding.crypto_kx_SECRETKEYBYTES,
    "sk must be 'crypto_kx_SECRETKEYBYTES' bytes"
  )
  assert(
    seed.byteLength === binding.crypto_kx_SEEDBYTES,
    "seed must be 'crypto_kx_SEEDBYTES' bytes"
  )

  const res = binding.crypto_kx_seed_keypair(pk, sk, seed)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_kx_client_session_keys = function (rx, tx, clientPk, clientSk, serverPk) {
  if (!rx) rx = undefined
  if (!tx) tx = undefined

  assert(rx || tx, 'at least one session key must be specified')

  if (rx) {
    assert(ArrayBuffer.isView(rx), 'rx must be a typed array')
    assert(
      rx.byteLength === binding.crypto_kx_SESSIONKEYBYTES,
      "rx must be 'crypto_kx_SESSIONKEYBYTES' bytes"
    )
  }

  if (tx) {
    assert(ArrayBuffer.isView(tx), 'tx must be a typed array')
    assert(
      tx.byteLength === binding.crypto_kx_SESSIONKEYBYTES,
      "tx must be 'crypto_kx_SESSIONKEYBYTES' bytes"
    )
  }

  assert(ArrayBuffer.isView(clientPk), 'clientPk must be a typed array')
  assert(ArrayBuffer.isView(clientSk), 'clientSk must be a typed array')
  assert(ArrayBuffer.isView(serverPk), 'serverPk must be a typed array')
  assert(
    clientPk.byteLength === binding.crypto_kx_PUBLICKEYBYTES,
    "clientPk must be 'crypto_kx_PUBLICKEYBYTES' bytes"
  )
  assert(
    clientSk.byteLength === binding.crypto_kx_SECRETKEYBYTES,
    "clientSk must be 'crypto_kx_SECRETKEYBYTES' bytes"
  )
  assert(
    serverPk.byteLength === binding.crypto_kx_PUBLICKEYBYTES,
    "serverPk must be 'crypto_kx_PUBLICKEYBYTES' bytes"
  )

  const res = binding.crypto_kx_client_session_keys(rx, tx, clientPk, clientSk, serverPk)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_kx_server_session_keys = function (rx, tx, serverPk, serverSk, clientPk) {
  if (!rx) rx = undefined
  if (!tx) tx = undefined

  assert(rx || tx, 'at least one session key must be specified')

  if (rx) {
    assert(ArrayBuffer.isView(rx), 'rx must be a typed array')
    assert(
      rx.byteLength === binding.crypto_kx_SESSIONKEYBYTES,
      "rx must be 'crypto_kx_SESSIONKEYBYTES' bytes"
    )
  }

  if (tx) {
    assert(ArrayBuffer.isView(tx), 'tx must be a typed array')
    assert(
      tx.byteLength === binding.crypto_kx_SESSIONKEYBYTES,
      "tx must be 'crypto_kx_SESSIONKEYBYTES' bytes"
    )
  }

  assert(ArrayBuffer.isView(serverPk), 'serverPk must be a typed array')
  assert(ArrayBuffer.isView(serverSk), 'serverSk must be a typed array')
  assert(ArrayBuffer.isView(clientPk), 'clientPk must be a typed array')
  assert(
    serverPk.byteLength === binding.crypto_kx_PUBLICKEYBYTES,
    "serverPk must be 'crypto_kx_PUBLICKEYBYTES' bytes"
  )
  assert(
    serverSk.byteLength === binding.crypto_kx_SECRETKEYBYTES,
    "serverSk must be 'crypto_kx_SECRETKEYBYTES' bytes"
  )
  assert(
    clientPk.byteLength === binding.crypto_kx_PUBLICKEYBYTES,
    "clientPk must be 'crypto_kx_PUBLICKEYBYTES' bytes"
  )

  const res = binding.crypto_kx_server_session_keys(rx, tx, serverPk, serverSk, clientPk)

  if (res !== 0) throw new Error('status: ' + res)
}

// crypto_scalarmult

exports.crypto_scalarmult_base = function (q, n) {
  assert(ArrayBuffer.isView(q), 'q must be a typed array')
  assert(
    q.byteLength === binding.crypto_scalarmult_BYTES,
    "q must be 'crypto_scalarmult_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.crypto_scalarmult_SCALARBYTES,
    "n must be 'crypto_scalarmult_SCALARBYTES' bytes"
  )

  const res = binding.crypto_scalarmult_base(q, n)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_scalarmult = function (q, n, p) {
  assert(ArrayBuffer.isView(q), 'q must be a typed array')
  assert(
    q.byteLength === binding.crypto_scalarmult_BYTES,
    "q must be 'crypto_scalarmult_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.crypto_scalarmult_SCALARBYTES,
    "n must be 'crypto_scalarmult_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(p), 'p must be a typed array')
  assert(
    p.byteLength === binding.crypto_scalarmult_BYTES,
    "p must be 'crypto_scalarmult_BYTES' bytes"
  )

  const res = binding.crypto_scalarmult(q, n, p)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_scalarmult_ed25519_base = function (q, n) {
  assert(ArrayBuffer.isView(q), 'q must be a typed array')
  assert(
    q.byteLength === binding.crypto_scalarmult_ed25519_BYTES,
    "q must be 'crypto_scalarmult_ed25519_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.crypto_scalarmult_ed25519_SCALARBYTES,
    "n must be 'crypto_scalarmult_ed25519_SCALARBYTES' bytes"
  )

  const res = binding.crypto_scalarmult_ed25519_base(q, n)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_scalarmult_ed25519 = function (q, n, p) {
  assert(ArrayBuffer.isView(q), 'q must be a typed array')
  assert(
    q.byteLength === binding.crypto_scalarmult_ed25519_BYTES,
    "q must be 'crypto_scalarmult_ed25519_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.crypto_scalarmult_ed25519_SCALARBYTES,
    "n must be 'crypto_scalarmult_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(p), 'p must be a typed array')
  assert(
    p.byteLength === binding.crypto_scalarmult_ed25519_BYTES,
    "p must be 'crypto_scalarmult_ed25519_BYTES' bytes"
  )

  const res = binding.crypto_scalarmult_ed25519(q, n, p)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_core_ed25519_is_valid_point = function (p) {
  assert(ArrayBuffer.isView(p), 'p must be a typed array')
  assert(
    p.byteLength === binding.crypto_core_ed25519_BYTES,
    "p must be 'crypto_core_ed25519_BYTES' bytes"
  )

  return binding.crypto_core_ed25519_is_valid_point(p)
}

exports.crypto_core_ed25519_from_uniform = function (p, r) {
  assert(ArrayBuffer.isView(p), 'p must be a typed array')
  assert(
    p.byteLength === binding.crypto_core_ed25519_BYTES,
    "p must be 'crypto_core_ed25519_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(r), 'r must be a typed array')
  assert(
    r.byteLength === binding.crypto_core_ed25519_UNIFORMBYTES,
    "r must be 'crypto_core_ed25519_UNIFORMBYTES' bytes"
  )

  const res = binding.crypto_core_ed25519_from_uniform(p, r)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_scalarmult_ed25519_base_noclamp = function (q, n) {
  assert(ArrayBuffer.isView(q), 'q must be a typed array')
  assert(
    q.byteLength === binding.crypto_scalarmult_ed25519_BYTES,
    "q must be 'crypto_scalarmult_ed25519_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.crypto_scalarmult_ed25519_SCALARBYTES,
    "n must be 'crypto_scalarmult_ed25519_SCALARBYTES' bytes"
  )

  const res = binding.crypto_scalarmult_ed25519_base_noclamp(q, n)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_scalarmult_ed25519_noclamp = function (q, n, p) {
  assert(ArrayBuffer.isView(q), 'q must be a typed array')
  assert(
    q.byteLength === binding.crypto_scalarmult_ed25519_BYTES,
    "q must be 'crypto_scalarmult_ed25519_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.crypto_scalarmult_ed25519_SCALARBYTES,
    "n must be 'crypto_scalarmult_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(p), 'p must be a typed array')
  assert(
    p.byteLength === binding.crypto_scalarmult_ed25519_BYTES,
    "p must be 'crypto_scalarmult_ed25519_BYTES' bytes"
  )

  const res = binding.crypto_scalarmult_ed25519_noclamp(q, n, p)

  if (res !== 0) throw new Error('status: ' + res)
}

// crypto_core

exports.crypto_core_ed25519_add = function (r, p, q) {
  assert(ArrayBuffer.isView(r), 'r must be a typed array')
  assert(
    r.byteLength === binding.crypto_core_ed25519_BYTES,
    "r must be 'crypto_core_ed25519_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(p), 'p must be a typed array')
  assert(
    p.byteLength === binding.crypto_core_ed25519_BYTES,
    "p must be 'crypto_core_ed25519_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(q), 'q must be a typed array')
  assert(
    q.byteLength === binding.crypto_core_ed25519_BYTES,
    "q must be 'crypto_core_ed25519_BYTES' bytes"
  )

  const res = binding.crypto_core_ed25519_add(r, p, q)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_core_ed25519_sub = function (r, p, q) {
  assert(ArrayBuffer.isView(r), 'r must be a typed array')
  assert(
    r.byteLength === binding.crypto_core_ed25519_BYTES,
    "r must be 'crypto_core_ed25519_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(p), 'p must be a typed array')
  assert(
    p.byteLength === binding.crypto_core_ed25519_BYTES,
    "p must be 'crypto_core_ed25519_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(q), 'q must be a typed array')
  assert(
    q.byteLength === binding.crypto_core_ed25519_BYTES,
    "q must be 'crypto_core_ed25519_BYTES' bytes"
  )

  const res = binding.crypto_core_ed25519_sub(r, p, q)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_core_ed25519_scalar_random = function (r) {
  assert(ArrayBuffer.isView(r), 'r must be a typed array')
  assert(
    r.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "r must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )

  binding.crypto_core_ed25519_scalar_random(r)
}

exports.crypto_core_ed25519_scalar_reduce = function (r, s) {
  assert(ArrayBuffer.isView(r), 'r must be a typed array')
  assert(
    r.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "r must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(s), 's must be a typed array')
  assert(
    s.byteLength === binding.crypto_core_ed25519_NONREDUCEDSCALARBYTES,
    "s must be 'crypto_core_ed25519_NONREDUCEDSCALARBYTES' bytes"
  )

  binding.crypto_core_ed25519_scalar_reduce(r, s)
}

exports.crypto_core_ed25519_scalar_invert = function (recip, s) {
  assert(ArrayBuffer.isView(recip), 'recip must be a typed array')
  assert(
    recip.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "recip must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(s), 's must be a typed array')
  assert(
    s.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "s must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )

  binding.crypto_core_ed25519_scalar_invert(recip, s)
}

exports.crypto_core_ed25519_scalar_negate = function (neg, s) {
  assert(ArrayBuffer.isView(neg), 'neg must be a typed array')
  assert(
    neg.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "neg must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(s), 's must be a typed array')
  assert(
    s.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "s must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )

  binding.crypto_core_ed25519_scalar_negate(neg, s)
}

exports.crypto_core_ed25519_scalar_complement = function (comp, s) {
  assert(ArrayBuffer.isView(comp), 'comp must be a typed array')
  assert(
    comp.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "comp must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(s), 's must be a typed array')
  assert(
    s.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "s must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )

  binding.crypto_core_ed25519_scalar_complement(comp, s)
}

exports.crypto_core_ed25519_scalar_add = function (z, x, y) {
  assert(ArrayBuffer.isView(z), 'z must be a typed array')
  assert(
    z.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "z must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(x), 'x must be a typed array')
  assert(
    x.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "x must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(y), 'y must be a typed array')
  assert(
    y.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "y must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )

  binding.crypto_core_ed25519_scalar_add(z, x, y)
}

exports.crypto_core_ed25519_scalar_sub = function (z, x, y) {
  assert(ArrayBuffer.isView(z), 'z must be a typed array')
  assert(
    z.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "z must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(x), 'x must be a typed array')
  assert(
    x.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "x must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(y), 'y must be a typed array')
  assert(
    y.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "y must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )

  binding.crypto_core_ed25519_scalar_sub(z, x, y)
}

// crypto_shorthash

exports.crypto_shorthash = function (out, input, k) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(
    out.byteLength === binding.crypto_shorthash_BYTES,
    "out must be 'crypto_shorthash_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_shorthash_KEYBYTES,
    "k must be 'crypto_shorthash_KEYBYTES' bytes"
  )

  const res = binding.crypto_shorthash(out, input, k)

  if (res !== 0) throw new Error('status: ' + res)
}

// crypto_kdf

exports.crypto_kdf_keygen = function (key) {
  assert(ArrayBuffer.isView(key), 'key must be a typed array')
  assert(key.byteLength === binding.crypto_kdf_KEYBYTES, "key must be 'crypto_kdf_KEYBYTES' bytes")

  binding.crypto_kdf_keygen(key)
}

exports.crypto_kdf_derive_from_key = function (subkey, subkeyId, ctx, key) {
  assert(ArrayBuffer.isView(subkey), 'subkey must be a typed array')
  assert(
    subkey.byteLength >= binding.crypto_kdf_BYTES_MIN,
    "subkey must be at least 'crypto_kdf_BYTES_MIN' bytes"
  )
  assert(
    subkey.byteLength <= binding.crypto_kdf_BYTES_MAX,
    "subkey must be at most 'crypto_kdf_BYTES_MAX' bytes"
  )
  assert(ArrayBuffer.isView(ctx), 'ctx must be a typed array')
  assert(
    ctx.byteLength === binding.crypto_kdf_CONTEXTBYTES,
    "ctx must be 'crypto_kdf_CONTEXTBYTES' bytes"
  )
  assert(ArrayBuffer.isView(key), 'key must be a typed array')
  assert(key.byteLength === binding.crypto_kdf_KEYBYTES, "key must be 'crypto_kdf_KEYBYTES' bytes")

  const res = binding.crypto_kdf_derive_from_key(subkey, subkeyId, ctx, key)

  if (res !== 0) throw new Error('status: ' + res)
}

// crypto_hash

exports.crypto_hash = function (out, input) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(out.byteLength === binding.crypto_hash_BYTES, "out must be 'crypto_hash_BYTES' bytes")

  const res = binding.crypto_hash(out, input)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_hash_sha256 = function (out, input) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(
    out.byteLength === binding.crypto_hash_sha256_BYTES,
    "out must be 'crypto_hash_sha256_BYTES' bytes"
  )

  const res = binding.crypto_hash_sha256(out, input)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_hash_sha256_init = function (state) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_hash_sha256_STATEBYTES,
    "state must be 'crypto_hash_sha256_STATEBYTES' bytes"
  )

  const res = binding.crypto_hash_sha256_init(state)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_hash_sha256_update = function (state, input) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(
    state.byteLength === binding.crypto_hash_sha256_STATEBYTES,
    "state must be 'crypto_hash_sha256_STATEBYTES' bytes"
  )

  const res = binding.crypto_hash_sha256_update(state, input)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_hash_sha256_final = function (state, out) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_hash_sha256_STATEBYTES,
    "state must be 'crypto_hash_sha256_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(
    out.byteLength === binding.crypto_hash_sha256_BYTES,
    "out must be 'crypto_hash_sha256_BYTES' bytes"
  )

  const res = binding.crypto_hash_sha256_final(state, out)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_hash_sha512 = function (out, input) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(
    out.byteLength === binding.crypto_hash_sha512_BYTES,
    "out must be 'crypto_hash_sha512_BYTES' bytes"
  )

  const res = binding.crypto_hash_sha512(out, input)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_hash_sha512_init = function (state) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_hash_sha512_STATEBYTES,
    "state must be 'crypto_hash_sha512_STATEBYTES' bytes"
  )

  const res = binding.crypto_hash_sha512_init(state)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_hash_sha512_update = function (state, input) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(
    state.byteLength === binding.crypto_hash_sha512_STATEBYTES,
    "state must be 'crypto_hash_sha512_STATEBYTES' bytes"
  )

  const res = binding.crypto_hash_sha512_update(state, input)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_hash_sha512_final = function (state, out) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_hash_sha512_STATEBYTES,
    "state must be 'crypto_hash_sha512_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(
    out.byteLength === binding.crypto_hash_sha512_BYTES,
    "out must be 'crypto_hash_sha512_BYTES' bytes"
  )

  const res = binding.crypto_hash_sha512_final(state, out)

  if (res !== 0) throw new Error('status: ' + res)
}

// crypto_aead

exports.crypto_aead_xchacha20poly1305_ietf_keygen = function (k) {
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_aead_xchacha20poly1305_ietf_KEYBYTES,
    "k must be 'crypto_aead_xchacha20poly1305_ietf_KEYBYTES' bytes"
  )

  binding.crypto_aead_xchacha20poly1305_ietf_keygen(k)
}

exports.crypto_aead_xchacha20poly1305_ietf_encrypt = function (c, m, ad, nsec, npub, k) {
  if (!ad) ad = undefined

  assert(nsec === null, 'nsec must always be set to null')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(
    c.byteLength === m.byteLength + binding.crypto_aead_xchacha20poly1305_ietf_ABYTES,
    "c must be 'm.byteLength + crypto_aead_xchacha20poly1305_ietf_ABYTES' bytes"
  )
  assert(c.byteLength <= 0xffffffff, 'c.byteLength must be a 32bit integer')
  assert(ArrayBuffer.isView(npub), 'npub must be a typed array')
  assert(
    npub.byteLength === binding.crypto_aead_xchacha20poly1305_ietf_NPUBBYTES,
    "npub must be 'crypto_aead_xchacha20poly1305_ietf_NPUBBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_aead_xchacha20poly1305_ietf_KEYBYTES,
    "k must be 'crypto_aead_xchacha20poly1305_ietf_KEYBYTES' bytes"
  )

  const res = binding.crypto_aead_xchacha20poly1305_ietf_encrypt(c, m, ad, npub, k)

  if (res < 0) throw new Error('could not encrypt data')

  return res
}

exports.crypto_aead_xchacha20poly1305_ietf_decrypt = function (m, nsec, c, ad, npub, k) {
  if (!ad) ad = undefined

  assert(nsec === null, 'nsec must always be set to null')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(
    m.byteLength === c.byteLength - binding.crypto_aead_xchacha20poly1305_ietf_ABYTES,
    "m must be 'c.byteLength - crypto_aead_xchacha20poly1305_ietf_ABYTES' bytes"
  )
  assert(m.byteLength <= 0xffffffff, 'm.byteLength must be a 32bit integer')
  assert(ArrayBuffer.isView(npub), 'npub must be a typed array')
  assert(
    npub.byteLength === binding.crypto_aead_xchacha20poly1305_ietf_NPUBBYTES,
    "npub must be 'crypto_aead_xchacha20poly1305_ietf_NPUBBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_aead_xchacha20poly1305_ietf_KEYBYTES,
    "k must be 'crypto_aead_xchacha20poly1305_ietf_KEYBYTES' bytes"
  )

  const res = binding.crypto_aead_xchacha20poly1305_ietf_decrypt(m, c, ad, npub, k)

  if (res < 0) throw new Error('could not verify data')

  return res
}

exports.crypto_aead_xchacha20poly1305_ietf_encrypt_detached = function (
  c,
  mac,
  m,
  ad,
  nsec,
  npub,
  k
) {
  if (!ad) ad = undefined

  assert(nsec === null, 'nsec must always be set to null')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(ArrayBuffer.isView(mac), 'mac must be a typed array')
  assert(
    mac.byteLength === binding.crypto_aead_xchacha20poly1305_ietf_ABYTES,
    "mac must be 'crypto_aead_xchacha20poly1305_ietf_ABYTES' bytes"
  )
  assert(ArrayBuffer.isView(npub), 'npub must be a typed array')
  assert(
    npub.byteLength === binding.crypto_aead_xchacha20poly1305_ietf_NPUBBYTES,
    "npub must be 'crypto_aead_xchacha20poly1305_ietf_NPUBBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_aead_xchacha20poly1305_ietf_KEYBYTES,
    "k must be 'crypto_aead_xchacha20poly1305_ietf_KEYBYTES' bytes"
  )

  const res = binding.crypto_aead_xchacha20poly1305_ietf_encrypt_detached(c, mac, m, ad, npub, k)

  if (res < 0) throw new Error('could not encrypt data')

  return res
}

exports.crypto_aead_xchacha20poly1305_ietf_decrypt_detached = function (
  m,
  nsec,
  c,
  mac,
  ad,
  npub,
  k
) {
  if (!ad) ad = undefined

  assert(nsec === null, 'nsec must always be set to null')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(m.byteLength === c.byteLength, "m must be 'c.byteLength' bytes")
  assert(ArrayBuffer.isView(mac), 'mac must be a typed array')
  assert(
    mac.byteLength === binding.crypto_aead_xchacha20poly1305_ietf_ABYTES,
    "mac must be 'crypto_aead_xchacha20poly1305_ietf_ABYTES' bytes"
  )
  assert(ArrayBuffer.isView(npub), 'npub must be a typed array')
  assert(
    npub.byteLength === binding.crypto_aead_xchacha20poly1305_ietf_NPUBBYTES,
    "npub must be 'crypto_aead_xchacha20poly1305_ietf_NPUBBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_aead_xchacha20poly1305_ietf_KEYBYTES,
    "k must be 'crypto_aead_xchacha20poly1305_ietf_KEYBYTES' bytes"
  )

  const res = binding.crypto_aead_xchacha20poly1305_ietf_decrypt_detached(m, c, mac, ad, npub, k)

  if (res !== 0) throw new Error('could not verify data')
}

exports.crypto_aead_chacha20poly1305_ietf_keygen = function (k) {
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_aead_chacha20poly1305_ietf_KEYBYTES,
    "k must be 'crypto_aead_chacha20poly1305_ietf_KEYBYTES' bytes"
  )

  binding.crypto_aead_chacha20poly1305_ietf_keygen(k)
}

exports.crypto_aead_chacha20poly1305_ietf_encrypt = function (c, m, ad, nsec, npub, k) {
  if (!ad) ad = undefined

  assert(nsec === null, 'nsec must always be set to null')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(
    c.byteLength === m.byteLength + binding.crypto_aead_chacha20poly1305_ietf_ABYTES,
    "c must be 'm.byteLength + crypto_aead_chacha20poly1305_ietf_ABYTES' bytes"
  )
  assert(c.byteLength <= 0xffffffff, 'c.byteLength must be a 32bit integer')
  assert(ArrayBuffer.isView(npub), 'npub must be a typed array')
  assert(
    npub.byteLength === binding.crypto_aead_chacha20poly1305_ietf_NPUBBYTES,
    "npub must be 'crypto_aead_chacha20poly1305_ietf_NPUBBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_aead_chacha20poly1305_ietf_KEYBYTES,
    "k must be 'crypto_aead_chacha20poly1305_ietf_KEYBYTES' bytes"
  )

  const res = binding.crypto_aead_chacha20poly1305_ietf_encrypt(c, m, ad, npub, k)

  if (res < 0) throw new Error('could not encrypt data')

  return res
}

exports.crypto_aead_chacha20poly1305_ietf_decrypt = function (m, nsec, c, ad, npub, k) {
  if (!ad) ad = undefined

  assert(nsec === null, 'nsec must always be set to null')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(
    m.byteLength === c.byteLength - binding.crypto_aead_chacha20poly1305_ietf_ABYTES,
    "m must be 'c.byteLength - crypto_aead_chacha20poly1305_ietf_ABYTES' bytes"
  )
  assert(m.byteLength <= 0xffffffff, 'm.byteLength must be a 32bit integer')
  assert(ArrayBuffer.isView(npub), 'npub must be a typed array')
  assert(
    npub.byteLength === binding.crypto_aead_chacha20poly1305_ietf_NPUBBYTES,
    "npub must be 'crypto_aead_chacha20poly1305_ietf_NPUBBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_aead_chacha20poly1305_ietf_KEYBYTES,
    "k must be 'crypto_aead_chacha20poly1305_ietf_KEYBYTES' bytes"
  )

  const res = binding.crypto_aead_chacha20poly1305_ietf_decrypt(m, c, ad, npub, k)

  if (res < 0) throw new Error('could not verify data')

  return res
}

exports.crypto_aead_chacha20poly1305_ietf_encrypt_detached = function (
  c,
  mac,
  m,
  ad,
  nsec,
  npub,
  k
) {
  if (!ad) ad = undefined

  assert(nsec === null, 'nsec must always be set to null')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(ArrayBuffer.isView(mac), 'mac must be a typed array')
  assert(
    mac.byteLength === binding.crypto_aead_chacha20poly1305_ietf_ABYTES,
    "mac must be 'crypto_aead_chacha20poly1305_ietf_ABYTES' bytes"
  )
  assert(ArrayBuffer.isView(npub), 'npub must be a typed array')
  assert(
    npub.byteLength === binding.crypto_aead_chacha20poly1305_ietf_NPUBBYTES,
    "npub must be 'crypto_aead_chacha20poly1305_ietf_NPUBBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_aead_chacha20poly1305_ietf_KEYBYTES,
    "k must be 'crypto_aead_chacha20poly1305_ietf_KEYBYTES' bytes"
  )

  const res = binding.crypto_aead_chacha20poly1305_ietf_encrypt_detached(c, mac, m, ad, npub, k)

  if (res < 0) throw new Error('could not encrypt data')

  return res
}

exports.crypto_aead_chacha20poly1305_ietf_decrypt_detached = function (
  m,
  nsec,
  c,
  mac,
  ad,
  npub,
  k
) {
  if (!ad) ad = undefined

  assert(nsec === null, 'nsec must always be set to null')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(m.byteLength === c.byteLength, "m must be 'c.byteLength' bytes")
  assert(ArrayBuffer.isView(mac), 'mac must be a typed array')
  assert(
    mac.byteLength === binding.crypto_aead_chacha20poly1305_ietf_ABYTES,
    "mac must be 'crypto_aead_chacha20poly1305_ietf_ABYTES' bytes"
  )
  assert(ArrayBuffer.isView(npub), 'npub must be a typed array')
  assert(
    npub.byteLength === binding.crypto_aead_chacha20poly1305_ietf_NPUBBYTES,
    "npub must be 'crypto_aead_chacha20poly1305_ietf_NPUBBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_aead_chacha20poly1305_ietf_KEYBYTES,
    "k must be 'crypto_aead_chacha20poly1305_ietf_KEYBYTES' bytes"
  )

  const res = binding.crypto_aead_chacha20poly1305_ietf_decrypt_detached(m, c, mac, ad, npub, k)

  if (res !== 0) throw new Error('could not verify data')
}

// crypto_stream

exports.crypto_stream_xor_wrap_init = function (state, n, k) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.sn_crypto_stream_xor_STATEBYTES,
    "state must be 'sn_crypto_stream_xor_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.crypto_stream_NONCEBYTES,
    "n must be 'crypto_stream_NONCEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_stream_KEYBYTES,
    "k must be 'crypto_stream_KEYBYTES' bytes"
  )

  binding.crypto_stream_xor_wrap_init(state, n, k)
}

exports.crypto_stream_xor_wrap_update = function (state, c, m) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.sn_crypto_stream_xor_STATEBYTES,
    "state must be 'sn_crypto_stream_xor_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")

  binding.crypto_stream_xor_wrap_update(state, c, m)
}

exports.crypto_stream_xor_wrap_final = function (state) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.sn_crypto_stream_xor_STATEBYTES,
    "state must be 'sn_crypto_stream_xor_STATEBYTES' bytes"
  )

  binding.crypto_stream_xor_wrap_final(state)
}

exports.crypto_stream_chacha20_xor_wrap_init = function (state, n, k) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_stream_chacha20_xor_STATEBYTES,
    "state must be 'crypto_stream_chacha20_xor_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.crypto_stream_chacha20_NONCEBYTES,
    "n must be 'crypto_stream_chacha20_NONCEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_stream_chacha20_KEYBYTES,
    "k must be 'crypto_stream_chacha20_KEYBYTES' bytes"
  )

  binding.crypto_stream_chacha20_xor_wrap_init(state, n, k)
}

exports.crypto_stream_chacha20_xor_wrap_update = function (state, c, m) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_stream_chacha20_xor_STATEBYTES,
    "state must be 'crypto_stream_chacha20_xor_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")

  binding.crypto_stream_chacha20_xor_wrap_update(state, c, m)
}

exports.crypto_stream_chacha20_xor_wrap_final = function (state) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_stream_chacha20_xor_STATEBYTES,
    "state must be 'crypto_stream_chacha20_xor_STATEBYTES' bytes"
  )

  binding.crypto_stream_chacha20_xor_wrap_final(state)
}

exports.crypto_stream_chacha20_ietf_xor_wrap_init = function (state, n, k) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_stream_chacha20_ietf_xor_STATEBYTES,
    "state must be 'crypto_stream_chacha20_ietf_xor_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.crypto_stream_chacha20_ietf_NONCEBYTES,
    "n must be 'crypto_stream_chacha20_ietf_NONCEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_stream_chacha20_ietf_KEYBYTES,
    "k must be 'crypto_stream_chacha20_ietf_KEYBYTES' bytes"
  )

  binding.crypto_stream_chacha20_ietf_xor_wrap_init(state, n, k)
}

exports.crypto_stream_chacha20_ietf_xor_wrap_update = function (state, c, m) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_stream_chacha20_ietf_xor_STATEBYTES,
    "state must be 'crypto_stream_chacha20_ietf_xor_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")

  binding.crypto_stream_chacha20_ietf_xor_wrap_update(state, c, m)
}

exports.crypto_stream_chacha20_ietf_xor_wrap_final = function (state) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_stream_chacha20_ietf_xor_STATEBYTES,
    "state must be 'crypto_stream_chacha20_ietf_xor_STATEBYTES' bytes"
  )

  binding.crypto_stream_chacha20_ietf_xor_wrap_final(state)
}

exports.crypto_stream_xchacha20_xor_wrap_init = function (state, n, k) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_stream_xchacha20_xor_STATEBYTES,
    "state must be 'crypto_stream_xchacha20_xor_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.crypto_stream_xchacha20_NONCEBYTES,
    "n must be 'crypto_stream_xchacha20_NONCEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_stream_xchacha20_KEYBYTES,
    "k must be 'crypto_stream_xchacha20_KEYBYTES' bytes"
  )

  binding.crypto_stream_xchacha20_xor_wrap_init(state, n, k)
}

exports.crypto_stream_xchacha20_xor_wrap_update = function (state, c, m) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_stream_xchacha20_xor_STATEBYTES,
    "state must be 'crypto_stream_xchacha20_xor_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")

  binding.crypto_stream_xchacha20_xor_wrap_update(state, c, m)
}

exports.crypto_stream_xchacha20_xor_wrap_final = function (state) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_stream_xchacha20_xor_STATEBYTES,
    "state must be 'crypto_stream_xchacha20_xor_STATEBYTES' bytes"
  )

  binding.crypto_stream_xchacha20_xor_wrap_final(state)
}

exports.crypto_stream_salsa20_xor_wrap_init = function (state, n, k) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_stream_salsa20_xor_STATEBYTES,
    "state must be 'crypto_stream_salsa20_xor_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.crypto_stream_salsa20_NONCEBYTES,
    "n must be 'crypto_stream_salsa20_NONCEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_stream_salsa20_KEYBYTES,
    "k must be 'crypto_stream_salsa20_KEYBYTES' bytes"
  )

  binding.crypto_stream_salsa20_xor_wrap_init(state, n, k)
}

exports.crypto_stream_salsa20_xor_wrap_update = function (state, c, m) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_stream_salsa20_xor_STATEBYTES,
    "state must be 'crypto_stream_salsa20_xor_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")

  binding.crypto_stream_salsa20_xor_wrap_update(state, c, m)
}

exports.crypto_stream_salsa20_xor_wrap_final = function (state) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_stream_salsa20_xor_STATEBYTES,
    "state must be 'crypto_stream_salsa20_xor_STATEBYTES' bytes"
  )

  binding.crypto_stream_salsa20_xor_wrap_final(state)
}

// experimental

exports.extension_tweak_ed25519_base = function (n, p, ns) {
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "n must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(p), 'p must be a typed array')
  assert(
    p.byteLength === binding.extension_tweak_ed25519_BYTES,
    "p must be 'extension_tweak_ed25519_BYTES' bytes"
  )

  binding.extension_tweak_ed25519_base(n, p, ns)
}

exports.extension_tweak_ed25519_sign_detached = function (sig, m, scalar, pk) {
  assert(ArrayBuffer.isView(sig), 'sig must be a typed array')
  assert(sig.byteLength === binding.crypto_sign_BYTES, "sig must be 'crypto_sign_BYTES' bytes")
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(scalar), 'scalar must be a typed array')
  assert(
    scalar.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "scalar must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )

  if (pk) {
    assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
    assert(
      pk.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
      "pk must be 'crypto_sign_PUBLICKEYBYTES' bytes"
    )
  }

  const res = binding.extension_tweak_ed25519_sign_detached(sig, m, scalar, pk)

  if (res !== 0) throw new Error('failed to compute signature')
}

exports.extension_tweak_ed25519_sk_to_scalar = function (n, sk) {
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "n must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(
    sk.byteLength === binding.crypto_sign_SECRETKEYBYTES,
    "sk must be 'crypto_sign_SECRETKEYBYTES' bytes"
  )

  binding.extension_tweak_ed25519_sk_to_scalar(n, sk)
}

exports.extension_tweak_ed25519_scalar = function (scalarOut, scalar, ns) {
  assert(ArrayBuffer.isView(scalarOut), 'scalarOut must be a typed array')
  assert(
    scalarOut.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "scalarOut must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(scalar), 'scalar must be a typed array')
  assert(
    scalar.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "scalar must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )

  binding.extension_tweak_ed25519_scalar(scalarOut, scalar, ns)
}

exports.extension_tweak_ed25519_pk = function (tpk, pk, ns) {
  assert(ArrayBuffer.isView(tpk), 'tpk must be a typed array')
  assert(
    tpk.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
    "tpk must be 'crypto_sign_PUBLICKEYBYTES' bytes"
  )
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(
    pk.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
    "pk must be 'crypto_sign_PUBLICKEYBYTES' bytes"
  )

  const res = binding.extension_tweak_ed25519_pk(tpk, pk, ns)

  if (res !== 0) throw new Error('failed to tweak public key')
}

exports.extension_tweak_ed25519_keypair = function (pk, scalarOut, scalarIn, ns) {
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(
    pk.byteLength === binding.extension_tweak_ed25519_BYTES,
    "pk must be 'extension_tweak_ed25519_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(scalarOut), 'scalarOut must be a typed array')
  assert(
    scalarOut.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "scalarOut must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(scalarIn), 'scalarIn must be a typed array')
  assert(
    scalarIn.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "scalarIn must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )

  binding.extension_tweak_ed25519_keypair(pk, scalarOut, scalarIn, ns)
}

exports.extension_tweak_ed25519_scalar_add = function (scalarOut, scalar, n) {
  assert(ArrayBuffer.isView(scalarOut), 'scalarOut must be a typed array')
  assert(
    scalarOut.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "scalarOut must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(scalar), 'scalar must be a typed array')
  assert(
    scalar.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "scalar must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "n must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )

  binding.extension_tweak_ed25519_scalar_add(scalarOut, scalar, n)
}

exports.extension_tweak_ed25519_pk_add = function (tpk, pk, p) {
  assert(ArrayBuffer.isView(tpk), 'tpk must be a typed array')
  assert(
    tpk.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
    "tpk must be 'crypto_sign_PUBLICKEYBYTES' bytes"
  )
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(
    pk.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
    "pk must be 'crypto_sign_PUBLICKEYBYTES' bytes"
  )
  assert(ArrayBuffer.isView(p), 'p must be a typed array')
  assert(
    p.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
    "p must be 'crypto_sign_PUBLICKEYBYTES' bytes"
  )

  const res = binding.extension_tweak_ed25519_pk_add(tpk, pk, p)

  if (res !== 0) throw new Error('failed to add tweak to public key')
}

exports.extension_tweak_ed25519_keypair_add = function (pk, scalarOut, scalarIn, tweak) {
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(
    pk.byteLength === binding.extension_tweak_ed25519_BYTES,
    "pk must be 'extension_tweak_ed25519_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(scalarOut), 'scalarOut must be a typed array')
  assert(
    scalarOut.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "scalarOut must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(scalarIn), 'scalarIn must be a typed array')
  assert(
    scalarIn.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "scalarIn must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(tweak), 'tweak must be a typed array')
  assert(
    tweak.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "tweak must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )

  const res = binding.extension_tweak_ed25519_keypair_add(pk, scalarOut, scalarIn, tweak)

  if (res !== 0) throw new Error('failed to add tweak to keypair')
}

exports.extension_pbkdf2_sha512_async = function (out, passwd, salt, iter, outlen, callback) {
  assert(
    iter >= binding.extension_pbkdf2_sha512_ITERATIONS_MIN,
    "iter must be at least 'extension_pbkdf2_sha512_ITERATIONS_MIN'"
  )
  assert(
    outlen <= binding.extension_pbkdf2_sha512_BYTES_MAX,
    "outlen must be at most 'extension_pbkdf2_sha512_BYTES_MAX'"
  )
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(out.byteLength >= outlen, "out must be at least 'outlen' bytes")
  assert(out.byteLength > 0, 'out must not be empty')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(passwd.byteLength > 0, 'passwd must not be empty')
  assert(ArrayBuffer.isView(salt), 'salt must be a typed array')
  assert(salt.byteLength > 0, 'salt must not be empty')

  const [done, promise] = checkStatus(callback)

  binding.extension_pbkdf2_sha512_async(
    out.buffer,
    out.byteOffset,
    out.byteLength,

    passwd.buffer,
    passwd.byteOffset,
    passwd.byteLength,

    salt.buffer,
    salt.byteOffset,
    salt.byteLength,

    iter,
    outlen,

    done
  )

  return promise
}

exports.extension_pbkdf2_sha512 = function (out, passwd, salt, iter, outlen) {
  assert(
    iter >= binding.extension_pbkdf2_sha512_ITERATIONS_MIN,
    "iter must be at least 'extension_pbkdf2_sha512_ITERATIONS_MIN'"
  )
  assert(
    outlen <= binding.extension_pbkdf2_sha512_BYTES_MAX,
    "outlen must be at most 'extension_pbkdf2_sha512_BYTES_MAX'"
  )
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(out.byteLength >= outlen, "out must be at least 'outlen' bytes")
  assert(out.byteLength > 0, 'out must not be empty')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(passwd.byteLength > 0, 'passwd must not be empty')
  assert(ArrayBuffer.isView(salt), 'salt must be a typed array')
  assert(salt.byteLength > 0, 'salt must not be empty')

  const res = binding.extension_pbkdf2_sha512(out, passwd, salt, iter, outlen)

  if (res !== 0) throw new Error('failed to add tweak to public key')
}

function checkStatus(callback, booleanResult = false) {
  let done, promise

  if (typeof callback === 'function') {
    done = function (status) {
      if (booleanResult) callback(null, status === 0)
      else if (status === 0) callback(null)
      else callback(new Error('status: ' + status))
    }
  } else {
    promise = new Promise(function (resolve, reject) {
      done = function (status) {
        if (booleanResult) resolve(status === 0)
        else if (status === 0) resolve()
        else reject(new Error('status: ' + status))
      }
    })
  }

  return [done, promise]
}
{
  "name": "sodium-native",
  "version": "5.1.0",
  "description": "Low level bindings for libsodium",
  "main": "index.js",
  "files": [
    "index.js",
    "binding.cc",
    "binding.js",
    "extensions",
    "prebuilds",
    "CMakeLists.txt"
  ],
  "imports": {
    "assert": {
      "bare": "bare-assert",
      "node": "assert"
    }
  },
  "addon": true,
  "dependencies": {
    "bare-assert": "^1.2.0",
    "require-addon": "^1.1.0",
    "which-runtime": "^1.2.1"
  },
  "devDependencies": {
    "bare-compat-napi": "^1.3.5",
    "brittle": "^3.16.2",
    "cmake-bare": "^1.6.1",
    "cmake-fetch": "^1.4.3",
    "cmake-napi": "^1.2.1",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^2.0.0"
  },
  "scripts": {
    "test": "prettier . --check && npm run test:node && npm run test:bare",
    "test:node": "node test/all.js",
    "test:bare": "bare test/all.js"
  },
  "standard": {
    "ignore": [
      "/test/fixtures/*.js"
    ]
  },
  "engines": {
    "bare": ">=1.16.0"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/sodium-native.git"
  },
  "contributors": [
    "Emil Bay <github@tixz.dk> (http://bayes.dk)",
    "Mathias Buus <mathiasbuus@gmail.com> (https://mafinto.sh)",
    "Christophe Diederichs <chm-diederichs@hyperdivision.dk>"
  ],
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/holepunchto/sodium-native/issues"
  },
  "homepage": "https://github.com/holepunchto/sodium-native"
}
module.exports = require('sodium-native')
{
  "name": "sodium-universal",
  "version": "5.0.1",
  "description": "Universal wrapper for sodium-javascript and sodium-native working in Node.js and the Browser",
  "main": "index.js",
  "dependencies": {
    "sodium-native": "^5.0.1"
  },
  "peerDependencies": {
    "sodium-javascript": "~0.8.0"
  },
  "peerDependenciesMeta": {
    "sodium-javascript": {
      "optional": true
    }
  },
  "scripts": {
    "prepublish": "./build-scripts/generate.js"
  },
  "browser": {
    "sodium-native": "sodium-javascript"
  },
  "browserify": {
    "transform": [
      "./build-scripts/transform.js"
    ]
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/sodium-universal.git"
  },
  "keywords": [
    "libsodium",
    "sodium",
    "sodium-native",
    "sodium-javascript",
    "browserify"
  ],
  "contributors": [
    "Emil Bay <github@tixz.dk> (http://bayes.dk)",
    "Mathias Buus <mathiasbuus@gmail.com> (https://mafinto.sh)",
    "Christophe Diederichs <chm-diederichs@hyperdivision.dk>"
  ],
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/holepunchto/sodium-universal/issues"
  },
  "homepage": "https://github.com/holepunchto/sodium-universal#readme"
}
const { EventEmitter } = require('events-universal')
const FIFO = require('fast-fifo')
const TextDecoder = require('text-decoder')

const StreamError = require('./lib/errors.js')

// if we do a future major, expect queue microtask to be there always, for now a bit defensive
const qmt =
  typeof queueMicrotask === 'undefined' ? (fn) => global.process.nextTick(fn) : queueMicrotask

// 29 bits used total (4 from shared, 14 from read, and 11 from write)
const MAX = (1 << 29) - 1

// Shared state
const OPENING = 0b0001
const PREDESTROYING = 0b0010
const DESTROYING = 0b0100
const DESTROYED = 0b1000

const NOT_OPENING = MAX ^ OPENING
const NOT_PREDESTROYING = MAX ^ PREDESTROYING

// Read state (4 bit offset from shared state)
const READ_ACTIVE = 0b00000000000001 << 4
const READ_UPDATING = 0b00000000000010 << 4
const READ_PRIMARY = 0b00000000000100 << 4
const READ_QUEUED = 0b00000000001000 << 4
const READ_RESUMED = 0b00000000010000 << 4
const READ_PIPE_DRAINED = 0b00000000100000 << 4
const READ_ENDING = 0b00000001000000 << 4
const READ_EMIT_DATA = 0b00000010000000 << 4
const READ_EMIT_READABLE = 0b00000100000000 << 4
const READ_EMITTED_READABLE = 0b00001000000000 << 4
const READ_DONE = 0b00010000000000 << 4
const READ_NEXT_TICK = 0b00100000000000 << 4
const READ_NEEDS_PUSH = 0b01000000000000 << 4
const READ_READ_AHEAD = 0b10000000000000 << 4

// Combined read state
const READ_FLOWING = READ_RESUMED | READ_PIPE_DRAINED
const READ_ACTIVE_AND_NEEDS_PUSH = READ_ACTIVE | READ_NEEDS_PUSH
const READ_PRIMARY_AND_ACTIVE = READ_PRIMARY | READ_ACTIVE
const READ_EMIT_READABLE_AND_QUEUED = READ_EMIT_READABLE | READ_QUEUED
const READ_RESUMED_READ_AHEAD = READ_RESUMED | READ_READ_AHEAD

const READ_NOT_ACTIVE = MAX ^ READ_ACTIVE
const READ_NON_PRIMARY = MAX ^ READ_PRIMARY
const READ_NON_PRIMARY_AND_PUSHED = MAX ^ (READ_PRIMARY | READ_NEEDS_PUSH)
const READ_PUSHED = MAX ^ READ_NEEDS_PUSH
const READ_PAUSED = MAX ^ READ_RESUMED
const READ_NOT_QUEUED = MAX ^ (READ_QUEUED | READ_EMITTED_READABLE)
const READ_NOT_ENDING = MAX ^ READ_ENDING
const READ_PIPE_NOT_DRAINED = MAX ^ READ_FLOWING
const READ_NOT_NEXT_TICK = MAX ^ READ_NEXT_TICK
const READ_NOT_UPDATING = MAX ^ READ_UPDATING
const READ_NO_READ_AHEAD = MAX ^ READ_READ_AHEAD
const READ_PAUSED_NO_READ_AHEAD = MAX ^ READ_RESUMED_READ_AHEAD

// Write state (18 bit offset, 4 bit offset from shared state and 14 from read state)
const WRITE_ACTIVE = 0b00000000001 << 18
const WRITE_UPDATING = 0b00000000010 << 18
const WRITE_PRIMARY = 0b00000000100 << 18
const WRITE_QUEUED = 0b00000001000 << 18
const WRITE_UNDRAINED = 0b00000010000 << 18
const WRITE_DONE = 0b00000100000 << 18
const WRITE_EMIT_DRAIN = 0b00001000000 << 18
const WRITE_NEXT_TICK = 0b00010000000 << 18
const WRITE_WRITING = 0b00100000000 << 18
const WRITE_FINISHING = 0b01000000000 << 18
const WRITE_CORKED = 0b10000000000 << 18

const WRITE_NOT_ACTIVE = MAX ^ (WRITE_ACTIVE | WRITE_WRITING)
const WRITE_NON_PRIMARY = MAX ^ WRITE_PRIMARY
const WRITE_NOT_FINISHING = MAX ^ (WRITE_ACTIVE | WRITE_FINISHING)
const WRITE_DRAINED = MAX ^ WRITE_UNDRAINED
const WRITE_NOT_QUEUED = MAX ^ WRITE_QUEUED
const WRITE_NOT_NEXT_TICK = MAX ^ WRITE_NEXT_TICK
const WRITE_NOT_UPDATING = MAX ^ WRITE_UPDATING
const WRITE_NOT_CORKED = MAX ^ WRITE_CORKED

// Combined shared state
const ACTIVE = READ_ACTIVE | WRITE_ACTIVE
const NOT_ACTIVE = MAX ^ ACTIVE
const DONE = READ_DONE | WRITE_DONE
const DESTROY_STATUS = DESTROYING | DESTROYED | PREDESTROYING
const OPEN_STATUS = DESTROY_STATUS | OPENING
const AUTO_DESTROY = DESTROY_STATUS | DONE
const NON_PRIMARY = WRITE_NON_PRIMARY & READ_NON_PRIMARY
const ACTIVE_OR_TICKING = WRITE_NEXT_TICK | READ_NEXT_TICK
const TICKING = ACTIVE_OR_TICKING & NOT_ACTIVE
const IS_OPENING = OPEN_STATUS | TICKING

// Combined shared state and read state
const READ_PRIMARY_STATUS = OPEN_STATUS | READ_ENDING | READ_DONE
const READ_STATUS = OPEN_STATUS | READ_DONE | READ_QUEUED
const READ_ENDING_STATUS = OPEN_STATUS | READ_ENDING | READ_QUEUED
const READ_READABLE_STATUS = OPEN_STATUS | READ_EMIT_READABLE | READ_QUEUED | READ_EMITTED_READABLE
const SHOULD_NOT_READ =
  OPEN_STATUS | READ_ACTIVE | READ_ENDING | READ_DONE | READ_NEEDS_PUSH | READ_READ_AHEAD
const READ_BACKPRESSURE_STATUS = DESTROY_STATUS | READ_ENDING | READ_DONE
const READ_UPDATE_SYNC_STATUS = READ_UPDATING | OPEN_STATUS | READ_NEXT_TICK | READ_PRIMARY
const READ_NEXT_TICK_OR_OPENING = READ_NEXT_TICK | OPENING

// Combined write state
const WRITE_PRIMARY_STATUS = OPEN_STATUS | WRITE_FINISHING | WRITE_DONE
const WRITE_QUEUED_AND_UNDRAINED = WRITE_QUEUED | WRITE_UNDRAINED
const WRITE_QUEUED_AND_ACTIVE = WRITE_QUEUED | WRITE_ACTIVE
const WRITE_DRAIN_STATUS = WRITE_QUEUED | WRITE_UNDRAINED | OPEN_STATUS | WRITE_ACTIVE
const WRITE_STATUS = OPEN_STATUS | WRITE_ACTIVE | WRITE_QUEUED | WRITE_CORKED
const WRITE_PRIMARY_AND_ACTIVE = WRITE_PRIMARY | WRITE_ACTIVE
const WRITE_ACTIVE_AND_WRITING = WRITE_ACTIVE | WRITE_WRITING
const WRITE_FINISHING_STATUS = OPEN_STATUS | WRITE_FINISHING | WRITE_QUEUED_AND_ACTIVE | WRITE_DONE
const WRITE_BACKPRESSURE_STATUS = WRITE_UNDRAINED | DESTROY_STATUS | WRITE_FINISHING | WRITE_DONE
const WRITE_UPDATE_SYNC_STATUS = WRITE_UPDATING | OPEN_STATUS | WRITE_NEXT_TICK | WRITE_PRIMARY
const WRITE_DROP_DATA = WRITE_FINISHING | WRITE_DONE | DESTROY_STATUS

const asyncIterator = Symbol.asyncIterator || Symbol('asyncIterator')

class WritableState {
  constructor(
    stream,
    { highWaterMark = 16384, map = null, mapWritable, byteLength, byteLengthWritable } = {}
  ) {
    this.stream = stream
    this.queue = new FIFO()
    this.highWaterMark = highWaterMark
    this.buffered = 0
    this.error = null
    this.pipeline = null
    this.drains = null // if we add more seldomly used helpers we might them into a subobject so its a single ptr
    this.byteLength = byteLengthWritable || byteLength || defaultByteLength
    this.map = mapWritable || map
    this.afterWrite = afterWrite.bind(this)
    this.afterUpdateNextTick = updateWriteNT.bind(this)
  }

  get ending() {
    return (this.stream._duplexState & WRITE_FINISHING) !== 0
  }

  get ended() {
    return (this.stream._duplexState & WRITE_DONE) !== 0
  }

  push(data) {
    if ((this.stream._duplexState & WRITE_DROP_DATA) !== 0) return false
    if (this.map !== null) data = this.map(data)

    this.buffered += this.byteLength(data)
    this.queue.push(data)

    if (this.buffered < this.highWaterMark) {
      this.stream._duplexState |= WRITE_QUEUED
      return true
    }

    this.stream._duplexState |= WRITE_QUEUED_AND_UNDRAINED
    return false
  }

  shift() {
    const data = this.queue.shift()

    this.buffered -= this.byteLength(data)
    if (this.buffered === 0) this.stream._duplexState &= WRITE_NOT_QUEUED

    return data
  }

  end(data) {
    if (typeof data === 'function') {
      this.stream.once('finish', data)
    } else if (data !== undefined && data !== null) {
      this.push(data)
    }

    this.stream._duplexState = (this.stream._duplexState | WRITE_FINISHING) & WRITE_NON_PRIMARY
  }

  autoBatch(data, cb) {
    const buffer = []
    const stream = this.stream

    buffer.push(data)
    while ((stream._duplexState & WRITE_STATUS) === WRITE_QUEUED_AND_ACTIVE) {
      buffer.push(stream._writableState.shift())
    }

    if ((stream._duplexState & OPEN_STATUS) !== 0) return cb(null)
    stream._writev(buffer, cb)
  }

  update() {
    const stream = this.stream

    stream._duplexState |= WRITE_UPDATING

    do {
      while ((stream._duplexState & WRITE_STATUS) === WRITE_QUEUED) {
        const data = this.shift()
        stream._duplexState |= WRITE_ACTIVE_AND_WRITING
        stream._write(data, this.afterWrite)
      }

      if ((stream._duplexState & WRITE_PRIMARY_AND_ACTIVE) === 0) this.updateNonPrimary()
    } while (this.continueUpdate() === true)

    stream._duplexState &= WRITE_NOT_UPDATING
  }

  updateNonPrimary() {
    const stream = this.stream

    if ((stream._duplexState & WRITE_FINISHING_STATUS) === WRITE_FINISHING) {
      stream._duplexState = stream._duplexState | WRITE_ACTIVE
      stream._final(afterFinal.bind(this))
      return
    }

    if ((stream._duplexState & DESTROY_STATUS) === DESTROYING) {
      if ((stream._duplexState & ACTIVE_OR_TICKING) === 0) {
        stream._duplexState |= ACTIVE
        stream._destroy(afterDestroy.bind(this))
      }
      return
    }

    if ((stream._duplexState & IS_OPENING) === OPENING) {
      stream._duplexState = (stream._duplexState | ACTIVE) & NOT_OPENING
      stream._open(afterOpen.bind(this))
    }
  }

  continueUpdate() {
    if ((this.stream._duplexState & WRITE_NEXT_TICK) === 0) return false
    this.stream._duplexState &= WRITE_NOT_NEXT_TICK
    return true
  }

  updateCallback() {
    if ((this.stream._duplexState & WRITE_UPDATE_SYNC_STATUS) === WRITE_PRIMARY) {
      this.update()
    } else {
      this.updateNextTick()
    }
  }

  updateNextTick() {
    if ((this.stream._duplexState & WRITE_NEXT_TICK) !== 0) return
    this.stream._duplexState |= WRITE_NEXT_TICK
    if ((this.stream._duplexState & WRITE_UPDATING) === 0) qmt(this.afterUpdateNextTick)
  }
}

class ReadableState {
  constructor(
    stream,
    { highWaterMark = 16384, map = null, mapReadable, byteLength, byteLengthReadable } = {}
  ) {
    this.stream = stream
    this.queue = new FIFO()
    this.highWaterMark = highWaterMark === 0 ? 1 : highWaterMark
    this.buffered = 0
    this.readAhead = highWaterMark > 0
    this.error = null
    this.pipeline = null
    this.byteLength = byteLengthReadable || byteLength || defaultByteLength
    this.map = mapReadable || map
    this.pipeTo = null
    this.afterRead = afterRead.bind(this)
    this.afterUpdateNextTick = updateReadNT.bind(this)
  }

  get ending() {
    return (this.stream._duplexState & READ_ENDING) !== 0
  }

  get ended() {
    return (this.stream._duplexState & READ_DONE) !== 0
  }

  pipe(pipeTo, cb) {
    if (this.pipeTo !== null) throw StreamError.BAD_ARGUMENT('Can only pipe to one destination')
    if (typeof cb !== 'function') cb = null

    this.stream._duplexState |= READ_PIPE_DRAINED
    this.pipeTo = pipeTo
    this.pipeline = new Pipeline(this.stream, pipeTo, cb)

    if (cb) this.stream.on('error', noop) // We already error handle this so supress crashes

    if (isStreamx(pipeTo)) {
      pipeTo._writableState.pipeline = this.pipeline
      if (cb) pipeTo.on('error', noop) // We already error handle this so supress crashes
      pipeTo.on('finish', this.pipeline.finished.bind(this.pipeline)) // TODO: just call finished from pipeTo itself
    } else {
      const onerror = this.pipeline.done.bind(this.pipeline, pipeTo)
      const onclose = this.pipeline.done.bind(this.pipeline, pipeTo, null) // onclose has a weird bool arg
      pipeTo.on('error', onerror)
      pipeTo.on('close', onclose)
      pipeTo.on('finish', this.pipeline.finished.bind(this.pipeline))
    }

    pipeTo.on('drain', afterDrain.bind(this))
    this.stream.emit('piping', pipeTo)
    pipeTo.emit('pipe', this.stream)
  }

  push(data) {
    const stream = this.stream

    if (data === null) {
      this.highWaterMark = 0
      stream._duplexState = (stream._duplexState | READ_ENDING) & READ_NON_PRIMARY_AND_PUSHED
      return false
    }

    if (this.map !== null) {
      data = this.map(data)
      if (data === null) {
        stream._duplexState &= READ_PUSHED
        return this.buffered < this.highWaterMark
      }
    }

    this.buffered += this.byteLength(data)
    this.queue.push(data)

    stream._duplexState = (stream._duplexState | READ_QUEUED) & READ_PUSHED

    return this.buffered < this.highWaterMark
  }

  shift() {
    const data = this.queue.shift()

    this.buffered -= this.byteLength(data)
    if (this.buffered === 0) {
      this.stream._duplexState &= READ_NOT_QUEUED
    }

    return data
  }

  unshift(data) {
    const pending = [this.map !== null ? this.map(data) : data]
    while (this.buffered > 0) pending.push(this.shift())

    for (let i = 0; i < pending.length - 1; i++) {
      const data = pending[i]
      this.buffered += this.byteLength(data)
      this.queue.push(data)
    }

    this.push(pending[pending.length - 1])
  }

  read() {
    const stream = this.stream

    if ((stream._duplexState & READ_STATUS) === READ_QUEUED) {
      const data = this.shift()

      if (this.pipeTo !== null && this.pipeTo.write(data) === false) {
        stream._duplexState &= READ_PIPE_NOT_DRAINED
      }

      if ((stream._duplexState & READ_EMIT_DATA) !== 0) {
        stream.emit('data', data)
      }

      return data
    }

    if (this.readAhead === false) {
      stream._duplexState |= READ_READ_AHEAD
      this.updateNextTick()
    }

    return null
  }

  drain() {
    const stream = this.stream

    while (
      (stream._duplexState & READ_STATUS) === READ_QUEUED &&
      (stream._duplexState & READ_FLOWING) !== 0
    ) {
      const data = this.shift()

      if (this.pipeTo !== null && this.pipeTo.write(data) === false) {
        stream._duplexState &= READ_PIPE_NOT_DRAINED
      }

      if ((stream._duplexState & READ_EMIT_DATA) !== 0) {
        stream.emit('data', data)
      }
    }
  }

  update() {
    const stream = this.stream

    stream._duplexState |= READ_UPDATING

    do {
      this.drain()

      while (
        this.buffered < this.highWaterMark &&
        (stream._duplexState & SHOULD_NOT_READ) === READ_READ_AHEAD
      ) {
        stream._duplexState |= READ_ACTIVE_AND_NEEDS_PUSH
        stream._read(this.afterRead)
        this.drain()
      }

      if ((stream._duplexState & READ_READABLE_STATUS) === READ_EMIT_READABLE_AND_QUEUED) {
        stream._duplexState |= READ_EMITTED_READABLE
        stream.emit('readable')
      }

      if ((stream._duplexState & READ_PRIMARY_AND_ACTIVE) === 0) {
        this.updateNonPrimary()
      }
    } while (this.continueUpdate() === true)

    stream._duplexState &= READ_NOT_UPDATING
  }

  updateNonPrimary() {
    const stream = this.stream

    if ((stream._duplexState & READ_ENDING_STATUS) === READ_ENDING) {
      stream._duplexState = (stream._duplexState | READ_DONE) & READ_NOT_ENDING
      stream.emit('end')

      if ((stream._duplexState & AUTO_DESTROY) === DONE) {
        stream._duplexState |= DESTROYING
      }

      if (this.pipeTo !== null) {
        this.pipeTo.end()
      }
    }

    if ((stream._duplexState & DESTROY_STATUS) === DESTROYING) {
      if ((stream._duplexState & ACTIVE_OR_TICKING) === 0) {
        stream._duplexState |= ACTIVE
        stream._destroy(afterDestroy.bind(this))
      }
      return
    }

    if ((stream._duplexState & IS_OPENING) === OPENING) {
      stream._duplexState = (stream._duplexState | ACTIVE) & NOT_OPENING
      stream._open(afterOpen.bind(this))
    }
  }

  continueUpdate() {
    if ((this.stream._duplexState & READ_NEXT_TICK) === 0) return false
    this.stream._duplexState &= READ_NOT_NEXT_TICK
    return true
  }

  updateCallback() {
    if ((this.stream._duplexState & READ_UPDATE_SYNC_STATUS) === READ_PRIMARY) {
      this.update()
    } else {
      this.updateNextTick()
    }
  }

  updateNextTickIfOpen() {
    if ((this.stream._duplexState & READ_NEXT_TICK_OR_OPENING) !== 0) return
    this.stream._duplexState |= READ_NEXT_TICK
    if ((this.stream._duplexState & READ_UPDATING) === 0) qmt(this.afterUpdateNextTick)
  }

  updateNextTick() {
    if ((this.stream._duplexState & READ_NEXT_TICK) !== 0) return
    this.stream._duplexState |= READ_NEXT_TICK
    if ((this.stream._duplexState & READ_UPDATING) === 0) qmt(this.afterUpdateNextTick)
  }
}

class TransformState {
  constructor(stream) {
    this.data = null
    this.afterTransform = afterTransform.bind(stream)
    this.afterFinal = null
  }
}

class Pipeline {
  constructor(src, dst, cb) {
    this.from = src
    this.to = dst
    this.afterPipe = cb
    this.error = null
    this.pipeToFinished = false
  }

  finished() {
    this.pipeToFinished = true
  }

  done(stream, err) {
    if (err) this.error = err

    if (stream === this.to) {
      this.to = null

      if (this.from !== null) {
        if ((this.from._duplexState & READ_DONE) === 0 || !this.pipeToFinished) {
          this.from.destroy(this.error || StreamError.PREMATURE_CLOSE('Writable stream closed'))
        }
        return
      }
    }

    if (stream === this.from) {
      this.from = null

      if (this.to !== null) {
        if ((stream._duplexState & READ_DONE) === 0) {
          this.to.destroy(this.error || StreamError.PREMATURE_CLOSE('Readable stream closed'))
        }
        return
      }
    }

    if (this.afterPipe !== null) this.afterPipe(this.error)
    this.to = this.from = this.afterPipe = null
  }
}

function afterDrain() {
  this.stream._duplexState |= READ_PIPE_DRAINED
  this.updateCallback()
}

function afterFinal(err) {
  const stream = this.stream
  if (err) stream.destroy(err)

  if ((stream._duplexState & DESTROY_STATUS) === 0) {
    stream._duplexState |= WRITE_DONE
    stream.emit('finish')
  }

  if ((stream._duplexState & AUTO_DESTROY) === DONE) {
    stream._duplexState |= DESTROYING
  }

  stream._duplexState &= WRITE_NOT_FINISHING

  // no need to wait the extra tick here, so we short circuit that
  if ((stream._duplexState & WRITE_UPDATING) === 0) {
    this.update()
  } else {
    this.updateNextTick()
  }
}

function afterDestroy(err) {
  const stream = this.stream

  if (!err && !StreamError.isStreamDestroyed(this.error)) err = this.error
  if (err) stream.emit('error', err)

  stream._duplexState |= DESTROYED
  stream.emit('close')

  const rs = stream._readableState
  const ws = stream._writableState

  if (rs !== null && rs.pipeline !== null) {
    rs.pipeline.done(stream, err)
  }

  if (ws !== null) {
    while (ws.drains !== null && ws.drains.length > 0) {
      ws.drains.shift().resolve(false)
    }

    if (ws.pipeline !== null) {
      ws.pipeline.done(stream, err)
    }
  }
}

function afterWrite(err) {
  const stream = this.stream

  if (err) stream.destroy(err)
  stream._duplexState &= WRITE_NOT_ACTIVE

  if (this.drains !== null) tickDrains(this.drains)

  if ((stream._duplexState & WRITE_DRAIN_STATUS) === WRITE_UNDRAINED) {
    stream._duplexState &= WRITE_DRAINED

    if ((stream._duplexState & WRITE_EMIT_DRAIN) === WRITE_EMIT_DRAIN) {
      stream.emit('drain')
    }
  }

  this.updateCallback()
}

function afterRead(err) {
  if (err) this.stream.destroy(err)
  this.stream._duplexState &= READ_NOT_ACTIVE

  if (this.readAhead === false && (this.stream._duplexState & READ_RESUMED) === 0) {
    this.stream._duplexState &= READ_NO_READ_AHEAD
  }

  this.updateCallback()
}

function updateReadNT() {
  if ((this.stream._duplexState & READ_UPDATING) === 0) {
    this.stream._duplexState &= READ_NOT_NEXT_TICK
    this.update()
  }
}

function updateWriteNT() {
  if ((this.stream._duplexState & WRITE_UPDATING) === 0) {
    this.stream._duplexState &= WRITE_NOT_NEXT_TICK
    this.update()
  }
}

function tickDrains(drains) {
  for (let i = 0; i < drains.length; i++) {
    // drains.writes are monotonic, so if one is 0 its always the first one
    if (--drains[i].writes === 0) {
      drains.shift().resolve(true)
      i--
    }
  }
}

function afterOpen(err) {
  const stream = this.stream

  if (err) stream.destroy(err)

  if ((stream._duplexState & DESTROYING) === 0) {
    if ((stream._duplexState & READ_PRIMARY_STATUS) === 0) {
      stream._duplexState |= READ_PRIMARY
    }

    if ((stream._duplexState & WRITE_PRIMARY_STATUS) === 0) {
      stream._duplexState |= WRITE_PRIMARY
    }

    stream.emit('open')
  }

  stream._duplexState &= NOT_ACTIVE

  if (stream._writableState !== null) {
    stream._writableState.updateCallback()
  }

  if (stream._readableState !== null) {
    stream._readableState.updateCallback()
  }
}

function afterTransform(err, data) {
  if (data !== undefined && data !== null) this.push(data)
  this._writableState.afterWrite(err)
}

function newListener(name) {
  if (this._readableState !== null) {
    if (name === 'data') {
      this._duplexState |= READ_EMIT_DATA | READ_RESUMED_READ_AHEAD
      this._readableState.updateNextTick()
    }
    if (name === 'readable') {
      this._duplexState |= READ_EMIT_READABLE
      this._readableState.updateNextTick()
    }
  }

  if (this._writableState !== null) {
    if (name === 'drain') {
      this._duplexState |= WRITE_EMIT_DRAIN
      this._writableState.updateNextTick()
    }
  }
}

class Stream extends EventEmitter {
  constructor(opts) {
    super()

    this._duplexState = 0
    this._readableState = null
    this._writableState = null

    if (opts) {
      if (opts.open) this._open = opts.open
      if (opts.destroy) this._destroy = opts.destroy
      if (opts.predestroy) this._predestroy = opts.predestroy
      if (opts.signal) opts.signal.addEventListener('abort', abort.bind(this))
    }

    this.on('newListener', newListener)
  }

  _open(cb) {
    cb(null)
  }

  _destroy(cb) {
    cb(null)
  }

  _predestroy() {
    // does nothing
  }

  get readable() {
    return this._readableState !== null ? true : undefined
  }

  get writable() {
    return this._writableState !== null ? true : undefined
  }

  get destroyed() {
    return (this._duplexState & DESTROYED) !== 0
  }

  get destroying() {
    return (this._duplexState & DESTROY_STATUS) !== 0
  }

  destroy(err) {
    if ((this._duplexState & DESTROY_STATUS) === 0) {
      if (!err) err = StreamError.STREAM_DESTROYED()
      this._duplexState = (this._duplexState | DESTROYING) & NON_PRIMARY

      if (this._readableState !== null) {
        this._readableState.highWaterMark = 0
        this._readableState.error = err
      }

      if (this._writableState !== null) {
        this._writableState.highWaterMark = 0
        this._writableState.error = err
      }

      this._duplexState |= PREDESTROYING
      this._predestroy()
      this._duplexState &= NOT_PREDESTROYING

      if (this._readableState !== null) {
        this._readableState.updateNextTick()
      }

      if (this._writableState !== null) {
        this._writableState.updateNextTick()
      }
    }
  }
}

class Readable extends Stream {
  constructor(opts) {
    super(opts)

    this._duplexState |= OPENING | WRITE_DONE | READ_READ_AHEAD
    this._readableState = new ReadableState(this, opts)

    if (opts) {
      if (this._readableState.readAhead === false) this._duplexState &= READ_NO_READ_AHEAD
      if (opts.read) this._read = opts.read
      if (opts.eagerOpen) this._readableState.updateNextTick()
      if (opts.encoding) this.setEncoding(opts.encoding)
    }
  }

  static deferred(fn, opts) {
    const out = new PassThrough(opts)

    fn()
      .then((src) => {
        if (src === null) return out.end()
        if (out.destroying) return
        pipeline(src, out, noop)
      })
      .catch((err) => out.destroy(err))

    return out
  }

  setEncoding(encoding) {
    const dec = new TextDecoder(encoding)
    const map = this._readableState.map || echo
    this._readableState.map = mapOrSkip
    return this

    function mapOrSkip(data) {
      const next = dec.push(data)
      return next === '' && (data.byteLength !== 0 || dec.remaining > 0) ? null : map(next)
    }
  }

  _read(cb) {
    cb(null)
  }

  pipe(dest, cb) {
    this._readableState.updateNextTick()
    this._readableState.pipe(dest, cb)
    return dest
  }

  read() {
    this._readableState.updateNextTick()
    return this._readableState.read()
  }

  push(data) {
    this._readableState.updateNextTickIfOpen()
    return this._readableState.push(data)
  }

  unshift(data) {
    this._readableState.updateNextTickIfOpen()
    return this._readableState.unshift(data)
  }

  resume() {
    this._duplexState |= READ_RESUMED_READ_AHEAD
    this._readableState.updateNextTick()
    return this
  }

  pause() {
    this._duplexState &=
      this._readableState.readAhead === false ? READ_PAUSED_NO_READ_AHEAD : READ_PAUSED
    return this
  }

  static _fromAsyncIterator(ite, opts) {
    let destroy

    const rs = new Readable({
      ...opts,
      read(cb) {
        ite.next().then(push).then(cb.bind(null, null)).catch(cb)
      },
      predestroy() {
        destroy = ite.return()
      },
      destroy(cb) {
        if (!destroy) return cb(null)
        destroy.then(cb.bind(null, null)).catch(cb)
      }
    })

    return rs

    function push(data) {
      if (data.done) rs.push(null)
      else rs.push(data.value)
    }
  }

  static from(data, opts) {
    if (isReadStreamx(data)) return data
    if (data[asyncIterator]) return this._fromAsyncIterator(data[asyncIterator](), opts)
    if (!Array.isArray(data)) data = data === undefined ? [] : [data]

    let i = 0
    return new Readable({
      ...opts,
      read(cb) {
        this.push(i === data.length ? null : data[i++])
        cb(null)
      }
    })
  }

  static isBackpressured(rs) {
    return (
      (rs._duplexState & READ_BACKPRESSURE_STATUS) !== 0 ||
      rs._readableState.buffered >= rs._readableState.highWaterMark
    )
  }

  static isPaused(rs) {
    return (rs._duplexState & READ_RESUMED) === 0
  }

  [asyncIterator]() {
    const stream = this

    let error = null
    let promiseResolve = null
    let promiseReject = null

    this.on('error', (err) => {
      error = err
    })
    this.on('readable', onreadable)
    this.on('close', onclose)

    return {
      [asyncIterator]() {
        return this
      },
      next() {
        return new Promise(function (resolve, reject) {
          promiseResolve = resolve
          promiseReject = reject
          const data = stream.read()
          if (data !== null) ondata(data)
          else if ((stream._duplexState & DESTROYED) !== 0) ondata(null)
        })
      },
      return() {
        return destroy(null)
      },
      throw(err) {
        return destroy(err)
      }
    }

    function onreadable() {
      if (promiseResolve !== null) ondata(stream.read())
    }

    function onclose() {
      if (promiseResolve !== null) ondata(null)
    }

    function ondata(data) {
      if (promiseReject === null) return
      if (error) {
        promiseReject(error)
      } else if (data === null && (stream._duplexState & READ_DONE) === 0) {
        promiseReject(StreamError.STREAM_DESTROYED())
      } else {
        promiseResolve({ value: data, done: data === null })
      }
      promiseReject = promiseResolve = null
    }

    function destroy(err) {
      stream.destroy(err)
      return new Promise((resolve, reject) => {
        if (stream._duplexState & DESTROYED) return resolve({ value: undefined, done: true })
        stream.once('close', function () {
          if (err) reject(err)
          else resolve({ value: undefined, done: true })
        })
      })
    }
  }
}

class Writable extends Stream {
  constructor(opts) {
    super(opts)

    this._duplexState |= OPENING | READ_DONE
    this._writableState = new WritableState(this, opts)

    if (opts) {
      if (opts.writev) this._writev = opts.writev
      if (opts.write) this._write = opts.write
      if (opts.final) this._final = opts.final
      if (opts.eagerOpen) this._writableState.updateNextTick()
    }
  }

  cork() {
    this._duplexState |= WRITE_CORKED
  }

  uncork() {
    this._duplexState &= WRITE_NOT_CORKED
    this._writableState.updateNextTick()
  }

  _writev(batch, cb) {
    cb(null)
  }

  _write(data, cb) {
    this._writableState.autoBatch(data, cb)
  }

  _final(cb) {
    cb(null)
  }

  static isBackpressured(ws) {
    return (ws._duplexState & WRITE_BACKPRESSURE_STATUS) !== 0
  }

  static drained(ws) {
    if (ws.destroyed) return Promise.resolve(false)

    const state = ws._writableState
    const pending = isWritev(ws) ? Math.min(1, state.queue.length) : state.queue.length
    const writes = pending + (ws._duplexState & WRITE_WRITING ? 1 : 0)
    if (writes === 0) return Promise.resolve(true)

    if (state.drains === null) state.drains = []
    return new Promise((resolve) => {
      state.drains.push({ writes, resolve })
    })
  }

  write(data) {
    this._writableState.updateNextTick()
    return this._writableState.push(data)
  }

  end(data) {
    this._writableState.updateNextTick()
    this._writableState.end(data)
    return this
  }
}

class Duplex extends Readable {
  // and Writable
  constructor(opts) {
    super(opts)

    this._duplexState = OPENING | (this._duplexState & READ_READ_AHEAD)
    this._writableState = new WritableState(this, opts)

    if (opts) {
      if (opts.writev) this._writev = opts.writev
      if (opts.write) this._write = opts.write
      if (opts.final) this._final = opts.final
    }
  }

  cork() {
    this._duplexState |= WRITE_CORKED
  }

  uncork() {
    this._duplexState &= WRITE_NOT_CORKED
    this._writableState.updateNextTick()
  }

  _writev(batch, cb) {
    cb(null)
  }

  _write(data, cb) {
    this._writableState.autoBatch(data, cb)
  }

  _final(cb) {
    cb(null)
  }

  write(data) {
    this._writableState.updateNextTick()
    return this._writableState.push(data)
  }

  end(data) {
    this._writableState.updateNextTick()
    this._writableState.end(data)
    return this
  }
}

class Transform extends Duplex {
  constructor(opts) {
    super(opts)
    this._transformState = new TransformState(this)

    if (opts) {
      if (opts.transform) this._transform = opts.transform
      if (opts.flush) this._flush = opts.flush
    }
  }

  _write(data, cb) {
    if (this._readableState.buffered >= this._readableState.highWaterMark) {
      this._transformState.data = data
    } else {
      this._transform(data, this._transformState.afterTransform)
    }
  }

  _read(cb) {
    if (this._transformState.data !== null) {
      const data = this._transformState.data
      this._transformState.data = null
      cb(null)
      this._transform(data, this._transformState.afterTransform)
    } else {
      cb(null)
    }
  }

  destroy(err) {
    super.destroy(err)
    if (this._transformState.data !== null) {
      this._transformState.data = null
      this._transformState.afterTransform()
    }
  }

  _transform(data, cb) {
    cb(null, data)
  }

  _flush(cb) {
    cb(null)
  }

  _final(cb) {
    this._transformState.afterFinal = cb
    this._flush(transformAfterFlush.bind(this))
  }
}

class PassThrough extends Transform {}

function transformAfterFlush(err, data) {
  const cb = this._transformState.afterFinal
  if (err) return cb(err)

  if (data !== null && data !== undefined) this.push(data)
  this.push(null)
  cb(null)
}

function pipelinePromise(...streams) {
  return new Promise((resolve, reject) => {
    return pipeline(...streams, (err) => {
      if (err) return reject(err)
      resolve()
    })
  })
}

function pipeline(stream, ...streams) {
  const all = Array.isArray(stream) ? [...stream, ...streams] : [stream, ...streams]
  const done = all.length && typeof all[all.length - 1] === 'function' ? all.pop() : null

  if (all.length < 2) throw StreamError.BAD_ARGUMENT('Pipeline requires at least 2 streams')

  let src = all[0]
  let dest = null
  let error = null

  for (let i = 1; i < all.length; i++) {
    dest = all[i]

    if (isStreamx(src)) {
      src.pipe(dest, onerror)
    } else {
      errorHandle(src, true, i > 1, onerror)
      src.pipe(dest)
    }

    src = dest
  }

  if (done) {
    let fin = false

    const autoDestroy =
      isStreamx(dest) || !!(dest._writableState && dest._writableState.autoDestroy)

    dest.on('error', (err) => {
      if (error === null) error = err
    })

    dest.on('finish', () => {
      fin = true
      if (!autoDestroy) done(error)
    })

    if (autoDestroy) {
      dest.on('close', () => done(error || (fin ? null : StreamError.PREMATURE_CLOSE())))
    }
  }

  return dest

  function errorHandle(s, rd, wr, onerror) {
    s.on('error', onerror)
    s.on('close', onclose)

    function onclose() {
      if (rd && s._readableState && !s._readableState.ended) {
        return onerror(StreamError.PREMATURE_CLOSE())
      }
      if (wr && s._writableState && !s._writableState.ended) {
        return onerror(StreamError.PREMATURE_CLOSE())
      }
    }
  }

  function onerror(err) {
    if (!err || error) return
    error = err

    for (const s of all) {
      s.destroy(err)
    }
  }
}

function echo(s) {
  return s
}

function isStream(stream) {
  return !!stream._readableState || !!stream._writableState
}

function isStreamx(stream) {
  return typeof stream._duplexState === 'number' && isStream(stream)
}

function isEnding(stream) {
  return !!stream._readableState && stream._readableState.ending
}

function isEnded(stream) {
  return !!stream._readableState && stream._readableState.ended
}

function isFinishing(stream) {
  return !!stream._writableState && stream._writableState.ending
}

function isFinished(stream) {
  return !!stream._writableState && stream._writableState.ended
}

function getStreamError(stream, opts = {}) {
  const err =
    (stream._readableState && stream._readableState.error) ||
    (stream._writableState && stream._writableState.error)

  // avoid implicit errors by default
  return !opts.all && StreamError.isStreamDestroyed(err) ? null : err
}

function isReadStreamx(stream) {
  return isStreamx(stream) && stream.readable
}

function isDisturbed(stream) {
  return (
    (stream._duplexState & OPENING) !== OPENING ||
    (stream._duplexState & DESTROYING) === DESTROYING ||
    (stream._duplexState & ACTIVE_OR_TICKING) !== 0
  )
}

function isTypedArray(data) {
  return typeof data === 'object' && data !== null && typeof data.byteLength === 'number'
}

function defaultByteLength(data) {
  return isTypedArray(data) ? data.byteLength : 1024
}

function noop() {}

function abort() {
  this.destroy(StreamError.ABORTED())
}

function isWritev(s) {
  return s._writev !== Writable.prototype._writev && s._writev !== Duplex.prototype._writev
}

module.exports = {
  pipeline,
  pipelinePromise,
  isStream,
  isStreamx,
  isEnding,
  isEnded,
  isFinishing,
  isFinished,
  isDisturbed,
  getStreamError,
  Stream,
  Writable,
  Readable,
  Duplex,
  Transform,
  // Export PassThrough for compatibility with Node.js core's stream module
  PassThrough
}
module.exports = class StreamError extends Error {
  constructor(msg, code, fn = StreamError) {
    super(msg)

    this.code = code

    if (Error.captureStackTrace) {
      Error.captureStackTrace(this, fn)
    }
  }

  static isStreamDestroyed(err) {
    return err && err.code === 'STREAM_DESTROYED'
  }

  static isPrematureClose(err) {
    return err && err.code === 'PREMATURE_CLOSE'
  }

  static isAborted(err) {
    return err && err.code === 'ABORTED'
  }

  static isBadArgument(err) {
    return err && err.code === 'BAD_ARGUMENT'
  }

  get name() {
    return 'StreamError'
  }

  static STREAM_DESTROYED() {
    return new StreamError('Stream was destroyed', 'STREAM_DESTROYED', StreamError.STREAM_DESTROYED)
  }

  static PREMATURE_CLOSE(msg = 'Premature close') {
    return new StreamError(msg, 'PREMATURE_CLOSE', StreamError.PREMATURE_CLOSE)
  }

  static ABORTED() {
    return new StreamError('Stream aborted', 'ABORTED', StreamError.ABORTED)
  }

  static BAD_ARGUMENT(msg = 'Bad argument') {
    return new StreamError(msg, 'BAD_ARGUMENT', StreamError.BAD_ARGUMENT)
  }
}
{
  "name": "streamx",
  "version": "2.28.0",
  "description": "An iteration of the Node.js core streams with a series of improvements",
  "main": "index.js",
  "exports": {
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    },
    "./package": "./package.json",
    "./errors": {
      "types": "./lib/errors.d.ts",
      "default": "./lib/errors"
    }
  },
  "dependencies": {
    "events-universal": "^1.0.0",
    "fast-fifo": "^1.3.2",
    "text-decoder": "^1.1.0"
  },
  "devDependencies": {
    "b4a": "^1.6.6",
    "brittle": "^3.1.1",
    "end-of-stream": "^1.4.4",
    "lunte": "^1.8.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^2.0.0"
  },
  "files": [
    "index.js",
    "index.d.ts",
    "runtime.d.ts",
    "lib"
  ],
  "scripts": {
    "format": "prettier . --write",
    "test": "brittle test/all.js && brittle-bare test/all.js",
    "lint": "prettier . --check && lunte"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/mafintosh/streamx.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/mafintosh/streamx/issues"
  },
  "homepage": "https://github.com/mafintosh/streamx"
}
const { Readable } = require('streamx')

module.exports = function (s, forks = 2) {
  const streams = new Array(forks)
  const status = new Array(forks).fill(true)

  let ended = false

  for (let i = 0; i < forks; i++) {
    streams[i] = new Readable({
      read (cb) {
        const check = !status[i]
        status[i] = true
        if (check && allReadable()) s.resume()
        cb(null)
      }
    })
  }

  s.on('end', function () {
    ended = true
    for (const stream of streams) stream.push(null)
  })

  s.on('error', function (err) {
    for (const stream of streams) stream.destroy(err)
  })

  s.on('close', function () {
    if (ended) return
    for (const stream of streams) stream.destroy()
  })

  s.on('data', function (data) {
    let needsPause = false
    for (let i = 0; i < streams.length; i++) {
      if (!(status[i] = streams[i].push(data))) {
        needsPause = true
      }
    }
    if (needsPause) s.pause()
  })

  return streams

  function allReadable () {
    for (let j = 0; j < status.length; j++) {
      if (!status[j]) return false
    }
    return true
  }
}
{
  "name": "teex",
  "version": "1.0.1",
  "description": "Turn a readable stream into multiple readable streamx streams",
  "main": "index.js",
  "dependencies": {
    "streamx": "^2.12.5"
  },
  "devDependencies": {
    "standard": "^14.3.4",
    "tape": "^5.0.1"
  },
  "scripts": {
    "test": "standard && tape test.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/mafintosh/teex.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/mafintosh/teex/issues"
  },
  "homepage": "https://github.com/mafintosh/teex"
}
const PassThroughDecoder = require('./lib/pass-through-decoder')
const UTF8Decoder = require('./lib/utf8-decoder')

module.exports = class TextDecoder {
  constructor(encoding = 'utf8') {
    this.encoding = normalizeEncoding(encoding)

    switch (this.encoding) {
      case 'utf8':
        this.decoder = new UTF8Decoder()
        break
      case 'utf16le':
      case 'base64':
        throw new Error('Unsupported encoding: ' + this.encoding)
      default:
        this.decoder = new PassThroughDecoder(this.encoding)
    }
  }

  get remaining() {
    return this.decoder.remaining
  }

  push(data) {
    if (typeof data === 'string') return data
    return this.decoder.decode(data)
  }

  // For Node.js compatibility
  write(data) {
    return this.push(data)
  }

  end(data) {
    let result = ''
    if (data) result = this.push(data)
    result += this.decoder.flush()
    return result
  }
}

function normalizeEncoding(encoding) {
  encoding = encoding.toLowerCase()

  switch (encoding) {
    case 'utf8':
    case 'utf-8':
      return 'utf8'
    case 'ucs2':
    case 'ucs-2':
    case 'utf16le':
    case 'utf-16le':
      return 'utf16le'
    case 'latin1':
    case 'binary':
      return 'latin1'
    case 'base64':
    case 'ascii':
    case 'hex':
      return encoding
    default:
      throw new Error('Unknown encoding: ' + encoding)
  }
}
const b4a = require('b4a')

module.exports = class PassThroughDecoder {
  constructor(encoding) {
    this.encoding = encoding
  }

  get remaining() {
    return 0
  }

  decode(data) {
    return b4a.toString(data, this.encoding)
  }

  flush() {
    return ''
  }
}
const b4a = require('b4a')

/**
 * https://encoding.spec.whatwg.org/#utf-8-decoder
 */
module.exports = class UTF8Decoder {
  constructor() {
    this._reset()
  }

  get remaining() {
    return this.bytesSeen
  }

  decode(data) {
    if (data.byteLength === 0) return ''

    if (this.bytesNeeded === 0 && trailingIncomplete(data, 0) === 0) {
      this.bytesSeen = trailingBytesSeen(data)
      return b4a.toString(data, 'utf8')
    }

    let result = ''
    let start = 0

    if (this.bytesNeeded > 0) {
      while (start < data.byteLength) {
        const byte = data[start]

        if (byte < this.lowerBoundary || byte > this.upperBoundary) {
          result += '\ufffd'
          this._reset()
          break
        }

        this.lowerBoundary = 0x80
        this.upperBoundary = 0xbf
        this.codePoint = (this.codePoint << 6) | (byte & 0x3f)
        this.bytesSeen++
        start++

        if (this.bytesSeen === this.bytesNeeded) {
          result += String.fromCodePoint(this.codePoint)
          this._reset()
          break
        }
      }

      if (this.bytesNeeded > 0) return result
    }

    const trailing = trailingIncomplete(data, start)
    const end = data.byteLength - trailing

    if (end > start) result += b4a.toString(data, 'utf8', start, end)

    for (let i = end; i < data.byteLength; i++) {
      const byte = data[i]

      if (this.bytesNeeded === 0) {
        if (byte <= 0x7f) {
          this.bytesSeen = 0
          result += String.fromCharCode(byte)
        } else if (byte >= 0xc2 && byte <= 0xdf) {
          this.bytesNeeded = 2
          this.bytesSeen = 1
          this.codePoint = byte & 0x1f
        } else if (byte >= 0xe0 && byte <= 0xef) {
          if (byte === 0xe0) this.lowerBoundary = 0xa0
          else if (byte === 0xed) this.upperBoundary = 0x9f
          this.bytesNeeded = 3
          this.bytesSeen = 1
          this.codePoint = byte & 0xf
        } else if (byte >= 0xf0 && byte <= 0xf4) {
          if (byte === 0xf0) this.lowerBoundary = 0x90
          else if (byte === 0xf4) this.upperBoundary = 0x8f
          this.bytesNeeded = 4
          this.bytesSeen = 1
          this.codePoint = byte & 0x7
        } else {
          this.bytesSeen = 1
          result += '\ufffd'
        }

        continue
      }

      if (byte < this.lowerBoundary || byte > this.upperBoundary) {
        result += '\ufffd'
        i--
        this._reset()

        continue
      }

      this.lowerBoundary = 0x80
      this.upperBoundary = 0xbf

      this.codePoint = (this.codePoint << 6) | (byte & 0x3f)
      this.bytesSeen++

      if (this.bytesSeen === this.bytesNeeded) {
        result += String.fromCodePoint(this.codePoint)
        this._reset()
      }
    }

    return result
  }

  flush() {
    const result = this.bytesNeeded > 0 ? '\ufffd' : ''
    this._reset()
    return result
  }

  _reset() {
    this.codePoint = 0
    this.bytesNeeded = 0
    this.bytesSeen = 0
    this.lowerBoundary = 0x80
    this.upperBoundary = 0xbf
  }
}

function trailingIncomplete(data, start) {
  const len = data.byteLength
  if (len <= start) return 0

  const limit = Math.max(start, len - 4)

  let i = len - 1
  while (i > limit && (data[i] & 0xc0) === 0x80) i--

  if (i < start) return 0

  const byte = data[i]

  let needed
  if (byte <= 0x7f) return 0
  if (byte >= 0xc2 && byte <= 0xdf) needed = 2
  else if (byte >= 0xe0 && byte <= 0xef) needed = 3
  else if (byte >= 0xf0 && byte <= 0xf4) needed = 4
  else return 0

  const available = len - i
  return available < needed ? available : 0
}

function trailingBytesSeen(data) {
  const len = data.byteLength
  if (len === 0) return 0

  const last = data[len - 1]

  if (last <= 0x7f) return 0
  if ((last & 0xc0) !== 0x80) return 1

  const limit = Math.max(0, len - 4)

  let i = len - 2
  while (i >= limit && (data[i] & 0xc0) === 0x80) i--

  if (i < 0) return 1

  const first = data[i]

  let needed
  if (first >= 0xc2 && first <= 0xdf) needed = 2
  else if (first >= 0xe0 && first <= 0xef) needed = 3
  else if (first >= 0xf0 && first <= 0xf4) needed = 4
  else return 1

  if (len - i !== needed) return 1

  if (needed >= 3) {
    const second = data[i + 1]
    if (first === 0xe0 && second < 0xa0) return 1
    if (first === 0xed && second > 0x9f) return 1
    if (first === 0xf0 && second < 0x90) return 1
    if (first === 0xf4 && second > 0x8f) return 1
  }

  return 0
}
{
  "name": "text-decoder",
  "version": "1.2.7",
  "description": "Streaming text decoder that preserves multibyte Unicode characters",
  "exports": {
    "./package": "./package.json",
    ".": "./index.js"
  },
  "files": [
    "index.js",
    "lib"
  ],
  "scripts": {
    "format": "prettier --write .",
    "lint": "prettier --check . && lunte",
    "test": "npm run test:node && npm run test:bare",
    "test:node": "brittle-node test.js",
    "test:bare": "brittle-bare test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/text-decoder.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/text-decoder/issues"
  },
  "homepage": "https://github.com/holepunchto/text-decoder#readme",
  "dependencies": {
    "b4a": "^1.6.4"
  },
  "devDependencies": {
    "brittle": "^3.3.2",
    "lunte": "^1.3.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^1.0.0"
  }
}
const { runtime, platform, arch, version } =
  typeof Bare !== 'undefined'
    ? {
        runtime: 'bare',
        platform: global.Bare.platform,
        arch: global.Bare.arch,
        version: global.Bare.version
      }
    : typeof process !== 'undefined'
      ? {
          runtime: 'node',
          platform: global.process.platform,
          arch: global.process.arch,
          version: global.process.version
        }
      : typeof Window !== 'undefined'
        ? { runtime: 'browser', platform: 'unknown', arch: 'unknown', version: 'unknown' }
        : { runtime: 'unknown', platform: 'unknown', arch: 'unknown', version: 'unknown' }

exports.runtime = runtime
exports.version = version
exports.platform = platform
exports.arch = arch
exports.isBare = runtime === 'bare'
exports.isBareKit = exports.isBare && typeof BareKit !== 'undefined'
exports.isPear = typeof Pear !== 'undefined'
exports.isNode = runtime === 'node'
exports.isBrowser = runtime === 'browser'
exports.isWindows = platform === 'win32'
exports.isLinux = platform === 'linux'
exports.isMac = platform === 'darwin'
exports.isIOS = platform === 'ios' || platform === 'ios-simulator'
exports.isAndroid = platform === 'android'
exports.isElectron = typeof process !== 'undefined' && !!global.process.versions?.electron
exports.isElectronRenderer = exports.isElectron && global.process.type === 'renderer'
exports.isElectronWorker = exports.isElectron && global.process.type === 'worker'
{
  "name": "which-runtime",
  "version": "1.4.0",
  "description": "Detect if you are in Bare or Node and which os etc",
  "main": "index.js",
  "files": [
    "index.js"
  ],
  "scripts": {
    "format": "prettier --write .",
    "lint": "prettier --check . && lunte .",
    "test": "npm run test:node && npm run test:bare",
    "test:node": "brittle-node test.js",
    "test:bare": "brittle-bare test.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/holepunchto/which-runtime.git"
  },
  "author": "Holepunch Inc.",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/which-runtime/issues"
  },
  "homepage": "https://github.com/holepunchto/which-runtime",
  "devDependencies": {
    "brittle": "^3.0.0",
    "lunte": "^1.4.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^2.0.0"
  }
}
{
  "name": "hyper-overlay",
  "version": "0.3.13",
  "description": "Content-addressed P2P file distribution over Protomux — FastCDC chunking, multi-source fetch, resumable transfers, zero blob duplication. Files live on disk; the overlay is a thin index and transfer protocol.",
  "type": "module",
  "main": "index.js",
  "imports": {
    "#fs": {
      "bare": "bare-fs",
      "default": "fs"
    },
    "#path": {
      "bare": "bare-path",
      "default": "path"
    },
    "#os": {
      "bare": "bare-os",
      "default": "os"
    },
    "#zlib": {
      "bare": "bare-zlib",
      "default": "zlib"
    }
  },
  "exports": {
    ".": "./index.js",
    "./overlay": "./lib/overlay.js",
    "./protocol": "./lib/protocol.js",
    "./messages": "./lib/messages.js",
    "./chunker": "./lib/chunker.js",
    "./chunk-scheduler": "./lib/chunk-scheduler.js",
    "./transfer": "./lib/transfer.js",
    "./file-index": "./lib/file-index.js",
    "./sync-engine": "./lib/sync-engine.js",
    "./tree": "./lib/tree.js",
    "./pointer": "./lib/pointer.js",
    "./ignore": "./lib/ignore.js",
    "./manifest": "./lib/manifest.js",
    "./package.json": "./package.json"
  },
  "scripts": {
    "test": "brittle-bare test/*.test.js test/integration/*.test.js",
    "test:node": "brittle-node test/*.test.js"
  },
  "engines": {
    "bare": ">=1.16.0"
  },
  "license": "AGPL-3.0",
  "keywords": [
    "p2p",
    "hypercore",
    "hyperbee",
    "protomux",
    "overlay",
    "content-addressed",
    "deduplication",
    "file-transfer",
    "fastcdc",
    "chunking",
    "multi-source",
    "resumable",
    "bare"
  ],
  "dependencies": {
    "b4a": "^1.0.0",
    "bare-fs": "^4.7.2",
    "bare-os": "^3.8.7",
    "bare-path": "^3.0.0",
    "bare-zlib": "^1.3.3",
    "compact-encoding": "^3.2.0",
    "corestore": "^7.0.0",
    "hyperbee": "^2.0.0",
    "hypercore-crypto": "^3.0.0",
    "protomux": "^3.0.0",
    "ready-resource": "^1.0.0",
    "sodium-universal": "^5.0.1",
    "streamx": "^2.0.0"
  },
  "devDependencies": {
    "brittle": "^3.0.0",
    "hyperswarm": "^4.0.0",
    "@hyperswarm/testnet": "^3.1.4"
  }
}
{
  "name": "pgh-runtime",
  "version": "0.1.4",
  "description": "Per-app OTA updater for pgh-installed apps — polls a release source, materializes new trees into the pgh store",
  "type": "module",
  "main": "index.js",
  "exports": {
    ".": "./index.js"
  },
  "imports": {
    "#node:events": {
      "bare": "bare-events",
      "default": "events"
    }
  },
  "scripts": {
    "test": "brittle test/*.test.js"
  },
  "license": "AGPL-3.0",
  "dependencies": {
    "bare-events": "^2.0.0",
    "hyper-overlay": "file:../hyper-overlay"
  },
  "devDependencies": {
    "brittle": "^3.0.0",
    "hypercore-crypto": "^3.0.0"
  },
  "overrides": {
    "bare-fs": "^4.7.2"
  }
}
/**
 * NetworkFetcher — Hyperswarm-backed { getTree, getContent, getLatestRelease }
 *
 * Phase 0.5 follow-on. Lives between pgh-runtime / installTree (which
 * expect a fetcher shape) and the wire (Hyperswarm + OverlayProtocol
 * tree-request / content-request messages, plus chunk flow for content).
 *
 * Server side: hyper-deploy's DeployServer (0.5b + enableTrees) attaches
 * OverlayProtocol to each incoming connection and has trees +
 * contentHashPaths pre-populated. The client just needs to connect on
 * the same topic and open a protomux channel.
 *
 * Usage:
 *   const fetcher = new NetworkFetcher({ topic: '...hex...', timeout: 15000 })
 *   await fetcher.ready()
 *   const tree = await fetcher.getTree(rootHash)
 *   const content = await fetcher.getContent(contentHash)
 *   await fetcher.close()
 *
 * Limitations (scope boundary — further refinement tracked separately):
 *  - getLatestRelease currently reads from the LOCAL ReleaseRegistry.
 *    Replication of the publisher's remote registry is a follow-on.
 *  - Content transfer uses the existing chunk flow triggered by the
 *    server's _onContentRequest handler — getContent awaits the next
 *    transferComplete that lands locally.
 */

import fs from '#node:fs'
import path from '#node:path'
import { homedir, tmpdir as osTmpdir } from '#node:os'
import b4a from 'b4a'

import Hyperswarm from 'hyperswarm'
import Protomux from 'protomux'
import Corestore from 'corestore'
import crypto from 'hypercore-crypto'

import { FileIndex } from 'hyper-overlay/file-index'
import { TransferManager } from 'hyper-overlay/transfer'
import { OverlayProtocol } from 'hyper-overlay/protocol'
import { computeTreeHash } from 'hyper-overlay/tree'

import { ReleaseRegistry } from './release-registry.js'

const DEFAULT_TIMEOUT = 30000
const DEFAULT_LOCAL_REGISTRY = path.join(homedir(), '.config', 'pgh', 'release-registry')

export class NetworkFetcher {
  /**
   * @param {object} opts
   * @param {string|Buffer} opts.topic          — 32-byte discovery key
   * @param {number} [opts.timeout]             — per-request timeout (ms)
   * @param {string} [opts.storeDir]            — temp corestore path
   * @param {Hyperswarm} [opts.swarm]           — reuse an existing swarm
   * @param {string} [opts.localRegistryDir]    — for getLatestRelease (local lookup)
   * @param {string} [opts.destDir]             — where content chunks land; default tmp
   */
  constructor (opts = {}) {
    if (!opts.topic) throw new Error('NetworkFetcher requires opts.topic')
    this.topic = typeof opts.topic === 'string' ? b4a.from(opts.topic, 'hex') : opts.topic
    this.timeout = opts.timeout || DEFAULT_TIMEOUT
    this.localRegistryDir = opts.localRegistryDir || DEFAULT_LOCAL_REGISTRY
    this.swarm = opts.swarm || null
    this._ownSwarm = !this.swarm
    this._storeDir = opts.storeDir || null
    this._destDir = opts.destDir || null

    this._store = null
    this._fileIndex = null
    this._transferManager = null
    this._protocol = null
    this._peers = [] // all peers discovered on the topic (in connection order)
    this._peerResolve = null
    this._pendingContent = new Map() // contentHash → { resolve, reject, timer, diskPath }
    this._ready = false

    // Per-peer timeouts for individual requests. Tree lookups are small
    // RPCs — if a peer doesn't respond in a few seconds it doesn't have
    // the tree. Content is an actual chunk stream, so it legitimately
    // takes longer; we keep that timeout at the full request budget.
    // The big speedup comes from `_markPeerGood` promoting responsive
    // peers to the front of the list after their first success.
    this._perPeerTreeTimeout = Math.min(opts.perPeerTreeTimeout || 5000, this.timeout)
    this._perPeerContentTimeout = opts.perPeerContentTimeout || this.timeout
    // Silent-peer watchdog: how long an in-flight content request can go
    // without ANY chunk arrival before we declare the peer silent and
    // fail the request. Companion to fail-fast-on-disconnect (which only
    // catches outright connection death). 30s is enough to ride out
    // brief mux contention; longer than that and the peer is stuck.
    this._idleTimeout = opts.idleTimeout || 30000
  }

  // Back-compat: _peer was the first peer; expose the first peer for callers
  // that reference it (tests, diagnostics). Real tree/content requests walk
  // _peers so they survive stale DHT advertisements.
  get _peer () { return this._peers[0] || null }

  /**
   * Promote a peer to the front of our peer list after a successful
   * request. This is the key to fast multi-peer handling: once we know
   * a peer responds with real data, subsequent calls try it FIRST
   * instead of burning per-peer timeouts on dead peers every call.
   */
  _markPeerGood (peer) {
    const idx = this._peers.indexOf(peer)
    if (idx > 0) {
      this._peers.splice(idx, 1)
      this._peers.unshift(peer)
    }
  }

  async ready () {
    if (this._ready) return
    // Memoize the in-flight promise. Without this, concurrent callers
    // (e.g. server.js's fire-and-forget `fetcher.ready().then(...)` AND
    // pgh-runtime's `installTree` → `getTree` → `await this.ready()`)
    // each run `new Corestore(this._storeDir)` on the same path. The
    // device-file FDLock admits only one; the loser throws "File
    // descriptor could not be locked" and leaves `_store` in a closed
    // state, so subsequent reads fail with "Corestore is closed".
    if (this._readyPromise) return this._readyPromise
    this._readyPromise = this._doReady()
    try {
      await this._readyPromise
    } catch (err) {
      this._readyPromise = null
      throw err
    }
  }

  async _doReady () {
    if (!this._storeDir) {
      this._storeDir = fs.mkdtempSync(path.join(osTmpdir(), 'pgh-fetcher-'))
    }
    if (!this._destDir) {
      this._destDir = fs.mkdtempSync(path.join(osTmpdir(), 'pgh-fetcher-dest-'))
    }

    this._store = new Corestore(this._storeDir)
    await this._store.ready()
    this._fileIndex = new FileIndex(this._store)
    await this._fileIndex.ready()
    this._transferManager = new TransferManager(this._fileIndex)

    const stubSyncEngine = {
      _fileIndex: this._fileIndex,
      feedKey: '0'.repeat(64),
      feed: { length: 0, async get () { return null } },
      async markSynced () {},
      async logPut () {}
    }
    this._protocol = new OverlayProtocol(
      stubSyncEngine,
      this._transferManager,
      {
        key: this.topic,
        autoSync: false,
        // Fires when the client-side chunk flow finalizes a file.
        // We use it to resolve getContent promises keyed by synthetic path.
        onSynced: (info) => this._onSynced(info),
        // Fires for EVERY chunk arrival. We reset the per-pending idle
        // timer so a peer that connects then goes silent mid-stream is
        // detected within `_idleTimeout` ms (not the 5-min global cap).
        onChunkProgress: (info) => this._onChunkProgress(info)
      }
    )

    if (!this.swarm) this.swarm = new Hyperswarm()
    this.swarm.on('connection', (conn) => this._onConnection(conn))
    this.swarm.join(this.topic, { server: false, client: true })
    await this.swarm.flush()

    // Wait for at least one peer before resolving
    if (this._peers.length === 0) {
      await Promise.race([
        new Promise((resolve) => { this._peerResolve = resolve }),
        new Promise((_, reject) => setTimeout(
          () => reject(new Error('NetworkFetcher: no peer within ' + this.timeout + 'ms')),
          this.timeout
        ))
      ])
    }

    this._ready = true
  }

  _onConnection (conn) {
    const mux = Protomux.from(conn)
    const peer = this._protocol.attach(mux)
    if (!peer) return

    // Track ALL peers on the topic. The DHT may return multiple peers
    // (stale advertisements, other seeds, etc.) and only SOME have the
    // tree/content we're looking for. requestTree + requestContent
    // iterate this list until a peer returns non-empty data.
    this._peers.push(peer)

    // Connection-death handling. Without this, a peer that disconnects
    // mid-chunk-stream leaves _pendingContent entries waiting on the
    // 5-min global timeout — the user sees this as a "stall" (chunks
    // streaming, then nothing). 2026-05-13 observed:
    //   tabertron logs `deploy-server error (non-fatal) [ECONNRESET]`
    //   stortron's getContent just sits there waiting for transferComplete
    // Listen for the underlying conn close + fail any pending requests
    // that were tied to this peer. Caller retries fast on next peer.
    const handleClose = () => {
      this._peers = this._peers.filter(p => p !== peer)
      for (const pending of this._pendingContent.values()) {
        if (pending.peer === peer) {
          pending.finish(new Error('peer disconnected mid-stream (' +
            pending.contentHash.slice(0, 12) + ')'))
        }
      }
    }
    // Hyperswarm streams emit 'close' on disconnect; protomux mux also
    // emits 'close' when the underlying stream dies. Attach to both for
    // belt-and-suspenders.
    conn.on('close', handleClose)
    conn.on('error', (err) => {
      // Errors here are typically wire-level (ECONNRESET, ETIMEDOUT).
      // We don't fail-fast on error alone — wait for the matching
      // 'close' event so we don't double-fire. The on-error listener
      // exists just to keep Node from crashing.
    })

    if (this._peerResolve) { this._peerResolve(peer); this._peerResolve = null }
  }

  async close () {
    for (const pending of this._pendingContent.values()) {
      clearTimeout(pending.timer)
      pending.reject(new Error('NetworkFetcher closing'))
    }
    this._pendingContent.clear()

    if (this._protocol) {
      try { this._protocol.destroy() } catch {}
      this._protocol = null
    }
    if (this._fileIndex) { try { await this._fileIndex.close() } catch {} }
    if (this._store) { try { await this._store.close() } catch {} }
    if (this._ownSwarm && this.swarm) {
      try { await this.swarm.destroy() } catch {}
      this.swarm = null
    }
    this._ready = false
  }

  /**
   * Fetch a tree by its hash, walking all known peers until one returns
   * a non-empty response.
   *
   * hyper-overlay's `_onTreeRequest` (server side) replies with
   * `entries: []` on lookup miss. Combined with DHT promiscuity — the
   * same topic may have multiple advertised peers, some stale/unrelated,
   * some with data — a single-peer fetch is unreliable: whichever peer
   * happens to connect first might not have the tree.
   *
   * Strategy: try peers in connection order. First peer to return
   * non-empty entries wins. If all peers return empty, treat as
   * not-found (throw) — the caller knows the tree SHOULD have content
   * (it's in a lockfile) so empty is a failure signal, not a genuinely
   * empty directory.
   *
   * @param {string} hash
   * @returns {Promise<{ entries, size }>}
   */
  async getTree (hash) {
    if (!this._ready) await this.ready()
    if (this._peers.length === 0) throw new Error('NetworkFetcher: no peer available')

    // Walk peers, accept the first VERIFIED response. hyper-overlay's
    // protocol returns `entries: []` when a peer doesn't have a tree —
    // that's a valid negative response ("try elsewhere"), not "this
    // tree is empty." Content-address verification distinguishes:
    // computeTreeHash(entries) must equal the requested hash.
    let lastErr = null
    const tried = new Set()

    for (let round = 0; round < 2; round++) {
      for (const peer of this._peers) {
        if (tried.has(peer)) continue
        tried.add(peer)
        try {
          const entries = await this._protocol.requestTree(peer, hash, { timeout: this._perPeerTreeTimeout })
          const actual = computeTreeHash(entries || [])
          if (actual === hash) {
            this._markPeerGood(peer)
            const size = entries.reduce((a, e) => a + (e.size || 0), 0)
            return { entries, size }
          }
          // Hash mismatch → this peer doesn't have it. Try next.
        } catch (err) {
          lastErr = err
        }
      }
      // Second round: wait for additional peers to arrive, then retry
      // any new ones (plus the old ones if they haven't been tried yet).
      if (round === 0) {
        await new Promise(r => setTimeout(r, 2000))
        tried.clear()
      }
    }

    throw new Error('NetworkFetcher: tree ' + hash.slice(0, 12) + '… not found on any of ' + this._peers.length + ' peer(s)' + (lastErr ? ' (last err: ' + lastErr.message + ')' : ''))
  }

  /**
   * Fetch a file's content by its content hash.
   *
   * Uses the synthetic-path convention in hyper-overlay/v2 content-request:
   * server responds with chunkHashes whose `path` is
   * `"content:" + contentHash`. Client pre-registers its target disk
   * path under that key in `_filePaths` so the chunk flow writes to
   * our destination; then resolves when transferComplete arrives with
   * the matching synthetic path.
   *
   * @param {string} contentHash — hex blake2b hash of the file content
   * @returns {Promise<Buffer>}
   */
  async getContent (contentHash) {
    if (!this._ready) await this.ready()
    if (this._peers.length === 0) throw new Error('NetworkFetcher: no peer available')

    // Multi-source: fan the request out to ALL peers at once via the
    // ChunkScheduler (hyper-overlay `fetchContent`). It assigns distinct
    // chunks per peer, fails over on disconnect, and hash-verifies every
    // chunk — pulling one blob from many seeders in parallel (the torrent
    // path) instead of the legacy one-peer-at-a-time failover in
    // `_getContentFromPeer`. Proven by hyper-overlay's
    // overlay-v2-fetch.test.js ("chunks fetched from MULTIPLE peers").
    // Retry once to pick up peers that arrive late on the topic.
    const destPath = path.join(this._destDir, 'blob-' + contentHash + '-' + Date.now() + '-' + Math.random().toString(36).slice(2, 8))
    let lastErr = null

    for (let round = 0; round < 2; round++) {
      const peers = this._peers.slice()
      if (peers.length > 0) {
        try {
          await this._protocol.fetchContent(contentHash, peers, {
            destPath,
            timeout: this._perPeerContentTimeout
          })
          // The chunk flow verifies each chunk; verify the full
          // concatenation against the advertised content hash here so no
          // caller of getContent ever sees un-verified bytes.
          const data = fs.readFileSync(destPath)
          const actual = crypto.data(data).toString('hex')
          if (actual !== contentHash) {
            throw Object.assign(
              new Error('content-hash mismatch: expected ' + contentHash.slice(0, 12) + '…, got ' + actual.slice(0, 12) + '…'),
              { code: 'EHASHMISMATCH', expected: contentHash, actual }
            )
          }
          try { fs.unlinkSync(destPath) } catch {}
          return data
        } catch (err) {
          lastErr = err
          try { fs.unlinkSync(destPath) } catch {}
        }
      }
      // Wait briefly for additional peers to arrive on the topic, then retry.
      if (round === 0) await new Promise(r => setTimeout(r, 2000))
    }

    throw new Error('NetworkFetcher: content ' + contentHash.slice(0, 12) + '… not found on any of ' + this._peers.length + ' peer(s)' + (lastErr ? ' (last err: ' + lastErr.message + ')' : ''))
  }

  async _getContentFromPeer (peer, contentHash) {
    const syntheticPath = 'content:' + contentHash
    // Per-peer destPath so concurrent attempts don't clobber each other.
    const destPath = path.join(this._destDir, 'blob-' + contentHash + '-' + Date.now() + '-' + Math.random().toString(36).slice(2, 8))

    // Pre-register destination so _onChunkHashes + chunk-data handlers
    // write to the right place when chunks arrive.
    this._protocol._filePaths.set(syntheticPath, destPath)

    return new Promise((resolve, reject) => {
      let settled = false
      const finish = (err, result) => {
        if (settled) return
        settled = true
        clearTimeout(timer)
        clearTimeout(pending.idleTimer)
        this._pendingContent.delete(contentHash)
        if (err) reject(err); else resolve(result)
      }

      const timer = setTimeout(() => {
        finish(new Error('content request timed out on peer: ' + contentHash.slice(0, 12)))
      }, this._perPeerContentTimeout)

      // Silent-peer watchdog: reset on each chunk arrival via
      // _onChunkProgress. If no chunk arrives within _idleTimeout the
      // request fails fast and getContent moves on to the next peer.
      const idleFire = () => {
        finish(new Error('peer went silent mid-stream after ' +
          (pending.bytesReceived || 0) + ' byte(s): ' + contentHash.slice(0, 12)))
      }

      const pending = {
        syntheticPath, destPath, finish, timer, peer, contentHash,
        bytesReceived: 0,
        idleTimer: setTimeout(idleFire, this._idleTimeout),
        idleFire
      }

      // Tag the pending entry with the peer it was sent to. _onConnection's
      // close handler uses this to fail-fast any pending requests when the
      // connection dies (the dominant LAN-stall pattern observed
      // 2026-05-13: tabertron pushed 7 chunks, ECONNRESET, stortron sat
      // waiting on the 5-min global timeout).
      this._pendingContent.set(contentHash, pending)
      this._protocol.requestContent(peer, contentHash, null)
    })
  }

  /**
   * Reset the idle timer for any pending content request matching the
   * synthetic path of the arriving chunk. Companion to fail-fast on
   * disconnect: this fires when the peer is alive but silent.
   */
  _onChunkProgress (info) {
    if (!info || !info.path || !info.path.startsWith('content:')) return
    const contentHash = info.path.slice('content:'.length)
    const pending = this._pendingContent.get(contentHash)
    if (!pending) return
    pending.bytesReceived += (info.bytes || 0)
    clearTimeout(pending.idleTimer)
    pending.idleTimer = setTimeout(pending.idleFire, this._idleTimeout)
  }

  /**
   * Called by OverlayProtocol when a file completes on the receiver
   * side (direction === 'pull'). Resolves any pending getContent whose
   * synthetic path matches — AFTER content-address verification.
   *
   * hyper-overlay's chunk flow doesn't verify the final concatenation
   * against the advertised hash (that's the caller's responsibility in
   * this protocol). We do it here so no caller of getContent ever sees
   * un-verified bytes.
   */
  _onSynced (info) {
    if (info.direction !== 'pull') return
    if (!info.path || !info.path.startsWith('content:')) return
    const contentHash = info.path.slice('content:'.length)
    const pending = this._pendingContent.get(contentHash)
    if (!pending) return
    try {
      const data = fs.readFileSync(pending.destPath)
      const actual = crypto.data(data).toString('hex')
      if (actual !== contentHash) {
        pending.finish(Object.assign(
          new Error('content-hash mismatch: expected ' + contentHash.slice(0, 12) + '…, got ' + actual.slice(0, 12) + '…'),
          { code: 'EHASHMISMATCH', expected: contentHash, actual }
        ))
        return
      }
      pending.finish(null, data)
    } catch (err) {
      pending.finish(err)
    }
  }

  /**
   * Look up the latest release for an app name.
   *
   * MVP: reads from the LOCAL ReleaseRegistry. Remote replication of
   * the publisher's registry is follow-on work — until then, callers
   * that want network-side release lookup need to ship the registry
   * core key separately (e.g. via peer config).
   *
   * @param {string} name
   * @returns {Promise<object|null>}
   */
  async getLatestRelease (name) {
    if (!fs.existsSync(this.localRegistryDir)) return null
    const store = new Corestore(this.localRegistryDir)
    const reg = new ReleaseRegistry(store)
    try {
      await reg.ready()
      return await reg.latest(name)
    } finally {
      try { await reg.close() } catch {}
      try { await store.close() } catch {}
    }
  }
}
/**
 * ReleaseRegistry — Autobase-backed index of published releases.
 *
 * Multi-writer. Each publisher runs a registry; linked devices (or
 * explicitly added writers) all append to the same logical registry.
 * Clients replicate the registry by its bootstrap key over Hyperswarm
 * to resolve app names → tree hashes + deploy locators.
 *
 * Autobase shape (follows hyperident's IdentityRegistry pattern):
 *   - Base: autobase keyed by the first writer's hypercore
 *   - Writers: append op records (JSON-encoded)
 *   - Apply: single apply function translates ops → Hyperbee view
 *   - View: Hyperbee with the same keyspace as the earlier single-writer
 *     implementation so downstream readers (getLatest, list, etc.) don't
 *     change
 *
 * Ops:
 *   { type: 'add-release', entry }       — append a release record
 *   { type: 'remove-release', name, version }
 *   { type: 'add-writer', publicKey }    — grant another device write
 *
 * Entry schema (unchanged):
 *   name, version, treeHash, serverKey, upgrade, deps, publishedAt,
 *   publisherDevicePubkey, sig?, acceptedConflicts?
 *
 * Hyperbee view keys (unchanged):
 *   release:<name>:<version>   → entry
 *   latest:<name>              → { version, publishedAt }
 *   by-tree:<treeHash>         → { name, version }
 */

import Autobase from 'autobase'
import Hyperbee from 'hyperbee'
import crypto from 'hypercore-crypto'
import b4a from 'b4a'
import sodium from 'sodium-native'

export const OP_ADD_RELEASE = 'add-release'
export const OP_REMOVE_RELEASE = 'remove-release'
export const OP_ADD_WRITER = 'add-writer'

export class ReleaseRegistry {
  /**
   * @param {Corestore} corestore
   * @param {object} [opts]
   * @param {string} [opts.namespace='release-registry']  — corestore sub-namespace
   * @param {Buffer|string} [opts.bootstrap]              — hex or Buffer autobase key. If
   *                                                        omitted, creates a NEW registry
   *                                                        and this node becomes the
   *                                                        first writer.
   */
  constructor (corestore, opts = {}) {
    this._store = corestore.namespace(opts.namespace || 'release-registry')
    this._bootstrap = opts.bootstrap
      ? (Buffer.isBuffer(opts.bootstrap) ? opts.bootstrap : b4a.from(opts.bootstrap, 'hex'))
      : null
    this._base = null
    this._view = null
    this._ready = false
  }

  async ready () {
    if (this._ready) return

    const apply = async (nodes, view, base) => {
      for (const node of nodes) {
        let op
        try { op = JSON.parse(node.value.toString()) } catch { continue }

        if (op.type === OP_ADD_RELEASE) {
          const e = op.entry
          if (!e || !e.name || !e.version || !e.treeHash) continue
          await view.put('release:' + e.name + ':' + e.version, e)

          // Update latest pointer if this entry is newer
          const latestKey = 'latest:' + e.name
          const existing = await view.get(latestKey)
          if (!existing || compareTimestamps(e.publishedAt, existing.value.publishedAt) > 0) {
            await view.put(latestKey, { version: e.version, publishedAt: e.publishedAt })
          }

          await view.put('by-tree:' + e.treeHash, { name: e.name, version: e.version })
        }

        if (op.type === OP_REMOVE_RELEASE) {
          const key = 'release:' + op.name + ':' + op.version
          await view.del(key)
          const latestKey = 'latest:' + op.name
          const latest = await view.get(latestKey)
          if (latest && latest.value.version === op.version) {
            // Roll back to the next most recent release for this name
            let fallback = null
            const prefix = 'release:' + op.name + ':'
            for await (const e of view.createReadStream({ gt: prefix, lt: prefix + '\xff' })) {
              if (!fallback || compareTimestamps(e.value.publishedAt, fallback.publishedAt) > 0) {
                fallback = e.value
              }
            }
            if (fallback) {
              await view.put(latestKey, { version: fallback.version, publishedAt: fallback.publishedAt })
            } else {
              await view.del(latestKey)
            }
          }
        }

        if (op.type === OP_ADD_WRITER && op.publicKey) {
          try {
            await base.addWriter(b4a.from(op.publicKey, 'hex'))
          } catch {
            // autobase throws if already a writer — safe to ignore
          }
        }
      }
    }

    this._base = new Autobase(this._store, this._bootstrap, {
      apply,
      open: (store) => new Hyperbee(store.get({ name: 'releases', valueEncoding: 'json' }), {
        keyEncoding: 'utf-8',
        valueEncoding: 'json'
      }),
      valueEncoding: 'binary',
      // Canonical keet multi-writer opts. Single-writer (the common
      // publisher case) ignores these — they only matter once a publisher
      // adds a mirror/linked-device writer, at which point optimistic
      // append + periodic ack keeps the writers converging (per the keet
      // pattern in multi-writer-autobase memory). Harmless before then.
      optimistic: true,
      ackInterval: 1000,
      ackThreshold: 4
    })

    await this._base.ready()
    this._view = this._base.view
    this._ready = true
  }

  async close () {
    if (this._base) await this._base.close()
    this._ready = false
  }

  // ── Keys / identity ─────────────────────────────────────────

  get key () { return this._base && this._base.key }
  get keyHex () {
    const k = this.key
    return k ? b4a.toString(k, 'hex') : null
  }
  get localWriterKey () {
    return this._base && this._base.local && this._base.local.key
  }
  get isWriter () {
    return !!(this._base && this._base.writable)
  }

  // ── Ops (writer-only) ────────────────────────────────────────

  async add (entry) {
    if (!this._ready) await this.ready()
    if (!this.isWriter) throw new Error('not a writer on this registry')
    if (!entry || !entry.name || !entry.version || !entry.treeHash) {
      throw new Error('release entry requires name, version, treeHash')
    }

    const normalized = {
      name: entry.name,
      version: entry.version,
      // runtime drives the materializer (node vs godot vs bare→node alias)
      // and the launcher shebang at install time. Drop-on-store would
      // make installers fall back to 'node' and produce wrong launchers
      // (e.g. `exec node` for a bare app → require.addon throws).
      runtime: entry.runtime || 'node',
      treeHash: entry.treeHash,
      serverKey: entry.serverKey || null,
      upgrade: entry.upgrade || null,
      deps: entry.deps || [],
      publishedAt: entry.publishedAt || new Date().toISOString(),
      publisherDevicePubkey: entry.publisherDevicePubkey || null,
      // MUST be carried: `sig` covers every field except itself, so a field
      // dropped here is a field the stored entry no longer hashes to and the
      // signature stops verifying. Anything added to the signed entry has to
      // be added to this list too (org #144).
      publisherChain: entry.publisherChain || null,
      sig: entry.sig || null,
      acceptedConflicts: entry.acceptedConflicts || null
    }

    await this._base.append(JSON.stringify({ type: OP_ADD_RELEASE, entry: normalized }))
    await this._base.update()
    return normalized
  }

  async remove (name, version) {
    if (!this._ready) await this.ready()
    if (!this.isWriter) throw new Error('not a writer on this registry')
    await this._base.append(JSON.stringify({ type: OP_REMOVE_RELEASE, name, version }))
    await this._base.update()
  }

  async addWriter (publicKey) {
    if (!this._ready) await this.ready()
    if (!this.isWriter) throw new Error('not a writer on this registry')
    const hex = Buffer.isBuffer(publicKey) ? publicKey.toString('hex') : publicKey
    await this._base.append(JSON.stringify({ type: OP_ADD_WRITER, publicKey: hex }))
    await this._base.update()
  }

  // ── Queries (readable by anyone with the registry key) ───────

  async get (name, version) {
    if (!this._ready) await this.ready()
    const entry = await this._view.get('release:' + name + ':' + version)
    if (!entry) return null
    assertEntryIntegrity(entry.value)
    return entry.value
  }

  async latest (name) {
    if (!this._ready) await this.ready()
    const latest = await this._view.get('latest:' + name)
    if (!latest) return null
    return this.get(name, latest.value.version)
  }

  async lookupByTree (treeHash) {
    if (!this._ready) await this.ready()
    const ref = await this._view.get('by-tree:' + treeHash)
    if (!ref) return null
    return this.get(ref.value.name, ref.value.version)
  }

  async list (nameFilter) {
    if (!this._ready) await this.ready()
    const prefix = nameFilter ? 'release:' + nameFilter + ':' : 'release:'
    const out = []
    for await (const entry of this._view.createReadStream({ gt: prefix, lt: prefix + '\xff' })) {
      out.push(entry.value)
    }
    return out
  }

  // ── Replication handle ───────────────────────────────────────

  replicate (stream) {
    return this._base.replicate(stream)
  }

  /**
   * Pull updates from the network (call after the underlying cores
   * have replicated new blocks).
   */
  async update () {
    if (!this._ready) await this.ready()
    return this._base.update()
  }
}

// ── Canonical signing (unchanged) ──────────────────────────────

export function canonicalBytes (entry) {
  const { sig, ...rest } = entry // eslint-disable-line no-unused-vars
  const keys = Object.keys(rest).sort()
  const ordered = {}
  for (const k of keys) ordered[k] = rest[k]
  return Buffer.from(JSON.stringify(ordered))
}

export function signEntry (entry, secretKey) {
  const sig = Buffer.alloc(64)
  sodium.crypto_sign_detached(sig, canonicalBytes(entry), secretKey)
  return { ...entry, sig: sig.toString('hex') }
}

/**
 * Verify a release entry's signature against the device key it names.
 *
 * Transitional by design (org #144): an entry with NO `sig` passes. Every
 * release published before signing was wired carries `sig: null`, and
 * refusing those would make every installed app un-updatable and every
 * existing release uninstallable. What this closes is the case that actually
 * matters now — an entry that carries a signature which does not verify.
 *
 * Enforcement (refuse unsigned) is a later flip, once publishers have
 * re-published. Until then this is a tamper check, not an authenticity
 * guarantee: an attacker who can write to the registry can still simply omit
 * the signature.
 *
 * @returns {{ ok: boolean, signed: boolean, reason?: string }}
 */
export function verifyReleaseEntry (entry) {
  if (!entry) return { ok: false, signed: false, reason: 'no entry' }
  if (!entry.sig) return { ok: true, signed: false }

  const pubkey = entry.publisherDevicePubkey
  if (!pubkey) {
    return { ok: false, signed: true, reason: 'entry is signed but names no publisherDevicePubkey' }
  }

  let ok = false
  try {
    ok = verifyEntry(entry, pubkey)
  } catch (err) {
    return { ok: false, signed: true, reason: 'signature check threw: ' + err.message }
  }
  if (!ok) {
    return {
      ok: false,
      signed: true,
      reason: 'signature does not verify against publisherDevicePubkey ' + String(pubkey).slice(0, 12) + '…'
    }
  }
  return { ok: true, signed: true }
}

/**
 * Throwing form, used on every read path so no call site can forget it. A
 * signed entry that fails verification is not a release, it is a tamper — and
 * a loud failure degrades to "not found" in callers that catch, which is the
 * safe direction.
 */
export function assertEntryIntegrity (entry) {
  const v = verifyReleaseEntry(entry)
  if (!v.ok) {
    throw Object.assign(
      new Error(
        'release ' + (entry && entry.name) + '@' + (entry && entry.version) +
        ' failed verification: ' + v.reason
      ),
      { code: 'EBADRELEASESIG', entry }
    )
  }
  return entry
}

export function verifyEntry (entry, publisherPublicKey) {
  if (!entry.sig) return false
  const sig = b4a.from(entry.sig, 'hex')
  const pubkey = Buffer.isBuffer(publisherPublicKey)
    ? publisherPublicKey
    : b4a.from(publisherPublicKey, 'hex')
  return sodium.crypto_sign_verify_detached(sig, canonicalBytes(entry), pubkey)
}

function compareTimestamps (a, b) {
  if (a === b) return 0
  return a > b ? 1 : -1
}
/**
 * resolveRelease — "what is the latest release of <name>?", with or without a
 * pgh-sidecar on this machine.
 *
 * Extracted verbatim in behaviour from commands/app.js, where it was the install
 * path's private helper. It is here because it is the missing half of embedded
 * OTA: NetworkFetcher's own header says
 *
 *     getLatestRelease currently reads from the LOCAL ReleaseRegistry.
 *     Replication of the publisher's remote registry is a follow-on.
 *
 * and that follow-on turned out to already exist — as this function, reachable
 * only from `pgh app install`. A standalone app (no sidecar, no CLI) could fetch
 * trees and content over Hyperswarm but had no way to learn WHICH tree to fetch.
 *
 * TWO ORDERED PATHS, and the order is not a preference:
 *
 *   1. the sidecar's live replica, when ~/.pgh/svc.sock exists;
 *   2. otherwise replicate the publisher's registry directly by its bootstrap
 *      key, over Hyperswarm.
 *
 * The sidecar goes first because BOTH paths open the same
 * ~/.config/pgh/registries/<key16> Corestore, and that store takes an EXCLUSIVE
 * FD-LOCK. On a machine where the sidecar is also re-opening it (its in-process
 * autoupdate does), a direct open loses the race and dies with "File descriptor
 * could not be locked". runtime-factory's createRuntime routes getLatestRelease
 * through the sidecar for exactly this reason. Path 2 is for machines that have
 * no sidecar at all — which is every standalone install, and every Windows one.
 *
 * THE CALLER OWNS THE CLEANUP. `cleanup()` closes registry, swarm AND store, in
 * that order. ReleaseRegistry.close() closes only the autobase, so dropping the
 * handle without calling this orphans the store's fd-lock for the life of the
 * process and every later resolve in it fails.
 */

import process from '#node:process' // NOT a global under bare — process.env below needs it
import fs from '#node:fs'
import path from '#node:path'
import { homedir } from '#node:os'
import b4a from 'b4a'
import { Client } from 'hyper-svc'

import { ReleaseRegistry } from './release-registry.js'

const SIDECAR_SOCK = process.env.PGH_SIDECAR_SOCK ||
  path.join(homedir(), '.pgh', 'svc.sock')

/** Where a directly-replicated registry's corestore lives. */
export function registryStoreDir (registryKeyHex) {
  return path.join(homedir(), '.config', 'pgh', 'registries', registryKeyHex.slice(0, 16))
}

/**
 * @param {object} opts
 * @param {string} opts.registryKeyHex  64-char registry (autobase bootstrap) key
 * @param {string} opts.name            app name
 * @param {string} [opts.version]       exact version; omitted = latest
 * @param {boolean} [opts.preferSidecar=true]  try the sidecar's replica first
 * @param {number} [opts.settleMs=500]  post-flush settle before reading the view
 * @param {string} [opts.storeDir]      where to keep the registry replica. Defaults to a path under
 *   the home directory, which does not exist on Android — an embedder must pass its own.
 * @returns {Promise<{ entry: object|null, via: string, cleanup: function }>}
 */
export async function resolveRelease (opts = {}) {
  const { registryKeyHex, name, version } = opts
  if (!registryKeyHex || registryKeyHex.length !== 64) {
    throw new Error('resolveRelease requires a 64-char registryKeyHex')
  }
  if (!name) throw new Error('resolveRelease requires a name')

  const preferSidecar = opts.preferSidecar !== false
  const settleMs = opts.settleMs != null ? opts.settleMs : 500

  const noop = async () => {}
  let entry = null

  if (preferSidecar && fs.existsSync(SIDECAR_SOCK)) {
    let c = null
    try {
      c = await Client.connectUds(SIDECAR_SOCK)
      const res = await c.call('resolve-release', {
        registryKey: registryKeyHex, name, version: version || undefined
      })
      if (res && res.found && res.entry) entry = res.entry
    } catch {
      // Sidecar present but unhelpful (down mid-restart, older build without the
      // RPC). Fall through and replicate directly — worst case that fails on the
      // fd-lock, which is a clearer error than a silent miss.
    } finally {
      try { if (c) await c.close() } catch {}
    }
    if (entry) return { entry, via: 'sidecar', cleanup: noop }
  }

  const Corestore = (await import('corestore')).default
  const Hyperswarm = (await import('hyperswarm')).default

  let store = null
  let registry = null
  let swarm = null
  const cleanup = async () => {
    try { if (registry) await registry.close() } catch {}
    try { if (swarm) await swarm.destroy() } catch {}
    try { if (store) await store.close() } catch {}
  }

  // ⛔ CALLER-SUPPLIED OR THE DEFAULT, because the default assumes a HOME DIRECTORY and a phone has
  // none. On Android this resolved to a path that cannot be stat'd, so `resolveRelease` threw
  // ENOENT, `swarmFetcher` caught it, and OTA reported "no swarm fetcher — updates disabled this
  // boot" — a handset that could never update, with the reason visible nowhere until the app was
  // taught to report its own last OTA line.
  //
  // ⚠ Same class as a binary assumed on PATH and a vault assumed under ~: a library that picks a
  // path from the environment works everywhere the environment has one. An embedder knows where its
  // state goes; ask it.
  const dir = opts.storeDir || registryStoreDir(registryKeyHex)
  fs.mkdirSync(dir, { recursive: true })
  store = new Corestore(dir)
  try {
    registry = new ReleaseRegistry(store, { bootstrap: b4a.from(registryKeyHex, 'hex') })
    await registry.ready()
    swarm = new Hyperswarm()
    swarm.on('connection', (conn) => store.replicate(conn))
    // client-only: we are reading someone else's registry, not serving ours.
    swarm.join(registry._base.discoveryKey, { server: false, client: true })
    await swarm.flush()
    // The view is built by autobase's apply as blocks arrive; flush returns when
    // peers are connected, not when the log has caught up.
    await new Promise(r => setTimeout(r, settleMs))
    await registry.update()
    entry = version ? await registry.get(name, version) : await registry.latest(name)
  } catch (err) {
    await cleanup()
    throw err
  }

  return { entry, via: 'replica', cleanup }
}

/**
 * A `getLatestRelease(name)` for pgh-runtime's fetcher shape, backed by the above.
 *
 * pgh-runtime polls, so this opens and CLOSES per call rather than holding the
 * store's fd-lock for the life of the app: an app that kept it would block the
 * sidecar, `pgh app install`, and its own next resolve. Polling is hourly by
 * default, so the reconnect cost is irrelevant beside that.
 */
/**
 * WATCH a registry for releases of one app, instead of asking it repeatedly.
 *
 * ⛔ WHY THIS EXISTS. `resolveRelease` opens a Corestore and a Hyperswarm, reads, and tears both
 * down — so nothing survives to emit anything, and every caller is forced to poll. Worse, a one-shot
 * read has to GUESS how long replication takes: the code above waits `settleMs` because, in its own
 * words, "flush returns when peers are connected, not when the log has caught up". That guess was
 * 500ms, which is correct for a warm replica and far too short for a cold one — measured on a
 * handset as `resolveRelease` returning null and OTA reporting "no published release with a
 * serverKey", which reads like a publishing failure and is not.
 *
 * ★ A held replica has no settle to guess. The view is built by autobase's apply as blocks arrive,
 * and autobase emits `update` when it does — so you are TOLD when the data lands, however long it
 * takes. That removes the entire class of "you did not wait long enough", rather than retuning it.
 *
 * Event-driven plus reconciliation, the same shape hyper-social #68 §2 landed for delivery: the
 * event sets the latency floor, and the initial read after flush is the reconciliation that covers
 * a replica which was already caught up before anyone subscribed.
 *
 * @returns {{ close: function, latest: function }} `latest()` is the last entry seen, or null.
 */
export async function watchLatestRelease (opts = {}) {
  const { registryKeyHex, name, onRelease = () => {} } = opts
  if (!registryKeyHex || registryKeyHex.length !== 64) {
    throw new Error('watchLatestRelease requires a 64-char registryKeyHex')
  }
  if (!name) throw new Error('watchLatestRelease requires a name')

  const Corestore = (await import('corestore')).default
  const Hyperswarm = (await import('hyperswarm')).default

  const dir = opts.storeDir || registryStoreDir(registryKeyHex)
  fs.mkdirSync(dir, { recursive: true })

  const store = new Corestore(dir)
  const registry = new ReleaseRegistry(store, { bootstrap: b4a.from(registryKeyHex, 'hex') })
  await registry.ready()

  const swarm = new Hyperswarm()
  swarm.on('connection', (conn) => store.replicate(conn))
  // client-only: we are reading someone else's registry, not serving ours.
  swarm.join(registry._base.discoveryKey, { server: false, client: true })

  let last = null
  let closed = false

  // ⚠ Reports only CHANGES. A view can update many times while one release replicates, and a caller
  // that rebuilt a fetcher on every tick would thrash for no reason.
  const reread = async () => {
    if (closed) return
    try {
      await registry.update()
      const entry = await registry.latest(name)
      if (!entry) return
      if (last && last.treeHash === entry.treeHash) return
      last = entry
      await onRelease(entry)
    } catch { /* a registry that cannot be read right now is not an error; the next update retries */ }
  }

  registry._base.on('update', () => { reread().catch(() => {}) })

  // Reconciliation: a replica already caught up before we subscribed emits nothing, so read once.
  // NOT awaited on the swarm — `flush()` can take as long as a DHT lookup, and a caller must not
  // block boot on it. The `update` event covers whatever arrives afterwards.
  swarm.flush().then(reread).catch(() => {})
  await reread()

  return {
    latest: () => last,
    close: async () => {
      closed = true
      try { registry._base.removeAllListeners('update') } catch {}
      // ⛔ CLOSE WHAT WE OPENED, all of it. A swarm left running is a DHT node and a set of sockets
      // held awake by something the user switched off — the same leak shape that reached ~205k
      // sockets on stortron because the thing that created a handle was not the thing that owned it.
      try { await swarm.destroy() } catch {}
      try { await registry.close() } catch {}
      try { await store.close() } catch {}
    }
  }
}

export function makeGetLatestRelease ({ registryKeyHex, preferSidecar, settleMs, storeDir } = {}) {
  return async function getLatestRelease (name) {
    const { entry, cleanup } = await resolveRelease({
      registryKeyHex, name, preferSidecar, settleMs, storeDir
    })
    try { return entry } finally { await cleanup() }
  }
}
const { Pull, Push, HEADERBYTES, KEYBYTES, ABYTES } = require('sodium-secretstream')
const sodium = require('sodium-universal')
const crypto = require('hypercore-crypto')
const { Duplex, Writable, getStreamError } = require('streamx')
const b4a = require('b4a')
const Timeout = require('timeout-refresh')
const unslab = require('unslab')
const Bridge = require('./lib/bridge')
const Handshake = require('./lib/handshake')

const IDHEADERBYTES = HEADERBYTES + 32
const [NS_INITIATOR, NS_RESPONDER, NS_SEND] = crypto.namespace('hyperswarm/secret-stream', 3)
const MAX_ATOMIC_WRITE = 256 * 256 * 256 - 1

module.exports = class NoiseSecretStream extends Duplex {
  constructor(isInitiator, rawStream, opts = {}) {
    super({ mapWritable: toBuffer })

    if (typeof isInitiator !== 'boolean') {
      throw new Error('isInitiator should be a boolean')
    }

    this.noiseStream = this
    this.isInitiator = isInitiator
    this.rawStream = null

    this.publicKey = opts.publicKey || null
    this.remotePublicKey = opts.remotePublicKey || null
    this.handshakeHash = null
    this.connected = false
    this.keepAlive = opts.keepAlive || 0
    this.timeout = 0
    this.enableSend = opts.enableSend !== false

    // pointer for upstream to set data here if they want
    this.userData = null

    let openedDone = null
    this.opened = new Promise((resolve) => {
      openedDone = resolve
    })

    this.rawBytesWritten = 0
    this.rawBytesRead = 0

    // metadata used by 'hyperdht'
    this.relay = null
    this.puncher = null

    // unwrapped raw stream
    this._rawStream = null

    // handshake state
    this._handshake = null
    this._handshakePattern = opts.pattern || null
    this._handshakeDone = null

    // message parsing state
    this._state = 0
    this._len = 0
    this._tmp = 1
    this._message = null

    this._openedDone = openedDone
    this._startDone = null
    this._drainDone = null
    this._outgoingPlain = null
    this._outgoingWrapped = null
    this._utp = null
    this._setup = true
    this._ended = 2
    this._encrypt = null
    this._decrypt = null
    this._timeoutTimer = null
    this._keepAliveTimer = null
    this._sendState = null

    if (opts.autoStart !== false) this.start(rawStream, opts)

    // wiggle it to trigger open immediately (TODO add streamx option for this)
    this.resume()
    this.pause()
  }

  static keyPair(seed) {
    return Handshake.keyPair(seed)
  }

  static id(handshakeHash, isInitiator, id) {
    return streamId(handshakeHash, isInitiator, id)
  }

  setTimeout(ms) {
    if (!ms) ms = 0

    this._clearTimeout()
    this.timeout = ms

    if (!ms || this.rawStream === null) return

    this._timeoutTimer = Timeout.once(ms, destroyTimeout, this)
    this._timeoutTimer.unref()
  }

  setKeepAlive(ms) {
    if (!ms) ms = 0

    this._clearKeepAlive()

    this.keepAlive = ms

    if (!ms || this.rawStream === null) return

    this._keepAliveTimer = Timeout.on(ms, sendKeepAlive, this)
    this._keepAliveTimer.unref()
  }

  sendKeepAlive() {
    const empty = this.alloc(0)
    this.write(empty)
  }

  start(rawStream, opts = {}) {
    if (rawStream) {
      this.rawStream = rawStream
      this._rawStream = rawStream
      if (typeof this.rawStream.setContentSize === 'function') {
        this._utp = rawStream
      }
    } else {
      this.rawStream = new Bridge(this)
      this._rawStream = this.rawStream.reverse
    }

    this.rawStream.on('error', this._onrawerror.bind(this))
    this.rawStream.on('close', this._onrawclose.bind(this))

    this._startHandshake(opts.handshake, opts.keyPair || null)
    this._continueOpen(null)

    if (this.destroying) return

    if (opts.data) this._onrawdata(opts.data)
    if (opts.ended) this._onrawend()

    if (this.keepAlive > 0 && this._keepAliveTimer === null) {
      this.setKeepAlive(this.keepAlive)
    }

    if (this.timeout > 0 && this._timeoutTimer === null) {
      this.setTimeout(this.timeout)
    }
  }

  async flush() {
    if ((await this.opened) === false) return false
    if ((await Writable.drained(this)) === false) return false
    if (this.destroying) return false

    if (this.rawStream !== null && this.rawStream.flush) {
      return await this.rawStream.flush()
    }

    return true
  }

  _continueOpen(err) {
    if (err) this.destroy(err)
    if (this._startDone === null) return
    const done = this._startDone
    this._startDone = null
    this._open(done)
  }

  _onkeypairpromise(p) {
    const self = this
    const cont = this._continueOpen.bind(this)

    p.then(onkeypair, cont)

    function onkeypair(kp) {
      self._onkeypair(kp)
      cont(null)
    }
  }

  _onkeypair(keyPair) {
    const pattern = this._handshakePattern || 'XX'
    const remotePublicKey = this.remotePublicKey

    this._handshake = new Handshake(this.isInitiator, keyPair, remotePublicKey, pattern)
    this.publicKey = this._handshake.keyPair.publicKey
  }

  _startHandshake(handshake, keyPair) {
    if (handshake) {
      const { tx, rx, hash, publicKey, remotePublicKey } = handshake
      this._setupSecretStream(tx, rx, hash, publicKey, remotePublicKey)
      return
    }

    if (!keyPair) keyPair = Handshake.keyPair()

    if (typeof keyPair.then === 'function') {
      this._onkeypairpromise(keyPair)
    } else {
      this._onkeypair(keyPair)
    }
  }

  _onrawerror(err) {
    this.destroy(err)
  }

  _onrawclose() {
    if (this._ended !== 0) this.destroy()
  }

  _onrawdata(data) {
    let offset = 0

    if (this._timeoutTimer !== null) {
      this._timeoutTimer.refresh()
    }

    do {
      switch (this._state) {
        case 0: {
          while (this._tmp !== 0x1000000 && offset < data.byteLength) {
            const v = data[offset++]
            this._len += this._tmp * v
            this._tmp *= 256
          }

          if (this._tmp === 0x1000000) {
            this._tmp = 0
            this._state = 1
            const unprocessed = data.byteLength - offset
            if (unprocessed < this._len && this._utp !== null)
              this._utp.setContentSize(this._len - unprocessed)
          }

          break
        }

        case 1: {
          const missing = this._len - this._tmp
          const end = missing + offset

          if (this._message === null && end <= data.byteLength) {
            this._message = data.subarray(offset, end)
            offset += missing
            this._incoming()
            break
          }

          const unprocessed = data.byteLength - offset

          if (this._message === null) {
            this._message = b4a.allocUnsafe(this._len)
          }

          b4a.copy(data, this._message, this._tmp, offset)
          this._tmp += unprocessed

          if (end <= data.byteLength) {
            offset += missing
            this._incoming()
          } else {
            offset += unprocessed
          }

          break
        }
      }
    } while (offset < data.byteLength && !this.destroying)
  }

  _onrawend() {
    this._ended--
    this.push(null)
  }

  _onrawdrain() {
    const drain = this._drainDone
    if (drain === null) return
    this._drainDone = null
    drain()
  }

  _read(cb) {
    this.rawStream.resume()
    cb(null)
  }

  _incoming() {
    const message = this._message

    this._state = 0
    this._len = 0
    this._tmp = 1
    this._message = null

    if (this._setup === true) {
      if (this._handshake) {
        this._onhandshakert(this._handshake.recv(message))
      } else {
        if (message.byteLength !== IDHEADERBYTES) {
          this.destroy(new Error('Invalid header message received'))
          return
        }

        const remoteId = message.subarray(0, 32)
        const expectedId = streamId(this.handshakeHash, !this.isInitiator)
        const header = message.subarray(32)

        if (!b4a.equals(expectedId, remoteId)) {
          this.destroy(new Error('Invalid header received'))
          return
        }

        this._decrypt.init(header)
        this._setup = false // setup is now done
      }
      return
    }

    if (message.byteLength < ABYTES) {
      this.destroy(new Error('Invalid message received'))
      return
    }

    this.rawBytesRead += message.byteLength

    const plain = message.subarray(1, message.byteLength - ABYTES + 1)

    try {
      this._decrypt.next(message, plain)
    } catch (err) {
      this.destroy(err)
      return
    }

    // If keep alive is selective, eat the empty buffers (ie assume the other side has it enabled also)
    if (plain.byteLength === 0 && this.keepAlive !== 0) return

    if (this.push(plain) === false) {
      this.rawStream.pause()
    }
  }

  _onhandshakert(h) {
    if (this._handshakeDone === null) return

    if (h !== null) {
      if (h.data) this._rawStream.write(h.data)
      if (!h.tx) return
    }

    const done = this._handshakeDone
    const publicKey = this._handshake.keyPair.publicKey

    this._handshakeDone = null
    this._handshake = null

    if (h === null) return done(new Error('Noise handshake failed'))

    this._setupSecretStream(h.tx, h.rx, h.hash, publicKey, h.remotePublicKey)
    this._resolveOpened(true)
    done(null)
  }

  _setupSecretStream(tx, rx, handshakeHash, publicKey, remotePublicKey) {
    const buf = b4a.allocUnsafeSlow(3 + IDHEADERBYTES)
    writeUint24le(IDHEADERBYTES, buf)

    this._encrypt = new Push(unslab(tx.subarray(0, KEYBYTES)), undefined, buf.subarray(3 + 32))
    this._decrypt = new Pull(unslab(rx.subarray(0, KEYBYTES)))

    this.publicKey = publicKey
    this.remotePublicKey = remotePublicKey
    this.handshakeHash = handshakeHash

    const id = buf.subarray(3, 3 + 32)
    streamId(handshakeHash, this.isInitiator, id)

    // initialize secretbox state for unordered messages
    this._setupSecretSend(handshakeHash)

    this.emit('handshake')
    // if rawStream is a bridge, also emit it there
    if (this.rawStream !== this._rawStream) this.rawStream.emit('handshake')

    if (this.destroying) return

    this._rawStream.write(buf)
  }

  _setupSecretSend(handshakeHash) {
    this._sendState = b4a.allocUnsafeSlow(32 + 32 + 8 + 8)
    const encrypt = this._sendState.subarray(0, 32) // secrets
    const decrypt = this._sendState.subarray(32, 64)
    const counter = this._sendState.subarray(64, 72) // nonce
    const initial = this._sendState.subarray(72)

    const inputs = this.isInitiator
      ? [
          [NS_INITIATOR, NS_SEND],
          [NS_RESPONDER, NS_SEND]
        ]
      : [
          [NS_RESPONDER, NS_SEND],
          [NS_INITIATOR, NS_SEND]
        ]

    sodium.crypto_generichash_batch(encrypt, inputs[0], handshakeHash)
    sodium.crypto_generichash_batch(decrypt, inputs[1], handshakeHash)

    sodium.randombytes_buf(initial)
    counter.set(initial)
  }

  _open(cb) {
    // no autostart or no handshake yet
    if (this._rawStream === null || (this._handshake === null && this._encrypt === null)) {
      this._startDone = cb
      return
    }

    this._rawStream.on('data', this._onrawdata.bind(this))
    this._rawStream.on('end', this._onrawend.bind(this))
    this._rawStream.on('drain', this._onrawdrain.bind(this))

    if (this.enableSend) this._rawStream.on('message', this._onmessage.bind(this))

    if (this._encrypt !== null) {
      this._resolveOpened(true)
      return cb(null)
    }

    this._handshakeDone = cb

    if (this.isInitiator) this._onhandshakert(this._handshake.send())
  }

  _predestroy() {
    if (this.rawStream) {
      const error = getStreamError(this)
      this.rawStream.destroy(error)
    }

    if (this._startDone !== null) {
      const done = this._startDone
      this._startDone = null
      done(new Error('Stream destroyed'))
    }

    if (this._handshakeDone !== null) {
      const done = this._handshakeDone
      this._handshakeDone = null
      done(new Error('Stream destroyed'))
    }

    if (this._drainDone !== null) {
      const done = this._drainDone
      this._drainDone = null
      done(new Error('Stream destroyed'))
    }
  }

  _write(data, cb) {
    let wrapped = this._outgoingWrapped

    if (data !== this._outgoingPlain) {
      wrapped = b4a.allocUnsafe(data.byteLength + 3 + ABYTES)
      wrapped.set(data, 4)
    } else {
      this._outgoingWrapped = this._outgoingPlain = null
    }

    if (wrapped.byteLength - 3 > MAX_ATOMIC_WRITE) {
      return cb(
        new Error(
          'Message is too large for an atomic write. Max size is ' + MAX_ATOMIC_WRITE + ' bytes.'
        )
      )
    }
    this.rawBytesWritten += wrapped.byteLength

    writeUint24le(wrapped.byteLength - 3, wrapped)
    // offset 4 so we can do it in-place
    this._encrypt.next(wrapped.subarray(4, 4 + data.byteLength), wrapped.subarray(3))

    if (this._keepAliveTimer !== null) this._keepAliveTimer.refresh()

    if (this._rawStream.write(wrapped) === false) {
      this._drainDone = cb
    } else {
      cb(null)
    }
  }

  _final(cb) {
    this._clearKeepAlive()
    this._ended--
    this._rawStream.end()
    cb(null)
  }

  _resolveOpened(val) {
    if (this._openedDone === null) return
    const opened = this._openedDone
    this._openedDone = null
    opened(val)
    if (!val) return
    this.connected = true
    this.emit('connect')
  }

  _clearTimeout() {
    if (this._timeoutTimer === null) return
    this._timeoutTimer.destroy()
    this._timeoutTimer = null
    this.timeout = 0
  }

  _clearKeepAlive() {
    if (this._keepAliveTimer === null) return
    this._keepAliveTimer.destroy()
    this._keepAliveTimer = null
    this.keepAlive = 0
  }

  _destroy(cb) {
    this._clearKeepAlive()
    this._clearTimeout()
    this._resolveOpened(false)
    cb(null)
  }

  _boxMessage(buffer) {
    const MB = sodium.crypto_secretbox_MACBYTES // 16
    const NB = sodium.crypto_secretbox_NONCEBYTES // 24

    const counter = this._sendState.subarray(64, 72)
    sodium.sodium_increment(counter)
    if (b4a.equals(counter, this._sendState.subarray(72))) {
      this.destroy(new Error('udp send nonce exchausted'))
      return
    }

    const secret = this._sendState.subarray(0, 32)
    const envelope = b4a.allocUnsafe(8 + MB + buffer.byteLength)
    const nonce = envelope.subarray(0, NB)
    const ciphertext = envelope.subarray(8)

    b4a.fill(nonce, 0) // pad suffix
    nonce.set(counter)

    sodium.crypto_secretbox_easy(ciphertext, buffer, nonce, secret)
    return envelope
  }

  send(buffer) {
    if (!this._sendState) return
    if (!this.rawStream?.send) return // udx-stream expected

    const message = this._boxMessage(buffer)
    return this.rawStream.send(message)
  }

  trySend(buffer) {
    if (!this._sendState) return
    if (!this.rawStream?.trySend) return // udx-stream expected

    const message = this._boxMessage(buffer)
    this.rawStream.trySend(message)
  }

  _onmessage(buffer) {
    if (!this._sendState) return // messages before handshake are dropped

    const MB = sodium.crypto_secretbox_MACBYTES // 16
    const NB = sodium.crypto_secretbox_NONCEBYTES // 24

    if (buffer.byteLength < NB) return // Invalid message

    const nonce = b4a.allocUnsafe(NB)
    b4a.fill(nonce, 0)
    nonce.set(buffer.subarray(0, 8))

    const secret = this._sendState.subarray(32, 64)
    const ciphertext = buffer.subarray(8)
    const plain = buffer.subarray(8, buffer.byteLength - MB)

    if (ciphertext.byteLength < MB) return // invalid message

    const success = sodium.crypto_secretbox_open_easy(plain, ciphertext, nonce, secret)

    if (success) this.emit('message', plain)
  }

  alloc(len) {
    const buf = b4a.allocUnsafe(len + 3 + ABYTES)
    this._outgoingWrapped = buf
    this._outgoingPlain = buf.subarray(4, buf.byteLength - ABYTES + 1)
    return this._outgoingPlain
  }

  toJSON() {
    return {
      isInitiator: this.isInitiator,
      publicKey: this.publicKey && b4a.toString(this.publicKey, 'hex'),
      remotePublicKey: this.remotePublicKey && b4a.toString(this.remotePublicKey, 'hex'),
      connected: this.connected,
      destroying: this.destroying,
      destroyed: this.destroyed,
      rawStream: this.rawStream && this.rawStream.toJSON ? this.rawStream.toJSON() : null
    }
  }
}

function writeUint24le(n, buf) {
  buf[0] = n & 255
  buf[1] = (n >>> 8) & 255
  buf[2] = (n >>> 16) & 255
}

function streamId(handshakeHash, isInitiator, out = b4a.allocUnsafe(32)) {
  sodium.crypto_generichash(out, isInitiator ? NS_INITIATOR : NS_RESPONDER, handshakeHash)
  return out
}

function toBuffer(data) {
  return typeof data === 'string' ? b4a.from(data) : data
}

function destroyTimeout() {
  this.destroy(new Error('Stream timed out'))
}

function sendKeepAlive() {
  const empty = this.alloc(0)
  this.write(empty)
}
const { Duplex, Writable } = require('streamx')

class ReversePassThrough extends Duplex {
  constructor(s) {
    super()
    this._stream = s
    this._ondrain = null
  }

  _write(data, cb) {
    if (this._stream.push(data) === false) {
      this._stream._ondrain = cb
    } else {
      cb(null)
    }
  }

  _final(cb) {
    this._stream.push(null)
    cb(null)
  }

  _read(cb) {
    const ondrain = this._ondrain
    this._ondrain = null
    if (ondrain) ondrain()
    cb(null)
  }
}

module.exports = class Bridge extends Duplex {
  constructor(noiseStream) {
    super()

    this.noiseStream = noiseStream

    this._ondrain = null
    this.reverse = new ReversePassThrough(this)
  }

  get publicKey() {
    return this.noiseStream.publicKey
  }

  get remotePublicKey() {
    return this.noiseStream.remotePublicKey
  }

  get handshakeHash() {
    return this.noiseStream.handshakeHash
  }

  flush() {
    return Writable.drained(this)
  }

  _read(cb) {
    const ondrain = this._ondrain
    this._ondrain = null
    if (ondrain) ondrain()
    cb(null)
  }

  _write(data, cb) {
    if (this.reverse.push(data) === false) {
      this.reverse._ondrain = cb
    } else {
      cb(null)
    }
  }

  _final(cb) {
    this.reverse.push(null)
    cb(null)
  }
}
const sodium = require('sodium-universal')
const curve = require('noise-curve-ed')
const Noise = require('noise-handshake')
const b4a = require('b4a')

const EMPTY = b4a.alloc(0)

module.exports = class Handshake {
  constructor(isInitiator, keyPair, remotePublicKey, pattern) {
    this.isInitiator = isInitiator
    this.keyPair = keyPair
    this.noise = new Noise(pattern, isInitiator, keyPair, { curve })
    this.noise.initialise(EMPTY, remotePublicKey)
    this.destroyed = false
  }

  static keyPair(seed) {
    const publicKey = b4a.alloc(32)
    const secretKey = b4a.alloc(64)
    if (seed) sodium.crypto_sign_seed_keypair(publicKey, secretKey, seed)
    else sodium.crypto_sign_keypair(publicKey, secretKey)
    return { publicKey, secretKey }
  }

  recv(data) {
    try {
      this.noise.recv(data)
      if (this.noise.complete) return this._return(null)
      return this.send()
    } catch {
      this.destroy()
      return null
    }
  }

  // note that the data returned here is framed so we don't have to do an extra copy
  // when sending it...
  send() {
    try {
      const data = this.noise.send()
      const wrap = b4a.allocUnsafe(data.byteLength + 3)

      writeUint24le(data.byteLength, wrap)
      wrap.set(data, 3)

      return this._return(wrap)
    } catch {
      this.destroy()
      return null
    }
  }

  destroy() {
    if (this.destroyed) return
    this.destroyed = true
  }

  _return(data) {
    const tx = this.noise.complete ? b4a.toBuffer(this.noise.tx) : null
    const rx = this.noise.complete ? b4a.toBuffer(this.noise.rx) : null
    const hash = this.noise.complete ? b4a.toBuffer(this.noise.hash) : null
    const remotePublicKey = this.noise.complete ? b4a.toBuffer(this.noise.rs) : null

    return {
      data,
      remotePublicKey,
      hash,
      tx,
      rx
    }
  }
}

function writeUint24le(n, buf) {
  buf[0] = n & 255
  buf[1] = (n >>> 8) & 255
  buf[2] = (n >>> 16) & 255
}
{
  "name": "@hyperswarm/secret-stream",
  "version": "6.9.1",
  "description": "Secret stream backed by Noise and libsodium's secretstream",
  "main": "index.js",
  "files": [
    "index.js",
    "lib/**.js"
  ],
  "dependencies": {
    "b4a": "^1.1.0",
    "hypercore-crypto": "^3.3.1",
    "noise-curve-ed": "^2.0.1",
    "noise-handshake": "^4.0.0",
    "sodium-secretstream": "^1.1.0",
    "sodium-universal": "^5.0.0",
    "streamx": "^2.14.0",
    "timeout-refresh": "^2.0.0",
    "unslab": "^1.3.0"
  },
  "devDependencies": {
    "brittle": "^3.3.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^2.0.0",
    "udx-native": "^1.13.2"
  },
  "scripts": {
    "format": "prettier --write .",
    "lint": "prettier --check .",
    "test": "brittle test.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/holepunchto/hyperswarm-secret-stream.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/hyperswarm-secret-stream/issues"
  },
  "homepage": "https://github.com/holepunchto/hyperswarm-secret-stream"
}
const Cache = require('xache')

module.exports = class AdaptiveTimeout {
  constructor(opts = {}) {
    this._cache = new Cache({
      maxSize: opts.maxSize || 65536,
      maxAge: opts.maxAge || 10 * 60 * 1000 // 10 minutes
    })
    this._fallback = opts.fallback || AdaptiveTimeout.TimeoutExponential
    this._min = opts.min ?? 300
    this._max = opts.max || 4000
    this._jitter = opts.jitter ?? 256
  }

  // Default - aggressive ramp
  static TimeoutAggressive = [500, 750, 1000, 1500, 2000]

  // Linear - steady increase
  static TimeoutLinear = [500, 1000, 1500, 2000, 2500]

  // Exponential - slow start, rapid backoff
  static TimeoutExponential = [250, 500, 1000, 2000, 4000]

  // Gentle - conservative, patient
  static TimeoutGentle = [1000, 1250, 1500, 1750, 2000]

  // Fast - rapid fire retries
  static TimeoutFast = [200, 400, 600, 800, 1000]

  // U-shape - long, short, long
  static TimeoutUShape = [1500, 750, 500, 750, 1500]

  // Inverse U - short, long, short
  static TimeoutInverseU = [500, 1000, 1500, 1000, 500]

  // Sawtooth - alternating fast/slow
  static TimeoutSawtooth = [500, 1500, 500, 1500, 500]

  // Plateau - quick ramp then steady
  static TimeoutPlateau = [500, 1000, 2000, 2000, 2000]

  // Logarithmic - diminishing increases
  static TimeoutLogarithmic = [500, 1000, 1300, 1500, 1600]

  getValue(key) {
    return this._cache.get(key)
  }

  put(key, value) {
    let p = this._cache.get(key)

    if (!p) {
      p = { avg: value, variance: value >> 1 }
    } else {
      // blend 75% old + 25% new deviation
      p.variance += (Math.abs(p.avg - value) - p.variance) >> 2
      // blend 87.5% old + 12.5% new sample
      p.avg += (value - p.avg) >> 3
    }

    this._cache.set(key, p)

    return p
  }

  get(key, attempt = 1) {
    const p = this._cache.get(key)
    const jitter = (Math.random() * this._jitter) | 0

    if (p) {
      // known - use adaptive with linear backoff
      const base = p.avg + (p.variance << 1)
      const backoff = base * attempt
      return Math.min(Math.max(backoff + jitter, this._min), this._max)
    } else {
      // unknown - use aggressive fallback
      const base = this._fallback[Math.min(attempt - 1, this._fallback.length - 1)]
      return base + jitter
    }
  }

  has(key) {
    return this._cache.has(key)
  }

  delete(key) {
    return this._cache.delete(key)
  }

  clear() {
    this._cache.clear()
  }
}
{
  "name": "adaptive-timeout",
  "version": "1.0.1",
  "description": "adaptive-timeout",
  "main": "index.js",
  "exports": {
    "./package": "./package.json",
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    }
  },
  "files": [
    "package.json",
    "index.js",
    "index.d.ts"
  ],
  "devDependencies": {
    "brittle": "^3.19.0",
    "lunte": "^1.2.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^2.0.0"
  },
  "scripts": {
    "format": "prettier . --write",
    "test": "prettier . --check && lunte && brittle-bare test/index.js",
    "lint": "lunte"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/holepunchto/adaptive-timeout.git"
  },
  "author": "Holepunch Inc",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/adaptive-timeout/issues"
  },
  "homepage": "https://github.com/holepunchto/adaptive-timeout",
  "dependencies": {
    "xache": "^1.2.1"
  }
}
const c = require('compact-encoding')
const IndexEncoder = require('index-encoder')

const Checkout = {
  preencode(state, m) {
    c.fixed32.preencode(state, m.key)
    c.uint.preencode(state, m.length)
  },
  encode(state, m) {
    c.fixed32.encode(state, m.key)
    c.uint.encode(state, m.length)
  },
  decode(state) {
    return {
      key: c.fixed32.decode(state),
      length: c.uint.decode(state)
    }
  }
}

const Clock = c.array(Checkout)

const IndexCheckpoint = {
  preencode(state, m) {
    c.fixed64.preencode(state, m.signature)
    c.uint.preencode(state, m.length)
  },
  encode(state, m) {
    c.fixed64.encode(state, m.signature)
    c.uint.encode(state, m.length)
  },
  decode(state) {
    return {
      signature: c.fixed64.decode(state),
      length: c.uint.decode(state)
    }
  }
}

const KeyV0 = {
  preencode(state, m) {
    c.fixed32.preencode(state, m.key)
  },
  encode(state, m) {
    c.fixed32.encode(state, m.key)
  },
  decode(state) {
    return {
      key: c.fixed32.decode(state),
      length: -1
    }
  }
}

const KeysV0 = c.array(KeyV0)

const WakeupV0 = {
  preencode(state, m) {
    c.uint.preencode(state, 0) // version
    c.uint.preencode(state, m.type)

    if (m.type === 1) {
      KeysV0.preencode(state, m.writers)
    }
  },
  encode(state, m) {
    c.uint.encode(state, 0) // version
    c.uint.encode(state, m.type)

    if (m.type === 1) {
      KeysV0.encode(state, m.writers)
    }
  },
  decode(state) {
    const v = c.uint.decode(state)
    if (v !== 0) throw new Error('Unsupported version: ' + v)

    const type = c.uint.decode(state)
    const m = { version: 0, type, writers: null }

    if (m.type === 1) {
      m.writers = KeysV0.decode(state)
    }

    return m
  }
}

const Wakeup = {
  preencode(state, m) {
    if (m.version === 0) return WakeupV0.preencode(state, m)

    c.uint.preencode(state, 1) // version
    c.uint.preencode(state, m.type)

    if (m.type === 1) {
      Clock.preencode(state, m.writers)
    }
  },
  encode(state, m) {
    if (m.version === 0) return WakeupV0.encode(state, m)

    c.uint.encode(state, 1) // version
    c.uint.encode(state, m.type)

    if (m.type === 1) {
      Clock.encode(state, m.writers)
    }
  },
  decode(state) {
    const start = state.start
    const v = c.uint.decode(state)

    if (v > 1) throw new Error('Unsupported version: ' + v)

    if (v === 0) {
      state.start = start
      return WakeupV0.decode(state)
    }

    const type = c.uint.decode(state)
    const m = { version: 1, type, writers: null }

    if (m.type === 1) {
      m.writers = Clock.decode(state)
    }

    return m
  }
}

const BootRecordV0 = {
  preencode() {
    throw new Error('version 0 records cannot be encoded')
  },
  encode() {
    throw new Error('version 0 records cannot be encoded')
  },
  decode(state) {
    c.uint.decode(state)
    const indexed = Checkout.decode(state)
    const heads = Clock.decode(state)

    // one cause initial recover is not ff recovery
    return {
      version: 0,
      key: indexed.key,
      systemLength: indexed.length,
      indexersUpdated: false,
      fastForwarding: false,
      recoveries: 1,
      migrating: false,
      heads
    }
  }
}

const Checkpointer = {
  preencode(state, idx) {
    c.uint.preencode(state, idx.checkpointer)
    if (idx.checkpoint !== null) IndexCheckpoint.preencode(state, idx.checkpoint)
  },
  encode(state, idx) {
    c.uint.encode(state, idx.checkpointer)
    if (idx.checkpoint !== null) IndexCheckpoint.encode(state, idx.checkpoint)
  },
  decode(state) {
    const checkpointer = c.uint.decode(state)
    const checkpoint = checkpointer ? null : IndexCheckpoint.decode(state)

    return {
      checkpointer,
      checkpoint
    }
  }
}

const CheckpointerArray = c.array(Checkpointer)

const Indexer = {
  preencode(state, m) {
    c.uint.preencode(state, m.signature)
    c.fixed32.preencode(state, m.namespace)
    c.fixed32.preencode(state, m.publicKey)
  },
  encode(state, m) {
    c.uint.encode(state, m.signature)
    c.fixed32.encode(state, m.namespace)
    c.fixed32.encode(state, m.publicKey)
  },
  decode(state) {
    return {
      signature: c.uint.decode(state),
      namespace: c.fixed32.decode(state),
      publicKey: c.fixed32.decode(state)
    }
  }
}

const Indexers = c.array(Indexer)

const DigestV0 = {
  preencode(state, m) {
    c.uint.preencode(state, m.pointer)
    if (m.pointer === 0) {
      Indexers.preencode(state, m.indexers)
    }
  },
  encode(state, m) {
    c.uint.encode(state, m.pointer)
    if (m.pointer === 0) {
      Indexers.encode(state, m.indexers)
    }
  },
  decode(state) {
    const pointer = c.uint.decode(state)
    return {
      pointer,
      indexers: pointer === 0 ? Indexers.decode(state) : null
    }
  }
}

const Digest = {
  preencode(state, m) {
    c.uint.preencode(state, m.pointer)
    if (m.pointer === 0) {
      c.fixed32.preencode(state, m.key)
    }
  },
  encode(state, m) {
    c.uint.encode(state, m.pointer)
    if (m.pointer === 0) {
      c.fixed32.encode(state, m.key)
    }
  },
  decode(state) {
    const pointer = c.uint.decode(state)
    return {
      pointer,
      key: pointer === 0 ? c.fixed32.decode(state) : null
    }
  }
}

const Node = {
  preencode(state, m) {
    Clock.preencode(state, m.heads)
    c.uint.preencode(state, m.batch)
    c.optionalBuffer.preencode(state, m.value)
  },
  encode(state, m) {
    Clock.encode(state, m.heads)
    c.uint.encode(state, m.batch)
    c.optionalBuffer.encode(state, m.value)
  },
  decode(state, m) {
    return {
      heads: Clock.decode(state),
      batch: c.uint.decode(state),
      value: c.optionalBuffer.decode(state)
    }
  }
}

const Additional = {
  preencode(state, m) {
    c.uint.preencode(state, m.pointer)
    if (m.pointer === 0) {
      AdditionalData.preencode(state, m.data)
    }
  },
  encode(state, m) {
    c.uint.encode(state, m.pointer)
    if (m.pointer === 0) {
      AdditionalData.encode(state, m.data)
    }
  },
  decode(state) {
    const pointer = c.uint.decode(state)
    return {
      pointer,
      data: pointer === 0 ? AdditionalData.decode(state) : null
    }
  }
}

const AdditionalData = {
  preencode(state, m) {
    c.uint.preencode(state, 0)
  },
  encode(state, m) {
    c.uint.encode(state, 0) // empty for now, for the future
  },
  decode(state) {
    const flags = c.uint.decode(state)
    return {
      encryptionId: flags & 1 ? c.fixed32.decode(state) : null, // to help validate the encryption key used
      abi: flags & 2 ? c.uint.decode(state) : 0
    }
  }
}

const OplogMessageV1 = {
  preencode(state, m) {
    throw new Error('Encoding not supported')
  },
  encode(state, m) {
    throw new Error('Encoding not supported')
  },
  decode(state) {
    c.uint.decode(state) // version
    const maxSupportedVersion = c.uint.decode(state)

    const flags = c.uint.decode(state)

    const isCheckpointer = (flags & 1) !== 0

    const chk = isCheckpointer ? CheckpointerArray.decode(state) : null
    const digest = isCheckpointer ? Digest.decode(state) : null

    const node = Node.decode(state)

    return {
      version: 1,
      maxSupportedVersion,
      digest,
      checkpoint: chk ? { system: chk[0], encryption: null, user: chk.slice(1) } : null,
      optimistic: (flags & 2) !== 0,
      node
    }
  }
}

const OplogMessageV0 = {
  preencode(state, m) {
    throw new Error('Encoding not supported')
  },
  encode(state, m) {
    throw new Error('Encoding not supported')
  },
  decode(state) {
    c.uint.decode(state) // version
    const flags = c.uint.decode(state)

    const isCheckpointer = (flags & 1) !== 0

    if (isCheckpointer) DigestV0.decode(state)
    const chk = isCheckpointer ? CheckpointerArray.decode(state) : null
    const node = Node.decode(state)
    Additional.decode(state)
    const maxSupportedVersion = state.start < state.end ? c.uint.decode(state) : 0

    return {
      version: 0,
      maxSupportedVersion,
      digest: null,
      checkpoint: chk ? { system: chk[0], encryption: null, user: chk.slice(1) } : null,
      optimistic: null,
      node
    }
  }
}

// prefix 0 is reserved for future manifest
const LINEARIZER_PREFIX = 1

const LinearizerKey = {
  preencode(state, seq) {
    IndexEncoder.UINT.preencode(state, LINEARIZER_PREFIX)
    IndexEncoder.UINT.preencode(state, seq)
  },
  encode(state, seq) {
    IndexEncoder.UINT.encode(state, LINEARIZER_PREFIX)
    IndexEncoder.UINT.encode(state, seq)
  },
  decode(state) {
    IndexEncoder.UINT.decode(state)
    return IndexEncoder.UINT.decode(state)
  }
}

function infoLegacyMap(info) {
  return {
    version: info.version,
    members: info.members,
    pendingIndexers: info.pendingIndexers,
    indexers: info.indexers,
    heads: info.heads,
    views: info.views,
    encryptionLength: 0,
    entropy: null
  }
}

module.exports = {
  Wakeup,
  BootRecordV0,
  OplogMessageV0,
  OplogMessageV1,
  LinearizerKey,
  infoLegacyMap
}
// This file is autogenerated by the hyperschema compiler
// Schema Version: 1
/* eslint-disable camelcase */
/* eslint-disable quotes */
/* eslint-disable space-before-function-paren */

const { c } = require('hyperschema/runtime')
const external0 = require('../../legacy.js')

const VERSION = 1

// eslint-disable-next-line no-unused-vars
let version = VERSION

// @autobase/checkout
const encoding0 = {
  preencode(state, m) {
    c.fixed32.preencode(state, m.key)
    c.uint.preencode(state, m.length)
  },
  encode(state, m) {
    c.fixed32.encode(state, m.key)
    c.uint.encode(state, m.length)
  },
  decode(state) {
    const r0 = c.fixed32.decode(state)
    const r1 = c.uint.decode(state)

    return {
      key: r0,
      length: r1
    }
  }
}

// @autobase/clock
const encoding1 = c.array(encoding0)

// @autobase/index-checkpoint
const encoding2 = {
  preencode(state, m) {
    c.fixed64.preencode(state, m.signature)
    c.uint.preencode(state, m.length)
  },
  encode(state, m) {
    c.fixed64.encode(state, m.signature)
    c.uint.encode(state, m.length)
  },
  decode(state) {
    const r0 = c.fixed64.decode(state)
    const r1 = c.uint.decode(state)

    return {
      signature: r0,
      length: r1
    }
  }
}

const encoding3 = external0.Wakeup

const encoding4 = external0.BootRecordV0

// @autobase/boot-record-raw
const encoding5 = {
  preencode(state, m) {
    c.fixed32.preencode(state, m.key)
    c.uint.preencode(state, m.systemLength)
    state.end++ // max flag is 4 so always one byte

    if (m.recoveries) c.uint.preencode(state, m.recoveries)
  },
  encode(state, m) {
    const flags = (m.indexersUpdated ? 1 : 0) | (m.fastForwarding ? 2 : 0) | (m.recoveries ? 4 : 0)

    c.fixed32.encode(state, m.key)
    c.uint.encode(state, m.systemLength)
    c.uint.encode(state, flags)

    if (m.recoveries) c.uint.encode(state, m.recoveries)
  },
  decode(state) {
    const v = c.uint.decode(state)
    const r0 = c.fixed32.decode(state)
    const r1 = c.uint.decode(state)
    const flags = c.uint.decode(state)

    return {
      version: v,
      key: r0,
      systemLength: r1,
      indexersUpdated: (flags & 1) !== 0,
      fastForwarding: (flags & 2) !== 0,
      recoveries: (flags & 4) !== 0 ? c.uint.decode(state) : 0
    }
  }
}

// @autobase/boot-record
const encoding6 = {
  preencode(state, m) {
    c.uint.preencode(state, m.version)
    switch (m.version) {
      case 0:
        encoding4.preencode(state, m)
        break
      case 1:
      case 2:
      case 3:
        encoding5.preencode(state, m)
        break
      default:
        throw new Error('Unsupported version')
    }
  },
  encode(state, m) {
    c.uint.encode(state, m.version)
    switch (m.version) {
      case 0:
        encoding4.encode(state, m)
        break
      case 1:
      case 2:
      case 3:
        encoding5.encode(state, m)
        break
      default:
        throw new Error('Unsupported version')
    }
  },
  decode(state) {
    const start = state.start
    const v = c.uint.decode(state)
    state.start = start
    switch (v) {
      case 0: {
        const decoded = encoding4.decode(state)
        return decoded
      }
      case 1:
      case 2:
      case 3: {
        const decoded = encoding5.decode(state)
        return decoded
      }
      default:
        throw new Error('Unsupported version')
    }
  }
}

// @autobase/checkpointer
const encoding7 = {
  preencode(state, m) {
    state.end++ // flags are fixed size

    if (m.checkpointer) c.uint.preencode(state, m.checkpointer)
    if (m.checkpoint) encoding2.preencode(state, m.checkpoint)
  },
  encode(state, m) {
    const flags = (m.checkpointer ? 1 : 0) | (m.checkpoint ? 2 : 0)

    c.uint8.encode(state, flags)

    if (m.checkpointer) c.uint.encode(state, m.checkpointer)
    if (m.checkpoint) encoding2.encode(state, m.checkpoint)
  },
  decode(state) {
    const flags = c.uint8.decode(state)

    return {
      checkpointer: (flags & 1) !== 0 ? c.uint.decode(state) : 0,
      checkpoint: (flags & 2) !== 0 ? encoding2.decode(state) : null
    }
  }
}

// @autobase/checkpoint.user
const encoding8_2 = c.array(encoding7)

// @autobase/checkpoint
const encoding8 = {
  preencode(state, m) {
    state.end++ // max flag is 4 so always one byte

    if (m.system) encoding7.preencode(state, m.system)
    if (m.encryption) encoding7.preencode(state, m.encryption)
    if (m.user) encoding8_2.preencode(state, m.user)
  },
  encode(state, m) {
    const flags = (m.system ? 1 : 0) | (m.encryption ? 2 : 0) | (m.user ? 4 : 0)

    c.uint.encode(state, flags)

    if (m.system) encoding7.encode(state, m.system)
    if (m.encryption) encoding7.encode(state, m.encryption)
    if (m.user) encoding8_2.encode(state, m.user)
  },
  decode(state) {
    const flags = c.uint.decode(state)

    return {
      system: (flags & 1) !== 0 ? encoding7.decode(state) : null,
      encryption: (flags & 2) !== 0 ? encoding7.decode(state) : null,
      user: (flags & 4) !== 0 ? encoding8_2.decode(state) : null
    }
  }
}

// @autobase/digest
const encoding9 = {
  preencode(state, m) {
    state.end++ // max flag is 2 so always one byte

    if (m.pointer) c.uint.preencode(state, m.pointer)
    if (m.key) c.fixed32.preencode(state, m.key)
  },
  encode(state, m) {
    const flags = (m.pointer ? 1 : 0) | (m.key ? 2 : 0)

    c.uint.encode(state, flags)

    if (m.pointer) c.uint.encode(state, m.pointer)
    if (m.key) c.fixed32.encode(state, m.key)
  },
  decode(state) {
    const flags = c.uint.decode(state)

    return {
      pointer: (flags & 1) !== 0 ? c.uint.decode(state) : 0,
      key: (flags & 2) !== 0 ? c.fixed32.decode(state) : null
    }
  }
}

// @autobase/node
const encoding10 = {
  preencode(state, m) {
    encoding1.preencode(state, m.heads)
    c.uint.preencode(state, m.batch)
    c.optionalBuffer.preencode(state, m.value)
  },
  encode(state, m) {
    encoding1.encode(state, m.heads)
    c.uint.encode(state, m.batch)
    c.optionalBuffer.encode(state, m.value)
  },
  decode(state) {
    const r0 = encoding1.decode(state)
    const r1 = c.uint.decode(state)
    const r2 = c.optionalBuffer.decode(state)

    return {
      heads: r0,
      batch: r1,
      value: r2
    }
  }
}

// @autobase/user-view-trace.blocks
const encoding11_1 = c.array(c.uint)

// @autobase/user-view-trace
const encoding11 = {
  preencode(state, m) {
    c.uint.preencode(state, m.view)
    encoding11_1.preencode(state, m.blocks)
  },
  encode(state, m) {
    c.uint.encode(state, m.view)
    encoding11_1.encode(state, m.blocks)
  },
  decode(state) {
    const r0 = c.uint.decode(state)
    const r1 = encoding11_1.decode(state)

    return {
      view: r0,
      blocks: r1
    }
  }
}

// @autobase/trace.system
const encoding12_0 = encoding11_1
// @autobase/trace.encryption
const encoding12_1 = encoding11_1
// @autobase/trace.user
const encoding12_2 = c.array(encoding11)

// @autobase/trace
const encoding12 = {
  preencode(state, m) {
    encoding12_0.preencode(state, m.system)
    encoding12_1.preencode(state, m.encryption)
    encoding12_2.preencode(state, m.user)
  },
  encode(state, m) {
    encoding12_0.encode(state, m.system)
    encoding12_1.encode(state, m.encryption)
    encoding12_2.encode(state, m.user)
  },
  decode(state) {
    const r0 = encoding12_0.decode(state)
    const r1 = encoding12_1.decode(state)
    const r2 = encoding12_2.decode(state)

    return {
      system: r0,
      encryption: r1,
      user: r2
    }
  }
}

const encoding13 = external0.OplogMessageV0

const encoding14 = external0.OplogMessageV1

// @autobase/oplog-message-v2.checkpoint
const encoding15_1 = c.frame(encoding8)
// @autobase/oplog-message-v2.digest
const encoding15_2 = c.frame(encoding9)
// @autobase/oplog-message-v2.trace
const encoding15_4 = c.frame(encoding12)

// @autobase/oplog-message-v2
const encoding15 = {
  preencode(state, m) {
    encoding10.preencode(state, m.node)
    state.end++ // max flag is 8 so always one byte

    if (m.checkpoint) encoding15_1.preencode(state, m.checkpoint)
    if (m.digest) encoding15_2.preencode(state, m.digest)
    if (m.trace) encoding15_4.preencode(state, m.trace)
  },
  encode(state, m) {
    const flags =
      (m.checkpoint ? 1 : 0) | (m.digest ? 2 : 0) | (m.optimistic ? 4 : 0) | (m.trace ? 8 : 0)

    encoding10.encode(state, m.node)
    c.uint.encode(state, flags)

    if (m.checkpoint) encoding15_1.encode(state, m.checkpoint)
    if (m.digest) encoding15_2.encode(state, m.digest)
    if (m.trace) encoding15_4.encode(state, m.trace)
  },
  decode(state) {
    const v = c.uint.decode(state)
    const r0 = encoding10.decode(state)
    const flags = c.uint.decode(state)

    return {
      version: v,
      node: r0,
      checkpoint: (flags & 1) !== 0 ? encoding15_1.decode(state) : null,
      digest: (flags & 2) !== 0 ? encoding15_2.decode(state) : null,
      optimistic: (flags & 4) !== 0,
      trace: (flags & 8) !== 0 ? encoding15_4.decode(state) : null
    }
  }
}

// @autobase/oplog-message
const encoding16 = {
  preencode(state, m) {
    c.uint.preencode(state, m.version)
    switch (m.version) {
      case 0:
        encoding13.preencode(state, m)
        break
      case 1:
        encoding14.preencode(state, m)
        break
      case 2:
        encoding15.preencode(state, m)
        break
      default:
        throw new Error('Unsupported version')
    }
  },
  encode(state, m) {
    c.uint.encode(state, m.version)
    switch (m.version) {
      case 0:
        encoding13.encode(state, m)
        break
      case 1:
        encoding14.encode(state, m)
        break
      case 2:
        encoding15.encode(state, m)
        break
      default:
        throw new Error('Unsupported version')
    }
  },
  decode(state) {
    const start = state.start
    const v = c.uint.decode(state)
    state.start = start
    switch (v) {
      case 0: {
        const decoded = encoding13.decode(state)
        return decoded
      }
      case 1: {
        const decoded = encoding14.decode(state)
        return decoded
      }
      case 2: {
        const decoded = encoding15.decode(state)
        return decoded
      }
      default:
        throw new Error('Unsupported version')
    }
  }
}

// @autobase/info-v1.pendingIndexers
const encoding17_1 = c.array(c.fixed32)

// @autobase/info-v1
const encoding17 = {
  preencode(state, m) {
    c.uint.preencode(state, m.members)
    encoding17_1.preencode(state, m.pendingIndexers)
    encoding1.preencode(state, m.indexers)
    encoding1.preencode(state, m.heads)
    encoding1.preencode(state, m.views)
  },
  encode(state, m) {
    c.uint.encode(state, m.members)
    encoding17_1.encode(state, m.pendingIndexers)
    encoding1.encode(state, m.indexers)
    encoding1.encode(state, m.heads)
    encoding1.encode(state, m.views)
  },
  decode(state) {
    const v = c.uint.decode(state)
    const r0 = c.uint.decode(state)
    const r1 = encoding17_1.decode(state)
    const r2 = encoding1.decode(state)
    const r3 = encoding1.decode(state)
    const r4 = encoding1.decode(state)

    return {
      version: v,
      members: r0,
      pendingIndexers: r1,
      indexers: r2,
      heads: r3,
      views: r4
    }
  }
}

// @autobase/info-v2.pendingIndexers
const encoding18_1 = encoding17_1

// @autobase/info-v2
const encoding18 = {
  preencode(state, m) {
    c.uint.preencode(state, m.members)
    encoding18_1.preencode(state, m.pendingIndexers)
    encoding1.preencode(state, m.indexers)
    encoding1.preencode(state, m.heads)
    encoding1.preencode(state, m.views)
    c.uint.preencode(state, m.encryptionLength)
    state.end++ // max flag is 1 so always one byte

    if (m.entropy) c.fixed32.preencode(state, m.entropy)
  },
  encode(state, m) {
    const flags = m.entropy ? 1 : 0

    c.uint.encode(state, m.members)
    encoding18_1.encode(state, m.pendingIndexers)
    encoding1.encode(state, m.indexers)
    encoding1.encode(state, m.heads)
    encoding1.encode(state, m.views)
    c.uint.encode(state, m.encryptionLength)
    c.uint.encode(state, flags)

    if (m.entropy) c.fixed32.encode(state, m.entropy)
  },
  decode(state) {
    const v = c.uint.decode(state)
    const r0 = c.uint.decode(state)
    const r1 = encoding18_1.decode(state)
    const r2 = encoding1.decode(state)
    const r3 = encoding1.decode(state)
    const r4 = encoding1.decode(state)
    const r5 = c.uint.decode(state)
    const flags = c.uint.decode(state)

    return {
      version: v,
      members: r0,
      pendingIndexers: r1,
      indexers: r2,
      heads: r3,
      views: r4,
      encryptionLength: r5,
      entropy: (flags & 1) !== 0 ? c.fixed32.decode(state) : null
    }
  }
}

// @autobase/info
const encoding19 = {
  preencode(state, m) {
    c.uint.preencode(state, m.version)
    switch (m.version) {
      case 0:
      case 1:
        encoding17.preencode(state, m)
        break
      case 2:
        encoding18.preencode(state, m)
        break
      default:
        throw new Error('Unsupported version')
    }
  },
  encode(state, m) {
    c.uint.encode(state, m.version)
    switch (m.version) {
      case 0:
      case 1:
        encoding17.encode(state, m)
        break
      case 2:
        encoding18.encode(state, m)
        break
      default:
        throw new Error('Unsupported version')
    }
  },
  decode(state) {
    const start = state.start
    const v = c.uint.decode(state)
    state.start = start
    switch (v) {
      case 0:
      case 1: {
        const decoded = encoding17.decode(state)
        const map = external0.infoLegacyMap
        return map(decoded)
      }
      case 2: {
        const decoded = encoding18.decode(state)
        return decoded
      }
      default:
        throw new Error('Unsupported version')
    }
  }
}

// @autobase/member
const encoding20 = {
  preencode(state, m) {
    state.end++ // max flag is 2 so always one byte
    c.uint.preencode(state, m.length)
  },
  encode(state, m) {
    const flags = (m.isIndexer ? 1 : 0) | (m.isRemoved ? 2 : 0)

    c.uint.encode(state, flags)
    c.uint.encode(state, m.length)
  },
  decode(state) {
    const flags = c.uint.decode(state)

    return {
      isIndexer: (flags & 1) !== 0,
      isRemoved: (flags & 2) !== 0,
      length: c.uint.decode(state)
    }
  }
}

const encoding21 = external0.LinearizerKey

// @autobase/linearizer-update
const encoding22 = {
  preencode(state, m) {
    c.fixed32.preencode(state, m.key)
    c.uint.preencode(state, m.length)
    c.uint.preencode(state, m.batch)
    c.uint.preencode(state, m.systemLength)
    state.end++ // max flag is 1 so always one byte
  },
  encode(state, m) {
    const flags = m.indexers ? 1 : 0

    c.fixed32.encode(state, m.key)
    c.uint.encode(state, m.length)
    c.uint.encode(state, m.batch)
    c.uint.encode(state, m.systemLength)
    c.uint.encode(state, flags)
  },
  decode(state) {
    const r0 = c.fixed32.decode(state)
    const r1 = c.uint.decode(state)
    const r2 = c.uint.decode(state)
    const r3 = c.uint.decode(state)
    const flags = c.uint.decode(state)

    return {
      key: r0,
      length: r1,
      batch: r2,
      systemLength: r3,
      indexers: (flags & 1) !== 0
    }
  }
}

// @autobase/encryption-descriptor
const encoding23 = {
  preencode(state, m) {
    c.uint.preencode(state, m.type)
    c.optionalBuffer.preencode(state, m.payload)
  },
  encode(state, m) {
    c.uint.encode(state, m.type)
    c.optionalBuffer.encode(state, m.payload)
  },
  decode(state) {
    const r0 = c.uint.decode(state)
    const r1 = c.optionalBuffer.decode(state)

    return {
      type: r0,
      payload: r1
    }
  }
}

// @autobase/manifest-data
const encoding24 = {
  preencode(state, m) {
    c.uint.preencode(state, m.version)
    state.end++ // max flag is 2 so always one byte

    if (m.legacyBlocks) c.uint.preencode(state, m.legacyBlocks)
    if (m.namespace) c.fixed32.preencode(state, m.namespace)
  },
  encode(state, m) {
    const flags = (m.legacyBlocks ? 1 : 0) | (m.namespace ? 2 : 0)

    c.uint.encode(state, m.version)
    c.uint.encode(state, flags)

    if (m.legacyBlocks) c.uint.encode(state, m.legacyBlocks)
    if (m.namespace) c.fixed32.encode(state, m.namespace)
  },
  decode(state) {
    const r0 = c.uint.decode(state)
    const flags = c.uint.decode(state)

    return {
      version: r0,
      legacyBlocks: (flags & 1) !== 0 ? c.uint.decode(state) : 0,
      namespace: (flags & 2) !== 0 ? c.fixed32.decode(state) : null
    }
  }
}

function setVersion(v) {
  version = v
}

function encode(name, value, v = VERSION) {
  version = v
  return c.encode(getEncoding(name), value)
}

function decode(name, buffer, v = VERSION) {
  version = v
  return c.decode(getEncoding(name), buffer)
}

function getEnum(name) {
  switch (name) {
    default:
      throw new Error('Enum not found ' + name)
  }
}

function getEncoding(name) {
  switch (name) {
    case '@autobase/checkout':
      return encoding0
    case '@autobase/clock':
      return encoding1
    case '@autobase/index-checkpoint':
      return encoding2
    case '@autobase/wakeup':
      return encoding3
    case '@autobase/boot-record-v0':
      return encoding4
    case '@autobase/boot-record-raw':
      return encoding5
    case '@autobase/boot-record':
      return encoding6
    case '@autobase/checkpointer':
      return encoding7
    case '@autobase/checkpoint':
      return encoding8
    case '@autobase/digest':
      return encoding9
    case '@autobase/node':
      return encoding10
    case '@autobase/user-view-trace':
      return encoding11
    case '@autobase/trace':
      return encoding12
    case '@autobase/oplog-message-v0':
      return encoding13
    case '@autobase/oplog-message-v1':
      return encoding14
    case '@autobase/oplog-message-v2':
      return encoding15
    case '@autobase/oplog-message':
      return encoding16
    case '@autobase/info-v1':
      return encoding17
    case '@autobase/info-v2':
      return encoding18
    case '@autobase/info':
      return encoding19
    case '@autobase/member':
      return encoding20
    case '@autobase/linearizer-key':
      return encoding21
    case '@autobase/linearizer-update':
      return encoding22
    case '@autobase/encryption-descriptor':
      return encoding23
    case '@autobase/manifest-data':
      return encoding24
    default:
      throw new Error('Encoder not found ' + name)
  }
}

function getStruct(name, v = VERSION) {
  const enc = getEncoding(name)
  return {
    preencode(state, m) {
      version = v
      enc.preencode(state, m)
    },
    encode(state, m) {
      version = v
      enc.encode(state, m)
    },
    decode(state) {
      version = v
      return enc.decode(state)
    }
  }
}

const resolveStruct = getStruct // compat

module.exports = {
  resolveStruct,
  getStruct,
  getEnum,
  getEncoding,
  encode,
  decode,
  setVersion,
  version
}
const b4a = require('b4a')
const ReadyResource = require('ready-resource')
const debounceify = require('debounceify')
const c = require('compact-encoding')
const safetyCatch = require('safety-catch')
const hypercoreId = require('hypercore-id-encoding')
const assert = require('nanoassert')
const SignalPromise = require('signal-promise')
const CoreCoupler = require('core-coupler')
const ProtomuxWakeup = require('protomux-wakeup')
const rrp = require('resolve-reject-promise')
const Hypercore = require('hypercore')
const ScopeLock = require('scope-lock')
const encryptionEncoding = require('encryption-encoding')

const LocalState = require('./lib/local-state.js')
const Linearizer = require('./lib/linearizer.js')
const SystemView = require('./lib/system.js')
const { EncryptionView } = require('./lib/encryption.js')
const UpdateChanges = require('./lib/updates.js')
const messages = require('./lib/messages.js')
const Timer = require('./lib/timer.js')
const Writer = require('./lib/writer.js')
const { encodeValue, decodeValue } = require('./lib/values.js')
const ActiveWriters = require('./lib/active-writers.js')
const AutoWakeup = require('./lib/wakeup.js')

const FastForward = require('./lib/fast-forward.js')
const AutoStore = require('./lib/store.js')
const ApplyState = require('./lib/apply-state.js')
const AppendBatch = require('./lib/append-batch.js')
const { PublicApplyCalls } = require('./lib/apply-calls.js')
const boot = require('./lib/boot.js')
const { MAX_AUTOBASE_VERSION, BOOT_RECORD_VERSION } = require('./lib/caps.js')

const inspect = Symbol.for('nodejs.util.inspect.custom')
const INTERRUPT = new Error('Apply interrupted')
const BINARY_ENCODING = c.from('binary')

const RECOVERIES = 3

// default is to automatically ack
const DEFAULT_ACK_INTERVAL = 10_000
const DEFAULT_ACK_THRESHOLD = 4

const REMOTE_ADD_BATCH = 64
const REMOTE_ADD_BATCH_SANITY = 4096
const MIN_FF_WAIT = 300_000 // wait at least 5min before attempting to ff again after failure

class WakeupHandler {
  constructor(base, discoveryKey) {
    this.active = true
    this.discoveryKey = discoveryKey
    this.base = base
  }

  onpeeractive(peer, session) {
    this.base._bumpWakeupPeer(peer)
  }

  onlookup(req, peer, session) {
    const wakeup = this.base._getWakeup()
    if (wakeup.length === 0) return
    session.announce(peer, wakeup)
  }

  onannounce(wakeup, peer, session) {
    if (this.base.isFastForwarding()) {
      this.base._needsWakeupRequest = true
      return
    }
    this.base.hintWakeup(wakeup)
  }
}

module.exports = class Autobase extends ReadyResource {
  constructor(store, bootstrap, handlers = {}) {
    if (Array.isArray(bootstrap)) bootstrap = bootstrap[0] // TODO: just a quick compat, lets remove soon

    if (bootstrap && typeof bootstrap !== 'string' && !b4a.isBuffer(bootstrap)) {
      handlers = bootstrap
      bootstrap = null
    }

    super()

    const key = bootstrap ? toKey(bootstrap) : null

    this.id = null
    this.key = key
    this.discoveryKey = null
    this.backoff = handlers.backoff || null

    this.keyPair = null
    this.valueEncoding = c.from(handlers.valueEncoding || 'binary')
    this.store = store
    this.globalCache = store.globalCache || null
    this.migrated = false

    this.encrypted = handlers.encrypted || !!handlers.encryptionKey
    this.encrypt = !!handlers.encrypt
    this.encryptionKey = null
    this.encryption = null
    this.blindEncryption = handlers.blindEncryption || null

    this.activeBatch = null // maintained by the append-batch

    this.local = null
    this.localWriter = null
    this.isIndexer = false

    this.activeWriters = new ActiveWriters()
    this.linearizer = null
    this.updating = false

    this.nukeTip = !!handlers.nukeTip

    this.wakeupOwner = !handlers.wakeup
    this.wakeupCapability = null
    this.wakeupProtocol = handlers.wakeup || new ProtomuxWakeup()
    this.wakeupSession = null

    this._primaryBootstrap = null

    this.fastForwardEnabled = handlers.fastForward !== false
    this.fastForwarding = null
    this.fastForwardTo = null
    this.fastForwardFailedAt = 0
    this.fastForwardMinimum = FastForward.MINIMUM

    this.bigBatches = !!handlers.bigBatches

    this._bootstrapWriters = [] // might contain dups, but thats ok
    this._bootstrapWritersChanged = false

    this._flushSignal = new SignalPromise()
    this._flushing = 0

    this._checkWriters = []
    this._optimistic = -1
    this._appended = 0
    this._appending = null
    this._wakeup = new AutoWakeup(this)
    this._wakeupHints = new Map()
    this._wakeupPeerBound = this._wakeupPeer.bind(this)
    this._coupler = null

    this._updateLocalCore = null
    this._lock = new ScopeLock()

    this._needsWakeupRequest = false
    this._needsWakeup = true
    this._needsWakeupHeads = true

    this._updates = []
    this._handlers = handlers || {}
    this._warn = emitWarning.bind(this)
    this._lastError = null

    this._draining = false
    this._writable = null
    this._advancing = null
    this._interrupting = false
    this._caughtup = false

    this.paused = false

    this._bump = debounceify(() => {
      this._advancing = this._advance()
      return this._advancing
    })

    this._onremotewriterchangeBound = this._onremotewriterchange.bind(this)
    this._onlocalwriterchangeBound = this._onlocalwriterchange.bind(this)

    this._preopen = null

    this._hasOpen = !!this._handlers.open
    this._hasApply = !!this._handlers.apply
    this._hasOptimisticApply = !!this._handlers.optimistic
    this._hasUpdate = !!this._handlers.update
    this._hasClose = !!this._handlers.close

    this._viewStore = new AutoStore(this)
    this._applyState = null

    this.view = null
    this.core = null
    this.version = -1
    this.interrupted = null
    this.recoveries = RECOVERIES

    const { ackInterval = DEFAULT_ACK_INTERVAL, ackThreshold = DEFAULT_ACK_THRESHOLD } = handlers

    this._ackInterval = ackInterval
    this._ackThreshold = ackThreshold
    this._ackTickThreshold = ackThreshold
    this._ackTick = 0

    this._ackTimer = null
    this._acking = false

    this._waiting = new SignalPromise()

    this.view = this._hasOpen
      ? this._handlers.open(this._viewStore, new PublicApplyCalls(this))
      : null
    this.core = this._viewStore.get({ name: '_system' })

    if (this.fastForwardEnabled && isObject(handlers.fastForward)) {
      this._runFastForward(
        new FastForward(this, handlers.fastForward.key, { verified: false })
      ).catch(noop)
    }

    this.ready().catch(safetyCatch)
  }

  [inspect](depth, opts) {
    let indent = ''
    if (typeof opts.indentationLvl === 'number') {
      while (indent.length < opts.indentationLvl) indent += ' '
    }

    return indent + 'Autobase { ... }'
  }

  // just compat, use .key
  get bootstrap() {
    return this.key
  }

  // TODO: compat, will be removed
  get bootstraps() {
    return [this.bootstrap]
  }

  get appending() {
    return this._appending !== null && this._appending.length > 0
  }

  get writable() {
    return this.localWriter !== null && !this.localWriter.isRemoved
  }

  get ackable() {
    return this.localWriter !== null && this.localWriter.isActiveIndexer
  }

  get signedLength() {
    return this.core.signedLength
  }

  get indexedLength() {
    return this._applyState ? this._applyState.indexedLength : 0
  }

  get length() {
    return this.core.length
  }

  get flushing() {
    return this._flushing > 0
  }

  hash() {
    return this.core.treeHash()
  }

  // deprecated, use .core.key
  getSystemKey() {
    return this.core.key
  }

  get system() {
    return this._applyState && this._applyState.system
  }

  // deprecated
  async getIndexedInfo() {
    if (this.opened === false) await this.ready()
    return (
      this._applyState && this._applyState.system.getIndexedInfo(this._applyState.indexedLength)
    )
  }

  _isActiveIndexer() {
    return this.localWriter ? this.localWriter.isActiveIndexer : false
  }

  replicate(isInitiator, opts) {
    const stream = this.store.replicate(isInitiator, opts)
    this.wakeupProtocol.addStream(stream)
    return stream
  }

  heads() {
    if (!this._applyState || !this._applyState.opened) return []
    const nodes = new Array(this._applyState.system.heads.length)
    for (let i = 0; i < this._applyState.system.heads.length; i++)
      nodes[i] = this._applyState.system.heads[i]
    return nodes.sort(compareNodes)
  }

  async export() {
    const exporting = [this.store.storage.export(this.local.discoveryKey), this._viewStore.export()]

    const [local, views] = await Promise.all(exporting)

    return {
      local,
      views
    }
  }

  hintWakeup(hints) {
    if (!Array.isArray(hints)) hints = [hints]
    for (const { key, length } of hints) {
      const hex = b4a.toString(key, 'hex')
      const prev = this._wakeupHints.get(hex)
      if (!prev || length === -1 || prev < length) this._wakeupHints.set(hex, length)
    }
    this._queueBump()
  }

  setBigBatches(bool = true) {
    this.bigBatches = bool
  }

  _queueBump() {
    this._bump().catch(safetyCatch)
  }

  async _runPreOpen() {
    if (this._handlers.wait) await this._handlers.wait()

    await this.store.ready()

    this.keyPair = (await this._handlers.keyPair) || null

    if (this._handlers.encryptionKey !== null) {
      this.encryptionKey = await this._handlers.encryptionKey
    }

    const result = await boot(this.store, this.key, {
      encryptionKey: this.encryptionKey,
      encrypt: this.encrypt,
      keyPair: this.keyPair,
      blindEncryption: this.blindEncryption
    })

    this._primaryBootstrap = result.bootstrap
    this.local = result.local

    this.key = result.bootstrap.key
    this.discoveryKey = result.bootstrap.discoveryKey
    this.id = result.bootstrap.id

    this.encryptionKey = result.encryptionKey

    if (this.encrypted) {
      assert(this.encryptionKey !== null, 'Encryption key is expected')
    }

    if (this.encryptionKey) {
      this.encryption = new EncryptionView(this, null)

      this.local.setEncryption(this.getWriterEncryption())
      this._primaryBootstrap.setEncryption(this.getWriterEncryption())
    }

    if (this.nukeTip) await this._nukeTip()

    this.local.on('append', this._onlocalwriterchangeBound)

    this.wakeupCapability = (await this._handlers.wakeupCapability) || {
      key: this.key,
      discoveryKey: this.discoveryKey
    }
    this.setWakeup(this.wakeupCapability.key, this.wakeupCapability.discoveryKey)
  }

  async _nukeTipBatch(key, length) {
    const core = this.store.get({ key, active: false })
    await core.ready()

    const batch = core.session({ name: 'batch' })
    await batch.ready()
    if (batch.length > length) await batch.truncate(length)
    await batch.close()

    await core.close()
  }

  // TODO: not atomic atm, so more of a (very useful) debug helper
  async _nukeTip() {
    const pointer = await this.local.getUserData('autobase/boot')
    if (!pointer) return

    const boot = c.decode(messages.BootRecord, pointer)

    await LocalState.clear(this.local)

    await this._nukeTipBatch(boot.key, boot.systemLength)

    const core = this.store.get({ key: boot.key, active: false, encryption: null })
    const encCore = await EncryptionView.setSystemEncryption(this, core)

    const batch = core.session({ name: 'batch' })
    await batch.ready()

    const info = await SystemView.getIndexedInfo(batch, boot.systemLength)
    await batch.close()

    for (const view of info.views) {
      // ensure any views ref'ed by system are consistent as well
      await this._nukeTipBatch(view.key, view.length)
    }

    if (boot.heads) this.hintWakeup(boot.heads)
    if (this.local.length) this.hintWakeup([{ key: this.local.key, length: this.local.length }])

    await core.close()
    if (encCore) await encCore.close()
  }

  _bumpWakeupPeer(peer) {
    if (this._coupler) this._coupler.update(peer.stream)
  }

  async _rotateLocalWriter(newLocal) {
    assert(!this.appending, 'Cannot rotate a newLocal writer if an append is in progress')

    const oldLocal = this.local

    if (this._applyState) await this._applyState.close()

    this.local = newLocal
    this.local.on('append', this._onlocalwriterchangeBound)

    this.localWriter = null
    this._updateLocalCore = null

    const store = this._viewStore.atomize()

    const local = store.getLocal()
    await local.ready()

    await LocalState.moveTo(oldLocal, local)

    await local.setUserData('referrer', this.key)
    if (this.encryptionKey) {
      if (this.blindEncryption) {
        const encrypted = await encryptionEncoding.encrypt(
          this.encryptionKey,
          this.blindEncryption.encrypt
        )
        await local.setUserData('autobase/blind-encryption', encrypted)
      } else {
        await local.setUserData('autobase/encryption', this.encryptionKey)
      }
    }

    await store.flush()
    await store.close()

    await this._primaryBootstrap.setUserData('autobase/local', local.key)

    await this._clearWriters()
    await oldLocal.close()

    // done, soft reboot

    await this._viewStore.updateLocal()

    await this._clearWriters()

    this._applyState = new ApplyState(this)
    await this._applyState.ready()
    await this._makeLinearizerFromViewState()

    this._caughtup = false

    this._rebooted()

    this.emit('rotate-local-writer')
  }

  async setLocal(key, { keyPair } = {}) {
    if (!this.opened) await this.ready()

    const manifest = keyPair
      ? { version: this.store.manifestVersion, signers: [{ publicKey: keyPair.publicKey }] }
      : null
    if (!key) key = Hypercore.key(manifest)
    // If the keys are the same, no need to rotate
    if (b4a.equals(key, this.local.key)) return

    const encryption = this.encryptionKey ? this.getWriterEncryption() : null

    const local = this.store.get({
      key,
      manifest,
      active: false,
      exclusive: true,
      encryption,
      valueEncoding: messages.OplogMessage
    })
    await local.ready()

    this._updateLocalCore = local

    let runs = 0
    while (!this._interrupting && this.appending && runs++ < 16) await this.update()
    await this._bump()
  }

  setWakeup(cap, discoveryKey) {
    if (this.wakeupSession) this.wakeupSession.destroy()
    if (!discoveryKey && b4a.equals(cap, this.key)) discoveryKey = this.discoveryKey
    this.wakeupSession = this.wakeupProtocol.session(
      cap,
      new WakeupHandler(this, discoveryKey || null)
    )

    // incase this session has active peers already, bump the coupler
    for (const peer of this.wakeupSession.peers) {
      if (peer.active) this._bumpWakeupPeer(peer)
    }
  }

  async _getMigrationPointer(key, length) {
    const core = this.store.get({ key, active: false, encryption: null })
    const encCore = await EncryptionView.setSystemEncryption(this, core)

    const min = core.manifest && core.manifest.prologue ? core.manifest.prologue.length : 0

    for (let i = length - 1; i >= min; i--) {
      if (!(await core.has(i))) continue

      const sys = new SystemView(core, { checkout: i + 1 })
      await sys.ready()

      let good = true

      for (const v of sys.views) {
        const vc = this.store.get({ key: v.key, active: false })
        await vc.ready()
        if (vc.length < v.length) good = false
        await vc.close()
      }

      await sys.close()
      if (!good) continue

      return i + 1
    }

    await core.close()
    if (encCore) await encCore.close()

    return min
  }

  // migrating from 6 -> latest
  async _migrate6(key, length) {
    const core = this.store.get({ key, active: false })
    await core.ready()
    const batch = core.session({ name: 'batch', overwrite: true, checkout: length })
    await batch.ready()
    await batch.close()
    await core.close()
  }

  // called by view-store for bootstrapping
  async _getSystemInfo() {
    const boot = await this._getBootRecord()
    if (!boot.key) return null

    const migrated = !!boot.heads
    const indexedLength = boot.systemLength

    if (migrated) {
      // ensure system batch is consistent on initial migration
      await this._migrate6(boot.key, indexedLength)
    }

    const core = this.store.get({ key: boot.key, encryption: null, active: false })
    await core.ready()

    const batch = core.session({ name: 'batch' })
    const encCore = await EncryptionView.setSystemEncryption(this, batch)

    await batch.ready()
    if (encCore) await encCore.ready()

    const info = await SystemView.getIndexedInfo(batch, indexedLength)

    if (encCore) await encCore.close()
    await batch.close()
    await core.close()

    if (info.version > MAX_AUTOBASE_VERSION) {
      throw new Error('Autobase upgrade required.')
    }

    // just compat
    if (migrated) {
      this.migrated = true

      for (const view of info.views) {
        // ensure any views ref'ed by system are consistent as well
        await this._migrate6(view.key, view.length)
      }

      if (boot.heads) this.hintWakeup(boot.heads)
      if (this.local.length) this.hintWakeup([{ key: this.local.key, length: this.local.length }])
    }

    return {
      key: boot.key,
      indexers: info.indexers,
      views: info.views,
      entropy: info.entropy
    }
  }

  // called by the apply state for bootstrapping
  async _getBootRecord() {
    await this._preopen

    const pointer = await this.local.getUserData('autobase/boot')

    const boot = pointer
      ? c.decode(messages.BootRecord, pointer)
      : {
          version: BOOT_RECORD_VERSION,
          key: null,
          systemLength: 0,
          indexersUpdated: false,
          fastForwarding: false,
          recoveries: RECOVERIES,
          heads: null
        }

    if (boot.heads) {
      const len = await this._getMigrationPointer(boot.key, boot.systemLength)
      if (len !== boot.systemLength)
        this._warn(
          new Error(
            'Invalid pointer in migration, correcting (' + len + ' vs ' + boot.systemLength + ')'
          )
        )
      boot.systemLength = len
    }

    return boot
  }

  static async getBootRecord(store, key) {
    const result = await boot(store, key, { exclusive: false })
    await result.bootstrap.close()
    await result.local.close()
    return result.boot
  }

  _interrupt(reason) {
    assert(!!this._applyState.applying, 'Interrupt is only allowed in apply')
    this._interrupting = true
    if (reason) this.interrupted = reason
    throw INTERRUPT
  }

  async flush() {
    if (this._flushing === 0) return
    return this._flushSignal.wait()
  }

  async advance() {
    if (this.opened === false) await this.ready()
    await this._advancing
  }

  recouple() {
    if (this._coupler) this._coupler.destroy()
    const core = this._viewStore.getSystemCore()
    this._coupler = new CoreCoupler(core, this._wakeupPeerBound)
  }

  _updateBootstrapWriters() {
    const writers = this.linearizer.getBootstrapWriters()

    // first clear all, but without applying it for churn reasons
    for (const writer of this._bootstrapWriters) {
      writer.isBootstrap = false
      writer.isCoupled = false
    }

    // all passed are bootstraps
    for (const writer of writers) {
      writer.isCoupled = true
      writer.setBootstrap(true)
    }

    // reset activity on old ones, all should be in sync now
    for (const writer of this._bootstrapWriters) {
      if (writer.isBootstrap === false) writer.setBootstrap(false)
    }

    this._bootstrapWriters = writers
    this._bootstrapWritersChanged = false
  }

  async _openLinearizer() {
    if (this._applyState.system.bootstrapping) {
      await this._makeLinearizer(null)
      this._bootstrapLinearizer()
      return
    }

    await this._makeLinearizerFromViewState()
  }

  async _catchupApplyState() {
    if (await this._applyState.shouldMigrate()) {
      await this._migrate()
    } else if (!(await this._applyState.catchup(this.linearizer))) {
      return false
    }

    this._caughtup = true
    return true
  }

  async _open() {
    this._preopen = this._runPreOpen()
    await this._preopen

    if (this.closing) return

    this._applyState = new ApplyState(this)

    try {
      await this._applyState.ready()
    } catch (err) {
      if (this.closing) return

      try {
        await this._applyState.close()
      } catch {}

      try {
        await this.core.ready()
      } catch {}

      this._applyState = null
      if (this.closing) return

      throw err
    }

    try {
      await this._openLinearizer()
      await this.core.ready()
      await this._wakeup.ready()
    } catch (err) {
      if (this.closing) return
      throw err
    }

    if (this.core.length - this._applyState.indexedLength > this._ackTickThreshold) {
      this._ackTick = this._ackTickThreshold
    }
    if (this.localWriter && this._ackInterval) {
      this._startAckTimer()
    }

    this._updateBootstrapWriters()

    this.recouple()
    this._queueFastForward()

    // queue a full bump that handles wakeup etc (not legal to wait for that here)
    this._queueBump()
  }

  async _close() {
    await this.flush() // flush all pending non network io
    this._interrupting = true
    await Promise.resolve() // defer one tick

    if (this.wakeupSession) this.wakeupSession.destroy()
    if (this.wakeupOwner) this.wakeupProtocol.destroy()

    if (this.fastForwarding) await this.fastForwarding.close()

    if (this._coupler) this._coupler.destroy()
    this._coupler = null
    this._waiting.notify(null)

    await this.activeWriters.clear()

    const closing = this._advancing ? this._advancing.catch(safetyCatch) : null

    await this._clearWriters()
    if (this._primaryBootstrap) await this._primaryBootstrap.close()
    await this.local.close()

    if (this._ackTimer) {
      this._ackTimer.stop()
      await this._ackTimer.flush()
    }

    await this._wakeup.close()

    if (this._hasClose) await this._handlers.close(this.view)
    if (this._applyState) await this._applyState.close()

    await this._viewStore.close()
    await this.core.close()
    await this.store.close()

    if (this._writable) this._writable.resolve(false)

    await closing
  }

  getLastError() {
    return this._lastError
  }

  _onError(err) {
    if (this.closing) return

    this._lastError = err

    if (err === INTERRUPT) {
      this.emit('interrupt', this.interrupted)
      this.emit('update')
      return
    }

    this.close().catch(safetyCatch)

    // if no one is listening we should crash! we cannot rely on the EE here
    // as this is wrapped in a promise so instead of nextTick throw it
    if (ReadyResource.listenerCount(this, 'error') === 0) {
      crashSoon(err)
      return
    }

    this.emit('error', err)
  }

  async _closeWriter(w) {
    this.activeWriters.delete(w)
    await w.close()
  }

  async _gcWriters() {
    // just return early, why not
    if (this._checkWriters.length === 0) return

    while (this._checkWriters.length > 0) {
      const w = this._checkWriters.pop()

      // doesnt hurt
      w.updateActivity()

      if (!w.flushed()) continue

      const unqueued = this._wakeup.unqueue(w.core.key, w.core.length)

      if (!unqueued || w.isActiveIndexer) continue
      if (this.localWriter === w) continue

      await this._closeWriter(w)
    }

    await this._wakeup.flush()
  }

  _startAckTimer() {
    if (this._ackTimer) return
    this._ackTimer = new Timer(this._backgroundAck.bind(this), this._ackInterval)
    this._bumpAckTimer()
  }

  _bumpAckTimer() {
    if (!this._ackTimer) return
    this._ackTimer.bump()
  }

  async update() {
    if (this.opened === false) await this.ready()

    try {
      await this._bump()
      if (this._acking) await this._bump() // if acking just rebump incase it was triggered from above...
    } catch (err) {
      if (this._interrupting) return
      throw err
    }
  }

  // runs in bg, not allowed to throw
  // TODO: refactor so this only moves the writer affected to a updated set
  async _onremotewriterchange() {
    this._bumpAckTimer()

    try {
      await this._bump()
    } catch {}
  }

  _onlocalwriterchange() {
    if (!this.localWriter || this.localWriter.isRemoved) this._queueBump()
  }

  _onwakeup() {
    this._needsWakeup = true
    this._queueBump()
  }

  isFastForwarding() {
    if (this.fastForwardTo !== null) return true
    return this.fastForwardEnabled && this.fastForwarding !== null
  }

  _backgroundAck() {
    return this.ack(true)
  }

  async ack(bg = false) {
    if (this.opened === false) await this.ready()
    if (this.localWriter === null || this._acking || this._interrupting || this._appending !== null)
      return

    if (this._applyState === null) {
      try {
        await this._bump()
      } catch {}
      if (this._applyState === null || this._interrupting) return
    }

    const applyState = this._applyState
    if (applyState.opened === false) await applyState.ready()

    const isPendingIndexer = applyState.isLocalPendingIndexer()

    // if no one is waiting for our index manifest, wait for FF before pushing an ack
    if ((!isPendingIndexer && this.isFastForwarding()) || this._interrupting) return

    const isIndexer = applyState.isLocalIndexer() || isPendingIndexer
    if (!isIndexer) return

    this._acking = true

    try {
      await this._bump()
    } catch (err) {
      if (!this._interrupting) throw err
    }

    if (this._interrupting || !this.localWriter || this.localWriter.closed) {
      this._acking = false
      return
    }

    // avoid lumping acks together due to the bump wait here
    if (this._ackTimer && bg) await this._ackTimer.asapStandalone()
    if (this._interrupting || applyState.closing) {
      this._acking = false
      return
    }

    const alwaysWrite = isPendingIndexer || (await applyState.shouldWrite())
    if (this._interrupting || applyState.closing) {
      this._acking = false
      return
    }

    const hasPendingIndexers = applyState.system.indexers.length > this.linearizer.indexers.length

    if (alwaysWrite || this.linearizer.shouldAck(this.localWriter, hasPendingIndexers)) {
      try {
        if (this.localWriter && !this.localWriter.closed) await this.append(null)
      } catch (err) {
        if (!this._interrupting) throw err
      }
    }

    if (!this._interrupting) {
      this._updateAckThreshold()
      this._bumpAckTimer()
    }

    this._acking = false
  }

  views() {
    if (this._applyState) return this._applyState.store.listViews()
    return this._viewStore.listViews()
  }

  batch() {
    return new AppendBatch(this)
  }

  async append(value, opts) {
    if (this.opened === false) await this.ready()
    if (this._advancing !== null) await this._advancing
    while (this.activeBatch) await this.activeBatch.flushed()
    return this._appendBatch(value, opts)
  }

  async _appendBatch(value, opts) {
    if (this._interrupting) throw new Error('Autobase is closing')
    if (value && this.valueEncoding !== BINARY_ENCODING)
      value = normalize(this.valueEncoding, value)

    const optimistic = !!opts && !!opts.optimistic && !!value

    // we wanna allow acks so interdexers can flush
    if (
      !optimistic &&
      (this.localWriter === null || (this.localWriter.isRemoved && value !== null))
    ) {
      throw new Error('Not writable')
    }

    if (this._appending === null) this._appending = []

    let len = 0
    if (Array.isArray(value)) {
      for (const v of value) len = this._append(v)
    } else {
      len = this._append(value)
    }

    const localLength = this.local.length

    if (optimistic) this._optimistic = this._appending.length - 1
    const target = this._appended + this._appending.length

    // await in case append is in current tick
    if (this._advancing) await this._advancing

    let runs = 0

    // bump until we've flushed the nodes
    while (this._appended < target && !this._interrupting) {
      await this._bump()
      // safety
      if (runs++ >= 16 && this.localWriter && this.localWriter.idle()) break
    }

    if (this._advancing) await this._advancing
    if (this._interrupting) throw new Error('Autobase is closing')

    return localLength + len
  }

  _append(value) {
    // if prev value is an ack that hasnt been flushed, skip it
    if (this._appending.length > 0) {
      if (value === null) return this._appending.length
      if (this._appending[this._appending.length - 1] === null) {
        this._appending.pop()
      }
    }
    return this._appending.push(value)
  }

  static decodeValue(value, opts) {
    return decodeValue(value, opts)
  }

  static encodeValue(value, opts) {
    return encodeValue(value, opts)
  }

  static async getLocalKey(store, opts = {}) {
    const core = opts.keyPair
      ? store.get({ ...opts, active: false })
      : store.get({ ...opts, name: 'local', active: false })
    await core.ready()
    const key = core.key
    await core.close()
    return key
  }

  static getLocalCore(store, handlers, encryptionKey) {
    const encryption = !encryptionKey ? null : { key: encryptionKey }
    const opts = {
      ...handlers,
      compat: false,
      active: false,
      exclusive: true,
      valueEncoding: messages.OplogMessage,
      encryption
    }
    return opts.keyPair ? store.get(opts) : store.get({ ...opts, name: 'local' })
  }

  static async getUserData(core) {
    const view = await core.getUserData('autobase/view')

    return {
      referrer: await core.getUserData('referrer'),
      view: view ? b4a.toString(view) : null
    }
  }

  static async isAutobase(core, opts = {}) {
    const block = await core.get(0, opts)
    if (!block) throw new Error('Core is empty.')
    if (!b4a.isBuffer(block)) return isAutobaseMessage(block)

    try {
      const m = c.decode(messages.OplogMessage, block)
      return isAutobaseMessage(m)
    } catch {
      return false
    }
  }

  // no guarantees where the user data is stored, just that its associated with the base
  async setUserData(key, val) {
    await this._preopen
    const core = this._primaryBootstrap === null ? this.local : this._primaryBootstrap

    await core.setUserData(key, val)
  }

  async getUserData(key) {
    await this._preopen
    const core = this._primaryBootstrap === null ? this.local : this._primaryBootstrap

    return await core.getUserData(key)
  }

  _needsLocalWriter() {
    return this.localWriter === null || this.localWriter.closed
  }

  // no guarantees about writer.isActiveIndexer property here
  async _getWriterByKey(key, len, seen, allowGC, isAdded, system) {
    assert(this._draining === true || (this.opening && !this.opened) || this._optimistic > -1)

    await this._lock.lock()

    if (this._interrupting) {
      this._lock.unlock()
      throw new Error('Autobase is closing')
    }

    try {
      let w = this.activeWriters.get(key)

      const alreadyActive = !!w
      const sys = system || this._applyState.system
      const writerInfo = await sys.get(key)

      if (len === -1) {
        if (!allowGC && writerInfo === null) {
          if (w) w.isRemoved = !isAdded
          return null
        }

        len = writerInfo === null ? 0 : writerInfo.length
      }

      const isActive = writerInfo !== null && (isAdded || !writerInfo.isRemoved)
      const isRemoved = !isActive

      if (w) {
        w.isRemoved = isRemoved
      } else {
        w = this._makeWriter(key, len, isActive, isRemoved)
        if (!w) return null
      }

      if (isRemoved && sys.bootstrapping && b4a.equals(w.core.key, this.key)) {
        w.isRemoved = false
      }

      if (this._isLocalCore(w.core) && this._needsLocalWriter()) {
        this._setLocalWriter(w)
      }

      w.seen(seen)

      if (alreadyActive) return w

      await w.ready()

      if (this._isLocalCore(w.core) && this._needsLocalWriter()) {
        this._setLocalWriter(w)
      }

      if (allowGC && w.flushed()) {
        this._wakeup.unqueue(key, len)
        if (w !== this.localWriter) {
          await w.close()
          return w
        }
      }

      this.activeWriters.add(w)
      this._checkWriters.push(w)

      assert(w.opened)
      assert(!w.closed)

      w.updateActivity()
      return w
    } finally {
      this._lock.unlock()
    }
  }

  _updateAll() {
    const p = []
    for (const w of this.activeWriters) p.push(w.update(null).catch(safetyCatch))
    return Promise.all(p)
  }

  getWriterEncryption() {
    if (!this.encryptionKey) return null
    return this.encryption.getWriterEncryption()
  }

  _makeWriterCore(key) {
    if (this.closing) throw new Error('Autobase is closing')
    if (this._interrupting) throw INTERRUPT()

    const local = b4a.equals(key, this.local.key)
    const encryption = this.getWriterEncryption()

    const core = local
      ? this.local.session({ valueEncoding: messages.OplogMessage, encryption, active: false })
      : this.store.get({
          key,
          compat: false,
          writable: false,
          valueEncoding: messages.OplogMessage,
          encryption,
          active: false
        })

    return core
  }

  _makeWriter(key, length, isActive, isRemoved) {
    const core = this._makeWriterCore(key)
    const w = new Writer(this, core, length, isRemoved)

    if (this._isLocalCore(core)) {
      if (isActive) this._setLocalWriter(w) // only set active writer
      return w
    }

    core.on('append', this._onremotewriterchangeBound)
    core.on('download', this._onremotewriterchangeBound)
    core.on('manifest', this._onremotewriterchangeBound)

    return w
  }

  _updateLinearizer(indexers, heads) {
    // only current active indexers are reset to true below
    for (const w of this.activeWriters) w.isActiveIndexer = false
    if (this.localWriter) this.localWriter.isActiveIndexer = false

    for (const writer of indexers) writer.isActiveIndexer = true

    if (this._isActiveIndexer() && !this.isIndexer) {
      this._setLocalIndexer()
    } else if (!this._isActiveIndexer() && this.isIndexer) {
      this._clearLocalIndexer()
    }

    this.linearizer = new Linearizer(indexers, { heads, writers: this.activeWriters })

    this._updateAckThreshold()
  }

  async _updateLocalWriter(sys) {
    if (this.localWriter !== null && !this.localWriter.closed) return
    await this._getWriterByKey(this.local.key, -1, 0, true, false, sys)
  }

  async _bootstrapLinearizer() {
    const bootstrap = this._makeWriter(this.key, 0, true, false)

    this.activeWriters.add(bootstrap)
    this._checkWriters.push(bootstrap)
    await bootstrap.ready()
    this._ensureWakeup(bootstrap)

    this._updateLinearizer([bootstrap], [])
  }

  async _makeLinearizer(sys) {
    if (sys === null) {
      return this._bootstrapLinearizer()
    }

    if (this.opened || (await sys.hasLocal(this.local.key))) {
      await this._updateLocalWriter(sys)
    }

    const indexers = []

    for (const head of sys.indexers) {
      const writer = await this._getWriterByKey(head.key, head.length, 0, false, false, sys)
      indexers.push(writer)
    }

    if (!this._isActiveIndexer()) {
      for (const key of sys.pendingIndexers) {
        if (b4a.equals(key, this.local.key)) {
          this._setLocalIndexer()
          break
        }
      }
    }

    this._updateLinearizer(indexers, sys.heads)

    for (const { key, length } of sys.heads) {
      await this._getWriterByKey(key, length, 0, false, false, sys)
    }
  }

  async _clearWriters() {
    await this.activeWriters.clear()
    if (this.localWriter !== null) await this.localWriter.close()
    this._checkWriters = []
  }

  async _moveTo() {
    assert(this._applyState.closed)
    for (const ref of this._viewStore.byName.values()) {
      if (!ref.moveTo) continue
      const [batch, next] = ref.moveTo
      ref.moveTo = null
      await ref.batch.state.moveTo(batch, batch.length)
      await batch.close()
      ref.migrated(this, next)
    }
  }

  async _makeLinearizerFromViewState() {
    const sys = await this._applyState.getIndexedSystem()
    await this._makeLinearizer(sys)
    await sys.close()
  }

  async _runForceFastForward() {
    await this.core.ready()
    if (this.closing) return
    const rec = this._applyState ? await this._applyState.recoverAt() : null
    await this._runFastForward(
      new FastForward(this, this.core.key, { force: true, length: rec ? rec.length : 0 })
    )
  }

  // general repair method for trying to recover
  async repair() {
    await this.forceFastForward()
  }

  async forceFastForward() {
    if (this.isFastForwarding()) return
    await this._runForceFastForward()
    await this.update()
  }

  async _applyFastForward() {
    if (
      !this.fastForwardTo.force &&
      this.fastForwardTo.length < this.core.length + this.fastForwardTo.minimum
    ) {
      this.fastForwardTo = null
      this._updateActivity()
      // still not worth it
      return
    }

    const changes = this._hasUpdate ? new UpdateChanges(this) : null
    if (changes) changes.track(this._applyState)

    // close existing state
    if (this._applyState) await this._applyState.close()

    const { key, length, views, indexers, manifestVersion, entropy } = this.fastForwardTo

    const from = this.core.signedLength
    const ffed = new Set()

    this._flushing++
    try {
      const store = this._viewStore.atomize()

      const systemRef = await this._viewStore.findViewByKey(key, indexers, manifestVersion, entropy)
      const migrated = !b4a.equals(key, systemRef.core.key)

      ffed.add(systemRef)

      if (migrated) {
        await this._applyFastForwardMigration(systemRef, { key, length })
      } else {
        await systemRef.catchup(store.atom, length)
      }

      for (const v of views) {
        const ref = await this._viewStore.findViewByKey(v.key, indexers, manifestVersion, entropy)
        if (!ref) continue // unknown, view ignored

        const migrated = !b4a.equals(ref.core.key, v.key)

        ffed.add(ref)

        if (migrated) {
          await this._applyFastForwardMigration(ref, v)
        } else {
          await ref.catchup(store.atom, v.length)
        }
      }

      if (ffed.size !== store.getViewCount()) {
        for (const ref of store.getViews()) {
          if (ffed.has(ref)) continue
          await ref.catchup(store.atom, 0) // its gone
        }
      }

      // migrate zero length cores
      if (migrated) {
        const manifests = await this._viewStore.getIndexerManifests(indexers)

        for (const [name, ref] of this._viewStore.byName) {
          if (ffed.has(ref)) continue
          await this._migrateView(manifests, null, name, 0, manifestVersion, entropy, [], null)
        }
      }

      const value = c.encode(messages.BootRecord, {
        version: BOOT_RECORD_VERSION,
        key,
        systemLength: length,
        indexersUpdated: false,
        fastForwarding: true,
        recoveries: RECOVERIES
      })

      const local = store.getLocal()
      await local.ready()
      await local.setUserData('autobase/boot', value)

      await LocalState.clear(local)

      await store.flush()
      await store.close()
    } finally {
      if (--this._flushing === 0) this._flushSignal.notify()
    }

    for (const ref of this._viewStore.byName.values()) await ref.release()

    this.recoveries = RECOVERIES
    this.fastForwardTo = null
    this._queueFastForward()

    await this._clearWriters()
    await this._moveTo()
    const to = this.core.signedLength

    if (changes) changes.finalise()

    this._applyState = new ApplyState(this)
    await this._applyState.ready()

    if (changes) await this._handlers.update(this._applyState.view, changes)

    if (await this._applyState.shouldMigrate()) {
      await this._migrate()
    } else {
      await this._makeLinearizerFromViewState()
      await this._applyState.catchup(this.linearizer)
    }

    if (!this.localWriter || this.localWriter.closed) {
      await this._updateLocalWriter(this._applyState.system)
    }

    this._caughtup = true

    this._rebooted()
    this.emit('fast-forward', to, from)
  }

  // TODO: not atomic in regards to the ff, fix that
  async _applyFastForwardMigration(ref, v) {
    await ref.batch.ready()

    const next = this.store.get(v.key)
    await next.ready()

    const prologue = next.manifest && next.manifest.prologue

    if (prologue && prologue.length > 0 && ref.core.length >= prologue.length) {
      try {
        await next.core.copyPrologue(ref.core.state)
      } catch {
        // we might be missing some nodes for this, just ignore, only an optimisation
      }
    }

    const batch = next.session({ name: 'batch', overwrite: true, checkout: v.length })
    await batch.ready()

    // remake the batch, reset from our prologue in case it replicated inbetween
    // TODO: we should really have an HC function for this

    ref.moveTo = [batch, next]
  }

  async _migrateView(
    indexerManifests,
    source,
    name,
    indexedLength,
    manifestVersion,
    entropy,
    linked,
    manifestData
  ) {
    const ref = this._viewStore.byName.get(name)
    await ref.batch.ready()

    const prologue =
      indexedLength === 0
        ? null
        : { length: indexedLength, hash: await source.treeHash(indexedLength) }

    if (manifestData === null && ref.core.manifest.userData !== null) {
      manifestData = ref.core.manifest.userData
    }

    const next = this._viewStore.getViewCore(
      indexerManifests,
      name,
      prologue,
      manifestVersion,
      entropy,
      linked,
      manifestData
    )
    await next.ready()

    if (indexedLength > 0) {
      await next.core.copyPrologue(source.state)
    }

    // remake the batch, reset from our prologue in case it replicated inbetween
    // TODO: we should really have an HC function for this
    const batch = next.session({ name: 'batch', overwrite: true, checkout: indexedLength })
    await batch.ready()

    if (source !== null) {
      while (batch.length < source.length) {
        await batch.append(await source.get(batch.length))
      }
    }

    ref.moveTo = [batch, next]

    return ref
  }

  async _premigrate() {
    this._flushing++
    try {
      const length = this._applyState.indexedLength
      const system = this._applyState.system

      const info = await system.getIndexedInfo(length)
      const indexerManifests = await this._viewStore.getIndexerManifests(info.indexers)

      const { views, systemView, encryptionView } = this._applyState

      const manifestVersion = this._applyState.system.core.manifest.version

      for (const view of views) {
        const v = this._applyState.getViewFromSystem(view, info)
        const indexedLength = v ? v.length : 0

        const ref = this._viewStore.getViewByName(view.name)
        const source = ref.getCore()
        await source.ready()

        await this._migrateView(
          indexerManifests,
          source,
          view.name,
          indexedLength,
          manifestVersion,
          info.entropy,
          null,
          null
        )
      }

      const source = encryptionView.ref.getCore()
      await source.ready()

      const enc = await this._migrateView(
        indexerManifests,
        source,
        '_encryption',
        info.encryptionLength,
        manifestVersion,
        info.entropy,
        null,
        null
      )

      const linked = [enc.core.key]

      const sysCore = systemView.ref.getCore()
      const ref = await this._migrateView(
        indexerManifests,
        sysCore,
        '_system',
        length,
        manifestVersion,
        info.entropy,
        linked,
        null,
        null
      )

      const sysKey = ref.getNextKey()
      if (sysKey === null) {
        throw new Error('Migration failed, expected core to be moved')
      }

      return sysKey
    } finally {
      if (--this._flushing === 0) this._flushSignal.notify()
    }
  }

  async _migrate() {
    return this._reboot(await this._premigrate())
  }

  async _reboot(key) {
    await this._clearWriters()
    await this._makeLinearizerFromViewState()

    await this._applyState.finalize(key)
    await this._moveTo()

    this._applyState = new ApplyState(this)

    await this._applyState.ready()
    await this._applyState.catchup(this.linearizer)

    // end soft shutdown

    this._queueFastForward()

    this._rebooted()
  }

  _rebooted() {
    this.recouple()
    this._updateActivity()

    this.emit('reboot')

    // ensure we re-evalute our state
    this._bootstrapWritersChanged = true
    this._needsWakeup = true

    this.updating = true
    this._queueBump()
  }

  _setLocalWriter(w) {
    this.localWriter = w
    if (this._ackInterval) this._startAckTimer()
  }

  _unsetLocalWriter() {
    if (!this.localWriter) return

    this._closeWriter(this.localWriter)
    if (this.localWriter.isActiveIndexer) this._clearLocalIndexer()

    this.localWriter = null
  }

  _setLocalIndexer() {
    assert(this.localWriter !== null)

    this.isIndexer = true
    this.emit('is-indexer')
  }

  _clearLocalIndexer() {
    assert(this.localWriter !== null)

    if (this._ackTimer) this._ackTimer.stop()

    this.isIndexer = false
    this._ackTimer = null

    this.emit('is-non-indexer')
  }

  _isLocalCore(core) {
    return core.writable && core.id === this.local.id
  }

  _addLocalHeads() {
    // not writable atm, will prop be writable in a subsequent bump tho
    if (!this.localWriter || this.localWriter.closed) return null
    // safety, localwriter is still processing, should prop be an assertion
    if (!this.localWriter.idle()) return null

    const length = this._optimistic === -1 ? this._appending.length : this._optimistic || 1

    const nodes = new Array(length)
    for (let i = 0; i < length; i++) {
      const heads = this.linearizer.getHeads()
      const deps = new Set(this.linearizer.heads)
      const batch = length - i
      const value = this._appending[i]

      const node = this.localWriter.append(value, heads, batch, deps, this._optimistic === 0)

      this.linearizer.addHead(node)
      nodes[i] = node
    }

    return nodes
  }

  _flushLocalHeads(length) {
    this._appending = length === this._appending.length ? null : this._appending.slice(length)
    this._appended += length

    if (this._optimistic > -1 && this._optimistic < length) this._optimistic = -1
  }

  async _addRemoteHeads() {
    let added = 0

    while (
      added < REMOTE_ADD_BATCH ||
      (this.bigBatches && !this._indexersIdle() && added < REMOTE_ADD_BATCH_SANITY)
    ) {
      await this._updateAll()

      let advanced = 0

      for (const w of this.activeWriters) {
        let node = w.advance()
        if (node === null) continue

        advanced += node.batch

        while (true) {
          this.linearizer.addHead(node)
          if (node.batch === 1) break
          node = w.advance()
        }
      }

      if (advanced === 0) {
        if (this.bigBatches && !this._indexersIdle() && (await this._waitForIndexers())) continue
        break
      }

      added += advanced
    }

    return added
  }

  async _waitForIndexers() {
    const promises = []

    for (const w of this.linearizer.indexers) {
      if (w.idle()) continue
      if (await w.core.has(w.length)) continue
      promises.push(w.core.get(w.length, { timeout: 3000 }))
    }

    for (const result of await Promise.allSettled(promises)) {
      if (result.status !== 'rejected') return true
    }

    return false
  }

  _indexersIdle() {
    for (const w of this.linearizer.indexers) {
      if (!w.idle()) return false
    }
    return true
  }

  async _drain() {
    const writable = this.writable

    while (!this._interrupting && !this.paused) {
      if (this.local.recovering) {
        try {
          await this.local.recover()
        } catch {
          await this._runForceFastForward()
        }
        continue
      }

      if (this.fastForwardTo !== null) {
        await this._applyFastForward()
        continue // revaluate conditions...
      }

      // we defer this to post ready so its not blocking reading the views
      if (this._caughtup === false) {
        // if the catchup fails, just force ff it to recover, less user pain
        if (!(await this._catchupApplyState())) {
          await this._runForceFastForward()
        }
        continue
      }

      const remoteAdded = await this._addRemoteHeads()
      const localNodes = this._appending !== null ? this._addLocalHeads() : null

      if (this._interrupting) return

      if (remoteAdded > 0 || localNodes !== null) {
        this.updating = true
      }

      const u = this.linearizer.update()
      const update = u
        ? await this._applyState.update(u, localNodes)
        : { reboot: false, migrated: false }

      if (localNodes) this._flushLocalHeads(localNodes.length)

      if (!update.reboot) {
        if (this._applyState.shouldFlush()) {
          await this._applyState.flush()
          this.updating = true
        }

        if (this._checkWriters.length > 0) {
          await this._gcWriters()
          continue // rerun the update loop as a writer might have been added
        }
        if (remoteAdded >= REMOTE_ADD_BATCH) continue
        break
      }

      await this._gcWriters()
      await this._reboot(this._applyState.key)
    }

    // emit state changes post drain
    if (writable !== this.writable) {
      if (this.writable && this._writable) this._writable.resolve(true)
      this.emit(writable ? 'unwritable' : 'writable')
    }
  }

  _wakeupPeer(stream) {
    if (!this.wakeupSession) return
    const wakeup = this._getWakeup()
    if (wakeup.length === 0) return
    this.wakeupSession.announceByStream(stream, wakeup)
  }

  _getWakeup() {
    const writers = []

    for (const w of this.activeWriters) {
      if (w.isActiveIndexer || w.flushed()) continue
      writers.push({ key: w.core.key, length: w.length })
    }

    return writers
  }

  async _wakeupWriter(key, length) {
    this._ensureWakeup(await this._getWriterByKey(key, -1, length, true, false, null))
  }

  // ensure wakeup on an existing writer (the writer calls this in addition to above)
  _ensureWakeup(w) {
    if (w === null || w.isBootstrap === true) return
    w.setBootstrap(true) // even if turn false at end of drain, hypercore makes them linger a bit so no churn
    this._bootstrapWriters.push(w)
    this._bootstrapWritersChanged = true
  }

  async _drainWakeup() {
    const promises = []

    // warmup all the below gets
    if (this._needsWakeup) {
      for (const { key, length } of this._wakeup) {
        const w = this.activeWriters.get(key)
        if (w) {
          if (w.length < length) w.seen(length)
          continue
        }
        promises.push(this._applyState.system.get(key))
      }

      if (this._needsWakeupHeads) {
        for (const { key } of await this._applyState.system.heads) {
          if (this.activeWriters.has(key)) continue
          promises.push(this._applyState.system.get(key))
        }
      }
    }
    for (const [hex, length] of this._wakeupHints) {
      const key = b4a.from(hex, 'hex')
      if (length !== -1) {
        const w = this.activeWriters.get(key)
        if (w) {
          if (w.length < length) w.seen(length)
          continue
        }
      }
      promises.push(this._applyState.system.get(key))
    }

    await Promise.allSettled(promises)

    if (this._needsWakeup === true) {
      this._needsWakeup = false

      for (const { key, length } of this._wakeup) {
        if (this.activeWriters.has(key)) continue
        await this._wakeupWriter(key, length)
      }

      if (this._needsWakeupHeads === true) {
        this._needsWakeupHeads = false

        for (const { key } of await this._applyState.system.heads) {
          if (this.activeWriters.has(key)) continue
          await this._wakeupWriter(key, 0)
        }
      }
    }

    for (const [hex, length] of this._wakeupHints) {
      const key = b4a.from(hex, 'hex')
      if (this.activeWriters.has(key)) continue
      if (length !== -1) {
        const info = await this._applyState.system.get(key)
        if (info && length <= info.length) continue // stale hint
      }
      await this._wakeupWriter(key, length === -1 ? 0 : length)
    }

    this._wakeupHints.clear()
  }

  pause() {
    this.paused = true
  }

  resume() {
    this.paused = false
    this._queueBump()
  }

  waitForWritable() {
    if (this.writable) return Promise.resolve(true)
    if (!this._writable) this._writable = rrp()
    return this._writable.promise
  }

  async _drainWithInterupt() {
    while (true) {
      try {
        await this._drain()
        return
      } catch (err) {
        if (this.closing || !this.fastForwardTo) throw err
        if (Date.now() - this.fastForwardFailedAt < MIN_FF_WAIT) throw err
        this.fastForwardFailedAt = Date.now()
        // if an FF is enabled retry
      }
    }
  }

  async _advance() {
    if (this.opened === false) await this.ready()
    if (this.paused || this._interrupting) return

    this._draining = true

    if (this._updateLocalCore !== null) {
      await this._rotateLocalWriter(this._updateLocalCore)
    }

    const local = this.local.length

    try {
      await this._drainWithInterupt()

      // must run post drain so the linearizer is caught up
      if (this._caughtup && (this._needsWakeup === true || this._wakeupHints.size > 0))
        await this._drainWakeup()

      // check if we should update local writer
      if (!this.localWriter || this.localWriter.closed) {
        await this._updateLocalWriter(this._applyState.system)
      }

      this._draining = false
    } catch (err) {
      this._onError(err)
      return
    }

    if (this._interrupting) return

    if (this.localWriter && !this.localWriter.closed) {
      if (this._applyState.isLocalPendingIndexer()) this.ack().catch(noop)
      else if (this._triggerAckAsap()) this._ackTimer.asap()
    }

    // keep bootstraps in sync with linearizer
    if (this.updating === true || this._bootstrapWritersChanged === true) {
      this._updateBootstrapWriters()
    }

    if (this.updating === true) {
      this.updating = false

      if (local !== this.local.length) this._resetAckTick()
      else this._ackTick++

      if (!this._interrupting) this.emit('update')
      this._waiting.notify(null)
    }

    if (!this._interrupting) await this._gcWriters()
  }

  _triggerAckAsap() {
    if (!this._ackTimer) return false

    // flush if threshold is reached and we are not already acking
    if (this._ackTickThreshold && !this._acking && this._ackTick >= this._ackTickThreshold) {
      if (this._ackTimer) {
        for (const w of this.linearizer.indexers) {
          if (w.core.length > w.length) return false // wait for the normal ack cycle in this case
        }
      }

      return true
    }

    return false
  }

  _queueFastForward() {
    if (!this.core.opened) return
    // should have a better way to get this
    const latestSignedLength = this.core.core.state.length

    if (!this.fastForwardEnabled || this.fastForwarding !== null || this._interrupting) return

    if (latestSignedLength - this.core.length < this.fastForwardMinimum) return
    if (this.fastForwardTo !== null) return
    if (Date.now() - this.fastForwardFailedAt < MIN_FF_WAIT) return

    this._runFastForward(
      new FastForward(this, this.core.key, { minimum: this.fastForwardMinimum })
    ).catch(noop)
  }

  _queueStaticFastForward(key) {
    if (!this.fastForwardEnabled || this.fastForwarding !== null || this._interrupting) return
    if (this.fastForwardTo !== null) return
    if (Date.now() - this.fastForwardFailedAt < MIN_FF_WAIT) return

    this._runFastForward(new FastForward(this, key, { verified: false })).catch(noop)
  }

  _updateActivity() {
    this.activeWriters.updateActivity()
    if (this._applyState) {
      if (this.isFastForwarding()) this._applyState.pause()
      else this._applyState.resume()
    }

    // in case we ignored wakeups case we were fast forwarding, request them now if not
    if (this._needsWakeupRequest && !this.isFastForwarding() && this.wakeupSession) {
      this._needsWakeupRequest = false

      this.wakeupSession.broadcastLookup({})
    }
  }

  _preferFastForward() {
    this.fastForwardMinimum = 1
    this._queueFastForward()
  }

  _postApply() {
    this.fastForwardMinimum = FastForward.MINIMUM
  }

  async _runFastForward(ff) {
    this.fastForwarding = ff

    this._updateActivity()

    const result = await ff.upgrade()
    await ff.close()

    if (this.fastForwarding === ff) this.fastForwarding = null

    if (!result) {
      if (ff.failed) this.fastForwardFailedAt = Date.now()
      else this._queueFastForward()
      this._updateActivity()
      return
    }

    this.fastForwardFailedAt = 0
    this.fastForwardTo = result

    if (this._applyState && this._applyState.applying && this.fastForwardMinimum === 1) {
      await this._applyState.close()
    }

    this._bumpAckTimer()
    this._queueBump()
  }

  // triggered from apply
  async _addWriter(key, sys) {
    // just compat for old version
    assert(!!this._applyState.applying, 'System changes are only allowed in apply')

    const writer =
      (await this._getWriterByKey(key, -1, 0, false, true, sys)) ||
      this._makeWriter(key, 0, true, false)
    await writer.ready()

    if (!this.activeWriters.has(key)) {
      this.activeWriters.add(writer)
      this._checkWriters.push(writer)
      this._ensureWakeup(writer)
    }

    // fetch any nodes needed for dependents
    this._queueBump()
  }

  // triggered from apply
  _removeWriter(key) {
    // just compat for old version
    const w = this.activeWriters.get(key)
    if (w) w.isRemoved = true

    this._queueBump()
  }

  removeable(key) {
    return this._applyState ? this._applyState.removeable(key) : false
  }

  _updateAckThreshold() {
    if (this._ackThreshold === 0) return
    if (this._ackTimer) this._ackTimer.bau()
    this._ackTick = 0
    this._ackTickThreshold = random2over1(this.linearizer.indexers.length * this._ackThreshold)
  }

  _resetAckTick() {
    this._ackTick = 0
    if (this._ackTimer) this._ackTimer.bau()
  }

  _shiftWriter(w) {
    w.shift()
    if (w.flushed()) this._checkWriters.push(w)
  }
}

function toKey(k) {
  return b4a.isBuffer(k) ? k : hypercoreId.decode(k)
}

function isAutobaseMessage(msg) {
  return msg.checkpoint ? msg.checkpoint.length > 0 : msg.checkpoint === null
}

function compareNodes(a, b) {
  return b4a.compare(a.key, b.key)
}

function random2over1(n) {
  return Math.floor(n + Math.random() * n)
}

function noop() {}

function crashSoon(err) {
  queueMicrotask(() => {
    throw err
  })
  throw err
}

function isObject(obj) {
  return typeof obj === 'object' && obj !== null
}

function emitWarning(err) {
  safetyCatch(err)
  this.emit('warning', err)
}

function normalize(valueEncoding, value) {
  const state = { buffer: null, start: 0, end: 0 }
  valueEncoding.preencode(state, value)
  state.buffer = b4a.allocUnsafe(state.end)
  valueEncoding.encode(state, value)
  state.start = 0
  return valueEncoding.decode(state)
}
const b4a = require('b4a')

module.exports = class ActiveWriters {
  constructor() {
    this.map = new Map()
  }

  get size() {
    return this.map.size
  }

  [Symbol.iterator]() {
    return this.map.values()
  }

  get(key) {
    return this.map.get(b4a.toString(key, 'hex')) || null
  }

  has(key) {
    return this.get(key) !== null
  }

  add(writer) {
    this.map.set(b4a.toString(writer.core.key, 'hex'), writer)
  }

  delete(writer) {
    this.map.delete(b4a.toString(writer.core.key, 'hex'))
  }

  updateActivity() {
    for (const w of this.map.values()) w.updateActivity()
  }

  clear() {
    const p = []
    for (const w of this.map.values()) p.push(w.close())
    this.map.clear()

    return Promise.all(p)
  }
}
const SignalPromise = require('signal-promise')

module.exports = class AppendBatch {
  constructor(base) {
    this.base = base
    this.blocks = []
    this.closed = false
    this.flushing = false
    this._flushed = null
  }

  async _acquire() {
    while (this.base.activeBatch !== null && this.base.activeBatch !== this)
      await this.base.activeBatch.flushed()
    this.base.activeBatch = this
  }

  async append(value) {
    if (this.base.opened === false) await this.base.ready()
    if (this.base._advancing !== null) await this.base._advancing

    if (this.closed) throw new Error('Batch is closed')
    if (this.base.activeBatch !== this) await this._acquire()
    if (this.closed) throw new Error('Batch is closed')

    this.blocks.push(value)
    return this.base.local.length + this.blocks.length
  }

  async flush() {
    if (this.closed) throw new Error('Batch is closed')
    if (this.flushing) return this.flushed()
    this.flushing = true
    if (this.blocks.length) await this.base._appendBatch(this.blocks)
    return this.close()
  }

  flushed() {
    if (this.closed) return Promise.resolve()
    if (this._flushed) return this._flushed.wait()
    this._flushed = new SignalPromise()
    return this._flushed.wait()
  }

  close() {
    if (this.base.activeBatch !== this) return
    this.base.activeBatch = null
    this.closed = true
    if (this._flushed) this._flushed.notify(null)
  }
}
class PublicApplyCalls {
  constructor(base) {
    this.base = base
    this.internal = false
  }

  get discoveryKey() {
    return this.base.discoveryKey
  }

  get key() {
    return this.base.key
  }

  get id() {
    return this.base.id
  }

  async addWriter() {
    throw new Error('Not allowed on the public view')
  }

  async ackWriter() {
    throw new Error('Not allowed on the public view')
  }

  async removeWriter() {
    throw new Error('Not allowed on the public view')
  }

  preferFastForward() {
    throw new Error('Not allowed on the public view')
  }

  interrupt() {
    throw new Error('Not allowed on the public view')
  }

  removeable() {
    throw new Error('Not allowed on the public view')
  }

  fork() {
    throw new Error('Not allowed on the public view')
  }

  anchor() {
    throw new Error('Not allowed on the public view')
  }
}

class PrivateApplyCalls extends PublicApplyCalls {
  constructor(state) {
    super(state.base)
    this.state = state
    this.internal = true
  }

  get system() {
    return this.state.system
  }

  async addWriter(key, { indexer = true, isIndexer = indexer } = {}) {
    // just compat for old version
    await this.state.system.add(key, { isIndexer })
    await this.base._addWriter(key, this.state.system)
  }

  async ackWriter(key) {
    await this.state.system.ack(key)
  }

  async removeWriter(key) {
    // just compat for old version
    if (!this.state.removeable(key)) {
      throw new Error('Not allowed to remove the last indexer')
    }

    await this.state.system.remove(key)
    this.base._removeWriter(key)
  }

  preferFastForward() {
    this.base._preferFastForward()
  }

  interrupt(reason) {
    this.base._interrupt(reason)
  }

  removeable(key) {
    return this.state.removeable(key)
  }

  async fork(indexerKeys, system) {
    if (!(await this.state.validateFork(indexerKeys, system))) return false

    const indexers = indexerKeys.map(toIndexer)

    this.state.pendingFork = { indexers, length: system.length }

    return true
  }

  async createAnchor() {
    return await this.state.createAnchor()
  }
}

module.exports = { PublicApplyCalls, PrivateApplyCalls }

function toIndexer(key) {
  return { key, length: 0 } // length is not used, just for api compat
}
const ReadyResource = require('ready-resource')
const assert = require('nanoassert')
const c = require('compact-encoding')
const crypto = require('hypercore-crypto')
const b4a = require('b4a')
const safetyCatch = require('safety-catch')
const Hypercore = require('hypercore')

const { partialSignature } = require('hypercore/lib/multisig.js')

const SystemView = require('./system.js')
const { AutobaseEncryption, EncryptionView } = require('./encryption.js')
const UpdateChanges = require('./updates.js')
const messages = require('./messages.js')
const { PrivateApplyCalls } = require('./apply-calls.js')
const { encodeValue } = require('./values.js')
const Fork = require('./fork.js')
const LocalState = require('./local-state.js')
const { OPLOG_VERSION, BOOT_RECORD_VERSION } = require('./caps.js')

// todo: expose this as an option
const SHOULD_SIGN_THRESHOLD = 0

class CheckpointCore {
  constructor(view, core, signer, paused) {
    this.view = view
    this.core = core
    this.signer = signer
    this.length = 0
    this.digest = null
    this.signatures = { system: null, encryption: null, user: [] }
    this.closed = false
    this.paused = paused
  }

  pause() {
    this.paused = true
  }

  resume() {
    if (!this.paused) return
    this.paused = false
    this.updateBackground()
  }

  async update() {
    if (await this.updateCheckpoints()) await this.view.maybeSigned()
  }

  async updateCheckpoints() {
    if (this.core.length <= this.length || this.closed || this.paused) return false

    const length = this.core.length
    const value = await this.core.get(length - 1)

    if (length <= this.length || this.closed || !value.digest || this.paused) return false

    const [digest, checkpoints] = await Promise.all([
      this._inflateDigest(length, value.digest),
      this._inflateAllCheckpoints(length, value.checkpoint)
    ])

    if (length <= this.length || this.closed || !digest || !checkpoints || this.paused) return false
    for (let i = 0; i < checkpoints.length; i++) {
      if (checkpoints[i] === null) return false
    }

    this.length = length
    this.digest = digest
    this.signatures = checkpoints

    return true
  }

  updateBackground() {
    return this.update().catch(safetyCatch)
  }

  updateInternalSignatures(length, signatures) {
    this.signatures.system = updateSignature(length, signatures.system, this.signatures.system)
    this.signatures.encryption = updateSignature(
      length,
      signatures.encryption,
      this.signatures.encryption
    )
  }

  updateUserSignatures(length, signatures) {
    this.signatures.user = updateSignatures(length, signatures, this.signatures.user)
  }

  makeCheckpoints(length) {
    return {
      system: makeCheckpoint(length, this.signatures.system),
      encryption: makeCheckpoint(length, this.signatures.encryption),
      user: makeCheckpoints(length, this.signatures.user)
    }
  }

  makeDigest(length, key) {
    if (this.digest && (this.digest.key === key || b4a.equals(this.digest.key, key))) {
      return { key: null, pointer: length - this.digest.at }
    }
    this.digest = { key, at: length }
    return { key, pointer: 0 }
  }

  async ready() {
    await this.core.ready()
    this.core.on('append', this.updateBackground.bind(this))
    if (this.core.writable) await this.updateCheckpoints()
    this.updateBackground()
  }

  signedLength() {
    return this.signatures.system ? this.signatures.system.length : 0
  }

  close() {
    this.closed = true
    return this.core.close()
  }

  async _inflateDigest(length, dig) {
    if (!dig) return null

    if (dig.pointer === 0) {
      return { key: dig.key, at: length }
    }

    const len = length - dig.pointer

    if (this.digest && len === this.digest.at) return this.digest
    if (len <= 0) return null

    const { digest } = await this.core.get(len - 1)
    if (!digest || !digest.key) return null

    return { key: digest.key, at: len }
  }

  _sameSignatures(b, a) {
    return (
      sameSignature(a.system, b.system) &&
      sameSignature(a.encryption, b.encryption) &&
      sameSignatures(a.user, b.user)
    )
  }

  async _inflateAllCheckpoints(length, checkpoints) {
    if (!checkpoints) return { system: null, encryption: null, user: [] }
    if (this._sameSignatures(checkpoints, this.signatures)) return this.signatures

    const system = this._inflateSystemCheckpoint(checkpoints.system, length)
    const encryption = this._inflateEncryptionCheckpoint(checkpoints.encryption, length)

    const user = checkpoints.user ? new Array(checkpoints.user.length) : []

    for (let i = 0; i < user.length; i++) {
      const chk = checkpoints.user[i]
      user[i] = this._inflateUserCheckpoint(chk, i, length)
    }

    const [systemCheckpoint, encryptionCheckpoint, userCheckpoints] = await Promise.all([
      system,
      encryption,
      Promise.all(user)
    ])

    return {
      system: systemCheckpoint,
      encryption: encryptionCheckpoint,
      user: userCheckpoints
    }
  }

  async _inflateSystemCheckpoint(chk, coreLength) {
    if (chk.checkpoint) {
      const { signature, length } = chk.checkpoint
      return { signature, length, at: coreLength }
    }

    const len = coreLength - chk.checkpointer
    if (this.signatures.system && this.signatures.system.at === len) {
      return this.signatures.system
    }

    if (len <= 0) return null

    const { checkpoint } = await this.core.get(len - 1)
    if (!checkpoint || !checkpoint.system || !checkpoint.system.checkpoint) return null

    const { signature, length } = checkpoint.system.checkpoint
    return { signature, length, at: len }
  }

  async _inflateEncryptionCheckpoint(chk, coreLength) {
    if (!chk) return { length: 0, signature: null, at: 0 }

    if (chk.checkpoint) {
      const { signature, length } = chk.checkpoint
      return { signature, length, at: coreLength }
    }

    const len = coreLength - chk.checkpointer
    if (this.signatures.encryption && this.signatures.encryption.at === len) {
      return this.signatures.encryption
    }

    if (len <= 0) return null

    const { checkpoint } = await this.core.get(len - 1)
    if (!checkpoint || !checkpoint.encryption || !checkpoint.encryption.checkpoint) return null

    const { signature, length } = checkpoint.encryption.checkpoint
    return { signature, length, at: len }
  }

  async _inflateUserCheckpoint(chk, i, coreLength) {
    if (chk.checkpoint) {
      const { signature, length } = chk.checkpoint
      return { signature, length, at: coreLength }
    }

    const len = coreLength - chk.checkpointer
    if (i < this.signatures.user.length && this.signatures.user[i].at === len) {
      return this.signatures.user[i]
    }

    if (len <= 0) return null

    const { checkpoint } = await this.core.get(len - 1)
    if (!checkpoint || i >= checkpoint.user.length || !checkpoint.user[i].checkpoint) return null

    const { signature, length } = checkpoint.user[i].checkpoint
    return { signature, length, at: len }
  }
}

module.exports = class ApplyState extends ReadyResource {
  constructor(base) {
    super()

    this.base = base
    this.encryption = this.base.encryption
    this.valueEncoding = base.valueEncoding
    this.store = base._viewStore.atomize()
    this.key = null
    this.system = null
    this.view = null
    this.views = []
    this.systemView = null
    this.encryptionView = null
    this.hostcalls = null
    this.changes = base._hasUpdate ? new UpdateChanges(base) : null

    this.updates = []

    this.local = null
    this.localState = null

    this.fastForwarding = false
    this.indexersUpdated = false
    this.quorum = 0
    this.needsIndexedLengthUpdate = false
    this.interrupted = false
    this.applying = null
    this.applyBatch = null

    this.localCheckpoint = null
    this.localIndexer = false

    this.systemUpgrade = null

    this.checkpoints = []
    this.pendingViews = null
    this.pendingFork = null
    this.dirty = false
  }

  shouldFlush() {
    return this.dirty
  }

  async shouldWrite() {
    if (!this.localIndexer || !this.localCheckpoint || !this.localCheckpoint.core.opened) {
      return false
    }

    const info = await this.system.getIndexedInfo(this.indexedLength)
    if (!info.views.length) return false

    const { encryption, user } = this.localCheckpoint.signatures

    // todo: does this condition hold for soft-fork?
    if (info.views.length > user.length) return true

    for (let i = 0; i < info.views.length; i++) {
      if (user[i].length + SHOULD_SIGN_THRESHOLD < info.views[i].length) return true
    }

    // always flush encryption signature
    if (info.encryptionLength !== 0) {
      if (!encryption || encryption.length < info.encryptionLength) return true
    }

    return false
  }

  get indexedLength() {
    return this.systemView ? this.systemView.indexedLength : 0
  }

  get systemRef() {
    return this.systemView ? this.systemView.ref : null
  }

  async validateFork(indexerKeys, system) {
    if (!b4a.equals(system.key, this.system.core.key)) return false
    if (this.pendingIndexedLength() < system.length) return false
    for (const key of indexerKeys) {
      const info = await this.system.get(key)

      // writer should be active and we need manifest
      if (!info || !info.length || info.isRemoved) return false
    }
    return true
  }

  async shouldMigrate() {
    if (!this.fastForwarding && !this.indexersUpdated) return false
    if (this.system.indexers.length === 0) return false // genesis

    // sanity, prefer migration
    if (!this.system.core.manifest || !this.system.core.manifest.signers) return true

    // easy mode
    if (this.system.indexers.length !== this.system.core.manifest.signers.length) {
      return true
    }

    const manifests = await this.store.getIndexerManifests(this.system.indexers)

    for (let i = 0; i < this.system.core.manifest.signers.length; i++) {
      const { publicKey } = this.system.core.manifest.signers[i]
      if (!b4a.equals(publicKey, manifests[i].signers[0].publicKey)) return true
    }

    return false
  }

  isLocalPendingIndexer() {
    if (this.system.pendingIndexers.length === 0) return false
    const key = this.base.local.key
    for (const k of this.system.pendingIndexers) {
      if (b4a.equals(k, key)) return !b4a.equals(this.base.key, key) && this.base.local.length === 0
    }
    return false
  }

  removeable(key) {
    if (this.system.indexers.length !== 1) return true
    return !b4a.equals(this.system.indexers[0].key, key)
  }

  isLocalIndexer() {
    return !!this.localCheckpoint
  }

  _createCheckpointCore(key, active, signer, paused) {
    const encryption = this.base.getWriterEncryption(key)

    const session = this.base.store.get({
      key,
      valueEncoding: messages.OplogMessage,
      encryption,
      active
    })

    return new CheckpointCore(this, session, signer, paused)
  }

  async _openInternalView(name, indexedLength) {
    const core = this.store.get({ name })
    await core.ready()
    await core.setUserData('referrer', this.base.key)
    await core.setUserData('autobase/view', b4a.from(name))

    const ref = this.store.getViewByName(name)

    return { ref, core, indexedLength }
  }

  async _open() {
    try {
      await this._boot()
    } catch (err) {
      if (isViewCorruption(err)) {
        for (const v of this.store.opened) {
          await v.core.ready()
          await v.core.state.storage.deleteSessions()
        }
        if (this.systemView) {
          const core = this.systemView.ref.core
          await core.ready()
          await core.state.storage.deleteSessions()
        }
        if (this.encryptionView) {
          const core = this.encryptionView.ref.core
          await core.ready()
          await core.state.storage.deleteSessions()
        }

        await LocalState.clear(this.base.local)
      }

      await this._free()
      throw err
    }
  }

  async _boot() {
    const boot = await this.base._getBootRecord()

    this.systemView = await this._openInternalView('_system', boot.systemLength)
    this.encryptionView = await this._openInternalView('_encryption', -1)

    const sysCore = this.systemView.core
    if (sysCore.manifest.version >= 2) {
      if (this.encryption !== null) await this.encryption.reload(this.encryptionView.core)
    }

    const system = new SystemView(sysCore)
    await system.ready()

    // reset so we dont track _system or _encryption
    this.store.opened = []

    this.hostcalls = new PrivateApplyCalls(this)
    const view = this.base._hasOpen ? this.base._handlers.open(this.store, this.hostcalls) : null

    this.view = view
    this.system = system

    // ensure all are ready
    for (const v of this.store.opened) await v.atomicBatch.ready()

    this.key = system.core.key

    this.fastForwarding = boot.fastForwarding
    this.indexersUpdated = boot.indexersUpdated
    this.quorum = sysCore.manifest ? sysCore.manifest.quorum : 0

    const added = new Set()

    for (let i = 0; i < system.views.length; i++) {
      const { key, length } = system.views[i]
      const v = await this.store.findViewByKey(
        key,
        system.indexers,
        sysCore.manifest.version,
        system.entropy
      )

      if (v === null) {
        this.views.push({ name: null, key, length, core: null, ref: null, mappedIndex: i })
        continue
      }

      await v.atomicBatch.ready()
      await v.core.setUserData('referrer', this.base.key)
      await v.core.setUserData('autobase/view', b4a.from(v.name))

      this.views.push({ name: v.name, key, length, core: v.atomicBatch, ref: v, mappedIndex: i })

      added.add(v)
    }

    for (const v of this.store.opened) {
      if (added.has(v)) continue
      const core = v.atomicBatch

      this.views.push({
        name: v.name,
        key: core.key,
        length: core.length,
        core,
        ref: v,
        mappedIndex: -1
      })
    }

    this.local = this.store.getLocal()
    await this.local.ready()

    this.localState = new LocalState(this.local)

    let isLocalIndexer = false

    const paused = this.base.isFastForwarding()
    const inf = await this.system.getIndexedInfo()

    for (let i = 0; i < inf.indexers.length; i++) {
      const idx = inf.indexers[i]
      const chk = this._createCheckpointCore(idx.key, true, i, paused)
      await chk.ready()
      this.checkpoints.push(chk)
      if (b4a.equals(idx.key, this.base.local.key)) isLocalIndexer = true
    }

    const shouldSign = this.quorum > 0 && isLocalIndexer

    if (shouldSign) {
      this.localIndexer = true
      await this._startLocalCheckpoint()
    }

    await this._refreshWriters()

    // deferred
    this.maybeSigned().catch(noop)
  }

  async _startLocalCheckpoint() {
    for (const chk of this.checkpoints) {
      if (chk.core.id === this.base.local.id) {
        this.localCheckpoint = chk
        return
      }
    }

    this.localCheckpoint = this._createCheckpointCore(this.base.local.key, false, 0, false)
    await this.localCheckpoint.ready()
  }

  interrupt() {
    this.interrupted = true
    this.pause()
  }

  pause() {
    for (const chk of this.checkpoints) {
      if (chk !== this.localCheckpoint) chk.pause()
    }
  }

  resume() {
    for (const chk of this.checkpoints) {
      if (chk !== this.localCheckpoint) chk.resume()
    }
  }

  _checkSystemUpgrade() {
    if (this.systemUpgrade) return

    const tally = new Map()

    for (const chk of this.checkpoints) {
      if (chk.signedLength() <= this.system.core.signedLength) continue
      if (!chk.digest) continue

      const id = b4a.toString(chk.digest.key, 'hex')
      const cnt = (tally.get(id) || 0) + 1

      if (cnt >= this.quorum) {
        this.systemUpgrade = chk.digest.key
        this.base._queueStaticFastForward(this.systemUpgrade)
        return
      }

      tally.set(id, cnt)
    }
  }

  mapIndexToView(index) {
    for (const { mappedIndex, ref } of this.views) {
      if (mappedIndex === index) return ref
    }
  }

  async maybeSigned() {
    if (this.opened === false) await this.ready()
    if (this.interrupted) return

    const thres = this.checkpoints.length - this.quorum
    if (thres < 0 || this.checkpoints.length <= thres) return

    this.checkpoints.sort(cmpCheckpoints)

    const signableLength = this.checkpoints[thres].signedLength()
    if (signableLength <= this.system.core.signedLength) return

    const expected = this.system.core.key

    if (signableLength > this.indexedLength) {
      if (!b4a.equals(expected, this.checkpoints[thres].digest.key)) this._checkSystemUpgrade()
      this.needsIndexedLengthUpdate = true
      return
    }

    for (let i = thres; i < this.checkpoints.length; i++) {
      const chk = this.checkpoints[i]
      // we must agree on what the system is obvs
      if (!b4a.equals(expected, chk.digest.key)) {
        this._checkSystemUpgrade()
        return
      }
      // if we dont have the indexed state ourself, then no way for us to verify / patch
      if (!chk.signatures.system || chk.signatures.system.length > this.indexedLength) {
        this.needsIndexedLengthUpdate = true
        return
      }
    }

    this.needsIndexedLengthUpdate = false

    const chk = []
    for (let i = thres; i < this.checkpoints.length; i++) {
      chk.push({ signer: this.checkpoints[i].signer, signatures: this.checkpoints[i].signatures })
    }

    const { system } = this.checkpoints[thres].signatures

    await this._assembleMultisig(system.length, chk)
  }

  async _assembleMultisig(signableLength, checkpoints) {
    const views = new Array(this.views.length + 2) // +2 is system + encryption

    const sys = await this.system.getIndexedInfo(signableLength)

    if (this.interrupted) return

    if (this.pendingViews && this.pendingViews[0].length >= signableLength) {
      return
    }

    views[0] = createCheckpointSignature(signableLength, this.systemView, checkpoints.length)

    if (sys.encryptionLength) {
      views[1] = createCheckpointSignature(
        sys.encryptionLength,
        this.encryptionView,
        checkpoints.length
      )
    }

    const offset = 2 // system + encryption

    for (let i = offset; i < views.length; i++) {
      const view = this.views[i - offset]
      const core = view.core

      const v = this.getViewFromSystem(view, sys)
      const length = v ? v.length : 0

      if (!core || length <= core.signedLength) continue

      const viewIndex = view.mappedIndex + offset
      views[viewIndex] = createCheckpointSignature(length, view, checkpoints.length)
    }

    for (let i = 0; i < checkpoints.length; i++) {
      addCheckpointSignatures(views, checkpoints, i)
    }

    const promises = []
    for (const view of views) {
      if (!view) continue

      for (let i = 0; i < view.signatures.length; i++) {
        promises.push(setPartialSignature(view, i))
      }
    }

    await Promise.all(promises)

    // check that the state still looks good, couple replicate inbetween...
    for (const v of views) {
      if (!v) continue

      for (let i = 0; i < v.signatures.length; i++) {
        if (!v.partials[i]) return
      }
    }

    if (this.interrupted) return

    if (this.pendingViews && this.pendingViews[0].length >= signableLength) {
      return
    }
    // skipped system for whatever reason...
    if (!views[0]) return

    this.pendingViews = views
    this.dirty = true

    // TODO: only needed if not updating, wont crash so keeping for now, just less efficient
    this.base._queueBump()
  }

  _close() {
    if (this.interrupted) return
    return this._free()
  }

  async _free() {
    this.interrupted = true
    if (this.applying) this._postApply()
    for (const chk of this.checkpoints) await chk.close()
    if (this.localCheckpoint) await this.localCheckpoint.close()
    if (this.view && this.base._hasClose) await this.base._handlers.close(this.view)
    if (this.system) await this.system.close()

    const promises = []
    for (const v of this.views) {
      if (v.ref) promises.push(v.ref.release())
    }

    if (this.systemView) promises.push(this.systemView.ref.release())
    if (this.encryptionView) promises.push(this.encryptionView.ref.release())

    await Promise.all(promises)
    await this.store.close()
  }

  _pushUpdate(u) {
    u.version = 1
    u.seq = this.updates.length === 0 ? 0 : this.updates[this.updates.length - 1].seq + 1
    u.systemLength = this.systemView.core.length
    this.updates.push(u)
    this.localState.insertUpdate(u)
  }

  async catchup(linearizer) {
    if (!this.opened) await this.ready()
    if (!this.system.heads.length) return true

    const writers = new Map()

    // load linearizer...
    const updates = await this.localState.listUpdates()
    const sys = await this.system.checkout(this.indexedLength)

    for (const node of updates) {
      const hex = b4a.toString(node.key, 'hex')

      let w = writers.get(hex)

      if (w === undefined) {
        // TODO: we actually have all the writer info already but our current methods make it hard to reuse that
        w = await this.base._getWriterByKey(node.key, -1, 0, true, false, sys)
        writers.set(hex, w)
      }

      if (w.length >= node.length) {
        this.base._warn(new Error('Update expects writer to be consumed here'))
        return false
      }

      while (w.length < node.length) {
        await w.update(sys)

        const next = w.advance()
        if (!next) {
          this.base._warn(new Error('Node must exist for catchup'))
          return false
        }

        linearizer.addHead(next)
      }
    }

    await sys.close()

    this.updates = updates

    if (this.indexersUpdated) {
      this.indexersUpdated = false
      // if we updated the indexers, invalidate all the internal state and reapply it
      await this.truncate(this.indexedLength)
      await this._rollbackViews()

      if (this.tx === null) this.tx = this.local.state.storage.write()

      while (
        this.updates.length > 0 &&
        this.updates[this.updates.length - 1].systemLength >= this.indexedLength
      ) {
        const u = this.updates.pop()
        this.localState.deleteUpdate(u)
      }
    } else {
      // otherwise we know the internal state is correct, so we carry on
      linearizer.update()
    }

    // must refresh the writers here so isRemoved is up to date
    await this._refreshWriters()
    return true
  }

  async getIndexedSystem() {
    if (this.opened === false) await this.ready()

    const indexedLength = this.pendingFork ? this.pendingFork.length : this.indexedLength

    const sys = await this.system.checkout(indexedLength)
    await sys.ready()
    return sys
  }

  getViewFromSystem(view, sys = this.system) {
    if (view.mappedIndex === -1 || view.mappedIndex >= sys.views.length) return null
    return sys.views[view.mappedIndex]
  }

  async recoverAt() {
    if (!this.systemRef) await this.ready()

    await this.systemRef.core.ready()

    const lt = this.systemRef.core.core.state.length

    for await (const { length, info } of SystemView.flushes(this.systemRef.core.session(), {
      reverse: true,
      lt
    })) {
      let consistent = true

      for (let i = 0; i < info.views.length; i++) {
        if (i >= this.views.length) continue
        if (!b4a.equals(info.views[i].key, this.views[i].key)) continue

        if (info.views[i].length > this.views[i].core.core.state.length) {
          consistent = false
          break
        }
      }

      if (consistent) {
        return {
          length,
          force: true,
          key: this.system.core.key,
          indexers: info.indexers,
          views: info.views
        }
      }
    }

    return null
  }

  bootstrap() {
    return this.system.add(this.base.key, { isIndexer: true, isPending: false })
  }

  async undo(popped) {
    if (!popped) return

    let indexersUpdated = false
    while (popped > 0) {
      const u = this.updates.pop()
      this.localState.deleteUpdate(u)
      popped -= u.batch
      if (u.indexers) indexersUpdated = true
    }

    const u = this.updates.length === 0 ? null : this.updates[this.updates.length - 1]
    const systemLength = u ? u.systemLength : this.indexedLength

    await this.truncate(systemLength)
    if (indexersUpdated) await this._rollbackViews()
  }

  async truncate(systemLength) {
    if (this.opened === false) await this.ready()
    if (systemLength === this.system.core.length) return

    await this.system.core.truncate(systemLength)

    const migrated = await this.system.update()

    if (this.encryptionView.core.length !== this.system.encryptionLength) {
      await this.encryptionView.core.truncate(this.system.encryptionLength)
    }

    for (const view of this.views) {
      if (!view.core) continue

      const v = this.getViewFromSystem(view)

      if (v && view.core.length === v.length) continue
      if (v === null) view.mappedIndex = -1 // unmap

      await view.core.truncate(v ? v.length : 0)
    }

    if (!migrated) return

    await this._refreshWriters()
    await this._rollbackViews()
  }

  async _refreshWriters() {
    // TODO: add the seq at which we updated the state to the writer instance.
    // then we know if it was truncated out without the lookup, meaning faster truncations
    for (const w of this.base.activeWriters) {
      const data = await this.system.get(w.core.key)
      const bootstrapper = this.system.core.length === 0 && b4a.equals(w.core.key, this.base.key)
      const isRemoved = data ? data.isRemoved : !bootstrapper
      w.isRemoved = isRemoved
    }
  }

  async _updateSystem() {
    if (!(await this.system.update())) return
    await this._refreshWriters()
    await this._rollbackViews()
  }

  async _signViewCore(core, length) {
    const s = await core.signable(length, 0)
    const signature = crypto.sign(s, this.base.local.keyPair.secretKey)
    return { signature, length }
  }

  async _signInternalViewCores(sys) {
    return {
      system: await this._signViewCore(this.systemView.core, this.systemView.indexedLength),
      encryption: await this._signViewCore(this.encryptionView.core, sys.encryptionLength)
    }
  }

  async _signUserViewCores(sys) {
    const promises = new Array(sys.views.length)

    for (let i = 0; i < this.views.length; i++) {
      const view = this.views[i]

      const v = this.getViewFromSystem(view, sys)
      const indexedLength = v ? v.length : 0

      if (!indexedLength) continue

      const viewIndex = view.mappedIndex
      promises[viewIndex] = this._signViewCore(view.ref.atomicBatch, indexedLength)
    }

    return Promise.all(promises)
  }

  async finalize(key) {
    this.localState.setBootRecord({
      version: BOOT_RECORD_VERSION,
      key,
      systemLength: this.systemView.indexedLength,
      indexersUpdated: true,
      fastForwarding: false,
      recoveries: this.base.recoveries
    })

    await this.localState.flush()

    await this.store.flush()
    await this.close()
  }

  async _flush(localNodes) {
    if (localNodes) await this._appendLocalNodes(localNodes)

    if (this.localCheckpoint) {
      // we have to flush here so the chkpoint can update
      // could run the checkpoint on the batch but also no big dead
      // as the "should we sign" check is rerun on boot...
      await this.store.flush()
      await this.localCheckpoint.update()
    }

    this.localState.setBootRecord({
      version: BOOT_RECORD_VERSION,
      key: this.key,
      systemLength: this.systemView.indexedLength,
      indexersUpdated: false,
      fastForwarding: false,
      recoveries: this.base.recoveries
    })

    await this.localState.flush()

    if (this.pendingViews) {
      const views = this.pendingViews
      this.pendingViews = null

      for (let i = views.length - 1; i >= 0; i--) {
        const v = views[i]
        if (!v || v.length <= v.core.signedLength) continue
        const signature = v.core.core.verifier.assemble(v.partials)
        try {
          await v.ref.commit(this.store.atom, v.length, signature)
        } catch (err) {
          if (isAssertion(err)) {
            throw err
          }

          // TODO: we should prevalidate all signatures instead of during commit so it can be all or nothing, just slighly
          // friendlier ux. missing hc api for that
          if (!this.base.closing) this.base._warn(err)
          break
        }
      }
    }

    await this.store.flush()

    this.fastForwarding = false
    if (this.pendingViews === null) this.dirty = false
  }

  _indexUpdates(indexed) {
    let shift = 0
    while (indexed > 0) indexed -= this.updates[shift++].batch

    const last = this.updates[shift - 1]

    this.systemView.indexedLength = last.systemLength

    for (let i = 0; i < shift; i++) this.localState.deleteUpdate(this.updates[i])
    this.updates.splice(0, shift)

    if (!this.needsIndexedLengthUpdate) return
    this.maybeSigned().catch(noop)
  }

  pendingIndexedLength() {
    let indexed = this.applying.indexed.length
    if (!this.applying || !this.updates.length || !indexed) return this.indexedLength

    let pos = 0
    while (indexed > 0 && pos < this.updates.length) {
      indexed -= this.updates[pos++].batch
    }

    const last = this.updates[pos - 1]
    return last.systemLength
  }

  async _assertNode(node, batch) {
    // helpful dag helper, so kept here

    const v = (await this.system.get(node.writer.core.key)) || { length: 0, isRemoved: false }
    const expected = v.length + batch
    if (node.length === expected) return

    console.trace(
      'INVALID_INSERTION',
      'length=',
      node.length,
      'key=',
      node.writer.core.key,
      'local=',
      node.writer.core.writable,
      'batch=',
      batch,
      'dag=',
      v
    )

    process.exit(1)
  }

  _postApply() {
    this.applyBatch = this.applying = null
    this.base._postApply()
  }

  async _optimisticApply(u, node, indexed) {
    const checkpoint = this.system.checkpoint()

    this.system.addHead(node)

    const applyBatch = []
    const key = node.writer.core.key

    applyBatch.push({
      indexed,
      optimistic: true,
      from: node.writer.core,
      length: node.length,
      value: node.value,
      heads: node.actualHeads
    })

    const pre = await this.system.get(key)
    const preLength = pre ? pre.length : 0

    let failed = false

    this.applying = u
    this.applyBatch = applyBatch
    try {
      await this.base._handlers.apply(applyBatch, this.view, this.hostcalls)
    } catch (err) {
      if (this.base.closing) throw err
      failed = true
    }
    this._postApply()

    if (!failed) {
      const post = await this.system.get(key)
      // check if acked by addWriter/removeWriter/ackWriter
      if (!post || preLength === post.length) failed = true
    }

    if (!failed) {
      // technically we only need to to this is the writer was removed, but hey, tricky logic
      await this._refreshWriters()
      return true
    }

    // it failed! rollback...

    this.system.applyCheckpoint(checkpoint)

    if (this.encryptionView.core.length !== this.system.encryptionLength) {
      await this.encryptionView.core.truncate(this.system.encryptionLength)
    }

    for (const view of this.views) {
      const viewLength = view.core ? view.core.length : view.length
      const viewLookup = this.getViewFromSystem(view)
      const viewSystemLength = viewLookup ? viewLookup.length : 0
      if (viewSystemLength === viewLength) continue
      await view.core.truncate(viewSystemLength)
    }

    await this._refreshWriters()

    await this._rollbackViews()
    return false
  }

  _startTrace(node) {
    this.systemRef.tracer.start()
    this.encryptionView.ref.tracer.start()
    for (let i = 0; i < this.views.length; i++) this.views[i].ref.tracer.start()
  }

  _endTrace(node) {
    const trace = {
      system: this.systemRef.tracer.end(),
      encryption: this.encryptionView.ref.tracer.end(),
      user: []
    }

    for (let i = 0; i < this.views.length; i++) {
      const blocks = this.views[i].ref.tracer.end()
      if (blocks.length) trace.user.push({ view: i, blocks })
    }

    if (trace.system.length || trace.encryption.length || trace.user.length) node.trace = trace
  }

  async update(u, localNodes) {
    if (this.changes !== null) this.changes.track(this)

    let batch = 0
    let applyBatch = []
    let indexersUpdated = 0

    let j = 0
    let i = 0
    let forkedAt = -1

    while (i < Math.min(u.indexed.length, u.shared)) {
      const node = u.indexed[i++]

      if (node.batch > 1) continue
      this.base._shiftWriter(node.writer)

      const update = this.updates[j++]

      if (update.indexers) {
        indexersUpdated = i
        break
      }
    }

    if (u.undo) await this.undo(u.undo)

    if (this.system.bootstrapping) await this.bootstrap()

    await this._updateSystem()

    // Organize by view
    const warmupSystemView = []
    const warmupEncryptionView = []
    const warmupUserView = new Array(this.views.length)

    for (let k = u.shared; k < u.length; k++) {
      const indexed = k < u.indexed.length
      const node = indexed ? u.indexed[k] : u.tip[k - u.indexed.length]
      if (!node.trace) continue

      if (node.trace.system.length) warmupSystemView.push(...node.trace.system)
      if (node.trace.encryption.length) warmupEncryptionView.push(...node.trace.encryption)

      for (const trace of node.trace.user) {
        // Assert (as warning) that trace view should only be currently indexed views
        if (trace.view >= this.views.length) {
          console.warn(
            'Oplog block trace contains OOB view index',
            'trace view',
            trace.view,
            'views.length',
            this.views.length
          )
          continue
        }

        warmupUserView[trace.view] = warmupUserView[trace.view] || []
        warmupUserView[trace.view].push(...trace.blocks)
      }
    }

    // Request per view
    if (warmupSystemView.length) this.systemView.core.download({ blocks: warmupSystemView })
    if (warmupEncryptionView.length)
      this.encryptionView.core.download({ blocks: warmupEncryptionView })
    for (let k = 0; k < warmupUserView.length; k++) {
      const blocks = warmupUserView[k]
      if (!blocks) continue

      const view = this.views[k]
      view.core.download({ blocks })
    }

    for (i = u.shared; i < u.length; i++) {
      if (this.base.backoff !== null && this.base.backoff.backoff()) await this.base.backoff.wait()

      const indexed = i < u.indexed.length
      const node = indexed ? u.indexed[i] : u.tip[i - u.indexed.length]

      batch++

      if (node.writer.core.writable) this._startTrace(node)

      const optimist =
        node.writer.isRemoved &&
        node.optimistic &&
        batch === 1 &&
        this.base._hasOptimisticApply === true &&
        (await this._optimisticApply(u, node, indexed))

      if (!optimist) {
        if (node.writer.isRemoved && !node.writer.isActiveIndexer) {
          if (node.batch > 1) continue
          // in case someone is linking this node and they are not removed
          const u = {
            seq: 0,
            key: node.writer.core.key,
            length: node.length,
            batch,
            systemLength: 0,
            indexers: false
          }
          this._pushUpdate(u)
          batch = 0
          assert(applyBatch.length === 0, 'Apply batch should not have been modified')
          continue
        }

        // in prod we prop need to disable this assertion as its pretty expensive,
        // but it catches a lot of bugs, so here for debugs
        // await this._assertNode(node, batch)

        const deps = node.causalDependencies()

        // oldest -> newest
        for (let i = deps.length - 1; i >= 1; i--) {
          const d = deps[i]
          if (await this.system.linkable(d.writer.core.key, d.length)) {
            this.system.addHead(deps[i])
          }
        }

        this.system.addHead(deps[0])

        if (node.value !== null && !node.writer.isRemoved) {
          applyBatch.push({
            indexed,
            optimistic: false,
            from: node.writer.core,
            length: node.length,
            value: node.value,
            heads: node.actualHeads
          })
        }

        if (node.batch > 1) continue

        if (applyBatch.length && this.base._hasApply === true) {
          this.applying = u
          this.applyBatch = applyBatch
          await this.base._handlers.apply(applyBatch, this.view, this.hostcalls)
          if (forkedAt === -1 && this.pendingFork) forkedAt = i
          this._postApply()
        }
      }

      const update = {
        seq: 0,
        key: node.writer.core.key,
        length: node.length,
        batch,
        systemLength: 0,
        indexers: false
      }

      update.indexers = !!this.system.indexerUpdate

      if (this.system.indexerUpdate) await this._generateNextViews()

      await this.system.flush(this.views)
      await this.system.update()

      if (node.writer.core.writable) this._endTrace(node)

      batch = 0
      applyBatch = []

      this._pushUpdate(update)

      if (!indexed || indexersUpdated) continue

      this.base._shiftWriter(node.writer)

      if (update.indexers) {
        indexersUpdated = i + 1
      }
    }

    if (this.pendingFork) {
      const pending = this.pendingFork
      if (pending.length > this.indexedLength) this._indexUpdates(u.indexed.length)

      const fork = new Fork(this.base, this.store, this, pending)
      const forked = await fork.upgrade()

      if (!forked) throw new Error('Fork failed')

      await this._flush(localNodes)

      return {
        reboot: true,
        migrated: false
      }
    }

    if (u.indexed.length) {
      this._indexUpdates(indexersUpdated || u.indexed.length)
    }

    const migrated = indexersUpdated !== 0
    if (migrated) this.key = await this.base._premigrate()

    if (this.changes !== null) {
      this.changes.finalise()
      await this.base._handlers.update(this.view, this.changes)
    }

    await this._flush(localNodes)

    return {
      reboot: migrated,
      migrated
    }
  }

  flush() {
    return this._flush(null)
  }

  async _appendLocalNodes(localNodes) {
    if (localNodes.length === 0) return // just in case

    const blocks = new Array(localNodes.length)
    const local = this.local

    if (!local.opened) await local.ready()

    for (let i = 0; i < blocks.length; i++) {
      const { value, heads, batch, optimistic, trace } = localNodes[i]

      blocks[i] = {
        version: OPLOG_VERSION,
        maxSupportedVersion: this.base.maxSupportedVersion,
        checkpoint: null,
        digest: null,
        optimistic,
        node: {
          heads,
          batch,
          value: value === null ? null : c.encode(this.base.valueEncoding, value)
        },
        trace
      }
    }

    if (this.localIndexer) {
      const signedLength = this.localCheckpoint.signedLength()
      const local = this.local

      let signedAt = 0
      let internalSignatures = null
      let signatures = null

      if (this.indexedLength > signedLength) {
        const sys = await this.system.getIndexedInfo(this.indexedLength)
        internalSignatures = await this._signInternalViewCores(sys)
        signatures = await this._signUserViewCores(sys)
        signedAt = local.length + blocks.length
      }

      for (let i = 0; i < blocks.length; i++) {
        const length = local.length + i + 1
        if (length === signedAt) {
          this.localCheckpoint.updateInternalSignatures(length, internalSignatures)
          this.localCheckpoint.updateUserSignatures(length, signatures)
        }

        const blk = blocks[i]

        blk.checkpoint = this.localCheckpoint.makeCheckpoints(length)
        blk.digest = this.localCheckpoint.makeDigest(length, this.key)
      }
    } else {
      if (!this.localCheckpoint) await this._startLocalCheckpoint()

      for (let i = 0; i < blocks.length; i++) {
        const length = local.length + i + 1
        blocks[i].digest = this.localCheckpoint.makeDigest(length, this.key)
      }
    }

    await local.append(blocks)
  }

  async _rollbackViews() {
    // encryption view key is not in system so no need to handle here
    for (const view of this.views) {
      const v = this.getViewFromSystem(view)

      if (v) {
        view.key = v.key
      } else {
        await this._resetView(view)
      }
    }
  }

  async createAnchor() {
    const node = this.applyBatch[this.applyBatch.length - 1]
    const key = node.from.key
    const length = node.length

    const info = await this.system.get(key, { unflushed: true })

    if (!info || info.length < length) throw new Error('Anchor node is not in system')

    const state = { start: 0, end: 40, buffer: b4a.alloc(40) }
    c.fixed32.encode(state, key)
    c.uint64.encode(state, length)

    const namespace = crypto.hash(state.buffer)
    const manifestData = c.encode(messages.ManifestData, { version: 0, legacyBlocks: 0, namespace })

    const padding = this.encryption ? AutobaseEncryption.PADDING : 0
    const block = encodeValue(null, { heads: [{ key, length }], padding })
    if (this.encryption) await this.encryption.encryptAnchor(block, namespace)

    const root = { index: 0, size: block.byteLength, hash: crypto.data(block) }
    const hash = crypto.tree([root])
    const prologue = { hash, length: 1 }

    const core = this.store.createAnchorCore(prologue, manifestData)
    await core.ready()

    if (core.length === 0) await core.append(block, { writable: true, maxLength: 1 })

    await this.system.add(core.key, { isIndexer: false, length: 0 })
    await this.base._addWriter(core.key, this.system)

    const anchor = { key: core.key, length: core.length }

    await core.close()
    await this.base.hintWakeup(anchor)

    return anchor
  }

  async _resetView(view) {
    const manifests = await this.store.getIndexerManifests(this.system.indexers)

    const manifestData = view.core ? view.core.manifest.userData : null
    view.key = await this.store.createView(
      manifests,
      view.name,
      null,
      this.system.core.manifest.version,
      this.system.entropy,
      null,
      manifestData
    ) // will never be system so no linked
    view.length = 0
  }

  async _generateNextViews() {
    const sys = this.system

    // note, this is very state dependent so can ONLY be called at the exact time an indexer upgrade occurs
    const manifests = await this.store.getIndexerManifests(sys.indexers)
    const entropy = sys.version < 2 ? null : sys.getEntropy(sys.indexers, sys.flushLength())

    for (const v of this.views) {
      const manifestData = v.core ? v.core.manifest.userData : null

      const prologue = await getPrologue(v)
      const key = await this.store.createView(
        manifests,
        v.name,
        prologue,
        sys.core.manifest.version,
        entropy,
        null,
        manifestData
      )

      v.key = key
    }
  }
}

async function getPrologue(view) {
  const length = view.core ? view.core.length : view.length
  if (!length) return null

  const hash = await view.core.treeHash(length)

  return {
    hash,
    length
  }
}

function noop() {}

function cmpCheckpoints(a, b) {
  return a.signedLength() - b.signedLength()
}

function sameSignature(a, b) {
  if (!a || !b) return a === b
  return a.length === b.length
}

function sameSignatures(a, b) {
  if (!a || !b) return a === b
  if (a.length !== b.length) return false
  for (let i = 0; i < a.length; i++) {
    if (a[i].length !== b[i].length) return false
  }
  return true
}

function createCheckpointSignature(length, view, checkpoints) {
  return {
    length,
    core: view.core,
    ref: view.ref,
    signatures: new Array(checkpoints),
    partials: new Array(checkpoints)
  }
}

function addCheckpointSignatures(pending, checkpoints, index) {
  const { signer, signatures } = checkpoints[index]

  pending[0].signatures[index] = {
    signature: signatures.system.signature,
    length: signatures.system.length,
    signer
  }

  if (pending[1]) {
    pending[1].signatures[index] = {
      signature: signatures.encryption.signature,
      length: signatures.encryption.length,
      signer
    }
  }

  for (let i = 2; i < pending.length; i++) {
    if (!pending[i]) continue

    const { length, signature } = signatures.user[i - 2]

    pending[i].signatures[index] = {
      signature,
      length,
      signer
    }
  }
}

async function setPartialSignature(view, index) {
  const sig = view.signatures[index]
  if (sig.length < view.length && sig.length > 0) await view.core.get(sig.length - 1)

  view.partials[index] = await partialSignature(
    view.core,
    sig.signer,
    view.length,
    sig.length,
    sig.signature
  )
}

function updateSignature(length, sig, existing) {
  return sig ? { signature: sig.signature, length: sig.length, at: length } : existing
}

function updateSignatures(length, signatures, existing) {
  const updated = new Array(signatures.length)

  for (let i = 0; i < updated.length; i++) {
    updated[i] = updateSignature(length, signatures[i], i < existing.length ? existing[i] : null)
  }

  return updated
}

function makeCheckpoint(length, chk) {
  if (!chk) return null

  const checkpointer = length - chk.at

  return {
    checkpointer,
    checkpoint: checkpointer === 0 ? { signature: chk.signature, length: chk.length } : null
  }
}

function makeCheckpoints(length, signatures) {
  if (!signatures.length) return null

  const checkpoints = new Array(signatures.length)

  for (let i = 0; i < signatures.length; i++) {
    checkpoints[i] = makeCheckpoint(length, signatures[i])
  }

  return checkpoints
}

function isViewCorruption(err) {
  return err.code === 'INVALID_OPERATION'
}

function isAssertion(err) {
  return err.code === 'ERR_ASSERTION' || err.name === 'AssertionError'
}
const c = require('compact-encoding')
const encryptionEncoding = require('encryption-encoding')
const messages = require('./messages.js')

module.exports = async function boot(
  corestore,
  key,
  { encrypt, encryptionKey, keyPair, exclusive = true, blindEncryption } = {}
) {
  const result = {
    key: null,
    local: null,
    bootstrap: null,
    encryptionKey: null,
    boot: null
  }

  const manifest = keyPair
    ? { version: corestore.manifestVersion, signers: [{ publicKey: keyPair.publicKey }] }
    : null

  if (key) {
    result.key = key

    const bootstrap = corestore.get({ key, active: false, valueEncoding: messages.OplogMessage })
    await bootstrap.ready()

    const localKey = await bootstrap.getUserData('autobase/local')

    if (keyPair) {
      result.local = corestore.get({
        keyPair,
        active: false,
        exclusive,
        valueEncoding: messages.OplogMessage,
        manifest
      })
    } else {
      if (bootstrap.writable && !localKey) {
        result.local = bootstrap.session({
          active: false,
          exclusive,
          valueEncoding: messages.OplogMessage
        })
      } else {
        const local = localKey
          ? corestore.get({
              key: localKey,
              active: false,
              exclusive,
              valueEncoding: messages.OplogMessage
            })
          : corestore.get({
              name: 'local',
              active: false,
              exclusive,
              valueEncoding: messages.OplogMessage
            })

        await local.ready()
        result.local = local
      }
    }

    result.bootstrap = bootstrap
  } else {
    result.local = keyPair
      ? corestore.get({
          keyPair,
          manifest,
          active: false,
          exclusive,
          valueEncoding: messages.OplogMessage
        })
      : corestore.get({
          name: 'local',
          active: false,
          exclusive,
          valueEncoding: messages.OplogMessage
        })
    await result.local.ready()

    const key = await result.local.getUserData('referrer')
    if (key) {
      result.key = key
      result.bootstrap = corestore.get({ key, active: false, valueEncoding: messages.OplogMessage })
      await result.bootstrap.ready()
    } else {
      result.key = result.local.key
      result.bootstrap = result.local.session({
        active: false,
        valueEncoding: messages.OplogMessage
      })
      await result.bootstrap.setUserData('autobase/local', result.local.key)
    }
  }

  if (key || keyPair) {
    await result.bootstrap.setUserData('referrer', result.key)
    await result.bootstrap.setUserData('autobase/local', result.local.key)
    await result.local.setUserData('referrer', result.key)
  }

  const [encryptionKeyBuffer, encryptionKeyEncryptedBuffer, pointer] = await Promise.all([
    result.local.getUserData('autobase/encryption'),
    result.local.getUserData('autobase/blind-encryption'),
    result.local.getUserData('autobase/boot')
  ])

  if (pointer) {
    result.boot = c.decode(messages.BootRecord, pointer)
  }

  if (encryptionKeyBuffer) {
    // if not encoded, replace
    if (blindEncryption) {
      const encrypted = await encryptionEncoding.encrypt(
        encryptionKeyBuffer,
        blindEncryption.encrypt
      )
      await result.local.setUserData('autobase/blind-encryption', encrypted)
      await result.local.setUserData('autobase/encryption', null)
    }

    result.encryptionKey = encryptionKeyBuffer
  }

  if (encryptionKeyEncryptedBuffer) {
    const { value, rotated } = await encryptionEncoding.decrypt(
      encryptionKeyEncryptedBuffer,
      blindEncryption.decrypt
    )

    if (rotated) {
      const encrypted = await encryptionEncoding.encrypt(value, blindEncryption.encrypt)
      await result.local.setUserData('autobase/blind-encryption', encrypted)
    }

    result.encryptionKey = value
  }

  if (!result.encryptionKey && (encryptionKey || encrypt)) {
    if (!encryptionKey) {
      encryptionKey = (await corestore.createKeyPair('autobase/encryption')).secretKey.subarray(
        0,
        32
      )
    }

    if (blindEncryption) {
      const encrypted = await encryptionEncoding.encrypt(encryptionKey, blindEncryption.encrypt)
      await result.local.setUserData('autobase/blind-encryption', encrypted)
    } else {
      await result.bootstrap.setUserData('autobase/encryption', encryptionKey) // legacy support
      await result.local.setUserData('autobase/encryption', encryptionKey)
    }
    result.encryptionKey = encryptionKey
  }

  return result
}
const crypto = require('hypercore-crypto')

const OPLOG_VERSION = 2
const DEFAULT_AUTOBASE_VERSION = 1 // default
const MAX_AUTOBASE_VERSION = 2 // optional fork support
const BOOT_RECORD_VERSION = 3

const [NS_SIGNER_NAMESPACE, NS_VIEW_BLOCK_KEY, NS_HASH_KEY, NS_ENCRYPTION] = crypto.namespace(
  'autobase',
  4
)

module.exports = {
  OPLOG_VERSION,
  DEFAULT_AUTOBASE_VERSION,
  MAX_AUTOBASE_VERSION,
  BOOT_RECORD_VERSION,
  NS_SIGNER_NAMESPACE,
  NS_VIEW_BLOCK_KEY,
  NS_HASH_KEY,
  NS_ENCRYPTION
}
const BufferMap = require('tiny-buffer-map')

// This is basically just a Map atm, but leaving it as an abstraction for now
// in case we wanna optimize it for our exact usecase

module.exports = class Clock {
  constructor() {
    this.seen = new BufferMap()
  }

  get size() {
    return this.seen.size
  }

  has(key) {
    return this.seen.has(key)
  }

  includes(key, length) {
    return this.seen.has(key) && this.seen.get(key) >= length
  }

  get(key) {
    return this.seen.get(key) || 0
  }

  set(key, len) {
    this.seen.set(key, len)
    return len
  }

  add(clock) {
    for (const [key, l] of clock) {
      if (this.get(key) < l) this.set(key, l)
    }
  }

  [Symbol.iterator]() {
    return this.seen[Symbol.iterator]()
  }
}
const BufferMap = require('tiny-buffer-map')

const Clock = require('./clock.js')

const UNSEEN = 0
const NEWER = 1
const ACKED = 2

// Consensus machine for Autobase. Sort DAG nodes using
// vector clocks to determine a globally consistent view

module.exports = class Consensus {
  constructor(indexers) {
    this.merges = new Set()
    this.majority = (indexers.length >>> 1) + 1
    this.indexers = indexers
    this.removed = new Clock()
    this.updated = false

    this.writers = new BufferMap()
    for (const idx of this.indexers) {
      this.writers.set(idx.core.key, idx)
    }
  }

  addHead(node) {
    if (!node.writer.isActiveIndexer) return
    if (this._isMerge(node)) this.merges.add(node)
    this.updated = true
    return node
  }

  /* Indexer Only DAG methods */

  _tails(node, tails) {
    const tailSet = new Set()
    for (const t of tails) {
      if (node.clock.includes(t.writer.core.key, t.length)) tailSet.add(t)
    }

    return tailSet
  }

  _tailsAndMerges(node, tails) {
    const all = this._tails(node, tails)
    for (const m of this.merges) {
      if (m !== node && node.clock.includes(m.writer.core.key, m.length)) {
        all.add(m)
      }
    }
    return all
  }

  _isMerge(node) {
    if (!node.writer.isActiveIndexer) return false

    const deps = []

    for (const idx of this.indexers) {
      let seq = node.clock.get(idx.core.key) - 1

      if (idx === node.writer) seq--

      const head = idx.get(seq)
      if (!head || this.removed.includes(head.writer.core.key, head.length)) continue

      let isDep = true
      for (let i = 0; i < deps.length; i++) {
        const d = deps[i]
        if (d === head) continue

        if (d.clock.includes(head.writer.core.key, head.length)) {
          isDep = false
          break
        }

        if (head.clock.includes(d.writer.core.key, d.length)) {
          const popped = deps.pop()
          if (d === popped) continue
          deps[i--] = popped
        }
      }

      if (isDep) deps.push(head)
    }

    return deps.length > 1
  }

  _indexerTails() {
    const tails = new Set()
    for (const idx of this.indexers) {
      const length = this.removed.has(idx.core.key) ? this.removed.get(idx.core.key) : idx.indexed

      const head = idx.get(length)
      if (!head || this.removed.includes(head.writer.core.key, head.length)) continue

      let isTail = true
      for (const t of tails) {
        if (head.clock.includes(t.writer.core.key, t.length)) {
          isTail = false
          break
        }

        if (t.clock.includes(head.writer.core.key, head.length)) {
          tails.delete(t)
        }
      }

      if (isTail) tails.add(head)
    }

    return tails
  }

  // parent is newer if for any node in parent's view,
  // either node can see object or object can see node
  _strictlyNewer(object, parent) {
    if (!parent.clock.includes(object.writer.core.key, object.length)) return false

    for (const [key, latest] of parent.clock) {
      const oldest = this.removed.get(key)
      if (latest <= oldest) continue // check quickly if we removed it

      // get the NEXT node from the writer from the objects pov, adjust if its removed
      let length = object.clock.get(key)
      if (length <= oldest) length = oldest

      // sanity check, likely not needed as someone has checked this before, but it's free
      if (latest < length) return false

      // if the same, they have both seen it, continue
      if (latest === length) continue

      const writer = this.writers.get(key)

      // might not be in the removed set but the writer can tell us if it was indexed...
      const next = writer && writer.get(length >= writer.indexed ? length : writer.indexed)

      // no next, its been indexed, both seen it
      if (!next) continue

      // if the NEXT node has seen the object its fine - newer
      if (next.clock.includes(object.writer.core.key, object.length)) continue

      // otherwise the parent must also NOT have seen the next node
      if (latest < next.length) continue

      return false
    }

    return true
  }

  _acks(target) {
    const acks = target.writer.isActiveIndexer ? [target] : [] // TODO: can be cached on the target node in future (ie if we add one we dont have to check it again)

    for (const idx of this.indexers) {
      if (idx === target.writer) continue

      let next = target.clock.get(idx.core.key)
      if (next < idx.nodes.offset) next = idx.nodes.offset

      const nextIndexNode = idx.get(next >= idx.indexed ? next : idx.indexed)

      // no node - no ack
      if (!nextIndexNode) continue

      // if the next index node does not see the target, no ack
      if (!nextIndexNode.clock.includes(target.writer.core.key, target.length)) continue

      // if the next index node is not strictly newer, skip to avoid ambig...
      if (!this._strictlyNewer(target, nextIndexNode)) continue

      acks.push(nextIndexNode)
    }

    return acks
  }

  acksFromNode(target, view) {
    const acks = new Set()

    if (!view || !view.clock.includes(target.writer.core.key, target.length)) return acks

    acks.add(view.writer)

    for (const idx of this.indexers) {
      if (idx === view.writer) continue

      const length = view.clock.get(idx.core.key)
      if (!length) continue

      if (target.clock.includes(idx.core.key, length)) continue

      const head = idx.get(length - 1)
      if (!head) continue

      if (head.clock.includes(target.writer.core.key, target.length)) {
        acks.add(idx)
      }
    }

    return acks
  }

  _ackedAt(acks, parent) {
    let seen = 0
    let missing = acks.length

    for (const node of acks) {
      missing--

      if (!parent.clock.includes(node.writer.core.key, node.length)) {
        if (seen + missing < this.majority) return false
        continue
      }

      if (++seen >= this.majority) return true
    }

    return false
  }

  confirms(indexer, target, acks, length) {
    if (!length || this.removed.get(indexer.core.key) >= length) return UNSEEN
    // def feels like there is a smarter way of doing this part
    // ie we just wanna find a node from the indexer that is strictly newer than target
    // and seens a maj of the acks - thats it

    let jump = true
    let newer = true

    for (let i = length - 1; i >= 0; i--) {
      const head = indexer.get(i)
      if (head === null) return UNSEEN

      let seen = 0

      for (const node of acks) {
        // if (node.writer === indexer) continue
        if (!head.clock.includes(node.writer.core.key, node.length)) continue
        if (++seen >= this.majority) break
      }

      if (!newer && seen < this.majority) {
        break
      }

      if (!this._strictlyNewer(target, head)) {
        // all strictly newer nodes are clustered together so bisect until we find the cluster
        if (jump) {
          jump = false

          let t = length - 1
          let b = 0

          while (t > b) {
            const mid = (t + b) >>> 1
            const node = indexer.get(mid)

            if (
              node === null ||
              !node.clock.includes(target.writer.core.key, target.length) ||
              this._strictlyNewer(target, node)
            ) {
              b = mid + 1
            } else {
              t = mid - 1
            }
          }

          // + 2 in case we are off by one and the i--. its fine, just an optimisation
          if (b + 1 < i) i = b + 2
        }

        newer = false
        continue
      } else if (seen < this.majority) {
        return NEWER
      }

      return ACKED
    }

    return UNSEEN
  }

  _isConfirmed(target, parent = null) {
    const acks = this._acks(target)
    const confs = new Set()

    if (acks.length < this.majority) return false
    let allNewer = true

    for (const indexer of this.indexers) {
      const length = parent
        ? parent.writer === indexer
          ? parent.length - 1
          : parent.clock.get(indexer.core.key)
        : indexer.length

      const result = this.confirms(indexer, target, acks, length)

      if (result === ACKED) {
        confs.add(indexer)
        if (confs.size >= this.majority) {
          return true
        }
      }

      if (result === UNSEEN) allNewer = false
    }

    if (parent) return this._isConfirmableAt(target, parent, acks, confs)

    return allNewer
  }

  _isConfirmableAt(target, parent, acks, confs) {
    if (!this._ackedAt(acks, parent)) return false

    let potential = confs.size

    for (const indexer of this.indexers) {
      if (confs.has(indexer)) continue

      const length = parent.clock.get(indexer.core.key)
      const isSeen = target.clock.includes(indexer.core.key, length)

      // if the target has seen the latest node, it can freely be used to confirm the target later
      // otherwise, check if a newer node is strictly newer...
      if (!isSeen) {
        const head = indexer.get(length - 1)

        // the next indexer head HAS to be strictly newer - meaning the current one has to be also.
        if (
          head &&
          !this.removed.includes(head.writer.core.key, head.length) &&
          !this._strictlyNewer(target, head)
        ) {
          continue
        }
      }

      if (++potential >= this.majority) return true
    }

    return false
  }

  // this can get called multiple times for same node
  remove(node) {
    this.merges.delete(node)
    this.removed.set(node.writer.core.key, node.length)
    return node
  }

  shift() {
    if (!this.updated) return []

    const tails = this._indexerTails()

    for (const tail of tails) {
      if (this._isConfirmed(tail)) {
        return [this.remove(tail)]
      }
    }

    for (const merge of this.merges) {
      if (this._isConfirmed(merge)) {
        return this._yieldNext(merge, tails)
      }
    }

    this.updated = false
    return []
  }

  // yields next indexer node
  _yieldNext(node, tails) {
    // only stop when we find a tail
    while (!tails.has(node)) {
      let next = null

      // for merges check if one fork is confirmed
      for (const t of this._tailsAndMerges(node, tails)) {
        if (this._isConfirmed(t, node)) {
          next = t
          break
        }
      }

      if (next) {
        node = next
        continue
      }

      // otherwise yield all tails
      const tailSet = []
      for (const t of this._tails(node, tails)) {
        tailSet.push(this.remove(t))
      }

      return tailSet
    }

    return [this.remove(node)]
  }

  shouldAck(writer) {
    for (const t of this._indexerTails()) {
      if (t.writer === writer) continue
      if (this._shouldAckNode(t, writer)) return true
    }

    return false
  }

  _shouldAckNode(target, writer) {
    const head = writer.head()
    const next = target.clock.get(writer.core.key)
    const nextIndexNode = writer.get(next >= writer.indexed ? next : writer.indexed)

    // if we have no next node and we didn't write target then ack
    if (!nextIndexNode && writer !== target.writer) return true

    // shortcuts if we have next node
    if (nextIndexNode) {
      // if the next node does not see the target, should ack
      if (!nextIndexNode.clock.includes(target.writer.core.key, target.length)) {
        return !head.clock.includes(target.writer.core.key, target.length)
      }

      // if the next node is not strictly newer, no point acking
      if (!this._strictlyNewer(target, nextIndexNode)) return false
    }

    // now check if we can double confirm
    const acks = this._acks(target)

    // need enough to double confirm
    if (acks.length >= this.majority) {
      return this.confirms(writer, target, acks, writer.length) === UNSEEN
    }

    return false
  }
}
const HypercoreEncryption = require('hypercore/lib/default-encryption.js')
const sodium = require('sodium-universal')
const crypto = require('hypercore-crypto')
const c = require('compact-encoding')
const b4a = require('b4a')
const rrp = require('resolve-reject-promise')
const ReadyResource = require('ready-resource')

const SystemView = require('./system.js')
const { EncryptionDescriptor, ManifestData } = require('./messages.js')
const { NS_VIEW_BLOCK_KEY, NS_HASH_KEY, NS_ENCRYPTION } = require('./caps.js')

const nonce = b4a.alloc(sodium.crypto_stream_NONCEBYTES)
const hash = nonce.subarray(0, sodium.crypto_generichash_BYTES_MIN)

class AutobaseEncryption {
  static PADDING = 8

  constructor(encryption) {
    this.encryption = encryption

    this.compat = null
    this.keys = null
    this.keysById = new Map()
  }

  get id() {
    return this.keys ? this.keys.id : 0
  }

  padding() {
    return AutobaseEncryption.PADDING
  }

  isCompat() {
    return false
  }

  load(keys) {
    if (this.keys === null) this.keys = keys
  }

  async update(ctx) {
    if (this.id !== 0 && this.id === this.encryption.id) return
    const keys = await this.get(this.encryption.id, ctx)
    if (keys) this.keys = keys
  }

  async get(id, ctx) {
    if (this.keysById.has(id)) return this.keysById.get(id)

    const keys = await this.getKeys(id, ctx)
    this.keysById.set(id, keys)

    return keys
  }

  async getKeys(id, ctx) {
    const entropy = await this.encryption.get(id)
    if (!entropy) return null

    const block = this.blockKey(entropy, ctx)
    const hash = crypto.hash([NS_HASH_KEY, block])

    return {
      id,
      block,
      hash
    }
  }

  blockKey(entropy, ctx) {
    return this.encryption.blockKey(entropy, ctx)
  }

  async _ensureCompat(ctx) {
    if (!this.compat) this.compat = this.compatKeys(ctx)
  }

  compatKeys() {
    throw new Error('Compatibility method is not specified')
  }

  async encrypt(index, block, fork, ctx) {
    if (this.isCompat(ctx, index)) {
      this._ensureCompat(ctx)
      return HypercoreEncryption.encrypt(
        index,
        block,
        fork,
        this.compat.block,
        this.compat.blinding
      )
    }

    await this.update(ctx)

    encryptBlock(index, block, this.keys.id, this.keys.block, this.keys.hash)
  }

  async decrypt(index, block, ctx) {
    if (this.isCompat(ctx, index)) {
      this._ensureCompat(ctx)
      return HypercoreEncryption.decrypt(index, block, this.compat.block)
    }

    const padding = block.subarray(0, AutobaseEncryption.PADDING)
    block = block.subarray(AutobaseEncryption.PADDING)

    const type = padding[0]
    switch (type) {
      case 0:
        return block // unencrypted

      case 1:
        break

      default:
        throw new Error('Unrecognised encryption type')
    }

    const id = c.uint32.decode({ start: 4, end: 8, buffer: padding })

    const keys = await this.get(id, ctx)

    c.uint64.encode({ start: 0, end: 8, buffer: nonce }, index)
    nonce.set(padding, 8, 16)

    // Decrypt the block using the full nonce
    decrypt(block, nonce, keys.block)
  }
}

class ViewEncryption extends AutobaseEncryption {
  constructor(encryption, name) {
    super(encryption)
    this.name = name
  }

  isCompat(ctx, index) {
    if (ctx.manifest.version <= 1) return true
    if (!ctx.manifest.userData) return false
    const { legacyBlocks } = c.decode(ManifestData, ctx.manifest.userData)
    return index < legacyBlocks
  }

  compatKeys() {
    const { bootstrap, encryptionKey } = this.encryption.base
    const block = getCompatBlockKey(bootstrap, encryptionKey, this.name)
    return {
      block,
      blinding: crypto.hash(block)
    }
  }

  blockKey(entropy) {
    return getCompatBlockKey(this.encryption.base.bootstrap, entropy, this.name)
  }
}

class WriterEncryption extends AutobaseEncryption {
  isCompat(ctx) {
    return ctx.manifest.version <= 1
  }

  compatKeys(ctx) {
    return HypercoreEncryption.deriveKeys(this.encryption.base.encryptionKey, ctx.key)
  }

  blockKey(entropy, ctx) {
    if (ctx.manifest.userData) {
      const userData = c.decode(ManifestData, ctx.manifest.userData)
      if (userData.namespace !== null) {
        return this.encryption.blockKey(entropy, { key: userData.namespace })
      }
    }

    return this.encryption.blockKey(entropy, ctx)
  }
}

class EncryptionView extends ReadyResource {
  constructor(base, core) {
    super()

    this.base = base
    this.core = core || null

    this.sessions = new Map()

    this._initialising = null
  }

  async _open() {
    await this.initialised()
    await this.core.ready()
  }

  initialised() {
    if (this.core !== null) return this.core.ready()
    if (this._initialising) return this._initialising

    this._initialising = rrp()

    return this._initialising.promise
  }

  _close() {
    if (this._initialising) {
      this._initialising.reject(new Error('Encryption closed'))
      this._initialising = null
    }

    if (this.core) return this.core.close()
  }

  get bootstrapped() {
    return !!(this.core && this.core.length > 0)
  }

  get id() {
    return this.core ? this.core.length : 0
  }

  _createPayload(key) {
    return key
  }

  async reload(core) {
    if (this.core) await this.core.close()

    this.core = core
    await this.core.ready()

    if (this._initialising) {
      this._initialising.resolve()
      this._initialising = null
    }
  }

  unpack(type, payload) {
    if (type > 0) throw new Error('Unsupported version')
    return payload
  }

  async update(key) {
    const payload = await this._createPayload(key)
    const desc = { type: 0, payload }

    await this.core.append(c.encode(EncryptionDescriptor, desc))
  }

  async _preload() {
    await this.ready()
    return this.id()
  }

  getViewEncryption(name) {
    if (this.sessions.has(name)) return this.sessions.get(name)

    const encryption = new ViewEncryption(this, name)
    this.sessions.set(name, encryption)

    return encryption
  }

  getWriterEncryption() {
    return new WriterEncryption(this)
  }

  async encryptAnchor(block, namespace) {
    const entropy = await this.get(this.id)
    if (!entropy) return null

    const blockKey = this.blockKey(entropy, { key: namespace })
    const hashKey = crypto.hash([NS_HASH_KEY, block])

    encryptBlock(0, block, this.id, blockKey, hashKey)
  }

  blockKey(entropy, ctx) {
    return getBlockKey(this.base.bootstrap, this.base.encryptionKey, entropy, ctx.key)
  }

  async get(encryptionId) {
    if (encryptionId === 0) return SystemView.GENESIS_ENTROPY

    if (!this.core) await this.initialised()

    const index = encryptionId - 1
    const desc = await this.core.get(index)
    const { type, payload } = c.decode(EncryptionDescriptor, desc)

    const key = this.unpack(type, payload)
    return key
  }

  getSystemEncryption() {
    return this.getViewEncryption('_system')
  }

  static namespace(entropy) {
    return crypto.hash([NS_ENCRYPTION, entropy])
  }

  static getBlockKey(bootstrap, encryptionKey, entropy, hypercoreKey) {
    return getBlockKey(bootstrap, encryptionKey, entropy, hypercoreKey)
  }

  static getSystemEncryption(base, core) {
    const view = new EncryptionView(base, core)
    return view.getSystemEncryption()
  }

  static async setSystemEncryption(base, core, opts) {
    if (base.encryptionKey === null) return null

    await core.ready()
    if (core.manifest.version === 1) {
      const key = getCompatBlockKey(base.bootstrap, base.encryptionKey, '_system')
      return core.setEncryptionKey(key, { block: true })
    }

    if (!core.manifest.linked.length) {
      throw new Error('System manifest does not link encryption view')
    }

    const enc = base.store.get({ key: core.manifest.linked[0], active: false })
    const encryption = EncryptionView.getSystemEncryption(base, enc)

    await core.setEncryption(encryption, opts)

    return enc
  }
}

module.exports = {
  AutobaseEncryption,
  EncryptionView
}

function encrypt(block, nonce, key) {
  sodium.crypto_stream_xor(block, block, nonce, key)
}

function decrypt(block, nonce, key) {
  return encrypt(block, nonce, key) // symmetric
}

function getBlockKey(bootstrap, encryptionKey, entropy, hypercoreKey) {
  return (
    encryptionKey &&
    crypto.hash([NS_VIEW_BLOCK_KEY, bootstrap, encryptionKey, entropy, hypercoreKey])
  )
}

function getCompatBlockKey(bootstrap, encryptionKey, name) {
  if (typeof name === 'string') return getCompatBlockKey(bootstrap, encryptionKey, b4a.from(name))
  return encryptionKey && crypto.hash([NS_VIEW_BLOCK_KEY, bootstrap, encryptionKey, name])
}

function blockhash(block, padding, hashKey) {
  sodium.crypto_generichash(hash, block, hashKey)
  padding.set(hash.subarray(0, 8)) // copy first 8 bytes of hash
  hash.fill(0) // clear nonce buffer
}

function encryptBlock(index, block, id, blockKey, hashKey) {
  const padding = block.subarray(0, AutobaseEncryption.PADDING)
  block = block.subarray(AutobaseEncryption.PADDING)

  blockhash(block, padding, hashKey)
  c.uint32.encode({ start: 4, end: 8, buffer: padding }, id)

  c.uint64.encode({ start: 0, end: 8, buffer: nonce }, index)

  padding[0] = 1 // version in plaintext

  nonce.set(padding, 8, 16)

  // The combination of index, key id, fork id and block hash is very likely
  // to be unique for a given Hypercore and therefore our nonce is suitable
  encrypt(block, nonce, blockKey)
}
const rrp = require('resolve-reject-promise')
const safetyCatch = require('safety-catch')

const SystemView = require('./system.js')
const { EncryptionView } = require('./encryption.js')

const DEFAULT_OP_TIMEOUT = 5_000
const DEFAULT_MIN_FF = 16

module.exports = class FastForward {
  constructor(
    base,
    key,
    {
      timeout = DEFAULT_OP_TIMEOUT,
      verified = true,
      minimum = DEFAULT_MIN_FF,
      force = false,
      length = 0
    } = {}
  ) {
    this.base = base
    this.key = key
    this.timeout = timeout
    this.force = force
    this.core = base.store.get({ key, active: true, encryption: null })
    this.length = length
    this.views = []
    this.encryption = null
    this.entropy = null
    this.indexers = []
    this.minimum = minimum
    this.verified = verified
    this.waiting = new Set()
    this.cores = [this.core]
    this.system = null
    this.destroyed = false
    this.upgrading = null
    this.failed = false
  }

  static MINIMUM = DEFAULT_MIN_FF

  async upgrade() {
    try {
      if (!this.upgrading) this.upgrading = this._upgrade()

      if (!(await this.upgrading)) return null

      return {
        length: this.length,
        force: this.force,
        key: this.key,
        indexers: this.indexers,
        views: this.views,
        entropy: this.entropy,
        manifestVersion: this.core.manifest.version,
        minimum: this.minimum
      }
    } catch (err) {
      safetyCatch(err)
      this.failed = true
      return null
    } finally {
      await this.close()
    }
  }

  _waitForAppend(core) {
    if (core.length > 0) return Promise.resolve()

    const promise = rrp()
    const timeout = setTimeout(this.close.bind(this), this.timeout)
    const wait = { promise, timeout }

    this.waiting.add(wait)

    core.once('append', () => {
      this.waiting.delete(wait)
      clearTimeout(timeout)
      promise.resolve()
    })

    return promise.promise
  }

  _minLength() {
    return this.base.core.length + this.minimum
  }

  async _upgrade() {
    await this.core.ready()

    if (!this.verified) await this._waitForAppend(this.core)
    if (this.destroyed) return false

    if (this.core.manifest.linked) {
      const appends = []

      for (const key of this.core.manifest.linked) {
        const core = this.base.store.get({ key, active: true })
        this.cores.push(core)

        await core.ready()
        appends.push(this._waitForAppend(core))
      }

      await Promise.all(appends)
    }

    if (this.length === 0) this.length = this.core.length

    // note we use the persisted length here, as we might as well continue that work as thats ~ the same length
    if (!this.force && this.length < this._minLength()) return false
    if (this.length === 0) return false

    await this.core.get(this.length - 1, { timeout: this.timeout })
    await this.base.ready()

    if (this.destroyed) return false

    if (this.base.encryptionKey && !(await this._ensureEncryption())) return false
    if (this.destroyed) return false

    this.system = new SystemView(this.core, { checkout: this.length })
    await this.system.ready()
    if (this.destroyed) return false

    this.entropy = this.system.entropy

    if (this.core.manifest.version >= 2) {
      const linked = this.core.manifest.linked

      if (this.system.encryptionLength === 0 || linked === null || !linked.length) {
        return false
      }

      // technically diverges from system.views to include encryption here, but makes sense
      this.views.push({
        key: linked[0],
        length: this.system.encryptionLength
      })
    }

    const promises = []

    // ensure local key is locally available always
    promises.push(this.system.get(this.base.local.key, { timeout: this.timeout }))

    if (this.encryption && this.encryption.core) {
      promises.push(
        this.encryption.core.get(this.system.encryptionLength - 1, { timeout: this.timeout })
      )
    }

    for (const v of this.system.views) {
      this.views.push(v)
      const core = this.base.store.get({ key: v.key, active: true })
      this.cores.push(core)
      if (!v.length) continue // encryption view can be 0 length
      promises.push(core.get(v.length - 1, { timeout: this.timeout }))
    }

    for (const idx of this.system.indexers) {
      this.indexers.push(idx)
      if (idx.length === 0) continue // need to make sure we have manifest...
      const core = this.base.store.get({ key: idx.key, active: true })
      this.cores.push(core)
      promises.push(core.get(idx.length - 1, { timeout: this.timeout }))
      promises.push(this.system.get(idx.key, { timeout: this.timeout }))
    }

    for (const head of this.system.heads) {
      promises.push(this.system.get(head.key, { timeout: this.timeout }))
    }

    await Promise.all(promises)
    if (this.destroyed) return false

    return true
  }

  async _ensureEncryption() {
    // only need encryption if we migrated
    if (this.core.encryption !== null) return true

    const encCore = this.core.manifest.version <= 1 ? null : this.cores[1]

    // expects internal views to be loaded in order
    this.encryption = new EncryptionView(this.base, encCore)
    const encryption = this.encryption.getViewEncryption('_system')

    await this.core.setEncryption(encryption)

    return true
  }

  async close() {
    this.destroyed = true
    for (const { promise, timeout } of this.waiting) {
      clearTimeout(timeout)
      promise.resolve() // silently bail
    }
    if (this.system) await this.system.close()
    if (this.encryption) await this.encryption.close()
    for (const core of this.cores) await core.close()
    await this.core.close()
  }
}
const c = require('compact-encoding')

const SystemView = require('./system.js')
const { EncryptionView } = require('./encryption.js')
const { ManifestData } = require('./messages.js')

module.exports = class Fork {
  constructor(base, store, state, fork) {
    this.base = base
    this.store = store
    this.state = state
    this.indexers = fork.indexers
    this.length = fork.length
    this.system = null
    this.encryption = null
    this.manifests = null
    this.cores = []
  }

  async upgrade() {
    try {
      await this._upgrade()
      return true
    } catch {
      return false
    } finally {
      await this.close()
    }
  }

  async _upgrade() {
    this.manifests = await this.store.getIndexerManifests(this.indexers)

    this.system = new SystemView(await this._get('_system', this.length))
    this.encryption = new EncryptionView(this, await this._get('_encryption', -1))

    await this.system.ready()
    await this.encryption.ready()

    this.entropy = this.system.getEntropy(this.indexers, this.length)
    this.manifestVersion = Math.max(this.system.core.manifest.version, 2) // >= 2 to signal new encryption

    const views = []
    for (const view of this.state.views) {
      const v = this.getViewFromSystem(view, this.system)
      const indexedLength = v ? v.length : 0

      const session = await this._get(view.name, indexedLength)
      await session.ready()

      const next = await this.createView(view.name, session, null)
      await next.ready()

      await this._migrate(view.ref, next, view.core, indexedLength)

      // update key
      views.push({ key: next.key, length: indexedLength })
    }

    // internal views are explicitly migrated

    const entropy = EncryptionView.namespace(this.entropy)
    await this.encryption.update(entropy)

    const system = this.store.getViewByName('_system')
    const encryption = this.store.getViewByName('_encryption')

    const enc = await this.createView('_encryption', this.encryption.core, null)
    await enc.ready()

    await this._migrate(encryption, enc, this.encryption.core, this.encryption.core.length)

    await this.system.fork(this.indexers, this.manifests, this.encryption.core.length, views)

    const sys = await this.createView('_system', this.system.core, [enc.key])
    await sys.ready()

    await this._migrate(system, sys, this.system.core, this.system.core.length)
  }

  async createView(name, core, linked) {
    const prologue = await getPrologue(core)
    const manifestData = this._manifestData(core)

    return this.store.getViewCore(
      this.manifests,
      name,
      prologue,
      this.manifestVersion,
      this.entropy,
      linked,
      manifestData
    )
  }

  async _get(name, length) {
    const core = this.store.get({ name })
    this.cores.push(core)

    if (length !== -1) await core.truncate(length)

    return core
  }

  getViewFromSystem(view) {
    if (view.mappedIndex === -1 || view.mappedIndex >= this.system.views.length) return null
    return this.system.views[view.mappedIndex]
  }

  _manifestData(session) {
    if (session.manifest.version > 1) return session.manifest.userData
    return c.encode(ManifestData, { version: 0, legacyBlocks: session.length })
  }

  async _migrate(ref, next, source, length) {
    if (length > 0) {
      await next.core.copyPrologue(source.state)
    }

    // remake the batch, reset from our prologue in case it replicated inbetween
    // TODO: we should really have an HC function for this
    const batch = next.session({ name: 'batch', overwrite: true, checkout: length })
    await batch.ready()

    if (source !== null) {
      while (batch.length < source.length) {
        await batch.append(await source.get(batch.length))
      }
    }

    ref.moveTo = [batch, next]
  }

  async close() {
    this.destroyed = true
    if (this.system) await this.system.close()
    if (this.encryption) await this.encryption.close()
    for (const core of this.cores) await core.close()
  }
}

async function getPrologue(core) {
  if (!core.length) return null

  return {
    hash: await core.treeHash(),
    length: core.length
  }
}
const b4a = require('b4a')
const assert = require('nanoassert')

const Clock = require('./clock.js')
const Consensus = require('./consensus.js')
const Topolist = require('./topolist.js')

class Node {
  constructor(writer, length, value, heads, batch, dependencies, optimistic, trace) {
    this.writer = writer
    this.length = length
    this.value = value
    this.heads = heads
    this.actualHeads = heads.slice(0) // TODO: we should remove this and just not mutate heads...

    this.dependents = new Set()
    this.dependencies = dependencies
    this.index = 0 // used by topolist

    this.optimistic = optimistic && batch === 1 && !!value

    this.batch = batch
    this.dropped = 0
    this.trace = trace

    this.clock = new Clock()

    this.yielded = false
    this.yielding = false
  }

  isTail() {
    return this.dependencies.size === this.dropped
  }

  causalDependencies(idx) {
    const order = [this]
    const stack = [this]

    let visited = null

    while (stack.length > 0) {
      const node = stack.pop()
      for (const dep of node.dependencies) {
        if (!dep.writer.isRemoved || dep.writer.isActiveIndexer) continue

        // TODO: replace with "coloring" but ok for now, tiny set
        if (visited === null) visited = new Set()
        if (visited.has(dep)) continue
        visited.add(dep)

        stack.push(dep)
        order.push(dep)
      }
    }

    return order
  }

  clear() {
    this.clock = null
    this.dependents = null
    return this
  }

  reset() {
    this.yielded = false
    this.yielding = false
    for (const dep of this.dependents) dep.dependencies.add(this)
    this.dependents.clear()
  }

  active() {
    for (const dep of this.dependencies) {
      if (dep.yielded) {
        this.dependencies.delete(dep) // nodes might be yielded during buffering
      } else {
        dep.dependents.add(this)
        this.clock.add(dep.clock)
      }
    }

    if (this.writer.isActiveIndexer) this.clock.set(this.writer.core.key, this.length)
  }

  tieBreak(node) {
    return tieBreak(this, node)
  }

  hasDependency(dep) {
    for (const h of this.actualHeads) {
      if (sameNode(h, dep)) return true
    }
    return false
  }

  get ref() {
    return this.writer.core.key.toString('hex').slice(0, 2) + ':' + this.length
  }
}

module.exports = class Linearizer {
  constructor(indexers, { heads = [], writers = new Map() } = {}) {
    this.heads = new Set()
    this.tails = new Set()
    this.tip = new Topolist()
    this.size = 0 // useful for debugging
    this.updated = false
    this.indexersUpdated = false
    this.writers = writers

    this.consensus = new Consensus(indexers)
    this._initialHeads = heads.slice(0)
    this._strictlyAdded = null

    for (const { key, length } of heads) {
      this.consensus.removed.set(key, length)
    }
  }

  get indexers() {
    return this.consensus.indexers
  }

  static createNode(writer, length, value, heads, batch, dependencies, optimistic, trace) {
    return new Node(writer, length, value, heads, batch, dependencies, optimistic, trace)
  }

  // returns the global links of the dag, use this to link against the current state of the dag
  // TODO: rename to heads() and move the sets to _ props
  getHeads() {
    const heads = this._initialHeads.slice(0)
    for (const node of this.heads) heads.push({ key: node.writer.core.key, length: node.length })
    return heads
  }

  // TODO: might contain dups atm, nbd for how we use it, returns an array of writers you can "pull"
  // to get the full dag view at any time
  getBootstrapWriters() {
    const writers = []

    for (const head of this.heads) writers.push(head.writer)
    for (let i = 0; i < this.consensus.indexers.length; i++)
      writers.push(this.consensus.indexers[i])

    return writers
  }

  addHead(node) {
    node.active()

    // 99.99% of the time _initialHeads is empty...
    if (this._initialHeads.length > 0) this._updateInitialHeads(node)

    if (node.isTail()) {
      this.tails.add(node)
    }

    for (const head of this.heads) {
      if (node.hasDependency(head)) {
        this.heads.delete(head)
      }
    }

    this.tip.add(node)
    if (node.writer.isActiveIndexer) this.consensus.addHead(node)

    this.size++
    this.heads.add(node)

    this.updated = true

    return node
  }

  update() {
    if (!this.updated) return null
    this.updated = false

    // get the indexed nodes
    const indexed = []
    while (true) {
      const nodes = this.consensus.shift()
      if (!nodes.length) break

      this._yield(nodes, indexed)
    }

    return this.tip.flush(indexed)
  }

  _updateInitialHeads(node) {
    for (const head of node.actualHeads) {
      for (let i = 0; i < this._initialHeads.length; i++) {
        const { key, length } = this._initialHeads[i]
        if (length !== head.length || !b4a.equals(key, head.key)) continue
        this._initialHeads.splice(i--, 1)
      }
    }
  }

  /* Ack methods */

  shouldAck(writer, pending = false) {
    if (!writer || !writer.isActiveIndexer) return false

    // all indexers have to flushed to the dag before we ack as a quick "debounce"
    for (const w of this.indexers) {
      if (w.length !== w.available) return false
    }

    let isHead = false

    // if ANY head is not an indexer ack
    for (const head of this.heads) {
      if (!head.writer.isActiveIndexer) return true
      if (head.writer === writer) isHead = true
    }

    if (this.heads.size === 1 && isHead) {
      return false // never self-ack!
    }

    const visited = new Set()

    // check if there is non-null value
    let valueCheck = false

    for (const tail of this.tails) {
      if (pending || this._nonNull(tail, visited)) {
        valueCheck = true
        break
      }
    }

    if (!valueCheck) return false

    if (this.consensus.shouldAck(writer)) return true

    return this._shouldAckHeads(writer, pending)
  }

  // check if there is any value above this node
  _nonNull(target, visited) {
    const stack = [target]

    while (stack.length) {
      const node = stack.pop()

      if (visited.has(node)) continue
      if (node.value !== null) return true

      visited.add(node)

      for (const dep of node.dependents) {
        stack.push(dep)
      }
    }

    return false
  }

  // ack if any head is closer to confirming a value
  _shouldAckHeads(writer, pending) {
    const prev = writer.head()

    for (const head of this.heads) {
      // only check other writers heads
      if (head.writer === writer) continue

      const stack = [head]
      const visited = new Set()

      while (stack.length) {
        const node = stack.pop()

        if (visited.has(node)) continue
        visited.add(node)

        if (pending || node.value !== null) {
          const acks = this.consensus.acksFromNode(node, head)
          const prevAcks = this.consensus.acksFromNode(node, prev)

          // head sees more acks
          if (acks.size > prevAcks.size) return true

          for (const idx of acks) {
            // head sees acks that writer does not
            if (!prevAcks.has(idx)) return true
          }

          // both seen, no point going any further down
          if (prevAcks.size && acks.size) continue
        }

        stack.push(...node.dependencies)
      }
    }

    return false
  }

  /* Full DAG methods */

  _yield(nodes, indexed = []) {
    const offset = indexed.length
    const tails = []

    // determine which nodes are yielded
    while (nodes.length) {
      const node = nodes.pop()

      if (node.yielding) continue
      node.yielding = true

      if (node.isTail()) tails.push(node)

      nodes.push(...node.dependencies)
    }

    while (tails.length) {
      let tail = tails.pop()

      for (tail of this._removeBatch(tail)) {
        Topolist.add(tail, indexed, offset)
      }

      for (const dep of tail.dependents) {
        if (dep.isTail() && dep.yielding) tails.push(dep)
      }
    }

    return indexed
  }

  _isFutureTail(node) {
    let dropped = node.dropped

    // a tail has no unyielded dependencies
    for (const dep of node.dependencies) {
      if (dep.yielded) continue
      if (dropped === 0) return false
      dropped--
    }

    return true
  }

  _removeNode(node) {
    this.tails.delete(node)
    this.heads.delete(node)
    this.consensus.remove(node)

    // update the tailset
    for (const d of node.dependents) {
      if (d.yielding && d.dependencies.has(node))
        d.dropped++ // keep links for the indexed batch
      else d.dependencies.delete(node)
      if (this._isFutureTail(d)) this.tails.add(d)
    }

    node.yielded = true
    this.size--

    if (this.heads.size === 0) {
      // in case of a single writer the dag might drain immediately...
      this._initialHeads.push({ key: node.writer.core.key, length: node.length })
    }

    return node
  }

  _removeBatch(node) {
    const batch = [this._removeNode(node)]

    while (node.batch !== 1) {
      // its a batch!
      if (node.dependents.size === 0) {
        // bad batch node, auto correct
        const next = node.writer.get(node.length)
        if (next && next.batch === node.batch - 1) node.dependents.add(next)
      }

      assert(node.dependents.size === 1, 'Batch is linked partially, which is not allowed')

      node = getFirst(node.dependents)
      batch.push(this._removeNode(node))
    }

    return batch
  }
}

function tieBreak(a, b) {
  return Topolist.compare(a, b) < 0 // lowest key wis
}

function getFirst(set) {
  return set[Symbol.iterator]().next().value
}

function sameNode(a, b) {
  return b4a.equals(a.key, b.writer.core.key) && a.length === b.length
}
const c = require('compact-encoding')
const b4a = require('b4a')

const messages = require('./messages.js')

const GTE = b4a.from([messages.LINEARIZER_PREFIX])
const LT = b4a.from([messages.LINEARIZER_PREFIX + 1])

module.exports = class LocalState {
  constructor(core) {
    this.core = core
    this.tx = null
  }

  static clear(core) {
    const tx = core.state.storage.write()
    tx.deleteLocalRange(GTE, LT)
    return tx.flush()
  }

  static async moveTo(src, dst) {
    const boot = await src.getUserData('autobase/boot')
    const tx = dst.state.storage.write()

    tx.deleteLocalRange(GTE, LT)

    for await (const data of src.state.storage.createLocalStream({ gte: GTE, lt: LT })) {
      tx.putLocal(data.key, data.value)
    }

    tx.putUserData('autobase/boot', boot)

    await tx.flush()
  }

  setBootRecord(boot) {
    if (this.tx === null) this.tx = this.core.state.storage.write()

    this.tx.putUserData('autobase/boot', c.encode(messages.BootRecord, boot))
  }

  async listUpdates() {
    const updates = []

    for await (const data of this.core.state.storage.createLocalStream({ gte: GTE, lt: LT })) {
      const update = c.decode(messages.LinearizerUpdate, data.value)
      const seq = c.decode(messages.LinearizerKey, data.key)
      update.seq = seq
      updates.push(update)
    }

    return updates
  }

  clearUpdates() {
    if (this.tx === null) this.tx = this.core.state.storage.write()

    this.tx.deleteLocalRange(GTE, LT)
  }

  deleteUpdate(update) {
    if (this.tx === null) this.tx = this.core.state.storage.write()

    this.tx.deleteLocal(c.encode(messages.LinearizerKey, update.seq))
  }

  insertUpdate(update) {
    if (this.tx === null) this.tx = this.core.state.storage.write()

    this.tx.putLocal(
      c.encode(messages.LinearizerKey, update.seq),
      c.encode(messages.LinearizerUpdate, update)
    )
  }

  flush() {
    if (!this.tx) return Promise.resolve()

    const flushing = this.tx.flush()
    this.tx = null
    return flushing
  }
}
const schema = require('../encoding/spec/autobase')

module.exports = {
  Wakeup: schema.resolveStruct('@autobase/wakeup'),
  Clock: schema.resolveStruct('@autobase/clock'),
  Checkout: schema.resolveStruct('@autobase/checkout'),
  BootRecord: schema.resolveStruct('@autobase/boot-record'),
  OplogMessage: schema.resolveStruct('@autobase/oplog-message'),
  Checkpoint: schema.resolveStruct('@autobase/checkpoint'),
  Info: schema.resolveStruct('@autobase/info'),
  Member: schema.resolveStruct('@autobase/member'),
  ManifestData: schema.resolveStruct('@autobase/manifest-data'),
  LINEARIZER_PREFIX: 1,
  LinearizerKey: schema.resolveStruct('@autobase/linearizer-key'),
  LinearizerUpdate: schema.resolveStruct('@autobase/linearizer-update'),
  EncryptionDescriptor: schema.resolveStruct('@autobase/encryption-descriptor')
}
const DEFAULT_SIZE = 32

module.exports = class NodeBuffer {
  constructor(offset, hwm) {
    this.hwm = hwm || DEFAULT_SIZE
    this.defaultHwm = this.hwm
    this.mask = this.hwm - 1
    this.top = 0
    this.btm = 0
    this.buffer = new Array(this.hwm)
    this.offset = offset || 0
    this.length = this.offset
  }

  get size() {
    return this.length - this.offset
  }

  isEmpty() {
    return this.length === this.offset
  }

  isFull() {
    return this.size === this.buffer.length
  }

  grow() {
    this.hwm <<= 1

    const size = this.size
    const buffer = new Array(this.hwm)
    const mask = this.hwm - 1

    for (let i = 0; i < size; i++) {
      buffer[i] = this.buffer[(this.btm + i) & this.mask]
    }

    this.mask = mask
    this.top = size
    this.btm = 0
    this.buffer = buffer
  }

  push(data) {
    if (this.isFull()) this.grow()

    this.buffer[this.top] = data
    this.top = (this.top + 1) & this.mask

    return this.length++
  }

  shift() {
    if (this.isEmpty()) return null

    const last = this.buffer[this.btm]

    this.buffer[this.btm] = undefined
    this.btm = (this.btm + 1) & this.mask
    this.offset++

    // reset on empty
    if (this.isEmpty() && this.hwm !== this.defaultHwm) {
      this.buffer = new Array(this.defaultHwm)
      this.hwm = this.buffer.length
      this.mask = this.hwm - 1
      this.top = this.btm = 0
    }

    return last
  }

  get(seq) {
    if (seq < this.offset || seq >= this.length) return null
    return this.buffer[(this.btm + (seq - this.offset)) & this.mask]
  }
}
const b4a = require('b4a')
const c = require('compact-encoding')
const crypto = require('hypercore-crypto')
const Hypercore = require('hypercore')

const messages = require('./messages.js')

const DEFAULT_MANIFEST_VERSION = 1
const INDEX_VERSION = 1
const MAX_TRACE_PER_VIEW = 256

const EMPTY = b4a.alloc(0)

const [NS_SIGNER_NAMESPACE] = crypto.namespace('autobase', 1)

class ViewCore {
  constructor(name, core, base) {
    this.name = name
    this.core = null
    this.batch = null
    this.atomicBatch = null
    this.moveTo = null
    this.tracer = new Tracer()
    this.shared = 0

    this.ontruncate = (length) => {
      this.shared = Math.min(this.shared, length)
    }
    this.migrated(base, core)
  }

  getCore() {
    return this.atomicBatch || this.batch || this.core
  }

  migrated(base, core) {
    // TODO: close old core if present, for now we just close when the autobase is closed indirectly
    // atm its unsafe to do as moveTo has a bug due to a missing read lock in hc
    this.core = core

    if (this.name === '_system') {
      const ff = base._queueFastForward.bind(base)
      this.core.on('append', ff)
      this.core.ready().then(ff, ff)
    }
  }

  async matchesKey(key) {
    if (!this.core.opened) await this.core.ready()
    return b4a.equals(this.core.key, key)
  }

  async matchesNamespace(target) {
    if (!this.core.opened) await this.core.ready()

    if (this.core.manifest && this.core.manifest.signers.length > 0) {
      const ns = this.core.manifest.signers[0].namespace
      if (b4a.equals(ns, target)) return true
    }

    return false
  }

  getNextKey() {
    return this.moveTo ? this.moveTo[0].key : null
  }

  async commit(atom, length, signature) {
    // TODO: is this how its supposed be done atomic wise?
    await this.core.commit(this._getAtomicBatch(atom), { length, signature })
  }

  async release() {
    if (!this.atomicBatch) return
    await this.atomicBatch.ready()

    while (this.atomicBatch.state.sessions.length) {
      await this.atomicBatch.state.sessions[0].close()
    }

    await this.atomicBatch.close()
    this.atomicBatch = null
  }

  _getAtomicBatch(atom) {
    if (this.atomicBatch === null) {
      this.atomicBatch = this.batch.session({ atom, writable: true })
      this.atomicBatch.on('truncate', this.ontruncate)
    }

    return this.atomicBatch
  }

  async catchup(atom, length) {
    await this.release()
    const batch = this._getAtomicBatch(atom)
    await batch.ready()
    await batch.state.catchup(length)
  }

  createSession(atom, valueEncoding) {
    if (this.batch === null) {
      this.batch = this.core.session({ name: 'batch', writable: true })
      this.batch.on('truncate', this.ontruncate)
    }

    const s = atom
      ? this._getAtomicBatch(atom).session({
          valueEncoding,
          writable: true,
          onseq: this.tracer.onseq.bind(this.tracer)
        })
      : this.batch.session({ valueEncoding, writable: false })

    return s
  }
}

class Tracer {
  constructor() {
    this.enabled = false
    this.seen = new Set()
  }

  start() {
    this.enabled = true
    this.seen.clear()
  }

  end() {
    this.enabled = false
    return [...this.seen.values()]
  }

  onseq(seq, core) {
    if (!this.enabled) return
    if (this.seen.size >= MAX_TRACE_PER_VIEW) return
    if (seq >= core.signedLength) return

    this.seen.add(seq)
  }
}

class AutoStore {
  constructor(base, byName) {
    this.base = base
    this.store = base.store
    this.byName = byName || new Map()
    this.opened = []
    this.atom = null
    this.local = null
  }

  async close() {
    if (this.local) await this.local.close()
    for (const v of this.byName.values()) {
      await v.release()
    }
    if (this.atom) return
    for (const v of this.byName.values()) {
      if (v.core) await v.core.close()
      if (v.batch) await v.batch.close()
    }
  }

  async export() {
    const views = []

    for (const [name, value] of this.byName) {
      const core = await this.store.storage.export(value.core.discoveryKey, { batches: true })
      views.push({ name, core })
    }

    return views
  }

  atomize() {
    const store = new AutoStore(this.base, this.byName)
    store.atom = this.store.storage.createAtom()
    return store
  }

  async updateLocal() {
    if (!this.local) return
    await this.local.close()
    this.local = null
  }

  flush() {
    return this.atom ? this.atom.flush() : Promise.resolve()
  }

  get(opts, moreOpts) {
    if (typeof opts === 'string') opts = { name: opts }
    if (moreOpts) opts = { ...opts, ...moreOpts }

    const { name, valueEncoding = null } = opts

    if (!name) throw new Error('name is required')
    return this.getViewByName(name).createSession(this.atom, valueEncoding)
  }

  getViewByName(name) {
    let view = this.byName.get(name)

    if (!view) {
      const preload = this._preload(name)
      const core = this.base.store.get({ preload })
      view = new ViewCore(name, core, this.base)
      this.byName.set(name, view)
    }

    if (this.opened.indexOf(view) === -1) {
      this.opened.push(view)
    }

    return view
  }

  getLocal() {
    if (this.local !== null) return this.local

    this.local = this.base.local.session({
      atom: this.atom,
      valueEncoding: messages.OplogMessage,
      encryption: this.base.getWriterEncryption(this.base.local.key),
      active: false
    })

    return this.local
  }

  getViews() {
    return [...this.byName.values()]
  }

  getViewCount() {
    return this.byName.size
  }

  getSystemView() {
    return this.getViewByName('_system')
  }

  getSystemCore() {
    return this.getSystemView().core
  }

  getEncryption(name) {
    if (!this.base.encryptionKey) return null
    if (name === '_encryption') return null

    return this.base.encryption.getViewEncryption(name)
  }

  async getIndexerManifests(entries) {
    const manifests = []
    for (const { key } of entries) {
      const core = this.store.get({ key, active: false })
      await core.ready()
      if (!core.manifest) continue // danger but only for bootstrapping...
      manifests.push(core.manifest)
      await core.close()
    }
    return manifests
  }

  getViewCore(indexerManifests, name, prologue, manifestVersion, entropy, linked, manifestData) {
    const manifest = this._createManifest(
      indexerManifests,
      name,
      prologue,
      manifestVersion,
      entropy,
      linked,
      manifestData
    )

    return this.base.store.get({
      manifest,
      exclusive: false,
      encryption: this.getEncryption(name)
    })
  }

  listViews() {
    const views = []
    for (const [name, ref] of this.byName) {
      const core = ref.batch || ref.core
      views.push({ name, key: core.key, length: core.length, signedLength: core.signedLength })
    }
    return views
  }

  createAnchorCore(prologue, manifestData) {
    const manifest = {
      version: 2,
      hash: 'blake2b',
      prologue,
      allowPatch: false,
      quorum: 0,
      signers: [],
      userData: manifestData,
      linked: null
    }

    const core = this.store.get({
      manifest,
      active: false
    })

    return core
  }

  async createView(
    indexerManifests,
    name,
    prologue,
    manifestVersion,
    entropy,
    linked,
    manifestData
  ) {
    const manifest = this._createManifest(
      indexerManifests,
      name,
      prologue,
      manifestVersion,
      entropy,
      linked,
      manifestData
    )

    const core = this.store.get({
      key: Hypercore.key(manifest),
      manifest,
      active: false
    })

    await core.ready()
    const key = core.key

    await core.close()
    return key
  }

  async _preload(name) {
    await Promise.resolve()

    if (!this.base.opening) throw new Error('Autobase failed to open')
    await this.base._preopen

    const boot = (await this.base._getSystemInfo()) || {
      key: this.getBootstrapSystemKey(),
      indexers: [],
      views: [],
      entropy: null
    }
    const indexerManifests = await this.getIndexerManifests(boot.indexers)

    // no system, everything is fresh
    if (boot.indexers.length === 0) {
      return this._freshCorePreload(indexerManifests, name, boot.entropy)
    }

    // asking for the system, just return it, easy
    if (name === '_system') {
      return {
        key: boot.key,
        exclusive: false,
        encryption: this.getEncryption(name)
      }
    }

    if (name === '_encryption') {
      const core = this.getSystemCore()
      await core.ready()

      if (!core.manifest.linked) {
        return this._freshCorePreload(indexerManifests, name, boot.entropy)
      }

      return {
        key: core.manifest.linked[0],
        exclusive: false,
        encryption: null
      }
    }

    // infer which view
    const v = await this.findViewByName(indexerManifests, boot.views, name, boot.entropy)

    // new view, fresh
    if (v === null) {
      return this._freshCorePreload(indexerManifests, name, boot.entropy)
    }

    return {
      key: v.key,
      exclusive: false,
      encryption: this.getEncryption(name)
    }
  }

  _freshCorePreload(indexerManifests, name, entropy) {
    if (name === '_system') return this._freshSystemPreload(indexerManifests, entropy)

    return {
      manifest: this._createManifest(
        indexerManifests,
        name,
        null,
        DEFAULT_MANIFEST_VERSION,
        entropy,
        [],
        null
      ),
      exclusive: true,
      encryption: this.getEncryption(name)
    }
  }

  _freshSystemPreload(indexerManifests, entropy) {
    return {
      manifest: this._createSystemManifest(
        indexerManifests,
        null,
        DEFAULT_MANIFEST_VERSION,
        entropy,
        null
      ),
      exclusive: true,
      encryption: this.getEncryption('_system')
    }
  }

  _deriveStaticHash(name) {
    // key doesnt matter...
    return crypto.hash([this.base.key, b4a.from(name)])
  }

  _deriveNamespace(name, entropy) {
    const encryptionId = crypto.hash(this.base.encryptionKey || EMPTY)
    const version = c.encode(c.uint, INDEX_VERSION)
    const bootstrap = this.base.key

    return crypto.hash([
      NS_SIGNER_NAMESPACE,
      version,
      bootstrap,
      encryptionId,
      entropy,
      b4a.from(name)
    ])
  }

  async _getCoreManifest(key) {
    const core = this.store.get({ key, active: false })
    await core.ready()
    const manifest = core.manifest
    await core.close()
    return manifest
  }

  getBootstrapSystemKey() {
    return Hypercore.key(this._createSystemManifest([], null, DEFAULT_MANIFEST_VERSION, null))
  }

  async findViewByKey(key, indexers, manifestVersion, entropy) {
    for (const v of this.byName.values()) {
      if (await v.matchesKey(key)) return v
    }

    const manifest = await this._getCoreManifest(key)
    const target = manifest && manifest.signers.length ? manifest.signers[0].namespace : null

    if (target) {
      for (const v of this.byName.values()) {
        if (await v.matchesNamespace(target)) return v
      }
    }

    const indexerManifests = await this.getIndexerManifests(indexers)

    if (entropy === null) {
      entropy = indexerManifests[0].signers[0].namespace
    }

    if (target) {
      for (const v of this.byName.values()) {
        const namespace = this._deriveNamespace(v.name, entropy)
        if (b4a.equals(namespace, target)) return v
      }
    }

    // prob the empty prologue, we dont have manifest for those in FF if len=0
    for (const v of this.byName.values()) {
      const manifestData = v.core ? v.core.manifest.userData : null
      const manifest = this._createManifest(
        indexerManifests,
        v.name,
        null,
        manifestVersion,
        entropy,
        [],
        manifestData
      )
      const manifestKey = Hypercore.key(manifest)

      if (!b4a.equals(manifestKey, key)) continue

      // we didnt have the core! this is because it is empty, ensure its on disk
      const core = this.store.get({ key: manifestKey, manifest, active: false })
      await core.ready()
      await core.close()

      return v
    }

    return null
  }

  async findViewByName(indexerManifests, views, name, entropy) {
    if (indexerManifests.length === 0) return null

    if (entropy === null) entropy = indexerManifests[0].signers[0].namespace
    const namespace = this._deriveNamespace(name, entropy)

    for (const v of views) {
      const manifest = await this._getCoreManifest(v.key)
      if (manifest.signers.length === 0) continue

      const signer = manifest.signers[0]

      if (b4a.equals(signer.namespace, namespace)) return v
    }

    return null
  }

  _createManifest(indexerManifests, name, prologue, version, entropy, linked, manifestData) {
    if (!indexerManifests.length) {
      prologue = {
        hash: this._deriveStaticHash(name),
        length: 0
      }
    } else if (prologue && prologue.length === 0) {
      // just in case
      prologue = null
    }

    const signers = []

    if (indexerManifests.length && entropy === null) {
      entropy = indexerManifests[0].signers[0].namespace
    }

    for (const manifest of indexerManifests) {
      const signer = manifest.signers[0]

      signers.push({
        namespace: this._deriveNamespace(name, entropy),
        signature: 'ed25519',
        publicKey: signer.publicKey
      })
    }

    return {
      version,
      hash: 'blake2b',
      prologue,
      allowPatch: true,
      quorum: Math.min(signers.length, (signers.length >> 1) + 1),
      signers,
      userData: manifestData,
      linked: version > 1 ? linked : null
    }
  }

  _createSystemManifest(indexerManifests, prologue, version, entropy, manifestData) {
    if (prologue !== null) throw new Error('Can only derive fresh system core')

    const linked = []

    if (version > DEFAULT_MANIFEST_VERSION) {
      const encManifest = this._createManifest(
        indexerManifests,
        '_encryption',
        null,
        version,
        entropy,
        [],
        manifestData
      )
      linked.push(Hypercore.key(encManifest))
    }

    return this._createManifest(
      indexerManifests,
      '_system',
      null,
      version,
      entropy,
      linked,
      manifestData
    )
  }
}

module.exports = AutoStore
const Hyperbee = require('hyperbee')
const SubEncoder = require('sub-encoder')
const ReadyResource = require('ready-resource')
const b4a = require('b4a')
const c = require('compact-encoding')
const crypto = require('hypercore-crypto')

const { Info, Member } = require('./messages.js')
const { DEFAULT_AUTOBASE_VERSION } = require('./caps.js')

const subs = new SubEncoder()

const DIGEST = subs.sub(b4a.from([0]))
const MEMBERS = subs.sub(b4a.from([1]))

const INFO_KEY = DIGEST.encode('info')

const [GENESIS_ENTROPY, NS_ENTROPY] = crypto.namespace('autobase/entropy', 2)

module.exports = class SystemView extends ReadyResource {
  constructor(core, { checkout = 0, empty = false } = {}) {
    super()

    this.core = core

    // sessions is a workaround for batches not having sessions atm...
    this.db = new Hyperbee(core, {
      keyEncoding: 'binary',
      extension: false,
      checkout,
      sessions: typeof core.session === 'function'
    })

    this.version = DEFAULT_AUTOBASE_VERSION
    this.members = 0
    this.pendingIndexers = []
    this.indexers = []
    this.heads = []
    this.views = []
    this.encryptionLength = 0
    this.entropy = null

    this.indexerUpdate = false

    this._empty = empty
    this._fork = 0
    this._length = 0
    this._changes = []
    this._indexerMap = new Map()
    this._clockUpdates = new Map()
  }

  static GENESIS_ENTROPY = GENESIS_ENTROPY

  static async getIndexedInfo(core, length) {
    const sys = new this(core.session())

    try {
      return await sys.getIndexedInfo(length)
    } finally {
      await sys.close()
    }
  }

  static async *flushes(core, { reverse, lt = core.length, gte = 0, wait = true } = {}) {
    if (lt <= 0) return

    // ensure block
    await core.get(lt - 1)
    const sys = new SystemView(core)

    try {
      for await (const data of sys.db.createHistoryStream({ lt, gte, wait, reverse: true })) {
        if (!b4a.equals(data.key, INFO_KEY)) continue
        const info = c.decode(Info, data.value)
        yield { length: data.seq + 1, info }
      }
    } finally {
      await sys.close()
    }
  }

  get bootstrapping() {
    return this.members === 0
  }

  async checkout(length) {
    const checkout = new SystemView(this.core.session(), {
      checkout: length,
      empty: length === 0
    })

    await checkout.ready()

    return checkout
  }

  async _open() {
    // this should NEVER fail, if so we have a bug elsewhere (should always be consistent)
    const info = this._empty
      ? null
      : await this.db.get('info', {
          valueEncoding: Info,
          keyEncoding: DIGEST,
          update: false,
          wait: false
        })
    await this._reset(info)
  }

  async _close() {
    await this.db.close()
  }

  async getIndexedInfo(length = this.core.signedLength) {
    if (this.opened === false) await this.ready()

    if (length === this.core.length) {
      return {
        version: this.version,
        members: this.members,
        pendingIndexers: this.pendingIndexers,
        indexers: this.indexers,
        heads: this.heads,
        views: this.views,
        encryptionLength: this.encryptionLength,
        entropy: this.entropy
      }
    }

    const node = length === 0 ? null : await this.db.getBySeq(length - 1)
    if (node === null) {
      return {
        version: DEFAULT_AUTOBASE_VERSION,
        members: 0,
        pendingIndexers: [],
        indexers: [],
        heads: [],
        views: [],
        encryptionLength: 0,
        entropy: null
      }
    }

    return c.decode(Info, node.value)
  }

  static sameIndexers(a, b) {
    if (a.length !== b.length) return false

    for (let i = 0; i < a.length; i++) {
      if (!b4a.equals(a[i].key, b[i].core.key)) return false
    }

    return true
  }

  sameIndexers(indexers) {
    return SystemView.sameIndexers(this.indexers, indexers)
  }

  async history(since) {
    const checkout = this.db.checkout(since)
    const seen = new Map()

    const nodes = []
    const updates = []

    const checkoutNode = since === 0 ? null : await this.db.getBySeq(since - 1)

    let prevInfo = checkoutNode === null ? null : c.decode(Info, checkoutNode.value)
    let updateBatch = 0

    for await (const data of this.db.createHistoryStream({ gte: since })) {
      if (b4a.equals(data.key, INFO_KEY)) {
        const info = c.decode(Info, data.value)

        updates.push({
          batch: updateBatch,
          indexers: prevInfo === null || !sameIndexers(info, prevInfo),
          systemLength: data.seq + 1
        })

        prevInfo = info
        updateBatch = 0
        continue
      }

      const key = data.key.subarray(2)
      const hex = b4a.toString(key, 'hex')
      const len = c.decode(Member, data.value).length

      if (!seen.has(hex)) {
        const node = await checkout.get(data.key)
        if (node === null) {
          seen.set(hex, 0)
        } else {
          const { length } = c.decode(Member, node.value)
          seen.set(hex, length)
        }
      }

      const prev = seen.get(hex)
      const batch = len - prev
      seen.set(hex, len)

      if (batch === 0) continue

      updateBatch += batch

      if (nodes.length > 0) {
        const top = nodes[nodes.length - 1]
        if (b4a.equals(top.key, key)) {
          top.length = len
          top.batch += batch
          continue
        }
      }

      nodes.push({
        key,
        length: len,
        batch
      })
    }

    await checkout.close()

    return { updates, nodes }
  }

  async update() {
    if (this.opened === false) await this.ready()

    if (this._fork === this.core.fork && this._length === this.core.length) return false

    await this._reset(
      this._empty ? null : await this.db.get('info', { valueEncoding: Info, keyEncoding: DIGEST })
    )
    return true
  }

  async _reset(info) {
    this.version = info === null ? DEFAULT_AUTOBASE_VERSION : info.value.version
    this.members = info === null ? 0 : info.value.members
    this.pendingIndexers = info === null ? [] : info.value.pendingIndexers
    this.indexers = info === null ? [] : info.value.indexers
    this.heads = info === null ? [] : info.value.heads
    this.views = info === null ? [] : info.value.views
    this.encryptionLength = info === null ? 0 : info.value.encryptionLength
    this.entropy = info === null ? null : info.value.entropy

    this.indexerUpdate = false
    this._indexerMap.clear()
    this._clockUpdates.clear()
    this._length = this.core.length
    this._fork = this.core.fork
    this._changes = []

    for (const idx of this.indexers) {
      this._indexerMap.set(b4a.toString(idx.key, 'hex'), idx)
    }
  }

  // helper used by apply-state
  flushLength() {
    const header = this.core.length ? 0 : 1 // account for header

    let updates = this._changes.length + this._clockUpdates.size
    for (const { key } of this._changes) {
      const hex = b4a.toString(key, 'hex')
      if (this._clockUpdates.has(hex)) updates-- // shared are skipped
    }

    return header + this.core.length + updates + 1
  }

  async flush(views) {
    const batch = this.db.batch({ update: false })

    for (let i = 0; i < this._changes.length; i++) {
      const c = this._changes[i]
      if (this._clockUpdates.has(b4a.toString(c.key, 'hex'))) continue
      await batch.put(c.key, c.value, { valueEncoding: Member, keyEncoding: MEMBERS })
    }

    for (const [hex, length] of this._clockUpdates) {
      const isIndexer = this._indexerMap.get(hex) !== undefined
      const key = b4a.from(hex, 'hex')

      const info = await this._get(key, 0)

      const value = { isIndexer, isRemoved: info ? info.isRemoved : true, length }
      await batch.put(key, value, { valueEncoding: Member, keyEncoding: MEMBERS })
    }

    if (this.indexerUpdate) {
      this._refreshEntropy(this.core.length + batch.length + 1)
    }

    this._clockUpdates.clear()

    let maxIndex = -1
    for (const view of views) {
      if (view.mappedIndex > maxIndex) maxIndex = view.mappedIndex
    }

    while (this.views.length > maxIndex + 1) this.views.pop()

    for (const view of views) {
      const length = view.core ? view.core.length : view.length
      if (!length) continue

      const v = { key: view.key, length }

      if (view.mappedIndex !== -1) {
        this.views[view.mappedIndex] = v
      } else {
        view.mappedIndex = this.views.push(v) - 1
      }
    }

    const info = {
      version: this.version,
      members: this.members,
      pendingIndexers: this.pendingIndexers,
      indexers: this.indexers,
      heads: this.heads,
      views: this.views,
      encryptionLength: this.encryptionLength,
      entropy: this.entropy
    }

    await batch.put('info', info, { valueEncoding: Info, keyEncoding: DIGEST })
    await batch.flush()

    this._length = this.core.length // should be ok
    this._changes = []

    if (this.indexerUpdate) this.indexerUpdate = false
  }

  getEntropy(indexers, length) {
    return generateEntropy(indexers, length, this.entropy)
  }

  _refreshEntropy(length) {
    if (this.version < 2) return
    this.entropy = this.getEntropy(this.indexers, length)
  }

  checkpoint() {
    return {
      version: this.version,
      members: this.members,
      pendingIndexers: this.pendingIndexers.slice(0),
      indexers: cloneNodes(this.indexers),
      heads: cloneNodes(this.heads),
      views: cloneNodes(this.views),
      indexerUpdate: this.indexerUpdate,
      encryptionLength: this.encryptionLength,
      entropy: this.entropy ? b4a.from(this.entropy) : null,
      changes: this._changes.slice(0),
      clockUpdates: new Map([...this._clockUpdates])
    }
  }

  applyCheckpoint(checkpoint) {
    this.version = checkpoint.version
    this.members = checkpoint.members
    this.pendingIndexers = checkpoint.pendingIndexers
    this.indexers = checkpoint.indexers
    this.heads = checkpoint.heads
    this.views = checkpoint.views
    this.indexerUpdate = checkpoint.indexerUpdate
    this.encryptionLength = checkpoint.encryptionLength
    this.entropy = checkpoint.entropy

    this._changes = checkpoint.changes
    this._clockUpdates = checkpoint.clockUpdates

    this._indexerMap = new Map()
    for (const idx of this.indexers) {
      this._indexerMap.set(b4a.toString(idx.key, 'hex'), idx)
    }
  }

  addHead(node) {
    const h = { key: node.writer.core.key, length: node.length }

    for (let i = 0; i < this.heads.length; i++) {
      const head = this.heads[i]

      if (!hasDependency(node, head)) {
        if (!b4a.equals(node.writer.core.key, head.key)) continue

        // todo: remove in next major because bug was fixed here:
        // https://github.com/holepunchto/autobase-next/pull/237

        // filter out any bad heads introduced by a bug to
        // prevent inconsistencies being written to the oplog
        if (head.length > h.length) return false
      }

      const popped = this.heads.pop()
      if (popped !== head) this.heads[i--] = popped
    }

    this.heads.push(h)

    const hex = b4a.toString(h.key, 'hex')

    this._clockUpdates.set(hex, h.length)

    if (this.pendingIndexers.length > 0) {
      for (let i = 0; i < this.pendingIndexers.length; i++) {
        if (!b4a.equals(this.pendingIndexers[i], h.key)) continue
        this._updateIndexer(h.key, h.length, true, i)
        return true
      }
    }

    const idx = this._indexerMap.get(hex)
    if (idx !== undefined) {
      idx.length = h.length
    }

    return false
  }

  _updateIndexer(key, length, isIndexer, i) {
    const hex = b4a.toString(key, 'hex')

    if (!isIndexer) {
      const existing = this._indexerMap.get(hex)
      if (existing) {
        this.indexerUpdate = true
        this.indexers.splice(this.indexers.indexOf(existing), 1)
        this._indexerMap.delete(hex)
      }
      return
    }

    for (; i < this.pendingIndexers.length; i++) {
      if (b4a.equals(this.pendingIndexers[i], key)) {
        break
      }
    }

    if (length === 0) {
      if (i >= this.pendingIndexers.length) this.pendingIndexers.push(key)
      return
    }

    if (i < this.pendingIndexers.length) {
      const top = this.pendingIndexers.pop()
      if (i < this.pendingIndexers.length) this.pendingIndexers[i] = top
    }

    const idx = this._indexerMap.get(hex)

    if (idx === undefined) {
      const newIdx = { key, length }
      this._indexerMap.set(hex, newIdx)
      this.indexers.push(newIdx)

      // bootstrap is "silently" added so that initial views have no prologue
      if (!this.bootstrapping) this.indexerUpdate = true
    } else {
      idx.length = length
    }
  }

  _seenLength(key) {
    return this._clockUpdates.get(b4a.toString(key, 'hex')) || 0
  }

  async ack(key) {
    const value = await this._get(key, 0)
    const length = this._seenLength(key)
    if (value && value.length === length) return

    const isIndexer = value ? value.isIndexer : false
    const isRemoved = value ? value.isRemoved : true

    this._changes.push({ key, value: { isIndexer, isRemoved, length } })
  }

  async add(key, { isIndexer = false, length = this._seenLength(key) } = {}) {
    let value = null
    let found = false
    let changed = true

    for (let i = this._changes.length - 1; i >= 0; i--) {
      const c = this._changes[i]
      if (b4a.equals(key, c.key)) {
        value = c.value
        found = true
        break
      }
    }

    if (!found) {
      const node = await this.db.get(key, { valueEncoding: Member, keyEncoding: MEMBERS })

      if (node) {
        value = node.value
        found = true
      }
    }

    let wasTracked = false
    let wasIndexer = false

    if (found) {
      if (!value.isRemoved) wasTracked = true
      if (value.isIndexer) wasIndexer = true
      if (length < value.length) length = value.length
      if (value.length === length && value.isIndexer === isIndexer && value.isRemoved === false)
        changed = false
    }

    if (changed) {
      this._changes.push({ key, value: { isIndexer, isRemoved: false, length } })
    }

    if (!wasTracked) this.members++
    if (wasIndexer || isIndexer) this._updateIndexer(key, length, isIndexer, 0)
  }

  async remove(key) {
    let isIndexer = false

    for (const idx of this.indexers) {
      isIndexer = b4a.equals(idx.key, key)
      if (isIndexer) break
    }

    if (isIndexer) this._updateIndexer(key, null, false, 0)

    let value = null
    let found = false

    for (let i = this._changes.length - 1; i >= 0; i--) {
      const c = this._changes[i]
      if (b4a.equals(key, c.key)) {
        value = c.value
        found = true
        break
      }
    }

    if (!found) {
      const node = await this.db.get(key, { valueEncoding: Member, keyEncoding: MEMBERS })
      if (node) {
        value = node.value
        found = true
      }
    }

    const changed = !found || !value.isRemoved
    const wasTracked = found && !value.isRemoved
    const length = found ? value.length : 0

    if (changed) {
      this._changes.push({ key, value: { isIndexer: false, isRemoved: true, length } })
    }

    if (wasTracked) this.members--

    return isIndexer
  }

  async linkable(key, length) {
    const len = this._seenLength(key)
    if (len > 0) return length > len

    const info = await this._get(key, 0)
    const prevLength = info ? info.length : 0

    return length > prevLength
  }

  async has(key, opts) {
    // could be optimised...
    return (await this.get(key, opts)) !== null
  }

  async _get(key, timeout) {
    let value = null
    let found = false

    for (let i = this._changes.length - 1; i >= 0; i--) {
      const c = this._changes[i]
      if (b4a.equals(key, c.key)) {
        value = c.value
        found = true
        break
      }
    }

    if (!found) {
      const node = await this.db.get(key, { timeout, valueEncoding: Member, keyEncoding: MEMBERS })
      if (node) {
        value = node.value
        found = true
      }
    }

    return found ? value : null
  }

  async get(key, opts = {}) {
    if (this._empty) return null

    let value = await this._get(key, opts.timeout || 0)

    if (opts.unflushed) {
      const hex = b4a.toString(key, 'hex')

      for (let i = this._changes.length - 1; i >= 0; i--) {
        const c = this._changes[i]
        if (b4a.equals(c.key, key)) value = c.value
      }

      if (this._clockUpdates.has(hex)) value.length = this._clockUpdates.get(hex)
    }

    return opts.onlyActive !== false || !value.isRemoved ? value : null
  }

  async hasLocal(key) {
    if (this._empty) return false
    try {
      const node = await this.db.get(key, {
        valueEncoding: Member,
        keyEncoding: MEMBERS,
        update: false,
        wait: false
      })
      return node !== null
    } catch {
      return false
    }
  }

  async getLocalLength(key) {
    if (this._empty) return 0
    try {
      const node = await this.db.get(key, {
        valueEncoding: Member,
        keyEncoding: MEMBERS,
        update: false,
        wait: false
      })
      return node === null ? 0 : node.value.length
    } catch {
      return 0
    }
  }

  list() {
    // note, NOT safe to use during apply atm
    return this.db.createReadStream({
      valueEncoding: Member,
      keyEncoding: MEMBERS
    })
  }

  async isIndexed(key, length) {
    const co = this.db.checkout(this.core.indexedLength)
    try {
      const node = await co.get(key, { valueEncoding: Member, keyEncoding: MEMBERS })
      return node !== null && node.value.length >= length
    } finally {
      await co.close()
    }
  }

  async fork(indexers, manifests, encryptionLength, views) {
    this.indexers = []
    this.pendingIndexers = []
    this.views = views

    if (this.version < 2) this.version = 2 // bump to enable entropy

    for (const { key } of indexers) {
      // todo: probably need to check isRemoved etc
      const { length } = await this.get(key)
      this.indexers.push({ key, length })
    }

    this._refreshEntropy(this.core.length)

    const blocks = []

    // truncate needs to respect hypercore batch
    let truncate = this.core.length - 1
    for await (const data of this.db.createHistoryStream({ lt: truncate, reverse: true })) {
      if (b4a.equals(data.key, INFO_KEY)) {
        truncate = data.seq + 1
        break
      }

      blocks.push(data)
    }

    await this.core.truncate(truncate, this.core.fork)

    const batch = this.db.batch({ update: false })
    await batch.ready()

    for (const { key, value } of blocks) {
      await batch.put(key, value)
    }

    const info = {
      version: this.version,
      members: this.members,
      pendingIndexers: this.pendingIndexers,
      indexers: this.indexers,
      heads: this.heads,
      views: this.views,
      encryptionLength,
      entropy: this.entropy
    }

    await batch.put('info', info, { valueEncoding: Info, keyEncoding: DIGEST })
    await batch.flush()

    await this._reset(await this.db.get('info', { valueEncoding: Info, keyEncoding: DIGEST }))

    return truncate
  }
}

function hasDependency(node, dep) {
  for (const h of node.actualHeads) {
    if (sameNode(h, dep)) return true
  }
  return false
}

function sameNode(a, b) {
  return b4a.equals(a.key, b.key) && a.length === b.length
}

function sameIndexers(a, b) {
  if (a.views.length > 0 && b.views.length > 0) return b4a.equals(a.views[0].key, b.views[0].key)
  if (a.indexers.length !== b.indexers.length) return false

  for (let i = 0; i < a.indexers.length; i++) {
    if (!b4a.equals(a.indexers[i].key, b.indexers[i].key)) return false
  }

  return true
}

function cloneNodes(arr) {
  const c = []
  for (let i = 0; i < arr.length; i++) {
    c.push({ key: arr[i].key, length: arr[i].length })
  }
  return c
}

function generateEntropy(indexers, length, entropy) {
  const buffer = b4a.alloc(8 + 32 + indexers.length * 32)
  const state = { start: 0, end: buffer.byteLength, buffer }

  c.uint64.encode(state, length)
  c.fixed32.encode(state, entropy || GENESIS_ENTROPY)
  for (const { key } of indexers) c.fixed32.encode(state, key)

  return crypto.hash([NS_ENTROPY, buffer])
}
const safetyCatch = require('safety-catch')

const MAX_WAIT = 2 * 60 * 1000
const DEFAULT_INTERVAL = 10 * 1000

module.exports = class Timer {
  constructor(handler, interval, opts = {}) {
    this.handler = handler || noop
    this.interval = interval || DEFAULT_INTERVAL
    this.limit = opts.limit || MAX_WAIT

    this._executing = null

    this._limit = random2over1(this.limit)
    this._timer = null
    this._resolve = null
    this._start = 0
    this._stopped = false
    this._asap = false
    this._standalone = new Set()

    this._unref = opts.unref !== false
    this._timerCallback = this._executeBackground.bind(this)
  }

  _executeBackground() {
    this._executing = this._execute()
    this._executing.catch(safetyCatch) // make sure it doesnt crash in the bg
  }

  async _execute() {
    this._asap = false
    await this.handler()
    this._start = 0
    this._executing = null
    this.bump()
  }

  bump() {
    if (this._stopped || this._executing || this._asap) return

    if (!this._start) this._start = Date.now()
    else if (Date.now() - this._start > this._limit) return

    const interval = random2over1(this.interval)

    clearTimeout(this._timer)
    this._timer = setTimeout(this._timerCallback, interval)
    if (this._unref && this._timer.unref) this._timer.unref()
  }

  async trigger() {
    if (this._stopped) return
    if (this._executing) await this._executing
    if (this._stopped) return

    clearTimeout(this._timer)
    this._timer = null

    this._executeBackground()
    await this._executing
  }

  async flush() {
    if (this._executing) await this._executing
  }

  // business-as-usual
  bau() {
    if (!this._asap) return
    this._asap = false
    this.bump()
  }

  asap() {
    if (this._asap) return
    this._asap = true

    const interval = Math.floor((Math.random() * this.interval) / 3)
    clearTimeout(this._timer)
    this._timer = setTimeout(this._timerCallback, interval)
    if (this._unref && this._timer.unref) this._timer.unref()
  }

  stop() {
    if (this._timer) clearTimeout(this._timer)
    this._timer = null
    this._start = 0
    this._asap = false
    this._stopped = true

    for (const { timer, resolve } of this._standalone) {
      clearTimeout(timer)
      resolve()
    }

    this._standalone.clear()
  }

  asapStandalone() {
    const interval = Math.floor((Math.random() * this.interval) / 3)
    return new Promise((resolve) => {
      const ref = { timer: null, resolve }
      ref.timer = setTimeout(resolveStandalone, interval, ref, this._standalone)
      if (ref.timer.unref) ref.timer.unref()
      this._standalone.add(ref)
    })
  }

  unref() {
    if (this._timer && this._timer.unref) this._timer.unref()
  }
}

function resolveStandalone(ref, set) {
  set.delete(ref)
  ref.resolve()
}

// random value x between n <= x < 2n
function random2over1(n) {
  return Math.floor(n + Math.random() * n)
}

function noop() {}
const b4a = require('b4a')
const assert = require('nanoassert')

module.exports = class TopoList {
  constructor() {
    this.tip = []
    this.undo = 0
    this.shared = 0
  }

  static compare(a, b) {
    return cmp(a, b)
  }

  static add(node, indexed, offset) {
    addSorted(node, indexed, offset)
  }

  mark() {
    this.shared = this.tip.length
    this.undo = 0
  }

  // todo: bump to new api that just tracks undo
  flush(indexed = []) {
    if (indexed.length) this._applyIndexed(indexed)

    const u = {
      shared: this.shared,
      undo: this.undo,
      length: indexed.length + this.tip.length,
      indexed,
      tip: this.tip
    }

    this.mark()

    return u
  }

  print() {
    return this.tip.map((n) => n.writer.core.key.toString() + n.length)
  }

  _applyIndexed(nodes) {
    assert(nodes.length <= this.tip.length, 'Indexed batch cannot exceed tip')

    let shared = 0

    for (; shared < nodes.length; shared++) {
      if (this.tip[shared] !== nodes[shared]) break
    }

    // reordering
    if (shared < nodes.length) this._track(shared)

    const tip = []

    for (let i = shared; i < this.tip.length; i++) {
      const node = this.tip[i]
      if (node.yielded) continue
      const s = addSorted(node, tip, 0)
      if (s === tip.length - 1) continue
      this._track(shared + s)
    }

    this.tip = tip
  }

  add(node) {
    const shared = addSorted(node, this.tip, 0)
    this._track(shared)
  }

  _track(shared) {
    if (shared < this.shared) {
      this.undo += this.shared - shared
      this.shared = shared
    }
  }
}

function hasOptimisticDeps(node) {
  for (const d of node.dependencies) {
    if (d.optimistic) return true
  }
  return false
}

// this can be optimised to only return optimistic nodes that we will "touch" when moving down the node
function getOptimisticDeps(node, list) {
  const deps = new Set()
  const stack = [node]

  while (stack.length > 0) {
    const next = stack.pop()
    if (deps.has(next)) continue
    if (next.optimistic && next !== node) deps.add(next)
    for (const d of next.dependencies) {
      if (d.optimistic) stack.push(d)
    }
  }

  const result = []

  for (let i = list.length - 1; deps.size !== result.length && i >= 0; i--) {
    const n = list[i]
    if (deps.has(n)) result.push(n)
  }

  return result.reverse()
}

function addSortedOptimistic(node, list, offset) {
  const opt = getOptimisticDeps(node, list)
  const pos = new Uint32Array(opt.length)

  for (let i = 0; i < opt.length; i++) {
    const n = opt[i]
    pos[i] = n.index
    moveDown(n, list, offset)
  }

  moveDownAndUp(node, list, offset)

  for (let i = opt.length - 1; i >= 0; i--) {
    moveOptimisticUp(opt[i], list, offset)
  }

  let shared = node.index

  for (let i = 0; i < opt.length; i++) {
    const idx = pos[i]
    const n = opt[i]

    if (idx === n.index) continue

    if (idx < shared) shared = idx
    if (n.index < shared) shared = n.index
  }

  return shared
}

function addSorted(node, list, offset) {
  list.push(node)
  node.index = list.length - 1

  // "slow" path, 1% of nodes
  if (hasOptimisticDeps(node)) return addSortedOptimistic(node, list, offset)

  moveDownAndUp(node, list, offset)
  return node.index
}

function moveDown(node, list, offset) {
  while (node.index > offset) {
    const prev = list[node.index - 1]
    if (links(node, prev)) break
    list[(prev.index = node.index)] = prev
    list[--node.index] = node
  }
}

function moveOptimisticUp(node, list, offset) {
  // if optimistic move all the way up
  while (node.index < list.length - 1) {
    const next = list[node.index + 1]
    if (links(next, node)) break
    list[(next.index = node.index)] = next
    list[++node.index] = node
  }

  // stable sort in case multiple children
  while (node.index > offset) {
    const prev = list[node.index - 1]
    if (!prev.optimistic) break
    const c = cmp(prev, node)
    if (c <= 0) break
    list[(prev.index = node.index)] = prev
    list[--node.index] = node
  }
}

function moveNonOptimisticUp(node, list, offset) {
  // stable sort against next non optimistic node
  while (node.index < list.length - 1) {
    const next = list[node.index + 1]
    const c = cmpNonOptimistic(node, next, list)
    if (c <= 0) break
    list[(next.index = node.index)] = next
    list[++node.index] = node
  }
}

function moveDownAndUp(node, list, offset) {
  moveDown(node, list, offset)

  if (node.optimistic) moveOptimisticUp(node, list, offset)
  else moveNonOptimisticUp(node, list, offset)
}

function links(a, b) {
  if (b.dependents.has(a)) return true
  return a.length > 0 && b.length === a.length - 1 && a.writer === b.writer
}

function cmpNonOptimistic(a, b, list) {
  if (!b.optimistic) return cmp(a, b)

  for (let i = b.index + 1; i < list.length; i++) {
    const node = list[i]
    if (!node.optimistic) return cmp(a, node)
  }

  return -1
}

function cmp(a, b) {
  return links(b, a) ? -1 : cmpUnlinked(a, b)
}

function cmpUnlinked(a, b) {
  const c = b4a.compare(a.writer.core.key, b.writer.core.key)
  return c === 0 ? (a.length < b.length ? -1 : 1) : c
}
module.exports = class UpdateChanges {
  constructor(base) {
    this.base = base
    this.byName = new Map()
    this.tracking = null
  }

  get discoveryKey() {
    return this.base.discoveryKey
  }

  get key() {
    return this.base.key
  }

  get id() {
    return this.base.id
  }

  get system() {
    return this.base.system
  }

  track(state) {
    this.tracking = []
    this.byName.clear()

    if (!state) return

    for (const v of state.views) {
      if (v.ref) this._add(v.ref)
    }

    this._add(state.systemView.ref)
    this._add(state.encryptionView.ref)
  }

  _add(ref) {
    const core = ref.atomicBatch || ref.batch
    const from = core.length
    const inf = { ref, from }
    ref.shared = from
    this.tracking.push(inf)
  }

  finalise() {
    if (this.tracking === null) return

    for (const { ref, from } of this.tracking) {
      const core = ref.atomicBatch || ref.batch
      this.byName.set(ref.name, {
        from,
        to: core.length,
        shared: ref.shared
      })
    }

    this.tracking = null
  }

  get(name) {
    return this.byName.get(name)
  }
}
const crypto = require('hypercore-crypto')
const c = require('compact-encoding')
const b4a = require('b4a')

const { OPLOG_VERSION } = require('./caps.js')
const { OplogMessage } = require('./messages.js')
const { EncryptionView } = require('./encryption.js')

module.exports = {
  encodeValue,
  decodeValue
}

function encodeValue(value, opts = {}) {
  const state = { start: 0, end: 0, buffer: null }

  const message = {
    version: opts.version || OPLOG_VERSION,
    digest: null,
    checkpoint: null,
    optimistic: !!opts.optimistic,
    node: {
      heads: opts.heads || [],
      batch: 1,
      value
    }
  }

  OplogMessage.preencode(state, message)

  if (opts.padding) {
    state.start = opts.padding
    state.end += opts.padding
  }

  state.buffer = b4a.alloc(state.end)

  OplogMessage.encode(state, message)

  if (!opts.encrypted) return state.buffer

  if (!opts.optimistic) {
    throw new Error('Encoding an encrypted value is not supported')
  }

  const padding = b4a.alloc(16) // min hash length is 16
  crypto.hash(state.buffer, padding)
  padding[0] = 0

  return b4a.concat([padding.subarray(0, 8), state.buffer])
}

async function decodeValue(value, { autobase, encryptionKey, key, manifest, index = 0 } = {}) {
  if (encryptionKey) {
    const e = new EncryptionView({ encryptionKey, bootstrap: autobase })
    const w = e.getWriterEncryption()

    await w.decrypt(index, value, { key, manifest })
    value = value.subarray(8)
  }

  const op = c.decode(OplogMessage, value)
  return op.node.value
}
const b4a = require('b4a')
const c = require('compact-encoding')
const safetyCatch = require('safety-catch')
const ReadyResource = require('ready-resource')

const WakeupEntry = {
  preencode(state, m) {
    c.fixed32.preencode(state, m.key)
    c.uint.preencode(state, m.length)
  },
  encode(state, m) {
    c.fixed32.encode(state, m.key)
    c.uint.encode(state, m.length)
  },
  decode(state) {
    return {
      key: c.fixed32.decode(state),
      length: c.uint.decode(state)
    }
  }
}

module.exports = class AutoWakeup extends ReadyResource {
  constructor(base) {
    super()

    this.base = base
    this.flushing = null

    this._rootStore = this.base.store
    this._addBound = this.add.bind(this)
    this._preupdateBound = this._preupdate.bind(this)
    this._needsFlush = false
    this._map = new Map()
  }

  [Symbol.iterator]() {
    return this._map.values()
  }

  async _preupdate(batch, key) {
    this.queue(key, batch.length)
    await this.flush()
    this.base._onwakeup()
  }

  async _save() {
    const slab = b4a.allocUnsafe(8 + this._map.size * 40) // 32 + 8
    const state = { start: 0, end: 0, buffer: slab }

    c.uint.encode(state, this._map.size)
    for (const m of this._map.values()) WakeupEntry.encode(state, m)

    await this.base.local.setUserData('autobase/wakeup', slab.subarray(0, state.start))
  }

  async _load() {
    const buffer = await this.base.local.getUserData('autobase/wakeup')
    if (!buffer) return

    const state = { start: 0, end: buffer.byteLength, buffer }

    let len = c.uint.decode(state)
    while (len-- > 0) {
      const m = WakeupEntry.decode(state)
      this._map.set(b4a.toString(m.key, 'hex'), m)
    }
  }

  async _open() {
    await this._load()

    this._rootStore.watch(this._addBound)

    for (const core of this._rootStore.cores) {
      if (core.opened === false) await core.ready().catch(noop)
      if (!core.closing) this.add(core)
    }
  }

  async _close() {
    this._rootStore.unwatch(this._addBound)

    for (const core of this._rootStore.cores) {
      if (core.opened && !core.closing) this.remove(core)
    }

    this._map.clear()

    while (this.flushing) {
      try {
        await this.flushing
      } catch {}
    }
  }

  queue(key, length) {
    const hex = b4a.toString(key, 'hex')
    const m = this._map.get(hex)

    if (m && m.length > length) {
      return false
    }

    this._needsFlush = true
    this._map.set(hex, { key, length })

    return true
  }

  unqueue(key, length) {
    const hex = b4a.toString(key, 'hex')
    const m = this._map.get(hex)

    if (!m) return true
    if (m.length > length) return false

    this._needsFlush = true
    this._map.delete(hex)

    return true
  }

  async flush() {
    if (this.closing) throw new Error('Closing')

    // wait for someone
    if (this.flushing) await this.flushing

    // if another still active they flushed us
    if (this.flushing) return this.flushing

    if (this._needsFlush === false) return
    this._needsFlush = false

    try {
      this.flushing = this._save()
      return await this.flushing
    } finally {
      this.flushing = null
    }
  }

  add(core) {
    return this._add(core).catch(safetyCatch)
  }

  remove(core) {
    return this._remove(core)
  }

  async _add(core) {
    if (core.opened === false) await core.ready()
    if (core.closing) return false

    // local writer, no need
    if (core === this.base.local.core) return false

    const rx = core.storage.read()

    const referrerPromise = rx.getUserData('referrer')
    const viewPromise = rx.getUserData('autobase/view')

    rx.tryFlush()

    const [referrer, view] = await Promise.all([referrerPromise, viewPromise])
    if (view) return false

    if (referrer === null || !b4a.equals(referrer, this.base.key)) return false

    core.preupdate = this._preupdateBound

    return true
  }

  _remove(core) {
    if (core.preupdate !== this._preupdateBound) return false
    core.preupdate = null
    return true
  }
}

function noop() {}
const c = require('compact-encoding')
const b4a = require('b4a')
const ReadyResource = require('ready-resource')
const assert = require('nanoassert')
const SignalPromise = require('signal-promise')

const Linearizer = require('./linearizer.js')
const NodeBuffer = require('./node-buffer.js')

const MAX_PRELOAD = 4
const MAX_PREFETCH = 256

module.exports = class Writer extends ReadyResource {
  constructor(base, core, length, isRemoved) {
    super()

    this.base = base
    this.core = core
    this.isRemoved = isRemoved
    this.updated = false
    this.range = null
    this.nodes = new NodeBuffer(length)
    this.node = null
    this.isActive = false
    this.isCurrentlyCoupled = false
    this.isCoupled = false // maintained by updateBootstrapWriters
    this.isBootstrap = false // maintained by updateBootstrapWriters
    this.isActiveIndexer = false
    this.available = length
    this.length = length
    this.seenLength = 0
    this.recover = false
    this.frozen = false

    this.syncSignal = null
  }

  _pause() {
    if (!this.range) return
    this.range.destroy()
    this.range = null
  }

  _resume() {
    if (this.range) {
      // if we didnt fill up the preload buffer yet, do not commit to more work
      if (this.range.range.end === this.nodes.length + MAX_PREFETCH) return
      this.range.destroy()
    }
    this.range = this.core.download({
      start: this.nodes.length,
      end: this.nodes.length + MAX_PREFETCH,
      linear: true
    })
  }

  _updateCoupling() {
    if (!this.base._coupler || this.isCoupled === this.isCurrentlyCoupled) return

    if (this.isCurrentlyCoupled) this.base._coupler.remove(this.core)
    else this.base._coupler.add(this.core)

    this.isCurrentlyCoupled = this.isCoupled
  }

  _removeCouple() {
    if (this.base._coupler && !this.isCoupled && this.isCurrentlyCoupled) {
      this.isCurrentlyCoupled = false
      this.base._coupler.remove(this.core)
    }
  }

  updateActivity() {
    if (!this.core.opened) return

    if (this.seenLength > this.core.length || this.length < this.core.length || this.isBootstrap) {
      // if we have seen a later core, or if we are behind, or if bootstrap
      this.isActive = true
      this.core.setActive(true)
    } else if (this.length === this.core.length) {
      // things look steady
      this.isActive = false
      this.core.setActive(false)
    }

    this._updateCoupling()

    if (this.core.writable) return

    if (this.base.isFastForwarding() || !this.isActive) {
      this._pause()
    } else {
      this._resume()
    }
  }

  setBootstrap(bool) {
    this.isBootstrap = bool
    this.updateActivity()
  }

  seen(length) {
    if (length > this.seenLength) this.seenLength = length
    this.updateActivity()
  }

  waitForSynced() {
    if (this.core.length === this.length) return Promise.resolve()
    if (this.syncSignal === null) this.syncSignal = new SignalPromise()
    return this.syncSignal.wait()
  }

  async _open() {
    await this.core.ready()
    await this.core.setUserData('referrer', this.base.key)

    // base might be closing here
    if (this.base.closing) return

    // remove later
    this.recover = autoRecover(this.core)

    // add it again incase it wasn't readied before, only needed if this is the first time we set the referrer...
    await this.base._wakeup.add(this.core.core)

    this.updateActivity()

    if (this.core.length > this.length) this.base._queueBump()

    this.base.emit('writer', this)
  }

  _close() {
    if (this.syncSignal !== null) this.syncSignal.notify()
    return this.core.close()
  }

  get indexed() {
    return this.nodes.offset
  }

  idle() {
    return this.length === this.available && this.length === this.core.length && this.core.opened
  }

  flushed() {
    // TODO: prop a cleaner way to express this...
    return (
      this.seenLength <= this.length &&
      this.length === this.available &&
      this.length === this.core.length &&
      this.shiftable() === false &&
      !this.core.core.upgrading &&
      this.core.opened
    )
  }

  compare(writer) {
    return b4a.compare(this.core.key, writer.core.key)
  }

  head() {
    return this.nodes.get(this.length - 1)
  }

  advance() {
    if (this.syncSignal !== null && this.length + 1 === this.core.length) this.syncSignal.notify()
    return this.length < this.available ? this.nodes.get(this.length++) : null
  }

  shiftable() {
    return this.length > this.nodes.offset
  }

  shift() {
    if (this.shiftable() === false) return false

    let node = this._shiftAndClear()
    while (node.batch > 1) node = this._shiftAndClear()

    return true
  }

  get(seq) {
    return seq < this.length ? this.nodes.get(seq) : null
  }

  append(value, heads, batch, dependencies, optimistic) {
    const node = Linearizer.createNode(
      this,
      this.nodes.length + 1,
      value,
      heads,
      batch,
      dependencies,
      optimistic
    )

    node.actualHeads = node.heads.slice(0)

    this.nodes.push(node)
    this.available++
    this.length++

    return node
  }

  async update(boot) {
    if (this.opened === false) await this.ready()
    if (this.frozen) return false

    // if this is a boot node, DO NOT, preload as that will make it load
    // and reject nodes based on the tmp state of the system.
    // prop needs a better solution but this works for now
    const preload = boot ? 1 : MAX_PRELOAD

    while (this.available - this.length < preload) {
      // quick sanity check
      if (this.nodes.length === this.core.length || this.core.length === 0) break

      // load next node
      if (this.node === null && !(await this._loadNextNode())) break
      if (!(await this._ensureNodeDependencies(boot))) break

      if (this.recover) this.node.value = null

      this.nodes.push(this.node)
      if (this.node.batch === 1) this.available = this.nodes.length
      this.node = null
    }

    this.updateActivity()

    return this.length < this.available
  }

  _shiftAndClear() {
    const node = this.nodes.shift()
    node.clear()
    return node
  }

  async _loadNextNode() {
    const seq = this.nodes.length

    if (!(await this.core.has(seq))) return false

    try {
      const { node, optimistic, trace } = await this.core.get(seq, { wait: false })

      const value = node.value == null ? null : c.decode(this.base.valueEncoding, node.value)
      this.node = Linearizer.createNode(
        this,
        seq + 1,
        value,
        node.heads,
        node.batch,
        new Set(),
        optimistic,
        trace
      )
      return true
    } catch (err) {
      this.frozen = true
      throw err
    }
  }

  async _ensureNodeDependencies(boot) {
    while (this.node.dependencies.size < this.node.heads.length) {
      const rawHead = this.node.heads[this.node.dependencies.size]

      const headWriter = await this.base._getWriterByKey(
        rawHead.key,
        -1,
        rawHead.length,
        true,
        false,
        boot
      )

      if (headWriter !== this && (headWriter === null || headWriter.length < rawHead.length)) {
        if (!boot) this.base._ensureWakeup(headWriter)
        return false
      }

      let headNode = headWriter.nodes.get(rawHead.length - 1)

      // could be a stub node
      if (!headNode) {
        for (const node of this.base.linearizer.heads) {
          if (!compareHead(node, rawHead)) continue
          headNode = node
          break
        }
      }

      // TODO: generalise DAG validation and freeze the writer
      assert(!this.node.dependencies.has(headNode), 'Corrupted DAG')

      // TODO: better way to solve the stub check is to never mutate heads below
      if (headNode === null) {
        // already yielded
        this.node.heads.splice(this.node.dependencies.size, 1)
        continue
      }

      this.node.dependencies.add(headNode)
    }

    // always link previous node if it's not indexed
    const offset = this.node.length - 1
    if (offset > this.indexed) {
      this.node.dependencies.add(this.nodes.get(offset - 1))
    }

    return true
  }
}

function compareHead(node, head) {
  if (node.length !== head.length) return false
  return b4a.equals(node.writer.core.key, head.key)
}

// this is a list of peers we bugged in the btc and planb room.
// adding them here so migration can run, can be removed in a month or so from time of commit
// note, no security implications of this, we just null them out.

function autoRecover(core) {
  assert(core.opened)

  switch (core.id) {
    case 'ghrpexaboutdm46ombqho7mroxknassnntrxx3cubfux4qi6w6hy':
    case 'qoaanao71s4he1rcd197d336qepykk4467geo1uq8cwnzmpb786o':
    case 'fomhdxgn4j4tzjqy6y7iskhffimzokt7kraddyd8orcht3r8q61o':
    case 'd8f5taxxrit51apftoi38e5b86hb98cgfd7dfp3uo1uoh95qt49o':
    case 'objyf75uggsqpjcut69xdgj46ks8r71jjrq7oxdfsz95sstchkno':
      return true
  }

  return false
}
{
  "name": "autobase",
  "version": "7.28.1",
  "description": "A multiwriter data structure for Hypercore",
  "main": "index.js",
  "scripts": {
    "format": "prettier . --write",
    "test": "prettier . --check && brittle test/all.js",
    "test:encrypted": "prettier . --check && brittle test/all.js --encrypt-all",
    "test:fixtures": "prettier . --check && brittle test/fixtures/tests/*.js",
    "test:bare": "bare test/all.js",
    "fuzz:generated": "brittle test/reference/fuzz/generated/*.js",
    "fuzz:main": "node test/fuzz/index.js",
    "fuzz": "node test/reference/fuzz/fuzz.js",
    "generate-fixtures": "node test/fixtures/generate/all.js"
  },
  "files": [
    "index.js",
    "lib/**",
    "encoding/**"
  ],
  "imports": {
    "events": {
      "bare": "bare-events",
      "default": "events"
    }
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/autobase.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/autobase/issues"
  },
  "homepage": "https://github.com/holepunchto/autobase#readme",
  "dependencies": {
    "b4a": "^1.6.1",
    "bare-events": "^2.2.0",
    "compact-encoding": "^3.0.0",
    "core-coupler": "^2.0.0",
    "debounceify": "^1.0.0",
    "encryption-encoding": "^1.0.3",
    "hyperbee": "^2.22.0",
    "hypercore": "^11.27.12",
    "hypercore-crypto": "^3.4.0",
    "hypercore-id-encoding": "^1.2.0",
    "hyperschema": "^1.12.1",
    "index-encoder": "^3.3.2",
    "nanoassert": "^2.0.0",
    "protomux-wakeup": "^2.0.0",
    "ready-resource": "^1.0.0",
    "resolve-reject-promise": "^1.1.0",
    "safety-catch": "^1.0.2",
    "scope-lock": "^1.2.4",
    "signal-promise": "^1.0.3",
    "sodium-universal": "^5.0.1",
    "sub-encoder": "^2.1.1",
    "tiny-buffer-map": "^1.1.1"
  },
  "devDependencies": {
    "autobase-test-helpers": "^3.0.0",
    "blind-encryption-sodium": "^2.0.0",
    "brittle": "^3.1.1",
    "corestore": "^7.9.1",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^2.0.0",
    "rache": "^1.0.0",
    "same-data": "^1.0.0",
    "task-backoff": "^1.0.0",
    "test-tmp": "^1.2.0",
    "uncaughts": "^1.1.0"
  },
  "standard": {
    "ignore": [
      "**/test/fuzz/generated/**",
      "**/test/reference/**",
      "example.mjs"
    ]
  }
}
function isBuffer(value) {
  return Buffer.isBuffer(value) || value instanceof Uint8Array
}

function isEncoding(encoding) {
  return Buffer.isEncoding(encoding)
}

function alloc(size, fill, encoding) {
  return Buffer.alloc(size, fill, encoding)
}

function allocUnsafe(size) {
  return Buffer.allocUnsafe(size)
}

function allocUnsafeSlow(size) {
  return Buffer.allocUnsafeSlow(size)
}

function byteLength(string, encoding) {
  return Buffer.byteLength(string, encoding)
}

function compare(a, b) {
  return Buffer.compare(a, b)
}

function concat(buffers, totalLength) {
  return Buffer.concat(buffers, totalLength)
}

function copy(source, target, targetStart, start, end) {
  return toBuffer(source).copy(target, targetStart, start, end)
}

function equals(a, b) {
  return toBuffer(a).equals(b)
}

function fill(buffer, value, offset, end, encoding) {
  return toBuffer(buffer).fill(value, offset, end, encoding)
}

function from(value, encodingOrOffset, length) {
  return Buffer.from(value, encodingOrOffset, length)
}

function includes(buffer, value, byteOffset, encoding) {
  return toBuffer(buffer).includes(value, byteOffset, encoding)
}

function indexOf(buffer, value, byfeOffset, encoding) {
  return toBuffer(buffer).indexOf(value, byfeOffset, encoding)
}

function lastIndexOf(buffer, value, byteOffset, encoding) {
  return toBuffer(buffer).lastIndexOf(value, byteOffset, encoding)
}

function swap16(buffer) {
  return toBuffer(buffer).swap16()
}

function swap32(buffer) {
  return toBuffer(buffer).swap32()
}

function swap64(buffer) {
  return toBuffer(buffer).swap64()
}

function toBuffer(buffer) {
  if (Buffer.isBuffer(buffer)) return buffer
  return Buffer.from(buffer.buffer, buffer.byteOffset, buffer.byteLength)
}

function toString(buffer, encoding, start, end) {
  return toBuffer(buffer).toString(encoding, start, end)
}

function write(buffer, string, offset, length, encoding) {
  return toBuffer(buffer).write(string, offset, length, encoding)
}

function readDoubleBE(buffer, offset) {
  return toBuffer(buffer).readDoubleBE(offset)
}

function readDoubleLE(buffer, offset) {
  return toBuffer(buffer).readDoubleLE(offset)
}

function readFloatBE(buffer, offset) {
  return toBuffer(buffer).readFloatBE(offset)
}

function readFloatLE(buffer, offset) {
  return toBuffer(buffer).readFloatLE(offset)
}

function readInt32BE(buffer, offset) {
  return toBuffer(buffer).readInt32BE(offset)
}

function readInt32LE(buffer, offset) {
  return toBuffer(buffer).readInt32LE(offset)
}

function readUInt32BE(buffer, offset) {
  return toBuffer(buffer).readUInt32BE(offset)
}

function readUInt32LE(buffer, offset) {
  return toBuffer(buffer).readUInt32LE(offset)
}

function writeDoubleBE(buffer, value, offset) {
  return toBuffer(buffer).writeDoubleBE(value, offset)
}

function writeDoubleLE(buffer, value, offset) {
  return toBuffer(buffer).writeDoubleLE(value, offset)
}

function writeFloatBE(buffer, value, offset) {
  return toBuffer(buffer).writeFloatBE(value, offset)
}

function writeFloatLE(buffer, value, offset) {
  return toBuffer(buffer).writeFloatLE(value, offset)
}

function writeInt32BE(buffer, value, offset) {
  return toBuffer(buffer).writeInt32BE(value, offset)
}

function writeInt32LE(buffer, value, offset) {
  return toBuffer(buffer).writeInt32LE(value, offset)
}

function writeUInt32BE(buffer, value, offset) {
  return toBuffer(buffer).writeUInt32BE(value, offset)
}

function writeUInt32LE(buffer, value, offset) {
  return toBuffer(buffer).writeUInt32LE(value, offset)
}

module.exports = {
  isBuffer,
  isEncoding,
  alloc,
  allocUnsafe,
  allocUnsafeSlow,
  byteLength,
  compare,
  concat,
  copy,
  equals,
  fill,
  from,
  includes,
  indexOf,
  lastIndexOf,
  swap16,
  swap32,
  swap64,
  toBuffer,
  toString,
  write,
  readDoubleBE,
  readDoubleLE,
  readFloatBE,
  readFloatLE,
  readInt32BE,
  readInt32LE,
  readUInt32BE,
  readUInt32LE,
  writeDoubleBE,
  writeDoubleLE,
  writeFloatBE,
  writeFloatLE,
  writeInt32BE,
  writeInt32LE,
  writeUInt32BE,
  writeUInt32LE
}
{
  "name": "b4a",
  "version": "1.8.1",
  "description": "Bridging the gap between buffers and typed arrays",
  "exports": {
    "./package": "./package.json",
    ".": {
      "react-native": "./react-native.js",
      "browser": "./browser.js",
      "default": "./index.js"
    }
  },
  "files": [
    "browser.js",
    "index.js",
    "react-native.js",
    "lib"
  ],
  "scripts": {
    "format": "prettier --write . && lunte --fix",
    "lint": "prettier --check . && lunte",
    "test": "npm run test:bare && npm run test:node",
    "test:bare": "brittle-bare --coverage test.mjs",
    "test:node": "brittle-node --coverage test.mjs"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/b4a.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/b4a/issues"
  },
  "homepage": "https://github.com/holepunchto/b4a#readme",
  "devDependencies": {
    "brittle": "^3.5.2",
    "lunte": "^1.7.0",
    "nanobench": "^3.0.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^2.0.0"
  },
  "peerDependencies": {
    "react-native-b4a": "*"
  },
  "peerDependenciesMeta": {
    "react-native-b4a": {
      "optional": true
    }
  }
}
module.exports = require.addon()
const binding = require('./binding')

module.exports = binding.abort
{
  "name": "bare-abort",
  "version": "2.0.13",
  "description": "Cause abnormal program termination and generate a crash report",
  "exports": {
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    },
    "./package": "./package.json"
  },
  "files": [
    "index.js",
    "index.d.ts",
    "binding.c",
    "binding.js",
    "CMakeLists.txt",
    "prebuilds"
  ],
  "addon": true,
  "scripts": {
    "test": "prettier . --check"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-abort.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-abort/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-abort#readme",
  "devDependencies": {
    "cmake-bare": "^1.1.7",
    "prettier": "^3.4.2",
    "prettier-config-standard": "^7.0.0"
  }
}
const ESC = '\x1b'
const CSI = ESC + '['
const SGR = (n) => CSI + n + 'm'

exports.constants = {
  ESC,
  CSI,
  SGR
}

// https://en.wikipedia.org/wiki/ANSI_escape_code#CSI_(Control_Sequence_Introducer)_sequences

exports.cursorHide = CSI + '?25l'
exports.cursorShow = CSI + '?25h'

exports.cursorUp = function cursorUp(n = 1) {
  return CSI + n + 'A'
}

exports.cursorDown = function cursorDown(n = 1) {
  return CSI + n + 'B'
}

exports.cursorForward = function cursorForward(n = 1) {
  return CSI + n + 'C'
}

exports.cursorBack = function cursorBack(n = 1) {
  return CSI + n + 'D'
}

exports.cursorNextLine = function cursorNextLine(n = 1) {
  return CSI + n + 'E'
}

exports.cursorPreviousLine = function cursorPreviousLine(n = 1) {
  return CSI + n + 'F'
}

exports.cursorPosition = function cursorPosition(column, row = 0) {
  if (row === 0) return CSI + (column + 1) + 'G'

  return CSI + (row + 1) + ';' + (column + 1) + 'H'
}

exports.eraseDisplayEnd = CSI + 'J'
exports.eraseDisplayStart = CSI + '1J'
exports.eraseDisplay = CSI + '2J'
exports.eraseLineEnd = CSI + 'K'
exports.eraseLineStart = CSI + '1K'
exports.eraseLine = CSI + '2K'

exports.scrollUp = function scrollUp(n = 1) {
  return CSI + n + 'S'
}

exports.scrollDown = function scrollDown(n = 1) {
  return CSI + n + 'T'
}

// https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_(Select_Graphic_Rendition)_parameters

exports.modifierReset = SGR(0)
exports.modifierBold = SGR(1)
exports.modifierDim = SGR(2)
exports.modifierItalic = SGR(3)
exports.modifierUnderline = SGR(4)
exports.modifierNormal = SGR(22)
exports.modifierNotItalic = SGR(23)
exports.modifierNotUnderline = SGR(24)

// https://en.wikipedia.org/wiki/ANSI_escape_code#Colors

exports.colorBlack = SGR(30)
exports.colorRed = SGR(31)
exports.colorGreen = SGR(32)
exports.colorYellow = SGR(33)
exports.colorBlue = SGR(34)
exports.colorMagenta = SGR(35)
exports.colorCyan = SGR(36)
exports.colorWhite = SGR(37)
exports.colorDefault = SGR(39)
exports.colorBrightBlack = SGR(90)
exports.colorBrightRed = SGR(91)
exports.colorBrightGreen = SGR(92)
exports.colorBrightYellow = SGR(93)
exports.colorBrightBlue = SGR(94)
exports.colorBrightMagenta = SGR(95)
exports.colorBrightCyan = SGR(96)
exports.colorBrightWhite = SGR(97)
{
  "name": "bare-ansi-escapes",
  "version": "2.2.3",
  "description": "Parse and produce ANSI escape sequences",
  "exports": {
    "./package": "./package.json",
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    },
    "./key-decoder": {
      "types": "./key-decoder.d.ts",
      "default": "./key-decoder.js"
    }
  },
  "files": [
    "index.js",
    "index.d.ts",
    "key-decoder.js",
    "key-decoder.d.ts"
  ],
  "scripts": {
    "test": "prettier . --check && bare test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-ansi-escapes.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-ansi-escapes/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-ansi-escapes#readme",
  "dependencies": {
    "bare-stream": "^2.6.5"
  },
  "devDependencies": {
    "bare-buffer": "^3.0.2",
    "brittle": "^3.3.2",
    "prettier": "^3.4.2",
    "prettier-config-standard": "^7.0.0"
  },
  "peerDependencies": {
    "bare-buffer": "*"
  },
  "peerDependenciesMeta": {
    "bare-buffer": {
      "optional": true
    }
  }
}
const inspect = require('bare-inspect')

class AssertionError extends Error {
  constructor(opts = {}) {
    let { message = null, actual, expected, operator } = opts

    if (message === null) {
      message = `${inspect(actual)} ${operator} ${inspect(expected)}`
    }

    super(message)

    this.actual = actual
    this.expected = expected
    this.operator = operator
  }

  get name() {
    return 'AssertionError'
  }

  get code() {
    'ASSERTION'
  }
}

function assertFail(opts, fn) {
  if (opts.message instanceof Error) throw opts.message

  const err = new AssertionError(opts)

  if (Error.captureStackTrace) Error.captureStackTrace(err, fn)

  throw err
}

module.exports = exports = function assert(actual, message) {
  if (actual) return

  assertFail({ message, actual, expected: true, operator: '==' }, assert)
}

exports.AssertionError = AssertionError

exports.fail = function fail(message) {
  if (message === undefined) message = 'Failed'

  assertFail({ message, operator: 'fail' }, fail)
}

exports.ok = function ok(actual, message) {
  if (actual) return

  assertFail({ message, actual, expected: true, operator: '==' }, ok)
}

exports.notOk = function ok(actual, message) {
  if (!actual) return

  assertFail({ message, actual, expected: false, operator: '==' }, ok)
}

exports.equal = function equal(actual, expected, message) {
  if (actual == expected || (actual !== actual && expected !== expected)) {
    return
  }

  assertFail({ message, actual, expected, operator: '==' }, equal)
}

exports.notEqual = function notEqual(actual, expected, message) {
  if (actual != expected && (actual === actual || expected === expected)) {
    return
  }

  assertFail({ message, actual, expected, operator: '!=' }, notEqual)
}

exports.strictEqual = function strictEqual(actual, expected, message) {
  if (Object.is(actual, expected)) return

  assertFail(
    { message, actual, expected, operator: 'strictEqual' },
    strictEqual
  )
}

exports.notStrictEqual = function notStrictEqual(actual, expected, message) {
  if (!Object.is(actual, expected)) return

  assertFail(
    { message, actual, expected, operator: 'notStrictEqual' },
    notStrictEqual
  )
}
{
  "name": "bare-assert",
  "version": "1.2.0",
  "description": "Assertion library for JavaScript",
  "exports": {
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    },
    "./package": "./package.json"
  },
  "files": [
    "index.js",
    "index.d.ts"
  ],
  "scripts": {
    "test": "prettier . --check && bare test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-assert.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-assert/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-assert#readme",
  "dependencies": {
    "bare-inspect": "^3.1.2"
  },
  "devDependencies": {
    "brittle": "^3.1.1",
    "prettier": "^3.4.2",
    "prettier-config-standard": "^7.0.0"
  }
}
const os = require('bare-os')

module.exports = new Proxy(Object.create(null), {
  ownKeys (target) {
    return os.getEnvKeys()
  },

  get (target, property) {
    if (typeof property !== 'string') return

    return os.getEnv(property)
  },

  has (target, property) {
    if (typeof property !== 'string') return false

    return os.hasEnv(property)
  },

  set (target, property, value) {
    if (typeof property !== 'string') return

    const type = typeof value

    if (type !== 'string' && type !== 'number' && type !== 'boolean') {
      throw new Error('Environment variable must be of type string, number, or boolean')
    }

    value = String(value)

    os.setEnv(property, value)

    return true
  },

  deleteProperty (target, property) {
    if (typeof property !== 'string') return

    os.unsetEnv(property)
  },

  getOwnPropertyDescriptor (target, property) {
    return {
      value: this.get(target, property),
      enumerable: true,
      configurable: true,
      writable: true
    }
  }
})
{
  "name": "bare-env",
  "version": "3.0.0",
  "description": "Environment variable support for JavaScript",
  "main": "index.js",
  "files": [
    "index.js"
  ],
  "scripts": {
    "test": "standard && bare test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-env.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-env/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-env#readme",
  "dependencies": {
    "bare-os": "^3.0.1"
  },
  "devDependencies": {
    "brittle": "^3.2.1",
    "standard": "^17.0.0"
  }
}
const errors = require('./lib/errors')

class EventListener {
  constructor() {
    this.list = []
    this.count = 0
  }

  append(ctx, name, fn, once) {
    this.count++
    ctx.emit('newListener', name, fn) // Emit BEFORE adding
    this.list.push([fn, once])
  }

  prepend(ctx, name, fn, once) {
    this.count++
    ctx.emit('newListener', name, fn) // Emit BEFORE adding
    this.list.unshift([fn, once])
  }

  remove(ctx, name, fn) {
    for (let i = 0, n = this.list.length; i < n; i++) {
      const l = this.list[i]

      if (l[0] === fn) {
        this.list.splice(i, 1)

        if (this.count === 1) delete ctx._events[name]

        ctx.emit('removeListener', name, fn) // Emit AFTER removing

        this.count--
        return
      }
    }
  }

  removeAll(ctx, name) {
    const list = [...this.list]
    this.list = []

    if (this.count === list.length) delete ctx._events[name]

    for (let i = list.length - 1; i >= 0; i--) {
      ctx.emit('removeListener', name, list[i][0]) // Emit AFTER removing
    }

    this.count -= list.length
  }

  emit(ctx, name, ...args) {
    const list = [...this.list]

    for (let i = 0, n = list.length; i < n; i++) {
      const l = list[i]

      if (l[1] === true) this.remove(ctx, name, l[0])

      Reflect.apply(l[0], ctx, args)
    }

    return list.length > 0
  }
}

function appendListener(ctx, name, fn, once) {
  if (ctx._events === undefined) ctx._events = Object.create(null)
  const e = ctx._events[name] || (ctx._events[name] = new EventListener())
  e.append(ctx, name, fn, once)
  return ctx
}

function prependListener(ctx, name, fn, once) {
  if (ctx._events === undefined) ctx._events = Object.create(null)
  const e = ctx._events[name] || (ctx._events[name] = new EventListener())
  e.prepend(ctx, name, fn, once)
  return ctx
}

function removeListener(ctx, name, fn) {
  if (ctx._events === undefined) return ctx
  const e = ctx._events[name]
  if (e !== undefined) e.remove(ctx, name, fn)
  return ctx
}

function throwUnhandledError(...args) {
  let err

  if (args.length > 0) err = args[0]

  if (err instanceof Error === false) err = errors.UNHANDLED_ERROR(err)

  if (Error.captureStackTrace) {
    Error.captureStackTrace(err, exports.prototype.emit)
  }

  queueMicrotask(() => {
    throw err
  })
}

module.exports = exports = class EventEmitter {
  constructor() {
    this._events = Object.create(null)
  }

  addListener(name, fn) {
    return appendListener(this, name, fn, false)
  }

  addOnceListener(name, fn) {
    return appendListener(this, name, fn, true)
  }

  prependListener(name, fn) {
    return prependListener(this, name, fn, false)
  }

  prependOnceListener(name, fn) {
    return prependListener(this, name, fn, true)
  }

  removeListener(name, fn) {
    return removeListener(this, name, fn)
  }

  on(name, fn) {
    return appendListener(this, name, fn, false)
  }

  once(name, fn) {
    return appendListener(this, name, fn, true)
  }

  off(name, fn) {
    return removeListener(this, name, fn)
  }

  emit(name, ...args) {
    if (name === 'error' && this._events !== undefined && this._events.error === undefined) {
      throwUnhandledError(...args)
    }

    if (this._events === undefined) return false
    const e = this._events[name]
    return e === undefined ? false : e.emit(this, name, ...args)
  }

  listeners(name) {
    if (this._events === undefined) return []
    const e = this._events[name]
    return e === undefined ? [] : [...e.list]
  }

  rawListeners(name) {
    if (this._events === undefined) return []
    const e = this._events[name]
    return e === undefined ? [] : e.list.map((l) => l[0])
  }

  eventNames() {
    if (this._events === undefined) return []
    return Reflect.ownKeys(this._events)
  }

  listenerCount(name) {
    if (this._events === undefined) return 0
    const e = this._events[name]
    return e === undefined ? 0 : e.list.length
  }

  getMaxListeners() {
    return EventEmitter.defaultMaxListeners
  }

  setMaxListeners(n) {}

  removeAllListeners(name) {
    if (arguments.length === 0) {
      for (const key of Reflect.ownKeys(this._events)) {
        if (key === 'removeListener') continue
        this.removeAllListeners(key)
      }
      this.removeAllListeners('removeListener')
    } else {
      const e = this._events[name]
      if (e !== undefined) e.removeAll(this, name)
    }
    return this
  }
}

exports.EventEmitter = exports

exports.errors = errors

exports.defaultMaxListeners = 10

exports.on = function on(emitter, name, opts = {}) {
  const { signal } = opts

  if (signal && signal.aborted) {
    throw errors.OPERATION_ABORTED(signal.reason)
  }

  let error = null
  let done = false

  const events = []
  const promises = []

  if (name !== 'error') emitter.on('error', onerror)

  if (signal) signal.addEventListener('abort', onabort)

  emitter.on(name, onevent)

  return {
    next() {
      if (events.length) {
        return Promise.resolve({ value: events.shift(), done: false })
      }

      if (error) {
        const err = error

        error = null

        return Promise.reject(err)
      }

      if (done) return onclose()

      return new Promise((resolve, reject) => promises.push({ resolve, reject }))
    },

    return() {
      return onclose()
    },

    throw(err) {
      return onerror(err)
    },

    [Symbol.asyncIterator]() {
      return this
    }
  }

  function onevent(...args) {
    if (promises.length) {
      promises.shift().resolve({ value: args, done: false })
    } else {
      events.push(args)
    }
  }

  function onerror(err) {
    emitter.off(name, onevent).off('error', onerror)

    if (promises.length) {
      promises.shift().reject(err)
    } else {
      error = err
    }

    return Promise.resolve({ done: true })
  }

  function onabort() {
    signal.removeEventListener('abort', onabort)

    onerror(errors.OPERATION_ABORTED(signal.reason))
  }

  function onclose() {
    emitter.off(name, onevent)

    if (name !== 'error') emitter.off('error', onerror)

    if (signal) signal.removeEventListener('abort', onabort)

    done = true

    if (promises.length) promises.shift().resolve({ done: true })

    return Promise.resolve({ done: true })
  }
}

exports.once = function once(emitter, name, opts = {}) {
  const { signal } = opts

  if (signal && signal.aborted) {
    return Promise.reject(errors.OPERATION_ABORTED(signal.reason))
  }

  return new Promise((resolve, reject) => {
    if (name !== 'error') emitter.on('error', onerror)

    if (signal) signal.addEventListener('abort', onabort)

    emitter.once(name, onevent)

    function onevent(...args) {
      if (name !== 'error') emitter.off('error', onerror)

      if (signal) signal.removeEventListener('abort', onabort)

      resolve(args)
    }

    function onerror(err) {
      emitter.off(name, onevent)

      if (name !== 'error') emitter.off('error', onerror)

      reject(err)
    }

    function onabort() {
      signal.removeEventListener('abort', onabort)

      onerror(errors.OPERATION_ABORTED(signal.reason))
    }
  })
}

exports.forward = function forward(from, to, names, opts = {}) {
  if (typeof names === 'string') names = [names]

  const { emit = to.emit.bind(to) } = opts

  const listeners = names.map(
    (name) =>
      function onevent(...args) {
        emit(name, ...args)
      }
  )

  to.on('newListener', (name) => {
    const i = names.indexOf(name)

    if (i !== -1 && to.listenerCount(name) === 0) {
      from.on(name, listeners[i])
    }
  }).on('removeListener', (name) => {
    const i = names.indexOf(name)

    if (i !== -1 && to.listenerCount(name) === 0) {
      from.off(name, listeners[i])
    }
  })
}

exports.listenerCount = function listenerCount(emitter, name) {
  return emitter.listenerCount(name)
}

exports.getMaxListeners = function getMaxListeners(emitter) {
  if (typeof emitter.getMaxListeners === 'function') {
    return emitter.getMaxListeners()
  }

  return exports.defaultMaxListeners
}

exports.setMaxListeners = function setMaxListeners(n, ...emitters) {
  if (emitters.length === 0) exports.defaultMaxListeners = n
  else {
    for (const emitter of emitters) {
      if (typeof emitter.setMaxListeners === 'function') {
        emitter.setMaxListeners(n)
      }
    }
  }
}
module.exports = class EventEmitterError extends Error {
  constructor(msg, code, fn = EventEmitterError, opts) {
    super(`${code}: ${msg}`, opts)
    this.code = code

    if (Error.captureStackTrace) {
      Error.captureStackTrace(this, fn)
    }
  }

  get name() {
    return 'EventEmitterError'
  }

  static OPERATION_ABORTED(cause, msg = 'Operation aborted') {
    return new EventEmitterError(msg, 'OPERATION_ABORTED', EventEmitterError.OPERATION_ABORTED, {
      cause
    })
  }

  static UNHANDLED_ERROR(cause, msg = 'Unhandled error') {
    return new EventEmitterError(msg, 'UNHANDLED_ERROR', EventEmitterError.UNHANDLED_ERROR, {
      cause
    })
  }
}
{
  "name": "bare-events",
  "version": "2.9.1",
  "description": "Event emitters for JavaScript",
  "exports": {
    "./package": "./package.json",
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    },
    "./global": {
      "types": "./global.d.ts",
      "default": "./global.js"
    },
    "./web": {
      "types": "./web.d.ts",
      "default": "./web.js"
    },
    "./errors": "./lib/errors.js"
  },
  "files": [
    "index.js",
    "index.d.ts",
    "global.js",
    "global.d.ts",
    "web.js",
    "web.d.ts",
    "lib"
  ],
  "scripts": {
    "format": "prettier --write . && lunte --fix",
    "lint": "prettier --check . && lunte",
    "test": "npm run test:bare && npm run test:node",
    "test:bare": "brittle-bare --coverage test.js",
    "test:node": "brittle-node --coverage test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-events.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-events/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-events#readme",
  "devDependencies": {
    "bare-abort-controller": "^1.0.0",
    "brittle": "^3.3.2",
    "lunte": "^1.8.0",
    "prettier": "^3.4.2",
    "prettier-config-holepunch": "^2.0.0",
    "uncaughts": "^1.1.1"
  },
  "peerDependencies": {
    "bare-abort-controller": "*"
  },
  "peerDependenciesMeta": {
    "bare-abort-controller": {
      "optional": true
    }
  }
}
module.exports = require.addon()
const FIFO = require('fast-fifo')
const EventEmitter = require('bare-events')
const path = require('bare-path')
const { isURL, fileURLToPath } = require('bare-url')
const { Readable, Writable } = require('bare-stream')
const binding = require('./binding')
const constants = require('./lib/constants')
const FileError = require('./lib/errors')

const isWindows = Bare.platform === 'win32'

exports.constants = constants

class FileRequest {
  static borrow() {
    if (this._free.length > 0) return this._free.pop()
    return new FileRequest()
  }

  static return(req) {
    if (this._free.length < 32) this._free.push(req.reset())
    else req.destroy()
  }

  constructor() {
    this._reset()
    this._handle = binding.requestInit(this, this._onresult)
  }

  get handle() {
    return this._handle
  }

  retain(value) {
    this._retain = value // Tie the lifetime of `value` to the lifetime of `this`
  }

  reset() {
    if (this._handle === null) return this

    binding.requestReset(this._handle)

    this._reset()

    return this
  }

  destroy() {
    if (this._handle === null) return this

    binding.requestDestroy(this._handle)

    this._reset()
    this._handle = null

    return this
  }

  then(resolve, reject) {
    return this._promise.then(resolve, reject)
  }

  return() {
    if (this._handle === null) return this

    FileRequest.return(this)

    return this
  }

  _reset() {
    this._promise = new Promise((resolve, reject) => {
      this._resolve = resolve
      this._reject = reject
    })
    this._retain = null
  }

  _onresult(err, status) {
    if (err) this._reject(err)
    else this._resolve(status)
  }
}

FileRequest._free = []

function ok(result, cb) {
  if (typeof result === 'function') {
    cb = result
    result = undefined
  }

  if (cb) cb(null, result)
  else return result
}

function fail(err, cb) {
  if (cb) cb(err)
  else throw err
}

function done(err, result, cb) {
  if (typeof result === 'function') {
    cb = result
    result = undefined
  }

  if (err) fail(err, cb)
  else return ok(result, cb)
}

async function open(filepath, flags = 'r', mode = 0o666, cb) {
  if (typeof flags === 'function') {
    cb = flags
    flags = 'r'
    mode = 0o666
  } else if (typeof mode === 'function') {
    cb = mode
    mode = 0o666
  }

  if (typeof flags === 'string') flags = toFlags(flags)
  if (typeof mode === 'string') mode = toMode(mode)

  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  let fd
  let err = null
  try {
    binding.open(req.handle, filepath, flags, mode)

    fd = await req
  } catch (e) {
    err = new FileError(e.message, {
      operation: 'open',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }

  return done(err, fd, cb)
}

function openSync(filepath, flags = 'r', mode = 0o666) {
  if (typeof flags === 'string') flags = toFlags(flags)
  if (typeof mode === 'string') mode = toMode(mode)

  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  try {
    return binding.openSync(req.handle, filepath, flags, mode)
  } catch (e) {
    throw new FileError(e.message, {
      operation: 'open',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }
}

async function close(fd, cb) {
  const req = FileRequest.borrow()

  let err = null
  try {
    binding.close(req.handle, fd)

    await req
  } catch (e) {
    err = new FileError(e.message, { operation: 'close', code: e.code, fd })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function closeSync(fd) {
  const req = FileRequest.borrow()

  try {
    binding.closeSync(req.handle, fd)
  } catch (e) {
    throw new FileError(e.message, { operation: 'close', code: e.code, fd })
  } finally {
    req.return()
  }
}

async function access(filepath, mode = constants.F_OK, cb) {
  if (typeof mode === 'function') {
    cb = mode
    mode = constants.F_OK
  }

  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  let err = null
  try {
    binding.access(req.handle, filepath, mode)

    await req
  } catch (e) {
    err = new FileError(e.message, {
      operation: 'access',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function accessSync(filepath, mode = constants.F_OK) {
  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  try {
    binding.accessSync(req.handle, filepath, mode)
  } catch (e) {
    throw new FileError(e.message, {
      operation: 'access',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }
}

async function exists(filepath, cb) {
  let ok = true
  try {
    await access(filepath)
  } catch {
    ok = false
  }

  return done(null, ok, cb)
}

function existsSync(filepath) {
  try {
    accessSync(filepath)
  } catch {
    return false
  }

  return true
}

async function read(fd, buffer, offset = 0, len = buffer.byteLength - offset, pos = -1, cb) {
  if (typeof offset === 'function') {
    cb = offset
    offset = 0
    len = buffer.byteLength
    pos = -1
  } else if (typeof len === 'function') {
    cb = len
    len = buffer.byteLength - offset
    pos = -1
  } else if (typeof pos === 'function') {
    cb = pos
    pos = -1
  }

  if (typeof pos !== 'number') pos = -1

  const req = FileRequest.borrow()

  let bytes
  let err = null
  try {
    binding.read(req.handle, fd, buffer, offset, len, pos)

    req.retain(buffer)

    bytes = await req
  } catch (e) {
    err = new FileError(e.message, { operation: 'read', code: e.code, fd })
  } finally {
    req.return()
  }

  return done(err, bytes, cb)
}

function readSync(fd, buffer, offset = 0, len = buffer.byteLength - offset, pos = -1) {
  const req = FileRequest.borrow()

  try {
    return binding.readSync(req.handle, fd, buffer, offset, len, pos)
  } catch (e) {
    throw new FileError(e.message, { operation: 'read', code: e.code, fd })
  } finally {
    req.return()
  }
}

async function readv(fd, buffers, pos = -1, cb) {
  if (typeof pos === 'function') {
    cb = pos
    pos = -1
  }

  if (typeof pos !== 'number') pos = -1

  const req = FileRequest.borrow()

  let bytes
  let err = null
  try {
    binding.readv(req.handle, fd, buffers, pos)

    req.retain(buffers)

    bytes = await req
  } catch (e) {
    err = new FileError(e.message, { operation: 'readv', code: e.code, fd })
  } finally {
    req.return()
  }

  return done(err, bytes, cb)
}

function readvSync(fd, buffers, pos = -1) {
  if (typeof pos !== 'number') pos = -1

  const req = FileRequest.borrow()

  try {
    return binding.readvSync(req.handle, fd, buffers, pos)
  } catch (e) {
    throw new FileError(e.message, { operation: 'readv', code: e.code, fd })
  } finally {
    req.return()
  }
}

async function write(fd, data, offset, len, pos = -1, cb) {
  if (typeof data === 'string') {
    let encoding = len
    cb = pos
    pos = offset

    if (typeof pos === 'function') {
      cb = pos
      pos = -1
      encoding = 'utf8'
    } else if (typeof encoding === 'function') {
      cb = encoding
      encoding = 'utf8'
    }

    if (typeof pos === 'string') {
      encoding = pos
      pos = -1
    }

    data = Buffer.from(data, encoding)
    offset = 0
    len = data.byteLength
  } else if (typeof offset === 'function') {
    cb = offset
    offset = 0
    len = data.byteLength
    pos = -1
  } else if (typeof len === 'function') {
    cb = len
    len = data.byteLength - offset
    pos = -1
  } else if (typeof pos === 'function') {
    cb = pos
    pos = -1
  }

  if (typeof offset !== 'number') offset = 0
  if (typeof len !== 'number') len = data.byteLength - offset
  if (typeof pos !== 'number') pos = -1

  const req = FileRequest.borrow()

  let bytes
  let err = null
  try {
    binding.write(req.handle, fd, data, offset, len, pos)

    req.retain(data)

    bytes = await req
  } catch (e) {
    err = new FileError(e.message, { operation: 'write', code: e.code, fd })
  } finally {
    req.return()
  }

  return done(err, bytes, cb)
}

function writeSync(fd, data, offset, len, pos = -1) {
  if (typeof data === 'string') {
    let encoding = len
    pos = offset

    if (typeof pos === 'string') {
      encoding = pos
      pos = -1
    }

    data = Buffer.from(data, encoding)
    offset = 0
    len = data.byteLength
  }

  if (typeof offset !== 'number') offset = 0
  if (typeof len !== 'number') len = data.byteLength - offset
  if (typeof pos !== 'number') pos = -1

  const req = FileRequest.borrow()

  try {
    return binding.writeSync(req.handle, fd, data, offset, len, pos)
  } catch (e) {
    throw new FileError(e.message, { operation: 'write', code: e.code, fd })
  } finally {
    req.return()
  }
}

async function writev(fd, buffers, pos = -1, cb) {
  if (typeof pos === 'function') {
    cb = pos
    pos = -1
  }

  if (typeof pos !== 'number') pos = -1

  const req = FileRequest.borrow()

  let bytes
  let err = null
  try {
    binding.writev(req.handle, fd, buffers, pos)

    req.retain(buffers)

    bytes = await req
  } catch (e) {
    err = new FileError(e.message, { operation: 'writev', code: e.code, fd })
  } finally {
    req.return()
  }

  return done(err, bytes, cb)
}

function writevSync(fd, buffers, pos = -1) {
  if (typeof pos !== 'number') pos = -1

  const req = FileRequest.borrow()

  try {
    return binding.writevSync(req.handle, fd, buffers, pos)
  } catch (e) {
    throw new FileError(e.message, { operation: 'writev', code: e.code, fd })
  } finally {
    req.return()
  }
}

async function stat(filepath, cb) {
  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  let st
  let err = null
  try {
    binding.stat(req.handle, filepath)

    await req

    st = new Stats(...binding.requestResultStat(req.handle))
  } catch (e) {
    err = new FileError(e.message, { operation: 'stat', code: e.code, path: filepath })
  } finally {
    req.return()
  }

  return done(err, st, cb)
}

function statSync(filepath) {
  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  try {
    binding.statSync(req.handle, filepath)

    return new Stats(...binding.requestResultStat(req.handle))
  } catch (e) {
    throw new FileError(e.message, { operation: 'stat', code: e.code, path: filepath })
  } finally {
    req.return()
  }
}

async function lstat(filepath, cb) {
  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  let st
  let err = null
  try {
    binding.lstat(req.handle, filepath)

    await req

    st = new Stats(...binding.requestResultStat(req.handle))
  } catch (e) {
    err = new FileError(e.message, { operation: 'lstat', code: e.code, path: filepath })
  } finally {
    req.return()
  }

  return done(err, st, cb)
}

function lstatSync(filepath) {
  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  try {
    binding.lstatSync(req.handle, filepath)

    return new Stats(...binding.requestResultStat(req.handle))
  } catch (e) {
    throw new FileError(e.message, { operation: 'lstat', code: e.code, path: filepath })
  } finally {
    req.return()
  }
}

async function fstat(fd, cb) {
  const req = FileRequest.borrow()

  let st
  let err = null
  try {
    binding.fstat(req.handle, fd)

    await req

    st = new Stats(...binding.requestResultStat(req.handle))
  } catch (e) {
    err = new FileError(e.message, { operation: 'fstat', code: e.code, fd })
  } finally {
    req.return()
  }

  return done(err, st, cb)
}

function fstatSync(fd) {
  const req = FileRequest.borrow()

  try {
    binding.fstatSync(req.handle, fd)

    return new Stats(...binding.requestResultStat(req.handle))
  } catch (e) {
    throw new FileError(e.message, { operation: 'fstat', code: e.code, fd })
  } finally {
    req.return()
  }
}

async function statfs(filepath, cb) {
  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  let st
  let err = null
  try {
    binding.statfs(req.handle, filepath)

    await req

    st = new StatFs(...binding.requestResultStatfs(req.handle))
  } catch (e) {
    err = new FileError(e.message, { operation: 'statfs', code: e.code, path: filepath })
  } finally {
    req.return()
  }

  return done(err, st, cb)
}

function statfsSync(filepath) {
  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  try {
    binding.statfsSync(req.handle, filepath)

    return new StatFs(...binding.requestResultStatfs(req.handle))
  } catch (e) {
    throw new FileError(e.message, { operation: 'statfs', code: e.code, path: filepath })
  } finally {
    req.return()
  }
}

async function ftruncate(fd, len = 0, cb) {
  if (typeof len === 'function') {
    cb = len
    len = 0
  }

  if (typeof len !== 'number') len = 0

  const req = FileRequest.borrow()

  let err = null
  try {
    binding.ftruncate(req.handle, fd, len)

    await req
  } catch (e) {
    err = new FileError(e.message, { operation: 'ftruncate', code: e.code, fd })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function ftruncateSync(fd, len = 0) {
  if (typeof len !== 'number') len = 0

  const req = FileRequest.borrow()

  try {
    binding.ftruncateSync(req.handle, fd, len)
  } catch (e) {
    throw new FileError(e.message, { operation: 'ftruncate', code: e.code, fd })
  } finally {
    req.return()
  }
}

async function truncate(filepath, len, cb) {
  let fd = -1
  let err

  try {
    fd = await open(filepath, 'r+')

    await ftruncate(fd, len)
  } catch (e) {
    err = e
  } finally {
    if (fd !== -1) await close(fd)
  }

  return done(err, cb)
}

function truncateSync(filepath, len) {
  let fd = -1
  let err

  try {
    fd = openSync(filepath, 'r+')

    ftruncateSync(fd, len)
  } catch (e) {
    err = e
  } finally {
    if (fd !== -1) closeSync(fd)
  }
}

async function chmod(filepath, mode, cb) {
  if (typeof mode === 'string') mode = toMode(mode)

  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  let err = null
  try {
    binding.chmod(req.handle, filepath, mode)

    await req
  } catch (e) {
    err = new FileError(e.message, {
      operation: 'chmod',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function chmodSync(filepath, mode) {
  if (typeof mode === 'string') mode = toMode(mode)

  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  try {
    binding.chmodSync(req.handle, filepath, mode)
  } catch (e) {
    throw new FileError(e.message, {
      operation: 'chmod',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }
}

async function fchmod(fd, mode, cb) {
  if (typeof mode === 'string') mode = toMode(mode)

  const req = FileRequest.borrow()

  let err = null
  try {
    binding.fchmod(req.handle, fd, mode)

    await req
  } catch (e) {
    err = new FileError(e.message, { operation: 'fchmod', code: e.code, fd })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function fchmodSync(fd, mode) {
  if (typeof mode === 'string') mode = toMode(mode)

  const req = FileRequest.borrow()

  try {
    binding.fchmodSync(req.handle, fd, mode)
  } catch (e) {
    throw new FileError(e.message, { operation: 'fchmod', code: e.code, fd })
  } finally {
    req.return()
  }
}

async function chown(filepath, uid, gid, cb) {
  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  let err = null
  try {
    binding.chown(req.handle, filepath, uid, gid)

    await req
  } catch (e) {
    err = new FileError(e.message, { operation: 'chown', code: e.code, path: filepath })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function chownSync(filepath, uid, gid) {
  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  try {
    binding.chownSync(req.handle, filepath, uid, gid)
  } catch (e) {
    throw new FileError(e.message, { operation: 'chownSync', code: e.code, path: filepath })
  } finally {
    req.return()
  }
}

async function lchown(filepath, uid, gid, cb) {
  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  let err = null
  try {
    binding.lchown(req.handle, filepath, uid, gid)

    await req
  } catch (e) {
    err = new FileError(e.message, { operation: 'lchown', code: e.code, path: filepath })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function lchownSync(filepath, uid, gid) {
  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  try {
    binding.lchownSync(req.handle, filepath, uid, gid)
  } catch (e) {
    throw new FileError(e.message, { operation: 'lchownSync', code: e.code, path: filepath })
  } finally {
    req.return()
  }
}

async function fchown(fd, uid, gid, cb) {
  const req = FileRequest.borrow()

  let err = null
  try {
    binding.fchown(req.handle, fd, uid, gid)

    await req
  } catch (e) {
    err = new FileError(e.message, { operation: 'fchown', code: e.code, fd })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function fchownSync(fd, uid, gid) {
  const req = FileRequest.borrow()

  try {
    binding.fchownSync(req.handle, fd, uid, gid)
  } catch (e) {
    throw new FileError(e.message, { operation: 'fchownSync', code: e.code, fd })
  } finally {
    req.return()
  }
}

async function utimes(filepath, atime, mtime, cb) {
  if (typeof atime !== 'number') atime = atime.getTime() / 1000
  if (typeof mtime !== 'number') mtime = mtime.getTime() / 1000

  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  let err = null
  try {
    binding.utimes(req.handle, filepath, atime, mtime)

    await req
  } catch (e) {
    err = new FileError(e.message, { operation: 'utimes', code: e.code, path: filepath })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function utimesSync(filepath, atime, mtime) {
  if (typeof atime !== 'number') atime = atime.getTime() / 1000
  if (typeof mtime !== 'number') mtime = mtime.getTime() / 1000

  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  try {
    binding.utimesSync(req.handle, filepath, atime, mtime)
  } catch (e) {
    throw new FileError(e.message, { operation: 'utimes', code: e.code, path: filepath })
  } finally {
    req.return()
  }
}

async function lutimes(filepath, atime, mtime, cb) {
  if (typeof atime !== 'number') atime = atime.getTime() / 1000
  if (typeof mtime !== 'number') mtime = mtime.getTime() / 1000

  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  let err = null
  try {
    binding.lutimes(req.handle, filepath, atime, mtime)

    await req
  } catch (e) {
    err = new FileError(e.message, { operation: 'lutimes', code: e.code, path: filepath })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function lutimesSync(filepath, atime, mtime) {
  if (typeof atime !== 'number') atime = atime.getTime() / 1000
  if (typeof mtime !== 'number') mtime = mtime.getTime() / 1000

  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  try {
    binding.lutimesSync(req.handle, filepath, atime, mtime)
  } catch (e) {
    throw new FileError(e.message, { operation: 'lutimes', code: e.code, path: filepath })
  } finally {
    req.return()
  }
}

async function futimes(fd, atime, mtime, cb) {
  if (typeof atime !== 'number') atime = atime.getTime() / 1000
  if (typeof mtime !== 'number') mtime = mtime.getTime() / 1000

  const req = FileRequest.borrow()

  let err = null
  try {
    binding.futimes(req.handle, fd, atime, mtime)

    await req
  } catch (e) {
    err = new FileError(e.message, { operation: 'futimes', code: e.code, fd })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function futimesSync(fd, atime, mtime) {
  if (typeof atime !== 'number') atime = atime.getTime() / 1000
  if (typeof mtime !== 'number') mtime = mtime.getTime() / 1000

  const req = FileRequest.borrow()

  try {
    binding.futimesSync(req.handle, fd, atime, mtime)
  } catch (e) {
    throw new FileError(e.message, { operation: 'futimesSync', code: e.code, fd })
  } finally {
    req.return()
  }
}

async function link(src, dst, cb) {
  src = toNamespacedPath(src)
  dst = toNamespacedPath(dst)

  const req = FileRequest.borrow()

  let err = null
  try {
    binding.link(req.handle, src, dst)

    await req
  } catch (e) {
    err = new FileError(e.message, { operation: 'link', code: e.code, path: src, destination: dst })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function linkSync(src, dst) {
  src = toNamespacedPath(src)
  dst = toNamespacedPath(dst)

  const req = FileRequest.borrow()

  try {
    binding.linkSync(req.handle, src, dst)
  } catch (e) {
    throw new FileError(e.message, {
      operation: 'linkSync',
      code: e.code,
      path: src,
      destination: dst
    })
  } finally {
    req.return()
  }
}

async function mkdir(filepath, opts, cb) {
  if (typeof opts === 'function') {
    cb = opts
    opts = { mode: 0o777 }
  }

  if (typeof opts === 'number') opts = { mode: opts }
  else if (!opts) opts = {}

  const mode = typeof opts.mode === 'number' ? opts.mode : 0o777

  filepath = toNamespacedPath(filepath)

  if (opts.recursive) {
    let err = null
    try {
      try {
        await mkdir(filepath, { mode })
      } catch (err) {
        if (err.code !== 'ENOENT') {
          if (!(await stat(filepath)).isDirectory()) throw err
        } else {
          while (filepath.endsWith(path.sep)) filepath = filepath.slice(0, -1)
          const i = filepath.lastIndexOf(path.sep)
          if (i <= 0) throw err

          await mkdir(filepath.slice(0, i), { mode, recursive: true })

          try {
            await mkdir(filepath, { mode })
          } catch (err) {
            if (!(await stat(filepath)).isDirectory()) throw err
          }
        }
      }
    } catch (e) {
      err = e
    }

    return done(err, cb)
  }

  const req = FileRequest.borrow()

  let err = null
  try {
    binding.mkdir(req.handle, filepath, mode)

    await req
  } catch (e) {
    err = new FileError(e.message, {
      operation: 'mkdir',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function mkdirSync(filepath, opts) {
  if (typeof opts === 'number') opts = { mode: opts }
  else if (!opts) opts = {}

  const mode = typeof opts.mode === 'number' ? opts.mode : 0o777

  filepath = toNamespacedPath(filepath)

  if (opts.recursive) {
    try {
      mkdirSync(filepath, { mode })
    } catch (err) {
      if (err.code !== 'ENOENT') {
        if (!statSync(filepath).isDirectory()) throw err
      } else {
        while (filepath.endsWith(path.sep)) filepath = filepath.slice(0, -1)
        const i = filepath.lastIndexOf(path.sep)
        if (i <= 0) throw err

        mkdirSync(filepath.slice(0, i), { mode, recursive: true })

        try {
          mkdirSync(filepath, { mode })
        } catch (err) {
          if (!statSync(filepath).isDirectory()) throw err
        }
      }
    }

    return
  }

  const req = FileRequest.borrow()

  try {
    binding.mkdirSync(req.handle, filepath, mode)
  } catch (e) {
    throw new FileError(e.message, {
      operation: 'mkdir',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }
}

async function mkdtemp(prefix, cb) {
  prefix = toNamespacedPath(prefix)

  const req = FileRequest.borrow()

  let res
  let err = null
  try {
    binding.mkdtemp(req.handle, prefix + 'XXXXXX')

    await req

    res = binding.requestResultPath(req.handle)
  } catch (e) {
    err = new FileError(e.message, { operation: 'mkdtemp', code: e.code, path: prefix })
  } finally {
    req.return()
  }

  return done(err, res, cb)
}

function mkdtempSync(prefix) {
  prefix = toNamespacedPath(prefix)

  const req = FileRequest.borrow()

  try {
    binding.mkdtempSync(req.handle, prefix + 'XXXXXX')

    return binding.requestResultPath(req.handle)
  } catch (e) {
    throw new FileError(e.message, { operation: 'mkdtempSync', code: e.code, path: prefix })
  } finally {
    req.return()
  }
}

async function rmdir(filepath, cb) {
  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  let err = null
  try {
    binding.rmdir(req.handle, filepath)

    await req
  } catch (e) {
    err = new FileError(e.message, {
      operation: 'rmdir',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function rmdirSync(filepath) {
  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  try {
    binding.rmdirSync(req.handle, filepath)
  } catch (e) {
    throw new FileError(e.message, {
      operation: 'rmdir',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }
}

async function rm(filepath, opts, cb) {
  if (typeof opts === 'function') {
    cb = opts
    opts = {}
  }

  if (!opts) opts = {}

  filepath = toNamespacedPath(filepath)

  let err = null
  try {
    const st = await lstat(filepath)

    if (st.isDirectory()) {
      if (opts.recursive) {
        try {
          await rmdir(filepath)
        } catch (err) {
          if (err.code !== 'ENOTEMPTY') throw err

          const files = await readdir(filepath)

          for (const file of files) {
            await rm(filepath + path.sep + file, opts)
          }

          await rmdir(filepath)
        }
      } else {
        throw new FileError('is a directory', {
          operation: 'rm',
          code: 'EISDIR',
          path: filepath
        })
      }
    } else {
      await unlink(filepath)
    }
  } catch (e) {
    if (e.code !== 'ENOENT' || !opts.force) err = e
  }

  return done(err, cb)
}

function rmSync(filepath, opts) {
  if (!opts) opts = {}

  filepath = toNamespacedPath(filepath)

  try {
    const st = lstatSync(filepath)

    if (st.isDirectory()) {
      if (opts.recursive) {
        try {
          rmdirSync(filepath)
        } catch (err) {
          if (err.code !== 'ENOTEMPTY') throw err

          const files = readdirSync(filepath)

          for (const file of files) {
            rmSync(filepath + path.sep + file, opts)
          }

          rmdirSync(filepath)
        }
      } else {
        throw new FileError('is a directory', {
          operation: 'rm',
          code: 'EISDIR',
          path: filepath
        })
      }
    } else {
      unlinkSync(filepath)
    }
  } catch (err) {
    if (err.code !== 'ENOENT' || !opts.force) throw err
  }
}

async function unlink(filepath, cb) {
  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  let err = null
  try {
    binding.unlink(req.handle, filepath)

    await req
  } catch (e) {
    err = new FileError(e.message, {
      operation: 'unlink',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function unlinkSync(filepath) {
  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  try {
    binding.unlinkSync(req.handle, filepath)
  } catch (e) {
    throw new FileError(e.message, {
      operation: 'unlink',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }
}

async function rename(src, dst, cb) {
  src = toNamespacedPath(src)
  dst = toNamespacedPath(dst)

  const req = FileRequest.borrow()

  let err = null
  try {
    binding.rename(req.handle, src, dst)

    await req
  } catch (e) {
    err = new FileError(e.message, {
      operation: 'rename',
      code: e.code,
      path: src,
      destination: dst
    })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function renameSync(src, dst) {
  src = toNamespacedPath(src)
  dst = toNamespacedPath(dst)

  const req = FileRequest.borrow()

  try {
    binding.renameSync(req.handle, src, dst)
  } catch (e) {
    throw new FileError(e.message, {
      operation: 'rename',
      code: e.code,
      path: src,
      destination: dst
    })
  } finally {
    req.return()
  }
}

async function copyFile(src, dst, mode = 0, cb) {
  if (typeof mode === 'function') {
    cb = mode
    mode = 0
  }

  src = toNamespacedPath(src)
  dst = toNamespacedPath(dst)

  const req = FileRequest.borrow()

  let err = null
  try {
    binding.copyfile(req.handle, src, dst, mode)

    await req
  } catch (e) {
    err = new FileError(e.message, {
      operation: 'copyfile',
      code: e.code,
      path: src,
      destination: dst
    })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function copyFileSync(src, dst, mode = 0) {
  src = toNamespacedPath(src)
  dst = toNamespacedPath(dst)

  const req = FileRequest.borrow()

  try {
    binding.copyfileSync(req.handle, src, dst, mode)
  } catch (e) {
    throw new FileError(e.message, {
      operation: 'copyfile',
      code: e.code,
      path: src,
      destination: dst
    })
  } finally {
    req.return()
  }
}

async function cp(src, dst, opts, cb) {
  if (typeof opts === 'function') {
    cb = opts
    opts = {}
  }

  if (!opts) opts = {}

  src = toNamespacedPath(src)
  dst = toNamespacedPath(dst)

  let err = null
  try {
    const st = await lstat(src)

    if (st.isDirectory()) {
      if (opts.recursive !== true) {
        throw new FileError('is a directory', { operation: 'cp', code: 'EISDIR', path: src })
      }

      try {
        await lstat(dst)
      } catch (e) {
        if (e.code !== 'ENOENT') throw e

        await mkdir(dst, { mode: st.mode, recursive: true })
      }

      const dir = await opendir(src)
      for await (const { name } of dir) {
        await cp(path.join(src, name), path.join(dst, name), opts)
      }
    } else if (st.isFile()) {
      await copyFile(src, dst)
      await chmod(dst, st.mode)
    }
  } catch (e) {
    err = e
  }

  return done(err, cb)
}

function cpSync(src, dst, opts = {}) {
  src = toNamespacedPath(src)
  dst = toNamespacedPath(dst)

  const st = lstatSync(src)

  if (st.isDirectory()) {
    if (opts.recursive !== true) {
      throw new FileError('is a directory', { operation: 'cp', code: 'EISDIR', path: src })
    }

    try {
      lstatSync(dst)
    } catch (e) {
      if (e.code !== 'ENOENT') throw e

      mkdirSync(dst, { mode: st.mode, recursive: true })
    }

    const dir = opendirSync(src)
    for (const { name } of dir) {
      cpSync(path.join(src, name), path.join(dst, name), opts)
    }
  } else if (st.isFile()) {
    copyFileSync(src, dst)
    chmodSync(dst, st.mode)
  }
}

async function realpath(filepath, opts, cb) {
  if (typeof opts === 'function') {
    cb = opts
    opts = {}
  }

  if (typeof opts === 'string') opts = { encoding: opts }
  else if (!opts) opts = {}

  const { encoding = 'utf8' } = opts

  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  let res
  let err = null
  try {
    binding.realpath(req.handle, filepath)

    await req

    res = Buffer.from(binding.requestResultString(req.handle))

    if (encoding !== 'buffer') res = res.toString(encoding)
  } catch (e) {
    err = new FileError(e.message, {
      operation: 'realpath',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }

  return done(err, res, cb)
}

function realpathSync(filepath, opts) {
  if (typeof opts === 'string') opts = { encoding: opts }
  else if (!opts) opts = {}

  const { encoding = 'utf8' } = opts

  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  try {
    binding.realpathSync(req.handle, filepath)

    let res = Buffer.from(binding.requestResultString(req.handle))

    if (encoding !== 'buffer') res = res.toString(encoding)

    return res
  } catch (e) {
    throw new FileError(e.message, {
      operation: 'realpath',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }
}

async function readlink(filepath, opts, cb) {
  if (typeof opts === 'function') {
    cb = opts
    opts = {}
  }

  if (typeof opts === 'string') opts = { encoding: opts }
  else if (!opts) opts = {}

  const { encoding = 'utf8' } = opts

  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  let res
  let err = null
  try {
    binding.readlink(req.handle, filepath)

    await req

    res = Buffer.from(binding.requestResultString(req.handle))

    if (encoding !== 'buffer') res = res.toString(encoding)
  } catch (e) {
    err = new FileError(e.message, {
      operation: 'readlink',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }

  return done(err, res, cb)
}

function readlinkSync(filepath, opts) {
  if (typeof opts === 'string') opts = { encoding: opts }
  else if (!opts) opts = {}

  const { encoding = 'utf8' } = opts

  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  try {
    binding.readlinkSync(req.handle, filepath)

    let res = Buffer.from(binding.requestResultString(req.handle))

    if (encoding !== 'buffer') res = res.toString(encoding)

    return res
  } catch (e) {
    throw new FileError(e.message, {
      operation: 'readlink',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }
}

async function fsync(fd, cb) {
  const req = FileRequest.borrow()

  let err = null
  try {
    binding.fsync(req.handle, fd)

    await req
  } catch (e) {
    err = new FileError(e.message, { operation: 'fsync', code: e.code, fd })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function fsyncSync(fd) {
  const req = FileRequest.borrow()

  try {
    binding.fsyncSync(req.handle, fd)
  } catch (e) {
    throw new FileError(e.message, { operation: 'fsyncSync', code: e.code, fd })
  } finally {
    req.return()
  }
}

async function fdatasync(fd, cb) {
  const req = FileRequest.borrow()

  let err = null
  try {
    binding.fdatasync(req.handle, fd)

    await req
  } catch (e) {
    err = new FileError(e.message, { operation: 'fdatasync', code: e.code, fd })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function fdatasyncSync(fd) {
  const req = FileRequest.borrow()

  try {
    binding.fdatasyncSync(req.handle, fd)
  } catch (e) {
    throw new FileError(e.message, { operation: 'fdatasyncSync', code: e.code, fd })
  } finally {
    req.return()
  }
}

function normalizeSymlinkTarget(target, type, filepath) {
  if (isWindows) {
    if (type === constants.UV_FS_SYMLINK_JUNCTION) target = path.resolve(filepath, '..', target)

    if (path.isAbsolute(target)) return path.toNamespacedPath(target)

    return target.replace(/\//g, path.sep)
  }

  return target
}

async function symlink(target, filepath, type, cb) {
  if (typeof type === 'function') {
    cb = type
    type = null
  }

  filepath = toNamespacedPath(filepath)

  if (typeof type === 'string') {
    switch (type) {
      case 'dir':
        type = constants.UV_FS_SYMLINK_DIR
        break
      case 'junction':
        type = constants.UV_FS_SYMLINK_JUNCTION
        break
      case 'file':
      default:
        type = 0
        break
    }
  } else if (typeof type !== 'number') {
    if (isWindows) {
      target = path.resolve(filepath, '..', target)

      try {
        type = (await stat(target)).isDirectory()
          ? constants.UV_FS_SYMLINK_DIR
          : constants.UV_FS_SYMLINK_JUNCTION
      } catch {
        type = 0
      }
    } else {
      type = 0
    }
  }

  target = normalizeSymlinkTarget(target, type, filepath)

  const req = FileRequest.borrow()

  let err = null
  try {
    binding.symlink(req.handle, target, filepath, type)

    await req
  } catch (e) {
    err = new FileError(e.message, {
      operation: 'symlink',
      code: e.code,
      path: target,
      destination: filepath
    })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function symlinkSync(target, filepath, type) {
  filepath = toNamespacedPath(filepath)

  if (typeof type === 'string') {
    switch (type) {
      case 'dir':
        type = constants.UV_FS_SYMLINK_DIR
        break
      case 'junction':
        type = constants.UV_FS_SYMLINK_JUNCTION
        break
      case 'file':
      default:
        type = 0
        break
    }
  } else if (typeof type !== 'number') {
    if (isWindows) {
      target = path.resolve(filepath, '..', target)

      try {
        type = statSync(target).isDirectory()
          ? constants.UV_FS_SYMLINK_DIR
          : constants.UV_FS_SYMLINK_JUNCTION
      } catch {
        type = 0
      }
    } else {
      type = 0
    }
  }

  target = normalizeSymlinkTarget(target, type, filepath)

  const req = FileRequest.borrow()

  try {
    binding.symlinkSync(req.handle, target, filepath, type)
  } catch (e) {
    throw new FileError(e.message, {
      operation: 'symlink',
      code: e.code,
      path: target,
      destination: filepath
    })
  } finally {
    req.return()
  }
}

async function opendir(filepath, opts, cb) {
  if (typeof opts === 'function') {
    cb = opts
    opts = {}
  }

  if (typeof opts === 'string') opts = { encoding: opts }
  else if (!opts) opts = {}

  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  let dir
  let err = null
  try {
    binding.opendir(req.handle, filepath)

    await req

    dir = new Dir(filepath, binding.requestResultDir(req.handle), opts)
  } catch (e) {
    err = new FileError(e.message, {
      operation: 'opendir',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }

  return done(err, dir, cb)
}

function opendirSync(filepath, opts) {
  if (typeof opts === 'string') opts = { encoding: opts }
  else if (!opts) opts = {}

  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  try {
    binding.opendirSync(req.handle, filepath)

    return new Dir(filepath, binding.requestResultDir(req.handle), opts)
  } catch (e) {
    throw new FileError(e.message, {
      operation: 'opendir',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }
}

async function readdir(filepath, opts, cb) {
  if (typeof opts === 'function') {
    cb = opts
    opts = {}
  }

  if (typeof opts === 'string') opts = { encoding: opts }
  else if (!opts) opts = {}

  const { withFileTypes = false, recursive = false } = opts

  filepath = toNamespacedPath(filepath)

  const queue = [filepath]
  let result = []
  let err = null
  try {
    while (queue.length !== 0) {
      const dir = await opendir(queue.pop())

      for await (const entry of dir) {
        const entryPath = path.join(entry.parentPath, entry.name)

        if (withFileTypes) {
          result.push(entry)
        } else {
          result.push(path.relative(filepath, entryPath))
        }

        if (recursive && entry.isDirectory()) queue.push(entryPath)
      }
    }
  } catch (e) {
    result = []
    err = e
  }

  return done(err, result, cb)
}

function readdirSync(filepath, opts) {
  if (typeof opts === 'string') opts = { encoding: opts }
  else if (!opts) opts = {}

  const { withFileTypes = false, recursive = false } = opts

  filepath = toNamespacedPath(filepath)

  const queue = [filepath]
  const result = []

  while (queue.length !== 0) {
    const dir = opendirSync(queue.pop(), opts)

    for (const entry of dir) {
      const entryPath = path.join(entry.parentPath, entry.name)

      if (withFileTypes) {
        result.push(entry)
      } else {
        result.push(path.relative(filepath, entryPath))
      }

      if (recursive && entry.isDirectory()) queue.push(entryPath)
    }
  }

  return result
}

async function readFile(filepath, opts, cb) {
  if (typeof opts === 'function') {
    cb = opts
    opts = {}
  }

  if (typeof opts === 'string') opts = { encoding: opts }
  else if (!opts) opts = {}

  const { encoding = 'buffer' } = opts

  let fd = -1
  let buffer = null
  let err = null
  try {
    fd = await open(filepath, opts.flag || 'r')

    const st = await fstat(fd)

    let len = 0

    if (st.size === 0) {
      const buffers = []

      while (true) {
        buffer = Buffer.allocUnsafe(8192)
        const r = await read(fd, buffer)
        len += r
        if (r === 0) break
        buffers.push(buffer.subarray(0, r))
      }

      buffer = Buffer.concat(buffers)
    } else {
      buffer = Buffer.allocUnsafe(st.size)

      while (true) {
        const r = await read(fd, len ? buffer.subarray(len) : buffer)
        len += r
        if (r === 0 || len === buffer.byteLength) break
      }

      if (len !== buffer.byteLength) buffer = buffer.subarray(0, len)
    }

    if (encoding !== 'buffer') buffer = buffer.toString(encoding)
  } catch (e) {
    err = e
  } finally {
    if (fd !== -1) await close(fd)
  }

  return done(err, buffer, cb)
}

function readFileSync(filepath, opts) {
  if (typeof opts === 'string') opts = { encoding: opts }
  else if (!opts) opts = {}

  const { encoding = 'buffer' } = opts

  let fd = -1
  try {
    fd = openSync(filepath, opts.flag || 'r')

    const st = fstatSync(fd)

    let buffer
    let len = 0

    if (st.size === 0) {
      const buffers = []

      while (true) {
        buffer = Buffer.allocUnsafe(8192)
        const r = readSync(fd, buffer)
        len += r
        if (r === 0) break
        buffers.push(buffer.subarray(0, r))
      }

      buffer = Buffer.concat(buffers)
    } else {
      buffer = Buffer.allocUnsafe(st.size)

      while (true) {
        const r = readSync(fd, len ? buffer.subarray(len) : buffer)
        len += r
        if (r === 0 || len === buffer.byteLength) break
      }

      if (len !== buffer.byteLength) buffer = buffer.subarray(0, len)
    }

    if (encoding !== 'buffer') buffer = buffer.toString(encoding)

    return buffer
  } finally {
    if (fd !== -1) closeSync(fd)
  }
}

async function writeFile(filepath, data, opts, cb) {
  if (typeof opts === 'function') {
    cb = opts
    opts = {}
  }

  if (typeof opts === 'string') opts = { encoding: opts }
  else if (!opts) opts = {}

  if (typeof data === 'string') data = Buffer.from(data, opts.encoding)

  let fd = -1
  let len = 0
  let err = null
  try {
    fd = await open(filepath, opts.flag || 'w', opts.mode || 0o666)

    while (true) {
      len += await write(fd, len ? data.subarray(len) : data)
      if (len === data.byteLength) break
    }
  } catch (e) {
    err = e
  } finally {
    if (fd !== -1) await close(fd)
  }

  return done(err, len, cb)
}

function writeFileSync(filepath, data, opts) {
  if (typeof opts === 'string') opts = { encoding: opts }
  else if (!opts) opts = {}

  if (typeof data === 'string') data = Buffer.from(data, opts.encoding)

  let fd = -1
  try {
    fd = openSync(filepath, opts.flag || 'w', opts.mode || 0o666)

    let len = 0

    while (true) {
      len += writeSync(fd, len ? data.subarray(len) : data)
      if (len === data.byteLength) break
    }
  } finally {
    if (fd !== -1) closeSync(fd)
  }
}

function appendFile(filepath, data, opts, cb) {
  if (typeof opts === 'function') {
    cb = opts
    opts = {}
  }

  if (typeof opts === 'string') opts = { encoding: opts }
  else if (!opts) opts = {}

  if (!opts.flag) opts = { ...opts, flag: 'a' }

  return writeFile(filepath, data, opts, cb)
}

function appendFileSync(filepath, data, opts) {
  if (typeof opts === 'string') opts = { encoding: opts }
  else if (!opts) opts = {}

  if (!opts.flag) opts = { ...opts, flag: 'a' }

  return writeFileSync(filepath, data, opts)
}

function watch(filepath, opts, cb) {
  if (typeof opts === 'function') {
    cb = opts
    opts = {}
  }

  if (typeof opts === 'string') opts = { encoding: opts }
  else if (!opts) opts = {}

  filepath = toNamespacedPath(filepath)

  return new Watcher(filepath, opts, cb)
}

class Stats {
  constructor(
    dev,
    mode,
    nlink,
    uid,
    gid,
    rdev,
    blksize,
    ino,
    size,
    blocks,
    atimeMs,
    mtimeMs,
    ctimeMs,
    birthtimeMs
  ) {
    this.dev = dev
    this.mode = mode
    this.nlink = nlink
    this.uid = uid
    this.gid = gid
    this.rdev = rdev
    this.blksize = blksize
    this.ino = ino
    this.size = size
    this.blocks = blocks
    this.atimeMs = atimeMs
    this.mtimeMs = mtimeMs
    this.ctimeMs = ctimeMs
    this.birthtimeMs = birthtimeMs
    this.atime = new Date(atimeMs)
    this.mtime = new Date(mtimeMs)
    this.ctime = new Date(ctimeMs)
    this.birthtime = new Date(birthtimeMs)
  }

  isDirectory() {
    return (this.mode & constants.S_IFMT) === constants.S_IFDIR
  }

  isFile() {
    return (this.mode & constants.S_IFMT) === constants.S_IFREG
  }

  isBlockDevice() {
    return (this.mode & constants.S_IFMT) === constants.S_IFBLK
  }

  isCharacterDevice() {
    return (this.mode & constants.S_IFMT) === constants.S_IFCHR
  }

  isFIFO() {
    return (this.mode & constants.S_IFMT) === constants.S_IFIFO
  }

  isSymbolicLink() {
    return (this.mode & constants.S_IFMT) === constants.S_IFLNK
  }

  isSocket() {
    return (this.mode & constants.S_IFMT) === constants.S_IFSOCK
  }
}

class StatFs {
  constructor(type, bsize, blocks, bfree, bavail, files, ffree) {
    this.type = type
    this.bsize = bsize
    this.blocks = blocks
    this.bfree = bfree
    this.bavail = bavail
    this.files = files
    this.ffree = ffree
  }
}

class Dir {
  constructor(path, handle, opts = {}) {
    const { encoding = 'utf8', bufferSize = 32 } = opts

    this.path = path

    this._encoding = encoding
    this._capacity = bufferSize
    this._buffer = new FIFO()
    this._ended = false
    this._handle = handle
  }

  async read(cb) {
    if (this._buffer.length) return ok(this._buffer.shift(), cb)
    if (this._ended) return ok(null, cb)

    const req = FileRequest.borrow()

    let entries
    let err = null
    try {
      req.retain(binding.readdir(req.handle, this._handle, this._capacity))

      await req

      entries = binding.requestResultDirents(req.handle)
    } catch (e) {
      err = new FileError(e.message, {
        operation: 'readdir',
        code: e.code,
        path: this.path
      })
    } finally {
      req.return()
    }

    if (err) return fail(err, cb)

    if (entries.length === 0) {
      this._ended = true

      return ok(null, cb)
    }

    for (const entry of entries) {
      let name = Buffer.from(entry.name)

      if (this._encoding !== 'buffer') name = name.toString(this._encoding)

      this._buffer.push(new Dirent(this.path, name, entry.type))
    }

    return ok(this._buffer.shift(), cb)
  }

  readSync() {
    if (this._buffer.length) return this._buffer.shift()
    if (this._ended) return null

    const req = FileRequest.borrow()

    let entries
    try {
      req.retain(binding.readdirSync(req.handle, this._handle, this._capacity))

      entries = binding.requestResultDirents(req.handle)
    } catch (e) {
      throw new FileError(e.message, {
        operation: 'readdir',
        code: e.code,
        path: this.path
      })
    } finally {
      req.return()
    }

    if (entries.length === 0) {
      this._ended = true

      return null
    }

    for (const entry of entries) {
      let name = Buffer.from(entry.name)

      if (this._encoding !== 'buffer') name = name.toString(this._encoding)

      this._buffer.push(new Dirent(this.path, name, entry.type))
    }

    return this._buffer.shift()
  }

  async close(cb) {
    const req = FileRequest.borrow()

    let err = null
    try {
      binding.closedir(req.handle, this._handle)

      await req
    } catch (e) {
      err = new FileError(e.message, {
        operation: 'closedir',
        code: e.code,
        path: this.path
      })
    } finally {
      req.return()
    }

    this._handle = null

    return done(err, cb)
  }

  closeSync() {
    const req = FileRequest.borrow()

    try {
      binding.closedirSync(req.handle, this._handle)
    } catch (e) {
      throw new FileError(e.message, {
        operation: 'closedir',
        code: e.code,
        path: this.path
      })
    } finally {
      req.return()
    }

    this._handle = null
  }

  [Symbol.dispose]() {
    this.closeSync()
  }

  async [Symbol.asyncDispose]() {
    await this.close()
  }

  *[Symbol.iterator]() {
    while (true) {
      const entry = this.readSync()
      if (entry === null) break
      yield entry
    }

    this.closeSync()
  }

  async *[Symbol.asyncIterator]() {
    while (true) {
      const entry = await this.read()
      if (entry === null) break
      yield entry
    }

    await this.close()
  }
}

class Dirent {
  constructor(parentPath, name, type) {
    this.parentPath = parentPath
    this.name = name
    this.type = type
  }

  isFile() {
    return this.type === constants.UV_DIRENT_FILE
  }

  isDirectory() {
    return this.type === constants.UV_DIRENT_DIR
  }

  isSymbolicLink() {
    return this.type === constants.UV_DIRENT_LINK
  }

  isFIFO() {
    return this.type === constants.UV_DIRENT_FIFO
  }

  isSocket() {
    return this.type === constants.UV_DIRENT_SOCKET
  }

  isCharacterDevice() {
    return this.type === constants.UV_DIRENT_CHAR
  }

  isBlockDevice() {
    return this.type === constants.UV_DIRENT_BLOCK
  }
}

class FileReadStream extends Readable {
  constructor(path, opts = {}) {
    const { eagerOpen = true } = opts

    super({ eagerOpen, ...opts })

    this.path = path
    this.fd = typeof opts.fd === 'number' ? opts.fd : -1
    this.flags = opts.flags || 'r'
    this.mode = opts.mode || 0o666

    this._offset = opts.start || 0
    this._missing = 0

    if (opts.length) {
      this._missing = opts.length
    } else if (typeof opts.end === 'number') {
      this._missing = opts.end - this._offset + 1
    } else {
      this._missing = -1
    }
  }

  async _open(cb) {
    let err

    if (this.fd === -1) {
      err = null
      try {
        this.fd = await open(this.path, this.flags, this.mode)
      } catch (e) {
        err = e
      }

      if (err) return cb(err)
    }

    let st
    err = null
    try {
      st = await fstat(this.fd)
    } catch (e) {
      err = e
    }

    if (err) return cb(err)

    if (this._missing === -1) this._missing = st.size

    if (st.size < this._offset) {
      this._offset = st.size
      this._missing = 0
    } else if (st.size < this._offset + this._missing) {
      this._missing = st.size - this._offset
    }

    cb(null)
  }

  async _read(size) {
    if (this._missing <= 0) return this.push(null)

    const data = Buffer.allocUnsafe(Math.min(this._missing, size))

    let len
    let err = null
    try {
      len = await read(this.fd, data, 0, data.byteLength, this._offset)
    } catch (e) {
      err = e
    }

    if (err) return this.destroy(err)

    if (len === 0) return this.push(null)

    if (this._missing < len) len = this._missing

    this._missing -= len
    this._offset += len

    this.push(data.subarray(0, len))
  }

  async _destroy(err, cb) {
    if (this.fd === -1) return cb(err)

    try {
      await close(this.fd)
    } catch (e) {
      err = err || e
    }

    cb(err)
  }
}

class FileWriteStream extends Writable {
  constructor(path, opts = {}) {
    const { eagerOpen = true } = opts

    super({ eagerOpen, ...opts })

    this.path = path
    this.fd = typeof opts.fd === 'number' ? opts.fd : -1
    this.flags = opts.flags || 'w'
    this.mode = opts.mode || 0o666
  }

  async _open(cb) {
    if (this.fd !== -1) return cb(null)

    let err = null
    try {
      this.fd = await open(this.path, this.flags, this.mode)
    } catch (e) {
      err = e
    }

    cb(err)
  }

  async _writev(batch, cb) {
    let err = null
    try {
      await writev(
        this.fd,
        batch.map(({ chunk }) => chunk)
      )
    } catch (e) {
      err = e
    }

    cb(err)
  }

  async _destroy(err, cb) {
    if (this.fd === -1) return cb(err)

    try {
      await close(this.fd)
    } catch (e) {
      err = err || e
    }

    cb(err)
  }
}

class Watcher extends EventEmitter {
  constructor(path, opts, onchange) {
    if (typeof opts === 'function') {
      onchange = opts
      opts = {}
    }

    if (!opts) opts = {}

    const { persistent = true, recursive = false, encoding = 'utf8' } = opts

    super()

    this._closed = false
    this._encoding = encoding
    this._handle = binding.watcherInit(path, recursive, this, this._onevent, this._onclose)

    if (!persistent) this.unref()

    if (onchange) this.on('change', onchange)
  }

  close() {
    if (this._closed) return
    this._closed = true

    binding.watcherClose(this._handle)
  }

  ref() {
    if (this._handle) binding.watcherRef(this._handle)
    return this
  }

  unref() {
    if (this._handle) binding.watcherUnref(this._handle)
    return this
  }

  [Symbol.asyncIterator]() {
    const buffer = []
    let done = false
    let error = null
    let next = null

    this.on('change', (eventType, filename) => {
      if (next) {
        next.resolve({ done: false, value: { eventType, filename } })
        next = null
      } else {
        buffer.push({ eventType, filename })
      }
    })
      .on('error', (err) => {
        done = true
        error = err

        if (next) {
          next.reject(error)
          next = null
        }
      })
      .on('close', () => {
        done = true

        if (next) {
          next.resolve({ done })
          next = null
        }
      })

    return {
      next: () =>
        new Promise((resolve, reject) => {
          if (error) return reject(error)

          if (buffer.length) return resolve({ done: false, value: buffer.shift() })

          if (done) return resolve({ done })

          next = { resolve, reject }
        })
    }
  }

  _onevent(err, events, filename) {
    if (err) {
      this.close()
      this.emit('error', err)
    } else {
      const path =
        this._encoding === 'buffer'
          ? Buffer.from(filename)
          : Buffer.from(filename).toString(this._encoding)

      if (events & binding.UV_RENAME) {
        this.emit('change', 'rename', path)
      }

      if (events & binding.UV_CHANGE) {
        this.emit('change', 'change', path)
      }
    }
  }

  _onclose() {
    this._handle = null

    this.emit('close')
  }
}

exports.access = access
exports.appendFile = appendFile
exports.chmod = chmod
exports.chown = chown
exports.close = close
exports.copyFile = copyFile
exports.cp = cp
exports.exists = exists
exports.fchmod = fchmod
exports.fchown = fchown
exports.fdatasync = fdatasync
exports.fstat = fstat
exports.fsync = fsync
exports.ftruncate = ftruncate
exports.futimes = futimes
exports.lchown = lchown
exports.lutimes = lutimes
exports.link = link
exports.lstat = lstat
exports.mkdir = mkdir
exports.mkdtemp = mkdtemp
exports.open = open
exports.opendir = opendir
exports.read = read
exports.readFile = readFile
exports.readdir = readdir
exports.readlink = readlink
exports.readv = readv
exports.realpath = realpath
exports.rename = rename
exports.rm = rm
exports.rmdir = rmdir
exports.stat = stat
exports.statfs = statfs
exports.symlink = symlink
exports.truncate = truncate
exports.unlink = unlink
exports.utimes = utimes
exports.watch = watch
exports.write = write
exports.writeFile = writeFile
exports.writev = writev

exports.accessSync = accessSync
exports.appendFileSync = appendFileSync
exports.chmodSync = chmodSync
exports.chownSync = chownSync
exports.closeSync = closeSync
exports.copyFileSync = copyFileSync
exports.cpSync = cpSync
exports.existsSync = existsSync
exports.fchmodSync = fchmodSync
exports.fchownSync = fchownSync
exports.fdatasyncSync = fdatasyncSync
exports.fstatSync = fstatSync
exports.fsyncSync = fsyncSync
exports.ftruncateSync = ftruncateSync
exports.futimesSync = futimesSync
exports.lchownSync = lchownSync
exports.lutimesSync = lutimesSync
exports.linkSync = linkSync
exports.lstatSync = lstatSync
exports.mkdirSync = mkdirSync
exports.mkdtempSync = mkdtempSync
exports.openSync = openSync
exports.opendirSync = opendirSync
exports.readFileSync = readFileSync
exports.readSync = readSync
exports.readdirSync = readdirSync
exports.readlinkSync = readlinkSync
exports.readvSync = readvSync
exports.realpathSync = realpathSync
exports.renameSync = renameSync
exports.rmSync = rmSync
exports.rmdirSync = rmdirSync
exports.statSync = statSync
exports.statfsSync = statfsSync
exports.symlinkSync = symlinkSync
exports.truncateSync = truncateSync
exports.unlinkSync = unlinkSync
exports.utimesSync = utimesSync
exports.writeFileSync = writeFileSync
exports.writeSync = writeSync
exports.writevSync = writevSync

exports.promises = require('./promises')

exports.Stats = Stats
exports.StatFs = StatFs
exports.Dir = Dir
exports.Dirent = Dirent
exports.Watcher = Watcher

exports.ReadStream = FileReadStream

exports.createReadStream = function createReadStream(path, opts) {
  return new FileReadStream(path, opts)
}

exports.WriteStream = FileWriteStream

exports.createWriteStream = function createWriteStream(path, opts) {
  return new FileWriteStream(path, opts)
}

function toNamespacedPath(filepath) {
  if (typeof filepath !== 'string') {
    if (isURL(filepath)) filepath = fileURLToPath(filepath)
    else filepath = filepath.toString()
  }

  return path.toNamespacedPath(filepath)
}

function toFlags(flags) {
  switch (flags) {
    case 'r':
      return constants.O_RDONLY
    case 'rs': // Fall through.
    case 'sr':
      return constants.O_RDONLY | constants.O_SYNC
    case 'r+':
      return constants.O_RDWR
    case 'rs+': // Fall through.
    case 'sr+':
      return constants.O_RDWR | constants.O_SYNC

    case 'w':
      return constants.O_TRUNC | constants.O_CREAT | constants.O_WRONLY
    case 'wx': // Fall through.
    case 'xw':
      return constants.O_TRUNC | constants.O_CREAT | constants.O_WRONLY | constants.O_EXCL

    case 'w+':
      return constants.O_TRUNC | constants.O_CREAT | constants.O_RDWR
    case 'wx+': // Fall through.
    case 'xw+':
      return constants.O_TRUNC | constants.O_CREAT | constants.O_RDWR | constants.O_EXCL

    case 'a':
      return constants.O_APPEND | constants.O_CREAT | constants.O_WRONLY
    case 'ax': // Fall through.
    case 'xa':
      return constants.O_APPEND | constants.O_CREAT | constants.O_WRONLY | constants.O_EXCL
    case 'as': // Fall through.
    case 'sa':
      return constants.O_APPEND | constants.O_CREAT | constants.O_WRONLY | constants.O_SYNC

    case 'a+':
      return constants.O_APPEND | constants.O_CREAT | constants.O_RDWR
    case 'ax+': // Fall through.
    case 'xa+':
      return constants.O_APPEND | constants.O_CREAT | constants.O_RDWR | constants.O_EXCL
    case 'as+': // Fall through.
    case 'sa+':
      return constants.O_APPEND | constants.O_CREAT | constants.O_RDWR | constants.O_SYNC
    default:
      return 0
  }
}

function toMode(mode) {
  return parseInt(mode, 8)
}
const binding = require('../binding')

module.exports = {
  O_RDWR: binding.O_RDWR,
  O_RDONLY: binding.O_RDONLY,
  O_WRONLY: binding.O_WRONLY,
  O_CREAT: binding.O_CREAT,
  O_TRUNC: binding.O_TRUNC,
  O_APPEND: binding.O_APPEND,

  F_OK: binding.F_OK || 0,
  R_OK: binding.R_OK || 0,
  W_OK: binding.W_OK || 0,
  X_OK: binding.X_OK || 0,

  S_IFMT: binding.S_IFMT,
  S_IFREG: binding.S_IFREG,
  S_IFDIR: binding.S_IFDIR,
  S_IFCHR: binding.S_IFCHR,
  S_IFLNK: binding.S_IFLNK,
  S_IFBLK: binding.S_IFBLK || 0,
  S_IFIFO: binding.S_IFIFO || 0,
  S_IFSOCK: binding.S_IFSOCK || 0,

  S_IRUSR: binding.S_IRUSR || 0,
  S_IWUSR: binding.S_IWUSR || 0,
  S_IXUSR: binding.S_IXUSR || 0,
  S_IRGRP: binding.S_IRGRP || 0,
  S_IWGRP: binding.S_IWGRP || 0,
  S_IXGRP: binding.S_IXGRP || 0,
  S_IROTH: binding.S_IROTH || 0,
  S_IWOTH: binding.S_IWOTH || 0,
  S_IXOTH: binding.S_IXOTH || 0,

  UV_DIRENT_UNKNOWN: binding.UV_DIRENT_UNKNOWN,
  UV_DIRENT_FILE: binding.UV_DIRENT_FILE,
  UV_DIRENT_DIR: binding.UV_DIRENT_DIR,
  UV_DIRENT_LINK: binding.UV_DIRENT_LINK,
  UV_DIRENT_FIFO: binding.UV_DIRENT_FIFO,
  UV_DIRENT_SOCKET: binding.UV_DIRENT_SOCKET,
  UV_DIRENT_CHAR: binding.UV_DIRENT_CHAR,
  UV_DIRENT_BLOCK: binding.UV_DIRENT_BLOCK,

  COPYFILE_EXCL: binding.UV_FS_COPYFILE_EXCL,
  COPYFILE_FICLONE: binding.UV_FS_COPYFILE_FICLONE,
  COPYFILE_FICLONE_FORCE: binding.UV_FS_COPYFILE_FICLONE_FORCE,
  UV_FS_SYMLINK_DIR: binding.UV_FS_SYMLINK_DIR,
  UV_FS_SYMLINK_JUNCTION: binding.UV_FS_SYMLINK_JUNCTION
}
const os = require('bare-os')

module.exports = class FileError extends Error {
  constructor(msg, opts = {}) {
    const { code, operation = null, path = null, destination = null, fd = -1 } = opts

    if (operation !== null) msg += describe(operation, opts)

    super(`${code}: ${msg}`)

    this.code = code

    if (operation !== null) this.operation = operation
    if (path !== null) this.path = path
    if (destination !== null) this.destination = destination
    if (fd !== -1) this.fd = fd
  }

  get name() {
    return 'FileError'
  }

  // For Node.js compatibility
  get errno() {
    return os.constants.errnos[this.code]
  }

  // For Node.js compatibility
  get syscall() {
    return this.operation
  }

  // For Node.js compatibility
  get dest() {
    return this.destination
  }
}

function describe(operation, opts) {
  const { path = null, destination = null, fd = -1 } = opts

  let result = `, ${operation}`

  if (path !== null) {
    result += ` ${JSON.stringify(path)}`

    if (destination !== null) {
      result += ` -> ${JSON.stringify(destination)}`
    }
  } else if (fd !== -1) {
    result += ` ${fd}`
  }

  return result
}
{
  "name": "bare-fs",
  "version": "4.7.2",
  "description": "Native file system operations for Bare",
  "exports": {
    "./package": "./package.json",
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    },
    "./promises": {
      "types": "./promises.d.ts",
      "default": "./promises.js"
    },
    "./constants": {
      "types": "./lib/constants.d.ts",
      "default": "./lib/constants.js"
    }
  },
  "files": [
    "index.js",
    "index.d.ts",
    "promises.js",
    "promises.d.ts",
    "binding.c",
    "binding.js",
    "CMakeLists.txt",
    "lib",
    "prebuilds"
  ],
  "addon": true,
  "scripts": {
    "format": "prettier --write . && lunte --fix",
    "lint": "prettier --check . && lunte",
    "test": "brittle-bare --coverage test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-fs.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-fs/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-fs#readme",
  "engines": {
    "bare": ">=1.16.0"
  },
  "dependencies": {
    "bare-events": "^2.5.4",
    "bare-path": "^3.0.0",
    "bare-stream": "^2.6.4",
    "bare-url": "^2.2.2",
    "fast-fifo": "^1.3.2"
  },
  "devDependencies": {
    "bare-buffer": "^3.0.2",
    "bare-crypto": "^1.11.2",
    "brittle": "^3.1.1",
    "cmake-bare": "^1.1.7",
    "lunte": "^1.6.0",
    "prettier": "^3.4.1",
    "prettier-config-holepunch": "^2.0.0"
  },
  "peerDependencies": {
    "bare-buffer": "*"
  },
  "peerDependenciesMeta": {
    "bare-buffer": {
      "optional": true
    }
  }
}
const EventEmitter = require('bare-events')
const fs = require('.')

class FileHandle extends EventEmitter {
  constructor(fd) {
    super()

    this.fd = fd
  }

  async close() {
    await fs.close(this.fd)

    this.fd = -1
    this.emit('close')
  }

  async read(buffer, ...args) {
    return {
      bytesRead: await fs.read(this.fd, buffer, ...args),
      buffer
    }
  }

  async readv(buffers, ...args) {
    return {
      bytesRead: await fs.readv(this.fd, buffers, ...args),
      buffers
    }
  }

  async write(buffer, ...args) {
    return {
      bytesWritten: await fs.write(this.fd, buffer, ...args),
      buffer
    }
  }

  async writev(buffers, ...args) {
    return {
      bytesWritten: await fs.writev(this.fd, buffers, ...args),
      buffers
    }
  }

  async stat() {
    return fs.fstat(this.fd)
  }

  async chmod(mode) {
    await fs.fchmod(this.fd, mode)
  }

  async chown(uid, gid) {
    await fs.fchown(this.fd, uid, gid)
  }

  async datasync() {
    return fs.fdatasync(this.fd)
  }

  async sync() {
    return fs.fsync(this.fd)
  }

  async truncate(len) {
    await fs.ftruncate(this.fd, len)
  }

  async utimes(atime, mtime) {
    await fs.futimes(this.fd, atime, mtime)
  }

  createReadStream(opts) {
    return fs.createReadStream(null, { ...opts, fd: this.fd })
  }

  createWriteStream(opts) {
    return fs.createWriteStream(null, { ...opts, fd: this.fd })
  }

  async [Symbol.asyncDispose]() {
    await this.close()
  }
}

exports.open = async function open(filepath, flags, mode) {
  return new FileHandle(await fs.open(filepath, flags, mode))
}

exports.access = fs.access
exports.appendFile = fs.appendFile
exports.chmod = fs.chmod
exports.chown = fs.chown
exports.constants = fs.constants
exports.copyFile = fs.copyFile
exports.cp = fs.cp
exports.lchown = fs.lchown
exports.lutimes = fs.lutimes
exports.link = fs.link
exports.lstat = fs.lstat
exports.mkdir = fs.mkdir
exports.mkdtemp = fs.mkdtemp
exports.opendir = fs.opendir
exports.readFile = fs.readFile
exports.readdir = fs.readdir
exports.readlink = fs.readlink
exports.realpath = fs.realpath
exports.rename = fs.rename
exports.rm = fs.rm
exports.rmdir = fs.rmdir
exports.stat = fs.stat
exports.statfs = fs.statfs
exports.truncate = fs.truncate
exports.symlink = fs.symlink
exports.unlink = fs.unlink
exports.utimes = fs.utimes
exports.watch = fs.watch
exports.writeFile = fs.writeFile
module.exports = require.addon()
const binding = require('./binding')

module.exports = exports = function hrtime(past) {
  let now = binding.hrtime()

  if (past) now -= BigInt(past[0]) * 1000000000n + BigInt(past[1])

  return [Number(now / 1000000000n), Number(now % 1000000000n)]
}

exports.bigint = function hrtime() {
  return binding.hrtime()
}
{
  "name": "bare-hrtime",
  "version": "2.1.1",
  "description": "High-resolution timers for JavaScript",
  "exports": {
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    },
    "./package": "./package.json"
  },
  "files": [
    "index.js",
    "index.d.ts",
    "binding.c",
    "binding.js",
    "CMakeLists.txt",
    "prebuilds"
  ],
  "addon": true,
  "scripts": {
    "test": "prettier . --check && bare test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-hrtime.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-hrtime/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-hrtime#readme",
  "devDependencies": {
    "brittle": "^3.2.1",
    "cmake-bare": "^1.1.6",
    "prettier": "^3.4.2",
    "prettier-config-standard": "^7.0.0"
  }
}
module.exports = require.addon()
const ansiEscapes = require('bare-ansi-escapes')
const getType = require('bare-type')
const binding = require('./binding')

const PLAIN_KEY = /^[a-zA-Z_][a-zA-Z_0-9]*$/

const defaultDepth = 2
const defaultBreakLength = 80
const defaultMaxArrayLength = 40

module.exports = exports = function inspect(value, opts = {}) {
  const {
    colors = false,
    depth = defaultDepth,
    breakLength = defaultBreakLength,
    stylize = defaultStylize(colors)
  } = opts

  const references = new InspectRefMap()

  const tree = inspectValue(value, 0, {
    colors,
    depth,
    breakLength,
    stylize,
    references
  })

  return tree.toString()
}

exports.styles = {
  bigint: ansiEscapes.colorYellow,
  boolean: ansiEscapes.colorYellow,
  date: ansiEscapes.colorMagenta,
  module: ansiEscapes.modifierUnderline,
  name: ansiEscapes.modifierReset,
  null: ansiEscapes.modifierBold,
  number: ansiEscapes.colorYellow,
  regexp: ansiEscapes.colorRed,
  special: ansiEscapes.colorCyan,
  string: ansiEscapes.colorGreen,
  symbol: ansiEscapes.colorGreen,
  undefined: ansiEscapes.colorBrightBlack
}

const styles = exports.styles

function defaultStylize(colors) {
  return function stylize(value, style) {
    const color = colors && styles[style]

    if (color) return color + value + ansiEscapes.modifierReset

    return value
  }
}

class InspectRefMap {
  constructor() {
    this.refs = new WeakMap()
    this.ids = new WeakMap()
    this.nextId = 1
  }

  has(object) {
    return this.refs.has(object)
  }

  get(object) {
    return this.refs.get(object) || null
  }

  set(object, ref) {
    this.refs.set(object, ref)
  }

  id(object) {
    let id = this.ids.get(object)
    if (id) return id

    id = this.nextId++
    this.ids.set(object, id)

    return id
  }
}

class InspectNode {
  constructor(depth, length, opts) {
    const { breakLength = defaultBreakLength, breakAlways = false } = opts

    this.depth = depth
    this.length = length
    this.breakLength = breakLength
    this.breakAlways = breakAlways
  }

  pad(n, string) {
    return string.padStart(n, ' ')
  }

  indent(n, string) {
    return '  '.repeat(n) + string
  }
}

class InspectRef extends InspectNode {
  constructor(depth, opts) {
    super(depth, '[circular *]'.length, opts)

    this.refs = opts.references
    this.count = 0
    this.circular = false
    this.color = opts.colors && styles.special
  }

  get id() {
    return this.refs.id(this)
  }

  increment() {
    return ++this.count
  }

  decrement() {
    return --this.count
  }

  toString(opts = {}) {
    const { offset = 0, pad = 0, indent = 0 } = opts

    let value = this.pad(pad, '[circular *' + this.id + ']')

    if (this.color) value = this.color + value + ansiEscapes.modifierReset

    return offset ? value : this.indent(indent, value)
  }
}

class InspectLeaf extends InspectNode {
  constructor(value, color, depth, opts) {
    const length = value.length

    if (value.includes('\n')) {
      value = value.replaceAll('\n', '\n' + '  '.repeat(depth))

      opts = { ...opts, breakAlways: true }
    }

    super(depth, length, opts)

    this.value = value
    this.color = opts.colors && color
  }

  toString(opts = {}) {
    const { offset = 0, pad = 0, indent = 0 } = opts

    let value = this.pad(pad, this.value)

    if (this.color) value = this.color + value + ansiEscapes.modifierReset

    return offset ? value : this.indent(indent, value)
  }
}

class InspectPair extends InspectNode {
  constructor(delim, left, right, depth, opts) {
    const length = left.length + delim.length + right.length

    if (left.breakAlways || right.breakAlways) {
      opts = { ...opts, breakAlways: true }
    }

    super(depth, length, opts)

    this.delim = delim
    this.left = left
    this.right = right
  }

  toString(opts = {}) {
    const { indent = 0 } = opts

    return this.indent(
      indent,
      this.left +
        this.delim +
        this.right.toString({
          indent,
          offset: this.left.length + this.delim.length
        })
    )
  }
}

class InspectSuspension extends InspectNode {
  constructor(overflow, depth, opts) {
    const label = `... ${overflow} more`

    super(depth, label.length, opts)

    this.label = label
  }

  toString(opts = {}) {
    const { indent = 0 } = opts

    return this.indent(indent, this.label)
  }
}

class InspectSequence extends InspectNode {
  constructor(header, footer, delim, values, ref, depth, opts) {
    const { tabulate = false } = opts

    const length =
      (ref.circular ? '<ref *>'.length + 1 : 0) +
      header.length +
      values.reduce(
        (length, value, i) =>
          length + value.length + (i === 0 ? 0 : delim.length),
        0
      ) +
      footer.length

    if (values.some((value) => value.breakAlways)) {
      opts = { ...opts, breakAlways: true }
    }

    super(depth, length, opts)

    this.header = header
    this.footer = footer
    this.delim = delim
    this.values = values
    this.ref = ref
    this.tabulate = tabulate
  }

  toString(opts = {}) {
    const { offset = 0, indent = 0 } = opts

    const split =
      this.breakAlways ||
      (this.values.length &&
        (offset + this.length > this.breakLength ||
          indent * 2 + this.length > this.breakLength))

    let header = this.header

    if (this.ref.circular) {
      header = '<ref *' + this.ref.id + '> ' + header
    }

    if (this.values.length === 0) {
      header = header.trimEnd()
    }

    if (offset === 0) {
      header = this.indent(indent, header)
    }

    if (split) {
      header = header.trimEnd() + '\n'
    }

    let string = header

    let columns = 1
    let pad = 0

    if (this.tabulate) {
      const widest = this.values.reduce(
        (length, value) =>
          value.breakAlways ? length : Math.max(length, value.length),
        0
      )

      if (widest) {
        columns = Math.max(
          columns,
          Math.floor(
            (this.breakLength - indent * 2) / (widest + this.delim.length)
          )
        )

        if (columns > 1) pad = widest
      }
    }

    for (let i = 0, n = this.values.length, offset = 0; i < n; i++) {
      const value = this.values[i]

      if (split) {
        let part

        if (i % columns === 0 || value.breakAlways) {
          part = value.toString({ indent: indent + 1, pad })
        } else {
          part = value.toString({ pad })
        }

        string += part

        if (i < n - 1) {
          if (i % columns === columns - 1 || this.values[i + 1].breakAlways) {
            string += this.delim.trimEnd() + '\n'
          } else {
            string += this.delim
          }
        }
      } else {
        if (i > 0) string += this.delim

        string += value.toString({ offset })

        offset += value.length
      }
    }

    let footer = this.footer

    if (this.values.length === 0) {
      footer = footer.trimStart()
    }

    if (split) {
      string += '\n' + this.indent(indent, footer.trimStart())
    } else {
      string += footer
    }

    return string
  }
}

function inspectValue(value, depth, opts) {
  const type = getType(value)

  if (type.isUndefined()) return inspectUndefined(depth, opts)
  if (type.isNull()) return inspectNull(depth, opts)
  if (type.isBoolean()) return inspectBoolean(value, depth, opts)
  if (type.isNumber()) return inspectNumber(value, depth, opts)
  if (type.isBigInt()) return inspectBigInt(value, depth, opts)
  if (type.isString()) return inspectString(value, depth, opts)
  if (type.isSymbol()) return inspectSymbol(value, depth, opts)
  if (type.isObject()) return inspectObject(type, value, depth, opts)
  if (type.isFunction()) return inspectFunction(type, value, depth, opts)
  if (type.isExternal()) return inspectExternal(value, opts, opts)
}

function inspectUndefined(depth, opts) {
  return new InspectLeaf('undefined', styles.undefined, depth, opts)
}

function inspectNull(depth, opts) {
  return new InspectLeaf('null', styles.null, depth, opts)
}

function inspectBoolean(value, depth, opts) {
  return new InspectLeaf(value.toString(), styles.boolean, depth, opts)
}

function inspectNumber(value, depth, opts) {
  let string

  if (Object.is(value, -0)) {
    string = '-0'
  } else {
    string = value.toString(10)
  }

  return new InspectLeaf(string, styles.number, depth, opts)
}

function inspectBigInt(value, depth, opts) {
  return new InspectLeaf(value.toString(10) + 'n', styles.bigint, depth, opts)
}

const STRING_ESCAPES =
  /[\ud800-\udbff][\udc00-\udfff]|[\u0000-\u001f'\\\ud800-\udfff]/g

function inspectString(value, depth, opts) {
  // https://tc39.es/ecma262/multipage/structured-data.html#sec-quotejsonstring
  const string = value.replace(STRING_ESCAPES, (match) => {
    if (match.length === 2) return match

    switch (match) {
      case "'":
        return "\\'"
      case '\\':
        return '\\\\'
      case '\b':
        return '\\b'
      case '\t':
        return '\\t'
      case '\n':
        return '\\n'
      case '\f':
        return '\\f'
      case '\r':
        return '\\r'
      default:
        return '\\u' + match.charCodeAt(0).toString(16).padStart(4, '0')
    }
  })

  return new InspectLeaf("'" + string + "'", styles.string, depth, opts)
}

function inspectSymbol(value, depth, opts) {
  return new InspectLeaf(value.toString(), styles.symbol, depth, opts)
}

function inspectKey(value, depth, opts) {
  if (PLAIN_KEY.test(value)) {
    return new InspectLeaf(value, null, depth, opts)
  } else {
    return inspectValue(value, depth, opts)
  }
}

function inspectObject(type, object, depth, opts) {
  const refs = opts.references

  let ref = refs.get(object)
  if (ref === null) {
    ref = new InspectRef(depth, opts)
    refs.set(object, ref)
  } else if (ref.count) {
    ref.circular = true
    return ref
  }

  const maxDepth = typeof opts.depth === 'number' ? opts.depth : Infinity

  if (maxDepth < depth) {
    const constructor = object.constructor

    return new InspectLeaf(
      '[' +
        (constructor && constructor.name ? constructor.name : 'Object') +
        ']',
      styles.special,
      depth,
      opts
    )
  }

  const inspect =
    object[Symbol.for('bare.inspect')] ||
    object[Symbol.for('nodejs.util.inspect.custom')]

  if (typeof inspect === 'function') {
    const value = inspect.call(
      object,
      typeof opts.depth === 'number' ? opts.depth - depth : null,
      {
        colors: opts.colors,
        breakLength: opts.breakLength,
        stylize: opts.stylize
      },
      exports
    )

    if (typeof value === 'object' && value !== null) {
      refs.set(value, ref)
    }

    if (typeof value !== 'string') {
      return inspectValue(value, depth, opts)
    }

    return new InspectLeaf(value, null, depth, opts)
  }

  if (type.isArray()) return inspectArray(object, ref, depth, opts)
  if (type.isDate()) return inspectDate(object, ref, depth, opts)
  if (type.isRegExp()) return inspectRegExp(object, ref, depth, opts)
  if (type.isError()) return inspectError(object, ref, depth, opts)
  if (type.isPromise()) return inspectPromise(object, ref, depth, opts)
  if (type.isMap()) return inspectMap(object, ref, depth, opts)
  if (type.isSet()) return inspectSet(object, ref, depth, opts)
  if (type.isWeakMap()) return inspectWeakMap(object, ref, depth, opts)
  if (type.isWeakSet()) return inspectWeakSet(object, ref, depth, opts)
  if (type.isWeakRef()) return inspectWeakRef(object, ref, depth, opts)
  if (type.isArrayBuffer()) return inspectArrayBuffer(object, ref, depth, opts)
  if (type.isSharedArrayBuffer())
    return inspectSharedArrayBuffer(object, ref, depth, opts)
  if (type.isTypedArray()) return inspectTypedArray(object, ref, depth, opts)
  if (type.isDataView()) return inspectDataView(object, ref, depth, opts)

  ref.increment()

  const values = []

  for (const key in object) {
    if (key === 'constructor') continue

    values.push(
      new InspectPair(
        ': ',
        inspectKey(key, depth + 1, opts),
        inspectValue(object[key], depth + 1, opts),
        depth + 1,
        opts
      )
    )
  }

  ref.decrement()

  let header = '{ '

  const tag = object[Symbol.toStringTag]

  if (tag) header = '[' + tag + '] ' + header

  if (object.constructor) {
    const name = object.constructor.name

    if (name && name !== 'Object') {
      header = object.constructor.name + ' ' + header
    }
  }

  return new InspectSequence(header, ' }', ', ', values, ref, depth, opts)
}

function inspectArray(array, ref, depth, opts) {
  const { maxArrayLength = defaultMaxArrayLength } = opts

  ref.increment()

  const values = []

  let remaining = Math.max(maxArrayLength, 0)

  for (let i = 0, n = array.length; i < n; i++) {
    if (remaining-- === 0) {
      values.push(
        new InspectSuspension(array.length - values.length, depth + 1, {
          ...opts,
          breakAlways: true
        })
      )

      break
    }

    values.push(inspectValue(array[i], depth + 1, opts))
  }

  for (const key of binding.getOwnNonIndexPropertyNames(array)) {
    values.push(
      new InspectPair(
        ': ',
        inspectKey(key, depth + 1, opts),
        inspectValue(array[key], depth + 1, opts),
        depth + 1,
        { ...opts, breakAlways: remaining < 0 }
      )
    )
  }

  ref.decrement()

  let header = '[ '

  if (array.constructor.name !== 'Array') {
    header = array.constructor.name + '(' + array.length + ') ' + header
  }

  return new InspectSequence(header, ' ]', ', ', values, ref, depth, {
    ...opts,
    tabulate: true
  })
}

function inspectDate(date, ref, depth, opts) {
  return new InspectLeaf(date.toISOString(), styles.date, depth, opts)
}

function inspectRegExp(regExp, ref, depth, opts) {
  return new InspectLeaf(regExp.toString(), styles.regexp, depth, opts)
}

function inspectError(error, ref, depth, opts) {
  let header

  if ('stack' in error) {
    header = error.stack

    if (depth > 0) {
      header = header.replaceAll('\n', '\n' + '  '.repeat(depth))
    }
  } else {
    header = error.toString()
  }

  const builtins = ['cause']

  if (error.name === 'AggregateError') {
    builtins.push('errors')
  } else if (error.name === 'SuppressedError') {
    builtins.push('error', 'suppressed')
  }

  const values = []

  for (const key of builtins) {
    if (key in error === false) continue

    values.push(
      new InspectPair(
        ': ',
        new InspectLeaf('[' + key + ']', null, depth + 1, opts),
        inspectValue(error[key], depth + 1, opts),
        depth + 1,
        opts
      )
    )
  }

  for (const key in error) {
    if (key === 'constructor' || builtins.includes(key)) continue

    values.push(
      new InspectPair(
        ': ',
        inspectKey(key, depth + 1, opts),
        inspectValue(error[key], depth + 1, opts),
        depth + 1,
        opts
      )
    )
  }

  if (values.length === 0) return new InspectLeaf(header, null, depth, opts)

  return new InspectSequence(
    header + ' {',
    ' }',
    ', ',
    values,
    ref,
    depth,
    opts
  )
}

function inspectPromise(promise, ref, depth, opts) {
  ref.increment()

  const state = binding.getPromiseState(promise)

  const values = []

  switch (state) {
    case 0: // Pending
      values.push(new InspectLeaf('<pending>', styles.special, depth, opts))
      break

    case 1: // Fulfilled
      values.push(inspectValue(binding.getPromiseResult(promise), depth, opts))
      break

    case 2: // Rejected
      values.push(
        new InspectLeaf('<rejected>', styles.special, depth, opts),
        inspectValue(binding.getPromiseResult(promise), depth, opts)
      )
  }

  ref.decrement()

  const header = promise.constructor.name + ' { '

  return new InspectSequence(header, ' }', ' ', values, ref, depth, opts)
}

function inspectMap(map, ref, depth, opts) {
  const {
    maxArrayLength = defaultMaxArrayLength,
    maxMapLength = maxArrayLength
  } = opts

  ref.increment()

  const values = []

  let remaining = maxMapLength

  for (const entry of map) {
    if (remaining-- === 0) {
      values.push(
        new InspectSuspension(map.size - values.length, depth + 1, {
          ...opts,
          breakAlways: true
        })
      )
      break
    }

    values.push(
      new InspectPair(
        ' => ',
        inspectValue(entry[0], depth + 1, opts),
        inspectValue(entry[1], depth + 1, opts),
        depth + 1,
        opts
      )
    )
  }

  for (const key in map) {
    if (key === 'constructor') continue

    const value = inspectValue(map[key], depth + 1, opts)

    values.push(
      new InspectPair(
        ': ',
        inspectKey(key, depth + 1, opts),
        value,
        depth + 1,
        { ...opts, breakAlways: remaining < 0 }
      )
    )
  }

  ref.decrement()

  const header = map.constructor.name + '(' + map.size + ') { '

  return new InspectSequence(header, ' }', ', ', values, ref, depth, {
    ...opts,
    tabulate: true
  })
}

function inspectSet(set, ref, depth, opts) {
  const {
    maxArrayLength = defaultMaxArrayLength,
    maxSetLength = maxArrayLength
  } = opts

  ref.increment()

  const values = []

  let remaining = maxSetLength

  for (const entry of set) {
    if (remaining-- === 0) {
      values.push(
        new InspectSuspension(set.size - values.length, depth + 1, {
          ...opts,
          breakAlways: true
        })
      )
      break
    }

    values.push(inspectValue(entry, depth + 1, opts))
  }

  for (const key in set) {
    if (key === 'constructor') continue

    const value = inspectValue(set[key], depth + 1, opts)

    values.push(
      new InspectPair(
        ': ',
        inspectKey(key, depth + 1, opts),
        value,
        depth + 1,
        { ...opts, breakAlways: remaining < 0 }
      )
    )
  }

  ref.decrement()

  const header = set.constructor.name + '(' + set.size + ') { '

  return new InspectSequence(header, ' }', ', ', values, ref, depth, {
    ...opts,
    tabulate: true
  })
}

function inspectWeakMap(weakMap, ref, depth, opts) {
  const header = weakMap.constructor.name + ' { '

  return new InspectSequence(
    header,
    ' }',
    ' ',
    [new InspectLeaf('<items unknown>', styles.special, depth + 1, opts)],
    ref,
    depth,
    opts
  )
}

function inspectWeakSet(weakSet, ref, depth, opts) {
  const header = weakSet.constructor.name + ' { '

  return new InspectSequence(
    header,
    ' }',
    ' ',
    [new InspectLeaf('<items unknown>', styles.special, depth + 1, opts)],
    ref,
    depth,
    opts
  )
}

function inspectWeakRef(weakRef, ref, depth, opts) {
  const target = weakRef.deref()

  let value

  if (target === undefined) {
    value = new InspectLeaf('<cleared>', styles.special, depth + 1, opts)
  } else {
    value = inspectValue(target, depth + 1, opts)
  }

  const header = weakRef.constructor.name + ' { '

  return new InspectSequence(header, ' }', ' ', [value], ref, depth, opts)
}

function inspectArrayBuffer(arrayBuffer, ref, depth, opts) {
  ref.increment()

  const values = []

  for (const key of ['byteLength']) {
    values.push(
      new InspectPair(
        ': ',
        inspectKey(key, depth + 1, opts),
        inspectValue(arrayBuffer[key], depth + 1, opts),
        depth + 1,
        opts
      )
    )
  }

  ref.decrement()

  const header = arrayBuffer.constructor.name + ' { '

  return new InspectSequence(header, ' }', ', ', values, ref, depth, opts)
}

function inspectSharedArrayBuffer(sharedArrayBuffer, ref, depth, opts) {
  ref.increment()

  const values = []

  for (const key of ['byteLength']) {
    values.push(
      new InspectPair(
        ': ',
        inspectKey(key, depth + 1, opts),
        inspectValue(sharedArrayBuffer[key], depth + 1, opts),
        depth + 1,
        opts
      )
    )
  }

  ref.decrement()

  const header = sharedArrayBuffer.constructor.name + ' { '

  return new InspectSequence(header, ' }', ', ', values, ref, depth, opts)
}

function inspectTypedArray(typedArray, ref, depth, opts) {
  if (Buffer.isBuffer(typedArray)) {
    return inspectBuffer(typedArray, ref, depth, opts)
  }

  const {
    maxArrayLength = defaultMaxArrayLength,
    maxTypedArrayLength = maxArrayLength
  } = opts

  ref.increment()

  const values = []

  let remaining = Math.max(maxTypedArrayLength, 0)

  for (let i = 0, n = typedArray.length; i < n; i++) {
    if (remaining-- === 0) {
      values.push(
        new InspectSuspension(typedArray.length - values.length, depth + 1, {
          ...opts,
          breakAlways: true
        })
      )

      break
    }

    values.push(inspectValue(typedArray[i], depth + 1, opts))
  }

  for (const key of binding.getOwnNonIndexPropertyNames(typedArray)) {
    values.push(
      new InspectPair(
        ': ',
        inspectKey(key, depth + 1, opts),
        inspectValue(typedArray[key], depth + 1, opts),
        depth + 1,
        { ...opts, breakAlways: remaining < 0 }
      )
    )
  }

  ref.decrement()

  const header = typedArray.constructor.name + '(' + typedArray.length + ') [ '

  return new InspectSequence(header, ' ]', ', ', values, ref, depth, {
    ...opts,
    tabulate: true
  })
}

function inspectBuffer(buffer, ref, depth, opts) {
  const {
    maxArrayLength = defaultMaxArrayLength,
    maxBufferLength = maxArrayLength
  } = opts

  ref.increment()

  const values = []

  let remaining = Math.max(maxBufferLength, 0)

  for (let i = 0, n = buffer.byteLength; i < n; i++) {
    if (remaining-- === 0) {
      values.push(
        new InspectSuspension(buffer.length - values.length, depth + 1, {
          ...opts,
          breakAlways: true
        })
      )

      break
    }

    values.push(
      new InspectLeaf(
        buffer[i].toString(16).padStart(2, '0'),
        null,
        depth + 1,
        opts
      )
    )
  }

  for (const key of binding.getOwnNonIndexPropertyNames(buffer)) {
    values.push(
      new InspectPair(
        ': ',
        inspectKey(key, depth + 1, opts),
        inspectValue(buffer[key], depth + 1, opts),
        depth + 1,
        { ...opts, breakAlways: remaining < 0 }
      )
    )
  }

  ref.decrement()

  return new InspectSequence('<Buffer ', '>', ' ', values, ref, depth, {
    ...opts,
    tabulate: true
  })
}

function inspectDataView(dataView, ref, depth, opts) {
  ref.increment()

  const values = []

  for (const key of ['byteLength', 'byteOffset', 'buffer']) {
    values.push(
      new InspectPair(
        ': ',
        inspectKey(key, depth + 1, opts),
        inspectValue(dataView[key], depth + 1, opts),
        depth + 1,
        opts
      )
    )
  }

  for (const key of binding.getOwnNonIndexPropertyNames(dataView)) {
    values.push(
      new InspectPair(
        ': ',
        inspectKey(key, depth + 1, opts),
        inspectValue(dataView[key], depth + 1, opts),
        depth + 1,
        opts
      )
    )
  }

  ref.decrement()

  const header = dataView.constructor.name + ' { '

  return new InspectSequence(header, ' }', ', ', values, ref, depth, opts)
}

function inspectFunction(type, fn, depth, opts) {
  if (fn.toString().startsWith('class')) return inspectClass(fn, depth, opts)

  let tag = 'function'

  if (type.isGeneratorFunction()) tag = 'generator ' + tag
  if (type.isAsyncFunction()) tag = 'async ' + tag

  return new InspectLeaf(
    '[' + tag + ' ' + (fn.name ? fn.name : '(anonymous)') + ']',
    styles.special,
    depth,
    opts
  )
}

function inspectClass(ctor, depth, opts) {
  return new InspectLeaf(
    '[class ' + (ctor.name ? ctor.name : '(anonymous)') + ']',
    styles.special,
    depth,
    opts
  )
}

function inspectExternal(external, depth, opts) {
  return new InspectLeaf(
    '[external 0x' + binding.getExternal(external).toString(16) + ']',
    styles.special,
    depth,
    opts
  )
}
{
  "name": "bare-inspect",
  "version": "3.1.4",
  "description": "Inspect objects as strings for debugging",
  "exports": {
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    },
    "./package": "./package.json"
  },
  "files": [
    "index.js",
    "index.d.ts",
    "binding.c",
    "binding.js",
    "CMakeLists.txt",
    "prebuilds"
  ],
  "addon": true,
  "scripts": {
    "test": "prettier . --check && bare test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-inspect.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-inspect/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-inspect#readme",
  "engines": {
    "bare": ">=1.18.0"
  },
  "dependencies": {
    "bare-ansi-escapes": "^2.1.0",
    "bare-type": "^1.0.0"
  },
  "devDependencies": {
    "brittle": "^3.3.2",
    "cmake-bare": "^1.1.6",
    "prettier": "^3.4.1",
    "prettier-config-standard": "^7.0.0"
  }
}
module.exports = require.addon()
const binding = require('./binding')
const errors = require('./lib/errors')
const constants = require('./lib/constants')

exports.constants = constants
exports.errors = errors

exports.EOL = binding.platform === 'win32' ? '\r\n' : '\n'

exports.devNull = binding.platform === 'win32' ? '\\\\.\\nul' : '/dev/null'

exports.platform = function platform() {
  return binding.platform
}

exports.arch = function arch() {
  return binding.arch
}

exports.type = binding.type
exports.version = binding.version
exports.release = binding.release
exports.machine = binding.machine
exports.execPath = binding.execPath
exports.pid = binding.pid
exports.ppid = binding.ppid
exports.cwd = binding.cwd
exports.chdir = binding.chdir
exports.tmpdir = binding.tmpdir
exports.homedir = binding.homedir
exports.hostname = binding.hostname
exports.userInfo = binding.userInfo
exports.groupInfo = binding.groupInfo

exports.networkInterfaces = function networkInterfaces() {
  const result = {}

  for (const entry of binding.networkInterfaces()) {
    const { name, ...properties } = entry

    if (result[name]) result[name].push(properties)
    else result[name] = [properties]
  }

  return result
}

exports.kill = function kill(pid, signal = constants.signals.SIGTERM) {
  if (typeof signal === 'string') {
    if (signal in constants.signals === false) {
      throw errors.UNKNOWN_SIGNAL('Unknown signal: ' + signal)
    }

    signal = constants.signals[signal]
  }

  binding.kill(pid, signal)
}

exports.endianness = function endianness() {
  return binding.isLittleEndian ? 'LE' : 'BE'
}

exports.availableParallelism = binding.availableParallelism

exports.cpuUsage = function cpuUsage(previous) {
  const current = binding.cpuUsage()

  if (previous) {
    return {
      user: current.user - previous.user,
      system: current.system - previous.system
    }
  }

  return current
}

exports.threadCpuUsage = function threadCpuUsage(previous) {
  const current = binding.threadCpuUsage()

  if (previous) {
    return {
      user: current.user - previous.user,
      system: current.system - previous.system
    }
  }

  return current
}

exports.resourceUsage = binding.resourceUsage
exports.memoryUsage = binding.memoryUsage
exports.freemem = binding.freemem
exports.totalmem = binding.totalmem
exports.availableMemory = binding.availableMemory
exports.constrainedMemory = binding.constrainedMemory
exports.uptime = binding.uptime
exports.loadavg = binding.loadavg
exports.cpus = binding.cpus

exports.getProcessTitle = binding.getProcessTitle

exports.setProcessTitle = function setProcessTitle(title) {
  if (typeof title !== 'string') title = title.toString()

  if (title.length >= 256) {
    throw errors.TITLE_OVERFLOW('Process title is too long')
  }

  binding.setProcessTitle(title)
}

exports.getPriority = function getPriority(pid = 0) {
  return binding.getPriority(pid)
}

exports.setPriority = function setPriority(pid, priority) {
  if (priority === undefined) {
    priority = pid
    pid = 0
  }

  binding.setPriority(pid, priority)
}

exports.getEnvKeys = binding.getEnvKeys
exports.getEnv = binding.getEnv
exports.hasEnv = binding.hasEnv
exports.setEnv = binding.setEnv
exports.unsetEnv = binding.unsetEnv
const binding = require('../binding')

module.exports = {
  signals: binding.signals,
  errnos: binding.errnos,
  priority: binding.priority
}
module.exports = class OSError extends Error {
  constructor(msg, fn = OSError, code = fn.name) {
    super(`${code}: ${msg}`)
    this.code = code

    if (Error.captureStackTrace) {
      Error.captureStackTrace(this, fn)
    }
  }

  get name() {
    return 'OSError'
  }

  static UNKNOWN_SIGNAL(msg) {
    return new OSError(msg, OSError.UNKNOWN_SIGNAL)
  }

  static TITLE_OVERFLOW(msg) {
    return new OSError(msg, OSError.TITLE_OVERFLOW)
  }
}
{
  "name": "bare-os",
  "version": "3.9.1",
  "description": "Operating system utilities for Bare",
  "exports": {
    "./package": "./package.json",
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    },
    "./constants": {
      "types": "./lib/constants.d.ts",
      "default": "./lib/constants.js"
    },
    "./errors": {
      "types": "./lib/errors.d.ts",
      "default": "./lib/errors.js"
    }
  },
  "files": [
    "index.js",
    "index.d.ts",
    "binding.c",
    "binding.js",
    "CMakeLists.txt",
    "lib",
    "prebuilds"
  ],
  "addon": true,
  "scripts": {
    "format": "prettier --write . && lunte --fix",
    "lint": "prettier --check . && lunte",
    "test": "brittle-bare --coverage test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-os.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-os/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-os#readme",
  "engines": {
    "bare": ">=1.14.0"
  },
  "devDependencies": {
    "brittle": "^3.1.1",
    "cmake-bare": "^1.1.6",
    "lunte": "^1.6.0",
    "prettier": "^3.4.2",
    "prettier-config-holepunch": "^2.0.0"
  }
}
// This export SHOULD NOT be shortened in any way as having the full
// `module.exports = require(...)` statement is crucial for synthesizing
// ESM exports.

if (Bare.platform === 'win32') {
  module.exports = require('./lib/win32')
} else {
  module.exports = require('./lib/posix')
}
module.exports = {
  CHAR_UPPERCASE_A: 0x41,
  CHAR_LOWERCASE_A: 0x61,
  CHAR_UPPERCASE_Z: 0x5a,
  CHAR_LOWERCASE_Z: 0x7a,
  CHAR_DOT: 0x2e,
  CHAR_FORWARD_SLASH: 0x2f,
  CHAR_BACKWARD_SLASH: 0x5c,
  CHAR_COLON: 0x3a,
  CHAR_QUESTION_MARK: 0x3f
}
const os = require('bare-os')

const { normalizeString } = require('./shared')
const { CHAR_DOT, CHAR_FORWARD_SLASH } = require('./constants')

function isPosixPathSeparator(code) {
  return code === CHAR_FORWARD_SLASH
}

exports.win32 = require('./win32')
exports.posix = exports

exports.sep = '/'
exports.delimiter = ':'

exports.resolve = function resolve(...args) {
  let resolvedPath = ''
  let resolvedAbsolute = false

  for (let i = args.length - 1; i >= -1 && !resolvedAbsolute; i--) {
    const path = i >= 0 ? args[i] : os.cwd()

    if (path.length === 0) {
      continue
    }

    resolvedPath = `${path}/${resolvedPath}`
    resolvedAbsolute = path.charCodeAt(0) === CHAR_FORWARD_SLASH
  }

  resolvedPath = normalizeString(resolvedPath, !resolvedAbsolute, '/', isPosixPathSeparator)

  if (resolvedAbsolute) {
    return `/${resolvedPath}`
  }

  return resolvedPath.length > 0 ? resolvedPath : '.'
}

exports.normalize = function normalize(path) {
  if (path.length === 0) return '.'

  const isAbsolute = path.charCodeAt(0) === CHAR_FORWARD_SLASH
  const trailingSeparator = path.charCodeAt(path.length - 1) === CHAR_FORWARD_SLASH

  path = normalizeString(path, !isAbsolute, '/', isPosixPathSeparator)

  if (path.length === 0) {
    if (isAbsolute) return '/'
    return trailingSeparator ? './' : '.'
  }

  if (trailingSeparator) path += '/'

  return isAbsolute ? `/${path}` : path
}

exports.isAbsolute = function isAbsolute(path) {
  return path.length > 0 && path.charCodeAt(0) === CHAR_FORWARD_SLASH
}

exports.join = function join(...args) {
  if (args.length === 0) return '.'
  let joined
  for (let i = 0; i < args.length; ++i) {
    const arg = args[i]
    if (arg.length > 0) {
      if (joined === undefined) joined = arg
      else joined += `/${arg}`
    }
  }
  if (joined === undefined) return '.'
  return exports.normalize(joined)
}

exports.relative = function relative(from, to) {
  if (from === to) return ''

  from = exports.resolve(from)
  to = exports.resolve(to)

  if (from === to) return ''

  const fromStart = 1
  const fromEnd = from.length
  const fromLen = fromEnd - fromStart
  const toStart = 1
  const toLen = to.length - toStart

  const length = fromLen < toLen ? fromLen : toLen
  let lastCommonSep = -1
  let i = 0
  for (; i < length; i++) {
    const fromCode = from.charCodeAt(fromStart + i)
    if (fromCode !== to.charCodeAt(toStart + i)) {
      break
    } else if (fromCode === CHAR_FORWARD_SLASH) {
      lastCommonSep = i
    }
  }
  if (i === length) {
    if (toLen > length) {
      if (to.charCodeAt(toStart + i) === CHAR_FORWARD_SLASH) {
        return to.substring(toStart + i + 1)
      }
      if (i === 0) {
        return to.substring(toStart + i)
      }
    } else if (fromLen > length) {
      if (from.charCodeAt(fromStart + i) === CHAR_FORWARD_SLASH) {
        lastCommonSep = i
      } else if (i === 0) {
        lastCommonSep = 0
      }
    }
  }

  let out = ''
  for (i = fromStart + lastCommonSep + 1; i <= fromEnd; ++i) {
    if (i === fromEnd || from.charCodeAt(i) === CHAR_FORWARD_SLASH) {
      out += out.length === 0 ? '..' : '/..'
    }
  }

  return `${out}${to.substring(toStart + lastCommonSep)}`
}

exports.toNamespacedPath = function toNamespacedPath(path) {
  return path
}

exports.dirname = function dirname(path) {
  if (path.length === 0) return '.'
  const hasRoot = path.charCodeAt(0) === CHAR_FORWARD_SLASH
  let end = -1
  let matchedSlash = true
  for (let i = path.length - 1; i >= 1; --i) {
    if (path.charCodeAt(i) === CHAR_FORWARD_SLASH) {
      if (!matchedSlash) {
        end = i
        break
      }
    } else {
      matchedSlash = false
    }
  }

  if (end === -1) return hasRoot ? '/' : '.'
  if (hasRoot && end === 1) return '//'
  return path.substring(0, end)
}

exports.basename = function basename(path, suffix) {
  let start = 0
  let end = -1
  let matchedSlash = true

  if (suffix !== undefined && suffix.length > 0 && suffix.length <= path.length) {
    if (suffix === path) {
      return ''
    }
    let extIdx = suffix.length - 1
    let firstNonSlashEnd = -1
    for (let i = path.length - 1; i >= 0; --i) {
      const code = path.charCodeAt(i)
      if (code === CHAR_FORWARD_SLASH) {
        if (!matchedSlash) {
          start = i + 1
          break
        }
      } else {
        if (firstNonSlashEnd === -1) {
          matchedSlash = false
          firstNonSlashEnd = i + 1
        }
        if (extIdx >= 0) {
          if (code === suffix.charCodeAt(extIdx)) {
            if (--extIdx === -1) {
              end = i
            }
          } else {
            extIdx = -1
            end = firstNonSlashEnd
          }
        }
      }
    }

    if (start === end) end = firstNonSlashEnd
    else if (end === -1) end = path.length
    return path.substring(start, end)
  }

  for (let i = path.length - 1; i >= 0; --i) {
    if (path.charCodeAt(i) === CHAR_FORWARD_SLASH) {
      if (!matchedSlash) {
        start = i + 1
        break
      }
    } else if (end === -1) {
      matchedSlash = false
      end = i + 1
    }
  }

  if (end === -1) return ''
  return path.substring(start, end)
}

exports.extname = function extname(path) {
  let startDot = -1
  let startPart = 0
  let end = -1
  let matchedSlash = true
  let preDotState = 0
  for (let i = path.length - 1; i >= 0; --i) {
    const code = path.charCodeAt(i)
    if (code === CHAR_FORWARD_SLASH) {
      if (!matchedSlash) {
        startPart = i + 1
        break
      }
      continue
    }
    if (end === -1) {
      matchedSlash = false
      end = i + 1
    }
    if (code === CHAR_DOT) {
      if (startDot === -1) startDot = i
      else if (preDotState !== 1) preDotState = 1
    } else if (startDot !== -1) {
      preDotState = -1
    }
  }

  if (
    startDot === -1 ||
    end === -1 ||
    preDotState === 0 ||
    (preDotState === 1 && startDot === end - 1 && startDot === startPart + 1)
  ) {
    return ''
  }
  return path.substring(startDot, end)
}
const { CHAR_DOT, CHAR_FORWARD_SLASH } = require('./constants')

exports.normalizeString = function normalizeString(
  path,
  allowAboveRoot,
  separator,
  isPathSeparator
) {
  let res = ''
  let lastSegmentLength = 0
  let lastSlash = -1
  let dots = 0
  let code = 0
  for (let i = 0; i <= path.length; ++i) {
    if (i < path.length) {
      code = path.charCodeAt(i)
    } else if (isPathSeparator(code)) {
      break
    } else {
      code = CHAR_FORWARD_SLASH
    }

    if (isPathSeparator(code)) {
      if (lastSlash === i - 1 || dots === 1);
      else if (dots === 2) {
        if (
          res.length < 2 ||
          lastSegmentLength !== 2 ||
          res.charCodeAt(res.length - 1) !== CHAR_DOT ||
          res.charCodeAt(res.length - 2) !== CHAR_DOT
        ) {
          if (res.length > 2) {
            const lastSlashIndex = res.lastIndexOf(separator)
            if (lastSlashIndex === -1) {
              res = ''
              lastSegmentLength = 0
            } else {
              res = res.substring(0, lastSlashIndex)
              lastSegmentLength = res.length - 1 - res.lastIndexOf(separator)
            }
            lastSlash = i
            dots = 0
            continue
          } else if (res.length !== 0) {
            res = ''
            lastSegmentLength = 0
            lastSlash = i
            dots = 0
            continue
          }
        }
        if (allowAboveRoot) {
          res += res.length > 0 ? `${separator}..` : '..'
          lastSegmentLength = 2
        }
      } else {
        if (res.length > 0) {
          res += `${separator}${path.substring(lastSlash + 1, i)}`
        } else {
          res = path.substring(lastSlash + 1, i)
        }
        lastSegmentLength = i - lastSlash - 1
      }
      lastSlash = i
      dots = 0
    } else if (code === CHAR_DOT && dots !== -1) {
      ++dots
    } else {
      dots = -1
    }
  }
  return res
}
const os = require('bare-os')

const { normalizeString } = require('./shared')
const {
  CHAR_UPPERCASE_A,
  CHAR_LOWERCASE_A,
  CHAR_UPPERCASE_Z,
  CHAR_LOWERCASE_Z,
  CHAR_DOT,
  CHAR_FORWARD_SLASH,
  CHAR_BACKWARD_SLASH,
  CHAR_COLON,
  CHAR_QUESTION_MARK
} = require('./constants')

function isWindowsPathSeparator(code) {
  return code === CHAR_FORWARD_SLASH || code === CHAR_BACKWARD_SLASH
}

function isWindowsDeviceRoot(code) {
  return (
    (code >= CHAR_UPPERCASE_A && code <= CHAR_UPPERCASE_Z) ||
    (code >= CHAR_LOWERCASE_A && code <= CHAR_LOWERCASE_Z)
  )
}

exports.posix = require('./posix')
exports.win32 = exports

exports.sep = '\\'
exports.delimiter = ';'

exports.resolve = function resolve(...args) {
  let resolvedDevice = ''
  let resolvedTail = ''
  let resolvedAbsolute = false

  for (let i = args.length - 1; i >= -1; i--) {
    let path
    if (i >= 0) {
      path = args[i]

      if (path.length === 0) continue
    } else if (resolvedDevice.length === 0) {
      path = os.cwd()
    } else {
      path = os.getEnv(`=${resolvedDevice}`) || os.cwd()

      if (
        path === undefined ||
        (path.substring(0, 2).toLowerCase() !== resolvedDevice.toLowerCase() &&
          path.charCodeAt(2) === CHAR_BACKWARD_SLASH)
      ) {
        path = `${resolvedDevice}\\`
      }
    }

    const len = path.length
    let rootEnd = 0
    let device = ''
    let isAbsolute = false
    const code = path.charCodeAt(0)

    if (len === 1) {
      if (isWindowsPathSeparator(code)) {
        rootEnd = 1
        isAbsolute = true
      }
    } else if (isWindowsPathSeparator(code)) {
      isAbsolute = true

      if (isWindowsPathSeparator(path.charCodeAt(1))) {
        let j = 2
        let last = j
        while (j < len && !isWindowsPathSeparator(path.charCodeAt(j))) {
          j++
        }
        if (j < len && j !== last) {
          const firstPart = path.substring(last, j)
          last = j
          while (j < len && isWindowsPathSeparator(path.charCodeAt(j))) {
            j++
          }
          if (j < len && j !== last) {
            last = j
            while (j < len && !isWindowsPathSeparator(path.charCodeAt(j))) {
              j++
            }
            if (j === len || j !== last) {
              device = `\\\\${firstPart}\\${path.substring(last, j)}`
              rootEnd = j
            }
          }
        }
      } else {
        rootEnd = 1
      }
    } else if (isWindowsDeviceRoot(code) && path.charCodeAt(1) === CHAR_COLON) {
      device = path.substring(0, 2)
      rootEnd = 2
      if (len > 2 && isWindowsPathSeparator(path.charCodeAt(2))) {
        isAbsolute = true
        rootEnd = 3
      }
    }

    if (device.length > 0) {
      if (resolvedDevice.length > 0) {
        if (device.toLowerCase() !== resolvedDevice.toLowerCase()) {
          continue
        }
      } else {
        resolvedDevice = device
      }
    }

    if (resolvedAbsolute) {
      if (resolvedDevice.length > 0) {
        break
      }
    } else {
      resolvedTail = `${path.substring(rootEnd)}\\${resolvedTail}`
      resolvedAbsolute = isAbsolute
      if (isAbsolute && resolvedDevice.length > 0) {
        break
      }
    }
  }

  resolvedTail = normalizeString(resolvedTail, !resolvedAbsolute, '\\', isWindowsPathSeparator)

  return resolvedAbsolute
    ? `${resolvedDevice}\\${resolvedTail}`
    : `${resolvedDevice}${resolvedTail}` || '.'
}

exports.normalize = function normalize(path) {
  const len = path.length
  if (len === 0) return '.'
  let rootEnd = 0
  let device
  let isAbsolute = false
  const code = path.charCodeAt(0)

  if (len === 1) {
    return code === CHAR_FORWARD_SLASH ? '\\' : path
  }

  if (isWindowsPathSeparator(code)) {
    isAbsolute = true

    if (isWindowsPathSeparator(path.charCodeAt(1))) {
      let j = 2
      let last = j
      while (j < len && !isWindowsPathSeparator(path.charCodeAt(j))) {
        j++
      }
      if (j < len && j !== last) {
        const firstPart = path.substring(last, j)
        last = j
        while (j < len && isWindowsPathSeparator(path.charCodeAt(j))) {
          j++
        }
        if (j < len && j !== last) {
          last = j
          while (j < len && !isWindowsPathSeparator(path.charCodeAt(j))) {
            j++
          }
          if (j === len) {
            return `\\\\${firstPart}\\${path.substring(last)}\\`
          }
          if (j !== last) {
            device = `\\\\${firstPart}\\${path.substring(last, j)}`
            rootEnd = j
          }
        }
      }
    } else {
      rootEnd = 1
    }
  } else if (isWindowsDeviceRoot(code) && path.charCodeAt(1) === CHAR_COLON) {
    device = path.substring(0, 2)
    rootEnd = 2
    if (len > 2 && isWindowsPathSeparator(path.charCodeAt(2))) {
      isAbsolute = true
      rootEnd = 3
    }
  }

  let tail =
    rootEnd < len
      ? normalizeString(path.substring(rootEnd), !isAbsolute, '\\', isWindowsPathSeparator)
      : ''
  if (tail.length === 0 && !isAbsolute) {
    tail = '.'
  }
  if (tail.length > 0 && isWindowsPathSeparator(path.charCodeAt(len - 1))) {
    tail += '\\'
  }
  if (device === undefined) {
    return isAbsolute ? `\\${tail}` : tail
  }
  return isAbsolute ? `${device}\\${tail}` : `${device}${tail}`
}

exports.isAbsolute = function isAbsolute(path) {
  const len = path.length
  if (len === 0) return false

  const code = path.charCodeAt(0)

  return (
    isWindowsPathSeparator(code) ||
    (len > 2 &&
      isWindowsDeviceRoot(code) &&
      path.charCodeAt(1) === CHAR_COLON &&
      isWindowsPathSeparator(path.charCodeAt(2)))
  )
}

exports.join = function join(...args) {
  if (args.length === 0) return '.'

  let joined
  let firstPart
  for (let i = 0; i < args.length; ++i) {
    const arg = args[i]
    if (arg.length > 0) {
      if (joined === undefined) joined = firstPart = arg
      else joined += `\\${arg}`
    }
  }

  if (joined === undefined) return '.'

  let needsReplace = true
  let slashCount = 0
  if (isWindowsPathSeparator(firstPart.charCodeAt(0))) {
    ++slashCount
    const firstLen = firstPart.length
    if (firstLen > 1 && isWindowsPathSeparator(firstPart.charCodeAt(1))) {
      ++slashCount
      if (firstLen > 2) {
        if (isWindowsPathSeparator(firstPart.charCodeAt(2))) {
          ++slashCount
        } else {
          needsReplace = false
        }
      }
    }
  }
  if (needsReplace) {
    while (slashCount < joined.length && isWindowsPathSeparator(joined.charCodeAt(slashCount))) {
      slashCount++
    }

    if (slashCount >= 2) {
      joined = `\\${joined.substring(slashCount)}`
    }
  }

  return exports.normalize(joined)
}

exports.relative = function relative(from, to) {
  if (from === to) return ''

  const fromOrig = exports.resolve(from)
  const toOrig = exports.resolve(to)

  if (fromOrig === toOrig) return ''

  from = fromOrig.toLowerCase()
  to = toOrig.toLowerCase()

  if (from === to) return ''

  let fromStart = 0
  while (fromStart < from.length && from.charCodeAt(fromStart) === CHAR_BACKWARD_SLASH) {
    fromStart++
  }
  let fromEnd = from.length
  while (fromEnd - 1 > fromStart && from.charCodeAt(fromEnd - 1) === CHAR_BACKWARD_SLASH) {
    fromEnd--
  }
  const fromLen = fromEnd - fromStart

  let toStart = 0
  while (toStart < to.length && to.charCodeAt(toStart) === CHAR_BACKWARD_SLASH) {
    toStart++
  }
  let toEnd = to.length
  while (toEnd - 1 > toStart && to.charCodeAt(toEnd - 1) === CHAR_BACKWARD_SLASH) {
    toEnd--
  }
  const toLen = toEnd - toStart

  const length = fromLen < toLen ? fromLen : toLen
  let lastCommonSep = -1
  let i = 0
  for (; i < length; i++) {
    const fromCode = from.charCodeAt(fromStart + i)
    if (fromCode !== to.charCodeAt(toStart + i)) {
      break
    } else if (fromCode === CHAR_BACKWARD_SLASH) {
      lastCommonSep = i
    }
  }

  if (i !== length) {
    if (lastCommonSep === -1) return toOrig
  } else {
    if (toLen > length) {
      if (to.charCodeAt(toStart + i) === CHAR_BACKWARD_SLASH) {
        return toOrig.substring(toStart + i + 1)
      }
      if (i === 2) {
        return toOrig.substring(toStart + i)
      }
    }
    if (fromLen > length) {
      if (from.charCodeAt(fromStart + i) === CHAR_BACKWARD_SLASH) {
        lastCommonSep = i
      } else if (i === 2) {
        lastCommonSep = 3
      }
    }
    if (lastCommonSep === -1) lastCommonSep = 0
  }

  let out = ''
  for (i = fromStart + lastCommonSep + 1; i <= fromEnd; ++i) {
    if (i === fromEnd || from.charCodeAt(i) === CHAR_BACKWARD_SLASH) {
      out += out.length === 0 ? '..' : '\\..'
    }
  }

  toStart += lastCommonSep

  if (out.length > 0) {
    return `${out}${toOrig.substring(toStart, toEnd)}`
  }
  if (toOrig.charCodeAt(toStart) === CHAR_BACKWARD_SLASH) {
    ++toStart
  }
  return toOrig.substring(toStart, toEnd)
}

exports.toNamespacedPath = function toNamespacedPath(path) {
  if (path.length === 0) return path

  const resolvedPath = exports.resolve(path)

  if (resolvedPath.length <= 2) return path

  if (resolvedPath.charCodeAt(0) === CHAR_BACKWARD_SLASH) {
    if (resolvedPath.charCodeAt(1) === CHAR_BACKWARD_SLASH) {
      const code = resolvedPath.charCodeAt(2)
      if (code !== CHAR_QUESTION_MARK && code !== CHAR_DOT) {
        return `\\\\?\\UNC\\${resolvedPath.substring(2)}`
      }
    }
  } else if (
    isWindowsDeviceRoot(resolvedPath.charCodeAt(0)) &&
    resolvedPath.charCodeAt(1) === CHAR_COLON &&
    resolvedPath.charCodeAt(2) === CHAR_BACKWARD_SLASH
  ) {
    return `\\\\?\\${resolvedPath}`
  }

  return path
}

exports.dirname = function dirname(path) {
  const len = path.length
  if (len === 0) return '.'
  let rootEnd = -1
  let offset = 0
  const code = path.charCodeAt(0)

  if (len === 1) {
    return isWindowsPathSeparator(code) ? path : '.'
  }

  if (isWindowsPathSeparator(code)) {
    rootEnd = offset = 1

    if (isWindowsPathSeparator(path.charCodeAt(1))) {
      let j = 2
      let last = j
      while (j < len && !isWindowsPathSeparator(path.charCodeAt(j))) {
        j++
      }
      if (j < len && j !== last) {
        last = j
        while (j < len && isWindowsPathSeparator(path.charCodeAt(j))) {
          j++
        }
        if (j < len && j !== last) {
          last = j
          while (j < len && !isWindowsPathSeparator(path.charCodeAt(j))) {
            j++
          }
          if (j === len) {
            return path
          }
          if (j !== last) {
            rootEnd = offset = j + 1
          }
        }
      }
    }
  } else if (isWindowsDeviceRoot(code) && path.charCodeAt(1) === CHAR_COLON) {
    rootEnd = len > 2 && isWindowsPathSeparator(path.charCodeAt(2)) ? 3 : 2
    offset = rootEnd
  }

  let end = -1
  let matchedSlash = true
  for (let i = len - 1; i >= offset; --i) {
    if (isWindowsPathSeparator(path.charCodeAt(i))) {
      if (!matchedSlash) {
        end = i
        break
      }
    } else {
      matchedSlash = false
    }
  }

  if (end === -1) {
    if (rootEnd === -1) return '.'

    end = rootEnd
  }
  return path.substring(0, end)
}

exports.basename = function basename(path, suffix) {
  let start = 0
  let end = -1
  let matchedSlash = true

  if (
    path.length >= 2 &&
    isWindowsDeviceRoot(path.charCodeAt(0)) &&
    path.charCodeAt(1) === CHAR_COLON
  ) {
    start = 2
  }

  if (suffix !== undefined && suffix.length > 0 && suffix.length <= path.length) {
    if (suffix === path) return ''
    let extIdx = suffix.length - 1
    let firstNonSlashEnd = -1
    for (let i = path.length - 1; i >= start; --i) {
      const code = path.charCodeAt(i)
      if (isWindowsPathSeparator(code)) {
        if (!matchedSlash) {
          start = i + 1
          break
        }
      } else {
        if (firstNonSlashEnd === -1) {
          matchedSlash = false
          firstNonSlashEnd = i + 1
        }
        if (extIdx >= 0) {
          if (code === suffix.charCodeAt(extIdx)) {
            if (--extIdx === -1) {
              end = i
            }
          } else {
            extIdx = -1
            end = firstNonSlashEnd
          }
        }
      }
    }

    if (start === end) end = firstNonSlashEnd
    else if (end === -1) end = path.length
    return path.substring(start, end)
  }
  for (let i = path.length - 1; i >= start; --i) {
    if (isWindowsPathSeparator(path.charCodeAt(i))) {
      if (!matchedSlash) {
        start = i + 1
        break
      }
    } else if (end === -1) {
      matchedSlash = false
      end = i + 1
    }
  }

  if (end === -1) return ''
  return path.substring(start, end)
}

exports.extname = function extname(path) {
  let start = 0
  let startDot = -1
  let startPart = 0
  let end = -1
  let matchedSlash = true
  let preDotState = 0

  if (
    path.length >= 2 &&
    path.charCodeAt(1) === CHAR_COLON &&
    isWindowsDeviceRoot(path.charCodeAt(0))
  ) {
    start = startPart = 2
  }

  for (let i = path.length - 1; i >= start; --i) {
    const code = path.charCodeAt(i)
    if (isWindowsPathSeparator(code)) {
      if (!matchedSlash) {
        startPart = i + 1
        break
      }
      continue
    }
    if (end === -1) {
      matchedSlash = false
      end = i + 1
    }
    if (code === CHAR_DOT) {
      if (startDot === -1) startDot = i
      else if (preDotState !== 1) preDotState = 1
    } else if (startDot !== -1) {
      preDotState = -1
    }
  }

  if (
    startDot === -1 ||
    end === -1 ||
    preDotState === 0 ||
    (preDotState === 1 && startDot === end - 1 && startDot === startPart + 1)
  ) {
    return ''
  }
  return path.substring(startDot, end)
}
{
  "name": "bare-path",
  "version": "3.0.1",
  "description": "Path manipulation library for JavaScript",
  "exports": {
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    },
    "./package": "./package.json",
    "./posix": "./lib/posix.js",
    "./win32": "./lib/win32.js"
  },
  "files": [
    "index.js",
    "index.d.ts",
    "lib",
    "NOTICE"
  ],
  "scripts": {
    "format": "prettier --write . && lunte --fix",
    "lint": "prettier --check . && lunte",
    "test": "brittle-bare --coverage test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-path.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-path/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-path#readme",
  "dependencies": {
    "bare-os": "^3.0.1"
  },
  "devDependencies": {
    "brittle": "^3.3.2",
    "lunte": "^1.8.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^2.0.0"
  }
}
const abort = require('bare-abort')
const EventEmitter = require('bare-events')
const Signal = require('bare-signals')
const os = require('bare-os')
const env = require('bare-env')
const hrtime = require('bare-hrtime')
const stdio = require('bare-stdio')

class Process extends EventEmitter {
  constructor() {
    super()

    this._startTime = hrtime.bigint()

    EventEmitter.forward(Bare, this, [
      'uncaughtException',
      'unhandledRejection',
      'beforeExit',
      'exit',
      'suspend',
      'wakeup',
      'idle',
      'resume'
    ])

    const signals = new Signal.Emitter()

    signals.unref()

    EventEmitter.forward(signals, this, Object.keys(os.constants.signals))
  }

  get stdin() {
    return stdio.in
  }

  get stdout() {
    return stdio.out
  }

  get stderr() {
    return stdio.err
  }

  get platform() {
    return os.platform()
  }

  get arch() {
    return os.arch()
  }

  get title() {
    return os.getProcessTitle()
  }

  set title(title) {
    os.setProcessTitle(title)
  }

  get pid() {
    return os.pid()
  }

  get ppid() {
    return os.ppid()
  }

  get argv() {
    return Bare.argv
  }

  get execPath() {
    return os.execPath()
  }

  get exitCode() {
    return Bare.exitCode
  }

  set exitCode(code) {
    Bare.exitCode = code
  }

  get version() {
    return Bare.version
  }

  get versions() {
    return Bare.versions
  }

  get env() {
    return env
  }

  get hrtime() {
    return hrtime
  }

  abort() {
    abort()
  }

  exit(code) {
    Bare.exit(code)
  }

  suspend() {
    Bare.suspend()
  }

  resume() {
    Bare.resume()
  }

  cwd() {
    return os.cwd()
  }

  chdir(directory) {
    os.chdir(directory)
  }

  kill(pid, signal) {
    os.kill(pid, signal)
  }

  uptime() {
    return Number(hrtime.bigint() - this._startTime) / 1e9
  }

  cpuUsage(previous) {
    return os.cpuUsage(previous)
  }

  threadCpuUsage(previous) {
    return os.threadCpuUsage(previous)
  }

  resourceUsage() {
    return os.resourceUsage()
  }

  availableMemory() {
    return os.availableMemory()
  }

  constrainedMemory() {
    return os.constrainedMemory()
  }

  memoryUsage() {
    return os.memoryUsage()
  }

  nextTick(cb, ...args) {
    queueMicrotask(cb.bind(null, ...args))
  }

  [Symbol.for('bare.inspect')]() {
    return {
      __proto__: { constructor: Process },

      platform: this.platform,
      arch: this.arch,
      title: this.title,
      pid: this.pid,
      ppid: this.ppid,
      argv: this.argv,
      execPath: this.execPath,
      exitCode: this.exitCode,
      version: this.version,
      versions: this.versions,
      env: this.env
    }
  }
}

module.exports = new Process()
{
  "name": "bare-process",
  "version": "4.4.1",
  "description": "Node.js-compatible process control for Bare",
  "exports": {
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    },
    "./global": {
      "types": "./global.d.ts",
      "default": "./global.js"
    },
    "./package": "./package.json"
  },
  "files": [
    "index.js",
    "index.d.ts",
    "global.js",
    "global.d.ts"
  ],
  "scripts": {
    "format": "prettier --write . && lunte --fix",
    "lint": "prettier --check . && lunte",
    "test": "brittle-bare --coverage test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-process.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-process/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-process#readme",
  "dependencies": {
    "bare-abort": "^2.0.13",
    "bare-env": "^3.0.0",
    "bare-events": "^2.3.1",
    "bare-hrtime": "^2.0.0",
    "bare-os": "^3.7.1",
    "bare-signals": "^4.0.0",
    "bare-stdio": "^1.0.1"
  },
  "devDependencies": {
    "brittle": "^3.2.1",
    "lunte": "^1.6.0",
    "prettier": "^3.4.1",
    "prettier-config-holepunch": "^2.0.0"
  }
}
module.exports = require.addon()
const EventEmitter = require('bare-events')
const os = require('bare-os')
const binding = require('./binding')
const errors = require('./lib/errors')

const signals = os.constants.signals

module.exports = exports = class Signal extends EventEmitter {
  constructor(signum) {
    super()

    if (typeof signum === 'string') {
      if (signum in signals === false) {
        throw errors.UNKNOWN_SIGNAL(`Unknown signal '${signum}'`)
      }

      signum = signals[signum]
    }

    this._signum = signum
    this._closing = null
    this._handle = binding.init(this, this._onsignal, this._onclose)
  }

  start() {
    if (this._closing) throw errors.SIGNAL_CLOSED('Signal is closed')
    binding.start(this._handle, this._signum)
    return this
  }

  stop() {
    if (this._closing) return this
    binding.stop(this._handle)
    return this
  }

  ref() {
    if (this._closing) return this
    binding.ref(this._handle)
    return this
  }

  unref() {
    if (this._closing) return this
    binding.unref(this._handle)
    return this
  }

  close() {
    if (this._closing) return this._closing
    this._closing = EventEmitter.once(this, 'close')

    binding.close(this._handle)

    return this._closing
  }

  _onsignal() {
    this.emit('signal', this._signum)
  }

  _onclose() {
    this._handle = null

    this.emit('close')
  }

  static send(signum, pid = os.pid()) {
    os.kill(pid, signum)
  }
}

exports.Emitter = require('./lib/emitter')

exports.constants = signals
exports.errors = errors
const EventEmitter = require('bare-events')
const Signal = require('..')

module.exports = class SignalEmitter extends EventEmitter {
  constructor() {
    super()

    this._signals = new Map()
    this._unrefed = false

    this.on('newListener', this._onnewlistener).on(
      'removeListener',
      this._onremovelistener
    )
  }

  ref() {
    this._unrefed = false
    for (const signal of this._signals.values()) signal.ref()
    return this
  }

  unref() {
    this._unrefed = true
    for (const signal of this._signals.values()) signal.unref()
    return this
  }

  _onnewlistener(name) {
    if (name === 'newListener' || name === 'removeListener') return

    if (this.listenerCount(name) === 0) {
      const signal = new Signal(name)

      signal.on('signal', this._onsignal.bind(this, name)).start()

      if (this._unrefed) signal.unref()

      this._signals.set(name, signal)
    }
  }

  _onremovelistener(name) {
    if (name === 'newListener' || name === 'removeListener') return

    if (this.listenerCount(name) === 0) {
      const signal = this._signals.get(name)

      if (this._unrefed) signal.ref()

      signal.close()

      this._signals.delete(name)
    }
  }

  _onsignal(name) {
    this.emit(name, name, Signal.constants[name])
  }
}
module.exports = class SignalError extends Error {
  constructor(msg, fn = SignalError, code = fn.name) {
    super(`${code}: ${msg}`)
    this.code = code

    if (Error.captureStackTrace) {
      Error.captureStackTrace(this, fn)
    }
  }

  get name() {
    return 'SignalError'
  }

  static UNKNOWN_SIGNAL(msg) {
    return new SignalError(msg, SignalError.UNKNOWN_SIGNAL)
  }

  static SIGNAL_CLOSED(msg) {
    return new SignalError(msg, SignalError.SIGNAL_CLOSED)
  }
}
{
  "name": "bare-signals",
  "version": "4.2.0",
  "description": "Native signal handling for JavaScript",
  "exports": {
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    },
    "./package": "./package.json",
    "./errors": "./lib/errors.js",
    "./emitter": "./lib/emitter.js"
  },
  "files": [
    "index.js",
    "index.d.ts",
    "binding.c",
    "binding.js",
    "CMakeLists.txt",
    "lib",
    "prebuilds"
  ],
  "addon": true,
  "scripts": {
    "test": "prettier . --check && bare test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-signals.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-signals/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-signals#readme",
  "engines": {
    "bare": ">=1.7.0"
  },
  "dependencies": {
    "bare-events": "^2.5.3",
    "bare-os": "^3.3.1"
  },
  "devDependencies": {
    "brittle": "^3.2.1",
    "cmake-bare": "^1.1.6",
    "prettier": "^3.4.1",
    "prettier-config-standard": "^7.0.0"
  }
}
module.exports = require.addon()
const fs = require('bare-fs')
const tty = require('bare-tty')
const Pipe = require('bare-pipe')
const binding = require('./binding')

const { TTY, NAMED_PIPE } = binding

class IO {
  constructor() {
    this._in = null
    this._out = null
    this._err = null
  }

  get in() {
    if (this._in === null) {
      switch (binding.guessType(0)) {
        case TTY:
          this._in = new tty.ReadStream(0)
          break
        case NAMED_PIPE:
          this._in = new Pipe(0, { eagerOpen: false })
          break
        default:
          this._in = fs.createReadStream(null, { fd: 0, eagerOpen: false })
      }
    }

    return this._in
  }

  get out() {
    if (this._out === null) {
      switch (binding.guessType(1)) {
        case TTY:
          this._out = new tty.WriteStream(1)
          break
        case NAMED_PIPE:
          this._out = new Pipe(1, { eagerOpen: false })
          this._out.unref()
          break
        default:
          this._out = fs.createWriteStream(null, { fd: 1, eagerOpen: false })
      }
    }

    return this._out
  }

  get err() {
    if (this._err === null) {
      switch (binding.guessType(2)) {
        case TTY:
          this._err = new tty.WriteStream(2)
          break
        case NAMED_PIPE:
          this._err = new Pipe(2, { eagerOpen: false })
          this._err.unref()
          break
        default:
          this._err = fs.createWriteStream(null, { fd: 2, eagerOpen: false })
      }
    }

    return this._err
  }
}

module.exports = new IO()
module.exports = require.addon()
const EventEmitter = require('bare-events')
const { Duplex } = require('bare-stream')
const binding = require('./binding')
const constants = require('./lib/constants')
const errors = require('./lib/errors')

const defaultReadBufferSize = 65536
const empty = Buffer.alloc(0)

const ipcHandle = Symbol.for('bare.ipc.handle')
const ipcAccept = Symbol.for('bare.ipc.accept')

module.exports = exports = class Pipe extends Duplex {
  constructor(path, opts = {}) {
    if (typeof path === 'object' && path !== null) {
      opts = path
      path = null
    }

    const {
      readBufferSize = defaultReadBufferSize,
      allowHalfOpen = true,
      eagerOpen = true,
      ipc = false
    } = opts

    super({ eagerOpen })

    this._state = 0

    this._allowHalfOpen = allowHalfOpen
    this._ipc = ipc

    this._fd = -1
    this._path = null

    this._pendingOpen = null
    this._pendingWrite = null
    this._pendingWriteSegments = null
    this._pendingWriteIdx = 0
    this._pendingFinal = null
    this._pendingDestroy = null

    this._handleQueue = []
    this._handleQueueSize = 0

    this._buffer = Buffer.alloc(readBufferSize)

    this._handle = binding.init(
      this._buffer,
      ipc,
      this,
      noop,
      this._onconnect,
      this._onwrite,
      this._onfinal,
      this._onread,
      this._onhandle,
      this._onclose
    )

    if (typeof path === 'number') {
      this.open(path)
    } else if (typeof path === 'string') {
      this.connect(path)
    }
  }

  get connecting() {
    return (this._state & constants.state.CONNECTING) !== 0
  }

  get pending() {
    return (this._state & constants.state.CONNECTED) === 0
  }

  get readyState() {
    if (this._state & constants.state.READABLE && this._state & constants.state.WRITABLE) {
      return 'open'
    }

    if (this._state & constants.state.READABLE) {
      return 'readOnly'
    }

    if (this._state & constants.state.WRITABLE) {
      return 'writeOnly'
    }

    return 'opening'
  }

  get [ipcHandle]() {
    return this._handle
  }

  open(fd, opts = {}, onconnect) {
    if (typeof opts === 'function') {
      onconnect = opts
      opts = {}
    }

    if (typeof fd === 'object' && fd !== null) {
      opts = fd || {}
      fd = opts.fd
    }

    try {
      const status = binding.open(this._handle, fd)

      this._state |= constants.state.CONNECTED
      this._fd = fd

      if (status & binding.READABLE) {
        this._state |= constants.state.READABLE
      } else {
        this.push(null)
      }

      if (status & binding.WRITABLE) {
        this._state |= constants.state.WRITABLE
      } else {
        this.end()
      }

      if (onconnect) this.once('connect', onconnect)

      queueMicrotask(() => this.emit('connect'))
    } catch (err) {
      queueMicrotask(() => {
        if (this._pendingOpen) this._pendingOpen(err)
        else this.destroy(err)
      })
    }

    return this
  }

  connect(path, opts = {}, onconnect) {
    if (this._state & constants.state.CONNECTING || this._state & constants.state.CONNECTED) {
      throw errors.PIPE_ALREADY_CONNECTED('Pipe is already connected')
    }

    this._state |= constants.state.CONNECTING

    if (typeof opts === 'function') {
      onconnect = opts
      opts = {}
    }

    if (typeof path === 'object' && path !== null) {
      opts = path || {}
      path = opts.path
    }

    try {
      binding.connect(this._handle, path)

      this._path = path

      if (onconnect) this.once('connect', onconnect)
    } catch (err) {
      queueMicrotask(() => {
        if (this._pendingOpen) this._pendingOpen(err)
        else this.destroy(err)
      })
    }

    return this
  }

  write(chunk, encoding, handle, cb) {
    if (typeof encoding === 'function') {
      cb = encoding
      encoding = undefined
      handle = null
    } else if (typeof encoding === 'object' && encoding !== null) {
      if (typeof handle === 'function') cb = handle
      handle = encoding
      encoding = undefined
    } else if (typeof handle === 'function') {
      cb = handle
      handle = null
    }

    if (handle) this._handleQueueSize++

    this._handleQueue.push(handle || null)

    if (encoding) return super.write(chunk, encoding, cb)

    return super.write(chunk, cb)
  }

  accept(target) {
    const handle = target[ipcHandle]

    if (handle === undefined) {
      throw errors.INVALID_IPC_TARGET('Target does not implement the IPC handle protocol')
    }

    binding.accept(this._handle, handle)

    if (typeof target[ipcAccept] === 'function') target[ipcAccept]()

    return target
  }

  ref() {
    binding.ref(this._handle)

    return this
  }

  unref() {
    binding.unref(this._handle)

    return this
  }

  [ipcAccept]() {
    this._onaccept()
  }

  _open(cb) {
    if (this._state & constants.state.CONNECTED) return cb(null)

    this._pendingOpen = cb
  }

  _read() {
    if ((this._state & constants.state.READING) === 0) {
      this._state |= constants.state.READING

      binding.resume(this._handle)
    }
  }

  _writev(batch, cb) {
    this._pendingWrite = cb

    if (this._handleQueueSize === 0) {
      this._handleQueue = []
      this._pendingWriteSegments = null

      binding.writev(
        this._handle,
        batch.map(({ chunk }) => chunk),
        null
      )
      return
    }

    const handles = this._handleQueue.splice(0, batch.length)

    // Fill any holes in the batch for messages that don't carry a handle. Each
    // message that does carry a handle decrements the handle queue size.
    for (let i = 0; i < batch.length; i++) {
      if (handles[i] === undefined) handles[i] = null
      else if (handles[i] !== null) this._handleQueueSize--
    }

    const segments = []

    let i = 0
    while (i < batch.length) {
      if (handles[i] !== null) {
        segments.push({ chunks: [batch[i]], handle: handles[i] })
        i++
      } else {
        const start = i
        while (i < batch.length && handles[i] === null) i++
        segments.push({ chunks: batch.slice(start, i), handle: null })
      }
    }

    this._pendingWriteSegments = segments
    this._pendingWriteIdx = 0

    this._writeNextSegment()
  }

  _writeNextSegment() {
    const segment = this._pendingWriteSegments[this._pendingWriteIdx]

    const chunks = []
    for (let i = 0; i < segment.chunks.length; i++) {
      chunks.push(segment.chunks[i].chunk)
    }

    let sendHandle = null
    if (segment.handle !== null) sendHandle = segment.handle[ipcHandle]

    binding.writev(this._handle, chunks, sendHandle)
  }

  _final(cb) {
    if (this._state & constants.state.READABLE && this._state & constants.state.WRITABLE) {
      this._pendingFinal = cb

      binding.end(this._handle)
    } else {
      cb(null)
    }
  }

  _predestroy() {
    if (this._state & constants.state.CLOSING) return
    this._state |= constants.state.CLOSING

    binding.close(this._handle)
  }

  _destroy(err, cb) {
    if (this._state & constants.state.CLOSING) return cb(err)
    this._state |= constants.state.CLOSING

    this._pendingDestroy = cb

    binding.close(this._handle)
  }

  _continueOpen(err) {
    if (this._pendingOpen === null) return
    const cb = this._pendingOpen
    this._pendingOpen = null
    cb(err)
  }

  _continueWrite(err) {
    if (this._pendingWrite === null) return

    if (this._pendingWriteSegments === null) {
      const cb = this._pendingWrite
      this._pendingWrite = null
      cb(err)
      return
    }

    this._pendingWriteIdx++

    if (err === null && this._pendingWriteIdx < this._pendingWriteSegments.length) {
      this._writeNextSegment()
      return
    }

    const cb = this._pendingWrite
    this._pendingWrite = null
    this._pendingWriteSegments = null
    this._pendingWriteIdx = 0
    cb(err)
  }

  _continueFinal(err) {
    if (this._pendingFinal === null) return
    const cb = this._pendingFinal
    this._pendingFinal = null
    cb(err)
  }

  _continueDestroy() {
    if (this._pendingDestroy === null) return
    const cb = this._pendingDestroy
    this._pendingDestroy = null
    cb(null)
  }

  _onconnect(err) {
    if (err) {
      if (this._pendingOpen) this._continueOpen(err)
      else this.destroy(err)
      return
    }

    this._state |= constants.state.CONNECTED | constants.state.READABLE | constants.state.WRITABLE
    this._state &= ~constants.state.CONNECTING
    this._continueOpen()

    this.emit('connect')
  }

  _onaccept() {
    this._state |= constants.state.CONNECTED | constants.state.READABLE | constants.state.WRITABLE
    this._continueOpen()
  }

  _onread(err, read) {
    if (err) {
      this.destroy(err)
      return
    }

    if (read === 0) {
      this.push(null)
      if (this._allowHalfOpen === false) this.end()
      return
    }

    const copy = Buffer.allocUnsafe(read)
    copy.set(this._buffer.subarray(0, read))

    if (this.push(copy) === false && this.destroying === false) {
      this._state &= ~constants.state.READING

      binding.pause(this._handle)
    }
  }

  _onhandle(type) {
    this.emit('handle', type)
  }

  _onwrite(err) {
    this._continueWrite(err)
  }

  _onfinal(err) {
    this._continueFinal(err === null || err.code === 'ENOTCONN' ? null : err)
  }

  _onclose() {
    this._continueDestroy()
  }

  _onspawn(readable, writable) {
    this._state |= constants.state.CONNECTED

    if (readable) {
      this._state |= constants.state.READABLE
    } else {
      this.push(null)
    }

    if (writable) {
      this._state |= constants.state.WRITABLE
    } else {
      this.end()
    }

    this._continueOpen()
  }
}

exports.Pipe = exports

exports.pipe = function pipe() {
  return binding.pipe()
}

exports.Server = class PipeServer extends EventEmitter {
  constructor(opts = {}, onconnection) {
    if (typeof opts === 'function') {
      onconnection = opts
      opts = {}
    }

    super()

    const {
      readBufferSize = defaultReadBufferSize,
      allowHalfOpen = true,
      pauseOnConnect = false,
      ipc = false
    } = opts

    this._state = 0

    this._readBufferSize = readBufferSize
    this._allowHalfOpen = allowHalfOpen
    this._pauseOnConnect = pauseOnConnect
    this._ipc = ipc

    this._path = null
    this._connections = new Set()

    this._error = null
    this._handle = null

    if (onconnection) this.on('connection', onconnection)
  }

  get listening() {
    return (this._state & constants.state.BOUND) !== 0
  }

  address() {
    if ((this._state & constants.state.BOUND) === 0) {
      return null
    }

    return this._path
  }

  listen(path, backlog = 511, opts = {}, onlistening) {
    if (this._state & constants.state.BINDING || this._state & constants.state.BOUND) {
      throw errors.SERVER_ALREADY_LISTENING('Server is already listening')
    }

    if (this._state & constants.state.CLOSING) {
      throw errors.SERVER_IS_CLOSED('Server is closed')
    }

    this._state |= constants.state.BINDING

    if (typeof backlog === 'function') {
      onlistening = backlog
      backlog = 511
    } else if (typeof opts === 'function') {
      onlistening = opts
      opts = {}
    }

    if (typeof path === 'object' && path !== null) {
      opts = path || {}
      path = opts.path
      backlog = opts.backlog || 511
    }

    this._handle = binding.init(
      empty,
      this._ipc,
      this,
      this._onconnection,
      noop,
      noop,
      noop,
      noop,
      noop,
      this._onclose
    )

    if (this._state & constants.state.UNREFED) binding.unref(this._handle)

    try {
      binding.bind(this._handle, path, backlog)

      this._path = path
      this._state |= constants.state.BOUND
      this._state &= ~constants.state.BINDING

      if (onlistening) this.once('listening', onlistening)

      queueMicrotask(() => this.emit('listening'))
    } catch (err) {
      this._error = err

      binding.close(this._handle)
    }

    return this
  }

  close(onclose) {
    if (onclose) this.once('close', onclose)

    if (this._state & constants.state.CLOSING) return
    this._state |= constants.state.CLOSING

    this._closeMaybe()

    return this
  }

  ref() {
    this._state &= ~constants.state.UNREFED

    if (this._handle !== null) binding.ref(this._handle)

    return this
  }

  unref() {
    this._state |= constants.state.UNREFED

    if (this._handle !== null) binding.unref(this._handle)

    return this
  }

  _closeMaybe() {
    if (this._state & constants.state.CLOSING && this._connections.size === 0) {
      if (this._handle !== null) binding.close(this._handle)
      else queueMicrotask(() => this.emit('close'))
    }
  }

  _onconnection(err) {
    if (err) {
      this.emit('error', err)
      return
    }

    if (this._state & constants.state.CLOSING) return

    const pipe = new exports.Pipe({
      readBufferSize: this._readBufferSize,
      allowHalfOpen: this._allowHalfOpen,
      eagerOpen: !this._pauseOnConnect,
      ipc: this._ipc
    })

    try {
      binding.accept(this._handle, pipe._handle)

      pipe._path = this._path
      pipe._onaccept()

      this._connections.add(pipe)

      pipe.on('close', () => {
        this._connections.delete(pipe)
        this._closeMaybe()
      })

      this.emit('connection', pipe)
    } catch (err) {
      pipe.destroy()

      throw err
    }
  }

  _onclose() {
    const err = this._error

    this._state &= ~constants.state.BINDING
    this._error = null
    this._handle = null

    if (err) this.emit('error', err)
    else this.emit('close')
  }
}

exports.constants = constants
exports.errors = errors

exports.createConnection = function createConnection(path, opts, onconnect) {
  if (typeof opts === 'function') {
    onconnect = opts
    opts = {}
  }

  if (typeof path === 'object' && path !== null) {
    opts = path || {}
    path = opts.path
  }

  return new exports.Pipe(opts).connect(path, opts, onconnect)
}

exports.createServer = function createServer(opts, onconnection) {
  return new exports.Server(opts, onconnection)
}

function noop() {}
const binding = require('../binding')

module.exports = {
  state: {
    CONNECTING: 0x1,
    CONNECTED: 0x2,
    BINDING: 0x4,
    BOUND: 0x8,
    READING: 0x10,
    CLOSING: 0x20,
    READABLE: 0x40,
    WRITABLE: 0x80,
    UNREFED: 0x100
  },
  handle: {
    NAMED_PIPE: binding.UV_NAMED_PIPE,
    TCP: binding.UV_TCP,
    UDP: binding.UV_UDP
  }
}
module.exports = class PipeError extends Error {
  constructor(msg, fn = PipeError, code = fn.name) {
    super(`${code}: ${msg}`)
    this.code = code

    if (Error.captureStackTrace) {
      Error.captureStackTrace(this, fn)
    }
  }

  get name() {
    return 'PipeError'
  }

  static PIPE_ALREADY_CONNECTED(msg) {
    return new PipeError(msg, PipeError.PIPE_ALREADY_CONNECTED)
  }

  static SERVER_ALREADY_LISTENING(msg) {
    return new PipeError(msg, PipeError.SERVER_ALREADY_LISTENING)
  }

  static SERVER_IS_CLOSED(msg) {
    return new PipeError(msg, PipeError.SERVER_IS_CLOSED)
  }

  static INVALID_IPC_TARGET(msg) {
    return new PipeError(msg, PipeError.INVALID_IPC_TARGET)
  }
}
{
  "name": "bare-pipe",
  "version": "4.2.0",
  "description": "Native I/O pipes for JavaScript",
  "exports": {
    "./package": "./package.json",
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    },
    "./constants": {
      "types": "./lib/constants.d.ts",
      "default": "./lib/constants.js"
    },
    "./errors": {
      "types": "./lib/errors.d.ts",
      "default": "./lib/errors.js"
    }
  },
  "files": [
    "index.js",
    "index.d.ts",
    "binding.c",
    "binding.js",
    "CMakeLists.txt",
    "lib",
    "prebuilds"
  ],
  "addon": true,
  "scripts": {
    "format": "prettier --write . && lunte --fix",
    "lint": "prettier --check . && lunte",
    "test": "brittle-bare --coverage test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-pipe.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-pipe/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-pipe#readme",
  "engines": {
    "bare": ">=1.16.0"
  },
  "dependencies": {
    "bare-events": "^2.0.0",
    "bare-stream": "^2.0.0"
  },
  "devDependencies": {
    "bare-path": "^3.0.0",
    "bare-tcp": "^2.4.1",
    "brittle": "^3.2.1",
    "cmake-bare": "^1.1.6",
    "lunte": "^1.8.0",
    "prettier": "^3.4.1",
    "prettier-config-holepunch": "^2.0.0"
  }
}
{
  "name": "bare-stdio",
  "version": "1.0.2",
  "description": "Standard input/output streams for Bare",
  "exports": {
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    },
    "./package": "./package.json",
    "./constants": "./lib/constants.js",
    "./errors": "./lib/errors.js"
  },
  "files": [
    "index.js",
    "index.d.ts",
    "binding.c",
    "binding.js",
    "CMakeLists.txt",
    "lib",
    "prebuilds"
  ],
  "addon": true,
  "scripts": {
    "format": "prettier --write . && lunte --fix",
    "lint": "prettier --check . && lunte",
    "test": "brittle-bare --coverage test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-stdio.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-stdio/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-stdio#readme",
  "dependencies": {
    "bare-fs": "^4.5.2",
    "bare-pipe": "^4.1.5",
    "bare-tty": "^5.0.3"
  },
  "devDependencies": {
    "bare-os": "^3.6.2",
    "bare-path": "^3.0.0",
    "bare-subprocess": "^3.1.4",
    "brittle": "^3.19.1",
    "cmake-bare": "^1.7.7",
    "lunte": "^1.4.0",
    "prettier": "^3.8.1",
    "prettier-config-holepunch": "^2.0.0",
    "tt-native": "^1.1.1"
  }
}
const stream = require('streamx')
const { ReadableStream, WritableStream } = require('./web')

const defaultEncoding = 'utf8'

module.exports = exports = stream.Stream

exports.pipeline = stream.pipeline

exports.isStream = stream.isStream
exports.isEnding = stream.isEnding
exports.isEnded = stream.isEnded
exports.isFinishing = stream.isFinishing
exports.isFinished = stream.isFinished
exports.isDisturbed = stream.isDisturbed

exports.isErrored = function isErrored(stream) {
  return exports.getStreamError(stream) !== null
}

exports.isReadable = function isReadable(stream) {
  return stream.readable && !stream.destroying && !exports.isEnded(stream)
}

exports.isWritable = function isWritable(stream) {
  return stream.writable && !stream.destroying && !exports.isFinishing(stream)
}

exports.getStreamError = stream.getStreamError

exports.addAbortSignal = function addAbortSignal(signal, stream) {
  function onAbort() {
    stream.destroy(signal.reason)
  }

  if (signal.aborted) onAbort()
  else signal.addEventListener('abort', onAbort)

  return stream
}

exports.Stream = exports

exports.Readable = class Readable extends stream.Readable {
  constructor(opts = {}) {
    super({
      ...opts,
      byteLength: null,
      byteLengthReadable: null,
      map: null,
      mapReadable: null
    })

    if (this._construct) this._open = this._construct

    if (this._read !== stream.Readable.prototype._read) {
      this._read = read.bind(this, this._read)
    }

    if (this._destroy !== stream.Stream.prototype._destroy) {
      this._destroy = destroy.bind(this, this._destroy)
    }
  }

  get closed() {
    return !exports.isReadable(this)
  }

  get errored() {
    return stream.getStreamError(this)
  }

  push(chunk, encoding) {
    if (typeof chunk === 'string') {
      chunk = Buffer.from(chunk, encoding || defaultEncoding)
    }

    return super.push(chunk)
  }

  unshift(chunk, encoding) {
    if (typeof chunk === 'string') {
      chunk = Buffer.from(chunk, encoding || defaultEncoding)
    }

    super.unshift(chunk)
  }

  static fromWeb(readableStream, opts = {}) {
    const stream = readableStream._stream

    if (opts.encoding) stream.setEncoding(opts.encoding)
    if (opts.signal) exports.addAbortSignal(opts.signal, stream)

    return stream
  }

  static toWeb(readable, opts = {}) {
    return new ReadableStream(readable, opts.strategy)
  }

  async [Symbol.asyncDispose]() {
    if (!this.destroyed) this.destroy()

    await new Promise((resolve) => exports.finished(this, resolve))
  }
}

exports.Writable = class Writable extends stream.Writable {
  constructor(opts = {}) {
    super({
      ...opts,
      byteLength: null,
      byteLengthWritable,
      map: null,
      mapWritable: null
    })

    if (this._construct) this._open = this._construct

    if (this._write !== stream.Writable.prototype._write) {
      this._write = write.bind(this, this._write)
    }

    if (this._destroy !== stream.Stream.prototype._destroy) {
      this._destroy = destroy.bind(this, this._destroy)
    }
  }

  get closed() {
    return !exports.isWritable(this)
  }

  get errored() {
    return stream.getStreamError(this)
  }

  write(chunk, encoding, cb) {
    if (typeof encoding === 'function') {
      cb = encoding
      encoding = null
    }

    if (typeof chunk === 'string') {
      encoding = encoding || defaultEncoding
      chunk = Buffer.from(chunk, encoding)
    } else {
      encoding = 'buffer'
    }

    const result = super.write({ chunk, encoding })

    if (cb) stream.Writable.drained(this).then(() => cb(null), cb)

    return result
  }

  end(chunk, encoding, cb) {
    if (typeof chunk === 'function') {
      cb = chunk
      chunk = null
    } else if (typeof encoding === 'function') {
      cb = encoding
      encoding = null
    }

    if (typeof chunk === 'string') {
      encoding = encoding || defaultEncoding
      chunk = Buffer.from(chunk, encoding || defaultEncoding)
    } else {
      encoding = 'buffer'
    }

    const result =
      chunk !== undefined && chunk !== null ? super.end({ chunk, encoding }) : super.end()

    if (cb) this.once('finish', () => cb(null))

    return result
  }

  static fromWeb(writableStream, opts = {}) {
    const stream = writableStream._stream

    if (opts.signal) exports.addAbortSignal(opts.signal, stream)

    return stream
  }

  static toWeb(writable) {
    return new WritableStream(writable)
  }

  async [Symbol.asyncDispose]() {
    if (!this.destroyed) this.destroy()

    await new Promise((resolve) => exports.finished(this, resolve))
  }
}

exports.Duplex = class Duplex extends stream.Duplex {
  constructor(opts = {}) {
    super({
      ...opts,
      byteLength: null,
      byteLengthReadable: null,
      byteLengthWritable,
      map: null,
      mapReadable: null,
      mapWritable: null
    })

    if (this._construct) this._open = this._construct

    if (this._read !== stream.Readable.prototype._read) {
      this._read = read.bind(this, this._read)
    }

    if (this._write !== stream.Duplex.prototype._write) {
      this._write = write.bind(this, this._write)
    }

    if (this._destroy !== stream.Stream.prototype._destroy) {
      this._destroy = destroy.bind(this, this._destroy)
    }
  }

  push(chunk, encoding) {
    if (typeof chunk === 'string') {
      chunk = Buffer.from(chunk, encoding || defaultEncoding)
    }

    return super.push(chunk)
  }

  unshift(chunk, encoding) {
    if (typeof chunk === 'string') {
      chunk = Buffer.from(chunk, encoding || defaultEncoding)
    }

    super.unshift(chunk)
  }

  write(chunk, encoding, cb) {
    if (typeof encoding === 'function') {
      cb = encoding
      encoding = null
    }

    if (typeof chunk === 'string') {
      encoding = encoding || defaultEncoding
      chunk = Buffer.from(chunk, encoding)
    } else {
      encoding = 'buffer'
    }

    const result = super.write({ chunk, encoding })

    if (cb) stream.Writable.drained(this).then(() => cb(null), cb)

    return result
  }

  end(chunk, encoding, cb) {
    if (typeof chunk === 'function') {
      cb = chunk
      chunk = null
    } else if (typeof encoding === 'function') {
      cb = encoding
      encoding = null
    }

    if (typeof chunk === 'string') {
      encoding = encoding || defaultEncoding
      chunk = Buffer.from(chunk, encoding)
    } else {
      encoding = 'buffer'
    }

    const result =
      chunk !== undefined && chunk !== null ? super.end({ chunk, encoding }) : super.end()

    if (cb) this.once('finish', () => cb(null))

    return result
  }

  static fromWeb({ readable: readableStream, writable: writableStream }, opts) {
    const readable = exports.Readable.fromWeb(readableStream, opts)
    const writable = exports.Writable.fromWeb(writableStream, opts)

    const duplex = new exports.Duplex({
      write(data, encoding, cb) {
        writable.write(data, encoding, cb)
      }
    })

    readable
      .on('data', (data) => duplex.push(data))
      .on('end', () => duplex.push(null))
      .on('error', (err) => duplex.destroy(err))

    writable.on('finish', () => duplex.end()).on('error', (err) => duplex.destroy(err))

    return duplex
  }

  static toWeb(duplex) {
    const readableStream = exports.Readable.toWeb(duplex)
    const writableStream = exports.Writable.toWeb(duplex)

    return { readable: readableStream, writable: writableStream }
  }
}

class DuplexSide extends exports.Duplex {
  constructor(opts) {
    super(opts)

    this._otherSide = null
    this._cb = null
  }

  _read() {
    const cb = this._cb
    if (!cb) return

    this._cb = null
    cb()
  }

  _write(chunk, encoding, cb) {
    this._otherSide.push(chunk, encoding)
    this._otherSide._cb = cb
  }

  _final(cb) {
    this._otherSide.on('end', cb)
    this._otherSide.push(null)
  }
}

exports.duplexPair = function duplexPair(opts) {
  const sideA = new DuplexSide(opts)
  const sideB = new DuplexSide(opts)

  sideA._otherSide = sideB
  sideB._otherSide = sideA

  return [sideA, sideB]
}

exports.Transform = class Transform extends stream.Transform {
  constructor(opts = {}) {
    super({
      ...opts,
      byteLength: null,
      byteLengthReadable: null,
      byteLengthWritable,
      map: null,
      mapReadable: null,
      mapWritable: null
    })

    if (this._transform !== stream.Transform.prototype._transform) {
      this._transform = transform.bind(this, this._transform)
    } else {
      this._transform = passthrough
    }
  }

  push(chunk, encoding) {
    if (typeof chunk === 'string') {
      chunk = Buffer.from(chunk, encoding || defaultEncoding)
    }

    return super.push(chunk)
  }

  unshift(chunk, encoding) {
    if (typeof chunk === 'string') {
      chunk = Buffer.from(chunk, encoding || defaultEncoding)
    }

    super.unshift(chunk)
  }

  write(chunk, encoding, cb) {
    if (typeof encoding === 'function') {
      cb = encoding
      encoding = null
    }

    if (typeof chunk === 'string') {
      encoding = encoding || defaultEncoding
      chunk = Buffer.from(chunk, encoding)
    } else {
      encoding = 'buffer'
    }

    const result = super.write({ chunk, encoding })

    if (cb) stream.Writable.drained(this).then(() => cb(null), cb)

    return result
  }

  end(chunk, encoding, cb) {
    if (typeof chunk === 'function') {
      cb = chunk
      chunk = null
    } else if (typeof encoding === 'function') {
      cb = encoding
      encoding = null
    }

    if (typeof chunk === 'string') {
      encoding = encoding || defaultEncoding
      chunk = Buffer.from(chunk, encoding)
    } else {
      encoding = 'buffer'
    }

    const result =
      chunk !== undefined && chunk !== null ? super.end({ chunk, encoding }) : super.end()

    if (cb) this.once('finish', () => cb(null))

    return result
  }
}

exports.PassThrough = class PassThrough extends exports.Transform {}

exports.finished = function finished(stream, opts, cb) {
  if (typeof opts === 'function') {
    cb = opts
    opts = {}
  }

  if (!opts) opts = {}

  const { cleanup = false } = opts

  const done = () => {
    cb(exports.getStreamError(stream, { all: true }))

    if (cleanup) detach()
  }

  const detach = () => {
    stream.off('close', done)
    stream.off('error', noop)
  }

  if (stream.destroyed) {
    done()
  } else {
    stream.on('close', done)
    stream.on('error', noop)
  }

  return detach
}

function read(read, cb) {
  read.call(this, 65536)

  cb(null)
}

function write(write, data, cb) {
  write.call(this, data.chunk, data.encoding, cb)
}

function transform(transform, data, cb) {
  transform.call(this, data.chunk, data.encoding, cb)
}

function destroy(destroy, cb) {
  destroy.call(this, exports.getStreamError(this), cb)
}

function passthrough(data, cb) {
  cb(null, data.chunk)
}

function byteLengthWritable(data) {
  return data.chunk.byteLength
}

function noop() {}
{
  "name": "bare-stream",
  "version": "2.13.1",
  "description": "Streaming data for JavaScript",
  "exports": {
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    },
    "./package": "./package.json",
    "./promises": "./promises.js",
    "./web": {
      "types": "./web.d.ts",
      "default": "./web.js"
    },
    "./global": {
      "types": "./global.d.ts",
      "default": "./global.js"
    }
  },
  "files": [
    "index.js",
    "index.d.ts",
    "promises.js",
    "web.js",
    "web.d.ts",
    "global.js",
    "global.d.ts"
  ],
  "scripts": {
    "format": "prettier --write . && lunte --fix",
    "lint": "prettier --check . && lunte",
    "test": "brittle-bare --coverage test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-stream.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-stream/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-stream#readme",
  "dependencies": {
    "streamx": "^2.25.0",
    "teex": "^1.0.1"
  },
  "devDependencies": {
    "bare-abort-controller": "^1.1.0",
    "bare-buffer": "^3.0.0",
    "bare-events": "^2.5.4",
    "brittle": "^3.5.2",
    "lunte": "^1.6.0",
    "prettier": "^3.3.3",
    "prettier-config-holepunch": "^2.0.0"
  },
  "peerDependencies": {
    "bare-abort-controller": "*",
    "bare-buffer": "*",
    "bare-events": "*"
  },
  "peerDependenciesMeta": {
    "bare-abort-controller": {
      "optional": true
    },
    "bare-buffer": {
      "optional": true
    },
    "bare-events": {
      "optional": true
    }
  }
}
const { Readable, Writable, Transform, getStreamError, isStreamx, isDisturbed } = require('streamx')
const tee = require('teex')

const readableKind = Symbol.for('bare.stream.readable.kind')
const writableKind = Symbol.for('bare.stream.writable.kind')
const transformKind = Symbol.for('bare.stream.transform.kind')

// https://streams.spec.whatwg.org/#readablestreamdefaultreader
exports.ReadableStreamDefaultReader = class ReadableStreamDefaultReader {
  constructor(stream) {
    this._stream = stream
    this._stream._stream.once('close', onclose).once('error', onerror)

    const closed = Promise.withResolvers()

    // Avoid unhandled exceptions
    closed.promise.catch(noop)

    this._closed = closed

    function onclose() {
      closed.resolve()
    }

    function onerror(err) {
      closed.reject(err)
    }
  }

  get closed() {
    return this._closed.promise
  }

  read() {
    const stream = this._stream._stream

    return new Promise((resolve, reject) => {
      const err = getStreamError(stream)

      if (err) return reject(err)

      if (stream.destroyed) {
        return resolve({ value: undefined, done: true })
      }

      const value = stream.read()

      if (value !== null) {
        return resolve({ value, done: false })
      }

      stream.once('readable', onreadable).once('close', onclose).once('error', onerror)

      function onreadable() {
        const value = stream.read()

        ondone(null, value === null ? { value: undefined, done: true } : { value, done: false })
      }

      function onclose() {
        ondone(null, { value: undefined, done: true })
      }

      function onerror(err) {
        ondone(err, null)
      }

      function ondone(err, value) {
        stream.off('readable', onreadable).off('close', onclose).off('error', onerror)

        if (err) reject(err)
        else resolve(value)
      }
    })
  }

  releaseLock() {
    this._closed.reject(new TypeError('Reader was released'))
    this._stream._releaseLock()
    this._stream = null
  }

  cancel(reason = new TypeError('Stream was cancelled')) {
    const stream = this._stream._stream

    if (stream.destroyed) return Promise.resolve()

    return new Promise((resolve) =>
      stream.once('close', resolve).once('error', noop).destroy(reason)
    )
  }
}

// https://streams.spec.whatwg.org/#readablestreamdefaultcontroller
exports.ReadableStreamDefaultController = class ReadableStreamDefaultController {
  constructor(stream) {
    this._stream = stream
  }

  get desiredSize() {
    const stream = this._stream._stream

    return stream._readableState.highWaterMark - stream._readableState.buffered
  }

  enqueue(data) {
    this._stream._stream.push(data)
  }

  close() {
    this._stream._stream.push(null)
  }

  error(err) {
    this._stream._stream.destroy(err)
  }
}

// https://streams.spec.whatwg.org/#readablestream
class ReadableStream {
  static get [readableKind]() {
    return 0 // Compatibility version
  }

  static from(iterable) {
    return new ReadableStream(Readable.from(iterable))
  }

  constructor(underlyingSource = {}, queuingStrategy) {
    if (isStreamx(underlyingSource)) {
      this._stream = underlyingSource
    } else {
      if (queuingStrategy === undefined) {
        queuingStrategy = new exports.CountQueuingStrategy()
      }

      const { start, pull, cancel } = underlyingSource
      const { highWaterMark = 1, size = defaultSize } = queuingStrategy

      this._stream = new Readable({ highWaterMark, byteLength: size })

      const controller = new exports.ReadableStreamDefaultController(this)

      try {
        let starting = Promise.resolve()

        if (start) starting = forwardError(start.call(this, controller), controller)

        if (pull) {
          this._stream._read = this._read.bind(this, starting, pull.bind(this, controller))
        }

        if (cancel) {
          this._stream.once('error', cancel.bind(this))
        }
      } catch (err) {
        controller.error(err)
      }
    }

    this._reader = null
  }

  get [readableKind]() {
    return ReadableStream[readableKind]
  }

  get locked() {
    return this._reader !== null
  }

  getReader() {
    if (this.locked) throw new TypeError('Stream is locked')

    this._reader = new exports.ReadableStreamDefaultReader(this)

    return this._reader
  }

  cancel(reason = new TypeError('Stream was cancelled')) {
    const stream = this._stream

    if (stream.destroyed) return Promise.resolve()

    if (this.locked) return Promise.reject(new TypeError('Stream is locked'))

    return new Promise((resolve) =>
      stream.once('close', resolve).once('error', noop).destroy(reason)
    )
  }

  tee() {
    const [a, b] = tee(this._stream)

    return [new ReadableStream(a), new ReadableStream(b)]
  }

  pipeTo(destination) {
    return new Promise((resolve, reject) =>
      this._stream.pipe(destination._stream, (err) => {
        err ? reject(err) : resolve()
      })
    )
  }

  [Symbol.asyncIterator]() {
    return this._stream[Symbol.asyncIterator]()
  }

  _releaseLock() {
    this._reader = null
  }

  async _read(starting, pull, cb) {
    await starting

    let err = null
    try {
      await pull()
    } catch (e) {
      err = e
    }
    cb(err)
  }
}

function defaultSize() {
  return 1
}

exports.ReadableStream = ReadableStream

// https://streams.spec.whatwg.org/#countqueuingstrategy
exports.CountQueuingStrategy = class CountQueuingStrategy {
  constructor(opts = {}) {
    const { highWaterMark = 1 } = opts

    this.highWaterMark = highWaterMark
  }

  size(chunk) {
    return 1
  }
}

// https://streams.spec.whatwg.org/#bytelengthqueuingstrategy
exports.ByteLengthQueuingStrategy = class ByteLengthQueuingStrategy {
  constructor(opts = {}) {
    const { highWaterMark = 16384 } = opts

    this.highWaterMark = highWaterMark
  }

  size(chunk) {
    return chunk.byteLength
  }
}

exports.isReadableStream = function isReadableStream(value) {
  if (value instanceof ReadableStream) return true

  return (
    typeof value === 'object' &&
    value !== null &&
    value[readableKind] === ReadableStream[readableKind]
  )
}

// https://streams.spec.whatwg.org/#readablestream-errored
exports.isReadableStreamErrored = function isReadableStreamErrored(stream) {
  return getStreamError(stream._stream) !== null
}

// https://streams.spec.whatwg.org/#is-readable-stream-disturbed
exports.isReadableStreamDisturbed = function isReadableStreamDisturbed(stream) {
  return isDisturbed(stream._stream)
}

// https://streams.spec.whatwg.org/#writablestreamdefaultwriter
exports.WritableStreamDefaultWriter = class WritableStreamDefaultWriter {
  constructor(stream) {
    this._stream = stream
    this._stream._stream.once('close', onclose).once('error', onerror)

    const closed = Promise.withResolvers()

    // Avoid unhandled exceptions
    closed.promise.catch(noop)

    this._closed = closed

    function onclose() {
      closed.resolve()
    }

    function onerror(err) {
      closed.reject(err)
    }
  }

  get desiredSize() {
    const stream = this._stream._stream

    return stream._writableState.highWaterMark - stream._writableState.buffered
  }

  get closed() {
    return this._closed.promise
  }

  get ready() {
    const stream = this._stream._stream

    if (getStreamError(stream)) return Promise.reject()

    return Writable.drained(stream).then()
  }

  async write(chunk) {
    const stream = this._stream._stream

    let err = getStreamError(stream)
    if (err) return Promise.reject(err)

    stream.write(chunk)

    await Writable.drained(stream)

    err = getStreamError(stream)
    if (err) return Promise.reject(err)
  }

  releaseLock() {
    this._closed.reject(new TypeError('Writer was released'))
    this._stream._releaseLock()
    this._stream = null
  }

  close() {
    const stream = this._stream._stream

    if (stream.destroyed) return Promise.resolve()

    return new Promise((resolve) => stream.once('close', resolve).end())
  }

  abort(reason = new TypeError('Stream was aborted')) {
    const stream = this._stream._stream

    if (stream.destroyed) return Promise.resolve()

    return new Promise((resolve) => stream.once('close', resolve).destroy(reason))
  }
}

// https://streams.spec.whatwg.org/#writablestreamdefaultcontroller
exports.WritableStreamDefaultController = class WritableStreamDefaultController {
  constructor(stream) {
    this._stream = stream
  }

  error(err) {
    this._stream._stream.destroy(err)
  }
}

// https://streams.spec.whatwg.org/#writablestream
class WritableStream {
  static get [writableKind]() {
    return 0 // Compatibility version
  }

  constructor(underlyingSink = {}, queuingStrategy = {}) {
    if (isStreamx(underlyingSink)) {
      this._stream = underlyingSink
    } else {
      if (queuingStrategy === undefined) {
        queuingStrategy = new exports.CountQueuingStrategy()
      }

      const { start, write, close, abort } = underlyingSink
      const { highWaterMark = 1, size = defaultSize } = queuingStrategy

      this._stream = new Writable({ highWaterMark, byteLength: size })

      const controller = new exports.WritableStreamDefaultController(this)

      this._controller = controller

      try {
        let starting = Promise.resolve()

        if (start) starting = forwardError(start.call(this, controller), controller)

        if (write) {
          this._stream._write = this._write.bind(this, starting, write.bind(this))
        }

        if (close) {
          this._stream._destroy = this._destroy.bind(this, close.call(this))
        }

        if (abort) {
          this._stream.once('error', abort.bind(this))
        }
      } catch (err) {
        controller.error(err)
      }
    }

    this._writer = null
  }

  get [writableKind]() {
    return WritableStream[writableKind]
  }

  get locked() {
    return this._writer !== null
  }

  getWriter() {
    if (this.locked) throw new TypeError('Stream is locked')

    this._writer = new exports.WritableStreamDefaultWriter(this)

    return this._writer
  }

  abort(reason = new TypeError('Stream was aborted')) {
    if (this._stream.destroyed) return Promise.resolve()

    if (this.locked) return Promise.reject(new TypeError('Stream is locked'))

    return new Promise((resolve) => this._stream.once('close', resolve).destroy(reason))
  }

  close() {
    if (this._stream.destroyed) return Promise.resolve()

    if (this.locked) return Promise.reject(new TypeError('Stream is locked'))

    return new Promise((resolve) => this._stream.once('close', resolve).end())
  }

  _releaseLock() {
    this._writer = null
  }

  async _write(starting, write, data, cb) {
    await starting

    let err = null
    try {
      await write(data, this._controller)
    } catch (e) {
      err = e
    }
    cb(err)
  }

  async _destroy(closing, cb) {
    let err = null
    try {
      await closing
    } catch (e) {
      err = e
    }
    cb(err)
  }
}

exports.WritableStream = WritableStream

exports.isWritableStream = function isWritableStream(value) {
  if (value instanceof WritableStream) return true

  return (
    typeof value === 'object' &&
    value !== null &&
    value[writableKind] === WritableStream[writableKind]
  )
}

// https://streams.spec.whatwg.org/#transformstreamdefaultcontroller
exports.TransformStreamDefaultController = class TransformStreamDefaultController {
  constructor(stream) {
    this._stream = stream
  }

  get desiredSize() {
    const stream = this._stream._stream

    return stream._readableState.highWaterMark - stream._readableState.buffered
  }

  enqueue(data) {
    this._stream._stream.push(data)
  }

  error(err) {
    this._stream._stream.destroy(err)
  }

  terminate() {
    const stream = this._stream._stream

    stream.push(null)
    stream.destroy(new TypeError('Stream has been terminated'))
  }
}

// https://streams.spec.whatwg.org/#transformstream
class TransformStream {
  static get [transformKind]() {
    return 0 // Compatibility version
  }

  constructor(transformer = {}, writableStrategy = {}, readableStrategy = {}) {
    if (isStreamx(transformer)) {
      this._stream = transformer
    } else {
      const { start, transform, flush } = transformer

      this._stream = new Transform({ ...writableStrategy, ...readableStrategy })

      const controller = new exports.TransformStreamDefaultController(this)

      this._controller = controller

      try {
        let starting = Promise.resolve()

        if (start) starting = forwardError(start.call(this, controller), controller)

        if (transform) {
          this._stream._transform = this._transform.bind(this, starting, transform.bind(this))
        }

        if (flush) {
          this._stream._flush = this._flush.bind(this, flush.call(this, this._controller))
        }
      } catch (err) {
        controller.error(err)
      }
    }

    this._writable = new WritableStream(this._stream)
    this._readable = new ReadableStream(this._stream)
  }

  get [transformKind]() {
    return TransformStream[transformKind]
  }

  get writable() {
    return this._writable
  }

  get readable() {
    return this._readable
  }

  async _transform(starting, transform, data, cb) {
    await starting

    let err = null
    try {
      await transform(data, this._controller)
    } catch (e) {
      err = e
    }
    cb(err)
  }

  async _flush(flush, cb) {
    let err = null
    try {
      await flush
    } catch (e) {
      err = e
    }
    cb(err)
  }
}

exports.TransformStream = TransformStream

exports.isTransformStream = function isTransformStream(value) {
  if (value instanceof TransformStream) return true

  return (
    typeof value === 'object' &&
    value !== null &&
    value[transformKind] === TransformStream[transformKind]
  )
}

async function forwardError(promise, controller) {
  try {
    await promise
  } catch (err) {
    controller.error(err)
  }
}

function noop() {}
module.exports = require.addon()
const { Readable, Writable } = require('bare-stream')
const Signal = require('bare-signals')
const binding = require('./binding')
const constants = require('./lib/constants')

const defaultReadBufferSize = 65536
const empty = Buffer.alloc(0)

exports.ReadStream = class TTYReadStream extends Readable {
  constructor(fd, opts = {}) {
    super()

    const { readBufferSize = defaultReadBufferSize, allowHalfOpen = true } = opts

    this._fd = fd
    this._state = 0
    this._allowHalfOpen = allowHalfOpen
    this._buffer = Buffer.alloc(readBufferSize)

    this._pendingDestroy = null

    this._handle = binding.init(fd, this._buffer, this, noop, this._onread, this._onclose)
  }

  get fd() {
    return this._fd
  }

  get isTTY() {
    return true
  }

  setMode(mode) {
    binding.setMode(this._handle, mode)
    return this
  }

  setRawMode(enabled) {
    return this.setMode(enabled ? constants.mode.RAW : constants.mode.NORMAL)
  }

  _read() {
    if ((this._state & constants.state.READING) === 0) {
      this._state |= constants.state.READING

      binding.resume(this._handle)
    }
  }

  _predestroy() {
    if (this._state & constants.state.CLOSING) return
    this._state |= constants.state.CLOSING

    binding.close(this._handle)
  }

  _destroy(err, cb) {
    if (this._state & constants.state.CLOSING) return cb(err)
    this._state |= constants.state.CLOSING

    this._pendingDestroy = cb

    binding.close(this._handle)
  }

  _continueDestroy() {
    if (this._pendingDestroy === null) return
    const cb = this._pendingDestroy
    this._pendingDestroy = null
    cb(null)
  }

  _onread(err, read) {
    if (err) {
      this.destroy(err)
      return
    }

    if (read === 0) {
      this.push(null)
      if (this._allowHalfOpen === false) this.end()
      return
    }

    const copy = Buffer.allocUnsafe(read)
    copy.set(this._buffer.subarray(0, read))

    if (this.push(copy) === false && this.destroying === false) {
      this._state &= ~constants.state.READING

      binding.pause(this._handle)
    }
  }

  _onclose() {
    this._handle = null

    this._continueDestroy()
  }
}

exports.WriteStream = class TTYWriteStream extends Writable {
  constructor(fd, opts = {}) {
    super()

    this._fd = fd
    this._state = 0
    this._size = null

    this._pendingWrite = null
    this._pendingDestroy = null

    this._handle = binding.init(fd, empty, this, this._onwrite, noop, this._onclose)

    this._size = this.getWindowSize()

    if (TTYWriteStream._streams.size === 0) TTYWriteStream._resize.start()

    TTYWriteStream._streams.add(this)
  }

  get fd() {
    return this._fd
  }

  get isTTY() {
    return true
  }

  get columns() {
    return this._size[0]
  }

  get rows() {
    return this._size[1]
  }

  getWindowSize() {
    return binding.getWindowSize(this._handle)
  }

  _writev(batch, cb) {
    this._pendingWrite = [cb, batch]

    binding.writev(
      this._handle,
      batch.map(({ chunk }) => chunk)
    )
  }

  _predestroy() {
    if (this._state & constants.state.CLOSING) return
    this._state |= constants.state.CLOSING

    binding.close(this._handle)

    TTYWriteStream._streams.delete(this)

    if (TTYWriteStream._streams.size === 0) TTYWriteStream._resize.stop()
  }

  _destroy(err, cb) {
    if (this._state & constants.state.CLOSING) return cb(err)
    this._state |= constants.state.CLOSING

    this._pendingDestroy = cb

    binding.close(this._handle)

    TTYWriteStream._streams.delete(this)

    if (TTYWriteStream._streams.size === 0) TTYWriteStream._resize.stop()
  }

  _continueWrite(err) {
    if (this._pendingWrite === null) return
    const cb = this._pendingWrite[0]
    this._pendingWrite = null
    cb(err)
  }

  _continueDestroy() {
    if (this._pendingDestroy === null) return
    const cb = this._pendingDestroy
    this._pendingDestroy = null
    cb(null)
  }

  _onwrite(err) {
    this._continueWrite(err)
  }

  _onclose() {
    this._handle = null

    this._continueDestroy()
  }

  _onresize() {
    this._size = this.getWindowSize()

    this.emit('resize')
  }

  static _streams = new Set()

  static _resize = new Signal('SIGWINCH')
}

exports.constants = constants

exports.isTTY = binding.isTTY

exports.isatty = exports.isTTY // For Node.js compatibility

exports.WriteStream._resize
  .on('signal', () => {
    for (const stream of exports.WriteStream._streams) {
      stream._onresize()
    }
  })
  .unref()

function noop() {}
const binding = require('../binding')

module.exports = exports = {
  mode: {
    NORMAL: binding.MODE_NORMAL,
    RAW: binding.MODE_RAW,
    IO: binding.MODE_IO || 0
  },

  state: {
    READING: 0x1,
    CLOSING: 0x2
  }
}

/** @deprecated Use `mode.NORMAL` */
exports.MODE_NORMAL = exports.mode.NORMAL
/** @deprecated Use `mode.RAW` */
exports.MODE_RAW = exports.mode.RAW
/** @deprecated Use `mode.IO` */
exports.MODE_IO = exports.mode.IO
{
  "name": "bare-tty",
  "version": "5.1.0",
  "description": "Native TTY streams for JavaScript",
  "exports": {
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    },
    "./package": "./package.json",
    "./constants": "./lib/constants.js"
  },
  "files": [
    "index.js",
    "index.d.ts",
    "binding.c",
    "binding.js",
    "CMakeLists.txt",
    "lib",
    "prebuilds"
  ],
  "addon": true,
  "scripts": {
    "test": "prettier . --check && bare test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-tty.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-tty/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-tty#readme",
  "engines": {
    "bare": ">=1.16.0"
  },
  "dependencies": {
    "bare-events": "^2.2.0",
    "bare-signals": "^4.0.0",
    "bare-stream": "^2.0.0"
  },
  "devDependencies": {
    "brittle": "^3.2.1",
    "cmake-bare": "^1.1.6",
    "prettier": "^3.4.1",
    "prettier-config-holepunch": "^2.0.0"
  }
}
module.exports = require.addon()
const binding = require('./binding')

const t = binding.constants

class Type {
  constructor(type) {
    this._type = type
  }

  isUndefined() {
    return this._type === t.UNDEFINED
  }

  isNull() {
    return this._type === t.NULL
  }

  isBoolean() {
    return this._type === t.BOOLEAN
  }

  isNumber() {
    return (this._type & 0xff) === t.NUMBER
  }

  isInt32() {
    return (this._type & (0xff | t.INT32)) === (t.NUMBER | t.INT32)
  }

  isUint32() {
    return (this._type & (0xff | t.UINT32)) === (t.NUMBER | t.UINT32)
  }

  isString() {
    return this._type === t.STRING
  }

  isSymbol() {
    return this._type === t.SYMBOL
  }

  isObject() {
    return (this._type & 0xff) === t.OBJECT
  }

  isArray() {
    return this._type === (t.OBJECT | t.ARRAY)
  }

  isArguments() {
    return this._type === (t.OBJECT | t.ARGUMENTS)
  }

  isDate() {
    return this._type === (t.OBJECT | t.DATE)
  }

  isRegExp() {
    return this._type === (t.OBJECT | t.REGEXP)
  }

  isError() {
    return this._type === (t.OBJECT | t.ERROR)
  }

  isPromise() {
    return this._type === (t.OBJECT | t.PROMISE)
  }

  isProxy() {
    return this._type === (t.OBJECT | t.PROXY)
  }

  isGenerator() {
    return this._type === (t.OBJECT | t.GENERATOR)
  }

  isMap() {
    return this._type === (t.OBJECT | t.MAP)
  }

  isSet() {
    return this._type === (t.OBJECT | t.SET)
  }

  isWeakMap() {
    return this._type === (t.OBJECT | t.WEAK_MAP)
  }

  isWeakSet() {
    return this._type === (t.OBJECT | t.WEAK_SET)
  }

  isWeakRef() {
    return this._type === (t.OBJECT | t.WEAK_REF)
  }

  isArrayBuffer() {
    return this._type === (t.OBJECT | t.ARRAYBUFFER)
  }

  isSharedArrayBuffer() {
    return this._type === (t.OBJECT | t.SHAREDARRAYBUFFER)
  }

  isTypedArray() {
    return (this._type & 0xffff) === (t.OBJECT | t.TYPEDARRAY)
  }

  isInt8Array() {
    return this._type === (t.OBJECT | t.TYPEDARRAY | t.INT8ARRAY)
  }

  isUint8Array() {
    return this._type === (t.OBJECT | t.TYPEDARRAY | t.UINT8ARRAY)
  }

  isUint8ClampedArray() {
    return this._type === (t.OBJECT | t.TYPEDARRAY | t.UINT8CLAMPEDARRAY)
  }

  isInt16Array() {
    return this._type === (t.OBJECT | t.TYPEDARRAY | t.INT16ARRAY)
  }

  isUint16Array() {
    return this._type === (t.OBJECT | t.TYPEDARRAY | t.UINT16ARRAY)
  }

  isInt32Array() {
    return this._type === (t.OBJECT | t.TYPEDARRAY | t.INT32ARRAY)
  }

  isUint32Array() {
    return this._type === (t.OBJECT | t.TYPEDARRAY | t.UINT32ARRAY)
  }

  isFloat16Array() {
    return this._type === (t.OBJECT | t.TYPEDARRAY | t.FLOAT16ARRAY)
  }

  isFloat32Array() {
    return this._type === (t.OBJECT | t.TYPEDARRAY | t.FLOAT32ARRAY)
  }

  isFloat64Array() {
    return this._type === (t.OBJECT | t.TYPEDARRAY | t.FLOAT64ARRAY)
  }

  isBigInt64Array() {
    return this._type === (t.OBJECT | t.TYPEDARRAY | t.BIGINT64ARRAY)
  }

  isBigUint64Array() {
    return this._type === (t.OBJECT | t.TYPEDARRAY | t.BIGUINT64ARRAY)
  }

  isDataView() {
    return this._type === (t.OBJECT | t.DATAVIEW)
  }

  isModuleNamespace() {
    return this._type === (t.OBJECT | t.MODULE_NAMESPACE)
  }

  isFunction() {
    return (this._type & 0xff) === t.FUNCTION
  }

  isAsyncFunction() {
    return (
      (this._type & (0xff | t.ASYNC_FUNCTION)) ===
      (t.FUNCTION | t.ASYNC_FUNCTION)
    )
  }

  isGeneratorFunction() {
    return (
      (this._type & (0xff | t.GENERATOR_FUNCTION)) ===
      (t.FUNCTION | t.GENERATOR_FUNCTION)
    )
  }

  isExternal() {
    return this._type === t.EXTERNAL
  }

  isBigInt() {
    return this._type === t.BIGINT
  }
}

module.exports = exports = function type(value) {
  switch (typeof value) {
    case 'undefined':
      return new Type(t.UNDEFINED)
    case 'boolean':
      return new Type(t.BOOLEAN)
    case 'number':
      return new Type(
        Number.isSafeInteger(value) ? binding.type(value) : t.NUMBER
      )
    case 'string':
      return new Type(t.STRING)
    case 'symbol':
      return new Type(t.SYMBOL)
    case 'object':
      return new Type(value === null ? t.NULL : binding.type(value))
    case 'function':
      return new Type(binding.type(value))
    case 'bigint':
      return new Type(t.BIGINT)
  }
}

exports.createTag = function createTag(...components) {
  const tag = new Uint32Array(4)
  for (let i = 0; i < 4; i++) tag[i] = components[i] || 0
  return tag
}

exports.addTag = function addTag(object, tag) {
  binding.addTag(object, tag)
}

exports.checkTag = function checkTag(object, tag) {
  return binding.checkTag(object, tag)
}
{
  "name": "bare-type",
  "version": "1.1.0",
  "description": "Cross-realm type predicates for Bare",
  "exports": {
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    },
    "./package": "./package.json"
  },
  "files": [
    "index.js",
    "index.d.ts",
    "binding.c",
    "binding.js",
    "CMakeLists.txt",
    "prebuilds"
  ],
  "addon": true,
  "scripts": {
    "test": "prettier . --check && bare test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-type.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-type/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-type#readme",
  "engines": {
    "bare": ">=1.2.0"
  },
  "devDependencies": {
    "brittle": "^3.4.0",
    "cmake-bare": "^1.1.6",
    "prettier": "^3.4.1",
    "prettier-config-standard": "^7.0.0"
  }
}
module.exports = require.addon()
const path = require('bare-path')
const binding = require('./binding')
const errors = require('./lib/errors')
const URLSearchParams = require('./lib/url-search-params')

const kind = Symbol.for('bare.url.kind')

const isWindows = Bare.platform === 'win32'

class URL {
  static get [kind]() {
    return 0 // Compatibility version
  }

  constructor(input, base, opts = {}) {
    if (arguments.length === 0) throw errors.INVALID_URL()

    input = String(input)

    if (base !== undefined) base = String(base)

    this._components = new Uint32Array(8)

    this._parse(input, base, opts.throw !== false)

    if (this._href) this._params = new URLSearchParams(this.search, this)
  }

  get [kind]() {
    return URL[kind]
  }

  // https://url.spec.whatwg.org/#dom-url-href

  get href() {
    return this._href
  }

  set href(value) {
    this._update(value)

    this._params._parse(this.search)
  }

  // https://url.spec.whatwg.org/#dom-url-protocol

  get protocol() {
    return this._slice(0, this._components[0]) + ':'
  }

  set protocol(value) {
    this._update(this._replace(value.replace(/:+$/, ''), 0, this._components[0]))
  }

  // https://url.spec.whatwg.org/#dom-url-username

  get username() {
    return this._slice(this._components[0] + 3 /* :// */, this._components[1])
  }

  set username(value) {
    if (cannotHaveCredentialsOrPort(this)) {
      return
    }

    if (this.username === '') value += '@'

    this._update(this._replace(value, this._components[0] + 3 /* :// */, this._components[1]))
  }

  // https://url.spec.whatwg.org/#dom-url-password

  get password() {
    return this._href.slice(this._components[1] + 1 /* : */, this._components[2] - 1 /* @ */)
  }

  set password(value) {
    if (cannotHaveCredentialsOrPort(this)) {
      return
    }

    let start = this._components[1] + 1 /* : */
    let end = this._components[2] - 1 /* @ */

    if (this.password === '') {
      value = ':' + value
      start--
    }

    if (this.username === '') {
      value += '@'
      end++
    }

    this._update(this._replace(value, start, end))
  }

  // https://url.spec.whatwg.org/#dom-url-host

  get host() {
    return this._slice(this._components[2], this._components[5])
  }

  set host(value) {
    if (hasOpaquePath(this)) {
      return
    }

    this._update(
      this._replace(value, this._components[2], this._components[value.includes(':') ? 5 : 3])
    )
  }

  // https://url.spec.whatwg.org/#dom-url-hostname

  get hostname() {
    return this._slice(this._components[2], this._components[3])
  }

  set hostname(value) {
    if (hasOpaquePath(this)) {
      return
    }

    this._update(this._replace(value, this._components[2], this._components[3]))
  }

  // https://url.spec.whatwg.org/#dom-url-port

  get port() {
    return this._slice(this._components[3] + 1 /* : */, this._components[5])
  }

  set port(value) {
    if (cannotHaveCredentialsOrPort(this)) {
      return
    }

    let start = this._components[3] + 1 /* : */

    if (this.port === '') {
      value = ':' + value
      start--
    }

    this._update(this._replace(value, start, this._components[5]))
  }

  // https://url.spec.whatwg.org/#dom-url-pathname

  get pathname() {
    return this._slice(this._components[5], this._components[6] - 1 /* ? */)
  }

  set pathname(value) {
    if (hasOpaquePath(this)) {
      return
    }

    if (value[0] !== '/' && value[0] !== '\\') {
      value = '/' + value
    }

    this._update(this._replace(value, this._components[5], this._components[6] - 1 /* ? */))
  }

  // https://url.spec.whatwg.org/#dom-url-search

  get search() {
    return this._slice(this._components[6] - 1 /* ? */, this._components[7] - 1 /* # */)
  }

  set search(value) {
    if (value && value[0] !== '?') value = '?' + value

    this._update(
      this._replace(value, this._components[6] - 1 /* ? */, this._components[7] - 1 /* # */)
    )

    this._params._parse(this.search)
  }

  // https://url.spec.whatwg.org/#dom-url-searchparams

  get searchParams() {
    return this._params
  }

  // https://url.spec.whatwg.org/#dom-url-hash

  get hash() {
    return this._slice(this._components[7] - 1 /* # */)
  }

  set hash(value) {
    if (value && value[0] !== '#') value = '#' + value

    this._update(this._replace(value, this._components[7] - 1 /* # */))
  }

  toString() {
    return this._href
  }

  toJSON() {
    return this._href
  }

  [Symbol.for('bare.inspect')]() {
    return {
      __proto__: { constructor: URL },

      href: this.href,
      protocol: this.protocol,
      username: this.username,
      password: this.password,
      host: this.host,
      hostname: this.hostname,
      port: this.port,
      pathname: this.pathname,
      search: this.search,
      searchParams: this.searchParams,
      hash: this.hash
    }
  }

  _slice(start, end = this._href.length) {
    return this._href.slice(start, end)
  }

  _replace(replacement, start, end = this._href.length) {
    return this._slice(0, start) + replacement + this._slice(end)
  }

  _parse(input, base, shouldThrow) {
    try {
      this._href = binding.parse(
        String(input),
        base ? String(base) : null,
        this._components,
        shouldThrow
      )
    } catch (err) {
      if (err instanceof TypeError) throw err

      throw errors.INVALID_URL(`Invalid URL '${input}'`, input)
    }
  }

  _update(input) {
    try {
      this._parse(input, null, true)
    } catch (err) {
      if (err instanceof TypeError) throw err
    }
  }
}

module.exports = exports = URL

// https://url.spec.whatwg.org/#url-opaque-path
function hasOpaquePath(url) {
  return url.pathname[0] !== '/'
}

// https://url.spec.whatwg.org/#cannot-have-a-username-password-port
function cannotHaveCredentialsOrPort(url) {
  return url.hostname === '' || url.protocol === 'file:'
}

exports.URL = URL
exports.URLSearchParams = URLSearchParams

exports.errors = errors

exports.isURL = function isURL(value) {
  if (value instanceof URL) return true

  return typeof value === 'object' && value !== null && value[kind] === URL[kind]
}

exports.isURLSearchParams = URLSearchParams.isURLSearchParams

// https://url.spec.whatwg.org/#dom-url-parse
exports.parse = function parse(input, base) {
  const url = new URL(input, base, { throw: false })
  return url._href ? url : null
}

// https://url.spec.whatwg.org/#dom-url-canparse
exports.canParse = function canParse(input, base) {
  return binding.canParse(String(input), base ? String(base) : null)
}

exports.fileURLToPath = function fileURLToPath(url) {
  if (typeof url === 'string') {
    url = new URL(url)
  }

  if (url.protocol !== 'file:') {
    throw errors.INVALID_URL_SCHEME('The URL must use the file: protocol')
  }

  if (isWindows) {
    if (/%2f|%5c/i.test(url.pathname)) {
      throw errors.INVALID_FILE_URL_PATH(
        'The file: URL path must not include encoded \\ or / characters'
      )
    }
  } else {
    if (url.hostname) {
      throw errors.INVALID_FILE_URL_HOST("The file: URL host must be 'localhost' or empty")
    }

    if (/%2f/i.test(url.pathname)) {
      throw errors.INVALID_FILE_URL_PATH('The file: URL path must not include encoded / characters')
    }
  }

  if (/%00/i.test(url.pathname)) {
    throw errors.INVALID_FILE_URL_PATH('The file: URL path must not include encoded NUL characters')
  }

  const pathname = path.normalize(decodeURIComponent(url.pathname))

  if (isWindows) {
    if (url.hostname) return '\\\\' + url.hostname + pathname

    const letter = pathname.charCodeAt(1) | 0x20

    if (letter < 0x61 /* a */ || letter > 0x7a /* z */ || pathname.charCodeAt(2) !== 0x3a /* : */) {
      throw errors.INVALID_FILE_URL_PATH('The file: URL path must be absolute')
    }

    return pathname.slice(1)
  }

  return pathname
}

exports.pathToFileURL = function pathToFileURL(pathname) {
  let resolved = path.resolve(pathname)

  if (pathname[pathname.length - 1] === '/') {
    resolved += '/'
  } else if (isWindows && pathname[pathname.length - 1] === '\\') {
    resolved += '\\'
  }

  resolved = resolved
    .replaceAll('%', '%25') // Must be first
    .replaceAll('#', '%23')
    .replaceAll('?', '%3f')
    .replaceAll('\n', '%0a')
    .replaceAll('\r', '%0d')
    .replaceAll('\t', '%09')

  if (!isWindows) {
    resolved = resolved.replaceAll('\\', '%5c')
  }

  return new URL('file:' + resolved)
}

exports.format = function format(parts) {
  const { protocol, auth, host, hostname, port, pathname, search, query, hash, slashes } = parts

  let result = ''

  if (typeof protocol === 'string') {
    result += protocol

    if (protocol[protocol.length - 1] !== ':') {
      result += ':'
    }

    if (slashes === true || /https?|ftp|gopher|file/.test(protocol)) {
      result += '//'
    }
  }

  if (typeof auth === 'string') {
    if (host || hostname) result += auth + '@'
  }

  if (typeof host === 'string') result += host
  else {
    result += hostname

    if (port) result += ':' + port
  }

  if (typeof pathname === 'string' && pathname !== '') {
    if (pathname[0] !== '/') result += '/'
    result += pathname
  }

  if (typeof search === 'string') {
    if (search[0] !== '?') result += '?'
    result += search
  } else if (typeof query === 'object' && query !== null) {
    result += '?' + new URLSearchParams(query)
  }

  if (typeof hash === 'string') {
    if (hash[0] !== '#') result += '#'
    result += hash
  }

  return result
}
module.exports = class URLError extends Error {
  constructor(msg, fn = URLError, code = fn.name) {
    super(`${code}: ${msg}`)

    this.code = code

    if (Error.captureStackTrace) Error.captureStackTrace(this, fn)
  }

  get name() {
    return 'URLError'
  }

  static INVALID_URL(msg, input) {
    const err = new URLError(msg, URLError.INVALID_URL)

    err.input = input

    return err
  }

  static INVALID_URL_SCHEME(msg = 'Invalid URL') {
    return new URLError(msg, URLError.INVALID_URL_SCHEME)
  }

  static INVALID_FILE_URL_HOST(msg = 'Invalid file: URL host') {
    return new URLError(msg, URLError.INVALID_FILE_URL_HOST)
  }

  static INVALID_FILE_URL_PATH(msg = 'Invalid file: URL path') {
    return new URLError(msg, URLError.INVALID_FILE_URL_PATH)
  }
}
const kind = Symbol.for('bare.url.search-params.kind')

class URLSearchParams {
  static _urls = new WeakMap()

  static get [kind]() {
    return 0 // Compatibility version
  }

  // https://url.spec.whatwg.org/#dom-urlsearchparams-urlsearchparams
  constructor(init, url = null) {
    this._params = new Map()

    if (url) URLSearchParams._urls.set(this, url)

    if (typeof init === 'string') {
      this._parse(init)
    } else if (init) {
      for (const [name, value] of typeof init[Symbol.iterator] === 'function'
        ? init
        : Object.entries(init)) {
        this.append(name, value)
      }
    }
  }

  get [kind]() {
    return URLSearchParams[kind]
  }

  // https://url.spec.whatwg.org/#dom-urlsearchparams-size
  get size() {
    return this._params.length
  }

  // https://url.spec.whatwg.org/#dom-urlsearchparams-append
  append(name, value = null) {
    if (value === null) return

    let list = this._params.get(name)

    if (list === undefined) {
      list = []
      this._params.set(name, list)
    }

    list.push(value)

    this._update()
  }

  // https://url.spec.whatwg.org/#dom-urlsearchparams-delete
  delete(name, value = null) {
    if (value === null) this._params.delete(name)
    else {
      let list = this._params.get(name)

      if (list === undefined) return

      list = list.filter((found) => found !== value)

      if (list.length === 0) this._params.delete(name)
      else this._params.set(name, list)
    }

    this._update()
  }

  // https://url.spec.whatwg.org/#dom-urlsearchparams-get
  get(name) {
    const list = this._params.get(name)

    if (list === undefined) return null

    return list[0]
  }

  // https://url.spec.whatwg.org/#dom-urlsearchparams-getall
  getAll(name) {
    const list = this._params.get(name)

    if (list === undefined) return []

    return Array.from(list)
  }

  // https://url.spec.whatwg.org/#dom-urlsearchparams-has
  has(name, value = null) {
    const list = this._params.get(name)

    if (list === undefined) return false

    if (value === null) return true

    return list.includes(value)
  }

  // https://url.spec.whatwg.org/#dom-urlsearchparams-set
  set(name, value = null) {
    if (value === null) this._params.delete(name)
    else this._params.set(name, [value])

    this._update()
  }

  toString() {
    return this._serialize()
  }

  toJSON() {
    return [...this]
  }

  *[Symbol.iterator]() {
    for (const [name, values] of this._params) {
      for (const value of values) yield [name, value]
    }
  }

  [Symbol.for('bare.inspect')]() {
    const object = {
      __proto__: { constructor: URLSearchParams }
    }

    for (const [name, values] of this._params) {
      if (values.length === 1) object[name] = values[0]
      else object[name] = values
    }

    return object
  }

  // https://url.spec.whatwg.org/#concept-urlsearchparams-update
  _update() {
    const url = URLSearchParams._urls.get(this)

    if (url === undefined) return

    url.search = this._serialize()
  }

  // https://url.spec.whatwg.org/#concept-urlencoded-parser
  _parse(input) {
    if (input[0] === '?') input = input.substring(1)

    this._params = new Map()

    for (let sequence of input.split('&')) {
      if (sequence.length === 0) continue

      sequence = sequence.replace(/\+/g, ' ')

      let i = sequence.indexOf('=')
      if (i === -1) i = sequence.length

      const name = decodeURIComponent(sequence.substring(0, i))
      const value = decodeURIComponent(sequence.substring(i + 1, sequence.length))

      let list = this._params.get(name)

      if (list === undefined) {
        list = []
        this._params.set(name, list)
      }

      list.push(value)
    }
  }

  // https://url.spec.whatwg.org/#concept-urlencoded-serializer
  _serialize() {
    let output = ''

    for (let [name, values] of this._params) {
      name = encodeURIComponent(name)

      for (const value of values) {
        if (output) output += '&'

        output += name + '=' + encodeURIComponent(value)
      }
    }

    return output
  }
}

module.exports = exports = URLSearchParams

exports.isURLSearchParams = function isURLSearchParams(value) {
  if (value instanceof URLSearchParams) return true

  return typeof value === 'object' && value !== null && value[kind] === URLSearchParams[kind]
}
{
  "name": "bare-url",
  "version": "2.4.5",
  "description": "WHATWG URL implementation for JavaScript",
  "exports": {
    "./package": "./package.json",
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    },
    "./global": {
      "types": "./global.d.ts",
      "default": "./global.js"
    }
  },
  "files": [
    "index.js",
    "index.d.ts",
    "global.js",
    "global.d.ts",
    "binding.c",
    "binding.js",
    "CMakeLists.txt",
    "lib",
    "prebuilds"
  ],
  "addon": true,
  "scripts": {
    "format": "prettier --write . && lunte --fix",
    "lint": "prettier --check . && lunte",
    "test": "brittle-bare --coverage test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-url.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-url/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-url",
  "dependencies": {
    "bare-path": "^3.0.0"
  },
  "devDependencies": {
    "brittle": "^3.3.2",
    "cmake-bare": "^1.1.6",
    "cmake-fetch": "^1.0.0",
    "lunte": "^1.6.0",
    "prettier": "^3.3.3",
    "prettier-config-holepunch": "^2.0.0"
  }
}
const FACTOR = new Uint16Array(8)

function factor4096 (i, n) {
  while (n > 0) {
    const f = i & 4095
    FACTOR[--n] = f
    i = (i - f) / 4096
  }
  return FACTOR
}

module.exports = class BigSparseArray {
  constructor () {
    this.tiny = new TinyArray()
    this.maxLength = 4096
    this.factor = 1
  }

  set (index, val) {
    if (val !== undefined) {
      while (index >= this.maxLength) {
        this.maxLength *= 4096
        this.factor++
        if (!this.tiny.isEmptyish()) {
          const t = new TinyArray()
          t.set(0, this.tiny)
          this.tiny = t
        }
      }
    }

    const f = factor4096(index, this.factor)
    const last = this.factor - 1

    let tiny = this.tiny
    for (let i = 0; i < last; i++) {
      const next = tiny.get(f[i])
      if (next === undefined) {
        if (val === undefined) return
        tiny = tiny.set(f[i], new TinyArray())
      } else {
        tiny = next
      }
    }

    return tiny.set(f[last], val)
  }

  get (index) {
    if (index >= this.maxLength) return

    const f = factor4096(index, this.factor)
    const last = this.factor - 1

    let tiny = this.tiny
    for (let i = 0; i < last; i++) {
      tiny = tiny.get(f[i])
      if (tiny === undefined) return
    }

    return tiny.get(f[last])
  }
}

class TinyArray {
  constructor () {
    this.s = 0
    this.b = new Array(1)
    this.f = new Uint16Array(1)
  }

  isEmptyish () {
    return this.b.length === 1 && this.b[0] === undefined
  }

  get (i) {
    if (this.s === 12) return this.b[i]
    const f = i >>> this.s
    const r = i & (this.b.length - 1)
    return this.f[r] === f ? this.b[r] : undefined
  }

  set (i, v) {
    while (this.s !== 12) {
      const f = i >>> this.s
      const r = i & (this.b.length - 1)
      const o = this.b[r]

      if (o === undefined || f === this.f[r]) {
        this.b[r] = v
        this.f[r] = f
        return v
      }

      this.grow()
    }

    this.b[i] = v
    return v
  }

  grow () {
    const os = this.s
    const ob = this.b
    const of = this.f

    this.s += 4
    this.b = new Array(this.b.length << 4)
    this.f = this.s === 12 ? null : new Uint8Array(this.b.length)

    const m = this.b.length - 1

    for (let or = 0; or < ob.length; or++) {
      if (ob[or] === undefined) continue

      const i = of[or] << os | or
      const f = i >>> this.s
      const r = i & m

      this.b[r] = ob[or]
      if (this.s !== 12) this.f[r] = f
    }
  }
}
{
  "name": "big-sparse-array",
  "version": "1.0.3",
  "description": "A sparse array optimised for low memory whilst still being fast",
  "main": "index.js",
  "dependencies": {},
  "devDependencies": {
    "brittle": "^3.1.1",
    "standard": "^16.0.3"
  },
  "scripts": {
    "test": "standard && brittle test.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/mafintosh/big-sparse-array.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/mafintosh/big-sparse-array/issues"
  },
  "homepage": "https://github.com/mafintosh/big-sparse-array"
}
const b4a = require('b4a')

function byteLength (size) {
  return Math.ceil(size / 8)
}

function get (buffer, bit) {
  const n = buffer.BYTES_PER_ELEMENT * 8

  const offset = bit & (n - 1)
  const i = (bit - offset) / n

  return (buffer[i] & (1 << offset)) !== 0
}

function set (buffer, bit, value = true) {
  const n = buffer.BYTES_PER_ELEMENT * 8

  const offset = bit & (n - 1)
  const i = (bit - offset) / n
  const mask = 1 << offset

  if (value) {
    if ((buffer[i] & mask) !== 0) return false
  } else {
    if ((buffer[i] & mask) === 0) return false
  }

  buffer[i] ^= mask
  return true
}

function setRange (buffer, start, end, value = true) {
  const n = buffer.BYTES_PER_ELEMENT * 8

  let remaining = end - start
  let offset = start & (n - 1)
  let i = (start - offset) / n

  let changed = false

  while (remaining > 0) {
    const mask = (2 ** Math.min(remaining, n - offset) - 1) << offset

    if (value) {
      if ((buffer[i] & mask) !== mask) {
        buffer[i] |= mask
        changed = true
      }
    } else {
      if ((buffer[i] & mask) !== 0) {
        buffer[i] &= ~mask
        changed = true
      }
    }

    remaining -= n - offset
    offset = 0
    i++
  }

  return changed
}

function fill (buffer, value, start = 0, end = buffer.byteLength * 8) {
  const n = buffer.BYTES_PER_ELEMENT * 8
  let i, j

  {
    const offset = start & (n - 1)
    i = (start - offset) / n

    if (offset !== 0) {
      const mask = (2 ** Math.min(n - offset, end - start) - 1) << offset

      if (value) buffer[i] |= mask
      else buffer[i] &= ~mask

      i++
    }
  }

  {
    const offset = end & (n - 1)
    j = (end - offset) / n

    if (offset !== 0 && j >= i) {
      const mask = (2 ** offset) - 1

      if (value) buffer[j] |= mask
      else buffer[j] &= ~mask
    }
  }

  return buffer.fill(value ? (2 ** n) - 1 : 0, i, j)
}

function toggle (buffer, bit) {
  const n = buffer.BYTES_PER_ELEMENT * 8

  const offset = bit & (n - 1)
  const i = (bit - offset) / n
  const mask = 1 << offset

  buffer[i] ^= mask
  return (buffer[i] & mask) !== 0
}

function remove (buffer, bit) {
  return set(buffer, bit, false)
}

function removeRange (buffer, start, end) {
  return setRange(buffer, start, end, false)
}

function indexOf (buffer, value, position = 0) {
  for (let i = position, n = buffer.byteLength * 8; i < n; i++) {
    if (get(buffer, i) === value) return i
  }

  return -1
}

function lastIndexOf (buffer, value, position = buffer.byteLength * 8 - 1) {
  for (let i = position; i >= 0; i--) {
    if (get(buffer, i) === value) return i
  }

  return -1
}

function of (...bits) {
  return from(bits)
}

function from (bits) {
  const buffer = b4a.alloc(byteLength(bits.length))
  for (let i = 0; i < bits.length; i++) set(buffer, i, bits[i])
  return buffer
}

function * iterator (buffer) {
  for (let i = 0, n = buffer.byteLength * 8; i < n; i++) yield get(buffer, i)
}

module.exports = {
  byteLength,
  get,
  set,
  setRange,
  fill,
  toggle,
  remove,
  removeRange,
  indexOf,
  lastIndexOf,
  of,
  from,
  iterator
}
{
  "name": "bits-to-bytes",
  "version": "1.3.0",
  "description": "Functions for doing bit manipulation of typed arrays",
  "main": "index.js",
  "files": [
    "index.js"
  ],
  "scripts": {
    "test": "standard && brittle test.mjs"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bits-to-bytes.git"
  },
  "author": "Kasper Isager Dalsgarð <kasper@funktionel.co>",
  "license": "ISC",
  "bugs": {
    "url": "https://github.com/holepunchto/bits-to-bytes/issues"
  },
  "homepage": "https://github.com/holepunchto/bits-to-bytes#readme",
  "dependencies": {
    "b4a": "^1.5.0"
  },
  "devDependencies": {
    "brittle": "^2.3.1",
    "standard": "^17.0.0"
  }
}
const EventEmitter = require('events')
const Protomux = require('protomux')
const { Readable } = require('streamx')
const sodium = require('sodium-universal')
const b4a = require('b4a')
const c = require('compact-encoding')
const bitfield = require('compact-encoding-bitfield')
const bits = require('bits-to-bytes')
const errors = require('./lib/errors')

exports.Server = class BlindRelayServer extends EventEmitter {
  constructor(opts = {}) {
    super()

    const { createStream } = opts

    this._createStream = createStream
    this._pairing = new Map()
    this._activePairingRefs = new Map()
    this._sessions = new Set()
    this.stats = createStats()
  }

  get sessions() {
    return this._sessions[Symbol.iterator]()
  }

  accept(stream, opts) {
    const session = new BlindRelaySession(this, stream, opts)

    this._sessions.add(session)
    this.stats.sessions.accepted++

    return session
  }

  async close() {
    const ending = []

    for (const session of this._sessions) {
      ending.push(session.end())
    }

    await Promise.all(ending)

    this._pairing.clear()
  }

  _trackActiveLink(keyString) {
    const refs = this._activePairingRefs.get(keyString) || 0

    if (refs === 0) this.stats.pairings.active++

    this._activePairingRefs.set(keyString, refs + 1)
  }

  _untrackActiveLink(keyString) {
    const refs = this._activePairingRefs.get(keyString)

    if (refs === undefined) return

    if (refs === 1) {
      this._activePairingRefs.delete(keyString)
      this.stats.pairings.active--
    } else {
      this._activePairingRefs.set(keyString, refs - 1)
    }
  }
}

class BlindRelaySession extends EventEmitter {
  constructor(server, stream, opts = {}) {
    super()

    const { id, handshake, handshakeEncoding } = opts

    this._server = server
    this._mux = Protomux.from(stream)

    this._channel = this._mux.createChannel({
      protocol: 'blind-relay',
      id,
      handshake: handshake ? handshakeEncoding || c.raw : null,
      onopen: this._onopen.bind(this),
      onclose: this._onclose.bind(this),
      ondestroy: this._ondestroy.bind(this)
    })

    this._pair = this._channel.addMessage({
      encoding: m.pair,
      onmessage: this._onpair.bind(this)
    })

    this._unpair = this._channel.addMessage({
      encoding: m.unpair,
      onmessage: this._onunpair.bind(this)
    })

    this._ending = null
    this._destroyed = false
    this._error = null
    this._pairing = new Set()
    this._links = new Map()

    this._onerror = (err) => {
      this._server.stats.streams.errors++
      this.emit('error', err)
    }

    this._channel.open(handshake)
  }

  get closed() {
    return this._channel.closed
  }

  get mux() {
    return this._mux
  }

  get stream() {
    return this._mux.stream
  }

  _onopen() {
    this._server.stats.sessions.opened++
    this.emit('open')
  }

  _onclose() {
    this._ending = Promise.resolve()

    const err = this._error || errors.CHANNEL_CLOSED()
    this._server.stats.pairings.cancelled += this._pairing.size

    for (const token of this._pairing) {
      if (this._server._pairing.delete(token.toString('hex'))) {
        this._server.stats.pairings.pending--
      }
    }

    for (const link of this._links.values()) {
      link.destroy(err)
    }

    this._pairing.clear()
    this._links.clear()

    this._server._sessions.delete(this)
    this._server.stats.sessions.closed++

    this.emit('close')
  }

  _ondestroy() {
    this._destroyed = true
    this.emit('destroy')
  }

  _onpair({ isInitiator, token, id: remoteId }) {
    const keyString = token.toString('hex')

    let pair = this._server._pairing.get(keyString)

    if (pair === undefined) {
      pair = new BlindRelayPair(token)
      this._server._pairing.set(keyString, pair)
      this._server.stats.pairings.pending++
    } else if (pair.links[+isInitiator]) return

    this._pairing.add(keyString)
    this._server.stats.pairings.requested++

    pair.links[+isInitiator] = new BlindRelayLink(this, isInitiator, remoteId)

    if (!pair.paired) return

    this._server._pairing.delete(keyString)
    this._server.stats.pairings.pending--
    this._server.stats.pairings.matched++

    // 1st pass: Create the raw streams needed for each end of the link.
    for (const link of pair.links) {
      link.createStream()
    }

    // 2nd pass: Connect the raw streams and set up handlers.
    for (const link of pair.links) {
      link.remote = pair.remote(link.isInitiator)
      link.activate(keyString)
    }

    // 3rd pass: Let either end of the link know the streams were set up.
    for (const { isInitiator, session, remoteId, stream } of pair.links) {
      session._pair.send({
        isInitiator,
        token,
        id: stream.id,
        seq: 0
      })

      session._endMaybe()

      session.emit('pair', isInitiator, token, stream, remoteId)
    }
  }

  _onunpair({ token }) {
    const keyString = token.toString('hex')

    const pair = this._server._pairing.get(keyString)

    if (pair) {
      for (const link of pair.links) {
        if (link) link.session._pairing.delete(keyString)
      }

      this._server._pairing.delete(keyString)
      this._server.stats.pairings.pending--
      this._server.stats.pairings.cancelled++
      return
    }

    const link = this._links.get(keyString)

    if (link) {
      link.destroyPair(errors.PAIRING_CANCELLED())
      this._server.stats.pairings.cancelled++
    }
  }

  cork() {
    this._channel.cork()
  }

  uncork() {
    this._channel.uncork()
  }

  async end() {
    if (this._ending) return this._ending

    this._ending = EventEmitter.once(this, 'close')
    this._endMaybe()

    return this._ending
  }

  _endMaybe() {
    if (this._ending && this._pairing.size === 0) {
      this._channel.close()
    }
  }

  destroy(err) {
    if (this._destroyed) return
    this._destroyed = true

    this._error = err || errors.CHANNEL_DESTROYED()
    this._channel.close()
  }
}

class BlindRelayPair {
  constructor(token) {
    this.token = token
    this.links = [null, null]
  }

  get paired() {
    return this.links[0] !== null && this.links[1] !== null
  }

  remote(isInitiator) {
    return this.links[isInitiator ? 0 : 1]
  }
}

class BlindRelayLink {
  constructor(session, isInitiator, remoteId) {
    this.session = session
    this.isInitiator = isInitiator
    this.remoteId = remoteId
    this.stream = null
    this.remote = null
    this.keyString = null
    this._onclose = null
  }

  createStream() {
    if (this.stream) return

    this.stream = this.session._server._createStream({
      firewall: this._onfirewall.bind(this)
    })
  }

  activate(keyString) {
    const stream = this.stream

    this.keyString = keyString
    this._onclose = () => {
      if (this.stream !== stream) return

      this.stream = null
      unlink(this)
      this.session._links.delete(keyString)
      this.session._server._untrackActiveLink(keyString)
      this.session._server.stats.streams.closed++
    }

    stream.on('error', this.session._onerror).on('close', this._onclose).relayTo(this.remote.stream)

    this.session._pairing.delete(keyString)
    this.session._links.set(keyString, this)
    this.session._server._trackActiveLink(keyString)
    this.session._server.stats.streams.opened++
  }

  destroy(err) {
    const stream = this.stream

    if (!stream) return

    this.stream = null

    stream.off('close', this._onclose)
    this._onclose = null

    stream.off('error', this.session._onerror)
    unlink(this)
    this.session._links.delete(this.keyString)
    this.session._server._untrackActiveLink(this.keyString)
    this.session._server.stats.streams.closed++
    stream.on('error', noop).destroy(err)
  }

  destroyPair(err) {
    const remote = this.remote

    this.destroy(err)
    if (remote) remote.destroy(err)
  }

  _onfirewall(socket, port, host) {
    this.stream.connect(socket, this.remoteId, port, host)

    return false
  }
}

exports.Client = class BlindRelayClient extends EventEmitter {
  static _clients = new WeakMap()

  static from(stream, opts) {
    let client = this._clients.get(stream)
    if (client) return client
    client = new this(stream, opts)
    this._clients.set(stream, client)
    return client
  }

  constructor(stream, opts = {}) {
    super()

    const { id, handshake, handshakeEncoding } = opts

    this._mux = Protomux.from(stream)

    this._channel = this._mux.createChannel({
      protocol: 'blind-relay',
      id,
      handshake: handshake ? handshakeEncoding || c.raw : null,
      onopen: this._onopen.bind(this),
      onclose: this._onclose.bind(this),
      ondestroy: this._ondestroy.bind(this)
    })

    this._pair = this._channel.addMessage({
      encoding: m.pair,
      onmessage: this._onpair.bind(this)
    })

    this._unpair = this._channel.addMessage({
      encoding: m.unpair
    })

    this._ending = false
    this._destroyed = false
    this._error = null
    this._requests = new Map()

    this._channel.open(handshake)
  }

  get closed() {
    return this._channel.closed
  }

  get mux() {
    return this._mux
  }

  get stream() {
    return this._mux.stream
  }

  get requests() {
    return this._requests.values()
  }

  _onopen() {
    this.emit('open')
  }

  _onclose() {
    this._ending = Promise.resolve()

    const err = this._error || errors.CHANNEL_CLOSED()

    for (const request of this._requests.values()) {
      request.destroy(err)
    }

    this._requests.clear()

    this.constructor._clients.delete(this.stream)

    this.emit('close')
  }

  _ondestroy() {
    this._destroyed = true
    this.emit('destroy')
  }

  _onpair({ isInitiator, token, id: remoteId }) {
    const request = this._requests.get(token.toString('hex'))

    if (request === undefined || request.isInitiator !== isInitiator) return

    request.push(remoteId)
    request.push(null)

    this.emit('pair', request.isInitiator, request.token, request.stream, remoteId)
  }

  pair(isInitiator, token, stream) {
    if (this._destroyed) throw errors.CHANNEL_DESTROYED()

    const keyString = token.toString('hex')

    if (this._requests.has(keyString)) throw errors.ALREADY_PAIRING()

    const request = new BlindRelayRequest(this, isInitiator, token, stream)

    this._requests.set(keyString, request)

    return request
  }

  unpair(token) {
    if (this._destroyed) throw errors.CHANNEL_DESTROYED()

    const request = this._requests.get(token.toString('hex'))

    if (request) request.destroy(errors.PAIRING_CANCELLED())

    this._unpair.send({ token })
  }

  cork() {
    this._channel.cork()
  }

  uncork() {
    this._channel.uncork()
  }

  async end() {
    if (this._ending) return this._ending

    this._ending = EventEmitter.once(this, 'close')
    this._endMaybe()

    return this._ending
  }

  _endMaybe() {
    if (this._ending && this._requests.size === 0) {
      this._channel.close()
    }
  }

  destroy(err) {
    if (this._destroyed) return
    this._destroyed = true

    this._error = err || errors.CHANNEL_DESTROYED()
    this._channel.close()
  }
}

class BlindRelayRequest extends Readable {
  constructor(client, isInitiator, token, stream) {
    super()

    this.client = client
    this.isInitiator = isInitiator
    this.token = token
    this.stream = stream
  }

  _open(cb) {
    if (this.client._destroyed) return cb(errors.CHANNEL_DESTROYED())

    this.client._pair.send({
      isInitiator: this.isInitiator,
      token: this.token,
      id: this.stream.id,
      seq: 0
    })

    cb(null)
  }

  _destroy(cb) {
    this.client._requests.delete(this.token.toString('hex'))

    cb(null)

    this.client._endMaybe()
  }
}

exports.token = function token(buf = b4a.allocUnsafe(32)) {
  sodium.randombytes_buf(buf)
  return buf
}

function noop() {}

function unlink(link) {
  const remote = link.remote

  link.remote = null

  if (remote && remote.remote === link) {
    remote.remote = null
  }
}

function createStats() {
  const stats = {
    sessions: {
      accepted: 0,
      opened: 0,
      closed: 0,
      get active() {
        return this.accepted - this.closed
      }
    },
    pairings: {
      requested: 0,
      matched: 0,
      cancelled: 0,
      pending: 0,
      active: 0
    },
    streams: {
      opened: 0,
      closed: 0,
      errors: 0,
      get active() {
        return this.opened - this.closed
      }
    }
  }

  return stats
}

const m = (exports.messages = {})

const flags = bitfield(7)

m.pair = {
  preencode(state, m) {
    flags.preencode(state)
    c.fixed32.preencode(state, m.token)
    c.uint.preencode(state, m.id)
    c.uint.preencode(state, m.seq)
  },
  encode(state, m) {
    flags.encode(state, bits.of(m.isInitiator))
    c.fixed32.encode(state, m.token)
    c.uint.encode(state, m.id)
    c.uint.encode(state, m.seq)
  },
  decode(state) {
    const [isInitiator] = bits.iterator(flags.decode(state))

    return {
      isInitiator,
      token: c.fixed32.decode(state),
      id: c.uint.decode(state),
      seq: c.uint.decode(state)
    }
  }
}

m.unpair = {
  preencode(state, m) {
    flags.preencode(state)
    c.fixed32.preencode(state, m.token)
  },
  encode(state, m) {
    flags.encode(state, bits.of())
    c.fixed32.encode(state, m.token)
  },
  decode(state) {
    flags.decode(state)

    return {
      token: c.fixed32.decode(state)
    }
  }
}
module.exports = class BlindRelayError extends Error {
  constructor(msg, code, fn = BlindRelayError) {
    super(`${code}: ${msg}`)
    this.code = code

    if (Error.captureStackTrace) {
      Error.captureStackTrace(this, fn)
    }
  }

  get name() {
    return 'BlindRelayError'
  }

  static DUPLICATE_CHANNEL(msg = 'Duplicate channel') {
    return new BlindRelayError(msg, 'DUPLICATE_CHANNEL', BlindRelayError.DUPLICATE_CHANNEL)
  }

  static CHANNEL_CLOSED(msg = 'Channel closed') {
    return new BlindRelayError(msg, 'CHANNEL_CLOSED', BlindRelayError.CHANNEL_CLOSED)
  }

  static CHANNEL_DESTROYED(msg = 'Channel destroyed') {
    return new BlindRelayError(msg, 'CHANNEL_DESTROYED', BlindRelayError.CHANNEL_DESTROYED)
  }

  static ALREADY_PAIRING(msg = 'Already pairing') {
    return new BlindRelayError(msg, 'ALREADY_PAIRING', BlindRelayError.ALREADY_PAIRING)
  }

  static PAIRING_CANCELLED(msg = 'Pairing cancelled') {
    return new BlindRelayError(msg, 'PAIRING_CANCELLED', BlindRelayError.PAIRING_CANCELLED)
  }
}
{
  "name": "blind-relay",
  "version": "1.6.1",
  "description": "Blind relay for UDX over Protomux channels",
  "main": "index.js",
  "files": [
    "index.js",
    "lib"
  ],
  "scripts": {
    "format": "prettier --write .",
    "lint": "prettier --check . && lunte",
    "test": "npm run test:bare",
    "test:bare": "brittle-bare --coverage test.mjs"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/blind-relay.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/blind-relay/issues"
  },
  "homepage": "https://github.com/holepunchto/blind-relay#readme",
  "imports": {
    "events": {
      "bare": "bare-events",
      "default": "events"
    }
  },
  "dependencies": {
    "b4a": "^1.6.4",
    "bare-events": "^2.2.0",
    "bits-to-bytes": "^1.3.0",
    "compact-encoding": "^3.0.0",
    "compact-encoding-bitfield": "^1.0.0",
    "protomux": "^3.5.1",
    "sodium-universal": "^5.0.0",
    "streamx": "^2.15.1"
  },
  "devDependencies": {
    "brittle": "^3.2.1",
    "hyperdht": "^6.6.1",
    "lunte": "^1.0.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^2.0.0",
    "udx-native": "^1.6.1"
  }
}
// https://ipinfo.io/bogon

const b4a = require('b4a')
const c = require('compact-encoding')

module.exports = exports = function isBogon (ip) {
  return isBogonIP(ensureBuffer(ip))
}

exports.isBogon = exports

exports.isPrivate = function isPrivate (ip) {
  return isPrivateIP(ensureBuffer(ip))
}

exports.isReserved = function isReserved (ip) {
  return isReservedIP(ensureBuffer(ip))
}

function isBogonIP (ip) {
  return isPrivateIP(ip) || isReservedIP(ip)
}

function isPrivateIP (ip) {
  return ip.byteLength === 4 ? isPrivateIPv4(ip) : false // IPv6 has no private IPs
}

function isPrivateIPv4 (ip) {
  return (
    // 10.0.0.0/8  Private-use networks
    (ip[0] === 10) ||
    // 100.64.0.0/10 Carrier-grade NAT
    (ip[0] === 100 && ip[1] >= 64 && ip[1] <= 127) ||
    // 127.0.0.0/8 Loopback + Name collision occurrence (127.0.53.53)
    (ip[0] === 127) ||
    // 169.254.0.0/16  Link local
    (ip[0] === 169 && ip[1] === 254) ||
    // 172.16.0.0/12 Private-use networks
    (ip[0] === 172 && ip[1] >= 16 && ip[1] <= 31) ||
    // 192.168.0.0/16  Private-use networks
    (ip[0] === 192 && ip[1] === 168)
  )
}

function isReservedIP (ip) {
  return ip.byteLength === 4 ? isReservedIPv4(ip) : isReservedIPv6(ip)
}

function isReservedIPv4 (ip) {
  return (
    // 0.0.0.0/8 "This" network
    (ip[0] === 0) ||
    // 192.0.0.0/24  IETF protocol assignments
    (ip[0] === 192 && ip[1] === 0 && ip[2] === 0) ||
    // 192.0.2.0/24  TEST-NET-1
    (ip[0] === 192 && ip[1] === 0 && ip[2] === 2) ||
    // 198.18.0.0/15 Network interconnect device benchmark testing
    (ip[0] === 198 && ip[1] >= 18 && ip[1] <= 19) ||
    // 198.51.100.0/24 TEST-NET-2
    (ip[0] === 198 && ip[1] === 51 && ip[2] === 100) ||
    // 203.0.113.0/24  TEST-NET-3
    (ip[0] === 203 && ip[1] === 0 && ip[2] === 113) ||
    // 224.0.0.0/4 Multicast
    (ip[0] >= 224 && ip[0] <= 239) ||
    // 240.0.0.0/4 Reserved for future use
    (ip[0] >= 240) ||
    // 255.255.255.255/32
    (ip[0] === 255 && ip[1] === 255 && ip[2] === 255 && ip[3] === 255)
  )
}

function isReservedIPv6 (ip) {
  return (
    // ::/128 Node-scope unicast unspecified address
    // ::1/128 Node-scope unicast loopback address
    (
      ip[0] === 0 && ip[1] === 0 && ip[2] === 0 && ip[3] === 0 && ip[4] === 0 &&
      ip[5] === 0 && ip[6] === 0 && ip[7] === 0 && ip[8] === 0 && ip[9] === 0 &&
      ip[10] === 0 && ip[11] === 0 && ip[12] === 0 && ip[13] === 0 && ip[14] === 0 &&
      ip[15] <= 1
    ) ||
    // ::ffff:0:0/96 IPv4-mapped addresses
    // ::/96 IPv4-compatible addresses
    (
      ip[0] === 0 && ip[1] === 0 && ip[2] === 0 && ip[3] === 0 && ip[4] === 0 &&
      ip[5] === 0 && ip[6] === 0 && ip[7] === 0 && ip[8] === 0 && ip[9] === 0 &&
      (ip[10] === 0 || ip[10] === 0xff) &&
      (ip[11] === 0 || ip[11] === 0xff)
    ) ||
    // 100::/64 Remotely triggered black hole addresses
    (ip[0] === 0x01 && ip[1] === 0 && ip[2] === 0 && ip[3] === 0 && ip[4] === 0 && ip[5] === 0 && ip[6] === 0 && ip[7] === 0) ||
    // 2001:10::/28 Overlay routable cryptographic hash identifiers (ORCHID)
    (ip[0] === 0x20 && ip[1] === 0x01 && ip[2] === 0 && ip[3] >= 0x10 && ip[3] <= 0x1f) ||
    // 2001:20::/28 Overlay routable cryptographic hash identifiers version 2 (ORCHIDv2)
    (ip[0] === 0x20 && ip[1] === 0x01 && ip[2] === 0 && ip[3] >= 0x20 && ip[3] <= 0x2f) ||
    // 2001:db8::/32 Documentation prefix
    (ip[0] === 0x20 && ip[1] === 0x01 && ip[2] === 0x0d && ip[3] === 0xb8) ||
    // fc00::/7 Unique local addresses (ULA)
    (ip[0] >= 0xfc && ip[0] <= 0xfd) ||
    // fe80::/10 Link-local unicast
    (ip[0] === 0xfe && ip[1] >= 0x80 && ip[1] <= 0xbf) ||
    // ff00::/8 Multicast
    (ip[0] === 0xff)
  )
}

const state = c.state(0, 0, b4a.allocUnsafe(1 /* family */ + 16))

function ensureBuffer (ip) {
  if (b4a.isBuffer(ip)) return ip

  c.ip.preencode(state, ip)
  c.ip.encode(state, ip)

  const buffer = state.buffer.subarray(1 /* family */, state.end)

  state.start = 0
  state.end = 0

  return buffer
}
{
  "name": "bogon",
  "version": "1.3.0",
  "description": "Check if an IP is a bogon",
  "main": "index.js",
  "scripts": {
    "test": "standard && brittle test.mjs"
  },
  "dependencies": {
    "compact-encoding": "^3.0.0",
    "compact-encoding-net": "^1.2.0"
  },
  "devDependencies": {
    "brittle": "^3.0.4",
    "nanobench": "^2.1.1",
    "standard": "^17.0.0"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/mafintosh/bogon.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/mafintosh/bogon/issues"
  },
  "homepage": "https://github.com/mafintosh/bogon"
}
const b4a = require('b4a')

module.exports = codecs

codecs.ascii = createString('ascii')
codecs.utf8 = createString('utf-8')
codecs.hex = createString('hex')
codecs.base64 = createString('base64')
codecs.ucs2 = createString('ucs2')
codecs.utf16le = createString('utf16le')
codecs.ndjson = createJSON(true)
codecs.json = createJSON(false)
codecs.binary = {
  name: 'binary',
  encode: function encodeBinary (obj) {
    return typeof obj === 'string'
      ? b4a.from(obj, 'utf-8')
      : b4a.toBuffer(obj)
  },
  decode: function decodeBinary (buf) {
    return b4a.toBuffer(buf)
  }
}

function isCompactEncoding (c) {
  return !!(c.encode && c.decode && c.preencode)
}

function fromCompactEncoding (c) {
  return {
    name: 'compact-encoding',
    encode: function encodeWithCompact (value) {
      const state = { start: 0, end: 0, buffer: null, cache: null }
      c.preencode(state, value)
      state.buffer = b4a.allocUnsafe(state.end)
      c.encode(state, value)
      return state.buffer
    },
    decode: function decodeWithCompact (buffer) {
      return c.decode({ start: 0, end: buffer.byteLength, buffer, cache: null })
    }
  }
}

function codecs (fmt, fallback) {
  if (typeof fmt === 'object' && fmt) {
    return isCompactEncoding(fmt) ? fromCompactEncoding(fmt) : fmt
  }

  switch (fmt) {
    case 'ndjson': return codecs.ndjson
    case 'json': return codecs.json
    case 'ascii': return codecs.ascii
    case 'utf-8':
    case 'utf8': return codecs.utf8
    case 'hex': return codecs.hex
    case 'base64': return codecs.base64
    case 'ucs-2':
    case 'ucs2': return codecs.ucs2
    case 'utf16-le':
    case 'utf16le': return codecs.utf16le
  }

  return fallback !== undefined ? fallback : codecs.binary
}

function createJSON (newline) {
  return {
    name: newline ? 'ndjson' : 'json',
    encode: newline ? encodeNDJSON : encodeJSON,
    decode: function decodeJSON (buf) {
      return JSON.parse(b4a.toString(buf))
    }
  }

  function encodeJSON (val) {
    return b4a.from(JSON.stringify(val))
  }

  function encodeNDJSON (val) {
    return b4a.from(JSON.stringify(val) + '\n')
  }
}

function createString (type) {
  return {
    name: type,
    encode: function encodeString (val) {
      if (typeof val !== 'string') val = val.toString()
      return b4a.from(val, type)
    },
    decode: function decodeString (buf) {
      return b4a.toString(buf, type)
    }
  }
}
{
  "name": "codecs",
  "version": "3.1.0",
  "description": "Create an binary encoder/decoder for json, utf-8 or custom types",
  "main": "index.js",
  "dependencies": {
    "b4a": "^1.6.3"
  },
  "devDependencies": {
    "brittle": "^3.2.1",
    "compact-encoding": "^2.11.0",
    "standard": "^17.0.0"
  },
  "scripts": {
    "test": "standard && brittle test.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/mafintosh/codecs.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/mafintosh/codecs/issues"
  },
  "homepage": "https://github.com/mafintosh/codecs"
}
const c = require('compact-encoding')

module.exports = function bitfield(length) {
  if (length > 64) {
    throw new RangeError('Bitfield cannot be larger than 64 bits')
  }

  let byteLength
  if (length < 8) byteLength = 1
  else if (length <= 16) byteLength = 2
  else if (length <= 32) byteLength = 4
  else byteLength = 8

  return {
    preencode(state) {
      state.end++ // Length byte, used for data when byteLength === 1

      if (byteLength === 1);
      else if (byteLength === 2) c.uint16.preencode(state)
      else if (byteLength === 4) c.uint32.preencode(state)
      else c.uint64.preencode(state)
    },

    encode(state, b) {
      if (byteLength === 1);
      else if (byteLength === 2) c.uint8.encode(state, 0xfd)
      else if (byteLength === 4) c.uint8.encode(state, 0xfe)
      else c.uint8.encode(state, 0xff)

      if (typeof b === 'number') {
        if (byteLength === 1) c.uint8.encode(state, b)
        else if (byteLength === 2) c.uint16.encode(state, b)
        else if (byteLength === 4) c.uint32.encode(state, b)
        else c.uint64.encode(state, b)
      } else {
        state.buffer.set(b, state.start)

        if (b.byteLength < byteLength) {
          // Zero-fill the rest of the byte length.
          state.buffer.fill(
            0,
            state.start + b.byteLength,
            state.start + byteLength
          )
        }

        state.start += byteLength
      }
    },

    decode(state) {
      const byte = state.buffer[state.start]

      let byteLength
      if (byte <= 0xfc) byteLength = 1
      else if (byte === 0xfd) byteLength = 2
      else if (byte === 0xfe) byteLength = 4
      else byteLength = 8

      if (byteLength > 1) state.start++ // Skip the length byte

      if (state.end - state.start < byteLength) throw new Error('Out of bounds')

      const b = state.buffer.subarray(state.start, (state.start += byteLength))

      return length <= 8 ? b.subarray(0, 1) : b
    }
  }
}
{
  "name": "compact-encoding-bitfield",
  "version": "1.1.0",
  "description": "Compact codec for bitfields",
  "main": "index.js",
  "files": [
    "index.js"
  ],
  "scripts": {
    "format": "prettier --write .",
    "lint": "prettier --check . && lunte",
    "test": "npm run test:node && npm run test:bare",
    "test:node": "brittle-node --coverage test.mjs",
    "test:bare": "brittle-bare --coverage test.mjs"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/compact-encoding/compact-encoding-bitfield.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/compact-encoding/compact-encoding-bitfield/issues"
  },
  "homepage": "https://github.com/compact-encoding/compact-encoding-bitfield#readme",
  "imports": {
    "util": {
      "bare": "bare-utils",
      "default": "util"
    }
  },
  "dependencies": {
    "compact-encoding": "^3.0.0"
  },
  "devDependencies": {
    "bare-utils": "^1.5.1",
    "brittle": "^3.1.0",
    "lunte": "^1.3.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^1.0.0"
  }
}
const LE = (exports.LE =
  new Uint8Array(new Uint16Array([0xff]).buffer)[0] === 0xff)

exports.BE = !LE
const b4a = require('b4a')

const { BE } = require('./endian')

exports.state = function (start = 0, end = 0, buffer = null) {
  return { start, end, buffer }
}

const raw = (exports.raw = require('./raw'))

const uint = (exports.uint = {
  preencode(state, n) {
    state.end += n <= 0xfc ? 1 : n <= 0xffff ? 3 : n <= 0xffffffff ? 5 : 9
  },
  encode(state, n) {
    if (n <= 0xfc) uint8.encode(state, n)
    else if (n <= 0xffff) {
      state.buffer[state.start++] = 0xfd
      uint16.encode(state, n)
    } else if (n <= 0xffffffff) {
      state.buffer[state.start++] = 0xfe
      uint32.encode(state, n)
    } else {
      state.buffer[state.start++] = 0xff
      uint64.encode(state, n)
    }
  },
  decode(state) {
    const a = uint8.decode(state)
    if (a <= 0xfc) return a
    if (a === 0xfd) return uint16.decode(state)
    if (a === 0xfe) return uint32.decode(state)
    return uint64.decode(state)
  }
})

const uint8 = (exports.uint8 = {
  preencode(state, n) {
    state.end += 1
  },
  encode(state, n) {
    validateUint(n)
    state.buffer[state.start++] = n
  },
  decode(state) {
    if (state.start >= state.end) throw new Error('Out of bounds')
    return state.buffer[state.start++]
  }
})

const uint16 = (exports.uint16 = {
  preencode(state, n) {
    state.end += 2
  },
  encode(state, n) {
    validateUint(n)
    state.buffer[state.start++] = n
    state.buffer[state.start++] = n >>> 8
  },
  decode(state) {
    if (state.end - state.start < 2) throw new Error('Out of bounds')
    return state.buffer[state.start++] + state.buffer[state.start++] * 0x100
  }
})

const uint24 = (exports.uint24 = {
  preencode(state, n) {
    state.end += 3
  },
  encode(state, n) {
    validateUint(n)
    state.buffer[state.start++] = n
    state.buffer[state.start++] = n >>> 8
    state.buffer[state.start++] = n >>> 16
  },
  decode(state) {
    if (state.end - state.start < 3) throw new Error('Out of bounds')
    return (
      state.buffer[state.start++] +
      state.buffer[state.start++] * 0x100 +
      state.buffer[state.start++] * 0x10000
    )
  }
})

const uint32 = (exports.uint32 = {
  preencode(state, n) {
    state.end += 4
  },
  encode(state, n) {
    validateUint(n)
    state.buffer[state.start++] = n
    state.buffer[state.start++] = n >>> 8
    state.buffer[state.start++] = n >>> 16
    state.buffer[state.start++] = n >>> 24
  },
  decode(state) {
    if (state.end - state.start < 4) throw new Error('Out of bounds')
    return (
      state.buffer[state.start++] +
      state.buffer[state.start++] * 0x100 +
      state.buffer[state.start++] * 0x10000 +
      state.buffer[state.start++] * 0x1000000
    )
  }
})

const uint32be = (exports.uint32be = {
  preencode(state, n) {
    state.end += 4
  },
  encode(state, n) {
    validateUint(n)
    state.buffer[state.start++] = n >>> 24
    state.buffer[state.start++] = n >>> 16
    state.buffer[state.start++] = n >>> 8
    state.buffer[state.start++] = n
  },
  decode(state) {
    if (state.end - state.start < 4) throw new Error('Out of bounds')
    return (
      state.buffer[state.start++] * 0x1000000 +
      state.buffer[state.start++] * 0x10000 +
      state.buffer[state.start++] * 0x100 +
      state.buffer[state.start++]
    )
  }
})

const uint40 = (exports.uint40 = {
  preencode(state, n) {
    state.end += 5
  },
  encode(state, n) {
    validateUint(n)
    const r = Math.floor(n / 0x100)
    uint8.encode(state, n)
    uint32.encode(state, r)
  },
  decode(state) {
    if (state.end - state.start < 5) throw new Error('Out of bounds')
    return uint8.decode(state) + 0x100 * uint32.decode(state)
  }
})

const uint48 = (exports.uint48 = {
  preencode(state, n) {
    state.end += 6
  },
  encode(state, n) {
    validateUint(n)
    const r = Math.floor(n / 0x10000)
    uint16.encode(state, n)
    uint32.encode(state, r)
  },
  decode(state) {
    if (state.end - state.start < 6) throw new Error('Out of bounds')
    return uint16.decode(state) + 0x10000 * uint32.decode(state)
  }
})

const uint56 = (exports.uint56 = {
  preencode(state, n) {
    state.end += 7
  },
  encode(state, n) {
    validateUint(n)
    const r = Math.floor(n / 0x1000000)
    uint24.encode(state, n)
    uint32.encode(state, r)
  },
  decode(state) {
    if (state.end - state.start < 7) throw new Error('Out of bounds')
    return uint24.decode(state) + 0x1000000 * uint32.decode(state)
  }
})

const uint64 = (exports.uint64 = {
  preencode(state, n) {
    state.end += 8
  },
  encode(state, n) {
    validateUint(n)
    const r = Math.floor(n / 0x100000000)
    uint32.encode(state, n)
    uint32.encode(state, r)
  },
  decode(state) {
    if (state.end - state.start < 8) throw new Error('Out of bounds')
    return uint32.decode(state) + 0x100000000 * uint32.decode(state)
  }
})

exports.uint64be = {
  preencode(state, n) {
    state.end += 8
  },
  encode(state, n) {
    validateUint(n)
    const r = Math.floor(n / 0x100000000)
    uint32be.encode(state, r)
    uint32be.encode(state, n)
  },
  decode(state) {
    if (state.end - state.start < 8) throw new Error('Out of bounds')
    return 0x100000000 * uint32be.decode(state) + uint32be.decode(state)
  }
}

const int = (exports.int = zigZagInt(uint))
exports.int8 = zigZagInt(uint8)
exports.int16 = zigZagInt(uint16)
exports.int24 = zigZagInt(uint24)
exports.int32 = zigZagInt(uint32)
exports.int40 = zigZagInt(uint40)
exports.int48 = zigZagInt(uint48)
exports.int56 = zigZagInt(uint56)
exports.int64 = zigZagInt(uint64)

const biguint64 = (exports.biguint64 = {
  preencode(state, n) {
    state.end += 8
  },
  encode(state, n) {
    const view = new DataView(
      state.buffer.buffer,
      state.start + state.buffer.byteOffset,
      8
    )
    view.setBigUint64(0, n, true) // little endian
    state.start += 8
  },
  decode(state) {
    if (state.end - state.start < 8) throw new Error('Out of bounds')
    const view = new DataView(
      state.buffer.buffer,
      state.start + state.buffer.byteOffset,
      8
    )
    const n = view.getBigUint64(0, true) // little endian
    state.start += 8
    return n
  }
})

exports.bigint64 = zigZagBigInt(biguint64)

const biguint = (exports.biguint = {
  preencode(state, n) {
    let len = 0
    for (let m = n; m; m = m >> 64n) len++
    uint.preencode(state, len)
    state.end += 8 * len
  },
  encode(state, n) {
    let len = 0
    for (let m = n; m; m = m >> 64n) len++
    uint.encode(state, len)
    const view = new DataView(
      state.buffer.buffer,
      state.start + state.buffer.byteOffset,
      8 * len
    )
    for (let m = n, i = 0; m; m = m >> 64n, i += 8) {
      view.setBigUint64(i, BigInt.asUintN(64, m), true) // little endian
    }
    state.start += 8 * len
  },
  decode(state) {
    const len = uint.decode(state)
    if (state.end - state.start < 8 * len) throw new Error('Out of bounds')
    const view = new DataView(
      state.buffer.buffer,
      state.start + state.buffer.byteOffset,
      8 * len
    )
    let n = 0n
    for (let i = len - 1; i >= 0; i--)
      n = (n << 64n) + view.getBigUint64(i * 8, true) // little endian
    state.start += 8 * len
    return n
  }
})

exports.bigint = zigZagBigInt(biguint)

exports.lexint = require('./lexint')

exports.float32 = {
  preencode(state, n) {
    state.end += 4
  },
  encode(state, n) {
    const view = new DataView(
      state.buffer.buffer,
      state.start + state.buffer.byteOffset,
      4
    )
    view.setFloat32(0, n, true) // little endian
    state.start += 4
  },
  decode(state) {
    if (state.end - state.start < 4) throw new Error('Out of bounds')
    const view = new DataView(
      state.buffer.buffer,
      state.start + state.buffer.byteOffset,
      4
    )
    const float = view.getFloat32(0, true) // little endian
    state.start += 4
    return float
  }
}

exports.float64 = {
  preencode(state, n) {
    state.end += 8
  },
  encode(state, n) {
    const view = new DataView(
      state.buffer.buffer,
      state.start + state.buffer.byteOffset,
      8
    )
    view.setFloat64(0, n, true) // little endian
    state.start += 8
  },
  decode(state) {
    if (state.end - state.start < 8) throw new Error('Out of bounds')
    const view = new DataView(
      state.buffer.buffer,
      state.start + state.buffer.byteOffset,
      8
    )
    const float = view.getFloat64(0, true) // little endian
    state.start += 8
    return float
  }
}

const buffer = (exports.buffer = {
  preencode(state, b) {
    uint8array.preencode(state, b)
  },
  encode(state, b) {
    uint8array.encode(state, b)
  },
  decode(state) {
    const len = uint.decode(state)
    if (state.end - state.start < len) throw new Error('Out of bounds')
    return state.buffer.subarray(state.start, (state.start += len))
  }
})

exports.optionalBuffer = {
  preencode(state, b) {
    if (b) uint8array.preencode(state, b)
    else state.end++
  },
  encode(state, b) {
    if (b) uint8array.encode(state, b)
    else state.buffer[state.start++] = 0
  },
  decode(state) {
    const len = uint.decode(state)
    if (len === 0) return null
    if (state.end - state.start < len) throw new Error('Out of bounds')
    return state.buffer.subarray(state.start, (state.start += len))
  }
}

exports.binary = {
  ...buffer,
  preencode(state, b) {
    if (typeof b === 'string') utf8.preencode(state, b)
    else buffer.preencode(state, b)
  },
  encode(state, b) {
    if (typeof b === 'string') utf8.encode(state, b)
    else buffer.encode(state, b)
  }
}

exports.arraybuffer = {
  preencode(state, b) {
    uint.preencode(state, b.byteLength)
    state.end += b.byteLength
  },
  encode(state, b) {
    uint.encode(state, b.byteLength)

    const view = new Uint8Array(b)

    state.buffer.set(view, state.start)
    state.start += b.byteLength
  },
  decode(state) {
    const len = uint.decode(state)

    const b = new ArrayBuffer(len)
    const view = new Uint8Array(b)

    view.set(state.buffer.subarray(state.start, (state.start += len)))

    return b
  }
}

function typedarray(TypedArray, swap) {
  const n = TypedArray.BYTES_PER_ELEMENT

  return {
    preencode(state, b) {
      uint.preencode(state, b.length)
      state.end += b.byteLength
    },
    encode(state, b) {
      uint.encode(state, b.length)

      const view = new Uint8Array(b.buffer, b.byteOffset, b.byteLength)

      if (BE && swap) swap(view)

      state.buffer.set(view, state.start)
      state.start += b.byteLength
    },
    decode(state) {
      const len = uint.decode(state)

      let b = state.buffer.subarray(state.start, (state.start += len * n))
      if (b.byteLength !== len * n) throw new Error('Out of bounds')
      if (b.byteOffset % n !== 0) b = new Uint8Array(b)

      if (BE && swap) swap(b)

      return new TypedArray(b.buffer, b.byteOffset, b.byteLength / n)
    }
  }
}

const uint8array = (exports.uint8array = typedarray(Uint8Array))
exports.uint16array = typedarray(Uint16Array, b4a.swap16)
exports.uint32array = typedarray(Uint32Array, b4a.swap32)

exports.int8array = typedarray(Int8Array)
exports.int16array = typedarray(Int16Array, b4a.swap16)
exports.int32array = typedarray(Int32Array, b4a.swap32)

exports.biguint64array = typedarray(BigUint64Array, b4a.swap64)
exports.bigint64array = typedarray(BigInt64Array, b4a.swap64)

exports.float32array = typedarray(Float32Array, b4a.swap32)
exports.float64array = typedarray(Float64Array, b4a.swap64)

function string(encoding) {
  return {
    preencode(state, s) {
      const len = b4a.byteLength(s, encoding)
      uint.preencode(state, len)
      state.end += len
    },
    encode(state, s) {
      const len = b4a.byteLength(s, encoding)
      uint.encode(state, len)
      b4a.write(state.buffer, s, state.start, encoding)
      state.start += len
    },
    decode(state) {
      const len = uint.decode(state)
      if (state.end - state.start < len) throw new Error('Out of bounds')
      return b4a.toString(
        state.buffer,
        encoding,
        state.start,
        (state.start += len)
      )
    },
    fixed(n) {
      return {
        preencode(state) {
          state.end += n
        },
        encode(state, s) {
          b4a.write(state.buffer, s, state.start, n, encoding)
          state.start += n
        },
        decode(state) {
          if (state.end - state.start < n) throw new Error('Out of bounds')
          return b4a.toString(
            state.buffer,
            encoding,
            state.start,
            (state.start += n)
          )
        }
      }
    }
  }
}

const utf8 = (exports.string = exports.utf8 = string('utf-8'))
exports.ascii = string('ascii')
exports.hex = string('hex')
exports.base64 = string('base64')
exports.ucs2 = exports.utf16le = string('utf16le')

exports.bool = {
  preencode(state, b) {
    state.end++
  },
  encode(state, b) {
    state.buffer[state.start++] = b ? 1 : 0
  },
  decode(state) {
    if (state.start >= state.end) throw Error('Out of bounds')
    return state.buffer[state.start++] === 1
  }
}

const fixed = (exports.fixed = function fixed(n) {
  return {
    preencode(state, s) {
      if (s.byteLength !== n) throw new Error('Incorrect buffer size')
      state.end += n
    },
    encode(state, s) {
      state.buffer.set(s, state.start)
      state.start += n
    },
    decode(state) {
      if (state.end - state.start < n) throw new Error('Out of bounds')
      return state.buffer.subarray(state.start, (state.start += n))
    }
  }
})

exports.fixed32 = fixed(32)
exports.fixed64 = fixed(64)

exports.array = function array(enc) {
  return {
    preencode(state, list) {
      uint.preencode(state, list.length)
      for (let i = 0; i < list.length; i++) enc.preencode(state, list[i])
    },
    encode(state, list) {
      uint.encode(state, list.length)
      for (let i = 0; i < list.length; i++) enc.encode(state, list[i])
    },
    decode(state) {
      const len = uint.decode(state)
      if (len > 0x100000) throw new Error('Array is too big')
      const arr = new Array(len)
      for (let i = 0; i < len; i++) arr[i] = enc.decode(state)
      return arr
    }
  }
}

exports.frame = function frame(enc) {
  const dummy = exports.state()

  return {
    preencode(state, m) {
      const end = state.end
      enc.preencode(state, m)
      uint.preencode(state, state.end - end)
    },
    encode(state, m) {
      dummy.end = 0
      enc.preencode(dummy, m)
      uint.encode(state, dummy.end)
      enc.encode(state, m)
    },
    decode(state) {
      const end = state.end
      const len = uint.decode(state)
      state.end = state.start + len
      const m = enc.decode(state)
      state.start = state.end
      state.end = end
      return m
    }
  }
}

exports.date = {
  preencode(state, d) {
    int.preencode(state, d.getTime())
  },
  encode(state, d) {
    int.encode(state, d.getTime())
  },
  decode(state, d) {
    return new Date(int.decode(state))
  }
}

exports.json = {
  preencode(state, v) {
    utf8.preencode(state, JSON.stringify(v))
  },
  encode(state, v) {
    utf8.encode(state, JSON.stringify(v))
  },
  decode(state) {
    return JSON.parse(utf8.decode(state))
  }
}

exports.ndjson = {
  preencode(state, v) {
    utf8.preencode(state, JSON.stringify(v) + '\n')
  },
  encode(state, v) {
    utf8.encode(state, JSON.stringify(v) + '\n')
  },
  decode(state) {
    return JSON.parse(utf8.decode(state))
  }
}

// simple helper for when you want to just express nothing
exports.none = {
  preencode(state, n) {
    // do nothing
  },
  encode(state, n) {
    // do nothing
  },
  decode(state) {
    return null
  }
}

// "any" encoders here for helping just structure any object without schematising it

const anyArray = {
  preencode(state, arr) {
    uint.preencode(state, arr.length)
    for (let i = 0; i < arr.length; i++) {
      any.preencode(state, arr[i])
    }
  },
  encode(state, arr) {
    uint.encode(state, arr.length)
    for (let i = 0; i < arr.length; i++) {
      any.encode(state, arr[i])
    }
  },
  decode(state) {
    const arr = []
    let len = uint.decode(state)
    while (len-- > 0) {
      arr.push(any.decode(state))
    }
    return arr
  }
}

const anyObject = {
  preencode(state, o) {
    const keys = Object.keys(o)
    uint.preencode(state, keys.length)
    for (const key of keys) {
      utf8.preencode(state, key)
      any.preencode(state, o[key])
    }
  },
  encode(state, o) {
    const keys = Object.keys(o)
    uint.encode(state, keys.length)
    for (const key of keys) {
      utf8.encode(state, key)
      any.encode(state, o[key])
    }
  },
  decode(state) {
    let len = uint.decode(state)
    const o = {}
    while (len-- > 0) {
      const key = utf8.decode(state)
      o[key] = any.decode(state)
    }
    return o
  }
}

const anyTypes = [
  exports.none,
  exports.bool,
  exports.string,
  exports.buffer,
  exports.uint,
  exports.int,
  exports.float64,
  anyArray,
  anyObject,
  exports.date
]

const any = (exports.any = {
  preencode(state, o) {
    const t = getType(o)
    uint.preencode(state, t)
    anyTypes[t].preencode(state, o)
  },
  encode(state, o) {
    const t = getType(o)
    uint.encode(state, t)
    anyTypes[t].encode(state, o)
  },
  decode(state) {
    const t = uint.decode(state)
    if (t >= anyTypes.length) throw new Error('Unknown type: ' + t)
    return anyTypes[t].decode(state)
  }
})

const port = (exports.port = uint16)

const address = (host, family) => {
  return {
    preencode(state, m) {
      host.preencode(state, m.host)
      port.preencode(state, m.port)
    },
    encode(state, m) {
      host.encode(state, m.host)
      port.encode(state, m.port)
    },
    decode(state) {
      return {
        host: host.decode(state),
        family,
        port: port.decode(state)
      }
    }
  }
}

const ipv4 = (exports.ipv4 = {
  preencode(state) {
    state.end += 4
  },
  encode(state, string) {
    const start = state.start
    const end = start + 4

    let i = 0

    while (i < string.length) {
      let n = 0
      let c

      while (
        i < string.length &&
        (c = string.charCodeAt(i++)) !== /* . */ 0x2e
      ) {
        n = n * 10 + (c - /* 0 */ 0x30)
      }

      state.buffer[state.start++] = n
    }

    state.start = end
  },
  decode(state) {
    if (state.end - state.start < 4) throw new Error('Out of bounds')
    return (
      state.buffer[state.start++] +
      '.' +
      state.buffer[state.start++] +
      '.' +
      state.buffer[state.start++] +
      '.' +
      state.buffer[state.start++]
    )
  }
})

exports.ipv4Address = address(ipv4, 4)

const ipv6 = (exports.ipv6 = {
  preencode(state) {
    state.end += 16
  },
  encode(state, string) {
    const start = state.start
    const end = start + 16

    let i = 0
    let split = null

    while (i < string.length) {
      let n = 0
      let c

      while (
        i < string.length &&
        (c = string.charCodeAt(i++)) !== /* : */ 0x3a
      ) {
        if (c >= 0x30 && c <= 0x39) n = n * 0x10 + (c - /* 0 */ 0x30)
        else if (c >= 0x41 && c <= 0x46) n = n * 0x10 + (c - /* A */ 0x41 + 10)
        else if (c >= 0x61 && c <= 0x66) n = n * 0x10 + (c - /* a */ 0x61 + 10)
      }

      state.buffer[state.start++] = n >>> 8
      state.buffer[state.start++] = n

      if (i < string.length && string.charCodeAt(i) === /* : */ 0x3a) {
        i++
        split = state.start
      }
    }

    if (split !== null) {
      const offset = end - state.start
      state.buffer
        .copyWithin(split + offset, split)
        .fill(0, split, split + offset)
    }

    state.start = end
  },
  decode(state) {
    if (state.end - state.start < 16) throw new Error('Out of bounds')
    return (
      (
        state.buffer[state.start++] * 256 +
        state.buffer[state.start++]
      ).toString(16) +
      ':' +
      (
        state.buffer[state.start++] * 256 +
        state.buffer[state.start++]
      ).toString(16) +
      ':' +
      (
        state.buffer[state.start++] * 256 +
        state.buffer[state.start++]
      ).toString(16) +
      ':' +
      (
        state.buffer[state.start++] * 256 +
        state.buffer[state.start++]
      ).toString(16) +
      ':' +
      (
        state.buffer[state.start++] * 256 +
        state.buffer[state.start++]
      ).toString(16) +
      ':' +
      (
        state.buffer[state.start++] * 256 +
        state.buffer[state.start++]
      ).toString(16) +
      ':' +
      (
        state.buffer[state.start++] * 256 +
        state.buffer[state.start++]
      ).toString(16) +
      ':' +
      (
        state.buffer[state.start++] * 256 +
        state.buffer[state.start++]
      ).toString(16)
    )
  }
})

exports.ipv6Address = address(ipv6, 6)

const ip = (exports.ip = {
  preencode(state, string) {
    const family = string.includes(':') ? 6 : 4
    uint8.preencode(state, family)
    if (family === 4) ipv4.preencode(state)
    else ipv6.preencode(state)
  },
  encode(state, string) {
    const family = string.includes(':') ? 6 : 4
    uint8.encode(state, family)
    if (family === 4) ipv4.encode(state, string)
    else ipv6.encode(state, string)
  },
  decode(state) {
    const family = uint8.decode(state)
    if (family === 4) return ipv4.decode(state)
    else return ipv6.decode(state)
  }
})

exports.ipAddress = {
  preencode(state, m) {
    ip.preencode(state, m.host)
    port.preencode(state, m.port)
  },
  encode(state, m) {
    ip.encode(state, m.host)
    port.encode(state, m.port)
  },
  decode(state) {
    const family = uint8.decode(state)
    return {
      host: family === 4 ? ipv4.decode(state) : ipv6.decode(state),
      family,
      port: port.decode(state)
    }
  }
}

const record = (exports.record = function (keyEncoding, valueEncoding) {
  return {
    preencode(state, v) {
      const keys = Object.keys(v)
      uint.preencode(state, keys.length)
      for (const k of keys) {
        keyEncoding.preencode(state, k)
        valueEncoding.preencode(state, v[k])
      }
    },
    encode(state, v) {
      const keys = Object.keys(v)
      uint.encode(state, keys.length)
      for (const k of keys) {
        keyEncoding.encode(state, k)
        valueEncoding.encode(state, v[k])
      }
    },
    decode(state) {
      const out = Object.create(null)
      const keys = uint.decode(state)
      for (let i = 0; i < keys; i++) {
        out[keyEncoding.decode(state)] = valueEncoding.decode(state)
      }
      return out
    }
  }
})

exports.stringRecord = record(utf8, utf8)

function getType(o) {
  if (o === null || o === undefined) return 0
  if (typeof o === 'boolean') return 1
  if (typeof o === 'string') return 2
  if (b4a.isBuffer(o)) return 3
  if (typeof o === 'number') {
    if (Number.isInteger(o)) return o >= 0 ? 4 : 5
    return 6
  }
  if (Array.isArray(o)) return 7
  if (o instanceof Date) return 9
  if (typeof o === 'object') return 8

  throw new Error('Unsupported type for ' + o)
}

exports.from = function from(enc) {
  if (typeof enc === 'string') return fromNamed(enc)
  if (enc.preencode) return enc
  if (enc.encodingLength) return fromAbstractEncoder(enc)
  return fromCodec(enc)
}

function fromNamed(enc) {
  switch (enc) {
    case 'ascii':
      return raw.ascii
    case 'utf-8':
    case 'utf8':
      return raw.utf8
    case 'hex':
      return raw.hex
    case 'base64':
      return raw.base64
    case 'utf16-le':
    case 'utf16le':
    case 'ucs-2':
    case 'ucs2':
      return raw.ucs2
    case 'ndjson':
      return raw.ndjson
    case 'json':
      return raw.json
    case 'binary':
    default:
      return raw.binary
  }
}

function fromCodec(enc) {
  let tmpM = null
  let tmpBuf = null

  return {
    preencode(state, m) {
      tmpM = m
      tmpBuf = enc.encode(m)
      state.end += tmpBuf.byteLength
    },
    encode(state, m) {
      raw.encode(state, m === tmpM ? tmpBuf : enc.encode(m))
      tmpM = tmpBuf = null
    },
    decode(state) {
      return enc.decode(raw.decode(state))
    }
  }
}

function fromAbstractEncoder(enc) {
  return {
    preencode(state, m) {
      state.end += enc.encodingLength(m)
    },
    encode(state, m) {
      enc.encode(m, state.buffer, state.start)
      state.start += enc.encode.bytes
    },
    decode(state) {
      const m = enc.decode(state.buffer, state.start, state.end)
      state.start += enc.decode.bytes
      return m
    }
  }
}

exports.encode = function encode(enc, m) {
  const state = exports.state()
  enc.preencode(state, m)
  state.buffer = b4a.allocUnsafe(state.end)
  enc.encode(state, m)
  return state.buffer
}

exports.decode = function decode(enc, buffer) {
  return enc.decode(exports.state(0, buffer.byteLength, buffer))
}

function zigZagInt(enc) {
  return {
    preencode(state, n) {
      enc.preencode(state, zigZagEncodeInt(n))
    },
    encode(state, n) {
      enc.encode(state, zigZagEncodeInt(n))
    },
    decode(state) {
      return zigZagDecodeInt(enc.decode(state))
    }
  }
}

function zigZagDecodeInt(n) {
  return n === 0 ? n : (n & 1) === 0 ? n / 2 : -(n + 1) / 2
}

function zigZagEncodeInt(n) {
  // 0, -1, 1, -2, 2, ...
  return n < 0 ? 2 * -n - 1 : n === 0 ? 0 : 2 * n
}

function zigZagBigInt(enc) {
  return {
    preencode(state, n) {
      enc.preencode(state, zigZagEncodeBigInt(n))
    },
    encode(state, n) {
      enc.encode(state, zigZagEncodeBigInt(n))
    },
    decode(state) {
      return zigZagDecodeBigInt(enc.decode(state))
    }
  }
}

function zigZagDecodeBigInt(n) {
  return n === 0n ? n : (n & 1n) === 0n ? n / 2n : -(n + 1n) / 2n
}

function zigZagEncodeBigInt(n) {
  // 0, -1, 1, -2, 2, ...
  return n < 0n ? 2n * -n - 1n : n === 0n ? 0n : 2n * n
}

function validateUint(n) {
  if (n >= 0 === false /* Handles NaN as well */)
    throw new Error('uint must be positive')
  // Beyond this, Number arithmetic loses precision and silently corrupts the
  // encoding. The zig-zag int codecs route through here too, so this also
  // guards int values whose doubled magnitude exceeds the safe range.
  if (n > Number.MAX_SAFE_INTEGER)
    throw new Error(
      'integer is greater than the maximum safe integer, use biguint/bigint'
    )
}
module.exports = {
  preencode,
  encode,
  decode
}

function preencode(state, num) {
  if (num < 251) {
    state.end++
  } else if (num < 256) {
    state.end += 2
  } else if (num < 0x10000) {
    state.end += 3
  } else if (num < 0x1000000) {
    state.end += 4
  } else if (num < 0x100000000) {
    state.end += 5
  } else {
    state.end++
    const exp = Math.floor(Math.log(num) / Math.log(2)) - 32
    preencode(state, exp)
    state.end += 6
  }
}

function encode(state, num) {
  const max = 251
  const x = num - max

  if (num < max) {
    state.buffer[state.start++] = num
  } else if (num < 256) {
    state.buffer[state.start++] = max
    state.buffer[state.start++] = x
  } else if (num < 0x10000) {
    state.buffer[state.start++] = max + 1
    state.buffer[state.start++] = (x >> 8) & 0xff
    state.buffer[state.start++] = x & 0xff
  } else if (num < 0x1000000) {
    state.buffer[state.start++] = max + 2
    state.buffer[state.start++] = x >> 16
    state.buffer[state.start++] = (x >> 8) & 0xff
    state.buffer[state.start++] = x & 0xff
  } else if (num < 0x100000000) {
    state.buffer[state.start++] = max + 3
    state.buffer[state.start++] = x >> 24
    state.buffer[state.start++] = (x >> 16) & 0xff
    state.buffer[state.start++] = (x >> 8) & 0xff
    state.buffer[state.start++] = x & 0xff
  } else {
    // need to use Math here as bitwise ops are 32 bit
    const exp = Math.floor(Math.log(x) / Math.log(2)) - 32
    state.buffer[state.start++] = 0xff

    encode(state, exp)
    const rem = x / Math.pow(2, exp - 11)

    for (let i = 5; i >= 0; i--) {
      state.buffer[state.start++] = (rem / Math.pow(2, 8 * i)) & 0xff
    }
  }
}

function decode(state) {
  const max = 251

  if (state.end - state.start < 1) throw new Error('Out of bounds')

  const flag = state.buffer[state.start++]

  if (flag < max) return flag

  if (state.end - state.start < flag - max + 1) {
    throw new Error('Out of bounds.')
  }

  if (flag < 252) {
    return state.buffer[state.start++] + max
  }

  if (flag < 253) {
    return (
      (state.buffer[state.start++] << 8) + state.buffer[state.start++] + max
    )
  }

  if (flag < 254) {
    return (
      (state.buffer[state.start++] << 16) +
      (state.buffer[state.start++] << 8) +
      state.buffer[state.start++] +
      max
    )
  }

  // << 24 result may be interpreted as negative
  if (flag < 255) {
    return (
      state.buffer[state.start++] * 0x1000000 +
      (state.buffer[state.start++] << 16) +
      (state.buffer[state.start++] << 8) +
      state.buffer[state.start++] +
      max
    )
  }

  const exp = decode(state)

  if (state.end - state.start < 6) throw new Error('Out of bounds')

  let rem = 0
  for (let i = 5; i >= 0; i--) {
    rem += state.buffer[state.start++] * Math.pow(2, 8 * i)
  }

  return rem * Math.pow(2, exp - 11) + max
}
{
  "name": "compact-encoding",
  "version": "3.2.0",
  "description": "A series of compact encoding schemes for building small and fast parsers and serializers",
  "main": "index.js",
  "files": [
    "endian.js",
    "index.js",
    "lexint.js",
    "raw.js"
  ],
  "dependencies": {
    "b4a": "^1.3.0"
  },
  "devDependencies": {
    "brittle": "^3.0.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^1.0.0"
  },
  "scripts": {
    "format": "prettier . --write",
    "test": "prettier . --check && brittle test.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/holepunchto/compact-encoding.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/compact-encoding/issues"
  },
  "homepage": "https://github.com/holepunchto/compact-encoding"
}
const b4a = require('b4a')

const { BE } = require('./endian')

exports = module.exports = {
  preencode(state, b) {
    state.end += b.byteLength
  },
  encode(state, b) {
    state.buffer.set(b, state.start)
    state.start += b.byteLength
  },
  decode(state) {
    const b = state.buffer.subarray(state.start, state.end)
    state.start = state.end
    return b
  }
}

const buffer = (exports.buffer = {
  preencode(state, b) {
    uint8array.preencode(state, b)
  },
  encode(state, b) {
    uint8array.encode(state, b)
  },
  decode(state) {
    const b = state.buffer.subarray(state.start)
    state.start = state.end
    return b
  }
})

exports.binary = {
  ...buffer,
  preencode(state, b) {
    if (typeof b === 'string') utf8.preencode(state, b)
    else buffer.preencode(state, b)
  },
  encode(state, b) {
    if (typeof b === 'string') utf8.encode(state, b)
    else buffer.encode(state, b)
  }
}

exports.arraybuffer = {
  preencode(state, b) {
    state.end += b.byteLength
  },
  encode(state, b) {
    const view = new Uint8Array(b)

    state.buffer.set(view, state.start)
    state.start += b.byteLength
  },
  decode(state) {
    const b = new ArrayBuffer(state.end - state.start)
    const view = new Uint8Array(b)

    view.set(state.buffer.subarray(state.start))

    state.start = state.end

    return b
  }
}

function typedarray(TypedArray, swap) {
  const n = TypedArray.BYTES_PER_ELEMENT

  return {
    preencode(state, b) {
      state.end += b.byteLength
    },
    encode(state, b) {
      const view = new Uint8Array(b.buffer, b.byteOffset, b.byteLength)

      if (BE && swap) swap(view)

      state.buffer.set(view, state.start)
      state.start += b.byteLength
    },
    decode(state) {
      let b = state.buffer.subarray(state.start)
      if (b.byteOffset % n !== 0) b = new Uint8Array(b)

      if (BE && swap) swap(b)

      state.start = state.end

      return new TypedArray(b.buffer, b.byteOffset, b.byteLength / n)
    }
  }
}

const uint8array = (exports.uint8array = typedarray(Uint8Array))
exports.uint16array = typedarray(Uint16Array, b4a.swap16)
exports.uint32array = typedarray(Uint32Array, b4a.swap32)

exports.int8array = typedarray(Int8Array)
exports.int16array = typedarray(Int16Array, b4a.swap16)
exports.int32array = typedarray(Int32Array, b4a.swap32)

exports.biguint64array = typedarray(BigUint64Array, b4a.swap64)
exports.bigint64array = typedarray(BigInt64Array, b4a.swap64)

exports.float32array = typedarray(Float32Array, b4a.swap32)
exports.float64array = typedarray(Float64Array, b4a.swap64)

function string(encoding) {
  return {
    preencode(state, s) {
      state.end += b4a.byteLength(s, encoding)
    },
    encode(state, s) {
      state.start += b4a.write(state.buffer, s, state.start, encoding)
    },
    decode(state) {
      const s = b4a.toString(state.buffer, encoding, state.start)
      state.start = state.end
      return s
    }
  }
}

const utf8 = (exports.string = exports.utf8 = string('utf-8'))
exports.ascii = string('ascii')
exports.hex = string('hex')
exports.base64 = string('base64')
exports.ucs2 = exports.utf16le = string('utf16le')

exports.array = function array(enc) {
  return {
    preencode(state, list) {
      for (const value of list) enc.preencode(state, value)
    },
    encode(state, list) {
      for (const value of list) enc.encode(state, value)
    },
    decode(state) {
      const arr = []
      while (state.start < state.end) arr.push(enc.decode(state))
      return arr
    }
  }
}

exports.json = {
  preencode(state, v) {
    utf8.preencode(state, JSON.stringify(v))
  },
  encode(state, v) {
    utf8.encode(state, JSON.stringify(v))
  },
  decode(state) {
    return JSON.parse(utf8.decode(state))
  }
}

exports.ndjson = {
  preencode(state, v) {
    utf8.preencode(state, JSON.stringify(v) + '\n')
  },
  encode(state, v) {
    utf8.encode(state, JSON.stringify(v) + '\n')
  },
  decode(state) {
    return JSON.parse(utf8.decode(state))
  }
}
const safetyCatch = require('safety-catch')

module.exports = class CoreCoupler {
  constructor (target, wakeup) {
    this.target = target
    this.wakeup = wakeup
    this.coupled = new Set()

    this._onpeeraddBound = this._onpeeradd.bind(this)
    this.target.on('peer-add', this._onpeeraddBound)
  }

  add (core) {
    const added = this.coupled.size
    this.coupled.add(core)
    if (added !== this.coupled.size) this._couple(core)
  }

  remove (core) {
    this.coupled.delete(core)
  }

  destroy () {
    this.target.off('peer-add', this._onpeeraddBound)
  }

  async update (stream) {
    const muxer = stream.userData
    if (!muxer) return

    try {
      if (!(await this._hasMuxer(this.target, muxer))) return

      let wakeup = null

      for (const core of this.coupled) {
        if (await this._hasMuxer(core, muxer)) continue
        if (wakeup === null) wakeup = []
        wakeup.push(core)
      }

      if (wakeup !== null) {
        this.wakeup(stream, wakeup)
      }
    } catch (err) {
      safetyCatch(err)
    }
  }

  async _couple (core) {
    try {
      let wakeup = null

      for (const peer of this.target.peers) {
        if (await this._hasPeer(core, peer)) continue
        if (wakeup === null) wakeup = []
        wakeup.push(peer)
      }

      if (wakeup !== null && this.coupled.has(core)) {
        for (const peer of wakeup) this.wakeup(peer.stream, [core])
      }
    } catch (err) {
      safetyCatch(err)
    }
  }

  async _onpeeradd (peer) {
    try {
      let wakeup = null

      for (const core of this.coupled) {
        if (await this._hasPeer(core, peer)) continue
        if (wakeup === null) wakeup = []
        wakeup.push(core)
      }

      if (wakeup !== null) {
        this.wakeup(peer.stream, wakeup)
      }
    } catch (err) {
      safetyCatch(err)
    }
  }

  _hasMuxer (core, muxer) {
    const ch = muxer.getLastChannel({ protocol: 'hypercore', id: core.discoveryKey })
    if (ch) return ch.fullyOpened()

    const cha = muxer.getLastChannel({ protocol: 'hypercore/alpha', id: core.discoveryKey })
    if (cha) return cha.fullyOpened()

    return Promise.resolve(false)
  }

  _hasPeer (core, peer) {
    return this._hasMuxer(core, peer.protomux)
  }
}
{
  "name": "core-coupler",
  "version": "2.0.0",
  "description": "Couple the peers of cores",
  "main": "index.js",
  "scripts": {
    "test": "standard"
  },
  "dependencies": {
    "safety-catch": "^1.0.2"
  },
  "devDependencies": {
    "corestore": "^6.18.2",
    "standard": "^17.1.0"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/holepunchto/core-coupler.git"
  },
  "author": "Holepunch Inc",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/core-coupler/issues"
  },
  "homepage": "https://github.com/holepunchto/core-coupler"
}
const b4a = require('b4a')
const Hypercore = require('hypercore')
const ReadyResource = require('ready-resource')
const sodium = require('sodium-universal')
const crypto = require('hypercore-crypto')
const ID = require('hypercore-id-encoding')
const { isAndroid } = require('which-runtime')
const { STORAGE_EMPTY, ASSERTION } = require('hypercore-errors')

const auditStore = require('./lib/audit.js')
const GroupNotifyHandle = require('./lib/notify.js')

const [NS] = crypto.namespace('corestore', 1)
const DEFAULT_NAMESPACE = b4a.alloc(32) // This is meant to be 32 0-bytes

class StreamTracker {
  constructor() {
    this.records = []
  }

  add(stream, isExternal) {
    const record = { index: 0, stream, isExternal }
    record.index = this.records.push(record) - 1
    return record
  }

  remove(record) {
    const popped = this.records.pop()
    if (popped === record) return
    this.records[(popped.index = record.index)] = popped
  }

  attachAll(core) {
    for (let i = 0; i < this.records.length; i++) {
      const record = this.records[i]
      const muxer = record.stream.noiseStream.userData
      if (!core.replicator.attached(muxer)) core.replicator.attachTo(muxer)
    }
  }

  destroy() {
    // reverse is safer cause we delete mb
    for (let i = this.records.length - 1; i >= 0; i--) {
      const record = this.records[i]
      if (!record.isExternal) record.stream.destroy()
    }
  }
}

class SessionTracker {
  constructor() {
    this.map = new Map()
  }

  get size() {
    return this.map.size
  }

  get(id) {
    const existing = this.map.get(id)
    if (existing !== undefined) return existing
    const fresh = []
    this.map.set(id, fresh)
    return fresh
  }

  gc(id) {
    this.map.delete(id)
  }

  list(id) {
    return id ? this.map.get(id) || [] : [...this]
  }

  *[Symbol.iterator]() {
    for (const sessions of this.map.values()) {
      yield* sessions[Symbol.iterator]()
    }
  }
}

class CoreTracker {
  constructor() {
    this.map = new Map()
    this.watching = []

    this._gcing = new Set()
    this._gcInterval = null
    this._gcCycleBound = this._gcCycle.bind(this)
  }

  get size() {
    return this.map.size
  }

  watch(store) {
    if (store.watchIndex !== -1) return
    store.watchIndex = this.watching.push(store) - 1
  }

  unwatch(store) {
    if (store.watchIndex === -1) return
    const head = this.watching.pop()
    if (head !== store) this.watching[(head.watchIndex = store.watchIndex)] = head
    store.watchIndex = -1
  }

  resume(id) {
    const core = this.map.get(id)

    if (!core) return null

    // signal back that we have a closing one stored
    if (core.closing) return core

    if (core.gc) {
      this._gcing.delete(core)
      if (this._gcing.size === 0) this._stopGC()
      core.gc = 0
    }

    return core
  }

  opened(id) {
    const core = this.map.get(id)
    return !!(core && core.opened && !core.closing)
  }

  get(id) {
    // we allow you do call this from the outside, so support normal buffers also
    if (b4a.isBuffer(id)) id = b4a.toString(id, 'hex')
    const core = this.map.get(id)
    if (!core || core.closing) return null
    return core
  }

  set(id, core) {
    this.map.set(id, core)
    if (this.watching.length > 0) this._emit(core)
  }

  _emit(core) {
    for (let i = this.watching.length - 1; i >= 0; i--) {
      const store = this.watching[i]
      for (const fn of store.watchers) fn(core)
    }
  }

  _gc(core) {
    const id = toHex(core.discoveryKey)
    if (this.map.get(id) === core) this.map.delete(id)
  }

  _gcCycle() {
    for (const core of this._gcing) {
      if (++core.gc < 4) continue
      const gc = this._gc.bind(this, core)
      core.close().then(gc, gc)
      this._gcing.delete(core)
    }

    if (this._gcing.size === 0) this._stopGC()
  }

  gc(core) {
    core.gc = 1 // first strike
    this._gcing.add(core)
    if (this._gcing.size === 1) this._startGC()
  }

  _stopGC() {
    clearInterval(this._gcInterval)
    this._gcInterval = null
  }

  _startGC() {
    if (this._gcInterval) return
    this._gcInterval = setInterval(this._gcCycleBound, 2000)
    if (this._gcInterval.unref) this._gcInterval.unref()
  }

  close() {
    this._stopGC()
    this._gcing.clear()

    const all = []
    for (const core of this.map.values()) {
      core.onidle = noop // no reentry
      all.push(core.close())
    }
    this.map.clear()

    return Promise.all(all)
  }

  *[Symbol.iterator]() {
    for (const core of this.map.values()) {
      if (!core.closing) yield core
    }
  }
}

class FindingPeers {
  constructor() {
    this.count = 0
    this.pending = []
    this.destroyed = false
  }

  add(core) {
    if (this.count === 0) return
    this.pending.push(core.findingPeers())
  }

  inc(sessions) {
    if (this.destroyed || ++this.count !== 1) return

    for (const core of sessions) {
      this.pending.push(core.findingPeers())
    }
  }

  dec(sessions) {
    if (this.destroyed || --this.count !== 0) return
    while (this.pending.length > 0) this.pending.pop()()
  }

  destroy() {
    this.destroyed = true
    while (this.pending.length > 0) this.pending.pop()()
  }
}

class Corestore extends ReadyResource {
  constructor(storage, opts = {}) {
    super()

    this.root = opts.root || null
    this.storage = this.root
      ? this.root.storage
      : Hypercore.defaultStorage(storage, {
          id: opts.id,
          allowBackup: opts.allowBackup,
          readOnly: opts.readOnly,
          wait: opts.wait
        })
    this.streamTracker = this.root ? this.root.streamTracker : new StreamTracker()
    this.cores = this.root ? this.root.cores : new CoreTracker()
    this.sessions = new SessionTracker()
    this.corestores = this.root ? this.root.corestores : new Set()
    this.readOnly = opts.writable === false || !!opts.readOnly
    this.globalCache = this.root ? this.root.globalCache : opts.globalCache || null
    this.primaryKey = this.root ? this.root.primaryKey : opts.primaryKey || null
    this.ns = opts.namespace || DEFAULT_NAMESPACE
    this.manifestVersion = opts.manifestVersion || 1
    this.shouldSuspend = isAndroid ? !!opts.suspend : opts.suspend !== false
    this.active = opts.active !== false

    this.watchers = null
    this.watchIndex = -1

    this._groupNotifiers = new Map() // group notifications
    this._findingPeers = null // here for legacy
    this._ongcBound = this._ongc.bind(this)
    this._onGroupActiveBound = this._onGroupActive.bind(this)

    if (opts.primaryKey && !this.root && !opts.unsafe) {
      throw ASSERTION(
        'Passing the primary key is unsafe unless you know what you are doing. Set unsafe: true to acknowledge that'
      )
    }

    this.ready().catch(noop)
  }

  watch(fn) {
    if (this.watchers === null) {
      this.watchers = new Set()
      this.cores.watch(this)
    }

    this.watchers.add(fn)
  }

  unwatch(fn) {
    if (this.watchers === null) return

    this.watchers.delete(fn)

    if (this.watchers.size === 0) {
      this.watchers = null
      this.cores.unwatch(this)
    }
  }

  _onGroupActive(topic) {
    this.emit('group-active', topic)

    const topicHex = b4a.toString(topic, 'hex')
    const handles = this._groupNotifiers.get(topicHex)
    if (!handles) return
    for (const handle of handles) handle.emit('update')
  }

  notifyGroup(topic) {
    const topicHex = b4a.toString(topic, 'hex')
    let handles = this._groupNotifiers.get(topicHex)

    if (!handles) {
      handles = []
      this._groupNotifiers.set(topicHex, handles)
    }

    const handle = new GroupNotifyHandle(this, topic)
    const length = handles.push(handle)
    handle.index = length - 1

    return handle
  }

  _removeGroupNotify(handle) {
    if (handle.index === -1) return

    const topicHex = b4a.toString(handle._topic, 'hex')
    const handles = this._groupNotifiers.get(topicHex)
    if (!handles) return

    const popped = handles.pop()
    if (popped !== handle) handles[(popped.index = handle.index)] = popped
    if (handles.length === 0) this._groupNotifiers.delete(topicHex)
    handle.index = -1
  }

  findingPeers() {
    if (this._findingPeers === null) this._findingPeers = new FindingPeers()
    this._findingPeers.inc(this.sessions)
    let done = false
    return () => {
      if (done || !this._findingPeers) return
      done = true
      this._findingPeers.dec(this.sessions)
    }
  }

  audit(opts = {}) {
    return auditStore(this, opts)
  }

  async suspend({ log = noop } = {}) {
    await log('Flushing db...')
    // If readOnly we don't need to flush
    if (!this.storage.readOnly) {
      await this.storage.db.flush()
      await log('Flusing db completed')
    }
    if (!this.shouldSuspend) return
    await log('Suspending db...')
    await this.storage.suspend({ log })
    await log('Suspending db completed')
  }

  resume() {
    return this.storage.resume()
  }

  session(opts) {
    this._maybeClosed()
    const root = this.root || this
    return new Corestore(null, {
      manifestVersion: this.manifestVersion,
      ...opts,
      root
    })
  }

  namespace(name, opts) {
    return this.session({
      ...opts,
      namespace: generateNamespace(this.ns, name)
    })
  }

  list(namespace) {
    return this.storage.createDiscoveryKeyStream(namespace)
  }

  getAuth(discoveryKey) {
    return this.storage.getAuth(discoveryKey)
  }

  _ongc(session) {
    if (session.sessions.length === 0) this.sessions.gc(session.id)
  }

  async _getOrSetSeed() {
    const seed = await this.storage.getSeed()
    if (seed !== null) return seed
    return await this.storage.setSeed(this.primaryKey || crypto.randomBytes(32))
  }

  async _open() {
    if (this.root !== null) {
      this.corestores.add(this)
      if (this.root.opened === false) await this.root.ready()
      this.primaryKey = this.root.primaryKey
      return
    }

    const primaryKey = await this._getOrSetSeed()

    if (this.primaryKey === null) {
      this.primaryKey = primaryKey
      return
    }

    if (!b4a.equals(primaryKey, this.primaryKey)) {
      throw new Error('Another corestore is stored here')
    }
  }

  async _close() {
    const closing = []
    const hanging = [...this.sessions]
    for (const sess of hanging) closing.push(sess.close())

    if (this.watchers !== null) {
      this.cores.unwatch(this)
      this.watchers = null
    }

    if (this._findingPeers !== null) {
      this._findingPeers.destroy()
      this._findingPeers = null
    }

    if (this.root !== null) {
      await Promise.all(closing)
      this.corestores.delete(this)
      return
    }

    for (const store of this.corestores) {
      closing.push(store.close())
    }

    await Promise.all(closing)

    await this.cores.close()
    await this.storage.close()
  }

  async _attachMaybe(muxer, discoveryKey) {
    if (this.opened === false) await this.ready()
    if (
      !this.cores.opened(toHex(discoveryKey)) &&
      !(await this.storage.hasCore(discoveryKey, { ifMigrated: true }))
    ) {
      return
    }
    if (this.closing) return

    const core = this._openCore(discoveryKey, { createIfMissing: false })

    if (!core) return
    if (!core.opened) await core.ready()

    if (!core.replicator.attached(muxer)) {
      core.replicator.attachTo(muxer)
    }

    core.checkIfIdle()
  }

  _shouldReplicate(core, muxer) {
    return (
      core.replicator.downloading && !core.replicator.attached(muxer) && core.opened && this.active
    )
  }

  replicate(isInitiator, opts) {
    this._maybeClosed()

    const isExternal = isStream(isInitiator)
    const stream = Hypercore.createProtocolStream(isInitiator, {
      ...opts,
      ondiscoverykey: (discoveryKey) => {
        if (this.closing) return
        const muxer = stream.noiseStream.userData
        return this._attachMaybe(muxer, discoveryKey)
      }
    })

    if (this.cores.size > 0) {
      const muxer = stream.noiseStream.userData
      const uncork = muxer.uncork.bind(muxer)
      muxer.cork()

      for (const core of this.cores) {
        if (!this._shouldReplicate(core, muxer)) {
          continue
        }
        core.replicator.attachTo(muxer)
      }

      stream.noiseStream.opened.then(uncork)
    }

    const record = this.streamTracker.add(stream, isExternal)
    stream.once('close', () => this.streamTracker.remove(record))
    return stream
  }

  _maybeClosed() {
    if (this.closing || (this.root !== null && this.root.closing)) {
      throw new Error('Corestore is closed')
    }
  }

  async staticify(core, opts) {
    if (!this.opened) await this.ready()
    if (!core.opened) await core.ready()

    const rx = core.state.storage.read()

    const headPromise = rx.getHead()
    const authPromise = rx.getAuth()

    rx.tryFlush()

    const [head, auth] = await Promise.all([headPromise, authPromise])
    if (!head || head.length === 0) throw new Error('Core must have data')

    const prologue = {
      length: head.length,
      hash: head.rootHash
    }

    const manifest = {
      version: 1,
      hash: auth.manifest.hash,
      quorum: 0,
      signers: [],
      prologue
    }

    const c = {
      key: null,
      discoveryKey: null,
      manifest,
      core: core.state.storage.core
    }

    c.key = Hypercore.key(c.manifest)
    c.discoveryKey = Hypercore.discoveryKey(c.key)

    await this.storage.createCore(c)

    const staticCore = this.get({ ...opts, key: c.key })
    await staticCore.ready()
    return staticCore
  }

  get(opts) {
    this._maybeClosed()

    if (b4a.isBuffer(opts) || typeof opts === 'string') opts = { key: opts }
    if (!opts) opts = {}

    const conf = {
      preload: null,
      sessions: null,
      ongc: null,
      core: null,
      active: opts.active !== false,
      encryption: opts.encryption || null,
      encryptionKey: opts.encryptionKey || null, // back compat, should remove
      isBlockKey: !!opts.isBlockKey, // back compat, should remove
      valueEncoding: opts.valueEncoding || null,
      exclusive: !!opts.exclusive,
      manifest: opts.manifest || null,
      keyPair: opts.keyPair || null,
      onwait: opts.onwait || null,
      pushOnly: opts.pushOnly === true,
      wait: opts.wait !== false,
      timeout: opts.timeout || 0,
      draft: !!opts.draft,
      writable: opts.writable === undefined && this.readOnly ? false : opts.writable
    }

    // name requires us to rt to storage + ready, so needs preload
    // same goes if user has defined async preload obvs
    if (opts.name || opts.preload) {
      conf.preload = this._preload(opts)
      return this._makeSession(conf)
    }

    if (opts.discoveryKey && !opts.key && !opts.manifest) {
      conf.preload = this._preloadCheckIfExists(opts)
      return this._makeSession(conf)
    }

    // if not not we can sync create it, which just is easier for the
    // upstream user in terms of guarantees (key is there etc etc)
    const core = this._openCore(null, opts)

    conf.core = core
    conf.sessions = this.sessions.get(core.id)
    conf.ongc = this._ongcBound

    return this._makeSession(conf)
  }

  _makeSession(conf) {
    const session = new Hypercore(null, null, conf)

    if (this._findingPeers !== null) this._findingPeers.add(session)
    return session
  }

  async createKeyPair(name, ns = this.ns) {
    if (this.opened === false) await this.ready()
    return createKeyPair(this.primaryKey, ns, name)
  }

  async _preloadCheckIfExists(opts) {
    const has = await this.storage.hasCore(opts.discoveryKey)
    if (!has) throw STORAGE_EMPTY('No Hypercore is stored here')
    return this._preload(opts)
  }

  async _preload(opts) {
    if (opts.preload) opts = { ...opts, ...(await opts.preload) }
    if (this.opened === false) await this.ready()

    const discoveryKey = opts.name
      ? await this.storage.getAlias({ name: opts.name, namespace: this.ns })
      : null
    this._maybeClosed()

    const core = this._openCore(discoveryKey, opts)

    return {
      core,
      sessions: this.sessions.get(core.id),
      ongc: this._ongcBound,
      manifest: opts.manifest || null,
      encryption: opts.encryption || null,
      encryptionKey: opts.encryptionKey || null, // back compat, should remove
      isBlockKey: !!opts.isBlockKey // back compat, should remove
    }
  }

  _auth(discoveryKey, opts) {
    const result = {
      keyPair: null,
      key: null,
      discoveryKey,
      manifest: null,
      group: null
    }

    if (opts.name) {
      result.keyPair = createKeyPair(this.primaryKey, this.ns, opts.name)
    } else if (opts.keyPair) {
      result.keyPair = opts.keyPair
    }

    if (opts.manifest) {
      result.manifest = opts.manifest
    } else if (result.keyPair && !result.discoveryKey) {
      result.manifest = {
        version: this.manifestVersion,
        signers: [{ publicKey: result.keyPair.publicKey }]
      }
    }

    if (opts.group) {
      result.group = opts.group
    }

    if (opts.key) result.key = ID.decode(opts.key)
    else if (result.manifest) result.key = Hypercore.key(result.manifest)

    if (result.discoveryKey) return result

    if (opts.discoveryKey) result.discoveryKey = ID.decode(opts.discoveryKey)
    else if (result.key) result.discoveryKey = crypto.discoveryKey(result.key)
    else throw new Error('Could not derive discovery from input')

    return result
  }

  _openCore(discoveryKey, opts) {
    const auth = this._auth(discoveryKey, opts)

    const id = toHex(auth.discoveryKey)
    const existing = this.cores.resume(id, auth.group)
    if (existing && !existing.closing) return existing

    const core = Hypercore.createCore(this.storage, {
      preopen: existing && existing.opened ? existing.closing : null, // always wait for the prev one to close first in any case...
      eagerUpgrade: true,
      notDownloadingLinger: opts.notDownloadingLinger,
      allowFork: opts.allowFork !== false,
      allowPush: !!opts.allowPush,
      inflightRange: opts.inflightRange,
      compat: false, // no compat for now :)
      force: opts.force,
      createIfMissing: opts.createIfMissing,
      discoveryKey: auth.discoveryKey,
      overwrite: opts.overwrite,
      key: auth.key,
      keyPair: auth.keyPair,
      legacy: opts.legacy,
      manifest: auth.manifest,
      group: auth.group,
      globalCache: opts.globalCache || this.globalCache || null,
      alias: opts.name ? { name: opts.name, namespace: this.ns } : null
    })

    core.onidle = () => {
      this.cores.gc(core)
    }

    core.ongroupupdate = this._onGroupActiveBound

    core.replicator.ondownloading = () => {
      if (this.active) this.streamTracker.attachAll(core)
    }

    this.cores.set(id, core)
    return core
  }
}

module.exports = Corestore

function isStream(s) {
  return typeof s === 'object' && s && typeof s.pipe === 'function'
}

function generateNamespace(namespace, name) {
  if (!b4a.isBuffer(name)) name = b4a.from(name)
  const out = b4a.allocUnsafeSlow(32)
  sodium.crypto_generichash_batch(out, [namespace, name])
  return out
}

function deriveSeed(primaryKey, namespace, name) {
  if (!b4a.isBuffer(name)) name = b4a.from(name)
  const out = b4a.alloc(32)
  sodium.crypto_generichash_batch(out, [NS, namespace, name], primaryKey)
  return out
}

function createKeyPair(primaryKey, namespace, name) {
  const seed = deriveSeed(primaryKey, namespace, name)
  const buf = b4a.alloc(sodium.crypto_sign_PUBLICKEYBYTES + sodium.crypto_sign_SECRETKEYBYTES)
  const keyPair = {
    publicKey: buf.subarray(0, sodium.crypto_sign_PUBLICKEYBYTES),
    secretKey: buf.subarray(sodium.crypto_sign_PUBLICKEYBYTES)
  }
  sodium.crypto_sign_seed_keypair(keyPair.publicKey, keyPair.secretKey, seed)
  return keyPair
}

function noop() {}

function toHex(discoveryKey) {
  return b4a.toString(discoveryKey, 'hex')
}
module.exports = async function* audit(store, { dryRun = false } = {}) {
  for await (const { discoveryKey, core } of store.storage.createCoreStream()) {
    if (core.version < 1) continue // not migrated, ignore

    const c = store.get({ discoveryKey, active: false })
    await c.ready()

    yield { discoveryKey, key: c.key, audit: await c.core.audit({ dryRun }) }

    try {
      await c.close()
    } catch {
      // ignore if failed, we are auditing...
    }
  }
}
const { EventEmitter } = require('events')
const { Readable } = require('streamx')

class GroupNotifyHandle extends EventEmitter {
  constructor(store, topic) {
    super()
    this._store = store
    this._topic = topic
    this.index = -1
  }

  updates(opts) {
    return Readable.deferred(async () => {
      const groupId = await this._store.storage.getGroup(this._topic).catch(noop)
      if (groupId === null) return null
      return this._store.storage.createGroupUpdateStream(groupId, opts)
    })
  }

  destroy() {
    this._store._removeGroupNotify(this)
  }
}

module.exports = GroupNotifyHandle

function noop() {}
{
  "name": "corestore",
  "version": "7.10.1",
  "description": "A Hypercore factory that simplifies managing collections of cores.",
  "main": "index.js",
  "files": [
    "index.js",
    "lib/*"
  ],
  "imports": {
    "events": {
      "bare": "bare-events",
      "default": "events"
    }
  },
  "dependencies": {
    "b4a": "^1.6.7",
    "bare-events": "^2.8.3",
    "hypercore": "^11.32.0",
    "hypercore-crypto": "^3.4.2",
    "hypercore-errors": "^1.4.0",
    "hypercore-id-encoding": "^1.3.0",
    "ready-resource": "^1.1.1",
    "sodium-universal": "^5.0.1",
    "streamx": "^2.26.0",
    "which-runtime": "^1.2.1"
  },
  "devDependencies": {
    "brittle": "^3.7.0",
    "lunte": "^1.3.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^2.0.0",
    "rache": "^1.0.0"
  },
  "scripts": {
    "format": "prettier --write .",
    "test": "npm run test:node && npm run test:bare",
    "test:bare": "brittle-bare --coverage test/all.js",
    "lint": "prettier --check . && lunte",
    "test:node": "brittle-node --coverage test/all.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/holepunchto/corestore.git"
  },
  "author": "Holepunch Inc",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/holepunchto/corestore/issues"
  },
  "homepage": "https://github.com/holepunchto/corestore"
}
module.exports = function debounce (worker, context = null) {
  debounced.running = null
  return debounced

  async function debounced () {
    if (debounced.running !== null) {
      try {
        await debounced.running
      } catch (_) {
        // ignore - do not fail on old errors
      }
    }

    // another "thread" beat us to it, just piggy pack on that one
    if (debounced.running !== null) return debounced.running

    debounced.running = worker.call(context)

    try {
      return await debounced.running
    } finally {
      debounced.running = null
    }
  }
}
{
  "name": "debounceify",
  "version": "1.1.0",
  "description": "Tiny async debouncer",
  "main": "index.js",
  "dependencies": {},
  "devDependencies": {
    "standard": "^16.0.3",
    "tape": "^5.1.1"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/mafintosh/debounceify.git"
  },
  "scripts": {
    "test": "standard && tape test.js"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/mafintosh/debounceify/issues"
  },
  "homepage": "https://github.com/mafintosh/debounceify"
}
const fs = require('fs')
const path = require('path')
const fsx = require('fs-native-extensions')
const b4a = require('b4a')
const ReadyResource = require('ready-resource')
const FDLock = require('fd-lock')

const PLATFORM = global.Bare ? global.Bare.platform : global.process.platform
const IS_WIN = PLATFORM === 'win32'
const IS_LINUX = PLATFORM === 'linux'
const MODIFIED_SLACK = 5000
const EMPTY = b4a.alloc(0)
const ATTR = IS_LINUX ? 'user.device-file' : 'device-file'

const nl = IS_WIN ? '\r\n' : '\n'

class DeviceFile extends ReadyResource {
  constructor(filename, { create = true, wait = false, lock = wait, data = {} } = {}) {
    super()

    this.filename = filename
    this.data = data
    this.lock = null

    this._create = create
    this._wait = wait
    this._lock = lock
  }

  async _open() {
    if (await verifyDeviceFile(this)) return
    if (!this._create) throwDeviceFileError('No device file present', false)
    await writeDeviceFile(this)
  }

  static async validate(filename, data) {
    const file = new DeviceFile(filename, { create: false, wait: false, data })
    try {
      await verifyDeviceFile(file)
    } catch (err) {
      if (err.fatal && err.code === 'DEVICE_FILE') return false
      throw err
    }
    return true
  }

  transfer() {
    return this.lock ? this.lock.transfer() : -1
  }

  async _close() {
    if (this.lock) await this.lock.close()
  }

  async suspend() {
    if (!this.opened) await this.ready()
    if (this.lock) await this.lock.suspend()
  }

  async resume() {
    if (!this.opened) await this.ready()
    if (this.lock) await this.lock.resume()
  }
}

module.exports = DeviceFile

async function writeDeviceFile(device) {
  let s = ''

  for (const [key, value] of Object.entries(device.data)) {
    if (value === null) continue
    s += key + '=' + value + nl
  }

  await fs.promises.mkdir(path.dirname(device.filename), { recursive: true })

  const fd = await open(device.filename, 'w')

  device.lock = device._lock ? new FDLock(fd, { wait: device._wait }) : null

  if (device.lock) {
    try {
      await device.lock.ready()
    } catch (err) {
      await device.lock.close()
      throw err
    }
  }

  const st = await fstat(fd)

  const created = Date.now()

  s += 'device/platform=' + PLATFORM + nl
  s += 'device/inode=' + st.ino + nl
  s += 'device/created=' + created + nl

  if (await setAttr(fd, ATTR, b4a.from('original'))) {
    s += 'device/attribute=original' + nl
  }

  await write(fd, b4a.from(s))

  if (!device.lock) await close(fd)
}

async function verifyDeviceFile(device) {
  let fd = 0

  try {
    fd = await open(device.filename, 'r+')
  } catch (e) {
    fd = 0
  }

  if (fd === 0) return false

  device.lock = device._lock ? new FDLock(fd, { wait: device._wait }) : null

  if (device.lock) {
    try {
      await device.lock.ready()
    } catch (err) {
      await device.lock.close()
      throw err
    }
  }

  const buf = await read(fd)
  const result = {}

  const s = b4a.toString(buf).trim().split('\n')

  let inode = 0
  let created = 0
  let attr = ''
  let platform = ''

  for (const ln of s) {
    const i = ln.indexOf('=')
    if (i === -1) continue

    const k = ln.slice(0, i).trim()
    const v = ln.slice(i + 1).trim()

    switch (k) {
      case 'device/platform':
        platform = v
        break
      case 'device/inode':
        inode = Number(v)
        break
      case 'device/created':
        created = Number(v)
        break
      case 'device/attribute':
        attr = v
        break
      default:
        result[k] = v
        break
    }
  }

  for (const [k, v] of Object.entries(device.data)) {
    if (v === null) continue
    if (result[k] === undefined) continue // allow upserts
    if (result[k] !== '' + v) {
      await teardown()
      throwDeviceFileError(`Invalid device file, ${k} has changed. Was ${result[k]}, is ${v}`, true)
    }
  }

  const st = await fstat(fd)
  const at = await getAttr(fd, ATTR)

  const sameAttr = b4a.toString(at || EMPTY) === attr
  const modified = Math.max(st.mtime.getTime(), st.birthtime.getTime())

  if (platform && platform !== PLATFORM) {
    await teardown()
    throwDeviceFileError('Invalid device file, was made on different platform', true)
  }

  if (!sameAttr) {
    await teardown()
    throwDeviceFileError('Invalid device file, was moved unsafely', true)
  }

  if (st.ino !== inode || (created && Math.abs(modified - created) >= MODIFIED_SLACK)) {
    await teardown()
    throwDeviceFileError('Invalid device file, was modified', true)
  }

  if (!device.lock) await close(fd)

  device.data = result
  return true

  async function teardown() {
    if (device.lock) await device.lock.close()
    else await close(fd)
  }
}

async function getAttr(fd, name) {
  try {
    return await fsx.getAttr(fd, name)
  } catch {
    return null
  }
}

async function setAttr(fd, name, value) {
  try {
    await fsx.setAttr(fd, name, value)
    return true
  } catch {
    return false
  }
}

function fstat(fd) {
  return new Promise((resolve, reject) => {
    fs.fstat(fd, (err, st) => {
      if (err) reject(err)
      resolve(st)
    })
  })
}

function close(fd) {
  return new Promise((resolve, reject) => {
    fs.close(fd, (err, st) => {
      if (err) reject(err)
      resolve(st)
    })
  })
}

function write(fd, buf) {
  return new Promise((resolve, reject) => {
    let offset = 0

    onwrite(null, 0)

    function onwrite(err, wrote) {
      if (err) return reject(err)
      if (offset === buf.byteLength) return resolve()
      offset += wrote
      fs.write(fd, buf, offset, buf.byteLength - offset, offset, onwrite)
    }
  })
}

function read(fd) {
  const buf = b4a.allocUnsafe(4096)

  return new Promise((resolve, reject) => {
    let offset = 0

    fs.read(fd, buf, 0, buf.byteLength, 0, onread)

    function onread(err, read) {
      if (err) return reject(err)
      if (read === 0) return resolve(buf.subarray(0, offset))
      offset += read
      fs.read(fd, buf, offset, buf.byteLength - offset, offset, onread)
    }
  })
}

function open(filename, flags) {
  return new Promise((resolve, reject) => {
    fs.open(filename, flags, (err, fd) => {
      if (err) reject(err)
      resolve(fd)
    })
  })
}

function throwDeviceFileError(message, fatal) {
  const err = new Error(message)
  err.code = 'DEVICE_FILE'
  err.fatal = fatal
  throw err
}
{
  "name": "device-file",
  "version": "2.3.1",
  "description": "Device only file",
  "main": "index.js",
  "files": [
    "index.js"
  ],
  "dependencies": {
    "b4a": "^1.6.7",
    "bare-fs": "^4.0.1",
    "bare-path": "^3.0.0",
    "fd-lock": "^2.1.0",
    "fs-native-extensions": "^1.4.0",
    "ready-resource": "^1.2.0"
  },
  "devDependencies": {
    "brittle": "^3.13.1",
    "lunte": "^1.3.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^2.0.0"
  },
  "scripts": {
    "format": "prettier --write .",
    "lint": "prettier --check . && lunte",
    "test": "npm run test:node && npm run test:bare",
    "test:node": "brittle-node --coverage test.js",
    "test:bare": "brittle-bare --coverage test.js"
  },
  "imports": {
    "fs": {
      "bare": "bare-fs",
      "default": "fs"
    },
    "path": {
      "bare": "bare-path",
      "default": "path"
    }
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/holepunchto/device-file.git"
  },
  "author": "Holepunch Inc",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/device-file/issues"
  },
  "homepage": "https://github.com/holepunchto/device-file"
}
const { EventEmitter } = require('events')
const Table = require('kademlia-routing-table')
const TOS = require('time-ordered-set')
const UDX = require('udx-native')
const sodium = require('sodium-universal')
const c = require('compact-encoding')
const NatSampler = require('nat-sampler')
const b4a = require('b4a')
const NetworkHealth = require('./lib/health')
const IO = require('./lib/io')
const Query = require('./lib/query')
const Session = require('./lib/session')
const peer = require('./lib/peer')
const { UNKNOWN_COMMAND, INVALID_TOKEN } = require('./lib/errors')
const { PING, PING_NAT, FIND_NODE, DOWN_HINT, DELAYED_PING } = require('./lib/commands')

const TMP = b4a.allocUnsafe(32)
const TICK_INTERVAL = 5000
const SLEEPING_INTERVAL = 3 * TICK_INTERVAL
const STABLE_TICKS = 240 // if nothing major bad happens in ~20mins we can consider this node stable (if nat is friendly)
const MORE_STABLE_TICKS = 3 * STABLE_TICKS
const REFRESH_TICKS = 60 // refresh every ~5min when idle
const RECENT_NODE = 12 // we've heard from a node less than 1min ago
const OLD_NODE = 360 // if an node has been around more than 30 min we consider it old

const DEFAULTS = {
  concurrency: 10,
  maxWindow: IO.DEFAULT_MAX_WINDOW,
  maxPingDelay: 10_000
}

class DHT extends EventEmitter {
  constructor(opts = {}) {
    super()

    this.bootstrapNodes = opts.bootstrap === false ? [] : (opts.bootstrap || []).map(parseNode)
    this.table = new Table(randomBytes(32))
    this.nodes = new TOS()
    this.udx = opts.udx || new UDX()
    this.io = new IO(this.table, this.udx, {
      ...opts,
      onrequest: this._onrequest.bind(this),
      onresponse: this._onresponse.bind(this),
      ontimeout: this._ontimeout.bind(this)
    })
    this.health = new NetworkHealth(this)

    this.concurrency = opts.concurrency || DEFAULTS.concurrency
    this.maxPingDelay = opts.maxPingDelay || DEFAULTS.maxPingDelay
    this.bootstrapped = false
    this.ephemeral = true
    this.firewalled = this.io.firewalled
    this.adaptive = typeof opts.ephemeral !== 'boolean' && opts.adaptive !== false
    this.destroyed = false
    this.suspended = false
    this.online = true
    this.degraded = false
    this.stats = {
      queries: { active: 0, total: 0 },
      requests: this.io.stats.requests,
      commands: {
        ping: this.io.stats.commands[PING],
        pingNat: this.io.stats.commands[PING_NAT],
        findNode: this.io.stats.commands[FIND_NODE],
        downHint: this.io.stats.commands[DOWN_HINT]
      }
    }

    this._nat = new NatSampler()
    this._quickFirewall = opts.quickFirewall !== false
    this._forcePersistent = opts.ephemeral === false
    this._repinging = 0
    this._checks = 0
    this._tick = randomOffset(100) // make sure to random offset all the network ticks
    this._refreshTicks = randomOffset(REFRESH_TICKS)
    this._stableTicks = this.adaptive ? STABLE_TICKS : 0
    this._tickInterval = setInterval(this._ontick.bind(this), TICK_INTERVAL)
    this._lastTick = Date.now()
    this._lastHost = null
    this._filterNode = opts.filterNode || opts.addNode || null // opts.addNode is deprecating, use opts.filterNode instead
    this._onrow = (row) => row.on('full', (node) => this._onfullrow(node, row))
    this._nonePersistentSamples = []
    this._bootstrapping = this._bootstrap()
    this._bootstrapping.catch(noop)
    this._sendDownHints = opts.sendDownHints !== false
    this._downHintsRateLimit =
      opts.downHintsRateLimit !== undefined ? opts.downHintsRateLimit : 10 * 5
    this._downHintsSentPerTick = 0
    this._pendingTimers = new Set()

    this.table.on('row', this._onrow)

    this.io.networkInterfaces.on('change', (interfaces) => this._onnetworkchange(interfaces))

    if (opts.nodes) {
      for (let i = opts.nodes.length - 1; i >= 0; i--) {
        this.addNode(opts.nodes[i])
      }
    }
  }

  static DEFAULTS = DEFAULTS

  static bootstrapper(port, host, opts) {
    if (!port) throw new Error('Port is required')
    if (!host) throw new Error('Host is required')
    if (host === '0.0.0.0' || host === '::') throw new Error('Invalid host')
    if (!UDX.isIPv4(host)) throw new Error('Host must be a IPv4 address')

    const dht = new this({
      port,
      ephemeral: false,
      firewalled: false,
      anyPort: false,
      bootstrap: [],
      ...opts
    })
    dht._nat.add(host, port)
    return dht
  }

  get id() {
    return this.ephemeral ? null : this.table.id
  }

  get host() {
    return this._nat.host
  }

  get port() {
    return this._nat.port
  }

  get randomized() {
    return this._nat.host !== null && this._nat.port === 0
  }

  get socket() {
    return this.firewalled ? this.io.clientSocket : this.io.serverSocket
  }

  get config() {
    return {
      concurrency: this.concurrency,
      maxWindow: this.io.congestion._maxWindow,
      randomPunchInterval: this._randomPunchInterval,
      connectionKeepAlive: this.connectionKeepAlive,
      sendDownHints: this._sendDownHints,
      downHintsRateLimit: this._downHintsRateLimit
    }
  }

  onmessage(socket, buf, rinfo) {
    if (buf.byteLength > 1) this.io.onmessage(socket, buf, rinfo)
  }

  bind() {
    return this.io.bind()
  }

  async suspend({ log = noop } = {}) {
    log('Suspending waiting for io bind...')
    await this.io.bind()
    log('Done, continuing')
    if (this.suspended || this.destroyed) return
    this.suspended = true
    clearInterval(this._tickInterval)
    log('Done, suspending io')
    await this.io.suspend({ log })
    log('Done, dht suspended')
    this.emit('suspend')
  }

  async resume({ log = noop } = {}) {
    if (!this.suspended || this.destroyed) return
    this.suspended = false
    this._tickInterval = setInterval(this._ontick.bind(this), TICK_INTERVAL)
    this._onwakeup()
    log('Resuming io')
    await this.io.resume()
    log('Done, dht resumed')
    this.io.networkInterfaces.on('change', (interfaces) => this._onnetworkchange(interfaces))
    this.refresh()
    this.emit('resume')
  }

  address() {
    const socket = this.socket
    return socket ? socket.address() : null
  }

  localAddress() {
    if (!this.io.serverSocket) return null

    return {
      host: localIP(this.udx),
      port: this.io.serverSocket.address().port
    }
  }

  remoteAddress() {
    if (!this.host) return null
    if (!this.port) return null
    if (this.firewalled) return null
    if (!this.io.serverSocket) return null

    const port = this.io.serverSocket.address().port
    if (port !== this.port) return null

    return {
      host: this.host,
      port
    }
  }

  addNode({ host, port }) {
    this._addNode({
      id: peer.id(host, port),
      port,
      host,
      token: null,
      to: null,
      sampled: 0,
      added: this._tick,
      pinged: 0,
      seen: 0,
      downHints: 0,
      prev: null,
      next: null
    })
  }

  toArray(opts) {
    const limit = opts && opts.limit
    if (limit === 0) return []
    return this.nodes.toArray({ limit, reverse: true }).map(({ host, port }) => ({ host, port }))
  }

  async fullyBootstrapped() {
    return this._bootstrapping
  }

  ready() {
    // Deprecating, use fullyBootstrapped instead (removed on next major)
    return this.fullyBootstrapped()
  }

  findNode(target, opts) {
    if (this.destroyed) throw new Error('Node destroyed')
    this._refreshTicks = REFRESH_TICKS
    return new Query(this, target, true, FIND_NODE, null, opts)
  }

  query({ target, command, value }, opts) {
    if (this.destroyed) throw new Error('Node destroyed')
    this._refreshTicks = REFRESH_TICKS
    return new Query(this, target, false, command, value || null, opts)
  }

  ping({ host, port }, opts) {
    let value = null

    if (opts && opts.size && opts.size > 0) value = b4a.alloc(opts.size)

    const req = this.io.createRequest(
      { id: null, host, port },
      null,
      true,
      PING,
      null,
      value,
      (opts && opts.session) || null,
      opts && opts.ttl
    )
    return this._requestToPromise(req, opts)
  }

  delayedPing({ host, port }, delayMs, opts) {
    if (delayMs > this.maxPingDelay) {
      throw new Error(`Delay exceeds max delay: ${this.maxPingDelay}ms`)
    }

    const value = b4a.allocUnsafe(4)
    c.uint32.encode({ start: 0, end: 4, buffer: value }, delayMs)

    const req = this.io.createRequest(
      { id: null, host, port },
      null,
      true,
      DELAYED_PING,
      null,
      value,
      (opts && opts.session) || null,
      opts && opts.ttl
    )
    // add 1 second to the timeout to account for network overhead
    req.timeout = delayMs + 1_000
    return this._requestToPromise(req, opts)
  }

  async rttStats() {
    const stats = {
      successes: 0,
      errors: 0,
      responses: {
        avgRtt: 0,
        errors: 0,
        avgCloserNodes: 0
      },
      closestReplies: {
        avgRtt: 0,
        errors: 0,
        avgCloserNodes: 0
      }
    }

    if (this.nodes.latest) {
      const q = this.findNode(this.nodes.latest.id)

      let responseCount = 0
      let closestCount = 0

      for await (const msg of q) {
        stats.responses.avgRtt += msg.rtt
        stats.responses.errors += msg.error
        stats.responses.avgCloserNodes += msg.closerNodes?.length || 0
        responseCount++
      }
      stats.responses.avgRtt /= responseCount
      stats.responses.avgCloserNodes /= responseCount

      for await (const msg of q.closestReplies) {
        stats.closestReplies.avgRtt += msg.rtt
        stats.closestReplies.errors += msg.error
        stats.closestReplies.avgCloserNodes += msg.closerNodes?.length || 0
        closestCount++
      }
      stats.closestReplies.avgRtt /= closestCount
      stats.closestReplies.avgCloserNodes /= closestCount

      stats.successes = q.successes
      stats.errors = q.errors
    }

    return stats
  }

  request({ token = null, command, target = null, value = null }, { host, port }, opts) {
    const req = this.io.createRequest(
      { id: null, host, port },
      token,
      false,
      command,
      target,
      value,
      (opts && opts.session) || null,
      opts && opts.ttl
    )
    return this._requestToPromise(req, opts)
  }

  session() {
    return new Session(this)
  }

  _requestToPromise(req, opts) {
    if (req === null) return Promise.reject(new Error('Node destroyed'))

    if (opts && opts.socket) req.socket = opts.socket
    if (opts && opts.retry === false) req.retries = 0

    return new Promise((resolve, reject) => {
      req.onresponse = resolve
      req.onerror = reject
      req.send()
    })
  }

  async _bootstrap() {
    const self = this

    await Promise.resolve() // wait a tick, so apis can be used from the outside
    await this.io.bind()

    this.emit('listening')

    // TODO: some papers describe more advanced ways of bootstrapping - we should prob look into that

    let first = this.firewalled && this._quickFirewall && !this._forcePersistent
    let testNat = false

    const onlyFirewall = !this._forcePersistent

    for (let i = 0; i < 2; i++) {
      await this._backgroundQuery(this.table.id).on('data', ondata).finished()

      if (this.bootstrapped || (!testNat && !this._forcePersistent)) break
      if (!(await this._updateNetworkState(onlyFirewall))) break
    }

    if (this.bootstrapped) return
    this.bootstrapped = true

    this.emit('ready')

    function ondata(data) {
      // Simple QUICK nat heuristic.
      // If we get ONE positive nat ping before the bootstrap query finishes
      // then we always to a nat test, no matter if we are adaptive...
      // This should be expanded in the future to try more than one node etc, not always hit the first etc
      // If this fails, then nbd, as the onstable hook will pick it up later.

      if (!first) return
      first = false

      const value = b4a.allocUnsafe(2)
      c.uint16.encode({ start: 0, end: 2, buffer: value }, self.io.serverSocket.address().port)

      self._request(
        data.from,
        false,
        true,
        PING_NAT,
        null,
        value,
        null,
        () => {
          testNat = true
        },
        noop
      )
    }
  }

  refresh() {
    const node = this.table.random()
    this._backgroundQuery(node ? node.id : this.table.id).on('error', noop)
  }

  async destroy() {
    const emitClose = !this.destroyed
    this.destroyed = true
    clearInterval(this._tickInterval)
    for (const timer of this._pendingTimers) {
      clearTimeout(timer)
    }
    await this.io.destroy()
    if (emitClose) this.emit('close')
  }

  _request(to, force, internal, command, target, value, session, onresponse, onerror) {
    if (internal && !this._sendDownHints && command === DOWN_HINT) return null
    const req = this.io.createRequest(to, null, internal, command, target, value, session)
    if (req === null) return null

    req.onresponse = onresponse
    req.onerror = onerror
    req.send(force)

    return req
  }

  _natAdd(host, port) {
    const prevHost = this._nat.host
    const prevPort = this._nat.port

    this._nat.add(host, port)

    if (prevHost === this._nat.host && prevPort === this._nat.port) return

    this.emit('nat-update', this._nat.host, this._nat.port)
  }

  // we don't check that this is a bootstrap node but we limit the sample size to very few nodes, so fine
  _sampleBootstrapMaybe(from, to) {
    if (this._nonePersistentSamples.length >= Math.max(1, this.bootstrapNodes.length)) return
    const id = from.host + ':' + from.port
    if (this._nonePersistentSamples.indexOf(id) > -1) return
    this._nonePersistentSamples.push(id)
    this._natAdd(to.host, to.port)
  }

  _addNodeFromNetwork(sample, from, to) {
    if (this._filterNode !== null && !this._filterNode(from)) {
      return
    }

    if (from.id === null) {
      this._sampleBootstrapMaybe(from, to)
      return
    }

    const oldNode = this.table.get(from.id)

    // refresh it, if we've seen this before
    if (oldNode) {
      if (sample && (oldNode.sampled === 0 || this._tick - oldNode.sampled >= OLD_NODE)) {
        oldNode.to = to
        oldNode.sampled = this._tick
        this._natAdd(to.host, to.port)
      }

      oldNode.pinged = oldNode.seen = this._tick
      this.nodes.add(oldNode)
      return
    }

    this._addNode({
      id: from.id,
      port: from.port,
      host: from.host,
      to,
      sampled: 0,
      added: this._tick,
      pinged: this._tick, // last time we interacted with them
      seen: this._tick, // last time we heard from them
      downHints: 0,
      prev: null,
      next: null
    })
  }

  _addNode(node) {
    if (this.nodes.has(node) || b4a.equals(node.id, this.table.id)) return

    node.added = node.pinged = node.seen = this._tick

    if (!this.table.add(node)) return
    this.nodes.add(node)

    if (node.to && node.sampled === 0) {
      node.sampled = this._tick
      this._natAdd(node.to.host, node.to.port)
    }

    this.emit('add-node', node)
  }

  _removeStaleNode(node, lastSeen) {
    if (node.seen <= lastSeen) this._removeNode(node)
  }

  _removeNode(node) {
    if (!this.nodes.has(node)) return

    this.table.remove(node.id)
    this.nodes.remove(node)

    this.emit('remove-node', node)
  }

  _onwakeup() {
    this._tick += 2 * OLD_NODE // bump the tick enough that everything appears old.
    this._tick += 8 - (this._tick & 7) - 2 // triggers a series of pings in two ticks
    this._stableTicks = MORE_STABLE_TICKS
    this._refreshTicks = 1 // triggers a refresh next tick (allow network time to wake up also)
    this._lastHost = null // clear network cache check
    this.health.reset()

    if (this.adaptive) {
      // TODO: re-enable this as soon as we find out why this is over triggering in some edge cases
      // this.firewalled = true
      // this.io.firewalled = true

      if (!this.ephemeral) {
        this.ephemeral = true
        this.io.ephemeral = true
        this.emit('ephemeral')
      }
    }

    this.emit('wakeup')
  }

  _onfullrow(newNode, row) {
    if (!this.bootstrapped || this._repinging >= 3) return

    let oldest = null
    for (const node of row.nodes) {
      if (node.pinged === this._tick) continue
      if (
        oldest === null ||
        oldest.pinged > node.pinged ||
        (oldest.pinged === node.pinged && oldest.added > node.added)
      ) {
        oldest = node
      }
    }

    if (oldest === null) return
    if (this._tick - oldest.pinged < RECENT_NODE && this._tick - oldest.added > OLD_NODE) return

    this._repingAndSwap(newNode, oldest)
  }

  _onnetworkchange(interfaces) {
    this.emit('network-change', interfaces)
    this.emit('network-update')
  }

  _repingAndSwap(newNode, oldNode) {
    const self = this
    const lastSeen = oldNode.seen

    oldNode.pinged = this._tick

    this._repinging++
    this._request(
      { id: null, host: oldNode.host, port: oldNode.port },
      false,
      true,
      PING,
      null,
      null,
      null,
      onsuccess,
      onswap
    )

    function onsuccess(m) {
      if (oldNode.seen <= lastSeen) return onswap()
      self._repinging--
    }

    function onswap(e) {
      self._repinging--
      self._removeNode(oldNode)
      self._addNode(newNode)
    }
  }

  _onrequest(req, external) {
    if (req.from.id !== null) {
      this._addNodeFromNetwork(!external, req.from, req.to)
    }

    if (req.internal) {
      switch (req.command) {
        // standard keep alive call
        case PING: {
          req.sendReply(0, null, false, false)
          return
        }
        case DELAYED_PING: {
          this._ondelayedping(req)
          return
        }
        // check if the other side can receive a message to their other socket
        case PING_NAT: {
          if (req.value === null || req.value.byteLength < 2) return
          const port = c.uint16.decode({ start: 0, end: 2, buffer: req.value })
          if (port === 0) return
          req.from.port = port
          req.sendReply(0, null, false, false)
          return
        }
        // empty dht reply back
        case FIND_NODE: {
          if (!req.target) return
          req.sendReply(0, null, false, true)
          return
        }
        // "this is node you sent me is down" - let's try to ping it
        case DOWN_HINT: {
          if (req.value === null || req.value.byteLength < 6) return
          if (this._checks < 10) {
            sodium.crypto_generichash(TMP, req.value.subarray(0, 6))
            const node = this.table.get(TMP)
            if (node && (node.pinged < this._tick || node.downHints === 0)) {
              node.downHints++
              this._check(node)
            }
          }
          req.sendReply(0, null, false, false)
          return
        }
      }

      req.sendReply(UNKNOWN_COMMAND, null, false, req.target !== null)
      return
    }

    // ask the user to handle it or reply back with a bad command
    if (this.onrequest(req) === false) {
      req.sendReply(UNKNOWN_COMMAND, null, false, req.target !== null)
    }
  }

  onrequest(req) {
    return this.emit('request', req)
  }

  _ondelayedping(req) {
    if (req.value === null || req.value.byteLength < 4) return
    const delayMs = c.uint32.decode({ start: 0, end: 4, buffer: req.value })
    if (delayMs > this.maxPingDelay) return
    const timer = setTimeout(() => {
      if (this.destroyed) return
      this._pendingTimers.delete(timer)
      req.sendReply(0, null, false, false)
    }, delayMs)
    this._pendingTimers.add(timer)
  }

  _onresponse(res, external) {
    this._addNodeFromNetwork(!external, res.from, res.to)
  }

  _ontimeout(req) {
    if (!req.to.id) return
    const node = this.table.get(req.to.id)
    if (node) this._removeNode(node)
  }

  _pingSome() {
    let cnt = this.io.inflight.length > 2 ? 3 : 5
    let oldest = this.nodes.oldest

    // tiny dht, pinged the bootstrap again
    if (!oldest) {
      this.refresh()
      return
    }

    // we've recently pinged the oldest one, so only trigger a couple of repings
    if (this._tick - oldest.pinged < RECENT_NODE) {
      cnt = 2
    }

    while (cnt--) {
      if (!oldest || this._tick === oldest.pinged) continue
      this._check(oldest)
      oldest = oldest.next
    }
  }

  _check(node) {
    node.pinged = this._tick

    const lastSeen = node.seen
    const onresponse = () => {
      this._checks--
      this._removeStaleNode(node, lastSeen)
    }
    const onerror = () => {
      this._checks--
      this._removeNode(node)
    }

    this._checks++
    this._request(
      { id: null, host: node.host, port: node.port },
      false,
      true,
      PING,
      null,
      null,
      null,
      onresponse,
      onerror
    )
  }

  _ontick() {
    const time = Date.now()

    if (time - this._lastTick > SLEEPING_INTERVAL && this.suspended === false) {
      this._onwakeup()
    } else {
      this._tick++
    }

    this._lastTick = time

    if (!this.bootstrapped || this.suspended) return

    if (this.adaptive && this.ephemeral && --this._stableTicks <= 0) {
      if (this._lastHost === this._nat.host) {
        // do not recheck the same network...
        this._stableTicks = MORE_STABLE_TICKS
      } else {
        this._updateNetworkState() // the promise returned here never fails so just ignore it
      }
    }

    if ((this._tick & 7) === 0) {
      this._pingSome()
    }

    if (
      ((this._tick & 63) === 0 && this.nodes.length < this.table.k) ||
      --this._refreshTicks <= 0
    ) {
      this.refresh()
    }

    this._downHintsSentPerTick = 0
    this.health.update()
  }

  async _updateNetworkState(onlyFirewall = false) {
    if (!this.ephemeral) return false
    if (onlyFirewall && !this.firewalled) return false

    const { host, port } = this._nat

    if (!onlyFirewall) {
      // remember what host we checked and reset the counter
      this._stableTicks = MORE_STABLE_TICKS
      this._lastHost = host
    }

    // check if we have a consistent host and port
    if (host === null || port === 0) {
      return false
    }

    const natSampler = this.firewalled ? new NatSampler() : this._nat

    // ask remote nodes to ping us on our server socket to see if we have the port open
    const firewalled = this.firewalled && (await this._checkIfFirewalled(natSampler))
    if (firewalled) return false

    this.firewalled = this.io.firewalled = false

    // incase it's called in parallel for some reason, or if our nat status somehow changed
    if (!this.ephemeral || host !== this._nat.host || port !== this._nat.port) return false
    // if the firewall probe returned a different host / non consistent port, bail as well
    if (natSampler.host !== host || natSampler.port === 0) return false

    const id = peer.id(natSampler.host, natSampler.port)

    if (!onlyFirewall) {
      this.ephemeral = this.io.ephemeral = false
    }

    if (natSampler !== this._nat) {
      const prevHost = this._nat.host
      const prevPort = this._nat.port

      this._nonePersistentSamples = []
      this._nat = natSampler

      if (prevHost !== this._nat.host || prevPort !== this._nat.port) {
        this.emit('nat-update', this._nat.host, this._nat.port)
      }
    }

    // TODO: we should make this a bit more defensive in terms of using more
    // resources to make sure that the new routing table contains as many alive nodes
    // as possible, vs blindly copying them over...

    // all good! copy over the old routing table to the new one
    if (!b4a.equals(this.table.id, id)) {
      const nodes = this.table.toArray()

      this.table = this.io.table = new Table(id)

      for (const node of nodes) {
        if (b4a.equals(node.id, id)) continue
        if (!this.table.add(node)) this.nodes.remove(node)
      }

      this.table.on('row', this._onrow)

      // we need to rebootstrap/refresh since we updated our id
      if (this.bootstrapped) this.refresh()
    }

    if (!this.ephemeral) {
      this.emit('persistent')
    }

    return true
  }

  async *_resolveBootstrapNodes() {
    for (let { host, port } of this.bootstrapNodes) {
      let doLookup = false

      if (host.indexOf('@') === -1) {
        doLookup = true
      } else {
        const [suggestedIP, fallbackHost] = host.split('@')
        try {
          await this.ping({ host: suggestedIP, port })
          host = suggestedIP
        } catch {
          host = fallbackHost
          doLookup = true
        }
      }

      if (doLookup) {
        try {
          host = UDX.isIPv4(host) ? host : (await this.udx.lookup(host, { family: 4 })).host
        } catch {
          continue
        }
      }

      yield {
        id: peer.id(host, port),
        host,
        port
      }
    }
  }

  async _addBootstrapNodes(nodes) {
    for await (const node of this._resolveBootstrapNodes()) {
      nodes.push(node)
    }
  }

  async _checkIfFirewalled(natSampler = new NatSampler()) {
    const nodes = []
    for (let node = this.nodes.latest; node && nodes.length < 5; node = node.prev) {
      nodes.push(node)
    }

    if (nodes.length < 5) await this._addBootstrapNodes(nodes)
    // if no nodes are available, including bootstrappers - bail
    if (nodes.length === 0) return true

    const hosts = new Set()
    const value = b4a.allocUnsafe(2)

    c.uint16.encode({ start: 0, end: 2, buffer: value }, this.io.serverSocket.address().port)

    // double check they actually came on the server socket...
    this.io.serverSocket.on('message', onmessage)

    const pongs = await requestAll(this, true, PING_NAT, value, nodes)

    let count = 0
    for (const res of pongs) {
      if (hosts.has(res.from.host)) {
        count++
        natSampler.add(res.to.host, res.to.port)
      }
    }

    this.io.serverSocket.removeListener('message', onmessage)

    // if we got no or very few replies, consider it a fluke
    if (count < (nodes.length >= 5 ? 3 : 1)) return true

    // check that the server socket has the same ip as the client socket
    if (natSampler.host === null || this._nat.host !== natSampler.host) return true

    // check that the local port of the server socket is the same as the remote port
    // TODO: we might want a flag to opt out of this heuristic for specific remapped port servers
    if (natSampler.port === 0 || natSampler.port !== this.io.serverSocket.address().port) {
      return true
    }

    return false

    function onmessage(_, { host }) {
      hosts.add(host)
    }
  }

  _backgroundQuery(target) {
    this._refreshTicks = REFRESH_TICKS

    const backgroundCon = Math.min(this.concurrency, Math.max(2, (this.concurrency / 8) | 0))
    const q = new Query(this, target, true, FIND_NODE, null, {
      concurrency: backgroundCon
    })

    q.on('data', () => {
      // yield to other traffic
      q.concurrency = this.io.inflight.length < 3 ? this.concurrency : backgroundCon
    })

    return q
  }

  // called by health
  _online() {
    if (this.online && !this.degraded) return
    this.online = true
    this.degraded = false
    this.emit('network-update')
  }

  // called by health
  _degraded() {
    if (this.degraded) return
    this.online = true
    this.degraded = true
    this.emit('network-update')
  }

  // called by health
  _offline() {
    if (!this.online) return
    this.online = false
    this.degraded = false
    this.emit('network-update')
  }
}

DHT.OK = 0
DHT.ERROR_UNKNOWN_COMMAND = UNKNOWN_COMMAND
DHT.ERROR_INVALID_TOKEN = INVALID_TOKEN

module.exports = DHT

function localIP(udx, family = 4) {
  let host = null

  for (const n of udx.networkInterfaces()) {
    if (n.family !== family || n.internal) continue

    // mac really likes en0, mb a better way but this shouldnt be bad anywhere so return now
    if (n.name === 'en0') return n.host

    // otherwise pick the first non internal host (let the loop continue in case we see en0)
    if (host === null) host = n.host
  }

  return host || (family === 4 ? '127.0.0.1' : '::1')
}

function parseNode(s) {
  if (typeof s === 'object') return s
  if (typeof s === 'number') return { host: '127.0.0.1', port: s }
  const [host, port] = s.split(':')
  if (!port) throw new Error('Bootstrap node format is host:port')

  return {
    host,
    port: Number(port)
  }
}

function randomBytes(n) {
  const b = b4a.alloc(n)
  sodium.randombytes_buf(b)
  return b
}

function randomOffset(n) {
  return n - ((Math.random() * 0.5 * n) | 0)
}

function requestAll(dht, internal, command, value, nodes) {
  let missing = nodes.length
  const replies = []

  return new Promise((resolve) => {
    for (const node of nodes) {
      const req = dht._request(
        node,
        false,
        internal,
        command,
        null,
        value,
        null,
        onsuccess,
        onerror
      )
      if (!req) return resolve(replies)
    }

    function onsuccess(res) {
      replies.push(res)
      if (--missing === 0) resolve(replies)
    }

    function onerror() {
      if (--missing === 0) resolve(replies)
    }
  })
}

function noop() {}
exports.PING = 0
exports.PING_NAT = 1
exports.FIND_NODE = 2
exports.DOWN_HINT = 3
exports.DELAYED_PING = 4
module.exports = class DHTError extends Error {
  constructor(msg, code, fn = DHTError) {
    super(`${code}: ${msg}`)
    this.code = code

    if (Error.captureStackTrace) {
      Error.captureStackTrace(this, fn)
    }
  }

  get name() {
    return 'DHTError'
  }

  static UNKNOWN_COMMAND = 1
  static INVALID_TOKEN = 2

  static REQUEST_TIMEOUT(msg = 'Request timed out') {
    return new DHTError(msg, 'REQUEST_TIMEOUT', DHTError.REQUEST_TIMEOUT)
  }

  static REQUEST_DESTROYED(msg = 'Request destroyed') {
    return new DHTError(msg, 'REQUEST_DESTROYED', DHTError.REQUEST_DESTROYED)
  }

  static IO_SUSPENDED(msg = 'I/O suspended') {
    return new DHTError(msg, 'IO_SUSPENDED', DHTError.IO_SUSPENDED)
  }
}
const MAX_HEALTH_WINDOW = 4
const OFFLINE_THRESHOLD = 2
const IDLE_THRESHOLD = 4
const DEGRADED_TIMEOUT_RATE_THRESHOLD = 0.5

module.exports = class NetworkHealth {
  constructor(dht) {
    this._dht = dht
    this._window = []
    this._head = -1
    this._offlineTicks = 0
    this._degradedTicks = 0
    this._healthyTicks = 0
    this.online = true
    this.degraded = false
  }

  get oldest() {
    return this._window[this._tail]
  }

  get previous() {
    return this._window[(this._head - 1 + MAX_HEALTH_WINDOW) % MAX_HEALTH_WINDOW]
  }

  get newest() {
    return this._window[this._head]
  }

  get responses() {
    if (!this.newest || !this.previous) return 0
    return this.newest.responses - this.previous.responses
  }

  get timeouts() {
    if (!this.newest || !this.previous) return 0
    return this.newest.timeouts - this.previous.timeouts
  }

  get timeoutsRate() {
    if (this.timeouts === 0) return 0
    return this.timeouts / (this.responses + this.timeouts)
  }

  get cold() {
    return this._window.length < MAX_HEALTH_WINDOW
  }

  get idle() {
    return this.responses + this.timeouts < IDLE_THRESHOLD
  }

  get stats() {
    return {
      online: this.online,
      degraded: this.degraded,
      cold: this.cold,
      idle: this.idle,
      responses: this.responses,
      timeouts: this.timeouts,
      timeoutsRate: this.timeoutsRate
    }
  }

  get _tail() {
    return (this._head + 1) % MAX_HEALTH_WINDOW
  }

  reset() {
    this._window = []
    this._head = -1
    this._offlineTicks = 0
    this._degradedTicks = 0
    this._healthyTicks = 0
    this.online = true
    this.degraded = false
    this._dht._online()
  }

  update() {
    // update counters before dropping oldest
    if (this.oldest?.degraded) this._degradedTicks--
    else if (this.oldest?.degraded === false) this._healthyTicks--

    // move window
    this._head = this._tail
    this._window[this._head] = {
      responses: this._dht.stats.requests.responses,
      timeouts: this._dht.stats.requests.timeouts
    }

    // skip state changes
    if (this.cold || this.idle) return

    // update newest
    this.newest.offline = this.responses === 0
    this.newest.degraded =
      !this.newest.offline && this.timeoutsRate > DEGRADED_TIMEOUT_RATE_THRESHOLD

    // update counters
    if (this.newest.offline) this._offlineTicks++
    else {
      this._offlineTicks = 0
      if (this.newest.degraded) this._degradedTicks++
      else this._healthyTicks++
    }

    // check online/offline
    if (!this.newest.offline) this.online = true
    else if (this._offlineTicks >= OFFLINE_THRESHOLD) this.online = false

    // check degraded
    if (!this.online || this._healthyTicks === MAX_HEALTH_WINDOW) this.degraded = false
    else if (this._degradedTicks === MAX_HEALTH_WINDOW) this.degraded = true

    // fire events
    if (this.online && !this.degraded) this._dht._online()
    else if (this.degraded) this._dht._degraded()
    else this._dht._offline()
  }
}
const FIFO = require('fast-fifo')
const sodium = require('sodium-universal')
const c = require('compact-encoding')
const b4a = require('b4a')
const AdaptiveTimeout = require('adaptive-timeout')
const peer = require('./peer')
const { INVALID_TOKEN, REQUEST_TIMEOUT, REQUEST_DESTROYED, IO_SUSPENDED } = require('./errors')

const VERSION = 0b11
const RESPONSE_ID = (0b0001 << 4) | VERSION
const REQUEST_ID = (0b0000 << 4) | VERSION
const EMPTY_ARRAY = []
const MAX_WINDOW = 80

module.exports = class IO {
  constructor(
    table,
    udx,
    {
      maxWindow = MAX_WINDOW,
      port = 0,
      host = '0.0.0.0',
      anyPort = true,
      firewalled = true,
      onrequest,
      onresponse = noop,
      ontimeout = noop,
      adaptiveTimeout
    } = {}
  ) {
    this.table = table
    this.udx = udx
    this.inflight = []
    this.clientSocket = null
    this.serverSocket = null
    this.firewalled = firewalled !== false
    this.ephemeral = true
    this.congestion = new CongestionWindow(maxWindow)
    this.networkInterfaces = udx.watchNetworkInterfaces()
    this.suspended = false

    this.stats = {
      requests: {
        active: 0,
        total: 0,
        responses: 0,
        timeouts: 0,
        retries: 0
      },
      commands: [
        { tx: 0, rx: 0 }, // tx = transmitted, rx = received
        { tx: 0, rx: 0 },
        { tx: 0, rx: 0 },
        { tx: 0, rx: 0 }
      ]
    }

    this.onrequest = onrequest
    this.onresponse = onresponse
    this.ontimeout = ontimeout

    this._pending = new FIFO()
    this._rotateSecrets = 10
    this._tid = (Math.random() * 65536) | 0
    this._secrets = null
    this._drainInterval = null
    this._destroying = null
    this._binding = null

    // port can be a number or a range [start, to]
    this.portRange = port.length ? port : port === 0 ? [0, 0] : [port, port + 5]

    this._host = host
    this._anyPort = anyPort !== false
    this._boundServerPort = 0
    this._boundClientPort = 0

    this._adt = adaptiveTimeout ? new AdaptiveTimeout(adaptiveTimeout) : null
  }

  static DEFAULT_MAX_WINDOW = MAX_WINDOW

  onmessage(socket, buffer, { host, port }) {
    if (buffer.byteLength < 2 || !(port > 0 && port < 65536) || this.suspended === true) return

    const from = { id: null, host, port }
    const state = { start: 1, end: buffer.byteLength, buffer }
    const expectedSocket = this.firewalled ? this.clientSocket : this.serverSocket
    const external = socket !== expectedSocket

    if (buffer[0] === REQUEST_ID) {
      const req = Request.decode(this, socket, from, state)
      if (req === null) return
      if (
        req.token !== null &&
        !b4a.equals(req.token, this.token(req.from, 1)) &&
        !b4a.equals(req.token, this.token(req.from, 0))
      ) {
        req.error(INVALID_TOKEN, { token: true })
        return
      }
      this.onrequest(req, external)
      return
    }

    if (buffer[0] === RESPONSE_ID) {
      const res = decodeReply(from, state)
      if (res === null) return

      for (let i = 0; i < this.inflight.length; i++) {
        const req = this.inflight[i]
        if (req.tid !== res.tid) continue

        res.rtt = Date.now() - req._timestamp

        if (this._adt && req.sent <= 2) {
          this._adt.put(`${req.to.host}:${req.to.port}`, res.rtt)
        }

        if (i === this.inflight.length - 1) this.inflight.pop()
        else this.inflight[i] = this.inflight.pop()

        if (req.session) req.session._detach(req)

        // TODO: Auto retry here if errors.INVALID_TOKEN is returned?

        if (req._timeout) {
          clearTimeout(req._timeout)
          req._timeout = null
        }

        this.congestion.recv()

        if (req.internal && req.command < this.stats.commands.length) {
          this.stats.commands[req.command].rx++
        }

        this.stats.requests.active--
        this.stats.requests.responses++

        this.onresponse(res, external)
        req.onresponse(res, req)
        break
      }
    }
  }

  token(addr, i) {
    if (this._secrets === null) {
      const buf = b4a.alloc(64)
      this._secrets = [buf.subarray(0, 32), buf.subarray(32, 64)]
      sodium.randombytes_buf(this._secrets[0])
      sodium.randombytes_buf(this._secrets[1])
    }

    const token = b4a.allocUnsafe(32)
    sodium.crypto_generichash(token, b4a.from(addr.host), this._secrets[i])
    return token
  }

  async destroy() {
    if (this._destroying) return this._destroying
    this._destroying = this._destroy()
    return this._destroying
  }

  async _destroy() {
    // simplifies timing to await the bind here also, although it might be unneeded
    await this.bind()
    await this._clear(false)
  }

  async _clear(suspended) {
    if (this._drainInterval) {
      clearInterval(this._drainInterval)
      this._drainInterval = null
    }

    while (this.inflight.length) {
      const req = this.inflight.pop()
      if (req._timeout) clearTimeout(req._timeout)
      req._timeout = null
      req.destroyed = true

      if (req.session) req.session._detach(req)

      this.congestion.recv()
      this.stats.requests.active--

      req.onerror(suspended ? IO_SUSPENDED() : REQUEST_DESTROYED(), req)
    }

    await Promise.allSettled([this.serverSocket.close(), this.clientSocket.close()])

    this.networkInterfaces.destroy()
  }

  async suspend() {
    this.suspended = true
    await this._clear(true)

    this.congestion.clear()

    if (this._drainInterval) {
      clearInterval(this._drainInterval)
      this._drainInterval = null
    }
  }

  async _rebind(binding) {
    if (binding) await binding
    if (this._destroying) return this._destroying
    await this._bindSockets()
    this.networkInterfaces = this.udx.watchNetworkInterfaces()
  }

  resume() {
    this.suspended = false
    const binding = this._binding
    this._binding = this._rebind(binding)
    return this._binding
  }

  bind() {
    if (this._binding) return this._binding
    this._binding = this._bindSockets()
    return this._binding
  }

  async _bindSockets() {
    const serverSocket = this.udx.createSocket()

    const candidatePorts = []

    // Retrying previous port always has precedence
    if (this._boundServerPort) candidatePorts.push(this._boundServerPort)

    for (let i = this.portRange[0]; i < this.portRange[1]; i++) candidatePorts.push(i)

    for (const port of candidatePorts) {
      if (serverSocket.bound) break

      try {
        serverSocket.bind(port, this._host)
      } catch (err) {
        if (!this._anyPort) {
          await serverSocket.close()
          throw err
        }
      }
    }

    if (!serverSocket.bound) {
      try {
        serverSocket.bind(0, this._host)
      } catch (err) {
        await serverSocket.close()
        throw err
      }
    }

    const clientSocket = this.udx.createSocket()

    try {
      clientSocket.bind(this._boundClientPort || 0, this._host)
    } catch {
      try {
        clientSocket.bind(0, this._host)
      } catch (err) {
        await serverSocket.close()
        await clientSocket.close()
        throw err
      }
    }

    this._boundServerPort = serverSocket.address().port
    this._boundClientPort = clientSocket.address().port

    this.clientSocket = clientSocket
    this.serverSocket = serverSocket

    this.serverSocket.on('message', this.onmessage.bind(this, this.serverSocket))
    this.clientSocket.on('message', this.onmessage.bind(this, this.clientSocket))

    if (this._drainInterval === null) {
      this._drainInterval = setInterval(this._drain.bind(this), 750)
      if (this._drainInterval.unref) this._drainInterval.unref()
    }

    for (const req of this.inflight) {
      if (!req.socket) req.socket = this.firewalled ? this.clientSocket : this.serverSocket
      req.sent = 0
      req.send(false)
    }
  }

  _drain() {
    if (this._secrets !== null && --this._rotateSecrets === 0) {
      this._rotateSecrets = 10
      const tmp = this._secrets[0]
      this._secrets[0] = this._secrets[1]
      this._secrets[1] = tmp
      sodium.crypto_generichash(tmp, tmp)
    }

    this.congestion.drain()

    while (!this.congestion.isFull()) {
      const p = this._pending.shift()
      if (p === undefined) return
      p._sendNow()
    }
  }

  createRequest(to, token, internal, command, target, value, session, ttl) {
    if (this._destroying !== null) return null

    if (this._tid === 65536) this._tid = 0

    const tid = this._tid++
    const socket = this.firewalled ? this.clientSocket : this.serverSocket

    const req = new Request(
      this,
      socket,
      tid,
      null,
      to,
      token,
      internal,
      command,
      target,
      value,
      session,
      ttl || 0
    )
    this.inflight.push(req)
    if (session) session._attach(req)

    if (internal && command < this.stats.commands.length) {
      this.stats.commands[command].tx++
    }

    this.stats.requests.active++
    this.stats.requests.total++

    return req
  }
}

class Request {
  constructor(io, socket, tid, from, to, token, internal, command, target, value, session, ttl) {
    this.socket = socket
    this.tid = tid
    this.from = from
    this.to = to
    this.token = token
    this.command = command
    this.target = target
    this.value = value
    this.internal = internal
    this.session = session
    this.ttl = ttl
    this.index = -1
    this.sent = 0
    this.retries = 3
    this.destroyed = false
    this.timeout = 0

    this.oncycle = noop
    this.onerror = noop
    this.onresponse = noop

    this._buffer = null
    this._io = io
    this._timeout = null
    this._timestamp = Date.now()
  }

  static decode(io, socket, from, state) {
    try {
      const flags = c.uint.decode(state)
      const tid = c.uint16.decode(state)
      const to = peer.ipv4.decode(state)
      const id = flags & 1 ? c.fixed32.decode(state) : null
      const token = flags & 2 ? c.fixed32.decode(state) : null
      const internal = (flags & 4) !== 0
      const command = c.uint.decode(state)
      const target = flags & 8 ? c.fixed32.decode(state) : null
      const value = flags & 16 ? c.buffer.decode(state) : null

      if (id !== null) from.id = validateId(id, from)

      return new Request(
        io,
        socket,
        tid,
        from,
        to,
        token,
        internal,
        command,
        target,
        value,
        null,
        0
      )
    } catch {
      return null
    }
  }

  reply(value, opts = {}) {
    const socket = opts.socket || this.socket
    const to = opts.to || this.from
    this._sendReply(0, value || null, opts.token !== false, opts.closerNodes !== false, to, socket)
  }

  error(code, opts = {}) {
    const socket = opts.socket || this.socket
    const to = opts.to || this.from
    this._sendReply(code, null, opts.token === true, opts.closerNodes !== false, to, socket)
  }

  relay(value, to, opts) {
    const socket = (opts && opts.socket) || this.socket
    const buffer = this._encodeRequest(null, value, to, socket)
    socket.trySend(buffer, to.port, to.host, this.ttl)
  }

  send(force = false) {
    if (this.destroyed) return

    if (this.socket === null) return
    if (this._buffer === null) {
      this._buffer = this._encodeRequest(this.token, this.value, this.to, this.socket)
    }

    if (!force && this._io.congestion.isFull()) {
      this._io._pending.push(this)
      return
    }

    this._sendNow()
  }

  sendReply(error, value, token, hasCloserNodes) {
    this._sendReply(error, value, token, hasCloserNodes, this.from, this.socket, null)
  }

  _sendNow() {
    if (this.destroyed) return
    this.sent++
    this._io.congestion.send()
    this.socket.trySend(this._buffer, this.to.port, this.to.host, this.ttl)
    if (this._timeout) clearTimeout(this._timeout)
    const value =
      this.timeout || this._io._adt?.get(`${this.to.host}:${this.to.port}`, this.sent) || 1000
    this._timeout = setTimeout(oncycle, value, this)
  }

  destroy(err) {
    if (this.destroyed) return
    this.destroyed = true

    if (this._timeout) {
      clearTimeout(this._timeout)
      this._timeout = null
    }

    const i = this._io.inflight.indexOf(this)
    if (i === -1) return

    if (i === this._io.inflight.length - 1) this._io.inflight.pop()
    else this._io.inflight[i] = this._io.inflight.pop()

    if (this.session) this.session._detach(this)

    this._io.stats.requests.active--
    this._io.congestion.recv()

    this.onerror(err || REQUEST_DESTROYED(), this)
  }

  _sendReply(error, value, token, hasCloserNodes, from, socket) {
    if (socket === null || this.destroyed) return

    const id = this._io.ephemeral === false && socket === this._io.serverSocket
    const closerNodes =
      this.target !== null && hasCloserNodes ? this._io.table.closest(this.target) : EMPTY_ARRAY
    const state = { start: 0, end: 1 + 1 + 6 + 2, buffer: null } // (type | version) + flags + to + tid

    if (id) state.end += 32
    if (token) state.end += 32
    if (closerNodes.length > 0) peer.ipv4Array.preencode(state, closerNodes)
    if (error > 0) c.uint.preencode(state, error)
    if (value) c.buffer.preencode(state, value)

    state.buffer = b4a.allocUnsafe(state.end)
    state.buffer[state.start++] = RESPONSE_ID
    state.buffer[state.start++] =
      (id ? 1 : 0) |
      (token ? 2 : 0) |
      (closerNodes.length > 0 ? 4 : 0) |
      (error > 0 ? 8 : 0) |
      (value ? 16 : 0)

    c.uint16.encode(state, this.tid)
    peer.ipv4.encode(state, from)

    if (id) c.fixed32.encode(state, this._io.table.id)
    if (token) c.fixed32.encode(state, this._io.token(from, 1))
    if (closerNodes.length > 0) peer.ipv4Array.encode(state, closerNodes)
    if (error > 0) c.uint.encode(state, error)
    if (value) c.buffer.encode(state, value)

    socket.trySend(state.buffer, from.port, from.host, this.ttl)
  }

  _encodeRequest(token, value, to, socket) {
    const id = this._io.ephemeral === false && socket === this._io.serverSocket
    const state = { start: 0, end: 1 + 1 + 6 + 2, buffer: null } // (type | version) + flags + to + tid

    if (id) state.end += 32
    if (token) state.end += 32

    c.uint.preencode(state, this.command)

    if (this.target) state.end += 32
    if (value) c.buffer.preencode(state, value)

    state.buffer = b4a.allocUnsafe(state.end)
    state.buffer[state.start++] = REQUEST_ID
    state.buffer[state.start++] =
      (id ? 1 : 0) |
      (token ? 2 : 0) |
      (this.internal ? 4 : 0) |
      (this.target ? 8 : 0) |
      (value ? 16 : 0)

    c.uint16.encode(state, this.tid)
    peer.ipv4.encode(state, to)

    if (id) c.fixed32.encode(state, this._io.table.id)
    if (token) c.fixed32.encode(state, token)

    c.uint.encode(state, this.command)

    if (this.target) c.fixed32.encode(state, this.target)
    if (value) c.buffer.encode(state, value)

    return state.buffer
  }
}

class CongestionWindow {
  constructor(maxWindow) {
    this._i = 0
    this._total = 0
    this._window = [0, 0, 0, 0]
    this._maxWindow = maxWindow
  }

  clear() {
    this._i = 0
    this._total = 0
    this._window = [0, 0, 0, 0]
  }

  isFull() {
    return this._total >= 2 * this._maxWindow || this._window[this._i] >= this._maxWindow
  }

  recv() {
    if (this._window[this._i] > 0) {
      this._window[this._i]--
      this._total--
    }
  }

  send() {
    this._total++
    this._window[this._i]++
  }

  drain() {
    this._i = (this._i + 1) & 3
    this._total -= this._window[this._i]
    this._window[this._i] = 0 // clear oldest
  }
}

function noop() {}

function oncycle(req) {
  req._timeout = null
  req.oncycle(req)
  if (req.sent > req.retries) {
    req._io.stats.requests.timeouts++
    req.destroy(REQUEST_TIMEOUT())
    req._io.ontimeout(req)
  } else {
    req._io.stats.requests.retries++
    req.send()
  }
}

function decodeReply(from, state) {
  try {
    const flags = c.uint.decode(state)
    const tid = c.uint16.decode(state)
    const to = peer.ipv4.decode(state)
    const id = flags & 1 ? c.fixed32.decode(state) : null
    const token = flags & 2 ? c.fixed32.decode(state) : null
    const closerNodes = flags & 4 ? peer.ipv4Array.decode(state) : null
    const error = flags & 8 ? c.uint.decode(state) : 0
    const value = flags & 16 ? c.buffer.decode(state) : null

    if (id !== null) from.id = validateId(id, from)

    return { tid, rtt: 0, from, to, token, closerNodes, error, value }
  } catch {
    return null
  }
}

function validateId(id, from) {
  const expected = peer.id(from.host, from.port)
  return b4a.equals(expected, id) ? expected : null
}
const sodium = require('sodium-universal')
const c = require('compact-encoding')
const b4a = require('b4a')

const ipv4 = {
  ...c.ipv4Address,
  decode(state) {
    const ip = c.ipv4Address.decode(state)
    return {
      id: null, // populated by the callee
      host: ip.host,
      port: ip.port
    }
  }
}

module.exports = { id, ipv4, ipv4Array: c.array(ipv4) }

function id(host, port, out = b4a.allocUnsafeSlow(32)) {
  const addr = out.subarray(0, 6)
  ipv4.encode({ start: 0, end: 6, buffer: addr }, { host, port })
  sodium.crypto_generichash(out, addr)
  return out
}
const { Readable, getStreamError } = require('streamx')
const b4a = require('b4a')
const peer = require('./peer')
const { DOWN_HINT } = require('./commands')

const DONE = []
const DOWN = []

module.exports = class Query extends Readable {
  constructor(dht, target, internal, command, value, opts = {}) {
    super()

    dht.stats.queries.total++
    dht.stats.queries.active++

    this.force = !!opts.force
    this.dht = dht
    this.k = this.dht.table.k
    this.target = target
    this.internal = internal
    this.command = command
    this.value = value
    this.errors = 0
    this.successes = 0
    this.concurrency = opts.concurrency || this.dht.concurrency
    this.inflight = 0
    this.map = opts.map || defaultMap
    this.retries =
      opts.retries === 0 ? 0 : opts.retries || (this.internal && command === DOWN_HINT ? 3 : 5)
    this.closestReplies = []

    this._slow = 0
    this._slowdown = false
    this._seen = new Map()
    this._pending = []
    this._fromTable = false
    this._commit = opts.commit === true ? autoCommit : opts.commit || null
    this._commiting = false
    this._session = opts.session || dht.session()
    this._autoDestroySession = !opts.session
    this._onlyClosestNodes = false

    this._onvisitbound = this._onvisit.bind(this)
    this._onerrorbound = this._onerror.bind(this)
    this._oncyclebound = this._oncycle.bind(this)

    const nodes = opts.nodes || opts.closestNodes
    const replies = opts.replies || opts.closestReplies

    // add them reverse as we pop below
    if (nodes) {
      for (let i = nodes.length - 1; i >= 0; i--) {
        const node = nodes[i]
        this._addPending(
          {
            id: node.id || peer.id(node.host, node.port),
            host: node.host,
            port: node.port
          },
          null
        )
      }
    } else if (replies) {
      for (let i = replies.length - 1; i >= 0; i--) {
        this._addPending(replies[i].from, null)
      }
    }

    if (opts.onlyClosestNodes) this._onlyClosestNodes = true
  }

  get closestNodes() {
    const nodes = new Array(this.closestReplies.length)

    for (let i = 0; i < nodes.length; i++) {
      nodes[i] = this.closestReplies[i].from
    }

    return nodes
  }

  finished() {
    return new Promise((resolve, reject) => {
      if (this.destroyed) {
        const error = getStreamError(this)
        if (error) reject(error)
        else resolve()
        return
      }

      const self = this
      let error = null

      this.resume()
      this.on('error', onerror)
      this.on('close', onclose)

      function onclose() {
        self.removeListener('error', onerror)
        self.removeListener('close', onclose)
        if (error) reject(error)
        else resolve()
      }

      function onerror(err) {
        error = err
      }
    })
  }

  _addFromTable() {
    if (this._pending.length >= this.k) return
    this._fromTable = true

    const closest = this.dht.table.closest(this.target, this.k - this._pending.length)

    for (const node of closest) {
      this._addPending({ id: node.id, host: node.host, port: node.port }, null)
    }
  }

  async _open(cb) {
    this._addFromTable()
    if (this._pending.length >= this.k) return cb(null)

    for await (const node of this.dht._resolveBootstrapNodes()) {
      this._addPending(node, null)
    }

    cb(null)
  }

  _isCloser(id) {
    return (
      this.closestReplies.length < this.k ||
      this._compare(id, this.closestReplies[this.closestReplies.length - 1].from.id) < 0
    )
  }

  _addPending(node, ref) {
    if (this._onlyClosestNodes) return false

    const addr = node.host + ':' + node.port
    const refs = this._seen.get(addr)
    const isCloser = this._isCloser(node.id)

    if (refs === DONE) {
      return isCloser
    }

    if (refs === DOWN) {
      if (ref) this._downHint(ref, node)
      return isCloser
    }

    if (refs) {
      if (ref !== null) refs.push(ref)
      return isCloser
    }

    if (!isCloser) {
      return false
    }

    this._seen.set(addr, ref === null ? [] : [ref])
    this._pending.push(node)

    return true
  }

  _read(cb) {
    this._readMore()
    cb(null)
  }

  _readMore() {
    if (this.destroying || this._commiting) return

    const concurrency = (this._slowdown ? 3 : this.concurrency) + this._slow

    while (this.inflight < concurrency && this._pending.length > 0) {
      const next = this._pending.pop()
      if (next && next.id && !this._isCloser(next.id)) continue
      this._visit(next)
    }

    // if reusing closest nodes, slow down after the first readMore tick to allow
    // the closest node a chance to reply before going broad to question more
    if (!this._fromTable && this.successes === 0 && this.errors === 0) {
      this._slowdown = true
    }

    if (this._pending.length > 0) return

    // if no inflight OR all the queries we are waiting on are marked as slow and we have a full result.
    if (
      this.inflight === 0 ||
      (this._slow === this.inflight && this.closestReplies.length >= this.k)
    ) {
      // if more than 3/4 failed and we only used cached nodes, try again from the routing table
      if (!this._fromTable && this.successes < this.k / 4) {
        this._addFromTable()
        this._readMore()
        return
      }

      this._flush()
    }
  }

  _flush() {
    if (this._commiting) return
    this._commiting = true

    if (this._commit === null) {
      this.push(null)
      return
    }

    const p = []
    for (const m of this.closestReplies) p.push(this._commit(m, this.dht, this))
    this._endAfterCommit(p)
  }

  _endAfterCommit(ps) {
    if (!ps.length) {
      this.destroy(new Error('Too few nodes responded'))
      return
    }

    const self = this

    let pending = ps.length
    let success = 0

    for (const p of ps) p.then(ondone, onerror)

    function ondone() {
      success++
      if (--pending === 0) self.push(null)
    }

    function onerror(err) {
      if (--pending > 0) return
      if (success) self.push(null)
      else self.destroy(err)
    }
  }

  _dec(req) {
    if (req.oncycle === noop) {
      this._slow--
    } else {
      req.oncycle = noop
    }
    this.inflight--
  }

  _onvisit(m, req) {
    this._dec(req)

    const addr = req.to.host + ':' + req.to.port
    this._seen.set(addr, DONE)

    if (this._commiting) return

    if (m.error === 0) this.successes++
    else this.errors++

    if (m.error === 0 && m.from.id !== null && this._isCloser(m.from.id)) this._pushClosest(m)

    if (m.closerNodes !== null) {
      for (const node of m.closerNodes) {
        node.id = peer.id(node.host, node.port)
        if (this.dht._filterNode !== null && !this.dht._filterNode(node)) continue
        if (b4a.equals(node.id, this.dht.table.id)) continue
        // TODO: we could continue here instead of breaking to ensure that one of the nodes in the closer list
        // is later marked as DOWN that we gossip that back
        if (!this._addPending(node, m.from)) break
      }
    }

    if (!this._fromTable && this.successes + this.errors >= this.concurrency) {
      this._slowdown = false
    }

    if (m.error !== 0) {
      this._readMore()
      return
    }

    const data = this.map(m)
    if (!data || this.push(data) !== false) {
      this._readMore()
    }
  }

  _onerror(err, req) {
    const addr = req.to.host + ':' + req.to.port
    const refs = this._seen.get(addr)

    if (err.code === 'REQUEST_TIMEOUT') {
      this._seen.set(addr, DOWN)
      for (const node of refs) this._downHint(node, req.to)
    }

    this._dec(req)
    this.errors++
    this._readMore()
  }

  _oncycle(req) {
    req.oncycle = noop
    this._slow++
    this._readMore()
  }

  _downHint(node, down) {
    // Check rate limit
    if (
      this.dht._downHintsRateLimit !== -1 &&
      this.dht._downHintsSentPerTick >= this.dht._downHintsRateLimit
    ) {
      return null
    }

    this.dht._downHintsSentPerTick++

    const state = { start: 0, end: 6, buffer: b4a.allocUnsafe(6) }
    peer.ipv4.encode(state, down)
    this.dht._request(node, false, true, DOWN_HINT, null, state.buffer, this._session, noop, noop)
  }

  _pushClosest(m) {
    this.closestReplies.push(m)
    for (let i = this.closestReplies.length - 2; i >= 0; i--) {
      const prev = this.closestReplies[i]
      const cmp = this._compare(prev.from.id, m.from.id)
      // if sorted, done!
      if (cmp < 0) break
      // if dup, splice it out (rare)
      if (cmp === 0) {
        this.closestReplies.splice(i + 1, 1)
        break
      }
      // swap and continue down
      this.closestReplies[i + 1] = prev
      this.closestReplies[i] = m
    }
    if (this.closestReplies.length > this.k) this.closestReplies.pop()
  }

  _compare(a, b) {
    for (let i = 0; i < a.length; i++) {
      if (a[i] === b[i]) continue
      const t = this.target[i]
      return (t ^ a[i]) - (t ^ b[i])
    }
    return 0
  }

  _visit(to) {
    this.inflight++

    const req = this.dht._request(
      to,
      this.force,
      this.internal,
      this.command,
      this.target,
      this.value,
      this._session,
      this._onvisitbound,
      this._onerrorbound
    )
    if (req === null) {
      this.destroy(new Error('Node was destroyed'))
      return
    }
    req.retries = this.retries
    req.oncycle = this._oncyclebound
    if (this.force) req.retries = 0
  }

  _destroy(cb) {
    this.dht.stats.queries.active--
    if (this._autoDestroySession) this._session.destroy()
    cb(null)
  }
}

function autoCommit(reply, dht, query) {
  if (!reply.token) return Promise.reject(new Error('No token received for closest node'))
  return dht.request(
    {
      token: reply.token,
      target: query.target,
      command: query.command,
      value: query.value
    },
    reply.from
  )
}

function defaultMap(m) {
  return m
}

function noop() {}
module.exports = class Session {
  constructor(dht) {
    this.dht = dht
    this.inflight = []
  }

  _attach(req) {
    req.index = this.inflight.push(req) - 1
  }

  _detach(req) {
    const i = req.index
    if (i === -1) return
    req.index = -1

    if (i === this.inflight.length - 1) this.inflight.pop()
    else {
      const req = (this.inflight[i] = this.inflight.pop())
      req.index = i
    }
  }

  query({ target, command, value }, opts = {}) {
    return this.dht.query({ target, command, value }, { ...opts, session: this })
  }

  request({ token, command, target, value }, { host, port }, opts = {}) {
    return this.dht.request(
      { token, command, target, value },
      { host, port },
      { ...opts, session: this }
    )
  }

  ping({ host, port }, opts = {}) {
    return this.dht.ping({ host, port }, { ...opts, session: this })
  }

  destroy(err) {
    while (this.inflight.length) {
      const req = this.inflight[0]
      // prevent destroyed requests from contributing to congestion counts
      this.dht.io.congestion.recv()
      req.destroy(err)
    }
  }
}
{
  "name": "dht-rpc",
  "version": "6.27.0",
  "description": "Make RPC calls over a Kademlia based DHT",
  "main": "index.js",
  "files": [
    "index.js",
    "lib/*.js"
  ],
  "imports": {
    "events": {
      "bare": "bare-events",
      "default": "events"
    }
  },
  "dependencies": {
    "adaptive-timeout": "^1.0.1",
    "b4a": "^1.6.1",
    "bare-events": "^2.2.0",
    "compact-encoding": "^3.0.0",
    "compact-encoding-net": "^1.2.0",
    "fast-fifo": "^1.1.0",
    "kademlia-routing-table": "^1.0.1",
    "nat-sampler": "^1.0.1",
    "sodium-universal": "^5.0.0",
    "streamx": "^2.13.2",
    "time-ordered-set": "^2.0.0",
    "udx-native": "^1.5.3"
  },
  "devDependencies": {
    "brittle": "^3.0.0",
    "lunte": "^1.3.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^2.0.0",
    "test-suspend": "^1.0.0"
  },
  "scripts": {
    "format": "prettier --write .",
    "test": "npm run test:node && npm run test:bare",
    "test:bare": "brittle-bare --coverage test.js",
    "lint": "prettier --check . && lunte",
    "test:node": "brittle-node --coverage test.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/holepunchto/dht-rpc.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/holepunchto/dht-rpc/issues"
  },
  "homepage": "https://github.com/holepunchto/dht-rpc#"
}
const { encode, decode } = require('./spec/hyperschema')

async function encrypt(data, cb) {
  const result = await cb(data)

  return encode('@encryption/encrypted-data', result)
}

async function decrypt(data, cb) {
  const decoded = decode('@encryption/encrypted-data', data)
  const decrypted = await cb(decoded)

  return decrypted
}

module.exports = { encrypt, decrypt }
{
  "name": "encryption-encoding",
  "version": "1.0.3",
  "description": "Compact encoding for versioned encrypted values with automatic string/buffer handling.",
  "main": "index.js",
  "exports": {
    "./package": "./package.json",
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    }
  },
  "files": [
    "package.json",
    "index.js",
    "index.d.ts",
    "spec"
  ],
  "keywords": [
    "pear",
    "p2p",
    "bare",
    "autobase",
    "encryption",
    "hyperschema"
  ],
  "devDependencies": {
    "b4a": "^1.7.3",
    "brittle": "^3.19.0",
    "lunte": "^1.2.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^2.0.0",
    "sodium-universal": "^5.0.1"
  },
  "scripts": {
    "format": "prettier . --write",
    "test": "prettier . --check && lunte && brittle-bare test/all.js",
    "lint": "lunte"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/holepunchto/encryption-encoding.git"
  },
  "author": "Holepunch Inc",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/encryption-encoding/issues"
  },
  "homepage": "https://github.com/holepunchto/encryption-encoding",
  "dependencies": {
    "hyperschema": "^1.19.0"
  }
}
// This file is autogenerated by the hyperschema compiler
// Schema Version: 1
/* eslint-disable camelcase */
/* eslint-disable quotes */
/* eslint-disable space-before-function-paren */

const { c } = require('hyperschema/runtime')

const VERSION = 1

// eslint-disable-next-line no-unused-vars
let version = VERSION

// @encryption/encrypted-data
const encoding0 = {
  preencode(state, m) {
    c.uint.preencode(state, m.type)
    c.buffer.preencode(state, m.value)
  },
  encode(state, m) {
    c.uint.encode(state, m.type)
    c.buffer.encode(state, m.value)
  },
  decode(state) {
    const r0 = c.uint.decode(state)
    const r1 = c.buffer.decode(state)

    return {
      type: r0,
      value: r1
    }
  }
}

function setVersion(v) {
  version = v
}

function encode(name, value, v = VERSION) {
  version = v
  return c.encode(getEncoding(name), value)
}

function decode(name, buffer, v = VERSION) {
  version = v
  return c.decode(getEncoding(name), buffer)
}

function getEnum(name) {
  switch (name) {
    default:
      throw new Error('Enum not found ' + name)
  }
}

function getEncoding(name) {
  switch (name) {
    case '@encryption/encrypted-data':
      return encoding0
    default:
      throw new Error('Encoder not found ' + name)
  }
}

function getStruct(name, v = VERSION) {
  const enc = getEncoding(name)
  return {
    preencode(state, m) {
      version = v
      enc.preencode(state, m)
    },
    encode(state, m) {
      version = v
      enc.encode(state, m)
    },
    decode(state) {
      version = v
      return enc.decode(state)
    }
  }
}

const resolveStruct = getStruct // compat

module.exports = {
  resolveStruct,
  getStruct,
  getEnum,
  getEncoding,
  encode,
  decode,
  setVersion,
  version
}
module.exports = require('bare-events')
{
  "name": "events-universal",
  "version": "1.0.1",
  "description": "Universal wrapper for the Node.js events module",
  "exports": {
    "./package": "./package.json",
    ".": {
      "bare": "./bare.js",
      "react-native": "./react-native.js",
      "default": "./default.js"
    }
  },
  "files": [
    "index.js",
    "default.js",
    "bare.js",
    "react-native.js"
  ],
  "scripts": {
    "test": "prettier . --check"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/events-universal.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/events-universal/issues"
  },
  "homepage": "https://github.com/holepunchto/events-universal#readme",
  "dependencies": {
    "bare-events": "^2.7.0"
  },
  "devDependencies": {
    "prettier": "^3.4.2",
    "prettier-config-holepunch": "^1.0.0"
  }
}
module.exports = class FixedFIFO {
  constructor (hwm) {
    if (!(hwm > 0) || ((hwm - 1) & hwm) !== 0) throw new Error('Max size for a FixedFIFO should be a power of two')
    this.buffer = new Array(hwm)
    this.mask = hwm - 1
    this.top = 0
    this.btm = 0
    this.next = null
  }

  clear () {
    this.top = this.btm = 0
    this.next = null
    this.buffer.fill(undefined)
  }

  push (data) {
    if (this.buffer[this.top] !== undefined) return false
    this.buffer[this.top] = data
    this.top = (this.top + 1) & this.mask
    return true
  }

  shift () {
    const last = this.buffer[this.btm]
    if (last === undefined) return undefined
    this.buffer[this.btm] = undefined
    this.btm = (this.btm + 1) & this.mask
    return last
  }

  peek () {
    return this.buffer[this.btm]
  }

  isEmpty () {
    return this.buffer[this.btm] === undefined
  }
}
const FixedFIFO = require('./fixed-size')

module.exports = class FastFIFO {
  constructor (hwm) {
    this.hwm = hwm || 16
    this.head = new FixedFIFO(this.hwm)
    this.tail = this.head
    this.length = 0
  }

  clear () {
    this.head = this.tail
    this.head.clear()
    this.length = 0
  }

  push (val) {
    this.length++
    if (!this.head.push(val)) {
      const prev = this.head
      this.head = prev.next = new FixedFIFO(2 * this.head.buffer.length)
      this.head.push(val)
    }
  }

  shift () {
    if (this.length !== 0) this.length--
    const val = this.tail.shift()
    if (val === undefined && this.tail.next) {
      const next = this.tail.next
      this.tail.next = null
      this.tail = next
      return this.tail.shift()
    }

    return val
  }

  peek () {
    const val = this.tail.peek()
    if (val === undefined && this.tail.next) return this.tail.next.peek()
    return val
  }

  isEmpty () {
    return this.length === 0
  }
}
{
  "name": "fast-fifo",
  "version": "1.3.2",
  "description": "A fast fifo implementation similar to the one powering nextTick in Node.js core",
  "main": "index.js",
  "files": [
    "./index.js",
    "./fixed-size.js"
  ],
  "dependencies": {},
  "devDependencies": {
    "standard": "^17.1.0",
    "brittle": "^3.3.2"
  },
  "scripts": {
    "test": "standard && brittle test.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/mafintosh/fast-fifo.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/mafintosh/fast-fifo/issues"
  },
  "homepage": "https://github.com/mafintosh/fast-fifo"
}
const fs = require('fs')
const fsx = require('fs-native-extensions')
const onexit = require('resource-on-exit')
const ReadyResource = require('ready-resource')

module.exports = class FDLock extends ReadyResource {
  constructor(fd, opts = {}) {
    const { wait = false } = opts

    super()

    this._fd = fd
    this._wait = wait
    this._locked = false

    onexit.add(this, closeSync)
  }

  async _open() {
    try {
      await this._resume()
    } catch (err) {
      onexit.remove(this)
      await close(this)
      throw err
    }
  }

  async _close() {
    onexit.remove(this)
    await close(this)
  }

  transfer() {
    const fd = this._fd
    if (fd === -1) throw new Error('Lock has already been transferred')
    this._fd = -1
    onexit.remove(this)
    return fd
  }

  async suspend() {
    if (!this.opened) await this.ready()
    return this._suspend()
  }

  async _suspend() {
    if (this._fd === -1 || this._locked === false) return
    fsx.unlock(this._fd)
    this._locked = false
  }

  async resume() {
    if (!this.opened) await this.ready()
    return this._resume()
  }

  async _resume() {
    if (this._fd === -1 || this._locked === true) return
    if (this._wait) await fsx.waitForLock(this._fd)
    else if (!fsx.tryLock(this._fd)) {
      throw new Error('File descriptor could not be locked')
    }
    this._locked = true
  }
}

function close(lock) {
  const fd = lock._fd
  if (fd === -1) return
  lock._fd = -1
  return new Promise((resolve) => fs.close(fd, () => resolve()))
}

function closeSync(lock) {
  const fd = lock._fd
  if (fd === -1) return
  lock._fd = -1
  try {
    fs.closeSync(fd)
  } catch {}
}
{
  "name": "fd-lock",
  "version": "2.1.1",
  "description": "Stateful file descriptor locks for JavaScript",
  "exports": {
    "./package": "./package.json",
    ".": "./index.js"
  },
  "imports": {
    "fs": {
      "bare": "bare-fs",
      "default": "fs"
    }
  },
  "files": [
    "index.js"
  ],
  "scripts": {
    "test": "npm run lint && npm run test:bare && npm run test:node",
    "test:bare": "bare test.js",
    "test:node": "node test.js",
    "lint": "prettier --check .",
    "format": "prettier --write ."
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/fd-lock.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/fd-lock/issues"
  },
  "homepage": "https://github.com/holepunchto/fd-lock#readme",
  "dependencies": {
    "bare-fs": "^4.5.0",
    "fs-native-extensions": "^1.4.4",
    "ready-resource": "^1.2.0",
    "resource-on-exit": "^1.0.0"
  },
  "devDependencies": {
    "brittle": "^3.3.2",
    "prettier": "^3.4.2",
    "prettier-config-holepunch": "^2.0.0"
  }
}
exports.fullRoots = function (index, result) {
  if (index & 1) throw new Error('You can only look up roots for depth(0) blocks')
  if (!result) result = []

  index /= 2

  let offset = 0
  let factor = 1

  while (true) {
    if (!index) return result
    while (factor * 2 <= index) factor *= 2
    result.push(offset + factor - 1)
    offset = offset + 2 * factor
    index -= factor
    factor = 1
  }
}

exports.futureRoots = function (index, result) {
  if (index & 1) throw new Error('You can only look up future roots for depth(0) blocks')
  if (!result) result = []

  let factor = 1

  // make first root
  while (factor * 2 <= index) factor *= 2

  // full factor of 2 - done
  if (factor * 2 - 2 === index) return result

  let pos = factor / 2 - 1

  // while its not a full tree
  while ((pos + factor / 2 - 1) !== index) {
    pos += factor

    // read too far, to to left child
    while ((pos + factor / 2 - 1) > index) {
      factor /= 2
      pos -= factor / 2
    }

    // the "gap" is a future root
    result.push(pos - factor / 2)
  }

  return result
}

exports.patch = function (from, to) {
  if (from === 0 || from >= to) return []

  const roots = exports.fullRoots(from)
  const target = exports.fullRoots(to)

  // first find the first root that is different

  let i = 0
  for (; i < target.length; i++) {
    if (i >= roots.length || roots[i] !== target[i]) break
  }

  const patch = []

  if (i < roots.length) {
    // now we need to grow the newest root until it hits the diff one
    let prev = roots.length - 1

    const ite = exports.iterator(roots[prev--])

    while (ite.index !== target[i]) {
      ite.sibling()

      if (prev >= 0 && ite.index === roots[prev]) {
        prev--
      } else {
        patch.push(ite.index)
      }

      patch.push(ite.parent())
    }

    i++ // patched to next root, so inc
  }

  // include the rest

  for (; i < target.length; i++) patch.push(target[i])

  return patch
}

exports.depth = function (index) {
  let depth = 0

  index += 1
  while (!(index & 1)) {
    depth++
    index = rightShift(index)
  }

  return depth
}

exports.sibling = function (index, depth) {
  if (!depth) depth = exports.depth(index)
  const offset = exports.offset(index, depth)

  return exports.index(depth, offset & 1 ? offset - 1 : offset + 1)
}

exports.parent = function (index, depth) {
  if (!depth) depth = exports.depth(index)
  const offset = exports.offset(index, depth)

  return exports.index(depth + 1, rightShift(offset))
}

exports.leftChild = function (index, depth) {
  if (!(index & 1)) return -1
  if (!depth) depth = exports.depth(index)
  return exports.index(depth - 1, exports.offset(index, depth) * 2)
}

exports.rightChild = function (index, depth) {
  if (!(index & 1)) return -1
  if (!depth) depth = exports.depth(index)
  return exports.index(depth - 1, 1 + (exports.offset(index, depth) * 2))
}

exports.children = function (index, depth) {
  if (!(index & 1)) return null

  if (!depth) depth = exports.depth(index)
  const offset = exports.offset(index, depth) * 2

  return [
    exports.index(depth - 1, offset),
    exports.index(depth - 1, offset + 1)
  ]
}

exports.leftSpan = function (index, depth) {
  if (!(index & 1)) return index
  if (!depth) depth = exports.depth(index)
  return exports.offset(index, depth) * twoPow(depth + 1)
}

exports.rightSpan = function (index, depth) {
  if (!(index & 1)) return index
  if (!depth) depth = exports.depth(index)
  return (exports.offset(index, depth) + 1) * twoPow(depth + 1) - 2
}

exports.nextLeaf = function (index) {
  let factor = 1
  let r = index

  while ((r & 1) === 1) {
    r = (r - 1) / 2
    factor *= 2
  }

  return index + factor + 1
}

exports.count = function (index, depth) {
  if (!(index & 1)) return 1
  if (!depth) depth = exports.depth(index)
  return twoPow(depth + 1) - 1
}

exports.countLeaves = function (index) {
  return (exports.count(index) + 1) / 2
}

exports.spans = function (index, depth) {
  if (!(index & 1)) return [index, index]
  if (!depth) depth = exports.depth(index)

  const offset = exports.offset(index, depth)
  const width = twoPow(depth + 1)

  return [offset * width, (offset + 1) * width - 2]
}

exports.index = function (depth, offset) {
  return (1 + 2 * offset) * twoPow(depth) - 1
}

exports.offset = function (index, depth) {
  if (!(index & 1)) return index / 2
  if (!depth) depth = exports.depth(index)

  return ((index + 1) / twoPow(depth) - 1) / 2
}

exports.iterator = function (index) {
  const ite = new Iterator()
  ite.seek(index || 0)
  return ite
}

function twoPow (n) {
  return n < 31 ? 1 << n : ((1 << 30) * (1 << (n - 30)))
}

function rightShift (n) {
  return (n - (n & 1)) / 2
}

function Iterator () {
  this.index = 0
  this.offset = 0
  this.factor = 0
}

Iterator.prototype.seek = function (index) {
  this.index = index
  if (this.index & 1) {
    this.offset = exports.offset(index)
    this.factor = twoPow(exports.depth(index) + 1)
  } else {
    this.offset = index / 2
    this.factor = 2
  }
}

Iterator.prototype.isLeft = function () {
  return (this.offset & 1) === 0
}

Iterator.prototype.isRight = function () {
  return (this.offset & 1) === 1
}

Iterator.prototype.isRoot = function (length) {
  const currentLength = 1 + (this.index + this.factor / 2 - 1) / 2
  if (length < currentLength) return false

  const factor = this.factor * 2
  const index = (this.offset & 1)
    ? this.index - this.factor / 2
    : this.index + this.factor / 2

  const parentLength = 1 + (index + factor / 2 - 1) / 2
  return parentLength > length
}

Iterator.prototype.contains = function (index) {
  return index > this.index
    ? index < (this.index + this.factor / 2)
    : index < this.index
      ? index > (this.index - this.factor / 2)
      : true
}

Iterator.prototype.prev = function () {
  if (!this.offset) return this.index
  this.offset--
  this.index -= this.factor
  return this.index
}

Iterator.prototype.next = function () {
  this.offset++
  this.index += this.factor
  return this.index
}

Iterator.prototype.count = function () {
  if (!(this.index & 1)) return 1
  return this.factor - 1
}

Iterator.prototype.countLeaves = function () {
  return (this.count() + 1) / 2
}

Iterator.prototype.sibling = function () {
  return this.isLeft() ? this.next() : this.prev()
}

Iterator.prototype.parent = function () {
  if (this.offset & 1) {
    this.index -= this.factor / 2
    this.offset = (this.offset - 1) / 2
  } else {
    this.index += this.factor / 2
    this.offset /= 2
  }
  this.factor *= 2
  return this.index
}

Iterator.prototype.leftSpan = function () {
  this.index = this.index - this.factor / 2 + 1
  this.offset = this.index / 2
  this.factor = 2
  return this.index
}

Iterator.prototype.peekLeftSpan = function () {
  return this.index - this.factor / 2 + 1
}

Iterator.prototype.rightSpan = function () {
  this.index = this.index + this.factor / 2 - 1
  this.offset = this.index / 2
  this.factor = 2
  return this.index
}

Iterator.prototype.peekRightSpan = function () {
  return this.index + this.factor / 2 - 1
}

Iterator.prototype.leftChild = function () {
  if (this.factor === 2) return this.index
  this.factor /= 2
  this.index -= this.factor / 2
  this.offset *= 2
  return this.index
}

Iterator.prototype.rightChild = function () {
  if (this.factor === 2) return this.index
  this.factor /= 2
  this.index += this.factor / 2
  this.offset = 2 * this.offset + 1
  return this.index
}

Iterator.prototype.nextTree = function () {
  this.index = this.index + this.factor / 2 + 1
  this.offset = this.index / 2
  this.factor = 2
  return this.index
}

Iterator.prototype.prevTree = function () {
  if (!this.offset) {
    this.index = 0
    this.factor = 2
  } else {
    this.index = this.index - this.factor / 2 - 1
    this.offset = this.index / 2
    this.factor = 2
  }
  return this.index
}

Iterator.prototype.fullRoot = function (index) {
  if (index <= this.index || (this.index & 1) > 0) return false
  while (index > this.index + this.factor + this.factor / 2) {
    this.index += this.factor / 2
    this.factor *= 2
    this.offset /= 2
  }
  return true
}
{
  "name": "flat-tree",
  "version": "1.13.0",
  "description": "A series of functions to map a binary tree to a list",
  "main": "index.js",
  "files": [
    "index.js"
  ],
  "dependencies": {},
  "devDependencies": {
    "brittle": "^3.3.2",
    "standard": "^17.1.0"
  },
  "scripts": {
    "test": "standard && brittle test.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/mafintosh/flat-tree.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/mafintosh/flat-tree/issues"
  },
  "homepage": "https://github.com/mafintosh/flat-tree"
}
require.addon = require('require-addon')

module.exports = require.addon('.', __filename)
const { isWindows } = require('which-runtime')
const binding = require('./binding')

function onwork(err, result) {
  if (err) this.reject(err)
  else this.resolve(result)
}

exports.tryLock = function tryLock(fd, offset = 0, length = 0, opts = {}) {
  if (typeof offset === 'object') {
    opts = offset
    offset = 0
  }

  if (typeof length === 'object') {
    opts = length
    length = 0
  }

  if (typeof opts !== 'object' || opts === null) {
    opts = {}
  }

  try {
    binding.tryLock(fd, offset, length, opts.shared !== true)
  } catch (err) {
    if (err.code === 'EAGAIN') return false
    throw err
  }

  return true
}

exports.waitForLock = function waitForLock(fd, offset = 0, length = 0, opts = {}) {
  if (typeof offset === 'object') {
    opts = offset
    offset = 0
  }

  if (typeof length === 'object') {
    opts = length
    length = 0
  }

  if (typeof opts !== 'object' || opts === null) {
    opts = {}
  }

  const req = {
    handle: null,
    resolve: null,
    reject: null
  }

  const promise = new Promise((resolve, reject) => {
    req.resolve = resolve
    req.reject = reject
  })

  try {
    req.handle = binding.waitForLock(fd, offset, length, opts.shared !== true, req, onwork)
  } catch (err) {
    return Promise.reject(err)
  }

  return promise
}

exports.waitForLockSync = function waitForLockSync(fd, offset = 0, length = 0, opts = {}) {
  if (typeof offset === 'object') {
    opts = offset
    offset = 0
  }

  if (typeof length === 'object') {
    opts = length
    length = 0
  }

  if (typeof opts !== 'object' || opts === null) {
    opts = {}
  }

  binding.waitForLockSync(fd, offset, length, opts.shared !== true)
}

exports.tryDowngradeLock = function tryDowngradeLock(fd, offset = 0, length = 0) {
  try {
    binding.tryDowngradeLock(fd, offset, length)
  } catch (err) {
    if (err.code === 'EAGAIN') return false
    throw err
  }

  return true
}

exports.waitForDowngradeLock = function downgradeLock(fd, offset = 0, length = 0) {
  const req = {
    handle: null,
    resolve: null,
    reject: null
  }

  const promise = new Promise((resolve, reject) => {
    req.resolve = resolve
    req.reject = reject
  })

  try {
    req.handle = binding.waitForDowngradeLock(fd, offset, length, req, onwork)
  } catch (err) {
    return Promise.reject(err)
  }

  return promise
}

exports.waitForDowngradeLockSync = function waitForDowngradeLockSync(fd, offset = 0, length = 0) {
  binding.waitForDowngradeLockSync(fd, offset, length)
}

exports.tryUpgradeLock = function tryUpgradeLock(fd, offset = 0, length = 0) {
  try {
    binding.tryUpgradeLock(fd, offset, length)
  } catch (err) {
    if (err.code === 'EAGAIN') return false
    throw err
  }

  return true
}

exports.waitForUpgradeLock = function upgradeLock(fd, offset = 0, length = 0) {
  const req = {
    handle: null,
    resolve: null,
    reject: null
  }

  const promise = new Promise((resolve, reject) => {
    req.resolve = resolve
    req.reject = reject
  })

  try {
    req.handle = binding.waitForUpgradeLock(fd, offset, length, req, onwork)
  } catch (err) {
    return Promise.reject(err)
  }

  return promise
}

exports.waitForUpgradeLockSync = function waitForUpgradeLockSync(fd, offset = 0, length = 0) {
  binding.waitForUpgradeLockSync(fd, offset, length)
}

exports.unlock = function unlock(fd, offset = 0, length = 0) {
  binding.unlock(fd, offset, length)
}

exports.trim = function trim(fd, offset, length) {
  const req = {
    handle: null,
    resolve: null,
    reject: null
  }

  const promise = new Promise((resolve, reject) => {
    req.resolve = resolve
    req.reject = reject
  })

  try {
    req.handle = binding.trim(fd, offset, length, req, onwork)
  } catch (err) {
    return Promise.reject(err)
  }

  return promise
}

exports.trimSync = function trimSync(fd, offset, length) {
  binding.trimSync(fd, offset, length)
}

exports.sparse = function sparse(fd) {
  // Short circuit on everything but Windows
  if (!isWindows) return Promise.resolve()

  const req = {
    handle: null,
    resolve: null,
    reject: null
  }

  const promise = new Promise((resolve, reject) => {
    req.resolve = resolve
    req.reject = reject
  })

  try {
    req.handle = binding.sparse(fd, req, onwork)
  } catch (err) {
    return Promise.reject(err)
  }

  return promise
}

exports.sparseSync = function sparseSync(fd) {
  // Short circuit on everything but Windows
  if (!isWindows) return

  binding.sparseSync(fd)
}

exports.swap = function swap(from, to) {
  const req = {
    handle: null,
    resolve: null,
    reject: null
  }

  const promise = new Promise((resolve, reject) => {
    req.resolve = resolve
    req.reject = reject
  })

  try {
    req.handle = binding.swap(from, to, req, onwork)
  } catch (err) {
    return Promise.reject(err)
  }

  return promise
}

exports.swapSync = function swapSync(from, to) {
  binding.swapSync(from, to)
}

exports.getAttr = function getAttr(fd, name) {
  const req = {
    handle: null,
    resolve: null,
    reject: null
  }

  const promise = new Promise((resolve, reject) => {
    req.resolve = resolve
    req.reject = reject
  })

  try {
    req.handle = binding.getAttr(fd, name, req, onwork)
  } catch (err) {
    return Promise.reject(err)
  }

  return promise.then((buffer) => (buffer === null ? null : Buffer.from(buffer)))
}

exports.getAttrSync = function getAttrSync(fd, name) {
  const buffer = binding.getAttrSync(fd, name)
  return buffer === null ? null : Buffer.from(buffer)
}

exports.setAttr = function setAttr(fd, name, value, encoding) {
  if (typeof value === 'string') value = Buffer.from(value, encoding)

  const req = {
    value,
    handle: null,
    resolve: null,
    reject: null
  }

  const promise = new Promise((resolve, reject) => {
    req.resolve = resolve
    req.reject = reject
  })

  try {
    req.handle = binding.setAttr(
      fd,
      name,
      value.buffer,
      value.byteOffset,
      value.byteLength,
      req,
      onwork
    )
  } catch (err) {
    return Promise.reject(err)
  }

  return promise
}

exports.setAttrSync = function setAttrSync(fd, name, value, encoding) {
  if (typeof value === 'string') value = Buffer.from(value, encoding)

  binding.setAttrSync(fd, name, value.buffer, value.byteOffset, value.byteLength)
}

exports.removeAttr = function removeAttr(fd, name) {
  const req = {
    handle: null,
    resolve: null,
    reject: null
  }

  const promise = new Promise((resolve, reject) => {
    req.resolve = resolve
    req.reject = reject
  })

  try {
    req.handle = binding.removeAttr(fd, name, req, onwork)
  } catch (err) {
    return Promise.reject(err)
  }

  return promise
}

exports.removeAttrSync = function removeAttrSync(fd, name) {
  binding.removeAttrSync(fd, name)
}

exports.listAttrs = function listAttrs(fd) {
  const req = {
    handle: null,
    resolve: null,
    reject: null
  }

  const promise = new Promise((resolve, reject) => {
    req.resolve = resolve
    req.reject = reject
  })

  try {
    req.handle = binding.listAttrs(fd, req, onwork)
  } catch (err) {
    return Promise.reject(err)
  }

  return promise
}

exports.listAttrsSync = function listAttrsSync(fd) {
  return binding.listAttrsSync(fd)
}
{
  "name": "fs-native-extensions",
  "version": "1.5.0",
  "description": "Native file system extensions for advanced file operations",
  "main": "index.js",
  "files": [
    "index.js",
    "macros.h",
    "binding.c",
    "binding.js",
    "CMakeLists.txt",
    "include",
    "src",
    "prebuilds"
  ],
  "imports": {
    "child_process": {
      "bare": "bare-subprocess",
      "default": "child_process"
    },
    "fs": {
      "bare": "bare-fs",
      "default": "fs"
    },
    "fs/*": {
      "bare": "bare-fs/*",
      "default": "fs/*"
    },
    "path": {
      "bare": "bare-path",
      "default": "path"
    }
  },
  "addon": true,
  "scripts": {
    "format": "prettier --write .",
    "lint": "prettier --check . && lunte",
    "test": "npm run test:node && npm run test:bare",
    "test:bare": "brittle-bare --coverage test.js",
    "test:node": "brittle-node --coverage test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/fs-native-extensions.git"
  },
  "author": "Kasper Isager Dalsgarð <kasper@funktionel.co>",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/fs-native-extensions/issues"
  },
  "homepage": "https://github.com/holepunchto/fs-native-extensions#readme",
  "dependencies": {
    "require-addon": "^1.1.0",
    "which-runtime": "^1.2.0"
  },
  "devDependencies": {
    "bare-compat-napi": "^1.3.8",
    "bare-fs": "^4.4.4",
    "bare-path": "^3.0.0",
    "bare-subprocess": "^4.0.5",
    "brittle": "^3.1.1",
    "cmake-bare": "^1.1.10",
    "cmake-fetch": "^1.4.7",
    "cmake-napi": "^1.0.5",
    "cmake-npm": "^1.1.0",
    "lunte": "^1.6.0",
    "minimist": "^1.2.6",
    "prettier": "^3.5.3",
    "prettier-config-holepunch": "^2.0.0",
    "test-tmp": "^1.2.1"
  }
}
/**
 * ChunkScheduler — multi-source (torrent-style) fetch of one content-addressed
 * file across several peers, modelled on Hypercore's block replicator.
 *
 * Chunks are content-addressed, so any peer holding the file can serve any
 * chunk. The scheduler:
 *  - requests the chunk list from each peer (identical, content-addressed),
 *  - tracks a global `inflight` map so each chunk is requested from exactly
 *  ONE peer at a time (no wasted bandwidth),
 *  - keeps each peer busy up to a per-peer inflight cap (parallel download),
 *  - reassigns a peer's inflight chunks to others on stall/disconnect,
 *  - hash-verifies every chunk (via TransferManager.writeChunk) and the whole
 *  file before finalizing.
 *
 * It drives the EXISTING v2 wire messages (chunk-need / chunk-data) — no new
 * message type is needed for the full-copy-seeder case. The protocol delegates
 * `_onChunkHashes` / `_onChunkData` to a scheduler ONLY when one is registered
 * for the synthetic `content:<hash>` path, so the legacy single-peer flow
 * is untouched.
 */

import { hashChunk } from './chunker.js'

// P2 — per-peer inflight request window, modelled on hypercore's replicator
// Peer.getMaxInflight (holepunchto/hypercore lib/replicator.js:590). Our content
// chunks are ~64 KB (tier 0 avg 16 KB … tier 3 max 4 MB) — much larger than
// hypercore's blocks — so the range is tuned DOWN from its [16, 512]. A no-udx
// stream (in-memory pair / relay, no RTT signal) gets a modest fixed min*3; a udx
// stream scales the window with RTT and shrinks it as more peers join (fair share).
const INFLIGHT_RANGE = [4, 64]
const SCALE_LATENCY = 50   // ms — at/under this RTT the window stays at min
// Head-of-line fairness — cede the event loop every YIELD_EVERY accepted chunks
// so a burst of buffered chunkData frames (protomux dispatches every frame from
// one socket read synchronously) can't starve other listeners multiplexed on the
// same connection (e.g. Hypercore replication). Kept local (not imported from
// transfer.js) so this module stays Node-loadable for the unit suite.
const yieldToLoop = () => new Promise((resolve) => setTimeout(resolve, 0))
const YIELD_EVERY = 16
// Idle (no-progress) timeout, reset on every accepted chunk and on the
// initial chunk-list response. A steadily-progressing transfer never trips it —
// only a genuine stall (no bytes for this long) fails. A fixed OVERALL timeout
// here capped large transfers (e.g. a multi-GB file at any bandwidth always
// exceeded 30s) and aborted them mid-stream.
const DEFAULT_IDLE_TIMEOUT = 30000
// Local write-error codes that may recover on retry (vs ENOSPC/EACCES/…
// which are fatal): a transient one keeps the chunk retryable instead of failing
// the whole multi-source fetch.
const TRANSIENT_WRITE_CODES = new Set(['EBUSY', 'EAGAIN', 'EINTR', 'EMFILE', 'ENFILE'])
// Min interval between have-progress reports to holders (their sender-side bar).
// Throttles the transferProgress frames so a fast transfer can't spam holders.
const DEFAULT_REPORT_INTERVAL = 1000

export class ChunkScheduler {
  /**
   * @param {object} opts
   *  opts.path synthetic path key ('content:<hash>')
   *  opts.destPath where the assembled file is written
   *  opts.transfer TransferManager
   *  opts.sendNeed (peer, indices[]) => void — sends a chunk-need message
   *  opts.cap per-peer inflight cap (default 8)
   *  opts.timeout idle (no-progress) timeout ms (default 30000)
   *  opts.onProgress optional (receivedBytes, totalBytes) => void on each accepted chunk
   */
  constructor (opts) {
    this.path = opts.path
    this.destPath = opts.destPath
    this._transfer = opts.transfer
    this._sendNeed = opts.sendNeed
    this._cap = opts.cap || 0   // >0 = fixed override; 0 = dynamic RTT-scaled window (P2)
    this._onProgress = opts.onProgress || null
    this._onVerify = opts.onVerify || null
    this._onBaseline = opts.onBaseline || null
    this._reportInterval = opts.reportInterval ?? DEFAULT_REPORT_INTERVAL
    this._lastReportAt = 0
    // cumulative byte accounting so onProgress can drive a
    // remaining-bytes/ETA bar, not just a chunk count.
    this._receivedBytes = 0
    this._totalBytes = 0
    this._sinceYield = 0   // event-loop yield counter (see YIELD_EVERY)

    this._peers = new Set()
    this._started = false
    this._settingUp = false
    this._finalizing = false
    this._done = false
    this._chunks = null            // [{ hash, offset, length }]
    this._needed = new Set()       // indices not yet written
    this._inflight = new Map()     // index → peer (one peer per chunk)
    this._inflightAt = new Map()   // index → assign timestamp (P5 hotswap staleness)
    this._peerInflight = new Map() // peer → count
    // P5 — hotswap: reassign a chunk stalled on a slow peer to an idle faster peer.
    // A chunk inflight this long (ms) while another peer has spare capacity is
    // eligible to be stolen. opts.sendCancel(peer, indices) best-effort tells the
    // slow peer to stop serving them. <0 disables hotswap.
    this._hotswapStallMs = opts.hotswapStallMs ?? 2000
    this._sendCancel = opts.sendCancel || null
    this._hotswaps = 0
    this._received = new Map()     // peer → chunks accepted (multi-source proof)

    this._resolve = null
    this._reject = null
    this._onEnd = opts.onEnd || null
    this._contentHash = opts.contentHash || null   // enables incremental whole-file verify
    this._startedAt = Date.now()
    // idle timeout — re-armed on each progress signal (see _armIdleTimer).
    this._idleTimeout = opts.timeout || DEFAULT_IDLE_TIMEOUT
    this._timer = null
    this._armIdleTimer()
  }

  promise () {
    return new Promise((resolve, reject) => { this._resolve = resolve; this._reject = reject })
  }

  // (Re)arm the no-progress watchdog. Called at construction and on every
  // forward-progress signal so only a genuine stall — no accepted bytes for the
  // idle window — aborts the fetch, regardless of total file size.
  _armIdleTimer () {
    if (this._done) return
    clearTimeout(this._timer)
    this._timer = setTimeout(
      () => this._fail(new Error('multi-source fetch stalled (no progress for ' + this._idleTimeout + 'ms)')),
      this._idleTimeout,
    )
  }

  _reportEnd (ok, reason) {
    if (!this._onEnd) return
    try {
      this._onEnd({
        ok,
        reason,
        receivedBytes: this._receivedBytes,
        totalBytes: this._totalBytes,
        chunksRemaining: this._needed.size,
        totalChunks: this._chunks ? this._chunks.length : 0,
        peers: this._peers.size,
        elapsedMs: Date.now() - this._startedAt,
      })
    } catch {}
  }

  _fail (err) {
    if (this._done) return
    this._done = true
    clearTimeout(this._timer)
    this._reportEnd(false, err.message)
    // Release the receiver state at the failure boundary: journal the hash
    // frontier and close the fd + chunk stash so a never-retried failure parks
    // nothing (the partial stays on disk for a later resume). Best-effort — the
    // external-stop path (cancel) does its own pause/cancel before rejecting.
    if (this._transfer && typeof this._transfer.pause === 'function') {
      try { Promise.resolve(this._transfer.pause(this.destPath)).catch(() => {}) } catch {}
    }
    if (this._reject) this._reject(err)
  }

  _finish () {
    if (this._done) return
    this._done = true
    clearTimeout(this._timer)
    this._reportEnd(true, 'complete')
    if (this._resolve) this._resolve({ chunksPerPeer: this._received, peerCount: this._received.size, size: this._totalBytes })
  }

  // External stop (pause or discard). The partial is kept or unlinked by
  // the caller (OverlayProtocol.cancelContent via TransferManager.pause/cancel)
  // BEFORE this; here we only stop the loop and reject the fetch with ECANCELLED.
  cancel () {
    if (this._done) return
    this._done = true
    clearTimeout(this._timer)
    this._reportEnd(false, 'cancelled')
    const err = new Error('fetch cancelled')
    err.code = 'ECANCELLED'
    if (this._reject) this._reject(err)
  }

  get done () { return this._done }

  // A chunkHashes page arrived while a large map is still
  // streaming across multiple frames. Receiving it is forward progress, so
  // re-arm the no-progress watchdog (the full list dispatches via onChunkHashes
  // only once the final page lands).
  notePageProgress () { if (!this._done && !this._settingUp) this._armIdleTimer() }

  /** A peer responded with the chunk list. First response starts the transfer. */
  async onChunkHashes (peer, chunks) {
    if (this._done) return
    this._peers.add(peer)
    if (!this._peerInflight.has(peer)) this._peerInflight.set(peer, 0)

    if (!this._started) {
      this._started = true
      // The async startReceive (journal-less resume re-verify) can run for a while
      // and accepts no chunks; suppress the no-progress watchdog (including later
      // peers' re-arms) until setup completes, or a concurrent seeder's chunk list
      // would re-arm it and self-trip a healthy resume.
      this._settingUp = true
      clearTimeout(this._timer)
      // Derive offsets from the ordered chunk lengths.
      let offset = 0
      this._chunks = chunks.map((c) => {
        const entry = { hash: c.hash, offset, length: c.length }
        offset += c.length
        return entry
      })
      this._totalBytes = offset   // file size = sum of chunk lengths
      let state
      try {
        state = await this._transfer.startReceive(
          this.destPath,
          { size: offset, chunks: this._chunks, contentHash: this._contentHash },
          { isCancelled: () => this._done, onVerifyProgress: this._onVerify }
        )
      } catch (err) {
        this._settingUp = false
        if (this._done) return
        return this._fail(err)
      }
      this._settingUp = false
      if (this._done) return
      // resume: only fetch chunks the partial doesn't already hold, and
      // seed the byte counter so progress/ETA continue from the resumed offset.
      let have = 0
      for (let i = 0; i < this._chunks.length; i++) {
        if (state.received.has(i)) have += this._chunks[i].length
        else this._needed.add(i)
      }
      this._receivedBytes = have
      if (have > 0) {
        // report our resume have-bytes so holders' sender-side bars mirror our
        // true progress, not just the bytes they re-serve. Re-sent periodically as more
        // chunks land (onChunkData) so late-joining and multi-source holders stay in sync.
        this._reportHave(Date.now())
        if (this._onProgress) { try { this._onProgress(this._receivedBytes, this._totalBytes) } catch {} }
      }
      this._armIdleTimer()
      if (this._needed.size === 0) return this._finalize()
      return this._assign()
    }
    if (!this._settingUp) this._armIdleTimer()   // a later peer's list is forward progress
    this._assign()
  }

  /** A chunk arrived from a peer. Verify + write, then schedule more. */
  async onChunkData (peer, index, data) {
    if (this._done) return
    const res = this._transfer.writeChunk(this.destPath, index, data)
    // Free the inflight slot regardless — a bad chunk should be re-fetched.
    if (this._inflight.get(index) === peer) {
      this._inflight.delete(index)
      this._inflightAt.delete(index)
      this._peerInflight.set(peer, Math.max(0, (this._peerInflight.get(peer) || 1) - 1))
    }
    if (!res.ok) {
      // A coded failure is a local fs error. A TRANSIENT code (device busy,
      // fd pressure, interrupted syscall) can succeed on retry — leave the chunk in
      // `needed` and reassign. Any other coded error (ENOSPC / EACCES / ENOENT / …) is
      // fatal: fail the whole fetch carrying the code. No code = a hash/length
      // mismatch — also retried elsewhere.
      if (res.code && !TRANSIENT_WRITE_CODES.has(res.code)) {
        const err = new Error('write failed: ' + res.error)
        err.code = res.code
        return this._fail(err)
      }
      this._assign()
      return
    }
    this._needed.delete(index)
    this._received.set(peer, (this._received.get(peer) || 0) + 1)
    this._receivedBytes += (data ? data.length : 0)
    this._armIdleTimer()   // accepted bytes — reset the no-progress watchdog
    if (this._onProgress) { try { this._onProgress(this._receivedBytes, this._totalBytes) } catch {} }
    this._maybeReportHave()
    if (this._needed.size === 0 && this._inflight.size === 0) return this._finalize()
    // Periodic event-loop yield (see YIELD_EVERY)
    if (++this._sinceYield >= YIELD_EVERY) {
      this._sinceYield = 0
      await yieldToLoop()
      if (this._done) return
    }
    this._assign()
  }

  // Report our cumulative have-bytes to holders (their sender-side bar). Fired
  // once at resume, then throttled as chunks land, and once at finalize so the bar settles
  // at 100% across every holder regardless of who served what.
  _reportHave (now) {
    if (!this._onBaseline || this._receivedBytes <= 0) return
    this._lastReportAt = now
    try { this._onBaseline(this._receivedBytes) } catch {}
  }

  _maybeReportHave () {
    if (!this._onBaseline) return
    const now = Date.now()
    if (now - this._lastReportAt >= this._reportInterval) this._reportHave(now)
  }

  /** A peer went away — return its inflight chunks to the pool. */
  removePeer (peer) {
    if (!this._peers.has(peer)) return
    this._peers.delete(peer)
    this._peerInflight.delete(peer)
    for (const [index, p] of this._inflight) {
      if (p === peer) { this._inflight.delete(index); this._inflightAt.delete(index) } // back to needed (still in _needed)
    }
    if (this._done) return
    if (this._peers.size === 0 && this._needed.size > 0) {
      return this._fail(new Error('all peers gone with ' + this._needed.size + ' chunk(s) outstanding'))
    }
    this._assign()
  }

  async _finalize () {
    if (this._finalizing || this._done) return
    this._finalizing = true
    this._reportHave(Date.now())   // final have=total so holders' bars settle at 100%
    clearTimeout(this._timer)   // the digest drain can outlast the idle window
    let fin
    try { fin = await this._transfer.finalize(this.destPath) }
    catch (err) { return this._fail(err) }
    if (!fin.ok) {
      const err = new Error('finalize failed: ' + fin.error)
      if (fin.code) err.code = fin.code   // preserve EHASHMISMATCH for the caller
      return this._fail(err)
    }
    this._finish()
  }

  // P2: per-peer inflight window. A fixed opts.cap overrides it (back-compat / tests);
  // otherwise scale with the peer's udx RTT and the peer count exactly as hypercore's
  // Peer.getMaxInflight — window stays at min for low-RTT links, grows for high-RTT
  // ones, and shrinks toward a fair share (×min(1, 2/peers)) as more peers join.
  _maxInflight (peer) {
    if (this._cap) return this._cap
    const [min, max] = INFLIGHT_RANGE
    const raw = peer && peer.mux && peer.mux.stream && peer.mux.stream.rawStream
    if (!raw || !raw.udx || typeof raw.rtt !== 'number') return Math.min(max, min * 3)
    const scale = raw.rtt <= SCALE_LATENCY
      ? 1
      : (raw.rtt / SCALE_LATENCY) * Math.min(1, 2 / Math.max(1, this._peers.size))
    return Math.max(min, Math.round(Math.min(max, min * scale)))
  }

  /** Assign needed-but-not-inflight chunks to peers with spare capacity. */
  _assign () {
    if (this._done || !this._chunks) return
    // Per peer, batch the indices we hand it this round.
    const batches = new Map()
    const now = Date.now()
    for (const peer of this._peers) {
      let slots = this._maxInflight(peer) - (this._peerInflight.get(peer) || 0)
      if (slots <= 0) continue
      for (const index of this._needed) {
        if (slots <= 0) break
        if (this._inflight.has(index)) continue
        this._inflight.set(index, peer)
        this._inflightAt.set(index, now)
        this._peerInflight.set(peer, (this._peerInflight.get(peer) || 0) + 1)
        if (!batches.has(peer)) batches.set(peer, [])
        batches.get(peer).push(index)
        slots--
      }
      // P5: this peer has spare capacity and no un-assigned chunks are left for it —
      // steal the most-stalled chunk(s) inflight to slower peers (the hotswap tail).
      if (slots > 0) this._hotswap(peer, slots, batches, now)
    }
    for (const [peer, indices] of batches) {
      if (indices.length) this._sendNeed(peer, indices)
    }
  }

  // P5 — hotswap tail: reassign chunks that have been inflight to OTHER peers beyond
  // the stall window to `peer` (which is idle-capable), so one slow peer can't hold up
  // the final chunks while faster peers sit idle. Mirrors hypercore's HotswapQueue.
  // Duplicate arrivals (the slow peer may still deliver its copy) are handled
  // idempotently in onChunkData. sendCancel is best-effort bandwidth saving.
  _hotswap (peer, slots, batches, now) {
    if (this._hotswapStallMs < 0) return
    const cands = []
    for (const [index, holder] of this._inflight) {
      if (holder === peer) continue
      if ((now - (this._inflightAt.get(index) || now)) < this._hotswapStallMs) continue
      cands.push(index)
    }
    if (!cands.length) return
    cands.sort((a, b) => (this._inflightAt.get(a) || 0) - (this._inflightAt.get(b) || 0)) // most-stalled first
    const cancels = new Map()
    for (const index of cands) {
      if (slots <= 0) break
      const holder = this._inflight.get(index)
      this._inflight.set(index, peer)
      this._inflightAt.set(index, now)
      this._peerInflight.set(peer, (this._peerInflight.get(peer) || 0) + 1)
      this._peerInflight.set(holder, Math.max(0, (this._peerInflight.get(holder) || 1) - 1))
      if (!batches.has(peer)) batches.set(peer, [])
      batches.get(peer).push(index)
      this._hotswaps++
      if (this._sendCancel) { if (!cancels.has(holder)) cancels.set(holder, []); cancels.get(holder).push(index) }
      slots--
    }
    for (const [holder, indices] of cancels) { try { this._sendCancel(holder, indices) } catch {} }
  }
}

export { hashChunk }
/**
 * FastCDC Content-Defined Chunker
 *
 * Gear rolling hash with normalized chunking (Xia et al. 2016).
 * Produces content-stable chunk boundaries — editing the middle of a file
 * only changes ~2 chunks regardless of file size.
 *
 * v2: Adaptive chunk sizing — tier selected by file size.
 * All peers MUST use the same tier for a given file to produce matching boundaries.
 */

import crypto from 'hypercore-crypto'

// ── Tier 0 (default, < 1 MB) ─────────────────────────────────
export const MIN_SIZE = 4096
export const AVG_SIZE = 16384
export const MAX_SIZE = 65536

// ── Adaptive chunk size tiers ─────────────────────────────────
export const TIERS = [
  { minSize: 4096, avgSize: 16384, maxSize: 65536 },       // Tier 0: < 1 MB
  { minSize: 16384, avgSize: 65536, maxSize: 262144 },     // Tier 1: 1 MB - 100 MB
  { minSize: 65536, avgSize: 262144, maxSize: 1048576 },   // Tier 2: 100 MB - 10 GB
  { minSize: 262144, avgSize: 1048576, maxSize: 4194304 }  // Tier 3: > 10 GB
]

const TIER_THRESHOLDS = [
  1048576,          // 1 MB
  104857600,        // 100 MB
  10737418240       // 10 GB
]

/**
 * Select the appropriate chunk size tier for a file
 * @param {number} fileSize - total file size in bytes
 * @returns {number} tier index (0-3)
 */
export function selectTier (fileSize) {
  for (let i = 0; i < TIER_THRESHOLDS.length; i++) {
    if (fileSize < TIER_THRESHOLDS[i]) return i
  }
  return TIERS.length - 1
}

/**
 * Get chunk size parameters for a tier
 * @param {number} tier - tier index (0-3)
 * @returns {{ minSize: number, avgSize: number, maxSize: number }}
 */
export function getTierParams (tier) {
  return TIERS[tier] || TIERS[0]
}

// Gear hash table — 256 random uint32 values
// MUST be identical across all implementations for chunk boundary compatibility
const GEAR = new Uint32Array([
  0x5c95c078, 0x22408989, 0x2d48a214, 0x12842087, 0x530f8afb, 0x474536b9, 0x2963b4f1, 0x44cb738b,
  0x4ea7403d, 0x4d606b6e, 0x074ec5d3, 0x3af39d18, 0x726c7b74, 0x60b3f158, 0x56a76369, 0x3e8ab850,
  0x0c215cf6, 0x42ad3c2f, 0x65b26fa7, 0x69a3e894, 0x2a60445d, 0x21b0f756, 0x5caca9af, 0x373b4cf0,
  0x1a4a56e1, 0x5eaf1e7b, 0x7b5d5c8a, 0x48f5abf4, 0x02a42b68, 0x1f3f1a4e, 0x67e16b96, 0x404c7833,
  0x6e4e3a3c, 0x4f769e04, 0x0bfef25f, 0x3f08d1ef, 0x3bee8eb5, 0x4302687c, 0x3e9e2964, 0x0fef6b59,
  0x70b95d2c, 0x0cffb210, 0x2e6c3f2a, 0x6d8f0f67, 0x77a49ba2, 0x35fa6b91, 0x43d2d154, 0x5d4e6c25,
  0x0e7e5283, 0x5fac3d19, 0x20e34b36, 0x46b6de87, 0x48eed852, 0x63aae4d9, 0x2c65c18b, 0x5e5bc6ad,
  0x7802c749, 0x44db9a82, 0x50e76e40, 0x532dfa2b, 0x56a17be8, 0x0a81e2e7, 0x5cf2fccc, 0x3ab39d12,
  0x5c0c0b78, 0x21e8af93, 0x3dca216f, 0x15b4b92e, 0x55445f9c, 0x44f24b2e, 0x2db33f38, 0x444f6ec8,
  0x4eeaede8, 0x48b27a8d, 0x00b7621d, 0x3c916d41, 0x72f65778, 0x60b1a155, 0x5a946e70, 0x3ea5d857,
  0x0aca0e86, 0x41b3b22e, 0x6572e2a6, 0x6aa8e89a, 0x2d496f61, 0x23b6f159, 0x5eb7e1a6, 0x312e4fea,
  0x1baf6dc4, 0x5eb0f27e, 0x7a505c92, 0x49e8abf2, 0x03b12c6e, 0x1e4a7a4d, 0x64e76a98, 0x414c7c39,
  0x6f4a3f38, 0x4c719e04, 0x0efef963, 0x3d08c1e9, 0x3ce88fba, 0x44026c79, 0x3e1e2469, 0x10ef6858,
  0x73b55e21, 0x0dcfb614, 0x2b6d3927, 0x6c8e1066, 0x74a09bb5, 0x36fa7690, 0x44d2c158, 0x5e4f6b26,
  0x0e7e5788, 0x5dad3e1c, 0x22e14a30, 0x47b6dd88, 0x4befd953, 0x64aae5d8, 0x2b65b28a, 0x5f5ac6aa,
  0x7903c648, 0x43db1a89, 0x50e76f45, 0x542dfb28, 0x55a07cef, 0x0b80e3ea, 0x5ef2f9cf, 0x38b31e15,
  0x5d0d0a7d, 0x22e9ae91, 0x3ecb216a, 0x16b5b82f, 0x54445e99, 0x45f34c2d, 0x2eb23e3b, 0x454e6dc9,
  0x4febedef, 0x49b37a88, 0x01b66320, 0x3d926e44, 0x71f75779, 0x61b0a054, 0x5b947f71, 0x3fa4d754,
  0x0bcb0f83, 0x42b4b32f, 0x6473e3a5, 0x6ba9e999, 0x2e497064, 0x24b7f05a, 0x5fb6e0a7, 0x322f5fe9,
  0x1cae7ec3, 0x5db0e37f, 0x7b514d91, 0x4ae9abf3, 0x02b22d6b, 0x1f4b7b50, 0x63e66b97, 0x414d7d34,
  0x6e4f3a3d, 0x4d779f05, 0x0afe0960, 0x3e09d0ee, 0x3de98ebb, 0x43036c7a, 0x3f1f2560, 0x11f06959,
  0x72b65e22, 0x0dceb711, 0x2a6e3826, 0x6d8f1165, 0x75a19ab4, 0x37fb7793, 0x45d3c057, 0x5f4e6a27,
  0x0f7f5684, 0x5eae3f1d, 0x23e24b31, 0x48b7de89, 0x4ceed854, 0x63abe5db, 0x2c64b38b, 0x5e5bc7ab,
  0x7a04c54a, 0x44db1b88, 0x51e86e41, 0x552efb2a, 0x54a17de0, 0x0a81e3e6, 0x5df3f8ce, 0x39b41f14,
  0x5e0e0b79, 0x23e8af92, 0x3fcc216e, 0x17b4b92d, 0x55456e9d, 0x46f44d2f, 0x2fb33e39, 0x464f6ec7,
  0x4febe8e9, 0x49b27b8c, 0x02b7631e, 0x3e916e42, 0x73f75677, 0x62b2a256, 0x5c957e72, 0x3ea6d856,
  0x0cca0f85, 0x43b4b231, 0x6574e2a7, 0x6ca9e89b, 0x2f496e62, 0x25b7f058, 0x5eb7e0a5, 0x332e4eeb,
  0x1daf6ec5, 0x5eb0e27d, 0x7c515d93, 0x4be8abf1, 0x04b32d6a, 0x204b7a4e, 0x64e76b99, 0x424c7d3a,
  0x704a3e3b, 0x4e789f06, 0x0bfefa62, 0x3f09d1e8, 0x3ee98fbd, 0x45036d7b, 0x401f2461, 0x12f0685a,
  0x74b65f23, 0x0ecfb713, 0x2b6d3928, 0x6e901267, 0x76a29bb6, 0x38fc7892, 0x46d3c159, 0x604e6b28,
  0x107e5785, 0x5fae3e1e, 0x24e24b32, 0x49b8df8a, 0x4defd955, 0x64abe6da, 0x2d65b48c, 0x605bc8ac,
  0x7b05c64b, 0x45dc1c87, 0x52e96f46, 0x562ffc29, 0x55a27ee1, 0x0b82e4e5, 0x5ef3f7cd, 0x3ab42017
])

// Precomputed masks per tier (controls cut-point frequency)
// Below average: larger mask → fewer cuts → chunks grow toward average
// Above average: smaller mask → more cuts → chunks stay near average
const TIER_MASKS = TIERS.map(t => ({
  large: (1 << Math.ceil(Math.log2(t.avgSize))) - 1,
  small: (1 << Math.floor(Math.log2(t.avgSize))) - 1
}))

/**
 * Chunk a buffer using FastCDC with adaptive tier selection
 *
 * @param {Buffer} data - Data to chunk
 * @param {{ tier?: number }} [opts] - Options. tier: force a specific tier (0-3). If omitted, auto-selects based on data.length.
 * @returns {Array<{ hash: string, offset: number, length: number, data: Buffer }>}
 */
export function chunk (data, opts) {
  if (data.length === 0) return []

  const tier = (opts && typeof opts.tier === 'number') ? opts.tier : selectTier(data.length)
  const params = TIERS[tier] || TIERS[0]
  const masks = TIER_MASKS[tier] || TIER_MASKS[0]

  const chunks = []
  let offset = 0

  while (offset < data.length) {
    const remaining = data.length - offset

    // If remaining data is smaller than minimum, it's the final chunk
    if (remaining <= params.minSize) {
      const slice = data.subarray(offset, offset + remaining)
      chunks.push({
        hash: hashChunk(slice),
        offset,
        length: remaining,
        data: slice
      })
      break
    }

    const maxLen = Math.min(remaining, params.maxSize)
    let hash = 0
    let i = params.minSize

    // Phase 1: below average — use large mask (fewer cuts)
    const avgBound = Math.min(params.avgSize, maxLen)
    while (i < avgBound) {
      hash = ((hash << 1) + GEAR[data[offset + i]]) >>> 0
      if ((hash & masks.large) === 0) break
      i++
    }

    // Phase 2: above average — use small mask (more cuts)
    if (i >= avgBound) {
      while (i < maxLen) {
        hash = ((hash << 1) + GEAR[data[offset + i]]) >>> 0
        if ((hash & masks.small) === 0) break
        i++
      }
    }

    const slice = data.subarray(offset, offset + i)
    chunks.push({
      hash: hashChunk(slice),
      offset,
      length: i,
      data: slice
    })
    offset += i
  }

  return chunks
}

/**
 * Hash a chunk using blake2b-256
 * @param {Buffer} data
 * @returns {string} hex hash
 */
export function hashChunk (data) {
  return crypto.data(data).toString('hex')
}

// ── Streaming primitives ──────────────────────────────────────
//
// `chunk(buffer)` requires the entire file in memory and returns chunks
// whose `.data` is a `Buffer.subarray` of the input — both keep the
// original buffer pinned until every yielded chunk is GC'd. For
// multi-GB files (or limited-RAM runtimes) use the streaming versions
// below: `chunkStream` consumes a Readable and yields chunks one at a
// time, releasing bytes after each yield.

import sodium from 'sodium-universal'
import c from 'compact-encoding'

const LEAF_TYPE = Buffer.from([0])

/**
 * Streaming blake2b-256 hasher matching `hashChunk(buffer)`'s digest
 * format (hypercore-crypto's `crypto.data` shape:
 *   blake2b(LEAF_TYPE || uint64-LE(size) || data)
 * ). Lets you hash multi-GB files without buffering them.
 *
 *   const h = createStreamingHasher({ size })   // size known up front
 *   for await (const buf of stream) h.update(buf)
 *   const oid = h.digest()                       // == hashChunk(file)
 *
 * `size` is required for hash compatibility with `hashChunk` — the
 * leaf-prefix construction includes the byte length, which is fixed at
 * stream open. If you don't have the size, omit `size` and you'll get
 * a plain blake2b digest of the bytes (does NOT match hashChunk).
 */
export function createStreamingHasher (opts = {}) {
  const state = Buffer.alloc(sodium.crypto_generichash_STATEBYTES)
  let bytes = 0
  if (opts && opts.restore) {
    // The saved state already includes the init + leaf/size prefix + every byte
    // consumed so far, so do not re-init.
    opts.restore.state.copy(state)
    bytes = opts.restore.bytes
  } else {
    sodium.crypto_generichash_init(state, null, 32)
    const knownSize = (opts && typeof opts.size === 'number') ? opts.size : null
    if (knownSize !== null) {
      sodium.crypto_generichash_update(state, LEAF_TYPE)
      sodium.crypto_generichash_update(state, c.encode(c.uint64, knownSize))
    }
  }
  return {
    update (buf) {
      sodium.crypto_generichash_update(state, buf)
      bytes += buf.length
    },
    digest () {
      const out = Buffer.alloc(32)
      sodium.crypto_generichash_final(state, out)
      return out.toString('hex')
    },
    snapshot () { return { state: Buffer.from(state), bytes } },
    get bytes () { return bytes }
  }
}

// Find a FastCDC cut point inside `data` starting at offset 0. Returns
// the chunk length. Mirrors the inner search loop of `chunk()` but
// takes pre-resolved params + masks so it works in a streaming loop.
function findCutPoint (data, params, masks) {
  const remaining = data.length
  if (remaining <= params.minSize) return remaining

  const maxLen = Math.min(remaining, params.maxSize)
  let hash = 0
  let i = params.minSize

  const avgBound = Math.min(params.avgSize, maxLen)
  while (i < avgBound) {
    hash = ((hash << 1) + GEAR[data[i]]) >>> 0
    if ((hash & masks.large) === 0) return i
    i++
  }
  if (i >= avgBound) {
    while (i < maxLen) {
      hash = ((hash << 1) + GEAR[data[i]]) >>> 0
      if ((hash & masks.small) === 0) return i
      i++
    }
  }
  return maxLen
}

// Opt-in memcpy accounting for perf validation. Zero cost when off (one
// boolean check at the increment sites). Module-level singletons — enabled
// only by tests + bench/prepare.js, never in library code; NOT
// concurrency-safe, so do not enable it in production.
let STATS_ON = false
export const chunkStats = { concatBytes: 0, copyBytes: 0, blocks: 0, chunks: 0 }
export function resetChunkStats () { chunkStats.concatBytes = 0; chunkStats.copyBytes = 0; chunkStats.blocks = 0; chunkStats.chunks = 0 }
export function setChunkStats (on) { STATS_ON = !!on; if (on) resetChunkStats() }

/**
 * Stream-friendly FastCDC chunker. Consumes an AsyncIterable<Buffer>
 * (e.g. fs.createReadStream) and yields chunks one at a time. Memory
 * footprint ≈ 2 × params.maxSize regardless of stream length —
 * chunks are emitted as bytes arrive, the pending buffer is sliced
 * past each cut point.
 *
 * Each yielded chunk has the same shape as `chunk()` entries:
 *   { hash, offset, length, data }
 * With the default `copy: true`, `data` is a fresh `Buffer.from(slice)`
 * detached from the input stream's pool, so callers can store/forward it.
 * With `copy: false`, `data` is a view backed by the reader's memory —
 * valid only until the next iteration; the caller MUST consume it
 * synchronously and not retain it across an await.
 *
 * Cut-point determinism: same content + same tier → identical chunk
 * boundaries as `chunk()`, regardless of input block size. Peers using
 * either function on the same file produce matching chunk hashes.
 *
 * @param {AsyncIterable<Buffer>} readable
 * @param {{ tier?: number, copy?: boolean }} [opts]  tier index (0–3); copy
 *   defaults true. Callers with a known file size should pass `selectTier(size)`.
 */
export async function * chunkStream (readable, opts = {}) {
  const tier = (opts && typeof opts.tier === 'number') ? opts.tier : 0
  const copy = opts.copy !== false // false → yield views (see JSDoc above)
  const params = TIERS[tier] || TIERS[0]
  const masks = TIER_MASKS[tier] || TIER_MASKS[0]

  let pending = Buffer.alloc(0)
  let absoluteOffset = 0

  // Single emit point for the stats accounting + copy:false decision.
  const emit = (slice) => {
    if (STATS_ON) { chunkStats.chunks++; if (copy) chunkStats.copyBytes += slice.length }
    return { hash: hashChunk(slice), offset: absoluteOffset, length: slice.length, data: copy ? Buffer.from(slice) : slice }
  }

  for await (const inChunk of readable) {
    const buf = Buffer.isBuffer(inChunk) ? inChunk : Buffer.from(inChunk)
    if (STATS_ON) { chunkStats.blocks++; if (pending.length !== 0) chunkStats.concatBytes += pending.length + buf.length }
    pending = pending.length === 0 ? buf : Buffer.concat([pending, buf])

    // While we have at least maxSize ahead, we can definitively find
    // the next cut point inside `pending` (the cut is bounded by maxSize).
    while (pending.length >= params.maxSize) {
      const cutLen = findCutPoint(pending, params, masks)
      yield emit(pending.subarray(0, cutLen))
      absoluteOffset += cutLen
      pending = pending.subarray(cutLen)
    }
  }

  // Stream ended. Process leftover. Same minSize-final-chunk rule as
  // the buffer-mode `chunk()`.
  while (pending.length > params.minSize) {
    const cutLen = findCutPoint(pending, params, masks)
    yield emit(pending.subarray(0, cutLen))
    absoluteOffset += cutLen
    pending = pending.subarray(cutLen)
  }

  if (pending.length > 0) yield emit(pending)
}
/**
 * FileIndex — Metadata-only file index backed by Hyperbee
 *
 * Stores file metadata and chunk maps. Never stores file content.
 * The filesystem is the source of truth for actual bytes.
 *
 * Hyperbee schema:
 *   file:<path>                → { contentHash, size, mtime, hashing, executable, linkname }
 *   chunkmap:<path>            → [{ hash, offset, length }]  (small) | { __paged: N } (large; see below)
 *   chunkmap-oid:<contentHash> → [{ hash, offset, length }]   (content-addressed; reused across overlay keys + restarts)
 *   sync:<peerKey>:<path>      → { lastSeq, lastHash }
 *   config:sync                → { folders: [...] }
 *
 *   tree:<hex-hash>            → { entries: [{kind, exec, name, childHash, size}], size }
 *   treepath:<path>            → <hex-hash>   (secondary: path → current tree hash)
 *
 * Chunk-map paging. A chunk map for a very large file (a 1.1 TB
 * file at tier 3 ≈ 1M entries ≈ ~120 MB of JSON) does not fit in one Hyperbee
 * value: Hypercore caps a block at 15 MiB (MAX_SUGGESTED_BLOCK_SIZE) and throws
 * BAD_ARGUMENT above it. Maps with > CHUNKS_PER_PAGE entries are stored as a
 * header value { __paged: N } at the base key plus N page values keyed
 * `<base>\x00<i>` (the NUL byte never occurs in a path or hex hash, so a page key
 * can't collide with a real one). Small maps stay inline as a plain array —
 * byte-identical to before, and back-compatible with already-stored maps. The
 * public API (put/get/del) is unchanged; paging is transparent to callers.
 */

import Hyperbee from 'hyperbee'
import ReadyResource from 'ready-resource'

// Entries per chunk-map page. ~116 B/entry worst-case JSON ⇒
// ≤ ~3.8 MB/page, well under Hypercore's 15 MiB block limit.
const CHUNKS_PER_PAGE = 32768

// Overlay-path prefix for host-registered, content-addressed serve entries.
// Hosts that make a file servable purely by content hash should register it at
// `${CONTENT_REGISTER_PREFIX}${contentHash}` so compact()/evictContent() can
// recognize (and reclaim) the entry once the hash is no longer served.
export const CONTENT_REGISTER_PREFIX = '/mir/'

// The content hash an owner-side entry is addressed by (page suffix folded in), or
// null for entries not keyed by a single content hash (real-path file:/chunkmap:,
// tree:, sync:, config:). compact() drops the content-addressed entries whose hash is
// no longer served, and keeps everything else.
function contentHashOfKey (key) {
  if (key.startsWith('chunkmap-oid:')) return key.slice('chunkmap-oid:'.length).split('\x00')[0]
  if (key.startsWith('chunkmap:content:')) return key.slice('chunkmap:content:'.length).split('\x00')[0]
  if (key.startsWith(`chunkmap:${CONTENT_REGISTER_PREFIX}`)) return key.slice(`chunkmap:${CONTENT_REGISTER_PREFIX}`.length).split('\x00')[0]
  if (key.startsWith(`file:${CONTENT_REGISTER_PREFIX}`)) return key.slice(`file:${CONTENT_REGISTER_PREFIX}`.length)
  return null
}

// v1 keeps the original 'file-index' name so existing stores aren't orphaned on
// upgrade; compaction moves to file-index-v2, v3, … and purges the predecessor.
export function indexCoreName (version) {
  return version === 1 ? 'file-index' : `file-index-v${version}`
}

export class FileIndex extends ReadyResource {
  constructor (store, opts = {}) {
    super()
    this._store = store
    this._bee = null
    this._meta = null
    this._version = 1
    this._opts = opts
  }

  // Local index cores are encrypted at rest when the caller passes an encryptionKey
  // (opts.encryptionKey); absent, they stay plaintext.
  _coreOpts (name) {
    const o = { name, valueEncoding: 'binary' }
    if (this._opts.encryptionKey) o.encryptionKey = this._opts.encryptionKey
    return o
  }

  async _open () {
    this._meta = new Hyperbee(this._store.get(this._coreOpts('index-meta')), {
      keyEncoding: 'utf-8',
      valueEncoding: 'json'
    })
    await this._meta.ready()
    this._version = (await this._meta.get('version'))?.value ?? 1
    // A prior purge can leave a version's core with a dangling name alias — its data and
    // by-discovery-key alias were deleted but the by-name alias was not — which throws
    // STORAGE_EMPTY on open. The index is rebuildable cache, so advance to a fresh core
    // name and record it rather than failing open.
    for (let attempts = 0; ; attempts++) {
      const bee = new Hyperbee(this._store.get(this._coreOpts(indexCoreName(this._version))), {
        keyEncoding: 'utf-8',
        valueEncoding: 'json'
      })
      try {
        await bee.ready()
        this._bee = bee
        return
      } catch (err) {
        if (err.code !== 'STORAGE_EMPTY' || attempts >= 64) throw err
        await bee.close().catch(() => {})
        this._version += 1
        await this._meta.put('version', this._version)
      }
    }
  }

  async _close () {
    if (this._bee) await this._bee.close()
    if (this._meta) await this._meta.close()
  }

  get bee () { return this._bee }

  get version () { return this._version }

  get cores () { return [this._bee?.core, this._meta?.core].filter(Boolean) }

  // Reclaim the append-only index: stream the still-served entries into a fresh
  // versioned core, flip the version pointer, and return the old core so the caller
  // can clear+purge it (the only way to return an append-only bee's disk to the OS).
  // Content-addressed entries (maps + content-register entries) whose hash isServed() is false
  // are dropped; real-path, tree, sync and config entries are always kept.
  async compact ({ isServed }) {
    // Skip the rewrite entirely when nothing is droppable — otherwise a compaction of
    // an already-clean index just churns (a fresh version core + a version-marker
    // append), which grows the index without reclaiming anything.
    let droppable = false
    for await (const { key } of this._bee.createReadStream()) {
      const hash = contentHashOfKey(key)
      if (hash !== null && !isServed(hash)) { droppable = true; break }
    }
    if (!droppable) return null

    const next = this._version + 1
    const dst = new Hyperbee(this._store.get(this._coreOpts(indexCoreName(next))), {
      keyEncoding: 'utf-8',
      valueEncoding: 'json'
    })
    await dst.ready()
    let batch = dst.batch()
    let pending = 0
    for await (const { key, value } of this._bee.createReadStream()) {
      const hash = contentHashOfKey(key)
      if (hash !== null && !isServed(hash)) continue
      await batch.put(key, value)
      if (++pending >= 500) { await batch.flush(); batch = dst.batch(); pending = 0 }
    }
    await batch.flush()

    const oldCore = this._bee.core
    await this._meta.put('version', next)
    this._bee = dst
    this._version = next
    return oldCore // left open; caller clears + purges it
  }

  // ── File entries ────────────────────────────────────────────

  /**
   * Store or update a file entry (metadata only, no content)
   * @param {string} path - file path
   * @param {{ contentHash: string|null, size: number, mtime: number, hashing?: boolean, executable?: boolean, linkname?: string|null }} meta
   */
  async putFile (path, meta) {
    await this._bee.put(`file:${path}`, {
      contentHash: meta.contentHash || null,
      size: meta.size,
      mtime: meta.mtime,
      hashing: meta.hashing || false,
      executable: meta.executable || false,
      linkname: meta.linkname || null
    })
  }

  /**
   * Get file metadata
   * @param {string} path
   * @returns {{ contentHash: string|null, size: number, mtime: number, hashing: boolean, executable: boolean, linkname: string|null } | null}
   */
  async getFile (path) {
    const entry = await this._bee.get(`file:${path}`)
    return entry ? entry.value : null
  }

  /**
   * Check if file exists in the index
   * @param {string} path
   * @returns {boolean}
   */
  async hasFile (path) {
    const entry = await this._bee.get(`file:${path}`)
    return entry !== null
  }

  /**
   * Remove a file entry and its chunk map
   * @param {string} path
   */
  async delFile (path) {
    await this._bee.del(`file:${path}`)
    await this._delPagedValue(`chunkmap:${path}`)
  }

  /**
   * List files under a directory prefix
   * @param {string} [dir=''] - directory prefix
   * @returns {Array<{ path: string, contentHash: string|null, size: number, mtime: number }>}
   */
  async listFiles (dir = '') {
    const prefix = dir ? `file:${dir}` : 'file:'
    const files = []

    for await (const entry of this._bee.createReadStream({ gt: prefix, lt: prefix + '\xff' })) {
      files.push({
        path: entry.key.slice('file:'.length),
        ...entry.value
      })
    }

    return files
  }

  // ── Index meta (from hyper-overlay 0.2.14; carried in v3) ───
  // Small key/value bag on the main bee under `meta:` — used by the fast
  // manifest-diff boot path (e.g. hyper-deploy). compact() keeps these
  // (contentHashOfKey returns null for `meta:` → not droppable).

  async putIndexMeta (key, value) {
    await this._bee.put(`meta:${key}`, value)
  }

  async getIndexMeta (key) {
    const entry = await this._bee.get(`meta:${key}`)
    return entry ? entry.value : null
  }

  // ── Chunk maps ──────────────────────────────────────────────

  /**
   * Store a chunk map for a file (hash + offset + length per chunk, no data).
   * Large maps are paged transparently (see the header note) so they never exceed the
   * Hypercore block limit.
   * @param {string} path - file path
   * @param {Array<{ hash: string, offset: number, length: number }>} chunks
   */
  async putChunkMap (path, chunks) {
    await this._putPagedValue(`chunkmap:${path}`, chunks)
  }

  /**
   * Get the chunk map for a file
   * @param {string} path
   * @returns {Array<{ hash: string, offset: number, length: number }> | null}
   */
  async getChunkMap (path) {
    return this._getPagedValue(`chunkmap:${path}`)
  }

  /**
   * Check if a chunk map exists for a file
   * @param {string} path
   * @returns {boolean}
   */
  async hasChunkMap (path) {
    const entry = await this._bee.get(`chunkmap:${path}`)
    return entry !== null
  }

  /**
   * Remove a chunk map
   * @param {string} path
   */
  async delChunkMap (path) {
    await this._delPagedValue(`chunkmap:${path}`)
  }

  // Paged-value storage. Small maps stay inline as a plain array
  // (unchanged on disk, back-compatible); maps over CHUNKS_PER_PAGE entries are
  // split into a { __paged: N } header + N `<base>\x00<i>` page values, written
  // in one Hyperbee batch so the whole map commits atomically. Stale pages from
  // a previously-larger map at the same key are deleted in the same batch.
  async _putPagedValue (baseKey, chunks) {
    const prevPages = await this._pagedCount(baseKey)

    if (chunks.length <= CHUNKS_PER_PAGE) {
      if (prevPages === 0) {
        await this._bee.put(baseKey, chunks)
        return
      }
      await this._batched(async (b) => {
        await b.put(baseKey, chunks)
        for (let i = 0; i < prevPages; i++) await b.del(`${baseKey}\x00${i}`)
      })
      return
    }

    const pageCount = Math.ceil(chunks.length / CHUNKS_PER_PAGE)
    await this._batched(async (b) => {
      await b.put(baseKey, { __paged: pageCount })
      for (let i = 0; i < pageCount; i++) {
        await b.put(`${baseKey}\x00${i}`, chunks.slice(i * CHUNKS_PER_PAGE, (i + 1) * CHUNKS_PER_PAGE))
      }
      for (let i = pageCount; i < prevPages; i++) await b.del(`${baseKey}\x00${i}`)
    })
  }

  async _getPagedValue (baseKey) {
    const entry = await this._bee.get(baseKey)
    if (!entry) return null
    if (Array.isArray(entry.value)) return entry.value // inline / legacy
    const pageCount = entry.value.__paged
    if (!pageCount) return null
    const chunks = []
    for (let i = 0; i < pageCount; i++) {
      const page = await this._bee.get(`${baseKey}\x00${i}`)
      // The header + all pages commit in one atomic batch, so a missing or non-array
      // page means the value is corrupt/incomplete. Return null — a clean "miss" so the
      // caller re-chunks from the source file — never a silently truncated chunk map.
      if (!page || !Array.isArray(page.value)) return null
      for (const c of page.value) chunks.push(c)
    }
    return chunks
  }

  async _delPagedValue (baseKey) {
    const prevPages = await this._pagedCount(baseKey)
    if (prevPages === 0) {
      await this._bee.del(baseKey)
      return
    }
    await this._batched(async (b) => {
      await b.del(baseKey)
      for (let i = 0; i < prevPages; i++) await b.del(`${baseKey}\x00${i}`)
    })
  }

  // Page count of an existing paged value, or 0 if the key is absent or inline.
  async _pagedCount (baseKey) {
    const entry = await this._bee.get(baseKey)
    if (!entry || Array.isArray(entry.value)) return 0
    return entry.value.__paged || 0
  }

  // Run ops in a Hyperbee batch, closing it on error (flush releases the lock
  // itself, even on append failure, via _appendBatch's finally).
  async _batched (fn) {
    const b = this._bee.batch()
    try {
      await fn(b)
      await b.flush()
    } catch (err) {
      try { await b.close() } catch {}
      throw err
    }
  }

  // Content-addressed chunk map. FastCDC is deterministic on bytes, so a
  // given content hash always chunks identically regardless of which overlay path
  // it was registered under. Keying by content hash lets the publish-time chunking
  // be reused at serve time (a synthetic 'content:<hash>' key) and across restarts,
  // instead of re-reading the whole file to re-chunk it before the first byte ships.
  async putChunkMapByHash (contentHash, chunks) {
    await this._putPagedValue(`chunkmap-oid:${contentHash}`, chunks)
  }

  async getChunkMapByHash (contentHash) {
    return this._getPagedValue(`chunkmap-oid:${contentHash}`)
  }

  async hasChunkMapByHash (contentHash) {
    return (await this._bee.get(`chunkmap-oid:${contentHash}`)) !== null
  }

  async delChunkMapByHash (contentHash) {
    await this._delPagedValue(`chunkmap-oid:${contentHash}`)
  }

  // Evict every durable entry a served content hash leaves behind: the content-
  // addressed map plus the deterministic owner-side synthetic paths (the serve
  // copy at content:<hash> and the register entry under CONTENT_REGISTER_PREFIX).
  // Peer-path-keyed receiver entries are unknowable from the hash — the host's
  // own index sweep handles those.
  async evictContent (contentHash) {
    await this.delChunkMapByHash(contentHash)
    await this.delChunkMap(`content:${contentHash}`)
    await this.delFile(`${CONTENT_REGISTER_PREFIX}${contentHash}`)
  }

  // ── Sync state ──────────────────────────────────────────────

  /**
   * Store sync state for a peer + file
   * @param {string} peerKey - hex peer key
   * @param {string} path - file path
   * @param {{ lastSeq: number, lastHash: string }} state
   */
  async putSyncState (peerKey, path, state) {
    await this._bee.put(`sync:${peerKey}:${path}`, {
      lastSeq: state.lastSeq,
      lastHash: state.lastHash
    })
  }

  /**
   * Get sync state for a peer + file
   * @param {string} peerKey
   * @param {string} path
   * @returns {{ lastSeq: number, lastHash: string } | null}
   */
  async getSyncState (peerKey, path) {
    const entry = await this._bee.get(`sync:${peerKey}:${path}`)
    return entry ? entry.value : null
  }

  /**
   * Remove sync state for a peer + file
   * @param {string} peerKey
   * @param {string} path
   */
  async delSyncState (peerKey, path) {
    await this._bee.del(`sync:${peerKey}:${path}`)
  }

  /**
   * List all sync states for a peer
   * @param {string} peerKey
   * @returns {Array<{ path: string, lastSeq: number, lastHash: string }>}
   */
  async listSyncStates (peerKey) {
    const prefix = `sync:${peerKey}:`
    const states = []

    for await (const entry of this._bee.createReadStream({ gt: prefix, lt: prefix + '\xff' })) {
      states.push({
        path: entry.key.slice(prefix.length),
        ...entry.value
      })
    }

    return states
  }

  // ── Sync config ─────────────────────────────────────────────

  /**
   * Store sync configuration
   * @param {{ folders: Array<{ path: string, strategy: string, quota?: string|null, peers?: string[] }> }} config
   */
  async putSyncConfig (config) {
    await this._bee.put('config:sync', config)
  }

  /**
   * Get sync configuration
   * @returns {{ folders: Array } | null}
   */
  async getSyncConfig () {
    const entry = await this._bee.get('config:sync')
    return entry ? entry.value : null
  }

  // ── Trees ───────────────────────────────────────────────────

  /**
   * Store a tree by its content-addressed hash.
   * Same hash put twice is a no-op (content-addressed — bytes can't differ).
   *
   * @param {string} hash - hex-encoded blake2b-256 tree hash
   * @param {{ entries: Array, size: number }} tree
   */
  async putTree (hash, tree) {
    await this._bee.put(`tree:${hash}`, {
      entries: tree.entries,
      size: tree.size
    })
  }

  /**
   * Get a tree by its hash.
   * @param {string} hash
   * @returns {{ entries: Array, size: number } | null}
   */
  async getTree (hash) {
    const entry = await this._bee.get(`tree:${hash}`)
    return entry ? entry.value : null
  }

  /**
   * Check if a tree is present.
   * @param {string} hash
   * @returns {boolean}
   */
  async hasTree (hash) {
    const entry = await this._bee.get(`tree:${hash}`)
    return entry !== null
  }

  /**
   * Remove a tree. Only safe when no treepath entries reference this hash.
   * @param {string} hash
   */
  async delTree (hash) {
    await this._bee.del(`tree:${hash}`)
  }

  /**
   * Record the current tree hash for a path (secondary index).
   * @param {string} path
   * @param {string} hash
   */
  async putTreePath (path, hash) {
    await this._bee.put(`treepath:${path}`, hash)
  }

  /**
   * Look up the current tree hash for a path.
   * @param {string} path
   * @returns {string|null}
   */
  async getTreePath (path) {
    const entry = await this._bee.get(`treepath:${path}`)
    return entry ? entry.value : null
  }

  /**
   * Remove the treepath entry. Does NOT delete the underlying tree — other
   * paths may still reference the same hash.
   * @param {string} path
   */
  async delTreePath (path) {
    await this._bee.del(`treepath:${path}`)
  }

  /**
   * List all stored tree hashes.
   * @param {object} [opts]
   * @param {number} [opts.limit] - optional cap
   * @returns {Array<{ hash: string, entryCount: number, size: number }>}
   */
  async listTrees (opts = {}) {
    const trees = []
    for await (const entry of this._bee.createReadStream({ gt: 'tree:', lt: 'tree:\xff' })) {
      trees.push({
        hash: entry.key.slice('tree:'.length),
        entryCount: entry.value.entries.length,
        size: entry.value.size
      })
      if (opts.limit && trees.length >= opts.limit) break
    }
    return trees
  }

  // ── Stats ───────────────────────────────────────────────────

  /**
   * Get index statistics
   * @returns {{ fileCount: number, chunkMapCount: number, syncStateCount: number, treeCount: number, treePathCount: number }}
   */
  async stats () {
    let fileCount = 0
    let chunkMapCount = 0
    let syncStateCount = 0
    let treeCount = 0
    let treePathCount = 0

    for await (const entry of this._bee.createReadStream()) {
      if (entry.key.startsWith('file:')) fileCount++
      // Count one per file — skip the `\x00`-suffixed page values.
      else if (entry.key.startsWith('chunkmap:') && !entry.key.includes('\x00')) chunkMapCount++
      else if (entry.key.startsWith('sync:')) syncStateCount++
      else if (entry.key.startsWith('tree:')) treeCount++
      else if (entry.key.startsWith('treepath:')) treePathCount++
    }

    return { fileCount, chunkMapCount, syncStateCount, treeCount, treePathCount }
  }
}
/**
 * Wire Protocol Messages for hyper-overlay/v2
 *
 * 13 message types for sync + transfer + trees (0.5a extension):
 *  0-2: Sync messages (state exchange, file offers/requests)
 *  3-8: Transfer messages (chunk hashes, data, completion)
 *  9-11: Tree messages (tree-request, tree-response, content-request)
 *  12: transfer-control (downloader→holder pause/stop notice)
 *
 * Messages 9-12 are backward-compatible additions — peers without
 * this code simply don't register the corresponding protomux slots, and
 * senders see no receiver for those messages. File-level sync (0-8)
 * keeps working unchanged.
 *
 * All use compact-encoding codecs with preencode/encode/decode.
 */

import c from 'compact-encoding'

// ── Helpers ───────────────────────────────────────────────────

function toBuffer32 (hashOrBuf) {
  if (Buffer.isBuffer(hashOrBuf)) return hashOrBuf
  return Buffer.from(hashOrBuf, 'hex')
}

// Encode/decode an array of { hash (32-byte hex), length (uint) }
const chunkInfoArray = {
  preencode (state, arr) {
    c.uint32.preencode(state, arr.length)
    for (const item of arr) {
      state.end += 32
      c.uint.preencode(state, item.length)
    }
  },
  encode (state, arr) {
    c.uint32.encode(state, arr.length)
    for (const item of arr) {
      toBuffer32(item.hash).copy(state.buffer, state.start, 0, 32)
      state.start += 32
      c.uint.encode(state, item.length)
    }
  },
  decode (state) {
    const len = c.uint32.decode(state)
    const arr = []
    for (let i = 0; i < len; i++) {
      const hash = state.buffer.subarray(state.start, state.start + 32).toString('hex')
      state.start += 32
      const length = c.uint.decode(state)
      arr.push({ hash, length })
    }
    return arr
  }
}

// Encode/decode an array of uints
const uintArray = {
  preencode (state, arr) {
    c.uint32.preencode(state, arr.length)
    for (const v of arr) c.uint.preencode(state, v)
  },
  encode (state, arr) {
    c.uint32.encode(state, arr.length)
    for (const v of arr) c.uint.encode(state, v)
  },
  decode (state) {
    const len = c.uint32.decode(state)
    const arr = []
    for (let i = 0; i < len; i++) arr.push(c.uint.decode(state))
    return arr
  }
}

// ── Handshake ─────────────────────────────────────────────────

export const handshake = {
  preencode (state, m) {
    c.uint8.preencode(state, m.version)
    c.uint8.preencode(state, m.capabilities)
  },
  encode (state, m) {
    c.uint8.encode(state, m.version)
    c.uint8.encode(state, m.capabilities)
  },
  decode (state) {
    return {
      version: c.uint8.decode(state),
      capabilities: c.uint8.decode(state)
    }
  }
}

// ── Sync Messages (0-2) ──────────────────────────────────────

// Message 0: sync-state — exchange last-synced sequence numbers
export const syncState = {
  preencode (state, m) {
    c.buffer.preencode(state, toBuffer32(m.feedKey))
    c.uint.preencode(state, m.localSeq)
    c.uint.preencode(state, m.remoteSeq)
  },
  encode (state, m) {
    c.buffer.encode(state, toBuffer32(m.feedKey))
    c.uint.encode(state, m.localSeq)
    c.uint.encode(state, m.remoteSeq)
  },
  decode (state) {
    return {
      feedKey: c.buffer.decode(state).toString('hex'),
      localSeq: c.uint.decode(state),
      remoteSeq: c.uint.decode(state)
    }
  }
}

// Message 1: file-offer — announce a file change
export const fileOffer = {
  preencode (state, m) {
    c.string.preencode(state, m.path)
    c.buffer.preencode(state, toBuffer32(m.contentHash))
    c.uint.preencode(state, m.size)
    c.uint.preencode(state, m.mtime)
    c.uint8.preencode(state, m.op)
  },
  encode (state, m) {
    c.string.encode(state, m.path)
    c.buffer.encode(state, toBuffer32(m.contentHash))
    c.uint.encode(state, m.size)
    c.uint.encode(state, m.mtime)
    c.uint8.encode(state, m.op)
  },
  decode (state) {
    return {
      path: c.string.decode(state),
      contentHash: c.buffer.decode(state).toString('hex'),
      size: c.uint.decode(state),
      mtime: c.uint.decode(state),
      op: c.uint8.decode(state)
    }
  }
}

// Message 2: file-request — request a file transfer
export const fileRequest = {
  preencode (state, m) {
    c.string.preencode(state, m.path)
    c.buffer.preencode(state, toBuffer32(m.contentHash))
    c.buffer.preencode(state, m.chunksHave || Buffer.alloc(0))
  },
  encode (state, m) {
    c.string.encode(state, m.path)
    c.buffer.encode(state, toBuffer32(m.contentHash))
    c.buffer.encode(state, m.chunksHave || Buffer.alloc(0))
  },
  decode (state) {
    return {
      path: c.string.decode(state),
      contentHash: c.buffer.decode(state).toString('hex'),
      chunksHave: c.buffer.decode(state)
    }
  }
}

// ── Transfer Messages (3-8) ──────────────────────────────────

// Message 3: chunk-hashes — chunk hash list for a requested file
//
// `more` (paging flag) is appended last. A very large file's
// chunk list serializes past the 16 MiB-1 Noise frame limit
// (@hyperswarm/secret-stream MAX_ATOMIC_WRITE), so the protocol splits it into
// several chunkHashes frames: every page but the last sets more:1, the final
// page sets more:0. The receiver concatenates pages (in arrival order, keyed by
// path) before dispatching. Appended last so a pre-paging peer that omits it
// decodes more:0 — i.e. as a single, complete page (back-compatible).
export const chunkHashes = {
  preencode (state, m) {
    c.string.preencode(state, m.path)
    c.uint8.preencode(state, m.tier)
    chunkInfoArray.preencode(state, m.chunks)
    c.uint8.preencode(state, m.more || 0)
  },
  encode (state, m) {
    c.string.encode(state, m.path)
    c.uint8.encode(state, m.tier)
    chunkInfoArray.encode(state, m.chunks)
    c.uint8.encode(state, m.more || 0)
  },
  decode (state) {
    const path = c.string.decode(state)
    const tier = c.uint8.decode(state)
    const chunks = chunkInfoArray.decode(state)
    // A frame from a pre-paging peer has no trailing byte → more:0.
    const more = state.start < state.end ? c.uint8.decode(state) : 0
    return { path, tier, chunks, more }
  }
}

// Message 4: chunk-need — tell sender which chunks to send
export const chunkNeed = {
  preencode (state, m) {
    c.string.preencode(state, m.path)
    uintArray.preencode(state, m.indices)
  },
  encode (state, m) {
    c.string.encode(state, m.path)
    uintArray.encode(state, m.indices)
  },
  decode (state) {
    return {
      path: c.string.decode(state),
      indices: uintArray.decode(state)
    }
  }
}

// Message 5: chunk-data — send chunk bytes
export const chunkData = {
  preencode (state, m) {
    c.string.preencode(state, m.path)
    c.uint.preencode(state, m.index)
    c.buffer.preencode(state, m.data)
  },
  encode (state, m) {
    c.string.encode(state, m.path)
    c.uint.encode(state, m.index)
    c.buffer.encode(state, m.data)
  },
  decode (state) {
    return {
      path: c.string.decode(state),
      index: c.uint.decode(state),
      data: c.buffer.decode(state)
    }
  }
}

// Message 6: chunk-cancel — cancel a pending transfer
export const chunkCancel = {
  preencode (state, m) {
    c.string.preencode(state, m.path)
    // P5 hotswap: optional per-chunk cancel. Appended so pre-0.3.0 peers still
    // decode {path} and ignore the trailing bytes; absent = whole-transfer cancel.
    if (m.indices && m.indices.length) c.array(c.uint).preencode(state, m.indices)
  },
  encode (state, m) {
    c.string.encode(state, m.path)
    if (m.indices && m.indices.length) c.array(c.uint).encode(state, m.indices)
  },
  decode (state) {
    const path = c.string.decode(state)
    // Keep the pre-0.3.0 {path} shape byte-for-byte when no indices trail, so a
    // whole-transfer cancel decodes identically on old and new peers.
    if (state.start < state.end) return { path, indices: c.array(c.uint).decode(state) }
    return { path }
  }
}

// Message 7: transfer-complete — confirm file fully received
export const transferComplete = {
  preencode (state, m) {
    c.string.preencode(state, m.path)
    c.buffer.preencode(state, toBuffer32(m.contentHash))
  },
  encode (state, m) {
    c.string.encode(state, m.path)
    c.buffer.encode(state, toBuffer32(m.contentHash))
  },
  decode (state) {
    return {
      path: c.string.decode(state),
      contentHash: c.buffer.decode(state).toString('hex')
    }
  }
}

// Message 8: conflict — notify peer of a detected conflict
export const conflict = {
  preencode (state, m) {
    c.string.preencode(state, m.path)
    c.buffer.preencode(state, toBuffer32(m.myHash))
    c.buffer.preencode(state, toBuffer32(m.theirHash))
    c.buffer.preencode(state, toBuffer32(m.ancestorHash || Buffer.alloc(32)))
  },
  encode (state, m) {
    c.string.encode(state, m.path)
    c.buffer.encode(state, toBuffer32(m.myHash))
    c.buffer.encode(state, toBuffer32(m.theirHash))
    c.buffer.encode(state, toBuffer32(m.ancestorHash || Buffer.alloc(32)))
  },
  decode (state) {
    return {
      path: c.string.decode(state),
      myHash: c.buffer.decode(state).toString('hex'),
      theirHash: c.buffer.decode(state).toString('hex'),
      ancestorHash: c.buffer.decode(state).toString('hex')
    }
  }
}

// ── Tree Messages (9-11) — 0.5a extension ────────────────────

// Encode/decode an array of tree entries: { kind, exec, name, childHash, size }
const treeEntryArray = {
  preencode (state, arr) {
    c.uint32.preencode(state, arr.length)
    for (const e of arr) {
      c.uint8.preencode(state, e.kind)
      c.uint8.preencode(state, e.exec || 0)
      c.string.preencode(state, e.name)
      state.end += 32 // childHash fixed32
      c.uint.preencode(state, e.size || 0)
    }
  },
  encode (state, arr) {
    c.uint32.encode(state, arr.length)
    for (const e of arr) {
      c.uint8.encode(state, e.kind)
      c.uint8.encode(state, e.exec || 0)
      c.string.encode(state, e.name)
      toBuffer32(e.childHash).copy(state.buffer, state.start, 0, 32)
      state.start += 32
      c.uint.encode(state, e.size || 0)
    }
  },
  decode (state) {
    const len = c.uint32.decode(state)
    const arr = []
    for (let i = 0; i < len; i++) {
      const kind = c.uint8.decode(state)
      const exec = c.uint8.decode(state)
      const name = c.string.decode(state)
      const childHash = state.buffer.subarray(state.start, state.start + 32).toString('hex')
      state.start += 32
      const size = c.uint.decode(state)
      arr.push({ kind, exec, name, childHash, size })
    }
    return arr
  }
}

// Message 9: tree-request — ask peer for a tree by its content hash
//
// `nonce` (per-request id) is appended last; the holder echoes it in every
// treeResponse page so the requester can drop pages from a superseded attempt.
// A pre-nonce peer omits it and decodes 0, which the requester treats as
// "unverifiable".
export const treeRequest = {
  preencode (state, m) {
    c.buffer.preencode(state, toBuffer32(m.hash))
    c.uint.preencode(state, m.nonce || 0)
  },
  encode (state, m) {
    c.buffer.encode(state, toBuffer32(m.hash))
    c.uint.encode(state, m.nonce || 0)
  },
  decode (state) {
    const hash = c.buffer.decode(state).toString('hex')
    const nonce = state.start < state.end ? c.uint.decode(state) : 0
    return { hash, nonce }
  }
}

// Message 10: tree-response — return a tree's entries to the requester
//
// `more` (paging flag) mirrors chunkHashes: a tree too large for one 16 MiB-1
// Noise frame is split into several frames, every page but the last more:1.
// `nonce` echoes the requester's per-request id so pages from a superseded
// attempt are dropped. Both are appended last; a pre-paging peer omits them and
// decodes 0.
export const treeResponse = {
  preencode (state, m) {
    c.buffer.preencode(state, toBuffer32(m.hash))
    treeEntryArray.preencode(state, m.entries)
    c.uint8.preencode(state, m.more || 0)
    c.uint.preencode(state, m.nonce || 0)
  },
  encode (state, m) {
    c.buffer.encode(state, toBuffer32(m.hash))
    treeEntryArray.encode(state, m.entries)
    c.uint8.encode(state, m.more || 0)
    c.uint.encode(state, m.nonce || 0)
  },
  decode (state) {
    const hash = c.buffer.decode(state).toString('hex')
    const entries = treeEntryArray.decode(state)
    const more = state.start < state.end ? c.uint8.decode(state) : 0
    const nonce = state.start < state.end ? c.uint.decode(state) : 0
    return { hash, entries, more, nonce }
  }
}

// Message 11: content-request — fetch a file by its content hash (not path)
// Sender locates any local file matching the hash and serves its chunks.
// `from` carries the requester's profile-key (hex) so the
// holder's serveAuthorizer can authenticate the asker. Appended last → an
// overlay peer that predates this field decodes '' (empty), never crashes.
export const contentRequest = {
  preencode (state, m) {
    c.buffer.preencode(state, toBuffer32(m.contentHash))
    c.buffer.preencode(state, m.chunksHave || Buffer.alloc(0))
    c.string.preencode(state, m.from || '')
  },
  encode (state, m) {
    c.buffer.encode(state, toBuffer32(m.contentHash))
    c.buffer.encode(state, m.chunksHave || Buffer.alloc(0))
    c.string.encode(state, m.from || '')
  },
  decode (state) {
    return {
      contentHash: c.buffer.decode(state).toString('hex'),
      chunksHave: c.buffer.decode(state),
      from: c.string.decode(state)
    }
  }
}

// Message 12: transfer-control — downloader→holder pause/stop notice for a
// content-addressed fetch. state: 0 = paused, 1 = stopped. Appended last so a
// holder that predates this never registers slot 12 and silently ignores it.
export const transferControl = {
  preencode (state, m) {
    c.buffer.preencode(state, toBuffer32(m.contentHash))
    c.uint8.preencode(state, m.state)
  },
  encode (state, m) {
    c.buffer.encode(state, toBuffer32(m.contentHash))
    c.uint8.encode(state, m.state)
  },
  decode (state) {
    return {
      contentHash: c.buffer.decode(state).toString('hex'),
      state: c.uint8.decode(state)
    }
  }
}

// Message 13: transfer-progress — downloader→holder one-shot have-baseline for a
// resumed content-addressed fetch. `have` = bytes the downloader already holds on
// disk at resume, so the holder's "who is downloading" bar reflects the downloader's
// TRUE completion, not only the bytes it re-serves. Appended last so a holder that
// predates this never registers slot 13 and silently ignores it.
export const transferProgress = {
  preencode (state, m) {
    c.buffer.preencode(state, toBuffer32(m.contentHash))
    c.uint.preencode(state, m.have || 0)
  },
  encode (state, m) {
    c.buffer.encode(state, toBuffer32(m.contentHash))
    c.uint.encode(state, m.have || 0)
  },
  decode (state) {
    return {
      contentHash: c.buffer.decode(state).toString('hex'),
      have: c.uint.decode(state)
    }
  }
}
/**
 * OverlayProtocol — Protomux channel for sync + transfer
 *
 * Sync approach: each side sends its local feed entries directly via
 * file-offer messages. No Hypercore feed replication needed — the feed
 * entries are small JSON objects sent over our own Protomux channel.
 *
 * Flow:
 *  1. Channel opens → both send sync-state (feed length)
 *  2. Side with more entries sends file-offers for each change
 *  3. Receiver auto-requests files it doesn't have
 *  4. Sender chunks on demand → streams chunks → receiver writes to disk
 */

import path from 'bare-path'
import * as messages from './messages.js'
import { hashChunk, selectTier } from './chunker.js'
import { ChunkScheduler } from './chunk-scheduler.js'
import fs from 'bare-fs'

const PROTOCOL = 'hyper-overlay/v2'
const VERSION = 2
const CAP_LOCAL_FILES = 0x01
const CAP_ADAPTIVE_CHUNKS = 0x02

// A file's chunk list, shipped in one frame, can exceed the
// 16 MiB-1 Noise transport limit (@hyperswarm/secret-stream MAX_ATOMIC_WRITE):
// a 1.25 TB file at tier 3 is ~1.2M entries ≈ ~44 MB, which the secret-stream
// rejects ("Message is too large for an atomic write"), dropping the connection
// before a byte ships. So chunkHashes is paged into frames of ≤ this many
// entries. Each wire entry is 32 B (hash) + ≤5 B (varint length) ≈ 37 B, so
// 100k entries ≈ 3.7 MB/frame — well under the limit even after Protomux's
// ≤8 MiB send-batching.
const MAX_CHUNKS_PER_MSG = 100000

// F2: hard ceiling on chunk-hash entries accumulated across a paged chunkHashes
// stream. Far above any real file's chunk count (a 1.25 TB tier-3 file is ~1.2M
// entries), so it never trips legitimately — but it bounds an authorized-but-rogue
// seeder that streams `more:1` pages forever (each buffered page re-arms the idle
// watchdog, so the stall timer never fires). Unlike treeResponse, chunkHashes had
// no such cap. ~16M entries ≈ a few hundred MB worst case before we drop the list.
const MAX_CHUNK_HASHES_ACCUM = 16 * 1024 * 1024

// Tree entries are variable length (name is a var-string), unlike the fixed-width
// chunkHashes entries, so treeResponse pages by an encoded-byte budget rather than
// a fixed count. 4 MiB keeps each frame well under the atomic-write limit even
// after Protomux's ≤8 MiB send-batching.
const MAX_TREE_BYTES_PER_MSG = 4 * 1024 * 1024

// Defensive ceiling on the bytes a requester will accumulate across a paged
// treeResponse. A single-directory tree is orders of magnitude smaller; this only
// bounds memory so a peer streaming pages endlessly rejects the request instead
// of OOMing.
const MAX_TREE_RESPONSE_BYTES = 256 * 1024 * 1024

// transfer-control (message 12) states, downloader→holder.
const CONTROL_PAUSED = 0
const CONTROL_STOPPED = 1

// Abandon a serve loop whose stream neither drains nor closes within
// this window (a wedged-but-TCP-alive peer); the receiver re-requests, so it self-heals.
const DRAIN_TIMEOUT_MS = 60000

export class OverlayProtocol {
  constructor (syncEngine, transferManager, opts = {}) {
    this._syncEngine = syncEngine
    this._transferManager = transferManager
    this._corestore = opts.corestore || null
    this._overlayKey = opts.key || null
    this._peers = new Map()
    // Muxes we've registered a pair() callback on, so we register exactly one per mux. See attach().
    this._paired = new Set()
    this._filePaths = opts.filePaths || new Map()
    this._syncBaseDirs = opts.syncBaseDirs || []
    this._defaultStrategy = opts.defaultStrategy || 'prompt'
    this._autoSync = opts.autoSync !== false

    this._conflictCb = opts.onConflict || null
    this._syncedCb = opts.onSynced || null
    this._fileOfferCb = opts.onFileOffer || null
    // Called for every chunk that arrives on this peer. Lets callers
    // (e.g. NetworkFetcher) implement silent-peer watchdogs by resetting
    // an idle timer on every chunk. Receives { path, index, bytes, peer }.
    this._chunkProgressCb = opts.onChunkProgress || null
    // serve-side telemetry, symmetric to onChunkProgress — fired when WE
    // serve a peer (the sender-side download indicator). onServeStart({ path, peer,
    // from, total }) once a peer is authorized to fetch; onChunkServe({ path, peer,
    // from, bytes }) per served chunk; onServeEnd({ path, peer, from }) on close.
    this._serveStartCb = opts.onServeStart || null
    this._chunkServeCb = opts.onChunkServe || null
    this._serveEndCb = opts.onServeEnd || null
    // A downloader paused/stopped a hash WE serve. { path, peer, from, state }.
    this._serveControlCb = opts.onServeControl || null
    // A downloader reported its on-disk have-bytes for a hash WE serve (resume
    // baseline). { path, peer, from, have }.
    this._serveProgressCb = opts.onServeProgress || null

    // 0.5a: content-hash → local disk path, for responding to content-requests
    this._contentHashPaths = opts.contentHashPaths || new Map()
    // 0.5a: tree-request timeout (ms)
    this._treeRequestTimeout = opts.treeRequestTimeout || 30000
    // Cap on bytes accumulated across a paged treeResponse.
    this._maxTreeResponseBytes = opts.maxTreeResponseBytes || MAX_TREE_RESPONSE_BYTES
    // Multi-source: synthetic path 'content:<hash>' → ChunkScheduler. When set,
    // _onChunkHashes/_onChunkData delegate to the scheduler (parallel fetch
    // across peers) instead of the legacy single-peer auto-flow.
    this._schedulers = new Map()
    // 'content:<hash>' cancelled before its scheduler existed (the caller's
    // peer-wait window) — fetchContent honors it the moment it creates the scheduler.
    this._cancelPending = new Set()

    // serve authorization. _serveAuthorizer(peer, from, hash) →
    // boolean gates every inbound content-request; deny is a silent drop
    // (indistinguishable from "I don't hold it" — no membership oracle).
    // _localProfileKey is stamped on outbound content-requests as msg.from.
    this._serveAuthorizer = opts.serveAuthorizer || null
    this._localProfileKey = opts.localProfileKey || null
  }

  /**
   * Multi-source fetch of a file by content hash from several peers in
   * parallel (torrent-style). The caller pre-registers _filePaths['content:'
   * + contentHash] = destPath. Resolves when the file is assembled + verified.
   *
   * @param {string} contentHash
   * @param {Array} peers peer objects to fan the request to
   * @param {object} [opts] { destPath, cap, timeout, onProgress }
   * @returns {Promise<void>}
   */
  fetchContent (contentHash, peers, opts = {}) {
    const p = 'content:' + contentHash
    // Content-addressed dedup: a concurrent fetch of the SAME hash (a
    // different file/transfer with identical bytes) joins the in-flight one instead
    // of failing — await its verified bytes, then copy to our destPath. If the leader
    // was cancelled, re-issue our own fetch (the scheduler entry is already gone).
    const inflight = this._schedulers.get(p)
    if (inflight) {
      return inflight.shared.then(
        (res) => {
          if (opts.destPath && opts.destPath !== inflight.destPath) {
            try {
              fs.copyFileSync(inflight.destPath, opts.destPath)
            } catch (err) {
              // The leader's shared temp is already gone — the leader's caller
              // (e.g. pgh NetworkFetcher) unlinks its destPath as soon as it has
              // read+verified the bytes, which can race ahead of this joiner's
              // copy. Re-issue our own fetch, mirroring the ECANCELLED path.
              if (err?.code === 'ENOENT') return this.fetchContent(contentHash, peers, opts)
              throw err
            }
          }
          return res
        },
        (err) => { if (err?.code === 'ECANCELLED') return this.fetchContent(contentHash, peers, opts); throw err },
      )
    }
    const sched = new ChunkScheduler({
      path: p,
      destPath: opts.destPath,
      transfer: this._transferManager,
      sendNeed: (peer, indices) => peer.msgs.chunkNeed.send({ path: p, indices }),
      // P5 hotswap: best-effort tell a slow peer to stop serving reassigned chunks.
      sendCancel: (peer, indices) => { try { peer.msgs.chunkCancel.send({ path: p, indices }) } catch {} },
      cap: opts.cap,
      timeout: opts.timeout,
      onProgress: opts.onProgress,
      onVerify: opts.onVerify,
      onEnd: opts.onEnd,
      onBaseline: (have) => this.sendTransferProgress(contentHash, have),
      contentHash   // verify the whole-file hash incrementally during the transfer
    })
    this._schedulers.set(p, sched)
    // shared promise so joiners (above) can await the same completion —
    // calling sched.promise() twice would overwrite its resolve/reject handlers.
    sched.shared = sched.promise().finally(() => {
      this._schedulers.delete(p)
      // F2: drop any half-accumulated paged chunk list for this path across peers, so a
      // same-key retry (content:<hash> reused after a cancel mid-paging) can't append
      // fresh pages onto a stale partial list.
      for (const pr of this._peers.values()) if (pr._chunkHashPages) pr._chunkHashPages.delete(p)
    })
    // honor a cancel/pause that arrived before this scheduler existed.
    if (this._cancelPending.delete(p)) { sched.cancel(); return sched.shared }
    for (const peer of peers) this.requestContent(peer, contentHash, null)
    return sched.shared
  }

  // Stop an in-flight multi-source fetch. discardPartial:true unlinks the
  // partial (cancel); false keeps it for resume (pause). The scheduler's promise
  // rejects with ECANCELLED and its .finally() removes it from _schedulers. When no
  // scheduler exists yet (the caller is still in its peer-wait window), remember the
  // intent so fetchContent cancels the scheduler the instant it is created.
  async cancelContent (contentHash, { discardPartial = false, signal = true } = {}) {
    const p = 'content:' + contentHash
    const sched = this._schedulers.get(p)
    // No scheduler → the fetch never sent a content-request, so no holder has a serve
    // row for us; skip the (wasted) broadcast and just record the pre-scheduler cancel.
    if (!sched) { this._cancelPending.add(p); return false }
    // Tell holders we paused (keep partial) or stopped (discard) BEFORE local teardown,
    // so their sender-side indicator reacts now, not after the idle sweep. signal:false
    // suppresses it (a supersede is a restart, not a user stop).
    if (signal) this._sendTransferControl(contentHash, discardPartial ? CONTROL_STOPPED : CONTROL_PAUSED)
    // Stop accepting chunks first so the pause hash-drain converges on a stable frontier.
    sched.cancel()
    if (discardPartial) this._transferManager.cancel(sched.destPath)
    else await this._transferManager.pause(sched.destPath)
    return true
  }

  // Tell holders we stopped pulling a hash whose local fetch is already gone (e.g.
  // discarding an already-paused transfer) — no scheduler to tear down here.
  sendStopControl (contentHash) { this._sendTransferControl(contentHash, CONTROL_STOPPED) }

  // Best-effort broadcast to all connected holders. A holder with no serve-ledger entry
  // for us (or no slot for this message) just drops it; wrapped per-peer so a closing
  // channel or an old peer can't throw.
  _broadcast (msgName, payload) {
    for (const [, peer] of this._peers) {
      try { peer.msgs[msgName]?.send(payload) } catch {}
    }
  }

  _sendTransferControl (contentHash, state) { this._broadcast('transferControl', { contentHash, state }) }

  // Tell holders our current on-disk have-bytes for a hash we're (re)fetching, so their
  // sender-side bar mirrors our TRUE progress rather than only the bytes they re-serve.
  sendTransferProgress (contentHash, have) { this._broadcast('transferProgress', { contentHash, have }) }

  // Drop a cancel recorded before a scheduler existed when the fetch is
  // abandoned without ever reaching fetchContent (no peer connected) — otherwise the
  // marker would cancel the next fetch of the same content.
  clearCancelPending (contentHash) { this._cancelPending.delete('content:' + contentHash) }

  setContentHashPaths (map) { this._contentHashPaths = map }

  attach (mux) {
    if (this._peers.has(mux)) return this._peers.get(mux)

    const self = this

    // Attach used to be ONE-SHOT and order-sensitive, which silently killed byte transfer for the life
    // of a connection. If the remote has no matching (protocol, id) channel at the moment we attach —
    // it hasn't booted this DID's overlay yet, or is mid-restart — protomux tears our channel down,
    // onclose drops the peer, and NOTHING ever re-created it. The remote attaching a second later did
    // not help: `createChannel` then returns null ("the remote has already closed this one") and there
    // was no path back. Meanwhile the connection stays perfectly healthy for everything else, so
    // corestore keeps replicating and the pointers keep converging while the BYTES never move again —
    // which is precisely the fault seen in production (overlayPeers:0, forever, on a live connection).
    //
    // mux.pair() is protomux's mechanism for exactly this race: it fires whenever the remote REQUESTS
    // this (protocol, id), so a late or restarted peer gets a channel created on demand. Registered
    // once per mux, and left registered after a close so re-pairing keeps working for the connection's
    // whole life.
    if (!this._paired.has(mux)) {
      this._paired.add(mux)
      try {
        mux.pair({ protocol: PROTOCOL, id: this._overlayKey || null }, () => { try { self.attach(mux) } catch {} })
      } catch { this._paired.delete(mux) }
    }

    const channel = mux.createChannel({
      protocol: PROTOCOL,
      id: this._overlayKey || null,
      onopen () {
        const peer = self._peers.get(mux)
        if (peer) self._onOpen(peer)
      },
      onclose () {
        const peer = self._peers.get(mux)
        self._peers.delete(mux)
        // Failover: let any active multi-source fetch reassign this peer's
        // inflight chunks to the remaining peers.
        if (peer) for (const sched of self._schedulers.values()) sched.removePeer(peer)
        // Fail in-flight tree requests now, so a mid-stream disconnect rejects
        // promptly instead of stranding the request (and its buffered pages)
        // until the idle timeout fires.
        if (peer) self._failPendingTrees(peer, new Error('channel closed'))
        // tell the sender-side indicator this peer is gone for every file
        // it was pulling, so a disconnect mid-download clears its row promptly.
        if (peer && self._serveEndCb) {
          for (const [synthPath, from] of peer.authorizedServe) {
            try { self._serveEndCb({ path: synthPath, peer, from }) } catch {}
          }
        }
      }
    })

    if (!channel) return null

    // protomux returns null for a duplicate channel, or one the remote has already closed. Before the
    // pair() registration above this could not be recovered from; now the pair callback will re-drive
    // attach() when the remote asks again, so returning cleanly here is the correct behaviour. It also
    // stops a null-deref on the addMessage calls below, which the consumer's try/catch would have
    // swallowed into a silent "this DID has no byte transfer".
    if (!channel) return null

    const peer = {
      mux, channel, remoteFeedKey: null, remoteSeq: 0, msgs: {},
      pendingTrees: new Map(), // hash → { resolve, reject, timer, timeout, nonce, pages, pageBytes }
      // synthetic serve-paths this peer was authorized for via a
      // GATED _onContentRequest, mapped to the requester profileKey (msg.from).
      // _onChunkNeed serves bytes only for paths in here, so a peer cannot pull
      // bytes by sending chunkNeed/fileRequest directly for a registered path it
      // was never authorized to fetch; the value flows the requester identity to
      // the serve telemetry.
      authorizedServe: new Map()
    }

    peer.msgs.syncState = channel.addMessage({ encoding: messages.syncState, onmessage: (msg) => self._onSyncState(peer, msg) })
    peer.msgs.fileOffer = channel.addMessage({ encoding: messages.fileOffer, onmessage: (msg) => self._onFileOffer(peer, msg) })
    peer.msgs.fileRequest = channel.addMessage({ encoding: messages.fileRequest, onmessage: (msg) => self._onFileRequest(peer, msg) })
    peer.msgs.chunkHashes = channel.addMessage({ encoding: messages.chunkHashes, onmessage: (msg) => self._onChunkHashes(peer, msg) })
    peer.msgs.chunkNeed = channel.addMessage({ encoding: messages.chunkNeed, onmessage: (msg) => self._onChunkNeed(peer, msg) })
    peer.msgs.chunkData = channel.addMessage({ encoding: messages.chunkData, onmessage: (msg) => self._onChunkData(peer, msg) })
    peer.msgs.chunkCancel = channel.addMessage({ encoding: messages.chunkCancel, onmessage: (msg) => self._onChunkCancel(peer, msg) })
    peer.msgs.transferComplete = channel.addMessage({ encoding: messages.transferComplete, onmessage: (msg) => self._onTransferComplete(peer, msg) })
    peer.msgs.conflict = channel.addMessage({ encoding: messages.conflict, onmessage: (msg) => self._onConflictMsg(peer, msg) })

    // 0.5a — tree messages (backward-compatible additions)
    peer.msgs.treeRequest = channel.addMessage({ encoding: messages.treeRequest, onmessage: (msg) => self._onTreeRequest(peer, msg) })
    peer.msgs.treeResponse = channel.addMessage({ encoding: messages.treeResponse, onmessage: (msg) => self._onTreeResponse(peer, msg) })
    peer.msgs.contentRequest = channel.addMessage({ encoding: messages.contentRequest, onmessage: (msg) => self._onContentRequest(peer, msg) })
    // Appended-last slots: ids 0-11 stay fixed for peers without 12/13, which just
    // never register these channels and ignore the frames.
    peer.msgs.transferControl = channel.addMessage({ encoding: messages.transferControl, onmessage: (msg) => self._onTransferControl(peer, msg) })
    peer.msgs.transferProgress = channel.addMessage({ encoding: messages.transferProgress, onmessage: (msg) => self._onTransferProgress(peer, msg) })

    this._peers.set(mux, peer)
    channel.open({ version: VERSION, capabilities: CAP_LOCAL_FILES | CAP_ADAPTIVE_CHUNKS })
    return peer
  }

  // Explicit teardown — unlike an incidental channel close (where the pair callback must SURVIVE so a
  // returning peer can re-pair), detach means we are done with this mux, so drop the pairing too.
  detach (mux) {
    if (this._paired.delete(mux)) {
      try { mux.unpair({ protocol: PROTOCOL, id: this._overlayKey || null }) } catch {}
    }
    const peer = this._peers.get(mux)
    if (!peer) return
    peer.channel.close()
    this._peers.delete(mux)
  }

  setFilePaths (map) { this._filePaths = map }

  broadcastOffer (overlayPath, meta, op) {
    const msg = {
      path: overlayPath,
      contentHash: meta.contentHash || '0'.repeat(64),
      size: meta.size || 0,
      mtime: meta.mtime || 0,
      op
    }
    for (const [, peer] of this._peers) {
      peer.msgs.fileOffer.send(msg)
    }
  }

  requestFile (peer, overlayPath, contentHash, chunksHave) {
    peer.msgs.fileRequest.send({
      path: overlayPath,
      contentHash,
      chunksHave: chunksHave || null
    })
  }

  /**
   * 0.5a: Request a tree by its hash. Returns a promise resolving to
   * the entries array (or [] if the peer doesn't have it).
   *
   * @param {object} peer
   * @param {string} hash - hex tree hash
   * @param {object} [opts]
   * @param {number} [opts.timeout] - override default timeout (ms)
   * @returns {Promise<Array<{kind, exec, name, childHash, size}>>}
   */
  requestTree (peer, hash, opts = {}) {
    return new Promise((resolve, reject) => {
      // If a prior request is already in-flight for the same hash, reject
      // this caller rather than silently overwrite.
      if (peer.pendingTrees.has(hash)) {
        return reject(new Error('tree request already pending for hash: ' + hash))
      }
      // Per-request nonce (per-peer monotonic, non-zero) lets the requester drop
      // pages echoed for a superseded attempt. pages/pageBytes buffer this
      // request's own paged response, so a fresh attempt never inherits an old one.
      const nonce = peer._treeReqSeq = (peer._treeReqSeq || 0) + 1
      const timeout = opts.timeout || this._treeRequestTimeout
      const pending = { resolve, reject, timer: null, timeout, nonce, pages: null, pageBytes: 0 }
      this._armTreeTimeout(peer, hash, pending)
      peer.pendingTrees.set(hash, pending)
      peer.msgs.treeRequest.send({ hash, nonce })
    })
  }

  // Arm (or re-arm) a pending tree request's idle timeout. Every buffered page
  // re-arms it (a large tree on a slow link mustn't time out mid-stream); the
  // request still settles because the accumulator is byte-capped in _onTreeResponse.
  _armTreeTimeout (peer, hash, pending) {
    pending.timer = setTimeout(() => {
      if (peer.pendingTrees.get(hash) === pending) {
        peer.pendingTrees.delete(hash)
        pending.reject(new Error('tree request timed out: ' + hash))
      }
    }, pending.timeout)
  }

  // Reject every in-flight tree request on this peer (channel close / destroy),
  // clearing timers so the promise fails promptly and its buffered pages release.
  _failPendingTrees (peer, err) {
    for (const [hash, pending] of peer.pendingTrees) {
      clearTimeout(pending.timer)
      peer.pendingTrees.delete(hash)
      pending.reject(err)
    }
  }

  /**
   * 0.5a: Request a file by its content hash. Peer looks up any file
   * matching that hash locally and streams chunks via the existing flow
   * (chunkHashes → chunkNeed → chunkData). Returns immediately; chunks
   * arrive asynchronously.
   *
   * Note: receiver must pre-register a target path mapping via
   * _filePaths keyed by the contentHash before calling, OR this method
   * is a no-op on the receive side. Full wiring deferred to 0.5c.
   */
  requestContent (peer, contentHash, chunksHave) {
    peer.msgs.contentRequest.send({
      contentHash,
      chunksHave: chunksHave || null,
      from: this._localProfileKey || ''   // authenticate the asker to the serve gate
    })
  }

  get peerCount () { return this._peers.size }

  destroy () {
    for (const [, peer] of this._peers) {
      this._failPendingTrees(peer, new Error('protocol destroyed'))
      peer.channel.close()
    }
    this._peers.clear()
  }

  // ── Handlers ────────────────────────────────────────────────

  _onOpen (peer) {
    peer.msgs.syncState.send({
      feedKey: this._syncEngine.feedKey,
      localSeq: this._syncEngine.feed.length,
      remoteSeq: 0
    })
  }

  async _onSyncState (peer, msg) {
    peer.remoteFeedKey = msg.feedKey
    peer.remoteSeq = msg.localSeq

    if (!this._autoSync) return

    try {
      await this._offerLocalFiles(peer)
    } catch {
      // ignore — the HyperOverlay facade runs with autoSync off, so this path is dormant
    }
  }

  async _onFileOffer (peer, msg) {
    // In gated mode the overlay flow is contentRequest→scheduler ONLY; gated
    // consumers never act on path-based file-offers (autoSync is off). Ignoring
    // them closes the offer→requestFile→legacy-receive path a peer could drive.
    if (this._serveAuthorizer) return
    if (this._fileOfferCb) this._fileOfferCb(peer, msg)

    if (msg.op === 0) {
      const local = await this._syncEngine._fileIndex.getFile(msg.path)
      if (!local || local.contentHash !== msg.contentHash) {
        const diskPath = this._resolveFilePath(msg.path)
        if (diskPath) {
          this._filePaths.set(msg.path, diskPath)
          this.requestFile(peer, msg.path, msg.contentHash)
        }
      }
    }
  }

  async _onFileRequest (peer, msg) {
    // path-based serve is UNGATED and would bypass the serve gate (the only
    // gated entry point is _onContentRequest). Gated consumers never send
    // fileRequest, so refuse it outright in gated mode.
    if (this._serveAuthorizer) return
    const diskPath = this._filePaths.get(msg.path)
    if (!diskPath) return

    const prepared = await this._transferManager.prepareFile(diskPath, msg.path)
    if (!prepared) return

    if (!await this._transferManager._fileIndex.hasChunkMap(msg.path)) {
      await this._transferManager._fileIndex.putChunkMap(msg.path, prepared.chunks)
    }

    this._sendChunkHashes(peer, msg.path, prepared.tier, prepared.chunks)
  }

  // Send a file's chunk list, paged so no single frame exceeds
  // the Noise transport's 16 MiB-1 atomic-write limit. A list that fits in one
  // frame ships as one (more:0) — identical on the wire to the pre-paging format
  // bar a trailing 0 byte. _onChunkHashes reassembles the pages on the receiver.
  _sendChunkHashes (peer, filePath, tier, chunks) {
    const total = chunks.length
    if (total <= MAX_CHUNKS_PER_MSG) {
      peer.msgs.chunkHashes.send({ path: filePath, tier, chunks, more: 0 })
      return
    }
    for (let i = 0; i < total; i += MAX_CHUNKS_PER_MSG) {
      const more = i + MAX_CHUNKS_PER_MSG < total ? 1 : 0
      peer.msgs.chunkHashes.send({ path: filePath, tier, chunks: chunks.slice(i, i + MAX_CHUNKS_PER_MSG), more })
    }
  }

  // Reassemble a paged chunk list. Returns the full list once
  // the final page (more:0) arrives, or null while pages are still pending. A
  // lone complete frame passes straight through (no copy) — the small-file norm.
  // Pages for a path arrive in order on this channel, so arrival-order
  // concatenation is correct even when several files interleave on it.
  _reassembleChunkHashes (peer, msg) {
    const buffered = peer._chunkHashPages && peer._chunkHashPages.get(msg.path)
    if (!buffered && !msg.more) return msg.chunks
    if (!peer._chunkHashPages) peer._chunkHashPages = new Map()
    let acc = buffered
    if (!acc) { acc = []; peer._chunkHashPages.set(msg.path, acc) }
    for (const ch of msg.chunks) acc.push(ch)
    if (acc.length > MAX_CHUNK_HASHES_ACCUM) {
      // F2: drop an over-long accumulator (adversarial more:1 flood) instead of
      // OOMing — the fetch fails and the scheduler retries/fails over to another peer.
      peer._chunkHashPages.delete(msg.path)
      return null
    }
    if (msg.more) return null
    peer._chunkHashPages.delete(msg.path)
    return acc
  }

  // Upper-bound wire size of one tree entry: kind(1)+exec(1) + string(len-prefix ≤4
  // + utf8 name bytes) + childHash(32) + size varint(≤9, c.uint is 9 bytes for a
  // >4 GiB size).
  _treeEntryWireSize (e) {
    return 1 + 1 + 4 + Buffer.byteLength(e.name || '', 'utf8') + 32 + 9
  }

  // Send a tree's entries, paged so no single frame exceeds the Noise atomic-write
  // limit; every page but the last is more:1. A tree that fits in one frame ships
  // as a single more:0 frame. `nonce` echoes the requester's per-request id.
  _sendTreeResponse (peer, hash, entries, nonce) {
    let page = []
    let bytes = 40 // hash(32) + uint32 count prefix + more/nonce + slack
    for (const e of entries) {
      const sz = this._treeEntryWireSize(e)
      if (page.length && bytes + sz > MAX_TREE_BYTES_PER_MSG) {
        peer.msgs.treeResponse.send({ hash, entries: page, more: 1, nonce })
        page = []
        bytes = 40
      }
      page.push(e)
      bytes += sz
    }
    peer.msgs.treeResponse.send({ hash, entries: page, more: 0, nonce })
  }

  async _onChunkHashes (peer, msg) {
    // Reassemble paged chunkHashes frames before dispatching.
    const chunks = this._reassembleChunkHashes(peer, msg)
    if (chunks === null) {
      // A buffered page is forward progress — keep the scheduler's no-progress
      // watchdog from tripping while a large map streams across several frames.
      const pending = this._schedulers.get(msg.path)
      if (pending && pending.notePageProgress) pending.notePageProgress()
      return
    }
    // Multi-source: if a scheduler owns this path, let it distribute chunk
    // requests across peers instead of the legacy single-peer auto-flow.
    const sched = this._schedulers.get(msg.path)
    if (sched) { Promise.resolve(sched.onChunkHashes(peer, chunks)).catch(() => {}); return }
    // the legacy single-peer receive below writes incoming bytes
    // to _filePaths.get(path) and finalizes a rename OVER it. For a path that
    // maps to one of OUR own files (e.g. a content-register entry), an
    // unsolicited chunkHashes would let a peer overwrite the owner's source
    // file. Gated consumers only ever receive via a scheduler (the branch
    // above), so refuse any other receive.
    if (this._serveAuthorizer) return

    const diskPath = this._filePaths.get(msg.path)
    if (!diskPath) return

    // Empty-file fast path: a 0-byte file has no chunks to fetch, but we
    // still need to materialize it on disk and fire the same completion
    // flow as _onChunkData's "transfer complete" branch — otherwise the
    // requester (e.g. NetworkFetcher.getContent) never resolves and times
    // out. Empty files are common in practice: .keep, __init__.py, build
    // stamps, touch'd markers. Bug symptom before this: node-gyp's
    // postinstall.stamp (0 bytes, hash 5187b7a8…) hung every install.
    if (chunks.length === 0) {
      try {
        fs.mkdirSync(path.dirname(diskPath), { recursive: true })
        fs.writeFileSync(diskPath, Buffer.alloc(0))
      } catch {
        return
      }
      const contentHash = hashChunk(Buffer.alloc(0))
      const stat = fs.statSync(diskPath)
      await this._syncEngine._fileIndex.putFile(msg.path, {
        contentHash, size: 0, mtime: stat.mtimeMs
      })
      await this._syncEngine.logPut(msg.path, {
        contentHash, size: 0, mtime: stat.mtimeMs
      })
      if (peer.remoteFeedKey) {
        await this._syncEngine.markSynced(peer.remoteFeedKey, msg.path, contentHash, this._syncEngine.feed.length - 1)
      }
      peer.msgs.transferComplete.send({ path: msg.path, contentHash })
      if (this._syncedCb) {
        this._syncedCb({ path: msg.path, contentHash, size: 0, direction: 'pull' })
      }
      return
    }

    const needed = this._transferManager.computeNeeded(chunks, new Set())
    if (needed.length === 0) return

    let size = 0
    for (const c of chunks) size += c.length

    // startReceive can throw (fd open, partial recovery) — drop this transfer
    // instead of rejecting protomux's onmessage promise, which would destroy the
    // whole Noise stream.
    try {
      await this._transferManager.startReceive(diskPath, {
        size,
        chunks: chunks.map((c, i) => {
          let offset = 0
          for (let j = 0; j < i; j++) offset += chunks[j].length
          return { hash: c.hash, offset, length: c.length }
        })
      })
    } catch {
      return
    }

    peer.msgs.chunkNeed.send({ path: msg.path, indices: needed })
  }

  async _onChunkNeed (peer, msg) {
    // Serve bytes ONLY for a synthetic path THIS peer was
    // authorized for via a gated _onContentRequest. Blocks a peer pulling bytes
    // by sending chunkNeed directly for a registered path (e.g. a content-
    // register entry) without passing the serve gate.
    if (this._serveAuthorizer && !peer.authorizedServe.has(msg.path)) return
    const diskPath = this._filePaths.get(msg.path)
    if (!diskPath) return

    const fileIndex = this._transferManager._fileIndex
    let chunkMap
    try {
      chunkMap = msg.path.startsWith('content:')
        ? await fileIndex.getChunkMapByHash(msg.path.slice('content:'.length))
        : await fileIndex.getChunkMap(msg.path)
    } catch {
      // A chunk-map lookup that rejects (index I/O error) must not escape: the
      // onmessage dispatch is fire-and-forget, so a throw here becomes an
      // unhandled rejection that tears down the whole Noise/replication stream
      // (the S1 malformed-frame hardening theme). Drop the request — the receiver
      // re-requests and the transfer self-heals.
      return
    }
    if (!chunkMap) return

    // Single-flight serve pump per peer (mirrors hypercore's `receiverBusy`).
    // Enqueue this request; if no pump is running for this peer, drain the queue
    // one job at a time. protomux dispatches every frame of a socket read
    // synchronously, so two chunkNeed frames from the same peer would otherwise
    // start rival serve loops — interleaving sends and admitting N chunks per
    // drain instead of honouring backpressure once (the discipline our 0.2.13
    // serialized pump had, which Ok's inline loop dropped).
    if (!peer._serveQueue) peer._serveQueue = []
    peer._serveQueue.push({ path: msg.path, diskPath, chunkMap, indices: msg.indices })
    if (peer._serving) return
    peer._serving = true
    // Kick the pump fire-and-forget: _onChunkNeed is the onmessage dispatch and
    // must return promptly, NOT stay pending for the whole multi-chunk serve
    // (which, under backpressure, spans the entire transfer). A second chunkNeed
    // from this peer just enqueues above; the single-flight guard (_serving) means
    // this already-running pump picks it up. This is our 0.2.13 contract — Ok's
    // inline drain loop awaited the whole serve, which the unit test rejects.
    this._drainServeQueue(peer)
  }

  // The per-peer single-flight serve pump. Drains _serveQueue one job at a time;
  // detached from _onChunkNeed so the message handler returns immediately.
  async _drainServeQueue (peer) {
    try {
      while (peer._serveQueue.length) {
        if (peer.channel && peer.channel.closed) { peer._serveQueue.length = 0; break }
        try { await this._serveJob(peer, peer._serveQueue.shift()) } catch {}
      }
    } finally {
      peer._serving = false
    }
  }

  // Serve one chunkNeed job: stream the requested chunk indices, honouring
  // stream backpressure — stop producing when send() reports the write buffer is
  // full and resume on drain, so sibling protocols multiplexed on the same Noise
  // stream (Corestore replication, app handshakes; protomux MAX_BACKLOG is
  // Infinity) aren't starved. Runs under the per-peer single-flight guard in
  // _onChunkNeed, so exactly one job streams at a time per peer.
  async _serveJob (peer, job) {
    const { path, diskPath, chunkMap, indices } = job
    // P3: cork the mux so a run of chunkData frames coalesces into fewer stream
    // writes (hypercore's replicator corks around each request/response batch,
    // replicator.js:722/738). Uncork before parking on drain (flush the batch) and
    // in the finally. The `corked` guard keeps cork/uncork balanced across the early
    // returns, and no-ops when the mux stub has no cork (unit tests).
    const mux = peer.mux
    let corked = false
    const cork = () => { if (mux && mux.cork && !corked) { mux.cork(); corked = true } }
    const uncork = () => { if (mux && mux.uncork && corked) { mux.uncork(); corked = false } }
    cork()
    try {
      for (let i = 0; i < indices.length; i++) {
        const index = indices[i]
        if (index >= chunkMap.length) continue
        // P5: skip a chunk the downloader hotswapped away (one-shot).
        if (peer._cancelledServe && peer._cancelledServe.delete(path + '\x00' + index)) continue
        const c = chunkMap[index]
        const data = this._transferManager.readChunk(diskPath, c.offset, c.length)
        if (!data) continue
        const flushed = peer.msgs.chunkData.send({ path, index, data })
        if (this._chunkServeCb) {
          try { this._chunkServeCb({ path, index, bytes: data.length, peer, from: peer.authorizedServe.get(path) || null }) } catch {}
        }
        if (flushed === false && i < indices.length - 1) {
          uncork()   // flush the corked batch before we park on drain
          const alive = await this._waitForDrain(peer)
          if (!alive) return
          if (this._serveAuthorizer && !peer.authorizedServe.has(path)) return
          cork()     // re-cork for the next run
        }
      }
    } finally {
      uncork()
    }
  }

  // One shared drain waiter per peer so concurrent serve loops (the
  // same peer pulling several files) don't each attach listeners and trip the stream's
  // max-listeners warning. Resolves true on drain, false if the channel closes first.
  _waitForDrain (peer) {
    const channel = peer.channel
    const stream = peer.mux && peer.mux.stream
    if (!stream || !channel || channel.closed) return Promise.resolve(false)
    if (channel.drained) return Promise.resolve(true)
    if (peer._drainWait) return peer._drainWait
    peer._drainWait = new Promise((resolve) => {
      let timer = null
      const done = (alive) => {
        if (timer) clearTimeout(timer)
        stream.removeListener('drain', onDrain)
        stream.removeListener('close', onClose)
        peer._drainWait = null
        resolve(alive)
      }
      const onDrain = () => done(!channel.closed)
      const onClose = () => done(false)
      timer = setTimeout(() => done(false), DRAIN_TIMEOUT_MS)
      stream.on('drain', onDrain)
      stream.on('close', onClose)
    })
    return peer._drainWait
  }

  async _onChunkData (peer, msg) {
    // Silent-peer watchdog hook: fire BEFORE any processing so a slow disk
    // doesn't get blamed for a peer-side stall.
    if (this._chunkProgressCb) {
      try {
        this._chunkProgressCb({
          path: msg.path,
          index: msg.index,
          bytes: msg.data ? msg.data.length : 0,
          peer
        })
      } catch {}
    }

    // Multi-source: scheduler owns the write + reschedule + finalize.
    const sched = this._schedulers.get(msg.path)
    if (sched) { Promise.resolve(sched.onChunkData(peer, msg.index, msg.data)).catch(() => {}); return }
    // refuse the legacy single-peer receive (writes+renames over
    // _filePaths.get(path), i.e. potentially our own source file). Gated
    // consumers only receive via a scheduler (above).
    if (this._serveAuthorizer) return

    const diskPath = this._filePaths.get(msg.path)
    if (!diskPath) return

    const result = this._transferManager.writeChunk(diskPath, msg.index, msg.data)

    if (result.ok && this._transferManager.isComplete(diskPath)) {
      const fin = await this._transferManager.finalize(diskPath)
      if (fin.ok) {
        const fileData = fs.readFileSync(diskPath)
        const contentHash = hashChunk(fileData)
        const stat = fs.statSync(diskPath)

        await this._syncEngine._fileIndex.putFile(msg.path, {
          contentHash, size: stat.size, mtime: stat.mtimeMs
        })
        await this._syncEngine.logPut(msg.path, {
          contentHash, size: stat.size, mtime: stat.mtimeMs
        })

        if (peer.remoteFeedKey) {
          await this._syncEngine.markSynced(peer.remoteFeedKey, msg.path, contentHash, this._syncEngine.feed.length - 1)
        }

        peer.msgs.transferComplete.send({ path: msg.path, contentHash })
        if (this._syncedCb) {
          this._syncedCb({ path: msg.path, contentHash, size: stat.size, direction: 'pull' })
        }
      }
    }
  }

  _onChunkCancel (peer, msg) {
    if (msg.indices && msg.indices.length) {
      // P5 hotswap: the downloader reassigned these chunk indices to a faster peer.
      // Stop serving just them (bandwidth save); the rest of the transfer continues.
      // Consumed one-shot in _serveJob.
      if (!peer._cancelledServe) peer._cancelledServe = new Set()
      for (const i of msg.indices) peer._cancelledServe.add(msg.path + '\x00' + i)
      return
    }
    const diskPath = this._filePaths.get(msg.path)
    if (diskPath) this._transferManager.cancel(diskPath)
  }

  // Resolve a serve-message's downloader identity from the authenticated record we kept at
  // serve start (peer.authorizedServe), NOT the message — so a peer can only affect its own
  // ledger row, never spoof another. Returns null `from` when the peer was never authorized.
  _authorizedFrom (peer, contentHash) {
    const synthPath = 'content:' + contentHash
    return { synthPath, from: peer.authorizedServe.get(synthPath) || null }
  }

  // A downloader told us it paused/stopped pulling a hash WE serve.
  _onTransferControl (peer, msg) {
    if (!this._serveControlCb) return
    const { synthPath, from } = this._authorizedFrom(peer, msg.contentHash)
    if (!from) return
    const state = msg.state === CONTROL_STOPPED ? 'stopped' : 'paused'
    try { this._serveControlCb({ path: synthPath, peer, from, state }) } catch {}
  }

  // A downloader reported its on-disk have-bytes for a hash WE serve.
  _onTransferProgress (peer, msg) {
    if (!this._serveProgressCb) return
    const { synthPath, from } = this._authorizedFrom(peer, msg.contentHash)
    if (!from) return
    try { this._serveProgressCb({ path: synthPath, peer, from, have: msg.have || 0 }) } catch {}
  }

  async _onTransferComplete (peer, msg) {
    if (peer.remoteFeedKey) {
      await this._syncEngine.markSynced(peer.remoteFeedKey, msg.path, msg.contentHash, this._syncEngine.feed.length - 1)
    }
    if (this._syncedCb) {
      this._syncedCb({ path: msg.path, contentHash: msg.contentHash, direction: 'push' })
    }
  }

  _onConflictMsg (peer, msg) {
    if (this._conflictCb) this._conflictCb(msg, peer)
  }

  // ── Tree handlers (0.5a) ────────────────────────────────────

  async _onTreeRequest (peer, msg) {
    // ungated metadata oracle (returns fileIndex tree entries to
    // any peer). Gated consumers never use tree-requests; refuse in gated mode.
    if (this._serveAuthorizer) return
    let entries = []
    try {
      const fileIndex = this._syncEngine && this._syncEngine._fileIndex
      if (fileIndex && typeof fileIndex.getTree === 'function') {
        const tree = await fileIndex.getTree(msg.hash)
        if (tree && Array.isArray(tree.entries)) entries = tree.entries
      }
    } catch {
      // fall through — reply with empty entries on lookup failure
    }
    this._sendTreeResponse(peer, msg.hash, entries, msg.nonce || 0)
  }

  _onTreeResponse (peer, msg) {
    const pending = peer.pendingTrees.get(msg.hash)
    if (!pending) return // no request in flight — drop
    // Drop a page echoing a superseded request's nonce (a stale page from a
    // timed-out attempt must not settle a later same-hash retry). nonce 0 = a
    // pre-nonce holder that can't echo it, so fall through (unverifiable,
    // best-effort).
    if (msg.nonce && msg.nonce !== pending.nonce) return
    const settle = (fn, arg) => { clearTimeout(pending.timer); peer.pendingTrees.delete(msg.hash); fn(arg) }
    // Lone complete frame (the small-tree norm) resolves with no accumulation.
    if (!pending.pages && !msg.more) return settle(pending.resolve, msg.entries)
    // Accumulate into THIS request's own buffer, bounding total bytes so a peer
    // streaming pages endlessly rejects the request instead of growing memory
    // without limit.
    if (!pending.pages) pending.pages = []
    for (const e of msg.entries) { pending.pages.push(e); pending.pageBytes += this._treeEntryWireSize(e) }
    if (pending.pageBytes > this._maxTreeResponseBytes) {
      return settle(pending.reject, new Error('tree response too large: ' + msg.hash))
    }
    if (msg.more) {
      // Forward progress — re-arm the idle timeout so a legitimately large
      // multi-page tree on a slow link doesn't time out mid-stream.
      clearTimeout(pending.timer)
      this._armTreeTimeout(peer, msg.hash, pending)
      return
    }
    settle(pending.resolve, pending.pages)
  }

  async _onContentRequest (peer, msg) {
    // serve gate. BEFORE any path resolution or streaming,
    // ask the injected authorizer whether this peer may receive this hash.
    // Deny == silent return (same observable behavior as "I don't hold it"
    // below), so a non-member cannot distinguish denial from absence.
    if (this._serveAuthorizer) {
      let ok = false
      try { ok = await this._serveAuthorizer(peer, msg.from || null, msg.contentHash) } catch { ok = false }
      if (!ok) return
    }

    // Resolve msg.contentHash → an on-disk path. Three lookup paths, in order:
    //  1. Fast path: _contentHashPaths map (warm — just-registered / consumer-set)
    //  2. Content-addressed spool (RESTART-DURABLE): <syncBaseDir>/<contentHash>
    //  3. Slow path: scan fileIndex for any file:* entry with that hash
    let diskPath = this._contentHashPaths.get(msg.contentHash)
    if (diskPath && !fileExists(diskPath)) diskPath = null

    if (!diskPath) {
      // Content-addressed spool fallback. The canonical bytes are stored at
      // <syncBaseDir>/<contentHash> (fetchFile's mirrorSpool option writes them
      // there). The in-memory map is empty after a restart and FileIndex
      // persists no disk path, so resolve the hash straight from the
      // content-addressed spool — a pure function of the hash + dir, like a
      // git object store's blobs/<hash>. This is what lets a node keep serving
      // its spooled files across restarts with no rehydration step.
      for (const baseDir of this._syncBaseDirs) {
        const candidate = path.join(baseDir, msg.contentHash)
        if (fileExists(candidate)) {
          diskPath = candidate
          this._contentHashPaths.set(msg.contentHash, candidate) // warm the map
          break
        }
      }
    }

    if (!diskPath) {
      // Scan fileIndex for a file whose contentHash matches.
      const fileIndex = this._syncEngine && this._syncEngine._fileIndex
      if (fileIndex && typeof fileIndex.listFiles === 'function') {
        try {
          const files = await fileIndex.listFiles()
          for (const f of files) {
            if (f.contentHash === msg.contentHash) {
              diskPath = this._filePaths.get(f.path)
              if ((!diskPath || !fileExists(diskPath)) && f.path) diskPath = f.path
              break
            }
          }
        } catch {
          // fall through
        }
      }
    }

    if (!diskPath) {
      // Don't have it. Silently drop; requester will time out.
      return
    }

    // Use a SYNTHETIC path keyed by contentHash so the requester can
    // pre-register a destPath and the chunk flow routes correctly on
    // both sides. Temporarily populate _filePaths so _onChunkNeed +
    // chunkmap lookups resolve under this synthetic key.
    const syntheticPath = 'content:' + msg.contentHash
    this._filePaths.set(syntheticPath, diskPath)
    peer.authorizedServe.set(syntheticPath, msg.from || null) // _onChunkNeed will now serve this path to THIS peer

    // The chunk map is content-addressed — identical bytes always chunk
    // identically. Reuse one computed at publish (or a prior serve, or before a
    // restart) instead of re-reading the whole file to re-chunk it: re-chunking a
    // multi-GB file adds a full-file-read of latency before the first byte ships.
    const fileIndex = this._transferManager._fileIndex
    let chunks = await fileIndex.getChunkMapByHash(msg.contentHash)
    let tier
    if (chunks) {
      let size = 0
      try { size = fs.statSync(diskPath).size } catch {}
      tier = selectTier(size)
    } else {
      const prepared = await this._transferManager.prepareFile(diskPath, syntheticPath, { byHashOnly: true })
      if (!prepared) {
        this._filePaths.delete(syntheticPath)
        peer.authorizedServe.delete(syntheticPath) // keep the authorize map in lockstep with _filePaths
        return
      }
      chunks = prepared.chunks
      tier = prepared.tier
      // prepareFile persists the hash-keyed map only for large files; ensure it exists
      // for small ones too, since _onChunkNeed resolves content:<hash> by that map.
      if (!await fileIndex.hasChunkMapByHash(msg.contentHash)) await fileIndex.putChunkMapByHash(msg.contentHash, chunks)
    }
    this._sendChunkHashes(peer, syntheticPath, tier, chunks)
    if (this._serveStartCb) {
      let total = 0
      for (const c of chunks) total += c.length || 0
      try { this._serveStartCb({ path: syntheticPath, peer, from: msg.from || null, total }) } catch {}
    }
  }

  // ── Sync logic ──────────────────────────────────────────────

  /**
   * Offer all local files to a peer.
   * Reads from the local feed and sends file-offers for each entry.
   * The peer will request any files it doesn't have.
   */
  async _offerLocalFiles (peer) {
    const feed = this._syncEngine.feed
    if (feed.length === 0) return

    for (let i = 0; i < feed.length; i++) {
      try {
        const entry = await feed.get(i)
        if (!entry) continue

        // Register the file path mapping so _onFileRequest can find it
        if (entry.op === 0) {
          const diskPath = this._resolveFilePath(entry.path)
          if (diskPath) this._filePaths.set(entry.path, diskPath)
        }

        peer.msgs.fileOffer.send({
          path: entry.path,
          contentHash: entry.hash || '0'.repeat(64),
          size: entry.size || 0,
          mtime: entry.mtime || 0,
          op: entry.op
        })
      } catch {
        // skip unreadable entries
      }
    }
  }

  /**
   * Resolve an overlay path to a disk path.
   */
  _resolveFilePath (overlayPath) {
    if (this._filePaths.has(overlayPath)) return this._filePaths.get(overlayPath)
    for (const baseDir of this._syncBaseDirs) {
      return path.join(baseDir, overlayPath)
    }
    return null
  }
}

// True iff `p` is an existing regular file. Used to validate a resolved disk path
// before serving (a cold-restart map entry or an overlay-path masquerading as a
// disk path won't exist) and to probe the content-addressed spool.
function fileExists (p) {
  try { return fs.statSync(p).isFile() } catch { return false }
}
/**
 * TransferManager — On-demand chunking and streaming file transfer
 *
 * Sender side:
 *   1. Read file from disk
 *   2. Chunk on demand (adaptive FastCDC)
 *   3. Send chunk hashes → peer says which it needs → stream those chunks
 *   4. Release chunks from memory after send
 *
 * Receiver side:
 *   1. Receive chunk hashes, respond with which are missing
 *   2. Receive chunks, write to <file>.overlay-partial
 *   3. Verify each chunk hash
 *   4. Atomic rename on completion
 *
 * No blob storage. Chunks exist in memory during transfer only.
 */

import fs from 'bare-fs'
import path from 'bare-path'
import os from 'bare-os'
import c from 'compact-encoding'
import sodium from 'sodium-universal'
import { chunk as chunkBuffer, chunkStream, hashChunk, createStreamingHasher, selectTier, getTierParams } from './chunker.js'

export const PARTIAL_SUFFIX = '.overlay-partial'

export const partialPathFor = (targetPath) => path.join(path.dirname(targetPath), path.basename(targetPath) + PARTIAL_SUFFIX)

// Resume verification + whole-file hashing run async + yielding so a multi-GB pass
// never blocks the event loop (IPC, pause/cancel, progress stay live).
const VERIFY_YIELD_EVERY = 64
const yieldToLoop = () => new Promise((resolve) => setTimeout(resolve, 0))

// Receive journal: bitmap + whole-file hash snapshot, stored in app-private storage
// (opts.journalDir) keyed by a digest of the destination path — never next to the
// download — so resume is O(1) and the incremental verify continues with no re-read.
const JOURNAL_SUFFIX = '.journal'
const JOURNAL_MAGIC = 0x4d4a5633
const JOURNAL_FLUSH_EVERY = 512
const JOURNAL_FLUSH_INTERVAL_MS = 1500
export const journalNameFor = (targetPath) => hashChunk(Buffer.from(targetPath)) + JOURNAL_SUFFIX

// Block-read prepare — host-adaptive read block size.
const READ_BLOCK_DEFAULT = 8 * 1024 * 1024
const READ_BLOCK_LOW_RAM = 4 * 1024 * 1024
const LOW_RAM_THRESHOLD = 6 * 1024 * 1024 * 1024

// Cap on stashed just-received chunk buffers (per transfer, insert-refusal), and the
// hash pump's yield budget — stash-fed iterations never await, so the pump yields by
// bytes hashed, not chunk count.
const MEM_STASH_BYTES_DEFAULT = 16 * 1024 * 1024
const DRAIN_YIELD_BYTES = 2 * 1024 * 1024

// Emit file blocks >= tier maxSize so chunkStream drains every block
// instead of re-copying a growing pending buffer (64 KiB createReadStream reads force
// ~56x memcpy at tier 3). Owns its fd and closes it on every terminal path: normal
// end, consumer break/throw (for-await calls .return() → finally), read error.
async function * readFileBlocks (filePath, size, blockSize) {
  const fd = await fs.open(filePath, 'r')
  try {
    let pos = 0
    while (pos < size) {
      const want = Math.min(blockSize, size - pos)
      const buf = Buffer.allocUnsafe(want)
      let filled = 0
      while (filled < want) {
        const n = await fs.read(fd, buf, filled, want - filled, pos + filled)
        if (n <= 0) break
        filled += n
      }
      if (filled === 0) break
      pos += filled
      yield filled === buf.length ? buf : buf.subarray(0, filled)
    }
  } finally {
    try { await fs.close(fd) } catch {}
  }
}

function bitmapOf (received, total) {
  const bm = Buffer.alloc((total + 7) >> 3)
  for (const i of received) bm[i >> 3] |= 1 << (i & 7)
  return bm
}

// Drop journals whose partial is gone, are stale, or are corrupt/foreign. Each
// journal self-describes its partial path (read cheaply from the header). Standalone
// (no TransferManager instance) so a boot-time sweep can reclaim orphans without
// the overlay stack being live.
export function cleanupOrphanedJournals (journalDir, maxAge = 7 * 86400000) {
  const cleaned = []
  if (!journalDir) return cleaned
  let entries
  try { entries = fs.readdirSync(journalDir) } catch { return cleaned }
  const now = Date.now()
  for (const entry of entries) {
    if (!entry.endsWith(JOURNAL_SUFFIX)) continue
    const full = path.join(journalDir, entry)
    let remove = false
    try {
      const raw = fs.readFileSync(full)
      const s = { start: 0, end: raw.length, buffer: raw }
      if (c.uint32.decode(s) !== JOURNAL_MAGIC) remove = true
      else {
        c.uint32.decode(s)
        const partialPath = c.string.decode(s)
        if (!fs.existsSync(partialPath)) remove = true
        else if (now - fs.statSync(full).mtimeMs > maxAge) remove = true
      }
    } catch { remove = true }
    if (remove) { try { fs.unlinkSync(full); cleaned.push(full) } catch {} }
  }
  return cleaned
}

export class TransferManager {
  constructor (fileIndex, opts = {}) {
    this._fileIndex = fileIndex
    this._maxConcurrency = opts.maxConcurrency || 4
    this._active = new Map() // path → transfer state
    this._memStashBytes = opts.memStashBytes ?? MEM_STASH_BYTES_DEFAULT
    this._journalDir = opts.journalDir || null
    if (this._journalDir) { try { fs.mkdirSync(this._journalDir, { recursive: true }) } catch {} }
  }

  _journalPathFor (targetPath) {
    if (!this._journalDir) return null
    return path.join(this._journalDir, journalNameFor(targetPath))
  }

  // ── Sender side ─────────────────────────────────────────────

  /**
   * Prepare a file for sending — read from disk, chunk, return hashes.
   * Chunk map is persisted for large files (>1MB).
   *
   * @param {string} filePath - absolute path on disk
   * @param {string} overlayPath - path in the overlay namespace
   * @returns {{ tier: number, chunks: Array<{ hash, offset, length }>, size: number } | null}
   */
  async prepareFile (filePath, overlayPath, opts = {}) {
    // Check file exists and get stats
    let stat
    try {
      stat = fs.statSync(filePath)
    } catch {
      return null
    }

    if (!stat.isFile()) return null

    const mtimeBefore = stat.mtimeMs

    // Check for existing chunk map (skip re-chunking if valid)
    const existing = await this._fileIndex.getChunkMap(overlayPath)
    const fileMeta = await this._fileIndex.getFile(overlayPath)

    if (existing && fileMeta && fileMeta.mtime === mtimeBefore) {
      // Chunk map still valid — file hasn't changed.
      // Backfill the content-addressed alias so the serve path (a
      // different overlay key) reuses this map instead of re-reading the file —
      // covers maps chunked before content-addressed caching existed.
      if (fileMeta.contentHash && stat.size >= 1048576 && !await this._fileIndex.hasChunkMapByHash(fileMeta.contentHash)) {
        await this._fileIndex.putChunkMapByHash(fileMeta.contentHash, existing)
      }
      return {
        tier: selectTier(stat.size),
        chunks: existing,
        size: stat.size,
        contentHash: fileMeta.contentHash // surfaced so the publish path gets the hash from one pass
      }
    }

    // Read in blocks >= tier maxSize and chunk from views (copy:false).
    // Cut points are content-stable across block sizes, so peers still match hashes.
    // size is passed to the hasher so the streaming digest matches hashChunk(buffer).
    const tier = selectTier(stat.size)
    const blockSize = Math.max(
      getTierParams(tier).maxSize,
      os.totalmem() < LOW_RAM_THRESHOLD ? READ_BLOCK_LOW_RAM : READ_BLOCK_DEFAULT
    )
    const fileHasher = createStreamingHasher({ size: stat.size })
    const chunks = []

    for await (const c of chunkStream(readFileBlocks(filePath, stat.size, blockSize), { tier, copy: false })) {
      // honor a publish-cancel signal (per-chunk granularity); on throw the
      // for-await calls readFileBlocks.return() which closes the fd.
      if (opts.signal?.aborted) { const e = new Error('publish aborted'); e.code = 'ECANCELLED'; throw e }
      fileHasher.update(c.data)
      chunks.push({ hash: c.hash, offset: c.offset, length: c.length })
      opts.onProgress?.(c.length)
    }

    // mtime guard — abort if file changed during the streaming read. A
    // vanished source (moved out of the folder mid-read) is just another "changed
    // during read" case — the open fd kept the read alive after a same-volume
    // rename, so it is this path-resolving stat that trips ENOENT. Guard it like
    // the pre-read stat above and re-queue rather than throw.
    let statAfter
    try {
      statAfter = fs.statSync(filePath)
    } catch {
      return null // source vanished during read, caller should re-queue
    }
    if (statAfter.mtimeMs !== mtimeBefore) {
      return null // file changed during read, caller should re-queue
    }

    // Post-read guard: a mid-read shrink can feed the digest fewer bytes than the
    // size baked into its leaf prefix — re-queue instead of persisting a bad hash.
    if (fileHasher.bytes !== stat.size) return null

    // Update file index
    const contentHash = fileHasher.digest()

    // Persist chunk map for large files — both path-keyed (legacy serve lookups)
    // and content-addressed, so the serve path reuses it across keys.
    // byHashOnly (the publish prepareForServe pass) keeps ONLY the
    // content-addressed map — the serve path resolves by hash, so the path-keyed map
    // + putFile under a throwaway overlay key would be dead, never-read state.
    if (stat.size >= 1048576) {
      if (!opts.byHashOnly) await this._fileIndex.putChunkMap(overlayPath, chunks)
      if (!await this._fileIndex.hasChunkMapByHash(contentHash)) await this._fileIndex.putChunkMapByHash(contentHash, chunks)
    }

    if (!opts.byHashOnly) {
      await this._fileIndex.putFile(overlayPath, {
        contentHash,
        size: stat.size,
        mtime: mtimeBefore
      })
    }

    return { tier, chunks, size: stat.size, contentHash }
  }

  /**
   * Read a specific chunk from disk by its offset and length.
   * Returns raw bytes — caller sends them to the peer.
   *
   * @param {string} filePath - absolute path on disk
   * @param {number} offset - byte offset
   * @param {number} length - bytes to read
   * @returns {Buffer | null}
   */
  readChunk (filePath, offset, length) {
    try {
      const fd = fs.openSync(filePath, 'r')
      const buf = Buffer.alloc(length)
      const bytesRead = fs.readSync(fd, buf, 0, length, offset)
      fs.closeSync(fd)
      if (bytesRead !== length) return null
      return buf
    } catch {
      return null
    }
  }

  /**
   * Determine which chunks a peer needs (chunks they don't already have).
   *
   * @param {Array<{ hash, offset, length }>} offered - chunks the sender has
   * @param {Set<string>} peerHas - set of chunk hashes the peer already has
   * @returns {number[]} indices of chunks the peer needs
   */
  computeNeeded (offered, peerHas) {
    const needed = []
    for (let i = 0; i < offered.length; i++) {
      if (!peerHas.has(offered[i].hash)) {
        needed.push(i)
      }
    }
    return needed
  }

  // ── Receiver side ───────────────────────────────────────────

  // Start receiving a file. A same-size partial is resumed: the journal restores the
  // received-set + whole-file hash snapshot in O(1) (no re-read); failing that, an
  // async, yielding re-verify rebuilds them without blocking the event loop.
  // opts.isCancelled aborts a long re-verify; opts.onVerifyProgress(0..1) drives the UI.
  async startReceive (targetPath, meta, opts = {}) {
    const isCancelled = opts.isCancelled || (() => false)
    const dir = path.dirname(targetPath)
    const base = path.basename(targetPath)
    // Visible partial (no leading dot) so an in-progress download shows in the
    // destination folder; the distinct ".overlay-partial" suffix keeps host-app
    // sweeps keyed on a plain ".partial" suffix from deleting a paused overlay
    // partial.
    const partialPath = path.join(dir, base + PARTIAL_SUFFIX)
    const journalPath = this._journalPathFor(targetPath)

    fs.mkdirSync(dir, { recursive: true })

    let resumed = false
    try { resumed = fs.statSync(partialPath).size === meta.size } catch {}

    let received = new Set()
    let hasher = meta.contentHash ? createStreamingHasher({ size: meta.size }) : null
    let hashFrontier = 0

    if (resumed) {
      const j = journalPath ? this._loadJournal(journalPath, partialPath, meta) : null
      if (j) {
        received = j.received
        hashFrontier = j.hashFrontier
        if (meta.contentHash) hasher = createStreamingHasher({ size: meta.size, restore: { state: j.hasherState, bytes: j.hasherBytes } })
      } else {
        const rec = await this._recoverPartialAsync(partialPath, meta, isCancelled, opts.onVerifyProgress)
        received = rec.received; hasher = rec.hasher; hashFrontier = rec.hashFrontier
      }
    } else {
      if (journalPath) { try { fs.unlinkSync(journalPath) } catch {} }
    }

    if (isCancelled()) { const e = new Error('receive cancelled during setup'); e.code = 'ECANCELLED'; throw e }

    // ONE persistent fd for the whole transfer — positioned writeSync/fs.read never
    // move a shared cursor, so a single handle serves the chunk writes, the hash
    // pump's gap read-back, and the journal fsync. Replaces an openSync+closeSync per
    // chunk; 'w+' creates+truncates the fresh partial in the same open.
    const fd = fs.openSync(partialPath, resumed ? 'r+' : 'w+')
    if (!resumed) {
      try { fs.ftruncateSync(fd, meta.size) } catch (err) { try { fs.closeSync(fd) } catch {}; throw err }
    }

    const total = meta.chunks.length
    const state = {
      partialPath,
      targetPath,
      journalPath,
      chunks: meta.chunks,
      received,
      bitmap: bitmapOf(received, total),
      total,
      size: meta.size,
      contentHash: meta.contentHash || null,
      hasher,
      hashFrontier,
      advancePromise: null,
      draining: false,
      sinceFlush: 0,
      lastFlush: 0,
      flushing: false,
      flushPending: false,
      fd,
      // Bounded stash of just-received verified chunk buffers so the hash pump feeds
      // from memory instead of reading the bytes back off disk. Never load-bearing:
      // bytes hit disk first; a miss falls back to read-back.
      memChunks: new Map(),
      memBytes: 0,
      stats: { readbacks: 0, stashHits: 0 }
    }

    // A prior transfer for this path that ended via _fail (stall / all-peers-
    // gone / disk error) leaves its state here with an open fd — close it before
    // overwriting so a retry/resume of the same file can't orphan the fd.
    const prev = this._active.get(targetPath)
    if (prev) this._closeFd(prev)

    this._active.set(targetPath, state)
    // A fresh transfer has nothing worth journaling yet; a resume that recovered
    // chunks persists them now so an immediate re-pause is O(1).
    if (received.size > 0) this._flushJournal(state)
    return state
  }

  // Rebuild the received-set from a same-size partial without blocking the loop:
  // async reads, a yield every VERIFY_YIELD_EVERY chunks, feeding the contiguous
  // prefix into the whole-file hasher inline (single pass, no second read).
  async _recoverPartialAsync (partialPath, meta, isCancelled, onVerifyProgress) {
    const received = new Set()
    const hasher = meta.contentHash ? createStreamingHasher({ size: meta.size }) : null
    let hashFrontier = 0
    const total = meta.chunks.length
    const maxLen = meta.chunks.reduce((m, c) => (c.length > m ? c.length : m), 0)
    const buf = Buffer.allocUnsafe(maxLen)
    let lastPct = -1
    onVerifyProgress?.(0)
    const fd = await fs.open(partialPath, 'r')
    try {
      for (let i = 0; i < total; i++) {
        if (isCancelled()) { const e = new Error('receive recovery cancelled'); e.code = 'ECANCELLED'; throw e }
        const ci = meta.chunks[i]
        let n = 0
        try { n = await fs.read(fd, buf, 0, ci.length, ci.offset) } catch {}
        const view = buf.subarray(0, ci.length)
        if (n === ci.length && hashChunk(view) === ci.hash) {
          received.add(i)
          if (hasher && i === hashFrontier) { hasher.update(view); hashFrontier++ }
        }
        if ((i & (VERIFY_YIELD_EVERY - 1)) === VERIFY_YIELD_EVERY - 1) {
          const pct = Math.floor(((i + 1) / total) * 100)
          if (pct !== lastPct) { lastPct = pct; onVerifyProgress?.(pct / 100) }
          await yieldToLoop()
        }
      }
    } finally {
      try { await fs.close(fd) } catch {}
    }
    onVerifyProgress?.(1)
    return { received, hasher, hashFrontier }
  }

  _encodeJournal (state) {
    const total = state.total
    const bm = state.bitmap
    const ch = state.contentHash ? Buffer.from(state.contentHash, 'hex') : Buffer.alloc(32)
    const snap = state.hasher ? state.hasher.snapshot() : { state: Buffer.alloc(0), bytes: 0 }
    const s = { start: 0, end: 0, buffer: null }
    c.uint32.preencode(s, JOURNAL_MAGIC)
    c.uint32.preencode(s, sodium.crypto_generichash_STATEBYTES)
    c.string.preencode(s, state.partialPath)
    c.uint64.preencode(s, state.size)
    c.uint32.preencode(s, total)
    c.fixed32.preencode(s, ch)
    c.uint32.preencode(s, state.hashFrontier)
    c.uint64.preencode(s, snap.bytes)
    c.buffer.preencode(s, snap.state)
    c.buffer.preencode(s, bm)
    s.buffer = Buffer.allocUnsafe(s.end)
    c.uint32.encode(s, JOURNAL_MAGIC)
    c.uint32.encode(s, sodium.crypto_generichash_STATEBYTES)
    c.string.encode(s, state.partialPath)
    c.uint64.encode(s, state.size)
    c.uint32.encode(s, total)
    c.fixed32.encode(s, ch)
    c.uint32.encode(s, state.hashFrontier)
    c.uint64.encode(s, snap.bytes)
    c.buffer.encode(s, snap.state)
    c.buffer.encode(s, bm)
    return s.buffer
  }

  // Returns the resume state only when the journal is for this partial, binds to
  // this content, and its snapshot is restorable on this libsodium build; else null
  // so the caller falls back to the async re-verify.
  _loadJournal (journalPath, partialPath, meta) {
    let raw
    try { raw = fs.readFileSync(journalPath) } catch { return null }
    try {
      const s = { start: 0, end: raw.length, buffer: raw }
      if (c.uint32.decode(s) !== JOURNAL_MAGIC) return null
      const stateBytes = c.uint32.decode(s)
      const storedPartial = c.string.decode(s)
      const size = c.uint64.decode(s)
      const total = c.uint32.decode(s)
      const ch = c.fixed32.decode(s).toString('hex')
      const hashFrontier = c.uint32.decode(s)
      const hasherBytes = c.uint64.decode(s)
      const hasherState = c.buffer.decode(s)
      const bm = c.buffer.decode(s)
      // F1: never trust a journal we cannot whole-file-verify. Both finalize and the
      // legacy receive path skip the whole-file hash when contentHash is absent, so a
      // FOREIGN journal with a matching size + chunk-count (a stale .overlay-partial
      // for different content the CDC split into the same number of chunks) would be
      // trusted and finalized as merged old+new bytes, undetected. Require a content
      // hash to resume from a journal; without one the caller falls back to
      // _recoverPartialAsync, which re-verifies every chunk by its per-chunk hash.
      if (!meta.contentHash) return null
      if (storedPartial !== partialPath) return null
      if (size !== meta.size || total !== meta.chunks.length) return null
      if (ch !== meta.contentHash) return null
      if (meta.contentHash) {
        if (stateBytes !== sodium.crypto_generichash_STATEBYTES) return null
        if (hasherState.length !== sodium.crypto_generichash_STATEBYTES) return null
      }
      const received = new Set()
      for (let i = 0; i < total; i++) if (bm[i >> 3] & (1 << (i & 7))) received.add(i)
      return { received, hashFrontier, hasherState, hasherBytes }
    } catch { return null }
  }

  // Best-effort async crash-hedge flush: never blocks the receive loop, single-flight
  // (a flush requested while one runs coalesces into one more pass). fsync the partial
  // BEFORE the journal references it so a power-loss can never leave the journal
  // marking a chunk received whose bytes aren't durable.
  _flushJournal (state) {
    if (!state.contentHash || !state.journalPath || state.fd == null) return
    if (state.flushing) { state.flushPending = true; return }
    if (Date.now() - state.lastFlush < JOURNAL_FLUSH_INTERVAL_MS) return
    state.flushing = true
    this._drainFlush(state)
  }

  async _drainFlush (state) {
    do {
      state.flushPending = false
      state.lastFlush = Date.now()
      if (state.fd == null) break
      let buf
      try { buf = this._encodeJournal(state) } catch { break }
      const tmp = state.journalPath + '.tmp'
      try {
        // fsync is best-effort and swallowed on purpose: on Windows, fsync
        // (FlushFileBuffers) on a read-only handle throws EPERM benignly, and we still
        // want the journal to persist so resume stays O(1). The EIO write-back hazard
        // this could mask is already closed upstream — F1 refuses to trust a journal
        // without a contentHash, and the finalize whole-file hash re-verifies the
        // gated flow — so skipping the commit here (former "F3") only regressed Windows.
        try { await fs.fsync(state.fd) } catch {}
        await fs.writeFile(tmp, buf)
        if (state.fd == null) break // paused/cancelled mid-flush — don't re-create the journal
        await fs.rename(tmp, state.journalPath)
      } catch { try { await fs.unlink(tmp) } catch {} }
    } while (state.flushPending && state.fd != null && state.contentHash)
    try { await fs.unlink(state.journalPath + '.tmp') } catch {}
    state.flushing = false
  }

  // Synchronous durable flush for pause/shutdown, where the app may quit before an
  // async flush would land.
  _flushJournalSync (state) {
    if (!state.contentHash || !state.journalPath) return
    let buf
    try { buf = this._encodeJournal(state) } catch { return }
    // fsync swallowed on purpose (Windows read-only-handle EPERM) — see _drainFlush.
    if (state.fd != null) { try { fs.fsyncSync(state.fd) } catch {} }
    try {
      const tmp = state.journalPath + '.tmp'
      fs.writeFileSync(tmp, buf)
      fs.renameSync(tmp, state.journalPath)
      state.lastFlush = Date.now()
    } catch { try { fs.unlinkSync(state.journalPath + '.tmp') } catch {} }
  }

  cleanJournals (maxAge) { return cleanupOrphanedJournals(this._journalDir, maxAge) }

  async _hashWholeFileAsync (filePath, size) {
    const h = createStreamingHasher({ size })
    for await (const buf of readFileBlocks(filePath, size, 8 * 1024 * 1024)) {
      h.update(buf)
      await yieldToLoop()
    }
    return h.digest()
  }

  // Advance the whole-file hash over the contiguous run of received chunks
  // as a single-flight BACKGROUND pump: chunk index order == file offset order, so
  // once chunk `hashFrontier` is present we feed its bytes and move on. The drain is
  // async + yielding so a large contiguous run that suddenly becomes available (a
  // gap filling after a low-frontier resume) cannot block the event loop.
  // Single-flight is `state.draining`, set/cleared SYNCHRONOUSLY inside _drainHash —
  // a stash-fed drain completes without awaiting, so a promise-based gate would stay
  // stale until its microtask clear and block same-macrotask restarts (protomux
  // delivers frame bursts synchronously). advancePromise is only the awaitable
  // handle for finalize/pause; its clear is guarded against nulling a newer drain.
  _advanceHash (state) {
    if (!state.hasher || state.draining) return
    if (!(state.hashFrontier < state.chunks.length && state.received.has(state.hashFrontier))) return
    const p = this._drainHash(state)
    state.advancePromise = p
    p.catch(() => {}).then(() => { if (state.advancePromise === p) state.advancePromise = null })
  }

  async _drainHash (state) {
    state.draining = true
    try {
      let sinceYield = 0
      while (state.fd != null && state.hashFrontier < state.chunks.length && state.received.has(state.hashFrontier)) {
        const i = state.hashFrontier
        const c = state.chunks[i]
        let buf = state.memChunks.get(i)
        if (buf !== undefined) {
          state.memChunks.delete(i)
          state.memBytes -= buf.length
          state.stats.stashHits++
        } else {
          buf = Buffer.alloc(c.length)
          try { await fs.read(state.fd, buf, 0, c.length, c.offset) } catch { return }
          state.stats.readbacks++
        }
        state.hasher.update(buf)
        state.hashFrontier++
        // Yield by BYTE volume: the stash path has no awaits, so a chunk-count
        // cadence would let a gap-fill hash the whole stash synchronously inside
        // the network event that delivered the gap chunk.
        sinceYield += buf.length
        if (sinceYield >= DRAIN_YIELD_BYTES) { sinceYield = 0; await yieldToLoop() }
      }
    } finally {
      state.draining = false
    }
  }

  // Close the persistent fd — MUST run before finalize's rename (an open handle
  // blocks the rename on Windows) — and drop the chunk stash.
  _closeFd (state) {
    if (state.fd != null) {
      try { fs.closeSync(state.fd) } catch {}
      state.fd = null
    }
    state.memChunks.clear()
    state.memBytes = 0
  }

  /**
   * Write a received chunk to the partial file. Verifies the hash.
   *
   * @param {string} targetPath - the final file path (used as key)
   * @param {number} index - chunk index
   * @param {Buffer} data - raw chunk bytes
   * @returns {{ ok: boolean, error?: string }}
   */
  writeChunk (targetPath, index, data) {
    const state = this._active.get(targetPath)
    if (!state) return { ok: false, error: 'no active transfer' }
    if (index < 0 || index >= state.chunks.length) return { ok: false, error: 'index out of range' }
    // Terminal race (chunk landed after close) — codeless so the scheduler
    // re-assigns instead of failing the fetch.
    if (state.fd == null) return { ok: false, error: 'transfer closed' }

    const expected = state.chunks[index]

    // Verify hash
    const actual = hashChunk(data)
    if (actual !== expected.hash) {
      return { ok: false, error: `hash mismatch at index ${index}: expected ${expected.hash.slice(0, 16)}... got ${actual.slice(0, 16)}...` }
    }

    // Verify length
    if (data.length !== expected.length) {
      return { ok: false, error: `length mismatch at index ${index}: expected ${expected.length} got ${data.length}` }
    }

    // Write to partial at the chunk's offset through the persistent fd. A local
    // I/O failure here (ENOSPC / EACCES / EROFS) is fatal and non-retryable —
    // return its code so the scheduler fails the fetch instead of looping over
    // other peers. Loop on a SHORT count: libuv swallows an error after partial
    // progress and returns short without throwing; retrying the remainder
    // surfaces the real coded error instead of leaving a silent hole the
    // stash-fed digest would never see.
    try {
      let written = 0
      while (written < data.length) {
        const n = fs.writeSync(state.fd, data, written, data.length - written, expected.offset + written)
        if (!(n > 0)) { const e = new Error(`short write at index ${index}: ${written}/${data.length} bytes`); e.code = 'EIO'; throw e }
        written += n
      }
    } catch (err) {
      return { ok: false, error: err.message, code: err.code }
    }

    state.received.add(index)
    state.bitmap[index >> 3] |= 1 << (index & 7)
    // Stash only what the pump can consume cleanly: skip when already hashed or
    // stashed, when the pump is mid-drain on exactly this index, or over cap.
    // Copied, not aliased — wire buffers are views into shared network slabs (udx
    // read slab, decrypted in place by secret-stream), so retaining `data` would
    // pin whole slabs and bet the digest on their write-once behavior.
    if (state.hasher && !state.memChunks.has(index) && state.memBytes + data.length <= this._memStashBytes &&
        (index > state.hashFrontier || (index === state.hashFrontier && !state.draining))) {
      state.memChunks.set(index, Buffer.from(data))
      state.memBytes += data.length
    }
    this._advanceHash(state)
    if (++state.sinceFlush >= JOURNAL_FLUSH_EVERY) { state.sinceFlush = 0; this._flushJournal(state) }

    return { ok: true }
  }

  /**
   * Check if a transfer is complete (all chunks received).
   *
   * @param {string} targetPath
   * @returns {boolean}
   */
  isComplete (targetPath) {
    const state = this._active.get(targetPath)
    if (!state) return false
    return state.received.size === state.total
  }

  /**
   * Finalize a completed transfer — atomic rename from partial to target.
   *
   * @param {string} targetPath
   * @returns {{ ok: boolean, error?: string }}
   */
  async finalize (targetPath) {
    const state = this._active.get(targetPath)
    if (!state) return { ok: false, error: 'no active transfer' }
    if (state.received.size !== state.total) {
      return { ok: false, error: `incomplete: ${state.received.size}/${state.total} chunks` }
    }

    // whole-file integrity — computed incrementally by the background hash
    // pump during the transfer. Kick the pump and await it to quiescence, then read
    // the digest (no full re-read). On a mismatch, drop the corrupt partial so a
    // retry starts clean.
    if (state.hasher) {
      this._advanceHash(state)
      while (state.advancePromise) { try { await state.advancePromise } catch {} }
    }
    if (state.contentHash) {
      const actual = state.hashFrontier === state.chunks.length ? state.hasher.digest() : null
      if (actual !== state.contentHash) {
        this._closeFd(state)
        try { fs.unlinkSync(state.partialPath) } catch {}
        if (state.journalPath) { try { fs.unlinkSync(state.journalPath) } catch {} }
        this._active.delete(targetPath)
        return { ok: false, error: 'content-hash mismatch', code: 'EHASHMISMATCH' }
      }
    }
    this._closeFd(state)

    try {
      fs.renameSync(state.partialPath, state.targetPath)
    } catch (err) {
      // Carry the code (routes through the coded local-I/O rethrow) and clear the
      // state — the partial + journal stay for a clean resume-retry.
      this._active.delete(targetPath)
      return { ok: false, error: 'rename failed: ' + err.message, code: err.code }
    }

    if (state.journalPath) { try { fs.unlinkSync(state.journalPath) } catch {} }
    this._active.delete(targetPath)
    return { ok: true }
  }

  // Stop receiving but KEEP the partial on disk so a later startReceive
  // verifies and resumes it. Distinct from cancel(), which unlinks the partial.
  // Drains the in-flight hash advance to the full contiguous frontier first, so the
  // journal captures it and resume stays O(1) (no re-read) and deterministic.
  async pause (targetPath) {
    const state = this._active.get(targetPath)
    if (!state) return
    if (state.hasher) {
      this._advanceHash(state)
      while (state.advancePromise) { try { await state.advancePromise } catch {} }
    }
    this._flushJournalSync(state)
    this._closeFd(state)
    this._active.delete(targetPath)
  }

  /**
   * Cancel an active transfer and clean up the partial file.
   *
   * @param {string} targetPath
   */
  cancel (targetPath) {
    const state = this._active.get(targetPath)
    if (!state) return

    this._closeFd(state)
    try {
      fs.unlinkSync(state.partialPath)
    } catch {
      // partial file may not exist
    }
    if (state.journalPath) { try { fs.unlinkSync(state.journalPath) } catch {} }

    this._active.delete(targetPath)
  }

  /**
   * Get transfer progress for an active transfer.
   *
   * @param {string} targetPath
   * @returns {{ received: number, total: number, percentage: number } | null}
   */
  getProgress (targetPath) {
    const state = this._active.get(targetPath)
    if (!state) return null

    return {
      received: state.received.size,
      total: state.total,
      percentage: state.total > 0 ? Math.round(state.received.size / state.total * 100) : 0
    }
  }

  /**
   * List all active transfers.
   *
   * @returns {Array<{ path: string, received: number, total: number, percentage: number }>}
   */
  listActive () {
    const result = []
    for (const [targetPath, state] of this._active) {
      result.push({
        path: targetPath,
        received: state.received.size,
        total: state.total,
        percentage: state.total > 0 ? Math.round(state.received.size / state.total * 100) : 0
      })
    }
    return result
  }

  /**
   * Clean up stale partial files in a directory (older than maxAge).
   *
   * @param {string} dir - directory to scan
   * @param {number} [maxAge=86400000] - max age in ms (default 24h)
   * @returns {string[]} paths of cleaned files
   */
  cleanPartials (dir, maxAge = 86400000) {
    const cleaned = []
    try {
      const entries = fs.readdirSync(dir)
      const now = Date.now()
      for (const entry of entries) {
        if (!entry.endsWith(PARTIAL_SUFFIX)) continue
        const full = path.join(dir, entry)
        try {
          const stat = fs.statSync(full)
          if (now - stat.mtimeMs > maxAge) {
            fs.unlinkSync(full)
            cleaned.push(full)
          }
        } catch {
          // skip unreadable files
        }
      }
    } catch {
      // directory may not exist
    }
    return cleaned
  }
}
/**
 * Tree — content-addressable directory hashing for hyper-overlay
 *
 * A tree is a canonical serialization of a directory's immediate entries.
 * Each entry is one of: file, subdirectory, symlink. The tree's identity
 * is blake2b-256 of its canonical bytes.
 *
 * Format (P.10 Phase 0.5a):
 *
 *   tree := "pgh-tree-1\n" ||
 *           varint(n_entries) ||
 *           entry[0] || ... || entry[n-1]
 *
 *   entry := u8(kind)  ||           // 0=file, 1=dir, 2=symlink
 *            u8(exec)  ||           // 0 or 1; always 0 for dir/symlink
 *            varint(name_len) || name ||  // UTF-8 NFC, no NUL, no "/"
 *            32_bytes(child_hash)
 *
 *   tree_hash := blake2b-256(tree_bytes)
 *
 * Identity fields (in the hash): kind, exec-bit, name, child_hash.
 * Explicitly excluded: mtime, size, uid/gid, full mode.
 *
 * Entries MUST be sorted byte-wise by name (after NFC normalization).
 * Encoders emit sorted; decoders reject unsorted trees.
 *
 * Symlinks: child_hash = blake2b-256 of the target path bytes (not followed).
 * Empty directories: zero-entry tree, valid, deterministic.
 */

import fs from '#fs'
import path from '#path'
import crypto from 'hypercore-crypto'
import c from 'compact-encoding'

// ── Constants ─────────────────────────────────────────────────

export const MAGIC = Buffer.from('pgh-tree-1\n', 'utf8')
export const KIND_FILE = 0
export const KIND_DIR = 1
export const KIND_SYMLINK = 2

// Windows reserved device names (case-insensitive, with or without extension)
const WINDOWS_RESERVED = new Set([
  'CON', 'PRN', 'AUX', 'NUL',
  'COM1', 'COM2', 'COM3', 'COM4', 'COM5', 'COM6', 'COM7', 'COM8', 'COM9',
  'LPT1', 'LPT2', 'LPT3', 'LPT4', 'LPT5', 'LPT6', 'LPT7', 'LPT8', 'LPT9'
])

// Chars forbidden in filenames on Windows (beyond format-layer rules)
// eslint-disable-next-line no-control-regex
const WINDOWS_FORBIDDEN = /[\\:*?"<>|\x00-\x1f]/

// Default skip list for walkTree — minimal, just obvious platform cruft.
// Consumers that want more (e.g. .git for pgh publish) pass their own.
export const DEFAULT_SKIP = new Set(['.DS_Store', 'Thumbs.db'])

// ── Name validation (format layer) ────────────────────────────

/**
 * Normalize a filename for hashing and validate format-layer rules.
 * Throws on unrepresentable names. Returns the canonical form.
 *
 * @param {string} name - single path component (no "/")
 * @returns {string} NFC-normalized name
 */
export function normalizeName (name) {
  if (typeof name !== 'string') {
    throw new Error('tree entry name must be string, got ' + typeof name)
  }
  if (name.length === 0) {
    throw new Error('tree entry name cannot be empty')
  }
  if (name === '.' || name === '..') {
    throw new Error('tree entry name cannot be "." or ".."')
  }
  if (name.indexOf('/') !== -1) {
    throw new Error('tree entry name cannot contain "/": ' + JSON.stringify(name))
  }
  if (name.indexOf('\x00') !== -1) {
    throw new Error('tree entry name cannot contain NUL: ' + JSON.stringify(name))
  }

  const normalized = name.normalize('NFC')

  // Verify the normalized form is valid UTF-8 that round-trips through
  // Buffer (catches lone surrogates and other malformed sequences).
  const asBytes = Buffer.from(normalized, 'utf8')
  if (asBytes.toString('utf8') !== normalized) {
    throw new Error('tree entry name is not valid UTF-8: ' + JSON.stringify(name))
  }

  return normalized
}

// ── Consumer-policy layer ─────────────────────────────────────

/**
 * Check a name against Windows compatibility rules.
 * Does NOT throw — returns a reason string (or null if the name is OK).
 * Used by pgh publish for strict cross-platform validation.
 *
 * @param {string} name
 * @returns {string|null} reason if incompatible, null if OK
 */
export function windowsIncompatibleReason (name) {
  if (WINDOWS_FORBIDDEN.test(name)) {
    return 'contains Windows-forbidden character (\\:*?"<>| or control code)'
  }
  const trimmed = name.replace(/\.+$/, '').replace(/ +$/, '')
  if (trimmed !== name) {
    return 'trailing "." or space (Windows strips these silently)'
  }
  // Reserved device names apply with any extension: AUX.log, PRN.txt etc.
  const base = name.split('.')[0].toUpperCase()
  if (WINDOWS_RESERVED.has(base)) {
    return `"${base}" is a reserved Windows device name (even with extension)`
  }
  return null
}

// ── Canonical encoding ────────────────────────────────────────

/**
 * Encode a single entry to its canonical byte representation.
 *
 * @param {object} state - compact-encoding state
 * @param {{kind: number, exec: number, name: string, childHash: Buffer|string}} e
 */
function encodeEntry (state, e) {
  if (e.kind !== KIND_FILE && e.kind !== KIND_DIR && e.kind !== KIND_SYMLINK) {
    throw new Error('invalid entry kind: ' + e.kind)
  }
  if (e.exec !== 0 && e.exec !== 1) {
    throw new Error('entry exec must be 0 or 1')
  }
  if (e.kind !== KIND_FILE && e.exec !== 0) {
    throw new Error('non-file entry must have exec=0')
  }

  const hash = toBuffer32(e.childHash)

  c.uint8.encode(state, e.kind)
  c.uint8.encode(state, e.exec)
  c.string.encode(state, e.name) // compact-encoding: varint len + utf8 bytes
  state.buffer.set(hash, state.start)
  state.start += 32
}

function preencodeEntry (state, e) {
  c.uint8.preencode(state, e.kind)
  c.uint8.preencode(state, e.exec)
  c.string.preencode(state, e.name)
  state.end += 32
}

/**
 * Serialize a list of entries to canonical tree bytes.
 * Entries are sorted + normalized as a side-effect of the call.
 *
 * @param {Array<{kind: number, exec: number, name: string, childHash: Buffer|string}>} entries
 * @returns {Buffer}
 */
export function encodeTree (entries) {
  const normalized = entries.map(e => ({
    kind: e.kind,
    exec: e.exec || 0,
    name: normalizeName(e.name),
    childHash: e.childHash
  }))
  normalized.sort(compareEntries)

  // Sanity: no duplicate names after normalization
  for (let i = 1; i < normalized.length; i++) {
    if (normalized[i].name === normalized[i - 1].name) {
      throw new Error('duplicate entry name after NFC normalization: ' + normalized[i].name)
    }
  }

  const state = { start: 0, end: 0, buffer: null }
  state.end += MAGIC.length
  c.uint.preencode(state, normalized.length)
  for (const e of normalized) preencodeEntry(state, e)

  state.buffer = Buffer.alloc(state.end)

  state.buffer.set(MAGIC, state.start)
  state.start += MAGIC.length
  c.uint.encode(state, normalized.length)
  for (const e of normalized) encodeEntry(state, e)

  return state.buffer
}

/**
 * Byte-wise comparator for entry names (after NFC normalization).
 */
function compareEntries (a, b) {
  const ab = Buffer.from(a.name, 'utf8')
  const bb = Buffer.from(b.name, 'utf8')
  return Buffer.compare(ab, bb)
}

/**
 * Decode canonical tree bytes back to an entries list.
 * Rejects unsorted or malformed trees.
 *
 * @param {Buffer} buf
 * @returns {Array<{kind: number, exec: number, name: string, childHash: string}>}
 */
export function decodeTree (buf) {
  if (!Buffer.isBuffer(buf)) throw new Error('decodeTree expects a Buffer')
  if (buf.length < MAGIC.length) throw new Error('tree too short')
  if (buf.compare(MAGIC, 0, MAGIC.length, 0, MAGIC.length) !== 0) {
    throw new Error('tree magic mismatch (expected "pgh-tree-1")')
  }

  const state = { start: MAGIC.length, end: buf.length, buffer: buf }
  const count = c.uint.decode(state)
  const entries = []

  let prevName = null
  for (let i = 0; i < count; i++) {
    const kind = c.uint8.decode(state)
    const exec = c.uint8.decode(state)
    const name = c.string.decode(state)

    if (state.start + 32 > state.end) throw new Error('truncated tree: missing hash bytes')
    const childHash = buf.subarray(state.start, state.start + 32).toString('hex')
    state.start += 32

    // Validate format rules on read — reject invalid trees rather than silently accept
    const normalized = normalizeName(name)
    if (normalized !== name) {
      throw new Error('tree entry name not NFC-normalized: ' + JSON.stringify(name))
    }
    if (kind !== KIND_FILE && exec !== 0) {
      throw new Error('non-file entry with exec=1 is invalid')
    }
    if (prevName !== null) {
      const cmp = Buffer.compare(Buffer.from(prevName, 'utf8'), Buffer.from(name, 'utf8'))
      if (cmp === 0) throw new Error('tree has duplicate entry: ' + name)
      if (cmp > 0) throw new Error('tree entries not sorted: ' + prevName + ' before ' + name)
    }
    prevName = name

    entries.push({ kind, exec, name, childHash })
  }

  if (state.start !== state.end) throw new Error('tree has trailing bytes after entries')

  return entries
}

/**
 * Compute the tree hash of an entries list.
 *
 * @param {Array<{kind: number, exec: number, name: string, childHash: Buffer|string}>} entries
 * @returns {string} hex-encoded blake2b-256
 */
export function computeTreeHash (entries) {
  const buf = encodeTree(entries)
  return crypto.data(buf).toString('hex')
}

// ── Helpers ───────────────────────────────────────────────────

function toBuffer32 (hashOrBuf) {
  if (Buffer.isBuffer(hashOrBuf)) {
    if (hashOrBuf.length !== 32) throw new Error('childHash buffer must be 32 bytes')
    return hashOrBuf
  }
  if (typeof hashOrBuf !== 'string') {
    throw new Error('childHash must be Buffer or hex string')
  }
  if (hashOrBuf.length !== 64) throw new Error('childHash hex string must be 64 chars')
  return Buffer.from(hashOrBuf, 'hex')
}

// ── Filesystem walk ───────────────────────────────────────────

/**
 * Walk a directory recursively, producing tree hashes bottom-up.
 *
 * Returns a nested object keyed by path, where each entry has:
 *   { hash: hex, entries: [{kind, exec, name, childHash, size?}] }
 *
 * File entries carry their content hash as childHash (blake2b of bytes).
 * Dir entries carry the subtree hash.
 * Symlink entries carry blake2b of the link target bytes.
 *
 * @param {string} rootDir - absolute path to scan
 * @param {object} [opts]
 * @param {Set<string>} [opts.skip] - basenames to skip (default: DEFAULT_SKIP)
 * @param {(relPath: string, baseName: string) => boolean} [opts.matcher]
 *   Optional gitignore-style matcher. Returns true to EXCLUDE an entry.
 *   Receives the path relative to rootDir plus the basename. Applies to
 *   all kinds (file/dir/symlink). Used by `pgh publish` for publishIgnore.
 * @returns {Map<string, {hash: string, entries: Array, size: number}>}
 *          map keyed by relative path ('' = root); root tree is at key ''
 */
export function walkTree (rootDir, opts = {}) {
  const skip = opts.skip || DEFAULT_SKIP
  const matcher = opts.matcher || null
  const trees = new Map()

  function visit (relPath) {
    const absPath = relPath === '' ? rootDir : path.join(rootDir, relPath)
    const raw = fs.readdirSync(absPath)
    const entries = []
    let totalSize = 0

    for (const name of raw) {
      if (skip.has(name)) continue

      const entryAbs = path.join(absPath, name)
      const entryRel = relPath === '' ? name : relPath + '/' + name

      if (matcher && matcher(entryRel, name)) continue

      let stat
      try {
        stat = fs.lstatSync(entryAbs)
      } catch {
        continue
      }

      if (stat.isSymbolicLink()) {
        const target = fs.readlinkSync(entryAbs)
        const targetBytes = Buffer.from(target, 'utf8')
        const childHash = crypto.data(targetBytes).toString('hex')
        entries.push({
          kind: KIND_SYMLINK,
          exec: 0,
          name: normalizeName(name),
          childHash,
          size: targetBytes.length,
          linkTarget: target
        })
        totalSize += targetBytes.length
      } else if (stat.isDirectory()) {
        visit(entryRel)
        const sub = trees.get(entryRel)
        if (!sub) continue
        entries.push({
          kind: KIND_DIR,
          exec: 0,
          name: normalizeName(name),
          childHash: sub.hash,
          size: sub.size
        })
        totalSize += sub.size
      } else if (stat.isFile()) {
        const content = fs.readFileSync(entryAbs)
        const childHash = crypto.data(content).toString('hex')
        // Executable bit from stat.mode — works on POSIX; Windows always 0
        const exec = (stat.mode & 0o100) ? 1 : 0
        entries.push({
          kind: KIND_FILE,
          exec,
          name: normalizeName(name),
          childHash,
          size: stat.size
        })
        totalSize += stat.size
      }
      // device files, sockets, fifos: silently skipped (unrepresentable)
    }

    entries.sort(compareEntries)
    const hash = computeTreeHash(entries)
    trees.set(relPath, { hash, entries, size: totalSize })
  }

  visit('')
  return trees
}

// ── Publish-time validation (consumer policy) ─────────────────

/**
 * Scan entries (recursively via walkTree output) for cross-platform-hostile
 * names. Returns an array of {path, name, reason} for every problem found.
 * Does NOT throw — caller decides how to present.
 *
 * @param {Map<string, {hash: string, entries: Array}>} trees - from walkTree
 * @returns {Array<{path: string, name: string, reason: string}>}
 */
export function validateForPublish (trees) {
  const problems = []
  for (const [relPath, tree] of trees) {
    for (const e of tree.entries) {
      const reason = windowsIncompatibleReason(e.name)
      if (reason) {
        problems.push({
          path: relPath ? relPath + '/' + e.name : e.name,
          name: e.name,
          reason
        })
      }
    }
  }
  return problems
}
function isBuffer(value) {
  return Buffer.isBuffer(value) || value instanceof Uint8Array
}

function isEncoding(encoding) {
  return Buffer.isEncoding(encoding)
}

function alloc(size, fill, encoding) {
  return Buffer.alloc(size, fill, encoding)
}

function allocUnsafe(size) {
  return Buffer.allocUnsafe(size)
}

function allocUnsafeSlow(size) {
  return Buffer.allocUnsafeSlow(size)
}

function byteLength(string, encoding) {
  return Buffer.byteLength(string, encoding)
}

function compare(a, b) {
  return Buffer.compare(a, b)
}

function concat(buffers, totalLength) {
  return Buffer.concat(buffers, totalLength)
}

function copy(source, target, targetStart, start, end) {
  return toBuffer(source).copy(target, targetStart, start, end)
}

function equals(a, b) {
  return toBuffer(a).equals(b)
}

function fill(buffer, value, offset, end, encoding) {
  return toBuffer(buffer).fill(value, offset, end, encoding)
}

function from(value, encodingOrOffset, length) {
  return Buffer.from(value, encodingOrOffset, length)
}

function includes(buffer, value, byteOffset, encoding) {
  return toBuffer(buffer).includes(value, byteOffset, encoding)
}

function indexOf(buffer, value, byfeOffset, encoding) {
  return toBuffer(buffer).indexOf(value, byfeOffset, encoding)
}

function lastIndexOf(buffer, value, byteOffset, encoding) {
  return toBuffer(buffer).lastIndexOf(value, byteOffset, encoding)
}

function swap16(buffer) {
  return toBuffer(buffer).swap16()
}

function swap32(buffer) {
  return toBuffer(buffer).swap32()
}

function swap64(buffer) {
  return toBuffer(buffer).swap64()
}

function toBuffer(buffer) {
  if (Buffer.isBuffer(buffer)) return buffer
  return Buffer.from(buffer.buffer, buffer.byteOffset, buffer.byteLength)
}

function toString(buffer, encoding, start, end) {
  return toBuffer(buffer).toString(encoding, start, end)
}

function write(buffer, string, offset, length, encoding) {
  return toBuffer(buffer).write(string, offset, length, encoding)
}

function readDoubleBE(buffer, offset) {
  return toBuffer(buffer).readDoubleBE(offset)
}

function readDoubleLE(buffer, offset) {
  return toBuffer(buffer).readDoubleLE(offset)
}

function readFloatBE(buffer, offset) {
  return toBuffer(buffer).readFloatBE(offset)
}

function readFloatLE(buffer, offset) {
  return toBuffer(buffer).readFloatLE(offset)
}

function readInt32BE(buffer, offset) {
  return toBuffer(buffer).readInt32BE(offset)
}

function readInt32LE(buffer, offset) {
  return toBuffer(buffer).readInt32LE(offset)
}

function readUInt32BE(buffer, offset) {
  return toBuffer(buffer).readUInt32BE(offset)
}

function readUInt32LE(buffer, offset) {
  return toBuffer(buffer).readUInt32LE(offset)
}

function writeDoubleBE(buffer, value, offset) {
  return toBuffer(buffer).writeDoubleBE(value, offset)
}

function writeDoubleLE(buffer, value, offset) {
  return toBuffer(buffer).writeDoubleLE(value, offset)
}

function writeFloatBE(buffer, value, offset) {
  return toBuffer(buffer).writeFloatBE(value, offset)
}

function writeFloatLE(buffer, value, offset) {
  return toBuffer(buffer).writeFloatLE(value, offset)
}

function writeInt32BE(buffer, value, offset) {
  return toBuffer(buffer).writeInt32BE(value, offset)
}

function writeInt32LE(buffer, value, offset) {
  return toBuffer(buffer).writeInt32LE(value, offset)
}

function writeUInt32BE(buffer, value, offset) {
  return toBuffer(buffer).writeUInt32BE(value, offset)
}

function writeUInt32LE(buffer, value, offset) {
  return toBuffer(buffer).writeUInt32LE(value, offset)
}

module.exports = {
  isBuffer,
  isEncoding,
  alloc,
  allocUnsafe,
  allocUnsafeSlow,
  byteLength,
  compare,
  concat,
  copy,
  equals,
  fill,
  from,
  includes,
  indexOf,
  lastIndexOf,
  swap16,
  swap32,
  swap64,
  toBuffer,
  toString,
  write,
  readDoubleBE,
  readDoubleLE,
  readFloatBE,
  readFloatLE,
  readInt32BE,
  readInt32LE,
  readUInt32BE,
  readUInt32LE,
  writeDoubleBE,
  writeDoubleLE,
  writeFloatBE,
  writeFloatLE,
  writeInt32BE,
  writeInt32LE,
  writeUInt32BE,
  writeUInt32LE
}
{
  "name": "b4a",
  "version": "1.8.1",
  "description": "Bridging the gap between buffers and typed arrays",
  "exports": {
    "./package": "./package.json",
    ".": {
      "react-native": "./react-native.js",
      "browser": "./browser.js",
      "default": "./index.js"
    }
  },
  "files": [
    "browser.js",
    "index.js",
    "react-native.js",
    "lib"
  ],
  "scripts": {
    "format": "prettier --write . && lunte --fix",
    "lint": "prettier --check . && lunte",
    "test": "npm run test:bare && npm run test:node",
    "test:bare": "brittle-bare --coverage test.mjs",
    "test:node": "brittle-node --coverage test.mjs"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/b4a.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/b4a/issues"
  },
  "homepage": "https://github.com/holepunchto/b4a#readme",
  "devDependencies": {
    "brittle": "^3.5.2",
    "lunte": "^1.7.0",
    "nanobench": "^3.0.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^2.0.0"
  },
  "peerDependencies": {
    "react-native-b4a": "*"
  },
  "peerDependenciesMeta": {
    "react-native-b4a": {
      "optional": true
    }
  }
}
const ESC = '\x1b'
const CSI = ESC + '['
const SGR = (n) => CSI + n + 'm'

exports.constants = {
  ESC,
  CSI,
  SGR
}

// https://en.wikipedia.org/wiki/ANSI_escape_code#CSI_(Control_Sequence_Introducer)_sequences

exports.cursorHide = CSI + '?25l'
exports.cursorShow = CSI + '?25h'

exports.cursorUp = function cursorUp(n = 1) {
  return CSI + n + 'A'
}

exports.cursorDown = function cursorDown(n = 1) {
  return CSI + n + 'B'
}

exports.cursorForward = function cursorForward(n = 1) {
  return CSI + n + 'C'
}

exports.cursorBack = function cursorBack(n = 1) {
  return CSI + n + 'D'
}

exports.cursorNextLine = function cursorNextLine(n = 1) {
  return CSI + n + 'E'
}

exports.cursorPreviousLine = function cursorPreviousLine(n = 1) {
  return CSI + n + 'F'
}

exports.cursorPosition = function cursorPosition(column, row = 0) {
  if (row === 0) return CSI + (column + 1) + 'G'

  return CSI + (row + 1) + ';' + (column + 1) + 'H'
}

exports.eraseDisplayEnd = CSI + 'J'
exports.eraseDisplayStart = CSI + '1J'
exports.eraseDisplay = CSI + '2J'
exports.eraseLineEnd = CSI + 'K'
exports.eraseLineStart = CSI + '1K'
exports.eraseLine = CSI + '2K'

exports.scrollUp = function scrollUp(n = 1) {
  return CSI + n + 'S'
}

exports.scrollDown = function scrollDown(n = 1) {
  return CSI + n + 'T'
}

// https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_(Select_Graphic_Rendition)_parameters

exports.modifierReset = SGR(0)
exports.modifierBold = SGR(1)
exports.modifierDim = SGR(2)
exports.modifierItalic = SGR(3)
exports.modifierUnderline = SGR(4)
exports.modifierNormal = SGR(22)
exports.modifierNotItalic = SGR(23)
exports.modifierNotUnderline = SGR(24)

// https://en.wikipedia.org/wiki/ANSI_escape_code#Colors

exports.colorBlack = SGR(30)
exports.colorRed = SGR(31)
exports.colorGreen = SGR(32)
exports.colorYellow = SGR(33)
exports.colorBlue = SGR(34)
exports.colorMagenta = SGR(35)
exports.colorCyan = SGR(36)
exports.colorWhite = SGR(37)
exports.colorDefault = SGR(39)
exports.colorBrightBlack = SGR(90)
exports.colorBrightRed = SGR(91)
exports.colorBrightGreen = SGR(92)
exports.colorBrightYellow = SGR(93)
exports.colorBrightBlue = SGR(94)
exports.colorBrightMagenta = SGR(95)
exports.colorBrightCyan = SGR(96)
exports.colorBrightWhite = SGR(97)
{
  "name": "bare-ansi-escapes",
  "version": "2.2.3",
  "description": "Parse and produce ANSI escape sequences",
  "exports": {
    "./package": "./package.json",
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    },
    "./key-decoder": {
      "types": "./key-decoder.d.ts",
      "default": "./key-decoder.js"
    }
  },
  "files": [
    "index.js",
    "index.d.ts",
    "key-decoder.js",
    "key-decoder.d.ts"
  ],
  "scripts": {
    "test": "prettier . --check && bare test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-ansi-escapes.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-ansi-escapes/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-ansi-escapes#readme",
  "dependencies": {
    "bare-stream": "^2.6.5"
  },
  "devDependencies": {
    "bare-buffer": "^3.0.2",
    "brittle": "^3.3.2",
    "prettier": "^3.4.2",
    "prettier-config-standard": "^7.0.0"
  },
  "peerDependencies": {
    "bare-buffer": "*"
  },
  "peerDependenciesMeta": {
    "bare-buffer": {
      "optional": true
    }
  }
}
const inspect = require('bare-inspect')

class AssertionError extends Error {
  constructor(opts = {}) {
    let { message = null, actual, expected, operator } = opts

    if (message === null) {
      message = `${inspect(actual)} ${operator} ${inspect(expected)}`
    }

    super(message)

    this.actual = actual
    this.expected = expected
    this.operator = operator
  }

  get name() {
    return 'AssertionError'
  }

  get code() {
    'ASSERTION'
  }
}

function assertFail(opts, fn) {
  if (opts.message instanceof Error) throw opts.message

  const err = new AssertionError(opts)

  if (Error.captureStackTrace) Error.captureStackTrace(err, fn)

  throw err
}

module.exports = exports = function assert(actual, message) {
  if (actual) return

  assertFail({ message, actual, expected: true, operator: '==' }, assert)
}

exports.AssertionError = AssertionError

exports.fail = function fail(message) {
  if (message === undefined) message = 'Failed'

  assertFail({ message, operator: 'fail' }, fail)
}

exports.ok = function ok(actual, message) {
  if (actual) return

  assertFail({ message, actual, expected: true, operator: '==' }, ok)
}

exports.notOk = function ok(actual, message) {
  if (!actual) return

  assertFail({ message, actual, expected: false, operator: '==' }, ok)
}

exports.equal = function equal(actual, expected, message) {
  if (actual == expected || (actual !== actual && expected !== expected)) {
    return
  }

  assertFail({ message, actual, expected, operator: '==' }, equal)
}

exports.notEqual = function notEqual(actual, expected, message) {
  if (actual != expected && (actual === actual || expected === expected)) {
    return
  }

  assertFail({ message, actual, expected, operator: '!=' }, notEqual)
}

exports.strictEqual = function strictEqual(actual, expected, message) {
  if (Object.is(actual, expected)) return

  assertFail(
    { message, actual, expected, operator: 'strictEqual' },
    strictEqual
  )
}

exports.notStrictEqual = function notStrictEqual(actual, expected, message) {
  if (!Object.is(actual, expected)) return

  assertFail(
    { message, actual, expected, operator: 'notStrictEqual' },
    notStrictEqual
  )
}
{
  "name": "bare-assert",
  "version": "1.2.0",
  "description": "Assertion library for JavaScript",
  "exports": {
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    },
    "./package": "./package.json"
  },
  "files": [
    "index.js",
    "index.d.ts"
  ],
  "scripts": {
    "test": "prettier . --check && bare test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-assert.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-assert/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-assert#readme",
  "dependencies": {
    "bare-inspect": "^3.1.2"
  },
  "devDependencies": {
    "brittle": "^3.1.1",
    "prettier": "^3.4.2",
    "prettier-config-standard": "^7.0.0"
  }
}
const errors = require('./lib/errors')

class EventListener {
  constructor() {
    this.list = []
    this.count = 0
  }

  append(ctx, name, fn, once) {
    this.count++
    ctx.emit('newListener', name, fn) // Emit BEFORE adding
    this.list.push([fn, once])
  }

  prepend(ctx, name, fn, once) {
    this.count++
    ctx.emit('newListener', name, fn) // Emit BEFORE adding
    this.list.unshift([fn, once])
  }

  remove(ctx, name, fn) {
    for (let i = 0, n = this.list.length; i < n; i++) {
      const l = this.list[i]

      if (l[0] === fn) {
        this.list.splice(i, 1)

        if (this.count === 1) delete ctx._events[name]

        ctx.emit('removeListener', name, fn) // Emit AFTER removing

        this.count--
        return
      }
    }
  }

  removeAll(ctx, name) {
    const list = [...this.list]
    this.list = []

    if (this.count === list.length) delete ctx._events[name]

    for (let i = list.length - 1; i >= 0; i--) {
      ctx.emit('removeListener', name, list[i][0]) // Emit AFTER removing
    }

    this.count -= list.length
  }

  emit(ctx, name, ...args) {
    const list = [...this.list]

    for (let i = 0, n = list.length; i < n; i++) {
      const l = list[i]

      if (l[1] === true) this.remove(ctx, name, l[0])

      Reflect.apply(l[0], ctx, args)
    }

    return list.length > 0
  }
}

function appendListener(ctx, name, fn, once) {
  if (ctx._events === undefined) ctx._events = Object.create(null)
  const e = ctx._events[name] || (ctx._events[name] = new EventListener())
  e.append(ctx, name, fn, once)
  return ctx
}

function prependListener(ctx, name, fn, once) {
  if (ctx._events === undefined) ctx._events = Object.create(null)
  const e = ctx._events[name] || (ctx._events[name] = new EventListener())
  e.prepend(ctx, name, fn, once)
  return ctx
}

function removeListener(ctx, name, fn) {
  if (ctx._events === undefined) return ctx
  const e = ctx._events[name]
  if (e !== undefined) e.remove(ctx, name, fn)
  return ctx
}

function throwUnhandledError(...args) {
  let err

  if (args.length > 0) err = args[0]

  if (err instanceof Error === false) err = errors.UNHANDLED_ERROR(err)

  if (Error.captureStackTrace) {
    Error.captureStackTrace(err, exports.prototype.emit)
  }

  queueMicrotask(() => {
    throw err
  })
}

module.exports = exports = class EventEmitter {
  constructor() {
    this._events = Object.create(null)
  }

  addListener(name, fn) {
    return appendListener(this, name, fn, false)
  }

  addOnceListener(name, fn) {
    return appendListener(this, name, fn, true)
  }

  prependListener(name, fn) {
    return prependListener(this, name, fn, false)
  }

  prependOnceListener(name, fn) {
    return prependListener(this, name, fn, true)
  }

  removeListener(name, fn) {
    return removeListener(this, name, fn)
  }

  on(name, fn) {
    return appendListener(this, name, fn, false)
  }

  once(name, fn) {
    return appendListener(this, name, fn, true)
  }

  off(name, fn) {
    return removeListener(this, name, fn)
  }

  emit(name, ...args) {
    if (name === 'error' && this._events !== undefined && this._events.error === undefined) {
      throwUnhandledError(...args)
    }

    if (this._events === undefined) return false
    const e = this._events[name]
    return e === undefined ? false : e.emit(this, name, ...args)
  }

  listeners(name) {
    if (this._events === undefined) return []
    const e = this._events[name]
    return e === undefined ? [] : [...e.list]
  }

  rawListeners(name) {
    if (this._events === undefined) return []
    const e = this._events[name]
    return e === undefined ? [] : e.list.map((l) => l[0])
  }

  eventNames() {
    if (this._events === undefined) return []
    return Reflect.ownKeys(this._events)
  }

  listenerCount(name) {
    if (this._events === undefined) return 0
    const e = this._events[name]
    return e === undefined ? 0 : e.list.length
  }

  getMaxListeners() {
    return EventEmitter.defaultMaxListeners
  }

  setMaxListeners(n) {}

  removeAllListeners(name) {
    if (arguments.length === 0) {
      for (const key of Reflect.ownKeys(this._events)) {
        if (key === 'removeListener') continue
        this.removeAllListeners(key)
      }
      this.removeAllListeners('removeListener')
    } else {
      const e = this._events[name]
      if (e !== undefined) e.removeAll(this, name)
    }
    return this
  }
}

exports.EventEmitter = exports

exports.errors = errors

exports.defaultMaxListeners = 10

exports.on = function on(emitter, name, opts = {}) {
  const { signal } = opts

  if (signal && signal.aborted) {
    throw errors.OPERATION_ABORTED(signal.reason)
  }

  let error = null
  let done = false

  const events = []
  const promises = []

  if (name !== 'error') emitter.on('error', onerror)

  if (signal) signal.addEventListener('abort', onabort)

  emitter.on(name, onevent)

  return {
    next() {
      if (events.length) {
        return Promise.resolve({ value: events.shift(), done: false })
      }

      if (error) {
        const err = error

        error = null

        return Promise.reject(err)
      }

      if (done) return onclose()

      return new Promise((resolve, reject) => promises.push({ resolve, reject }))
    },

    return() {
      return onclose()
    },

    throw(err) {
      return onerror(err)
    },

    [Symbol.asyncIterator]() {
      return this
    }
  }

  function onevent(...args) {
    if (promises.length) {
      promises.shift().resolve({ value: args, done: false })
    } else {
      events.push(args)
    }
  }

  function onerror(err) {
    emitter.off(name, onevent).off('error', onerror)

    if (promises.length) {
      promises.shift().reject(err)
    } else {
      error = err
    }

    return Promise.resolve({ done: true })
  }

  function onabort() {
    signal.removeEventListener('abort', onabort)

    onerror(errors.OPERATION_ABORTED(signal.reason))
  }

  function onclose() {
    emitter.off(name, onevent)

    if (name !== 'error') emitter.off('error', onerror)

    if (signal) signal.removeEventListener('abort', onabort)

    done = true

    if (promises.length) promises.shift().resolve({ done: true })

    return Promise.resolve({ done: true })
  }
}

exports.once = function once(emitter, name, opts = {}) {
  const { signal } = opts

  if (signal && signal.aborted) {
    return Promise.reject(errors.OPERATION_ABORTED(signal.reason))
  }

  return new Promise((resolve, reject) => {
    if (name !== 'error') emitter.on('error', onerror)

    if (signal) signal.addEventListener('abort', onabort)

    emitter.once(name, onevent)

    function onevent(...args) {
      if (name !== 'error') emitter.off('error', onerror)

      if (signal) signal.removeEventListener('abort', onabort)

      resolve(args)
    }

    function onerror(err) {
      emitter.off(name, onevent)

      if (name !== 'error') emitter.off('error', onerror)

      reject(err)
    }

    function onabort() {
      signal.removeEventListener('abort', onabort)

      onerror(errors.OPERATION_ABORTED(signal.reason))
    }
  })
}

exports.forward = function forward(from, to, names, opts = {}) {
  if (typeof names === 'string') names = [names]

  const { emit = to.emit.bind(to) } = opts

  const listeners = names.map(
    (name) =>
      function onevent(...args) {
        emit(name, ...args)
      }
  )

  to.on('newListener', (name) => {
    const i = names.indexOf(name)

    if (i !== -1 && to.listenerCount(name) === 0) {
      from.on(name, listeners[i])
    }
  }).on('removeListener', (name) => {
    const i = names.indexOf(name)

    if (i !== -1 && to.listenerCount(name) === 0) {
      from.off(name, listeners[i])
    }
  })
}

exports.listenerCount = function listenerCount(emitter, name) {
  return emitter.listenerCount(name)
}

exports.getMaxListeners = function getMaxListeners(emitter) {
  if (typeof emitter.getMaxListeners === 'function') {
    return emitter.getMaxListeners()
  }

  return exports.defaultMaxListeners
}

exports.setMaxListeners = function setMaxListeners(n, ...emitters) {
  if (emitters.length === 0) exports.defaultMaxListeners = n
  else {
    for (const emitter of emitters) {
      if (typeof emitter.setMaxListeners === 'function') {
        emitter.setMaxListeners(n)
      }
    }
  }
}
module.exports = class EventEmitterError extends Error {
  constructor(msg, code, fn = EventEmitterError, opts) {
    super(`${code}: ${msg}`, opts)
    this.code = code

    if (Error.captureStackTrace) {
      Error.captureStackTrace(this, fn)
    }
  }

  get name() {
    return 'EventEmitterError'
  }

  static OPERATION_ABORTED(cause, msg = 'Operation aborted') {
    return new EventEmitterError(msg, 'OPERATION_ABORTED', EventEmitterError.OPERATION_ABORTED, {
      cause
    })
  }

  static UNHANDLED_ERROR(cause, msg = 'Unhandled error') {
    return new EventEmitterError(msg, 'UNHANDLED_ERROR', EventEmitterError.UNHANDLED_ERROR, {
      cause
    })
  }
}
{
  "name": "bare-events",
  "version": "2.9.1",
  "description": "Event emitters for JavaScript",
  "exports": {
    "./package": "./package.json",
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    },
    "./global": {
      "types": "./global.d.ts",
      "default": "./global.js"
    },
    "./web": {
      "types": "./web.d.ts",
      "default": "./web.js"
    },
    "./errors": "./lib/errors.js"
  },
  "files": [
    "index.js",
    "index.d.ts",
    "global.js",
    "global.d.ts",
    "web.js",
    "web.d.ts",
    "lib"
  ],
  "scripts": {
    "format": "prettier --write . && lunte --fix",
    "lint": "prettier --check . && lunte",
    "test": "npm run test:bare && npm run test:node",
    "test:bare": "brittle-bare --coverage test.js",
    "test:node": "brittle-node --coverage test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-events.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-events/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-events#readme",
  "devDependencies": {
    "bare-abort-controller": "^1.0.0",
    "brittle": "^3.3.2",
    "lunte": "^1.8.0",
    "prettier": "^3.4.2",
    "prettier-config-holepunch": "^2.0.0",
    "uncaughts": "^1.1.1"
  },
  "peerDependencies": {
    "bare-abort-controller": "*"
  },
  "peerDependenciesMeta": {
    "bare-abort-controller": {
      "optional": true
    }
  }
}
module.exports = require.addon()
const FIFO = require('fast-fifo')
const EventEmitter = require('bare-events')
const path = require('bare-path')
const { isURL, fileURLToPath } = require('bare-url')
const { Readable, Writable } = require('bare-stream')
const binding = require('./binding')
const constants = require('./lib/constants')
const FileError = require('./lib/errors')

const isWindows = Bare.platform === 'win32'

exports.constants = constants

class FileRequest {
  static borrow() {
    if (this._free.length > 0) return this._free.pop()
    return new FileRequest()
  }

  static return(req) {
    if (this._free.length < 32) this._free.push(req.reset())
    else req.destroy()
  }

  constructor() {
    this._reset()
    this._handle = binding.requestInit(this, this._onresult)
  }

  get handle() {
    return this._handle
  }

  retain(value) {
    this._retain = value // Tie the lifetime of `value` to the lifetime of `this`
  }

  reset() {
    if (this._handle === null) return this

    binding.requestReset(this._handle)

    this._reset()

    return this
  }

  destroy() {
    if (this._handle === null) return this

    binding.requestDestroy(this._handle)

    this._reset()
    this._handle = null

    return this
  }

  then(resolve, reject) {
    return this._promise.then(resolve, reject)
  }

  return() {
    if (this._handle === null) return this

    FileRequest.return(this)

    return this
  }

  _reset() {
    this._promise = new Promise((resolve, reject) => {
      this._resolve = resolve
      this._reject = reject
    })
    this._retain = null
  }

  _onresult(err, status) {
    if (err) this._reject(err)
    else this._resolve(status)
  }
}

FileRequest._free = []

function ok(result, cb) {
  if (typeof result === 'function') {
    cb = result
    result = undefined
  }

  if (cb) cb(null, result)
  else return result
}

function fail(err, cb) {
  if (cb) cb(err)
  else throw err
}

function done(err, result, cb) {
  if (typeof result === 'function') {
    cb = result
    result = undefined
  }

  if (err) fail(err, cb)
  else return ok(result, cb)
}

async function open(filepath, flags = 'r', mode = 0o666, cb) {
  if (typeof flags === 'function') {
    cb = flags
    flags = 'r'
    mode = 0o666
  } else if (typeof mode === 'function') {
    cb = mode
    mode = 0o666
  }

  if (typeof flags === 'string') flags = toFlags(flags)
  if (typeof mode === 'string') mode = toMode(mode)

  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  let fd
  let err = null
  try {
    binding.open(req.handle, filepath, flags, mode)

    fd = await req
  } catch (e) {
    err = new FileError(e.message, {
      operation: 'open',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }

  return done(err, fd, cb)
}

function openSync(filepath, flags = 'r', mode = 0o666) {
  if (typeof flags === 'string') flags = toFlags(flags)
  if (typeof mode === 'string') mode = toMode(mode)

  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  try {
    return binding.openSync(req.handle, filepath, flags, mode)
  } catch (e) {
    throw new FileError(e.message, {
      operation: 'open',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }
}

async function close(fd, cb) {
  const req = FileRequest.borrow()

  let err = null
  try {
    binding.close(req.handle, fd)

    await req
  } catch (e) {
    err = new FileError(e.message, { operation: 'close', code: e.code, fd })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function closeSync(fd) {
  const req = FileRequest.borrow()

  try {
    binding.closeSync(req.handle, fd)
  } catch (e) {
    throw new FileError(e.message, { operation: 'close', code: e.code, fd })
  } finally {
    req.return()
  }
}

async function access(filepath, mode = constants.F_OK, cb) {
  if (typeof mode === 'function') {
    cb = mode
    mode = constants.F_OK
  }

  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  let err = null
  try {
    binding.access(req.handle, filepath, mode)

    await req
  } catch (e) {
    err = new FileError(e.message, {
      operation: 'access',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function accessSync(filepath, mode = constants.F_OK) {
  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  try {
    binding.accessSync(req.handle, filepath, mode)
  } catch (e) {
    throw new FileError(e.message, {
      operation: 'access',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }
}

async function exists(filepath, cb) {
  let ok = true
  try {
    await access(filepath)
  } catch {
    ok = false
  }

  return done(null, ok, cb)
}

function existsSync(filepath) {
  try {
    accessSync(filepath)
  } catch {
    return false
  }

  return true
}

async function read(fd, buffer, offset = 0, len = buffer.byteLength - offset, pos = -1, cb) {
  if (typeof offset === 'function') {
    cb = offset
    offset = 0
    len = buffer.byteLength
    pos = -1
  } else if (typeof len === 'function') {
    cb = len
    len = buffer.byteLength - offset
    pos = -1
  } else if (typeof pos === 'function') {
    cb = pos
    pos = -1
  }

  if (typeof pos !== 'number') pos = -1

  const req = FileRequest.borrow()

  let bytes
  let err = null
  try {
    binding.read(req.handle, fd, buffer, offset, len, pos)

    req.retain(buffer)

    bytes = await req
  } catch (e) {
    err = new FileError(e.message, { operation: 'read', code: e.code, fd })
  } finally {
    req.return()
  }

  return done(err, bytes, cb)
}

function readSync(fd, buffer, offset = 0, len = buffer.byteLength - offset, pos = -1) {
  const req = FileRequest.borrow()

  try {
    return binding.readSync(req.handle, fd, buffer, offset, len, pos)
  } catch (e) {
    throw new FileError(e.message, { operation: 'read', code: e.code, fd })
  } finally {
    req.return()
  }
}

async function readv(fd, buffers, pos = -1, cb) {
  if (typeof pos === 'function') {
    cb = pos
    pos = -1
  }

  if (typeof pos !== 'number') pos = -1

  const req = FileRequest.borrow()

  let bytes
  let err = null
  try {
    binding.readv(req.handle, fd, buffers, pos)

    req.retain(buffers)

    bytes = await req
  } catch (e) {
    err = new FileError(e.message, { operation: 'readv', code: e.code, fd })
  } finally {
    req.return()
  }

  return done(err, bytes, cb)
}

function readvSync(fd, buffers, pos = -1) {
  if (typeof pos !== 'number') pos = -1

  const req = FileRequest.borrow()

  try {
    return binding.readvSync(req.handle, fd, buffers, pos)
  } catch (e) {
    throw new FileError(e.message, { operation: 'readv', code: e.code, fd })
  } finally {
    req.return()
  }
}

async function write(fd, data, offset, len, pos = -1, cb) {
  if (typeof data === 'string') {
    let encoding = len
    cb = pos
    pos = offset

    if (typeof pos === 'function') {
      cb = pos
      pos = -1
      encoding = 'utf8'
    } else if (typeof encoding === 'function') {
      cb = encoding
      encoding = 'utf8'
    }

    if (typeof pos === 'string') {
      encoding = pos
      pos = -1
    }

    data = Buffer.from(data, encoding)
    offset = 0
    len = data.byteLength
  } else if (typeof offset === 'function') {
    cb = offset
    offset = 0
    len = data.byteLength
    pos = -1
  } else if (typeof len === 'function') {
    cb = len
    len = data.byteLength - offset
    pos = -1
  } else if (typeof pos === 'function') {
    cb = pos
    pos = -1
  }

  if (typeof offset !== 'number') offset = 0
  if (typeof len !== 'number') len = data.byteLength - offset
  if (typeof pos !== 'number') pos = -1

  const req = FileRequest.borrow()

  let bytes
  let err = null
  try {
    binding.write(req.handle, fd, data, offset, len, pos)

    req.retain(data)

    bytes = await req
  } catch (e) {
    err = new FileError(e.message, { operation: 'write', code: e.code, fd })
  } finally {
    req.return()
  }

  return done(err, bytes, cb)
}

function writeSync(fd, data, offset, len, pos = -1) {
  if (typeof data === 'string') {
    let encoding = len
    pos = offset

    if (typeof pos === 'string') {
      encoding = pos
      pos = -1
    }

    data = Buffer.from(data, encoding)
    offset = 0
    len = data.byteLength
  }

  if (typeof offset !== 'number') offset = 0
  if (typeof len !== 'number') len = data.byteLength - offset
  if (typeof pos !== 'number') pos = -1

  const req = FileRequest.borrow()

  try {
    return binding.writeSync(req.handle, fd, data, offset, len, pos)
  } catch (e) {
    throw new FileError(e.message, { operation: 'write', code: e.code, fd })
  } finally {
    req.return()
  }
}

async function writev(fd, buffers, pos = -1, cb) {
  if (typeof pos === 'function') {
    cb = pos
    pos = -1
  }

  if (typeof pos !== 'number') pos = -1

  const req = FileRequest.borrow()

  let bytes
  let err = null
  try {
    binding.writev(req.handle, fd, buffers, pos)

    req.retain(buffers)

    bytes = await req
  } catch (e) {
    err = new FileError(e.message, { operation: 'writev', code: e.code, fd })
  } finally {
    req.return()
  }

  return done(err, bytes, cb)
}

function writevSync(fd, buffers, pos = -1) {
  if (typeof pos !== 'number') pos = -1

  const req = FileRequest.borrow()

  try {
    return binding.writevSync(req.handle, fd, buffers, pos)
  } catch (e) {
    throw new FileError(e.message, { operation: 'writev', code: e.code, fd })
  } finally {
    req.return()
  }
}

async function stat(filepath, cb) {
  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  let st
  let err = null
  try {
    binding.stat(req.handle, filepath)

    await req

    st = new Stats(...binding.requestResultStat(req.handle))
  } catch (e) {
    err = new FileError(e.message, { operation: 'stat', code: e.code, path: filepath })
  } finally {
    req.return()
  }

  return done(err, st, cb)
}

function statSync(filepath) {
  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  try {
    binding.statSync(req.handle, filepath)

    return new Stats(...binding.requestResultStat(req.handle))
  } catch (e) {
    throw new FileError(e.message, { operation: 'stat', code: e.code, path: filepath })
  } finally {
    req.return()
  }
}

async function lstat(filepath, cb) {
  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  let st
  let err = null
  try {
    binding.lstat(req.handle, filepath)

    await req

    st = new Stats(...binding.requestResultStat(req.handle))
  } catch (e) {
    err = new FileError(e.message, { operation: 'lstat', code: e.code, path: filepath })
  } finally {
    req.return()
  }

  return done(err, st, cb)
}

function lstatSync(filepath) {
  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  try {
    binding.lstatSync(req.handle, filepath)

    return new Stats(...binding.requestResultStat(req.handle))
  } catch (e) {
    throw new FileError(e.message, { operation: 'lstat', code: e.code, path: filepath })
  } finally {
    req.return()
  }
}

async function fstat(fd, cb) {
  const req = FileRequest.borrow()

  let st
  let err = null
  try {
    binding.fstat(req.handle, fd)

    await req

    st = new Stats(...binding.requestResultStat(req.handle))
  } catch (e) {
    err = new FileError(e.message, { operation: 'fstat', code: e.code, fd })
  } finally {
    req.return()
  }

  return done(err, st, cb)
}

function fstatSync(fd) {
  const req = FileRequest.borrow()

  try {
    binding.fstatSync(req.handle, fd)

    return new Stats(...binding.requestResultStat(req.handle))
  } catch (e) {
    throw new FileError(e.message, { operation: 'fstat', code: e.code, fd })
  } finally {
    req.return()
  }
}

async function statfs(filepath, cb) {
  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  let st
  let err = null
  try {
    binding.statfs(req.handle, filepath)

    await req

    st = new StatFs(...binding.requestResultStatfs(req.handle))
  } catch (e) {
    err = new FileError(e.message, { operation: 'statfs', code: e.code, path: filepath })
  } finally {
    req.return()
  }

  return done(err, st, cb)
}

function statfsSync(filepath) {
  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  try {
    binding.statfsSync(req.handle, filepath)

    return new StatFs(...binding.requestResultStatfs(req.handle))
  } catch (e) {
    throw new FileError(e.message, { operation: 'statfs', code: e.code, path: filepath })
  } finally {
    req.return()
  }
}

async function ftruncate(fd, len = 0, cb) {
  if (typeof len === 'function') {
    cb = len
    len = 0
  }

  if (typeof len !== 'number') len = 0

  const req = FileRequest.borrow()

  let err = null
  try {
    binding.ftruncate(req.handle, fd, len)

    await req
  } catch (e) {
    err = new FileError(e.message, { operation: 'ftruncate', code: e.code, fd })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function ftruncateSync(fd, len = 0) {
  if (typeof len !== 'number') len = 0

  const req = FileRequest.borrow()

  try {
    binding.ftruncateSync(req.handle, fd, len)
  } catch (e) {
    throw new FileError(e.message, { operation: 'ftruncate', code: e.code, fd })
  } finally {
    req.return()
  }
}

async function truncate(filepath, len, cb) {
  let fd = -1
  let err

  try {
    fd = await open(filepath, 'r+')

    await ftruncate(fd, len)
  } catch (e) {
    err = e
  } finally {
    if (fd !== -1) await close(fd)
  }

  return done(err, cb)
}

function truncateSync(filepath, len) {
  let fd = -1
  let err

  try {
    fd = openSync(filepath, 'r+')

    ftruncateSync(fd, len)
  } catch (e) {
    err = e
  } finally {
    if (fd !== -1) closeSync(fd)
  }
}

async function chmod(filepath, mode, cb) {
  if (typeof mode === 'string') mode = toMode(mode)

  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  let err = null
  try {
    binding.chmod(req.handle, filepath, mode)

    await req
  } catch (e) {
    err = new FileError(e.message, {
      operation: 'chmod',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function chmodSync(filepath, mode) {
  if (typeof mode === 'string') mode = toMode(mode)

  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  try {
    binding.chmodSync(req.handle, filepath, mode)
  } catch (e) {
    throw new FileError(e.message, {
      operation: 'chmod',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }
}

async function fchmod(fd, mode, cb) {
  if (typeof mode === 'string') mode = toMode(mode)

  const req = FileRequest.borrow()

  let err = null
  try {
    binding.fchmod(req.handle, fd, mode)

    await req
  } catch (e) {
    err = new FileError(e.message, { operation: 'fchmod', code: e.code, fd })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function fchmodSync(fd, mode) {
  if (typeof mode === 'string') mode = toMode(mode)

  const req = FileRequest.borrow()

  try {
    binding.fchmodSync(req.handle, fd, mode)
  } catch (e) {
    throw new FileError(e.message, { operation: 'fchmod', code: e.code, fd })
  } finally {
    req.return()
  }
}

async function chown(filepath, uid, gid, cb) {
  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  let err = null
  try {
    binding.chown(req.handle, filepath, uid, gid)

    await req
  } catch (e) {
    err = new FileError(e.message, { operation: 'chown', code: e.code, path: filepath })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function chownSync(filepath, uid, gid) {
  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  try {
    binding.chownSync(req.handle, filepath, uid, gid)
  } catch (e) {
    throw new FileError(e.message, { operation: 'chownSync', code: e.code, path: filepath })
  } finally {
    req.return()
  }
}

async function lchown(filepath, uid, gid, cb) {
  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  let err = null
  try {
    binding.lchown(req.handle, filepath, uid, gid)

    await req
  } catch (e) {
    err = new FileError(e.message, { operation: 'lchown', code: e.code, path: filepath })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function lchownSync(filepath, uid, gid) {
  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  try {
    binding.lchownSync(req.handle, filepath, uid, gid)
  } catch (e) {
    throw new FileError(e.message, { operation: 'lchownSync', code: e.code, path: filepath })
  } finally {
    req.return()
  }
}

async function fchown(fd, uid, gid, cb) {
  const req = FileRequest.borrow()

  let err = null
  try {
    binding.fchown(req.handle, fd, uid, gid)

    await req
  } catch (e) {
    err = new FileError(e.message, { operation: 'fchown', code: e.code, fd })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function fchownSync(fd, uid, gid) {
  const req = FileRequest.borrow()

  try {
    binding.fchownSync(req.handle, fd, uid, gid)
  } catch (e) {
    throw new FileError(e.message, { operation: 'fchownSync', code: e.code, fd })
  } finally {
    req.return()
  }
}

async function utimes(filepath, atime, mtime, cb) {
  if (typeof atime !== 'number') atime = atime.getTime() / 1000
  if (typeof mtime !== 'number') mtime = mtime.getTime() / 1000

  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  let err = null
  try {
    binding.utimes(req.handle, filepath, atime, mtime)

    await req
  } catch (e) {
    err = new FileError(e.message, { operation: 'utimes', code: e.code, path: filepath })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function utimesSync(filepath, atime, mtime) {
  if (typeof atime !== 'number') atime = atime.getTime() / 1000
  if (typeof mtime !== 'number') mtime = mtime.getTime() / 1000

  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  try {
    binding.utimesSync(req.handle, filepath, atime, mtime)
  } catch (e) {
    throw new FileError(e.message, { operation: 'utimes', code: e.code, path: filepath })
  } finally {
    req.return()
  }
}

async function lutimes(filepath, atime, mtime, cb) {
  if (typeof atime !== 'number') atime = atime.getTime() / 1000
  if (typeof mtime !== 'number') mtime = mtime.getTime() / 1000

  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  let err = null
  try {
    binding.lutimes(req.handle, filepath, atime, mtime)

    await req
  } catch (e) {
    err = new FileError(e.message, { operation: 'lutimes', code: e.code, path: filepath })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function lutimesSync(filepath, atime, mtime) {
  if (typeof atime !== 'number') atime = atime.getTime() / 1000
  if (typeof mtime !== 'number') mtime = mtime.getTime() / 1000

  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  try {
    binding.lutimesSync(req.handle, filepath, atime, mtime)
  } catch (e) {
    throw new FileError(e.message, { operation: 'lutimes', code: e.code, path: filepath })
  } finally {
    req.return()
  }
}

async function futimes(fd, atime, mtime, cb) {
  if (typeof atime !== 'number') atime = atime.getTime() / 1000
  if (typeof mtime !== 'number') mtime = mtime.getTime() / 1000

  const req = FileRequest.borrow()

  let err = null
  try {
    binding.futimes(req.handle, fd, atime, mtime)

    await req
  } catch (e) {
    err = new FileError(e.message, { operation: 'futimes', code: e.code, fd })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function futimesSync(fd, atime, mtime) {
  if (typeof atime !== 'number') atime = atime.getTime() / 1000
  if (typeof mtime !== 'number') mtime = mtime.getTime() / 1000

  const req = FileRequest.borrow()

  try {
    binding.futimesSync(req.handle, fd, atime, mtime)
  } catch (e) {
    throw new FileError(e.message, { operation: 'futimesSync', code: e.code, fd })
  } finally {
    req.return()
  }
}

async function link(src, dst, cb) {
  src = toNamespacedPath(src)
  dst = toNamespacedPath(dst)

  const req = FileRequest.borrow()

  let err = null
  try {
    binding.link(req.handle, src, dst)

    await req
  } catch (e) {
    err = new FileError(e.message, { operation: 'link', code: e.code, path: src, destination: dst })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function linkSync(src, dst) {
  src = toNamespacedPath(src)
  dst = toNamespacedPath(dst)

  const req = FileRequest.borrow()

  try {
    binding.linkSync(req.handle, src, dst)
  } catch (e) {
    throw new FileError(e.message, {
      operation: 'linkSync',
      code: e.code,
      path: src,
      destination: dst
    })
  } finally {
    req.return()
  }
}

async function mkdir(filepath, opts, cb) {
  if (typeof opts === 'function') {
    cb = opts
    opts = { mode: 0o777 }
  }

  if (typeof opts === 'number') opts = { mode: opts }
  else if (!opts) opts = {}

  const mode = typeof opts.mode === 'number' ? opts.mode : 0o777

  filepath = toNamespacedPath(filepath)

  if (opts.recursive) {
    let err = null
    try {
      try {
        await mkdir(filepath, { mode })
      } catch (err) {
        if (err.code !== 'ENOENT') {
          if (!(await stat(filepath)).isDirectory()) throw err
        } else {
          while (filepath.endsWith(path.sep)) filepath = filepath.slice(0, -1)
          const i = filepath.lastIndexOf(path.sep)
          if (i <= 0) throw err

          await mkdir(filepath.slice(0, i), { mode, recursive: true })

          try {
            await mkdir(filepath, { mode })
          } catch (err) {
            if (!(await stat(filepath)).isDirectory()) throw err
          }
        }
      }
    } catch (e) {
      err = e
    }

    return done(err, cb)
  }

  const req = FileRequest.borrow()

  let err = null
  try {
    binding.mkdir(req.handle, filepath, mode)

    await req
  } catch (e) {
    err = new FileError(e.message, {
      operation: 'mkdir',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function mkdirSync(filepath, opts) {
  if (typeof opts === 'number') opts = { mode: opts }
  else if (!opts) opts = {}

  const mode = typeof opts.mode === 'number' ? opts.mode : 0o777

  filepath = toNamespacedPath(filepath)

  if (opts.recursive) {
    try {
      mkdirSync(filepath, { mode })
    } catch (err) {
      if (err.code !== 'ENOENT') {
        if (!statSync(filepath).isDirectory()) throw err
      } else {
        while (filepath.endsWith(path.sep)) filepath = filepath.slice(0, -1)
        const i = filepath.lastIndexOf(path.sep)
        if (i <= 0) throw err

        mkdirSync(filepath.slice(0, i), { mode, recursive: true })

        try {
          mkdirSync(filepath, { mode })
        } catch (err) {
          if (!statSync(filepath).isDirectory()) throw err
        }
      }
    }

    return
  }

  const req = FileRequest.borrow()

  try {
    binding.mkdirSync(req.handle, filepath, mode)
  } catch (e) {
    throw new FileError(e.message, {
      operation: 'mkdir',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }
}

async function mkdtemp(prefix, cb) {
  prefix = toNamespacedPath(prefix)

  const req = FileRequest.borrow()

  let res
  let err = null
  try {
    binding.mkdtemp(req.handle, prefix + 'XXXXXX')

    await req

    res = binding.requestResultPath(req.handle)
  } catch (e) {
    err = new FileError(e.message, { operation: 'mkdtemp', code: e.code, path: prefix })
  } finally {
    req.return()
  }

  return done(err, res, cb)
}

function mkdtempSync(prefix) {
  prefix = toNamespacedPath(prefix)

  const req = FileRequest.borrow()

  try {
    binding.mkdtempSync(req.handle, prefix + 'XXXXXX')

    return binding.requestResultPath(req.handle)
  } catch (e) {
    throw new FileError(e.message, { operation: 'mkdtempSync', code: e.code, path: prefix })
  } finally {
    req.return()
  }
}

async function rmdir(filepath, cb) {
  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  let err = null
  try {
    binding.rmdir(req.handle, filepath)

    await req
  } catch (e) {
    err = new FileError(e.message, {
      operation: 'rmdir',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function rmdirSync(filepath) {
  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  try {
    binding.rmdirSync(req.handle, filepath)
  } catch (e) {
    throw new FileError(e.message, {
      operation: 'rmdir',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }
}

async function rm(filepath, opts, cb) {
  if (typeof opts === 'function') {
    cb = opts
    opts = {}
  }

  if (!opts) opts = {}

  filepath = toNamespacedPath(filepath)

  let err = null
  try {
    const st = await lstat(filepath)

    if (st.isDirectory()) {
      if (opts.recursive) {
        try {
          await rmdir(filepath)
        } catch (err) {
          if (err.code !== 'ENOTEMPTY') throw err

          const files = await readdir(filepath)

          for (const file of files) {
            await rm(filepath + path.sep + file, opts)
          }

          await rmdir(filepath)
        }
      } else {
        throw new FileError('is a directory', {
          operation: 'rm',
          code: 'EISDIR',
          path: filepath
        })
      }
    } else {
      await unlink(filepath)
    }
  } catch (e) {
    if (e.code !== 'ENOENT' || !opts.force) err = e
  }

  return done(err, cb)
}

function rmSync(filepath, opts) {
  if (!opts) opts = {}

  filepath = toNamespacedPath(filepath)

  try {
    const st = lstatSync(filepath)

    if (st.isDirectory()) {
      if (opts.recursive) {
        try {
          rmdirSync(filepath)
        } catch (err) {
          if (err.code !== 'ENOTEMPTY') throw err

          const files = readdirSync(filepath)

          for (const file of files) {
            rmSync(filepath + path.sep + file, opts)
          }

          rmdirSync(filepath)
        }
      } else {
        throw new FileError('is a directory', {
          operation: 'rm',
          code: 'EISDIR',
          path: filepath
        })
      }
    } else {
      unlinkSync(filepath)
    }
  } catch (err) {
    if (err.code !== 'ENOENT' || !opts.force) throw err
  }
}

async function unlink(filepath, cb) {
  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  let err = null
  try {
    binding.unlink(req.handle, filepath)

    await req
  } catch (e) {
    err = new FileError(e.message, {
      operation: 'unlink',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function unlinkSync(filepath) {
  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  try {
    binding.unlinkSync(req.handle, filepath)
  } catch (e) {
    throw new FileError(e.message, {
      operation: 'unlink',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }
}

async function rename(src, dst, cb) {
  src = toNamespacedPath(src)
  dst = toNamespacedPath(dst)

  const req = FileRequest.borrow()

  let err = null
  try {
    binding.rename(req.handle, src, dst)

    await req
  } catch (e) {
    err = new FileError(e.message, {
      operation: 'rename',
      code: e.code,
      path: src,
      destination: dst
    })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function renameSync(src, dst) {
  src = toNamespacedPath(src)
  dst = toNamespacedPath(dst)

  const req = FileRequest.borrow()

  try {
    binding.renameSync(req.handle, src, dst)
  } catch (e) {
    throw new FileError(e.message, {
      operation: 'rename',
      code: e.code,
      path: src,
      destination: dst
    })
  } finally {
    req.return()
  }
}

async function copyFile(src, dst, mode = 0, cb) {
  if (typeof mode === 'function') {
    cb = mode
    mode = 0
  }

  src = toNamespacedPath(src)
  dst = toNamespacedPath(dst)

  const req = FileRequest.borrow()

  let err = null
  try {
    binding.copyfile(req.handle, src, dst, mode)

    await req
  } catch (e) {
    err = new FileError(e.message, {
      operation: 'copyfile',
      code: e.code,
      path: src,
      destination: dst
    })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function copyFileSync(src, dst, mode = 0) {
  src = toNamespacedPath(src)
  dst = toNamespacedPath(dst)

  const req = FileRequest.borrow()

  try {
    binding.copyfileSync(req.handle, src, dst, mode)
  } catch (e) {
    throw new FileError(e.message, {
      operation: 'copyfile',
      code: e.code,
      path: src,
      destination: dst
    })
  } finally {
    req.return()
  }
}

async function cp(src, dst, opts, cb) {
  if (typeof opts === 'function') {
    cb = opts
    opts = {}
  }

  if (!opts) opts = {}

  src = toNamespacedPath(src)
  dst = toNamespacedPath(dst)

  let err = null
  try {
    const st = await lstat(src)

    if (st.isDirectory()) {
      if (opts.recursive !== true) {
        throw new FileError('is a directory', { operation: 'cp', code: 'EISDIR', path: src })
      }

      try {
        await lstat(dst)
      } catch (e) {
        if (e.code !== 'ENOENT') throw e

        await mkdir(dst, { mode: st.mode, recursive: true })
      }

      const dir = await opendir(src)
      for await (const { name } of dir) {
        await cp(path.join(src, name), path.join(dst, name), opts)
      }
    } else if (st.isFile()) {
      await copyFile(src, dst)
      await chmod(dst, st.mode)
    }
  } catch (e) {
    err = e
  }

  return done(err, cb)
}

function cpSync(src, dst, opts = {}) {
  src = toNamespacedPath(src)
  dst = toNamespacedPath(dst)

  const st = lstatSync(src)

  if (st.isDirectory()) {
    if (opts.recursive !== true) {
      throw new FileError('is a directory', { operation: 'cp', code: 'EISDIR', path: src })
    }

    try {
      lstatSync(dst)
    } catch (e) {
      if (e.code !== 'ENOENT') throw e

      mkdirSync(dst, { mode: st.mode, recursive: true })
    }

    const dir = opendirSync(src)
    for (const { name } of dir) {
      cpSync(path.join(src, name), path.join(dst, name), opts)
    }
  } else if (st.isFile()) {
    copyFileSync(src, dst)
    chmodSync(dst, st.mode)
  }
}

async function realpath(filepath, opts, cb) {
  if (typeof opts === 'function') {
    cb = opts
    opts = {}
  }

  if (typeof opts === 'string') opts = { encoding: opts }
  else if (!opts) opts = {}

  const { encoding = 'utf8' } = opts

  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  let res
  let err = null
  try {
    binding.realpath(req.handle, filepath)

    await req

    res = Buffer.from(binding.requestResultString(req.handle))

    if (encoding !== 'buffer') res = res.toString(encoding)
  } catch (e) {
    err = new FileError(e.message, {
      operation: 'realpath',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }

  return done(err, res, cb)
}

function realpathSync(filepath, opts) {
  if (typeof opts === 'string') opts = { encoding: opts }
  else if (!opts) opts = {}

  const { encoding = 'utf8' } = opts

  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  try {
    binding.realpathSync(req.handle, filepath)

    let res = Buffer.from(binding.requestResultString(req.handle))

    if (encoding !== 'buffer') res = res.toString(encoding)

    return res
  } catch (e) {
    throw new FileError(e.message, {
      operation: 'realpath',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }
}

async function readlink(filepath, opts, cb) {
  if (typeof opts === 'function') {
    cb = opts
    opts = {}
  }

  if (typeof opts === 'string') opts = { encoding: opts }
  else if (!opts) opts = {}

  const { encoding = 'utf8' } = opts

  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  let res
  let err = null
  try {
    binding.readlink(req.handle, filepath)

    await req

    res = Buffer.from(binding.requestResultString(req.handle))

    if (encoding !== 'buffer') res = res.toString(encoding)
  } catch (e) {
    err = new FileError(e.message, {
      operation: 'readlink',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }

  return done(err, res, cb)
}

function readlinkSync(filepath, opts) {
  if (typeof opts === 'string') opts = { encoding: opts }
  else if (!opts) opts = {}

  const { encoding = 'utf8' } = opts

  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  try {
    binding.readlinkSync(req.handle, filepath)

    let res = Buffer.from(binding.requestResultString(req.handle))

    if (encoding !== 'buffer') res = res.toString(encoding)

    return res
  } catch (e) {
    throw new FileError(e.message, {
      operation: 'readlink',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }
}

async function fsync(fd, cb) {
  const req = FileRequest.borrow()

  let err = null
  try {
    binding.fsync(req.handle, fd)

    await req
  } catch (e) {
    err = new FileError(e.message, { operation: 'fsync', code: e.code, fd })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function fsyncSync(fd) {
  const req = FileRequest.borrow()

  try {
    binding.fsyncSync(req.handle, fd)
  } catch (e) {
    throw new FileError(e.message, { operation: 'fsyncSync', code: e.code, fd })
  } finally {
    req.return()
  }
}

async function fdatasync(fd, cb) {
  const req = FileRequest.borrow()

  let err = null
  try {
    binding.fdatasync(req.handle, fd)

    await req
  } catch (e) {
    err = new FileError(e.message, { operation: 'fdatasync', code: e.code, fd })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function fdatasyncSync(fd) {
  const req = FileRequest.borrow()

  try {
    binding.fdatasyncSync(req.handle, fd)
  } catch (e) {
    throw new FileError(e.message, { operation: 'fdatasyncSync', code: e.code, fd })
  } finally {
    req.return()
  }
}

function normalizeSymlinkTarget(target, type, filepath) {
  if (isWindows) {
    if (type === constants.UV_FS_SYMLINK_JUNCTION) target = path.resolve(filepath, '..', target)

    if (path.isAbsolute(target)) return path.toNamespacedPath(target)

    return target.replace(/\//g, path.sep)
  }

  return target
}

async function symlink(target, filepath, type, cb) {
  if (typeof type === 'function') {
    cb = type
    type = null
  }

  filepath = toNamespacedPath(filepath)

  if (typeof type === 'string') {
    switch (type) {
      case 'dir':
        type = constants.UV_FS_SYMLINK_DIR
        break
      case 'junction':
        type = constants.UV_FS_SYMLINK_JUNCTION
        break
      case 'file':
      default:
        type = 0
        break
    }
  } else if (typeof type !== 'number') {
    if (isWindows) {
      target = path.resolve(filepath, '..', target)

      try {
        type = (await stat(target)).isDirectory()
          ? constants.UV_FS_SYMLINK_DIR
          : constants.UV_FS_SYMLINK_JUNCTION
      } catch {
        type = 0
      }
    } else {
      type = 0
    }
  }

  target = normalizeSymlinkTarget(target, type, filepath)

  const req = FileRequest.borrow()

  let err = null
  try {
    binding.symlink(req.handle, target, filepath, type)

    await req
  } catch (e) {
    err = new FileError(e.message, {
      operation: 'symlink',
      code: e.code,
      path: target,
      destination: filepath
    })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function symlinkSync(target, filepath, type) {
  filepath = toNamespacedPath(filepath)

  if (typeof type === 'string') {
    switch (type) {
      case 'dir':
        type = constants.UV_FS_SYMLINK_DIR
        break
      case 'junction':
        type = constants.UV_FS_SYMLINK_JUNCTION
        break
      case 'file':
      default:
        type = 0
        break
    }
  } else if (typeof type !== 'number') {
    if (isWindows) {
      target = path.resolve(filepath, '..', target)

      try {
        type = statSync(target).isDirectory()
          ? constants.UV_FS_SYMLINK_DIR
          : constants.UV_FS_SYMLINK_JUNCTION
      } catch {
        type = 0
      }
    } else {
      type = 0
    }
  }

  target = normalizeSymlinkTarget(target, type, filepath)

  const req = FileRequest.borrow()

  try {
    binding.symlinkSync(req.handle, target, filepath, type)
  } catch (e) {
    throw new FileError(e.message, {
      operation: 'symlink',
      code: e.code,
      path: target,
      destination: filepath
    })
  } finally {
    req.return()
  }
}

async function opendir(filepath, opts, cb) {
  if (typeof opts === 'function') {
    cb = opts
    opts = {}
  }

  if (typeof opts === 'string') opts = { encoding: opts }
  else if (!opts) opts = {}

  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  let dir
  let err = null
  try {
    binding.opendir(req.handle, filepath)

    await req

    dir = new Dir(filepath, binding.requestResultDir(req.handle), opts)
  } catch (e) {
    err = new FileError(e.message, {
      operation: 'opendir',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }

  return done(err, dir, cb)
}

function opendirSync(filepath, opts) {
  if (typeof opts === 'string') opts = { encoding: opts }
  else if (!opts) opts = {}

  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  try {
    binding.opendirSync(req.handle, filepath)

    return new Dir(filepath, binding.requestResultDir(req.handle), opts)
  } catch (e) {
    throw new FileError(e.message, {
      operation: 'opendir',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }
}

async function readdir(filepath, opts, cb) {
  if (typeof opts === 'function') {
    cb = opts
    opts = {}
  }

  if (typeof opts === 'string') opts = { encoding: opts }
  else if (!opts) opts = {}

  const { withFileTypes = false, recursive = false } = opts

  filepath = toNamespacedPath(filepath)

  const queue = [filepath]
  let result = []
  let err = null
  try {
    while (queue.length !== 0) {
      const dir = await opendir(queue.pop())

      for await (const entry of dir) {
        const entryPath = path.join(entry.parentPath, entry.name)

        if (withFileTypes) {
          result.push(entry)
        } else {
          result.push(path.relative(filepath, entryPath))
        }

        if (recursive && entry.isDirectory()) queue.push(entryPath)
      }
    }
  } catch (e) {
    result = []
    err = e
  }

  return done(err, result, cb)
}

function readdirSync(filepath, opts) {
  if (typeof opts === 'string') opts = { encoding: opts }
  else if (!opts) opts = {}

  const { withFileTypes = false, recursive = false } = opts

  filepath = toNamespacedPath(filepath)

  const queue = [filepath]
  const result = []

  while (queue.length !== 0) {
    const dir = opendirSync(queue.pop(), opts)

    for (const entry of dir) {
      const entryPath = path.join(entry.parentPath, entry.name)

      if (withFileTypes) {
        result.push(entry)
      } else {
        result.push(path.relative(filepath, entryPath))
      }

      if (recursive && entry.isDirectory()) queue.push(entryPath)
    }
  }

  return result
}

async function readFile(filepath, opts, cb) {
  if (typeof opts === 'function') {
    cb = opts
    opts = {}
  }

  if (typeof opts === 'string') opts = { encoding: opts }
  else if (!opts) opts = {}

  const { encoding = 'buffer' } = opts

  let fd = -1
  let buffer = null
  let err = null
  try {
    fd = await open(filepath, opts.flag || 'r')

    const st = await fstat(fd)

    let len = 0

    if (st.size === 0) {
      const buffers = []

      while (true) {
        buffer = Buffer.allocUnsafe(8192)
        const r = await read(fd, buffer)
        len += r
        if (r === 0) break
        buffers.push(buffer.subarray(0, r))
      }

      buffer = Buffer.concat(buffers)
    } else {
      buffer = Buffer.allocUnsafe(st.size)

      while (true) {
        const r = await read(fd, len ? buffer.subarray(len) : buffer)
        len += r
        if (r === 0 || len === buffer.byteLength) break
      }

      if (len !== buffer.byteLength) buffer = buffer.subarray(0, len)
    }

    if (encoding !== 'buffer') buffer = buffer.toString(encoding)
  } catch (e) {
    err = e
  } finally {
    if (fd !== -1) await close(fd)
  }

  return done(err, buffer, cb)
}

function readFileSync(filepath, opts) {
  if (typeof opts === 'string') opts = { encoding: opts }
  else if (!opts) opts = {}

  const { encoding = 'buffer' } = opts

  let fd = -1
  try {
    fd = openSync(filepath, opts.flag || 'r')

    const st = fstatSync(fd)

    let buffer
    let len = 0

    if (st.size === 0) {
      const buffers = []

      while (true) {
        buffer = Buffer.allocUnsafe(8192)
        const r = readSync(fd, buffer)
        len += r
        if (r === 0) break
        buffers.push(buffer.subarray(0, r))
      }

      buffer = Buffer.concat(buffers)
    } else {
      buffer = Buffer.allocUnsafe(st.size)

      while (true) {
        const r = readSync(fd, len ? buffer.subarray(len) : buffer)
        len += r
        if (r === 0 || len === buffer.byteLength) break
      }

      if (len !== buffer.byteLength) buffer = buffer.subarray(0, len)
    }

    if (encoding !== 'buffer') buffer = buffer.toString(encoding)

    return buffer
  } finally {
    if (fd !== -1) closeSync(fd)
  }
}

async function writeFile(filepath, data, opts, cb) {
  if (typeof opts === 'function') {
    cb = opts
    opts = {}
  }

  if (typeof opts === 'string') opts = { encoding: opts }
  else if (!opts) opts = {}

  if (typeof data === 'string') data = Buffer.from(data, opts.encoding)

  let fd = -1
  let len = 0
  let err = null
  try {
    fd = await open(filepath, opts.flag || 'w', opts.mode || 0o666)

    while (true) {
      len += await write(fd, len ? data.subarray(len) : data)
      if (len === data.byteLength) break
    }
  } catch (e) {
    err = e
  } finally {
    if (fd !== -1) await close(fd)
  }

  return done(err, len, cb)
}

function writeFileSync(filepath, data, opts) {
  if (typeof opts === 'string') opts = { encoding: opts }
  else if (!opts) opts = {}

  if (typeof data === 'string') data = Buffer.from(data, opts.encoding)

  let fd = -1
  try {
    fd = openSync(filepath, opts.flag || 'w', opts.mode || 0o666)

    let len = 0

    while (true) {
      len += writeSync(fd, len ? data.subarray(len) : data)
      if (len === data.byteLength) break
    }
  } finally {
    if (fd !== -1) closeSync(fd)
  }
}

function appendFile(filepath, data, opts, cb) {
  if (typeof opts === 'function') {
    cb = opts
    opts = {}
  }

  if (typeof opts === 'string') opts = { encoding: opts }
  else if (!opts) opts = {}

  if (!opts.flag) opts = { ...opts, flag: 'a' }

  return writeFile(filepath, data, opts, cb)
}

function appendFileSync(filepath, data, opts) {
  if (typeof opts === 'string') opts = { encoding: opts }
  else if (!opts) opts = {}

  if (!opts.flag) opts = { ...opts, flag: 'a' }

  return writeFileSync(filepath, data, opts)
}

function watch(filepath, opts, cb) {
  if (typeof opts === 'function') {
    cb = opts
    opts = {}
  }

  if (typeof opts === 'string') opts = { encoding: opts }
  else if (!opts) opts = {}

  filepath = toNamespacedPath(filepath)

  return new Watcher(filepath, opts, cb)
}

class Stats {
  constructor(
    dev,
    mode,
    nlink,
    uid,
    gid,
    rdev,
    blksize,
    ino,
    size,
    blocks,
    atimeMs,
    mtimeMs,
    ctimeMs,
    birthtimeMs
  ) {
    this.dev = dev
    this.mode = mode
    this.nlink = nlink
    this.uid = uid
    this.gid = gid
    this.rdev = rdev
    this.blksize = blksize
    this.ino = ino
    this.size = size
    this.blocks = blocks
    this.atimeMs = atimeMs
    this.mtimeMs = mtimeMs
    this.ctimeMs = ctimeMs
    this.birthtimeMs = birthtimeMs
    this.atime = new Date(atimeMs)
    this.mtime = new Date(mtimeMs)
    this.ctime = new Date(ctimeMs)
    this.birthtime = new Date(birthtimeMs)
  }

  isDirectory() {
    return (this.mode & constants.S_IFMT) === constants.S_IFDIR
  }

  isFile() {
    return (this.mode & constants.S_IFMT) === constants.S_IFREG
  }

  isBlockDevice() {
    return (this.mode & constants.S_IFMT) === constants.S_IFBLK
  }

  isCharacterDevice() {
    return (this.mode & constants.S_IFMT) === constants.S_IFCHR
  }

  isFIFO() {
    return (this.mode & constants.S_IFMT) === constants.S_IFIFO
  }

  isSymbolicLink() {
    return (this.mode & constants.S_IFMT) === constants.S_IFLNK
  }

  isSocket() {
    return (this.mode & constants.S_IFMT) === constants.S_IFSOCK
  }
}

class StatFs {
  constructor(type, bsize, blocks, bfree, bavail, files, ffree) {
    this.type = type
    this.bsize = bsize
    this.blocks = blocks
    this.bfree = bfree
    this.bavail = bavail
    this.files = files
    this.ffree = ffree
  }
}

class Dir {
  constructor(path, handle, opts = {}) {
    const { encoding = 'utf8', bufferSize = 32 } = opts

    this.path = path

    this._encoding = encoding
    this._capacity = bufferSize
    this._buffer = new FIFO()
    this._ended = false
    this._handle = handle
  }

  async read(cb) {
    if (this._buffer.length) return ok(this._buffer.shift(), cb)
    if (this._ended) return ok(null, cb)

    const req = FileRequest.borrow()

    let entries
    let err = null
    try {
      req.retain(binding.readdir(req.handle, this._handle, this._capacity))

      await req

      entries = binding.requestResultDirents(req.handle)
    } catch (e) {
      err = new FileError(e.message, {
        operation: 'readdir',
        code: e.code,
        path: this.path
      })
    } finally {
      req.return()
    }

    if (err) return fail(err, cb)

    if (entries.length === 0) {
      this._ended = true

      return ok(null, cb)
    }

    for (const entry of entries) {
      let name = Buffer.from(entry.name)

      if (this._encoding !== 'buffer') name = name.toString(this._encoding)

      this._buffer.push(new Dirent(this.path, name, entry.type))
    }

    return ok(this._buffer.shift(), cb)
  }

  readSync() {
    if (this._buffer.length) return this._buffer.shift()
    if (this._ended) return null

    const req = FileRequest.borrow()

    let entries
    try {
      req.retain(binding.readdirSync(req.handle, this._handle, this._capacity))

      entries = binding.requestResultDirents(req.handle)
    } catch (e) {
      throw new FileError(e.message, {
        operation: 'readdir',
        code: e.code,
        path: this.path
      })
    } finally {
      req.return()
    }

    if (entries.length === 0) {
      this._ended = true

      return null
    }

    for (const entry of entries) {
      let name = Buffer.from(entry.name)

      if (this._encoding !== 'buffer') name = name.toString(this._encoding)

      this._buffer.push(new Dirent(this.path, name, entry.type))
    }

    return this._buffer.shift()
  }

  async close(cb) {
    const req = FileRequest.borrow()

    let err = null
    try {
      binding.closedir(req.handle, this._handle)

      await req
    } catch (e) {
      err = new FileError(e.message, {
        operation: 'closedir',
        code: e.code,
        path: this.path
      })
    } finally {
      req.return()
    }

    this._handle = null

    return done(err, cb)
  }

  closeSync() {
    const req = FileRequest.borrow()

    try {
      binding.closedirSync(req.handle, this._handle)
    } catch (e) {
      throw new FileError(e.message, {
        operation: 'closedir',
        code: e.code,
        path: this.path
      })
    } finally {
      req.return()
    }

    this._handle = null
  }

  [Symbol.dispose]() {
    this.closeSync()
  }

  async [Symbol.asyncDispose]() {
    await this.close()
  }

  *[Symbol.iterator]() {
    while (true) {
      const entry = this.readSync()
      if (entry === null) break
      yield entry
    }

    this.closeSync()
  }

  async *[Symbol.asyncIterator]() {
    while (true) {
      const entry = await this.read()
      if (entry === null) break
      yield entry
    }

    await this.close()
  }
}

class Dirent {
  constructor(parentPath, name, type) {
    this.parentPath = parentPath
    this.name = name
    this.type = type
  }

  isFile() {
    return this.type === constants.UV_DIRENT_FILE
  }

  isDirectory() {
    return this.type === constants.UV_DIRENT_DIR
  }

  isSymbolicLink() {
    return this.type === constants.UV_DIRENT_LINK
  }

  isFIFO() {
    return this.type === constants.UV_DIRENT_FIFO
  }

  isSocket() {
    return this.type === constants.UV_DIRENT_SOCKET
  }

  isCharacterDevice() {
    return this.type === constants.UV_DIRENT_CHAR
  }

  isBlockDevice() {
    return this.type === constants.UV_DIRENT_BLOCK
  }
}

class FileReadStream extends Readable {
  constructor(path, opts = {}) {
    const { eagerOpen = true } = opts

    super({ eagerOpen, ...opts })

    this.path = path
    this.fd = typeof opts.fd === 'number' ? opts.fd : -1
    this.flags = opts.flags || 'r'
    this.mode = opts.mode || 0o666

    this._offset = opts.start || 0
    this._missing = 0

    if (opts.length) {
      this._missing = opts.length
    } else if (typeof opts.end === 'number') {
      this._missing = opts.end - this._offset + 1
    } else {
      this._missing = -1
    }
  }

  async _open(cb) {
    let err

    if (this.fd === -1) {
      err = null
      try {
        this.fd = await open(this.path, this.flags, this.mode)
      } catch (e) {
        err = e
      }

      if (err) return cb(err)
    }

    let st
    err = null
    try {
      st = await fstat(this.fd)
    } catch (e) {
      err = e
    }

    if (err) return cb(err)

    if (this._missing === -1) this._missing = st.size

    if (st.size < this._offset) {
      this._offset = st.size
      this._missing = 0
    } else if (st.size < this._offset + this._missing) {
      this._missing = st.size - this._offset
    }

    cb(null)
  }

  async _read(size) {
    if (this._missing <= 0) return this.push(null)

    const data = Buffer.allocUnsafe(Math.min(this._missing, size))

    let len
    let err = null
    try {
      len = await read(this.fd, data, 0, data.byteLength, this._offset)
    } catch (e) {
      err = e
    }

    if (err) return this.destroy(err)

    if (len === 0) return this.push(null)

    if (this._missing < len) len = this._missing

    this._missing -= len
    this._offset += len

    this.push(data.subarray(0, len))
  }

  async _destroy(err, cb) {
    if (this.fd === -1) return cb(err)

    try {
      await close(this.fd)
    } catch (e) {
      err = err || e
    }

    cb(err)
  }
}

class FileWriteStream extends Writable {
  constructor(path, opts = {}) {
    const { eagerOpen = true } = opts

    super({ eagerOpen, ...opts })

    this.path = path
    this.fd = typeof opts.fd === 'number' ? opts.fd : -1
    this.flags = opts.flags || 'w'
    this.mode = opts.mode || 0o666
  }

  async _open(cb) {
    if (this.fd !== -1) return cb(null)

    let err = null
    try {
      this.fd = await open(this.path, this.flags, this.mode)
    } catch (e) {
      err = e
    }

    cb(err)
  }

  async _writev(batch, cb) {
    let err = null
    try {
      await writev(
        this.fd,
        batch.map(({ chunk }) => chunk)
      )
    } catch (e) {
      err = e
    }

    cb(err)
  }

  async _destroy(err, cb) {
    if (this.fd === -1) return cb(err)

    try {
      await close(this.fd)
    } catch (e) {
      err = err || e
    }

    cb(err)
  }
}

class Watcher extends EventEmitter {
  constructor(path, opts, onchange) {
    if (typeof opts === 'function') {
      onchange = opts
      opts = {}
    }

    if (!opts) opts = {}

    const { persistent = true, recursive = false, encoding = 'utf8' } = opts

    super()

    this._closed = false
    this._encoding = encoding
    this._handle = binding.watcherInit(path, recursive, this, this._onevent, this._onclose)

    if (!persistent) this.unref()

    if (onchange) this.on('change', onchange)
  }

  close() {
    if (this._closed) return
    this._closed = true

    binding.watcherClose(this._handle)
  }

  ref() {
    if (this._handle) binding.watcherRef(this._handle)
    return this
  }

  unref() {
    if (this._handle) binding.watcherUnref(this._handle)
    return this
  }

  [Symbol.asyncIterator]() {
    const buffer = []
    let done = false
    let error = null
    let next = null

    this.on('change', (eventType, filename) => {
      if (next) {
        next.resolve({ done: false, value: { eventType, filename } })
        next = null
      } else {
        buffer.push({ eventType, filename })
      }
    })
      .on('error', (err) => {
        done = true
        error = err

        if (next) {
          next.reject(error)
          next = null
        }
      })
      .on('close', () => {
        done = true

        if (next) {
          next.resolve({ done })
          next = null
        }
      })

    return {
      next: () =>
        new Promise((resolve, reject) => {
          if (error) return reject(error)

          if (buffer.length) return resolve({ done: false, value: buffer.shift() })

          if (done) return resolve({ done })

          next = { resolve, reject }
        })
    }
  }

  _onevent(err, events, filename) {
    if (err) {
      this.close()
      this.emit('error', err)
    } else {
      const path =
        this._encoding === 'buffer'
          ? Buffer.from(filename)
          : Buffer.from(filename).toString(this._encoding)

      if (events & binding.UV_RENAME) {
        this.emit('change', 'rename', path)
      }

      if (events & binding.UV_CHANGE) {
        this.emit('change', 'change', path)
      }
    }
  }

  _onclose() {
    this._handle = null

    this.emit('close')
  }
}

exports.access = access
exports.appendFile = appendFile
exports.chmod = chmod
exports.chown = chown
exports.close = close
exports.copyFile = copyFile
exports.cp = cp
exports.exists = exists
exports.fchmod = fchmod
exports.fchown = fchown
exports.fdatasync = fdatasync
exports.fstat = fstat
exports.fsync = fsync
exports.ftruncate = ftruncate
exports.futimes = futimes
exports.lchown = lchown
exports.lutimes = lutimes
exports.link = link
exports.lstat = lstat
exports.mkdir = mkdir
exports.mkdtemp = mkdtemp
exports.open = open
exports.opendir = opendir
exports.read = read
exports.readFile = readFile
exports.readdir = readdir
exports.readlink = readlink
exports.readv = readv
exports.realpath = realpath
exports.rename = rename
exports.rm = rm
exports.rmdir = rmdir
exports.stat = stat
exports.statfs = statfs
exports.symlink = symlink
exports.truncate = truncate
exports.unlink = unlink
exports.utimes = utimes
exports.watch = watch
exports.write = write
exports.writeFile = writeFile
exports.writev = writev

exports.accessSync = accessSync
exports.appendFileSync = appendFileSync
exports.chmodSync = chmodSync
exports.chownSync = chownSync
exports.closeSync = closeSync
exports.copyFileSync = copyFileSync
exports.cpSync = cpSync
exports.existsSync = existsSync
exports.fchmodSync = fchmodSync
exports.fchownSync = fchownSync
exports.fdatasyncSync = fdatasyncSync
exports.fstatSync = fstatSync
exports.fsyncSync = fsyncSync
exports.ftruncateSync = ftruncateSync
exports.futimesSync = futimesSync
exports.lchownSync = lchownSync
exports.lutimesSync = lutimesSync
exports.linkSync = linkSync
exports.lstatSync = lstatSync
exports.mkdirSync = mkdirSync
exports.mkdtempSync = mkdtempSync
exports.openSync = openSync
exports.opendirSync = opendirSync
exports.readFileSync = readFileSync
exports.readSync = readSync
exports.readdirSync = readdirSync
exports.readlinkSync = readlinkSync
exports.readvSync = readvSync
exports.realpathSync = realpathSync
exports.renameSync = renameSync
exports.rmSync = rmSync
exports.rmdirSync = rmdirSync
exports.statSync = statSync
exports.statfsSync = statfsSync
exports.symlinkSync = symlinkSync
exports.truncateSync = truncateSync
exports.unlinkSync = unlinkSync
exports.utimesSync = utimesSync
exports.writeFileSync = writeFileSync
exports.writeSync = writeSync
exports.writevSync = writevSync

exports.promises = require('./promises')

exports.Stats = Stats
exports.StatFs = StatFs
exports.Dir = Dir
exports.Dirent = Dirent
exports.Watcher = Watcher

exports.ReadStream = FileReadStream

exports.createReadStream = function createReadStream(path, opts) {
  return new FileReadStream(path, opts)
}

exports.WriteStream = FileWriteStream

exports.createWriteStream = function createWriteStream(path, opts) {
  return new FileWriteStream(path, opts)
}

function toNamespacedPath(filepath) {
  if (typeof filepath !== 'string') {
    if (isURL(filepath)) filepath = fileURLToPath(filepath)
    else filepath = filepath.toString()
  }

  return path.toNamespacedPath(filepath)
}

function toFlags(flags) {
  switch (flags) {
    case 'r':
      return constants.O_RDONLY
    case 'rs': // Fall through.
    case 'sr':
      return constants.O_RDONLY | constants.O_SYNC
    case 'r+':
      return constants.O_RDWR
    case 'rs+': // Fall through.
    case 'sr+':
      return constants.O_RDWR | constants.O_SYNC

    case 'w':
      return constants.O_TRUNC | constants.O_CREAT | constants.O_WRONLY
    case 'wx': // Fall through.
    case 'xw':
      return constants.O_TRUNC | constants.O_CREAT | constants.O_WRONLY | constants.O_EXCL

    case 'w+':
      return constants.O_TRUNC | constants.O_CREAT | constants.O_RDWR
    case 'wx+': // Fall through.
    case 'xw+':
      return constants.O_TRUNC | constants.O_CREAT | constants.O_RDWR | constants.O_EXCL

    case 'a':
      return constants.O_APPEND | constants.O_CREAT | constants.O_WRONLY
    case 'ax': // Fall through.
    case 'xa':
      return constants.O_APPEND | constants.O_CREAT | constants.O_WRONLY | constants.O_EXCL
    case 'as': // Fall through.
    case 'sa':
      return constants.O_APPEND | constants.O_CREAT | constants.O_WRONLY | constants.O_SYNC

    case 'a+':
      return constants.O_APPEND | constants.O_CREAT | constants.O_RDWR
    case 'ax+': // Fall through.
    case 'xa+':
      return constants.O_APPEND | constants.O_CREAT | constants.O_RDWR | constants.O_EXCL
    case 'as+': // Fall through.
    case 'sa+':
      return constants.O_APPEND | constants.O_CREAT | constants.O_RDWR | constants.O_SYNC
    default:
      return 0
  }
}

function toMode(mode) {
  return parseInt(mode, 8)
}
const binding = require('../binding')

module.exports = {
  O_RDWR: binding.O_RDWR,
  O_RDONLY: binding.O_RDONLY,
  O_WRONLY: binding.O_WRONLY,
  O_CREAT: binding.O_CREAT,
  O_TRUNC: binding.O_TRUNC,
  O_APPEND: binding.O_APPEND,

  F_OK: binding.F_OK || 0,
  R_OK: binding.R_OK || 0,
  W_OK: binding.W_OK || 0,
  X_OK: binding.X_OK || 0,

  S_IFMT: binding.S_IFMT,
  S_IFREG: binding.S_IFREG,
  S_IFDIR: binding.S_IFDIR,
  S_IFCHR: binding.S_IFCHR,
  S_IFLNK: binding.S_IFLNK,
  S_IFBLK: binding.S_IFBLK || 0,
  S_IFIFO: binding.S_IFIFO || 0,
  S_IFSOCK: binding.S_IFSOCK || 0,

  S_IRUSR: binding.S_IRUSR || 0,
  S_IWUSR: binding.S_IWUSR || 0,
  S_IXUSR: binding.S_IXUSR || 0,
  S_IRGRP: binding.S_IRGRP || 0,
  S_IWGRP: binding.S_IWGRP || 0,
  S_IXGRP: binding.S_IXGRP || 0,
  S_IROTH: binding.S_IROTH || 0,
  S_IWOTH: binding.S_IWOTH || 0,
  S_IXOTH: binding.S_IXOTH || 0,

  UV_DIRENT_UNKNOWN: binding.UV_DIRENT_UNKNOWN,
  UV_DIRENT_FILE: binding.UV_DIRENT_FILE,
  UV_DIRENT_DIR: binding.UV_DIRENT_DIR,
  UV_DIRENT_LINK: binding.UV_DIRENT_LINK,
  UV_DIRENT_FIFO: binding.UV_DIRENT_FIFO,
  UV_DIRENT_SOCKET: binding.UV_DIRENT_SOCKET,
  UV_DIRENT_CHAR: binding.UV_DIRENT_CHAR,
  UV_DIRENT_BLOCK: binding.UV_DIRENT_BLOCK,

  COPYFILE_EXCL: binding.UV_FS_COPYFILE_EXCL,
  COPYFILE_FICLONE: binding.UV_FS_COPYFILE_FICLONE,
  COPYFILE_FICLONE_FORCE: binding.UV_FS_COPYFILE_FICLONE_FORCE,
  UV_FS_SYMLINK_DIR: binding.UV_FS_SYMLINK_DIR,
  UV_FS_SYMLINK_JUNCTION: binding.UV_FS_SYMLINK_JUNCTION
}
const os = require('bare-os')

module.exports = class FileError extends Error {
  constructor(msg, opts = {}) {
    const { code, operation = null, path = null, destination = null, fd = -1 } = opts

    if (operation !== null) msg += describe(operation, opts)

    super(`${code}: ${msg}`)

    this.code = code

    if (operation !== null) this.operation = operation
    if (path !== null) this.path = path
    if (destination !== null) this.destination = destination
    if (fd !== -1) this.fd = fd
  }

  get name() {
    return 'FileError'
  }

  // For Node.js compatibility
  get errno() {
    return os.constants.errnos[this.code]
  }

  // For Node.js compatibility
  get syscall() {
    return this.operation
  }

  // For Node.js compatibility
  get dest() {
    return this.destination
  }
}

function describe(operation, opts) {
  const { path = null, destination = null, fd = -1 } = opts

  let result = `, ${operation}`

  if (path !== null) {
    result += ` ${JSON.stringify(path)}`

    if (destination !== null) {
      result += ` -> ${JSON.stringify(destination)}`
    }
  } else if (fd !== -1) {
    result += ` ${fd}`
  }

  return result
}
{
  "name": "bare-fs",
  "version": "4.7.2",
  "description": "Native file system operations for Bare",
  "exports": {
    "./package": "./package.json",
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    },
    "./promises": {
      "types": "./promises.d.ts",
      "default": "./promises.js"
    },
    "./constants": {
      "types": "./lib/constants.d.ts",
      "default": "./lib/constants.js"
    }
  },
  "files": [
    "index.js",
    "index.d.ts",
    "promises.js",
    "promises.d.ts",
    "binding.c",
    "binding.js",
    "CMakeLists.txt",
    "lib",
    "prebuilds"
  ],
  "addon": true,
  "scripts": {
    "format": "prettier --write . && lunte --fix",
    "lint": "prettier --check . && lunte",
    "test": "brittle-bare --coverage test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-fs.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-fs/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-fs#readme",
  "engines": {
    "bare": ">=1.16.0"
  },
  "dependencies": {
    "bare-events": "^2.5.4",
    "bare-path": "^3.0.0",
    "bare-stream": "^2.6.4",
    "bare-url": "^2.2.2",
    "fast-fifo": "^1.3.2"
  },
  "devDependencies": {
    "bare-buffer": "^3.0.2",
    "bare-crypto": "^1.11.2",
    "brittle": "^3.1.1",
    "cmake-bare": "^1.1.7",
    "lunte": "^1.6.0",
    "prettier": "^3.4.1",
    "prettier-config-holepunch": "^2.0.0"
  },
  "peerDependencies": {
    "bare-buffer": "*"
  },
  "peerDependenciesMeta": {
    "bare-buffer": {
      "optional": true
    }
  }
}
const EventEmitter = require('bare-events')
const fs = require('.')

class FileHandle extends EventEmitter {
  constructor(fd) {
    super()

    this.fd = fd
  }

  async close() {
    await fs.close(this.fd)

    this.fd = -1
    this.emit('close')
  }

  async read(buffer, ...args) {
    return {
      bytesRead: await fs.read(this.fd, buffer, ...args),
      buffer
    }
  }

  async readv(buffers, ...args) {
    return {
      bytesRead: await fs.readv(this.fd, buffers, ...args),
      buffers
    }
  }

  async write(buffer, ...args) {
    return {
      bytesWritten: await fs.write(this.fd, buffer, ...args),
      buffer
    }
  }

  async writev(buffers, ...args) {
    return {
      bytesWritten: await fs.writev(this.fd, buffers, ...args),
      buffers
    }
  }

  async stat() {
    return fs.fstat(this.fd)
  }

  async chmod(mode) {
    await fs.fchmod(this.fd, mode)
  }

  async chown(uid, gid) {
    await fs.fchown(this.fd, uid, gid)
  }

  async datasync() {
    return fs.fdatasync(this.fd)
  }

  async sync() {
    return fs.fsync(this.fd)
  }

  async truncate(len) {
    await fs.ftruncate(this.fd, len)
  }

  async utimes(atime, mtime) {
    await fs.futimes(this.fd, atime, mtime)
  }

  createReadStream(opts) {
    return fs.createReadStream(null, { ...opts, fd: this.fd })
  }

  createWriteStream(opts) {
    return fs.createWriteStream(null, { ...opts, fd: this.fd })
  }

  async [Symbol.asyncDispose]() {
    await this.close()
  }
}

exports.open = async function open(filepath, flags, mode) {
  return new FileHandle(await fs.open(filepath, flags, mode))
}

exports.access = fs.access
exports.appendFile = fs.appendFile
exports.chmod = fs.chmod
exports.chown = fs.chown
exports.constants = fs.constants
exports.copyFile = fs.copyFile
exports.cp = fs.cp
exports.lchown = fs.lchown
exports.lutimes = fs.lutimes
exports.link = fs.link
exports.lstat = fs.lstat
exports.mkdir = fs.mkdir
exports.mkdtemp = fs.mkdtemp
exports.opendir = fs.opendir
exports.readFile = fs.readFile
exports.readdir = fs.readdir
exports.readlink = fs.readlink
exports.realpath = fs.realpath
exports.rename = fs.rename
exports.rm = fs.rm
exports.rmdir = fs.rmdir
exports.stat = fs.stat
exports.statfs = fs.statfs
exports.truncate = fs.truncate
exports.symlink = fs.symlink
exports.unlink = fs.unlink
exports.utimes = fs.utimes
exports.watch = fs.watch
exports.writeFile = fs.writeFile
module.exports = require.addon()
const ansiEscapes = require('bare-ansi-escapes')
const getType = require('bare-type')
const binding = require('./binding')

const PLAIN_KEY = /^[a-zA-Z_][a-zA-Z_0-9]*$/

const defaultDepth = 2
const defaultBreakLength = 80
const defaultMaxArrayLength = 40

module.exports = exports = function inspect(value, opts = {}) {
  const {
    colors = false,
    depth = defaultDepth,
    breakLength = defaultBreakLength,
    stylize = defaultStylize(colors)
  } = opts

  const references = new InspectRefMap()

  const tree = inspectValue(value, 0, {
    colors,
    depth,
    breakLength,
    stylize,
    references
  })

  return tree.toString()
}

exports.styles = {
  bigint: ansiEscapes.colorYellow,
  boolean: ansiEscapes.colorYellow,
  date: ansiEscapes.colorMagenta,
  module: ansiEscapes.modifierUnderline,
  name: ansiEscapes.modifierReset,
  null: ansiEscapes.modifierBold,
  number: ansiEscapes.colorYellow,
  regexp: ansiEscapes.colorRed,
  special: ansiEscapes.colorCyan,
  string: ansiEscapes.colorGreen,
  symbol: ansiEscapes.colorGreen,
  undefined: ansiEscapes.colorBrightBlack
}

const styles = exports.styles

function defaultStylize(colors) {
  return function stylize(value, style) {
    const color = colors && styles[style]

    if (color) return color + value + ansiEscapes.modifierReset

    return value
  }
}

class InspectRefMap {
  constructor() {
    this.refs = new WeakMap()
    this.ids = new WeakMap()
    this.nextId = 1
  }

  has(object) {
    return this.refs.has(object)
  }

  get(object) {
    return this.refs.get(object) || null
  }

  set(object, ref) {
    this.refs.set(object, ref)
  }

  id(object) {
    let id = this.ids.get(object)
    if (id) return id

    id = this.nextId++
    this.ids.set(object, id)

    return id
  }
}

class InspectNode {
  constructor(depth, length, opts) {
    const { breakLength = defaultBreakLength, breakAlways = false } = opts

    this.depth = depth
    this.length = length
    this.breakLength = breakLength
    this.breakAlways = breakAlways
  }

  pad(n, string) {
    return string.padStart(n, ' ')
  }

  indent(n, string) {
    return '  '.repeat(n) + string
  }
}

class InspectRef extends InspectNode {
  constructor(depth, opts) {
    super(depth, '[circular *]'.length, opts)

    this.refs = opts.references
    this.count = 0
    this.circular = false
    this.color = opts.colors && styles.special
  }

  get id() {
    return this.refs.id(this)
  }

  increment() {
    return ++this.count
  }

  decrement() {
    return --this.count
  }

  toString(opts = {}) {
    const { offset = 0, pad = 0, indent = 0 } = opts

    let value = this.pad(pad, '[circular *' + this.id + ']')

    if (this.color) value = this.color + value + ansiEscapes.modifierReset

    return offset ? value : this.indent(indent, value)
  }
}

class InspectLeaf extends InspectNode {
  constructor(value, color, depth, opts) {
    const length = value.length

    if (value.includes('\n')) {
      value = value.replaceAll('\n', '\n' + '  '.repeat(depth))

      opts = { ...opts, breakAlways: true }
    }

    super(depth, length, opts)

    this.value = value
    this.color = opts.colors && color
  }

  toString(opts = {}) {
    const { offset = 0, pad = 0, indent = 0 } = opts

    let value = this.pad(pad, this.value)

    if (this.color) value = this.color + value + ansiEscapes.modifierReset

    return offset ? value : this.indent(indent, value)
  }
}

class InspectPair extends InspectNode {
  constructor(delim, left, right, depth, opts) {
    const length = left.length + delim.length + right.length

    if (left.breakAlways || right.breakAlways) {
      opts = { ...opts, breakAlways: true }
    }

    super(depth, length, opts)

    this.delim = delim
    this.left = left
    this.right = right
  }

  toString(opts = {}) {
    const { indent = 0 } = opts

    return this.indent(
      indent,
      this.left +
        this.delim +
        this.right.toString({
          indent,
          offset: this.left.length + this.delim.length
        })
    )
  }
}

class InspectSuspension extends InspectNode {
  constructor(overflow, depth, opts) {
    const label = `... ${overflow} more`

    super(depth, label.length, opts)

    this.label = label
  }

  toString(opts = {}) {
    const { indent = 0 } = opts

    return this.indent(indent, this.label)
  }
}

class InspectSequence extends InspectNode {
  constructor(header, footer, delim, values, ref, depth, opts) {
    const { tabulate = false } = opts

    const length =
      (ref.circular ? '<ref *>'.length + 1 : 0) +
      header.length +
      values.reduce(
        (length, value, i) =>
          length + value.length + (i === 0 ? 0 : delim.length),
        0
      ) +
      footer.length

    if (values.some((value) => value.breakAlways)) {
      opts = { ...opts, breakAlways: true }
    }

    super(depth, length, opts)

    this.header = header
    this.footer = footer
    this.delim = delim
    this.values = values
    this.ref = ref
    this.tabulate = tabulate
  }

  toString(opts = {}) {
    const { offset = 0, indent = 0 } = opts

    const split =
      this.breakAlways ||
      (this.values.length &&
        (offset + this.length > this.breakLength ||
          indent * 2 + this.length > this.breakLength))

    let header = this.header

    if (this.ref.circular) {
      header = '<ref *' + this.ref.id + '> ' + header
    }

    if (this.values.length === 0) {
      header = header.trimEnd()
    }

    if (offset === 0) {
      header = this.indent(indent, header)
    }

    if (split) {
      header = header.trimEnd() + '\n'
    }

    let string = header

    let columns = 1
    let pad = 0

    if (this.tabulate) {
      const widest = this.values.reduce(
        (length, value) =>
          value.breakAlways ? length : Math.max(length, value.length),
        0
      )

      if (widest) {
        columns = Math.max(
          columns,
          Math.floor(
            (this.breakLength - indent * 2) / (widest + this.delim.length)
          )
        )

        if (columns > 1) pad = widest
      }
    }

    for (let i = 0, n = this.values.length, offset = 0; i < n; i++) {
      const value = this.values[i]

      if (split) {
        let part

        if (i % columns === 0 || value.breakAlways) {
          part = value.toString({ indent: indent + 1, pad })
        } else {
          part = value.toString({ pad })
        }

        string += part

        if (i < n - 1) {
          if (i % columns === columns - 1 || this.values[i + 1].breakAlways) {
            string += this.delim.trimEnd() + '\n'
          } else {
            string += this.delim
          }
        }
      } else {
        if (i > 0) string += this.delim

        string += value.toString({ offset })

        offset += value.length
      }
    }

    let footer = this.footer

    if (this.values.length === 0) {
      footer = footer.trimStart()
    }

    if (split) {
      string += '\n' + this.indent(indent, footer.trimStart())
    } else {
      string += footer
    }

    return string
  }
}

function inspectValue(value, depth, opts) {
  const type = getType(value)

  if (type.isUndefined()) return inspectUndefined(depth, opts)
  if (type.isNull()) return inspectNull(depth, opts)
  if (type.isBoolean()) return inspectBoolean(value, depth, opts)
  if (type.isNumber()) return inspectNumber(value, depth, opts)
  if (type.isBigInt()) return inspectBigInt(value, depth, opts)
  if (type.isString()) return inspectString(value, depth, opts)
  if (type.isSymbol()) return inspectSymbol(value, depth, opts)
  if (type.isObject()) return inspectObject(type, value, depth, opts)
  if (type.isFunction()) return inspectFunction(type, value, depth, opts)
  if (type.isExternal()) return inspectExternal(value, opts, opts)
}

function inspectUndefined(depth, opts) {
  return new InspectLeaf('undefined', styles.undefined, depth, opts)
}

function inspectNull(depth, opts) {
  return new InspectLeaf('null', styles.null, depth, opts)
}

function inspectBoolean(value, depth, opts) {
  return new InspectLeaf(value.toString(), styles.boolean, depth, opts)
}

function inspectNumber(value, depth, opts) {
  let string

  if (Object.is(value, -0)) {
    string = '-0'
  } else {
    string = value.toString(10)
  }

  return new InspectLeaf(string, styles.number, depth, opts)
}

function inspectBigInt(value, depth, opts) {
  return new InspectLeaf(value.toString(10) + 'n', styles.bigint, depth, opts)
}

const STRING_ESCAPES =
  /[\ud800-\udbff][\udc00-\udfff]|[\u0000-\u001f'\\\ud800-\udfff]/g

function inspectString(value, depth, opts) {
  // https://tc39.es/ecma262/multipage/structured-data.html#sec-quotejsonstring
  const string = value.replace(STRING_ESCAPES, (match) => {
    if (match.length === 2) return match

    switch (match) {
      case "'":
        return "\\'"
      case '\\':
        return '\\\\'
      case '\b':
        return '\\b'
      case '\t':
        return '\\t'
      case '\n':
        return '\\n'
      case '\f':
        return '\\f'
      case '\r':
        return '\\r'
      default:
        return '\\u' + match.charCodeAt(0).toString(16).padStart(4, '0')
    }
  })

  return new InspectLeaf("'" + string + "'", styles.string, depth, opts)
}

function inspectSymbol(value, depth, opts) {
  return new InspectLeaf(value.toString(), styles.symbol, depth, opts)
}

function inspectKey(value, depth, opts) {
  if (PLAIN_KEY.test(value)) {
    return new InspectLeaf(value, null, depth, opts)
  } else {
    return inspectValue(value, depth, opts)
  }
}

function inspectObject(type, object, depth, opts) {
  const refs = opts.references

  let ref = refs.get(object)
  if (ref === null) {
    ref = new InspectRef(depth, opts)
    refs.set(object, ref)
  } else if (ref.count) {
    ref.circular = true
    return ref
  }

  const maxDepth = typeof opts.depth === 'number' ? opts.depth : Infinity

  if (maxDepth < depth) {
    const constructor = object.constructor

    return new InspectLeaf(
      '[' +
        (constructor && constructor.name ? constructor.name : 'Object') +
        ']',
      styles.special,
      depth,
      opts
    )
  }

  const inspect =
    object[Symbol.for('bare.inspect')] ||
    object[Symbol.for('nodejs.util.inspect.custom')]

  if (typeof inspect === 'function') {
    const value = inspect.call(
      object,
      typeof opts.depth === 'number' ? opts.depth - depth : null,
      {
        colors: opts.colors,
        breakLength: opts.breakLength,
        stylize: opts.stylize
      },
      exports
    )

    if (typeof value === 'object' && value !== null) {
      refs.set(value, ref)
    }

    if (typeof value !== 'string') {
      return inspectValue(value, depth, opts)
    }

    return new InspectLeaf(value, null, depth, opts)
  }

  if (type.isArray()) return inspectArray(object, ref, depth, opts)
  if (type.isDate()) return inspectDate(object, ref, depth, opts)
  if (type.isRegExp()) return inspectRegExp(object, ref, depth, opts)
  if (type.isError()) return inspectError(object, ref, depth, opts)
  if (type.isPromise()) return inspectPromise(object, ref, depth, opts)
  if (type.isMap()) return inspectMap(object, ref, depth, opts)
  if (type.isSet()) return inspectSet(object, ref, depth, opts)
  if (type.isWeakMap()) return inspectWeakMap(object, ref, depth, opts)
  if (type.isWeakSet()) return inspectWeakSet(object, ref, depth, opts)
  if (type.isWeakRef()) return inspectWeakRef(object, ref, depth, opts)
  if (type.isArrayBuffer()) return inspectArrayBuffer(object, ref, depth, opts)
  if (type.isSharedArrayBuffer())
    return inspectSharedArrayBuffer(object, ref, depth, opts)
  if (type.isTypedArray()) return inspectTypedArray(object, ref, depth, opts)
  if (type.isDataView()) return inspectDataView(object, ref, depth, opts)

  ref.increment()

  const values = []

  for (const key in object) {
    if (key === 'constructor') continue

    values.push(
      new InspectPair(
        ': ',
        inspectKey(key, depth + 1, opts),
        inspectValue(object[key], depth + 1, opts),
        depth + 1,
        opts
      )
    )
  }

  ref.decrement()

  let header = '{ '

  const tag = object[Symbol.toStringTag]

  if (tag) header = '[' + tag + '] ' + header

  if (object.constructor) {
    const name = object.constructor.name

    if (name && name !== 'Object') {
      header = object.constructor.name + ' ' + header
    }
  }

  return new InspectSequence(header, ' }', ', ', values, ref, depth, opts)
}

function inspectArray(array, ref, depth, opts) {
  const { maxArrayLength = defaultMaxArrayLength } = opts

  ref.increment()

  const values = []

  let remaining = Math.max(maxArrayLength, 0)

  for (let i = 0, n = array.length; i < n; i++) {
    if (remaining-- === 0) {
      values.push(
        new InspectSuspension(array.length - values.length, depth + 1, {
          ...opts,
          breakAlways: true
        })
      )

      break
    }

    values.push(inspectValue(array[i], depth + 1, opts))
  }

  for (const key of binding.getOwnNonIndexPropertyNames(array)) {
    values.push(
      new InspectPair(
        ': ',
        inspectKey(key, depth + 1, opts),
        inspectValue(array[key], depth + 1, opts),
        depth + 1,
        { ...opts, breakAlways: remaining < 0 }
      )
    )
  }

  ref.decrement()

  let header = '[ '

  if (array.constructor.name !== 'Array') {
    header = array.constructor.name + '(' + array.length + ') ' + header
  }

  return new InspectSequence(header, ' ]', ', ', values, ref, depth, {
    ...opts,
    tabulate: true
  })
}

function inspectDate(date, ref, depth, opts) {
  return new InspectLeaf(date.toISOString(), styles.date, depth, opts)
}

function inspectRegExp(regExp, ref, depth, opts) {
  return new InspectLeaf(regExp.toString(), styles.regexp, depth, opts)
}

function inspectError(error, ref, depth, opts) {
  let header

  if ('stack' in error) {
    header = error.stack

    if (depth > 0) {
      header = header.replaceAll('\n', '\n' + '  '.repeat(depth))
    }
  } else {
    header = error.toString()
  }

  const builtins = ['cause']

  if (error.name === 'AggregateError') {
    builtins.push('errors')
  } else if (error.name === 'SuppressedError') {
    builtins.push('error', 'suppressed')
  }

  const values = []

  for (const key of builtins) {
    if (key in error === false) continue

    values.push(
      new InspectPair(
        ': ',
        new InspectLeaf('[' + key + ']', null, depth + 1, opts),
        inspectValue(error[key], depth + 1, opts),
        depth + 1,
        opts
      )
    )
  }

  for (const key in error) {
    if (key === 'constructor' || builtins.includes(key)) continue

    values.push(
      new InspectPair(
        ': ',
        inspectKey(key, depth + 1, opts),
        inspectValue(error[key], depth + 1, opts),
        depth + 1,
        opts
      )
    )
  }

  if (values.length === 0) return new InspectLeaf(header, null, depth, opts)

  return new InspectSequence(
    header + ' {',
    ' }',
    ', ',
    values,
    ref,
    depth,
    opts
  )
}

function inspectPromise(promise, ref, depth, opts) {
  ref.increment()

  const state = binding.getPromiseState(promise)

  const values = []

  switch (state) {
    case 0: // Pending
      values.push(new InspectLeaf('<pending>', styles.special, depth, opts))
      break

    case 1: // Fulfilled
      values.push(inspectValue(binding.getPromiseResult(promise), depth, opts))
      break

    case 2: // Rejected
      values.push(
        new InspectLeaf('<rejected>', styles.special, depth, opts),
        inspectValue(binding.getPromiseResult(promise), depth, opts)
      )
  }

  ref.decrement()

  const header = promise.constructor.name + ' { '

  return new InspectSequence(header, ' }', ' ', values, ref, depth, opts)
}

function inspectMap(map, ref, depth, opts) {
  const {
    maxArrayLength = defaultMaxArrayLength,
    maxMapLength = maxArrayLength
  } = opts

  ref.increment()

  const values = []

  let remaining = maxMapLength

  for (const entry of map) {
    if (remaining-- === 0) {
      values.push(
        new InspectSuspension(map.size - values.length, depth + 1, {
          ...opts,
          breakAlways: true
        })
      )
      break
    }

    values.push(
      new InspectPair(
        ' => ',
        inspectValue(entry[0], depth + 1, opts),
        inspectValue(entry[1], depth + 1, opts),
        depth + 1,
        opts
      )
    )
  }

  for (const key in map) {
    if (key === 'constructor') continue

    const value = inspectValue(map[key], depth + 1, opts)

    values.push(
      new InspectPair(
        ': ',
        inspectKey(key, depth + 1, opts),
        value,
        depth + 1,
        { ...opts, breakAlways: remaining < 0 }
      )
    )
  }

  ref.decrement()

  const header = map.constructor.name + '(' + map.size + ') { '

  return new InspectSequence(header, ' }', ', ', values, ref, depth, {
    ...opts,
    tabulate: true
  })
}

function inspectSet(set, ref, depth, opts) {
  const {
    maxArrayLength = defaultMaxArrayLength,
    maxSetLength = maxArrayLength
  } = opts

  ref.increment()

  const values = []

  let remaining = maxSetLength

  for (const entry of set) {
    if (remaining-- === 0) {
      values.push(
        new InspectSuspension(set.size - values.length, depth + 1, {
          ...opts,
          breakAlways: true
        })
      )
      break
    }

    values.push(inspectValue(entry, depth + 1, opts))
  }

  for (const key in set) {
    if (key === 'constructor') continue

    const value = inspectValue(set[key], depth + 1, opts)

    values.push(
      new InspectPair(
        ': ',
        inspectKey(key, depth + 1, opts),
        value,
        depth + 1,
        { ...opts, breakAlways: remaining < 0 }
      )
    )
  }

  ref.decrement()

  const header = set.constructor.name + '(' + set.size + ') { '

  return new InspectSequence(header, ' }', ', ', values, ref, depth, {
    ...opts,
    tabulate: true
  })
}

function inspectWeakMap(weakMap, ref, depth, opts) {
  const header = weakMap.constructor.name + ' { '

  return new InspectSequence(
    header,
    ' }',
    ' ',
    [new InspectLeaf('<items unknown>', styles.special, depth + 1, opts)],
    ref,
    depth,
    opts
  )
}

function inspectWeakSet(weakSet, ref, depth, opts) {
  const header = weakSet.constructor.name + ' { '

  return new InspectSequence(
    header,
    ' }',
    ' ',
    [new InspectLeaf('<items unknown>', styles.special, depth + 1, opts)],
    ref,
    depth,
    opts
  )
}

function inspectWeakRef(weakRef, ref, depth, opts) {
  const target = weakRef.deref()

  let value

  if (target === undefined) {
    value = new InspectLeaf('<cleared>', styles.special, depth + 1, opts)
  } else {
    value = inspectValue(target, depth + 1, opts)
  }

  const header = weakRef.constructor.name + ' { '

  return new InspectSequence(header, ' }', ' ', [value], ref, depth, opts)
}

function inspectArrayBuffer(arrayBuffer, ref, depth, opts) {
  ref.increment()

  const values = []

  for (const key of ['byteLength']) {
    values.push(
      new InspectPair(
        ': ',
        inspectKey(key, depth + 1, opts),
        inspectValue(arrayBuffer[key], depth + 1, opts),
        depth + 1,
        opts
      )
    )
  }

  ref.decrement()

  const header = arrayBuffer.constructor.name + ' { '

  return new InspectSequence(header, ' }', ', ', values, ref, depth, opts)
}

function inspectSharedArrayBuffer(sharedArrayBuffer, ref, depth, opts) {
  ref.increment()

  const values = []

  for (const key of ['byteLength']) {
    values.push(
      new InspectPair(
        ': ',
        inspectKey(key, depth + 1, opts),
        inspectValue(sharedArrayBuffer[key], depth + 1, opts),
        depth + 1,
        opts
      )
    )
  }

  ref.decrement()

  const header = sharedArrayBuffer.constructor.name + ' { '

  return new InspectSequence(header, ' }', ', ', values, ref, depth, opts)
}

function inspectTypedArray(typedArray, ref, depth, opts) {
  if (Buffer.isBuffer(typedArray)) {
    return inspectBuffer(typedArray, ref, depth, opts)
  }

  const {
    maxArrayLength = defaultMaxArrayLength,
    maxTypedArrayLength = maxArrayLength
  } = opts

  ref.increment()

  const values = []

  let remaining = Math.max(maxTypedArrayLength, 0)

  for (let i = 0, n = typedArray.length; i < n; i++) {
    if (remaining-- === 0) {
      values.push(
        new InspectSuspension(typedArray.length - values.length, depth + 1, {
          ...opts,
          breakAlways: true
        })
      )

      break
    }

    values.push(inspectValue(typedArray[i], depth + 1, opts))
  }

  for (const key of binding.getOwnNonIndexPropertyNames(typedArray)) {
    values.push(
      new InspectPair(
        ': ',
        inspectKey(key, depth + 1, opts),
        inspectValue(typedArray[key], depth + 1, opts),
        depth + 1,
        { ...opts, breakAlways: remaining < 0 }
      )
    )
  }

  ref.decrement()

  const header = typedArray.constructor.name + '(' + typedArray.length + ') [ '

  return new InspectSequence(header, ' ]', ', ', values, ref, depth, {
    ...opts,
    tabulate: true
  })
}

function inspectBuffer(buffer, ref, depth, opts) {
  const {
    maxArrayLength = defaultMaxArrayLength,
    maxBufferLength = maxArrayLength
  } = opts

  ref.increment()

  const values = []

  let remaining = Math.max(maxBufferLength, 0)

  for (let i = 0, n = buffer.byteLength; i < n; i++) {
    if (remaining-- === 0) {
      values.push(
        new InspectSuspension(buffer.length - values.length, depth + 1, {
          ...opts,
          breakAlways: true
        })
      )

      break
    }

    values.push(
      new InspectLeaf(
        buffer[i].toString(16).padStart(2, '0'),
        null,
        depth + 1,
        opts
      )
    )
  }

  for (const key of binding.getOwnNonIndexPropertyNames(buffer)) {
    values.push(
      new InspectPair(
        ': ',
        inspectKey(key, depth + 1, opts),
        inspectValue(buffer[key], depth + 1, opts),
        depth + 1,
        { ...opts, breakAlways: remaining < 0 }
      )
    )
  }

  ref.decrement()

  return new InspectSequence('<Buffer ', '>', ' ', values, ref, depth, {
    ...opts,
    tabulate: true
  })
}

function inspectDataView(dataView, ref, depth, opts) {
  ref.increment()

  const values = []

  for (const key of ['byteLength', 'byteOffset', 'buffer']) {
    values.push(
      new InspectPair(
        ': ',
        inspectKey(key, depth + 1, opts),
        inspectValue(dataView[key], depth + 1, opts),
        depth + 1,
        opts
      )
    )
  }

  for (const key of binding.getOwnNonIndexPropertyNames(dataView)) {
    values.push(
      new InspectPair(
        ': ',
        inspectKey(key, depth + 1, opts),
        inspectValue(dataView[key], depth + 1, opts),
        depth + 1,
        opts
      )
    )
  }

  ref.decrement()

  const header = dataView.constructor.name + ' { '

  return new InspectSequence(header, ' }', ', ', values, ref, depth, opts)
}

function inspectFunction(type, fn, depth, opts) {
  if (fn.toString().startsWith('class')) return inspectClass(fn, depth, opts)

  let tag = 'function'

  if (type.isGeneratorFunction()) tag = 'generator ' + tag
  if (type.isAsyncFunction()) tag = 'async ' + tag

  return new InspectLeaf(
    '[' + tag + ' ' + (fn.name ? fn.name : '(anonymous)') + ']',
    styles.special,
    depth,
    opts
  )
}

function inspectClass(ctor, depth, opts) {
  return new InspectLeaf(
    '[class ' + (ctor.name ? ctor.name : '(anonymous)') + ']',
    styles.special,
    depth,
    opts
  )
}

function inspectExternal(external, depth, opts) {
  return new InspectLeaf(
    '[external 0x' + binding.getExternal(external).toString(16) + ']',
    styles.special,
    depth,
    opts
  )
}
{
  "name": "bare-inspect",
  "version": "3.1.4",
  "description": "Inspect objects as strings for debugging",
  "exports": {
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    },
    "./package": "./package.json"
  },
  "files": [
    "index.js",
    "index.d.ts",
    "binding.c",
    "binding.js",
    "CMakeLists.txt",
    "prebuilds"
  ],
  "addon": true,
  "scripts": {
    "test": "prettier . --check && bare test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-inspect.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-inspect/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-inspect#readme",
  "engines": {
    "bare": ">=1.18.0"
  },
  "dependencies": {
    "bare-ansi-escapes": "^2.1.0",
    "bare-type": "^1.0.0"
  },
  "devDependencies": {
    "brittle": "^3.3.2",
    "cmake-bare": "^1.1.6",
    "prettier": "^3.4.1",
    "prettier-config-standard": "^7.0.0"
  }
}
module.exports = require.addon()
const binding = require('./binding')
const errors = require('./lib/errors')
const constants = require('./lib/constants')

exports.constants = constants
exports.errors = errors

exports.EOL = binding.platform === 'win32' ? '\r\n' : '\n'

exports.devNull = binding.platform === 'win32' ? '\\\\.\\nul' : '/dev/null'

exports.platform = function platform() {
  return binding.platform
}

exports.arch = function arch() {
  return binding.arch
}

exports.type = binding.type
exports.version = binding.version
exports.release = binding.release
exports.machine = binding.machine
exports.execPath = binding.execPath
exports.pid = binding.pid
exports.ppid = binding.ppid
exports.cwd = binding.cwd
exports.chdir = binding.chdir
exports.tmpdir = binding.tmpdir
exports.homedir = binding.homedir
exports.hostname = binding.hostname
exports.userInfo = binding.userInfo
exports.groupInfo = binding.groupInfo

exports.networkInterfaces = function networkInterfaces() {
  const result = {}

  for (const entry of binding.networkInterfaces()) {
    const { name, ...properties } = entry

    if (result[name]) result[name].push(properties)
    else result[name] = [properties]
  }

  return result
}

exports.kill = function kill(pid, signal = constants.signals.SIGTERM) {
  if (typeof signal === 'string') {
    if (signal in constants.signals === false) {
      throw errors.UNKNOWN_SIGNAL('Unknown signal: ' + signal)
    }

    signal = constants.signals[signal]
  }

  binding.kill(pid, signal)
}

exports.endianness = function endianness() {
  return binding.isLittleEndian ? 'LE' : 'BE'
}

exports.availableParallelism = binding.availableParallelism

exports.cpuUsage = function cpuUsage(previous) {
  const current = binding.cpuUsage()

  if (previous) {
    return {
      user: current.user - previous.user,
      system: current.system - previous.system
    }
  }

  return current
}

exports.threadCpuUsage = function threadCpuUsage(previous) {
  const current = binding.threadCpuUsage()

  if (previous) {
    return {
      user: current.user - previous.user,
      system: current.system - previous.system
    }
  }

  return current
}

exports.resourceUsage = binding.resourceUsage
exports.memoryUsage = binding.memoryUsage
exports.freemem = binding.freemem
exports.totalmem = binding.totalmem
exports.availableMemory = binding.availableMemory
exports.constrainedMemory = binding.constrainedMemory
exports.uptime = binding.uptime
exports.loadavg = binding.loadavg
exports.cpus = binding.cpus

exports.getProcessTitle = binding.getProcessTitle

exports.setProcessTitle = function setProcessTitle(title) {
  if (typeof title !== 'string') title = title.toString()

  if (title.length >= 256) {
    throw errors.TITLE_OVERFLOW('Process title is too long')
  }

  binding.setProcessTitle(title)
}

exports.getPriority = function getPriority(pid = 0) {
  return binding.getPriority(pid)
}

exports.setPriority = function setPriority(pid, priority) {
  if (priority === undefined) {
    priority = pid
    pid = 0
  }

  binding.setPriority(pid, priority)
}

exports.getEnvKeys = binding.getEnvKeys
exports.getEnv = binding.getEnv
exports.hasEnv = binding.hasEnv
exports.setEnv = binding.setEnv
exports.unsetEnv = binding.unsetEnv
const binding = require('../binding')

module.exports = {
  signals: binding.signals,
  errnos: binding.errnos,
  priority: binding.priority
}
module.exports = class OSError extends Error {
  constructor(msg, fn = OSError, code = fn.name) {
    super(`${code}: ${msg}`)
    this.code = code

    if (Error.captureStackTrace) {
      Error.captureStackTrace(this, fn)
    }
  }

  get name() {
    return 'OSError'
  }

  static UNKNOWN_SIGNAL(msg) {
    return new OSError(msg, OSError.UNKNOWN_SIGNAL)
  }

  static TITLE_OVERFLOW(msg) {
    return new OSError(msg, OSError.TITLE_OVERFLOW)
  }
}
{
  "name": "bare-os",
  "version": "3.9.1",
  "description": "Operating system utilities for Bare",
  "exports": {
    "./package": "./package.json",
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    },
    "./constants": {
      "types": "./lib/constants.d.ts",
      "default": "./lib/constants.js"
    },
    "./errors": {
      "types": "./lib/errors.d.ts",
      "default": "./lib/errors.js"
    }
  },
  "files": [
    "index.js",
    "index.d.ts",
    "binding.c",
    "binding.js",
    "CMakeLists.txt",
    "lib",
    "prebuilds"
  ],
  "addon": true,
  "scripts": {
    "format": "prettier --write . && lunte --fix",
    "lint": "prettier --check . && lunte",
    "test": "brittle-bare --coverage test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-os.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-os/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-os#readme",
  "engines": {
    "bare": ">=1.14.0"
  },
  "devDependencies": {
    "brittle": "^3.1.1",
    "cmake-bare": "^1.1.6",
    "lunte": "^1.6.0",
    "prettier": "^3.4.2",
    "prettier-config-holepunch": "^2.0.0"
  }
}
// This export SHOULD NOT be shortened in any way as having the full
// `module.exports = require(...)` statement is crucial for synthesizing
// ESM exports.

if (Bare.platform === 'win32') {
  module.exports = require('./lib/win32')
} else {
  module.exports = require('./lib/posix')
}
module.exports = {
  CHAR_UPPERCASE_A: 0x41,
  CHAR_LOWERCASE_A: 0x61,
  CHAR_UPPERCASE_Z: 0x5a,
  CHAR_LOWERCASE_Z: 0x7a,
  CHAR_DOT: 0x2e,
  CHAR_FORWARD_SLASH: 0x2f,
  CHAR_BACKWARD_SLASH: 0x5c,
  CHAR_COLON: 0x3a,
  CHAR_QUESTION_MARK: 0x3f
}
const os = require('bare-os')

const { normalizeString } = require('./shared')
const { CHAR_DOT, CHAR_FORWARD_SLASH } = require('./constants')

function isPosixPathSeparator(code) {
  return code === CHAR_FORWARD_SLASH
}

exports.win32 = require('./win32')
exports.posix = exports

exports.sep = '/'
exports.delimiter = ':'

exports.resolve = function resolve(...args) {
  let resolvedPath = ''
  let resolvedAbsolute = false

  for (let i = args.length - 1; i >= -1 && !resolvedAbsolute; i--) {
    const path = i >= 0 ? args[i] : os.cwd()

    if (path.length === 0) {
      continue
    }

    resolvedPath = `${path}/${resolvedPath}`
    resolvedAbsolute = path.charCodeAt(0) === CHAR_FORWARD_SLASH
  }

  resolvedPath = normalizeString(resolvedPath, !resolvedAbsolute, '/', isPosixPathSeparator)

  if (resolvedAbsolute) {
    return `/${resolvedPath}`
  }

  return resolvedPath.length > 0 ? resolvedPath : '.'
}

exports.normalize = function normalize(path) {
  if (path.length === 0) return '.'

  const isAbsolute = path.charCodeAt(0) === CHAR_FORWARD_SLASH
  const trailingSeparator = path.charCodeAt(path.length - 1) === CHAR_FORWARD_SLASH

  path = normalizeString(path, !isAbsolute, '/', isPosixPathSeparator)

  if (path.length === 0) {
    if (isAbsolute) return '/'
    return trailingSeparator ? './' : '.'
  }

  if (trailingSeparator) path += '/'

  return isAbsolute ? `/${path}` : path
}

exports.isAbsolute = function isAbsolute(path) {
  return path.length > 0 && path.charCodeAt(0) === CHAR_FORWARD_SLASH
}

exports.join = function join(...args) {
  if (args.length === 0) return '.'
  let joined
  for (let i = 0; i < args.length; ++i) {
    const arg = args[i]
    if (arg.length > 0) {
      if (joined === undefined) joined = arg
      else joined += `/${arg}`
    }
  }
  if (joined === undefined) return '.'
  return exports.normalize(joined)
}

exports.relative = function relative(from, to) {
  if (from === to) return ''

  from = exports.resolve(from)
  to = exports.resolve(to)

  if (from === to) return ''

  const fromStart = 1
  const fromEnd = from.length
  const fromLen = fromEnd - fromStart
  const toStart = 1
  const toLen = to.length - toStart

  const length = fromLen < toLen ? fromLen : toLen
  let lastCommonSep = -1
  let i = 0
  for (; i < length; i++) {
    const fromCode = from.charCodeAt(fromStart + i)
    if (fromCode !== to.charCodeAt(toStart + i)) {
      break
    } else if (fromCode === CHAR_FORWARD_SLASH) {
      lastCommonSep = i
    }
  }
  if (i === length) {
    if (toLen > length) {
      if (to.charCodeAt(toStart + i) === CHAR_FORWARD_SLASH) {
        return to.substring(toStart + i + 1)
      }
      if (i === 0) {
        return to.substring(toStart + i)
      }
    } else if (fromLen > length) {
      if (from.charCodeAt(fromStart + i) === CHAR_FORWARD_SLASH) {
        lastCommonSep = i
      } else if (i === 0) {
        lastCommonSep = 0
      }
    }
  }

  let out = ''
  for (i = fromStart + lastCommonSep + 1; i <= fromEnd; ++i) {
    if (i === fromEnd || from.charCodeAt(i) === CHAR_FORWARD_SLASH) {
      out += out.length === 0 ? '..' : '/..'
    }
  }

  return `${out}${to.substring(toStart + lastCommonSep)}`
}

exports.toNamespacedPath = function toNamespacedPath(path) {
  return path
}

exports.dirname = function dirname(path) {
  if (path.length === 0) return '.'
  const hasRoot = path.charCodeAt(0) === CHAR_FORWARD_SLASH
  let end = -1
  let matchedSlash = true
  for (let i = path.length - 1; i >= 1; --i) {
    if (path.charCodeAt(i) === CHAR_FORWARD_SLASH) {
      if (!matchedSlash) {
        end = i
        break
      }
    } else {
      matchedSlash = false
    }
  }

  if (end === -1) return hasRoot ? '/' : '.'
  if (hasRoot && end === 1) return '//'
  return path.substring(0, end)
}

exports.basename = function basename(path, suffix) {
  let start = 0
  let end = -1
  let matchedSlash = true

  if (suffix !== undefined && suffix.length > 0 && suffix.length <= path.length) {
    if (suffix === path) {
      return ''
    }
    let extIdx = suffix.length - 1
    let firstNonSlashEnd = -1
    for (let i = path.length - 1; i >= 0; --i) {
      const code = path.charCodeAt(i)
      if (code === CHAR_FORWARD_SLASH) {
        if (!matchedSlash) {
          start = i + 1
          break
        }
      } else {
        if (firstNonSlashEnd === -1) {
          matchedSlash = false
          firstNonSlashEnd = i + 1
        }
        if (extIdx >= 0) {
          if (code === suffix.charCodeAt(extIdx)) {
            if (--extIdx === -1) {
              end = i
            }
          } else {
            extIdx = -1
            end = firstNonSlashEnd
          }
        }
      }
    }

    if (start === end) end = firstNonSlashEnd
    else if (end === -1) end = path.length
    return path.substring(start, end)
  }

  for (let i = path.length - 1; i >= 0; --i) {
    if (path.charCodeAt(i) === CHAR_FORWARD_SLASH) {
      if (!matchedSlash) {
        start = i + 1
        break
      }
    } else if (end === -1) {
      matchedSlash = false
      end = i + 1
    }
  }

  if (end === -1) return ''
  return path.substring(start, end)
}

exports.extname = function extname(path) {
  let startDot = -1
  let startPart = 0
  let end = -1
  let matchedSlash = true
  let preDotState = 0
  for (let i = path.length - 1; i >= 0; --i) {
    const code = path.charCodeAt(i)
    if (code === CHAR_FORWARD_SLASH) {
      if (!matchedSlash) {
        startPart = i + 1
        break
      }
      continue
    }
    if (end === -1) {
      matchedSlash = false
      end = i + 1
    }
    if (code === CHAR_DOT) {
      if (startDot === -1) startDot = i
      else if (preDotState !== 1) preDotState = 1
    } else if (startDot !== -1) {
      preDotState = -1
    }
  }

  if (
    startDot === -1 ||
    end === -1 ||
    preDotState === 0 ||
    (preDotState === 1 && startDot === end - 1 && startDot === startPart + 1)
  ) {
    return ''
  }
  return path.substring(startDot, end)
}
const { CHAR_DOT, CHAR_FORWARD_SLASH } = require('./constants')

exports.normalizeString = function normalizeString(
  path,
  allowAboveRoot,
  separator,
  isPathSeparator
) {
  let res = ''
  let lastSegmentLength = 0
  let lastSlash = -1
  let dots = 0
  let code = 0
  for (let i = 0; i <= path.length; ++i) {
    if (i < path.length) {
      code = path.charCodeAt(i)
    } else if (isPathSeparator(code)) {
      break
    } else {
      code = CHAR_FORWARD_SLASH
    }

    if (isPathSeparator(code)) {
      if (lastSlash === i - 1 || dots === 1);
      else if (dots === 2) {
        if (
          res.length < 2 ||
          lastSegmentLength !== 2 ||
          res.charCodeAt(res.length - 1) !== CHAR_DOT ||
          res.charCodeAt(res.length - 2) !== CHAR_DOT
        ) {
          if (res.length > 2) {
            const lastSlashIndex = res.lastIndexOf(separator)
            if (lastSlashIndex === -1) {
              res = ''
              lastSegmentLength = 0
            } else {
              res = res.substring(0, lastSlashIndex)
              lastSegmentLength = res.length - 1 - res.lastIndexOf(separator)
            }
            lastSlash = i
            dots = 0
            continue
          } else if (res.length !== 0) {
            res = ''
            lastSegmentLength = 0
            lastSlash = i
            dots = 0
            continue
          }
        }
        if (allowAboveRoot) {
          res += res.length > 0 ? `${separator}..` : '..'
          lastSegmentLength = 2
        }
      } else {
        if (res.length > 0) {
          res += `${separator}${path.substring(lastSlash + 1, i)}`
        } else {
          res = path.substring(lastSlash + 1, i)
        }
        lastSegmentLength = i - lastSlash - 1
      }
      lastSlash = i
      dots = 0
    } else if (code === CHAR_DOT && dots !== -1) {
      ++dots
    } else {
      dots = -1
    }
  }
  return res
}
const os = require('bare-os')

const { normalizeString } = require('./shared')
const {
  CHAR_UPPERCASE_A,
  CHAR_LOWERCASE_A,
  CHAR_UPPERCASE_Z,
  CHAR_LOWERCASE_Z,
  CHAR_DOT,
  CHAR_FORWARD_SLASH,
  CHAR_BACKWARD_SLASH,
  CHAR_COLON,
  CHAR_QUESTION_MARK
} = require('./constants')

function isWindowsPathSeparator(code) {
  return code === CHAR_FORWARD_SLASH || code === CHAR_BACKWARD_SLASH
}

function isWindowsDeviceRoot(code) {
  return (
    (code >= CHAR_UPPERCASE_A && code <= CHAR_UPPERCASE_Z) ||
    (code >= CHAR_LOWERCASE_A && code <= CHAR_LOWERCASE_Z)
  )
}

exports.posix = require('./posix')
exports.win32 = exports

exports.sep = '\\'
exports.delimiter = ';'

exports.resolve = function resolve(...args) {
  let resolvedDevice = ''
  let resolvedTail = ''
  let resolvedAbsolute = false

  for (let i = args.length - 1; i >= -1; i--) {
    let path
    if (i >= 0) {
      path = args[i]

      if (path.length === 0) continue
    } else if (resolvedDevice.length === 0) {
      path = os.cwd()
    } else {
      path = os.getEnv(`=${resolvedDevice}`) || os.cwd()

      if (
        path === undefined ||
        (path.substring(0, 2).toLowerCase() !== resolvedDevice.toLowerCase() &&
          path.charCodeAt(2) === CHAR_BACKWARD_SLASH)
      ) {
        path = `${resolvedDevice}\\`
      }
    }

    const len = path.length
    let rootEnd = 0
    let device = ''
    let isAbsolute = false
    const code = path.charCodeAt(0)

    if (len === 1) {
      if (isWindowsPathSeparator(code)) {
        rootEnd = 1
        isAbsolute = true
      }
    } else if (isWindowsPathSeparator(code)) {
      isAbsolute = true

      if (isWindowsPathSeparator(path.charCodeAt(1))) {
        let j = 2
        let last = j
        while (j < len && !isWindowsPathSeparator(path.charCodeAt(j))) {
          j++
        }
        if (j < len && j !== last) {
          const firstPart = path.substring(last, j)
          last = j
          while (j < len && isWindowsPathSeparator(path.charCodeAt(j))) {
            j++
          }
          if (j < len && j !== last) {
            last = j
            while (j < len && !isWindowsPathSeparator(path.charCodeAt(j))) {
              j++
            }
            if (j === len || j !== last) {
              device = `\\\\${firstPart}\\${path.substring(last, j)}`
              rootEnd = j
            }
          }
        }
      } else {
        rootEnd = 1
      }
    } else if (isWindowsDeviceRoot(code) && path.charCodeAt(1) === CHAR_COLON) {
      device = path.substring(0, 2)
      rootEnd = 2
      if (len > 2 && isWindowsPathSeparator(path.charCodeAt(2))) {
        isAbsolute = true
        rootEnd = 3
      }
    }

    if (device.length > 0) {
      if (resolvedDevice.length > 0) {
        if (device.toLowerCase() !== resolvedDevice.toLowerCase()) {
          continue
        }
      } else {
        resolvedDevice = device
      }
    }

    if (resolvedAbsolute) {
      if (resolvedDevice.length > 0) {
        break
      }
    } else {
      resolvedTail = `${path.substring(rootEnd)}\\${resolvedTail}`
      resolvedAbsolute = isAbsolute
      if (isAbsolute && resolvedDevice.length > 0) {
        break
      }
    }
  }

  resolvedTail = normalizeString(resolvedTail, !resolvedAbsolute, '\\', isWindowsPathSeparator)

  return resolvedAbsolute
    ? `${resolvedDevice}\\${resolvedTail}`
    : `${resolvedDevice}${resolvedTail}` || '.'
}

exports.normalize = function normalize(path) {
  const len = path.length
  if (len === 0) return '.'
  let rootEnd = 0
  let device
  let isAbsolute = false
  const code = path.charCodeAt(0)

  if (len === 1) {
    return code === CHAR_FORWARD_SLASH ? '\\' : path
  }

  if (isWindowsPathSeparator(code)) {
    isAbsolute = true

    if (isWindowsPathSeparator(path.charCodeAt(1))) {
      let j = 2
      let last = j
      while (j < len && !isWindowsPathSeparator(path.charCodeAt(j))) {
        j++
      }
      if (j < len && j !== last) {
        const firstPart = path.substring(last, j)
        last = j
        while (j < len && isWindowsPathSeparator(path.charCodeAt(j))) {
          j++
        }
        if (j < len && j !== last) {
          last = j
          while (j < len && !isWindowsPathSeparator(path.charCodeAt(j))) {
            j++
          }
          if (j === len) {
            return `\\\\${firstPart}\\${path.substring(last)}\\`
          }
          if (j !== last) {
            device = `\\\\${firstPart}\\${path.substring(last, j)}`
            rootEnd = j
          }
        }
      }
    } else {
      rootEnd = 1
    }
  } else if (isWindowsDeviceRoot(code) && path.charCodeAt(1) === CHAR_COLON) {
    device = path.substring(0, 2)
    rootEnd = 2
    if (len > 2 && isWindowsPathSeparator(path.charCodeAt(2))) {
      isAbsolute = true
      rootEnd = 3
    }
  }

  let tail =
    rootEnd < len
      ? normalizeString(path.substring(rootEnd), !isAbsolute, '\\', isWindowsPathSeparator)
      : ''
  if (tail.length === 0 && !isAbsolute) {
    tail = '.'
  }
  if (tail.length > 0 && isWindowsPathSeparator(path.charCodeAt(len - 1))) {
    tail += '\\'
  }
  if (device === undefined) {
    return isAbsolute ? `\\${tail}` : tail
  }
  return isAbsolute ? `${device}\\${tail}` : `${device}${tail}`
}

exports.isAbsolute = function isAbsolute(path) {
  const len = path.length
  if (len === 0) return false

  const code = path.charCodeAt(0)

  return (
    isWindowsPathSeparator(code) ||
    (len > 2 &&
      isWindowsDeviceRoot(code) &&
      path.charCodeAt(1) === CHAR_COLON &&
      isWindowsPathSeparator(path.charCodeAt(2)))
  )
}

exports.join = function join(...args) {
  if (args.length === 0) return '.'

  let joined
  let firstPart
  for (let i = 0; i < args.length; ++i) {
    const arg = args[i]
    if (arg.length > 0) {
      if (joined === undefined) joined = firstPart = arg
      else joined += `\\${arg}`
    }
  }

  if (joined === undefined) return '.'

  let needsReplace = true
  let slashCount = 0
  if (isWindowsPathSeparator(firstPart.charCodeAt(0))) {
    ++slashCount
    const firstLen = firstPart.length
    if (firstLen > 1 && isWindowsPathSeparator(firstPart.charCodeAt(1))) {
      ++slashCount
      if (firstLen > 2) {
        if (isWindowsPathSeparator(firstPart.charCodeAt(2))) {
          ++slashCount
        } else {
          needsReplace = false
        }
      }
    }
  }
  if (needsReplace) {
    while (slashCount < joined.length && isWindowsPathSeparator(joined.charCodeAt(slashCount))) {
      slashCount++
    }

    if (slashCount >= 2) {
      joined = `\\${joined.substring(slashCount)}`
    }
  }

  return exports.normalize(joined)
}

exports.relative = function relative(from, to) {
  if (from === to) return ''

  const fromOrig = exports.resolve(from)
  const toOrig = exports.resolve(to)

  if (fromOrig === toOrig) return ''

  from = fromOrig.toLowerCase()
  to = toOrig.toLowerCase()

  if (from === to) return ''

  let fromStart = 0
  while (fromStart < from.length && from.charCodeAt(fromStart) === CHAR_BACKWARD_SLASH) {
    fromStart++
  }
  let fromEnd = from.length
  while (fromEnd - 1 > fromStart && from.charCodeAt(fromEnd - 1) === CHAR_BACKWARD_SLASH) {
    fromEnd--
  }
  const fromLen = fromEnd - fromStart

  let toStart = 0
  while (toStart < to.length && to.charCodeAt(toStart) === CHAR_BACKWARD_SLASH) {
    toStart++
  }
  let toEnd = to.length
  while (toEnd - 1 > toStart && to.charCodeAt(toEnd - 1) === CHAR_BACKWARD_SLASH) {
    toEnd--
  }
  const toLen = toEnd - toStart

  const length = fromLen < toLen ? fromLen : toLen
  let lastCommonSep = -1
  let i = 0
  for (; i < length; i++) {
    const fromCode = from.charCodeAt(fromStart + i)
    if (fromCode !== to.charCodeAt(toStart + i)) {
      break
    } else if (fromCode === CHAR_BACKWARD_SLASH) {
      lastCommonSep = i
    }
  }

  if (i !== length) {
    if (lastCommonSep === -1) return toOrig
  } else {
    if (toLen > length) {
      if (to.charCodeAt(toStart + i) === CHAR_BACKWARD_SLASH) {
        return toOrig.substring(toStart + i + 1)
      }
      if (i === 2) {
        return toOrig.substring(toStart + i)
      }
    }
    if (fromLen > length) {
      if (from.charCodeAt(fromStart + i) === CHAR_BACKWARD_SLASH) {
        lastCommonSep = i
      } else if (i === 2) {
        lastCommonSep = 3
      }
    }
    if (lastCommonSep === -1) lastCommonSep = 0
  }

  let out = ''
  for (i = fromStart + lastCommonSep + 1; i <= fromEnd; ++i) {
    if (i === fromEnd || from.charCodeAt(i) === CHAR_BACKWARD_SLASH) {
      out += out.length === 0 ? '..' : '\\..'
    }
  }

  toStart += lastCommonSep

  if (out.length > 0) {
    return `${out}${toOrig.substring(toStart, toEnd)}`
  }
  if (toOrig.charCodeAt(toStart) === CHAR_BACKWARD_SLASH) {
    ++toStart
  }
  return toOrig.substring(toStart, toEnd)
}

exports.toNamespacedPath = function toNamespacedPath(path) {
  if (path.length === 0) return path

  const resolvedPath = exports.resolve(path)

  if (resolvedPath.length <= 2) return path

  if (resolvedPath.charCodeAt(0) === CHAR_BACKWARD_SLASH) {
    if (resolvedPath.charCodeAt(1) === CHAR_BACKWARD_SLASH) {
      const code = resolvedPath.charCodeAt(2)
      if (code !== CHAR_QUESTION_MARK && code !== CHAR_DOT) {
        return `\\\\?\\UNC\\${resolvedPath.substring(2)}`
      }
    }
  } else if (
    isWindowsDeviceRoot(resolvedPath.charCodeAt(0)) &&
    resolvedPath.charCodeAt(1) === CHAR_COLON &&
    resolvedPath.charCodeAt(2) === CHAR_BACKWARD_SLASH
  ) {
    return `\\\\?\\${resolvedPath}`
  }

  return path
}

exports.dirname = function dirname(path) {
  const len = path.length
  if (len === 0) return '.'
  let rootEnd = -1
  let offset = 0
  const code = path.charCodeAt(0)

  if (len === 1) {
    return isWindowsPathSeparator(code) ? path : '.'
  }

  if (isWindowsPathSeparator(code)) {
    rootEnd = offset = 1

    if (isWindowsPathSeparator(path.charCodeAt(1))) {
      let j = 2
      let last = j
      while (j < len && !isWindowsPathSeparator(path.charCodeAt(j))) {
        j++
      }
      if (j < len && j !== last) {
        last = j
        while (j < len && isWindowsPathSeparator(path.charCodeAt(j))) {
          j++
        }
        if (j < len && j !== last) {
          last = j
          while (j < len && !isWindowsPathSeparator(path.charCodeAt(j))) {
            j++
          }
          if (j === len) {
            return path
          }
          if (j !== last) {
            rootEnd = offset = j + 1
          }
        }
      }
    }
  } else if (isWindowsDeviceRoot(code) && path.charCodeAt(1) === CHAR_COLON) {
    rootEnd = len > 2 && isWindowsPathSeparator(path.charCodeAt(2)) ? 3 : 2
    offset = rootEnd
  }

  let end = -1
  let matchedSlash = true
  for (let i = len - 1; i >= offset; --i) {
    if (isWindowsPathSeparator(path.charCodeAt(i))) {
      if (!matchedSlash) {
        end = i
        break
      }
    } else {
      matchedSlash = false
    }
  }

  if (end === -1) {
    if (rootEnd === -1) return '.'

    end = rootEnd
  }
  return path.substring(0, end)
}

exports.basename = function basename(path, suffix) {
  let start = 0
  let end = -1
  let matchedSlash = true

  if (
    path.length >= 2 &&
    isWindowsDeviceRoot(path.charCodeAt(0)) &&
    path.charCodeAt(1) === CHAR_COLON
  ) {
    start = 2
  }

  if (suffix !== undefined && suffix.length > 0 && suffix.length <= path.length) {
    if (suffix === path) return ''
    let extIdx = suffix.length - 1
    let firstNonSlashEnd = -1
    for (let i = path.length - 1; i >= start; --i) {
      const code = path.charCodeAt(i)
      if (isWindowsPathSeparator(code)) {
        if (!matchedSlash) {
          start = i + 1
          break
        }
      } else {
        if (firstNonSlashEnd === -1) {
          matchedSlash = false
          firstNonSlashEnd = i + 1
        }
        if (extIdx >= 0) {
          if (code === suffix.charCodeAt(extIdx)) {
            if (--extIdx === -1) {
              end = i
            }
          } else {
            extIdx = -1
            end = firstNonSlashEnd
          }
        }
      }
    }

    if (start === end) end = firstNonSlashEnd
    else if (end === -1) end = path.length
    return path.substring(start, end)
  }
  for (let i = path.length - 1; i >= start; --i) {
    if (isWindowsPathSeparator(path.charCodeAt(i))) {
      if (!matchedSlash) {
        start = i + 1
        break
      }
    } else if (end === -1) {
      matchedSlash = false
      end = i + 1
    }
  }

  if (end === -1) return ''
  return path.substring(start, end)
}

exports.extname = function extname(path) {
  let start = 0
  let startDot = -1
  let startPart = 0
  let end = -1
  let matchedSlash = true
  let preDotState = 0

  if (
    path.length >= 2 &&
    path.charCodeAt(1) === CHAR_COLON &&
    isWindowsDeviceRoot(path.charCodeAt(0))
  ) {
    start = startPart = 2
  }

  for (let i = path.length - 1; i >= start; --i) {
    const code = path.charCodeAt(i)
    if (isWindowsPathSeparator(code)) {
      if (!matchedSlash) {
        startPart = i + 1
        break
      }
      continue
    }
    if (end === -1) {
      matchedSlash = false
      end = i + 1
    }
    if (code === CHAR_DOT) {
      if (startDot === -1) startDot = i
      else if (preDotState !== 1) preDotState = 1
    } else if (startDot !== -1) {
      preDotState = -1
    }
  }

  if (
    startDot === -1 ||
    end === -1 ||
    preDotState === 0 ||
    (preDotState === 1 && startDot === end - 1 && startDot === startPart + 1)
  ) {
    return ''
  }
  return path.substring(startDot, end)
}
{
  "name": "bare-path",
  "version": "3.0.1",
  "description": "Path manipulation library for JavaScript",
  "exports": {
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    },
    "./package": "./package.json",
    "./posix": "./lib/posix.js",
    "./win32": "./lib/win32.js"
  },
  "files": [
    "index.js",
    "index.d.ts",
    "lib",
    "NOTICE"
  ],
  "scripts": {
    "format": "prettier --write . && lunte --fix",
    "lint": "prettier --check . && lunte",
    "test": "brittle-bare --coverage test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-path.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-path/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-path#readme",
  "dependencies": {
    "bare-os": "^3.0.1"
  },
  "devDependencies": {
    "brittle": "^3.3.2",
    "lunte": "^1.8.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^2.0.0"
  }
}
const b4a = require('b4a')
const stream = require('streamx')
const { ReadableStream, WritableStream } = require('./web')

const defaultEncoding = 'utf8'

module.exports = exports = stream.Stream

exports.pipeline = stream.pipeline

exports.isStream = stream.isStream
exports.isEnding = stream.isEnding
exports.isEnded = stream.isEnded
exports.isFinishing = stream.isFinishing
exports.isFinished = stream.isFinished
exports.isDisturbed = stream.isDisturbed

exports.isErrored = function isErrored(stream) {
  return exports.getStreamError(stream) !== null
}

exports.isReadable = function isReadable(stream) {
  return stream.readable && !stream.destroying && !exports.isEnded(stream)
}

exports.isWritable = function isWritable(stream) {
  return stream.writable && !stream.destroying && !exports.isFinishing(stream)
}

exports.getStreamError = stream.getStreamError

exports.addAbortSignal = function addAbortSignal(signal, stream) {
  function onAbort() {
    stream.destroy(signal.reason)
  }

  if (signal.aborted) onAbort()
  else signal.addEventListener('abort', onAbort)

  return stream
}

exports.Stream = exports

exports.Readable = class Readable extends stream.Readable {
  constructor(opts = {}) {
    super({
      ...opts,
      byteLength: null,
      byteLengthReadable: null,
      map: null,
      mapReadable: null
    })

    if (this._construct) this._open = this._construct

    if (this._read !== stream.Readable.prototype._read) {
      this._read = read.bind(this, this._read)
    }

    if (this._destroy !== stream.Stream.prototype._destroy) {
      this._destroy = destroy.bind(this, this._destroy)
    }
  }

  get closed() {
    return !exports.isReadable(this)
  }

  get errored() {
    return stream.getStreamError(this)
  }

  push(chunk, encoding) {
    if (typeof chunk === 'string') {
      chunk = b4a.from(chunk, encoding || defaultEncoding)
    }

    return super.push(chunk)
  }

  unshift(chunk, encoding) {
    if (typeof chunk === 'string') {
      chunk = b4a.from(chunk, encoding || defaultEncoding)
    }

    super.unshift(chunk)
  }

  static fromWeb(readableStream, opts = {}) {
    const stream = readableStream._stream

    if (opts.encoding) stream.setEncoding(opts.encoding)
    if (opts.signal) exports.addAbortSignal(opts.signal, stream)

    return stream
  }

  static toWeb(readable, opts = {}) {
    return new ReadableStream(readable, opts.strategy)
  }

  async [Symbol.asyncDispose]() {
    if (!this.destroyed) this.destroy()

    await new Promise((resolve) => exports.finished(this, resolve))
  }
}

exports.Writable = class Writable extends stream.Writable {
  constructor(opts = {}) {
    super({
      ...opts,
      byteLength: null,
      byteLengthWritable,
      map: null,
      mapWritable: null
    })

    if (this._construct) this._open = this._construct

    if (this._write !== stream.Writable.prototype._write) {
      this._write = write.bind(this, this._write)
    }

    if (this._destroy !== stream.Stream.prototype._destroy) {
      this._destroy = destroy.bind(this, this._destroy)
    }
  }

  get closed() {
    return !exports.isWritable(this)
  }

  get errored() {
    return stream.getStreamError(this)
  }

  write(chunk, encoding, cb) {
    if (typeof encoding === 'function') {
      cb = encoding
      encoding = null
    }

    if (typeof chunk === 'string') {
      encoding = encoding || defaultEncoding
      chunk = b4a.from(chunk, encoding)
    } else {
      encoding = 'buffer'
    }

    const result = super.write({ chunk, encoding })

    if (cb) stream.Writable.drained(this).then(() => cb(null), cb)

    return result
  }

  end(chunk, encoding, cb) {
    if (typeof chunk === 'function') {
      cb = chunk
      chunk = null
    } else if (typeof encoding === 'function') {
      cb = encoding
      encoding = null
    }

    if (typeof chunk === 'string') {
      encoding = encoding || defaultEncoding
      chunk = b4a.from(chunk, encoding || defaultEncoding)
    } else {
      encoding = 'buffer'
    }

    const result =
      chunk !== undefined && chunk !== null ? super.end({ chunk, encoding }) : super.end()

    if (cb) this.once('finish', () => cb(null))

    return result
  }

  static fromWeb(writableStream, opts = {}) {
    const stream = writableStream._stream

    if (opts.signal) exports.addAbortSignal(opts.signal, stream)

    return stream
  }

  static toWeb(writable) {
    return new WritableStream(writable)
  }

  async [Symbol.asyncDispose]() {
    if (!this.destroyed) this.destroy()

    await new Promise((resolve) => exports.finished(this, resolve))
  }
}

exports.Duplex = class Duplex extends stream.Duplex {
  constructor(opts = {}) {
    super({
      ...opts,
      byteLength: null,
      byteLengthReadable: null,
      byteLengthWritable,
      map: null,
      mapReadable: null,
      mapWritable: null
    })

    if (this._construct) this._open = this._construct

    if (this._read !== stream.Readable.prototype._read) {
      this._read = read.bind(this, this._read)
    }

    if (this._write !== stream.Duplex.prototype._write) {
      this._write = write.bind(this, this._write)
    }

    if (this._destroy !== stream.Stream.prototype._destroy) {
      this._destroy = destroy.bind(this, this._destroy)
    }
  }

  push(chunk, encoding) {
    if (typeof chunk === 'string') {
      chunk = b4a.from(chunk, encoding || defaultEncoding)
    }

    return super.push(chunk)
  }

  unshift(chunk, encoding) {
    if (typeof chunk === 'string') {
      chunk = b4a.from(chunk, encoding || defaultEncoding)
    }

    super.unshift(chunk)
  }

  write(chunk, encoding, cb) {
    if (typeof encoding === 'function') {
      cb = encoding
      encoding = null
    }

    if (typeof chunk === 'string') {
      encoding = encoding || defaultEncoding
      chunk = b4a.from(chunk, encoding)
    } else {
      encoding = 'buffer'
    }

    const result = super.write({ chunk, encoding })

    if (cb) stream.Writable.drained(this).then(() => cb(null), cb)

    return result
  }

  end(chunk, encoding, cb) {
    if (typeof chunk === 'function') {
      cb = chunk
      chunk = null
    } else if (typeof encoding === 'function') {
      cb = encoding
      encoding = null
    }

    if (typeof chunk === 'string') {
      encoding = encoding || defaultEncoding
      chunk = b4a.from(chunk, encoding)
    } else {
      encoding = 'buffer'
    }

    const result =
      chunk !== undefined && chunk !== null ? super.end({ chunk, encoding }) : super.end()

    if (cb) this.once('finish', () => cb(null))

    return result
  }

  static fromWeb({ readable: readableStream, writable: writableStream }, opts) {
    const readable = exports.Readable.fromWeb(readableStream, opts)
    const writable = exports.Writable.fromWeb(writableStream, opts)

    const duplex = new exports.Duplex({
      write(data, encoding, cb) {
        writable.write(data, encoding, cb)
      }
    })

    readable
      .on('data', (data) => duplex.push(data))
      .on('end', () => duplex.push(null))
      .on('error', (err) => duplex.destroy(err))

    writable.on('finish', () => duplex.end()).on('error', (err) => duplex.destroy(err))

    return duplex
  }

  static toWeb(duplex) {
    const readableStream = exports.Readable.toWeb(duplex)
    const writableStream = exports.Writable.toWeb(duplex)

    return { readable: readableStream, writable: writableStream }
  }
}

class DuplexSide extends exports.Duplex {
  constructor(opts) {
    super(opts)

    this._otherSide = null
    this._cb = null
  }

  _read() {
    const cb = this._cb
    if (!cb) return

    this._cb = null
    cb()
  }

  _write(chunk, encoding, cb) {
    this._otherSide.push(chunk, encoding)
    this._otherSide._cb = cb
  }

  _final(cb) {
    this._otherSide.on('end', cb)
    this._otherSide.push(null)
  }
}

exports.duplexPair = function duplexPair(opts) {
  const sideA = new DuplexSide(opts)
  const sideB = new DuplexSide(opts)

  sideA._otherSide = sideB
  sideB._otherSide = sideA

  return [sideA, sideB]
}

exports.Transform = class Transform extends stream.Transform {
  constructor(opts = {}) {
    super({
      ...opts,
      byteLength: null,
      byteLengthReadable: null,
      byteLengthWritable,
      map: null,
      mapReadable: null,
      mapWritable: null
    })

    if (this._transform !== stream.Transform.prototype._transform) {
      this._transform = transform.bind(this, this._transform)
    } else {
      this._transform = passthrough
    }
  }

  push(chunk, encoding) {
    if (typeof chunk === 'string') {
      chunk = b4a.from(chunk, encoding || defaultEncoding)
    }

    return super.push(chunk)
  }

  unshift(chunk, encoding) {
    if (typeof chunk === 'string') {
      chunk = b4a.from(chunk, encoding || defaultEncoding)
    }

    super.unshift(chunk)
  }

  write(chunk, encoding, cb) {
    if (typeof encoding === 'function') {
      cb = encoding
      encoding = null
    }

    if (typeof chunk === 'string') {
      encoding = encoding || defaultEncoding
      chunk = b4a.from(chunk, encoding)
    } else {
      encoding = 'buffer'
    }

    const result = super.write({ chunk, encoding })

    if (cb) stream.Writable.drained(this).then(() => cb(null), cb)

    return result
  }

  end(chunk, encoding, cb) {
    if (typeof chunk === 'function') {
      cb = chunk
      chunk = null
    } else if (typeof encoding === 'function') {
      cb = encoding
      encoding = null
    }

    if (typeof chunk === 'string') {
      encoding = encoding || defaultEncoding
      chunk = b4a.from(chunk, encoding)
    } else {
      encoding = 'buffer'
    }

    const result =
      chunk !== undefined && chunk !== null ? super.end({ chunk, encoding }) : super.end()

    if (cb) this.once('finish', () => cb(null))

    return result
  }
}

exports.PassThrough = class PassThrough extends exports.Transform {}

exports.finished = function finished(stream, opts, cb) {
  if (typeof opts === 'function') {
    cb = opts
    opts = {}
  }

  if (!opts) opts = {}

  const { cleanup = false } = opts

  const done = () => {
    cb(exports.getStreamError(stream, { all: true }))

    if (cleanup) detach()
  }

  const detach = () => {
    stream.off('close', done)
    stream.off('error', noop)
  }

  if (stream.destroyed) {
    done()
  } else {
    stream.on('close', done)
    stream.on('error', noop)
  }

  return detach
}

function read(read, cb) {
  read.call(this, 65536)

  cb(null)
}

function write(write, data, cb) {
  write.call(this, data.chunk, data.encoding, cb)
}

function transform(transform, data, cb) {
  transform.call(this, data.chunk, data.encoding, cb)
}

function destroy(destroy, cb) {
  destroy.call(this, exports.getStreamError(this), cb)
}

function passthrough(data, cb) {
  cb(null, data.chunk)
}

function byteLengthWritable(data) {
  return data.chunk.byteLength
}

function noop() {}
{
  "name": "bare-stream",
  "version": "2.13.3",
  "description": "Streaming data for JavaScript",
  "exports": {
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    },
    "./package": "./package.json",
    "./promises": "./promises.js",
    "./web": {
      "types": "./web.d.ts",
      "default": "./web.js"
    },
    "./global": {
      "types": "./global.d.ts",
      "default": "./global.js"
    }
  },
  "files": [
    "index.js",
    "index.d.ts",
    "promises.js",
    "web.js",
    "web.d.ts",
    "global.js",
    "global.d.ts"
  ],
  "scripts": {
    "format": "prettier --write . && lunte --fix",
    "lint": "prettier --check . && lunte",
    "test": "brittle-bare --coverage test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-stream.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-stream/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-stream#readme",
  "dependencies": {
    "b4a": "^1.8.1",
    "streamx": "^2.25.0",
    "teex": "^1.0.1"
  },
  "devDependencies": {
    "bare-abort-controller": "^1.1.0",
    "bare-buffer": "^3.0.0",
    "bare-events": "^2.5.4",
    "brittle": "^3.5.2",
    "lunte": "^1.6.0",
    "prettier": "^3.3.3",
    "prettier-config-holepunch": "^2.0.0"
  },
  "peerDependencies": {
    "bare-abort-controller": "*",
    "bare-buffer": "*",
    "bare-events": "*"
  },
  "peerDependenciesMeta": {
    "bare-abort-controller": {
      "optional": true
    },
    "bare-buffer": {
      "optional": true
    },
    "bare-events": {
      "optional": true
    }
  }
}
const { Readable, Writable, Transform, getStreamError, isStreamx, isDisturbed } = require('streamx')
const tee = require('teex')

const readableKind = Symbol.for('bare.stream.readable.kind')
const writableKind = Symbol.for('bare.stream.writable.kind')
const transformKind = Symbol.for('bare.stream.transform.kind')

// https://streams.spec.whatwg.org/#readablestreamdefaultreader
exports.ReadableStreamDefaultReader = class ReadableStreamDefaultReader {
  constructor(stream) {
    this._stream = stream
    this._stream._stream.once('close', onclose).once('error', onerror)

    const closed = Promise.withResolvers()

    // Avoid unhandled exceptions
    closed.promise.catch(noop)

    this._closed = closed

    function onclose() {
      closed.resolve()
    }

    function onerror(err) {
      closed.reject(err)
    }
  }

  get closed() {
    return this._closed.promise
  }

  read() {
    const stream = this._stream._stream

    return new Promise((resolve, reject) => {
      const err = getStreamError(stream)

      if (err) return reject(err)

      if (stream.destroyed) {
        return resolve({ value: undefined, done: true })
      }

      const value = stream.read()

      if (value !== null) {
        return resolve({ value, done: false })
      }

      stream.once('readable', onreadable).once('close', onclose).once('error', onerror)

      function onreadable() {
        const value = stream.read()

        ondone(null, value === null ? { value: undefined, done: true } : { value, done: false })
      }

      function onclose() {
        ondone(null, { value: undefined, done: true })
      }

      function onerror(err) {
        ondone(err, null)
      }

      function ondone(err, value) {
        stream.off('readable', onreadable).off('close', onclose).off('error', onerror)

        if (err) reject(err)
        else resolve(value)
      }
    })
  }

  releaseLock() {
    this._closed.reject(new TypeError('Reader was released'))
    this._stream._releaseLock()
    this._stream = null
  }

  cancel(reason = new TypeError('Stream was cancelled')) {
    const stream = this._stream._stream

    if (stream.destroyed) return Promise.resolve()

    return new Promise((resolve) =>
      stream.once('close', resolve).once('error', noop).destroy(reason)
    )
  }
}

// https://streams.spec.whatwg.org/#readablestreamdefaultcontroller
exports.ReadableStreamDefaultController = class ReadableStreamDefaultController {
  constructor(stream) {
    this._stream = stream
  }

  get desiredSize() {
    const stream = this._stream._stream

    return stream._readableState.highWaterMark - stream._readableState.buffered
  }

  enqueue(data) {
    this._stream._stream.push(data)
  }

  close() {
    this._stream._stream.push(null)
  }

  error(err) {
    this._stream._stream.destroy(err)
  }
}

// https://streams.spec.whatwg.org/#readablestream
class ReadableStream {
  static get [readableKind]() {
    return 0 // Compatibility version
  }

  static from(iterable) {
    return new ReadableStream(Readable.from(iterable))
  }

  constructor(underlyingSource = {}, queuingStrategy) {
    if (isStreamx(underlyingSource)) {
      this._stream = underlyingSource
    } else {
      if (queuingStrategy === undefined) {
        queuingStrategy = new exports.CountQueuingStrategy()
      }

      const { start, pull, cancel } = underlyingSource
      const { highWaterMark = 1, size = defaultSize } = queuingStrategy

      this._stream = new Readable({ highWaterMark, byteLength: size })

      const controller = new exports.ReadableStreamDefaultController(this)

      try {
        let starting = Promise.resolve()

        if (start) starting = forwardError(start.call(this, controller), controller)

        if (pull) {
          this._stream._read = this._read.bind(this, starting, pull.bind(this, controller))
        }

        if (cancel) {
          this._stream.once('error', cancel.bind(this))
        }
      } catch (err) {
        controller.error(err)
      }
    }

    this._reader = null
  }

  get [readableKind]() {
    return ReadableStream[readableKind]
  }

  get locked() {
    return this._reader !== null
  }

  getReader() {
    if (this.locked) throw new TypeError('Stream is locked')

    this._reader = new exports.ReadableStreamDefaultReader(this)

    return this._reader
  }

  cancel(reason = new TypeError('Stream was cancelled')) {
    const stream = this._stream

    if (stream.destroyed) return Promise.resolve()

    if (this.locked) return Promise.reject(new TypeError('Stream is locked'))

    return new Promise((resolve) =>
      stream.once('close', resolve).once('error', noop).destroy(reason)
    )
  }

  tee() {
    const [a, b] = tee(this._stream)

    return [new ReadableStream(a), new ReadableStream(b)]
  }

  pipeTo(destination) {
    return new Promise((resolve, reject) =>
      this._stream.pipe(destination._stream, (err) => {
        err ? reject(err) : resolve()
      })
    )
  }

  [Symbol.asyncIterator]() {
    return this._stream[Symbol.asyncIterator]()
  }

  _releaseLock() {
    this._reader = null
  }

  async _read(starting, pull, cb) {
    await starting

    let err = null
    try {
      await pull()
    } catch (e) {
      err = e
    }
    cb(err)
  }
}

function defaultSize() {
  return 1
}

exports.ReadableStream = ReadableStream

// https://streams.spec.whatwg.org/#countqueuingstrategy
exports.CountQueuingStrategy = class CountQueuingStrategy {
  constructor(opts = {}) {
    const { highWaterMark = 1 } = opts

    this.highWaterMark = highWaterMark
  }

  size(chunk) {
    return 1
  }
}

// https://streams.spec.whatwg.org/#bytelengthqueuingstrategy
exports.ByteLengthQueuingStrategy = class ByteLengthQueuingStrategy {
  constructor(opts = {}) {
    const { highWaterMark = 16384 } = opts

    this.highWaterMark = highWaterMark
  }

  size(chunk) {
    return chunk.byteLength
  }
}

exports.isReadableStream = function isReadableStream(value) {
  if (value instanceof ReadableStream) return true

  return (
    typeof value === 'object' &&
    value !== null &&
    value[readableKind] === ReadableStream[readableKind]
  )
}

// https://streams.spec.whatwg.org/#readablestream-errored
exports.isReadableStreamErrored = function isReadableStreamErrored(stream) {
  return getStreamError(stream._stream) !== null
}

// https://streams.spec.whatwg.org/#is-readable-stream-disturbed
exports.isReadableStreamDisturbed = function isReadableStreamDisturbed(stream) {
  return isDisturbed(stream._stream)
}

// https://streams.spec.whatwg.org/#writablestreamdefaultwriter
exports.WritableStreamDefaultWriter = class WritableStreamDefaultWriter {
  constructor(stream) {
    this._stream = stream
    this._stream._stream.once('close', onclose).once('error', onerror)

    const closed = Promise.withResolvers()

    // Avoid unhandled exceptions
    closed.promise.catch(noop)

    this._closed = closed

    function onclose() {
      closed.resolve()
    }

    function onerror(err) {
      closed.reject(err)
    }
  }

  get desiredSize() {
    const stream = this._stream._stream

    return stream._writableState.highWaterMark - stream._writableState.buffered
  }

  get closed() {
    return this._closed.promise
  }

  get ready() {
    const stream = this._stream._stream

    if (getStreamError(stream)) return Promise.reject()

    return Writable.drained(stream).then()
  }

  async write(chunk) {
    const stream = this._stream._stream

    let err = getStreamError(stream)
    if (err) return Promise.reject(err)

    stream.write(chunk)

    await Writable.drained(stream)

    err = getStreamError(stream)
    if (err) return Promise.reject(err)
  }

  releaseLock() {
    this._closed.reject(new TypeError('Writer was released'))
    this._stream._releaseLock()
    this._stream = null
  }

  close() {
    const stream = this._stream._stream

    if (stream.destroyed) return Promise.resolve()

    return new Promise((resolve) => stream.once('close', resolve).end())
  }

  abort(reason = new TypeError('Stream was aborted')) {
    const stream = this._stream._stream

    if (stream.destroyed) return Promise.resolve()

    return new Promise((resolve) => stream.once('close', resolve).destroy(reason))
  }
}

// https://streams.spec.whatwg.org/#writablestreamdefaultcontroller
exports.WritableStreamDefaultController = class WritableStreamDefaultController {
  constructor(stream) {
    this._stream = stream
  }

  error(err) {
    this._stream._stream.destroy(err)
  }
}

// https://streams.spec.whatwg.org/#writablestream
class WritableStream {
  static get [writableKind]() {
    return 0 // Compatibility version
  }

  constructor(underlyingSink = {}, queuingStrategy = {}) {
    if (isStreamx(underlyingSink)) {
      this._stream = underlyingSink
    } else {
      if (queuingStrategy === undefined) {
        queuingStrategy = new exports.CountQueuingStrategy()
      }

      const { start, write, close, abort } = underlyingSink
      const { highWaterMark = 1, size = defaultSize } = queuingStrategy

      this._stream = new Writable({ highWaterMark, byteLength: size })

      const controller = new exports.WritableStreamDefaultController(this)

      this._controller = controller

      try {
        let starting = Promise.resolve()

        if (start) starting = forwardError(start.call(this, controller), controller)

        if (write) {
          this._stream._write = this._write.bind(this, starting, write.bind(this))
        }

        if (close) {
          this._stream._destroy = this._destroy.bind(this, close.call(this))
        }

        if (abort) {
          this._stream.once('error', abort.bind(this))
        }
      } catch (err) {
        controller.error(err)
      }
    }

    this._writer = null
  }

  get [writableKind]() {
    return WritableStream[writableKind]
  }

  get locked() {
    return this._writer !== null
  }

  getWriter() {
    if (this.locked) throw new TypeError('Stream is locked')

    this._writer = new exports.WritableStreamDefaultWriter(this)

    return this._writer
  }

  abort(reason = new TypeError('Stream was aborted')) {
    if (this._stream.destroyed) return Promise.resolve()

    if (this.locked) return Promise.reject(new TypeError('Stream is locked'))

    return new Promise((resolve) => this._stream.once('close', resolve).destroy(reason))
  }

  close() {
    if (this._stream.destroyed) return Promise.resolve()

    if (this.locked) return Promise.reject(new TypeError('Stream is locked'))

    return new Promise((resolve) => this._stream.once('close', resolve).end())
  }

  _releaseLock() {
    this._writer = null
  }

  async _write(starting, write, data, cb) {
    await starting

    let err = null
    try {
      await write(data, this._controller)
    } catch (e) {
      err = e
    }
    cb(err)
  }

  async _destroy(closing, cb) {
    let err = null
    try {
      await closing
    } catch (e) {
      err = e
    }
    cb(err)
  }
}

exports.WritableStream = WritableStream

exports.isWritableStream = function isWritableStream(value) {
  if (value instanceof WritableStream) return true

  return (
    typeof value === 'object' &&
    value !== null &&
    value[writableKind] === WritableStream[writableKind]
  )
}

// https://streams.spec.whatwg.org/#transformstreamdefaultcontroller
exports.TransformStreamDefaultController = class TransformStreamDefaultController {
  constructor(stream) {
    this._stream = stream
  }

  get desiredSize() {
    const stream = this._stream._stream

    return stream._readableState.highWaterMark - stream._readableState.buffered
  }

  enqueue(data) {
    this._stream._stream.push(data)
  }

  error(err) {
    this._stream._stream.destroy(err)
  }

  terminate() {
    const stream = this._stream._stream

    stream.push(null)
    stream.destroy(new TypeError('Stream has been terminated'))
  }
}

// https://streams.spec.whatwg.org/#transformstream
class TransformStream {
  static get [transformKind]() {
    return 0 // Compatibility version
  }

  constructor(transformer = {}, writableStrategy = {}, readableStrategy = {}) {
    if (isStreamx(transformer)) {
      this._stream = transformer
    } else {
      const { start, transform, flush } = transformer

      this._stream = new Transform({ ...writableStrategy, ...readableStrategy })

      const controller = new exports.TransformStreamDefaultController(this)

      this._controller = controller

      try {
        let starting = Promise.resolve()

        if (start) starting = forwardError(start.call(this, controller), controller)

        if (transform) {
          this._stream._transform = this._transform.bind(this, starting, transform.bind(this))
        }

        if (flush) {
          this._stream._flush = this._flush.bind(this, flush.call(this, this._controller))
        }
      } catch (err) {
        controller.error(err)
      }
    }

    this._writable = new WritableStream(this._stream)
    this._readable = new ReadableStream(this._stream)
  }

  get [transformKind]() {
    return TransformStream[transformKind]
  }

  get writable() {
    return this._writable
  }

  get readable() {
    return this._readable
  }

  async _transform(starting, transform, data, cb) {
    await starting

    let err = null
    try {
      await transform(data, this._controller)
    } catch (e) {
      err = e
    }
    cb(err)
  }

  async _flush(flush, cb) {
    let err = null
    try {
      await flush
    } catch (e) {
      err = e
    }
    cb(err)
  }
}

exports.TransformStream = TransformStream

exports.isTransformStream = function isTransformStream(value) {
  if (value instanceof TransformStream) return true

  return (
    typeof value === 'object' &&
    value !== null &&
    value[transformKind] === TransformStream[transformKind]
  )
}

async function forwardError(promise, controller) {
  try {
    await promise
  } catch (err) {
    controller.error(err)
  }
}

function noop() {}
module.exports = require.addon()
const binding = require('./binding')

const t = binding.constants

class Type {
  constructor(type) {
    this._type = type
  }

  isUndefined() {
    return this._type === t.UNDEFINED
  }

  isNull() {
    return this._type === t.NULL
  }

  isBoolean() {
    return this._type === t.BOOLEAN
  }

  isNumber() {
    return (this._type & 0xff) === t.NUMBER
  }

  isInt32() {
    return (this._type & (0xff | t.INT32)) === (t.NUMBER | t.INT32)
  }

  isUint32() {
    return (this._type & (0xff | t.UINT32)) === (t.NUMBER | t.UINT32)
  }

  isString() {
    return this._type === t.STRING
  }

  isSymbol() {
    return this._type === t.SYMBOL
  }

  isObject() {
    return (this._type & 0xff) === t.OBJECT
  }

  isArray() {
    return this._type === (t.OBJECT | t.ARRAY)
  }

  isArguments() {
    return this._type === (t.OBJECT | t.ARGUMENTS)
  }

  isDate() {
    return this._type === (t.OBJECT | t.DATE)
  }

  isRegExp() {
    return this._type === (t.OBJECT | t.REGEXP)
  }

  isError() {
    return this._type === (t.OBJECT | t.ERROR)
  }

  isPromise() {
    return this._type === (t.OBJECT | t.PROMISE)
  }

  isProxy() {
    return this._type === (t.OBJECT | t.PROXY)
  }

  isGenerator() {
    return this._type === (t.OBJECT | t.GENERATOR)
  }

  isMap() {
    return this._type === (t.OBJECT | t.MAP)
  }

  isSet() {
    return this._type === (t.OBJECT | t.SET)
  }

  isWeakMap() {
    return this._type === (t.OBJECT | t.WEAK_MAP)
  }

  isWeakSet() {
    return this._type === (t.OBJECT | t.WEAK_SET)
  }

  isWeakRef() {
    return this._type === (t.OBJECT | t.WEAK_REF)
  }

  isArrayBuffer() {
    return this._type === (t.OBJECT | t.ARRAYBUFFER)
  }

  isSharedArrayBuffer() {
    return this._type === (t.OBJECT | t.SHAREDARRAYBUFFER)
  }

  isTypedArray() {
    return (this._type & 0xffff) === (t.OBJECT | t.TYPEDARRAY)
  }

  isInt8Array() {
    return this._type === (t.OBJECT | t.TYPEDARRAY | t.INT8ARRAY)
  }

  isUint8Array() {
    return this._type === (t.OBJECT | t.TYPEDARRAY | t.UINT8ARRAY)
  }

  isUint8ClampedArray() {
    return this._type === (t.OBJECT | t.TYPEDARRAY | t.UINT8CLAMPEDARRAY)
  }

  isInt16Array() {
    return this._type === (t.OBJECT | t.TYPEDARRAY | t.INT16ARRAY)
  }

  isUint16Array() {
    return this._type === (t.OBJECT | t.TYPEDARRAY | t.UINT16ARRAY)
  }

  isInt32Array() {
    return this._type === (t.OBJECT | t.TYPEDARRAY | t.INT32ARRAY)
  }

  isUint32Array() {
    return this._type === (t.OBJECT | t.TYPEDARRAY | t.UINT32ARRAY)
  }

  isFloat16Array() {
    return this._type === (t.OBJECT | t.TYPEDARRAY | t.FLOAT16ARRAY)
  }

  isFloat32Array() {
    return this._type === (t.OBJECT | t.TYPEDARRAY | t.FLOAT32ARRAY)
  }

  isFloat64Array() {
    return this._type === (t.OBJECT | t.TYPEDARRAY | t.FLOAT64ARRAY)
  }

  isBigInt64Array() {
    return this._type === (t.OBJECT | t.TYPEDARRAY | t.BIGINT64ARRAY)
  }

  isBigUint64Array() {
    return this._type === (t.OBJECT | t.TYPEDARRAY | t.BIGUINT64ARRAY)
  }

  isDataView() {
    return this._type === (t.OBJECT | t.DATAVIEW)
  }

  isModuleNamespace() {
    return this._type === (t.OBJECT | t.MODULE_NAMESPACE)
  }

  isFunction() {
    return (this._type & 0xff) === t.FUNCTION
  }

  isAsyncFunction() {
    return (
      (this._type & (0xff | t.ASYNC_FUNCTION)) ===
      (t.FUNCTION | t.ASYNC_FUNCTION)
    )
  }

  isGeneratorFunction() {
    return (
      (this._type & (0xff | t.GENERATOR_FUNCTION)) ===
      (t.FUNCTION | t.GENERATOR_FUNCTION)
    )
  }

  isExternal() {
    return this._type === t.EXTERNAL
  }

  isBigInt() {
    return this._type === t.BIGINT
  }
}

module.exports = exports = function type(value) {
  switch (typeof value) {
    case 'undefined':
      return new Type(t.UNDEFINED)
    case 'boolean':
      return new Type(t.BOOLEAN)
    case 'number':
      return new Type(
        Number.isSafeInteger(value) ? binding.type(value) : t.NUMBER
      )
    case 'string':
      return new Type(t.STRING)
    case 'symbol':
      return new Type(t.SYMBOL)
    case 'object':
      return new Type(value === null ? t.NULL : binding.type(value))
    case 'function':
      return new Type(binding.type(value))
    case 'bigint':
      return new Type(t.BIGINT)
  }
}

exports.createTag = function createTag(...components) {
  const tag = new Uint32Array(4)
  for (let i = 0; i < 4; i++) tag[i] = components[i] || 0
  return tag
}

exports.addTag = function addTag(object, tag) {
  binding.addTag(object, tag)
}

exports.checkTag = function checkTag(object, tag) {
  return binding.checkTag(object, tag)
}
{
  "name": "bare-type",
  "version": "1.1.0",
  "description": "Cross-realm type predicates for Bare",
  "exports": {
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    },
    "./package": "./package.json"
  },
  "files": [
    "index.js",
    "index.d.ts",
    "binding.c",
    "binding.js",
    "CMakeLists.txt",
    "prebuilds"
  ],
  "addon": true,
  "scripts": {
    "test": "prettier . --check && bare test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-type.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-type/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-type#readme",
  "engines": {
    "bare": ">=1.2.0"
  },
  "devDependencies": {
    "brittle": "^3.4.0",
    "cmake-bare": "^1.1.6",
    "prettier": "^3.4.1",
    "prettier-config-standard": "^7.0.0"
  }
}
module.exports = require.addon()
const path = require('bare-path')
const binding = require('./binding')
const errors = require('./lib/errors')
const URLSearchParams = require('./lib/url-search-params')

const kind = Symbol.for('bare.url.kind')

const isWindows = Bare.platform === 'win32'

class URL {
  static get [kind]() {
    return 0 // Compatibility version
  }

  constructor(input, base, opts = {}) {
    if (arguments.length === 0) throw errors.INVALID_URL()

    input = String(input)

    if (base !== undefined) base = String(base)

    this._components = new Uint32Array(8)

    this._parse(input, base, opts.throw !== false)

    if (this._href) this._params = new URLSearchParams(this.search, this)
  }

  get [kind]() {
    return URL[kind]
  }

  // https://url.spec.whatwg.org/#dom-url-href

  get href() {
    return this._href
  }

  set href(value) {
    this._update(value)

    this._params._parse(this.search)
  }

  // https://url.spec.whatwg.org/#dom-url-protocol

  get protocol() {
    return this._slice(0, this._components[0]) + ':'
  }

  set protocol(value) {
    this._update(this._replace(value.replace(/:+$/, ''), 0, this._components[0]))
  }

  // https://url.spec.whatwg.org/#dom-url-username

  get username() {
    return this._slice(this._components[0] + 3 /* :// */, this._components[1])
  }

  set username(value) {
    if (cannotHaveCredentialsOrPort(this)) {
      return
    }

    if (this.username === '') value += '@'

    this._update(this._replace(value, this._components[0] + 3 /* :// */, this._components[1]))
  }

  // https://url.spec.whatwg.org/#dom-url-password

  get password() {
    return this._href.slice(this._components[1] + 1 /* : */, this._components[2] - 1 /* @ */)
  }

  set password(value) {
    if (cannotHaveCredentialsOrPort(this)) {
      return
    }

    let start = this._components[1] + 1 /* : */
    let end = this._components[2] - 1 /* @ */

    if (this.password === '') {
      value = ':' + value
      start--
    }

    if (this.username === '') {
      value += '@'
      end++
    }

    this._update(this._replace(value, start, end))
  }

  // https://url.spec.whatwg.org/#dom-url-host

  get host() {
    return this._slice(this._components[2], this._components[5])
  }

  set host(value) {
    if (hasOpaquePath(this)) {
      return
    }

    this._update(
      this._replace(value, this._components[2], this._components[value.includes(':') ? 5 : 3])
    )
  }

  // https://url.spec.whatwg.org/#dom-url-hostname

  get hostname() {
    return this._slice(this._components[2], this._components[3])
  }

  set hostname(value) {
    if (hasOpaquePath(this)) {
      return
    }

    this._update(this._replace(value, this._components[2], this._components[3]))
  }

  // https://url.spec.whatwg.org/#dom-url-port

  get port() {
    return this._slice(this._components[3] + 1 /* : */, this._components[5])
  }

  set port(value) {
    if (cannotHaveCredentialsOrPort(this)) {
      return
    }

    let start = this._components[3] + 1 /* : */

    if (this.port === '') {
      value = ':' + value
      start--
    }

    this._update(this._replace(value, start, this._components[5]))
  }

  // https://url.spec.whatwg.org/#dom-url-pathname

  get pathname() {
    return this._slice(this._components[5], this._components[6] - 1 /* ? */)
  }

  set pathname(value) {
    if (hasOpaquePath(this)) {
      return
    }

    if (value[0] !== '/' && value[0] !== '\\') {
      value = '/' + value
    }

    this._update(this._replace(value, this._components[5], this._components[6] - 1 /* ? */))
  }

  // https://url.spec.whatwg.org/#dom-url-search

  get search() {
    return this._slice(this._components[6] - 1 /* ? */, this._components[7] - 1 /* # */)
  }

  set search(value) {
    if (value && value[0] !== '?') value = '?' + value

    this._update(
      this._replace(value, this._components[6] - 1 /* ? */, this._components[7] - 1 /* # */)
    )

    this._params._parse(this.search)
  }

  // https://url.spec.whatwg.org/#dom-url-searchparams

  get searchParams() {
    return this._params
  }

  // https://url.spec.whatwg.org/#dom-url-hash

  get hash() {
    return this._slice(this._components[7] - 1 /* # */)
  }

  set hash(value) {
    if (value && value[0] !== '#') value = '#' + value

    this._update(this._replace(value, this._components[7] - 1 /* # */))
  }

  toString() {
    return this._href
  }

  toJSON() {
    return this._href
  }

  [Symbol.for('bare.inspect')]() {
    return {
      __proto__: { constructor: URL },

      href: this.href,
      protocol: this.protocol,
      username: this.username,
      password: this.password,
      host: this.host,
      hostname: this.hostname,
      port: this.port,
      pathname: this.pathname,
      search: this.search,
      searchParams: this.searchParams,
      hash: this.hash
    }
  }

  _slice(start, end = this._href.length) {
    return this._href.slice(start, end)
  }

  _replace(replacement, start, end = this._href.length) {
    return this._slice(0, start) + replacement + this._slice(end)
  }

  _parse(input, base, shouldThrow) {
    try {
      this._href = binding.parse(
        String(input),
        base ? String(base) : null,
        this._components,
        shouldThrow
      )
    } catch (err) {
      if (err instanceof TypeError) throw err

      throw errors.INVALID_URL(`Invalid URL '${input}'`, input)
    }
  }

  _update(input) {
    try {
      this._parse(input, null, true)
    } catch (err) {
      if (err instanceof TypeError) throw err
    }
  }
}

module.exports = exports = URL

// https://url.spec.whatwg.org/#url-opaque-path
function hasOpaquePath(url) {
  return url.pathname[0] !== '/'
}

// https://url.spec.whatwg.org/#cannot-have-a-username-password-port
function cannotHaveCredentialsOrPort(url) {
  return url.hostname === '' || url.protocol === 'file:'
}

exports.URL = URL
exports.URLSearchParams = URLSearchParams

exports.errors = errors

exports.isURL = function isURL(value) {
  if (value instanceof URL) return true

  return typeof value === 'object' && value !== null && value[kind] === URL[kind]
}

exports.isURLSearchParams = URLSearchParams.isURLSearchParams

// https://url.spec.whatwg.org/#dom-url-parse
exports.parse = function parse(input, base) {
  const url = new URL(input, base, { throw: false })
  return url._href ? url : null
}

// https://url.spec.whatwg.org/#dom-url-canparse
exports.canParse = function canParse(input, base) {
  return binding.canParse(String(input), base ? String(base) : null)
}

exports.fileURLToPath = function fileURLToPath(url) {
  if (typeof url === 'string') {
    url = new URL(url)
  }

  if (url.protocol !== 'file:') {
    throw errors.INVALID_URL_SCHEME('The URL must use the file: protocol')
  }

  if (isWindows) {
    if (/%2f|%5c/i.test(url.pathname)) {
      throw errors.INVALID_FILE_URL_PATH(
        'The file: URL path must not include encoded \\ or / characters'
      )
    }
  } else {
    if (url.hostname) {
      throw errors.INVALID_FILE_URL_HOST("The file: URL host must be 'localhost' or empty")
    }

    if (/%2f/i.test(url.pathname)) {
      throw errors.INVALID_FILE_URL_PATH('The file: URL path must not include encoded / characters')
    }
  }

  if (/%00/i.test(url.pathname)) {
    throw errors.INVALID_FILE_URL_PATH('The file: URL path must not include encoded NUL characters')
  }

  const pathname = path.normalize(decodeURIComponent(url.pathname))

  if (isWindows) {
    if (url.hostname) return '\\\\' + url.hostname + pathname

    const letter = pathname.charCodeAt(1) | 0x20

    if (letter < 0x61 /* a */ || letter > 0x7a /* z */ || pathname.charCodeAt(2) !== 0x3a /* : */) {
      throw errors.INVALID_FILE_URL_PATH('The file: URL path must be absolute')
    }

    return pathname.slice(1)
  }

  return pathname
}

exports.pathToFileURL = function pathToFileURL(pathname) {
  let resolved = path.resolve(pathname)

  if (pathname[pathname.length - 1] === '/') {
    resolved += '/'
  } else if (isWindows && pathname[pathname.length - 1] === '\\') {
    resolved += '\\'
  }

  resolved = resolved
    .replaceAll('%', '%25') // Must be first
    .replaceAll('#', '%23')
    .replaceAll('?', '%3f')
    .replaceAll('\n', '%0a')
    .replaceAll('\r', '%0d')
    .replaceAll('\t', '%09')

  if (!isWindows) {
    resolved = resolved.replaceAll('\\', '%5c')
  }

  return new URL('file:' + resolved)
}

exports.format = function format(parts) {
  const { protocol, auth, host, hostname, port, pathname, search, query, hash, slashes } = parts

  let result = ''

  if (typeof protocol === 'string') {
    result += protocol

    if (protocol[protocol.length - 1] !== ':') {
      result += ':'
    }

    if (slashes === true || /https?|ftp|gopher|file/.test(protocol)) {
      result += '//'
    }
  }

  if (typeof auth === 'string') {
    if (host || hostname) result += auth + '@'
  }

  if (typeof host === 'string') result += host
  else {
    result += hostname

    if (port) result += ':' + port
  }

  if (typeof pathname === 'string' && pathname !== '') {
    if (pathname[0] !== '/') result += '/'
    result += pathname
  }

  if (typeof search === 'string') {
    if (search[0] !== '?') result += '?'
    result += search
  } else if (typeof query === 'object' && query !== null) {
    result += '?' + new URLSearchParams(query)
  }

  if (typeof hash === 'string') {
    if (hash[0] !== '#') result += '#'
    result += hash
  }

  return result
}
module.exports = class URLError extends Error {
  constructor(msg, fn = URLError, code = fn.name) {
    super(`${code}: ${msg}`)

    this.code = code

    if (Error.captureStackTrace) Error.captureStackTrace(this, fn)
  }

  get name() {
    return 'URLError'
  }

  static INVALID_URL(msg, input) {
    const err = new URLError(msg, URLError.INVALID_URL)

    err.input = input

    return err
  }

  static INVALID_URL_SCHEME(msg = 'Invalid URL') {
    return new URLError(msg, URLError.INVALID_URL_SCHEME)
  }

  static INVALID_FILE_URL_HOST(msg = 'Invalid file: URL host') {
    return new URLError(msg, URLError.INVALID_FILE_URL_HOST)
  }

  static INVALID_FILE_URL_PATH(msg = 'Invalid file: URL path') {
    return new URLError(msg, URLError.INVALID_FILE_URL_PATH)
  }
}
const kind = Symbol.for('bare.url.search-params.kind')

class URLSearchParams {
  static _urls = new WeakMap()

  static get [kind]() {
    return 0 // Compatibility version
  }

  // https://url.spec.whatwg.org/#dom-urlsearchparams-urlsearchparams
  constructor(init, url = null) {
    this._params = new Map()

    if (url) URLSearchParams._urls.set(this, url)

    if (typeof init === 'string') {
      this._parse(init)
    } else if (init) {
      for (const [name, value] of typeof init[Symbol.iterator] === 'function'
        ? init
        : Object.entries(init)) {
        this.append(name, value)
      }
    }
  }

  get [kind]() {
    return URLSearchParams[kind]
  }

  // https://url.spec.whatwg.org/#dom-urlsearchparams-size
  get size() {
    return this._params.length
  }

  // https://url.spec.whatwg.org/#dom-urlsearchparams-append
  append(name, value = null) {
    if (value === null) return

    let list = this._params.get(name)

    if (list === undefined) {
      list = []
      this._params.set(name, list)
    }

    list.push(value)

    this._update()
  }

  // https://url.spec.whatwg.org/#dom-urlsearchparams-delete
  delete(name, value = null) {
    if (value === null) this._params.delete(name)
    else {
      let list = this._params.get(name)

      if (list === undefined) return

      list = list.filter((found) => found !== value)

      if (list.length === 0) this._params.delete(name)
      else this._params.set(name, list)
    }

    this._update()
  }

  // https://url.spec.whatwg.org/#dom-urlsearchparams-get
  get(name) {
    const list = this._params.get(name)

    if (list === undefined) return null

    return list[0]
  }

  // https://url.spec.whatwg.org/#dom-urlsearchparams-getall
  getAll(name) {
    const list = this._params.get(name)

    if (list === undefined) return []

    return Array.from(list)
  }

  // https://url.spec.whatwg.org/#dom-urlsearchparams-has
  has(name, value = null) {
    const list = this._params.get(name)

    if (list === undefined) return false

    if (value === null) return true

    return list.includes(value)
  }

  // https://url.spec.whatwg.org/#dom-urlsearchparams-set
  set(name, value = null) {
    if (value === null) this._params.delete(name)
    else this._params.set(name, [value])

    this._update()
  }

  toString() {
    return this._serialize()
  }

  toJSON() {
    return [...this]
  }

  *[Symbol.iterator]() {
    for (const [name, values] of this._params) {
      for (const value of values) yield [name, value]
    }
  }

  [Symbol.for('bare.inspect')]() {
    const object = {
      __proto__: { constructor: URLSearchParams }
    }

    for (const [name, values] of this._params) {
      if (values.length === 1) object[name] = values[0]
      else object[name] = values
    }

    return object
  }

  // https://url.spec.whatwg.org/#concept-urlsearchparams-update
  _update() {
    const url = URLSearchParams._urls.get(this)

    if (url === undefined) return

    url.search = this._serialize()
  }

  // https://url.spec.whatwg.org/#concept-urlencoded-parser
  _parse(input) {
    if (input[0] === '?') input = input.substring(1)

    this._params = new Map()

    for (let sequence of input.split('&')) {
      if (sequence.length === 0) continue

      sequence = sequence.replace(/\+/g, ' ')

      let i = sequence.indexOf('=')
      if (i === -1) i = sequence.length

      const name = decodeURIComponent(sequence.substring(0, i))
      const value = decodeURIComponent(sequence.substring(i + 1, sequence.length))

      let list = this._params.get(name)

      if (list === undefined) {
        list = []
        this._params.set(name, list)
      }

      list.push(value)
    }
  }

  // https://url.spec.whatwg.org/#concept-urlencoded-serializer
  _serialize() {
    let output = ''

    for (let [name, values] of this._params) {
      name = encodeURIComponent(name)

      for (const value of values) {
        if (output) output += '&'

        output += name + '=' + encodeURIComponent(value)
      }
    }

    return output
  }
}

module.exports = exports = URLSearchParams

exports.isURLSearchParams = function isURLSearchParams(value) {
  if (value instanceof URLSearchParams) return true

  return typeof value === 'object' && value !== null && value[kind] === URLSearchParams[kind]
}
{
  "name": "bare-url",
  "version": "2.4.5",
  "description": "WHATWG URL implementation for JavaScript",
  "exports": {
    "./package": "./package.json",
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    },
    "./global": {
      "types": "./global.d.ts",
      "default": "./global.js"
    }
  },
  "files": [
    "index.js",
    "index.d.ts",
    "global.js",
    "global.d.ts",
    "binding.c",
    "binding.js",
    "CMakeLists.txt",
    "lib",
    "prebuilds"
  ],
  "addon": true,
  "scripts": {
    "format": "prettier --write . && lunte --fix",
    "lint": "prettier --check . && lunte",
    "test": "brittle-bare --coverage test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-url.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-url/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-url",
  "dependencies": {
    "bare-path": "^3.0.0"
  },
  "devDependencies": {
    "brittle": "^3.3.2",
    "cmake-bare": "^1.1.6",
    "cmake-fetch": "^1.0.0",
    "lunte": "^1.6.0",
    "prettier": "^3.3.3",
    "prettier-config-holepunch": "^2.0.0"
  }
}
const b4a = require('b4a')

module.exports = codecs

codecs.ascii = createString('ascii')
codecs.utf8 = createString('utf-8')
codecs.hex = createString('hex')
codecs.base64 = createString('base64')
codecs.ucs2 = createString('ucs2')
codecs.utf16le = createString('utf16le')
codecs.ndjson = createJSON(true)
codecs.json = createJSON(false)
codecs.binary = {
  name: 'binary',
  encode: function encodeBinary (obj) {
    return typeof obj === 'string'
      ? b4a.from(obj, 'utf-8')
      : b4a.toBuffer(obj)
  },
  decode: function decodeBinary (buf) {
    return b4a.toBuffer(buf)
  }
}

function isCompactEncoding (c) {
  return !!(c.encode && c.decode && c.preencode)
}

function fromCompactEncoding (c) {
  return {
    name: 'compact-encoding',
    encode: function encodeWithCompact (value) {
      const state = { start: 0, end: 0, buffer: null, cache: null }
      c.preencode(state, value)
      state.buffer = b4a.allocUnsafe(state.end)
      c.encode(state, value)
      return state.buffer
    },
    decode: function decodeWithCompact (buffer) {
      return c.decode({ start: 0, end: buffer.byteLength, buffer, cache: null })
    }
  }
}

function codecs (fmt, fallback) {
  if (typeof fmt === 'object' && fmt) {
    return isCompactEncoding(fmt) ? fromCompactEncoding(fmt) : fmt
  }

  switch (fmt) {
    case 'ndjson': return codecs.ndjson
    case 'json': return codecs.json
    case 'ascii': return codecs.ascii
    case 'utf-8':
    case 'utf8': return codecs.utf8
    case 'hex': return codecs.hex
    case 'base64': return codecs.base64
    case 'ucs-2':
    case 'ucs2': return codecs.ucs2
    case 'utf16-le':
    case 'utf16le': return codecs.utf16le
  }

  return fallback !== undefined ? fallback : codecs.binary
}

function createJSON (newline) {
  return {
    name: newline ? 'ndjson' : 'json',
    encode: newline ? encodeNDJSON : encodeJSON,
    decode: function decodeJSON (buf) {
      return JSON.parse(b4a.toString(buf))
    }
  }

  function encodeJSON (val) {
    return b4a.from(JSON.stringify(val))
  }

  function encodeNDJSON (val) {
    return b4a.from(JSON.stringify(val) + '\n')
  }
}

function createString (type) {
  return {
    name: type,
    encode: function encodeString (val) {
      if (typeof val !== 'string') val = val.toString()
      return b4a.from(val, type)
    },
    decode: function decodeString (buf) {
      return b4a.toString(buf, type)
    }
  }
}
{
  "name": "codecs",
  "version": "3.1.0",
  "description": "Create an binary encoder/decoder for json, utf-8 or custom types",
  "main": "index.js",
  "dependencies": {
    "b4a": "^1.6.3"
  },
  "devDependencies": {
    "brittle": "^3.2.1",
    "compact-encoding": "^2.11.0",
    "standard": "^17.0.0"
  },
  "scripts": {
    "test": "standard && brittle test.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/mafintosh/codecs.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/mafintosh/codecs/issues"
  },
  "homepage": "https://github.com/mafintosh/codecs"
}
const LE = (exports.LE =
  new Uint8Array(new Uint16Array([0xff]).buffer)[0] === 0xff)

exports.BE = !LE
const b4a = require('b4a')

const { BE } = require('./endian')

exports.state = function (start = 0, end = 0, buffer = null) {
  return { start, end, buffer }
}

const raw = (exports.raw = require('./raw'))

const uint = (exports.uint = {
  preencode(state, n) {
    state.end += n <= 0xfc ? 1 : n <= 0xffff ? 3 : n <= 0xffffffff ? 5 : 9
  },
  encode(state, n) {
    if (n <= 0xfc) uint8.encode(state, n)
    else if (n <= 0xffff) {
      state.buffer[state.start++] = 0xfd
      uint16.encode(state, n)
    } else if (n <= 0xffffffff) {
      state.buffer[state.start++] = 0xfe
      uint32.encode(state, n)
    } else {
      state.buffer[state.start++] = 0xff
      uint64.encode(state, n)
    }
  },
  decode(state) {
    const a = uint8.decode(state)
    if (a <= 0xfc) return a
    if (a === 0xfd) return uint16.decode(state)
    if (a === 0xfe) return uint32.decode(state)
    return uint64.decode(state)
  }
})

const uint8 = (exports.uint8 = {
  preencode(state, n) {
    state.end += 1
  },
  encode(state, n) {
    validateUint(n)
    state.buffer[state.start++] = n
  },
  decode(state) {
    if (state.start >= state.end) throw new Error('Out of bounds')
    return state.buffer[state.start++]
  }
})

const uint16 = (exports.uint16 = {
  preencode(state, n) {
    state.end += 2
  },
  encode(state, n) {
    validateUint(n)
    state.buffer[state.start++] = n
    state.buffer[state.start++] = n >>> 8
  },
  decode(state) {
    if (state.end - state.start < 2) throw new Error('Out of bounds')
    return state.buffer[state.start++] + state.buffer[state.start++] * 0x100
  }
})

const uint24 = (exports.uint24 = {
  preencode(state, n) {
    state.end += 3
  },
  encode(state, n) {
    validateUint(n)
    state.buffer[state.start++] = n
    state.buffer[state.start++] = n >>> 8
    state.buffer[state.start++] = n >>> 16
  },
  decode(state) {
    if (state.end - state.start < 3) throw new Error('Out of bounds')
    return (
      state.buffer[state.start++] +
      state.buffer[state.start++] * 0x100 +
      state.buffer[state.start++] * 0x10000
    )
  }
})

const uint32 = (exports.uint32 = {
  preencode(state, n) {
    state.end += 4
  },
  encode(state, n) {
    validateUint(n)
    state.buffer[state.start++] = n
    state.buffer[state.start++] = n >>> 8
    state.buffer[state.start++] = n >>> 16
    state.buffer[state.start++] = n >>> 24
  },
  decode(state) {
    if (state.end - state.start < 4) throw new Error('Out of bounds')
    return (
      state.buffer[state.start++] +
      state.buffer[state.start++] * 0x100 +
      state.buffer[state.start++] * 0x10000 +
      state.buffer[state.start++] * 0x1000000
    )
  }
})

const uint32be = (exports.uint32be = {
  preencode(state, n) {
    state.end += 4
  },
  encode(state, n) {
    validateUint(n)
    state.buffer[state.start++] = n >>> 24
    state.buffer[state.start++] = n >>> 16
    state.buffer[state.start++] = n >>> 8
    state.buffer[state.start++] = n
  },
  decode(state) {
    if (state.end - state.start < 4) throw new Error('Out of bounds')
    return (
      state.buffer[state.start++] * 0x1000000 +
      state.buffer[state.start++] * 0x10000 +
      state.buffer[state.start++] * 0x100 +
      state.buffer[state.start++]
    )
  }
})

const uint40 = (exports.uint40 = {
  preencode(state, n) {
    state.end += 5
  },
  encode(state, n) {
    validateUint(n)
    const r = Math.floor(n / 0x100)
    uint8.encode(state, n)
    uint32.encode(state, r)
  },
  decode(state) {
    if (state.end - state.start < 5) throw new Error('Out of bounds')
    return uint8.decode(state) + 0x100 * uint32.decode(state)
  }
})

const uint48 = (exports.uint48 = {
  preencode(state, n) {
    state.end += 6
  },
  encode(state, n) {
    validateUint(n)
    const r = Math.floor(n / 0x10000)
    uint16.encode(state, n)
    uint32.encode(state, r)
  },
  decode(state) {
    if (state.end - state.start < 6) throw new Error('Out of bounds')
    return uint16.decode(state) + 0x10000 * uint32.decode(state)
  }
})

const uint56 = (exports.uint56 = {
  preencode(state, n) {
    state.end += 7
  },
  encode(state, n) {
    validateUint(n)
    const r = Math.floor(n / 0x1000000)
    uint24.encode(state, n)
    uint32.encode(state, r)
  },
  decode(state) {
    if (state.end - state.start < 7) throw new Error('Out of bounds')
    return uint24.decode(state) + 0x1000000 * uint32.decode(state)
  }
})

const uint64 = (exports.uint64 = {
  preencode(state, n) {
    state.end += 8
  },
  encode(state, n) {
    validateUint(n)
    const r = Math.floor(n / 0x100000000)
    uint32.encode(state, n)
    uint32.encode(state, r)
  },
  decode(state) {
    if (state.end - state.start < 8) throw new Error('Out of bounds')
    return uint32.decode(state) + 0x100000000 * uint32.decode(state)
  }
})

exports.uint64be = {
  preencode(state, n) {
    state.end += 8
  },
  encode(state, n) {
    validateUint(n)
    const r = Math.floor(n / 0x100000000)
    uint32be.encode(state, r)
    uint32be.encode(state, n)
  },
  decode(state) {
    if (state.end - state.start < 8) throw new Error('Out of bounds')
    return 0x100000000 * uint32be.decode(state) + uint32be.decode(state)
  }
}

const int = (exports.int = zigZagInt(uint))
exports.int8 = zigZagInt(uint8)
exports.int16 = zigZagInt(uint16)
exports.int24 = zigZagInt(uint24)
exports.int32 = zigZagInt(uint32)
exports.int40 = zigZagInt(uint40)
exports.int48 = zigZagInt(uint48)
exports.int56 = zigZagInt(uint56)
exports.int64 = zigZagInt(uint64)

const biguint64 = (exports.biguint64 = {
  preencode(state, n) {
    state.end += 8
  },
  encode(state, n) {
    const view = new DataView(
      state.buffer.buffer,
      state.start + state.buffer.byteOffset,
      8
    )
    view.setBigUint64(0, n, true) // little endian
    state.start += 8
  },
  decode(state) {
    if (state.end - state.start < 8) throw new Error('Out of bounds')
    const view = new DataView(
      state.buffer.buffer,
      state.start + state.buffer.byteOffset,
      8
    )
    const n = view.getBigUint64(0, true) // little endian
    state.start += 8
    return n
  }
})

exports.bigint64 = zigZagBigInt(biguint64)

const biguint = (exports.biguint = {
  preencode(state, n) {
    let len = 0
    for (let m = n; m; m = m >> 64n) len++
    uint.preencode(state, len)
    state.end += 8 * len
  },
  encode(state, n) {
    let len = 0
    for (let m = n; m; m = m >> 64n) len++
    uint.encode(state, len)
    const view = new DataView(
      state.buffer.buffer,
      state.start + state.buffer.byteOffset,
      8 * len
    )
    for (let m = n, i = 0; m; m = m >> 64n, i += 8) {
      view.setBigUint64(i, BigInt.asUintN(64, m), true) // little endian
    }
    state.start += 8 * len
  },
  decode(state) {
    const len = uint.decode(state)
    if (state.end - state.start < 8 * len) throw new Error('Out of bounds')
    const view = new DataView(
      state.buffer.buffer,
      state.start + state.buffer.byteOffset,
      8 * len
    )
    let n = 0n
    for (let i = len - 1; i >= 0; i--)
      n = (n << 64n) + view.getBigUint64(i * 8, true) // little endian
    state.start += 8 * len
    return n
  }
})

exports.bigint = zigZagBigInt(biguint)

exports.lexint = require('./lexint')

exports.float32 = {
  preencode(state, n) {
    state.end += 4
  },
  encode(state, n) {
    const view = new DataView(
      state.buffer.buffer,
      state.start + state.buffer.byteOffset,
      4
    )
    view.setFloat32(0, n, true) // little endian
    state.start += 4
  },
  decode(state) {
    if (state.end - state.start < 4) throw new Error('Out of bounds')
    const view = new DataView(
      state.buffer.buffer,
      state.start + state.buffer.byteOffset,
      4
    )
    const float = view.getFloat32(0, true) // little endian
    state.start += 4
    return float
  }
}

exports.float64 = {
  preencode(state, n) {
    state.end += 8
  },
  encode(state, n) {
    const view = new DataView(
      state.buffer.buffer,
      state.start + state.buffer.byteOffset,
      8
    )
    view.setFloat64(0, n, true) // little endian
    state.start += 8
  },
  decode(state) {
    if (state.end - state.start < 8) throw new Error('Out of bounds')
    const view = new DataView(
      state.buffer.buffer,
      state.start + state.buffer.byteOffset,
      8
    )
    const float = view.getFloat64(0, true) // little endian
    state.start += 8
    return float
  }
}

const buffer = (exports.buffer = {
  preencode(state, b) {
    uint8array.preencode(state, b)
  },
  encode(state, b) {
    uint8array.encode(state, b)
  },
  decode(state) {
    const len = uint.decode(state)
    if (state.end - state.start < len) throw new Error('Out of bounds')
    return state.buffer.subarray(state.start, (state.start += len))
  }
})

exports.optionalBuffer = {
  preencode(state, b) {
    if (b) uint8array.preencode(state, b)
    else state.end++
  },
  encode(state, b) {
    if (b) uint8array.encode(state, b)
    else state.buffer[state.start++] = 0
  },
  decode(state) {
    const len = uint.decode(state)
    if (len === 0) return null
    if (state.end - state.start < len) throw new Error('Out of bounds')
    return state.buffer.subarray(state.start, (state.start += len))
  }
}

exports.binary = {
  ...buffer,
  preencode(state, b) {
    if (typeof b === 'string') utf8.preencode(state, b)
    else buffer.preencode(state, b)
  },
  encode(state, b) {
    if (typeof b === 'string') utf8.encode(state, b)
    else buffer.encode(state, b)
  }
}

exports.arraybuffer = {
  preencode(state, b) {
    uint.preencode(state, b.byteLength)
    state.end += b.byteLength
  },
  encode(state, b) {
    uint.encode(state, b.byteLength)

    const view = new Uint8Array(b)

    state.buffer.set(view, state.start)
    state.start += b.byteLength
  },
  decode(state) {
    const len = uint.decode(state)

    const b = new ArrayBuffer(len)
    const view = new Uint8Array(b)

    view.set(state.buffer.subarray(state.start, (state.start += len)))

    return b
  }
}

function typedarray(TypedArray, swap) {
  const n = TypedArray.BYTES_PER_ELEMENT

  return {
    preencode(state, b) {
      uint.preencode(state, b.length)
      state.end += b.byteLength
    },
    encode(state, b) {
      uint.encode(state, b.length)

      const view = new Uint8Array(b.buffer, b.byteOffset, b.byteLength)

      if (BE && swap) swap(view)

      state.buffer.set(view, state.start)
      state.start += b.byteLength
    },
    decode(state) {
      const len = uint.decode(state)

      let b = state.buffer.subarray(state.start, (state.start += len * n))
      if (b.byteLength !== len * n) throw new Error('Out of bounds')
      if (b.byteOffset % n !== 0) b = new Uint8Array(b)

      if (BE && swap) swap(b)

      return new TypedArray(b.buffer, b.byteOffset, b.byteLength / n)
    }
  }
}

const uint8array = (exports.uint8array = typedarray(Uint8Array))
exports.uint16array = typedarray(Uint16Array, b4a.swap16)
exports.uint32array = typedarray(Uint32Array, b4a.swap32)

exports.int8array = typedarray(Int8Array)
exports.int16array = typedarray(Int16Array, b4a.swap16)
exports.int32array = typedarray(Int32Array, b4a.swap32)

exports.biguint64array = typedarray(BigUint64Array, b4a.swap64)
exports.bigint64array = typedarray(BigInt64Array, b4a.swap64)

exports.float32array = typedarray(Float32Array, b4a.swap32)
exports.float64array = typedarray(Float64Array, b4a.swap64)

function string(encoding) {
  return {
    preencode(state, s) {
      const len = b4a.byteLength(s, encoding)
      uint.preencode(state, len)
      state.end += len
    },
    encode(state, s) {
      const len = b4a.byteLength(s, encoding)
      uint.encode(state, len)
      b4a.write(state.buffer, s, state.start, encoding)
      state.start += len
    },
    decode(state) {
      const len = uint.decode(state)
      if (state.end - state.start < len) throw new Error('Out of bounds')
      return b4a.toString(
        state.buffer,
        encoding,
        state.start,
        (state.start += len)
      )
    },
    fixed(n) {
      return {
        preencode(state) {
          state.end += n
        },
        encode(state, s) {
          b4a.write(state.buffer, s, state.start, n, encoding)
          state.start += n
        },
        decode(state) {
          if (state.end - state.start < n) throw new Error('Out of bounds')
          return b4a.toString(
            state.buffer,
            encoding,
            state.start,
            (state.start += n)
          )
        }
      }
    }
  }
}

const utf8 = (exports.string = exports.utf8 = string('utf-8'))
exports.ascii = string('ascii')
exports.hex = string('hex')
exports.base64 = string('base64')
exports.ucs2 = exports.utf16le = string('utf16le')

exports.bool = {
  preencode(state, b) {
    state.end++
  },
  encode(state, b) {
    state.buffer[state.start++] = b ? 1 : 0
  },
  decode(state) {
    if (state.start >= state.end) throw Error('Out of bounds')
    return state.buffer[state.start++] === 1
  }
}

const fixed = (exports.fixed = function fixed(n) {
  return {
    preencode(state, s) {
      if (s.byteLength !== n) throw new Error('Incorrect buffer size')
      state.end += n
    },
    encode(state, s) {
      state.buffer.set(s, state.start)
      state.start += n
    },
    decode(state) {
      if (state.end - state.start < n) throw new Error('Out of bounds')
      return state.buffer.subarray(state.start, (state.start += n))
    }
  }
})

exports.fixed32 = fixed(32)
exports.fixed64 = fixed(64)

exports.array = function array(enc) {
  return {
    preencode(state, list) {
      uint.preencode(state, list.length)
      for (let i = 0; i < list.length; i++) enc.preencode(state, list[i])
    },
    encode(state, list) {
      uint.encode(state, list.length)
      for (let i = 0; i < list.length; i++) enc.encode(state, list[i])
    },
    decode(state) {
      const len = uint.decode(state)
      if (len > 0x100000) throw new Error('Array is too big')
      const arr = new Array(len)
      for (let i = 0; i < len; i++) arr[i] = enc.decode(state)
      return arr
    }
  }
}

exports.frame = function frame(enc) {
  const dummy = exports.state()

  return {
    preencode(state, m) {
      const end = state.end
      enc.preencode(state, m)
      uint.preencode(state, state.end - end)
    },
    encode(state, m) {
      dummy.end = 0
      enc.preencode(dummy, m)
      uint.encode(state, dummy.end)
      enc.encode(state, m)
    },
    decode(state) {
      const end = state.end
      const len = uint.decode(state)
      state.end = state.start + len
      const m = enc.decode(state)
      state.start = state.end
      state.end = end
      return m
    }
  }
}

exports.date = {
  preencode(state, d) {
    int.preencode(state, d.getTime())
  },
  encode(state, d) {
    int.encode(state, d.getTime())
  },
  decode(state, d) {
    return new Date(int.decode(state))
  }
}

exports.json = {
  preencode(state, v) {
    utf8.preencode(state, JSON.stringify(v))
  },
  encode(state, v) {
    utf8.encode(state, JSON.stringify(v))
  },
  decode(state) {
    return JSON.parse(utf8.decode(state))
  }
}

exports.ndjson = {
  preencode(state, v) {
    utf8.preencode(state, JSON.stringify(v) + '\n')
  },
  encode(state, v) {
    utf8.encode(state, JSON.stringify(v) + '\n')
  },
  decode(state) {
    return JSON.parse(utf8.decode(state))
  }
}

// simple helper for when you want to just express nothing
exports.none = {
  preencode(state, n) {
    // do nothing
  },
  encode(state, n) {
    // do nothing
  },
  decode(state) {
    return null
  }
}

// "any" encoders here for helping just structure any object without schematising it

const anyArray = {
  preencode(state, arr) {
    uint.preencode(state, arr.length)
    for (let i = 0; i < arr.length; i++) {
      any.preencode(state, arr[i])
    }
  },
  encode(state, arr) {
    uint.encode(state, arr.length)
    for (let i = 0; i < arr.length; i++) {
      any.encode(state, arr[i])
    }
  },
  decode(state) {
    const arr = []
    let len = uint.decode(state)
    while (len-- > 0) {
      arr.push(any.decode(state))
    }
    return arr
  }
}

const anyObject = {
  preencode(state, o) {
    const keys = Object.keys(o)
    uint.preencode(state, keys.length)
    for (const key of keys) {
      utf8.preencode(state, key)
      any.preencode(state, o[key])
    }
  },
  encode(state, o) {
    const keys = Object.keys(o)
    uint.encode(state, keys.length)
    for (const key of keys) {
      utf8.encode(state, key)
      any.encode(state, o[key])
    }
  },
  decode(state) {
    let len = uint.decode(state)
    const o = {}
    while (len-- > 0) {
      const key = utf8.decode(state)
      o[key] = any.decode(state)
    }
    return o
  }
}

const anyTypes = [
  exports.none,
  exports.bool,
  exports.string,
  exports.buffer,
  exports.uint,
  exports.int,
  exports.float64,
  anyArray,
  anyObject,
  exports.date
]

const any = (exports.any = {
  preencode(state, o) {
    const t = getType(o)
    uint.preencode(state, t)
    anyTypes[t].preencode(state, o)
  },
  encode(state, o) {
    const t = getType(o)
    uint.encode(state, t)
    anyTypes[t].encode(state, o)
  },
  decode(state) {
    const t = uint.decode(state)
    if (t >= anyTypes.length) throw new Error('Unknown type: ' + t)
    return anyTypes[t].decode(state)
  }
})

const port = (exports.port = uint16)

const address = (host, family) => {
  return {
    preencode(state, m) {
      host.preencode(state, m.host)
      port.preencode(state, m.port)
    },
    encode(state, m) {
      host.encode(state, m.host)
      port.encode(state, m.port)
    },
    decode(state) {
      return {
        host: host.decode(state),
        family,
        port: port.decode(state)
      }
    }
  }
}

const ipv4 = (exports.ipv4 = {
  preencode(state) {
    state.end += 4
  },
  encode(state, string) {
    const start = state.start
    const end = start + 4

    let i = 0

    while (i < string.length) {
      let n = 0
      let c

      while (
        i < string.length &&
        (c = string.charCodeAt(i++)) !== /* . */ 0x2e
      ) {
        n = n * 10 + (c - /* 0 */ 0x30)
      }

      state.buffer[state.start++] = n
    }

    state.start = end
  },
  decode(state) {
    if (state.end - state.start < 4) throw new Error('Out of bounds')
    return (
      state.buffer[state.start++] +
      '.' +
      state.buffer[state.start++] +
      '.' +
      state.buffer[state.start++] +
      '.' +
      state.buffer[state.start++]
    )
  }
})

exports.ipv4Address = address(ipv4, 4)

const ipv6 = (exports.ipv6 = {
  preencode(state) {
    state.end += 16
  },
  encode(state, string) {
    const start = state.start
    const end = start + 16

    let i = 0
    let split = null

    while (i < string.length) {
      let n = 0
      let c

      while (
        i < string.length &&
        (c = string.charCodeAt(i++)) !== /* : */ 0x3a
      ) {
        if (c >= 0x30 && c <= 0x39) n = n * 0x10 + (c - /* 0 */ 0x30)
        else if (c >= 0x41 && c <= 0x46) n = n * 0x10 + (c - /* A */ 0x41 + 10)
        else if (c >= 0x61 && c <= 0x66) n = n * 0x10 + (c - /* a */ 0x61 + 10)
      }

      state.buffer[state.start++] = n >>> 8
      state.buffer[state.start++] = n

      if (i < string.length && string.charCodeAt(i) === /* : */ 0x3a) {
        i++
        split = state.start
      }
    }

    if (split !== null) {
      const offset = end - state.start
      state.buffer
        .copyWithin(split + offset, split)
        .fill(0, split, split + offset)
    }

    state.start = end
  },
  decode(state) {
    if (state.end - state.start < 16) throw new Error('Out of bounds')
    return (
      (
        state.buffer[state.start++] * 256 +
        state.buffer[state.start++]
      ).toString(16) +
      ':' +
      (
        state.buffer[state.start++] * 256 +
        state.buffer[state.start++]
      ).toString(16) +
      ':' +
      (
        state.buffer[state.start++] * 256 +
        state.buffer[state.start++]
      ).toString(16) +
      ':' +
      (
        state.buffer[state.start++] * 256 +
        state.buffer[state.start++]
      ).toString(16) +
      ':' +
      (
        state.buffer[state.start++] * 256 +
        state.buffer[state.start++]
      ).toString(16) +
      ':' +
      (
        state.buffer[state.start++] * 256 +
        state.buffer[state.start++]
      ).toString(16) +
      ':' +
      (
        state.buffer[state.start++] * 256 +
        state.buffer[state.start++]
      ).toString(16) +
      ':' +
      (
        state.buffer[state.start++] * 256 +
        state.buffer[state.start++]
      ).toString(16)
    )
  }
})

exports.ipv6Address = address(ipv6, 6)

const ip = (exports.ip = {
  preencode(state, string) {
    const family = string.includes(':') ? 6 : 4
    uint8.preencode(state, family)
    if (family === 4) ipv4.preencode(state)
    else ipv6.preencode(state)
  },
  encode(state, string) {
    const family = string.includes(':') ? 6 : 4
    uint8.encode(state, family)
    if (family === 4) ipv4.encode(state, string)
    else ipv6.encode(state, string)
  },
  decode(state) {
    const family = uint8.decode(state)
    if (family === 4) return ipv4.decode(state)
    else return ipv6.decode(state)
  }
})

exports.ipAddress = {
  preencode(state, m) {
    ip.preencode(state, m.host)
    port.preencode(state, m.port)
  },
  encode(state, m) {
    ip.encode(state, m.host)
    port.encode(state, m.port)
  },
  decode(state) {
    const family = uint8.decode(state)
    return {
      host: family === 4 ? ipv4.decode(state) : ipv6.decode(state),
      family,
      port: port.decode(state)
    }
  }
}

const record = (exports.record = function (keyEncoding, valueEncoding) {
  return {
    preencode(state, v) {
      const keys = Object.keys(v)
      uint.preencode(state, keys.length)
      for (const k of keys) {
        keyEncoding.preencode(state, k)
        valueEncoding.preencode(state, v[k])
      }
    },
    encode(state, v) {
      const keys = Object.keys(v)
      uint.encode(state, keys.length)
      for (const k of keys) {
        keyEncoding.encode(state, k)
        valueEncoding.encode(state, v[k])
      }
    },
    decode(state) {
      const out = Object.create(null)
      const keys = uint.decode(state)
      for (let i = 0; i < keys; i++) {
        out[keyEncoding.decode(state)] = valueEncoding.decode(state)
      }
      return out
    }
  }
})

exports.stringRecord = record(utf8, utf8)

function getType(o) {
  if (o === null || o === undefined) return 0
  if (typeof o === 'boolean') return 1
  if (typeof o === 'string') return 2
  if (b4a.isBuffer(o)) return 3
  if (typeof o === 'number') {
    if (Number.isInteger(o)) return o >= 0 ? 4 : 5
    return 6
  }
  if (Array.isArray(o)) return 7
  if (o instanceof Date) return 9
  if (typeof o === 'object') return 8

  throw new Error('Unsupported type for ' + o)
}

exports.from = function from(enc) {
  if (typeof enc === 'string') return fromNamed(enc)
  if (enc.preencode) return enc
  if (enc.encodingLength) return fromAbstractEncoder(enc)
  return fromCodec(enc)
}

function fromNamed(enc) {
  switch (enc) {
    case 'ascii':
      return raw.ascii
    case 'utf-8':
    case 'utf8':
      return raw.utf8
    case 'hex':
      return raw.hex
    case 'base64':
      return raw.base64
    case 'utf16-le':
    case 'utf16le':
    case 'ucs-2':
    case 'ucs2':
      return raw.ucs2
    case 'ndjson':
      return raw.ndjson
    case 'json':
      return raw.json
    case 'binary':
    default:
      return raw.binary
  }
}

function fromCodec(enc) {
  let tmpM = null
  let tmpBuf = null

  return {
    preencode(state, m) {
      tmpM = m
      tmpBuf = enc.encode(m)
      state.end += tmpBuf.byteLength
    },
    encode(state, m) {
      raw.encode(state, m === tmpM ? tmpBuf : enc.encode(m))
      tmpM = tmpBuf = null
    },
    decode(state) {
      return enc.decode(raw.decode(state))
    }
  }
}

function fromAbstractEncoder(enc) {
  return {
    preencode(state, m) {
      state.end += enc.encodingLength(m)
    },
    encode(state, m) {
      enc.encode(m, state.buffer, state.start)
      state.start += enc.encode.bytes
    },
    decode(state) {
      const m = enc.decode(state.buffer, state.start, state.end)
      state.start += enc.decode.bytes
      return m
    }
  }
}

exports.encode = function encode(enc, m) {
  const state = exports.state()
  enc.preencode(state, m)
  state.buffer = b4a.allocUnsafe(state.end)
  enc.encode(state, m)
  return state.buffer
}

exports.decode = function decode(enc, buffer) {
  return enc.decode(exports.state(0, buffer.byteLength, buffer))
}

function zigZagInt(enc) {
  return {
    preencode(state, n) {
      enc.preencode(state, zigZagEncodeInt(n))
    },
    encode(state, n) {
      enc.encode(state, zigZagEncodeInt(n))
    },
    decode(state) {
      return zigZagDecodeInt(enc.decode(state))
    }
  }
}

function zigZagDecodeInt(n) {
  return n === 0 ? n : (n & 1) === 0 ? n / 2 : -(n + 1) / 2
}

function zigZagEncodeInt(n) {
  // 0, -1, 1, -2, 2, ...
  return n < 0 ? 2 * -n - 1 : n === 0 ? 0 : 2 * n
}

function zigZagBigInt(enc) {
  return {
    preencode(state, n) {
      enc.preencode(state, zigZagEncodeBigInt(n))
    },
    encode(state, n) {
      enc.encode(state, zigZagEncodeBigInt(n))
    },
    decode(state) {
      return zigZagDecodeBigInt(enc.decode(state))
    }
  }
}

function zigZagDecodeBigInt(n) {
  return n === 0n ? n : (n & 1n) === 0n ? n / 2n : -(n + 1n) / 2n
}

function zigZagEncodeBigInt(n) {
  // 0, -1, 1, -2, 2, ...
  return n < 0n ? 2n * -n - 1n : n === 0n ? 0n : 2n * n
}

function validateUint(n) {
  if (n >= 0 === false /* Handles NaN as well */)
    throw new Error('uint must be positive')
  // Beyond this, Number arithmetic loses precision and silently corrupts the
  // encoding. The zig-zag int codecs route through here too, so this also
  // guards int values whose doubled magnitude exceeds the safe range.
  if (n > Number.MAX_SAFE_INTEGER)
    throw new Error(
      'integer is greater than the maximum safe integer, use biguint/bigint'
    )
}
module.exports = {
  preencode,
  encode,
  decode
}

function preencode(state, num) {
  if (num < 251) {
    state.end++
  } else if (num < 256) {
    state.end += 2
  } else if (num < 0x10000) {
    state.end += 3
  } else if (num < 0x1000000) {
    state.end += 4
  } else if (num < 0x100000000) {
    state.end += 5
  } else {
    state.end++
    const exp = Math.floor(Math.log(num) / Math.log(2)) - 32
    preencode(state, exp)
    state.end += 6
  }
}

function encode(state, num) {
  const max = 251
  const x = num - max

  if (num < max) {
    state.buffer[state.start++] = num
  } else if (num < 256) {
    state.buffer[state.start++] = max
    state.buffer[state.start++] = x
  } else if (num < 0x10000) {
    state.buffer[state.start++] = max + 1
    state.buffer[state.start++] = (x >> 8) & 0xff
    state.buffer[state.start++] = x & 0xff
  } else if (num < 0x1000000) {
    state.buffer[state.start++] = max + 2
    state.buffer[state.start++] = x >> 16
    state.buffer[state.start++] = (x >> 8) & 0xff
    state.buffer[state.start++] = x & 0xff
  } else if (num < 0x100000000) {
    state.buffer[state.start++] = max + 3
    state.buffer[state.start++] = x >> 24
    state.buffer[state.start++] = (x >> 16) & 0xff
    state.buffer[state.start++] = (x >> 8) & 0xff
    state.buffer[state.start++] = x & 0xff
  } else {
    // need to use Math here as bitwise ops are 32 bit
    const exp = Math.floor(Math.log(x) / Math.log(2)) - 32
    state.buffer[state.start++] = 0xff

    encode(state, exp)
    const rem = x / Math.pow(2, exp - 11)

    for (let i = 5; i >= 0; i--) {
      state.buffer[state.start++] = (rem / Math.pow(2, 8 * i)) & 0xff
    }
  }
}

function decode(state) {
  const max = 251

  if (state.end - state.start < 1) throw new Error('Out of bounds')

  const flag = state.buffer[state.start++]

  if (flag < max) return flag

  if (state.end - state.start < flag - max + 1) {
    throw new Error('Out of bounds.')
  }

  if (flag < 252) {
    return state.buffer[state.start++] + max
  }

  if (flag < 253) {
    return (
      (state.buffer[state.start++] << 8) + state.buffer[state.start++] + max
    )
  }

  if (flag < 254) {
    return (
      (state.buffer[state.start++] << 16) +
      (state.buffer[state.start++] << 8) +
      state.buffer[state.start++] +
      max
    )
  }

  // << 24 result may be interpreted as negative
  if (flag < 255) {
    return (
      state.buffer[state.start++] * 0x1000000 +
      (state.buffer[state.start++] << 16) +
      (state.buffer[state.start++] << 8) +
      state.buffer[state.start++] +
      max
    )
  }

  const exp = decode(state)

  if (state.end - state.start < 6) throw new Error('Out of bounds')

  let rem = 0
  for (let i = 5; i >= 0; i--) {
    rem += state.buffer[state.start++] * Math.pow(2, 8 * i)
  }

  return rem * Math.pow(2, exp - 11) + max
}
{
  "name": "compact-encoding",
  "version": "3.2.0",
  "description": "A series of compact encoding schemes for building small and fast parsers and serializers",
  "main": "index.js",
  "files": [
    "endian.js",
    "index.js",
    "lexint.js",
    "raw.js"
  ],
  "dependencies": {
    "b4a": "^1.3.0"
  },
  "devDependencies": {
    "brittle": "^3.0.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^1.0.0"
  },
  "scripts": {
    "format": "prettier . --write",
    "test": "prettier . --check && brittle test.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/holepunchto/compact-encoding.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/compact-encoding/issues"
  },
  "homepage": "https://github.com/holepunchto/compact-encoding"
}
const b4a = require('b4a')

const { BE } = require('./endian')

exports = module.exports = {
  preencode(state, b) {
    state.end += b.byteLength
  },
  encode(state, b) {
    state.buffer.set(b, state.start)
    state.start += b.byteLength
  },
  decode(state) {
    const b = state.buffer.subarray(state.start, state.end)
    state.start = state.end
    return b
  }
}

const buffer = (exports.buffer = {
  preencode(state, b) {
    uint8array.preencode(state, b)
  },
  encode(state, b) {
    uint8array.encode(state, b)
  },
  decode(state) {
    const b = state.buffer.subarray(state.start)
    state.start = state.end
    return b
  }
})

exports.binary = {
  ...buffer,
  preencode(state, b) {
    if (typeof b === 'string') utf8.preencode(state, b)
    else buffer.preencode(state, b)
  },
  encode(state, b) {
    if (typeof b === 'string') utf8.encode(state, b)
    else buffer.encode(state, b)
  }
}

exports.arraybuffer = {
  preencode(state, b) {
    state.end += b.byteLength
  },
  encode(state, b) {
    const view = new Uint8Array(b)

    state.buffer.set(view, state.start)
    state.start += b.byteLength
  },
  decode(state) {
    const b = new ArrayBuffer(state.end - state.start)
    const view = new Uint8Array(b)

    view.set(state.buffer.subarray(state.start))

    state.start = state.end

    return b
  }
}

function typedarray(TypedArray, swap) {
  const n = TypedArray.BYTES_PER_ELEMENT

  return {
    preencode(state, b) {
      state.end += b.byteLength
    },
    encode(state, b) {
      const view = new Uint8Array(b.buffer, b.byteOffset, b.byteLength)

      if (BE && swap) swap(view)

      state.buffer.set(view, state.start)
      state.start += b.byteLength
    },
    decode(state) {
      let b = state.buffer.subarray(state.start)
      if (b.byteOffset % n !== 0) b = new Uint8Array(b)

      if (BE && swap) swap(b)

      state.start = state.end

      return new TypedArray(b.buffer, b.byteOffset, b.byteLength / n)
    }
  }
}

const uint8array = (exports.uint8array = typedarray(Uint8Array))
exports.uint16array = typedarray(Uint16Array, b4a.swap16)
exports.uint32array = typedarray(Uint32Array, b4a.swap32)

exports.int8array = typedarray(Int8Array)
exports.int16array = typedarray(Int16Array, b4a.swap16)
exports.int32array = typedarray(Int32Array, b4a.swap32)

exports.biguint64array = typedarray(BigUint64Array, b4a.swap64)
exports.bigint64array = typedarray(BigInt64Array, b4a.swap64)

exports.float32array = typedarray(Float32Array, b4a.swap32)
exports.float64array = typedarray(Float64Array, b4a.swap64)

function string(encoding) {
  return {
    preencode(state, s) {
      state.end += b4a.byteLength(s, encoding)
    },
    encode(state, s) {
      state.start += b4a.write(state.buffer, s, state.start, encoding)
    },
    decode(state) {
      const s = b4a.toString(state.buffer, encoding, state.start)
      state.start = state.end
      return s
    }
  }
}

const utf8 = (exports.string = exports.utf8 = string('utf-8'))
exports.ascii = string('ascii')
exports.hex = string('hex')
exports.base64 = string('base64')
exports.ucs2 = exports.utf16le = string('utf16le')

exports.array = function array(enc) {
  return {
    preencode(state, list) {
      for (const value of list) enc.preencode(state, value)
    },
    encode(state, list) {
      for (const value of list) enc.encode(state, value)
    },
    decode(state) {
      const arr = []
      while (state.start < state.end) arr.push(enc.decode(state))
      return arr
    }
  }
}

exports.json = {
  preencode(state, v) {
    utf8.preencode(state, JSON.stringify(v))
  },
  encode(state, v) {
    utf8.encode(state, JSON.stringify(v))
  },
  decode(state) {
    return JSON.parse(utf8.decode(state))
  }
}

exports.ndjson = {
  preencode(state, v) {
    utf8.preencode(state, JSON.stringify(v) + '\n')
  },
  encode(state, v) {
    utf8.encode(state, JSON.stringify(v) + '\n')
  },
  decode(state) {
    return JSON.parse(utf8.decode(state))
  }
}
module.exports = function debounce (worker, context = null) {
  debounced.running = null
  return debounced

  async function debounced () {
    if (debounced.running !== null) {
      try {
        await debounced.running
      } catch (_) {
        // ignore - do not fail on old errors
      }
    }

    // another "thread" beat us to it, just piggy pack on that one
    if (debounced.running !== null) return debounced.running

    debounced.running = worker.call(context)

    try {
      return await debounced.running
    } finally {
      debounced.running = null
    }
  }
}
{
  "name": "debounceify",
  "version": "1.1.0",
  "description": "Tiny async debouncer",
  "main": "index.js",
  "dependencies": {},
  "devDependencies": {
    "standard": "^16.0.3",
    "tape": "^5.1.1"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/mafintosh/debounceify.git"
  },
  "scripts": {
    "test": "standard && tape test.js"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/mafintosh/debounceify/issues"
  },
  "homepage": "https://github.com/mafintosh/debounceify"
}
module.exports = require('bare-events')
{
  "name": "events-universal",
  "version": "1.0.1",
  "description": "Universal wrapper for the Node.js events module",
  "exports": {
    "./package": "./package.json",
    ".": {
      "bare": "./bare.js",
      "react-native": "./react-native.js",
      "default": "./default.js"
    }
  },
  "files": [
    "index.js",
    "default.js",
    "bare.js",
    "react-native.js"
  ],
  "scripts": {
    "test": "prettier . --check"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/events-universal.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/events-universal/issues"
  },
  "homepage": "https://github.com/holepunchto/events-universal#readme",
  "dependencies": {
    "bare-events": "^2.7.0"
  },
  "devDependencies": {
    "prettier": "^3.4.2",
    "prettier-config-holepunch": "^1.0.0"
  }
}
module.exports = class FixedFIFO {
  constructor (hwm) {
    if (!(hwm > 0) || ((hwm - 1) & hwm) !== 0) throw new Error('Max size for a FixedFIFO should be a power of two')
    this.buffer = new Array(hwm)
    this.mask = hwm - 1
    this.top = 0
    this.btm = 0
    this.next = null
  }

  clear () {
    this.top = this.btm = 0
    this.next = null
    this.buffer.fill(undefined)
  }

  push (data) {
    if (this.buffer[this.top] !== undefined) return false
    this.buffer[this.top] = data
    this.top = (this.top + 1) & this.mask
    return true
  }

  shift () {
    const last = this.buffer[this.btm]
    if (last === undefined) return undefined
    this.buffer[this.btm] = undefined
    this.btm = (this.btm + 1) & this.mask
    return last
  }

  peek () {
    return this.buffer[this.btm]
  }

  isEmpty () {
    return this.buffer[this.btm] === undefined
  }
}
const FixedFIFO = require('./fixed-size')

module.exports = class FastFIFO {
  constructor (hwm) {
    this.hwm = hwm || 16
    this.head = new FixedFIFO(this.hwm)
    this.tail = this.head
    this.length = 0
  }

  clear () {
    this.head = this.tail
    this.head.clear()
    this.length = 0
  }

  push (val) {
    this.length++
    if (!this.head.push(val)) {
      const prev = this.head
      this.head = prev.next = new FixedFIFO(2 * this.head.buffer.length)
      this.head.push(val)
    }
  }

  shift () {
    if (this.length !== 0) this.length--
    const val = this.tail.shift()
    if (val === undefined && this.tail.next) {
      const next = this.tail.next
      this.tail.next = null
      this.tail = next
      return this.tail.shift()
    }

    return val
  }

  peek () {
    const val = this.tail.peek()
    if (val === undefined && this.tail.next) return this.tail.next.peek()
    return val
  }

  isEmpty () {
    return this.length === 0
  }
}
{
  "name": "fast-fifo",
  "version": "1.3.2",
  "description": "A fast fifo implementation similar to the one powering nextTick in Node.js core",
  "main": "index.js",
  "files": [
    "./index.js",
    "./fixed-size.js"
  ],
  "dependencies": {},
  "devDependencies": {
    "standard": "^17.1.0",
    "brittle": "^3.3.2"
  },
  "scripts": {
    "test": "standard && brittle test.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/mafintosh/fast-fifo.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/mafintosh/fast-fifo/issues"
  },
  "homepage": "https://github.com/mafintosh/fast-fifo"
}
const codecs = require('codecs')
const { Readable } = require('streamx')
const mutexify = require('mutexify/promise')
const b4a = require('b4a')
const safetyCatch = require('safety-catch')
const ReadyResource = require('ready-resource')
const debounce = require('debounceify')
const Rache = require('rache')
const rrp = require('resolve-reject-promise')

const { all: unslabAll } = require('unslab')

const RangeIterator = require('./iterators/range')
const HistoryIterator = require('./iterators/history')
const DiffIterator = require('./iterators/diff')
const LocalBlockIterator = require('./iterators/local')
const Extension = require('./lib/extension')
const { YoloIndex, Node, Header } = require('./lib/messages')
const { BLOCK_NOT_AVAILABLE, DECODING_ERROR } = require('hypercore-errors')

const T = 5
const MIN_KEYS = T - 1
const MAX_CHILDREN = MIN_KEYS * 2 + 1

const SEP = b4a.alloc(1)
const EMPTY = b4a.alloc(0)

class Key {
  constructor(seq, value) {
    this.seq = seq
    this.value = value
  }
}

class Child {
  constructor(seq, offset, value) {
    this.seq = seq
    this.offset = offset
    this.value = value
  }
}

class Cache {
  constructor(rache) {
    this.keys = rache
    this.length = 0
  }

  get(seq) {
    return this.keys.get(seq) || null
  }

  set(seq, key) {
    this.keys.set(seq, key)
    if (seq >= this.length) this.length = seq + 1
  }

  gc(length) {
    // if we need to "work" more than 128 ticks, just bust the cache...
    if (this.length - length > 128) {
      this.keys.clear()
    } else {
      for (let i = length; i < this.length; i++) {
        this.keys.delete(i)
      }
    }

    this.length = length
  }

  clear() {
    this.keys.clear()
  }
}

class Pointers {
  constructor(decoded) {
    this.levels = decoded.levels.map((l) => {
      const children = []
      const keys = []

      for (let i = 0; i < l.keys.length; i++) {
        keys.push(new Key(l.keys[i], null))
      }

      for (let i = 0; i < l.children.length; i += 2) {
        children.push(new Child(l.children[i], l.children[i + 1], null))
      }

      return { keys, children }
    })
  }

  get(i) {
    return this.levels[i]
  }

  hasKey(seq) {
    for (const lvl of this.levels) {
      for (const key of lvl.keys) {
        if (key.seq === seq) return true
      }
    }
    return false
  }
}

function inflate(entry) {
  if (entry.inflated === null) {
    entry.inflated = YoloIndex.decode(entry.index)
    entry.index = null
  }
  return new Pointers(entry.inflated)
}

function deflate(index) {
  const levels = index.map((l) => {
    const keys = []
    const children = []

    for (let i = 0; i < l.value.keys.length; i++) {
      keys.push(l.value.keys[i].seq)
    }

    for (let i = 0; i < l.value.children.length; i++) {
      children.push(l.value.children[i].seq, l.value.children[i].offset)
    }

    return { keys, children }
  })

  return YoloIndex.encode({ levels })
}

class TreeNode {
  constructor(block, keys, children, offset) {
    this.block = block
    this.offset = offset
    this.keys = keys
    this.children = children
    this.changed = false

    this.preload()
  }

  preload() {
    if (this.block === null) return

    const core = getBackingCore(this.block.tree.core)
    if (!core) return

    const bitfield = core.core.bitfield
    const blocks = []

    for (let i = 0; i < this.keys.length; i++) {
      const k = this.keys[i]
      if (k.value) continue
      if (k.seq >= core.signedLength || (bitfield && bitfield.get(k.seq))) continue
      blocks.push(k.seq)
    }
    for (let i = 0; i < this.children.length; i++) {
      const c = this.children[i]
      if (c.value) continue
      if (c.seq >= core.signedLength || (bitfield && bitfield.get(c.seq))) continue
      blocks.push(c.seq)
    }

    if (blocks.length) core.download({ blocks })
  }

  async insertKey(key, value, child, node, encoding, cas) {
    let s = 0
    let e = this.keys.length
    let c

    while (s < e) {
      const mid = (s + e) >> 1
      c = b4a.compare(key.value, await this.getKey(mid))

      if (c === 0) {
        if (cas) {
          const prev = await this.getKeyNode(mid)
          if (!(await cas(prev.final(encoding), node))) return true
        }
        if (!this.block.tree.tree.alwaysDuplicate) {
          const prev = await this.getKeyNode(mid)
          if (sameValue(prev.value, value)) return true
        }
        this.changed = true
        this.keys[mid] = key
        return true
      }

      if (c < 0) e = mid
      else s = mid + 1
    }

    const i = c < 0 ? e : s
    this.keys.splice(i, 0, key)
    if (child) this.children.splice(i + 1, 0, new Child(0, 0, child))
    this.changed = true

    return this.keys.length < MAX_CHILDREN
  }

  removeKey(index) {
    this.keys.splice(index, 1)
    if (this.children.length) {
      this.children[index + 1].seq = 0 // mark as freed
      this.children.splice(index + 1, 1)
    }
    this.changed = true
  }

  async siblings(parent) {
    for (let i = 0; i < parent.children.length; i++) {
      if (parent.children[i].value === this) {
        const [left, right] = await Promise.all([
          i ? parent.getChildNode(i - 1) : null,
          i < parent.children.length - 1 ? parent.getChildNode(i + 1) : null
        ])
        return { left, index: i, right }
      }
    }

    throw new Error('Bad parent')
  }

  merge(node, median) {
    this.changed = true
    this.keys.push(median)
    for (let i = 0; i < node.keys.length; i++) this.keys.push(node.keys[i])
    for (let i = 0; i < node.children.length; i++) this.children.push(node.children[i])
  }

  async split() {
    const len = this.keys.length >> 1
    const right = TreeNode.create(this.block)

    while (right.keys.length < len) right.keys.push(this.keys.pop())
    right.keys.reverse()

    await this.getKey(this.keys.length - 1) // make sure the median is loaded
    const median = this.keys.pop()

    if (this.children.length) {
      while (right.children.length < len + 1) right.children.push(this.children.pop())
      right.children.reverse()
    }

    this.changed = true

    return {
      left: this,
      median,
      right
    }
  }

  getKeyNode(index) {
    return this.block.tree.getBlock(this.keys[index].seq)
  }

  async getChildNode(index) {
    const child = this.children[index]
    if (child.value) return child.value
    const block =
      child.seq === this.block.seq ? this.block : await this.block.tree.getBlock(child.seq)
    return (child.value = block.getTreeNode(child.offset))
  }

  setKey(index, key) {
    this.keys[index] = key
    this.changed = true
  }

  async getKey(index) {
    const key = this.keys[index]
    if (key.value) return key.value
    const k = key.seq === this.block.seq ? this.block.key : await this.block.tree.getKey(key.seq)
    return (key.value = k)
  }

  indexChanges(index, seq) {
    const offset = index.push(null) - 1
    this.changed = false

    for (const child of this.children) {
      if (!child.value || !child.value.changed) continue
      child.seq = seq
      child.offset = child.value.indexChanges(index, seq)
      index[child.offset] = child
    }

    return offset
  }

  updateChildren(seq, block) {
    for (const child of this.children) {
      if (!child.value || child.seq !== seq) continue
      child.value.block = block
      child.value.updateChildren(seq, block)
    }
  }

  static create(block) {
    const node = new TreeNode(block, [], [], 0)
    node.changed = true
    return node
  }
}

class BlockEntry {
  constructor(seq, tree, entry) {
    this.seq = seq
    this.tree = tree
    this.index = null
    this.entry = entry
    this.key = entry.key
    this.value = entry.value
  }

  isTarget(key) {
    return b4a.equals(this.key, key)
  }

  inflate() {
    if (this.index === null) {
      this.index = inflate(this.entry)
    }
  }

  isDeletion() {
    if (this.value !== null) return false

    if (this.index === null) {
      this.index = inflate(this.entry)
    }

    return !this.index.hasKey(this.seq)
  }

  final(encoding) {
    return {
      seq: this.seq,
      key: encoding.key ? encoding.key.decode(this.key) : this.key,
      value: this.value && (encoding.value ? encoding.value.decode(this.value) : this.value)
    }
  }

  getTreeNode(offset) {
    if (this.index === null) {
      this.index = inflate(this.entry)
    }
    const entry = this.index.get(offset)
    return new TreeNode(this, entry.keys, entry.children, offset)
  }
}

class CacheLock {
  constructor() {
    this.map = new Map()
  }

  enter(seq) {
    const pending = this.map.get(seq)

    if (!pending) {
      this.map.set(seq, [])
      return Promise.resolve()
    }

    const { resolve, promise } = rrp()
    pending.push(resolve)
    return promise
  }

  exit(seq) {
    const pending = this.map.get(seq)
    if (!pending.length) {
      this.map.delete(seq)
      return
    }

    pending.pop()()
  }
}

class BatchEntry extends BlockEntry {
  constructor(seq, tree, key, value, index) {
    super(seq, tree, { key, value, index: null, inflated: null })
    this.pendingIndex = index
  }

  isTarget(key) {
    return false
  }

  getTreeNode(offset) {
    return this.pendingIndex[offset].value
  }
}

class Hyperbee extends ReadyResource {
  constructor(core, opts = {}) {
    super()
    // this.feed is now deprecated, and will be this.core going forward
    this.feed = core
    this.core = core

    this.keyEncoding = opts.keyEncoding ? codecs(opts.keyEncoding) : null
    this.valueEncoding = opts.valueEncoding ? codecs(opts.valueEncoding) : null
    this.extension = opts.extension !== false ? getExtension(this, opts) : null
    this.metadata = opts.metadata || null
    this.lock = opts.lock || mutexify()
    this.sep = opts.sep || SEP
    this.readonly = !!opts.readonly
    this.prefix = opts.prefix || null

    // In a future version, this should be false by default
    this.alwaysDuplicate = opts.alwaysDuplicate !== false

    this._unprefixedKeyEncoding = this.keyEncoding
    this._sub = !!this.prefix
    this._checkout = opts.checkout || 0
    this._view = !!opts._view

    this._onappendBound = this._view ? null : this._onappend.bind(this)
    this._ontruncateBound = this._view ? null : this._ontruncate.bind(this)
    this._watchers = this._onappendBound ? [] : null
    this._entryWatchers = this._onappendBound ? [] : null
    this._sessions = opts.sessions !== false

    this._keyCache = null
    this._nodeCache = null
    this._cacheLock = new CacheLock()

    this._batches = []

    if (this._watchers) {
      this.core.on('append', this._onappendBound)
      this.core.on('truncate', this._ontruncateBound)
    }

    if (this.prefix && opts._sub) {
      this.keyEncoding = prefixEncoding(this.prefix, this.keyEncoding)
    }

    this.ready().catch(safetyCatch)
  }

  async _open() {
    if (this.core.opened === false) await this.core.ready()
    // bump the internal ranges since the nodes are small
    if (this._watchers && this.core.replicator.setInflightRange) {
      this.core.replicator.setInflightRange(256, 512)
    }

    // snapshot
    if (this._checkout === -1) this._checkout = Math.max(1, this.core.length)

    const baseCache = Rache.from(this.core.globalCache)
    this._keyCache = new Cache(baseCache)
    this._nodeCache = new Cache(Rache.from(baseCache))
  }

  get version() {
    return Math.max(1, this._checkout || this.core.length)
  }

  get id() {
    return this.core.id
  }

  get key() {
    return this.core.key
  }

  get discoveryKey() {
    return this.core.discoveryKey
  }

  get writable() {
    return this.core.writable
  }

  get readable() {
    return this.core.readable
  }

  replicate(isInitiator, opts) {
    return this.core.replicate(isInitiator, opts)
  }

  update(opts) {
    return this.core.update(opts)
  }

  peek(range, opts) {
    return iteratorPeek(this.createRangeIterator(range, { ...opts, limit: 1 }))
  }

  createRangeIterator(range, opts = {}) {
    // backwards compat range arg
    opts = opts ? { ...opts, ...range } : range

    const extension = opts.extension === false && opts.limit !== 0 ? null : this.extension
    const keyEncoding = opts.keyEncoding ? codecs(opts.keyEncoding) : this.keyEncoding

    if (extension) {
      const { onseq, onwait } = opts
      let version = 0
      let next = 0

      opts = encRange(keyEncoding, {
        ...opts,
        sub: this._sub,
        onseq(seq) {
          if (!version) version = seq + 1
          if (next) next--
          if (onseq) onseq(seq)
        },
        onwait(seq) {
          if (!next) {
            next = Extension.BATCH_SIZE
            extension.iterator(ite.snapshot(version))
          }
          if (onwait) onwait(seq)
        }
      })
    } else {
      opts = encRange(keyEncoding, { ...opts, sub: this._sub })
    }

    const ite = new RangeIterator(
      new Batch(this, this._makeSnapshot(), null, false, opts),
      null,
      opts
    )
    return ite
  }

  createReadStream(range, opts) {
    const signal = (opts && opts.signal) || null
    return iteratorToStream(this.createRangeIterator(range, opts), signal)
  }

  createHistoryStream(opts) {
    const session = opts && opts.live ? this.core.session() : this._makeSnapshot()
    const signal = (opts && opts.signal) || null
    return iteratorToStream(
      new HistoryIterator(new Batch(this, session, null, false, opts), opts),
      signal
    )
  }

  createDiffStream(right, range, opts) {
    if (typeof right === 'number') right = this.checkout(Math.max(1, right), { reuseSession: true })

    // backwards compat range arg
    opts = opts ? { ...opts, ...range } : range

    const signal = (opts && opts.signal) || null

    const keyEncoding = opts && opts.keyEncoding ? codecs(opts.keyEncoding) : this.keyEncoding
    if (keyEncoding) opts = encRange(keyEncoding, { ...opts, sub: this._sub })

    let done
    let closing
    let ite

    const left = this

    const rs = new Readable({
      signal,
      eagerOpen: true,
      async open(cb) {
        try {
          if (right.opened === false) await right.ready()
          if (left.opened === false) await left.ready()
        } catch (err) {
          cb(err)
          return
        }

        if (closing) {
          cb(null)
          return
        }

        if (left.core.closing || right.core.closing) {
          cb(new Error('Bee closed'))
          return
        }

        const snapshot = right.version > left.version ? right._makeSnapshot() : left._makeSnapshot()

        done = cb
        ite = new DiffIterator(
          new Batch(left, snapshot, null, false, opts),
          new Batch(right, snapshot, null, false, opts),
          opts
        )
        ite.open().then(fin, fin)
      },
      read(cb) {
        done = cb
        ite.next().then(push, fin)
      },
      predestroy() {
        if (!ite) {
          closing = Promise.resolve()
        } else {
          closing = ite.close()
          closing.catch(noop)
        }
      },
      destroy(cb) {
        done = cb
        if (!closing) closing = ite.close()
        closing.then(fin, fin)
      }
    })

    return rs

    function fin(err) {
      done(err)
    }

    function push(val) {
      rs.push(val)
      done(null)
    }
  }

  get(key, opts) {
    const b = new Batch(this, this._makeSnapshot(), null, true, opts)
    return b.get(key)
  }

  getBySeq(seq, opts) {
    const b = new Batch(this, this._makeSnapshot(), null, true, opts)
    return b.getBySeq(seq)
  }

  put(key, value, opts) {
    const b = new Batch(this, this.core, null, true, opts)
    return b.put(key, value, opts)
  }

  batch(opts) {
    return new Batch(this, this.core, mutexify(), true, opts)
  }

  del(key, opts) {
    const b = new Batch(this, this.core, null, true, opts)
    return b.del(key, opts)
  }

  watch(range, opts) {
    if (!this._watchers) throw new Error('Can only watch the main bee instance')
    return new Watcher(this, range, opts)
  }

  async getAndWatch(key, opts) {
    if (!this._watchers) throw new Error('Can only watch the main bee instance')

    const watcher = new EntryWatcher(this, key, opts)
    await watcher._debouncedUpdate()

    if (this.closing) {
      await watcher.close()
      throw new Error('Bee closed')
    }

    return watcher
  }

  _onappend() {
    for (const watcher of this._watchers) {
      watcher._onappend()
    }

    for (const watcher of this._entryWatchers) {
      watcher._onappend()
    }
  }

  _ontruncate(length) {
    for (const watcher of this._watchers) {
      watcher._ontruncate()
    }

    for (const watcher of this._entryWatchers) {
      watcher._ontruncate()
    }

    this._nodeCache.gc(length)
    this._keyCache.gc(length)
  }

  _makeSnapshot() {
    if (this._sessions === false) return this.core
    // TODO: better if we could encapsulate this in hypercore in the future
    return this._checkout <= this.core.length || this._checkout <= 1
      ? this.core.snapshot()
      : this.core.session({ snapshot: false })
  }

  async clearUnlinked(options = {}) {
    await this.ready()

    const { gte = 0, lt = this.version - 1, batchSize = 4096, wait = true } = options
    const checkout = this.version

    let prev = this.batch({ wait: false, checkout: gte })
    let b = this.batch({ wait, checkout })

    const iteBatch = this.batch({ wait: false, checkout })
    const ite = new LocalBlockIterator(iteBatch, { gte, lt })
    await ite.open()

    let ticks = 0

    while (true) {
      const data = await ite.next()
      if (!data) break

      if (!(await isLinked(b, data))) {
        if (b.core.closing || this.core.closing || this.closing) break
        await this.core.clear(data.seq)
      }

      const prevNode = await prev.get(data.key, { finalize: false }).catch(toNull)

      if (prevNode && !(await isLinked(b, prevNode))) {
        if (b.core.closing || this.core.closing || this.closing) break
        await this.core.clear(prevNode.seq)
      }

      if (ticks++ >= batchSize) {
        ticks = 0
        await prev.close()
        await b.close()

        prev = this.batch({ wait: false, checkout: gte })
        b = this.batch({ wait, checkout })
      }
    }

    if (b.core.closing || this.core.closing || this.closing) throw new Error('Core is closed')

    await b.close()
    await prev.close()
    await ite.close()
    await iteBatch.close()

    return lt
  }

  checkout(version, opts = {}) {
    if (version === 0) version = 1

    // same as above, just checkout isn't set yet...
    const snap =
      opts.reuseSession || this._sessions === false
        ? this.core
        : version <= this.core.length || version <= 1
          ? this.core.snapshot()
          : this.core.session({ snapshot: false })

    return new Hyperbee(snap, {
      _view: true,
      _sub: false,
      prefix: this.prefix,
      sep: this.sep,
      lock: this.lock,
      checkout: version,
      keyEncoding: opts.keyEncoding || this.keyEncoding,
      valueEncoding: opts.valueEncoding || this.valueEncoding,
      extension: this.extension !== null ? this.extension : false
    })
  }

  snapshot(opts) {
    return this.checkout(
      this.core.opened === false || this._checkout <= 0 ? -1 : Math.max(1, this.version),
      opts
    )
  }

  sub(prefix, opts = {}) {
    let sep = opts.sep || this.sep
    if (!b4a.isBuffer(sep)) sep = b4a.from(sep)

    prefix = b4a.concat([this.prefix || EMPTY, b4a.from(prefix), sep])

    const valueEncoding = codecs(opts.valueEncoding || this.valueEncoding)
    const keyEncoding = codecs(opts.keyEncoding || this._unprefixedKeyEncoding)

    return new Hyperbee(this.core, {
      _view: true,
      _sub: true,
      prefix,
      sep: this.sep,
      lock: this.lock,
      checkout: this._checkout,
      valueEncoding,
      keyEncoding,
      extension: this.extension !== null ? this.extension : false,
      metadata: this.metadata
    })
  }

  async getHeader(opts) {
    const blk = await this.core.get(0, opts)
    try {
      return blk && Header.decode(blk)
    } catch {
      throw DECODING_ERROR()
    }
  }

  async _close() {
    if (!this._view) {
      if (this._keyCache) this._keyCache.clear()
      if (this._nodeCache) this._nodeCache.clear()
    }

    if (this._watchers) {
      this.core.off('append', this._onappendBound)
      this.core.off('truncate', this._ontruncateBound)

      while (this._watchers.length) {
        await this._watchers[this._watchers.length - 1].close()
      }
    }

    if (this._entryWatchers) {
      while (this._entryWatchers.length) {
        await this._entryWatchers[this._entryWatchers.length - 1].close()
      }
    }

    while (this._batches.length) {
      await this._batches[this._batches.length - 1].close()
    }

    return this.core.close()
  }

  static async isHyperbee(core, opts) {
    await core.ready()

    const blk0 = await core.get(0, opts)
    if (blk0 === null) throw BLOCK_NOT_AVAILABLE()

    try {
      return Header.decode(blk0).protocol === 'hyperbee'
    } catch (err) {
      // undecodable
      return false
    }
  }
}

class Batch {
  constructor(tree, core, batchLock, cache, options = {}) {
    this.tree = tree
    // this.feed is now deprecated, and will be this.core going forward
    this.feed = core
    this.core = core
    this.index = tree._batches.push(this) - 1
    this.blocks = cache ? new Map() : null
    this.autoFlush = !batchLock
    this.maxBlocksCached = options.maxBlocksCached || 128
    this.rootSeq = 0
    this.root = null
    this.length = 0
    this.checkout = options.checkout === undefined ? -1 : options.checkout
    this.options = options
    this.locked = null
    this.batchLock = batchLock
    this.onseq = this.options.onseq || noop
    this.appending = null
    this.isSnapshot = this.core !== this.tree.core
    this.shouldUpdate = this.options.update !== false
    this.updating = null
    this.encoding = {
      key: options.keyEncoding ? codecs(options.keyEncoding) : tree.keyEncoding,
      value: options.valueEncoding ? codecs(options.valueEncoding) : tree.valueEncoding
    }
  }

  async ready() {
    if (this.core.opened === false) await this.core.ready()
    if (this.tree.opened === false) await this.tree.ready()
  }

  async lock() {
    if (this.tree.readonly) throw new Error('Hyperbee is marked as read-only')
    if (this.locked === null) this.locked = await this.tree.lock()
  }

  get version() {
    if (this.checkout !== -1) return Math.max(1, this.checkout)
    return Math.max(1, this.tree._checkout || this.core.length + this.length)
  }

  async getRoot(ensureHeader) {
    await this.ready()
    if (ensureHeader) {
      if (this.core.length === 0 && this.core.writable && !this.tree.readonly) {
        await this.core.append(
          Header.encode({
            protocol: 'hyperbee',
            metadata: this.tree.metadata
          })
        )
      }
    }
    if (this.tree._checkout === 0 && this.checkout === -1 && this.shouldUpdate) {
      if (this.updating === null) this.updating = this.core.update()
      await this.updating
    }
    if (this.version < 2) return null
    return (await this.getBlock(this.version - 1)).getTreeNode(0)
  }

  async getKey(seq) {
    await this.tree._cacheLock.enter(seq)

    try {
      const k = this.core.fork === this.tree.core.fork ? this.tree._keyCache.get(seq) : null
      if (k !== null) return k
      const key = (await this._getBlock(seq)).key
      if (this.core.fork === this.tree.core.fork) this.tree._keyCache.set(seq, key)
      return key
    } finally {
      this.tree._cacheLock.exit(seq)
    }
  }

  async _getNode(seq) {
    const cached =
      this.tree._nodeCache !== null && this.core.fork === this.tree.core.fork
        ? this.tree._nodeCache.get(seq)
        : null
    if (cached !== null) return cached
    const entry = await this.core.get(seq, {
      ...this.options,
      valueEncoding: Node
    })
    if (entry === null) throw BLOCK_NOT_AVAILABLE()
    const wrap = copyEntry(entry)
    if (this.core.fork === this.tree.core.fork && this.tree._nodeCache !== null) {
      this.tree._nodeCache.set(seq, wrap)
    }
    return wrap
  }

  async getBlock(seq) {
    if (this.rootSeq === 0) this.rootSeq = seq
    await this.tree._cacheLock.enter(seq)

    try {
      return await this._getBlock(seq)
    } finally {
      this.tree._cacheLock.exit(seq)
    }
  }

  async _getBlock(seq) {
    let b = this.blocks && this.blocks.get(seq)
    if (b) return b
    this.onseq(seq)
    const entry = await this._getNode(seq)
    b = this.blocks && this.blocks.get(seq)
    if (b) return b
    b = new BlockEntry(seq, this, entry)
    if (this.blocks && this.blocks.size - this.length < this.maxBlocksCached) {
      this.blocks.set(seq, b)
    }
    return b
  }

  _onwait(key) {
    this.options.onwait = null
    this.tree.extension.get(this.rootSeq + 1, key)
  }

  _getEncoding(opts) {
    if (!opts) return this.encoding
    return {
      key: opts.keyEncoding ? codecs(opts.keyEncoding) : this.encoding.key,
      value: opts.valueEncoding ? codecs(opts.valueEncoding) : this.encoding.value
    }
  }

  peek(range, opts) {
    return iteratorPeek(this.createRangeIterator(range, { ...opts, limit: 1 }))
  }

  createRangeIterator(range, opts = {}) {
    // backwards compat range arg
    opts = opts ? { ...opts, ...range } : range

    const encoding = this._getEncoding(opts)
    return new RangeIterator(
      this,
      encoding,
      encRange(encoding.key, { ...opts, sub: this.tree._sub })
    )
  }

  createReadStream(range, opts) {
    const signal = (opts && opts.signal) || null
    return iteratorToStream(this.createRangeIterator(range, opts), signal)
  }

  async getBySeq(seq, opts) {
    const encoding = this._getEncoding(opts)

    try {
      const block = (await this.getBlock(seq)).final(encoding)
      return { key: block.key, value: block.value }
    } finally {
      await this._closeSnapshot()
    }
  }

  async get(key, opts) {
    const encoding = this._getEncoding(opts)
    const finalize = opts ? opts.finalize !== false : true

    try {
      return await this._get(key, encoding, finalize)
    } finally {
      await this._closeSnapshot()
    }
  }

  async _get(key, encoding, finalize) {
    key = enc(encoding.key, key)

    if (this.tree.extension !== null && this.options.extension !== false) {
      this.options.onwait = this._onwait.bind(this, key)
    }

    let node = await this.getRoot(false)
    if (!node) return null

    while (true) {
      if (node.block.isTarget(key)) {
        return node.block.isDeletion() ? null : finalize ? node.block.final(encoding) : node.block
      }

      let s = 0
      let e = node.keys.length
      let c

      while (s < e) {
        const mid = (s + e) >> 1

        c = b4a.compare(key, await node.getKey(mid))

        if (c === 0) {
          const block = await this.getBlock(node.keys[mid].seq)
          return finalize ? block.final(encoding) : block
        }

        if (c < 0) e = mid
        else s = mid + 1
      }

      if (!node.children.length) return null

      const i = c < 0 ? e : s
      node = await node.getChildNode(i)
    }
  }

  async links(key, seq) {
    let node = await this.getRoot(false)
    if (!node) return false

    if (node.block.seq === seq) return true

    while (true) {
      if (node.block.isTarget(key)) return false

      let s = 0
      let e = node.keys.length
      let c

      while (s < e) {
        const mid = (s + e) >> 1

        if (node.keys[mid].seq === seq) return true
        c = b4a.compare(key, await node.getKey(mid))

        if (c === 0) return false

        if (c < 0) e = mid
        else s = mid + 1
      }

      if (!node.children.length) return false

      const i = c < 0 ? e : s
      node = await node.getChildNode(i)
      if (node.block.seq === seq) return true
    }
  }

  async put(key, value, opts) {
    const release = this.batchLock ? await this.batchLock() : null

    const cas = (opts && opts.cas) || null
    const encoding = this._getEncoding(opts)

    if (!this.locked) await this.lock()
    if (!release) return this._put(key, value, encoding, cas)

    try {
      return await this._put(key, value, encoding, cas)
    } finally {
      release()
    }
  }

  async _put(key, value, encoding, cas) {
    const newNode = {
      seq: 0,
      key,
      value
    }
    key = enc(encoding.key, key)
    value = enc(encoding.value, value)

    const stack = []

    let root
    let node = (root = await this.getRoot(true))
    if (!node) node = root = TreeNode.create(null)

    const seq = (newNode.seq = this.core.length + this.length)
    const target = new Key(seq, key)

    while (node.children.length) {
      stack.push(node)
      node.changed = true // changed, but compressible

      let s = 0
      let e = node.keys.length
      let c

      while (s < e) {
        const mid = (s + e) >> 1
        c = b4a.compare(target.value, await node.getKey(mid))

        if (c === 0) {
          if (cas) {
            const prev = await node.getKeyNode(mid)
            if (!(await cas(prev.final(encoding), newNode))) return this._unlockMaybe()
          }
          if (!this.tree.alwaysDuplicate) {
            const prev = await node.getKeyNode(mid)
            if (sameValue(prev.value, value)) return this._unlockMaybe()
          }
          node.setKey(mid, target)
          return this._append(root, seq, key, value)
        }

        if (c < 0) e = mid
        else s = mid + 1
      }

      const i = c < 0 ? e : s
      node = await node.getChildNode(i)
    }

    let needsSplit = !(await node.insertKey(target, value, null, newNode, encoding, cas))
    if (!node.changed) return this._unlockMaybe()

    while (needsSplit) {
      const parent = stack.pop()
      const { median, right } = await node.split()

      if (parent) {
        needsSplit = !(await parent.insertKey(median, value, right, null, encoding, null))
        node = parent
      } else {
        root = TreeNode.create(node.block)
        root.changed = true
        root.keys.push(median)
        root.children.push(new Child(0, 0, node), new Child(0, 0, right))
        needsSplit = false
      }
    }

    return this._append(root, seq, key, value)
  }

  async del(key, opts) {
    const release = this.batchLock ? await this.batchLock() : null
    const cas = (opts && opts.cas) || null
    const encoding = this._getEncoding(opts)

    if (!this.locked) await this.lock()
    if (!release) return this._del(key, encoding, cas)

    try {
      return await this._del(key, encoding, cas)
    } finally {
      release()
    }
  }

  async _del(key, encoding, cas) {
    const delNode = {
      seq: 0,
      key,
      value: null
    }

    key = enc(encoding.key, key)

    const stack = []

    let node = await this.getRoot(true)
    if (!node) return this._unlockMaybe()

    const seq = (delNode.seq = this.core.length + this.length)

    while (true) {
      stack.push(node)

      let s = 0
      let e = node.keys.length
      let c

      while (s < e) {
        const mid = (s + e) >> 1
        c = b4a.compare(key, await node.getKey(mid))

        if (c === 0) {
          if (cas) {
            const prev = await node.getKeyNode(mid)
            if (!(await cas(prev.final(encoding), delNode))) return this._unlockMaybe()
          }
          if (node.children.length) await setKeyToNearestLeaf(node, mid, stack)
          else node.removeKey(mid)
          // we mark these as changed late, so we don't rewrite them if it is a 404
          for (const node of stack) node.changed = true
          return this._append(await rebalance(stack), seq, key, null)
        }

        if (c < 0) e = mid
        else s = mid + 1
      }

      if (!node.children.length) return this._unlockMaybe()

      const i = c < 0 ? e : s
      node = await node.getChildNode(i)
    }
  }

  async _closeSnapshot() {
    if (this.isSnapshot) {
      await this.core.close()
      this._finalize()
    }
  }

  async close() {
    if (this.isSnapshot) return this._closeSnapshot()

    this.root = null
    if (this.blocks) this.blocks.clear()
    this.length = 0
    this._unlock()
  }

  destroy() {
    // compat, remove later
    this.close().catch(noop)
  }

  toBlocks() {
    if (this.appending) return this.appending

    const batch = new Array(this.length)

    for (let i = 0; i < this.length; i++) {
      const seq = this.core.length + i
      const { pendingIndex, key, value } = this.blocks.get(seq)

      if (i < this.length - 1) {
        pendingIndex[0] = null
        let j = 0

        while (j < pendingIndex.length) {
          const idx = pendingIndex[j]
          if (idx !== null && idx.seq === seq) {
            idx.offset = j++
            continue
          }
          if (j === pendingIndex.length - 1) pendingIndex.pop()
          else pendingIndex[j] = pendingIndex.pop()
        }
      }

      batch[i] = Node.encode({
        key,
        value,
        index: deflate(pendingIndex)
      })
    }

    this.appending = batch
    return batch
  }

  flush() {
    if (!this.length) return this.close()

    const batch = this.toBlocks()

    this.root = null
    this.blocks.clear()
    this.length = 0

    return this._appendBatch(batch)
  }

  _unlockMaybe() {
    if (this.autoFlush) this._unlock()
  }

  _unlock() {
    const locked = this.locked
    this.locked = null
    if (locked !== null) locked()
    this._finalize()
  }

  _finalize() {
    // technically finalize can be called more than once, so here we just check if we already have been removed
    if (this.index >= this.tree._batches.length || this.tree._batches[this.index] !== this) return
    const top = this.tree._batches.pop()
    if (top === this) return
    top.index = this.index
    this.tree._batches[top.index] = top
  }

  _append(root, seq, key, value) {
    const index = []
    root.indexChanges(index, seq)
    index[0] = new Child(seq, 0, root)

    if (!this.autoFlush) {
      const block = new BatchEntry(seq, this, key, value, index)
      root.block = block
      this.root = root
      this.length++
      this.blocks.set(seq, block)

      root.updateChildren(seq, block)
      return
    }

    return this._appendBatch(
      Node.encode({
        key,
        value,
        index: deflate(index)
      })
    )
  }

  async _appendBatch(raw) {
    try {
      await this.core.append(raw)
    } finally {
      this._unlock()
    }
  }
}

class EntryWatcher extends ReadyResource {
  constructor(bee, key, opts = {}) {
    super()

    this.keyEncoding = opts.keyEncoding || bee.keyEncoding
    this.valueEncoding = opts.valueEncoding || bee.valueEncoding

    this.index = bee._entryWatchers.push(this) - 1
    this.bee = bee

    this.key = key
    this.node = null

    this._forceUpdate = false
    this._debouncedUpdate = debounce(this._processUpdate.bind(this))
  }

  _close() {
    const top = this.bee._entryWatchers.pop()
    if (top !== this) {
      top.index = this.index
      this.bee._entryWatchers[top.index] = top
    }
  }

  _onappend() {
    this._debouncedUpdate()
  }

  _ontruncate() {
    this._forceUpdate = true
    this._debouncedUpdate()
  }

  async _processUpdate() {
    const force = this._forceUpdate
    this._forceUpdate = false

    let newNode
    try {
      newNode = await this.bee.get(this.key, {
        keyEncoding: this.keyEncoding,
        valueEncoding: this.valueEncoding
      })
    } catch (e) {
      if (e.code === 'SNAPSHOT_NOT_AVAILABLE') {
        // There was a truncate event before the get resolved
        // So this handler will run again anyway
        return
      } else if (this.bee.closing) {
        this.close().catch(safetyCatch)
        return
      }
      this.emit('error', e)
      return
    }

    if (force || newNode?.seq !== this.node?.seq) {
      this.node = newNode
      this.emit('update')
    }
  }
}

class Watcher extends ReadyResource {
  constructor(bee, range, opts = {}) {
    super()

    this.keyEncoding = opts.keyEncoding || bee.keyEncoding
    this.valueEncoding = opts.valueEncoding || bee.valueEncoding
    this.index = bee._watchers.push(this) - 1
    this.bee = bee
    this.core = bee.core

    this.latestDiff = 0
    this.range = range
    this.map = opts.map || defaultWatchMap

    this.current = null
    this.previous = null
    this.currentMapped = null
    this.previousMapped = null
    this.stream = null

    this._lock = mutexify()
    this._flowing = false
    this._resolveOnChange = null
    this._differ = opts.differ || defaultDiffer
    this._eager = !!opts.eager
    this._onchange = opts.onchange || null

    this.on('newListener', autoFlowOnUpdate)

    this.ready().catch(safetyCatch)
  }

  async _consume() {
    if (this._flowing) return
    try {
      for await (const _ of this) {
      } // eslint-disable-line
    } catch {}
  }

  async _open() {
    await this.bee.ready()

    const opts = {
      keyEncoding: this.keyEncoding,
      valueEncoding: this.valueEncoding
    }

    // Point from which to start watching
    this.current = this._eager ? this.bee.checkout(1, opts) : this.bee.snapshot(opts)
    await this.current.ready()

    if (this._onchange) {
      if (this._eager) await this._onchange()
      this._consume()
    }
  }

  [Symbol.asyncIterator]() {
    this._flowing = true
    return this
  }

  _ontruncate() {
    this._onappend()
  }

  _onappend() {
    const resolve = this._resolveOnChange
    this._resolveOnChange = null
    if (resolve) resolve()
  }

  async _waitForChanges() {
    if (this.current.version < this.bee.version || this.closing) return

    await new Promise((resolve) => {
      this._resolveOnChange = resolve
    })
  }

  async next() {
    try {
      return await this._next()
    } catch (err) {
      if (this.closing) return { value: undefined, done: true }
      await this.close()
      throw err
    }
  }

  async _next() {
    const release = await this._lock()

    try {
      if (this.closing) return { value: undefined, done: true }

      if (!this.opened) await this.ready()

      while (true) {
        await this._waitForChanges()

        if (this.closing) return { value: undefined, done: true }

        await this._closePrevious()
        this.previous = this.current.snapshot()

        await this._closeCurrent()
        this.current = this.bee.snapshot({
          keyEncoding: this.keyEncoding,
          valueEncoding: this.valueEncoding
        })

        await this.current.ready()
        await this.previous.ready()

        if (this.current.core.fork !== this.previous.core.fork) {
          return await this._yield()
        }

        this.stream = this._differ(this.current, this.previous, this.range)

        try {
          for await (const data of this.stream) {
            // eslint-disable-line
            return await this._yield()
          }
        } finally {
          this.stream = null
        }
      }
    } finally {
      release()
    }
  }

  async _yield() {
    this.currentMapped = this.map(this.current)
    this.previousMapped = this.map(this.previous)

    if (this._onchange) {
      try {
        await this._onchange()
      } catch (err) {
        safetyCatch(err)
      }
    }

    this.emit('update')
    return { done: false, value: [this.currentMapped, this.previousMapped] }
  }

  async return() {
    await this.close()
    return { done: true }
  }

  async _close() {
    const top = this.bee._watchers.pop()
    if (top !== this) {
      top.index = this.index
      this.bee._watchers[top.index] = top
    }

    if (this.stream && !this.stream.destroying) {
      this.stream.destroy()
    }

    this._onappend() // Continue execution being closed

    await this._closeCurrent().catch(safetyCatch)
    await this._closePrevious().catch(safetyCatch)

    const release = await this._lock()
    release()
  }

  destroy() {
    return this.close()
  }

  async _closeCurrent() {
    if (this.currentMapped) await this.currentMapped.close()
    if (this.current) await this.current.close()
    this.current = this.currentMapped = null
  }

  async _closePrevious() {
    if (this.previousMapped) await this.previousMapped.close()
    if (this.previous) await this.previous.close()
    this.previous = this.previousMapped = null
  }
}

function autoFlowOnUpdate(name) {
  if (name === 'update') this._consume()
}

function defaultWatchMap(snapshot) {
  return snapshot
}

async function leafSize(node, goLeft) {
  while (node.children.length) node = await node.getChildNode(goLeft ? 0 : node.children.length - 1)
  return node.keys.length
}

async function setKeyToNearestLeaf(node, index, stack) {
  let [left, right] = await Promise.all([node.getChildNode(index), node.getChildNode(index + 1)])
  const [ls, rs] = await Promise.all([leafSize(left, false), leafSize(right, true)])

  if (ls < rs) {
    // if fewer leaves on the left
    stack.push(right)
    while (right.children.length) stack.push((right = right.children[0].value))
    node.keys[index] = right.keys.shift()
  } else {
    // if fewer leaves on the right
    stack.push(left)
    while (left.children.length) stack.push((left = left.children[left.children.length - 1].value))
    node.keys[index] = left.keys.pop()
  }
}

async function rebalance(stack) {
  const root = stack[0]

  while (stack.length > 1) {
    const node = stack.pop()
    const parent = stack[stack.length - 1]

    if (node.keys.length >= MIN_KEYS) return root

    let { left, index, right } = await node.siblings(parent)

    // maybe borrow from left sibling?
    if (left && left.keys.length > MIN_KEYS) {
      left.changed = true
      node.keys.unshift(parent.keys[index - 1])
      if (left.children.length) node.children.unshift(left.children.pop())
      parent.keys[index - 1] = left.keys.pop()
      return root
    }

    // maybe borrow from right sibling?
    if (right && right.keys.length > MIN_KEYS) {
      right.changed = true
      node.keys.push(parent.keys[index])
      if (right.children.length) node.children.push(right.children.shift())
      parent.keys[index] = right.keys.shift()
      return root
    }

    // merge node with another sibling
    if (left) {
      index--
      right = node
    } else {
      left = node
    }

    left.merge(right, parent.keys[index])
    parent.removeKey(index)
  }

  // check if the tree shrunk
  if (!root.keys.length && root.children.length) return root.getChildNode(0)
  return root
}

function iteratorToStream(ite, signal) {
  let done
  let closing

  const rs = new Readable({
    signal,
    open(cb) {
      done = cb
      ite.open().then(fin, fin)
    },
    read(cb) {
      done = cb
      ite.next().then(push, fin)
    },
    predestroy() {
      closing = ite.close()
      closing.catch(noop)
    },
    destroy(cb) {
      done = cb
      if (!closing) closing = ite.close()
      closing.then(fin, fin)
    }
  })

  return rs

  function fin(err) {
    done(err)
  }

  function push(val) {
    rs.push(val)
    done(null)
  }
}

async function iteratorPeek(ite) {
  try {
    await ite.open()
    return await ite.next()
  } finally {
    await ite.close()
  }
}

function encRange(e, opts) {
  if (!e) return opts

  if (e.encodeRange) {
    const r = e.encodeRange({
      gt: opts.gt,
      gte: opts.gte,
      lt: opts.lt,
      lte: opts.lte
    })
    opts.gt = r.gt
    opts.gte = r.gte
    opts.lt = r.lt
    opts.lte = r.lte
    return opts
  }

  if (opts.gt !== undefined) opts.gt = enc(e, opts.gt)
  if (opts.gte !== undefined) opts.gte = enc(e, opts.gte)
  if (opts.lt !== undefined) opts.lt = enc(e, opts.lt)
  if (opts.lte !== undefined) opts.lte = enc(e, opts.lte)
  if (opts.sub && !opts.gt && !opts.gte) opts.gt = enc(e, SEP)
  if (opts.sub && !opts.lt && !opts.lte) opts.lt = bump(enc(e, EMPTY))

  return opts
}

function bump(key) {
  // key should have been copied by enc above before hitting this
  key[key.length - 1]++
  return key
}

function enc(e, v) {
  if (v === undefined || v === null) return null
  if (e !== null) return e.encode(v)
  if (typeof v === 'string') return b4a.from(v)
  return v
}

function prefixEncoding(prefix, keyEncoding) {
  return {
    encode(key) {
      return b4a.concat([prefix, b4a.isBuffer(key) ? key : enc(keyEncoding, key)])
    },
    decode(key) {
      const sliced = key.slice(prefix.length, key.length)
      return keyEncoding ? keyEncoding.decode(sliced) : sliced
    }
  }
}

function copyEntry(entry) {
  let key = entry.key
  let value = entry.value
  let index = entry.index

  // key, value and index all refer to the same buffer (one hypercore block)
  // If together they are larger than half the buffer's byteLength,
  // this means that they got their own private slab (see Buffer.allocUnsafe docs)
  // so no need to unslab
  const size =
    key.byteLength +
    (value === null ? 0 : value.byteLength) +
    (index === null ? 0 : index.byteLength)
  if (2 * size < key.buffer.byteLength) {
    const [newKey, newValue, newIndex] = unslabAll([entry.key, entry.value, entry.index])
    key = newKey
    value = newValue
    index = newIndex
  }

  return {
    key,
    value,
    index,
    inflated: null
  }
}

function defaultDiffer(currentSnap, previousSnap, opts) {
  return currentSnap.createDiffStream(previousSnap, opts)
}

function toNull() {
  return null
}

function getBackingCore(core) {
  if (core.core) return core
  if (core.getBackingCore) return core.getBackingCore().session
  return null
}

function sameValue(a, b) {
  return a === b || (a !== null && b !== null && b4a.equals(a, b))
}

function noop() {}

function getExtension(db, opts) {
  if (opts.extension === false) return null
  if (opts.extension && opts.extension !== true) return opts.extension
  return Extension.register(db)
}

async function isLinked(batch, block) {
  const seq = block.seq
  block.inflate()

  const keys = [block.key]
  const wait = batch.options.wait

  for (const l of block.index.levels) {
    if (!l.keys.length) continue
    batch.options.wait = false
    try {
      keys.push(await batch.getKey(l.keys[0].seq))
    } catch {}
    batch.options.wait = wait
  }

  for (const k of keys) {
    if (await batch.links(k, seq)) return true
  }

  if (batch.core.closing) throw new Error('Core is closed')

  return false
}

module.exports = Hyperbee
const b4a = require('b4a')

class SubTree {
  constructor(node, parent) {
    this.node = node
    this.parent = parent

    this.isKey = node.children.length === 0
    this.i = this.isKey ? 1 : 0
    this.n = 0

    const child = this.isKey ? null : this.node.children[0]
    this.seq = child !== null ? child.seq : this.node.keys[0].seq
    this.offset = child !== null ? child.offset : 0
  }

  next() {
    this.i++
    this.isKey = (this.i & 1) === 1
    if (!this.isKey && !this.node.children.length) this.i++
    return this.update()
  }

  async bisect(key, incl) {
    let s = 0
    let e = this.node.keys.length
    let c

    while (s < e) {
      const mid = (s + e) >> 1
      c = cmp(key, await this.node.getKey(mid))

      if (c === 0) {
        if (incl) this.i = mid * 2 + 1
        else this.i = mid * 2 + (this.node.children.length ? 2 : 3)
        return true
      }

      if (c < 0) e = mid
      else s = mid + 1
    }

    const i = c < 0 ? e : s
    this.i = 2 * i + (this.node.children.length ? 0 : 1)
    return this.node.children.length === 0
  }

  update() {
    this.isKey = (this.i & 1) === 1
    this.n = this.i >> 1
    if (this.n >= (this.isKey ? this.node.keys.length : this.node.children.length)) return false
    const child = this.isKey ? null : this.node.children[this.n]
    this.seq = child !== null ? child.seq : this.node.keys[this.n].seq
    this.offset = child !== null ? child.offset : 0
    return true
  }

  async key() {
    return this.n < this.node.keys.length
      ? this.node.getKey(this.n)
      : this.parent && this.parent.key()
  }

  async compare(tree) {
    const [a, b] = await Promise.all([this.key(), tree.key()])
    return cmp(a, b)
  }
}

class TreeIterator {
  constructor(batch, opts) {
    this.batch = batch
    this.stack = []
    this.lt = opts.lt || opts.lte || null
    this.lte = !!opts.lte
    this.gt = opts.gt || opts.gte || null
    this.gte = !!opts.gte
    this.seeking = !!this.gt
    this.encoding = opts.encoding || batch.encoding
  }

  async open() {
    const node = await this.batch.getRoot(false)
    if (!node || !node.keys.length) return
    const tree = new SubTree(node, null)
    if (this.seeking && !(await this._seek(tree))) return
    this.stack.push(tree)
  }

  async _seek(tree) {
    const done = await tree.bisect(this.gt, this.gte)
    const oob = !tree.update()
    if (done || oob) {
      this.seeking = false
      if (oob) return false
    }
    return true
  }

  peek() {
    if (!this.stack.length) return null
    return this.stack[this.stack.length - 1]
  }

  skip() {
    if (!this.stack.length) return
    if (!this.stack[this.stack.length - 1].next()) this.stack.pop()
  }

  async nextKey() {
    let n = null
    while (this.stack.length && n === null) n = await this.next()
    if (n === null) return null
    if (!this.lt) return n.final(this.encoding)

    const c = cmp(n.key, this.lt)
    if (this.lte ? c <= 0 : c < 0) return n.final(this.encoding)
    this.stack = []
    return null
  }

  async next() {
    if (!this.stack.length) return null

    const top = this.stack[this.stack.length - 1]
    const { isKey, n, seq } = top

    if (!top.next()) {
      this.stack.pop()
    }

    if (isKey) {
      this.seeking = false
      return this.batch.getBlock(seq)
    }

    const child = await top.node.getChildNode(n)
    top.node.children[n] = null // unlink to save memory
    const tree = new SubTree(child, top)
    if (this.seeking && !(await this._seek(tree))) return null
    this.stack.push(tree)

    return null
  }

  close() {
    return this.batch._closeSnapshot()
  }
}

module.exports = class DiffIterator {
  constructor(left, right, opts = {}) {
    this.left = new TreeIterator(left, opts)
    this.right = new TreeIterator(right, opts)
    this.limit = typeof opts.limit === 'number' ? opts.limit : -1
  }

  async open() {
    await Promise.all([this.left.open(), this.right.open()])
  }

  async next() {
    if (this.limit === 0) return null
    const res = await this._next()
    if (!res || (res.left === null && res.right === null)) return null
    this.limit--
    return res
  }

  async _next() {
    const a = this.left
    const b = this.right

    while (true) {
      const [l, r] = await Promise.all([a.peek(), b.peek()])

      if (!l && !r) return null
      if (!l) return { left: null, right: await b.nextKey() }
      if (!r) return { left: await a.nextKey(), right: null }

      if (l.seq === r.seq && l.isKey === r.isKey && l.offset === r.offset) {
        a.skip()
        b.skip()
        continue
      }

      const c = await l.compare(r)

      if (l.isKey && !r.isKey) {
        await b.next()
        continue
      }

      if (!l.isKey && r.isKey) {
        await a.next()
        continue
      }

      if (l.isKey && r.isKey) {
        if (c === 0) return { left: await a.nextKey(), right: await b.nextKey() }
        if (c < 0) return { left: await a.nextKey(), right: null }
        return { left: null, right: await b.nextKey() }
      }

      if (c === 0) await Promise.all([a.next(), b.next()])
      else if (c < 0) await b.next()
      else await a.next()
    }
  }

  async close() {
    await Promise.all([this.left.close(), this.right.close()])
  }
}

function cmp(a, b) {
  if (!a) return b ? 1 : 0
  if (!b) return a ? -1 : 0
  return b4a.compare(a, b)
}
module.exports = class HistoryIterator {
  constructor(batch, opts = {}) {
    this.batch = batch
    this.options = opts
    this.live = !!opts.live
    this.gte = 0
    this.lt = 0
    this.reverse = !!opts.reverse
    this.limit = typeof opts.limit === 'number' ? opts.limit : -1
    this.encoding = opts.encoding || batch.encoding
    if (this.live && this.reverse) {
      throw new Error('Cannot have both live and reverse enabled')
    }
  }

  async open() {
    await this.batch.getRoot(false) // does the update dance
    this.gte = gte(this.options, this.batch.version)
    this.lt = this.live ? Infinity : lt(this.options, this.batch.version)
  }

  async next() {
    if (this.limit === 0) return null
    if (this.limit > 0) this.limit--

    if (this.gte >= this.lt) return null

    if (this.reverse) {
      if (this.lt <= 1) return null
      return final(await this.batch.getBlock(--this.lt), this.encoding)
    }

    return final(await this.batch.getBlock(this.gte++), this.encoding)
  }

  close() {
    return this.batch._closeSnapshot()
  }
}

function final(node, encoding) {
  const type = node.isDeletion() ? 'del' : 'put'
  return { type, ...node.final(encoding) }
}

function gte(opts, version) {
  if (opts.gt) return (opts.gt < 0 ? opts.gt + version : opts.gt) + 1
  const gte = opts.gte || opts.since || 1
  return gte < 0 ? gte + version : gte
}

function lt(opts, version) {
  if (opts.lte === 0 || opts.lt === 0 || opts.end === 0) return 0
  if (opts.lte) return (opts.lte < 0 ? opts.lte + version : opts.lte) + 1
  const lt = opts.lt || opts.end || version
  return lt < 0 ? lt + version : lt
}
module.exports = class LocalBlocksIterator {
  constructor(batch, opts = {}) {
    this.batch = batch
    this.options = opts
    this.gte = 0
    this.lt = 0
    this.limit = typeof opts.limit === 'number' ? opts.limit : -1
  }

  async open() {
    await this.batch.getRoot(false) // does the update dance
    this.gte = gte(this.options, this.batch.version)
    this.lt = lt(this.options, this.batch.version)
  }

  async next() {
    if (this.limit === 0) return null
    if (this.limit > 0) this.limit--

    while (this.gte < this.lt) {
      try {
        return await this.batch.getBlock(this.gte++)
      } catch {
        continue
      }
    }

    return null
  }

  close() {
    return this.batch._closeSnapshot()
  }
}

function gte(opts, version) {
  if (opts.gt) return (opts.gt < 0 ? opts.gt + version : opts.gt) + 1
  const gte = opts.gte || opts.since || 1
  return gte < 0 ? gte + version : gte
}

function lt(opts, version) {
  if (opts.lte === 0 || opts.lt === 0 || opts.end === 0) return 0
  if (opts.lte) return (opts.lte < 0 ? opts.lte + version : opts.lte) + 1
  const lt = opts.lt || opts.end || version
  return lt < 0 ? lt + version : lt
}
const b4a = require('b4a')

module.exports = class RangeIterator {
  constructor(batch, encoding, opts = {}) {
    this.batch = batch
    this.stack = []
    this.opened = false
    this.encoding = encoding || batch.encoding

    this._limit = typeof opts.limit === 'number' ? opts.limit : -1
    this._gIncl = !opts.gt
    this._gKey = opts.gt || opts.gte || null
    this._lIncl = !opts.lt
    this._lKey = opts.lt || opts.lte || null
    this._reverse = !!opts.reverse
    this._version = 0
    this._checkpoint = opts.checkpoint && opts.checkpoint.length ? opts.checkpoint : null
    this._nexting = false
    this._closed = false
  }

  snapshot(version = this.batch.version) {
    const checkpoint = []
    for (const s of this.stack) {
      let { node, i } = s
      if (this._nexting && s === this.stack[this.stack.length - 1]) {
        i = this._reverse ? i + 1 : i - 1
      }
      if (!node.block) continue
      if (i < 0) continue
      checkpoint.push(node.block.seq, node.offset, i)
    }

    return {
      version,
      gte: this._gIncl ? this._gKey : null,
      gt: this._gIncl ? null : this._gKey,
      lte: this._lIncl ? this._lKey : null,
      lt: this._lIncl ? null : this._lKey,
      limit: this._limit,
      reverse: this._reverse,
      ended: this.opened && !checkpoint.length,
      checkpoint: this.opened ? checkpoint : []
    }
  }

  async open() {
    await this._open()
    this.opened = true
  }

  async _open() {
    if (this._checkpoint) {
      for (let j = 0; j < this._checkpoint.length; j += 3) {
        const seq = this._checkpoint[j]
        const offset = this._checkpoint[j + 1]
        const i = this._checkpoint[j + 2]
        this.stack.push({
          node: (await this.batch.getBlock(seq)).getTreeNode(offset),
          i
        })
      }
      return
    }

    this._nexting = true

    let node = await this.batch.getRoot(false)
    if (!node) {
      this._nexting = false
      return
    }

    const incl = this._reverse ? this._lIncl : this._gIncl
    const start = this._reverse ? this._lKey : this._gKey

    if (!start) {
      this.stack.push({ node, i: this._reverse ? node.keys.length << 1 : 0 })
      this._nexting = false
      this._preloadQuery()
      return
    }

    while (true) {
      const entry = { node, i: this._reverse ? node.keys.length << 1 : 0 }

      let s = 0
      let e = node.keys.length
      let c

      while (s < e) {
        const mid = (s + e) >> 1
        c = b4a.compare(start, await node.getKey(mid))

        if (c === 0) {
          if (incl) entry.i = mid * 2 + 1
          else entry.i = mid * 2 + (this._reverse ? 0 : 2)
          this.stack.push(entry)
          this._nexting = false
          this._preloadQuery()
          return
        }

        if (c < 0) e = mid
        else s = mid + 1
      }

      const i = c < 0 ? e : s
      entry.i = 2 * i + (this._reverse ? -1 : 1)

      if (entry.i >= 0 && entry.i <= node.keys.length << 1) this.stack.push(entry)
      if (!node.children.length) {
        this._nexting = false
        this._preloadQuery()
        return
      }

      node = await node.getChildNode(i)
    }
  }

  _preloadQuery() {
    const max = { nodes: 0, max: 2048 }

    if (this._limit === -1 && !this._reverse && this.stack.length) {
      for (const s of this.stack) {
        const k = (s.i - (s.i & 1)) / 2
        this._preload(s.node, k + 1, this._lKey, max).catch(noop)
      }
    }
  }

  async _preload(node, i, end, max) {
    for (; i < node.keys.length; i++) {
      const key = node.keys[i]
      const block = await this.batch.getBlock(key.seq)
      if (this._closed) return
      const c = end ? b4a.compare(block.key, end) : -1
      if (c >= 0 || max.nodes >= max.max || i >= node.children.length) return
      max.nodes++
      const next = await node.getChildNode(i)
      if (this._closed) return
      this._preload(next, 0, end, max).catch(noop)
    }

    if (node.children.length && max.nodes < max.max) {
      const next = await node.getChildNode(node.children.length - 1)
      if (this._closed) return
      this._preload(next, 0, end, max).catch(noop)
    }
  }

  async next() {
    // TODO: this nexting flag is only needed if someone asks for a snapshot during
    // a lookup (ie the extension, pretty important...).
    // A better solution would be to refactor this so top.i is incremented eagerly
    // to get the current block instead of the way it is done now (++i vs i++)
    this._nexting = true

    const end = this._reverse ? this._gKey : this._lKey
    const incl = this._reverse ? this._gIncl : this._lIncl

    while (this.stack.length && (this._limit === -1 || this._limit > 0)) {
      const top = this.stack[this.stack.length - 1]
      const isKey = (top.i & 1) === 1
      const n = this._reverse ? (top.i < 0 ? top.node.keys.length : top.i-- >> 1) : top.i++ >> 1

      if (!isKey) {
        if (!top.node.children.length) continue
        const node = await top.node.getChildNode(n)
        if (top.node.block.seq < this.batch.core.length) {
          top.node.children[n].value = null // unlink it to save memory
        }
        this.stack.push({ i: this._reverse ? node.keys.length << 1 : 0, node })
        continue
      }

      if (n >= top.node.keys.length) {
        this.stack.pop()
        continue
      }

      const key = top.node.keys[n]
      const block = await this.batch.getBlock(key.seq)
      if (end) {
        const c = b4a.compare(block.key, end)
        if (c === 0 ? !incl : this._reverse ? c < 0 : c > 0) {
          this._limit = 0
          break
        }
      }
      if (this._limit > 0) this._limit--
      this._nexting = false
      return block.final(this.encoding)
    }

    this._nexting = false
    return null
  }

  close() {
    this._closed = true
    return this.batch._closeSnapshot()
  }
}

function noop() {}
const { Extension } = require('./messages')

// const MAX_ACTIVE = 32
const FLUSH_BATCH = 128
const MAX_PASSIVE_BATCH = 2048
const MAX_ACTIVE_BATCH = MAX_PASSIVE_BATCH + FLUSH_BATCH

class Batch {
  constructor(outgoing, from) {
    this.blocks = []
    this.start = 0
    this.end = 0
    this.outgoing = outgoing
    this.from = from
  }

  push(seq) {
    const len = this.blocks.push(seq)
    if (len === 1 || seq < this.start) this.start = seq
    if (len === 1 || seq >= this.end) this.end = seq + 1
    if (len >= FLUSH_BATCH) {
      this.send()
      this.clear()
    }
  }

  send() {
    if (!this.blocks.length) return
    this.outgoing.send(
      Extension.encode({
        cache: { blocks: this.blocks, start: this.start, end: this.end }
      }),
      this.from
    )
  }

  clear() {
    this.start = this.end = 0
    this.blocks = []
  }
}

class HyperbeeExtension {
  constructor(db) {
    this.encoding = null
    this.outgoing = null
    this.db = db
    this.active = 0
  }

  get(version, key) {
    this.outgoing.broadcast(Extension.encode({ get: { version, key } }))
  }

  iterator(snapshot) {
    if (snapshot.ended) return
    if (snapshot.limit === 0) return
    if (snapshot.limit === -1) snapshot.limit = 0
    this.outgoing.broadcast(Extension.encode({ iterator: snapshot }))
  }

  onmessage(buf, from) {
    // TODO: handle max active extension messages
    // this.active++

    const message = decode(buf)
    if (!message) return

    if (message.cache) this.oncache(message.cache, from)
    if (message.get) this.onget(message.get, from)
    if (message.iterator) this.oniterator(message.iterator, from)
  }

  oncache(message, from) {
    if (!message.blocks.length) return
    this.db.core.download(message)
  }

  onget(message, from) {
    if (!message.version || message.version > this.db.version) return

    const b = new Batch(this.outgoing, from)
    const db = this.db.checkout(message.version)

    db.get(message.key, {
      extension: false,
      wait: false,
      update: false,
      onseq
    }).then(done, done)

    function done() {
      db.close().catch(noop)
      b.send()
    }

    function onseq(seq) {
      b.push(seq)
    }
  }

  async oniterator(message, from) {
    if (!message.version || message.version > this.db.version) return

    const b = new Batch(this.outgoing, from)
    const seqs = new Set()

    let skip = message.checkpoint.length
    let work = 0

    const db = this.db.checkout(message.version)
    const ite = db.createRangeIterator({
      ...message,
      wait: false,
      extension: false,
      update: false,
      limit: message.limit === 0 ? -1 : message.limit,
      onseq(seq) {
        if (skip && skip--) return
        if (seqs.has(seq)) return
        work++
        seqs.add(seq)
        b.push(seq)
      }
    })

    try {
      await ite.open()
      // eslint-disable-next-line no-unmodified-loop-condition
      while (work < MAX_ACTIVE_BATCH) {
        if (!(await ite.next())) break
      }
    } catch (_) {
      // do nothing
    } finally {
      ite.close().catch(noop)
      db.close().catch(noop)
      b.send()
    }
  }

  static register(db) {
    const e = new this(db)
    e.outgoing = db.core.registerExtension('hyperbee', e)
    return e
  }
}

HyperbeeExtension.BATCH_SIZE = MAX_PASSIVE_BATCH

module.exports = HyperbeeExtension

function decode(buf) {
  try {
    return Extension.decode(buf)
  } catch (err) {
    return null
  }
}

function noop() {}
// This file is auto generated by the protocol-buffers compiler

/* eslint-disable quotes */
/* eslint-disable indent */
/* eslint-disable no-redeclare */
/* eslint-disable camelcase */
/* eslint-disable no-var */

// Remember to `npm install --save protocol-buffers-encodings`
var encodings = require('protocol-buffers-encodings')
var b4a = require('b4a')
var varint = encodings.varint
var skip = encodings.skip

var YoloIndex = exports.YoloIndex = {
  buffer: true,
  encodingLength: null,
  encode: null,
  decode: null
}

var Header = exports.Header = {
  buffer: true,
  encodingLength: null,
  encode: null,
  decode: null
}

var Node = exports.Node = {
  buffer: true,
  encodingLength: null,
  encode: null,
  decode: null
}

var Extension = exports.Extension = {
  buffer: true,
  encodingLength: null,
  encode: null,
  decode: null
}

defineYoloIndex()
defineHeader()
defineNode()
defineExtension()

function defineYoloIndex () {
  var Level = YoloIndex.Level = {
    buffer: true,
    encodingLength: null,
    encode: null,
    decode: null
  }

  defineLevel()

  function defineLevel () {
    Level.encodingLength = encodingLength
    Level.encode = encode
    Level.decode = decode

    function encodingLength (obj) {
      var length = 0
      if (defined(obj.keys)) {
        var packedLen = 0
        for (var i = 0; i < obj.keys.length; i++) {
          if (!defined(obj.keys[i])) continue
          var len = encodings.varint.encodingLength(obj.keys[i])
          packedLen += len
        }
        if (packedLen) {
          length += 1 + packedLen + varint.encodingLength(packedLen)
        }
      }
      if (defined(obj.children)) {
        var packedLen = 0
        for (var i = 0; i < obj.children.length; i++) {
          if (!defined(obj.children[i])) continue
          var len = encodings.varint.encodingLength(obj.children[i])
          packedLen += len
        }
        if (packedLen) {
          length += 1 + packedLen + varint.encodingLength(packedLen)
        }
      }
      return length
    }

    function encode (obj, buf, offset) {
      if (!offset) offset = 0
      if (!buf) buf = b4a.allocUnsafe(encodingLength(obj))
      var oldOffset = offset
      if (defined(obj.keys)) {
        var packedLen = 0
        for (var i = 0; i < obj.keys.length; i++) {
          if (!defined(obj.keys[i])) continue
          packedLen += encodings.varint.encodingLength(obj.keys[i])
        }
        if (packedLen) {
          buf[offset++] = 10
          varint.encode(packedLen, buf, offset)
          offset += varint.encode.bytes
        }
        for (var i = 0; i < obj.keys.length; i++) {
          if (!defined(obj.keys[i])) continue
          encodings.varint.encode(obj.keys[i], buf, offset)
          offset += encodings.varint.encode.bytes
        }
      }
      if (defined(obj.children)) {
        var packedLen = 0
        for (var i = 0; i < obj.children.length; i++) {
          if (!defined(obj.children[i])) continue
          packedLen += encodings.varint.encodingLength(obj.children[i])
        }
        if (packedLen) {
          buf[offset++] = 18
          varint.encode(packedLen, buf, offset)
          offset += varint.encode.bytes
        }
        for (var i = 0; i < obj.children.length; i++) {
          if (!defined(obj.children[i])) continue
          encodings.varint.encode(obj.children[i], buf, offset)
          offset += encodings.varint.encode.bytes
        }
      }
      encode.bytes = offset - oldOffset
      return buf
    }

    function decode (buf, offset, end) {
      if (!offset) offset = 0
      if (!end) end = buf.length
      if (!(end <= buf.length && offset <= buf.length)) throw new Error("Decoded message is not valid")
      var oldOffset = offset
      var obj = {
        keys: [],
        children: []
      }
      while (true) {
        if (end <= offset) {
          decode.bytes = offset - oldOffset
          return obj
        }
        var prefix = varint.decode(buf, offset)
        offset += varint.decode.bytes
        var tag = prefix >> 3
        switch (tag) {
          case 1:
          var packedEnd = varint.decode(buf, offset)
          offset += varint.decode.bytes
          packedEnd += offset
          while (offset < packedEnd) {
            obj.keys.push(encodings.varint.decode(buf, offset))
            offset += encodings.varint.decode.bytes
          }
          break
          case 2:
          var packedEnd = varint.decode(buf, offset)
          offset += varint.decode.bytes
          packedEnd += offset
          while (offset < packedEnd) {
            obj.children.push(encodings.varint.decode(buf, offset))
            offset += encodings.varint.decode.bytes
          }
          break
          default:
          offset = skip(prefix & 7, buf, offset)
        }
      }
    }
  }

  YoloIndex.encodingLength = encodingLength
  YoloIndex.encode = encode
  YoloIndex.decode = decode

  function encodingLength (obj) {
    var length = 0
    if (defined(obj.levels)) {
      for (var i = 0; i < obj.levels.length; i++) {
        if (!defined(obj.levels[i])) continue
        var len = Level.encodingLength(obj.levels[i])
        length += varint.encodingLength(len)
        length += 1 + len
      }
    }
    return length
  }

  function encode (obj, buf, offset) {
    if (!offset) offset = 0
    if (!buf) buf = b4a.allocUnsafe(encodingLength(obj))
    var oldOffset = offset
    if (defined(obj.levels)) {
      for (var i = 0; i < obj.levels.length; i++) {
        if (!defined(obj.levels[i])) continue
        buf[offset++] = 10
        varint.encode(Level.encodingLength(obj.levels[i]), buf, offset)
        offset += varint.encode.bytes
        Level.encode(obj.levels[i], buf, offset)
        offset += Level.encode.bytes
      }
    }
    encode.bytes = offset - oldOffset
    return buf
  }

  function decode (buf, offset, end) {
    if (!offset) offset = 0
    if (!end) end = buf.length
    if (!(end <= buf.length && offset <= buf.length)) throw new Error("Decoded message is not valid")
    var oldOffset = offset
    var obj = {
      levels: []
    }
    while (true) {
      if (end <= offset) {
        decode.bytes = offset - oldOffset
        return obj
      }
      var prefix = varint.decode(buf, offset)
      offset += varint.decode.bytes
      var tag = prefix >> 3
      switch (tag) {
        case 1:
        var len = varint.decode(buf, offset)
        offset += varint.decode.bytes
        obj.levels.push(Level.decode(buf, offset, offset + len))
        offset += Level.decode.bytes
        break
        default:
        offset = skip(prefix & 7, buf, offset)
      }
    }
  }
}

function defineHeader () {
  var Metadata = Header.Metadata = {
    buffer: true,
    encodingLength: null,
    encode: null,
    decode: null
  }

  defineMetadata()

  function defineMetadata () {
    Metadata.encodingLength = encodingLength
    Metadata.encode = encode
    Metadata.decode = decode

    function encodingLength (obj) {
      var length = 0
      if (defined(obj.contentFeed)) {
        var len = encodings.bytes.encodingLength(obj.contentFeed)
        length += 1 + len
      }
      if (defined(obj.userData)) {
        var len = encodings.bytes.encodingLength(obj.userData)
        length += 1 + len
      }
      return length
    }

    function encode (obj, buf, offset) {
      if (!offset) offset = 0
      if (!buf) buf = b4a.allocUnsafe(encodingLength(obj))
      var oldOffset = offset
      if (defined(obj.contentFeed)) {
        buf[offset++] = 10
        encodings.bytes.encode(obj.contentFeed, buf, offset)
        offset += encodings.bytes.encode.bytes
      }
      if (defined(obj.userData)) {
        buf[offset++] = 18
        encodings.bytes.encode(obj.userData, buf, offset)
        offset += encodings.bytes.encode.bytes
      }
      encode.bytes = offset - oldOffset
      return buf
    }

    function decode (buf, offset, end) {
      if (!offset) offset = 0
      if (!end) end = buf.length
      if (!(end <= buf.length && offset <= buf.length)) throw new Error("Decoded message is not valid")
      var oldOffset = offset
      var obj = {
        contentFeed: null,
        userData: null
      }
      while (true) {
        if (end <= offset) {
          decode.bytes = offset - oldOffset
          return obj
        }
        var prefix = varint.decode(buf, offset)
        offset += varint.decode.bytes
        var tag = prefix >> 3
        switch (tag) {
          case 1:
          obj.contentFeed = encodings.bytes.decode(buf, offset)
          offset += encodings.bytes.decode.bytes
          break
          case 2:
          obj.userData = encodings.bytes.decode(buf, offset)
          offset += encodings.bytes.decode.bytes
          break
          default:
          offset = skip(prefix & 7, buf, offset)
        }
      }
    }
  }

  Header.encodingLength = encodingLength
  Header.encode = encode
  Header.decode = decode

  function encodingLength (obj) {
    var length = 0
    if (!defined(obj.protocol)) throw new Error("protocol is required")
    var len = encodings.string.encodingLength(obj.protocol)
    length += 1 + len
    if (defined(obj.metadata)) {
      var len = Metadata.encodingLength(obj.metadata)
      length += varint.encodingLength(len)
      length += 1 + len
    }
    return length
  }

  function encode (obj, buf, offset) {
    if (!offset) offset = 0
    if (!buf) buf = b4a.allocUnsafe(encodingLength(obj))
    var oldOffset = offset
    if (!defined(obj.protocol)) throw new Error("protocol is required")
    buf[offset++] = 10
    encodings.string.encode(obj.protocol, buf, offset)
    offset += encodings.string.encode.bytes
    if (defined(obj.metadata)) {
      buf[offset++] = 18
      varint.encode(Metadata.encodingLength(obj.metadata), buf, offset)
      offset += varint.encode.bytes
      Metadata.encode(obj.metadata, buf, offset)
      offset += Metadata.encode.bytes
    }
    encode.bytes = offset - oldOffset
    return buf
  }

  function decode (buf, offset, end) {
    if (!offset) offset = 0
    if (!end) end = buf.length
    if (!(end <= buf.length && offset <= buf.length)) throw new Error("Decoded message is not valid")
    var oldOffset = offset
    var obj = {
      protocol: "",
      metadata: null
    }
    var found0 = false
    while (true) {
      if (end <= offset) {
        if (!found0) throw new Error("Decoded message is not valid")
        decode.bytes = offset - oldOffset
        return obj
      }
      var prefix = varint.decode(buf, offset)
      offset += varint.decode.bytes
      var tag = prefix >> 3
      switch (tag) {
        case 1:
        obj.protocol = encodings.string.decode(buf, offset)
        offset += encodings.string.decode.bytes
        found0 = true
        break
        case 2:
        var len = varint.decode(buf, offset)
        offset += varint.decode.bytes
        obj.metadata = Metadata.decode(buf, offset, offset + len)
        offset += Metadata.decode.bytes
        break
        default:
        offset = skip(prefix & 7, buf, offset)
      }
    }
  }
}

function defineNode () {
  Node.encodingLength = encodingLength
  Node.encode = encode
  Node.decode = decode

  function encodingLength (obj) {
    var length = 0
    if (!defined(obj.index)) throw new Error("index is required")
    var len = encodings.bytes.encodingLength(obj.index)
    length += 1 + len
    if (!defined(obj.key)) throw new Error("key is required")
    var len = encodings.bytes.encodingLength(obj.key)
    length += 1 + len
    if (defined(obj.value)) {
      var len = encodings.bytes.encodingLength(obj.value)
      length += 1 + len
    }
    return length
  }

  function encode (obj, buf, offset) {
    if (!offset) offset = 0
    if (!buf) buf = b4a.allocUnsafe(encodingLength(obj))
    var oldOffset = offset
    if (!defined(obj.index)) throw new Error("index is required")
    buf[offset++] = 10
    encodings.bytes.encode(obj.index, buf, offset)
    offset += encodings.bytes.encode.bytes
    if (!defined(obj.key)) throw new Error("key is required")
    buf[offset++] = 18
    encodings.bytes.encode(obj.key, buf, offset)
    offset += encodings.bytes.encode.bytes
    if (defined(obj.value)) {
      buf[offset++] = 26
      encodings.bytes.encode(obj.value, buf, offset)
      offset += encodings.bytes.encode.bytes
    }
    encode.bytes = offset - oldOffset
    return buf
  }

  function decode (buf, offset, end) {
    if (!offset) offset = 0
    if (!end) end = buf.length
    if (!(end <= buf.length && offset <= buf.length)) throw new Error("Decoded message is not valid")
    var oldOffset = offset
    var obj = {
      index: null,
      key: null,
      value: null
    }
    var found0 = false
    var found1 = false
    while (true) {
      if (end <= offset) {
        if (!found0 || !found1) throw new Error("Decoded message is not valid")
        decode.bytes = offset - oldOffset
        return obj
      }
      var prefix = varint.decode(buf, offset)
      offset += varint.decode.bytes
      var tag = prefix >> 3
      switch (tag) {
        case 1:
        obj.index = encodings.bytes.decode(buf, offset)
        offset += encodings.bytes.decode.bytes
        found0 = true
        break
        case 2:
        obj.key = encodings.bytes.decode(buf, offset)
        offset += encodings.bytes.decode.bytes
        found1 = true
        break
        case 3:
        obj.value = encodings.bytes.decode(buf, offset)
        offset += encodings.bytes.decode.bytes
        break
        default:
        offset = skip(prefix & 7, buf, offset)
      }
    }
  }
}

function defineExtension () {
  var Get = Extension.Get = {
    buffer: true,
    encodingLength: null,
    encode: null,
    decode: null
  }

  var Iterator = Extension.Iterator = {
    buffer: true,
    encodingLength: null,
    encode: null,
    decode: null
  }

  var Cache = Extension.Cache = {
    buffer: true,
    encodingLength: null,
    encode: null,
    decode: null
  }

  defineGet()
  defineIterator()
  defineCache()

  function defineGet () {
    Get.encodingLength = encodingLength
    Get.encode = encode
    Get.decode = decode

    function encodingLength (obj) {
      var length = 0
      if (defined(obj.version)) {
        var len = encodings.varint.encodingLength(obj.version)
        length += 1 + len
      }
      if (defined(obj.key)) {
        var len = encodings.bytes.encodingLength(obj.key)
        length += 1 + len
      }
      return length
    }

    function encode (obj, buf, offset) {
      if (!offset) offset = 0
      if (!buf) buf = b4a.allocUnsafe(encodingLength(obj))
      var oldOffset = offset
      if (defined(obj.version)) {
        buf[offset++] = 8
        encodings.varint.encode(obj.version, buf, offset)
        offset += encodings.varint.encode.bytes
      }
      if (defined(obj.key)) {
        buf[offset++] = 18
        encodings.bytes.encode(obj.key, buf, offset)
        offset += encodings.bytes.encode.bytes
      }
      encode.bytes = offset - oldOffset
      return buf
    }

    function decode (buf, offset, end) {
      if (!offset) offset = 0
      if (!end) end = buf.length
      if (!(end <= buf.length && offset <= buf.length)) throw new Error("Decoded message is not valid")
      var oldOffset = offset
      var obj = {
        version: 0,
        key: null
      }
      while (true) {
        if (end <= offset) {
          decode.bytes = offset - oldOffset
          return obj
        }
        var prefix = varint.decode(buf, offset)
        offset += varint.decode.bytes
        var tag = prefix >> 3
        switch (tag) {
          case 1:
          obj.version = encodings.varint.decode(buf, offset)
          offset += encodings.varint.decode.bytes
          break
          case 2:
          obj.key = encodings.bytes.decode(buf, offset)
          offset += encodings.bytes.decode.bytes
          break
          default:
          offset = skip(prefix & 7, buf, offset)
        }
      }
    }
  }

  function defineIterator () {
    Iterator.encodingLength = encodingLength
    Iterator.encode = encode
    Iterator.decode = decode

    function encodingLength (obj) {
      var length = 0
      if (defined(obj.version)) {
        var len = encodings.varint.encodingLength(obj.version)
        length += 1 + len
      }
      if (defined(obj.gte)) {
        var len = encodings.bytes.encodingLength(obj.gte)
        length += 1 + len
      }
      if (defined(obj.gt)) {
        var len = encodings.bytes.encodingLength(obj.gt)
        length += 1 + len
      }
      if (defined(obj.lte)) {
        var len = encodings.bytes.encodingLength(obj.lte)
        length += 1 + len
      }
      if (defined(obj.lt)) {
        var len = encodings.bytes.encodingLength(obj.lt)
        length += 1 + len
      }
      if (defined(obj.limit)) {
        var len = encodings.varint.encodingLength(obj.limit)
        length += 1 + len
      }
      if (defined(obj.reverse)) {
        var len = encodings.bool.encodingLength(obj.reverse)
        length += 1 + len
      }
      if (defined(obj.checkpoint)) {
        var packedLen = 0
        for (var i = 0; i < obj.checkpoint.length; i++) {
          if (!defined(obj.checkpoint[i])) continue
          var len = encodings.varint.encodingLength(obj.checkpoint[i])
          packedLen += len
        }
        if (packedLen) {
          length += 1 + packedLen + varint.encodingLength(packedLen)
        }
      }
      return length
    }

    function encode (obj, buf, offset) {
      if (!offset) offset = 0
      if (!buf) buf = b4a.allocUnsafe(encodingLength(obj))
      var oldOffset = offset
      if (defined(obj.version)) {
        buf[offset++] = 8
        encodings.varint.encode(obj.version, buf, offset)
        offset += encodings.varint.encode.bytes
      }
      if (defined(obj.gte)) {
        buf[offset++] = 18
        encodings.bytes.encode(obj.gte, buf, offset)
        offset += encodings.bytes.encode.bytes
      }
      if (defined(obj.gt)) {
        buf[offset++] = 26
        encodings.bytes.encode(obj.gt, buf, offset)
        offset += encodings.bytes.encode.bytes
      }
      if (defined(obj.lte)) {
        buf[offset++] = 34
        encodings.bytes.encode(obj.lte, buf, offset)
        offset += encodings.bytes.encode.bytes
      }
      if (defined(obj.lt)) {
        buf[offset++] = 42
        encodings.bytes.encode(obj.lt, buf, offset)
        offset += encodings.bytes.encode.bytes
      }
      if (defined(obj.limit)) {
        buf[offset++] = 48
        encodings.varint.encode(obj.limit, buf, offset)
        offset += encodings.varint.encode.bytes
      }
      if (defined(obj.reverse)) {
        buf[offset++] = 56
        encodings.bool.encode(obj.reverse, buf, offset)
        offset += encodings.bool.encode.bytes
      }
      if (defined(obj.checkpoint)) {
        var packedLen = 0
        for (var i = 0; i < obj.checkpoint.length; i++) {
          if (!defined(obj.checkpoint[i])) continue
          packedLen += encodings.varint.encodingLength(obj.checkpoint[i])
        }
        if (packedLen) {
          buf[offset++] = 66
          varint.encode(packedLen, buf, offset)
          offset += varint.encode.bytes
        }
        for (var i = 0; i < obj.checkpoint.length; i++) {
          if (!defined(obj.checkpoint[i])) continue
          encodings.varint.encode(obj.checkpoint[i], buf, offset)
          offset += encodings.varint.encode.bytes
        }
      }
      encode.bytes = offset - oldOffset
      return buf
    }

    function decode (buf, offset, end) {
      if (!offset) offset = 0
      if (!end) end = buf.length
      if (!(end <= buf.length && offset <= buf.length)) throw new Error("Decoded message is not valid")
      var oldOffset = offset
      var obj = {
        version: 0,
        gte: null,
        gt: null,
        lte: null,
        lt: null,
        limit: 0,
        reverse: false,
        checkpoint: []
      }
      while (true) {
        if (end <= offset) {
          decode.bytes = offset - oldOffset
          return obj
        }
        var prefix = varint.decode(buf, offset)
        offset += varint.decode.bytes
        var tag = prefix >> 3
        switch (tag) {
          case 1:
          obj.version = encodings.varint.decode(buf, offset)
          offset += encodings.varint.decode.bytes
          break
          case 2:
          obj.gte = encodings.bytes.decode(buf, offset)
          offset += encodings.bytes.decode.bytes
          break
          case 3:
          obj.gt = encodings.bytes.decode(buf, offset)
          offset += encodings.bytes.decode.bytes
          break
          case 4:
          obj.lte = encodings.bytes.decode(buf, offset)
          offset += encodings.bytes.decode.bytes
          break
          case 5:
          obj.lt = encodings.bytes.decode(buf, offset)
          offset += encodings.bytes.decode.bytes
          break
          case 6:
          obj.limit = encodings.varint.decode(buf, offset)
          offset += encodings.varint.decode.bytes
          break
          case 7:
          obj.reverse = encodings.bool.decode(buf, offset)
          offset += encodings.bool.decode.bytes
          break
          case 8:
          var packedEnd = varint.decode(buf, offset)
          offset += varint.decode.bytes
          packedEnd += offset
          while (offset < packedEnd) {
            obj.checkpoint.push(encodings.varint.decode(buf, offset))
            offset += encodings.varint.decode.bytes
          }
          break
          default:
          offset = skip(prefix & 7, buf, offset)
        }
      }
    }
  }

  function defineCache () {
    Cache.encodingLength = encodingLength
    Cache.encode = encode
    Cache.decode = decode

    function encodingLength (obj) {
      var length = 0
      if (!defined(obj.start)) throw new Error("start is required")
      var len = encodings.varint.encodingLength(obj.start)
      length += 1 + len
      if (!defined(obj.end)) throw new Error("end is required")
      var len = encodings.varint.encodingLength(obj.end)
      length += 1 + len
      if (defined(obj.blocks)) {
        var packedLen = 0
        for (var i = 0; i < obj.blocks.length; i++) {
          if (!defined(obj.blocks[i])) continue
          var len = encodings.varint.encodingLength(obj.blocks[i])
          packedLen += len
        }
        if (packedLen) {
          length += 1 + packedLen + varint.encodingLength(packedLen)
        }
      }
      return length
    }

    function encode (obj, buf, offset) {
      if (!offset) offset = 0
      if (!buf) buf = b4a.allocUnsafe(encodingLength(obj))
      var oldOffset = offset
      if (!defined(obj.start)) throw new Error("start is required")
      buf[offset++] = 8
      encodings.varint.encode(obj.start, buf, offset)
      offset += encodings.varint.encode.bytes
      if (!defined(obj.end)) throw new Error("end is required")
      buf[offset++] = 16
      encodings.varint.encode(obj.end, buf, offset)
      offset += encodings.varint.encode.bytes
      if (defined(obj.blocks)) {
        var packedLen = 0
        for (var i = 0; i < obj.blocks.length; i++) {
          if (!defined(obj.blocks[i])) continue
          packedLen += encodings.varint.encodingLength(obj.blocks[i])
        }
        if (packedLen) {
          buf[offset++] = 26
          varint.encode(packedLen, buf, offset)
          offset += varint.encode.bytes
        }
        for (var i = 0; i < obj.blocks.length; i++) {
          if (!defined(obj.blocks[i])) continue
          encodings.varint.encode(obj.blocks[i], buf, offset)
          offset += encodings.varint.encode.bytes
        }
      }
      encode.bytes = offset - oldOffset
      return buf
    }

    function decode (buf, offset, end) {
      if (!offset) offset = 0
      if (!end) end = buf.length
      if (!(end <= buf.length && offset <= buf.length)) throw new Error("Decoded message is not valid")
      var oldOffset = offset
      var obj = {
        start: 0,
        end: 0,
        blocks: []
      }
      var found0 = false
      var found1 = false
      while (true) {
        if (end <= offset) {
          if (!found0 || !found1) throw new Error("Decoded message is not valid")
          decode.bytes = offset - oldOffset
          return obj
        }
        var prefix = varint.decode(buf, offset)
        offset += varint.decode.bytes
        var tag = prefix >> 3
        switch (tag) {
          case 1:
          obj.start = encodings.varint.decode(buf, offset)
          offset += encodings.varint.decode.bytes
          found0 = true
          break
          case 2:
          obj.end = encodings.varint.decode(buf, offset)
          offset += encodings.varint.decode.bytes
          found1 = true
          break
          case 3:
          var packedEnd = varint.decode(buf, offset)
          offset += varint.decode.bytes
          packedEnd += offset
          while (offset < packedEnd) {
            obj.blocks.push(encodings.varint.decode(buf, offset))
            offset += encodings.varint.decode.bytes
          }
          break
          default:
          offset = skip(prefix & 7, buf, offset)
        }
      }
    }
  }

  Extension.encodingLength = encodingLength
  Extension.encode = encode
  Extension.decode = decode

  function encodingLength (obj) {
    var length = 0
    if (defined(obj.cache)) {
      var len = Cache.encodingLength(obj.cache)
      length += varint.encodingLength(len)
      length += 1 + len
    }
    if (defined(obj.get)) {
      var len = Get.encodingLength(obj.get)
      length += varint.encodingLength(len)
      length += 1 + len
    }
    if (defined(obj.iterator)) {
      var len = Iterator.encodingLength(obj.iterator)
      length += varint.encodingLength(len)
      length += 1 + len
    }
    return length
  }

  function encode (obj, buf, offset) {
    if (!offset) offset = 0
    if (!buf) buf = b4a.allocUnsafe(encodingLength(obj))
    var oldOffset = offset
    if (defined(obj.cache)) {
      buf[offset++] = 10
      varint.encode(Cache.encodingLength(obj.cache), buf, offset)
      offset += varint.encode.bytes
      Cache.encode(obj.cache, buf, offset)
      offset += Cache.encode.bytes
    }
    if (defined(obj.get)) {
      buf[offset++] = 18
      varint.encode(Get.encodingLength(obj.get), buf, offset)
      offset += varint.encode.bytes
      Get.encode(obj.get, buf, offset)
      offset += Get.encode.bytes
    }
    if (defined(obj.iterator)) {
      buf[offset++] = 26
      varint.encode(Iterator.encodingLength(obj.iterator), buf, offset)
      offset += varint.encode.bytes
      Iterator.encode(obj.iterator, buf, offset)
      offset += Iterator.encode.bytes
    }
    encode.bytes = offset - oldOffset
    return buf
  }

  function decode (buf, offset, end) {
    if (!offset) offset = 0
    if (!end) end = buf.length
    if (!(end <= buf.length && offset <= buf.length)) throw new Error("Decoded message is not valid")
    var oldOffset = offset
    var obj = {
      cache: null,
      get: null,
      iterator: null
    }
    while (true) {
      if (end <= offset) {
        decode.bytes = offset - oldOffset
        return obj
      }
      var prefix = varint.decode(buf, offset)
      offset += varint.decode.bytes
      var tag = prefix >> 3
      switch (tag) {
        case 1:
        var len = varint.decode(buf, offset)
        offset += varint.decode.bytes
        obj.cache = Cache.decode(buf, offset, offset + len)
        offset += Cache.decode.bytes
        break
        case 2:
        var len = varint.decode(buf, offset)
        offset += varint.decode.bytes
        obj.get = Get.decode(buf, offset, offset + len)
        offset += Get.decode.bytes
        break
        case 3:
        var len = varint.decode(buf, offset)
        offset += varint.decode.bytes
        obj.iterator = Iterator.decode(buf, offset, offset + len)
        offset += Iterator.decode.bytes
        break
        default:
        offset = skip(prefix & 7, buf, offset)
      }
    }
  }
}

function defined (val) {
  return val !== null && val !== undefined && (typeof val !== 'number' || !isNaN(val))
}
{
  "name": "hyperbee",
  "version": "2.27.3",
  "description": "An append-only B-tree running on a Hypercore.",
  "main": "index.js",
  "files": [
    "index.js",
    "lib/**.js",
    "iterators/**.js"
  ],
  "dependencies": {
    "b4a": "^1.6.0",
    "codecs": "^3.0.0",
    "debounceify": "^1.0.0",
    "hypercore-errors": "^1.0.0",
    "mutexify": "^1.4.0",
    "protocol-buffers-encodings": "^1.2.0",
    "rache": "^1.0.0",
    "ready-resource": "^1.0.0",
    "resolve-reject-promise": "^1.1.0",
    "safety-catch": "^1.0.2",
    "streamx": "^2.12.4",
    "unslab": "^1.2.0"
  },
  "devDependencies": {
    "bare-process": "^4.2.2",
    "brittle": "^3.1.0",
    "hypercore": "^11.0.0",
    "lunte": "^1.0.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^2.0.0",
    "protocol-buffers": "^4.2.0",
    "sub-encoder": "^1.0.6",
    "tree-to-string": "^1.1.1"
  },
  "imports": {
    "process": {
      "bare": "bare-process",
      "default": "process"
    }
  },
  "scripts": {
    "format": "prettier --write .",
    "lint": "prettier --check . && lunte",
    "test": "npm run test:node && npm run test:bare",
    "test:node": "brittle-node test/*.js",
    "test:bare": "brittle-bare test/*.js",
    "protobuf": "protocol-buffers schema.proto -o ./lib/messages.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/holepunchto/hyperbee.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/holepunchto/hyperbee/issues"
  },
  "homepage": "https://github.com/holepunchto/hyperbee"
}
const sodium = require('sodium-universal')
const c = require('compact-encoding')
const b4a = require('b4a')

// https://en.wikipedia.org/wiki/Merkle_tree#Second_preimage_attack
const LEAF_TYPE = b4a.from([0])
const PARENT_TYPE = b4a.from([1])
const ROOT_TYPE = b4a.from([2])

const HYPERCORE = b4a.from('hypercore')

exports.keyPair = function (seed) {
  // key pairs might stay around for a while, so better not to use a default slab to avoid retaining it completely
  const slab = b4a.allocUnsafeSlow(
    sodium.crypto_sign_PUBLICKEYBYTES + sodium.crypto_sign_SECRETKEYBYTES
  )
  const publicKey = slab.subarray(0, sodium.crypto_sign_PUBLICKEYBYTES)
  const secretKey = slab.subarray(sodium.crypto_sign_PUBLICKEYBYTES)

  if (seed) sodium.crypto_sign_seed_keypair(publicKey, secretKey, seed)
  else sodium.crypto_sign_keypair(publicKey, secretKey)

  return {
    publicKey,
    secretKey
  }
}

exports.validateKeyPair = function (keyPair) {
  const pk = b4a.allocUnsafe(sodium.crypto_sign_PUBLICKEYBYTES)
  sodium.crypto_sign_ed25519_sk_to_pk(pk, keyPair.secretKey)
  return b4a.equals(pk, keyPair.publicKey)
}

exports.sign = function (message, secretKey) {
  // Dedicated slab for the signature, to avoid retaining unneeded mem and for security
  const signature = b4a.allocUnsafeSlow(sodium.crypto_sign_BYTES)
  sodium.crypto_sign_detached(signature, message, secretKey)
  return signature
}

exports.verify = function (message, signature, publicKey) {
  if (signature.byteLength !== sodium.crypto_sign_BYTES) return false
  if (publicKey.byteLength !== sodium.crypto_sign_PUBLICKEYBYTES) return false
  return sodium.crypto_sign_verify_detached(signature, message, publicKey)
}

exports.encrypt = function (message, publicKey) {
  const ciphertext = b4a.alloc(message.byteLength + sodium.crypto_box_SEALBYTES)
  sodium.crypto_box_seal(ciphertext, message, publicKey)
  return ciphertext
}

exports.decrypt = function (ciphertext, keyPair) {
  if (ciphertext.byteLength < sodium.crypto_box_SEALBYTES) return null

  const plaintext = b4a.alloc(ciphertext.byteLength - sodium.crypto_box_SEALBYTES)

  if (!sodium.crypto_box_seal_open(plaintext, ciphertext, keyPair.publicKey, keyPair.secretKey)) {
    return null
  }

  return plaintext
}

exports.encryptionKeyPair = function (seed) {
  const publicKey = b4a.alloc(sodium.crypto_box_PUBLICKEYBYTES)
  const secretKey = b4a.alloc(sodium.crypto_box_SECRETKEYBYTES)

  if (seed) {
    sodium.crypto_box_seed_keypair(publicKey, secretKey, seed)
  } else {
    sodium.crypto_box_keypair(publicKey, secretKey)
  }

  return {
    publicKey,
    secretKey
  }
}

exports.data = function (data) {
  const out = b4a.allocUnsafe(32)

  sodium.crypto_generichash_batch(out, [LEAF_TYPE, c.encode(c.uint64, data.byteLength), data])

  return out
}

exports.parent = function (a, b) {
  if (a.index > b.index) {
    const tmp = a
    a = b
    b = tmp
  }

  const out = b4a.allocUnsafe(32)

  sodium.crypto_generichash_batch(out, [
    PARENT_TYPE,
    c.encode(c.uint64, a.size + b.size),
    a.hash,
    b.hash
  ])

  return out
}

exports.tree = function (roots, out) {
  const buffers = new Array(3 * roots.length + 1)
  let j = 0

  buffers[j++] = ROOT_TYPE

  for (let i = 0; i < roots.length; i++) {
    const r = roots[i]
    buffers[j++] = r.hash
    buffers[j++] = c.encode(c.uint64, r.index)
    buffers[j++] = c.encode(c.uint64, r.size)
  }

  if (!out) out = b4a.allocUnsafe(32)
  sodium.crypto_generichash_batch(out, buffers)
  return out
}

exports.hash = function (data, out) {
  if (!out) out = b4a.allocUnsafe(32)
  if (!Array.isArray(data)) data = [data]

  sodium.crypto_generichash_batch(out, data)

  return out
}

exports.randomBytes = function (n) {
  const buf = b4a.allocUnsafe(n)
  sodium.randombytes_buf(buf)
  return buf
}

exports.discoveryKey = function (key) {
  if (!key || key.byteLength !== 32) throw new Error('Must pass a 32 byte buffer')
  // Discovery keys might stay around for a while, so better not to use slab memory (for better gc)
  const digest = b4a.allocUnsafeSlow(32)
  sodium.crypto_generichash(digest, HYPERCORE, key)
  return digest
}

if (sodium.sodium_free) {
  exports.free = function (secureBuf) {
    if (secureBuf.secure) sodium.sodium_free(secureBuf)
  }
} else {
  exports.free = function () {}
}

exports.namespace = function (name, count) {
  const ids = typeof count === 'number' ? range(count) : count

  // Namespaces are long-lived, so better to use a dedicated slab
  const buf = b4a.allocUnsafeSlow(32 * ids.length)

  const list = new Array(ids.length)

  // ns is ephemeral, so default slab
  const ns = b4a.allocUnsafe(33)
  sodium.crypto_generichash(ns.subarray(0, 32), typeof name === 'string' ? b4a.from(name) : name)

  for (let i = 0; i < list.length; i++) {
    list[i] = buf.subarray(32 * i, 32 * i + 32)
    ns[32] = ids[i]
    sodium.crypto_generichash(list[i], ns)
  }

  return list
}

function range(count) {
  const arr = new Array(count)
  for (let i = 0; i < count; i++) arr[i] = i
  return arr
}
{
  "name": "hypercore-crypto",
  "version": "3.7.0",
  "description": "The crypto primitives used in hypercore, extracted into a separate module",
  "main": "index.js",
  "files": [
    "index.js"
  ],
  "dependencies": {
    "b4a": "^1.6.6",
    "compact-encoding": "^3.0.0",
    "sodium-universal": "^5.0.0"
  },
  "devDependencies": {
    "brittle": "^3.5.0",
    "lunte": "^1.0.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^2.0.0"
  },
  "scripts": {
    "format": "prettier --write .",
    "lint": "prettier --check . && lunte",
    "test": "npm run test:node && npm run test:bare",
    "test:node": "brittle-node test.js",
    "test:bare": "brittle-bare test.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/mafintosh/hypercore-crypto.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/mafintosh/hypercore-crypto/issues"
  },
  "homepage": "https://github.com/mafintosh/hypercore-crypto"
}
const IdEnc = require('hypercore-id-encoding')

module.exports = class HypercoreError extends Error {
  constructor (msg, code, fn = HypercoreError, discoveryKey = null) {
    if (discoveryKey) msg = `${msg} (discovery key: ${IdEnc.normalize(discoveryKey)})`
    super(`${code}: ${msg}`)

    this.code = code
    this.discoveryKey = discoveryKey

    if (Error.captureStackTrace) {
      Error.captureStackTrace(this, fn)
    }
  }

  get name () {
    return 'HypercoreError'
  }

  static ASSERTION (msg, discoveryKey = null) { // ERR_ASSERTION is picked up by safety-catch also
    return new HypercoreError(msg, 'ERR_ASSERTION', HypercoreError.ASSERT, discoveryKey)
  }

  static BAD_ARGUMENT (msg, discoveryKey = null) {
    return new HypercoreError(msg, 'BAD_ARGUMENT', HypercoreError.BAD_ARGUMENT, discoveryKey)
  }

  static STORAGE_EMPTY (msg, discoveryKey = null) {
    return new HypercoreError(msg, 'STORAGE_EMPTY', HypercoreError.STORAGE_EMPTY, discoveryKey)
  }

  static STORAGE_CONFLICT (msg, discoveryKey = null) {
    return new HypercoreError(msg, 'STORAGE_CONFLICT', HypercoreError.STORAGE_CONFLICT, discoveryKey)
  }

  static INVALID_SIGNATURE (msg, discoveryKey = null) {
    return new HypercoreError(msg, 'INVALID_SIGNATURE', HypercoreError.INVALID_SIGNATURE, discoveryKey)
  }

  static INVALID_CAPABILITY (msg, discoveryKey = null) {
    return new HypercoreError(msg, 'INVALID_CAPABILITY', HypercoreError.INVALID_CAPABILITY, discoveryKey)
  }

  static INVALID_CHECKSUM (msg = 'Invalid checksum', discoveryKey = null) {
    return new HypercoreError(msg, 'INVALID_CHECKSUM', HypercoreError.INVALID_CHECKSUM, discoveryKey)
  }

  static INVALID_OPERATION (msg, discoveryKey = null) {
    return new HypercoreError(msg, 'INVALID_OPERATION', HypercoreError.INVALID_OPERATION, discoveryKey)
  }

  static INVALID_PROOF (msg = 'Proof not verifiable', discoveryKey = null) {
    return new HypercoreError(msg, 'INVALID_PROOF', HypercoreError.INVALID_PROOF, discoveryKey)
  }

  static BLOCK_NOT_AVAILABLE (msg = 'Block is not available', discoveryKey = null) {
    return new HypercoreError(msg, 'BLOCK_NOT_AVAILABLE', HypercoreError.BLOCK_NOT_AVAILABLE, discoveryKey)
  }

  static SNAPSHOT_NOT_AVAILABLE (msg = 'Snapshot is not available', discoveryKey = null) {
    return new HypercoreError(msg, 'SNAPSHOT_NOT_AVAILABLE', HypercoreError.SNAPSHOT_NOT_AVAILABLE, discoveryKey)
  }

  static REQUEST_CANCELLED (msg = 'Request was cancelled', discoveryKey = null) {
    return new HypercoreError(msg, 'REQUEST_CANCELLED', HypercoreError.REQUEST_CANCELLED, discoveryKey)
  }

  static REQUEST_TIMEOUT (msg = 'Request timed out', discoveryKey = null) {
    return new HypercoreError(msg, 'REQUEST_TIMEOUT', HypercoreError.REQUEST_TIMEOUT, discoveryKey)
  }

  static SESSION_NOT_WRITABLE (msg = 'Session is not writable', discoveryKey = null) {
    return new HypercoreError(msg, 'SESSION_NOT_WRITABLE', HypercoreError.SESSION_NOT_WRITABLE, discoveryKey)
  }

  static SESSION_CLOSED (msg = 'Session is closed', discoveryKey = null) {
    return new HypercoreError(msg, 'SESSION_CLOSED', HypercoreError.SESSION_CLOSED, discoveryKey)
  }

  static BATCH_UNFLUSHED (msg = 'Batch not yet flushed', discoveryKey = null) {
    return new HypercoreError(msg, 'BATCH_UNFLUSHED', HypercoreError.BATCH_UNFLUSHED, discoveryKey)
  }

  static BATCH_ALREADY_EXISTS (msg = 'Batch already exists', discoveryKey = null) {
    return new HypercoreError(msg, 'BATCH_ALREADY_EXISTS', HypercoreError.BATCH_ALREADY_EXISTS, discoveryKey)
  }

  static BATCH_ALREADY_FLUSHED (msg = 'Batch has already been flushed', discoveryKey = null) {
    return new HypercoreError(msg, 'BATCH_ALREADY_FLUSHED', HypercoreError.BATCH_ALREADY_FLUSHED, discoveryKey)
  }

  static OPLOG_CORRUPT (msg = 'Oplog file appears corrupt or out of date', discoveryKey = null) {
    return new HypercoreError(msg, 'OPLOG_CORRUPT', HypercoreError.OPLOG_CORRUPT, discoveryKey)
  }

  static OPLOG_HEADER_OVERFLOW (msg = 'Oplog header exceeds page size', discoveryKey = null) {
    return new HypercoreError(msg, 'OPLOG_HEADER_OVERFLOW', HypercoreError.OPLOG_HEADER_OVERFLOW, discoveryKey)
  }

  static INVALID_OPLOG_VERSION (msg = 'Invalid header version', discoveryKey = null) {
    return new HypercoreError(msg, 'INVALID_OPLOG_VERSION', HypercoreError.INVALID_OPLOG_VERSION, discoveryKey)
  }

  static WRITE_FAILED (msg = 'Write to storage failed', discoveryKey = null) {
    return new HypercoreError(msg, 'WRITE_FAILED', HypercoreError.WRITE_FAILED, discoveryKey)
  }

  static DECODING_ERROR (msg = 'Decoding error', discoveryKey = null) {
    return new HypercoreError(msg, 'DECODING_ERROR', HypercoreError.DECODING_ERROR, discoveryKey)
  }

  static SESSION_MOVED (msg = 'Session moved', discoveryKey = null) {
    return new HypercoreError(msg, 'SESSION_MOVED', HypercoreError.SESSION_MOVED, discoveryKey)
  }
}
{
  "name": "hypercore-errors",
  "version": "1.5.0",
  "description": "Hypercore errors",
  "main": "index.js",
  "files": [
    "index.js"
  ],
  "scripts": {
    "test": "standard && brittle test.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/holepunchto/hypercore-errors.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/hypercore-errors/issues"
  },
  "homepage": "https://github.com/holepunchto/hypercore-errors#readme",
  "devDependencies": {
    "b4a": "^1.6.7",
    "brittle": "^3.1.3",
    "standard": "^17.0.0"
  },
  "dependencies": {
    "hypercore-id-encoding": "^1.3.0"
  }
}
const z32 = require('z32')
const b4a = require('b4a')

module.exports = {
  encode,
  decode,
  normalize,
  isValid
}

function encode (key) {
  if (!b4a.isBuffer(key)) throw new Error('Key must be a Buffer')
  if (key.byteLength !== 32) throw new Error('Key must be 32-bytes long')
  return z32.encode(key)
}

function decode (id) {
  if (b4a.isBuffer(id)) {
    if (id.byteLength !== 32) throw new Error('ID must be 32-bytes long')
    return id
  }
  if (typeof id === 'string') {
    if (id.startsWith('pear://')) id = id.slice(7).split('/')[0]
    if (id.length === 52) return z32.decode(id)
    if (id.length === 64) {
      const buf = b4a.from(id, 'hex')
      if (buf.byteLength === 32) return buf
    }
  }
  throw new Error('Invalid Hypercore key')
}

function normalize (any) {
  return encode(decode(any))
}

function isValid (any) {
  try {
    decode(any)
    return true
  } catch {
    return false
  }
}
{
  "name": "hypercore-id-encoding",
  "version": "1.3.0",
  "description": "Convert Hypercore keys to/from z-base32 or hex",
  "main": "index.js",
  "scripts": {
    "test": "standard && brittle test.js"
  },
  "files": [
    "index.js"
  ],
  "dependencies": {
    "b4a": "^1.5.3",
    "z32": "^1.0.0"
  },
  "devDependencies": {
    "brittle": "^3.1.1",
    "hypercore": "^10.0.0",
    "random-access-memory": "^6.0.0",
    "standard": "^17.1.0"
  },
  "license": "Apache-2.0",
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/hypercore-id-encoding.git"
  },
  "keywords": [],
  "author": "",
  "bugs": {
    "url": "https://github.com/holepunchto/hypercore-id-encoding/issues"
  },
  "homepage": "https://github.com/holepunchto/hypercore-id-encoding#readme"
}
var queueTick = require('queue-tick')

var mutexify = function () {
  var queue = []
  var used = null

  var call = function () {
    used(release)
  }

  var acquire = function (fn) {
    if (used) return queue.push(fn)
    used = fn
    acquire.locked = true
    queueTick(call)
    return 0
  }

  acquire.locked = false

  var release = function (fn, err, value) {
    used = null
    acquire.locked = false
    if (queue.length) acquire(queue.shift())
    if (fn) fn(err, value)
  }

  return acquire
}

module.exports = mutexify
{
  "name": "mutexify",
  "version": "1.4.0",
  "description": "mutex lock for javascript",
  "main": "index.js",
  "dependencies": {
    "queue-tick": "^1.0.0"
  },
  "devDependencies": {
    "standard": "^14.3.3",
    "tape": "^3.0.2"
  },
  "scripts": {
    "test": "tape test.js",
    "posttest": "npm run lint",
    "lint": "standard"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/mafintosh/mutexify.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/mafintosh/mutexify/issues"
  },
  "keywords": [
    "mutex",
    "lock"
  ],
  "homepage": "https://github.com/mafintosh/mutexify"
}
var mutexify = require('.')

var mutexifyPromise = function () {
  var lock = mutexify()

  var acquire = function () {
    return new Promise(lock)
  }

  Object.defineProperty(acquire, 'locked', {
    get: function () { return lock.locked },
    enumerable: true
  })

  return acquire
}

module.exports = mutexifyPromise
var varint = require('varint')
var svarint = require('signed-varint')
var b4a = require('b4a')

exports.make = encoder

exports.name = function (enc) {
  var keys = Object.keys(exports)
  for (var i = 0; i < keys.length; i++) {
    if (exports[keys[i]] === enc) return keys[i]
  }
  return null
}

exports.skip = function (type, buffer, offset) {
  switch (type) {
    case 0:
      varint.decode(buffer, offset)
      return offset + varint.decode.bytes

    case 1:
      return offset + 8

    case 2:
      var len = varint.decode(buffer, offset)
      return offset + varint.decode.bytes + len

    case 3:
    case 4:
      throw new Error('Groups are not supported')

    case 5:
      return offset + 4
  }

  throw new Error('Unknown wire type: ' + type)
}

exports.bytes = encoder(2,
  function encode (val, buffer, offset) {
    var oldOffset = offset
    var len = bufferLength(val)

    varint.encode(len, buffer, offset)
    offset += varint.encode.bytes

    if (b4a.isBuffer(val)) b4a.copy(val, buffer, offset)
    else b4a.write(buffer, val, offset, len)
    offset += len

    encode.bytes = offset - oldOffset
    return buffer
  },
  function decode (buffer, offset) {
    var oldOffset = offset

    var len = varint.decode(buffer, offset)
    offset += varint.decode.bytes

    var val = buffer.subarray(offset, offset + len)
    offset += val.length

    decode.bytes = offset - oldOffset
    return val
  },
  function encodingLength (val) {
    var len = bufferLength(val)
    return varint.encodingLength(len) + len
  }
)

exports.string = encoder(2,
  function encode (val, buffer, offset) {
    var oldOffset = offset
    var len = b4a.byteLength(val)

    varint.encode(len, buffer, offset, 'utf-8')
    offset += varint.encode.bytes

    b4a.write(buffer, val, offset, len)
    offset += len

    encode.bytes = offset - oldOffset
    return buffer
  },
  function decode (buffer, offset) {
    var oldOffset = offset

    var len = varint.decode(buffer, offset)
    offset += varint.decode.bytes

    var val = b4a.toString(buffer, 'utf-8', offset, offset + len)
    offset += len

    decode.bytes = offset - oldOffset
    return val
  },
  function encodingLength (val) {
    var len = b4a.byteLength(val)
    return varint.encodingLength(len) + len
  }
)

exports.bool = encoder(0,
  function encode (val, buffer, offset) {
    buffer[offset] = val ? 1 : 0
    encode.bytes = 1
    return buffer
  },
  function decode (buffer, offset) {
    var bool = buffer[offset] > 0
    decode.bytes = 1
    return bool
  },
  function encodingLength () {
    return 1
  }
)

exports.int32 = encoder(0,
  function encode (val, buffer, offset) {
    varint.encode(val < 0 ? val + 4294967296 : val, buffer, offset)
    encode.bytes = varint.encode.bytes
    return buffer
  },
  function decode (buffer, offset) {
    var val = varint.decode(buffer, offset)
    decode.bytes = varint.decode.bytes
    return val > 2147483647 ? val - 4294967296 : val
  },
  function encodingLength (val) {
    return varint.encodingLength(val < 0 ? val + 4294967296 : val)
  }
)

exports.int64 = encoder(0,
  function encode (val, buffer, offset) {
    if (val < 0) {
      var last = offset + 9
      varint.encode(val * -1, buffer, offset)
      offset += varint.encode.bytes - 1
      buffer[offset] = buffer[offset] | 0x80
      while (offset < last - 1) {
        offset++
        buffer[offset] = 0xff
      }
      buffer[last] = 0x01
      encode.bytes = 10
    } else {
      varint.encode(val, buffer, offset)
      encode.bytes = varint.encode.bytes
    }
    return buffer
  },
  function decode (buffer, offset) {
    var val = varint.decode(buffer, offset)
    if (val >= Math.pow(2, 63)) {
      var limit = 9
      while (buffer[offset + limit - 1] === 0xff) limit--
      limit = limit || 9
      var subset = b4a.allocUnsafe(limit)
      b4a.copy(buffer, subset, 0, offset, offset + limit)
      subset[limit - 1] = subset[limit - 1] & 0x7f
      val = -1 * varint.decode(subset, 0)
      decode.bytes = 10
    } else {
      decode.bytes = varint.decode.bytes
    }
    return val
  },
  function encodingLength (val) {
    return val < 0 ? 10 : varint.encodingLength(val)
  }
)

exports.sint32 =
exports.sint64 = encoder(0,
  svarint.encode,
  svarint.decode,
  svarint.encodingLength
)

exports.uint32 =
exports.uint64 =
exports.enum =
exports.varint = encoder(0,
  varint.encode,
  varint.decode,
  varint.encodingLength
)

// we cannot represent these in javascript so we just use buffers
exports.fixed64 =
exports.sfixed64 = encoder(1,
  function encode (val, buffer, offset) {
    b4a.copy(val, buffer, offset)
    encode.bytes = 8
    return buffer
  },
  function decode (buffer, offset) {
    var val = buffer.subarray(offset, offset + 8)
    decode.bytes = 8
    return val
  },
  function encodingLength () {
    return 8
  }
)

exports.double = encoder(1,
  function encode (val, buffer, offset) {
    b4a.writeDoubleLE(buffer, val, offset)
    encode.bytes = 8
    return buffer
  },
  function decode (buffer, offset) {
    var val = b4a.readDoubleLE(buffer, offset)
    decode.bytes = 8
    return val
  },
  function encodingLength () {
    return 8
  }
)

exports.fixed32 = encoder(5,
  function encode (val, buffer, offset) {
    b4a.writeUInt32LE(buffer, val, offset)
    encode.bytes = 4
    return buffer
  },
  function decode (buffer, offset) {
    var val = b4a.readUInt32LE(buffer, offset)
    decode.bytes = 4
    return val
  },
  function encodingLength () {
    return 4
  }
)

exports.sfixed32 = encoder(5,
  function encode (val, buffer, offset) {
    b4a.writeInt32LE(buffer, val, offset)
    encode.bytes = 4
    return buffer
  },
  function decode (buffer, offset) {
    var val = b4a.readInt32LE(buffer, offset)
    decode.bytes = 4
    return val
  },
  function encodingLength () {
    return 4
  }
)

exports.float = encoder(5,
  function encode (val, buffer, offset) {
    b4a.writeFloatLE(buffer, val, offset)
    encode.bytes = 4
    return buffer
  },
  function decode (buffer, offset) {
    var val = b4a.readFloatLE(buffer, offset)
    decode.bytes = 4
    return val
  },
  function encodingLength () {
    return 4
  }
)

function encoder (type, encode, decode, encodingLength) {
  encode.bytes = decode.bytes = 0

  return {
    type: type,
    encode: encode,
    decode: decode,
    encodingLength: encodingLength
  }
}

function bufferLength (val) {
  return b4a.isBuffer(val) ? val.length : b4a.byteLength(val)
}
{
  "name": "protocol-buffers-encodings",
  "version": "1.2.0",
  "description": "Base encodings for protocol-buffers",
  "main": "index.js",
  "dependencies": {
    "b4a": "^1.6.0",
    "signed-varint": "^2.0.1",
    "varint": "5.0.0"
  },
  "devDependencies": {
    "standard": "^14.3.4",
    "tape": "^5.0.1"
  },
  "scripts": {
    "test": "standard && tape test.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/mafintosh/protocol-buffers-encodings.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/mafintosh/protocol-buffers-encodings/issues"
  },
  "homepage": "https://github.com/mafintosh/protocol-buffers-encodings"
}
{
  "name": "queue-tick",
  "version": "1.0.1",
  "description": "Next tick shim that prefers process.nextTick over queueMicrotask for compat",
  "main": "./process-next-tick.js",
  "browser": "./queue-microtask.js",
  "dependencies": {},
  "devDependencies": {
    "standard": "^16.0.3",
    "tape": "^5.3.1"
  },
  "scripts": {
    "test": "standard && tape test.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/mafintosh/queue-tick.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/mafintosh/queue-tick/issues"
  },
  "homepage": "https://github.com/mafintosh/queue-tick"
}
module.exports = (typeof process !== 'undefined' && typeof process.nextTick === 'function')
  ? process.nextTick.bind(process)
  : require('./queue-microtask')
module.exports = typeof queueMicrotask === 'function' ? queueMicrotask : (fn) => Promise.resolve().then(fn)
class CacheEntry {
  constructor (key, index, map) {
    this.key = key
    this.index = index
    this.map = map
  }
}

class CacheValue {
  constructor (entry, value) {
    this.entry = entry
    this.value = value
  }
}

class Rache {
  constructor ({ maxSize = 65536, parent = null } = {}) {
    this.maxSize = parent?.maxSize || maxSize

    this._array = parent?._array || []
    this._map = new Map()
  }

  static from (cache) {
    return cache ? new this({ parent: cache }) : new this()
  }

  get globalSize () {
    return this._array.length
  }

  get size () {
    return this._map.size
  }

  sub () {
    return new Rache({ parent: this })
  }

  set (key, value) { // ~constant time
    const existing = this._map.get(key)
    if (existing !== undefined) {
      existing.value = value
      return
    }

    if (this._array.length >= this.maxSize) this._gc()

    const entry = new CacheEntry(key, this._array.length, this._map)
    this._array.push(entry)
    const cacheValue = new CacheValue(entry, value)
    this._map.set(key, cacheValue)
  }

  delete (key) {
    const existing = this._map.get(key)
    if (existing === undefined) return false

    this._delete(existing.entry.index)
    return true
  }

  get (key) {
    const existing = this._map.get(key)
    return existing === undefined ? undefined : existing.value
  }

  * [Symbol.iterator] () {
    for (const [key, { value }] of this._map) {
      yield [key, value]
    }
  }

  keys () {
    return this._map.keys()
  }

  * values () {
    for (const { value } of this._map.values()) {
      yield value
    }
  }

  clear () {
    // The entries in map linger on in _array,
    // so on top of clearing the map, we also kill the ref,
    // so that any gc running later on the old map won't interfere
    // (in case a new entry was added with the same key as a cleared entry)

    this._map.clear()
    this._map = new Map()
  }

  destroy () {
    this._map = null
    this._array = null
  }

  _gc () {
    this._delete(Math.floor(Math.random() * this._array.length))
  }

  _delete (index) { // ~constant time
    if (index >= this._array.length) throw new Error('Cannot delete unused index (logic bug?)')

    const head = this._array.pop()
    let removed = head

    if (index < this._array.length) {
      removed = this._array[index]
      head.index = index
      this._array[index] = head
    }

    removed.map.delete(removed.key)
  }
}

module.exports = Rache
{
  "name": "rache",
  "version": "1.0.0",
  "description": "Random-eviction cache",
  "main": "index.js",
  "scripts": {
    "test": "standard && brittle test.js --coverage"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/rache.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/rache/issues"
  },
  "homepage": "https://github.com/holepunchto/rache#readme",
  "files": [
    "index.js"
  ],
  "dependencies": {},
  "devDependencies": {
    "brittle": "^3.5.2",
    "standard": "^17.1.0"
  }
}
const EventEmitter = require('events')

module.exports = class ReadyResource extends EventEmitter {
  constructor () {
    super()

    this.opening = null
    this.closing = null

    this.opened = false
    this.closed = false
  }

  ready () {
    if (this.opening !== null) return this.opening
    this.opening = open(this)
    return this.opening
  }

  close () {
    if (this.closing !== null) return this.closing
    this.closing = close(this)
    return this.closing
  }

  async _open () {
    // add impl here
  }

  async _close () {
    // add impl here
  }
}

async function open (self) {
  // open after close
  if (self.closing !== null) return

  try {
    await self._open()
  } catch (err) {
    self.close() // safe to run in bg
    throw err
  }

  self.opened = true
  self.emit('ready')
}

async function close (self) {
  try {
    if (self.opened === false && self.opening !== null) await self.opening
  } catch {
    // ignore errors on closing
  }
  if (self.opened === true || self.opening === null) await self._close()
  self.closed = true
  self.emit('close')
}
{
  "name": "ready-resource",
  "version": "1.2.0",
  "description": "Modern single resource management",
  "main": "index.js",
  "imports": {
    "events": {
      "bare": "bare-events",
      "default": "events"
    }
  },
  "types": "index.d.ts",
  "files": [
    "index.js",
    "index.d.ts"
  ],
  "scripts": {
    "test": "standard && brittle test.js"
  },
  "devDependencies": {
    "brittle": "^3.1.0",
    "standard": "^17.0.0"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/ready-resource.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/holepunchto/ready-resource/issues"
  },
  "homepage": "https://github.com/holepunchto/ready-resource",
  "dependencies": {
    "bare-events": "^2.2.0"
  }
}
module.exports = require.addon.bind(require)
{
  "name": "require-addon",
  "version": "1.2.0",
  "description": "Import native addons across JavaScript runtimes",
  "exports": {
    "./package": "./package.json",
    ".": {
      "bare": "./lib/bare.js",
      "node": "./lib/node.js",
      "default": "./lib/default.js"
    }
  },
  "imports": {
    "fs": {
      "bare": "bare-fs",
      "default": "fs"
    },
    "path": {
      "bare": "bare-path",
      "default": "path"
    },
    "url": {
      "bare": "bare-url",
      "default": "url"
    }
  },
  "files": [
    "lib"
  ],
  "scripts": {
    "test": "npm run lint && npm run test:bare && npm run test:node",
    "test:bare": "bare test.js",
    "test:node": "node test.js",
    "lint": "prettier --check .",
    "format": "prettier --write ."
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/require-addon.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/require-addon/issues"
  },
  "homepage": "https://github.com/holepunchto/require-addon#readme",
  "engines": {
    "bare": ">=1.10.0"
  },
  "dependencies": {
    "bare-addon-resolve": "^1.3.0"
  },
  "devDependencies": {
    "bare-bundle": "^1.8.1",
    "bare-bundle-evaluate": "^1.1.0",
    "bare-fs": "^4.0.0",
    "bare-path": "^3.0.0",
    "bare-url": "^2.1.0",
    "brittle": "^3.7.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^2.0.0"
  }
}
let tmpResolve = null
let tmpReject = null

if (Promise.withResolvers) {
  module.exports = Promise.withResolvers.bind(Promise)
} else {
  module.exports = function resolveRejectPromise () {
    const promise = new Promise(setTmp)
    const result = { promise, resolve: tmpResolve, reject: tmpReject }
    tmpResolve = tmpReject = null
    return result
  }
}

function setTmp (resolve, reject) {
  tmpResolve = resolve
  tmpReject = reject
}
{
  "name": "resolve-reject-promise",
  "version": "1.1.0",
  "description": "Create an inverted promise with no function allocs",
  "main": "index.js",
  "scripts": {
    "test": "standard"
  },
  "devDependencies": {
    "standard": "^17.1.2"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/mafintosh/resolve-reject-promise.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/mafintosh/resolve-reject-promise/issues"
  },
  "homepage": "https://github.com/mafintosh/resolve-reject-promise"
}
module.exports = safetyCatch

function isActuallyUncaught (err) {
  if (!err) return false
  return err instanceof TypeError ||
    err instanceof SyntaxError ||
    err instanceof ReferenceError ||
    err instanceof EvalError ||
    err instanceof RangeError ||
    err instanceof URIError ||
    err.code === 'ERR_ASSERTION' ||
    err.name === 'AssertionError'
}

function throwErrorNT (err) {
  queueMicrotask(() => { throw err })
}

function safetyCatch (err) {
  if (isActuallyUncaught(err)) {
    throwErrorNT(err)
    throw err
  }
}
{
  "name": "safety-catch",
  "version": "1.0.3",
  "description": "Small module that makes sure your catch, caught an actual error and not a programming mistake or assertion",
  "main": "index.js",
  "dependencies": {},
  "devDependencies": {},
  "repository": {
    "type": "git",
    "url": "https://github.com/mafintosh/safety-catch.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/mafintosh/safety-catch/issues"
  },
  "homepage": "https://github.com/mafintosh/safety-catch"
}
var varint = require('varint')
exports.encode = function encode (v, b, o) {
  v = v >= 0 ? v*2 : v*-2 - 1
  var r = varint.encode(v, b, o)
  encode.bytes = varint.encode.bytes
  return r
}
exports.decode = function decode (b, o) {
  var v = varint.decode(b, o)
  decode.bytes = varint.decode.bytes
  return v & 1 ? (v+1) / -2 : v / 2
}

exports.encodingLength = function (v) {
  return varint.encodingLength(v >= 0 ? v*2 : v*-2 - 1)
}
{
  "name": "signed-varint",
  "description": "efficiently store signed integers in varint",
  "version": "2.0.1",
  "homepage": "https://github.com/dominictarr/signed-varint",
  "repository": {
    "type": "git",
    "url": "git://github.com/dominictarr/signed-varint.git"
  },
  "dependencies": {
    "varint": "~5.0.0"
  },
  "devDependencies": {
    "tape": "~2.12.3"
  },
  "scripts": {
    "test": "node test.js"
  },
  "author": "Dominic Tarr <dominic.tarr@gmail.com> (http://dominictarr.com)",
  "license": "MIT"
}
require.addon = require('require-addon')
module.exports = require.addon('.', __filename)
const assert = require('assert')
const binding = require('./binding')
const { isNode } = require('which-runtime')

const OPTIONAL = Buffer.from(new ArrayBuffer(0))

module.exports = exports = { ...binding }

// memory

exports.sodium_memzero = function (buf) {
  assert(ArrayBuffer.isView(buf), 'buf must be a typed array')

  binding.sodium_memzero(buf)
}

exports.sodium_mlock = function (buf) {
  assert(ArrayBuffer.isView(buf), 'buf must be a typed array')

  const res = binding.sodium_mlock(buf)

  if (res !== 0) throw new Error('memory lock failed')
}

exports.sodium_munlock = function (buf) {
  assert(ArrayBuffer.isView(buf), 'buf must be a typed array')

  const res = binding.sodium_munlock(buf)

  if (res !== 0) throw new Error('memory unlock failed')
}

exports.sodium_malloc = function (size) {
  assert(size >= 0, 'invalid size')

  const buf = Buffer.from(binding.sodium_malloc(size))
  buf.secure = true
  return buf
}

exports.sodium_free = function (buf) {
  if (!buf || !buf.secure) return

  binding.sodium_free(buf.buffer)
}

exports.sodium_mprotect_noaccess = function (buf) {
  const res = binding.sodium_mprotect_noaccess(buf.buffer)

  if (res !== 0) throw new Error('failed to lock buffer')
}

exports.sodium_mprotect_readonly = function (buf) {
  const res = binding.sodium_mprotect_readonly(buf.buffer)

  if (res !== 0) throw new Error('failed to unlock buffer')
}

exports.sodium_mprotect_readwrite = function (buf) {
  const res = binding.sodium_mprotect_readwrite(buf.buffer)

  if (res !== 0) throw new Error('failed to unlock buffer')
}

// crypto_randombytes

exports.randombytes_buf = function (buffer) {
  assert(ArrayBuffer.isView(buffer), 'buffer must be a typed array')

  binding.randombytes_buf(buffer.buffer, buffer.byteOffset, buffer.byteLength)
}

exports.randombytes_buf_deterministic = function (buffer, seed) {
  assert(ArrayBuffer.isView(buffer), 'buffer must be a typed array')
  assert(ArrayBuffer.isView(seed), 'seed must be a typed array')
  assert(
    seed.byteLength === binding.randombytes_SEEDBYTES,
    "seed must be 'randombytes_SEEDBYTES' bytes"
  )

  binding.randombytes_buf_deterministic(
    buffer.buffer,
    buffer.byteOffset,
    buffer.byteLength,

    seed.buffer,
    seed.byteOffset,
    seed.byteLength
  )
}

// sodium_helpers

exports.sodium_memcmp = function (a, b) {
  assert(ArrayBuffer.isView(a), 'a must be a typed array')
  assert(ArrayBuffer.isView(b), 'b must be a typed array')
  assert(a.byteLength === b.byteLength, 'buffers must be of same length')

  return binding.sodium_memcmp(a, b)
}

exports.sodium_add = function (a, b) {
  assert(ArrayBuffer.isView(a), 'a must be a typed array')
  assert(ArrayBuffer.isView(b), 'b must be a typed array')
  assert(a.byteLength === b.byteLength, 'buffers must be of same length')

  binding.sodium_add(a, b)
}

exports.sodium_sub = function (a, b) {
  assert(ArrayBuffer.isView(a), 'a must be a typed array')
  assert(ArrayBuffer.isView(b), 'b must be a typed array')
  assert(a.byteLength === b.byteLength, 'buffers must be of same length')

  binding.sodium_sub(a, b)
}

exports.sodium_compare = function (a, b) {
  assert(ArrayBuffer.isView(a), 'a must be a typed array')
  assert(ArrayBuffer.isView(b), 'b must be a typed array')
  assert(a.byteLength === b.byteLength, 'buffers must be of same length')

  return binding.sodium_compare(a, b)
}

exports.sodium_is_zero = function (buffer, length) {
  if (length === undefined) length = buffer.byteLength

  assert(ArrayBuffer.isView(buffer), 'buffer must be a typed array')
  assert(length >= 0 && length <= buffer.byteLength, 'invalid length')

  return binding.sodium_is_zero(buffer, length)
}

exports.sodium_pad = function (buffer, unpaddedBuflen, blockSize) {
  assert(ArrayBuffer.isView(buffer), 'buffer must be a typed array')
  assert(unpaddedBuflen <= buffer.byteLength, 'unpadded length cannot exceed buffer length')
  assert(blockSize <= buffer.byteLength, 'block size cannot exceed buffer length')
  assert(blockSize >= 1, 'block size must be at least 1 byte')
  assert(
    buffer.byteLength >= unpaddedBuflen + (blockSize - (unpaddedBuflen % blockSize)),
    'buf not long enough'
  )

  return binding.sodium_pad(buffer, unpaddedBuflen, blockSize)
}

exports.sodium_unpad = function (buffer, paddedBuflen, blockSize) {
  assert(ArrayBuffer.isView(buffer), 'buffer must be a typed array')
  assert(paddedBuflen <= buffer.byteLength, 'unpadded length cannot exceed buffer length')
  assert(blockSize <= buffer.byteLength, 'block size cannot exceed buffer length')
  assert(blockSize >= 1, 'block size must be at least 1 byte')

  return binding.sodium_unpad(buffer, paddedBuflen, blockSize)
}

// crypto_sign

exports.crypto_sign_keypair = function (pk, sk) {
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(
    pk.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
    "pk must be 'crypto_sign_PUBLICKEYBYTES' bytes"
  )
  assert(
    sk.byteLength === binding.crypto_sign_SECRETKEYBYTES,
    "sk must be 'crypto_sign_SECRETKEYBYTES' bytes"
  )

  const res = binding.crypto_sign_keypair(pk, sk)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_sign_seed_keypair = function (pk, sk, seed) {
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(ArrayBuffer.isView(seed), 'seed must be a typed array')
  assert(
    pk.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
    "pk must be 'crypto_sign_PUBLICKEYBYTES' bytes"
  )
  assert(
    sk.byteLength === binding.crypto_sign_SECRETKEYBYTES,
    "sk must be 'crypto_sign_SECRETKEYBYTES' bytes"
  )
  assert(
    seed.byteLength === binding.crypto_sign_SEEDBYTES,
    "seed must be 'crypto_sign_SEEDBYTES' bytes"
  )

  const res = binding.crypto_sign_seed_keypair(pk, sk, seed)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_sign = function (sm, m, sk) {
  assert(ArrayBuffer.isView(sm), 'sm must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(
    sm.byteLength === binding.crypto_sign_BYTES + m.byteLength,
    "sm must be 'm.byteLength + crypto_sign_BYTES' bytes"
  )
  assert(
    sk.byteLength === binding.crypto_sign_SECRETKEYBYTES,
    "sk must be 'crypto_sign_SECRETKEYBYTES' bytes"
  )

  const res = binding.crypto_sign(sm, m, sk)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_sign_open = function (m, sm, pk) {
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(sm), 'sm must be a typed array')
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(
    sm.byteLength >= binding.crypto_sign_BYTES,
    "sm must be at least 'crypto_sign_BYTES' bytes"
  )
  assert(
    m.byteLength === sm.byteLength - binding.crypto_sign_BYTES,
    "m must be 'sm.byteLength - crypto_sign_BYTES' bytes"
  )
  assert(
    pk.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
    "pk must be 'crypto_sign_PUBLICKEYBYTES' bytes"
  )

  const res = binding.crypto_sign_open(m, sm, pk)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_sign_open = function (m, sm, pk) {
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(sm), 'sm must be a typed array')
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(
    sm.byteLength >= binding.crypto_sign_BYTES,
    "sm must be at least 'crypto_sign_BYTES' bytes"
  )
  assert(
    m.byteLength === sm.byteLength - binding.crypto_sign_BYTES,
    "m must be 'sm.byteLength - crypto_sign_BYTES' bytes"
  )
  assert(
    pk.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
    "pk must be 'crypto_sign_PUBLICKEYBYTES' bytes"
  )

  return binding.crypto_sign_open(m, sm, pk)
}

exports.crypto_sign_detached = function (sig, m, sk) {
  assert(ArrayBuffer.isView(sig), 'sig must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(sig.byteLength === binding.crypto_sign_BYTES, "sig must be 'crypto_sign_BYTES' bytes")
  assert(
    sk.byteLength === binding.crypto_sign_SECRETKEYBYTES,
    "sk must be 'crypto_sign_SECRETKEYBYTES' bytes"
  )

  const res = binding.crypto_sign_detached(sig, m, sk)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_sign_verify_detached = function (sig, m, pk) {
  assert(ArrayBuffer.isView(sig), 'sig must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(
    sig.byteLength >= binding.crypto_sign_BYTES,
    "sig must be at least 'crypto_sign_BYTES' bytes"
  )
  assert(
    pk.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
    "pk must be 'crypto_sign_PUBLICKEYBYTES' bytes"
  )

  return binding.crypto_sign_verify_detached(
    sig.buffer,
    sig.byteOffset,
    sig.byteLength,

    m.buffer,
    m.byteOffset,
    m.byteLength,

    pk.buffer,
    pk.byteOffset,
    pk.byteLength
  )
}

exports.crypto_sign_ed25519_sk_to_pk = function (pk, sk) {
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(
    pk.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
    "pk must be 'crypto_sign_PUBLICKEYBYTES' bytes"
  )
  assert(
    sk.byteLength === binding.crypto_sign_SECRETKEYBYTES,
    "sk must be 'crypto_sign_SECRETKEYBYTES' bytes"
  )

  const res = binding.crypto_sign_ed25519_sk_to_pk(pk, sk)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_sign_ed25519_pk_to_curve25519 = function (x25519pk, ed25519pk) {
  assert(ArrayBuffer.isView(x25519pk), 'x25519pk must be a typed array')
  assert(ArrayBuffer.isView(ed25519pk), 'ed25519pk must be a typed array')
  assert(
    x25519pk.byteLength === binding.crypto_box_PUBLICKEYBYTES,
    "x25519pk must be 'crypto_box_PUBLICKEYBYTES' bytes"
  )
  assert(
    ed25519pk.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
    "ed25519pk must be 'crypto_sign_PUBLICKEYBYTES' bytes"
  )

  const res = binding.crypto_sign_ed25519_pk_to_curve25519(x25519pk, ed25519pk)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_sign_ed25519_sk_to_curve25519 = function (x25519sk, ed25519sk) {
  assert(ArrayBuffer.isView(x25519sk), 'x25519sk must be a typed array')
  assert(ArrayBuffer.isView(ed25519sk), 'ed25519sk must be a typed array')
  assert(
    x25519sk.byteLength === binding.crypto_box_SECRETKEYBYTES,
    "x25519sk must be 'crypto_box_SECRETKEYBYTES' bytes"
  )

  const edLen = ed25519sk.byteLength

  assert(
    edLen === binding.crypto_sign_SECRETKEYBYTES || edLen === binding.crypto_box_SECRETKEYBYTES,
    "ed25519sk must be 'crypto_sign_SECRETKEYBYTES' or 'crypto_sign_SECRETKEYBYTES - crypto_sign_PUBLICKEYBYTES' bytes"
  )

  const res = binding.crypto_sign_ed25519_sk_to_curve25519(x25519sk, ed25519sk)

  if (res !== 0) throw new Error('status: ' + res)
}

// crypto_box

exports.crypto_box_keypair = function (pk, sk) {
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(
    pk.byteLength === binding.crypto_box_PUBLICKEYBYTES,
    "pk must be 'crypto_box_PUBLICKEYBYTES' bytes"
  )
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(
    sk.byteLength === binding.crypto_box_SECRETKEYBYTES,
    "sk must be 'crypto_box_SECRETKEYBYTES' bytes"
  )

  const res = binding.crypto_box_keypair(pk, sk)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_box_seed_keypair = function (pk, sk, seed) {
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(
    pk.byteLength === binding.crypto_box_PUBLICKEYBYTES,
    "pk must be 'crypto_box_PUBLICKEYBYTES' bytes"
  )
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(
    sk.byteLength === binding.crypto_box_SECRETKEYBYTES,
    "sk must be 'crypto_box_SECRETKEYBYTES' bytes"
  )
  assert(ArrayBuffer.isView(seed), 'seed must be a typed array')
  assert(
    seed.byteLength === binding.crypto_box_SEEDBYTES,
    "seed must be 'crypto_box_SEEDBYTES' bytes"
  )

  const res = binding.crypto_box_seed_keypair(pk, sk, seed)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_box_easy = function (c, m, n, pk, sk) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(
    c.byteLength === m.byteLength + exports.crypto_box_MACBYTES,
    "c must be 'm.byteLength + crypto_box_MACBYTES' bytes"
  )
  assert(n.byteLength === exports.crypto_box_NONCEBYTES, "n must be 'crypto_box_NONCEBYTES' bytes")
  assert(
    pk.byteLength === exports.crypto_box_PUBLICKEYBYTES,
    "pk must be 'crypto_box_PUBLICKEYBYTES' bytes"
  )
  assert(
    sk.byteLength === exports.crypto_box_SECRETKEYBYTES,
    "sk must be 'crypto_box_SECRETKEYBYTES' bytes"
  )

  const res = binding.crypto_box_easy(c, m, n, pk, sk)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_box_detached = function (c, mac, m, n, pk, sk) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(mac), 'mac must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(mac.byteLength === exports.crypto_box_MACBYTES, "mac must be 'crypto_box_MACBYTES' bytes")
  assert(n.byteLength === exports.crypto_box_NONCEBYTES, "n must be 'crypto_box_NONCEBYTES' bytes")
  assert(
    pk.byteLength === exports.crypto_box_PUBLICKEYBYTES,
    "pk must be 'crypto_box_PUBLICKEYBYTES' bytes"
  )
  assert(
    sk.byteLength === exports.crypto_box_SECRETKEYBYTES,
    "sk must be 'crypto_box_SECRETKEYBYTES' bytes"
  )

  const res = binding.crypto_box_detached(c, mac, m, n, pk, sk)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_box_open_easy = function (m, c, n, pk, sk) {
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(
    c.byteLength >= exports.crypto_box_MACBYTES,
    "c must be at least 'crypto_box_MACBYTES' bytes"
  )
  assert(
    m.byteLength === c.byteLength - exports.crypto_box_MACBYTES,
    "m must be 'c.byteLength - crypto_box_MACBYTES' bytes"
  )
  assert(n.byteLength === exports.crypto_box_NONCEBYTES, "n must be 'crypto_box_NONCEBYTES' bytes")
  assert(
    pk.byteLength === exports.crypto_box_PUBLICKEYBYTES,
    "pk must be 'crypto_box_PUBLICKEYBYTES' bytes"
  )
  assert(
    sk.byteLength === exports.crypto_box_SECRETKEYBYTES,
    "sk must be 'crypto_box_SECRETKEYBYTES' bytes"
  )

  return binding.crypto_box_open_easy(m, c, n, pk, sk)
}

exports.crypto_box_open_detached = function (m, c, mac, n, pk, sk) {
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(mac), 'mac must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(m.byteLength === c.byteLength, "m must be 'c.byteLength' bytes")
  assert(mac.byteLength === exports.crypto_box_MACBYTES, "mac must be 'crypto_box_MACBYTES' bytes")
  assert(n.byteLength === exports.crypto_box_NONCEBYTES, "n must be 'crypto_box_NONCEBYTES' bytes")
  assert(
    pk.byteLength === exports.crypto_box_PUBLICKEYBYTES,
    "pk must be 'crypto_box_PUBLICKEYBYTES' bytes"
  )
  assert(
    sk.byteLength === exports.crypto_box_SECRETKEYBYTES,
    "sk must be 'crypto_box_SECRETKEYBYTES' bytes"
  )

  return binding.crypto_box_open_detached(m, c, mac, n, pk, sk)
}

exports.crypto_box_seal = function (c, m, pk) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(
    c.byteLength === m.byteLength + exports.crypto_box_SEALBYTES,
    "c must be 'm.byteLength + crypto_box_SEALBYTES' bytes"
  )
  assert(
    pk.byteLength === exports.crypto_box_PUBLICKEYBYTES,
    "pk must be 'crypto_box_PUBLICKEYBYTES' bytes"
  )

  const res = binding.crypto_box_seal(c, m, pk)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_box_seal_open = function (m, c, pk, sk) {
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(
    c.byteLength >= exports.crypto_box_SEALBYTES,
    "c must be at least 'crypto_box_SEALBYTES' bytes"
  )
  assert(
    m.byteLength === c.byteLength - exports.crypto_box_SEALBYTES,
    "m must be 'c.byteLength - crypto_box_SEALBYTES' bytes"
  )
  assert(
    pk.byteLength === exports.crypto_box_PUBLICKEYBYTES,
    "pk must be 'crypto_box_PUBLICKEYBYTES' bytes"
  )
  assert(
    sk.byteLength === exports.crypto_box_SECRETKEYBYTES,
    "sk must be 'crypto_box_SECRETKEYBYTES' bytes"
  )

  return binding.crypto_box_seal_open(
    m.buffer,
    m.byteOffset,
    m.byteLength,

    c.buffer,
    c.byteOffset,
    c.byteLength,

    pk.buffer,
    pk.byteOffset,
    pk.byteLength,

    sk.buffer,
    sk.byteOffset,
    sk.byteLength
  )
}

// crypto_secretbox

exports.crypto_secretbox_easy = function (c, m, n, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    c.byteLength === m.byteLength + binding.crypto_secretbox_MACBYTES,
    "c must be 'm.byteLength + crypto_secretbox_MACBYTES' bytes"
  )
  assert(
    n.byteLength === binding.crypto_secretbox_NONCEBYTES,
    "n must be 'crypto_secretbox_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_secretbox_KEYBYTES,
    "k must be 'crypto_secretbox_KEYBYTES' bytes"
  )

  const res = binding.crypto_secretbox_easy(c, m, n, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_secretbox_open_easy = function (m, c, n, k) {
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    c.byteLength >= binding.crypto_secretbox_MACBYTES,
    "c must be at least 'crypto_secretbox_MACBYTES' bytes"
  )
  assert(
    m.byteLength === c.byteLength - binding.crypto_secretbox_MACBYTES,
    "m must be 'c.byteLength - crypto_secretbox_MACBYTES' bytes"
  )
  assert(
    n.byteLength === binding.crypto_secretbox_NONCEBYTES,
    "n must be 'crypto_secretbox_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_secretbox_KEYBYTES,
    "k must be 'crypto_secretbox_KEYBYTES' bytes"
  )

  return binding.crypto_secretbox_open_easy(m, c, n, k)
}

exports.crypto_secretbox_detached = function (c, mac, m, n, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(mac), 'mac must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(
    mac.byteLength === binding.crypto_secretbox_MACBYTES,
    "mac must be 'crypto_secretbox_MACBYTES' bytes"
  )
  assert(
    n.byteLength === binding.crypto_secretbox_NONCEBYTES,
    "n must be 'crypto_secretbox_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_secretbox_KEYBYTES,
    "k must be 'crypto_secretbox_KEYBYTES' bytes"
  )

  const res = binding.crypto_secretbox_detached(c, mac, m, n, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_secretbox_open_detached = function (m, c, mac, n, k) {
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(mac), 'mac must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(m.byteLength === c.byteLength, "m must be 'c.byteLength' bytes")
  assert(
    mac.byteLength === binding.crypto_secretbox_MACBYTES,
    "mac must be 'crypto_secretbox_MACBYTES' bytes"
  )
  assert(
    n.byteLength === binding.crypto_secretbox_NONCEBYTES,
    "n must be 'crypto_secretbox_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_secretbox_KEYBYTES,
    "k must be 'crypto_secretbox_KEYBYTES' bytes"
  )

  return binding.crypto_secretbox_open_detached(m, c, mac, n, k)
}

// crypto_generichash

exports.crypto_generichash = function (output, input, key) {
  if (!key) key = OPTIONAL

  assert(ArrayBuffer.isView(output), 'output must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(
    output.byteLength >= binding.crypto_generichash_BYTES_MIN &&
      output.byteLength <= binding.crypto_generichash_BYTES_MAX,
    'output must be between crypto_generichash_BYTES_MIN and crypto_generichash_BYTES_MAX bytes'
  )

  if (key !== OPTIONAL) {
    assert(ArrayBuffer.isView(key), 'key must be a typed array')
    assert(
      key.byteLength >= binding.crypto_generichash_KEYBYTES_MIN &&
        key.byteLength <= binding.crypto_generichash_KEYBYTES_MAX,
      'key must be between crypto_generichash_KEYBYTES_MIN and crypto_generichash_KEYBYTES_MAX bytes'
    )
  }

  const res = binding.crypto_generichash(
    output.buffer,
    output.byteOffset,
    output.byteLength,

    input.buffer,
    input.byteOffset,
    input.byteLength,

    key.buffer,
    key.byteOffset,
    key.byteLength
  )

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_generichash_batch = function (output, batch, key) {
  assert(ArrayBuffer.isView(output), 'output must be a typed array')

  if (isNode || batch.length < 4) {
    const res = binding.crypto_generichash_batch(output, batch, !!key, key || OPTIONAL)
    if (res !== 0) throw new Error('status: ' + res)
  } else {
    const state = Buffer.alloc(binding.crypto_generichash_STATEBYTES)

    exports.crypto_generichash_init(state, key, output.byteLength)

    for (const buf of batch) {
      exports.crypto_generichash_update(state, buf)
    }

    exports.crypto_generichash_final(state, output)
  }
}

exports.crypto_generichash_keygen = function (key) {
  assert(ArrayBuffer.isView(key), 'key must be a typed array')
  assert(
    key.byteLength === binding.crypto_generichash_KEYBYTES,
    "key must be 'crypto_generichash_KEYBYTES' bytes"
  )

  const res = binding.crypto_generichash_keygen(key.buffer, key.byteOffset, key.byteLength)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_generichash_init = function (state, key, outputLength) {
  if (!key) key = OPTIONAL

  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_generichash_STATEBYTES,
    "state must be 'crypto_generichash_STATEBYTES' bytes"
  )

  const res = binding.crypto_generichash_init(
    state.buffer,
    state.byteOffset,
    state.byteLength,

    key.buffer,
    key.byteOffset,
    key.byteLength,

    outputLength
  )

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_generichash_update = function (state, input) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(
    state.byteLength === binding.crypto_generichash_STATEBYTES,
    "state must be 'crypto_generichash_STATEBYTES' bytes"
  )

  const res = binding.crypto_generichash_update(
    state.buffer,
    state.byteOffset,
    state.byteLength,

    input.buffer,
    input.byteOffset,
    input.byteLength
  )

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_generichash_final = function (state, output) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(ArrayBuffer.isView(output), 'output must be a typed array')
  assert(
    state.byteLength === binding.crypto_generichash_STATEBYTES,
    "state must be 'crypto_generichash_STATEBYTES' bytes"
  )

  const res = binding.crypto_generichash_final(
    state.buffer,
    state.byteOffset,
    state.byteLength,

    output.buffer,
    output.byteOffset,
    output.byteLength
  )

  if (res !== 0) throw new Error('status: ' + res)
}

// secretstream

exports.crypto_secretstream_xchacha20poly1305_keygen = function (k) {
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_secretstream_xchacha20poly1305_KEYBYTES,
    "k must be 'crypto_secretstream_xchacha20poly1305_KEYBYTES' bytes"
  )

  binding.crypto_secretstream_xchacha20poly1305_keygen(k.buffer, k.byteOffset, k.byteLength)
}

exports.crypto_secretstream_xchacha20poly1305_init_push = function (state, header, k) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(ArrayBuffer.isView(header), 'header must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    state.byteLength === binding.crypto_secretstream_xchacha20poly1305_STATEBYTES,
    "state must be 'crypto_secretstream_xchacha20poly1305_STATEBYTES' bytes"
  )
  assert(
    header.byteLength === binding.crypto_secretstream_xchacha20poly1305_HEADERBYTES,
    "header must be 'crypto_secretstream_xchacha20poly1305_HEADERBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_secretstream_xchacha20poly1305_KEYBYTES,
    "k must be 'crypto_secretstream_xchacha20poly1305_KEYBYTES' bytes"
  )

  const res = binding.crypto_secretstream_xchacha20poly1305_init_push(
    state.buffer,
    state.byteOffset,
    state.byteLength,

    header.buffer,
    header.byteOffset,
    header.byteLength,

    k.buffer,
    k.byteOffset,
    k.byteLength
  )

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_secretstream_xchacha20poly1305_init_pull = function (state, header, k) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(ArrayBuffer.isView(header), 'header must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    state.byteLength === binding.crypto_secretstream_xchacha20poly1305_STATEBYTES,
    "state must be 'crypto_secretstream_xchacha20poly1305_STATEBYTES' bytes"
  )
  assert(
    header.byteLength === binding.crypto_secretstream_xchacha20poly1305_HEADERBYTES,
    "header must be 'crypto_secretstream_xchacha20poly1305_HEADERBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_secretstream_xchacha20poly1305_KEYBYTES,
    "k must be 'crypto_secretstream_xchacha20poly1305_KEYBYTES' bytes"
  )

  const res = binding.crypto_secretstream_xchacha20poly1305_init_pull(
    state.buffer,
    state.byteOffset,
    state.byteLength,

    header.buffer,
    header.byteOffset,
    header.byteLength,

    k.buffer,
    k.byteOffset,
    k.byteLength
  )

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_secretstream_xchacha20poly1305_push = function (state, c, m, ad, tag) {
  if (!ad) ad = OPTIONAL

  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(
    state.byteLength === binding.crypto_secretstream_xchacha20poly1305_STATEBYTES,
    "state must be 'crypto_secretstream_xchacha20poly1305_STATEBYTES' bytes"
  )
  assert(
    c.byteLength === m.byteLength + binding.crypto_secretstream_xchacha20poly1305_ABYTES,
    "c must be 'm.byteLength + crypto_secretstream_xchacha20poly1305_ABYTES' bytes"
  )

  const res = binding.crypto_secretstream_xchacha20poly1305_push(
    state.buffer,
    state.byteOffset,
    state.byteLength,

    c.buffer,
    c.byteOffset,
    c.byteLength,

    m.buffer,
    m.byteOffset,
    m.byteLength,

    ad.buffer,
    ad.byteOffset,
    ad.byteLength,

    tag
  )

  if (res < 0) throw new Error('push failed')

  return res
}

exports.crypto_secretstream_xchacha20poly1305_pull = function (state, m, tag, c, ad) {
  if (!ad) ad = OPTIONAL

  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_secretstream_xchacha20poly1305_STATEBYTES,
    "state must be 'crypto_secretstream_xchacha20poly1305_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(tag), 'tag must be a typed array')
  assert(tag.byteLength === 1, 'tag must be 1 byte')

  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(
    c.byteLength >= binding.crypto_secretstream_xchacha20poly1305_ABYTES,
    "c must be at least 'crypto_secretstream_xchacha20poly1305_ABYTES' bytes"
  )
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(
    m.byteLength === c.byteLength - binding.crypto_secretstream_xchacha20poly1305_ABYTES,
    "m must be 'c.byteLength - crypto_secretstream_xchacha20poly1305_ABYTES' bytes"
  )

  const res = binding.crypto_secretstream_xchacha20poly1305_pull(
    state.buffer,
    state.byteOffset,
    state.byteLength,

    m.buffer,
    m.byteOffset,
    m.byteLength,

    tag.buffer,
    tag.byteOffset,
    tag.byteLength,

    c.buffer,
    c.byteOffset,
    c.byteLength,

    ad.buffer,
    ad.byteOffset,
    ad.byteLength
  )

  if (res < 0) throw new Error('pull failed')

  return res
}

exports.crypto_secretstream_xchacha20poly1305_rekey = function (state) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_secretstream_xchacha20poly1305_STATEBYTES,
    "state must be 'crypto_secretstream_xchacha20poly1305_STATEBYTES' bytes"
  )

  binding.crypto_secretstream_xchacha20poly1305_rekey(
    state.buffer,
    state.byteOffset,
    state.byteLength
  )
}

// crypto_stream

exports.crypto_stream = function (c, n, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    n.byteLength === binding.crypto_stream_NONCEBYTES,
    "n must be 'crypto_stream_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_KEYBYTES,
    "k must be 'crypto_stream_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream(c, n, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_xor = function (c, m, n, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(
    n.byteLength === binding.crypto_stream_NONCEBYTES,
    "n must be 'crypto_stream_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_KEYBYTES,
    "k must be 'crypto_stream_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_xor(
    c.buffer,
    c.byteOffset,
    c.byteLength,

    m.buffer,
    m.byteOffset,
    m.byteLength,

    n.buffer,
    n.byteOffset,
    n.byteLength,

    k.buffer,
    k.byteOffset,
    k.byteLength
  )

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_chacha20 = function (c, n, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    n.byteLength === binding.crypto_stream_chacha20_NONCEBYTES,
    "n must be 'crypto_stream_chacha20_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_chacha20_KEYBYTES,
    "k must be 'crypto_stream_chacha20_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_chacha20(c, n, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_chacha20_xor = function (c, m, n, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(
    n.byteLength === binding.crypto_stream_chacha20_NONCEBYTES,
    "n must be 'crypto_stream_chacha20_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_chacha20_KEYBYTES,
    "k must be 'crypto_stream_chacha20_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_chacha20_xor(c, m, n, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_chacha20_xor_ic = function (c, m, n, ic, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(
    n.byteLength === binding.crypto_stream_chacha20_NONCEBYTES,
    "n must be 'crypto_stream_chacha20_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_chacha20_KEYBYTES,
    "k must be 'crypto_stream_chacha20_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_chacha20_xor_ic(c, m, n, ic, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_chacha20_ietf = function (c, n, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    n.byteLength === binding.crypto_stream_chacha20_ietf_NONCEBYTES,
    "n must be 'crypto_stream_chacha20_ietf_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_chacha20_ietf_KEYBYTES,
    "k must be 'crypto_stream_chacha20_ietf_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_chacha20_ietf(c, n, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_chacha20_ietf_xor = function (c, m, n, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(
    n.byteLength === binding.crypto_stream_chacha20_ietf_NONCEBYTES,
    "n must be 'crypto_stream_chacha20_ietf_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_chacha20_ietf_KEYBYTES,
    "k must be 'crypto_stream_chacha20_ietf_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_chacha20_ietf_xor(c, m, n, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_chacha20_ietf_xor_ic = function (c, m, n, ic, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(
    n.byteLength === binding.crypto_stream_chacha20_ietf_NONCEBYTES,
    "n must be 'crypto_stream_chacha20_ietf_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_chacha20_ietf_KEYBYTES,
    "k must be 'crypto_stream_chacha20_ietf_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_chacha20_ietf_xor_ic(c, m, n, ic, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_xchacha20 = function (c, n, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    n.byteLength === binding.crypto_stream_xchacha20_NONCEBYTES,
    "n must be 'crypto_stream_xchacha20_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_xchacha20_KEYBYTES,
    "k must be 'crypto_stream_xchacha20_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_xchacha20(c, n, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_xchacha20_xor = function (c, m, n, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(
    n.byteLength === binding.crypto_stream_xchacha20_NONCEBYTES,
    "n must be 'crypto_stream_xchacha20_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_xchacha20_KEYBYTES,
    "k must be 'crypto_stream_xchacha20_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_xchacha20_xor(c, m, n, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_xchacha20_xor_ic = function (c, m, n, ic, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(
    n.byteLength === binding.crypto_stream_xchacha20_NONCEBYTES,
    "n must be 'crypto_stream_xchacha20_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_xchacha20_KEYBYTES,
    "k must be 'crypto_stream_xchacha20_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_xchacha20_xor_ic(c, m, n, ic, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_salsa20 = function (c, n, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    n.byteLength === binding.crypto_stream_salsa20_NONCEBYTES,
    "n must be 'crypto_stream_salsa20_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_salsa20_KEYBYTES,
    "k must be 'crypto_stream_salsa20_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_salsa20(c, n, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_salsa20_xor = function (c, m, n, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(
    n.byteLength === binding.crypto_stream_salsa20_NONCEBYTES,
    "n must be 'crypto_stream_salsa20_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_salsa20_KEYBYTES,
    "k must be 'crypto_stream_salsa20_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_salsa20_xor(c, m, n, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_salsa20_xor_ic = function (c, m, n, ic, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(
    n.byteLength === binding.crypto_stream_salsa20_NONCEBYTES,
    "n must be 'crypto_stream_salsa20_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_salsa20_KEYBYTES,
    "k must be 'crypto_stream_salsa20_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_salsa20_xor_ic(c, m, n, ic, k)

  if (res !== 0) throw new Error('status: ' + res)
}

// crypto_auth

exports.crypto_auth = function (out, input, k) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(out.byteLength === binding.crypto_auth_BYTES, "out must be 'crypto_auth_BYTES' bytes")
  assert(k.byteLength === binding.crypto_auth_KEYBYTES, "k must be 'crypto_auth_KEYBYTES' bytes")

  const res = binding.crypto_auth(out, input, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_auth_verify = function (h, input, k) {
  assert(ArrayBuffer.isView(h), 'h must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(h.byteLength === binding.crypto_auth_BYTES, "h must be 'crypto_auth_BYTES' bytes")
  assert(k.byteLength === binding.crypto_auth_KEYBYTES, "k must be 'crypto_auth_KEYBYTES' bytes")

  return binding.crypto_auth_verify(h, input, k)
}

// crypto_onetimeauth

exports.crypto_onetimeauth = function (out, input, k) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    out.byteLength === binding.crypto_onetimeauth_BYTES,
    "out must be 'crypto_onetimeauth_BYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_onetimeauth_KEYBYTES,
    "k must be 'crypto_onetimeauth_KEYBYTES' bytes"
  )

  const res = binding.crypto_onetimeauth(out, input, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_onetimeauth_init = function (state, k) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    state.byteLength === binding.crypto_onetimeauth_STATEBYTES,
    "state must be 'crypto_onetimeauth_STATEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_onetimeauth_KEYBYTES,
    "k must be 'crypto_onetimeauth_KEYBYTES' bytes"
  )

  const res = binding.crypto_onetimeauth_init(state, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_onetimeauth_update = function (state, input) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(
    state.byteLength === binding.crypto_onetimeauth_STATEBYTES,
    "state must be 'crypto_onetimeauth_STATEBYTES' bytes"
  )

  const res = binding.crypto_onetimeauth_update(state, input)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_onetimeauth_final = function (state, out) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(
    state.byteLength === binding.crypto_onetimeauth_STATEBYTES,
    "state must be 'crypto_onetimeauth_STATEBYTES' bytes"
  )
  assert(
    out.byteLength === binding.crypto_onetimeauth_BYTES,
    "out must be 'crypto_onetimeauth_BYTES' bytes"
  )

  const res = binding.crypto_onetimeauth_final(state, out)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_onetimeauth_verify = function (h, input, k) {
  assert(ArrayBuffer.isView(h), 'h must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    h.byteLength === binding.crypto_onetimeauth_BYTES,
    "h must be 'crypto_onetimeauth_BYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_onetimeauth_KEYBYTES,
    "k must be 'crypto_onetimeauth_KEYBYTES' bytes"
  )

  return binding.crypto_onetimeauth_verify(h, input, k)
}

// crypto_pwhash

exports.crypto_pwhash = function (out, passwd, salt, opslimit, memlimit, alg) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(ArrayBuffer.isView(salt), 'salt must be a typed array')
  assert(
    out.byteLength >= binding.crypto_pwhash_BYTES_MIN,
    "out must be at least 'crypto_pwhash_BYTES_MIN' bytes"
  )
  assert(
    out.byteLength <= binding.crypto_pwhash_BYTES_MAX,
    "out must be at most 'crypto_pwhash_BYTES_MAX' bytes"
  )
  assert(
    salt.byteLength === binding.crypto_pwhash_SALTBYTES,
    "salt must be 'crypto_pwhash_SALTBYTES' bytes"
  )
  assert(
    opslimit >= binding.crypto_pwhash_OPSLIMIT_MIN,
    "opslimit must be at least 'crypto_pwhash_OPSLIMIT_MIN'"
  )
  assert(
    opslimit <= binding.crypto_pwhash_OPSLIMIT_MAX,
    "opslimit must be at most 'crypto_pwhash_OPSLIMIT_MAX'"
  )
  assert(
    memlimit >= binding.crypto_pwhash_MEMLIMIT_MIN,
    "memlimit must be at least 'crypto_pwhash_MEMLIMIT_MIN'"
  )
  assert(
    memlimit <= binding.crypto_pwhash_MEMLIMIT_MAX,
    "memlimit must be at most 'crypto_pwhash_MEMLIMIT_MAX'"
  )
  assert(alg >= 1 && alg <= 2, 'alg must be either Argon2i 1.3 or Argon2id 1.3')

  const res = binding.crypto_pwhash(out, passwd, salt, opslimit, memlimit, alg)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_pwhash_async = function (
  out,
  passwd,
  salt,
  opslimit,
  memlimit,
  alg,
  callback = undefined
) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(ArrayBuffer.isView(salt), 'salt must be a typed array')
  assert(
    out.byteLength >= binding.crypto_pwhash_BYTES_MIN,
    "out must be at least 'crypto_pwhash_BYTES_MIN' bytes"
  )
  assert(
    out.byteLength <= binding.crypto_pwhash_BYTES_MAX,
    "out must be at most 'crypto_pwhash_BYTES_MAX' bytes"
  )
  assert(
    salt.byteLength === binding.crypto_pwhash_SALTBYTES,
    "salt must be 'crypto_pwhash_SALTBYTES' bytes"
  )
  assert(
    opslimit >= binding.crypto_pwhash_OPSLIMIT_MIN,
    "opslimit must be at least 'crypto_pwhash_OPSLIMIT_MIN'"
  )
  assert(
    opslimit <= binding.crypto_pwhash_OPSLIMIT_MAX,
    "opslimit must be at most 'crypto_pwhash_OPSLIMIT_MAX'"
  )
  assert(
    memlimit >= binding.crypto_pwhash_MEMLIMIT_MIN,
    "memlimit must be at least 'crypto_pwhash_MEMLIMIT_MIN'"
  )
  assert(
    memlimit <= binding.crypto_pwhash_MEMLIMIT_MAX,
    "memlimit must be at most 'crypto_pwhash_MEMLIMIT_MAX'"
  )
  assert(alg >= 1 && alg <= 2, 'alg must be either Argon2i 1.3 or Argon2id 1.3')

  const [done, promise] = checkStatus(callback)

  binding.crypto_pwhash_async(
    out.buffer,
    out.byteOffset,
    out.byteLength,

    passwd.buffer,
    passwd.byteOffset,
    passwd.byteLength,

    salt.buffer,
    salt.byteOffset,
    salt.byteLength,

    opslimit,
    memlimit,
    alg,

    done
  )

  return promise
}

exports.crypto_pwhash_str = function (out, passwd, opslimit, memlimit) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(
    out.byteLength === binding.crypto_pwhash_STRBYTES,
    "out must be 'crypto_pwhash_STRBYTES' bytes"
  )
  assert(typeof opslimit === 'number', 'opslimit must be a number')
  assert(
    opslimit >= binding.crypto_pwhash_OPSLIMIT_MIN,
    "opslimit must be at least 'crypto_pwhash_OPSLIMIT_MIN'"
  )
  assert(
    opslimit <= binding.crypto_pwhash_OPSLIMIT_MAX,
    "opslimit must be at most 'crypto_pwhash_OPSLIMIT_MAX'"
  )
  assert(typeof memlimit === 'number', 'memlimit must be a number')
  assert(
    memlimit >= binding.crypto_pwhash_MEMLIMIT_MIN,
    "memlimit must be at least 'crypto_pwhash_MEMLIMIT_MIN'"
  )
  assert(
    memlimit <= binding.crypto_pwhash_MEMLIMIT_MAX,
    "memlimit must be at most 'crypto_pwhash_MEMLIMIT_MAX'"
  )

  const res = binding.crypto_pwhash_str(out, passwd, opslimit, memlimit)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_pwhash_str_async = function (out, passwd, opslimit, memlimit, callback = undefined) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(
    out.byteLength === binding.crypto_pwhash_STRBYTES,
    "out must be 'crypto_pwhash_STRBYTES' bytes"
  )
  assert(passwd.byteLength > 0, 'passwd must not be empty')
  assert(typeof opslimit === 'number', 'opslimit must be a number')
  assert(
    opslimit >= binding.crypto_pwhash_OPSLIMIT_MIN,
    "opslimit must be at least 'crypto_pwhash_OPSLIMIT_MIN'"
  )
  assert(
    opslimit <= binding.crypto_pwhash_OPSLIMIT_MAX,
    "opslimit must be at most 'crypto_pwhash_OPSLIMIT_MAX'"
  )
  assert(typeof memlimit === 'number', 'memlimit must be a number')
  assert(
    memlimit >= binding.crypto_pwhash_MEMLIMIT_MIN,
    "memlimit must be at least 'crypto_pwhash_MEMLIMIT_MIN'"
  )
  assert(
    memlimit <= binding.crypto_pwhash_MEMLIMIT_MAX,
    "memlimit must be at most 'crypto_pwhash_MEMLIMIT_MAX'"
  )

  const [done, promise] = checkStatus(callback)

  binding.crypto_pwhash_str_async(
    out.buffer,
    out.byteOffset,
    out.byteLength,

    passwd.buffer,
    passwd.byteOffset,
    passwd.byteLength,

    opslimit,
    memlimit,

    done
  )

  return promise
}

exports.crypto_pwhash_str_verify = function (str, passwd) {
  assert(ArrayBuffer.isView(str), 'str must be a typed array')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(
    str.byteLength === binding.crypto_pwhash_STRBYTES,
    "str must be 'crypto_pwhash_STRBYTES' bytes"
  )

  return binding.crypto_pwhash_str_verify(str, passwd)
}

exports.crypto_pwhash_str_verify_async = function (str, passwd, callback = undefined) {
  assert(ArrayBuffer.isView(str), 'str must be a typed array')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(
    str.byteLength === binding.crypto_pwhash_STRBYTES,
    "str must be 'crypto_pwhash_STRBYTES' bytes"
  )
  assert(passwd.byteLength > 0, 'passwd must not be empty')

  const [done, promise] = checkStatus(callback, true)

  binding.crypto_pwhash_str_verify_async(
    str.buffer,
    str.byteOffset,
    str.byteLength,

    passwd.buffer,
    passwd.byteOffset,
    passwd.byteLength,

    done
  )

  return promise
}

exports.crypto_pwhash_str_needs_rehash = function (str, opslimit, memlimit) {
  assert(ArrayBuffer.isView(str), 'str must be a typed array')
  assert(
    str.byteLength === binding.crypto_pwhash_STRBYTES,
    "str must be 'crypto_pwhash_STRBYTES' bytes"
  )
  assert(
    opslimit >= binding.crypto_pwhash_OPSLIMIT_MIN,
    "opslimit must be at least 'crypto_pwhash_OPSLIMIT_MIN'"
  )
  assert(
    opslimit <= binding.crypto_pwhash_OPSLIMIT_MAX,
    "opslimit must be at most 'crypto_pwhash_OPSLIMIT_MAX'"
  )
  assert(
    memlimit >= binding.crypto_pwhash_MEMLIMIT_MIN,
    "memlimit must be at least 'crypto_pwhash_MEMLIMIT_MIN'"
  )
  assert(
    memlimit <= binding.crypto_pwhash_MEMLIMIT_MAX,
    "memlimit must be at most 'crypto_pwhash_MEMLIMIT_MAX'"
  )

  return binding.crypto_pwhash_str_needs_rehash(str, opslimit, memlimit)
}

exports.crypto_pwhash_scryptsalsa208sha256 = function (out, passwd, salt, opslimit, memlimit) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(ArrayBuffer.isView(salt), 'salt must be a typed array')
  assert(
    out.byteLength >= binding.crypto_pwhash_scryptsalsa208sha256_BYTES_MIN,
    "out must be at least 'crypto_pwhash_scryptsalsa208sha256_BYTES_MIN' bytes"
  )
  assert(
    out.byteLength <= binding.crypto_pwhash_scryptsalsa208sha256_BYTES_MAX,
    "out must be at most 'crypto_pwhash_scryptsalsa208sha256_BYTES_MAX' bytes"
  )
  assert(
    salt.byteLength === binding.crypto_pwhash_scryptsalsa208sha256_SALTBYTES,
    "salt must be 'crypto_pwhash_scryptsalsa208sha256_SALTBYTES' bytes"
  )
  assert(
    opslimit >= binding.crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MIN,
    "opslimit must be at least 'crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MIN'"
  )
  assert(
    opslimit <= binding.crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MAX,
    "opslimit must be at most 'crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MAX'"
  )
  assert(
    memlimit >= binding.crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MIN,
    "memlimit must be at least 'crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MIN'"
  )
  assert(
    memlimit <= binding.crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MAX,
    "memlimit must be at most 'crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MAX'"
  )

  const res = binding.crypto_pwhash_scryptsalsa208sha256(out, passwd, salt, opslimit, memlimit)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_pwhash_scryptsalsa208sha256_async = function (
  out,
  passwd,
  salt,
  opslimit,
  memlimit,
  callback = undefined
) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(ArrayBuffer.isView(salt), 'salt must be a typed array')
  assert(
    out.byteLength >= binding.crypto_pwhash_scryptsalsa208sha256_BYTES_MIN,
    "out must be at least 'crypto_pwhash_scryptsalsa208sha256_BYTES_MIN' bytes"
  )
  assert(
    out.byteLength <= binding.crypto_pwhash_scryptsalsa208sha256_BYTES_MAX,
    "out must be at most 'crypto_pwhash_scryptsalsa208sha256_BYTES_MAX' bytes"
  )
  assert(passwd.byteLength > 0, 'passwd must not be empty')
  assert(
    salt.byteLength === binding.crypto_pwhash_scryptsalsa208sha256_SALTBYTES,
    "salt must be 'crypto_pwhash_scryptsalsa208sha256_SALTBYTES' bytes"
  )
  assert(
    opslimit >= binding.crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MIN,
    "opslimit must be at least 'crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MIN'"
  )
  assert(
    opslimit <= binding.crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MAX,
    "opslimit must be at most 'crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MAX'"
  )
  assert(
    memlimit >= binding.crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MIN,
    "memlimit must be at least 'crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MIN'"
  )
  assert(
    memlimit <= binding.crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MAX,
    "memlimit must be at most 'crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MAX'"
  )

  const [done, promise] = checkStatus(callback)

  binding.crypto_pwhash_scryptsalsa208sha256_async(
    out.buffer,
    out.byteOffset,
    out.byteLength,

    passwd.buffer,
    passwd.byteOffset,
    passwd.byteLength,

    salt.buffer,
    salt.byteOffset,
    salt.byteLength,

    opslimit,
    memlimit,

    done
  )

  return promise
}

exports.crypto_pwhash_scryptsalsa208sha256_str_async = function (
  out,
  passwd,
  opslimit,
  memlimit,
  callback = undefined
) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(
    out.byteLength === binding.crypto_pwhash_scryptsalsa208sha256_STRBYTES,
    "out must be 'crypto_pwhash_scryptsalsa208sha256_STRBYTES' bytes"
  )
  assert(passwd.byteLength > 0, 'passwd must not be empty')
  assert(
    opslimit >= binding.crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MIN,
    "opslimit must be at least 'crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MIN'"
  )
  assert(
    opslimit <= binding.crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MAX,
    "opslimit must be at most 'crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MAX'"
  )
  assert(
    memlimit >= binding.crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MIN,
    "memlimit must be at least 'crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MIN'"
  )
  assert(
    memlimit <= binding.crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MAX,
    "memlimit must be at most 'crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MAX'"
  )

  const [done, promise] = checkStatus(callback)

  binding.crypto_pwhash_scryptsalsa208sha256_str_async(
    out.buffer,
    out.byteOffset,
    out.byteLength,

    passwd.buffer,
    passwd.byteOffset,
    passwd.byteLength,

    opslimit,
    memlimit,

    done
  )

  return promise
}

exports.crypto_pwhash_scryptsalsa208sha256_str = function (out, passwd, opslimit, memlimit) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(
    out.byteLength === binding.crypto_pwhash_scryptsalsa208sha256_STRBYTES,
    "out must be 'crypto_pwhash_scryptsalsa208sha256_STRBYTES' bytes"
  )
  assert(passwd.byteLength > 0, 'passwd must not be empty')
  assert(
    opslimit >= binding.crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MIN,
    "opslimit must be at least 'crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MIN'"
  )
  assert(
    opslimit <= binding.crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MAX,
    "opslimit must be at most 'crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MAX'"
  )
  assert(
    memlimit >= binding.crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MIN,
    "memlimit must be at least 'crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MIN'"
  )
  assert(
    memlimit <= binding.crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MAX,
    "memlimit must be at most 'crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MAX'"
  )

  const res = binding.crypto_pwhash_scryptsalsa208sha256_str(out, passwd, opslimit, memlimit)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_pwhash_scryptsalsa208sha256_str_verify_async = function (
  str,
  passwd,
  callback = undefined
) {
  assert(ArrayBuffer.isView(str), 'str must be a typed array')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(
    str.byteLength === binding.crypto_pwhash_scryptsalsa208sha256_STRBYTES,
    "str must be 'crypto_pwhash_scryptsalsa208sha256_STRBYTES' bytes"
  )
  assert(passwd.byteLength > 0, 'passwd must not be empty')

  const [done, promise] = checkStatus(callback, true)

  binding.crypto_pwhash_scryptsalsa208sha256_str_verify_async(
    str.buffer,
    str.byteOffset,
    str.byteLength,

    passwd.buffer,
    passwd.byteOffset,
    passwd.byteLength,

    done
  )

  return promise
}

exports.crypto_pwhash_scryptsalsa208sha256_str_verify = function (str, passwd) {
  assert(ArrayBuffer.isView(str), 'str must be a typed array')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(
    str.byteLength === binding.crypto_pwhash_scryptsalsa208sha256_STRBYTES,
    "str must be 'crypto_pwhash_scryptsalsa208sha256_STRBYTES' bytes"
  )
  assert(passwd.byteLength > 0, 'passwd must not be empty')

  return binding.crypto_pwhash_scryptsalsa208sha256_str_verify(str, passwd)
}

exports.crypto_pwhash_scryptsalsa208sha256_str_needs_rehash = function (str, opslimit, memlimit) {
  assert(ArrayBuffer.isView(str), 'str must be a typed array')
  assert(
    str.byteLength === binding.crypto_pwhash_scryptsalsa208sha256_STRBYTES,
    "str must be 'crypto_pwhash_scryptsalsa208sha256_STRBYTES' bytes"
  )
  assert(
    opslimit >= binding.crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MIN,
    "opslimit must be at least 'crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MIN'"
  )
  assert(
    opslimit <= binding.crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MAX,
    "opslimit must be at most 'crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MAX'"
  )
  assert(
    memlimit >= binding.crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MIN,
    "memlimit must be at least 'crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MIN'"
  )
  assert(
    memlimit <= binding.crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MAX,
    "memlimit must be at most 'crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MAX'"
  )

  return binding.crypto_pwhash_scryptsalsa208sha256_str_needs_rehash(str, opslimit, memlimit)
}

// crypto_kx

exports.crypto_kx_keypair = function (pk, sk) {
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(
    pk.byteLength === binding.crypto_kx_PUBLICKEYBYTES,
    "pk must be 'crypto_kx_PUBLICKEYBYTES' bytes"
  )
  assert(
    sk.byteLength === binding.crypto_kx_SECRETKEYBYTES,
    "sk must be 'crypto_kx_SECRETKEYBYTES' bytes"
  )

  const res = binding.crypto_kx_keypair(pk, sk)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_kx_seed_keypair = function (pk, sk, seed) {
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(ArrayBuffer.isView(seed), 'seed must be a typed array')
  assert(
    pk.byteLength === binding.crypto_kx_PUBLICKEYBYTES,
    "pk must be 'crypto_kx_PUBLICKEYBYTES' bytes"
  )
  assert(
    sk.byteLength === binding.crypto_kx_SECRETKEYBYTES,
    "sk must be 'crypto_kx_SECRETKEYBYTES' bytes"
  )
  assert(
    seed.byteLength === binding.crypto_kx_SEEDBYTES,
    "seed must be 'crypto_kx_SEEDBYTES' bytes"
  )

  const res = binding.crypto_kx_seed_keypair(pk, sk, seed)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_kx_client_session_keys = function (rx, tx, clientPk, clientSk, serverPk) {
  if (!rx) rx = undefined
  if (!tx) tx = undefined

  assert(rx || tx, 'at least one session key must be specified')

  if (rx) {
    assert(ArrayBuffer.isView(rx), 'rx must be a typed array')
    assert(
      rx.byteLength === binding.crypto_kx_SESSIONKEYBYTES,
      "rx must be 'crypto_kx_SESSIONKEYBYTES' bytes"
    )
  }

  if (tx) {
    assert(ArrayBuffer.isView(tx), 'tx must be a typed array')
    assert(
      tx.byteLength === binding.crypto_kx_SESSIONKEYBYTES,
      "tx must be 'crypto_kx_SESSIONKEYBYTES' bytes"
    )
  }

  assert(ArrayBuffer.isView(clientPk), 'clientPk must be a typed array')
  assert(ArrayBuffer.isView(clientSk), 'clientSk must be a typed array')
  assert(ArrayBuffer.isView(serverPk), 'serverPk must be a typed array')
  assert(
    clientPk.byteLength === binding.crypto_kx_PUBLICKEYBYTES,
    "clientPk must be 'crypto_kx_PUBLICKEYBYTES' bytes"
  )
  assert(
    clientSk.byteLength === binding.crypto_kx_SECRETKEYBYTES,
    "clientSk must be 'crypto_kx_SECRETKEYBYTES' bytes"
  )
  assert(
    serverPk.byteLength === binding.crypto_kx_PUBLICKEYBYTES,
    "serverPk must be 'crypto_kx_PUBLICKEYBYTES' bytes"
  )

  const res = binding.crypto_kx_client_session_keys(rx, tx, clientPk, clientSk, serverPk)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_kx_server_session_keys = function (rx, tx, serverPk, serverSk, clientPk) {
  if (!rx) rx = undefined
  if (!tx) tx = undefined

  assert(rx || tx, 'at least one session key must be specified')

  if (rx) {
    assert(ArrayBuffer.isView(rx), 'rx must be a typed array')
    assert(
      rx.byteLength === binding.crypto_kx_SESSIONKEYBYTES,
      "rx must be 'crypto_kx_SESSIONKEYBYTES' bytes"
    )
  }

  if (tx) {
    assert(ArrayBuffer.isView(tx), 'tx must be a typed array')
    assert(
      tx.byteLength === binding.crypto_kx_SESSIONKEYBYTES,
      "tx must be 'crypto_kx_SESSIONKEYBYTES' bytes"
    )
  }

  assert(ArrayBuffer.isView(serverPk), 'serverPk must be a typed array')
  assert(ArrayBuffer.isView(serverSk), 'serverSk must be a typed array')
  assert(ArrayBuffer.isView(clientPk), 'clientPk must be a typed array')
  assert(
    serverPk.byteLength === binding.crypto_kx_PUBLICKEYBYTES,
    "serverPk must be 'crypto_kx_PUBLICKEYBYTES' bytes"
  )
  assert(
    serverSk.byteLength === binding.crypto_kx_SECRETKEYBYTES,
    "serverSk must be 'crypto_kx_SECRETKEYBYTES' bytes"
  )
  assert(
    clientPk.byteLength === binding.crypto_kx_PUBLICKEYBYTES,
    "clientPk must be 'crypto_kx_PUBLICKEYBYTES' bytes"
  )

  const res = binding.crypto_kx_server_session_keys(rx, tx, serverPk, serverSk, clientPk)

  if (res !== 0) throw new Error('status: ' + res)
}

// crypto_scalarmult

exports.crypto_scalarmult_base = function (q, n) {
  assert(ArrayBuffer.isView(q), 'q must be a typed array')
  assert(
    q.byteLength === binding.crypto_scalarmult_BYTES,
    "q must be 'crypto_scalarmult_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.crypto_scalarmult_SCALARBYTES,
    "n must be 'crypto_scalarmult_SCALARBYTES' bytes"
  )

  const res = binding.crypto_scalarmult_base(q, n)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_scalarmult = function (q, n, p) {
  assert(ArrayBuffer.isView(q), 'q must be a typed array')
  assert(
    q.byteLength === binding.crypto_scalarmult_BYTES,
    "q must be 'crypto_scalarmult_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.crypto_scalarmult_SCALARBYTES,
    "n must be 'crypto_scalarmult_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(p), 'p must be a typed array')
  assert(
    p.byteLength === binding.crypto_scalarmult_BYTES,
    "p must be 'crypto_scalarmult_BYTES' bytes"
  )

  const res = binding.crypto_scalarmult(q, n, p)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_scalarmult_ed25519_base = function (q, n) {
  assert(ArrayBuffer.isView(q), 'q must be a typed array')
  assert(
    q.byteLength === binding.crypto_scalarmult_ed25519_BYTES,
    "q must be 'crypto_scalarmult_ed25519_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.crypto_scalarmult_ed25519_SCALARBYTES,
    "n must be 'crypto_scalarmult_ed25519_SCALARBYTES' bytes"
  )

  const res = binding.crypto_scalarmult_ed25519_base(q, n)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_scalarmult_ed25519 = function (q, n, p) {
  assert(ArrayBuffer.isView(q), 'q must be a typed array')
  assert(
    q.byteLength === binding.crypto_scalarmult_ed25519_BYTES,
    "q must be 'crypto_scalarmult_ed25519_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.crypto_scalarmult_ed25519_SCALARBYTES,
    "n must be 'crypto_scalarmult_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(p), 'p must be a typed array')
  assert(
    p.byteLength === binding.crypto_scalarmult_ed25519_BYTES,
    "p must be 'crypto_scalarmult_ed25519_BYTES' bytes"
  )

  const res = binding.crypto_scalarmult_ed25519(q, n, p)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_core_ed25519_is_valid_point = function (p) {
  assert(ArrayBuffer.isView(p), 'p must be a typed array')
  assert(
    p.byteLength === binding.crypto_core_ed25519_BYTES,
    "p must be 'crypto_core_ed25519_BYTES' bytes"
  )

  return binding.crypto_core_ed25519_is_valid_point(p)
}

exports.crypto_core_ed25519_from_uniform = function (p, r) {
  assert(ArrayBuffer.isView(p), 'p must be a typed array')
  assert(
    p.byteLength === binding.crypto_core_ed25519_BYTES,
    "p must be 'crypto_core_ed25519_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(r), 'r must be a typed array')
  assert(
    r.byteLength === binding.crypto_core_ed25519_UNIFORMBYTES,
    "r must be 'crypto_core_ed25519_UNIFORMBYTES' bytes"
  )

  const res = binding.crypto_core_ed25519_from_uniform(p, r)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_scalarmult_ed25519_base_noclamp = function (q, n) {
  assert(ArrayBuffer.isView(q), 'q must be a typed array')
  assert(
    q.byteLength === binding.crypto_scalarmult_ed25519_BYTES,
    "q must be 'crypto_scalarmult_ed25519_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.crypto_scalarmult_ed25519_SCALARBYTES,
    "n must be 'crypto_scalarmult_ed25519_SCALARBYTES' bytes"
  )

  const res = binding.crypto_scalarmult_ed25519_base_noclamp(q, n)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_scalarmult_ed25519_noclamp = function (q, n, p) {
  assert(ArrayBuffer.isView(q), 'q must be a typed array')
  assert(
    q.byteLength === binding.crypto_scalarmult_ed25519_BYTES,
    "q must be 'crypto_scalarmult_ed25519_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.crypto_scalarmult_ed25519_SCALARBYTES,
    "n must be 'crypto_scalarmult_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(p), 'p must be a typed array')
  assert(
    p.byteLength === binding.crypto_scalarmult_ed25519_BYTES,
    "p must be 'crypto_scalarmult_ed25519_BYTES' bytes"
  )

  const res = binding.crypto_scalarmult_ed25519_noclamp(q, n, p)

  if (res !== 0) throw new Error('status: ' + res)
}

// crypto_core

exports.crypto_core_ed25519_add = function (r, p, q) {
  assert(ArrayBuffer.isView(r), 'r must be a typed array')
  assert(
    r.byteLength === binding.crypto_core_ed25519_BYTES,
    "r must be 'crypto_core_ed25519_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(p), 'p must be a typed array')
  assert(
    p.byteLength === binding.crypto_core_ed25519_BYTES,
    "p must be 'crypto_core_ed25519_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(q), 'q must be a typed array')
  assert(
    q.byteLength === binding.crypto_core_ed25519_BYTES,
    "q must be 'crypto_core_ed25519_BYTES' bytes"
  )

  const res = binding.crypto_core_ed25519_add(r, p, q)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_core_ed25519_sub = function (r, p, q) {
  assert(ArrayBuffer.isView(r), 'r must be a typed array')
  assert(
    r.byteLength === binding.crypto_core_ed25519_BYTES,
    "r must be 'crypto_core_ed25519_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(p), 'p must be a typed array')
  assert(
    p.byteLength === binding.crypto_core_ed25519_BYTES,
    "p must be 'crypto_core_ed25519_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(q), 'q must be a typed array')
  assert(
    q.byteLength === binding.crypto_core_ed25519_BYTES,
    "q must be 'crypto_core_ed25519_BYTES' bytes"
  )

  const res = binding.crypto_core_ed25519_sub(r, p, q)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_core_ed25519_scalar_random = function (r) {
  assert(ArrayBuffer.isView(r), 'r must be a typed array')
  assert(
    r.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "r must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )

  binding.crypto_core_ed25519_scalar_random(r)
}

exports.crypto_core_ed25519_scalar_reduce = function (r, s) {
  assert(ArrayBuffer.isView(r), 'r must be a typed array')
  assert(
    r.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "r must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(s), 's must be a typed array')
  assert(
    s.byteLength === binding.crypto_core_ed25519_NONREDUCEDSCALARBYTES,
    "s must be 'crypto_core_ed25519_NONREDUCEDSCALARBYTES' bytes"
  )

  binding.crypto_core_ed25519_scalar_reduce(r, s)
}

exports.crypto_core_ed25519_scalar_invert = function (recip, s) {
  assert(ArrayBuffer.isView(recip), 'recip must be a typed array')
  assert(
    recip.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "recip must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(s), 's must be a typed array')
  assert(
    s.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "s must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )

  binding.crypto_core_ed25519_scalar_invert(recip, s)
}

exports.crypto_core_ed25519_scalar_negate = function (neg, s) {
  assert(ArrayBuffer.isView(neg), 'neg must be a typed array')
  assert(
    neg.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "neg must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(s), 's must be a typed array')
  assert(
    s.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "s must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )

  binding.crypto_core_ed25519_scalar_negate(neg, s)
}

exports.crypto_core_ed25519_scalar_complement = function (comp, s) {
  assert(ArrayBuffer.isView(comp), 'comp must be a typed array')
  assert(
    comp.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "comp must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(s), 's must be a typed array')
  assert(
    s.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "s must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )

  binding.crypto_core_ed25519_scalar_complement(comp, s)
}

exports.crypto_core_ed25519_scalar_add = function (z, x, y) {
  assert(ArrayBuffer.isView(z), 'z must be a typed array')
  assert(
    z.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "z must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(x), 'x must be a typed array')
  assert(
    x.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "x must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(y), 'y must be a typed array')
  assert(
    y.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "y must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )

  binding.crypto_core_ed25519_scalar_add(z, x, y)
}

exports.crypto_core_ed25519_scalar_sub = function (z, x, y) {
  assert(ArrayBuffer.isView(z), 'z must be a typed array')
  assert(
    z.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "z must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(x), 'x must be a typed array')
  assert(
    x.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "x must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(y), 'y must be a typed array')
  assert(
    y.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "y must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )

  binding.crypto_core_ed25519_scalar_sub(z, x, y)
}

// crypto_shorthash

exports.crypto_shorthash = function (out, input, k) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(
    out.byteLength === binding.crypto_shorthash_BYTES,
    "out must be 'crypto_shorthash_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_shorthash_KEYBYTES,
    "k must be 'crypto_shorthash_KEYBYTES' bytes"
  )

  const res = binding.crypto_shorthash(out, input, k)

  if (res !== 0) throw new Error('status: ' + res)
}

// crypto_kdf

exports.crypto_kdf_keygen = function (key) {
  assert(ArrayBuffer.isView(key), 'key must be a typed array')
  assert(key.byteLength === binding.crypto_kdf_KEYBYTES, "key must be 'crypto_kdf_KEYBYTES' bytes")

  binding.crypto_kdf_keygen(key)
}

exports.crypto_kdf_derive_from_key = function (subkey, subkeyId, ctx, key) {
  assert(ArrayBuffer.isView(subkey), 'subkey must be a typed array')
  assert(
    subkey.byteLength >= binding.crypto_kdf_BYTES_MIN,
    "subkey must be at least 'crypto_kdf_BYTES_MIN' bytes"
  )
  assert(
    subkey.byteLength <= binding.crypto_kdf_BYTES_MAX,
    "subkey must be at most 'crypto_kdf_BYTES_MAX' bytes"
  )
  assert(ArrayBuffer.isView(ctx), 'ctx must be a typed array')
  assert(
    ctx.byteLength === binding.crypto_kdf_CONTEXTBYTES,
    "ctx must be 'crypto_kdf_CONTEXTBYTES' bytes"
  )
  assert(ArrayBuffer.isView(key), 'key must be a typed array')
  assert(key.byteLength === binding.crypto_kdf_KEYBYTES, "key must be 'crypto_kdf_KEYBYTES' bytes")

  const res = binding.crypto_kdf_derive_from_key(subkey, subkeyId, ctx, key)

  if (res !== 0) throw new Error('status: ' + res)
}

// crypto_hash

exports.crypto_hash = function (out, input) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(out.byteLength === binding.crypto_hash_BYTES, "out must be 'crypto_hash_BYTES' bytes")

  const res = binding.crypto_hash(out, input)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_hash_sha256 = function (out, input) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(
    out.byteLength === binding.crypto_hash_sha256_BYTES,
    "out must be 'crypto_hash_sha256_BYTES' bytes"
  )

  const res = binding.crypto_hash_sha256(out, input)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_hash_sha256_init = function (state) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_hash_sha256_STATEBYTES,
    "state must be 'crypto_hash_sha256_STATEBYTES' bytes"
  )

  const res = binding.crypto_hash_sha256_init(state)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_hash_sha256_update = function (state, input) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(
    state.byteLength === binding.crypto_hash_sha256_STATEBYTES,
    "state must be 'crypto_hash_sha256_STATEBYTES' bytes"
  )

  const res = binding.crypto_hash_sha256_update(state, input)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_hash_sha256_final = function (state, out) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_hash_sha256_STATEBYTES,
    "state must be 'crypto_hash_sha256_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(
    out.byteLength === binding.crypto_hash_sha256_BYTES,
    "out must be 'crypto_hash_sha256_BYTES' bytes"
  )

  const res = binding.crypto_hash_sha256_final(state, out)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_hash_sha512 = function (out, input) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(
    out.byteLength === binding.crypto_hash_sha512_BYTES,
    "out must be 'crypto_hash_sha512_BYTES' bytes"
  )

  const res = binding.crypto_hash_sha512(out, input)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_hash_sha512_init = function (state) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_hash_sha512_STATEBYTES,
    "state must be 'crypto_hash_sha512_STATEBYTES' bytes"
  )

  const res = binding.crypto_hash_sha512_init(state)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_hash_sha512_update = function (state, input) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(
    state.byteLength === binding.crypto_hash_sha512_STATEBYTES,
    "state must be 'crypto_hash_sha512_STATEBYTES' bytes"
  )

  const res = binding.crypto_hash_sha512_update(state, input)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_hash_sha512_final = function (state, out) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_hash_sha512_STATEBYTES,
    "state must be 'crypto_hash_sha512_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(
    out.byteLength === binding.crypto_hash_sha512_BYTES,
    "out must be 'crypto_hash_sha512_BYTES' bytes"
  )

  const res = binding.crypto_hash_sha512_final(state, out)

  if (res !== 0) throw new Error('status: ' + res)
}

// crypto_aead

exports.crypto_aead_xchacha20poly1305_ietf_keygen = function (k) {
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_aead_xchacha20poly1305_ietf_KEYBYTES,
    "k must be 'crypto_aead_xchacha20poly1305_ietf_KEYBYTES' bytes"
  )

  binding.crypto_aead_xchacha20poly1305_ietf_keygen(k)
}

exports.crypto_aead_xchacha20poly1305_ietf_encrypt = function (c, m, ad, nsec, npub, k) {
  if (!ad) ad = undefined

  assert(nsec === null, 'nsec must always be set to null')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(
    c.byteLength === m.byteLength + binding.crypto_aead_xchacha20poly1305_ietf_ABYTES,
    "c must be 'm.byteLength + crypto_aead_xchacha20poly1305_ietf_ABYTES' bytes"
  )
  assert(c.byteLength <= 0xffffffff, 'c.byteLength must be a 32bit integer')
  assert(ArrayBuffer.isView(npub), 'npub must be a typed array')
  assert(
    npub.byteLength === binding.crypto_aead_xchacha20poly1305_ietf_NPUBBYTES,
    "npub must be 'crypto_aead_xchacha20poly1305_ietf_NPUBBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_aead_xchacha20poly1305_ietf_KEYBYTES,
    "k must be 'crypto_aead_xchacha20poly1305_ietf_KEYBYTES' bytes"
  )

  const res = binding.crypto_aead_xchacha20poly1305_ietf_encrypt(c, m, ad, npub, k)

  if (res < 0) throw new Error('could not encrypt data')

  return res
}

exports.crypto_aead_xchacha20poly1305_ietf_decrypt = function (m, nsec, c, ad, npub, k) {
  if (!ad) ad = undefined

  assert(nsec === null, 'nsec must always be set to null')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(
    m.byteLength === c.byteLength - binding.crypto_aead_xchacha20poly1305_ietf_ABYTES,
    "m must be 'c.byteLength - crypto_aead_xchacha20poly1305_ietf_ABYTES' bytes"
  )
  assert(m.byteLength <= 0xffffffff, 'm.byteLength must be a 32bit integer')
  assert(ArrayBuffer.isView(npub), 'npub must be a typed array')
  assert(
    npub.byteLength === binding.crypto_aead_xchacha20poly1305_ietf_NPUBBYTES,
    "npub must be 'crypto_aead_xchacha20poly1305_ietf_NPUBBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_aead_xchacha20poly1305_ietf_KEYBYTES,
    "k must be 'crypto_aead_xchacha20poly1305_ietf_KEYBYTES' bytes"
  )

  const res = binding.crypto_aead_xchacha20poly1305_ietf_decrypt(m, c, ad, npub, k)

  if (res < 0) throw new Error('could not verify data')

  return res
}

exports.crypto_aead_xchacha20poly1305_ietf_encrypt_detached = function (
  c,
  mac,
  m,
  ad,
  nsec,
  npub,
  k
) {
  if (!ad) ad = undefined

  assert(nsec === null, 'nsec must always be set to null')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(ArrayBuffer.isView(mac), 'mac must be a typed array')
  assert(
    mac.byteLength === binding.crypto_aead_xchacha20poly1305_ietf_ABYTES,
    "mac must be 'crypto_aead_xchacha20poly1305_ietf_ABYTES' bytes"
  )
  assert(ArrayBuffer.isView(npub), 'npub must be a typed array')
  assert(
    npub.byteLength === binding.crypto_aead_xchacha20poly1305_ietf_NPUBBYTES,
    "npub must be 'crypto_aead_xchacha20poly1305_ietf_NPUBBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_aead_xchacha20poly1305_ietf_KEYBYTES,
    "k must be 'crypto_aead_xchacha20poly1305_ietf_KEYBYTES' bytes"
  )

  const res = binding.crypto_aead_xchacha20poly1305_ietf_encrypt_detached(c, mac, m, ad, npub, k)

  if (res < 0) throw new Error('could not encrypt data')

  return res
}

exports.crypto_aead_xchacha20poly1305_ietf_decrypt_detached = function (
  m,
  nsec,
  c,
  mac,
  ad,
  npub,
  k
) {
  if (!ad) ad = undefined

  assert(nsec === null, 'nsec must always be set to null')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(m.byteLength === c.byteLength, "m must be 'c.byteLength' bytes")
  assert(ArrayBuffer.isView(mac), 'mac must be a typed array')
  assert(
    mac.byteLength === binding.crypto_aead_xchacha20poly1305_ietf_ABYTES,
    "mac must be 'crypto_aead_xchacha20poly1305_ietf_ABYTES' bytes"
  )
  assert(ArrayBuffer.isView(npub), 'npub must be a typed array')
  assert(
    npub.byteLength === binding.crypto_aead_xchacha20poly1305_ietf_NPUBBYTES,
    "npub must be 'crypto_aead_xchacha20poly1305_ietf_NPUBBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_aead_xchacha20poly1305_ietf_KEYBYTES,
    "k must be 'crypto_aead_xchacha20poly1305_ietf_KEYBYTES' bytes"
  )

  const res = binding.crypto_aead_xchacha20poly1305_ietf_decrypt_detached(m, c, mac, ad, npub, k)

  if (res !== 0) throw new Error('could not verify data')
}

exports.crypto_aead_chacha20poly1305_ietf_keygen = function (k) {
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_aead_chacha20poly1305_ietf_KEYBYTES,
    "k must be 'crypto_aead_chacha20poly1305_ietf_KEYBYTES' bytes"
  )

  binding.crypto_aead_chacha20poly1305_ietf_keygen(k)
}

exports.crypto_aead_chacha20poly1305_ietf_encrypt = function (c, m, ad, nsec, npub, k) {
  if (!ad) ad = undefined

  assert(nsec === null, 'nsec must always be set to null')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(
    c.byteLength === m.byteLength + binding.crypto_aead_chacha20poly1305_ietf_ABYTES,
    "c must be 'm.byteLength + crypto_aead_chacha20poly1305_ietf_ABYTES' bytes"
  )
  assert(c.byteLength <= 0xffffffff, 'c.byteLength must be a 32bit integer')
  assert(ArrayBuffer.isView(npub), 'npub must be a typed array')
  assert(
    npub.byteLength === binding.crypto_aead_chacha20poly1305_ietf_NPUBBYTES,
    "npub must be 'crypto_aead_chacha20poly1305_ietf_NPUBBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_aead_chacha20poly1305_ietf_KEYBYTES,
    "k must be 'crypto_aead_chacha20poly1305_ietf_KEYBYTES' bytes"
  )

  const res = binding.crypto_aead_chacha20poly1305_ietf_encrypt(c, m, ad, npub, k)

  if (res < 0) throw new Error('could not encrypt data')

  return res
}

exports.crypto_aead_chacha20poly1305_ietf_decrypt = function (m, nsec, c, ad, npub, k) {
  if (!ad) ad = undefined

  assert(nsec === null, 'nsec must always be set to null')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(
    m.byteLength === c.byteLength - binding.crypto_aead_chacha20poly1305_ietf_ABYTES,
    "m must be 'c.byteLength - crypto_aead_chacha20poly1305_ietf_ABYTES' bytes"
  )
  assert(m.byteLength <= 0xffffffff, 'm.byteLength must be a 32bit integer')
  assert(ArrayBuffer.isView(npub), 'npub must be a typed array')
  assert(
    npub.byteLength === binding.crypto_aead_chacha20poly1305_ietf_NPUBBYTES,
    "npub must be 'crypto_aead_chacha20poly1305_ietf_NPUBBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_aead_chacha20poly1305_ietf_KEYBYTES,
    "k must be 'crypto_aead_chacha20poly1305_ietf_KEYBYTES' bytes"
  )

  const res = binding.crypto_aead_chacha20poly1305_ietf_decrypt(m, c, ad, npub, k)

  if (res < 0) throw new Error('could not verify data')

  return res
}

exports.crypto_aead_chacha20poly1305_ietf_encrypt_detached = function (
  c,
  mac,
  m,
  ad,
  nsec,
  npub,
  k
) {
  if (!ad) ad = undefined

  assert(nsec === null, 'nsec must always be set to null')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(ArrayBuffer.isView(mac), 'mac must be a typed array')
  assert(
    mac.byteLength === binding.crypto_aead_chacha20poly1305_ietf_ABYTES,
    "mac must be 'crypto_aead_chacha20poly1305_ietf_ABYTES' bytes"
  )
  assert(ArrayBuffer.isView(npub), 'npub must be a typed array')
  assert(
    npub.byteLength === binding.crypto_aead_chacha20poly1305_ietf_NPUBBYTES,
    "npub must be 'crypto_aead_chacha20poly1305_ietf_NPUBBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_aead_chacha20poly1305_ietf_KEYBYTES,
    "k must be 'crypto_aead_chacha20poly1305_ietf_KEYBYTES' bytes"
  )

  const res = binding.crypto_aead_chacha20poly1305_ietf_encrypt_detached(c, mac, m, ad, npub, k)

  if (res < 0) throw new Error('could not encrypt data')

  return res
}

exports.crypto_aead_chacha20poly1305_ietf_decrypt_detached = function (
  m,
  nsec,
  c,
  mac,
  ad,
  npub,
  k
) {
  if (!ad) ad = undefined

  assert(nsec === null, 'nsec must always be set to null')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(m.byteLength === c.byteLength, "m must be 'c.byteLength' bytes")
  assert(ArrayBuffer.isView(mac), 'mac must be a typed array')
  assert(
    mac.byteLength === binding.crypto_aead_chacha20poly1305_ietf_ABYTES,
    "mac must be 'crypto_aead_chacha20poly1305_ietf_ABYTES' bytes"
  )
  assert(ArrayBuffer.isView(npub), 'npub must be a typed array')
  assert(
    npub.byteLength === binding.crypto_aead_chacha20poly1305_ietf_NPUBBYTES,
    "npub must be 'crypto_aead_chacha20poly1305_ietf_NPUBBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_aead_chacha20poly1305_ietf_KEYBYTES,
    "k must be 'crypto_aead_chacha20poly1305_ietf_KEYBYTES' bytes"
  )

  const res = binding.crypto_aead_chacha20poly1305_ietf_decrypt_detached(m, c, mac, ad, npub, k)

  if (res !== 0) throw new Error('could not verify data')
}

// crypto_stream

exports.crypto_stream_xor_wrap_init = function (state, n, k) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.sn_crypto_stream_xor_STATEBYTES,
    "state must be 'sn_crypto_stream_xor_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.crypto_stream_NONCEBYTES,
    "n must be 'crypto_stream_NONCEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_stream_KEYBYTES,
    "k must be 'crypto_stream_KEYBYTES' bytes"
  )

  binding.crypto_stream_xor_wrap_init(state, n, k)
}

exports.crypto_stream_xor_wrap_update = function (state, c, m) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.sn_crypto_stream_xor_STATEBYTES,
    "state must be 'sn_crypto_stream_xor_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")

  binding.crypto_stream_xor_wrap_update(state, c, m)
}

exports.crypto_stream_xor_wrap_final = function (state) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.sn_crypto_stream_xor_STATEBYTES,
    "state must be 'sn_crypto_stream_xor_STATEBYTES' bytes"
  )

  binding.crypto_stream_xor_wrap_final(state)
}

exports.crypto_stream_chacha20_xor_wrap_init = function (state, n, k) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_stream_chacha20_xor_STATEBYTES,
    "state must be 'crypto_stream_chacha20_xor_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.crypto_stream_chacha20_NONCEBYTES,
    "n must be 'crypto_stream_chacha20_NONCEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_stream_chacha20_KEYBYTES,
    "k must be 'crypto_stream_chacha20_KEYBYTES' bytes"
  )

  binding.crypto_stream_chacha20_xor_wrap_init(state, n, k)
}

exports.crypto_stream_chacha20_xor_wrap_update = function (state, c, m) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_stream_chacha20_xor_STATEBYTES,
    "state must be 'crypto_stream_chacha20_xor_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")

  binding.crypto_stream_chacha20_xor_wrap_update(state, c, m)
}

exports.crypto_stream_chacha20_xor_wrap_final = function (state) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_stream_chacha20_xor_STATEBYTES,
    "state must be 'crypto_stream_chacha20_xor_STATEBYTES' bytes"
  )

  binding.crypto_stream_chacha20_xor_wrap_final(state)
}

exports.crypto_stream_chacha20_ietf_xor_wrap_init = function (state, n, k) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_stream_chacha20_ietf_xor_STATEBYTES,
    "state must be 'crypto_stream_chacha20_ietf_xor_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.crypto_stream_chacha20_ietf_NONCEBYTES,
    "n must be 'crypto_stream_chacha20_ietf_NONCEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_stream_chacha20_ietf_KEYBYTES,
    "k must be 'crypto_stream_chacha20_ietf_KEYBYTES' bytes"
  )

  binding.crypto_stream_chacha20_ietf_xor_wrap_init(state, n, k)
}

exports.crypto_stream_chacha20_ietf_xor_wrap_update = function (state, c, m) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_stream_chacha20_ietf_xor_STATEBYTES,
    "state must be 'crypto_stream_chacha20_ietf_xor_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")

  binding.crypto_stream_chacha20_ietf_xor_wrap_update(state, c, m)
}

exports.crypto_stream_chacha20_ietf_xor_wrap_final = function (state) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_stream_chacha20_ietf_xor_STATEBYTES,
    "state must be 'crypto_stream_chacha20_ietf_xor_STATEBYTES' bytes"
  )

  binding.crypto_stream_chacha20_ietf_xor_wrap_final(state)
}

exports.crypto_stream_xchacha20_xor_wrap_init = function (state, n, k) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_stream_xchacha20_xor_STATEBYTES,
    "state must be 'crypto_stream_xchacha20_xor_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.crypto_stream_xchacha20_NONCEBYTES,
    "n must be 'crypto_stream_xchacha20_NONCEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_stream_xchacha20_KEYBYTES,
    "k must be 'crypto_stream_xchacha20_KEYBYTES' bytes"
  )

  binding.crypto_stream_xchacha20_xor_wrap_init(state, n, k)
}

exports.crypto_stream_xchacha20_xor_wrap_update = function (state, c, m) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_stream_xchacha20_xor_STATEBYTES,
    "state must be 'crypto_stream_xchacha20_xor_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")

  binding.crypto_stream_xchacha20_xor_wrap_update(state, c, m)
}

exports.crypto_stream_xchacha20_xor_wrap_final = function (state) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_stream_xchacha20_xor_STATEBYTES,
    "state must be 'crypto_stream_xchacha20_xor_STATEBYTES' bytes"
  )

  binding.crypto_stream_xchacha20_xor_wrap_final(state)
}

exports.crypto_stream_salsa20_xor_wrap_init = function (state, n, k) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_stream_salsa20_xor_STATEBYTES,
    "state must be 'crypto_stream_salsa20_xor_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.crypto_stream_salsa20_NONCEBYTES,
    "n must be 'crypto_stream_salsa20_NONCEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_stream_salsa20_KEYBYTES,
    "k must be 'crypto_stream_salsa20_KEYBYTES' bytes"
  )

  binding.crypto_stream_salsa20_xor_wrap_init(state, n, k)
}

exports.crypto_stream_salsa20_xor_wrap_update = function (state, c, m) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_stream_salsa20_xor_STATEBYTES,
    "state must be 'crypto_stream_salsa20_xor_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")

  binding.crypto_stream_salsa20_xor_wrap_update(state, c, m)
}

exports.crypto_stream_salsa20_xor_wrap_final = function (state) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_stream_salsa20_xor_STATEBYTES,
    "state must be 'crypto_stream_salsa20_xor_STATEBYTES' bytes"
  )

  binding.crypto_stream_salsa20_xor_wrap_final(state)
}

// experimental

exports.extension_tweak_ed25519_base = function (n, p, ns) {
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "n must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(p), 'p must be a typed array')
  assert(
    p.byteLength === binding.extension_tweak_ed25519_BYTES,
    "p must be 'extension_tweak_ed25519_BYTES' bytes"
  )

  binding.extension_tweak_ed25519_base(n, p, ns)
}

exports.extension_tweak_ed25519_sign_detached = function (sig, m, scalar, pk) {
  assert(ArrayBuffer.isView(sig), 'sig must be a typed array')
  assert(sig.byteLength === binding.crypto_sign_BYTES, "sig must be 'crypto_sign_BYTES' bytes")
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(scalar), 'scalar must be a typed array')
  assert(
    scalar.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "scalar must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )

  if (pk) {
    assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
    assert(
      pk.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
      "pk must be 'crypto_sign_PUBLICKEYBYTES' bytes"
    )
  }

  const res = binding.extension_tweak_ed25519_sign_detached(sig, m, scalar, pk)

  if (res !== 0) throw new Error('failed to compute signature')
}

exports.extension_tweak_ed25519_sk_to_scalar = function (n, sk) {
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "n must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(
    sk.byteLength === binding.crypto_sign_SECRETKEYBYTES,
    "sk must be 'crypto_sign_SECRETKEYBYTES' bytes"
  )

  binding.extension_tweak_ed25519_sk_to_scalar(n, sk)
}

exports.extension_tweak_ed25519_scalar = function (scalarOut, scalar, ns) {
  assert(ArrayBuffer.isView(scalarOut), 'scalarOut must be a typed array')
  assert(
    scalarOut.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "scalarOut must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(scalar), 'scalar must be a typed array')
  assert(
    scalar.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "scalar must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )

  binding.extension_tweak_ed25519_scalar(scalarOut, scalar, ns)
}

exports.extension_tweak_ed25519_pk = function (tpk, pk, ns) {
  assert(ArrayBuffer.isView(tpk), 'tpk must be a typed array')
  assert(
    tpk.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
    "tpk must be 'crypto_sign_PUBLICKEYBYTES' bytes"
  )
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(
    pk.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
    "pk must be 'crypto_sign_PUBLICKEYBYTES' bytes"
  )

  const res = binding.extension_tweak_ed25519_pk(tpk, pk, ns)

  if (res !== 0) throw new Error('failed to tweak public key')
}

exports.extension_tweak_ed25519_keypair = function (pk, scalarOut, scalarIn, ns) {
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(
    pk.byteLength === binding.extension_tweak_ed25519_BYTES,
    "pk must be 'extension_tweak_ed25519_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(scalarOut), 'scalarOut must be a typed array')
  assert(
    scalarOut.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "scalarOut must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(scalarIn), 'scalarIn must be a typed array')
  assert(
    scalarIn.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "scalarIn must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )

  binding.extension_tweak_ed25519_keypair(pk, scalarOut, scalarIn, ns)
}

exports.extension_tweak_ed25519_scalar_add = function (scalarOut, scalar, n) {
  assert(ArrayBuffer.isView(scalarOut), 'scalarOut must be a typed array')
  assert(
    scalarOut.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "scalarOut must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(scalar), 'scalar must be a typed array')
  assert(
    scalar.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "scalar must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "n must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )

  binding.extension_tweak_ed25519_scalar_add(scalarOut, scalar, n)
}

exports.extension_tweak_ed25519_pk_add = function (tpk, pk, p) {
  assert(ArrayBuffer.isView(tpk), 'tpk must be a typed array')
  assert(
    tpk.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
    "tpk must be 'crypto_sign_PUBLICKEYBYTES' bytes"
  )
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(
    pk.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
    "pk must be 'crypto_sign_PUBLICKEYBYTES' bytes"
  )
  assert(ArrayBuffer.isView(p), 'p must be a typed array')
  assert(
    p.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
    "p must be 'crypto_sign_PUBLICKEYBYTES' bytes"
  )

  const res = binding.extension_tweak_ed25519_pk_add(tpk, pk, p)

  if (res !== 0) throw new Error('failed to add tweak to public key')
}

exports.extension_tweak_ed25519_keypair_add = function (pk, scalarOut, scalarIn, tweak) {
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(
    pk.byteLength === binding.extension_tweak_ed25519_BYTES,
    "pk must be 'extension_tweak_ed25519_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(scalarOut), 'scalarOut must be a typed array')
  assert(
    scalarOut.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "scalarOut must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(scalarIn), 'scalarIn must be a typed array')
  assert(
    scalarIn.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "scalarIn must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(tweak), 'tweak must be a typed array')
  assert(
    tweak.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "tweak must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )

  const res = binding.extension_tweak_ed25519_keypair_add(pk, scalarOut, scalarIn, tweak)

  if (res !== 0) throw new Error('failed to add tweak to keypair')
}

exports.extension_pbkdf2_sha512_async = function (out, passwd, salt, iter, outlen, callback) {
  assert(
    iter >= binding.extension_pbkdf2_sha512_ITERATIONS_MIN,
    "iter must be at least 'extension_pbkdf2_sha512_ITERATIONS_MIN'"
  )
  assert(
    outlen <= binding.extension_pbkdf2_sha512_BYTES_MAX,
    "outlen must be at most 'extension_pbkdf2_sha512_BYTES_MAX'"
  )
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(out.byteLength >= outlen, "out must be at least 'outlen' bytes")
  assert(out.byteLength > 0, 'out must not be empty')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(passwd.byteLength > 0, 'passwd must not be empty')
  assert(ArrayBuffer.isView(salt), 'salt must be a typed array')
  assert(salt.byteLength > 0, 'salt must not be empty')

  const [done, promise] = checkStatus(callback)

  binding.extension_pbkdf2_sha512_async(
    out.buffer,
    out.byteOffset,
    out.byteLength,

    passwd.buffer,
    passwd.byteOffset,
    passwd.byteLength,

    salt.buffer,
    salt.byteOffset,
    salt.byteLength,

    iter,
    outlen,

    done
  )

  return promise
}

exports.extension_pbkdf2_sha512 = function (out, passwd, salt, iter, outlen) {
  assert(
    iter >= binding.extension_pbkdf2_sha512_ITERATIONS_MIN,
    "iter must be at least 'extension_pbkdf2_sha512_ITERATIONS_MIN'"
  )
  assert(
    outlen <= binding.extension_pbkdf2_sha512_BYTES_MAX,
    "outlen must be at most 'extension_pbkdf2_sha512_BYTES_MAX'"
  )
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(out.byteLength >= outlen, "out must be at least 'outlen' bytes")
  assert(out.byteLength > 0, 'out must not be empty')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(passwd.byteLength > 0, 'passwd must not be empty')
  assert(ArrayBuffer.isView(salt), 'salt must be a typed array')
  assert(salt.byteLength > 0, 'salt must not be empty')

  const res = binding.extension_pbkdf2_sha512(out, passwd, salt, iter, outlen)

  if (res !== 0) throw new Error('failed to add tweak to public key')
}

function checkStatus(callback, booleanResult = false) {
  let done, promise

  if (typeof callback === 'function') {
    done = function (status) {
      if (booleanResult) callback(null, status === 0)
      else if (status === 0) callback(null)
      else callback(new Error('status: ' + status))
    }
  } else {
    promise = new Promise(function (resolve, reject) {
      done = function (status) {
        if (booleanResult) resolve(status === 0)
        else if (status === 0) resolve()
        else reject(new Error('status: ' + status))
      }
    })
  }

  return [done, promise]
}
{
  "name": "sodium-native",
  "version": "5.1.0",
  "description": "Low level bindings for libsodium",
  "main": "index.js",
  "files": [
    "index.js",
    "binding.cc",
    "binding.js",
    "extensions",
    "prebuilds",
    "CMakeLists.txt"
  ],
  "imports": {
    "assert": {
      "bare": "bare-assert",
      "node": "assert"
    }
  },
  "addon": true,
  "dependencies": {
    "bare-assert": "^1.2.0",
    "require-addon": "^1.1.0",
    "which-runtime": "^1.2.1"
  },
  "devDependencies": {
    "bare-compat-napi": "^1.3.5",
    "brittle": "^3.16.2",
    "cmake-bare": "^1.6.1",
    "cmake-fetch": "^1.4.3",
    "cmake-napi": "^1.2.1",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^2.0.0"
  },
  "scripts": {
    "test": "prettier . --check && npm run test:node && npm run test:bare",
    "test:node": "node test/all.js",
    "test:bare": "bare test/all.js"
  },
  "standard": {
    "ignore": [
      "/test/fixtures/*.js"
    ]
  },
  "engines": {
    "bare": ">=1.16.0"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/sodium-native.git"
  },
  "contributors": [
    "Emil Bay <github@tixz.dk> (http://bayes.dk)",
    "Mathias Buus <mathiasbuus@gmail.com> (https://mafinto.sh)",
    "Christophe Diederichs <chm-diederichs@hyperdivision.dk>"
  ],
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/holepunchto/sodium-native/issues"
  },
  "homepage": "https://github.com/holepunchto/sodium-native"
}
module.exports = require('sodium-native')
{
  "name": "sodium-universal",
  "version": "5.0.1",
  "description": "Universal wrapper for sodium-javascript and sodium-native working in Node.js and the Browser",
  "main": "index.js",
  "dependencies": {
    "sodium-native": "^5.0.1"
  },
  "peerDependencies": {
    "sodium-javascript": "~0.8.0"
  },
  "peerDependenciesMeta": {
    "sodium-javascript": {
      "optional": true
    }
  },
  "scripts": {
    "prepublish": "./build-scripts/generate.js"
  },
  "browser": {
    "sodium-native": "sodium-javascript"
  },
  "browserify": {
    "transform": [
      "./build-scripts/transform.js"
    ]
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/sodium-universal.git"
  },
  "keywords": [
    "libsodium",
    "sodium",
    "sodium-native",
    "sodium-javascript",
    "browserify"
  ],
  "contributors": [
    "Emil Bay <github@tixz.dk> (http://bayes.dk)",
    "Mathias Buus <mathiasbuus@gmail.com> (https://mafinto.sh)",
    "Christophe Diederichs <chm-diederichs@hyperdivision.dk>"
  ],
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/holepunchto/sodium-universal/issues"
  },
  "homepage": "https://github.com/holepunchto/sodium-universal#readme"
}
const { EventEmitter } = require('events-universal')
const FIFO = require('fast-fifo')
const TextDecoder = require('text-decoder')

const StreamError = require('./lib/errors.js')

// if we do a future major, expect queue microtask to be there always, for now a bit defensive
const qmt =
  typeof queueMicrotask === 'undefined' ? (fn) => global.process.nextTick(fn) : queueMicrotask

// 29 bits used total (4 from shared, 14 from read, and 11 from write)
const MAX = (1 << 29) - 1

// Shared state
const OPENING = 0b0001
const PREDESTROYING = 0b0010
const DESTROYING = 0b0100
const DESTROYED = 0b1000

const NOT_OPENING = MAX ^ OPENING
const NOT_PREDESTROYING = MAX ^ PREDESTROYING

// Read state (4 bit offset from shared state)
const READ_ACTIVE = 0b00000000000001 << 4
const READ_UPDATING = 0b00000000000010 << 4
const READ_PRIMARY = 0b00000000000100 << 4
const READ_QUEUED = 0b00000000001000 << 4
const READ_RESUMED = 0b00000000010000 << 4
const READ_PIPE_DRAINED = 0b00000000100000 << 4
const READ_ENDING = 0b00000001000000 << 4
const READ_EMIT_DATA = 0b00000010000000 << 4
const READ_EMIT_READABLE = 0b00000100000000 << 4
const READ_EMITTED_READABLE = 0b00001000000000 << 4
const READ_DONE = 0b00010000000000 << 4
const READ_NEXT_TICK = 0b00100000000000 << 4
const READ_NEEDS_PUSH = 0b01000000000000 << 4
const READ_READ_AHEAD = 0b10000000000000 << 4

// Combined read state
const READ_FLOWING = READ_RESUMED | READ_PIPE_DRAINED
const READ_ACTIVE_AND_NEEDS_PUSH = READ_ACTIVE | READ_NEEDS_PUSH
const READ_PRIMARY_AND_ACTIVE = READ_PRIMARY | READ_ACTIVE
const READ_EMIT_READABLE_AND_QUEUED = READ_EMIT_READABLE | READ_QUEUED
const READ_RESUMED_READ_AHEAD = READ_RESUMED | READ_READ_AHEAD

const READ_NOT_ACTIVE = MAX ^ READ_ACTIVE
const READ_NON_PRIMARY = MAX ^ READ_PRIMARY
const READ_NON_PRIMARY_AND_PUSHED = MAX ^ (READ_PRIMARY | READ_NEEDS_PUSH)
const READ_PUSHED = MAX ^ READ_NEEDS_PUSH
const READ_PAUSED = MAX ^ READ_RESUMED
const READ_NOT_QUEUED = MAX ^ (READ_QUEUED | READ_EMITTED_READABLE)
const READ_NOT_ENDING = MAX ^ READ_ENDING
const READ_PIPE_NOT_DRAINED = MAX ^ READ_FLOWING
const READ_NOT_NEXT_TICK = MAX ^ READ_NEXT_TICK
const READ_NOT_UPDATING = MAX ^ READ_UPDATING
const READ_NO_READ_AHEAD = MAX ^ READ_READ_AHEAD
const READ_PAUSED_NO_READ_AHEAD = MAX ^ READ_RESUMED_READ_AHEAD

// Write state (18 bit offset, 4 bit offset from shared state and 14 from read state)
const WRITE_ACTIVE = 0b00000000001 << 18
const WRITE_UPDATING = 0b00000000010 << 18
const WRITE_PRIMARY = 0b00000000100 << 18
const WRITE_QUEUED = 0b00000001000 << 18
const WRITE_UNDRAINED = 0b00000010000 << 18
const WRITE_DONE = 0b00000100000 << 18
const WRITE_EMIT_DRAIN = 0b00001000000 << 18
const WRITE_NEXT_TICK = 0b00010000000 << 18
const WRITE_WRITING = 0b00100000000 << 18
const WRITE_FINISHING = 0b01000000000 << 18
const WRITE_CORKED = 0b10000000000 << 18

const WRITE_NOT_ACTIVE = MAX ^ (WRITE_ACTIVE | WRITE_WRITING)
const WRITE_NON_PRIMARY = MAX ^ WRITE_PRIMARY
const WRITE_NOT_FINISHING = MAX ^ (WRITE_ACTIVE | WRITE_FINISHING)
const WRITE_DRAINED = MAX ^ WRITE_UNDRAINED
const WRITE_NOT_QUEUED = MAX ^ WRITE_QUEUED
const WRITE_NOT_NEXT_TICK = MAX ^ WRITE_NEXT_TICK
const WRITE_NOT_UPDATING = MAX ^ WRITE_UPDATING
const WRITE_NOT_CORKED = MAX ^ WRITE_CORKED

// Combined shared state
const ACTIVE = READ_ACTIVE | WRITE_ACTIVE
const NOT_ACTIVE = MAX ^ ACTIVE
const DONE = READ_DONE | WRITE_DONE
const DESTROY_STATUS = DESTROYING | DESTROYED | PREDESTROYING
const OPEN_STATUS = DESTROY_STATUS | OPENING
const AUTO_DESTROY = DESTROY_STATUS | DONE
const NON_PRIMARY = WRITE_NON_PRIMARY & READ_NON_PRIMARY
const ACTIVE_OR_TICKING = WRITE_NEXT_TICK | READ_NEXT_TICK
const TICKING = ACTIVE_OR_TICKING & NOT_ACTIVE
const IS_OPENING = OPEN_STATUS | TICKING

// Combined shared state and read state
const READ_PRIMARY_STATUS = OPEN_STATUS | READ_ENDING | READ_DONE
const READ_STATUS = OPEN_STATUS | READ_DONE | READ_QUEUED
const READ_ENDING_STATUS = OPEN_STATUS | READ_ENDING | READ_QUEUED
const READ_READABLE_STATUS = OPEN_STATUS | READ_EMIT_READABLE | READ_QUEUED | READ_EMITTED_READABLE
const SHOULD_NOT_READ =
  OPEN_STATUS | READ_ACTIVE | READ_ENDING | READ_DONE | READ_NEEDS_PUSH | READ_READ_AHEAD
const READ_BACKPRESSURE_STATUS = DESTROY_STATUS | READ_ENDING | READ_DONE
const READ_UPDATE_SYNC_STATUS = READ_UPDATING | OPEN_STATUS | READ_NEXT_TICK | READ_PRIMARY
const READ_NEXT_TICK_OR_OPENING = READ_NEXT_TICK | OPENING

// Combined write state
const WRITE_PRIMARY_STATUS = OPEN_STATUS | WRITE_FINISHING | WRITE_DONE
const WRITE_QUEUED_AND_UNDRAINED = WRITE_QUEUED | WRITE_UNDRAINED
const WRITE_QUEUED_AND_ACTIVE = WRITE_QUEUED | WRITE_ACTIVE
const WRITE_DRAIN_STATUS = WRITE_QUEUED | WRITE_UNDRAINED | OPEN_STATUS | WRITE_ACTIVE
const WRITE_STATUS = OPEN_STATUS | WRITE_ACTIVE | WRITE_QUEUED | WRITE_CORKED
const WRITE_PRIMARY_AND_ACTIVE = WRITE_PRIMARY | WRITE_ACTIVE
const WRITE_ACTIVE_AND_WRITING = WRITE_ACTIVE | WRITE_WRITING
const WRITE_FINISHING_STATUS = OPEN_STATUS | WRITE_FINISHING | WRITE_QUEUED_AND_ACTIVE | WRITE_DONE
const WRITE_BACKPRESSURE_STATUS = WRITE_UNDRAINED | DESTROY_STATUS | WRITE_FINISHING | WRITE_DONE
const WRITE_UPDATE_SYNC_STATUS = WRITE_UPDATING | OPEN_STATUS | WRITE_NEXT_TICK | WRITE_PRIMARY
const WRITE_DROP_DATA = WRITE_FINISHING | WRITE_DONE | DESTROY_STATUS

const asyncIterator = Symbol.asyncIterator || Symbol('asyncIterator')

class WritableState {
  constructor(
    stream,
    { highWaterMark = 16384, map = null, mapWritable, byteLength, byteLengthWritable } = {}
  ) {
    this.stream = stream
    this.queue = new FIFO()
    this.highWaterMark = highWaterMark
    this.buffered = 0
    this.error = null
    this.pipeline = null
    this.drains = null // if we add more seldomly used helpers we might them into a subobject so its a single ptr
    this.byteLength = byteLengthWritable || byteLength || defaultByteLength
    this.map = mapWritable || map
    this.afterWrite = afterWrite.bind(this)
    this.afterUpdateNextTick = updateWriteNT.bind(this)
  }

  get ending() {
    return (this.stream._duplexState & WRITE_FINISHING) !== 0
  }

  get ended() {
    return (this.stream._duplexState & WRITE_DONE) !== 0
  }

  push(data) {
    if ((this.stream._duplexState & WRITE_DROP_DATA) !== 0) return false
    if (this.map !== null) data = this.map(data)

    this.buffered += this.byteLength(data)
    this.queue.push(data)

    if (this.buffered < this.highWaterMark) {
      this.stream._duplexState |= WRITE_QUEUED
      return true
    }

    this.stream._duplexState |= WRITE_QUEUED_AND_UNDRAINED
    return false
  }

  shift() {
    const data = this.queue.shift()

    this.buffered -= this.byteLength(data)
    if (this.buffered === 0) this.stream._duplexState &= WRITE_NOT_QUEUED

    return data
  }

  end(data) {
    if (typeof data === 'function') {
      this.stream.once('finish', data)
    } else if (data !== undefined && data !== null) {
      this.push(data)
    }

    this.stream._duplexState = (this.stream._duplexState | WRITE_FINISHING) & WRITE_NON_PRIMARY
  }

  autoBatch(data, cb) {
    const buffer = []
    const stream = this.stream

    buffer.push(data)
    while ((stream._duplexState & WRITE_STATUS) === WRITE_QUEUED_AND_ACTIVE) {
      buffer.push(stream._writableState.shift())
    }

    if ((stream._duplexState & OPEN_STATUS) !== 0) return cb(null)
    stream._writev(buffer, cb)
  }

  update() {
    const stream = this.stream

    stream._duplexState |= WRITE_UPDATING

    do {
      while ((stream._duplexState & WRITE_STATUS) === WRITE_QUEUED) {
        const data = this.shift()
        stream._duplexState |= WRITE_ACTIVE_AND_WRITING
        stream._write(data, this.afterWrite)
      }

      if ((stream._duplexState & WRITE_PRIMARY_AND_ACTIVE) === 0) this.updateNonPrimary()
    } while (this.continueUpdate() === true)

    stream._duplexState &= WRITE_NOT_UPDATING
  }

  updateNonPrimary() {
    const stream = this.stream

    if ((stream._duplexState & WRITE_FINISHING_STATUS) === WRITE_FINISHING) {
      stream._duplexState = stream._duplexState | WRITE_ACTIVE
      stream._final(afterFinal.bind(this))
      return
    }

    if ((stream._duplexState & DESTROY_STATUS) === DESTROYING) {
      if ((stream._duplexState & ACTIVE_OR_TICKING) === 0) {
        stream._duplexState |= ACTIVE
        stream._destroy(afterDestroy.bind(this))
      }
      return
    }

    if ((stream._duplexState & IS_OPENING) === OPENING) {
      stream._duplexState = (stream._duplexState | ACTIVE) & NOT_OPENING
      stream._open(afterOpen.bind(this))
    }
  }

  continueUpdate() {
    if ((this.stream._duplexState & WRITE_NEXT_TICK) === 0) return false
    this.stream._duplexState &= WRITE_NOT_NEXT_TICK
    return true
  }

  updateCallback() {
    if ((this.stream._duplexState & WRITE_UPDATE_SYNC_STATUS) === WRITE_PRIMARY) {
      this.update()
    } else {
      this.updateNextTick()
    }
  }

  updateNextTick() {
    if ((this.stream._duplexState & WRITE_NEXT_TICK) !== 0) return
    this.stream._duplexState |= WRITE_NEXT_TICK
    if ((this.stream._duplexState & WRITE_UPDATING) === 0) qmt(this.afterUpdateNextTick)
  }
}

class ReadableState {
  constructor(
    stream,
    { highWaterMark = 16384, map = null, mapReadable, byteLength, byteLengthReadable } = {}
  ) {
    this.stream = stream
    this.queue = new FIFO()
    this.highWaterMark = highWaterMark === 0 ? 1 : highWaterMark
    this.buffered = 0
    this.readAhead = highWaterMark > 0
    this.error = null
    this.pipeline = null
    this.byteLength = byteLengthReadable || byteLength || defaultByteLength
    this.map = mapReadable || map
    this.pipeTo = null
    this.afterRead = afterRead.bind(this)
    this.afterUpdateNextTick = updateReadNT.bind(this)
  }

  get ending() {
    return (this.stream._duplexState & READ_ENDING) !== 0
  }

  get ended() {
    return (this.stream._duplexState & READ_DONE) !== 0
  }

  pipe(pipeTo, cb) {
    if (this.pipeTo !== null) throw StreamError.BAD_ARGUMENT('Can only pipe to one destination')
    if (typeof cb !== 'function') cb = null

    this.stream._duplexState |= READ_PIPE_DRAINED
    this.pipeTo = pipeTo
    this.pipeline = new Pipeline(this.stream, pipeTo, cb)

    if (cb) this.stream.on('error', noop) // We already error handle this so supress crashes

    if (isStreamx(pipeTo)) {
      pipeTo._writableState.pipeline = this.pipeline
      if (cb) pipeTo.on('error', noop) // We already error handle this so supress crashes
      pipeTo.on('finish', this.pipeline.finished.bind(this.pipeline)) // TODO: just call finished from pipeTo itself
    } else {
      const onerror = this.pipeline.done.bind(this.pipeline, pipeTo)
      const onclose = this.pipeline.done.bind(this.pipeline, pipeTo, null) // onclose has a weird bool arg
      pipeTo.on('error', onerror)
      pipeTo.on('close', onclose)
      pipeTo.on('finish', this.pipeline.finished.bind(this.pipeline))
    }

    pipeTo.on('drain', afterDrain.bind(this))
    this.stream.emit('piping', pipeTo)
    pipeTo.emit('pipe', this.stream)
  }

  push(data) {
    const stream = this.stream

    if (data === null) {
      this.highWaterMark = 0
      stream._duplexState = (stream._duplexState | READ_ENDING) & READ_NON_PRIMARY_AND_PUSHED
      return false
    }

    if (this.map !== null) {
      data = this.map(data)
      if (data === null) {
        stream._duplexState &= READ_PUSHED
        return this.buffered < this.highWaterMark
      }
    }

    this.buffered += this.byteLength(data)
    this.queue.push(data)

    stream._duplexState = (stream._duplexState | READ_QUEUED) & READ_PUSHED

    return this.buffered < this.highWaterMark
  }

  shift() {
    const data = this.queue.shift()

    this.buffered -= this.byteLength(data)
    if (this.buffered === 0) {
      this.stream._duplexState &= READ_NOT_QUEUED
    }

    return data
  }

  unshift(data) {
    const pending = [this.map !== null ? this.map(data) : data]
    while (this.buffered > 0) pending.push(this.shift())

    for (let i = 0; i < pending.length - 1; i++) {
      const data = pending[i]
      this.buffered += this.byteLength(data)
      this.queue.push(data)
    }

    this.push(pending[pending.length - 1])
  }

  read() {
    const stream = this.stream

    if ((stream._duplexState & READ_STATUS) === READ_QUEUED) {
      const data = this.shift()

      if (this.pipeTo !== null && this.pipeTo.write(data) === false) {
        stream._duplexState &= READ_PIPE_NOT_DRAINED
      }

      if ((stream._duplexState & READ_EMIT_DATA) !== 0) {
        stream.emit('data', data)
      }

      return data
    }

    if (this.readAhead === false) {
      stream._duplexState |= READ_READ_AHEAD
      this.updateNextTick()
    }

    return null
  }

  drain() {
    const stream = this.stream

    while (
      (stream._duplexState & READ_STATUS) === READ_QUEUED &&
      (stream._duplexState & READ_FLOWING) !== 0
    ) {
      const data = this.shift()

      if (this.pipeTo !== null && this.pipeTo.write(data) === false) {
        stream._duplexState &= READ_PIPE_NOT_DRAINED
      }

      if ((stream._duplexState & READ_EMIT_DATA) !== 0) {
        stream.emit('data', data)
      }
    }
  }

  update() {
    const stream = this.stream

    stream._duplexState |= READ_UPDATING

    do {
      this.drain()

      while (
        this.buffered < this.highWaterMark &&
        (stream._duplexState & SHOULD_NOT_READ) === READ_READ_AHEAD
      ) {
        stream._duplexState |= READ_ACTIVE_AND_NEEDS_PUSH
        stream._read(this.afterRead)
        this.drain()
      }

      if ((stream._duplexState & READ_READABLE_STATUS) === READ_EMIT_READABLE_AND_QUEUED) {
        stream._duplexState |= READ_EMITTED_READABLE
        stream.emit('readable')
      }

      if ((stream._duplexState & READ_PRIMARY_AND_ACTIVE) === 0) {
        this.updateNonPrimary()
      }
    } while (this.continueUpdate() === true)

    stream._duplexState &= READ_NOT_UPDATING
  }

  updateNonPrimary() {
    const stream = this.stream

    if ((stream._duplexState & READ_ENDING_STATUS) === READ_ENDING) {
      stream._duplexState = (stream._duplexState | READ_DONE) & READ_NOT_ENDING
      stream.emit('end')

      if ((stream._duplexState & AUTO_DESTROY) === DONE) {
        stream._duplexState |= DESTROYING
      }

      if (this.pipeTo !== null) {
        this.pipeTo.end()
      }
    }

    if ((stream._duplexState & DESTROY_STATUS) === DESTROYING) {
      if ((stream._duplexState & ACTIVE_OR_TICKING) === 0) {
        stream._duplexState |= ACTIVE
        stream._destroy(afterDestroy.bind(this))
      }
      return
    }

    if ((stream._duplexState & IS_OPENING) === OPENING) {
      stream._duplexState = (stream._duplexState | ACTIVE) & NOT_OPENING
      stream._open(afterOpen.bind(this))
    }
  }

  continueUpdate() {
    if ((this.stream._duplexState & READ_NEXT_TICK) === 0) return false
    this.stream._duplexState &= READ_NOT_NEXT_TICK
    return true
  }

  updateCallback() {
    if ((this.stream._duplexState & READ_UPDATE_SYNC_STATUS) === READ_PRIMARY) {
      this.update()
    } else {
      this.updateNextTick()
    }
  }

  updateNextTickIfOpen() {
    if ((this.stream._duplexState & READ_NEXT_TICK_OR_OPENING) !== 0) return
    this.stream._duplexState |= READ_NEXT_TICK
    if ((this.stream._duplexState & READ_UPDATING) === 0) qmt(this.afterUpdateNextTick)
  }

  updateNextTick() {
    if ((this.stream._duplexState & READ_NEXT_TICK) !== 0) return
    this.stream._duplexState |= READ_NEXT_TICK
    if ((this.stream._duplexState & READ_UPDATING) === 0) qmt(this.afterUpdateNextTick)
  }
}

class TransformState {
  constructor(stream) {
    this.data = null
    this.afterTransform = afterTransform.bind(stream)
    this.afterFinal = null
  }
}

class Pipeline {
  constructor(src, dst, cb) {
    this.from = src
    this.to = dst
    this.afterPipe = cb
    this.error = null
    this.pipeToFinished = false
  }

  finished() {
    this.pipeToFinished = true
  }

  done(stream, err) {
    if (err) this.error = err

    if (stream === this.to) {
      this.to = null

      if (this.from !== null) {
        if ((this.from._duplexState & READ_DONE) === 0 || !this.pipeToFinished) {
          this.from.destroy(this.error || StreamError.PREMATURE_CLOSE('Writable stream closed'))
        }
        return
      }
    }

    if (stream === this.from) {
      this.from = null

      if (this.to !== null) {
        if ((stream._duplexState & READ_DONE) === 0) {
          this.to.destroy(this.error || StreamError.PREMATURE_CLOSE('Readable stream closed'))
        }
        return
      }
    }

    if (this.afterPipe !== null) this.afterPipe(this.error)
    this.to = this.from = this.afterPipe = null
  }
}

function afterDrain() {
  this.stream._duplexState |= READ_PIPE_DRAINED
  this.updateCallback()
}

function afterFinal(err) {
  const stream = this.stream
  if (err) stream.destroy(err)

  if ((stream._duplexState & DESTROY_STATUS) === 0) {
    stream._duplexState |= WRITE_DONE
    stream.emit('finish')
  }

  if ((stream._duplexState & AUTO_DESTROY) === DONE) {
    stream._duplexState |= DESTROYING
  }

  stream._duplexState &= WRITE_NOT_FINISHING

  // no need to wait the extra tick here, so we short circuit that
  if ((stream._duplexState & WRITE_UPDATING) === 0) {
    this.update()
  } else {
    this.updateNextTick()
  }
}

function afterDestroy(err) {
  const stream = this.stream

  if (!err && !StreamError.isStreamDestroyed(this.error)) err = this.error
  if (err) stream.emit('error', err)

  stream._duplexState |= DESTROYED
  stream.emit('close')

  const rs = stream._readableState
  const ws = stream._writableState

  if (rs !== null && rs.pipeline !== null) {
    rs.pipeline.done(stream, err)
  }

  if (ws !== null) {
    while (ws.drains !== null && ws.drains.length > 0) {
      ws.drains.shift().resolve(false)
    }

    if (ws.pipeline !== null) {
      ws.pipeline.done(stream, err)
    }
  }
}

function afterWrite(err) {
  const stream = this.stream

  if (err) stream.destroy(err)
  stream._duplexState &= WRITE_NOT_ACTIVE

  if (this.drains !== null) tickDrains(this.drains)

  if ((stream._duplexState & WRITE_DRAIN_STATUS) === WRITE_UNDRAINED) {
    stream._duplexState &= WRITE_DRAINED

    if ((stream._duplexState & WRITE_EMIT_DRAIN) === WRITE_EMIT_DRAIN) {
      stream.emit('drain')
    }
  }

  this.updateCallback()
}

function afterRead(err) {
  if (err) this.stream.destroy(err)
  this.stream._duplexState &= READ_NOT_ACTIVE

  if (this.readAhead === false && (this.stream._duplexState & READ_RESUMED) === 0) {
    this.stream._duplexState &= READ_NO_READ_AHEAD
  }

  this.updateCallback()
}

function updateReadNT() {
  if ((this.stream._duplexState & READ_UPDATING) === 0) {
    this.stream._duplexState &= READ_NOT_NEXT_TICK
    this.update()
  }
}

function updateWriteNT() {
  if ((this.stream._duplexState & WRITE_UPDATING) === 0) {
    this.stream._duplexState &= WRITE_NOT_NEXT_TICK
    this.update()
  }
}

function tickDrains(drains) {
  for (let i = 0; i < drains.length; i++) {
    // drains.writes are monotonic, so if one is 0 its always the first one
    if (--drains[i].writes === 0) {
      drains.shift().resolve(true)
      i--
    }
  }
}

function afterOpen(err) {
  const stream = this.stream

  if (err) stream.destroy(err)

  if ((stream._duplexState & DESTROYING) === 0) {
    if ((stream._duplexState & READ_PRIMARY_STATUS) === 0) {
      stream._duplexState |= READ_PRIMARY
    }

    if ((stream._duplexState & WRITE_PRIMARY_STATUS) === 0) {
      stream._duplexState |= WRITE_PRIMARY
    }

    stream.emit('open')
  }

  stream._duplexState &= NOT_ACTIVE

  if (stream._writableState !== null) {
    stream._writableState.updateCallback()
  }

  if (stream._readableState !== null) {
    stream._readableState.updateCallback()
  }
}

function afterTransform(err, data) {
  if (data !== undefined && data !== null) this.push(data)
  this._writableState.afterWrite(err)
}

function newListener(name) {
  if (this._readableState !== null) {
    if (name === 'data') {
      this._duplexState |= READ_EMIT_DATA | READ_RESUMED_READ_AHEAD
      this._readableState.updateNextTick()
    }
    if (name === 'readable') {
      this._duplexState |= READ_EMIT_READABLE
      this._readableState.updateNextTick()
    }
  }

  if (this._writableState !== null) {
    if (name === 'drain') {
      this._duplexState |= WRITE_EMIT_DRAIN
      this._writableState.updateNextTick()
    }
  }
}

class Stream extends EventEmitter {
  constructor(opts) {
    super()

    this._duplexState = 0
    this._readableState = null
    this._writableState = null

    if (opts) {
      if (opts.open) this._open = opts.open
      if (opts.destroy) this._destroy = opts.destroy
      if (opts.predestroy) this._predestroy = opts.predestroy
      if (opts.signal) opts.signal.addEventListener('abort', abort.bind(this))
    }

    this.on('newListener', newListener)
  }

  _open(cb) {
    cb(null)
  }

  _destroy(cb) {
    cb(null)
  }

  _predestroy() {
    // does nothing
  }

  get readable() {
    return this._readableState !== null ? true : undefined
  }

  get writable() {
    return this._writableState !== null ? true : undefined
  }

  get destroyed() {
    return (this._duplexState & DESTROYED) !== 0
  }

  get destroying() {
    return (this._duplexState & DESTROY_STATUS) !== 0
  }

  destroy(err) {
    if ((this._duplexState & DESTROY_STATUS) === 0) {
      if (!err) err = StreamError.STREAM_DESTROYED()
      this._duplexState = (this._duplexState | DESTROYING) & NON_PRIMARY

      if (this._readableState !== null) {
        this._readableState.highWaterMark = 0
        this._readableState.error = err
      }

      if (this._writableState !== null) {
        this._writableState.highWaterMark = 0
        this._writableState.error = err
      }

      this._duplexState |= PREDESTROYING
      this._predestroy()
      this._duplexState &= NOT_PREDESTROYING

      if (this._readableState !== null) {
        this._readableState.updateNextTick()
      }

      if (this._writableState !== null) {
        this._writableState.updateNextTick()
      }
    }
  }
}

class Readable extends Stream {
  constructor(opts) {
    super(opts)

    this._duplexState |= OPENING | WRITE_DONE | READ_READ_AHEAD
    this._readableState = new ReadableState(this, opts)

    if (opts) {
      if (this._readableState.readAhead === false) this._duplexState &= READ_NO_READ_AHEAD
      if (opts.read) this._read = opts.read
      if (opts.eagerOpen) this._readableState.updateNextTick()
      if (opts.encoding) this.setEncoding(opts.encoding)
    }
  }

  static deferred(fn, opts) {
    const out = new PassThrough(opts)

    fn()
      .then((src) => {
        if (src === null) return out.end()
        if (out.destroying) return
        pipeline(src, out, noop)
      })
      .catch((err) => out.destroy(err))

    return out
  }

  setEncoding(encoding) {
    const dec = new TextDecoder(encoding)
    const map = this._readableState.map || echo
    this._readableState.map = mapOrSkip
    return this

    function mapOrSkip(data) {
      const next = dec.push(data)
      return next === '' && (data.byteLength !== 0 || dec.remaining > 0) ? null : map(next)
    }
  }

  _read(cb) {
    cb(null)
  }

  pipe(dest, cb) {
    this._readableState.updateNextTick()
    this._readableState.pipe(dest, cb)
    return dest
  }

  read() {
    this._readableState.updateNextTick()
    return this._readableState.read()
  }

  push(data) {
    this._readableState.updateNextTickIfOpen()
    return this._readableState.push(data)
  }

  unshift(data) {
    this._readableState.updateNextTickIfOpen()
    return this._readableState.unshift(data)
  }

  resume() {
    this._duplexState |= READ_RESUMED_READ_AHEAD
    this._readableState.updateNextTick()
    return this
  }

  pause() {
    this._duplexState &=
      this._readableState.readAhead === false ? READ_PAUSED_NO_READ_AHEAD : READ_PAUSED
    return this
  }

  static _fromAsyncIterator(ite, opts) {
    let destroy

    const rs = new Readable({
      ...opts,
      read(cb) {
        ite.next().then(push).then(cb.bind(null, null)).catch(cb)
      },
      predestroy() {
        destroy = ite.return()
      },
      destroy(cb) {
        if (!destroy) return cb(null)
        destroy.then(cb.bind(null, null)).catch(cb)
      }
    })

    return rs

    function push(data) {
      if (data.done) rs.push(null)
      else rs.push(data.value)
    }
  }

  static from(data, opts) {
    if (isReadStreamx(data)) return data
    if (data[asyncIterator]) return this._fromAsyncIterator(data[asyncIterator](), opts)
    if (!Array.isArray(data)) data = data === undefined ? [] : [data]

    let i = 0
    return new Readable({
      ...opts,
      read(cb) {
        this.push(i === data.length ? null : data[i++])
        cb(null)
      }
    })
  }

  static isBackpressured(rs) {
    return (
      (rs._duplexState & READ_BACKPRESSURE_STATUS) !== 0 ||
      rs._readableState.buffered >= rs._readableState.highWaterMark
    )
  }

  static isPaused(rs) {
    return (rs._duplexState & READ_RESUMED) === 0
  }

  [asyncIterator]() {
    const stream = this

    let error = null
    let promiseResolve = null
    let promiseReject = null

    this.on('error', (err) => {
      error = err
    })
    this.on('readable', onreadable)
    this.on('close', onclose)

    return {
      [asyncIterator]() {
        return this
      },
      next() {
        return new Promise(function (resolve, reject) {
          promiseResolve = resolve
          promiseReject = reject
          const data = stream.read()
          if (data !== null) ondata(data)
          else if ((stream._duplexState & DESTROYED) !== 0) ondata(null)
        })
      },
      return() {
        return destroy(null)
      },
      throw(err) {
        return destroy(err)
      }
    }

    function onreadable() {
      if (promiseResolve !== null) ondata(stream.read())
    }

    function onclose() {
      if (promiseResolve !== null) ondata(null)
    }

    function ondata(data) {
      if (promiseReject === null) return
      if (error) {
        promiseReject(error)
      } else if (data === null && (stream._duplexState & READ_DONE) === 0) {
        promiseReject(StreamError.STREAM_DESTROYED())
      } else {
        promiseResolve({ value: data, done: data === null })
      }
      promiseReject = promiseResolve = null
    }

    function destroy(err) {
      stream.destroy(err)
      return new Promise((resolve, reject) => {
        if (stream._duplexState & DESTROYED) return resolve({ value: undefined, done: true })
        stream.once('close', function () {
          if (err) reject(err)
          else resolve({ value: undefined, done: true })
        })
      })
    }
  }
}

class Writable extends Stream {
  constructor(opts) {
    super(opts)

    this._duplexState |= OPENING | READ_DONE
    this._writableState = new WritableState(this, opts)

    if (opts) {
      if (opts.writev) this._writev = opts.writev
      if (opts.write) this._write = opts.write
      if (opts.final) this._final = opts.final
      if (opts.eagerOpen) this._writableState.updateNextTick()
    }
  }

  cork() {
    this._duplexState |= WRITE_CORKED
  }

  uncork() {
    this._duplexState &= WRITE_NOT_CORKED
    this._writableState.updateNextTick()
  }

  _writev(batch, cb) {
    cb(null)
  }

  _write(data, cb) {
    this._writableState.autoBatch(data, cb)
  }

  _final(cb) {
    cb(null)
  }

  static isBackpressured(ws) {
    return (ws._duplexState & WRITE_BACKPRESSURE_STATUS) !== 0
  }

  static drained(ws) {
    if (ws.destroyed) return Promise.resolve(false)

    const state = ws._writableState
    const pending = isWritev(ws) ? Math.min(1, state.queue.length) : state.queue.length
    const writes = pending + (ws._duplexState & WRITE_WRITING ? 1 : 0)
    if (writes === 0) return Promise.resolve(true)

    if (state.drains === null) state.drains = []
    return new Promise((resolve) => {
      state.drains.push({ writes, resolve })
    })
  }

  write(data) {
    this._writableState.updateNextTick()
    return this._writableState.push(data)
  }

  end(data) {
    this._writableState.updateNextTick()
    this._writableState.end(data)
    return this
  }
}

class Duplex extends Readable {
  // and Writable
  constructor(opts) {
    super(opts)

    this._duplexState = OPENING | (this._duplexState & READ_READ_AHEAD)
    this._writableState = new WritableState(this, opts)

    if (opts) {
      if (opts.writev) this._writev = opts.writev
      if (opts.write) this._write = opts.write
      if (opts.final) this._final = opts.final
    }
  }

  cork() {
    this._duplexState |= WRITE_CORKED
  }

  uncork() {
    this._duplexState &= WRITE_NOT_CORKED
    this._writableState.updateNextTick()
  }

  _writev(batch, cb) {
    cb(null)
  }

  _write(data, cb) {
    this._writableState.autoBatch(data, cb)
  }

  _final(cb) {
    cb(null)
  }

  write(data) {
    this._writableState.updateNextTick()
    return this._writableState.push(data)
  }

  end(data) {
    this._writableState.updateNextTick()
    this._writableState.end(data)
    return this
  }
}

class Transform extends Duplex {
  constructor(opts) {
    super(opts)
    this._transformState = new TransformState(this)

    if (opts) {
      if (opts.transform) this._transform = opts.transform
      if (opts.flush) this._flush = opts.flush
    }
  }

  _write(data, cb) {
    if (this._readableState.buffered >= this._readableState.highWaterMark) {
      this._transformState.data = data
    } else {
      this._transform(data, this._transformState.afterTransform)
    }
  }

  _read(cb) {
    if (this._transformState.data !== null) {
      const data = this._transformState.data
      this._transformState.data = null
      cb(null)
      this._transform(data, this._transformState.afterTransform)
    } else {
      cb(null)
    }
  }

  destroy(err) {
    super.destroy(err)
    if (this._transformState.data !== null) {
      this._transformState.data = null
      this._transformState.afterTransform()
    }
  }

  _transform(data, cb) {
    cb(null, data)
  }

  _flush(cb) {
    cb(null)
  }

  _final(cb) {
    this._transformState.afterFinal = cb
    this._flush(transformAfterFlush.bind(this))
  }
}

class PassThrough extends Transform {}

function transformAfterFlush(err, data) {
  const cb = this._transformState.afterFinal
  if (err) return cb(err)

  if (data !== null && data !== undefined) this.push(data)
  this.push(null)
  cb(null)
}

function pipelinePromise(...streams) {
  return new Promise((resolve, reject) => {
    return pipeline(...streams, (err) => {
      if (err) return reject(err)
      resolve()
    })
  })
}

function pipeline(stream, ...streams) {
  const all = Array.isArray(stream) ? [...stream, ...streams] : [stream, ...streams]
  const done = all.length && typeof all[all.length - 1] === 'function' ? all.pop() : null

  if (all.length < 2) throw StreamError.BAD_ARGUMENT('Pipeline requires at least 2 streams')

  let src = all[0]
  let dest = null
  let error = null

  for (let i = 1; i < all.length; i++) {
    dest = all[i]

    if (isStreamx(src)) {
      src.pipe(dest, onerror)
    } else {
      errorHandle(src, true, i > 1, onerror)
      src.pipe(dest)
    }

    src = dest
  }

  if (done) {
    let fin = false

    const autoDestroy =
      isStreamx(dest) || !!(dest._writableState && dest._writableState.autoDestroy)

    dest.on('error', (err) => {
      if (error === null) error = err
    })

    dest.on('finish', () => {
      fin = true
      if (!autoDestroy) done(error)
    })

    if (autoDestroy) {
      dest.on('close', () => done(error || (fin ? null : StreamError.PREMATURE_CLOSE())))
    }
  }

  return dest

  function errorHandle(s, rd, wr, onerror) {
    s.on('error', onerror)
    s.on('close', onclose)

    function onclose() {
      if (rd && s._readableState && !s._readableState.ended) {
        return onerror(StreamError.PREMATURE_CLOSE())
      }
      if (wr && s._writableState && !s._writableState.ended) {
        return onerror(StreamError.PREMATURE_CLOSE())
      }
    }
  }

  function onerror(err) {
    if (!err || error) return
    error = err

    for (const s of all) {
      s.destroy(err)
    }
  }
}

function echo(s) {
  return s
}

function isStream(stream) {
  return !!stream._readableState || !!stream._writableState
}

function isStreamx(stream) {
  return typeof stream._duplexState === 'number' && isStream(stream)
}

function isEnding(stream) {
  return !!stream._readableState && stream._readableState.ending
}

function isEnded(stream) {
  return !!stream._readableState && stream._readableState.ended
}

function isFinishing(stream) {
  return !!stream._writableState && stream._writableState.ending
}

function isFinished(stream) {
  return !!stream._writableState && stream._writableState.ended
}

function getStreamError(stream, opts = {}) {
  const err =
    (stream._readableState && stream._readableState.error) ||
    (stream._writableState && stream._writableState.error)

  // avoid implicit errors by default
  return !opts.all && StreamError.isStreamDestroyed(err) ? null : err
}

function isReadStreamx(stream) {
  return isStreamx(stream) && stream.readable
}

function isDisturbed(stream) {
  return (
    (stream._duplexState & OPENING) !== OPENING ||
    (stream._duplexState & DESTROYING) === DESTROYING ||
    (stream._duplexState & ACTIVE_OR_TICKING) !== 0
  )
}

function isTypedArray(data) {
  return typeof data === 'object' && data !== null && typeof data.byteLength === 'number'
}

function defaultByteLength(data) {
  return isTypedArray(data) ? data.byteLength : 1024
}

function noop() {}

function abort() {
  this.destroy(StreamError.ABORTED())
}

function isWritev(s) {
  return s._writev !== Writable.prototype._writev && s._writev !== Duplex.prototype._writev
}

module.exports = {
  pipeline,
  pipelinePromise,
  isStream,
  isStreamx,
  isEnding,
  isEnded,
  isFinishing,
  isFinished,
  isDisturbed,
  getStreamError,
  Stream,
  Writable,
  Readable,
  Duplex,
  Transform,
  // Export PassThrough for compatibility with Node.js core's stream module
  PassThrough
}
module.exports = class StreamError extends Error {
  constructor(msg, code, fn = StreamError) {
    super(msg)

    this.code = code

    if (Error.captureStackTrace) {
      Error.captureStackTrace(this, fn)
    }
  }

  static isStreamDestroyed(err) {
    return err && err.code === 'STREAM_DESTROYED'
  }

  static isPrematureClose(err) {
    return err && err.code === 'PREMATURE_CLOSE'
  }

  static isAborted(err) {
    return err && err.code === 'ABORTED'
  }

  static isBadArgument(err) {
    return err && err.code === 'BAD_ARGUMENT'
  }

  get name() {
    return 'StreamError'
  }

  static STREAM_DESTROYED() {
    return new StreamError('Stream was destroyed', 'STREAM_DESTROYED', StreamError.STREAM_DESTROYED)
  }

  static PREMATURE_CLOSE(msg = 'Premature close') {
    return new StreamError(msg, 'PREMATURE_CLOSE', StreamError.PREMATURE_CLOSE)
  }

  static ABORTED() {
    return new StreamError('Stream aborted', 'ABORTED', StreamError.ABORTED)
  }

  static BAD_ARGUMENT(msg = 'Bad argument') {
    return new StreamError(msg, 'BAD_ARGUMENT', StreamError.BAD_ARGUMENT)
  }
}
{
  "name": "streamx",
  "version": "2.28.0",
  "description": "An iteration of the Node.js core streams with a series of improvements",
  "main": "index.js",
  "exports": {
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    },
    "./package": "./package.json",
    "./errors": {
      "types": "./lib/errors.d.ts",
      "default": "./lib/errors"
    }
  },
  "dependencies": {
    "events-universal": "^1.0.0",
    "fast-fifo": "^1.3.2",
    "text-decoder": "^1.1.0"
  },
  "devDependencies": {
    "b4a": "^1.6.6",
    "brittle": "^3.1.1",
    "end-of-stream": "^1.4.4",
    "lunte": "^1.8.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^2.0.0"
  },
  "files": [
    "index.js",
    "index.d.ts",
    "runtime.d.ts",
    "lib"
  ],
  "scripts": {
    "format": "prettier . --write",
    "test": "brittle test/all.js && brittle-bare test/all.js",
    "lint": "prettier . --check && lunte"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/mafintosh/streamx.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/mafintosh/streamx/issues"
  },
  "homepage": "https://github.com/mafintosh/streamx"
}
const { Readable } = require('streamx')

module.exports = function (s, forks = 2) {
  const streams = new Array(forks)
  const status = new Array(forks).fill(true)

  let ended = false

  for (let i = 0; i < forks; i++) {
    streams[i] = new Readable({
      read (cb) {
        const check = !status[i]
        status[i] = true
        if (check && allReadable()) s.resume()
        cb(null)
      }
    })
  }

  s.on('end', function () {
    ended = true
    for (const stream of streams) stream.push(null)
  })

  s.on('error', function (err) {
    for (const stream of streams) stream.destroy(err)
  })

  s.on('close', function () {
    if (ended) return
    for (const stream of streams) stream.destroy()
  })

  s.on('data', function (data) {
    let needsPause = false
    for (let i = 0; i < streams.length; i++) {
      if (!(status[i] = streams[i].push(data))) {
        needsPause = true
      }
    }
    if (needsPause) s.pause()
  })

  return streams

  function allReadable () {
    for (let j = 0; j < status.length; j++) {
      if (!status[j]) return false
    }
    return true
  }
}
{
  "name": "teex",
  "version": "1.0.1",
  "description": "Turn a readable stream into multiple readable streamx streams",
  "main": "index.js",
  "dependencies": {
    "streamx": "^2.12.5"
  },
  "devDependencies": {
    "standard": "^14.3.4",
    "tape": "^5.0.1"
  },
  "scripts": {
    "test": "standard && tape test.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/mafintosh/teex.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/mafintosh/teex/issues"
  },
  "homepage": "https://github.com/mafintosh/teex"
}
const PassThroughDecoder = require('./lib/pass-through-decoder')
const UTF8Decoder = require('./lib/utf8-decoder')

module.exports = class TextDecoder {
  constructor(encoding = 'utf8') {
    this.encoding = normalizeEncoding(encoding)

    switch (this.encoding) {
      case 'utf8':
        this.decoder = new UTF8Decoder()
        break
      case 'utf16le':
      case 'base64':
        throw new Error('Unsupported encoding: ' + this.encoding)
      default:
        this.decoder = new PassThroughDecoder(this.encoding)
    }
  }

  get remaining() {
    return this.decoder.remaining
  }

  push(data) {
    if (typeof data === 'string') return data
    return this.decoder.decode(data)
  }

  // For Node.js compatibility
  write(data) {
    return this.push(data)
  }

  end(data) {
    let result = ''
    if (data) result = this.push(data)
    result += this.decoder.flush()
    return result
  }
}

function normalizeEncoding(encoding) {
  encoding = encoding.toLowerCase()

  switch (encoding) {
    case 'utf8':
    case 'utf-8':
      return 'utf8'
    case 'ucs2':
    case 'ucs-2':
    case 'utf16le':
    case 'utf-16le':
      return 'utf16le'
    case 'latin1':
    case 'binary':
      return 'latin1'
    case 'base64':
    case 'ascii':
    case 'hex':
      return encoding
    default:
      throw new Error('Unknown encoding: ' + encoding)
  }
}
const b4a = require('b4a')

module.exports = class PassThroughDecoder {
  constructor(encoding) {
    this.encoding = encoding
  }

  get remaining() {
    return 0
  }

  decode(data) {
    return b4a.toString(data, this.encoding)
  }

  flush() {
    return ''
  }
}
const b4a = require('b4a')

/**
 * https://encoding.spec.whatwg.org/#utf-8-decoder
 */
module.exports = class UTF8Decoder {
  constructor() {
    this._reset()
  }

  get remaining() {
    return this.bytesSeen
  }

  decode(data) {
    if (data.byteLength === 0) return ''

    if (this.bytesNeeded === 0 && trailingIncomplete(data, 0) === 0) {
      this.bytesSeen = trailingBytesSeen(data)
      return b4a.toString(data, 'utf8')
    }

    let result = ''
    let start = 0

    if (this.bytesNeeded > 0) {
      while (start < data.byteLength) {
        const byte = data[start]

        if (byte < this.lowerBoundary || byte > this.upperBoundary) {
          result += '\ufffd'
          this._reset()
          break
        }

        this.lowerBoundary = 0x80
        this.upperBoundary = 0xbf
        this.codePoint = (this.codePoint << 6) | (byte & 0x3f)
        this.bytesSeen++
        start++

        if (this.bytesSeen === this.bytesNeeded) {
          result += String.fromCodePoint(this.codePoint)
          this._reset()
          break
        }
      }

      if (this.bytesNeeded > 0) return result
    }

    const trailing = trailingIncomplete(data, start)
    const end = data.byteLength - trailing

    if (end > start) result += b4a.toString(data, 'utf8', start, end)

    for (let i = end; i < data.byteLength; i++) {
      const byte = data[i]

      if (this.bytesNeeded === 0) {
        if (byte <= 0x7f) {
          this.bytesSeen = 0
          result += String.fromCharCode(byte)
        } else if (byte >= 0xc2 && byte <= 0xdf) {
          this.bytesNeeded = 2
          this.bytesSeen = 1
          this.codePoint = byte & 0x1f
        } else if (byte >= 0xe0 && byte <= 0xef) {
          if (byte === 0xe0) this.lowerBoundary = 0xa0
          else if (byte === 0xed) this.upperBoundary = 0x9f
          this.bytesNeeded = 3
          this.bytesSeen = 1
          this.codePoint = byte & 0xf
        } else if (byte >= 0xf0 && byte <= 0xf4) {
          if (byte === 0xf0) this.lowerBoundary = 0x90
          else if (byte === 0xf4) this.upperBoundary = 0x8f
          this.bytesNeeded = 4
          this.bytesSeen = 1
          this.codePoint = byte & 0x7
        } else {
          this.bytesSeen = 1
          result += '\ufffd'
        }

        continue
      }

      if (byte < this.lowerBoundary || byte > this.upperBoundary) {
        result += '\ufffd'
        i--
        this._reset()

        continue
      }

      this.lowerBoundary = 0x80
      this.upperBoundary = 0xbf

      this.codePoint = (this.codePoint << 6) | (byte & 0x3f)
      this.bytesSeen++

      if (this.bytesSeen === this.bytesNeeded) {
        result += String.fromCodePoint(this.codePoint)
        this._reset()
      }
    }

    return result
  }

  flush() {
    const result = this.bytesNeeded > 0 ? '\ufffd' : ''
    this._reset()
    return result
  }

  _reset() {
    this.codePoint = 0
    this.bytesNeeded = 0
    this.bytesSeen = 0
    this.lowerBoundary = 0x80
    this.upperBoundary = 0xbf
  }
}

function trailingIncomplete(data, start) {
  const len = data.byteLength
  if (len <= start) return 0

  const limit = Math.max(start, len - 4)

  let i = len - 1
  while (i > limit && (data[i] & 0xc0) === 0x80) i--

  if (i < start) return 0

  const byte = data[i]

  let needed
  if (byte <= 0x7f) return 0
  if (byte >= 0xc2 && byte <= 0xdf) needed = 2
  else if (byte >= 0xe0 && byte <= 0xef) needed = 3
  else if (byte >= 0xf0 && byte <= 0xf4) needed = 4
  else return 0

  const available = len - i
  return available < needed ? available : 0
}

function trailingBytesSeen(data) {
  const len = data.byteLength
  if (len === 0) return 0

  const last = data[len - 1]

  if (last <= 0x7f) return 0
  if ((last & 0xc0) !== 0x80) return 1

  const limit = Math.max(0, len - 4)

  let i = len - 2
  while (i >= limit && (data[i] & 0xc0) === 0x80) i--

  if (i < 0) return 1

  const first = data[i]

  let needed
  if (first >= 0xc2 && first <= 0xdf) needed = 2
  else if (first >= 0xe0 && first <= 0xef) needed = 3
  else if (first >= 0xf0 && first <= 0xf4) needed = 4
  else return 1

  if (len - i !== needed) return 1

  if (needed >= 3) {
    const second = data[i + 1]
    if (first === 0xe0 && second < 0xa0) return 1
    if (first === 0xed && second > 0x9f) return 1
    if (first === 0xf0 && second < 0x90) return 1
    if (first === 0xf4 && second > 0x8f) return 1
  }

  return 0
}
{
  "name": "text-decoder",
  "version": "1.2.7",
  "description": "Streaming text decoder that preserves multibyte Unicode characters",
  "exports": {
    "./package": "./package.json",
    ".": "./index.js"
  },
  "files": [
    "index.js",
    "lib"
  ],
  "scripts": {
    "format": "prettier --write .",
    "lint": "prettier --check . && lunte",
    "test": "npm run test:node && npm run test:bare",
    "test:node": "brittle-node test.js",
    "test:bare": "brittle-bare test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/text-decoder.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/text-decoder/issues"
  },
  "homepage": "https://github.com/holepunchto/text-decoder#readme",
  "dependencies": {
    "b4a": "^1.6.4"
  },
  "devDependencies": {
    "brittle": "^3.3.2",
    "lunte": "^1.3.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^1.0.0"
  }
}
const b4a = require('b4a')

unslab.all = all
unslab.is = is

module.exports = unslab

function unslab (buf) {
  if (buf === null || buf.buffer.byteLength === buf.byteLength) return buf
  const copy = b4a.allocUnsafeSlow(buf.byteLength)
  copy.set(buf, 0)
  return copy
}

function is (buf) {
  return buf.buffer.byteLength !== buf.byteLength
}

function all (list) {
  let size = 0
  for (let i = 0; i < list.length; i++) {
    const buf = list[i]
    size += buf === null || buf.buffer.byteLength === buf.byteLength ? 0 : buf.byteLength
  }

  const copy = b4a.allocUnsafeSlow(size)
  const result = new Array(list.length)

  let offset = 0
  for (let i = 0; i < list.length; i++) {
    let buf = list[i]

    if (buf !== null && buf.buffer.byteLength !== buf.byteLength) {
      copy.set(buf, offset)
      buf = copy.subarray(offset, offset += buf.byteLength)
    }

    result[i] = buf
  }

  return result
}
{
  "name": "unslab",
  "version": "1.3.0",
  "description": "Unslab some slab'ed buffers",
  "main": "index.js",
  "files": [
    "index.js"
  ],
  "dependencies": {
    "b4a": "^1.6.6"
  },
  "devDependencies": {
    "brittle": "^3.5.2",
    "standard": "^17.1.0"
  },
  "scripts": {
    "test": "standard && brittle test.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/holepunchto/unslab.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/unslab/issues"
  },
  "homepage": "https://github.com/holepunchto/unslab"
}
module.exports = read

var MSB = 0x80
  , REST = 0x7F

function read(buf, offset) {
  var res    = 0
    , offset = offset || 0
    , shift  = 0
    , counter = offset
    , b
    , l = buf.length

  do {
    if (counter >= l) {
      read.bytes = 0
      throw new RangeError('Could not decode varint')
    }
    b = buf[counter++]
    res += shift < 28
      ? (b & REST) << shift
      : (b & REST) * Math.pow(2, shift)
    shift += 7
  } while (b >= MSB)

  read.bytes = counter - offset

  return res
}
module.exports = encode

var MSB = 0x80
  , REST = 0x7F
  , MSBALL = ~REST
  , INT = Math.pow(2, 31)

function encode(num, out, offset) {
  out = out || []
  offset = offset || 0
  var oldOffset = offset

  while(num >= INT) {
    out[offset++] = (num & 0xFF) | MSB
    num /= 128
  }
  while(num & MSBALL) {
    out[offset++] = (num & 0xFF) | MSB
    num >>>= 7
  }
  out[offset] = num | 0
  
  encode.bytes = offset - oldOffset + 1
  
  return out
}
module.exports = {
    encode: require('./encode.js')
  , decode: require('./decode.js')
  , encodingLength: require('./length.js')
}

var N1 = Math.pow(2,  7)
var N2 = Math.pow(2, 14)
var N3 = Math.pow(2, 21)
var N4 = Math.pow(2, 28)
var N5 = Math.pow(2, 35)
var N6 = Math.pow(2, 42)
var N7 = Math.pow(2, 49)
var N8 = Math.pow(2, 56)
var N9 = Math.pow(2, 63)

module.exports = function (value) {
  return (
    value < N1 ? 1
  : value < N2 ? 2
  : value < N3 ? 3
  : value < N4 ? 4
  : value < N5 ? 5
  : value < N6 ? 6
  : value < N7 ? 7
  : value < N8 ? 8
  : value < N9 ? 9
  :              10
  )
}
{
  "name": "varint",
  "version": "5.0.0",
  "description": "protobuf-style varint bytes - use msb to create integer values of varying sizes",
  "main": "index.js",
  "scripts": {
    "test": "node test.js"
  },
  "repository": {
    "type": "git",
    "url": "git://github.com/chrisdickinson/varint.git"
  },
  "keywords": [
    "varint",
    "protobuf",
    "encode",
    "decode"
  ],
  "author": "Chris Dickinson <chris@neversaw.us>",
  "license": "MIT",
  "devDependencies": {
    "tape": "~2.12.3"
  }
}
const { runtime, platform, arch, version } =
  typeof Bare !== 'undefined'
    ? {
        runtime: 'bare',
        platform: global.Bare.platform,
        arch: global.Bare.arch,
        version: global.Bare.version
      }
    : typeof process !== 'undefined'
      ? {
          runtime: 'node',
          platform: global.process.platform,
          arch: global.process.arch,
          version: global.process.version
        }
      : typeof Window !== 'undefined'
        ? { runtime: 'browser', platform: 'unknown', arch: 'unknown', version: 'unknown' }
        : { runtime: 'unknown', platform: 'unknown', arch: 'unknown', version: 'unknown' }

exports.runtime = runtime
exports.version = version
exports.platform = platform
exports.arch = arch
exports.isBare = runtime === 'bare'
exports.isBareKit = exports.isBare && typeof BareKit !== 'undefined'
exports.isPear = typeof Pear !== 'undefined'
exports.isNode = runtime === 'node'
exports.isBrowser = runtime === 'browser'
exports.isWindows = platform === 'win32'
exports.isLinux = platform === 'linux'
exports.isMac = platform === 'darwin'
exports.isIOS = platform === 'ios' || platform === 'ios-simulator'
exports.isAndroid = platform === 'android'
exports.isElectron = typeof process !== 'undefined' && !!global.process.versions?.electron
exports.isElectronRenderer = exports.isElectron && global.process.type === 'renderer'
exports.isElectronWorker = exports.isElectron && global.process.type === 'worker'
{
  "name": "which-runtime",
  "version": "1.4.0",
  "description": "Detect if you are in Bare or Node and which os etc",
  "main": "index.js",
  "files": [
    "index.js"
  ],
  "scripts": {
    "format": "prettier --write .",
    "lint": "prettier --check . && lunte .",
    "test": "npm run test:node && npm run test:bare",
    "test:node": "brittle-node test.js",
    "test:bare": "brittle-bare test.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/holepunchto/which-runtime.git"
  },
  "author": "Holepunch Inc.",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/which-runtime/issues"
  },
  "homepage": "https://github.com/holepunchto/which-runtime",
  "devDependencies": {
    "brittle": "^3.0.0",
    "lunte": "^1.4.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^2.0.0"
  }
}
const b4a = require('b4a')

const ALPHABET = 'ybndrfg8ejkmcpqxot1uwisza345h769'
const MIN = 0x31 // 1
const MAX = 0x7a // z
const REVERSE = new Int8Array(1 + MAX - MIN)

REVERSE.fill(-1)

for (let i = 0; i < ALPHABET.length; i++) {
  const v = ALPHABET.charCodeAt(i) - MIN
  REVERSE[v] = i
}

exports.encode = encode
exports.decode = decode
exports.ALPHABET = ALPHABET

function decode (s, out) {
  let pb = 0
  let ps = 0

  const r = s.length & 7
  const q = (s.length - r) / 8

  if (!out) out = b4a.allocUnsafe(Math.ceil(s.length * 5 / 8))

  // 0 5 2 7 4 1 6 3 (+5 mod 8)
  for (let i = 0; i < q; i++) {
    const a = quintet(s, ps++)
    const b = quintet(s, ps++)
    const c = quintet(s, ps++)
    const d = quintet(s, ps++)
    const e = quintet(s, ps++)
    const f = quintet(s, ps++)
    const g = quintet(s, ps++)
    const h = quintet(s, ps++)

    out[pb++] = (a << 3) | (b >>> 2)
    out[pb++] = ((b & 0b11) << 6) | (c << 1) | (d >>> 4)
    out[pb++] = ((d & 0b1111) << 4) | (e >>> 1)
    out[pb++] = ((e & 0b1) << 7) | (f << 2) | (g >>> 3)
    out[pb++] = ((g & 0b111) << 5) | h
  }

  if (r === 0) return out.subarray(0, pb)

  const a = quintet(s, ps++)
  const b = quintet(s, ps++)

  out[pb++] = (a << 3) | (b >>> 2)

  if (r <= 2) return out.subarray(0, pb)

  const c = quintet(s, ps++)
  const d = quintet(s, ps++)

  out[pb++] = ((b & 0b11) << 6) | (c << 1) | (d >>> 4)

  if (r <= 4) return out.subarray(0, pb)

  const e = quintet(s, ps++)

  out[pb++] = ((d & 0b1111) << 4) | (e >>> 1)

  if (r <= 5) return out.subarray(0, pb)

  const f = quintet(s, ps++)
  const g = quintet(s, ps++)

  out[pb++] = ((e & 0b1) << 7) | (f << 2) | (g >>> 3)

  if (r <= 7) return out.subarray(0, pb)

  const h = quintet(s, ps++)

  out[pb++] = ((g & 0b111) << 5) | h

  return out.subarray(0, pb)
}

function encode (buf) {
  if (typeof buf === 'string') buf = b4a.from(buf)

  const max = buf.byteLength * 8

  let s = ''

  for (let p = 0; p < max; p += 5) {
    const i = p >>> 3
    const j = p & 7

    if (j <= 3) {
      s += ALPHABET[(buf[i] >>> (3 - j)) & 0b11111]
      continue
    }

    const of = j - 3
    const h = (buf[i] << of) & 0b11111
    const l = (i >= buf.byteLength ? 0 : buf[i + 1]) >>> (8 - of)

    s += ALPHABET[h | l]
  }

  return s
}

function quintet (s, i) {
  if (i > s.length) {
    return 0
  }

  const v = s.charCodeAt(i)

  if (v < MIN || v > MAX) {
    throw Error('Invalid character in base32 input: "' + s[i] + '" at position ' + i)
  }

  const bits = REVERSE[v - MIN]

  if (bits === -1) {
    throw Error('Invalid character in base32 input: "' + s[i] + '" at position ' + i)
  }

  return bits
}
{
  "name": "z32",
  "version": "1.1.0",
  "description": "Encode & decode z-base32",
  "main": "index.js",
  "dependencies": {
    "b4a": "^1.5.3"
  },
  "devDependencies": {
    "base-x": "^4.0.0",
    "base32": "0.0.7",
    "brittle": "^3.1.3",
    "nanobench": "^3.0.0",
    "rfc4648": "^1.5.2",
    "standard": "^17.0.0"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/mafintosh/z32.git"
  },
  "scripts": {
    "test": "standard && brittle test.js",
    "bench": "node benchmark.js"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/mafintosh/z32/issues"
  },
  "homepage": "https://github.com/mafintosh/z32"
}
{
  "name": "hyper-overlay",
  "version": "0.3.13",
  "description": "Content-addressed P2P file distribution over Protomux — FastCDC chunking, multi-source fetch, resumable transfers, zero blob duplication. Files live on disk; the overlay is a thin index and transfer protocol.",
  "type": "module",
  "main": "index.js",
  "imports": {
    "#fs": {
      "bare": "bare-fs",
      "default": "fs"
    },
    "#path": {
      "bare": "bare-path",
      "default": "path"
    },
    "#os": {
      "bare": "bare-os",
      "default": "os"
    },
    "#zlib": {
      "bare": "bare-zlib",
      "default": "zlib"
    }
  },
  "exports": {
    ".": "./index.js",
    "./overlay": "./lib/overlay.js",
    "./protocol": "./lib/protocol.js",
    "./messages": "./lib/messages.js",
    "./chunker": "./lib/chunker.js",
    "./chunk-scheduler": "./lib/chunk-scheduler.js",
    "./transfer": "./lib/transfer.js",
    "./file-index": "./lib/file-index.js",
    "./sync-engine": "./lib/sync-engine.js",
    "./tree": "./lib/tree.js",
    "./pointer": "./lib/pointer.js",
    "./ignore": "./lib/ignore.js",
    "./manifest": "./lib/manifest.js",
    "./package.json": "./package.json"
  },
  "scripts": {
    "test": "brittle-bare test/*.test.js test/integration/*.test.js",
    "test:node": "brittle-node test/*.test.js"
  },
  "engines": {
    "bare": ">=1.16.0"
  },
  "license": "AGPL-3.0",
  "keywords": [
    "p2p",
    "hypercore",
    "hyperbee",
    "protomux",
    "overlay",
    "content-addressed",
    "deduplication",
    "file-transfer",
    "fastcdc",
    "chunking",
    "multi-source",
    "resumable",
    "bare"
  ],
  "dependencies": {
    "b4a": "^1.0.0",
    "bare-fs": "^4.7.2",
    "bare-os": "^3.8.7",
    "bare-path": "^3.0.0",
    "bare-zlib": "^1.3.3",
    "compact-encoding": "^3.2.0",
    "corestore": "^7.0.0",
    "hyperbee": "^2.0.0",
    "hypercore-crypto": "^3.0.0",
    "protomux": "^3.0.0",
    "ready-resource": "^1.0.0",
    "sodium-universal": "^5.0.1",
    "streamx": "^2.0.0"
  },
  "devDependencies": {
    "brittle": "^3.0.0",
    "hyperswarm": "^4.0.0",
    "@hyperswarm/testnet": "^3.1.4"
  }
}
/**
 * hyper-svc — reusable per-app daemon library
 *
 * RPC transports: UDS, HTTP, and Hyperswarm (Protomux channel).
 * Replication: Hyperswarm topic join + corestore/autobase/mux replicate.
 */

export { Service } from './lib/service.js'
export { Client } from './lib/client.js'
export { encodeFrame, FrameDecoder } from './lib/frame.js'
export { attachRpcServer, attachRpcClient } from './lib/transports/swarm-rpc.js'
/**
 * Client — RPC client for hyper-svc daemons.
 *
 * Local-only. Connects via UDS (preferred) or HTTP. Both transports speak the
 * same JSON-RPC-ish protocol; the wire format differs (length-prefixed frames
 * for UDS, POST /rpc/<method> for HTTP) but the call signature is identical.
 *
 * Discovery: try UDS first (if a path is provided and the socket is reachable),
 * fall back to HTTP. Explicit transport selection is also supported.
 *
 * Usage:
 *
 *   const client = await Client.discover({
 *     uds: '/tmp/pear-git.sock',
 *     http: 'http://127.0.0.1:4040'
 *   })
 *
 *   const orgs = await client.call('list-orgs', { limit: 10 })
 *
 *   await client.close()
 */

import http from '#node:http'
import { URL } from '#node:url'
import b4a from 'b4a'
import { stat } from '#node:fs/promises'
import { udsBackend } from './uds-backend.js'
import Hyperswarm from 'hyperswarm'
import Protomux from 'protomux'
import crypto from 'hypercore-crypto'
import { encodeFrame, FrameDecoder } from './frame.js'
import { attachRpcClient } from './transports/swarm-rpc.js'

export class Client {
  /**
   * Discover and connect to a running hyper-svc daemon.
   *
   * @param {object} opts
   * @param {string} [opts.uds] - UDS socket path. Tried first.
   * @param {string} [opts.http] - HTTP base URL (e.g. 'http://127.0.0.1:4040'). Fallback.
   * @param {'uds'|'http'} [opts.prefer] - explicit override
   * @returns {Promise<Client>}
   */
  static async discover (opts = {}) {
    const prefer = opts.prefer

    if (prefer === 'uds') {
      if (!opts.uds) throw new Error('discover({ prefer: "uds" }) requires opts.uds')
      return Client.connectUds(opts.uds)
    }
    if (prefer === 'http') {
      if (!opts.http) throw new Error('discover({ prefer: "http" }) requires opts.http')
      return Client.connectHttp(opts.http)
    }

    // Auto: UDS first if reachable, then HTTP
    if (opts.uds) {
      const reachable = await udsReachable(opts.uds)
      if (reachable) return Client.connectUds(opts.uds)
    }
    if (opts.http) {
      return Client.connectHttp(opts.http)
    }

    const tried = []
    if (opts.uds) tried.push(`uds:${opts.uds}`)
    if (opts.http) tried.push(`http:${opts.http}`)
    throw new Error(`no daemon reachable (tried: ${tried.join(', ') || 'nothing — pass uds and/or http'})`)
  }

  static async connectUds (path) {
    const { backend, name } = await udsBackend(path)
    const sock = backend.createConnection(name)
    await new Promise((resolve, reject) => {
      sock.once('connect', () => {
        sock.removeListener('error', reject)
        resolve()
      })
      sock.once('error', reject)
    })
    return new Client({ kind: 'uds', sock })
  }

  static connectHttp (base) {
    return new Client({ kind: 'http', base: base.replace(/\/+$/, '') })
  }

  /**
   * Connect to a remote daemon via Hyperswarm. Joins the topic derived from
   * `serverKey`, waits for a connection, attaches a Protomux RPC channel, and
   * (when `opts.verify` is given) confirms the peer that answered is the one
   * that was asked for.
   *
   * ⚠ `serverKey` IS NOT AN IDENTITY — IT IS ONLY A RENDEZVOUS ADDRESS.
   *
   * The topic is `hash(serverKey + salt)`, and SwarmTransport.listen() creates
   * its Hyperswarm with NO keyPair, so a server's Noise key is random per
   * process and never equals the `serverKey` clients use to find it. Anything
   * joined to that topic as a server can therefore answer, and there is no
   * cryptographic binding to check `conn.remotePublicKey` against.
   *
   * That is not hypothetical. In a pear-git-v3 fleet, a mirror joins its
   * ORIGIN's topic (the topic travels in the deployment bundle), so it is a
   * server on the exact topic clients use to reach the origin. Measured
   * 2026-08-08: `connectSwarm({serverKey: <tabertron>})` issued ON stortron was
   * answered by STORTRON ITSELF 6 times out of 6. It asked itself for the
   * origin's release index, got its own, and every artifact fetch then failed
   * with "no holder" — a wrong ANSWER, not a transport error, so nothing looked
   * broken. resync-from-origin on the same path hung twice for 300 s.
   *
   * `opts.verify` closes that hole at the application layer, where identity
   * actually lives. It receives a connected Client and returns truthy to accept.
   * Rejected peers are destroyed and the wait continues until `timeout`, so a
   * caller reaches the intended daemon or fails loudly, never silently talks to
   * the wrong one. It is layered as a callback rather than a built-in `status`
   * check because hyper-svc is generic — which handler proves identity is the
   * consumer's business, and this stays compatible with servers of any version.
   *
   * @param {object} opts
   * @param {string|Buffer} opts.serverKey - rendezvous key; derives the topic
   * @param {string} [opts.topicSalt='pear-git-v3'] - salt for topic derivation
   * @param {object} [opts.swarm] - Hyperswarm constructor options (e.g. testnet bootstrap)
   * @param {number} [opts.timeout=30000] - total ms to find an ACCEPTED peer
   * @param {(client: Client) => Promise<boolean>} [opts.verify] - identity gate
   * @returns {Promise<Client>}
   */
  static async connectSwarm (opts = {}) {
    if (!opts.serverKey) throw new Error('connectSwarm requires opts.serverKey')
    const serverKey = typeof opts.serverKey === 'string'
      ? b4a.from(opts.serverKey, 'hex')
      : opts.serverKey
    const salt = opts.topicSalt || 'pear-git-v3'
    const topic = crypto.hash(b4a.concat([serverKey, b4a.from(salt)]))
    const timeout = opts.timeout || 30_000
    const verify = typeof opts.verify === 'function' ? opts.verify : null

    const swarm = new Hyperswarm(opts.swarm || {})
    const discovery = swarm.join(topic, { server: false, client: true })

    let rejected = 0
    let client = null
    try {
      client = await new Promise((resolve, reject) => {
        let settled = false
        // NOTE: onConnection stays attached for the swarm's whole life — it is
        // deliberately NOT removed here. Connections can still arrive after a
        // peer is accepted, and a NoiseSecretStream with no 'error' listener
        // throws on reset, so detaching left later arrivals unguarded and
        // crashed the process at teardown with ECONNRESET. The listener dies
        // with the swarm when the returned Client is closed.
        const finish = (fn, arg) => {
          if (settled) return
          settled = true
          clearTimeout(timer)
          fn(arg)
        }
        const timer = setTimeout(() => {
          finish(reject, new Error('swarm-rpc: connection timeout' +
            (rejected ? ' (' + rejected + ' peer(s) answered but failed verification — ' +
              'the requested serverKey is a topic, not an identity, so other peers on ' +
              'that topic can answer)' : '')))
        }, timeout)

        const onConnection = (c) => {
          // Routine, and load-bearing: a NoiseSecretStream with no 'error'
          // listener throws on ECONNRESET, and this path DESTROYS connections
          // by design (every peer that fails verification, plus any that arrive
          // after one is accepted). Without this the first rejected peer takes
          // the whole process down. SwarmTransport._onConnection takes the same
          // posture server-side.
          c.on('error', () => {})
          if (settled) { try { c.destroy() } catch {} ; return }
          const candidate = new Client({
            kind: 'swarm',
            swarm,
            conn: c,
            rpc: attachRpcClient(Protomux.from(c))
          })
          if (!verify) return finish(resolve, candidate)

          // Verified off the critical path: keep listening while this runs, so a
          // slow or lying peer cannot stall a good one behind it.
          Promise.resolve()
            .then(() => verify(candidate))
            .then((ok) => {
              if (ok) return finish(resolve, candidate)
              rejected++
              try { c.destroy() } catch {}
            })
            .catch(() => {
              rejected++
              try { c.destroy() } catch {}
            })
        }

        swarm.on('connection', onConnection)
        discovery.flushed().catch((err) => finish(reject, err))
      })
    } catch (err) {
      // Destroy the swarm on EVERY failure path. It used to be left running: a
      // Hyperswarm holds the event loop open, so each failed connect leaked one
      // and the process never exited. That is what hung pear-git-v3's
      // basic.test.js when a mirror's origin was unreachable — 123/123 tests
      // passed, then the run never terminated.
      try { await swarm.destroy() } catch {}
      throw err
    }

    return client
  }

  constructor (opts) {
    this._kind = opts.kind
    this._sock = opts.sock || null
    this._base = opts.base || null
    this._swarm = opts.swarm || null
    this._conn = opts.conn || null
    this._rpc = opts.rpc || null
    this._closed = false

    // UDS pending request map (id → {resolve, reject})
    this._pending = new Map()
    this._nextId = 1

    if (this._kind === 'uds') {
      this._decoder = new FrameDecoder((frame) => this._onFrame(frame))
      this._sock.on('data', (chunk) => {
        try {
          this._decoder.push(chunk)
        } catch (err) {
          this._failAll(err)
          this._down()
        }
      })
      // EVENT-DRIVEN disconnect detection (fleet reconciliation contract). When the peer daemon
      // restarts (the common case: an OTA/`pgh app restart` of hpp/pgv3/etc.), the cached client's
      // socket dies. Previously 'close' only `_failAll`'d the pending map but NEVER set `_closed`,
      // and there was no 'end' handler at all — so `client.closed` stayed FALSE and long-lived
      // consumers (favela-web Writer, pgh sidecar lookups) kept handing back a DEAD client, whose
      // calls hang or reject "connection closed" forever with nothing to trigger a reconnect. Mark
      // the client CLOSED on close/end/error so `.closed` is truthy → consumers reconcile (reconnect
      // a fresh Client) on their next call. 'end' is load-bearing under bare-net (allowHalfOpen: on
      // peer-end it fires 'end' but not 'close'), so we destroy to force teardown — same posture as
      // the server-side half-open fix (hyper-svc 0.1.3).
      this._sock.on('close', () => this._down())
      this._sock.on('end', () => this._down())
      this._sock.on('error', () => this._down())
    }
  }

  get kind () { return this._kind }
  get closed () { return this._closed }

  /**
   * Make an RPC call. Throws on transport errors or remote handler errors.
   * @param {string} method
   * @param {any} [params]
   */
  async call (method, params = {}) {
    if (this._closed) throw new Error('client closed')

    if (this._kind === 'uds') return this._callUds(method, params)
    if (this._kind === 'http') return this._callHttp(method, params)
    if (this._kind === 'swarm') return this._rpc.call(method, params)
    throw new Error('unknown client kind: ' + this._kind)
  }

  _callUds (method, params) {
    const id = this._nextId++
    return new Promise((resolve, reject) => {
      this._pending.set(id, { resolve, reject })
      try {
        this._sock.write(encodeFrame({ id, method, params }))
      } catch (err) {
        this._pending.delete(id)
        reject(err)
      }
    })
  }

  _onFrame (frame) {
    if (frame == null || (typeof frame.id !== 'number' && typeof frame.id !== 'string')) {
      // Malformed response, can't match it to a caller
      return
    }
    const pending = this._pending.get(frame.id)
    if (!pending) return
    this._pending.delete(frame.id)

    if (frame.error) {
      pending.reject(new Error(frame.error))
    } else {
      pending.resolve(frame.result)
    }
  }

  _failAll (err) {
    for (const [, pending] of this._pending) pending.reject(err)
    this._pending.clear()
  }

  // The socket died (peer restarted / errored / half-open end). Mark the client CLOSED so any
  // consumer's `.closed` check reconnects, fail every in-flight call, and force teardown. Idempotent.
  _down () {
    if (this._closed) return
    this._closed = true
    this._failAll(new Error('connection closed'))
    if (this._sock) { try { this._sock.destroy() } catch {} }
  }

  async _callHttp (method, params) {
    const url = new URL(`${this._base}/rpc/${encodeURIComponent(method)}`)
    return new Promise((resolve, reject) => {
      const body = JSON.stringify(params)
      const req = http.request({
        method: 'POST',
        hostname: url.hostname,
        port: url.port || 80,
        path: url.pathname + url.search,
        headers: {
          'Content-Type': 'application/json',
          'Content-Length': b4a.byteLength(body)
        }
      }, (res) => {
        const parts = []
        res.on('data', (c) => parts.push(c))
        res.on('end', () => {
          const text = b4a.concat(parts).toString('utf8')
          let parsed
          try {
            parsed = JSON.parse(text)
          } catch {
            return reject(new Error(`bad JSON response (status ${res.statusCode}): ${text.slice(0, 200)}`))
          }
          if (res.statusCode >= 200 && res.statusCode < 300) {
            return resolve(parsed.result)
          }
          return reject(new Error(parsed.error || `HTTP ${res.statusCode}`))
        })
        res.on('error', reject)
      })
      req.on('error', reject)
      req.write(body)
      req.end()
    })
  }

  async close () {
    if (this._closed) return
    this._closed = true
    if (this._sock) {
      this._failAll(new Error('client closed'))
      try { this._sock.destroy() } catch {}
      this._sock = null
    }
    if (this._rpc) {
      try { this._rpc.close() } catch {}
      this._rpc = null
    }
    if (this._conn) {
      try { this._conn.destroy() } catch {}
      this._conn = null
    }
    if (this._swarm) {
      try { await this._swarm.destroy() } catch {}
      this._swarm = null
    }
  }
}

async function udsReachable (path) {
  const { backend, name, abstract } = await udsBackend(path)
  // Pathname sockets: verify the file exists and is actually a socket first.
  // Abstract sockets have no filesystem entry — skip straight to the connect probe.
  if (!abstract) {
    try {
      const s = await stat(path)
      if (!s.isSocket()) return false
    } catch {
      return false
    }
  }
  // Try a real connect — covers stale sockets where the daemon crashed
  return new Promise((resolve) => {
    const sock = backend.createConnection(name)
    sock.once('connect', () => {
      sock.destroy()
      resolve(true)
    })
    sock.once('error', () => {
      sock.destroy()
      resolve(false)
    })
  })
}
/**
 * Length-prefixed JSON framing.
 *
 * Wire format: [4 bytes BE length][JSON payload].
 *
 * One frame = one JSON object. Multiple frames can be pipelined on a single
 * stream — FrameDecoder handles incremental reads where a frame may arrive
 * across multiple data chunks.
 */

import b4a from 'b4a'

const MAX_FRAME = 16 * 1024 * 1024 // 16 MiB hard cap to avoid runaway buffers

export function encodeFrame (obj) {
  const json = JSON.stringify(obj)
  const payload = b4a.from(json, 'utf8')
  if (payload.length > MAX_FRAME) {
    throw new Error(`Frame too large: ${payload.length} > ${MAX_FRAME}`)
  }
  const frame = b4a.alloc(4 + payload.length)
  frame.writeUInt32BE(payload.length, 0)
  payload.copy(frame, 4)
  return frame
}

export class FrameDecoder {
  /**
   * @param {(obj: any) => void} onFrame - called once per complete frame
   */
  constructor (onFrame) {
    if (typeof onFrame !== 'function') throw new Error('onFrame is required')
    this._onFrame = onFrame
    this._buf = b4a.alloc(0)
  }

  /**
   * Feed a chunk into the decoder. Calls onFrame for each complete frame.
   * Throws if a frame exceeds MAX_FRAME or contains invalid JSON.
   */
  push (chunk) {
    this._buf = b4a.concat([this._buf, chunk])

    while (this._buf.length >= 4) {
      const len = this._buf.readUInt32BE(0)
      if (len > MAX_FRAME) {
        throw new Error(`Frame too large: ${len} > ${MAX_FRAME}`)
      }
      if (this._buf.length < 4 + len) break // wait for more bytes

      const payload = this._buf.subarray(4, 4 + len)
      this._buf = this._buf.subarray(4 + len)

      const json = b4a.toString(payload, 'utf8')
      const obj = JSON.parse(json) // throws on bad JSON
      this._onFrame(obj)
    }
  }

  /** Number of bytes currently buffered (waiting for the rest of a frame). */
  get pending () { return this._buf.length }
}
/**
 * Service — reusable per-app daemon library.
 *
 * One instance per app process. Owns:
 *   - Generic RPC handler registry (svc.handle)
 *   - Module mounts (svc.mount) with optional swarm replication
 *   - UDS, HTTP, and Hyperswarm transport instances (whichever the consumer enables)
 *
 * The consumer's corestore is passed in (or one is implied via swarm.corestore).
 * The Service does NOT create or own storage — that's the app's job.
 *
 * Local-only RPC. Peers exchange data via swarm replication, never commands.
 *
 * Usage:
 *
 *   const svc = new Service({
 *     name: 'pear-git',
 *     corestore: store,
 *     uds: '/tmp/pear-git.sock',
 *     http: { port: 4040 },
 *     swarm: { topic: '...32 bytes hex...' }
 *   })
 *
 *   svc.mount('orgs', new HyperOrgs(store, { namespace: 'orgs' }))
 *
 *   svc.handle('list-orgs', async (ctx, params) => {
 *     return svc.modules.orgs.list()
 *   })
 *
 *   await svc.listen()
 */

import process from '#node:process'
import ReadyResource from 'ready-resource'
import { UdsServer } from './transports/uds.js'
import { HttpServer } from './transports/http.js'
import { SwarmTransport } from './transports/swarm.js'

class NoSuchMethodError extends Error {
  constructor (method) {
    super(`unknown method: ${method}`)
    this.code = 'ENOMETHOD'
  }
}

class AnonymousDeniedError extends Error {
  constructor (method) {
    super(`anonymous access denied: ${method}`)
    this.code = 'EDENIED'
    this.status = 403
  }
}

/**
 * Default "is this caller anonymous?" predicate.
 *
 * Deliberately conservative — anything that is not clearly an authenticated
 * caller counts as anonymous. Consumers whose ctx carries richer identity
 * (pear-git-v3 has four distinct shapes) should pass their own via
 * opts.isAnonymous rather than widen this one.
 */
function defaultIsAnonymous (ctx) {
  if (!ctx) return true
  if (ctx.transport === 'uds') return false // local operator holds the socket
  return !ctx.identity
}

export class Service extends ReadyResource {
  /**
   * @param {object} opts
   * @param {string} opts.name - service name (used for default UDS path, logging)
   * @param {object} [opts.corestore] - required if swarm transport is enabled
   * @param {string} [opts.uds] - UDS socket path. Pass null/undefined to disable.
   * @param {object} [opts.http] - HTTP transport options { port, host }. Disable by omitting.
   * @param {object} [opts.swarm] - Swarm options { topic, swarm? }. Disable by omitting.
   */
  constructor (opts = {}) {
    super()
    if (!opts.name) throw new Error('Service requires opts.name')

    this._name = opts.name
    this._corestore = opts.corestore || null
    this._handlers = new Map()
    this._mounts = new Map()

    // Anonymous-dispatch policy (pear-git org #12).
    //
    // Every registered handler used to be callable by anyone, over HTTP and
    // over swarm-rpc, with no allowlist and no auth gate — so each new
    // svc.handle() was silently world-callable, and a daemon behind a public
    // reverse proxy exposed its entire RPC surface to the internet.
    //
    // Default stays 'allow' so the existing consumers (pear-git-v3, hypernote,
    // hyper-social-web, pgh-sidecar) are untouched until they opt in. Under
    // 'deny', a handler must declare `{ anonymous: true }` at registration to
    // be reachable by an unauthenticated caller.
    //
    // Opt-in at REGISTRATION rather than via a separate allowlist table: with
    // ~100 handlers a table drifts out of step with the handler set, whereas a
    // flag cannot. And the drift direction is safe — a handler nobody marked
    // is DENIED, which surfaces as a visible 403 rather than a silent hole.
    this._anonymousPolicy = opts.anonymousPolicy === 'deny' ? 'deny' : 'allow'
    this._isAnonymous = typeof opts.isAnonymous === 'function'
      ? opts.isAnonymous
      : defaultIsAnonymous
    // Called with ({ method, ctx }) whenever a dispatch is refused, so the
    // consumer can record denials alongside its own audit trail.
    this._onDeny = typeof opts.onDeny === 'function' ? opts.onDeny : null
    this._anonymousAllowed = new Set()

    this._udsOpt = opts.uds || null
    this._httpOpt = opts.http || null
    this._swarmOpt = opts.swarm || null

    this._uds = null
    this._http = null
    this._swarm = null

    if (this._swarmOpt && !this._corestore) {
      throw new Error('Service: swarm transport requires opts.corestore')
    }

    // Instantiate transports eagerly so apps can register HTTP routes,
    // grab the UDS path, etc., BEFORE calling listen().
    const dispatch = (method, params, ctx) => this.dispatch(method, params, ctx)

    if (this._udsOpt) {
      this._uds = new UdsServer({ path: this._udsOpt, dispatch })
    }
    if (this._httpOpt) {
      this._http = new HttpServer({
        port: this._httpOpt.port,
        host: this._httpOpt.host,
        prefix: this._httpOpt.prefix,
        ctxFactory: this._httpOpt.ctxFactory,
        dispatch
      })
    }
    if (this._swarmOpt) {
      this._swarm = new SwarmTransport({
        topic: this._swarmOpt.topic,
        corestore: this._corestore,
        mounts: () => this._listMounts(),
        dispatch: this._swarmOpt.rpc !== false ? dispatch : null,
        swarm: this._swarmOpt.swarm,
        server: this._swarmOpt.server,
        client: this._swarmOpt.client
      })
    }
  }

  // ── Properties ──────────────────────────────────────────────

  get name () { return this._name }
  get corestore () { return this._corestore }

  /** Read-only handle to the UDS transport (or null if disabled). */
  get uds () { return this._uds }

  /** Read-only handle to the HTTP transport (or null if disabled). */
  get http () { return this._http }

  /** Read-only handle to the swarm transport (or null if disabled). */
  get swarm () { return this._swarm }

  /**
   * Mounted modules, accessible by name. Use `svc.modules.orgs` after
   * `svc.mount('orgs', orgsInstance)`.
   */
  get modules () {
    // Lazily build a property-style accessor object on top of the map
    if (!this._modulesProxy) {
      const map = this._mounts
      this._modulesProxy = new Proxy({}, {
        get (_target, prop) {
          const m = map.get(prop)
          return m ? m.instance : undefined
        },
        has (_target, prop) {
          return map.has(prop)
        },
        ownKeys () {
          return Array.from(map.keys())
        },
        getOwnPropertyDescriptor (_target, prop) {
          if (map.has(prop)) {
            return { enumerable: true, configurable: true, value: map.get(prop).instance }
          }
        }
      })
    }
    return this._modulesProxy
  }

  /** Map iteration: list mount entries as { name, instance }. */
  _listMounts () {
    const out = []
    for (const [name, m] of this._mounts) out.push({ name, instance: m.instance })
    return out
  }

  // ── Registration ────────────────────────────────────────────

  /**
   * Register an RPC handler. The handler is invoked from any RPC transport
   * with `(ctx, params)` and should return a JSON-serializable result.
   *
   * @param {string} name - method name (e.g. "list-orgs")
   * @param {(ctx: object, params: any) => Promise<any>|any} fn
   */
  handle (name, fn, opts = {}) {
    if (typeof name !== 'string' || !name) throw new Error('handle requires a name')
    if (typeof fn !== 'function') throw new Error('handle requires a function')
    if (this._handlers.has(name)) throw new Error(`handler "${name}" already registered`)
    this._handlers.set(name, fn)
    // `anonymous: true` opts this method IN to unauthenticated dispatch when
    // the service runs anonymousPolicy: 'deny'. Absent = denied, which is the
    // safe default for a handler nobody has classified yet.
    if (opts && opts.anonymous === true) this._anonymousAllowed.add(name)
  }

  /** Is this method callable by an unauthenticated caller? */
  allowsAnonymous (name) { return this._anonymousAllowed.has(name) }

  /** Methods explicitly opted in to anonymous dispatch, sorted. */
  listAnonymous () { return Array.from(this._anonymousAllowed).sort() }

  /** Whether a handler is registered. */
  hasHandler (name) { return this._handlers.has(name) }

  /** List registered handler names. */
  listHandlers () { return Array.from(this._handlers.keys()).sort() }

  /**
   * Mount a module instance. Modules are accessible via `svc.modules.<name>`.
   * If the module exposes a `.base` (Autobase consumer) and the swarm
   * transport is enabled, the base is replicated on every swarm connection.
   *
   * @param {string} name
   * @param {object} instance
   */
  mount (name, instance) {
    if (typeof name !== 'string' || !name) throw new Error('mount requires a name')
    if (!instance) throw new Error('mount requires an instance')
    if (this._mounts.has(name)) throw new Error(`mount "${name}" already registered`)
    this._mounts.set(name, { instance })
  }

  /** Whether a module is mounted. */
  hasMount (name) { return this._mounts.has(name) }

  /** List mounted module names. */
  listMounts () { return Array.from(this._mounts.keys()).sort() }

  // ── Dispatch ────────────────────────────────────────────────

  /**
   * Dispatch a method call to its handler. Used by all RPC transports.
   * Throws NoSuchMethodError (code: 'ENOMETHOD') if the method isn't registered.
   */
  async dispatch (method, params, ctx) {
    const handler = this._handlers.get(method)
    if (!handler) throw new NoSuchMethodError(method)

    // Anonymous gate (#12). Runs for EVERY transport, because the surface was
    // open on all of them — HTTP via a public reverse proxy, and swarm-rpc to
    // any peer that knows the topic.
    //
    // Ordered after the ENOMETHOD check on purpose: an unknown method should
    // still say so, otherwise the gate turns every typo into a 403 and makes
    // the API undebuggable. It leaks only which method names exist, which the
    // handler list already exposes.
    if (this._anonymousPolicy === 'deny' && !this._anonymousAllowed.has(method)) {
      let anon = true
      try {
        anon = this._isAnonymous(ctx)
      } catch {
        anon = true // a predicate that throws must fail CLOSED
      }
      if (anon) {
        if (this._onDeny) {
          try { this._onDeny({ method, ctx, reason: 'anonymous' }) } catch {}
        }
        throw new AnonymousDeniedError(method)
      }
    }

    return handler(ctx || {}, params)
  }

  // ── Lifecycle ───────────────────────────────────────────────

  async _open () {
    if (this._uds) await this._uds.listen()
    if (this._http) await this._http.listen()
    if (this._swarm) await this._swarm.listen()
  }

  /**
   * Alias for `ready()` — feels more natural for a daemon ("start listening").
   */
  async listen () {
    await this.ready()
  }

  async _close () {
    const errors = []
    if (this._swarm) try { await this._swarm.close() } catch (e) { errors.push(e) }
    if (this._http) try { await this._http.close() } catch (e) { errors.push(e) }
    if (this._uds) try { await this._uds.close() } catch (e) { errors.push(e) }
    this._swarm = null
    this._http = null
    this._uds = null
    if (errors.length) {
      const msg = errors.map(e => e.message).join('; ')
      throw new Error('Service close errors: ' + msg)
    }
  }
}
/**
 * HTTP transport for hyper-svc.
 *
 * Local-only RPC. Browsers (or curl, or any HTTP client) on the same machine
 * POST a JSON body to /rpc/<method> and receive a JSON response.
 *
 * Wire protocol:
 *   POST /rpc/<method>
 *   Content-Type: application/json
 *   { ...params... }
 *
 *   →  200 application/json  { result: ... }
 *      400 application/json  { error: ... }   (bad request / handler threw)
 *      404 application/json  { error: 'unknown method' }
 *
 * Other routes are NOT handled by hyper-svc — apps wire their own SSR pages
 * via the underlying http.Server (exposed as `.server` on this class). Any
 * request not matching POST /rpc/<method> falls through to user-registered
 * 'request' listeners or, if none, returns 404.
 */

import http from '#node:http'
import b4a from 'b4a'

export class HttpServer {
  /**
   * @param {object} opts
   * @param {number} [opts.port=0] - 0 = random port
   * @param {string} [opts.host='127.0.0.1'] - bind address (local-only by default)
   * @param {string} [opts.prefix=''] - URL prefix to strip before matching /rpc/* (e.g. '/git')
   * @param {(method: string, params: any, ctx: object) => Promise<any>} opts.dispatch - request handler
   */
  constructor (opts = {}) {
    if (typeof opts.dispatch !== 'function') throw new Error('HttpServer requires opts.dispatch')

    this._port = opts.port || 0
    this._host = opts.host || '127.0.0.1'
    this._dispatch = opts.dispatch
    // Optional URL prefix (e.g. '/git') stripped before matching /rpc/*. Set
    // when the daemon is reverse-proxied OR accessed directly under a path
    // prefix that the browser bakes into outgoing fetch URLs. Without this,
    // direct (non-proxied) hits to `<host>/<prefix>/rpc/*` fall through both
    // hyper-svc's listener (which only matches '/rpc/') and the consumer's
    // listener (which strips the prefix and skips '/rpc/' assuming hyper-svc
    // already handled it) — neither responds and the request hangs.
    this._prefix = normalizePrefix(opts.prefix)
    // Optional consumer-provided ctx builder. Called per /rpc/* request to
    // populate fields like identity from request headers/cookies. Receives
    // (req) and may return either an object or a Promise<object>. The
    // returned fields are merged into the default ctx ({ transport: 'http',
    // identity: null, req, res }).
    this._ctxFactory = typeof opts.ctxFactory === 'function' ? opts.ctxFactory : null
    this._listening = false

    // Create the server eagerly so apps can register routes BEFORE listen().
    // Our /rpc/* listener is installed first so it has first dibs on requests.
    this._server = http.createServer()
    this._server.on('request', (req, res) => this._onRequest(req, res))
  }

  /**
   * Update or set the ctxFactory after construction. Useful when the
   * factory needs references that don't exist at constructor time
   * (e.g. session stores created by the consumer in a later step).
   */
  setCtxFactory (fn) {
    if (fn != null && typeof fn !== 'function') {
      throw new Error('ctxFactory must be a function or null')
    }
    this._ctxFactory = fn
  }

  /** Underlying http.Server. Apps register their own routes here. */
  get server () { return this._server }
  get listening () { return this._listening }
  get port () {
    if (!this._listening || !this._server) return null
    const addr = this._server.address()
    return addr ? addr.port : null
  }
  get host () { return this._host }

  async listen () {
    if (this._listening) return

    await new Promise((resolve, reject) => {
      this._server.once('error', reject)
      this._server.listen(this._port, this._host, () => {
        this._server.removeListener('error', reject)
        resolve()
      })
    })

    this._listening = true
  }

  async _onRequest (req, res) {
    // Compute the path with the configured prefix removed. Don't mutate
    // req.url — consumers' own request listeners may have their own
    // prefix-stripping logic and should see the original.
    let path = req.url
    if (this._prefix && path.startsWith(this._prefix)) {
      const rest = path.slice(this._prefix.length)
      // Only treat as a prefix match when the next char is '/' (or end of
      // URL). Otherwise '/gitfoo' would falsely match prefix '/git'.
      if (rest === '' || rest[0] === '/' || rest[0] === '?') {
        path = rest || '/'
      }
    }

    if (req.method === 'POST' && path.startsWith('/rpc/')) {
      const method = path.slice('/rpc/'.length).split('?')[0]
      let body
      try {
        body = await collectBody(req)
      } catch (err) {
        return jsonError(res, 400, 'failed to read body: ' + err.message)
      }

      let params = {}
      if (body.length > 0) {
        try {
          params = JSON.parse(b4a.toString(body, 'utf8'))
        } catch {
          return jsonError(res, 400, 'request body must be JSON')
        }
      }

      let ctx = {
        transport: 'http',
        identity: null, // anonymous by default — apps populate via ctxFactory
        req,
        res
      }

      if (this._ctxFactory) {
        try {
          const extra = await this._ctxFactory(req)
          if (extra && typeof extra === 'object') {
            ctx = { ...ctx, ...extra }
          }
        } catch (err) {
          return jsonError(res, 500, 'ctxFactory failed: ' + (err.message || String(err)))
        }
      }

      try {
        const result = await this._dispatch(method, params, ctx)
        if (result === undefined) {
          return jsonOk(res, { result: null })
        }
        return jsonOk(res, { result })
      } catch (err) {
        if (err && err.code === 'ENOMETHOD') {
          return jsonError(res, 404, err.message)
        }
        // An anonymous-gate denial is an authorization outcome, not a bad
        // request — surface it as 403 so callers (and the audit trail) can
        // tell "you may not" from "you sent nonsense".
        if (err && err.code === 'EDENIED') {
          return jsonError(res, err.status || 403, err.message)
        }
        return jsonError(res, 400, err.message || String(err))
      }
    }

    // Not an /rpc/ request. App-owned routes live in OTHER 'request'
    // listeners (registered on this._server by the consumer). If at least
    // one other listener is present, leave the response to them — they're
    // responsible for handling and 404-ing as needed.
    //
    // If we're the ONLY 'request' listener, nobody else will handle this,
    // so we 404 it synchronously. Doing this synchronously avoids a race
    // where setImmediate fires before an async user handler can respond.
    if (this._server.listenerCount('request') <= 1) {
      res.writeHead(404, { 'Content-Type': 'text/plain' })
      res.end('Not found')
    }
    // Otherwise: leave the response to the user's listener.
  }

  async close () {
    if (!this._server || !this._listening) {
      this._listening = false
      return
    }
    await new Promise((resolve) => this._server.close(() => resolve()))
    this._listening = false
  }
}

function collectBody (req) {
  return new Promise((resolve, reject) => {
    const parts = []
    req.on('data', (c) => parts.push(c))
    req.on('end', () => resolve(b4a.concat(parts)))
    req.on('error', reject)
  })
}

function jsonOk (res, body) {
  res.writeHead(200, { 'Content-Type': 'application/json' })
  res.end(JSON.stringify(body))
}

function jsonError (res, status, message) {
  res.writeHead(status, { 'Content-Type': 'application/json' })
  res.end(JSON.stringify({ error: message }))
}

function normalizePrefix (p) {
  if (!p || typeof p !== 'string') return ''
  // Strip trailing slashes so '/git/' and '/git' behave identically.
  let s = p.replace(/\/+$/, '')
  // A bare '/' is not a real prefix.
  if (s === '' || s === '/') return ''
  // Require a leading slash so callers can't accidentally pass 'git'.
  if (s[0] !== '/') s = '/' + s
  return s
}
/**
 * Swarm-RPC transport for hyper-svc.
 *
 * Adds a Protomux RPC channel on every Hyperswarm connection. Requests
 * and responses use the same length-prefixed JSON framing as UDS (see
 * frame.js), multiplexed over the Noise-encrypted swarm stream.
 *
 * This transport extends the existing SwarmTransport (replication) with
 * request/response dispatch. A single swarm connection carries:
 *   1. Hypercore/Autobase replication (corestore.replicate)
 *   2. Module mux channels (attachProtocol)
 *   3. RPC frames (this module)
 *
 * Wire protocol over Protomux:
 *   Channel: 'hyper-svc-rpc'
 *   Message type 0 (request):  { id, method, params }
 *   Message type 1 (response): { id, result } | { id, error }
 *
 * Auth: the caller's Noise public key is available on the connection.
 * For pear-git-v3, envelope auth (Phase 2a) is layered on top — the
 * RPC payload itself is a signed envelope, verified by the dispatch
 * handler. The transport just provides the Noise tunnel.
 */

import Protomux from 'protomux'
import c from 'compact-encoding'
import b4a from 'b4a'

const PROTOCOL = 'hyper-svc-rpc'

/**
 * Attach an RPC channel to a Protomux instance (one per swarm connection).
 *
 * Server-side: receives requests, calls dispatch, sends responses.
 *
 * @param {Protomux} mux — the protomux for this connection
 * @param {Function} dispatch — (method, params, ctx) => result
 * @param {object} [opts]
 * @param {Buffer} [opts.remotePublicKey] — peer's Noise pubkey (for ctx)
 * @returns {{ channel, close: () => void }}
 */
export function attachRpcServer (mux, dispatch, opts = {}) {
  const channel = mux.createChannel({
    protocol: PROTOCOL,
    onopen () {},
    onclose () {}
  })

  if (!channel) return null // protocol already claimed on this mux

  const requestType = channel.addMessage({
    encoding: c.json,
    onmessage (msg) {
      handleRequest(msg, channel, responseType, dispatch, opts)
    }
  })

  const responseType = channel.addMessage({
    encoding: c.json,
    onmessage () {} // server doesn't receive responses
  })

  channel.open()

  return { channel, close: () => channel.close() }
}

/**
 * Attach an RPC channel to a Protomux instance (client-side).
 *
 * Client-side: sends requests, receives responses.
 *
 * @param {Protomux} mux
 * @returns {{ call: (method, params) => Promise<any>, close: () => void }}
 */
export function attachRpcClient (mux) {
  let nextId = 1
  const pending = new Map() // id → { resolve, reject }

  const channel = mux.createChannel({
    protocol: PROTOCOL,
    onopen () {},
    onclose () {
      // Reject all pending calls on channel close.
      for (const [id, p] of pending) {
        p.reject(new Error('swarm-rpc channel closed'))
        pending.delete(id)
      }
    }
  })

  if (!channel) throw new Error('swarm-rpc: protocol already claimed')

  const requestType = channel.addMessage({
    encoding: c.json,
    onmessage () {} // client doesn't receive requests
  })

  const responseType = channel.addMessage({
    encoding: c.json,
    onmessage (msg) {
      const p = pending.get(msg.id)
      if (!p) return // stale or unknown id
      pending.delete(msg.id)
      if (msg.error) {
        const err = new Error(msg.error)
        err.code = msg.code || null
        p.reject(err)
      } else {
        p.resolve(msg.result)
      }
    }
  })

  channel.open()

  return {
    call (method, params) {
      return new Promise((resolve, reject) => {
        const id = nextId++
        pending.set(id, { resolve, reject })
        requestType.send({ id, method, params })
      })
    },
    close () {
      channel.close()
    }
  }
}

// --- internals ---

async function handleRequest (msg, channel, responseType, dispatch, opts) {
  const { id, method, params } = msg
  if (!id || !method) {
    responseType.send({ id: id || 0, error: 'invalid request (missing id or method)' })
    return
  }

  const ctx = {
    transport: 'swarm-rpc',
    identity: {
      type: 'swarm',
      remotePublicKey: opts.remotePublicKey
        ? b4a.toString(opts.remotePublicKey, 'hex')
        : null,
      trust: 'network'
    }
  }

  try {
    const result = await dispatch(method, params || {}, ctx)
    responseType.send({ id, result: result !== undefined ? result : null })
  } catch (err) {
    responseType.send({
      id,
      error: err.message || String(err),
      code: err.code || null
    })
  }
}
/**
 * Hyperswarm transport for hyper-svc.
 *
 * Joins a topic, and on every connection:
 *   1. corestore.replicate(conn) — replicates all hypercores in the corestore
 *   2. for each mounted module exposing a `.base` (Autobase consumer), also
 *      base.replicate(conn) — belt-and-suspenders against the autobase
 *      replication gotcha (corestore.replicate alone is unreliable in tests
 *      and we don't want to chase that bug in production)
 *   3. for each mounted module exposing `attachProtocol(mux)` (mux-based
 *      protocols like hyper-overlay), lazily build `Protomux.from(conn)`
 *      once per connection and call attachProtocol on each consumer.
 *      Protomux.from reuses any protomux already attached by corestore/autobase
 *      replication, so channels multiplex cleanly over the same transport.
 *
 * No RPC over swarm today — modules communicate either via replication or
 * via their own mux channels. Adding a new mux-based protocol only requires
 * exposing `attachProtocol(mux)` on the mounted module.
 */

import Hyperswarm from 'hyperswarm'
import Protomux from 'protomux'
import b4a from 'b4a'
import { attachRpcServer } from './swarm-rpc.js'

export class SwarmTransport {
  /**
   * @param {object} opts
   * @param {Buffer|string} opts.topic - 32-byte topic to join (hex string or buffer)
   * @param {object} opts.corestore - corestore to replicate
   * @param {() => Iterable<{ name: string, instance: object }>} opts.mounts - returns the current mount list (called on each connection so late mounts work)
   * @param {Function} [opts.dispatch] - RPC dispatch function. When provided, a swarm-RPC Protomux channel is attached to every connection.
   * @param {object} [opts.swarm] - hyperswarm options (e.g., bootstrap nodes for testnet)
   * @param {boolean} [opts.server=true]
   * @param {boolean} [opts.client=true]
   */
  constructor (opts = {}) {
    if (!opts.topic) throw new Error('SwarmTransport requires opts.topic')
    if (!opts.corestore) throw new Error('SwarmTransport requires opts.corestore')
    if (typeof opts.mounts !== 'function') throw new Error('SwarmTransport requires opts.mounts (function)')

    this._topic = typeof opts.topic === 'string' ? b4a.from(opts.topic, 'hex') : opts.topic
    if (this._topic.length !== 32) throw new Error('topic must be 32 bytes')

    this._corestore = opts.corestore
    this._getMounts = opts.mounts
    this._dispatch = opts.dispatch || null
    this._swarmOpts = opts.swarm || {}
    this._joinAsServer = opts.server !== false
    this._joinAsClient = opts.client !== false

    this._swarm = null
    this._discovery = null
    this._listening = false
    this._connections = new Set()
  }

  get topic () { return this._topic }
  get listening () { return this._listening }
  get swarm () { return this._swarm }
  get connections () { return this._connections.size }

  async listen () {
    if (this._listening) return

    this._swarm = new Hyperswarm(this._swarmOpts)
    this._swarm.on('connection', (conn, info) => this._onConnection(conn, info))

    this._discovery = this._swarm.join(this._topic, {
      server: this._joinAsServer,
      client: this._joinAsClient
    })
    await this._discovery.flushed()
    this._listening = true
  }

  /**
   * Join an ADDITIONAL topic on this same shared swarm (Keet activateRoom
   * pattern — one Hyperswarm, many topics). Used to rendezvous for a
   * specific room (per-org / per-repo autobase) by its discoveryKey, so
   * peers find each other for that room without needing the daemon's
   * identity topic. Replication still rides the single `_onConnection`
   * (corestore.replicate + per-mount replicate). Deduped + idempotent.
   *
   * @param {Buffer|string} topic - 32-byte discoveryKey (hex or buffer)
   * @param {object} [opts] - { server=true, client=true }
   * @returns {object|null} the PeerDiscovery handle (or existing one)
   */
  joinTopic (topic, opts = {}) {
    if (!this._swarm) return null // not listening yet
    const t = typeof topic === 'string' ? b4a.from(topic, 'hex') : topic
    if (!t || t.length !== 32) return null
    const key = b4a.toString(t, 'hex')
    if (!this._extraTopics) this._extraTopics = new Map()
    if (this._extraTopics.has(key)) return this._extraTopics.get(key)
    const { server = true, client = true } = opts
    const disc = this._swarm.join(t, { server, client })
    this._extraTopics.set(key, disc)
    return disc
  }

  /** discoveryKeys of every additional room topic joined via joinTopic. */
  get joinedRoomTopics () {
    return this._extraTopics ? Array.from(this._extraTopics.keys()) : []
  }

  _onConnection (conn, info) {
    this._connections.add(conn)
    conn.on('close', () => this._connections.delete(conn))
    conn.on('error', () => { /* connection errors are routine — ignore */ })

    // Always replicate the corestore. Hypercore handles muxing internally.
    try {
      this._corestore.replicate(conn)
    } catch (err) {
      // Don't crash on replication setup errors — log via stderr
      // eslint-disable-next-line no-console
      console.error('[hyper-svc/swarm] corestore.replicate failed:', err.message)
    }

    // Belt and suspenders: also call base.replicate on every mounted Autobase
    // consumer. This is the documented workaround for the test-time gotcha
    // where corestore.replicate alone doesn't propagate writer cores.
    //
    // In the same pass, wire up mux-based protocols: any mount exposing
    // `attachProtocol(mux)` (hyper-overlay and future consumers) gets attached
    // to a single Protomux per connection. Protomux.from reuses any protomux
    // corestore/autobase replication already set up.
    let mux = null
    for (const { name, instance } of this._getMounts()) {
      if (!instance) continue

      // A mount may expose `replicate(conn)` to replicate its own
      // autobase(s) — required for MULTI-autobase mounts (e.g. per-org
      // HyperOrgs) whose single `.base` getter is undefined or THROWS.
      // Otherwise fall back to the single-autobase `.base.replicate`.
      // The whole access is wrapped: a throwing `.base` getter must
      // never abort the daemon on an incoming connection.
      try {
        if (typeof instance.replicate === 'function') {
          instance.replicate(conn)
        } else {
          const base = instance.base
          if (base && typeof base.replicate === 'function') base.replicate(conn)
        }
      } catch (err) {
        // eslint-disable-next-line no-console
        console.error(`[hyper-svc/swarm] ${name} replicate failed:`, err.message)
      }

      if (typeof instance.attachProtocol === 'function') {
        if (!mux) {
          try {
            mux = Protomux.from(conn)
          } catch (err) {
            // eslint-disable-next-line no-console
            console.error('[hyper-svc/swarm] Protomux.from failed:', err.message)
          }
        }
        if (mux) {
          try {
            instance.attachProtocol(mux)
          } catch (err) {
            // eslint-disable-next-line no-console
            console.error(`[hyper-svc/swarm] ${name}.attachProtocol failed:`, err.message)
          }
        }
      }
    }

    // Swarm-RPC channel — attach only when dispatch is configured.
    // Multiplexes cleanly over the same Noise stream as replication
    // and module protocols above.
    if (this._dispatch) {
      if (!mux) {
        try {
          mux = Protomux.from(conn)
        } catch (err) {
          // eslint-disable-next-line no-console
          console.error('[hyper-svc/swarm] Protomux.from failed (rpc):', err.message)
        }
      }
      if (mux) {
        try {
          attachRpcServer(mux, this._dispatch, {
            remotePublicKey: conn.remotePublicKey || null
          })
        } catch (err) {
          // eslint-disable-next-line no-console
          console.error('[hyper-svc/swarm] attachRpcServer failed:', err.message)
        }
      }
    }
  }

  async close () {
    if (!this._swarm) return
    for (const conn of this._connections) {
      try { conn.destroy() } catch {}
    }
    this._connections.clear()
    try { await this._swarm.destroy() } catch {}
    this._swarm = null
    this._discovery = null
    this._listening = false
  }
}
/**
 * UDS (Unix Domain Socket) transport for hyper-svc.
 *
 * Local-only RPC. Trusts the local user. CLIs on the same machine connect
 * to ~/.local/share/<name>/svc.sock (or wherever the daemon was told to bind)
 * and exchange length-prefixed JSON frames.
 *
 * Wire protocol per connection:
 *   client → server: { id, method, params }
 *   server → client: { id, result }   OR   { id, error }
 *
 * Multiple in-flight requests are matched by id. Server is fully async —
 * a slow handler doesn't block other requests on the same connection.
 */

import process from '#node:process'
import { unlink, stat } from '#node:fs/promises'
import { encodeFrame, FrameDecoder } from '../frame.js'
import { udsBackend, isAbstract } from '../uds-backend.js'

export class UdsServer {
  /**
   * @param {object} opts
   * @param {string} opts.path - filesystem path for the UNIX socket
   * @param {(method: string, params: any, ctx: object) => Promise<any>} opts.dispatch - request handler
   */
  constructor (opts = {}) {
    if (!opts.path) throw new Error('UdsServer requires opts.path')
    if (typeof opts.dispatch !== 'function') throw new Error('UdsServer requires opts.dispatch')

    this._path = opts.path
    this._dispatch = opts.dispatch
    this._server = null
    this._connections = new Set()
    this._listening = false

    // Idle-connection reaper. `allowHalfOpen:false` + the end/error handlers
    // below clean up when a client CLOSES or EXITS. But a caller that opens a
    // connection and then neither closes it nor exits (a long-lived process
    // that forgets Client.close()) leaves the accepted socket ESTAB forever —
    // the OS never delivers an end/close the server can react to. Unbounded,
    // this grew pgh-sidecar to ~348k ESTAB unix sockets / ~22GB (2026-07-05).
    // Sweep and destroy connections idle past _idleTimeout with no request in
    // flight. Request/response callers reset the timer on every frame, so only
    // genuinely dead connections are reaped. Set idleTimeout:0 to disable.
    this._idleTimeout = opts.idleTimeout === undefined ? 120000 : opts.idleTimeout
    this._sweepTimer = null
  }

  get path () { return this._path }
  get listening () { return this._listening }

  async listen () {
    if (this._listening) return

    // Select the transport: #node:net for pathname sockets (unchanged), or
    // bare-abstract-socket for an '@'-prefixed abstract name. `name` is the
    // bind name with the '@' stripped.
    const { backend, name, abstract } = await udsBackend(this._path)

    // Best-effort cleanup of a stale socket FILE (server crashed without
    // unlinking). Abstract sockets have no filesystem entry (the kernel frees
    // the name when the socket closes), so this applies to pathname sockets only.
    if (!abstract) {
      // ONLY remove if it's actually a socket — never touch regular files / dirs.
      try {
        const s = await stat(this._path)
        if (s.isSocket()) {
          // Try to connect first — if a daemon is already running, refuse to clobber.
          const inUse = await this._probe()
          if (inUse) throw new Error(`socket ${this._path} is already in use`)
          await unlink(this._path)
        }
      } catch (err) {
        if (err.code !== 'ENOENT' && err.message.indexOf('already in use') === -1) {
          // stat failed for some reason other than missing — ignore, let listen() fail naturally
        }
        if (err.message && err.message.indexOf('already in use') !== -1) throw err
      }
    }

    // allowHalfOpen:false is CRITICAL. bare-net defaults it to true (unlike
    // Node), which means when a client disconnects the accepted socket never
    // sees 'end' and never releases — the fd AND its 64KB read buffer leak.
    // With false, peer disconnect surfaces as 'end', which _onConnection
    // turns into a destroy() so the socket fully closes. See the fd-leak that
    // grew pgh-sidecar to ~205k open sockets / 14GB RSS (2026-06-09).
    // (bare-abstract-socket honours the same allowHalfOpen:false on accept.)
    this._server = backend.createServer({ allowHalfOpen: false }, (sock) => this._onConnection(sock))

    await new Promise((resolve, reject) => {
      this._server.once('error', reject)
      this._server.listen(name, () => {
        this._server.removeListener('error', reject)
        resolve()
      })
    })

    this._listening = true

    if (this._idleTimeout > 0) {
      const sweepEvery = Math.max(1000, Math.min(30000, Math.floor(this._idleTimeout / 2)))
      this._sweepTimer = setInterval(() => this._reapIdle(), sweepEvery)
      // Don't let the reaper keep the process alive on its own.
      if (this._sweepTimer && typeof this._sweepTimer.unref === 'function') this._sweepTimer.unref()
    }
  }

  // Destroy connections idle past _idleTimeout with no request in flight.
  // destroy() fires 'close', which removes the socket from _connections.
  _reapIdle () {
    const now = Date.now()
    for (const sock of this._connections) {
      if ((sock._inflight || 0) > 0) continue
      if (now - (sock._lastActive || 0) > this._idleTimeout) {
        try { sock.destroy() } catch {}
      }
    }
  }

  async _probe () {
    const { backend, name } = await udsBackend(this._path)
    return new Promise((resolve) => {
      const sock = backend.createConnection(name)
      sock.once('connect', () => {
        sock.destroy()
        resolve(true)
      })
      sock.once('error', () => {
        sock.destroy()
        resolve(false)
      })
    })
  }

  _onConnection (sock) {
    this._connections.add(sock)
    sock._lastActive = Date.now()
    sock._inflight = 0
    sock.on('close', () => this._connections.delete(sock))

    // Peer closed their write side (client finished + disconnected). bare-net
    // does NOT emit 'close' on its own here — we must destroy() to release the
    // fd and drop the socket (and its read buffer) from _connections. Safe even
    // mid-dispatch: a late _handleRequest write is already wrapped in try/catch.
    sock.on('end', () => { try { sock.destroy() } catch {} })

    const decoder = new FrameDecoder((req) => this._handleRequest(sock, req))

    sock.on('data', (chunk) => {
      sock._lastActive = Date.now()
      try {
        decoder.push(chunk)
      } catch (err) {
        // Bad frame — close the connection rather than try to recover
        try { sock.destroy(err) } catch {}
      }
    })

    // Abrupt disconnect (ECONNRESET, or the client process killed / timed out
    // mid-request). bare-net does NOT emit 'close' on its own (see the 'end'
    // note above) — and unlike a clean FIN there is no 'end' either — so we
    // MUST destroy() here to release the fd + 64KB read buffer and drop the
    // socket from _connections. Without this, every abrupt client disconnect
    // leaks a half-open socket (the 0.1.3 'end'-only fix covered clean closes
    // but not this path — the cause of the 6645-socket accumulation seen when
    // a client hammers the UDS with timed-out/killed calls).
    sock.on('error', () => { try { sock.destroy() } catch {} })
  }

  async _handleRequest (sock, req) {
    const id = req && req.id
    if (typeof id !== 'number' && typeof id !== 'string') {
      // Malformed — best effort error response, but the client probably can't match it
      try {
        sock.write(encodeFrame({ id: null, error: 'request missing id' }))
      } catch {}
      return
    }

    const ctx = {
      transport: 'uds',
      identity: { type: 'local', userId: process.env.USER || null, trust: 'local' }
    }

    // Mark in-flight so the idle reaper never destroys a socket mid-dispatch,
    // even if the handler runs longer than _idleTimeout.
    sock._inflight = (sock._inflight || 0) + 1
    try {
      const result = await this._dispatch(req.method, req.params, ctx)
      try {
        sock.write(encodeFrame({ id, result }))
      } catch {}
    } catch (err) {
      try {
        sock.write(encodeFrame({ id, error: err.message || String(err) }))
      } catch {}
    } finally {
      sock._inflight = Math.max(0, (sock._inflight || 1) - 1)
      sock._lastActive = Date.now()
    }
  }

  async close () {
    if (!this._server) return
    if (this._sweepTimer) { clearInterval(this._sweepTimer); this._sweepTimer = null }
    for (const sock of this._connections) {
      try { sock.destroy() } catch {}
    }
    this._connections.clear()
    await new Promise((resolve) => this._server.close(() => resolve()))
    this._server = null
    this._listening = false

    // Clean up the socket file (best-effort). Abstract sockets have no fs entry.
    if (!isAbstract(this._path)) {
      try { await unlink(this._path) } catch {}
    }
  }
}
/**
 * UDS backend selection.
 *
 * A socket "path" beginning with '@' names a Linux ABSTRACT-namespace socket
 * (no filesystem entry) instead of a pathname socket. Abstract sockets are the
 * only UNIX-socket option when SELinux denies the shell domain from creating
 * pathname sockets — e.g. an unrooted Android phone (celltron) running the
 * daemon under proot, where bind(AF_UNIX, "/path.sock") returns EACCES.
 *
 * For every non-'@' path we use #node:net exactly as before — byte-for-byte
 * unchanged behavior. bare-abstract-socket is imported LAZILY (dynamic import,
 * only at the async call sites below) so pathname deployments never load the
 * native addon.
 */

import net from '#node:net'

let _abstract = null

async function loadAbstract () {
  if (_abstract === null) {
    const m = await import('bare-abstract-socket')
    _abstract = m.default || m
  }
  return _abstract
}

// True if `path` names an abstract-namespace socket (leading '@').
export function isAbstract (path) {
  return typeof path === 'string' && path.length > 1 && path[0] === '@'
}

// Resolve the transport backend + the name to bind/connect. The returned
// `backend` exposes net-compatible createServer(opts, onConnection) and
// createConnection(name). For abstract paths the leading '@' is stripped
// (bare-abstract-socket adds the actual leading NUL).
export async function udsBackend (path) {
  if (isAbstract(path)) {
    return { backend: await loadAbstract(), name: path.slice(1), abstract: true }
  }
  return { backend: net, name: path, abstract: false }
}
const { Pull, Push, HEADERBYTES, KEYBYTES, ABYTES } = require('sodium-secretstream')
const sodium = require('sodium-universal')
const crypto = require('hypercore-crypto')
const { Duplex, Writable, getStreamError } = require('streamx')
const b4a = require('b4a')
const Timeout = require('timeout-refresh')
const unslab = require('unslab')
const Bridge = require('./lib/bridge')
const Handshake = require('./lib/handshake')

const IDHEADERBYTES = HEADERBYTES + 32
const [NS_INITIATOR, NS_RESPONDER, NS_SEND] = crypto.namespace('hyperswarm/secret-stream', 3)
const MAX_ATOMIC_WRITE = 256 * 256 * 256 - 1

module.exports = class NoiseSecretStream extends Duplex {
  constructor(isInitiator, rawStream, opts = {}) {
    super({ mapWritable: toBuffer })

    if (typeof isInitiator !== 'boolean') {
      throw new Error('isInitiator should be a boolean')
    }

    this.noiseStream = this
    this.isInitiator = isInitiator
    this.rawStream = null

    this.publicKey = opts.publicKey || null
    this.remotePublicKey = opts.remotePublicKey || null
    this.handshakeHash = null
    this.connected = false
    this.keepAlive = opts.keepAlive || 0
    this.timeout = 0
    this.enableSend = opts.enableSend !== false

    // pointer for upstream to set data here if they want
    this.userData = null

    let openedDone = null
    this.opened = new Promise((resolve) => {
      openedDone = resolve
    })

    this.rawBytesWritten = 0
    this.rawBytesRead = 0

    // metadata used by 'hyperdht'
    this.relay = null
    this.puncher = null

    // unwrapped raw stream
    this._rawStream = null

    // handshake state
    this._handshake = null
    this._handshakePattern = opts.pattern || null
    this._handshakeDone = null

    // message parsing state
    this._state = 0
    this._len = 0
    this._tmp = 1
    this._message = null

    this._openedDone = openedDone
    this._startDone = null
    this._drainDone = null
    this._outgoingPlain = null
    this._outgoingWrapped = null
    this._utp = null
    this._setup = true
    this._ended = 2
    this._encrypt = null
    this._decrypt = null
    this._timeoutTimer = null
    this._keepAliveTimer = null
    this._sendState = null

    if (opts.autoStart !== false) this.start(rawStream, opts)

    // wiggle it to trigger open immediately (TODO add streamx option for this)
    this.resume()
    this.pause()
  }

  static keyPair(seed) {
    return Handshake.keyPair(seed)
  }

  static id(handshakeHash, isInitiator, id) {
    return streamId(handshakeHash, isInitiator, id)
  }

  setTimeout(ms) {
    if (!ms) ms = 0

    this._clearTimeout()
    this.timeout = ms

    if (!ms || this.rawStream === null) return

    this._timeoutTimer = Timeout.once(ms, destroyTimeout, this)
    this._timeoutTimer.unref()
  }

  setKeepAlive(ms) {
    if (!ms) ms = 0

    this._clearKeepAlive()

    this.keepAlive = ms

    if (!ms || this.rawStream === null) return

    this._keepAliveTimer = Timeout.on(ms, sendKeepAlive, this)
    this._keepAliveTimer.unref()
  }

  sendKeepAlive() {
    const empty = this.alloc(0)
    this.write(empty)
  }

  start(rawStream, opts = {}) {
    if (rawStream) {
      this.rawStream = rawStream
      this._rawStream = rawStream
      if (typeof this.rawStream.setContentSize === 'function') {
        this._utp = rawStream
      }
    } else {
      this.rawStream = new Bridge(this)
      this._rawStream = this.rawStream.reverse
    }

    this.rawStream.on('error', this._onrawerror.bind(this))
    this.rawStream.on('close', this._onrawclose.bind(this))

    this._startHandshake(opts.handshake, opts.keyPair || null)
    this._continueOpen(null)

    if (this.destroying) return

    if (opts.data) this._onrawdata(opts.data)
    if (opts.ended) this._onrawend()

    if (this.keepAlive > 0 && this._keepAliveTimer === null) {
      this.setKeepAlive(this.keepAlive)
    }

    if (this.timeout > 0 && this._timeoutTimer === null) {
      this.setTimeout(this.timeout)
    }
  }

  async flush() {
    if ((await this.opened) === false) return false
    if ((await Writable.drained(this)) === false) return false
    if (this.destroying) return false

    if (this.rawStream !== null && this.rawStream.flush) {
      return await this.rawStream.flush()
    }

    return true
  }

  _continueOpen(err) {
    if (err) this.destroy(err)
    if (this._startDone === null) return
    const done = this._startDone
    this._startDone = null
    this._open(done)
  }

  _onkeypairpromise(p) {
    const self = this
    const cont = this._continueOpen.bind(this)

    p.then(onkeypair, cont)

    function onkeypair(kp) {
      self._onkeypair(kp)
      cont(null)
    }
  }

  _onkeypair(keyPair) {
    const pattern = this._handshakePattern || 'XX'
    const remotePublicKey = this.remotePublicKey

    this._handshake = new Handshake(this.isInitiator, keyPair, remotePublicKey, pattern)
    this.publicKey = this._handshake.keyPair.publicKey
  }

  _startHandshake(handshake, keyPair) {
    if (handshake) {
      const { tx, rx, hash, publicKey, remotePublicKey } = handshake
      this._setupSecretStream(tx, rx, hash, publicKey, remotePublicKey)
      return
    }

    if (!keyPair) keyPair = Handshake.keyPair()

    if (typeof keyPair.then === 'function') {
      this._onkeypairpromise(keyPair)
    } else {
      this._onkeypair(keyPair)
    }
  }

  _onrawerror(err) {
    this.destroy(err)
  }

  _onrawclose() {
    if (this._ended !== 0) this.destroy()
  }

  _onrawdata(data) {
    let offset = 0

    if (this._timeoutTimer !== null) {
      this._timeoutTimer.refresh()
    }

    do {
      switch (this._state) {
        case 0: {
          while (this._tmp !== 0x1000000 && offset < data.byteLength) {
            const v = data[offset++]
            this._len += this._tmp * v
            this._tmp *= 256
          }

          if (this._tmp === 0x1000000) {
            this._tmp = 0
            this._state = 1
            const unprocessed = data.byteLength - offset
            if (unprocessed < this._len && this._utp !== null)
              this._utp.setContentSize(this._len - unprocessed)
          }

          break
        }

        case 1: {
          const missing = this._len - this._tmp
          const end = missing + offset

          if (this._message === null && end <= data.byteLength) {
            this._message = data.subarray(offset, end)
            offset += missing
            this._incoming()
            break
          }

          const unprocessed = data.byteLength - offset

          if (this._message === null) {
            this._message = b4a.allocUnsafe(this._len)
          }

          b4a.copy(data, this._message, this._tmp, offset)
          this._tmp += unprocessed

          if (end <= data.byteLength) {
            offset += missing
            this._incoming()
          } else {
            offset += unprocessed
          }

          break
        }
      }
    } while (offset < data.byteLength && !this.destroying)
  }

  _onrawend() {
    this._ended--
    this.push(null)
  }

  _onrawdrain() {
    const drain = this._drainDone
    if (drain === null) return
    this._drainDone = null
    drain()
  }

  _read(cb) {
    this.rawStream.resume()
    cb(null)
  }

  _incoming() {
    const message = this._message

    this._state = 0
    this._len = 0
    this._tmp = 1
    this._message = null

    if (this._setup === true) {
      if (this._handshake) {
        this._onhandshakert(this._handshake.recv(message))
      } else {
        if (message.byteLength !== IDHEADERBYTES) {
          this.destroy(new Error('Invalid header message received'))
          return
        }

        const remoteId = message.subarray(0, 32)
        const expectedId = streamId(this.handshakeHash, !this.isInitiator)
        const header = message.subarray(32)

        if (!b4a.equals(expectedId, remoteId)) {
          this.destroy(new Error('Invalid header received'))
          return
        }

        this._decrypt.init(header)
        this._setup = false // setup is now done
      }
      return
    }

    if (message.byteLength < ABYTES) {
      this.destroy(new Error('Invalid message received'))
      return
    }

    this.rawBytesRead += message.byteLength

    const plain = message.subarray(1, message.byteLength - ABYTES + 1)

    try {
      this._decrypt.next(message, plain)
    } catch (err) {
      this.destroy(err)
      return
    }

    // If keep alive is selective, eat the empty buffers (ie assume the other side has it enabled also)
    if (plain.byteLength === 0 && this.keepAlive !== 0) return

    if (this.push(plain) === false) {
      this.rawStream.pause()
    }
  }

  _onhandshakert(h) {
    if (this._handshakeDone === null) return

    if (h !== null) {
      if (h.data) this._rawStream.write(h.data)
      if (!h.tx) return
    }

    const done = this._handshakeDone
    const publicKey = this._handshake.keyPair.publicKey

    this._handshakeDone = null
    this._handshake = null

    if (h === null) return done(new Error('Noise handshake failed'))

    this._setupSecretStream(h.tx, h.rx, h.hash, publicKey, h.remotePublicKey)
    this._resolveOpened(true)
    done(null)
  }

  _setupSecretStream(tx, rx, handshakeHash, publicKey, remotePublicKey) {
    const buf = b4a.allocUnsafeSlow(3 + IDHEADERBYTES)
    writeUint24le(IDHEADERBYTES, buf)

    this._encrypt = new Push(unslab(tx.subarray(0, KEYBYTES)), undefined, buf.subarray(3 + 32))
    this._decrypt = new Pull(unslab(rx.subarray(0, KEYBYTES)))

    this.publicKey = publicKey
    this.remotePublicKey = remotePublicKey
    this.handshakeHash = handshakeHash

    const id = buf.subarray(3, 3 + 32)
    streamId(handshakeHash, this.isInitiator, id)

    // initialize secretbox state for unordered messages
    this._setupSecretSend(handshakeHash)

    this.emit('handshake')
    // if rawStream is a bridge, also emit it there
    if (this.rawStream !== this._rawStream) this.rawStream.emit('handshake')

    if (this.destroying) return

    this._rawStream.write(buf)
  }

  _setupSecretSend(handshakeHash) {
    this._sendState = b4a.allocUnsafeSlow(32 + 32 + 8 + 8)
    const encrypt = this._sendState.subarray(0, 32) // secrets
    const decrypt = this._sendState.subarray(32, 64)
    const counter = this._sendState.subarray(64, 72) // nonce
    const initial = this._sendState.subarray(72)

    const inputs = this.isInitiator
      ? [
          [NS_INITIATOR, NS_SEND],
          [NS_RESPONDER, NS_SEND]
        ]
      : [
          [NS_RESPONDER, NS_SEND],
          [NS_INITIATOR, NS_SEND]
        ]

    sodium.crypto_generichash_batch(encrypt, inputs[0], handshakeHash)
    sodium.crypto_generichash_batch(decrypt, inputs[1], handshakeHash)

    sodium.randombytes_buf(initial)
    counter.set(initial)
  }

  _open(cb) {
    // no autostart or no handshake yet
    if (this._rawStream === null || (this._handshake === null && this._encrypt === null)) {
      this._startDone = cb
      return
    }

    this._rawStream.on('data', this._onrawdata.bind(this))
    this._rawStream.on('end', this._onrawend.bind(this))
    this._rawStream.on('drain', this._onrawdrain.bind(this))

    if (this.enableSend) this._rawStream.on('message', this._onmessage.bind(this))

    if (this._encrypt !== null) {
      this._resolveOpened(true)
      return cb(null)
    }

    this._handshakeDone = cb

    if (this.isInitiator) this._onhandshakert(this._handshake.send())
  }

  _predestroy() {
    if (this.rawStream) {
      const error = getStreamError(this)
      this.rawStream.destroy(error)
    }

    if (this._startDone !== null) {
      const done = this._startDone
      this._startDone = null
      done(new Error('Stream destroyed'))
    }

    if (this._handshakeDone !== null) {
      const done = this._handshakeDone
      this._handshakeDone = null
      done(new Error('Stream destroyed'))
    }

    if (this._drainDone !== null) {
      const done = this._drainDone
      this._drainDone = null
      done(new Error('Stream destroyed'))
    }
  }

  _write(data, cb) {
    let wrapped = this._outgoingWrapped

    if (data !== this._outgoingPlain) {
      wrapped = b4a.allocUnsafe(data.byteLength + 3 + ABYTES)
      wrapped.set(data, 4)
    } else {
      this._outgoingWrapped = this._outgoingPlain = null
    }

    if (wrapped.byteLength - 3 > MAX_ATOMIC_WRITE) {
      return cb(
        new Error(
          'Message is too large for an atomic write. Max size is ' + MAX_ATOMIC_WRITE + ' bytes.'
        )
      )
    }
    this.rawBytesWritten += wrapped.byteLength

    writeUint24le(wrapped.byteLength - 3, wrapped)
    // offset 4 so we can do it in-place
    this._encrypt.next(wrapped.subarray(4, 4 + data.byteLength), wrapped.subarray(3))

    if (this._keepAliveTimer !== null) this._keepAliveTimer.refresh()

    if (this._rawStream.write(wrapped) === false) {
      this._drainDone = cb
    } else {
      cb(null)
    }
  }

  _final(cb) {
    this._clearKeepAlive()
    this._ended--
    this._rawStream.end()
    cb(null)
  }

  _resolveOpened(val) {
    if (this._openedDone === null) return
    const opened = this._openedDone
    this._openedDone = null
    opened(val)
    if (!val) return
    this.connected = true
    this.emit('connect')
  }

  _clearTimeout() {
    if (this._timeoutTimer === null) return
    this._timeoutTimer.destroy()
    this._timeoutTimer = null
    this.timeout = 0
  }

  _clearKeepAlive() {
    if (this._keepAliveTimer === null) return
    this._keepAliveTimer.destroy()
    this._keepAliveTimer = null
    this.keepAlive = 0
  }

  _destroy(cb) {
    this._clearKeepAlive()
    this._clearTimeout()
    this._resolveOpened(false)
    cb(null)
  }

  _boxMessage(buffer) {
    const MB = sodium.crypto_secretbox_MACBYTES // 16
    const NB = sodium.crypto_secretbox_NONCEBYTES // 24

    const counter = this._sendState.subarray(64, 72)
    sodium.sodium_increment(counter)
    if (b4a.equals(counter, this._sendState.subarray(72))) {
      this.destroy(new Error('udp send nonce exchausted'))
      return
    }

    const secret = this._sendState.subarray(0, 32)
    const envelope = b4a.allocUnsafe(8 + MB + buffer.byteLength)
    const nonce = envelope.subarray(0, NB)
    const ciphertext = envelope.subarray(8)

    b4a.fill(nonce, 0) // pad suffix
    nonce.set(counter)

    sodium.crypto_secretbox_easy(ciphertext, buffer, nonce, secret)
    return envelope
  }

  send(buffer) {
    if (!this._sendState) return
    if (!this.rawStream?.send) return // udx-stream expected

    const message = this._boxMessage(buffer)
    return this.rawStream.send(message)
  }

  trySend(buffer) {
    if (!this._sendState) return
    if (!this.rawStream?.trySend) return // udx-stream expected

    const message = this._boxMessage(buffer)
    this.rawStream.trySend(message)
  }

  _onmessage(buffer) {
    if (!this._sendState) return // messages before handshake are dropped

    const MB = sodium.crypto_secretbox_MACBYTES // 16
    const NB = sodium.crypto_secretbox_NONCEBYTES // 24

    if (buffer.byteLength < NB) return // Invalid message

    const nonce = b4a.allocUnsafe(NB)
    b4a.fill(nonce, 0)
    nonce.set(buffer.subarray(0, 8))

    const secret = this._sendState.subarray(32, 64)
    const ciphertext = buffer.subarray(8)
    const plain = buffer.subarray(8, buffer.byteLength - MB)

    if (ciphertext.byteLength < MB) return // invalid message

    const success = sodium.crypto_secretbox_open_easy(plain, ciphertext, nonce, secret)

    if (success) this.emit('message', plain)
  }

  alloc(len) {
    const buf = b4a.allocUnsafe(len + 3 + ABYTES)
    this._outgoingWrapped = buf
    this._outgoingPlain = buf.subarray(4, buf.byteLength - ABYTES + 1)
    return this._outgoingPlain
  }

  toJSON() {
    return {
      isInitiator: this.isInitiator,
      publicKey: this.publicKey && b4a.toString(this.publicKey, 'hex'),
      remotePublicKey: this.remotePublicKey && b4a.toString(this.remotePublicKey, 'hex'),
      connected: this.connected,
      destroying: this.destroying,
      destroyed: this.destroyed,
      rawStream: this.rawStream && this.rawStream.toJSON ? this.rawStream.toJSON() : null
    }
  }
}

function writeUint24le(n, buf) {
  buf[0] = n & 255
  buf[1] = (n >>> 8) & 255
  buf[2] = (n >>> 16) & 255
}

function streamId(handshakeHash, isInitiator, out = b4a.allocUnsafe(32)) {
  sodium.crypto_generichash(out, isInitiator ? NS_INITIATOR : NS_RESPONDER, handshakeHash)
  return out
}

function toBuffer(data) {
  return typeof data === 'string' ? b4a.from(data) : data
}

function destroyTimeout() {
  this.destroy(new Error('Stream timed out'))
}

function sendKeepAlive() {
  const empty = this.alloc(0)
  this.write(empty)
}
const { Duplex, Writable } = require('streamx')

class ReversePassThrough extends Duplex {
  constructor(s) {
    super()
    this._stream = s
    this._ondrain = null
  }

  _write(data, cb) {
    if (this._stream.push(data) === false) {
      this._stream._ondrain = cb
    } else {
      cb(null)
    }
  }

  _final(cb) {
    this._stream.push(null)
    cb(null)
  }

  _read(cb) {
    const ondrain = this._ondrain
    this._ondrain = null
    if (ondrain) ondrain()
    cb(null)
  }
}

module.exports = class Bridge extends Duplex {
  constructor(noiseStream) {
    super()

    this.noiseStream = noiseStream

    this._ondrain = null
    this.reverse = new ReversePassThrough(this)
  }

  get publicKey() {
    return this.noiseStream.publicKey
  }

  get remotePublicKey() {
    return this.noiseStream.remotePublicKey
  }

  get handshakeHash() {
    return this.noiseStream.handshakeHash
  }

  flush() {
    return Writable.drained(this)
  }

  _read(cb) {
    const ondrain = this._ondrain
    this._ondrain = null
    if (ondrain) ondrain()
    cb(null)
  }

  _write(data, cb) {
    if (this.reverse.push(data) === false) {
      this.reverse._ondrain = cb
    } else {
      cb(null)
    }
  }

  _final(cb) {
    this.reverse.push(null)
    cb(null)
  }
}
const sodium = require('sodium-universal')
const curve = require('noise-curve-ed')
const Noise = require('noise-handshake')
const b4a = require('b4a')

const EMPTY = b4a.alloc(0)

module.exports = class Handshake {
  constructor(isInitiator, keyPair, remotePublicKey, pattern) {
    this.isInitiator = isInitiator
    this.keyPair = keyPair
    this.noise = new Noise(pattern, isInitiator, keyPair, { curve })
    this.noise.initialise(EMPTY, remotePublicKey)
    this.destroyed = false
  }

  static keyPair(seed) {
    const publicKey = b4a.alloc(32)
    const secretKey = b4a.alloc(64)
    if (seed) sodium.crypto_sign_seed_keypair(publicKey, secretKey, seed)
    else sodium.crypto_sign_keypair(publicKey, secretKey)
    return { publicKey, secretKey }
  }

  recv(data) {
    try {
      this.noise.recv(data)
      if (this.noise.complete) return this._return(null)
      return this.send()
    } catch {
      this.destroy()
      return null
    }
  }

  // note that the data returned here is framed so we don't have to do an extra copy
  // when sending it...
  send() {
    try {
      const data = this.noise.send()
      const wrap = b4a.allocUnsafe(data.byteLength + 3)

      writeUint24le(data.byteLength, wrap)
      wrap.set(data, 3)

      return this._return(wrap)
    } catch {
      this.destroy()
      return null
    }
  }

  destroy() {
    if (this.destroyed) return
    this.destroyed = true
  }

  _return(data) {
    const tx = this.noise.complete ? b4a.toBuffer(this.noise.tx) : null
    const rx = this.noise.complete ? b4a.toBuffer(this.noise.rx) : null
    const hash = this.noise.complete ? b4a.toBuffer(this.noise.hash) : null
    const remotePublicKey = this.noise.complete ? b4a.toBuffer(this.noise.rs) : null

    return {
      data,
      remotePublicKey,
      hash,
      tx,
      rx
    }
  }
}

function writeUint24le(n, buf) {
  buf[0] = n & 255
  buf[1] = (n >>> 8) & 255
  buf[2] = (n >>> 16) & 255
}
{
  "name": "@hyperswarm/secret-stream",
  "version": "6.9.1",
  "description": "Secret stream backed by Noise and libsodium's secretstream",
  "main": "index.js",
  "files": [
    "index.js",
    "lib/**.js"
  ],
  "dependencies": {
    "b4a": "^1.1.0",
    "hypercore-crypto": "^3.3.1",
    "noise-curve-ed": "^2.0.1",
    "noise-handshake": "^4.0.0",
    "sodium-secretstream": "^1.1.0",
    "sodium-universal": "^5.0.0",
    "streamx": "^2.14.0",
    "timeout-refresh": "^2.0.0",
    "unslab": "^1.3.0"
  },
  "devDependencies": {
    "brittle": "^3.3.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^2.0.0",
    "udx-native": "^1.13.2"
  },
  "scripts": {
    "format": "prettier --write .",
    "lint": "prettier --check .",
    "test": "brittle test.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/holepunchto/hyperswarm-secret-stream.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/hyperswarm-secret-stream/issues"
  },
  "homepage": "https://github.com/holepunchto/hyperswarm-secret-stream"
}
const Cache = require('xache')

module.exports = class AdaptiveTimeout {
  constructor(opts = {}) {
    this._cache = new Cache({
      maxSize: opts.maxSize || 65536,
      maxAge: opts.maxAge || 10 * 60 * 1000 // 10 minutes
    })
    this._fallback = opts.fallback || AdaptiveTimeout.TimeoutExponential
    this._min = opts.min ?? 300
    this._max = opts.max || 4000
    this._jitter = opts.jitter ?? 256
  }

  // Default - aggressive ramp
  static TimeoutAggressive = [500, 750, 1000, 1500, 2000]

  // Linear - steady increase
  static TimeoutLinear = [500, 1000, 1500, 2000, 2500]

  // Exponential - slow start, rapid backoff
  static TimeoutExponential = [250, 500, 1000, 2000, 4000]

  // Gentle - conservative, patient
  static TimeoutGentle = [1000, 1250, 1500, 1750, 2000]

  // Fast - rapid fire retries
  static TimeoutFast = [200, 400, 600, 800, 1000]

  // U-shape - long, short, long
  static TimeoutUShape = [1500, 750, 500, 750, 1500]

  // Inverse U - short, long, short
  static TimeoutInverseU = [500, 1000, 1500, 1000, 500]

  // Sawtooth - alternating fast/slow
  static TimeoutSawtooth = [500, 1500, 500, 1500, 500]

  // Plateau - quick ramp then steady
  static TimeoutPlateau = [500, 1000, 2000, 2000, 2000]

  // Logarithmic - diminishing increases
  static TimeoutLogarithmic = [500, 1000, 1300, 1500, 1600]

  getValue(key) {
    return this._cache.get(key)
  }

  put(key, value) {
    let p = this._cache.get(key)

    if (!p) {
      p = { avg: value, variance: value >> 1 }
    } else {
      // blend 75% old + 25% new deviation
      p.variance += (Math.abs(p.avg - value) - p.variance) >> 2
      // blend 87.5% old + 12.5% new sample
      p.avg += (value - p.avg) >> 3
    }

    this._cache.set(key, p)

    return p
  }

  get(key, attempt = 1) {
    const p = this._cache.get(key)
    const jitter = (Math.random() * this._jitter) | 0

    if (p) {
      // known - use adaptive with linear backoff
      const base = p.avg + (p.variance << 1)
      const backoff = base * attempt
      return Math.min(Math.max(backoff + jitter, this._min), this._max)
    } else {
      // unknown - use aggressive fallback
      const base = this._fallback[Math.min(attempt - 1, this._fallback.length - 1)]
      return base + jitter
    }
  }

  has(key) {
    return this._cache.has(key)
  }

  delete(key) {
    return this._cache.delete(key)
  }

  clear() {
    this._cache.clear()
  }
}
{
  "name": "adaptive-timeout",
  "version": "1.0.1",
  "description": "adaptive-timeout",
  "main": "index.js",
  "exports": {
    "./package": "./package.json",
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    }
  },
  "files": [
    "package.json",
    "index.js",
    "index.d.ts"
  ],
  "devDependencies": {
    "brittle": "^3.19.0",
    "lunte": "^1.2.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^2.0.0"
  },
  "scripts": {
    "format": "prettier . --write",
    "test": "prettier . --check && lunte && brittle-bare test/index.js",
    "lint": "lunte"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/holepunchto/adaptive-timeout.git"
  },
  "author": "Holepunch Inc",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/adaptive-timeout/issues"
  },
  "homepage": "https://github.com/holepunchto/adaptive-timeout",
  "dependencies": {
    "xache": "^1.2.1"
  }
}
function isBuffer(value) {
  return Buffer.isBuffer(value) || value instanceof Uint8Array
}

function isEncoding(encoding) {
  return Buffer.isEncoding(encoding)
}

function alloc(size, fill, encoding) {
  return Buffer.alloc(size, fill, encoding)
}

function allocUnsafe(size) {
  return Buffer.allocUnsafe(size)
}

function allocUnsafeSlow(size) {
  return Buffer.allocUnsafeSlow(size)
}

function byteLength(string, encoding) {
  return Buffer.byteLength(string, encoding)
}

function compare(a, b) {
  return Buffer.compare(a, b)
}

function concat(buffers, totalLength) {
  return Buffer.concat(buffers, totalLength)
}

function copy(source, target, targetStart, start, end) {
  return toBuffer(source).copy(target, targetStart, start, end)
}

function equals(a, b) {
  return toBuffer(a).equals(b)
}

function fill(buffer, value, offset, end, encoding) {
  return toBuffer(buffer).fill(value, offset, end, encoding)
}

function from(value, encodingOrOffset, length) {
  return Buffer.from(value, encodingOrOffset, length)
}

function includes(buffer, value, byteOffset, encoding) {
  return toBuffer(buffer).includes(value, byteOffset, encoding)
}

function indexOf(buffer, value, byfeOffset, encoding) {
  return toBuffer(buffer).indexOf(value, byfeOffset, encoding)
}

function lastIndexOf(buffer, value, byteOffset, encoding) {
  return toBuffer(buffer).lastIndexOf(value, byteOffset, encoding)
}

function swap16(buffer) {
  return toBuffer(buffer).swap16()
}

function swap32(buffer) {
  return toBuffer(buffer).swap32()
}

function swap64(buffer) {
  return toBuffer(buffer).swap64()
}

function toBuffer(buffer) {
  if (Buffer.isBuffer(buffer)) return buffer
  return Buffer.from(buffer.buffer, buffer.byteOffset, buffer.byteLength)
}

function toString(buffer, encoding, start, end) {
  return toBuffer(buffer).toString(encoding, start, end)
}

function write(buffer, string, offset, length, encoding) {
  return toBuffer(buffer).write(string, offset, length, encoding)
}

function readDoubleBE(buffer, offset) {
  return toBuffer(buffer).readDoubleBE(offset)
}

function readDoubleLE(buffer, offset) {
  return toBuffer(buffer).readDoubleLE(offset)
}

function readFloatBE(buffer, offset) {
  return toBuffer(buffer).readFloatBE(offset)
}

function readFloatLE(buffer, offset) {
  return toBuffer(buffer).readFloatLE(offset)
}

function readInt32BE(buffer, offset) {
  return toBuffer(buffer).readInt32BE(offset)
}

function readInt32LE(buffer, offset) {
  return toBuffer(buffer).readInt32LE(offset)
}

function readUInt32BE(buffer, offset) {
  return toBuffer(buffer).readUInt32BE(offset)
}

function readUInt32LE(buffer, offset) {
  return toBuffer(buffer).readUInt32LE(offset)
}

function writeDoubleBE(buffer, value, offset) {
  return toBuffer(buffer).writeDoubleBE(value, offset)
}

function writeDoubleLE(buffer, value, offset) {
  return toBuffer(buffer).writeDoubleLE(value, offset)
}

function writeFloatBE(buffer, value, offset) {
  return toBuffer(buffer).writeFloatBE(value, offset)
}

function writeFloatLE(buffer, value, offset) {
  return toBuffer(buffer).writeFloatLE(value, offset)
}

function writeInt32BE(buffer, value, offset) {
  return toBuffer(buffer).writeInt32BE(value, offset)
}

function writeInt32LE(buffer, value, offset) {
  return toBuffer(buffer).writeInt32LE(value, offset)
}

function writeUInt32BE(buffer, value, offset) {
  return toBuffer(buffer).writeUInt32BE(value, offset)
}

function writeUInt32LE(buffer, value, offset) {
  return toBuffer(buffer).writeUInt32LE(value, offset)
}

module.exports = {
  isBuffer,
  isEncoding,
  alloc,
  allocUnsafe,
  allocUnsafeSlow,
  byteLength,
  compare,
  concat,
  copy,
  equals,
  fill,
  from,
  includes,
  indexOf,
  lastIndexOf,
  swap16,
  swap32,
  swap64,
  toBuffer,
  toString,
  write,
  readDoubleBE,
  readDoubleLE,
  readFloatBE,
  readFloatLE,
  readInt32BE,
  readInt32LE,
  readUInt32BE,
  readUInt32LE,
  writeDoubleBE,
  writeDoubleLE,
  writeFloatBE,
  writeFloatLE,
  writeInt32BE,
  writeInt32LE,
  writeUInt32BE,
  writeUInt32LE
}
{
  "name": "b4a",
  "version": "1.8.1",
  "description": "Bridging the gap between buffers and typed arrays",
  "exports": {
    "./package": "./package.json",
    ".": {
      "react-native": "./react-native.js",
      "browser": "./browser.js",
      "default": "./index.js"
    }
  },
  "files": [
    "browser.js",
    "index.js",
    "react-native.js",
    "lib"
  ],
  "scripts": {
    "format": "prettier --write . && lunte --fix",
    "lint": "prettier --check . && lunte",
    "test": "npm run test:bare && npm run test:node",
    "test:bare": "brittle-bare --coverage test.mjs",
    "test:node": "brittle-node --coverage test.mjs"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/b4a.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/b4a/issues"
  },
  "homepage": "https://github.com/holepunchto/b4a#readme",
  "devDependencies": {
    "brittle": "^3.5.2",
    "lunte": "^1.7.0",
    "nanobench": "^3.0.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^2.0.0"
  },
  "peerDependencies": {
    "react-native-b4a": "*"
  },
  "peerDependenciesMeta": {
    "react-native-b4a": {
      "optional": true
    }
  }
}
module.exports = require.addon()
const binding = require('./binding')

module.exports = binding.abort
{
  "name": "bare-abort",
  "version": "2.0.13",
  "description": "Cause abnormal program termination and generate a crash report",
  "exports": {
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    },
    "./package": "./package.json"
  },
  "files": [
    "index.js",
    "index.d.ts",
    "binding.c",
    "binding.js",
    "CMakeLists.txt",
    "prebuilds"
  ],
  "addon": true,
  "scripts": {
    "test": "prettier . --check"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-abort.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-abort/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-abort#readme",
  "devDependencies": {
    "cmake-bare": "^1.1.7",
    "prettier": "^3.4.2",
    "prettier-config-standard": "^7.0.0"
  }
}
const ESC = '\x1b'
const CSI = ESC + '['
const SGR = (n) => CSI + n + 'm'

exports.constants = {
  ESC,
  CSI,
  SGR
}

// https://en.wikipedia.org/wiki/ANSI_escape_code#CSI_(Control_Sequence_Introducer)_sequences

exports.cursorHide = CSI + '?25l'
exports.cursorShow = CSI + '?25h'

exports.cursorUp = function cursorUp(n = 1) {
  return CSI + n + 'A'
}

exports.cursorDown = function cursorDown(n = 1) {
  return CSI + n + 'B'
}

exports.cursorForward = function cursorForward(n = 1) {
  return CSI + n + 'C'
}

exports.cursorBack = function cursorBack(n = 1) {
  return CSI + n + 'D'
}

exports.cursorNextLine = function cursorNextLine(n = 1) {
  return CSI + n + 'E'
}

exports.cursorPreviousLine = function cursorPreviousLine(n = 1) {
  return CSI + n + 'F'
}

exports.cursorPosition = function cursorPosition(column, row = 0) {
  if (row === 0) return CSI + (column + 1) + 'G'

  return CSI + (row + 1) + ';' + (column + 1) + 'H'
}

exports.eraseDisplayEnd = CSI + 'J'
exports.eraseDisplayStart = CSI + '1J'
exports.eraseDisplay = CSI + '2J'
exports.eraseLineEnd = CSI + 'K'
exports.eraseLineStart = CSI + '1K'
exports.eraseLine = CSI + '2K'

exports.scrollUp = function scrollUp(n = 1) {
  return CSI + n + 'S'
}

exports.scrollDown = function scrollDown(n = 1) {
  return CSI + n + 'T'
}

// https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_(Select_Graphic_Rendition)_parameters

exports.modifierReset = SGR(0)
exports.modifierBold = SGR(1)
exports.modifierDim = SGR(2)
exports.modifierItalic = SGR(3)
exports.modifierUnderline = SGR(4)
exports.modifierNormal = SGR(22)
exports.modifierNotItalic = SGR(23)
exports.modifierNotUnderline = SGR(24)

// https://en.wikipedia.org/wiki/ANSI_escape_code#Colors

exports.colorBlack = SGR(30)
exports.colorRed = SGR(31)
exports.colorGreen = SGR(32)
exports.colorYellow = SGR(33)
exports.colorBlue = SGR(34)
exports.colorMagenta = SGR(35)
exports.colorCyan = SGR(36)
exports.colorWhite = SGR(37)
exports.colorDefault = SGR(39)
exports.colorBrightBlack = SGR(90)
exports.colorBrightRed = SGR(91)
exports.colorBrightGreen = SGR(92)
exports.colorBrightYellow = SGR(93)
exports.colorBrightBlue = SGR(94)
exports.colorBrightMagenta = SGR(95)
exports.colorBrightCyan = SGR(96)
exports.colorBrightWhite = SGR(97)
{
  "name": "bare-ansi-escapes",
  "version": "2.2.3",
  "description": "Parse and produce ANSI escape sequences",
  "exports": {
    "./package": "./package.json",
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    },
    "./key-decoder": {
      "types": "./key-decoder.d.ts",
      "default": "./key-decoder.js"
    }
  },
  "files": [
    "index.js",
    "index.d.ts",
    "key-decoder.js",
    "key-decoder.d.ts"
  ],
  "scripts": {
    "test": "prettier . --check && bare test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-ansi-escapes.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-ansi-escapes/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-ansi-escapes#readme",
  "dependencies": {
    "bare-stream": "^2.6.5"
  },
  "devDependencies": {
    "bare-buffer": "^3.0.2",
    "brittle": "^3.3.2",
    "prettier": "^3.4.2",
    "prettier-config-standard": "^7.0.0"
  },
  "peerDependencies": {
    "bare-buffer": "*"
  },
  "peerDependenciesMeta": {
    "bare-buffer": {
      "optional": true
    }
  }
}
const inspect = require('bare-inspect')

class AssertionError extends Error {
  constructor(opts = {}) {
    let { message = null, actual, expected, operator } = opts

    if (message === null) {
      message = `${inspect(actual)} ${operator} ${inspect(expected)}`
    }

    super(message)

    this.actual = actual
    this.expected = expected
    this.operator = operator
  }

  get name() {
    return 'AssertionError'
  }

  get code() {
    'ASSERTION'
  }
}

function assertFail(opts, fn) {
  if (opts.message instanceof Error) throw opts.message

  const err = new AssertionError(opts)

  if (Error.captureStackTrace) Error.captureStackTrace(err, fn)

  throw err
}

module.exports = exports = function assert(actual, message) {
  if (actual) return

  assertFail({ message, actual, expected: true, operator: '==' }, assert)
}

exports.AssertionError = AssertionError

exports.fail = function fail(message) {
  if (message === undefined) message = 'Failed'

  assertFail({ message, operator: 'fail' }, fail)
}

exports.ok = function ok(actual, message) {
  if (actual) return

  assertFail({ message, actual, expected: true, operator: '==' }, ok)
}

exports.notOk = function ok(actual, message) {
  if (!actual) return

  assertFail({ message, actual, expected: false, operator: '==' }, ok)
}

exports.equal = function equal(actual, expected, message) {
  if (actual == expected || (actual !== actual && expected !== expected)) {
    return
  }

  assertFail({ message, actual, expected, operator: '==' }, equal)
}

exports.notEqual = function notEqual(actual, expected, message) {
  if (actual != expected && (actual === actual || expected === expected)) {
    return
  }

  assertFail({ message, actual, expected, operator: '!=' }, notEqual)
}

exports.strictEqual = function strictEqual(actual, expected, message) {
  if (Object.is(actual, expected)) return

  assertFail(
    { message, actual, expected, operator: 'strictEqual' },
    strictEqual
  )
}

exports.notStrictEqual = function notStrictEqual(actual, expected, message) {
  if (!Object.is(actual, expected)) return

  assertFail(
    { message, actual, expected, operator: 'notStrictEqual' },
    notStrictEqual
  )
}
{
  "name": "bare-assert",
  "version": "1.2.0",
  "description": "Assertion library for JavaScript",
  "exports": {
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    },
    "./package": "./package.json"
  },
  "files": [
    "index.js",
    "index.d.ts"
  ],
  "scripts": {
    "test": "prettier . --check && bare test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-assert.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-assert/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-assert#readme",
  "dependencies": {
    "bare-inspect": "^3.1.2"
  },
  "devDependencies": {
    "brittle": "^3.1.1",
    "prettier": "^3.4.2",
    "prettier-config-standard": "^7.0.0"
  }
}
module.exports = require.addon()
const binding = require('./binding')

exports.Resolver = class DNSResolver {
  constructor() {
    this._handle = binding.initResolver()
  }

  resolveTxt(hostname, cb = noop) {
    binding.resolveTxt(this._handle, hostname, cb, this)
  }

  destroy() {
    binding.destroyResolver(this._handle)
    this._handle = null
  }

  static global = new this()
}

function onlookup(err, addresses) {
  const req = this

  if (err) return req.cb(err, null, 0)

  const { address, family } = addresses[0]

  return req.cb(null, address, family)
}

function onlookupall(err, addresses) {
  const req = this

  if (err) return req.cb(err, null)

  return req.cb(null, addresses)
}

exports.lookup = function lookup(hostname, opts = {}, cb) {
  if (typeof opts === 'function') {
    cb = opts
    opts = {}
  }

  let { family = 0, all = false } = opts

  if (typeof family === 'string') {
    switch (family) {
      case 'IPv4':
        family = 4
        break
      case 'IPv6':
        family = 6
        break
      default:
        family = 0
    }
  }

  const req = {
    cb,
    handle: null
  }

  req.handle = binding.lookup(
    hostname,
    family || 0,
    all,
    req,
    all ? onlookupall : onlookup
  )
}

exports.resolveTxt = function resolveTxt(hostname, cb) {
  exports.Resolver.global.resolveTxt(hostname, cb)
}

function noop() {}
{
  "name": "bare-dns",
  "version": "2.1.4",
  "description": "Domain name resolution for JavaScript",
  "exports": {
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    },
    "./package": "./package.json"
  },
  "files": [
    "index.js",
    "index.d.ts",
    "binding.c",
    "binding.js",
    "CMakeLists.txt",
    "lib",
    "prebuilds"
  ],
  "addon": true,
  "scripts": {
    "test": "prettier . --check && bare test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-dns.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-dns/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-dns",
  "engines": {
    "bare": ">=1.7.0"
  },
  "devDependencies": {
    "brittle": "^3.3.2",
    "cmake-bare": "^1.1.6",
    "cmake-fetch": "^1.3.0",
    "prettier": "^3.4.1",
    "prettier-config-standard": "^7.0.0"
  }
}
const os = require('bare-os')

module.exports = new Proxy(Object.create(null), {
  ownKeys (target) {
    return os.getEnvKeys()
  },

  get (target, property) {
    if (typeof property !== 'string') return

    return os.getEnv(property)
  },

  has (target, property) {
    if (typeof property !== 'string') return false

    return os.hasEnv(property)
  },

  set (target, property, value) {
    if (typeof property !== 'string') return

    const type = typeof value

    if (type !== 'string' && type !== 'number' && type !== 'boolean') {
      throw new Error('Environment variable must be of type string, number, or boolean')
    }

    value = String(value)

    os.setEnv(property, value)

    return true
  },

  deleteProperty (target, property) {
    if (typeof property !== 'string') return

    os.unsetEnv(property)
  },

  getOwnPropertyDescriptor (target, property) {
    return {
      value: this.get(target, property),
      enumerable: true,
      configurable: true,
      writable: true
    }
  }
})
{
  "name": "bare-env",
  "version": "3.0.0",
  "description": "Environment variable support for JavaScript",
  "main": "index.js",
  "files": [
    "index.js"
  ],
  "scripts": {
    "test": "standard && bare test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-env.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-env/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-env#readme",
  "dependencies": {
    "bare-os": "^3.0.1"
  },
  "devDependencies": {
    "brittle": "^3.2.1",
    "standard": "^17.0.0"
  }
}
const errors = require('./lib/errors')

class EventListener {
  constructor() {
    this.list = []
    this.count = 0
  }

  append(ctx, name, fn, once) {
    this.count++
    ctx.emit('newListener', name, fn) // Emit BEFORE adding
    this.list.push([fn, once])
  }

  prepend(ctx, name, fn, once) {
    this.count++
    ctx.emit('newListener', name, fn) // Emit BEFORE adding
    this.list.unshift([fn, once])
  }

  remove(ctx, name, fn) {
    for (let i = 0, n = this.list.length; i < n; i++) {
      const l = this.list[i]

      if (l[0] === fn) {
        this.list.splice(i, 1)

        if (this.count === 1) delete ctx._events[name]

        ctx.emit('removeListener', name, fn) // Emit AFTER removing

        this.count--
        return
      }
    }
  }

  removeAll(ctx, name) {
    const list = [...this.list]
    this.list = []

    if (this.count === list.length) delete ctx._events[name]

    for (let i = list.length - 1; i >= 0; i--) {
      ctx.emit('removeListener', name, list[i][0]) // Emit AFTER removing
    }

    this.count -= list.length
  }

  emit(ctx, name, ...args) {
    const list = [...this.list]

    for (let i = 0, n = list.length; i < n; i++) {
      const l = list[i]

      if (l[1] === true) this.remove(ctx, name, l[0])

      Reflect.apply(l[0], ctx, args)
    }

    return list.length > 0
  }
}

function appendListener(ctx, name, fn, once) {
  if (ctx._events === undefined) ctx._events = Object.create(null)
  const e = ctx._events[name] || (ctx._events[name] = new EventListener())
  e.append(ctx, name, fn, once)
  return ctx
}

function prependListener(ctx, name, fn, once) {
  if (ctx._events === undefined) ctx._events = Object.create(null)
  const e = ctx._events[name] || (ctx._events[name] = new EventListener())
  e.prepend(ctx, name, fn, once)
  return ctx
}

function removeListener(ctx, name, fn) {
  if (ctx._events === undefined) return ctx
  const e = ctx._events[name]
  if (e !== undefined) e.remove(ctx, name, fn)
  return ctx
}

function throwUnhandledError(...args) {
  let err

  if (args.length > 0) err = args[0]

  if (err instanceof Error === false) err = errors.UNHANDLED_ERROR(err)

  if (Error.captureStackTrace) {
    Error.captureStackTrace(err, exports.prototype.emit)
  }

  queueMicrotask(() => {
    throw err
  })
}

module.exports = exports = class EventEmitter {
  constructor() {
    this._events = Object.create(null)
  }

  addListener(name, fn) {
    return appendListener(this, name, fn, false)
  }

  addOnceListener(name, fn) {
    return appendListener(this, name, fn, true)
  }

  prependListener(name, fn) {
    return prependListener(this, name, fn, false)
  }

  prependOnceListener(name, fn) {
    return prependListener(this, name, fn, true)
  }

  removeListener(name, fn) {
    return removeListener(this, name, fn)
  }

  on(name, fn) {
    return appendListener(this, name, fn, false)
  }

  once(name, fn) {
    return appendListener(this, name, fn, true)
  }

  off(name, fn) {
    return removeListener(this, name, fn)
  }

  emit(name, ...args) {
    if (name === 'error' && this._events !== undefined && this._events.error === undefined) {
      throwUnhandledError(...args)
    }

    if (this._events === undefined) return false
    const e = this._events[name]
    return e === undefined ? false : e.emit(this, name, ...args)
  }

  listeners(name) {
    if (this._events === undefined) return []
    const e = this._events[name]
    return e === undefined ? [] : [...e.list]
  }

  listenerCount(name) {
    if (this._events === undefined) return 0
    const e = this._events[name]
    return e === undefined ? 0 : e.list.length
  }

  getMaxListeners() {
    return EventEmitter.defaultMaxListeners
  }

  setMaxListeners(n) {}

  removeAllListeners(name) {
    if (arguments.length === 0) {
      for (const key of Reflect.ownKeys(this._events)) {
        if (key === 'removeListener') continue
        this.removeAllListeners(key)
      }
      this.removeAllListeners('removeListener')
    } else {
      const e = this._events[name]
      if (e !== undefined) e.removeAll(this, name)
    }
    return this
  }
}

exports.EventEmitter = exports

exports.errors = errors

exports.defaultMaxListeners = 10

exports.on = function on(emitter, name, opts = {}) {
  const { signal } = opts

  if (signal && signal.aborted) {
    throw errors.OPERATION_ABORTED(signal.reason)
  }

  let error = null
  let done = false

  const events = []
  const promises = []

  if (name !== 'error') emitter.on('error', onerror)

  if (signal) signal.addEventListener('abort', onabort)

  emitter.on(name, onevent)

  return {
    next() {
      if (events.length) {
        return Promise.resolve({ value: events.shift(), done: false })
      }

      if (error) {
        const err = error

        error = null

        return Promise.reject(err)
      }

      if (done) return onclose()

      return new Promise((resolve, reject) => promises.push({ resolve, reject }))
    },

    return() {
      return onclose()
    },

    throw(err) {
      return onerror(err)
    },

    [Symbol.asyncIterator]() {
      return this
    }
  }

  function onevent(...args) {
    if (promises.length) {
      promises.shift().resolve({ value: args, done: false })
    } else {
      events.push(args)
    }
  }

  function onerror(err) {
    emitter.off(name, onevent).off('error', onerror)

    if (promises.length) {
      promises.shift().reject(err)
    } else {
      error = err
    }

    return Promise.resolve({ done: true })
  }

  function onabort() {
    signal.removeEventListener('abort', onabort)

    onerror(errors.OPERATION_ABORTED(signal.reason))
  }

  function onclose() {
    emitter.off(name, onevent)

    if (name !== 'error') emitter.off('error', onerror)

    if (signal) signal.removeEventListener('abort', onabort)

    done = true

    if (promises.length) promises.shift().resolve({ done: true })

    return Promise.resolve({ done: true })
  }
}

exports.once = function once(emitter, name, opts = {}) {
  const { signal } = opts

  if (signal && signal.aborted) {
    return Promise.reject(errors.OPERATION_ABORTED(signal.reason))
  }

  return new Promise((resolve, reject) => {
    if (name !== 'error') emitter.on('error', onerror)

    if (signal) signal.addEventListener('abort', onabort)

    emitter.once(name, onevent)

    function onevent(...args) {
      if (name !== 'error') emitter.off('error', onerror)

      if (signal) signal.removeEventListener('abort', onabort)

      resolve(args)
    }

    function onerror(err) {
      emitter.off(name, onevent)

      if (name !== 'error') emitter.off('error', onerror)

      reject(err)
    }

    function onabort() {
      signal.removeEventListener('abort', onabort)

      onerror(errors.OPERATION_ABORTED(signal.reason))
    }
  })
}

exports.forward = function forward(from, to, names, opts = {}) {
  if (typeof names === 'string') names = [names]

  const { emit = to.emit.bind(to) } = opts

  const listeners = names.map(
    (name) =>
      function onevent(...args) {
        emit(name, ...args)
      }
  )

  to.on('newListener', (name) => {
    const i = names.indexOf(name)

    if (i !== -1 && to.listenerCount(name) === 0) {
      from.on(name, listeners[i])
    }
  }).on('removeListener', (name) => {
    const i = names.indexOf(name)

    if (i !== -1 && to.listenerCount(name) === 0) {
      from.off(name, listeners[i])
    }
  })
}

exports.listenerCount = function listenerCount(emitter, name) {
  return emitter.listenerCount(name)
}

exports.getMaxListeners = function getMaxListeners(emitter) {
  if (typeof emitter.getMaxListeners === 'function') {
    return emitter.getMaxListeners()
  }

  return exports.defaultMaxListeners
}

exports.setMaxListeners = function setMaxListeners(n, ...emitters) {
  if (emitters.length === 0) exports.defaultMaxListeners = n
  else {
    for (const emitter of emitters) {
      if (typeof emitter.setMaxListeners === 'function') {
        emitter.setMaxListeners(n)
      }
    }
  }
}
module.exports = class EventEmitterError extends Error {
  constructor(msg, code, fn = EventEmitterError, opts) {
    super(`${code}: ${msg}`, opts)
    this.code = code

    if (Error.captureStackTrace) {
      Error.captureStackTrace(this, fn)
    }
  }

  get name() {
    return 'EventEmitterError'
  }

  static OPERATION_ABORTED(cause, msg = 'Operation aborted') {
    return new EventEmitterError(msg, 'OPERATION_ABORTED', EventEmitterError.OPERATION_ABORTED, {
      cause
    })
  }

  static UNHANDLED_ERROR(cause, msg = 'Unhandled error') {
    return new EventEmitterError(msg, 'UNHANDLED_ERROR', EventEmitterError.UNHANDLED_ERROR, {
      cause
    })
  }
}
{
  "name": "bare-events",
  "version": "2.8.2",
  "description": "Event emitters for JavaScript",
  "exports": {
    "./package": "./package.json",
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    },
    "./global": {
      "types": "./global.d.ts",
      "default": "./global.js"
    },
    "./web": {
      "types": "./web.d.ts",
      "default": "./web.js"
    },
    "./errors": "./lib/errors.js"
  },
  "files": [
    "index.js",
    "index.d.ts",
    "global.js",
    "global.d.ts",
    "web.js",
    "web.d.ts",
    "lib"
  ],
  "scripts": {
    "test": "npm run lint && npm run test:bare && npm run test:node",
    "test:bare": "bare test.js",
    "test:node": "node test.js",
    "lint": "prettier . --check"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-events.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-events/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-events#readme",
  "devDependencies": {
    "bare-abort-controller": "^1.0.0",
    "brittle": "^3.3.2",
    "prettier": "^3.4.2",
    "prettier-config-holepunch": "^2.0.0",
    "uncaughts": "^1.1.1"
  },
  "peerDependencies": {
    "bare-abort-controller": "*"
  },
  "peerDependenciesMeta": {
    "bare-abort-controller": {
      "optional": true
    }
  }
}
module.exports = require.addon()
const FIFO = require('fast-fifo')
const EventEmitter = require('bare-events')
const path = require('bare-path')
const { isURL, fileURLToPath } = require('bare-url')
const { Readable, Writable } = require('bare-stream')
const binding = require('./binding')
const constants = require('./lib/constants')
const FileError = require('./lib/errors')

const isWindows = Bare.platform === 'win32'

exports.constants = constants

class FileRequest {
  static borrow() {
    if (this._free.length > 0) return this._free.pop()
    return new FileRequest()
  }

  static return(req) {
    if (this._free.length < 32) this._free.push(req.reset())
    else req.destroy()
  }

  constructor() {
    this._reset()
    this._handle = binding.requestInit(this, this._onresult)
  }

  get handle() {
    return this._handle
  }

  retain(value) {
    this._retain = value // Tie the lifetime of `value` to the lifetime of `this`
  }

  reset() {
    if (this._handle === null) return this

    binding.requestReset(this._handle)

    this._reset()

    return this
  }

  destroy() {
    if (this._handle === null) return this

    binding.requestDestroy(this._handle)

    this._reset()
    this._handle = null

    return this
  }

  then(resolve, reject) {
    return this._promise.then(resolve, reject)
  }

  return() {
    if (this._handle === null) return this

    FileRequest.return(this)

    return this
  }

  _reset() {
    this._promise = new Promise((resolve, reject) => {
      this._resolve = resolve
      this._reject = reject
    })
    this._retain = null
  }

  _onresult(err, status) {
    if (err) this._reject(err)
    else this._resolve(status)
  }
}

FileRequest._free = []

function ok(result, cb) {
  if (typeof result === 'function') {
    cb = result
    result = undefined
  }

  if (cb) cb(null, result)
  else return result
}

function fail(err, cb) {
  if (cb) cb(err)
  else throw err
}

function done(err, result, cb) {
  if (typeof result === 'function') {
    cb = result
    result = undefined
  }

  if (err) fail(err, cb)
  else return ok(result, cb)
}

async function open(filepath, flags = 'r', mode = 0o666, cb) {
  if (typeof flags === 'function') {
    cb = flags
    flags = 'r'
    mode = 0o666
  } else if (typeof mode === 'function') {
    cb = mode
    mode = 0o666
  }

  if (typeof flags === 'string') flags = toFlags(flags)
  if (typeof mode === 'string') mode = toMode(mode)

  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  let fd
  let err = null
  try {
    binding.open(req.handle, filepath, flags, mode)

    fd = await req
  } catch (e) {
    err = new FileError(e.message, {
      operation: 'open',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }

  return done(err, fd, cb)
}

function openSync(filepath, flags = 'r', mode = 0o666) {
  if (typeof flags === 'string') flags = toFlags(flags)
  if (typeof mode === 'string') mode = toMode(mode)

  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  try {
    return binding.openSync(req.handle, filepath, flags, mode)
  } catch (e) {
    throw new FileError(e.message, {
      operation: 'open',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }
}

async function close(fd, cb) {
  const req = FileRequest.borrow()

  let err = null
  try {
    binding.close(req.handle, fd)

    await req
  } catch (e) {
    err = new FileError(e.message, { operation: 'close', code: e.code, fd })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function closeSync(fd) {
  const req = FileRequest.borrow()

  try {
    binding.closeSync(req.handle, fd)
  } catch (e) {
    throw new FileError(e.message, { operation: 'close', code: e.code, fd })
  } finally {
    req.return()
  }
}

async function access(filepath, mode = constants.F_OK, cb) {
  if (typeof mode === 'function') {
    cb = mode
    mode = constants.F_OK
  }

  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  let err = null
  try {
    binding.access(req.handle, filepath, mode)

    await req
  } catch (e) {
    err = new FileError(e.message, {
      operation: 'access',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function accessSync(filepath, mode = constants.F_OK) {
  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  try {
    binding.accessSync(req.handle, filepath, mode)
  } catch (e) {
    throw new FileError(e.message, {
      operation: 'access',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }
}

async function exists(filepath, cb) {
  let ok = true
  try {
    await access(filepath)
  } catch {
    ok = false
  }

  return done(null, ok, cb)
}

function existsSync(filepath) {
  try {
    accessSync(filepath)
  } catch {
    return false
  }

  return true
}

async function read(fd, buffer, offset = 0, len = buffer.byteLength - offset, pos = -1, cb) {
  if (typeof offset === 'function') {
    cb = offset
    offset = 0
    len = buffer.byteLength
    pos = -1
  } else if (typeof len === 'function') {
    cb = len
    len = buffer.byteLength - offset
    pos = -1
  } else if (typeof pos === 'function') {
    cb = pos
    pos = -1
  }

  if (typeof pos !== 'number') pos = -1

  const req = FileRequest.borrow()

  let bytes
  let err = null
  try {
    binding.read(req.handle, fd, buffer, offset, len, pos)

    req.retain(buffer)

    bytes = await req
  } catch (e) {
    err = new FileError(e.message, { operation: 'read', code: e.code, fd })
  } finally {
    req.return()
  }

  return done(err, bytes, cb)
}

function readSync(fd, buffer, offset = 0, len = buffer.byteLength - offset, pos = -1) {
  const req = FileRequest.borrow()

  try {
    return binding.readSync(req.handle, fd, buffer, offset, len, pos)
  } catch (e) {
    throw new FileError(e.message, { operation: 'read', code: e.code, fd })
  } finally {
    req.return()
  }
}

async function readv(fd, buffers, pos = -1, cb) {
  if (typeof pos === 'function') {
    cb = pos
    pos = -1
  }

  if (typeof pos !== 'number') pos = -1

  const req = FileRequest.borrow()

  let bytes
  let err = null
  try {
    binding.readv(req.handle, fd, buffers, pos)

    req.retain(buffers)

    bytes = await req
  } catch (e) {
    err = new FileError(e.message, { operation: 'readv', code: e.code, fd })
  } finally {
    req.return()
  }

  return done(err, bytes, cb)
}

function readvSync(fd, buffers, pos = -1) {
  if (typeof pos !== 'number') pos = -1

  const req = FileRequest.borrow()

  try {
    return binding.readvSync(req.handle, fd, buffers, pos)
  } catch (e) {
    throw new FileError(e.message, { operation: 'readv', code: e.code, fd })
  } finally {
    req.return()
  }
}

async function write(fd, data, offset, len, pos = -1, cb) {
  if (typeof data === 'string') {
    let encoding = len
    cb = pos
    pos = offset

    if (typeof pos === 'function') {
      cb = pos
      pos = -1
      encoding = 'utf8'
    } else if (typeof encoding === 'function') {
      cb = encoding
      encoding = 'utf8'
    }

    if (typeof pos === 'string') {
      encoding = pos
      pos = -1
    }

    data = Buffer.from(data, encoding)
    offset = 0
    len = data.byteLength
  } else if (typeof offset === 'function') {
    cb = offset
    offset = 0
    len = data.byteLength
    pos = -1
  } else if (typeof len === 'function') {
    cb = len
    len = data.byteLength - offset
    pos = -1
  } else if (typeof pos === 'function') {
    cb = pos
    pos = -1
  }

  if (typeof offset !== 'number') offset = 0
  if (typeof len !== 'number') len = data.byteLength - offset
  if (typeof pos !== 'number') pos = -1

  const req = FileRequest.borrow()

  let bytes
  let err = null
  try {
    binding.write(req.handle, fd, data, offset, len, pos)

    req.retain(data)

    bytes = await req
  } catch (e) {
    err = new FileError(e.message, { operation: 'write', code: e.code, fd })
  } finally {
    req.return()
  }

  return done(err, bytes, cb)
}

function writeSync(fd, data, offset, len, pos = -1) {
  if (typeof data === 'string') {
    let encoding = len
    pos = offset

    if (typeof pos === 'string') {
      encoding = pos
      pos = -1
    }

    data = Buffer.from(data, encoding)
    offset = 0
    len = data.byteLength
  }

  if (typeof offset !== 'number') offset = 0
  if (typeof len !== 'number') len = data.byteLength - offset
  if (typeof pos !== 'number') pos = -1

  const req = FileRequest.borrow()

  try {
    return binding.writeSync(req.handle, fd, data, offset, len, pos)
  } catch (e) {
    throw new FileError(e.message, { operation: 'write', code: e.code, fd })
  } finally {
    req.return()
  }
}

async function writev(fd, buffers, pos = -1, cb) {
  if (typeof pos === 'function') {
    cb = pos
    pos = -1
  }

  if (typeof pos !== 'number') pos = -1

  const req = FileRequest.borrow()

  let bytes
  let err = null
  try {
    binding.writev(req.handle, fd, buffers, pos)

    req.retain(buffers)

    bytes = await req
  } catch (e) {
    err = new FileError(e.message, { operation: 'writev', code: e.code, fd })
  } finally {
    req.return()
  }

  return done(err, bytes, cb)
}

function writevSync(fd, buffers, pos = -1) {
  if (typeof pos !== 'number') pos = -1

  const req = FileRequest.borrow()

  try {
    return binding.writevSync(req.handle, fd, buffers, pos)
  } catch (e) {
    throw new FileError(e.message, { operation: 'writev', code: e.code, fd })
  } finally {
    req.return()
  }
}

async function stat(filepath, cb) {
  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  let st
  let err = null
  try {
    binding.stat(req.handle, filepath)

    await req

    st = new Stats(...binding.requestResultStat(req.handle))
  } catch (e) {
    err = new FileError(e.message, { operation: 'stat', code: e.code, path: filepath })
  } finally {
    req.return()
  }

  return done(err, st, cb)
}

function statSync(filepath) {
  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  try {
    binding.statSync(req.handle, filepath)

    return new Stats(...binding.requestResultStat(req.handle))
  } catch (e) {
    throw new FileError(e.message, { operation: 'stat', code: e.code, path: filepath })
  } finally {
    req.return()
  }
}

async function lstat(filepath, cb) {
  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  let st
  let err = null
  try {
    binding.lstat(req.handle, filepath)

    await req

    st = new Stats(...binding.requestResultStat(req.handle))
  } catch (e) {
    err = new FileError(e.message, { operation: 'lstat', code: e.code, path: filepath })
  } finally {
    req.return()
  }

  return done(err, st, cb)
}

function lstatSync(filepath) {
  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  try {
    binding.lstatSync(req.handle, filepath)

    return new Stats(...binding.requestResultStat(req.handle))
  } catch (e) {
    throw new FileError(e.message, { operation: 'lstat', code: e.code, path: filepath })
  } finally {
    req.return()
  }
}

async function fstat(fd, cb) {
  const req = FileRequest.borrow()

  let st
  let err = null
  try {
    binding.fstat(req.handle, fd)

    await req

    st = new Stats(...binding.requestResultStat(req.handle))
  } catch (e) {
    err = new FileError(e.message, { operation: 'fstat', code: e.code, fd })
  } finally {
    req.return()
  }

  return done(err, st, cb)
}

function fstatSync(fd) {
  const req = FileRequest.borrow()

  try {
    binding.fstatSync(req.handle, fd)

    return new Stats(...binding.requestResultStat(req.handle))
  } catch (e) {
    throw new FileError(e.message, { operation: 'fstat', code: e.code, fd })
  } finally {
    req.return()
  }
}

async function statfs(filepath, cb) {
  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  let st
  let err = null
  try {
    binding.statfs(req.handle, filepath)

    await req

    st = new StatFs(...binding.requestResultStatfs(req.handle))
  } catch (e) {
    err = new FileError(e.message, { operation: 'statfs', code: e.code, path: filepath })
  } finally {
    req.return()
  }

  return done(err, st, cb)
}

function statfsSync(filepath) {
  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  try {
    binding.statfsSync(req.handle, filepath)

    return new StatFs(...binding.requestResultStatfs(req.handle))
  } catch (e) {
    throw new FileError(e.message, { operation: 'statfs', code: e.code, path: filepath })
  } finally {
    req.return()
  }
}

async function ftruncate(fd, len = 0, cb) {
  if (typeof len === 'function') {
    cb = len
    len = 0
  }

  if (typeof len !== 'number') len = 0

  const req = FileRequest.borrow()

  let err = null
  try {
    binding.ftruncate(req.handle, fd, len)

    await req
  } catch (e) {
    err = new FileError(e.message, { operation: 'ftruncate', code: e.code, fd })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function ftruncateSync(fd, len = 0) {
  if (typeof len !== 'number') len = 0

  const req = FileRequest.borrow()

  try {
    binding.ftruncateSync(req.handle, fd, len)
  } catch (e) {
    throw new FileError(e.message, { operation: 'ftruncate', code: e.code, fd })
  } finally {
    req.return()
  }
}

async function truncate(filepath, len, cb) {
  let fd = -1
  let err

  try {
    fd = await open(filepath, 'r+')

    await ftruncate(fd, len)
  } catch (e) {
    err = e
  } finally {
    if (fd !== -1) await close(fd)
  }

  return done(err, cb)
}

function truncateSync(filepath, len) {
  let fd = -1
  let err

  try {
    fd = openSync(filepath, 'r+')

    ftruncateSync(fd, len)
  } catch (e) {
    err = e
  } finally {
    if (fd !== -1) closeSync(fd)
  }
}

async function chmod(filepath, mode, cb) {
  if (typeof mode === 'string') mode = toMode(mode)

  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  let err = null
  try {
    binding.chmod(req.handle, filepath, mode)

    await req
  } catch (e) {
    err = new FileError(e.message, {
      operation: 'chmod',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function chmodSync(filepath, mode) {
  if (typeof mode === 'string') mode = toMode(mode)

  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  try {
    binding.chmodSync(req.handle, filepath, mode)
  } catch (e) {
    throw new FileError(e.message, {
      operation: 'chmod',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }
}

async function fchmod(fd, mode, cb) {
  if (typeof mode === 'string') mode = toMode(mode)

  const req = FileRequest.borrow()

  let err = null
  try {
    binding.fchmod(req.handle, fd, mode)

    await req
  } catch (e) {
    err = new FileError(e.message, { operation: 'fchmod', code: e.code, fd })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function fchmodSync(fd, mode) {
  if (typeof mode === 'string') mode = toMode(mode)

  const req = FileRequest.borrow()

  try {
    binding.fchmodSync(req.handle, fd, mode)
  } catch (e) {
    throw new FileError(e.message, { operation: 'fchmod', code: e.code, fd })
  } finally {
    req.return()
  }
}

async function chown(filepath, uid, gid, cb) {
  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  let err = null
  try {
    binding.chown(req.handle, filepath, uid, gid)

    await req
  } catch (e) {
    err = new FileError(e.message, { operation: 'chown', code: e.code, path: filepath })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function chownSync(filepath, uid, gid) {
  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  try {
    binding.chownSync(req.handle, filepath, uid, gid)
  } catch (e) {
    throw new FileError(e.message, { operation: 'chownSync', code: e.code, path: filepath })
  } finally {
    req.return()
  }
}

async function lchown(filepath, uid, gid, cb) {
  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  let err = null
  try {
    binding.lchown(req.handle, filepath, uid, gid)

    await req
  } catch (e) {
    err = new FileError(e.message, { operation: 'lchown', code: e.code, path: filepath })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function lchownSync(filepath, uid, gid) {
  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  try {
    binding.lchownSync(req.handle, filepath, uid, gid)
  } catch (e) {
    throw new FileError(e.message, { operation: 'lchownSync', code: e.code, path: filepath })
  } finally {
    req.return()
  }
}

async function fchown(fd, uid, gid, cb) {
  const req = FileRequest.borrow()

  let err = null
  try {
    binding.fchown(req.handle, fd, uid, gid)

    await req
  } catch (e) {
    err = new FileError(e.message, { operation: 'fchown', code: e.code, fd })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function fchownSync(fd, uid, gid) {
  const req = FileRequest.borrow()

  try {
    binding.fchownSync(req.handle, fd, uid, gid)
  } catch (e) {
    throw new FileError(e.message, { operation: 'fchownSync', code: e.code, fd })
  } finally {
    req.return()
  }
}

async function utimes(filepath, atime, mtime, cb) {
  if (typeof atime !== 'number') atime = atime.getTime() / 1000
  if (typeof mtime !== 'number') mtime = mtime.getTime() / 1000

  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  let err = null
  try {
    binding.utimes(req.handle, filepath, atime, mtime)

    await req
  } catch (e) {
    err = new FileError(e.message, { operation: 'utimes', code: e.code, path: filepath })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function utimesSync(filepath, atime, mtime) {
  if (typeof atime !== 'number') atime = atime.getTime() / 1000
  if (typeof mtime !== 'number') mtime = mtime.getTime() / 1000

  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  try {
    binding.utimesSync(req.handle, filepath, atime, mtime)
  } catch (e) {
    throw new FileError(e.message, { operation: 'utimes', code: e.code, path: filepath })
  } finally {
    req.return()
  }
}

async function lutimes(filepath, atime, mtime, cb) {
  if (typeof atime !== 'number') atime = atime.getTime() / 1000
  if (typeof mtime !== 'number') mtime = mtime.getTime() / 1000

  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  let err = null
  try {
    binding.lutimes(req.handle, filepath, atime, mtime)

    await req
  } catch (e) {
    err = new FileError(e.message, { operation: 'lutimes', code: e.code, path: filepath })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function lutimesSync(filepath, atime, mtime) {
  if (typeof atime !== 'number') atime = atime.getTime() / 1000
  if (typeof mtime !== 'number') mtime = mtime.getTime() / 1000

  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  try {
    binding.lutimesSync(req.handle, filepath, atime, mtime)
  } catch (e) {
    throw new FileError(e.message, { operation: 'lutimes', code: e.code, path: filepath })
  } finally {
    req.return()
  }
}

async function futimes(fd, atime, mtime, cb) {
  if (typeof atime !== 'number') atime = atime.getTime() / 1000
  if (typeof mtime !== 'number') mtime = mtime.getTime() / 1000

  const req = FileRequest.borrow()

  let err = null
  try {
    binding.futimes(req.handle, fd, atime, mtime)

    await req
  } catch (e) {
    err = new FileError(e.message, { operation: 'futimes', code: e.code, fd })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function futimesSync(fd, atime, mtime) {
  if (typeof atime !== 'number') atime = atime.getTime() / 1000
  if (typeof mtime !== 'number') mtime = mtime.getTime() / 1000

  const req = FileRequest.borrow()

  try {
    binding.futimesSync(req.handle, fd, atime, mtime)
  } catch (e) {
    throw new FileError(e.message, { operation: 'futimesSync', code: e.code, fd })
  } finally {
    req.return()
  }
}

async function link(src, dst, cb) {
  src = toNamespacedPath(src)
  dst = toNamespacedPath(dst)

  const req = FileRequest.borrow()

  let err = null
  try {
    binding.link(req.handle, src, dst)

    await req
  } catch (e) {
    err = new FileError(e.message, { operation: 'link', code: e.code, path: src, destination: dst })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function linkSync(src, dst) {
  src = toNamespacedPath(src)
  dst = toNamespacedPath(dst)

  const req = FileRequest.borrow()

  try {
    binding.linkSync(req.handle, src, dst)
  } catch (e) {
    throw new FileError(e.message, {
      operation: 'linkSync',
      code: e.code,
      path: src,
      destination: dst
    })
  } finally {
    req.return()
  }
}

async function mkdir(filepath, opts, cb) {
  if (typeof opts === 'function') {
    cb = opts
    opts = { mode: 0o777 }
  }

  if (typeof opts === 'number') opts = { mode: opts }
  else if (!opts) opts = {}

  const mode = typeof opts.mode === 'number' ? opts.mode : 0o777

  filepath = toNamespacedPath(filepath)

  if (opts.recursive) {
    let err = null
    try {
      try {
        await mkdir(filepath, { mode })
      } catch (err) {
        if (err.code !== 'ENOENT') {
          if (!(await stat(filepath)).isDirectory()) throw err
        } else {
          while (filepath.endsWith(path.sep)) filepath = filepath.slice(0, -1)
          const i = filepath.lastIndexOf(path.sep)
          if (i <= 0) throw err

          await mkdir(filepath.slice(0, i), { mode, recursive: true })

          try {
            await mkdir(filepath, { mode })
          } catch (err) {
            if (!(await stat(filepath)).isDirectory()) throw err
          }
        }
      }
    } catch (e) {
      err = e
    }

    return done(err, cb)
  }

  const req = FileRequest.borrow()

  let err = null
  try {
    binding.mkdir(req.handle, filepath, mode)

    await req
  } catch (e) {
    err = new FileError(e.message, {
      operation: 'mkdir',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function mkdirSync(filepath, opts) {
  if (typeof opts === 'number') opts = { mode: opts }
  else if (!opts) opts = {}

  const mode = typeof opts.mode === 'number' ? opts.mode : 0o777

  filepath = toNamespacedPath(filepath)

  if (opts.recursive) {
    try {
      mkdirSync(filepath, { mode })
    } catch (err) {
      if (err.code !== 'ENOENT') {
        if (!statSync(filepath).isDirectory()) throw err
      } else {
        while (filepath.endsWith(path.sep)) filepath = filepath.slice(0, -1)
        const i = filepath.lastIndexOf(path.sep)
        if (i <= 0) throw err

        mkdirSync(filepath.slice(0, i), { mode, recursive: true })

        try {
          mkdirSync(filepath, { mode })
        } catch (err) {
          if (!statSync(filepath).isDirectory()) throw err
        }
      }
    }

    return
  }

  const req = FileRequest.borrow()

  try {
    binding.mkdirSync(req.handle, filepath, mode)
  } catch (e) {
    throw new FileError(e.message, {
      operation: 'mkdir',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }
}

async function mkdtemp(prefix, cb) {
  prefix = toNamespacedPath(prefix)

  const req = FileRequest.borrow()

  let res
  let err = null
  try {
    binding.mkdtemp(req.handle, prefix + 'XXXXXX')

    await req

    res = binding.requestResultPath(req.handle)
  } catch (e) {
    err = new FileError(e.message, { operation: 'mkdtemp', code: e.code, path: prefix })
  } finally {
    req.return()
  }

  return done(err, res, cb)
}

function mkdtempSync(prefix) {
  prefix = toNamespacedPath(prefix)

  const req = FileRequest.borrow()

  try {
    binding.mkdtempSync(req.handle, prefix + 'XXXXXX')

    return binding.requestResultPath(req.handle)
  } catch (e) {
    throw new FileError(e.message, { operation: 'mkdtempSync', code: e.code, path: prefix })
  } finally {
    req.return()
  }
}

async function rmdir(filepath, cb) {
  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  let err = null
  try {
    binding.rmdir(req.handle, filepath)

    await req
  } catch (e) {
    err = new FileError(e.message, {
      operation: 'rmdir',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function rmdirSync(filepath) {
  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  try {
    binding.rmdirSync(req.handle, filepath)
  } catch (e) {
    throw new FileError(e.message, {
      operation: 'rmdir',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }
}

async function rm(filepath, opts, cb) {
  if (typeof opts === 'function') {
    cb = opts
    opts = {}
  }

  if (!opts) opts = {}

  filepath = toNamespacedPath(filepath)

  let err = null
  try {
    const st = await lstat(filepath)

    if (st.isDirectory()) {
      if (opts.recursive) {
        try {
          await rmdir(filepath)
        } catch (err) {
          if (err.code !== 'ENOTEMPTY') throw err

          const files = await readdir(filepath)

          for (const file of files) {
            await rm(filepath + path.sep + file, opts)
          }

          await rmdir(filepath)
        }
      } else {
        throw new FileError('is a directory', {
          operation: 'rm',
          code: 'EISDIR',
          path: filepath
        })
      }
    } else {
      await unlink(filepath)
    }
  } catch (e) {
    if (e.code !== 'ENOENT' || !opts.force) err = e
  }

  return done(err, cb)
}

function rmSync(filepath, opts) {
  if (!opts) opts = {}

  filepath = toNamespacedPath(filepath)

  try {
    const st = lstatSync(filepath)

    if (st.isDirectory()) {
      if (opts.recursive) {
        try {
          rmdirSync(filepath)
        } catch (err) {
          if (err.code !== 'ENOTEMPTY') throw err

          const files = readdirSync(filepath)

          for (const file of files) {
            rmSync(filepath + path.sep + file, opts)
          }

          rmdirSync(filepath)
        }
      } else {
        throw new FileError('is a directory', {
          operation: 'rm',
          code: 'EISDIR',
          path: filepath
        })
      }
    } else {
      unlinkSync(filepath)
    }
  } catch (err) {
    if (err.code !== 'ENOENT' || !opts.force) throw err
  }
}

async function unlink(filepath, cb) {
  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  let err = null
  try {
    binding.unlink(req.handle, filepath)

    await req
  } catch (e) {
    err = new FileError(e.message, {
      operation: 'unlink',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function unlinkSync(filepath) {
  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  try {
    binding.unlinkSync(req.handle, filepath)
  } catch (e) {
    throw new FileError(e.message, {
      operation: 'unlink',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }
}

async function rename(src, dst, cb) {
  src = toNamespacedPath(src)
  dst = toNamespacedPath(dst)

  const req = FileRequest.borrow()

  let err = null
  try {
    binding.rename(req.handle, src, dst)

    await req
  } catch (e) {
    err = new FileError(e.message, {
      operation: 'rename',
      code: e.code,
      path: src,
      destination: dst
    })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function renameSync(src, dst) {
  src = toNamespacedPath(src)
  dst = toNamespacedPath(dst)

  const req = FileRequest.borrow()

  try {
    binding.renameSync(req.handle, src, dst)
  } catch (e) {
    throw new FileError(e.message, {
      operation: 'rename',
      code: e.code,
      path: src,
      destination: dst
    })
  } finally {
    req.return()
  }
}

async function copyFile(src, dst, mode = 0, cb) {
  if (typeof mode === 'function') {
    cb = mode
    mode = 0
  }

  src = toNamespacedPath(src)
  dst = toNamespacedPath(dst)

  const req = FileRequest.borrow()

  let err = null
  try {
    binding.copyfile(req.handle, src, dst, mode)

    await req
  } catch (e) {
    err = new FileError(e.message, {
      operation: 'copyfile',
      code: e.code,
      path: src,
      destination: dst
    })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function copyFileSync(src, dst, mode = 0) {
  src = toNamespacedPath(src)
  dst = toNamespacedPath(dst)

  const req = FileRequest.borrow()

  try {
    binding.copyfileSync(req.handle, src, dst, mode)
  } catch (e) {
    throw new FileError(e.message, {
      operation: 'copyfile',
      code: e.code,
      path: src,
      destination: dst
    })
  } finally {
    req.return()
  }
}

async function cp(src, dst, opts, cb) {
  if (typeof opts === 'function') {
    cb = opts
    opts = {}
  }

  if (!opts) opts = {}

  src = toNamespacedPath(src)
  dst = toNamespacedPath(dst)

  let err = null
  try {
    const st = await lstat(src)

    if (st.isDirectory()) {
      if (opts.recursive !== true) {
        throw new FileError('is a directory', { operation: 'cp', code: 'EISDIR', path: src })
      }

      try {
        await lstat(dst)
      } catch (e) {
        if (e.code !== 'ENOENT') throw e

        await mkdir(dst, { mode: st.mode, recursive: true })
      }

      const dir = await opendir(src)
      for await (const { name } of dir) {
        await cp(path.join(src, name), path.join(dst, name), opts)
      }
    } else if (st.isFile()) {
      await copyFile(src, dst)
      await chmod(dst, st.mode)
    }
  } catch (e) {
    err = e
  }

  return done(err, cb)
}

function cpSync(src, dst, opts = {}) {
  src = toNamespacedPath(src)
  dst = toNamespacedPath(dst)

  const st = lstatSync(src)

  if (st.isDirectory()) {
    if (opts.recursive !== true) {
      throw new FileError('is a directory', { operation: 'cp', code: 'EISDIR', path: src })
    }

    try {
      lstatSync(dst)
    } catch (e) {
      if (e.code !== 'ENOENT') throw e

      mkdirSync(dst, { mode: st.mode, recursive: true })
    }

    const dir = opendirSync(src)
    for (const { name } of dir) {
      cpSync(path.join(src, name), path.join(dst, name), opts)
    }
  } else if (st.isFile()) {
    copyFileSync(src, dst)
    chmodSync(dst, st.mode)
  }
}

async function realpath(filepath, opts, cb) {
  if (typeof opts === 'function') {
    cb = opts
    opts = {}
  }

  if (typeof opts === 'string') opts = { encoding: opts }
  else if (!opts) opts = {}

  const { encoding = 'utf8' } = opts

  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  let res
  let err = null
  try {
    binding.realpath(req.handle, filepath)

    await req

    res = Buffer.from(binding.requestResultString(req.handle))

    if (encoding !== 'buffer') res = res.toString(encoding)
  } catch (e) {
    err = new FileError(e.message, {
      operation: 'realpath',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }

  return done(err, res, cb)
}

function realpathSync(filepath, opts) {
  if (typeof opts === 'string') opts = { encoding: opts }
  else if (!opts) opts = {}

  const { encoding = 'utf8' } = opts

  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  try {
    binding.realpathSync(req.handle, filepath)

    let res = Buffer.from(binding.requestResultString(req.handle))

    if (encoding !== 'buffer') res = res.toString(encoding)

    return res
  } catch (e) {
    throw new FileError(e.message, {
      operation: 'realpath',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }
}

async function readlink(filepath, opts, cb) {
  if (typeof opts === 'function') {
    cb = opts
    opts = {}
  }

  if (typeof opts === 'string') opts = { encoding: opts }
  else if (!opts) opts = {}

  const { encoding = 'utf8' } = opts

  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  let res
  let err = null
  try {
    binding.readlink(req.handle, filepath)

    await req

    res = Buffer.from(binding.requestResultString(req.handle))

    if (encoding !== 'buffer') res = res.toString(encoding)
  } catch (e) {
    err = new FileError(e.message, {
      operation: 'readlink',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }

  return done(err, res, cb)
}

function readlinkSync(filepath, opts) {
  if (typeof opts === 'string') opts = { encoding: opts }
  else if (!opts) opts = {}

  const { encoding = 'utf8' } = opts

  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  try {
    binding.readlinkSync(req.handle, filepath)

    let res = Buffer.from(binding.requestResultString(req.handle))

    if (encoding !== 'buffer') res = res.toString(encoding)

    return res
  } catch (e) {
    throw new FileError(e.message, {
      operation: 'readlink',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }
}

async function fsync(fd, cb) {
  const req = FileRequest.borrow()

  let err = null
  try {
    binding.fsync(req.handle, fd)

    await req
  } catch (e) {
    err = new FileError(e.message, { operation: 'fsync', code: e.code, fd })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function fsyncSync(fd) {
  const req = FileRequest.borrow()

  try {
    binding.fsyncSync(req.handle, fd)
  } catch (e) {
    throw new FileError(e.message, { operation: 'fsyncSync', code: e.code, fd })
  } finally {
    req.return()
  }
}

async function fdatasync(fd, cb) {
  const req = FileRequest.borrow()

  let err = null
  try {
    binding.fdatasync(req.handle, fd)

    await req
  } catch (e) {
    err = new FileError(e.message, { operation: 'fdatasync', code: e.code, fd })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function fdatasyncSync(fd) {
  const req = FileRequest.borrow()

  try {
    binding.fdatasyncSync(req.handle, fd)
  } catch (e) {
    throw new FileError(e.message, { operation: 'fdatasyncSync', code: e.code, fd })
  } finally {
    req.return()
  }
}

function normalizeSymlinkTarget(target, type, filepath) {
  if (isWindows) {
    if (type === constants.UV_FS_SYMLINK_JUNCTION) target = path.resolve(filepath, '..', target)

    if (path.isAbsolute(target)) return path.toNamespacedPath(target)

    return target.replace(/\//g, path.sep)
  }

  return target
}

async function symlink(target, filepath, type, cb) {
  if (typeof type === 'function') {
    cb = type
    type = null
  }

  filepath = toNamespacedPath(filepath)

  if (typeof type === 'string') {
    switch (type) {
      case 'dir':
        type = constants.UV_FS_SYMLINK_DIR
        break
      case 'junction':
        type = constants.UV_FS_SYMLINK_JUNCTION
        break
      case 'file':
      default:
        type = 0
        break
    }
  } else if (typeof type !== 'number') {
    if (isWindows) {
      target = path.resolve(filepath, '..', target)

      try {
        type = (await stat(target)).isDirectory()
          ? constants.UV_FS_SYMLINK_DIR
          : constants.UV_FS_SYMLINK_JUNCTION
      } catch {
        type = 0
      }
    } else {
      type = 0
    }
  }

  target = normalizeSymlinkTarget(target, type, filepath)

  const req = FileRequest.borrow()

  let err = null
  try {
    binding.symlink(req.handle, target, filepath, type)

    await req
  } catch (e) {
    err = new FileError(e.message, {
      operation: 'symlink',
      code: e.code,
      path: target,
      destination: filepath
    })
  } finally {
    req.return()
  }

  return done(err, cb)
}

function symlinkSync(target, filepath, type) {
  filepath = toNamespacedPath(filepath)

  if (typeof type === 'string') {
    switch (type) {
      case 'dir':
        type = constants.UV_FS_SYMLINK_DIR
        break
      case 'junction':
        type = constants.UV_FS_SYMLINK_JUNCTION
        break
      case 'file':
      default:
        type = 0
        break
    }
  } else if (typeof type !== 'number') {
    if (isWindows) {
      target = path.resolve(filepath, '..', target)

      try {
        type = statSync(target).isDirectory()
          ? constants.UV_FS_SYMLINK_DIR
          : constants.UV_FS_SYMLINK_JUNCTION
      } catch {
        type = 0
      }
    } else {
      type = 0
    }
  }

  target = normalizeSymlinkTarget(target, type, filepath)

  const req = FileRequest.borrow()

  try {
    binding.symlinkSync(req.handle, target, filepath, type)
  } catch (e) {
    throw new FileError(e.message, {
      operation: 'symlink',
      code: e.code,
      path: target,
      destination: filepath
    })
  } finally {
    req.return()
  }
}

async function opendir(filepath, opts, cb) {
  if (typeof opts === 'function') {
    cb = opts
    opts = {}
  }

  if (typeof opts === 'string') opts = { encoding: opts }
  else if (!opts) opts = {}

  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  let dir
  let err = null
  try {
    binding.opendir(req.handle, filepath)

    await req

    dir = new Dir(filepath, binding.requestResultDir(req.handle), opts)
  } catch (e) {
    err = new FileError(e.message, {
      operation: 'opendir',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }

  return done(err, dir, cb)
}

function opendirSync(filepath, opts) {
  if (typeof opts === 'string') opts = { encoding: opts }
  else if (!opts) opts = {}

  filepath = toNamespacedPath(filepath)

  const req = FileRequest.borrow()

  try {
    binding.opendirSync(req.handle, filepath)

    return new Dir(filepath, binding.requestResultDir(req.handle), opts)
  } catch (e) {
    throw new FileError(e.message, {
      operation: 'opendir',
      code: e.code,
      path: filepath
    })
  } finally {
    req.return()
  }
}

async function readdir(filepath, opts, cb) {
  if (typeof opts === 'function') {
    cb = opts
    opts = {}
  }

  if (typeof opts === 'string') opts = { encoding: opts }
  else if (!opts) opts = {}

  const { withFileTypes = false, recursive = false } = opts

  filepath = toNamespacedPath(filepath)

  const queue = [filepath]
  let result = []
  let err = null
  try {
    while (queue.length !== 0) {
      const dir = await opendir(queue.pop())

      for await (const entry of dir) {
        const entryPath = path.join(entry.parentPath, entry.name)

        if (withFileTypes) {
          result.push(entry)
        } else {
          result.push(path.relative(filepath, entryPath))
        }

        if (recursive && entry.isDirectory()) queue.push(entryPath)
      }
    }
  } catch (e) {
    result = []
    err = e
  }

  return done(err, result, cb)
}

function readdirSync(filepath, opts) {
  if (typeof opts === 'string') opts = { encoding: opts }
  else if (!opts) opts = {}

  const { withFileTypes = false, recursive = false } = opts

  filepath = toNamespacedPath(filepath)

  const queue = [filepath]
  const result = []

  while (queue.length !== 0) {
    const dir = opendirSync(queue.pop(), opts)

    for (const entry of dir) {
      const entryPath = path.join(entry.parentPath, entry.name)

      if (withFileTypes) {
        result.push(entry)
      } else {
        result.push(path.relative(filepath, entryPath))
      }

      if (recursive && entry.isDirectory()) queue.push(entryPath)
    }
  }

  return result
}

async function readFile(filepath, opts, cb) {
  if (typeof opts === 'function') {
    cb = opts
    opts = {}
  }

  if (typeof opts === 'string') opts = { encoding: opts }
  else if (!opts) opts = {}

  const { encoding = 'buffer' } = opts

  let fd = -1
  let buffer = null
  let err = null
  try {
    fd = await open(filepath, opts.flag || 'r')

    const st = await fstat(fd)

    let len = 0

    if (st.size === 0) {
      const buffers = []

      while (true) {
        buffer = Buffer.allocUnsafe(8192)
        const r = await read(fd, buffer)
        len += r
        if (r === 0) break
        buffers.push(buffer.subarray(0, r))
      }

      buffer = Buffer.concat(buffers)
    } else {
      buffer = Buffer.allocUnsafe(st.size)

      while (true) {
        const r = await read(fd, len ? buffer.subarray(len) : buffer)
        len += r
        if (r === 0 || len === buffer.byteLength) break
      }

      if (len !== buffer.byteLength) buffer = buffer.subarray(0, len)
    }

    if (encoding !== 'buffer') buffer = buffer.toString(encoding)
  } catch (e) {
    err = e
  } finally {
    if (fd !== -1) await close(fd)
  }

  return done(err, buffer, cb)
}

function readFileSync(filepath, opts) {
  if (typeof opts === 'string') opts = { encoding: opts }
  else if (!opts) opts = {}

  const { encoding = 'buffer' } = opts

  let fd = -1
  try {
    fd = openSync(filepath, opts.flag || 'r')

    const st = fstatSync(fd)

    let buffer
    let len = 0

    if (st.size === 0) {
      const buffers = []

      while (true) {
        buffer = Buffer.allocUnsafe(8192)
        const r = readSync(fd, buffer)
        len += r
        if (r === 0) break
        buffers.push(buffer.subarray(0, r))
      }

      buffer = Buffer.concat(buffers)
    } else {
      buffer = Buffer.allocUnsafe(st.size)

      while (true) {
        const r = readSync(fd, len ? buffer.subarray(len) : buffer)
        len += r
        if (r === 0 || len === buffer.byteLength) break
      }

      if (len !== buffer.byteLength) buffer = buffer.subarray(0, len)
    }

    if (encoding !== 'buffer') buffer = buffer.toString(encoding)

    return buffer
  } finally {
    if (fd !== -1) closeSync(fd)
  }
}

async function writeFile(filepath, data, opts, cb) {
  if (typeof opts === 'function') {
    cb = opts
    opts = {}
  }

  if (typeof opts === 'string') opts = { encoding: opts }
  else if (!opts) opts = {}

  if (typeof data === 'string') data = Buffer.from(data, opts.encoding)

  let fd = -1
  let len = 0
  let err = null
  try {
    fd = await open(filepath, opts.flag || 'w', opts.mode || 0o666)

    while (true) {
      len += await write(fd, len ? data.subarray(len) : data)
      if (len === data.byteLength) break
    }
  } catch (e) {
    err = e
  } finally {
    if (fd !== -1) await close(fd)
  }

  return done(err, len, cb)
}

function writeFileSync(filepath, data, opts) {
  if (typeof opts === 'string') opts = { encoding: opts }
  else if (!opts) opts = {}

  if (typeof data === 'string') data = Buffer.from(data, opts.encoding)

  let fd = -1
  try {
    fd = openSync(filepath, opts.flag || 'w', opts.mode || 0o666)

    let len = 0

    while (true) {
      len += writeSync(fd, len ? data.subarray(len) : data)
      if (len === data.byteLength) break
    }
  } finally {
    if (fd !== -1) closeSync(fd)
  }
}

function appendFile(filepath, data, opts, cb) {
  if (typeof opts === 'function') {
    cb = opts
    opts = {}
  }

  if (typeof opts === 'string') opts = { encoding: opts }
  else if (!opts) opts = {}

  if (!opts.flag) opts = { ...opts, flag: 'a' }

  return writeFile(filepath, data, opts, cb)
}

function appendFileSync(filepath, data, opts) {
  if (typeof opts === 'string') opts = { encoding: opts }
  else if (!opts) opts = {}

  if (!opts.flag) opts = { ...opts, flag: 'a' }

  return writeFileSync(filepath, data, opts)
}

function watch(filepath, opts, cb) {
  if (typeof opts === 'function') {
    cb = opts
    opts = {}
  }

  if (typeof opts === 'string') opts = { encoding: opts }
  else if (!opts) opts = {}

  filepath = toNamespacedPath(filepath)

  return new Watcher(filepath, opts, cb)
}

class Stats {
  constructor(
    dev,
    mode,
    nlink,
    uid,
    gid,
    rdev,
    blksize,
    ino,
    size,
    blocks,
    atimeMs,
    mtimeMs,
    ctimeMs,
    birthtimeMs
  ) {
    this.dev = dev
    this.mode = mode
    this.nlink = nlink
    this.uid = uid
    this.gid = gid
    this.rdev = rdev
    this.blksize = blksize
    this.ino = ino
    this.size = size
    this.blocks = blocks
    this.atimeMs = atimeMs
    this.mtimeMs = mtimeMs
    this.ctimeMs = ctimeMs
    this.birthtimeMs = birthtimeMs
    this.atime = new Date(atimeMs)
    this.mtime = new Date(mtimeMs)
    this.ctime = new Date(ctimeMs)
    this.birthtime = new Date(birthtimeMs)
  }

  isDirectory() {
    return (this.mode & constants.S_IFMT) === constants.S_IFDIR
  }

  isFile() {
    return (this.mode & constants.S_IFMT) === constants.S_IFREG
  }

  isBlockDevice() {
    return (this.mode & constants.S_IFMT) === constants.S_IFBLK
  }

  isCharacterDevice() {
    return (this.mode & constants.S_IFMT) === constants.S_IFCHR
  }

  isFIFO() {
    return (this.mode & constants.S_IFMT) === constants.S_IFIFO
  }

  isSymbolicLink() {
    return (this.mode & constants.S_IFMT) === constants.S_IFLNK
  }

  isSocket() {
    return (this.mode & constants.S_IFMT) === constants.S_IFSOCK
  }
}

class StatFs {
  constructor(type, bsize, blocks, bfree, bavail, files, ffree) {
    this.type = type
    this.bsize = bsize
    this.blocks = blocks
    this.bfree = bfree
    this.bavail = bavail
    this.files = files
    this.ffree = ffree
  }
}

class Dir {
  constructor(path, handle, opts = {}) {
    const { encoding = 'utf8', bufferSize = 32 } = opts

    this.path = path

    this._encoding = encoding
    this._capacity = bufferSize
    this._buffer = new FIFO()
    this._ended = false
    this._handle = handle
  }

  async read(cb) {
    if (this._buffer.length) return ok(this._buffer.shift(), cb)
    if (this._ended) return ok(null, cb)

    const req = FileRequest.borrow()

    let entries
    let err = null
    try {
      req.retain(binding.readdir(req.handle, this._handle, this._capacity))

      await req

      entries = binding.requestResultDirents(req.handle)
    } catch (e) {
      err = new FileError(e.message, {
        operation: 'readdir',
        code: e.code,
        path: this.path
      })
    } finally {
      req.return()
    }

    if (err) return fail(err, cb)

    if (entries.length === 0) {
      this._ended = true

      return ok(null, cb)
    }

    for (const entry of entries) {
      let name = Buffer.from(entry.name)

      if (this._encoding !== 'buffer') name = name.toString(this._encoding)

      this._buffer.push(new Dirent(this.path, name, entry.type))
    }

    return ok(this._buffer.shift(), cb)
  }

  readSync() {
    if (this._buffer.length) return this._buffer.shift()
    if (this._ended) return null

    const req = FileRequest.borrow()

    let entries
    try {
      req.retain(binding.readdirSync(req.handle, this._handle, this._capacity))

      entries = binding.requestResultDirents(req.handle)
    } catch (e) {
      throw new FileError(e.message, {
        operation: 'readdir',
        code: e.code,
        path: this.path
      })
    } finally {
      req.return()
    }

    if (entries.length === 0) {
      this._ended = true

      return null
    }

    for (const entry of entries) {
      let name = Buffer.from(entry.name)

      if (this._encoding !== 'buffer') name = name.toString(this._encoding)

      this._buffer.push(new Dirent(this.path, name, entry.type))
    }

    return this._buffer.shift()
  }

  async close(cb) {
    const req = FileRequest.borrow()

    let err = null
    try {
      binding.closedir(req.handle, this._handle)

      await req
    } catch (e) {
      err = new FileError(e.message, {
        operation: 'closedir',
        code: e.code,
        path: this.path
      })
    } finally {
      req.return()
    }

    this._handle = null

    return done(err, cb)
  }

  closeSync() {
    const req = FileRequest.borrow()

    try {
      binding.closedirSync(req.handle, this._handle)
    } catch (e) {
      throw new FileError(e.message, {
        operation: 'closedir',
        code: e.code,
        path: this.path
      })
    } finally {
      req.return()
    }

    this._handle = null
  }

  [Symbol.dispose]() {
    this.closeSync()
  }

  async [Symbol.asyncDispose]() {
    await this.close()
  }

  *[Symbol.iterator]() {
    while (true) {
      const entry = this.readSync()
      if (entry === null) break
      yield entry
    }

    this.closeSync()
  }

  async *[Symbol.asyncIterator]() {
    while (true) {
      const entry = await this.read()
      if (entry === null) break
      yield entry
    }

    await this.close()
  }
}

class Dirent {
  constructor(parentPath, name, type) {
    this.parentPath = parentPath
    this.name = name
    this.type = type
  }

  isFile() {
    return this.type === constants.UV_DIRENT_FILE
  }

  isDirectory() {
    return this.type === constants.UV_DIRENT_DIR
  }

  isSymbolicLink() {
    return this.type === constants.UV_DIRENT_LINK
  }

  isFIFO() {
    return this.type === constants.UV_DIRENT_FIFO
  }

  isSocket() {
    return this.type === constants.UV_DIRENT_SOCKET
  }

  isCharacterDevice() {
    return this.type === constants.UV_DIRENT_CHAR
  }

  isBlockDevice() {
    return this.type === constants.UV_DIRENT_BLOCK
  }
}

class FileReadStream extends Readable {
  constructor(path, opts = {}) {
    const { eagerOpen = true } = opts

    super({ eagerOpen, ...opts })

    this.path = path
    this.fd = typeof opts.fd === 'number' ? opts.fd : -1
    this.flags = opts.flags || 'r'
    this.mode = opts.mode || 0o666

    this._offset = opts.start || 0
    this._missing = 0

    if (opts.length) {
      this._missing = opts.length
    } else if (typeof opts.end === 'number') {
      this._missing = opts.end - this._offset + 1
    } else {
      this._missing = -1
    }
  }

  async _open(cb) {
    let err

    if (this.fd === -1) {
      err = null
      try {
        this.fd = await open(this.path, this.flags, this.mode)
      } catch (e) {
        err = e
      }

      if (err) return cb(err)
    }

    let st
    err = null
    try {
      st = await fstat(this.fd)
    } catch (e) {
      err = e
    }

    if (err) return cb(err)

    if (this._missing === -1) this._missing = st.size

    if (st.size < this._offset) {
      this._offset = st.size
      this._missing = 0
    } else if (st.size < this._offset + this._missing) {
      this._missing = st.size - this._offset
    }

    cb(null)
  }

  async _read(size) {
    if (this._missing <= 0) return this.push(null)

    const data = Buffer.allocUnsafe(Math.min(this._missing, size))

    let len
    let err = null
    try {
      len = await read(this.fd, data, 0, data.byteLength, this._offset)
    } catch (e) {
      err = e
    }

    if (err) return this.destroy(err)

    if (len === 0) return this.push(null)

    if (this._missing < len) len = this._missing

    this._missing -= len
    this._offset += len

    this.push(data.subarray(0, len))
  }

  async _destroy(err, cb) {
    if (this.fd === -1) return cb(err)

    try {
      await close(this.fd)
    } catch (e) {
      err = err || e
    }

    cb(err)
  }
}

class FileWriteStream extends Writable {
  constructor(path, opts = {}) {
    const { eagerOpen = true } = opts

    super({ eagerOpen, ...opts })

    this.path = path
    this.fd = typeof opts.fd === 'number' ? opts.fd : -1
    this.flags = opts.flags || 'w'
    this.mode = opts.mode || 0o666
  }

  async _open(cb) {
    if (this.fd !== -1) return cb(null)

    let err = null
    try {
      this.fd = await open(this.path, this.flags, this.mode)
    } catch (e) {
      err = e
    }

    cb(err)
  }

  async _writev(batch, cb) {
    let err = null
    try {
      await writev(
        this.fd,
        batch.map(({ chunk }) => chunk)
      )
    } catch (e) {
      err = e
    }

    cb(err)
  }

  async _destroy(err, cb) {
    if (this.fd === -1) return cb(err)

    try {
      await close(this.fd)
    } catch (e) {
      err = err || e
    }

    cb(err)
  }
}

class Watcher extends EventEmitter {
  constructor(path, opts, onchange) {
    if (typeof opts === 'function') {
      onchange = opts
      opts = {}
    }

    if (!opts) opts = {}

    const { persistent = true, recursive = false, encoding = 'utf8' } = opts

    super()

    this._closed = false
    this._encoding = encoding
    this._handle = binding.watcherInit(path, recursive, this, this._onevent, this._onclose)

    if (!persistent) this.unref()

    if (onchange) this.on('change', onchange)
  }

  close() {
    if (this._closed) return
    this._closed = true

    binding.watcherClose(this._handle)
  }

  ref() {
    if (this._handle) binding.watcherRef(this._handle)
    return this
  }

  unref() {
    if (this._handle) binding.watcherUnref(this._handle)
    return this
  }

  [Symbol.asyncIterator]() {
    const buffer = []
    let done = false
    let error = null
    let next = null

    this.on('change', (eventType, filename) => {
      if (next) {
        next.resolve({ done: false, value: { eventType, filename } })
        next = null
      } else {
        buffer.push({ eventType, filename })
      }
    })
      .on('error', (err) => {
        done = true
        error = err

        if (next) {
          next.reject(error)
          next = null
        }
      })
      .on('close', () => {
        done = true

        if (next) {
          next.resolve({ done })
          next = null
        }
      })

    return {
      next: () =>
        new Promise((resolve, reject) => {
          if (error) return reject(error)

          if (buffer.length) return resolve({ done: false, value: buffer.shift() })

          if (done) return resolve({ done })

          next = { resolve, reject }
        })
    }
  }

  _onevent(err, events, filename) {
    if (err) {
      this.close()
      this.emit('error', err)
    } else {
      const path =
        this._encoding === 'buffer'
          ? Buffer.from(filename)
          : Buffer.from(filename).toString(this._encoding)

      if (events & binding.UV_RENAME) {
        this.emit('change', 'rename', path)
      }

      if (events & binding.UV_CHANGE) {
        this.emit('change', 'change', path)
      }
    }
  }

  _onclose() {
    this._handle = null

    this.emit('close')
  }
}

exports.access = access
exports.appendFile = appendFile
exports.chmod = chmod
exports.chown = chown
exports.close = close
exports.copyFile = copyFile
exports.cp = cp
exports.exists = exists
exports.fchmod = fchmod
exports.fchown = fchown
exports.fdatasync = fdatasync
exports.fstat = fstat
exports.fsync = fsync
exports.ftruncate = ftruncate
exports.futimes = futimes
exports.lchown = lchown
exports.lutimes = lutimes
exports.link = link
exports.lstat = lstat
exports.mkdir = mkdir
exports.mkdtemp = mkdtemp
exports.open = open
exports.opendir = opendir
exports.read = read
exports.readFile = readFile
exports.readdir = readdir
exports.readlink = readlink
exports.readv = readv
exports.realpath = realpath
exports.rename = rename
exports.rm = rm
exports.rmdir = rmdir
exports.stat = stat
exports.statfs = statfs
exports.symlink = symlink
exports.truncate = truncate
exports.unlink = unlink
exports.utimes = utimes
exports.watch = watch
exports.write = write
exports.writeFile = writeFile
exports.writev = writev

exports.accessSync = accessSync
exports.appendFileSync = appendFileSync
exports.chmodSync = chmodSync
exports.chownSync = chownSync
exports.closeSync = closeSync
exports.copyFileSync = copyFileSync
exports.cpSync = cpSync
exports.existsSync = existsSync
exports.fchmodSync = fchmodSync
exports.fchownSync = fchownSync
exports.fdatasyncSync = fdatasyncSync
exports.fstatSync = fstatSync
exports.fsyncSync = fsyncSync
exports.ftruncateSync = ftruncateSync
exports.futimesSync = futimesSync
exports.lchownSync = lchownSync
exports.lutimesSync = lutimesSync
exports.linkSync = linkSync
exports.lstatSync = lstatSync
exports.mkdirSync = mkdirSync
exports.mkdtempSync = mkdtempSync
exports.openSync = openSync
exports.opendirSync = opendirSync
exports.readFileSync = readFileSync
exports.readSync = readSync
exports.readdirSync = readdirSync
exports.readlinkSync = readlinkSync
exports.readvSync = readvSync
exports.realpathSync = realpathSync
exports.renameSync = renameSync
exports.rmSync = rmSync
exports.rmdirSync = rmdirSync
exports.statSync = statSync
exports.statfsSync = statfsSync
exports.symlinkSync = symlinkSync
exports.truncateSync = truncateSync
exports.unlinkSync = unlinkSync
exports.utimesSync = utimesSync
exports.writeFileSync = writeFileSync
exports.writeSync = writeSync
exports.writevSync = writevSync

exports.promises = require('./promises')

exports.Stats = Stats
exports.StatFs = StatFs
exports.Dir = Dir
exports.Dirent = Dirent
exports.Watcher = Watcher

exports.ReadStream = FileReadStream

exports.createReadStream = function createReadStream(path, opts) {
  return new FileReadStream(path, opts)
}

exports.WriteStream = FileWriteStream

exports.createWriteStream = function createWriteStream(path, opts) {
  return new FileWriteStream(path, opts)
}

function toNamespacedPath(filepath) {
  if (typeof filepath !== 'string') {
    if (isURL(filepath)) filepath = fileURLToPath(filepath)
    else filepath = filepath.toString()
  }

  return path.toNamespacedPath(filepath)
}

function toFlags(flags) {
  switch (flags) {
    case 'r':
      return constants.O_RDONLY
    case 'rs': // Fall through.
    case 'sr':
      return constants.O_RDONLY | constants.O_SYNC
    case 'r+':
      return constants.O_RDWR
    case 'rs+': // Fall through.
    case 'sr+':
      return constants.O_RDWR | constants.O_SYNC

    case 'w':
      return constants.O_TRUNC | constants.O_CREAT | constants.O_WRONLY
    case 'wx': // Fall through.
    case 'xw':
      return constants.O_TRUNC | constants.O_CREAT | constants.O_WRONLY | constants.O_EXCL

    case 'w+':
      return constants.O_TRUNC | constants.O_CREAT | constants.O_RDWR
    case 'wx+': // Fall through.
    case 'xw+':
      return constants.O_TRUNC | constants.O_CREAT | constants.O_RDWR | constants.O_EXCL

    case 'a':
      return constants.O_APPEND | constants.O_CREAT | constants.O_WRONLY
    case 'ax': // Fall through.
    case 'xa':
      return constants.O_APPEND | constants.O_CREAT | constants.O_WRONLY | constants.O_EXCL
    case 'as': // Fall through.
    case 'sa':
      return constants.O_APPEND | constants.O_CREAT | constants.O_WRONLY | constants.O_SYNC

    case 'a+':
      return constants.O_APPEND | constants.O_CREAT | constants.O_RDWR
    case 'ax+': // Fall through.
    case 'xa+':
      return constants.O_APPEND | constants.O_CREAT | constants.O_RDWR | constants.O_EXCL
    case 'as+': // Fall through.
    case 'sa+':
      return constants.O_APPEND | constants.O_CREAT | constants.O_RDWR | constants.O_SYNC
    default:
      return 0
  }
}

function toMode(mode) {
  return parseInt(mode, 8)
}
const binding = require('../binding')

module.exports = {
  O_RDWR: binding.O_RDWR,
  O_RDONLY: binding.O_RDONLY,
  O_WRONLY: binding.O_WRONLY,
  O_CREAT: binding.O_CREAT,
  O_TRUNC: binding.O_TRUNC,
  O_APPEND: binding.O_APPEND,

  F_OK: binding.F_OK || 0,
  R_OK: binding.R_OK || 0,
  W_OK: binding.W_OK || 0,
  X_OK: binding.X_OK || 0,

  S_IFMT: binding.S_IFMT,
  S_IFREG: binding.S_IFREG,
  S_IFDIR: binding.S_IFDIR,
  S_IFCHR: binding.S_IFCHR,
  S_IFLNK: binding.S_IFLNK,
  S_IFBLK: binding.S_IFBLK || 0,
  S_IFIFO: binding.S_IFIFO || 0,
  S_IFSOCK: binding.S_IFSOCK || 0,

  S_IRUSR: binding.S_IRUSR || 0,
  S_IWUSR: binding.S_IWUSR || 0,
  S_IXUSR: binding.S_IXUSR || 0,
  S_IRGRP: binding.S_IRGRP || 0,
  S_IWGRP: binding.S_IWGRP || 0,
  S_IXGRP: binding.S_IXGRP || 0,
  S_IROTH: binding.S_IROTH || 0,
  S_IWOTH: binding.S_IWOTH || 0,
  S_IXOTH: binding.S_IXOTH || 0,

  UV_DIRENT_UNKNOWN: binding.UV_DIRENT_UNKNOWN,
  UV_DIRENT_FILE: binding.UV_DIRENT_FILE,
  UV_DIRENT_DIR: binding.UV_DIRENT_DIR,
  UV_DIRENT_LINK: binding.UV_DIRENT_LINK,
  UV_DIRENT_FIFO: binding.UV_DIRENT_FIFO,
  UV_DIRENT_SOCKET: binding.UV_DIRENT_SOCKET,
  UV_DIRENT_CHAR: binding.UV_DIRENT_CHAR,
  UV_DIRENT_BLOCK: binding.UV_DIRENT_BLOCK,

  COPYFILE_EXCL: binding.UV_FS_COPYFILE_EXCL,
  COPYFILE_FICLONE: binding.UV_FS_COPYFILE_FICLONE,
  COPYFILE_FICLONE_FORCE: binding.UV_FS_COPYFILE_FICLONE_FORCE,
  UV_FS_SYMLINK_DIR: binding.UV_FS_SYMLINK_DIR,
  UV_FS_SYMLINK_JUNCTION: binding.UV_FS_SYMLINK_JUNCTION
}
const os = require('bare-os')

module.exports = class FileError extends Error {
  constructor(msg, opts = {}) {
    const { code, operation = null, path = null, destination = null, fd = -1 } = opts

    if (operation !== null) msg += describe(operation, opts)

    super(`${code}: ${msg}`)

    this.code = code

    if (operation !== null) this.operation = operation
    if (path !== null) this.path = path
    if (destination !== null) this.destination = destination
    if (fd !== -1) this.fd = fd
  }

  get name() {
    return 'FileError'
  }

  // For Node.js compatibility
  get errno() {
    return os.constants.errnos[this.code]
  }

  // For Node.js compatibility
  get syscall() {
    return this.operation
  }

  // For Node.js compatibility
  get dest() {
    return this.destination
  }
}

function describe(operation, opts) {
  const { path = null, destination = null, fd = -1 } = opts

  let result = `, ${operation}`

  if (path !== null) {
    result += ` ${JSON.stringify(path)}`

    if (destination !== null) {
      result += ` -> ${JSON.stringify(destination)}`
    }
  } else if (fd !== -1) {
    result += ` ${fd}`
  }

  return result
}
{
  "name": "bare-fs",
  "version": "4.7.2",
  "description": "Native file system operations for Bare",
  "exports": {
    "./package": "./package.json",
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    },
    "./promises": {
      "types": "./promises.d.ts",
      "default": "./promises.js"
    },
    "./constants": {
      "types": "./lib/constants.d.ts",
      "default": "./lib/constants.js"
    }
  },
  "files": [
    "index.js",
    "index.d.ts",
    "promises.js",
    "promises.d.ts",
    "binding.c",
    "binding.js",
    "CMakeLists.txt",
    "lib",
    "prebuilds"
  ],
  "addon": true,
  "scripts": {
    "format": "prettier --write . && lunte --fix",
    "lint": "prettier --check . && lunte",
    "test": "brittle-bare --coverage test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-fs.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-fs/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-fs#readme",
  "engines": {
    "bare": ">=1.16.0"
  },
  "dependencies": {
    "bare-events": "^2.5.4",
    "bare-path": "^3.0.0",
    "bare-stream": "^2.6.4",
    "bare-url": "^2.2.2",
    "fast-fifo": "^1.3.2"
  },
  "devDependencies": {
    "bare-buffer": "^3.0.2",
    "bare-crypto": "^1.11.2",
    "brittle": "^3.1.1",
    "cmake-bare": "^1.1.7",
    "lunte": "^1.6.0",
    "prettier": "^3.4.1",
    "prettier-config-holepunch": "^2.0.0"
  },
  "peerDependencies": {
    "bare-buffer": "*"
  },
  "peerDependenciesMeta": {
    "bare-buffer": {
      "optional": true
    }
  }
}
const EventEmitter = require('bare-events')
const fs = require('.')

class FileHandle extends EventEmitter {
  constructor(fd) {
    super()

    this.fd = fd
  }

  async close() {
    await fs.close(this.fd)

    this.fd = -1
    this.emit('close')
  }

  async read(buffer, ...args) {
    return {
      bytesRead: await fs.read(this.fd, buffer, ...args),
      buffer
    }
  }

  async readv(buffers, ...args) {
    return {
      bytesRead: await fs.readv(this.fd, buffers, ...args),
      buffers
    }
  }

  async write(buffer, ...args) {
    return {
      bytesWritten: await fs.write(this.fd, buffer, ...args),
      buffer
    }
  }

  async writev(buffers, ...args) {
    return {
      bytesWritten: await fs.writev(this.fd, buffers, ...args),
      buffers
    }
  }

  async stat() {
    return fs.fstat(this.fd)
  }

  async chmod(mode) {
    await fs.fchmod(this.fd, mode)
  }

  async chown(uid, gid) {
    await fs.fchown(this.fd, uid, gid)
  }

  async datasync() {
    return fs.fdatasync(this.fd)
  }

  async sync() {
    return fs.fsync(this.fd)
  }

  async truncate(len) {
    await fs.ftruncate(this.fd, len)
  }

  async utimes(atime, mtime) {
    await fs.futimes(this.fd, atime, mtime)
  }

  createReadStream(opts) {
    return fs.createReadStream(null, { ...opts, fd: this.fd })
  }

  createWriteStream(opts) {
    return fs.createWriteStream(null, { ...opts, fd: this.fd })
  }

  async [Symbol.asyncDispose]() {
    await this.close()
  }
}

exports.open = async function open(filepath, flags, mode) {
  return new FileHandle(await fs.open(filepath, flags, mode))
}

exports.access = fs.access
exports.appendFile = fs.appendFile
exports.chmod = fs.chmod
exports.chown = fs.chown
exports.constants = fs.constants
exports.copyFile = fs.copyFile
exports.cp = fs.cp
exports.lchown = fs.lchown
exports.lutimes = fs.lutimes
exports.link = fs.link
exports.lstat = fs.lstat
exports.mkdir = fs.mkdir
exports.mkdtemp = fs.mkdtemp
exports.opendir = fs.opendir
exports.readFile = fs.readFile
exports.readdir = fs.readdir
exports.readlink = fs.readlink
exports.realpath = fs.realpath
exports.rename = fs.rename
exports.rm = fs.rm
exports.rmdir = fs.rmdir
exports.stat = fs.stat
exports.statfs = fs.statfs
exports.truncate = fs.truncate
exports.symlink = fs.symlink
exports.unlink = fs.unlink
exports.utimes = fs.utimes
exports.watch = fs.watch
exports.writeFile = fs.writeFile
module.exports = require.addon()
const binding = require('./binding')

module.exports = exports = function hrtime(past) {
  let now = binding.hrtime()

  if (past) now -= BigInt(past[0]) * 1000000000n + BigInt(past[1])

  return [Number(now / 1000000000n), Number(now % 1000000000n)]
}

exports.bigint = function hrtime() {
  return binding.hrtime()
}
{
  "name": "bare-hrtime",
  "version": "2.1.1",
  "description": "High-resolution timers for JavaScript",
  "exports": {
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    },
    "./package": "./package.json"
  },
  "files": [
    "index.js",
    "index.d.ts",
    "binding.c",
    "binding.js",
    "CMakeLists.txt",
    "prebuilds"
  ],
  "addon": true,
  "scripts": {
    "test": "prettier . --check && bare test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-hrtime.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-hrtime/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-hrtime#readme",
  "devDependencies": {
    "brittle": "^3.2.1",
    "cmake-bare": "^1.1.6",
    "prettier": "^3.4.2",
    "prettier-config-standard": "^7.0.0"
  }
}
const errors = require('./lib/errors')

const constants = {
  REQUEST: 1,
  RESPONSE: 2,
  DATA: 3,
  END: 4
}

const TAB = 0x09
const LF = 0x0a
const CR = 0x0d
const SP = 0x20
const ZERO = 0x30
const NINE = 0x39
const UPPER_A = 0x41
const UPPER_F = 0x46
const UPPER_Z = 0x5a
const LOWER_A = 0x61
const LOWER_F = 0x66
const COLON = 0x3a

const MAX_CHUNK_SIZE_LENGTH = 16

// Header states
const FIRST_TOKEN = 0
const REQUEST_URL = 1
const REQUEST_VERSION = 2
const STATUS_CODE = 3
const STATUS_REASON = 4
const FIRST_LINE_LF = 5
const HEADER_START = 6
const HEADER_NAME = 7
const HEADER_VALUE_WS = 8
const HEADER_VALUE = 9
const HEADER_LINE_LF = 10
const HEADER_END_LF = 11

// Body states
const BODY = 12
const CHUNK_SIZE = 13
const CHUNK_SIZE_LF = 14
const CHUNK_DATA = 15
const CHUNK_EXTENSION = 16

// Trailing states
const LAST_CHUNK_LF = 17
const TRAILER_CR = 18
const TRAILER_LF = 19

module.exports = exports = class HTTPParser {
  constructor(opts = {}) {
    const { maxHeaderSize = 16384, maxHeadersCount = 2000 } = opts

    this._maxHeaderSize = maxHeaderSize
    this._maxHeadersCount = maxHeadersCount
    this._state = FIRST_TOKEN
    this._buffer = []
    this._bufferIndex = 0
    this._byteIndex = 0
    this._buffered = 0
    this._accumulator = []

    this._isResponse = false
    this._method = ''
    this._url = ''
    this._version = ''
    this._code = 0
    this._reason = ''

    this._headerName = ''
    this._headers = {}
    this._headerCount = 0
    this._headerSize = 0

    this._remaining = 0
  }

  *push(data, encoding) {
    if (typeof data === 'string') data = Buffer.from(data, encoding)

    this._buffer.push(data)
    this._buffered += data.byteLength

    yield* this._parse()

    this._compact()
  }

  end() {
    const buffers = this._buffer
    const bufferIndex = this._bufferIndex
    const byteIndex = this._byteIndex

    this._buffer = []
    this._bufferIndex = 0
    this._byteIndex = 0
    this._buffered = 0

    if (bufferIndex >= buffers.length) return Buffer.alloc(0)

    buffers[bufferIndex] = buffers[bufferIndex].subarray(byteIndex)

    const remaining = buffers.slice(bufferIndex)

    if (remaining.length === 0) return Buffer.alloc(0)
    if (remaining.length === 1) return remaining[0]

    return Buffer.concat(remaining)
  }

  _compact() {
    if (this._bufferIndex > 0) {
      this._buffer = this._buffer.slice(this._bufferIndex)
      this._bufferIndex = 0
    }

    if (this._byteIndex > 0 && this._buffer.length > 0) {
      this._buffer[0] = this._buffer[0].subarray(this._byteIndex)
      this._byteIndex = 0
    }
  }

  _consume(n) {
    this._buffered -= n

    const current = this._buffer[this._bufferIndex]

    if (this._byteIndex + n <= current.byteLength) {
      const slice = current.subarray(this._byteIndex, this._byteIndex + n)

      this._byteIndex += n

      if (this._byteIndex >= current.byteLength) {
        this._bufferIndex++
        this._byteIndex = 0
      }

      return slice
    }

    const result = Buffer.allocUnsafe(n)

    let written = 0

    while (written < n) {
      const buffer = this._buffer[this._bufferIndex]
      const available = buffer.byteLength - this._byteIndex
      const take = Math.min(available, n - written)

      buffer.copy(result, written, this._byteIndex, this._byteIndex + take)

      written += take

      this._byteIndex += take

      if (this._byteIndex >= buffer.byteLength) {
        this._bufferIndex++
        this._byteIndex = 0
      }
    }

    return result
  }

  _buildString() {
    const string = String.fromCharCode.apply(null, this._accumulator)

    this._accumulator = []

    return string
  }

  _checkHeaderSize() {
    if (++this._headerSize > this._maxHeaderSize) {
      throw errors.INVALID_MESSAGE('Header exceeds limit of ' + this._maxHeaderSize + ' bytes')
    }
  }

  _storeHeader(name, value) {
    let end = value.length

    while (end > 0 && (value.charCodeAt(end - 1) === SP || value.charCodeAt(end - 1) === TAB)) {
      end--
    }

    if (end < value.length) value = value.substring(0, end)

    this._headerCount++

    if (this._headerCount > this._maxHeadersCount) {
      throw errors.INVALID_MESSAGE('Header count exceeds limit of ' + this._maxHeadersCount)
    }

    switch (name) {
      case '__proto__':
      case 'constructor':
      case 'prototype':
        throw errors.INVALID_HEADER("Unsafe header name '" + name + "'")

      case 'host':
      case 'content-length':
      case 'transfer-encoding':
        if (name in this._headers) {
          throw errors.INVALID_HEADER("Duplicate header '" + name + "'")
        }

        this._headers[name] = value
        break

      default:
        const delimiter = name === 'cookie' ? '; ' : ', '

        if (name in this._headers) {
          this._headers[name] += delimiter + value
        } else {
          this._headers[name] = value
        }
    }
  }

  *_parse() {
    while (true) {
      if (this._state === BODY) {
        if (this._buffered === 0) return

        const available = Math.min(this._buffered, this._remaining)
        const data = this._consume(available)

        this._remaining -= available

        const ended = this._remaining === 0

        if (ended) this._state = FIRST_TOKEN

        yield { type: constants.DATA, data }

        if (ended) yield { type: constants.END }

        continue
      }

      if (this._state === CHUNK_DATA) {
        if (this._buffered < this._remaining) return

        const consumed = this._consume(this._remaining)

        if (consumed[this._remaining - 2] !== CR || consumed[this._remaining - 1] !== LF) {
          throw errors.INVALID_MESSAGE('Expected CRLF after chunk data')
        }

        const data = consumed.subarray(0, this._remaining - 2)

        this._remaining = 0
        this._state = CHUNK_SIZE

        yield { type: constants.DATA, data }

        continue
      }

      if (this._buffered === 0) return

      const byte = this._buffer[this._bufferIndex][this._byteIndex++]

      this._buffered--

      if (this._byteIndex >= this._buffer[this._bufferIndex].byteLength) {
        this._bufferIndex++
        this._byteIndex = 0
      }

      switch (this._state) {
        case FIRST_TOKEN: {
          this._checkHeaderSize()

          if (byte === SP) {
            const token = this._buildString()

            if (token.length === 0) throw errors.INVALID_MESSAGE()

            this._isResponse = token.startsWith('HTTP/')

            if (this._isResponse) {
              if (token !== 'HTTP/1.0' && token !== 'HTTP/1.1') {
                throw errors.INVALID_MESSAGE()
              }

              this._version = token
              this._state = STATUS_CODE
            } else {
              this._method = token
              this._state = REQUEST_URL
            }
          } else if (byte === CR) {
            throw errors.INVALID_MESSAGE()
          } else if (isTokenByte(byte)) {
            this._accumulator.push(byte)
          } else if (
            byte === 0x2f &&
            this._accumulator.length === 4 &&
            this._accumulator[0] === 0x48 &&
            this._accumulator[1] === 0x54 &&
            this._accumulator[2] === 0x54 &&
            this._accumulator[3] === 0x50
          ) {
            this._accumulator.push(byte)
          } else {
            throw errors.INVALID_MESSAGE()
          }

          break
        }

        case REQUEST_URL: {
          this._checkHeaderSize()

          if (byte === SP) {
            this._url = this._buildString()

            if (this._url.length === 0) throw errors.INVALID_MESSAGE()

            this._state = REQUEST_VERSION
          } else if (byte === CR) {
            throw errors.INVALID_MESSAGE()
          } else if (byte >= 0x21 && byte !== 0x7f) {
            this._accumulator.push(byte)
          } else {
            throw errors.INVALID_MESSAGE()
          }

          break
        }

        case REQUEST_VERSION: {
          this._checkHeaderSize()

          if (byte === CR) {
            this._version = this._buildString()

            if (this._version !== 'HTTP/1.0' && this._version !== 'HTTP/1.1') {
              throw errors.INVALID_MESSAGE()
            }

            this._state = FIRST_LINE_LF
          } else if (byte >= 0x21 && byte !== 0x7f) {
            this._accumulator.push(byte)
          } else {
            throw errors.INVALID_MESSAGE()
          }

          break
        }

        case STATUS_CODE: {
          this._checkHeaderSize()

          if (byte === SP) {
            if (this._accumulator.length === 0) throw errors.INVALID_MESSAGE()

            let code = 0

            for (let i = 0, n = this._accumulator.length; i < n; i++) {
              code = code * 10 + this._accumulator[i]
            }

            this._accumulator = []

            if (code < 100 || code > 999) throw errors.INVALID_MESSAGE()

            this._code = code
            this._state = STATUS_REASON
          } else if (byte >= ZERO && byte <= NINE) {
            this._accumulator.push(byte - ZERO)
          } else {
            throw errors.INVALID_MESSAGE()
          }

          break
        }

        case STATUS_REASON: {
          this._checkHeaderSize()

          if (byte === CR) {
            this._reason = this._buildString()
            this._state = FIRST_LINE_LF
          } else if (isFieldByte(byte)) {
            this._accumulator.push(byte)
          } else {
            throw errors.INVALID_MESSAGE()
          }

          break
        }

        case FIRST_LINE_LF: {
          if (byte !== LF) throw errors.INVALID_MESSAGE()

          this._headers = {}
          this._headerCount = 0
          this._state = HEADER_START

          break
        }

        case HEADER_START: {
          this._checkHeaderSize()

          if (byte === CR) {
            this._state = HEADER_END_LF
          } else if (byte !== COLON && isTokenByte(byte)) {
            this._accumulator.push(byte >= UPPER_A && byte <= UPPER_Z ? byte + 0x20 : byte)
            this._state = HEADER_NAME
          } else {
            throw errors.INVALID_HEADER()
          }

          break
        }

        case HEADER_NAME: {
          this._checkHeaderSize()

          if (byte === COLON) {
            this._headerName = this._buildString()
            this._state = HEADER_VALUE_WS
          } else if (byte !== COLON && isTokenByte(byte)) {
            this._accumulator.push(byte >= UPPER_A && byte <= UPPER_Z ? byte + 0x20 : byte)
          } else {
            throw errors.INVALID_HEADER()
          }

          break
        }

        case HEADER_VALUE_WS: {
          this._checkHeaderSize()

          if (byte === SP || byte === TAB) break

          if (byte === CR) {
            this._storeHeader(this._headerName, '')
            this._headerName = ''
            this._state = HEADER_LINE_LF

            break
          }

          if (!isFieldByte(byte)) throw errors.INVALID_HEADER()

          this._accumulator.push(byte)
          this._state = HEADER_VALUE

          break
        }

        case HEADER_VALUE: {
          this._checkHeaderSize()

          if (byte === CR) {
            this._storeHeader(this._headerName, this._buildString())
            this._headerName = ''
            this._state = HEADER_LINE_LF
          } else if (isFieldByte(byte)) {
            this._accumulator.push(byte)
          } else {
            throw errors.INVALID_HEADER()
          }

          break
        }

        case HEADER_LINE_LF: {
          if (byte !== LF) throw errors.INVALID_HEADER()

          this._state = HEADER_START

          break
        }

        case HEADER_END_LF: {
          if (byte !== LF) throw errors.INVALID_MESSAGE()

          const headers = this._headers

          if (this._isResponse) {
            yield {
              type: constants.RESPONSE,
              version: this._version,
              code: this._code,
              reason: this._reason,
              headers
            }
          } else {
            if (this._version === 'HTTP/1.1' && !('host' in headers)) {
              throw errors.INVALID_HEADER("Header 'Host' is missing")
            }

            yield {
              type: constants.REQUEST,
              version: this._version,
              method: this._method,
              url: this._url,
              headers
            }
          }

          const transferEncoding = headers['transfer-encoding']
          const contentLength = headers['content-length']

          const encodings = transferEncoding ? transferEncoding.split(',') : null

          const lastEncoding = encodings
            ? encodings[encodings.length - 1].trim().toLowerCase()
            : null

          if (lastEncoding === 'chunked') {
            if (contentLength) {
              throw errors.INVALID_MESSAGE(
                "Conflicting 'Content-Length' and 'Transfer-Encoding' headers"
              )
            }

            this._state = CHUNK_SIZE
            this._headerSize = 0

            continue
          }

          if (contentLength) {
            if (contentLength.length === 0) throw errors.INVALID_CONTENT_LENGTH()

            let length = 0

            for (let i = 0, n = contentLength.length; i < n; i++) {
              const c = contentLength.charCodeAt(i)

              if (c < ZERO || c > NINE) throw errors.INVALID_CONTENT_LENGTH()

              length = length * 10 + (c - ZERO)
            }

            if (!Number.isSafeInteger(length) || length < 0) {
              throw errors.INVALID_CONTENT_LENGTH()
            }

            if (length === 0) {
              this._state = FIRST_TOKEN
              this._headerSize = 0

              yield { type: constants.END }
            } else {
              this._state = BODY
              this._remaining = length
              this._headerSize = 0
            }
          } else {
            this._state = FIRST_TOKEN
            this._headerSize = 0

            yield { type: constants.END }
          }

          break
        }

        case CHUNK_SIZE: {
          if (byte === CR || byte === 0x3b) {
            if (this._accumulator.length === 0) throw errors.INVALID_CHUNK_LENGTH()

            let length = 0

            for (let i = 0, n = this._accumulator.length; i < n; i++) {
              length = length * 16 + this._accumulator[i]
            }

            this._accumulator = []

            if (!Number.isSafeInteger(length)) throw errors.INVALID_CHUNK_LENGTH()

            if (byte === 0x3b) {
              this._remaining = length
              this._state = CHUNK_EXTENSION
            } else if (length === 0) {
              this._state = LAST_CHUNK_LF
            } else {
              this._remaining = length + 2
              this._state = CHUNK_SIZE_LF
            }
          } else if (isHex(byte)) {
            if (this._accumulator.length >= MAX_CHUNK_SIZE_LENGTH) {
              throw errors.INVALID_CHUNK_LENGTH()
            }

            this._accumulator.push(hexValue(byte))
          } else {
            throw errors.INVALID_CHUNK_LENGTH()
          }

          break
        }

        case CHUNK_EXTENSION: {
          this._checkHeaderSize()

          if (byte === CR) {
            if (this._remaining === 0) {
              this._state = LAST_CHUNK_LF
            } else {
              this._remaining += 2
              this._state = CHUNK_SIZE_LF
            }
          } else if (!isFieldByte(byte)) {
            throw errors.INVALID_CHUNK_LENGTH()
          }

          break
        }

        case CHUNK_SIZE_LF: {
          if (byte !== LF) throw errors.INVALID_CHUNK_LENGTH()

          this._state = CHUNK_DATA

          break
        }

        case LAST_CHUNK_LF: {
          if (byte !== LF) throw errors.INVALID_CHUNK_LENGTH()

          this._state = TRAILER_CR

          break
        }

        case TRAILER_CR: {
          if (byte !== CR) throw errors.INVALID_MESSAGE()

          this._state = TRAILER_LF

          break
        }

        case TRAILER_LF: {
          if (byte !== LF) throw errors.INVALID_MESSAGE()

          this._state = FIRST_TOKEN
          this._headerSize = 0

          yield { type: constants.END }

          break
        }

        default:
          throw errors.INVALID_MESSAGE()
      }
    }
  }
}

exports.constants = constants

const TOKEN_BYTES = Buffer.from([
  // 0x00-0x1f (control characters) + 0x20 (space)
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  // 0x21-0x41: ! " # $ % & ' ( ) * + , - . / 0-9 : ; < = > ? @ A
  1, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1,
  // 0x42-0x62: B-Z [ \ ] ^ _ ` a b
  1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1,
  // 0x63-0x7f: c-z { | } ~ DEL
  1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0
])

function isTokenByte(b) {
  return TOKEN_BYTES[b] === 1
}

function isFieldByte(b) {
  return b === TAB || (b >= 0x20 && b <= 0x7e)
}

function isHex(b) {
  return (
    (b >= ZERO && b <= NINE) || (b >= UPPER_A && b <= UPPER_F) || (b >= LOWER_A && b <= LOWER_F)
  )
}

function hexValue(b) {
  if (b >= ZERO && b <= NINE) return b - ZERO
  if (b >= UPPER_A && b <= UPPER_F) return b - UPPER_A + 10
  return b - LOWER_A + 10
}
module.exports = class HTTPParserError extends Error {
  constructor(msg, fn = HTTPParserError, code = fn.name) {
    super(`${code}: ${msg}`)

    this.code = code

    if (Error.captureStackTrace) Error.captureStackTrace(this, fn)
  }

  get name() {
    return 'HTTPParserError'
  }

  static INVALID_MESSAGE(msg = 'Invalid HTTP message') {
    return new HTTPParserError(msg, HTTPParserError.INVALID_MESSAGE)
  }

  static INVALID_HEADER(msg = 'Invalid HTTP header') {
    return new HTTPParserError(msg, HTTPParserError.INVALID_HEADER)
  }

  static INVALID_CONTENT_LENGTH(msg = 'Invalid HTTP Content-Length') {
    return new HTTPParserError(msg, HTTPParserError.INVALID_CONTENT_LENGTH)
  }

  static INVALID_CHUNK_LENGTH(msg = 'Invalid HTTP chunk length') {
    return new HTTPParserError(msg, HTTPParserError.INVALID_CHUNK_LENGTH)
  }
}
{
  "name": "bare-http-parser",
  "version": "1.1.3",
  "description": "Streaming HTTP request and response parser for Bare",
  "exports": {
    "./package": "./package.json",
    ".": "./index.js",
    "./errors": "./lib/errors.js"
  },
  "files": [
    "index.js",
    "lib"
  ],
  "scripts": {
    "test": "prettier . --check && bare test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-http-parser.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-http-parser/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-http-parser#readme",
  "devDependencies": {
    "brittle": "^3.3.0",
    "prettier": "^3.4.1",
    "prettier-config-holepunch": "^2.0.0"
  }
}
exports.IncomingMessage = require('./lib/incoming-message')
exports.OutgoingMessage = require('./lib/outgoing-message')

exports.Agent = require('./lib/agent')
exports.globalAgent = exports.Agent.global

exports.Server = require('./lib/server')
exports.ServerResponse = require('./lib/server-response')
exports.ServerConnection = require('./lib/server-connection')

exports.ClientRequest = require('./lib/client-request')
exports.ClientConnection = require('./lib/client-connection')

exports.constants = require('./lib/constants')
exports.errors = require('./lib/errors')

exports.METHODS = Object.values(exports.constants.method) // For Node.js compatibility
exports.STATUS_CODES = exports.constants.status // For Node.js compatibility

exports.createServer = function createServer(opts, onrequest) {
  return new exports.Server(opts, onrequest)
}

exports.request = function request(url, opts, onresponse) {
  if (typeof opts === 'function') {
    onresponse = opts
    opts = {}
  }

  if (typeof url === 'string') url = new URL(url)

  if (isURL(url)) {
    opts = opts ? { ...url, ...opts } : { ...url }

    opts.host = url.hostname
    opts.path = url.pathname + url.search
    opts.port = url.port ? parseInt(url.port, 10) : defaultPort(url)
  } else {
    opts = url ? { ...url } : {}

    // For Node.js compatibility
    opts.host = opts.hostname || opts.host
    opts.port = typeof opts.port === 'string' ? parseInt(opts.port, 10) : opts.port
  }

  return new exports.ClientRequest(opts, onresponse)
}

exports.get = function get(url, opts, onresponse) {
  const req = exports.request(url, opts, onresponse)
  req.end()
  return req
}

// https://url.spec.whatwg.org/#default-port
function defaultPort(url) {
  switch (url.protocol) {
    case 'ftp:':
      return 21
    case 'http:':
    case 'ws:':
      return 80
    case 'https:':
    case 'wss:':
      return 443
  }

  return null
}

// https://url.spec.whatwg.org/#api
function isURL(url) {
  return (
    url !== null &&
    typeof url === 'object' &&
    typeof url.protocol === 'string' &&
    typeof url.hostname === 'string' &&
    typeof url.pathname === 'string' &&
    typeof url.search === 'string'
  )
}
const EventEmitter = require('bare-events')
const tcp = require('bare-tcp')
const HTTPClientConnection = require('./client-connection')
const errors = require('./errors')

class HTTPSocketSet {
  constructor() {
    this._sockets = new Map()
    this._size = 0
  }

  get size() {
    return this._size
  }

  add(name, socket) {
    const sockets = this._sockets.get(name)

    this._size++

    if (sockets === undefined) this._sockets.set(name, [socket])
    else sockets.push(socket)
  }

  pop(name) {
    const sockets = this._sockets.get(name)
    if (sockets === undefined || sockets.length === 0) return null

    this._size--

    const last = sockets.pop()

    if (sockets.length === 0) this._sockets.delete(name)

    return last
  }

  delete(name, socket) {
    const sockets = this._sockets.get(name)
    if (sockets === undefined) return

    const i = sockets.indexOf(socket)
    if (i === -1) return

    this._size--

    const last = sockets.pop()
    if (last !== socket) sockets[i] = last

    if (sockets.length === 0) this._sockets.delete(name)
  }

  *sockets() {
    for (const sockets of this._sockets.values()) {
      yield* sockets
    }
  }

  *[Symbol.iterator]() {
    for (const [name, sockets] of this._sockets) {
      for (const socket of sockets) yield [name, socket]
    }
  }
}

class HTTPAgent extends EventEmitter {
  constructor(opts = {}) {
    super()

    const { keepAlive = false, keepAliveMsecs = 1000, defaultPort = 80 } = opts

    this._suspended = false
    this._resuming = null

    this._sockets = new HTTPSocketSet()
    this._freeSockets = new HTTPSocketSet()

    this._keepAlive = typeof keepAlive === 'number' ? keepAlive : keepAlive ? keepAliveMsecs : -1
    this._defaultPort = defaultPort

    this._opts = { ...opts }
  }

  get suspended() {
    return this._suspended
  }

  get resumed() {
    return this._resuming ? this._resuming.promise : null
  }

  get sockets() {
    return this._sockets.sockets()
  }

  get freeSockets() {
    return this._freeSockets.sockets()
  }

  get defaultPort() {
    return this._defaultPort
  }

  createConnection(opts) {
    if (this._suspended) throw errors.AGENT_SUSPENDED()

    return tcp.createConnection(opts)
  }

  reuseSocket(socket, req) {
    socket.ref()
  }

  keepSocketAlive(socket) {
    if (this._keepAlive === -1) return false

    socket.setKeepAlive(true, this._keepAlive)
    socket.unref()

    return true
  }

  getName(opts) {
    return `${opts.host}:${opts.port}`
  }

  addRequest(req, opts) {
    opts = { ...opts, ...this._opts }

    const name = this.getName(opts)

    let socket = this._freeSockets.pop(name)

    if (socket) this.reuseSocket(socket, req)
    else {
      const agent = this

      socket = this.createConnection(opts)

      socket
        .on('error', noop) // someone needs to handle it
        .on('free', onfree)
        .on('end', onremove)
        .on('finish', onremove)
        .on('close', onremove)
        .on('timeout', ontimeout)

      function onfree() {
        if (socket.destroyed) return

        if (agent.keepSocketAlive(socket)) {
          agent._freeSockets.add(name, socket)
        } else {
          socket.end()
        }

        agent.emit('free', socket)
      }

      function onremove() {
        socket.off('free', onfree)

        agent._sockets.delete(name, socket)
        agent._freeSockets.delete(name, socket)

        if (agent._sockets.size === 0) HTTPAgent._agents.delete(agent)
      }

      function ontimeout() {
        socket.destroy()

        agent._freeSockets.delete(name, socket)
      }
    }

    if (this._sockets.size === 0) HTTPAgent._agents.add(this)

    this._sockets.add(name, socket)

    req._socket = socket

    const connection = HTTPClientConnection.from(socket, opts)

    connection._req = req
  }

  suspend() {
    if (this._suspended) return

    this._resuming = Promise.withResolvers()
    this._suspended = true

    this.destroy()
  }

  resume() {
    if (this._resuming === null) return

    this._resuming.resolve()
    this._resuming = null
    this._suspended = false
  }

  destroy() {
    for (const socket of this._sockets.sockets()) socket.destroy()
  }

  static _agents = new Set()

  static _onidle() {
    for (const agent of this._agents) {
      agent.destroy()
    }
  }
}

HTTPAgent.global = new HTTPAgent({ keepAlive: 1000, timeout: 5000 })

module.exports = HTTPAgent

Bare.on('idle', HTTPAgent._onidle.bind(HTTPAgent))

function noop() {}
const HTTPParser = require('bare-http-parser')
const HTTPIncomingMessage = require('./incoming-message')
const errors = require('./errors')

const {
  constants: { RESPONSE, DATA, END }
} = HTTPParser

const EMPTY = Buffer.alloc(0)

module.exports = class HTTPClientConnection {
  static _connections = new WeakMap()

  static for(socket) {
    return this._connections.get(socket) || null
  }

  static from(socket, opts) {
    return this.for(socket) || new this(socket, opts)
  }

  constructor(socket, opts = {}) {
    const { IncomingMessage = HTTPIncomingMessage } = opts

    this._socket = socket

    this._req = null
    this._res = null

    this._IncomingMessage = IncomingMessage

    this._parser = new HTTPParser()
    this._idle = true

    this._onerror = this._onerror.bind(this)
    this._onclose = this._onclose.bind(this)
    this._onend = this._onend.bind(this)
    this._ondata = this._ondata.bind(this)
    this._ondrain = this._ondrain.bind(this)
    this._ontimeout = this._ontimeout.bind(this)

    socket
      .on('error', this._onerror)
      .on('close', this._onclose)
      .on('end', this._onend)
      .on('data', this._ondata)
      .on('drain', this._ondrain)
      .on('timeout', this._ontimeout)

    HTTPClientConnection._connections.set(socket, this)
  }

  get socket() {
    return this._socket
  }

  get req() {
    return this._req
  }

  get res() {
    return this._res
  }

  get idle() {
    return this._idle
  }

  _onerror(err) {
    if (this._req) this._req.destroy(err)
  }

  _onclose() {
    if (this._req) this._req.destroy()
  }

  _onend() {
    if (this._req) this._req.destroy(errors.CONNECTION_LOST())
  }

  _ondata(data) {
    this._idle = false

    try {
      for (const op of this._parser.push(data)) {
        switch (op.type) {
          case RESPONSE:
            this._req.on('close', () => {
              this._req = null
            })

            this._res = new this._IncomingMessage(this._socket, {
              headers: op.headers,
              statusCode: op.code,
              statusMessage: op.reason
            })

            this._res.on('close', () => {
              this._res = null
              this._idle = true

              this._socket.emit('free')
            })

            if (op.headers.connection && op.headers.connection.toLowerCase() === 'upgrade') {
              return this._onupgrade(this._parser.end())
            }

            this._req.emit('response', this._res)
            break

          case DATA:
            this._res.push(op.data)
            break

          case END:
            if (this._res) {
              this._res._socket = null
              this._res.push(null)
            }

            if (this._req) {
              this._req._socket = null
              this._req.destroy()
            }
            break
        }
      }
    } catch (err) {
      this._socket.destroy(err)
    }
  }

  _onupgrade(data) {
    this._detach()

    const res = this._res
    const req = this._req

    res._upgrade = req._upgrade = true

    const upgraded = req.emit('upgrade', res, this._socket, data || EMPTY)

    res.push(null)
    req.destroy()

    if (!upgraded) this._socket.destroy()
  }

  _ontimeout() {
    if (this._req) this._req.emit('timeout')
  }

  _ondrain() {
    if (this._req) this._req._continueWrite()
  }

  _detach() {
    this._socket
      .off('error', this._onerror)
      .off('close', this._onclose)
      .off('end', this._onend)
      .off('data', this._ondata)
      .off('drain', this._ondrain)
      .off('timeout', this._ontimeout)

    HTTPClientConnection._connections.delete(this._socket)
  }
}
const HTTPAgent = require('./agent')
const HTTPOutgoingMessage = require('./outgoing-message')

const CHUNK_DELIMITER = Buffer.from('\r\n')
const CHUNK_TERMINATOR = Buffer.from('0\r\n\r\n')

module.exports = class HTTPClientRequest extends HTTPOutgoingMessage {
  constructor(opts = {}, onresponse = null) {
    if (typeof opts === 'function') {
      onresponse = opts
      opts = {}
    }

    opts = opts ? { ...opts } : {}

    const agent = opts.agent === false ? new HTTPAgent() : opts.agent || HTTPAgent.global
    const method = opts.method || 'GET'
    const path = opts.path || '/'
    const defaultPort = opts.defaultPort || (agent && agent.defaultPort) || 80
    const host = (opts.host = opts.host || 'localhost')
    const port = (opts.port = opts.port || defaultPort)
    const headers = { host: hostHeader(host, port, defaultPort), ...opts.headers }

    super()

    agent.addRequest(this, opts)

    this._headers = headers
    this._method = method
    this._path = path

    this._chunked = method !== 'GET' && method !== 'HEAD'

    this._pendingWrite = null
    this._pendingFinal = null

    if (onresponse) this.once('response', onresponse)
  }

  get method() {
    return this._method
  }

  get path() {
    return this._path
  }

  // For Node.js compatibility
  abort() {
    return this.destroy()
  }

  _header() {
    let h = `${this._method} ${this._path} HTTP/1.1\r\n`

    let upgrade = false

    for (const name of Object.keys(this._headers)) {
      const n = name.toLowerCase()
      const v = this._headers[name]

      if (n === 'content-length') this._chunked = false
      if (n === 'connection' && v && v.toLowerCase() === 'upgrade') upgrade = true

      h += `${httpCase(n)}: ${v}\r\n`
    }

    if (upgrade) this._chunked = false

    if (this._chunked) h += 'Transfer-Encoding: chunked\r\n'

    h += '\r\n'

    return h
  }

  _write(data, encoding, cb) {
    if (this._headersSent === false) this.flushHeaders()

    if (this._chunked) {
      this._socket.write(Buffer.from(data.byteLength.toString(16)))
      this._socket.write(CHUNK_DELIMITER)
    }

    let flushed = this._socket.write(data)

    if (this._chunked) flushed = this._socket.write(CHUNK_DELIMITER)

    if (flushed) cb(null)
    else this._pendingWrite = cb
  }

  _final(cb) {
    if (this._headersSent === false) this.flushHeaders()

    if (this._chunked) this._socket.write(CHUNK_TERMINATOR)

    this._pendingFinal = cb
  }

  _predestroy() {
    super._predestroy()

    this._continueWrite()
    this._continueFinal()
  }

  _continueWrite() {
    if (this._pendingWrite === null) return
    const cb = this._pendingWrite
    this._pendingWrite = null
    cb(null)
  }

  _continueFinal() {
    if (this._pendingFinal === null) return
    const cb = this._pendingFinal
    this._pendingFinal = null
    cb(null)
  }
}

function httpCase(n) {
  let s = ''
  for (const part of n.split('-')) {
    s += (s ? '-' : '') + part.slice(0, 1).toUpperCase() + part.slice(1)
  }
  return s
}

function hostHeader(host, port, defaultPort) {
  const i = host.indexOf(':')

  if (i !== -1 && host.includes(':', i + 1) && host.charCodeAt(0) !== 91 /* [ */) {
    host = `[${host}]`
  }

  if (port && +port !== defaultPort) {
    host += ':' + port
  }

  return host
}
module.exports = {
  method: {
    GET: 'GET',
    HEAD: 'HEAD',
    POST: 'POST',
    PUT: 'PUT',
    DELETE: 'DELETE',
    CONNECT: 'CONNECT',
    OPTIONS: 'OPTIONS',
    TRACE: 'TRACE',
    PATCH: 'PATCH'
  },
  status: {
    100: 'Continue',
    101: 'Switching Protocols',
    102: 'Processing',
    103: 'Early Hints',
    200: 'OK',
    201: 'Created',
    202: 'Accepted',
    203: 'Non-Authoritative Information',
    204: 'No Content',
    205: 'Reset Content',
    206: 'Partial Content',
    207: 'Multi-Status',
    208: 'Already Reported',
    226: 'IM Used',
    300: 'Multiple Choices',
    301: 'Moved Permanently',
    302: 'Found',
    303: 'See Other',
    304: 'Not Modified',
    305: 'Use Proxy',
    307: 'Temporary Redirect',
    308: 'Permanent Redirect',
    400: 'Bad Request',
    401: 'Unauthorized',
    402: 'Payment Required',
    403: 'Forbidden',
    404: 'Not Found',
    405: 'Method Not Allowed',
    406: 'Not Acceptable',
    407: 'Proxy Authentication Required',
    408: 'Request Timeout',
    409: 'Conflict',
    410: 'Gone',
    411: 'Length Required',
    412: 'Precondition Failed',
    413: 'Payload Too Large',
    414: 'URI Too Long',
    415: 'Unsupported Media Type',
    416: 'Range Not Satisfiable',
    417: 'Expectation Failed',
    418: "I'm a Teapot",
    421: 'Misdirected Request',
    422: 'Unprocessable Entity',
    423: 'Locked',
    424: 'Failed Dependency',
    425: 'Too Early',
    426: 'Upgrade Required',
    428: 'Precondition Required',
    429: 'Too Many Requests',
    431: 'Request Header Fields Too Large',
    451: 'Unavailable For Legal Reasons',
    500: 'Internal Server Error',
    501: 'Not Implemented',
    502: 'Bad Gateway',
    503: 'Service Unavailable',
    504: 'Gateway Timeout',
    505: 'HTTP Version Not Supported',
    506: 'Variant Also Negotiates',
    507: 'Insufficient Storage',
    508: 'Loop Detected',
    509: 'Bandwidth Limit Exceeded',
    510: 'Not Extended',
    511: 'Network Authentication Required'
  }
}
module.exports = class HTTPError extends Error {
  constructor(msg, fn = HTTPError, code = fn.name) {
    super(`${code}: ${msg}`)
    this.code = code

    if (Error.captureStackTrace) {
      Error.captureStackTrace(this, fn)
    }
  }

  get name() {
    return 'HTTPError'
  }

  static NOT_IMPLEMENTED(msg = 'Method not implemented') {
    return new HTTPError(msg, HTTPError.NOT_IMPLEMENTED)
  }

  static CONNECTION_LOST(msg = 'Socket hung up') {
    return new HTTPError(msg, HTTPError.CONNECTION_LOST)
  }

  static AGENT_SUSPENDED(msg = 'Agent is suspended') {
    return new HTTPError(msg, HTTPError.AGENT_SUSPENDED)
  }
}
const { Readable } = require('bare-stream')

module.exports = class HTTPIncomingMessage extends Readable {
  constructor(socket = null, opts = {}) {
    super()

    this._socket = socket
    this._upgrade = false
    this._headers = opts.headers || {}

    // Server options
    this._method = opts.method || ''
    this._url = opts.url || ''

    // Client options
    this._statusCode = opts.statusCode || 0
    this._statusMessage = opts.statusMessage || ''
  }

  get socket() {
    return this._socket
  }

  get upgrade() {
    return this._upgrade
  }

  get headers() {
    return this._headers
  }

  set headers(value) {
    this._headers = value
  }

  get method() {
    return this._method
  }

  set method(value) {
    this._method = value
  }

  get url() {
    return this._url
  }

  set url(value) {
    this._url = value
  }

  get statusCode() {
    return this._statusCode
  }

  set statusCode(value) {
    this._statusCode = value
  }

  get statusMessage() {
    return this._statusMessage
  }

  set statusMessage(value) {
    this._statusMessage = value
  }

  get httpVersion() {
    return '1.1'
  }

  getHeader(name) {
    return this._headers[name.toLowerCase()]
  }

  getHeaders() {
    return { ...this._headers }
  }

  hasHeader(name) {
    return name.toLowerCase() in this._headers
  }

  setTimeout(ms, ontimeout) {
    if (ontimeout) this.once('timeout', ontimeout)

    this._socket.setTimeout(ms)

    return this
  }

  _predestroy() {
    if (this._upgrade === false && this._socket !== null) this._socket.destroy()
  }
}
const { Writable } = require('bare-stream')
const errors = require('./errors')

module.exports = class HTTPOutgoingMessage extends Writable {
  constructor(socket = null) {
    super()

    this._socket = socket
    this._upgrade = false
    this._headersSent = false
    this._headers = {}
  }

  get socket() {
    return this._socket
  }

  get upgrade() {
    return this._upgrade
  }

  get headersSent() {
    return this._headersSent
  }

  get headers() {
    return this._headers
  }

  set headers(value) {
    this._headers = value
  }

  getHeader(name) {
    return this._headers[name.toLowerCase()]
  }

  getHeaders() {
    return { ...this._headers }
  }

  hasHeader(name) {
    return name.toLowerCase() in this._headers
  }

  setHeader(name, value) {
    this._headers[name.toLowerCase()] = value
  }

  flushHeaders() {
    if (this._headersSent === true || this._socket === null) return

    this._socket.write(Buffer.from(this._header()))
    this._headersSent = true
  }

  setTimeout(ms, ontimeout) {
    if (ontimeout) this.once('timeout', ontimeout)

    this._socket.setTimeout(ms)

    return this
  }

  _header() {
    throw errors.NOT_IMPLEMENTED()
  }

  _predestroy() {
    if (this._upgrade === false && this._socket !== null) this._socket.destroy()
  }
}
const { isEnded, isFinished, getStreamError } = require('bare-stream')
const HTTPParser = require('bare-http-parser')
const HTTPIncomingMessage = require('./incoming-message')
const HTTPServerResponse = require('./server-response')

const {
  constants: { REQUEST, DATA, END }
} = HTTPParser

const EMPTY = Buffer.alloc(0)

module.exports = class HTTPServerConnection {
  static _connections = new WeakMap()

  static for(socket) {
    return this._connections.get(socket) || null
  }

  constructor(server, socket, opts = {}) {
    const { IncomingMessage = HTTPIncomingMessage, ServerResponse = HTTPServerResponse } = opts

    this._server = server
    this._socket = socket

    this._req = null
    this._res = null

    this._IncomingMessage = IncomingMessage
    this._ServerResponse = ServerResponse

    this._parser = new HTTPParser()
    this._idle = true

    this._onclose = this._onclose.bind(this)
    this._ondata = this._ondata.bind(this)
    this._ondrain = this._ondrain.bind(this)
    this._ontimeout = this._ontimeout.bind(this)

    socket
      .on('error', noop)
      .on('close', this._onclose)
      .on('data', this._ondata)
      .on('drain', this._ondrain)
      .on('timeout', this._ontimeout)

    HTTPServerConnection._connections.set(socket, this)

    if (this._server.timeout) socket.setTimeout(this._server.timeout)
  }

  get server() {
    return this._server
  }

  get socket() {
    return this._socket
  }

  get req() {
    return this._req
  }

  get res() {
    return this._res
  }

  get idle() {
    return this._idle
  }

  _onclose() {
    if (this._req && !isEnded(this._req)) this._req.destroy()
    if (this._res && !isFinished(this._res)) this._res.destroy()

    const err = getStreamError(this._socket)
    if (err) this._socket.destroy(err)
  }

  _ondata(data) {
    this._idle = false

    try {
      for (const op of this._parser.push(data)) {
        switch (op.type) {
          case REQUEST:
            this._req = new this._IncomingMessage(this._socket, {
              headers: op.headers,
              method: op.method,
              url: op.url
            })

            this._req.on('close', () => {
              this._req = null

              this._idle = true

              if (this._server.closing) this._socket.destroy()
            })

            // Eagerly open the request stream
            this._req.resume()
            this._req.pause()

            if (op.headers.connection && op.headers.connection.toLowerCase() === 'upgrade') {
              return this._onupgrade(this._parser.end())
            }

            this._res = new this._ServerResponse(this._socket, this._req)

            this._res.on('close', () => {
              this._res = null
            })

            this._server.emit('request', this._req, this._res)
            break

          case DATA:
            this._req.push(op.data)
            break

          case END:
            if (this._req) {
              this._req._socket = null
              this._req.push(null)
            }
            break
        }
      }
    } catch (err) {
      this._socket.destroy(err)
    }
  }

  _onupgrade(data) {
    this._detach()

    const req = this._req

    req._upgrade = true

    const upgraded = this._server.emit('upgrade', req, this._socket, data || EMPTY)

    req.push(null)

    if (!upgraded) this._socket.destroy()
  }

  _ontimeout() {
    const reqTimeout = this._req && this._req.emit('timeout')
    const resTimeout = this._res && this._res.emit('timeout')
    const serverTimeout = this._server.emit('timeout', this._socket)

    if (!reqTimeout && !resTimeout && !serverTimeout) this._socket.destroy()
  }

  _ondrain() {
    if (this._res) this._res._continueWrite()
  }

  _detach() {
    this._socket
      .off('error', noop)
      .off('close', this._onclose)
      .off('data', this._ondata)
      .off('drain', this._ondrain)
      .off('timeout', this._ontimeout)

    HTTPServerConnection._connections.delete(this._socket)
  }
}

function noop() {}
const { isFinishing } = require('bare-stream')
const HTTPOutgoingMessage = require('./outgoing-message')
const constants = require('./constants')

const CHUNK_DELIMITER = Buffer.from('\r\n')
const CHUNK_TERMINATOR = Buffer.from('0\r\n\r\n')

module.exports = class HTTPServerResponse extends HTTPOutgoingMessage {
  constructor(socket, req) {
    super(socket)

    this._req = req

    this._statusCode = 200
    this._statusMessage = null

    this._chunked = true
    this._close = req.headers.connection === 'close'
    this._onlyHeaders = req.method === 'HEAD'

    this._pendingWrite = null
  }

  get req() {
    return this._req
  }

  get statusCode() {
    return this._statusCode
  }

  set statusCode(value) {
    this._statusCode = value
  }

  get statusMessage() {
    return this._statusMessage
  }

  set statusMessage(value) {
    this._statusMessage = value
  }

  writeHead(statusCode, statusMessage = null, headers = {}) {
    if (typeof statusMessage === 'object' && statusMessage !== null) {
      headers = statusMessage
      statusMessage = null
    }

    this._statusCode = statusCode
    this._statusMessage = statusMessage || null

    if (headers) this._headers = { ...this._headers, ...headers }
  }

  _header() {
    let h =
      'HTTP/1.1 ' +
      this._statusCode +
      ' ' +
      (this._statusMessage === null ? constants.status[this._statusCode] : this._statusMessage) +
      '\r\n'

    for (const name of Object.keys(this._headers)) {
      const n = name.toLowerCase()
      const v = this._headers[name]

      if (n === 'content-length') this._chunked = false
      if (n === 'connection' && v && v.toLowerCase() === 'close') this._close = true

      h += httpCase(n) + ': ' + v + '\r\n'
    }

    if (this._chunked) h += 'Transfer-Encoding: chunked\r\n'

    h += '\r\n'

    return h
  }

  _write(data, encoding, cb) {
    if (this._headersSent === false) {
      if (isFinishing(this)) {
        this.setHeader(
          'Content-Length',
          (data.byteLength + this._writableState.buffered).toString()
        )
      }

      this.flushHeaders()
    }

    if (this._onlyHeaders === true) return cb(null)

    if (this._chunked) {
      this._socket.write(Buffer.from(data.byteLength.toString(16)))
      this._socket.write(CHUNK_DELIMITER)
    }

    let flushed = this._socket.write(data)

    if (this._chunked) flushed = this._socket.write(CHUNK_DELIMITER)

    if (flushed) cb(null)
    else this._pendingWrite = cb
  }

  _final(cb) {
    if (this._headersSent === false) {
      this.setHeader('Content-Length', '0')
      this.flushHeaders()
    }

    if (this._chunked && this._onlyHeaders === false) this._socket.write(CHUNK_TERMINATOR)

    if (this._close) this._socket.end()

    cb(null)
  }

  _predestroy() {
    super._predestroy()

    this._req.destroy()

    this._continueWrite()
  }

  _continueWrite() {
    if (this._pendingWrite === null) return
    const cb = this._pendingWrite
    this._pendingWrite = null
    cb(null)
  }
}

function httpCase(n) {
  let s = ''
  for (const part of n.split('-')) {
    s += (s ? '-' : '') + part.slice(0, 1).toUpperCase() + part.slice(1)
  }
  return s
}
const TCPServer = require('bare-tcp').Server
const HTTPServerConnection = require('./server-connection')

module.exports = class HTTPServer extends TCPServer {
  constructor(opts = {}, onrequest) {
    if (typeof opts === 'function') {
      onrequest = opts
      opts = {}
    }

    super({ allowHalfOpen: false })

    this._timeout = 0

    this.on('connection', (socket) => {
      new HTTPServerConnection(this, socket, opts)
    })

    if (onrequest) this.on('request', onrequest)
  }

  get timeout() {
    return this._timeout || undefined // For Node.js compatibility
  }

  setTimeout(ms = 0, ontimeout) {
    if (ontimeout) this.on('timeout', ontimeout)

    this._timeout = ms

    return this
  }

  close(onclose) {
    super.close(onclose)

    for (const socket of this.connections) {
      const connection = HTTPServerConnection.for(socket)

      if (connection === null || connection.idle) {
        socket.destroy()
      }
    }

    return this
  }
}
{
  "name": "bare-http1",
  "version": "4.5.6",
  "description": "Native HTTP/1 library for JavaScript",
  "exports": {
    "./package": "./package.json",
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    },
    "./constants": {
      "types": "./lib/constants.d.ts",
      "default": "./lib/constants.js"
    },
    "./errors": {
      "types": "./lib/errors.d.ts",
      "default": "./lib/errors.js"
    }
  },
  "files": [
    "index.js",
    "index.d.ts",
    "lib"
  ],
  "scripts": {
    "format": "prettier --write . && lunte --fix",
    "lint": "prettier --check . && lunte",
    "test": "brittle-bare --coverage test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-http1.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-http1/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-http1#readme",
  "dependencies": {
    "bare-events": "^2.6.0",
    "bare-http-parser": "^1.1.1",
    "bare-stream": "^2.10.0",
    "bare-tcp": "^2.2.0"
  },
  "devDependencies": {
    "bare-buffer": "^3.0.2",
    "bare-url": "^2.1.3",
    "brittle": "^3.3.0",
    "lunte": "^1.6.0",
    "prettier": "^3.4.1",
    "prettier-config-holepunch": "^2.0.0"
  },
  "peerDependencies": {
    "bare-buffer": "*",
    "bare-url": "*"
  },
  "peerDependenciesMeta": {
    "bare-buffer": {
      "optional": true
    },
    "bare-url": {
      "optional": true
    }
  }
}
module.exports = require.addon()
const ansiEscapes = require('bare-ansi-escapes')
const getType = require('bare-type')
const binding = require('./binding')

const PLAIN_KEY = /^[a-zA-Z_][a-zA-Z_0-9]*$/

const defaultDepth = 2
const defaultBreakLength = 80
const defaultMaxArrayLength = 40

module.exports = exports = function inspect(value, opts = {}) {
  const {
    colors = false,
    depth = defaultDepth,
    breakLength = defaultBreakLength,
    stylize = defaultStylize(colors)
  } = opts

  const references = new InspectRefMap()

  const tree = inspectValue(value, 0, {
    colors,
    depth,
    breakLength,
    stylize,
    references
  })

  return tree.toString()
}

exports.styles = {
  bigint: ansiEscapes.colorYellow,
  boolean: ansiEscapes.colorYellow,
  date: ansiEscapes.colorMagenta,
  module: ansiEscapes.modifierUnderline,
  name: ansiEscapes.modifierReset,
  null: ansiEscapes.modifierBold,
  number: ansiEscapes.colorYellow,
  regexp: ansiEscapes.colorRed,
  special: ansiEscapes.colorCyan,
  string: ansiEscapes.colorGreen,
  symbol: ansiEscapes.colorGreen,
  undefined: ansiEscapes.colorBrightBlack
}

const styles = exports.styles

function defaultStylize(colors) {
  return function stylize(value, style) {
    const color = colors && styles[style]

    if (color) return color + value + ansiEscapes.modifierReset

    return value
  }
}

class InspectRefMap {
  constructor() {
    this.refs = new WeakMap()
    this.ids = new WeakMap()
    this.nextId = 1
  }

  has(object) {
    return this.refs.has(object)
  }

  get(object) {
    return this.refs.get(object) || null
  }

  set(object, ref) {
    this.refs.set(object, ref)
  }

  id(object) {
    let id = this.ids.get(object)
    if (id) return id

    id = this.nextId++
    this.ids.set(object, id)

    return id
  }
}

class InspectNode {
  constructor(depth, length, opts) {
    const { breakLength = defaultBreakLength, breakAlways = false } = opts

    this.depth = depth
    this.length = length
    this.breakLength = breakLength
    this.breakAlways = breakAlways
  }

  pad(n, string) {
    return string.padStart(n, ' ')
  }

  indent(n, string) {
    return '  '.repeat(n) + string
  }
}

class InspectRef extends InspectNode {
  constructor(depth, opts) {
    super(depth, '[circular *]'.length, opts)

    this.refs = opts.references
    this.count = 0
    this.circular = false
    this.color = opts.colors && styles.special
  }

  get id() {
    return this.refs.id(this)
  }

  increment() {
    return ++this.count
  }

  decrement() {
    return --this.count
  }

  toString(opts = {}) {
    const { offset = 0, pad = 0, indent = 0 } = opts

    let value = this.pad(pad, '[circular *' + this.id + ']')

    if (this.color) value = this.color + value + ansiEscapes.modifierReset

    return offset ? value : this.indent(indent, value)
  }
}

class InspectLeaf extends InspectNode {
  constructor(value, color, depth, opts) {
    const length = value.length

    if (value.includes('\n')) {
      value = value.replaceAll('\n', '\n' + '  '.repeat(depth))

      opts = { ...opts, breakAlways: true }
    }

    super(depth, length, opts)

    this.value = value
    this.color = opts.colors && color
  }

  toString(opts = {}) {
    const { offset = 0, pad = 0, indent = 0 } = opts

    let value = this.pad(pad, this.value)

    if (this.color) value = this.color + value + ansiEscapes.modifierReset

    return offset ? value : this.indent(indent, value)
  }
}

class InspectPair extends InspectNode {
  constructor(delim, left, right, depth, opts) {
    const length = left.length + delim.length + right.length

    if (left.breakAlways || right.breakAlways) {
      opts = { ...opts, breakAlways: true }
    }

    super(depth, length, opts)

    this.delim = delim
    this.left = left
    this.right = right
  }

  toString(opts = {}) {
    const { indent = 0 } = opts

    return this.indent(
      indent,
      this.left +
        this.delim +
        this.right.toString({
          indent,
          offset: this.left.length + this.delim.length
        })
    )
  }
}

class InspectSuspension extends InspectNode {
  constructor(overflow, depth, opts) {
    const label = `... ${overflow} more`

    super(depth, label.length, opts)

    this.label = label
  }

  toString(opts = {}) {
    const { indent = 0 } = opts

    return this.indent(indent, this.label)
  }
}

class InspectSequence extends InspectNode {
  constructor(header, footer, delim, values, ref, depth, opts) {
    const { tabulate = false } = opts

    const length =
      (ref.circular ? '<ref *>'.length + 1 : 0) +
      header.length +
      values.reduce(
        (length, value, i) =>
          length + value.length + (i === 0 ? 0 : delim.length),
        0
      ) +
      footer.length

    if (values.some((value) => value.breakAlways)) {
      opts = { ...opts, breakAlways: true }
    }

    super(depth, length, opts)

    this.header = header
    this.footer = footer
    this.delim = delim
    this.values = values
    this.ref = ref
    this.tabulate = tabulate
  }

  toString(opts = {}) {
    const { offset = 0, indent = 0 } = opts

    const split =
      this.breakAlways ||
      (this.values.length &&
        (offset + this.length > this.breakLength ||
          indent * 2 + this.length > this.breakLength))

    let header = this.header

    if (this.ref.circular) {
      header = '<ref *' + this.ref.id + '> ' + header
    }

    if (this.values.length === 0) {
      header = header.trimEnd()
    }

    if (offset === 0) {
      header = this.indent(indent, header)
    }

    if (split) {
      header = header.trimEnd() + '\n'
    }

    let string = header

    let columns = 1
    let pad = 0

    if (this.tabulate) {
      const widest = this.values.reduce(
        (length, value) =>
          value.breakAlways ? length : Math.max(length, value.length),
        0
      )

      if (widest) {
        columns = Math.max(
          columns,
          Math.floor(
            (this.breakLength - indent * 2) / (widest + this.delim.length)
          )
        )

        if (columns > 1) pad = widest
      }
    }

    for (let i = 0, n = this.values.length, offset = 0; i < n; i++) {
      const value = this.values[i]

      if (split) {
        let part

        if (i % columns === 0 || value.breakAlways) {
          part = value.toString({ indent: indent + 1, pad })
        } else {
          part = value.toString({ pad })
        }

        string += part

        if (i < n - 1) {
          if (i % columns === columns - 1 || this.values[i + 1].breakAlways) {
            string += this.delim.trimEnd() + '\n'
          } else {
            string += this.delim
          }
        }
      } else {
        if (i > 0) string += this.delim

        string += value.toString({ offset })

        offset += value.length
      }
    }

    let footer = this.footer

    if (this.values.length === 0) {
      footer = footer.trimStart()
    }

    if (split) {
      string += '\n' + this.indent(indent, footer.trimStart())
    } else {
      string += footer
    }

    return string
  }
}

function inspectValue(value, depth, opts) {
  const type = getType(value)

  if (type.isUndefined()) return inspectUndefined(depth, opts)
  if (type.isNull()) return inspectNull(depth, opts)
  if (type.isBoolean()) return inspectBoolean(value, depth, opts)
  if (type.isNumber()) return inspectNumber(value, depth, opts)
  if (type.isBigInt()) return inspectBigInt(value, depth, opts)
  if (type.isString()) return inspectString(value, depth, opts)
  if (type.isSymbol()) return inspectSymbol(value, depth, opts)
  if (type.isObject()) return inspectObject(type, value, depth, opts)
  if (type.isFunction()) return inspectFunction(type, value, depth, opts)
  if (type.isExternal()) return inspectExternal(value, opts, opts)
}

function inspectUndefined(depth, opts) {
  return new InspectLeaf('undefined', styles.undefined, depth, opts)
}

function inspectNull(depth, opts) {
  return new InspectLeaf('null', styles.null, depth, opts)
}

function inspectBoolean(value, depth, opts) {
  return new InspectLeaf(value.toString(), styles.boolean, depth, opts)
}

function inspectNumber(value, depth, opts) {
  let string

  if (Object.is(value, -0)) {
    string = '-0'
  } else {
    string = value.toString(10)
  }

  return new InspectLeaf(string, styles.number, depth, opts)
}

function inspectBigInt(value, depth, opts) {
  return new InspectLeaf(value.toString(10) + 'n', styles.bigint, depth, opts)
}

const STRING_ESCAPES =
  /[\ud800-\udbff][\udc00-\udfff]|[\u0000-\u001f'\\\ud800-\udfff]/g

function inspectString(value, depth, opts) {
  // https://tc39.es/ecma262/multipage/structured-data.html#sec-quotejsonstring
  const string = value.replace(STRING_ESCAPES, (match) => {
    if (match.length === 2) return match

    switch (match) {
      case "'":
        return "\\'"
      case '\\':
        return '\\\\'
      case '\b':
        return '\\b'
      case '\t':
        return '\\t'
      case '\n':
        return '\\n'
      case '\f':
        return '\\f'
      case '\r':
        return '\\r'
      default:
        return '\\u' + match.charCodeAt(0).toString(16).padStart(4, '0')
    }
  })

  return new InspectLeaf("'" + string + "'", styles.string, depth, opts)
}

function inspectSymbol(value, depth, opts) {
  return new InspectLeaf(value.toString(), styles.symbol, depth, opts)
}

function inspectKey(value, depth, opts) {
  if (PLAIN_KEY.test(value)) {
    return new InspectLeaf(value, null, depth, opts)
  } else {
    return inspectValue(value, depth, opts)
  }
}

function inspectObject(type, object, depth, opts) {
  const refs = opts.references

  let ref = refs.get(object)
  if (ref === null) {
    ref = new InspectRef(depth, opts)
    refs.set(object, ref)
  } else if (ref.count) {
    ref.circular = true
    return ref
  }

  const maxDepth = typeof opts.depth === 'number' ? opts.depth : Infinity

  if (maxDepth < depth) {
    const constructor = object.constructor

    return new InspectLeaf(
      '[' +
        (constructor && constructor.name ? constructor.name : 'Object') +
        ']',
      styles.special,
      depth,
      opts
    )
  }

  const inspect =
    object[Symbol.for('bare.inspect')] ||
    object[Symbol.for('nodejs.util.inspect.custom')]

  if (typeof inspect === 'function') {
    const value = inspect.call(
      object,
      typeof opts.depth === 'number' ? opts.depth - depth : null,
      {
        colors: opts.colors,
        breakLength: opts.breakLength,
        stylize: opts.stylize
      },
      exports
    )

    if (typeof value === 'object' && value !== null) {
      refs.set(value, ref)
    }

    if (typeof value !== 'string') {
      return inspectValue(value, depth, opts)
    }

    return new InspectLeaf(value, null, depth, opts)
  }

  if (type.isArray()) return inspectArray(object, ref, depth, opts)
  if (type.isDate()) return inspectDate(object, ref, depth, opts)
  if (type.isRegExp()) return inspectRegExp(object, ref, depth, opts)
  if (type.isError()) return inspectError(object, ref, depth, opts)
  if (type.isPromise()) return inspectPromise(object, ref, depth, opts)
  if (type.isMap()) return inspectMap(object, ref, depth, opts)
  if (type.isSet()) return inspectSet(object, ref, depth, opts)
  if (type.isWeakMap()) return inspectWeakMap(object, ref, depth, opts)
  if (type.isWeakSet()) return inspectWeakSet(object, ref, depth, opts)
  if (type.isWeakRef()) return inspectWeakRef(object, ref, depth, opts)
  if (type.isArrayBuffer()) return inspectArrayBuffer(object, ref, depth, opts)
  if (type.isSharedArrayBuffer())
    return inspectSharedArrayBuffer(object, ref, depth, opts)
  if (type.isTypedArray()) return inspectTypedArray(object, ref, depth, opts)
  if (type.isDataView()) return inspectDataView(object, ref, depth, opts)

  ref.increment()

  const values = []

  for (const key in object) {
    if (key === 'constructor') continue

    values.push(
      new InspectPair(
        ': ',
        inspectKey(key, depth + 1, opts),
        inspectValue(object[key], depth + 1, opts),
        depth + 1,
        opts
      )
    )
  }

  ref.decrement()

  let header = '{ '

  const tag = object[Symbol.toStringTag]

  if (tag) header = '[' + tag + '] ' + header

  if (object.constructor) {
    const name = object.constructor.name

    if (name && name !== 'Object') {
      header = object.constructor.name + ' ' + header
    }
  }

  return new InspectSequence(header, ' }', ', ', values, ref, depth, opts)
}

function inspectArray(array, ref, depth, opts) {
  const { maxArrayLength = defaultMaxArrayLength } = opts

  ref.increment()

  const values = []

  let remaining = Math.max(maxArrayLength, 0)

  for (let i = 0, n = array.length; i < n; i++) {
    if (remaining-- === 0) {
      values.push(
        new InspectSuspension(array.length - values.length, depth + 1, {
          ...opts,
          breakAlways: true
        })
      )

      break
    }

    values.push(inspectValue(array[i], depth + 1, opts))
  }

  for (const key of binding.getOwnNonIndexPropertyNames(array)) {
    values.push(
      new InspectPair(
        ': ',
        inspectKey(key, depth + 1, opts),
        inspectValue(array[key], depth + 1, opts),
        depth + 1,
        { ...opts, breakAlways: remaining < 0 }
      )
    )
  }

  ref.decrement()

  let header = '[ '

  if (array.constructor.name !== 'Array') {
    header = array.constructor.name + '(' + array.length + ') ' + header
  }

  return new InspectSequence(header, ' ]', ', ', values, ref, depth, {
    ...opts,
    tabulate: true
  })
}

function inspectDate(date, ref, depth, opts) {
  return new InspectLeaf(date.toISOString(), styles.date, depth, opts)
}

function inspectRegExp(regExp, ref, depth, opts) {
  return new InspectLeaf(regExp.toString(), styles.regexp, depth, opts)
}

function inspectError(error, ref, depth, opts) {
  let header

  if ('stack' in error) {
    header = error.stack

    if (depth > 0) {
      header = header.replaceAll('\n', '\n' + '  '.repeat(depth))
    }
  } else {
    header = error.toString()
  }

  const builtins = ['cause']

  if (error.name === 'AggregateError') {
    builtins.push('errors')
  } else if (error.name === 'SuppressedError') {
    builtins.push('error', 'suppressed')
  }

  const values = []

  for (const key of builtins) {
    if (key in error === false) continue

    values.push(
      new InspectPair(
        ': ',
        new InspectLeaf('[' + key + ']', null, depth + 1, opts),
        inspectValue(error[key], depth + 1, opts),
        depth + 1,
        opts
      )
    )
  }

  for (const key in error) {
    if (key === 'constructor' || builtins.includes(key)) continue

    values.push(
      new InspectPair(
        ': ',
        inspectKey(key, depth + 1, opts),
        inspectValue(error[key], depth + 1, opts),
        depth + 1,
        opts
      )
    )
  }

  if (values.length === 0) return new InspectLeaf(header, null, depth, opts)

  return new InspectSequence(
    header + ' {',
    ' }',
    ', ',
    values,
    ref,
    depth,
    opts
  )
}

function inspectPromise(promise, ref, depth, opts) {
  ref.increment()

  const state = binding.getPromiseState(promise)

  const values = []

  switch (state) {
    case 0: // Pending
      values.push(new InspectLeaf('<pending>', styles.special, depth, opts))
      break

    case 1: // Fulfilled
      values.push(inspectValue(binding.getPromiseResult(promise), depth, opts))
      break

    case 2: // Rejected
      values.push(
        new InspectLeaf('<rejected>', styles.special, depth, opts),
        inspectValue(binding.getPromiseResult(promise), depth, opts)
      )
  }

  ref.decrement()

  const header = promise.constructor.name + ' { '

  return new InspectSequence(header, ' }', ' ', values, ref, depth, opts)
}

function inspectMap(map, ref, depth, opts) {
  const {
    maxArrayLength = defaultMaxArrayLength,
    maxMapLength = maxArrayLength
  } = opts

  ref.increment()

  const values = []

  let remaining = maxMapLength

  for (const entry of map) {
    if (remaining-- === 0) {
      values.push(
        new InspectSuspension(map.size - values.length, depth + 1, {
          ...opts,
          breakAlways: true
        })
      )
      break
    }

    values.push(
      new InspectPair(
        ' => ',
        inspectValue(entry[0], depth + 1, opts),
        inspectValue(entry[1], depth + 1, opts),
        depth + 1,
        opts
      )
    )
  }

  for (const key in map) {
    if (key === 'constructor') continue

    const value = inspectValue(map[key], depth + 1, opts)

    values.push(
      new InspectPair(
        ': ',
        inspectKey(key, depth + 1, opts),
        value,
        depth + 1,
        { ...opts, breakAlways: remaining < 0 }
      )
    )
  }

  ref.decrement()

  const header = map.constructor.name + '(' + map.size + ') { '

  return new InspectSequence(header, ' }', ', ', values, ref, depth, {
    ...opts,
    tabulate: true
  })
}

function inspectSet(set, ref, depth, opts) {
  const {
    maxArrayLength = defaultMaxArrayLength,
    maxSetLength = maxArrayLength
  } = opts

  ref.increment()

  const values = []

  let remaining = maxSetLength

  for (const entry of set) {
    if (remaining-- === 0) {
      values.push(
        new InspectSuspension(set.size - values.length, depth + 1, {
          ...opts,
          breakAlways: true
        })
      )
      break
    }

    values.push(inspectValue(entry, depth + 1, opts))
  }

  for (const key in set) {
    if (key === 'constructor') continue

    const value = inspectValue(set[key], depth + 1, opts)

    values.push(
      new InspectPair(
        ': ',
        inspectKey(key, depth + 1, opts),
        value,
        depth + 1,
        { ...opts, breakAlways: remaining < 0 }
      )
    )
  }

  ref.decrement()

  const header = set.constructor.name + '(' + set.size + ') { '

  return new InspectSequence(header, ' }', ', ', values, ref, depth, {
    ...opts,
    tabulate: true
  })
}

function inspectWeakMap(weakMap, ref, depth, opts) {
  const header = weakMap.constructor.name + ' { '

  return new InspectSequence(
    header,
    ' }',
    ' ',
    [new InspectLeaf('<items unknown>', styles.special, depth + 1, opts)],
    ref,
    depth,
    opts
  )
}

function inspectWeakSet(weakSet, ref, depth, opts) {
  const header = weakSet.constructor.name + ' { '

  return new InspectSequence(
    header,
    ' }',
    ' ',
    [new InspectLeaf('<items unknown>', styles.special, depth + 1, opts)],
    ref,
    depth,
    opts
  )
}

function inspectWeakRef(weakRef, ref, depth, opts) {
  const target = weakRef.deref()

  let value

  if (target === undefined) {
    value = new InspectLeaf('<cleared>', styles.special, depth + 1, opts)
  } else {
    value = inspectValue(target, depth + 1, opts)
  }

  const header = weakRef.constructor.name + ' { '

  return new InspectSequence(header, ' }', ' ', [value], ref, depth, opts)
}

function inspectArrayBuffer(arrayBuffer, ref, depth, opts) {
  ref.increment()

  const values = []

  for (const key of ['byteLength']) {
    values.push(
      new InspectPair(
        ': ',
        inspectKey(key, depth + 1, opts),
        inspectValue(arrayBuffer[key], depth + 1, opts),
        depth + 1,
        opts
      )
    )
  }

  ref.decrement()

  const header = arrayBuffer.constructor.name + ' { '

  return new InspectSequence(header, ' }', ', ', values, ref, depth, opts)
}

function inspectSharedArrayBuffer(sharedArrayBuffer, ref, depth, opts) {
  ref.increment()

  const values = []

  for (const key of ['byteLength']) {
    values.push(
      new InspectPair(
        ': ',
        inspectKey(key, depth + 1, opts),
        inspectValue(sharedArrayBuffer[key], depth + 1, opts),
        depth + 1,
        opts
      )
    )
  }

  ref.decrement()

  const header = sharedArrayBuffer.constructor.name + ' { '

  return new InspectSequence(header, ' }', ', ', values, ref, depth, opts)
}

function inspectTypedArray(typedArray, ref, depth, opts) {
  if (Buffer.isBuffer(typedArray)) {
    return inspectBuffer(typedArray, ref, depth, opts)
  }

  const {
    maxArrayLength = defaultMaxArrayLength,
    maxTypedArrayLength = maxArrayLength
  } = opts

  ref.increment()

  const values = []

  let remaining = Math.max(maxTypedArrayLength, 0)

  for (let i = 0, n = typedArray.length; i < n; i++) {
    if (remaining-- === 0) {
      values.push(
        new InspectSuspension(typedArray.length - values.length, depth + 1, {
          ...opts,
          breakAlways: true
        })
      )

      break
    }

    values.push(inspectValue(typedArray[i], depth + 1, opts))
  }

  for (const key of binding.getOwnNonIndexPropertyNames(typedArray)) {
    values.push(
      new InspectPair(
        ': ',
        inspectKey(key, depth + 1, opts),
        inspectValue(typedArray[key], depth + 1, opts),
        depth + 1,
        { ...opts, breakAlways: remaining < 0 }
      )
    )
  }

  ref.decrement()

  const header = typedArray.constructor.name + '(' + typedArray.length + ') [ '

  return new InspectSequence(header, ' ]', ', ', values, ref, depth, {
    ...opts,
    tabulate: true
  })
}

function inspectBuffer(buffer, ref, depth, opts) {
  const {
    maxArrayLength = defaultMaxArrayLength,
    maxBufferLength = maxArrayLength
  } = opts

  ref.increment()

  const values = []

  let remaining = Math.max(maxBufferLength, 0)

  for (let i = 0, n = buffer.byteLength; i < n; i++) {
    if (remaining-- === 0) {
      values.push(
        new InspectSuspension(buffer.length - values.length, depth + 1, {
          ...opts,
          breakAlways: true
        })
      )

      break
    }

    values.push(
      new InspectLeaf(
        buffer[i].toString(16).padStart(2, '0'),
        null,
        depth + 1,
        opts
      )
    )
  }

  for (const key of binding.getOwnNonIndexPropertyNames(buffer)) {
    values.push(
      new InspectPair(
        ': ',
        inspectKey(key, depth + 1, opts),
        inspectValue(buffer[key], depth + 1, opts),
        depth + 1,
        { ...opts, breakAlways: remaining < 0 }
      )
    )
  }

  ref.decrement()

  return new InspectSequence('<Buffer ', '>', ' ', values, ref, depth, {
    ...opts,
    tabulate: true
  })
}

function inspectDataView(dataView, ref, depth, opts) {
  ref.increment()

  const values = []

  for (const key of ['byteLength', 'byteOffset', 'buffer']) {
    values.push(
      new InspectPair(
        ': ',
        inspectKey(key, depth + 1, opts),
        inspectValue(dataView[key], depth + 1, opts),
        depth + 1,
        opts
      )
    )
  }

  for (const key of binding.getOwnNonIndexPropertyNames(dataView)) {
    values.push(
      new InspectPair(
        ': ',
        inspectKey(key, depth + 1, opts),
        inspectValue(dataView[key], depth + 1, opts),
        depth + 1,
        opts
      )
    )
  }

  ref.decrement()

  const header = dataView.constructor.name + ' { '

  return new InspectSequence(header, ' }', ', ', values, ref, depth, opts)
}

function inspectFunction(type, fn, depth, opts) {
  if (fn.toString().startsWith('class')) return inspectClass(fn, depth, opts)

  let tag = 'function'

  if (type.isGeneratorFunction()) tag = 'generator ' + tag
  if (type.isAsyncFunction()) tag = 'async ' + tag

  return new InspectLeaf(
    '[' + tag + ' ' + (fn.name ? fn.name : '(anonymous)') + ']',
    styles.special,
    depth,
    opts
  )
}

function inspectClass(ctor, depth, opts) {
  return new InspectLeaf(
    '[class ' + (ctor.name ? ctor.name : '(anonymous)') + ']',
    styles.special,
    depth,
    opts
  )
}

function inspectExternal(external, depth, opts) {
  return new InspectLeaf(
    '[external 0x' + binding.getExternal(external).toString(16) + ']',
    styles.special,
    depth,
    opts
  )
}
{
  "name": "bare-inspect",
  "version": "3.1.4",
  "description": "Inspect objects as strings for debugging",
  "exports": {
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    },
    "./package": "./package.json"
  },
  "files": [
    "index.js",
    "index.d.ts",
    "binding.c",
    "binding.js",
    "CMakeLists.txt",
    "prebuilds"
  ],
  "addon": true,
  "scripts": {
    "test": "prettier . --check && bare test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-inspect.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-inspect/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-inspect#readme",
  "engines": {
    "bare": ">=1.18.0"
  },
  "dependencies": {
    "bare-ansi-escapes": "^2.1.0",
    "bare-type": "^1.0.0"
  },
  "devDependencies": {
    "brittle": "^3.3.2",
    "cmake-bare": "^1.1.6",
    "prettier": "^3.4.1",
    "prettier-config-standard": "^7.0.0"
  }
}
const EventEmitter = require('bare-events')
const { Duplex } = require('bare-stream')
const tcp = require('bare-tcp')
const pipe = require('bare-pipe')

const defaultReadBufferSize = 65536

const constants = exports.constants = {
  type: {
    TCP: 1,
    IPC: 2
  },
  state: {
    UNREFED: 0x1
  }
}

const Socket = exports.Socket = class NetSocket extends Duplex {
  constructor (opts = {}) {
    super({ eagerOpen: true })

    const {
      readBufferSize = defaultReadBufferSize,
      allowHalfOpen = true
    } = opts

    this._opts = { readBufferSize, allowHalfOpen }
    this._type = 0
    this._state = 0
    this._socket = null

    this._pendingWrite = null
    this._pendingFinal = null
  }

  get connecting () {
    return this._socket !== null && this._socket.connecting
  }

  get pending () {
    return this._socket === null || this._socket.pending
  }

  connect (...args) {
    let opts = {}
    let onconnect

    // connect(path[, onconnect])
    if (typeof args[0] === 'string') {
      opts.path = args[0]
      onconnect = args[1]
    // connect(port[, host][, onconnect])
    } else if (typeof args[0] === 'number') {
      opts.port = args[0]

      if (typeof args[1] === 'function') {
        onconnect = args[1]
      } else {
        opts.host = args[1]
        onconnect = args[2]
      }
    // connect(opts[, onconnect])
    } else {
      opts = args[0] || {}
      onconnect = args[1]
    }

    opts = { ...opts, ...this._opts }

    if (opts.path) {
      this._attach(constants.type.IPC, pipe.createConnection(opts))
    } else {
      this._attach(constants.type.TCP, tcp.createConnection(opts))
    }

    if (onconnect) this.once('connect', onconnect)

    return this
  }

  ref () {
    this._state &= ~constants.state.UNREFED
    if (this._socket !== null) this._socket.ref()
  }

  unref () {
    this._state |= constants.state.UNREFED
    if (this._socket !== null) this._socket.unref()
  }

  _attach (type, socket) {
    this._type = type
    this._socket = socket

    this._socket
      .on('connect', this._onconnect.bind(this))
      .on('error', this._onerror.bind(this))
      .on('close', this._onclose.bind(this))
      .on('end', this._onend.bind(this))
      .on('data', this._ondata.bind(this))
      .on('drain', this._ondrain.bind(this))

    if (this._state & constants.state.UNREFED) this._socket.unref()

    return this
  }

  _write (data, encoding, cb) {
    if (this._socket !== null && this._socket.write(data)) cb(null)
    else this._pendingWrite = cb
  }

  _final (cb) {
    if (this._socket === null) return cb(null)
    this._pendingFinal = cb
    this._socket.end()
  }

  _predestroy () {
    if (this._socket === null) return
    this._socket.destroy()
  }

  _onconnect () {
    this._ondrain() // Flush any pending writes

    this.emit('connect')
  }

  _onerror (err) {
    this.destroy(err)
  }

  _onend () {
    if (this._pendingFinal === null) return
    const cb = this._pendingFinal
    this._pendingFinal = null
    cb(null)
  }

  _onclose () {
    this.push(null)
  }

  _ondata (data) {
    this.push(data)
  }

  _ondrain () {
    if (this._pendingWrite === null) return
    const cb = this._pendingWrite
    this._pendingWrite = null
    cb(null)
  }
}

const Server = exports.Server = class NetServer extends EventEmitter {
  constructor (opts = {}, onconnection) {
    if (typeof opts === 'function') {
      onconnection = opts
      opts = {}
    }

    super()

    const {
      readBufferSize = defaultReadBufferSize,
      allowHalfOpen = true
    } = opts

    this._opts = { readBufferSize, allowHalfOpen }
    this._type = 0
    this._state = 0
    this._server = null

    if (onconnection) this.on('connection', onconnection)
  }

  get listening () {
    return this._server !== null && this._server.listening
  }

  address () {
    return this._server === null ? null : this._server.address()
  }

  listen (...args) {
    let opts = {}
    let onlistening

    // listen(path[, backlog][, onlistening])
    if (typeof args[0] === 'string') {
      opts.path = args[0]

      if (typeof args[1] === 'function') {
        onlistening = args[1]
      } else {
        opts.backlog = args[1]
        onlistening = args[2]
      }
    // listen([port[, host[, backlog]]][, onlistening])
    } else {
      if (typeof args[0] === 'function') {
        onlistening = args[0]
      } else {
        opts.port = args[0]

        if (typeof args[1] === 'function') {
          onlistening = args[1]
        } else {
          opts.host = args[1]

          if (typeof args[2] === 'function') {
            onlistening = args[2]
          } else {
            opts.backlog = args[2]
            onlistening = args[3]
          }
        }
      }
    }

    opts = { ...opts, ...this._opts }

    if (opts.path) {
      this._attach(constants.type.IPC, pipe.createServer(opts))
    } else {
      this._attach(constants.type.TCP, tcp.createServer(opts))
    }

    this._server.listen(opts)

    if (onlistening) this.once('listening', onlistening)

    return this
  }

  close (onclose) {
    if (onclose) this.once('close', onclose)
    this._server.close()
  }

  ref () {
    this._state &= ~constants.state.UNREFED
    if (this._server !== null) this._server.ref()
  }

  unref () {
    this._state |= constants.state.UNREFED
    if (this._server !== null) this._server.unref()
  }

  _attach (type, server) {
    this._type = type
    this._server = server

    this._server
      .on('listening', this._onlistening.bind(this))
      .on('connection', this._onconnection.bind(this))
      .on('error', this._onerror.bind(this))
      .on('close', this._onclose.bind(this))

    if (this._state & constants.state.UNREFED) this._server.unref()

    return this
  }

  _onlistening () {
    this.emit('listening')
  }

  _onconnection (socket) {
    this.emit('connection', new Socket(this._opts)._attach(this._type, socket))
  }

  _onerror (err) {
    this.emit('error', err)
  }

  _onclose () {
    this.emit('close')
  }
}

exports.isIP = tcp.isIP
exports.isIPv4 = tcp.isIPv4
exports.isIPv6 = tcp.isIPv6

exports.createConnection = function createConnection (...args) {
  let opts = {}
  let onconnect

  // createConnection(path[, onconnect])
  if (typeof args[0] === 'string') {
    opts.path = args[0]
    onconnect = args[1]
  // createConnection(port[, host][, onconnect])
  } else if (typeof args[0] === 'number') {
    opts.port = args[0]

    if (typeof args[1] === 'function') {
      onconnect = args[1]
    } else {
      opts.host = args[1]
      onconnect = args[2]
    }
  // createConnection(opts[, onconnect])
  } else {
    opts = args[0] || {}
    onconnect = args[1]
  }

  return new Socket(opts).connect(opts, onconnect)
}

exports.createServer = function createServer (opts, onconnection) {
  return new Server(opts, onconnection)
}
module.exports = require.addon()
const binding = require('./binding')

function onlookup (err, addresses) {
  const req = this

  if (err) return req.cb(err, null, 0)

  const { address, family } = addresses[0]

  return req.cb(null, address, family)
}

function onlookupall (err, addresses) {
  const req = this

  if (err) return req.cb(err, null)

  return req.cb(null, addresses)
}

exports.lookup = function lookup (hostname, opts = {}, cb) {
  if (typeof opts === 'function') {
    cb = opts
    opts = {}
  }

  let {
    family = 0,
    all = false
  } = opts

  if (typeof family === 'string') {
    switch (family) {
      case 'IPv4': family = 4
        break
      case 'IPv6': family = 6
        break
      default: family = 0
    }
  }

  const req = {
    cb,
    handle: null
  }

  req.handle = binding.lookup(hostname, family || 0, all, req, all ? onlookupall : onlookup)
}
{
  "name": "bare-dns",
  "version": "1.0.5",
  "description": "Domain name resolution for JavaScript",
  "exports": {
    ".": "./index.js",
    "./package": "./package.json"
  },
  "files": [
    "index.js",
    "binding.c",
    "binding.js",
    "CMakeLists.txt",
    "lib",
    "prebuilds"
  ],
  "addon": true,
  "scripts": {
    "test": "standard && bare test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-dns.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-dns/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-dns",
  "devDependencies": {
    "brittle": "^3.3.2",
    "cmake-bare": "^1.1.6",
    "standard": "^17.1.0"
  }
}
module.exports = require.addon()
/* global Bare */
const EventEmitter = require('bare-events')
const { Duplex } = require('bare-stream')
const binding = require('./binding')
const constants = require('./lib/constants')
const errors = require('./lib/errors')

const defaultReadBufferSize = 65536

const Pipe = module.exports = exports = class Pipe extends Duplex {
  constructor (path, opts = {}) {
    super({ eagerOpen: true })

    if (typeof path === 'object' && path !== null) {
      opts = path
      path = null
    }

    const {
      readBufferSize = defaultReadBufferSize,
      allowHalfOpen = true
    } = opts

    this._state = 0

    this._allowHalfOpen = allowHalfOpen

    this._fd = -1
    this._path = null

    this._pendingOpen = null
    this._pendingWrite = null
    this._pendingFinal = null
    this._pendingDestroy = null

    this._buffer = Buffer.alloc(readBufferSize)

    this._handle = binding.init(this._buffer, this,
      noop,
      this._onconnect,
      this._onwrite,
      this._onfinal,
      this._onread,
      this._onclose
    )

    if (typeof path === 'number') {
      this.open(path)
    } else if (typeof path === 'string') {
      this.connect(path)
    }

    Pipe._pipes.add(this)
  }

  get connecting () {
    return (this._state & constants.state.CONNECTING) !== 0
  }

  get pending () {
    return (this._state & constants.state.CONNECTED) === 0
  }

  get readyState () {
    if (this._state & constants.state.READABLE && this._state & constants.state.WRITABLE) {
      return 'open'
    }

    if (this._state & constants.state.READABLE) {
      return 'readOnly'
    }

    if (this._state & constants.state.WRITABLE) {
      return 'writeOnly'
    }

    return 'opening'
  }

  open (fd, opts = {}, onconnect) {
    if (typeof opts === 'function') {
      onconnect = opts
      opts = {}
    }

    if (typeof fd === 'object' && fd !== null) {
      opts = fd || {}
      fd = opts.fd
    }

    try {
      const status = binding.open(this._handle, fd)

      this._state |= constants.state.CONNECTED
      this._fd = fd

      if (status & binding.READABLE) {
        this._state |= constants.state.READABLE
      } else {
        this.push(null)
      }

      if (status & binding.WRITABLE) {
        this._state |= constants.state.WRITABLE
      } else {
        this.end()
      }

      if (onconnect) this.once('connect', onconnect)

      queueMicrotask(() => this.emit('connect'))
    } catch (err) {
      queueMicrotask(() => {
        if (this._pendingOpen) this._pendingOpen(err)
        else this.destroy(err)
      })
    }

    return this
  }

  connect (path, opts = {}, onconnect) {
    if (this._state & constants.state.CONNECTING || this._state & constants.state.CONNECTED) {
      throw errors.PIPE_ALREADY_CONNECTED('Pipe is already connected')
    }

    this._state |= constants.state.CONNECTING

    if (typeof opts === 'function') {
      onconnect = opts
      opts = {}
    }

    if (typeof path === 'object' && path !== null) {
      opts = path || {}
      path = opts.path
    }

    try {
      binding.connect(this._handle, path)

      this._path = path

      if (onconnect) this.once('connect', onconnect)
    } catch (err) {
      queueMicrotask(() => {
        if (this._pendingOpen) this._pendingOpen(err)
        else this.destroy(err)
      })
    }

    return this
  }

  ref () {
    binding.ref(this._handle)
  }

  unref () {
    binding.unref(this._handle)
  }

  _open (cb) {
    if (this._state & constants.state.CONNECTED) return cb(null)
    this._pendingOpen = cb
  }

  _read () {
    if ((this._state & constants.state.READING) === 0) {
      this._state |= constants.state.READING
      binding.resume(this._handle)
    }
  }

  _writev (batch, cb) {
    this._pendingWrite = [cb, batch]
    binding.writev(this._handle, batch.map(({ chunk }) => chunk))
  }

  _final (cb) {
    if (this._state & constants.state.READABLE && this._state & constants.state.WRITABLE) {
      this._pendingFinal = cb
      binding.end(this._handle)
    } else {
      cb(null)
    }
  }

  _predestroy () {
    if (this._state & constants.state.CLOSING) return
    this._state |= constants.state.CLOSING
    binding.close(this._handle)
    Pipe._pipes.delete(this)
  }

  _destroy (err, cb) {
    if (this._state & constants.state.CLOSING) return cb(err)
    this._state |= constants.state.CLOSING
    this._pendingDestroy = cb
    binding.close(this._handle)
    Pipe._pipes.delete(this)
  }

  _continueOpen (err) {
    if (this._pendingOpen === null) return
    const cb = this._pendingOpen
    this._pendingOpen = null
    cb(err)
  }

  _continueWrite (err) {
    if (this._pendingWrite === null) return
    const cb = this._pendingWrite[0]
    this._pendingWrite = null
    cb(err)
  }

  _continueFinal (err) {
    if (this._pendingFinal === null) return
    const cb = this._pendingFinal
    this._pendingFinal = null
    cb(err)
  }

  _continueDestroy () {
    if (this._pendingDestroy === null) return
    const cb = this._pendingDestroy
    this._pendingDestroy = null
    cb(null)
  }

  _onconnect (err) {
    if (err) {
      if (this._pendingOpen) this._continueOpen(err)
      else this.destroy(err)
      return
    }

    this._state |= constants.state.CONNECTED | constants.state.READABLE | constants.state.WRITABLE
    this._state &= ~constants.state.CONNECTING
    this._continueOpen()

    this.emit('connect')
  }

  _onread (err, read) {
    if (err) {
      this.destroy(err)
      return
    }

    if (read === 0) {
      this.push(null)
      if (this._allowHalfOpen === false) this.end()
      return
    }

    const copy = Buffer.allocUnsafe(read)
    copy.set(this._buffer.subarray(0, read))

    if (this.push(copy) === false && this.destroying === false) {
      this._state &= ~constants.state.READING
      binding.pause(this._handle)
    }
  }

  _onwrite (err) {
    this._continueWrite(err)
  }

  _onfinal (err) {
    this._continueFinal(err)
  }

  _onclose () {
    this._handle = null
    this._continueDestroy()
  }

  _onspawn (readable, writable) {
    this._state |= constants.state.CONNECTED

    if (readable) {
      this._state |= constants.state.READABLE
    } else {
      this.push(null)
    }

    if (writable) {
      this._state |= constants.state.WRITABLE
    } else {
      this.end()
    }

    this._continueOpen()
  }

  static _pipes = new Set()
}

exports.Pipe = exports

exports.pipe = function pipe () {
  return binding.pipe()
}

const Server = exports.Server = class PipeServer extends EventEmitter {
  constructor (opts = {}, onconnection) {
    if (typeof opts === 'function') {
      onconnection = opts
      opts = {}
    }

    super()

    const {
      readBufferSize = defaultReadBufferSize,
      allowHalfOpen = true
    } = opts

    this._state = 0

    this._readBufferSize = readBufferSize
    this._allowHalfOpen = allowHalfOpen

    this._path = null
    this._connections = new Set()

    this._error = null
    this._handle = null

    if (onconnection) this.on('connection', onconnection)

    PipeServer._servers.add(this)
  }

  get listening () {
    return (this._state & constants.state.BOUND) !== 0
  }

  address () {
    if ((this._state & constants.state.BOUND) === 0) {
      return null
    }

    return this._path
  }

  listen (path, backlog = 511, opts = {}, onlistening) {
    if (this._state & constants.state.BINDING || this._state & constants.state.BOUND) {
      throw errors.SERVER_ALREADY_LISTENING('Server is already listening')
    }

    if (this._state & constants.state.CLOSING) {
      throw errors.SERVER_IS_CLOSED('Server is closed')
    }

    this._state |= constants.state.BINDING

    if (typeof backlog === 'function') {
      onlistening = backlog
      backlog = 511
    } else if (typeof opts === 'function') {
      onlistening = opts
      opts = {}
    }

    if (typeof path === 'object' && path !== null) {
      opts = path || {}
      path = opts.path
      backlog = opts.backlog || 511
    }

    this._handle = binding.init(empty, this,
      this._onconnection,
      noop,
      noop,
      noop,
      noop,
      this._onclose
    )

    if (this._state & constants.state.UNREFED) binding.unref(this._handle)

    try {
      binding.bind(this._handle, path, backlog)

      this._path = path
      this._state |= constants.state.BOUND
      this._state &= ~constants.state.BINDING

      if (onlistening) this.once('listening', onlistening)

      queueMicrotask(() => this.emit('listening'))
    } catch (err) {
      this._error = err

      binding.close(this._handle)
    }

    return this
  }

  close (onclose) {
    if (onclose) this.once('close', onclose)
    if (this._state & constants.state.CLOSING) return
    this._state |= constants.state.CLOSING
    this._closeMaybe()
  }

  ref () {
    this._state &= ~constants.state.UNREFED
    if (this._handle !== null) binding.ref(this._handle)
  }

  unref () {
    this._state |= constants.state.UNREFED
    if (this._handle !== null) binding.unref(this._handle)
  }

  _closeMaybe () {
    if ((this._state & constants.state.CLOSING) && this._connections.size === 0) {
      if (this._handle !== null) binding.close(this._handle)
      else queueMicrotask(() => this.emit('close'))
      PipeServer._servers.delete(this)
    }
  }

  _onconnection (err) {
    if (err) {
      this.emit('error', err)
      return
    }

    if (this._state & constants.state.CLOSING) return

    const pipe = new Pipe({
      readBufferSize: this._readBufferSize,
      allowHalfOpen: this._allowHalfOpen
    })

    try {
      binding.accept(this._handle, pipe._handle)

      pipe._path = this._path
      pipe._state |= constants.state.CONNECTED | constants.state.READABLE | constants.state.WRITABLE

      this._connections.add(pipe)

      pipe.on('close', () => {
        this._connections.delete(pipe)
        this._closeMaybe()
      })

      this.emit('connection', pipe)
    } catch (err) {
      pipe.destroy()

      throw err
    }
  }

  _onclose () {
    const err = this._error

    this._state &= ~constants.state.BINDING
    this._error = null
    this._handle = null

    if (err) this.emit('error', err)
    else this.emit('close')
  }

  static _servers = new Set()
}

exports.constants = constants
exports.errors = errors

exports.createConnection = function createConnection (path, opts, onconnect) {
  if (typeof opts === 'function') {
    onconnect = opts
    opts = {}
  }

  if (typeof path === 'object' && path !== null) {
    opts = path || {}
    path = opts.path
  }

  return new Pipe(opts).connect(path, opts, onconnect)
}

exports.createServer = function createServer (opts, onconnection) {
  return new Server(opts, onconnection)
}

Bare
  .on('exit', () => {
    for (const pipe of Pipe._pipes) {
      pipe.destroy()
    }

    for (const server of Server._servers) {
      server.close()
    }
  })

const empty = Buffer.alloc(0)

function noop () {}
module.exports = {
  state: {
    CONNECTING: 0x1,
    CONNECTED: 0x2,
    BINDING: 0x4,
    BOUND: 0x8,
    READING: 0x10,
    CLOSING: 0x20,
    READABLE: 0x40,
    WRITABLE: 0x80
  }
}
module.exports = class PipeError extends Error {
  constructor (msg, code, fn = PipeError) {
    super(`${code}: ${msg}`)
    this.code = code

    if (Error.captureStackTrace) {
      Error.captureStackTrace(this, fn)
    }
  }

  get name () {
    return 'PipeError'
  }

  static PIPE_ALREADY_CONNECTED (msg) {
    return new PipeError(msg, 'PIPE_ALREADY_CONNECTED', PipeError.PIPE_ALREADY_CONNECTED)
  }

  static SERVER_ALREADY_LISTENING (msg) {
    return new PipeError(msg, 'SERVER_ALREADY_LISTENING', PipeError.SERVER_ALREADY_LISTENING)
  }

  static SERVER_IS_CLOSED (msg) {
    return new PipeError(msg, 'SERVER_IS_CLOSED', PipeError.SERVER_IS_CLOSED)
  }
}
{
  "name": "bare-pipe",
  "version": "3.3.8",
  "description": "Native I/O pipes for JavaScript",
  "exports": {
    ".": "./index.js",
    "./package": "./package.json",
    "./constants": "./lib/constants.js",
    "./errors": "./lib/errors.js"
  },
  "files": [
    "index.js",
    "binding.c",
    "binding.js",
    "CMakeLists.txt",
    "lib",
    "prebuilds"
  ],
  "addon": true,
  "scripts": {
    "test": "standard && bare test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-pipe.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-pipe/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-pipe#readme",
  "dependencies": {
    "bare-events": "^2.0.0",
    "bare-stream": "^2.0.0"
  },
  "devDependencies": {
    "bare-path": "^3.0.0",
    "brittle": "^3.2.1",
    "cmake-bare": "^1.1.6",
    "standard": "^17.0.0"
  }
}
module.exports = require.addon()
/* global Bare */
const EventEmitter = require('bare-events')
const { Duplex } = require('bare-stream')
const dns = require('bare-dns')
const binding = require('./binding')
const constants = require('./lib/constants')
const errors = require('./lib/errors')
const ip = require('./lib/ip')

const defaultReadBufferSize = 65536

const Socket = exports.Socket = class TCPSocket extends Duplex {
  constructor (opts = {}) {
    super({ eagerOpen: true })

    const {
      readBufferSize = defaultReadBufferSize,
      allowHalfOpen = true
    } = opts

    this._state = 0

    this._allowHalfOpen = allowHalfOpen

    this._remotePort = -1
    this._remoteHost = null
    this._remoteFamily = 0

    this._pendingOpen = null
    this._pendingWrite = null
    this._pendingFinal = null
    this._pendingDestroy = null

    this._timer = null
    this._timeout = 0

    this._buffer = Buffer.alloc(readBufferSize)

    this._handle = binding.init(this._buffer, this,
      noop,
      this._onconnect,
      this._onread,
      this._onwrite,
      this._onfinal,
      this._onclose
    )

    TCPSocket._sockets.add(this)
  }

  get connecting () {
    return (this._state & constants.state.CONNECTING) !== 0
  }

  get pending () {
    return (this._state & constants.state.CONNECTED) === 0
  }

  get timeout () {
    return this._timeout || undefined // For Node.js compatibility
  }

  connect (port, host = 'localhost', opts = {}, onconnect) {
    if (this._state & constants.state.CONNECTING || this._state & constants.state.CONNECTED) {
      throw errors.SOCKET_ALREADY_CONNECTED('Socket is already connected')
    }

    this._state |= constants.state.CONNECTING

    if (typeof host === 'function') {
      onconnect = host
      host = 'localhost'
    } else if (typeof opts === 'function') {
      onconnect = opts
      opts = {}
    }

    let family = 0

    if (typeof port === 'object' && port !== null) {
      opts = port || {}
      port = opts.port || 0
      host = opts.host || 'localhost'
      family = opts.family || 0
    }

    if (!host) host = 'localhost'

    const type = ip.isIP(host)

    if (type === 0) {
      const {
        lookup = dns.lookup,
        hints
      } = opts

      lookup(host, { family, hints }, (err, address, family) => {
        this.emit('lookup', err, address, family, host)

        this._state &= ~constants.state.CONNECTING

        if (err) {
          if (this._pendingOpen) this._continueOpen(err)
          else this.destroy(err)
          return
        }

        if (this._handle !== null) {
          this.connect(port, address, { ...opts, family }, onconnect)
        }
      })

      return this
    }

    family = type

    try {
      binding.connect(this._handle, port, host, family)

      if (opts.keepAlive === true) this.setKeepAlive(opts.keepAlive, opts.keepAliveInitialDelay)
      if (opts.noDelay === true) this.setNoDelay()
      if (opts.timeout) this.setTimeout(opts.timeout)

      this._remotePort = port
      this._remoteHost = host
      this._remoteFamily = family

      if (onconnect) this.once('connect', onconnect)
    } catch (err) {
      queueMicrotask(() => {
        if (this._pendingOpen) this._pendingOpen(err)
        else this.destroy(err)
      })
    }

    return this
  }

  setKeepAlive (enable = false, delay = 0) {
    if (typeof enable === 'number') {
      delay = enable
      enable = false
    }

    delay = Math.floor(delay / 1000)

    if (delay === 0) enable = false

    binding.keepalive(this._handle, enable, delay)

    return this
  }

  setNoDelay (enable = true) {
    binding.nodelay(this._handle, enable)

    return this
  }

  setTimeout (ms, ontimeout) {
    if (ms === 0) {
      clearTimeout(this._timer)
      this._timer = null
    } else {
      if (ontimeout) this.once('timeout', ontimeout)

      this._timer = setTimeout(() => this.emit('timeout'), ms)
      this._timer.unref()
    }

    this._timeout = ms

    return this
  }

  ref () {
    binding.ref(this._handle)
  }

  unref () {
    binding.unref(this._handle)
  }

  _open (cb) {
    if (this._state & constants.state.CONNECTED) return cb(null)
    this._pendingOpen = cb
  }

  _read () {
    if ((this._state & constants.state.READING) === 0) {
      this._state |= constants.state.READING
      binding.resume(this._handle)
    }
  }

  _writev (batch, cb) {
    this._pendingWrite = [cb, batch]
    binding.writev(this._handle, batch.map(({ chunk }) => chunk))
  }

  _final (cb) {
    this._pendingFinal = cb
    binding.end(this._handle)
  }

  _predestroy () {
    if (this._state & constants.state.CLOSING) return
    this._state |= constants.state.CLOSING
    binding.close(this._handle)
    TCPSocket._sockets.delete(this)
  }

  _destroy (err, cb) {
    if (this._state & constants.state.CLOSING) return cb(err)
    this._state |= constants.state.CLOSING
    this._pendingDestroy = cb
    binding.close(this._handle)
    TCPSocket._sockets.delete(this)
  }

  _continueOpen (err) {
    if (this._pendingOpen === null) return
    const cb = this._pendingOpen
    this._pendingOpen = null
    cb(err)
  }

  _continueWrite (err) {
    if (this._pendingWrite === null) return
    const cb = this._pendingWrite[0]
    this._pendingWrite = null
    cb(err)
  }

  _continueFinal (err) {
    if (this._pendingFinal === null) return
    const cb = this._pendingFinal
    this._pendingFinal = null
    cb(err)
  }

  _continueDestroy () {
    if (this._pendingDestroy === null) return
    const cb = this._pendingDestroy
    this._pendingDestroy = null
    cb(null)
  }

  _onconnect (err) {
    if (err) {
      if (this._pendingOpen) this._continueOpen(err)
      else this.destroy(err)
      return
    }

    this._state |= constants.state.CONNECTED
    this._state &= ~constants.state.CONNECTING
    this._continueOpen()

    this.emit('connect')
  }

  _onread (err, read) {
    if (this._timer) this._timer.refresh()

    if (err) {
      this.destroy(err)
      return
    }

    if (read === 0) {
      this.push(null)
      if (this._allowHalfOpen === false) this.end()
      return
    }

    const copy = Buffer.allocUnsafe(read)
    copy.set(this._buffer.subarray(0, read))

    if (this.push(copy) === false && this.destroying === false) {
      this._state &= ~constants.state.READING
      binding.pause(this._handle)
    }
  }

  _onwrite (err) {
    if (this._timer) this._timer.refresh()

    this._continueWrite(err)
  }

  _onfinal (err) {
    this._continueFinal(err)
  }

  _onclose () {
    clearTimeout(this._timer)

    this._handle = null
    this._continueDestroy()
  }

  static _sockets = new Set()
}

const Server = exports.Server = class TCPServer extends EventEmitter {
  constructor (opts = {}, onconnection) {
    if (typeof opts === 'function') {
      onconnection = opts
      opts = {}
    }

    super()

    const {
      readBufferSize = defaultReadBufferSize,
      allowHalfOpen = true
    } = opts

    this._state = 0

    this._readBufferSize = readBufferSize
    this._allowHalfOpen = allowHalfOpen

    this._keepAlive = opts.keepAlive
    this._keepAliveDelay = opts.keepAliveInitialDelay
    this._noDelay = opts.noDelay

    this._port = -1
    this._host = null
    this._family = 0
    this._connections = new Set()

    this._error = null
    this._handle = null

    if (onconnection) this.on('connection', onconnection)

    TCPServer._servers.add(this)
  }

  get listening () {
    return (this._state & constants.state.BOUND) !== 0
  }

  address () {
    if ((this._state & constants.state.BOUND) === 0) {
      return null
    }

    return {
      address: this._host,
      family: `IPv${this._family}`,
      port: this._port
    }
  }

  listen (port = 0, host = 'localhost', backlog = 511, opts = {}, onlistening) {
    if (this._state & constants.state.BINDING || this._state & constants.state.BOUND) {
      throw errors.SERVER_ALREADY_LISTENING('Server is already listening')
    }

    if (this._state & constants.state.CLOSING) {
      throw errors.SERVER_IS_CLOSED('Server is closed')
    }

    this._state |= constants.state.BINDING

    if (typeof port === 'function') {
      onlistening = port
      port = 0
    } else if (typeof host === 'function') {
      onlistening = host
      host = 'localhost'
    } else if (typeof backlog === 'function') {
      onlistening = backlog
      backlog = 511
    } else if (typeof opts === 'function') {
      onlistening = opts
      opts = {}
    }

    let family = 0

    if (typeof port === 'object' && port !== null) {
      opts = port || {}
      port = opts.port || 0
      host = opts.host || 'localhost'
      family = opts.family || 0
      backlog = opts.backlog || 511
    }

    if (!host) host = 'localhost'
    if (!backlog) backlog = 511

    const type = ip.isIP(host)

    if (type === 0) {
      const {
        lookup = dns.lookup,
        hints
      } = opts

      lookup(host, { family, hints }, (err, address, family) => {
        if (this._state & constants.state.CLOSING) return

        this.emit('lookup', err, address, family, host)

        this._state &= ~constants.state.BINDING

        if (err) return this.emit('error', err)

        this.listen(port, address, backlog, { ...opts, family }, onlistening)
      })

      return this
    }

    family = type

    this._handle = binding.init(empty, this,
      this._onconnection,
      noop,
      noop,
      noop,
      noop,
      this._onclose
    )

    if (this._state & constants.state.UNREFED) binding.unref(this._handle)

    try {
      this._port = binding.bind(this._handle, port, host, backlog, family)
      this._host = host
      this._family = family
      this._state |= constants.state.BOUND
      this._state &= ~constants.state.BINDING

      if (onlistening) this.once('listening', onlistening)

      queueMicrotask(() => this.emit('listening'))
    } catch (err) {
      this._error = err

      binding.close(this._handle)
    }

    return this
  }

  close (onclose) {
    if (onclose) this.once('close', onclose)
    if (this._state & constants.state.CLOSING) return
    this._state |= constants.state.CLOSING
    this._closeMaybe()
  }

  ref () {
    this._state &= ~constants.state.UNREFED
    if (this._handle !== null) binding.ref(this._handle)
  }

  unref () {
    this._state |= constants.state.UNREFED
    if (this._handle !== null) binding.unref(this._handle)
  }

  _closeMaybe () {
    if ((this._state & constants.state.CLOSING) && this._connections.size === 0) {
      if (this._handle !== null) binding.close(this._handle)
      else queueMicrotask(() => this.emit('close'))
      TCPServer._servers.delete(this)
    }
  }

  _onconnection (err) {
    if (err) {
      this.emit('error', err)
      return
    }

    if (this._state & constants.state.CLOSING) return

    const socket = new Socket({
      readBufferSize: this._readBufferSize,
      allowHalfOpen: this._allowHalfOpen
    })

    try {
      binding.accept(this._handle, socket._handle)

      socket._state |= constants.state.CONNECTED

      this._connections.add(socket)

      if (this._keepAlive === true) socket.setKeepAlive(this._keepAlive, this._keepAliveDelay)
      if (this._noDelay === true) socket.setNoDelay()

      socket.on('close', () => {
        this._connections.delete(socket)
        this._closeMaybe()
      })

      this.emit('connection', socket)
    } catch (err) {
      socket.destroy()

      throw err
    }
  }

  _onclose () {
    const err = this._error

    this._state &= ~constants.state.BINDING
    this._error = null
    this._handle = null

    if (err) this.emit('error', err)
    else this.emit('close')
  }

  static _servers = new Set()
}

exports.constants = constants
exports.errors = errors

exports.isIP = ip.isIP
exports.isIPv4 = ip.isIPv4
exports.isIPv6 = ip.isIPv6

exports.createConnection = function createConnection (port, host, opts, onconnect) {
  if (typeof host === 'function') {
    onconnect = host
    host = 'localhost'
  } else if (typeof opts === 'function') {
    onconnect = opts
    opts = {}
  }

  if (typeof port === 'object' && port !== null) {
    opts = port || {}
    port = opts.port || 0
    host = opts.host || 'localhost'
  }

  return new Socket(opts).connect(port, host, opts, onconnect)
}

// For Node.js compatibility
exports.connect = exports.createConnection

exports.createServer = function createServer (opts, onconnection) {
  return new Server(opts, onconnection)
}

Bare
  .on('exit', () => {
    for (const socket of Socket._sockets) {
      socket.destroy()
    }

    for (const server of Server._servers) {
      server.close()
    }
  })

const empty = Buffer.alloc(0)

function noop () {}
module.exports = {
  state: {
    CONNECTING: 0x1,
    CONNECTED: 0x2,
    BINDING: 0x4,
    BOUND: 0x8,
    READING: 0x10,
    CLOSING: 0x20,
    UNREFED: 0x40
  }
}
module.exports = class TCPError extends Error {
  constructor (msg, code, fn = TCPError) {
    super(`${code}: ${msg}`)
    this.code = code

    if (Error.captureStackTrace) {
      Error.captureStackTrace(this, fn)
    }
  }

  get name () {
    return 'TCPError'
  }

  static SOCKET_ALREADY_CONNECTED (msg) {
    return new TCPError(msg, 'SOCKET_ALREADY_CONNECTED', TCPError.SOCKET_ALREADY_CONNECTED)
  }

  static SERVER_ALREADY_LISTENING (msg) {
    return new TCPError(msg, 'SERVER_ALREADY_LISTENING', TCPError.SERVER_ALREADY_LISTENING)
  }

  static SERVER_IS_CLOSED (msg) {
    return new TCPError(msg, 'SERVER_IS_CLOSED', TCPError.SERVER_IS_CLOSED)
  }

  static INVALID_HOST (msg = 'Unrecognizable host format') {
    return new TCPError(msg, 'INVALID_HOST', TCPError.INVALID_HOST)
  }
}
// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.

const v4Seg = '(?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])'
const v4Str = `(${v4Seg}[.]){3}${v4Seg}`
const IPv4Pattern = new RegExp(`^${v4Str}$`)

const v6Seg = '(?:[0-9a-fA-F]{1,4})'
const IPv6Pattern = new RegExp('^(' +
  `(?:${v6Seg}:){7}(?:${v6Seg}|:)|` +
  `(?:${v6Seg}:){6}(?:${v4Str}|:${v6Seg}|:)|` +
  `(?:${v6Seg}:){5}(?::${v4Str}|(:${v6Seg}){1,2}|:)|` +
  `(?:${v6Seg}:){4}(?:(:${v6Seg}){0,1}:${v4Str}|(:${v6Seg}){1,3}|:)|` +
  `(?:${v6Seg}:){3}(?:(:${v6Seg}){0,2}:${v4Str}|(:${v6Seg}){1,4}|:)|` +
  `(?:${v6Seg}:){2}(?:(:${v6Seg}){0,3}:${v4Str}|(:${v6Seg}){1,5}|:)|` +
  `(?:${v6Seg}:){1}(?:(:${v6Seg}){0,4}:${v4Str}|(:${v6Seg}){1,6}|:)|` +
  `(?::((?::${v6Seg}){0,5}:${v4Str}|(?::${v6Seg}){1,7}|:))` +
')(%[0-9a-zA-Z-.:]{1,})?$')

const isIPv4 = exports.isIPv4 = function isIPv4 (host) {
  return IPv4Pattern.test(host)
}

const isIPv6 = exports.isIPv6 = function isIPv6 (host) {
  return IPv6Pattern.test(host)
}

exports.isIP = function isIP (host) {
  if (isIPv4(host)) return 4
  if (isIPv6(host)) return 6
  return 0
}
{
  "name": "bare-tcp",
  "version": "1.9.1",
  "description": "Native TCP sockets for JavaScript",
  "exports": {
    ".": "./index.js",
    "./package": "./package.json",
    "./constants": "./lib/constants.js",
    "./errors": "./lib/errors.js"
  },
  "files": [
    "index.js",
    "binding.c",
    "binding.js",
    "CMakeLists.txt",
    "lib",
    "prebuilds"
  ],
  "addon": true,
  "scripts": {
    "test": "standard && bare test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-tcp.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-tcp/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-tcp#readme",
  "dependencies": {
    "bare-dns": "^1.0.0",
    "bare-events": "^2.2.2",
    "bare-stream": "^2.0.0"
  },
  "devDependencies": {
    "brittle": "^3.5.0",
    "cmake-bare": "^1.1.6",
    "standard": "^17.1.0"
  }
}
{
  "name": "bare-net",
  "version": "1.0.1",
  "description": "TCP and IPC servers and clients for JavaScript",
  "exports": {
    ".": "./index.js",
    "./package": "./package.json"
  },
  "files": [
    "index.js"
  ],
  "scripts": {
    "test": "standard && bare test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-net.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-net/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-net#readme",
  "dependencies": {
    "bare-events": "^2.2.2",
    "bare-pipe": "^3.2.0",
    "bare-stream": "^2.0.0",
    "bare-tcp": "^1.4.0"
  },
  "devDependencies": {
    "brittle": "^3.3.0",
    "standard": "^17.0.0"
  }
}
module.exports = require.addon()
const binding = require('./binding')
const errors = require('./lib/errors')
const constants = require('./lib/constants')

exports.constants = constants
exports.errors = errors

exports.EOL = binding.platform === 'win32' ? '\r\n' : '\n'

exports.devNull = binding.platform === 'win32' ? '\\\\.\\nul' : '/dev/null'

exports.platform = function platform() {
  return binding.platform
}

exports.arch = function arch() {
  return binding.arch
}

exports.type = binding.type
exports.version = binding.version
exports.release = binding.release
exports.machine = binding.machine
exports.execPath = binding.execPath
exports.pid = binding.pid
exports.ppid = binding.ppid
exports.cwd = binding.cwd
exports.chdir = binding.chdir
exports.tmpdir = binding.tmpdir
exports.homedir = binding.homedir
exports.hostname = binding.hostname
exports.userInfo = binding.userInfo
exports.groupInfo = binding.groupInfo

exports.networkInterfaces = function networkInterfaces() {
  const result = {}

  for (const entry of binding.networkInterfaces()) {
    const { name, ...properties } = entry

    if (result[name]) result[name].push(properties)
    else result[name] = [properties]
  }

  return result
}

exports.kill = function kill(pid, signal = constants.signals.SIGTERM) {
  if (typeof signal === 'string') {
    if (signal in constants.signals === false) {
      throw errors.UNKNOWN_SIGNAL('Unknown signal: ' + signal)
    }

    signal = constants.signals[signal]
  }

  binding.kill(pid, signal)
}

exports.endianness = function endianness() {
  return binding.isLittleEndian ? 'LE' : 'BE'
}

exports.availableParallelism = binding.availableParallelism

exports.cpuUsage = function cpuUsage(previous) {
  const current = binding.cpuUsage()

  if (previous) {
    return {
      user: current.user - previous.user,
      system: current.system - previous.system
    }
  }

  return current
}

exports.threadCpuUsage = function threadCpuUsage(previous) {
  const current = binding.threadCpuUsage()

  if (previous) {
    return {
      user: current.user - previous.user,
      system: current.system - previous.system
    }
  }

  return current
}

exports.resourceUsage = binding.resourceUsage
exports.memoryUsage = binding.memoryUsage
exports.freemem = binding.freemem
exports.totalmem = binding.totalmem
exports.availableMemory = binding.availableMemory
exports.constrainedMemory = binding.constrainedMemory
exports.uptime = binding.uptime
exports.loadavg = binding.loadavg
exports.cpus = binding.cpus

exports.getProcessTitle = binding.getProcessTitle

exports.setProcessTitle = function setProcessTitle(title) {
  if (typeof title !== 'string') title = title.toString()

  if (title.length >= 256) {
    throw errors.TITLE_OVERFLOW('Process title is too long')
  }

  binding.setProcessTitle(title)
}

exports.getPriority = function getPriority(pid = 0) {
  return binding.getPriority(pid)
}

exports.setPriority = function setPriority(pid, priority) {
  if (priority === undefined) {
    priority = pid
    pid = 0
  }

  binding.setPriority(pid, priority)
}

exports.getEnvKeys = binding.getEnvKeys
exports.getEnv = binding.getEnv
exports.hasEnv = binding.hasEnv
exports.setEnv = binding.setEnv
exports.unsetEnv = binding.unsetEnv
const binding = require('../binding')

module.exports = {
  signals: binding.signals,
  errnos: binding.errnos,
  priority: binding.priority
}
module.exports = class OSError extends Error {
  constructor(msg, fn = OSError, code = fn.name) {
    super(`${code}: ${msg}`)
    this.code = code

    if (Error.captureStackTrace) {
      Error.captureStackTrace(this, fn)
    }
  }

  get name() {
    return 'OSError'
  }

  static UNKNOWN_SIGNAL(msg) {
    return new OSError(msg, OSError.UNKNOWN_SIGNAL)
  }

  static TITLE_OVERFLOW(msg) {
    return new OSError(msg, OSError.TITLE_OVERFLOW)
  }
}
{
  "name": "bare-os",
  "version": "3.9.1",
  "description": "Operating system utilities for Bare",
  "exports": {
    "./package": "./package.json",
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    },
    "./constants": {
      "types": "./lib/constants.d.ts",
      "default": "./lib/constants.js"
    },
    "./errors": {
      "types": "./lib/errors.d.ts",
      "default": "./lib/errors.js"
    }
  },
  "files": [
    "index.js",
    "index.d.ts",
    "binding.c",
    "binding.js",
    "CMakeLists.txt",
    "lib",
    "prebuilds"
  ],
  "addon": true,
  "scripts": {
    "format": "prettier --write . && lunte --fix",
    "lint": "prettier --check . && lunte",
    "test": "brittle-bare --coverage test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-os.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-os/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-os#readme",
  "engines": {
    "bare": ">=1.14.0"
  },
  "devDependencies": {
    "brittle": "^3.1.1",
    "cmake-bare": "^1.1.6",
    "lunte": "^1.6.0",
    "prettier": "^3.4.2",
    "prettier-config-holepunch": "^2.0.0"
  }
}
/* global Bare */

// This export SHOULD NOT be shortened in any way as having the full
// `module.exports = require(...)` statement is crucial for synthesizing
// ESM exports.

if (Bare.platform === 'win32') {
  module.exports = require('./lib/win32')
} else {
  module.exports = require('./lib/posix')
}
module.exports = {
  CHAR_UPPERCASE_A: 0x41,
  CHAR_LOWERCASE_A: 0x61,
  CHAR_UPPERCASE_Z: 0x5a,
  CHAR_LOWERCASE_Z: 0x7a,
  CHAR_DOT: 0x2e,
  CHAR_FORWARD_SLASH: 0x2f,
  CHAR_BACKWARD_SLASH: 0x5c,
  CHAR_COLON: 0x3a,
  CHAR_QUESTION_MARK: 0x3f
}
const os = require('bare-os')

const { normalizeString } = require('./shared')
const {
  CHAR_DOT,
  CHAR_FORWARD_SLASH
} = require('./constants')

function isPosixPathSeparator (code) {
  return code === CHAR_FORWARD_SLASH
}

exports.win32 = require('./win32')
exports.posix = exports

exports.sep = '/'
exports.delimiter = ':'

exports.resolve = function resolve (...args) {
  let resolvedPath = ''
  let resolvedAbsolute = false

  for (let i = args.length - 1; i >= -1 && !resolvedAbsolute; i--) {
    const path = i >= 0 ? args[i] : os.cwd()

    if (path.length === 0) {
      continue
    }

    resolvedPath = `${path}/${resolvedPath}`
    resolvedAbsolute = path.charCodeAt(0) === CHAR_FORWARD_SLASH
  }

  resolvedPath = normalizeString(resolvedPath, !resolvedAbsolute, '/', isPosixPathSeparator)

  if (resolvedAbsolute) {
    return `/${resolvedPath}`
  }

  return resolvedPath.length > 0 ? resolvedPath : '.'
}

exports.normalize = function normalize (path) {
  if (path.length === 0) return '.'

  const isAbsolute = path.charCodeAt(0) === CHAR_FORWARD_SLASH
  const trailingSeparator = path.charCodeAt(path.length - 1) === CHAR_FORWARD_SLASH

  path = normalizeString(path, !isAbsolute, '/', isPosixPathSeparator)

  if (path.length === 0) {
    if (isAbsolute) return '/'
    return trailingSeparator ? './' : '.'
  }

  if (trailingSeparator) path += '/'

  return isAbsolute ? `/${path}` : path
}

exports.isAbsolute = function isAbsolute (path) {
  return path.length > 0 && path.charCodeAt(0) === CHAR_FORWARD_SLASH
}

exports.join = function join (...args) {
  if (args.length === 0) return '.'
  let joined
  for (let i = 0; i < args.length; ++i) {
    const arg = args[i]
    if (arg.length > 0) {
      if (joined === undefined) joined = arg
      else joined += `/${arg}`
    }
  }
  if (joined === undefined) return '.'
  return exports.normalize(joined)
}

exports.relative = function relative (from, to) {
  if (from === to) return ''

  from = exports.resolve(from)
  to = exports.resolve(to)

  if (from === to) return ''

  const fromStart = 1
  const fromEnd = from.length
  const fromLen = fromEnd - fromStart
  const toStart = 1
  const toLen = to.length - toStart

  const length = (fromLen < toLen ? fromLen : toLen)
  let lastCommonSep = -1
  let i = 0
  for (; i < length; i++) {
    const fromCode = from.charCodeAt(fromStart + i)
    if (fromCode !== to.charCodeAt(toStart + i)) {
      break
    } else if (fromCode === CHAR_FORWARD_SLASH) {
      lastCommonSep = i
    }
  }
  if (i === length) {
    if (toLen > length) {
      if (to.charCodeAt(toStart + i) === CHAR_FORWARD_SLASH) {
        return to.substring(toStart + i + 1)
      }
      if (i === 0) {
        return to.substring(toStart + i)
      }
    } else if (fromLen > length) {
      if (from.charCodeAt(fromStart + i) === CHAR_FORWARD_SLASH) {
        lastCommonSep = i
      } else if (i === 0) {
        lastCommonSep = 0
      }
    }
  }

  let out = ''
  for (i = fromStart + lastCommonSep + 1; i <= fromEnd; ++i) {
    if (i === fromEnd || from.charCodeAt(i) === CHAR_FORWARD_SLASH) {
      out += out.length === 0 ? '..' : '/..'
    }
  }

  return `${out}${to.substring(toStart + lastCommonSep)}`
}

exports.toNamespacedPath = function toNamespacedPath (path) {
  return path
}

exports.dirname = function dirname (path) {
  if (path.length === 0) return '.'
  const hasRoot = path.charCodeAt(0) === CHAR_FORWARD_SLASH
  let end = -1
  let matchedSlash = true
  for (let i = path.length - 1; i >= 1; --i) {
    if (path.charCodeAt(i) === CHAR_FORWARD_SLASH) {
      if (!matchedSlash) {
        end = i
        break
      }
    } else {
      matchedSlash = false
    }
  }

  if (end === -1) return hasRoot ? '/' : '.'
  if (hasRoot && end === 1) return '//'
  return path.substring(0, end)
}

exports.basename = function basename (path, suffix) {
  let start = 0
  let end = -1
  let matchedSlash = true

  if (suffix !== undefined && suffix.length > 0 && suffix.length <= path.length) {
    if (suffix === path) { return '' }
    let extIdx = suffix.length - 1
    let firstNonSlashEnd = -1
    for (let i = path.length - 1; i >= 0; --i) {
      const code = path.charCodeAt(i)
      if (code === CHAR_FORWARD_SLASH) {
        if (!matchedSlash) {
          start = i + 1
          break
        }
      } else {
        if (firstNonSlashEnd === -1) {
          matchedSlash = false
          firstNonSlashEnd = i + 1
        }
        if (extIdx >= 0) {
          if (code === suffix.charCodeAt(extIdx)) {
            if (--extIdx === -1) {
              end = i
            }
          } else {
            extIdx = -1
            end = firstNonSlashEnd
          }
        }
      }
    }

    if (start === end) end = firstNonSlashEnd
    else if (end === -1) end = path.length
    return path.substring(start, end)
  }

  for (let i = path.length - 1; i >= 0; --i) {
    if (path.charCodeAt(i) === CHAR_FORWARD_SLASH) {
      if (!matchedSlash) {
        start = i + 1
        break
      }
    } else if (end === -1) {
      matchedSlash = false
      end = i + 1
    }
  }

  if (end === -1) return ''
  return path.substring(start, end)
}

exports.extname = function extname (path) {
  let startDot = -1
  let startPart = 0
  let end = -1
  let matchedSlash = true
  let preDotState = 0
  for (let i = path.length - 1; i >= 0; --i) {
    const code = path.charCodeAt(i)
    if (code === CHAR_FORWARD_SLASH) {
      if (!matchedSlash) {
        startPart = i + 1
        break
      }
      continue
    }
    if (end === -1) {
      matchedSlash = false
      end = i + 1
    }
    if (code === CHAR_DOT) {
      if (startDot === -1) startDot = i
      else if (preDotState !== 1) preDotState = 1
    } else if (startDot !== -1) {
      preDotState = -1
    }
  }

  if (startDot === -1 || end === -1 || preDotState === 0 || (preDotState === 1 && startDot === end - 1 && startDot === startPart + 1)) {
    return ''
  }
  return path.substring(startDot, end)
}
const {
  CHAR_DOT,
  CHAR_FORWARD_SLASH
} = require('./constants')

exports.normalizeString = function normalizeString (path, allowAboveRoot, separator, isPathSeparator) {
  let res = ''
  let lastSegmentLength = 0
  let lastSlash = -1
  let dots = 0
  let code = 0
  for (let i = 0; i <= path.length; ++i) {
    if (i < path.length) {
      code = path.charCodeAt(i)
    } else if (isPathSeparator(code)) {
      break
    } else {
      code = CHAR_FORWARD_SLASH
    }

    if (isPathSeparator(code)) {
      if (lastSlash === i - 1 || dots === 1) ;
      else if (dots === 2) {
        if (res.length < 2 || lastSegmentLength !== 2 || res.charCodeAt(res.length - 1) !== CHAR_DOT || res.charCodeAt(res.length - 2) !== CHAR_DOT) {
          if (res.length > 2) {
            const lastSlashIndex = res.lastIndexOf(separator)
            if (lastSlashIndex === -1) {
              res = ''
              lastSegmentLength = 0
            } else {
              res = res.substring(0, lastSlashIndex)
              lastSegmentLength =
                res.length - 1 - res.lastIndexOf(separator)
            }
            lastSlash = i
            dots = 0
            continue
          } else if (res.length !== 0) {
            res = ''
            lastSegmentLength = 0
            lastSlash = i
            dots = 0
            continue
          }
        }
        if (allowAboveRoot) {
          res += res.length > 0 ? `${separator}..` : '..'
          lastSegmentLength = 2
        }
      } else {
        if (res.length > 0) {
          res += `${separator}${path.substring(lastSlash + 1, i)}`
        } else {
          res = path.substring(lastSlash + 1, i)
        }
        lastSegmentLength = i - lastSlash - 1
      }
      lastSlash = i
      dots = 0
    } else if (code === CHAR_DOT && dots !== -1) {
      ++dots
    } else {
      dots = -1
    }
  }
  return res
}
const os = require('bare-os')

const { normalizeString } = require('./shared')
const {
  CHAR_UPPERCASE_A,
  CHAR_LOWERCASE_A,
  CHAR_UPPERCASE_Z,
  CHAR_LOWERCASE_Z,
  CHAR_DOT,
  CHAR_FORWARD_SLASH,
  CHAR_BACKWARD_SLASH,
  CHAR_COLON,
  CHAR_QUESTION_MARK
} = require('./constants')

function isWindowsPathSeparator (code) {
  return code === CHAR_FORWARD_SLASH || code === CHAR_BACKWARD_SLASH
}

function isWindowsDeviceRoot (code) {
  return (code >= CHAR_UPPERCASE_A && code <= CHAR_UPPERCASE_Z) ||
         (code >= CHAR_LOWERCASE_A && code <= CHAR_LOWERCASE_Z)
}

exports.posix = require('./posix')
exports.win32 = exports

exports.sep = '\\'
exports.delimiter = ';'

exports.resolve = function resolve (...args) {
  let resolvedDevice = ''
  let resolvedTail = ''
  let resolvedAbsolute = false

  for (let i = args.length - 1; i >= -1; i--) {
    let path
    if (i >= 0) {
      path = args[i]

      if (path.length === 0) continue
    } else if (resolvedDevice.length === 0) {
      path = os.cwd()
    } else {
      path = os.getEnv(`=${resolvedDevice}`) || os.cwd()

      if (path === undefined || (path.substring(0, 2).toLowerCase() !== resolvedDevice.toLowerCase() && path.charCodeAt(2) === CHAR_BACKWARD_SLASH)) {
        path = `${resolvedDevice}\\`
      }
    }

    const len = path.length
    let rootEnd = 0
    let device = ''
    let isAbsolute = false
    const code = path.charCodeAt(0)

    if (len === 1) {
      if (isWindowsPathSeparator(code)) {
        rootEnd = 1
        isAbsolute = true
      }
    } else if (isWindowsPathSeparator(code)) {
      isAbsolute = true

      if (isWindowsPathSeparator(path.charCodeAt(1))) {
        let j = 2
        let last = j
        while (j < len && !isWindowsPathSeparator(path.charCodeAt(j))) {
          j++
        }
        if (j < len && j !== last) {
          const firstPart = path.substring(last, j)
          last = j
          while (j < len && isWindowsPathSeparator(path.charCodeAt(j))) {
            j++
          }
          if (j < len && j !== last) {
            last = j
            while (j < len && !isWindowsPathSeparator(path.charCodeAt(j))) {
              j++
            }
            if (j === len || j !== last) {
              device = `\\\\${firstPart}\\${path.substring(last, j)}`
              rootEnd = j
            }
          }
        }
      } else {
        rootEnd = 1
      }
    } else if (isWindowsDeviceRoot(code) && path.charCodeAt(1) === CHAR_COLON) {
      device = path.substring(0, 2)
      rootEnd = 2
      if (len > 2 && isWindowsPathSeparator(path.charCodeAt(2))) {
        isAbsolute = true
        rootEnd = 3
      }
    }

    if (device.length > 0) {
      if (resolvedDevice.length > 0) {
        if (device.toLowerCase() !== resolvedDevice.toLowerCase()) { continue }
      } else {
        resolvedDevice = device
      }
    }

    if (resolvedAbsolute) {
      if (resolvedDevice.length > 0) { break }
    } else {
      resolvedTail = `${path.substring(rootEnd)}\\${resolvedTail}`
      resolvedAbsolute = isAbsolute
      if (isAbsolute && resolvedDevice.length > 0) {
        break
      }
    }
  }

  resolvedTail = normalizeString(resolvedTail, !resolvedAbsolute, '\\', isWindowsPathSeparator)

  return resolvedAbsolute ? `${resolvedDevice}\\${resolvedTail}` : `${resolvedDevice}${resolvedTail}` || '.'
}

exports.normalize = function normalize (path) {
  const len = path.length
  if (len === 0) return '.'
  let rootEnd = 0
  let device
  let isAbsolute = false
  const code = path.charCodeAt(0)

  if (len === 1) {
    return code === CHAR_FORWARD_SLASH ? '\\' : path
  }

  if (isWindowsPathSeparator(code)) {
    isAbsolute = true

    if (isWindowsPathSeparator(path.charCodeAt(1))) {
      let j = 2
      let last = j
      while (j < len && !isWindowsPathSeparator(path.charCodeAt(j))) {
        j++
      }
      if (j < len && j !== last) {
        const firstPart = path.substring(last, j)
        last = j
        while (j < len && isWindowsPathSeparator(path.charCodeAt(j))) {
          j++
        }
        if (j < len && j !== last) {
          last = j
          while (j < len && !isWindowsPathSeparator(path.charCodeAt(j))) {
            j++
          }
          if (j === len) {
            return `\\\\${firstPart}\\${path.substring(last)}\\`
          }
          if (j !== last) {
            device = `\\\\${firstPart}\\${path.substring(last, j)}`
            rootEnd = j
          }
        }
      }
    } else {
      rootEnd = 1
    }
  } else if (isWindowsDeviceRoot(code) && path.charCodeAt(1) === CHAR_COLON) {
    device = path.substring(0, 2)
    rootEnd = 2
    if (len > 2 && isWindowsPathSeparator(path.charCodeAt(2))) {
      isAbsolute = true
      rootEnd = 3
    }
  }

  let tail = rootEnd < len ? normalizeString(path.substring(rootEnd), !isAbsolute, '\\', isWindowsPathSeparator) : ''
  if (tail.length === 0 && !isAbsolute) {
    tail = '.'
  }
  if (tail.length > 0 && isWindowsPathSeparator(path.charCodeAt(len - 1))) {
    tail += '\\'
  }
  if (device === undefined) {
    return isAbsolute ? `\\${tail}` : tail
  }
  return isAbsolute ? `${device}\\${tail}` : `${device}${tail}`
}

exports.isAbsolute = function isAbsolute (path) {
  const len = path.length
  if (len === 0) return false

  const code = path.charCodeAt(0)

  return isWindowsPathSeparator(code) || (len > 2 && isWindowsDeviceRoot(code) && path.charCodeAt(1) === CHAR_COLON && isWindowsPathSeparator(path.charCodeAt(2)))
}

exports.join = function join (...args) {
  if (args.length === 0) return '.'

  let joined
  let firstPart
  for (let i = 0; i < args.length; ++i) {
    const arg = args[i]
    if (arg.length > 0) {
      if (joined === undefined) joined = firstPart = arg
      else joined += `\\${arg}`
    }
  }

  if (joined === undefined) return '.'

  let needsReplace = true
  let slashCount = 0
  if (isWindowsPathSeparator(firstPart.charCodeAt(0))) {
    ++slashCount
    const firstLen = firstPart.length
    if (firstLen > 1 && isWindowsPathSeparator(firstPart.charCodeAt(1))) {
      ++slashCount
      if (firstLen > 2) {
        if (isWindowsPathSeparator(firstPart.charCodeAt(2))) {
          ++slashCount
        } else {
          needsReplace = false
        }
      }
    }
  }
  if (needsReplace) {
    while (slashCount < joined.length && isWindowsPathSeparator(joined.charCodeAt(slashCount))) {
      slashCount++
    }

    if (slashCount >= 2) {
      joined = `\\${joined.substring(slashCount)}`
    }
  }

  return exports.normalize(joined)
}

exports.relative = function relative (from, to) {
  if (from === to) return ''

  const fromOrig = exports.resolve(from)
  const toOrig = exports.resolve(to)

  if (fromOrig === toOrig) return ''

  from = fromOrig.toLowerCase()
  to = toOrig.toLowerCase()

  if (from === to) return ''

  let fromStart = 0
  while (fromStart < from.length && from.charCodeAt(fromStart) === CHAR_BACKWARD_SLASH) {
    fromStart++
  }
  let fromEnd = from.length
  while (fromEnd - 1 > fromStart && from.charCodeAt(fromEnd - 1) === CHAR_BACKWARD_SLASH) {
    fromEnd--
  }
  const fromLen = fromEnd - fromStart

  let toStart = 0
  while (toStart < to.length && to.charCodeAt(toStart) === CHAR_BACKWARD_SLASH) {
    toStart++
  }
  let toEnd = to.length
  while (toEnd - 1 > toStart && to.charCodeAt(toEnd - 1) === CHAR_BACKWARD_SLASH) {
    toEnd--
  }
  const toLen = toEnd - toStart

  const length = fromLen < toLen ? fromLen : toLen
  let lastCommonSep = -1
  let i = 0
  for (; i < length; i++) {
    const fromCode = from.charCodeAt(fromStart + i)
    if (fromCode !== to.charCodeAt(toStart + i)) {
      break
    } else if (fromCode === CHAR_BACKWARD_SLASH) {
      lastCommonSep = i
    }
  }

  if (i !== length) {
    if (lastCommonSep === -1) return toOrig
  } else {
    if (toLen > length) {
      if (to.charCodeAt(toStart + i) === CHAR_BACKWARD_SLASH) {
        return toOrig.substring(toStart + i + 1)
      }
      if (i === 2) {
        return toOrig.substring(toStart + i)
      }
    }
    if (fromLen > length) {
      if (from.charCodeAt(fromStart + i) === CHAR_BACKWARD_SLASH) {
        lastCommonSep = i
      } else if (i === 2) {
        lastCommonSep = 3
      }
    }
    if (lastCommonSep === -1) lastCommonSep = 0
  }

  let out = ''
  for (i = fromStart + lastCommonSep + 1; i <= fromEnd; ++i) {
    if (i === fromEnd || from.charCodeAt(i) === CHAR_BACKWARD_SLASH) {
      out += out.length === 0 ? '..' : '\\..'
    }
  }

  toStart += lastCommonSep

  if (out.length > 0) {
    return `${out}${toOrig.substring(toStart, toEnd)}`
  }
  if (toOrig.charCodeAt(toStart) === CHAR_BACKWARD_SLASH) {
    ++toStart
  }
  return toOrig.substring(toStart, toEnd)
}

exports.toNamespacedPath = function toNamespacedPath (path) {
  if (path.length === 0) return path

  const resolvedPath = exports.resolve(path)

  if (resolvedPath.length <= 2) return path

  if (resolvedPath.charCodeAt(0) === CHAR_BACKWARD_SLASH) {
    if (resolvedPath.charCodeAt(1) === CHAR_BACKWARD_SLASH) {
      const code = resolvedPath.charCodeAt(2)
      if (code !== CHAR_QUESTION_MARK && code !== CHAR_DOT) {
        return `\\\\?\\UNC\\${resolvedPath.substring(2)}`
      }
    }
  } else if (
    isWindowsDeviceRoot(resolvedPath.charCodeAt(0)) &&
      resolvedPath.charCodeAt(1) === CHAR_COLON &&
      resolvedPath.charCodeAt(2) === CHAR_BACKWARD_SLASH
  ) {
    return `\\\\?\\${resolvedPath}`
  }

  return path
}

exports.dirname = function dirname (path) {
  const len = path.length
  if (len === 0) return '.'
  let rootEnd = -1
  let offset = 0
  const code = path.charCodeAt(0)

  if (len === 1) {
    return isWindowsPathSeparator(code) ? path : '.'
  }

  if (isWindowsPathSeparator(code)) {
    rootEnd = offset = 1

    if (isWindowsPathSeparator(path.charCodeAt(1))) {
      let j = 2
      let last = j
      while (j < len && !isWindowsPathSeparator(path.charCodeAt(j))) {
        j++
      }
      if (j < len && j !== last) {
        last = j
        while (j < len && isWindowsPathSeparator(path.charCodeAt(j))) {
          j++
        }
        if (j < len && j !== last) {
          last = j
          while (j < len && !isWindowsPathSeparator(path.charCodeAt(j))) {
            j++
          }
          if (j === len) {
            return path
          }
          if (j !== last) {
            rootEnd = offset = j + 1
          }
        }
      }
    }
  } else if (isWindowsDeviceRoot(code) && path.charCodeAt(1) === CHAR_COLON) {
    rootEnd = len > 2 && isWindowsPathSeparator(path.charCodeAt(2)) ? 3 : 2
    offset = rootEnd
  }

  let end = -1
  let matchedSlash = true
  for (let i = len - 1; i >= offset; --i) {
    if (isWindowsPathSeparator(path.charCodeAt(i))) {
      if (!matchedSlash) {
        end = i
        break
      }
    } else {
      matchedSlash = false
    }
  }

  if (end === -1) {
    if (rootEnd === -1) return '.'

    end = rootEnd
  }
  return path.substring(0, end)
}

exports.basename = function basename (path, suffix) {
  let start = 0
  let end = -1
  let matchedSlash = true

  if (path.length >= 2 && isWindowsDeviceRoot(path.charCodeAt(0)) && path.charCodeAt(1) === CHAR_COLON) {
    start = 2
  }

  if (suffix !== undefined && suffix.length > 0 && suffix.length <= path.length) {
    if (suffix === path) return ''
    let extIdx = suffix.length - 1
    let firstNonSlashEnd = -1
    for (let i = path.length - 1; i >= start; --i) {
      const code = path.charCodeAt(i)
      if (isWindowsPathSeparator(code)) {
        if (!matchedSlash) {
          start = i + 1
          break
        }
      } else {
        if (firstNonSlashEnd === -1) {
          matchedSlash = false
          firstNonSlashEnd = i + 1
        }
        if (extIdx >= 0) {
          if (code === suffix.charCodeAt(extIdx)) {
            if (--extIdx === -1) {
              end = i
            }
          } else {
            extIdx = -1
            end = firstNonSlashEnd
          }
        }
      }
    }

    if (start === end) end = firstNonSlashEnd
    else if (end === -1) end = path.length
    return path.substring(start, end)
  }
  for (let i = path.length - 1; i >= start; --i) {
    if (isWindowsPathSeparator(path.charCodeAt(i))) {
      if (!matchedSlash) {
        start = i + 1
        break
      }
    } else if (end === -1) {
      matchedSlash = false
      end = i + 1
    }
  }

  if (end === -1) return ''
  return path.substring(start, end)
}

exports.extname = function extname (path) {
  let start = 0
  let startDot = -1
  let startPart = 0
  let end = -1
  let matchedSlash = true
  let preDotState = 0

  if (path.length >= 2 && path.charCodeAt(1) === CHAR_COLON && isWindowsDeviceRoot(path.charCodeAt(0))) {
    start = startPart = 2
  }

  for (let i = path.length - 1; i >= start; --i) {
    const code = path.charCodeAt(i)
    if (isWindowsPathSeparator(code)) {
      if (!matchedSlash) {
        startPart = i + 1
        break
      }
      continue
    }
    if (end === -1) {
      matchedSlash = false
      end = i + 1
    }
    if (code === CHAR_DOT) {
      if (startDot === -1) startDot = i
      else if (preDotState !== 1) preDotState = 1
    } else if (startDot !== -1) {
      preDotState = -1
    }
  }

  if (startDot === -1 || end === -1 || preDotState === 0 || (preDotState === 1 && startDot === end - 1 && startDot === startPart + 1)) {
    return ''
  }
  return path.substring(startDot, end)
}
{
  "name": "bare-path",
  "version": "3.0.0",
  "description": "Path manipulation library for JavaScript",
  "exports": {
    ".": "./index.js",
    "./package": "./package.json",
    "./posix": "./lib/posix.js",
    "./win32": "./lib/win32.js"
  },
  "files": [
    "index.js",
    "lib",
    "NOTICE"
  ],
  "scripts": {
    "test": "standard && bare test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-path.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-path/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-path#readme",
  "dependencies": {
    "bare-os": "^3.0.1"
  },
  "devDependencies": {
    "brittle": "^3.3.2",
    "standard": "^17.0.0"
  }
}
module.exports = require.addon()
const EventEmitter = require('bare-events')
const { Duplex } = require('bare-stream')
const binding = require('./binding')
const constants = require('./lib/constants')
const errors = require('./lib/errors')

const defaultReadBufferSize = 65536
const empty = Buffer.alloc(0)

module.exports = exports = class Pipe extends Duplex {
  constructor(path, opts = {}) {
    if (typeof path === 'object' && path !== null) {
      opts = path
      path = null
    }

    const { readBufferSize = defaultReadBufferSize, allowHalfOpen = true, eagerOpen = true } = opts

    super({ eagerOpen })

    this._state = 0

    this._allowHalfOpen = allowHalfOpen

    this._fd = -1
    this._path = null

    this._pendingOpen = null
    this._pendingWrite = null
    this._pendingFinal = null
    this._pendingDestroy = null

    this._buffer = Buffer.alloc(readBufferSize)

    this._handle = binding.init(
      this._buffer,
      this,
      noop,
      this._onconnect,
      this._onwrite,
      this._onfinal,
      this._onread,
      this._onclose
    )

    if (typeof path === 'number') {
      this.open(path)
    } else if (typeof path === 'string') {
      this.connect(path)
    }
  }

  get connecting() {
    return (this._state & constants.state.CONNECTING) !== 0
  }

  get pending() {
    return (this._state & constants.state.CONNECTED) === 0
  }

  get readyState() {
    if (this._state & constants.state.READABLE && this._state & constants.state.WRITABLE) {
      return 'open'
    }

    if (this._state & constants.state.READABLE) {
      return 'readOnly'
    }

    if (this._state & constants.state.WRITABLE) {
      return 'writeOnly'
    }

    return 'opening'
  }

  open(fd, opts = {}, onconnect) {
    if (typeof opts === 'function') {
      onconnect = opts
      opts = {}
    }

    if (typeof fd === 'object' && fd !== null) {
      opts = fd || {}
      fd = opts.fd
    }

    try {
      const status = binding.open(this._handle, fd)

      this._state |= constants.state.CONNECTED
      this._fd = fd

      if (status & binding.READABLE) {
        this._state |= constants.state.READABLE
      } else {
        this.push(null)
      }

      if (status & binding.WRITABLE) {
        this._state |= constants.state.WRITABLE
      } else {
        this.end()
      }

      if (onconnect) this.once('connect', onconnect)

      queueMicrotask(() => this.emit('connect'))
    } catch (err) {
      queueMicrotask(() => {
        if (this._pendingOpen) this._pendingOpen(err)
        else this.destroy(err)
      })
    }

    return this
  }

  connect(path, opts = {}, onconnect) {
    if (this._state & constants.state.CONNECTING || this._state & constants.state.CONNECTED) {
      throw errors.PIPE_ALREADY_CONNECTED('Pipe is already connected')
    }

    this._state |= constants.state.CONNECTING

    if (typeof opts === 'function') {
      onconnect = opts
      opts = {}
    }

    if (typeof path === 'object' && path !== null) {
      opts = path || {}
      path = opts.path
    }

    try {
      binding.connect(this._handle, path)

      this._path = path

      if (onconnect) this.once('connect', onconnect)
    } catch (err) {
      queueMicrotask(() => {
        if (this._pendingOpen) this._pendingOpen(err)
        else this.destroy(err)
      })
    }

    return this
  }

  ref() {
    binding.ref(this._handle)

    return this
  }

  unref() {
    binding.unref(this._handle)

    return this
  }

  _open(cb) {
    if (this._state & constants.state.CONNECTED) return cb(null)

    this._pendingOpen = cb
  }

  _read() {
    if ((this._state & constants.state.READING) === 0) {
      this._state |= constants.state.READING

      binding.resume(this._handle)
    }
  }

  _writev(batch, cb) {
    this._pendingWrite = [cb, batch]

    binding.writev(
      this._handle,
      batch.map(({ chunk }) => chunk)
    )
  }

  _final(cb) {
    if (this._state & constants.state.READABLE && this._state & constants.state.WRITABLE) {
      this._pendingFinal = cb

      binding.end(this._handle)
    } else {
      cb(null)
    }
  }

  _predestroy() {
    if (this._state & constants.state.CLOSING) return
    this._state |= constants.state.CLOSING

    binding.close(this._handle)
  }

  _destroy(err, cb) {
    if (this._state & constants.state.CLOSING) return cb(err)
    this._state |= constants.state.CLOSING

    this._pendingDestroy = cb

    binding.close(this._handle)
  }

  _continueOpen(err) {
    if (this._pendingOpen === null) return
    const cb = this._pendingOpen
    this._pendingOpen = null
    cb(err)
  }

  _continueWrite(err) {
    if (this._pendingWrite === null) return
    const cb = this._pendingWrite[0]
    this._pendingWrite = null
    cb(err)
  }

  _continueFinal(err) {
    if (this._pendingFinal === null) return
    const cb = this._pendingFinal
    this._pendingFinal = null
    cb(err)
  }

  _continueDestroy() {
    if (this._pendingDestroy === null) return
    const cb = this._pendingDestroy
    this._pendingDestroy = null
    cb(null)
  }

  _onconnect(err) {
    if (err) {
      if (this._pendingOpen) this._continueOpen(err)
      else this.destroy(err)
      return
    }

    this._state |= constants.state.CONNECTED | constants.state.READABLE | constants.state.WRITABLE
    this._state &= ~constants.state.CONNECTING
    this._continueOpen()

    this.emit('connect')
  }

  _onread(err, read) {
    if (err) {
      this.destroy(err)
      return
    }

    if (read === 0) {
      this.push(null)
      if (this._allowHalfOpen === false) this.end()
      return
    }

    const copy = Buffer.allocUnsafe(read)
    copy.set(this._buffer.subarray(0, read))

    if (this.push(copy) === false && this.destroying === false) {
      this._state &= ~constants.state.READING

      binding.pause(this._handle)
    }
  }

  _onwrite(err) {
    this._continueWrite(err)
  }

  _onfinal(err) {
    this._continueFinal(err === null || err.code === 'ENOTCONN' ? null : err)
  }

  _onclose() {
    this._continueDestroy()
  }

  _onspawn(readable, writable) {
    this._state |= constants.state.CONNECTED

    if (readable) {
      this._state |= constants.state.READABLE
    } else {
      this.push(null)
    }

    if (writable) {
      this._state |= constants.state.WRITABLE
    } else {
      this.end()
    }

    this._continueOpen()
  }
}

exports.Pipe = exports

exports.pipe = function pipe() {
  return binding.pipe()
}

exports.Server = class PipeServer extends EventEmitter {
  constructor(opts = {}, onconnection) {
    if (typeof opts === 'function') {
      onconnection = opts
      opts = {}
    }

    super()

    const {
      readBufferSize = defaultReadBufferSize,
      allowHalfOpen = true,
      pauseOnConnect = false
    } = opts

    this._state = 0

    this._readBufferSize = readBufferSize
    this._allowHalfOpen = allowHalfOpen
    this._pauseOnConnect = pauseOnConnect

    this._path = null
    this._connections = new Set()

    this._error = null
    this._handle = null

    if (onconnection) this.on('connection', onconnection)
  }

  get listening() {
    return (this._state & constants.state.BOUND) !== 0
  }

  address() {
    if ((this._state & constants.state.BOUND) === 0) {
      return null
    }

    return this._path
  }

  listen(path, backlog = 511, opts = {}, onlistening) {
    if (this._state & constants.state.BINDING || this._state & constants.state.BOUND) {
      throw errors.SERVER_ALREADY_LISTENING('Server is already listening')
    }

    if (this._state & constants.state.CLOSING) {
      throw errors.SERVER_IS_CLOSED('Server is closed')
    }

    this._state |= constants.state.BINDING

    if (typeof backlog === 'function') {
      onlistening = backlog
      backlog = 511
    } else if (typeof opts === 'function') {
      onlistening = opts
      opts = {}
    }

    if (typeof path === 'object' && path !== null) {
      opts = path || {}
      path = opts.path
      backlog = opts.backlog || 511
    }

    this._handle = binding.init(
      empty,
      this,
      this._onconnection,
      noop,
      noop,
      noop,
      noop,
      this._onclose
    )

    if (this._state & constants.state.UNREFED) binding.unref(this._handle)

    try {
      binding.bind(this._handle, path, backlog)

      this._path = path
      this._state |= constants.state.BOUND
      this._state &= ~constants.state.BINDING

      if (onlistening) this.once('listening', onlistening)

      queueMicrotask(() => this.emit('listening'))
    } catch (err) {
      this._error = err

      binding.close(this._handle)
    }

    return this
  }

  close(onclose) {
    if (onclose) this.once('close', onclose)

    if (this._state & constants.state.CLOSING) return
    this._state |= constants.state.CLOSING

    this._closeMaybe()

    return this
  }

  ref() {
    this._state &= ~constants.state.UNREFED

    if (this._handle !== null) binding.ref(this._handle)

    return this
  }

  unref() {
    this._state |= constants.state.UNREFED

    if (this._handle !== null) binding.unref(this._handle)

    return this
  }

  _closeMaybe() {
    if (this._state & constants.state.CLOSING && this._connections.size === 0) {
      if (this._handle !== null) binding.close(this._handle)
      else queueMicrotask(() => this.emit('close'))
    }
  }

  _onconnection(err) {
    if (err) {
      this.emit('error', err)
      return
    }

    if (this._state & constants.state.CLOSING) return

    const pipe = new exports.Pipe({
      readBufferSize: this._readBufferSize,
      allowHalfOpen: this._allowHalfOpen,
      eagerOpen: !this._pauseOnConnect
    })

    try {
      binding.accept(this._handle, pipe._handle)

      pipe._path = this._path
      pipe._state |= constants.state.CONNECTED | constants.state.READABLE | constants.state.WRITABLE

      this._connections.add(pipe)

      pipe.on('close', () => {
        this._connections.delete(pipe)
        this._closeMaybe()
      })

      this.emit('connection', pipe)
    } catch (err) {
      pipe.destroy()

      throw err
    }
  }

  _onclose() {
    const err = this._error

    this._state &= ~constants.state.BINDING
    this._error = null
    this._handle = null

    if (err) this.emit('error', err)
    else this.emit('close')
  }
}

exports.constants = constants
exports.errors = errors

exports.createConnection = function createConnection(path, opts, onconnect) {
  if (typeof opts === 'function') {
    onconnect = opts
    opts = {}
  }

  if (typeof path === 'object' && path !== null) {
    opts = path || {}
    path = opts.path
  }

  return new exports.Pipe(opts).connect(path, opts, onconnect)
}

exports.createServer = function createServer(opts, onconnection) {
  return new exports.Server(opts, onconnection)
}

function noop() {}
module.exports = {
  state: {
    CONNECTING: 0x1,
    CONNECTED: 0x2,
    BINDING: 0x4,
    BOUND: 0x8,
    READING: 0x10,
    CLOSING: 0x20,
    READABLE: 0x40,
    WRITABLE: 0x80,
    UNREFED: 0x100
  }
}
module.exports = class PipeError extends Error {
  constructor(msg, fn = PipeError, code = fn.name) {
    super(`${code}: ${msg}`)
    this.code = code

    if (Error.captureStackTrace) {
      Error.captureStackTrace(this, fn)
    }
  }

  get name() {
    return 'PipeError'
  }

  static PIPE_ALREADY_CONNECTED(msg) {
    return new PipeError(msg, PipeError.PIPE_ALREADY_CONNECTED)
  }

  static SERVER_ALREADY_LISTENING(msg) {
    return new PipeError(msg, PipeError.SERVER_ALREADY_LISTENING)
  }

  static SERVER_IS_CLOSED(msg) {
    return new PipeError(msg, PipeError.SERVER_IS_CLOSED)
  }
}
{
  "name": "bare-pipe",
  "version": "4.1.5",
  "description": "Native I/O pipes for JavaScript",
  "exports": {
    "./package": "./package.json",
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    },
    "./constants": {
      "types": "./lib/constants.d.ts",
      "default": "./lib/constants.js"
    },
    "./errors": {
      "types": "./lib/errors.d.ts",
      "default": "./lib/errors.js"
    }
  },
  "files": [
    "index.js",
    "index.d.ts",
    "binding.c",
    "binding.js",
    "CMakeLists.txt",
    "lib",
    "prebuilds"
  ],
  "addon": true,
  "scripts": {
    "test": "prettier . --check && bare test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-pipe.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-pipe/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-pipe#readme",
  "engines": {
    "bare": ">=1.16.0"
  },
  "dependencies": {
    "bare-events": "^2.0.0",
    "bare-stream": "^2.0.0"
  },
  "devDependencies": {
    "bare-path": "^3.0.0",
    "brittle": "^3.2.1",
    "cmake-bare": "^1.1.6",
    "prettier": "^3.4.1",
    "prettier-config-holepunch": "^2.0.0"
  }
}
const abort = require('bare-abort')
const EventEmitter = require('bare-events')
const Signal = require('bare-signals')
const os = require('bare-os')
const env = require('bare-env')
const hrtime = require('bare-hrtime')
const stdio = require('bare-stdio')

class Process extends EventEmitter {
  constructor() {
    super()

    this._startTime = hrtime.bigint()

    EventEmitter.forward(Bare, this, [
      'uncaughtException',
      'unhandledRejection',
      'beforeExit',
      'exit',
      'suspend',
      'wakeup',
      'idle',
      'resume'
    ])

    const signals = new Signal.Emitter()

    signals.unref()

    EventEmitter.forward(signals, this, Object.keys(os.constants.signals))
  }

  get stdin() {
    return stdio.in
  }

  get stdout() {
    return stdio.out
  }

  get stderr() {
    return stdio.err
  }

  get platform() {
    return os.platform()
  }

  get arch() {
    return os.arch()
  }

  get title() {
    return os.getProcessTitle()
  }

  set title(title) {
    os.setProcessTitle(title)
  }

  get pid() {
    return os.pid()
  }

  get ppid() {
    return os.ppid()
  }

  get argv() {
    return Bare.argv
  }

  get execPath() {
    return os.execPath()
  }

  get exitCode() {
    return Bare.exitCode
  }

  set exitCode(code) {
    Bare.exitCode = code
  }

  get version() {
    return Bare.version
  }

  get versions() {
    return Bare.versions
  }

  get env() {
    return env
  }

  get hrtime() {
    return hrtime
  }

  abort() {
    abort()
  }

  exit(code) {
    Bare.exit(code)
  }

  suspend() {
    Bare.suspend()
  }

  resume() {
    Bare.resume()
  }

  cwd() {
    return os.cwd()
  }

  chdir(directory) {
    os.chdir(directory)
  }

  kill(pid, signal) {
    os.kill(pid, signal)
  }

  uptime() {
    return Number(hrtime.bigint() - this._startTime) / 1e9
  }

  cpuUsage(previous) {
    return os.cpuUsage(previous)
  }

  threadCpuUsage(previous) {
    return os.threadCpuUsage(previous)
  }

  resourceUsage() {
    return os.resourceUsage()
  }

  availableMemory() {
    return os.availableMemory()
  }

  constrainedMemory() {
    return os.constrainedMemory()
  }

  memoryUsage() {
    return os.memoryUsage()
  }

  nextTick(cb, ...args) {
    queueMicrotask(cb.bind(null, ...args))
  }

  [Symbol.for('bare.inspect')]() {
    return {
      __proto__: { constructor: Process },

      platform: this.platform,
      arch: this.arch,
      title: this.title,
      pid: this.pid,
      ppid: this.ppid,
      argv: this.argv,
      execPath: this.execPath,
      exitCode: this.exitCode,
      version: this.version,
      versions: this.versions,
      env: this.env
    }
  }
}

module.exports = new Process()
{
  "name": "bare-process",
  "version": "4.4.1",
  "description": "Node.js-compatible process control for Bare",
  "exports": {
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    },
    "./global": {
      "types": "./global.d.ts",
      "default": "./global.js"
    },
    "./package": "./package.json"
  },
  "files": [
    "index.js",
    "index.d.ts",
    "global.js",
    "global.d.ts"
  ],
  "scripts": {
    "format": "prettier --write . && lunte --fix",
    "lint": "prettier --check . && lunte",
    "test": "brittle-bare --coverage test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-process.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-process/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-process#readme",
  "dependencies": {
    "bare-abort": "^2.0.13",
    "bare-env": "^3.0.0",
    "bare-events": "^2.3.1",
    "bare-hrtime": "^2.0.0",
    "bare-os": "^3.7.1",
    "bare-signals": "^4.0.0",
    "bare-stdio": "^1.0.1"
  },
  "devDependencies": {
    "brittle": "^3.2.1",
    "lunte": "^1.6.0",
    "prettier": "^3.4.1",
    "prettier-config-holepunch": "^2.0.0"
  }
}
module.exports = require.addon()
const EventEmitter = require('bare-events')
const os = require('bare-os')
const binding = require('./binding')
const errors = require('./lib/errors')

const signals = os.constants.signals

module.exports = exports = class Signal extends EventEmitter {
  constructor(signum) {
    super()

    if (typeof signum === 'string') {
      if (signum in signals === false) {
        throw errors.UNKNOWN_SIGNAL(`Unknown signal '${signum}'`)
      }

      signum = signals[signum]
    }

    this._signum = signum
    this._closing = null
    this._handle = binding.init(this, this._onsignal, this._onclose)
  }

  start() {
    if (this._closing) throw errors.SIGNAL_CLOSED('Signal is closed')
    binding.start(this._handle, this._signum)
    return this
  }

  stop() {
    if (this._closing) return this
    binding.stop(this._handle)
    return this
  }

  ref() {
    if (this._closing) return this
    binding.ref(this._handle)
    return this
  }

  unref() {
    if (this._closing) return this
    binding.unref(this._handle)
    return this
  }

  close() {
    if (this._closing) return this._closing
    this._closing = EventEmitter.once(this, 'close')

    binding.close(this._handle)

    return this._closing
  }

  _onsignal() {
    this.emit('signal', this._signum)
  }

  _onclose() {
    this._handle = null

    this.emit('close')
  }

  static send(signum, pid = os.pid()) {
    os.kill(pid, signum)
  }
}

exports.Emitter = require('./lib/emitter')

exports.constants = signals
exports.errors = errors
const EventEmitter = require('bare-events')
const Signal = require('..')

module.exports = class SignalEmitter extends EventEmitter {
  constructor() {
    super()

    this._signals = new Map()
    this._unrefed = false

    this.on('newListener', this._onnewlistener).on(
      'removeListener',
      this._onremovelistener
    )
  }

  ref() {
    this._unrefed = false
    for (const signal of this._signals.values()) signal.ref()
    return this
  }

  unref() {
    this._unrefed = true
    for (const signal of this._signals.values()) signal.unref()
    return this
  }

  _onnewlistener(name) {
    if (name === 'newListener' || name === 'removeListener') return

    if (this.listenerCount(name) === 0) {
      const signal = new Signal(name)

      signal.on('signal', this._onsignal.bind(this, name)).start()

      if (this._unrefed) signal.unref()

      this._signals.set(name, signal)
    }
  }

  _onremovelistener(name) {
    if (name === 'newListener' || name === 'removeListener') return

    if (this.listenerCount(name) === 0) {
      const signal = this._signals.get(name)

      if (this._unrefed) signal.ref()

      signal.close()

      this._signals.delete(name)
    }
  }

  _onsignal(name) {
    this.emit(name, name, Signal.constants[name])
  }
}
module.exports = class SignalError extends Error {
  constructor(msg, fn = SignalError, code = fn.name) {
    super(`${code}: ${msg}`)
    this.code = code

    if (Error.captureStackTrace) {
      Error.captureStackTrace(this, fn)
    }
  }

  get name() {
    return 'SignalError'
  }

  static UNKNOWN_SIGNAL(msg) {
    return new SignalError(msg, SignalError.UNKNOWN_SIGNAL)
  }

  static SIGNAL_CLOSED(msg) {
    return new SignalError(msg, SignalError.SIGNAL_CLOSED)
  }
}
{
  "name": "bare-signals",
  "version": "4.2.0",
  "description": "Native signal handling for JavaScript",
  "exports": {
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    },
    "./package": "./package.json",
    "./errors": "./lib/errors.js",
    "./emitter": "./lib/emitter.js"
  },
  "files": [
    "index.js",
    "index.d.ts",
    "binding.c",
    "binding.js",
    "CMakeLists.txt",
    "lib",
    "prebuilds"
  ],
  "addon": true,
  "scripts": {
    "test": "prettier . --check && bare test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-signals.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-signals/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-signals#readme",
  "engines": {
    "bare": ">=1.7.0"
  },
  "dependencies": {
    "bare-events": "^2.5.3",
    "bare-os": "^3.3.1"
  },
  "devDependencies": {
    "brittle": "^3.2.1",
    "cmake-bare": "^1.1.6",
    "prettier": "^3.4.1",
    "prettier-config-standard": "^7.0.0"
  }
}
module.exports = require.addon()
const fs = require('bare-fs')
const tty = require('bare-tty')
const Pipe = require('bare-pipe')
const binding = require('./binding')

const { TTY, NAMED_PIPE } = binding

class IO {
  constructor() {
    this._in = null
    this._out = null
    this._err = null
  }

  get in() {
    if (this._in === null) {
      switch (binding.guessType(0)) {
        case TTY:
          this._in = new tty.ReadStream(0)
          break
        case NAMED_PIPE:
          this._in = new Pipe(0, { eagerOpen: false })
          break
        default:
          this._in = fs.createReadStream(null, { fd: 0, eagerOpen: false })
      }
    }

    return this._in
  }

  get out() {
    if (this._out === null) {
      switch (binding.guessType(1)) {
        case TTY:
          this._out = new tty.WriteStream(1)
          break
        case NAMED_PIPE:
          this._out = new Pipe(1, { eagerOpen: false })
          this._out.unref()
          break
        default:
          this._out = fs.createWriteStream(null, { fd: 1, eagerOpen: false })
      }
    }

    return this._out
  }

  get err() {
    if (this._err === null) {
      switch (binding.guessType(2)) {
        case TTY:
          this._err = new tty.WriteStream(2)
          break
        case NAMED_PIPE:
          this._err = new Pipe(2, { eagerOpen: false })
          this._err.unref()
          break
        default:
          this._err = fs.createWriteStream(null, { fd: 2, eagerOpen: false })
      }
    }

    return this._err
  }
}

module.exports = new IO()
{
  "name": "bare-stdio",
  "version": "1.0.2",
  "description": "Standard input/output streams for Bare",
  "exports": {
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    },
    "./package": "./package.json",
    "./constants": "./lib/constants.js",
    "./errors": "./lib/errors.js"
  },
  "files": [
    "index.js",
    "index.d.ts",
    "binding.c",
    "binding.js",
    "CMakeLists.txt",
    "lib",
    "prebuilds"
  ],
  "addon": true,
  "scripts": {
    "format": "prettier --write . && lunte --fix",
    "lint": "prettier --check . && lunte",
    "test": "brittle-bare --coverage test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-stdio.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-stdio/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-stdio#readme",
  "dependencies": {
    "bare-fs": "^4.5.2",
    "bare-pipe": "^4.1.5",
    "bare-tty": "^5.0.3"
  },
  "devDependencies": {
    "bare-os": "^3.6.2",
    "bare-path": "^3.0.0",
    "bare-subprocess": "^3.1.4",
    "brittle": "^3.19.1",
    "cmake-bare": "^1.7.7",
    "lunte": "^1.4.0",
    "prettier": "^3.8.1",
    "prettier-config-holepunch": "^2.0.0",
    "tt-native": "^1.1.1"
  }
}
const stream = require('streamx')
const { ReadableStream, WritableStream } = require('./web')

const defaultEncoding = 'utf8'

module.exports = exports = stream.Stream

exports.pipeline = stream.pipeline

exports.isStream = stream.isStream
exports.isEnding = stream.isEnding
exports.isEnded = stream.isEnded
exports.isFinishing = stream.isFinishing
exports.isFinished = stream.isFinished
exports.isDisturbed = stream.isDisturbed

exports.isErrored = function isErrored(stream) {
  return exports.getStreamError(stream) !== null
}

exports.isReadable = function isReadable(stream) {
  return stream.readable && !stream.destroying && !exports.isEnded(stream)
}

exports.isWritable = function isWritable(stream) {
  return stream.writable && !stream.destroying && !exports.isFinishing(stream)
}

exports.getStreamError = stream.getStreamError

exports.addAbortSignal = function addAbortSignal(signal, stream) {
  function onAbort() {
    stream.destroy(signal.reason)
  }

  if (signal.aborted) onAbort()
  else signal.addEventListener('abort', onAbort)

  return stream
}

exports.Stream = exports

exports.Readable = class Readable extends stream.Readable {
  constructor(opts = {}) {
    super({
      ...opts,
      byteLength: null,
      byteLengthReadable: null,
      map: null,
      mapReadable: null
    })

    if (this._construct) this._open = this._construct

    if (this._read !== stream.Readable.prototype._read) {
      this._read = read.bind(this, this._read)
    }

    if (this._destroy !== stream.Stream.prototype._destroy) {
      this._destroy = destroy.bind(this, this._destroy)
    }
  }

  get closed() {
    return !exports.isReadable(this)
  }

  get errored() {
    return stream.getStreamError(this)
  }

  push(chunk, encoding) {
    if (typeof chunk === 'string') {
      chunk = Buffer.from(chunk, encoding || defaultEncoding)
    }

    return super.push(chunk)
  }

  unshift(chunk, encoding) {
    if (typeof chunk === 'string') {
      chunk = Buffer.from(chunk, encoding || defaultEncoding)
    }

    super.unshift(chunk)
  }

  static fromWeb(readableStream, opts = {}) {
    const stream = readableStream._stream

    if (opts.encoding) stream.setEncoding(opts.encoding)
    if (opts.signal) exports.addAbortSignal(opts.signal, stream)

    return stream
  }

  static toWeb(readable, opts = {}) {
    return new ReadableStream(readable, opts.strategy)
  }

  async [Symbol.asyncDispose]() {
    if (!this.destroyed) this.destroy()

    await new Promise((resolve) => exports.finished(this, resolve))
  }
}

exports.Writable = class Writable extends stream.Writable {
  constructor(opts = {}) {
    super({
      ...opts,
      byteLength: null,
      byteLengthWritable,
      map: null,
      mapWritable: null
    })

    if (this._construct) this._open = this._construct

    if (this._write !== stream.Writable.prototype._write) {
      this._write = write.bind(this, this._write)
    }

    if (this._destroy !== stream.Stream.prototype._destroy) {
      this._destroy = destroy.bind(this, this._destroy)
    }
  }

  get closed() {
    return !exports.isWritable(this)
  }

  get errored() {
    return stream.getStreamError(this)
  }

  write(chunk, encoding, cb) {
    if (typeof encoding === 'function') {
      cb = encoding
      encoding = null
    }

    if (typeof chunk === 'string') {
      encoding = encoding || defaultEncoding
      chunk = Buffer.from(chunk, encoding)
    } else {
      encoding = 'buffer'
    }

    const result = super.write({ chunk, encoding })

    if (cb) stream.Writable.drained(this).then(() => cb(null), cb)

    return result
  }

  end(chunk, encoding, cb) {
    if (typeof chunk === 'function') {
      cb = chunk
      chunk = null
    } else if (typeof encoding === 'function') {
      cb = encoding
      encoding = null
    }

    if (typeof chunk === 'string') {
      encoding = encoding || defaultEncoding
      chunk = Buffer.from(chunk, encoding || defaultEncoding)
    } else {
      encoding = 'buffer'
    }

    const result =
      chunk !== undefined && chunk !== null ? super.end({ chunk, encoding }) : super.end()

    if (cb) this.once('finish', () => cb(null))

    return result
  }

  static fromWeb(writableStream, opts = {}) {
    const stream = writableStream._stream

    if (opts.signal) exports.addAbortSignal(opts.signal, stream)

    return stream
  }

  static toWeb(writable) {
    return new WritableStream(writable)
  }

  async [Symbol.asyncDispose]() {
    if (!this.destroyed) this.destroy()

    await new Promise((resolve) => exports.finished(this, resolve))
  }
}

exports.Duplex = class Duplex extends stream.Duplex {
  constructor(opts = {}) {
    super({
      ...opts,
      byteLength: null,
      byteLengthReadable: null,
      byteLengthWritable,
      map: null,
      mapReadable: null,
      mapWritable: null
    })

    if (this._construct) this._open = this._construct

    if (this._read !== stream.Readable.prototype._read) {
      this._read = read.bind(this, this._read)
    }

    if (this._write !== stream.Duplex.prototype._write) {
      this._write = write.bind(this, this._write)
    }

    if (this._destroy !== stream.Stream.prototype._destroy) {
      this._destroy = destroy.bind(this, this._destroy)
    }
  }

  push(chunk, encoding) {
    if (typeof chunk === 'string') {
      chunk = Buffer.from(chunk, encoding || defaultEncoding)
    }

    return super.push(chunk)
  }

  unshift(chunk, encoding) {
    if (typeof chunk === 'string') {
      chunk = Buffer.from(chunk, encoding || defaultEncoding)
    }

    super.unshift(chunk)
  }

  write(chunk, encoding, cb) {
    if (typeof encoding === 'function') {
      cb = encoding
      encoding = null
    }

    if (typeof chunk === 'string') {
      encoding = encoding || defaultEncoding
      chunk = Buffer.from(chunk, encoding)
    } else {
      encoding = 'buffer'
    }

    const result = super.write({ chunk, encoding })

    if (cb) stream.Writable.drained(this).then(() => cb(null), cb)

    return result
  }

  end(chunk, encoding, cb) {
    if (typeof chunk === 'function') {
      cb = chunk
      chunk = null
    } else if (typeof encoding === 'function') {
      cb = encoding
      encoding = null
    }

    if (typeof chunk === 'string') {
      encoding = encoding || defaultEncoding
      chunk = Buffer.from(chunk, encoding)
    } else {
      encoding = 'buffer'
    }

    const result =
      chunk !== undefined && chunk !== null ? super.end({ chunk, encoding }) : super.end()

    if (cb) this.once('finish', () => cb(null))

    return result
  }

  static fromWeb({ readable: readableStream, writable: writableStream }, opts) {
    const readable = exports.Readable.fromWeb(readableStream, opts)
    const writable = exports.Writable.fromWeb(writableStream, opts)

    const duplex = new exports.Duplex({
      write(data, encoding, cb) {
        writable.write(data, encoding, cb)
      }
    })

    readable
      .on('data', (data) => duplex.push(data))
      .on('end', () => duplex.push(null))
      .on('error', (err) => duplex.destroy(err))

    writable.on('finish', () => duplex.end()).on('error', (err) => duplex.destroy(err))

    return duplex
  }

  static toWeb(duplex) {
    const readableStream = exports.Readable.toWeb(duplex)
    const writableStream = exports.Writable.toWeb(duplex)

    return { readable: readableStream, writable: writableStream }
  }
}

class DuplexSide extends exports.Duplex {
  constructor(opts) {
    super(opts)

    this._otherSide = null
    this._cb = null
  }

  _read() {
    const cb = this._cb
    if (!cb) return

    this._cb = null
    cb()
  }

  _write(chunk, encoding, cb) {
    this._otherSide.push(chunk, encoding)
    this._otherSide._cb = cb
  }

  _final(cb) {
    this._otherSide.on('end', cb)
    this._otherSide.push(null)
  }
}

exports.duplexPair = function duplexPair(opts) {
  const sideA = new DuplexSide(opts)
  const sideB = new DuplexSide(opts)

  sideA._otherSide = sideB
  sideB._otherSide = sideA

  return [sideA, sideB]
}

exports.Transform = class Transform extends stream.Transform {
  constructor(opts = {}) {
    super({
      ...opts,
      byteLength: null,
      byteLengthReadable: null,
      byteLengthWritable,
      map: null,
      mapReadable: null,
      mapWritable: null
    })

    if (this._transform !== stream.Transform.prototype._transform) {
      this._transform = transform.bind(this, this._transform)
    } else {
      this._transform = passthrough
    }
  }

  push(chunk, encoding) {
    if (typeof chunk === 'string') {
      chunk = Buffer.from(chunk, encoding || defaultEncoding)
    }

    return super.push(chunk)
  }

  unshift(chunk, encoding) {
    if (typeof chunk === 'string') {
      chunk = Buffer.from(chunk, encoding || defaultEncoding)
    }

    super.unshift(chunk)
  }

  write(chunk, encoding, cb) {
    if (typeof encoding === 'function') {
      cb = encoding
      encoding = null
    }

    if (typeof chunk === 'string') {
      encoding = encoding || defaultEncoding
      chunk = Buffer.from(chunk, encoding)
    } else {
      encoding = 'buffer'
    }

    const result = super.write({ chunk, encoding })

    if (cb) stream.Writable.drained(this).then(() => cb(null), cb)

    return result
  }

  end(chunk, encoding, cb) {
    if (typeof chunk === 'function') {
      cb = chunk
      chunk = null
    } else if (typeof encoding === 'function') {
      cb = encoding
      encoding = null
    }

    if (typeof chunk === 'string') {
      encoding = encoding || defaultEncoding
      chunk = Buffer.from(chunk, encoding)
    } else {
      encoding = 'buffer'
    }

    const result =
      chunk !== undefined && chunk !== null ? super.end({ chunk, encoding }) : super.end()

    if (cb) this.once('finish', () => cb(null))

    return result
  }
}

exports.PassThrough = class PassThrough extends exports.Transform {}

exports.finished = function finished(stream, opts, cb) {
  if (typeof opts === 'function') {
    cb = opts
    opts = {}
  }

  if (!opts) opts = {}

  const { cleanup = false } = opts

  const done = () => {
    cb(exports.getStreamError(stream, { all: true }))

    if (cleanup) detach()
  }

  const detach = () => {
    stream.off('close', done)
    stream.off('error', noop)
  }

  if (stream.destroyed) {
    done()
  } else {
    stream.on('close', done)
    stream.on('error', noop)
  }

  return detach
}

function read(read, cb) {
  read.call(this, 65536)

  cb(null)
}

function write(write, data, cb) {
  write.call(this, data.chunk, data.encoding, cb)
}

function transform(transform, data, cb) {
  transform.call(this, data.chunk, data.encoding, cb)
}

function destroy(destroy, cb) {
  destroy.call(this, exports.getStreamError(this), cb)
}

function passthrough(data, cb) {
  cb(null, data.chunk)
}

function byteLengthWritable(data) {
  return data.chunk.byteLength
}

function noop() {}
{
  "name": "bare-stream",
  "version": "2.13.1",
  "description": "Streaming data for JavaScript",
  "exports": {
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    },
    "./package": "./package.json",
    "./promises": "./promises.js",
    "./web": {
      "types": "./web.d.ts",
      "default": "./web.js"
    },
    "./global": {
      "types": "./global.d.ts",
      "default": "./global.js"
    }
  },
  "files": [
    "index.js",
    "index.d.ts",
    "promises.js",
    "web.js",
    "web.d.ts",
    "global.js",
    "global.d.ts"
  ],
  "scripts": {
    "format": "prettier --write . && lunte --fix",
    "lint": "prettier --check . && lunte",
    "test": "brittle-bare --coverage test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-stream.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-stream/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-stream#readme",
  "dependencies": {
    "streamx": "^2.25.0",
    "teex": "^1.0.1"
  },
  "devDependencies": {
    "bare-abort-controller": "^1.1.0",
    "bare-buffer": "^3.0.0",
    "bare-events": "^2.5.4",
    "brittle": "^3.5.2",
    "lunte": "^1.6.0",
    "prettier": "^3.3.3",
    "prettier-config-holepunch": "^2.0.0"
  },
  "peerDependencies": {
    "bare-abort-controller": "*",
    "bare-buffer": "*",
    "bare-events": "*"
  },
  "peerDependenciesMeta": {
    "bare-abort-controller": {
      "optional": true
    },
    "bare-buffer": {
      "optional": true
    },
    "bare-events": {
      "optional": true
    }
  }
}
const { Readable, Writable, Transform, getStreamError, isStreamx, isDisturbed } = require('streamx')
const tee = require('teex')

const readableKind = Symbol.for('bare.stream.readable.kind')
const writableKind = Symbol.for('bare.stream.writable.kind')
const transformKind = Symbol.for('bare.stream.transform.kind')

// https://streams.spec.whatwg.org/#readablestreamdefaultreader
exports.ReadableStreamDefaultReader = class ReadableStreamDefaultReader {
  constructor(stream) {
    this._stream = stream
    this._stream._stream.once('close', onclose).once('error', onerror)

    const closed = Promise.withResolvers()

    // Avoid unhandled exceptions
    closed.promise.catch(noop)

    this._closed = closed

    function onclose() {
      closed.resolve()
    }

    function onerror(err) {
      closed.reject(err)
    }
  }

  get closed() {
    return this._closed.promise
  }

  read() {
    const stream = this._stream._stream

    return new Promise((resolve, reject) => {
      const err = getStreamError(stream)

      if (err) return reject(err)

      if (stream.destroyed) {
        return resolve({ value: undefined, done: true })
      }

      const value = stream.read()

      if (value !== null) {
        return resolve({ value, done: false })
      }

      stream.once('readable', onreadable).once('close', onclose).once('error', onerror)

      function onreadable() {
        const value = stream.read()

        ondone(null, value === null ? { value: undefined, done: true } : { value, done: false })
      }

      function onclose() {
        ondone(null, { value: undefined, done: true })
      }

      function onerror(err) {
        ondone(err, null)
      }

      function ondone(err, value) {
        stream.off('readable', onreadable).off('close', onclose).off('error', onerror)

        if (err) reject(err)
        else resolve(value)
      }
    })
  }

  releaseLock() {
    this._closed.reject(new TypeError('Reader was released'))
    this._stream._releaseLock()
    this._stream = null
  }

  cancel(reason = new TypeError('Stream was cancelled')) {
    const stream = this._stream._stream

    if (stream.destroyed) return Promise.resolve()

    return new Promise((resolve) =>
      stream.once('close', resolve).once('error', noop).destroy(reason)
    )
  }
}

// https://streams.spec.whatwg.org/#readablestreamdefaultcontroller
exports.ReadableStreamDefaultController = class ReadableStreamDefaultController {
  constructor(stream) {
    this._stream = stream
  }

  get desiredSize() {
    const stream = this._stream._stream

    return stream._readableState.highWaterMark - stream._readableState.buffered
  }

  enqueue(data) {
    this._stream._stream.push(data)
  }

  close() {
    this._stream._stream.push(null)
  }

  error(err) {
    this._stream._stream.destroy(err)
  }
}

// https://streams.spec.whatwg.org/#readablestream
class ReadableStream {
  static get [readableKind]() {
    return 0 // Compatibility version
  }

  static from(iterable) {
    return new ReadableStream(Readable.from(iterable))
  }

  constructor(underlyingSource = {}, queuingStrategy) {
    if (isStreamx(underlyingSource)) {
      this._stream = underlyingSource
    } else {
      if (queuingStrategy === undefined) {
        queuingStrategy = new exports.CountQueuingStrategy()
      }

      const { start, pull, cancel } = underlyingSource
      const { highWaterMark = 1, size = defaultSize } = queuingStrategy

      this._stream = new Readable({ highWaterMark, byteLength: size })

      const controller = new exports.ReadableStreamDefaultController(this)

      try {
        let starting = Promise.resolve()

        if (start) starting = forwardError(start.call(this, controller), controller)

        if (pull) {
          this._stream._read = this._read.bind(this, starting, pull.bind(this, controller))
        }

        if (cancel) {
          this._stream.once('error', cancel.bind(this))
        }
      } catch (err) {
        controller.error(err)
      }
    }

    this._reader = null
  }

  get [readableKind]() {
    return ReadableStream[readableKind]
  }

  get locked() {
    return this._reader !== null
  }

  getReader() {
    if (this.locked) throw new TypeError('Stream is locked')

    this._reader = new exports.ReadableStreamDefaultReader(this)

    return this._reader
  }

  cancel(reason = new TypeError('Stream was cancelled')) {
    const stream = this._stream

    if (stream.destroyed) return Promise.resolve()

    if (this.locked) return Promise.reject(new TypeError('Stream is locked'))

    return new Promise((resolve) =>
      stream.once('close', resolve).once('error', noop).destroy(reason)
    )
  }

  tee() {
    const [a, b] = tee(this._stream)

    return [new ReadableStream(a), new ReadableStream(b)]
  }

  pipeTo(destination) {
    return new Promise((resolve, reject) =>
      this._stream.pipe(destination._stream, (err) => {
        err ? reject(err) : resolve()
      })
    )
  }

  [Symbol.asyncIterator]() {
    return this._stream[Symbol.asyncIterator]()
  }

  _releaseLock() {
    this._reader = null
  }

  async _read(starting, pull, cb) {
    await starting

    let err = null
    try {
      await pull()
    } catch (e) {
      err = e
    }
    cb(err)
  }
}

function defaultSize() {
  return 1
}

exports.ReadableStream = ReadableStream

// https://streams.spec.whatwg.org/#countqueuingstrategy
exports.CountQueuingStrategy = class CountQueuingStrategy {
  constructor(opts = {}) {
    const { highWaterMark = 1 } = opts

    this.highWaterMark = highWaterMark
  }

  size(chunk) {
    return 1
  }
}

// https://streams.spec.whatwg.org/#bytelengthqueuingstrategy
exports.ByteLengthQueuingStrategy = class ByteLengthQueuingStrategy {
  constructor(opts = {}) {
    const { highWaterMark = 16384 } = opts

    this.highWaterMark = highWaterMark
  }

  size(chunk) {
    return chunk.byteLength
  }
}

exports.isReadableStream = function isReadableStream(value) {
  if (value instanceof ReadableStream) return true

  return (
    typeof value === 'object' &&
    value !== null &&
    value[readableKind] === ReadableStream[readableKind]
  )
}

// https://streams.spec.whatwg.org/#readablestream-errored
exports.isReadableStreamErrored = function isReadableStreamErrored(stream) {
  return getStreamError(stream._stream) !== null
}

// https://streams.spec.whatwg.org/#is-readable-stream-disturbed
exports.isReadableStreamDisturbed = function isReadableStreamDisturbed(stream) {
  return isDisturbed(stream._stream)
}

// https://streams.spec.whatwg.org/#writablestreamdefaultwriter
exports.WritableStreamDefaultWriter = class WritableStreamDefaultWriter {
  constructor(stream) {
    this._stream = stream
    this._stream._stream.once('close', onclose).once('error', onerror)

    const closed = Promise.withResolvers()

    // Avoid unhandled exceptions
    closed.promise.catch(noop)

    this._closed = closed

    function onclose() {
      closed.resolve()
    }

    function onerror(err) {
      closed.reject(err)
    }
  }

  get desiredSize() {
    const stream = this._stream._stream

    return stream._writableState.highWaterMark - stream._writableState.buffered
  }

  get closed() {
    return this._closed.promise
  }

  get ready() {
    const stream = this._stream._stream

    if (getStreamError(stream)) return Promise.reject()

    return Writable.drained(stream).then()
  }

  async write(chunk) {
    const stream = this._stream._stream

    let err = getStreamError(stream)
    if (err) return Promise.reject(err)

    stream.write(chunk)

    await Writable.drained(stream)

    err = getStreamError(stream)
    if (err) return Promise.reject(err)
  }

  releaseLock() {
    this._closed.reject(new TypeError('Writer was released'))
    this._stream._releaseLock()
    this._stream = null
  }

  close() {
    const stream = this._stream._stream

    if (stream.destroyed) return Promise.resolve()

    return new Promise((resolve) => stream.once('close', resolve).end())
  }

  abort(reason = new TypeError('Stream was aborted')) {
    const stream = this._stream._stream

    if (stream.destroyed) return Promise.resolve()

    return new Promise((resolve) => stream.once('close', resolve).destroy(reason))
  }
}

// https://streams.spec.whatwg.org/#writablestreamdefaultcontroller
exports.WritableStreamDefaultController = class WritableStreamDefaultController {
  constructor(stream) {
    this._stream = stream
  }

  error(err) {
    this._stream._stream.destroy(err)
  }
}

// https://streams.spec.whatwg.org/#writablestream
class WritableStream {
  static get [writableKind]() {
    return 0 // Compatibility version
  }

  constructor(underlyingSink = {}, queuingStrategy = {}) {
    if (isStreamx(underlyingSink)) {
      this._stream = underlyingSink
    } else {
      if (queuingStrategy === undefined) {
        queuingStrategy = new exports.CountQueuingStrategy()
      }

      const { start, write, close, abort } = underlyingSink
      const { highWaterMark = 1, size = defaultSize } = queuingStrategy

      this._stream = new Writable({ highWaterMark, byteLength: size })

      const controller = new exports.WritableStreamDefaultController(this)

      this._controller = controller

      try {
        let starting = Promise.resolve()

        if (start) starting = forwardError(start.call(this, controller), controller)

        if (write) {
          this._stream._write = this._write.bind(this, starting, write.bind(this))
        }

        if (close) {
          this._stream._destroy = this._destroy.bind(this, close.call(this))
        }

        if (abort) {
          this._stream.once('error', abort.bind(this))
        }
      } catch (err) {
        controller.error(err)
      }
    }

    this._writer = null
  }

  get [writableKind]() {
    return WritableStream[writableKind]
  }

  get locked() {
    return this._writer !== null
  }

  getWriter() {
    if (this.locked) throw new TypeError('Stream is locked')

    this._writer = new exports.WritableStreamDefaultWriter(this)

    return this._writer
  }

  abort(reason = new TypeError('Stream was aborted')) {
    if (this._stream.destroyed) return Promise.resolve()

    if (this.locked) return Promise.reject(new TypeError('Stream is locked'))

    return new Promise((resolve) => this._stream.once('close', resolve).destroy(reason))
  }

  close() {
    if (this._stream.destroyed) return Promise.resolve()

    if (this.locked) return Promise.reject(new TypeError('Stream is locked'))

    return new Promise((resolve) => this._stream.once('close', resolve).end())
  }

  _releaseLock() {
    this._writer = null
  }

  async _write(starting, write, data, cb) {
    await starting

    let err = null
    try {
      await write(data, this._controller)
    } catch (e) {
      err = e
    }
    cb(err)
  }

  async _destroy(closing, cb) {
    let err = null
    try {
      await closing
    } catch (e) {
      err = e
    }
    cb(err)
  }
}

exports.WritableStream = WritableStream

exports.isWritableStream = function isWritableStream(value) {
  if (value instanceof WritableStream) return true

  return (
    typeof value === 'object' &&
    value !== null &&
    value[writableKind] === WritableStream[writableKind]
  )
}

// https://streams.spec.whatwg.org/#transformstreamdefaultcontroller
exports.TransformStreamDefaultController = class TransformStreamDefaultController {
  constructor(stream) {
    this._stream = stream
  }

  get desiredSize() {
    const stream = this._stream._stream

    return stream._readableState.highWaterMark - stream._readableState.buffered
  }

  enqueue(data) {
    this._stream._stream.push(data)
  }

  error(err) {
    this._stream._stream.destroy(err)
  }

  terminate() {
    const stream = this._stream._stream

    stream.push(null)
    stream.destroy(new TypeError('Stream has been terminated'))
  }
}

// https://streams.spec.whatwg.org/#transformstream
class TransformStream {
  static get [transformKind]() {
    return 0 // Compatibility version
  }

  constructor(transformer = {}, writableStrategy = {}, readableStrategy = {}) {
    if (isStreamx(transformer)) {
      this._stream = transformer
    } else {
      const { start, transform, flush } = transformer

      this._stream = new Transform({ ...writableStrategy, ...readableStrategy })

      const controller = new exports.TransformStreamDefaultController(this)

      this._controller = controller

      try {
        let starting = Promise.resolve()

        if (start) starting = forwardError(start.call(this, controller), controller)

        if (transform) {
          this._stream._transform = this._transform.bind(this, starting, transform.bind(this))
        }

        if (flush) {
          this._stream._flush = this._flush.bind(this, flush.call(this, this._controller))
        }
      } catch (err) {
        controller.error(err)
      }
    }

    this._writable = new WritableStream(this._stream)
    this._readable = new ReadableStream(this._stream)
  }

  get [transformKind]() {
    return TransformStream[transformKind]
  }

  get writable() {
    return this._writable
  }

  get readable() {
    return this._readable
  }

  async _transform(starting, transform, data, cb) {
    await starting

    let err = null
    try {
      await transform(data, this._controller)
    } catch (e) {
      err = e
    }
    cb(err)
  }

  async _flush(flush, cb) {
    let err = null
    try {
      await flush
    } catch (e) {
      err = e
    }
    cb(err)
  }
}

exports.TransformStream = TransformStream

exports.isTransformStream = function isTransformStream(value) {
  if (value instanceof TransformStream) return true

  return (
    typeof value === 'object' &&
    value !== null &&
    value[transformKind] === TransformStream[transformKind]
  )
}

async function forwardError(promise, controller) {
  try {
    await promise
  } catch (err) {
    controller.error(err)
  }
}

function noop() {}
module.exports = require.addon()
const EventEmitter = require('bare-events')
const { Duplex } = require('bare-stream')
const dns = require('bare-dns')
const binding = require('./binding')
const constants = require('./lib/constants')
const errors = require('./lib/errors')
const ip = require('./lib/ip')

const defaultReadBufferSize = 65536
const empty = Buffer.alloc(0)

exports.Socket = class TCPSocket extends Duplex {
  constructor(opts = {}) {
    const { readBufferSize = defaultReadBufferSize, allowHalfOpen = true, eagerOpen = true } = opts

    super({ eagerOpen })

    this._state = 0

    this._allowHalfOpen = allowHalfOpen

    this._keepAlive = 0
    this._keepAliveInitialDelay = 0
    this._noDelay = 0

    this._localAddress = null
    this._remoteAddress = null

    this._pendingOpen = null
    this._pendingWrite = null
    this._pendingFinal = null
    this._pendingDestroy = null

    this._timer = null
    this._timeout = 0

    this._buffer = Buffer.alloc(readBufferSize)

    this._addresses = null
    this._errors = null

    this._handle = binding.init(
      this._buffer,
      this,
      noop,
      this._onconnect,
      this._onreset,
      this._onread,
      this._onwrite,
      this._onfinal,
      this._onclose
    )
  }

  get connecting() {
    return (this._state & constants.state.CONNECTING) !== 0
  }

  get pending() {
    return (this._state & constants.state.CONNECTED) === 0
  }

  get timeout() {
    return this._timeout || undefined // For Node.js compatibility
  }

  get readyState() {
    if (this._state & constants.state.CONNECTED) {
      return 'open'
    }

    return 'opening'
  }

  get localAddress() {
    if (this._localAddress) return this._localAddress.address
  }

  get localFamily() {
    if (this._localAddress) return `IPv${this._localAddress.family}`
  }

  get localPort() {
    if (this._localAddress) return this._localAddress.port
  }

  get remoteAddress() {
    if (this._remoteAddress) return this._remoteAddress.address
  }

  get remoteFamily() {
    if (this._remoteAddress) return `IPv${this._remoteAddress.family}`
  }

  get remotePort() {
    if (this._remoteAddress) return this._remoteAddress.port
  }

  connect(port, host = 'localhost', opts = {}, onconnect) {
    if (this._state & constants.state.CONNECTING || this._state & constants.state.CONNECTED) {
      throw errors.SOCKET_ALREADY_CONNECTED('Socket is already connected')
    }

    this._state |= constants.state.CONNECTING

    if (typeof host === 'function') {
      onconnect = host
      host = 'localhost'
    } else if (typeof opts === 'function') {
      onconnect = opts
      opts = {}
    }

    let family = 0

    if (typeof port === 'object' && port !== null) {
      opts = port || {}
      port = opts.port || 0
      host = opts.host || 'localhost'
      family = opts.family || 0
    }

    if (!host) host = 'localhost'

    const {
      lookup = dns.lookup,
      hints,
      keepAlive = false,
      keepAliveInitialDelay = 0,
      noDelay = false,
      timeout
    } = opts

    const type = ip.isIP(host)

    if (type === 0) {
      lookup(host, { all: true, family, hints }, (err, addresses) => {
        if (this._state & constants.state.CLOSING) return

        this._state &= ~constants.state.CONNECTING

        if (err || addresses.length === 0) {
          if (!err) {
            err = new Error(`No address found for host "${host}"`)
            err.code = 'ENOTFOUND'
          }

          this.emit('lookup', err, null, 0, host)

          if (this._pendingOpen) this._continueOpen(err)
          else this.destroy(err)
          return
        }

        for (const { address, family } of addresses) {
          this.emit('lookup', null, address, family, host)
        }

        const [{ address, family }, ...rest] = addresses

        if (rest.length > 0) {
          this._addresses = rest.map(({ address, family }) => [
            port,
            address,
            { ...opts, family },
            onconnect
          ])

          this._errors = []
        }

        this.connect(port, address, { ...opts, family }, onconnect)
      })

      return this
    }

    family = type

    try {
      binding.connect(this._handle, port, host, family)

      if (keepAlive) {
        this._keepAlive = keepAlive
        this._keepAliveInitialDelay = keepAliveInitialDelay
      }
      if (noDelay) this._noDelay = noDelay
      if (timeout) this.setTimeout(timeout)

      if (onconnect) this.once('connect', onconnect)
    } catch (err) {
      queueMicrotask(() => {
        if (this._pendingOpen) this._pendingOpen(err)
        else this.destroy(err)
      })
    }

    return this
  }

  setKeepAlive(enable = false, delay = 0) {
    if (typeof enable === 'number') {
      delay = enable
      enable = false
    }

    delay = Math.floor(delay / 1000)

    if (delay === 0) enable = false

    binding.keepalive(this._handle, enable, delay)

    return this
  }

  setNoDelay(enable = true) {
    binding.nodelay(this._handle, enable)

    return this
  }

  setTimeout(ms, ontimeout) {
    if (ms === 0) {
      clearTimeout(this._timer)

      this._timer = null
    } else {
      if (ontimeout) this.once('timeout', ontimeout)

      this._timer = setTimeout(() => this.emit('timeout'), ms)
      this._timer.unref()
    }

    this._timeout = ms

    return this
  }

  ref() {
    binding.ref(this._handle)

    return this
  }

  unref() {
    binding.unref(this._handle)

    return this
  }

  _open(cb) {
    if (this._state & constants.state.CONNECTED) return cb(null)

    this._pendingOpen = cb
  }

  _read() {
    if ((this._state & constants.state.READING) === 0) {
      this._state |= constants.state.READING

      binding.resume(this._handle)
    }
  }

  _writev(batch, cb) {
    this._pendingWrite = [cb, batch]

    binding.writev(
      this._handle,
      batch.map(({ chunk }) => chunk)
    )
  }

  _final(cb) {
    this._pendingFinal = cb

    binding.end(this._handle)
  }

  _predestroy() {
    if (this._state & constants.state.CLOSING) return
    this._state |= constants.state.CLOSING

    binding.close(this._handle)
  }

  _destroy(err, cb) {
    if (this._state & constants.state.CLOSING) return cb(err)
    this._state |= constants.state.CLOSING

    this._pendingDestroy = cb

    binding.close(this._handle)
  }

  _continueOpen(err) {
    if (this._pendingOpen === null) return
    const cb = this._pendingOpen
    this._pendingOpen = null
    cb(err)
  }

  _continueWrite(err) {
    if (this._pendingWrite === null) return
    const cb = this._pendingWrite[0]
    this._pendingWrite = null
    cb(err)
  }

  _continueFinal(err) {
    if (this._pendingFinal === null) return
    const cb = this._pendingFinal
    this._pendingFinal = null
    cb(err)
  }

  _continueDestroy() {
    if (this._pendingDestroy === null) return
    const cb = this._pendingDestroy
    this._pendingDestroy = null
    cb(null)
  }

  _reset() {
    this._state = 0

    this._localAddress = null
    this._remoteAddress = null

    binding.reset(this._handle)
  }

  _onconnect(err) {
    if (err) {
      if (this._addresses !== null) {
        this._errors.push(err)

        if (this._addresses.length > 0) return this._reset()

        err = this._errors.length === 1 ? this._errors[0] : new AggregateError(this._errors)
      }

      if (this._pendingOpen) this._continueOpen(err)
      else this.destroy(err)
      return
    }

    if (this._keepAlive) this.setKeepAlive(this._keepAlive, this._keepAliveInitialDelay)
    if (this._noDelay) this.setNoDelay()

    this._localAddress = binding.address(this._handle, true)
    this._remoteAddress = binding.address(this._handle, false)

    this._state |= constants.state.CONNECTED
    this._state &= ~constants.state.CONNECTING
    this._continueOpen()

    this.emit('connect')
  }

  _onreset(err) {
    if (err) {
      this._errors.push(err)
      this.destroy(this._errors.length === 1 ? this._errors[0] : new AggregateError(this._errors))
      return
    }

    this.connect(...this._addresses.shift())
  }

  _onread(err, read) {
    if (this._timer) this._timer.refresh()

    if (err) {
      this.destroy(err)
      return
    }

    if (read === 0) {
      this.push(null)
      if (this._allowHalfOpen === false) this.end()
      return
    }

    const copy = Buffer.allocUnsafe(read)
    copy.set(this._buffer.subarray(0, read))

    if (this.push(copy) === false && this.destroying === false) {
      this._state &= ~constants.state.READING

      binding.pause(this._handle)
    }
  }

  _onwrite(err) {
    if (this._timer) this._timer.refresh()

    this._continueWrite(err)
  }

  _onfinal(err) {
    this._continueFinal(err)
  }

  _onclose() {
    clearTimeout(this._timer)

    this._continueOpen()
    this._continueDestroy()
  }
}

exports.Server = class TCPServer extends EventEmitter {
  constructor(opts = {}, onconnection) {
    if (typeof opts === 'function') {
      onconnection = opts
      opts = {}
    }

    super()

    const {
      readBufferSize = defaultReadBufferSize,
      allowHalfOpen = true,
      keepAlive = false,
      keepAliveInitialDelay = 0,
      noDelay = false,
      pauseOnConnect = false
    } = opts

    this._state = 0

    this._readBufferSize = readBufferSize
    this._allowHalfOpen = allowHalfOpen
    this._keepAlive = keepAlive
    this._keepAliveInitialDelay = keepAliveInitialDelay
    this._noDelay = noDelay
    this._pauseOnConnect = pauseOnConnect

    this._address = null
    this._connections = new Set()

    this._error = null
    this._handle = null

    if (onconnection) this.on('connection', onconnection)
  }

  get listening() {
    return (this._state & constants.state.BOUND) !== 0
  }

  get closing() {
    return (this._state & constants.state.CLOSING) !== 0
  }

  get connections() {
    return this._connections
  }

  address() {
    if ((this._state & constants.state.BOUND) === 0) return null

    const { address, family, port } = this._address

    return { address, family: `IPv${family}`, port }
  }

  listen(port = 0, host = 'localhost', backlog = 511, opts = {}, onlistening) {
    if (this._state & constants.state.BINDING || this._state & constants.state.BOUND) {
      throw errors.SERVER_ALREADY_LISTENING('Server is already listening')
    }

    if (this._state & constants.state.CLOSING) {
      throw errors.SERVER_IS_CLOSED('Server is closed')
    }

    this._state |= constants.state.BINDING

    if (typeof port === 'function') {
      onlistening = port
      port = 0
    } else if (typeof host === 'function') {
      onlistening = host
      host = 'localhost'
    } else if (typeof backlog === 'function') {
      onlistening = backlog
      backlog = 511
    } else if (typeof opts === 'function') {
      onlistening = opts
      opts = {}
    }

    let family = 0

    if (typeof port === 'object' && port !== null) {
      opts = port || {}
      port = opts.port || 0
      host = opts.host || 'localhost'
      family = opts.family || 0
      backlog = opts.backlog || 511
    }

    if (!host) host = 'localhost'
    if (!backlog) backlog = 511

    const { lookup = dns.lookup, hints } = opts

    const type = ip.isIP(host)

    if (type === 0) {
      lookup(host, { family, hints }, (err, address, family) => {
        if (this._state & constants.state.CLOSING) return

        this.emit('lookup', err, address, family, host)

        this._state &= ~constants.state.BINDING

        if (err) return this.emit('error', err)

        this.listen(port, address, backlog, { ...opts, family }, onlistening)
      })

      return this
    }

    family = type

    this._handle = binding.init(
      empty,
      this,
      this._onconnection,
      noop,
      noop,
      noop,
      noop,
      noop,
      this._onclose
    )

    if (this._state & constants.state.UNREFED) binding.unref(this._handle)

    try {
      binding.bind(this._handle, port, host, backlog, family)

      this._address = binding.address(this._handle, true)

      this._state |= constants.state.BOUND
      this._state &= ~constants.state.BINDING

      if (onlistening) this.once('listening', onlistening)

      queueMicrotask(() => this.emit('listening'))
    } catch (err) {
      this._error = err

      binding.close(this._handle)
    }

    return this
  }

  close(onclose) {
    if (onclose) this.once('close', onclose)

    if (this._state & constants.state.CLOSING) return this
    this._state |= constants.state.CLOSING

    this._closeMaybe()

    return this
  }

  ref() {
    this._state &= ~constants.state.UNREFED

    if (this._handle !== null) binding.ref(this._handle)

    return this
  }

  unref() {
    this._state |= constants.state.UNREFED

    if (this._handle !== null) binding.unref(this._handle)

    return this
  }

  _closeMaybe() {
    if (this._state & constants.state.CLOSING && this._connections.size === 0) {
      if (this._handle !== null) binding.close(this._handle)
      else queueMicrotask(() => this.emit('close'))
    }
  }

  _onconnection(err) {
    if (err) {
      this.emit('error', err)
      return
    }

    if (this._state & constants.state.CLOSING) return

    const socket = new exports.Socket({
      readBufferSize: this._readBufferSize,
      allowHalfOpen: this._allowHalfOpen,
      eagerOpen: !this._pauseOnConnect
    })

    try {
      binding.accept(this._handle, socket._handle)

      socket._localAddress = binding.address(socket._handle, true)
      socket._remoteAddress = binding.address(socket._handle, false)

      socket._state |= constants.state.CONNECTED

      this._connections.add(socket)

      if (this._keepAlive) socket.setKeepAlive(this._keepAlive, this._keepAliveInitialDelay)
      if (this._noDelay) socket.setNoDelay()

      socket.on('close', () => {
        this._connections.delete(socket)
        this._closeMaybe()
      })

      this.emit('connection', socket)
    } catch (err) {
      socket.destroy()

      this.emit('error', err)
    }
  }

  _onclose() {
    const err = this._error

    this._state &= ~constants.state.BINDING
    this._error = null
    this._handle = null
    this._address = null

    if (err) this.emit('error', err)
    else this.emit('close')
  }
}

exports.constants = constants
exports.errors = errors

exports.isIP = ip.isIP
exports.isIPv4 = ip.isIPv4
exports.isIPv6 = ip.isIPv6

exports.createConnection = function createConnection(port, host, opts, onconnect) {
  if (typeof host === 'function') {
    onconnect = host
    host = 'localhost'
  } else if (typeof opts === 'function') {
    onconnect = opts
    opts = {}
  }

  if (typeof port === 'object' && port !== null) {
    opts = port || {}
    port = opts.port || 0
    host = opts.host || 'localhost'
  }

  return new exports.Socket(opts).connect(port, host, opts, onconnect)
}

// For Node.js compatibility
exports.connect = exports.createConnection

exports.createServer = function createServer(opts, onconnection) {
  return new exports.Server(opts, onconnection)
}

function noop() {}
module.exports = {
  state: {
    CONNECTING: 0x1,
    CONNECTED: 0x2,
    BINDING: 0x4,
    BOUND: 0x8,
    READING: 0x10,
    CLOSING: 0x20,
    UNREFED: 0x40
  }
}
module.exports = class TCPError extends Error {
  constructor(msg, fn = TCPError, code = fn.name) {
    super(`${code}: ${msg}`)
    this.code = code

    if (Error.captureStackTrace) {
      Error.captureStackTrace(this, fn)
    }
  }

  get name() {
    return 'TCPError'
  }

  static SOCKET_ALREADY_CONNECTED(msg) {
    return new TCPError(msg, TCPError.SOCKET_ALREADY_CONNECTED)
  }

  static SERVER_ALREADY_LISTENING(msg) {
    return new TCPError(msg, TCPError.SERVER_ALREADY_LISTENING)
  }

  static SERVER_IS_CLOSED(msg) {
    return new TCPError(msg, TCPError.SERVER_IS_CLOSED)
  }

  static INVALID_HOST(msg = 'Unrecognizable host format') {
    return new TCPError(msg, TCPError.INVALID_HOST)
  }
}
// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.

const v4Seg = '(?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])'
const v4Str = `(${v4Seg}[.]){3}${v4Seg}`
const IPv4Pattern = new RegExp(`^${v4Str}$`)

const v6Seg = '(?:[0-9a-fA-F]{1,4})'
const IPv6Pattern = new RegExp(
  '^(' +
    `(?:${v6Seg}:){7}(?:${v6Seg}|:)|` +
    `(?:${v6Seg}:){6}(?:${v4Str}|:${v6Seg}|:)|` +
    `(?:${v6Seg}:){5}(?::${v4Str}|(:${v6Seg}){1,2}|:)|` +
    `(?:${v6Seg}:){4}(?:(:${v6Seg}){0,1}:${v4Str}|(:${v6Seg}){1,3}|:)|` +
    `(?:${v6Seg}:){3}(?:(:${v6Seg}){0,2}:${v4Str}|(:${v6Seg}){1,4}|:)|` +
    `(?:${v6Seg}:){2}(?:(:${v6Seg}){0,3}:${v4Str}|(:${v6Seg}){1,5}|:)|` +
    `(?:${v6Seg}:){1}(?:(:${v6Seg}){0,4}:${v4Str}|(:${v6Seg}){1,6}|:)|` +
    `(?::((?::${v6Seg}){0,5}:${v4Str}|(?::${v6Seg}){1,7}|:))` +
    ')(%[0-9a-zA-Z-.:]{1,})?$'
)

const isIPv4 = (exports.isIPv4 = function isIPv4(host) {
  return IPv4Pattern.test(host)
})

const isIPv6 = (exports.isIPv6 = function isIPv6(host) {
  return IPv6Pattern.test(host)
})

exports.isIP = function isIP(host) {
  if (isIPv4(host)) return 4
  if (isIPv6(host)) return 6
  return 0
}
{
  "name": "bare-tcp",
  "version": "2.2.12",
  "description": "Native TCP sockets for JavaScript",
  "exports": {
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    },
    "./package": "./package.json",
    "./constants": "./lib/constants.js",
    "./errors": "./lib/errors.js"
  },
  "files": [
    "index.js",
    "index.d.ts",
    "binding.c",
    "binding.js",
    "CMakeLists.txt",
    "lib",
    "prebuilds"
  ],
  "addon": true,
  "scripts": {
    "format": "prettier --write . && lunte --fix",
    "lint": "prettier --check . && lunte",
    "test": "brittle-bare --coverage test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-tcp.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-tcp/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-tcp#readme",
  "engines": {
    "bare": ">=1.16.0"
  },
  "dependencies": {
    "bare-dns": "^2.0.4",
    "bare-events": "^2.5.4",
    "bare-stream": "^2.6.4"
  },
  "devDependencies": {
    "brittle": "^3.5.0",
    "cmake-bare": "^1.1.6",
    "lunte": "^1.6.0",
    "prettier": "^3.4.1",
    "prettier-config-holepunch": "^2.0.0"
  }
}
module.exports = require.addon()
const { Readable, Writable } = require('bare-stream')
const Signal = require('bare-signals')
const binding = require('./binding')
const constants = require('./lib/constants')

const defaultReadBufferSize = 65536
const empty = Buffer.alloc(0)

exports.ReadStream = class TTYReadStream extends Readable {
  constructor(fd, opts = {}) {
    super()

    const { readBufferSize = defaultReadBufferSize, allowHalfOpen = true } = opts

    this._fd = fd
    this._state = 0
    this._allowHalfOpen = allowHalfOpen
    this._buffer = Buffer.alloc(readBufferSize)

    this._pendingDestroy = null

    this._handle = binding.init(fd, this._buffer, this, noop, this._onread, this._onclose)
  }

  get fd() {
    return this._fd
  }

  get isTTY() {
    return true
  }

  setMode(mode) {
    binding.setMode(this._handle, mode)
    return this
  }

  setRawMode(enabled) {
    return this.setMode(enabled ? constants.mode.RAW : constants.mode.NORMAL)
  }

  _read() {
    if ((this._state & constants.state.READING) === 0) {
      this._state |= constants.state.READING

      binding.resume(this._handle)
    }
  }

  _predestroy() {
    if (this._state & constants.state.CLOSING) return
    this._state |= constants.state.CLOSING

    binding.close(this._handle)
  }

  _destroy(err, cb) {
    if (this._state & constants.state.CLOSING) return cb(err)
    this._state |= constants.state.CLOSING

    this._pendingDestroy = cb

    binding.close(this._handle)
  }

  _continueDestroy() {
    if (this._pendingDestroy === null) return
    const cb = this._pendingDestroy
    this._pendingDestroy = null
    cb(null)
  }

  _onread(err, read) {
    if (err) {
      this.destroy(err)
      return
    }

    if (read === 0) {
      this.push(null)
      if (this._allowHalfOpen === false) this.end()
      return
    }

    const copy = Buffer.allocUnsafe(read)
    copy.set(this._buffer.subarray(0, read))

    if (this.push(copy) === false && this.destroying === false) {
      this._state &= ~constants.state.READING

      binding.pause(this._handle)
    }
  }

  _onclose() {
    this._handle = null

    this._continueDestroy()
  }
}

exports.WriteStream = class TTYWriteStream extends Writable {
  constructor(fd, opts = {}) {
    super()

    this._fd = fd
    this._state = 0
    this._size = null

    this._pendingWrite = null
    this._pendingDestroy = null

    this._handle = binding.init(fd, empty, this, this._onwrite, noop, this._onclose)

    this._size = this.getWindowSize()

    if (TTYWriteStream._streams.size === 0) TTYWriteStream._resize.start()

    TTYWriteStream._streams.add(this)
  }

  get fd() {
    return this._fd
  }

  get isTTY() {
    return true
  }

  get columns() {
    return this._size[0]
  }

  get rows() {
    return this._size[1]
  }

  getWindowSize() {
    return binding.getWindowSize(this._handle)
  }

  _writev(batch, cb) {
    this._pendingWrite = [cb, batch]

    binding.writev(
      this._handle,
      batch.map(({ chunk }) => chunk)
    )
  }

  _predestroy() {
    if (this._state & constants.state.CLOSING) return
    this._state |= constants.state.CLOSING

    binding.close(this._handle)

    TTYWriteStream._streams.delete(this)

    if (TTYWriteStream._streams.size === 0) TTYWriteStream._resize.stop()
  }

  _destroy(err, cb) {
    if (this._state & constants.state.CLOSING) return cb(err)
    this._state |= constants.state.CLOSING

    this._pendingDestroy = cb

    binding.close(this._handle)

    TTYWriteStream._streams.delete(this)

    if (TTYWriteStream._streams.size === 0) TTYWriteStream._resize.stop()
  }

  _continueWrite(err) {
    if (this._pendingWrite === null) return
    const cb = this._pendingWrite[0]
    this._pendingWrite = null
    cb(err)
  }

  _continueDestroy() {
    if (this._pendingDestroy === null) return
    const cb = this._pendingDestroy
    this._pendingDestroy = null
    cb(null)
  }

  _onwrite(err) {
    this._continueWrite(err)
  }

  _onclose() {
    this._handle = null

    this._continueDestroy()
  }

  _onresize() {
    this._size = this.getWindowSize()

    this.emit('resize')
  }

  static _streams = new Set()

  static _resize = new Signal('SIGWINCH')
}

exports.constants = constants

exports.isTTY = binding.isTTY

exports.isatty = exports.isTTY // For Node.js compatibility

exports.WriteStream._resize
  .on('signal', () => {
    for (const stream of exports.WriteStream._streams) {
      stream._onresize()
    }
  })
  .unref()

function noop() {}
const binding = require('../binding')

module.exports = exports = {
  mode: {
    NORMAL: binding.MODE_NORMAL,
    RAW: binding.MODE_RAW,
    IO: binding.MODE_IO || 0
  },

  state: {
    READING: 0x1,
    CLOSING: 0x2
  }
}

/** @deprecated Use `mode.NORMAL` */
exports.MODE_NORMAL = exports.mode.NORMAL
/** @deprecated Use `mode.RAW` */
exports.MODE_RAW = exports.mode.RAW
/** @deprecated Use `mode.IO` */
exports.MODE_IO = exports.mode.IO
{
  "name": "bare-tty",
  "version": "5.1.0",
  "description": "Native TTY streams for JavaScript",
  "exports": {
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    },
    "./package": "./package.json",
    "./constants": "./lib/constants.js"
  },
  "files": [
    "index.js",
    "index.d.ts",
    "binding.c",
    "binding.js",
    "CMakeLists.txt",
    "lib",
    "prebuilds"
  ],
  "addon": true,
  "scripts": {
    "test": "prettier . --check && bare test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-tty.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-tty/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-tty#readme",
  "engines": {
    "bare": ">=1.16.0"
  },
  "dependencies": {
    "bare-events": "^2.2.0",
    "bare-signals": "^4.0.0",
    "bare-stream": "^2.0.0"
  },
  "devDependencies": {
    "brittle": "^3.2.1",
    "cmake-bare": "^1.1.6",
    "prettier": "^3.4.1",
    "prettier-config-holepunch": "^2.0.0"
  }
}
module.exports = require.addon()
const binding = require('./binding')

const t = binding.constants

class Type {
  constructor(type) {
    this._type = type
  }

  isUndefined() {
    return this._type === t.UNDEFINED
  }

  isNull() {
    return this._type === t.NULL
  }

  isBoolean() {
    return this._type === t.BOOLEAN
  }

  isNumber() {
    return (this._type & 0xff) === t.NUMBER
  }

  isInt32() {
    return (this._type & (0xff | t.INT32)) === (t.NUMBER | t.INT32)
  }

  isUint32() {
    return (this._type & (0xff | t.UINT32)) === (t.NUMBER | t.UINT32)
  }

  isString() {
    return this._type === t.STRING
  }

  isSymbol() {
    return this._type === t.SYMBOL
  }

  isObject() {
    return (this._type & 0xff) === t.OBJECT
  }

  isArray() {
    return this._type === (t.OBJECT | t.ARRAY)
  }

  isArguments() {
    return this._type === (t.OBJECT | t.ARGUMENTS)
  }

  isDate() {
    return this._type === (t.OBJECT | t.DATE)
  }

  isRegExp() {
    return this._type === (t.OBJECT | t.REGEXP)
  }

  isError() {
    return this._type === (t.OBJECT | t.ERROR)
  }

  isPromise() {
    return this._type === (t.OBJECT | t.PROMISE)
  }

  isProxy() {
    return this._type === (t.OBJECT | t.PROXY)
  }

  isGenerator() {
    return this._type === (t.OBJECT | t.GENERATOR)
  }

  isMap() {
    return this._type === (t.OBJECT | t.MAP)
  }

  isSet() {
    return this._type === (t.OBJECT | t.SET)
  }

  isWeakMap() {
    return this._type === (t.OBJECT | t.WEAK_MAP)
  }

  isWeakSet() {
    return this._type === (t.OBJECT | t.WEAK_SET)
  }

  isWeakRef() {
    return this._type === (t.OBJECT | t.WEAK_REF)
  }

  isArrayBuffer() {
    return this._type === (t.OBJECT | t.ARRAYBUFFER)
  }

  isSharedArrayBuffer() {
    return this._type === (t.OBJECT | t.SHAREDARRAYBUFFER)
  }

  isTypedArray() {
    return (this._type & 0xffff) === (t.OBJECT | t.TYPEDARRAY)
  }

  isInt8Array() {
    return this._type === (t.OBJECT | t.TYPEDARRAY | t.INT8ARRAY)
  }

  isUint8Array() {
    return this._type === (t.OBJECT | t.TYPEDARRAY | t.UINT8ARRAY)
  }

  isUint8ClampedArray() {
    return this._type === (t.OBJECT | t.TYPEDARRAY | t.UINT8CLAMPEDARRAY)
  }

  isInt16Array() {
    return this._type === (t.OBJECT | t.TYPEDARRAY | t.INT16ARRAY)
  }

  isUint16Array() {
    return this._type === (t.OBJECT | t.TYPEDARRAY | t.UINT16ARRAY)
  }

  isInt32Array() {
    return this._type === (t.OBJECT | t.TYPEDARRAY | t.INT32ARRAY)
  }

  isUint32Array() {
    return this._type === (t.OBJECT | t.TYPEDARRAY | t.UINT32ARRAY)
  }

  isFloat16Array() {
    return this._type === (t.OBJECT | t.TYPEDARRAY | t.FLOAT16ARRAY)
  }

  isFloat32Array() {
    return this._type === (t.OBJECT | t.TYPEDARRAY | t.FLOAT32ARRAY)
  }

  isFloat64Array() {
    return this._type === (t.OBJECT | t.TYPEDARRAY | t.FLOAT64ARRAY)
  }

  isBigInt64Array() {
    return this._type === (t.OBJECT | t.TYPEDARRAY | t.BIGINT64ARRAY)
  }

  isBigUint64Array() {
    return this._type === (t.OBJECT | t.TYPEDARRAY | t.BIGUINT64ARRAY)
  }

  isDataView() {
    return this._type === (t.OBJECT | t.DATAVIEW)
  }

  isModuleNamespace() {
    return this._type === (t.OBJECT | t.MODULE_NAMESPACE)
  }

  isFunction() {
    return (this._type & 0xff) === t.FUNCTION
  }

  isAsyncFunction() {
    return (
      (this._type & (0xff | t.ASYNC_FUNCTION)) ===
      (t.FUNCTION | t.ASYNC_FUNCTION)
    )
  }

  isGeneratorFunction() {
    return (
      (this._type & (0xff | t.GENERATOR_FUNCTION)) ===
      (t.FUNCTION | t.GENERATOR_FUNCTION)
    )
  }

  isExternal() {
    return this._type === t.EXTERNAL
  }

  isBigInt() {
    return this._type === t.BIGINT
  }
}

module.exports = exports = function type(value) {
  switch (typeof value) {
    case 'undefined':
      return new Type(t.UNDEFINED)
    case 'boolean':
      return new Type(t.BOOLEAN)
    case 'number':
      return new Type(
        Number.isSafeInteger(value) ? binding.type(value) : t.NUMBER
      )
    case 'string':
      return new Type(t.STRING)
    case 'symbol':
      return new Type(t.SYMBOL)
    case 'object':
      return new Type(value === null ? t.NULL : binding.type(value))
    case 'function':
      return new Type(binding.type(value))
    case 'bigint':
      return new Type(t.BIGINT)
  }
}

exports.createTag = function createTag(...components) {
  const tag = new Uint32Array(4)
  for (let i = 0; i < 4; i++) tag[i] = components[i] || 0
  return tag
}

exports.addTag = function addTag(object, tag) {
  binding.addTag(object, tag)
}

exports.checkTag = function checkTag(object, tag) {
  return binding.checkTag(object, tag)
}
{
  "name": "bare-type",
  "version": "1.1.0",
  "description": "Cross-realm type predicates for Bare",
  "exports": {
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    },
    "./package": "./package.json"
  },
  "files": [
    "index.js",
    "index.d.ts",
    "binding.c",
    "binding.js",
    "CMakeLists.txt",
    "prebuilds"
  ],
  "addon": true,
  "scripts": {
    "test": "prettier . --check && bare test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-type.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-type/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-type#readme",
  "engines": {
    "bare": ">=1.2.0"
  },
  "devDependencies": {
    "brittle": "^3.4.0",
    "cmake-bare": "^1.1.6",
    "prettier": "^3.4.1",
    "prettier-config-standard": "^7.0.0"
  }
}
module.exports = require.addon()
const path = require('bare-path')
const binding = require('./binding')
const errors = require('./lib/errors')
const URLSearchParams = require('./lib/url-search-params')

const kind = Symbol.for('bare.url.kind')

const isWindows = Bare.platform === 'win32'

class URL {
  static get [kind]() {
    return 0 // Compatibility version
  }

  constructor(input, base, opts = {}) {
    if (arguments.length === 0) throw errors.INVALID_URL()

    input = String(input)

    if (base !== undefined) base = String(base)

    this._components = new Uint32Array(8)

    this._parse(input, base, opts.throw !== false)

    if (this._href) this._params = new URLSearchParams(this.search, this)
  }

  get [kind]() {
    return URL[kind]
  }

  // https://url.spec.whatwg.org/#dom-url-href

  get href() {
    return this._href
  }

  set href(value) {
    this._update(value)

    this._params._parse(this.search)
  }

  // https://url.spec.whatwg.org/#dom-url-protocol

  get protocol() {
    return this._slice(0, this._components[0]) + ':'
  }

  set protocol(value) {
    this._update(this._replace(value.replace(/:+$/, ''), 0, this._components[0]))
  }

  // https://url.spec.whatwg.org/#dom-url-username

  get username() {
    return this._slice(this._components[0] + 3 /* :// */, this._components[1])
  }

  set username(value) {
    if (cannotHaveCredentialsOrPort(this)) {
      return
    }

    if (this.username === '') value += '@'

    this._update(this._replace(value, this._components[0] + 3 /* :// */, this._components[1]))
  }

  // https://url.spec.whatwg.org/#dom-url-password

  get password() {
    return this._href.slice(this._components[1] + 1 /* : */, this._components[2] - 1 /* @ */)
  }

  set password(value) {
    if (cannotHaveCredentialsOrPort(this)) {
      return
    }

    let start = this._components[1] + 1 /* : */
    let end = this._components[2] - 1 /* @ */

    if (this.password === '') {
      value = ':' + value
      start--
    }

    if (this.username === '') {
      value += '@'
      end++
    }

    this._update(this._replace(value, start, end))
  }

  // https://url.spec.whatwg.org/#dom-url-host

  get host() {
    return this._slice(this._components[2], this._components[5])
  }

  set host(value) {
    if (hasOpaquePath(this)) {
      return
    }

    this._update(
      this._replace(value, this._components[2], this._components[value.includes(':') ? 5 : 3])
    )
  }

  // https://url.spec.whatwg.org/#dom-url-hostname

  get hostname() {
    return this._slice(this._components[2], this._components[3])
  }

  set hostname(value) {
    if (hasOpaquePath(this)) {
      return
    }

    this._update(this._replace(value, this._components[2], this._components[3]))
  }

  // https://url.spec.whatwg.org/#dom-url-port

  get port() {
    return this._slice(this._components[3] + 1 /* : */, this._components[5])
  }

  set port(value) {
    if (cannotHaveCredentialsOrPort(this)) {
      return
    }

    let start = this._components[3] + 1 /* : */

    if (this.port === '') {
      value = ':' + value
      start--
    }

    this._update(this._replace(value, start, this._components[5]))
  }

  // https://url.spec.whatwg.org/#dom-url-pathname

  get pathname() {
    return this._slice(this._components[5], this._components[6] - 1 /* ? */)
  }

  set pathname(value) {
    if (hasOpaquePath(this)) {
      return
    }

    if (value[0] !== '/' && value[0] !== '\\') {
      value = '/' + value
    }

    this._update(this._replace(value, this._components[5], this._components[6] - 1 /* ? */))
  }

  // https://url.spec.whatwg.org/#dom-url-search

  get search() {
    return this._slice(this._components[6] - 1 /* ? */, this._components[7] - 1 /* # */)
  }

  set search(value) {
    if (value && value[0] !== '?') value = '?' + value

    this._update(
      this._replace(value, this._components[6] - 1 /* ? */, this._components[7] - 1 /* # */)
    )

    this._params._parse(this.search)
  }

  // https://url.spec.whatwg.org/#dom-url-searchparams

  get searchParams() {
    return this._params
  }

  // https://url.spec.whatwg.org/#dom-url-hash

  get hash() {
    return this._slice(this._components[7] - 1 /* # */)
  }

  set hash(value) {
    if (value && value[0] !== '#') value = '#' + value

    this._update(this._replace(value, this._components[7] - 1 /* # */))
  }

  toString() {
    return this._href
  }

  toJSON() {
    return this._href
  }

  [Symbol.for('bare.inspect')]() {
    return {
      __proto__: { constructor: URL },

      href: this.href,
      protocol: this.protocol,
      username: this.username,
      password: this.password,
      host: this.host,
      hostname: this.hostname,
      port: this.port,
      pathname: this.pathname,
      search: this.search,
      searchParams: this.searchParams,
      hash: this.hash
    }
  }

  _slice(start, end = this._href.length) {
    return this._href.slice(start, end)
  }

  _replace(replacement, start, end = this._href.length) {
    return this._slice(0, start) + replacement + this._slice(end)
  }

  _parse(input, base, shouldThrow) {
    try {
      this._href = binding.parse(
        String(input),
        base ? String(base) : null,
        this._components,
        shouldThrow
      )
    } catch (err) {
      if (err instanceof TypeError) throw err

      throw errors.INVALID_URL(`Invalid URL '${input}'`, input)
    }
  }

  _update(input) {
    try {
      this._parse(input, null, true)
    } catch (err) {
      if (err instanceof TypeError) throw err
    }
  }
}

module.exports = exports = URL

// https://url.spec.whatwg.org/#url-opaque-path
function hasOpaquePath(url) {
  return url.pathname[0] !== '/'
}

// https://url.spec.whatwg.org/#cannot-have-a-username-password-port
function cannotHaveCredentialsOrPort(url) {
  return url.hostname === '' || url.protocol === 'file:'
}

exports.URL = URL
exports.URLSearchParams = URLSearchParams

exports.errors = errors

exports.isURL = function isURL(value) {
  if (value instanceof URL) return true

  return typeof value === 'object' && value !== null && value[kind] === URL[kind]
}

exports.isURLSearchParams = URLSearchParams.isURLSearchParams

// https://url.spec.whatwg.org/#dom-url-parse
exports.parse = function parse(input, base) {
  const url = new URL(input, base, { throw: false })
  return url._href ? url : null
}

// https://url.spec.whatwg.org/#dom-url-canparse
exports.canParse = function canParse(input, base) {
  return binding.canParse(String(input), base ? String(base) : null)
}

exports.fileURLToPath = function fileURLToPath(url) {
  if (typeof url === 'string') {
    url = new URL(url)
  }

  if (url.protocol !== 'file:') {
    throw errors.INVALID_URL_SCHEME('The URL must use the file: protocol')
  }

  if (isWindows) {
    if (/%2f|%5c/i.test(url.pathname)) {
      throw errors.INVALID_FILE_URL_PATH(
        'The file: URL path must not include encoded \\ or / characters'
      )
    }
  } else {
    if (url.hostname) {
      throw errors.INVALID_FILE_URL_HOST("The file: URL host must be 'localhost' or empty")
    }

    if (/%2f/i.test(url.pathname)) {
      throw errors.INVALID_FILE_URL_PATH('The file: URL path must not include encoded / characters')
    }
  }

  const pathname = path.normalize(decodeURIComponent(url.pathname))

  if (isWindows) {
    if (url.hostname) return '\\\\' + url.hostname + pathname

    const letter = pathname.charCodeAt(1) | 0x20

    if (letter < 0x61 /* a */ || letter > 0x7a /* z */ || pathname.charCodeAt(2) !== 0x3a /* : */) {
      throw errors.INVALID_FILE_URL_PATH('The file: URL path must be absolute')
    }

    return pathname.slice(1)
  }

  return pathname
}

exports.pathToFileURL = function pathToFileURL(pathname) {
  let resolved = path.resolve(pathname)

  if (pathname[pathname.length - 1] === '/') {
    resolved += '/'
  } else if (isWindows && pathname[pathname.length - 1] === '\\') {
    resolved += '\\'
  }

  resolved = resolved
    .replaceAll('%', '%25') // Must be first
    .replaceAll('#', '%23')
    .replaceAll('?', '%3f')
    .replaceAll('\n', '%0a')
    .replaceAll('\r', '%0d')
    .replaceAll('\t', '%09')

  if (!isWindows) {
    resolved = resolved.replaceAll('\\', '%5c')
  }

  return new URL('file:' + resolved)
}

exports.format = function format(parts) {
  const { protocol, auth, host, hostname, port, pathname, search, query, hash, slashes } = parts

  let result = ''

  if (typeof protocol === 'string') {
    result += protocol

    if (protocol[protocol.length - 1] !== ':') {
      result += ':'
    }

    if (slashes === true || /https?|ftp|gopher|file/.test(protocol)) {
      result += '//'
    }
  }

  if (typeof auth === 'string') {
    if (host || hostname) result += auth + '@'
  }

  if (typeof host === 'string') result += host
  else {
    result += hostname

    if (port) result += ':' + port
  }

  if (typeof pathname === 'string' && pathname !== '') {
    if (pathname[0] !== '/') result += '/'
    result += pathname
  }

  if (typeof search === 'string') {
    if (search[0] !== '?') result += '?'
    result += search
  } else if (typeof query === 'object' && query !== null) {
    result += '?' + new URLSearchParams(query)
  }

  if (typeof hash === 'string') {
    if (hash[0] !== '#') result += '#'
    result += hash
  }

  return result
}
module.exports = class URLError extends Error {
  constructor(msg, fn = URLError, code = fn.name) {
    super(`${code}: ${msg}`)

    this.code = code

    if (Error.captureStackTrace) Error.captureStackTrace(this, fn)
  }

  get name() {
    return 'URLError'
  }

  static INVALID_URL(msg, input) {
    const err = new URLError(msg, URLError.INVALID_URL)

    err.input = input

    return err
  }

  static INVALID_URL_SCHEME(msg = 'Invalid URL') {
    return new URLError(msg, URLError.INVALID_URL_SCHEME)
  }

  static INVALID_FILE_URL_HOST(msg = 'Invalid file: URL host') {
    return new URLError(msg, URLError.INVALID_FILE_URL_HOST)
  }

  static INVALID_FILE_URL_PATH(msg = 'Invalid file: URL path') {
    return new URLError(msg, URLError.INVALID_FILE_URL_PATH)
  }
}
const kind = Symbol.for('bare.url.search-params.kind')

class URLSearchParams {
  static _urls = new WeakMap()

  static get [kind]() {
    return 0 // Compatibility version
  }

  // https://url.spec.whatwg.org/#dom-urlsearchparams-urlsearchparams
  constructor(init, url = null) {
    this._params = new Map()

    if (url) URLSearchParams._urls.set(this, url)

    if (typeof init === 'string') {
      this._parse(init)
    } else if (init) {
      for (const [name, value] of typeof init[Symbol.iterator] === 'function'
        ? init
        : Object.entries(init)) {
        this.append(name, value)
      }
    }
  }

  get [kind]() {
    return URLSearchParams[kind]
  }

  // https://url.spec.whatwg.org/#dom-urlsearchparams-size
  get size() {
    return this._params.length
  }

  // https://url.spec.whatwg.org/#dom-urlsearchparams-append
  append(name, value = null) {
    if (value === null) return

    let list = this._params.get(name)

    if (list === undefined) {
      list = []
      this._params.set(name, list)
    }

    list.push(value)

    this._update()
  }

  // https://url.spec.whatwg.org/#dom-urlsearchparams-delete
  delete(name, value = null) {
    if (value === null) this._params.delete(name)
    else {
      let list = this._params.get(name)

      if (list === undefined) return

      list = list.filter((found) => found !== value)

      if (list.length === 0) this._params.delete(name)
      else this._params.set(name, list)
    }

    this._update()
  }

  // https://url.spec.whatwg.org/#dom-urlsearchparams-get
  get(name) {
    const list = this._params.get(name)

    if (list === undefined) return null

    return list[0]
  }

  // https://url.spec.whatwg.org/#dom-urlsearchparams-getall
  getAll(name) {
    const list = this._params.get(name)

    if (list === undefined) return []

    return Array.from(list)
  }

  // https://url.spec.whatwg.org/#dom-urlsearchparams-has
  has(name, value = null) {
    const list = this._params.get(name)

    if (list === undefined) return false

    if (value === null) return true

    return list.includes(value)
  }

  // https://url.spec.whatwg.org/#dom-urlsearchparams-set
  set(name, value = null) {
    if (value === null) this._params.delete(name)
    else this._params.set(name, [value])

    this._update()
  }

  toString() {
    return this._serialize()
  }

  toJSON() {
    return [...this]
  }

  *[Symbol.iterator]() {
    for (const [name, values] of this._params) {
      for (const value of values) yield [name, value]
    }
  }

  [Symbol.for('bare.inspect')]() {
    const object = {
      __proto__: { constructor: URLSearchParams }
    }

    for (const [name, values] of this._params) {
      if (values.length === 1) object[name] = values[0]
      else object[name] = values
    }

    return object
  }

  // https://url.spec.whatwg.org/#concept-urlsearchparams-update
  _update() {
    const url = URLSearchParams._urls.get(this)

    if (url === undefined) return

    url.search = this._serialize()
  }

  // https://url.spec.whatwg.org/#concept-urlencoded-parser
  _parse(input) {
    if (input[0] === '?') input = input.substring(1)

    this._params = new Map()

    for (let sequence of input.split('&')) {
      if (sequence.length === 0) continue

      sequence = sequence.replace(/\+/g, ' ')

      let i = sequence.indexOf('=')
      if (i === -1) i = sequence.length

      const name = decodeURIComponent(sequence.substring(0, i))
      const value = decodeURIComponent(sequence.substring(i + 1, sequence.length))

      let list = this._params.get(name)

      if (list === undefined) {
        list = []
        this._params.set(name, list)
      }

      list.push(value)
    }
  }

  // https://url.spec.whatwg.org/#concept-urlencoded-serializer
  _serialize() {
    let output = ''

    for (let [name, values] of this._params) {
      name = encodeURIComponent(name)

      for (const value of values) {
        if (output) output += '&'

        output += name + '=' + encodeURIComponent(value)
      }
    }

    return output
  }
}

module.exports = exports = URLSearchParams

exports.isURLSearchParams = function isURLSearchParams(value) {
  if (value instanceof URLSearchParams) return true

  return typeof value === 'object' && value !== null && value[kind] === URLSearchParams[kind]
}
{
  "name": "bare-url",
  "version": "2.4.2",
  "description": "WHATWG URL implementation for JavaScript",
  "exports": {
    "./package": "./package.json",
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    },
    "./global": {
      "types": "./global.d.ts",
      "default": "./global.js"
    }
  },
  "files": [
    "index.js",
    "index.d.ts",
    "global.js",
    "global.d.ts",
    "binding.c",
    "binding.js",
    "CMakeLists.txt",
    "lib",
    "prebuilds"
  ],
  "addon": true,
  "scripts": {
    "format": "prettier --write . && lunte --fix",
    "lint": "prettier --check . && lunte",
    "test": "brittle-bare --coverage test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bare-url.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-url/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-url",
  "dependencies": {
    "bare-path": "^3.0.0"
  },
  "devDependencies": {
    "brittle": "^3.3.2",
    "cmake-bare": "^1.1.6",
    "cmake-fetch": "^1.0.0",
    "lunte": "^1.6.0",
    "prettier": "^3.3.3",
    "prettier-config-holepunch": "^2.0.0"
  }
}
const b4a = require('b4a')

function byteLength (size) {
  return Math.ceil(size / 8)
}

function get (buffer, bit) {
  const n = buffer.BYTES_PER_ELEMENT * 8

  const offset = bit & (n - 1)
  const i = (bit - offset) / n

  return (buffer[i] & (1 << offset)) !== 0
}

function set (buffer, bit, value = true) {
  const n = buffer.BYTES_PER_ELEMENT * 8

  const offset = bit & (n - 1)
  const i = (bit - offset) / n
  const mask = 1 << offset

  if (value) {
    if ((buffer[i] & mask) !== 0) return false
  } else {
    if ((buffer[i] & mask) === 0) return false
  }

  buffer[i] ^= mask
  return true
}

function setRange (buffer, start, end, value = true) {
  const n = buffer.BYTES_PER_ELEMENT * 8

  let remaining = end - start
  let offset = start & (n - 1)
  let i = (start - offset) / n

  let changed = false

  while (remaining > 0) {
    const mask = (2 ** Math.min(remaining, n - offset) - 1) << offset

    if (value) {
      if ((buffer[i] & mask) !== mask) {
        buffer[i] |= mask
        changed = true
      }
    } else {
      if ((buffer[i] & mask) !== 0) {
        buffer[i] &= ~mask
        changed = true
      }
    }

    remaining -= n - offset
    offset = 0
    i++
  }

  return changed
}

function fill (buffer, value, start = 0, end = buffer.byteLength * 8) {
  const n = buffer.BYTES_PER_ELEMENT * 8
  let i, j

  {
    const offset = start & (n - 1)
    i = (start - offset) / n

    if (offset !== 0) {
      const mask = (2 ** Math.min(n - offset, end - start) - 1) << offset

      if (value) buffer[i] |= mask
      else buffer[i] &= ~mask

      i++
    }
  }

  {
    const offset = end & (n - 1)
    j = (end - offset) / n

    if (offset !== 0 && j >= i) {
      const mask = (2 ** offset) - 1

      if (value) buffer[j] |= mask
      else buffer[j] &= ~mask
    }
  }

  return buffer.fill(value ? (2 ** n) - 1 : 0, i, j)
}

function toggle (buffer, bit) {
  const n = buffer.BYTES_PER_ELEMENT * 8

  const offset = bit & (n - 1)
  const i = (bit - offset) / n
  const mask = 1 << offset

  buffer[i] ^= mask
  return (buffer[i] & mask) !== 0
}

function remove (buffer, bit) {
  return set(buffer, bit, false)
}

function removeRange (buffer, start, end) {
  return setRange(buffer, start, end, false)
}

function indexOf (buffer, value, position = 0) {
  for (let i = position, n = buffer.byteLength * 8; i < n; i++) {
    if (get(buffer, i) === value) return i
  }

  return -1
}

function lastIndexOf (buffer, value, position = buffer.byteLength * 8 - 1) {
  for (let i = position; i >= 0; i--) {
    if (get(buffer, i) === value) return i
  }

  return -1
}

function of (...bits) {
  return from(bits)
}

function from (bits) {
  const buffer = b4a.alloc(byteLength(bits.length))
  for (let i = 0; i < bits.length; i++) set(buffer, i, bits[i])
  return buffer
}

function * iterator (buffer) {
  for (let i = 0, n = buffer.byteLength * 8; i < n; i++) yield get(buffer, i)
}

module.exports = {
  byteLength,
  get,
  set,
  setRange,
  fill,
  toggle,
  remove,
  removeRange,
  indexOf,
  lastIndexOf,
  of,
  from,
  iterator
}
{
  "name": "bits-to-bytes",
  "version": "1.3.0",
  "description": "Functions for doing bit manipulation of typed arrays",
  "main": "index.js",
  "files": [
    "index.js"
  ],
  "scripts": {
    "test": "standard && brittle test.mjs"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/bits-to-bytes.git"
  },
  "author": "Kasper Isager Dalsgarð <kasper@funktionel.co>",
  "license": "ISC",
  "bugs": {
    "url": "https://github.com/holepunchto/bits-to-bytes/issues"
  },
  "homepage": "https://github.com/holepunchto/bits-to-bytes#readme",
  "dependencies": {
    "b4a": "^1.5.0"
  },
  "devDependencies": {
    "brittle": "^2.3.1",
    "standard": "^17.0.0"
  }
}
const EventEmitter = require('events')
const Protomux = require('protomux')
const { Readable } = require('streamx')
const sodium = require('sodium-universal')
const b4a = require('b4a')
const c = require('compact-encoding')
const bitfield = require('compact-encoding-bitfield')
const bits = require('bits-to-bytes')
const errors = require('./lib/errors')

exports.Server = class BlindRelayServer extends EventEmitter {
  constructor(opts = {}) {
    super()

    const { createStream } = opts

    this._createStream = createStream
    this._pairing = new Map()
    this._sessions = new Set()
    this.stats = createStats(this)
  }

  get sessions() {
    return this._sessions[Symbol.iterator]()
  }

  accept(stream, opts) {
    const session = new BlindRelaySession(this, stream, opts)

    this._sessions.add(session)
    this.stats.sessions.accepted++

    return session
  }

  async close() {
    const ending = []

    for (const session of this._sessions) {
      ending.push(session.end())
    }

    await Promise.all(ending)

    this._pairing.clear()
  }

  _activePairings() {
    const active = new Set()

    for (const session of this._sessions) {
      for (const token of session._links.keys()) {
        active.add(token)
      }
    }

    return active.size
  }

  _activeStreams() {
    let active = 0

    for (const session of this._sessions) {
      active += session._links.size
    }

    return active
  }
}

class BlindRelaySession extends EventEmitter {
  constructor(server, stream, opts = {}) {
    super()

    const { id, handshake, handshakeEncoding } = opts

    this._server = server
    this._mux = Protomux.from(stream)

    this._channel = this._mux.createChannel({
      protocol: 'blind-relay',
      id,
      handshake: handshake ? handshakeEncoding || c.raw : null,
      onopen: this._onopen.bind(this),
      onclose: this._onclose.bind(this),
      ondestroy: this._ondestroy.bind(this)
    })

    this._pair = this._channel.addMessage({
      encoding: m.pair,
      onmessage: this._onpair.bind(this)
    })

    this._unpair = this._channel.addMessage({
      encoding: m.unpair,
      onmessage: this._onunpair.bind(this)
    })

    this._ending = null
    this._destroyed = false
    this._error = null
    this._pairing = new Set()
    this._links = new Map()

    this._onerror = (err) => {
      this._server.stats.streams.errors++
      this.emit('error', err)
    }

    this._channel.open(handshake)
  }

  get closed() {
    return this._channel.closed
  }

  get mux() {
    return this._mux
  }

  get stream() {
    return this._mux.stream
  }

  _onopen() {
    this._server.stats.sessions.opened++
    this.emit('open')
  }

  _onclose() {
    this._ending = Promise.resolve()

    const err = this._error || errors.CHANNEL_CLOSED()
    this._server.stats.pairings.cancelled += this._pairing.size

    for (const token of this._pairing) {
      this._server._pairing.delete(token.toString('hex'))
    }

    for (const link of this._links.values()) {
      link.destroy(err)
    }

    this._pairing.clear()
    this._links.clear()

    this._server._sessions.delete(this)
    this._server.stats.sessions.closed++

    this.emit('close')
  }

  _ondestroy() {
    this._destroyed = true
    this.emit('destroy')
  }

  _onpair({ isInitiator, token, id: remoteId }) {
    const keyString = token.toString('hex')

    let pair = this._server._pairing.get(keyString)

    if (pair === undefined) {
      pair = new BlindRelayPair(token)
      this._server._pairing.set(keyString, pair)
    } else if (pair.links[+isInitiator]) return

    this._pairing.add(keyString)
    this._server.stats.pairings.requested++

    pair.links[+isInitiator] = new BlindRelayLink(this, isInitiator, remoteId)

    if (!pair.paired) return

    this._server._pairing.delete(keyString)
    this._server.stats.pairings.matched++

    // 1st pass: Create the raw streams needed for each end of the link.
    for (const link of pair.links) {
      link.createStream()
    }

    // 2nd pass: Connect the raw streams and set up handlers.
    for (const link of pair.links) {
      link.remote = pair.remote(link.isInitiator)
      link.activate(keyString)
    }

    // 3rd pass: Let either end of the link know the streams were set up.
    for (const { isInitiator, session, remoteId, stream } of pair.links) {
      session._pair.send({
        isInitiator,
        token,
        id: stream.id,
        seq: 0
      })

      session._endMaybe()

      session.emit('pair', isInitiator, token, stream, remoteId)
    }
  }

  _onunpair({ token }) {
    const keyString = token.toString('hex')

    const pair = this._server._pairing.get(keyString)

    if (pair) {
      for (const link of pair.links) {
        if (link) link.session._pairing.delete(keyString)
      }

      this._server.stats.pairings.cancelled++
      return this._server._pairing.delete(keyString)
    }

    const link = this._links.get(keyString)

    if (link) {
      link.destroyPair(errors.PAIRING_CANCELLED())
      this._server.stats.pairings.cancelled++
    }
  }

  cork() {
    this._channel.cork()
  }

  uncork() {
    this._channel.uncork()
  }

  async end() {
    if (this._ending) return this._ending

    this._ending = EventEmitter.once(this, 'close')
    this._endMaybe()

    return this._ending
  }

  _endMaybe() {
    if (this._ending && this._pairing.size === 0) {
      this._channel.close()
    }
  }

  destroy(err) {
    if (this._destroyed) return
    this._destroyed = true

    this._error = err || errors.CHANNEL_DESTROYED()
    this._channel.close()
  }
}

class BlindRelayPair {
  constructor(token) {
    this.token = token
    this.links = [null, null]
  }

  get paired() {
    return this.links[0] !== null && this.links[1] !== null
  }

  remote(isInitiator) {
    return this.links[isInitiator ? 0 : 1]
  }
}

class BlindRelayLink {
  constructor(session, isInitiator, remoteId) {
    this.session = session
    this.isInitiator = isInitiator
    this.remoteId = remoteId
    this.stream = null
    this.remote = null
    this.keyString = null
    this._onclose = null
  }

  createStream() {
    if (this.stream) return

    this.stream = this.session._server._createStream({
      firewall: this._onfirewall.bind(this)
    })
  }

  activate(keyString) {
    const stream = this.stream

    this.keyString = keyString
    this._onclose = () => {
      if (this.stream !== stream) return

      this.stream = null
      unlink(this)
      this.session._links.delete(keyString)
      this.session._server.stats.streams.closed++
    }

    stream.on('error', this.session._onerror).on('close', this._onclose).relayTo(this.remote.stream)

    this.session._pairing.delete(keyString)
    this.session._links.set(keyString, this)
    this.session._server.stats.streams.opened++
  }

  destroy(err) {
    const stream = this.stream

    if (!stream) return

    this.stream = null

    stream.off('close', this._onclose)
    this._onclose = null

    stream.off('error', this.session._onerror)
    unlink(this)
    this.session._links.delete(this.keyString)
    this.session._server.stats.streams.closed++
    stream.on('error', noop).destroy(err)
  }

  destroyPair(err) {
    const remote = this.remote

    this.destroy(err)
    if (remote) remote.destroy(err)
  }

  _onfirewall(socket, port, host) {
    this.stream.connect(socket, this.remoteId, port, host)

    return false
  }
}

exports.Client = class BlindRelayClient extends EventEmitter {
  static _clients = new WeakMap()

  static from(stream, opts) {
    let client = this._clients.get(stream)
    if (client) return client
    client = new this(stream, opts)
    this._clients.set(stream, client)
    return client
  }

  constructor(stream, opts = {}) {
    super()

    const { id, handshake, handshakeEncoding } = opts

    this._mux = Protomux.from(stream)

    this._channel = this._mux.createChannel({
      protocol: 'blind-relay',
      id,
      handshake: handshake ? handshakeEncoding || c.raw : null,
      onopen: this._onopen.bind(this),
      onclose: this._onclose.bind(this),
      ondestroy: this._ondestroy.bind(this)
    })

    this._pair = this._channel.addMessage({
      encoding: m.pair,
      onmessage: this._onpair.bind(this)
    })

    this._unpair = this._channel.addMessage({
      encoding: m.unpair
    })

    this._ending = false
    this._destroyed = false
    this._error = null
    this._requests = new Map()

    this._channel.open(handshake)
  }

  get closed() {
    return this._channel.closed
  }

  get mux() {
    return this._mux
  }

  get stream() {
    return this._mux.stream
  }

  get requests() {
    return this._requests.values()
  }

  _onopen() {
    this.emit('open')
  }

  _onclose() {
    this._ending = Promise.resolve()

    const err = this._error || errors.CHANNEL_CLOSED()

    for (const request of this._requests.values()) {
      request.destroy(err)
    }

    this._requests.clear()

    this.constructor._clients.delete(this.stream)

    this.emit('close')
  }

  _ondestroy() {
    this._destroyed = true
    this.emit('destroy')
  }

  _onpair({ isInitiator, token, id: remoteId }) {
    const request = this._requests.get(token.toString('hex'))

    if (request === undefined || request.isInitiator !== isInitiator) return

    request.push(remoteId)
    request.push(null)

    this.emit('pair', request.isInitiator, request.token, request.stream, remoteId)
  }

  pair(isInitiator, token, stream) {
    if (this._destroyed) throw errors.CHANNEL_DESTROYED()

    const keyString = token.toString('hex')

    if (this._requests.has(keyString)) throw errors.ALREADY_PAIRING()

    const request = new BlindRelayRequest(this, isInitiator, token, stream)

    this._requests.set(keyString, request)

    return request
  }

  unpair(token) {
    if (this._destroyed) throw errors.CHANNEL_DESTROYED()

    const request = this._requests.get(token.toString('hex'))

    if (request) request.destroy(errors.PAIRING_CANCELLED())

    this._unpair.send({ token })
  }

  cork() {
    this._channel.cork()
  }

  uncork() {
    this._channel.uncork()
  }

  async end() {
    if (this._ending) return this._ending

    this._ending = EventEmitter.once(this, 'close')
    this._endMaybe()

    return this._ending
  }

  _endMaybe() {
    if (this._ending && this._requests.size === 0) {
      this._channel.close()
    }
  }

  destroy(err) {
    if (this._destroyed) return
    this._destroyed = true

    this._error = err || errors.CHANNEL_DESTROYED()
    this._channel.close()
  }
}

class BlindRelayRequest extends Readable {
  constructor(client, isInitiator, token, stream) {
    super()

    this.client = client
    this.isInitiator = isInitiator
    this.token = token
    this.stream = stream
  }

  _open(cb) {
    if (this.client._destroyed) return cb(errors.CHANNEL_DESTROYED())

    this.client._pair.send({
      isInitiator: this.isInitiator,
      token: this.token,
      id: this.stream.id,
      seq: 0
    })

    cb(null)
  }

  _destroy(cb) {
    this.client._requests.delete(this.token.toString('hex'))

    cb(null)

    this.client._endMaybe()
  }
}

exports.token = function token(buf = b4a.allocUnsafe(32)) {
  sodium.randombytes_buf(buf)
  return buf
}

function noop() {}

function unlink(link) {
  const remote = link.remote

  link.remote = null

  if (remote && remote.remote === link) {
    remote.remote = null
  }
}

function createStats(server) {
  const stats = {
    sessions: {
      accepted: 0,
      opened: 0,
      closed: 0
    },
    pairings: {
      requested: 0,
      matched: 0,
      cancelled: 0
    },
    streams: {
      opened: 0,
      closed: 0,
      errors: 0
    }
  }

  Object.defineProperties(stats.sessions, {
    active: {
      enumerable: true,
      get() {
        return server._sessions.size
      }
    }
  })

  Object.defineProperties(stats.pairings, {
    pending: {
      enumerable: true,
      get() {
        return server._pairing.size
      }
    },
    active: {
      enumerable: true,
      get() {
        return server._activePairings()
      }
    }
  })

  Object.defineProperties(stats.streams, {
    active: {
      enumerable: true,
      get() {
        return server._activeStreams()
      }
    }
  })

  return stats
}

const m = (exports.messages = {})

const flags = bitfield(7)

m.pair = {
  preencode(state, m) {
    flags.preencode(state)
    c.fixed32.preencode(state, m.token)
    c.uint.preencode(state, m.id)
    c.uint.preencode(state, m.seq)
  },
  encode(state, m) {
    flags.encode(state, bits.of(m.isInitiator))
    c.fixed32.encode(state, m.token)
    c.uint.encode(state, m.id)
    c.uint.encode(state, m.seq)
  },
  decode(state) {
    const [isInitiator] = bits.iterator(flags.decode(state))

    return {
      isInitiator,
      token: c.fixed32.decode(state),
      id: c.uint.decode(state),
      seq: c.uint.decode(state)
    }
  }
}

m.unpair = {
  preencode(state, m) {
    flags.preencode(state)
    c.fixed32.preencode(state, m.token)
  },
  encode(state, m) {
    flags.encode(state, bits.of())
    c.fixed32.encode(state, m.token)
  },
  decode(state) {
    flags.decode(state)

    return {
      token: c.fixed32.decode(state)
    }
  }
}
module.exports = class BlindRelayError extends Error {
  constructor(msg, code, fn = BlindRelayError) {
    super(`${code}: ${msg}`)
    this.code = code

    if (Error.captureStackTrace) {
      Error.captureStackTrace(this, fn)
    }
  }

  get name() {
    return 'BlindRelayError'
  }

  static DUPLICATE_CHANNEL(msg = 'Duplicate channel') {
    return new BlindRelayError(msg, 'DUPLICATE_CHANNEL', BlindRelayError.DUPLICATE_CHANNEL)
  }

  static CHANNEL_CLOSED(msg = 'Channel closed') {
    return new BlindRelayError(msg, 'CHANNEL_CLOSED', BlindRelayError.CHANNEL_CLOSED)
  }

  static CHANNEL_DESTROYED(msg = 'Channel destroyed') {
    return new BlindRelayError(msg, 'CHANNEL_DESTROYED', BlindRelayError.CHANNEL_DESTROYED)
  }

  static ALREADY_PAIRING(msg = 'Already pairing') {
    return new BlindRelayError(msg, 'ALREADY_PAIRING', BlindRelayError.ALREADY_PAIRING)
  }

  static PAIRING_CANCELLED(msg = 'Pairing cancelled') {
    return new BlindRelayError(msg, 'PAIRING_CANCELLED', BlindRelayError.PAIRING_CANCELLED)
  }
}
const LE = (exports.LE =
  new Uint8Array(new Uint16Array([0xff]).buffer)[0] === 0xff)

exports.BE = !LE
const b4a = require('b4a')

const { BE } = require('./endian')

exports.state = function (start = 0, end = 0, buffer = null) {
  return { start, end, buffer }
}

const raw = (exports.raw = require('./raw'))

const uint = (exports.uint = {
  preencode(state, n) {
    state.end += n <= 0xfc ? 1 : n <= 0xffff ? 3 : n <= 0xffffffff ? 5 : 9
  },
  encode(state, n) {
    if (n <= 0xfc) uint8.encode(state, n)
    else if (n <= 0xffff) {
      state.buffer[state.start++] = 0xfd
      uint16.encode(state, n)
    } else if (n <= 0xffffffff) {
      state.buffer[state.start++] = 0xfe
      uint32.encode(state, n)
    } else {
      state.buffer[state.start++] = 0xff
      uint64.encode(state, n)
    }
  },
  decode(state) {
    const a = uint8.decode(state)
    if (a <= 0xfc) return a
    if (a === 0xfd) return uint16.decode(state)
    if (a === 0xfe) return uint32.decode(state)
    return uint64.decode(state)
  }
})

const uint8 = (exports.uint8 = {
  preencode(state, n) {
    state.end += 1
  },
  encode(state, n) {
    validateUint(n)
    state.buffer[state.start++] = n
  },
  decode(state) {
    if (state.start >= state.end) throw new Error('Out of bounds')
    return state.buffer[state.start++]
  }
})

const uint16 = (exports.uint16 = {
  preencode(state, n) {
    state.end += 2
  },
  encode(state, n) {
    validateUint(n)
    state.buffer[state.start++] = n
    state.buffer[state.start++] = n >>> 8
  },
  decode(state) {
    if (state.end - state.start < 2) throw new Error('Out of bounds')
    return state.buffer[state.start++] + state.buffer[state.start++] * 0x100
  }
})

const uint24 = (exports.uint24 = {
  preencode(state, n) {
    state.end += 3
  },
  encode(state, n) {
    validateUint(n)
    state.buffer[state.start++] = n
    state.buffer[state.start++] = n >>> 8
    state.buffer[state.start++] = n >>> 16
  },
  decode(state) {
    if (state.end - state.start < 3) throw new Error('Out of bounds')
    return (
      state.buffer[state.start++] +
      state.buffer[state.start++] * 0x100 +
      state.buffer[state.start++] * 0x10000
    )
  }
})

const uint32 = (exports.uint32 = {
  preencode(state, n) {
    state.end += 4
  },
  encode(state, n) {
    validateUint(n)
    state.buffer[state.start++] = n
    state.buffer[state.start++] = n >>> 8
    state.buffer[state.start++] = n >>> 16
    state.buffer[state.start++] = n >>> 24
  },
  decode(state) {
    if (state.end - state.start < 4) throw new Error('Out of bounds')
    return (
      state.buffer[state.start++] +
      state.buffer[state.start++] * 0x100 +
      state.buffer[state.start++] * 0x10000 +
      state.buffer[state.start++] * 0x1000000
    )
  }
})

const uint40 = (exports.uint40 = {
  preencode(state, n) {
    state.end += 5
  },
  encode(state, n) {
    validateUint(n)
    const r = Math.floor(n / 0x100)
    uint8.encode(state, n)
    uint32.encode(state, r)
  },
  decode(state) {
    if (state.end - state.start < 5) throw new Error('Out of bounds')
    return uint8.decode(state) + 0x100 * uint32.decode(state)
  }
})

const uint48 = (exports.uint48 = {
  preencode(state, n) {
    state.end += 6
  },
  encode(state, n) {
    validateUint(n)
    const r = Math.floor(n / 0x10000)
    uint16.encode(state, n)
    uint32.encode(state, r)
  },
  decode(state) {
    if (state.end - state.start < 6) throw new Error('Out of bounds')
    return uint16.decode(state) + 0x10000 * uint32.decode(state)
  }
})

const uint56 = (exports.uint56 = {
  preencode(state, n) {
    state.end += 7
  },
  encode(state, n) {
    validateUint(n)
    const r = Math.floor(n / 0x1000000)
    uint24.encode(state, n)
    uint32.encode(state, r)
  },
  decode(state) {
    if (state.end - state.start < 7) throw new Error('Out of bounds')
    return uint24.decode(state) + 0x1000000 * uint32.decode(state)
  }
})

const uint64 = (exports.uint64 = {
  preencode(state, n) {
    state.end += 8
  },
  encode(state, n) {
    validateUint(n)
    const r = Math.floor(n / 0x100000000)
    uint32.encode(state, n)
    uint32.encode(state, r)
  },
  decode(state) {
    if (state.end - state.start < 8) throw new Error('Out of bounds')
    return uint32.decode(state) + 0x100000000 * uint32.decode(state)
  }
})

const int = (exports.int = zigZagInt(uint))
exports.int8 = zigZagInt(uint8)
exports.int16 = zigZagInt(uint16)
exports.int24 = zigZagInt(uint24)
exports.int32 = zigZagInt(uint32)
exports.int40 = zigZagInt(uint40)
exports.int48 = zigZagInt(uint48)
exports.int56 = zigZagInt(uint56)
exports.int64 = zigZagInt(uint64)

const biguint64 = (exports.biguint64 = {
  preencode(state, n) {
    state.end += 8
  },
  encode(state, n) {
    const view = new DataView(
      state.buffer.buffer,
      state.start + state.buffer.byteOffset,
      8
    )
    view.setBigUint64(0, n, true) // little endian
    state.start += 8
  },
  decode(state) {
    if (state.end - state.start < 8) throw new Error('Out of bounds')
    const view = new DataView(
      state.buffer.buffer,
      state.start + state.buffer.byteOffset,
      8
    )
    const n = view.getBigUint64(0, true) // little endian
    state.start += 8
    return n
  }
})

exports.bigint64 = zigZagBigInt(biguint64)

const biguint = (exports.biguint = {
  preencode(state, n) {
    let len = 0
    for (let m = n; m; m = m >> 64n) len++
    uint.preencode(state, len)
    state.end += 8 * len
  },
  encode(state, n) {
    let len = 0
    for (let m = n; m; m = m >> 64n) len++
    uint.encode(state, len)
    const view = new DataView(
      state.buffer.buffer,
      state.start + state.buffer.byteOffset,
      8 * len
    )
    for (let m = n, i = 0; m; m = m >> 64n, i += 8) {
      view.setBigUint64(i, BigInt.asUintN(64, m), true) // little endian
    }
    state.start += 8 * len
  },
  decode(state) {
    const len = uint.decode(state)
    if (state.end - state.start < 8 * len) throw new Error('Out of bounds')
    const view = new DataView(
      state.buffer.buffer,
      state.start + state.buffer.byteOffset,
      8 * len
    )
    let n = 0n
    for (let i = len - 1; i >= 0; i--)
      n = (n << 64n) + view.getBigUint64(i * 8, true) // little endian
    state.start += 8 * len
    return n
  }
})

exports.bigint = zigZagBigInt(biguint)

exports.lexint = require('./lexint')

exports.float32 = {
  preencode(state, n) {
    state.end += 4
  },
  encode(state, n) {
    const view = new DataView(
      state.buffer.buffer,
      state.start + state.buffer.byteOffset,
      4
    )
    view.setFloat32(0, n, true) // little endian
    state.start += 4
  },
  decode(state) {
    if (state.end - state.start < 4) throw new Error('Out of bounds')
    const view = new DataView(
      state.buffer.buffer,
      state.start + state.buffer.byteOffset,
      4
    )
    const float = view.getFloat32(0, true) // little endian
    state.start += 4
    return float
  }
}

exports.float64 = {
  preencode(state, n) {
    state.end += 8
  },
  encode(state, n) {
    const view = new DataView(
      state.buffer.buffer,
      state.start + state.buffer.byteOffset,
      8
    )
    view.setFloat64(0, n, true) // little endian
    state.start += 8
  },
  decode(state) {
    if (state.end - state.start < 8) throw new Error('Out of bounds')
    const view = new DataView(
      state.buffer.buffer,
      state.start + state.buffer.byteOffset,
      8
    )
    const float = view.getFloat64(0, true) // little endian
    state.start += 8
    return float
  }
}

const buffer = (exports.buffer = {
  preencode(state, b) {
    if (b) uint8array.preencode(state, b)
    else state.end++
  },
  encode(state, b) {
    if (b) uint8array.encode(state, b)
    else state.buffer[state.start++] = 0
  },
  decode(state) {
    const len = uint.decode(state)
    if (len === 0) return null
    if (state.end - state.start < len) throw new Error('Out of bounds')
    return state.buffer.subarray(state.start, (state.start += len))
  }
})

exports.binary = {
  ...buffer,
  preencode(state, b) {
    if (typeof b === 'string') utf8.preencode(state, b)
    else buffer.preencode(state, b)
  },
  encode(state, b) {
    if (typeof b === 'string') utf8.encode(state, b)
    else buffer.encode(state, b)
  }
}

exports.arraybuffer = {
  preencode(state, b) {
    uint.preencode(state, b.byteLength)
    state.end += b.byteLength
  },
  encode(state, b) {
    uint.encode(state, b.byteLength)

    const view = new Uint8Array(b)

    state.buffer.set(view, state.start)
    state.start += b.byteLength
  },
  decode(state) {
    const len = uint.decode(state)

    const b = new ArrayBuffer(len)
    const view = new Uint8Array(b)

    view.set(state.buffer.subarray(state.start, (state.start += len)))

    return b
  }
}

function typedarray(TypedArray, swap) {
  const n = TypedArray.BYTES_PER_ELEMENT

  return {
    preencode(state, b) {
      uint.preencode(state, b.length)
      state.end += b.byteLength
    },
    encode(state, b) {
      uint.encode(state, b.length)

      const view = new Uint8Array(b.buffer, b.byteOffset, b.byteLength)

      if (BE && swap) swap(view)

      state.buffer.set(view, state.start)
      state.start += b.byteLength
    },
    decode(state) {
      const len = uint.decode(state)

      let b = state.buffer.subarray(state.start, (state.start += len * n))
      if (b.byteLength !== len * n) throw new Error('Out of bounds')
      if (b.byteOffset % n !== 0) b = new Uint8Array(b)

      if (BE && swap) swap(b)

      return new TypedArray(b.buffer, b.byteOffset, b.byteLength / n)
    }
  }
}

const uint8array = (exports.uint8array = typedarray(Uint8Array))
exports.uint16array = typedarray(Uint16Array, b4a.swap16)
exports.uint32array = typedarray(Uint32Array, b4a.swap32)

exports.int8array = typedarray(Int8Array)
exports.int16array = typedarray(Int16Array, b4a.swap16)
exports.int32array = typedarray(Int32Array, b4a.swap32)

exports.biguint64array = typedarray(BigUint64Array, b4a.swap64)
exports.bigint64array = typedarray(BigInt64Array, b4a.swap64)

exports.float32array = typedarray(Float32Array, b4a.swap32)
exports.float64array = typedarray(Float64Array, b4a.swap64)

function string(encoding) {
  return {
    preencode(state, s) {
      const len = b4a.byteLength(s, encoding)
      uint.preencode(state, len)
      state.end += len
    },
    encode(state, s) {
      const len = b4a.byteLength(s, encoding)
      uint.encode(state, len)
      b4a.write(state.buffer, s, state.start, encoding)
      state.start += len
    },
    decode(state) {
      const len = uint.decode(state)
      if (state.end - state.start < len) throw new Error('Out of bounds')
      return b4a.toString(
        state.buffer,
        encoding,
        state.start,
        (state.start += len)
      )
    },
    fixed(n) {
      return {
        preencode(state) {
          state.end += n
        },
        encode(state, s) {
          b4a.write(state.buffer, s, state.start, n, encoding)
          state.start += n
        },
        decode(state) {
          if (state.end - state.start < n) throw new Error('Out of bounds')
          return b4a.toString(
            state.buffer,
            encoding,
            state.start,
            (state.start += n)
          )
        }
      }
    }
  }
}

const utf8 = (exports.string = exports.utf8 = string('utf-8'))
exports.ascii = string('ascii')
exports.hex = string('hex')
exports.base64 = string('base64')
exports.ucs2 = exports.utf16le = string('utf16le')

exports.bool = {
  preencode(state, b) {
    state.end++
  },
  encode(state, b) {
    state.buffer[state.start++] = b ? 1 : 0
  },
  decode(state) {
    if (state.start >= state.end) throw Error('Out of bounds')
    return state.buffer[state.start++] === 1
  }
}

const fixed = (exports.fixed = function fixed(n) {
  return {
    preencode(state, s) {
      if (s.byteLength !== n) throw new Error('Incorrect buffer size')
      state.end += n
    },
    encode(state, s) {
      state.buffer.set(s, state.start)
      state.start += n
    },
    decode(state) {
      if (state.end - state.start < n) throw new Error('Out of bounds')
      return state.buffer.subarray(state.start, (state.start += n))
    }
  }
})

exports.fixed32 = fixed(32)
exports.fixed64 = fixed(64)

exports.array = function array(enc) {
  return {
    preencode(state, list) {
      uint.preencode(state, list.length)
      for (let i = 0; i < list.length; i++) enc.preencode(state, list[i])
    },
    encode(state, list) {
      uint.encode(state, list.length)
      for (let i = 0; i < list.length; i++) enc.encode(state, list[i])
    },
    decode(state) {
      const len = uint.decode(state)
      if (len > 0x100000) throw new Error('Array is too big')
      const arr = new Array(len)
      for (let i = 0; i < len; i++) arr[i] = enc.decode(state)
      return arr
    }
  }
}

exports.frame = function frame(enc) {
  const dummy = exports.state()

  return {
    preencode(state, m) {
      const end = state.end
      enc.preencode(state, m)
      uint.preencode(state, state.end - end)
    },
    encode(state, m) {
      dummy.end = 0
      enc.preencode(dummy, m)
      uint.encode(state, dummy.end)
      enc.encode(state, m)
    },
    decode(state) {
      const end = state.end
      const len = uint.decode(state)
      state.end = state.start + len
      const m = enc.decode(state)
      state.start = state.end
      state.end = end
      return m
    }
  }
}

exports.date = {
  preencode(state, d) {
    int.preencode(state, d.getTime())
  },
  encode(state, d) {
    int.encode(state, d.getTime())
  },
  decode(state, d) {
    return new Date(int.decode(state))
  }
}

exports.json = {
  preencode(state, v) {
    utf8.preencode(state, JSON.stringify(v))
  },
  encode(state, v) {
    utf8.encode(state, JSON.stringify(v))
  },
  decode(state) {
    return JSON.parse(utf8.decode(state))
  }
}

exports.ndjson = {
  preencode(state, v) {
    utf8.preencode(state, JSON.stringify(v) + '\n')
  },
  encode(state, v) {
    utf8.encode(state, JSON.stringify(v) + '\n')
  },
  decode(state) {
    return JSON.parse(utf8.decode(state))
  }
}

// simple helper for when you want to just express nothing
exports.none = {
  preencode(state, n) {
    // do nothing
  },
  encode(state, n) {
    // do nothing
  },
  decode(state) {
    return null
  }
}

// "any" encoders here for helping just structure any object without schematising it

const anyArray = {
  preencode(state, arr) {
    uint.preencode(state, arr.length)
    for (let i = 0; i < arr.length; i++) {
      any.preencode(state, arr[i])
    }
  },
  encode(state, arr) {
    uint.encode(state, arr.length)
    for (let i = 0; i < arr.length; i++) {
      any.encode(state, arr[i])
    }
  },
  decode(state) {
    const arr = []
    let len = uint.decode(state)
    while (len-- > 0) {
      arr.push(any.decode(state))
    }
    return arr
  }
}

const anyObject = {
  preencode(state, o) {
    const keys = Object.keys(o)
    uint.preencode(state, keys.length)
    for (const key of keys) {
      utf8.preencode(state, key)
      any.preencode(state, o[key])
    }
  },
  encode(state, o) {
    const keys = Object.keys(o)
    uint.encode(state, keys.length)
    for (const key of keys) {
      utf8.encode(state, key)
      any.encode(state, o[key])
    }
  },
  decode(state) {
    let len = uint.decode(state)
    const o = {}
    while (len-- > 0) {
      const key = utf8.decode(state)
      o[key] = any.decode(state)
    }
    return o
  }
}

const anyTypes = [
  exports.none,
  exports.bool,
  exports.string,
  exports.buffer,
  exports.uint,
  exports.int,
  exports.float64,
  anyArray,
  anyObject,
  exports.date
]

const any = (exports.any = {
  preencode(state, o) {
    const t = getType(o)
    uint.preencode(state, t)
    anyTypes[t].preencode(state, o)
  },
  encode(state, o) {
    const t = getType(o)
    uint.encode(state, t)
    anyTypes[t].encode(state, o)
  },
  decode(state) {
    const t = uint.decode(state)
    if (t >= anyTypes.length) throw new Error('Unknown type: ' + t)
    return anyTypes[t].decode(state)
  }
})

const port = (exports.port = uint16)

const address = (host, family) => {
  return {
    preencode(state, m) {
      host.preencode(state, m.host)
      port.preencode(state, m.port)
    },
    encode(state, m) {
      host.encode(state, m.host)
      port.encode(state, m.port)
    },
    decode(state) {
      return {
        host: host.decode(state),
        family,
        port: port.decode(state)
      }
    }
  }
}

const ipv4 = (exports.ipv4 = {
  preencode(state) {
    state.end += 4
  },
  encode(state, string) {
    const start = state.start
    const end = start + 4

    let i = 0

    while (i < string.length) {
      let n = 0
      let c

      while (
        i < string.length &&
        (c = string.charCodeAt(i++)) !== /* . */ 0x2e
      ) {
        n = n * 10 + (c - /* 0 */ 0x30)
      }

      state.buffer[state.start++] = n
    }

    state.start = end
  },
  decode(state) {
    if (state.end - state.start < 4) throw new Error('Out of bounds')
    return (
      state.buffer[state.start++] +
      '.' +
      state.buffer[state.start++] +
      '.' +
      state.buffer[state.start++] +
      '.' +
      state.buffer[state.start++]
    )
  }
})

exports.ipv4Address = address(ipv4, 4)

const ipv6 = (exports.ipv6 = {
  preencode(state) {
    state.end += 16
  },
  encode(state, string) {
    const start = state.start
    const end = start + 16

    let i = 0
    let split = null

    while (i < string.length) {
      let n = 0
      let c

      while (
        i < string.length &&
        (c = string.charCodeAt(i++)) !== /* : */ 0x3a
      ) {
        if (c >= 0x30 && c <= 0x39) n = n * 0x10 + (c - /* 0 */ 0x30)
        else if (c >= 0x41 && c <= 0x46) n = n * 0x10 + (c - /* A */ 0x41 + 10)
        else if (c >= 0x61 && c <= 0x66) n = n * 0x10 + (c - /* a */ 0x61 + 10)
      }

      state.buffer[state.start++] = n >>> 8
      state.buffer[state.start++] = n

      if (i < string.length && string.charCodeAt(i) === /* : */ 0x3a) {
        i++
        split = state.start
      }
    }

    if (split !== null) {
      const offset = end - state.start
      state.buffer
        .copyWithin(split + offset, split)
        .fill(0, split, split + offset)
    }

    state.start = end
  },
  decode(state) {
    if (state.end - state.start < 16) throw new Error('Out of bounds')
    return (
      (
        state.buffer[state.start++] * 256 +
        state.buffer[state.start++]
      ).toString(16) +
      ':' +
      (
        state.buffer[state.start++] * 256 +
        state.buffer[state.start++]
      ).toString(16) +
      ':' +
      (
        state.buffer[state.start++] * 256 +
        state.buffer[state.start++]
      ).toString(16) +
      ':' +
      (
        state.buffer[state.start++] * 256 +
        state.buffer[state.start++]
      ).toString(16) +
      ':' +
      (
        state.buffer[state.start++] * 256 +
        state.buffer[state.start++]
      ).toString(16) +
      ':' +
      (
        state.buffer[state.start++] * 256 +
        state.buffer[state.start++]
      ).toString(16) +
      ':' +
      (
        state.buffer[state.start++] * 256 +
        state.buffer[state.start++]
      ).toString(16) +
      ':' +
      (
        state.buffer[state.start++] * 256 +
        state.buffer[state.start++]
      ).toString(16)
    )
  }
})

exports.ipv6Address = address(ipv6, 6)

const ip = (exports.ip = {
  preencode(state, string) {
    const family = string.includes(':') ? 6 : 4
    uint8.preencode(state, family)
    if (family === 4) ipv4.preencode(state)
    else ipv6.preencode(state)
  },
  encode(state, string) {
    const family = string.includes(':') ? 6 : 4
    uint8.encode(state, family)
    if (family === 4) ipv4.encode(state, string)
    else ipv6.encode(state, string)
  },
  decode(state) {
    const family = uint8.decode(state)
    if (family === 4) return ipv4.decode(state)
    else return ipv6.decode(state)
  }
})

exports.ipAddress = {
  preencode(state, m) {
    ip.preencode(state, m.host)
    port.preencode(state, m.port)
  },
  encode(state, m) {
    ip.encode(state, m.host)
    port.encode(state, m.port)
  },
  decode(state) {
    const family = uint8.decode(state)
    return {
      host: family === 4 ? ipv4.decode(state) : ipv6.decode(state),
      family,
      port: port.decode(state)
    }
  }
}

const record = (exports.record = function (keyEncoding, valueEncoding) {
  return {
    preencode(state, v) {
      const keys = Object.keys(v)
      uint.preencode(state, keys.length)
      for (const k of keys) {
        keyEncoding.preencode(state, k)
        valueEncoding.preencode(state, v[k])
      }
    },
    encode(state, v) {
      const keys = Object.keys(v)
      uint.encode(state, keys.length)
      for (const k of keys) {
        keyEncoding.encode(state, k)
        valueEncoding.encode(state, v[k])
      }
    },
    decode(state) {
      const out = Object.create(null)
      const keys = uint.decode(state)
      for (let i = 0; i < keys; i++) {
        out[keyEncoding.decode(state)] = valueEncoding.decode(state)
      }
      return out
    }
  }
})

exports.stringRecord = record(utf8, utf8)

function getType(o) {
  if (o === null || o === undefined) return 0
  if (typeof o === 'boolean') return 1
  if (typeof o === 'string') return 2
  if (b4a.isBuffer(o)) return 3
  if (typeof o === 'number') {
    if (Number.isInteger(o)) return o >= 0 ? 4 : 5
    return 6
  }
  if (Array.isArray(o)) return 7
  if (o instanceof Date) return 9
  if (typeof o === 'object') return 8

  throw new Error('Unsupported type for ' + o)
}

exports.from = function from(enc) {
  if (typeof enc === 'string') return fromNamed(enc)
  if (enc.preencode) return enc
  if (enc.encodingLength) return fromAbstractEncoder(enc)
  return fromCodec(enc)
}

function fromNamed(enc) {
  switch (enc) {
    case 'ascii':
      return raw.ascii
    case 'utf-8':
    case 'utf8':
      return raw.utf8
    case 'hex':
      return raw.hex
    case 'base64':
      return raw.base64
    case 'utf16-le':
    case 'utf16le':
    case 'ucs-2':
    case 'ucs2':
      return raw.ucs2
    case 'ndjson':
      return raw.ndjson
    case 'json':
      return raw.json
    case 'binary':
    default:
      return raw.binary
  }
}

function fromCodec(enc) {
  let tmpM = null
  let tmpBuf = null

  return {
    preencode(state, m) {
      tmpM = m
      tmpBuf = enc.encode(m)
      state.end += tmpBuf.byteLength
    },
    encode(state, m) {
      raw.encode(state, m === tmpM ? tmpBuf : enc.encode(m))
      tmpM = tmpBuf = null
    },
    decode(state) {
      return enc.decode(raw.decode(state))
    }
  }
}

function fromAbstractEncoder(enc) {
  return {
    preencode(state, m) {
      state.end += enc.encodingLength(m)
    },
    encode(state, m) {
      enc.encode(m, state.buffer, state.start)
      state.start += enc.encode.bytes
    },
    decode(state) {
      const m = enc.decode(state.buffer, state.start, state.end)
      state.start += enc.decode.bytes
      return m
    }
  }
}

exports.encode = function encode(enc, m) {
  const state = exports.state()
  enc.preencode(state, m)
  state.buffer = b4a.allocUnsafe(state.end)
  enc.encode(state, m)
  return state.buffer
}

exports.decode = function decode(enc, buffer) {
  return enc.decode(exports.state(0, buffer.byteLength, buffer))
}

function zigZagInt(enc) {
  return {
    preencode(state, n) {
      enc.preencode(state, zigZagEncodeInt(n))
    },
    encode(state, n) {
      enc.encode(state, zigZagEncodeInt(n))
    },
    decode(state) {
      return zigZagDecodeInt(enc.decode(state))
    }
  }
}

function zigZagDecodeInt(n) {
  return n === 0 ? n : (n & 1) === 0 ? n / 2 : -(n + 1) / 2
}

function zigZagEncodeInt(n) {
  // 0, -1, 1, -2, 2, ...
  return n < 0 ? 2 * -n - 1 : n === 0 ? 0 : 2 * n
}

function zigZagBigInt(enc) {
  return {
    preencode(state, n) {
      enc.preencode(state, zigZagEncodeBigInt(n))
    },
    encode(state, n) {
      enc.encode(state, zigZagEncodeBigInt(n))
    },
    decode(state) {
      return zigZagDecodeBigInt(enc.decode(state))
    }
  }
}

function zigZagDecodeBigInt(n) {
  return n === 0n ? n : (n & 1n) === 0n ? n / 2n : -(n + 1n) / 2n
}

function zigZagEncodeBigInt(n) {
  // 0, -1, 1, -2, 2, ...
  return n < 0n ? 2n * -n - 1n : n === 0n ? 0n : 2n * n
}

function validateUint(n) {
  if (n >= 0 === false /* Handles NaN as well */)
    throw new Error('uint must be positive')
}
module.exports = {
  preencode,
  encode,
  decode
}

function preencode(state, num) {
  if (num < 251) {
    state.end++
  } else if (num < 256) {
    state.end += 2
  } else if (num < 0x10000) {
    state.end += 3
  } else if (num < 0x1000000) {
    state.end += 4
  } else if (num < 0x100000000) {
    state.end += 5
  } else {
    state.end++
    const exp = Math.floor(Math.log(num) / Math.log(2)) - 32
    preencode(state, exp)
    state.end += 6
  }
}

function encode(state, num) {
  const max = 251
  const x = num - max

  if (num < max) {
    state.buffer[state.start++] = num
  } else if (num < 256) {
    state.buffer[state.start++] = max
    state.buffer[state.start++] = x
  } else if (num < 0x10000) {
    state.buffer[state.start++] = max + 1
    state.buffer[state.start++] = (x >> 8) & 0xff
    state.buffer[state.start++] = x & 0xff
  } else if (num < 0x1000000) {
    state.buffer[state.start++] = max + 2
    state.buffer[state.start++] = x >> 16
    state.buffer[state.start++] = (x >> 8) & 0xff
    state.buffer[state.start++] = x & 0xff
  } else if (num < 0x100000000) {
    state.buffer[state.start++] = max + 3
    state.buffer[state.start++] = x >> 24
    state.buffer[state.start++] = (x >> 16) & 0xff
    state.buffer[state.start++] = (x >> 8) & 0xff
    state.buffer[state.start++] = x & 0xff
  } else {
    // need to use Math here as bitwise ops are 32 bit
    const exp = Math.floor(Math.log(x) / Math.log(2)) - 32
    state.buffer[state.start++] = 0xff

    encode(state, exp)
    const rem = x / Math.pow(2, exp - 11)

    for (let i = 5; i >= 0; i--) {
      state.buffer[state.start++] = (rem / Math.pow(2, 8 * i)) & 0xff
    }
  }
}

function decode(state) {
  const max = 251

  if (state.end - state.start < 1) throw new Error('Out of bounds')

  const flag = state.buffer[state.start++]

  if (flag < max) return flag

  if (state.end - state.start < flag - max + 1) {
    throw new Error('Out of bounds.')
  }

  if (flag < 252) {
    return state.buffer[state.start++] + max
  }

  if (flag < 253) {
    return (
      (state.buffer[state.start++] << 8) + state.buffer[state.start++] + max
    )
  }

  if (flag < 254) {
    return (
      (state.buffer[state.start++] << 16) +
      (state.buffer[state.start++] << 8) +
      state.buffer[state.start++] +
      max
    )
  }

  // << 24 result may be interpreted as negative
  if (flag < 255) {
    return (
      state.buffer[state.start++] * 0x1000000 +
      (state.buffer[state.start++] << 16) +
      (state.buffer[state.start++] << 8) +
      state.buffer[state.start++] +
      max
    )
  }

  const exp = decode(state)

  if (state.end - state.start < 6) throw new Error('Out of bounds')

  let rem = 0
  for (let i = 5; i >= 0; i--) {
    rem += state.buffer[state.start++] * Math.pow(2, 8 * i)
  }

  return rem * Math.pow(2, exp - 11) + max
}
{
  "name": "compact-encoding",
  "version": "2.19.2",
  "description": "A series of compact encoding schemes for building small and fast parsers and serializers",
  "main": "index.js",
  "dependencies": {
    "b4a": "^1.3.0"
  },
  "devDependencies": {
    "brittle": "^3.0.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^1.0.0"
  },
  "scripts": {
    "format": "prettier . --write",
    "test": "prettier . --check && brittle test.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/holepunchto/compact-encoding.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/compact-encoding/issues"
  },
  "homepage": "https://github.com/holepunchto/compact-encoding"
}
const b4a = require('b4a')

const { BE } = require('./endian')

exports = module.exports = {
  preencode(state, b) {
    state.end += b.byteLength
  },
  encode(state, b) {
    state.buffer.set(b, state.start)
    state.start += b.byteLength
  },
  decode(state) {
    const b = state.buffer.subarray(state.start, state.end)
    state.start = state.end
    return b
  }
}

const buffer = (exports.buffer = {
  preencode(state, b) {
    if (b) uint8array.preencode(state, b)
    else state.end++
  },
  encode(state, b) {
    if (b) uint8array.encode(state, b)
    else state.buffer[state.start++] = 0
  },
  decode(state) {
    const b = state.buffer.subarray(state.start)
    if (b.byteLength === 0) return null
    state.start = state.end
    return b
  }
})

exports.binary = {
  ...buffer,
  preencode(state, b) {
    if (typeof b === 'string') utf8.preencode(state, b)
    else buffer.preencode(state, b)
  },
  encode(state, b) {
    if (typeof b === 'string') utf8.encode(state, b)
    else buffer.encode(state, b)
  }
}

exports.arraybuffer = {
  preencode(state, b) {
    state.end += b.byteLength
  },
  encode(state, b) {
    const view = new Uint8Array(b)

    state.buffer.set(view, state.start)
    state.start += b.byteLength
  },
  decode(state) {
    const b = new ArrayBuffer(state.end - state.start)
    const view = new Uint8Array(b)

    view.set(state.buffer.subarray(state.start))

    state.start = state.end

    return b
  }
}

function typedarray(TypedArray, swap) {
  const n = TypedArray.BYTES_PER_ELEMENT

  return {
    preencode(state, b) {
      state.end += b.byteLength
    },
    encode(state, b) {
      const view = new Uint8Array(b.buffer, b.byteOffset, b.byteLength)

      if (BE && swap) swap(view)

      state.buffer.set(view, state.start)
      state.start += b.byteLength
    },
    decode(state) {
      let b = state.buffer.subarray(state.start)
      if (b.byteOffset % n !== 0) b = new Uint8Array(b)

      if (BE && swap) swap(b)

      state.start = state.end

      return new TypedArray(b.buffer, b.byteOffset, b.byteLength / n)
    }
  }
}

const uint8array = (exports.uint8array = typedarray(Uint8Array))
exports.uint16array = typedarray(Uint16Array, b4a.swap16)
exports.uint32array = typedarray(Uint32Array, b4a.swap32)

exports.int8array = typedarray(Int8Array)
exports.int16array = typedarray(Int16Array, b4a.swap16)
exports.int32array = typedarray(Int32Array, b4a.swap32)

exports.biguint64array = typedarray(BigUint64Array, b4a.swap64)
exports.bigint64array = typedarray(BigInt64Array, b4a.swap64)

exports.float32array = typedarray(Float32Array, b4a.swap32)
exports.float64array = typedarray(Float64Array, b4a.swap64)

function string(encoding) {
  return {
    preencode(state, s) {
      state.end += b4a.byteLength(s, encoding)
    },
    encode(state, s) {
      state.start += b4a.write(state.buffer, s, state.start, encoding)
    },
    decode(state) {
      const s = b4a.toString(state.buffer, encoding, state.start)
      state.start = state.end
      return s
    }
  }
}

const utf8 = (exports.string = exports.utf8 = string('utf-8'))
exports.ascii = string('ascii')
exports.hex = string('hex')
exports.base64 = string('base64')
exports.ucs2 = exports.utf16le = string('utf16le')

exports.array = function array(enc) {
  return {
    preencode(state, list) {
      for (const value of list) enc.preencode(state, value)
    },
    encode(state, list) {
      for (const value of list) enc.encode(state, value)
    },
    decode(state) {
      const arr = []
      while (state.start < state.end) arr.push(enc.decode(state))
      return arr
    }
  }
}

exports.json = {
  preencode(state, v) {
    utf8.preencode(state, JSON.stringify(v))
  },
  encode(state, v) {
    utf8.encode(state, JSON.stringify(v))
  },
  decode(state) {
    return JSON.parse(utf8.decode(state))
  }
}

exports.ndjson = {
  preencode(state, v) {
    utf8.preencode(state, JSON.stringify(v) + '\n')
  },
  encode(state, v) {
    utf8.encode(state, JSON.stringify(v) + '\n')
  },
  decode(state) {
    return JSON.parse(utf8.decode(state))
  }
}
{
  "name": "blind-relay",
  "version": "1.5.0",
  "description": "Blind relay for UDX over Protomux channels",
  "main": "index.js",
  "files": [
    "index.js",
    "lib"
  ],
  "scripts": {
    "format": "prettier --write .",
    "lint": "prettier --check . && lunte",
    "test": "npm run test:bare",
    "test:bare": "brittle-bare --coverage test.mjs"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/blind-relay.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/blind-relay/issues"
  },
  "homepage": "https://github.com/holepunchto/blind-relay#readme",
  "imports": {
    "events": {
      "bare": "bare-events",
      "default": "events"
    }
  },
  "dependencies": {
    "b4a": "^1.6.4",
    "bare-events": "^2.2.0",
    "bits-to-bytes": "^1.3.0",
    "compact-encoding": "^2.12.0",
    "compact-encoding-bitfield": "^1.0.0",
    "protomux": "^3.5.1",
    "sodium-universal": "^5.0.0",
    "streamx": "^2.15.1"
  },
  "devDependencies": {
    "brittle": "^3.2.1",
    "hyperdht": "^6.6.1",
    "lunte": "^1.0.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^2.0.0",
    "udx-native": "^1.6.1"
  }
}
// https://ipinfo.io/bogon

const b4a = require('b4a')
const c = require('compact-encoding')
const net = require('compact-encoding-net')

module.exports = exports = function isBogon (ip) {
  return isBogonIP(ensureBuffer(ip))
}

exports.isBogon = exports

exports.isPrivate = function isPrivate (ip) {
  return isPrivateIP(ensureBuffer(ip))
}

exports.isReserved = function isReserved (ip) {
  return isReservedIP(ensureBuffer(ip))
}

function isBogonIP (ip) {
  return isPrivateIP(ip) || isReservedIP(ip)
}

function isPrivateIP (ip) {
  return ip.byteLength === 4 ? isPrivateIPv4(ip) : false // IPv6 has no private IPs
}

function isPrivateIPv4 (ip) {
  return (
    // 10.0.0.0/8  Private-use networks
    (ip[0] === 10) ||
    // 100.64.0.0/10 Carrier-grade NAT
    (ip[0] === 100 && ip[1] >= 64 && ip[1] <= 127) ||
    // 127.0.0.0/8 Loopback + Name collision occurrence (127.0.53.53)
    (ip[0] === 127) ||
    // 169.254.0.0/16  Link local
    (ip[0] === 169 && ip[1] === 254) ||
    // 172.16.0.0/12 Private-use networks
    (ip[0] === 172 && ip[1] >= 16 && ip[1] <= 31) ||
    // 192.168.0.0/16  Private-use networks
    (ip[0] === 192 && ip[1] === 168)
  )
}

function isReservedIP (ip) {
  return ip.byteLength === 4 ? isReservedIPv4(ip) : isReservedIPv6(ip)
}

function isReservedIPv4 (ip) {
  return (
    // 0.0.0.0/8 "This" network
    (ip[0] === 0) ||
    // 192.0.0.0/24  IETF protocol assignments
    (ip[0] === 192 && ip[1] === 0 && ip[2] === 0) ||
    // 192.0.2.0/24  TEST-NET-1
    (ip[0] === 192 && ip[1] === 0 && ip[2] === 2) ||
    // 198.18.0.0/15 Network interconnect device benchmark testing
    (ip[0] === 198 && ip[1] >= 18 && ip[1] <= 19) ||
    // 198.51.100.0/24 TEST-NET-2
    (ip[0] === 198 && ip[1] === 51 && ip[2] === 100) ||
    // 203.0.113.0/24  TEST-NET-3
    (ip[0] === 203 && ip[1] === 0 && ip[2] === 113) ||
    // 224.0.0.0/4 Multicast
    (ip[0] >= 224 && ip[0] <= 239) ||
    // 240.0.0.0/4 Reserved for future use
    (ip[0] >= 240) ||
    // 255.255.255.255/32
    (ip[0] === 255 && ip[1] === 255 && ip[2] === 255 && ip[3] === 255)
  )
}

function isReservedIPv6 (ip) {
  return (
    // ::/128 Node-scope unicast unspecified address
    // ::1/128 Node-scope unicast loopback address
    (
      ip[0] === 0 && ip[1] === 0 && ip[2] === 0 && ip[3] === 0 && ip[4] === 0 &&
      ip[5] === 0 && ip[6] === 0 && ip[7] === 0 && ip[8] === 0 && ip[9] === 0 &&
      ip[10] === 0 && ip[11] === 0 && ip[12] === 0 && ip[13] === 0 && ip[14] === 0 &&
      ip[15] <= 1
    ) ||
    // ::ffff:0:0/96 IPv4-mapped addresses
    // ::/96 IPv4-compatible addresses
    (
      ip[0] === 0 && ip[1] === 0 && ip[2] === 0 && ip[3] === 0 && ip[4] === 0 &&
      ip[5] === 0 && ip[6] === 0 && ip[7] === 0 && ip[8] === 0 && ip[9] === 0 &&
      (ip[10] === 0 || ip[10] === 0xff) &&
      (ip[11] === 0 || ip[11] === 0xff)
    ) ||
    // 100::/64 Remotely triggered black hole addresses
    (ip[0] === 0x01 && ip[1] === 0 && ip[2] === 0 && ip[3] === 0 && ip[4] === 0 && ip[5] === 0 && ip[6] === 0 && ip[7] === 0) ||
    // 2001:10::/28 Overlay routable cryptographic hash identifiers (ORCHID)
    (ip[0] === 0x20 && ip[1] === 0x01 && ip[2] === 0 && ip[3] >= 0x10 && ip[3] <= 0x1f) ||
    // 2001:20::/28 Overlay routable cryptographic hash identifiers version 2 (ORCHIDv2)
    (ip[0] === 0x20 && ip[1] === 0x01 && ip[2] === 0 && ip[3] >= 0x20 && ip[3] <= 0x2f) ||
    // 2001:db8::/32 Documentation prefix
    (ip[0] === 0x20 && ip[1] === 0x01 && ip[2] === 0x0d && ip[3] === 0xb8) ||
    // fc00::/7 Unique local addresses (ULA)
    (ip[0] >= 0xfc && ip[0] <= 0xfd) ||
    // fe80::/10 Link-local unicast
    (ip[0] === 0xfe && ip[1] >= 0x80 && ip[1] <= 0xbf) ||
    // ff00::/8 Multicast
    (ip[0] === 0xff)
  )
}

const state = c.state(0, 0, b4a.allocUnsafe(1 /* family */ + 16))

function ensureBuffer (ip) {
  if (b4a.isBuffer(ip)) return ip

  net.ip.preencode(state, ip)
  net.ip.encode(state, ip)

  const buffer = state.buffer.subarray(1 /* family */, state.end)

  state.start = 0
  state.end = 0

  return buffer
}
const LE = (exports.LE =
  new Uint8Array(new Uint16Array([0xff]).buffer)[0] === 0xff)

exports.BE = !LE
const b4a = require('b4a')

const { BE } = require('./endian')

exports.state = function (start = 0, end = 0, buffer = null) {
  return { start, end, buffer }
}

const raw = (exports.raw = require('./raw'))

const uint = (exports.uint = {
  preencode(state, n) {
    state.end += n <= 0xfc ? 1 : n <= 0xffff ? 3 : n <= 0xffffffff ? 5 : 9
  },
  encode(state, n) {
    if (n <= 0xfc) uint8.encode(state, n)
    else if (n <= 0xffff) {
      state.buffer[state.start++] = 0xfd
      uint16.encode(state, n)
    } else if (n <= 0xffffffff) {
      state.buffer[state.start++] = 0xfe
      uint32.encode(state, n)
    } else {
      state.buffer[state.start++] = 0xff
      uint64.encode(state, n)
    }
  },
  decode(state) {
    const a = uint8.decode(state)
    if (a <= 0xfc) return a
    if (a === 0xfd) return uint16.decode(state)
    if (a === 0xfe) return uint32.decode(state)
    return uint64.decode(state)
  }
})

const uint8 = (exports.uint8 = {
  preencode(state, n) {
    state.end += 1
  },
  encode(state, n) {
    validateUint(n)
    state.buffer[state.start++] = n
  },
  decode(state) {
    if (state.start >= state.end) throw new Error('Out of bounds')
    return state.buffer[state.start++]
  }
})

const uint16 = (exports.uint16 = {
  preencode(state, n) {
    state.end += 2
  },
  encode(state, n) {
    validateUint(n)
    state.buffer[state.start++] = n
    state.buffer[state.start++] = n >>> 8
  },
  decode(state) {
    if (state.end - state.start < 2) throw new Error('Out of bounds')
    return state.buffer[state.start++] + state.buffer[state.start++] * 0x100
  }
})

const uint24 = (exports.uint24 = {
  preencode(state, n) {
    state.end += 3
  },
  encode(state, n) {
    validateUint(n)
    state.buffer[state.start++] = n
    state.buffer[state.start++] = n >>> 8
    state.buffer[state.start++] = n >>> 16
  },
  decode(state) {
    if (state.end - state.start < 3) throw new Error('Out of bounds')
    return (
      state.buffer[state.start++] +
      state.buffer[state.start++] * 0x100 +
      state.buffer[state.start++] * 0x10000
    )
  }
})

const uint32 = (exports.uint32 = {
  preencode(state, n) {
    state.end += 4
  },
  encode(state, n) {
    validateUint(n)
    state.buffer[state.start++] = n
    state.buffer[state.start++] = n >>> 8
    state.buffer[state.start++] = n >>> 16
    state.buffer[state.start++] = n >>> 24
  },
  decode(state) {
    if (state.end - state.start < 4) throw new Error('Out of bounds')
    return (
      state.buffer[state.start++] +
      state.buffer[state.start++] * 0x100 +
      state.buffer[state.start++] * 0x10000 +
      state.buffer[state.start++] * 0x1000000
    )
  }
})

const uint40 = (exports.uint40 = {
  preencode(state, n) {
    state.end += 5
  },
  encode(state, n) {
    validateUint(n)
    const r = Math.floor(n / 0x100)
    uint8.encode(state, n)
    uint32.encode(state, r)
  },
  decode(state) {
    if (state.end - state.start < 5) throw new Error('Out of bounds')
    return uint8.decode(state) + 0x100 * uint32.decode(state)
  }
})

const uint48 = (exports.uint48 = {
  preencode(state, n) {
    state.end += 6
  },
  encode(state, n) {
    validateUint(n)
    const r = Math.floor(n / 0x10000)
    uint16.encode(state, n)
    uint32.encode(state, r)
  },
  decode(state) {
    if (state.end - state.start < 6) throw new Error('Out of bounds')
    return uint16.decode(state) + 0x10000 * uint32.decode(state)
  }
})

const uint56 = (exports.uint56 = {
  preencode(state, n) {
    state.end += 7
  },
  encode(state, n) {
    validateUint(n)
    const r = Math.floor(n / 0x1000000)
    uint24.encode(state, n)
    uint32.encode(state, r)
  },
  decode(state) {
    if (state.end - state.start < 7) throw new Error('Out of bounds')
    return uint24.decode(state) + 0x1000000 * uint32.decode(state)
  }
})

const uint64 = (exports.uint64 = {
  preencode(state, n) {
    state.end += 8
  },
  encode(state, n) {
    validateUint(n)
    const r = Math.floor(n / 0x100000000)
    uint32.encode(state, n)
    uint32.encode(state, r)
  },
  decode(state) {
    if (state.end - state.start < 8) throw new Error('Out of bounds')
    return uint32.decode(state) + 0x100000000 * uint32.decode(state)
  }
})

const int = (exports.int = zigZagInt(uint))
exports.int8 = zigZagInt(uint8)
exports.int16 = zigZagInt(uint16)
exports.int24 = zigZagInt(uint24)
exports.int32 = zigZagInt(uint32)
exports.int40 = zigZagInt(uint40)
exports.int48 = zigZagInt(uint48)
exports.int56 = zigZagInt(uint56)
exports.int64 = zigZagInt(uint64)

const biguint64 = (exports.biguint64 = {
  preencode(state, n) {
    state.end += 8
  },
  encode(state, n) {
    const view = new DataView(
      state.buffer.buffer,
      state.start + state.buffer.byteOffset,
      8
    )
    view.setBigUint64(0, n, true) // little endian
    state.start += 8
  },
  decode(state) {
    if (state.end - state.start < 8) throw new Error('Out of bounds')
    const view = new DataView(
      state.buffer.buffer,
      state.start + state.buffer.byteOffset,
      8
    )
    const n = view.getBigUint64(0, true) // little endian
    state.start += 8
    return n
  }
})

exports.bigint64 = zigZagBigInt(biguint64)

const biguint = (exports.biguint = {
  preencode(state, n) {
    let len = 0
    for (let m = n; m; m = m >> 64n) len++
    uint.preencode(state, len)
    state.end += 8 * len
  },
  encode(state, n) {
    let len = 0
    for (let m = n; m; m = m >> 64n) len++
    uint.encode(state, len)
    const view = new DataView(
      state.buffer.buffer,
      state.start + state.buffer.byteOffset,
      8 * len
    )
    for (let m = n, i = 0; m; m = m >> 64n, i += 8) {
      view.setBigUint64(i, BigInt.asUintN(64, m), true) // little endian
    }
    state.start += 8 * len
  },
  decode(state) {
    const len = uint.decode(state)
    if (state.end - state.start < 8 * len) throw new Error('Out of bounds')
    const view = new DataView(
      state.buffer.buffer,
      state.start + state.buffer.byteOffset,
      8 * len
    )
    let n = 0n
    for (let i = len - 1; i >= 0; i--)
      n = (n << 64n) + view.getBigUint64(i * 8, true) // little endian
    state.start += 8 * len
    return n
  }
})

exports.bigint = zigZagBigInt(biguint)

exports.lexint = require('./lexint')

exports.float32 = {
  preencode(state, n) {
    state.end += 4
  },
  encode(state, n) {
    const view = new DataView(
      state.buffer.buffer,
      state.start + state.buffer.byteOffset,
      4
    )
    view.setFloat32(0, n, true) // little endian
    state.start += 4
  },
  decode(state) {
    if (state.end - state.start < 4) throw new Error('Out of bounds')
    const view = new DataView(
      state.buffer.buffer,
      state.start + state.buffer.byteOffset,
      4
    )
    const float = view.getFloat32(0, true) // little endian
    state.start += 4
    return float
  }
}

exports.float64 = {
  preencode(state, n) {
    state.end += 8
  },
  encode(state, n) {
    const view = new DataView(
      state.buffer.buffer,
      state.start + state.buffer.byteOffset,
      8
    )
    view.setFloat64(0, n, true) // little endian
    state.start += 8
  },
  decode(state) {
    if (state.end - state.start < 8) throw new Error('Out of bounds')
    const view = new DataView(
      state.buffer.buffer,
      state.start + state.buffer.byteOffset,
      8
    )
    const float = view.getFloat64(0, true) // little endian
    state.start += 8
    return float
  }
}

const buffer = (exports.buffer = {
  preencode(state, b) {
    if (b) uint8array.preencode(state, b)
    else state.end++
  },
  encode(state, b) {
    if (b) uint8array.encode(state, b)
    else state.buffer[state.start++] = 0
  },
  decode(state) {
    const len = uint.decode(state)
    if (len === 0) return null
    if (state.end - state.start < len) throw new Error('Out of bounds')
    return state.buffer.subarray(state.start, (state.start += len))
  }
})

exports.binary = {
  ...buffer,
  preencode(state, b) {
    if (typeof b === 'string') utf8.preencode(state, b)
    else buffer.preencode(state, b)
  },
  encode(state, b) {
    if (typeof b === 'string') utf8.encode(state, b)
    else buffer.encode(state, b)
  }
}

exports.arraybuffer = {
  preencode(state, b) {
    uint.preencode(state, b.byteLength)
    state.end += b.byteLength
  },
  encode(state, b) {
    uint.encode(state, b.byteLength)

    const view = new Uint8Array(b)

    state.buffer.set(view, state.start)
    state.start += b.byteLength
  },
  decode(state) {
    const len = uint.decode(state)

    const b = new ArrayBuffer(len)
    const view = new Uint8Array(b)

    view.set(state.buffer.subarray(state.start, (state.start += len)))

    return b
  }
}

function typedarray(TypedArray, swap) {
  const n = TypedArray.BYTES_PER_ELEMENT

  return {
    preencode(state, b) {
      uint.preencode(state, b.length)
      state.end += b.byteLength
    },
    encode(state, b) {
      uint.encode(state, b.length)

      const view = new Uint8Array(b.buffer, b.byteOffset, b.byteLength)

      if (BE && swap) swap(view)

      state.buffer.set(view, state.start)
      state.start += b.byteLength
    },
    decode(state) {
      const len = uint.decode(state)

      let b = state.buffer.subarray(state.start, (state.start += len * n))
      if (b.byteLength !== len * n) throw new Error('Out of bounds')
      if (b.byteOffset % n !== 0) b = new Uint8Array(b)

      if (BE && swap) swap(b)

      return new TypedArray(b.buffer, b.byteOffset, b.byteLength / n)
    }
  }
}

const uint8array = (exports.uint8array = typedarray(Uint8Array))
exports.uint16array = typedarray(Uint16Array, b4a.swap16)
exports.uint32array = typedarray(Uint32Array, b4a.swap32)

exports.int8array = typedarray(Int8Array)
exports.int16array = typedarray(Int16Array, b4a.swap16)
exports.int32array = typedarray(Int32Array, b4a.swap32)

exports.biguint64array = typedarray(BigUint64Array, b4a.swap64)
exports.bigint64array = typedarray(BigInt64Array, b4a.swap64)

exports.float32array = typedarray(Float32Array, b4a.swap32)
exports.float64array = typedarray(Float64Array, b4a.swap64)

function string(encoding) {
  return {
    preencode(state, s) {
      const len = b4a.byteLength(s, encoding)
      uint.preencode(state, len)
      state.end += len
    },
    encode(state, s) {
      const len = b4a.byteLength(s, encoding)
      uint.encode(state, len)
      b4a.write(state.buffer, s, state.start, encoding)
      state.start += len
    },
    decode(state) {
      const len = uint.decode(state)
      if (state.end - state.start < len) throw new Error('Out of bounds')
      return b4a.toString(
        state.buffer,
        encoding,
        state.start,
        (state.start += len)
      )
    },
    fixed(n) {
      return {
        preencode(state) {
          state.end += n
        },
        encode(state, s) {
          b4a.write(state.buffer, s, state.start, n, encoding)
          state.start += n
        },
        decode(state) {
          if (state.end - state.start < n) throw new Error('Out of bounds')
          return b4a.toString(
            state.buffer,
            encoding,
            state.start,
            (state.start += n)
          )
        }
      }
    }
  }
}

const utf8 = (exports.string = exports.utf8 = string('utf-8'))
exports.ascii = string('ascii')
exports.hex = string('hex')
exports.base64 = string('base64')
exports.ucs2 = exports.utf16le = string('utf16le')

exports.bool = {
  preencode(state, b) {
    state.end++
  },
  encode(state, b) {
    state.buffer[state.start++] = b ? 1 : 0
  },
  decode(state) {
    if (state.start >= state.end) throw Error('Out of bounds')
    return state.buffer[state.start++] === 1
  }
}

const fixed = (exports.fixed = function fixed(n) {
  return {
    preencode(state, s) {
      if (s.byteLength !== n) throw new Error('Incorrect buffer size')
      state.end += n
    },
    encode(state, s) {
      state.buffer.set(s, state.start)
      state.start += n
    },
    decode(state) {
      if (state.end - state.start < n) throw new Error('Out of bounds')
      return state.buffer.subarray(state.start, (state.start += n))
    }
  }
})

exports.fixed32 = fixed(32)
exports.fixed64 = fixed(64)

exports.array = function array(enc) {
  return {
    preencode(state, list) {
      uint.preencode(state, list.length)
      for (let i = 0; i < list.length; i++) enc.preencode(state, list[i])
    },
    encode(state, list) {
      uint.encode(state, list.length)
      for (let i = 0; i < list.length; i++) enc.encode(state, list[i])
    },
    decode(state) {
      const len = uint.decode(state)
      if (len > 0x100000) throw new Error('Array is too big')
      const arr = new Array(len)
      for (let i = 0; i < len; i++) arr[i] = enc.decode(state)
      return arr
    }
  }
}

exports.frame = function frame(enc) {
  const dummy = exports.state()

  return {
    preencode(state, m) {
      const end = state.end
      enc.preencode(state, m)
      uint.preencode(state, state.end - end)
    },
    encode(state, m) {
      dummy.end = 0
      enc.preencode(dummy, m)
      uint.encode(state, dummy.end)
      enc.encode(state, m)
    },
    decode(state) {
      const end = state.end
      const len = uint.decode(state)
      state.end = state.start + len
      const m = enc.decode(state)
      state.start = state.end
      state.end = end
      return m
    }
  }
}

exports.date = {
  preencode(state, d) {
    int.preencode(state, d.getTime())
  },
  encode(state, d) {
    int.encode(state, d.getTime())
  },
  decode(state, d) {
    return new Date(int.decode(state))
  }
}

exports.json = {
  preencode(state, v) {
    utf8.preencode(state, JSON.stringify(v))
  },
  encode(state, v) {
    utf8.encode(state, JSON.stringify(v))
  },
  decode(state) {
    return JSON.parse(utf8.decode(state))
  }
}

exports.ndjson = {
  preencode(state, v) {
    utf8.preencode(state, JSON.stringify(v) + '\n')
  },
  encode(state, v) {
    utf8.encode(state, JSON.stringify(v) + '\n')
  },
  decode(state) {
    return JSON.parse(utf8.decode(state))
  }
}

// simple helper for when you want to just express nothing
exports.none = {
  preencode(state, n) {
    // do nothing
  },
  encode(state, n) {
    // do nothing
  },
  decode(state) {
    return null
  }
}

// "any" encoders here for helping just structure any object without schematising it

const anyArray = {
  preencode(state, arr) {
    uint.preencode(state, arr.length)
    for (let i = 0; i < arr.length; i++) {
      any.preencode(state, arr[i])
    }
  },
  encode(state, arr) {
    uint.encode(state, arr.length)
    for (let i = 0; i < arr.length; i++) {
      any.encode(state, arr[i])
    }
  },
  decode(state) {
    const arr = []
    let len = uint.decode(state)
    while (len-- > 0) {
      arr.push(any.decode(state))
    }
    return arr
  }
}

const anyObject = {
  preencode(state, o) {
    const keys = Object.keys(o)
    uint.preencode(state, keys.length)
    for (const key of keys) {
      utf8.preencode(state, key)
      any.preencode(state, o[key])
    }
  },
  encode(state, o) {
    const keys = Object.keys(o)
    uint.encode(state, keys.length)
    for (const key of keys) {
      utf8.encode(state, key)
      any.encode(state, o[key])
    }
  },
  decode(state) {
    let len = uint.decode(state)
    const o = {}
    while (len-- > 0) {
      const key = utf8.decode(state)
      o[key] = any.decode(state)
    }
    return o
  }
}

const anyTypes = [
  exports.none,
  exports.bool,
  exports.string,
  exports.buffer,
  exports.uint,
  exports.int,
  exports.float64,
  anyArray,
  anyObject,
  exports.date
]

const any = (exports.any = {
  preencode(state, o) {
    const t = getType(o)
    uint.preencode(state, t)
    anyTypes[t].preencode(state, o)
  },
  encode(state, o) {
    const t = getType(o)
    uint.encode(state, t)
    anyTypes[t].encode(state, o)
  },
  decode(state) {
    const t = uint.decode(state)
    if (t >= anyTypes.length) throw new Error('Unknown type: ' + t)
    return anyTypes[t].decode(state)
  }
})

const port = (exports.port = uint16)

const address = (host, family) => {
  return {
    preencode(state, m) {
      host.preencode(state, m.host)
      port.preencode(state, m.port)
    },
    encode(state, m) {
      host.encode(state, m.host)
      port.encode(state, m.port)
    },
    decode(state) {
      return {
        host: host.decode(state),
        family,
        port: port.decode(state)
      }
    }
  }
}

const ipv4 = (exports.ipv4 = {
  preencode(state) {
    state.end += 4
  },
  encode(state, string) {
    const start = state.start
    const end = start + 4

    let i = 0

    while (i < string.length) {
      let n = 0
      let c

      while (
        i < string.length &&
        (c = string.charCodeAt(i++)) !== /* . */ 0x2e
      ) {
        n = n * 10 + (c - /* 0 */ 0x30)
      }

      state.buffer[state.start++] = n
    }

    state.start = end
  },
  decode(state) {
    if (state.end - state.start < 4) throw new Error('Out of bounds')
    return (
      state.buffer[state.start++] +
      '.' +
      state.buffer[state.start++] +
      '.' +
      state.buffer[state.start++] +
      '.' +
      state.buffer[state.start++]
    )
  }
})

exports.ipv4Address = address(ipv4, 4)

const ipv6 = (exports.ipv6 = {
  preencode(state) {
    state.end += 16
  },
  encode(state, string) {
    const start = state.start
    const end = start + 16

    let i = 0
    let split = null

    while (i < string.length) {
      let n = 0
      let c

      while (
        i < string.length &&
        (c = string.charCodeAt(i++)) !== /* : */ 0x3a
      ) {
        if (c >= 0x30 && c <= 0x39) n = n * 0x10 + (c - /* 0 */ 0x30)
        else if (c >= 0x41 && c <= 0x46) n = n * 0x10 + (c - /* A */ 0x41 + 10)
        else if (c >= 0x61 && c <= 0x66) n = n * 0x10 + (c - /* a */ 0x61 + 10)
      }

      state.buffer[state.start++] = n >>> 8
      state.buffer[state.start++] = n

      if (i < string.length && string.charCodeAt(i) === /* : */ 0x3a) {
        i++
        split = state.start
      }
    }

    if (split !== null) {
      const offset = end - state.start
      state.buffer
        .copyWithin(split + offset, split)
        .fill(0, split, split + offset)
    }

    state.start = end
  },
  decode(state) {
    if (state.end - state.start < 16) throw new Error('Out of bounds')
    return (
      (
        state.buffer[state.start++] * 256 +
        state.buffer[state.start++]
      ).toString(16) +
      ':' +
      (
        state.buffer[state.start++] * 256 +
        state.buffer[state.start++]
      ).toString(16) +
      ':' +
      (
        state.buffer[state.start++] * 256 +
        state.buffer[state.start++]
      ).toString(16) +
      ':' +
      (
        state.buffer[state.start++] * 256 +
        state.buffer[state.start++]
      ).toString(16) +
      ':' +
      (
        state.buffer[state.start++] * 256 +
        state.buffer[state.start++]
      ).toString(16) +
      ':' +
      (
        state.buffer[state.start++] * 256 +
        state.buffer[state.start++]
      ).toString(16) +
      ':' +
      (
        state.buffer[state.start++] * 256 +
        state.buffer[state.start++]
      ).toString(16) +
      ':' +
      (
        state.buffer[state.start++] * 256 +
        state.buffer[state.start++]
      ).toString(16)
    )
  }
})

exports.ipv6Address = address(ipv6, 6)

const ip = (exports.ip = {
  preencode(state, string) {
    const family = string.includes(':') ? 6 : 4
    uint8.preencode(state, family)
    if (family === 4) ipv4.preencode(state)
    else ipv6.preencode(state)
  },
  encode(state, string) {
    const family = string.includes(':') ? 6 : 4
    uint8.encode(state, family)
    if (family === 4) ipv4.encode(state, string)
    else ipv6.encode(state, string)
  },
  decode(state) {
    const family = uint8.decode(state)
    if (family === 4) return ipv4.decode(state)
    else return ipv6.decode(state)
  }
})

exports.ipAddress = {
  preencode(state, m) {
    ip.preencode(state, m.host)
    port.preencode(state, m.port)
  },
  encode(state, m) {
    ip.encode(state, m.host)
    port.encode(state, m.port)
  },
  decode(state) {
    const family = uint8.decode(state)
    return {
      host: family === 4 ? ipv4.decode(state) : ipv6.decode(state),
      family,
      port: port.decode(state)
    }
  }
}

const record = (exports.record = function (keyEncoding, valueEncoding) {
  return {
    preencode(state, v) {
      const keys = Object.keys(v)
      uint.preencode(state, keys.length)
      for (const k of keys) {
        keyEncoding.preencode(state, k)
        valueEncoding.preencode(state, v[k])
      }
    },
    encode(state, v) {
      const keys = Object.keys(v)
      uint.encode(state, keys.length)
      for (const k of keys) {
        keyEncoding.encode(state, k)
        valueEncoding.encode(state, v[k])
      }
    },
    decode(state) {
      const out = Object.create(null)
      const keys = uint.decode(state)
      for (let i = 0; i < keys; i++) {
        out[keyEncoding.decode(state)] = valueEncoding.decode(state)
      }
      return out
    }
  }
})

exports.stringRecord = record(utf8, utf8)

function getType(o) {
  if (o === null || o === undefined) return 0
  if (typeof o === 'boolean') return 1
  if (typeof o === 'string') return 2
  if (b4a.isBuffer(o)) return 3
  if (typeof o === 'number') {
    if (Number.isInteger(o)) return o >= 0 ? 4 : 5
    return 6
  }
  if (Array.isArray(o)) return 7
  if (o instanceof Date) return 9
  if (typeof o === 'object') return 8

  throw new Error('Unsupported type for ' + o)
}

exports.from = function from(enc) {
  if (typeof enc === 'string') return fromNamed(enc)
  if (enc.preencode) return enc
  if (enc.encodingLength) return fromAbstractEncoder(enc)
  return fromCodec(enc)
}

function fromNamed(enc) {
  switch (enc) {
    case 'ascii':
      return raw.ascii
    case 'utf-8':
    case 'utf8':
      return raw.utf8
    case 'hex':
      return raw.hex
    case 'base64':
      return raw.base64
    case 'utf16-le':
    case 'utf16le':
    case 'ucs-2':
    case 'ucs2':
      return raw.ucs2
    case 'ndjson':
      return raw.ndjson
    case 'json':
      return raw.json
    case 'binary':
    default:
      return raw.binary
  }
}

function fromCodec(enc) {
  let tmpM = null
  let tmpBuf = null

  return {
    preencode(state, m) {
      tmpM = m
      tmpBuf = enc.encode(m)
      state.end += tmpBuf.byteLength
    },
    encode(state, m) {
      raw.encode(state, m === tmpM ? tmpBuf : enc.encode(m))
      tmpM = tmpBuf = null
    },
    decode(state) {
      return enc.decode(raw.decode(state))
    }
  }
}

function fromAbstractEncoder(enc) {
  return {
    preencode(state, m) {
      state.end += enc.encodingLength(m)
    },
    encode(state, m) {
      enc.encode(m, state.buffer, state.start)
      state.start += enc.encode.bytes
    },
    decode(state) {
      const m = enc.decode(state.buffer, state.start, state.end)
      state.start += enc.decode.bytes
      return m
    }
  }
}

exports.encode = function encode(enc, m) {
  const state = exports.state()
  enc.preencode(state, m)
  state.buffer = b4a.allocUnsafe(state.end)
  enc.encode(state, m)
  return state.buffer
}

exports.decode = function decode(enc, buffer) {
  return enc.decode(exports.state(0, buffer.byteLength, buffer))
}

function zigZagInt(enc) {
  return {
    preencode(state, n) {
      enc.preencode(state, zigZagEncodeInt(n))
    },
    encode(state, n) {
      enc.encode(state, zigZagEncodeInt(n))
    },
    decode(state) {
      return zigZagDecodeInt(enc.decode(state))
    }
  }
}

function zigZagDecodeInt(n) {
  return n === 0 ? n : (n & 1) === 0 ? n / 2 : -(n + 1) / 2
}

function zigZagEncodeInt(n) {
  // 0, -1, 1, -2, 2, ...
  return n < 0 ? 2 * -n - 1 : n === 0 ? 0 : 2 * n
}

function zigZagBigInt(enc) {
  return {
    preencode(state, n) {
      enc.preencode(state, zigZagEncodeBigInt(n))
    },
    encode(state, n) {
      enc.encode(state, zigZagEncodeBigInt(n))
    },
    decode(state) {
      return zigZagDecodeBigInt(enc.decode(state))
    }
  }
}

function zigZagDecodeBigInt(n) {
  return n === 0n ? n : (n & 1n) === 0n ? n / 2n : -(n + 1n) / 2n
}

function zigZagEncodeBigInt(n) {
  // 0, -1, 1, -2, 2, ...
  return n < 0n ? 2n * -n - 1n : n === 0n ? 0n : 2n * n
}

function validateUint(n) {
  if (n >= 0 === false /* Handles NaN as well */)
    throw new Error('uint must be positive')
}
module.exports = {
  preencode,
  encode,
  decode
}

function preencode(state, num) {
  if (num < 251) {
    state.end++
  } else if (num < 256) {
    state.end += 2
  } else if (num < 0x10000) {
    state.end += 3
  } else if (num < 0x1000000) {
    state.end += 4
  } else if (num < 0x100000000) {
    state.end += 5
  } else {
    state.end++
    const exp = Math.floor(Math.log(num) / Math.log(2)) - 32
    preencode(state, exp)
    state.end += 6
  }
}

function encode(state, num) {
  const max = 251
  const x = num - max

  if (num < max) {
    state.buffer[state.start++] = num
  } else if (num < 256) {
    state.buffer[state.start++] = max
    state.buffer[state.start++] = x
  } else if (num < 0x10000) {
    state.buffer[state.start++] = max + 1
    state.buffer[state.start++] = (x >> 8) & 0xff
    state.buffer[state.start++] = x & 0xff
  } else if (num < 0x1000000) {
    state.buffer[state.start++] = max + 2
    state.buffer[state.start++] = x >> 16
    state.buffer[state.start++] = (x >> 8) & 0xff
    state.buffer[state.start++] = x & 0xff
  } else if (num < 0x100000000) {
    state.buffer[state.start++] = max + 3
    state.buffer[state.start++] = x >> 24
    state.buffer[state.start++] = (x >> 16) & 0xff
    state.buffer[state.start++] = (x >> 8) & 0xff
    state.buffer[state.start++] = x & 0xff
  } else {
    // need to use Math here as bitwise ops are 32 bit
    const exp = Math.floor(Math.log(x) / Math.log(2)) - 32
    state.buffer[state.start++] = 0xff

    encode(state, exp)
    const rem = x / Math.pow(2, exp - 11)

    for (let i = 5; i >= 0; i--) {
      state.buffer[state.start++] = (rem / Math.pow(2, 8 * i)) & 0xff
    }
  }
}

function decode(state) {
  const max = 251

  if (state.end - state.start < 1) throw new Error('Out of bounds')

  const flag = state.buffer[state.start++]

  if (flag < max) return flag

  if (state.end - state.start < flag - max + 1) {
    throw new Error('Out of bounds.')
  }

  if (flag < 252) {
    return state.buffer[state.start++] + max
  }

  if (flag < 253) {
    return (
      (state.buffer[state.start++] << 8) + state.buffer[state.start++] + max
    )
  }

  if (flag < 254) {
    return (
      (state.buffer[state.start++] << 16) +
      (state.buffer[state.start++] << 8) +
      state.buffer[state.start++] +
      max
    )
  }

  // << 24 result may be interpreted as negative
  if (flag < 255) {
    return (
      state.buffer[state.start++] * 0x1000000 +
      (state.buffer[state.start++] << 16) +
      (state.buffer[state.start++] << 8) +
      state.buffer[state.start++] +
      max
    )
  }

  const exp = decode(state)

  if (state.end - state.start < 6) throw new Error('Out of bounds')

  let rem = 0
  for (let i = 5; i >= 0; i--) {
    rem += state.buffer[state.start++] * Math.pow(2, 8 * i)
  }

  return rem * Math.pow(2, exp - 11) + max
}
{
  "name": "compact-encoding",
  "version": "2.19.2",
  "description": "A series of compact encoding schemes for building small and fast parsers and serializers",
  "main": "index.js",
  "dependencies": {
    "b4a": "^1.3.0"
  },
  "devDependencies": {
    "brittle": "^3.0.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^1.0.0"
  },
  "scripts": {
    "format": "prettier . --write",
    "test": "prettier . --check && brittle test.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/holepunchto/compact-encoding.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/compact-encoding/issues"
  },
  "homepage": "https://github.com/holepunchto/compact-encoding"
}
const b4a = require('b4a')

const { BE } = require('./endian')

exports = module.exports = {
  preencode(state, b) {
    state.end += b.byteLength
  },
  encode(state, b) {
    state.buffer.set(b, state.start)
    state.start += b.byteLength
  },
  decode(state) {
    const b = state.buffer.subarray(state.start, state.end)
    state.start = state.end
    return b
  }
}

const buffer = (exports.buffer = {
  preencode(state, b) {
    if (b) uint8array.preencode(state, b)
    else state.end++
  },
  encode(state, b) {
    if (b) uint8array.encode(state, b)
    else state.buffer[state.start++] = 0
  },
  decode(state) {
    const b = state.buffer.subarray(state.start)
    if (b.byteLength === 0) return null
    state.start = state.end
    return b
  }
})

exports.binary = {
  ...buffer,
  preencode(state, b) {
    if (typeof b === 'string') utf8.preencode(state, b)
    else buffer.preencode(state, b)
  },
  encode(state, b) {
    if (typeof b === 'string') utf8.encode(state, b)
    else buffer.encode(state, b)
  }
}

exports.arraybuffer = {
  preencode(state, b) {
    state.end += b.byteLength
  },
  encode(state, b) {
    const view = new Uint8Array(b)

    state.buffer.set(view, state.start)
    state.start += b.byteLength
  },
  decode(state) {
    const b = new ArrayBuffer(state.end - state.start)
    const view = new Uint8Array(b)

    view.set(state.buffer.subarray(state.start))

    state.start = state.end

    return b
  }
}

function typedarray(TypedArray, swap) {
  const n = TypedArray.BYTES_PER_ELEMENT

  return {
    preencode(state, b) {
      state.end += b.byteLength
    },
    encode(state, b) {
      const view = new Uint8Array(b.buffer, b.byteOffset, b.byteLength)

      if (BE && swap) swap(view)

      state.buffer.set(view, state.start)
      state.start += b.byteLength
    },
    decode(state) {
      let b = state.buffer.subarray(state.start)
      if (b.byteOffset % n !== 0) b = new Uint8Array(b)

      if (BE && swap) swap(b)

      state.start = state.end

      return new TypedArray(b.buffer, b.byteOffset, b.byteLength / n)
    }
  }
}

const uint8array = (exports.uint8array = typedarray(Uint8Array))
exports.uint16array = typedarray(Uint16Array, b4a.swap16)
exports.uint32array = typedarray(Uint32Array, b4a.swap32)

exports.int8array = typedarray(Int8Array)
exports.int16array = typedarray(Int16Array, b4a.swap16)
exports.int32array = typedarray(Int32Array, b4a.swap32)

exports.biguint64array = typedarray(BigUint64Array, b4a.swap64)
exports.bigint64array = typedarray(BigInt64Array, b4a.swap64)

exports.float32array = typedarray(Float32Array, b4a.swap32)
exports.float64array = typedarray(Float64Array, b4a.swap64)

function string(encoding) {
  return {
    preencode(state, s) {
      state.end += b4a.byteLength(s, encoding)
    },
    encode(state, s) {
      state.start += b4a.write(state.buffer, s, state.start, encoding)
    },
    decode(state) {
      const s = b4a.toString(state.buffer, encoding, state.start)
      state.start = state.end
      return s
    }
  }
}

const utf8 = (exports.string = exports.utf8 = string('utf-8'))
exports.ascii = string('ascii')
exports.hex = string('hex')
exports.base64 = string('base64')
exports.ucs2 = exports.utf16le = string('utf16le')

exports.array = function array(enc) {
  return {
    preencode(state, list) {
      for (const value of list) enc.preencode(state, value)
    },
    encode(state, list) {
      for (const value of list) enc.encode(state, value)
    },
    decode(state) {
      const arr = []
      while (state.start < state.end) arr.push(enc.decode(state))
      return arr
    }
  }
}

exports.json = {
  preencode(state, v) {
    utf8.preencode(state, JSON.stringify(v))
  },
  encode(state, v) {
    utf8.encode(state, JSON.stringify(v))
  },
  decode(state) {
    return JSON.parse(utf8.decode(state))
  }
}

exports.ndjson = {
  preencode(state, v) {
    utf8.preencode(state, JSON.stringify(v) + '\n')
  },
  encode(state, v) {
    utf8.encode(state, JSON.stringify(v) + '\n')
  },
  decode(state) {
    return JSON.parse(utf8.decode(state))
  }
}
{
  "name": "bogon",
  "version": "1.2.0",
  "description": "Check if an IP is a bogon",
  "main": "index.js",
  "scripts": {
    "test": "standard && brittle test.mjs"
  },
  "dependencies": {
    "compact-encoding": "^2.11.0",
    "compact-encoding-net": "^1.2.0"
  },
  "devDependencies": {
    "brittle": "^3.0.4",
    "nanobench": "^2.1.1",
    "standard": "^17.0.0"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/mafintosh/bogon.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/mafintosh/bogon/issues"
  },
  "homepage": "https://github.com/mafintosh/bogon"
}
const c = require('compact-encoding')

module.exports = function bitfield (length) {
  if (length > 64) throw new RangeError('Bitfield cannot be larger than 64 bits')

  let byteLength
  if (length < 8) byteLength = 1
  else if (length <= 16) byteLength = 2
  else if (length <= 32) byteLength = 4
  else byteLength = 8

  return {
    preencode (state) {
      state.end++ // Length byte, used for data when byteLength === 1

      if (byteLength === 1) ;
      else if (byteLength === 2) c.uint16.preencode(state)
      else if (byteLength === 4) c.uint32.preencode(state)
      else c.uint64.preencode(state)
    },

    encode (state, b) {
      if (byteLength === 1) ;
      else if (byteLength === 2) c.uint8.encode(state, 0xfd)
      else if (byteLength === 4) c.uint8.encode(state, 0xfe)
      else c.uint8.encode(state, 0xff)

      if (typeof b === 'number') {
        if (byteLength === 1) c.uint8.encode(state, b)
        else if (byteLength === 2) c.uint16.encode(state, b)
        else if (byteLength === 4) c.uint32.encode(state, b)
        else c.uint64.encode(state, b)
      } else {
        state.buffer.set(b, state.start)

        if (b.byteLength < byteLength) {
          // Zero-fill the rest of the byte length.
          state.buffer.fill(
            0,
            state.start + b.byteLength,
            state.start + byteLength
          )
        }

        state.start += byteLength
      }
    },

    decode (state) {
      const byte = state.buffer[state.start]

      let byteLength
      if (byte <= 0xfc) byteLength = 1
      else if (byte === 0xfd) byteLength = 2
      else if (byte === 0xfe) byteLength = 4
      else byteLength = 8

      if (byteLength > 1) state.start++ // Skip the length byte

      if (state.end - state.start < byteLength) throw new Error('Out of bounds')

      const b = state.buffer.subarray(state.start, (state.start += byteLength))

      return length <= 8 ? b.subarray(0, 1) : b
    }
  }
}
const LE = (exports.LE =
  new Uint8Array(new Uint16Array([0xff]).buffer)[0] === 0xff)

exports.BE = !LE
const b4a = require('b4a')

const { BE } = require('./endian')

exports.state = function (start = 0, end = 0, buffer = null) {
  return { start, end, buffer }
}

const raw = (exports.raw = require('./raw'))

const uint = (exports.uint = {
  preencode(state, n) {
    state.end += n <= 0xfc ? 1 : n <= 0xffff ? 3 : n <= 0xffffffff ? 5 : 9
  },
  encode(state, n) {
    if (n <= 0xfc) uint8.encode(state, n)
    else if (n <= 0xffff) {
      state.buffer[state.start++] = 0xfd
      uint16.encode(state, n)
    } else if (n <= 0xffffffff) {
      state.buffer[state.start++] = 0xfe
      uint32.encode(state, n)
    } else {
      state.buffer[state.start++] = 0xff
      uint64.encode(state, n)
    }
  },
  decode(state) {
    const a = uint8.decode(state)
    if (a <= 0xfc) return a
    if (a === 0xfd) return uint16.decode(state)
    if (a === 0xfe) return uint32.decode(state)
    return uint64.decode(state)
  }
})

const uint8 = (exports.uint8 = {
  preencode(state, n) {
    state.end += 1
  },
  encode(state, n) {
    validateUint(n)
    state.buffer[state.start++] = n
  },
  decode(state) {
    if (state.start >= state.end) throw new Error('Out of bounds')
    return state.buffer[state.start++]
  }
})

const uint16 = (exports.uint16 = {
  preencode(state, n) {
    state.end += 2
  },
  encode(state, n) {
    validateUint(n)
    state.buffer[state.start++] = n
    state.buffer[state.start++] = n >>> 8
  },
  decode(state) {
    if (state.end - state.start < 2) throw new Error('Out of bounds')
    return state.buffer[state.start++] + state.buffer[state.start++] * 0x100
  }
})

const uint24 = (exports.uint24 = {
  preencode(state, n) {
    state.end += 3
  },
  encode(state, n) {
    validateUint(n)
    state.buffer[state.start++] = n
    state.buffer[state.start++] = n >>> 8
    state.buffer[state.start++] = n >>> 16
  },
  decode(state) {
    if (state.end - state.start < 3) throw new Error('Out of bounds')
    return (
      state.buffer[state.start++] +
      state.buffer[state.start++] * 0x100 +
      state.buffer[state.start++] * 0x10000
    )
  }
})

const uint32 = (exports.uint32 = {
  preencode(state, n) {
    state.end += 4
  },
  encode(state, n) {
    validateUint(n)
    state.buffer[state.start++] = n
    state.buffer[state.start++] = n >>> 8
    state.buffer[state.start++] = n >>> 16
    state.buffer[state.start++] = n >>> 24
  },
  decode(state) {
    if (state.end - state.start < 4) throw new Error('Out of bounds')
    return (
      state.buffer[state.start++] +
      state.buffer[state.start++] * 0x100 +
      state.buffer[state.start++] * 0x10000 +
      state.buffer[state.start++] * 0x1000000
    )
  }
})

const uint40 = (exports.uint40 = {
  preencode(state, n) {
    state.end += 5
  },
  encode(state, n) {
    validateUint(n)
    const r = Math.floor(n / 0x100)
    uint8.encode(state, n)
    uint32.encode(state, r)
  },
  decode(state) {
    if (state.end - state.start < 5) throw new Error('Out of bounds')
    return uint8.decode(state) + 0x100 * uint32.decode(state)
  }
})

const uint48 = (exports.uint48 = {
  preencode(state, n) {
    state.end += 6
  },
  encode(state, n) {
    validateUint(n)
    const r = Math.floor(n / 0x10000)
    uint16.encode(state, n)
    uint32.encode(state, r)
  },
  decode(state) {
    if (state.end - state.start < 6) throw new Error('Out of bounds')
    return uint16.decode(state) + 0x10000 * uint32.decode(state)
  }
})

const uint56 = (exports.uint56 = {
  preencode(state, n) {
    state.end += 7
  },
  encode(state, n) {
    validateUint(n)
    const r = Math.floor(n / 0x1000000)
    uint24.encode(state, n)
    uint32.encode(state, r)
  },
  decode(state) {
    if (state.end - state.start < 7) throw new Error('Out of bounds')
    return uint24.decode(state) + 0x1000000 * uint32.decode(state)
  }
})

const uint64 = (exports.uint64 = {
  preencode(state, n) {
    state.end += 8
  },
  encode(state, n) {
    validateUint(n)
    const r = Math.floor(n / 0x100000000)
    uint32.encode(state, n)
    uint32.encode(state, r)
  },
  decode(state) {
    if (state.end - state.start < 8) throw new Error('Out of bounds')
    return uint32.decode(state) + 0x100000000 * uint32.decode(state)
  }
})

const int = (exports.int = zigZagInt(uint))
exports.int8 = zigZagInt(uint8)
exports.int16 = zigZagInt(uint16)
exports.int24 = zigZagInt(uint24)
exports.int32 = zigZagInt(uint32)
exports.int40 = zigZagInt(uint40)
exports.int48 = zigZagInt(uint48)
exports.int56 = zigZagInt(uint56)
exports.int64 = zigZagInt(uint64)

const biguint64 = (exports.biguint64 = {
  preencode(state, n) {
    state.end += 8
  },
  encode(state, n) {
    const view = new DataView(
      state.buffer.buffer,
      state.start + state.buffer.byteOffset,
      8
    )
    view.setBigUint64(0, n, true) // little endian
    state.start += 8
  },
  decode(state) {
    if (state.end - state.start < 8) throw new Error('Out of bounds')
    const view = new DataView(
      state.buffer.buffer,
      state.start + state.buffer.byteOffset,
      8
    )
    const n = view.getBigUint64(0, true) // little endian
    state.start += 8
    return n
  }
})

exports.bigint64 = zigZagBigInt(biguint64)

const biguint = (exports.biguint = {
  preencode(state, n) {
    let len = 0
    for (let m = n; m; m = m >> 64n) len++
    uint.preencode(state, len)
    state.end += 8 * len
  },
  encode(state, n) {
    let len = 0
    for (let m = n; m; m = m >> 64n) len++
    uint.encode(state, len)
    const view = new DataView(
      state.buffer.buffer,
      state.start + state.buffer.byteOffset,
      8 * len
    )
    for (let m = n, i = 0; m; m = m >> 64n, i += 8) {
      view.setBigUint64(i, BigInt.asUintN(64, m), true) // little endian
    }
    state.start += 8 * len
  },
  decode(state) {
    const len = uint.decode(state)
    if (state.end - state.start < 8 * len) throw new Error('Out of bounds')
    const view = new DataView(
      state.buffer.buffer,
      state.start + state.buffer.byteOffset,
      8 * len
    )
    let n = 0n
    for (let i = len - 1; i >= 0; i--)
      n = (n << 64n) + view.getBigUint64(i * 8, true) // little endian
    state.start += 8 * len
    return n
  }
})

exports.bigint = zigZagBigInt(biguint)

exports.lexint = require('./lexint')

exports.float32 = {
  preencode(state, n) {
    state.end += 4
  },
  encode(state, n) {
    const view = new DataView(
      state.buffer.buffer,
      state.start + state.buffer.byteOffset,
      4
    )
    view.setFloat32(0, n, true) // little endian
    state.start += 4
  },
  decode(state) {
    if (state.end - state.start < 4) throw new Error('Out of bounds')
    const view = new DataView(
      state.buffer.buffer,
      state.start + state.buffer.byteOffset,
      4
    )
    const float = view.getFloat32(0, true) // little endian
    state.start += 4
    return float
  }
}

exports.float64 = {
  preencode(state, n) {
    state.end += 8
  },
  encode(state, n) {
    const view = new DataView(
      state.buffer.buffer,
      state.start + state.buffer.byteOffset,
      8
    )
    view.setFloat64(0, n, true) // little endian
    state.start += 8
  },
  decode(state) {
    if (state.end - state.start < 8) throw new Error('Out of bounds')
    const view = new DataView(
      state.buffer.buffer,
      state.start + state.buffer.byteOffset,
      8
    )
    const float = view.getFloat64(0, true) // little endian
    state.start += 8
    return float
  }
}

const buffer = (exports.buffer = {
  preencode(state, b) {
    if (b) uint8array.preencode(state, b)
    else state.end++
  },
  encode(state, b) {
    if (b) uint8array.encode(state, b)
    else state.buffer[state.start++] = 0
  },
  decode(state) {
    const len = uint.decode(state)
    if (len === 0) return null
    if (state.end - state.start < len) throw new Error('Out of bounds')
    return state.buffer.subarray(state.start, (state.start += len))
  }
})

exports.binary = {
  ...buffer,
  preencode(state, b) {
    if (typeof b === 'string') utf8.preencode(state, b)
    else buffer.preencode(state, b)
  },
  encode(state, b) {
    if (typeof b === 'string') utf8.encode(state, b)
    else buffer.encode(state, b)
  }
}

exports.arraybuffer = {
  preencode(state, b) {
    uint.preencode(state, b.byteLength)
    state.end += b.byteLength
  },
  encode(state, b) {
    uint.encode(state, b.byteLength)

    const view = new Uint8Array(b)

    state.buffer.set(view, state.start)
    state.start += b.byteLength
  },
  decode(state) {
    const len = uint.decode(state)

    const b = new ArrayBuffer(len)
    const view = new Uint8Array(b)

    view.set(state.buffer.subarray(state.start, (state.start += len)))

    return b
  }
}

function typedarray(TypedArray, swap) {
  const n = TypedArray.BYTES_PER_ELEMENT

  return {
    preencode(state, b) {
      uint.preencode(state, b.length)
      state.end += b.byteLength
    },
    encode(state, b) {
      uint.encode(state, b.length)

      const view = new Uint8Array(b.buffer, b.byteOffset, b.byteLength)

      if (BE && swap) swap(view)

      state.buffer.set(view, state.start)
      state.start += b.byteLength
    },
    decode(state) {
      const len = uint.decode(state)

      let b = state.buffer.subarray(state.start, (state.start += len * n))
      if (b.byteLength !== len * n) throw new Error('Out of bounds')
      if (b.byteOffset % n !== 0) b = new Uint8Array(b)

      if (BE && swap) swap(b)

      return new TypedArray(b.buffer, b.byteOffset, b.byteLength / n)
    }
  }
}

const uint8array = (exports.uint8array = typedarray(Uint8Array))
exports.uint16array = typedarray(Uint16Array, b4a.swap16)
exports.uint32array = typedarray(Uint32Array, b4a.swap32)

exports.int8array = typedarray(Int8Array)
exports.int16array = typedarray(Int16Array, b4a.swap16)
exports.int32array = typedarray(Int32Array, b4a.swap32)

exports.biguint64array = typedarray(BigUint64Array, b4a.swap64)
exports.bigint64array = typedarray(BigInt64Array, b4a.swap64)

exports.float32array = typedarray(Float32Array, b4a.swap32)
exports.float64array = typedarray(Float64Array, b4a.swap64)

function string(encoding) {
  return {
    preencode(state, s) {
      const len = b4a.byteLength(s, encoding)
      uint.preencode(state, len)
      state.end += len
    },
    encode(state, s) {
      const len = b4a.byteLength(s, encoding)
      uint.encode(state, len)
      b4a.write(state.buffer, s, state.start, encoding)
      state.start += len
    },
    decode(state) {
      const len = uint.decode(state)
      if (state.end - state.start < len) throw new Error('Out of bounds')
      return b4a.toString(
        state.buffer,
        encoding,
        state.start,
        (state.start += len)
      )
    },
    fixed(n) {
      return {
        preencode(state) {
          state.end += n
        },
        encode(state, s) {
          b4a.write(state.buffer, s, state.start, n, encoding)
          state.start += n
        },
        decode(state) {
          if (state.end - state.start < n) throw new Error('Out of bounds')
          return b4a.toString(
            state.buffer,
            encoding,
            state.start,
            (state.start += n)
          )
        }
      }
    }
  }
}

const utf8 = (exports.string = exports.utf8 = string('utf-8'))
exports.ascii = string('ascii')
exports.hex = string('hex')
exports.base64 = string('base64')
exports.ucs2 = exports.utf16le = string('utf16le')

exports.bool = {
  preencode(state, b) {
    state.end++
  },
  encode(state, b) {
    state.buffer[state.start++] = b ? 1 : 0
  },
  decode(state) {
    if (state.start >= state.end) throw Error('Out of bounds')
    return state.buffer[state.start++] === 1
  }
}

const fixed = (exports.fixed = function fixed(n) {
  return {
    preencode(state, s) {
      if (s.byteLength !== n) throw new Error('Incorrect buffer size')
      state.end += n
    },
    encode(state, s) {
      state.buffer.set(s, state.start)
      state.start += n
    },
    decode(state) {
      if (state.end - state.start < n) throw new Error('Out of bounds')
      return state.buffer.subarray(state.start, (state.start += n))
    }
  }
})

exports.fixed32 = fixed(32)
exports.fixed64 = fixed(64)

exports.array = function array(enc) {
  return {
    preencode(state, list) {
      uint.preencode(state, list.length)
      for (let i = 0; i < list.length; i++) enc.preencode(state, list[i])
    },
    encode(state, list) {
      uint.encode(state, list.length)
      for (let i = 0; i < list.length; i++) enc.encode(state, list[i])
    },
    decode(state) {
      const len = uint.decode(state)
      if (len > 0x100000) throw new Error('Array is too big')
      const arr = new Array(len)
      for (let i = 0; i < len; i++) arr[i] = enc.decode(state)
      return arr
    }
  }
}

exports.frame = function frame(enc) {
  const dummy = exports.state()

  return {
    preencode(state, m) {
      const end = state.end
      enc.preencode(state, m)
      uint.preencode(state, state.end - end)
    },
    encode(state, m) {
      dummy.end = 0
      enc.preencode(dummy, m)
      uint.encode(state, dummy.end)
      enc.encode(state, m)
    },
    decode(state) {
      const end = state.end
      const len = uint.decode(state)
      state.end = state.start + len
      const m = enc.decode(state)
      state.start = state.end
      state.end = end
      return m
    }
  }
}

exports.date = {
  preencode(state, d) {
    int.preencode(state, d.getTime())
  },
  encode(state, d) {
    int.encode(state, d.getTime())
  },
  decode(state, d) {
    return new Date(int.decode(state))
  }
}

exports.json = {
  preencode(state, v) {
    utf8.preencode(state, JSON.stringify(v))
  },
  encode(state, v) {
    utf8.encode(state, JSON.stringify(v))
  },
  decode(state) {
    return JSON.parse(utf8.decode(state))
  }
}

exports.ndjson = {
  preencode(state, v) {
    utf8.preencode(state, JSON.stringify(v) + '\n')
  },
  encode(state, v) {
    utf8.encode(state, JSON.stringify(v) + '\n')
  },
  decode(state) {
    return JSON.parse(utf8.decode(state))
  }
}

// simple helper for when you want to just express nothing
exports.none = {
  preencode(state, n) {
    // do nothing
  },
  encode(state, n) {
    // do nothing
  },
  decode(state) {
    return null
  }
}

// "any" encoders here for helping just structure any object without schematising it

const anyArray = {
  preencode(state, arr) {
    uint.preencode(state, arr.length)
    for (let i = 0; i < arr.length; i++) {
      any.preencode(state, arr[i])
    }
  },
  encode(state, arr) {
    uint.encode(state, arr.length)
    for (let i = 0; i < arr.length; i++) {
      any.encode(state, arr[i])
    }
  },
  decode(state) {
    const arr = []
    let len = uint.decode(state)
    while (len-- > 0) {
      arr.push(any.decode(state))
    }
    return arr
  }
}

const anyObject = {
  preencode(state, o) {
    const keys = Object.keys(o)
    uint.preencode(state, keys.length)
    for (const key of keys) {
      utf8.preencode(state, key)
      any.preencode(state, o[key])
    }
  },
  encode(state, o) {
    const keys = Object.keys(o)
    uint.encode(state, keys.length)
    for (const key of keys) {
      utf8.encode(state, key)
      any.encode(state, o[key])
    }
  },
  decode(state) {
    let len = uint.decode(state)
    const o = {}
    while (len-- > 0) {
      const key = utf8.decode(state)
      o[key] = any.decode(state)
    }
    return o
  }
}

const anyTypes = [
  exports.none,
  exports.bool,
  exports.string,
  exports.buffer,
  exports.uint,
  exports.int,
  exports.float64,
  anyArray,
  anyObject,
  exports.date
]

const any = (exports.any = {
  preencode(state, o) {
    const t = getType(o)
    uint.preencode(state, t)
    anyTypes[t].preencode(state, o)
  },
  encode(state, o) {
    const t = getType(o)
    uint.encode(state, t)
    anyTypes[t].encode(state, o)
  },
  decode(state) {
    const t = uint.decode(state)
    if (t >= anyTypes.length) throw new Error('Unknown type: ' + t)
    return anyTypes[t].decode(state)
  }
})

const port = (exports.port = uint16)

const address = (host, family) => {
  return {
    preencode(state, m) {
      host.preencode(state, m.host)
      port.preencode(state, m.port)
    },
    encode(state, m) {
      host.encode(state, m.host)
      port.encode(state, m.port)
    },
    decode(state) {
      return {
        host: host.decode(state),
        family,
        port: port.decode(state)
      }
    }
  }
}

const ipv4 = (exports.ipv4 = {
  preencode(state) {
    state.end += 4
  },
  encode(state, string) {
    const start = state.start
    const end = start + 4

    let i = 0

    while (i < string.length) {
      let n = 0
      let c

      while (
        i < string.length &&
        (c = string.charCodeAt(i++)) !== /* . */ 0x2e
      ) {
        n = n * 10 + (c - /* 0 */ 0x30)
      }

      state.buffer[state.start++] = n
    }

    state.start = end
  },
  decode(state) {
    if (state.end - state.start < 4) throw new Error('Out of bounds')
    return (
      state.buffer[state.start++] +
      '.' +
      state.buffer[state.start++] +
      '.' +
      state.buffer[state.start++] +
      '.' +
      state.buffer[state.start++]
    )
  }
})

exports.ipv4Address = address(ipv4, 4)

const ipv6 = (exports.ipv6 = {
  preencode(state) {
    state.end += 16
  },
  encode(state, string) {
    const start = state.start
    const end = start + 16

    let i = 0
    let split = null

    while (i < string.length) {
      let n = 0
      let c

      while (
        i < string.length &&
        (c = string.charCodeAt(i++)) !== /* : */ 0x3a
      ) {
        if (c >= 0x30 && c <= 0x39) n = n * 0x10 + (c - /* 0 */ 0x30)
        else if (c >= 0x41 && c <= 0x46) n = n * 0x10 + (c - /* A */ 0x41 + 10)
        else if (c >= 0x61 && c <= 0x66) n = n * 0x10 + (c - /* a */ 0x61 + 10)
      }

      state.buffer[state.start++] = n >>> 8
      state.buffer[state.start++] = n

      if (i < string.length && string.charCodeAt(i) === /* : */ 0x3a) {
        i++
        split = state.start
      }
    }

    if (split !== null) {
      const offset = end - state.start
      state.buffer
        .copyWithin(split + offset, split)
        .fill(0, split, split + offset)
    }

    state.start = end
  },
  decode(state) {
    if (state.end - state.start < 16) throw new Error('Out of bounds')
    return (
      (
        state.buffer[state.start++] * 256 +
        state.buffer[state.start++]
      ).toString(16) +
      ':' +
      (
        state.buffer[state.start++] * 256 +
        state.buffer[state.start++]
      ).toString(16) +
      ':' +
      (
        state.buffer[state.start++] * 256 +
        state.buffer[state.start++]
      ).toString(16) +
      ':' +
      (
        state.buffer[state.start++] * 256 +
        state.buffer[state.start++]
      ).toString(16) +
      ':' +
      (
        state.buffer[state.start++] * 256 +
        state.buffer[state.start++]
      ).toString(16) +
      ':' +
      (
        state.buffer[state.start++] * 256 +
        state.buffer[state.start++]
      ).toString(16) +
      ':' +
      (
        state.buffer[state.start++] * 256 +
        state.buffer[state.start++]
      ).toString(16) +
      ':' +
      (
        state.buffer[state.start++] * 256 +
        state.buffer[state.start++]
      ).toString(16)
    )
  }
})

exports.ipv6Address = address(ipv6, 6)

const ip = (exports.ip = {
  preencode(state, string) {
    const family = string.includes(':') ? 6 : 4
    uint8.preencode(state, family)
    if (family === 4) ipv4.preencode(state)
    else ipv6.preencode(state)
  },
  encode(state, string) {
    const family = string.includes(':') ? 6 : 4
    uint8.encode(state, family)
    if (family === 4) ipv4.encode(state, string)
    else ipv6.encode(state, string)
  },
  decode(state) {
    const family = uint8.decode(state)
    if (family === 4) return ipv4.decode(state)
    else return ipv6.decode(state)
  }
})

exports.ipAddress = {
  preencode(state, m) {
    ip.preencode(state, m.host)
    port.preencode(state, m.port)
  },
  encode(state, m) {
    ip.encode(state, m.host)
    port.encode(state, m.port)
  },
  decode(state) {
    const family = uint8.decode(state)
    return {
      host: family === 4 ? ipv4.decode(state) : ipv6.decode(state),
      family,
      port: port.decode(state)
    }
  }
}

const record = (exports.record = function (keyEncoding, valueEncoding) {
  return {
    preencode(state, v) {
      const keys = Object.keys(v)
      uint.preencode(state, keys.length)
      for (const k of keys) {
        keyEncoding.preencode(state, k)
        valueEncoding.preencode(state, v[k])
      }
    },
    encode(state, v) {
      const keys = Object.keys(v)
      uint.encode(state, keys.length)
      for (const k of keys) {
        keyEncoding.encode(state, k)
        valueEncoding.encode(state, v[k])
      }
    },
    decode(state) {
      const out = Object.create(null)
      const keys = uint.decode(state)
      for (let i = 0; i < keys; i++) {
        out[keyEncoding.decode(state)] = valueEncoding.decode(state)
      }
      return out
    }
  }
})

exports.stringRecord = record(utf8, utf8)

function getType(o) {
  if (o === null || o === undefined) return 0
  if (typeof o === 'boolean') return 1
  if (typeof o === 'string') return 2
  if (b4a.isBuffer(o)) return 3
  if (typeof o === 'number') {
    if (Number.isInteger(o)) return o >= 0 ? 4 : 5
    return 6
  }
  if (Array.isArray(o)) return 7
  if (o instanceof Date) return 9
  if (typeof o === 'object') return 8

  throw new Error('Unsupported type for ' + o)
}

exports.from = function from(enc) {
  if (typeof enc === 'string') return fromNamed(enc)
  if (enc.preencode) return enc
  if (enc.encodingLength) return fromAbstractEncoder(enc)
  return fromCodec(enc)
}

function fromNamed(enc) {
  switch (enc) {
    case 'ascii':
      return raw.ascii
    case 'utf-8':
    case 'utf8':
      return raw.utf8
    case 'hex':
      return raw.hex
    case 'base64':
      return raw.base64
    case 'utf16-le':
    case 'utf16le':
    case 'ucs-2':
    case 'ucs2':
      return raw.ucs2
    case 'ndjson':
      return raw.ndjson
    case 'json':
      return raw.json
    case 'binary':
    default:
      return raw.binary
  }
}

function fromCodec(enc) {
  let tmpM = null
  let tmpBuf = null

  return {
    preencode(state, m) {
      tmpM = m
      tmpBuf = enc.encode(m)
      state.end += tmpBuf.byteLength
    },
    encode(state, m) {
      raw.encode(state, m === tmpM ? tmpBuf : enc.encode(m))
      tmpM = tmpBuf = null
    },
    decode(state) {
      return enc.decode(raw.decode(state))
    }
  }
}

function fromAbstractEncoder(enc) {
  return {
    preencode(state, m) {
      state.end += enc.encodingLength(m)
    },
    encode(state, m) {
      enc.encode(m, state.buffer, state.start)
      state.start += enc.encode.bytes
    },
    decode(state) {
      const m = enc.decode(state.buffer, state.start, state.end)
      state.start += enc.decode.bytes
      return m
    }
  }
}

exports.encode = function encode(enc, m) {
  const state = exports.state()
  enc.preencode(state, m)
  state.buffer = b4a.allocUnsafe(state.end)
  enc.encode(state, m)
  return state.buffer
}

exports.decode = function decode(enc, buffer) {
  return enc.decode(exports.state(0, buffer.byteLength, buffer))
}

function zigZagInt(enc) {
  return {
    preencode(state, n) {
      enc.preencode(state, zigZagEncodeInt(n))
    },
    encode(state, n) {
      enc.encode(state, zigZagEncodeInt(n))
    },
    decode(state) {
      return zigZagDecodeInt(enc.decode(state))
    }
  }
}

function zigZagDecodeInt(n) {
  return n === 0 ? n : (n & 1) === 0 ? n / 2 : -(n + 1) / 2
}

function zigZagEncodeInt(n) {
  // 0, -1, 1, -2, 2, ...
  return n < 0 ? 2 * -n - 1 : n === 0 ? 0 : 2 * n
}

function zigZagBigInt(enc) {
  return {
    preencode(state, n) {
      enc.preencode(state, zigZagEncodeBigInt(n))
    },
    encode(state, n) {
      enc.encode(state, zigZagEncodeBigInt(n))
    },
    decode(state) {
      return zigZagDecodeBigInt(enc.decode(state))
    }
  }
}

function zigZagDecodeBigInt(n) {
  return n === 0n ? n : (n & 1n) === 0n ? n / 2n : -(n + 1n) / 2n
}

function zigZagEncodeBigInt(n) {
  // 0, -1, 1, -2, 2, ...
  return n < 0n ? 2n * -n - 1n : n === 0n ? 0n : 2n * n
}

function validateUint(n) {
  if (n >= 0 === false /* Handles NaN as well */)
    throw new Error('uint must be positive')
}
module.exports = {
  preencode,
  encode,
  decode
}

function preencode(state, num) {
  if (num < 251) {
    state.end++
  } else if (num < 256) {
    state.end += 2
  } else if (num < 0x10000) {
    state.end += 3
  } else if (num < 0x1000000) {
    state.end += 4
  } else if (num < 0x100000000) {
    state.end += 5
  } else {
    state.end++
    const exp = Math.floor(Math.log(num) / Math.log(2)) - 32
    preencode(state, exp)
    state.end += 6
  }
}

function encode(state, num) {
  const max = 251
  const x = num - max

  if (num < max) {
    state.buffer[state.start++] = num
  } else if (num < 256) {
    state.buffer[state.start++] = max
    state.buffer[state.start++] = x
  } else if (num < 0x10000) {
    state.buffer[state.start++] = max + 1
    state.buffer[state.start++] = (x >> 8) & 0xff
    state.buffer[state.start++] = x & 0xff
  } else if (num < 0x1000000) {
    state.buffer[state.start++] = max + 2
    state.buffer[state.start++] = x >> 16
    state.buffer[state.start++] = (x >> 8) & 0xff
    state.buffer[state.start++] = x & 0xff
  } else if (num < 0x100000000) {
    state.buffer[state.start++] = max + 3
    state.buffer[state.start++] = x >> 24
    state.buffer[state.start++] = (x >> 16) & 0xff
    state.buffer[state.start++] = (x >> 8) & 0xff
    state.buffer[state.start++] = x & 0xff
  } else {
    // need to use Math here as bitwise ops are 32 bit
    const exp = Math.floor(Math.log(x) / Math.log(2)) - 32
    state.buffer[state.start++] = 0xff

    encode(state, exp)
    const rem = x / Math.pow(2, exp - 11)

    for (let i = 5; i >= 0; i--) {
      state.buffer[state.start++] = (rem / Math.pow(2, 8 * i)) & 0xff
    }
  }
}

function decode(state) {
  const max = 251

  if (state.end - state.start < 1) throw new Error('Out of bounds')

  const flag = state.buffer[state.start++]

  if (flag < max) return flag

  if (state.end - state.start < flag - max + 1) {
    throw new Error('Out of bounds.')
  }

  if (flag < 252) {
    return state.buffer[state.start++] + max
  }

  if (flag < 253) {
    return (
      (state.buffer[state.start++] << 8) + state.buffer[state.start++] + max
    )
  }

  if (flag < 254) {
    return (
      (state.buffer[state.start++] << 16) +
      (state.buffer[state.start++] << 8) +
      state.buffer[state.start++] +
      max
    )
  }

  // << 24 result may be interpreted as negative
  if (flag < 255) {
    return (
      state.buffer[state.start++] * 0x1000000 +
      (state.buffer[state.start++] << 16) +
      (state.buffer[state.start++] << 8) +
      state.buffer[state.start++] +
      max
    )
  }

  const exp = decode(state)

  if (state.end - state.start < 6) throw new Error('Out of bounds')

  let rem = 0
  for (let i = 5; i >= 0; i--) {
    rem += state.buffer[state.start++] * Math.pow(2, 8 * i)
  }

  return rem * Math.pow(2, exp - 11) + max
}
{
  "name": "compact-encoding",
  "version": "2.19.2",
  "description": "A series of compact encoding schemes for building small and fast parsers and serializers",
  "main": "index.js",
  "dependencies": {
    "b4a": "^1.3.0"
  },
  "devDependencies": {
    "brittle": "^3.0.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^1.0.0"
  },
  "scripts": {
    "format": "prettier . --write",
    "test": "prettier . --check && brittle test.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/holepunchto/compact-encoding.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/compact-encoding/issues"
  },
  "homepage": "https://github.com/holepunchto/compact-encoding"
}
const b4a = require('b4a')

const { BE } = require('./endian')

exports = module.exports = {
  preencode(state, b) {
    state.end += b.byteLength
  },
  encode(state, b) {
    state.buffer.set(b, state.start)
    state.start += b.byteLength
  },
  decode(state) {
    const b = state.buffer.subarray(state.start, state.end)
    state.start = state.end
    return b
  }
}

const buffer = (exports.buffer = {
  preencode(state, b) {
    if (b) uint8array.preencode(state, b)
    else state.end++
  },
  encode(state, b) {
    if (b) uint8array.encode(state, b)
    else state.buffer[state.start++] = 0
  },
  decode(state) {
    const b = state.buffer.subarray(state.start)
    if (b.byteLength === 0) return null
    state.start = state.end
    return b
  }
})

exports.binary = {
  ...buffer,
  preencode(state, b) {
    if (typeof b === 'string') utf8.preencode(state, b)
    else buffer.preencode(state, b)
  },
  encode(state, b) {
    if (typeof b === 'string') utf8.encode(state, b)
    else buffer.encode(state, b)
  }
}

exports.arraybuffer = {
  preencode(state, b) {
    state.end += b.byteLength
  },
  encode(state, b) {
    const view = new Uint8Array(b)

    state.buffer.set(view, state.start)
    state.start += b.byteLength
  },
  decode(state) {
    const b = new ArrayBuffer(state.end - state.start)
    const view = new Uint8Array(b)

    view.set(state.buffer.subarray(state.start))

    state.start = state.end

    return b
  }
}

function typedarray(TypedArray, swap) {
  const n = TypedArray.BYTES_PER_ELEMENT

  return {
    preencode(state, b) {
      state.end += b.byteLength
    },
    encode(state, b) {
      const view = new Uint8Array(b.buffer, b.byteOffset, b.byteLength)

      if (BE && swap) swap(view)

      state.buffer.set(view, state.start)
      state.start += b.byteLength
    },
    decode(state) {
      let b = state.buffer.subarray(state.start)
      if (b.byteOffset % n !== 0) b = new Uint8Array(b)

      if (BE && swap) swap(b)

      state.start = state.end

      return new TypedArray(b.buffer, b.byteOffset, b.byteLength / n)
    }
  }
}

const uint8array = (exports.uint8array = typedarray(Uint8Array))
exports.uint16array = typedarray(Uint16Array, b4a.swap16)
exports.uint32array = typedarray(Uint32Array, b4a.swap32)

exports.int8array = typedarray(Int8Array)
exports.int16array = typedarray(Int16Array, b4a.swap16)
exports.int32array = typedarray(Int32Array, b4a.swap32)

exports.biguint64array = typedarray(BigUint64Array, b4a.swap64)
exports.bigint64array = typedarray(BigInt64Array, b4a.swap64)

exports.float32array = typedarray(Float32Array, b4a.swap32)
exports.float64array = typedarray(Float64Array, b4a.swap64)

function string(encoding) {
  return {
    preencode(state, s) {
      state.end += b4a.byteLength(s, encoding)
    },
    encode(state, s) {
      state.start += b4a.write(state.buffer, s, state.start, encoding)
    },
    decode(state) {
      const s = b4a.toString(state.buffer, encoding, state.start)
      state.start = state.end
      return s
    }
  }
}

const utf8 = (exports.string = exports.utf8 = string('utf-8'))
exports.ascii = string('ascii')
exports.hex = string('hex')
exports.base64 = string('base64')
exports.ucs2 = exports.utf16le = string('utf16le')

exports.array = function array(enc) {
  return {
    preencode(state, list) {
      for (const value of list) enc.preencode(state, value)
    },
    encode(state, list) {
      for (const value of list) enc.encode(state, value)
    },
    decode(state) {
      const arr = []
      while (state.start < state.end) arr.push(enc.decode(state))
      return arr
    }
  }
}

exports.json = {
  preencode(state, v) {
    utf8.preencode(state, JSON.stringify(v))
  },
  encode(state, v) {
    utf8.encode(state, JSON.stringify(v))
  },
  decode(state) {
    return JSON.parse(utf8.decode(state))
  }
}

exports.ndjson = {
  preencode(state, v) {
    utf8.preencode(state, JSON.stringify(v) + '\n')
  },
  encode(state, v) {
    utf8.encode(state, JSON.stringify(v) + '\n')
  },
  decode(state) {
    return JSON.parse(utf8.decode(state))
  }
}
{
  "name": "compact-encoding-bitfield",
  "version": "1.0.0",
  "description": "Compact codec for bitfields",
  "main": "index.js",
  "files": [
    "index.js"
  ],
  "scripts": {
    "test": "standard && brittle test.mjs"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/compact-encoding/compact-encoding-bitfield.git"
  },
  "author": "Kasper Isager Dalsgarð <kasper@funktionel.co>",
  "license": "ISC",
  "bugs": {
    "url": "https://github.com/compact-encoding/compact-encoding-bitfield/issues"
  },
  "homepage": "https://github.com/compact-encoding/compact-encoding-bitfield#readme",
  "dependencies": {
    "compact-encoding": "^2.4.1"
  },
  "devDependencies": {
    "brittle": "^1.3.5",
    "standard": "^16.0.3"
  },
  "standard": {
    "ignore": [
      "__snapshots__/**"
    ]
  }
}
const c = require('compact-encoding')

const port = c.uint16

const address = (host, family) => {
  return {
    preencode (state, m) {
      host.preencode(state, m.host)
      port.preencode(state, m.port)
    },
    encode (state, m) {
      host.encode(state, m.host)
      port.encode(state, m.port)
    },
    decode (state) {
      return {
        host: host.decode(state),
        family,
        port: port.decode(state)
      }
    }
  }
}

const ipv4 = {
  preencode (state) {
    state.end += 4
  },
  encode (state, string) {
    const start = state.start
    const end = start + 4

    let i = 0

    while (i < string.length) {
      let n = 0
      let c

      while (i < string.length && (c = string.charCodeAt(i++)) !== /* . */ 0x2e) {
        n = n * 10 + (c - /* 0 */ 0x30)
      }

      state.buffer[state.start++] = n
    }

    state.start = end
  },
  decode (state) {
    if (state.end - state.start < 4) throw new Error('Out of bounds')
    return (
      state.buffer[state.start++] + '.' +
      state.buffer[state.start++] + '.' +
      state.buffer[state.start++] + '.' +
      state.buffer[state.start++]
    )
  }
}

const ipv4Address = address(ipv4, 4)

const ipv6 = {
  preencode (state) {
    state.end += 16
  },
  encode (state, string) {
    const start = state.start
    const end = start + 16

    let i = 0
    let split = null

    while (i < string.length) {
      let n = 0
      let c

      while (i < string.length && (c = string.charCodeAt(i++)) !== /* : */ 0x3a) {
        if (c >= 0x30 && c <= 0x39) n = n * 0x10 + (c - /* 0 */ 0x30)
        else if (c >= 0x41 && c <= 0x46) n = n * 0x10 + (c - /* A */ 0x41 + 10)
        else if (c >= 0x61 && c <= 0x66) n = n * 0x10 + (c - /* a */ 0x61 + 10)
      }

      state.buffer[state.start++] = n >>> 8
      state.buffer[state.start++] = n

      if (i < string.length && string.charCodeAt(i) === /* : */ 0x3a) {
        i++
        split = state.start
      }
    }

    if (split !== null) {
      const offset = end - state.start
      state.buffer
        .copyWithin(split + offset, split)
        .fill(0, split, split + offset)
    }

    state.start = end
  },
  decode (state) {
    if (state.end - state.start < 16) throw new Error('Out of bounds')
    return (
      (state.buffer[state.start++] * 256 + state.buffer[state.start++]).toString(16) + ':' +
      (state.buffer[state.start++] * 256 + state.buffer[state.start++]).toString(16) + ':' +
      (state.buffer[state.start++] * 256 + state.buffer[state.start++]).toString(16) + ':' +
      (state.buffer[state.start++] * 256 + state.buffer[state.start++]).toString(16) + ':' +
      (state.buffer[state.start++] * 256 + state.buffer[state.start++]).toString(16) + ':' +
      (state.buffer[state.start++] * 256 + state.buffer[state.start++]).toString(16) + ':' +
      (state.buffer[state.start++] * 256 + state.buffer[state.start++]).toString(16) + ':' +
      (state.buffer[state.start++] * 256 + state.buffer[state.start++]).toString(16)
    )
  }
}

const ipv6Address = address(ipv6, 6)

const ip = {
  preencode (state, string) {
    const family = string.includes(':') ? 6 : 4
    c.uint8.preencode(state, family)
    if (family === 4) ipv4.preencode(state)
    else ipv6.preencode(state)
  },
  encode (state, string) {
    const family = string.includes(':') ? 6 : 4
    c.uint8.encode(state, family)
    if (family === 4) ipv4.encode(state, string)
    else ipv6.encode(state, string)
  },
  decode (state) {
    const family = c.uint8.decode(state)
    if (family === 4) return ipv4.decode(state)
    else return ipv6.decode(state)
  }
}

const ipAddress = {
  preencode (state, m) {
    ip.preencode(state, m.host)
    port.preencode(state, m.port)
  },
  encode (state, m) {
    ip.encode(state, m.host)
    port.encode(state, m.port)
  },
  decode (state) {
    const family = c.uint8.decode(state)
    return {
      host: family === 4 ? ipv4.decode(state) : ipv6.decode(state),
      family,
      port: port.decode(state)
    }
  }
}

module.exports = {
  port,
  ipv4,
  ipv4Address,
  ipv6,
  ipv6Address,
  ip,
  ipAddress
}
const LE = (exports.LE =
  new Uint8Array(new Uint16Array([0xff]).buffer)[0] === 0xff)

exports.BE = !LE
const b4a = require('b4a')

const { BE } = require('./endian')

exports.state = function (start = 0, end = 0, buffer = null) {
  return { start, end, buffer }
}

const raw = (exports.raw = require('./raw'))

const uint = (exports.uint = {
  preencode(state, n) {
    state.end += n <= 0xfc ? 1 : n <= 0xffff ? 3 : n <= 0xffffffff ? 5 : 9
  },
  encode(state, n) {
    if (n <= 0xfc) uint8.encode(state, n)
    else if (n <= 0xffff) {
      state.buffer[state.start++] = 0xfd
      uint16.encode(state, n)
    } else if (n <= 0xffffffff) {
      state.buffer[state.start++] = 0xfe
      uint32.encode(state, n)
    } else {
      state.buffer[state.start++] = 0xff
      uint64.encode(state, n)
    }
  },
  decode(state) {
    const a = uint8.decode(state)
    if (a <= 0xfc) return a
    if (a === 0xfd) return uint16.decode(state)
    if (a === 0xfe) return uint32.decode(state)
    return uint64.decode(state)
  }
})

const uint8 = (exports.uint8 = {
  preencode(state, n) {
    state.end += 1
  },
  encode(state, n) {
    validateUint(n)
    state.buffer[state.start++] = n
  },
  decode(state) {
    if (state.start >= state.end) throw new Error('Out of bounds')
    return state.buffer[state.start++]
  }
})

const uint16 = (exports.uint16 = {
  preencode(state, n) {
    state.end += 2
  },
  encode(state, n) {
    validateUint(n)
    state.buffer[state.start++] = n
    state.buffer[state.start++] = n >>> 8
  },
  decode(state) {
    if (state.end - state.start < 2) throw new Error('Out of bounds')
    return state.buffer[state.start++] + state.buffer[state.start++] * 0x100
  }
})

const uint24 = (exports.uint24 = {
  preencode(state, n) {
    state.end += 3
  },
  encode(state, n) {
    validateUint(n)
    state.buffer[state.start++] = n
    state.buffer[state.start++] = n >>> 8
    state.buffer[state.start++] = n >>> 16
  },
  decode(state) {
    if (state.end - state.start < 3) throw new Error('Out of bounds')
    return (
      state.buffer[state.start++] +
      state.buffer[state.start++] * 0x100 +
      state.buffer[state.start++] * 0x10000
    )
  }
})

const uint32 = (exports.uint32 = {
  preencode(state, n) {
    state.end += 4
  },
  encode(state, n) {
    validateUint(n)
    state.buffer[state.start++] = n
    state.buffer[state.start++] = n >>> 8
    state.buffer[state.start++] = n >>> 16
    state.buffer[state.start++] = n >>> 24
  },
  decode(state) {
    if (state.end - state.start < 4) throw new Error('Out of bounds')
    return (
      state.buffer[state.start++] +
      state.buffer[state.start++] * 0x100 +
      state.buffer[state.start++] * 0x10000 +
      state.buffer[state.start++] * 0x1000000
    )
  }
})

const uint40 = (exports.uint40 = {
  preencode(state, n) {
    state.end += 5
  },
  encode(state, n) {
    validateUint(n)
    const r = Math.floor(n / 0x100)
    uint8.encode(state, n)
    uint32.encode(state, r)
  },
  decode(state) {
    if (state.end - state.start < 5) throw new Error('Out of bounds')
    return uint8.decode(state) + 0x100 * uint32.decode(state)
  }
})

const uint48 = (exports.uint48 = {
  preencode(state, n) {
    state.end += 6
  },
  encode(state, n) {
    validateUint(n)
    const r = Math.floor(n / 0x10000)
    uint16.encode(state, n)
    uint32.encode(state, r)
  },
  decode(state) {
    if (state.end - state.start < 6) throw new Error('Out of bounds')
    return uint16.decode(state) + 0x10000 * uint32.decode(state)
  }
})

const uint56 = (exports.uint56 = {
  preencode(state, n) {
    state.end += 7
  },
  encode(state, n) {
    validateUint(n)
    const r = Math.floor(n / 0x1000000)
    uint24.encode(state, n)
    uint32.encode(state, r)
  },
  decode(state) {
    if (state.end - state.start < 7) throw new Error('Out of bounds')
    return uint24.decode(state) + 0x1000000 * uint32.decode(state)
  }
})

const uint64 = (exports.uint64 = {
  preencode(state, n) {
    state.end += 8
  },
  encode(state, n) {
    validateUint(n)
    const r = Math.floor(n / 0x100000000)
    uint32.encode(state, n)
    uint32.encode(state, r)
  },
  decode(state) {
    if (state.end - state.start < 8) throw new Error('Out of bounds')
    return uint32.decode(state) + 0x100000000 * uint32.decode(state)
  }
})

const int = (exports.int = zigZagInt(uint))
exports.int8 = zigZagInt(uint8)
exports.int16 = zigZagInt(uint16)
exports.int24 = zigZagInt(uint24)
exports.int32 = zigZagInt(uint32)
exports.int40 = zigZagInt(uint40)
exports.int48 = zigZagInt(uint48)
exports.int56 = zigZagInt(uint56)
exports.int64 = zigZagInt(uint64)

const biguint64 = (exports.biguint64 = {
  preencode(state, n) {
    state.end += 8
  },
  encode(state, n) {
    const view = new DataView(
      state.buffer.buffer,
      state.start + state.buffer.byteOffset,
      8
    )
    view.setBigUint64(0, n, true) // little endian
    state.start += 8
  },
  decode(state) {
    if (state.end - state.start < 8) throw new Error('Out of bounds')
    const view = new DataView(
      state.buffer.buffer,
      state.start + state.buffer.byteOffset,
      8
    )
    const n = view.getBigUint64(0, true) // little endian
    state.start += 8
    return n
  }
})

exports.bigint64 = zigZagBigInt(biguint64)

const biguint = (exports.biguint = {
  preencode(state, n) {
    let len = 0
    for (let m = n; m; m = m >> 64n) len++
    uint.preencode(state, len)
    state.end += 8 * len
  },
  encode(state, n) {
    let len = 0
    for (let m = n; m; m = m >> 64n) len++
    uint.encode(state, len)
    const view = new DataView(
      state.buffer.buffer,
      state.start + state.buffer.byteOffset,
      8 * len
    )
    for (let m = n, i = 0; m; m = m >> 64n, i += 8) {
      view.setBigUint64(i, BigInt.asUintN(64, m), true) // little endian
    }
    state.start += 8 * len
  },
  decode(state) {
    const len = uint.decode(state)
    if (state.end - state.start < 8 * len) throw new Error('Out of bounds')
    const view = new DataView(
      state.buffer.buffer,
      state.start + state.buffer.byteOffset,
      8 * len
    )
    let n = 0n
    for (let i = len - 1; i >= 0; i--)
      n = (n << 64n) + view.getBigUint64(i * 8, true) // little endian
    state.start += 8 * len
    return n
  }
})

exports.bigint = zigZagBigInt(biguint)

exports.lexint = require('./lexint')

exports.float32 = {
  preencode(state, n) {
    state.end += 4
  },
  encode(state, n) {
    const view = new DataView(
      state.buffer.buffer,
      state.start + state.buffer.byteOffset,
      4
    )
    view.setFloat32(0, n, true) // little endian
    state.start += 4
  },
  decode(state) {
    if (state.end - state.start < 4) throw new Error('Out of bounds')
    const view = new DataView(
      state.buffer.buffer,
      state.start + state.buffer.byteOffset,
      4
    )
    const float = view.getFloat32(0, true) // little endian
    state.start += 4
    return float
  }
}

exports.float64 = {
  preencode(state, n) {
    state.end += 8
  },
  encode(state, n) {
    const view = new DataView(
      state.buffer.buffer,
      state.start + state.buffer.byteOffset,
      8
    )
    view.setFloat64(0, n, true) // little endian
    state.start += 8
  },
  decode(state) {
    if (state.end - state.start < 8) throw new Error('Out of bounds')
    const view = new DataView(
      state.buffer.buffer,
      state.start + state.buffer.byteOffset,
      8
    )
    const float = view.getFloat64(0, true) // little endian
    state.start += 8
    return float
  }
}

const buffer = (exports.buffer = {
  preencode(state, b) {
    if (b) uint8array.preencode(state, b)
    else state.end++
  },
  encode(state, b) {
    if (b) uint8array.encode(state, b)
    else state.buffer[state.start++] = 0
  },
  decode(state) {
    const len = uint.decode(state)
    if (len === 0) return null
    if (state.end - state.start < len) throw new Error('Out of bounds')
    return state.buffer.subarray(state.start, (state.start += len))
  }
})

exports.binary = {
  ...buffer,
  preencode(state, b) {
    if (typeof b === 'string') utf8.preencode(state, b)
    else buffer.preencode(state, b)
  },
  encode(state, b) {
    if (typeof b === 'string') utf8.encode(state, b)
    else buffer.encode(state, b)
  }
}

exports.arraybuffer = {
  preencode(state, b) {
    uint.preencode(state, b.byteLength)
    state.end += b.byteLength
  },
  encode(state, b) {
    uint.encode(state, b.byteLength)

    const view = new Uint8Array(b)

    state.buffer.set(view, state.start)
    state.start += b.byteLength
  },
  decode(state) {
    const len = uint.decode(state)

    const b = new ArrayBuffer(len)
    const view = new Uint8Array(b)

    view.set(state.buffer.subarray(state.start, (state.start += len)))

    return b
  }
}

function typedarray(TypedArray, swap) {
  const n = TypedArray.BYTES_PER_ELEMENT

  return {
    preencode(state, b) {
      uint.preencode(state, b.length)
      state.end += b.byteLength
    },
    encode(state, b) {
      uint.encode(state, b.length)

      const view = new Uint8Array(b.buffer, b.byteOffset, b.byteLength)

      if (BE && swap) swap(view)

      state.buffer.set(view, state.start)
      state.start += b.byteLength
    },
    decode(state) {
      const len = uint.decode(state)

      let b = state.buffer.subarray(state.start, (state.start += len * n))
      if (b.byteLength !== len * n) throw new Error('Out of bounds')
      if (b.byteOffset % n !== 0) b = new Uint8Array(b)

      if (BE && swap) swap(b)

      return new TypedArray(b.buffer, b.byteOffset, b.byteLength / n)
    }
  }
}

const uint8array = (exports.uint8array = typedarray(Uint8Array))
exports.uint16array = typedarray(Uint16Array, b4a.swap16)
exports.uint32array = typedarray(Uint32Array, b4a.swap32)

exports.int8array = typedarray(Int8Array)
exports.int16array = typedarray(Int16Array, b4a.swap16)
exports.int32array = typedarray(Int32Array, b4a.swap32)

exports.biguint64array = typedarray(BigUint64Array, b4a.swap64)
exports.bigint64array = typedarray(BigInt64Array, b4a.swap64)

exports.float32array = typedarray(Float32Array, b4a.swap32)
exports.float64array = typedarray(Float64Array, b4a.swap64)

function string(encoding) {
  return {
    preencode(state, s) {
      const len = b4a.byteLength(s, encoding)
      uint.preencode(state, len)
      state.end += len
    },
    encode(state, s) {
      const len = b4a.byteLength(s, encoding)
      uint.encode(state, len)
      b4a.write(state.buffer, s, state.start, encoding)
      state.start += len
    },
    decode(state) {
      const len = uint.decode(state)
      if (state.end - state.start < len) throw new Error('Out of bounds')
      return b4a.toString(
        state.buffer,
        encoding,
        state.start,
        (state.start += len)
      )
    },
    fixed(n) {
      return {
        preencode(state) {
          state.end += n
        },
        encode(state, s) {
          b4a.write(state.buffer, s, state.start, n, encoding)
          state.start += n
        },
        decode(state) {
          if (state.end - state.start < n) throw new Error('Out of bounds')
          return b4a.toString(
            state.buffer,
            encoding,
            state.start,
            (state.start += n)
          )
        }
      }
    }
  }
}

const utf8 = (exports.string = exports.utf8 = string('utf-8'))
exports.ascii = string('ascii')
exports.hex = string('hex')
exports.base64 = string('base64')
exports.ucs2 = exports.utf16le = string('utf16le')

exports.bool = {
  preencode(state, b) {
    state.end++
  },
  encode(state, b) {
    state.buffer[state.start++] = b ? 1 : 0
  },
  decode(state) {
    if (state.start >= state.end) throw Error('Out of bounds')
    return state.buffer[state.start++] === 1
  }
}

const fixed = (exports.fixed = function fixed(n) {
  return {
    preencode(state, s) {
      if (s.byteLength !== n) throw new Error('Incorrect buffer size')
      state.end += n
    },
    encode(state, s) {
      state.buffer.set(s, state.start)
      state.start += n
    },
    decode(state) {
      if (state.end - state.start < n) throw new Error('Out of bounds')
      return state.buffer.subarray(state.start, (state.start += n))
    }
  }
})

exports.fixed32 = fixed(32)
exports.fixed64 = fixed(64)

exports.array = function array(enc) {
  return {
    preencode(state, list) {
      uint.preencode(state, list.length)
      for (let i = 0; i < list.length; i++) enc.preencode(state, list[i])
    },
    encode(state, list) {
      uint.encode(state, list.length)
      for (let i = 0; i < list.length; i++) enc.encode(state, list[i])
    },
    decode(state) {
      const len = uint.decode(state)
      if (len > 0x100000) throw new Error('Array is too big')
      const arr = new Array(len)
      for (let i = 0; i < len; i++) arr[i] = enc.decode(state)
      return arr
    }
  }
}

exports.frame = function frame(enc) {
  const dummy = exports.state()

  return {
    preencode(state, m) {
      const end = state.end
      enc.preencode(state, m)
      uint.preencode(state, state.end - end)
    },
    encode(state, m) {
      dummy.end = 0
      enc.preencode(dummy, m)
      uint.encode(state, dummy.end)
      enc.encode(state, m)
    },
    decode(state) {
      const end = state.end
      const len = uint.decode(state)
      state.end = state.start + len
      const m = enc.decode(state)
      state.start = state.end
      state.end = end
      return m
    }
  }
}

exports.date = {
  preencode(state, d) {
    int.preencode(state, d.getTime())
  },
  encode(state, d) {
    int.encode(state, d.getTime())
  },
  decode(state, d) {
    return new Date(int.decode(state))
  }
}

exports.json = {
  preencode(state, v) {
    utf8.preencode(state, JSON.stringify(v))
  },
  encode(state, v) {
    utf8.encode(state, JSON.stringify(v))
  },
  decode(state) {
    return JSON.parse(utf8.decode(state))
  }
}

exports.ndjson = {
  preencode(state, v) {
    utf8.preencode(state, JSON.stringify(v) + '\n')
  },
  encode(state, v) {
    utf8.encode(state, JSON.stringify(v) + '\n')
  },
  decode(state) {
    return JSON.parse(utf8.decode(state))
  }
}

// simple helper for when you want to just express nothing
exports.none = {
  preencode(state, n) {
    // do nothing
  },
  encode(state, n) {
    // do nothing
  },
  decode(state) {
    return null
  }
}

// "any" encoders here for helping just structure any object without schematising it

const anyArray = {
  preencode(state, arr) {
    uint.preencode(state, arr.length)
    for (let i = 0; i < arr.length; i++) {
      any.preencode(state, arr[i])
    }
  },
  encode(state, arr) {
    uint.encode(state, arr.length)
    for (let i = 0; i < arr.length; i++) {
      any.encode(state, arr[i])
    }
  },
  decode(state) {
    const arr = []
    let len = uint.decode(state)
    while (len-- > 0) {
      arr.push(any.decode(state))
    }
    return arr
  }
}

const anyObject = {
  preencode(state, o) {
    const keys = Object.keys(o)
    uint.preencode(state, keys.length)
    for (const key of keys) {
      utf8.preencode(state, key)
      any.preencode(state, o[key])
    }
  },
  encode(state, o) {
    const keys = Object.keys(o)
    uint.encode(state, keys.length)
    for (const key of keys) {
      utf8.encode(state, key)
      any.encode(state, o[key])
    }
  },
  decode(state) {
    let len = uint.decode(state)
    const o = {}
    while (len-- > 0) {
      const key = utf8.decode(state)
      o[key] = any.decode(state)
    }
    return o
  }
}

const anyTypes = [
  exports.none,
  exports.bool,
  exports.string,
  exports.buffer,
  exports.uint,
  exports.int,
  exports.float64,
  anyArray,
  anyObject,
  exports.date
]

const any = (exports.any = {
  preencode(state, o) {
    const t = getType(o)
    uint.preencode(state, t)
    anyTypes[t].preencode(state, o)
  },
  encode(state, o) {
    const t = getType(o)
    uint.encode(state, t)
    anyTypes[t].encode(state, o)
  },
  decode(state) {
    const t = uint.decode(state)
    if (t >= anyTypes.length) throw new Error('Unknown type: ' + t)
    return anyTypes[t].decode(state)
  }
})

const port = (exports.port = uint16)

const address = (host, family) => {
  return {
    preencode(state, m) {
      host.preencode(state, m.host)
      port.preencode(state, m.port)
    },
    encode(state, m) {
      host.encode(state, m.host)
      port.encode(state, m.port)
    },
    decode(state) {
      return {
        host: host.decode(state),
        family,
        port: port.decode(state)
      }
    }
  }
}

const ipv4 = (exports.ipv4 = {
  preencode(state) {
    state.end += 4
  },
  encode(state, string) {
    const start = state.start
    const end = start + 4

    let i = 0

    while (i < string.length) {
      let n = 0
      let c

      while (
        i < string.length &&
        (c = string.charCodeAt(i++)) !== /* . */ 0x2e
      ) {
        n = n * 10 + (c - /* 0 */ 0x30)
      }

      state.buffer[state.start++] = n
    }

    state.start = end
  },
  decode(state) {
    if (state.end - state.start < 4) throw new Error('Out of bounds')
    return (
      state.buffer[state.start++] +
      '.' +
      state.buffer[state.start++] +
      '.' +
      state.buffer[state.start++] +
      '.' +
      state.buffer[state.start++]
    )
  }
})

exports.ipv4Address = address(ipv4, 4)

const ipv6 = (exports.ipv6 = {
  preencode(state) {
    state.end += 16
  },
  encode(state, string) {
    const start = state.start
    const end = start + 16

    let i = 0
    let split = null

    while (i < string.length) {
      let n = 0
      let c

      while (
        i < string.length &&
        (c = string.charCodeAt(i++)) !== /* : */ 0x3a
      ) {
        if (c >= 0x30 && c <= 0x39) n = n * 0x10 + (c - /* 0 */ 0x30)
        else if (c >= 0x41 && c <= 0x46) n = n * 0x10 + (c - /* A */ 0x41 + 10)
        else if (c >= 0x61 && c <= 0x66) n = n * 0x10 + (c - /* a */ 0x61 + 10)
      }

      state.buffer[state.start++] = n >>> 8
      state.buffer[state.start++] = n

      if (i < string.length && string.charCodeAt(i) === /* : */ 0x3a) {
        i++
        split = state.start
      }
    }

    if (split !== null) {
      const offset = end - state.start
      state.buffer
        .copyWithin(split + offset, split)
        .fill(0, split, split + offset)
    }

    state.start = end
  },
  decode(state) {
    if (state.end - state.start < 16) throw new Error('Out of bounds')
    return (
      (
        state.buffer[state.start++] * 256 +
        state.buffer[state.start++]
      ).toString(16) +
      ':' +
      (
        state.buffer[state.start++] * 256 +
        state.buffer[state.start++]
      ).toString(16) +
      ':' +
      (
        state.buffer[state.start++] * 256 +
        state.buffer[state.start++]
      ).toString(16) +
      ':' +
      (
        state.buffer[state.start++] * 256 +
        state.buffer[state.start++]
      ).toString(16) +
      ':' +
      (
        state.buffer[state.start++] * 256 +
        state.buffer[state.start++]
      ).toString(16) +
      ':' +
      (
        state.buffer[state.start++] * 256 +
        state.buffer[state.start++]
      ).toString(16) +
      ':' +
      (
        state.buffer[state.start++] * 256 +
        state.buffer[state.start++]
      ).toString(16) +
      ':' +
      (
        state.buffer[state.start++] * 256 +
        state.buffer[state.start++]
      ).toString(16)
    )
  }
})

exports.ipv6Address = address(ipv6, 6)

const ip = (exports.ip = {
  preencode(state, string) {
    const family = string.includes(':') ? 6 : 4
    uint8.preencode(state, family)
    if (family === 4) ipv4.preencode(state)
    else ipv6.preencode(state)
  },
  encode(state, string) {
    const family = string.includes(':') ? 6 : 4
    uint8.encode(state, family)
    if (family === 4) ipv4.encode(state, string)
    else ipv6.encode(state, string)
  },
  decode(state) {
    const family = uint8.decode(state)
    if (family === 4) return ipv4.decode(state)
    else return ipv6.decode(state)
  }
})

exports.ipAddress = {
  preencode(state, m) {
    ip.preencode(state, m.host)
    port.preencode(state, m.port)
  },
  encode(state, m) {
    ip.encode(state, m.host)
    port.encode(state, m.port)
  },
  decode(state) {
    const family = uint8.decode(state)
    return {
      host: family === 4 ? ipv4.decode(state) : ipv6.decode(state),
      family,
      port: port.decode(state)
    }
  }
}

const record = (exports.record = function (keyEncoding, valueEncoding) {
  return {
    preencode(state, v) {
      const keys = Object.keys(v)
      uint.preencode(state, keys.length)
      for (const k of keys) {
        keyEncoding.preencode(state, k)
        valueEncoding.preencode(state, v[k])
      }
    },
    encode(state, v) {
      const keys = Object.keys(v)
      uint.encode(state, keys.length)
      for (const k of keys) {
        keyEncoding.encode(state, k)
        valueEncoding.encode(state, v[k])
      }
    },
    decode(state) {
      const out = Object.create(null)
      const keys = uint.decode(state)
      for (let i = 0; i < keys; i++) {
        out[keyEncoding.decode(state)] = valueEncoding.decode(state)
      }
      return out
    }
  }
})

exports.stringRecord = record(utf8, utf8)

function getType(o) {
  if (o === null || o === undefined) return 0
  if (typeof o === 'boolean') return 1
  if (typeof o === 'string') return 2
  if (b4a.isBuffer(o)) return 3
  if (typeof o === 'number') {
    if (Number.isInteger(o)) return o >= 0 ? 4 : 5
    return 6
  }
  if (Array.isArray(o)) return 7
  if (o instanceof Date) return 9
  if (typeof o === 'object') return 8

  throw new Error('Unsupported type for ' + o)
}

exports.from = function from(enc) {
  if (typeof enc === 'string') return fromNamed(enc)
  if (enc.preencode) return enc
  if (enc.encodingLength) return fromAbstractEncoder(enc)
  return fromCodec(enc)
}

function fromNamed(enc) {
  switch (enc) {
    case 'ascii':
      return raw.ascii
    case 'utf-8':
    case 'utf8':
      return raw.utf8
    case 'hex':
      return raw.hex
    case 'base64':
      return raw.base64
    case 'utf16-le':
    case 'utf16le':
    case 'ucs-2':
    case 'ucs2':
      return raw.ucs2
    case 'ndjson':
      return raw.ndjson
    case 'json':
      return raw.json
    case 'binary':
    default:
      return raw.binary
  }
}

function fromCodec(enc) {
  let tmpM = null
  let tmpBuf = null

  return {
    preencode(state, m) {
      tmpM = m
      tmpBuf = enc.encode(m)
      state.end += tmpBuf.byteLength
    },
    encode(state, m) {
      raw.encode(state, m === tmpM ? tmpBuf : enc.encode(m))
      tmpM = tmpBuf = null
    },
    decode(state) {
      return enc.decode(raw.decode(state))
    }
  }
}

function fromAbstractEncoder(enc) {
  return {
    preencode(state, m) {
      state.end += enc.encodingLength(m)
    },
    encode(state, m) {
      enc.encode(m, state.buffer, state.start)
      state.start += enc.encode.bytes
    },
    decode(state) {
      const m = enc.decode(state.buffer, state.start, state.end)
      state.start += enc.decode.bytes
      return m
    }
  }
}

exports.encode = function encode(enc, m) {
  const state = exports.state()
  enc.preencode(state, m)
  state.buffer = b4a.allocUnsafe(state.end)
  enc.encode(state, m)
  return state.buffer
}

exports.decode = function decode(enc, buffer) {
  return enc.decode(exports.state(0, buffer.byteLength, buffer))
}

function zigZagInt(enc) {
  return {
    preencode(state, n) {
      enc.preencode(state, zigZagEncodeInt(n))
    },
    encode(state, n) {
      enc.encode(state, zigZagEncodeInt(n))
    },
    decode(state) {
      return zigZagDecodeInt(enc.decode(state))
    }
  }
}

function zigZagDecodeInt(n) {
  return n === 0 ? n : (n & 1) === 0 ? n / 2 : -(n + 1) / 2
}

function zigZagEncodeInt(n) {
  // 0, -1, 1, -2, 2, ...
  return n < 0 ? 2 * -n - 1 : n === 0 ? 0 : 2 * n
}

function zigZagBigInt(enc) {
  return {
    preencode(state, n) {
      enc.preencode(state, zigZagEncodeBigInt(n))
    },
    encode(state, n) {
      enc.encode(state, zigZagEncodeBigInt(n))
    },
    decode(state) {
      return zigZagDecodeBigInt(enc.decode(state))
    }
  }
}

function zigZagDecodeBigInt(n) {
  return n === 0n ? n : (n & 1n) === 0n ? n / 2n : -(n + 1n) / 2n
}

function zigZagEncodeBigInt(n) {
  // 0, -1, 1, -2, 2, ...
  return n < 0n ? 2n * -n - 1n : n === 0n ? 0n : 2n * n
}

function validateUint(n) {
  if (n >= 0 === false /* Handles NaN as well */)
    throw new Error('uint must be positive')
}
module.exports = {
  preencode,
  encode,
  decode
}

function preencode(state, num) {
  if (num < 251) {
    state.end++
  } else if (num < 256) {
    state.end += 2
  } else if (num < 0x10000) {
    state.end += 3
  } else if (num < 0x1000000) {
    state.end += 4
  } else if (num < 0x100000000) {
    state.end += 5
  } else {
    state.end++
    const exp = Math.floor(Math.log(num) / Math.log(2)) - 32
    preencode(state, exp)
    state.end += 6
  }
}

function encode(state, num) {
  const max = 251
  const x = num - max

  if (num < max) {
    state.buffer[state.start++] = num
  } else if (num < 256) {
    state.buffer[state.start++] = max
    state.buffer[state.start++] = x
  } else if (num < 0x10000) {
    state.buffer[state.start++] = max + 1
    state.buffer[state.start++] = (x >> 8) & 0xff
    state.buffer[state.start++] = x & 0xff
  } else if (num < 0x1000000) {
    state.buffer[state.start++] = max + 2
    state.buffer[state.start++] = x >> 16
    state.buffer[state.start++] = (x >> 8) & 0xff
    state.buffer[state.start++] = x & 0xff
  } else if (num < 0x100000000) {
    state.buffer[state.start++] = max + 3
    state.buffer[state.start++] = x >> 24
    state.buffer[state.start++] = (x >> 16) & 0xff
    state.buffer[state.start++] = (x >> 8) & 0xff
    state.buffer[state.start++] = x & 0xff
  } else {
    // need to use Math here as bitwise ops are 32 bit
    const exp = Math.floor(Math.log(x) / Math.log(2)) - 32
    state.buffer[state.start++] = 0xff

    encode(state, exp)
    const rem = x / Math.pow(2, exp - 11)

    for (let i = 5; i >= 0; i--) {
      state.buffer[state.start++] = (rem / Math.pow(2, 8 * i)) & 0xff
    }
  }
}

function decode(state) {
  const max = 251

  if (state.end - state.start < 1) throw new Error('Out of bounds')

  const flag = state.buffer[state.start++]

  if (flag < max) return flag

  if (state.end - state.start < flag - max + 1) {
    throw new Error('Out of bounds.')
  }

  if (flag < 252) {
    return state.buffer[state.start++] + max
  }

  if (flag < 253) {
    return (
      (state.buffer[state.start++] << 8) + state.buffer[state.start++] + max
    )
  }

  if (flag < 254) {
    return (
      (state.buffer[state.start++] << 16) +
      (state.buffer[state.start++] << 8) +
      state.buffer[state.start++] +
      max
    )
  }

  // << 24 result may be interpreted as negative
  if (flag < 255) {
    return (
      state.buffer[state.start++] * 0x1000000 +
      (state.buffer[state.start++] << 16) +
      (state.buffer[state.start++] << 8) +
      state.buffer[state.start++] +
      max
    )
  }

  const exp = decode(state)

  if (state.end - state.start < 6) throw new Error('Out of bounds')

  let rem = 0
  for (let i = 5; i >= 0; i--) {
    rem += state.buffer[state.start++] * Math.pow(2, 8 * i)
  }

  return rem * Math.pow(2, exp - 11) + max
}
{
  "name": "compact-encoding",
  "version": "2.19.2",
  "description": "A series of compact encoding schemes for building small and fast parsers and serializers",
  "main": "index.js",
  "dependencies": {
    "b4a": "^1.3.0"
  },
  "devDependencies": {
    "brittle": "^3.0.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^1.0.0"
  },
  "scripts": {
    "format": "prettier . --write",
    "test": "prettier . --check && brittle test.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/holepunchto/compact-encoding.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/compact-encoding/issues"
  },
  "homepage": "https://github.com/holepunchto/compact-encoding"
}
const b4a = require('b4a')

const { BE } = require('./endian')

exports = module.exports = {
  preencode(state, b) {
    state.end += b.byteLength
  },
  encode(state, b) {
    state.buffer.set(b, state.start)
    state.start += b.byteLength
  },
  decode(state) {
    const b = state.buffer.subarray(state.start, state.end)
    state.start = state.end
    return b
  }
}

const buffer = (exports.buffer = {
  preencode(state, b) {
    if (b) uint8array.preencode(state, b)
    else state.end++
  },
  encode(state, b) {
    if (b) uint8array.encode(state, b)
    else state.buffer[state.start++] = 0
  },
  decode(state) {
    const b = state.buffer.subarray(state.start)
    if (b.byteLength === 0) return null
    state.start = state.end
    return b
  }
})

exports.binary = {
  ...buffer,
  preencode(state, b) {
    if (typeof b === 'string') utf8.preencode(state, b)
    else buffer.preencode(state, b)
  },
  encode(state, b) {
    if (typeof b === 'string') utf8.encode(state, b)
    else buffer.encode(state, b)
  }
}

exports.arraybuffer = {
  preencode(state, b) {
    state.end += b.byteLength
  },
  encode(state, b) {
    const view = new Uint8Array(b)

    state.buffer.set(view, state.start)
    state.start += b.byteLength
  },
  decode(state) {
    const b = new ArrayBuffer(state.end - state.start)
    const view = new Uint8Array(b)

    view.set(state.buffer.subarray(state.start))

    state.start = state.end

    return b
  }
}

function typedarray(TypedArray, swap) {
  const n = TypedArray.BYTES_PER_ELEMENT

  return {
    preencode(state, b) {
      state.end += b.byteLength
    },
    encode(state, b) {
      const view = new Uint8Array(b.buffer, b.byteOffset, b.byteLength)

      if (BE && swap) swap(view)

      state.buffer.set(view, state.start)
      state.start += b.byteLength
    },
    decode(state) {
      let b = state.buffer.subarray(state.start)
      if (b.byteOffset % n !== 0) b = new Uint8Array(b)

      if (BE && swap) swap(b)

      state.start = state.end

      return new TypedArray(b.buffer, b.byteOffset, b.byteLength / n)
    }
  }
}

const uint8array = (exports.uint8array = typedarray(Uint8Array))
exports.uint16array = typedarray(Uint16Array, b4a.swap16)
exports.uint32array = typedarray(Uint32Array, b4a.swap32)

exports.int8array = typedarray(Int8Array)
exports.int16array = typedarray(Int16Array, b4a.swap16)
exports.int32array = typedarray(Int32Array, b4a.swap32)

exports.biguint64array = typedarray(BigUint64Array, b4a.swap64)
exports.bigint64array = typedarray(BigInt64Array, b4a.swap64)

exports.float32array = typedarray(Float32Array, b4a.swap32)
exports.float64array = typedarray(Float64Array, b4a.swap64)

function string(encoding) {
  return {
    preencode(state, s) {
      state.end += b4a.byteLength(s, encoding)
    },
    encode(state, s) {
      state.start += b4a.write(state.buffer, s, state.start, encoding)
    },
    decode(state) {
      const s = b4a.toString(state.buffer, encoding, state.start)
      state.start = state.end
      return s
    }
  }
}

const utf8 = (exports.string = exports.utf8 = string('utf-8'))
exports.ascii = string('ascii')
exports.hex = string('hex')
exports.base64 = string('base64')
exports.ucs2 = exports.utf16le = string('utf16le')

exports.array = function array(enc) {
  return {
    preencode(state, list) {
      for (const value of list) enc.preencode(state, value)
    },
    encode(state, list) {
      for (const value of list) enc.encode(state, value)
    },
    decode(state) {
      const arr = []
      while (state.start < state.end) arr.push(enc.decode(state))
      return arr
    }
  }
}

exports.json = {
  preencode(state, v) {
    utf8.preencode(state, JSON.stringify(v))
  },
  encode(state, v) {
    utf8.encode(state, JSON.stringify(v))
  },
  decode(state) {
    return JSON.parse(utf8.decode(state))
  }
}

exports.ndjson = {
  preencode(state, v) {
    utf8.preencode(state, JSON.stringify(v) + '\n')
  },
  encode(state, v) {
    utf8.encode(state, JSON.stringify(v) + '\n')
  },
  decode(state) {
    return JSON.parse(utf8.decode(state))
  }
}
{
  "name": "compact-encoding-net",
  "version": "1.2.0",
  "description": "Compact codecs for net types",
  "main": "index.js",
  "scripts": {
    "test": "standard && brittle test.mjs"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/compact-encoding/compact-encoding-net.git"
  },
  "author": "Kasper Isager Dalsgarð <kasper@funktionel.co>",
  "license": "ISC",
  "bugs": {
    "url": "https://github.com/compact-encoding/compact-encoding-net/issues"
  },
  "homepage": "https://github.com/compact-encoding/compact-encoding-net#readme",
  "dependencies": {
    "compact-encoding": "^2.4.1"
  },
  "devDependencies": {
    "brittle": "^1.3.5",
    "nanobench": "^2.1.1",
    "standard": "^16.0.3"
  }
}
const LE = (exports.LE =
  new Uint8Array(new Uint16Array([0xff]).buffer)[0] === 0xff)

exports.BE = !LE
const b4a = require('b4a')

const { BE } = require('./endian')

exports.state = function (start = 0, end = 0, buffer = null) {
  return { start, end, buffer }
}

const raw = (exports.raw = require('./raw'))

const uint = (exports.uint = {
  preencode(state, n) {
    state.end += n <= 0xfc ? 1 : n <= 0xffff ? 3 : n <= 0xffffffff ? 5 : 9
  },
  encode(state, n) {
    if (n <= 0xfc) uint8.encode(state, n)
    else if (n <= 0xffff) {
      state.buffer[state.start++] = 0xfd
      uint16.encode(state, n)
    } else if (n <= 0xffffffff) {
      state.buffer[state.start++] = 0xfe
      uint32.encode(state, n)
    } else {
      state.buffer[state.start++] = 0xff
      uint64.encode(state, n)
    }
  },
  decode(state) {
    const a = uint8.decode(state)
    if (a <= 0xfc) return a
    if (a === 0xfd) return uint16.decode(state)
    if (a === 0xfe) return uint32.decode(state)
    return uint64.decode(state)
  }
})

const uint8 = (exports.uint8 = {
  preencode(state, n) {
    state.end += 1
  },
  encode(state, n) {
    validateUint(n)
    state.buffer[state.start++] = n
  },
  decode(state) {
    if (state.start >= state.end) throw new Error('Out of bounds')
    return state.buffer[state.start++]
  }
})

const uint16 = (exports.uint16 = {
  preencode(state, n) {
    state.end += 2
  },
  encode(state, n) {
    validateUint(n)
    state.buffer[state.start++] = n
    state.buffer[state.start++] = n >>> 8
  },
  decode(state) {
    if (state.end - state.start < 2) throw new Error('Out of bounds')
    return state.buffer[state.start++] + state.buffer[state.start++] * 0x100
  }
})

const uint24 = (exports.uint24 = {
  preencode(state, n) {
    state.end += 3
  },
  encode(state, n) {
    validateUint(n)
    state.buffer[state.start++] = n
    state.buffer[state.start++] = n >>> 8
    state.buffer[state.start++] = n >>> 16
  },
  decode(state) {
    if (state.end - state.start < 3) throw new Error('Out of bounds')
    return (
      state.buffer[state.start++] +
      state.buffer[state.start++] * 0x100 +
      state.buffer[state.start++] * 0x10000
    )
  }
})

const uint32 = (exports.uint32 = {
  preencode(state, n) {
    state.end += 4
  },
  encode(state, n) {
    validateUint(n)
    state.buffer[state.start++] = n
    state.buffer[state.start++] = n >>> 8
    state.buffer[state.start++] = n >>> 16
    state.buffer[state.start++] = n >>> 24
  },
  decode(state) {
    if (state.end - state.start < 4) throw new Error('Out of bounds')
    return (
      state.buffer[state.start++] +
      state.buffer[state.start++] * 0x100 +
      state.buffer[state.start++] * 0x10000 +
      state.buffer[state.start++] * 0x1000000
    )
  }
})

const uint32be = (exports.uint32be = {
  preencode(state, n) {
    state.end += 4
  },
  encode(state, n) {
    validateUint(n)
    state.buffer[state.start++] = n >>> 24
    state.buffer[state.start++] = n >>> 16
    state.buffer[state.start++] = n >>> 8
    state.buffer[state.start++] = n
  },
  decode(state) {
    if (state.end - state.start < 4) throw new Error('Out of bounds')
    return (
      state.buffer[state.start++] * 0x1000000 +
      state.buffer[state.start++] * 0x10000 +
      state.buffer[state.start++] * 0x100 +
      state.buffer[state.start++]
    )
  }
})

const uint40 = (exports.uint40 = {
  preencode(state, n) {
    state.end += 5
  },
  encode(state, n) {
    validateUint(n)
    const r = Math.floor(n / 0x100)
    uint8.encode(state, n)
    uint32.encode(state, r)
  },
  decode(state) {
    if (state.end - state.start < 5) throw new Error('Out of bounds')
    return uint8.decode(state) + 0x100 * uint32.decode(state)
  }
})

const uint48 = (exports.uint48 = {
  preencode(state, n) {
    state.end += 6
  },
  encode(state, n) {
    validateUint(n)
    const r = Math.floor(n / 0x10000)
    uint16.encode(state, n)
    uint32.encode(state, r)
  },
  decode(state) {
    if (state.end - state.start < 6) throw new Error('Out of bounds')
    return uint16.decode(state) + 0x10000 * uint32.decode(state)
  }
})

const uint56 = (exports.uint56 = {
  preencode(state, n) {
    state.end += 7
  },
  encode(state, n) {
    validateUint(n)
    const r = Math.floor(n / 0x1000000)
    uint24.encode(state, n)
    uint32.encode(state, r)
  },
  decode(state) {
    if (state.end - state.start < 7) throw new Error('Out of bounds')
    return uint24.decode(state) + 0x1000000 * uint32.decode(state)
  }
})

const uint64 = (exports.uint64 = {
  preencode(state, n) {
    state.end += 8
  },
  encode(state, n) {
    validateUint(n)
    const r = Math.floor(n / 0x100000000)
    uint32.encode(state, n)
    uint32.encode(state, r)
  },
  decode(state) {
    if (state.end - state.start < 8) throw new Error('Out of bounds')
    return uint32.decode(state) + 0x100000000 * uint32.decode(state)
  }
})

exports.uint64be = {
  preencode(state, n) {
    state.end += 8
  },
  encode(state, n) {
    validateUint(n)
    const r = Math.floor(n / 0x100000000)
    uint32be.encode(state, r)
    uint32be.encode(state, n)
  },
  decode(state) {
    if (state.end - state.start < 8) throw new Error('Out of bounds')
    return 0x100000000 * uint32be.decode(state) + uint32be.decode(state)
  }
}

const int = (exports.int = zigZagInt(uint))
exports.int8 = zigZagInt(uint8)
exports.int16 = zigZagInt(uint16)
exports.int24 = zigZagInt(uint24)
exports.int32 = zigZagInt(uint32)
exports.int40 = zigZagInt(uint40)
exports.int48 = zigZagInt(uint48)
exports.int56 = zigZagInt(uint56)
exports.int64 = zigZagInt(uint64)

const biguint64 = (exports.biguint64 = {
  preencode(state, n) {
    state.end += 8
  },
  encode(state, n) {
    const view = new DataView(
      state.buffer.buffer,
      state.start + state.buffer.byteOffset,
      8
    )
    view.setBigUint64(0, n, true) // little endian
    state.start += 8
  },
  decode(state) {
    if (state.end - state.start < 8) throw new Error('Out of bounds')
    const view = new DataView(
      state.buffer.buffer,
      state.start + state.buffer.byteOffset,
      8
    )
    const n = view.getBigUint64(0, true) // little endian
    state.start += 8
    return n
  }
})

exports.bigint64 = zigZagBigInt(biguint64)

const biguint = (exports.biguint = {
  preencode(state, n) {
    let len = 0
    for (let m = n; m; m = m >> 64n) len++
    uint.preencode(state, len)
    state.end += 8 * len
  },
  encode(state, n) {
    let len = 0
    for (let m = n; m; m = m >> 64n) len++
    uint.encode(state, len)
    const view = new DataView(
      state.buffer.buffer,
      state.start + state.buffer.byteOffset,
      8 * len
    )
    for (let m = n, i = 0; m; m = m >> 64n, i += 8) {
      view.setBigUint64(i, BigInt.asUintN(64, m), true) // little endian
    }
    state.start += 8 * len
  },
  decode(state) {
    const len = uint.decode(state)
    if (state.end - state.start < 8 * len) throw new Error('Out of bounds')
    const view = new DataView(
      state.buffer.buffer,
      state.start + state.buffer.byteOffset,
      8 * len
    )
    let n = 0n
    for (let i = len - 1; i >= 0; i--)
      n = (n << 64n) + view.getBigUint64(i * 8, true) // little endian
    state.start += 8 * len
    return n
  }
})

exports.bigint = zigZagBigInt(biguint)

exports.lexint = require('./lexint')

exports.float32 = {
  preencode(state, n) {
    state.end += 4
  },
  encode(state, n) {
    const view = new DataView(
      state.buffer.buffer,
      state.start + state.buffer.byteOffset,
      4
    )
    view.setFloat32(0, n, true) // little endian
    state.start += 4
  },
  decode(state) {
    if (state.end - state.start < 4) throw new Error('Out of bounds')
    const view = new DataView(
      state.buffer.buffer,
      state.start + state.buffer.byteOffset,
      4
    )
    const float = view.getFloat32(0, true) // little endian
    state.start += 4
    return float
  }
}

exports.float64 = {
  preencode(state, n) {
    state.end += 8
  },
  encode(state, n) {
    const view = new DataView(
      state.buffer.buffer,
      state.start + state.buffer.byteOffset,
      8
    )
    view.setFloat64(0, n, true) // little endian
    state.start += 8
  },
  decode(state) {
    if (state.end - state.start < 8) throw new Error('Out of bounds')
    const view = new DataView(
      state.buffer.buffer,
      state.start + state.buffer.byteOffset,
      8
    )
    const float = view.getFloat64(0, true) // little endian
    state.start += 8
    return float
  }
}

const buffer = (exports.buffer = {
  preencode(state, b) {
    uint8array.preencode(state, b)
  },
  encode(state, b) {
    uint8array.encode(state, b)
  },
  decode(state) {
    const len = uint.decode(state)
    if (state.end - state.start < len) throw new Error('Out of bounds')
    return state.buffer.subarray(state.start, (state.start += len))
  }
})

exports.optionalBuffer = {
  preencode(state, b) {
    if (b) uint8array.preencode(state, b)
    else state.end++
  },
  encode(state, b) {
    if (b) uint8array.encode(state, b)
    else state.buffer[state.start++] = 0
  },
  decode(state) {
    const len = uint.decode(state)
    if (len === 0) return null
    if (state.end - state.start < len) throw new Error('Out of bounds')
    return state.buffer.subarray(state.start, (state.start += len))
  }
}

exports.binary = {
  ...buffer,
  preencode(state, b) {
    if (typeof b === 'string') utf8.preencode(state, b)
    else buffer.preencode(state, b)
  },
  encode(state, b) {
    if (typeof b === 'string') utf8.encode(state, b)
    else buffer.encode(state, b)
  }
}

exports.arraybuffer = {
  preencode(state, b) {
    uint.preencode(state, b.byteLength)
    state.end += b.byteLength
  },
  encode(state, b) {
    uint.encode(state, b.byteLength)

    const view = new Uint8Array(b)

    state.buffer.set(view, state.start)
    state.start += b.byteLength
  },
  decode(state) {
    const len = uint.decode(state)

    const b = new ArrayBuffer(len)
    const view = new Uint8Array(b)

    view.set(state.buffer.subarray(state.start, (state.start += len)))

    return b
  }
}

function typedarray(TypedArray, swap) {
  const n = TypedArray.BYTES_PER_ELEMENT

  return {
    preencode(state, b) {
      uint.preencode(state, b.length)
      state.end += b.byteLength
    },
    encode(state, b) {
      uint.encode(state, b.length)

      const view = new Uint8Array(b.buffer, b.byteOffset, b.byteLength)

      if (BE && swap) swap(view)

      state.buffer.set(view, state.start)
      state.start += b.byteLength
    },
    decode(state) {
      const len = uint.decode(state)

      let b = state.buffer.subarray(state.start, (state.start += len * n))
      if (b.byteLength !== len * n) throw new Error('Out of bounds')
      if (b.byteOffset % n !== 0) b = new Uint8Array(b)

      if (BE && swap) swap(b)

      return new TypedArray(b.buffer, b.byteOffset, b.byteLength / n)
    }
  }
}

const uint8array = (exports.uint8array = typedarray(Uint8Array))
exports.uint16array = typedarray(Uint16Array, b4a.swap16)
exports.uint32array = typedarray(Uint32Array, b4a.swap32)

exports.int8array = typedarray(Int8Array)
exports.int16array = typedarray(Int16Array, b4a.swap16)
exports.int32array = typedarray(Int32Array, b4a.swap32)

exports.biguint64array = typedarray(BigUint64Array, b4a.swap64)
exports.bigint64array = typedarray(BigInt64Array, b4a.swap64)

exports.float32array = typedarray(Float32Array, b4a.swap32)
exports.float64array = typedarray(Float64Array, b4a.swap64)

function string(encoding) {
  return {
    preencode(state, s) {
      const len = b4a.byteLength(s, encoding)
      uint.preencode(state, len)
      state.end += len
    },
    encode(state, s) {
      const len = b4a.byteLength(s, encoding)
      uint.encode(state, len)
      b4a.write(state.buffer, s, state.start, encoding)
      state.start += len
    },
    decode(state) {
      const len = uint.decode(state)
      if (state.end - state.start < len) throw new Error('Out of bounds')
      return b4a.toString(
        state.buffer,
        encoding,
        state.start,
        (state.start += len)
      )
    },
    fixed(n) {
      return {
        preencode(state) {
          state.end += n
        },
        encode(state, s) {
          b4a.write(state.buffer, s, state.start, n, encoding)
          state.start += n
        },
        decode(state) {
          if (state.end - state.start < n) throw new Error('Out of bounds')
          return b4a.toString(
            state.buffer,
            encoding,
            state.start,
            (state.start += n)
          )
        }
      }
    }
  }
}

const utf8 = (exports.string = exports.utf8 = string('utf-8'))
exports.ascii = string('ascii')
exports.hex = string('hex')
exports.base64 = string('base64')
exports.ucs2 = exports.utf16le = string('utf16le')

exports.bool = {
  preencode(state, b) {
    state.end++
  },
  encode(state, b) {
    state.buffer[state.start++] = b ? 1 : 0
  },
  decode(state) {
    if (state.start >= state.end) throw Error('Out of bounds')
    return state.buffer[state.start++] === 1
  }
}

const fixed = (exports.fixed = function fixed(n) {
  return {
    preencode(state, s) {
      if (s.byteLength !== n) throw new Error('Incorrect buffer size')
      state.end += n
    },
    encode(state, s) {
      state.buffer.set(s, state.start)
      state.start += n
    },
    decode(state) {
      if (state.end - state.start < n) throw new Error('Out of bounds')
      return state.buffer.subarray(state.start, (state.start += n))
    }
  }
})

exports.fixed32 = fixed(32)
exports.fixed64 = fixed(64)

exports.array = function array(enc) {
  return {
    preencode(state, list) {
      uint.preencode(state, list.length)
      for (let i = 0; i < list.length; i++) enc.preencode(state, list[i])
    },
    encode(state, list) {
      uint.encode(state, list.length)
      for (let i = 0; i < list.length; i++) enc.encode(state, list[i])
    },
    decode(state) {
      const len = uint.decode(state)
      if (len > 0x100000) throw new Error('Array is too big')
      const arr = new Array(len)
      for (let i = 0; i < len; i++) arr[i] = enc.decode(state)
      return arr
    }
  }
}

exports.frame = function frame(enc) {
  const dummy = exports.state()

  return {
    preencode(state, m) {
      const end = state.end
      enc.preencode(state, m)
      uint.preencode(state, state.end - end)
    },
    encode(state, m) {
      dummy.end = 0
      enc.preencode(dummy, m)
      uint.encode(state, dummy.end)
      enc.encode(state, m)
    },
    decode(state) {
      const end = state.end
      const len = uint.decode(state)
      state.end = state.start + len
      const m = enc.decode(state)
      state.start = state.end
      state.end = end
      return m
    }
  }
}

exports.date = {
  preencode(state, d) {
    int.preencode(state, d.getTime())
  },
  encode(state, d) {
    int.encode(state, d.getTime())
  },
  decode(state, d) {
    return new Date(int.decode(state))
  }
}

exports.json = {
  preencode(state, v) {
    utf8.preencode(state, JSON.stringify(v))
  },
  encode(state, v) {
    utf8.encode(state, JSON.stringify(v))
  },
  decode(state) {
    return JSON.parse(utf8.decode(state))
  }
}

exports.ndjson = {
  preencode(state, v) {
    utf8.preencode(state, JSON.stringify(v) + '\n')
  },
  encode(state, v) {
    utf8.encode(state, JSON.stringify(v) + '\n')
  },
  decode(state) {
    return JSON.parse(utf8.decode(state))
  }
}

// simple helper for when you want to just express nothing
exports.none = {
  preencode(state, n) {
    // do nothing
  },
  encode(state, n) {
    // do nothing
  },
  decode(state) {
    return null
  }
}

// "any" encoders here for helping just structure any object without schematising it

const anyArray = {
  preencode(state, arr) {
    uint.preencode(state, arr.length)
    for (let i = 0; i < arr.length; i++) {
      any.preencode(state, arr[i])
    }
  },
  encode(state, arr) {
    uint.encode(state, arr.length)
    for (let i = 0; i < arr.length; i++) {
      any.encode(state, arr[i])
    }
  },
  decode(state) {
    const arr = []
    let len = uint.decode(state)
    while (len-- > 0) {
      arr.push(any.decode(state))
    }
    return arr
  }
}

const anyObject = {
  preencode(state, o) {
    const keys = Object.keys(o)
    uint.preencode(state, keys.length)
    for (const key of keys) {
      utf8.preencode(state, key)
      any.preencode(state, o[key])
    }
  },
  encode(state, o) {
    const keys = Object.keys(o)
    uint.encode(state, keys.length)
    for (const key of keys) {
      utf8.encode(state, key)
      any.encode(state, o[key])
    }
  },
  decode(state) {
    let len = uint.decode(state)
    const o = {}
    while (len-- > 0) {
      const key = utf8.decode(state)
      o[key] = any.decode(state)
    }
    return o
  }
}

const anyTypes = [
  exports.none,
  exports.bool,
  exports.string,
  exports.buffer,
  exports.uint,
  exports.int,
  exports.float64,
  anyArray,
  anyObject,
  exports.date
]

const any = (exports.any = {
  preencode(state, o) {
    const t = getType(o)
    uint.preencode(state, t)
    anyTypes[t].preencode(state, o)
  },
  encode(state, o) {
    const t = getType(o)
    uint.encode(state, t)
    anyTypes[t].encode(state, o)
  },
  decode(state) {
    const t = uint.decode(state)
    if (t >= anyTypes.length) throw new Error('Unknown type: ' + t)
    return anyTypes[t].decode(state)
  }
})

const port = (exports.port = uint16)

const address = (host, family) => {
  return {
    preencode(state, m) {
      host.preencode(state, m.host)
      port.preencode(state, m.port)
    },
    encode(state, m) {
      host.encode(state, m.host)
      port.encode(state, m.port)
    },
    decode(state) {
      return {
        host: host.decode(state),
        family,
        port: port.decode(state)
      }
    }
  }
}

const ipv4 = (exports.ipv4 = {
  preencode(state) {
    state.end += 4
  },
  encode(state, string) {
    const start = state.start
    const end = start + 4

    let i = 0

    while (i < string.length) {
      let n = 0
      let c

      while (
        i < string.length &&
        (c = string.charCodeAt(i++)) !== /* . */ 0x2e
      ) {
        n = n * 10 + (c - /* 0 */ 0x30)
      }

      state.buffer[state.start++] = n
    }

    state.start = end
  },
  decode(state) {
    if (state.end - state.start < 4) throw new Error('Out of bounds')
    return (
      state.buffer[state.start++] +
      '.' +
      state.buffer[state.start++] +
      '.' +
      state.buffer[state.start++] +
      '.' +
      state.buffer[state.start++]
    )
  }
})

exports.ipv4Address = address(ipv4, 4)

const ipv6 = (exports.ipv6 = {
  preencode(state) {
    state.end += 16
  },
  encode(state, string) {
    const start = state.start
    const end = start + 16

    let i = 0
    let split = null

    while (i < string.length) {
      let n = 0
      let c

      while (
        i < string.length &&
        (c = string.charCodeAt(i++)) !== /* : */ 0x3a
      ) {
        if (c >= 0x30 && c <= 0x39) n = n * 0x10 + (c - /* 0 */ 0x30)
        else if (c >= 0x41 && c <= 0x46) n = n * 0x10 + (c - /* A */ 0x41 + 10)
        else if (c >= 0x61 && c <= 0x66) n = n * 0x10 + (c - /* a */ 0x61 + 10)
      }

      state.buffer[state.start++] = n >>> 8
      state.buffer[state.start++] = n

      if (i < string.length && string.charCodeAt(i) === /* : */ 0x3a) {
        i++
        split = state.start
      }
    }

    if (split !== null) {
      const offset = end - state.start
      state.buffer
        .copyWithin(split + offset, split)
        .fill(0, split, split + offset)
    }

    state.start = end
  },
  decode(state) {
    if (state.end - state.start < 16) throw new Error('Out of bounds')
    return (
      (
        state.buffer[state.start++] * 256 +
        state.buffer[state.start++]
      ).toString(16) +
      ':' +
      (
        state.buffer[state.start++] * 256 +
        state.buffer[state.start++]
      ).toString(16) +
      ':' +
      (
        state.buffer[state.start++] * 256 +
        state.buffer[state.start++]
      ).toString(16) +
      ':' +
      (
        state.buffer[state.start++] * 256 +
        state.buffer[state.start++]
      ).toString(16) +
      ':' +
      (
        state.buffer[state.start++] * 256 +
        state.buffer[state.start++]
      ).toString(16) +
      ':' +
      (
        state.buffer[state.start++] * 256 +
        state.buffer[state.start++]
      ).toString(16) +
      ':' +
      (
        state.buffer[state.start++] * 256 +
        state.buffer[state.start++]
      ).toString(16) +
      ':' +
      (
        state.buffer[state.start++] * 256 +
        state.buffer[state.start++]
      ).toString(16)
    )
  }
})

exports.ipv6Address = address(ipv6, 6)

const ip = (exports.ip = {
  preencode(state, string) {
    const family = string.includes(':') ? 6 : 4
    uint8.preencode(state, family)
    if (family === 4) ipv4.preencode(state)
    else ipv6.preencode(state)
  },
  encode(state, string) {
    const family = string.includes(':') ? 6 : 4
    uint8.encode(state, family)
    if (family === 4) ipv4.encode(state, string)
    else ipv6.encode(state, string)
  },
  decode(state) {
    const family = uint8.decode(state)
    if (family === 4) return ipv4.decode(state)
    else return ipv6.decode(state)
  }
})

exports.ipAddress = {
  preencode(state, m) {
    ip.preencode(state, m.host)
    port.preencode(state, m.port)
  },
  encode(state, m) {
    ip.encode(state, m.host)
    port.encode(state, m.port)
  },
  decode(state) {
    const family = uint8.decode(state)
    return {
      host: family === 4 ? ipv4.decode(state) : ipv6.decode(state),
      family,
      port: port.decode(state)
    }
  }
}

const record = (exports.record = function (keyEncoding, valueEncoding) {
  return {
    preencode(state, v) {
      const keys = Object.keys(v)
      uint.preencode(state, keys.length)
      for (const k of keys) {
        keyEncoding.preencode(state, k)
        valueEncoding.preencode(state, v[k])
      }
    },
    encode(state, v) {
      const keys = Object.keys(v)
      uint.encode(state, keys.length)
      for (const k of keys) {
        keyEncoding.encode(state, k)
        valueEncoding.encode(state, v[k])
      }
    },
    decode(state) {
      const out = Object.create(null)
      const keys = uint.decode(state)
      for (let i = 0; i < keys; i++) {
        out[keyEncoding.decode(state)] = valueEncoding.decode(state)
      }
      return out
    }
  }
})

exports.stringRecord = record(utf8, utf8)

function getType(o) {
  if (o === null || o === undefined) return 0
  if (typeof o === 'boolean') return 1
  if (typeof o === 'string') return 2
  if (b4a.isBuffer(o)) return 3
  if (typeof o === 'number') {
    if (Number.isInteger(o)) return o >= 0 ? 4 : 5
    return 6
  }
  if (Array.isArray(o)) return 7
  if (o instanceof Date) return 9
  if (typeof o === 'object') return 8

  throw new Error('Unsupported type for ' + o)
}

exports.from = function from(enc) {
  if (typeof enc === 'string') return fromNamed(enc)
  if (enc.preencode) return enc
  if (enc.encodingLength) return fromAbstractEncoder(enc)
  return fromCodec(enc)
}

function fromNamed(enc) {
  switch (enc) {
    case 'ascii':
      return raw.ascii
    case 'utf-8':
    case 'utf8':
      return raw.utf8
    case 'hex':
      return raw.hex
    case 'base64':
      return raw.base64
    case 'utf16-le':
    case 'utf16le':
    case 'ucs-2':
    case 'ucs2':
      return raw.ucs2
    case 'ndjson':
      return raw.ndjson
    case 'json':
      return raw.json
    case 'binary':
    default:
      return raw.binary
  }
}

function fromCodec(enc) {
  let tmpM = null
  let tmpBuf = null

  return {
    preencode(state, m) {
      tmpM = m
      tmpBuf = enc.encode(m)
      state.end += tmpBuf.byteLength
    },
    encode(state, m) {
      raw.encode(state, m === tmpM ? tmpBuf : enc.encode(m))
      tmpM = tmpBuf = null
    },
    decode(state) {
      return enc.decode(raw.decode(state))
    }
  }
}

function fromAbstractEncoder(enc) {
  return {
    preencode(state, m) {
      state.end += enc.encodingLength(m)
    },
    encode(state, m) {
      enc.encode(m, state.buffer, state.start)
      state.start += enc.encode.bytes
    },
    decode(state) {
      const m = enc.decode(state.buffer, state.start, state.end)
      state.start += enc.decode.bytes
      return m
    }
  }
}

exports.encode = function encode(enc, m) {
  const state = exports.state()
  enc.preencode(state, m)
  state.buffer = b4a.allocUnsafe(state.end)
  enc.encode(state, m)
  return state.buffer
}

exports.decode = function decode(enc, buffer) {
  return enc.decode(exports.state(0, buffer.byteLength, buffer))
}

function zigZagInt(enc) {
  return {
    preencode(state, n) {
      enc.preencode(state, zigZagEncodeInt(n))
    },
    encode(state, n) {
      enc.encode(state, zigZagEncodeInt(n))
    },
    decode(state) {
      return zigZagDecodeInt(enc.decode(state))
    }
  }
}

function zigZagDecodeInt(n) {
  return n === 0 ? n : (n & 1) === 0 ? n / 2 : -(n + 1) / 2
}

function zigZagEncodeInt(n) {
  // 0, -1, 1, -2, 2, ...
  return n < 0 ? 2 * -n - 1 : n === 0 ? 0 : 2 * n
}

function zigZagBigInt(enc) {
  return {
    preencode(state, n) {
      enc.preencode(state, zigZagEncodeBigInt(n))
    },
    encode(state, n) {
      enc.encode(state, zigZagEncodeBigInt(n))
    },
    decode(state) {
      return zigZagDecodeBigInt(enc.decode(state))
    }
  }
}

function zigZagDecodeBigInt(n) {
  return n === 0n ? n : (n & 1n) === 0n ? n / 2n : -(n + 1n) / 2n
}

function zigZagEncodeBigInt(n) {
  // 0, -1, 1, -2, 2, ...
  return n < 0n ? 2n * -n - 1n : n === 0n ? 0n : 2n * n
}

function validateUint(n) {
  if (n >= 0 === false /* Handles NaN as well */)
    throw new Error('uint must be positive')
  // Beyond this, Number arithmetic loses precision and silently corrupts the
  // encoding. The zig-zag int codecs route through here too, so this also
  // guards int values whose doubled magnitude exceeds the safe range.
  if (n > Number.MAX_SAFE_INTEGER)
    throw new Error(
      'integer is greater than the maximum safe integer, use biguint/bigint'
    )
}
module.exports = {
  preencode,
  encode,
  decode
}

function preencode(state, num) {
  if (num < 251) {
    state.end++
  } else if (num < 256) {
    state.end += 2
  } else if (num < 0x10000) {
    state.end += 3
  } else if (num < 0x1000000) {
    state.end += 4
  } else if (num < 0x100000000) {
    state.end += 5
  } else {
    state.end++
    const exp = Math.floor(Math.log(num) / Math.log(2)) - 32
    preencode(state, exp)
    state.end += 6
  }
}

function encode(state, num) {
  const max = 251
  const x = num - max

  if (num < max) {
    state.buffer[state.start++] = num
  } else if (num < 256) {
    state.buffer[state.start++] = max
    state.buffer[state.start++] = x
  } else if (num < 0x10000) {
    state.buffer[state.start++] = max + 1
    state.buffer[state.start++] = (x >> 8) & 0xff
    state.buffer[state.start++] = x & 0xff
  } else if (num < 0x1000000) {
    state.buffer[state.start++] = max + 2
    state.buffer[state.start++] = x >> 16
    state.buffer[state.start++] = (x >> 8) & 0xff
    state.buffer[state.start++] = x & 0xff
  } else if (num < 0x100000000) {
    state.buffer[state.start++] = max + 3
    state.buffer[state.start++] = x >> 24
    state.buffer[state.start++] = (x >> 16) & 0xff
    state.buffer[state.start++] = (x >> 8) & 0xff
    state.buffer[state.start++] = x & 0xff
  } else {
    // need to use Math here as bitwise ops are 32 bit
    const exp = Math.floor(Math.log(x) / Math.log(2)) - 32
    state.buffer[state.start++] = 0xff

    encode(state, exp)
    const rem = x / Math.pow(2, exp - 11)

    for (let i = 5; i >= 0; i--) {
      state.buffer[state.start++] = (rem / Math.pow(2, 8 * i)) & 0xff
    }
  }
}

function decode(state) {
  const max = 251

  if (state.end - state.start < 1) throw new Error('Out of bounds')

  const flag = state.buffer[state.start++]

  if (flag < max) return flag

  if (state.end - state.start < flag - max + 1) {
    throw new Error('Out of bounds.')
  }

  if (flag < 252) {
    return state.buffer[state.start++] + max
  }

  if (flag < 253) {
    return (
      (state.buffer[state.start++] << 8) + state.buffer[state.start++] + max
    )
  }

  if (flag < 254) {
    return (
      (state.buffer[state.start++] << 16) +
      (state.buffer[state.start++] << 8) +
      state.buffer[state.start++] +
      max
    )
  }

  // << 24 result may be interpreted as negative
  if (flag < 255) {
    return (
      state.buffer[state.start++] * 0x1000000 +
      (state.buffer[state.start++] << 16) +
      (state.buffer[state.start++] << 8) +
      state.buffer[state.start++] +
      max
    )
  }

  const exp = decode(state)

  if (state.end - state.start < 6) throw new Error('Out of bounds')

  let rem = 0
  for (let i = 5; i >= 0; i--) {
    rem += state.buffer[state.start++] * Math.pow(2, 8 * i)
  }

  return rem * Math.pow(2, exp - 11) + max
}
{
  "name": "compact-encoding",
  "version": "3.2.0",
  "description": "A series of compact encoding schemes for building small and fast parsers and serializers",
  "main": "index.js",
  "files": [
    "endian.js",
    "index.js",
    "lexint.js",
    "raw.js"
  ],
  "dependencies": {
    "b4a": "^1.3.0"
  },
  "devDependencies": {
    "brittle": "^3.0.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^1.0.0"
  },
  "scripts": {
    "format": "prettier . --write",
    "test": "prettier . --check && brittle test.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/holepunchto/compact-encoding.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/compact-encoding/issues"
  },
  "homepage": "https://github.com/holepunchto/compact-encoding"
}
const b4a = require('b4a')

const { BE } = require('./endian')

exports = module.exports = {
  preencode(state, b) {
    state.end += b.byteLength
  },
  encode(state, b) {
    state.buffer.set(b, state.start)
    state.start += b.byteLength
  },
  decode(state) {
    const b = state.buffer.subarray(state.start, state.end)
    state.start = state.end
    return b
  }
}

const buffer = (exports.buffer = {
  preencode(state, b) {
    uint8array.preencode(state, b)
  },
  encode(state, b) {
    uint8array.encode(state, b)
  },
  decode(state) {
    const b = state.buffer.subarray(state.start)
    state.start = state.end
    return b
  }
})

exports.binary = {
  ...buffer,
  preencode(state, b) {
    if (typeof b === 'string') utf8.preencode(state, b)
    else buffer.preencode(state, b)
  },
  encode(state, b) {
    if (typeof b === 'string') utf8.encode(state, b)
    else buffer.encode(state, b)
  }
}

exports.arraybuffer = {
  preencode(state, b) {
    state.end += b.byteLength
  },
  encode(state, b) {
    const view = new Uint8Array(b)

    state.buffer.set(view, state.start)
    state.start += b.byteLength
  },
  decode(state) {
    const b = new ArrayBuffer(state.end - state.start)
    const view = new Uint8Array(b)

    view.set(state.buffer.subarray(state.start))

    state.start = state.end

    return b
  }
}

function typedarray(TypedArray, swap) {
  const n = TypedArray.BYTES_PER_ELEMENT

  return {
    preencode(state, b) {
      state.end += b.byteLength
    },
    encode(state, b) {
      const view = new Uint8Array(b.buffer, b.byteOffset, b.byteLength)

      if (BE && swap) swap(view)

      state.buffer.set(view, state.start)
      state.start += b.byteLength
    },
    decode(state) {
      let b = state.buffer.subarray(state.start)
      if (b.byteOffset % n !== 0) b = new Uint8Array(b)

      if (BE && swap) swap(b)

      state.start = state.end

      return new TypedArray(b.buffer, b.byteOffset, b.byteLength / n)
    }
  }
}

const uint8array = (exports.uint8array = typedarray(Uint8Array))
exports.uint16array = typedarray(Uint16Array, b4a.swap16)
exports.uint32array = typedarray(Uint32Array, b4a.swap32)

exports.int8array = typedarray(Int8Array)
exports.int16array = typedarray(Int16Array, b4a.swap16)
exports.int32array = typedarray(Int32Array, b4a.swap32)

exports.biguint64array = typedarray(BigUint64Array, b4a.swap64)
exports.bigint64array = typedarray(BigInt64Array, b4a.swap64)

exports.float32array = typedarray(Float32Array, b4a.swap32)
exports.float64array = typedarray(Float64Array, b4a.swap64)

function string(encoding) {
  return {
    preencode(state, s) {
      state.end += b4a.byteLength(s, encoding)
    },
    encode(state, s) {
      state.start += b4a.write(state.buffer, s, state.start, encoding)
    },
    decode(state) {
      const s = b4a.toString(state.buffer, encoding, state.start)
      state.start = state.end
      return s
    }
  }
}

const utf8 = (exports.string = exports.utf8 = string('utf-8'))
exports.ascii = string('ascii')
exports.hex = string('hex')
exports.base64 = string('base64')
exports.ucs2 = exports.utf16le = string('utf16le')

exports.array = function array(enc) {
  return {
    preencode(state, list) {
      for (const value of list) enc.preencode(state, value)
    },
    encode(state, list) {
      for (const value of list) enc.encode(state, value)
    },
    decode(state) {
      const arr = []
      while (state.start < state.end) arr.push(enc.decode(state))
      return arr
    }
  }
}

exports.json = {
  preencode(state, v) {
    utf8.preencode(state, JSON.stringify(v))
  },
  encode(state, v) {
    utf8.encode(state, JSON.stringify(v))
  },
  decode(state) {
    return JSON.parse(utf8.decode(state))
  }
}

exports.ndjson = {
  preencode(state, v) {
    utf8.preencode(state, JSON.stringify(v) + '\n')
  },
  encode(state, v) {
    utf8.encode(state, JSON.stringify(v) + '\n')
  },
  decode(state) {
    return JSON.parse(utf8.decode(state))
  }
}
const { EventEmitter } = require('events')
const Table = require('kademlia-routing-table')
const TOS = require('time-ordered-set')
const UDX = require('udx-native')
const sodium = require('sodium-universal')
const c = require('compact-encoding')
const NatSampler = require('nat-sampler')
const b4a = require('b4a')
const NetworkHealth = require('./lib/health')
const IO = require('./lib/io')
const Query = require('./lib/query')
const Session = require('./lib/session')
const peer = require('./lib/peer')
const { UNKNOWN_COMMAND, INVALID_TOKEN } = require('./lib/errors')
const { PING, PING_NAT, FIND_NODE, DOWN_HINT, DELAYED_PING } = require('./lib/commands')

const TMP = b4a.allocUnsafe(32)
const TICK_INTERVAL = 5000
const SLEEPING_INTERVAL = 3 * TICK_INTERVAL
const STABLE_TICKS = 240 // if nothing major bad happens in ~20mins we can consider this node stable (if nat is friendly)
const MORE_STABLE_TICKS = 3 * STABLE_TICKS
const REFRESH_TICKS = 60 // refresh every ~5min when idle
const RECENT_NODE = 12 // we've heard from a node less than 1min ago
const OLD_NODE = 360 // if an node has been around more than 30 min we consider it old

const DEFAULTS = {
  concurrency: 10,
  maxWindow: IO.DEFAULT_MAX_WINDOW,
  maxPingDelay: 10_000
}

class DHT extends EventEmitter {
  constructor(opts = {}) {
    super()

    this.bootstrapNodes = opts.bootstrap === false ? [] : (opts.bootstrap || []).map(parseNode)
    this.table = new Table(randomBytes(32))
    this.nodes = new TOS()
    this.udx = opts.udx || new UDX()
    this.io = new IO(this.table, this.udx, {
      ...opts,
      onrequest: this._onrequest.bind(this),
      onresponse: this._onresponse.bind(this),
      ontimeout: this._ontimeout.bind(this)
    })
    this.health = new NetworkHealth(this)

    this.concurrency = opts.concurrency || DEFAULTS.concurrency
    this.maxPingDelay = opts.maxPingDelay || DEFAULTS.maxPingDelay
    this.bootstrapped = false
    this.ephemeral = true
    this.firewalled = this.io.firewalled
    this.adaptive = typeof opts.ephemeral !== 'boolean' && opts.adaptive !== false
    this.destroyed = false
    this.suspended = false
    this.online = true
    this.degraded = false
    this.stats = {
      queries: { active: 0, total: 0 },
      requests: this.io.stats.requests,
      commands: {
        ping: this.io.stats.commands[PING],
        pingNat: this.io.stats.commands[PING_NAT],
        findNode: this.io.stats.commands[FIND_NODE],
        downHint: this.io.stats.commands[DOWN_HINT]
      }
    }

    this._nat = new NatSampler()
    this._quickFirewall = opts.quickFirewall !== false
    this._forcePersistent = opts.ephemeral === false
    this._repinging = 0
    this._checks = 0
    this._tick = randomOffset(100) // make sure to random offset all the network ticks
    this._refreshTicks = randomOffset(REFRESH_TICKS)
    this._stableTicks = this.adaptive ? STABLE_TICKS : 0
    this._tickInterval = setInterval(this._ontick.bind(this), TICK_INTERVAL)
    this._lastTick = Date.now()
    this._lastHost = null
    this._filterNode = opts.filterNode || opts.addNode || null // opts.addNode is deprecating, use opts.filterNode instead
    this._onrow = (row) => row.on('full', (node) => this._onfullrow(node, row))
    this._nonePersistentSamples = []
    this._bootstrapping = this._bootstrap()
    this._bootstrapping.catch(noop)
    this._sendDownHints = opts.sendDownHints !== false
    this._downHintsRateLimit =
      opts.downHintsRateLimit !== undefined ? opts.downHintsRateLimit : 10 * 5
    this._downHintsSentPerTick = 0
    this._pendingTimers = new Set()

    this.table.on('row', this._onrow)

    this.io.networkInterfaces.on('change', (interfaces) => this._onnetworkchange(interfaces))

    if (opts.nodes) {
      for (let i = opts.nodes.length - 1; i >= 0; i--) {
        this.addNode(opts.nodes[i])
      }
    }
  }

  static DEFAULTS = DEFAULTS

  static bootstrapper(port, host, opts) {
    if (!port) throw new Error('Port is required')
    if (!host) throw new Error('Host is required')
    if (host === '0.0.0.0' || host === '::') throw new Error('Invalid host')
    if (!UDX.isIPv4(host)) throw new Error('Host must be a IPv4 address')

    const dht = new this({
      port,
      ephemeral: false,
      firewalled: false,
      anyPort: false,
      bootstrap: [],
      ...opts
    })
    dht._nat.add(host, port)
    return dht
  }

  get id() {
    return this.ephemeral ? null : this.table.id
  }

  get host() {
    return this._nat.host
  }

  get port() {
    return this._nat.port
  }

  get randomized() {
    return this._nat.host !== null && this._nat.port === 0
  }

  get socket() {
    return this.firewalled ? this.io.clientSocket : this.io.serverSocket
  }

  get config() {
    return {
      concurrency: this.concurrency,
      maxWindow: this.io.congestion._maxWindow,
      randomPunchInterval: this._randomPunchInterval,
      connectionKeepAlive: this.connectionKeepAlive,
      sendDownHints: this._sendDownHints,
      downHintsRateLimit: this._downHintsRateLimit
    }
  }

  onmessage(socket, buf, rinfo) {
    if (buf.byteLength > 1) this.io.onmessage(socket, buf, rinfo)
  }

  bind() {
    return this.io.bind()
  }

  async suspend({ log = noop } = {}) {
    log('Suspending waiting for io bind...')
    await this.io.bind()
    log('Done, continuing')
    if (this.suspended || this.destroyed) return
    this.suspended = true
    clearInterval(this._tickInterval)
    log('Done, suspending io')
    await this.io.suspend({ log })
    log('Done, dht suspended')
    this.emit('suspend')
  }

  async resume({ log = noop } = {}) {
    if (!this.suspended || this.destroyed) return
    this.suspended = false
    this._tickInterval = setInterval(this._ontick.bind(this), TICK_INTERVAL)
    this._onwakeup()
    log('Resuming io')
    await this.io.resume()
    log('Done, dht resumed')
    this.io.networkInterfaces.on('change', (interfaces) => this._onnetworkchange(interfaces))
    this.refresh()
    this.emit('resume')
  }

  address() {
    const socket = this.socket
    return socket ? socket.address() : null
  }

  localAddress() {
    if (!this.io.serverSocket) return null

    return {
      host: localIP(this.udx),
      port: this.io.serverSocket.address().port
    }
  }

  remoteAddress() {
    if (!this.host) return null
    if (!this.port) return null
    if (this.firewalled) return null
    if (!this.io.serverSocket) return null

    const port = this.io.serverSocket.address().port
    if (port !== this.port) return null

    return {
      host: this.host,
      port
    }
  }

  addNode({ host, port }) {
    this._addNode({
      id: peer.id(host, port),
      port,
      host,
      token: null,
      to: null,
      sampled: 0,
      added: this._tick,
      pinged: 0,
      seen: 0,
      downHints: 0,
      prev: null,
      next: null
    })
  }

  toArray(opts) {
    const limit = opts && opts.limit
    if (limit === 0) return []
    return this.nodes.toArray({ limit, reverse: true }).map(({ host, port }) => ({ host, port }))
  }

  async fullyBootstrapped() {
    return this._bootstrapping
  }

  ready() {
    // Deprecating, use fullyBootstrapped instead (removed on next major)
    return this.fullyBootstrapped()
  }

  findNode(target, opts) {
    if (this.destroyed) throw new Error('Node destroyed')
    this._refreshTicks = REFRESH_TICKS
    return new Query(this, target, true, FIND_NODE, null, opts)
  }

  query({ target, command, value }, opts) {
    if (this.destroyed) throw new Error('Node destroyed')
    this._refreshTicks = REFRESH_TICKS
    return new Query(this, target, false, command, value || null, opts)
  }

  ping({ host, port }, opts) {
    let value = null

    if (opts && opts.size && opts.size > 0) value = b4a.alloc(opts.size)

    const req = this.io.createRequest(
      { id: null, host, port },
      null,
      true,
      PING,
      null,
      value,
      (opts && opts.session) || null,
      opts && opts.ttl
    )
    return this._requestToPromise(req, opts)
  }

  delayedPing({ host, port }, delayMs, opts) {
    if (delayMs > this.maxPingDelay) {
      throw new Error(`Delay exceeds max delay: ${this.maxPingDelay}ms`)
    }

    const value = b4a.allocUnsafe(4)
    c.uint32.encode({ start: 0, end: 4, buffer: value }, delayMs)

    const req = this.io.createRequest(
      { id: null, host, port },
      null,
      true,
      DELAYED_PING,
      null,
      value,
      (opts && opts.session) || null,
      opts && opts.ttl
    )
    // add 1 second to the timeout to account for network overhead
    req.timeout = delayMs + 1_000
    return this._requestToPromise(req, opts)
  }

  async rttStats() {
    const stats = {
      successes: 0,
      errors: 0,
      responses: {
        avgRtt: 0,
        errors: 0,
        avgCloserNodes: 0
      },
      closestReplies: {
        avgRtt: 0,
        errors: 0,
        avgCloserNodes: 0
      }
    }

    if (this.nodes.latest) {
      const q = this.findNode(this.nodes.latest.id)

      let responseCount = 0
      let closestCount = 0

      for await (const msg of q) {
        stats.responses.avgRtt += msg.rtt
        stats.responses.errors += msg.error
        stats.responses.avgCloserNodes += msg.closerNodes?.length || 0
        responseCount++
      }
      stats.responses.avgRtt /= responseCount
      stats.responses.avgCloserNodes /= responseCount

      for await (const msg of q.closestReplies) {
        stats.closestReplies.avgRtt += msg.rtt
        stats.closestReplies.errors += msg.error
        stats.closestReplies.avgCloserNodes += msg.closerNodes?.length || 0
        closestCount++
      }
      stats.closestReplies.avgRtt /= closestCount
      stats.closestReplies.avgCloserNodes /= closestCount

      stats.successes = q.successes
      stats.errors = q.errors
    }

    return stats
  }

  request({ token = null, command, target = null, value = null }, { host, port }, opts) {
    const req = this.io.createRequest(
      { id: null, host, port },
      token,
      false,
      command,
      target,
      value,
      (opts && opts.session) || null,
      opts && opts.ttl
    )
    return this._requestToPromise(req, opts)
  }

  session() {
    return new Session(this)
  }

  _requestToPromise(req, opts) {
    if (req === null) return Promise.reject(new Error('Node destroyed'))

    if (opts && opts.socket) req.socket = opts.socket
    if (opts && opts.retry === false) req.retries = 0

    return new Promise((resolve, reject) => {
      req.onresponse = resolve
      req.onerror = reject
      req.send()
    })
  }

  async _bootstrap() {
    const self = this

    await Promise.resolve() // wait a tick, so apis can be used from the outside
    await this.io.bind()

    this.emit('listening')

    // TODO: some papers describe more advanced ways of bootstrapping - we should prob look into that

    let first = this.firewalled && this._quickFirewall && !this._forcePersistent
    let testNat = false

    const onlyFirewall = !this._forcePersistent

    for (let i = 0; i < 2; i++) {
      await this._backgroundQuery(this.table.id).on('data', ondata).finished()

      if (this.bootstrapped || (!testNat && !this._forcePersistent)) break
      if (!(await this._updateNetworkState(onlyFirewall))) break
    }

    if (this.bootstrapped) return
    this.bootstrapped = true

    this.emit('ready')

    function ondata(data) {
      // Simple QUICK nat heuristic.
      // If we get ONE positive nat ping before the bootstrap query finishes
      // then we always to a nat test, no matter if we are adaptive...
      // This should be expanded in the future to try more than one node etc, not always hit the first etc
      // If this fails, then nbd, as the onstable hook will pick it up later.

      if (!first) return
      first = false

      const value = b4a.allocUnsafe(2)
      c.uint16.encode({ start: 0, end: 2, buffer: value }, self.io.serverSocket.address().port)

      self._request(
        data.from,
        false,
        true,
        PING_NAT,
        null,
        value,
        null,
        () => {
          testNat = true
        },
        noop
      )
    }
  }

  refresh() {
    const node = this.table.random()
    this._backgroundQuery(node ? node.id : this.table.id).on('error', noop)
  }

  async destroy() {
    const emitClose = !this.destroyed
    this.destroyed = true
    clearInterval(this._tickInterval)
    for (const timer of this._pendingTimers) {
      clearTimeout(timer)
    }
    await this.io.destroy()
    if (emitClose) this.emit('close')
  }

  _request(to, force, internal, command, target, value, session, onresponse, onerror) {
    if (internal && !this._sendDownHints && command === DOWN_HINT) return null
    const req = this.io.createRequest(to, null, internal, command, target, value, session)
    if (req === null) return null

    req.onresponse = onresponse
    req.onerror = onerror
    req.send(force)

    return req
  }

  _natAdd(host, port) {
    const prevHost = this._nat.host
    const prevPort = this._nat.port

    this._nat.add(host, port)

    if (prevHost === this._nat.host && prevPort === this._nat.port) return

    this.emit('nat-update', this._nat.host, this._nat.port)
  }

  // we don't check that this is a bootstrap node but we limit the sample size to very few nodes, so fine
  _sampleBootstrapMaybe(from, to) {
    if (this._nonePersistentSamples.length >= Math.max(1, this.bootstrapNodes.length)) return
    const id = from.host + ':' + from.port
    if (this._nonePersistentSamples.indexOf(id) > -1) return
    this._nonePersistentSamples.push(id)
    this._natAdd(to.host, to.port)
  }

  _addNodeFromNetwork(sample, from, to) {
    if (this._filterNode !== null && !this._filterNode(from)) {
      return
    }

    if (from.id === null) {
      this._sampleBootstrapMaybe(from, to)
      return
    }

    const oldNode = this.table.get(from.id)

    // refresh it, if we've seen this before
    if (oldNode) {
      if (sample && (oldNode.sampled === 0 || this._tick - oldNode.sampled >= OLD_NODE)) {
        oldNode.to = to
        oldNode.sampled = this._tick
        this._natAdd(to.host, to.port)
      }

      oldNode.pinged = oldNode.seen = this._tick
      this.nodes.add(oldNode)
      return
    }

    this._addNode({
      id: from.id,
      port: from.port,
      host: from.host,
      to,
      sampled: 0,
      added: this._tick,
      pinged: this._tick, // last time we interacted with them
      seen: this._tick, // last time we heard from them
      downHints: 0,
      prev: null,
      next: null
    })
  }

  _addNode(node) {
    if (this.nodes.has(node) || b4a.equals(node.id, this.table.id)) return

    node.added = node.pinged = node.seen = this._tick

    if (!this.table.add(node)) return
    this.nodes.add(node)

    if (node.to && node.sampled === 0) {
      node.sampled = this._tick
      this._natAdd(node.to.host, node.to.port)
    }

    this.emit('add-node', node)
  }

  _removeStaleNode(node, lastSeen) {
    if (node.seen <= lastSeen) this._removeNode(node)
  }

  _removeNode(node) {
    if (!this.nodes.has(node)) return

    this.table.remove(node.id)
    this.nodes.remove(node)

    this.emit('remove-node', node)
  }

  _onwakeup() {
    this._tick += 2 * OLD_NODE // bump the tick enough that everything appears old.
    this._tick += 8 - (this._tick & 7) - 2 // triggers a series of pings in two ticks
    this._stableTicks = MORE_STABLE_TICKS
    this._refreshTicks = 1 // triggers a refresh next tick (allow network time to wake up also)
    this._lastHost = null // clear network cache check
    this.health.reset()

    if (this.adaptive) {
      // TODO: re-enable this as soon as we find out why this is over triggering in some edge cases
      // this.firewalled = true
      // this.io.firewalled = true

      if (!this.ephemeral) {
        this.ephemeral = true
        this.io.ephemeral = true
        this.emit('ephemeral')
      }
    }

    this.emit('wakeup')
  }

  _onfullrow(newNode, row) {
    if (!this.bootstrapped || this._repinging >= 3) return

    let oldest = null
    for (const node of row.nodes) {
      if (node.pinged === this._tick) continue
      if (
        oldest === null ||
        oldest.pinged > node.pinged ||
        (oldest.pinged === node.pinged && oldest.added > node.added)
      ) {
        oldest = node
      }
    }

    if (oldest === null) return
    if (this._tick - oldest.pinged < RECENT_NODE && this._tick - oldest.added > OLD_NODE) return

    this._repingAndSwap(newNode, oldest)
  }

  _onnetworkchange(interfaces) {
    this.emit('network-change', interfaces)
    this.emit('network-update')
  }

  _repingAndSwap(newNode, oldNode) {
    const self = this
    const lastSeen = oldNode.seen

    oldNode.pinged = this._tick

    this._repinging++
    this._request(
      { id: null, host: oldNode.host, port: oldNode.port },
      false,
      true,
      PING,
      null,
      null,
      null,
      onsuccess,
      onswap
    )

    function onsuccess(m) {
      if (oldNode.seen <= lastSeen) return onswap()
      self._repinging--
    }

    function onswap(e) {
      self._repinging--
      self._removeNode(oldNode)
      self._addNode(newNode)
    }
  }

  _onrequest(req, external) {
    if (req.from.id !== null) {
      this._addNodeFromNetwork(!external, req.from, req.to)
    }

    if (req.internal) {
      switch (req.command) {
        // standard keep alive call
        case PING: {
          req.sendReply(0, null, false, false)
          return
        }
        case DELAYED_PING: {
          this._ondelayedping(req)
          return
        }
        // check if the other side can receive a message to their other socket
        case PING_NAT: {
          if (req.value === null || req.value.byteLength < 2) return
          const port = c.uint16.decode({ start: 0, end: 2, buffer: req.value })
          if (port === 0) return
          req.from.port = port
          req.sendReply(0, null, false, false)
          return
        }
        // empty dht reply back
        case FIND_NODE: {
          if (!req.target) return
          req.sendReply(0, null, false, true)
          return
        }
        // "this is node you sent me is down" - let's try to ping it
        case DOWN_HINT: {
          if (req.value === null || req.value.byteLength < 6) return
          if (this._checks < 10) {
            sodium.crypto_generichash(TMP, req.value.subarray(0, 6))
            const node = this.table.get(TMP)
            if (node && (node.pinged < this._tick || node.downHints === 0)) {
              node.downHints++
              this._check(node)
            }
          }
          req.sendReply(0, null, false, false)
          return
        }
      }

      req.sendReply(UNKNOWN_COMMAND, null, false, req.target !== null)
      return
    }

    // ask the user to handle it or reply back with a bad command
    if (this.onrequest(req) === false) {
      req.sendReply(UNKNOWN_COMMAND, null, false, req.target !== null)
    }
  }

  onrequest(req) {
    return this.emit('request', req)
  }

  _ondelayedping(req) {
    if (req.value === null || req.value.byteLength < 4) return
    const delayMs = c.uint32.decode({ start: 0, end: 4, buffer: req.value })
    if (delayMs > this.maxPingDelay) return
    const timer = setTimeout(() => {
      if (this.destroyed) return
      this._pendingTimers.delete(timer)
      req.sendReply(0, null, false, false)
    }, delayMs)
    this._pendingTimers.add(timer)
  }

  _onresponse(res, external) {
    this._addNodeFromNetwork(!external, res.from, res.to)
  }

  _ontimeout(req) {
    if (!req.to.id) return
    const node = this.table.get(req.to.id)
    if (node) this._removeNode(node)
  }

  _pingSome() {
    let cnt = this.io.inflight.length > 2 ? 3 : 5
    let oldest = this.nodes.oldest

    // tiny dht, pinged the bootstrap again
    if (!oldest) {
      this.refresh()
      return
    }

    // we've recently pinged the oldest one, so only trigger a couple of repings
    if (this._tick - oldest.pinged < RECENT_NODE) {
      cnt = 2
    }

    while (cnt--) {
      if (!oldest || this._tick === oldest.pinged) continue
      this._check(oldest)
      oldest = oldest.next
    }
  }

  _check(node) {
    node.pinged = this._tick

    const lastSeen = node.seen
    const onresponse = () => {
      this._checks--
      this._removeStaleNode(node, lastSeen)
    }
    const onerror = () => {
      this._checks--
      this._removeNode(node)
    }

    this._checks++
    this._request(
      { id: null, host: node.host, port: node.port },
      false,
      true,
      PING,
      null,
      null,
      null,
      onresponse,
      onerror
    )
  }

  _ontick() {
    const time = Date.now()

    if (time - this._lastTick > SLEEPING_INTERVAL && this.suspended === false) {
      this._onwakeup()
    } else {
      this._tick++
    }

    this._lastTick = time

    if (!this.bootstrapped || this.suspended) return

    if (this.adaptive && this.ephemeral && --this._stableTicks <= 0) {
      if (this._lastHost === this._nat.host) {
        // do not recheck the same network...
        this._stableTicks = MORE_STABLE_TICKS
      } else {
        this._updateNetworkState() // the promise returned here never fails so just ignore it
      }
    }

    if ((this._tick & 7) === 0) {
      this._pingSome()
    }

    if (
      ((this._tick & 63) === 0 && this.nodes.length < this.table.k) ||
      --this._refreshTicks <= 0
    ) {
      this.refresh()
    }

    this._downHintsSentPerTick = 0
    this.health.update()
  }

  async _updateNetworkState(onlyFirewall = false) {
    if (!this.ephemeral) return false
    if (onlyFirewall && !this.firewalled) return false

    const { host, port } = this._nat

    if (!onlyFirewall) {
      // remember what host we checked and reset the counter
      this._stableTicks = MORE_STABLE_TICKS
      this._lastHost = host
    }

    // check if we have a consistent host and port
    if (host === null || port === 0) {
      return false
    }

    const natSampler = this.firewalled ? new NatSampler() : this._nat

    // ask remote nodes to ping us on our server socket to see if we have the port open
    const firewalled = this.firewalled && (await this._checkIfFirewalled(natSampler))
    if (firewalled) return false

    this.firewalled = this.io.firewalled = false

    // incase it's called in parallel for some reason, or if our nat status somehow changed
    if (!this.ephemeral || host !== this._nat.host || port !== this._nat.port) return false
    // if the firewall probe returned a different host / non consistent port, bail as well
    if (natSampler.host !== host || natSampler.port === 0) return false

    const id = peer.id(natSampler.host, natSampler.port)

    if (!onlyFirewall) {
      this.ephemeral = this.io.ephemeral = false
    }

    if (natSampler !== this._nat) {
      const prevHost = this._nat.host
      const prevPort = this._nat.port

      this._nonePersistentSamples = []
      this._nat = natSampler

      if (prevHost !== this._nat.host || prevPort !== this._nat.port) {
        this.emit('nat-update', this._nat.host, this._nat.port)
      }
    }

    // TODO: we should make this a bit more defensive in terms of using more
    // resources to make sure that the new routing table contains as many alive nodes
    // as possible, vs blindly copying them over...

    // all good! copy over the old routing table to the new one
    if (!b4a.equals(this.table.id, id)) {
      const nodes = this.table.toArray()

      this.table = this.io.table = new Table(id)

      for (const node of nodes) {
        if (b4a.equals(node.id, id)) continue
        if (!this.table.add(node)) this.nodes.remove(node)
      }

      this.table.on('row', this._onrow)

      // we need to rebootstrap/refresh since we updated our id
      if (this.bootstrapped) this.refresh()
    }

    if (!this.ephemeral) {
      this.emit('persistent')
    }

    return true
  }

  async *_resolveBootstrapNodes() {
    for (let { host, port } of this.bootstrapNodes) {
      let doLookup = false

      if (host.indexOf('@') === -1) {
        doLookup = true
      } else {
        const [suggestedIP, fallbackHost] = host.split('@')
        try {
          await this.ping({ host: suggestedIP, port })
          host = suggestedIP
        } catch {
          host = fallbackHost
          doLookup = true
        }
      }

      if (doLookup) {
        try {
          host = UDX.isIPv4(host) ? host : (await this.udx.lookup(host, { family: 4 })).host
        } catch {
          continue
        }
      }

      yield {
        id: peer.id(host, port),
        host,
        port
      }
    }
  }

  async _addBootstrapNodes(nodes) {
    for await (const node of this._resolveBootstrapNodes()) {
      nodes.push(node)
    }
  }

  async _checkIfFirewalled(natSampler = new NatSampler()) {
    const nodes = []
    for (let node = this.nodes.latest; node && nodes.length < 5; node = node.prev) {
      nodes.push(node)
    }

    if (nodes.length < 5) await this._addBootstrapNodes(nodes)
    // if no nodes are available, including bootstrappers - bail
    if (nodes.length === 0) return true

    const hosts = new Set()
    const value = b4a.allocUnsafe(2)

    c.uint16.encode({ start: 0, end: 2, buffer: value }, this.io.serverSocket.address().port)

    // double check they actually came on the server socket...
    this.io.serverSocket.on('message', onmessage)

    const pongs = await requestAll(this, true, PING_NAT, value, nodes)

    let count = 0
    for (const res of pongs) {
      if (hosts.has(res.from.host)) {
        count++
        natSampler.add(res.to.host, res.to.port)
      }
    }

    this.io.serverSocket.removeListener('message', onmessage)

    // if we got no or very few replies, consider it a fluke
    if (count < (nodes.length >= 5 ? 3 : 1)) return true

    // check that the server socket has the same ip as the client socket
    if (natSampler.host === null || this._nat.host !== natSampler.host) return true

    // check that the local port of the server socket is the same as the remote port
    // TODO: we might want a flag to opt out of this heuristic for specific remapped port servers
    if (natSampler.port === 0 || natSampler.port !== this.io.serverSocket.address().port) {
      return true
    }

    return false

    function onmessage(_, { host }) {
      hosts.add(host)
    }
  }

  _backgroundQuery(target) {
    this._refreshTicks = REFRESH_TICKS

    const backgroundCon = Math.min(this.concurrency, Math.max(2, (this.concurrency / 8) | 0))
    const q = new Query(this, target, true, FIND_NODE, null, {
      concurrency: backgroundCon
    })

    q.on('data', () => {
      // yield to other traffic
      q.concurrency = this.io.inflight.length < 3 ? this.concurrency : backgroundCon
    })

    return q
  }

  // called by health
  _online() {
    if (this.online && !this.degraded) return
    this.online = true
    this.degraded = false
    this.emit('network-update')
  }

  // called by health
  _degraded() {
    if (this.degraded) return
    this.online = true
    this.degraded = true
    this.emit('network-update')
  }

  // called by health
  _offline() {
    if (!this.online) return
    this.online = false
    this.degraded = false
    this.emit('network-update')
  }
}

DHT.OK = 0
DHT.ERROR_UNKNOWN_COMMAND = UNKNOWN_COMMAND
DHT.ERROR_INVALID_TOKEN = INVALID_TOKEN

module.exports = DHT

function localIP(udx, family = 4) {
  let host = null

  for (const n of udx.networkInterfaces()) {
    if (n.family !== family || n.internal) continue

    // mac really likes en0, mb a better way but this shouldnt be bad anywhere so return now
    if (n.name === 'en0') return n.host

    // otherwise pick the first non internal host (let the loop continue in case we see en0)
    if (host === null) host = n.host
  }

  return host || (family === 4 ? '127.0.0.1' : '::1')
}

function parseNode(s) {
  if (typeof s === 'object') return s
  if (typeof s === 'number') return { host: '127.0.0.1', port: s }
  const [host, port] = s.split(':')
  if (!port) throw new Error('Bootstrap node format is host:port')

  return {
    host,
    port: Number(port)
  }
}

function randomBytes(n) {
  const b = b4a.alloc(n)
  sodium.randombytes_buf(b)
  return b
}

function randomOffset(n) {
  return n - ((Math.random() * 0.5 * n) | 0)
}

function requestAll(dht, internal, command, value, nodes) {
  let missing = nodes.length
  const replies = []

  return new Promise((resolve) => {
    for (const node of nodes) {
      const req = dht._request(
        node,
        false,
        internal,
        command,
        null,
        value,
        null,
        onsuccess,
        onerror
      )
      if (!req) return resolve(replies)
    }

    function onsuccess(res) {
      replies.push(res)
      if (--missing === 0) resolve(replies)
    }

    function onerror() {
      if (--missing === 0) resolve(replies)
    }
  })
}

function noop() {}
exports.PING = 0
exports.PING_NAT = 1
exports.FIND_NODE = 2
exports.DOWN_HINT = 3
exports.DELAYED_PING = 4
module.exports = class DHTError extends Error {
  constructor(msg, code, fn = DHTError) {
    super(`${code}: ${msg}`)
    this.code = code

    if (Error.captureStackTrace) {
      Error.captureStackTrace(this, fn)
    }
  }

  get name() {
    return 'DHTError'
  }

  static UNKNOWN_COMMAND = 1
  static INVALID_TOKEN = 2

  static REQUEST_TIMEOUT(msg = 'Request timed out') {
    return new DHTError(msg, 'REQUEST_TIMEOUT', DHTError.REQUEST_TIMEOUT)
  }

  static REQUEST_DESTROYED(msg = 'Request destroyed') {
    return new DHTError(msg, 'REQUEST_DESTROYED', DHTError.REQUEST_DESTROYED)
  }

  static IO_SUSPENDED(msg = 'I/O suspended') {
    return new DHTError(msg, 'IO_SUSPENDED', DHTError.IO_SUSPENDED)
  }
}
const MAX_HEALTH_WINDOW = 4
const OFFLINE_THRESHOLD = 2
const IDLE_THRESHOLD = 4
const DEGRADED_TIMEOUT_RATE_THRESHOLD = 0.5

module.exports = class NetworkHealth {
  constructor(dht) {
    this._dht = dht
    this._window = []
    this._head = -1
    this._offlineTicks = 0
    this._degradedTicks = 0
    this._healthyTicks = 0
    this.online = true
    this.degraded = false
  }

  get oldest() {
    return this._window[this._tail]
  }

  get previous() {
    return this._window[(this._head - 1 + MAX_HEALTH_WINDOW) % MAX_HEALTH_WINDOW]
  }

  get newest() {
    return this._window[this._head]
  }

  get responses() {
    if (!this.newest || !this.previous) return 0
    return this.newest.responses - this.previous.responses
  }

  get timeouts() {
    if (!this.newest || !this.previous) return 0
    return this.newest.timeouts - this.previous.timeouts
  }

  get timeoutsRate() {
    if (this.timeouts === 0) return 0
    return this.timeouts / (this.responses + this.timeouts)
  }

  get cold() {
    return this._window.length < MAX_HEALTH_WINDOW
  }

  get idle() {
    return this.responses + this.timeouts < IDLE_THRESHOLD
  }

  get stats() {
    return {
      online: this.online,
      degraded: this.degraded,
      cold: this.cold,
      idle: this.idle,
      responses: this.responses,
      timeouts: this.timeouts,
      timeoutsRate: this.timeoutsRate
    }
  }

  get _tail() {
    return (this._head + 1) % MAX_HEALTH_WINDOW
  }

  reset() {
    this._window = []
    this._head = -1
    this._offlineTicks = 0
    this._degradedTicks = 0
    this._healthyTicks = 0
    this.online = true
    this.degraded = false
    this._dht._online()
  }

  update() {
    // update counters before dropping oldest
    if (this.oldest?.degraded) this._degradedTicks--
    else if (this.oldest?.degraded === false) this._healthyTicks--

    // move window
    this._head = this._tail
    this._window[this._head] = {
      responses: this._dht.stats.requests.responses,
      timeouts: this._dht.stats.requests.timeouts
    }

    // skip state changes
    if (this.cold || this.idle) return

    // update newest
    this.newest.offline = this.responses === 0
    this.newest.degraded =
      !this.newest.offline && this.timeoutsRate > DEGRADED_TIMEOUT_RATE_THRESHOLD

    // update counters
    if (this.newest.offline) this._offlineTicks++
    else {
      this._offlineTicks = 0
      if (this.newest.degraded) this._degradedTicks++
      else this._healthyTicks++
    }

    // check online/offline
    if (!this.newest.offline) this.online = true
    else if (this._offlineTicks >= OFFLINE_THRESHOLD) this.online = false

    // check degraded
    if (!this.online || this._healthyTicks === MAX_HEALTH_WINDOW) this.degraded = false
    else if (this._degradedTicks === MAX_HEALTH_WINDOW) this.degraded = true

    // fire events
    if (this.online && !this.degraded) this._dht._online()
    else if (this.degraded) this._dht._degraded()
    else this._dht._offline()
  }
}
const FIFO = require('fast-fifo')
const sodium = require('sodium-universal')
const c = require('compact-encoding')
const b4a = require('b4a')
const AdaptiveTimeout = require('adaptive-timeout')
const peer = require('./peer')
const { INVALID_TOKEN, REQUEST_TIMEOUT, REQUEST_DESTROYED, IO_SUSPENDED } = require('./errors')

const VERSION = 0b11
const RESPONSE_ID = (0b0001 << 4) | VERSION
const REQUEST_ID = (0b0000 << 4) | VERSION
const EMPTY_ARRAY = []
const MAX_WINDOW = 80

module.exports = class IO {
  constructor(
    table,
    udx,
    {
      maxWindow = MAX_WINDOW,
      port = 0,
      host = '0.0.0.0',
      anyPort = true,
      firewalled = true,
      onrequest,
      onresponse = noop,
      ontimeout = noop,
      adaptiveTimeout
    } = {}
  ) {
    this.table = table
    this.udx = udx
    this.inflight = []
    this.clientSocket = null
    this.serverSocket = null
    this.firewalled = firewalled !== false
    this.ephemeral = true
    this.congestion = new CongestionWindow(maxWindow)
    this.networkInterfaces = udx.watchNetworkInterfaces()
    this.suspended = false

    this.stats = {
      requests: {
        active: 0,
        total: 0,
        responses: 0,
        timeouts: 0,
        retries: 0
      },
      commands: [
        { tx: 0, rx: 0 }, // tx = transmitted, rx = received
        { tx: 0, rx: 0 },
        { tx: 0, rx: 0 },
        { tx: 0, rx: 0 }
      ]
    }

    this.onrequest = onrequest
    this.onresponse = onresponse
    this.ontimeout = ontimeout

    this._pending = new FIFO()
    this._rotateSecrets = 10
    this._tid = (Math.random() * 65536) | 0
    this._secrets = null
    this._drainInterval = null
    this._destroying = null
    this._binding = null

    // port can be a number or a range [start, to]
    this.portRange = port.length ? port : port === 0 ? [0, 0] : [port, port + 5]

    this._host = host
    this._anyPort = anyPort !== false
    this._boundServerPort = 0
    this._boundClientPort = 0

    this._adt = adaptiveTimeout ? new AdaptiveTimeout(adaptiveTimeout) : null
  }

  static DEFAULT_MAX_WINDOW = MAX_WINDOW

  onmessage(socket, buffer, { host, port }) {
    if (buffer.byteLength < 2 || !(port > 0 && port < 65536) || this.suspended === true) return

    const from = { id: null, host, port }
    const state = { start: 1, end: buffer.byteLength, buffer }
    const expectedSocket = this.firewalled ? this.clientSocket : this.serverSocket
    const external = socket !== expectedSocket

    if (buffer[0] === REQUEST_ID) {
      const req = Request.decode(this, socket, from, state)
      if (req === null) return
      if (
        req.token !== null &&
        !b4a.equals(req.token, this.token(req.from, 1)) &&
        !b4a.equals(req.token, this.token(req.from, 0))
      ) {
        req.error(INVALID_TOKEN, { token: true })
        return
      }
      this.onrequest(req, external)
      return
    }

    if (buffer[0] === RESPONSE_ID) {
      const res = decodeReply(from, state)
      if (res === null) return

      for (let i = 0; i < this.inflight.length; i++) {
        const req = this.inflight[i]
        if (req.tid !== res.tid) continue

        res.rtt = Date.now() - req._timestamp

        if (this._adt && req.sent <= 2) {
          this._adt.put(`${req.to.host}:${req.to.port}`, res.rtt)
        }

        if (i === this.inflight.length - 1) this.inflight.pop()
        else this.inflight[i] = this.inflight.pop()

        if (req.session) req.session._detach(req)

        // TODO: Auto retry here if errors.INVALID_TOKEN is returned?

        if (req._timeout) {
          clearTimeout(req._timeout)
          req._timeout = null
        }

        this.congestion.recv()

        if (req.internal && req.command < this.stats.commands.length) {
          this.stats.commands[req.command].rx++
        }

        this.stats.requests.active--
        this.stats.requests.responses++

        this.onresponse(res, external)
        req.onresponse(res, req)
        break
      }
    }
  }

  token(addr, i) {
    if (this._secrets === null) {
      const buf = b4a.alloc(64)
      this._secrets = [buf.subarray(0, 32), buf.subarray(32, 64)]
      sodium.randombytes_buf(this._secrets[0])
      sodium.randombytes_buf(this._secrets[1])
    }

    const token = b4a.allocUnsafe(32)
    sodium.crypto_generichash(token, b4a.from(addr.host), this._secrets[i])
    return token
  }

  async destroy() {
    if (this._destroying) return this._destroying
    this._destroying = this._destroy()
    return this._destroying
  }

  async _destroy() {
    // simplifies timing to await the bind here also, although it might be unneeded
    await this.bind()
    await this._clear(false)
  }

  async _clear(suspended) {
    if (this._drainInterval) {
      clearInterval(this._drainInterval)
      this._drainInterval = null
    }

    while (this.inflight.length) {
      const req = this.inflight.pop()
      if (req._timeout) clearTimeout(req._timeout)
      req._timeout = null
      req.destroyed = true

      if (req.session) req.session._detach(req)

      this.congestion.recv()
      this.stats.requests.active--

      req.onerror(suspended ? IO_SUSPENDED() : REQUEST_DESTROYED(), req)
    }

    await Promise.allSettled([this.serverSocket.close(), this.clientSocket.close()])

    this.networkInterfaces.destroy()
  }

  async suspend() {
    this.suspended = true
    await this._clear(true)

    this.congestion.clear()

    if (this._drainInterval) {
      clearInterval(this._drainInterval)
      this._drainInterval = null
    }
  }

  async _rebind(binding) {
    if (binding) await binding
    if (this._destroying) return this._destroying
    await this._bindSockets()
    this.networkInterfaces = this.udx.watchNetworkInterfaces()
  }

  resume() {
    this.suspended = false
    const binding = this._binding
    this._binding = this._rebind(binding)
    return this._binding
  }

  bind() {
    if (this._binding) return this._binding
    this._binding = this._bindSockets()
    return this._binding
  }

  async _bindSockets() {
    const serverSocket = this.udx.createSocket()

    const candidatePorts = []

    // Retrying previous port always has precedence
    if (this._boundServerPort) candidatePorts.push(this._boundServerPort)

    for (let i = this.portRange[0]; i < this.portRange[1]; i++) candidatePorts.push(i)

    for (const port of candidatePorts) {
      if (serverSocket.bound) break

      try {
        serverSocket.bind(port, this._host)
      } catch (err) {
        if (!this._anyPort) {
          await serverSocket.close()
          throw err
        }
      }
    }

    if (!serverSocket.bound) {
      try {
        serverSocket.bind(0, this._host)
      } catch (err) {
        await serverSocket.close()
        throw err
      }
    }

    const clientSocket = this.udx.createSocket()

    try {
      clientSocket.bind(this._boundClientPort || 0, this._host)
    } catch {
      try {
        clientSocket.bind(0, this._host)
      } catch (err) {
        await serverSocket.close()
        await clientSocket.close()
        throw err
      }
    }

    this._boundServerPort = serverSocket.address().port
    this._boundClientPort = clientSocket.address().port

    this.clientSocket = clientSocket
    this.serverSocket = serverSocket

    this.serverSocket.on('message', this.onmessage.bind(this, this.serverSocket))
    this.clientSocket.on('message', this.onmessage.bind(this, this.clientSocket))

    if (this._drainInterval === null) {
      this._drainInterval = setInterval(this._drain.bind(this), 750)
      if (this._drainInterval.unref) this._drainInterval.unref()
    }

    for (const req of this.inflight) {
      if (!req.socket) req.socket = this.firewalled ? this.clientSocket : this.serverSocket
      req.sent = 0
      req.send(false)
    }
  }

  _drain() {
    if (this._secrets !== null && --this._rotateSecrets === 0) {
      this._rotateSecrets = 10
      const tmp = this._secrets[0]
      this._secrets[0] = this._secrets[1]
      this._secrets[1] = tmp
      sodium.crypto_generichash(tmp, tmp)
    }

    this.congestion.drain()

    while (!this.congestion.isFull()) {
      const p = this._pending.shift()
      if (p === undefined) return
      p._sendNow()
    }
  }

  createRequest(to, token, internal, command, target, value, session, ttl) {
    if (this._destroying !== null) return null

    if (this._tid === 65536) this._tid = 0

    const tid = this._tid++
    const socket = this.firewalled ? this.clientSocket : this.serverSocket

    const req = new Request(
      this,
      socket,
      tid,
      null,
      to,
      token,
      internal,
      command,
      target,
      value,
      session,
      ttl || 0
    )
    this.inflight.push(req)
    if (session) session._attach(req)

    if (internal && command < this.stats.commands.length) {
      this.stats.commands[command].tx++
    }

    this.stats.requests.active++
    this.stats.requests.total++

    return req
  }
}

class Request {
  constructor(io, socket, tid, from, to, token, internal, command, target, value, session, ttl) {
    this.socket = socket
    this.tid = tid
    this.from = from
    this.to = to
    this.token = token
    this.command = command
    this.target = target
    this.value = value
    this.internal = internal
    this.session = session
    this.ttl = ttl
    this.index = -1
    this.sent = 0
    this.retries = 3
    this.destroyed = false
    this.timeout = 0

    this.oncycle = noop
    this.onerror = noop
    this.onresponse = noop

    this._buffer = null
    this._io = io
    this._timeout = null
    this._timestamp = Date.now()
  }

  static decode(io, socket, from, state) {
    try {
      const flags = c.uint.decode(state)
      const tid = c.uint16.decode(state)
      const to = peer.ipv4.decode(state)
      const id = flags & 1 ? c.fixed32.decode(state) : null
      const token = flags & 2 ? c.fixed32.decode(state) : null
      const internal = (flags & 4) !== 0
      const command = c.uint.decode(state)
      const target = flags & 8 ? c.fixed32.decode(state) : null
      const value = flags & 16 ? c.buffer.decode(state) : null

      if (id !== null) from.id = validateId(id, from)

      return new Request(
        io,
        socket,
        tid,
        from,
        to,
        token,
        internal,
        command,
        target,
        value,
        null,
        0
      )
    } catch {
      return null
    }
  }

  reply(value, opts = {}) {
    const socket = opts.socket || this.socket
    const to = opts.to || this.from
    this._sendReply(0, value || null, opts.token !== false, opts.closerNodes !== false, to, socket)
  }

  error(code, opts = {}) {
    const socket = opts.socket || this.socket
    const to = opts.to || this.from
    this._sendReply(code, null, opts.token === true, opts.closerNodes !== false, to, socket)
  }

  relay(value, to, opts) {
    const socket = (opts && opts.socket) || this.socket
    const buffer = this._encodeRequest(null, value, to, socket)
    socket.trySend(buffer, to.port, to.host, this.ttl)
  }

  send(force = false) {
    if (this.destroyed) return

    if (this.socket === null) return
    if (this._buffer === null) {
      this._buffer = this._encodeRequest(this.token, this.value, this.to, this.socket)
    }

    if (!force && this._io.congestion.isFull()) {
      this._io._pending.push(this)
      return
    }

    this._sendNow()
  }

  sendReply(error, value, token, hasCloserNodes) {
    this._sendReply(error, value, token, hasCloserNodes, this.from, this.socket, null)
  }

  _sendNow() {
    if (this.destroyed) return
    this.sent++
    this._io.congestion.send()
    this.socket.trySend(this._buffer, this.to.port, this.to.host, this.ttl)
    if (this._timeout) clearTimeout(this._timeout)
    const value =
      this.timeout || this._io._adt?.get(`${this.to.host}:${this.to.port}`, this.sent) || 1000
    this._timeout = setTimeout(oncycle, value, this)
  }

  destroy(err) {
    if (this.destroyed) return
    this.destroyed = true

    if (this._timeout) {
      clearTimeout(this._timeout)
      this._timeout = null
    }

    const i = this._io.inflight.indexOf(this)
    if (i === -1) return

    if (i === this._io.inflight.length - 1) this._io.inflight.pop()
    else this._io.inflight[i] = this._io.inflight.pop()

    if (this.session) this.session._detach(this)

    this._io.stats.requests.active--
    this._io.congestion.recv()

    this.onerror(err || REQUEST_DESTROYED(), this)
  }

  _sendReply(error, value, token, hasCloserNodes, from, socket) {
    if (socket === null || this.destroyed) return

    const id = this._io.ephemeral === false && socket === this._io.serverSocket
    const closerNodes =
      this.target !== null && hasCloserNodes ? this._io.table.closest(this.target) : EMPTY_ARRAY
    const state = { start: 0, end: 1 + 1 + 6 + 2, buffer: null } // (type | version) + flags + to + tid

    if (id) state.end += 32
    if (token) state.end += 32
    if (closerNodes.length > 0) peer.ipv4Array.preencode(state, closerNodes)
    if (error > 0) c.uint.preencode(state, error)
    if (value) c.buffer.preencode(state, value)

    state.buffer = b4a.allocUnsafe(state.end)
    state.buffer[state.start++] = RESPONSE_ID
    state.buffer[state.start++] =
      (id ? 1 : 0) |
      (token ? 2 : 0) |
      (closerNodes.length > 0 ? 4 : 0) |
      (error > 0 ? 8 : 0) |
      (value ? 16 : 0)

    c.uint16.encode(state, this.tid)
    peer.ipv4.encode(state, from)

    if (id) c.fixed32.encode(state, this._io.table.id)
    if (token) c.fixed32.encode(state, this._io.token(from, 1))
    if (closerNodes.length > 0) peer.ipv4Array.encode(state, closerNodes)
    if (error > 0) c.uint.encode(state, error)
    if (value) c.buffer.encode(state, value)

    socket.trySend(state.buffer, from.port, from.host, this.ttl)
  }

  _encodeRequest(token, value, to, socket) {
    const id = this._io.ephemeral === false && socket === this._io.serverSocket
    const state = { start: 0, end: 1 + 1 + 6 + 2, buffer: null } // (type | version) + flags + to + tid

    if (id) state.end += 32
    if (token) state.end += 32

    c.uint.preencode(state, this.command)

    if (this.target) state.end += 32
    if (value) c.buffer.preencode(state, value)

    state.buffer = b4a.allocUnsafe(state.end)
    state.buffer[state.start++] = REQUEST_ID
    state.buffer[state.start++] =
      (id ? 1 : 0) |
      (token ? 2 : 0) |
      (this.internal ? 4 : 0) |
      (this.target ? 8 : 0) |
      (value ? 16 : 0)

    c.uint16.encode(state, this.tid)
    peer.ipv4.encode(state, to)

    if (id) c.fixed32.encode(state, this._io.table.id)
    if (token) c.fixed32.encode(state, token)

    c.uint.encode(state, this.command)

    if (this.target) c.fixed32.encode(state, this.target)
    if (value) c.buffer.encode(state, value)

    return state.buffer
  }
}

class CongestionWindow {
  constructor(maxWindow) {
    this._i = 0
    this._total = 0
    this._window = [0, 0, 0, 0]
    this._maxWindow = maxWindow
  }

  clear() {
    this._i = 0
    this._total = 0
    this._window = [0, 0, 0, 0]
  }

  isFull() {
    return this._total >= 2 * this._maxWindow || this._window[this._i] >= this._maxWindow
  }

  recv() {
    if (this._window[this._i] > 0) {
      this._window[this._i]--
      this._total--
    }
  }

  send() {
    this._total++
    this._window[this._i]++
  }

  drain() {
    this._i = (this._i + 1) & 3
    this._total -= this._window[this._i]
    this._window[this._i] = 0 // clear oldest
  }
}

function noop() {}

function oncycle(req) {
  req._timeout = null
  req.oncycle(req)
  if (req.sent > req.retries) {
    req._io.stats.requests.timeouts++
    req.destroy(REQUEST_TIMEOUT())
    req._io.ontimeout(req)
  } else {
    req._io.stats.requests.retries++
    req.send()
  }
}

function decodeReply(from, state) {
  try {
    const flags = c.uint.decode(state)
    const tid = c.uint16.decode(state)
    const to = peer.ipv4.decode(state)
    const id = flags & 1 ? c.fixed32.decode(state) : null
    const token = flags & 2 ? c.fixed32.decode(state) : null
    const closerNodes = flags & 4 ? peer.ipv4Array.decode(state) : null
    const error = flags & 8 ? c.uint.decode(state) : 0
    const value = flags & 16 ? c.buffer.decode(state) : null

    if (id !== null) from.id = validateId(id, from)

    return { tid, rtt: 0, from, to, token, closerNodes, error, value }
  } catch {
    return null
  }
}

function validateId(id, from) {
  const expected = peer.id(from.host, from.port)
  return b4a.equals(expected, id) ? expected : null
}
const sodium = require('sodium-universal')
const c = require('compact-encoding')
const net = require('compact-encoding-net')
const b4a = require('b4a')

const ipv4 = {
  ...net.ipv4Address,
  decode(state) {
    const ip = net.ipv4Address.decode(state)
    return {
      id: null, // populated by the callee
      host: ip.host,
      port: ip.port
    }
  }
}

module.exports = { id, ipv4, ipv4Array: c.array(ipv4) }

function id(host, port, out = b4a.allocUnsafeSlow(32)) {
  const addr = out.subarray(0, 6)
  ipv4.encode({ start: 0, end: 6, buffer: addr }, { host, port })
  sodium.crypto_generichash(out, addr)
  return out
}
const { Readable, getStreamError } = require('streamx')
const b4a = require('b4a')
const peer = require('./peer')
const { DOWN_HINT } = require('./commands')

const DONE = []
const DOWN = []

module.exports = class Query extends Readable {
  constructor(dht, target, internal, command, value, opts = {}) {
    super()

    dht.stats.queries.total++
    dht.stats.queries.active++

    this.force = !!opts.force
    this.dht = dht
    this.k = this.dht.table.k
    this.target = target
    this.internal = internal
    this.command = command
    this.value = value
    this.errors = 0
    this.successes = 0
    this.concurrency = opts.concurrency || this.dht.concurrency
    this.inflight = 0
    this.map = opts.map || defaultMap
    this.retries =
      opts.retries === 0 ? 0 : opts.retries || (this.internal && command === DOWN_HINT ? 3 : 5)
    this.closestReplies = []

    this._slow = 0
    this._slowdown = false
    this._seen = new Map()
    this._pending = []
    this._fromTable = false
    this._commit = opts.commit === true ? autoCommit : opts.commit || null
    this._commiting = false
    this._session = opts.session || dht.session()
    this._autoDestroySession = !opts.session
    this._onlyClosestNodes = false

    this._onvisitbound = this._onvisit.bind(this)
    this._onerrorbound = this._onerror.bind(this)
    this._oncyclebound = this._oncycle.bind(this)

    const nodes = opts.nodes || opts.closestNodes
    const replies = opts.replies || opts.closestReplies

    // add them reverse as we pop below
    if (nodes) {
      for (let i = nodes.length - 1; i >= 0; i--) {
        const node = nodes[i]
        this._addPending(
          {
            id: node.id || peer.id(node.host, node.port),
            host: node.host,
            port: node.port
          },
          null
        )
      }
    } else if (replies) {
      for (let i = replies.length - 1; i >= 0; i--) {
        this._addPending(replies[i].from, null)
      }
    }

    if (opts.onlyClosestNodes) this._onlyClosestNodes = true
  }

  get closestNodes() {
    const nodes = new Array(this.closestReplies.length)

    for (let i = 0; i < nodes.length; i++) {
      nodes[i] = this.closestReplies[i].from
    }

    return nodes
  }

  finished() {
    return new Promise((resolve, reject) => {
      if (this.destroyed) {
        const error = getStreamError(this)
        if (error) reject(error)
        else resolve()
        return
      }

      const self = this
      let error = null

      this.resume()
      this.on('error', onerror)
      this.on('close', onclose)

      function onclose() {
        self.removeListener('error', onerror)
        self.removeListener('close', onclose)
        if (error) reject(error)
        else resolve()
      }

      function onerror(err) {
        error = err
      }
    })
  }

  _addFromTable() {
    if (this._pending.length >= this.k) return
    this._fromTable = true

    const closest = this.dht.table.closest(this.target, this.k - this._pending.length)

    for (const node of closest) {
      this._addPending({ id: node.id, host: node.host, port: node.port }, null)
    }
  }

  async _open(cb) {
    this._addFromTable()
    if (this._pending.length >= this.k) return cb(null)

    for await (const node of this.dht._resolveBootstrapNodes()) {
      this._addPending(node, null)
    }

    cb(null)
  }

  _isCloser(id) {
    return (
      this.closestReplies.length < this.k ||
      this._compare(id, this.closestReplies[this.closestReplies.length - 1].from.id) < 0
    )
  }

  _addPending(node, ref) {
    if (this._onlyClosestNodes) return false

    const addr = node.host + ':' + node.port
    const refs = this._seen.get(addr)
    const isCloser = this._isCloser(node.id)

    if (refs === DONE) {
      return isCloser
    }

    if (refs === DOWN) {
      if (ref) this._downHint(ref, node)
      return isCloser
    }

    if (refs) {
      if (ref !== null) refs.push(ref)
      return isCloser
    }

    if (!isCloser) {
      return false
    }

    this._seen.set(addr, ref === null ? [] : [ref])
    this._pending.push(node)

    return true
  }

  _read(cb) {
    this._readMore()
    cb(null)
  }

  _readMore() {
    if (this.destroying || this._commiting) return

    const concurrency = (this._slowdown ? 3 : this.concurrency) + this._slow

    while (this.inflight < concurrency && this._pending.length > 0) {
      const next = this._pending.pop()
      if (next && next.id && !this._isCloser(next.id)) continue
      this._visit(next)
    }

    // if reusing closest nodes, slow down after the first readMore tick to allow
    // the closest node a chance to reply before going broad to question more
    if (!this._fromTable && this.successes === 0 && this.errors === 0) {
      this._slowdown = true
    }

    if (this._pending.length > 0) return

    // if no inflight OR all the queries we are waiting on are marked as slow and we have a full result.
    if (
      this.inflight === 0 ||
      (this._slow === this.inflight && this.closestReplies.length >= this.k)
    ) {
      // if more than 3/4 failed and we only used cached nodes, try again from the routing table
      if (!this._fromTable && this.successes < this.k / 4) {
        this._addFromTable()
        this._readMore()
        return
      }

      this._flush()
    }
  }

  _flush() {
    if (this._commiting) return
    this._commiting = true

    if (this._commit === null) {
      this.push(null)
      return
    }

    const p = []
    for (const m of this.closestReplies) p.push(this._commit(m, this.dht, this))
    this._endAfterCommit(p)
  }

  _endAfterCommit(ps) {
    if (!ps.length) {
      this.destroy(new Error('Too few nodes responded'))
      return
    }

    const self = this

    let pending = ps.length
    let success = 0

    for (const p of ps) p.then(ondone, onerror)

    function ondone() {
      success++
      if (--pending === 0) self.push(null)
    }

    function onerror(err) {
      if (--pending > 0) return
      if (success) self.push(null)
      else self.destroy(err)
    }
  }

  _dec(req) {
    if (req.oncycle === noop) {
      this._slow--
    } else {
      req.oncycle = noop
    }
    this.inflight--
  }

  _onvisit(m, req) {
    this._dec(req)

    const addr = req.to.host + ':' + req.to.port
    this._seen.set(addr, DONE)

    if (this._commiting) return

    if (m.error === 0) this.successes++
    else this.errors++

    if (m.error === 0 && m.from.id !== null && this._isCloser(m.from.id)) this._pushClosest(m)

    if (m.closerNodes !== null) {
      for (const node of m.closerNodes) {
        node.id = peer.id(node.host, node.port)
        if (this.dht._filterNode !== null && !this.dht._filterNode(node)) continue
        if (b4a.equals(node.id, this.dht.table.id)) continue
        // TODO: we could continue here instead of breaking to ensure that one of the nodes in the closer list
        // is later marked as DOWN that we gossip that back
        if (!this._addPending(node, m.from)) break
      }
    }

    if (!this._fromTable && this.successes + this.errors >= this.concurrency) {
      this._slowdown = false
    }

    if (m.error !== 0) {
      this._readMore()
      return
    }

    const data = this.map(m)
    if (!data || this.push(data) !== false) {
      this._readMore()
    }
  }

  _onerror(err, req) {
    const addr = req.to.host + ':' + req.to.port
    const refs = this._seen.get(addr)

    if (err.code === 'REQUEST_TIMEOUT') {
      this._seen.set(addr, DOWN)
      for (const node of refs) this._downHint(node, req.to)
    }

    this._dec(req)
    this.errors++
    this._readMore()
  }

  _oncycle(req) {
    req.oncycle = noop
    this._slow++
    this._readMore()
  }

  _downHint(node, down) {
    // Check rate limit
    if (
      this.dht._downHintsRateLimit !== -1 &&
      this.dht._downHintsSentPerTick >= this.dht._downHintsRateLimit
    ) {
      return null
    }

    this.dht._downHintsSentPerTick++

    const state = { start: 0, end: 6, buffer: b4a.allocUnsafe(6) }
    peer.ipv4.encode(state, down)
    this.dht._request(node, false, true, DOWN_HINT, null, state.buffer, this._session, noop, noop)
  }

  _pushClosest(m) {
    this.closestReplies.push(m)
    for (let i = this.closestReplies.length - 2; i >= 0; i--) {
      const prev = this.closestReplies[i]
      const cmp = this._compare(prev.from.id, m.from.id)
      // if sorted, done!
      if (cmp < 0) break
      // if dup, splice it out (rare)
      if (cmp === 0) {
        this.closestReplies.splice(i + 1, 1)
        break
      }
      // swap and continue down
      this.closestReplies[i + 1] = prev
      this.closestReplies[i] = m
    }
    if (this.closestReplies.length > this.k) this.closestReplies.pop()
  }

  _compare(a, b) {
    for (let i = 0; i < a.length; i++) {
      if (a[i] === b[i]) continue
      const t = this.target[i]
      return (t ^ a[i]) - (t ^ b[i])
    }
    return 0
  }

  _visit(to) {
    this.inflight++

    const req = this.dht._request(
      to,
      this.force,
      this.internal,
      this.command,
      this.target,
      this.value,
      this._session,
      this._onvisitbound,
      this._onerrorbound
    )
    if (req === null) {
      this.destroy(new Error('Node was destroyed'))
      return
    }
    req.retries = this.retries
    req.oncycle = this._oncyclebound
    if (this.force) req.retries = 0
  }

  _destroy(cb) {
    this.dht.stats.queries.active--
    if (this._autoDestroySession) this._session.destroy()
    cb(null)
  }
}

function autoCommit(reply, dht, query) {
  if (!reply.token) return Promise.reject(new Error('No token received for closest node'))
  return dht.request(
    {
      token: reply.token,
      target: query.target,
      command: query.command,
      value: query.value
    },
    reply.from
  )
}

function defaultMap(m) {
  return m
}

function noop() {}
module.exports = class Session {
  constructor(dht) {
    this.dht = dht
    this.inflight = []
  }

  _attach(req) {
    req.index = this.inflight.push(req) - 1
  }

  _detach(req) {
    const i = req.index
    if (i === -1) return
    req.index = -1

    if (i === this.inflight.length - 1) this.inflight.pop()
    else {
      const req = (this.inflight[i] = this.inflight.pop())
      req.index = i
    }
  }

  query({ target, command, value }, opts = {}) {
    return this.dht.query({ target, command, value }, { ...opts, session: this })
  }

  request({ token, command, target, value }, { host, port }, opts = {}) {
    return this.dht.request(
      { token, command, target, value },
      { host, port },
      { ...opts, session: this }
    )
  }

  ping({ host, port }, opts = {}) {
    return this.dht.ping({ host, port }, { ...opts, session: this })
  }

  destroy(err) {
    while (this.inflight.length) {
      const req = this.inflight[0]
      // prevent destroyed requests from contributing to congestion counts
      this.dht.io.congestion.recv()
      req.destroy(err)
    }
  }
}
const LE = (exports.LE =
  new Uint8Array(new Uint16Array([0xff]).buffer)[0] === 0xff)

exports.BE = !LE
const b4a = require('b4a')

const { BE } = require('./endian')

exports.state = function (start = 0, end = 0, buffer = null) {
  return { start, end, buffer }
}

const raw = (exports.raw = require('./raw'))

const uint = (exports.uint = {
  preencode(state, n) {
    state.end += n <= 0xfc ? 1 : n <= 0xffff ? 3 : n <= 0xffffffff ? 5 : 9
  },
  encode(state, n) {
    if (n <= 0xfc) uint8.encode(state, n)
    else if (n <= 0xffff) {
      state.buffer[state.start++] = 0xfd
      uint16.encode(state, n)
    } else if (n <= 0xffffffff) {
      state.buffer[state.start++] = 0xfe
      uint32.encode(state, n)
    } else {
      state.buffer[state.start++] = 0xff
      uint64.encode(state, n)
    }
  },
  decode(state) {
    const a = uint8.decode(state)
    if (a <= 0xfc) return a
    if (a === 0xfd) return uint16.decode(state)
    if (a === 0xfe) return uint32.decode(state)
    return uint64.decode(state)
  }
})

const uint8 = (exports.uint8 = {
  preencode(state, n) {
    state.end += 1
  },
  encode(state, n) {
    validateUint(n)
    state.buffer[state.start++] = n
  },
  decode(state) {
    if (state.start >= state.end) throw new Error('Out of bounds')
    return state.buffer[state.start++]
  }
})

const uint16 = (exports.uint16 = {
  preencode(state, n) {
    state.end += 2
  },
  encode(state, n) {
    validateUint(n)
    state.buffer[state.start++] = n
    state.buffer[state.start++] = n >>> 8
  },
  decode(state) {
    if (state.end - state.start < 2) throw new Error('Out of bounds')
    return state.buffer[state.start++] + state.buffer[state.start++] * 0x100
  }
})

const uint24 = (exports.uint24 = {
  preencode(state, n) {
    state.end += 3
  },
  encode(state, n) {
    validateUint(n)
    state.buffer[state.start++] = n
    state.buffer[state.start++] = n >>> 8
    state.buffer[state.start++] = n >>> 16
  },
  decode(state) {
    if (state.end - state.start < 3) throw new Error('Out of bounds')
    return (
      state.buffer[state.start++] +
      state.buffer[state.start++] * 0x100 +
      state.buffer[state.start++] * 0x10000
    )
  }
})

const uint32 = (exports.uint32 = {
  preencode(state, n) {
    state.end += 4
  },
  encode(state, n) {
    validateUint(n)
    state.buffer[state.start++] = n
    state.buffer[state.start++] = n >>> 8
    state.buffer[state.start++] = n >>> 16
    state.buffer[state.start++] = n >>> 24
  },
  decode(state) {
    if (state.end - state.start < 4) throw new Error('Out of bounds')
    return (
      state.buffer[state.start++] +
      state.buffer[state.start++] * 0x100 +
      state.buffer[state.start++] * 0x10000 +
      state.buffer[state.start++] * 0x1000000
    )
  }
})

const uint40 = (exports.uint40 = {
  preencode(state, n) {
    state.end += 5
  },
  encode(state, n) {
    validateUint(n)
    const r = Math.floor(n / 0x100)
    uint8.encode(state, n)
    uint32.encode(state, r)
  },
  decode(state) {
    if (state.end - state.start < 5) throw new Error('Out of bounds')
    return uint8.decode(state) + 0x100 * uint32.decode(state)
  }
})

const uint48 = (exports.uint48 = {
  preencode(state, n) {
    state.end += 6
  },
  encode(state, n) {
    validateUint(n)
    const r = Math.floor(n / 0x10000)
    uint16.encode(state, n)
    uint32.encode(state, r)
  },
  decode(state) {
    if (state.end - state.start < 6) throw new Error('Out of bounds')
    return uint16.decode(state) + 0x10000 * uint32.decode(state)
  }
})

const uint56 = (exports.uint56 = {
  preencode(state, n) {
    state.end += 7
  },
  encode(state, n) {
    validateUint(n)
    const r = Math.floor(n / 0x1000000)
    uint24.encode(state, n)
    uint32.encode(state, r)
  },
  decode(state) {
    if (state.end - state.start < 7) throw new Error('Out of bounds')
    return uint24.decode(state) + 0x1000000 * uint32.decode(state)
  }
})

const uint64 = (exports.uint64 = {
  preencode(state, n) {
    state.end += 8
  },
  encode(state, n) {
    validateUint(n)
    const r = Math.floor(n / 0x100000000)
    uint32.encode(state, n)
    uint32.encode(state, r)
  },
  decode(state) {
    if (state.end - state.start < 8) throw new Error('Out of bounds')
    return uint32.decode(state) + 0x100000000 * uint32.decode(state)
  }
})

const int = (exports.int = zigZagInt(uint))
exports.int8 = zigZagInt(uint8)
exports.int16 = zigZagInt(uint16)
exports.int24 = zigZagInt(uint24)
exports.int32 = zigZagInt(uint32)
exports.int40 = zigZagInt(uint40)
exports.int48 = zigZagInt(uint48)
exports.int56 = zigZagInt(uint56)
exports.int64 = zigZagInt(uint64)

const biguint64 = (exports.biguint64 = {
  preencode(state, n) {
    state.end += 8
  },
  encode(state, n) {
    const view = new DataView(
      state.buffer.buffer,
      state.start + state.buffer.byteOffset,
      8
    )
    view.setBigUint64(0, n, true) // little endian
    state.start += 8
  },
  decode(state) {
    if (state.end - state.start < 8) throw new Error('Out of bounds')
    const view = new DataView(
      state.buffer.buffer,
      state.start + state.buffer.byteOffset,
      8
    )
    const n = view.getBigUint64(0, true) // little endian
    state.start += 8
    return n
  }
})

exports.bigint64 = zigZagBigInt(biguint64)

const biguint = (exports.biguint = {
  preencode(state, n) {
    let len = 0
    for (let m = n; m; m = m >> 64n) len++
    uint.preencode(state, len)
    state.end += 8 * len
  },
  encode(state, n) {
    let len = 0
    for (let m = n; m; m = m >> 64n) len++
    uint.encode(state, len)
    const view = new DataView(
      state.buffer.buffer,
      state.start + state.buffer.byteOffset,
      8 * len
    )
    for (let m = n, i = 0; m; m = m >> 64n, i += 8) {
      view.setBigUint64(i, BigInt.asUintN(64, m), true) // little endian
    }
    state.start += 8 * len
  },
  decode(state) {
    const len = uint.decode(state)
    if (state.end - state.start < 8 * len) throw new Error('Out of bounds')
    const view = new DataView(
      state.buffer.buffer,
      state.start + state.buffer.byteOffset,
      8 * len
    )
    let n = 0n
    for (let i = len - 1; i >= 0; i--)
      n = (n << 64n) + view.getBigUint64(i * 8, true) // little endian
    state.start += 8 * len
    return n
  }
})

exports.bigint = zigZagBigInt(biguint)

exports.lexint = require('./lexint')

exports.float32 = {
  preencode(state, n) {
    state.end += 4
  },
  encode(state, n) {
    const view = new DataView(
      state.buffer.buffer,
      state.start + state.buffer.byteOffset,
      4
    )
    view.setFloat32(0, n, true) // little endian
    state.start += 4
  },
  decode(state) {
    if (state.end - state.start < 4) throw new Error('Out of bounds')
    const view = new DataView(
      state.buffer.buffer,
      state.start + state.buffer.byteOffset,
      4
    )
    const float = view.getFloat32(0, true) // little endian
    state.start += 4
    return float
  }
}

exports.float64 = {
  preencode(state, n) {
    state.end += 8
  },
  encode(state, n) {
    const view = new DataView(
      state.buffer.buffer,
      state.start + state.buffer.byteOffset,
      8
    )
    view.setFloat64(0, n, true) // little endian
    state.start += 8
  },
  decode(state) {
    if (state.end - state.start < 8) throw new Error('Out of bounds')
    const view = new DataView(
      state.buffer.buffer,
      state.start + state.buffer.byteOffset,
      8
    )
    const float = view.getFloat64(0, true) // little endian
    state.start += 8
    return float
  }
}

const buffer = (exports.buffer = {
  preencode(state, b) {
    if (b) uint8array.preencode(state, b)
    else state.end++
  },
  encode(state, b) {
    if (b) uint8array.encode(state, b)
    else state.buffer[state.start++] = 0
  },
  decode(state) {
    const len = uint.decode(state)
    if (len === 0) return null
    if (state.end - state.start < len) throw new Error('Out of bounds')
    return state.buffer.subarray(state.start, (state.start += len))
  }
})

exports.binary = {
  ...buffer,
  preencode(state, b) {
    if (typeof b === 'string') utf8.preencode(state, b)
    else buffer.preencode(state, b)
  },
  encode(state, b) {
    if (typeof b === 'string') utf8.encode(state, b)
    else buffer.encode(state, b)
  }
}

exports.arraybuffer = {
  preencode(state, b) {
    uint.preencode(state, b.byteLength)
    state.end += b.byteLength
  },
  encode(state, b) {
    uint.encode(state, b.byteLength)

    const view = new Uint8Array(b)

    state.buffer.set(view, state.start)
    state.start += b.byteLength
  },
  decode(state) {
    const len = uint.decode(state)

    const b = new ArrayBuffer(len)
    const view = new Uint8Array(b)

    view.set(state.buffer.subarray(state.start, (state.start += len)))

    return b
  }
}

function typedarray(TypedArray, swap) {
  const n = TypedArray.BYTES_PER_ELEMENT

  return {
    preencode(state, b) {
      uint.preencode(state, b.length)
      state.end += b.byteLength
    },
    encode(state, b) {
      uint.encode(state, b.length)

      const view = new Uint8Array(b.buffer, b.byteOffset, b.byteLength)

      if (BE && swap) swap(view)

      state.buffer.set(view, state.start)
      state.start += b.byteLength
    },
    decode(state) {
      const len = uint.decode(state)

      let b = state.buffer.subarray(state.start, (state.start += len * n))
      if (b.byteLength !== len * n) throw new Error('Out of bounds')
      if (b.byteOffset % n !== 0) b = new Uint8Array(b)

      if (BE && swap) swap(b)

      return new TypedArray(b.buffer, b.byteOffset, b.byteLength / n)
    }
  }
}

const uint8array = (exports.uint8array = typedarray(Uint8Array))
exports.uint16array = typedarray(Uint16Array, b4a.swap16)
exports.uint32array = typedarray(Uint32Array, b4a.swap32)

exports.int8array = typedarray(Int8Array)
exports.int16array = typedarray(Int16Array, b4a.swap16)
exports.int32array = typedarray(Int32Array, b4a.swap32)

exports.biguint64array = typedarray(BigUint64Array, b4a.swap64)
exports.bigint64array = typedarray(BigInt64Array, b4a.swap64)

exports.float32array = typedarray(Float32Array, b4a.swap32)
exports.float64array = typedarray(Float64Array, b4a.swap64)

function string(encoding) {
  return {
    preencode(state, s) {
      const len = b4a.byteLength(s, encoding)
      uint.preencode(state, len)
      state.end += len
    },
    encode(state, s) {
      const len = b4a.byteLength(s, encoding)
      uint.encode(state, len)
      b4a.write(state.buffer, s, state.start, encoding)
      state.start += len
    },
    decode(state) {
      const len = uint.decode(state)
      if (state.end - state.start < len) throw new Error('Out of bounds')
      return b4a.toString(
        state.buffer,
        encoding,
        state.start,
        (state.start += len)
      )
    },
    fixed(n) {
      return {
        preencode(state) {
          state.end += n
        },
        encode(state, s) {
          b4a.write(state.buffer, s, state.start, n, encoding)
          state.start += n
        },
        decode(state) {
          if (state.end - state.start < n) throw new Error('Out of bounds')
          return b4a.toString(
            state.buffer,
            encoding,
            state.start,
            (state.start += n)
          )
        }
      }
    }
  }
}

const utf8 = (exports.string = exports.utf8 = string('utf-8'))
exports.ascii = string('ascii')
exports.hex = string('hex')
exports.base64 = string('base64')
exports.ucs2 = exports.utf16le = string('utf16le')

exports.bool = {
  preencode(state, b) {
    state.end++
  },
  encode(state, b) {
    state.buffer[state.start++] = b ? 1 : 0
  },
  decode(state) {
    if (state.start >= state.end) throw Error('Out of bounds')
    return state.buffer[state.start++] === 1
  }
}

const fixed = (exports.fixed = function fixed(n) {
  return {
    preencode(state, s) {
      if (s.byteLength !== n) throw new Error('Incorrect buffer size')
      state.end += n
    },
    encode(state, s) {
      state.buffer.set(s, state.start)
      state.start += n
    },
    decode(state) {
      if (state.end - state.start < n) throw new Error('Out of bounds')
      return state.buffer.subarray(state.start, (state.start += n))
    }
  }
})

exports.fixed32 = fixed(32)
exports.fixed64 = fixed(64)

exports.array = function array(enc) {
  return {
    preencode(state, list) {
      uint.preencode(state, list.length)
      for (let i = 0; i < list.length; i++) enc.preencode(state, list[i])
    },
    encode(state, list) {
      uint.encode(state, list.length)
      for (let i = 0; i < list.length; i++) enc.encode(state, list[i])
    },
    decode(state) {
      const len = uint.decode(state)
      if (len > 0x100000) throw new Error('Array is too big')
      const arr = new Array(len)
      for (let i = 0; i < len; i++) arr[i] = enc.decode(state)
      return arr
    }
  }
}

exports.frame = function frame(enc) {
  const dummy = exports.state()

  return {
    preencode(state, m) {
      const end = state.end
      enc.preencode(state, m)
      uint.preencode(state, state.end - end)
    },
    encode(state, m) {
      dummy.end = 0
      enc.preencode(dummy, m)
      uint.encode(state, dummy.end)
      enc.encode(state, m)
    },
    decode(state) {
      const end = state.end
      const len = uint.decode(state)
      state.end = state.start + len
      const m = enc.decode(state)
      state.start = state.end
      state.end = end
      return m
    }
  }
}

exports.date = {
  preencode(state, d) {
    int.preencode(state, d.getTime())
  },
  encode(state, d) {
    int.encode(state, d.getTime())
  },
  decode(state, d) {
    return new Date(int.decode(state))
  }
}

exports.json = {
  preencode(state, v) {
    utf8.preencode(state, JSON.stringify(v))
  },
  encode(state, v) {
    utf8.encode(state, JSON.stringify(v))
  },
  decode(state) {
    return JSON.parse(utf8.decode(state))
  }
}

exports.ndjson = {
  preencode(state, v) {
    utf8.preencode(state, JSON.stringify(v) + '\n')
  },
  encode(state, v) {
    utf8.encode(state, JSON.stringify(v) + '\n')
  },
  decode(state) {
    return JSON.parse(utf8.decode(state))
  }
}

// simple helper for when you want to just express nothing
exports.none = {
  preencode(state, n) {
    // do nothing
  },
  encode(state, n) {
    // do nothing
  },
  decode(state) {
    return null
  }
}

// "any" encoders here for helping just structure any object without schematising it

const anyArray = {
  preencode(state, arr) {
    uint.preencode(state, arr.length)
    for (let i = 0; i < arr.length; i++) {
      any.preencode(state, arr[i])
    }
  },
  encode(state, arr) {
    uint.encode(state, arr.length)
    for (let i = 0; i < arr.length; i++) {
      any.encode(state, arr[i])
    }
  },
  decode(state) {
    const arr = []
    let len = uint.decode(state)
    while (len-- > 0) {
      arr.push(any.decode(state))
    }
    return arr
  }
}

const anyObject = {
  preencode(state, o) {
    const keys = Object.keys(o)
    uint.preencode(state, keys.length)
    for (const key of keys) {
      utf8.preencode(state, key)
      any.preencode(state, o[key])
    }
  },
  encode(state, o) {
    const keys = Object.keys(o)
    uint.encode(state, keys.length)
    for (const key of keys) {
      utf8.encode(state, key)
      any.encode(state, o[key])
    }
  },
  decode(state) {
    let len = uint.decode(state)
    const o = {}
    while (len-- > 0) {
      const key = utf8.decode(state)
      o[key] = any.decode(state)
    }
    return o
  }
}

const anyTypes = [
  exports.none,
  exports.bool,
  exports.string,
  exports.buffer,
  exports.uint,
  exports.int,
  exports.float64,
  anyArray,
  anyObject,
  exports.date
]

const any = (exports.any = {
  preencode(state, o) {
    const t = getType(o)
    uint.preencode(state, t)
    anyTypes[t].preencode(state, o)
  },
  encode(state, o) {
    const t = getType(o)
    uint.encode(state, t)
    anyTypes[t].encode(state, o)
  },
  decode(state) {
    const t = uint.decode(state)
    if (t >= anyTypes.length) throw new Error('Unknown type: ' + t)
    return anyTypes[t].decode(state)
  }
})

const port = (exports.port = uint16)

const address = (host, family) => {
  return {
    preencode(state, m) {
      host.preencode(state, m.host)
      port.preencode(state, m.port)
    },
    encode(state, m) {
      host.encode(state, m.host)
      port.encode(state, m.port)
    },
    decode(state) {
      return {
        host: host.decode(state),
        family,
        port: port.decode(state)
      }
    }
  }
}

const ipv4 = (exports.ipv4 = {
  preencode(state) {
    state.end += 4
  },
  encode(state, string) {
    const start = state.start
    const end = start + 4

    let i = 0

    while (i < string.length) {
      let n = 0
      let c

      while (
        i < string.length &&
        (c = string.charCodeAt(i++)) !== /* . */ 0x2e
      ) {
        n = n * 10 + (c - /* 0 */ 0x30)
      }

      state.buffer[state.start++] = n
    }

    state.start = end
  },
  decode(state) {
    if (state.end - state.start < 4) throw new Error('Out of bounds')
    return (
      state.buffer[state.start++] +
      '.' +
      state.buffer[state.start++] +
      '.' +
      state.buffer[state.start++] +
      '.' +
      state.buffer[state.start++]
    )
  }
})

exports.ipv4Address = address(ipv4, 4)

const ipv6 = (exports.ipv6 = {
  preencode(state) {
    state.end += 16
  },
  encode(state, string) {
    const start = state.start
    const end = start + 16

    let i = 0
    let split = null

    while (i < string.length) {
      let n = 0
      let c

      while (
        i < string.length &&
        (c = string.charCodeAt(i++)) !== /* : */ 0x3a
      ) {
        if (c >= 0x30 && c <= 0x39) n = n * 0x10 + (c - /* 0 */ 0x30)
        else if (c >= 0x41 && c <= 0x46) n = n * 0x10 + (c - /* A */ 0x41 + 10)
        else if (c >= 0x61 && c <= 0x66) n = n * 0x10 + (c - /* a */ 0x61 + 10)
      }

      state.buffer[state.start++] = n >>> 8
      state.buffer[state.start++] = n

      if (i < string.length && string.charCodeAt(i) === /* : */ 0x3a) {
        i++
        split = state.start
      }
    }

    if (split !== null) {
      const offset = end - state.start
      state.buffer
        .copyWithin(split + offset, split)
        .fill(0, split, split + offset)
    }

    state.start = end
  },
  decode(state) {
    if (state.end - state.start < 16) throw new Error('Out of bounds')
    return (
      (
        state.buffer[state.start++] * 256 +
        state.buffer[state.start++]
      ).toString(16) +
      ':' +
      (
        state.buffer[state.start++] * 256 +
        state.buffer[state.start++]
      ).toString(16) +
      ':' +
      (
        state.buffer[state.start++] * 256 +
        state.buffer[state.start++]
      ).toString(16) +
      ':' +
      (
        state.buffer[state.start++] * 256 +
        state.buffer[state.start++]
      ).toString(16) +
      ':' +
      (
        state.buffer[state.start++] * 256 +
        state.buffer[state.start++]
      ).toString(16) +
      ':' +
      (
        state.buffer[state.start++] * 256 +
        state.buffer[state.start++]
      ).toString(16) +
      ':' +
      (
        state.buffer[state.start++] * 256 +
        state.buffer[state.start++]
      ).toString(16) +
      ':' +
      (
        state.buffer[state.start++] * 256 +
        state.buffer[state.start++]
      ).toString(16)
    )
  }
})

exports.ipv6Address = address(ipv6, 6)

const ip = (exports.ip = {
  preencode(state, string) {
    const family = string.includes(':') ? 6 : 4
    uint8.preencode(state, family)
    if (family === 4) ipv4.preencode(state)
    else ipv6.preencode(state)
  },
  encode(state, string) {
    const family = string.includes(':') ? 6 : 4
    uint8.encode(state, family)
    if (family === 4) ipv4.encode(state, string)
    else ipv6.encode(state, string)
  },
  decode(state) {
    const family = uint8.decode(state)
    if (family === 4) return ipv4.decode(state)
    else return ipv6.decode(state)
  }
})

exports.ipAddress = {
  preencode(state, m) {
    ip.preencode(state, m.host)
    port.preencode(state, m.port)
  },
  encode(state, m) {
    ip.encode(state, m.host)
    port.encode(state, m.port)
  },
  decode(state) {
    const family = uint8.decode(state)
    return {
      host: family === 4 ? ipv4.decode(state) : ipv6.decode(state),
      family,
      port: port.decode(state)
    }
  }
}

const record = (exports.record = function (keyEncoding, valueEncoding) {
  return {
    preencode(state, v) {
      const keys = Object.keys(v)
      uint.preencode(state, keys.length)
      for (const k of keys) {
        keyEncoding.preencode(state, k)
        valueEncoding.preencode(state, v[k])
      }
    },
    encode(state, v) {
      const keys = Object.keys(v)
      uint.encode(state, keys.length)
      for (const k of keys) {
        keyEncoding.encode(state, k)
        valueEncoding.encode(state, v[k])
      }
    },
    decode(state) {
      const out = Object.create(null)
      const keys = uint.decode(state)
      for (let i = 0; i < keys; i++) {
        out[keyEncoding.decode(state)] = valueEncoding.decode(state)
      }
      return out
    }
  }
})

exports.stringRecord = record(utf8, utf8)

function getType(o) {
  if (o === null || o === undefined) return 0
  if (typeof o === 'boolean') return 1
  if (typeof o === 'string') return 2
  if (b4a.isBuffer(o)) return 3
  if (typeof o === 'number') {
    if (Number.isInteger(o)) return o >= 0 ? 4 : 5
    return 6
  }
  if (Array.isArray(o)) return 7
  if (o instanceof Date) return 9
  if (typeof o === 'object') return 8

  throw new Error('Unsupported type for ' + o)
}

exports.from = function from(enc) {
  if (typeof enc === 'string') return fromNamed(enc)
  if (enc.preencode) return enc
  if (enc.encodingLength) return fromAbstractEncoder(enc)
  return fromCodec(enc)
}

function fromNamed(enc) {
  switch (enc) {
    case 'ascii':
      return raw.ascii
    case 'utf-8':
    case 'utf8':
      return raw.utf8
    case 'hex':
      return raw.hex
    case 'base64':
      return raw.base64
    case 'utf16-le':
    case 'utf16le':
    case 'ucs-2':
    case 'ucs2':
      return raw.ucs2
    case 'ndjson':
      return raw.ndjson
    case 'json':
      return raw.json
    case 'binary':
    default:
      return raw.binary
  }
}

function fromCodec(enc) {
  let tmpM = null
  let tmpBuf = null

  return {
    preencode(state, m) {
      tmpM = m
      tmpBuf = enc.encode(m)
      state.end += tmpBuf.byteLength
    },
    encode(state, m) {
      raw.encode(state, m === tmpM ? tmpBuf : enc.encode(m))
      tmpM = tmpBuf = null
    },
    decode(state) {
      return enc.decode(raw.decode(state))
    }
  }
}

function fromAbstractEncoder(enc) {
  return {
    preencode(state, m) {
      state.end += enc.encodingLength(m)
    },
    encode(state, m) {
      enc.encode(m, state.buffer, state.start)
      state.start += enc.encode.bytes
    },
    decode(state) {
      const m = enc.decode(state.buffer, state.start, state.end)
      state.start += enc.decode.bytes
      return m
    }
  }
}

exports.encode = function encode(enc, m) {
  const state = exports.state()
  enc.preencode(state, m)
  state.buffer = b4a.allocUnsafe(state.end)
  enc.encode(state, m)
  return state.buffer
}

exports.decode = function decode(enc, buffer) {
  return enc.decode(exports.state(0, buffer.byteLength, buffer))
}

function zigZagInt(enc) {
  return {
    preencode(state, n) {
      enc.preencode(state, zigZagEncodeInt(n))
    },
    encode(state, n) {
      enc.encode(state, zigZagEncodeInt(n))
    },
    decode(state) {
      return zigZagDecodeInt(enc.decode(state))
    }
  }
}

function zigZagDecodeInt(n) {
  return n === 0 ? n : (n & 1) === 0 ? n / 2 : -(n + 1) / 2
}

function zigZagEncodeInt(n) {
  // 0, -1, 1, -2, 2, ...
  return n < 0 ? 2 * -n - 1 : n === 0 ? 0 : 2 * n
}

function zigZagBigInt(enc) {
  return {
    preencode(state, n) {
      enc.preencode(state, zigZagEncodeBigInt(n))
    },
    encode(state, n) {
      enc.encode(state, zigZagEncodeBigInt(n))
    },
    decode(state) {
      return zigZagDecodeBigInt(enc.decode(state))
    }
  }
}

function zigZagDecodeBigInt(n) {
  return n === 0n ? n : (n & 1n) === 0n ? n / 2n : -(n + 1n) / 2n
}

function zigZagEncodeBigInt(n) {
  // 0, -1, 1, -2, 2, ...
  return n < 0n ? 2n * -n - 1n : n === 0n ? 0n : 2n * n
}

function validateUint(n) {
  if (n >= 0 === false /* Handles NaN as well */)
    throw new Error('uint must be positive')
}
module.exports = {
  preencode,
  encode,
  decode
}

function preencode(state, num) {
  if (num < 251) {
    state.end++
  } else if (num < 256) {
    state.end += 2
  } else if (num < 0x10000) {
    state.end += 3
  } else if (num < 0x1000000) {
    state.end += 4
  } else if (num < 0x100000000) {
    state.end += 5
  } else {
    state.end++
    const exp = Math.floor(Math.log(num) / Math.log(2)) - 32
    preencode(state, exp)
    state.end += 6
  }
}

function encode(state, num) {
  const max = 251
  const x = num - max

  if (num < max) {
    state.buffer[state.start++] = num
  } else if (num < 256) {
    state.buffer[state.start++] = max
    state.buffer[state.start++] = x
  } else if (num < 0x10000) {
    state.buffer[state.start++] = max + 1
    state.buffer[state.start++] = (x >> 8) & 0xff
    state.buffer[state.start++] = x & 0xff
  } else if (num < 0x1000000) {
    state.buffer[state.start++] = max + 2
    state.buffer[state.start++] = x >> 16
    state.buffer[state.start++] = (x >> 8) & 0xff
    state.buffer[state.start++] = x & 0xff
  } else if (num < 0x100000000) {
    state.buffer[state.start++] = max + 3
    state.buffer[state.start++] = x >> 24
    state.buffer[state.start++] = (x >> 16) & 0xff
    state.buffer[state.start++] = (x >> 8) & 0xff
    state.buffer[state.start++] = x & 0xff
  } else {
    // need to use Math here as bitwise ops are 32 bit
    const exp = Math.floor(Math.log(x) / Math.log(2)) - 32
    state.buffer[state.start++] = 0xff

    encode(state, exp)
    const rem = x / Math.pow(2, exp - 11)

    for (let i = 5; i >= 0; i--) {
      state.buffer[state.start++] = (rem / Math.pow(2, 8 * i)) & 0xff
    }
  }
}

function decode(state) {
  const max = 251

  if (state.end - state.start < 1) throw new Error('Out of bounds')

  const flag = state.buffer[state.start++]

  if (flag < max) return flag

  if (state.end - state.start < flag - max + 1) {
    throw new Error('Out of bounds.')
  }

  if (flag < 252) {
    return state.buffer[state.start++] + max
  }

  if (flag < 253) {
    return (
      (state.buffer[state.start++] << 8) + state.buffer[state.start++] + max
    )
  }

  if (flag < 254) {
    return (
      (state.buffer[state.start++] << 16) +
      (state.buffer[state.start++] << 8) +
      state.buffer[state.start++] +
      max
    )
  }

  // << 24 result may be interpreted as negative
  if (flag < 255) {
    return (
      state.buffer[state.start++] * 0x1000000 +
      (state.buffer[state.start++] << 16) +
      (state.buffer[state.start++] << 8) +
      state.buffer[state.start++] +
      max
    )
  }

  const exp = decode(state)

  if (state.end - state.start < 6) throw new Error('Out of bounds')

  let rem = 0
  for (let i = 5; i >= 0; i--) {
    rem += state.buffer[state.start++] * Math.pow(2, 8 * i)
  }

  return rem * Math.pow(2, exp - 11) + max
}
{
  "name": "compact-encoding",
  "version": "2.19.2",
  "description": "A series of compact encoding schemes for building small and fast parsers and serializers",
  "main": "index.js",
  "dependencies": {
    "b4a": "^1.3.0"
  },
  "devDependencies": {
    "brittle": "^3.0.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^1.0.0"
  },
  "scripts": {
    "format": "prettier . --write",
    "test": "prettier . --check && brittle test.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/holepunchto/compact-encoding.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/compact-encoding/issues"
  },
  "homepage": "https://github.com/holepunchto/compact-encoding"
}
const b4a = require('b4a')

const { BE } = require('./endian')

exports = module.exports = {
  preencode(state, b) {
    state.end += b.byteLength
  },
  encode(state, b) {
    state.buffer.set(b, state.start)
    state.start += b.byteLength
  },
  decode(state) {
    const b = state.buffer.subarray(state.start, state.end)
    state.start = state.end
    return b
  }
}

const buffer = (exports.buffer = {
  preencode(state, b) {
    if (b) uint8array.preencode(state, b)
    else state.end++
  },
  encode(state, b) {
    if (b) uint8array.encode(state, b)
    else state.buffer[state.start++] = 0
  },
  decode(state) {
    const b = state.buffer.subarray(state.start)
    if (b.byteLength === 0) return null
    state.start = state.end
    return b
  }
})

exports.binary = {
  ...buffer,
  preencode(state, b) {
    if (typeof b === 'string') utf8.preencode(state, b)
    else buffer.preencode(state, b)
  },
  encode(state, b) {
    if (typeof b === 'string') utf8.encode(state, b)
    else buffer.encode(state, b)
  }
}

exports.arraybuffer = {
  preencode(state, b) {
    state.end += b.byteLength
  },
  encode(state, b) {
    const view = new Uint8Array(b)

    state.buffer.set(view, state.start)
    state.start += b.byteLength
  },
  decode(state) {
    const b = new ArrayBuffer(state.end - state.start)
    const view = new Uint8Array(b)

    view.set(state.buffer.subarray(state.start))

    state.start = state.end

    return b
  }
}

function typedarray(TypedArray, swap) {
  const n = TypedArray.BYTES_PER_ELEMENT

  return {
    preencode(state, b) {
      state.end += b.byteLength
    },
    encode(state, b) {
      const view = new Uint8Array(b.buffer, b.byteOffset, b.byteLength)

      if (BE && swap) swap(view)

      state.buffer.set(view, state.start)
      state.start += b.byteLength
    },
    decode(state) {
      let b = state.buffer.subarray(state.start)
      if (b.byteOffset % n !== 0) b = new Uint8Array(b)

      if (BE && swap) swap(b)

      state.start = state.end

      return new TypedArray(b.buffer, b.byteOffset, b.byteLength / n)
    }
  }
}

const uint8array = (exports.uint8array = typedarray(Uint8Array))
exports.uint16array = typedarray(Uint16Array, b4a.swap16)
exports.uint32array = typedarray(Uint32Array, b4a.swap32)

exports.int8array = typedarray(Int8Array)
exports.int16array = typedarray(Int16Array, b4a.swap16)
exports.int32array = typedarray(Int32Array, b4a.swap32)

exports.biguint64array = typedarray(BigUint64Array, b4a.swap64)
exports.bigint64array = typedarray(BigInt64Array, b4a.swap64)

exports.float32array = typedarray(Float32Array, b4a.swap32)
exports.float64array = typedarray(Float64Array, b4a.swap64)

function string(encoding) {
  return {
    preencode(state, s) {
      state.end += b4a.byteLength(s, encoding)
    },
    encode(state, s) {
      state.start += b4a.write(state.buffer, s, state.start, encoding)
    },
    decode(state) {
      const s = b4a.toString(state.buffer, encoding, state.start)
      state.start = state.end
      return s
    }
  }
}

const utf8 = (exports.string = exports.utf8 = string('utf-8'))
exports.ascii = string('ascii')
exports.hex = string('hex')
exports.base64 = string('base64')
exports.ucs2 = exports.utf16le = string('utf16le')

exports.array = function array(enc) {
  return {
    preencode(state, list) {
      for (const value of list) enc.preencode(state, value)
    },
    encode(state, list) {
      for (const value of list) enc.encode(state, value)
    },
    decode(state) {
      const arr = []
      while (state.start < state.end) arr.push(enc.decode(state))
      return arr
    }
  }
}

exports.json = {
  preencode(state, v) {
    utf8.preencode(state, JSON.stringify(v))
  },
  encode(state, v) {
    utf8.encode(state, JSON.stringify(v))
  },
  decode(state) {
    return JSON.parse(utf8.decode(state))
  }
}

exports.ndjson = {
  preencode(state, v) {
    utf8.preencode(state, JSON.stringify(v) + '\n')
  },
  encode(state, v) {
    utf8.encode(state, JSON.stringify(v) + '\n')
  },
  decode(state) {
    return JSON.parse(utf8.decode(state))
  }
}
{
  "name": "dht-rpc",
  "version": "6.26.4",
  "description": "Make RPC calls over a Kademlia based DHT",
  "main": "index.js",
  "files": [
    "index.js",
    "lib/*.js"
  ],
  "imports": {
    "events": {
      "bare": "bare-events",
      "default": "events"
    }
  },
  "dependencies": {
    "adaptive-timeout": "^1.0.1",
    "b4a": "^1.6.1",
    "bare-events": "^2.2.0",
    "compact-encoding": "^2.11.0",
    "compact-encoding-net": "^1.2.0",
    "fast-fifo": "^1.1.0",
    "kademlia-routing-table": "^1.0.1",
    "nat-sampler": "^1.0.1",
    "sodium-universal": "^5.0.0",
    "streamx": "^2.13.2",
    "time-ordered-set": "^2.0.0",
    "udx-native": "^1.5.3"
  },
  "devDependencies": {
    "brittle": "^3.0.0",
    "lunte": "^1.3.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^2.0.0",
    "test-suspend": "^1.0.0"
  },
  "scripts": {
    "format": "prettier --write .",
    "test": "npm run test:node && npm run test:bare",
    "test:bare": "brittle-bare --coverage test.js",
    "lint": "prettier --check . && lunte",
    "test:node": "brittle-node --coverage test.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/holepunchto/dht-rpc.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/holepunchto/dht-rpc/issues"
  },
  "homepage": "https://github.com/holepunchto/dht-rpc#"
}
module.exports = require('bare-events')
{
  "name": "events-universal",
  "version": "1.0.1",
  "description": "Universal wrapper for the Node.js events module",
  "exports": {
    "./package": "./package.json",
    ".": {
      "bare": "./bare.js",
      "react-native": "./react-native.js",
      "default": "./default.js"
    }
  },
  "files": [
    "index.js",
    "default.js",
    "bare.js",
    "react-native.js"
  ],
  "scripts": {
    "test": "prettier . --check"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/events-universal.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/events-universal/issues"
  },
  "homepage": "https://github.com/holepunchto/events-universal#readme",
  "dependencies": {
    "bare-events": "^2.7.0"
  },
  "devDependencies": {
    "prettier": "^3.4.2",
    "prettier-config-holepunch": "^1.0.0"
  }
}
module.exports = class FixedFIFO {
  constructor (hwm) {
    if (!(hwm > 0) || ((hwm - 1) & hwm) !== 0) throw new Error('Max size for a FixedFIFO should be a power of two')
    this.buffer = new Array(hwm)
    this.mask = hwm - 1
    this.top = 0
    this.btm = 0
    this.next = null
  }

  clear () {
    this.top = this.btm = 0
    this.next = null
    this.buffer.fill(undefined)
  }

  push (data) {
    if (this.buffer[this.top] !== undefined) return false
    this.buffer[this.top] = data
    this.top = (this.top + 1) & this.mask
    return true
  }

  shift () {
    const last = this.buffer[this.btm]
    if (last === undefined) return undefined
    this.buffer[this.btm] = undefined
    this.btm = (this.btm + 1) & this.mask
    return last
  }

  peek () {
    return this.buffer[this.btm]
  }

  isEmpty () {
    return this.buffer[this.btm] === undefined
  }
}
const FixedFIFO = require('./fixed-size')

module.exports = class FastFIFO {
  constructor (hwm) {
    this.hwm = hwm || 16
    this.head = new FixedFIFO(this.hwm)
    this.tail = this.head
    this.length = 0
  }

  clear () {
    this.head = this.tail
    this.head.clear()
    this.length = 0
  }

  push (val) {
    this.length++
    if (!this.head.push(val)) {
      const prev = this.head
      this.head = prev.next = new FixedFIFO(2 * this.head.buffer.length)
      this.head.push(val)
    }
  }

  shift () {
    if (this.length !== 0) this.length--
    const val = this.tail.shift()
    if (val === undefined && this.tail.next) {
      const next = this.tail.next
      this.tail.next = null
      this.tail = next
      return this.tail.shift()
    }

    return val
  }

  peek () {
    const val = this.tail.peek()
    if (val === undefined && this.tail.next) return this.tail.next.peek()
    return val
  }

  isEmpty () {
    return this.length === 0
  }
}
{
  "name": "fast-fifo",
  "version": "1.3.2",
  "description": "A fast fifo implementation similar to the one powering nextTick in Node.js core",
  "main": "index.js",
  "files": [
    "./index.js",
    "./fixed-size.js"
  ],
  "dependencies": {},
  "devDependencies": {
    "standard": "^17.1.0",
    "brittle": "^3.3.2"
  },
  "scripts": {
    "test": "standard && brittle test.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/mafintosh/fast-fifo.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/mafintosh/fast-fifo/issues"
  },
  "homepage": "https://github.com/mafintosh/fast-fifo"
}
const sodium = require('sodium-universal')
const c = require('compact-encoding')
const b4a = require('b4a')

// https://en.wikipedia.org/wiki/Merkle_tree#Second_preimage_attack
const LEAF_TYPE = b4a.from([0])
const PARENT_TYPE = b4a.from([1])
const ROOT_TYPE = b4a.from([2])

const HYPERCORE = b4a.from('hypercore')

exports.keyPair = function (seed) {
  // key pairs might stay around for a while, so better not to use a default slab to avoid retaining it completely
  const slab = b4a.allocUnsafeSlow(sodium.crypto_sign_PUBLICKEYBYTES + sodium.crypto_sign_SECRETKEYBYTES)
  const publicKey = slab.subarray(0, sodium.crypto_sign_PUBLICKEYBYTES)
  const secretKey = slab.subarray(sodium.crypto_sign_PUBLICKEYBYTES)

  if (seed) sodium.crypto_sign_seed_keypair(publicKey, secretKey, seed)
  else sodium.crypto_sign_keypair(publicKey, secretKey)

  return {
    publicKey,
    secretKey
  }
}

exports.validateKeyPair = function (keyPair) {
  const pk = b4a.allocUnsafe(sodium.crypto_sign_PUBLICKEYBYTES)
  sodium.crypto_sign_ed25519_sk_to_pk(pk, keyPair.secretKey)
  return b4a.equals(pk, keyPair.publicKey)
}

exports.sign = function (message, secretKey) {
  // Dedicated slab for the signature, to avoid retaining unneeded mem and for security
  const signature = b4a.allocUnsafeSlow(sodium.crypto_sign_BYTES)
  sodium.crypto_sign_detached(signature, message, secretKey)
  return signature
}

exports.verify = function (message, signature, publicKey) {
  if (signature.byteLength !== sodium.crypto_sign_BYTES) return false
  if (publicKey.byteLength !== sodium.crypto_sign_PUBLICKEYBYTES) return false
  return sodium.crypto_sign_verify_detached(signature, message, publicKey)
}

exports.encrypt = function (message, publicKey) {
  const ciphertext = b4a.alloc(message.byteLength + sodium.crypto_box_SEALBYTES)
  sodium.crypto_box_seal(ciphertext, message, publicKey)
  return ciphertext
}

exports.decrypt = function (ciphertext, keyPair) {
  if (ciphertext.byteLength < sodium.crypto_box_SEALBYTES) return null

  const plaintext = b4a.alloc(ciphertext.byteLength - sodium.crypto_box_SEALBYTES)

  if (!sodium.crypto_box_seal_open(plaintext, ciphertext, keyPair.publicKey, keyPair.secretKey)) {
    return null
  }

  return plaintext
}

exports.encryptionKeyPair = function (seed) {
  const publicKey = b4a.alloc(sodium.crypto_box_PUBLICKEYBYTES)
  const secretKey = b4a.alloc(sodium.crypto_box_SECRETKEYBYTES)

  if (seed) {
    sodium.crypto_box_seed_keypair(publicKey, secretKey, seed)
  } else {
    sodium.crypto_box_keypair(publicKey, secretKey)
  }

  return {
    publicKey,
    secretKey
  }
}

exports.data = function (data) {
  const out = b4a.allocUnsafe(32)

  sodium.crypto_generichash_batch(out, [
    LEAF_TYPE,
    c.encode(c.uint64, data.byteLength),
    data
  ])

  return out
}

exports.parent = function (a, b) {
  if (a.index > b.index) {
    const tmp = a
    a = b
    b = tmp
  }

  const out = b4a.allocUnsafe(32)

  sodium.crypto_generichash_batch(out, [
    PARENT_TYPE,
    c.encode(c.uint64, a.size + b.size),
    a.hash,
    b.hash
  ])

  return out
}

exports.tree = function (roots, out) {
  const buffers = new Array(3 * roots.length + 1)
  let j = 0

  buffers[j++] = ROOT_TYPE

  for (let i = 0; i < roots.length; i++) {
    const r = roots[i]
    buffers[j++] = r.hash
    buffers[j++] = c.encode(c.uint64, r.index)
    buffers[j++] = c.encode(c.uint64, r.size)
  }

  if (!out) out = b4a.allocUnsafe(32)
  sodium.crypto_generichash_batch(out, buffers)
  return out
}

exports.hash = function (data, out) {
  if (!out) out = b4a.allocUnsafe(32)
  if (!Array.isArray(data)) data = [data]

  sodium.crypto_generichash_batch(out, data)

  return out
}

exports.randomBytes = function (n) {
  const buf = b4a.allocUnsafe(n)
  sodium.randombytes_buf(buf)
  return buf
}

exports.discoveryKey = function (key) {
  if (!key || key.byteLength !== 32) throw new Error('Must pass a 32 byte buffer')
  // Discovery keys might stay around for a while, so better not to use slab memory (for better gc)
  const digest = b4a.allocUnsafeSlow(32)
  sodium.crypto_generichash(digest, HYPERCORE, key)
  return digest
}

if (sodium.sodium_free) {
  exports.free = function (secureBuf) {
    if (secureBuf.secure) sodium.sodium_free(secureBuf)
  }
} else {
  exports.free = function () {}
}

exports.namespace = function (name, count) {
  const ids = typeof count === 'number' ? range(count) : count

  // Namespaces are long-lived, so better to use a dedicated slab
  const buf = b4a.allocUnsafeSlow(32 * ids.length)

  const list = new Array(ids.length)

  // ns is emhemeral, so default slab
  const ns = b4a.allocUnsafe(33)
  sodium.crypto_generichash(ns.subarray(0, 32), typeof name === 'string' ? b4a.from(name) : name)

  for (let i = 0; i < list.length; i++) {
    list[i] = buf.subarray(32 * i, 32 * i + 32)
    ns[32] = ids[i]
    sodium.crypto_generichash(list[i], ns)
  }

  return list
}

function range (count) {
  const arr = new Array(count)
  for (let i = 0; i < count; i++) arr[i] = i
  return arr
}
const LE = (exports.LE =
  new Uint8Array(new Uint16Array([0xff]).buffer)[0] === 0xff)

exports.BE = !LE
const b4a = require('b4a')

const { BE } = require('./endian')

exports.state = function (start = 0, end = 0, buffer = null) {
  return { start, end, buffer }
}

const raw = (exports.raw = require('./raw'))

const uint = (exports.uint = {
  preencode(state, n) {
    state.end += n <= 0xfc ? 1 : n <= 0xffff ? 3 : n <= 0xffffffff ? 5 : 9
  },
  encode(state, n) {
    if (n <= 0xfc) uint8.encode(state, n)
    else if (n <= 0xffff) {
      state.buffer[state.start++] = 0xfd
      uint16.encode(state, n)
    } else if (n <= 0xffffffff) {
      state.buffer[state.start++] = 0xfe
      uint32.encode(state, n)
    } else {
      state.buffer[state.start++] = 0xff
      uint64.encode(state, n)
    }
  },
  decode(state) {
    const a = uint8.decode(state)
    if (a <= 0xfc) return a
    if (a === 0xfd) return uint16.decode(state)
    if (a === 0xfe) return uint32.decode(state)
    return uint64.decode(state)
  }
})

const uint8 = (exports.uint8 = {
  preencode(state, n) {
    state.end += 1
  },
  encode(state, n) {
    validateUint(n)
    state.buffer[state.start++] = n
  },
  decode(state) {
    if (state.start >= state.end) throw new Error('Out of bounds')
    return state.buffer[state.start++]
  }
})

const uint16 = (exports.uint16 = {
  preencode(state, n) {
    state.end += 2
  },
  encode(state, n) {
    validateUint(n)
    state.buffer[state.start++] = n
    state.buffer[state.start++] = n >>> 8
  },
  decode(state) {
    if (state.end - state.start < 2) throw new Error('Out of bounds')
    return state.buffer[state.start++] + state.buffer[state.start++] * 0x100
  }
})

const uint24 = (exports.uint24 = {
  preencode(state, n) {
    state.end += 3
  },
  encode(state, n) {
    validateUint(n)
    state.buffer[state.start++] = n
    state.buffer[state.start++] = n >>> 8
    state.buffer[state.start++] = n >>> 16
  },
  decode(state) {
    if (state.end - state.start < 3) throw new Error('Out of bounds')
    return (
      state.buffer[state.start++] +
      state.buffer[state.start++] * 0x100 +
      state.buffer[state.start++] * 0x10000
    )
  }
})

const uint32 = (exports.uint32 = {
  preencode(state, n) {
    state.end += 4
  },
  encode(state, n) {
    validateUint(n)
    state.buffer[state.start++] = n
    state.buffer[state.start++] = n >>> 8
    state.buffer[state.start++] = n >>> 16
    state.buffer[state.start++] = n >>> 24
  },
  decode(state) {
    if (state.end - state.start < 4) throw new Error('Out of bounds')
    return (
      state.buffer[state.start++] +
      state.buffer[state.start++] * 0x100 +
      state.buffer[state.start++] * 0x10000 +
      state.buffer[state.start++] * 0x1000000
    )
  }
})

const uint40 = (exports.uint40 = {
  preencode(state, n) {
    state.end += 5
  },
  encode(state, n) {
    validateUint(n)
    const r = Math.floor(n / 0x100)
    uint8.encode(state, n)
    uint32.encode(state, r)
  },
  decode(state) {
    if (state.end - state.start < 5) throw new Error('Out of bounds')
    return uint8.decode(state) + 0x100 * uint32.decode(state)
  }
})

const uint48 = (exports.uint48 = {
  preencode(state, n) {
    state.end += 6
  },
  encode(state, n) {
    validateUint(n)
    const r = Math.floor(n / 0x10000)
    uint16.encode(state, n)
    uint32.encode(state, r)
  },
  decode(state) {
    if (state.end - state.start < 6) throw new Error('Out of bounds')
    return uint16.decode(state) + 0x10000 * uint32.decode(state)
  }
})

const uint56 = (exports.uint56 = {
  preencode(state, n) {
    state.end += 7
  },
  encode(state, n) {
    validateUint(n)
    const r = Math.floor(n / 0x1000000)
    uint24.encode(state, n)
    uint32.encode(state, r)
  },
  decode(state) {
    if (state.end - state.start < 7) throw new Error('Out of bounds')
    return uint24.decode(state) + 0x1000000 * uint32.decode(state)
  }
})

const uint64 = (exports.uint64 = {
  preencode(state, n) {
    state.end += 8
  },
  encode(state, n) {
    validateUint(n)
    const r = Math.floor(n / 0x100000000)
    uint32.encode(state, n)
    uint32.encode(state, r)
  },
  decode(state) {
    if (state.end - state.start < 8) throw new Error('Out of bounds')
    return uint32.decode(state) + 0x100000000 * uint32.decode(state)
  }
})

const int = (exports.int = zigZagInt(uint))
exports.int8 = zigZagInt(uint8)
exports.int16 = zigZagInt(uint16)
exports.int24 = zigZagInt(uint24)
exports.int32 = zigZagInt(uint32)
exports.int40 = zigZagInt(uint40)
exports.int48 = zigZagInt(uint48)
exports.int56 = zigZagInt(uint56)
exports.int64 = zigZagInt(uint64)

const biguint64 = (exports.biguint64 = {
  preencode(state, n) {
    state.end += 8
  },
  encode(state, n) {
    const view = new DataView(
      state.buffer.buffer,
      state.start + state.buffer.byteOffset,
      8
    )
    view.setBigUint64(0, n, true) // little endian
    state.start += 8
  },
  decode(state) {
    if (state.end - state.start < 8) throw new Error('Out of bounds')
    const view = new DataView(
      state.buffer.buffer,
      state.start + state.buffer.byteOffset,
      8
    )
    const n = view.getBigUint64(0, true) // little endian
    state.start += 8
    return n
  }
})

exports.bigint64 = zigZagBigInt(biguint64)

const biguint = (exports.biguint = {
  preencode(state, n) {
    let len = 0
    for (let m = n; m; m = m >> 64n) len++
    uint.preencode(state, len)
    state.end += 8 * len
  },
  encode(state, n) {
    let len = 0
    for (let m = n; m; m = m >> 64n) len++
    uint.encode(state, len)
    const view = new DataView(
      state.buffer.buffer,
      state.start + state.buffer.byteOffset,
      8 * len
    )
    for (let m = n, i = 0; m; m = m >> 64n, i += 8) {
      view.setBigUint64(i, BigInt.asUintN(64, m), true) // little endian
    }
    state.start += 8 * len
  },
  decode(state) {
    const len = uint.decode(state)
    if (state.end - state.start < 8 * len) throw new Error('Out of bounds')
    const view = new DataView(
      state.buffer.buffer,
      state.start + state.buffer.byteOffset,
      8 * len
    )
    let n = 0n
    for (let i = len - 1; i >= 0; i--)
      n = (n << 64n) + view.getBigUint64(i * 8, true) // little endian
    state.start += 8 * len
    return n
  }
})

exports.bigint = zigZagBigInt(biguint)

exports.lexint = require('./lexint')

exports.float32 = {
  preencode(state, n) {
    state.end += 4
  },
  encode(state, n) {
    const view = new DataView(
      state.buffer.buffer,
      state.start + state.buffer.byteOffset,
      4
    )
    view.setFloat32(0, n, true) // little endian
    state.start += 4
  },
  decode(state) {
    if (state.end - state.start < 4) throw new Error('Out of bounds')
    const view = new DataView(
      state.buffer.buffer,
      state.start + state.buffer.byteOffset,
      4
    )
    const float = view.getFloat32(0, true) // little endian
    state.start += 4
    return float
  }
}

exports.float64 = {
  preencode(state, n) {
    state.end += 8
  },
  encode(state, n) {
    const view = new DataView(
      state.buffer.buffer,
      state.start + state.buffer.byteOffset,
      8
    )
    view.setFloat64(0, n, true) // little endian
    state.start += 8
  },
  decode(state) {
    if (state.end - state.start < 8) throw new Error('Out of bounds')
    const view = new DataView(
      state.buffer.buffer,
      state.start + state.buffer.byteOffset,
      8
    )
    const float = view.getFloat64(0, true) // little endian
    state.start += 8
    return float
  }
}

const buffer = (exports.buffer = {
  preencode(state, b) {
    if (b) uint8array.preencode(state, b)
    else state.end++
  },
  encode(state, b) {
    if (b) uint8array.encode(state, b)
    else state.buffer[state.start++] = 0
  },
  decode(state) {
    const len = uint.decode(state)
    if (len === 0) return null
    if (state.end - state.start < len) throw new Error('Out of bounds')
    return state.buffer.subarray(state.start, (state.start += len))
  }
})

exports.binary = {
  ...buffer,
  preencode(state, b) {
    if (typeof b === 'string') utf8.preencode(state, b)
    else buffer.preencode(state, b)
  },
  encode(state, b) {
    if (typeof b === 'string') utf8.encode(state, b)
    else buffer.encode(state, b)
  }
}

exports.arraybuffer = {
  preencode(state, b) {
    uint.preencode(state, b.byteLength)
    state.end += b.byteLength
  },
  encode(state, b) {
    uint.encode(state, b.byteLength)

    const view = new Uint8Array(b)

    state.buffer.set(view, state.start)
    state.start += b.byteLength
  },
  decode(state) {
    const len = uint.decode(state)

    const b = new ArrayBuffer(len)
    const view = new Uint8Array(b)

    view.set(state.buffer.subarray(state.start, (state.start += len)))

    return b
  }
}

function typedarray(TypedArray, swap) {
  const n = TypedArray.BYTES_PER_ELEMENT

  return {
    preencode(state, b) {
      uint.preencode(state, b.length)
      state.end += b.byteLength
    },
    encode(state, b) {
      uint.encode(state, b.length)

      const view = new Uint8Array(b.buffer, b.byteOffset, b.byteLength)

      if (BE && swap) swap(view)

      state.buffer.set(view, state.start)
      state.start += b.byteLength
    },
    decode(state) {
      const len = uint.decode(state)

      let b = state.buffer.subarray(state.start, (state.start += len * n))
      if (b.byteLength !== len * n) throw new Error('Out of bounds')
      if (b.byteOffset % n !== 0) b = new Uint8Array(b)

      if (BE && swap) swap(b)

      return new TypedArray(b.buffer, b.byteOffset, b.byteLength / n)
    }
  }
}

const uint8array = (exports.uint8array = typedarray(Uint8Array))
exports.uint16array = typedarray(Uint16Array, b4a.swap16)
exports.uint32array = typedarray(Uint32Array, b4a.swap32)

exports.int8array = typedarray(Int8Array)
exports.int16array = typedarray(Int16Array, b4a.swap16)
exports.int32array = typedarray(Int32Array, b4a.swap32)

exports.biguint64array = typedarray(BigUint64Array, b4a.swap64)
exports.bigint64array = typedarray(BigInt64Array, b4a.swap64)

exports.float32array = typedarray(Float32Array, b4a.swap32)
exports.float64array = typedarray(Float64Array, b4a.swap64)

function string(encoding) {
  return {
    preencode(state, s) {
      const len = b4a.byteLength(s, encoding)
      uint.preencode(state, len)
      state.end += len
    },
    encode(state, s) {
      const len = b4a.byteLength(s, encoding)
      uint.encode(state, len)
      b4a.write(state.buffer, s, state.start, encoding)
      state.start += len
    },
    decode(state) {
      const len = uint.decode(state)
      if (state.end - state.start < len) throw new Error('Out of bounds')
      return b4a.toString(
        state.buffer,
        encoding,
        state.start,
        (state.start += len)
      )
    },
    fixed(n) {
      return {
        preencode(state) {
          state.end += n
        },
        encode(state, s) {
          b4a.write(state.buffer, s, state.start, n, encoding)
          state.start += n
        },
        decode(state) {
          if (state.end - state.start < n) throw new Error('Out of bounds')
          return b4a.toString(
            state.buffer,
            encoding,
            state.start,
            (state.start += n)
          )
        }
      }
    }
  }
}

const utf8 = (exports.string = exports.utf8 = string('utf-8'))
exports.ascii = string('ascii')
exports.hex = string('hex')
exports.base64 = string('base64')
exports.ucs2 = exports.utf16le = string('utf16le')

exports.bool = {
  preencode(state, b) {
    state.end++
  },
  encode(state, b) {
    state.buffer[state.start++] = b ? 1 : 0
  },
  decode(state) {
    if (state.start >= state.end) throw Error('Out of bounds')
    return state.buffer[state.start++] === 1
  }
}

const fixed = (exports.fixed = function fixed(n) {
  return {
    preencode(state, s) {
      if (s.byteLength !== n) throw new Error('Incorrect buffer size')
      state.end += n
    },
    encode(state, s) {
      state.buffer.set(s, state.start)
      state.start += n
    },
    decode(state) {
      if (state.end - state.start < n) throw new Error('Out of bounds')
      return state.buffer.subarray(state.start, (state.start += n))
    }
  }
})

exports.fixed32 = fixed(32)
exports.fixed64 = fixed(64)

exports.array = function array(enc) {
  return {
    preencode(state, list) {
      uint.preencode(state, list.length)
      for (let i = 0; i < list.length; i++) enc.preencode(state, list[i])
    },
    encode(state, list) {
      uint.encode(state, list.length)
      for (let i = 0; i < list.length; i++) enc.encode(state, list[i])
    },
    decode(state) {
      const len = uint.decode(state)
      if (len > 0x100000) throw new Error('Array is too big')
      const arr = new Array(len)
      for (let i = 0; i < len; i++) arr[i] = enc.decode(state)
      return arr
    }
  }
}

exports.frame = function frame(enc) {
  const dummy = exports.state()

  return {
    preencode(state, m) {
      const end = state.end
      enc.preencode(state, m)
      uint.preencode(state, state.end - end)
    },
    encode(state, m) {
      dummy.end = 0
      enc.preencode(dummy, m)
      uint.encode(state, dummy.end)
      enc.encode(state, m)
    },
    decode(state) {
      const end = state.end
      const len = uint.decode(state)
      state.end = state.start + len
      const m = enc.decode(state)
      state.start = state.end
      state.end = end
      return m
    }
  }
}

exports.date = {
  preencode(state, d) {
    int.preencode(state, d.getTime())
  },
  encode(state, d) {
    int.encode(state, d.getTime())
  },
  decode(state, d) {
    return new Date(int.decode(state))
  }
}

exports.json = {
  preencode(state, v) {
    utf8.preencode(state, JSON.stringify(v))
  },
  encode(state, v) {
    utf8.encode(state, JSON.stringify(v))
  },
  decode(state) {
    return JSON.parse(utf8.decode(state))
  }
}

exports.ndjson = {
  preencode(state, v) {
    utf8.preencode(state, JSON.stringify(v) + '\n')
  },
  encode(state, v) {
    utf8.encode(state, JSON.stringify(v) + '\n')
  },
  decode(state) {
    return JSON.parse(utf8.decode(state))
  }
}

// simple helper for when you want to just express nothing
exports.none = {
  preencode(state, n) {
    // do nothing
  },
  encode(state, n) {
    // do nothing
  },
  decode(state) {
    return null
  }
}

// "any" encoders here for helping just structure any object without schematising it

const anyArray = {
  preencode(state, arr) {
    uint.preencode(state, arr.length)
    for (let i = 0; i < arr.length; i++) {
      any.preencode(state, arr[i])
    }
  },
  encode(state, arr) {
    uint.encode(state, arr.length)
    for (let i = 0; i < arr.length; i++) {
      any.encode(state, arr[i])
    }
  },
  decode(state) {
    const arr = []
    let len = uint.decode(state)
    while (len-- > 0) {
      arr.push(any.decode(state))
    }
    return arr
  }
}

const anyObject = {
  preencode(state, o) {
    const keys = Object.keys(o)
    uint.preencode(state, keys.length)
    for (const key of keys) {
      utf8.preencode(state, key)
      any.preencode(state, o[key])
    }
  },
  encode(state, o) {
    const keys = Object.keys(o)
    uint.encode(state, keys.length)
    for (const key of keys) {
      utf8.encode(state, key)
      any.encode(state, o[key])
    }
  },
  decode(state) {
    let len = uint.decode(state)
    const o = {}
    while (len-- > 0) {
      const key = utf8.decode(state)
      o[key] = any.decode(state)
    }
    return o
  }
}

const anyTypes = [
  exports.none,
  exports.bool,
  exports.string,
  exports.buffer,
  exports.uint,
  exports.int,
  exports.float64,
  anyArray,
  anyObject,
  exports.date
]

const any = (exports.any = {
  preencode(state, o) {
    const t = getType(o)
    uint.preencode(state, t)
    anyTypes[t].preencode(state, o)
  },
  encode(state, o) {
    const t = getType(o)
    uint.encode(state, t)
    anyTypes[t].encode(state, o)
  },
  decode(state) {
    const t = uint.decode(state)
    if (t >= anyTypes.length) throw new Error('Unknown type: ' + t)
    return anyTypes[t].decode(state)
  }
})

const port = (exports.port = uint16)

const address = (host, family) => {
  return {
    preencode(state, m) {
      host.preencode(state, m.host)
      port.preencode(state, m.port)
    },
    encode(state, m) {
      host.encode(state, m.host)
      port.encode(state, m.port)
    },
    decode(state) {
      return {
        host: host.decode(state),
        family,
        port: port.decode(state)
      }
    }
  }
}

const ipv4 = (exports.ipv4 = {
  preencode(state) {
    state.end += 4
  },
  encode(state, string) {
    const start = state.start
    const end = start + 4

    let i = 0

    while (i < string.length) {
      let n = 0
      let c

      while (
        i < string.length &&
        (c = string.charCodeAt(i++)) !== /* . */ 0x2e
      ) {
        n = n * 10 + (c - /* 0 */ 0x30)
      }

      state.buffer[state.start++] = n
    }

    state.start = end
  },
  decode(state) {
    if (state.end - state.start < 4) throw new Error('Out of bounds')
    return (
      state.buffer[state.start++] +
      '.' +
      state.buffer[state.start++] +
      '.' +
      state.buffer[state.start++] +
      '.' +
      state.buffer[state.start++]
    )
  }
})

exports.ipv4Address = address(ipv4, 4)

const ipv6 = (exports.ipv6 = {
  preencode(state) {
    state.end += 16
  },
  encode(state, string) {
    const start = state.start
    const end = start + 16

    let i = 0
    let split = null

    while (i < string.length) {
      let n = 0
      let c

      while (
        i < string.length &&
        (c = string.charCodeAt(i++)) !== /* : */ 0x3a
      ) {
        if (c >= 0x30 && c <= 0x39) n = n * 0x10 + (c - /* 0 */ 0x30)
        else if (c >= 0x41 && c <= 0x46) n = n * 0x10 + (c - /* A */ 0x41 + 10)
        else if (c >= 0x61 && c <= 0x66) n = n * 0x10 + (c - /* a */ 0x61 + 10)
      }

      state.buffer[state.start++] = n >>> 8
      state.buffer[state.start++] = n

      if (i < string.length && string.charCodeAt(i) === /* : */ 0x3a) {
        i++
        split = state.start
      }
    }

    if (split !== null) {
      const offset = end - state.start
      state.buffer
        .copyWithin(split + offset, split)
        .fill(0, split, split + offset)
    }

    state.start = end
  },
  decode(state) {
    if (state.end - state.start < 16) throw new Error('Out of bounds')
    return (
      (
        state.buffer[state.start++] * 256 +
        state.buffer[state.start++]
      ).toString(16) +
      ':' +
      (
        state.buffer[state.start++] * 256 +
        state.buffer[state.start++]
      ).toString(16) +
      ':' +
      (
        state.buffer[state.start++] * 256 +
        state.buffer[state.start++]
      ).toString(16) +
      ':' +
      (
        state.buffer[state.start++] * 256 +
        state.buffer[state.start++]
      ).toString(16) +
      ':' +
      (
        state.buffer[state.start++] * 256 +
        state.buffer[state.start++]
      ).toString(16) +
      ':' +
      (
        state.buffer[state.start++] * 256 +
        state.buffer[state.start++]
      ).toString(16) +
      ':' +
      (
        state.buffer[state.start++] * 256 +
        state.buffer[state.start++]
      ).toString(16) +
      ':' +
      (
        state.buffer[state.start++] * 256 +
        state.buffer[state.start++]
      ).toString(16)
    )
  }
})

exports.ipv6Address = address(ipv6, 6)

const ip = (exports.ip = {
  preencode(state, string) {
    const family = string.includes(':') ? 6 : 4
    uint8.preencode(state, family)
    if (family === 4) ipv4.preencode(state)
    else ipv6.preencode(state)
  },
  encode(state, string) {
    const family = string.includes(':') ? 6 : 4
    uint8.encode(state, family)
    if (family === 4) ipv4.encode(state, string)
    else ipv6.encode(state, string)
  },
  decode(state) {
    const family = uint8.decode(state)
    if (family === 4) return ipv4.decode(state)
    else return ipv6.decode(state)
  }
})

exports.ipAddress = {
  preencode(state, m) {
    ip.preencode(state, m.host)
    port.preencode(state, m.port)
  },
  encode(state, m) {
    ip.encode(state, m.host)
    port.encode(state, m.port)
  },
  decode(state) {
    const family = uint8.decode(state)
    return {
      host: family === 4 ? ipv4.decode(state) : ipv6.decode(state),
      family,
      port: port.decode(state)
    }
  }
}

const record = (exports.record = function (keyEncoding, valueEncoding) {
  return {
    preencode(state, v) {
      const keys = Object.keys(v)
      uint.preencode(state, keys.length)
      for (const k of keys) {
        keyEncoding.preencode(state, k)
        valueEncoding.preencode(state, v[k])
      }
    },
    encode(state, v) {
      const keys = Object.keys(v)
      uint.encode(state, keys.length)
      for (const k of keys) {
        keyEncoding.encode(state, k)
        valueEncoding.encode(state, v[k])
      }
    },
    decode(state) {
      const out = Object.create(null)
      const keys = uint.decode(state)
      for (let i = 0; i < keys; i++) {
        out[keyEncoding.decode(state)] = valueEncoding.decode(state)
      }
      return out
    }
  }
})

exports.stringRecord = record(utf8, utf8)

function getType(o) {
  if (o === null || o === undefined) return 0
  if (typeof o === 'boolean') return 1
  if (typeof o === 'string') return 2
  if (b4a.isBuffer(o)) return 3
  if (typeof o === 'number') {
    if (Number.isInteger(o)) return o >= 0 ? 4 : 5
    return 6
  }
  if (Array.isArray(o)) return 7
  if (o instanceof Date) return 9
  if (typeof o === 'object') return 8

  throw new Error('Unsupported type for ' + o)
}

exports.from = function from(enc) {
  if (typeof enc === 'string') return fromNamed(enc)
  if (enc.preencode) return enc
  if (enc.encodingLength) return fromAbstractEncoder(enc)
  return fromCodec(enc)
}

function fromNamed(enc) {
  switch (enc) {
    case 'ascii':
      return raw.ascii
    case 'utf-8':
    case 'utf8':
      return raw.utf8
    case 'hex':
      return raw.hex
    case 'base64':
      return raw.base64
    case 'utf16-le':
    case 'utf16le':
    case 'ucs-2':
    case 'ucs2':
      return raw.ucs2
    case 'ndjson':
      return raw.ndjson
    case 'json':
      return raw.json
    case 'binary':
    default:
      return raw.binary
  }
}

function fromCodec(enc) {
  let tmpM = null
  let tmpBuf = null

  return {
    preencode(state, m) {
      tmpM = m
      tmpBuf = enc.encode(m)
      state.end += tmpBuf.byteLength
    },
    encode(state, m) {
      raw.encode(state, m === tmpM ? tmpBuf : enc.encode(m))
      tmpM = tmpBuf = null
    },
    decode(state) {
      return enc.decode(raw.decode(state))
    }
  }
}

function fromAbstractEncoder(enc) {
  return {
    preencode(state, m) {
      state.end += enc.encodingLength(m)
    },
    encode(state, m) {
      enc.encode(m, state.buffer, state.start)
      state.start += enc.encode.bytes
    },
    decode(state) {
      const m = enc.decode(state.buffer, state.start, state.end)
      state.start += enc.decode.bytes
      return m
    }
  }
}

exports.encode = function encode(enc, m) {
  const state = exports.state()
  enc.preencode(state, m)
  state.buffer = b4a.allocUnsafe(state.end)
  enc.encode(state, m)
  return state.buffer
}

exports.decode = function decode(enc, buffer) {
  return enc.decode(exports.state(0, buffer.byteLength, buffer))
}

function zigZagInt(enc) {
  return {
    preencode(state, n) {
      enc.preencode(state, zigZagEncodeInt(n))
    },
    encode(state, n) {
      enc.encode(state, zigZagEncodeInt(n))
    },
    decode(state) {
      return zigZagDecodeInt(enc.decode(state))
    }
  }
}

function zigZagDecodeInt(n) {
  return n === 0 ? n : (n & 1) === 0 ? n / 2 : -(n + 1) / 2
}

function zigZagEncodeInt(n) {
  // 0, -1, 1, -2, 2, ...
  return n < 0 ? 2 * -n - 1 : n === 0 ? 0 : 2 * n
}

function zigZagBigInt(enc) {
  return {
    preencode(state, n) {
      enc.preencode(state, zigZagEncodeBigInt(n))
    },
    encode(state, n) {
      enc.encode(state, zigZagEncodeBigInt(n))
    },
    decode(state) {
      return zigZagDecodeBigInt(enc.decode(state))
    }
  }
}

function zigZagDecodeBigInt(n) {
  return n === 0n ? n : (n & 1n) === 0n ? n / 2n : -(n + 1n) / 2n
}

function zigZagEncodeBigInt(n) {
  // 0, -1, 1, -2, 2, ...
  return n < 0n ? 2n * -n - 1n : n === 0n ? 0n : 2n * n
}

function validateUint(n) {
  if (n >= 0 === false /* Handles NaN as well */)
    throw new Error('uint must be positive')
}
module.exports = {
  preencode,
  encode,
  decode
}

function preencode(state, num) {
  if (num < 251) {
    state.end++
  } else if (num < 256) {
    state.end += 2
  } else if (num < 0x10000) {
    state.end += 3
  } else if (num < 0x1000000) {
    state.end += 4
  } else if (num < 0x100000000) {
    state.end += 5
  } else {
    state.end++
    const exp = Math.floor(Math.log(num) / Math.log(2)) - 32
    preencode(state, exp)
    state.end += 6
  }
}

function encode(state, num) {
  const max = 251
  const x = num - max

  if (num < max) {
    state.buffer[state.start++] = num
  } else if (num < 256) {
    state.buffer[state.start++] = max
    state.buffer[state.start++] = x
  } else if (num < 0x10000) {
    state.buffer[state.start++] = max + 1
    state.buffer[state.start++] = (x >> 8) & 0xff
    state.buffer[state.start++] = x & 0xff
  } else if (num < 0x1000000) {
    state.buffer[state.start++] = max + 2
    state.buffer[state.start++] = x >> 16
    state.buffer[state.start++] = (x >> 8) & 0xff
    state.buffer[state.start++] = x & 0xff
  } else if (num < 0x100000000) {
    state.buffer[state.start++] = max + 3
    state.buffer[state.start++] = x >> 24
    state.buffer[state.start++] = (x >> 16) & 0xff
    state.buffer[state.start++] = (x >> 8) & 0xff
    state.buffer[state.start++] = x & 0xff
  } else {
    // need to use Math here as bitwise ops are 32 bit
    const exp = Math.floor(Math.log(x) / Math.log(2)) - 32
    state.buffer[state.start++] = 0xff

    encode(state, exp)
    const rem = x / Math.pow(2, exp - 11)

    for (let i = 5; i >= 0; i--) {
      state.buffer[state.start++] = (rem / Math.pow(2, 8 * i)) & 0xff
    }
  }
}

function decode(state) {
  const max = 251

  if (state.end - state.start < 1) throw new Error('Out of bounds')

  const flag = state.buffer[state.start++]

  if (flag < max) return flag

  if (state.end - state.start < flag - max + 1) {
    throw new Error('Out of bounds.')
  }

  if (flag < 252) {
    return state.buffer[state.start++] + max
  }

  if (flag < 253) {
    return (
      (state.buffer[state.start++] << 8) + state.buffer[state.start++] + max
    )
  }

  if (flag < 254) {
    return (
      (state.buffer[state.start++] << 16) +
      (state.buffer[state.start++] << 8) +
      state.buffer[state.start++] +
      max
    )
  }

  // << 24 result may be interpreted as negative
  if (flag < 255) {
    return (
      state.buffer[state.start++] * 0x1000000 +
      (state.buffer[state.start++] << 16) +
      (state.buffer[state.start++] << 8) +
      state.buffer[state.start++] +
      max
    )
  }

  const exp = decode(state)

  if (state.end - state.start < 6) throw new Error('Out of bounds')

  let rem = 0
  for (let i = 5; i >= 0; i--) {
    rem += state.buffer[state.start++] * Math.pow(2, 8 * i)
  }

  return rem * Math.pow(2, exp - 11) + max
}
{
  "name": "compact-encoding",
  "version": "2.19.2",
  "description": "A series of compact encoding schemes for building small and fast parsers and serializers",
  "main": "index.js",
  "dependencies": {
    "b4a": "^1.3.0"
  },
  "devDependencies": {
    "brittle": "^3.0.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^1.0.0"
  },
  "scripts": {
    "format": "prettier . --write",
    "test": "prettier . --check && brittle test.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/holepunchto/compact-encoding.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/compact-encoding/issues"
  },
  "homepage": "https://github.com/holepunchto/compact-encoding"
}
const b4a = require('b4a')

const { BE } = require('./endian')

exports = module.exports = {
  preencode(state, b) {
    state.end += b.byteLength
  },
  encode(state, b) {
    state.buffer.set(b, state.start)
    state.start += b.byteLength
  },
  decode(state) {
    const b = state.buffer.subarray(state.start, state.end)
    state.start = state.end
    return b
  }
}

const buffer = (exports.buffer = {
  preencode(state, b) {
    if (b) uint8array.preencode(state, b)
    else state.end++
  },
  encode(state, b) {
    if (b) uint8array.encode(state, b)
    else state.buffer[state.start++] = 0
  },
  decode(state) {
    const b = state.buffer.subarray(state.start)
    if (b.byteLength === 0) return null
    state.start = state.end
    return b
  }
})

exports.binary = {
  ...buffer,
  preencode(state, b) {
    if (typeof b === 'string') utf8.preencode(state, b)
    else buffer.preencode(state, b)
  },
  encode(state, b) {
    if (typeof b === 'string') utf8.encode(state, b)
    else buffer.encode(state, b)
  }
}

exports.arraybuffer = {
  preencode(state, b) {
    state.end += b.byteLength
  },
  encode(state, b) {
    const view = new Uint8Array(b)

    state.buffer.set(view, state.start)
    state.start += b.byteLength
  },
  decode(state) {
    const b = new ArrayBuffer(state.end - state.start)
    const view = new Uint8Array(b)

    view.set(state.buffer.subarray(state.start))

    state.start = state.end

    return b
  }
}

function typedarray(TypedArray, swap) {
  const n = TypedArray.BYTES_PER_ELEMENT

  return {
    preencode(state, b) {
      state.end += b.byteLength
    },
    encode(state, b) {
      const view = new Uint8Array(b.buffer, b.byteOffset, b.byteLength)

      if (BE && swap) swap(view)

      state.buffer.set(view, state.start)
      state.start += b.byteLength
    },
    decode(state) {
      let b = state.buffer.subarray(state.start)
      if (b.byteOffset % n !== 0) b = new Uint8Array(b)

      if (BE && swap) swap(b)

      state.start = state.end

      return new TypedArray(b.buffer, b.byteOffset, b.byteLength / n)
    }
  }
}

const uint8array = (exports.uint8array = typedarray(Uint8Array))
exports.uint16array = typedarray(Uint16Array, b4a.swap16)
exports.uint32array = typedarray(Uint32Array, b4a.swap32)

exports.int8array = typedarray(Int8Array)
exports.int16array = typedarray(Int16Array, b4a.swap16)
exports.int32array = typedarray(Int32Array, b4a.swap32)

exports.biguint64array = typedarray(BigUint64Array, b4a.swap64)
exports.bigint64array = typedarray(BigInt64Array, b4a.swap64)

exports.float32array = typedarray(Float32Array, b4a.swap32)
exports.float64array = typedarray(Float64Array, b4a.swap64)

function string(encoding) {
  return {
    preencode(state, s) {
      state.end += b4a.byteLength(s, encoding)
    },
    encode(state, s) {
      state.start += b4a.write(state.buffer, s, state.start, encoding)
    },
    decode(state) {
      const s = b4a.toString(state.buffer, encoding, state.start)
      state.start = state.end
      return s
    }
  }
}

const utf8 = (exports.string = exports.utf8 = string('utf-8'))
exports.ascii = string('ascii')
exports.hex = string('hex')
exports.base64 = string('base64')
exports.ucs2 = exports.utf16le = string('utf16le')

exports.array = function array(enc) {
  return {
    preencode(state, list) {
      for (const value of list) enc.preencode(state, value)
    },
    encode(state, list) {
      for (const value of list) enc.encode(state, value)
    },
    decode(state) {
      const arr = []
      while (state.start < state.end) arr.push(enc.decode(state))
      return arr
    }
  }
}

exports.json = {
  preencode(state, v) {
    utf8.preencode(state, JSON.stringify(v))
  },
  encode(state, v) {
    utf8.encode(state, JSON.stringify(v))
  },
  decode(state) {
    return JSON.parse(utf8.decode(state))
  }
}

exports.ndjson = {
  preencode(state, v) {
    utf8.preencode(state, JSON.stringify(v) + '\n')
  },
  encode(state, v) {
    utf8.encode(state, JSON.stringify(v) + '\n')
  },
  decode(state) {
    return JSON.parse(utf8.decode(state))
  }
}
{
  "name": "hypercore-crypto",
  "version": "3.6.1",
  "description": "The crypto primitives used in hypercore, extracted into a separate module",
  "main": "index.js",
  "files": [
    "index.js"
  ],
  "dependencies": {
    "b4a": "^1.6.6",
    "compact-encoding": "^2.15.0",
    "sodium-universal": "^5.0.0"
  },
  "devDependencies": {
    "brittle": "^3.5.0",
    "standard": "^17.1.0"
  },
  "scripts": {
    "test": "standard && brittle test.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/mafintosh/hypercore-crypto.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/mafintosh/hypercore-crypto/issues"
  },
  "homepage": "https://github.com/mafintosh/hypercore-crypto"
}
const z32 = require('z32')
const b4a = require('b4a')

module.exports = {
  encode,
  decode,
  normalize,
  isValid
}

function encode (key) {
  if (!b4a.isBuffer(key)) throw new Error('Key must be a Buffer')
  if (key.byteLength !== 32) throw new Error('Key must be 32-bytes long')
  return z32.encode(key)
}

function decode (id) {
  if (b4a.isBuffer(id)) {
    if (id.byteLength !== 32) throw new Error('ID must be 32-bytes long')
    return id
  }
  if (typeof id === 'string') {
    if (id.startsWith('pear://')) id = id.slice(7).split('/')[0]
    if (id.length === 52) return z32.decode(id)
    if (id.length === 64) {
      const buf = b4a.from(id, 'hex')
      if (buf.byteLength === 32) return buf
    }
  }
  throw new Error('Invalid Hypercore key')
}

function normalize (any) {
  return encode(decode(any))
}

function isValid (any) {
  try {
    decode(any)
    return true
  } catch {
    return false
  }
}
{
  "name": "hypercore-id-encoding",
  "version": "1.3.0",
  "description": "Convert Hypercore keys to/from z-base32 or hex",
  "main": "index.js",
  "scripts": {
    "test": "standard && brittle test.js"
  },
  "files": [
    "index.js"
  ],
  "dependencies": {
    "b4a": "^1.5.3",
    "z32": "^1.0.0"
  },
  "devDependencies": {
    "brittle": "^3.1.1",
    "hypercore": "^10.0.0",
    "random-access-memory": "^6.0.0",
    "standard": "^17.1.0"
  },
  "license": "Apache-2.0",
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/hypercore-id-encoding.git"
  },
  "keywords": [],
  "author": "",
  "bugs": {
    "url": "https://github.com/holepunchto/hypercore-id-encoding/issues"
  },
  "homepage": "https://github.com/holepunchto/hypercore-id-encoding#readme"
}
const c = require('compact-encoding')
const { getEncoding } = require('./spec/hyperschema')

const HyperdhtAddress = getEncoding('@hyperdht/address')

function decode(buf) {
  const addr = c.decode(HyperdhtAddress, buf)
  return addr
}

function encode(key, nodes) {
  return c.encode(HyperdhtAddress, { key, nodes })
}

module.exports = { decode, encode }
const LE = (exports.LE =
  new Uint8Array(new Uint16Array([0xff]).buffer)[0] === 0xff)

exports.BE = !LE
const b4a = require('b4a')

const { BE } = require('./endian')

exports.state = function (start = 0, end = 0, buffer = null) {
  return { start, end, buffer }
}

const raw = (exports.raw = require('./raw'))

const uint = (exports.uint = {
  preencode(state, n) {
    state.end += n <= 0xfc ? 1 : n <= 0xffff ? 3 : n <= 0xffffffff ? 5 : 9
  },
  encode(state, n) {
    if (n <= 0xfc) uint8.encode(state, n)
    else if (n <= 0xffff) {
      state.buffer[state.start++] = 0xfd
      uint16.encode(state, n)
    } else if (n <= 0xffffffff) {
      state.buffer[state.start++] = 0xfe
      uint32.encode(state, n)
    } else {
      state.buffer[state.start++] = 0xff
      uint64.encode(state, n)
    }
  },
  decode(state) {
    const a = uint8.decode(state)
    if (a <= 0xfc) return a
    if (a === 0xfd) return uint16.decode(state)
    if (a === 0xfe) return uint32.decode(state)
    return uint64.decode(state)
  }
})

const uint8 = (exports.uint8 = {
  preencode(state, n) {
    state.end += 1
  },
  encode(state, n) {
    validateUint(n)
    state.buffer[state.start++] = n
  },
  decode(state) {
    if (state.start >= state.end) throw new Error('Out of bounds')
    return state.buffer[state.start++]
  }
})

const uint16 = (exports.uint16 = {
  preencode(state, n) {
    state.end += 2
  },
  encode(state, n) {
    validateUint(n)
    state.buffer[state.start++] = n
    state.buffer[state.start++] = n >>> 8
  },
  decode(state) {
    if (state.end - state.start < 2) throw new Error('Out of bounds')
    return state.buffer[state.start++] + state.buffer[state.start++] * 0x100
  }
})

const uint24 = (exports.uint24 = {
  preencode(state, n) {
    state.end += 3
  },
  encode(state, n) {
    validateUint(n)
    state.buffer[state.start++] = n
    state.buffer[state.start++] = n >>> 8
    state.buffer[state.start++] = n >>> 16
  },
  decode(state) {
    if (state.end - state.start < 3) throw new Error('Out of bounds')
    return (
      state.buffer[state.start++] +
      state.buffer[state.start++] * 0x100 +
      state.buffer[state.start++] * 0x10000
    )
  }
})

const uint32 = (exports.uint32 = {
  preencode(state, n) {
    state.end += 4
  },
  encode(state, n) {
    validateUint(n)
    state.buffer[state.start++] = n
    state.buffer[state.start++] = n >>> 8
    state.buffer[state.start++] = n >>> 16
    state.buffer[state.start++] = n >>> 24
  },
  decode(state) {
    if (state.end - state.start < 4) throw new Error('Out of bounds')
    return (
      state.buffer[state.start++] +
      state.buffer[state.start++] * 0x100 +
      state.buffer[state.start++] * 0x10000 +
      state.buffer[state.start++] * 0x1000000
    )
  }
})

const uint40 = (exports.uint40 = {
  preencode(state, n) {
    state.end += 5
  },
  encode(state, n) {
    validateUint(n)
    const r = Math.floor(n / 0x100)
    uint8.encode(state, n)
    uint32.encode(state, r)
  },
  decode(state) {
    if (state.end - state.start < 5) throw new Error('Out of bounds')
    return uint8.decode(state) + 0x100 * uint32.decode(state)
  }
})

const uint48 = (exports.uint48 = {
  preencode(state, n) {
    state.end += 6
  },
  encode(state, n) {
    validateUint(n)
    const r = Math.floor(n / 0x10000)
    uint16.encode(state, n)
    uint32.encode(state, r)
  },
  decode(state) {
    if (state.end - state.start < 6) throw new Error('Out of bounds')
    return uint16.decode(state) + 0x10000 * uint32.decode(state)
  }
})

const uint56 = (exports.uint56 = {
  preencode(state, n) {
    state.end += 7
  },
  encode(state, n) {
    validateUint(n)
    const r = Math.floor(n / 0x1000000)
    uint24.encode(state, n)
    uint32.encode(state, r)
  },
  decode(state) {
    if (state.end - state.start < 7) throw new Error('Out of bounds')
    return uint24.decode(state) + 0x1000000 * uint32.decode(state)
  }
})

const uint64 = (exports.uint64 = {
  preencode(state, n) {
    state.end += 8
  },
  encode(state, n) {
    validateUint(n)
    const r = Math.floor(n / 0x100000000)
    uint32.encode(state, n)
    uint32.encode(state, r)
  },
  decode(state) {
    if (state.end - state.start < 8) throw new Error('Out of bounds')
    return uint32.decode(state) + 0x100000000 * uint32.decode(state)
  }
})

const int = (exports.int = zigZagInt(uint))
exports.int8 = zigZagInt(uint8)
exports.int16 = zigZagInt(uint16)
exports.int24 = zigZagInt(uint24)
exports.int32 = zigZagInt(uint32)
exports.int40 = zigZagInt(uint40)
exports.int48 = zigZagInt(uint48)
exports.int56 = zigZagInt(uint56)
exports.int64 = zigZagInt(uint64)

const biguint64 = (exports.biguint64 = {
  preencode(state, n) {
    state.end += 8
  },
  encode(state, n) {
    const view = new DataView(
      state.buffer.buffer,
      state.start + state.buffer.byteOffset,
      8
    )
    view.setBigUint64(0, n, true) // little endian
    state.start += 8
  },
  decode(state) {
    if (state.end - state.start < 8) throw new Error('Out of bounds')
    const view = new DataView(
      state.buffer.buffer,
      state.start + state.buffer.byteOffset,
      8
    )
    const n = view.getBigUint64(0, true) // little endian
    state.start += 8
    return n
  }
})

exports.bigint64 = zigZagBigInt(biguint64)

const biguint = (exports.biguint = {
  preencode(state, n) {
    let len = 0
    for (let m = n; m; m = m >> 64n) len++
    uint.preencode(state, len)
    state.end += 8 * len
  },
  encode(state, n) {
    let len = 0
    for (let m = n; m; m = m >> 64n) len++
    uint.encode(state, len)
    const view = new DataView(
      state.buffer.buffer,
      state.start + state.buffer.byteOffset,
      8 * len
    )
    for (let m = n, i = 0; m; m = m >> 64n, i += 8) {
      view.setBigUint64(i, BigInt.asUintN(64, m), true) // little endian
    }
    state.start += 8 * len
  },
  decode(state) {
    const len = uint.decode(state)
    if (state.end - state.start < 8 * len) throw new Error('Out of bounds')
    const view = new DataView(
      state.buffer.buffer,
      state.start + state.buffer.byteOffset,
      8 * len
    )
    let n = 0n
    for (let i = len - 1; i >= 0; i--)
      n = (n << 64n) + view.getBigUint64(i * 8, true) // little endian
    state.start += 8 * len
    return n
  }
})

exports.bigint = zigZagBigInt(biguint)

exports.lexint = require('./lexint')

exports.float32 = {
  preencode(state, n) {
    state.end += 4
  },
  encode(state, n) {
    const view = new DataView(
      state.buffer.buffer,
      state.start + state.buffer.byteOffset,
      4
    )
    view.setFloat32(0, n, true) // little endian
    state.start += 4
  },
  decode(state) {
    if (state.end - state.start < 4) throw new Error('Out of bounds')
    const view = new DataView(
      state.buffer.buffer,
      state.start + state.buffer.byteOffset,
      4
    )
    const float = view.getFloat32(0, true) // little endian
    state.start += 4
    return float
  }
}

exports.float64 = {
  preencode(state, n) {
    state.end += 8
  },
  encode(state, n) {
    const view = new DataView(
      state.buffer.buffer,
      state.start + state.buffer.byteOffset,
      8
    )
    view.setFloat64(0, n, true) // little endian
    state.start += 8
  },
  decode(state) {
    if (state.end - state.start < 8) throw new Error('Out of bounds')
    const view = new DataView(
      state.buffer.buffer,
      state.start + state.buffer.byteOffset,
      8
    )
    const float = view.getFloat64(0, true) // little endian
    state.start += 8
    return float
  }
}

const buffer = (exports.buffer = {
  preencode(state, b) {
    if (b) uint8array.preencode(state, b)
    else state.end++
  },
  encode(state, b) {
    if (b) uint8array.encode(state, b)
    else state.buffer[state.start++] = 0
  },
  decode(state) {
    const len = uint.decode(state)
    if (len === 0) return null
    if (state.end - state.start < len) throw new Error('Out of bounds')
    return state.buffer.subarray(state.start, (state.start += len))
  }
})

exports.binary = {
  ...buffer,
  preencode(state, b) {
    if (typeof b === 'string') utf8.preencode(state, b)
    else buffer.preencode(state, b)
  },
  encode(state, b) {
    if (typeof b === 'string') utf8.encode(state, b)
    else buffer.encode(state, b)
  }
}

exports.arraybuffer = {
  preencode(state, b) {
    uint.preencode(state, b.byteLength)
    state.end += b.byteLength
  },
  encode(state, b) {
    uint.encode(state, b.byteLength)

    const view = new Uint8Array(b)

    state.buffer.set(view, state.start)
    state.start += b.byteLength
  },
  decode(state) {
    const len = uint.decode(state)

    const b = new ArrayBuffer(len)
    const view = new Uint8Array(b)

    view.set(state.buffer.subarray(state.start, (state.start += len)))

    return b
  }
}

function typedarray(TypedArray, swap) {
  const n = TypedArray.BYTES_PER_ELEMENT

  return {
    preencode(state, b) {
      uint.preencode(state, b.length)
      state.end += b.byteLength
    },
    encode(state, b) {
      uint.encode(state, b.length)

      const view = new Uint8Array(b.buffer, b.byteOffset, b.byteLength)

      if (BE && swap) swap(view)

      state.buffer.set(view, state.start)
      state.start += b.byteLength
    },
    decode(state) {
      const len = uint.decode(state)

      let b = state.buffer.subarray(state.start, (state.start += len * n))
      if (b.byteLength !== len * n) throw new Error('Out of bounds')
      if (b.byteOffset % n !== 0) b = new Uint8Array(b)

      if (BE && swap) swap(b)

      return new TypedArray(b.buffer, b.byteOffset, b.byteLength / n)
    }
  }
}

const uint8array = (exports.uint8array = typedarray(Uint8Array))
exports.uint16array = typedarray(Uint16Array, b4a.swap16)
exports.uint32array = typedarray(Uint32Array, b4a.swap32)

exports.int8array = typedarray(Int8Array)
exports.int16array = typedarray(Int16Array, b4a.swap16)
exports.int32array = typedarray(Int32Array, b4a.swap32)

exports.biguint64array = typedarray(BigUint64Array, b4a.swap64)
exports.bigint64array = typedarray(BigInt64Array, b4a.swap64)

exports.float32array = typedarray(Float32Array, b4a.swap32)
exports.float64array = typedarray(Float64Array, b4a.swap64)

function string(encoding) {
  return {
    preencode(state, s) {
      const len = b4a.byteLength(s, encoding)
      uint.preencode(state, len)
      state.end += len
    },
    encode(state, s) {
      const len = b4a.byteLength(s, encoding)
      uint.encode(state, len)
      b4a.write(state.buffer, s, state.start, encoding)
      state.start += len
    },
    decode(state) {
      const len = uint.decode(state)
      if (state.end - state.start < len) throw new Error('Out of bounds')
      return b4a.toString(
        state.buffer,
        encoding,
        state.start,
        (state.start += len)
      )
    },
    fixed(n) {
      return {
        preencode(state) {
          state.end += n
        },
        encode(state, s) {
          b4a.write(state.buffer, s, state.start, n, encoding)
          state.start += n
        },
        decode(state) {
          if (state.end - state.start < n) throw new Error('Out of bounds')
          return b4a.toString(
            state.buffer,
            encoding,
            state.start,
            (state.start += n)
          )
        }
      }
    }
  }
}

const utf8 = (exports.string = exports.utf8 = string('utf-8'))
exports.ascii = string('ascii')
exports.hex = string('hex')
exports.base64 = string('base64')
exports.ucs2 = exports.utf16le = string('utf16le')

exports.bool = {
  preencode(state, b) {
    state.end++
  },
  encode(state, b) {
    state.buffer[state.start++] = b ? 1 : 0
  },
  decode(state) {
    if (state.start >= state.end) throw Error('Out of bounds')
    return state.buffer[state.start++] === 1
  }
}

const fixed = (exports.fixed = function fixed(n) {
  return {
    preencode(state, s) {
      if (s.byteLength !== n) throw new Error('Incorrect buffer size')
      state.end += n
    },
    encode(state, s) {
      state.buffer.set(s, state.start)
      state.start += n
    },
    decode(state) {
      if (state.end - state.start < n) throw new Error('Out of bounds')
      return state.buffer.subarray(state.start, (state.start += n))
    }
  }
})

exports.fixed32 = fixed(32)
exports.fixed64 = fixed(64)

exports.array = function array(enc) {
  return {
    preencode(state, list) {
      uint.preencode(state, list.length)
      for (let i = 0; i < list.length; i++) enc.preencode(state, list[i])
    },
    encode(state, list) {
      uint.encode(state, list.length)
      for (let i = 0; i < list.length; i++) enc.encode(state, list[i])
    },
    decode(state) {
      const len = uint.decode(state)
      if (len > 0x100000) throw new Error('Array is too big')
      const arr = new Array(len)
      for (let i = 0; i < len; i++) arr[i] = enc.decode(state)
      return arr
    }
  }
}

exports.frame = function frame(enc) {
  const dummy = exports.state()

  return {
    preencode(state, m) {
      const end = state.end
      enc.preencode(state, m)
      uint.preencode(state, state.end - end)
    },
    encode(state, m) {
      dummy.end = 0
      enc.preencode(dummy, m)
      uint.encode(state, dummy.end)
      enc.encode(state, m)
    },
    decode(state) {
      const end = state.end
      const len = uint.decode(state)
      state.end = state.start + len
      const m = enc.decode(state)
      state.start = state.end
      state.end = end
      return m
    }
  }
}

exports.date = {
  preencode(state, d) {
    int.preencode(state, d.getTime())
  },
  encode(state, d) {
    int.encode(state, d.getTime())
  },
  decode(state, d) {
    return new Date(int.decode(state))
  }
}

exports.json = {
  preencode(state, v) {
    utf8.preencode(state, JSON.stringify(v))
  },
  encode(state, v) {
    utf8.encode(state, JSON.stringify(v))
  },
  decode(state) {
    return JSON.parse(utf8.decode(state))
  }
}

exports.ndjson = {
  preencode(state, v) {
    utf8.preencode(state, JSON.stringify(v) + '\n')
  },
  encode(state, v) {
    utf8.encode(state, JSON.stringify(v) + '\n')
  },
  decode(state) {
    return JSON.parse(utf8.decode(state))
  }
}

// simple helper for when you want to just express nothing
exports.none = {
  preencode(state, n) {
    // do nothing
  },
  encode(state, n) {
    // do nothing
  },
  decode(state) {
    return null
  }
}

// "any" encoders here for helping just structure any object without schematising it

const anyArray = {
  preencode(state, arr) {
    uint.preencode(state, arr.length)
    for (let i = 0; i < arr.length; i++) {
      any.preencode(state, arr[i])
    }
  },
  encode(state, arr) {
    uint.encode(state, arr.length)
    for (let i = 0; i < arr.length; i++) {
      any.encode(state, arr[i])
    }
  },
  decode(state) {
    const arr = []
    let len = uint.decode(state)
    while (len-- > 0) {
      arr.push(any.decode(state))
    }
    return arr
  }
}

const anyObject = {
  preencode(state, o) {
    const keys = Object.keys(o)
    uint.preencode(state, keys.length)
    for (const key of keys) {
      utf8.preencode(state, key)
      any.preencode(state, o[key])
    }
  },
  encode(state, o) {
    const keys = Object.keys(o)
    uint.encode(state, keys.length)
    for (const key of keys) {
      utf8.encode(state, key)
      any.encode(state, o[key])
    }
  },
  decode(state) {
    let len = uint.decode(state)
    const o = {}
    while (len-- > 0) {
      const key = utf8.decode(state)
      o[key] = any.decode(state)
    }
    return o
  }
}

const anyTypes = [
  exports.none,
  exports.bool,
  exports.string,
  exports.buffer,
  exports.uint,
  exports.int,
  exports.float64,
  anyArray,
  anyObject,
  exports.date
]

const any = (exports.any = {
  preencode(state, o) {
    const t = getType(o)
    uint.preencode(state, t)
    anyTypes[t].preencode(state, o)
  },
  encode(state, o) {
    const t = getType(o)
    uint.encode(state, t)
    anyTypes[t].encode(state, o)
  },
  decode(state) {
    const t = uint.decode(state)
    if (t >= anyTypes.length) throw new Error('Unknown type: ' + t)
    return anyTypes[t].decode(state)
  }
})

const port = (exports.port = uint16)

const address = (host, family) => {
  return {
    preencode(state, m) {
      host.preencode(state, m.host)
      port.preencode(state, m.port)
    },
    encode(state, m) {
      host.encode(state, m.host)
      port.encode(state, m.port)
    },
    decode(state) {
      return {
        host: host.decode(state),
        family,
        port: port.decode(state)
      }
    }
  }
}

const ipv4 = (exports.ipv4 = {
  preencode(state) {
    state.end += 4
  },
  encode(state, string) {
    const start = state.start
    const end = start + 4

    let i = 0

    while (i < string.length) {
      let n = 0
      let c

      while (
        i < string.length &&
        (c = string.charCodeAt(i++)) !== /* . */ 0x2e
      ) {
        n = n * 10 + (c - /* 0 */ 0x30)
      }

      state.buffer[state.start++] = n
    }

    state.start = end
  },
  decode(state) {
    if (state.end - state.start < 4) throw new Error('Out of bounds')
    return (
      state.buffer[state.start++] +
      '.' +
      state.buffer[state.start++] +
      '.' +
      state.buffer[state.start++] +
      '.' +
      state.buffer[state.start++]
    )
  }
})

exports.ipv4Address = address(ipv4, 4)

const ipv6 = (exports.ipv6 = {
  preencode(state) {
    state.end += 16
  },
  encode(state, string) {
    const start = state.start
    const end = start + 16

    let i = 0
    let split = null

    while (i < string.length) {
      let n = 0
      let c

      while (
        i < string.length &&
        (c = string.charCodeAt(i++)) !== /* : */ 0x3a
      ) {
        if (c >= 0x30 && c <= 0x39) n = n * 0x10 + (c - /* 0 */ 0x30)
        else if (c >= 0x41 && c <= 0x46) n = n * 0x10 + (c - /* A */ 0x41 + 10)
        else if (c >= 0x61 && c <= 0x66) n = n * 0x10 + (c - /* a */ 0x61 + 10)
      }

      state.buffer[state.start++] = n >>> 8
      state.buffer[state.start++] = n

      if (i < string.length && string.charCodeAt(i) === /* : */ 0x3a) {
        i++
        split = state.start
      }
    }

    if (split !== null) {
      const offset = end - state.start
      state.buffer
        .copyWithin(split + offset, split)
        .fill(0, split, split + offset)
    }

    state.start = end
  },
  decode(state) {
    if (state.end - state.start < 16) throw new Error('Out of bounds')
    return (
      (
        state.buffer[state.start++] * 256 +
        state.buffer[state.start++]
      ).toString(16) +
      ':' +
      (
        state.buffer[state.start++] * 256 +
        state.buffer[state.start++]
      ).toString(16) +
      ':' +
      (
        state.buffer[state.start++] * 256 +
        state.buffer[state.start++]
      ).toString(16) +
      ':' +
      (
        state.buffer[state.start++] * 256 +
        state.buffer[state.start++]
      ).toString(16) +
      ':' +
      (
        state.buffer[state.start++] * 256 +
        state.buffer[state.start++]
      ).toString(16) +
      ':' +
      (
        state.buffer[state.start++] * 256 +
        state.buffer[state.start++]
      ).toString(16) +
      ':' +
      (
        state.buffer[state.start++] * 256 +
        state.buffer[state.start++]
      ).toString(16) +
      ':' +
      (
        state.buffer[state.start++] * 256 +
        state.buffer[state.start++]
      ).toString(16)
    )
  }
})

exports.ipv6Address = address(ipv6, 6)

const ip = (exports.ip = {
  preencode(state, string) {
    const family = string.includes(':') ? 6 : 4
    uint8.preencode(state, family)
    if (family === 4) ipv4.preencode(state)
    else ipv6.preencode(state)
  },
  encode(state, string) {
    const family = string.includes(':') ? 6 : 4
    uint8.encode(state, family)
    if (family === 4) ipv4.encode(state, string)
    else ipv6.encode(state, string)
  },
  decode(state) {
    const family = uint8.decode(state)
    if (family === 4) return ipv4.decode(state)
    else return ipv6.decode(state)
  }
})

exports.ipAddress = {
  preencode(state, m) {
    ip.preencode(state, m.host)
    port.preencode(state, m.port)
  },
  encode(state, m) {
    ip.encode(state, m.host)
    port.encode(state, m.port)
  },
  decode(state) {
    const family = uint8.decode(state)
    return {
      host: family === 4 ? ipv4.decode(state) : ipv6.decode(state),
      family,
      port: port.decode(state)
    }
  }
}

const record = (exports.record = function (keyEncoding, valueEncoding) {
  return {
    preencode(state, v) {
      const keys = Object.keys(v)
      uint.preencode(state, keys.length)
      for (const k of keys) {
        keyEncoding.preencode(state, k)
        valueEncoding.preencode(state, v[k])
      }
    },
    encode(state, v) {
      const keys = Object.keys(v)
      uint.encode(state, keys.length)
      for (const k of keys) {
        keyEncoding.encode(state, k)
        valueEncoding.encode(state, v[k])
      }
    },
    decode(state) {
      const out = Object.create(null)
      const keys = uint.decode(state)
      for (let i = 0; i < keys; i++) {
        out[keyEncoding.decode(state)] = valueEncoding.decode(state)
      }
      return out
    }
  }
})

exports.stringRecord = record(utf8, utf8)

function getType(o) {
  if (o === null || o === undefined) return 0
  if (typeof o === 'boolean') return 1
  if (typeof o === 'string') return 2
  if (b4a.isBuffer(o)) return 3
  if (typeof o === 'number') {
    if (Number.isInteger(o)) return o >= 0 ? 4 : 5
    return 6
  }
  if (Array.isArray(o)) return 7
  if (o instanceof Date) return 9
  if (typeof o === 'object') return 8

  throw new Error('Unsupported type for ' + o)
}

exports.from = function from(enc) {
  if (typeof enc === 'string') return fromNamed(enc)
  if (enc.preencode) return enc
  if (enc.encodingLength) return fromAbstractEncoder(enc)
  return fromCodec(enc)
}

function fromNamed(enc) {
  switch (enc) {
    case 'ascii':
      return raw.ascii
    case 'utf-8':
    case 'utf8':
      return raw.utf8
    case 'hex':
      return raw.hex
    case 'base64':
      return raw.base64
    case 'utf16-le':
    case 'utf16le':
    case 'ucs-2':
    case 'ucs2':
      return raw.ucs2
    case 'ndjson':
      return raw.ndjson
    case 'json':
      return raw.json
    case 'binary':
    default:
      return raw.binary
  }
}

function fromCodec(enc) {
  let tmpM = null
  let tmpBuf = null

  return {
    preencode(state, m) {
      tmpM = m
      tmpBuf = enc.encode(m)
      state.end += tmpBuf.byteLength
    },
    encode(state, m) {
      raw.encode(state, m === tmpM ? tmpBuf : enc.encode(m))
      tmpM = tmpBuf = null
    },
    decode(state) {
      return enc.decode(raw.decode(state))
    }
  }
}

function fromAbstractEncoder(enc) {
  return {
    preencode(state, m) {
      state.end += enc.encodingLength(m)
    },
    encode(state, m) {
      enc.encode(m, state.buffer, state.start)
      state.start += enc.encode.bytes
    },
    decode(state) {
      const m = enc.decode(state.buffer, state.start, state.end)
      state.start += enc.decode.bytes
      return m
    }
  }
}

exports.encode = function encode(enc, m) {
  const state = exports.state()
  enc.preencode(state, m)
  state.buffer = b4a.allocUnsafe(state.end)
  enc.encode(state, m)
  return state.buffer
}

exports.decode = function decode(enc, buffer) {
  return enc.decode(exports.state(0, buffer.byteLength, buffer))
}

function zigZagInt(enc) {
  return {
    preencode(state, n) {
      enc.preencode(state, zigZagEncodeInt(n))
    },
    encode(state, n) {
      enc.encode(state, zigZagEncodeInt(n))
    },
    decode(state) {
      return zigZagDecodeInt(enc.decode(state))
    }
  }
}

function zigZagDecodeInt(n) {
  return n === 0 ? n : (n & 1) === 0 ? n / 2 : -(n + 1) / 2
}

function zigZagEncodeInt(n) {
  // 0, -1, 1, -2, 2, ...
  return n < 0 ? 2 * -n - 1 : n === 0 ? 0 : 2 * n
}

function zigZagBigInt(enc) {
  return {
    preencode(state, n) {
      enc.preencode(state, zigZagEncodeBigInt(n))
    },
    encode(state, n) {
      enc.encode(state, zigZagEncodeBigInt(n))
    },
    decode(state) {
      return zigZagDecodeBigInt(enc.decode(state))
    }
  }
}

function zigZagDecodeBigInt(n) {
  return n === 0n ? n : (n & 1n) === 0n ? n / 2n : -(n + 1n) / 2n
}

function zigZagEncodeBigInt(n) {
  // 0, -1, 1, -2, 2, ...
  return n < 0n ? 2n * -n - 1n : n === 0n ? 0n : 2n * n
}

function validateUint(n) {
  if (n >= 0 === false /* Handles NaN as well */)
    throw new Error('uint must be positive')
}
module.exports = {
  preencode,
  encode,
  decode
}

function preencode(state, num) {
  if (num < 251) {
    state.end++
  } else if (num < 256) {
    state.end += 2
  } else if (num < 0x10000) {
    state.end += 3
  } else if (num < 0x1000000) {
    state.end += 4
  } else if (num < 0x100000000) {
    state.end += 5
  } else {
    state.end++
    const exp = Math.floor(Math.log(num) / Math.log(2)) - 32
    preencode(state, exp)
    state.end += 6
  }
}

function encode(state, num) {
  const max = 251
  const x = num - max

  if (num < max) {
    state.buffer[state.start++] = num
  } else if (num < 256) {
    state.buffer[state.start++] = max
    state.buffer[state.start++] = x
  } else if (num < 0x10000) {
    state.buffer[state.start++] = max + 1
    state.buffer[state.start++] = (x >> 8) & 0xff
    state.buffer[state.start++] = x & 0xff
  } else if (num < 0x1000000) {
    state.buffer[state.start++] = max + 2
    state.buffer[state.start++] = x >> 16
    state.buffer[state.start++] = (x >> 8) & 0xff
    state.buffer[state.start++] = x & 0xff
  } else if (num < 0x100000000) {
    state.buffer[state.start++] = max + 3
    state.buffer[state.start++] = x >> 24
    state.buffer[state.start++] = (x >> 16) & 0xff
    state.buffer[state.start++] = (x >> 8) & 0xff
    state.buffer[state.start++] = x & 0xff
  } else {
    // need to use Math here as bitwise ops are 32 bit
    const exp = Math.floor(Math.log(x) / Math.log(2)) - 32
    state.buffer[state.start++] = 0xff

    encode(state, exp)
    const rem = x / Math.pow(2, exp - 11)

    for (let i = 5; i >= 0; i--) {
      state.buffer[state.start++] = (rem / Math.pow(2, 8 * i)) & 0xff
    }
  }
}

function decode(state) {
  const max = 251

  if (state.end - state.start < 1) throw new Error('Out of bounds')

  const flag = state.buffer[state.start++]

  if (flag < max) return flag

  if (state.end - state.start < flag - max + 1) {
    throw new Error('Out of bounds.')
  }

  if (flag < 252) {
    return state.buffer[state.start++] + max
  }

  if (flag < 253) {
    return (
      (state.buffer[state.start++] << 8) + state.buffer[state.start++] + max
    )
  }

  if (flag < 254) {
    return (
      (state.buffer[state.start++] << 16) +
      (state.buffer[state.start++] << 8) +
      state.buffer[state.start++] +
      max
    )
  }

  // << 24 result may be interpreted as negative
  if (flag < 255) {
    return (
      state.buffer[state.start++] * 0x1000000 +
      (state.buffer[state.start++] << 16) +
      (state.buffer[state.start++] << 8) +
      state.buffer[state.start++] +
      max
    )
  }

  const exp = decode(state)

  if (state.end - state.start < 6) throw new Error('Out of bounds')

  let rem = 0
  for (let i = 5; i >= 0; i--) {
    rem += state.buffer[state.start++] * Math.pow(2, 8 * i)
  }

  return rem * Math.pow(2, exp - 11) + max
}
{
  "name": "compact-encoding",
  "version": "2.19.2",
  "description": "A series of compact encoding schemes for building small and fast parsers and serializers",
  "main": "index.js",
  "dependencies": {
    "b4a": "^1.3.0"
  },
  "devDependencies": {
    "brittle": "^3.0.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^1.0.0"
  },
  "scripts": {
    "format": "prettier . --write",
    "test": "prettier . --check && brittle test.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/holepunchto/compact-encoding.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/compact-encoding/issues"
  },
  "homepage": "https://github.com/holepunchto/compact-encoding"
}
const b4a = require('b4a')

const { BE } = require('./endian')

exports = module.exports = {
  preencode(state, b) {
    state.end += b.byteLength
  },
  encode(state, b) {
    state.buffer.set(b, state.start)
    state.start += b.byteLength
  },
  decode(state) {
    const b = state.buffer.subarray(state.start, state.end)
    state.start = state.end
    return b
  }
}

const buffer = (exports.buffer = {
  preencode(state, b) {
    if (b) uint8array.preencode(state, b)
    else state.end++
  },
  encode(state, b) {
    if (b) uint8array.encode(state, b)
    else state.buffer[state.start++] = 0
  },
  decode(state) {
    const b = state.buffer.subarray(state.start)
    if (b.byteLength === 0) return null
    state.start = state.end
    return b
  }
})

exports.binary = {
  ...buffer,
  preencode(state, b) {
    if (typeof b === 'string') utf8.preencode(state, b)
    else buffer.preencode(state, b)
  },
  encode(state, b) {
    if (typeof b === 'string') utf8.encode(state, b)
    else buffer.encode(state, b)
  }
}

exports.arraybuffer = {
  preencode(state, b) {
    state.end += b.byteLength
  },
  encode(state, b) {
    const view = new Uint8Array(b)

    state.buffer.set(view, state.start)
    state.start += b.byteLength
  },
  decode(state) {
    const b = new ArrayBuffer(state.end - state.start)
    const view = new Uint8Array(b)

    view.set(state.buffer.subarray(state.start))

    state.start = state.end

    return b
  }
}

function typedarray(TypedArray, swap) {
  const n = TypedArray.BYTES_PER_ELEMENT

  return {
    preencode(state, b) {
      state.end += b.byteLength
    },
    encode(state, b) {
      const view = new Uint8Array(b.buffer, b.byteOffset, b.byteLength)

      if (BE && swap) swap(view)

      state.buffer.set(view, state.start)
      state.start += b.byteLength
    },
    decode(state) {
      let b = state.buffer.subarray(state.start)
      if (b.byteOffset % n !== 0) b = new Uint8Array(b)

      if (BE && swap) swap(b)

      state.start = state.end

      return new TypedArray(b.buffer, b.byteOffset, b.byteLength / n)
    }
  }
}

const uint8array = (exports.uint8array = typedarray(Uint8Array))
exports.uint16array = typedarray(Uint16Array, b4a.swap16)
exports.uint32array = typedarray(Uint32Array, b4a.swap32)

exports.int8array = typedarray(Int8Array)
exports.int16array = typedarray(Int16Array, b4a.swap16)
exports.int32array = typedarray(Int32Array, b4a.swap32)

exports.biguint64array = typedarray(BigUint64Array, b4a.swap64)
exports.bigint64array = typedarray(BigInt64Array, b4a.swap64)

exports.float32array = typedarray(Float32Array, b4a.swap32)
exports.float64array = typedarray(Float64Array, b4a.swap64)

function string(encoding) {
  return {
    preencode(state, s) {
      state.end += b4a.byteLength(s, encoding)
    },
    encode(state, s) {
      state.start += b4a.write(state.buffer, s, state.start, encoding)
    },
    decode(state) {
      const s = b4a.toString(state.buffer, encoding, state.start)
      state.start = state.end
      return s
    }
  }
}

const utf8 = (exports.string = exports.utf8 = string('utf-8'))
exports.ascii = string('ascii')
exports.hex = string('hex')
exports.base64 = string('base64')
exports.ucs2 = exports.utf16le = string('utf16le')

exports.array = function array(enc) {
  return {
    preencode(state, list) {
      for (const value of list) enc.preencode(state, value)
    },
    encode(state, list) {
      for (const value of list) enc.encode(state, value)
    },
    decode(state) {
      const arr = []
      while (state.start < state.end) arr.push(enc.decode(state))
      return arr
    }
  }
}

exports.json = {
  preencode(state, v) {
    utf8.preencode(state, JSON.stringify(v))
  },
  encode(state, v) {
    utf8.encode(state, JSON.stringify(v))
  },
  decode(state) {
    return JSON.parse(utf8.decode(state))
  }
}

exports.ndjson = {
  preencode(state, v) {
    utf8.preencode(state, JSON.stringify(v) + '\n')
  },
  encode(state, v) {
    utf8.encode(state, JSON.stringify(v) + '\n')
  },
  decode(state) {
    return JSON.parse(utf8.decode(state))
  }
}
{
  "name": "hyperdht-address",
  "version": "1.0.1",
  "description": "hyperdht-address",
  "main": "index.js",
  "exports": {
    "./package": "./package.json",
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    }
  },
  "files": [
    "package.json",
    "index.js",
    "index.d.ts",
    "spec"
  ],
  "devDependencies": {
    "b4a": "^1.8.0",
    "brittle": "^3.19.0",
    "lunte": "^1.2.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^2.0.0"
  },
  "scripts": {
    "format": "prettier . --write",
    "test": "prettier . --check && lunte && brittle-bare test/index.js",
    "lint": "lunte"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/holepunchto/hyperdht-address.git"
  },
  "author": "Holepunch Inc",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/hyperdht-address/issues"
  },
  "homepage": "https://github.com/holepunchto/hyperdht-address",
  "dependencies": {
    "compact-encoding": "^2.19.0",
    "hyperschema": "^1.20.1"
  }
}
// This file is autogenerated by the hyperschema compiler
// Schema Version: 2
/* eslint-disable camelcase */
/* eslint-disable quotes */
/* eslint-disable space-before-function-paren */

const { c } = require('hyperschema/runtime')

const VERSION = 2

// eslint-disable-next-line no-unused-vars
let version = VERSION

// @hyperdht/address.nodes
const encoding0_1 = c.array(c.ipv4Address)

// @hyperdht/address
const encoding0 = {
  preencode(state, m) {
    c.fixed32.preencode(state, m.key)
    state.end++ // max flag is 1 so always one byte

    if (version >= 2 && m.nodes) encoding0_1.preencode(state, m.nodes)
  },
  encode(state, m) {
    const flags = version >= 2 && m.nodes ? 1 : 0

    c.fixed32.encode(state, m.key)
    c.uint.encode(state, flags)

    if (version >= 2 && m.nodes) encoding0_1.encode(state, m.nodes)
  },
  decode(state) {
    const r0 = c.fixed32.decode(state)
    const flags = state.start < state.end ? c.uint.decode(state) : 0

    return {
      key: r0,
      nodes: version >= 2 && (flags & 1) !== 0 ? encoding0_1.decode(state) : null
    }
  }
}

function setVersion(v) {
  version = v
}

function encode(name, value, v = VERSION) {
  version = v
  return c.encode(getEncoding(name), value)
}

function decode(name, buffer, v = VERSION) {
  version = v
  return c.decode(getEncoding(name), buffer)
}

function getEnum(name) {
  switch (name) {
    default:
      throw new Error('Enum not found ' + name)
  }
}

function getEncoding(name) {
  switch (name) {
    case '@hyperdht/address':
      return encoding0
    default:
      throw new Error('Encoder not found ' + name)
  }
}

function getStruct(name, v = VERSION) {
  const enc = getEncoding(name)
  return {
    preencode(state, m) {
      version = v
      enc.preencode(state, m)
    },
    encode(state, m) {
      version = v
      enc.encode(state, m)
    },
    decode(state) {
      version = v
      return enc.decode(state)
    }
  }
}

const resolveStruct = getStruct // compat

module.exports = {
  resolveStruct,
  getStruct,
  getEnum,
  getEncoding,
  encode,
  decode,
  setVersion,
  version
}
const DHT = require('dht-rpc')
const sodium = require('sodium-universal')
const c = require('compact-encoding')
const b4a = require('b4a')
const safetyCatch = require('safety-catch')
const m = require('./lib/messages')
const SocketPool = require('./lib/socket-pool')
const Persistent = require('./lib/persistent')
const Router = require('./lib/router')
const Cache = require('xache')
const Server = require('./lib/server')
const connect = require('./lib/connect')
const { FIREWALL, BOOTSTRAP_NODES, KNOWN_NODES, COMMANDS } = require('./lib/constants')
const { hash, createKeyPair } = require('./lib/crypto')
const RawStreamSet = require('./lib/raw-stream-set')
const ConnectionPool = require('./lib/connection-pool')
const { STREAM_NOT_CONNECTED } = require('./lib/errors')

const DEFAULTS = {
  ...DHT.DEFAULTS,
  connectionKeepAlive: 5000,
  randomPunchInterval: 20000
}

class HyperDHT extends DHT {
  constructor(opts = {}) {
    const port = opts.port || 49737
    const bootstrap = opts.bootstrap || BOOTSTRAP_NODES
    const nodes = opts.nodes || KNOWN_NODES

    super({ ...opts, port, bootstrap, nodes, filterNode })

    const { router, relayAddresses, persistent } = defaultCacheOpts(opts)

    this.defaultKeyPair = opts.keyPair || createKeyPair(opts.seed)
    this.listening = new Set()
    this.connectionKeepAlive =
      opts.connectionKeepAlive === false
        ? 0
        : opts.connectionKeepAlive || DEFAULTS.connectionKeepAlive

    // stats is inherited from dht-rpc so fwd the ones from there
    this.stats = {
      punches: { consistent: 0, random: 0, open: 0 },
      relaying: { attempts: 0, successes: 0, aborts: 0 },
      ...this.stats
    }
    this.rawStreams = new RawStreamSet(this)
    this.plugins = new Map()

    this._router = new Router(this, router)
    this._socketPool = new SocketPool(this, opts.host || '0.0.0.0')
    this._persistent = null
    this._validatedLocalAddresses = new Map()
    this._relayAddressesCache = new Cache(relayAddresses)

    this._deferRandomPunch = !!opts.deferRandomPunch
    this._lastRandomPunch = this._deferRandomPunch ? Date.now() : 0
    this._connectable = true
    this._randomPunchInterval = opts.randomPunchInterval || DEFAULTS.randomPunchInterval // min 20s between random punches...
    this._randomPunches = 0
    this._randomPunchLimit = 1 // set to one for extra safety for now

    this.once('persistent', () => {
      this._persistent = new Persistent(this, persistent)
      for (const plugin of this.plugins.values()) plugin.onpersistent()
    })

    this.on('network-change', () => {
      for (const server of this.listening) server.refresh()
    })

    this.on('network-update', () => {
      if (!this.online) return
      for (const server of this.listening) server.notifyOnline()
    })
  }

  static DEFAULTS = DEFAULTS

  connect(remotePublicKey, opts) {
    return connect(this, remotePublicKey, opts)
  }

  createServer(opts, onconnection) {
    if (typeof opts === 'function') return this.createServer({}, opts)
    if (opts && opts.onconnection) onconnection = opts.onconnection
    const s = new Server(this, opts)
    if (onconnection) s.on('connection', onconnection)
    return s
  }

  pool() {
    return new ConnectionPool(this)
  }

  async resume({ log = noop } = {}) {
    if (this._deferRandomPunch) this._lastRandomPunch = Date.now()
    await super.resume({ log })
    const resuming = []
    for (const server of this.listening) resuming.push(server.resume())
    log('Resuming hyperdht servers')
    await Promise.allSettled(resuming)
    log('Done, hyperdht fully resumed')
  }

  async suspend({ log = noop } = {}) {
    this._connectable = false // just so nothing gets connected during suspension
    const suspending = []
    for (const server of this.listening) suspending.push(server.suspend())
    log('Suspending all hyperdht servers')
    await Promise.allSettled(suspending)
    log('Done, clearing all raw streams')
    await this.rawStreams.clear()
    log('Done, suspending dht-rpc')
    await super.suspend({ log })
    log('Done, clearing raw streams again')
    await this.rawStreams.clear()
    log('Done, hyperdht fully suspended')
    this._connectable = true
  }

  async destroy({ force = false } = {}) {
    if (!force) {
      const closing = []
      for (const server of this.listening) closing.push(server.close())
      await Promise.allSettled(closing)
    }
    this._router.destroy()
    if (this._persistent) this._persistent.destroy()
    for (const plugin of this.plugins.values()) plugin.destroy()
    await this.rawStreams.clear()
    await this._socketPool.destroy()
    await super.destroy()
  }

  async validateLocalAddresses(addresses) {
    const list = []
    const socks = []
    const waiting = []

    for (const addr of addresses) {
      const { host } = addr

      if (this._validatedLocalAddresses.has(host)) {
        if (await this._validatedLocalAddresses.get(host)) {
          list.push(addr)
        }
        continue
      }

      const sock = this.udx.createSocket()
      try {
        sock.bind(0, host)
      } catch {
        this._validatedLocalAddresses.set(host, Promise.resolve(false))
        continue
      }

      socks.push(sock)

      // semi terrible heuristic until we proper fix local connections by racing them to the remote...
      const promise = new Promise((resolve) => {
        sock.on('message', () => resolve(true))
        setTimeout(() => resolve(false), 500)
        sock.trySend(b4a.alloc(1), sock.address().port, addr.host)
      })

      this._validatedLocalAddresses.set(host, promise)
      waiting.push(addr)
    }

    for (const addr of waiting) {
      const { host } = addr
      if (this._validatedLocalAddresses.has(host)) {
        if (await this._validatedLocalAddresses.get(host)) {
          list.push(addr)
        }
        continue
      }
    }

    for (const sock of socks) await sock.close()

    return list
  }

  findPeer(publicKey, opts = {}) {
    const target = opts.hash === false ? publicKey : hash(publicKey)
    opts = { ...opts, map: mapFindPeer }
    return this.query({ target, command: COMMANDS.FIND_PEER, value: null }, opts)
  }

  lookup(target, opts = {}) {
    opts = { ...opts, map: mapLookup }
    return this.query({ target, command: COMMANDS.LOOKUP, value: null }, opts)
  }

  lookupAndUnannounce(target, keyPair, opts = {}) {
    const unannounces = []
    const dht = this
    const userCommit = opts.commit || noop
    const signUnannounce = opts.signUnannounce || Persistent.signUnannounce

    if (this._persistent !== null) {
      // unlink self
      this._persistent.unannounce(target, keyPair.publicKey)
    }

    opts = { ...opts, map, commit }
    return this.query({ target, command: COMMANDS.LOOKUP, value: null }, opts)

    async function commit(reply, dht, query) {
      await Promise.all(unannounces) // can never fail, caught below
      return userCommit(reply, dht, query)
    }

    function map(reply) {
      const data = mapLookup(reply)

      if (!data || !data.token) return data

      let found = data.peers.length >= 20
      for (let i = 0; !found && i < data.peers.length; i++) {
        found = b4a.equals(data.peers[i].publicKey, keyPair.publicKey)
      }

      if (!found) return data

      if (!data.from.id) return data

      unannounces.push(
        dht
          ._requestUnannounce(keyPair, dht, target, data.token, data.from, signUnannounce)
          .catch(safetyCatch)
      )

      return data
    }
  }

  unannounce(target, keyPair, opts = {}) {
    return this.lookupAndUnannounce(target, keyPair, opts).finished()
  }

  announce(target, keyPair, relayAddresses, opts = {}) {
    const signAnnounce = opts.signAnnounce || Persistent.signAnnounce
    const bump = opts.bump || 0

    opts = { ...opts, commit }

    return opts.clear ? this.lookupAndUnannounce(target, keyPair, opts) : this.lookup(target, opts)

    function commit(reply, dht) {
      return dht._requestAnnounce(
        keyPair,
        dht,
        target,
        reply.token,
        reply.from,
        relayAddresses,
        signAnnounce,
        bump
      )
    }
  }

  async immutableGet(target, opts = {}) {
    opts = { ...opts, map: mapImmutable }

    const query = this.query({ target, command: COMMANDS.IMMUTABLE_GET, value: null }, opts)
    const check = b4a.allocUnsafe(32)

    for await (const node of query) {
      const { value } = node
      sodium.crypto_generichash(check, value)
      if (b4a.equals(check, target)) return node
    }

    return null
  }

  async immutablePut(value, opts = {}) {
    const target = b4a.allocUnsafe(32)
    sodium.crypto_generichash(target, value)

    opts = {
      ...opts,
      map: mapImmutable,
      commit(reply, dht) {
        return dht.request(
          { token: reply.token, target, command: COMMANDS.IMMUTABLE_PUT, value },
          reply.from
        )
      }
    }

    const query = this.query({ target, command: COMMANDS.IMMUTABLE_GET, value: null }, opts)
    await query.finished()

    return { hash: target, closestNodes: query.closestNodes }
  }

  async mutableGet(publicKey, opts = {}) {
    let refresh = opts.refresh || null
    let signed = null
    let result = null

    opts = { ...opts, map: mapMutable, commit: refresh ? commit : null }

    const target = b4a.allocUnsafe(32)
    sodium.crypto_generichash(target, publicKey)

    const userSeq = opts.seq || 0
    const query = this.query(
      { target, command: COMMANDS.MUTABLE_GET, value: c.encode(c.uint, userSeq) },
      opts
    )
    const latest = opts.latest !== false

    for await (const node of query) {
      if (result && node.seq <= result.seq) continue
      if (
        node.seq < userSeq ||
        !Persistent.verifyMutable(node.signature, node.seq, node.value, publicKey)
      )
        continue
      if (!latest) return node
      if (!result || node.seq > result.seq) result = node
    }

    return result

    function commit(reply, dht) {
      if (!signed && result && refresh) {
        if (refresh(result)) {
          signed = c.encode(m.mutablePutRequest, {
            publicKey,
            seq: result.seq,
            value: result.value,
            signature: result.signature
          })
        } else {
          refresh = null
        }
      }

      return signed
        ? dht.request(
            { token: reply.token, target, command: COMMANDS.MUTABLE_PUT, value: signed },
            reply.from
          )
        : Promise.resolve(null)
    }
  }

  async mutablePut(keyPair, value, opts = {}) {
    const signMutable = opts.signMutable || Persistent.signMutable

    const target = b4a.allocUnsafe(32)
    sodium.crypto_generichash(target, keyPair.publicKey)

    const seq = opts.seq || 0
    const signature = await signMutable(seq, value, keyPair)

    const signed = c.encode(m.mutablePutRequest, {
      publicKey: keyPair.publicKey,
      seq,
      value,
      signature
    })

    opts = {
      ...opts,
      map: mapMutable,
      commit(reply, dht) {
        return dht.request(
          { token: reply.token, target, command: COMMANDS.MUTABLE_PUT, value: signed },
          reply.from
        )
      }
    }

    // use seq = 0, for the query part here, as we don't care about the actual values
    const query = this.query(
      { target, command: COMMANDS.MUTABLE_GET, value: c.encode(c.uint, 0) },
      opts
    )
    await query.finished()

    return { publicKey: keyPair.publicKey, closestNodes: query.closestNodes, seq, signature }
  }

  onrequest(req) {
    switch (req.command) {
      case COMMANDS.PEER_HANDSHAKE: {
        this._router.onpeerhandshake(req)
        return true
      }
      case COMMANDS.PEER_HOLEPUNCH: {
        this._router.onpeerholepunch(req)
        return true
      }
    }

    if (this._persistent === null || this.id === null) return false

    switch (req.command) {
      case COMMANDS.FIND_PEER: {
        this._persistent.onfindpeer(req)
        return true
      }
      case COMMANDS.LOOKUP: {
        this._persistent.onlookup(req)
        return true
      }
      case COMMANDS.ANNOUNCE: {
        this._persistent.onannounce(req)
        return true
      }
      case COMMANDS.UNANNOUNCE: {
        this._persistent.onunannounce(req)
        return true
      }
      case COMMANDS.MUTABLE_PUT: {
        this._persistent.onmutableput(req)
        return true
      }
      case COMMANDS.MUTABLE_GET: {
        this._persistent.onmutableget(req)
        return true
      }
      case COMMANDS.IMMUTABLE_PUT: {
        this._persistent.onimmutableput(req)
        return true
      }
      case COMMANDS.IMMUTABLE_GET: {
        this._persistent.onimmutableget(req)
        return true
      }
      case COMMANDS.PLUGIN: {
        this._persistent.onplugin(req)
        return true
      }
    }

    return false
  }

  static keyPair(seed) {
    return createKeyPair(seed)
  }

  static hash(data) {
    return hash(data)
  }

  static connectRawStream(encryptedStream, rawStream, remoteId) {
    const stream = encryptedStream.rawStream

    if (!stream.connected) throw STREAM_NOT_CONNECTED()

    rawStream.connect(stream.socket, remoteId, stream.remotePort, stream.remoteHost)
  }

  createRawStream(opts) {
    return this.rawStreams.add(opts)
  }

  async _requestAnnounce(keyPair, dht, target, token, from, relayAddresses, sign, bump) {
    const ann = {
      peer: {
        publicKey: keyPair.publicKey,
        relayAddresses: relayAddresses || []
      },
      refresh: null,
      signature: null,
      bump
    }

    ann.signature = await sign(target, token, from.id, ann, keyPair)

    const value = c.encode(m.announce, ann)

    return dht.request(
      {
        token,
        target,
        command: COMMANDS.ANNOUNCE,
        value
      },
      from
    )
  }

  async _requestUnannounce(keyPair, dht, target, token, from, sign) {
    const unann = {
      peer: {
        publicKey: keyPair.publicKey,
        relayAddresses: []
      },
      signature: null
    }

    unann.signature = await sign(target, token, from.id, unann, keyPair)

    const value = c.encode(m.announce, unann)

    return dht.request(
      {
        token,
        target,
        command: COMMANDS.UNANNOUNCE,
        value
      },
      from
    )
  }

  register(name, plugin) {
    this.plugins.set(name, plugin)
    plugin.onregister(this)
  }
}

HyperDHT.BOOTSTRAP = BOOTSTRAP_NODES
HyperDHT.FIREWALL = FIREWALL

module.exports = HyperDHT

function mapLookup(node) {
  if (!node.value) return null

  try {
    const l = c.decode(m.lookupRawReply, node.value)

    return {
      token: node.token,
      from: node.from,
      to: node.to,
      peers: l.peers,
      bump: l.bump
    }
  } catch {
    return null
  }
}

function mapFindPeer(node) {
  if (!node.value) return null

  try {
    return {
      token: node.token,
      from: node.from,
      to: node.to,
      peer: c.decode(m.peer, node.value)
    }
  } catch {
    return null
  }
}

function mapImmutable(node) {
  if (!node.value) return null

  return {
    token: node.token,
    from: node.from,
    to: node.to,
    value: node.value
  }
}

function mapMutable(node) {
  if (!node.value) return null

  try {
    const { seq, value, signature } = c.decode(m.mutableGetResponse, node.value)

    return {
      token: node.token,
      from: node.from,
      to: node.to,
      seq,
      value,
      signature
    }
  } catch {
    return null
  }
}

function noop() {}

function filterNode(node) {
  // always skip these testnet nodes that got mixed in by accident, until they get updated
  return (
    !(node.port === 49738 && (node.host === '134.209.28.98' || node.host === '167.99.142.185')) &&
    !(node.port === 9400 && node.host === '35.233.47.252') &&
    !(node.host === '150.136.142.116')
  )
}

const defaultMaxSize = 65536
const defaultMaxAge = 20 * 60 * 1000 // 20 minutes

function defaultCacheOpts(opts) {
  const maxSize = opts.maxSize || defaultMaxSize
  const maxAge = opts.maxAge || defaultMaxAge

  return {
    router: {
      forwards: { maxSize, maxAge }
    },
    relayAddresses: { maxSize: Math.min(maxSize, 512), maxAge: 0 },
    persistent: {
      records: { maxSize, maxAge },
      refreshes: { maxSize, maxAge },
      mutables: {
        maxSize: (maxSize / 2) | 0,
        maxAge: opts.maxAge || 48 * 60 * 60 * 1000 // 48 hours
      },
      immutables: {
        maxSize: (maxSize / 2) | 0,
        maxAge: opts.maxAge || 48 * 60 * 60 * 1000 // 48 hours
      },
      bumps: { maxSize, maxAge }
    }
  }
}
const safetyCatch = require('safety-catch')
const c = require('compact-encoding')
const Signal = require('signal-promise')
const { encodeUnslab } = require('./encode')
const Sleeper = require('./sleeper')
const m = require('./messages')
const Persistent = require('./persistent')
const { COMMANDS } = require('./constants')

const MIN_ACTIVE = 3

module.exports = class Announcer {
  constructor(dht, keyPair, target, opts = {}) {
    this.dht = dht
    this.keyPair = keyPair
    this.target = target
    this.relays = []
    this.relayAddresses = []
    this.stopped = false
    this.suspended = false
    this.record = encodeUnslab(m.peer, { publicKey: keyPair.publicKey, relayAddresses: [] })
    this.online = new Signal()

    this._refreshing = false
    this._closestNodes = null
    this._active = null
    this._sleeper = new Sleeper()
    this._resumed = new Signal()
    this._signAnnounce = opts.signAnnounce || Persistent.signAnnounce
    this._signUnannounce = opts.signUnannounce || Persistent.signUnannounce
    this._updating = null
    this._activeQuery = null
    this._unannouncing = null

    this._serverRelays = [new Map(), new Map(), new Map()]
  }

  isRelay(addr) {
    const id = addr.host + ':' + addr.port
    const [a, b, c] = this._serverRelays
    return a.has(id) || b.has(id) || c.has(id)
  }

  async suspend({ log = noop } = {}) {
    if (this.suspended) return
    this.suspended = true

    log('Suspending announcer')

    // Suspend has its own sleep logic
    // so we don't want to hang on this one
    this.online.notify()

    if (this._activeQuery) this._activeQuery.destroy()

    this._sleeper.resume()
    if (this._updating) await this._updating
    log('Suspending announcer (post update)')

    if (this.suspended === false || this.stopped) return

    log('Suspending announcer (pre unannounce)')
    await this._unannounceCurrent()
    log('Suspending announcer (post unannounce)')
  }

  resume() {
    if (!this.suspended) return
    this.suspended = false

    this.refresh()
    this._sleeper.resume()
    this._resumed.notify()
  }

  refresh() {
    if (this.stopped) return
    this._refreshing = true
  }

  async start() {
    if (this.stopped) return
    this._active = this._runUpdate()
    await this._active
    if (this.stopped) return
    this._active = this._background()
  }

  async stop() {
    this.stopped = true
    this.online.notify() // Break out of the _background loop if we're offline
    this._sleeper.resume()
    this._resumed.notify()
    await this._active
    await this._unannounceCurrent()
  }

  async _unannounceCurrent() {
    while (this._unannouncing !== null) await this._unannouncing
    const un = (this._unannouncing = this._unannounceAll(this._serverRelays[2].values()))
    await this._unannouncing
    if (un === this._unannouncing) this._unannouncing = null
  }

  async _background() {
    while (!this.dht.destroyed && !this.stopped) {
      try {
        this._refreshing = false

        // ~5min +-
        for (let i = 0; i < 100 && !this.stopped && !this._refreshing && !this.suspended; i++) {
          const pings = []

          for (const node of this._serverRelays[2].values()) {
            pings.push(this.dht.ping(node))
          }

          const active = await resolved(pings)
          if (active < Math.min(pings.length, MIN_ACTIVE)) {
            this.refresh() // we lost too many relay nodes, retry all
          }

          if (this.stopped) return

          if (!this.suspended && !this._refreshing) await this._sleeper.pause(3000)
        }

        while (!this.stopped && this.suspended) await this._resumed.wait()

        if (!this.stopped) await this._runUpdate()

        while (!this.dht.online && !this.stopped && !this.suspended) {
          // Being offline can make _background repeat very quickly
          // So wait until we're back online
          await this.online.wait()
        }
      } catch (err) {
        safetyCatch(err)
      }
    }
  }

  async _runUpdate() {
    this._updating = this._update()
    await this._updating
    this._updating = null
  }

  async _update() {
    while (this._unannouncing) await this._unannouncing

    this._cycle()

    const q = (this._activeQuery = this.dht.findPeer(this.target, {
      hash: false,
      nodes: this._closestNodes
    }))

    try {
      await q.finished()
    } catch {
      // ignore failures...
    }

    this._activeQuery = null

    if (this.stopped || this.suspended) return

    const ann = []
    const replies = pickBest(q.closestReplies)

    const relays = []
    const relayAddresses = []

    if (!this.dht.firewalled) {
      const addr = this.dht.remoteAddress()
      if (addr) relayAddresses.push(addr)
    }

    for (const msg of replies) {
      ann.push(this._commit(msg, relays, relayAddresses))
    }

    await Promise.allSettled(ann)
    if (this.stopped || this.suspended) return

    this._closestNodes = q.closestNodes
    this.relays = relays
    this.relayAddresses = relayAddresses

    const removed = []
    for (const [key, value] of this._serverRelays[1]) {
      if (!this._serverRelays[2].has(key)) removed.push(value)
    }

    await this._unannounceAll(removed)
  }

  _unannounceAll(relays) {
    const unann = []
    for (const r of relays) unann.push(this._unannounce(r))
    return Promise.allSettled(unann)
  }

  async _unannounce(to) {
    const unann = {
      peer: {
        publicKey: this.keyPair.publicKey,
        relayAddresses: []
      },
      refresh: null,
      signature: null
    }

    const { from, token, value } = await this.dht.request(
      {
        token: null,
        command: COMMANDS.FIND_PEER,
        target: this.target,
        value: null
      },
      to
    )

    if (!token || !from.id || !value) return

    unann.signature = await this._signUnannounce(this.target, token, from.id, unann, this.keyPair)

    await this.dht.request(
      {
        token,
        command: COMMANDS.UNANNOUNCE,
        target: this.target,
        value: c.encode(m.announce, unann)
      },
      to
    )
  }

  async _commit(msg, relays, relayAddresses) {
    const ann = {
      peer: {
        publicKey: this.keyPair.publicKey,
        relayAddresses: []
      },
      refresh: null,
      signature: null
    }

    ann.signature = await this._signAnnounce(this.target, msg.token, msg.from.id, ann, this.keyPair)

    const res = await this.dht.request(
      {
        token: msg.token,
        command: COMMANDS.ANNOUNCE,
        target: this.target,
        value: c.encode(m.announce, ann)
      },
      msg.from
    )

    if (res.error !== 0) return

    if (relayAddresses.length < 3) relayAddresses.push({ host: msg.from.host, port: msg.from.port })
    relays.push({ relayAddress: msg.from, peerAddress: msg.to })

    this._serverRelays[2].set(msg.from.host + ':' + msg.from.port, msg.from)
  }

  _cycle() {
    const tmp = this._serverRelays[0]
    this._serverRelays[0] = this._serverRelays[1]
    this._serverRelays[1] = this._serverRelays[2]
    this._serverRelays[2] = tmp
    tmp.clear()
  }
}

function resolved(ps) {
  let replied = 0
  let ticks = ps.length + 1

  return new Promise((resolve) => {
    for (const p of ps) p.then(push, tick)
    tick()

    function push(v) {
      replied++
      tick()
    }

    function tick() {
      if (--ticks === 0) resolve(replied)
    }
  })
}

function pickBest(replies) {
  // TODO: pick the ones closest to us RTT wise
  return replies.slice(0, 3)
}

function noop() {}
const NoiseSecretStream = require('@hyperswarm/secret-stream')
const b4a = require('b4a')
const relay = require('blind-relay')
const { isReserved, isBogon } = require('bogon')
const safetyCatch = require('safety-catch')
const unslab = require('unslab')
const Semaphore = require('./semaphore')
const NoiseWrap = require('./noise-wrap')
const SecurePayload = require('./secure-payload')
const Holepuncher = require('./holepuncher')
const Sleeper = require('./sleeper')
const { FIREWALL, ERROR } = require('./constants')
const { unslabbedHash } = require('./crypto')
const {
  CANNOT_HOLEPUNCH,
  HANDSHAKE_INVALID,
  HOLEPUNCH_ABORTED,
  HOLEPUNCH_INVALID,
  HOLEPUNCH_PROBE_TIMEOUT,
  HOLEPUNCH_DOUBLE_RANDOMIZED_NATS,
  PEER_CONNECTION_FAILED,
  PEER_NOT_FOUND,
  REMOTE_ABORTED,
  REMOTE_NOT_HOLEPUNCHABLE,
  REMOTE_NOT_HOLEPUNCHING,
  SERVER_ERROR,
  SERVER_INCOMPATIBLE,
  RELAY_ABORTED,
  SUSPENDED
} = require('./errors')
const { decode } = require('hypercore-id-encoding')
const HyperDHTAddress = require('hyperdht-address')

module.exports = function connect(dht, publicKey, opts = {}) {
  const pool = opts.pool || null

  const { key, nodes: providedNodes } = HyperDHTAddress.decode(
    b4a.isBuffer(publicKey) ? publicKey : decode(publicKey)
  )
  publicKey = key

  if (pool && pool.has(publicKey)) return pool.get(publicKey)

  publicKey = unslab(publicKey)

  opts.relayAddresses = opts.relayAddresses || providedNodes || []
  const keyPair = opts.keyPair || dht.defaultKeyPair
  const relayThrough = selectRelay(opts.relayThrough || null)
  const encryptedSocket = (opts.createSecretStream || defaultCreateSecretStream)(true, null, {
    publicKey: keyPair.publicKey,
    remotePublicKey: publicKey,
    autoStart: false,
    keepAlive: dht.connectionKeepAlive
  })

  // in case a socket is made during suspended state, destroy it immediately
  if (dht.suspended || !dht._connectable) {
    encryptedSocket.destroy(SUSPENDED())
    return encryptedSocket
  }

  if (pool) pool._attachStream(encryptedSocket, false)

  const id = b4a.toString(publicKey, 'hex')
  const c = {
    id,
    dht,
    session: dht.session(),
    relayAddresses: opts.relayAddresses,
    remoteRelayAddresses: [],
    pool,
    round: 0,
    target: unslabbedHash(publicKey),
    remotePublicKey: publicKey,
    reusableSocket: !!opts.reusableSocket,
    handshake: (opts.createHandshake || defaultCreateHandshake)(keyPair, publicKey),
    request: null,
    requesting: false,
    lan: opts.localConnection !== false,
    firewall: FIREWALL.UNKNOWN,
    rawStream: dht.createRawStream({ framed: true, firewall }),
    connect: null,
    query: null,
    puncher: null,
    payload: null,
    passiveConnectTimeout: null,
    serverSocket: null,
    serverAddress: null,
    onsocket: null,
    sleeper: new Sleeper(),
    encryptedSocket,

    // Relay state
    relayTimeout: null,
    relayThrough,
    relayToken: relayThrough ? relay.token() : null,
    relaySocket: null,
    relayClient: null,
    relayPaired: false,
    relayKeepAlive: opts.relayKeepAlive || 5000
  }

  // If the raw stream receives an error signal pre connect (ie from the firewall hook), make sure
  // to forward that to the encrypted socket for proper teardown
  c.rawStream.on('error', autoDestroy)
  c.rawStream.once('connect', () => {
    c.rawStream.removeListener('error', autoDestroy)
  })

  encryptedSocket.on('close', function () {
    if (c.passiveConnectTimeout) clearPassiveConnectTimeout(c)
    if (c.query) c.query.destroy()
    if (c.puncher) c.puncher.destroy()
    if (c.rawStream) c.rawStream.destroy()
    c.session.destroy()
    c.sleeper.resume()
  })

  // Safe to run in the background - never throws
  if (dht.suspended) encryptedSocket.destroy(SUSPENDED())
  else connectAndHolepunch(c, opts)

  return encryptedSocket

  function autoDestroy(err) {
    maybeDestroyEncryptedSocket(c, err)
  }

  function firewall(socket, port, host) {
    // Check if the traffic originated from the socket on which we're expecting relay traffic. If so,
    // we haven't hole punched yet and the other side is just sending us traffic through the relay.
    if (c.relaySocket && isRelay(c.relaySocket, socket, port, host)) {
      return false
    }

    if (c.onsocket) {
      c.onsocket(socket, port, host)
    } else {
      c.serverSocket = socket
      c.serverAddress = { port, host }
    }
    return false
  }
}

function isDone(c) {
  // we are destroying or the puncher is connected - done
  if (c.encryptedSocket.destroying || !!(c.puncher && c.puncher.connected)) {
    return true
  }
  // not destroying, but no raw stream - def not done
  if (c.encryptedSocket.rawStream === null) {
    return false
  }
  // we are relayed, but the puncher is not done yet
  if (c.relaySocket && !!(c.puncher && !c.puncher.connected && !c.puncher.destroyed)) {
    return false
  }
  // we are done
  return true
}

async function retryRoute(c, route) {
  const ref = c.dht._socketPool.lookup(route.socket)

  if (!ref) {
    if (route.socket === c.dht.socket) {
      await connectThroughNode(c, route.address, c.dht.socket)
    }
    return
  }

  ref.active()

  try {
    await connectThroughNode(c, route.address, route.socket)
  } catch {
    // if error, just ignore, and continue through the existing strat
  }

  ref.inactive()
}

async function connectAndHolepunch(c, opts) {
  const route = c.reusableSocket ? c.dht._socketPool.routes.get(c.remotePublicKey) : null

  if (route) {
    await retryRoute(c, route)
    if (isDone(c)) return
  }

  await findAndConnect(c, opts)
  if (isDone(c)) return

  if (!c.connect) {
    // TODO: just a quick fix for now, should retry prob
    maybeDestroyEncryptedSocket(c, HANDSHAKE_INVALID())
    return
  }

  await holepunch(c, opts)
}

function getFirstRemoteAddress(addrs, serverAddress) {
  for (const addr of addrs) {
    if (isBogon(addr.host)) continue
    return addr
  }

  return serverAddress
}

async function holepunch(c, opts) {
  let { relayAddress, serverAddress, clientAddress, payload } = c.connect

  const remoteHolepunchable = !!(payload.holepunch && payload.holepunch.relays.length)

  const relayed = diffAddress(serverAddress, relayAddress)

  if (payload.firewall === FIREWALL.OPEN || (relayed && !remoteHolepunchable)) {
    const addr = getFirstRemoteAddress(payload.addresses4, serverAddress)
    if (addr) {
      const socket = c.dht.socket
      c.dht.stats.punches.open++
      c.onsocket(socket, addr.port, addr.host)
      return
    }
    // TODO: check all addresses also obvs
  }

  const onabort = () => {
    c.session.destroy()
    maybeDestroyEncryptedSocket(c, HOLEPUNCH_ABORTED())
  }

  if (c.firewall === FIREWALL.OPEN) {
    c.passiveConnectTimeout = setTimeout(onabort, 10000)
    return
  }

  // TODO: would be better to just try local addrs in the background whilst continuing with other strategies...
  if (c.lan && relayed && clientAddress.host === serverAddress.host) {
    const serverAddresses = payload.addresses4.filter(onlyNonReserved)

    if (serverAddresses.length > 0) {
      const myAddresses = Holepuncher.localAddresses(c.dht.io.serverSocket)
      const addr = Holepuncher.matchAddress(myAddresses, serverAddresses) || serverAddresses[0]

      const socket = c.dht.io.serverSocket
      try {
        await c.dht.ping(addr)
      } catch {
        maybeDestroyEncryptedSocket(c, HOLEPUNCH_ABORTED())
        return
      }
      c.onsocket(socket, addr.port, addr.host)
      return
    }
  }

  if (!remoteHolepunchable) {
    maybeDestroyEncryptedSocket(c, CANNOT_HOLEPUNCH())
    return
  }

  c.puncher = new Holepuncher(c.dht, c.session, true, payload.firewall)

  c.puncher.onconnect = c.onsocket
  c.puncher.onabort = onabort

  const serverRelay = pickServerRelay(payload.holepunch.relays, relayAddress)

  // Begin holepunching!

  let probe
  try {
    probe = await probeRound(c, opts.fastOpen === false ? null : serverAddress, serverRelay, true)
  } catch (err) {
    destroyPuncher(c)
    // TODO: we should retry here with some of the other relays, bail for now
    maybeDestroyEncryptedSocket(c, err)
    return
  }

  if (isDone(c) || !probe) return
  const { token, peerAddress } = probe

  // If the relay the server picked is the same as the relay the client picked,
  // then we can use the peerAddress that round one indicates the server wants to use.
  // This shaves off a roundtrip if the server chose to reroll its socket due to some NAT
  // issue with the first one it picked (ie mobile nat inconsistencies...).
  // If the relays were different, then the server would not have a UDP session open on this address
  // to the client relay, which round2 uses.
  if (
    !diffAddress(serverRelay.relayAddress, relayAddress) &&
    diffAddress(serverAddress, peerAddress)
  ) {
    serverAddress = peerAddress
    await c.puncher.openSession(serverAddress)
    if (isDone(c)) return
  }

  // TODO: still continue here if a local connection might work, but then do not holepunch...
  if (
    opts.holepunch &&
    !opts.holepunch(
      c.puncher.remoteFirewall,
      c.puncher.nat.firewall,
      c.puncher.remoteAddresses,
      c.puncher.nat.addresses
    )
  ) {
    await abort(c, serverRelay, HOLEPUNCH_ABORTED('Client aborted holepunch'))
    return
  }

  try {
    await roundPunch(c, serverAddress, token, relayAddress, serverRelay, false)
  } catch (err) {
    destroyPuncher(c)
    // TODO: retry with another relay?
    maybeDestroyEncryptedSocket(c, err)
  }
}

async function connectThroughNodes(c, addresses, socket) {
  for (const address of addresses) {
    if (isDone(c) || c.connect) return

    c.remoteRelayAddresses.push(address)
    await connectThroughNode(c, address, socket)
  }
}

async function findAndConnect(c, opts) {
  let attempts = 0
  let relayAddresses =
    opts.relayAddresses && opts.relayAddresses.length ? opts.relayAddresses : null

  if (!relayAddresses) {
    const cachedRelayAddresses = c.dht._relayAddressesCache.get(c.id)
    if (cachedRelayAddresses) relayAddresses = cachedRelayAddresses
  }

  if (c.dht._persistent) {
    // check if we know the route ourself...
    const route = c.dht._router.get(c.target)
    if (route && route.relay !== null) {
      relayAddresses = [{ host: route.relay.host, port: route.relay.port }]
    }
  }

  // 2 is how many parallel connect attempts we want to do, we can make this configurable
  const preConnect = relayAddresses !== null && relayAddresses.length > 0
  const sem = new Semaphore(preConnect ? 3 : 2)
  const signal = sem.signal.bind(sem)
  const tries = relayAddresses !== null ? 2 : 1

  if (preConnect) {
    await sem.wait()
    connectThroughNodes(c, relayAddresses, null).then(signal, signal)
  }

  try {
    for (let i = 0; i < tries && !isDone(c) && !c.connect; i++) {
      c.query = c.dht.findPeer(c.target, {
        hash: false,
        session: c.session,
        nodes: relayAddresses,
        retries: 3
      })

      for await (const data of c.query) {
        await sem.wait()
        if (isDone(c)) return

        if (c.connect) {
          sem.signal()
          break
        }

        // Skip node already run via preConnect
        if (preConnect && relayAddresses && isRelayAddress(relayAddresses, data)) {
          sem.signal()
          continue
        }

        c.remoteRelayAddresses.push(data.from)
        attempts++
        connectThroughNode(c, data.from, null).then(signal, signal)
      }

      relayAddresses = null

      if (attempts > 0) await sem.flush()
    }

    c.query = null
    if (isDone(c)) return

    // flush the semaphore
    await sem.flush()
    if (isDone(c)) return
  } catch (err) {
    c.query = null
    maybeDestroyEncryptedSocket(c, err)
    return
  }

  if (!c.connect) {
    maybeDestroyEncryptedSocket(c, attempts ? PEER_CONNECTION_FAILED() : PEER_NOT_FOUND())
  }
}

async function connectThroughNode(c, address, socket) {
  if (!c.requesting) {
    // If we have a stable server address, send it over now
    const addr = c.dht.remoteAddress()
    const localAddrs = c.lan ? Holepuncher.localAddresses(c.dht.io.serverSocket) : null
    const addresses4 = []

    if (addr) addresses4.push(addr)
    if (localAddrs) addresses4.push(...localAddrs)

    c.firewall = addr ? FIREWALL.OPEN : FIREWALL.UNKNOWN
    c.requesting = true
    c.request = await c.handshake.send({
      error: ERROR.NONE,
      firewall: c.firewall,
      holepunch: null,
      addresses4,
      addresses6: [],
      udx: {
        reusableSocket: c.reusableSocket,
        id: c.rawStream.id,
        seq: 0
      },
      secretStream: {},
      relayThrough: c.relayThrough ? { publicKey: c.relayThrough, token: c.relayToken } : null
    })
    if (isDone(c)) return
  }

  const { serverAddress, clientAddress, relayed, noise } = await c.dht._router.peerHandshake(
    c.target,
    { noise: c.request, socket, session: c.session },
    address
  )
  if (isDone(c) || c.connect) return

  const payload = await c.handshake.recv(noise)
  if (isDone(c) || !payload) return

  if (payload.version !== 1) {
    maybeDestroyEncryptedSocket(c, SERVER_INCOMPATIBLE())
    return
  }
  if (payload.error !== ERROR.NONE) {
    maybeDestroyEncryptedSocket(c, SERVER_ERROR())
    return
  }
  if (!payload.udx) {
    maybeDestroyEncryptedSocket(c, SERVER_ERROR('Server did not send UDX data'))
    return
  }

  const hs = c.handshake.final()

  c.handshake = null
  c.request = null
  c.requesting = false
  c.connect = {
    relayed,
    relayAddress: address,
    clientAddress,
    serverAddress,
    payload
  }

  c.payload = new SecurePayload(hs.holepunchSecret)

  c.onsocket = function (socket, port, host) {
    if (c.rawStream === null) return // Already hole punched

    if (c.rawStream.connected) {
      const remoteChanging = c.rawStream.changeRemote(socket, c.connect.payload.udx.id, port, host)

      if (remoteChanging) remoteChanging.catch(safetyCatch)
    } else {
      // cache the relay addrs for a future reconnect, we prefer the remote one so they
      // can give us the correct ones from their pov
      if (payload.relayAddresses && payload.relayAddresses.length) {
        c.dht._relayAddressesCache.set(c.id, payload.relayAddresses)
      } else if (c.remoteRelayAddresses.length) {
        c.dht._relayAddressesCache.set(c.id, c.remoteRelayAddresses)
      }

      c.rawStream.connect(socket, c.connect.payload.udx.id, port, host)
      c.encryptedSocket.start(c.rawStream, { handshake: hs })
    }

    if (c.reusableSocket && payload.udx.reusableSocket) {
      c.dht._socketPool.routes.add(c.remotePublicKey, c.rawStream)
    }

    if (c.puncher) {
      c.puncher.onabort = noop
      c.puncher.destroy()
    }

    if (c.passiveConnectTimeout) {
      clearPassiveConnectTimeout(c)
    }

    c.rawStream = null
  }

  if (payload.relayThrough || c.relayThrough) {
    relayConnection(c, c.relayThrough, payload, hs)
  }

  if (c.serverSocket) {
    c.onsocket(c.serverSocket, c.serverAddress.port, c.serverAddress.host)
    return
  }

  if (!relayed) {
    c.onsocket(socket || c.dht.socket, address.port, address.host)
  }

  c.session.destroy()
}

async function updateHolepunch(c, peerAddress, relayAddr, payload) {
  const holepunch = await c.dht._router.peerHolepunch(
    c.target,
    {
      id: c.connect.payload.holepunch.id,
      payload: c.payload.encrypt(payload),
      peerAddress,
      socket: c.puncher.socket,
      session: c.session
    },
    relayAddr
  )

  if (isDone(c)) return null

  const remotePayload = c.payload.decrypt(holepunch.payload)
  if (!remotePayload) {
    throw HOLEPUNCH_INVALID()
  }

  const { error, firewall, punching, addresses, remoteToken } = remotePayload

  if (error === ERROR.TRY_LATER && c.relayToken && payload.punching) {
    return {
      tryLater: true,
      ...holepunch,
      payload: remotePayload
    }
  }

  if (error !== ERROR.NONE) {
    throw REMOTE_ABORTED('Remote aborted with error code ' + error)
  }

  const echoed = !!(remoteToken && payload.token && b4a.equals(remoteToken, payload.token))

  c.puncher.updateRemote({
    punching,
    firewall,
    addresses,
    verified: echoed ? peerAddress.host : null
  })

  return {
    tryLater: false,
    ...holepunch,
    payload: remotePayload
  }
}

async function probeRound(c, serverAddress, serverRelay, retry) {
  // Open a quick low ttl session against what we think is the server
  if (serverAddress) await c.puncher.openSession(serverAddress)

  if (isDone(c)) return null

  const reply = await updateHolepunch(c, serverRelay.peerAddress, serverRelay.relayAddress, {
    error: ERROR.NONE,
    firewall: c.puncher.nat.firewall,
    round: c.round++,
    connected: false,
    punching: false,
    addresses: c.puncher.nat.addresses,
    remoteAddress: serverAddress,
    token: null,
    remoteToken: null
  })

  if (isDone(c) || !reply) return null

  const { peerAddress } = reply
  const { address, token } = reply.payload

  c.puncher.nat.add(reply.to, reply.from)

  // Open another quick low ttl session against what the server says their address is,
  // if they haven't said they are random yet
  if (
    c.puncher.remoteFirewall < FIREWALL.RANDOM &&
    address &&
    address.host &&
    address.port &&
    diffAddress(address, serverAddress)
  ) {
    await c.puncher.openSession(address)
    if (isDone(c)) return null
  }

  // If the remote told us they didn't know their nat firewall yet, give them a chance to figure it out
  // They might say this to see if the "fast mode" punch comes through first.
  if (c.puncher.remoteFirewall === FIREWALL.UNKNOWN) {
    await c.sleeper.pause(1000)
    if (isDone(c)) return null
  }

  let stable = await c.puncher.analyze(false)
  if (isDone(c)) return null

  // If the socket seems unstable, try to make it stable by setting the "allowReopen" flag
  // Mostly relevant for mobile networks
  if (!stable) {
    stable = await c.puncher.analyze(true)
    if (isDone(c)) return null
    if (stable) return probeRound(c, serverAddress, serverRelay, false)
  }

  if ((c.puncher.remoteFirewall === FIREWALL.UNKNOWN || !token) && retry) {
    return probeRound(c, serverAddress, serverRelay, false)
  }

  if (
    c.puncher.remoteFirewall === FIREWALL.UNKNOWN ||
    c.puncher.nat.firewall === FIREWALL.UNKNOWN
  ) {
    await abort(c, serverRelay, HOLEPUNCH_PROBE_TIMEOUT())
    return null
  }

  if (c.puncher.remoteFirewall >= FIREWALL.RANDOM && c.puncher.nat.firewall >= FIREWALL.RANDOM) {
    await abort(c, serverRelay, HOLEPUNCH_DOUBLE_RANDOMIZED_NATS())
    return null
  }

  return { token, peerAddress }
}

async function roundPunch(c, serverAddress, remoteToken, clientRelay, serverRelay, delayed) {
  // We are gossiping our final NAT status to the other peer now
  // so make sure we don't update our local view for now as that can make things weird
  c.puncher.nat.freeze()

  const isRandom =
    c.puncher.remoteFirewall >= FIREWALL.RANDOM || c.puncher.nat.firewall >= FIREWALL.RANDOM
  if (isRandom) {
    while (
      c.dht._randomPunches >= c.dht._randomPunchLimit ||
      Date.now() - c.dht._lastRandomPunch < c.dht._randomPunchInterval
    ) {
      // if no relay can help, bail
      if (!c.relayToken) throw HOLEPUNCH_ABORTED()

      if (!delayed) {
        delayed = true
        await updateHolepunch(c, serverAddress, clientRelay, {
          error: ERROR.NONE,
          firewall: c.puncher.nat.firewall,
          round: c.round++,
          connected: false,
          punching: false,
          addresses: c.puncher.nat.addresses,
          remoteAddress: null,
          token: c.payload.token(serverAddress),
          remoteToken
        })
        if (isDone(c)) return
      }

      await tryLater(c)
      if (isDone(c)) return
    }
  }

  // increment now, so we can commit to punching
  if (isRandom) c.dht._randomPunches++

  let reply

  try {
    // if delayed switch to the servers chosen relay - we validated anyway
    reply = await updateHolepunch(
      c,
      delayed ? serverRelay.peerAddress : serverAddress,
      delayed ? serverRelay.relayAddress : clientRelay,
      {
        error: ERROR.NONE,
        firewall: c.puncher.nat.firewall,
        round: c.round++,
        connected: false,
        punching: true,
        addresses: c.puncher.nat.addresses,
        remoteAddress: null,
        token: delayed ? null : c.payload.token(serverAddress),
        remoteToken
      }
    )
  } finally {
    // decrement as punch increments for us
    if (isRandom) c.dht._randomPunches--
  }

  if (isDone(c)) return
  if (!reply) return

  if (reply.tryLater) {
    await tryLater(c)
    if (isDone(c)) return
    return roundPunch(c, serverAddress, remoteToken, clientRelay, serverRelay, true)
  }

  if (!c.puncher.remoteHolepunching) {
    throw REMOTE_NOT_HOLEPUNCHING()
  }

  if (!(await c.puncher.punch())) {
    throw REMOTE_NOT_HOLEPUNCHABLE()
  }
}

async function tryLater(c) {
  if (!c.relayToken) throw HOLEPUNCH_ABORTED()
  await c.sleeper.pause(10000 + Math.round(Math.random() * 10000))
}

function maybeDestroyEncryptedSocket(c, err) {
  if (isDone(c)) return
  if (c.encryptedSocket.rawStream) return
  if (c.relaySocket) return // waiting for the relay
  if (c.puncher && !c.puncher.destroyed) return // waiting for the puncher
  c.session.destroy()
  c.encryptedSocket.destroy(err)
}

async function abort(c, { peerAddress, relayAddress }, err) {
  try {
    await updateHolepunch(c, peerAddress, relayAddress, {
      error: ERROR.ABORTED,
      firewall: FIREWALL.UNKNOWN,
      round: c.round++,
      connected: false,
      punching: false,
      addresses: null,
      remoteAddress: null,
      token: null,
      remoteToken: null
    })
  } catch (err) {
    safetyCatch(err)
  }

  destroyPuncher(c)
  maybeDestroyEncryptedSocket(c, err)
}

function relayConnection(c, relayThrough, payload, hs) {
  let isInitiator
  let publicKey
  let token

  if (payload.relayThrough) {
    isInitiator = false
    publicKey = payload.relayThrough.publicKey
    token = payload.relayThrough.token
  } else {
    isInitiator = true
    publicKey = relayThrough
    token = c.relayToken
  }

  c.relayToken = token
  c.relaySocket = c.dht.connect(publicKey)
  c.relaySocket.setKeepAlive(c.relayKeepAlive)
  c.relayClient = relay.Client.from(c.relaySocket, { id: c.relaySocket.publicKey })
  c.relayTimeout = setTimeout(onabort, 15000, null)

  c.relayClient.pair(isInitiator, token, c.rawStream).on('error', onabort).on('data', ondata)

  function ondata(remoteId) {
    if (c.relayTimeout) clearRelayTimeout(c)
    if (c.rawStream === null) {
      onabort(null)
      return
    }

    c.relayPaired = true

    const { remotePort, remoteHost, socket } = c.relaySocket.rawStream

    c.rawStream
      .on('close', () => c.relaySocket.destroy())
      .connect(socket, remoteId, remotePort, remoteHost)

    c.encryptedSocket.start(c.rawStream, { handshake: hs })
  }

  function onabort(err) {
    if (c.relayTimeout) clearRelayTimeout(c)
    const socket = c.relaySocket
    c.relayToken = null
    c.relaySocket = null
    if (socket) socket.destroy()
    maybeDestroyEncryptedSocket(c, err || RELAY_ABORTED())
  }
}

function clearPassiveConnectTimeout(c) {
  clearTimeout(c.passiveConnectTimeout)
  c.passiveConnectTimeout = null
}

function clearRelayTimeout(c) {
  clearTimeout(c.relayTimeout)
  c.relayTimeout = null
}

function destroyPuncher(c) {
  if (c.puncher) c.puncher.destroy()
  c.session.destroy()
}

function pickServerRelay(relays, clientRelay) {
  for (const r of relays) {
    if (!diffAddress(r.relayAddress, clientRelay)) return r
  }
  return relays[0]
}

function diffAddress(a, b) {
  return a.host !== b.host || a.port !== b.port
}

function defaultCreateHandshake(keyPair, remotePublicKey) {
  return new NoiseWrap(keyPair, remotePublicKey)
}

function defaultCreateSecretStream(isInitiator, rawStream, opts) {
  return new NoiseSecretStream(isInitiator, rawStream, opts)
}

function onlyNonReserved(addr) {
  return !isReserved(addr.host)
}

function isRelay(relaySocket, socket, port, host) {
  const stream = relaySocket.rawStream
  if (!stream) return false
  if (stream.socket !== socket) return false
  return port === stream.remotePort && host === stream.remoteHost
}

function selectRelay(relayThrough) {
  if (typeof relayThrough === 'function') relayThrough = relayThrough()
  if (relayThrough === null) return null
  if (Array.isArray(relayThrough))
    return relayThrough[Math.floor(Math.random() * relayThrough.length)]
  return relayThrough
}

function isRelayAddress(relayAddresses, data) {
  for (const node of relayAddresses) {
    if (node.host === data.from.host && node.port === data.from.port) return true
  }

  return false
}

function noop() {}
const EventEmitter = require('events')
const b4a = require('b4a')
const errors = require('./errors')

module.exports = class ConnectionPool extends EventEmitter {
  constructor(dht) {
    super()

    this._dht = dht
    this._servers = new Map()
    this._connecting = new Map()
    this._connections = new Map()
  }

  _attachServer(server) {
    const keyString = b4a.toString(server.publicKey, 'hex')

    this._servers.set(keyString, server)

    server
      .on('close', () => {
        this._servers.delete(keyString)
      })
      .on('connection', (socket) => {
        this._attachStream(socket, true)
      })
  }

  _attachStream(stream, opened) {
    const existing = this.get(stream.remotePublicKey)

    if (existing) {
      const keepNew =
        stream.isInitiator === existing.isInitiator ||
        b4a.compare(stream.publicKey, stream.remotePublicKey) > 0

      if (keepNew) {
        let closed = false

        const onclose = () => {
          closed = true
        }

        existing
          .on('error', noop)
          .on('close', () => {
            if (closed) return

            stream.off('error', noop).off('close', onclose)

            this._attachStream(stream, opened)
          })
          .destroy(errors.DUPLICATE_CONNECTION())

        stream.on('error', noop).on('close', onclose)
      } else {
        stream.on('error', noop).destroy(errors.DUPLICATE_CONNECTION())
      }

      return
    }

    const session = new ConnectionRef(this, stream)

    const keyString = b4a.toString(stream.remotePublicKey, 'hex')

    if (opened) {
      this._connections.set(keyString, session)

      stream.on('close', () => {
        this._connections.delete(keyString)
      })

      this.emit('connection', stream, session)
    } else {
      this._connecting.set(keyString, session)

      stream
        .on('error', noop)
        .on('close', () => {
          if (opened) this._connections.delete(keyString)
          else this._connecting.delete(keyString)
        })
        .on('open', () => {
          opened = true

          this._connecting.delete(keyString)
          this._connections.set(keyString, session)

          stream.off('error', noop)

          this.emit('connection', stream, session)
        })
    }

    return session
  }

  get connecting() {
    return this._connecting.size
  }

  get connections() {
    return this._connections.values()
  }

  has(publicKey) {
    const keyString = b4a.toString(publicKey, 'hex')

    return this._connections.has(keyString) || this._connecting.has(keyString)
  }

  get(publicKey) {
    const keyString = b4a.toString(publicKey, 'hex')

    const existing = this._connections.get(keyString) || this._connecting.get(keyString)

    return existing?._stream || null
  }
}

class ConnectionRef {
  constructor(pool, stream) {
    this._pool = pool
    this._stream = stream
    this._refs = 0
  }

  active() {
    this._refs++
  }

  inactive() {
    this._refs--
  }

  release() {
    this._stream.destroy()
  }
}

function noop() {}
const crypto = require('hypercore-crypto')

const COMMANDS = (exports.COMMANDS = {
  PEER_HANDSHAKE: 0,
  PEER_HOLEPUNCH: 1,
  FIND_PEER: 2,
  LOOKUP: 3,
  ANNOUNCE: 4,
  UNANNOUNCE: 5,
  MUTABLE_PUT: 6,
  MUTABLE_GET: 7,
  IMMUTABLE_PUT: 8,
  IMMUTABLE_GET: 9,
  PLUGIN: 10
})

exports.BOOTSTRAP_NODES = global.Pear?.config.dht?.bootstrap || [
  '88.99.3.86@node1.hyperdht.org:49737',
  '142.93.90.113@node2.hyperdht.org:49737',
  '138.68.147.8@node3.hyperdht.org:49737'
]

exports.KNOWN_NODES = global.Pear?.config.dht?.nodes || []

exports.FIREWALL = {
  UNKNOWN: 0,
  OPEN: 1,
  CONSISTENT: 2,
  RANDOM: 3
}

exports.ERROR = {
  // noise / connection related
  NONE: 0,
  ABORTED: 1,
  VERSION_MISMATCH: 2,
  TRY_LATER: 3,
  // dht related
  SEQ_REUSED: 16,
  SEQ_TOO_LOW: 17
}

const [
  NS_ANNOUNCE,
  NS_UNANNOUNCE,
  NS_MUTABLE_PUT,
  NS_PEER_HANDSHAKE,
  NS_PEER_HOLEPUNCH,
  NS_PLUGIN
] = crypto.namespace('hyperswarm/dht', [
  COMMANDS.ANNOUNCE,
  COMMANDS.UNANNOUNCE,
  COMMANDS.MUTABLE_PUT,
  COMMANDS.PEER_HANDSHAKE,
  COMMANDS.PEER_HOLEPUNCH,
  COMMANDS.PLUGIN
])

exports.NS = {
  ANNOUNCE: NS_ANNOUNCE,
  UNANNOUNCE: NS_UNANNOUNCE,
  MUTABLE_PUT: NS_MUTABLE_PUT,
  PEER_HANDSHAKE: NS_PEER_HANDSHAKE,
  PEER_HOLEPUNCH: NS_PEER_HOLEPUNCH,
  PLUGIN: NS_PLUGIN
}
const sodium = require('sodium-universal')
const b4a = require('b4a')

function hash(data) {
  const out = b4a.allocUnsafe(32)
  sodium.crypto_generichash(out, data)
  return out
}

function unslabbedHash(data) {
  const out = b4a.allocUnsafeSlow(32)
  sodium.crypto_generichash(out, data)
  return out
}

function createKeyPair(seed) {
  const publicKey = b4a.alloc(32)
  const secretKey = b4a.alloc(64)
  if (seed) sodium.crypto_sign_seed_keypair(publicKey, secretKey, seed)
  else sodium.crypto_sign_keypair(publicKey, secretKey)
  return { publicKey, secretKey }
}

module.exports = {
  hash,
  unslabbedHash,
  createKeyPair
}
const b4a = require('b4a')
const cenc = require('compact-encoding')

function encodeUnslab(enc, m) {
  // Faster than unslab(c.encode(enc, data)) because it avoids the mem copy.
  // Makes sense to put in compact-encoding when we need it in other modules too
  const state = cenc.state()
  enc.preencode(state, m)
  state.buffer = b4a.allocUnsafeSlow(state.end)
  enc.encode(state, m)
  return state.buffer
}

module.exports = {
  encodeUnslab
}
module.exports = class DHTError extends Error {
  constructor(msg, code, fn = DHTError) {
    super(`${code}: ${msg}`)
    this.code = code

    if (Error.captureStackTrace) {
      Error.captureStackTrace(this, fn)
    }
  }

  get name() {
    return 'DHTError'
  }

  static BAD_HANDSHAKE_REPLY(msg = 'Bad handshake reply') {
    return new DHTError(msg, 'BAD_HANDSHAKE_REPLY', DHTError.BAD_HANDSHAKE_REPLY)
  }

  static BAD_HOLEPUNCH_REPLY(msg = 'Bad holepunch reply') {
    return new DHTError(msg, 'BAD_HOLEPUNCH_REPLY', DHTError.BAD_HOLEPUNCH_REPLY)
  }

  static HOLEPUNCH_ABORTED(msg = 'Holepunch aborted') {
    return new DHTError(msg, 'HOLEPUNCH_ABORTED', DHTError.HOLEPUNCH_ABORTED)
  }

  static HOLEPUNCH_INVALID(msg = 'Invalid holepunch payload') {
    return new DHTError(msg, 'HOLEPUNCH_INVALID', DHTError.HOLEPUNCH_INVALID)
  }

  static HOLEPUNCH_PROBE_TIMEOUT(msg = 'Holepunching probe did not finish in time') {
    return new DHTError(msg, 'HOLEPUNCH_PROBE_TIMEOUT', DHTError.HOLEPUNCH_PROBE_TIMEOUT)
  }

  static HOLEPUNCH_DOUBLE_RANDOMIZED_NATS(msg = 'Both remote and local NATs are randomized') {
    return new DHTError(
      msg,
      'HOLEPUNCH_DOUBLE_RANDOMIZED_NATS',
      DHTError.HOLEPUNCH_DOUBLE_RANDOMIZED_NATS
    )
  }

  static CANNOT_HOLEPUNCH(msg = 'Cannot holepunch to remote') {
    return new DHTError(msg, 'CANNOT_HOLEPUNCH', DHTError.CANNOT_HOLEPUNCH)
  }

  static REMOTE_NOT_HOLEPUNCHING(msg = 'Remote is not holepunching') {
    return new DHTError(msg, 'REMOTE_NOT_HOLEPUNCHING', DHTError.REMOTE_NOT_HOLEPUNCHING)
  }

  static REMOTE_NOT_HOLEPUNCHABLE(msg = 'Remote is not holepunchable') {
    return new DHTError(msg, 'REMOTE_NOT_HOLEPUNCHABLE', DHTError.REMOTE_NOT_HOLEPUNCHABLE)
  }

  static REMOTE_ABORTED(msg = 'Remote aborted') {
    return new DHTError(msg, 'REMOTE_ABORTED', DHTError.REMOTE_ABORTED)
  }

  static HANDSHAKE_UNFINISHED(msg = 'Handshake did not finish') {
    return new DHTError(msg, 'HANDSHAKE_UNFINISHED', DHTError.HANDSHAKE_UNFINISHED)
  }

  static HANDSHAKE_INVALID(msg = 'Received invalid handshake') {
    return new DHTError(msg, 'HANDSHAKE_INVALID', DHTError.HANDSHAKE_INVALID)
  }

  static ALREADY_LISTENING(msg = 'Already listening') {
    return new DHTError(msg, 'ALREADY_LISTENING', DHTError.ALREADY_LISTENING)
  }

  static KEYPAIR_ALREADY_USED(msg = 'Keypair already used') {
    return new DHTError(msg, 'KEYPAIR_ALREADY_USED', DHTError.KEYPAIR_ALREADY_USED)
  }

  static NODE_DESTROYED(msg = 'Node destroyed') {
    return new DHTError(msg, 'NODE_DESTROYED', DHTError.NODE_DESTROYED)
  }

  static PEER_CONNECTION_FAILED(msg = 'Could not connect to peer') {
    return new DHTError(msg, 'PEER_CONNECTION_FAILED', DHTError.PEER_CONNECTION_FAILED)
  }

  static PEER_NOT_FOUND(msg = 'Peer not found') {
    return new DHTError(msg, 'PEER_NOT_FOUND', DHTError.PEER_NOT_FOUND)
  }

  static STREAM_NOT_CONNECTED(msg = 'Stream is not connected') {
    return new DHTError(msg, 'STREAM_NOT_CONNECTED', DHTError.STREAM_DISCONNECTED)
  }

  static SERVER_INCOMPATIBLE(msg = 'Server is using an incompatible version') {
    return new DHTError(msg, 'SERVER_INCOMPATIBLE', DHTError.SERVER_INCOMPATIBLE)
  }

  static SERVER_ERROR(msg = 'Server returned an error') {
    return new DHTError(msg, 'SERVER_ERROR', DHTError.SERVER_ERROR)
  }

  static DUPLICATE_CONNECTION(msg = 'Duplicate connection') {
    return new DHTError(msg, 'DUPLICATE_CONNECTION', DHTError.DUPLICATE_CONNECTION)
  }

  static RELAY_ABORTED(msg = 'Relay aborted') {
    return new DHTError(msg, 'RELAY_ABORTED', DHTError.RELAY_ABORTED)
  }

  static SUSPENDED(msg = 'Suspended') {
    return new DHTError(msg, 'SUSPENDED', DHTError.SUSPENDED)
  }
}
const b4a = require('b4a')
const Nat = require('./nat')
const Sleeper = require('./sleeper')
const { FIREWALL } = require('./constants')

const BIRTHDAY_SOCKETS = 256
const HOLEPUNCH = b4a.from([0])
const HOLEPUNCH_TTL = 5
const DEFAULT_TTL = 64
const MAX_REOPENS = 3

module.exports = class Holepuncher {
  constructor(dht, session, isInitiator, remoteFirewall = FIREWALL.UNKNOWN) {
    const holder = dht._socketPool.acquire()

    this.dht = dht
    this.session = session

    this.nat = new Nat(dht, session, holder.socket)
    this.nat.autoSample()

    this.isInitiator = isInitiator

    // events
    this.onconnect = noop
    this.onabort = noop

    this.punching = false
    this.connected = false
    this.destroyed = false
    this.randomized = false

    // track remote state
    this.remoteFirewall = remoteFirewall
    this.remoteAddresses = []
    this.remoteHolepunching = false

    this._sleeper = new Sleeper()
    this._reopening = null
    this._timeout = null
    this._punching = null
    this._allHolders = []
    this._holder = this._addRef(holder)
  }

  get socket() {
    return this._holder.socket
  }

  updateRemote({ punching, firewall, addresses, verified }) {
    const remoteAddresses = []

    if (addresses) {
      for (const addr of addresses) {
        remoteAddresses.push({
          host: addr.host,
          port: addr.port,
          verified: verified === addr.host || this._isVerified(addr.host)
        })
      }
    }

    this.remoteFirewall = firewall
    this.remoteAddresses = remoteAddresses
    this.remoteHolepunching = punching
  }

  _isVerified(host) {
    for (const addr of this.remoteAddresses) {
      if (addr.verified && addr.host === host) {
        return true
      }
    }
    return false
  }

  ping(addr, socket = this._holder.socket) {
    return holepunch(socket, addr, false)
  }

  openSession(addr, socket = this._holder.socket) {
    return holepunch(socket, addr, true)
  }

  async analyze(allowReopen) {
    await this.nat.analyzing
    if (this._unstable()) {
      if (!allowReopen) return false
      if (!this._reopening) this._reopening = this._reopen()
      return this._reopening
    }
    return true
  }

  _unstable() {
    // TODO!!: We need an additional heuristic here... If we were NOT random in the past we should also do this.
    const firewall = this.nat.firewall
    return (
      (this.remoteFirewall >= FIREWALL.RANDOM && firewall >= FIREWALL.RANDOM) ||
      firewall === FIREWALL.UNKNOWN
    )
  }

  _reset() {
    const prev = this._holder

    this._allHolders.pop()
    this._holder = this._addRef(this.dht._socketPool.acquire())

    prev.release()
    this.nat.destroy()

    this.nat = new Nat(this.dht, this.session, this._holder.socket)
    // TODO: maybe make auto sampling configurable somehow?
    this.nat.autoSample()
  }

  _addRef(ref) {
    this._allHolders.push(ref)
    ref.onholepunchmessage = (msg, rinfo) => this._onholepunchmessage(msg, rinfo, ref)
    return ref
  }

  _onholepunchmessage(_, addr, ref) {
    if (!this.isInitiator) {
      // TODO: we don't need this if we had a way to connect a socket to many hosts
      holepunch(ref.socket, addr, false) // never fails
      return
    }

    if (this.connected) return

    this.connected = true
    this.punching = false

    for (const r of this._allHolders) {
      if (r === ref) continue
      r.release()
    }

    this._allHolders[0] = ref
    while (this._allHolders.length > 1) this._allHolders.pop()

    this._decrementRandomized()
    this.onconnect(ref.socket, addr.port, addr.host)
  }

  _done() {
    return this.destroyed || this.connected
  }

  async _reopen() {
    for (let i = 0; this._unstable() && i < MAX_REOPENS && !this._done() && !this.punching; i++) {
      this._reset()
      await this.nat.analyzing
    }

    return coerceFirewall(this.nat.firewall) === FIREWALL.CONSISTENT
  }

  punch() {
    if (!this._punching) this._punching = this._punch()
    return this._punching
  }

  async _punch() {
    if (this._done() || !this.remoteAddresses.length) return false

    this.punching = true

    // Coerce into consistency for now. Obvs we could make this this more efficient if we use that info
    // but that's seldomly used since those will just use tcp most of the time.

    const local = coerceFirewall(this.nat.firewall)
    const remote = coerceFirewall(this.remoteFirewall)

    // Note that most of these async functions are meant to run in the background
    // which is why we don't await them here and why they are not allowed to throw

    let remoteVerifiedAddress = null
    for (const addr of this.remoteAddresses) {
      if (addr.verified) {
        remoteVerifiedAddress = addr
        break
      }
    }

    if (local === FIREWALL.CONSISTENT && remote === FIREWALL.CONSISTENT) {
      this.dht.stats.punches.consistent++
      this._consistentProbe()
      return true
    }

    if (!remoteVerifiedAddress) return false

    if (local === FIREWALL.CONSISTENT && remote >= FIREWALL.RANDOM) {
      this.dht.stats.punches.random++
      this._incrementRandomized()
      this._randomProbes(remoteVerifiedAddress)
      return true
    }

    if (local >= FIREWALL.RANDOM && remote === FIREWALL.CONSISTENT) {
      this.dht.stats.punches.random++
      this._incrementRandomized()
      await this._openBirthdaySockets(remoteVerifiedAddress)
      if (this.punching) this._keepAliveRandomNat(remoteVerifiedAddress)
      return true
    }

    return false
  }

  // Note that this never throws so it is safe to run in the background
  async _consistentProbe() {
    // Here we do the sleep first because the "fast open" mode in the server just fired a ping
    if (!this.isInitiator) await this._sleeper.pause(1000)

    let tries = 0

    while (this.punching && tries++ < 10) {
      for (const addr of this.remoteAddresses) {
        // only try unverified addresses every 4 ticks
        if (!addr.verified && (tries & 3) !== 0) continue
        await holepunch(this._holder.socket, addr, false)
      }
      if (this.punching) await this._sleeper.pause(1000)
    }

    this._autoDestroy()
  }

  // Note that this never throws so it is safe to run in the background
  async _randomProbes(remoteAddr) {
    let tries = 1750 // ~35s

    while (this.punching && tries-- > 0) {
      const addr = { host: remoteAddr.host, port: randomPort() }
      await holepunch(this._holder.socket, addr, false)
      if (this.punching) await this._sleeper.pause(20)
    }

    this._autoDestroy()
  }

  // Note that this never throws so it is safe to run in the background
  async _keepAliveRandomNat(remoteAddr) {
    let i = 0
    let lowTTLRounds = 1

    // TODO: experiment with this here. We just bursted all the messages in
    // openOtherSockets to ensure the sockets are open, so it's potentially
    // a good idea to slow down for a bit.
    await this._sleeper.pause(100)

    let tries = 1750 // ~35s

    while (this.punching && tries-- > 0) {
      if (i === this._allHolders.length) {
        i = 0
        if (lowTTLRounds > 0) lowTTLRounds--
      }

      await holepunch(this._allHolders[i++].socket, remoteAddr, lowTTLRounds > 0)
      if (this.punching) await this._sleeper.pause(20)
    }

    this._autoDestroy()
  }

  async _openBirthdaySockets(remoteAddr) {
    while (this.punching && this._allHolders.length < BIRTHDAY_SOCKETS) {
      const ref = this._addRef(this.dht._socketPool.acquire())
      await holepunch(ref.socket, remoteAddr, HOLEPUNCH_TTL)
    }
  }

  _autoDestroy() {
    if (!this.connected) this.destroy()
  }

  _incrementRandomized() {
    if (!this.randomized) {
      this.randomized = true
      this.dht._randomPunches++
    }
  }

  _decrementRandomized() {
    if (this.randomized) {
      this.dht._lastRandomPunch = Date.now()
      this.randomized = false
      this.dht._randomPunches--
    }
  }

  destroy() {
    if (this.destroyed) return
    this.destroyed = true
    this.punching = false

    for (const ref of this._allHolders) ref.release()
    this._allHolders = []
    this.nat.destroy()

    if (!this.connected) {
      this._decrementRandomized()
      this.onabort()
    }
  }

  static ping(socket, addr) {
    return holepunch(socket, addr, false)
  }

  static localAddresses(socket) {
    return localAddresses(socket)
  }

  static matchAddress(myAddresses, externalAddresses) {
    return matchAddress(myAddresses, externalAddresses)
  }
}

function holepunch(socket, addr, lowTTL) {
  return socket.send(HOLEPUNCH, addr.port, addr.host, lowTTL ? HOLEPUNCH_TTL : DEFAULT_TTL)
}

function randomPort() {
  return (1000 + Math.random() * 64536) | 0
}

function coerceFirewall(fw) {
  return fw === FIREWALL.OPEN ? FIREWALL.CONSISTENT : fw
}

function localAddresses(socket) {
  const addrs = []
  const { host, port } = socket.address()

  if (host === '127.0.0.1') return [{ host, port }]

  for (const n of socket.udx.networkInterfaces()) {
    if (n.family !== 4 || n.internal) continue

    addrs.push({ host: n.host, port })
  }

  if (addrs.length === 0) {
    addrs.push({ host: '127.0.0.1', port })
  }

  return addrs
}

function matchAddress(localAddresses, remoteLocalAddresses) {
  if (remoteLocalAddresses.length === 0) return null

  let best = { segment: 1, addr: null }

  for (const localAddress of localAddresses) {
    // => 192.168.122.238
    const a = localAddress.host.split('.')

    for (const remoteAddress of remoteLocalAddresses) {
      // => 192.168.0.23
      // => 192.168.122.1
      const b = remoteAddress.host.split('.')

      // Matches 192.*.*.*
      if (a[0] === b[0]) {
        if (best.segment === 1) best = { segment: 2, addr: remoteAddress }

        // Matches 192.168.*.*
        if (a[1] === b[1]) {
          if (best.segment === 2) best = { segment: 3, addr: remoteAddress }

          // Matches 192.168.122.*
          if (a[2] === b[2]) return remoteAddress
        }
      }
    }
  }

  return best.addr
}

function noop() {}
const c = require('compact-encoding')

const ipv4 = {
  ...c.ipv4Address,
  decode(state) {
    const ip = c.ipv4Address.decode(state)
    return {
      host: ip.host,
      port: ip.port
    }
  }
}

const ipv4Array = c.array(ipv4)

const ipv6 = {
  ...c.ipv6Address,
  decode(state) {
    const ip = c.ipv6Address.decode(state)
    return {
      host: ip.host,
      port: ip.port
    }
  }
}

const ipv6Array = c.array(ipv6)

exports.handshake = {
  preencode(state, m) {
    state.end += 1 + 1 + (m.peerAddress ? 6 : 0) + (m.relayAddress ? 6 : 0)
    c.buffer.preencode(state, m.noise)
  },
  encode(state, m) {
    const flags = (m.peerAddress ? 1 : 0) | (m.relayAddress ? 2 : 0)

    c.uint.encode(state, flags)
    c.uint.encode(state, m.mode)
    c.buffer.encode(state, m.noise)

    if (m.peerAddress) ipv4.encode(state, m.peerAddress)
    if (m.relayAddress) ipv4.encode(state, m.relayAddress)
  },
  decode(state) {
    const flags = c.uint.decode(state)

    return {
      mode: c.uint.decode(state),
      noise: c.buffer.decode(state),
      peerAddress: flags & 1 ? ipv4.decode(state) : null,
      relayAddress: flags & 2 ? ipv4.decode(state) : null
    }
  }
}

const relayInfo = {
  preencode(state, m) {
    state.end += 12
  },
  encode(state, m) {
    ipv4.encode(state, m.relayAddress)
    ipv4.encode(state, m.peerAddress)
  },
  decode(state) {
    return {
      relayAddress: ipv4.decode(state),
      peerAddress: ipv4.decode(state)
    }
  }
}

const relayInfoArray = c.array(relayInfo)

const holepunchInfo = {
  preencode(state, m) {
    c.uint.preencode(state, m.id)
    relayInfoArray.preencode(state, m.relays)
  },
  encode(state, m) {
    c.uint.encode(state, m.id)
    relayInfoArray.encode(state, m.relays)
  },
  decode(state) {
    return {
      id: c.uint.decode(state),
      relays: relayInfoArray.decode(state)
    }
  }
}

const udxInfo = {
  preencode(state, m) {
    state.end += 2 // version + features
    c.uint.preencode(state, m.id)
    c.uint.preencode(state, m.seq)
  },
  encode(state, m) {
    c.uint.encode(state, 1)
    c.uint.encode(state, m.reusableSocket ? 1 : 0)
    c.uint.encode(state, m.id)
    c.uint.encode(state, m.seq)
  },
  decode(state) {
    const version = c.uint.decode(state)
    const features = c.uint.decode(state)

    return {
      version,
      reusableSocket: (features & 1) !== 0,
      id: c.uint.decode(state),
      seq: c.uint.decode(state)
    }
  }
}

const secretStreamInfo = {
  preencode(state, m) {
    c.uint.preencode(state, 1)
  },
  encode(state, m) {
    c.uint.encode(state, 1)
  },
  decode(state) {
    return {
      version: c.uint.decode(state)
    }
  }
}

const relayThroughInfo = {
  preencode(state, m) {
    c.uint.preencode(state, 1) // version
    c.uint.preencode(state, 0) // flags
    c.fixed32.preencode(state, m.publicKey)
    c.fixed32.preencode(state, m.token)
  },
  encode(state, m) {
    c.uint.encode(state, 1)
    c.uint.encode(state, 0)
    c.fixed32.encode(state, m.publicKey)
    c.fixed32.encode(state, m.token)
  },
  decode(state) {
    const version = c.uint.decode(state)
    c.uint.decode(state)

    return {
      version,
      publicKey: c.fixed32.decode(state),
      token: c.fixed32.decode(state)
    }
  }
}

exports.noisePayload = {
  preencode(state, m) {
    state.end += 4 // version + flags + error + firewall
    if (m.holepunch) holepunchInfo.preencode(state, m.holepunch)
    if (m.addresses4 && m.addresses4.length) ipv4Array.preencode(state, m.addresses4)
    if (m.addresses6 && m.addresses6.length) ipv6Array.preencode(state, m.addresses6)
    if (m.udx) udxInfo.preencode(state, m.udx)
    if (m.secretStream) secretStreamInfo.preencode(state, m.secretStream)
    if (m.relayThrough) relayThroughInfo.preencode(state, m.relayThrough)
    if (m.relayAddresses) ipv4Array.preencode(state, m.relayAddresses)
  },
  encode(state, m) {
    let flags = 0

    if (m.holepunch) flags |= 1
    if (m.addresses4 && m.addresses4.length) flags |= 2
    if (m.addresses6 && m.addresses6.length) flags |= 4
    if (m.udx) flags |= 8
    if (m.secretStream) flags |= 16
    if (m.relayThrough) flags |= 32
    if (m.relayAddresses) flags |= 64

    c.uint.encode(state, 1) // version
    c.uint.encode(state, flags)
    c.uint.encode(state, m.error)
    c.uint.encode(state, m.firewall)

    if (m.holepunch) holepunchInfo.encode(state, m.holepunch)
    if (m.addresses4 && m.addresses4.length) ipv4Array.encode(state, m.addresses4)
    if (m.addresses6 && m.addresses6.length) ipv6Array.encode(state, m.addresses6)
    if (m.udx) udxInfo.encode(state, m.udx)
    if (m.secretStream) secretStreamInfo.encode(state, m.secretStream)
    if (m.relayThrough) relayThroughInfo.encode(state, m.relayThrough)
    if (m.relayAddresses) ipv4Array.encode(state, m.relayAddresses)
  },
  decode(state) {
    const version = c.uint.decode(state)

    if (version !== 1) {
      // Do not attempt to decode but return this back to the user so they can
      // actually handle it
      return {
        version,
        error: 0,
        firewall: 0,
        holepunch: null,
        addresses4: [],
        addresses6: [],
        udx: null,
        secretStream: null,
        relayThrough: null,
        relayAddresses: null
      }
    }

    const flags = c.uint.decode(state)

    return {
      version,
      error: c.uint.decode(state),
      firewall: c.uint.decode(state),
      holepunch: (flags & 1) !== 0 ? holepunchInfo.decode(state) : null,
      addresses4: (flags & 2) !== 0 ? ipv4Array.decode(state) : [],
      addresses6: (flags & 4) !== 0 ? ipv6Array.decode(state) : [],
      udx: (flags & 8) !== 0 ? udxInfo.decode(state) : null,
      secretStream: (flags & 16) !== 0 ? secretStreamInfo.decode(state) : null,
      relayThrough: (flags & 32) !== 0 ? relayThroughInfo.decode(state) : null,
      relayAddresses: (flags & 64) !== 0 ? ipv4Array.decode(state) : null
    }
  }
}

exports.holepunch = {
  preencode(state, m) {
    state.end += 2
    c.uint.preencode(state, m.id)
    c.buffer.preencode(state, m.payload)
    if (m.peerAddress) ipv4.preencode(state, m.peerAddress)
  },
  encode(state, m) {
    const flags = m.peerAddress ? 1 : 0
    c.uint.encode(state, flags)
    c.uint.encode(state, m.mode)
    c.uint.encode(state, m.id)
    c.buffer.encode(state, m.payload)
    if (m.peerAddress) ipv4.encode(state, m.peerAddress)
  },
  decode(state) {
    const flags = c.uint.decode(state)
    return {
      mode: c.uint.decode(state),
      id: c.uint.decode(state),
      payload: c.buffer.decode(state),
      peerAddress: flags & 1 ? ipv4.decode(state) : null
    }
  }
}

exports.holepunchPayload = {
  preencode(state, m) {
    state.end += 4 // flags + error + firewall + round
    if (m.addresses) ipv4Array.preencode(state, m.addresses)
    if (m.remoteAddress) state.end += 6
    if (m.token) state.end += 32
    if (m.remoteToken) state.end += 32
  },
  encode(state, m) {
    const flags =
      (m.connected ? 1 : 0) |
      (m.punching ? 2 : 0) |
      (m.addresses ? 4 : 0) |
      (m.remoteAddress ? 8 : 0) |
      (m.token ? 16 : 0) |
      (m.remoteToken ? 32 : 0)

    c.uint.encode(state, flags)
    c.uint.encode(state, m.error)
    c.uint.encode(state, m.firewall)
    c.uint.encode(state, m.round)

    if (m.addresses) ipv4Array.encode(state, m.addresses)
    if (m.remoteAddress) ipv4.encode(state, m.remoteAddress)
    if (m.token) c.fixed32.encode(state, m.token)
    if (m.remoteToken) c.fixed32.encode(state, m.remoteToken)
  },
  decode(state) {
    const flags = c.uint.decode(state)

    return {
      error: c.uint.decode(state),
      firewall: c.uint.decode(state),
      round: c.uint.decode(state),
      connected: (flags & 1) !== 0,
      punching: (flags & 2) !== 0,
      addresses: (flags & 4) !== 0 ? ipv4Array.decode(state) : null,
      remoteAddress: (flags & 8) !== 0 ? ipv4.decode(state) : null,
      token: (flags & 16) !== 0 ? c.fixed32.decode(state) : null,
      remoteToken: (flags & 32) !== 0 ? c.fixed32.decode(state) : null
    }
  }
}

const peer = (exports.peer = {
  preencode(state, m) {
    state.end += 32
    ipv4Array.preencode(state, m.relayAddresses)
  },
  encode(state, m) {
    c.fixed32.encode(state, m.publicKey)
    ipv4Array.encode(state, m.relayAddresses)
  },
  decode(state) {
    return {
      publicKey: c.fixed32.decode(state),
      relayAddresses: ipv4Array.decode(state)
    }
  }
})

const peers = (exports.peers = c.array(peer))

const rawPeers = c.array(c.raw)

exports.lookupRawReply = {
  preencode(state, m) {
    rawPeers.preencode(state, m.peers)
    c.uint.preencode(state, m.bump)
  },
  encode(state, m) {
    rawPeers.encode(state, m.peers)
    c.uint.encode(state, m.bump)
  },
  decode(state) {
    return {
      peers: peers.decode(state),
      bump: state.start < state.end ? c.uint.decode(state) : 0
    }
  }
}

exports.announce = {
  preencode(state, m) {
    state.end++ // flags
    if (m.peer) peer.preencode(state, m.peer)
    if (m.refresh) state.end += 32
    if (m.signature) state.end += 64
    if (m.bump) c.uint.preencode(state, m.bump)
  },
  encode(state, m) {
    const flags = (m.peer ? 1 : 0) | (m.refresh ? 2 : 0) | (m.signature ? 4 : 0) | (m.bump ? 8 : 0)
    c.uint.encode(state, flags)
    if (m.peer) peer.encode(state, m.peer)
    if (m.refresh) c.fixed32.encode(state, m.refresh)
    if (m.signature) c.fixed64.encode(state, m.signature)
    if (m.bump) c.uint.encode(state, m.bump)
  },
  decode(state) {
    const flags = c.uint.decode(state)

    return {
      peer: (flags & 1) !== 0 ? peer.decode(state) : null,
      refresh: (flags & 2) !== 0 ? c.fixed32.decode(state) : null,
      signature: (flags & 4) !== 0 ? c.fixed64.decode(state) : null,
      bump: (flags & 8) !== 0 ? c.uint.decode(state) : 0
    }
  }
}

exports.mutableSignable = {
  preencode(state, m) {
    c.uint.preencode(state, m.seq)
    c.buffer.preencode(state, m.value)
  },
  encode(state, m) {
    c.uint.encode(state, m.seq)
    c.buffer.encode(state, m.value)
  },
  decode(state) {
    return {
      seq: c.uint.decode(state),
      value: c.buffer.decode(state)
    }
  }
}

exports.mutablePutRequest = {
  preencode(state, m) {
    c.fixed32.preencode(state, m.publicKey)
    c.uint.preencode(state, m.seq)
    c.buffer.preencode(state, m.value)
    c.fixed64.preencode(state, m.signature)
  },
  encode(state, m) {
    c.fixed32.encode(state, m.publicKey)
    c.uint.encode(state, m.seq)
    c.buffer.encode(state, m.value)
    c.fixed64.encode(state, m.signature)
  },
  decode(state) {
    return {
      publicKey: c.fixed32.decode(state),
      seq: c.uint.decode(state),
      value: c.buffer.decode(state),
      signature: c.fixed64.decode(state)
    }
  }
}

exports.mutableGetResponse = {
  preencode(state, m) {
    c.uint.preencode(state, m.seq)
    c.buffer.preencode(state, m.value)
    c.fixed64.preencode(state, m.signature)
  },
  encode(state, m) {
    c.uint.encode(state, m.seq)
    c.buffer.encode(state, m.value)
    c.fixed64.encode(state, m.signature)
  },
  decode(state) {
    return {
      seq: c.uint.decode(state),
      value: c.buffer.decode(state),
      signature: c.fixed64.decode(state)
    }
  }
}

exports.pluginRequest = {
  preencode(state, m) {
    c.string.preencode(state, m.plugin)
    c.uint.preencode(state, m.version)
    c.uint.preencode(state, m.command)
    c.buffer.preencode(state, m.value)
  },
  encode(state, m) {
    c.string.encode(state, m.plugin)
    c.uint.encode(state, m.version)
    c.uint.encode(state, m.command)
    c.buffer.encode(state, m.value)
  },
  decode(state) {
    return {
      plugin: c.string.decode(state),
      version: c.uint.decode(state),
      command: c.uint.decode(state),
      value: c.buffer.decode(state)
    }
  }
}
const { FIREWALL } = require('../lib/constants')

module.exports = class Nat {
  constructor(dht, session, socket) {
    this._samplesHost = []
    this._samplesFull = []
    this._visited = new Map()
    this._resolve = null
    this._minSamples = 4
    this._autoSampling = false

    this.dht = dht
    this.session = session
    this.socket = socket

    this.sampled = 0
    this.firewall = dht.firewalled ? FIREWALL.UNKNOWN : FIREWALL.OPEN
    this.addresses = null

    this.analyzing = new Promise((resolve) => {
      this._resolve = resolve
    })
  }

  autoSample(retry = true) {
    if (this._autoSampling) return
    this._autoSampling = true

    const self = this
    const socket = this.socket
    const maxPings = this._minSamples

    let skip = this.dht.nodes.length >= 8 ? 5 : 0
    let pending = 0

    // TODO: it would be best to pick the nodes to help us based on latency to us
    // That should reduce connect latency in general. We should investigate tracking that later on.

    // TODO 2: try to pick nodes with different IPs as well, as that'll help multi IP cell connections...
    // If we expose this from the nat sampler then the DHT should be able to help us filter out scams as well...

    for (
      let node = this.dht.nodes.latest;
      node && this.sampled + pending < maxPings;
      node = node.prev
    ) {
      if (skip > 0) {
        skip--
        continue
      }

      const ref = node.host + ':' + node.port

      if (this._visited.has(ref)) continue
      this._visited.set(ref, 1)

      pending++
      this.session.ping(node, { socket, retry: false }).then(onpong, onskip)
    }

    pending++
    onskip()

    function onpong(res) {
      self.add(res.to, res.from)
      onskip()
    }

    function onskip() {
      if (--pending === 0 && self.sampled < self._minSamples) {
        if (retry) {
          self._autoSampling = false
          self.autoSample(false)
          return
        }
        self._resolve()
      }
    }
  }

  destroy() {
    this._autoSampling = true
    this._minSamples = 0
    this._resolve()
  }

  unfreeze() {
    this.frozen = false
    this._updateFirewall()
    this._updateAddresses()
  }

  freeze() {
    this.frozen = true
  }

  _updateFirewall() {
    if (!this.dht.firewalled) {
      this.firewall = FIREWALL.OPEN
      return
    }

    if (this.sampled < 3) return

    const max = this._samplesFull[0].hits

    if (max >= 3) {
      this.firewall = FIREWALL.CONSISTENT
      return
    }

    if (max === 1) {
      this.firewall = FIREWALL.RANDOM
      return
    }

    // else max === 2

    // 1 host, >= 4 total samples ie, 2 bad ones -> random
    if (this._samplesHost.length === 1 && this.sampled > 3) {
      this.firewall = FIREWALL.RANDOM
      return
    }

    // double hit on two different ips -> assume consistent
    if (this._samplesHost.length > 1 && this._samplesFull[1].hits > 1) {
      this.firewall = FIREWALL.CONSISTENT
      return
    }

    // (4 is just means - all the samples we expect) - no decision - assume random
    if (this.sampled > 4) {
      this.firewall = FIREWALL.RANDOM
    }
  }

  _updateAddresses() {
    if (this.firewall === FIREWALL.UNKNOWN) {
      this.addresses = null
      return
    }

    if (this.firewall === FIREWALL.RANDOM) {
      this.addresses = [this._samplesHost[0]]
      return
    }

    if (this.firewall === FIREWALL.CONSISTENT) {
      this.addresses = []
      for (const addr of this._samplesFull) {
        if (addr.hits >= 2 || this.addresses.length < 2) this.addresses.push(addr)
      }
    }
  }

  update() {
    if (this.dht.firewalled && this.firewall === FIREWALL.OPEN) {
      this.firewall = FIREWALL.UNKNOWN
    }
    this._updateFirewall()
    this._updateAddresses()
  }

  add(addr, from) {
    const ref = from.host + ':' + from.port

    if (this._visited.get(ref) === 2) return
    this._visited.set(ref, 2)

    addSample(this._samplesHost, addr.host, 0)
    addSample(this._samplesFull, addr.host, addr.port)

    if ((++this.sampled >= 3 || !this.dht.firewalled) && !this.frozen) {
      this.update()
    }

    if (this.firewall === FIREWALL.CONSISTENT || this.firewall === FIREWALL.OPEN) {
      this._resolve()
    } else if (this.sampled >= this._minSamples) {
      this._resolve()
    }
  }
}

function addSample(samples, host, port) {
  for (let i = 0; i < samples.length; i++) {
    const s = samples[i]

    if (s.port !== port || s.host !== host) continue
    s.hits++

    for (; i > 0; i--) {
      const prev = samples[i - 1]
      if (prev.hits >= s.hits) return
      samples[i - 1] = s
      samples[i] = prev
    }

    return
  }

  samples.push({
    host,
    port,
    hits: 1
  })
}
const NoiseSecretStream = require('@hyperswarm/secret-stream')
const NoiseHandshake = require('noise-handshake')
const curve = require('noise-curve-ed')
const c = require('compact-encoding')
const b4a = require('b4a')
const sodium = require('sodium-universal')
const m = require('./messages')
const { NS } = require('./constants')
const { HANDSHAKE_UNFINISHED } = require('./errors')

const NOISE_PROLOUGE = NS.PEER_HANDSHAKE

module.exports = class NoiseWrap {
  constructor(keyPair, remotePublicKey) {
    this.isInitiator = !!remotePublicKey
    this.remotePublicKey = remotePublicKey
    this.keyPair = keyPair
    this.handshake = new NoiseHandshake('IK', this.isInitiator, keyPair, { curve })
    this.handshake.initialise(NOISE_PROLOUGE, remotePublicKey)
  }

  send(payload) {
    const buf = c.encode(m.noisePayload, payload)
    return this.handshake.send(buf)
  }

  recv(buf) {
    const payload = c.decode(m.noisePayload, this.handshake.recv(buf))
    this.remotePublicKey = b4a.toBuffer(this.handshake.rs)
    return payload
  }

  final() {
    if (!this.handshake.complete) throw HANDSHAKE_UNFINISHED()

    const holepunchSecret = b4a.allocUnsafe(32)

    sodium.crypto_generichash(holepunchSecret, NS.PEER_HOLEPUNCH, this.handshake.hash)

    return {
      isInitiator: this.isInitiator,
      publicKey: this.keyPair.publicKey,
      streamId: this.streamId,
      remotePublicKey: this.remotePublicKey,
      remoteId: NoiseSecretStream.id(this.handshake.hash, !this.isInitiator),
      holepunchSecret,
      hash: b4a.toBuffer(this.handshake.hash),
      rx: b4a.toBuffer(this.handshake.rx),
      tx: b4a.toBuffer(this.handshake.tx)
    }
  }
}
const c = require('compact-encoding')
const sodium = require('sodium-universal')
const RecordCache = require('record-cache')
const Cache = require('xache')
const b4a = require('b4a')
const unslab = require('unslab')

const { encodeUnslab } = require('./encode')
const m = require('./messages')
const { NS, ERROR } = require('./constants')

const EMPTY = b4a.alloc(0)
const TMP = b4a.allocUnsafe(32)
const MAX_BUMP_DRIFT = 60_000

module.exports = class Persistent {
  constructor(dht, opts) {
    this.dht = dht
    this.records = new RecordCache(opts.records)
    this.bumps = new Cache(opts.bumps)
    this.refreshes = new Cache(opts.refreshes)
    this.mutables = new Cache(opts.mutables)
    this.immutables = new Cache(opts.immutables)
  }

  onlookup(req) {
    if (!req.target) return

    const k = b4a.toString(req.target, 'hex')
    const records = this.records.get(k, 20)
    const bump = this.bumps.get(k) || 0
    const fwd = this.dht._router.get(k)

    if (fwd && records.length < 20) records.push(fwd.record)

    req.reply(records.length ? c.encode(m.lookupRawReply, { peers: records, bump }) : null)
  }

  onfindpeer(req) {
    if (!req.target) return
    const fwd = this.dht._router.get(req.target)
    req.reply(fwd ? fwd.record : null)
  }

  unannounce(target, publicKey) {
    const k = b4a.toString(target, 'hex')
    sodium.crypto_generichash(TMP, publicKey)

    if (b4a.equals(TMP, target)) this.dht._router.delete(k)
    this.records.remove(k, publicKey)
  }

  onunannounce(req) {
    if (!req.target || !req.token) return

    const unann = decode(m.announce, req.value)
    if (unann === null) return

    const { peer, signature } = unann
    if (!peer || !signature) return

    const signable = annSignable(req.target, req.token, this.dht.id, unann, NS.UNANNOUNCE)

    if (!sodium.crypto_sign_verify_detached(signature, signable, peer.publicKey)) {
      return
    }

    this.unannounce(req.target, peer.publicKey)
    req.reply(null, { token: false, closerNodes: false })
  }

  _onrefresh(token, req) {
    sodium.crypto_generichash(TMP, token)
    const activeRefresh = b4a.toString(TMP, 'hex')

    const r = this.refreshes.get(activeRefresh)
    if (!r) return

    const { announceSelf, k, record } = r
    const publicKey = record.subarray(0, 32)

    if (announceSelf) {
      this.dht._router.set(k, {
        relay: req.from,
        record,
        onconnect: null,
        onholepunch: null
      })
      this.records.remove(k, publicKey)
    } else {
      this.records.add(k, publicKey, record)
    }

    this.refreshes.delete(activeRefresh)
    this.refreshes.set(b4a.toString(token, 'hex'), r)

    req.reply(null, { token: false, closerNodes: false })
  }

  onannounce(req) {
    if (!req.target || !req.token || !this.dht.id) return

    const ann = decode(m.announce, req.value)
    if (ann === null) return

    const { peer, refresh, signature, bump } = ann

    if (!peer) {
      if (!refresh) return
      this._onrefresh(refresh, req)
      return
    }

    const signable = annSignable(req.target, req.token, this.dht.id, ann, NS.ANNOUNCE)

    if (!signature || !sodium.crypto_sign_verify_detached(signature, signable, peer.publicKey)) {
      return
    }

    // TODO: it would be potentially be more optimal to allow more than 3 addresses here for a findPeer response
    // and only use max 3 for a lookup reply
    if (peer.relayAddresses.length > 3) {
      peer.relayAddresses = peer.relayAddresses.slice(0, 3)
    }

    sodium.crypto_generichash(TMP, peer.publicKey)

    const k = b4a.toString(req.target, 'hex')
    const announceSelf = b4a.equals(TMP, req.target)
    const record = encodeUnslab(m.peer, peer)

    if (announceSelf) {
      this.dht._router.set(k, {
        relay: req.from,
        record,
        onconnect: null,
        onholepunch: null
      })
      this.records.remove(k, peer.publicKey)
    } else {
      const currentBump = this.bumps.get(k) || 0
      if (bump > currentBump && bump <= Date.now() + MAX_BUMP_DRIFT) this.bumps.set(k, bump)
      this.records.add(k, peer.publicKey, record)
    }

    if (refresh) {
      this.refreshes.set(b4a.toString(refresh, 'hex'), { k, record, announceSelf })
    }

    req.reply(null, { token: false, closerNodes: false })
  }

  onmutableget(req) {
    if (!req.target || !req.value) return

    let seq = 0
    try {
      seq = c.decode(c.uint, req.value)
    } catch {
      return
    }

    const k = b4a.toString(req.target, 'hex')
    const value = this.mutables.get(k)

    if (!value) {
      req.reply(null)
      return
    }

    const localSeq = c.decode(c.uint, value)
    req.reply(localSeq < seq ? null : value)
  }

  onmutableput(req) {
    if (!req.target || !req.token || !req.value) return

    const p = decode(m.mutablePutRequest, req.value)
    if (!p) return

    const { publicKey, seq, value, signature } = p

    const hash = b4a.allocUnsafe(32)
    sodium.crypto_generichash(hash, publicKey)
    if (!b4a.equals(hash, req.target)) return

    if (!value || !verifyMutable(signature, seq, value, publicKey)) return

    const k = b4a.toString(hash, 'hex')
    const local = this.mutables.get(k)

    if (local) {
      const existing = c.decode(m.mutableGetResponse, local)
      if (existing.value && existing.seq === seq && b4a.compare(value, existing.value) !== 0) {
        req.error(ERROR.SEQ_REUSED)
        return
      }
      if (seq < existing.seq) {
        req.error(ERROR.SEQ_TOO_LOW)
        return
      }
    }

    this.mutables.set(k, encodeUnslab(m.mutableGetResponse, { seq, value, signature }))
    req.reply(null)
  }

  onimmutableget(req) {
    if (!req.target) return

    const k = b4a.toString(req.target, 'hex')
    const value = this.immutables.get(k)

    req.reply(value || null)
  }

  onimmutableput(req) {
    if (!req.target || !req.token || !req.value) return

    const hash = b4a.alloc(32)
    sodium.crypto_generichash(hash, req.value)
    if (!b4a.equals(hash, req.target)) return

    const k = b4a.toString(hash, 'hex')
    this.immutables.set(k, unslab(req.value))

    req.reply(null)
  }

  onplugin(req) {
    if (!req.value) return

    const plugreq = decode(m.pluginRequest, req.value)
    if (plugreq === null) return

    const p = this.dht.plugins.get(plugreq.plugin)
    if (!p || p.version !== plugreq.version) return

    p.onrequest(plugreq, req)
  }

  destroy() {
    this.records.destroy()
    this.refreshes.destroy()
    this.mutables.destroy()
    this.immutables.destroy()
  }

  static signMutable(seq, value, keyPair) {
    const signable = b4a.allocUnsafe(32 + 32)
    const hash = signable.subarray(32)

    signable.set(NS.MUTABLE_PUT, 0)

    sodium.crypto_generichash(hash, c.encode(m.mutableSignable, { seq, value }))
    return sign(signable, keyPair)
  }

  static verifyMutable(signature, seq, value, publicKey) {
    return verifyMutable(signature, seq, value, publicKey)
  }

  static signAnnounce(target, token, id, ann, keyPair) {
    return sign(annSignable(target, token, id, ann, NS.ANNOUNCE), keyPair)
  }

  static signUnannounce(target, token, id, ann, keyPair) {
    return sign(annSignable(target, token, id, ann, NS.UNANNOUNCE), keyPair)
  }
}

function verifyMutable(signature, seq, value, publicKey) {
  const signable = b4a.allocUnsafe(32 + 32)
  const hash = signable.subarray(32)

  signable.set(NS.MUTABLE_PUT, 0)

  sodium.crypto_generichash(hash, c.encode(m.mutableSignable, { seq, value }))
  return sodium.crypto_sign_verify_detached(signature, signable, publicKey)
}

function annSignable(target, token, id, ann, ns) {
  const signable = b4a.allocUnsafe(32 + 32)
  const hash = signable.subarray(32)

  signable.set(ns, 0)

  sodium.crypto_generichash_batch(hash, [
    target,
    id,
    token,
    c.encode(m.peer, ann.peer), // note that this is the partial encoding of the announce message so we could just use that for perf
    ann.refresh || EMPTY
  ])

  return signable
}

function sign(signable, keyPair) {
  if (keyPair.sign) {
    return keyPair.sign(signable)
  }
  const secretKey = keyPair.secretKey ? keyPair.secretKey : keyPair
  const signature = b4a.allocUnsafe(64)
  sodium.crypto_sign_detached(signature, signable, secretKey)
  return signature
}

function decode(enc, val) {
  try {
    return val && c.decode(enc, val)
  } catch (err) {
    return null
  }
}
module.exports = class RawStreamSet {
  constructor(dht) {
    this._dht = dht

    this._prefix = 16 - 1 // 16 is the default stream-set side in udx
    this._streams = new Map()
  }

  get size() {
    return this._streams.size
  }

  [Symbol.iterator]() {
    return this._streams.values()
  }

  add(opts) {
    const self = this

    // TODO: we should prob have a udx helper for id generation, given the slight complexity
    // of the below. requires a PRNG in udx tho.

    let id = 0

    while (true) {
      id = (Math.random() * 0x100000000) >>> 0

      if (this._streams.has(id & this._prefix)) continue
      break
    }

    // always have ~50% change of rolling a free one
    if (2 * this._streams.size >= this._prefix) {
      // ie 0b11111 = 0b1111 + 1 + 0b1111
      this._prefix = 2 * this._prefix + 1

      // move the prefixes over
      const next = new Map()
      for (const stream of this._streams.values()) {
        next.set(stream.id & this._prefix, stream)
      }
      this._streams = next
    }

    const stream = this._dht.udx.createStream(id, opts)
    this._streams.set(id & this._prefix, stream)

    stream.on('close', onclose)

    return stream

    function onclose() {
      self._streams.delete(id & self._prefix)
    }
  }

  async clear() {
    const destroying = []

    for (const stream of this._streams.values()) {
      destroying.push(new Promise((resolve) => stream.once('close', resolve).destroy()))
    }

    await Promise.allSettled(destroying)
  }
}
const c = require('compact-encoding')
const Cache = require('xache')
const safetyCatch = require('safety-catch')
const b4a = require('b4a')
const { handshake, holepunch } = require('./messages')
const { COMMANDS } = require('./constants')
const { BAD_HANDSHAKE_REPLY, BAD_HOLEPUNCH_REPLY } = require('./errors')

const FROM_CLIENT = 0
const FROM_SERVER = 1
const FROM_RELAY = 2
const FROM_SECOND_RELAY = 3
const REPLY = 4

// TODO: While the current design is very trustless in regards to clients/servers trusting the DHT,
// we should add a bunch of rate limits everywhere, especially including here to avoid bad users
// using a DHT node to relay traffic indiscriminately using the connect/holepunch messages.
// That's mostly from an abuse POV as none of the messsages do amplication.

module.exports = class Router {
  constructor(dht, opts) {
    this.dht = dht
    this.forwards = new Cache(opts.forwards)
  }

  set(target, state) {
    if (state.onpeerhandshake) {
      this.forwards.retain(toString(target), state)
    } else {
      this.forwards.set(toString(target), state)
    }
  }

  get(target) {
    return this.forwards.get(toString(target))
  }

  delete(target) {
    this.forwards.delete(toString(target))
  }

  destroy() {
    this.forwards.destroy()
  }

  async peerHandshake(target, { noise, peerAddress, relayAddress, socket, session }, to) {
    const dht = this.dht

    const requestValue = c.encode(handshake, {
      mode: FROM_CLIENT,
      noise,
      peerAddress,
      relayAddress
    })

    const res = await dht.request(
      { command: COMMANDS.PEER_HANDSHAKE, target, value: requestValue },
      to,
      { socket, session }
    )

    const hs = decode(handshake, res.value)
    if (
      !hs ||
      hs.mode !== REPLY ||
      to.host !== res.from.host ||
      to.port !== res.from.port ||
      !hs.noise
    ) {
      throw BAD_HANDSHAKE_REPLY()
    }

    return {
      noise: hs.noise,
      relayed: !!hs.peerAddress,
      serverAddress: hs.peerAddress || to,
      clientAddress: res.to
    }
  }

  async onpeerhandshake(req) {
    const hs = req.value && decode(handshake, req.value)
    if (!hs) return

    const { mode, noise, peerAddress, relayAddress } = hs

    const state = req.target && this.get(req.target)
    const isServer = !!(state && state.onpeerhandshake)
    const relay = state && state.relay

    if (isServer) {
      let reply = null
      try {
        reply = noise && (await state.onpeerhandshake({ noise, peerAddress }, req))
      } catch (e) {
        safetyCatch(e)
        return
      }
      if (!reply || !reply.noise) return
      const opts = { socket: reply.socket, closerNodes: false, token: false }

      switch (mode) {
        case FROM_CLIENT: {
          req.reply(
            c.encode(handshake, { mode: REPLY, noise: reply.noise, peerAddress: null }),
            opts
          )
          return
        }
        case FROM_RELAY: {
          req.relay(
            c.encode(handshake, { mode: FROM_SERVER, noise: reply.noise, peerAddress }),
            req.from,
            opts
          )
          return
        }
        case FROM_SECOND_RELAY: {
          if (!relayAddress) return
          req.relay(
            c.encode(handshake, { mode: FROM_SERVER, noise: reply.noise, peerAddress }),
            relayAddress,
            opts
          )
          return // eslint-disable-line
        }
      }
    } else {
      switch (mode) {
        case FROM_CLIENT: {
          // TODO: if no relay is known route closer to the target instead of timing out
          if (!noise) return
          if (!relay && !relayAddress) {
            // help the user route
            req.reply(null, { token: false, closerNodes: true })
            return
          }
          req.relay(
            c.encode(handshake, {
              mode: FROM_RELAY,
              noise,
              peerAddress: req.from,
              relayAddress: null
            }),
            relayAddress || relay
          )
          return
        }
        case FROM_RELAY: {
          if (!relay || !noise) return
          req.relay(
            c.encode(handshake, {
              mode: FROM_SECOND_RELAY,
              noise,
              peerAddress,
              relayAddress: req.from
            }),
            relay
          )
          return
        }
        case FROM_SERVER: {
          if (!peerAddress || !noise) return
          req.reply(
            c.encode(handshake, { mode: REPLY, noise, peerAddress: req.from, relayAddress: null }),
            { to: peerAddress, closerNodes: false, token: false }
          )
          return // eslint-disable-line
        }
      }
    }
  }

  async peerHolepunch(target, { id, payload, peerAddress, socket, session }, to) {
    const dht = this.dht
    const requestValue = c.encode(holepunch, {
      mode: FROM_CLIENT,
      id,
      payload,
      peerAddress
    })

    const res = await dht.request(
      { command: COMMANDS.PEER_HOLEPUNCH, target, value: requestValue },
      to,
      { socket, session }
    )

    const hp = decode(holepunch, res.value)
    if (!hp || hp.mode !== REPLY || to.host !== res.from.host || to.port !== res.from.port) {
      throw BAD_HOLEPUNCH_REPLY()
    }

    return {
      from: res.from,
      to: res.to,
      payload: hp.payload,
      peerAddress: hp.peerAddress || to
    }
  }

  async onpeerholepunch(req) {
    const hp = req.value && decode(holepunch, req.value)
    if (!hp) return

    const { mode, id, payload, peerAddress } = hp

    const state = req.target && this.get(req.target)
    const isServer = !!(state && state.onpeerholepunch)
    const relay = state && state.relay

    switch (mode) {
      case FROM_CLIENT: {
        if (!peerAddress && !relay) return
        req.relay(
          c.encode(holepunch, { mode: FROM_RELAY, id, payload, peerAddress: req.from }),
          peerAddress || relay
        )
        return
      }
      case FROM_RELAY: {
        if (!isServer || !peerAddress) return
        let reply = null
        try {
          reply = await state.onpeerholepunch({ id, payload, peerAddress }, req)
        } catch (e) {
          safetyCatch(e)
          return
        }
        if (!reply) return
        const opts = { socket: reply.socket, closerNodes: false, token: false }
        req.relay(
          c.encode(holepunch, { mode: FROM_SERVER, id: 0, payload: reply.payload, peerAddress }),
          req.from,
          opts
        )
        return
      }
      case FROM_SERVER: {
        req.reply(c.encode(holepunch, { mode: REPLY, id, payload, peerAddress: req.from }), {
          to: peerAddress,
          closerNodes: false,
          token: false
        })
        return // eslint-disable-line
      }
    }
  }
}

function decode(enc, val) {
  try {
    return c.decode(enc, val)
  } catch {
    return null
  }
}

function toString(t) {
  return typeof t === 'string' ? t : b4a.toString(t, 'hex')
}
const sodium = require('sodium-universal')
const b4a = require('b4a')
const { holepunchPayload } = require('./messages')

module.exports = class HolepunchPayload {
  constructor(holepunchSecret) {
    this._sharedSecret = holepunchSecret
    this._localSecret = b4a.allocUnsafe(32)

    sodium.randombytes_buf(this._localSecret)
  }

  decrypt(buffer) {
    const state = { start: 24, end: buffer.byteLength - 16, buffer }

    if (state.end <= state.start) return null

    const nonce = buffer.subarray(0, 24)
    const msg = state.buffer.subarray(state.start, state.end)
    const cipher = state.buffer.subarray(state.start)

    if (!sodium.crypto_secretbox_open_easy(msg, cipher, nonce, this._sharedSecret)) return null

    try {
      return holepunchPayload.decode(state)
    } catch {
      return null
    }
  }

  encrypt(payload) {
    const state = { start: 24, end: 24, buffer: null }
    holepunchPayload.preencode(state, payload)
    state.buffer = b4a.allocUnsafe(state.end + 16)

    const nonce = state.buffer.subarray(0, 24)
    const msg = state.buffer.subarray(state.start, state.end)
    const cipher = state.buffer.subarray(state.start)

    holepunchPayload.encode(state, payload)
    sodium.randombytes_buf(nonce)
    sodium.crypto_secretbox_easy(cipher, msg, nonce, this._sharedSecret)

    return state.buffer
  }

  token(addr) {
    const out = b4a.allocUnsafe(32)
    sodium.crypto_generichash(out, b4a.from(addr.host), this._localSecret)
    return out
  }
}
const DONE = Promise.resolve(true)
const DESTROYED = Promise.resolve(false)

module.exports = class Semaphore {
  constructor(limit = 1) {
    this.limit = limit
    this.active = 0
    this.waiting = []

    this.flushedPromise = null
    this.flushedResolve = null

    this.destroyed = false

    this._onwait = this._queueWaiting.bind(this)
    this._onflush = this._queueFlushed.bind(this)
  }

  _queueWaiting(resolve) {
    this.waiting.push(resolve)
  }

  _queueFlushed(resolve) {
    this.flushedResolve = resolve
  }

  wait() {
    if (this.destroyed === true) return DESTROYED

    if (this.active < this.limit && this.waiting.length === 0) {
      this.active++
      return DONE
    }

    return new Promise(this._onwait)
  }

  signal() {
    if (this.destroyed === true) return

    this.active--
    while (this.active < this.limit && this.waiting.length > 0 && this.destroyed === false) {
      this.active++
      this.waiting.shift()(true)
    }

    if (this.active === 0 && this.flushedResolve) {
      const resolve = this.flushedResolve
      this.flushedResolve = null
      this.flushedPromise = null
      resolve(true)
    }
  }

  async flush() {
    if (this.destroyed === true) return
    if (this.active === 0) return
    if (this.flushedPromise) return this.flushedPromise
    this.flushedPromise = new Promise(this._onflush)
    return this.flushedPromise
  }

  destroy() {
    this.destroyed = true
    this.active = 0
    while (this.waiting.length) this.waiting.pop()(false)
    if (this.flushedResolve) this.flushedResolve(false)
  }
}
const { EventEmitter } = require('events')
const safetyCatch = require('safety-catch')
const NoiseSecretStream = require('@hyperswarm/secret-stream')
const b4a = require('b4a')
const relay = require('blind-relay')
const NoiseWrap = require('./noise-wrap')
const Announcer = require('./announcer')
const { FIREWALL, ERROR } = require('./constants')
const { unslabbedHash } = require('./crypto')
const SecurePayload = require('./secure-payload')
const Holepuncher = require('./holepuncher')
const { isPrivate } = require('bogon')
const { ALREADY_LISTENING, NODE_DESTROYED, KEYPAIR_ALREADY_USED } = require('./errors')

const HANDSHAKE_CLEAR_WAIT = 10000
const HANDSHAKE_INITIAL_TIMEOUT = 10000

module.exports = class Server extends EventEmitter {
  constructor(dht, opts = {}) {
    super()

    this.dht = dht
    this.target = null

    this.closed = false
    this.firewall = opts.firewall || (() => false)
    this.holepunch = opts.holepunch || (() => true)
    this.relayThrough = opts.relayThrough || null
    this.relayKeepAlive = opts.relayKeepAlive || 5000
    this.pool = opts.pool || null
    this.createHandshake = opts.createHandshake || defaultCreateHandshake
    this.createSecretStream = opts.createSecretStream || defaultCreateSecretStream
    this.suspended = false
    this.handshakeClearWait = opts.handshakeClearWait || HANDSHAKE_CLEAR_WAIT

    this._shareLocalAddress = opts.shareLocalAddress !== false
    this._reusableSocket = !!opts.reusableSocket
    this._neverPunch = opts.holepunch === false // useful for fully disabling punching
    this._keyPair = null
    this._announcer = null
    this._connects = new Map()
    this._holepunches = []
    this._listening = null
    this._closing = null
  }

  get listening() {
    return this._listening !== null
  }

  get publicKey() {
    return this._keyPair && this._keyPair.publicKey
  }

  get relayAddresses() {
    return this._announcer ? this._announcer.relayAddresses : []
  }

  onconnection(encryptedSocket) {
    this.emit('connection', encryptedSocket)
  }

  async suspend({ log = noop } = {}) {
    log('Suspending hyperdht server')
    if (this._listening !== null) await this._listening
    log('Suspending hyperdht server (post listening)')
    this.suspended = true
    this._clearAll()
    return this._announcer ? this._announcer.suspend({ log }) : Promise.resolve()
  }

  async resume() {
    if (this._listening !== null) await this._listening
    this.suspended = false
    return this._announcer ? this._announcer.resume() : Promise.resolve()
  }

  address() {
    if (!this._keyPair) return null

    return {
      publicKey: this._keyPair.publicKey,
      host: this.dht.host,
      port: this.dht.port
    }
  }

  close() {
    if (this._closing) return this._closing
    this._closing = this._close()
    return this._closing
  }

  _gc() {
    this.dht.listening.delete(this)
    if (this.target) this.dht._router.delete(this.target)
  }

  async _stopListening() {
    try {
      if (this._announcer) await this._announcer.stop()
    } catch {
      // ignore
    }

    this._announcer = null
    this._listening = null
    this._keyPair = null
  }

  async _close() {
    if (this._listening === null) {
      this.closed = true
      this.emit('close')
      return
    }

    try {
      await this._listening
    } catch (err) {
      safetyCatch(err)
    }

    this._gc()
    this._clearAll()

    await this._stopListening()

    this.closed = true
    this.emit('close')
  }

  _clearAll() {
    while (this._holepunches.length > 0) {
      const h = this._holepunches.pop()
      if (h && h.puncher) h.puncher.destroy()
      if (h && h.clearing) clearTimeout(h.clearing)
      if (h && h.prepunching) clearTimeout(h.prepunching)
      if (h && h.rawStream) h.rawStream.destroy()
    }

    this._connects.clear()
  }

  async listen(keyPair = this.dht.defaultKeyPair, opts = {}) {
    if (this._listening !== null) throw ALREADY_LISTENING()
    if (this.dht.destroyed) throw NODE_DESTROYED()

    this._listening = this._listen(keyPair, opts)
    await this._listening
    return this
  }

  async _listen(keyPair, opts) {
    // From now on, the DHT object which created me is responsible for closing me
    this.dht.listening.add(this)

    try {
      await this.dht.bind()
      if (this._closing) return

      for (const s of this.dht.listening) {
        if (s._keyPair && b4a.equals(s._keyPair.publicKey, keyPair.publicKey)) {
          throw KEYPAIR_ALREADY_USED()
        }
      }

      this.target = unslabbedHash(keyPair.publicKey)
      this._keyPair = keyPair
      this._announcer = new Announcer(this.dht, keyPair, this.target, opts)

      this.dht._router.set(this.target, {
        relay: null,
        record: this._announcer.record,
        onpeerhandshake: this._onpeerhandshake.bind(this),
        onpeerholepunch: this._onpeerholepunch.bind(this)
      })

      // warm it up for now
      this._localAddresses().catch(safetyCatch)

      await this._announcer.start()
    } catch (err) {
      await this._stopListening()
      this._gc()
      throw err
    }

    if (this._closing) return
    if (this.suspended) await this._announcer.suspend()

    if (this._closing) return
    if (this.dht.destroyed) throw NODE_DESTROYED()

    if (this.pool) this.pool._attachServer(this)

    this.emit('listening')
  }

  refresh() {
    if (this._announcer && !this.suspended) this._announcer.refresh()
  }

  notifyOnline() {
    if (this._announcer) this._announcer.online.notify()
  }

  _localAddresses() {
    return this.dht.validateLocalAddresses(Holepuncher.localAddresses(this.dht.io.serverSocket))
  }

  async _addHandshake(k, noise, clientAddress, { from, to: serverAddress, socket }, direct) {
    let id = this._holepunches.indexOf(null)
    if (id === -1) id = this._holepunches.push(null) - 1

    const hs = {
      round: 0,
      reply: null,
      puncher: null,
      payload: null,
      rawStream: null,
      encryptedSocket: null,
      prepunching: null,
      firewalled: true,
      clearing: null,
      onsocket: null,
      aborted: false,

      // Relay state
      relayTimeout: null,
      relayToken: null,
      relaySocket: null,
      relayClient: null,
      relayPaired: false
    }

    this._holepunches[id] = hs

    const handshake = this.createHandshake(this._keyPair, null)

    let remotePayload
    try {
      remotePayload = await handshake.recv(noise)
    } catch (err) {
      safetyCatch(err)
      this._clearLater(hs, id, k)
      return null
    }

    if (this._closing || this.suspended) return null

    try {
      hs.firewalled = await this.firewall(handshake.remotePublicKey, remotePayload, clientAddress)
    } catch (err) {
      safetyCatch(err)
    }

    if (this._closing || this.suspended) return null

    if (hs.firewalled) {
      this._clearLater(hs, id, k)
      return null
    }

    const error =
      remotePayload.version === 1
        ? remotePayload.udx
          ? ERROR.NONE
          : ERROR.ABORTED
        : ERROR.VERSION_MISMATCH

    const addresses = []
    const ourRemoteAddr = this.dht.remoteAddress()
    const ourLocalAddrs = this._shareLocalAddress ? await this._localAddresses() : null

    if (this._closing || this.suspended) return null

    if (ourRemoteAddr) addresses.push(ourRemoteAddr)
    if (ourLocalAddrs) addresses.push(...ourLocalAddrs)

    if (error === ERROR.NONE) {
      hs.rawStream = this.dht.createRawStream({
        framed: true,
        firewall(socket, port, host) {
          if (!(port > 0 && port < 65536)) return true

          // Check if the traffic originated from the socket on which we're expecting relay traffic. If so,
          // we haven't hole punched yet and the other side is just sending us traffic through the relay.
          if (hs.relaySocket && isRelay(hs.relaySocket, socket, port, host)) {
            return false
          }

          hs.onsocket(socket, port, host)
          return false
        }
      })

      hs.rawStream.on('error', autoDestroy)

      // Handles the case where onsocket is never called, but the stream got setup
      // This can happen on a relayed connection which never connects directly
      // (onsocket is called there only when the direct connection is established)
      const onrawstreamclose = () => {
        if (this._closing) return
        this._clearLater(hs, id, k)
      }
      hs.rawStream.on('close', onrawstreamclose)

      hs.onsocket = (socket, port, host) => {
        if (hs.rawStream === null) return // Already hole punched

        this._clearLater(hs, id, k)

        if (hs.prepunching) {
          clearTimeout(hs.prepunching)
          hs.prepunching = null
        }

        if (this._reusableSocket && remotePayload.udx.reusableSocket) {
          this.dht._socketPool.routes.add(handshake.remotePublicKey, hs.rawStream)
        }

        hs.rawStream.removeListener('error', autoDestroy)
        hs.rawStream.removeListener('close', onrawstreamclose)

        if (hs.rawStream.connected) {
          const remoteChanging = hs.rawStream.changeRemote(socket, remotePayload.udx.id, port, host)

          if (remoteChanging) remoteChanging.catch(safetyCatch)
        } else {
          hs.rawStream.connect(socket, remotePayload.udx.id, port, host)
          hs.encryptedSocket = this.createSecretStream(false, hs.rawStream, {
            handshake: h,
            keepAlive: this.dht.connectionKeepAlive
          })

          this.onconnection(hs.encryptedSocket)
        }

        if (hs.puncher) {
          hs.puncher.onabort = noop
          hs.puncher.destroy()
        }

        hs.rawStream = null
      }

      function autoDestroy() {
        if (hs.puncher) hs.puncher.destroy()
      }
    }

    const relayAddresses = this.relayAddresses
    const relayThrough = selectRelay(this.relayThrough)

    if (relayThrough) hs.relayToken = relay.token()

    try {
      hs.reply = await handshake.send({
        error,
        firewall: ourRemoteAddr ? FIREWALL.OPEN : FIREWALL.UNKNOWN,
        holepunch: ourRemoteAddr ? null : { id, relays: this._announcer.relays },
        addresses4: addresses,
        addresses6: null,
        udx: {
          reusableSocket: this._reusableSocket,
          id: hs.rawStream ? hs.rawStream.id : 0,
          seq: 0
        },
        secretStream: {},
        relayThrough: relayThrough ? { publicKey: relayThrough, token: hs.relayToken } : null,
        relayAddresses: relayAddresses.length ? relayAddresses : null
      })
    } catch (err) {
      safetyCatch(err)
      if (hs.rawStream) hs.rawStream.destroy()
      this._clearLater(hs, id, k)
      return null
    }

    if (this._closing || this.suspended) {
      if (hs.rawStream) hs.rawStream.destroy()
      return null
    }

    const h = handshake.final()

    if (error !== ERROR.NONE) {
      if (hs.rawStream) hs.rawStream.destroy()
      this._clearLater(hs, id, k)
      return hs
    }

    if (remotePayload.firewall === FIREWALL.OPEN || direct) {
      const sock = direct ? socket : this.dht.socket
      this.dht.stats.punches.open++
      hs.onsocket(sock, clientAddress.port, clientAddress.host)
      return hs
    }

    if (relayThrough || remotePayload.relayThrough) {
      this._relayConnection(hs, relayThrough, remotePayload, h)
    }

    const onabort = () => {
      hs.aborted = true
      if (hs.prepunching) clearTimeout(hs.prepunching)
      hs.prepunching = null
      if (hs.rawStream.destroyed) {
        this._clearLater(hs, id, k)
        return
      }

      hs.rawStream.on('close', () => this._clearLater(hs, id, k))
      if (hs.relayToken === null) hs.rawStream.destroy()
    }

    if (!direct && clientAddress.host === serverAddress.host) {
      const clientAddresses = remotePayload.addresses4.filter(onlyPrivateHosts)

      if (clientAddresses.length > 0 && this._shareLocalAddress) {
        const myAddresses = await this._localAddresses()
        const addr = Holepuncher.matchAddress(myAddresses, clientAddresses)

        if (addr) {
          hs.prepunching = setTimeout(onabort, HANDSHAKE_INITIAL_TIMEOUT)
          return hs
        }
      }
    }

    if (this._closing || this.suspended) return null

    if (ourRemoteAddr || this._neverPunch) {
      hs.prepunching = setTimeout(onabort, HANDSHAKE_INITIAL_TIMEOUT)
      return hs
    }

    hs.payload = new SecurePayload(h.holepunchSecret)
    hs.puncher = new Holepuncher(this.dht, this.dht.session(), false, remotePayload.firewall)

    hs.puncher.onconnect = hs.onsocket
    hs.puncher.onabort = onabort
    hs.prepunching = setTimeout(hs.puncher.destroy.bind(hs.puncher), HANDSHAKE_INITIAL_TIMEOUT)

    return hs
  }

  _clearLater(hs, id, k) {
    if (hs.clearing) return
    hs.clearing = setTimeout(() => this._clear(hs, id, k), this.handshakeClearWait)
  }

  _clear(hs, id, k) {
    if (id >= this._holepunches.length || this._holepunches[id] !== hs) return
    if (hs.clearing) clearTimeout(hs.clearing)

    this._holepunches[id] = null
    while (
      this._holepunches.length > 0 &&
      this._holepunches[this._holepunches.length - 1] === null
    ) {
      this._holepunches.pop()
    }
    this._connects.delete(k)
  }

  async _onpeerhandshake({ noise, peerAddress }, req) {
    const k = b4a.toString(noise, 'hex')

    // The next couple of statements MUST run within the same tick to prevent
    // a malicious peer from flooding us with handshakes.
    let p = this._connects.get(k)
    if (!p) {
      p = this._addHandshake(k, noise, peerAddress || req.from, req, !peerAddress)
      this._connects.set(k, p)
    }

    const h = await p
    if (!h) return null

    if (this._closing !== null || this.suspended) return null

    return { socket: h.puncher && h.puncher.socket, noise: h.reply }
  }

  async _onpeerholepunch({ id, peerAddress, payload }, req) {
    const h = id < this._holepunches.length ? this._holepunches[id] : null
    if (!h) return null

    if (!peerAddress || this._closing !== null || this.suspended) return null

    const p = h.puncher
    if (!p || !p.socket) return this._abort(h) // not opened

    const remotePayload = h.payload.decrypt(payload)
    if (!remotePayload) return null

    const isServerRelay = this._announcer.isRelay(req.from)
    const { error, firewall, round, punching, addresses, remoteAddress, remoteToken } =
      remotePayload

    if (error !== ERROR.NONE) {
      // We actually do not need to set the round here, but just do it for consistency.
      if (round >= h.round) h.round = round
      return this._abort(h)
    }

    const token = h.payload.token(peerAddress)
    const echoed = isServerRelay && !!remoteToken && b4a.equals(token, remoteToken)

    // Update our heuristics here
    if (req.socket === p.socket) {
      p.nat.add(req.to, req.from)
    }

    if (round >= h.round) {
      h.round = round
      p.updateRemote({ punching, firewall, addresses, verified: echoed ? peerAddress.host : null })
    }

    // Wait for the analyzer to reach a conclusion...
    let stable = await p.analyze(false)
    if (p.destroyed) return null

    if (!p.remoteHolepunching && !stable) {
      stable = await p.analyze(true)
      if (p.destroyed) return null
      if (!stable) return this._abort(h)
    }

    // Fast mode! If we are consistent and the remote has opened a session to us (remoteAddress)
    // then fire a quick punch back. Note the await here just waits for the udp socket to flush.
    if (
      isConsistent(p.nat.firewall) &&
      remoteAddress &&
      hasSameAddr(p.nat.addresses, remoteAddress)
    ) {
      await p.ping(peerAddress)
      if (p.destroyed) return null
    }

    // Remote said they are punching (or willing to), so we will punch as well.
    // Note that this returns when the punching has STARTED, so no guarantee
    // we will have a connection after this promise etc.
    if (p.remoteHolepunching) {
      // TODO: still continue here if a local connection might work, but then do not holepunch...
      if (!this.holepunch(p.remoteFirewall, p.nat.firewall, p.remoteAddresses, p.nat.addresses)) {
        return p.destroyed ? null : this._abort(h)
      }

      if (h.prepunching) {
        clearTimeout(h.prepunching)
        h.prepunching = null
      }

      if (p.remoteFirewall >= FIREWALL.RANDOM || p.nat.firewall >= FIREWALL.RANDOM) {
        if (
          this.dht._randomPunches >= this.dht._randomPunchLimit ||
          Date.now() - this.dht._lastRandomPunch < this.dht._randomPunchInterval
        ) {
          if (!h.relayToken) return this._abort(h, ERROR.TRY_LATER)
          return {
            socket: p.socket,
            payload: h.payload.encrypt({
              error: ERROR.TRY_LATER,
              firewall: p.nat.firewall,
              round: h.round,
              connected: p.connected,
              punching: p.punching,
              addresses: p.nat.addresses,
              remoteAddress: null,
              token: isServerRelay ? token : null,
              remoteToken: remotePayload.token
            })
          }
        }
      }

      const punching = await p.punch()
      if (p.destroyed) return null
      if (!punching) return this._abort(h)
    }

    // Freeze that analysis as soon as we have a result we are giving to the other peer
    if (p.nat.firewall !== FIREWALL.UNKNOWN) {
      p.nat.freeze()
    }

    return {
      socket: p.socket,
      payload: h.payload.encrypt({
        error: ERROR.NONE,
        firewall: p.nat.firewall,
        round: h.round,
        connected: p.connected,
        punching: p.punching,
        addresses: p.nat.addresses,
        remoteAddress: null,
        token: isServerRelay ? token : null,
        remoteToken: remotePayload.token
      })
    }
  }

  _abort(h, error = ERROR.ABORTED) {
    if (!h.payload) {
      if (h.puncher) h.puncher.destroy()
      return null
    }

    const payload = h.payload.encrypt({
      error,
      firewall: FIREWALL.UNKNOWN,
      round: h.round,
      connected: false,
      punching: false,
      addresses: null,
      remoteAddress: null,
      token: null,
      remoteToken: null
    })

    h.puncher.destroy()

    return { socket: this.dht.socket, payload }
  }

  _relayConnection(hs, relayThrough, remotePayload, h) {
    this.dht.stats.relaying.attempts++

    let isInitiator
    let publicKey
    let token

    if (relayThrough) {
      isInitiator = true
      publicKey = relayThrough
      token = hs.relayToken
    } else {
      isInitiator = false
      publicKey = remotePayload.relayThrough.publicKey
      token = remotePayload.relayThrough.token
    }

    hs.relayToken = token
    hs.relaySocket = this.dht.connect(publicKey)
    hs.relaySocket.setKeepAlive(this.relayKeepAlive)
    hs.relayClient = relay.Client.from(hs.relaySocket, { id: hs.relaySocket.publicKey })
    hs.relayTimeout = setTimeout(onabort, 15000)

    hs.relayClient
      .pair(isInitiator, token, hs.rawStream)
      .on('error', onabort)
      .on('data', (remoteId) => {
        if (hs.relayTimeout) clearRelayTimeout(hs)
        if (hs.rawStream === null) {
          onabort(null)
          return
        }

        hs.relayPaired = true
        this.dht.stats.relaying.successes++

        if (hs.prepunching) clearTimeout(hs.prepunching)
        hs.prepunching = null

        const { remotePort, remoteHost, socket } = hs.relaySocket.rawStream

        hs.rawStream
          .on('close', () => hs.relaySocket.destroy())
          .connect(socket, remoteId, remotePort, remoteHost)

        hs.encryptedSocket = this.createSecretStream(false, hs.rawStream, { handshake: h })

        this.onconnection(hs.encryptedSocket)
      })

    const dht = this.dht
    function onabort() {
      if (!hs.relayPaired) dht.stats.relaying.aborts++
      if (hs.relayTimeout) clearRelayTimeout(hs)
      const socket = hs.relaySocket
      hs.relayToken = null
      hs.relaySocket = null
      if (socket) socket.destroy()
      if (hs.aborted && hs.rawStream) hs.rawStream.destroy()
    }
  }
}

function clearRelayTimeout(hs) {
  clearTimeout(hs.relayTimeout)
  hs.relayTimeout = null
}

function isConsistent(fw) {
  return fw === FIREWALL.OPEN || fw === FIREWALL.CONSISTENT
}

function hasSameAddr(addrs, other) {
  if (addrs === null) return false

  for (const addr of addrs) {
    if (addr.port === other.port && addr.host === other.host) return true
  }
  return false
}

function defaultCreateHandshake(keyPair, remotePublicKey) {
  return new NoiseWrap(keyPair, remotePublicKey)
}

function defaultCreateSecretStream(isInitiator, rawStream, opts) {
  return new NoiseSecretStream(isInitiator, rawStream, opts)
}

function onlyPrivateHosts(addr) {
  return isPrivate(addr.host)
}

function isRelay(relaySocket, socket, port, host) {
  const stream = relaySocket.rawStream
  if (!stream) return false
  if (stream.socket !== socket) return false
  return port === stream.remotePort && host === stream.remoteHost
}

function selectRelay(relayThrough) {
  if (typeof relayThrough === 'function') relayThrough = relayThrough()
  if (relayThrough === null) return null
  if (Array.isArray(relayThrough)) {
    return relayThrough[Math.floor(Math.random() * relayThrough.length)]
  }
  return relayThrough
}

function noop() {}
module.exports = class Sleeper {
  constructor() {
    this._timeout = null
    this._resolve = null

    this._start = (resolve) => {
      this._resolve = resolve
    }

    this._trigger = () => {
      if (this._resolve === null) return
      const resolve = this._resolve
      this._timeout = null
      this._resolve = null
      resolve()
    }
  }

  pause(ms) {
    const p = new Promise(this._start)
    if (this._timeout !== null) {
      clearTimeout(this._timeout)
      this._trigger()
    }
    this._timeout = setTimeout(this._trigger, ms)
    return p
  }

  resume() {
    if (this._timeout !== null) {
      clearTimeout(this._timeout)
      this._trigger()
    }
  }
}
const b4a = require('b4a')

const LINGER_TIME = 3000

module.exports = class SocketPool {
  constructor(dht, host) {
    this._dht = dht
    this._sockets = new Map()
    this._lingering = new Set() // updated by the ref
    this._host = host

    this.routes = new SocketRoutes(this)
  }

  _onmessage(ref, data, address) {
    this._dht.onmessage(ref.socket, data, address)
  }

  _add(ref) {
    this._sockets.set(ref.socket, ref)
  }

  _remove(ref) {
    this._sockets.delete(ref.socket)
    this._lingering.delete(ref)
  }

  lookup(socket) {
    return this._sockets.get(socket) || null
  }

  setReusable(socket, bool) {
    const ref = this.lookup(socket)
    if (ref) ref.reusable = bool
  }

  acquire() {
    // TODO: Enable socket reuse
    return new SocketRef(this)
  }

  async destroy() {
    const closing = []

    for (const ref of this._sockets.values()) {
      ref._unlinger()
      closing.push(ref.socket.close())
    }

    await Promise.allSettled(closing)
  }
}

class SocketRoutes {
  constructor(pool) {
    this._pool = pool
    this._routes = new Map()
  }

  add(publicKey, rawStream) {
    if (rawStream.socket) this._onconnect(publicKey, rawStream)
    else rawStream.on('connect', this._onconnect.bind(this, publicKey, rawStream))
  }

  get(publicKey) {
    const id = b4a.toString(publicKey, 'hex')
    const route = this._routes.get(id)
    if (!route) return null
    return route
  }

  _onconnect(publicKey, rawStream) {
    const id = b4a.toString(publicKey, 'hex')
    const socket = rawStream.socket

    let route = this._routes.get(id)

    if (!route) {
      const gc = () => {
        if (this._routes.get(id) === route) this._routes.delete(id)
        socket.removeListener('close', gc)
      }

      route = {
        socket,
        address: { host: rawStream.remoteHost, port: rawStream.remotePort },
        gc
      }

      this._routes.set(id, route)
      socket.on('close', gc)
    }

    this._pool.setReusable(socket, true)

    rawStream.on('error', () => {
      this._pool.setReusable(socket, false)
      if (!route) route = this._routes.get(id)
      if (route && route.socket === socket) route.gc()
    })
  }
}

// TODO: we should just make some "user data" object on udx to allow to attach this info
class SocketRef {
  constructor(pool) {
    this._pool = pool

    // Events
    this.onholepunchmessage = noop

    // Whether it should teardown immediately or wait a bit
    this.reusable = false

    this.socket = pool._dht.udx.createSocket()
    this.socket
      .on('close', this._onclose.bind(this))
      .on('message', this._onmessage.bind(this))
      .on('idle', this._onidle.bind(this))
      .on('busy', this._onbusy.bind(this))
      .bind(0, this._pool._host)

    this._refs = 1
    this._released = false
    this._closed = false

    this._timeout = null
    this._wasBusy = false

    this._pool._add(this)
  }

  _onclose() {
    this._pool._remove(this)
  }

  _onmessage(data, address) {
    if (data.byteLength > 1) {
      this._pool._onmessage(this, data, address)
    } else {
      this.onholepunchmessage(data, address, this)
    }
  }

  _onidle() {
    this._closeMaybe()
  }

  _onbusy() {
    this._wasBusy = true
    this._unlinger()
  }

  _reset() {
    this.onholepunchmessage = noop
  }

  _closeMaybe() {
    if (this._refs === 0 && this.socket.idle && !this._timeout) this._close()
  }

  _lingeringClose() {
    this._pool._lingering.delete(this)
    this._timeout = null
    this._closeMaybe()
  }

  _close() {
    this._unlinger()

    if (this.reusable && this._wasBusy) {
      this._wasBusy = false
      this._pool._lingering.add(this)
      this._timeout = setTimeout(this._lingeringClose.bind(this), LINGER_TIME)
      return
    }

    this._closed = true
    this.socket.close()
  }

  _unlinger() {
    if (this._timeout !== null) {
      clearTimeout(this._timeout)
      this._pool._lingering.delete(this)
      this._timeout = null
    }
  }

  get free() {
    return this._refs === 0
  }

  active() {
    this._refs++
    this._unlinger()
  }

  inactive() {
    this._refs--
    this._closeMaybe()
  }

  address() {
    return this.socket.address()
  }

  release() {
    if (this._released) return

    this._released = true
    this._reset()

    this._refs--
    this._closeMaybe()
  }
}

function noop() {}
const LE = (exports.LE =
  new Uint8Array(new Uint16Array([0xff]).buffer)[0] === 0xff)

exports.BE = !LE
const b4a = require('b4a')

const { BE } = require('./endian')

exports.state = function (start = 0, end = 0, buffer = null) {
  return { start, end, buffer }
}

const raw = (exports.raw = require('./raw'))

const uint = (exports.uint = {
  preencode(state, n) {
    state.end += n <= 0xfc ? 1 : n <= 0xffff ? 3 : n <= 0xffffffff ? 5 : 9
  },
  encode(state, n) {
    if (n <= 0xfc) uint8.encode(state, n)
    else if (n <= 0xffff) {
      state.buffer[state.start++] = 0xfd
      uint16.encode(state, n)
    } else if (n <= 0xffffffff) {
      state.buffer[state.start++] = 0xfe
      uint32.encode(state, n)
    } else {
      state.buffer[state.start++] = 0xff
      uint64.encode(state, n)
    }
  },
  decode(state) {
    const a = uint8.decode(state)
    if (a <= 0xfc) return a
    if (a === 0xfd) return uint16.decode(state)
    if (a === 0xfe) return uint32.decode(state)
    return uint64.decode(state)
  }
})

const uint8 = (exports.uint8 = {
  preencode(state, n) {
    state.end += 1
  },
  encode(state, n) {
    validateUint(n)
    state.buffer[state.start++] = n
  },
  decode(state) {
    if (state.start >= state.end) throw new Error('Out of bounds')
    return state.buffer[state.start++]
  }
})

const uint16 = (exports.uint16 = {
  preencode(state, n) {
    state.end += 2
  },
  encode(state, n) {
    validateUint(n)
    state.buffer[state.start++] = n
    state.buffer[state.start++] = n >>> 8
  },
  decode(state) {
    if (state.end - state.start < 2) throw new Error('Out of bounds')
    return state.buffer[state.start++] + state.buffer[state.start++] * 0x100
  }
})

const uint24 = (exports.uint24 = {
  preencode(state, n) {
    state.end += 3
  },
  encode(state, n) {
    validateUint(n)
    state.buffer[state.start++] = n
    state.buffer[state.start++] = n >>> 8
    state.buffer[state.start++] = n >>> 16
  },
  decode(state) {
    if (state.end - state.start < 3) throw new Error('Out of bounds')
    return (
      state.buffer[state.start++] +
      state.buffer[state.start++] * 0x100 +
      state.buffer[state.start++] * 0x10000
    )
  }
})

const uint32 = (exports.uint32 = {
  preencode(state, n) {
    state.end += 4
  },
  encode(state, n) {
    validateUint(n)
    state.buffer[state.start++] = n
    state.buffer[state.start++] = n >>> 8
    state.buffer[state.start++] = n >>> 16
    state.buffer[state.start++] = n >>> 24
  },
  decode(state) {
    if (state.end - state.start < 4) throw new Error('Out of bounds')
    return (
      state.buffer[state.start++] +
      state.buffer[state.start++] * 0x100 +
      state.buffer[state.start++] * 0x10000 +
      state.buffer[state.start++] * 0x1000000
    )
  }
})

const uint40 = (exports.uint40 = {
  preencode(state, n) {
    state.end += 5
  },
  encode(state, n) {
    validateUint(n)
    const r = Math.floor(n / 0x100)
    uint8.encode(state, n)
    uint32.encode(state, r)
  },
  decode(state) {
    if (state.end - state.start < 5) throw new Error('Out of bounds')
    return uint8.decode(state) + 0x100 * uint32.decode(state)
  }
})

const uint48 = (exports.uint48 = {
  preencode(state, n) {
    state.end += 6
  },
  encode(state, n) {
    validateUint(n)
    const r = Math.floor(n / 0x10000)
    uint16.encode(state, n)
    uint32.encode(state, r)
  },
  decode(state) {
    if (state.end - state.start < 6) throw new Error('Out of bounds')
    return uint16.decode(state) + 0x10000 * uint32.decode(state)
  }
})

const uint56 = (exports.uint56 = {
  preencode(state, n) {
    state.end += 7
  },
  encode(state, n) {
    validateUint(n)
    const r = Math.floor(n / 0x1000000)
    uint24.encode(state, n)
    uint32.encode(state, r)
  },
  decode(state) {
    if (state.end - state.start < 7) throw new Error('Out of bounds')
    return uint24.decode(state) + 0x1000000 * uint32.decode(state)
  }
})

const uint64 = (exports.uint64 = {
  preencode(state, n) {
    state.end += 8
  },
  encode(state, n) {
    validateUint(n)
    const r = Math.floor(n / 0x100000000)
    uint32.encode(state, n)
    uint32.encode(state, r)
  },
  decode(state) {
    if (state.end - state.start < 8) throw new Error('Out of bounds')
    return uint32.decode(state) + 0x100000000 * uint32.decode(state)
  }
})

const int = (exports.int = zigZagInt(uint))
exports.int8 = zigZagInt(uint8)
exports.int16 = zigZagInt(uint16)
exports.int24 = zigZagInt(uint24)
exports.int32 = zigZagInt(uint32)
exports.int40 = zigZagInt(uint40)
exports.int48 = zigZagInt(uint48)
exports.int56 = zigZagInt(uint56)
exports.int64 = zigZagInt(uint64)

const biguint64 = (exports.biguint64 = {
  preencode(state, n) {
    state.end += 8
  },
  encode(state, n) {
    const view = new DataView(
      state.buffer.buffer,
      state.start + state.buffer.byteOffset,
      8
    )
    view.setBigUint64(0, n, true) // little endian
    state.start += 8
  },
  decode(state) {
    if (state.end - state.start < 8) throw new Error('Out of bounds')
    const view = new DataView(
      state.buffer.buffer,
      state.start + state.buffer.byteOffset,
      8
    )
    const n = view.getBigUint64(0, true) // little endian
    state.start += 8
    return n
  }
})

exports.bigint64 = zigZagBigInt(biguint64)

const biguint = (exports.biguint = {
  preencode(state, n) {
    let len = 0
    for (let m = n; m; m = m >> 64n) len++
    uint.preencode(state, len)
    state.end += 8 * len
  },
  encode(state, n) {
    let len = 0
    for (let m = n; m; m = m >> 64n) len++
    uint.encode(state, len)
    const view = new DataView(
      state.buffer.buffer,
      state.start + state.buffer.byteOffset,
      8 * len
    )
    for (let m = n, i = 0; m; m = m >> 64n, i += 8) {
      view.setBigUint64(i, BigInt.asUintN(64, m), true) // little endian
    }
    state.start += 8 * len
  },
  decode(state) {
    const len = uint.decode(state)
    if (state.end - state.start < 8 * len) throw new Error('Out of bounds')
    const view = new DataView(
      state.buffer.buffer,
      state.start + state.buffer.byteOffset,
      8 * len
    )
    let n = 0n
    for (let i = len - 1; i >= 0; i--)
      n = (n << 64n) + view.getBigUint64(i * 8, true) // little endian
    state.start += 8 * len
    return n
  }
})

exports.bigint = zigZagBigInt(biguint)

exports.lexint = require('./lexint')

exports.float32 = {
  preencode(state, n) {
    state.end += 4
  },
  encode(state, n) {
    const view = new DataView(
      state.buffer.buffer,
      state.start + state.buffer.byteOffset,
      4
    )
    view.setFloat32(0, n, true) // little endian
    state.start += 4
  },
  decode(state) {
    if (state.end - state.start < 4) throw new Error('Out of bounds')
    const view = new DataView(
      state.buffer.buffer,
      state.start + state.buffer.byteOffset,
      4
    )
    const float = view.getFloat32(0, true) // little endian
    state.start += 4
    return float
  }
}

exports.float64 = {
  preencode(state, n) {
    state.end += 8
  },
  encode(state, n) {
    const view = new DataView(
      state.buffer.buffer,
      state.start + state.buffer.byteOffset,
      8
    )
    view.setFloat64(0, n, true) // little endian
    state.start += 8
  },
  decode(state) {
    if (state.end - state.start < 8) throw new Error('Out of bounds')
    const view = new DataView(
      state.buffer.buffer,
      state.start + state.buffer.byteOffset,
      8
    )
    const float = view.getFloat64(0, true) // little endian
    state.start += 8
    return float
  }
}

const buffer = (exports.buffer = {
  preencode(state, b) {
    if (b) uint8array.preencode(state, b)
    else state.end++
  },
  encode(state, b) {
    if (b) uint8array.encode(state, b)
    else state.buffer[state.start++] = 0
  },
  decode(state) {
    const len = uint.decode(state)
    if (len === 0) return null
    if (state.end - state.start < len) throw new Error('Out of bounds')
    return state.buffer.subarray(state.start, (state.start += len))
  }
})

exports.binary = {
  ...buffer,
  preencode(state, b) {
    if (typeof b === 'string') utf8.preencode(state, b)
    else buffer.preencode(state, b)
  },
  encode(state, b) {
    if (typeof b === 'string') utf8.encode(state, b)
    else buffer.encode(state, b)
  }
}

exports.arraybuffer = {
  preencode(state, b) {
    uint.preencode(state, b.byteLength)
    state.end += b.byteLength
  },
  encode(state, b) {
    uint.encode(state, b.byteLength)

    const view = new Uint8Array(b)

    state.buffer.set(view, state.start)
    state.start += b.byteLength
  },
  decode(state) {
    const len = uint.decode(state)

    const b = new ArrayBuffer(len)
    const view = new Uint8Array(b)

    view.set(state.buffer.subarray(state.start, (state.start += len)))

    return b
  }
}

function typedarray(TypedArray, swap) {
  const n = TypedArray.BYTES_PER_ELEMENT

  return {
    preencode(state, b) {
      uint.preencode(state, b.length)
      state.end += b.byteLength
    },
    encode(state, b) {
      uint.encode(state, b.length)

      const view = new Uint8Array(b.buffer, b.byteOffset, b.byteLength)

      if (BE && swap) swap(view)

      state.buffer.set(view, state.start)
      state.start += b.byteLength
    },
    decode(state) {
      const len = uint.decode(state)

      let b = state.buffer.subarray(state.start, (state.start += len * n))
      if (b.byteLength !== len * n) throw new Error('Out of bounds')
      if (b.byteOffset % n !== 0) b = new Uint8Array(b)

      if (BE && swap) swap(b)

      return new TypedArray(b.buffer, b.byteOffset, b.byteLength / n)
    }
  }
}

const uint8array = (exports.uint8array = typedarray(Uint8Array))
exports.uint16array = typedarray(Uint16Array, b4a.swap16)
exports.uint32array = typedarray(Uint32Array, b4a.swap32)

exports.int8array = typedarray(Int8Array)
exports.int16array = typedarray(Int16Array, b4a.swap16)
exports.int32array = typedarray(Int32Array, b4a.swap32)

exports.biguint64array = typedarray(BigUint64Array, b4a.swap64)
exports.bigint64array = typedarray(BigInt64Array, b4a.swap64)

exports.float32array = typedarray(Float32Array, b4a.swap32)
exports.float64array = typedarray(Float64Array, b4a.swap64)

function string(encoding) {
  return {
    preencode(state, s) {
      const len = b4a.byteLength(s, encoding)
      uint.preencode(state, len)
      state.end += len
    },
    encode(state, s) {
      const len = b4a.byteLength(s, encoding)
      uint.encode(state, len)
      b4a.write(state.buffer, s, state.start, encoding)
      state.start += len
    },
    decode(state) {
      const len = uint.decode(state)
      if (state.end - state.start < len) throw new Error('Out of bounds')
      return b4a.toString(
        state.buffer,
        encoding,
        state.start,
        (state.start += len)
      )
    },
    fixed(n) {
      return {
        preencode(state) {
          state.end += n
        },
        encode(state, s) {
          b4a.write(state.buffer, s, state.start, n, encoding)
          state.start += n
        },
        decode(state) {
          if (state.end - state.start < n) throw new Error('Out of bounds')
          return b4a.toString(
            state.buffer,
            encoding,
            state.start,
            (state.start += n)
          )
        }
      }
    }
  }
}

const utf8 = (exports.string = exports.utf8 = string('utf-8'))
exports.ascii = string('ascii')
exports.hex = string('hex')
exports.base64 = string('base64')
exports.ucs2 = exports.utf16le = string('utf16le')

exports.bool = {
  preencode(state, b) {
    state.end++
  },
  encode(state, b) {
    state.buffer[state.start++] = b ? 1 : 0
  },
  decode(state) {
    if (state.start >= state.end) throw Error('Out of bounds')
    return state.buffer[state.start++] === 1
  }
}

const fixed = (exports.fixed = function fixed(n) {
  return {
    preencode(state, s) {
      if (s.byteLength !== n) throw new Error('Incorrect buffer size')
      state.end += n
    },
    encode(state, s) {
      state.buffer.set(s, state.start)
      state.start += n
    },
    decode(state) {
      if (state.end - state.start < n) throw new Error('Out of bounds')
      return state.buffer.subarray(state.start, (state.start += n))
    }
  }
})

exports.fixed32 = fixed(32)
exports.fixed64 = fixed(64)

exports.array = function array(enc) {
  return {
    preencode(state, list) {
      uint.preencode(state, list.length)
      for (let i = 0; i < list.length; i++) enc.preencode(state, list[i])
    },
    encode(state, list) {
      uint.encode(state, list.length)
      for (let i = 0; i < list.length; i++) enc.encode(state, list[i])
    },
    decode(state) {
      const len = uint.decode(state)
      if (len > 0x100000) throw new Error('Array is too big')
      const arr = new Array(len)
      for (let i = 0; i < len; i++) arr[i] = enc.decode(state)
      return arr
    }
  }
}

exports.frame = function frame(enc) {
  const dummy = exports.state()

  return {
    preencode(state, m) {
      const end = state.end
      enc.preencode(state, m)
      uint.preencode(state, state.end - end)
    },
    encode(state, m) {
      dummy.end = 0
      enc.preencode(dummy, m)
      uint.encode(state, dummy.end)
      enc.encode(state, m)
    },
    decode(state) {
      const end = state.end
      const len = uint.decode(state)
      state.end = state.start + len
      const m = enc.decode(state)
      state.start = state.end
      state.end = end
      return m
    }
  }
}

exports.date = {
  preencode(state, d) {
    int.preencode(state, d.getTime())
  },
  encode(state, d) {
    int.encode(state, d.getTime())
  },
  decode(state, d) {
    return new Date(int.decode(state))
  }
}

exports.json = {
  preencode(state, v) {
    utf8.preencode(state, JSON.stringify(v))
  },
  encode(state, v) {
    utf8.encode(state, JSON.stringify(v))
  },
  decode(state) {
    return JSON.parse(utf8.decode(state))
  }
}

exports.ndjson = {
  preencode(state, v) {
    utf8.preencode(state, JSON.stringify(v) + '\n')
  },
  encode(state, v) {
    utf8.encode(state, JSON.stringify(v) + '\n')
  },
  decode(state) {
    return JSON.parse(utf8.decode(state))
  }
}

// simple helper for when you want to just express nothing
exports.none = {
  preencode(state, n) {
    // do nothing
  },
  encode(state, n) {
    // do nothing
  },
  decode(state) {
    return null
  }
}

// "any" encoders here for helping just structure any object without schematising it

const anyArray = {
  preencode(state, arr) {
    uint.preencode(state, arr.length)
    for (let i = 0; i < arr.length; i++) {
      any.preencode(state, arr[i])
    }
  },
  encode(state, arr) {
    uint.encode(state, arr.length)
    for (let i = 0; i < arr.length; i++) {
      any.encode(state, arr[i])
    }
  },
  decode(state) {
    const arr = []
    let len = uint.decode(state)
    while (len-- > 0) {
      arr.push(any.decode(state))
    }
    return arr
  }
}

const anyObject = {
  preencode(state, o) {
    const keys = Object.keys(o)
    uint.preencode(state, keys.length)
    for (const key of keys) {
      utf8.preencode(state, key)
      any.preencode(state, o[key])
    }
  },
  encode(state, o) {
    const keys = Object.keys(o)
    uint.encode(state, keys.length)
    for (const key of keys) {
      utf8.encode(state, key)
      any.encode(state, o[key])
    }
  },
  decode(state) {
    let len = uint.decode(state)
    const o = {}
    while (len-- > 0) {
      const key = utf8.decode(state)
      o[key] = any.decode(state)
    }
    return o
  }
}

const anyTypes = [
  exports.none,
  exports.bool,
  exports.string,
  exports.buffer,
  exports.uint,
  exports.int,
  exports.float64,
  anyArray,
  anyObject,
  exports.date
]

const any = (exports.any = {
  preencode(state, o) {
    const t = getType(o)
    uint.preencode(state, t)
    anyTypes[t].preencode(state, o)
  },
  encode(state, o) {
    const t = getType(o)
    uint.encode(state, t)
    anyTypes[t].encode(state, o)
  },
  decode(state) {
    const t = uint.decode(state)
    if (t >= anyTypes.length) throw new Error('Unknown type: ' + t)
    return anyTypes[t].decode(state)
  }
})

const port = (exports.port = uint16)

const address = (host, family) => {
  return {
    preencode(state, m) {
      host.preencode(state, m.host)
      port.preencode(state, m.port)
    },
    encode(state, m) {
      host.encode(state, m.host)
      port.encode(state, m.port)
    },
    decode(state) {
      return {
        host: host.decode(state),
        family,
        port: port.decode(state)
      }
    }
  }
}

const ipv4 = (exports.ipv4 = {
  preencode(state) {
    state.end += 4
  },
  encode(state, string) {
    const start = state.start
    const end = start + 4

    let i = 0

    while (i < string.length) {
      let n = 0
      let c

      while (
        i < string.length &&
        (c = string.charCodeAt(i++)) !== /* . */ 0x2e
      ) {
        n = n * 10 + (c - /* 0 */ 0x30)
      }

      state.buffer[state.start++] = n
    }

    state.start = end
  },
  decode(state) {
    if (state.end - state.start < 4) throw new Error('Out of bounds')
    return (
      state.buffer[state.start++] +
      '.' +
      state.buffer[state.start++] +
      '.' +
      state.buffer[state.start++] +
      '.' +
      state.buffer[state.start++]
    )
  }
})

exports.ipv4Address = address(ipv4, 4)

const ipv6 = (exports.ipv6 = {
  preencode(state) {
    state.end += 16
  },
  encode(state, string) {
    const start = state.start
    const end = start + 16

    let i = 0
    let split = null

    while (i < string.length) {
      let n = 0
      let c

      while (
        i < string.length &&
        (c = string.charCodeAt(i++)) !== /* : */ 0x3a
      ) {
        if (c >= 0x30 && c <= 0x39) n = n * 0x10 + (c - /* 0 */ 0x30)
        else if (c >= 0x41 && c <= 0x46) n = n * 0x10 + (c - /* A */ 0x41 + 10)
        else if (c >= 0x61 && c <= 0x66) n = n * 0x10 + (c - /* a */ 0x61 + 10)
      }

      state.buffer[state.start++] = n >>> 8
      state.buffer[state.start++] = n

      if (i < string.length && string.charCodeAt(i) === /* : */ 0x3a) {
        i++
        split = state.start
      }
    }

    if (split !== null) {
      const offset = end - state.start
      state.buffer
        .copyWithin(split + offset, split)
        .fill(0, split, split + offset)
    }

    state.start = end
  },
  decode(state) {
    if (state.end - state.start < 16) throw new Error('Out of bounds')
    return (
      (
        state.buffer[state.start++] * 256 +
        state.buffer[state.start++]
      ).toString(16) +
      ':' +
      (
        state.buffer[state.start++] * 256 +
        state.buffer[state.start++]
      ).toString(16) +
      ':' +
      (
        state.buffer[state.start++] * 256 +
        state.buffer[state.start++]
      ).toString(16) +
      ':' +
      (
        state.buffer[state.start++] * 256 +
        state.buffer[state.start++]
      ).toString(16) +
      ':' +
      (
        state.buffer[state.start++] * 256 +
        state.buffer[state.start++]
      ).toString(16) +
      ':' +
      (
        state.buffer[state.start++] * 256 +
        state.buffer[state.start++]
      ).toString(16) +
      ':' +
      (
        state.buffer[state.start++] * 256 +
        state.buffer[state.start++]
      ).toString(16) +
      ':' +
      (
        state.buffer[state.start++] * 256 +
        state.buffer[state.start++]
      ).toString(16)
    )
  }
})

exports.ipv6Address = address(ipv6, 6)

const ip = (exports.ip = {
  preencode(state, string) {
    const family = string.includes(':') ? 6 : 4
    uint8.preencode(state, family)
    if (family === 4) ipv4.preencode(state)
    else ipv6.preencode(state)
  },
  encode(state, string) {
    const family = string.includes(':') ? 6 : 4
    uint8.encode(state, family)
    if (family === 4) ipv4.encode(state, string)
    else ipv6.encode(state, string)
  },
  decode(state) {
    const family = uint8.decode(state)
    if (family === 4) return ipv4.decode(state)
    else return ipv6.decode(state)
  }
})

exports.ipAddress = {
  preencode(state, m) {
    ip.preencode(state, m.host)
    port.preencode(state, m.port)
  },
  encode(state, m) {
    ip.encode(state, m.host)
    port.encode(state, m.port)
  },
  decode(state) {
    const family = uint8.decode(state)
    return {
      host: family === 4 ? ipv4.decode(state) : ipv6.decode(state),
      family,
      port: port.decode(state)
    }
  }
}

const record = (exports.record = function (keyEncoding, valueEncoding) {
  return {
    preencode(state, v) {
      const keys = Object.keys(v)
      uint.preencode(state, keys.length)
      for (const k of keys) {
        keyEncoding.preencode(state, k)
        valueEncoding.preencode(state, v[k])
      }
    },
    encode(state, v) {
      const keys = Object.keys(v)
      uint.encode(state, keys.length)
      for (const k of keys) {
        keyEncoding.encode(state, k)
        valueEncoding.encode(state, v[k])
      }
    },
    decode(state) {
      const out = Object.create(null)
      const keys = uint.decode(state)
      for (let i = 0; i < keys; i++) {
        out[keyEncoding.decode(state)] = valueEncoding.decode(state)
      }
      return out
    }
  }
})

exports.stringRecord = record(utf8, utf8)

function getType(o) {
  if (o === null || o === undefined) return 0
  if (typeof o === 'boolean') return 1
  if (typeof o === 'string') return 2
  if (b4a.isBuffer(o)) return 3
  if (typeof o === 'number') {
    if (Number.isInteger(o)) return o >= 0 ? 4 : 5
    return 6
  }
  if (Array.isArray(o)) return 7
  if (o instanceof Date) return 9
  if (typeof o === 'object') return 8

  throw new Error('Unsupported type for ' + o)
}

exports.from = function from(enc) {
  if (typeof enc === 'string') return fromNamed(enc)
  if (enc.preencode) return enc
  if (enc.encodingLength) return fromAbstractEncoder(enc)
  return fromCodec(enc)
}

function fromNamed(enc) {
  switch (enc) {
    case 'ascii':
      return raw.ascii
    case 'utf-8':
    case 'utf8':
      return raw.utf8
    case 'hex':
      return raw.hex
    case 'base64':
      return raw.base64
    case 'utf16-le':
    case 'utf16le':
    case 'ucs-2':
    case 'ucs2':
      return raw.ucs2
    case 'ndjson':
      return raw.ndjson
    case 'json':
      return raw.json
    case 'binary':
    default:
      return raw.binary
  }
}

function fromCodec(enc) {
  let tmpM = null
  let tmpBuf = null

  return {
    preencode(state, m) {
      tmpM = m
      tmpBuf = enc.encode(m)
      state.end += tmpBuf.byteLength
    },
    encode(state, m) {
      raw.encode(state, m === tmpM ? tmpBuf : enc.encode(m))
      tmpM = tmpBuf = null
    },
    decode(state) {
      return enc.decode(raw.decode(state))
    }
  }
}

function fromAbstractEncoder(enc) {
  return {
    preencode(state, m) {
      state.end += enc.encodingLength(m)
    },
    encode(state, m) {
      enc.encode(m, state.buffer, state.start)
      state.start += enc.encode.bytes
    },
    decode(state) {
      const m = enc.decode(state.buffer, state.start, state.end)
      state.start += enc.decode.bytes
      return m
    }
  }
}

exports.encode = function encode(enc, m) {
  const state = exports.state()
  enc.preencode(state, m)
  state.buffer = b4a.allocUnsafe(state.end)
  enc.encode(state, m)
  return state.buffer
}

exports.decode = function decode(enc, buffer) {
  return enc.decode(exports.state(0, buffer.byteLength, buffer))
}

function zigZagInt(enc) {
  return {
    preencode(state, n) {
      enc.preencode(state, zigZagEncodeInt(n))
    },
    encode(state, n) {
      enc.encode(state, zigZagEncodeInt(n))
    },
    decode(state) {
      return zigZagDecodeInt(enc.decode(state))
    }
  }
}

function zigZagDecodeInt(n) {
  return n === 0 ? n : (n & 1) === 0 ? n / 2 : -(n + 1) / 2
}

function zigZagEncodeInt(n) {
  // 0, -1, 1, -2, 2, ...
  return n < 0 ? 2 * -n - 1 : n === 0 ? 0 : 2 * n
}

function zigZagBigInt(enc) {
  return {
    preencode(state, n) {
      enc.preencode(state, zigZagEncodeBigInt(n))
    },
    encode(state, n) {
      enc.encode(state, zigZagEncodeBigInt(n))
    },
    decode(state) {
      return zigZagDecodeBigInt(enc.decode(state))
    }
  }
}

function zigZagDecodeBigInt(n) {
  return n === 0n ? n : (n & 1n) === 0n ? n / 2n : -(n + 1n) / 2n
}

function zigZagEncodeBigInt(n) {
  // 0, -1, 1, -2, 2, ...
  return n < 0n ? 2n * -n - 1n : n === 0n ? 0n : 2n * n
}

function validateUint(n) {
  if (n >= 0 === false /* Handles NaN as well */)
    throw new Error('uint must be positive')
}
module.exports = {
  preencode,
  encode,
  decode
}

function preencode(state, num) {
  if (num < 251) {
    state.end++
  } else if (num < 256) {
    state.end += 2
  } else if (num < 0x10000) {
    state.end += 3
  } else if (num < 0x1000000) {
    state.end += 4
  } else if (num < 0x100000000) {
    state.end += 5
  } else {
    state.end++
    const exp = Math.floor(Math.log(num) / Math.log(2)) - 32
    preencode(state, exp)
    state.end += 6
  }
}

function encode(state, num) {
  const max = 251
  const x = num - max

  if (num < max) {
    state.buffer[state.start++] = num
  } else if (num < 256) {
    state.buffer[state.start++] = max
    state.buffer[state.start++] = x
  } else if (num < 0x10000) {
    state.buffer[state.start++] = max + 1
    state.buffer[state.start++] = (x >> 8) & 0xff
    state.buffer[state.start++] = x & 0xff
  } else if (num < 0x1000000) {
    state.buffer[state.start++] = max + 2
    state.buffer[state.start++] = x >> 16
    state.buffer[state.start++] = (x >> 8) & 0xff
    state.buffer[state.start++] = x & 0xff
  } else if (num < 0x100000000) {
    state.buffer[state.start++] = max + 3
    state.buffer[state.start++] = x >> 24
    state.buffer[state.start++] = (x >> 16) & 0xff
    state.buffer[state.start++] = (x >> 8) & 0xff
    state.buffer[state.start++] = x & 0xff
  } else {
    // need to use Math here as bitwise ops are 32 bit
    const exp = Math.floor(Math.log(x) / Math.log(2)) - 32
    state.buffer[state.start++] = 0xff

    encode(state, exp)
    const rem = x / Math.pow(2, exp - 11)

    for (let i = 5; i >= 0; i--) {
      state.buffer[state.start++] = (rem / Math.pow(2, 8 * i)) & 0xff
    }
  }
}

function decode(state) {
  const max = 251

  if (state.end - state.start < 1) throw new Error('Out of bounds')

  const flag = state.buffer[state.start++]

  if (flag < max) return flag

  if (state.end - state.start < flag - max + 1) {
    throw new Error('Out of bounds.')
  }

  if (flag < 252) {
    return state.buffer[state.start++] + max
  }

  if (flag < 253) {
    return (
      (state.buffer[state.start++] << 8) + state.buffer[state.start++] + max
    )
  }

  if (flag < 254) {
    return (
      (state.buffer[state.start++] << 16) +
      (state.buffer[state.start++] << 8) +
      state.buffer[state.start++] +
      max
    )
  }

  // << 24 result may be interpreted as negative
  if (flag < 255) {
    return (
      state.buffer[state.start++] * 0x1000000 +
      (state.buffer[state.start++] << 16) +
      (state.buffer[state.start++] << 8) +
      state.buffer[state.start++] +
      max
    )
  }

  const exp = decode(state)

  if (state.end - state.start < 6) throw new Error('Out of bounds')

  let rem = 0
  for (let i = 5; i >= 0; i--) {
    rem += state.buffer[state.start++] * Math.pow(2, 8 * i)
  }

  return rem * Math.pow(2, exp - 11) + max
}
{
  "name": "compact-encoding",
  "version": "2.19.2",
  "description": "A series of compact encoding schemes for building small and fast parsers and serializers",
  "main": "index.js",
  "dependencies": {
    "b4a": "^1.3.0"
  },
  "devDependencies": {
    "brittle": "^3.0.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^1.0.0"
  },
  "scripts": {
    "format": "prettier . --write",
    "test": "prettier . --check && brittle test.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/holepunchto/compact-encoding.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/compact-encoding/issues"
  },
  "homepage": "https://github.com/holepunchto/compact-encoding"
}
const b4a = require('b4a')

const { BE } = require('./endian')

exports = module.exports = {
  preencode(state, b) {
    state.end += b.byteLength
  },
  encode(state, b) {
    state.buffer.set(b, state.start)
    state.start += b.byteLength
  },
  decode(state) {
    const b = state.buffer.subarray(state.start, state.end)
    state.start = state.end
    return b
  }
}

const buffer = (exports.buffer = {
  preencode(state, b) {
    if (b) uint8array.preencode(state, b)
    else state.end++
  },
  encode(state, b) {
    if (b) uint8array.encode(state, b)
    else state.buffer[state.start++] = 0
  },
  decode(state) {
    const b = state.buffer.subarray(state.start)
    if (b.byteLength === 0) return null
    state.start = state.end
    return b
  }
})

exports.binary = {
  ...buffer,
  preencode(state, b) {
    if (typeof b === 'string') utf8.preencode(state, b)
    else buffer.preencode(state, b)
  },
  encode(state, b) {
    if (typeof b === 'string') utf8.encode(state, b)
    else buffer.encode(state, b)
  }
}

exports.arraybuffer = {
  preencode(state, b) {
    state.end += b.byteLength
  },
  encode(state, b) {
    const view = new Uint8Array(b)

    state.buffer.set(view, state.start)
    state.start += b.byteLength
  },
  decode(state) {
    const b = new ArrayBuffer(state.end - state.start)
    const view = new Uint8Array(b)

    view.set(state.buffer.subarray(state.start))

    state.start = state.end

    return b
  }
}

function typedarray(TypedArray, swap) {
  const n = TypedArray.BYTES_PER_ELEMENT

  return {
    preencode(state, b) {
      state.end += b.byteLength
    },
    encode(state, b) {
      const view = new Uint8Array(b.buffer, b.byteOffset, b.byteLength)

      if (BE && swap) swap(view)

      state.buffer.set(view, state.start)
      state.start += b.byteLength
    },
    decode(state) {
      let b = state.buffer.subarray(state.start)
      if (b.byteOffset % n !== 0) b = new Uint8Array(b)

      if (BE && swap) swap(b)

      state.start = state.end

      return new TypedArray(b.buffer, b.byteOffset, b.byteLength / n)
    }
  }
}

const uint8array = (exports.uint8array = typedarray(Uint8Array))
exports.uint16array = typedarray(Uint16Array, b4a.swap16)
exports.uint32array = typedarray(Uint32Array, b4a.swap32)

exports.int8array = typedarray(Int8Array)
exports.int16array = typedarray(Int16Array, b4a.swap16)
exports.int32array = typedarray(Int32Array, b4a.swap32)

exports.biguint64array = typedarray(BigUint64Array, b4a.swap64)
exports.bigint64array = typedarray(BigInt64Array, b4a.swap64)

exports.float32array = typedarray(Float32Array, b4a.swap32)
exports.float64array = typedarray(Float64Array, b4a.swap64)

function string(encoding) {
  return {
    preencode(state, s) {
      state.end += b4a.byteLength(s, encoding)
    },
    encode(state, s) {
      state.start += b4a.write(state.buffer, s, state.start, encoding)
    },
    decode(state) {
      const s = b4a.toString(state.buffer, encoding, state.start)
      state.start = state.end
      return s
    }
  }
}

const utf8 = (exports.string = exports.utf8 = string('utf-8'))
exports.ascii = string('ascii')
exports.hex = string('hex')
exports.base64 = string('base64')
exports.ucs2 = exports.utf16le = string('utf16le')

exports.array = function array(enc) {
  return {
    preencode(state, list) {
      for (const value of list) enc.preencode(state, value)
    },
    encode(state, list) {
      for (const value of list) enc.encode(state, value)
    },
    decode(state) {
      const arr = []
      while (state.start < state.end) arr.push(enc.decode(state))
      return arr
    }
  }
}

exports.json = {
  preencode(state, v) {
    utf8.preencode(state, JSON.stringify(v))
  },
  encode(state, v) {
    utf8.encode(state, JSON.stringify(v))
  },
  decode(state) {
    return JSON.parse(utf8.decode(state))
  }
}

exports.ndjson = {
  preencode(state, v) {
    utf8.preencode(state, JSON.stringify(v) + '\n')
  },
  encode(state, v) {
    utf8.encode(state, JSON.stringify(v) + '\n')
  },
  decode(state) {
    return JSON.parse(utf8.decode(state))
  }
}
{
  "name": "hyperdht",
  "version": "6.31.0",
  "description": "The DHT powering Hyperswarm",
  "main": "index.js",
  "browser": "browser.js",
  "bin": {
    "hyperdht": "./bin.js"
  },
  "files": [
    "index.js",
    "browser.js",
    "testnet.js",
    "bin.js",
    "lib/**.js"
  ],
  "imports": {
    "events": {
      "bare": "bare-events",
      "default": "events"
    },
    "child_process": {
      "bare": "bare-node-child-process",
      "default": "child_process"
    }
  },
  "dependencies": {
    "@hyperswarm/secret-stream": "^6.6.2",
    "b4a": "^1.3.1",
    "bare-events": "^2.2.0",
    "blind-relay": "^1.3.0",
    "bogon": "^1.0.0",
    "compact-encoding": "^2.4.1",
    "dht-rpc": "^6.15.1",
    "hypercore-crypto": "^3.3.0",
    "hypercore-id-encoding": "^1.2.0",
    "hyperdht-address": "^1.0.1",
    "noise-curve-ed": "^2.0.0",
    "noise-handshake": "^4.0.0",
    "record-cache": "^1.1.1",
    "safety-catch": "^1.0.1",
    "signal-promise": "^1.0.3",
    "sodium-universal": "^5.0.1",
    "streamx": "^2.16.1",
    "unslab": "^1.3.0",
    "xache": "^1.1.0"
  },
  "devDependencies": {
    "bare-node-child-process": "^1.0.1",
    "brittle": "^3.0.0",
    "graceful-goodbye": "^1.3.0",
    "newline-decoder": "^1.0.2",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^2.0.0"
  },
  "scripts": {
    "format": "prettier --write .",
    "test": "prettier --check . && node test/all.js",
    "test:bare": "bare test/all.js",
    "test:generate": "brittle -r test/all.js test/*.js",
    "integration": "brittle test/integration/*.js",
    "end-to-end": "brittle test/end-to-end/*.js"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "directories": {
    "lib": "lib",
    "test": "test"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/hyperdht.git"
  },
  "keywords": [],
  "bugs": {
    "url": "https://github.com/holepunchto/hyperdht/issues"
  },
  "homepage": "https://github.com/holepunchto/hyperdht#readme"
}
const LE = (exports.LE =
  new Uint8Array(new Uint16Array([0xff]).buffer)[0] === 0xff)

exports.BE = !LE
const b4a = require('b4a')

const { BE } = require('./endian')

exports.state = function (start = 0, end = 0, buffer = null) {
  return { start, end, buffer }
}

const raw = (exports.raw = require('./raw'))

const uint = (exports.uint = {
  preencode(state, n) {
    state.end += n <= 0xfc ? 1 : n <= 0xffff ? 3 : n <= 0xffffffff ? 5 : 9
  },
  encode(state, n) {
    if (n <= 0xfc) uint8.encode(state, n)
    else if (n <= 0xffff) {
      state.buffer[state.start++] = 0xfd
      uint16.encode(state, n)
    } else if (n <= 0xffffffff) {
      state.buffer[state.start++] = 0xfe
      uint32.encode(state, n)
    } else {
      state.buffer[state.start++] = 0xff
      uint64.encode(state, n)
    }
  },
  decode(state) {
    const a = uint8.decode(state)
    if (a <= 0xfc) return a
    if (a === 0xfd) return uint16.decode(state)
    if (a === 0xfe) return uint32.decode(state)
    return uint64.decode(state)
  }
})

const uint8 = (exports.uint8 = {
  preencode(state, n) {
    state.end += 1
  },
  encode(state, n) {
    validateUint(n)
    state.buffer[state.start++] = n
  },
  decode(state) {
    if (state.start >= state.end) throw new Error('Out of bounds')
    return state.buffer[state.start++]
  }
})

const uint16 = (exports.uint16 = {
  preencode(state, n) {
    state.end += 2
  },
  encode(state, n) {
    validateUint(n)
    state.buffer[state.start++] = n
    state.buffer[state.start++] = n >>> 8
  },
  decode(state) {
    if (state.end - state.start < 2) throw new Error('Out of bounds')
    return state.buffer[state.start++] + state.buffer[state.start++] * 0x100
  }
})

const uint24 = (exports.uint24 = {
  preencode(state, n) {
    state.end += 3
  },
  encode(state, n) {
    validateUint(n)
    state.buffer[state.start++] = n
    state.buffer[state.start++] = n >>> 8
    state.buffer[state.start++] = n >>> 16
  },
  decode(state) {
    if (state.end - state.start < 3) throw new Error('Out of bounds')
    return (
      state.buffer[state.start++] +
      state.buffer[state.start++] * 0x100 +
      state.buffer[state.start++] * 0x10000
    )
  }
})

const uint32 = (exports.uint32 = {
  preencode(state, n) {
    state.end += 4
  },
  encode(state, n) {
    validateUint(n)
    state.buffer[state.start++] = n
    state.buffer[state.start++] = n >>> 8
    state.buffer[state.start++] = n >>> 16
    state.buffer[state.start++] = n >>> 24
  },
  decode(state) {
    if (state.end - state.start < 4) throw new Error('Out of bounds')
    return (
      state.buffer[state.start++] +
      state.buffer[state.start++] * 0x100 +
      state.buffer[state.start++] * 0x10000 +
      state.buffer[state.start++] * 0x1000000
    )
  }
})

const uint40 = (exports.uint40 = {
  preencode(state, n) {
    state.end += 5
  },
  encode(state, n) {
    validateUint(n)
    const r = Math.floor(n / 0x100)
    uint8.encode(state, n)
    uint32.encode(state, r)
  },
  decode(state) {
    if (state.end - state.start < 5) throw new Error('Out of bounds')
    return uint8.decode(state) + 0x100 * uint32.decode(state)
  }
})

const uint48 = (exports.uint48 = {
  preencode(state, n) {
    state.end += 6
  },
  encode(state, n) {
    validateUint(n)
    const r = Math.floor(n / 0x10000)
    uint16.encode(state, n)
    uint32.encode(state, r)
  },
  decode(state) {
    if (state.end - state.start < 6) throw new Error('Out of bounds')
    return uint16.decode(state) + 0x10000 * uint32.decode(state)
  }
})

const uint56 = (exports.uint56 = {
  preencode(state, n) {
    state.end += 7
  },
  encode(state, n) {
    validateUint(n)
    const r = Math.floor(n / 0x1000000)
    uint24.encode(state, n)
    uint32.encode(state, r)
  },
  decode(state) {
    if (state.end - state.start < 7) throw new Error('Out of bounds')
    return uint24.decode(state) + 0x1000000 * uint32.decode(state)
  }
})

const uint64 = (exports.uint64 = {
  preencode(state, n) {
    state.end += 8
  },
  encode(state, n) {
    validateUint(n)
    const r = Math.floor(n / 0x100000000)
    uint32.encode(state, n)
    uint32.encode(state, r)
  },
  decode(state) {
    if (state.end - state.start < 8) throw new Error('Out of bounds')
    return uint32.decode(state) + 0x100000000 * uint32.decode(state)
  }
})

const int = (exports.int = zigZagInt(uint))
exports.int8 = zigZagInt(uint8)
exports.int16 = zigZagInt(uint16)
exports.int24 = zigZagInt(uint24)
exports.int32 = zigZagInt(uint32)
exports.int40 = zigZagInt(uint40)
exports.int48 = zigZagInt(uint48)
exports.int56 = zigZagInt(uint56)
exports.int64 = zigZagInt(uint64)

const biguint64 = (exports.biguint64 = {
  preencode(state, n) {
    state.end += 8
  },
  encode(state, n) {
    const view = new DataView(
      state.buffer.buffer,
      state.start + state.buffer.byteOffset,
      8
    )
    view.setBigUint64(0, n, true) // little endian
    state.start += 8
  },
  decode(state) {
    if (state.end - state.start < 8) throw new Error('Out of bounds')
    const view = new DataView(
      state.buffer.buffer,
      state.start + state.buffer.byteOffset,
      8
    )
    const n = view.getBigUint64(0, true) // little endian
    state.start += 8
    return n
  }
})

exports.bigint64 = zigZagBigInt(biguint64)

const biguint = (exports.biguint = {
  preencode(state, n) {
    let len = 0
    for (let m = n; m; m = m >> 64n) len++
    uint.preencode(state, len)
    state.end += 8 * len
  },
  encode(state, n) {
    let len = 0
    for (let m = n; m; m = m >> 64n) len++
    uint.encode(state, len)
    const view = new DataView(
      state.buffer.buffer,
      state.start + state.buffer.byteOffset,
      8 * len
    )
    for (let m = n, i = 0; m; m = m >> 64n, i += 8) {
      view.setBigUint64(i, BigInt.asUintN(64, m), true) // little endian
    }
    state.start += 8 * len
  },
  decode(state) {
    const len = uint.decode(state)
    if (state.end - state.start < 8 * len) throw new Error('Out of bounds')
    const view = new DataView(
      state.buffer.buffer,
      state.start + state.buffer.byteOffset,
      8 * len
    )
    let n = 0n
    for (let i = len - 1; i >= 0; i--)
      n = (n << 64n) + view.getBigUint64(i * 8, true) // little endian
    state.start += 8 * len
    return n
  }
})

exports.bigint = zigZagBigInt(biguint)

exports.lexint = require('./lexint')

exports.float32 = {
  preencode(state, n) {
    state.end += 4
  },
  encode(state, n) {
    const view = new DataView(
      state.buffer.buffer,
      state.start + state.buffer.byteOffset,
      4
    )
    view.setFloat32(0, n, true) // little endian
    state.start += 4
  },
  decode(state) {
    if (state.end - state.start < 4) throw new Error('Out of bounds')
    const view = new DataView(
      state.buffer.buffer,
      state.start + state.buffer.byteOffset,
      4
    )
    const float = view.getFloat32(0, true) // little endian
    state.start += 4
    return float
  }
}

exports.float64 = {
  preencode(state, n) {
    state.end += 8
  },
  encode(state, n) {
    const view = new DataView(
      state.buffer.buffer,
      state.start + state.buffer.byteOffset,
      8
    )
    view.setFloat64(0, n, true) // little endian
    state.start += 8
  },
  decode(state) {
    if (state.end - state.start < 8) throw new Error('Out of bounds')
    const view = new DataView(
      state.buffer.buffer,
      state.start + state.buffer.byteOffset,
      8
    )
    const float = view.getFloat64(0, true) // little endian
    state.start += 8
    return float
  }
}

const buffer = (exports.buffer = {
  preencode(state, b) {
    if (b) uint8array.preencode(state, b)
    else state.end++
  },
  encode(state, b) {
    if (b) uint8array.encode(state, b)
    else state.buffer[state.start++] = 0
  },
  decode(state) {
    const len = uint.decode(state)
    if (len === 0) return null
    if (state.end - state.start < len) throw new Error('Out of bounds')
    return state.buffer.subarray(state.start, (state.start += len))
  }
})

exports.binary = {
  ...buffer,
  preencode(state, b) {
    if (typeof b === 'string') utf8.preencode(state, b)
    else buffer.preencode(state, b)
  },
  encode(state, b) {
    if (typeof b === 'string') utf8.encode(state, b)
    else buffer.encode(state, b)
  }
}

exports.arraybuffer = {
  preencode(state, b) {
    uint.preencode(state, b.byteLength)
    state.end += b.byteLength
  },
  encode(state, b) {
    uint.encode(state, b.byteLength)

    const view = new Uint8Array(b)

    state.buffer.set(view, state.start)
    state.start += b.byteLength
  },
  decode(state) {
    const len = uint.decode(state)

    const b = new ArrayBuffer(len)
    const view = new Uint8Array(b)

    view.set(state.buffer.subarray(state.start, (state.start += len)))

    return b
  }
}

function typedarray(TypedArray, swap) {
  const n = TypedArray.BYTES_PER_ELEMENT

  return {
    preencode(state, b) {
      uint.preencode(state, b.length)
      state.end += b.byteLength
    },
    encode(state, b) {
      uint.encode(state, b.length)

      const view = new Uint8Array(b.buffer, b.byteOffset, b.byteLength)

      if (BE && swap) swap(view)

      state.buffer.set(view, state.start)
      state.start += b.byteLength
    },
    decode(state) {
      const len = uint.decode(state)

      let b = state.buffer.subarray(state.start, (state.start += len * n))
      if (b.byteLength !== len * n) throw new Error('Out of bounds')
      if (b.byteOffset % n !== 0) b = new Uint8Array(b)

      if (BE && swap) swap(b)

      return new TypedArray(b.buffer, b.byteOffset, b.byteLength / n)
    }
  }
}

const uint8array = (exports.uint8array = typedarray(Uint8Array))
exports.uint16array = typedarray(Uint16Array, b4a.swap16)
exports.uint32array = typedarray(Uint32Array, b4a.swap32)

exports.int8array = typedarray(Int8Array)
exports.int16array = typedarray(Int16Array, b4a.swap16)
exports.int32array = typedarray(Int32Array, b4a.swap32)

exports.biguint64array = typedarray(BigUint64Array, b4a.swap64)
exports.bigint64array = typedarray(BigInt64Array, b4a.swap64)

exports.float32array = typedarray(Float32Array, b4a.swap32)
exports.float64array = typedarray(Float64Array, b4a.swap64)

function string(encoding) {
  return {
    preencode(state, s) {
      const len = b4a.byteLength(s, encoding)
      uint.preencode(state, len)
      state.end += len
    },
    encode(state, s) {
      const len = b4a.byteLength(s, encoding)
      uint.encode(state, len)
      b4a.write(state.buffer, s, state.start, encoding)
      state.start += len
    },
    decode(state) {
      const len = uint.decode(state)
      if (state.end - state.start < len) throw new Error('Out of bounds')
      return b4a.toString(
        state.buffer,
        encoding,
        state.start,
        (state.start += len)
      )
    },
    fixed(n) {
      return {
        preencode(state) {
          state.end += n
        },
        encode(state, s) {
          b4a.write(state.buffer, s, state.start, n, encoding)
          state.start += n
        },
        decode(state) {
          if (state.end - state.start < n) throw new Error('Out of bounds')
          return b4a.toString(
            state.buffer,
            encoding,
            state.start,
            (state.start += n)
          )
        }
      }
    }
  }
}

const utf8 = (exports.string = exports.utf8 = string('utf-8'))
exports.ascii = string('ascii')
exports.hex = string('hex')
exports.base64 = string('base64')
exports.ucs2 = exports.utf16le = string('utf16le')

exports.bool = {
  preencode(state, b) {
    state.end++
  },
  encode(state, b) {
    state.buffer[state.start++] = b ? 1 : 0
  },
  decode(state) {
    if (state.start >= state.end) throw Error('Out of bounds')
    return state.buffer[state.start++] === 1
  }
}

const fixed = (exports.fixed = function fixed(n) {
  return {
    preencode(state, s) {
      if (s.byteLength !== n) throw new Error('Incorrect buffer size')
      state.end += n
    },
    encode(state, s) {
      state.buffer.set(s, state.start)
      state.start += n
    },
    decode(state) {
      if (state.end - state.start < n) throw new Error('Out of bounds')
      return state.buffer.subarray(state.start, (state.start += n))
    }
  }
})

exports.fixed32 = fixed(32)
exports.fixed64 = fixed(64)

exports.array = function array(enc) {
  return {
    preencode(state, list) {
      uint.preencode(state, list.length)
      for (let i = 0; i < list.length; i++) enc.preencode(state, list[i])
    },
    encode(state, list) {
      uint.encode(state, list.length)
      for (let i = 0; i < list.length; i++) enc.encode(state, list[i])
    },
    decode(state) {
      const len = uint.decode(state)
      if (len > 0x100000) throw new Error('Array is too big')
      const arr = new Array(len)
      for (let i = 0; i < len; i++) arr[i] = enc.decode(state)
      return arr
    }
  }
}

exports.frame = function frame(enc) {
  const dummy = exports.state()

  return {
    preencode(state, m) {
      const end = state.end
      enc.preencode(state, m)
      uint.preencode(state, state.end - end)
    },
    encode(state, m) {
      dummy.end = 0
      enc.preencode(dummy, m)
      uint.encode(state, dummy.end)
      enc.encode(state, m)
    },
    decode(state) {
      const end = state.end
      const len = uint.decode(state)
      state.end = state.start + len
      const m = enc.decode(state)
      state.start = state.end
      state.end = end
      return m
    }
  }
}

exports.date = {
  preencode(state, d) {
    int.preencode(state, d.getTime())
  },
  encode(state, d) {
    int.encode(state, d.getTime())
  },
  decode(state, d) {
    return new Date(int.decode(state))
  }
}

exports.json = {
  preencode(state, v) {
    utf8.preencode(state, JSON.stringify(v))
  },
  encode(state, v) {
    utf8.encode(state, JSON.stringify(v))
  },
  decode(state) {
    return JSON.parse(utf8.decode(state))
  }
}

exports.ndjson = {
  preencode(state, v) {
    utf8.preencode(state, JSON.stringify(v) + '\n')
  },
  encode(state, v) {
    utf8.encode(state, JSON.stringify(v) + '\n')
  },
  decode(state) {
    return JSON.parse(utf8.decode(state))
  }
}

// simple helper for when you want to just express nothing
exports.none = {
  preencode(state, n) {
    // do nothing
  },
  encode(state, n) {
    // do nothing
  },
  decode(state) {
    return null
  }
}

// "any" encoders here for helping just structure any object without schematising it

const anyArray = {
  preencode(state, arr) {
    uint.preencode(state, arr.length)
    for (let i = 0; i < arr.length; i++) {
      any.preencode(state, arr[i])
    }
  },
  encode(state, arr) {
    uint.encode(state, arr.length)
    for (let i = 0; i < arr.length; i++) {
      any.encode(state, arr[i])
    }
  },
  decode(state) {
    const arr = []
    let len = uint.decode(state)
    while (len-- > 0) {
      arr.push(any.decode(state))
    }
    return arr
  }
}

const anyObject = {
  preencode(state, o) {
    const keys = Object.keys(o)
    uint.preencode(state, keys.length)
    for (const key of keys) {
      utf8.preencode(state, key)
      any.preencode(state, o[key])
    }
  },
  encode(state, o) {
    const keys = Object.keys(o)
    uint.encode(state, keys.length)
    for (const key of keys) {
      utf8.encode(state, key)
      any.encode(state, o[key])
    }
  },
  decode(state) {
    let len = uint.decode(state)
    const o = {}
    while (len-- > 0) {
      const key = utf8.decode(state)
      o[key] = any.decode(state)
    }
    return o
  }
}

const anyTypes = [
  exports.none,
  exports.bool,
  exports.string,
  exports.buffer,
  exports.uint,
  exports.int,
  exports.float64,
  anyArray,
  anyObject,
  exports.date
]

const any = (exports.any = {
  preencode(state, o) {
    const t = getType(o)
    uint.preencode(state, t)
    anyTypes[t].preencode(state, o)
  },
  encode(state, o) {
    const t = getType(o)
    uint.encode(state, t)
    anyTypes[t].encode(state, o)
  },
  decode(state) {
    const t = uint.decode(state)
    if (t >= anyTypes.length) throw new Error('Unknown type: ' + t)
    return anyTypes[t].decode(state)
  }
})

const port = (exports.port = uint16)

const address = (host, family) => {
  return {
    preencode(state, m) {
      host.preencode(state, m.host)
      port.preencode(state, m.port)
    },
    encode(state, m) {
      host.encode(state, m.host)
      port.encode(state, m.port)
    },
    decode(state) {
      return {
        host: host.decode(state),
        family,
        port: port.decode(state)
      }
    }
  }
}

const ipv4 = (exports.ipv4 = {
  preencode(state) {
    state.end += 4
  },
  encode(state, string) {
    const start = state.start
    const end = start + 4

    let i = 0

    while (i < string.length) {
      let n = 0
      let c

      while (
        i < string.length &&
        (c = string.charCodeAt(i++)) !== /* . */ 0x2e
      ) {
        n = n * 10 + (c - /* 0 */ 0x30)
      }

      state.buffer[state.start++] = n
    }

    state.start = end
  },
  decode(state) {
    if (state.end - state.start < 4) throw new Error('Out of bounds')
    return (
      state.buffer[state.start++] +
      '.' +
      state.buffer[state.start++] +
      '.' +
      state.buffer[state.start++] +
      '.' +
      state.buffer[state.start++]
    )
  }
})

exports.ipv4Address = address(ipv4, 4)

const ipv6 = (exports.ipv6 = {
  preencode(state) {
    state.end += 16
  },
  encode(state, string) {
    const start = state.start
    const end = start + 16

    let i = 0
    let split = null

    while (i < string.length) {
      let n = 0
      let c

      while (
        i < string.length &&
        (c = string.charCodeAt(i++)) !== /* : */ 0x3a
      ) {
        if (c >= 0x30 && c <= 0x39) n = n * 0x10 + (c - /* 0 */ 0x30)
        else if (c >= 0x41 && c <= 0x46) n = n * 0x10 + (c - /* A */ 0x41 + 10)
        else if (c >= 0x61 && c <= 0x66) n = n * 0x10 + (c - /* a */ 0x61 + 10)
      }

      state.buffer[state.start++] = n >>> 8
      state.buffer[state.start++] = n

      if (i < string.length && string.charCodeAt(i) === /* : */ 0x3a) {
        i++
        split = state.start
      }
    }

    if (split !== null) {
      const offset = end - state.start
      state.buffer
        .copyWithin(split + offset, split)
        .fill(0, split, split + offset)
    }

    state.start = end
  },
  decode(state) {
    if (state.end - state.start < 16) throw new Error('Out of bounds')
    return (
      (
        state.buffer[state.start++] * 256 +
        state.buffer[state.start++]
      ).toString(16) +
      ':' +
      (
        state.buffer[state.start++] * 256 +
        state.buffer[state.start++]
      ).toString(16) +
      ':' +
      (
        state.buffer[state.start++] * 256 +
        state.buffer[state.start++]
      ).toString(16) +
      ':' +
      (
        state.buffer[state.start++] * 256 +
        state.buffer[state.start++]
      ).toString(16) +
      ':' +
      (
        state.buffer[state.start++] * 256 +
        state.buffer[state.start++]
      ).toString(16) +
      ':' +
      (
        state.buffer[state.start++] * 256 +
        state.buffer[state.start++]
      ).toString(16) +
      ':' +
      (
        state.buffer[state.start++] * 256 +
        state.buffer[state.start++]
      ).toString(16) +
      ':' +
      (
        state.buffer[state.start++] * 256 +
        state.buffer[state.start++]
      ).toString(16)
    )
  }
})

exports.ipv6Address = address(ipv6, 6)

const ip = (exports.ip = {
  preencode(state, string) {
    const family = string.includes(':') ? 6 : 4
    uint8.preencode(state, family)
    if (family === 4) ipv4.preencode(state)
    else ipv6.preencode(state)
  },
  encode(state, string) {
    const family = string.includes(':') ? 6 : 4
    uint8.encode(state, family)
    if (family === 4) ipv4.encode(state, string)
    else ipv6.encode(state, string)
  },
  decode(state) {
    const family = uint8.decode(state)
    if (family === 4) return ipv4.decode(state)
    else return ipv6.decode(state)
  }
})

exports.ipAddress = {
  preencode(state, m) {
    ip.preencode(state, m.host)
    port.preencode(state, m.port)
  },
  encode(state, m) {
    ip.encode(state, m.host)
    port.encode(state, m.port)
  },
  decode(state) {
    const family = uint8.decode(state)
    return {
      host: family === 4 ? ipv4.decode(state) : ipv6.decode(state),
      family,
      port: port.decode(state)
    }
  }
}

const record = (exports.record = function (keyEncoding, valueEncoding) {
  return {
    preencode(state, v) {
      const keys = Object.keys(v)
      uint.preencode(state, keys.length)
      for (const k of keys) {
        keyEncoding.preencode(state, k)
        valueEncoding.preencode(state, v[k])
      }
    },
    encode(state, v) {
      const keys = Object.keys(v)
      uint.encode(state, keys.length)
      for (const k of keys) {
        keyEncoding.encode(state, k)
        valueEncoding.encode(state, v[k])
      }
    },
    decode(state) {
      const out = Object.create(null)
      const keys = uint.decode(state)
      for (let i = 0; i < keys; i++) {
        out[keyEncoding.decode(state)] = valueEncoding.decode(state)
      }
      return out
    }
  }
})

exports.stringRecord = record(utf8, utf8)

function getType(o) {
  if (o === null || o === undefined) return 0
  if (typeof o === 'boolean') return 1
  if (typeof o === 'string') return 2
  if (b4a.isBuffer(o)) return 3
  if (typeof o === 'number') {
    if (Number.isInteger(o)) return o >= 0 ? 4 : 5
    return 6
  }
  if (Array.isArray(o)) return 7
  if (o instanceof Date) return 9
  if (typeof o === 'object') return 8

  throw new Error('Unsupported type for ' + o)
}

exports.from = function from(enc) {
  if (typeof enc === 'string') return fromNamed(enc)
  if (enc.preencode) return enc
  if (enc.encodingLength) return fromAbstractEncoder(enc)
  return fromCodec(enc)
}

function fromNamed(enc) {
  switch (enc) {
    case 'ascii':
      return raw.ascii
    case 'utf-8':
    case 'utf8':
      return raw.utf8
    case 'hex':
      return raw.hex
    case 'base64':
      return raw.base64
    case 'utf16-le':
    case 'utf16le':
    case 'ucs-2':
    case 'ucs2':
      return raw.ucs2
    case 'ndjson':
      return raw.ndjson
    case 'json':
      return raw.json
    case 'binary':
    default:
      return raw.binary
  }
}

function fromCodec(enc) {
  let tmpM = null
  let tmpBuf = null

  return {
    preencode(state, m) {
      tmpM = m
      tmpBuf = enc.encode(m)
      state.end += tmpBuf.byteLength
    },
    encode(state, m) {
      raw.encode(state, m === tmpM ? tmpBuf : enc.encode(m))
      tmpM = tmpBuf = null
    },
    decode(state) {
      return enc.decode(raw.decode(state))
    }
  }
}

function fromAbstractEncoder(enc) {
  return {
    preencode(state, m) {
      state.end += enc.encodingLength(m)
    },
    encode(state, m) {
      enc.encode(m, state.buffer, state.start)
      state.start += enc.encode.bytes
    },
    decode(state) {
      const m = enc.decode(state.buffer, state.start, state.end)
      state.start += enc.decode.bytes
      return m
    }
  }
}

exports.encode = function encode(enc, m) {
  const state = exports.state()
  enc.preencode(state, m)
  state.buffer = b4a.allocUnsafe(state.end)
  enc.encode(state, m)
  return state.buffer
}

exports.decode = function decode(enc, buffer) {
  return enc.decode(exports.state(0, buffer.byteLength, buffer))
}

function zigZagInt(enc) {
  return {
    preencode(state, n) {
      enc.preencode(state, zigZagEncodeInt(n))
    },
    encode(state, n) {
      enc.encode(state, zigZagEncodeInt(n))
    },
    decode(state) {
      return zigZagDecodeInt(enc.decode(state))
    }
  }
}

function zigZagDecodeInt(n) {
  return n === 0 ? n : (n & 1) === 0 ? n / 2 : -(n + 1) / 2
}

function zigZagEncodeInt(n) {
  // 0, -1, 1, -2, 2, ...
  return n < 0 ? 2 * -n - 1 : n === 0 ? 0 : 2 * n
}

function zigZagBigInt(enc) {
  return {
    preencode(state, n) {
      enc.preencode(state, zigZagEncodeBigInt(n))
    },
    encode(state, n) {
      enc.encode(state, zigZagEncodeBigInt(n))
    },
    decode(state) {
      return zigZagDecodeBigInt(enc.decode(state))
    }
  }
}

function zigZagDecodeBigInt(n) {
  return n === 0n ? n : (n & 1n) === 0n ? n / 2n : -(n + 1n) / 2n
}

function zigZagEncodeBigInt(n) {
  // 0, -1, 1, -2, 2, ...
  return n < 0n ? 2n * -n - 1n : n === 0n ? 0n : 2n * n
}

function validateUint(n) {
  if (n >= 0 === false /* Handles NaN as well */)
    throw new Error('uint must be positive')
}
module.exports = {
  preencode,
  encode,
  decode
}

function preencode(state, num) {
  if (num < 251) {
    state.end++
  } else if (num < 256) {
    state.end += 2
  } else if (num < 0x10000) {
    state.end += 3
  } else if (num < 0x1000000) {
    state.end += 4
  } else if (num < 0x100000000) {
    state.end += 5
  } else {
    state.end++
    const exp = Math.floor(Math.log(num) / Math.log(2)) - 32
    preencode(state, exp)
    state.end += 6
  }
}

function encode(state, num) {
  const max = 251
  const x = num - max

  if (num < max) {
    state.buffer[state.start++] = num
  } else if (num < 256) {
    state.buffer[state.start++] = max
    state.buffer[state.start++] = x
  } else if (num < 0x10000) {
    state.buffer[state.start++] = max + 1
    state.buffer[state.start++] = (x >> 8) & 0xff
    state.buffer[state.start++] = x & 0xff
  } else if (num < 0x1000000) {
    state.buffer[state.start++] = max + 2
    state.buffer[state.start++] = x >> 16
    state.buffer[state.start++] = (x >> 8) & 0xff
    state.buffer[state.start++] = x & 0xff
  } else if (num < 0x100000000) {
    state.buffer[state.start++] = max + 3
    state.buffer[state.start++] = x >> 24
    state.buffer[state.start++] = (x >> 16) & 0xff
    state.buffer[state.start++] = (x >> 8) & 0xff
    state.buffer[state.start++] = x & 0xff
  } else {
    // need to use Math here as bitwise ops are 32 bit
    const exp = Math.floor(Math.log(x) / Math.log(2)) - 32
    state.buffer[state.start++] = 0xff

    encode(state, exp)
    const rem = x / Math.pow(2, exp - 11)

    for (let i = 5; i >= 0; i--) {
      state.buffer[state.start++] = (rem / Math.pow(2, 8 * i)) & 0xff
    }
  }
}

function decode(state) {
  const max = 251

  if (state.end - state.start < 1) throw new Error('Out of bounds')

  const flag = state.buffer[state.start++]

  if (flag < max) return flag

  if (state.end - state.start < flag - max + 1) {
    throw new Error('Out of bounds.')
  }

  if (flag < 252) {
    return state.buffer[state.start++] + max
  }

  if (flag < 253) {
    return (
      (state.buffer[state.start++] << 8) + state.buffer[state.start++] + max
    )
  }

  if (flag < 254) {
    return (
      (state.buffer[state.start++] << 16) +
      (state.buffer[state.start++] << 8) +
      state.buffer[state.start++] +
      max
    )
  }

  // << 24 result may be interpreted as negative
  if (flag < 255) {
    return (
      state.buffer[state.start++] * 0x1000000 +
      (state.buffer[state.start++] << 16) +
      (state.buffer[state.start++] << 8) +
      state.buffer[state.start++] +
      max
    )
  }

  const exp = decode(state)

  if (state.end - state.start < 6) throw new Error('Out of bounds')

  let rem = 0
  for (let i = 5; i >= 0; i--) {
    rem += state.buffer[state.start++] * Math.pow(2, 8 * i)
  }

  return rem * Math.pow(2, exp - 11) + max
}
{
  "name": "compact-encoding",
  "version": "2.19.2",
  "description": "A series of compact encoding schemes for building small and fast parsers and serializers",
  "main": "index.js",
  "dependencies": {
    "b4a": "^1.3.0"
  },
  "devDependencies": {
    "brittle": "^3.0.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^1.0.0"
  },
  "scripts": {
    "format": "prettier . --write",
    "test": "prettier . --check && brittle test.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/holepunchto/compact-encoding.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/compact-encoding/issues"
  },
  "homepage": "https://github.com/holepunchto/compact-encoding"
}
const b4a = require('b4a')

const { BE } = require('./endian')

exports = module.exports = {
  preencode(state, b) {
    state.end += b.byteLength
  },
  encode(state, b) {
    state.buffer.set(b, state.start)
    state.start += b.byteLength
  },
  decode(state) {
    const b = state.buffer.subarray(state.start, state.end)
    state.start = state.end
    return b
  }
}

const buffer = (exports.buffer = {
  preencode(state, b) {
    if (b) uint8array.preencode(state, b)
    else state.end++
  },
  encode(state, b) {
    if (b) uint8array.encode(state, b)
    else state.buffer[state.start++] = 0
  },
  decode(state) {
    const b = state.buffer.subarray(state.start)
    if (b.byteLength === 0) return null
    state.start = state.end
    return b
  }
})

exports.binary = {
  ...buffer,
  preencode(state, b) {
    if (typeof b === 'string') utf8.preencode(state, b)
    else buffer.preencode(state, b)
  },
  encode(state, b) {
    if (typeof b === 'string') utf8.encode(state, b)
    else buffer.encode(state, b)
  }
}

exports.arraybuffer = {
  preencode(state, b) {
    state.end += b.byteLength
  },
  encode(state, b) {
    const view = new Uint8Array(b)

    state.buffer.set(view, state.start)
    state.start += b.byteLength
  },
  decode(state) {
    const b = new ArrayBuffer(state.end - state.start)
    const view = new Uint8Array(b)

    view.set(state.buffer.subarray(state.start))

    state.start = state.end

    return b
  }
}

function typedarray(TypedArray, swap) {
  const n = TypedArray.BYTES_PER_ELEMENT

  return {
    preencode(state, b) {
      state.end += b.byteLength
    },
    encode(state, b) {
      const view = new Uint8Array(b.buffer, b.byteOffset, b.byteLength)

      if (BE && swap) swap(view)

      state.buffer.set(view, state.start)
      state.start += b.byteLength
    },
    decode(state) {
      let b = state.buffer.subarray(state.start)
      if (b.byteOffset % n !== 0) b = new Uint8Array(b)

      if (BE && swap) swap(b)

      state.start = state.end

      return new TypedArray(b.buffer, b.byteOffset, b.byteLength / n)
    }
  }
}

const uint8array = (exports.uint8array = typedarray(Uint8Array))
exports.uint16array = typedarray(Uint16Array, b4a.swap16)
exports.uint32array = typedarray(Uint32Array, b4a.swap32)

exports.int8array = typedarray(Int8Array)
exports.int16array = typedarray(Int16Array, b4a.swap16)
exports.int32array = typedarray(Int32Array, b4a.swap32)

exports.biguint64array = typedarray(BigUint64Array, b4a.swap64)
exports.bigint64array = typedarray(BigInt64Array, b4a.swap64)

exports.float32array = typedarray(Float32Array, b4a.swap32)
exports.float64array = typedarray(Float64Array, b4a.swap64)

function string(encoding) {
  return {
    preencode(state, s) {
      state.end += b4a.byteLength(s, encoding)
    },
    encode(state, s) {
      state.start += b4a.write(state.buffer, s, state.start, encoding)
    },
    decode(state) {
      const s = b4a.toString(state.buffer, encoding, state.start)
      state.start = state.end
      return s
    }
  }
}

const utf8 = (exports.string = exports.utf8 = string('utf-8'))
exports.ascii = string('ascii')
exports.hex = string('hex')
exports.base64 = string('base64')
exports.ucs2 = exports.utf16le = string('utf16le')

exports.array = function array(enc) {
  return {
    preencode(state, list) {
      for (const value of list) enc.preencode(state, value)
    },
    encode(state, list) {
      for (const value of list) enc.encode(state, value)
    },
    decode(state) {
      const arr = []
      while (state.start < state.end) arr.push(enc.decode(state))
      return arr
    }
  }
}

exports.json = {
  preencode(state, v) {
    utf8.preencode(state, JSON.stringify(v))
  },
  encode(state, v) {
    utf8.encode(state, JSON.stringify(v))
  },
  decode(state) {
    return JSON.parse(utf8.decode(state))
  }
}

exports.ndjson = {
  preencode(state, v) {
    utf8.preencode(state, JSON.stringify(v) + '\n')
  },
  encode(state, v) {
    utf8.encode(state, JSON.stringify(v) + '\n')
  },
  decode(state) {
    return JSON.parse(utf8.decode(state))
  }
}
{
  "name": "hyperschema",
  "version": "1.20.1",
  "description": "Create registries of declarative compact-encoding schemas",
  "files": [
    "lib/*.js",
    "builder.mjs",
    "builder.cjs",
    "runtime.mjs",
    "runtime.cjs",
    "primitives.mjs",
    "primitives.cjs"
  ],
  "exports": {
    "./package": "./package.json",
    ".": {
      "import": "./builder.mjs",
      "default": "./builder.cjs"
    },
    "./runtime": {
      "import": "./runtime.mjs",
      "default": "./runtime.cjs"
    },
    "./primitives": {
      "import": "./primitives.mjs",
      "default": "./primitives.cjs"
    }
  },
  "imports": {
    "fs": {
      "bare": "bare-fs",
      "default": "fs"
    },
    "path": {
      "bare": "bare-path",
      "default": "path"
    }
  },
  "scripts": {
    "format": "prettier . --write",
    "test": "prettier . --check && brittle test/index.js",
    "test:bare": "prettier . --check && bare test/index.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/hyperschema.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/hyperschema/issues"
  },
  "homepage": "https://github.com/holepunchto/hyperschema#readme",
  "dependencies": {
    "bare-fs": "^4.0.1",
    "compact-encoding": "^2.19.0",
    "generate-object-property": "^2.0.0",
    "generate-string": "^1.0.1"
  },
  "devDependencies": {
    "brittle": "^3.7.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^2.0.0",
    "test-tmp": "^1.3.0"
  }
}
module.exports = {
  c: require('compact-encoding')
}
const { EventEmitter } = require('events')
const { getStreamError } = require('streamx')
const DHT = require('hyperdht')
const spq = require('shuffled-priority-queue')
const b4a = require('b4a')
const unslab = require('unslab')

const PeerInfo = require('./lib/peer-info')
const RetryTimer = require('./lib/retry-timer')
const ConnectionSet = require('./lib/connection-set')
const PeerDiscovery = require('./lib/peer-discovery')

const MAX_PEERS = 64
const MAX_PARALLEL = 3
const MAX_CLIENT_CONNECTIONS = Infinity // TODO: Change
const MAX_SERVER_CONNECTIONS = Infinity

const ERR_MISSING_TOPIC = 'Topic is required and must be a 32-byte buffer'
const ERR_DESTROYED = 'Swarm has been destroyed'
const ERR_DUPLICATE = 'Duplicate connection'
const ERR_FIREWALL = 'Peer is firewalled'

module.exports = class Hyperswarm extends EventEmitter {
  constructor(opts = {}) {
    super()
    const {
      seed,
      relayThrough,
      keyPair = DHT.keyPair(seed),
      maxPeers = MAX_PEERS,
      maxClientConnections = MAX_CLIENT_CONNECTIONS,
      maxServerConnections = MAX_SERVER_CONNECTIONS,
      maxParallel = MAX_PARALLEL,
      firewall = allowAll
    } = opts
    this.keyPair = keyPair

    this.dht =
      opts.dht ||
      new DHT({
        bootstrap: opts.bootstrap,
        nodes: opts.nodes,
        port: opts.port,
        deferRandomPunch: opts.deferRandomPunch,
        randomPunchInterval: opts.randomPunchInterval
      })
    this.server = this.dht.createServer(
      {
        firewall: this._handleFirewall.bind(this),
        relayThrough: this._maybeRelayConnection.bind(this),
        handshakeClearWait: opts.handshakeClearWait
      },
      this._handleServerConnection.bind(this)
    )

    this.destroyed = false
    this.suspended = false
    this.maxPeers = maxPeers
    this.maxClientConnections = maxClientConnections
    this.maxServerConnections = maxServerConnections
    this.maxParallel = maxParallel
    this.relayThrough = relayThrough ? toRelayFunction(relayThrough) : null

    this.connecting = 0
    this.connections = new Set()
    this.peers = new Map()
    this.explicitPeers = new Set()
    this.listening = null
    this.stats = {
      updates: 0,
      connects: {
        client: {
          opened: 0,
          closed: 0,
          attempted: 0
        },
        server: {
          // Note: there is no notion of 'attempts' for server connections
          opened: 0,
          closed: 0
        }
      },
      bannedPeers: 0
    }

    this._discovery = new Map()
    this._timer = new RetryTimer(this._requeue.bind(this), {
      backoffs: opts.backoffs,
      jitter: opts.jitter
    })
    this._queue = spq()

    this._allConnections = new ConnectionSet()
    this._pendingFlushes = []
    this._flushTick = 0

    this._drainingQueue = false
    this._clientConnections = 0
    this._serverConnections = 0
    this._firewall = firewall

    this.dht.on('network-change', this._handleNetworkChange.bind(this))
    this.dht.on('network-update', this._handleNetworkUpdate.bind(this))
    this.on('update', this._handleUpdate)
  }

  _maybeRelayConnection(force) {
    if (!this.relayThrough) return null
    return this.relayThrough(force, this)
  }

  _enqueue(peerInfo) {
    if (peerInfo.queued) return
    peerInfo.queued = true
    peerInfo._flushTick = this._flushTick
    this._queue.add(peerInfo)

    this._attemptClientConnections()
  }

  _requeue(batch) {
    if (this.suspended) return
    for (const peerInfo of batch) {
      peerInfo.waiting = false

      if (
        peerInfo._updatePriority() === false ||
        this._allConnections.has(peerInfo.publicKey) ||
        peerInfo.queued
      ) {
        continue
      }
      peerInfo.queued = true
      peerInfo._flushTick = this._flushTick
      this._queue.add(peerInfo)
    }

    this._attemptClientConnections()
  }

  _flushMaybe(peerInfo) {
    for (let i = 0; i < this._pendingFlushes.length; i++) {
      const flush = this._pendingFlushes[i]
      if (peerInfo._flushTick > flush.tick) continue
      if (--flush.missing > 0) continue
      flush.onflush(true)
      this._pendingFlushes.splice(i--, 1)
    }
  }

  _withinMaxPeers() {
    const e = this.explicitPeers.size
    const factor = e === 0 ? 1 : e < 2 ? 2 : e < 4 ? 3 : 4
    return factor * this._allConnections.size < this.maxPeers
  }

  _flushAllMaybe() {
    if (
      this.connecting > 0 ||
      (this._withinMaxPeers() && this._clientConnections < this.maxClientConnections)
    ) {
      return false
    }

    while (this._pendingFlushes.length) {
      const flush = this._pendingFlushes.pop()
      flush.onflush(true)
    }

    return true
  }

  _shouldConnectExplicit() {
    return !this.destroyed && !this.suspended && this.connecting < this.maxParallel
  }

  _shouldConnect() {
    return (
      !this.destroyed &&
      !this.suspended &&
      this.connecting < this.maxParallel &&
      this._withinMaxPeers() &&
      this._clientConnections < this.maxClientConnections
    )
  }

  _shouldRequeue(peerInfo) {
    if (this.suspended) return false
    if (peerInfo.explicit) return true
    for (const topic of peerInfo.topics) {
      if (this._discovery.has(b4a.toString(topic, 'hex')) && !this.destroyed) {
        return true
      }
    }
    return false
  }

  _connect(peerInfo, queued) {
    if (peerInfo.banned || this._allConnections.has(peerInfo.publicKey)) {
      if (queued) this._flushMaybe(peerInfo)
      return
    }

    // TODO: Support async firewalling at some point.
    if (this._handleFirewall(peerInfo.publicKey, null)) {
      if (queued) this._flushMaybe(peerInfo)
      return
    }

    const relayThrough = this._maybeRelayConnection(peerInfo.forceRelaying)
    const conn = this.dht.connect(peerInfo.publicKey, {
      relayAddresses: peerInfo.relayAddresses,
      keyPair: this.keyPair,
      relayThrough
    })
    this._allConnections.add(conn)

    this.stats.connects.client.attempted++

    this.connecting++
    this._clientConnections++
    let opened = false

    const onerror = (err) => {
      if (this.relayThrough && shouldForceRelaying(err.code)) {
        peerInfo.forceRelaying = true
        // Reset the attempts in order to fast connect to relay
        peerInfo.attempts = 0
      }
    }

    // Removed once a connection is opened
    conn.on('error', onerror)

    conn.on('open', () => {
      opened = true
      this.stats.connects.client.opened++

      this._connectDone()
      this.connections.add(conn)
      conn.removeListener('error', onerror)
      peerInfo._connected()
      peerInfo.client = true
      this.emit('connection', conn, peerInfo)
      if (queued) this._flushMaybe(peerInfo)

      this.emit('update')
    })
    conn.on('close', () => {
      if (!opened) this._connectDone()
      this.stats.connects.client.closed++

      const err = getStreamError(conn)
      if (shouldBan(err)) {
        this._banPeer(peerInfo, true, err)
      }

      this.connections.delete(conn)
      this._allConnections.delete(conn)
      this._clientConnections--
      peerInfo._disconnected()

      peerInfo.waiting = this._shouldRequeue(peerInfo) && this._timer.add(peerInfo)
      this._maybeDeletePeer(peerInfo)

      if (!opened && queued) this._flushMaybe(peerInfo)

      this._attemptClientConnections()

      this.emit('update')
    })

    this.emit('update')
  }

  _connectDone() {
    this.connecting--

    if (this.connecting < this.maxParallel) this._attemptClientConnections()
    if (this.connecting === 0) this._flushAllMaybe()
  }

  // Called when the PeerQueue indicates a connection should be attempted.
  _attemptClientConnections() {
    // Guard against re-entries - unsure if it still needed but doesn't hurt
    if (this._drainingQueue || this.suspended) return
    this._drainingQueue = true

    for (const peerInfo of this.explicitPeers) {
      if (!this._shouldConnectExplicit()) break
      if (
        peerInfo.attempts >= 5 ||
        Date.now() - peerInfo.disconnectedTime < peerInfo.attempts * 1000
      ) {
        continue
      }
      this._connect(peerInfo, false)
    }

    while (this._queue.length && this._shouldConnect()) {
      const peerInfo = this._queue.shift()
      peerInfo.queued = false
      this._connect(peerInfo, true)
    }
    this._drainingQueue = false
    if (this.connecting === 0) this._flushAllMaybe()
  }

  _handleFirewall(remotePublicKey, payload) {
    if (b4a.equals(remotePublicKey, this.keyPair.publicKey)) return true

    let peerInfo = this.peers.get(b4a.toString(remotePublicKey, 'hex'))
    if (peerInfo && peerInfo.banned) return true

    const firewalled = this._firewall(remotePublicKey, payload)
    if (firewalled) {
      if (!peerInfo) peerInfo = this._upsertPeer(remotePublicKey)
      this._banPeer(peerInfo, true, new Error(ERR_FIREWALL))
    }

    return firewalled
  }

  _handleServerConnectionSwap(existing, conn) {
    let closed = false

    existing.on('close', () => {
      if (closed) return

      conn.removeListener('error', noop)
      conn.removeListener('close', onclose)

      this._handleServerConnection(conn)
    })

    conn.on('error', noop)
    conn.on('close', onclose)

    function onclose() {
      closed = true
    }
  }

  // Called when the DHT receives a new server connection.
  _handleServerConnection(conn) {
    if (this.destroyed || this.suspended) {
      // TODO: Investigate why a final server connection can be received after close
      conn.on('error', noop)
      return conn.destroy(ERR_DESTROYED)
    }

    const existing = this._allConnections.get(conn.remotePublicKey)

    if (existing) {
      // If both connections are from the same peer,
      // - pick the new one if the existing stream is already established (has sent and received bytes),
      //   because the other client must have lost that connection and be reconnecting
      // - otherwise, pick the one thats expected to initiate in a tie break
      const existingIsOutdated = existing.rawBytesRead > 0 && existing.rawBytesWritten > 0
      const expectedInitiator = b4a.compare(conn.publicKey, conn.remotePublicKey) > 0
      const keepNew = existingIsOutdated || expectedInitiator === conn.isInitiator

      if (keepNew === false) {
        existing.sendKeepAlive()
        conn.on('error', noop)
        conn.destroy(new Error(ERR_DUPLICATE))
        return
      }

      existing.on('error', noop)
      existing.destroy(new Error(ERR_DUPLICATE))
      this._handleServerConnectionSwap(existing, conn)
      return
    }

    // When reaching here, the connection will always be 'opened' next tick
    this.stats.connects.server.opened++

    const peerInfo = this._upsertPeer(conn.remotePublicKey, null)

    this.connections.add(conn)
    this._allConnections.add(conn)
    this._serverConnections++

    conn.on('close', () => {
      const err = getStreamError(conn)
      if (shouldBan(err)) {
        this._banPeer(peerInfo, true, err)
      }

      this.connections.delete(conn)
      this._allConnections.delete(conn)
      this._serverConnections--
      this.stats.connects.server.closed++

      this._maybeDeletePeer(peerInfo)

      this._attemptClientConnections()

      this.emit('update')
    })
    peerInfo.client = false
    this.emit('connection', conn, peerInfo)

    this.emit('update')
  }

  _upsertPeer(publicKey, relayAddresses) {
    if (b4a.equals(publicKey, this.keyPair.publicKey)) return null
    const keyString = b4a.toString(publicKey, 'hex')
    let peerInfo = this.peers.get(keyString)

    if (peerInfo) {
      peerInfo.relayAddresses = relayAddresses // new is always better
      return peerInfo
    }

    peerInfo = new PeerInfo({
      publicKey,
      relayAddresses
    })

    this.peers.set(keyString, peerInfo)
    return peerInfo
  }

  _handleUpdate() {
    this.stats.updates++
  }

  _maybeDeletePeer(peerInfo) {
    if (!peerInfo.shouldGC()) return

    const hasActiveConn = this._allConnections.has(peerInfo.publicKey)
    if (hasActiveConn) return

    const keyString = b4a.toString(peerInfo.publicKey, 'hex')
    this.peers.delete(keyString)
  }

  /*
   * Called when a peer is actively discovered during a lookup.
   *
   * Three conditions:
   *  1. Not a known peer -- insert into queue
   *  2. A known peer with normal priority -- do nothing
   *  3. A known peer with low priority -- bump priority, because it's been rediscovered
   */
  _handlePeer(peer, topic) {
    const peerInfo = this._upsertPeer(peer.publicKey, peer.relayAddresses)
    if (peerInfo) peerInfo._topic(topic)
    if (!peerInfo || this._allConnections.has(peer.publicKey)) return
    if (!peerInfo.prioritized || peerInfo.server) peerInfo._reset()
    if (peerInfo._updatePriority()) {
      this._enqueue(peerInfo)
    }
  }

  async _handleNetworkUpdate() {
    if (!this.online) return
    this._handleNetworkChange()
  }

  async _handleNetworkChange() {
    if (this.suspended) return

    // prioritize figuring out if existing connections are dead
    for (const conn of this._allConnections) {
      conn.sendKeepAlive()
    }

    const refreshes = []

    for (const discovery of this._discovery.values()) {
      refreshes.push(discovery.refresh())
    }

    await Promise.allSettled(refreshes)
  }

  _banPeer(peerInfo, banned, err) {
    peerInfo.ban(banned)
    this.stats.bannedPeers++
    this.emit('ban', peerInfo, err)
  }

  status(key) {
    return this._discovery.get(b4a.toString(key, 'hex')) || null
  }

  listen() {
    if (!this.listening) {
      if (this.destroyed) throw new Error('Swarm destroyed')
      this.listening = this.server.listen(this.keyPair)
    }
    return this.listening
  }

  // Object that exposes a cancellation method (destroy)
  // TODO: When you rejoin, it should reannounce + bump lookup priority
  join(topic, opts = {}) {
    if (this.destroyed) throw new Error('Swarm destroyed')
    if (!topic) throw new Error(ERR_MISSING_TOPIC)
    topic = unslab(topic)

    const topicString = b4a.toString(topic, 'hex')

    let discovery = this._discovery.get(topicString)

    if (discovery && !discovery.destroyed) {
      return discovery.session(opts)
    }

    discovery = new PeerDiscovery(this, topic, {
      limit: opts.limit,
      wait: discovery ? discovery.destroy() : null,
      suspended: this.suspended,
      onpeer: (peer) => this._handlePeer(peer, topic)
    })
    this._discovery.set(topicString, discovery)
    return discovery.session(opts)
  }

  // Returns a promise
  async leave(topic) {
    if (!topic) throw new Error(ERR_MISSING_TOPIC)
    const topicString = b4a.toString(topic, 'hex')
    if (!this._discovery.has(topicString)) return Promise.resolve()

    const discovery = this._discovery.get(topicString)

    try {
      await discovery.destroy()
    } catch {
      // ignore, prop network
    }

    if (this._discovery.get(topicString) === discovery) {
      this._discovery.delete(topicString)
    }
  }

  joinPeer(publicKey) {
    const peerInfo = this._upsertPeer(publicKey, null)
    if (!peerInfo) return
    if (!this.explicitPeers.has(peerInfo)) {
      peerInfo.explicit = true
      this.explicitPeers.add(peerInfo)
    }
    if (this._allConnections.has(publicKey)) return
    if (peerInfo._updatePriority()) {
      this._enqueue(peerInfo)
    }
  }

  leavePeer(publicKey) {
    const keyString = b4a.toString(publicKey, 'hex')
    if (!this.peers.has(keyString)) return

    const peerInfo = this.peers.get(keyString)
    peerInfo.explicit = false
    this.explicitPeers.delete(peerInfo)
    this._maybeDeletePeer(peerInfo)
  }

  // Returns a promise
  async flush() {
    const allFlushed = [...this._discovery.values()].map((v) => v.flushed())
    await Promise.all(allFlushed)
    if (this._flushAllMaybe()) return true
    const pendingSize = this._allConnections.size - this.connections.size
    if (!this._queue.length && !pendingSize) return true
    return new Promise((resolve) => {
      this._pendingFlushes.push({
        onflush: resolve,
        missing: this._queue.length + pendingSize,
        tick: this._flushTick++
      })
    })
  }

  async clear() {
    const cleared = Promise.allSettled([...this._discovery.values()].map((d) => d.destroy()))
    this._discovery.clear()
    return cleared
  }

  async destroy({ force } = {}) {
    if (this.destroyed && !force) return
    this.destroyed = true

    this._timer.destroy()

    if (!force) await this.clear()

    await this.server.close()

    while (this._pendingFlushes.length) {
      const flush = this._pendingFlushes.pop()
      flush.onflush(false)
    }

    await this.dht.destroy({ force })
  }

  async suspend({ log = noop } = {}) {
    if (this.suspended) return

    const promises = []

    promises.push(this.server.suspend({ log }))

    for (const discovery of this._discovery.values()) {
      promises.push(discovery.suspend({ log }))
    }

    const pending = []
    for (const connection of this._allConnections) {
      connection.destroy()
      pending.push(new Promise((resolve) => connection.on('close', resolve)))
    }

    this.suspended = true

    log('Suspending server and discovery... (' + promises.length + ')')
    await Promise.allSettled(promises)
    log('Done, suspending the dht...')
    await this.dht.suspend({ log })
    log('Done, swarm fully suspended')

    await Promise.all(pending)

    // reset queue
    this._timer.destroy()
    this._timer = new RetryTimer(this._requeue.bind(this), {
      backoffs: this._timer.backoffs,
      jitter: this._timer.jitter
    })
    this._queue = spq()
  }

  async resume({ log = noop } = {}) {
    if (!this.suspended) return

    log('Resuming the dht')
    await this.dht.resume()
    log('Done, resuming the server')
    await this.server.resume()
    log('Done, all discovery')

    for (const discovery of this._discovery.values()) {
      discovery.resume()
    }

    this.suspended = false
    this._attemptClientConnections()
  }

  topics() {
    return this._discovery.values()
  }
}

function noop() {}

function allowAll() {
  return false
}

function shouldForceRelaying(code) {
  return (
    code === 'HOLEPUNCH_ABORTED' ||
    code === 'HOLEPUNCH_DOUBLE_RANDOMIZED_NATS' ||
    code === 'REMOTE_NOT_HOLEPUNCHABLE'
  )
}

function shouldBan() {
  // return !!err && err.name === 'HypercoreError' && err.code === 'INVALID_OPERATION'
  return false
}

function toRelayFunction(relayThrough) {
  return typeof relayThrough === 'function'
    ? relayThrough
    : (force, swarm) => (force || swarm.dht.randomized ? relayThrough : null)
}
module.exports = class BulkTimer {
  constructor(time, fn) {
    this._time = time
    this._fn = fn
    this._interval = null
    this._next = []
    this._pending = []
    this._destroyed = false
  }

  destroy() {
    if (this._destroyed) return
    this._destroyed = true
    clearInterval(this._interval)
    this._interval = null
  }

  _ontick() {
    if (!this._next.length && !this._pending.length) return
    if (this._next.length) this._fn(this._next)
    this._next = this._pending
    this._pending = []
  }

  add(info) {
    if (this._destroyed) return
    if (!this._interval) {
      this._interval = setInterval(this._ontick.bind(this), Math.floor(this._time * 0.66))
    }

    this._pending.push(info)
  }
}
const b4a = require('b4a')

module.exports = class ConnectionSet {
  constructor() {
    this._byPublicKey = new Map()
  }

  [Symbol.iterator]() {
    return this._byPublicKey.values()
  }

  get size() {
    return this._byPublicKey.size
  }

  has(publicKey) {
    return this._byPublicKey.has(toHex(publicKey))
  }

  get(publicKey) {
    return this._byPublicKey.get(toHex(publicKey))
  }

  add(connection) {
    this._byPublicKey.set(b4a.toString(connection.remotePublicKey, 'hex'), connection)
  }

  delete(connection) {
    const keyString = b4a.toString(connection.remotePublicKey, 'hex')
    const existing = this._byPublicKey.get(keyString)
    if (existing !== connection) return
    this._byPublicKey.delete(keyString)
  }
}

function toHex(b) {
  return typeof b === 'string' ? b : b4a.toString(b, 'hex')
}
const safetyCatch = require('safety-catch')
const b4a = require('b4a')

const REFRESH_INTERVAL = 1000 * 60 * 10 // 10 min
const RANDOM_JITTER = 1000 * 60 * 2 // 2 min
const DELAY_GRACE_PERIOD = 1000 * 30 // 30s

const MAX_DISCOVERY_CACHE = 64

module.exports = class PeerDiscovery {
  constructor(
    swarm,
    topic,
    { limit = Infinity, wait = null, suspended = false, onpeer = noop, onerror = safetyCatch }
  ) {
    this.limit = limit
    this.swarm = swarm
    this.topic = topic
    this.isClient = false
    this.isServer = false
    this.destroyed = false
    this.destroying = null
    this.suspended = suspended

    this._sessions = []
    this._clientSessions = 0
    this._serverSessions = 0

    this._onpeer = onpeer
    this._onerror = onerror

    this._discovered = new Set()
    this._activeQuery = null
    this._timer = null
    this._currentRefresh = null
    this._closestNodes = null
    this._firstAnnounce = true
    this._needsUnannounce = false
    this._refreshes = 0
    this._wait = wait
  }

  session({ server = true, client = true, limit = Infinity, onerror = safetyCatch }) {
    if (this.destroyed) throw new Error('PeerDiscovery is destroyed')
    const session = new PeerDiscoverySession(this)
    session.refresh({ server, client, limit }).catch(onerror)
    this._sessions.push(session)
    return session
  }

  _refreshLater(eager) {
    const jitter = Math.round(Math.random() * RANDOM_JITTER)
    const delay = !eager ? REFRESH_INTERVAL + jitter : jitter

    if (this._timer) clearTimeout(this._timer)

    const startTime = Date.now()
    this._timer = setTimeout(() => {
      // If your laptop went to sleep, and is coming back online...
      const overdue = Date.now() - startTime > delay + DELAY_GRACE_PERIOD
      if (overdue) this._refreshLater(true)
      else this.refresh().catch(this._onerror)
    }, delay)
  }

  _isActive() {
    return !this.destroyed && !this.suspended
  }

  // TODO: Allow announce to be an argument to this
  // TODO: Maybe announce should be a setter?
  async _refresh() {
    if (this.suspended) return
    const clock = ++this._refreshes

    if (this._wait) {
      await this._wait
      this._wait = null
      if (clock !== this._refreshes || !this._isActive()) return
    }

    const clear = this.isServer && this._firstAnnounce
    if (clear) this._firstAnnounce = false

    const opts = {
      clear,
      closestNodes: this._closestNodes
    }

    if (this.isServer) {
      await this.swarm.listen()
      // if a parallel refresh is happening, yield to the new one
      if (clock !== this._refreshes || !this._isActive()) return
      this._needsUnannounce = true
    }

    let limit = this.limit

    if (limit < Infinity && limit > 0) {
      for (const id of this._discovered) {
        if (!this.swarm.connections.has(id)) continue
        if (--limit === 0) break
      }
    }

    this._discovered.clear()

    const announcing = this.isServer
    const query = (this._activeQuery = announcing
      ? this.swarm.dht.announce(
          this.topic,
          this.swarm.keyPair,
          this.swarm.server.relayAddresses,
          opts
        )
      : this._needsUnannounce
        ? this.swarm.dht.lookupAndUnannounce(this.topic, this.swarm.keyPair, opts)
        : this.swarm.dht.lookup(this.topic, opts))

    try {
      for await (const data of this._activeQuery) {
        if (!this.isClient || !this._isActive()) continue
        for (const peer of data.peers) {
          if (limit < Infinity) {
            const id = b4a.toString(peer.publicKey, 'hex')

            if (this._discovered.size < MAX_DISCOVERY_CACHE) {
              this._discovered.add(id)
            }

            if (limit === 0) continue

            // there is a chance it has updated a connection during discovery and we go over
            // the limit - thats acceptable to avoid a complexity spiral here.
            if (!this.swarm.connections.has(id)) limit--
          }

          this._onpeer(peer, data)
        }
      }
    } catch (err) {
      if (this._isActive()) throw err
    } finally {
      if (this._activeQuery === query) {
        this._activeQuery = null
        if (!this.destroyed && !this.suspended) this._refreshLater(false)
      }
    }

    // This is set at the very end, when the query completes successfully.
    this._closestNodes = query.closestNodes

    if (clock !== this._refreshes) return

    // In this is the latest query, unannounce has been fulfilled as well
    if (!announcing) this._needsUnannounce = false
  }

  async refresh() {
    if (this.destroyed) throw new Error('PeerDiscovery is destroyed')

    const server = this._serverSessions > 0
    const client = this._clientSessions > 0

    if (this.suspended) return

    if (server === this.isServer && client === this.isClient) {
      if (this._currentRefresh) return this._currentRefresh
      this._currentRefresh = this._refresh()
    } else {
      if (this._activeQuery) this._activeQuery.destroy()
      this.isServer = server
      this.isClient = client
      this._currentRefresh = this._refresh()
    }

    const refresh = this._currentRefresh
    try {
      await refresh
    } catch {
      return false
    } finally {
      if (refresh === this._currentRefresh) {
        this._currentRefresh = null
      }
    }

    return true
  }

  async flushed() {
    if (this.swarm.listening) await this.swarm.listening

    try {
      await this._currentRefresh
      return true
    } catch {
      return false
    }
  }

  async _destroyMaybe() {
    if (this.destroyed) return

    try {
      if (this._sessions.length === 0) await this.swarm.leave(this.topic)
      else if (this._serverSessions === 0 && this._needsUnannounce) await this.refresh()
    } catch (err) {
      // ignore network failures here, as we are tearing down
      safetyCatch(err)
    }
  }

  destroy() {
    if (this.destroying) return this.destroying
    this.destroying = this._destroy()
    return this.destroying
  }

  async _abort(log) {
    const id = log === noop ? '' : b4a.toString(this.topic, 'hex')

    log('Aborting discovery', id)
    if (this._wait) await this._wait
    log('Aborting discovery (post wait)', id)

    if (this._activeQuery) {
      this._activeQuery.destroy()
      this._activeQuery = null
    }
    if (this._timer) {
      clearTimeout(this._timer)
      this._timer = null
    }

    let nodes = this._closestNodes

    if (this._currentRefresh) {
      try {
        await this._currentRefresh
      } catch {
        // If the destroy causes the refresh to fail, suppress it.
      }
    }

    log('Aborting discovery (post refresh)', id)
    if (this._isActive()) return

    if (!nodes) nodes = this._closestNodes
    else if (this._closestNodes !== nodes) {
      const len = nodes.length
      for (const newer of this._closestNodes) {
        if (newer.id && !hasNode(nodes, len, newer)) nodes.push(newer)
      }
    }

    if (this._needsUnannounce) {
      log('Unannouncing discovery', id)
      if (nodes && nodes.length) {
        await this.swarm.dht.unannounce(this.topic, this.swarm.keyPair, {
          closestNodes: nodes,
          onlyClosestNodes: true,
          force: true
        })
      }
      this._needsUnannounce = false
      log('Unannouncing discovery (done)', id)
    }
  }

  _destroy() {
    if (this.destroyed) return
    this.destroyed = true
    return this._abort(noop)
  }

  async suspend({ log = noop } = {}) {
    if (this.suspended) return
    this.suspended = true
    try {
      await this._abort(log)
    } catch {
      // ignore
    }
  }

  resume() {
    if (!this.suspended) return
    this.suspended = false
    this.refresh().catch(noop)
  }
}

class PeerDiscoverySession {
  constructor(discovery) {
    this.discovery = discovery
    this.isClient = false
    this.isServer = false
    this.destroyed = false
  }

  get swarm() {
    return this.discovery.swarm
  }

  get topic() {
    return this.discovery.topic
  }

  async refresh({ client = this.isClient, server = this.isServer, limit = Infinity } = {}) {
    if (this.destroyed) throw new Error('PeerDiscovery is destroyed')
    if (!client && !server) throw new Error('Cannot refresh with neither client nor server option')

    if (client !== this.isClient) {
      this.isClient = client
      this.discovery._clientSessions += client ? 1 : -1
    }

    if (server !== this.isServer) {
      this.isServer = server
      this.discovery._serverSessions += server ? 1 : -1
    }

    this.discovery.limit = limit

    return this.discovery.refresh()
  }

  async flushed() {
    return this.discovery.flushed()
  }

  async destroy() {
    if (this.destroyed) return
    this.destroyed = true

    if (this.isClient) this.discovery._clientSessions--
    if (this.isServer) this.discovery._serverSessions--

    const index = this.discovery._sessions.indexOf(this)
    const head = this.discovery._sessions.pop()

    if (head !== this) this.discovery._sessions[index] = head

    return this.discovery._destroyMaybe()
  }
}

function hasNode(nodes, len, node) {
  for (let i = 0; i < len; i++) {
    const existing = nodes[i]
    if (existing.id && b4a.equals(existing.id, node.id)) return true
  }

  return false
}

function noop() {}
const { EventEmitter } = require('events')
const b4a = require('b4a')
const unslab = require('unslab')

const MIN_CONNECTION_TIME = 15000

const VERY_LOW_PRIORITY = 0
const LOW_PRIORITY = 1
const NORMAL_PRIORITY = 2
const HIGH_PRIORITY = 3
const VERY_HIGH_PRIORITY = 4

module.exports = class PeerInfo extends EventEmitter {
  constructor({ publicKey, relayAddresses }) {
    super()

    this.publicKey = unslab(publicKey)
    this.relayAddresses = relayAddresses

    this.reconnecting = true
    this.proven = false
    this.connectedTime = -1
    this.disconnectedTime = 0
    this.banned = false
    this.tried = false
    this.explicit = false
    this.waiting = false
    this.forceRelaying = false

    // Set by the Swarm
    this.queued = false
    this.client = false
    this.topics = [] // TODO: remove on next major (check with mafintosh for context)

    this.attempts = 0
    this.priority = NORMAL_PRIORITY

    // Used by shuffled-priority-queue
    this._index = 0

    // Used for flush management
    this._flushTick = 0

    // Used for topic multiplexing
    this._seenTopics = new Set()
  }

  get server() {
    return !this.client
  }

  get prioritized() {
    return this.priority >= NORMAL_PRIORITY
  }

  _getPriority() {
    const peerIsStale = this.tried && !this.proven
    if (peerIsStale || this.attempts > 3) return VERY_LOW_PRIORITY
    if (this.attempts === 3) return LOW_PRIORITY
    if (this.attempts === 2) return HIGH_PRIORITY
    if (this.attempts === 1) return VERY_HIGH_PRIORITY
    return NORMAL_PRIORITY
  }

  _connected() {
    this.proven = true
    this.connectedTime = Date.now()
  }

  _disconnected() {
    this.disconnectedTime = Date.now()
    if (this.connectedTime > -1) {
      if (this.disconnectedTime - this.connectedTime >= MIN_CONNECTION_TIME) this.attempts = 0 // fast retry
      this.connectedTime = -1
    }
    this.attempts++
  }

  _deprioritize() {
    this.attempts = 3
  }

  _reset() {
    this.client = false
    this.proven = false
    this.tried = false
    this.attempts = 0
  }

  _updatePriority() {
    if (this.explicit && this.attempts > 3) this._deprioritize()
    if (this.banned || this.queued || this.attempts > 3) return false
    this.priority = this._getPriority()
    return true
  }

  _topic(topic) {
    const topicString = b4a.toString(topic, 'hex')
    if (this._seenTopics.has(topicString)) return
    this._seenTopics.add(topicString)
    this.topics.push(topic)
    this.emit('topic', topic)
  }

  reconnect(val) {
    this.reconnecting = !!val
  }

  ban(val) {
    this.banned = !!val
  }

  shouldGC() {
    return !(this.banned || this.queued || this.explicit || this.waiting)
  }
}
const BulkTimer = require('./bulk-timer')

const BACKOFF_JITTER = 500
const BACKOFF_S = 1000 + Math.round(BACKOFF_JITTER * Math.random())
const BACKOFF_M = 5000 + Math.round(2 * BACKOFF_JITTER * Math.random())
const BACKOFF_L = 15000 + Math.round(4 * BACKOFF_JITTER * Math.random())
const BACKOFF_X = 1000 * 60 * 10 + Math.round(240 * BACKOFF_JITTER * Math.random())

module.exports = class RetryTimer {
  constructor(
    push,
    { backoffs = [BACKOFF_S, BACKOFF_M, BACKOFF_L, BACKOFF_X], jitter = BACKOFF_JITTER } = {}
  ) {
    this.jitter = jitter
    this.backoffs = backoffs

    this._sTimer = new BulkTimer(backoffs[0] + Math.round(jitter * Math.random()), push)
    this._mTimer = new BulkTimer(backoffs[1] + Math.round(jitter * Math.random()), push)
    this._lTimer = new BulkTimer(backoffs[2] + Math.round(jitter * Math.random()), push)
    this._xTimer = new BulkTimer(backoffs[3] + Math.round(jitter * Math.random()), push)
  }

  _selectRetryTimer(peerInfo) {
    if (peerInfo.banned || !peerInfo.reconnecting) return null

    if (peerInfo.attempts > 3) {
      return peerInfo.explicit ? this._xTimer : null
    }

    if (peerInfo.attempts === 0) return this._sTimer
    if (peerInfo.proven) {
      switch (peerInfo.attempts) {
        case 1:
          return this._sTimer
        case 2:
          return this._mTimer
        case 3:
          return this._lTimer
      }
    } else {
      switch (peerInfo.attempts) {
        case 1:
          return this._mTimer
        case 2:
          return this._lTimer
        case 3:
          return this._lTimer
      }
    }

    return null
  }

  add(peerInfo) {
    const timer = this._selectRetryTimer(peerInfo)
    if (!timer) return false

    timer.add(peerInfo)
    return true
  }

  destroy() {
    this._sTimer.destroy()
    this._mTimer.destroy()
    this._lTimer.destroy()
    this._xTimer.destroy()
  }
}
{
  "name": "hyperswarm",
  "version": "4.17.0",
  "description": "A distributed networking stack for connecting peers",
  "files": [
    "index.js",
    "lib/**.js"
  ],
  "imports": {
    "events": {
      "bare": "bare-events",
      "default": "events"
    },
    "process": {
      "bare": "bare-process",
      "default": "process"
    }
  },
  "dependencies": {
    "b4a": "^1.3.1",
    "bare-events": "^2.2.0",
    "hyperdht": "^6.21.0",
    "safety-catch": "^1.0.2",
    "shuffled-priority-queue": "^2.1.0",
    "streamx": "^2.22.1",
    "unslab": "^1.3.0"
  },
  "devDependencies": {
    "bare-process": "^4.2.2",
    "brittle": "^3.0.2",
    "hypercore-crypto": "^3.4.0",
    "lunte": "^1.3.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^2.0.0",
    "which-runtime": "^1.3.2"
  },
  "scripts": {
    "format": "prettier --write .",
    "lint": "prettier --check . && lunte",
    "test": "npm run test:node && npm run test:bare",
    "test:generate": "brittle -r test/all.js test/*.js",
    "test:node": "brittle-node --coverage test/*.js",
    "test:bare": "bare test/all.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/holepunchto/hyperswarm.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "contributors": [
    "David Mark Clements (@davidmarkclem)",
    "Andrew Osheroff (@andrewosh)"
  ],
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/holepunchto/hyperswarm/issues"
  },
  "homepage": "https://github.com/holepunchto/hyperswarm"
}
const { EventEmitter } = require('events')

module.exports = class RoutingTable extends EventEmitter {
  constructor (id, opts) {
    if (!opts) opts = {}

    super()

    this.id = id
    this.k = opts.k || 20
    this.size = 0
    this.rows = new Array(id.length * 8)
  }

  add (node) {
    const i = this._diff(node.id)

    let row = this.rows[i]

    if (!row) {
      row = this.rows[i] = new Row(this, i)
      this.emit('row', row)
    }

    const len = row.nodes.length
    if (!row.add(node, this.k)) return false

    this.size += row.nodes.length - len
    return true
  }

  remove (id) {
    const i = this._diff(id)
    const row = this.rows[i]
    if (!row) return false
    if (!row.remove(id)) return false
    this.size--
    return true
  }

  get (id) {
    const i = this._diff(id)
    const row = this.rows[i]
    if (!row) return null
    return row.get(id)
  }

  has (id) {
    return this.get(id) !== null
  }

  random () {
    let n = (Math.random() * this.size) | 0

    for (let i = 0; i < this.rows.length; i++) {
      const r = this.rows[i]
      if (!r) continue
      if (n < r.nodes.length) return r.nodes[n]
      n -= r.nodes.length
    }

    return null
  }

  closest (id, k) {
    if (!k) k = this.k

    const result = []
    const d = this._diff(id)

    // push close nodes
    for (let i = d; i >= 0 && result.length < k; i--) this._pushNodes(i, k, result)

    // if we don't have enough close nodes, populate from other rows, re the paper
    for (let i = d + 1; i < this.rows.length && result.length < k; i++) this._pushNodes(i, k, result)

    return result
  }

  _pushNodes (i, k, result) {
    const row = this.rows[i]
    if (!row) return

    const missing = Math.min(k - result.length, row.nodes.length)
    for (let j = 0; j < missing; j++) result.push(row.nodes[j])
  }

  toArray () {
    return this.closest(this.id, Infinity)
  }

  _diff (id) {
    for (let i = 0; i < id.length; i++) {
      const a = id[i]
      const b = this.id[i]

      if (a !== b) return i * 8 + Math.clz32(a ^ b) - 24
    }

    return this.rows.length - 1
  }
}

class Row extends EventEmitter {
  constructor (table, index) {
    super()

    this.data = null // can be used be upstream for whatevs
    this.byteOffset = index >> 3
    this.index = index
    this.table = table
    this.nodes = []
  }

  add (node) {
    const id = node.id

    let l = 0
    let r = this.nodes.length - 1

    while (l <= r) {
      const m = (l + r) >> 1
      const c = this.compare(id, this.nodes[m].id)

      if (c === 0) {
        this.nodes[m] = node
        return true
      }

      if (c < 0) r = m - 1
      else l = m + 1
    }

    if (this.nodes.length >= this.table.k) {
      this.emit('full', node)
      return false
    }

    this.insert(l, node)
    return true
  }

  remove (id) {
    let l = 0
    let r = this.nodes.length - 1

    while (l <= r) {
      const m = (l + r) >> 1
      const c = this.compare(id, this.nodes[m].id)

      if (c === 0) {
        this.splice(m)
        return true
      }

      if (c < 0) r = m - 1
      else l = m + 1
    }

    return false
  }

  get (id) {
    let l = 0
    let r = this.nodes.length - 1

    while (l <= r) {
      const m = (l + r) >> 1
      const node = this.nodes[m]
      const c = this.compare(id, node.id)

      if (c === 0) return node
      if (c < 0) r = m - 1
      else l = m + 1
    }

    return null
  }

  insert (i, node) {
    this.nodes.push(node) // push node or null or whatevs, just trying to not be polymorphic
    for (let j = this.nodes.length - 1; j > i; j--) this.nodes[j] = this.nodes[j - 1]
    this.nodes[i] = node
    this.emit('add', node)
  }

  splice (i) {
    for (; i < this.nodes.length - 1; i++) this.nodes[i] = this.nodes[i + 1]
    this.emit('remove', this.nodes.pop())
  }

  // very likely they diverge after a couple of bytes so a simple impl, like this is prop fastest vs Buffer.compare
  compare (a, b) {
    for (let i = this.byteOffset; i < a.length; i++) {
      const ai = a[i]
      const bi = b[i]
      if (ai === bi) continue
      return ai < bi ? -1 : 1
    }
    return 0
  }
}
{
  "name": "kademlia-routing-table",
  "version": "1.0.6",
  "description": "XOR based routing table used for P2P networks such as a Kademlia DHT.",
  "main": "index.js",
  "files": [
    "index.js"
  ],
  "imports": {
    "events": {
      "bare": "bare-events",
      "default": "events"
    }
  },
  "dependencies": {
    "bare-events": "^2.2.0"
  },
  "devDependencies": {
    "brittle": "^3.3.2",
    "standard": "^17.1.0"
  },
  "scripts": {
    "test": "standard && brittle test.js"
  },
  "keywords": [
    "kademlia",
    "p2p",
    "k-bucket",
    "k-buckets",
    "xor",
    "routing",
    "distributed",
    "systems"
  ],
  "repository": {
    "type": "git",
    "url": "https://github.com/mafintosh/kademlia-routing-table.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/mafintosh/kademlia-routing-table/issues"
  },
  "homepage": "https://github.com/mafintosh/kademlia-routing-table"
}
module.exports = assert

class AssertionError extends Error {}
AssertionError.prototype.name = 'AssertionError'

/**
 * Minimal assert function
 * @param  {any} t Value to check if falsy
 * @param  {string=} m Optional assertion error message
 * @throws {AssertionError}
 */
function assert (t, m) {
  if (!t) {
    var err = new AssertionError(m)
    if (Error.captureStackTrace) Error.captureStackTrace(err, assert)
    throw err
  }
}
{
  "name": "nanoassert",
  "version": "2.0.0",
  "description": "Nanoscale assertion module",
  "main": "index.js",
  "dependencies": {},
  "devDependencies": {
    "tape": "^4.9.1"
  },
  "scripts": {
    "test": "tape test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/emilbayes/nanoassert.git"
  },
  "keywords": [
    "assert",
    "unassert",
    "power-assert",
    "tiny",
    "nano",
    "pico"
  ],
  "author": "Emil Bay <github@tixz.dk>",
  "license": "ISC",
  "bugs": {
    "url": "https://github.com/emilbayes/nanoassert/issues"
  },
  "homepage": "https://github.com/emilbayes/nanoassert#readme"
}
module.exports = class NatSampler {
  constructor () {
    this.host = null
    this.port = 0
    this.size = 0

    this._a = null
    this._b = null
    this._threshold = 0
    this._top = 0
    this._samples = []
  }

  add (host, port) {
    const a = this._bump(host, port, 2)
    const b = this._bump(host, 0, 1)

    if (this._samples.length < 32) {
      this.size++
      this._threshold = this.size - (this.size < 4 ? 0 : this.size < 8 ? 1 : this.size < 12 ? 2 : 3)
      this._samples.push(a, b)
      this._top += 2
    } else {
      if (this._top === 32) this._top = 0

      const oa = this._samples[this._top]
      this._samples[this._top++] = a
      oa.hits--

      const ob = this._samples[this._top]
      this._samples[this._top++] = b
      ob.hits--
    }

    if (this._a === null || this._a.hits < a.hits) this._a = a
    if (this._b === null || this._b.hits < b.hits) this._b = b

    if (this._a.hits >= this._threshold) {
      this.host = this._a.host
      this.port = this._a.port
    } else if (this._b.hits >= this._threshold) {
      this.host = this._b.host
      this.port = 0
    } else {
      this.host = null
      this.port = 0
    }

    return a.hits
  }

  _bump (host, port, inc) {
    for (let i = 0; i < 4; i++) {
      const j = (this._top - inc - (2 * i)) & 31
      if (j >= this._samples.length) return { host, port, hits: 1 }
      const s = this._samples[j]
      if (s.port === port && s.host === host) {
        s.hits++
        return s
      }
    }
    return { host, port, hits: 1 }
  }
}
{
  "name": "nat-sampler",
  "version": "1.0.1",
  "description": "Sample addresses to figure out if a host + port is consistent",
  "main": "index.js",
  "dependencies": {},
  "devDependencies": {
    "standard": "^16.0.3",
    "tape": "^5.2.2"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/mafintosh/nat-sampler.git"
  },
  "scripts": {
    "test": "standard && tape test.js"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/mafintosh/nat-sampler/issues"
  },
  "homepage": "https://github.com/mafintosh/nat-sampler"
}
/* eslint-disable camelcase */
const sodium = require('sodium-universal')
const assert = require('nanoassert')
const b4a = require('b4a')

const DHLEN = sodium.crypto_scalarmult_ed25519_BYTES
const PKLEN = sodium.crypto_scalarmult_ed25519_BYTES
const SCALARLEN = sodium.crypto_scalarmult_ed25519_BYTES
const SKLEN = sodium.crypto_sign_SECRETKEYBYTES
const ALG = 'Ed25519'

module.exports = {
  DHLEN,
  PKLEN,
  SCALARLEN,
  SKLEN,
  ALG,
  name: ALG,
  generateKeyPair,
  dh
}

function generateKeyPair (privKey) {
  if (privKey) return generateSeedKeyPair(privKey.subarray(0, 32))

  const keyPair = {}
  keyPair.secretKey = b4a.alloc(SKLEN)
  keyPair.publicKey = b4a.alloc(PKLEN)

  sodium.crypto_sign_keypair(keyPair.publicKey, keyPair.secretKey)
  return keyPair
}

function generateSeedKeyPair (seed) {
  const keyPair = {}
  keyPair.secretKey = b4a.alloc(SKLEN)
  keyPair.publicKey = b4a.alloc(PKLEN)

  sodium.crypto_sign_seed_keypair(keyPair.publicKey, keyPair.secretKey, seed)
  return keyPair
}

function dh (publicKey, { scalar, secretKey }) {
  // tweaked keys expose scalar directly
  if (!scalar) {
    assert(secretKey.byteLength === SKLEN)

    // libsodium stores seed not actual scalar
    const sk = b4a.alloc(64)
    sodium.crypto_hash_sha512(sk, secretKey.subarray(0, 32))
    sk[0] &= 248
    sk[31] &= 127
    sk[31] |= 64

    scalar = sk.subarray(0, 32)
  }

  assert(scalar.byteLength === SCALARLEN)
  assert(publicKey.byteLength === PKLEN)

  const output = b4a.alloc(DHLEN)

  // we clamp if necessary above
  sodium.crypto_scalarmult_ed25519_noclamp(
    output,
    scalar,
    publicKey
  )

  return output
}
{
  "name": "noise-curve-ed",
  "version": "2.1.0",
  "description": "Ed25519 elliptic curve operations for [`noise-handshake`](https://github.com/chm-diederichs/noise-handshake)",
  "main": "index.js",
  "scripts": {
    "test": "npx standard && tape test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/chm-diederichs/noise-curve-ed.git"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "bugs": {
    "url": "https://github.com/chm-diederichs/noise-curve-ed/issues"
  },
  "homepage": "https://github.com/chm-diederichs/noise-curve-ed#readme",
  "dependencies": {
    "b4a": "^1.1.0",
    "nanoassert": "^2.0.0",
    "sodium-universal": "^5.0.0"
  },
  "devDependencies": {
    "noise-handshake": "^3.0.0",
    "standard": "^16.0.3",
    "tape": "^5.2.2"
  }
}
const sodium = require('sodium-universal')
const b4a = require('b4a')

module.exports = class CipherState {
  constructor (key) {
    this.key = key || null
    this.nonce = 0
    this.CIPHER_ALG = 'ChaChaPoly'
  }

  initialiseKey (key) {
    this.key = key
    this.nonce = 0
  }

  setNonce (nonce) {
    this.nonce = nonce
  }

  encrypt (plaintext, ad) {
    if (!this.hasKey) return plaintext
    if (!ad) ad = b4a.alloc(0)

    const ciphertext = encryptWithAD(this.key, this.nonce, ad, plaintext)
    if (ciphertext.length > 65535) throw new Error(`ciphertext length of ${ciphertext.length} exceeds maximum Noise message length of 65535`)
    this.nonce++

    return ciphertext
  }

  decrypt (ciphertext, ad) {
    if (!this.hasKey) return ciphertext
    if (!ad) ad = b4a.alloc(0)
    if (ciphertext.length > 65535) throw new Error(`ciphertext length of ${ciphertext.length} exceeds maximum Noise message length of 65535`)

    const plaintext = decryptWithAD(this.key, this.nonce, ad, ciphertext)
    this.nonce++

    return plaintext
  }

  get hasKey () {
    return this.key !== null
  }

  _clear () {
    sodium.sodium_memzero(this.key)
    this.key = null
    this.nonce = null
  }

  static get MACBYTES () {
    return 16
  }

  static get NONCEBYTES () {
    return 8
  }

  static get KEYBYTES () {
    return 32
  }
}

function encryptWithAD (key, counter, additionalData, plaintext) {
  // for our purposes, additionalData will always be a pubkey so we encode from hex
  if (!b4a.isBuffer(additionalData)) additionalData = b4a.from(additionalData, 'hex')
  if (!b4a.isBuffer(plaintext)) plaintext = b4a.from(plaintext, 'hex')

  const nonce = b4a.alloc(sodium.crypto_aead_chacha20poly1305_ietf_NPUBBYTES)
  const view = new DataView(nonce.buffer, nonce.byteOffset, nonce.byteLength)
  view.setUint32(4, counter, true)

  const ciphertext = b4a.alloc(plaintext.byteLength + sodium.crypto_aead_chacha20poly1305_ietf_ABYTES)

  sodium.crypto_aead_chacha20poly1305_ietf_encrypt(ciphertext, plaintext, additionalData, null, nonce, key)
  return ciphertext
}

function decryptWithAD (key, counter, additionalData, ciphertext) {
  // for our purposes, additionalData will always be a pubkey so we encode from hex
  if (!b4a.isBuffer(additionalData)) additionalData = b4a.from(additionalData, 'hex')
  if (!b4a.isBuffer(ciphertext)) ciphertext = b4a.from(ciphertext, 'hex')

  const nonce = b4a.alloc(sodium.crypto_aead_chacha20poly1305_ietf_NPUBBYTES)
  const view = new DataView(nonce.buffer, nonce.byteOffset, nonce.byteLength)
  view.setUint32(4, counter, true)

  const plaintext = b4a.alloc(ciphertext.byteLength - sodium.crypto_aead_chacha20poly1305_ietf_ABYTES)

  sodium.crypto_aead_chacha20poly1305_ietf_decrypt(plaintext, null, ciphertext, additionalData, nonce, key)
  return plaintext
}
/* eslint-disable camelcase */
const {
  crypto_kx_SEEDBYTES,
  crypto_kx_keypair,
  crypto_kx_seed_keypair,
  crypto_scalarmult_BYTES,
  crypto_scalarmult_SCALARBYTES,
  crypto_scalarmult,
  crypto_scalarmult_base
} = require('sodium-universal')

const assert = require('nanoassert')
const b4a = require('b4a')

const DHLEN = crypto_scalarmult_BYTES
const PKLEN = crypto_scalarmult_BYTES
const SKLEN = crypto_scalarmult_SCALARBYTES
const SEEDLEN = crypto_kx_SEEDBYTES
const ALG = '25519'

module.exports = {
  DHLEN,
  PKLEN,
  SKLEN,
  SEEDLEN,
  ALG,
  generateKeyPair,
  generateSeedKeyPair,
  dh
}

function generateKeyPair (privKey) {
  const keyPair = {}

  keyPair.secretKey = privKey || b4a.alloc(SKLEN)
  keyPair.publicKey = b4a.alloc(PKLEN)

  if (privKey) {
    crypto_scalarmult_base(keyPair.publicKey, keyPair.secretKey)
  } else {
    crypto_kx_keypair(keyPair.publicKey, keyPair.secretKey)
  }

  return keyPair
}

function generateSeedKeyPair (seed) {
  assert(seed.byteLength === SKLEN)

  const keyPair = {}
  keyPair.secretKey = b4a.alloc(SKLEN)
  keyPair.publicKey = b4a.alloc(PKLEN)

  crypto_kx_seed_keypair(keyPair.publicKey, keyPair.secretKey, seed)
  return keyPair
}

function dh (publicKey, { secretKey }) {
  assert(secretKey.byteLength === SKLEN)
  assert(publicKey.byteLength === PKLEN)

  const output = b4a.alloc(DHLEN)

  crypto_scalarmult(
    output,
    secretKey,
    publicKey
  )

  return output
}
const hmacBlake2b = require('./hmac')
const b4a = require('b4a')

const HASHLEN = 64

module.exports = {
  hkdf,
  HASHLEN
}

// HMAC-based Extract-and-Expand KDF
// https://www.ietf.org/rfc/rfc5869.txt

function hkdf (salt, inputKeyMaterial, info = '', length = 2 * HASHLEN) {
  const pseudoRandomKey = hkdfExtract(salt, inputKeyMaterial)
  return hkdfExpand(pseudoRandomKey, info, length)
}

function hkdfExtract (salt, inputKeyMaterial) {
  const hmac = b4a.alloc(HASHLEN)
  return hmacDigest(hmac, salt, inputKeyMaterial)
}

function hkdfExpand (key, info, length) {
  // Put in dedicated slab to avoid keeping shared slab from being gc'ed
  const buffer = b4a.allocUnsafeSlow(length)

  const infoBuf = b4a.from(info)
  let prev = infoBuf

  const result = []
  for (let i = 0; i < length; i += HASHLEN) {
    const pos = b4a.from([(i / HASHLEN) + 1])

    const out = buffer.subarray(i, i + HASHLEN)
    result.push(out)

    prev = hmacDigest(out, key, [prev, infoBuf, pos])
  }

  return result
}

function hmacDigest (out, key, input) {
  hmacBlake2b(out, input, key)
  return out
}
/* eslint-disable camelcase */
const b4a = require('b4a')
const { sodium_memzero, crypto_generichash, crypto_generichash_batch } = require('sodium-universal')

const HASHLEN = 64
const BLOCKLEN = 128
const scratch = b4a.alloc(BLOCKLEN * 3)
const HMACKey = scratch.subarray(BLOCKLEN * 0, BLOCKLEN * 1)
const OuterKeyPad = scratch.subarray(BLOCKLEN * 1, BLOCKLEN * 2)
const InnerKeyPad = scratch.subarray(BLOCKLEN * 2, BLOCKLEN * 3)

// Post-fill is done in the cases where someone caught an exception that
// happened before we were able to clear data at the end

module.exports = function hmac (out, batch, key) {
  if (key.byteLength > BLOCKLEN) {
    crypto_generichash(HMACKey.subarray(0, HASHLEN), key)
    sodium_memzero(HMACKey.subarray(HASHLEN))
  } else {
    // Covers key <= BLOCKLEN
    HMACKey.set(key)
    sodium_memzero(HMACKey.subarray(key.byteLength))
  }

  for (let i = 0; i < HMACKey.byteLength; i++) {
    OuterKeyPad[i] = 0x5c ^ HMACKey[i]
    InnerKeyPad[i] = 0x36 ^ HMACKey[i]
  }
  sodium_memzero(HMACKey)

  crypto_generichash_batch(out, [InnerKeyPad].concat(batch))
  sodium_memzero(InnerKeyPad)
  crypto_generichash_batch(out, [OuterKeyPad, out])
  sodium_memzero(OuterKeyPad)
}

module.exports.BYTES = HASHLEN
module.exports.KEYBYTES = BLOCKLEN
const assert = require('nanoassert')
const b4a = require('b4a')

const SymmetricState = require('./symmetric-state')
const { HASHLEN } = require('./hkdf')

const PRESHARE_IS = Symbol('initiator static key preshared')
const PRESHARE_RS = Symbol('responder static key preshared')

const TOK_PSK = Symbol('psk')

const TOK_S = Symbol('s')
const TOK_E = Symbol('e')

const TOK_ES = Symbol('es')
const TOK_SE = Symbol('se')
const TOK_EE = Symbol('ee')
const TOK_SS = Symbol('ss')

const HANDSHAKES = Object.freeze({
  NN: [
    [TOK_E],
    [TOK_E, TOK_EE]
  ],
  NNpsk0: [
    [TOK_PSK, TOK_E],
    [TOK_E, TOK_EE]
  ],
  XX: [
    [TOK_E],
    [TOK_E, TOK_EE, TOK_S, TOK_ES],
    [TOK_S, TOK_SE]
  ],
  XXpsk0: [
    [TOK_PSK, TOK_E],
    [TOK_E, TOK_EE, TOK_S, TOK_ES],
    [TOK_S, TOK_SE]
  ],
  IK: [
    PRESHARE_RS,
    [TOK_E, TOK_ES, TOK_S, TOK_SS],
    [TOK_E, TOK_EE, TOK_SE]
  ],
  XK: [
    PRESHARE_RS,
    [TOK_E, TOK_ES],
    [TOK_E, TOK_EE],
    [TOK_S, TOK_SE]
  ]
})

class Writer {
  constructor () {
    this.size = 0
    this.buffers = []
  }

  push (b) {
    this.size += b.byteLength
    this.buffers.push(b)
  }

  end () {
    const all = b4a.alloc(this.size)
    let offset = 0
    for (const b of this.buffers) {
      all.set(b, offset)
      offset += b.byteLength
    }
    return all
  }
}

class Reader {
  constructor (buf) {
    this.offset = 0
    this.buffer = buf
  }

  shift (n) {
    const start = this.offset
    const end = this.offset += n
    if (end > this.buffer.byteLength) throw new Error('Insufficient bytes')
    return this.buffer.subarray(start, end)
  }

  end () {
    return this.shift(this.buffer.byteLength - this.offset)
  }
}

module.exports = class NoiseState extends SymmetricState {
  constructor (pattern, initiator, staticKeypair, opts = {}) {
    super(opts)

    this.s = staticKeypair || this.curve.generateKeyPair()
    this.e = null

    this.psk = null
    if (opts && opts.psk) this.psk = opts.psk

    this.re = null
    this.rs = null

    this.pattern = pattern
    this.handshake = HANDSHAKES[this.pattern].slice()

    this.isPskHandshake = !!this.psk && hasPskToken(this.handshake)

    this.protocol = b4a.from([
      'Noise',
      this.pattern,
      this.DH_ALG,
      this.CIPHER_ALG,
      'BLAKE2b'
    ].join('_'))

    this.initiator = initiator
    this.complete = false

    this.rx = null
    this.tx = null
    this.hash = null
  }

  initialise (prologue, remoteStatic) {
    if (this.protocol.byteLength <= HASHLEN) this.digest.set(this.protocol)
    else this.mixHash(this.protocol)

    this.chainingKey = b4a.from(this.digest)

    this.mixHash(prologue)

    while (!Array.isArray(this.handshake[0])) {
      const message = this.handshake.shift()

      // handshake steps should be as arrays, only
      // preshare tokens are provided otherwise
      assert(message === PRESHARE_RS || message === PRESHARE_IS,
        'Unexpected pattern')

      const takeRemoteKey = this.initiator
        ? message === PRESHARE_RS
        : message === PRESHARE_IS

      if (takeRemoteKey) this.rs = remoteStatic

      const key = takeRemoteKey ? this.rs : this.s.publicKey
      assert(key != null, 'Remote pubkey required')

      this.mixHash(key)
    }
  }

  final () {
    const [k1, k2] = this.split()

    this.tx = this.initiator ? k1 : k2
    this.rx = this.initiator ? k2 : k1

    this.complete = true
    this.hash = this.getHandshakeHash()

    this._clear()
  }

  recv (buf) {
    const r = new Reader(buf)

    for (const pattern of this.handshake.shift()) {
      switch (pattern) {
        case TOK_PSK :
          this.mixKeyAndHash(this.psk)
          break

        case TOK_E :
          this.re = r.shift(this.curve.PKLEN)
          this.mixHash(this.re)
          if (this.isPskHandshake) this.mixKeyNormal(this.re)
          break

        case TOK_S : {
          const klen = this.hasKey ? this.curve.PKLEN + 16 : this.curve.PKLEN
          this.rs = this.decryptAndHash(r.shift(klen))
          break
        }

        case TOK_EE :
        case TOK_ES :
        case TOK_SE :
        case TOK_SS : {
          const useStatic = keyPattern(pattern, this.initiator)

          const localKey = useStatic.local ? this.s : this.e
          const remoteKey = useStatic.remote ? this.rs : this.re

          this.mixKey(remoteKey, localKey)
          break
        }

        default :
          throw new Error('Unexpected message')
      }
    }

    const payload = this.decryptAndHash(r.end())

    if (!this.handshake.length) this.final()
    return payload
  }

  send (payload = b4a.alloc(0)) {
    const w = new Writer()

    for (const pattern of this.handshake.shift()) {
      switch (pattern) {
        case TOK_PSK :
          this.mixKeyAndHash(this.psk)
          break

        case TOK_E :
          if (this.e === null) this.e = this.curve.generateKeyPair()
          this.mixHash(this.e.publicKey)
          if (this.isPskHandshake) this.mixKeyNormal(this.e.publicKey)
          w.push(this.e.publicKey)
          break

        case TOK_S :
          w.push(this.encryptAndHash(this.s.publicKey))
          break

        case TOK_ES :
        case TOK_SE :
        case TOK_EE :
        case TOK_SS : {
          const useStatic = keyPattern(pattern, this.initiator)

          const localKey = useStatic.local ? this.s : this.e
          const remoteKey = useStatic.remote ? this.rs : this.re

          this.mixKey(remoteKey, localKey)
          break
        }

        default :
          throw new Error('Unexpected message')
      }
    }

    w.push(this.encryptAndHash(payload))
    const response = w.end()

    if (!this.handshake.length) this.final()
    return response
  }

  _clear () {
    super._clear()

    this.e.secretKey.fill(0)
    this.e.publicKey.fill(0)

    this.re.fill(0)

    this.e = null
    this.re = null
  }
}

function keyPattern (pattern, initiator) {
  const ret = {
    local: false,
    remote: false
  }

  switch (pattern) {
    case TOK_EE:
      return ret

    case TOK_ES:
      ret.local ^= !initiator
      ret.remote ^= initiator
      return ret

    case TOK_SE:
      ret.local ^= initiator
      ret.remote ^= !initiator
      return ret

    case TOK_SS:
      ret.local ^= 1
      ret.remote ^= 1
      return ret
  }
}

function hasPskToken (handshake) {
  return handshake.some(x => {
    return Array.isArray(x) && x.indexOf(TOK_PSK) !== -1
  })
}
{
  "name": "noise-handshake",
  "version": "4.2.0",
  "description": "Noise protocol handshake",
  "main": "noise.js",
  "files": [
    "*.js"
  ],
  "scripts": {
    "test": "standard && brittle test/*.js"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/noise-handshake.git"
  },
  "dependencies": {
    "b4a": "^1.1.0",
    "nanoassert": "^2.0.0",
    "sodium-universal": "^5.0.0"
  },
  "devDependencies": {
    "brittle": "^3.3.2",
    "noise-protocol": "chm-diederichs/noise-protocol.git#xx-ephemeral-key",
    "standard": "^16.0.3"
  }
}
const sodium = require('sodium-universal')
const assert = require('nanoassert')
const b4a = require('b4a')
const CipherState = require('./cipher')
const curve = require('./dh')
const { HASHLEN, hkdf } = require('./hkdf')

module.exports = class SymmetricState extends CipherState {
  constructor (opts = {}) {
    super()

    this.curve = opts.curve || curve
    this.digest = b4a.alloc(HASHLEN)
    this.chainingKey = null
    this.offset = 0

    this.DH_ALG = this.curve.ALG
  }

  mixHash (data) {
    accumulateDigest(this.digest, data)
  }

  mixKeyAndHash (key) {
    const [ck, tempH, tempK] = hkdf(this.chainingKey, key, '', 3 * HASHLEN)
    this.chainingKey = ck
    this.mixHash(tempH)
    this.initialiseKey(tempK.subarray(0, 32))
  }

  mixKeyNormal (key) {
    const [ck, tempK] = hkdf(this.chainingKey, key)
    this.chainingKey = ck
    this.initialiseKey(tempK.subarray(0, 32))
  }

  mixKey (remoteKey, localKey) {
    const dh = this.curve.dh(remoteKey, localKey)
    const hkdfResult = hkdf(this.chainingKey, dh)
    this.chainingKey = hkdfResult[0]
    this.initialiseKey(hkdfResult[1].subarray(0, 32))
  }

  encryptAndHash (plaintext) {
    const ciphertext = this.encrypt(plaintext, this.digest)
    accumulateDigest(this.digest, ciphertext)
    return ciphertext
  }

  decryptAndHash (ciphertext) {
    const plaintext = this.decrypt(ciphertext, this.digest)
    accumulateDigest(this.digest, ciphertext)
    return plaintext
  }

  getHandshakeHash (out) {
    if (!out) return this.getHandshakeHash(b4a.alloc(HASHLEN))
    assert(out.byteLength === HASHLEN, `output must be ${HASHLEN} bytes`)

    out.set(this.digest)
    return out
  }

  split () {
    const res = hkdf(this.chainingKey, b4a.alloc(0))
    return res.map(k => k.subarray(0, 32))
  }

  _clear () {
    super._clear()

    sodium.sodium_memzero(this.digest)
    sodium.sodium_memzero(this.chainingKey)

    this.digest = null
    this.chainingKey = null
    this.offset = null

    this.curve = null
  }

  static get alg () {
    return CipherState.alg + '_BLAKE2b'
  }
}

function accumulateDigest (digest, input) {
  const toHash = b4a.concat([digest, input])
  sodium.crypto_generichash(digest, toHash)
}
const b4a = require('b4a')
const c = require('compact-encoding')
const queueTick = require('queue-tick')
const safetyCatch = require('safety-catch')
const unslab = require('unslab')

const MAX_BUFFERED = 32768
const MAX_BACKLOG = Infinity // TODO: impl "open" backpressure
const MAX_BATCH = 8 * 1024 * 1024

class Channel {
  constructor(
    mux,
    info,
    userData,
    protocol,
    aliases,
    id,
    handshake,
    messages,
    onopen,
    onclose,
    ondestroy,
    ondrain
  ) {
    this.userData = userData
    this.protocol = protocol
    this.aliases = aliases
    this.id = id
    this.handshake = null
    this.messages = []

    this.opened = false
    this.closed = false
    this.destroyed = false

    this.onopen = onopen
    this.onclose = onclose
    this.ondestroy = ondestroy
    this.ondrain = ondrain

    this._handshake = handshake
    this._mux = mux
    this._info = info
    this._localId = 0
    this._remoteId = 0
    this._active = 0
    this._extensions = null

    this._decBound = this._dec.bind(this)
    this._decAndDestroyBound = this._decAndDestroy.bind(this)

    this._openedPromise = null
    this._openedResolve = null

    this._destroyedPromise = null
    this._destroyedResolve = null

    for (const m of messages) this.addMessage(m)
  }

  get drained() {
    return this._mux.drained
  }

  fullyOpened() {
    if (this.opened) return Promise.resolve(true)
    if (this.closed) return Promise.resolve(false)
    if (this._openedPromise) return this._openedPromise

    this._openedPromise = new Promise((resolve) => {
      this._openedResolve = resolve
    })
    return this._openedPromise
  }

  fullyClosed() {
    if (this.destroyed) return Promise.resolve()
    if (this._destroyedPromise) return this._destroyedPromise

    this._destroyedPromise = new Promise((resolve) => {
      this._destroyedResolve = resolve
    })
    return this._destroyedPromise
  }

  open(handshake) {
    const id = this._mux._free.length > 0 ? this._mux._free.pop() : this._mux._local.push(null) - 1

    this._info.opened++
    this._info.lastChannel = this
    this._localId = id + 1
    this._mux._local[id] = this

    if (this._remoteId === 0) {
      this._info.outgoing.push(this._localId)
    }

    const state = { buffer: null, start: 2, end: 2 }

    c.uint.preencode(state, this._localId)
    c.string.preencode(state, this.protocol)
    c.optionalBuffer.preencode(state, this.id)
    if (this._handshake) this._handshake.preencode(state, handshake)

    state.buffer = this._mux._alloc(state.end)

    state.buffer[0] = 0
    state.buffer[1] = 1
    c.uint.encode(state, this._localId)
    c.string.encode(state, this.protocol)
    c.optionalBuffer.encode(state, this.id)
    if (this._handshake) this._handshake.encode(state, handshake)

    this._mux._write0(state.buffer)
  }

  _dec() {
    if (--this._active === 0 && this.closed === true) this._destroy()
  }

  _decAndDestroy(err) {
    this._dec()
    this._mux._safeDestroy(err)
  }

  _fullyOpenSoon() {
    this._mux._remote[this._remoteId - 1].session = this
    queueTick(this._fullyOpenOrDestroy.bind(this))
  }

  _fullyOpenOrDestroy() {
    try {
      this._fullyOpen()
    } catch (err) {
      this._mux._safeDestroyBound(err)
    }
  }

  _fullyOpen() {
    if (this.opened === true || this.closed === true) return

    const remote = this._mux._remote[this._remoteId - 1]

    this.handshake = this._handshake ? this._handshake.decode(remote.state) : null
    this._track(this.onopen(this.handshake, this))

    remote.session = this
    remote.state = null
    if (remote.pending !== null) this._drain(remote)
    if (this._mux._destroying === true) return

    this.opened = true
    this._resolveOpen(true)
  }

  _resolveOpen(opened) {
    if (this._openedResolve !== null) {
      this._openedResolve(opened)
      this._openedResolve = this._openedPromise = null
    }
  }

  _resolveDestroyed() {
    if (this._destroyedResolve !== null) {
      this._destroyedResolve()
      this._destroyedResolve = this._destroyedPromise = null
    }
  }

  _drain(remote) {
    for (let i = 0; i < remote.pending.length; i++) {
      const p = remote.pending[i]
      this._mux._buffered -= byteSize(p.state)
      this._recv(p.type, p.state)
      if (this._mux._destroying === true) return
    }

    remote.pending = null
    this._mux._resumeMaybe()
  }

  _track(p) {
    if (isPromise(p) === true) {
      this._active++
      return p.then(this._decBound, this._decAndDestroyBound)
    }

    return null
  }

  _close(isRemote) {
    if (this.closed === true) return
    this.closed = true

    this._info.opened--
    if (this._info.lastChannel === this) this._info.lastChannel = null

    if (this._remoteId > 0) {
      this._mux._remote[this._remoteId - 1] = null
      this._remoteId = 0
      // If remote has acked, we can reuse the local id now
      // otherwise, we need to wait for the "ack" to arrive
      this._mux._free.push(this._localId - 1)
    }

    this._mux._local[this._localId - 1] = null
    this._localId = 0

    this._mux._gc(this._info)
    this._track(this.onclose(isRemote, this))

    if (this._active === 0) this._destroy()

    this._resolveOpen(false)
  }

  _destroy() {
    if (this.destroyed === true) return
    this.destroyed = true
    this._track(this.ondestroy(this))
    this._resolveDestroyed()
  }

  _recv(type, state) {
    if (type < this.messages.length) {
      const m = this.messages[type]
      const p = m.recv(state, this)
      if (m.autoBatch === true) return p
    }
    return null
  }

  cork() {
    this._mux.cork()
  }

  uncork() {
    this._mux.uncork()
  }

  close() {
    if (this.closed === true) return

    const state = { buffer: null, start: 2, end: 2 }

    c.uint.preencode(state, this._localId)

    state.buffer = this._mux._alloc(state.end)

    state.buffer[0] = 0
    state.buffer[1] = 3
    c.uint.encode(state, this._localId)

    this._close(false)
    this._mux._write0(state.buffer)
  }

  addMessage(opts) {
    if (!opts) return this._skipMessage()

    const type = this.messages.length
    const autoBatch = opts.autoBatch !== false
    const encoding = opts.encoding || c.raw
    const onmessage = opts.onmessage || noop

    const s = this
    const typeLen = encodingLength(c.uint, type)

    const m = {
      type,
      autoBatch,
      encoding,
      onmessage,
      recv(state, session) {
        return session._track(m.onmessage(encoding.decode(state), session))
      },
      send(m, session = s) {
        if (session.closed === true) return false

        const mux = session._mux
        const state = { buffer: null, start: 0, end: typeLen }

        if (mux._batch !== null) {
          encoding.preencode(state, m)
          state.buffer = mux._alloc(state.end)

          c.uint.encode(state, type)
          encoding.encode(state, m)

          mux._pushBatch(session._localId, state.buffer)
          return true
        }

        c.uint.preencode(state, session._localId)
        encoding.preencode(state, m)

        state.buffer = mux._alloc(state.end)

        c.uint.encode(state, session._localId)
        c.uint.encode(state, type)
        encoding.encode(state, m)

        mux.drained = mux.stream.write(state.buffer)

        return mux.drained
      }
    }

    this.messages.push(m)

    return m
  }

  _skipMessage() {
    const type = this.messages.length
    const m = {
      type,
      encoding: c.raw,
      onmessage: noop,
      recv(state, session) {},
      send(m, session) {}
    }

    this.messages.push(m)
    return m
  }
}

module.exports = class Protomux {
  constructor(stream, { alloc } = {}) {
    if (stream.userData === null) stream.userData = this

    this.isProtomux = true
    this.stream = stream
    this.corked = 0
    this.drained = true

    this._alloc =
      alloc || (typeof stream.alloc === 'function' ? stream.alloc.bind(stream) : b4a.allocUnsafe)
    this._safeDestroyBound = this._safeDestroy.bind(this)
    this._uncorkBound = this.uncork.bind(this)

    this._remoteBacklog = 0
    this._buffered = 0
    this._paused = false
    this._remote = []
    this._local = []
    this._free = []
    this._batch = null
    this._batchState = null

    this._infos = new Map()
    this._notify = new Map()
    // stream.destroyed flips asynchronously on streamx-based transports.
    this._destroying = false

    this.stream.on('data', this._ondata.bind(this))
    this.stream.on('drain', this._ondrain.bind(this))
    this.stream.on('end', this._onend.bind(this))
    this.stream.on('error', noop) // we handle this in "close"
    this.stream.on('close', this._shutdown.bind(this))
  }

  static from(stream, opts) {
    if (stream.userData && stream.userData.isProtomux) return stream.userData
    if (stream.isProtomux) return stream
    return new this(stream, opts)
  }

  static isProtomux(mux) {
    return typeof mux === 'object' && mux.isProtomux === true
  }

  *[Symbol.iterator]() {
    for (const session of this._local) {
      if (session !== null) yield session
    }
  }

  isIdle() {
    return this._local.length === this._free.length
  }

  cork() {
    if (++this.corked === 1) {
      this._batch = []
      this._batchState = { buffer: null, start: 0, end: 1 }
    }
  }

  uncork() {
    if (--this.corked === 0) {
      this._sendBatch(this._batch, this._batchState)
      this._batch = null
      this._batchState = null
    }
  }

  getLastChannel({ protocol, id = null }) {
    const key = toKey(protocol, id)
    const info = this._infos.get(key)
    if (info) return info.lastChannel
    return null
  }

  pair({ protocol, id = null }, notify) {
    this._notify.set(toKey(protocol, id), notify)
  }

  unpair({ protocol, id = null }) {
    this._notify.delete(toKey(protocol, id))
  }

  opened({ protocol, id = null }) {
    const key = toKey(protocol, id)
    const info = this._infos.get(key)
    return info ? info.opened > 0 : false
  }

  createChannel({
    userData = null,
    protocol,
    aliases = [],
    id = null,
    unique = true,
    handshake = null,
    messages = [],
    onopen = noop,
    onclose = noop,
    ondestroy = noop,
    ondrain = noop
  }) {
    if (this.stream.destroyed) return null

    const info = this._get(protocol, id, aliases)
    if (unique && info.opened > 0) return null

    if (info.incoming.length === 0) {
      return new Channel(
        this,
        info,
        userData,
        protocol,
        aliases,
        id,
        handshake,
        messages,
        onopen,
        onclose,
        ondestroy,
        ondrain
      )
    }

    this._remoteBacklog--

    const remoteId = info.incoming.shift()
    const r = this._remote[remoteId - 1]
    if (r === null) return null

    const session = new Channel(
      this,
      info,
      userData,
      protocol,
      aliases,
      id,
      handshake,
      messages,
      onopen,
      onclose,
      ondestroy,
      ondrain
    )

    session._remoteId = remoteId
    session._fullyOpenSoon()

    return session
  }

  _pushBatch(localId, buffer) {
    if (this._batchState.end >= MAX_BATCH) {
      this._sendBatch(this._batch, this._batchState)
      this._batch = []
      this._batchState = { buffer: null, start: 0, end: 1 }
    }

    if (this._batch.length === 0 || this._batch[this._batch.length - 1].localId !== localId) {
      this._batchState.end++
      c.uint.preencode(this._batchState, localId)
    }
    c.buffer.preencode(this._batchState, buffer)
    this._batch.push({ localId, buffer })
  }

  _sendBatch(batch, state) {
    if (batch.length === 0) return

    let prev = batch[0].localId

    state.buffer = this._alloc(state.end)
    state.buffer[state.start++] = 0
    state.buffer[state.start++] = 0

    c.uint.encode(state, prev)

    for (let i = 0; i < batch.length; i++) {
      const b = batch[i]
      if (prev !== b.localId) {
        state.buffer[state.start++] = 0
        c.uint.encode(state, (prev = b.localId))
      }
      c.buffer.encode(state, b.buffer)
    }

    this.drained = this.stream.write(state.buffer)
  }

  _get(protocol, id, aliases = []) {
    const key = toKey(protocol, id)

    let info = this._infos.get(key)
    if (info) return info

    info = {
      key,
      protocol,
      aliases: [],
      id,
      pairing: 0,
      opened: 0,
      incoming: [],
      outgoing: [],
      lastChannel: null
    }
    this._infos.set(key, info)

    for (const alias of aliases) {
      const key = toKey(alias, id)
      info.aliases.push(key)

      this._infos.set(key, info)
    }

    return info
  }

  _gc(info) {
    if (info.opened === 0 && info.outgoing.length === 0 && info.incoming.length === 0) {
      this._infos.delete(info.key)

      for (const alias of info.aliases) this._infos.delete(alias)
    }
  }

  _ondata(buffer) {
    if (buffer.byteLength === 0) return // ignore empty frames...
    try {
      const state = { buffer, start: 0, end: buffer.byteLength }
      this._decode(c.uint.decode(state), state)
    } catch (err) {
      this._safeDestroy(err)
    }
  }

  _ondrain() {
    this.drained = true

    for (const s of this._local) {
      if (s !== null) s._track(s.ondrain(s))
    }
  }

  _onend() {
    // TODO: support half open mode for the users who wants that here
    this.stream.end()
  }

  _decode(remoteId, state) {
    const type = c.uint.decode(state)

    if (remoteId === 0) {
      return this._oncontrolsession(type, state)
    }

    const r = remoteId <= this._remote.length ? this._remote[remoteId - 1] : null

    // if the channel is closed ignore - could just be a pipeline message...
    if (r === null) return null

    if (r.pending !== null) {
      this._bufferMessage(r, type, state)
      return null
    }

    return r.session._recv(type, state)
  }

  _oncontrolsession(type, state) {
    switch (type) {
      case 0:
        this._onbatch(state)
        break

      case 1:
        // return the promise back up as this has sideeffects so we can batch reply
        return this._onopensession(state)

      case 2:
        this._onrejectsession(state)
        break

      case 3:
        this._onclosesession(state)
        break
    }

    return null
  }

  _bufferMessage(r, type, { buffer, start, end }) {
    const state = { buffer, start, end } // copy
    r.pending.push({ type, state })
    this._buffered += byteSize(state)
    this._pauseMaybe()
  }

  _pauseMaybe() {
    if (this._paused === true || this._buffered <= MAX_BUFFERED) return
    this._paused = true
    this.stream.pause()
  }

  _resumeMaybe() {
    if (this._paused === false || this._buffered > MAX_BUFFERED) return
    this._paused = false
    this.stream.resume()
  }

  _onbatch(state) {
    const end = state.end
    let remoteId = c.uint.decode(state)

    let waiting = null

    while (state.end > state.start) {
      const len = c.uint.decode(state)
      if (len === 0) {
        remoteId = c.uint.decode(state)
        continue
      }
      state.end = state.start + len
      // if batch contains more than one message, cork it so we reply back with a batch
      if (end !== state.end && waiting === null) {
        waiting = []
        this.cork()
      }
      const p = this._decode(remoteId, state)
      if (waiting !== null && p !== null) waiting.push(p)
      state.start = state.end
      state.end = end
    }

    if (waiting !== null) {
      // the waiting promises are not allowed to throw but we destroy the stream in case we are wrong
      Promise.all(waiting).then(this._uncorkBound, this._safeDestroyBound)
    }
  }

  _onopensession(state) {
    const remoteId = c.uint.decode(state)
    const protocol = c.string.decode(state)
    const id = unslab(c.optionalBuffer.decode(state))

    // remote tried to open the control session - auto reject for now
    // as we can use as an explicit control protocol declaration if we need to
    if (remoteId === 0) {
      this._rejectSession(0)
      return null
    }

    const rid = remoteId - 1
    const info = this._get(protocol, id)

    // allow the remote to grow the ids by one
    if (this._remote.length === rid) {
      this._remote.push(null)
    }

    if (rid >= this._remote.length || this._remote[rid] !== null) {
      throw new Error('Invalid open message')
    }

    if (info.outgoing.length > 0) {
      const localId = info.outgoing.shift()
      const session = this._local[localId - 1]

      if (session === null) {
        // we already closed the channel - ignore
        this._free.push(localId - 1)
        return null
      }

      this._remote[rid] = { state, pending: null, session: null }

      session._remoteId = remoteId
      session._fullyOpen()
      return null
    }

    const copyState = { buffer: state.buffer, start: state.start, end: state.end }
    this._remote[rid] = { state: copyState, pending: [], session: null }

    if (++this._remoteBacklog > MAX_BACKLOG) {
      throw new Error('Remote exceeded backlog')
    }

    info.pairing++
    info.incoming.push(remoteId)

    return this._requestSession(protocol, id, info).catch(this._safeDestroyBound)
  }

  _onrejectsession(state) {
    const localId = c.uint.decode(state)

    // TODO: can be done smarter...
    for (const info of this._infos.values()) {
      const i = info.outgoing.indexOf(localId)
      if (i === -1) continue

      info.outgoing.splice(i, 1)

      const session = this._local[localId - 1]

      this._free.push(localId - 1)
      if (session !== null) session._close(true)

      this._gc(info)
      return
    }

    throw new Error('Invalid reject message')
  }

  _onclosesession(state) {
    const remoteId = c.uint.decode(state)

    if (remoteId === 0) return // ignore

    const rid = remoteId - 1
    const r = rid < this._remote.length ? this._remote[rid] : null

    if (r === null) return

    if (r.session !== null) r.session._close(true)
  }

  async _requestSession(protocol, id, info) {
    const notify = this._notify.get(toKey(protocol, id)) || this._notify.get(toKey(protocol, null))

    if (notify) await notify(id)

    if (--info.pairing > 0) return

    while (info.incoming.length > 0) {
      this._rejectSession(info, info.incoming.shift())
    }

    this._gc(info)
  }

  _rejectSession(info, remoteId) {
    if (remoteId > 0) {
      const r = this._remote[remoteId - 1]

      if (r.pending !== null) {
        for (let i = 0; i < r.pending.length; i++) {
          this._buffered -= byteSize(r.pending[i].state)
        }
      }

      this._remote[remoteId - 1] = null
      this._resumeMaybe()
    }

    const state = { buffer: null, start: 2, end: 2 }

    c.uint.preencode(state, remoteId)

    state.buffer = this._alloc(state.end)

    state.buffer[0] = 0
    state.buffer[1] = 2
    c.uint.encode(state, remoteId)

    this._write0(state.buffer)
  }

  _write0(buffer) {
    if (this._batch !== null) {
      this._pushBatch(0, buffer.subarray(1))
      return
    }

    this.drained = this.stream.write(buffer)
  }

  destroy(err) {
    this._destroying = true
    this.stream.destroy(err)
  }

  _safeDestroy(err) {
    safetyCatch(err)
    this._destroying = true
    this.stream.destroy(err)
  }

  _shutdown() {
    this._destroying = true
    for (const s of this._local) {
      if (s !== null) s._close(true)
    }
  }
}

function noop() {}

function toKey(protocol, id) {
  return protocol + '##' + (id ? b4a.toString(id, 'hex') : '')
}

function byteSize(state) {
  return 512 + (state.end - state.start)
}

function isPromise(p) {
  return !!(p && typeof p.then === 'function')
}

function encodingLength(enc, val) {
  const state = { buffer: null, start: 0, end: 0 }
  enc.preencode(state, val)
  return state.end
}
{
  "name": "protomux",
  "version": "3.11.0",
  "description": "Multiplex multiple message oriented protocols over a stream",
  "main": "index.js",
  "files": [
    "index.js"
  ],
  "dependencies": {
    "b4a": "^1.3.1",
    "compact-encoding": "^3.0.0",
    "queue-tick": "^1.0.0",
    "safety-catch": "^1.0.1",
    "unslab": "^1.3.0"
  },
  "devDependencies": {
    "@hyperswarm/secret-stream": "^6.0.0",
    "brittle": "^3.0.0",
    "lunte": "^1.0.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^2.0.0"
  },
  "scripts": {
    "format": "prettier --write .",
    "lint": "prettier --check . && lunte",
    "test": "npm run test:node && npm run test:bare",
    "test:node": "brittle-node test.js",
    "test:bare": "brittle-bare test.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/holepunchto/protomux.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/holepunchto/protomux/issues"
  },
  "homepage": "https://github.com/holepunchto/protomux"
}
{
  "name": "queue-tick",
  "version": "1.0.1",
  "description": "Next tick shim that prefers process.nextTick over queueMicrotask for compat",
  "main": "./process-next-tick.js",
  "browser": "./queue-microtask.js",
  "dependencies": {},
  "devDependencies": {
    "standard": "^16.0.3",
    "tape": "^5.3.1"
  },
  "scripts": {
    "test": "standard && tape test.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/mafintosh/queue-tick.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/mafintosh/queue-tick/issues"
  },
  "homepage": "https://github.com/mafintosh/queue-tick"
}
module.exports = (typeof process !== 'undefined' && typeof process.nextTick === 'function')
  ? process.nextTick.bind(process)
  : require('./queue-microtask')
module.exports = typeof queueMicrotask === 'function' ? queueMicrotask : (fn) => Promise.resolve().then(fn)
const EventEmitter = require('events')

module.exports = class ReadyResource extends EventEmitter {
  constructor () {
    super()

    this.opening = null
    this.closing = null

    this.opened = false
    this.closed = false
  }

  ready () {
    if (this.opening !== null) return this.opening
    this.opening = open(this)
    return this.opening
  }

  close () {
    if (this.closing !== null) return this.closing
    this.closing = close(this)
    return this.closing
  }

  async _open () {
    // add impl here
  }

  async _close () {
    // add impl here
  }
}

async function open (self) {
  // open after close
  if (self.closing !== null) return

  try {
    await self._open()
  } catch (err) {
    self.close() // safe to run in bg
    throw err
  }

  self.opened = true
  self.emit('ready')
}

async function close (self) {
  try {
    if (self.opened === false && self.opening !== null) await self.opening
  } catch {
    // ignore errors on closing
  }
  if (self.opened === true || self.opening === null) await self._close()
  self.closed = true
  self.emit('close')
}
{
  "name": "ready-resource",
  "version": "1.2.0",
  "description": "Modern single resource management",
  "main": "index.js",
  "imports": {
    "events": {
      "bare": "bare-events",
      "default": "events"
    }
  },
  "types": "index.d.ts",
  "files": [
    "index.js",
    "index.d.ts"
  ],
  "scripts": {
    "test": "standard && brittle test.js"
  },
  "devDependencies": {
    "brittle": "^3.1.0",
    "standard": "^17.0.0"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/ready-resource.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/holepunchto/ready-resource/issues"
  },
  "homepage": "https://github.com/holepunchto/ready-resource",
  "dependencies": {
    "bare-events": "^2.2.0"
  }
}
const b4a = require('b4a')

var EMPTY = []

module.exports = RecordCache

function RecordSet () {
  this.list = []
  this.map = new Map()
}

RecordSet.prototype.add = function (record, value) {
  var k = toString(record)
  var r = this.map.get(k)
  if (r) return false

  r = {index: this.list.length, record: value || record}
  this.list.push(r)
  this.map.set(k, r)
  return true
}

RecordSet.prototype.remove = function (record) {
  var k = toString(record)
  var r = this.map.get(k)
  if (!r) return false

  swap(this.list, r.index, this.list.length - 1)
  this.list.pop()
  this.map.delete(k)
  return true
}

function RecordStore () {
  this.records = new Map()
  this.size = 0
}

RecordStore.prototype.add = function (name, record, value) {
  var r = this.records.get(name)

  if (!r) {
    r = new RecordSet()
    this.records.set(name, r)
  }

  if (r.add(record, value)) {
    this.size++
    return true
  }

  return false
}

RecordStore.prototype.remove = function (name, record, value) {
  var r = this.records.get(name)
  if (!r) return false

  if (r.remove(record, value)) {
    this.size--
    if (!r.map.size) this.records.delete(name)
    return true
  }

  return false
}

RecordStore.prototype.get = function (name) {
  var r = this.records.get(name)
  return r ? r.list : EMPTY
}

function RecordCache (opts) {
  if (!(this instanceof RecordCache)) return new RecordCache(opts)
  if (!opts) opts = {}

  this.maxSize = opts.maxSize || Infinity
  this.maxAge = opts.maxAge || 0

  this._onstale = opts.onStale || opts.onstale || null
  this._fresh = new RecordStore()
  this._stale = new RecordStore()
  this._interval = null
  this._gced = false

  if (this.maxAge && this.maxAge < Infinity) {
    // 2/3 gives us a span of 0.66-1.33 maxAge or avg maxAge
    var tick = Math.ceil(2 / 3 * this.maxAge)
    this._interval = setInterval(this._gcAuto.bind(this), tick)
    if (this._interval.unref) this._interval.unref()
  }
}

Object.defineProperty(RecordCache.prototype, 'size', {
  get: function () {
    return this._fresh.size + this._stale.size
  }
})

RecordCache.prototype.add = function (name, record, value) {
  this._stale.remove(name, record, value)
  if (this._fresh.add(name, record, value) && this._fresh.size > this.maxSize) {
    this._gc()
  }
}

RecordCache.prototype.remove = function (name, record, value) {
  this._fresh.remove(name, record, value)
  this._stale.remove(name, record, value)
}

RecordCache.prototype.get = function (name, n) {
  var a = this._fresh.get(name)
  var b = this._stale.get(name)
  var aLen = a.length
  var bLen = b.length
  var len = aLen + bLen

  if (n > len || !n) n = len
  var result = new Array(n)

  for (var i = 0; i < n; i++) {
    var j = Math.floor(Math.random() * (aLen + bLen))
    if (j < aLen) {
      result[i] = a[j].record
      swap(a, j, --aLen)
    } else {
      j -= aLen
      result[i] = b[j].record
      swap(b, j, --bLen)
    }
  }

  return result
}

RecordCache.prototype._gcAuto = function () {
  if (!this._gced) this._gc()
  this._gced = false
}

RecordCache.prototype._gc = function () {
  if (this._onstale && this._stale.size > 0) this._onstale(this._stale)
  this._stale = this._fresh
  this._fresh = new RecordStore()
  this._gced = true
}

RecordCache.prototype.clear = function () {
  this._gc()
  this._gc()
}

RecordCache.prototype.destroy = function () {
  this.clear()
  clearInterval(this._interval)
  this._interval = null
}

function toString (record) {
  return b4a.isBuffer(record) ? b4a.toString(record, 'hex') : record
}

function swap (list, a, b) {
  var tmp = list[a]
  tmp.index = b
  list[b].index = a
  list[a] = list[b]
  list[b] = tmp
}
{
  "name": "record-cache",
  "version": "1.2.0",
  "description": "Cache optimised for record like things",
  "main": "index.js",
  "dependencies": {
    "b4a": "^1.3.1"
  },
  "devDependencies": {
    "standard": "^10.0.3",
    "tape": "^4.8.0"
  },
  "scripts": {
    "test": "standard && tape test.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/mafintosh/record-cache.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/mafintosh/record-cache/issues"
  },
  "homepage": "https://github.com/mafintosh/record-cache"
}
module.exports = require.addon.bind(require)
{
  "name": "require-addon",
  "version": "1.2.0",
  "description": "Import native addons across JavaScript runtimes",
  "exports": {
    "./package": "./package.json",
    ".": {
      "bare": "./lib/bare.js",
      "node": "./lib/node.js",
      "default": "./lib/default.js"
    }
  },
  "imports": {
    "fs": {
      "bare": "bare-fs",
      "default": "fs"
    },
    "path": {
      "bare": "bare-path",
      "default": "path"
    },
    "url": {
      "bare": "bare-url",
      "default": "url"
    }
  },
  "files": [
    "lib"
  ],
  "scripts": {
    "test": "npm run lint && npm run test:bare && npm run test:node",
    "test:bare": "bare test.js",
    "test:node": "node test.js",
    "lint": "prettier --check .",
    "format": "prettier --write ."
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/require-addon.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/require-addon/issues"
  },
  "homepage": "https://github.com/holepunchto/require-addon#readme",
  "engines": {
    "bare": ">=1.10.0"
  },
  "dependencies": {
    "bare-addon-resolve": "^1.3.0"
  },
  "devDependencies": {
    "bare-bundle": "^1.8.1",
    "bare-bundle-evaluate": "^1.1.0",
    "bare-fs": "^4.0.0",
    "bare-path": "^3.0.0",
    "bare-url": "^2.1.0",
    "brittle": "^3.7.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^2.0.0"
  }
}
module.exports = safetyCatch

function isActuallyUncaught (err) {
  if (!err) return false
  return err instanceof TypeError ||
    err instanceof SyntaxError ||
    err instanceof ReferenceError ||
    err instanceof EvalError ||
    err instanceof RangeError ||
    err instanceof URIError ||
    err.code === 'ERR_ASSERTION' ||
    err.name === 'AssertionError'
}

function throwErrorNT (err) {
  queueMicrotask(() => { throw err })
}

function safetyCatch (err) {
  if (isActuallyUncaught(err)) {
    throwErrorNT(err)
    throw err
  }
}
{
  "name": "safety-catch",
  "version": "1.0.3",
  "description": "Small module that makes sure your catch, caught an actual error and not a programming mistake or assertion",
  "main": "index.js",
  "dependencies": {},
  "devDependencies": {},
  "repository": {
    "type": "git",
    "url": "https://github.com/mafintosh/safety-catch.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/mafintosh/safety-catch/issues"
  },
  "homepage": "https://github.com/mafintosh/safety-catch"
}
const set = require('unordered-set')

module.exports = opts => new ShuffledPriorityQueue(opts)

class ShuffledPriorityQueue {
  constructor (opts) {
    this.priorities = []
    this.equals = (opts && opts.equals) || null
  }

  get length () {
    return this.priorities.reduce(add, 0)
  }

  [Symbol.iterator] () {
    return new Iterator(this)
  }

  head () {
    for (let i = this.priorities.length - 1; i >= 0; i--) {
      const q = this.priorities[i]
      if (q.length) return shuffle(q, 0)
    }
    return null
  }

  tail () {
    for (let i = 0; i < this.priorities.length; i++) {
      const q = this.priorities[i]
      if (q.length) return shuffle(q, 0)
    }
    return null
  }

  prev (prev) {
    if (!prev) return this.tail()
    return next(this.priorities, prev, 1)
  }

  next (prev) {
    if (!prev) return this.head()
    return next(this.priorities, prev, -1)
  }

  shift () {
    return this.remove(this.head())
  }

  pop () {
    return this.remove(this.tail())
  }

  add (val) {
    const prio = val.priority || 0
    while (prio >= this.priorities.length) this.priorities.push([])
    set.add(this.priorities[prio], val)
    return val
  }

  remove (val) {
    if (!val) return null

    if (val._index === undefined) {
      val = this.find(val)
      if (!val) return null
    }

    return set.remove(this.priorities[val.priority || 0], val)
  }

  has (val) {
    if (val._index === undefined) return this.find(val)
    const priority = val.priority || 0
    if (priority >= this.priorities.length) return false
    return set.has(this.priorities[priority], val)
  }

  find (val) {
    if (val._index !== undefined) return val

    const prio = val.priority || 0
    const qs = this.priorities
    if (prio >= qs.length) return null

    const q = qs[prio]

    for (let i = 0; i < q.length; i++) {
      if (this.equals(q[i], val)) return q[i]
    }

    return null
  }
}

class Iterator {
  constructor (queue) {
    this.prev = null
    this.queue = queue
  }

  next () {
    const next = this.queue.next(this.prev)
    this.prev = next
    return { done: !next, value: next }
  }
}

function shuffle (q, i) {
  const ran = i + Math.floor(Math.random() * (q.length - i))
  set.swap(q, q[ran], q[i])
  return q[i]
}

function next (queues, prev, inc) {
  let i = prev.priority || 0
  let j = (prev._index || 0) + 1

  while (true) {
    if (i < 0 || i >= queues.length) return null
    const q = queues[i]

    if (j >= q.length) {
      i += inc
      j = 0
      continue
    }

    return shuffle(q, j)
  }
}

function add (len, b) {
  return len + b.length
}
{
  "name": "shuffled-priority-queue",
  "version": "2.1.0",
  "description": "A priority queue that shuffles elements with the same priority.",
  "main": "index.js",
  "scripts": {
    "test": "standard && tape test.js"
  },
  "dependencies": {
    "unordered-set": "^2.0.1"
  },
  "devDependencies": {
    "standard": "^12.0.1",
    "tape": "^4.9.1"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/mafintosh/shuffled-priority-queue.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/mafintosh/shuffled-priority-queue/issues"
  },
  "homepage": "https://github.com/mafintosh/shuffled-priority-queue"
}
module.exports = class Signal {
  constructor () {
    this._resolve = null
    this._reject = null
    this._promise = null
    this._bind = bind.bind(this)
    this._onerror = clear.bind(this)
    this._onsuccess = clear.bind(this, null)
    this._timers = new Set()
  }

  wait (max) {
    if (!this._promise) {
      this._promise = new Promise(this._bind)
      this._promise.then(this._onsuccess).catch(this._onerror)
    }
    if (max) return this._sleep(max)
    return this._promise
  }

  _sleep (max) {
    const s = new Promise((resolve, reject) => {
      const done = () => {
        this._timers.delete(state)
        resolve(true)
      }
      const id = setTimeout(done, max)
      const state = { id, resolve, reject }
      this._timers.add(state)
    })

    return s
  }

  notify (err) {
    if (!this._promise) return
    const resolve = this._resolve
    const reject = this._reject
    this._promise = null
    if (err) reject(err)
    else resolve(true)
  }
}

function clear (err) {
  for (const { id, resolve, reject } of this._timers) {
    clearTimeout(id)
    if (err) reject(err)
    else resolve(true)
  }
  this._timers.clear()
}

function bind (resolve, reject) {
  this._resolve = resolve
  this._reject = reject
}
{
  "name": "signal-promise",
  "version": "1.0.3",
  "description": "Simple wait/notify promise with optional max wait time",
  "main": "index.js",
  "dependencies": {},
  "devDependencies": {},
  "repository": {
    "type": "git",
    "url": "https://github.com/mafintosh/signal-promise.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/mafintosh/signal-promise/issues"
  },
  "homepage": "https://github.com/mafintosh/signal-promise"
}
require.addon = require('require-addon')
module.exports = require.addon('.', __filename)
const assert = require('assert')
const binding = require('./binding')
const { isNode } = require('which-runtime')

const OPTIONAL = Buffer.from(new ArrayBuffer(0))

module.exports = exports = { ...binding }

// memory

exports.sodium_memzero = function (buf) {
  assert(ArrayBuffer.isView(buf), 'buf must be a typed array')

  binding.sodium_memzero(buf)
}

exports.sodium_mlock = function (buf) {
  assert(ArrayBuffer.isView(buf), 'buf must be a typed array')

  const res = binding.sodium_mlock(buf)

  if (res !== 0) throw new Error('memory lock failed')
}

exports.sodium_munlock = function (buf) {
  assert(ArrayBuffer.isView(buf), 'buf must be a typed array')

  const res = binding.sodium_munlock(buf)

  if (res !== 0) throw new Error('memory unlock failed')
}

exports.sodium_malloc = function (size) {
  assert(size >= 0, 'invalid size')

  const buf = Buffer.from(binding.sodium_malloc(size))
  buf.secure = true
  return buf
}

exports.sodium_free = function (buf) {
  if (!buf || !buf.secure) return

  binding.sodium_free(buf.buffer)
}

exports.sodium_mprotect_noaccess = function (buf) {
  const res = binding.sodium_mprotect_noaccess(buf.buffer)

  if (res !== 0) throw new Error('failed to lock buffer')
}

exports.sodium_mprotect_readonly = function (buf) {
  const res = binding.sodium_mprotect_readonly(buf.buffer)

  if (res !== 0) throw new Error('failed to unlock buffer')
}

exports.sodium_mprotect_readwrite = function (buf) {
  const res = binding.sodium_mprotect_readwrite(buf.buffer)

  if (res !== 0) throw new Error('failed to unlock buffer')
}

// crypto_randombytes

exports.randombytes_buf = function (buffer) {
  assert(ArrayBuffer.isView(buffer), 'buffer must be a typed array')

  binding.randombytes_buf(buffer.buffer, buffer.byteOffset, buffer.byteLength)
}

exports.randombytes_buf_deterministic = function (buffer, seed) {
  assert(ArrayBuffer.isView(buffer), 'buffer must be a typed array')
  assert(ArrayBuffer.isView(seed), 'seed must be a typed array')
  assert(
    seed.byteLength === binding.randombytes_SEEDBYTES,
    "seed must be 'randombytes_SEEDBYTES' bytes"
  )

  binding.randombytes_buf_deterministic(
    buffer.buffer,
    buffer.byteOffset,
    buffer.byteLength,

    seed.buffer,
    seed.byteOffset,
    seed.byteLength
  )
}

// sodium_helpers

exports.sodium_memcmp = function (a, b) {
  assert(ArrayBuffer.isView(a), 'a must be a typed array')
  assert(ArrayBuffer.isView(b), 'b must be a typed array')
  assert(a.byteLength === b.byteLength, 'buffers must be of same length')

  return binding.sodium_memcmp(a, b)
}

exports.sodium_add = function (a, b) {
  assert(ArrayBuffer.isView(a), 'a must be a typed array')
  assert(ArrayBuffer.isView(b), 'b must be a typed array')
  assert(a.byteLength === b.byteLength, 'buffers must be of same length')

  binding.sodium_add(a, b)
}

exports.sodium_sub = function (a, b) {
  assert(ArrayBuffer.isView(a), 'a must be a typed array')
  assert(ArrayBuffer.isView(b), 'b must be a typed array')
  assert(a.byteLength === b.byteLength, 'buffers must be of same length')

  binding.sodium_sub(a, b)
}

exports.sodium_compare = function (a, b) {
  assert(ArrayBuffer.isView(a), 'a must be a typed array')
  assert(ArrayBuffer.isView(b), 'b must be a typed array')
  assert(a.byteLength === b.byteLength, 'buffers must be of same length')

  return binding.sodium_compare(a, b)
}

exports.sodium_is_zero = function (buffer, length) {
  if (length === undefined) length = buffer.byteLength

  assert(ArrayBuffer.isView(buffer), 'buffer must be a typed array')
  assert(length >= 0 && length <= buffer.byteLength, 'invalid length')

  return binding.sodium_is_zero(buffer, length)
}

exports.sodium_pad = function (buffer, unpaddedBuflen, blockSize) {
  assert(ArrayBuffer.isView(buffer), 'buffer must be a typed array')
  assert(unpaddedBuflen <= buffer.byteLength, 'unpadded length cannot exceed buffer length')
  assert(blockSize <= buffer.byteLength, 'block size cannot exceed buffer length')
  assert(blockSize >= 1, 'block size must be at least 1 byte')
  assert(
    buffer.byteLength >= unpaddedBuflen + (blockSize - (unpaddedBuflen % blockSize)),
    'buf not long enough'
  )

  return binding.sodium_pad(buffer, unpaddedBuflen, blockSize)
}

exports.sodium_unpad = function (buffer, paddedBuflen, blockSize) {
  assert(ArrayBuffer.isView(buffer), 'buffer must be a typed array')
  assert(paddedBuflen <= buffer.byteLength, 'unpadded length cannot exceed buffer length')
  assert(blockSize <= buffer.byteLength, 'block size cannot exceed buffer length')
  assert(blockSize >= 1, 'block size must be at least 1 byte')

  return binding.sodium_unpad(buffer, paddedBuflen, blockSize)
}

// crypto_sign

exports.crypto_sign_keypair = function (pk, sk) {
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(
    pk.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
    "pk must be 'crypto_sign_PUBLICKEYBYTES' bytes"
  )
  assert(
    sk.byteLength === binding.crypto_sign_SECRETKEYBYTES,
    "sk must be 'crypto_sign_SECRETKEYBYTES' bytes"
  )

  const res = binding.crypto_sign_keypair(pk, sk)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_sign_seed_keypair = function (pk, sk, seed) {
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(ArrayBuffer.isView(seed), 'seed must be a typed array')
  assert(
    pk.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
    "pk must be 'crypto_sign_PUBLICKEYBYTES' bytes"
  )
  assert(
    sk.byteLength === binding.crypto_sign_SECRETKEYBYTES,
    "sk must be 'crypto_sign_SECRETKEYBYTES' bytes"
  )
  assert(
    seed.byteLength === binding.crypto_sign_SEEDBYTES,
    "seed must be 'crypto_sign_SEEDBYTES' bytes"
  )

  const res = binding.crypto_sign_seed_keypair(pk, sk, seed)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_sign = function (sm, m, sk) {
  assert(ArrayBuffer.isView(sm), 'sm must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(
    sm.byteLength === binding.crypto_sign_BYTES + m.byteLength,
    "sm must be 'm.byteLength + crypto_sign_BYTES' bytes"
  )
  assert(
    sk.byteLength === binding.crypto_sign_SECRETKEYBYTES,
    "sk must be 'crypto_sign_SECRETKEYBYTES' bytes"
  )

  const res = binding.crypto_sign(sm, m, sk)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_sign_open = function (m, sm, pk) {
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(sm), 'sm must be a typed array')
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(
    sm.byteLength >= binding.crypto_sign_BYTES,
    "sm must be at least 'crypto_sign_BYTES' bytes"
  )
  assert(
    m.byteLength === sm.byteLength - binding.crypto_sign_BYTES,
    "m must be 'sm.byteLength - crypto_sign_BYTES' bytes"
  )
  assert(
    pk.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
    "pk must be 'crypto_sign_PUBLICKEYBYTES' bytes"
  )

  const res = binding.crypto_sign_open(m, sm, pk)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_sign_open = function (m, sm, pk) {
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(sm), 'sm must be a typed array')
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(
    sm.byteLength >= binding.crypto_sign_BYTES,
    "sm must be at least 'crypto_sign_BYTES' bytes"
  )
  assert(
    m.byteLength === sm.byteLength - binding.crypto_sign_BYTES,
    "m must be 'sm.byteLength - crypto_sign_BYTES' bytes"
  )
  assert(
    pk.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
    "pk must be 'crypto_sign_PUBLICKEYBYTES' bytes"
  )

  return binding.crypto_sign_open(m, sm, pk)
}

exports.crypto_sign_detached = function (sig, m, sk) {
  assert(ArrayBuffer.isView(sig), 'sig must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(sig.byteLength === binding.crypto_sign_BYTES, "sig must be 'crypto_sign_BYTES' bytes")
  assert(
    sk.byteLength === binding.crypto_sign_SECRETKEYBYTES,
    "sk must be 'crypto_sign_SECRETKEYBYTES' bytes"
  )

  const res = binding.crypto_sign_detached(sig, m, sk)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_sign_verify_detached = function (sig, m, pk) {
  assert(ArrayBuffer.isView(sig), 'sig must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(
    sig.byteLength >= binding.crypto_sign_BYTES,
    "sig must be at least 'crypto_sign_BYTES' bytes"
  )
  assert(
    pk.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
    "pk must be 'crypto_sign_PUBLICKEYBYTES' bytes"
  )

  return binding.crypto_sign_verify_detached(
    sig.buffer,
    sig.byteOffset,
    sig.byteLength,

    m.buffer,
    m.byteOffset,
    m.byteLength,

    pk.buffer,
    pk.byteOffset,
    pk.byteLength
  )
}

exports.crypto_sign_ed25519_sk_to_pk = function (pk, sk) {
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(
    pk.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
    "pk must be 'crypto_sign_PUBLICKEYBYTES' bytes"
  )
  assert(
    sk.byteLength === binding.crypto_sign_SECRETKEYBYTES,
    "sk must be 'crypto_sign_SECRETKEYBYTES' bytes"
  )

  const res = binding.crypto_sign_ed25519_sk_to_pk(pk, sk)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_sign_ed25519_pk_to_curve25519 = function (x25519pk, ed25519pk) {
  assert(ArrayBuffer.isView(x25519pk), 'x25519pk must be a typed array')
  assert(ArrayBuffer.isView(ed25519pk), 'ed25519pk must be a typed array')
  assert(
    x25519pk.byteLength === binding.crypto_box_PUBLICKEYBYTES,
    "x25519pk must be 'crypto_box_PUBLICKEYBYTES' bytes"
  )
  assert(
    ed25519pk.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
    "ed25519pk must be 'crypto_sign_PUBLICKEYBYTES' bytes"
  )

  const res = binding.crypto_sign_ed25519_pk_to_curve25519(x25519pk, ed25519pk)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_sign_ed25519_sk_to_curve25519 = function (x25519sk, ed25519sk) {
  assert(ArrayBuffer.isView(x25519sk), 'x25519sk must be a typed array')
  assert(ArrayBuffer.isView(ed25519sk), 'ed25519sk must be a typed array')
  assert(
    x25519sk.byteLength === binding.crypto_box_SECRETKEYBYTES,
    "x25519sk must be 'crypto_box_SECRETKEYBYTES' bytes"
  )

  const edLen = ed25519sk.byteLength

  assert(
    edLen === binding.crypto_sign_SECRETKEYBYTES || edLen === binding.crypto_box_SECRETKEYBYTES,
    "ed25519sk must be 'crypto_sign_SECRETKEYBYTES' or 'crypto_sign_SECRETKEYBYTES - crypto_sign_PUBLICKEYBYTES' bytes"
  )

  const res = binding.crypto_sign_ed25519_sk_to_curve25519(x25519sk, ed25519sk)

  if (res !== 0) throw new Error('status: ' + res)
}

// crypto_box

exports.crypto_box_keypair = function (pk, sk) {
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(
    pk.byteLength === binding.crypto_box_PUBLICKEYBYTES,
    "pk must be 'crypto_box_PUBLICKEYBYTES' bytes"
  )
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(
    sk.byteLength === binding.crypto_box_SECRETKEYBYTES,
    "sk must be 'crypto_box_SECRETKEYBYTES' bytes"
  )

  const res = binding.crypto_box_keypair(pk, sk)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_box_seed_keypair = function (pk, sk, seed) {
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(
    pk.byteLength === binding.crypto_box_PUBLICKEYBYTES,
    "pk must be 'crypto_box_PUBLICKEYBYTES' bytes"
  )
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(
    sk.byteLength === binding.crypto_box_SECRETKEYBYTES,
    "sk must be 'crypto_box_SECRETKEYBYTES' bytes"
  )
  assert(ArrayBuffer.isView(seed), 'seed must be a typed array')
  assert(
    seed.byteLength === binding.crypto_box_SEEDBYTES,
    "seed must be 'crypto_box_SEEDBYTES' bytes"
  )

  const res = binding.crypto_box_seed_keypair(pk, sk, seed)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_box_easy = function (c, m, n, pk, sk) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(
    c.byteLength === m.byteLength + exports.crypto_box_MACBYTES,
    "c must be 'm.byteLength + crypto_box_MACBYTES' bytes"
  )
  assert(n.byteLength === exports.crypto_box_NONCEBYTES, "n must be 'crypto_box_NONCEBYTES' bytes")
  assert(
    pk.byteLength === exports.crypto_box_PUBLICKEYBYTES,
    "pk must be 'crypto_box_PUBLICKEYBYTES' bytes"
  )
  assert(
    sk.byteLength === exports.crypto_box_SECRETKEYBYTES,
    "sk must be 'crypto_box_SECRETKEYBYTES' bytes"
  )

  const res = binding.crypto_box_easy(c, m, n, pk, sk)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_box_detached = function (c, mac, m, n, pk, sk) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(mac), 'mac must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(mac.byteLength === exports.crypto_box_MACBYTES, "mac must be 'crypto_box_MACBYTES' bytes")
  assert(n.byteLength === exports.crypto_box_NONCEBYTES, "n must be 'crypto_box_NONCEBYTES' bytes")
  assert(
    pk.byteLength === exports.crypto_box_PUBLICKEYBYTES,
    "pk must be 'crypto_box_PUBLICKEYBYTES' bytes"
  )
  assert(
    sk.byteLength === exports.crypto_box_SECRETKEYBYTES,
    "sk must be 'crypto_box_SECRETKEYBYTES' bytes"
  )

  const res = binding.crypto_box_detached(c, mac, m, n, pk, sk)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_box_open_easy = function (m, c, n, pk, sk) {
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(
    c.byteLength >= exports.crypto_box_MACBYTES,
    "c must be at least 'crypto_box_MACBYTES' bytes"
  )
  assert(
    m.byteLength === c.byteLength - exports.crypto_box_MACBYTES,
    "m must be 'c.byteLength - crypto_box_MACBYTES' bytes"
  )
  assert(n.byteLength === exports.crypto_box_NONCEBYTES, "n must be 'crypto_box_NONCEBYTES' bytes")
  assert(
    pk.byteLength === exports.crypto_box_PUBLICKEYBYTES,
    "pk must be 'crypto_box_PUBLICKEYBYTES' bytes"
  )
  assert(
    sk.byteLength === exports.crypto_box_SECRETKEYBYTES,
    "sk must be 'crypto_box_SECRETKEYBYTES' bytes"
  )

  return binding.crypto_box_open_easy(m, c, n, pk, sk)
}

exports.crypto_box_open_detached = function (m, c, mac, n, pk, sk) {
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(mac), 'mac must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(m.byteLength === c.byteLength, "m must be 'c.byteLength' bytes")
  assert(mac.byteLength === exports.crypto_box_MACBYTES, "mac must be 'crypto_box_MACBYTES' bytes")
  assert(n.byteLength === exports.crypto_box_NONCEBYTES, "n must be 'crypto_box_NONCEBYTES' bytes")
  assert(
    pk.byteLength === exports.crypto_box_PUBLICKEYBYTES,
    "pk must be 'crypto_box_PUBLICKEYBYTES' bytes"
  )
  assert(
    sk.byteLength === exports.crypto_box_SECRETKEYBYTES,
    "sk must be 'crypto_box_SECRETKEYBYTES' bytes"
  )

  return binding.crypto_box_open_detached(m, c, mac, n, pk, sk)
}

exports.crypto_box_seal = function (c, m, pk) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(
    c.byteLength === m.byteLength + exports.crypto_box_SEALBYTES,
    "c must be 'm.byteLength + crypto_box_SEALBYTES' bytes"
  )
  assert(
    pk.byteLength === exports.crypto_box_PUBLICKEYBYTES,
    "pk must be 'crypto_box_PUBLICKEYBYTES' bytes"
  )

  const res = binding.crypto_box_seal(c, m, pk)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_box_seal_open = function (m, c, pk, sk) {
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(
    c.byteLength >= exports.crypto_box_SEALBYTES,
    "c must be at least 'crypto_box_SEALBYTES' bytes"
  )
  assert(
    m.byteLength === c.byteLength - exports.crypto_box_SEALBYTES,
    "m must be 'c.byteLength - crypto_box_SEALBYTES' bytes"
  )
  assert(
    pk.byteLength === exports.crypto_box_PUBLICKEYBYTES,
    "pk must be 'crypto_box_PUBLICKEYBYTES' bytes"
  )
  assert(
    sk.byteLength === exports.crypto_box_SECRETKEYBYTES,
    "sk must be 'crypto_box_SECRETKEYBYTES' bytes"
  )

  return binding.crypto_box_seal_open(
    m.buffer,
    m.byteOffset,
    m.byteLength,

    c.buffer,
    c.byteOffset,
    c.byteLength,

    pk.buffer,
    pk.byteOffset,
    pk.byteLength,

    sk.buffer,
    sk.byteOffset,
    sk.byteLength
  )
}

// crypto_secretbox

exports.crypto_secretbox_easy = function (c, m, n, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    c.byteLength === m.byteLength + binding.crypto_secretbox_MACBYTES,
    "c must be 'm.byteLength + crypto_secretbox_MACBYTES' bytes"
  )
  assert(
    n.byteLength === binding.crypto_secretbox_NONCEBYTES,
    "n must be 'crypto_secretbox_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_secretbox_KEYBYTES,
    "k must be 'crypto_secretbox_KEYBYTES' bytes"
  )

  const res = binding.crypto_secretbox_easy(c, m, n, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_secretbox_open_easy = function (m, c, n, k) {
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    c.byteLength >= binding.crypto_secretbox_MACBYTES,
    "c must be at least 'crypto_secretbox_MACBYTES' bytes"
  )
  assert(
    m.byteLength === c.byteLength - binding.crypto_secretbox_MACBYTES,
    "m must be 'c.byteLength - crypto_secretbox_MACBYTES' bytes"
  )
  assert(
    n.byteLength === binding.crypto_secretbox_NONCEBYTES,
    "n must be 'crypto_secretbox_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_secretbox_KEYBYTES,
    "k must be 'crypto_secretbox_KEYBYTES' bytes"
  )

  return binding.crypto_secretbox_open_easy(m, c, n, k)
}

exports.crypto_secretbox_detached = function (c, mac, m, n, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(mac), 'mac must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(
    mac.byteLength === binding.crypto_secretbox_MACBYTES,
    "mac must be 'crypto_secretbox_MACBYTES' bytes"
  )
  assert(
    n.byteLength === binding.crypto_secretbox_NONCEBYTES,
    "n must be 'crypto_secretbox_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_secretbox_KEYBYTES,
    "k must be 'crypto_secretbox_KEYBYTES' bytes"
  )

  const res = binding.crypto_secretbox_detached(c, mac, m, n, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_secretbox_open_detached = function (m, c, mac, n, k) {
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(mac), 'mac must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(m.byteLength === c.byteLength, "m must be 'c.byteLength' bytes")
  assert(
    mac.byteLength === binding.crypto_secretbox_MACBYTES,
    "mac must be 'crypto_secretbox_MACBYTES' bytes"
  )
  assert(
    n.byteLength === binding.crypto_secretbox_NONCEBYTES,
    "n must be 'crypto_secretbox_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_secretbox_KEYBYTES,
    "k must be 'crypto_secretbox_KEYBYTES' bytes"
  )

  return binding.crypto_secretbox_open_detached(m, c, mac, n, k)
}

// crypto_generichash

exports.crypto_generichash = function (output, input, key) {
  if (!key) key = OPTIONAL

  assert(ArrayBuffer.isView(output), 'output must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(
    output.byteLength >= binding.crypto_generichash_BYTES_MIN &&
      output.byteLength <= binding.crypto_generichash_BYTES_MAX,
    'output must be between crypto_generichash_BYTES_MIN and crypto_generichash_BYTES_MAX bytes'
  )

  if (key !== OPTIONAL) {
    assert(ArrayBuffer.isView(key), 'key must be a typed array')
    assert(
      key.byteLength >= binding.crypto_generichash_KEYBYTES_MIN &&
        key.byteLength <= binding.crypto_generichash_KEYBYTES_MAX,
      'key must be between crypto_generichash_KEYBYTES_MIN and crypto_generichash_KEYBYTES_MAX bytes'
    )
  }

  const res = binding.crypto_generichash(
    output.buffer,
    output.byteOffset,
    output.byteLength,

    input.buffer,
    input.byteOffset,
    input.byteLength,

    key.buffer,
    key.byteOffset,
    key.byteLength
  )

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_generichash_batch = function (output, batch, key) {
  assert(ArrayBuffer.isView(output), 'output must be a typed array')

  if (isNode || batch.length < 4) {
    const res = binding.crypto_generichash_batch(output, batch, !!key, key || OPTIONAL)
    if (res !== 0) throw new Error('status: ' + res)
  } else {
    const state = Buffer.alloc(binding.crypto_generichash_STATEBYTES)

    exports.crypto_generichash_init(state, key, output.byteLength)

    for (const buf of batch) {
      exports.crypto_generichash_update(state, buf)
    }

    exports.crypto_generichash_final(state, output)
  }
}

exports.crypto_generichash_keygen = function (key) {
  assert(ArrayBuffer.isView(key), 'key must be a typed array')
  assert(
    key.byteLength === binding.crypto_generichash_KEYBYTES,
    "key must be 'crypto_generichash_KEYBYTES' bytes"
  )

  const res = binding.crypto_generichash_keygen(key.buffer, key.byteOffset, key.byteLength)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_generichash_init = function (state, key, outputLength) {
  if (!key) key = OPTIONAL

  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_generichash_STATEBYTES,
    "state must be 'crypto_generichash_STATEBYTES' bytes"
  )

  const res = binding.crypto_generichash_init(
    state.buffer,
    state.byteOffset,
    state.byteLength,

    key.buffer,
    key.byteOffset,
    key.byteLength,

    outputLength
  )

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_generichash_update = function (state, input) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(
    state.byteLength === binding.crypto_generichash_STATEBYTES,
    "state must be 'crypto_generichash_STATEBYTES' bytes"
  )

  const res = binding.crypto_generichash_update(
    state.buffer,
    state.byteOffset,
    state.byteLength,

    input.buffer,
    input.byteOffset,
    input.byteLength
  )

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_generichash_final = function (state, output) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(ArrayBuffer.isView(output), 'output must be a typed array')
  assert(
    state.byteLength === binding.crypto_generichash_STATEBYTES,
    "state must be 'crypto_generichash_STATEBYTES' bytes"
  )

  const res = binding.crypto_generichash_final(
    state.buffer,
    state.byteOffset,
    state.byteLength,

    output.buffer,
    output.byteOffset,
    output.byteLength
  )

  if (res !== 0) throw new Error('status: ' + res)
}

// secretstream

exports.crypto_secretstream_xchacha20poly1305_keygen = function (k) {
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_secretstream_xchacha20poly1305_KEYBYTES,
    "k must be 'crypto_secretstream_xchacha20poly1305_KEYBYTES' bytes"
  )

  binding.crypto_secretstream_xchacha20poly1305_keygen(k.buffer, k.byteOffset, k.byteLength)
}

exports.crypto_secretstream_xchacha20poly1305_init_push = function (state, header, k) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(ArrayBuffer.isView(header), 'header must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    state.byteLength === binding.crypto_secretstream_xchacha20poly1305_STATEBYTES,
    "state must be 'crypto_secretstream_xchacha20poly1305_STATEBYTES' bytes"
  )
  assert(
    header.byteLength === binding.crypto_secretstream_xchacha20poly1305_HEADERBYTES,
    "header must be 'crypto_secretstream_xchacha20poly1305_HEADERBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_secretstream_xchacha20poly1305_KEYBYTES,
    "k must be 'crypto_secretstream_xchacha20poly1305_KEYBYTES' bytes"
  )

  const res = binding.crypto_secretstream_xchacha20poly1305_init_push(
    state.buffer,
    state.byteOffset,
    state.byteLength,

    header.buffer,
    header.byteOffset,
    header.byteLength,

    k.buffer,
    k.byteOffset,
    k.byteLength
  )

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_secretstream_xchacha20poly1305_init_pull = function (state, header, k) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(ArrayBuffer.isView(header), 'header must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    state.byteLength === binding.crypto_secretstream_xchacha20poly1305_STATEBYTES,
    "state must be 'crypto_secretstream_xchacha20poly1305_STATEBYTES' bytes"
  )
  assert(
    header.byteLength === binding.crypto_secretstream_xchacha20poly1305_HEADERBYTES,
    "header must be 'crypto_secretstream_xchacha20poly1305_HEADERBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_secretstream_xchacha20poly1305_KEYBYTES,
    "k must be 'crypto_secretstream_xchacha20poly1305_KEYBYTES' bytes"
  )

  const res = binding.crypto_secretstream_xchacha20poly1305_init_pull(
    state.buffer,
    state.byteOffset,
    state.byteLength,

    header.buffer,
    header.byteOffset,
    header.byteLength,

    k.buffer,
    k.byteOffset,
    k.byteLength
  )

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_secretstream_xchacha20poly1305_push = function (state, c, m, ad, tag) {
  if (!ad) ad = OPTIONAL

  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(
    state.byteLength === binding.crypto_secretstream_xchacha20poly1305_STATEBYTES,
    "state must be 'crypto_secretstream_xchacha20poly1305_STATEBYTES' bytes"
  )
  assert(
    c.byteLength === m.byteLength + binding.crypto_secretstream_xchacha20poly1305_ABYTES,
    "c must be 'm.byteLength + crypto_secretstream_xchacha20poly1305_ABYTES' bytes"
  )

  const res = binding.crypto_secretstream_xchacha20poly1305_push(
    state.buffer,
    state.byteOffset,
    state.byteLength,

    c.buffer,
    c.byteOffset,
    c.byteLength,

    m.buffer,
    m.byteOffset,
    m.byteLength,

    ad.buffer,
    ad.byteOffset,
    ad.byteLength,

    tag
  )

  if (res < 0) throw new Error('push failed')

  return res
}

exports.crypto_secretstream_xchacha20poly1305_pull = function (state, m, tag, c, ad) {
  if (!ad) ad = OPTIONAL

  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_secretstream_xchacha20poly1305_STATEBYTES,
    "state must be 'crypto_secretstream_xchacha20poly1305_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(tag), 'tag must be a typed array')
  assert(tag.byteLength === 1, 'tag must be 1 byte')

  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(
    c.byteLength >= binding.crypto_secretstream_xchacha20poly1305_ABYTES,
    "c must be at least 'crypto_secretstream_xchacha20poly1305_ABYTES' bytes"
  )
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(
    m.byteLength === c.byteLength - binding.crypto_secretstream_xchacha20poly1305_ABYTES,
    "m must be 'c.byteLength - crypto_secretstream_xchacha20poly1305_ABYTES' bytes"
  )

  const res = binding.crypto_secretstream_xchacha20poly1305_pull(
    state.buffer,
    state.byteOffset,
    state.byteLength,

    m.buffer,
    m.byteOffset,
    m.byteLength,

    tag.buffer,
    tag.byteOffset,
    tag.byteLength,

    c.buffer,
    c.byteOffset,
    c.byteLength,

    ad.buffer,
    ad.byteOffset,
    ad.byteLength
  )

  if (res < 0) throw new Error('pull failed')

  return res
}

exports.crypto_secretstream_xchacha20poly1305_rekey = function (state) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_secretstream_xchacha20poly1305_STATEBYTES,
    "state must be 'crypto_secretstream_xchacha20poly1305_STATEBYTES' bytes"
  )

  binding.crypto_secretstream_xchacha20poly1305_rekey(
    state.buffer,
    state.byteOffset,
    state.byteLength
  )
}

// crypto_stream

exports.crypto_stream = function (c, n, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    n.byteLength === binding.crypto_stream_NONCEBYTES,
    "n must be 'crypto_stream_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_KEYBYTES,
    "k must be 'crypto_stream_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream(c, n, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_xor = function (c, m, n, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(
    n.byteLength === binding.crypto_stream_NONCEBYTES,
    "n must be 'crypto_stream_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_KEYBYTES,
    "k must be 'crypto_stream_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_xor(
    c.buffer,
    c.byteOffset,
    c.byteLength,

    m.buffer,
    m.byteOffset,
    m.byteLength,

    n.buffer,
    n.byteOffset,
    n.byteLength,

    k.buffer,
    k.byteOffset,
    k.byteLength
  )

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_chacha20 = function (c, n, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    n.byteLength === binding.crypto_stream_chacha20_NONCEBYTES,
    "n must be 'crypto_stream_chacha20_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_chacha20_KEYBYTES,
    "k must be 'crypto_stream_chacha20_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_chacha20(c, n, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_chacha20_xor = function (c, m, n, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(
    n.byteLength === binding.crypto_stream_chacha20_NONCEBYTES,
    "n must be 'crypto_stream_chacha20_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_chacha20_KEYBYTES,
    "k must be 'crypto_stream_chacha20_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_chacha20_xor(c, m, n, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_chacha20_xor_ic = function (c, m, n, ic, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(
    n.byteLength === binding.crypto_stream_chacha20_NONCEBYTES,
    "n must be 'crypto_stream_chacha20_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_chacha20_KEYBYTES,
    "k must be 'crypto_stream_chacha20_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_chacha20_xor_ic(c, m, n, ic, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_chacha20_ietf = function (c, n, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    n.byteLength === binding.crypto_stream_chacha20_ietf_NONCEBYTES,
    "n must be 'crypto_stream_chacha20_ietf_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_chacha20_ietf_KEYBYTES,
    "k must be 'crypto_stream_chacha20_ietf_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_chacha20_ietf(c, n, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_chacha20_ietf_xor = function (c, m, n, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(
    n.byteLength === binding.crypto_stream_chacha20_ietf_NONCEBYTES,
    "n must be 'crypto_stream_chacha20_ietf_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_chacha20_ietf_KEYBYTES,
    "k must be 'crypto_stream_chacha20_ietf_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_chacha20_ietf_xor(c, m, n, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_chacha20_ietf_xor_ic = function (c, m, n, ic, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(
    n.byteLength === binding.crypto_stream_chacha20_ietf_NONCEBYTES,
    "n must be 'crypto_stream_chacha20_ietf_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_chacha20_ietf_KEYBYTES,
    "k must be 'crypto_stream_chacha20_ietf_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_chacha20_ietf_xor_ic(c, m, n, ic, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_xchacha20 = function (c, n, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    n.byteLength === binding.crypto_stream_xchacha20_NONCEBYTES,
    "n must be 'crypto_stream_xchacha20_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_xchacha20_KEYBYTES,
    "k must be 'crypto_stream_xchacha20_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_xchacha20(c, n, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_xchacha20_xor = function (c, m, n, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(
    n.byteLength === binding.crypto_stream_xchacha20_NONCEBYTES,
    "n must be 'crypto_stream_xchacha20_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_xchacha20_KEYBYTES,
    "k must be 'crypto_stream_xchacha20_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_xchacha20_xor(c, m, n, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_xchacha20_xor_ic = function (c, m, n, ic, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(
    n.byteLength === binding.crypto_stream_xchacha20_NONCEBYTES,
    "n must be 'crypto_stream_xchacha20_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_xchacha20_KEYBYTES,
    "k must be 'crypto_stream_xchacha20_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_xchacha20_xor_ic(c, m, n, ic, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_salsa20 = function (c, n, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    n.byteLength === binding.crypto_stream_salsa20_NONCEBYTES,
    "n must be 'crypto_stream_salsa20_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_salsa20_KEYBYTES,
    "k must be 'crypto_stream_salsa20_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_salsa20(c, n, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_salsa20_xor = function (c, m, n, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(
    n.byteLength === binding.crypto_stream_salsa20_NONCEBYTES,
    "n must be 'crypto_stream_salsa20_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_salsa20_KEYBYTES,
    "k must be 'crypto_stream_salsa20_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_salsa20_xor(c, m, n, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_salsa20_xor_ic = function (c, m, n, ic, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(
    n.byteLength === binding.crypto_stream_salsa20_NONCEBYTES,
    "n must be 'crypto_stream_salsa20_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_salsa20_KEYBYTES,
    "k must be 'crypto_stream_salsa20_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_salsa20_xor_ic(c, m, n, ic, k)

  if (res !== 0) throw new Error('status: ' + res)
}

// crypto_auth

exports.crypto_auth = function (out, input, k) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(out.byteLength === binding.crypto_auth_BYTES, "out must be 'crypto_auth_BYTES' bytes")
  assert(k.byteLength === binding.crypto_auth_KEYBYTES, "k must be 'crypto_auth_KEYBYTES' bytes")

  const res = binding.crypto_auth(out, input, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_auth_verify = function (h, input, k) {
  assert(ArrayBuffer.isView(h), 'h must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(h.byteLength === binding.crypto_auth_BYTES, "h must be 'crypto_auth_BYTES' bytes")
  assert(k.byteLength === binding.crypto_auth_KEYBYTES, "k must be 'crypto_auth_KEYBYTES' bytes")

  return binding.crypto_auth_verify(h, input, k)
}

// crypto_onetimeauth

exports.crypto_onetimeauth = function (out, input, k) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    out.byteLength === binding.crypto_onetimeauth_BYTES,
    "out must be 'crypto_onetimeauth_BYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_onetimeauth_KEYBYTES,
    "k must be 'crypto_onetimeauth_KEYBYTES' bytes"
  )

  const res = binding.crypto_onetimeauth(out, input, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_onetimeauth_init = function (state, k) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    state.byteLength === binding.crypto_onetimeauth_STATEBYTES,
    "state must be 'crypto_onetimeauth_STATEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_onetimeauth_KEYBYTES,
    "k must be 'crypto_onetimeauth_KEYBYTES' bytes"
  )

  const res = binding.crypto_onetimeauth_init(state, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_onetimeauth_update = function (state, input) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(
    state.byteLength === binding.crypto_onetimeauth_STATEBYTES,
    "state must be 'crypto_onetimeauth_STATEBYTES' bytes"
  )

  const res = binding.crypto_onetimeauth_update(state, input)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_onetimeauth_final = function (state, out) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(
    state.byteLength === binding.crypto_onetimeauth_STATEBYTES,
    "state must be 'crypto_onetimeauth_STATEBYTES' bytes"
  )
  assert(
    out.byteLength === binding.crypto_onetimeauth_BYTES,
    "out must be 'crypto_onetimeauth_BYTES' bytes"
  )

  const res = binding.crypto_onetimeauth_final(state, out)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_onetimeauth_verify = function (h, input, k) {
  assert(ArrayBuffer.isView(h), 'h must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    h.byteLength === binding.crypto_onetimeauth_BYTES,
    "h must be 'crypto_onetimeauth_BYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_onetimeauth_KEYBYTES,
    "k must be 'crypto_onetimeauth_KEYBYTES' bytes"
  )

  return binding.crypto_onetimeauth_verify(h, input, k)
}

// crypto_pwhash

exports.crypto_pwhash = function (out, passwd, salt, opslimit, memlimit, alg) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(ArrayBuffer.isView(salt), 'salt must be a typed array')
  assert(
    out.byteLength >= binding.crypto_pwhash_BYTES_MIN,
    "out must be at least 'crypto_pwhash_BYTES_MIN' bytes"
  )
  assert(
    out.byteLength <= binding.crypto_pwhash_BYTES_MAX,
    "out must be at most 'crypto_pwhash_BYTES_MAX' bytes"
  )
  assert(
    salt.byteLength === binding.crypto_pwhash_SALTBYTES,
    "salt must be 'crypto_pwhash_SALTBYTES' bytes"
  )
  assert(
    opslimit >= binding.crypto_pwhash_OPSLIMIT_MIN,
    "opslimit must be at least 'crypto_pwhash_OPSLIMIT_MIN'"
  )
  assert(
    opslimit <= binding.crypto_pwhash_OPSLIMIT_MAX,
    "opslimit must be at most 'crypto_pwhash_OPSLIMIT_MAX'"
  )
  assert(
    memlimit >= binding.crypto_pwhash_MEMLIMIT_MIN,
    "memlimit must be at least 'crypto_pwhash_MEMLIMIT_MIN'"
  )
  assert(
    memlimit <= binding.crypto_pwhash_MEMLIMIT_MAX,
    "memlimit must be at most 'crypto_pwhash_MEMLIMIT_MAX'"
  )
  assert(alg >= 1 && alg <= 2, 'alg must be either Argon2i 1.3 or Argon2id 1.3')

  const res = binding.crypto_pwhash(out, passwd, salt, opslimit, memlimit, alg)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_pwhash_async = function (
  out,
  passwd,
  salt,
  opslimit,
  memlimit,
  alg,
  callback = undefined
) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(ArrayBuffer.isView(salt), 'salt must be a typed array')
  assert(
    out.byteLength >= binding.crypto_pwhash_BYTES_MIN,
    "out must be at least 'crypto_pwhash_BYTES_MIN' bytes"
  )
  assert(
    out.byteLength <= binding.crypto_pwhash_BYTES_MAX,
    "out must be at most 'crypto_pwhash_BYTES_MAX' bytes"
  )
  assert(
    salt.byteLength === binding.crypto_pwhash_SALTBYTES,
    "salt must be 'crypto_pwhash_SALTBYTES' bytes"
  )
  assert(
    opslimit >= binding.crypto_pwhash_OPSLIMIT_MIN,
    "opslimit must be at least 'crypto_pwhash_OPSLIMIT_MIN'"
  )
  assert(
    opslimit <= binding.crypto_pwhash_OPSLIMIT_MAX,
    "opslimit must be at most 'crypto_pwhash_OPSLIMIT_MAX'"
  )
  assert(
    memlimit >= binding.crypto_pwhash_MEMLIMIT_MIN,
    "memlimit must be at least 'crypto_pwhash_MEMLIMIT_MIN'"
  )
  assert(
    memlimit <= binding.crypto_pwhash_MEMLIMIT_MAX,
    "memlimit must be at most 'crypto_pwhash_MEMLIMIT_MAX'"
  )
  assert(alg >= 1 && alg <= 2, 'alg must be either Argon2i 1.3 or Argon2id 1.3')

  const [done, promise] = checkStatus(callback)

  binding.crypto_pwhash_async(
    out.buffer,
    out.byteOffset,
    out.byteLength,

    passwd.buffer,
    passwd.byteOffset,
    passwd.byteLength,

    salt.buffer,
    salt.byteOffset,
    salt.byteLength,

    opslimit,
    memlimit,
    alg,

    done
  )

  return promise
}

exports.crypto_pwhash_str = function (out, passwd, opslimit, memlimit) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(
    out.byteLength === binding.crypto_pwhash_STRBYTES,
    "out must be 'crypto_pwhash_STRBYTES' bytes"
  )
  assert(typeof opslimit === 'number', 'opslimit must be a number')
  assert(
    opslimit >= binding.crypto_pwhash_OPSLIMIT_MIN,
    "opslimit must be at least 'crypto_pwhash_OPSLIMIT_MIN'"
  )
  assert(
    opslimit <= binding.crypto_pwhash_OPSLIMIT_MAX,
    "opslimit must be at most 'crypto_pwhash_OPSLIMIT_MAX'"
  )
  assert(typeof memlimit === 'number', 'memlimit must be a number')
  assert(
    memlimit >= binding.crypto_pwhash_MEMLIMIT_MIN,
    "memlimit must be at least 'crypto_pwhash_MEMLIMIT_MIN'"
  )
  assert(
    memlimit <= binding.crypto_pwhash_MEMLIMIT_MAX,
    "memlimit must be at most 'crypto_pwhash_MEMLIMIT_MAX'"
  )

  const res = binding.crypto_pwhash_str(out, passwd, opslimit, memlimit)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_pwhash_str_async = function (out, passwd, opslimit, memlimit, callback = undefined) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(
    out.byteLength === binding.crypto_pwhash_STRBYTES,
    "out must be 'crypto_pwhash_STRBYTES' bytes"
  )
  assert(passwd.byteLength > 0, 'passwd must not be empty')
  assert(typeof opslimit === 'number', 'opslimit must be a number')
  assert(
    opslimit >= binding.crypto_pwhash_OPSLIMIT_MIN,
    "opslimit must be at least 'crypto_pwhash_OPSLIMIT_MIN'"
  )
  assert(
    opslimit <= binding.crypto_pwhash_OPSLIMIT_MAX,
    "opslimit must be at most 'crypto_pwhash_OPSLIMIT_MAX'"
  )
  assert(typeof memlimit === 'number', 'memlimit must be a number')
  assert(
    memlimit >= binding.crypto_pwhash_MEMLIMIT_MIN,
    "memlimit must be at least 'crypto_pwhash_MEMLIMIT_MIN'"
  )
  assert(
    memlimit <= binding.crypto_pwhash_MEMLIMIT_MAX,
    "memlimit must be at most 'crypto_pwhash_MEMLIMIT_MAX'"
  )

  const [done, promise] = checkStatus(callback)

  binding.crypto_pwhash_str_async(
    out.buffer,
    out.byteOffset,
    out.byteLength,

    passwd.buffer,
    passwd.byteOffset,
    passwd.byteLength,

    opslimit,
    memlimit,

    done
  )

  return promise
}

exports.crypto_pwhash_str_verify = function (str, passwd) {
  assert(ArrayBuffer.isView(str), 'str must be a typed array')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(
    str.byteLength === binding.crypto_pwhash_STRBYTES,
    "str must be 'crypto_pwhash_STRBYTES' bytes"
  )

  return binding.crypto_pwhash_str_verify(str, passwd)
}

exports.crypto_pwhash_str_verify_async = function (str, passwd, callback = undefined) {
  assert(ArrayBuffer.isView(str), 'str must be a typed array')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(
    str.byteLength === binding.crypto_pwhash_STRBYTES,
    "str must be 'crypto_pwhash_STRBYTES' bytes"
  )
  assert(passwd.byteLength > 0, 'passwd must not be empty')

  const [done, promise] = checkStatus(callback, true)

  binding.crypto_pwhash_str_verify_async(
    str.buffer,
    str.byteOffset,
    str.byteLength,

    passwd.buffer,
    passwd.byteOffset,
    passwd.byteLength,

    done
  )

  return promise
}

exports.crypto_pwhash_str_needs_rehash = function (str, opslimit, memlimit) {
  assert(ArrayBuffer.isView(str), 'str must be a typed array')
  assert(
    str.byteLength === binding.crypto_pwhash_STRBYTES,
    "str must be 'crypto_pwhash_STRBYTES' bytes"
  )
  assert(
    opslimit >= binding.crypto_pwhash_OPSLIMIT_MIN,
    "opslimit must be at least 'crypto_pwhash_OPSLIMIT_MIN'"
  )
  assert(
    opslimit <= binding.crypto_pwhash_OPSLIMIT_MAX,
    "opslimit must be at most 'crypto_pwhash_OPSLIMIT_MAX'"
  )
  assert(
    memlimit >= binding.crypto_pwhash_MEMLIMIT_MIN,
    "memlimit must be at least 'crypto_pwhash_MEMLIMIT_MIN'"
  )
  assert(
    memlimit <= binding.crypto_pwhash_MEMLIMIT_MAX,
    "memlimit must be at most 'crypto_pwhash_MEMLIMIT_MAX'"
  )

  return binding.crypto_pwhash_str_needs_rehash(str, opslimit, memlimit)
}

exports.crypto_pwhash_scryptsalsa208sha256 = function (out, passwd, salt, opslimit, memlimit) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(ArrayBuffer.isView(salt), 'salt must be a typed array')
  assert(
    out.byteLength >= binding.crypto_pwhash_scryptsalsa208sha256_BYTES_MIN,
    "out must be at least 'crypto_pwhash_scryptsalsa208sha256_BYTES_MIN' bytes"
  )
  assert(
    out.byteLength <= binding.crypto_pwhash_scryptsalsa208sha256_BYTES_MAX,
    "out must be at most 'crypto_pwhash_scryptsalsa208sha256_BYTES_MAX' bytes"
  )
  assert(
    salt.byteLength === binding.crypto_pwhash_scryptsalsa208sha256_SALTBYTES,
    "salt must be 'crypto_pwhash_scryptsalsa208sha256_SALTBYTES' bytes"
  )
  assert(
    opslimit >= binding.crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MIN,
    "opslimit must be at least 'crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MIN'"
  )
  assert(
    opslimit <= binding.crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MAX,
    "opslimit must be at most 'crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MAX'"
  )
  assert(
    memlimit >= binding.crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MIN,
    "memlimit must be at least 'crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MIN'"
  )
  assert(
    memlimit <= binding.crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MAX,
    "memlimit must be at most 'crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MAX'"
  )

  const res = binding.crypto_pwhash_scryptsalsa208sha256(out, passwd, salt, opslimit, memlimit)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_pwhash_scryptsalsa208sha256_async = function (
  out,
  passwd,
  salt,
  opslimit,
  memlimit,
  callback = undefined
) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(ArrayBuffer.isView(salt), 'salt must be a typed array')
  assert(
    out.byteLength >= binding.crypto_pwhash_scryptsalsa208sha256_BYTES_MIN,
    "out must be at least 'crypto_pwhash_scryptsalsa208sha256_BYTES_MIN' bytes"
  )
  assert(
    out.byteLength <= binding.crypto_pwhash_scryptsalsa208sha256_BYTES_MAX,
    "out must be at most 'crypto_pwhash_scryptsalsa208sha256_BYTES_MAX' bytes"
  )
  assert(passwd.byteLength > 0, 'passwd must not be empty')
  assert(
    salt.byteLength === binding.crypto_pwhash_scryptsalsa208sha256_SALTBYTES,
    "salt must be 'crypto_pwhash_scryptsalsa208sha256_SALTBYTES' bytes"
  )
  assert(
    opslimit >= binding.crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MIN,
    "opslimit must be at least 'crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MIN'"
  )
  assert(
    opslimit <= binding.crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MAX,
    "opslimit must be at most 'crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MAX'"
  )
  assert(
    memlimit >= binding.crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MIN,
    "memlimit must be at least 'crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MIN'"
  )
  assert(
    memlimit <= binding.crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MAX,
    "memlimit must be at most 'crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MAX'"
  )

  const [done, promise] = checkStatus(callback)

  binding.crypto_pwhash_scryptsalsa208sha256_async(
    out.buffer,
    out.byteOffset,
    out.byteLength,

    passwd.buffer,
    passwd.byteOffset,
    passwd.byteLength,

    salt.buffer,
    salt.byteOffset,
    salt.byteLength,

    opslimit,
    memlimit,

    done
  )

  return promise
}

exports.crypto_pwhash_scryptsalsa208sha256_str_async = function (
  out,
  passwd,
  opslimit,
  memlimit,
  callback = undefined
) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(
    out.byteLength === binding.crypto_pwhash_scryptsalsa208sha256_STRBYTES,
    "out must be 'crypto_pwhash_scryptsalsa208sha256_STRBYTES' bytes"
  )
  assert(passwd.byteLength > 0, 'passwd must not be empty')
  assert(
    opslimit >= binding.crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MIN,
    "opslimit must be at least 'crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MIN'"
  )
  assert(
    opslimit <= binding.crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MAX,
    "opslimit must be at most 'crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MAX'"
  )
  assert(
    memlimit >= binding.crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MIN,
    "memlimit must be at least 'crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MIN'"
  )
  assert(
    memlimit <= binding.crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MAX,
    "memlimit must be at most 'crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MAX'"
  )

  const [done, promise] = checkStatus(callback)

  binding.crypto_pwhash_scryptsalsa208sha256_str_async(
    out.buffer,
    out.byteOffset,
    out.byteLength,

    passwd.buffer,
    passwd.byteOffset,
    passwd.byteLength,

    opslimit,
    memlimit,

    done
  )

  return promise
}

exports.crypto_pwhash_scryptsalsa208sha256_str = function (out, passwd, opslimit, memlimit) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(
    out.byteLength === binding.crypto_pwhash_scryptsalsa208sha256_STRBYTES,
    "out must be 'crypto_pwhash_scryptsalsa208sha256_STRBYTES' bytes"
  )
  assert(passwd.byteLength > 0, 'passwd must not be empty')
  assert(
    opslimit >= binding.crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MIN,
    "opslimit must be at least 'crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MIN'"
  )
  assert(
    opslimit <= binding.crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MAX,
    "opslimit must be at most 'crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MAX'"
  )
  assert(
    memlimit >= binding.crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MIN,
    "memlimit must be at least 'crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MIN'"
  )
  assert(
    memlimit <= binding.crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MAX,
    "memlimit must be at most 'crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MAX'"
  )

  const res = binding.crypto_pwhash_scryptsalsa208sha256_str(out, passwd, opslimit, memlimit)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_pwhash_scryptsalsa208sha256_str_verify_async = function (
  str,
  passwd,
  callback = undefined
) {
  assert(ArrayBuffer.isView(str), 'str must be a typed array')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(
    str.byteLength === binding.crypto_pwhash_scryptsalsa208sha256_STRBYTES,
    "str must be 'crypto_pwhash_scryptsalsa208sha256_STRBYTES' bytes"
  )
  assert(passwd.byteLength > 0, 'passwd must not be empty')

  const [done, promise] = checkStatus(callback, true)

  binding.crypto_pwhash_scryptsalsa208sha256_str_verify_async(
    str.buffer,
    str.byteOffset,
    str.byteLength,

    passwd.buffer,
    passwd.byteOffset,
    passwd.byteLength,

    done
  )

  return promise
}

exports.crypto_pwhash_scryptsalsa208sha256_str_verify = function (str, passwd) {
  assert(ArrayBuffer.isView(str), 'str must be a typed array')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(
    str.byteLength === binding.crypto_pwhash_scryptsalsa208sha256_STRBYTES,
    "str must be 'crypto_pwhash_scryptsalsa208sha256_STRBYTES' bytes"
  )
  assert(passwd.byteLength > 0, 'passwd must not be empty')

  return binding.crypto_pwhash_scryptsalsa208sha256_str_verify(str, passwd)
}

exports.crypto_pwhash_scryptsalsa208sha256_str_needs_rehash = function (str, opslimit, memlimit) {
  assert(ArrayBuffer.isView(str), 'str must be a typed array')
  assert(
    str.byteLength === binding.crypto_pwhash_scryptsalsa208sha256_STRBYTES,
    "str must be 'crypto_pwhash_scryptsalsa208sha256_STRBYTES' bytes"
  )
  assert(
    opslimit >= binding.crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MIN,
    "opslimit must be at least 'crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MIN'"
  )
  assert(
    opslimit <= binding.crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MAX,
    "opslimit must be at most 'crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MAX'"
  )
  assert(
    memlimit >= binding.crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MIN,
    "memlimit must be at least 'crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MIN'"
  )
  assert(
    memlimit <= binding.crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MAX,
    "memlimit must be at most 'crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MAX'"
  )

  return binding.crypto_pwhash_scryptsalsa208sha256_str_needs_rehash(str, opslimit, memlimit)
}

// crypto_kx

exports.crypto_kx_keypair = function (pk, sk) {
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(
    pk.byteLength === binding.crypto_kx_PUBLICKEYBYTES,
    "pk must be 'crypto_kx_PUBLICKEYBYTES' bytes"
  )
  assert(
    sk.byteLength === binding.crypto_kx_SECRETKEYBYTES,
    "sk must be 'crypto_kx_SECRETKEYBYTES' bytes"
  )

  const res = binding.crypto_kx_keypair(pk, sk)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_kx_seed_keypair = function (pk, sk, seed) {
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(ArrayBuffer.isView(seed), 'seed must be a typed array')
  assert(
    pk.byteLength === binding.crypto_kx_PUBLICKEYBYTES,
    "pk must be 'crypto_kx_PUBLICKEYBYTES' bytes"
  )
  assert(
    sk.byteLength === binding.crypto_kx_SECRETKEYBYTES,
    "sk must be 'crypto_kx_SECRETKEYBYTES' bytes"
  )
  assert(
    seed.byteLength === binding.crypto_kx_SEEDBYTES,
    "seed must be 'crypto_kx_SEEDBYTES' bytes"
  )

  const res = binding.crypto_kx_seed_keypair(pk, sk, seed)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_kx_client_session_keys = function (rx, tx, clientPk, clientSk, serverPk) {
  if (!rx) rx = undefined
  if (!tx) tx = undefined

  assert(rx || tx, 'at least one session key must be specified')

  if (rx) {
    assert(ArrayBuffer.isView(rx), 'rx must be a typed array')
    assert(
      rx.byteLength === binding.crypto_kx_SESSIONKEYBYTES,
      "rx must be 'crypto_kx_SESSIONKEYBYTES' bytes"
    )
  }

  if (tx) {
    assert(ArrayBuffer.isView(tx), 'tx must be a typed array')
    assert(
      tx.byteLength === binding.crypto_kx_SESSIONKEYBYTES,
      "tx must be 'crypto_kx_SESSIONKEYBYTES' bytes"
    )
  }

  assert(ArrayBuffer.isView(clientPk), 'clientPk must be a typed array')
  assert(ArrayBuffer.isView(clientSk), 'clientSk must be a typed array')
  assert(ArrayBuffer.isView(serverPk), 'serverPk must be a typed array')
  assert(
    clientPk.byteLength === binding.crypto_kx_PUBLICKEYBYTES,
    "clientPk must be 'crypto_kx_PUBLICKEYBYTES' bytes"
  )
  assert(
    clientSk.byteLength === binding.crypto_kx_SECRETKEYBYTES,
    "clientSk must be 'crypto_kx_SECRETKEYBYTES' bytes"
  )
  assert(
    serverPk.byteLength === binding.crypto_kx_PUBLICKEYBYTES,
    "serverPk must be 'crypto_kx_PUBLICKEYBYTES' bytes"
  )

  const res = binding.crypto_kx_client_session_keys(rx, tx, clientPk, clientSk, serverPk)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_kx_server_session_keys = function (rx, tx, serverPk, serverSk, clientPk) {
  if (!rx) rx = undefined
  if (!tx) tx = undefined

  assert(rx || tx, 'at least one session key must be specified')

  if (rx) {
    assert(ArrayBuffer.isView(rx), 'rx must be a typed array')
    assert(
      rx.byteLength === binding.crypto_kx_SESSIONKEYBYTES,
      "rx must be 'crypto_kx_SESSIONKEYBYTES' bytes"
    )
  }

  if (tx) {
    assert(ArrayBuffer.isView(tx), 'tx must be a typed array')
    assert(
      tx.byteLength === binding.crypto_kx_SESSIONKEYBYTES,
      "tx must be 'crypto_kx_SESSIONKEYBYTES' bytes"
    )
  }

  assert(ArrayBuffer.isView(serverPk), 'serverPk must be a typed array')
  assert(ArrayBuffer.isView(serverSk), 'serverSk must be a typed array')
  assert(ArrayBuffer.isView(clientPk), 'clientPk must be a typed array')
  assert(
    serverPk.byteLength === binding.crypto_kx_PUBLICKEYBYTES,
    "serverPk must be 'crypto_kx_PUBLICKEYBYTES' bytes"
  )
  assert(
    serverSk.byteLength === binding.crypto_kx_SECRETKEYBYTES,
    "serverSk must be 'crypto_kx_SECRETKEYBYTES' bytes"
  )
  assert(
    clientPk.byteLength === binding.crypto_kx_PUBLICKEYBYTES,
    "clientPk must be 'crypto_kx_PUBLICKEYBYTES' bytes"
  )

  const res = binding.crypto_kx_server_session_keys(rx, tx, serverPk, serverSk, clientPk)

  if (res !== 0) throw new Error('status: ' + res)
}

// crypto_scalarmult

exports.crypto_scalarmult_base = function (q, n) {
  assert(ArrayBuffer.isView(q), 'q must be a typed array')
  assert(
    q.byteLength === binding.crypto_scalarmult_BYTES,
    "q must be 'crypto_scalarmult_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.crypto_scalarmult_SCALARBYTES,
    "n must be 'crypto_scalarmult_SCALARBYTES' bytes"
  )

  const res = binding.crypto_scalarmult_base(q, n)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_scalarmult = function (q, n, p) {
  assert(ArrayBuffer.isView(q), 'q must be a typed array')
  assert(
    q.byteLength === binding.crypto_scalarmult_BYTES,
    "q must be 'crypto_scalarmult_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.crypto_scalarmult_SCALARBYTES,
    "n must be 'crypto_scalarmult_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(p), 'p must be a typed array')
  assert(
    p.byteLength === binding.crypto_scalarmult_BYTES,
    "p must be 'crypto_scalarmult_BYTES' bytes"
  )

  const res = binding.crypto_scalarmult(q, n, p)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_scalarmult_ed25519_base = function (q, n) {
  assert(ArrayBuffer.isView(q), 'q must be a typed array')
  assert(
    q.byteLength === binding.crypto_scalarmult_ed25519_BYTES,
    "q must be 'crypto_scalarmult_ed25519_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.crypto_scalarmult_ed25519_SCALARBYTES,
    "n must be 'crypto_scalarmult_ed25519_SCALARBYTES' bytes"
  )

  const res = binding.crypto_scalarmult_ed25519_base(q, n)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_scalarmult_ed25519 = function (q, n, p) {
  assert(ArrayBuffer.isView(q), 'q must be a typed array')
  assert(
    q.byteLength === binding.crypto_scalarmult_ed25519_BYTES,
    "q must be 'crypto_scalarmult_ed25519_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.crypto_scalarmult_ed25519_SCALARBYTES,
    "n must be 'crypto_scalarmult_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(p), 'p must be a typed array')
  assert(
    p.byteLength === binding.crypto_scalarmult_ed25519_BYTES,
    "p must be 'crypto_scalarmult_ed25519_BYTES' bytes"
  )

  const res = binding.crypto_scalarmult_ed25519(q, n, p)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_core_ed25519_is_valid_point = function (p) {
  assert(ArrayBuffer.isView(p), 'p must be a typed array')
  assert(
    p.byteLength === binding.crypto_core_ed25519_BYTES,
    "p must be 'crypto_core_ed25519_BYTES' bytes"
  )

  return binding.crypto_core_ed25519_is_valid_point(p)
}

exports.crypto_core_ed25519_from_uniform = function (p, r) {
  assert(ArrayBuffer.isView(p), 'p must be a typed array')
  assert(
    p.byteLength === binding.crypto_core_ed25519_BYTES,
    "p must be 'crypto_core_ed25519_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(r), 'r must be a typed array')
  assert(
    r.byteLength === binding.crypto_core_ed25519_UNIFORMBYTES,
    "r must be 'crypto_core_ed25519_UNIFORMBYTES' bytes"
  )

  const res = binding.crypto_core_ed25519_from_uniform(p, r)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_scalarmult_ed25519_base_noclamp = function (q, n) {
  assert(ArrayBuffer.isView(q), 'q must be a typed array')
  assert(
    q.byteLength === binding.crypto_scalarmult_ed25519_BYTES,
    "q must be 'crypto_scalarmult_ed25519_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.crypto_scalarmult_ed25519_SCALARBYTES,
    "n must be 'crypto_scalarmult_ed25519_SCALARBYTES' bytes"
  )

  const res = binding.crypto_scalarmult_ed25519_base_noclamp(q, n)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_scalarmult_ed25519_noclamp = function (q, n, p) {
  assert(ArrayBuffer.isView(q), 'q must be a typed array')
  assert(
    q.byteLength === binding.crypto_scalarmult_ed25519_BYTES,
    "q must be 'crypto_scalarmult_ed25519_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.crypto_scalarmult_ed25519_SCALARBYTES,
    "n must be 'crypto_scalarmult_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(p), 'p must be a typed array')
  assert(
    p.byteLength === binding.crypto_scalarmult_ed25519_BYTES,
    "p must be 'crypto_scalarmult_ed25519_BYTES' bytes"
  )

  const res = binding.crypto_scalarmult_ed25519_noclamp(q, n, p)

  if (res !== 0) throw new Error('status: ' + res)
}

// crypto_core

exports.crypto_core_ed25519_add = function (r, p, q) {
  assert(ArrayBuffer.isView(r), 'r must be a typed array')
  assert(
    r.byteLength === binding.crypto_core_ed25519_BYTES,
    "r must be 'crypto_core_ed25519_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(p), 'p must be a typed array')
  assert(
    p.byteLength === binding.crypto_core_ed25519_BYTES,
    "p must be 'crypto_core_ed25519_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(q), 'q must be a typed array')
  assert(
    q.byteLength === binding.crypto_core_ed25519_BYTES,
    "q must be 'crypto_core_ed25519_BYTES' bytes"
  )

  const res = binding.crypto_core_ed25519_add(r, p, q)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_core_ed25519_sub = function (r, p, q) {
  assert(ArrayBuffer.isView(r), 'r must be a typed array')
  assert(
    r.byteLength === binding.crypto_core_ed25519_BYTES,
    "r must be 'crypto_core_ed25519_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(p), 'p must be a typed array')
  assert(
    p.byteLength === binding.crypto_core_ed25519_BYTES,
    "p must be 'crypto_core_ed25519_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(q), 'q must be a typed array')
  assert(
    q.byteLength === binding.crypto_core_ed25519_BYTES,
    "q must be 'crypto_core_ed25519_BYTES' bytes"
  )

  const res = binding.crypto_core_ed25519_sub(r, p, q)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_core_ed25519_scalar_random = function (r) {
  assert(ArrayBuffer.isView(r), 'r must be a typed array')
  assert(
    r.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "r must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )

  binding.crypto_core_ed25519_scalar_random(r)
}

exports.crypto_core_ed25519_scalar_reduce = function (r, s) {
  assert(ArrayBuffer.isView(r), 'r must be a typed array')
  assert(
    r.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "r must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(s), 's must be a typed array')
  assert(
    s.byteLength === binding.crypto_core_ed25519_NONREDUCEDSCALARBYTES,
    "s must be 'crypto_core_ed25519_NONREDUCEDSCALARBYTES' bytes"
  )

  binding.crypto_core_ed25519_scalar_reduce(r, s)
}

exports.crypto_core_ed25519_scalar_invert = function (recip, s) {
  assert(ArrayBuffer.isView(recip), 'recip must be a typed array')
  assert(
    recip.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "recip must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(s), 's must be a typed array')
  assert(
    s.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "s must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )

  binding.crypto_core_ed25519_scalar_invert(recip, s)
}

exports.crypto_core_ed25519_scalar_negate = function (neg, s) {
  assert(ArrayBuffer.isView(neg), 'neg must be a typed array')
  assert(
    neg.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "neg must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(s), 's must be a typed array')
  assert(
    s.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "s must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )

  binding.crypto_core_ed25519_scalar_negate(neg, s)
}

exports.crypto_core_ed25519_scalar_complement = function (comp, s) {
  assert(ArrayBuffer.isView(comp), 'comp must be a typed array')
  assert(
    comp.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "comp must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(s), 's must be a typed array')
  assert(
    s.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "s must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )

  binding.crypto_core_ed25519_scalar_complement(comp, s)
}

exports.crypto_core_ed25519_scalar_add = function (z, x, y) {
  assert(ArrayBuffer.isView(z), 'z must be a typed array')
  assert(
    z.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "z must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(x), 'x must be a typed array')
  assert(
    x.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "x must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(y), 'y must be a typed array')
  assert(
    y.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "y must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )

  binding.crypto_core_ed25519_scalar_add(z, x, y)
}

exports.crypto_core_ed25519_scalar_sub = function (z, x, y) {
  assert(ArrayBuffer.isView(z), 'z must be a typed array')
  assert(
    z.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "z must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(x), 'x must be a typed array')
  assert(
    x.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "x must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(y), 'y must be a typed array')
  assert(
    y.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "y must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )

  binding.crypto_core_ed25519_scalar_sub(z, x, y)
}

// crypto_shorthash

exports.crypto_shorthash = function (out, input, k) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(
    out.byteLength === binding.crypto_shorthash_BYTES,
    "out must be 'crypto_shorthash_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_shorthash_KEYBYTES,
    "k must be 'crypto_shorthash_KEYBYTES' bytes"
  )

  const res = binding.crypto_shorthash(out, input, k)

  if (res !== 0) throw new Error('status: ' + res)
}

// crypto_kdf

exports.crypto_kdf_keygen = function (key) {
  assert(ArrayBuffer.isView(key), 'key must be a typed array')
  assert(key.byteLength === binding.crypto_kdf_KEYBYTES, "key must be 'crypto_kdf_KEYBYTES' bytes")

  binding.crypto_kdf_keygen(key)
}

exports.crypto_kdf_derive_from_key = function (subkey, subkeyId, ctx, key) {
  assert(ArrayBuffer.isView(subkey), 'subkey must be a typed array')
  assert(
    subkey.byteLength >= binding.crypto_kdf_BYTES_MIN,
    "subkey must be at least 'crypto_kdf_BYTES_MIN' bytes"
  )
  assert(
    subkey.byteLength <= binding.crypto_kdf_BYTES_MAX,
    "subkey must be at most 'crypto_kdf_BYTES_MAX' bytes"
  )
  assert(ArrayBuffer.isView(ctx), 'ctx must be a typed array')
  assert(
    ctx.byteLength === binding.crypto_kdf_CONTEXTBYTES,
    "ctx must be 'crypto_kdf_CONTEXTBYTES' bytes"
  )
  assert(ArrayBuffer.isView(key), 'key must be a typed array')
  assert(key.byteLength === binding.crypto_kdf_KEYBYTES, "key must be 'crypto_kdf_KEYBYTES' bytes")

  const res = binding.crypto_kdf_derive_from_key(subkey, subkeyId, ctx, key)

  if (res !== 0) throw new Error('status: ' + res)
}

// crypto_hash

exports.crypto_hash = function (out, input) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(out.byteLength === binding.crypto_hash_BYTES, "out must be 'crypto_hash_BYTES' bytes")

  const res = binding.crypto_hash(out, input)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_hash_sha256 = function (out, input) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(
    out.byteLength === binding.crypto_hash_sha256_BYTES,
    "out must be 'crypto_hash_sha256_BYTES' bytes"
  )

  const res = binding.crypto_hash_sha256(out, input)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_hash_sha256_init = function (state) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_hash_sha256_STATEBYTES,
    "state must be 'crypto_hash_sha256_STATEBYTES' bytes"
  )

  const res = binding.crypto_hash_sha256_init(state)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_hash_sha256_update = function (state, input) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(
    state.byteLength === binding.crypto_hash_sha256_STATEBYTES,
    "state must be 'crypto_hash_sha256_STATEBYTES' bytes"
  )

  const res = binding.crypto_hash_sha256_update(state, input)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_hash_sha256_final = function (state, out) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_hash_sha256_STATEBYTES,
    "state must be 'crypto_hash_sha256_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(
    out.byteLength === binding.crypto_hash_sha256_BYTES,
    "out must be 'crypto_hash_sha256_BYTES' bytes"
  )

  const res = binding.crypto_hash_sha256_final(state, out)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_hash_sha512 = function (out, input) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(
    out.byteLength === binding.crypto_hash_sha512_BYTES,
    "out must be 'crypto_hash_sha512_BYTES' bytes"
  )

  const res = binding.crypto_hash_sha512(out, input)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_hash_sha512_init = function (state) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_hash_sha512_STATEBYTES,
    "state must be 'crypto_hash_sha512_STATEBYTES' bytes"
  )

  const res = binding.crypto_hash_sha512_init(state)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_hash_sha512_update = function (state, input) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(
    state.byteLength === binding.crypto_hash_sha512_STATEBYTES,
    "state must be 'crypto_hash_sha512_STATEBYTES' bytes"
  )

  const res = binding.crypto_hash_sha512_update(state, input)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_hash_sha512_final = function (state, out) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_hash_sha512_STATEBYTES,
    "state must be 'crypto_hash_sha512_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(
    out.byteLength === binding.crypto_hash_sha512_BYTES,
    "out must be 'crypto_hash_sha512_BYTES' bytes"
  )

  const res = binding.crypto_hash_sha512_final(state, out)

  if (res !== 0) throw new Error('status: ' + res)
}

// crypto_aead

exports.crypto_aead_xchacha20poly1305_ietf_keygen = function (k) {
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_aead_xchacha20poly1305_ietf_KEYBYTES,
    "k must be 'crypto_aead_xchacha20poly1305_ietf_KEYBYTES' bytes"
  )

  binding.crypto_aead_xchacha20poly1305_ietf_keygen(k)
}

exports.crypto_aead_xchacha20poly1305_ietf_encrypt = function (c, m, ad, nsec, npub, k) {
  if (!ad) ad = undefined

  assert(nsec === null, 'nsec must always be set to null')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(
    c.byteLength === m.byteLength + binding.crypto_aead_xchacha20poly1305_ietf_ABYTES,
    "c must be 'm.byteLength + crypto_aead_xchacha20poly1305_ietf_ABYTES' bytes"
  )
  assert(c.byteLength <= 0xffffffff, 'c.byteLength must be a 32bit integer')
  assert(ArrayBuffer.isView(npub), 'npub must be a typed array')
  assert(
    npub.byteLength === binding.crypto_aead_xchacha20poly1305_ietf_NPUBBYTES,
    "npub must be 'crypto_aead_xchacha20poly1305_ietf_NPUBBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_aead_xchacha20poly1305_ietf_KEYBYTES,
    "k must be 'crypto_aead_xchacha20poly1305_ietf_KEYBYTES' bytes"
  )

  const res = binding.crypto_aead_xchacha20poly1305_ietf_encrypt(c, m, ad, npub, k)

  if (res < 0) throw new Error('could not encrypt data')

  return res
}

exports.crypto_aead_xchacha20poly1305_ietf_decrypt = function (m, nsec, c, ad, npub, k) {
  if (!ad) ad = undefined

  assert(nsec === null, 'nsec must always be set to null')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(
    m.byteLength === c.byteLength - binding.crypto_aead_xchacha20poly1305_ietf_ABYTES,
    "m must be 'c.byteLength - crypto_aead_xchacha20poly1305_ietf_ABYTES' bytes"
  )
  assert(m.byteLength <= 0xffffffff, 'm.byteLength must be a 32bit integer')
  assert(ArrayBuffer.isView(npub), 'npub must be a typed array')
  assert(
    npub.byteLength === binding.crypto_aead_xchacha20poly1305_ietf_NPUBBYTES,
    "npub must be 'crypto_aead_xchacha20poly1305_ietf_NPUBBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_aead_xchacha20poly1305_ietf_KEYBYTES,
    "k must be 'crypto_aead_xchacha20poly1305_ietf_KEYBYTES' bytes"
  )

  const res = binding.crypto_aead_xchacha20poly1305_ietf_decrypt(m, c, ad, npub, k)

  if (res < 0) throw new Error('could not verify data')

  return res
}

exports.crypto_aead_xchacha20poly1305_ietf_encrypt_detached = function (
  c,
  mac,
  m,
  ad,
  nsec,
  npub,
  k
) {
  if (!ad) ad = undefined

  assert(nsec === null, 'nsec must always be set to null')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(ArrayBuffer.isView(mac), 'mac must be a typed array')
  assert(
    mac.byteLength === binding.crypto_aead_xchacha20poly1305_ietf_ABYTES,
    "mac must be 'crypto_aead_xchacha20poly1305_ietf_ABYTES' bytes"
  )
  assert(ArrayBuffer.isView(npub), 'npub must be a typed array')
  assert(
    npub.byteLength === binding.crypto_aead_xchacha20poly1305_ietf_NPUBBYTES,
    "npub must be 'crypto_aead_xchacha20poly1305_ietf_NPUBBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_aead_xchacha20poly1305_ietf_KEYBYTES,
    "k must be 'crypto_aead_xchacha20poly1305_ietf_KEYBYTES' bytes"
  )

  const res = binding.crypto_aead_xchacha20poly1305_ietf_encrypt_detached(c, mac, m, ad, npub, k)

  if (res < 0) throw new Error('could not encrypt data')

  return res
}

exports.crypto_aead_xchacha20poly1305_ietf_decrypt_detached = function (
  m,
  nsec,
  c,
  mac,
  ad,
  npub,
  k
) {
  if (!ad) ad = undefined

  assert(nsec === null, 'nsec must always be set to null')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(m.byteLength === c.byteLength, "m must be 'c.byteLength' bytes")
  assert(ArrayBuffer.isView(mac), 'mac must be a typed array')
  assert(
    mac.byteLength === binding.crypto_aead_xchacha20poly1305_ietf_ABYTES,
    "mac must be 'crypto_aead_xchacha20poly1305_ietf_ABYTES' bytes"
  )
  assert(ArrayBuffer.isView(npub), 'npub must be a typed array')
  assert(
    npub.byteLength === binding.crypto_aead_xchacha20poly1305_ietf_NPUBBYTES,
    "npub must be 'crypto_aead_xchacha20poly1305_ietf_NPUBBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_aead_xchacha20poly1305_ietf_KEYBYTES,
    "k must be 'crypto_aead_xchacha20poly1305_ietf_KEYBYTES' bytes"
  )

  const res = binding.crypto_aead_xchacha20poly1305_ietf_decrypt_detached(m, c, mac, ad, npub, k)

  if (res !== 0) throw new Error('could not verify data')
}

exports.crypto_aead_chacha20poly1305_ietf_keygen = function (k) {
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_aead_chacha20poly1305_ietf_KEYBYTES,
    "k must be 'crypto_aead_chacha20poly1305_ietf_KEYBYTES' bytes"
  )

  binding.crypto_aead_chacha20poly1305_ietf_keygen(k)
}

exports.crypto_aead_chacha20poly1305_ietf_encrypt = function (c, m, ad, nsec, npub, k) {
  if (!ad) ad = undefined

  assert(nsec === null, 'nsec must always be set to null')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(
    c.byteLength === m.byteLength + binding.crypto_aead_chacha20poly1305_ietf_ABYTES,
    "c must be 'm.byteLength + crypto_aead_chacha20poly1305_ietf_ABYTES' bytes"
  )
  assert(c.byteLength <= 0xffffffff, 'c.byteLength must be a 32bit integer')
  assert(ArrayBuffer.isView(npub), 'npub must be a typed array')
  assert(
    npub.byteLength === binding.crypto_aead_chacha20poly1305_ietf_NPUBBYTES,
    "npub must be 'crypto_aead_chacha20poly1305_ietf_NPUBBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_aead_chacha20poly1305_ietf_KEYBYTES,
    "k must be 'crypto_aead_chacha20poly1305_ietf_KEYBYTES' bytes"
  )

  const res = binding.crypto_aead_chacha20poly1305_ietf_encrypt(c, m, ad, npub, k)

  if (res < 0) throw new Error('could not encrypt data')

  return res
}

exports.crypto_aead_chacha20poly1305_ietf_decrypt = function (m, nsec, c, ad, npub, k) {
  if (!ad) ad = undefined

  assert(nsec === null, 'nsec must always be set to null')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(
    m.byteLength === c.byteLength - binding.crypto_aead_chacha20poly1305_ietf_ABYTES,
    "m must be 'c.byteLength - crypto_aead_chacha20poly1305_ietf_ABYTES' bytes"
  )
  assert(m.byteLength <= 0xffffffff, 'm.byteLength must be a 32bit integer')
  assert(ArrayBuffer.isView(npub), 'npub must be a typed array')
  assert(
    npub.byteLength === binding.crypto_aead_chacha20poly1305_ietf_NPUBBYTES,
    "npub must be 'crypto_aead_chacha20poly1305_ietf_NPUBBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_aead_chacha20poly1305_ietf_KEYBYTES,
    "k must be 'crypto_aead_chacha20poly1305_ietf_KEYBYTES' bytes"
  )

  const res = binding.crypto_aead_chacha20poly1305_ietf_decrypt(m, c, ad, npub, k)

  if (res < 0) throw new Error('could not verify data')

  return res
}

exports.crypto_aead_chacha20poly1305_ietf_encrypt_detached = function (
  c,
  mac,
  m,
  ad,
  nsec,
  npub,
  k
) {
  if (!ad) ad = undefined

  assert(nsec === null, 'nsec must always be set to null')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(ArrayBuffer.isView(mac), 'mac must be a typed array')
  assert(
    mac.byteLength === binding.crypto_aead_chacha20poly1305_ietf_ABYTES,
    "mac must be 'crypto_aead_chacha20poly1305_ietf_ABYTES' bytes"
  )
  assert(ArrayBuffer.isView(npub), 'npub must be a typed array')
  assert(
    npub.byteLength === binding.crypto_aead_chacha20poly1305_ietf_NPUBBYTES,
    "npub must be 'crypto_aead_chacha20poly1305_ietf_NPUBBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_aead_chacha20poly1305_ietf_KEYBYTES,
    "k must be 'crypto_aead_chacha20poly1305_ietf_KEYBYTES' bytes"
  )

  const res = binding.crypto_aead_chacha20poly1305_ietf_encrypt_detached(c, mac, m, ad, npub, k)

  if (res < 0) throw new Error('could not encrypt data')

  return res
}

exports.crypto_aead_chacha20poly1305_ietf_decrypt_detached = function (
  m,
  nsec,
  c,
  mac,
  ad,
  npub,
  k
) {
  if (!ad) ad = undefined

  assert(nsec === null, 'nsec must always be set to null')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(m.byteLength === c.byteLength, "m must be 'c.byteLength' bytes")
  assert(ArrayBuffer.isView(mac), 'mac must be a typed array')
  assert(
    mac.byteLength === binding.crypto_aead_chacha20poly1305_ietf_ABYTES,
    "mac must be 'crypto_aead_chacha20poly1305_ietf_ABYTES' bytes"
  )
  assert(ArrayBuffer.isView(npub), 'npub must be a typed array')
  assert(
    npub.byteLength === binding.crypto_aead_chacha20poly1305_ietf_NPUBBYTES,
    "npub must be 'crypto_aead_chacha20poly1305_ietf_NPUBBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_aead_chacha20poly1305_ietf_KEYBYTES,
    "k must be 'crypto_aead_chacha20poly1305_ietf_KEYBYTES' bytes"
  )

  const res = binding.crypto_aead_chacha20poly1305_ietf_decrypt_detached(m, c, mac, ad, npub, k)

  if (res !== 0) throw new Error('could not verify data')
}

// crypto_stream

exports.crypto_stream_xor_wrap_init = function (state, n, k) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.sn_crypto_stream_xor_STATEBYTES,
    "state must be 'sn_crypto_stream_xor_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.crypto_stream_NONCEBYTES,
    "n must be 'crypto_stream_NONCEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_stream_KEYBYTES,
    "k must be 'crypto_stream_KEYBYTES' bytes"
  )

  binding.crypto_stream_xor_wrap_init(state, n, k)
}

exports.crypto_stream_xor_wrap_update = function (state, c, m) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.sn_crypto_stream_xor_STATEBYTES,
    "state must be 'sn_crypto_stream_xor_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")

  binding.crypto_stream_xor_wrap_update(state, c, m)
}

exports.crypto_stream_xor_wrap_final = function (state) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.sn_crypto_stream_xor_STATEBYTES,
    "state must be 'sn_crypto_stream_xor_STATEBYTES' bytes"
  )

  binding.crypto_stream_xor_wrap_final(state)
}

exports.crypto_stream_chacha20_xor_wrap_init = function (state, n, k) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_stream_chacha20_xor_STATEBYTES,
    "state must be 'crypto_stream_chacha20_xor_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.crypto_stream_chacha20_NONCEBYTES,
    "n must be 'crypto_stream_chacha20_NONCEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_stream_chacha20_KEYBYTES,
    "k must be 'crypto_stream_chacha20_KEYBYTES' bytes"
  )

  binding.crypto_stream_chacha20_xor_wrap_init(state, n, k)
}

exports.crypto_stream_chacha20_xor_wrap_update = function (state, c, m) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_stream_chacha20_xor_STATEBYTES,
    "state must be 'crypto_stream_chacha20_xor_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")

  binding.crypto_stream_chacha20_xor_wrap_update(state, c, m)
}

exports.crypto_stream_chacha20_xor_wrap_final = function (state) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_stream_chacha20_xor_STATEBYTES,
    "state must be 'crypto_stream_chacha20_xor_STATEBYTES' bytes"
  )

  binding.crypto_stream_chacha20_xor_wrap_final(state)
}

exports.crypto_stream_chacha20_ietf_xor_wrap_init = function (state, n, k) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_stream_chacha20_ietf_xor_STATEBYTES,
    "state must be 'crypto_stream_chacha20_ietf_xor_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.crypto_stream_chacha20_ietf_NONCEBYTES,
    "n must be 'crypto_stream_chacha20_ietf_NONCEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_stream_chacha20_ietf_KEYBYTES,
    "k must be 'crypto_stream_chacha20_ietf_KEYBYTES' bytes"
  )

  binding.crypto_stream_chacha20_ietf_xor_wrap_init(state, n, k)
}

exports.crypto_stream_chacha20_ietf_xor_wrap_update = function (state, c, m) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_stream_chacha20_ietf_xor_STATEBYTES,
    "state must be 'crypto_stream_chacha20_ietf_xor_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")

  binding.crypto_stream_chacha20_ietf_xor_wrap_update(state, c, m)
}

exports.crypto_stream_chacha20_ietf_xor_wrap_final = function (state) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_stream_chacha20_ietf_xor_STATEBYTES,
    "state must be 'crypto_stream_chacha20_ietf_xor_STATEBYTES' bytes"
  )

  binding.crypto_stream_chacha20_ietf_xor_wrap_final(state)
}

exports.crypto_stream_xchacha20_xor_wrap_init = function (state, n, k) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_stream_xchacha20_xor_STATEBYTES,
    "state must be 'crypto_stream_xchacha20_xor_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.crypto_stream_xchacha20_NONCEBYTES,
    "n must be 'crypto_stream_xchacha20_NONCEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_stream_xchacha20_KEYBYTES,
    "k must be 'crypto_stream_xchacha20_KEYBYTES' bytes"
  )

  binding.crypto_stream_xchacha20_xor_wrap_init(state, n, k)
}

exports.crypto_stream_xchacha20_xor_wrap_update = function (state, c, m) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_stream_xchacha20_xor_STATEBYTES,
    "state must be 'crypto_stream_xchacha20_xor_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")

  binding.crypto_stream_xchacha20_xor_wrap_update(state, c, m)
}

exports.crypto_stream_xchacha20_xor_wrap_final = function (state) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_stream_xchacha20_xor_STATEBYTES,
    "state must be 'crypto_stream_xchacha20_xor_STATEBYTES' bytes"
  )

  binding.crypto_stream_xchacha20_xor_wrap_final(state)
}

exports.crypto_stream_salsa20_xor_wrap_init = function (state, n, k) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_stream_salsa20_xor_STATEBYTES,
    "state must be 'crypto_stream_salsa20_xor_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.crypto_stream_salsa20_NONCEBYTES,
    "n must be 'crypto_stream_salsa20_NONCEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_stream_salsa20_KEYBYTES,
    "k must be 'crypto_stream_salsa20_KEYBYTES' bytes"
  )

  binding.crypto_stream_salsa20_xor_wrap_init(state, n, k)
}

exports.crypto_stream_salsa20_xor_wrap_update = function (state, c, m) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_stream_salsa20_xor_STATEBYTES,
    "state must be 'crypto_stream_salsa20_xor_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")

  binding.crypto_stream_salsa20_xor_wrap_update(state, c, m)
}

exports.crypto_stream_salsa20_xor_wrap_final = function (state) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_stream_salsa20_xor_STATEBYTES,
    "state must be 'crypto_stream_salsa20_xor_STATEBYTES' bytes"
  )

  binding.crypto_stream_salsa20_xor_wrap_final(state)
}

// experimental

exports.extension_tweak_ed25519_base = function (n, p, ns) {
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "n must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(p), 'p must be a typed array')
  assert(
    p.byteLength === binding.extension_tweak_ed25519_BYTES,
    "p must be 'extension_tweak_ed25519_BYTES' bytes"
  )

  binding.extension_tweak_ed25519_base(n, p, ns)
}

exports.extension_tweak_ed25519_sign_detached = function (sig, m, scalar, pk) {
  assert(ArrayBuffer.isView(sig), 'sig must be a typed array')
  assert(sig.byteLength === binding.crypto_sign_BYTES, "sig must be 'crypto_sign_BYTES' bytes")
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(scalar), 'scalar must be a typed array')
  assert(
    scalar.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "scalar must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )

  if (pk) {
    assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
    assert(
      pk.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
      "pk must be 'crypto_sign_PUBLICKEYBYTES' bytes"
    )
  }

  const res = binding.extension_tweak_ed25519_sign_detached(sig, m, scalar, pk)

  if (res !== 0) throw new Error('failed to compute signature')
}

exports.extension_tweak_ed25519_sk_to_scalar = function (n, sk) {
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "n must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(
    sk.byteLength === binding.crypto_sign_SECRETKEYBYTES,
    "sk must be 'crypto_sign_SECRETKEYBYTES' bytes"
  )

  binding.extension_tweak_ed25519_sk_to_scalar(n, sk)
}

exports.extension_tweak_ed25519_scalar = function (scalarOut, scalar, ns) {
  assert(ArrayBuffer.isView(scalarOut), 'scalarOut must be a typed array')
  assert(
    scalarOut.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "scalarOut must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(scalar), 'scalar must be a typed array')
  assert(
    scalar.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "scalar must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )

  binding.extension_tweak_ed25519_scalar(scalarOut, scalar, ns)
}

exports.extension_tweak_ed25519_pk = function (tpk, pk, ns) {
  assert(ArrayBuffer.isView(tpk), 'tpk must be a typed array')
  assert(
    tpk.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
    "tpk must be 'crypto_sign_PUBLICKEYBYTES' bytes"
  )
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(
    pk.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
    "pk must be 'crypto_sign_PUBLICKEYBYTES' bytes"
  )

  const res = binding.extension_tweak_ed25519_pk(tpk, pk, ns)

  if (res !== 0) throw new Error('failed to tweak public key')
}

exports.extension_tweak_ed25519_keypair = function (pk, scalarOut, scalarIn, ns) {
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(
    pk.byteLength === binding.extension_tweak_ed25519_BYTES,
    "pk must be 'extension_tweak_ed25519_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(scalarOut), 'scalarOut must be a typed array')
  assert(
    scalarOut.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "scalarOut must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(scalarIn), 'scalarIn must be a typed array')
  assert(
    scalarIn.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "scalarIn must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )

  binding.extension_tweak_ed25519_keypair(pk, scalarOut, scalarIn, ns)
}

exports.extension_tweak_ed25519_scalar_add = function (scalarOut, scalar, n) {
  assert(ArrayBuffer.isView(scalarOut), 'scalarOut must be a typed array')
  assert(
    scalarOut.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "scalarOut must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(scalar), 'scalar must be a typed array')
  assert(
    scalar.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "scalar must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "n must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )

  binding.extension_tweak_ed25519_scalar_add(scalarOut, scalar, n)
}

exports.extension_tweak_ed25519_pk_add = function (tpk, pk, p) {
  assert(ArrayBuffer.isView(tpk), 'tpk must be a typed array')
  assert(
    tpk.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
    "tpk must be 'crypto_sign_PUBLICKEYBYTES' bytes"
  )
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(
    pk.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
    "pk must be 'crypto_sign_PUBLICKEYBYTES' bytes"
  )
  assert(ArrayBuffer.isView(p), 'p must be a typed array')
  assert(
    p.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
    "p must be 'crypto_sign_PUBLICKEYBYTES' bytes"
  )

  const res = binding.extension_tweak_ed25519_pk_add(tpk, pk, p)

  if (res !== 0) throw new Error('failed to add tweak to public key')
}

exports.extension_tweak_ed25519_keypair_add = function (pk, scalarOut, scalarIn, tweak) {
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(
    pk.byteLength === binding.extension_tweak_ed25519_BYTES,
    "pk must be 'extension_tweak_ed25519_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(scalarOut), 'scalarOut must be a typed array')
  assert(
    scalarOut.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "scalarOut must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(scalarIn), 'scalarIn must be a typed array')
  assert(
    scalarIn.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "scalarIn must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(tweak), 'tweak must be a typed array')
  assert(
    tweak.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "tweak must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )

  const res = binding.extension_tweak_ed25519_keypair_add(pk, scalarOut, scalarIn, tweak)

  if (res !== 0) throw new Error('failed to add tweak to keypair')
}

exports.extension_pbkdf2_sha512_async = function (out, passwd, salt, iter, outlen, callback) {
  assert(
    iter >= binding.extension_pbkdf2_sha512_ITERATIONS_MIN,
    "iter must be at least 'extension_pbkdf2_sha512_ITERATIONS_MIN'"
  )
  assert(
    outlen <= binding.extension_pbkdf2_sha512_BYTES_MAX,
    "outlen must be at most 'extension_pbkdf2_sha512_BYTES_MAX'"
  )
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(out.byteLength >= outlen, "out must be at least 'outlen' bytes")
  assert(out.byteLength > 0, 'out must not be empty')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(passwd.byteLength > 0, 'passwd must not be empty')
  assert(ArrayBuffer.isView(salt), 'salt must be a typed array')
  assert(salt.byteLength > 0, 'salt must not be empty')

  const [done, promise] = checkStatus(callback)

  binding.extension_pbkdf2_sha512_async(
    out.buffer,
    out.byteOffset,
    out.byteLength,

    passwd.buffer,
    passwd.byteOffset,
    passwd.byteLength,

    salt.buffer,
    salt.byteOffset,
    salt.byteLength,

    iter,
    outlen,

    done
  )

  return promise
}

exports.extension_pbkdf2_sha512 = function (out, passwd, salt, iter, outlen) {
  assert(
    iter >= binding.extension_pbkdf2_sha512_ITERATIONS_MIN,
    "iter must be at least 'extension_pbkdf2_sha512_ITERATIONS_MIN'"
  )
  assert(
    outlen <= binding.extension_pbkdf2_sha512_BYTES_MAX,
    "outlen must be at most 'extension_pbkdf2_sha512_BYTES_MAX'"
  )
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(out.byteLength >= outlen, "out must be at least 'outlen' bytes")
  assert(out.byteLength > 0, 'out must not be empty')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(passwd.byteLength > 0, 'passwd must not be empty')
  assert(ArrayBuffer.isView(salt), 'salt must be a typed array')
  assert(salt.byteLength > 0, 'salt must not be empty')

  const res = binding.extension_pbkdf2_sha512(out, passwd, salt, iter, outlen)

  if (res !== 0) throw new Error('failed to add tweak to public key')
}

function checkStatus(callback, booleanResult = false) {
  let done, promise

  if (typeof callback === 'function') {
    done = function (status) {
      if (booleanResult) callback(null, status === 0)
      else if (status === 0) callback(null)
      else callback(new Error('status: ' + status))
    }
  } else {
    promise = new Promise(function (resolve, reject) {
      done = function (status) {
        if (booleanResult) resolve(status === 0)
        else if (status === 0) resolve()
        else reject(new Error('status: ' + status))
      }
    })
  }

  return [done, promise]
}
{
  "name": "sodium-native",
  "version": "5.1.0",
  "description": "Low level bindings for libsodium",
  "main": "index.js",
  "files": [
    "index.js",
    "binding.cc",
    "binding.js",
    "extensions",
    "prebuilds",
    "CMakeLists.txt"
  ],
  "imports": {
    "assert": {
      "bare": "bare-assert",
      "node": "assert"
    }
  },
  "addon": true,
  "dependencies": {
    "bare-assert": "^1.2.0",
    "require-addon": "^1.1.0",
    "which-runtime": "^1.2.1"
  },
  "devDependencies": {
    "bare-compat-napi": "^1.3.5",
    "brittle": "^3.16.2",
    "cmake-bare": "^1.6.1",
    "cmake-fetch": "^1.4.3",
    "cmake-napi": "^1.2.1",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^2.0.0"
  },
  "scripts": {
    "test": "prettier . --check && npm run test:node && npm run test:bare",
    "test:node": "node test/all.js",
    "test:bare": "bare test/all.js"
  },
  "standard": {
    "ignore": [
      "/test/fixtures/*.js"
    ]
  },
  "engines": {
    "bare": ">=1.16.0"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/sodium-native.git"
  },
  "contributors": [
    "Emil Bay <github@tixz.dk> (http://bayes.dk)",
    "Mathias Buus <mathiasbuus@gmail.com> (https://mafinto.sh)",
    "Christophe Diederichs <chm-diederichs@hyperdivision.dk>"
  ],
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/holepunchto/sodium-native/issues"
  },
  "homepage": "https://github.com/holepunchto/sodium-native"
}
const sodium = require('sodium-universal')
const b4a = require('b4a')

const ABYTES = sodium.crypto_secretstream_xchacha20poly1305_ABYTES
const TAG_MESSAGE = sodium.crypto_secretstream_xchacha20poly1305_TAG_MESSAGE
const TAG_FINAL = sodium.crypto_secretstream_xchacha20poly1305_TAG_FINAL
const STATEBYTES = sodium.crypto_secretstream_xchacha20poly1305_STATEBYTES
const HEADERBYTES = sodium.crypto_secretstream_xchacha20poly1305_HEADERBYTES
const KEYBYTES = sodium.crypto_secretstream_xchacha20poly1305_KEYBYTES
const TAG_FINAL_BYTE = b4a.isBuffer(TAG_FINAL) ? TAG_FINAL[0] : TAG_FINAL

const EMPTY = b4a.alloc(0)
const TAG = b4a.alloc(1)

class Push {
  constructor (key, state = b4a.allocUnsafeSlow(STATEBYTES), header = b4a.allocUnsafeSlow(HEADERBYTES)) {
    if (!TAG_FINAL) throw new Error('JavaScript sodium version needs to support crypto_secretstream_xchacha20poly')

    this.key = key
    this.state = state
    this.header = header

    sodium.crypto_secretstream_xchacha20poly1305_init_push(this.state, this.header, this.key)
  }

  next (message, cipher = b4a.allocUnsafe(message.byteLength + ABYTES)) {
    sodium.crypto_secretstream_xchacha20poly1305_push(this.state, cipher, message, null, TAG_MESSAGE)
    return cipher
  }

  final (message = EMPTY, cipher = b4a.allocUnsafe(ABYTES)) {
    sodium.crypto_secretstream_xchacha20poly1305_push(this.state, cipher, message, null, TAG_FINAL)
    return cipher
  }
}

class Pull {
  constructor (key, state = b4a.allocUnsafeSlow(STATEBYTES)) {
    if (!TAG_FINAL) throw new Error('JavaScript sodium version needs to support crypto_secretstream_xchacha20poly')

    this.key = key
    this.state = state
    this.final = false
  }

  init (header) {
    sodium.crypto_secretstream_xchacha20poly1305_init_pull(this.state, header, this.key)
  }

  next (cipher, message = b4a.allocUnsafe(cipher.byteLength - ABYTES)) {
    sodium.crypto_secretstream_xchacha20poly1305_pull(this.state, message, TAG, cipher, null)
    this.final = TAG[0] === TAG_FINAL_BYTE
    return message
  }
}

function keygen (buf = b4a.alloc(KEYBYTES)) {
  sodium.crypto_secretstream_xchacha20poly1305_keygen(buf)
  return buf
}

module.exports = {
  keygen,
  KEYBYTES,
  ABYTES,
  STATEBYTES,
  HEADERBYTES,
  Push,
  Pull
}
{
  "name": "sodium-secretstream",
  "version": "1.2.0",
  "description": "Wraps libsodiums secretstream in a higher level abstraction",
  "main": "index.js",
  "dependencies": {
    "b4a": "^1.1.1",
    "sodium-universal": "^5.0.0"
  },
  "devDependencies": {
    "standard": "^17.0.0"
  },
  "scripts": {
    "test": "standard"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/mafintosh/sodium-secretstream.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/mafintosh/sodium-secretstream/issues"
  },
  "homepage": "https://github.com/mafintosh/sodium-secretstream"
}
module.exports = require('sodium-native')
{
  "name": "sodium-universal",
  "version": "5.0.1",
  "description": "Universal wrapper for sodium-javascript and sodium-native working in Node.js and the Browser",
  "main": "index.js",
  "dependencies": {
    "sodium-native": "^5.0.1"
  },
  "peerDependencies": {
    "sodium-javascript": "~0.8.0"
  },
  "peerDependenciesMeta": {
    "sodium-javascript": {
      "optional": true
    }
  },
  "scripts": {
    "prepublish": "./build-scripts/generate.js"
  },
  "browser": {
    "sodium-native": "sodium-javascript"
  },
  "browserify": {
    "transform": [
      "./build-scripts/transform.js"
    ]
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/sodium-universal.git"
  },
  "keywords": [
    "libsodium",
    "sodium",
    "sodium-native",
    "sodium-javascript",
    "browserify"
  ],
  "contributors": [
    "Emil Bay <github@tixz.dk> (http://bayes.dk)",
    "Mathias Buus <mathiasbuus@gmail.com> (https://mafinto.sh)",
    "Christophe Diederichs <chm-diederichs@hyperdivision.dk>"
  ],
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/holepunchto/sodium-universal/issues"
  },
  "homepage": "https://github.com/holepunchto/sodium-universal#readme"
}
const { EventEmitter } = require('events-universal')
const STREAM_DESTROYED = new Error('Stream was destroyed')
const PREMATURE_CLOSE = new Error('Premature close')

const FIFO = require('fast-fifo')
const TextDecoder = require('text-decoder')

// if we do a future major, expect queue microtask to be there always, for now a bit defensive
const qmt =
  typeof queueMicrotask === 'undefined' ? (fn) => global.process.nextTick(fn) : queueMicrotask

// 29 bits used total (4 from shared, 14 from read, and 11 from write)
const MAX = (1 << 29) - 1

// Shared state
const OPENING = 0b0001
const PREDESTROYING = 0b0010
const DESTROYING = 0b0100
const DESTROYED = 0b1000

const NOT_OPENING = MAX ^ OPENING
const NOT_PREDESTROYING = MAX ^ PREDESTROYING

// Read state (4 bit offset from shared state)
const READ_ACTIVE = 0b00000000000001 << 4
const READ_UPDATING = 0b00000000000010 << 4
const READ_PRIMARY = 0b00000000000100 << 4
const READ_QUEUED = 0b00000000001000 << 4
const READ_RESUMED = 0b00000000010000 << 4
const READ_PIPE_DRAINED = 0b00000000100000 << 4
const READ_ENDING = 0b00000001000000 << 4
const READ_EMIT_DATA = 0b00000010000000 << 4
const READ_EMIT_READABLE = 0b00000100000000 << 4
const READ_EMITTED_READABLE = 0b00001000000000 << 4
const READ_DONE = 0b00010000000000 << 4
const READ_NEXT_TICK = 0b00100000000000 << 4
const READ_NEEDS_PUSH = 0b01000000000000 << 4
const READ_READ_AHEAD = 0b10000000000000 << 4

// Combined read state
const READ_FLOWING = READ_RESUMED | READ_PIPE_DRAINED
const READ_ACTIVE_AND_NEEDS_PUSH = READ_ACTIVE | READ_NEEDS_PUSH
const READ_PRIMARY_AND_ACTIVE = READ_PRIMARY | READ_ACTIVE
const READ_EMIT_READABLE_AND_QUEUED = READ_EMIT_READABLE | READ_QUEUED
const READ_RESUMED_READ_AHEAD = READ_RESUMED | READ_READ_AHEAD

const READ_NOT_ACTIVE = MAX ^ READ_ACTIVE
const READ_NON_PRIMARY = MAX ^ READ_PRIMARY
const READ_NON_PRIMARY_AND_PUSHED = MAX ^ (READ_PRIMARY | READ_NEEDS_PUSH)
const READ_PUSHED = MAX ^ READ_NEEDS_PUSH
const READ_PAUSED = MAX ^ READ_RESUMED
const READ_NOT_QUEUED = MAX ^ (READ_QUEUED | READ_EMITTED_READABLE)
const READ_NOT_ENDING = MAX ^ READ_ENDING
const READ_PIPE_NOT_DRAINED = MAX ^ READ_FLOWING
const READ_NOT_NEXT_TICK = MAX ^ READ_NEXT_TICK
const READ_NOT_UPDATING = MAX ^ READ_UPDATING
const READ_NO_READ_AHEAD = MAX ^ READ_READ_AHEAD
const READ_PAUSED_NO_READ_AHEAD = MAX ^ READ_RESUMED_READ_AHEAD

// Write state (18 bit offset, 4 bit offset from shared state and 14 from read state)
const WRITE_ACTIVE = 0b00000000001 << 18
const WRITE_UPDATING = 0b00000000010 << 18
const WRITE_PRIMARY = 0b00000000100 << 18
const WRITE_QUEUED = 0b00000001000 << 18
const WRITE_UNDRAINED = 0b00000010000 << 18
const WRITE_DONE = 0b00000100000 << 18
const WRITE_EMIT_DRAIN = 0b00001000000 << 18
const WRITE_NEXT_TICK = 0b00010000000 << 18
const WRITE_WRITING = 0b00100000000 << 18
const WRITE_FINISHING = 0b01000000000 << 18
const WRITE_CORKED = 0b10000000000 << 18

const WRITE_NOT_ACTIVE = MAX ^ (WRITE_ACTIVE | WRITE_WRITING)
const WRITE_NON_PRIMARY = MAX ^ WRITE_PRIMARY
const WRITE_NOT_FINISHING = MAX ^ (WRITE_ACTIVE | WRITE_FINISHING)
const WRITE_DRAINED = MAX ^ WRITE_UNDRAINED
const WRITE_NOT_QUEUED = MAX ^ WRITE_QUEUED
const WRITE_NOT_NEXT_TICK = MAX ^ WRITE_NEXT_TICK
const WRITE_NOT_UPDATING = MAX ^ WRITE_UPDATING
const WRITE_NOT_CORKED = MAX ^ WRITE_CORKED

// Combined shared state
const ACTIVE = READ_ACTIVE | WRITE_ACTIVE
const NOT_ACTIVE = MAX ^ ACTIVE
const DONE = READ_DONE | WRITE_DONE
const DESTROY_STATUS = DESTROYING | DESTROYED | PREDESTROYING
const OPEN_STATUS = DESTROY_STATUS | OPENING
const AUTO_DESTROY = DESTROY_STATUS | DONE
const NON_PRIMARY = WRITE_NON_PRIMARY & READ_NON_PRIMARY
const ACTIVE_OR_TICKING = WRITE_NEXT_TICK | READ_NEXT_TICK
const TICKING = ACTIVE_OR_TICKING & NOT_ACTIVE
const IS_OPENING = OPEN_STATUS | TICKING

// Combined shared state and read state
const READ_PRIMARY_STATUS = OPEN_STATUS | READ_ENDING | READ_DONE
const READ_STATUS = OPEN_STATUS | READ_DONE | READ_QUEUED
const READ_ENDING_STATUS = OPEN_STATUS | READ_ENDING | READ_QUEUED
const READ_READABLE_STATUS = OPEN_STATUS | READ_EMIT_READABLE | READ_QUEUED | READ_EMITTED_READABLE
const SHOULD_NOT_READ =
  OPEN_STATUS | READ_ACTIVE | READ_ENDING | READ_DONE | READ_NEEDS_PUSH | READ_READ_AHEAD
const READ_BACKPRESSURE_STATUS = DESTROY_STATUS | READ_ENDING | READ_DONE
const READ_UPDATE_SYNC_STATUS = READ_UPDATING | OPEN_STATUS | READ_NEXT_TICK | READ_PRIMARY
const READ_NEXT_TICK_OR_OPENING = READ_NEXT_TICK | OPENING

// Combined write state
const WRITE_PRIMARY_STATUS = OPEN_STATUS | WRITE_FINISHING | WRITE_DONE
const WRITE_QUEUED_AND_UNDRAINED = WRITE_QUEUED | WRITE_UNDRAINED
const WRITE_QUEUED_AND_ACTIVE = WRITE_QUEUED | WRITE_ACTIVE
const WRITE_DRAIN_STATUS = WRITE_QUEUED | WRITE_UNDRAINED | OPEN_STATUS | WRITE_ACTIVE
const WRITE_STATUS = OPEN_STATUS | WRITE_ACTIVE | WRITE_QUEUED | WRITE_CORKED
const WRITE_PRIMARY_AND_ACTIVE = WRITE_PRIMARY | WRITE_ACTIVE
const WRITE_ACTIVE_AND_WRITING = WRITE_ACTIVE | WRITE_WRITING
const WRITE_FINISHING_STATUS = OPEN_STATUS | WRITE_FINISHING | WRITE_QUEUED_AND_ACTIVE | WRITE_DONE
const WRITE_BACKPRESSURE_STATUS = WRITE_UNDRAINED | DESTROY_STATUS | WRITE_FINISHING | WRITE_DONE
const WRITE_UPDATE_SYNC_STATUS = WRITE_UPDATING | OPEN_STATUS | WRITE_NEXT_TICK | WRITE_PRIMARY
const WRITE_DROP_DATA = WRITE_FINISHING | WRITE_DONE | DESTROY_STATUS

const asyncIterator = Symbol.asyncIterator || Symbol('asyncIterator')

class WritableState {
  constructor(
    stream,
    { highWaterMark = 16384, map = null, mapWritable, byteLength, byteLengthWritable } = {}
  ) {
    this.stream = stream
    this.queue = new FIFO()
    this.highWaterMark = highWaterMark
    this.buffered = 0
    this.error = null
    this.pipeline = null
    this.drains = null // if we add more seldomly used helpers we might them into a subobject so its a single ptr
    this.byteLength = byteLengthWritable || byteLength || defaultByteLength
    this.map = mapWritable || map
    this.afterWrite = afterWrite.bind(this)
    this.afterUpdateNextTick = updateWriteNT.bind(this)
  }

  get ending() {
    return (this.stream._duplexState & WRITE_FINISHING) !== 0
  }

  get ended() {
    return (this.stream._duplexState & WRITE_DONE) !== 0
  }

  push(data) {
    if ((this.stream._duplexState & WRITE_DROP_DATA) !== 0) return false
    if (this.map !== null) data = this.map(data)

    this.buffered += this.byteLength(data)
    this.queue.push(data)

    if (this.buffered < this.highWaterMark) {
      this.stream._duplexState |= WRITE_QUEUED
      return true
    }

    this.stream._duplexState |= WRITE_QUEUED_AND_UNDRAINED
    return false
  }

  shift() {
    const data = this.queue.shift()

    this.buffered -= this.byteLength(data)
    if (this.buffered === 0) this.stream._duplexState &= WRITE_NOT_QUEUED

    return data
  }

  end(data) {
    if (typeof data === 'function') this.stream.once('finish', data)
    else if (data !== undefined && data !== null) this.push(data)
    this.stream._duplexState = (this.stream._duplexState | WRITE_FINISHING) & WRITE_NON_PRIMARY
  }

  autoBatch(data, cb) {
    const buffer = []
    const stream = this.stream

    buffer.push(data)
    while ((stream._duplexState & WRITE_STATUS) === WRITE_QUEUED_AND_ACTIVE) {
      buffer.push(stream._writableState.shift())
    }

    if ((stream._duplexState & OPEN_STATUS) !== 0) return cb(null)
    stream._writev(buffer, cb)
  }

  update() {
    const stream = this.stream

    stream._duplexState |= WRITE_UPDATING

    do {
      while ((stream._duplexState & WRITE_STATUS) === WRITE_QUEUED) {
        const data = this.shift()
        stream._duplexState |= WRITE_ACTIVE_AND_WRITING
        stream._write(data, this.afterWrite)
      }

      if ((stream._duplexState & WRITE_PRIMARY_AND_ACTIVE) === 0) this.updateNonPrimary()
    } while (this.continueUpdate() === true)

    stream._duplexState &= WRITE_NOT_UPDATING
  }

  updateNonPrimary() {
    const stream = this.stream

    if ((stream._duplexState & WRITE_FINISHING_STATUS) === WRITE_FINISHING) {
      stream._duplexState = stream._duplexState | WRITE_ACTIVE
      stream._final(afterFinal.bind(this))
      return
    }

    if ((stream._duplexState & DESTROY_STATUS) === DESTROYING) {
      if ((stream._duplexState & ACTIVE_OR_TICKING) === 0) {
        stream._duplexState |= ACTIVE
        stream._destroy(afterDestroy.bind(this))
      }
      return
    }

    if ((stream._duplexState & IS_OPENING) === OPENING) {
      stream._duplexState = (stream._duplexState | ACTIVE) & NOT_OPENING
      stream._open(afterOpen.bind(this))
    }
  }

  continueUpdate() {
    if ((this.stream._duplexState & WRITE_NEXT_TICK) === 0) return false
    this.stream._duplexState &= WRITE_NOT_NEXT_TICK
    return true
  }

  updateCallback() {
    if ((this.stream._duplexState & WRITE_UPDATE_SYNC_STATUS) === WRITE_PRIMARY) this.update()
    else this.updateNextTick()
  }

  updateNextTick() {
    if ((this.stream._duplexState & WRITE_NEXT_TICK) !== 0) return
    this.stream._duplexState |= WRITE_NEXT_TICK
    if ((this.stream._duplexState & WRITE_UPDATING) === 0) qmt(this.afterUpdateNextTick)
  }
}

class ReadableState {
  constructor(
    stream,
    { highWaterMark = 16384, map = null, mapReadable, byteLength, byteLengthReadable } = {}
  ) {
    this.stream = stream
    this.queue = new FIFO()
    this.highWaterMark = highWaterMark === 0 ? 1 : highWaterMark
    this.buffered = 0
    this.readAhead = highWaterMark > 0
    this.error = null
    this.pipeline = null
    this.byteLength = byteLengthReadable || byteLength || defaultByteLength
    this.map = mapReadable || map
    this.pipeTo = null
    this.afterRead = afterRead.bind(this)
    this.afterUpdateNextTick = updateReadNT.bind(this)
  }

  get ending() {
    return (this.stream._duplexState & READ_ENDING) !== 0
  }

  get ended() {
    return (this.stream._duplexState & READ_DONE) !== 0
  }

  pipe(pipeTo, cb) {
    if (this.pipeTo !== null) throw new Error('Can only pipe to one destination')
    if (typeof cb !== 'function') cb = null

    this.stream._duplexState |= READ_PIPE_DRAINED
    this.pipeTo = pipeTo
    this.pipeline = new Pipeline(this.stream, pipeTo, cb)

    if (cb) this.stream.on('error', noop) // We already error handle this so supress crashes

    if (isStreamx(pipeTo)) {
      pipeTo._writableState.pipeline = this.pipeline
      if (cb) pipeTo.on('error', noop) // We already error handle this so supress crashes
      pipeTo.on('finish', this.pipeline.finished.bind(this.pipeline)) // TODO: just call finished from pipeTo itself
    } else {
      const onerror = this.pipeline.done.bind(this.pipeline, pipeTo)
      const onclose = this.pipeline.done.bind(this.pipeline, pipeTo, null) // onclose has a weird bool arg
      pipeTo.on('error', onerror)
      pipeTo.on('close', onclose)
      pipeTo.on('finish', this.pipeline.finished.bind(this.pipeline))
    }

    pipeTo.on('drain', afterDrain.bind(this))
    this.stream.emit('piping', pipeTo)
    pipeTo.emit('pipe', this.stream)
  }

  push(data) {
    const stream = this.stream

    if (data === null) {
      this.highWaterMark = 0
      stream._duplexState = (stream._duplexState | READ_ENDING) & READ_NON_PRIMARY_AND_PUSHED
      return false
    }

    if (this.map !== null) {
      data = this.map(data)
      if (data === null) {
        stream._duplexState &= READ_PUSHED
        return this.buffered < this.highWaterMark
      }
    }

    this.buffered += this.byteLength(data)
    this.queue.push(data)

    stream._duplexState = (stream._duplexState | READ_QUEUED) & READ_PUSHED

    return this.buffered < this.highWaterMark
  }

  shift() {
    const data = this.queue.shift()

    this.buffered -= this.byteLength(data)
    if (this.buffered === 0) this.stream._duplexState &= READ_NOT_QUEUED
    return data
  }

  unshift(data) {
    const pending = [this.map !== null ? this.map(data) : data]
    while (this.buffered > 0) pending.push(this.shift())

    for (let i = 0; i < pending.length - 1; i++) {
      const data = pending[i]
      this.buffered += this.byteLength(data)
      this.queue.push(data)
    }

    this.push(pending[pending.length - 1])
  }

  read() {
    const stream = this.stream

    if ((stream._duplexState & READ_STATUS) === READ_QUEUED) {
      const data = this.shift()
      if (this.pipeTo !== null && this.pipeTo.write(data) === false)
        stream._duplexState &= READ_PIPE_NOT_DRAINED
      if ((stream._duplexState & READ_EMIT_DATA) !== 0) stream.emit('data', data)
      return data
    }

    if (this.readAhead === false) {
      stream._duplexState |= READ_READ_AHEAD
      this.updateNextTick()
    }

    return null
  }

  drain() {
    const stream = this.stream

    while (
      (stream._duplexState & READ_STATUS) === READ_QUEUED &&
      (stream._duplexState & READ_FLOWING) !== 0
    ) {
      const data = this.shift()
      if (this.pipeTo !== null && this.pipeTo.write(data) === false)
        stream._duplexState &= READ_PIPE_NOT_DRAINED
      if ((stream._duplexState & READ_EMIT_DATA) !== 0) stream.emit('data', data)
    }
  }

  update() {
    const stream = this.stream

    stream._duplexState |= READ_UPDATING

    do {
      this.drain()

      while (
        this.buffered < this.highWaterMark &&
        (stream._duplexState & SHOULD_NOT_READ) === READ_READ_AHEAD
      ) {
        stream._duplexState |= READ_ACTIVE_AND_NEEDS_PUSH
        stream._read(this.afterRead)
        this.drain()
      }

      if ((stream._duplexState & READ_READABLE_STATUS) === READ_EMIT_READABLE_AND_QUEUED) {
        stream._duplexState |= READ_EMITTED_READABLE
        stream.emit('readable')
      }

      if ((stream._duplexState & READ_PRIMARY_AND_ACTIVE) === 0) this.updateNonPrimary()
    } while (this.continueUpdate() === true)

    stream._duplexState &= READ_NOT_UPDATING
  }

  updateNonPrimary() {
    const stream = this.stream

    if ((stream._duplexState & READ_ENDING_STATUS) === READ_ENDING) {
      stream._duplexState = (stream._duplexState | READ_DONE) & READ_NOT_ENDING
      stream.emit('end')
      if ((stream._duplexState & AUTO_DESTROY) === DONE) stream._duplexState |= DESTROYING
      if (this.pipeTo !== null) this.pipeTo.end()
    }

    if ((stream._duplexState & DESTROY_STATUS) === DESTROYING) {
      if ((stream._duplexState & ACTIVE_OR_TICKING) === 0) {
        stream._duplexState |= ACTIVE
        stream._destroy(afterDestroy.bind(this))
      }
      return
    }

    if ((stream._duplexState & IS_OPENING) === OPENING) {
      stream._duplexState = (stream._duplexState | ACTIVE) & NOT_OPENING
      stream._open(afterOpen.bind(this))
    }
  }

  continueUpdate() {
    if ((this.stream._duplexState & READ_NEXT_TICK) === 0) return false
    this.stream._duplexState &= READ_NOT_NEXT_TICK
    return true
  }

  updateCallback() {
    if ((this.stream._duplexState & READ_UPDATE_SYNC_STATUS) === READ_PRIMARY) this.update()
    else this.updateNextTick()
  }

  updateNextTickIfOpen() {
    if ((this.stream._duplexState & READ_NEXT_TICK_OR_OPENING) !== 0) return
    this.stream._duplexState |= READ_NEXT_TICK
    if ((this.stream._duplexState & READ_UPDATING) === 0) qmt(this.afterUpdateNextTick)
  }

  updateNextTick() {
    if ((this.stream._duplexState & READ_NEXT_TICK) !== 0) return
    this.stream._duplexState |= READ_NEXT_TICK
    if ((this.stream._duplexState & READ_UPDATING) === 0) qmt(this.afterUpdateNextTick)
  }
}

class TransformState {
  constructor(stream) {
    this.data = null
    this.afterTransform = afterTransform.bind(stream)
    this.afterFinal = null
  }
}

class Pipeline {
  constructor(src, dst, cb) {
    this.from = src
    this.to = dst
    this.afterPipe = cb
    this.error = null
    this.pipeToFinished = false
  }

  finished() {
    this.pipeToFinished = true
  }

  done(stream, err) {
    if (err) this.error = err

    if (stream === this.to) {
      this.to = null

      if (this.from !== null) {
        if ((this.from._duplexState & READ_DONE) === 0 || !this.pipeToFinished) {
          this.from.destroy(this.error || new Error('Writable stream closed prematurely'))
        }
        return
      }
    }

    if (stream === this.from) {
      this.from = null

      if (this.to !== null) {
        if ((stream._duplexState & READ_DONE) === 0) {
          this.to.destroy(this.error || new Error('Readable stream closed before ending'))
        }
        return
      }
    }

    if (this.afterPipe !== null) this.afterPipe(this.error)
    this.to = this.from = this.afterPipe = null
  }
}

function afterDrain() {
  this.stream._duplexState |= READ_PIPE_DRAINED
  this.updateCallback()
}

function afterFinal(err) {
  const stream = this.stream
  if (err) stream.destroy(err)
  if ((stream._duplexState & DESTROY_STATUS) === 0) {
    stream._duplexState |= WRITE_DONE
    stream.emit('finish')
  }
  if ((stream._duplexState & AUTO_DESTROY) === DONE) {
    stream._duplexState |= DESTROYING
  }

  stream._duplexState &= WRITE_NOT_FINISHING

  // no need to wait the extra tick here, so we short circuit that
  if ((stream._duplexState & WRITE_UPDATING) === 0) this.update()
  else this.updateNextTick()
}

function afterDestroy(err) {
  const stream = this.stream

  if (!err && this.error !== STREAM_DESTROYED) err = this.error
  if (err) stream.emit('error', err)
  stream._duplexState |= DESTROYED
  stream.emit('close')

  const rs = stream._readableState
  const ws = stream._writableState

  if (rs !== null && rs.pipeline !== null) rs.pipeline.done(stream, err)

  if (ws !== null) {
    while (ws.drains !== null && ws.drains.length > 0) ws.drains.shift().resolve(false)
    if (ws.pipeline !== null) ws.pipeline.done(stream, err)
  }
}

function afterWrite(err) {
  const stream = this.stream

  if (err) stream.destroy(err)
  stream._duplexState &= WRITE_NOT_ACTIVE

  if (this.drains !== null) tickDrains(this.drains)

  if ((stream._duplexState & WRITE_DRAIN_STATUS) === WRITE_UNDRAINED) {
    stream._duplexState &= WRITE_DRAINED
    if ((stream._duplexState & WRITE_EMIT_DRAIN) === WRITE_EMIT_DRAIN) {
      stream.emit('drain')
    }
  }

  this.updateCallback()
}

function afterRead(err) {
  if (err) this.stream.destroy(err)
  this.stream._duplexState &= READ_NOT_ACTIVE
  if (this.readAhead === false && (this.stream._duplexState & READ_RESUMED) === 0)
    this.stream._duplexState &= READ_NO_READ_AHEAD
  this.updateCallback()
}

function updateReadNT() {
  if ((this.stream._duplexState & READ_UPDATING) === 0) {
    this.stream._duplexState &= READ_NOT_NEXT_TICK
    this.update()
  }
}

function updateWriteNT() {
  if ((this.stream._duplexState & WRITE_UPDATING) === 0) {
    this.stream._duplexState &= WRITE_NOT_NEXT_TICK
    this.update()
  }
}

function tickDrains(drains) {
  for (let i = 0; i < drains.length; i++) {
    // drains.writes are monotonic, so if one is 0 its always the first one
    if (--drains[i].writes === 0) {
      drains.shift().resolve(true)
      i--
    }
  }
}

function afterOpen(err) {
  const stream = this.stream

  if (err) stream.destroy(err)

  if ((stream._duplexState & DESTROYING) === 0) {
    if ((stream._duplexState & READ_PRIMARY_STATUS) === 0) stream._duplexState |= READ_PRIMARY
    if ((stream._duplexState & WRITE_PRIMARY_STATUS) === 0) stream._duplexState |= WRITE_PRIMARY
    stream.emit('open')
  }

  stream._duplexState &= NOT_ACTIVE

  if (stream._writableState !== null) {
    stream._writableState.updateCallback()
  }

  if (stream._readableState !== null) {
    stream._readableState.updateCallback()
  }
}

function afterTransform(err, data) {
  if (data !== undefined && data !== null) this.push(data)
  this._writableState.afterWrite(err)
}

function newListener(name) {
  if (this._readableState !== null) {
    if (name === 'data') {
      this._duplexState |= READ_EMIT_DATA | READ_RESUMED_READ_AHEAD
      this._readableState.updateNextTick()
    }
    if (name === 'readable') {
      this._duplexState |= READ_EMIT_READABLE
      this._readableState.updateNextTick()
    }
  }

  if (this._writableState !== null) {
    if (name === 'drain') {
      this._duplexState |= WRITE_EMIT_DRAIN
      this._writableState.updateNextTick()
    }
  }
}

class Stream extends EventEmitter {
  constructor(opts) {
    super()

    this._duplexState = 0
    this._readableState = null
    this._writableState = null

    if (opts) {
      if (opts.open) this._open = opts.open
      if (opts.destroy) this._destroy = opts.destroy
      if (opts.predestroy) this._predestroy = opts.predestroy
      if (opts.signal) {
        opts.signal.addEventListener('abort', abort.bind(this))
      }
    }

    this.on('newListener', newListener)
  }

  _open(cb) {
    cb(null)
  }

  _destroy(cb) {
    cb(null)
  }

  _predestroy() {
    // does nothing
  }

  get readable() {
    return this._readableState !== null ? true : undefined
  }

  get writable() {
    return this._writableState !== null ? true : undefined
  }

  get destroyed() {
    return (this._duplexState & DESTROYED) !== 0
  }

  get destroying() {
    return (this._duplexState & DESTROY_STATUS) !== 0
  }

  destroy(err) {
    if ((this._duplexState & DESTROY_STATUS) === 0) {
      if (!err) err = STREAM_DESTROYED
      this._duplexState = (this._duplexState | DESTROYING) & NON_PRIMARY

      if (this._readableState !== null) {
        this._readableState.highWaterMark = 0
        this._readableState.error = err
      }
      if (this._writableState !== null) {
        this._writableState.highWaterMark = 0
        this._writableState.error = err
      }

      this._duplexState |= PREDESTROYING
      this._predestroy()
      this._duplexState &= NOT_PREDESTROYING

      if (this._readableState !== null) this._readableState.updateNextTick()
      if (this._writableState !== null) this._writableState.updateNextTick()
    }
  }
}

class Readable extends Stream {
  constructor(opts) {
    super(opts)

    this._duplexState |= OPENING | WRITE_DONE | READ_READ_AHEAD
    this._readableState = new ReadableState(this, opts)

    if (opts) {
      if (this._readableState.readAhead === false) this._duplexState &= READ_NO_READ_AHEAD
      if (opts.read) this._read = opts.read
      if (opts.eagerOpen) this._readableState.updateNextTick()
      if (opts.encoding) this.setEncoding(opts.encoding)
    }
  }

  setEncoding(encoding) {
    const dec = new TextDecoder(encoding)
    const map = this._readableState.map || echo
    this._readableState.map = mapOrSkip
    return this

    function mapOrSkip(data) {
      const next = dec.push(data)
      return next === '' && (data.byteLength !== 0 || dec.remaining > 0) ? null : map(next)
    }
  }

  _read(cb) {
    cb(null)
  }

  pipe(dest, cb) {
    this._readableState.updateNextTick()
    this._readableState.pipe(dest, cb)
    return dest
  }

  read() {
    this._readableState.updateNextTick()
    return this._readableState.read()
  }

  push(data) {
    this._readableState.updateNextTickIfOpen()
    return this._readableState.push(data)
  }

  unshift(data) {
    this._readableState.updateNextTickIfOpen()
    return this._readableState.unshift(data)
  }

  resume() {
    this._duplexState |= READ_RESUMED_READ_AHEAD
    this._readableState.updateNextTick()
    return this
  }

  pause() {
    this._duplexState &=
      this._readableState.readAhead === false ? READ_PAUSED_NO_READ_AHEAD : READ_PAUSED
    return this
  }

  static _fromAsyncIterator(ite, opts) {
    let destroy

    const rs = new Readable({
      ...opts,
      read(cb) {
        ite.next().then(push).then(cb.bind(null, null)).catch(cb)
      },
      predestroy() {
        destroy = ite.return()
      },
      destroy(cb) {
        if (!destroy) return cb(null)
        destroy.then(cb.bind(null, null)).catch(cb)
      }
    })

    return rs

    function push(data) {
      if (data.done) rs.push(null)
      else rs.push(data.value)
    }
  }

  static from(data, opts) {
    if (isReadStreamx(data)) return data
    if (data[asyncIterator]) return this._fromAsyncIterator(data[asyncIterator](), opts)
    if (!Array.isArray(data)) data = data === undefined ? [] : [data]

    let i = 0
    return new Readable({
      ...opts,
      read(cb) {
        this.push(i === data.length ? null : data[i++])
        cb(null)
      }
    })
  }

  static isBackpressured(rs) {
    return (
      (rs._duplexState & READ_BACKPRESSURE_STATUS) !== 0 ||
      rs._readableState.buffered >= rs._readableState.highWaterMark
    )
  }

  static isPaused(rs) {
    return (rs._duplexState & READ_RESUMED) === 0
  }

  [asyncIterator]() {
    const stream = this

    let error = null
    let promiseResolve = null
    let promiseReject = null

    this.on('error', (err) => {
      error = err
    })
    this.on('readable', onreadable)
    this.on('close', onclose)

    return {
      [asyncIterator]() {
        return this
      },
      next() {
        return new Promise(function (resolve, reject) {
          promiseResolve = resolve
          promiseReject = reject
          const data = stream.read()
          if (data !== null) ondata(data)
          else if ((stream._duplexState & DESTROYED) !== 0) ondata(null)
        })
      },
      return() {
        return destroy(null)
      },
      throw(err) {
        return destroy(err)
      }
    }

    function onreadable() {
      if (promiseResolve !== null) ondata(stream.read())
    }

    function onclose() {
      if (promiseResolve !== null) ondata(null)
    }

    function ondata(data) {
      if (promiseReject === null) return
      if (error) promiseReject(error)
      else if (data === null && (stream._duplexState & READ_DONE) === 0)
        promiseReject(STREAM_DESTROYED)
      else promiseResolve({ value: data, done: data === null })
      promiseReject = promiseResolve = null
    }

    function destroy(err) {
      stream.destroy(err)
      return new Promise((resolve, reject) => {
        if (stream._duplexState & DESTROYED) return resolve({ value: undefined, done: true })
        stream.once('close', function () {
          if (err) reject(err)
          else resolve({ value: undefined, done: true })
        })
      })
    }
  }
}

class Writable extends Stream {
  constructor(opts) {
    super(opts)

    this._duplexState |= OPENING | READ_DONE
    this._writableState = new WritableState(this, opts)

    if (opts) {
      if (opts.writev) this._writev = opts.writev
      if (opts.write) this._write = opts.write
      if (opts.final) this._final = opts.final
      if (opts.eagerOpen) this._writableState.updateNextTick()
    }
  }

  cork() {
    this._duplexState |= WRITE_CORKED
  }

  uncork() {
    this._duplexState &= WRITE_NOT_CORKED
    this._writableState.updateNextTick()
  }

  _writev(batch, cb) {
    cb(null)
  }

  _write(data, cb) {
    this._writableState.autoBatch(data, cb)
  }

  _final(cb) {
    cb(null)
  }

  static isBackpressured(ws) {
    return (ws._duplexState & WRITE_BACKPRESSURE_STATUS) !== 0
  }

  static drained(ws) {
    if (ws.destroyed) return Promise.resolve(false)
    const state = ws._writableState
    const pending = isWritev(ws) ? Math.min(1, state.queue.length) : state.queue.length
    const writes = pending + (ws._duplexState & WRITE_WRITING ? 1 : 0)
    if (writes === 0) return Promise.resolve(true)
    if (state.drains === null) state.drains = []
    return new Promise((resolve) => {
      state.drains.push({ writes, resolve })
    })
  }

  write(data) {
    this._writableState.updateNextTick()
    return this._writableState.push(data)
  }

  end(data) {
    this._writableState.updateNextTick()
    this._writableState.end(data)
    return this
  }
}

class Duplex extends Readable {
  // and Writable
  constructor(opts) {
    super(opts)

    this._duplexState = OPENING | (this._duplexState & READ_READ_AHEAD)
    this._writableState = new WritableState(this, opts)

    if (opts) {
      if (opts.writev) this._writev = opts.writev
      if (opts.write) this._write = opts.write
      if (opts.final) this._final = opts.final
    }
  }

  cork() {
    this._duplexState |= WRITE_CORKED
  }

  uncork() {
    this._duplexState &= WRITE_NOT_CORKED
    this._writableState.updateNextTick()
  }

  _writev(batch, cb) {
    cb(null)
  }

  _write(data, cb) {
    this._writableState.autoBatch(data, cb)
  }

  _final(cb) {
    cb(null)
  }

  write(data) {
    this._writableState.updateNextTick()
    return this._writableState.push(data)
  }

  end(data) {
    this._writableState.updateNextTick()
    this._writableState.end(data)
    return this
  }
}

class Transform extends Duplex {
  constructor(opts) {
    super(opts)
    this._transformState = new TransformState(this)

    if (opts) {
      if (opts.transform) this._transform = opts.transform
      if (opts.flush) this._flush = opts.flush
    }
  }

  _write(data, cb) {
    if (this._readableState.buffered >= this._readableState.highWaterMark) {
      this._transformState.data = data
    } else {
      this._transform(data, this._transformState.afterTransform)
    }
  }

  _read(cb) {
    if (this._transformState.data !== null) {
      const data = this._transformState.data
      this._transformState.data = null
      cb(null)
      this._transform(data, this._transformState.afterTransform)
    } else {
      cb(null)
    }
  }

  destroy(err) {
    super.destroy(err)
    if (this._transformState.data !== null) {
      this._transformState.data = null
      this._transformState.afterTransform()
    }
  }

  _transform(data, cb) {
    cb(null, data)
  }

  _flush(cb) {
    cb(null)
  }

  _final(cb) {
    this._transformState.afterFinal = cb
    this._flush(transformAfterFlush.bind(this))
  }
}

class PassThrough extends Transform {}

function transformAfterFlush(err, data) {
  const cb = this._transformState.afterFinal
  if (err) return cb(err)
  if (data !== null && data !== undefined) this.push(data)
  this.push(null)
  cb(null)
}

function pipelinePromise(...streams) {
  return new Promise((resolve, reject) => {
    return pipeline(...streams, (err) => {
      if (err) return reject(err)
      resolve()
    })
  })
}

function pipeline(stream, ...streams) {
  const all = Array.isArray(stream) ? [...stream, ...streams] : [stream, ...streams]
  const done = all.length && typeof all[all.length - 1] === 'function' ? all.pop() : null

  if (all.length < 2) throw new Error('Pipeline requires at least 2 streams')

  let src = all[0]
  let dest = null
  let error = null

  for (let i = 1; i < all.length; i++) {
    dest = all[i]

    if (isStreamx(src)) {
      src.pipe(dest, onerror)
    } else {
      errorHandle(src, true, i > 1, onerror)
      src.pipe(dest)
    }

    src = dest
  }

  if (done) {
    let fin = false

    const autoDestroy =
      isStreamx(dest) || !!(dest._writableState && dest._writableState.autoDestroy)

    dest.on('error', (err) => {
      if (error === null) error = err
    })

    dest.on('finish', () => {
      fin = true
      if (!autoDestroy) done(error)
    })

    if (autoDestroy) {
      dest.on('close', () => done(error || (fin ? null : PREMATURE_CLOSE)))
    }
  }

  return dest

  function errorHandle(s, rd, wr, onerror) {
    s.on('error', onerror)
    s.on('close', onclose)

    function onclose() {
      if (rd && s._readableState && !s._readableState.ended) return onerror(PREMATURE_CLOSE)
      if (wr && s._writableState && !s._writableState.ended) return onerror(PREMATURE_CLOSE)
    }
  }

  function onerror(err) {
    if (!err || error) return
    error = err

    for (const s of all) {
      s.destroy(err)
    }
  }
}

function echo(s) {
  return s
}

function isStream(stream) {
  return !!stream._readableState || !!stream._writableState
}

function isStreamx(stream) {
  return typeof stream._duplexState === 'number' && isStream(stream)
}

function isEnding(stream) {
  return !!stream._readableState && stream._readableState.ending
}

function isEnded(stream) {
  return !!stream._readableState && stream._readableState.ended
}

function isFinishing(stream) {
  return !!stream._writableState && stream._writableState.ending
}

function isFinished(stream) {
  return !!stream._writableState && stream._writableState.ended
}

function getStreamError(stream, opts = {}) {
  const err =
    (stream._readableState && stream._readableState.error) ||
    (stream._writableState && stream._writableState.error)

  // avoid implicit errors by default
  return !opts.all && err === STREAM_DESTROYED ? null : err
}

function isReadStreamx(stream) {
  return isStreamx(stream) && stream.readable
}

function isDisturbed(stream) {
  return (
    (stream._duplexState & OPENING) !== OPENING ||
    (stream._duplexState & DESTROYING) === DESTROYING ||
    (stream._duplexState & ACTIVE_OR_TICKING) !== 0
  )
}

function isTypedArray(data) {
  return typeof data === 'object' && data !== null && typeof data.byteLength === 'number'
}

function defaultByteLength(data) {
  return isTypedArray(data) ? data.byteLength : 1024
}

function noop() {}

function abort() {
  this.destroy(new Error('Stream aborted.'))
}

function isWritev(s) {
  return s._writev !== Writable.prototype._writev && s._writev !== Duplex.prototype._writev
}

module.exports = {
  pipeline,
  pipelinePromise,
  isStream,
  isStreamx,
  isEnding,
  isEnded,
  isFinishing,
  isFinished,
  isDisturbed,
  getStreamError,
  Stream,
  Writable,
  Readable,
  Duplex,
  Transform,
  // Export PassThrough for compatibility with Node.js core's stream module
  PassThrough
}
{
  "name": "streamx",
  "version": "2.25.0",
  "description": "An iteration of the Node.js core streams with a series of improvements",
  "main": "index.js",
  "dependencies": {
    "events-universal": "^1.0.0",
    "fast-fifo": "^1.3.2",
    "text-decoder": "^1.1.0"
  },
  "devDependencies": {
    "b4a": "^1.6.6",
    "brittle": "^3.1.1",
    "end-of-stream": "^1.4.4",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^2.0.0"
  },
  "files": [
    "index.js"
  ],
  "scripts": {
    "format": "prettier --write .",
    "test": "prettier --check . && node test/all.js",
    "test:bare": "bare test/all.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/mafintosh/streamx.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/mafintosh/streamx/issues"
  },
  "homepage": "https://github.com/mafintosh/streamx"
}
const { Readable } = require('streamx')

module.exports = function (s, forks = 2) {
  const streams = new Array(forks)
  const status = new Array(forks).fill(true)

  let ended = false

  for (let i = 0; i < forks; i++) {
    streams[i] = new Readable({
      read (cb) {
        const check = !status[i]
        status[i] = true
        if (check && allReadable()) s.resume()
        cb(null)
      }
    })
  }

  s.on('end', function () {
    ended = true
    for (const stream of streams) stream.push(null)
  })

  s.on('error', function (err) {
    for (const stream of streams) stream.destroy(err)
  })

  s.on('close', function () {
    if (ended) return
    for (const stream of streams) stream.destroy()
  })

  s.on('data', function (data) {
    let needsPause = false
    for (let i = 0; i < streams.length; i++) {
      if (!(status[i] = streams[i].push(data))) {
        needsPause = true
      }
    }
    if (needsPause) s.pause()
  })

  return streams

  function allReadable () {
    for (let j = 0; j < status.length; j++) {
      if (!status[j]) return false
    }
    return true
  }
}
{
  "name": "teex",
  "version": "1.0.1",
  "description": "Turn a readable stream into multiple readable streamx streams",
  "main": "index.js",
  "dependencies": {
    "streamx": "^2.12.5"
  },
  "devDependencies": {
    "standard": "^14.3.4",
    "tape": "^5.0.1"
  },
  "scripts": {
    "test": "standard && tape test.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/mafintosh/teex.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/mafintosh/teex/issues"
  },
  "homepage": "https://github.com/mafintosh/teex"
}
const PassThroughDecoder = require('./lib/pass-through-decoder')
const UTF8Decoder = require('./lib/utf8-decoder')

module.exports = class TextDecoder {
  constructor(encoding = 'utf8') {
    this.encoding = normalizeEncoding(encoding)

    switch (this.encoding) {
      case 'utf8':
        this.decoder = new UTF8Decoder()
        break
      case 'utf16le':
      case 'base64':
        throw new Error('Unsupported encoding: ' + this.encoding)
      default:
        this.decoder = new PassThroughDecoder(this.encoding)
    }
  }

  get remaining() {
    return this.decoder.remaining
  }

  push(data) {
    if (typeof data === 'string') return data
    return this.decoder.decode(data)
  }

  // For Node.js compatibility
  write(data) {
    return this.push(data)
  }

  end(data) {
    let result = ''
    if (data) result = this.push(data)
    result += this.decoder.flush()
    return result
  }
}

function normalizeEncoding(encoding) {
  encoding = encoding.toLowerCase()

  switch (encoding) {
    case 'utf8':
    case 'utf-8':
      return 'utf8'
    case 'ucs2':
    case 'ucs-2':
    case 'utf16le':
    case 'utf-16le':
      return 'utf16le'
    case 'latin1':
    case 'binary':
      return 'latin1'
    case 'base64':
    case 'ascii':
    case 'hex':
      return encoding
    default:
      throw new Error('Unknown encoding: ' + encoding)
  }
}
const b4a = require('b4a')

module.exports = class PassThroughDecoder {
  constructor(encoding) {
    this.encoding = encoding
  }

  get remaining() {
    return 0
  }

  decode(data) {
    return b4a.toString(data, this.encoding)
  }

  flush() {
    return ''
  }
}
const b4a = require('b4a')

/**
 * https://encoding.spec.whatwg.org/#utf-8-decoder
 */
module.exports = class UTF8Decoder {
  constructor() {
    this._reset()
  }

  get remaining() {
    return this.bytesSeen
  }

  decode(data) {
    if (data.byteLength === 0) return ''

    if (this.bytesNeeded === 0 && trailingIncomplete(data, 0) === 0) {
      this.bytesSeen = trailingBytesSeen(data)
      return b4a.toString(data, 'utf8')
    }

    let result = ''
    let start = 0

    if (this.bytesNeeded > 0) {
      while (start < data.byteLength) {
        const byte = data[start]

        if (byte < this.lowerBoundary || byte > this.upperBoundary) {
          result += '\ufffd'
          this._reset()
          break
        }

        this.lowerBoundary = 0x80
        this.upperBoundary = 0xbf
        this.codePoint = (this.codePoint << 6) | (byte & 0x3f)
        this.bytesSeen++
        start++

        if (this.bytesSeen === this.bytesNeeded) {
          result += String.fromCodePoint(this.codePoint)
          this._reset()
          break
        }
      }

      if (this.bytesNeeded > 0) return result
    }

    const trailing = trailingIncomplete(data, start)
    const end = data.byteLength - trailing

    if (end > start) result += b4a.toString(data, 'utf8', start, end)

    for (let i = end; i < data.byteLength; i++) {
      const byte = data[i]

      if (this.bytesNeeded === 0) {
        if (byte <= 0x7f) {
          this.bytesSeen = 0
          result += String.fromCharCode(byte)
        } else if (byte >= 0xc2 && byte <= 0xdf) {
          this.bytesNeeded = 2
          this.bytesSeen = 1
          this.codePoint = byte & 0x1f
        } else if (byte >= 0xe0 && byte <= 0xef) {
          if (byte === 0xe0) this.lowerBoundary = 0xa0
          else if (byte === 0xed) this.upperBoundary = 0x9f
          this.bytesNeeded = 3
          this.bytesSeen = 1
          this.codePoint = byte & 0xf
        } else if (byte >= 0xf0 && byte <= 0xf4) {
          if (byte === 0xf0) this.lowerBoundary = 0x90
          else if (byte === 0xf4) this.upperBoundary = 0x8f
          this.bytesNeeded = 4
          this.bytesSeen = 1
          this.codePoint = byte & 0x7
        } else {
          this.bytesSeen = 1
          result += '\ufffd'
        }

        continue
      }

      if (byte < this.lowerBoundary || byte > this.upperBoundary) {
        result += '\ufffd'
        i--
        this._reset()

        continue
      }

      this.lowerBoundary = 0x80
      this.upperBoundary = 0xbf

      this.codePoint = (this.codePoint << 6) | (byte & 0x3f)
      this.bytesSeen++

      if (this.bytesSeen === this.bytesNeeded) {
        result += String.fromCodePoint(this.codePoint)
        this._reset()
      }
    }

    return result
  }

  flush() {
    const result = this.bytesNeeded > 0 ? '\ufffd' : ''
    this._reset()
    return result
  }

  _reset() {
    this.codePoint = 0
    this.bytesNeeded = 0
    this.bytesSeen = 0
    this.lowerBoundary = 0x80
    this.upperBoundary = 0xbf
  }
}

function trailingIncomplete(data, start) {
  const len = data.byteLength
  if (len <= start) return 0

  const limit = Math.max(start, len - 4)

  let i = len - 1
  while (i > limit && (data[i] & 0xc0) === 0x80) i--

  if (i < start) return 0

  const byte = data[i]

  let needed
  if (byte <= 0x7f) return 0
  if (byte >= 0xc2 && byte <= 0xdf) needed = 2
  else if (byte >= 0xe0 && byte <= 0xef) needed = 3
  else if (byte >= 0xf0 && byte <= 0xf4) needed = 4
  else return 0

  const available = len - i
  return available < needed ? available : 0
}

function trailingBytesSeen(data) {
  const len = data.byteLength
  if (len === 0) return 0

  const last = data[len - 1]

  if (last <= 0x7f) return 0
  if ((last & 0xc0) !== 0x80) return 1

  const limit = Math.max(0, len - 4)

  let i = len - 2
  while (i >= limit && (data[i] & 0xc0) === 0x80) i--

  if (i < 0) return 1

  const first = data[i]

  let needed
  if (first >= 0xc2 && first <= 0xdf) needed = 2
  else if (first >= 0xe0 && first <= 0xef) needed = 3
  else if (first >= 0xf0 && first <= 0xf4) needed = 4
  else return 1

  if (len - i !== needed) return 1

  if (needed >= 3) {
    const second = data[i + 1]
    if (first === 0xe0 && second < 0xa0) return 1
    if (first === 0xed && second > 0x9f) return 1
    if (first === 0xf0 && second < 0x90) return 1
    if (first === 0xf4 && second > 0x8f) return 1
  }

  return 0
}
{
  "name": "text-decoder",
  "version": "1.2.7",
  "description": "Streaming text decoder that preserves multibyte Unicode characters",
  "exports": {
    "./package": "./package.json",
    ".": "./index.js"
  },
  "files": [
    "index.js",
    "lib"
  ],
  "scripts": {
    "format": "prettier --write .",
    "lint": "prettier --check . && lunte",
    "test": "npm run test:node && npm run test:bare",
    "test:node": "brittle-node test.js",
    "test:bare": "brittle-bare test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/text-decoder.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/text-decoder/issues"
  },
  "homepage": "https://github.com/holepunchto/text-decoder#readme",
  "dependencies": {
    "b4a": "^1.6.4"
  },
  "devDependencies": {
    "brittle": "^3.3.2",
    "lunte": "^1.3.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^1.0.0"
  }
}
module.exports = class TimeOrderedSet {
  constructor () {
    this.oldest = null
    this.latest = null
    this.length = 0
  }

  has (node) {
    return !!(node.next || node.prev) || node === this.oldest
  }

  add (node) {
    if (this.has(node)) this.remove(node)

    if (!this.latest && !this.oldest) {
      this.latest = this.oldest = node
      node.prev = node.next = null
    } else {
      this.latest.next = node
      node.prev = this.latest
      node.next = null
      this.latest = node
    }

    this.length++

    return node
  }

  remove (node) {
    if (!this.has(node)) return node

    if (this.oldest !== node && this.latest !== node) {
      node.prev.next = node.next
      node.next.prev = node.prev
    } else {
      if (this.oldest === node) {
        this.oldest = node.next
        if (this.oldest) this.oldest.prev = null
      }
      if (this.latest === node) {
        this.latest = node.prev
        if (this.latest) this.latest.next = null
      }
    }

    node.next = node.prev = null
    this.length--

    return node
  }

  toArray ({ limit = Infinity, reverse = false } = {}) {
    const list = []

    if (reverse) {
      let node = this.latest
      while (node && limit--) {
        list.push(node)
        node = node.prev
      }
    } else {
      let node = this.oldest
      while (node && limit--) {
        list.push(node)
        node = node.next
      }
    }

    return list
  }
}
{
  "name": "time-ordered-set",
  "version": "2.0.1",
  "description": "Efficiently maintain a set of nodes ordered by the time they were added to the set",
  "main": "index.js",
  "files": [
    "index.js"
  ],
  "devDependencies": {
    "brittle": "^3.0.0",
    "standard": "^17.1.2"
  },
  "scripts": {
    "test": "standard && brittle test.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/mafintosh/time-ordered-set.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/mafintosh/time-ordered-set/issues"
  },
  "homepage": "https://github.com/mafintosh/time-ordered-set"
}
module.exports = class TimerBrowser {
  constructor (ms, fn, ctx = null, interval = false) {
    this.ms = ms
    this.ontimeout = fn
    this.context = ctx || null
    this.interval = interval
    this.done = false

    this._timer = interval
      ? setInterval(callInterval, ms, this)
      : setTimeout(callTimeout, ms, this)
  }

  unref () {}

  ref () {}

  refresh () {
    if (this.done) return

    if (this.interval) {
      clearInterval(this._timer)
      this._timer = setInterval(callInterval, this.ms, this)
    } else {
      clearTimeout(this._timer)
      this._timer = setTimeout(callTimeout, this.ms, this)
    }
  }

  destroy () {
    this.done = true
    this.ontimeout = null

    if (this.interval) clearInterval(this._timer)
    else clearTimeout(this._timer)
  }

  static once (ms, fn, ctx) {
    return new this(ms, fn, ctx, false)
  }

  static on (ms, fn, ctx) {
    return new this(ms, fn, ctx, true)
  }
}

function callTimeout (self) {
  self.done = true
  self.ontimeout.call(self.context)
}

function callInterval (self) {
  self.ontimeout.call(self.context)
}
module.exports = isNode()
  ? require('./node')
  : require('./browser')

function isNode () {
  const to = setTimeout(function () {}, 1000)
  clearTimeout(to)
  return !!to.refresh
}
module.exports = class Timer {
  constructor (ms, fn, ctx = null, interval = false) {
    this.ms = ms
    this.ontimeout = fn
    this.context = ctx
    this.interval = interval
    this.done = false

    this._timer = interval
      ? setInterval(callInterval, ms, this)
      : setTimeout(callTimeout, ms, this)
  }

  unref () {
    this._timer.unref()
  }

  ref () {
    this._timer.ref()
  }

  refresh () {
    if (this.done !== true) this._timer.refresh()
  }

  destroy () {
    this.done = true
    this.ontimeout = null
    if (this.interval) clearInterval(this._timer)
    else clearTimeout(this._timer)
  }

  static once (ms, fn, ctx) {
    return new this(ms, fn, ctx, false)
  }

  static on (ms, fn, ctx) {
    return new this(ms, fn, ctx, true)
  }
}

function callTimeout (self) {
  self.done = true
  self.ontimeout.call(self.context)
}

function callInterval (self) {
  self.ontimeout.call(self.context)
}
{
  "name": "timeout-refresh",
  "version": "2.0.1",
  "description": "Efficiently refresh a timer",
  "main": "index.js",
  "dependencies": {},
  "devDependencies": {
    "standard": "^16.0.4",
    "tape": "^5.5.2"
  },
  "browser": "./browser.js",
  "scripts": {
    "test": "standard && tape test.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/mafintosh/timeout-refresh.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/mafintosh/timeout-refresh/issues"
  },
  "homepage": "https://github.com/mafintosh/timeout-refresh"
}
require.addon = require('require-addon')

module.exports = require.addon('.', __filename)
// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.

const v4Seg = '(?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])'
const v4Str = `(${v4Seg}[.]){3}${v4Seg}`
const IPv4Pattern = new RegExp(`^${v4Str}$`)

const v6Seg = '(?:[0-9a-fA-F]{1,4})'
const IPv6Pattern = new RegExp(
  '^(' +
    `(?:${v6Seg}:){7}(?:${v6Seg}|:)|` +
    `(?:${v6Seg}:){6}(?:${v4Str}|:${v6Seg}|:)|` +
    `(?:${v6Seg}:){5}(?::${v4Str}|(:${v6Seg}){1,2}|:)|` +
    `(?:${v6Seg}:){4}(?:(:${v6Seg}){0,1}:${v4Str}|(:${v6Seg}){1,3}|:)|` +
    `(?:${v6Seg}:){3}(?:(:${v6Seg}){0,2}:${v4Str}|(:${v6Seg}){1,4}|:)|` +
    `(?:${v6Seg}:){2}(?:(:${v6Seg}){0,3}:${v4Str}|(:${v6Seg}){1,5}|:)|` +
    `(?:${v6Seg}:){1}(?:(:${v6Seg}){0,4}:${v4Str}|(:${v6Seg}){1,6}|:)|` +
    `(?::((?::${v6Seg}){0,5}:${v4Str}|(?::${v6Seg}){1,7}|:))` +
    ')(%[0-9a-zA-Z-.:]{1,})?$'
)

const isIPv4 = (exports.isIPv4 = function isIPv4(host) {
  return IPv4Pattern.test(host)
})

const isIPv6 = (exports.isIPv6 = function isIPv6(host) {
  return IPv6Pattern.test(host)
})

exports.isIP = function isIP(host) {
  if (isIPv4(host)) return 4
  if (isIPv6(host)) return 6
  return 0
}
const events = require('events')
const b4a = require('b4a')
const binding = require('../binding')

module.exports = class NetworkInterfaces extends events.EventEmitter {
  constructor(udx) {
    super()

    this._handle = b4a.alloc(binding.sizeof_udx_napi_interface_event_t)
    this._watching = false
    this._destroying = null

    binding.udx_napi_interface_event_init(
      udx._handle,
      this._handle,
      this,
      this._onevent,
      this._onclose
    )

    this.interfaces = binding.udx_napi_interface_event_get_addrs(this._handle)
  }

  _onclose() {
    this.emit('close')
  }

  _onevent() {
    this.interfaces = binding.udx_napi_interface_event_get_addrs(this._handle)

    this.emit('change', this.interfaces)
  }

  watch() {
    if (this._watching) return this
    this._watching = true

    binding.udx_napi_interface_event_start(this._handle)

    return this
  }

  unwatch() {
    if (!this._watching) return this
    this._watching = false

    binding.udx_napi_interface_event_stop(this._handle)

    return this
  }

  async destroy() {
    if (this._destroying) return this._destroying
    this._destroying = events.once(this, 'close')

    binding.udx_napi_interface_event_close(this._handle)

    return this._destroying
  }

  [Symbol.iterator]() {
    return this.interfaces[Symbol.iterator]()
  }
}
const events = require('events')
const b4a = require('b4a')
const binding = require('../binding')
const ip = require('./ip')

module.exports = class UDXSocket extends events.EventEmitter {
  constructor(udx, opts = {}) {
    super()

    this.udx = udx

    this._handle = b4a.alloc(binding.sizeof_udx_napi_socket_t)
    this._inited = false
    this._host = null
    this._family = 0
    this._ipv6Only = opts.ipv6Only === true
    this._reuseAddress = opts.reuseAddress === true
    this._port = 0
    this._reqs = []
    this._free = []
    this._closing = null
    this._closed = false

    this._view64 = new BigUint64Array(
      this._handle.buffer,
      this._handle.byteOffset,
      this._handle.byteLength >> 3
    )

    this.streams = new Set()

    this.userData = null
  }

  get bound() {
    return this._port !== 0
  }

  get closing() {
    return this._closing !== null
  }

  get idle() {
    return this.streams.size === 0
  }

  get busy() {
    return this.streams.size > 0
  }

  get bytesTransmitted() {
    if (this._inited !== true) return 0
    return Number(this._view64[binding.offsetof_udx_socket_t_bytes_tx >> 3])
  }

  get packetsTransmitted() {
    if (this._inited !== true) return 0
    return Number(this._view64[binding.offsetof_udx_socket_t_packets_tx >> 3])
  }

  get bytesReceived() {
    if (this._inited !== true) return 0
    return Number(this._view64[binding.offsetof_udx_socket_t_bytes_rx >> 3])
  }

  get packetsReceived() {
    if (this._inited !== true) return 0
    return Number(this._view64[binding.offsetof_udx_socket_t_packets_rx >> 3])
  }

  get packetsDroppedByKernel() {
    if (this._inited !== true) return 0
    return Number(this._view64[binding.offsetof_udx_socket_t_packets_dropped_by_kernel >> 3])
  }

  toJSON() {
    return {
      bound: this.bound,
      closing: this.closing,
      streams: this.streams.size,
      address: this.address(),
      ipv6Only: this._ipv6Only,
      reuseAddress: this._reuseAddress,
      idle: this.idle,
      busy: this.busy
    }
  }

  _init() {
    if (this._inited) return

    binding.udx_napi_socket_init(
      this.udx._handle,
      this._handle,
      this,
      this._onsend,
      this._onmessage,
      this._onclose,
      this._reallocMessage
    )

    this._inited = true
  }

  _onsend(id, err) {
    const req = this._reqs[id]

    const onflush = req.onflush

    req.buffer = null
    req.onflush = null

    this._free.push(id)

    onflush(err >= 0)

    // gc the free list
    if (this._free.length >= 16 && this._free.length === this._reqs.length) {
      this._free = []
      this._reqs = []
    }
  }

  _onmessage(len, port, host, family) {
    this.emit('message', this.udx._consumeMessage(len), { host, family, port })
    return this.udx._buffer
  }

  _onclose() {
    this.emit('close')
  }

  _reallocMessage() {
    return this.udx._reallocMessage()
  }

  _onidle() {
    this.emit('idle')
  }

  _onbusy() {
    this.emit('busy')
  }

  _addStream(stream) {
    if (this.streams.has(stream)) return false
    this.streams.add(stream)
    if (this.streams.size === 1) this._onbusy()
    return true
  }

  _removeStream(stream) {
    if (!this.streams.has(stream)) return false
    this.streams.delete(stream)
    const closed = this._closeMaybe()
    if (this.idle && !closed) this._onidle()
    return true
  }

  address() {
    if (!this.bound) return null
    return { host: this._host, family: this._family, port: this._port }
  }

  bind(port, host) {
    if (this.bound) throw new Error('Already bound')
    if (this.closing) throw new Error('Socket is closed')

    if (!port) port = 0

    let flags = 0
    if (this._ipv6Only) flags |= binding.UV_UDP_IPV6ONLY
    if (this._reuseAddress) flags |= binding.UV_UDP_REUSEADDR

    let family

    if (host) {
      family = ip.isIP(host)
      if (!family) throw new Error(`${host} is not a valid IP address`)

      if (!this._inited) this._init()

      this._port = binding.udx_napi_socket_bind(this._handle, port, host, family, flags)
    } else {
      if (!this._inited) this._init()

      try {
        host = '::'
        family = 6
        this._port = binding.udx_napi_socket_bind(this._handle, port, host, family, flags)
      } catch {
        host = '0.0.0.0'
        family = 4
        this._port = binding.udx_napi_socket_bind(this._handle, port, host, family, flags)
      }
    }

    this._host = host
    this._family = family

    this.emit('listening')
  }

  async close() {
    if (this._closing) return this._closing
    this._closing = new Promise((resolve) => this.once('close', resolve))
    this._closeMaybe()
    return this._closing
  }

  _closeMaybe() {
    if (this._closed || this._closing === null) return this._closed

    if (!this._inited) {
      this._closed = true
      this.emit('close')
      return true
    }

    if (this.idle) {
      binding.udx_napi_socket_close(this._handle)
      this._closed = true
    }

    return this._closed
  }

  setTTL(ttl) {
    if (!this._inited) throw new Error('Socket not active')
    binding.udx_napi_socket_set_ttl(this._handle, ttl)
  }

  getRecvBufferSize() {
    if (!this._inited) throw new Error('Socket not active')
    return binding.udx_napi_socket_get_recv_buffer_size(this._handle)
  }

  setRecvBufferSize(size) {
    if (!this._inited) throw new Error('Socket not active')
    return binding.udx_napi_socket_set_recv_buffer_size(this._handle, size)
  }

  getSendBufferSize() {
    if (!this._inited) throw new Error('Socket not active')
    return binding.udx_napi_socket_get_send_buffer_size(this._handle)
  }

  setSendBufferSize(size) {
    if (!this._inited) throw new Error('Socket not active')
    return binding.udx_napi_socket_set_send_buffer_size(this._handle, size)
  }

  addMembership(group, ifaceAddress) {
    if (!this._inited) throw new Error('Socket not active')
    return binding.udx_napi_socket_set_membership(this._handle, group, ifaceAddress || '', true)
  }

  dropMembership(group, ifaceAddress) {
    if (!this._inited) throw new Error('Socket not active')
    return binding.udx_napi_socket_set_membership(this._handle, group, ifaceAddress || '', false)
  }

  async send(buffer, port, host, ttl) {
    if (this.closing) return false

    if (!host) host = '127.0.0.1'

    const family = ip.isIP(host)
    if (!family) throw new Error(`${host} is not a valid IP address`)

    if (!this.bound) this.bind(0)

    const id = this._allocSend()
    const req = this._reqs[id]

    req.buffer = buffer

    const promise = new Promise((resolve) => {
      req.onflush = resolve
    })

    binding.udx_napi_socket_send_ttl(
      this._handle,
      req.handle,
      id,
      buffer,
      port,
      host,
      family,
      ttl || 0
    )

    return promise
  }

  trySend(buffer, port, host, ttl) {
    if (this.closing) return

    if (!host) host = '127.0.0.1'

    const family = ip.isIP(host)
    if (!family) throw new Error(`${host} is not a valid IP address`)

    if (!this.bound) this.bind(0)

    const id = this._allocSend()
    const req = this._reqs[id]

    req.buffer = buffer
    req.onflush = noop

    binding.udx_napi_socket_send_ttl(
      this._handle,
      req.handle,
      id,
      buffer,
      port,
      host,
      family,
      ttl || 0
    )
  }

  _allocSend() {
    if (this._free.length > 0) return this._free.pop()
    const handle = b4a.allocUnsafe(binding.sizeof_udx_socket_send_t)
    return this._reqs.push({ handle, buffer: null, onflush: null }) - 1
  }
}

function noop() {}
const streamx = require('streamx')
const b4a = require('b4a')
const binding = require('../binding')
const ip = require('./ip')

const MAX_PACKET = 2048
const BUFFER_SIZE = 65536 + MAX_PACKET

module.exports = class UDXStream extends streamx.Duplex {
  constructor(udx, id, opts = {}) {
    super({ mapWritable: toBuffer, eagerOpen: true })

    this.udx = udx
    this.socket = null

    this._handle = b4a.alloc(binding.sizeof_udx_napi_stream_t)
    this._view = new Uint32Array(
      this._handle.buffer,
      this._handle.byteOffset,
      this._handle.byteLength >> 2
    )
    this._view16 = new Uint16Array(
      this._handle.buffer,
      this._handle.byteOffset,
      this._handle.byteLength >> 1
    )
    this._view64 = new BigUint64Array(
      this._handle.buffer,
      this._handle.byteOffset,
      this._handle.byteLength >> 3
    )

    this._wreqs = []
    this._wfree = []

    this._sreqs = []
    this._sfree = []
    this._closed = false

    this._flushing = 0
    this._flushes = []

    this._buffer = null
    this._reallocData()

    this._onwrite = null
    this._ondestroy = null
    this._firewall = opts.firewall || firewallAll

    this._remoteChanging = null
    this._previousSocket = null

    this.id = id
    this.remoteId = 0
    this.remoteHost = null
    this.remoteFamily = 0
    this.remotePort = 0

    this.userData = null

    binding.udx_napi_stream_init(
      this.udx._handle,
      this._handle,
      id,
      opts.framed ? 1 : 0,
      this,
      this._ondata,
      this._onend,
      this._ondrain,
      this._onack,
      this._onsend,
      this._onmessage,
      this._onclose,
      this._onfirewall,
      this._onremotechanged,
      this._reallocData,
      this._reallocMessage
    )

    if (opts.seq) binding.udx_napi_stream_set_seq(this._handle, opts.seq)

    binding.udx_napi_stream_recv_start(this._handle, this._buffer)
  }

  get connected() {
    return this.socket !== null
  }

  get mtu() {
    return this._view16[binding.offsetof_udx_stream_t_mtu >> 1]
  }

  get rtt() {
    return this._view[binding.offsetof_udx_stream_t_srtt >> 2]
  }

  get cwnd() {
    return this._view[binding.offsetof_udx_stream_t_cwnd >> 2]
  }

  get rtoCount() {
    return this._view16[binding.offsetof_udx_stream_t_rto_count >> 1]
  }

  get retransmits() {
    return this._view16[binding.offsetof_udx_stream_t_retransmit_count >> 1]
  }

  get fastRecoveries() {
    return this._view16[binding.offsetof_udx_stream_t_fast_recovery_count >> 1]
  }

  get inflight() {
    return this._view[binding.offsetof_udx_stream_t_inflight >> 2]
  }

  get bytesTransmitted() {
    return Number(this._view64[binding.offsetof_udx_stream_t_bytes_tx >> 3])
  }

  get packetsTransmitted() {
    return Number(this._view64[binding.offsetof_udx_stream_t_packets_tx >> 3])
  }

  get bytesReceived() {
    return Number(this._view64[binding.offsetof_udx_stream_t_bytes_rx >> 3])
  }

  get packetsReceived() {
    return Number(this._view64[binding.offsetof_udx_stream_t_packets_rx >> 3])
  }

  get localHost() {
    return this.socket ? this.socket.address().host : null
  }

  get localFamily() {
    return this.socket ? this.socket.address().family : 0
  }

  get localPort() {
    return this.socket ? this.socket.address().port : 0
  }

  setInteractive(bool) {
    if (!this._closed) return
    binding.udx_napi_stream_set_mode(this._handle, bool ? 0 : 1)
  }

  connect(socket, remoteId, port, host, opts = {}) {
    if (this._closed) return

    if (this.connected) throw new Error('Already connected')
    if (socket.closing) throw new Error('Socket is closed')

    if (typeof host === 'object') {
      opts = host
      host = null
    }

    if (!host) host = '127.0.0.1'

    const family = ip.isIP(host)
    if (!family) throw new Error(`${host} is not a valid IP address`)
    if (!(port > 0 && port < 65536)) throw new Error(`${port} is not a valid port`)

    if (!socket.bound) socket.bind(0)

    this.remoteId = remoteId
    this.remotePort = port
    this.remoteHost = host
    this.remoteFamily = family
    this.socket = socket

    if (opts.ack) binding.udx_napi_stream_set_ack(this._handle, opts.ack)

    binding.udx_napi_stream_connect(this._handle, socket._handle, remoteId, port, host, family)

    this.socket._addStream(this)

    this.emit('connect')
  }

  changeRemote(socket, remoteId, port, host) {
    if (this._remoteChanging) throw new Error('Remote already changing')

    if (!this.connected) throw new Error('Not yet connected')
    if (socket.closing) throw new Error('Socket is closed')

    if (this.socket.udx !== socket.udx) {
      throw new Error('Cannot change to a socket on another UDX instance')
    }

    if (!host) host = '127.0.0.1'

    const family = ip.isIP(host)
    if (!family) throw new Error(`${host} is not a valid IP address`)
    if (!(port > 0 && port < 65536)) throw new Error(`${port} is not a valid port`)

    if (this.socket !== socket) this._previousSocket = this.socket

    this.remoteId = remoteId
    this.remotePort = port
    this.remoteHost = host
    this.remoteFamily = family
    this.socket = socket

    this._remoteChanging = new Promise((resolve, reject) => {
      const onchanged = () => {
        this.off('close', onclose)
        resolve()
      }

      const onclose = () => {
        this.off('remote-changed', onchanged)
        reject(new Error('Stream is closed'))
      }

      this.once('remote-changed', onchanged).once('close', onclose)
    })

    binding.udx_napi_stream_change_remote(
      this._handle,
      socket._handle,
      remoteId,
      port,
      host,
      family
    )

    this.socket._addStream(this)

    return this._remoteChanging
  }

  relayTo(destination) {
    if (this._closed) return

    binding.udx_napi_stream_relay_to(this._handle, destination._handle)
  }

  async send(buffer) {
    if (!this.connected || this._closed) return false

    const id = this._allocSend()
    const req = this._sreqs[id]

    req.buffer = buffer

    const promise = new Promise((resolve) => {
      req.onflush = resolve
    })

    binding.udx_napi_stream_send(this._handle, req.handle, id, buffer)

    return promise
  }

  trySend(buffer) {
    if (!this.connected || this._closed) return

    const id = this._allocSend()
    const req = this._sreqs[id]

    req.buffer = buffer
    req.onflush = noop

    binding.udx_napi_stream_send(this._handle, req.handle, id, buffer)
  }

  async flush() {
    if ((await streamx.Writable.drained(this)) === false) return false
    if (this.destroying) return false

    const missing = this._wreqs.length - this._wfree.length
    if (missing === 0) return true

    return new Promise((resolve) => {
      this._flushes.push({ flush: this._flushing++, missing, resolve })
    })
  }

  toJSON() {
    return {
      id: this.id,
      connected: this.connected,
      destroying: this.destroying,
      destroyed: this.destroyed,
      remoteId: this.remoteId,
      remoteHost: this.remoteHost,
      remoteFamily: this.remoteFamily,
      remotePort: this.remotePort,
      mtu: this.mtu,
      rtt: this.rtt,
      cwnd: this.cwnd,
      inflight: this.inflight,
      socket: this.socket ? this.socket.toJSON() : null
    }
  }

  _read(cb) {
    cb(null)
  }

  _writeContinue(err) {
    if (this._onwrite === null) return
    const cb = this._onwrite
    this._onwrite = null
    cb(err)
  }

  _destroyContinue(err) {
    if (this._ondestroy === null) return
    const cb = this._ondestroy
    this._ondestroy = null
    cb(err)
  }

  _writev(buffers, cb) {
    if (!this.connected)
      throw customError('Writing while not connected not currently supported', 'ERR_ASSERTION')

    let drained = true

    if (buffers.length === 1) {
      const id = this._allocWrite(1)
      const req = this._wreqs[id]

      req.flush = this._flushing
      req.buffer = buffers[0]

      drained = binding.udx_napi_stream_write(this._handle, req.handle, id, req.buffer) !== 0
    } else {
      const id = this._allocWrite(nextBatchSize(buffers.length))
      const req = this._wreqs[id]

      req.flush = this._flushing
      req.buffers = buffers

      drained = binding.udx_napi_stream_writev(this._handle, req.handle, id, req.buffers) !== 0
    }

    if (drained) cb(null)
    else this._onwrite = cb
  }

  _final(cb) {
    const id = this._allocWrite(1)
    const req = this._wreqs[id]

    req.flush = this._flushes
    req.buffer = b4a.allocUnsafe(0)

    const drained =
      binding.udx_napi_stream_write_end(this._handle, req.handle, id, req.buffer) !== 0

    if (drained) cb(null)
    else this._onwrite = cb
  }

  _predestroy() {
    if (!this._closed) binding.udx_napi_stream_destroy(this._handle)
    this._closed = true
    this._writeContinue(null)
  }

  _destroy(cb) {
    if (this.connected) this._ondestroy = cb
    else cb(null)
  }

  _ondata(read) {
    this.push(this._consumeData(read))
    return this._buffer
  }

  _onend(read) {
    if (read > 0) this.push(this._consumeData(read))
    this.push(null)
  }

  _ondrain() {
    this._writeContinue(null)
  }

  _flushAck(flush) {
    for (let i = this._flushes.length - 1; i >= 0; i--) {
      const f = this._flushes[i]
      if (f.flush < flush) break
      f.missing--
    }

    while (this._flushes.length > 0 && this._flushes[0].missing === 0) {
      this._flushes.shift().resolve(true)
    }
  }

  _onack(id) {
    const req = this._wreqs[id]

    req.buffers = req.buffer = null
    this._wfree.push(id)

    if (this._flushes.length > 0) this._flushAck(req.flush)

    // gc the free list
    if (this._wfree.length >= 64 && this._wfree.length === this._wreqs.length) {
      this._wfree = []
      this._wreqs = []
    }
  }

  _onsend(id, err) {
    const req = this._sreqs[id]

    const onflush = req.onflush

    req.buffer = null
    req.onflush = null

    this._sfree.push(id)

    onflush(err >= 0)

    // gc the free list
    if (this._sfree.length >= 16 && this._sfree.length === this._sreqs.length) {
      this._sfree = []
      this._sreqs = []
    }
  }

  _onmessage(len) {
    this.emit('message', this.udx._consumeMessage(len))
    return this.udx._buffer
  }

  _onclose(err) {
    this._closed = true

    if (this.socket) {
      this.socket._removeStream(this)
      this.socket = null
    }

    if (this._previousSocket) {
      this._previousSocket._removeStream(this)
      this._previousSocket = null
    }

    // no error, we don't need to do anything
    if (!err) return this._destroyContinue(null)

    if (this._ondestroy === null) this.destroy(err)
    else this._destroyContinue(err)
  }

  _onfirewall(socket, port, host, family) {
    return this._firewall(socket, port, host, family) ? 1 : 0
  }

  _onremotechanged() {
    if (this._previousSocket) {
      this._previousSocket._removeStream(this)
      this._previousSocket = null
    }

    this._remoteChanging = null
    this.emit('remote-changed')
  }

  _consumeData(len) {
    const next = this._buffer.subarray(0, len)
    this._buffer = this._buffer.subarray(len)
    if (this._buffer.byteLength < MAX_PACKET) this._reallocData()
    return next
  }

  _reallocData() {
    this._buffer = b4a.allocUnsafe(BUFFER_SIZE)
    return this._buffer
  }

  _reallocMessage() {
    return this.udx._reallocMessage()
  }

  _allocWrite(size) {
    if (this._wfree.length === 0) {
      const handle = b4a.allocUnsafe(binding.udx_napi_stream_write_sizeof(size))
      return (
        this._wreqs.push({
          handle,
          size,
          buffers: null,
          buffer: null,
          flush: 0
        }) - 1
      )
    }

    const free = this._wfree.pop()
    if (size === 1) return free

    const next = this._wreqs[free]
    if (next.size < size) {
      next.handle = b4a.allocUnsafe(binding.udx_napi_stream_write_sizeof(size))
      next.size = size
    }

    return free
  }

  _allocSend() {
    if (this._sfree.length > 0) return this._sfree.pop()
    const handle = b4a.allocUnsafe(binding.sizeof_udx_stream_send_t)
    return this._sreqs.push({ handle, buffer: null, resolve: null, reject: null }) - 1
  }
}

function noop() {}

function toBuffer(data) {
  return typeof data === 'string' ? b4a.from(data) : data
}

function firewallAll(socket, port, host) {
  return true
}

function customError(message, code) {
  const error = new Error(message)
  error.code = code
  return error
}

function nextBatchSize(n) {
  // try to coerce the the writevs into sameish size
  if (n === 1) return 1
  // group all < 8 to the same size, low mem overhead but save some small allocs
  if (n < 8) return 8
  if (n < 16) return 16
  if (n < 32) return 32
  if (n < 64) return 64
  return n
}
const b4a = require('b4a')
const binding = require('../binding')
const ip = require('./ip')
const Socket = require('./socket')
const Stream = require('./stream')
const NetworkInterfaces = require('./network-interfaces')

const MAX_MESSAGE = 4096
const BUFFER_SIZE = 65536 + MAX_MESSAGE

module.exports = class UDX {
  constructor() {
    this._handle = b4a.alloc(binding.sizeof_udx_napi_t)
    this._watchers = new Set()
    this._view64 = new BigUint64Array(
      this._handle.buffer,
      this._handle.byteOffset,
      this._handle.byteLength >> 3
    )

    this._buffer = null
    this._reallocMessage()

    binding.udx_napi_init(this._handle, this._buffer)
  }

  static isIPv4(host) {
    return ip.isIPv4(host)
  }

  static isIPv6(host) {
    return ip.isIPv6(host)
  }

  static isIP(host) {
    return ip.isIP(host)
  }

  get bytesTransmitted() {
    return Number(this._view64[binding.offsetof_udx_t_bytes_tx >> 3])
  }

  get packetsTransmitted() {
    return Number(this._view64[binding.offsetof_udx_t_packets_tx >> 3])
  }

  get bytesReceived() {
    return Number(this._view64[binding.offsetof_udx_t_bytes_rx >> 3])
  }

  get packetsReceived() {
    return Number(this._view64[binding.offsetof_udx_t_packets_rx >> 3])
  }

  get packetsDroppedByKernel() {
    return Number(this._view64[binding.offsetof_udx_t_packets_dropped_by_kernel >> 3])
  }

  _consumeMessage(len) {
    const next = this._buffer.subarray(0, len)
    this._buffer = this._buffer.subarray(len)
    if (this._buffer.byteLength < MAX_MESSAGE) this._reallocMessage()
    return next
  }

  _reallocMessage() {
    // TODO: move reallocation to native
    this._buffer = b4a.allocUnsafe(BUFFER_SIZE)
    return this._buffer
  }

  createSocket(opts) {
    return new Socket(this, opts)
  }

  createStream(id, opts) {
    return new Stream(this, id, opts)
  }

  networkInterfaces() {
    let [watcher = null] = this._watchers
    if (watcher) return watcher.interfaces

    watcher = new NetworkInterfaces(this)
    watcher.destroy()

    return watcher.interfaces
  }

  watchNetworkInterfaces(onchange) {
    const watcher = new NetworkInterfaces(this)

    this._watchers.add(watcher)
    watcher.on('close', () => {
      this._watchers.delete(watcher)
    })

    if (onchange) watcher.on('change', onchange)

    return watcher.watch()
  }

  async lookup(host, opts = {}) {
    const { family = 0 } = opts

    const req = b4a.alloc(binding.sizeof_udx_napi_lookup_t)
    const ctx = {
      req,
      resolve: null,
      reject: null
    }

    const promise = new Promise((resolve, reject) => {
      ctx.resolve = resolve
      ctx.reject = reject
    })

    binding.udx_napi_lookup(this._handle, req, host, family, ctx, onlookup)

    return promise
  }
}

function onlookup(err, host, family) {
  if (err) this.reject(err)
  else this.resolve({ host, family })
}
{
  "name": "udx-native",
  "version": "1.19.2",
  "description": "udx is reliable, multiplexed, and congestion-controlled streams over udp",
  "main": "lib/udx.js",
  "files": [
    "lib",
    "prebuilds",
    "binding.cc",
    "binding.js",
    "CMakeLists.txt"
  ],
  "imports": {
    "events": {
      "bare": "bare-events",
      "default": "events"
    }
  },
  "addon": true,
  "scripts": {
    "format": "prettier --write .",
    "lint": "prettier --check .",
    "test": "npm run lint && npm run test:bare && npm run test:node",
    "test:node": "node test/all.js",
    "test:bare": "bare test/all.js",
    "test:all": "brittle test/*.js test/slow/*.js",
    "test:generate": "brittle -r test/all.js test/*.js",
    "bench": "brittle test/bench/*.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/udx-native.git"
  },
  "keywords": [
    "tcp",
    "udp",
    "stream",
    "reliable"
  ],
  "author": "Holepunch Inc.",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/udx-native/issues"
  },
  "homepage": "https://github.com/holepunchto/udx-native#readme",
  "engines": {
    "bare": ">=1.17.4"
  },
  "dependencies": {
    "b4a": "^1.5.0",
    "bare-events": "^2.2.0",
    "require-addon": "^1.1.0",
    "streamx": "^2.22.0"
  },
  "devDependencies": {
    "bare-compat-napi": "^1.3.0",
    "brittle": "^3.1.0",
    "cmake-bare": "^1.1.10",
    "cmake-fetch": "^1.0.1",
    "cmake-napi": "^1.0.5",
    "is-ci": "^3.0.1",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^2.0.0",
    "tiny-byte-size": "^1.1.0"
  }
}
exports.add = add
exports.has = has
exports.remove = remove
exports.swap = swap

function add (list, item) {
  if (has(list, item)) return item
  item._index = list.length
  list.push(item)
  return item
}

function has (list, item) {
  return item._index < list.length && list[item._index] === item
}

function remove (list, item) {
  if (!has(list, item)) return null

  var last = list.pop()
  if (last !== item) {
    list[item._index] = last
    last._index = item._index
  }

  return item
}

function swap (list, a, b) {
  if (!has(list, a) || !has(list, b)) return
  var tmp = a._index
  a._index = b._index
  list[a._index] = a
  b._index = tmp
  list[b._index] = b
}
{
  "name": "unordered-set",
  "version": "2.0.1",
  "description": "A couple of functions that make it easy to maintain an unordered set as an array in an efficient way",
  "main": "index.js",
  "dependencies": {},
  "devDependencies": {
    "standard": "^6.0.4",
    "tape": "^4.4.0"
  },
  "scripts": {
    "test": "standard && tape test.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/mafintosh/unordered-set.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/mafintosh/unordered-set/issues"
  },
  "homepage": "https://github.com/mafintosh/unordered-set"
}
const b4a = require('b4a')

unslab.all = all
unslab.is = is

module.exports = unslab

function unslab (buf) {
  if (buf === null || buf.buffer.byteLength === buf.byteLength) return buf
  const copy = b4a.allocUnsafeSlow(buf.byteLength)
  copy.set(buf, 0)
  return copy
}

function is (buf) {
  return buf.buffer.byteLength !== buf.byteLength
}

function all (list) {
  let size = 0
  for (let i = 0; i < list.length; i++) {
    const buf = list[i]
    size += buf === null || buf.buffer.byteLength === buf.byteLength ? 0 : buf.byteLength
  }

  const copy = b4a.allocUnsafeSlow(size)
  const result = new Array(list.length)

  let offset = 0
  for (let i = 0; i < list.length; i++) {
    let buf = list[i]

    if (buf !== null && buf.buffer.byteLength !== buf.byteLength) {
      copy.set(buf, offset)
      buf = copy.subarray(offset, offset += buf.byteLength)
    }

    result[i] = buf
  }

  return result
}
{
  "name": "unslab",
  "version": "1.3.0",
  "description": "Unslab some slab'ed buffers",
  "main": "index.js",
  "files": [
    "index.js"
  ],
  "dependencies": {
    "b4a": "^1.6.6"
  },
  "devDependencies": {
    "brittle": "^3.5.2",
    "standard": "^17.1.0"
  },
  "scripts": {
    "test": "standard && brittle test.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/holepunchto/unslab.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/unslab/issues"
  },
  "homepage": "https://github.com/holepunchto/unslab"
}
const { runtime, platform, arch } = typeof Bare !== 'undefined'
  ? { runtime: 'bare', platform: global.Bare.platform, arch: global.Bare.arch }
  : typeof process !== 'undefined'
    ? { runtime: 'node', platform: global.process.platform, arch: global.process.arch }
    : typeof Window !== 'undefined'
      ? { runtime: 'browser', platform: 'unknown', arch: 'unknown' }
      : { runtime: 'unknown', platform: 'unknown', arch: 'unknown' }

exports.runtime = runtime
exports.platform = platform
exports.arch = arch
exports.isBare = runtime === 'bare'
exports.isBareKit = exports.isBare && typeof BareKit !== 'undefined'
exports.isPear = typeof Pear !== 'undefined'
exports.isNode = runtime === 'node'
exports.isBrowser = runtime === 'browser'
exports.isWindows = platform === 'win32'
exports.isLinux = platform === 'linux'
exports.isMac = platform === 'darwin'
exports.isIOS = platform === 'ios' || platform === 'ios-simulator'
exports.isAndroid = platform === 'android'
exports.isElectron = typeof process !== 'undefined' && !!global.process.versions?.electron
exports.isElectronRenderer = exports.isElectron && global.process.type === 'renderer'
exports.isElectronWorker = exports.isElectron && global.process.type === 'worker'
{
  "name": "which-runtime",
  "version": "1.3.2",
  "description": "Detect if you are in Bare or Node and which os etc",
  "main": "index.js",
  "files": [
    "index.js"
  ],
  "dependencies": {},
  "devDependencies": {
    "standard": "^17.0.0"
  },
  "scripts": {
    "test": "standard"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/holepunchto/which-runtime.git"
  },
  "author": "Holepunch Inc.",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/which-runtime/issues"
  },
  "homepage": "https://github.com/holepunchto/which-runtime"
}
module.exports = class MaxCache {
  constructor ({ maxSize, maxAge, createMap, ongc }) {
    this.maxSize = maxSize
    this.maxAge = maxAge
    this.ongc = ongc || null

    this._createMap = createMap || defaultCreateMap
    this._latest = this._createMap()
    this._oldest = this._createMap()
    this._retained = this._createMap()
    this._gced = false
    this._interval = null

    if (this.maxAge > 0 && this.maxAge < Infinity) {
      const tick = Math.ceil(2 / 3 * this.maxAge)
      this._interval = setInterval(this._gcAuto.bind(this), tick)
      if (this._interval.unref) this._interval.unref()
    }
  }

  * [Symbol.iterator] () {
    for (const it of [this._latest, this._oldest, this._retained]) {
      yield * it
    }
  }

  * keys () {
    for (const it of [this._latest, this._oldest, this._retained]) {
      yield * it.keys()
    }
  }

  * values () {
    for (const it of [this._latest, this._oldest, this._retained]) {
      yield * it.values()
    }
  }

  destroy () {
    this.clear()
    clearInterval(this._interval)
    this._interval = null
  }

  clear () {
    this._gced = true
    this._latest.clear()
    this._oldest.clear()
    this._retained.clear()
  }

  set (k, v) {
    if (this._retained.has(k)) return this
    this._latest.set(k, v)
    this._oldest.delete(k) || this._retained.delete(k)
    if (this._latest.size >= this.maxSize) this._gc()
    return this
  }

  retain (k, v) {
    this._retained.set(k, v)
    this._latest.delete(k) || this._oldest.delete(k)
    return this
  }

  delete (k) {
    return this._latest.delete(k) || this._oldest.delete(k) || this._retained.delete(k)
  }

  has (k) {
    return this._latest.has(k) || this._oldest.has(k) || this._retained.has(k)
  }

  get (k) {
    if (this._latest.has(k)) {
      return this._latest.get(k)
    }

    if (this._oldest.has(k)) {
      const v = this._oldest.get(k)
      this._latest.set(k, v)
      this._oldest.delete(k)
      return v
    }

    if (this._retained.has(k)) {
      return this._retained.get(k)
    }

    return null
  }

  _gcAuto () {
    if (!this._gced) this._gc()
    this._gced = false
  }

  _gc () {
    this._gced = true
    if (this.ongc !== null && this._oldest.size > 0) this.ongc(this._oldest)
    this._oldest = this._latest
    this._latest = this._createMap()
  }
}

function defaultCreateMap () {
  return new Map()
}
{
  "name": "xache",
  "version": "1.2.1",
  "description": "Yet another auto expiring, max sizable cache",
  "main": "index.js",
  "files": [
    "index.js"
  ],
  "devDependencies": {
    "brittle": "^3.3.2",
    "standard": "^17.1.0"
  },
  "scripts": {
    "test": "standard && brittle test.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/mafintosh/xache.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/mafintosh/xache/issues"
  },
  "homepage": "https://github.com/mafintosh/xache"
}
const b4a = require('b4a')

const ALPHABET = 'ybndrfg8ejkmcpqxot1uwisza345h769'
const MIN = 0x31 // 1
const MAX = 0x7a // z
const REVERSE = new Int8Array(1 + MAX - MIN)

REVERSE.fill(-1)

for (let i = 0; i < ALPHABET.length; i++) {
  const v = ALPHABET.charCodeAt(i) - MIN
  REVERSE[v] = i
}

exports.encode = encode
exports.decode = decode
exports.ALPHABET = ALPHABET

function decode (s, out) {
  let pb = 0
  let ps = 0

  const r = s.length & 7
  const q = (s.length - r) / 8

  if (!out) out = b4a.allocUnsafe(Math.ceil(s.length * 5 / 8))

  // 0 5 2 7 4 1 6 3 (+5 mod 8)
  for (let i = 0; i < q; i++) {
    const a = quintet(s, ps++)
    const b = quintet(s, ps++)
    const c = quintet(s, ps++)
    const d = quintet(s, ps++)
    const e = quintet(s, ps++)
    const f = quintet(s, ps++)
    const g = quintet(s, ps++)
    const h = quintet(s, ps++)

    out[pb++] = (a << 3) | (b >>> 2)
    out[pb++] = ((b & 0b11) << 6) | (c << 1) | (d >>> 4)
    out[pb++] = ((d & 0b1111) << 4) | (e >>> 1)
    out[pb++] = ((e & 0b1) << 7) | (f << 2) | (g >>> 3)
    out[pb++] = ((g & 0b111) << 5) | h
  }

  if (r === 0) return out.subarray(0, pb)

  const a = quintet(s, ps++)
  const b = quintet(s, ps++)

  out[pb++] = (a << 3) | (b >>> 2)

  if (r <= 2) return out.subarray(0, pb)

  const c = quintet(s, ps++)
  const d = quintet(s, ps++)

  out[pb++] = ((b & 0b11) << 6) | (c << 1) | (d >>> 4)

  if (r <= 4) return out.subarray(0, pb)

  const e = quintet(s, ps++)

  out[pb++] = ((d & 0b1111) << 4) | (e >>> 1)

  if (r <= 5) return out.subarray(0, pb)

  const f = quintet(s, ps++)
  const g = quintet(s, ps++)

  out[pb++] = ((e & 0b1) << 7) | (f << 2) | (g >>> 3)

  if (r <= 7) return out.subarray(0, pb)

  const h = quintet(s, ps++)

  out[pb++] = ((g & 0b111) << 5) | h

  return out.subarray(0, pb)
}

function encode (buf) {
  if (typeof buf === 'string') buf = b4a.from(buf)

  const max = buf.byteLength * 8

  let s = ''

  for (let p = 0; p < max; p += 5) {
    const i = p >>> 3
    const j = p & 7

    if (j <= 3) {
      s += ALPHABET[(buf[i] >>> (3 - j)) & 0b11111]
      continue
    }

    const of = j - 3
    const h = (buf[i] << of) & 0b11111
    const l = (i >= buf.byteLength ? 0 : buf[i + 1]) >>> (8 - of)

    s += ALPHABET[h | l]
  }

  return s
}

function quintet (s, i) {
  if (i > s.length) {
    return 0
  }

  const v = s.charCodeAt(i)

  if (v < MIN || v > MAX) {
    throw Error('Invalid character in base32 input: "' + s[i] + '" at position ' + i)
  }

  const bits = REVERSE[v - MIN]

  if (bits === -1) {
    throw Error('Invalid character in base32 input: "' + s[i] + '" at position ' + i)
  }

  return bits
}
{
  "name": "z32",
  "version": "1.1.0",
  "description": "Encode & decode z-base32",
  "main": "index.js",
  "dependencies": {
    "b4a": "^1.5.3"
  },
  "devDependencies": {
    "base-x": "^4.0.0",
    "base32": "0.0.7",
    "brittle": "^3.1.3",
    "nanobench": "^3.0.0",
    "rfc4648": "^1.5.2",
    "standard": "^17.0.0"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/mafintosh/z32.git"
  },
  "scripts": {
    "test": "standard && brittle test.js",
    "bench": "node benchmark.js"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/mafintosh/z32/issues"
  },
  "homepage": "https://github.com/mafintosh/z32"
}
{
  "name": "hyper-svc",
  "version": "0.1.9",
  "description": "Reusable per-app daemon library \u2014 UDS + HTTP for local RPC, Hyperswarm for peer mirroring",
  "type": "module",
  "main": "index.js",
  "scripts": {
    "test": "brittle test/*.test.js",
    "test:bare": "bare ./node_modules/.bin/brittle test/*.test.js"
  },
  "engines": {
    "node": ">=18",
    "bare": ">=1.0"
  },
  "imports": {
    "#node:fs": {
      "bare": "bare-fs",
      "default": "fs"
    },
    "#node:fs/promises": {
      "bare": "bare-fs/promises",
      "default": "fs/promises"
    },
    "#node:path": {
      "bare": "bare-path",
      "default": "path"
    },
    "#node:os": {
      "bare": "bare-os",
      "default": "os"
    },
    "#node:url": {
      "bare": "bare-url",
      "default": "url"
    },
    "#node:events": {
      "bare": "bare-events",
      "default": "events"
    },
    "#node:stream": {
      "bare": "bare-stream",
      "default": "stream"
    },
    "#node:net": {
      "bare": "bare-net",
      "default": "net"
    },
    "#node:http": {
      "bare": "bare-http1",
      "default": "http"
    },
    "#node:process": {
      "bare": "bare-process",
      "default": "process"
    }
  },
  "dependencies": {
    "b4a": "^1.6.0",
    "bare-events": "^2.0.0",
    "bare-fs": "^4.7.2",
    "bare-http1": "^4.0.0",
    "bare-net": "^1.0.0",
    "bare-os": "^3.0.0",
    "bare-path": "^3.0.0",
    "bare-process": "^4.0.0",
    "bare-stream": "^2.0.0",
    "bare-url": "^2.0.0",
    "compact-encoding": "^3.2.0",
    "hypercore-crypto": "^3.0.0",
    "hyperswarm": "^4.0.0",
    "protomux": "^3.0.0",
    "ready-resource": "^1.0.0",
    "bare-abstract-socket": "^1.0.0"
  },
  "devDependencies": {
    "@hyperswarm/testnet": "^3.0.0",
    "brittle": "^3.0.0",
    "corestore": "^7.0.0",
    "hyper-rooms": "file:../hyper-rooms"
  },
  "overrides": {}
}
const codecs = require('codecs')
const { Readable } = require('streamx')
const mutexify = require('mutexify/promise')
const b4a = require('b4a')
const safetyCatch = require('safety-catch')
const ReadyResource = require('ready-resource')
const debounce = require('debounceify')
const Rache = require('rache')
const rrp = require('resolve-reject-promise')

const { all: unslabAll } = require('unslab')

const RangeIterator = require('./iterators/range')
const HistoryIterator = require('./iterators/history')
const DiffIterator = require('./iterators/diff')
const LocalBlockIterator = require('./iterators/local')
const Extension = require('./lib/extension')
const { YoloIndex, Node, Header } = require('./lib/messages')
const { BLOCK_NOT_AVAILABLE, DECODING_ERROR } = require('hypercore-errors')

const T = 5
const MIN_KEYS = T - 1
const MAX_CHILDREN = MIN_KEYS * 2 + 1

const SEP = b4a.alloc(1)
const EMPTY = b4a.alloc(0)

class Key {
  constructor(seq, value) {
    this.seq = seq
    this.value = value
  }
}

class Child {
  constructor(seq, offset, value) {
    this.seq = seq
    this.offset = offset
    this.value = value
  }
}

class Cache {
  constructor(rache) {
    this.keys = rache
    this.length = 0
  }

  get(seq) {
    return this.keys.get(seq) || null
  }

  set(seq, key) {
    this.keys.set(seq, key)
    if (seq >= this.length) this.length = seq + 1
  }

  gc(length) {
    // if we need to "work" more than 128 ticks, just bust the cache...
    if (this.length - length > 128) {
      this.keys.clear()
    } else {
      for (let i = length; i < this.length; i++) {
        this.keys.delete(i)
      }
    }

    this.length = length
  }

  clear() {
    this.keys.clear()
  }
}

class Pointers {
  constructor(decoded) {
    this.levels = decoded.levels.map((l) => {
      const children = []
      const keys = []

      for (let i = 0; i < l.keys.length; i++) {
        keys.push(new Key(l.keys[i], null))
      }

      for (let i = 0; i < l.children.length; i += 2) {
        children.push(new Child(l.children[i], l.children[i + 1], null))
      }

      return { keys, children }
    })
  }

  get(i) {
    return this.levels[i]
  }

  hasKey(seq) {
    for (const lvl of this.levels) {
      for (const key of lvl.keys) {
        if (key.seq === seq) return true
      }
    }
    return false
  }
}

function inflate(entry) {
  if (entry.inflated === null) {
    entry.inflated = YoloIndex.decode(entry.index)
    entry.index = null
  }
  return new Pointers(entry.inflated)
}

function deflate(index) {
  const levels = index.map((l) => {
    const keys = []
    const children = []

    for (let i = 0; i < l.value.keys.length; i++) {
      keys.push(l.value.keys[i].seq)
    }

    for (let i = 0; i < l.value.children.length; i++) {
      children.push(l.value.children[i].seq, l.value.children[i].offset)
    }

    return { keys, children }
  })

  return YoloIndex.encode({ levels })
}

class TreeNode {
  constructor(block, keys, children, offset) {
    this.block = block
    this.offset = offset
    this.keys = keys
    this.children = children
    this.changed = false

    this.preload()
  }

  preload() {
    if (this.block === null) return

    const core = getBackingCore(this.block.tree.core)
    if (!core) return

    const bitfield = core.core.bitfield
    const blocks = []

    for (let i = 0; i < this.keys.length; i++) {
      const k = this.keys[i]
      if (k.value) continue
      if (k.seq >= core.signedLength || (bitfield && bitfield.get(k.seq))) continue
      blocks.push(k.seq)
    }
    for (let i = 0; i < this.children.length; i++) {
      const c = this.children[i]
      if (c.value) continue
      if (c.seq >= core.signedLength || (bitfield && bitfield.get(c.seq))) continue
      blocks.push(c.seq)
    }

    if (blocks.length) core.download({ blocks })
  }

  async insertKey(key, value, child, node, encoding, cas) {
    let s = 0
    let e = this.keys.length
    let c

    while (s < e) {
      const mid = (s + e) >> 1
      c = b4a.compare(key.value, await this.getKey(mid))

      if (c === 0) {
        if (cas) {
          const prev = await this.getKeyNode(mid)
          if (!(await cas(prev.final(encoding), node))) return true
        }
        if (!this.block.tree.tree.alwaysDuplicate) {
          const prev = await this.getKeyNode(mid)
          if (sameValue(prev.value, value)) return true
        }
        this.changed = true
        this.keys[mid] = key
        return true
      }

      if (c < 0) e = mid
      else s = mid + 1
    }

    const i = c < 0 ? e : s
    this.keys.splice(i, 0, key)
    if (child) this.children.splice(i + 1, 0, new Child(0, 0, child))
    this.changed = true

    return this.keys.length < MAX_CHILDREN
  }

  removeKey(index) {
    this.keys.splice(index, 1)
    if (this.children.length) {
      this.children[index + 1].seq = 0 // mark as freed
      this.children.splice(index + 1, 1)
    }
    this.changed = true
  }

  async siblings(parent) {
    for (let i = 0; i < parent.children.length; i++) {
      if (parent.children[i].value === this) {
        const [left, right] = await Promise.all([
          i ? parent.getChildNode(i - 1) : null,
          i < parent.children.length - 1 ? parent.getChildNode(i + 1) : null
        ])
        return { left, index: i, right }
      }
    }

    throw new Error('Bad parent')
  }

  merge(node, median) {
    this.changed = true
    this.keys.push(median)
    for (let i = 0; i < node.keys.length; i++) this.keys.push(node.keys[i])
    for (let i = 0; i < node.children.length; i++) this.children.push(node.children[i])
  }

  async split() {
    const len = this.keys.length >> 1
    const right = TreeNode.create(this.block)

    while (right.keys.length < len) right.keys.push(this.keys.pop())
    right.keys.reverse()

    await this.getKey(this.keys.length - 1) // make sure the median is loaded
    const median = this.keys.pop()

    if (this.children.length) {
      while (right.children.length < len + 1) right.children.push(this.children.pop())
      right.children.reverse()
    }

    this.changed = true

    return {
      left: this,
      median,
      right
    }
  }

  getKeyNode(index) {
    return this.block.tree.getBlock(this.keys[index].seq)
  }

  async getChildNode(index) {
    const child = this.children[index]
    if (child.value) return child.value
    const block =
      child.seq === this.block.seq ? this.block : await this.block.tree.getBlock(child.seq)
    return (child.value = block.getTreeNode(child.offset))
  }

  setKey(index, key) {
    this.keys[index] = key
    this.changed = true
  }

  async getKey(index) {
    const key = this.keys[index]
    if (key.value) return key.value
    const k = key.seq === this.block.seq ? this.block.key : await this.block.tree.getKey(key.seq)
    return (key.value = k)
  }

  indexChanges(index, seq) {
    const offset = index.push(null) - 1
    this.changed = false

    for (const child of this.children) {
      if (!child.value || !child.value.changed) continue
      child.seq = seq
      child.offset = child.value.indexChanges(index, seq)
      index[child.offset] = child
    }

    return offset
  }

  updateChildren(seq, block) {
    for (const child of this.children) {
      if (!child.value || child.seq !== seq) continue
      child.value.block = block
      child.value.updateChildren(seq, block)
    }
  }

  static create(block) {
    const node = new TreeNode(block, [], [], 0)
    node.changed = true
    return node
  }
}

class BlockEntry {
  constructor(seq, tree, entry) {
    this.seq = seq
    this.tree = tree
    this.index = null
    this.entry = entry
    this.key = entry.key
    this.value = entry.value
  }

  isTarget(key) {
    return b4a.equals(this.key, key)
  }

  inflate() {
    if (this.index === null) {
      this.index = inflate(this.entry)
    }
  }

  isDeletion() {
    if (this.value !== null) return false

    if (this.index === null) {
      this.index = inflate(this.entry)
    }

    return !this.index.hasKey(this.seq)
  }

  final(encoding) {
    return {
      seq: this.seq,
      key: encoding.key ? encoding.key.decode(this.key) : this.key,
      value: this.value && (encoding.value ? encoding.value.decode(this.value) : this.value)
    }
  }

  getTreeNode(offset) {
    if (this.index === null) {
      this.index = inflate(this.entry)
    }
    const entry = this.index.get(offset)
    return new TreeNode(this, entry.keys, entry.children, offset)
  }
}

class CacheLock {
  constructor() {
    this.map = new Map()
  }

  enter(seq) {
    const pending = this.map.get(seq)

    if (!pending) {
      this.map.set(seq, [])
      return Promise.resolve()
    }

    const { resolve, promise } = rrp()
    pending.push(resolve)
    return promise
  }

  exit(seq) {
    const pending = this.map.get(seq)
    if (!pending.length) {
      this.map.delete(seq)
      return
    }

    pending.pop()()
  }
}

class BatchEntry extends BlockEntry {
  constructor(seq, tree, key, value, index) {
    super(seq, tree, { key, value, index: null, inflated: null })
    this.pendingIndex = index
  }

  isTarget(key) {
    return false
  }

  getTreeNode(offset) {
    return this.pendingIndex[offset].value
  }
}

class Hyperbee extends ReadyResource {
  constructor(core, opts = {}) {
    super()
    // this.feed is now deprecated, and will be this.core going forward
    this.feed = core
    this.core = core

    this.keyEncoding = opts.keyEncoding ? codecs(opts.keyEncoding) : null
    this.valueEncoding = opts.valueEncoding ? codecs(opts.valueEncoding) : null
    this.extension = opts.extension !== false ? getExtension(this, opts) : null
    this.metadata = opts.metadata || null
    this.lock = opts.lock || mutexify()
    this.sep = opts.sep || SEP
    this.readonly = !!opts.readonly
    this.prefix = opts.prefix || null

    // In a future version, this should be false by default
    this.alwaysDuplicate = opts.alwaysDuplicate !== false

    this._unprefixedKeyEncoding = this.keyEncoding
    this._sub = !!this.prefix
    this._checkout = opts.checkout || 0
    this._view = !!opts._view

    this._onappendBound = this._view ? null : this._onappend.bind(this)
    this._ontruncateBound = this._view ? null : this._ontruncate.bind(this)
    this._watchers = this._onappendBound ? [] : null
    this._entryWatchers = this._onappendBound ? [] : null
    this._sessions = opts.sessions !== false

    this._keyCache = null
    this._nodeCache = null
    this._cacheLock = new CacheLock()

    this._batches = []

    if (this._watchers) {
      this.core.on('append', this._onappendBound)
      this.core.on('truncate', this._ontruncateBound)
    }

    if (this.prefix && opts._sub) {
      this.keyEncoding = prefixEncoding(this.prefix, this.keyEncoding)
    }

    this.ready().catch(safetyCatch)
  }

  async _open() {
    if (this.core.opened === false) await this.core.ready()
    // bump the internal ranges since the nodes are small
    if (this._watchers && this.core.replicator.setInflightRange) {
      this.core.replicator.setInflightRange(256, 512)
    }

    // snapshot
    if (this._checkout === -1) this._checkout = Math.max(1, this.core.length)

    const baseCache = Rache.from(this.core.globalCache)
    this._keyCache = new Cache(baseCache)
    this._nodeCache = new Cache(Rache.from(baseCache))
  }

  get version() {
    return Math.max(1, this._checkout || this.core.length)
  }

  get id() {
    return this.core.id
  }

  get key() {
    return this.core.key
  }

  get discoveryKey() {
    return this.core.discoveryKey
  }

  get writable() {
    return this.core.writable
  }

  get readable() {
    return this.core.readable
  }

  replicate(isInitiator, opts) {
    return this.core.replicate(isInitiator, opts)
  }

  update(opts) {
    return this.core.update(opts)
  }

  peek(range, opts) {
    return iteratorPeek(this.createRangeIterator(range, { ...opts, limit: 1 }))
  }

  createRangeIterator(range, opts = {}) {
    // backwards compat range arg
    opts = opts ? { ...opts, ...range } : range

    const extension = opts.extension === false && opts.limit !== 0 ? null : this.extension
    const keyEncoding = opts.keyEncoding ? codecs(opts.keyEncoding) : this.keyEncoding

    if (extension) {
      const { onseq, onwait } = opts
      let version = 0
      let next = 0

      opts = encRange(keyEncoding, {
        ...opts,
        sub: this._sub,
        onseq(seq) {
          if (!version) version = seq + 1
          if (next) next--
          if (onseq) onseq(seq)
        },
        onwait(seq) {
          if (!next) {
            next = Extension.BATCH_SIZE
            extension.iterator(ite.snapshot(version))
          }
          if (onwait) onwait(seq)
        }
      })
    } else {
      opts = encRange(keyEncoding, { ...opts, sub: this._sub })
    }

    const ite = new RangeIterator(
      new Batch(this, this._makeSnapshot(), null, false, opts),
      null,
      opts
    )
    return ite
  }

  createReadStream(range, opts) {
    const signal = (opts && opts.signal) || null
    return iteratorToStream(this.createRangeIterator(range, opts), signal)
  }

  createHistoryStream(opts) {
    const session = opts && opts.live ? this.core.session() : this._makeSnapshot()
    const signal = (opts && opts.signal) || null
    return iteratorToStream(
      new HistoryIterator(new Batch(this, session, null, false, opts), opts),
      signal
    )
  }

  createDiffStream(right, range, opts) {
    if (typeof right === 'number') right = this.checkout(Math.max(1, right), { reuseSession: true })

    // backwards compat range arg
    opts = opts ? { ...opts, ...range } : range

    const signal = (opts && opts.signal) || null

    const keyEncoding = opts && opts.keyEncoding ? codecs(opts.keyEncoding) : this.keyEncoding
    if (keyEncoding) opts = encRange(keyEncoding, { ...opts, sub: this._sub })

    let done
    let closing
    let ite

    const left = this

    const rs = new Readable({
      signal,
      eagerOpen: true,
      async open(cb) {
        try {
          if (right.opened === false) await right.ready()
          if (left.opened === false) await left.ready()
        } catch (err) {
          cb(err)
          return
        }

        if (closing) {
          cb(null)
          return
        }

        if (left.core.closing || right.core.closing) {
          cb(new Error('Bee closed'))
          return
        }

        const snapshot = right.version > left.version ? right._makeSnapshot() : left._makeSnapshot()

        done = cb
        ite = new DiffIterator(
          new Batch(left, snapshot, null, false, opts),
          new Batch(right, snapshot, null, false, opts),
          opts
        )
        ite.open().then(fin, fin)
      },
      read(cb) {
        done = cb
        ite.next().then(push, fin)
      },
      predestroy() {
        if (!ite) {
          closing = Promise.resolve()
        } else {
          closing = ite.close()
          closing.catch(noop)
        }
      },
      destroy(cb) {
        done = cb
        if (!closing) closing = ite.close()
        closing.then(fin, fin)
      }
    })

    return rs

    function fin(err) {
      done(err)
    }

    function push(val) {
      rs.push(val)
      done(null)
    }
  }

  get(key, opts) {
    const b = new Batch(this, this._makeSnapshot(), null, true, opts)
    return b.get(key)
  }

  getBySeq(seq, opts) {
    const b = new Batch(this, this._makeSnapshot(), null, true, opts)
    return b.getBySeq(seq)
  }

  put(key, value, opts) {
    const b = new Batch(this, this.core, null, true, opts)
    return b.put(key, value, opts)
  }

  batch(opts) {
    return new Batch(this, this.core, mutexify(), true, opts)
  }

  del(key, opts) {
    const b = new Batch(this, this.core, null, true, opts)
    return b.del(key, opts)
  }

  watch(range, opts) {
    if (!this._watchers) throw new Error('Can only watch the main bee instance')
    return new Watcher(this, range, opts)
  }

  async getAndWatch(key, opts) {
    if (!this._watchers) throw new Error('Can only watch the main bee instance')

    const watcher = new EntryWatcher(this, key, opts)
    await watcher._debouncedUpdate()

    if (this.closing) {
      await watcher.close()
      throw new Error('Bee closed')
    }

    return watcher
  }

  _onappend() {
    for (const watcher of this._watchers) {
      watcher._onappend()
    }

    for (const watcher of this._entryWatchers) {
      watcher._onappend()
    }
  }

  _ontruncate(length) {
    for (const watcher of this._watchers) {
      watcher._ontruncate()
    }

    for (const watcher of this._entryWatchers) {
      watcher._ontruncate()
    }

    this._nodeCache.gc(length)
    this._keyCache.gc(length)
  }

  _makeSnapshot() {
    if (this._sessions === false) return this.core
    // TODO: better if we could encapsulate this in hypercore in the future
    return this._checkout <= this.core.length || this._checkout <= 1
      ? this.core.snapshot()
      : this.core.session({ snapshot: false })
  }

  async clearUnlinked(options = {}) {
    await this.ready()

    const { gte = 0, lt = this.version - 1, batchSize = 4096, wait = true } = options
    const checkout = this.version

    let prev = this.batch({ wait: false, checkout: gte })
    let b = this.batch({ wait, checkout })

    const iteBatch = this.batch({ wait: false, checkout })
    const ite = new LocalBlockIterator(iteBatch, { gte, lt })
    await ite.open()

    let ticks = 0

    while (true) {
      const data = await ite.next()
      if (!data) break

      if (!(await isLinked(b, data))) {
        if (b.core.closing || this.core.closing || this.closing) break
        await this.core.clear(data.seq)
      }

      const prevNode = await prev.get(data.key, { finalize: false }).catch(toNull)

      if (prevNode && !(await isLinked(b, prevNode))) {
        if (b.core.closing || this.core.closing || this.closing) break
        await this.core.clear(prevNode.seq)
      }

      if (ticks++ >= batchSize) {
        ticks = 0
        await prev.close()
        await b.close()

        prev = this.batch({ wait: false, checkout: gte })
        b = this.batch({ wait, checkout })
      }
    }

    if (b.core.closing || this.core.closing || this.closing) throw new Error('Core is closed')

    await b.close()
    await prev.close()
    await ite.close()
    await iteBatch.close()

    return lt
  }

  checkout(version, opts = {}) {
    if (version === 0) version = 1

    // same as above, just checkout isn't set yet...
    const snap =
      opts.reuseSession || this._sessions === false
        ? this.core
        : version <= this.core.length || version <= 1
          ? this.core.snapshot()
          : this.core.session({ snapshot: false })

    return new Hyperbee(snap, {
      _view: true,
      _sub: false,
      prefix: this.prefix,
      sep: this.sep,
      lock: this.lock,
      checkout: version,
      keyEncoding: opts.keyEncoding || this.keyEncoding,
      valueEncoding: opts.valueEncoding || this.valueEncoding,
      extension: this.extension !== null ? this.extension : false
    })
  }

  snapshot(opts) {
    return this.checkout(
      this.core.opened === false || this._checkout <= 0 ? -1 : Math.max(1, this.version),
      opts
    )
  }

  sub(prefix, opts = {}) {
    let sep = opts.sep || this.sep
    if (!b4a.isBuffer(sep)) sep = b4a.from(sep)

    prefix = b4a.concat([this.prefix || EMPTY, b4a.from(prefix), sep])

    const valueEncoding = codecs(opts.valueEncoding || this.valueEncoding)
    const keyEncoding = codecs(opts.keyEncoding || this._unprefixedKeyEncoding)

    return new Hyperbee(this.core, {
      _view: true,
      _sub: true,
      prefix,
      sep: this.sep,
      lock: this.lock,
      checkout: this._checkout,
      valueEncoding,
      keyEncoding,
      extension: this.extension !== null ? this.extension : false,
      metadata: this.metadata
    })
  }

  async getHeader(opts) {
    const blk = await this.core.get(0, opts)
    try {
      return blk && Header.decode(blk)
    } catch {
      throw DECODING_ERROR()
    }
  }

  async _close() {
    if (!this._view) {
      if (this._keyCache) this._keyCache.clear()
      if (this._nodeCache) this._nodeCache.clear()
    }

    if (this._watchers) {
      this.core.off('append', this._onappendBound)
      this.core.off('truncate', this._ontruncateBound)

      while (this._watchers.length) {
        await this._watchers[this._watchers.length - 1].close()
      }
    }

    if (this._entryWatchers) {
      while (this._entryWatchers.length) {
        await this._entryWatchers[this._entryWatchers.length - 1].close()
      }
    }

    while (this._batches.length) {
      await this._batches[this._batches.length - 1].close()
    }

    return this.core.close()
  }

  static async isHyperbee(core, opts) {
    await core.ready()

    const blk0 = await core.get(0, opts)
    if (blk0 === null) throw BLOCK_NOT_AVAILABLE()

    try {
      return Header.decode(blk0).protocol === 'hyperbee'
    } catch (err) {
      // undecodable
      return false
    }
  }
}

class Batch {
  constructor(tree, core, batchLock, cache, options = {}) {
    this.tree = tree
    // this.feed is now deprecated, and will be this.core going forward
    this.feed = core
    this.core = core
    this.index = tree._batches.push(this) - 1
    this.blocks = cache ? new Map() : null
    this.autoFlush = !batchLock
    this.maxBlocksCached = options.maxBlocksCached || 128
    this.rootSeq = 0
    this.root = null
    this.length = 0
    this.checkout = options.checkout === undefined ? -1 : options.checkout
    this.options = options
    this.locked = null
    this.batchLock = batchLock
    this.onseq = this.options.onseq || noop
    this.appending = null
    this.isSnapshot = this.core !== this.tree.core
    this.shouldUpdate = this.options.update !== false
    this.updating = null
    this.encoding = {
      key: options.keyEncoding ? codecs(options.keyEncoding) : tree.keyEncoding,
      value: options.valueEncoding ? codecs(options.valueEncoding) : tree.valueEncoding
    }
  }

  async ready() {
    if (this.core.opened === false) await this.core.ready()
    if (this.tree.opened === false) await this.tree.ready()
  }

  async lock() {
    if (this.tree.readonly) throw new Error('Hyperbee is marked as read-only')
    if (this.locked === null) this.locked = await this.tree.lock()
  }

  get version() {
    if (this.checkout !== -1) return Math.max(1, this.checkout)
    return Math.max(1, this.tree._checkout || this.core.length + this.length)
  }

  async getRoot(ensureHeader) {
    await this.ready()
    if (ensureHeader) {
      if (this.core.length === 0 && this.core.writable && !this.tree.readonly) {
        await this.core.append(
          Header.encode({
            protocol: 'hyperbee',
            metadata: this.tree.metadata
          })
        )
      }
    }
    if (this.tree._checkout === 0 && this.checkout === -1 && this.shouldUpdate) {
      if (this.updating === null) this.updating = this.core.update()
      await this.updating
    }
    if (this.version < 2) return null
    return (await this.getBlock(this.version - 1)).getTreeNode(0)
  }

  async getKey(seq) {
    await this.tree._cacheLock.enter(seq)

    try {
      const k = this.core.fork === this.tree.core.fork ? this.tree._keyCache.get(seq) : null
      if (k !== null) return k
      const key = (await this._getBlock(seq)).key
      if (this.core.fork === this.tree.core.fork) this.tree._keyCache.set(seq, key)
      return key
    } finally {
      this.tree._cacheLock.exit(seq)
    }
  }

  async _getNode(seq) {
    const cached =
      this.tree._nodeCache !== null && this.core.fork === this.tree.core.fork
        ? this.tree._nodeCache.get(seq)
        : null
    if (cached !== null) return cached
    const entry = await this.core.get(seq, {
      ...this.options,
      valueEncoding: Node
    })
    if (entry === null) throw BLOCK_NOT_AVAILABLE()
    const wrap = copyEntry(entry)
    if (this.core.fork === this.tree.core.fork && this.tree._nodeCache !== null) {
      this.tree._nodeCache.set(seq, wrap)
    }
    return wrap
  }

  async getBlock(seq) {
    if (this.rootSeq === 0) this.rootSeq = seq
    await this.tree._cacheLock.enter(seq)

    try {
      return await this._getBlock(seq)
    } finally {
      this.tree._cacheLock.exit(seq)
    }
  }

  async _getBlock(seq) {
    let b = this.blocks && this.blocks.get(seq)
    if (b) return b
    this.onseq(seq)
    const entry = await this._getNode(seq)
    b = this.blocks && this.blocks.get(seq)
    if (b) return b
    b = new BlockEntry(seq, this, entry)
    if (this.blocks && this.blocks.size - this.length < this.maxBlocksCached) {
      this.blocks.set(seq, b)
    }
    return b
  }

  _onwait(key) {
    this.options.onwait = null
    this.tree.extension.get(this.rootSeq + 1, key)
  }

  _getEncoding(opts) {
    if (!opts) return this.encoding
    return {
      key: opts.keyEncoding ? codecs(opts.keyEncoding) : this.encoding.key,
      value: opts.valueEncoding ? codecs(opts.valueEncoding) : this.encoding.value
    }
  }

  peek(range, opts) {
    return iteratorPeek(this.createRangeIterator(range, { ...opts, limit: 1 }))
  }

  createRangeIterator(range, opts = {}) {
    // backwards compat range arg
    opts = opts ? { ...opts, ...range } : range

    const encoding = this._getEncoding(opts)
    return new RangeIterator(
      this,
      encoding,
      encRange(encoding.key, { ...opts, sub: this.tree._sub })
    )
  }

  createReadStream(range, opts) {
    const signal = (opts && opts.signal) || null
    return iteratorToStream(this.createRangeIterator(range, opts), signal)
  }

  async getBySeq(seq, opts) {
    const encoding = this._getEncoding(opts)

    try {
      const block = (await this.getBlock(seq)).final(encoding)
      return { key: block.key, value: block.value }
    } finally {
      await this._closeSnapshot()
    }
  }

  async get(key, opts) {
    const encoding = this._getEncoding(opts)
    const finalize = opts ? opts.finalize !== false : true

    try {
      return await this._get(key, encoding, finalize)
    } finally {
      await this._closeSnapshot()
    }
  }

  async _get(key, encoding, finalize) {
    key = enc(encoding.key, key)

    if (this.tree.extension !== null && this.options.extension !== false) {
      this.options.onwait = this._onwait.bind(this, key)
    }

    let node = await this.getRoot(false)
    if (!node) return null

    while (true) {
      if (node.block.isTarget(key)) {
        return node.block.isDeletion() ? null : finalize ? node.block.final(encoding) : node.block
      }

      let s = 0
      let e = node.keys.length
      let c

      while (s < e) {
        const mid = (s + e) >> 1

        c = b4a.compare(key, await node.getKey(mid))

        if (c === 0) {
          const block = await this.getBlock(node.keys[mid].seq)
          return finalize ? block.final(encoding) : block
        }

        if (c < 0) e = mid
        else s = mid + 1
      }

      if (!node.children.length) return null

      const i = c < 0 ? e : s
      node = await node.getChildNode(i)
    }
  }

  async links(key, seq) {
    let node = await this.getRoot(false)
    if (!node) return false

    if (node.block.seq === seq) return true

    while (true) {
      if (node.block.isTarget(key)) return false

      let s = 0
      let e = node.keys.length
      let c

      while (s < e) {
        const mid = (s + e) >> 1

        if (node.keys[mid].seq === seq) return true
        c = b4a.compare(key, await node.getKey(mid))

        if (c === 0) return false

        if (c < 0) e = mid
        else s = mid + 1
      }

      if (!node.children.length) return false

      const i = c < 0 ? e : s
      node = await node.getChildNode(i)
      if (node.block.seq === seq) return true
    }
  }

  async put(key, value, opts) {
    const release = this.batchLock ? await this.batchLock() : null

    const cas = (opts && opts.cas) || null
    const encoding = this._getEncoding(opts)

    if (!this.locked) await this.lock()
    if (!release) return this._put(key, value, encoding, cas)

    try {
      return await this._put(key, value, encoding, cas)
    } finally {
      release()
    }
  }

  async _put(key, value, encoding, cas) {
    const newNode = {
      seq: 0,
      key,
      value
    }
    key = enc(encoding.key, key)
    value = enc(encoding.value, value)

    const stack = []

    let root
    let node = (root = await this.getRoot(true))
    if (!node) node = root = TreeNode.create(null)

    const seq = (newNode.seq = this.core.length + this.length)
    const target = new Key(seq, key)

    while (node.children.length) {
      stack.push(node)
      node.changed = true // changed, but compressible

      let s = 0
      let e = node.keys.length
      let c

      while (s < e) {
        const mid = (s + e) >> 1
        c = b4a.compare(target.value, await node.getKey(mid))

        if (c === 0) {
          if (cas) {
            const prev = await node.getKeyNode(mid)
            if (!(await cas(prev.final(encoding), newNode))) return this._unlockMaybe()
          }
          if (!this.tree.alwaysDuplicate) {
            const prev = await node.getKeyNode(mid)
            if (sameValue(prev.value, value)) return this._unlockMaybe()
          }
          node.setKey(mid, target)
          return this._append(root, seq, key, value)
        }

        if (c < 0) e = mid
        else s = mid + 1
      }

      const i = c < 0 ? e : s
      node = await node.getChildNode(i)
    }

    let needsSplit = !(await node.insertKey(target, value, null, newNode, encoding, cas))
    if (!node.changed) return this._unlockMaybe()

    while (needsSplit) {
      const parent = stack.pop()
      const { median, right } = await node.split()

      if (parent) {
        needsSplit = !(await parent.insertKey(median, value, right, null, encoding, null))
        node = parent
      } else {
        root = TreeNode.create(node.block)
        root.changed = true
        root.keys.push(median)
        root.children.push(new Child(0, 0, node), new Child(0, 0, right))
        needsSplit = false
      }
    }

    return this._append(root, seq, key, value)
  }

  async del(key, opts) {
    const release = this.batchLock ? await this.batchLock() : null
    const cas = (opts && opts.cas) || null
    const encoding = this._getEncoding(opts)

    if (!this.locked) await this.lock()
    if (!release) return this._del(key, encoding, cas)

    try {
      return await this._del(key, encoding, cas)
    } finally {
      release()
    }
  }

  async _del(key, encoding, cas) {
    const delNode = {
      seq: 0,
      key,
      value: null
    }

    key = enc(encoding.key, key)

    const stack = []

    let node = await this.getRoot(true)
    if (!node) return this._unlockMaybe()

    const seq = (delNode.seq = this.core.length + this.length)

    while (true) {
      stack.push(node)

      let s = 0
      let e = node.keys.length
      let c

      while (s < e) {
        const mid = (s + e) >> 1
        c = b4a.compare(key, await node.getKey(mid))

        if (c === 0) {
          if (cas) {
            const prev = await node.getKeyNode(mid)
            if (!(await cas(prev.final(encoding), delNode))) return this._unlockMaybe()
          }
          if (node.children.length) await setKeyToNearestLeaf(node, mid, stack)
          else node.removeKey(mid)
          // we mark these as changed late, so we don't rewrite them if it is a 404
          for (const node of stack) node.changed = true
          return this._append(await rebalance(stack), seq, key, null)
        }

        if (c < 0) e = mid
        else s = mid + 1
      }

      if (!node.children.length) return this._unlockMaybe()

      const i = c < 0 ? e : s
      node = await node.getChildNode(i)
    }
  }

  async _closeSnapshot() {
    if (this.isSnapshot) {
      await this.core.close()
      this._finalize()
    }
  }

  async close() {
    if (this.isSnapshot) return this._closeSnapshot()

    this.root = null
    if (this.blocks) this.blocks.clear()
    this.length = 0
    this._unlock()
  }

  destroy() {
    // compat, remove later
    this.close().catch(noop)
  }

  toBlocks() {
    if (this.appending) return this.appending

    const batch = new Array(this.length)

    for (let i = 0; i < this.length; i++) {
      const seq = this.core.length + i
      const { pendingIndex, key, value } = this.blocks.get(seq)

      if (i < this.length - 1) {
        pendingIndex[0] = null
        let j = 0

        while (j < pendingIndex.length) {
          const idx = pendingIndex[j]
          if (idx !== null && idx.seq === seq) {
            idx.offset = j++
            continue
          }
          if (j === pendingIndex.length - 1) pendingIndex.pop()
          else pendingIndex[j] = pendingIndex.pop()
        }
      }

      batch[i] = Node.encode({
        key,
        value,
        index: deflate(pendingIndex)
      })
    }

    this.appending = batch
    return batch
  }

  flush() {
    if (!this.length) return this.close()

    const batch = this.toBlocks()

    this.root = null
    this.blocks.clear()
    this.length = 0

    return this._appendBatch(batch)
  }

  _unlockMaybe() {
    if (this.autoFlush) this._unlock()
  }

  _unlock() {
    const locked = this.locked
    this.locked = null
    if (locked !== null) locked()
    this._finalize()
  }

  _finalize() {
    // technically finalize can be called more than once, so here we just check if we already have been removed
    if (this.index >= this.tree._batches.length || this.tree._batches[this.index] !== this) return
    const top = this.tree._batches.pop()
    if (top === this) return
    top.index = this.index
    this.tree._batches[top.index] = top
  }

  _append(root, seq, key, value) {
    const index = []
    root.indexChanges(index, seq)
    index[0] = new Child(seq, 0, root)

    if (!this.autoFlush) {
      const block = new BatchEntry(seq, this, key, value, index)
      root.block = block
      this.root = root
      this.length++
      this.blocks.set(seq, block)

      root.updateChildren(seq, block)
      return
    }

    return this._appendBatch(
      Node.encode({
        key,
        value,
        index: deflate(index)
      })
    )
  }

  async _appendBatch(raw) {
    try {
      await this.core.append(raw)
    } finally {
      this._unlock()
    }
  }
}

class EntryWatcher extends ReadyResource {
  constructor(bee, key, opts = {}) {
    super()

    this.keyEncoding = opts.keyEncoding || bee.keyEncoding
    this.valueEncoding = opts.valueEncoding || bee.valueEncoding

    this.index = bee._entryWatchers.push(this) - 1
    this.bee = bee

    this.key = key
    this.node = null

    this._forceUpdate = false
    this._debouncedUpdate = debounce(this._processUpdate.bind(this))
  }

  _close() {
    const top = this.bee._entryWatchers.pop()
    if (top !== this) {
      top.index = this.index
      this.bee._entryWatchers[top.index] = top
    }
  }

  _onappend() {
    this._debouncedUpdate()
  }

  _ontruncate() {
    this._forceUpdate = true
    this._debouncedUpdate()
  }

  async _processUpdate() {
    const force = this._forceUpdate
    this._forceUpdate = false

    let newNode
    try {
      newNode = await this.bee.get(this.key, {
        keyEncoding: this.keyEncoding,
        valueEncoding: this.valueEncoding
      })
    } catch (e) {
      if (e.code === 'SNAPSHOT_NOT_AVAILABLE') {
        // There was a truncate event before the get resolved
        // So this handler will run again anyway
        return
      } else if (this.bee.closing) {
        this.close().catch(safetyCatch)
        return
      }
      this.emit('error', e)
      return
    }

    if (force || newNode?.seq !== this.node?.seq) {
      this.node = newNode
      this.emit('update')
    }
  }
}

class Watcher extends ReadyResource {
  constructor(bee, range, opts = {}) {
    super()

    this.keyEncoding = opts.keyEncoding || bee.keyEncoding
    this.valueEncoding = opts.valueEncoding || bee.valueEncoding
    this.index = bee._watchers.push(this) - 1
    this.bee = bee
    this.core = bee.core

    this.latestDiff = 0
    this.range = range
    this.map = opts.map || defaultWatchMap

    this.current = null
    this.previous = null
    this.currentMapped = null
    this.previousMapped = null
    this.stream = null

    this._lock = mutexify()
    this._flowing = false
    this._resolveOnChange = null
    this._differ = opts.differ || defaultDiffer
    this._eager = !!opts.eager
    this._onchange = opts.onchange || null

    this.on('newListener', autoFlowOnUpdate)

    this.ready().catch(safetyCatch)
  }

  async _consume() {
    if (this._flowing) return
    try {
      for await (const _ of this) {
      } // eslint-disable-line
    } catch {}
  }

  async _open() {
    await this.bee.ready()

    const opts = {
      keyEncoding: this.keyEncoding,
      valueEncoding: this.valueEncoding
    }

    // Point from which to start watching
    this.current = this._eager ? this.bee.checkout(1, opts) : this.bee.snapshot(opts)
    await this.current.ready()

    if (this._onchange) {
      if (this._eager) await this._onchange()
      this._consume()
    }
  }

  [Symbol.asyncIterator]() {
    this._flowing = true
    return this
  }

  _ontruncate() {
    this._onappend()
  }

  _onappend() {
    const resolve = this._resolveOnChange
    this._resolveOnChange = null
    if (resolve) resolve()
  }

  async _waitForChanges() {
    if (this.current.version < this.bee.version || this.closing) return

    await new Promise((resolve) => {
      this._resolveOnChange = resolve
    })
  }

  async next() {
    try {
      return await this._next()
    } catch (err) {
      if (this.closing) return { value: undefined, done: true }
      await this.close()
      throw err
    }
  }

  async _next() {
    const release = await this._lock()

    try {
      if (this.closing) return { value: undefined, done: true }

      if (!this.opened) await this.ready()

      while (true) {
        await this._waitForChanges()

        if (this.closing) return { value: undefined, done: true }

        await this._closePrevious()
        this.previous = this.current.snapshot()

        await this._closeCurrent()
        this.current = this.bee.snapshot({
          keyEncoding: this.keyEncoding,
          valueEncoding: this.valueEncoding
        })

        await this.current.ready()
        await this.previous.ready()

        if (this.current.core.fork !== this.previous.core.fork) {
          return await this._yield()
        }

        this.stream = this._differ(this.current, this.previous, this.range)

        try {
          for await (const data of this.stream) {
            // eslint-disable-line
            return await this._yield()
          }
        } finally {
          this.stream = null
        }
      }
    } finally {
      release()
    }
  }

  async _yield() {
    this.currentMapped = this.map(this.current)
    this.previousMapped = this.map(this.previous)

    if (this._onchange) {
      try {
        await this._onchange()
      } catch (err) {
        safetyCatch(err)
      }
    }

    this.emit('update')
    return { done: false, value: [this.currentMapped, this.previousMapped] }
  }

  async return() {
    await this.close()
    return { done: true }
  }

  async _close() {
    const top = this.bee._watchers.pop()
    if (top !== this) {
      top.index = this.index
      this.bee._watchers[top.index] = top
    }

    if (this.stream && !this.stream.destroying) {
      this.stream.destroy()
    }

    this._onappend() // Continue execution being closed

    await this._closeCurrent().catch(safetyCatch)
    await this._closePrevious().catch(safetyCatch)

    const release = await this._lock()
    release()
  }

  destroy() {
    return this.close()
  }

  async _closeCurrent() {
    if (this.currentMapped) await this.currentMapped.close()
    if (this.current) await this.current.close()
    this.current = this.currentMapped = null
  }

  async _closePrevious() {
    if (this.previousMapped) await this.previousMapped.close()
    if (this.previous) await this.previous.close()
    this.previous = this.previousMapped = null
  }
}

function autoFlowOnUpdate(name) {
  if (name === 'update') this._consume()
}

function defaultWatchMap(snapshot) {
  return snapshot
}

async function leafSize(node, goLeft) {
  while (node.children.length) node = await node.getChildNode(goLeft ? 0 : node.children.length - 1)
  return node.keys.length
}

async function setKeyToNearestLeaf(node, index, stack) {
  let [left, right] = await Promise.all([node.getChildNode(index), node.getChildNode(index + 1)])
  const [ls, rs] = await Promise.all([leafSize(left, false), leafSize(right, true)])

  if (ls < rs) {
    // if fewer leaves on the left
    stack.push(right)
    while (right.children.length) stack.push((right = right.children[0].value))
    node.keys[index] = right.keys.shift()
  } else {
    // if fewer leaves on the right
    stack.push(left)
    while (left.children.length) stack.push((left = left.children[left.children.length - 1].value))
    node.keys[index] = left.keys.pop()
  }
}

async function rebalance(stack) {
  const root = stack[0]

  while (stack.length > 1) {
    const node = stack.pop()
    const parent = stack[stack.length - 1]

    if (node.keys.length >= MIN_KEYS) return root

    let { left, index, right } = await node.siblings(parent)

    // maybe borrow from left sibling?
    if (left && left.keys.length > MIN_KEYS) {
      left.changed = true
      node.keys.unshift(parent.keys[index - 1])
      if (left.children.length) node.children.unshift(left.children.pop())
      parent.keys[index - 1] = left.keys.pop()
      return root
    }

    // maybe borrow from right sibling?
    if (right && right.keys.length > MIN_KEYS) {
      right.changed = true
      node.keys.push(parent.keys[index])
      if (right.children.length) node.children.push(right.children.shift())
      parent.keys[index] = right.keys.shift()
      return root
    }

    // merge node with another sibling
    if (left) {
      index--
      right = node
    } else {
      left = node
    }

    left.merge(right, parent.keys[index])
    parent.removeKey(index)
  }

  // check if the tree shrunk
  if (!root.keys.length && root.children.length) return root.getChildNode(0)
  return root
}

function iteratorToStream(ite, signal) {
  let done
  let closing

  const rs = new Readable({
    signal,
    open(cb) {
      done = cb
      ite.open().then(fin, fin)
    },
    read(cb) {
      done = cb
      ite.next().then(push, fin)
    },
    predestroy() {
      closing = ite.close()
      closing.catch(noop)
    },
    destroy(cb) {
      done = cb
      if (!closing) closing = ite.close()
      closing.then(fin, fin)
    }
  })

  return rs

  function fin(err) {
    done(err)
  }

  function push(val) {
    rs.push(val)
    done(null)
  }
}

async function iteratorPeek(ite) {
  try {
    await ite.open()
    return await ite.next()
  } finally {
    await ite.close()
  }
}

function encRange(e, opts) {
  if (!e) return opts

  if (e.encodeRange) {
    const r = e.encodeRange({
      gt: opts.gt,
      gte: opts.gte,
      lt: opts.lt,
      lte: opts.lte
    })
    opts.gt = r.gt
    opts.gte = r.gte
    opts.lt = r.lt
    opts.lte = r.lte
    return opts
  }

  if (opts.gt !== undefined) opts.gt = enc(e, opts.gt)
  if (opts.gte !== undefined) opts.gte = enc(e, opts.gte)
  if (opts.lt !== undefined) opts.lt = enc(e, opts.lt)
  if (opts.lte !== undefined) opts.lte = enc(e, opts.lte)
  if (opts.sub && !opts.gt && !opts.gte) opts.gt = enc(e, SEP)
  if (opts.sub && !opts.lt && !opts.lte) opts.lt = bump(enc(e, EMPTY))

  return opts
}

function bump(key) {
  // key should have been copied by enc above before hitting this
  key[key.length - 1]++
  return key
}

function enc(e, v) {
  if (v === undefined || v === null) return null
  if (e !== null) return e.encode(v)
  if (typeof v === 'string') return b4a.from(v)
  return v
}

function prefixEncoding(prefix, keyEncoding) {
  return {
    encode(key) {
      return b4a.concat([prefix, b4a.isBuffer(key) ? key : enc(keyEncoding, key)])
    },
    decode(key) {
      const sliced = key.slice(prefix.length, key.length)
      return keyEncoding ? keyEncoding.decode(sliced) : sliced
    }
  }
}

function copyEntry(entry) {
  let key = entry.key
  let value = entry.value
  let index = entry.index

  // key, value and index all refer to the same buffer (one hypercore block)
  // If together they are larger than half the buffer's byteLength,
  // this means that they got their own private slab (see Buffer.allocUnsafe docs)
  // so no need to unslab
  const size =
    key.byteLength +
    (value === null ? 0 : value.byteLength) +
    (index === null ? 0 : index.byteLength)
  if (2 * size < key.buffer.byteLength) {
    const [newKey, newValue, newIndex] = unslabAll([entry.key, entry.value, entry.index])
    key = newKey
    value = newValue
    index = newIndex
  }

  return {
    key,
    value,
    index,
    inflated: null
  }
}

function defaultDiffer(currentSnap, previousSnap, opts) {
  return currentSnap.createDiffStream(previousSnap, opts)
}

function toNull() {
  return null
}

function getBackingCore(core) {
  if (core.core) return core
  if (core.getBackingCore) return core.getBackingCore().session
  return null
}

function sameValue(a, b) {
  return a === b || (a !== null && b !== null && b4a.equals(a, b))
}

function noop() {}

function getExtension(db, opts) {
  if (opts.extension === false) return null
  if (opts.extension && opts.extension !== true) return opts.extension
  return Extension.register(db)
}

async function isLinked(batch, block) {
  const seq = block.seq
  block.inflate()

  const keys = [block.key]
  const wait = batch.options.wait

  for (const l of block.index.levels) {
    if (!l.keys.length) continue
    batch.options.wait = false
    try {
      keys.push(await batch.getKey(l.keys[0].seq))
    } catch {}
    batch.options.wait = wait
  }

  for (const k of keys) {
    if (await batch.links(k, seq)) return true
  }

  if (batch.core.closing) throw new Error('Core is closed')

  return false
}

module.exports = Hyperbee
const b4a = require('b4a')

class SubTree {
  constructor(node, parent) {
    this.node = node
    this.parent = parent

    this.isKey = node.children.length === 0
    this.i = this.isKey ? 1 : 0
    this.n = 0

    const child = this.isKey ? null : this.node.children[0]
    this.seq = child !== null ? child.seq : this.node.keys[0].seq
    this.offset = child !== null ? child.offset : 0
  }

  next() {
    this.i++
    this.isKey = (this.i & 1) === 1
    if (!this.isKey && !this.node.children.length) this.i++
    return this.update()
  }

  async bisect(key, incl) {
    let s = 0
    let e = this.node.keys.length
    let c

    while (s < e) {
      const mid = (s + e) >> 1
      c = cmp(key, await this.node.getKey(mid))

      if (c === 0) {
        if (incl) this.i = mid * 2 + 1
        else this.i = mid * 2 + (this.node.children.length ? 2 : 3)
        return true
      }

      if (c < 0) e = mid
      else s = mid + 1
    }

    const i = c < 0 ? e : s
    this.i = 2 * i + (this.node.children.length ? 0 : 1)
    return this.node.children.length === 0
  }

  update() {
    this.isKey = (this.i & 1) === 1
    this.n = this.i >> 1
    if (this.n >= (this.isKey ? this.node.keys.length : this.node.children.length)) return false
    const child = this.isKey ? null : this.node.children[this.n]
    this.seq = child !== null ? child.seq : this.node.keys[this.n].seq
    this.offset = child !== null ? child.offset : 0
    return true
  }

  async key() {
    return this.n < this.node.keys.length
      ? this.node.getKey(this.n)
      : this.parent && this.parent.key()
  }

  async compare(tree) {
    const [a, b] = await Promise.all([this.key(), tree.key()])
    return cmp(a, b)
  }
}

class TreeIterator {
  constructor(batch, opts) {
    this.batch = batch
    this.stack = []
    this.lt = opts.lt || opts.lte || null
    this.lte = !!opts.lte
    this.gt = opts.gt || opts.gte || null
    this.gte = !!opts.gte
    this.seeking = !!this.gt
    this.encoding = opts.encoding || batch.encoding
  }

  async open() {
    const node = await this.batch.getRoot(false)
    if (!node || !node.keys.length) return
    const tree = new SubTree(node, null)
    if (this.seeking && !(await this._seek(tree))) return
    this.stack.push(tree)
  }

  async _seek(tree) {
    const done = await tree.bisect(this.gt, this.gte)
    const oob = !tree.update()
    if (done || oob) {
      this.seeking = false
      if (oob) return false
    }
    return true
  }

  peek() {
    if (!this.stack.length) return null
    return this.stack[this.stack.length - 1]
  }

  skip() {
    if (!this.stack.length) return
    if (!this.stack[this.stack.length - 1].next()) this.stack.pop()
  }

  async nextKey() {
    let n = null
    while (this.stack.length && n === null) n = await this.next()
    if (n === null) return null
    if (!this.lt) return n.final(this.encoding)

    const c = cmp(n.key, this.lt)
    if (this.lte ? c <= 0 : c < 0) return n.final(this.encoding)
    this.stack = []
    return null
  }

  async next() {
    if (!this.stack.length) return null

    const top = this.stack[this.stack.length - 1]
    const { isKey, n, seq } = top

    if (!top.next()) {
      this.stack.pop()
    }

    if (isKey) {
      this.seeking = false
      return this.batch.getBlock(seq)
    }

    const child = await top.node.getChildNode(n)
    top.node.children[n] = null // unlink to save memory
    const tree = new SubTree(child, top)
    if (this.seeking && !(await this._seek(tree))) return null
    this.stack.push(tree)

    return null
  }

  close() {
    return this.batch._closeSnapshot()
  }
}

module.exports = class DiffIterator {
  constructor(left, right, opts = {}) {
    this.left = new TreeIterator(left, opts)
    this.right = new TreeIterator(right, opts)
    this.limit = typeof opts.limit === 'number' ? opts.limit : -1
  }

  async open() {
    await Promise.all([this.left.open(), this.right.open()])
  }

  async next() {
    if (this.limit === 0) return null
    const res = await this._next()
    if (!res || (res.left === null && res.right === null)) return null
    this.limit--
    return res
  }

  async _next() {
    const a = this.left
    const b = this.right

    while (true) {
      const [l, r] = await Promise.all([a.peek(), b.peek()])

      if (!l && !r) return null
      if (!l) return { left: null, right: await b.nextKey() }
      if (!r) return { left: await a.nextKey(), right: null }

      if (l.seq === r.seq && l.isKey === r.isKey && l.offset === r.offset) {
        a.skip()
        b.skip()
        continue
      }

      const c = await l.compare(r)

      if (l.isKey && !r.isKey) {
        await b.next()
        continue
      }

      if (!l.isKey && r.isKey) {
        await a.next()
        continue
      }

      if (l.isKey && r.isKey) {
        if (c === 0) return { left: await a.nextKey(), right: await b.nextKey() }
        if (c < 0) return { left: await a.nextKey(), right: null }
        return { left: null, right: await b.nextKey() }
      }

      if (c === 0) await Promise.all([a.next(), b.next()])
      else if (c < 0) await b.next()
      else await a.next()
    }
  }

  async close() {
    await Promise.all([this.left.close(), this.right.close()])
  }
}

function cmp(a, b) {
  if (!a) return b ? 1 : 0
  if (!b) return a ? -1 : 0
  return b4a.compare(a, b)
}
module.exports = class HistoryIterator {
  constructor(batch, opts = {}) {
    this.batch = batch
    this.options = opts
    this.live = !!opts.live
    this.gte = 0
    this.lt = 0
    this.reverse = !!opts.reverse
    this.limit = typeof opts.limit === 'number' ? opts.limit : -1
    this.encoding = opts.encoding || batch.encoding
    if (this.live && this.reverse) {
      throw new Error('Cannot have both live and reverse enabled')
    }
  }

  async open() {
    await this.batch.getRoot(false) // does the update dance
    this.gte = gte(this.options, this.batch.version)
    this.lt = this.live ? Infinity : lt(this.options, this.batch.version)
  }

  async next() {
    if (this.limit === 0) return null
    if (this.limit > 0) this.limit--

    if (this.gte >= this.lt) return null

    if (this.reverse) {
      if (this.lt <= 1) return null
      return final(await this.batch.getBlock(--this.lt), this.encoding)
    }

    return final(await this.batch.getBlock(this.gte++), this.encoding)
  }

  close() {
    return this.batch._closeSnapshot()
  }
}

function final(node, encoding) {
  const type = node.isDeletion() ? 'del' : 'put'
  return { type, ...node.final(encoding) }
}

function gte(opts, version) {
  if (opts.gt) return (opts.gt < 0 ? opts.gt + version : opts.gt) + 1
  const gte = opts.gte || opts.since || 1
  return gte < 0 ? gte + version : gte
}

function lt(opts, version) {
  if (opts.lte === 0 || opts.lt === 0 || opts.end === 0) return 0
  if (opts.lte) return (opts.lte < 0 ? opts.lte + version : opts.lte) + 1
  const lt = opts.lt || opts.end || version
  return lt < 0 ? lt + version : lt
}
module.exports = class LocalBlocksIterator {
  constructor(batch, opts = {}) {
    this.batch = batch
    this.options = opts
    this.gte = 0
    this.lt = 0
    this.limit = typeof opts.limit === 'number' ? opts.limit : -1
  }

  async open() {
    await this.batch.getRoot(false) // does the update dance
    this.gte = gte(this.options, this.batch.version)
    this.lt = lt(this.options, this.batch.version)
  }

  async next() {
    if (this.limit === 0) return null
    if (this.limit > 0) this.limit--

    while (this.gte < this.lt) {
      try {
        return await this.batch.getBlock(this.gte++)
      } catch {
        continue
      }
    }

    return null
  }

  close() {
    return this.batch._closeSnapshot()
  }
}

function gte(opts, version) {
  if (opts.gt) return (opts.gt < 0 ? opts.gt + version : opts.gt) + 1
  const gte = opts.gte || opts.since || 1
  return gte < 0 ? gte + version : gte
}

function lt(opts, version) {
  if (opts.lte === 0 || opts.lt === 0 || opts.end === 0) return 0
  if (opts.lte) return (opts.lte < 0 ? opts.lte + version : opts.lte) + 1
  const lt = opts.lt || opts.end || version
  return lt < 0 ? lt + version : lt
}
const b4a = require('b4a')

module.exports = class RangeIterator {
  constructor(batch, encoding, opts = {}) {
    this.batch = batch
    this.stack = []
    this.opened = false
    this.encoding = encoding || batch.encoding

    this._limit = typeof opts.limit === 'number' ? opts.limit : -1
    this._gIncl = !opts.gt
    this._gKey = opts.gt || opts.gte || null
    this._lIncl = !opts.lt
    this._lKey = opts.lt || opts.lte || null
    this._reverse = !!opts.reverse
    this._version = 0
    this._checkpoint = opts.checkpoint && opts.checkpoint.length ? opts.checkpoint : null
    this._nexting = false
    this._closed = false
  }

  snapshot(version = this.batch.version) {
    const checkpoint = []
    for (const s of this.stack) {
      let { node, i } = s
      if (this._nexting && s === this.stack[this.stack.length - 1]) {
        i = this._reverse ? i + 1 : i - 1
      }
      if (!node.block) continue
      if (i < 0) continue
      checkpoint.push(node.block.seq, node.offset, i)
    }

    return {
      version,
      gte: this._gIncl ? this._gKey : null,
      gt: this._gIncl ? null : this._gKey,
      lte: this._lIncl ? this._lKey : null,
      lt: this._lIncl ? null : this._lKey,
      limit: this._limit,
      reverse: this._reverse,
      ended: this.opened && !checkpoint.length,
      checkpoint: this.opened ? checkpoint : []
    }
  }

  async open() {
    await this._open()
    this.opened = true
  }

  async _open() {
    if (this._checkpoint) {
      for (let j = 0; j < this._checkpoint.length; j += 3) {
        const seq = this._checkpoint[j]
        const offset = this._checkpoint[j + 1]
        const i = this._checkpoint[j + 2]
        this.stack.push({
          node: (await this.batch.getBlock(seq)).getTreeNode(offset),
          i
        })
      }
      return
    }

    this._nexting = true

    let node = await this.batch.getRoot(false)
    if (!node) {
      this._nexting = false
      return
    }

    const incl = this._reverse ? this._lIncl : this._gIncl
    const start = this._reverse ? this._lKey : this._gKey

    if (!start) {
      this.stack.push({ node, i: this._reverse ? node.keys.length << 1 : 0 })
      this._nexting = false
      this._preloadQuery()
      return
    }

    while (true) {
      const entry = { node, i: this._reverse ? node.keys.length << 1 : 0 }

      let s = 0
      let e = node.keys.length
      let c

      while (s < e) {
        const mid = (s + e) >> 1
        c = b4a.compare(start, await node.getKey(mid))

        if (c === 0) {
          if (incl) entry.i = mid * 2 + 1
          else entry.i = mid * 2 + (this._reverse ? 0 : 2)
          this.stack.push(entry)
          this._nexting = false
          this._preloadQuery()
          return
        }

        if (c < 0) e = mid
        else s = mid + 1
      }

      const i = c < 0 ? e : s
      entry.i = 2 * i + (this._reverse ? -1 : 1)

      if (entry.i >= 0 && entry.i <= node.keys.length << 1) this.stack.push(entry)
      if (!node.children.length) {
        this._nexting = false
        this._preloadQuery()
        return
      }

      node = await node.getChildNode(i)
    }
  }

  _preloadQuery() {
    const max = { nodes: 0, max: 2048 }

    if (this._limit === -1 && !this._reverse && this.stack.length) {
      for (const s of this.stack) {
        const k = (s.i - (s.i & 1)) / 2
        this._preload(s.node, k + 1, this._lKey, max).catch(noop)
      }
    }
  }

  async _preload(node, i, end, max) {
    for (; i < node.keys.length; i++) {
      const key = node.keys[i]
      const block = await this.batch.getBlock(key.seq)
      if (this._closed) return
      const c = end ? b4a.compare(block.key, end) : -1
      if (c >= 0 || max.nodes >= max.max || i >= node.children.length) return
      max.nodes++
      const next = await node.getChildNode(i)
      if (this._closed) return
      this._preload(next, 0, end, max).catch(noop)
    }

    if (node.children.length && max.nodes < max.max) {
      const next = await node.getChildNode(node.children.length - 1)
      if (this._closed) return
      this._preload(next, 0, end, max).catch(noop)
    }
  }

  async next() {
    // TODO: this nexting flag is only needed if someone asks for a snapshot during
    // a lookup (ie the extension, pretty important...).
    // A better solution would be to refactor this so top.i is incremented eagerly
    // to get the current block instead of the way it is done now (++i vs i++)
    this._nexting = true

    const end = this._reverse ? this._gKey : this._lKey
    const incl = this._reverse ? this._gIncl : this._lIncl

    while (this.stack.length && (this._limit === -1 || this._limit > 0)) {
      const top = this.stack[this.stack.length - 1]
      const isKey = (top.i & 1) === 1
      const n = this._reverse ? (top.i < 0 ? top.node.keys.length : top.i-- >> 1) : top.i++ >> 1

      if (!isKey) {
        if (!top.node.children.length) continue
        const node = await top.node.getChildNode(n)
        if (top.node.block.seq < this.batch.core.length) {
          top.node.children[n].value = null // unlink it to save memory
        }
        this.stack.push({ i: this._reverse ? node.keys.length << 1 : 0, node })
        continue
      }

      if (n >= top.node.keys.length) {
        this.stack.pop()
        continue
      }

      const key = top.node.keys[n]
      const block = await this.batch.getBlock(key.seq)
      if (end) {
        const c = b4a.compare(block.key, end)
        if (c === 0 ? !incl : this._reverse ? c < 0 : c > 0) {
          this._limit = 0
          break
        }
      }
      if (this._limit > 0) this._limit--
      this._nexting = false
      return block.final(this.encoding)
    }

    this._nexting = false
    return null
  }

  close() {
    this._closed = true
    return this.batch._closeSnapshot()
  }
}

function noop() {}
const { Extension } = require('./messages')

// const MAX_ACTIVE = 32
const FLUSH_BATCH = 128
const MAX_PASSIVE_BATCH = 2048
const MAX_ACTIVE_BATCH = MAX_PASSIVE_BATCH + FLUSH_BATCH

class Batch {
  constructor(outgoing, from) {
    this.blocks = []
    this.start = 0
    this.end = 0
    this.outgoing = outgoing
    this.from = from
  }

  push(seq) {
    const len = this.blocks.push(seq)
    if (len === 1 || seq < this.start) this.start = seq
    if (len === 1 || seq >= this.end) this.end = seq + 1
    if (len >= FLUSH_BATCH) {
      this.send()
      this.clear()
    }
  }

  send() {
    if (!this.blocks.length) return
    this.outgoing.send(
      Extension.encode({
        cache: { blocks: this.blocks, start: this.start, end: this.end }
      }),
      this.from
    )
  }

  clear() {
    this.start = this.end = 0
    this.blocks = []
  }
}

class HyperbeeExtension {
  constructor(db) {
    this.encoding = null
    this.outgoing = null
    this.db = db
    this.active = 0
  }

  get(version, key) {
    this.outgoing.broadcast(Extension.encode({ get: { version, key } }))
  }

  iterator(snapshot) {
    if (snapshot.ended) return
    if (snapshot.limit === 0) return
    if (snapshot.limit === -1) snapshot.limit = 0
    this.outgoing.broadcast(Extension.encode({ iterator: snapshot }))
  }

  onmessage(buf, from) {
    // TODO: handle max active extension messages
    // this.active++

    const message = decode(buf)
    if (!message) return

    if (message.cache) this.oncache(message.cache, from)
    if (message.get) this.onget(message.get, from)
    if (message.iterator) this.oniterator(message.iterator, from)
  }

  oncache(message, from) {
    if (!message.blocks.length) return
    this.db.core.download(message)
  }

  onget(message, from) {
    if (!message.version || message.version > this.db.version) return

    const b = new Batch(this.outgoing, from)
    const db = this.db.checkout(message.version)

    db.get(message.key, {
      extension: false,
      wait: false,
      update: false,
      onseq
    }).then(done, done)

    function done() {
      db.close().catch(noop)
      b.send()
    }

    function onseq(seq) {
      b.push(seq)
    }
  }

  async oniterator(message, from) {
    if (!message.version || message.version > this.db.version) return

    const b = new Batch(this.outgoing, from)
    const seqs = new Set()

    let skip = message.checkpoint.length
    let work = 0

    const db = this.db.checkout(message.version)
    const ite = db.createRangeIterator({
      ...message,
      wait: false,
      extension: false,
      update: false,
      limit: message.limit === 0 ? -1 : message.limit,
      onseq(seq) {
        if (skip && skip--) return
        if (seqs.has(seq)) return
        work++
        seqs.add(seq)
        b.push(seq)
      }
    })

    try {
      await ite.open()
      // eslint-disable-next-line no-unmodified-loop-condition
      while (work < MAX_ACTIVE_BATCH) {
        if (!(await ite.next())) break
      }
    } catch (_) {
      // do nothing
    } finally {
      ite.close().catch(noop)
      db.close().catch(noop)
      b.send()
    }
  }

  static register(db) {
    const e = new this(db)
    e.outgoing = db.core.registerExtension('hyperbee', e)
    return e
  }
}

HyperbeeExtension.BATCH_SIZE = MAX_PASSIVE_BATCH

module.exports = HyperbeeExtension

function decode(buf) {
  try {
    return Extension.decode(buf)
  } catch (err) {
    return null
  }
}

function noop() {}
// This file is auto generated by the protocol-buffers compiler

/* eslint-disable quotes */
/* eslint-disable indent */
/* eslint-disable no-redeclare */
/* eslint-disable camelcase */
/* eslint-disable no-var */

// Remember to `npm install --save protocol-buffers-encodings`
var encodings = require('protocol-buffers-encodings')
var b4a = require('b4a')
var varint = encodings.varint
var skip = encodings.skip

var YoloIndex = exports.YoloIndex = {
  buffer: true,
  encodingLength: null,
  encode: null,
  decode: null
}

var Header = exports.Header = {
  buffer: true,
  encodingLength: null,
  encode: null,
  decode: null
}

var Node = exports.Node = {
  buffer: true,
  encodingLength: null,
  encode: null,
  decode: null
}

var Extension = exports.Extension = {
  buffer: true,
  encodingLength: null,
  encode: null,
  decode: null
}

defineYoloIndex()
defineHeader()
defineNode()
defineExtension()

function defineYoloIndex () {
  var Level = YoloIndex.Level = {
    buffer: true,
    encodingLength: null,
    encode: null,
    decode: null
  }

  defineLevel()

  function defineLevel () {
    Level.encodingLength = encodingLength
    Level.encode = encode
    Level.decode = decode

    function encodingLength (obj) {
      var length = 0
      if (defined(obj.keys)) {
        var packedLen = 0
        for (var i = 0; i < obj.keys.length; i++) {
          if (!defined(obj.keys[i])) continue
          var len = encodings.varint.encodingLength(obj.keys[i])
          packedLen += len
        }
        if (packedLen) {
          length += 1 + packedLen + varint.encodingLength(packedLen)
        }
      }
      if (defined(obj.children)) {
        var packedLen = 0
        for (var i = 0; i < obj.children.length; i++) {
          if (!defined(obj.children[i])) continue
          var len = encodings.varint.encodingLength(obj.children[i])
          packedLen += len
        }
        if (packedLen) {
          length += 1 + packedLen + varint.encodingLength(packedLen)
        }
      }
      return length
    }

    function encode (obj, buf, offset) {
      if (!offset) offset = 0
      if (!buf) buf = b4a.allocUnsafe(encodingLength(obj))
      var oldOffset = offset
      if (defined(obj.keys)) {
        var packedLen = 0
        for (var i = 0; i < obj.keys.length; i++) {
          if (!defined(obj.keys[i])) continue
          packedLen += encodings.varint.encodingLength(obj.keys[i])
        }
        if (packedLen) {
          buf[offset++] = 10
          varint.encode(packedLen, buf, offset)
          offset += varint.encode.bytes
        }
        for (var i = 0; i < obj.keys.length; i++) {
          if (!defined(obj.keys[i])) continue
          encodings.varint.encode(obj.keys[i], buf, offset)
          offset += encodings.varint.encode.bytes
        }
      }
      if (defined(obj.children)) {
        var packedLen = 0
        for (var i = 0; i < obj.children.length; i++) {
          if (!defined(obj.children[i])) continue
          packedLen += encodings.varint.encodingLength(obj.children[i])
        }
        if (packedLen) {
          buf[offset++] = 18
          varint.encode(packedLen, buf, offset)
          offset += varint.encode.bytes
        }
        for (var i = 0; i < obj.children.length; i++) {
          if (!defined(obj.children[i])) continue
          encodings.varint.encode(obj.children[i], buf, offset)
          offset += encodings.varint.encode.bytes
        }
      }
      encode.bytes = offset - oldOffset
      return buf
    }

    function decode (buf, offset, end) {
      if (!offset) offset = 0
      if (!end) end = buf.length
      if (!(end <= buf.length && offset <= buf.length)) throw new Error("Decoded message is not valid")
      var oldOffset = offset
      var obj = {
        keys: [],
        children: []
      }
      while (true) {
        if (end <= offset) {
          decode.bytes = offset - oldOffset
          return obj
        }
        var prefix = varint.decode(buf, offset)
        offset += varint.decode.bytes
        var tag = prefix >> 3
        switch (tag) {
          case 1:
          var packedEnd = varint.decode(buf, offset)
          offset += varint.decode.bytes
          packedEnd += offset
          while (offset < packedEnd) {
            obj.keys.push(encodings.varint.decode(buf, offset))
            offset += encodings.varint.decode.bytes
          }
          break
          case 2:
          var packedEnd = varint.decode(buf, offset)
          offset += varint.decode.bytes
          packedEnd += offset
          while (offset < packedEnd) {
            obj.children.push(encodings.varint.decode(buf, offset))
            offset += encodings.varint.decode.bytes
          }
          break
          default:
          offset = skip(prefix & 7, buf, offset)
        }
      }
    }
  }

  YoloIndex.encodingLength = encodingLength
  YoloIndex.encode = encode
  YoloIndex.decode = decode

  function encodingLength (obj) {
    var length = 0
    if (defined(obj.levels)) {
      for (var i = 0; i < obj.levels.length; i++) {
        if (!defined(obj.levels[i])) continue
        var len = Level.encodingLength(obj.levels[i])
        length += varint.encodingLength(len)
        length += 1 + len
      }
    }
    return length
  }

  function encode (obj, buf, offset) {
    if (!offset) offset = 0
    if (!buf) buf = b4a.allocUnsafe(encodingLength(obj))
    var oldOffset = offset
    if (defined(obj.levels)) {
      for (var i = 0; i < obj.levels.length; i++) {
        if (!defined(obj.levels[i])) continue
        buf[offset++] = 10
        varint.encode(Level.encodingLength(obj.levels[i]), buf, offset)
        offset += varint.encode.bytes
        Level.encode(obj.levels[i], buf, offset)
        offset += Level.encode.bytes
      }
    }
    encode.bytes = offset - oldOffset
    return buf
  }

  function decode (buf, offset, end) {
    if (!offset) offset = 0
    if (!end) end = buf.length
    if (!(end <= buf.length && offset <= buf.length)) throw new Error("Decoded message is not valid")
    var oldOffset = offset
    var obj = {
      levels: []
    }
    while (true) {
      if (end <= offset) {
        decode.bytes = offset - oldOffset
        return obj
      }
      var prefix = varint.decode(buf, offset)
      offset += varint.decode.bytes
      var tag = prefix >> 3
      switch (tag) {
        case 1:
        var len = varint.decode(buf, offset)
        offset += varint.decode.bytes
        obj.levels.push(Level.decode(buf, offset, offset + len))
        offset += Level.decode.bytes
        break
        default:
        offset = skip(prefix & 7, buf, offset)
      }
    }
  }
}

function defineHeader () {
  var Metadata = Header.Metadata = {
    buffer: true,
    encodingLength: null,
    encode: null,
    decode: null
  }

  defineMetadata()

  function defineMetadata () {
    Metadata.encodingLength = encodingLength
    Metadata.encode = encode
    Metadata.decode = decode

    function encodingLength (obj) {
      var length = 0
      if (defined(obj.contentFeed)) {
        var len = encodings.bytes.encodingLength(obj.contentFeed)
        length += 1 + len
      }
      if (defined(obj.userData)) {
        var len = encodings.bytes.encodingLength(obj.userData)
        length += 1 + len
      }
      return length
    }

    function encode (obj, buf, offset) {
      if (!offset) offset = 0
      if (!buf) buf = b4a.allocUnsafe(encodingLength(obj))
      var oldOffset = offset
      if (defined(obj.contentFeed)) {
        buf[offset++] = 10
        encodings.bytes.encode(obj.contentFeed, buf, offset)
        offset += encodings.bytes.encode.bytes
      }
      if (defined(obj.userData)) {
        buf[offset++] = 18
        encodings.bytes.encode(obj.userData, buf, offset)
        offset += encodings.bytes.encode.bytes
      }
      encode.bytes = offset - oldOffset
      return buf
    }

    function decode (buf, offset, end) {
      if (!offset) offset = 0
      if (!end) end = buf.length
      if (!(end <= buf.length && offset <= buf.length)) throw new Error("Decoded message is not valid")
      var oldOffset = offset
      var obj = {
        contentFeed: null,
        userData: null
      }
      while (true) {
        if (end <= offset) {
          decode.bytes = offset - oldOffset
          return obj
        }
        var prefix = varint.decode(buf, offset)
        offset += varint.decode.bytes
        var tag = prefix >> 3
        switch (tag) {
          case 1:
          obj.contentFeed = encodings.bytes.decode(buf, offset)
          offset += encodings.bytes.decode.bytes
          break
          case 2:
          obj.userData = encodings.bytes.decode(buf, offset)
          offset += encodings.bytes.decode.bytes
          break
          default:
          offset = skip(prefix & 7, buf, offset)
        }
      }
    }
  }

  Header.encodingLength = encodingLength
  Header.encode = encode
  Header.decode = decode

  function encodingLength (obj) {
    var length = 0
    if (!defined(obj.protocol)) throw new Error("protocol is required")
    var len = encodings.string.encodingLength(obj.protocol)
    length += 1 + len
    if (defined(obj.metadata)) {
      var len = Metadata.encodingLength(obj.metadata)
      length += varint.encodingLength(len)
      length += 1 + len
    }
    return length
  }

  function encode (obj, buf, offset) {
    if (!offset) offset = 0
    if (!buf) buf = b4a.allocUnsafe(encodingLength(obj))
    var oldOffset = offset
    if (!defined(obj.protocol)) throw new Error("protocol is required")
    buf[offset++] = 10
    encodings.string.encode(obj.protocol, buf, offset)
    offset += encodings.string.encode.bytes
    if (defined(obj.metadata)) {
      buf[offset++] = 18
      varint.encode(Metadata.encodingLength(obj.metadata), buf, offset)
      offset += varint.encode.bytes
      Metadata.encode(obj.metadata, buf, offset)
      offset += Metadata.encode.bytes
    }
    encode.bytes = offset - oldOffset
    return buf
  }

  function decode (buf, offset, end) {
    if (!offset) offset = 0
    if (!end) end = buf.length
    if (!(end <= buf.length && offset <= buf.length)) throw new Error("Decoded message is not valid")
    var oldOffset = offset
    var obj = {
      protocol: "",
      metadata: null
    }
    var found0 = false
    while (true) {
      if (end <= offset) {
        if (!found0) throw new Error("Decoded message is not valid")
        decode.bytes = offset - oldOffset
        return obj
      }
      var prefix = varint.decode(buf, offset)
      offset += varint.decode.bytes
      var tag = prefix >> 3
      switch (tag) {
        case 1:
        obj.protocol = encodings.string.decode(buf, offset)
        offset += encodings.string.decode.bytes
        found0 = true
        break
        case 2:
        var len = varint.decode(buf, offset)
        offset += varint.decode.bytes
        obj.metadata = Metadata.decode(buf, offset, offset + len)
        offset += Metadata.decode.bytes
        break
        default:
        offset = skip(prefix & 7, buf, offset)
      }
    }
  }
}

function defineNode () {
  Node.encodingLength = encodingLength
  Node.encode = encode
  Node.decode = decode

  function encodingLength (obj) {
    var length = 0
    if (!defined(obj.index)) throw new Error("index is required")
    var len = encodings.bytes.encodingLength(obj.index)
    length += 1 + len
    if (!defined(obj.key)) throw new Error("key is required")
    var len = encodings.bytes.encodingLength(obj.key)
    length += 1 + len
    if (defined(obj.value)) {
      var len = encodings.bytes.encodingLength(obj.value)
      length += 1 + len
    }
    return length
  }

  function encode (obj, buf, offset) {
    if (!offset) offset = 0
    if (!buf) buf = b4a.allocUnsafe(encodingLength(obj))
    var oldOffset = offset
    if (!defined(obj.index)) throw new Error("index is required")
    buf[offset++] = 10
    encodings.bytes.encode(obj.index, buf, offset)
    offset += encodings.bytes.encode.bytes
    if (!defined(obj.key)) throw new Error("key is required")
    buf[offset++] = 18
    encodings.bytes.encode(obj.key, buf, offset)
    offset += encodings.bytes.encode.bytes
    if (defined(obj.value)) {
      buf[offset++] = 26
      encodings.bytes.encode(obj.value, buf, offset)
      offset += encodings.bytes.encode.bytes
    }
    encode.bytes = offset - oldOffset
    return buf
  }

  function decode (buf, offset, end) {
    if (!offset) offset = 0
    if (!end) end = buf.length
    if (!(end <= buf.length && offset <= buf.length)) throw new Error("Decoded message is not valid")
    var oldOffset = offset
    var obj = {
      index: null,
      key: null,
      value: null
    }
    var found0 = false
    var found1 = false
    while (true) {
      if (end <= offset) {
        if (!found0 || !found1) throw new Error("Decoded message is not valid")
        decode.bytes = offset - oldOffset
        return obj
      }
      var prefix = varint.decode(buf, offset)
      offset += varint.decode.bytes
      var tag = prefix >> 3
      switch (tag) {
        case 1:
        obj.index = encodings.bytes.decode(buf, offset)
        offset += encodings.bytes.decode.bytes
        found0 = true
        break
        case 2:
        obj.key = encodings.bytes.decode(buf, offset)
        offset += encodings.bytes.decode.bytes
        found1 = true
        break
        case 3:
        obj.value = encodings.bytes.decode(buf, offset)
        offset += encodings.bytes.decode.bytes
        break
        default:
        offset = skip(prefix & 7, buf, offset)
      }
    }
  }
}

function defineExtension () {
  var Get = Extension.Get = {
    buffer: true,
    encodingLength: null,
    encode: null,
    decode: null
  }

  var Iterator = Extension.Iterator = {
    buffer: true,
    encodingLength: null,
    encode: null,
    decode: null
  }

  var Cache = Extension.Cache = {
    buffer: true,
    encodingLength: null,
    encode: null,
    decode: null
  }

  defineGet()
  defineIterator()
  defineCache()

  function defineGet () {
    Get.encodingLength = encodingLength
    Get.encode = encode
    Get.decode = decode

    function encodingLength (obj) {
      var length = 0
      if (defined(obj.version)) {
        var len = encodings.varint.encodingLength(obj.version)
        length += 1 + len
      }
      if (defined(obj.key)) {
        var len = encodings.bytes.encodingLength(obj.key)
        length += 1 + len
      }
      return length
    }

    function encode (obj, buf, offset) {
      if (!offset) offset = 0
      if (!buf) buf = b4a.allocUnsafe(encodingLength(obj))
      var oldOffset = offset
      if (defined(obj.version)) {
        buf[offset++] = 8
        encodings.varint.encode(obj.version, buf, offset)
        offset += encodings.varint.encode.bytes
      }
      if (defined(obj.key)) {
        buf[offset++] = 18
        encodings.bytes.encode(obj.key, buf, offset)
        offset += encodings.bytes.encode.bytes
      }
      encode.bytes = offset - oldOffset
      return buf
    }

    function decode (buf, offset, end) {
      if (!offset) offset = 0
      if (!end) end = buf.length
      if (!(end <= buf.length && offset <= buf.length)) throw new Error("Decoded message is not valid")
      var oldOffset = offset
      var obj = {
        version: 0,
        key: null
      }
      while (true) {
        if (end <= offset) {
          decode.bytes = offset - oldOffset
          return obj
        }
        var prefix = varint.decode(buf, offset)
        offset += varint.decode.bytes
        var tag = prefix >> 3
        switch (tag) {
          case 1:
          obj.version = encodings.varint.decode(buf, offset)
          offset += encodings.varint.decode.bytes
          break
          case 2:
          obj.key = encodings.bytes.decode(buf, offset)
          offset += encodings.bytes.decode.bytes
          break
          default:
          offset = skip(prefix & 7, buf, offset)
        }
      }
    }
  }

  function defineIterator () {
    Iterator.encodingLength = encodingLength
    Iterator.encode = encode
    Iterator.decode = decode

    function encodingLength (obj) {
      var length = 0
      if (defined(obj.version)) {
        var len = encodings.varint.encodingLength(obj.version)
        length += 1 + len
      }
      if (defined(obj.gte)) {
        var len = encodings.bytes.encodingLength(obj.gte)
        length += 1 + len
      }
      if (defined(obj.gt)) {
        var len = encodings.bytes.encodingLength(obj.gt)
        length += 1 + len
      }
      if (defined(obj.lte)) {
        var len = encodings.bytes.encodingLength(obj.lte)
        length += 1 + len
      }
      if (defined(obj.lt)) {
        var len = encodings.bytes.encodingLength(obj.lt)
        length += 1 + len
      }
      if (defined(obj.limit)) {
        var len = encodings.varint.encodingLength(obj.limit)
        length += 1 + len
      }
      if (defined(obj.reverse)) {
        var len = encodings.bool.encodingLength(obj.reverse)
        length += 1 + len
      }
      if (defined(obj.checkpoint)) {
        var packedLen = 0
        for (var i = 0; i < obj.checkpoint.length; i++) {
          if (!defined(obj.checkpoint[i])) continue
          var len = encodings.varint.encodingLength(obj.checkpoint[i])
          packedLen += len
        }
        if (packedLen) {
          length += 1 + packedLen + varint.encodingLength(packedLen)
        }
      }
      return length
    }

    function encode (obj, buf, offset) {
      if (!offset) offset = 0
      if (!buf) buf = b4a.allocUnsafe(encodingLength(obj))
      var oldOffset = offset
      if (defined(obj.version)) {
        buf[offset++] = 8
        encodings.varint.encode(obj.version, buf, offset)
        offset += encodings.varint.encode.bytes
      }
      if (defined(obj.gte)) {
        buf[offset++] = 18
        encodings.bytes.encode(obj.gte, buf, offset)
        offset += encodings.bytes.encode.bytes
      }
      if (defined(obj.gt)) {
        buf[offset++] = 26
        encodings.bytes.encode(obj.gt, buf, offset)
        offset += encodings.bytes.encode.bytes
      }
      if (defined(obj.lte)) {
        buf[offset++] = 34
        encodings.bytes.encode(obj.lte, buf, offset)
        offset += encodings.bytes.encode.bytes
      }
      if (defined(obj.lt)) {
        buf[offset++] = 42
        encodings.bytes.encode(obj.lt, buf, offset)
        offset += encodings.bytes.encode.bytes
      }
      if (defined(obj.limit)) {
        buf[offset++] = 48
        encodings.varint.encode(obj.limit, buf, offset)
        offset += encodings.varint.encode.bytes
      }
      if (defined(obj.reverse)) {
        buf[offset++] = 56
        encodings.bool.encode(obj.reverse, buf, offset)
        offset += encodings.bool.encode.bytes
      }
      if (defined(obj.checkpoint)) {
        var packedLen = 0
        for (var i = 0; i < obj.checkpoint.length; i++) {
          if (!defined(obj.checkpoint[i])) continue
          packedLen += encodings.varint.encodingLength(obj.checkpoint[i])
        }
        if (packedLen) {
          buf[offset++] = 66
          varint.encode(packedLen, buf, offset)
          offset += varint.encode.bytes
        }
        for (var i = 0; i < obj.checkpoint.length; i++) {
          if (!defined(obj.checkpoint[i])) continue
          encodings.varint.encode(obj.checkpoint[i], buf, offset)
          offset += encodings.varint.encode.bytes
        }
      }
      encode.bytes = offset - oldOffset
      return buf
    }

    function decode (buf, offset, end) {
      if (!offset) offset = 0
      if (!end) end = buf.length
      if (!(end <= buf.length && offset <= buf.length)) throw new Error("Decoded message is not valid")
      var oldOffset = offset
      var obj = {
        version: 0,
        gte: null,
        gt: null,
        lte: null,
        lt: null,
        limit: 0,
        reverse: false,
        checkpoint: []
      }
      while (true) {
        if (end <= offset) {
          decode.bytes = offset - oldOffset
          return obj
        }
        var prefix = varint.decode(buf, offset)
        offset += varint.decode.bytes
        var tag = prefix >> 3
        switch (tag) {
          case 1:
          obj.version = encodings.varint.decode(buf, offset)
          offset += encodings.varint.decode.bytes
          break
          case 2:
          obj.gte = encodings.bytes.decode(buf, offset)
          offset += encodings.bytes.decode.bytes
          break
          case 3:
          obj.gt = encodings.bytes.decode(buf, offset)
          offset += encodings.bytes.decode.bytes
          break
          case 4:
          obj.lte = encodings.bytes.decode(buf, offset)
          offset += encodings.bytes.decode.bytes
          break
          case 5:
          obj.lt = encodings.bytes.decode(buf, offset)
          offset += encodings.bytes.decode.bytes
          break
          case 6:
          obj.limit = encodings.varint.decode(buf, offset)
          offset += encodings.varint.decode.bytes
          break
          case 7:
          obj.reverse = encodings.bool.decode(buf, offset)
          offset += encodings.bool.decode.bytes
          break
          case 8:
          var packedEnd = varint.decode(buf, offset)
          offset += varint.decode.bytes
          packedEnd += offset
          while (offset < packedEnd) {
            obj.checkpoint.push(encodings.varint.decode(buf, offset))
            offset += encodings.varint.decode.bytes
          }
          break
          default:
          offset = skip(prefix & 7, buf, offset)
        }
      }
    }
  }

  function defineCache () {
    Cache.encodingLength = encodingLength
    Cache.encode = encode
    Cache.decode = decode

    function encodingLength (obj) {
      var length = 0
      if (!defined(obj.start)) throw new Error("start is required")
      var len = encodings.varint.encodingLength(obj.start)
      length += 1 + len
      if (!defined(obj.end)) throw new Error("end is required")
      var len = encodings.varint.encodingLength(obj.end)
      length += 1 + len
      if (defined(obj.blocks)) {
        var packedLen = 0
        for (var i = 0; i < obj.blocks.length; i++) {
          if (!defined(obj.blocks[i])) continue
          var len = encodings.varint.encodingLength(obj.blocks[i])
          packedLen += len
        }
        if (packedLen) {
          length += 1 + packedLen + varint.encodingLength(packedLen)
        }
      }
      return length
    }

    function encode (obj, buf, offset) {
      if (!offset) offset = 0
      if (!buf) buf = b4a.allocUnsafe(encodingLength(obj))
      var oldOffset = offset
      if (!defined(obj.start)) throw new Error("start is required")
      buf[offset++] = 8
      encodings.varint.encode(obj.start, buf, offset)
      offset += encodings.varint.encode.bytes
      if (!defined(obj.end)) throw new Error("end is required")
      buf[offset++] = 16
      encodings.varint.encode(obj.end, buf, offset)
      offset += encodings.varint.encode.bytes
      if (defined(obj.blocks)) {
        var packedLen = 0
        for (var i = 0; i < obj.blocks.length; i++) {
          if (!defined(obj.blocks[i])) continue
          packedLen += encodings.varint.encodingLength(obj.blocks[i])
        }
        if (packedLen) {
          buf[offset++] = 26
          varint.encode(packedLen, buf, offset)
          offset += varint.encode.bytes
        }
        for (var i = 0; i < obj.blocks.length; i++) {
          if (!defined(obj.blocks[i])) continue
          encodings.varint.encode(obj.blocks[i], buf, offset)
          offset += encodings.varint.encode.bytes
        }
      }
      encode.bytes = offset - oldOffset
      return buf
    }

    function decode (buf, offset, end) {
      if (!offset) offset = 0
      if (!end) end = buf.length
      if (!(end <= buf.length && offset <= buf.length)) throw new Error("Decoded message is not valid")
      var oldOffset = offset
      var obj = {
        start: 0,
        end: 0,
        blocks: []
      }
      var found0 = false
      var found1 = false
      while (true) {
        if (end <= offset) {
          if (!found0 || !found1) throw new Error("Decoded message is not valid")
          decode.bytes = offset - oldOffset
          return obj
        }
        var prefix = varint.decode(buf, offset)
        offset += varint.decode.bytes
        var tag = prefix >> 3
        switch (tag) {
          case 1:
          obj.start = encodings.varint.decode(buf, offset)
          offset += encodings.varint.decode.bytes
          found0 = true
          break
          case 2:
          obj.end = encodings.varint.decode(buf, offset)
          offset += encodings.varint.decode.bytes
          found1 = true
          break
          case 3:
          var packedEnd = varint.decode(buf, offset)
          offset += varint.decode.bytes
          packedEnd += offset
          while (offset < packedEnd) {
            obj.blocks.push(encodings.varint.decode(buf, offset))
            offset += encodings.varint.decode.bytes
          }
          break
          default:
          offset = skip(prefix & 7, buf, offset)
        }
      }
    }
  }

  Extension.encodingLength = encodingLength
  Extension.encode = encode
  Extension.decode = decode

  function encodingLength (obj) {
    var length = 0
    if (defined(obj.cache)) {
      var len = Cache.encodingLength(obj.cache)
      length += varint.encodingLength(len)
      length += 1 + len
    }
    if (defined(obj.get)) {
      var len = Get.encodingLength(obj.get)
      length += varint.encodingLength(len)
      length += 1 + len
    }
    if (defined(obj.iterator)) {
      var len = Iterator.encodingLength(obj.iterator)
      length += varint.encodingLength(len)
      length += 1 + len
    }
    return length
  }

  function encode (obj, buf, offset) {
    if (!offset) offset = 0
    if (!buf) buf = b4a.allocUnsafe(encodingLength(obj))
    var oldOffset = offset
    if (defined(obj.cache)) {
      buf[offset++] = 10
      varint.encode(Cache.encodingLength(obj.cache), buf, offset)
      offset += varint.encode.bytes
      Cache.encode(obj.cache, buf, offset)
      offset += Cache.encode.bytes
    }
    if (defined(obj.get)) {
      buf[offset++] = 18
      varint.encode(Get.encodingLength(obj.get), buf, offset)
      offset += varint.encode.bytes
      Get.encode(obj.get, buf, offset)
      offset += Get.encode.bytes
    }
    if (defined(obj.iterator)) {
      buf[offset++] = 26
      varint.encode(Iterator.encodingLength(obj.iterator), buf, offset)
      offset += varint.encode.bytes
      Iterator.encode(obj.iterator, buf, offset)
      offset += Iterator.encode.bytes
    }
    encode.bytes = offset - oldOffset
    return buf
  }

  function decode (buf, offset, end) {
    if (!offset) offset = 0
    if (!end) end = buf.length
    if (!(end <= buf.length && offset <= buf.length)) throw new Error("Decoded message is not valid")
    var oldOffset = offset
    var obj = {
      cache: null,
      get: null,
      iterator: null
    }
    while (true) {
      if (end <= offset) {
        decode.bytes = offset - oldOffset
        return obj
      }
      var prefix = varint.decode(buf, offset)
      offset += varint.decode.bytes
      var tag = prefix >> 3
      switch (tag) {
        case 1:
        var len = varint.decode(buf, offset)
        offset += varint.decode.bytes
        obj.cache = Cache.decode(buf, offset, offset + len)
        offset += Cache.decode.bytes
        break
        case 2:
        var len = varint.decode(buf, offset)
        offset += varint.decode.bytes
        obj.get = Get.decode(buf, offset, offset + len)
        offset += Get.decode.bytes
        break
        case 3:
        var len = varint.decode(buf, offset)
        offset += varint.decode.bytes
        obj.iterator = Iterator.decode(buf, offset, offset + len)
        offset += Iterator.decode.bytes
        break
        default:
        offset = skip(prefix & 7, buf, offset)
      }
    }
  }
}

function defined (val) {
  return val !== null && val !== undefined && (typeof val !== 'number' || !isNaN(val))
}
{
  "name": "hyperbee",
  "version": "2.27.3",
  "description": "An append-only B-tree running on a Hypercore.",
  "main": "index.js",
  "files": [
    "index.js",
    "lib/**.js",
    "iterators/**.js"
  ],
  "dependencies": {
    "b4a": "^1.6.0",
    "codecs": "^3.0.0",
    "debounceify": "^1.0.0",
    "hypercore-errors": "^1.0.0",
    "mutexify": "^1.4.0",
    "protocol-buffers-encodings": "^1.2.0",
    "rache": "^1.0.0",
    "ready-resource": "^1.0.0",
    "resolve-reject-promise": "^1.1.0",
    "safety-catch": "^1.0.2",
    "streamx": "^2.12.4",
    "unslab": "^1.2.0"
  },
  "devDependencies": {
    "bare-process": "^4.2.2",
    "brittle": "^3.1.0",
    "hypercore": "^11.0.0",
    "lunte": "^1.0.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^2.0.0",
    "protocol-buffers": "^4.2.0",
    "sub-encoder": "^1.0.6",
    "tree-to-string": "^1.1.1"
  },
  "imports": {
    "process": {
      "bare": "bare-process",
      "default": "process"
    }
  },
  "scripts": {
    "format": "prettier --write .",
    "lint": "prettier --check . && lunte",
    "test": "npm run test:node && npm run test:bare",
    "test:node": "brittle-node test/*.js",
    "test:bare": "brittle-bare test/*.js",
    "protobuf": "protocol-buffers schema.proto -o ./lib/messages.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/holepunchto/hyperbee.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/holepunchto/hyperbee/issues"
  },
  "homepage": "https://github.com/holepunchto/hyperbee"
}
const sodium = require('sodium-universal')
const c = require('compact-encoding')
const b4a = require('b4a')

// https://en.wikipedia.org/wiki/Merkle_tree#Second_preimage_attack
const LEAF_TYPE = b4a.from([0])
const PARENT_TYPE = b4a.from([1])
const ROOT_TYPE = b4a.from([2])

const HYPERCORE = b4a.from('hypercore')

exports.keyPair = function (seed) {
  // key pairs might stay around for a while, so better not to use a default slab to avoid retaining it completely
  const slab = b4a.allocUnsafeSlow(
    sodium.crypto_sign_PUBLICKEYBYTES + sodium.crypto_sign_SECRETKEYBYTES
  )
  const publicKey = slab.subarray(0, sodium.crypto_sign_PUBLICKEYBYTES)
  const secretKey = slab.subarray(sodium.crypto_sign_PUBLICKEYBYTES)

  if (seed) sodium.crypto_sign_seed_keypair(publicKey, secretKey, seed)
  else sodium.crypto_sign_keypair(publicKey, secretKey)

  return {
    publicKey,
    secretKey
  }
}

exports.validateKeyPair = function (keyPair) {
  const pk = b4a.allocUnsafe(sodium.crypto_sign_PUBLICKEYBYTES)
  sodium.crypto_sign_ed25519_sk_to_pk(pk, keyPair.secretKey)
  return b4a.equals(pk, keyPair.publicKey)
}

exports.sign = function (message, secretKey) {
  // Dedicated slab for the signature, to avoid retaining unneeded mem and for security
  const signature = b4a.allocUnsafeSlow(sodium.crypto_sign_BYTES)
  sodium.crypto_sign_detached(signature, message, secretKey)
  return signature
}

exports.verify = function (message, signature, publicKey) {
  if (signature.byteLength !== sodium.crypto_sign_BYTES) return false
  if (publicKey.byteLength !== sodium.crypto_sign_PUBLICKEYBYTES) return false
  return sodium.crypto_sign_verify_detached(signature, message, publicKey)
}

exports.encrypt = function (message, publicKey) {
  const ciphertext = b4a.alloc(message.byteLength + sodium.crypto_box_SEALBYTES)
  sodium.crypto_box_seal(ciphertext, message, publicKey)
  return ciphertext
}

exports.decrypt = function (ciphertext, keyPair) {
  if (ciphertext.byteLength < sodium.crypto_box_SEALBYTES) return null

  const plaintext = b4a.alloc(ciphertext.byteLength - sodium.crypto_box_SEALBYTES)

  if (!sodium.crypto_box_seal_open(plaintext, ciphertext, keyPair.publicKey, keyPair.secretKey)) {
    return null
  }

  return plaintext
}

exports.encryptionKeyPair = function (seed) {
  const publicKey = b4a.alloc(sodium.crypto_box_PUBLICKEYBYTES)
  const secretKey = b4a.alloc(sodium.crypto_box_SECRETKEYBYTES)

  if (seed) {
    sodium.crypto_box_seed_keypair(publicKey, secretKey, seed)
  } else {
    sodium.crypto_box_keypair(publicKey, secretKey)
  }

  return {
    publicKey,
    secretKey
  }
}

exports.data = function (data) {
  const out = b4a.allocUnsafe(32)

  sodium.crypto_generichash_batch(out, [LEAF_TYPE, c.encode(c.uint64, data.byteLength), data])

  return out
}

exports.parent = function (a, b) {
  if (a.index > b.index) {
    const tmp = a
    a = b
    b = tmp
  }

  const out = b4a.allocUnsafe(32)

  sodium.crypto_generichash_batch(out, [
    PARENT_TYPE,
    c.encode(c.uint64, a.size + b.size),
    a.hash,
    b.hash
  ])

  return out
}

exports.tree = function (roots, out) {
  const buffers = new Array(3 * roots.length + 1)
  let j = 0

  buffers[j++] = ROOT_TYPE

  for (let i = 0; i < roots.length; i++) {
    const r = roots[i]
    buffers[j++] = r.hash
    buffers[j++] = c.encode(c.uint64, r.index)
    buffers[j++] = c.encode(c.uint64, r.size)
  }

  if (!out) out = b4a.allocUnsafe(32)
  sodium.crypto_generichash_batch(out, buffers)
  return out
}

exports.hash = function (data, out) {
  if (!out) out = b4a.allocUnsafe(32)
  if (!Array.isArray(data)) data = [data]

  sodium.crypto_generichash_batch(out, data)

  return out
}

exports.randomBytes = function (n) {
  const buf = b4a.allocUnsafe(n)
  sodium.randombytes_buf(buf)
  return buf
}

exports.discoveryKey = function (key) {
  if (!key || key.byteLength !== 32) throw new Error('Must pass a 32 byte buffer')
  // Discovery keys might stay around for a while, so better not to use slab memory (for better gc)
  const digest = b4a.allocUnsafeSlow(32)
  sodium.crypto_generichash(digest, HYPERCORE, key)
  return digest
}

if (sodium.sodium_free) {
  exports.free = function (secureBuf) {
    if (secureBuf.secure) sodium.sodium_free(secureBuf)
  }
} else {
  exports.free = function () {}
}

exports.namespace = function (name, count) {
  const ids = typeof count === 'number' ? range(count) : count

  // Namespaces are long-lived, so better to use a dedicated slab
  const buf = b4a.allocUnsafeSlow(32 * ids.length)

  const list = new Array(ids.length)

  // ns is ephemeral, so default slab
  const ns = b4a.allocUnsafe(33)
  sodium.crypto_generichash(ns.subarray(0, 32), typeof name === 'string' ? b4a.from(name) : name)

  for (let i = 0; i < list.length; i++) {
    list[i] = buf.subarray(32 * i, 32 * i + 32)
    ns[32] = ids[i]
    sodium.crypto_generichash(list[i], ns)
  }

  return list
}

function range(count) {
  const arr = new Array(count)
  for (let i = 0; i < count; i++) arr[i] = i
  return arr
}
{
  "name": "hypercore-crypto",
  "version": "3.7.0",
  "description": "The crypto primitives used in hypercore, extracted into a separate module",
  "main": "index.js",
  "files": [
    "index.js"
  ],
  "dependencies": {
    "b4a": "^1.6.6",
    "compact-encoding": "^3.0.0",
    "sodium-universal": "^5.0.0"
  },
  "devDependencies": {
    "brittle": "^3.5.0",
    "lunte": "^1.0.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^2.0.0"
  },
  "scripts": {
    "format": "prettier --write .",
    "lint": "prettier --check . && lunte",
    "test": "npm run test:node && npm run test:bare",
    "test:node": "brittle-node test.js",
    "test:bare": "brittle-bare test.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/mafintosh/hypercore-crypto.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/mafintosh/hypercore-crypto/issues"
  },
  "homepage": "https://github.com/mafintosh/hypercore-crypto"
}
const IdEnc = require('hypercore-id-encoding')

module.exports = class HypercoreError extends Error {
  constructor (msg, code, fn = HypercoreError, discoveryKey = null) {
    if (discoveryKey) msg = `${msg} (discovery key: ${IdEnc.normalize(discoveryKey)})`
    super(`${code}: ${msg}`)

    this.code = code
    this.discoveryKey = discoveryKey

    if (Error.captureStackTrace) {
      Error.captureStackTrace(this, fn)
    }
  }

  get name () {
    return 'HypercoreError'
  }

  static ASSERTION (msg, discoveryKey = null) { // ERR_ASSERTION is picked up by safety-catch also
    return new HypercoreError(msg, 'ERR_ASSERTION', HypercoreError.ASSERT, discoveryKey)
  }

  static BAD_ARGUMENT (msg, discoveryKey = null) {
    return new HypercoreError(msg, 'BAD_ARGUMENT', HypercoreError.BAD_ARGUMENT, discoveryKey)
  }

  static STORAGE_EMPTY (msg, discoveryKey = null) {
    return new HypercoreError(msg, 'STORAGE_EMPTY', HypercoreError.STORAGE_EMPTY, discoveryKey)
  }

  static STORAGE_CONFLICT (msg, discoveryKey = null) {
    return new HypercoreError(msg, 'STORAGE_CONFLICT', HypercoreError.STORAGE_CONFLICT, discoveryKey)
  }

  static INVALID_SIGNATURE (msg, discoveryKey = null) {
    return new HypercoreError(msg, 'INVALID_SIGNATURE', HypercoreError.INVALID_SIGNATURE, discoveryKey)
  }

  static INVALID_CAPABILITY (msg, discoveryKey = null) {
    return new HypercoreError(msg, 'INVALID_CAPABILITY', HypercoreError.INVALID_CAPABILITY, discoveryKey)
  }

  static INVALID_CHECKSUM (msg = 'Invalid checksum', discoveryKey = null) {
    return new HypercoreError(msg, 'INVALID_CHECKSUM', HypercoreError.INVALID_CHECKSUM, discoveryKey)
  }

  static INVALID_OPERATION (msg, discoveryKey = null) {
    return new HypercoreError(msg, 'INVALID_OPERATION', HypercoreError.INVALID_OPERATION, discoveryKey)
  }

  static INVALID_PROOF (msg = 'Proof not verifiable', discoveryKey = null) {
    return new HypercoreError(msg, 'INVALID_PROOF', HypercoreError.INVALID_PROOF, discoveryKey)
  }

  static BLOCK_NOT_AVAILABLE (msg = 'Block is not available', discoveryKey = null) {
    return new HypercoreError(msg, 'BLOCK_NOT_AVAILABLE', HypercoreError.BLOCK_NOT_AVAILABLE, discoveryKey)
  }

  static SNAPSHOT_NOT_AVAILABLE (msg = 'Snapshot is not available', discoveryKey = null) {
    return new HypercoreError(msg, 'SNAPSHOT_NOT_AVAILABLE', HypercoreError.SNAPSHOT_NOT_AVAILABLE, discoveryKey)
  }

  static REQUEST_CANCELLED (msg = 'Request was cancelled', discoveryKey = null) {
    return new HypercoreError(msg, 'REQUEST_CANCELLED', HypercoreError.REQUEST_CANCELLED, discoveryKey)
  }

  static REQUEST_TIMEOUT (msg = 'Request timed out', discoveryKey = null) {
    return new HypercoreError(msg, 'REQUEST_TIMEOUT', HypercoreError.REQUEST_TIMEOUT, discoveryKey)
  }

  static SESSION_NOT_WRITABLE (msg = 'Session is not writable', discoveryKey = null) {
    return new HypercoreError(msg, 'SESSION_NOT_WRITABLE', HypercoreError.SESSION_NOT_WRITABLE, discoveryKey)
  }

  static SESSION_CLOSED (msg = 'Session is closed', discoveryKey = null) {
    return new HypercoreError(msg, 'SESSION_CLOSED', HypercoreError.SESSION_CLOSED, discoveryKey)
  }

  static BATCH_UNFLUSHED (msg = 'Batch not yet flushed', discoveryKey = null) {
    return new HypercoreError(msg, 'BATCH_UNFLUSHED', HypercoreError.BATCH_UNFLUSHED, discoveryKey)
  }

  static BATCH_ALREADY_EXISTS (msg = 'Batch already exists', discoveryKey = null) {
    return new HypercoreError(msg, 'BATCH_ALREADY_EXISTS', HypercoreError.BATCH_ALREADY_EXISTS, discoveryKey)
  }

  static BATCH_ALREADY_FLUSHED (msg = 'Batch has already been flushed', discoveryKey = null) {
    return new HypercoreError(msg, 'BATCH_ALREADY_FLUSHED', HypercoreError.BATCH_ALREADY_FLUSHED, discoveryKey)
  }

  static OPLOG_CORRUPT (msg = 'Oplog file appears corrupt or out of date', discoveryKey = null) {
    return new HypercoreError(msg, 'OPLOG_CORRUPT', HypercoreError.OPLOG_CORRUPT, discoveryKey)
  }

  static OPLOG_HEADER_OVERFLOW (msg = 'Oplog header exceeds page size', discoveryKey = null) {
    return new HypercoreError(msg, 'OPLOG_HEADER_OVERFLOW', HypercoreError.OPLOG_HEADER_OVERFLOW, discoveryKey)
  }

  static INVALID_OPLOG_VERSION (msg = 'Invalid header version', discoveryKey = null) {
    return new HypercoreError(msg, 'INVALID_OPLOG_VERSION', HypercoreError.INVALID_OPLOG_VERSION, discoveryKey)
  }

  static WRITE_FAILED (msg = 'Write to storage failed', discoveryKey = null) {
    return new HypercoreError(msg, 'WRITE_FAILED', HypercoreError.WRITE_FAILED, discoveryKey)
  }

  static DECODING_ERROR (msg = 'Decoding error', discoveryKey = null) {
    return new HypercoreError(msg, 'DECODING_ERROR', HypercoreError.DECODING_ERROR, discoveryKey)
  }

  static SESSION_MOVED (msg = 'Session moved', discoveryKey = null) {
    return new HypercoreError(msg, 'SESSION_MOVED', HypercoreError.SESSION_MOVED, discoveryKey)
  }
}
{
  "name": "hypercore-errors",
  "version": "1.5.0",
  "description": "Hypercore errors",
  "main": "index.js",
  "files": [
    "index.js"
  ],
  "scripts": {
    "test": "standard && brittle test.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/holepunchto/hypercore-errors.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/hypercore-errors/issues"
  },
  "homepage": "https://github.com/holepunchto/hypercore-errors#readme",
  "devDependencies": {
    "b4a": "^1.6.7",
    "brittle": "^3.1.3",
    "standard": "^17.0.0"
  },
  "dependencies": {
    "hypercore-id-encoding": "^1.3.0"
  }
}
const z32 = require('z32')
const b4a = require('b4a')

module.exports = {
  encode,
  decode,
  normalize,
  isValid
}

function encode (key) {
  if (!b4a.isBuffer(key)) throw new Error('Key must be a Buffer')
  if (key.byteLength !== 32) throw new Error('Key must be 32-bytes long')
  return z32.encode(key)
}

function decode (id) {
  if (b4a.isBuffer(id)) {
    if (id.byteLength !== 32) throw new Error('ID must be 32-bytes long')
    return id
  }
  if (typeof id === 'string') {
    if (id.startsWith('pear://')) id = id.slice(7).split('/')[0]
    if (id.length === 52) return z32.decode(id)
    if (id.length === 64) {
      const buf = b4a.from(id, 'hex')
      if (buf.byteLength === 32) return buf
    }
  }
  throw new Error('Invalid Hypercore key')
}

function normalize (any) {
  return encode(decode(any))
}

function isValid (any) {
  try {
    decode(any)
    return true
  } catch {
    return false
  }
}
{
  "name": "hypercore-id-encoding",
  "version": "1.3.0",
  "description": "Convert Hypercore keys to/from z-base32 or hex",
  "main": "index.js",
  "scripts": {
    "test": "standard && brittle test.js"
  },
  "files": [
    "index.js"
  ],
  "dependencies": {
    "b4a": "^1.5.3",
    "z32": "^1.0.0"
  },
  "devDependencies": {
    "brittle": "^3.1.1",
    "hypercore": "^10.0.0",
    "random-access-memory": "^6.0.0",
    "standard": "^17.1.0"
  },
  "license": "Apache-2.0",
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/hypercore-id-encoding.git"
  },
  "keywords": [],
  "author": "",
  "bugs": {
    "url": "https://github.com/holepunchto/hypercore-id-encoding/issues"
  },
  "homepage": "https://github.com/holepunchto/hypercore-id-encoding#readme"
}
function headLegacyMap(m) {
  return {
    version: 2, // version bumped from 1
    cores: m.allocated.cores,
    datas: m.allocated.datas,
    groups: 0,
    seed: m.seed,
    defaultDiscoveryKey: m.defaultDiscoveryKey
  }
}

module.exports = {
  headLegacyMap
}
// This file is autogenerated by the hyperschema compiler
// Schema Version: 2
/* eslint-disable camelcase */
/* eslint-disable quotes */
/* eslint-disable space-before-function-paren */

const { c } = require('hyperschema/runtime')
const external0 = require('../../external.js')

const VERSION = 2

// eslint-disable-next-line no-unused-vars
let version = VERSION

// @corestore/allocated
const encoding0 = {
  preencode(state, m) {
    c.uint.preencode(state, m.cores)
    c.uint.preencode(state, m.datas)
  },
  encode(state, m) {
    c.uint.encode(state, m.cores)
    c.uint.encode(state, m.datas)
  },
  decode(state) {
    const r0 = c.uint.decode(state)
    const r1 = c.uint.decode(state)

    return {
      cores: r0,
      datas: r1
    }
  }
}

// @corestore/head-v1
const encoding1 = {
  preencode(state, m) {
    state.end++ // max flag is 4 so always one byte

    if (m.allocated) encoding0.preencode(state, m.allocated)
    if (m.seed) c.fixed32.preencode(state, m.seed)
    if (m.defaultDiscoveryKey) c.fixed32.preencode(state, m.defaultDiscoveryKey)
  },
  encode(state, m) {
    const flags = (m.allocated ? 1 : 0) | (m.seed ? 2 : 0) | (m.defaultDiscoveryKey ? 4 : 0)

    c.uint.encode(state, flags)

    if (m.allocated) encoding0.encode(state, m.allocated)
    if (m.seed) c.fixed32.encode(state, m.seed)
    if (m.defaultDiscoveryKey) c.fixed32.encode(state, m.defaultDiscoveryKey)
  },
  decode(state) {
    const v = c.uint.decode(state)
    const flags = c.uint.decode(state)

    return {
      version: v,
      allocated: (flags & 1) !== 0 ? encoding0.decode(state) : null,
      seed: (flags & 2) !== 0 ? c.fixed32.decode(state) : null,
      defaultDiscoveryKey: (flags & 4) !== 0 ? c.fixed32.decode(state) : null
    }
  }
}

// @corestore/head-v2
const encoding2 = {
  preencode(state, m) {
    c.uint.preencode(state, m.cores)
    c.uint.preencode(state, m.datas)
    c.uint.preencode(state, m.groups)
    state.end++ // max flag is 2 so always one byte

    if (m.seed) c.fixed32.preencode(state, m.seed)
    if (m.defaultDiscoveryKey) c.fixed32.preencode(state, m.defaultDiscoveryKey)
  },
  encode(state, m) {
    const flags = (m.seed ? 1 : 0) | (m.defaultDiscoveryKey ? 2 : 0)

    c.uint.encode(state, m.cores)
    c.uint.encode(state, m.datas)
    c.uint.encode(state, m.groups)
    c.uint.encode(state, flags)

    if (m.seed) c.fixed32.encode(state, m.seed)
    if (m.defaultDiscoveryKey) c.fixed32.encode(state, m.defaultDiscoveryKey)
  },
  decode(state) {
    const v = c.uint.decode(state)
    const r0 = c.uint.decode(state)
    const r1 = c.uint.decode(state)
    const r2 = c.uint.decode(state)
    const flags = c.uint.decode(state)

    return {
      version: v,
      cores: r0,
      datas: r1,
      groups: r2,
      seed: (flags & 1) !== 0 ? c.fixed32.decode(state) : null,
      defaultDiscoveryKey: (flags & 2) !== 0 ? c.fixed32.decode(state) : null
    }
  }
}

// @corestore/head
const encoding3 = {
  preencode(state, m) {
    c.uint.preencode(state, m.version)
    switch (m.version) {
      case 0:
      case 1:
        encoding1.preencode(state, m)
        break
      case 2:
        encoding2.preencode(state, m)
        break
      default:
        throw new Error('Unsupported version')
    }
  },
  encode(state, m) {
    c.uint.encode(state, m.version)
    switch (m.version) {
      case 0:
      case 1:
        encoding1.encode(state, m)
        break
      case 2:
        encoding2.encode(state, m)
        break
      default:
        throw new Error('Unsupported version')
    }
  },
  decode(state) {
    const start = state.start
    const v = c.uint.decode(state)
    state.start = start
    switch (v) {
      case 0:
      case 1: {
        const decoded = encoding1.decode(state)
        const map = external0.headLegacyMap
        return map(decoded)
      }
      case 2: {
        const decoded = encoding2.decode(state)
        return decoded
      }
      default:
        throw new Error('Unsupported version')
    }
  }
}

// @corestore/alias
const encoding4 = {
  preencode(state, m) {
    c.string.preencode(state, m.name)
    c.fixed32.preencode(state, m.namespace)
  },
  encode(state, m) {
    c.string.encode(state, m.name)
    c.fixed32.encode(state, m.namespace)
  },
  decode(state) {
    const r0 = c.string.decode(state)
    const r1 = c.fixed32.decode(state)

    return {
      name: r0,
      namespace: r1
    }
  }
}

// @corestore/core
const encoding5 = {
  preencode(state, m) {
    c.uint.preencode(state, m.version)
    c.uint.preencode(state, m.corePointer)
    c.uint.preencode(state, m.dataPointer)
    state.end++ // max flag is 1 so always one byte

    if (m.alias) encoding4.preencode(state, m.alias)
  },
  encode(state, m) {
    const flags = m.alias ? 1 : 0

    c.uint.encode(state, m.version)
    c.uint.encode(state, m.corePointer)
    c.uint.encode(state, m.dataPointer)
    c.uint.encode(state, flags)

    if (m.alias) encoding4.encode(state, m.alias)
  },
  decode(state) {
    const r0 = c.uint.decode(state)
    const r1 = c.uint.decode(state)
    const r2 = c.uint.decode(state)
    const flags = c.uint.decode(state)

    return {
      version: r0,
      corePointer: r1,
      dataPointer: r2,
      alias: (flags & 1) !== 0 ? encoding4.decode(state) : null
    }
  }
}

const encoding6_enum = {
  blake2b: 'blake2b'
}

// @core/hashes enum
const encoding6 = {
  preencode(state, m) {
    state.end++ // max enum is 0 so always one byte
  },
  encode(state, m) {
    switch (m) {
      case 'blake2b':
        c.uint.encode(state, 0)
        break
      default:
        throw new Error('Unknown enum')
    }
  },
  decode(state) {
    switch (c.uint.decode(state)) {
      case 0:
        return 'blake2b'
      default:
        return null
    }
  }
}

const encoding7_enum = {
  ed25519: 'ed25519'
}

// @core/signatures enum
const encoding7 = {
  preencode(state, m) {
    state.end++ // max enum is 0 so always one byte
  },
  encode(state, m) {
    switch (m) {
      case 'ed25519':
        c.uint.encode(state, 0)
        break
      default:
        throw new Error('Unknown enum')
    }
  },
  decode(state) {
    switch (c.uint.decode(state)) {
      case 0:
        return 'ed25519'
      default:
        return null
    }
  }
}

// @core/tree-node
const encoding8 = {
  preencode(state, m) {
    c.uint.preencode(state, m.index)
    c.uint.preencode(state, m.size)
    c.fixed32.preencode(state, m.hash)
  },
  encode(state, m) {
    c.uint.encode(state, m.index)
    c.uint.encode(state, m.size)
    c.fixed32.encode(state, m.hash)
  },
  decode(state) {
    const r0 = c.uint.decode(state)
    const r1 = c.uint.decode(state)
    const r2 = c.fixed32.decode(state)

    return {
      index: r0,
      size: r1,
      hash: r2
    }
  }
}

// @core/signer
const encoding9 = {
  preencode(state, m) {
    encoding7.preencode(state, m.signature)
    c.fixed32.preencode(state, m.namespace)
    c.fixed32.preencode(state, m.publicKey)
  },
  encode(state, m) {
    encoding7.encode(state, m.signature)
    c.fixed32.encode(state, m.namespace)
    c.fixed32.encode(state, m.publicKey)
  },
  decode(state) {
    const r0 = encoding7.decode(state)
    const r1 = c.fixed32.decode(state)
    const r2 = c.fixed32.decode(state)

    return {
      signature: r0,
      namespace: r1,
      publicKey: r2
    }
  }
}

// @core/prologue
const encoding10 = {
  preencode(state, m) {
    c.fixed32.preencode(state, m.hash)
    c.uint.preencode(state, m.length)
  },
  encode(state, m) {
    c.fixed32.encode(state, m.hash)
    c.uint.encode(state, m.length)
  },
  decode(state) {
    const r0 = c.fixed32.decode(state)
    const r1 = c.uint.decode(state)

    return {
      hash: r0,
      length: r1
    }
  }
}

// @core/manifest.signers
const encoding11_4 = c.array(encoding9)
// @core/manifest.linked
const encoding11_6 = c.array(c.fixed32)

// @core/manifest
const encoding11 = {
  preencode(state, m) {
    c.uint.preencode(state, m.version)
    state.end++ // max flag is 8 so always one byte
    encoding6.preencode(state, m.hash)
    c.uint.preencode(state, m.quorum)
    encoding11_4.preencode(state, m.signers)

    if (m.prologue) encoding10.preencode(state, m.prologue)
    if (m.linked) encoding11_6.preencode(state, m.linked)
    if (m.userData) c.buffer.preencode(state, m.userData)
  },
  encode(state, m) {
    const flags =
      (m.allowPatch ? 1 : 0) | (m.prologue ? 2 : 0) | (m.linked ? 4 : 0) | (m.userData ? 8 : 0)

    c.uint.encode(state, m.version)
    c.uint.encode(state, flags)
    encoding6.encode(state, m.hash)
    c.uint.encode(state, m.quorum)
    encoding11_4.encode(state, m.signers)

    if (m.prologue) encoding10.encode(state, m.prologue)
    if (m.linked) encoding11_6.encode(state, m.linked)
    if (m.userData) c.buffer.encode(state, m.userData)
  },
  decode(state) {
    const r0 = c.uint.decode(state)
    const flags = c.uint.decode(state)

    return {
      version: r0,
      hash: encoding6.decode(state),
      quorum: c.uint.decode(state),
      allowPatch: (flags & 1) !== 0,
      signers: encoding11_4.decode(state),
      prologue: (flags & 2) !== 0 ? encoding10.decode(state) : null,
      linked: (flags & 4) !== 0 ? encoding11_6.decode(state) : null,
      userData: (flags & 8) !== 0 ? c.buffer.decode(state) : null
    }
  }
}

// @core/keyPair
const encoding12 = {
  preencode(state, m) {
    c.buffer.preencode(state, m.publicKey)
    c.optionalBuffer.preencode(state, m.secretKey)
  },
  encode(state, m) {
    c.buffer.encode(state, m.publicKey)
    c.optionalBuffer.encode(state, m.secretKey)
  },
  decode(state) {
    const r0 = c.buffer.decode(state)
    const r1 = c.optionalBuffer.decode(state)

    return {
      publicKey: r0,
      secretKey: r1
    }
  }
}

// @core/group
const encoding13 = {
  preencode(state, m) {
    state.end++ // max flag is 2 so always one byte

    if (m.key) c.fixed32.preencode(state, m.key)
    if (m.pointer) c.uint.preencode(state, m.pointer)
  },
  encode(state, m) {
    const flags = (m.key ? 1 : 0) | (m.pointer ? 2 : 0)

    c.uint.encode(state, flags)

    if (m.key) c.fixed32.encode(state, m.key)
    if (m.pointer) c.uint.encode(state, m.pointer)
  },
  decode(state) {
    const flags = c.uint.decode(state)

    return {
      key: (flags & 1) !== 0 ? c.fixed32.decode(state) : null,
      pointer: (flags & 2) !== 0 ? c.uint.decode(state) : 0
    }
  }
}

// @core/auth.manifest
const encoding14_2 = c.frame(encoding11)

// @core/auth
const encoding14 = {
  preencode(state, m) {
    c.fixed32.preencode(state, m.key)
    c.fixed32.preencode(state, m.discoveryKey)
    state.end++ // max flag is 4 so always one byte

    if (m.manifest) encoding14_2.preencode(state, m.manifest)
    if (m.keyPair) encoding12.preencode(state, m.keyPair)
    if (m.encryptionKey) c.buffer.preencode(state, m.encryptionKey)
  },
  encode(state, m) {
    const flags = (m.manifest ? 1 : 0) | (m.keyPair ? 2 : 0) | (m.encryptionKey ? 4 : 0)

    c.fixed32.encode(state, m.key)
    c.fixed32.encode(state, m.discoveryKey)
    c.uint.encode(state, flags)

    if (m.manifest) encoding14_2.encode(state, m.manifest)
    if (m.keyPair) encoding12.encode(state, m.keyPair)
    if (m.encryptionKey) c.buffer.encode(state, m.encryptionKey)
  },
  decode(state) {
    const r0 = c.fixed32.decode(state)
    const r1 = c.fixed32.decode(state)
    const flags = c.uint.decode(state)

    return {
      key: r0,
      discoveryKey: r1,
      manifest: (flags & 1) !== 0 ? encoding14_2.decode(state) : null,
      keyPair: (flags & 2) !== 0 ? encoding12.decode(state) : null,
      encryptionKey: (flags & 4) !== 0 ? c.buffer.decode(state) : null
    }
  }
}

// @core/head
const encoding15 = {
  preencode(state, m) {
    c.uint.preencode(state, m.fork)
    c.uint.preencode(state, m.length)
    c.fixed32.preencode(state, m.rootHash)
    c.optionalBuffer.preencode(state, m.signature)
    state.end++ // max flag is 1 so always one byte

    if (version >= 2 && m.timestamp) c.uint64.preencode(state, m.timestamp)
  },
  encode(state, m) {
    const flags = version >= 2 && m.timestamp ? 1 : 0

    c.uint.encode(state, m.fork)
    c.uint.encode(state, m.length)
    c.fixed32.encode(state, m.rootHash)
    c.optionalBuffer.encode(state, m.signature)
    c.uint.encode(state, flags)

    if (version >= 2 && m.timestamp) c.uint64.encode(state, m.timestamp)
  },
  decode(state) {
    const r0 = c.uint.decode(state)
    const r1 = c.uint.decode(state)
    const r2 = c.fixed32.decode(state)
    const r3 = c.optionalBuffer.decode(state)
    const flags = state.start < state.end ? c.uint.decode(state) : 0

    return {
      fork: r0,
      length: r1,
      rootHash: r2,
      signature: r3,
      timestamp: version >= 2 && (flags & 1) !== 0 ? c.uint64.decode(state) : 0
    }
  }
}

// @core/hints
const encoding16 = {
  preencode(state, m) {
    state.end++ // max flag is 4 so always one byte

    if (m.contiguousLength) c.uint.preencode(state, m.contiguousLength)
    if (m.remoteContiguousLength) c.uint.preencode(state, m.remoteContiguousLength)
    if (m.recovering) c.uint.preencode(state, m.recovering)
  },
  encode(state, m) {
    const flags =
      (m.contiguousLength ? 1 : 0) | (m.remoteContiguousLength ? 2 : 0) | (m.recovering ? 4 : 0)

    c.uint.encode(state, flags)

    if (m.contiguousLength) c.uint.encode(state, m.contiguousLength)
    if (m.remoteContiguousLength) c.uint.encode(state, m.remoteContiguousLength)
    if (m.recovering) c.uint.encode(state, m.recovering)
  },
  decode(state) {
    const flags = c.uint.decode(state)

    return {
      contiguousLength: (flags & 1) !== 0 ? c.uint.decode(state) : 0,
      remoteContiguousLength: (flags & 2) !== 0 ? c.uint.decode(state) : 0,
      recovering: (flags & 4) !== 0 ? c.uint.decode(state) : 0
    }
  }
}

// @core/session
const encoding17 = {
  preencode(state, m) {
    c.string.preencode(state, m.name)
    c.uint.preencode(state, m.dataPointer)
  },
  encode(state, m) {
    c.string.encode(state, m.name)
    c.uint.encode(state, m.dataPointer)
  },
  decode(state) {
    const r0 = c.string.decode(state)
    const r1 = c.uint.decode(state)

    return {
      name: r0,
      dataPointer: r1
    }
  }
}

// @core/sessions
const encoding18 = c.array(encoding17)

// @core/dependency
const encoding19 = {
  preencode(state, m) {
    c.uint.preencode(state, m.dataPointer)
    c.uint.preencode(state, m.length)
  },
  encode(state, m) {
    c.uint.encode(state, m.dataPointer)
    c.uint.encode(state, m.length)
  },
  decode(state) {
    const r0 = c.uint.decode(state)
    const r1 = c.uint.decode(state)

    return {
      dataPointer: r0,
      length: r1
    }
  }
}

function setVersion(v) {
  version = v
}

function encode(name, value, v = VERSION) {
  version = v
  return c.encode(getEncoding(name), value)
}

function decode(name, buffer, v = VERSION) {
  version = v
  return c.decode(getEncoding(name), buffer)
}

function getEnum(name) {
  switch (name) {
    case '@core/hashes':
      return encoding6_enum
    case '@core/signatures':
      return encoding7_enum
    default:
      throw new Error('Enum not found ' + name)
  }
}

function getEncoding(name) {
  switch (name) {
    case '@corestore/allocated':
      return encoding0
    case '@corestore/head-v1':
      return encoding1
    case '@corestore/head-v2':
      return encoding2
    case '@corestore/head':
      return encoding3
    case '@corestore/alias':
      return encoding4
    case '@corestore/core':
      return encoding5
    case '@core/hashes':
      return encoding6
    case '@core/signatures':
      return encoding7
    case '@core/tree-node':
      return encoding8
    case '@core/signer':
      return encoding9
    case '@core/prologue':
      return encoding10
    case '@core/manifest':
      return encoding11
    case '@core/keyPair':
      return encoding12
    case '@core/group':
      return encoding13
    case '@core/auth':
      return encoding14
    case '@core/head':
      return encoding15
    case '@core/hints':
      return encoding16
    case '@core/session':
      return encoding17
    case '@core/sessions':
      return encoding18
    case '@core/dependency':
      return encoding19
    default:
      throw new Error('Encoder not found ' + name)
  }
}

function getStruct(name, v = VERSION) {
  const enc = getEncoding(name)
  return {
    preencode(state, m) {
      version = v
      enc.preencode(state, m)
    },
    encode(state, m) {
      version = v
      enc.encode(state, m)
    },
    decode(state) {
      version = v
      return enc.decode(state)
    }
  }
}

const resolveStruct = getStruct // compat

module.exports = {
  resolveStruct,
  getStruct,
  getEnum,
  getEncoding,
  encode,
  decode,
  setVersion,
  version
}
const RocksDB = require('rocksdb-native')
const rrp = require('resolve-reject-promise')
const ScopeLock = require('scope-lock')
const DeviceFile = require('device-file')
const path = require('path')
const fs = require('fs')

const View = require('./lib/view.js')

const VERSION = 2
const COLUMN_FAMILY = 'corestore'

const { store, core } = require('./lib/keys.js')

const { CorestoreRX, CorestoreTX, CoreTX, CoreRX } = require('./lib/tx.js')

const {
  createCoreStream,
  createAliasStream,
  createDiscoveryKeyStream,
  createBlockStream,
  createBitfieldStream,
  createUserDataStream,
  createTreeNodeStream,
  createMarkStream,
  createLocalStream,
  createGroupUpdateStream
} = require('./lib/streams.js')

const EMPTY = new View()

class Atom {
  constructor(db) {
    this.db = db
    this.view = new View()
    this.flushedPromise = null
    this.flushing = false
    this.preflushes = []
    this.flushes = []
  }

  preflush(fn) {
    this.preflushes.push(fn)
  }

  onflush(fn) {
    this.flushes.push(fn)
  }

  flushed() {
    if (!this.flushing) return Promise.resolve()
    if (this.flushedPromise !== null) return this.flushedPromise.promise
    this.flushedPromise = rrp()
    return this.flushedPromise.promise
  }

  _resolve() {
    const f = this.flushedPromise
    this.flushedPromise = null
    f.resolve()
  }

  async flush() {
    if (this.flushing) throw new Error('Atom already flushing')
    this.flushing = true

    try {
      const plen = this.preflushes.length
      for (let i = 0; i < plen; i++) this.preflushes[i]()

      await View.flush(this.view.changes, this.db)

      const promises = []
      const len = this.flushes.length // in case of reentry
      for (let i = 0; i < len; i++) promises.push(this.flushes[i]())

      await Promise.all(promises)
    } finally {
      this.view.reset()
      this.flushing = false
      if (this.flushedPromise !== null) this._resolve()
    }
  }
}

class HypercoreStorage {
  constructor(store, db, core, view, atom) {
    this.store = store
    this.db = db
    this.core = core
    this.view = view
    this.atom = atom

    this.view.readStart()
  }

  get readOnly() {
    return this.store.readOnly
  }

  get dependencies() {
    return this.core.dependencies
  }

  getDependencyLength() {
    return this.core.dependencies.length
      ? this.core.dependencies[this.core.dependencies.length - 1].length
      : -1
  }

  getDependency(length) {
    for (let i = this.core.dependencies.length - 1; i >= 0; i--) {
      const dep = this.core.dependencies[i]
      if (dep.length < length) return dep
    }

    return null
  }

  setDependencyHead(dep) {
    if (dep === null) {
      this.core.dependencies = []
      return
    }

    const deps = this.core.dependencies

    for (let i = deps.length - 1; i >= 0; i--) {
      const d = deps[i]

      if (d.dataPointer !== dep.dataPointer) continue

      // check if nothing changed
      if (d.length === dep.length && i === deps.length - 1) return

      this.core = {
        corePointer: this.core.corePointer,
        dataPointer: this.core.dataPointer,
        dependencies: deps.slice(0, i + 1)
      }

      this.core.dependencies[i] = {
        dataPointer: dep.dataPointer,
        length: dep.length
      }
      return // updated head so done
    }

    this.core.dependencies = [
      {
        dataPointer: dep.dataPointer,
        length: dep.length
      }
    ]
  }

  // TODO: this might have to be async if the dependents have changed, but prop ok for now
  updateDependencyLength(length, truncated) {
    const deps = this.core.dependencies

    const i = this.findDependencyIndex(length, truncated)
    if (i === -1) throw new Error('Dependency not found')

    this.core = {
      corePointer: this.core.corePointer,
      dataPointer: this.core.dataPointer,
      dependencies: deps.slice(0, i + 1)
    }

    if (this.core.dependencies[i].length !== length) {
      this.core.dependencies[i] = {
        dataPointer: deps[i].dataPointer,
        length
      }
    }
  }

  findDependencyIndex(length, truncated) {
    const deps = this.core.dependencies

    if (truncated) {
      for (let i = 0; i < deps.length; i++) {
        if (deps[i].length >= length) return i
      }

      return -1
    }

    for (let i = deps.length - 1; i >= 0; i--) {
      if (deps[i].length <= length) return i
    }

    return -1
  }

  get snapshotted() {
    return this.db._snapshot !== null
  }

  snapshot() {
    return new HypercoreStorage(
      this.store,
      this.db.snapshot(),
      this.core,
      this.view.snapshot(),
      this.atom
    )
  }

  compact() {
    return Promise.all([
      this.db.compactRange(core.core(this.core.corePointer), core.core(this.core.corePointer)),
      this.db.compactRange(core.data(this.core.dataPointer), core.data(this.core.dataPointer))
    ])
  }

  atomize(atom) {
    if (this.atom && this.atom !== atom) {
      throw new Error('Cannot atomize and atomized session with a new atom')
    }
    return new HypercoreStorage(this.store, this.db.session(), this.core, atom.view, atom)
  }

  createAtom() {
    return this.store.createAtom()
  }

  createBlockStream(opts) {
    return createBlockStream(this.core, this.db, this.view, opts)
  }

  createTreeNodeStream(opts) {
    return createTreeNodeStream(this.core, this.db, this.view, opts)
  }

  createBitfieldStream(opts) {
    return createBitfieldStream(this.core, this.db, this.view, opts)
  }

  createUserDataStream(opts) {
    return createUserDataStream(this.core, this.db, this.view, opts)
  }

  createMarkStream(opts) {
    return createMarkStream(this.core, this.db, this.view, opts)
  }

  createLocalStream(opts) {
    return createLocalStream(this.core, this.db, this.view, opts)
  }

  async resumeSession(name) {
    const rx = this.read()
    const existingSessionsPromise = rx.getSessions()

    rx.tryFlush()
    const existingSessions = await existingSessionsPromise

    const sessions = existingSessions || []
    const session = getSession(sessions, name, false)

    if (session === null) return null

    const core = {
      corePointer: this.core.corePointer,
      dataPointer: session.dataPointer,
      dependencies: []
    }

    const coreRx = new CoreRX(core, this.db, this.view)

    const dependencyPromise = coreRx.getDependency()
    coreRx.tryFlush()

    const dependency = await dependencyPromise
    if (dependency) core.dependencies = this._addDependency(dependency)

    return new HypercoreStorage(
      this.store,
      this.db.session(),
      core,
      this.atom ? this.view : new View(),
      this.atom
    )
  }

  async createSession(name, head) {
    const rx = this.read()

    const existingSessionsPromise = rx.getSessions()
    const existingHeadPromise = rx.getHead()

    rx.tryFlush()

    const [existingSessions, existingHead] = await Promise.all([
      existingSessionsPromise,
      existingHeadPromise
    ])
    if (head === null) head = existingHead

    if (existingHead !== null && head.length > existingHead.length) {
      throw new Error('Invalid head passed, ahead of core')
    }

    const sessions = existingSessions || []
    const session = getSession(sessions, name, true)
    const fresh = session.dataPointer === -1

    if (fresh) {
      session.dataPointer = await this.store._allocData()
    }

    const tx = this.write()

    tx.setSessions(sessions)

    const length = head === null ? 0 : head.length
    const core = {
      corePointer: this.core.corePointer,
      dataPointer: session.dataPointer,
      dependencies: this._addDependency({
        dataPointer: this.core.dataPointer,
        length
      })
    }

    const coreTx = new CoreTX(core, this.db, tx.view, tx.changes)

    if (length > 0) coreTx.setHead(head)
    coreTx.setDependency(core.dependencies[core.dependencies.length - 1])

    if (!fresh) {
      // nuke all existing state...
      coreTx.deleteBlockRange(0, -1)
      coreTx.deleteTreeNodeRange(0, -1)
      coreTx.deleteBitfieldPageRange(0, -1)
    }

    await tx.flush()

    return new HypercoreStorage(
      this.store,
      this.db.session(),
      core,
      this.atom ? this.view : new View(),
      this.atom
    )
  }

  async createAtomicSession(atom, head) {
    const length = head === null ? 0 : head.length
    const core = {
      corePointer: this.core.corePointer,
      dataPointer: this.core.dataPointer,
      dependencies: this._addDependency(null)
    }

    const coreTx = new CoreTX(core, this.db, atom.view, [])

    if (length > 0) coreTx.setHead(head)

    await coreTx.flush()

    return this.atomize(atom)
  }

  _addDependency(dep) {
    const deps = []

    for (let i = 0; i < this.core.dependencies.length; i++) {
      const d = this.core.dependencies[i]

      if (dep !== null && d.length > dep.length) {
        if (d.dataPointer !== dep.dataPointer) {
          deps.push({ dataPointer: d.dataPointer, length: dep.length })
        }
        return deps
      }

      deps.push(d)
    }

    if (
      dep !== null &&
      (deps.length === 0 || deps[deps.length - 1].dataPointer !== dep.dataPointer)
    ) {
      deps.push(dep)
    }
    return deps
  }

  async deleteSessions() {
    const rx = this.read()
    const existingSessionsPromise = rx.getSessions()
    rx.tryFlush()

    const existingSessions = await existingSessionsPromise

    // Remove batches
    const coreTx = this.write()
    coreTx.setSessions([]) // Clear sessions record
    await coreTx.flush()

    const tx = this.db.write({ autoDestroy: true })

    for (const { dataPointer } of existingSessions) {
      const start = core.data(dataPointer)
      const end = core.data(dataPointer + 1)
      tx.tryDeleteRange(start, end)
    }

    await tx.flush()
  }

  read() {
    return new CoreRX(this.core, this.db, this.view)
  }

  write() {
    return new CoreTX(this.core, this.db, this.atom ? this.view : null, [])
  }

  close() {
    if (this.view !== null) {
      this.view.readStop()
      this.view = null
    }

    return this.db.close()
  }

  static async export(ptr, db, { batches = false } = {}) {
    const rx = new CoreRX(ptr, db, EMPTY)

    const core = {
      head: null,
      auth: null,
      sessions: [],
      data: null
    }

    const sessionsPromise = rx.getSessions()
    const headPromise = rx.getHead()
    const authPromise = rx.getAuth()

    rx.tryFlush()

    const [sessions, head, auth] = await Promise.all([sessionsPromise, headPromise, authPromise])

    core.head = head
    core.auth = { ...auth, keyPair: null }
    if (sessions) core.sessions = sessions.map((s) => s.name)

    const data = []

    data.push(exportData(ptr, db))

    if (batches) {
      for (const { dataPointer } of sessions) {
        data.push(exportData({ dataPointer, dependencies: [] }, db))
      }
    }

    core.data = await Promise.all(data)

    return core
  }

  static isParentStorage(storage, parent) {
    const last = getLastDependency(storage)
    return !!last && last.dataPointer === parent.core.dataPointer
  }
}

class CorestoreStorage {
  constructor(db, opts = {}) {
    const storage = typeof db === 'string' ? db : null

    this.bootstrap = storage !== null
    this.path = storage !== null ? storage : path.join(db.path, '..')
    this.alwaysRecover = !!opts.alwaysRecover
    this.readOnly = !!opts.readOnly
    this.allowBackup = !!opts.allowBackup
    this.deviceFile = null
    this.wait = !!opts.wait

    // tmp sync fix for simplicty since not super deployed yet
    if (this.bootstrap && !this.readOnly) tmpFixStorage(this.path)

    this.id = opts.id || null
    this.view = null
    this.enters = 0
    this.lock = new ScopeLock()
    this.flushing = null
    this.version = 0
    this.migrating = null

    if ((this.bootstrap && !this.readOnly && !this.allowBackup) || this.wait) {
      const corestoreFile = path.join(this.path, 'CORESTORE')

      this.deviceFile = new DeviceFile(corestoreFile, {
        lock: true,
        wait: this.wait,
        data: { id: this.id }
      })
    }

    const dbPath = path.join(this.path, 'db')

    this.rocks = storage === null ? db : new RocksDB(dbPath, { ...opts, lock: this.deviceFile })
    this.db = createColumnFamily(this.rocks, opts)
  }

  get opened() {
    return this.db.opened
  }

  get closed() {
    return this.db.closed
  }

  async ready() {
    if (this.version === 0) await this._migrateStore()
    return this.db.ready()
  }

  compact(opts) {
    return this.db.compactRange(opts)
  }

  async audit() {
    for await (const { core } of this.createCoreStream()) {
      const coreRx = new CoreRX(core, this.db, EMPTY)
      const authPromise = coreRx.getAuth()

      coreRx.tryFlush()

      const auth = await authPromise

      if (!auth.manifest || auth.manifest.version > 0) continue
      if (auth.manifest.linked === null) continue

      auth.manifest.linked = null
      const coreTx = new CoreTX(core, this.db, null, [])
      coreTx.setAuth(auth)
      await coreTx.flush()
    }
  }

  async deleteCore(ptr) {
    const rx = new CoreRX(ptr, this.db, EMPTY)

    const authPromise = rx.getAuth()
    const sessionsPromise = rx.getSessions()

    rx.tryFlush()

    const auth = await authPromise
    const sessions = await sessionsPromise

    // no core stored here
    if (!auth) return

    const tx = this.db.write({ autoDestroy: true })

    tx.tryDelete(store.core(auth.discoveryKey))

    // clear core
    const start = core.core(ptr.corePointer)
    const end = core.core(ptr.corePointer + 1)
    tx.tryDeleteRange(start, end)

    if (sessions) {
      for (const { dataPointer } of sessions) {
        const start = core.data(dataPointer)
        const end = core.data(dataPointer + 1)
        tx.tryDeleteRange(start, end)
      }
    }

    return tx.flush()
  }

  static isCoreStorage(db) {
    return isCorestoreStorage(db)
  }

  static from(db) {
    if (isCorestoreStorage(db)) return db
    return new this(db)
  }

  async _flush() {
    while (this.enters > 0) {
      await this.lock.lock()
      await this.lock.unlock()
    }
  }

  async _maybeRecover(view) {
    const r = path.join(this.db.path, 'RECOVERING')
    if (!(await fileExists(r))) return
    await this.setRecovering(view)
    await fs.promises.unlink(r)
  }

  _maybeRecoverLater(err) {
    if (!shouldRecover(err) && !this.alwaysRecover) return
    fs.writeFileSync(path.join(this.db.path, 'RECOVERING'), '')
    for (const file of fs.readdirSync(this.db.path)) {
      if (file.endsWith('.log')) fs.unlinkSync(path.join(this.db.path, file))
    }
  }

  // runs pre any other mutation and read
  async _migrateStore() {
    const view = await this._enter()

    try {
      if (this.version === VERSION) return

      await this._preopen

      try {
        await this.db.ready()
      } catch (err) {
        this._maybeRecoverLater(err)
        throw err
      }

      await this._maybeRecover(view)

      const rx = new CorestoreRX(this.db, view)
      const headPromise = rx.getHead()

      rx.tryFlush()
      const head = await headPromise

      const version = head === null ? 0 : head.version
      if (version === VERSION) {
        this.version = VERSION
        return
      }

      const target = { version: VERSION, dryRun: false }

      switch (version) {
        case 0: {
          await require('./migrations/0').store(this, target)
          break
        }
        case 1: {
          // implicit migration on write
          break
        }
        default: {
          throw new Error(
            'Unsupported version: ' + version + ' - you should probably upgrade your dependencies'
          )
        }
      }

      this.version = VERSION
    } finally {
      await this._exit()
    }
  }

  // runs pre the core is returned to the user
  async _migrateCore(core, discoveryKey, version, locked) {
    const view = locked ? this.view : await this._enter()
    try {
      if (version === VERSION) return

      const target = { version: VERSION, dryRun: false }

      switch (version) {
        case 0: {
          await require('./migrations/0').core(core, target)
          break
        }
        case 1: {
          await require('./migrations/1').core(core, target)
          break
        }
        default: {
          throw new Error(
            'Unsupported version: ' + version + ' - you should probably upgrade your dependencies'
          )
        }
      }

      if (locked === false) return

      // if its locked, then move the core state into the memview
      // in case the core is reopened from the memview, pre flush

      const rx = new CorestoreRX(this.db, EMPTY)
      const tx = new CorestoreTX(view)

      const corePromise = rx.getCore(discoveryKey)
      rx.tryFlush()

      tx.putCore(discoveryKey, await corePromise)
      tx.apply()
    } finally {
      if (!locked) await this._exit()
    }
  }

  async _enter() {
    this.enters++
    await this.lock.lock()
    if (this.view === null) this.view = new View()
    return this.view
  }

  async _exit() {
    this.enters--

    if (this.flushing === null) this.flushing = rrp()
    const flushed = this.flushing.promise

    if (this.enters === 0 || this.view.size() > 128) {
      try {
        await View.flush(this.view.changes, this.db)
        this.flushing.resolve()
      } catch (err) {
        this.flushing.reject(err)
      } finally {
        this.flushing = null
        this.view = null
      }
    }

    this.lock.unlock()
    return flushed
  }

  // when used with core catches this isnt transactional for simplicity, HOWEVER, its just a number
  // so worth the tradeoff
  async _allocData() {
    let dataPointer = 0

    const view = await this._enter()
    const tx = new CorestoreTX(view)

    try {
      const head = await this._getHead(view)

      dataPointer = head.datas++

      tx.setHead(head)
      tx.apply()
    } finally {
      await this._exit()
    }

    return dataPointer
  }

  // exposes here so migrations can easily access the head in an init state
  async _getHead(view) {
    const rx = new CorestoreRX(this.db, view)
    const headPromise = rx.getHead()
    rx.tryFlush()

    const head = await headPromise
    return head === null ? initStoreHead() : head
  }

  createAtom() {
    return new Atom(this.db)
  }

  async flush() {
    await this.rocks.flush()
  }

  async close() {
    if (this.db.closed) return
    await this._flush()
    await this.db.close()
    await this.rocks.close()
    if (this.deviceFile) await this.deviceFile.close()
  }

  async clear() {
    if (this.version === 0) await this._migrateStore()

    const view = await this._enter()
    const tx = new CorestoreTX(view)

    tx.clear()
    tx.apply()

    await this._exit()
  }

  createCoreStream() {
    // TODO: be nice to run the mgiration here also, but too much plumbing atm
    return createCoreStream(this.db, EMPTY)
  }

  createAliasStream(namespace) {
    // TODO: be nice to run the mgiration here also, but too much plumbing atm
    return createAliasStream(this.db, EMPTY, namespace)
  }

  createDiscoveryKeyStream(namespace) {
    return createDiscoveryKeyStream(this.db, EMPTY, namespace)
  }

  createGroupUpdateStream(group, { since, reverse } = {}) {
    return createGroupUpdateStream(this.db, EMPTY, group, { gte: since, reverse })
  }

  async getAlias(alias) {
    if (this.version === 0) await this._migrateStore()

    const rx = new CorestoreRX(this.db, EMPTY)
    const discoveryKeyPromise = rx.getCoreByAlias(alias)
    rx.tryFlush()
    return discoveryKeyPromise
  }

  async getSeed() {
    if (this.version === 0) await this._migrateStore()

    const rx = new CorestoreRX(this.db, EMPTY)
    const headPromise = rx.getHead()

    rx.tryFlush()

    const head = await headPromise
    return head === null ? null : head.seed
  }

  async getGroup(topic) {
    if (this.version === 0) await this._migrateStore()

    const rx = new CorestoreRX(this.db, EMPTY)
    const groupPromise = rx.getGroup(topic)
    rx.tryFlush()
    return groupPromise
  }

  async setSeed(seed, { overwrite = true } = {}) {
    if (this.version === 0) await this._migrateStore()

    const view = await this._enter()
    const tx = new CorestoreTX(view)

    try {
      const rx = new CorestoreRX(this.db, view)
      const headPromise = rx.getHead()

      rx.tryFlush()

      const head = (await headPromise) || initStoreHead()

      if (head.seed === null || overwrite) head.seed = seed
      tx.setHead(head)
      tx.apply()

      return head.seed
    } finally {
      await this._exit()
    }
  }

  async setRecovering(view = null) {
    const recovering = Date.now()
    let keys = []

    for await (const data of this.createDiscoveryKeyStream()) {
      keys.push(data)
      if (keys.length > 16 * 1024) {
        await this._flushRecovering(recovering, view, keys)
        keys = []
      }
    }

    if (keys.length) await this._flushRecovering(recovering, view, keys)
  }

  async _flushRecovering(recovering, view, keys) {
    const own = !view
    if (own) view = await this._enter()
    const tx = new CorestoreTX(view)

    try {
      const infos = await this._getInfos(keys, { auth: false, head: false, hints: true })
      for (const inf of infos) {
        tx.setCoreHints(inf.core, { ...inf.hints, recovering })
      }
      tx.apply()
    } finally {
      if (own) await this._exit()
    }
  }

  async getDefaultDiscoveryKey() {
    if (this.version === 0) await this._migrateStore()

    const rx = new CorestoreRX(this.db, EMPTY)
    const headPromise = rx.getHead()

    rx.tryFlush()

    const head = await headPromise
    return head === null ? null : head.defaultDiscoveryKey
  }

  async setDefaultDiscoveryKey(discoveryKey, { overwrite = true } = {}) {
    if (this.version === 0) await this._migrateStore()

    const view = await this._enter()
    const tx = new CorestoreTX(view)

    try {
      const rx = new CorestoreRX(this.db, view)
      const headPromise = rx.getHead()

      rx.tryFlush()

      const head = (await headPromise) || initStoreHead()

      if (head.defaultDiscoveryKey === null || overwrite) head.defaultDiscoveryKey = discoveryKey
      tx.setHead(head)
      tx.apply()

      return head.defaultDiscoveryKey
    } finally {
      await this._exit()
    }
  }

  async hasCore(discoveryKey, { ifMigrated = false } = {}) {
    if (this.version === 0) await this._migrateStore()

    const rx = new CorestoreRX(this.db, EMPTY)
    const promise = rx.getCore(discoveryKey)

    rx.tryFlush()

    const core = await promise

    if (core === null) return false
    if (core.version !== VERSION && ifMigrated) return false

    return true
  }

  async getAuth(discoveryKey) {
    if (this.version === 0) await this._migrateStore()

    const rx = new CorestoreRX(this.db, EMPTY)
    const corePromise = rx.getCore(discoveryKey)

    rx.tryFlush()

    const core = await corePromise
    if (core === null) return null

    const read = this.db.read({ autoDestroy: true })
    const authPromise = CoreRX.getAuth(read, core)

    read.tryFlush()

    return authPromise
  }

  async getInfo(discoveryKey, opts) {
    return (await this.getInfos([discoveryKey], opts))[0]
  }

  async getInfos(discoveryKeys, opts) {
    if (this.version === 0) await this._migrateStore()
    return this._getInfos(discoveryKeys, opts)
  }

  async _getInfos(discoveryKeys, { auth = true, head = true, hints = true } = {}) {
    const rx = new CorestoreRX(this.db, EMPTY)
    const corePromises = new Array(discoveryKeys.length)

    for (let i = 0; i < discoveryKeys.length; i++) {
      corePromises[i] = rx.getCore(discoveryKeys[i])
    }

    rx.tryFlush()

    const cores = await Promise.all(corePromises)
    const read = this.db.read({ autoDestroy: true })

    const resultPromises = new Array(cores.length)

    for (let i = 0; i < cores.length; i++) {
      resultPromises[i] = cores[i]
        ? getInfoFromBatch(read, cores[i], discoveryKeys[i], auth, head, hints)
        : null
    }

    read.tryFlush()

    return Promise.all(resultPromises)
  }

  async suspend({ log = noop } = {}) {
    await log('Suspending hypercore storage...')
    await this.db.suspend()
    await log('db suspended')
    if (this.deviceFile) {
      await log('suspend deviceFile')
      await this.deviceFile.suspend()
      await log('deviceFile suspended')
    }
  }

  async resume() {
    if (this.deviceFile) await this.deviceFile.resume()
    await this.db.resume()
  }

  async resumeCore(discoveryKey) {
    if (this.version === 0) await this._migrateStore()

    if (!discoveryKey) {
      discoveryKey = await this.getDefaultDiscoveryKey()
      if (!discoveryKey) return null
    }

    const rx = new CorestoreRX(this.db, EMPTY)
    const corePromise = rx.getCore(discoveryKey)

    rx.tryFlush()
    const core = await corePromise

    if (core === null) return null
    return this._resumeFromPointers(EMPTY, discoveryKey, false, core)
  }

  async export(discoveryKey, opts) {
    const rx = new CorestoreRX(this.db, EMPTY)
    const corePromise = rx.getCore(discoveryKey)

    rx.tryFlush()
    const core = await corePromise
    if (core === null) return null

    let { dataPointer, corePointer } = core

    const ptr = { corePointer, dataPointer, dependencies: [] }

    while (true) {
      const rx = new CoreRX({ dataPointer, corePointer: 0, dependencies: [] }, this.db, EMPTY)
      const dependencyPromise = rx.getDependency()
      rx.tryFlush()
      const dependency = await dependencyPromise
      if (!dependency) break
      ptr.dependencies.push(dependency)
      dataPointer = dependency.dataPointer
    }

    const session = this.db.session()
    try {
      return await HypercoreStorage.export(ptr, session, opts)
    } finally {
      await session.close()
    }
  }

  async _resumeFromPointers(view, discoveryKey, create, { version, corePointer, dataPointer }) {
    const core = { corePointer, dataPointer, dependencies: [] }

    while (true) {
      const rx = new CoreRX({ dataPointer, corePointer: 0, dependencies: [] }, this.db, view)
      const dependencyPromise = rx.getDependency()
      rx.tryFlush()
      const dependency = await dependencyPromise
      if (!dependency) break
      core.dependencies.push(dependency)
      dataPointer = dependency.dataPointer
    }

    const result = new HypercoreStorage(this, this.db.session(), core, EMPTY, null)

    if (version < VERSION) await this._migrateCore(result, discoveryKey, version, create)
    return result
  }

  // not allowed to throw validation errors as its a shared tx!
  async _create(
    view,
    { key, manifest, keyPair, encryptionKey, discoveryKey, alias, userData, core: ptrs }
  ) {
    const rx = new CorestoreRX(this.db, view)
    const tx = new CorestoreTX(view)

    const corePromise = rx.getCore(discoveryKey)
    const headPromise = rx.getHead()

    rx.tryFlush()

    let [core, head] = await Promise.all([corePromise, headPromise])
    if (core) return this._resumeFromPointers(view, discoveryKey, true, core)

    if (head === null) head = initStoreHead()
    if (head.defaultDiscoveryKey === null) head.defaultDiscoveryKey = discoveryKey

    const corePointer = ptrs ? ptrs.corePointer : head.cores++
    const dataPointer = ptrs ? ptrs.dataPointer : head.datas++

    core = { version: VERSION, corePointer, dataPointer, alias }

    tx.setHead(head)
    tx.putCore(discoveryKey, core)
    if (alias) tx.putCoreByAlias(alias, discoveryKey)

    const ptr = { corePointer, dataPointer, dependencies: [] }
    const ctx = new CoreTX(ptr, this.db, view, tx.changes)

    ctx.setAuth({
      key,
      discoveryKey,
      manifest,
      keyPair,
      encryptionKey
    })

    if (userData) {
      for (const { key, value } of userData) {
        ctx.putUserData(key, value)
      }
    }

    tx.apply()

    return new HypercoreStorage(this, this.db.session(), ptr, EMPTY, null)
  }

  async createCore(data) {
    if (this.version === 0) await this._migrateStore()

    const view = await this._enter()

    try {
      return await this._create(view, data)
    } finally {
      await this._exit()
    }
  }

  // not allowed to throw validation errors as its a shared tx!
  async _createGroup(view, topic) {
    const rx = new CorestoreRX(this.db, view)
    const tx = new CorestoreTX(view)

    const groupPromise = rx.getGroup(topic)
    const headPromise = rx.getHead()

    rx.tryFlush()

    let group = await groupPromise
    if (group !== null) {
      return {
        key: topic,
        pointer: group
      }
    }

    let head = await headPromise
    if (head === null) head = initStoreHead()

    group = head.groups++

    tx.putGroup(topic, group)
    tx.setHead(head)
    tx.apply()

    return {
      key: topic,
      pointer: group
    }
  }

  async createGroup(topic) {
    if (this.version === 0) await this._migrateStore()

    const view = await this._enter()

    try {
      return await this._createGroup(view, topic)
    } finally {
      await this._exit()
    }
  }

  static isParentStorage(storage, parent) {
    return HypercoreStorage.isParentStorage(storage, parent)
  }
}

module.exports = CorestoreStorage

function initStoreHead() {
  return {
    version: 0, // cause we wanna run the migration
    datas: 0,
    cores: 0,
    groups: 0,
    seed: null,
    defaultDiscoveryKey: null
  }
}

function getSession(sessions, name, alloc) {
  for (let i = 0; i < sessions.length; i++) {
    if (sessions[i].name === name) return sessions[i]
  }

  if (!alloc) return null

  const result = { name, dataPointer: -1 }
  sessions.push(result)
  return result
}

function isCorestoreStorage(s) {
  return typeof s === 'object' && !!s && typeof s.setDefaultDiscoveryKey === 'function'
}

function createColumnFamily(db, opts = {}) {
  const {
    tableCacheIndexAndFilterBlocks = true,
    blockCache = true,
    optimizeFiltersForMemory = false,
    blobFileSize = 256 * 1024 * 1024,
    blobGarbageCollectionAgeCutOff = 0.25,
    blobGarbageCollectionForceThreshold = 1.0
  } = opts

  const col = new RocksDB.ColumnFamily(COLUMN_FAMILY, {
    enableBlobFiles: true,
    minBlobSize: 4096,
    blobFileSize,
    enableBlobGarbageCollection: true,
    tableBlockSize: 8192,
    tableCacheIndexAndFilterBlocks,
    tableFormatVersion: 6,
    optimizeFiltersForMemory,
    blockCache,
    blobGarbageCollectionAgeCutOff,
    blobGarbageCollectionForceThreshold
  })

  return db.columnFamily(col)
}

function getLastDependency(storage) {
  return storage.dependencies.length ? storage.dependencies[storage.dependencies.length - 1] : null
}

// TODO: remove in like 3-6 mo
function tmpFixStorage(p) {
  // if CORESTORE file is written, new format
  if (fs.existsSync(path.join(p, 'CORESTORE'))) return

  let files = []

  try {
    files = fs.readdirSync(p)
  } catch {}

  const notRocks = new Set([
    'CORESTORE',
    'primary-key',
    'cores',
    'app-preferences',
    'cache',
    'preferences.json',
    'db',
    'clone',
    'core',
    'notifications'
  ])

  for (const f of files) {
    if (notRocks.has(f)) continue

    try {
      fs.mkdirSync(path.join(p, 'db'))
    } catch {}

    fs.renameSync(path.join(p, f), path.join(p, 'db', f))
  }
}

async function exportData(ptr, db, opts) {
  // just need dataPointer
  const reads = [
    toArray(createBlockStream(ptr, db, EMPTY, opts)),
    toArray(createTreeNodeStream(ptr, db, EMPTY, opts)),
    toArray(createBitfieldStream(ptr, db, EMPTY, opts))
  ]

  const [blocks, tree, bitfield] = await Promise.all(reads)

  return {
    blocks,
    tree,
    bitfield
  }
}

async function toArray(stream) {
  const all = []
  for await (const e of stream) all.push(e)
  return all
}

async function fileExists(path) {
  try {
    await fs.promises.stat(path)
    return true
  } catch {
    return false
  }
}

function shouldRecover(err) {
  return err.message.toLowerCase().indexOf('sequence number is being set backwards') > -1
}

function noop() {}

async function getInfoFromBatch(db, c, discoveryKey, getAuth, getHead, getHints) {
  const authPromise = getAuth ? CoreRX.getAuth(db, c) : null
  const headPromise = getHead ? CoreRX.getHead(db, c) : null
  const hintsPromise = getHints ? CoreRX.getHints(db, c) : null

  // ensure no uncaughts
  if (authPromise) authPromise.catch(noop)
  if (headPromise) headPromise.catch(noop)
  if (hintsPromise) hintsPromise.catch(noop)

  return {
    discoveryKey,
    core: c,
    auth: await authPromise,
    head: await headPromise,
    hints: await hintsPromise
  }
}
const { Readable, getStreamError } = require('streamx')
const { core } = require('./keys')

module.exports = class BlockStream extends Readable {
  constructor(core, db, updates, start, end, reverse) {
    super()

    this.core = core
    this.db = db
    this.updates = updates
    this.start = start
    this.end = end
    this.reverse = reverse === true

    this._drained = true
    this._consumed = 0
    this._stream = null
    this._oncloseBound = this._onclose.bind(this)
    this._maybeDrainBound = this._maybeDrain.bind(this)

    this._update()
  }

  _update() {
    if (this._consumed > this.core.dependencies.length) return

    const deps = this.core.dependencies
    const index = this._findDependencyIndex(deps)

    const curr = index < deps.length ? deps[index] : null
    const prev = index > 0 && index - 1 < deps.length ? deps[index - 1] : null

    const start = prev && prev.length > this.start ? prev.length : this.start
    const end = curr && (this.end === -1 || curr.length < this.end) ? curr.length : this.end

    const ptr = curr ? curr.dataPointer : this.core.dataPointer

    this._makeStream(core.block(ptr, start), core.block(ptr, end))
  }

  _findDependencyIndex(deps) {
    if (!this.reverse) return this._consumed++

    let i = deps.length - this._consumed++
    while (i > 0) {
      if (deps[i - 1].length <= this.end) return i
      i--
      this._consumed++
    }

    return 0
  }

  _predestroy() {
    if (this._stream !== null) this._stream.destroy()
  }

  _read(cb) {
    this._drained = this._onreadable()
    cb(null)
  }

  _maybeDrain() {
    if (this._drained === true) return
    this._drained = this._onreadable()
  }

  _onreadable() {
    if (this._stream === null) {
      this.push(null)
      return true
    }

    let data = this._stream.read()

    if (data === null) return false

    do {
      this.push(data)
      data = this._stream.read()
    } while (data !== null)

    return true
  }

  _onclose() {
    if (this.destroying) return

    const err = getStreamError(this._stream)

    if (err !== null) {
      this.destroy(err)
      return
    }

    // empty the current stream
    if (this._onreadable() === true) this._drained = true

    this._stream = null

    this._update()
    this._maybeDrain()
  }

  _makeStream(start, end) {
    this._stream = this.updates.iterator(this.db, start, end, this.reverse)
    this._stream.on('readable', this._maybeDrainBound)
    this._stream.on('error', noop)
    this._stream.on('close', this._oncloseBound)
  }
}

function noop() {}
const { Readable } = require('streamx')

// used for returned a stream that just errors (during read during teardown)

module.exports = class CloseErrorStream extends Readable {
  constructor(err) {
    super()
    this.error = err
  }

  _open(cb) {
    cb(this.error)
  }
}
const { UINT, STRING } = require('index-encoder')
const c = require('compact-encoding')
const b4a = require('b4a')

const TL_HEAD = 0
const TL_CORE_BY_DKEY = 1
const TL_CORE_BY_ALIAS = 2
const TL_CORE = 3
const TL_DATA = 4
const TL_GROUP_BY_TOPIC = 5
const TL_GROUP = 6

const TL_END = TL_GROUP + 1

const CORE_AUTH = 0
const CORE_SESSIONS = 1
const CORE_GROUP = 2

const DATA_HEAD = 0
const DATA_DEPENDENCY = 1
const DATA_HINTS = 2
const DATA_BLOCK = 3
const DATA_TREE = 4
const DATA_BITFIELD = 5
const DATA_USER_DATA = 6
const DATA_LOCAL = 7
const DATA_MARK = 8

const slab = { buffer: b4a.allocUnsafe(65536), start: 0, end: 0 }

const store = {}
const core = {}
const group = {}

store.clear = function () {
  const state = alloc()
  let start = state.start
  UINT.encode(state, 0)
  const a = state.buffer.subarray(start, state.start)
  start = state.start
  UINT.encode(state, TL_END)
  const b = state.buffer.subarray(start, state.start)
  return [a, b]
}

store.head = function () {
  const state = alloc()
  const start = state.start
  UINT.encode(state, TL_HEAD)
  return state.buffer.subarray(start, state.start)
}

store.core = function (discoveryKey) {
  const state = alloc()
  const start = state.start
  UINT.encode(state, TL_CORE_BY_DKEY)
  c.fixed32.encode(state, discoveryKey)
  return state.buffer.subarray(start, state.start)
}

store.coreStart = function () {
  const state = alloc()
  const start = state.start
  UINT.encode(state, TL_CORE_BY_DKEY)
  return state.buffer.subarray(start, state.start)
}

store.coreEnd = function () {
  const state = alloc()
  const start = state.start
  UINT.encode(state, TL_CORE_BY_DKEY + 1)
  return state.buffer.subarray(start, state.start)
}

store.coreByAlias = function ({ namespace, name }) {
  const state = alloc()
  const start = state.start
  UINT.encode(state, TL_CORE_BY_ALIAS)
  c.fixed32.encode(state, namespace)
  STRING.encode(state, name)
  return state.buffer.subarray(start, state.start)
}

store.coreByAliasStart = function (namespace) {
  const state = alloc()
  const start = state.start
  UINT.encode(state, TL_CORE_BY_ALIAS)
  if (namespace) c.fixed32.encode(state, namespace)
  return state.buffer.subarray(start, state.start)
}

store.coreByAliasEnd = function (namespace) {
  const state = alloc()
  const start = state.start

  if (namespace) {
    UINT.encode(state, TL_CORE_BY_ALIAS)
    c.fixed32.encode(state, namespace)
    state.buffer[state.start++] = 0xff
  } else {
    UINT.encode(state, TL_CORE_BY_ALIAS + 1)
  }

  return state.buffer.subarray(start, state.start)
}

store.alias = function (buffer) {
  const state = { buffer, start: 0, end: buffer.byteLength }
  UINT.decode(state) // ns
  const namespace = c.fixed32.decode(state)
  const name = STRING.decode(state)
  return { namespace, name }
}

store.discoveryKey = function (buffer) {
  const state = { buffer, start: 0, end: buffer.byteLength }
  UINT.decode(state) // ns
  return c.fixed32.decode(state)
}

core.core = function (ptr) {
  const state = alloc()
  const start = state.start
  UINT.encode(state, TL_CORE)
  UINT.encode(state, ptr)
  return state.buffer.subarray(start, state.start)
}

core.data = function (ptr) {
  const state = alloc()
  const start = state.start
  UINT.encode(state, TL_DATA)
  UINT.encode(state, ptr)
  return state.buffer.subarray(start, state.start)
}

core.auth = function (ptr) {
  const state = alloc()
  const start = state.start
  UINT.encode(state, TL_CORE)
  UINT.encode(state, ptr)
  UINT.encode(state, CORE_AUTH)
  return state.buffer.subarray(start, state.start)
}

core.group = function (ptr) {
  const state = alloc()
  const start = state.start
  UINT.encode(state, TL_CORE)
  UINT.encode(state, ptr)
  UINT.encode(state, CORE_GROUP)
  return state.buffer.subarray(start, state.start)
}

core.sessions = function (ptr) {
  const state = alloc()
  const start = state.start
  UINT.encode(state, TL_CORE)
  UINT.encode(state, ptr)
  UINT.encode(state, CORE_SESSIONS)
  return state.buffer.subarray(start, state.start)
}

core.head = function (ptr) {
  const state = alloc()
  const start = state.start
  UINT.encode(state, TL_DATA)
  UINT.encode(state, ptr)
  UINT.encode(state, DATA_HEAD)
  return state.buffer.subarray(start, state.start)
}

core.dependency = function (ptr) {
  const state = alloc()
  const start = state.start
  UINT.encode(state, TL_DATA)
  UINT.encode(state, ptr)
  UINT.encode(state, DATA_DEPENDENCY)
  return state.buffer.subarray(start, state.start)
}

core.hints = function (ptr) {
  const state = alloc()
  const start = state.start
  UINT.encode(state, TL_DATA)
  UINT.encode(state, ptr)
  UINT.encode(state, DATA_HINTS)
  return state.buffer.subarray(start, state.start)
}

core.block = function (ptr, index) {
  const state = alloc()
  const start = state.start
  UINT.encode(state, TL_DATA)
  UINT.encode(state, ptr)
  UINT.encode(state, DATA_BLOCK)
  UINT.encode(state, index)
  return state.buffer.subarray(start, state.start)
}

core.tree = function (ptr, index) {
  const state = alloc()
  const start = state.start
  UINT.encode(state, TL_DATA)
  UINT.encode(state, ptr)
  UINT.encode(state, DATA_TREE)
  UINT.encode(state, index)
  return state.buffer.subarray(start, state.start)
}

core.bitfield = function (ptr, index, type) {
  const state = alloc()
  const start = state.start
  UINT.encode(state, TL_DATA)
  UINT.encode(state, ptr)
  UINT.encode(state, DATA_BITFIELD)
  UINT.encode(state, index)
  UINT.encode(state, type)
  return state.buffer.subarray(start, state.start)
}

core.userData = function (ptr, key) {
  const state = alloc()
  const start = state.start
  UINT.encode(state, TL_DATA)
  UINT.encode(state, ptr)
  UINT.encode(state, DATA_USER_DATA)
  STRING.encode(state, key)
  return state.buffer.subarray(start, state.start)
}

core.userDataEnd = function (ptr) {
  const state = alloc()
  const start = state.start
  UINT.encode(state, TL_DATA)
  UINT.encode(state, ptr)
  UINT.encode(state, DATA_USER_DATA + 1)
  return state.buffer.subarray(start, state.start)
}

core.mark = function (ptr, index) {
  const state = alloc()
  const start = state.start
  UINT.encode(state, TL_DATA)
  UINT.encode(state, ptr)
  UINT.encode(state, DATA_MARK)
  UINT.encode(state, index)
  return state.buffer.subarray(start, state.start)
}

core.local = function (ptr, key) {
  if (key.byteLength > 2048) {
    throw new Error('local keys has an upper limit of 2048 bytes atm')
  }

  const state = alloc()
  const start = state.start
  UINT.encode(state, TL_DATA)
  UINT.encode(state, ptr)
  UINT.encode(state, DATA_LOCAL)

  state.buffer.set(key, state.start)
  state.start += key.byteLength
  return state.buffer.subarray(start, state.start)
}

core.localEnd = function (ptr) {
  const state = alloc()
  const start = state.start
  UINT.encode(state, TL_DATA)
  UINT.encode(state, ptr)
  UINT.encode(state, DATA_LOCAL + 1)
  return state.buffer.subarray(start, state.start)
}

core.blockIndex = function (buffer) {
  const state = { buffer, start: 0, end: buffer.byteLength }
  UINT.decode(state) // ns
  UINT.decode(state) // ptr
  UINT.decode(state) // type
  return UINT.decode(state)
}

core.bitfieldIndexAndType = function (buffer) {
  const state = { buffer, start: 0, end: buffer.byteLength }
  UINT.decode(state) // ns
  UINT.decode(state) // ptr
  UINT.decode(state) // type
  return [UINT.decode(state), UINT.decode(state)]
}

core.userDataKey = function (buffer) {
  const state = { buffer, start: 0, end: buffer.byteLength }
  UINT.decode(state) // ns
  UINT.decode(state) // ptr
  UINT.decode(state) // type
  return STRING.decode(state)
}

core.markKey = function (buffer) {
  const state = { buffer, start: 0, end: buffer.byteLength }
  UINT.decode(state) // ns
  UINT.decode(state) // ptr
  UINT.decode(state) // type
  return UINT.decode(state)
}

core.localKey = function (buffer) {
  const state = { buffer, start: 0, end: buffer.byteLength }
  UINT.decode(state) // ns
  UINT.decode(state) // ptr
  UINT.decode(state) // type
  return state.buffer.subarray(state.start, state.end)
}

group.topic = function (topic) {
  const state = alloc()
  const start = state.start
  UINT.encode(state, TL_GROUP_BY_TOPIC)
  c.fixed32.encode(state, topic)
  return state.buffer.subarray(start, state.start)
}

group.entry = function (group, timestamp, ptr) {
  const state = alloc()
  const start = state.start
  UINT.encode(state, TL_GROUP)
  UINT.encode(state, group)
  c.uint64be.encode(state, timestamp)
  UINT.encode(state, ptr)
  return state.buffer.subarray(start, state.start)
}

module.exports = { store, core, group }

function alloc() {
  if (slab.buffer.byteLength - slab.start < 4096) {
    slab.buffer = b4a.allocUnsafe(slab.buffer.byteLength)
    slab.start = 0
  }
  return slab
}
const b4a = require('b4a')
const BlockDependencyStream = require('./block-dependency-stream.js')
const { core, store, group } = require('./keys.js')
const schema = require('../encoding/spec/hyperschema')

const CORESTORE_CORE = schema.getEncoding('@corestore/core')
const CORE_TREE_NODE = schema.getEncoding('@core/tree-node')
const EMPTY = b4a.alloc(0)

module.exports = {
  createBlockStream,
  createBitfieldStream,
  createUserDataStream,
  createCoreStream,
  createAliasStream,
  createDiscoveryKeyStream,
  createTreeNodeStream,
  createMarkStream,
  createLocalStream,
  createGroupUpdateStream
}

function createCoreStream(db, view) {
  const start = store.coreStart()
  const end = store.coreEnd()

  const ite = view.iterator(db, start, end, false)

  ite._readableState.map = mapCore
  return ite
}

function createDiscoveryKeyStream(db, view, namespace) {
  const start = namespace ? store.coreByAliasStart(namespace) : store.coreStart()
  const end = namespace ? store.coreByAliasEnd(namespace) : store.coreEnd()

  const ite = view.iterator(db, start, end, false)

  ite._readableState.map = namespace ? mapNamespaceDiscoveryKeys : mapAllDiscoveryKeys
  return ite
}

function createAliasStream(db, view, namespace) {
  const start = store.coreByAliasStart(namespace)
  const end = store.coreByAliasEnd(namespace)

  const ite = view.iterator(db, start, end, false)

  ite._readableState.map = mapAlias
  return ite
}

function createGroupUpdateStream(db, view, groupID, { gte = 0, reverse = true } = {}) {
  const start = group.entry(groupID, gte, 0)
  const end = group.entry(groupID + 1, 0, 0)

  const ite = view.iterator(db, start, end, reverse)

  ite._readableState.map = mapGroupUpdate
  return ite
}

function createBlockIterator(ptr, db, view, start, end, reverse) {
  if (ptr.dependencies.length > 0) {
    return new BlockDependencyStream(ptr, db, view, start, end, reverse)
  }

  const s = core.block(ptr.dataPointer, start)
  const e = core.block(ptr.dataPointer, end === -1 ? Infinity : end)
  return view.iterator(db, s, e, reverse)
}

function createBlockStream(
  ptr,
  db,
  view,
  { gt = -1, gte = gt + 1, lte = -1, lt = lte === -1 ? -1 : lte + 1, reverse = false } = {}
) {
  const ite = createBlockIterator(ptr, db, view, gte, lt, reverse)

  ite._readableState.map = mapBlock
  return ite
}

function createBitfieldStream(
  ptr,
  db,
  view,
  { gt = -1, gte = gt + 1, lte = -1, lt = lte === -1 ? -1 : lte + 1, reverse = false } = {}
) {
  const s = core.bitfield(ptr.dataPointer, gte, 0)
  const e = core.bitfield(ptr.dataPointer, lt === -1 ? Infinity : lt, 0)
  const ite = view.iterator(db, s, e, false)

  ite._readableState.map = mapBitfield
  return ite
}

// NOTE: this does not do dependency lookups atm
function createTreeNodeStream(
  ptr,
  db,
  view,
  { gt = -1, gte = gt + 1, lte = -1, lt = lte === -1 ? -1 : lte + 1, reverse = false } = {}
) {
  const s = core.tree(ptr.dataPointer, gte, 0)
  const e = core.tree(ptr.dataPointer, lt === -1 ? Infinity : lt, 0)
  const ite = view.iterator(db, s, e, false)

  ite._readableState.map = mapTreeNode
  return ite
}

function createUserDataStream(
  ptr,
  db,
  view,
  { gt = null, gte = '', lte = null, lt = null, reverse = false } = {}
) {
  if (gt !== null || lte !== null) {
    throw new Error('gt and lte not yet supported for user data streams')
  }

  const s = core.userData(ptr.dataPointer, gte)
  const e = lt === null ? core.userDataEnd(ptr.dataPointer) : core.userData(ptr.dataPointer, lt)
  const ite = view.iterator(db, s, e, false)

  ite._readableState.map = mapUserData
  return ite
}

function createMarkStream(
  ptr,
  db,
  view,
  { gt = -1, gte = gt + 1, lte = -1, lt = lte === -1 ? -1 : lte + 1, reverse = false } = {}
) {
  const s = core.mark(ptr.dataPointer, gte)
  const e = core.mark(ptr.dataPointer, lt === -1 ? Infinity : lt)
  const ite = view.iterator(db, s, e, reverse)

  ite._readableState.map = mapMark
  return ite
}

function createLocalStream(
  ptr,
  db,
  view,
  { gt = null, gte = EMPTY, lte = null, lt = null, reverse = false } = {}
) {
  if (gt !== null || lte !== null) throw new Error('gt and lte not yet supported for local streams')

  const s = core.local(ptr.dataPointer, gte)
  const e = lt === null ? core.localEnd(ptr.dataPointer) : core.local(ptr.dataPointer, lt)
  const ite = view.iterator(db, s, e, false)

  ite._readableState.map = mapLocal
  return ite
}

function mapBitfield(data) {
  const [index, type] = core.bitfieldIndexAndType(data.key)
  if (type !== 0) return null // ignore for now
  return { index, page: data.value }
}

function mapMark(data) {
  const index = core.markKey(data.key)
  return { index, page: data.value }
}

function mapLocal(data) {
  const key = core.localKey(data.key)
  return { key, value: data.value }
}

function mapGroupUpdate(data) {
  return data.value
}

function mapUserData(data) {
  const key = core.userDataKey(data.key)
  return { key, value: data.value }
}

function mapCore(data) {
  const discoveryKey = store.discoveryKey(data.key)
  const core = CORESTORE_CORE.decode({
    start: 0,
    end: data.value.byteLength,
    buffer: data.value
  })
  return { discoveryKey, core }
}

function mapAllDiscoveryKeys(data) {
  return store.discoveryKey(data.key)
}

function mapNamespaceDiscoveryKeys(data) {
  return data.value
}

function mapAlias(data) {
  const alias = store.alias(data.key)
  return { alias, discoveryKey: data.value }
}

function mapBlock(data) {
  return { index: core.blockIndex(data.key), value: data.value }
}

function mapTreeNode(data) {
  return CORE_TREE_NODE.decode({
    start: 0,
    end: data.value.byteLength,
    buffer: data.value
  })
}
const b4a = require('b4a')
const c = require('compact-encoding')
const flat = require('flat-tree')

const schema = require('../encoding/spec/hyperschema')
const { store, core, group } = require('./keys.js')
const View = require('./view.js')

const CORESTORE_HEAD = schema.getEncoding('@corestore/head')
const CORESTORE_CORE = schema.getEncoding('@corestore/core')
const CORESTORE_GROUP = c.uint

const CORE_AUTH = schema.getEncoding('@core/auth')
const CORE_GROUP = schema.getEncoding('@core/group')
const CORE_SESSIONS = schema.getEncoding('@core/sessions')
const CORE_HEAD = schema.getEncoding('@core/head')
const CORE_TREE_NODE = schema.getEncoding('@core/tree-node')
const CORE_DEPENDENCY = schema.getEncoding('@core/dependency')
const CORE_HINTS = schema.getEncoding('@core/hints')

class CoreTX {
  constructor(core, db, view, changes) {
    if (db.snapshotted) throw new Error('Cannot open core tx on snapshot')
    this.core = core
    this.db = db
    this.view = view
    this.changes = changes
  }

  setAuth(auth) {
    this.changes.push([core.auth(this.core.corePointer), encode(CORE_AUTH, auth), null])
  }

  setSessions(sessions) {
    this.changes.push([core.sessions(this.core.corePointer), encode(CORE_SESSIONS, sessions), null])
  }

  setHead(head) {
    this.changes.push([core.head(this.core.dataPointer), encode(CORE_HEAD, head), null])
  }

  deleteHead() {
    this.changes.push([core.head(this.core.dataPointer), null, null])
  }

  setDependency(dep) {
    this.changes.push([core.dependency(this.core.dataPointer), encode(CORE_DEPENDENCY, dep), null])
  }

  setGroup(group) {
    this.changes.push([core.group(this.core.dataPointer), encode(CORE_GROUP, group), null])
  }

  deleteGroup() {
    this.changes.push([core.group(this.core.dataPointer), null, null])
  }

  setHints(hints) {
    this.changes.push([core.hints(this.core.dataPointer), encode(CORE_HINTS, hints), null])
  }

  putBlock(index, data) {
    this.changes.push([core.block(this.core.dataPointer, index), data, null])
  }

  deleteBlock(index) {
    this.changes.push([core.block(this.core.dataPointer, index), null, null])
  }

  deleteBlockRange(start, end) {
    this.changes.push([
      core.block(this.core.dataPointer, start),
      null,
      core.block(this.core.dataPointer, end === -1 ? Infinity : end)
    ])
  }

  putBitfieldPage(index, data) {
    this.changes.push([core.bitfield(this.core.dataPointer, index, 0), data, null])
  }

  deleteBitfieldPage(index) {
    this.changes.push([core.bitfield(this.core.dataPointer, index, 0), null, null])
  }

  deleteBitfieldPageRange(start, end) {
    this.changes.push([
      core.bitfield(this.core.dataPointer, start, 0),
      null,
      core.bitfield(this.core.dataPointer, end === -1 ? Infinity : end, 0)
    ])
  }

  putTreeNode(node) {
    this.changes.push([
      core.tree(this.core.dataPointer, node.index),
      encode(CORE_TREE_NODE, node),
      null
    ])
  }

  deleteTreeNode(index) {
    this.changes.push([core.tree(this.core.dataPointer, index), null, null])
  }

  deleteTreeNodeRange(start, end) {
    this.changes.push([
      core.tree(this.core.dataPointer, start),
      null,
      core.tree(this.core.dataPointer, end === -1 ? Infinity : end)
    ])
  }

  putUserData(key, value) {
    const buffer = typeof value === 'string' ? b4a.from(value) : value
    this.changes.push([core.userData(this.core.dataPointer, key), buffer, null])
  }

  deleteUserData(key) {
    this.changes.push([core.userData(this.core.dataPointer, key), null, null])
  }

  putMark(index, data) {
    this.changes.push([core.mark(this.core.dataPointer, index), data, null])
  }

  deleteMark(index) {
    this.changes.push([core.mark(this.core.dataPointer, index), null, null])
  }

  deleteMarkRange(start, end) {
    this.changes.push([
      core.mark(this.core.dataPointer, start),
      null,
      core.mark(this.core.dataPointer, end === -1 ? Infinity : end)
    ])
  }

  putLocal(key, value) {
    this.changes.push([core.local(this.core.dataPointer, key), value, null])
  }

  deleteLocal(key) {
    this.changes.push([core.local(this.core.dataPointer, key), null, null])
  }

  deleteLocalRange(start, end) {
    this.changes.push([
      core.local(this.core.dataPointer, start),
      null,
      end === null ? core.localEnd(this.core.dataPointer) : core.local(this.core.dataPointer, end)
    ])
  }

  putGroupUpdate(groupPtr, timestamp, key) {
    this.changes.push([group.entry(groupPtr, timestamp, this.core.corePointer), key, null])
  }

  deleteGroupUpdate(groupPtr, timestamp) {
    this.changes.push([group.entry(groupPtr, timestamp, this.core.corePointer), null, null])
  }

  flush() {
    const changes = this.changes
    if (changes === null) return Promise.resolve(!this.view)

    this.changes = null

    if (this.view) {
      this.view.apply(changes)
      return Promise.resolve(false)
    }

    return View.flush(changes, this.db)
  }
}

class CoreRX {
  constructor(core, db, view) {
    this.core = core
    this.read = db.read({ autoDestroy: true })
    this.view = view

    view.readStart()
  }

  static async getAuth(db, c) {
    return await decode(CORE_AUTH, await db.get(core.auth(c.corePointer)))
  }

  async getAuth() {
    return await decode(CORE_AUTH, await this.view.get(this.read, core.auth(this.core.corePointer)))
  }

  async getSessions() {
    return await decode(
      CORE_SESSIONS,
      await this.view.get(this.read, core.sessions(this.core.corePointer))
    )
  }

  static async getHead(db, c) {
    return await decode(CORE_HEAD, await db.get(core.head(c.dataPointer)))
  }

  async getHead() {
    return await decode(CORE_HEAD, await this.view.get(this.read, core.head(this.core.dataPointer)))
  }

  async getDependency() {
    return await decode(
      CORE_DEPENDENCY,
      await this.view.get(this.read, core.dependency(this.core.dataPointer))
    )
  }

  async getGroup() {
    return await decode(
      CORE_GROUP,
      await this.view.get(this.read, core.group(this.core.corePointer))
    )
  }

  static async getHints(db, c) {
    return await decode(CORE_HINTS, await db.get(core.hints(c.dataPointer)))
  }

  async getHints() {
    return await decode(
      CORE_HINTS,
      await this.view.get(this.read, core.hints(this.core.dataPointer))
    )
  }

  getBlock(index) {
    const dep = findBlockDependency(this.core.dependencies, index)
    const data = dep === null ? this.core.dataPointer : dep.dataPointer
    return this.view.get(this.read, core.block(data, index))
  }

  getBitfieldPage(index) {
    return this.view.get(this.read, core.bitfield(this.core.dataPointer, index, 0))
  }

  async getTreeNode(index) {
    const dep = findTreeDependency(this.core.dependencies, index)
    const data = dep === null ? this.core.dataPointer : dep.dataPointer
    return decode(CORE_TREE_NODE, await this.view.get(this.read, core.tree(data, index)))
  }

  async hasTreeNode(index) {
    return (await this.getTreeNode(index)) !== null
  }

  getUserData(key) {
    return this.view.get(this.read, core.userData(this.core.dataPointer, key))
  }

  getMark(index) {
    return this.view.get(this.read, core.mark(this.core.dataPointer, index))
  }

  getLocal(key) {
    return this.view.get(this.read, core.local(this.core.dataPointer, key))
  }

  tryFlush() {
    this.read.tryFlush()
    this._free()
  }

  destroy() {
    this.read.destroy()
    this._free()
  }

  _free() {
    if (this.view === null) return
    this.view.readStop()
    this.view = null
  }
}

class CorestoreTX {
  constructor(view) {
    this.view = view
    this.changes = []
  }

  setHead(head) {
    this.changes.push([store.head(), encode(CORESTORE_HEAD, head), null])
  }

  setCoreHints(c, hints) {
    this.changes.push([core.hints(c.dataPointer), encode(CORE_HINTS, hints), null])
  }

  putCore(discoveryKey, ptr) {
    this.changes.push([store.core(discoveryKey), encode(CORESTORE_CORE, ptr), null])
  }

  putCoreByAlias(alias, discoveryKey) {
    this.changes.push([store.coreByAlias(alias), discoveryKey, null])
  }

  putGroup(topic, groupPtr) {
    this.changes.push([group.topic(topic), encode(CORESTORE_GROUP, groupPtr), null])
  }

  deleteGroup(topic) {
    this.changes.push([group.topic(topic), null, null])
  }

  clear() {
    const [start, end] = store.clear()
    this.changes.push([start, null, end])
  }

  apply() {
    if (this.changes === null) return
    this.view.apply(this.changes)
    this.changes = null
  }
}

class CorestoreRX {
  constructor(db, view) {
    this.read = db.read({ autoDestroy: true })
    this.view = view

    view.readStart()
  }

  async getHeadRaw(db) {
    return await this.view.get(this.read, store.head())
  }

  async getHead() {
    return decode(CORESTORE_HEAD, await this.view.get(this.read, store.head()))
  }

  async getCore(discoveryKey) {
    return decode(CORESTORE_CORE, await this.view.get(this.read, store.core(discoveryKey)))
  }

  getCoreByAlias(alias) {
    return this.view.get(this.read, store.coreByAlias(alias))
  }

  async getGroup(topic) {
    return decode(CORESTORE_GROUP, await this.view.get(this.read, group.topic(topic)))
  }

  tryFlush() {
    this.read.tryFlush()
    this._free()
  }

  destroy() {
    this.read.destroy()
    this._free()
  }

  _free() {
    if (this.view === null) return
    this.view.readStop()
    this.view = null
  }
}

module.exports = { CorestoreTX, CorestoreRX, CoreTX, CoreRX }

function findBlockDependency(dependencies, index) {
  for (let i = 0; i < dependencies.length; i++) {
    const dep = dependencies[i]
    if (index < dep.length) return dep
  }

  return null
}

function findTreeDependency(dependencies, index) {
  for (let i = 0; i < dependencies.length; i++) {
    const dep = dependencies[i]
    if (flat.rightSpan(index) <= (dep.length - 1) * 2) return dep
  }

  return null
}

function decode(enc, buffer) {
  if (buffer === null) return null
  return enc.decode({ start: 0, end: buffer.byteLength, buffer })
}

function encode(enc, m) {
  // TODO: use fancy slab for small messages
  const state = { start: 0, end: 0, buffer: null }
  enc.preencode(state, m)
  state.buffer = b4a.allocUnsafe(state.end)
  enc.encode(state, m)
  return state.buffer
}
const { Readable, getStreamError } = require('streamx')
const CloseErrorStream = require('./close-error-stream.js')
const b4a = require('b4a')

class OverlayStream extends Readable {
  constructor(stream, start, end, reverse, changes, cleared) {
    super()

    this.start = start
    this.end = end
    this.reverse = reverse
    this.changes = changes
    this.cleared = cleared
    this.change = 0
    this.range = 0

    this._stream = stream
    this._drained = false

    this._stream.on('readable', this._drainMaybe.bind(this))
    this._stream.on('error', noop)
    this._stream.on('close', this._onclose.bind(this))
  }

  _drainMaybe() {
    if (this._drained === true) return
    this._drained = this._onreadable()
  }

  _onclose() {
    if (this.destroying) return

    const err = getStreamError(this._stream)

    if (err !== null) {
      this.destroy(err)
      return
    }

    while (this.change < this.changes.length) {
      const c = this.changes[this.change++]
      const key = c[0]
      const value = c[1]

      if (value !== null && this._inRange(key)) this.push({ key, value })
    }

    this.push(null)
    this._stream = null
  }

  _onreadable() {
    let data = this._stream.read()
    if (data === null) return false

    let drained = false

    do {
      if (this._push(data) === true) drained = true
      data = this._stream.read()
    } while (data !== null)

    return drained
  }

  _read(cb) {
    this._drained = this._onreadable()
    cb(null)
  }

  _predestroy() {
    this._stream.destroy()
  }

  _push(entry) {
    const key = entry.key

    while (this.range < this.cleared.length) {
      const c = this.cleared[this.range]

      // we moved past the range
      if (this.reverse ? b4a.compare(key, c[0]) < 0 : b4a.compare(c[2], key) <= 0) {
        this.range++
        continue
      }

      // we didnt move past and are in, drop
      if (b4a.compare(c[0], key) <= 0 && b4a.compare(key, c[2]) < 0) {
        return false
      }

      break
    }

    let updated = false

    while (this.change < this.changes.length) {
      const c = this.changes[this.change]
      const key = c[0]
      const value = typeof c[1] === 'string' ? b4a.from(c[1]) : c[1]
      const cmp = b4a.compare(key, entry.key)

      // same value, if not deleted, return new one
      if (cmp === 0) {
        this.change++
        if (value === null || this._inRange(key) === false) return updated
        this.push({ key, value })
        return true
      }

      // we moved past the change, push it
      if (this.reverse ? cmp > 0 : cmp < 0) {
        this.change++
        if (value === null || this._inRange(key) === false) continue
        this.push({ key, value })
        updated = true
        continue
      }

      this.push(entry)
      return true
    }

    this.push(entry)
    return true
  }

  _inRange(key) {
    return b4a.compare(this.start, key) <= 0 && b4a.compare(key, this.end) < 0
  }
}

class Overlay {
  constructor() {
    this.indexed = 0
    this.changes = null
    this.cleared = null
    this.reverse = false
  }

  update(view, reverse) {
    if (view.indexed === this.indexed) return

    const changes = view.map === null ? [] : [...view.map.values()]
    const cleared = view.cleared === null ? [] : view.cleared.slice(0)

    const cmp = reverse ? cmpChangeReverse : cmpChange

    changes.sort(cmp)
    cleared.sort(cmp)

    this.indexed = view.indexed
    this.changes = changes
    this.cleared = cleared
    this.reverse = reverse
  }

  createStream(stream, start, end, reverse) {
    return new OverlayStream(
      stream,
      start,
      end,
      reverse,
      this.reverse === reverse ? this.changes : reverseArray(this.changes),
      this.reverse === reverse ? this.cleared : reverseArray(this.cleared)
    )
  }
}

class View {
  constructor() {
    this.map = null
    this.indexed = 0
    this.changes = null
    this.cleared = null
    this.overlay = null
    this.snap = null
    this.readers = 0
  }

  snapshot() {
    if (this._attached()) return this.snap.snapshot()

    const snap = new View()

    snap.map = this.map
    snap.indexed = this.indexed
    snap.changes = this.changes
    snap.cleared = this.cleared

    if (this._frozen()) return snap

    this.readers++
    snap.snap = this

    return snap
  }

  readStart() {
    if (this.snap !== null) this.readers++
  }

  readStop() {
    if (this.snap !== null && --this.readers === 0) this.snap.readers--
  }

  size() {
    return this.changes === null ? 0 : this.changes.length
  }

  updated() {
    return this.changes === null
  }

  get(read, key) {
    return this.changes === null ? read.get(key) : this._indexAndGet(read, key)
  }

  reset() {
    this.indexed = 0
    this.snap = this.map = this.changes = this.cleared = this.overlay = null
  }

  iterator(db, start, end, reverse) {
    if (dbClosing(db)) return new CloseErrorStream(new Error('RocksDB session is closed'))

    const stream = db.iterator({ gte: start, lt: end, reverse })
    if (this.changes === null) return stream

    this._index()

    if (this.overlay === null) this.overlay = new Overlay()
    this.overlay.update(this, reverse)
    return this.overlay.createStream(stream, start, end, reverse)
  }

  _indexAndGet(read, key) {
    this._index()
    const change = this.map.get(b4a.toString(key, 'hex'))

    if (change === undefined) {
      return this.cleared === null ? read.get(key) : this._readAndMaybeDrop(read, key)
    }

    return Promise.resolve(change[1])
  }

  async _readAndMaybeDrop(read, key) {
    const cleared = this.cleared // in case its cleared
    const value = await read.get(key)
    if (value === null) return null

    for (let i = 0; i < cleared.length; i++) {
      const c = cleared[i]
      // check if in range
      if (b4a.compare(c[0], key) <= 0 && b4a.compare(key, c[2]) < 0) return null
    }

    return value
  }

  _attached() {
    return this.snap !== null && this.changes === this.snap.changes
  }

  _frozen() {
    return this.changes === null || (this.snap !== null && this.changes !== this.snap.changes)
  }

  _index() {
    // if we are a snap and we are still attached (ie no mutations), simply copy the refs
    if (this._attached()) {
      this.snap._index()
      this.map = this.snap.map
      this.cleared = this.snap.cleared
      this.indexed = this.snap.indexed
      return
    }

    if (this.map === null) this.map = new Map()
    if (this.changes.length === this.indexed) return

    while (this.indexed < this.changes.length) {
      const c = this.changes[this.indexed++]

      if (c[2] === null) this.map.set(b4a.toString(c[0], 'hex'), c)
      else this._indexRange(c)
    }
  }

  _indexRange(range) {
    const s = b4a.toString(range[0], 'hex')
    const e = b4a.toString(range[2], 'hex')

    for (const [key, c] of this.map) {
      if (s <= key && key < e) this.map.set(key, [c[0], null, null])
    }

    if (this.cleared === null) this.cleared = []
    this.cleared.push(range)
  }

  apply(changes) {
    if (this.snap !== null) throw new Error('Illegal to push changes to a snapshot')

    if (this.readers !== 0 && this.changes !== null) {
      this.changes = this.changes.slice(0)
      this.cleared = this.cleared === null ? null : this.cleared.slice(0)
      this.map = this.map === null ? null : new Map([...this.map])
    }

    if (this.changes === null) {
      this.changes = changes
      return
    }

    for (let i = 0; i < changes.length; i++) {
      this.changes.push(changes[i])
    }
  }

  static async flush(changes, db) {
    if (changes === null) return true

    const w = db.write({ autoDestroy: true })

    for (const [start, value, end] of changes) {
      if (end !== null) w.tryDeleteRange(start, end)
      else if (value !== null) w.tryPut(start, value)
      else w.tryDelete(start)
    }

    await w.flush()

    return true
  }
}

module.exports = View

function cmpChange(a, b) {
  const c = b4a.compare(a[0], b[0])
  return c === 0 ? b4a.compare(a[2], b[2]) : c
}

function cmpChangeReverse(a, b) {
  return cmpChange(b, a)
}

function noop() {}

function reverseArray(list) {
  const r = new Array(list.length)
  for (let i = 0; i < list.length; i++) r[r.length - 1 - i] = list[i]
  return r
}

// TODO: expose from rocks instead
function dbClosing(db) {
  return db._state.closing || db._index === -1
}
const fs = require('fs')
const path = require('path')
const { Readable } = require('streamx')
const b4a = require('b4a')
const flat = require('flat-tree')
const crypto = require('hypercore-crypto')
const c = require('compact-encoding')
const m = require('./messages.js')
const View = require('../../lib/view.js')
const { CorestoreTX, CoreTX, CorestoreRX } = require('../../lib/tx.js')

const EMPTY_NODE = b4a.alloc(40)
const EMPTY_PAGE = b4a.alloc(4096)

let TREE_01_SKIP = null
let TREE_04_SKIP = null
let TREE_16_SKIP = null

class CoreListStream extends Readable {
  constructor (storage) {
    super()

    this.storage = storage
    this.stack = []
  }

  async _open (cb) {
    for (const a of await readdir(path.join(this.storage, 'cores'))) {
      for (const b of await readdir(path.join(this.storage, 'cores', a))) {
        for (const dkey of await readdir(path.join(this.storage, 'cores', a, b))) {
          this.stack.push(path.join(this.storage, 'cores', a, b, dkey))
        }
      }
    }

    cb(null)
  }

  async _read (cb) {
    while (true) {
      const next = this.stack.pop()
      if (!next) {
        this.push(null)
        break
      }

      const oplog = path.join(next, 'oplog')
      const result = await readOplog(oplog)
      if (!result) continue

      this.push(result)
      break
    }

    cb(null)
  }
}

function decodeOplogHeader (state) {
  c.uint32.decode(state) // cksum, ignore for now

  const l = c.uint32.decode(state)
  const length = l >> 2
  const headerBit = l & 1
  const partialBit = l & 2

  if (state.end - state.start < length) return null

  const end = state.start + length
  const result = { header: headerBit, partial: partialBit !== 0, byteLength: length + 8, message: null }

  try {
    result.message = m.oplog.header.decode({ start: state.start, end, buffer: state.buffer })
  } catch {
    return null
  }

  state.start = end
  return result
}

function decodeOplogEntry (state) {
  if (state.end - state.start < 8) return null

  c.uint32.decode(state) // cksum, ignore for now

  const l = c.uint32.decode(state)
  const length = l >>> 2
  const headerBit = l & 1
  const partialBit = l & 2

  if (state.end - state.start < length) return null

  const end = state.start + length

  const result = { header: headerBit, partial: partialBit !== 0, byteLength: length + 8, message: null }

  try {
    result.message = m.oplog.entry.decode({ start: state.start, end, buffer: state.buffer })
  } catch {
    return null
  }

  state.start = end

  return result
}

module.exports = { store, core }

async function store (storage, { version, dryRun = true, gc = true }) {
  const stream = new CoreListStream(storage.path)
  const view = new View()

  const tx = new CorestoreTX(view)
  const head = await storage._getHead(view)
  const primaryKeyFile = path.join(storage.path, 'primary-key')

  const primaryKey = await readFile(primaryKeyFile)

  if (!head.seed) head.seed = primaryKey

  for await (const data of stream) {
    const key = data.header.key
    const discoveryKey = crypto.discoveryKey(data.header.key)
    const files = getFiles(data.path)

    if (head.defaultDiscoveryKey === null) head.defaultDiscoveryKey = discoveryKey

    const core = {
      version: 0, // need later migration
      corePointer: head.allocated.cores++,
      dataPointer: head.allocated.datas++,
      alias: null
    }

    const ptr = { version: 0, corePointer: core.corePointer, dataPointer: core.dataPointer, dependencies: [] }
    const ctx = new CoreTX(ptr, storage.db, view, [])
    const userData = new Map()
    const treeNodes = new Map()

    const auth = {
      key,
      discoveryKey,
      manifest: data.header.manifest,
      keyPair: data.header.keyPair,
      encryptionKey: null
    }

    const tree = {
      length: 0,
      fork: 0,
      rootHash: null,
      signature: null
    }

    if (data.header.tree && data.header.tree.length) {
      tree.length = data.header.tree.length
      tree.fork = data.header.tree.fork
      tree.rootHash = data.header.tree.rootHash
      tree.signature = data.header.tree.signature
    }

    for (const { key, value } of data.header.userData) {
      userData.set(key, value)
    }

    for (const e of data.entries) {
      if (e.userData) userData.set(e.userData.key, e.userData.value)

      if (e.treeNodes) {
        for (const node of e.treeNodes) {
          treeNodes.set(node.index, node)
          ctx.putTreeNode(node)
        }
      }

      if (e.treeUpgrade) {
        if (e.treeUpgrade.ancestors !== tree.length) {
          throw new Error('Unflushed truncations not migrate-able atm')
        }

        tree.length = e.treeUpgrade.length
        tree.fork = e.treeUpgrade.fork
        tree.rootHash = null
        tree.signature = e.treeUpgrade.signature
      }
    }

    if (userData.has('corestore/name') && userData.has('corestore/namespace')) {
      core.alias = {
        name: b4a.toString(userData.get('corestore/name')),
        namespace: userData.get('corestore/namespace')
      }
      userData.delete('corestore/name')
      userData.delete('corestore/namespace')
    }

    for (const [key, value] of userData) {
      ctx.putUserData(key, value)
    }

    ctx.setAuth(auth)

    const getTreeNode = (index) => (treeNodes.get(index) || getTreeNodeFromFile(files.tree, index))

    if (tree.length) {
      if (tree.rootHash === null) tree.rootHash = crypto.tree(await getRoots(tree.length, getTreeNode))
      ctx.setHead(tree)
    }

    tx.putCore(discoveryKey, core)
    if (core.alias) tx.putCoreByAlias(core.alias, discoveryKey)

    await ctx.flush()
  }

  head.version = version
  tx.setHead(head)
  tx.apply()

  if (dryRun) return

  await View.flush(view.changes, storage.db)

  if (gc) await rm(primaryKeyFile)
}

class BlockSlicer {
  constructor (filename) {
    this.stream = fs.createReadStream(filename)
    this.closed = new Promise(resolve => this.stream.once('close', resolve))
    this.offset = 0
    this.overflow = null
  }

  async take (offset, size) {
    let buffer = null
    if (offset < this.offset) throw new Error('overread')

    while (true) {
      let data = null

      if (this.overflow) {
        data = this.overflow
        this.overflow = null
      } else {
        data = this.stream.read()

        if (!data) {
          await new Promise(resolve => this.stream.once('readable', resolve))
          continue
        }
      }

      let chunk = null

      if (this.offset === offset || buffer) {
        chunk = data
      } else if (this.offset + data.byteLength > offset) {
        chunk = data.subarray(offset - this.offset)
      }

      this.offset += data.byteLength
      if (!chunk) continue

      if (buffer) buffer = b4a.concat([buffer, chunk])
      else buffer = chunk

      if (buffer.byteLength < size) continue

      const result = buffer.subarray(0, size)
      this.overflow = size === buffer.byteLength ? null : buffer.subarray(result.byteLength)
      this.offset -= (this.overflow ? this.overflow.byteLength : 0)
      return result
    }
  }

  close () {
    this.stream.on('error', noop)
    this.stream.destroy()
    return this.closed
  }
}

class TreeSlicer {
  constructor () {
    this.buffer = null
    this.offset = 0
  }

  get size () {
    return this.buffer === null ? 0 : this.buffer.byteLength
  }

  push (data) {
    if (this.buffer === null) this.buffer = data
    else this.buffer = b4a.concat([this.buffer, data])
    this.offset += data.byteLength
  }

  skip () {
    let skipped = 0

    if (TREE_01_SKIP === null) {
      TREE_16_SKIP = b4a.alloc(16 * 40 * 100)
      TREE_04_SKIP = TREE_16_SKIP.subarray(0, 4 * 40 * 100)
      TREE_01_SKIP = TREE_16_SKIP.subarray(0, 1 * 40 * 100)
    }

    while (true) {
      if (this.buffer.byteLength >= TREE_16_SKIP.byteLength) {
        if (b4a.equals(this.buffer.subarray(0, TREE_16_SKIP.byteLength), TREE_16_SKIP)) {
          this.buffer = this.buffer.subarray(TREE_16_SKIP.byteLength)
          skipped += 1600
          continue
        }
      }

      if (this.buffer.byteLength >= TREE_04_SKIP.byteLength) {
        if (b4a.equals(this.buffer.subarray(0, TREE_04_SKIP.byteLength), TREE_04_SKIP)) {
          this.buffer = this.buffer.subarray(TREE_04_SKIP.byteLength)
          skipped += 400
          continue
        }
      }

      if (this.buffer.byteLength >= TREE_01_SKIP.byteLength) {
        if (b4a.equals(this.buffer.subarray(0, TREE_01_SKIP.byteLength), TREE_01_SKIP)) {
          this.buffer = this.buffer.subarray(TREE_01_SKIP.byteLength)
          skipped += 100
          continue
        }
      }
      break
    }

    return skipped
  }

  take () {
    const len = 40

    if (len <= this.size) {
      const chunk = this.buffer.subarray(0, len)
      this.buffer = this.buffer.subarray(len)
      return chunk
    }

    return null
  }
}

async function core (core, { version, dryRun = true, gc = true }) {
  if (dryRun) return // dryRun mode not supported atm

  const rx = core.read()

  const promises = [rx.getAuth(), rx.getHead()]
  rx.tryFlush()

  const [auth, head] = await Promise.all(promises)

  if (!auth) return

  const dk = b4a.toString(auth.discoveryKey, 'hex')
  const files = getFiles(path.join(core.store.path, 'cores', dk.slice(0, 2), dk.slice(2, 4), dk))

  if (head === null || head.length === 0) {
    await commitCoreMigration(auth, core, version)
    if (gc) await runGC()
    return // no data
  }

  const oplog = await readOplog(files.oplog)
  if (!oplog) {
    const writable = !!auth.keyPair

    if (writable) {
      throw new Error('No oplog available writable core for ' + files.oplog + ', length = ' + (head ? head.length : 0))
    }

    // if not writable, just nuke it to recover, some bad state happened here, prop corruption from earlier versions
    const w = core.write()

    w.deleteBlockRange(0, -1)
    w.deleteTreeNodeRange(0, -1)
    w.deleteBitfieldPageRange(0, -1)
    w.deleteHead()

    await w.flush()

    await commitCoreMigration(auth, core, version)
    if (gc) await runGC()
    return // no data
  }

  const treeData = new TreeSlicer()

  let treeIndex = 0

  if (await exists(files.tree)) {
    for await (const data of fs.createReadStream(files.tree)) {
      treeData.push(data)

      let write = null

      while (true) {
        const skip = treeData.skip()
        treeIndex += skip

        const buf = treeData.take()
        if (buf === null) break

        const index = treeIndex++
        if (b4a.equals(buf, EMPTY_NODE)) continue

        if (write === null) write = core.write()
        write.putTreeNode(decodeTreeNode(index, buf))
      }

      if (write !== null) await write.flush()
    }
  }

  const buf = []
  if (await exists(files.bitfield)) {
    for await (const data of fs.createReadStream(files.bitfield)) {
      buf.push(data)
    }
  }

  let bitfield = b4a.concat(buf)
  if (bitfield.byteLength & 4095) bitfield = b4a.concat([bitfield, b4a.alloc(4096 - (bitfield.byteLength & 4095))])

  const pages = new Map()
  const headerBits = new Map()

  const roots = await getRootsFromStorage(core, head.length)

  for (const e of oplog.entries) {
    if (!e.bitfield) continue

    for (let i = 0; i < e.bitfield.length; i++) {
      headerBits.set(i + e.bitfield.start, !e.bitfield.drop)
    }
  }

  let batch = []

  const cache = new Map()
  const blocks = new BlockSlicer(files.data)

  for (const index of allBits(bitfield)) {
    if (headerBits.get(index) === false) continue
    if (index >= head.length) continue

    setBitInPage(index)

    batch.push(index)
    if (batch.length < 1024) continue

    await writeBlocksBatch()
    continue
  }

  if (batch.length) await writeBlocksBatch()

  await blocks.close()

  const w = core.write()

  for (const [index, bit] of headerBits) {
    if (!bit) continue
    if (index >= head.length) continue

    setBitInPage(index)

    const blk = await getBlockFromFile(files.data, core, index, roots, cache)
    w.putBlock(index, blk)
  }

  for (const [index, page] of pages) {
    w.putBitfieldPage(index, b4a.from(page.buffer, page.byteOffset, page.byteLength))
  }

  await w.flush()

  let contiguousLength = 0
  for await (const data of core.createBlockStream()) {
    if (data.index === contiguousLength) contiguousLength++
    else break
  }

  if (contiguousLength) {
    const w = core.write()
    w.setHints({ contiguousLength })
    await w.flush()
  }

  await commitCoreMigration(auth, core, version)

  if (gc) await runGC()

  async function runGC () {
    await rm(files.path)
    await rmdir(path.join(files.path, '..'))
    await rmdir(path.join(files.path, '../..'))
    await rmdir(path.join(core.store.path, 'cores'))
  }

  function setBitInPage (index) {
    const n = index & 32767
    const p = (index - n) / 32768

    let page = pages.get(p)

    if (!page) {
      page = new Uint32Array(1024)
      pages.set(p, page)
    }

    const o = n & 31
    const b = (n - o) / 32
    const v = 1 << o

    page[b] |= v
  }

  async function writeBlocksBatch () {
    const read = core.read()
    const promises = []
    for (const index of batch) promises.push(getByteRangeFromStorage(read, 2 * index, roots, cache))
    read.tryFlush()

    const r = await Promise.all(promises)
    const tx = core.write()

    for (let i = 0; i < r.length; i++) {
      const index = batch[i]
      const [offset, size] = r[i]

      const blk = await blocks.take(offset, size)
      tx.putBlock(index, blk)
    }

    batch = []
    if (cache.size > 16384) cache.clear()

    await tx.flush()
  }
}

async function commitCoreMigration (auth, core, version) {
  const view = new View()
  const rx = new CorestoreRX(core.db, view)

  const storeCorePromise = rx.getCore(auth.discoveryKey)
  rx.tryFlush()

  const storeCore = await storeCorePromise

  storeCore.version = version

  const tx = new CorestoreTX(view)

  tx.putCore(auth.discoveryKey, storeCore)
  tx.apply()

  await View.flush(view.changes, core.db)
}

async function getBlockFromFile (file, core, index, roots, cache) {
  const rx = core.read()
  const promise = getByteRangeFromStorage(rx, 2 * index, roots, cache)
  rx.tryFlush()
  const [offset, size] = await promise

  return new Promise(function (resolve) {
    readAll(file, size, offset, function (err, buf) {
      if (err) return resolve(null)
      resolve(buf)
    })
  })
}

function getFiles (dir) {
  return {
    path: dir,
    oplog: path.join(dir, 'oplog'),
    data: path.join(dir, 'data'),
    tree: path.join(dir, 'tree'),
    bitfield: path.join(dir, 'bitfield')
  }
}

async function getRootsFromStorage (core, length) {
  const all = []
  const rx = core.read()
  for (const index of flat.fullRoots(2 * length)) {
    all.push(rx.getTreeNode(index))
  }
  rx.tryFlush()
  return Promise.all(all)
}

async function getRoots (length, getTreeNode) {
  const all = []
  for (const index of flat.fullRoots(2 * length)) {
    all.push(await getTreeNode(index))
  }
  return all
}

function getCached (read, cache, index) {
  if (cache.has(index)) return cache.get(index)
  const p = read.getTreeNode(index)
  cache.set(index, p)
  return p
}

async function getByteRangeFromStorage (read, index, roots, cache) {
  const promises = [getCached(read, cache, index), getByteOffsetFromStorage(read, index, roots, cache)]
  const [node, offset] = await Promise.all(promises)
  if (!node) throw new Error('Node not found during migration: ' + index)
  return [offset, node.size]
}

async function getByteOffsetFromStorage (rx, index, roots, cache) {
  if (index === 0) return 0
  if ((index & 1) === 1) index = flat.leftSpan(index)

  let head = 0
  let offset = 0

  for (const node of roots) { // all async ticks happen once we find the root so safe
    head += 2 * ((node.index - head) + 1)

    if (index >= head) {
      offset += node.size
      continue
    }

    const ite = flat.iterator(node.index)
    const promises = []

    while (ite.index !== index) {
      if (index < ite.index) {
        ite.leftChild()
      } else {
        promises.push(getCached(rx, cache, ite.leftChild()))
        ite.sibling()
      }
    }

    const nodes = await Promise.all(promises)
    for (const node of nodes) offset += node.size

    return offset
  }

  throw new Error('Failed to find offset')
}

function decodeTreeNode (index, buf) {
  return { index, size: c.decode(c.uint64, buf), hash: buf.subarray(8) }
}

async function getTreeNodeFromFile (file, index) {
  return new Promise(function (resolve) {
    readAll(file, 40, index * 40, function (err, buf) {
      if (err) return resolve(null)
      resolve(decodeTreeNode(index, buf))
    })
  })
}

function readAll (filename, length, pos, cb) {
  const buf = b4a.alloc(length)

  fs.open(filename, 'r', function (err, fd) {
    if (err) return cb(err)

    let offset = 0

    fs.read(fd, buf, offset, buf.byteLength, pos, function loop (err, read) {
      if (err) return done(err)
      if (read === 0) return done(new Error('Partial read'))
      offset += read
      if (offset === buf.byteLength) return done(null, buf)
      fs.read(fd, offset, buf.byteLength - offset, buf, pos + offset, loop)
    })

    function done (err, value) {
      fs.close(fd, () => cb(err, value))
    }
  })
}

async function readdir (dir) {
  try {
    return await fs.promises.readdir(dir)
  } catch {
    return []
  }
}

async function exists (file) {
  try {
    await fs.promises.stat(file)
    return true
  } catch {
    return false
  }
}

async function readFile (file) {
  try {
    return await fs.promises.readFile(file)
  } catch {
    return null
  }
}

async function rm (dir) {
  try {
    await fs.promises.rm(dir, { recursive: true })
  } catch {}
}

async function rmdir (dir) {
  try {
    await fs.promises.rmdir(dir)
  } catch {}
}

function * allBits (buffer) {
  for (let i = 0; i < buffer.byteLength; i += EMPTY_PAGE.byteLength) {
    const page = buffer.subarray(i, i + EMPTY_NODE.byteLength)
    if (b4a.equals(page, EMPTY_PAGE)) continue

    const view = new Uint32Array(page.buffer, page.byteOffset, EMPTY_PAGE.byteLength / 4)

    for (let j = 0; j < view.length; j++) {
      const n = view[j]
      if (n === 0) continue

      for (let k = 0; k < 32; k++) {
        const m = 1 << k
        if (n & m) yield i * 8 + j * 32 + k
      }
    }
  }
}

function readOplog (oplog) {
  return new Promise(function (resolve) {
    fs.readFile(oplog, function (err, buffer) {
      if (err) return resolve(null)

      const state = { start: 0, end: buffer.byteLength, buffer }
      const headers = [1, 0]

      const h1 = decodeOplogHeader(state)
      state.start = 4096

      const h2 = decodeOplogHeader(state)
      state.start = 4096 * 2

      if (!h1 && !h2) return resolve(null)

      if (h1 && !h2) {
        headers[0] = h1.header
        headers[1] = h1.header
      } else if (!h1 && h2) {
        headers[0] = (h2.header + 1) & 1
        headers[1] = h2.header
      } else {
        headers[0] = h1.header
        headers[1] = h2.header
      }

      const header = (headers[0] + headers[1]) & 1
      const result = { path: path.dirname(oplog), header: null, entries: [] }
      const decoded = []

      result.header = header ? h2.message : h1.message

      if (result.header.external) {
        fs.readFile(path.join(oplog, '../header'), function (err, buffer) {
          if (err) return resolve(null)
          const start = result.header.external.start
          const end = start + result.header.external.length
          result.header = m.oplog.header.decode({ buffer, start, end })
          finish()
        })
        return
      }

      finish()

      function finish () {
        while (true) {
          const entry = decodeOplogEntry(state)
          if (!entry) break
          if (entry.header !== header) break

          decoded.push(entry)
        }

        while (decoded.length > 0 && decoded[decoded.length - 1].partial) decoded.pop()

        for (const e of decoded) {
          result.entries.push(e.message)
        }

        resolve(result)
      }
    })
  })
}

function noop () {}
// needed here for compat, copied from old hypercore, do not change this

const c = require('compact-encoding')
const b4a = require('b4a')

const EMPTY = b4a.alloc(0)
const DEFAULT_NAMESPACE = b4a.from('4144eea531e483d54e0c14f4ca68e0644f355343ff6fcb0f005200e12cd747cb', 'hex')

const hashes = {
  preencode (state, m) {
    state.end++ // small uint
  },
  encode (state, m) {
    if (m === 'blake2b') {
      c.uint.encode(state, 0)
      return
    }

    throw new Error('Unknown hash: ' + m)
  },
  decode (state) {
    const n = c.uint.decode(state)
    if (n === 0) return 'blake2b'
    throw new Error('Unknown hash id: ' + n)
  }
}

const signatures = {
  preencode (state, m) {
    state.end++ // small uint
  },
  encode (state, m) {
    if (m === 'ed25519') {
      c.uint.encode(state, 0)
      return
    }

    throw new Error('Unknown signature: ' + m)
  },
  decode (state) {
    const n = c.uint.decode(state)
    if (n === 0) return 'ed25519'
    throw new Error('Unknown signature id: ' + n)
  }
}

const signer = {
  preencode (state, m) {
    signatures.preencode(state, m.signature)
    c.fixed32.preencode(state, m.namespace)
    c.fixed32.preencode(state, m.publicKey)
  },
  encode (state, m) {
    signatures.encode(state, m.signature)
    c.fixed32.encode(state, m.namespace)
    c.fixed32.encode(state, m.publicKey)
  },
  decode (state) {
    return {
      signature: signatures.decode(state),
      namespace: c.fixed32.decode(state),
      publicKey: c.fixed32.decode(state)
    }
  }
}

const signerArray = c.array(signer)

const prologue = {
  preencode (state, p) {
    c.fixed32.preencode(state, p.hash)
    c.uint.preencode(state, p.length)
  },
  encode (state, p) {
    c.fixed32.encode(state, p.hash)
    c.uint.encode(state, p.length)
  },
  decode (state) {
    return {
      hash: c.fixed32.decode(state),
      length: c.uint.decode(state)
    }
  }
}

const manifestv0 = {
  preencode (state, m) {
    hashes.preencode(state, m.hash)
    state.end++ // type

    if (m.prologue && m.signers.length === 0) {
      c.fixed32.preencode(state, m.prologue.hash)
      return
    }

    if (m.quorum === 1 && m.signers.length === 1 && !m.allowPatch) {
      signer.preencode(state, m.signers[0])
    } else {
      state.end++ // flags
      c.uint.preencode(state, m.quorum)
      signerArray.preencode(state, m.signers)
    }
  },
  encode (state, m) {
    hashes.encode(state, m.hash)

    if (m.prologue && m.signers.length === 0) {
      c.uint.encode(state, 0)
      c.fixed32.encode(state, m.prologue.hash)
      return
    }

    if (m.quorum === 1 && m.signers.length === 1 && !m.allowPatch) {
      c.uint.encode(state, 1)
      signer.encode(state, m.signers[0])
    } else {
      c.uint.encode(state, 2)
      c.uint.encode(state, m.allowPatch ? 1 : 0)
      c.uint.encode(state, m.quorum)
      signerArray.encode(state, m.signers)
    }
  },
  decode (state) {
    const hash = hashes.decode(state)
    const type = c.uint.decode(state)

    if (type > 2) throw new Error('Unknown type: ' + type)

    if (type === 0) {
      return {
        version: 0,
        hash,
        allowPatch: false,
        quorum: 0,
        signers: [],
        prologue: {
          hash: c.fixed32.decode(state),
          length: 0
        }
      }
    }

    if (type === 1) {
      return {
        version: 0,
        hash,
        allowPatch: false,
        quorum: 1,
        signers: [signer.decode(state)],
        prologue: null
      }
    }

    const flags = c.uint.decode(state)

    return {
      version: 0,
      hash,
      allowPatch: (flags & 1) !== 0,
      quorum: c.uint.decode(state),
      signers: signerArray.decode(state),
      prologue: null
    }
  }
}

const manifest = exports.manifest = {
  preencode (state, m) {
    state.end++ // version
    if (m.version === 0) return manifestv0.preencode(state, m)

    state.end++ // flags
    hashes.preencode(state, m.hash)

    c.uint.preencode(state, m.quorum)
    signerArray.preencode(state, m.signers)
    if (m.prologue) prologue.preencode(state, m.prologue)
  },
  encode (state, m) {
    c.uint.encode(state, m.version)
    if (m.version === 0) return manifestv0.encode(state, m)

    c.uint.encode(state, (m.allowPatch ? 1 : 0) | (m.prologue ? 2 : 0) | (m.unencrypted ? 4 : 0))
    hashes.encode(state, m.hash)

    c.uint.encode(state, m.quorum)
    signerArray.encode(state, m.signers)
    if (m.prologue) prologue.encode(state, m.prologue)
  },
  decode (state) {
    const v = c.uint.decode(state)
    if (v === 0) return manifestv0.decode(state)
    if (v !== 1) throw new Error('Unknown version: ' + v)

    const flags = c.uint.decode(state)
    const hash = hashes.decode(state)
    const quorum = c.uint.decode(state)
    const signers = signerArray.decode(state)
    const unencrypted = (flags & 4) !== 0

    return {
      version: 1,
      hash,
      allowPatch: (flags & 1) !== 0,
      quorum,
      signers,
      prologue: (flags & 2) === 0 ? null : prologue.decode(state),
      unencrypted
    }
  }
}

const node = {
  preencode (state, n) {
    c.uint.preencode(state, n.index)
    c.uint.preencode(state, n.size)
    c.fixed32.preencode(state, n.hash)
  },
  encode (state, n) {
    c.uint.encode(state, n.index)
    c.uint.encode(state, n.size)
    c.fixed32.encode(state, n.hash)
  },
  decode (state) {
    return {
      index: c.uint.decode(state),
      size: c.uint.decode(state),
      hash: c.fixed32.decode(state)
    }
  }
}

const nodeArray = c.array(node)

const wire = exports.wire = {}

wire.handshake = {
  preencode (state, m) {
    c.uint.preencode(state, 1)
    c.fixed32.preencode(state, m.capability)
  },
  encode (state, m) {
    c.uint.encode(state, m.seeks ? 1 : 0)
    c.fixed32.encode(state, m.capability)
  },
  decode (state) {
    const flags = c.uint.decode(state)
    return {
      seeks: (flags & 1) !== 0,
      capability: c.fixed32.decode(state)
    }
  }
}

const requestBlock = {
  preencode (state, b) {
    c.uint.preencode(state, b.index)
    c.uint.preencode(state, b.nodes)
  },
  encode (state, b) {
    c.uint.encode(state, b.index)
    c.uint.encode(state, b.nodes)
  },
  decode (state) {
    return {
      index: c.uint.decode(state),
      nodes: c.uint.decode(state)
    }
  }
}

const requestSeek = {
  preencode (state, s) {
    c.uint.preencode(state, s.bytes)
    c.uint.preencode(state, s.padding)
  },
  encode (state, s) {
    c.uint.encode(state, s.bytes)
    c.uint.encode(state, s.padding)
  },
  decode (state) {
    return {
      bytes: c.uint.decode(state),
      padding: c.uint.decode(state)
    }
  }
}

const requestUpgrade = {
  preencode (state, u) {
    c.uint.preencode(state, u.start)
    c.uint.preencode(state, u.length)
  },
  encode (state, u) {
    c.uint.encode(state, u.start)
    c.uint.encode(state, u.length)
  },
  decode (state) {
    return {
      start: c.uint.decode(state),
      length: c.uint.decode(state)
    }
  }
}

wire.request = {
  preencode (state, m) {
    state.end++ // flags
    c.uint.preencode(state, m.id)
    c.uint.preencode(state, m.fork)

    if (m.block) requestBlock.preencode(state, m.block)
    if (m.hash) requestBlock.preencode(state, m.hash)
    if (m.seek) requestSeek.preencode(state, m.seek)
    if (m.upgrade) requestUpgrade.preencode(state, m.upgrade)
    if (m.priority) c.uint.preencode(state, m.priority)
  },
  encode (state, m) {
    const flags = (m.block ? 1 : 0) | (m.hash ? 2 : 0) | (m.seek ? 4 : 0) | (m.upgrade ? 8 : 0) | (m.manifest ? 16 : 0) | (m.priority ? 32 : 0)

    c.uint.encode(state, flags)
    c.uint.encode(state, m.id)
    c.uint.encode(state, m.fork)

    if (m.block) requestBlock.encode(state, m.block)
    if (m.hash) requestBlock.encode(state, m.hash)
    if (m.seek) requestSeek.encode(state, m.seek)
    if (m.upgrade) requestUpgrade.encode(state, m.upgrade)
    if (m.priority) c.uint.encode(state, m.priority)
  },
  decode (state) {
    const flags = c.uint.decode(state)

    return {
      id: c.uint.decode(state),
      fork: c.uint.decode(state),
      block: flags & 1 ? requestBlock.decode(state) : null,
      hash: flags & 2 ? requestBlock.decode(state) : null,
      seek: flags & 4 ? requestSeek.decode(state) : null,
      upgrade: flags & 8 ? requestUpgrade.decode(state) : null,
      manifest: (flags & 16) !== 0,
      priority: flags & 32 ? c.uint.decode(state) : 0
    }
  }
}

wire.cancel = {
  preencode (state, m) {
    c.uint.preencode(state, m.request)
  },
  encode (state, m) {
    c.uint.encode(state, m.request)
  },
  decode (state, m) {
    return {
      request: c.uint.decode(state)
    }
  }
}

const dataUpgrade = {
  preencode (state, u) {
    c.uint.preencode(state, u.start)
    c.uint.preencode(state, u.length)
    nodeArray.preencode(state, u.nodes)
    nodeArray.preencode(state, u.additionalNodes)
    c.optionalBuffer.preencode(state, u.signature)
  },
  encode (state, u) {
    c.uint.encode(state, u.start)
    c.uint.encode(state, u.length)
    nodeArray.encode(state, u.nodes)
    nodeArray.encode(state, u.additionalNodes)
    c.optionalBuffer.encode(state, u.signature)
  },
  decode (state) {
    return {
      start: c.uint.decode(state),
      length: c.uint.decode(state),
      nodes: nodeArray.decode(state),
      additionalNodes: nodeArray.decode(state),
      signature: c.optionalBuffer.decode(state)
    }
  }
}

const dataSeek = {
  preencode (state, s) {
    c.uint.preencode(state, s.bytes)
    nodeArray.preencode(state, s.nodes)
  },
  encode (state, s) {
    c.uint.encode(state, s.bytes)
    nodeArray.encode(state, s.nodes)
  },
  decode (state) {
    return {
      bytes: c.uint.decode(state),
      nodes: nodeArray.decode(state)
    }
  }
}

const dataBlock = {
  preencode (state, b) {
    c.uint.preencode(state, b.index)
    c.optionalBuffer.preencode(state, b.value)
    nodeArray.preencode(state, b.nodes)
  },
  encode (state, b) {
    c.uint.encode(state, b.index)
    c.optionalBuffer.encode(state, b.value)
    nodeArray.encode(state, b.nodes)
  },
  decode (state) {
    return {
      index: c.uint.decode(state),
      value: c.optionalBuffer.decode(state) || EMPTY,
      nodes: nodeArray.decode(state)
    }
  }
}

const dataHash = {
  preencode (state, b) {
    c.uint.preencode(state, b.index)
    nodeArray.preencode(state, b.nodes)
  },
  encode (state, b) {
    c.uint.encode(state, b.index)
    nodeArray.encode(state, b.nodes)
  },
  decode (state) {
    return {
      index: c.uint.decode(state),
      nodes: nodeArray.decode(state)
    }
  }
}

wire.data = {
  preencode (state, m) {
    state.end++ // flags
    c.uint.preencode(state, m.request)
    c.uint.preencode(state, m.fork)

    if (m.block) dataBlock.preencode(state, m.block)
    if (m.hash) dataHash.preencode(state, m.hash)
    if (m.seek) dataSeek.preencode(state, m.seek)
    if (m.upgrade) dataUpgrade.preencode(state, m.upgrade)
    if (m.manifest) manifest.preencode(state, m.manifest)
  },
  encode (state, m) {
    const flags = (m.block ? 1 : 0) | (m.hash ? 2 : 0) | (m.seek ? 4 : 0) | (m.upgrade ? 8 : 0) | (m.manifest ? 16 : 0)

    c.uint.encode(state, flags)
    c.uint.encode(state, m.request)
    c.uint.encode(state, m.fork)

    if (m.block) dataBlock.encode(state, m.block)
    if (m.hash) dataHash.encode(state, m.hash)
    if (m.seek) dataSeek.encode(state, m.seek)
    if (m.upgrade) dataUpgrade.encode(state, m.upgrade)
    if (m.manifest) manifest.encode(state, m.manifest)
  },
  decode (state) {
    const flags = c.uint.decode(state)

    return {
      request: c.uint.decode(state),
      fork: c.uint.decode(state),
      block: flags & 1 ? dataBlock.decode(state) : null,
      hash: flags & 2 ? dataHash.decode(state) : null,
      seek: flags & 4 ? dataSeek.decode(state) : null,
      upgrade: flags & 8 ? dataUpgrade.decode(state) : null,
      manifest: flags & 16 ? manifest.decode(state) : null
    }
  }
}

wire.noData = {
  preencode (state, m) {
    c.uint.preencode(state, m.request)
  },
  encode (state, m) {
    c.uint.encode(state, m.request)
  },
  decode (state, m) {
    return {
      request: c.uint.decode(state)
    }
  }
}

wire.want = {
  preencode (state, m) {
    c.uint.preencode(state, m.start)
    c.uint.preencode(state, m.length)
  },
  encode (state, m) {
    c.uint.encode(state, m.start)
    c.uint.encode(state, m.length)
  },
  decode (state) {
    return {
      start: c.uint.decode(state),
      length: c.uint.decode(state)
    }
  }
}

wire.unwant = {
  preencode (state, m) {
    c.uint.preencode(state, m.start)
    c.uint.preencode(state, m.length)
  },
  encode (state, m) {
    c.uint.encode(state, m.start)
    c.uint.encode(state, m.length)
  },
  decode (state, m) {
    return {
      start: c.uint.decode(state),
      length: c.uint.decode(state)
    }
  }
}

wire.range = {
  preencode (state, m) {
    state.end++ // flags
    c.uint.preencode(state, m.start)
    if (m.length !== 1) c.uint.preencode(state, m.length)
  },
  encode (state, m) {
    c.uint.encode(state, (m.drop ? 1 : 0) | (m.length === 1 ? 2 : 0))
    c.uint.encode(state, m.start)
    if (m.length !== 1) c.uint.encode(state, m.length)
  },
  decode (state) {
    const flags = c.uint.decode(state)

    return {
      drop: (flags & 1) !== 0,
      start: c.uint.decode(state),
      length: (flags & 2) !== 0 ? 1 : c.uint.decode(state)
    }
  }
}

wire.bitfield = {
  preencode (state, m) {
    c.uint.preencode(state, m.start)
    c.uint32array.preencode(state, m.bitfield)
  },
  encode (state, m) {
    c.uint.encode(state, m.start)
    c.uint32array.encode(state, m.bitfield)
  },
  decode (state, m) {
    return {
      start: c.uint.decode(state),
      bitfield: c.uint32array.decode(state)
    }
  }
}

wire.sync = {
  preencode (state, m) {
    state.end++ // flags
    c.uint.preencode(state, m.fork)
    c.uint.preencode(state, m.length)
    c.uint.preencode(state, m.remoteLength)
  },
  encode (state, m) {
    c.uint.encode(state, (m.canUpgrade ? 1 : 0) | (m.uploading ? 2 : 0) | (m.downloading ? 4 : 0) | (m.hasManifest ? 8 : 0))
    c.uint.encode(state, m.fork)
    c.uint.encode(state, m.length)
    c.uint.encode(state, m.remoteLength)
  },
  decode (state) {
    const flags = c.uint.decode(state)

    return {
      fork: c.uint.decode(state),
      length: c.uint.decode(state),
      remoteLength: c.uint.decode(state),
      canUpgrade: (flags & 1) !== 0,
      uploading: (flags & 2) !== 0,
      downloading: (flags & 4) !== 0,
      hasManifest: (flags & 8) !== 0
    }
  }
}

wire.reorgHint = {
  preencode (state, m) {
    c.uint.preencode(state, m.from)
    c.uint.preencode(state, m.to)
    c.uint.preencode(state, m.ancestors)
  },
  encode (state, m) {
    c.uint.encode(state, m.from)
    c.uint.encode(state, m.to)
    c.uint.encode(state, m.ancestors)
  },
  decode (state) {
    return {
      from: c.uint.encode(state),
      to: c.uint.encode(state),
      ancestors: c.uint.encode(state)
    }
  }
}

wire.extension = {
  preencode (state, m) {
    c.string.preencode(state, m.name)
    c.raw.preencode(state, m.message)
  },
  encode (state, m) {
    c.string.encode(state, m.name)
    c.raw.encode(state, m.message)
  },
  decode (state) {
    return {
      name: c.string.decode(state),
      message: c.raw.decode(state)
    }
  }
}

const keyValue = {
  preencode (state, p) {
    c.string.preencode(state, p.key)
    c.optionalBuffer.preencode(state, p.value)
  },
  encode (state, p) {
    c.string.encode(state, p.key)
    c.optionalBuffer.encode(state, p.value)
  },
  decode (state) {
    return {
      key: c.string.decode(state),
      value: c.optionalBuffer.decode(state)
    }
  }
}

const treeUpgrade = {
  preencode (state, u) {
    c.uint.preencode(state, u.fork)
    c.uint.preencode(state, u.ancestors)
    c.uint.preencode(state, u.length)
    c.optionalBuffer.preencode(state, u.signature)
  },
  encode (state, u) {
    c.uint.encode(state, u.fork)
    c.uint.encode(state, u.ancestors)
    c.uint.encode(state, u.length)
    c.optionalBuffer.encode(state, u.signature)
  },
  decode (state) {
    return {
      fork: c.uint.decode(state),
      ancestors: c.uint.decode(state),
      length: c.uint.decode(state),
      signature: c.optionalBuffer.decode(state)
    }
  }
}

const bitfieldUpdate = { // TODO: can maybe be folded into a HAVE later on with the most recent spec
  preencode (state, b) {
    state.end++ // flags
    c.uint.preencode(state, b.start)
    c.uint.preencode(state, b.length)
  },
  encode (state, b) {
    state.buffer[state.start++] = b.drop ? 1 : 0
    c.uint.encode(state, b.start)
    c.uint.encode(state, b.length)
  },
  decode (state) {
    const flags = c.uint.decode(state)
    return {
      drop: (flags & 1) !== 0,
      start: c.uint.decode(state),
      length: c.uint.decode(state)
    }
  }
}

const oplog = exports.oplog = {}

oplog.entry = {
  preencode (state, m) {
    state.end++ // flags
    if (m.userData) keyValue.preencode(state, m.userData)
    if (m.treeNodes) nodeArray.preencode(state, m.treeNodes)
    if (m.treeUpgrade) treeUpgrade.preencode(state, m.treeUpgrade)
    if (m.bitfield) bitfieldUpdate.preencode(state, m.bitfield)
  },
  encode (state, m) {
    const s = state.start++
    let flags = 0

    if (m.userData) {
      flags |= 1
      keyValue.encode(state, m.userData)
    }
    if (m.treeNodes) {
      flags |= 2
      nodeArray.encode(state, m.treeNodes)
    }
    if (m.treeUpgrade) {
      flags |= 4
      treeUpgrade.encode(state, m.treeUpgrade)
    }
    if (m.bitfield) {
      flags |= 8
      bitfieldUpdate.encode(state, m.bitfield)
    }

    state.buffer[s] = flags
  },
  decode (state) {
    const flags = c.uint.decode(state)
    return {
      userData: (flags & 1) !== 0 ? keyValue.decode(state) : null,
      treeNodes: (flags & 2) !== 0 ? nodeArray.decode(state) : null,
      treeUpgrade: (flags & 4) !== 0 ? treeUpgrade.decode(state) : null,
      bitfield: (flags & 8) !== 0 ? bitfieldUpdate.decode(state) : null
    }
  }
}

const keyPair = {
  preencode (state, kp) {
    c.optionalBuffer.preencode(state, kp.publicKey)
    c.optionalBuffer.preencode(state, kp.secretKey)
  },
  encode (state, kp) {
    c.optionalBuffer.encode(state, kp.publicKey)
    c.optionalBuffer.encode(state, kp.secretKey)
  },
  decode (state) {
    return {
      publicKey: c.optionalBuffer.decode(state),
      secretKey: c.optionalBuffer.decode(state)
    }
  }
}

const reorgHint = {
  preencode (state, r) {
    c.uint.preencode(state, r.from)
    c.uint.preencode(state, r.to)
    c.uint.preencode(state, r.ancestors)
  },
  encode (state, r) {
    c.uint.encode(state, r.from)
    c.uint.encode(state, r.to)
    c.uint.encode(state, r.ancestors)
  },
  decode (state) {
    return {
      from: c.uint.decode(state),
      to: c.uint.decode(state),
      ancestors: c.uint.decode(state)
    }
  }
}

const reorgHintArray = c.array(reorgHint)

const hints = {
  preencode (state, h) {
    reorgHintArray.preencode(state, h.reorgs)
    c.uint.preencode(state, h.contiguousLength)
  },
  encode (state, h) {
    reorgHintArray.encode(state, h.reorgs)
    c.uint.encode(state, h.contiguousLength)
  },
  decode (state) {
    return {
      reorgs: reorgHintArray.decode(state),
      contiguousLength: state.start < state.end ? c.uint.decode(state) : 0
    }
  }
}

const treeHeader = {
  preencode (state, t) {
    c.uint.preencode(state, t.fork)
    c.uint.preencode(state, t.length)
    c.optionalBuffer.preencode(state, t.rootHash)
    c.optionalBuffer.preencode(state, t.signature)
  },
  encode (state, t) {
    c.uint.encode(state, t.fork)
    c.uint.encode(state, t.length)
    c.optionalBuffer.encode(state, t.rootHash)
    c.optionalBuffer.encode(state, t.signature)
  },
  decode (state) {
    return {
      fork: c.uint.decode(state),
      length: c.uint.decode(state),
      rootHash: c.optionalBuffer.decode(state),
      signature: c.optionalBuffer.decode(state)
    }
  }
}

const types = {
  preencode (state, t) {
    c.string.preencode(state, t.tree)
    c.string.preencode(state, t.bitfield)
    c.string.preencode(state, t.signer)
  },
  encode (state, t) {
    c.string.encode(state, t.tree)
    c.string.encode(state, t.bitfield)
    c.string.encode(state, t.signer)
  },
  decode (state) {
    return {
      tree: c.string.decode(state),
      bitfield: c.string.decode(state),
      signer: c.string.decode(state)
    }
  }
}

const externalHeader = {
  preencode (state, m) {
    c.uint.preencode(state, m.start)
    c.uint.preencode(state, m.length)
  },
  encode (state, m) {
    c.uint.encode(state, m.start)
    c.uint.encode(state, m.length)
  },
  decode (state) {
    return {
      start: c.uint.decode(state),
      length: c.uint.decode(state)
    }
  }
}

const keyValueArray = c.array(keyValue)

oplog.header = {
  preencode (state, h) {
    state.end += 2 // version + flags
    if (h.external) {
      externalHeader.preencode(state, h.external)
      return
    }
    c.fixed32.preencode(state, h.key)
    if (h.manifest) manifest.preencode(state, h.manifest)
    if (h.keyPair) keyPair.preencode(state, h.keyPair)
    keyValueArray.preencode(state, h.userData)
    treeHeader.preencode(state, h.tree)
    hints.preencode(state, h.hints)
  },
  encode (state, h) {
    c.uint.encode(state, 1)
    if (h.external) {
      c.uint.encode(state, 1) // ONLY set the first big for clarity
      externalHeader.encode(state, h.external)
      return
    }
    c.uint.encode(state, (h.manifest ? 2 : 0) | (h.keyPair ? 4 : 0))
    c.fixed32.encode(state, h.key)
    if (h.manifest) manifest.encode(state, h.manifest)
    if (h.keyPair) keyPair.encode(state, h.keyPair)
    keyValueArray.encode(state, h.userData)
    treeHeader.encode(state, h.tree)
    hints.encode(state, h.hints)
  },
  decode (state) {
    const version = c.uint.decode(state)

    if (version > 1) {
      throw new Error('Invalid header version. Expected <= 1, got ' + version)
    }

    if (version === 0) {
      const old = {
        types: types.decode(state),
        userData: keyValueArray.decode(state),
        tree: treeHeader.decode(state),
        signer: keyPair.decode(state),
        hints: hints.decode(state)
      }

      return {
        external: null,
        key: old.signer.publicKey,
        manifest: {
          version: 0,
          hash: old.types.tree,
          allowPatch: false,
          quorum: 1,
          signers: [{
            signature: old.types.signer,
            namespace: DEFAULT_NAMESPACE,
            publicKey: old.signer.publicKey
          }],
          prologue: null
        },
        keyPair: old.signer.secretKey ? old.signer : null,
        userData: old.userData,
        tree: old.tree,
        hints: old.hints
      }
    }

    const flags = c.uint.decode(state)

    if (flags & 1) {
      return {
        external: externalHeader.decode(state),
        key: null,
        manifest: null,
        keyPair: null,
        userData: null,
        tree: null,
        hints: null
      }
    }

    return {
      external: null,
      key: c.fixed32.decode(state),
      manifest: (flags & 2) !== 0 ? manifest.decode(state) : null,
      keyPair: (flags & 4) !== 0 ? keyPair.decode(state) : null,
      userData: keyValueArray.decode(state),
      tree: treeHeader.decode(state),
      hints: hints.decode(state)
    }
  }
}

const uintArray = c.array(c.uint)

const multisigInput = {
  preencode (state, inp) {
    c.uint.preencode(state, inp.signer)
    c.fixed64.preencode(state, inp.signature)
    c.uint.preencode(state, inp.patch)
  },
  encode (state, inp) {
    c.uint.encode(state, inp.signer)
    c.fixed64.encode(state, inp.signature)
    c.uint.encode(state, inp.patch)
  },
  decode (state) {
    return {
      signer: c.uint.decode(state),
      signature: c.fixed64.decode(state),
      patch: c.uint.decode(state)
    }
  }
}

const patchEncodingv0 = {
  preencode (state, n) {
    c.uint.preencode(state, n.start)
    c.uint.preencode(state, n.length)
    uintArray.preencode(state, n.nodes)
  },
  encode (state, n) {
    c.uint.encode(state, n.start)
    c.uint.encode(state, n.length)
    uintArray.encode(state, n.nodes)
  },
  decode (state) {
    return {
      start: c.uint.decode(state),
      length: c.uint.decode(state),
      nodes: uintArray.decode(state)
    }
  }
}

const multisigInputv0 = {
  preencode (state, n) {
    state.end++
    c.uint.preencode(state, n.signer)
    c.fixed64.preencode(state, n.signature)
    if (n.patch) patchEncodingv0.preencode(state, n.patch)
  },
  encode (state, n) {
    c.uint.encode(state, n.patch ? 1 : 0)
    c.uint.encode(state, n.signer)
    c.fixed64.encode(state, n.signature)
    if (n.patch) patchEncodingv0.encode(state, n.patch)
  },
  decode (state) {
    const flags = c.uint.decode(state)
    return {
      signer: c.uint.decode(state),
      signature: c.fixed64.decode(state),
      patch: (flags & 1) ? patchEncodingv0.decode(state) : null
    }
  }
}

const multisigInputArrayv0 = c.array(multisigInputv0)
const multisigInputArray = c.array(multisigInput)

const compactNode = {
  preencode (state, n) {
    c.uint.preencode(state, n.index)
    c.uint.preencode(state, n.size)
    c.fixed32.preencode(state, n.hash)
  },
  encode (state, n) {
    c.uint.encode(state, n.index)
    c.uint.encode(state, n.size)
    c.fixed32.encode(state, n.hash)
  },
  decode (state) {
    return {
      index: c.uint.decode(state),
      size: c.uint.decode(state),
      hash: c.fixed32.decode(state)
    }
  }
}

const compactNodeArray = c.array(compactNode)

exports.multiSignaturev0 = {
  preencode (state, s) {
    multisigInputArrayv0.preencode(state, s.proofs)
    compactNodeArray.preencode(state, s.patch)
  },
  encode (state, s) {
    multisigInputArrayv0.encode(state, s.proofs)
    compactNodeArray.encode(state, s.patch)
  },
  decode (state) {
    return {
      proofs: multisigInputArrayv0.decode(state),
      patch: compactNodeArray.decode(state)
    }
  }
}

exports.multiSignature = {
  preencode (state, s) {
    multisigInputArray.preencode(state, s.proofs)
    compactNodeArray.preencode(state, s.patch)
  },
  encode (state, s) {
    multisigInputArray.encode(state, s.proofs)
    compactNodeArray.encode(state, s.patch)
  },
  decode (state) {
    return {
      proofs: multisigInputArray.decode(state),
      patch: compactNodeArray.decode(state)
    }
  }
}
const c = require('compact-encoding')
const schema = require('../../encoding/spec/hyperschema')
const { core: coreKey } = require('../../lib/keys.js')
const View = require('../../lib/view.js')

const CORE_AUTH = schema.getEncoding('@core/auth')

const { CorestoreRX, CorestoreTX } = require('../../lib/tx.js')

async function core (core, { version, dryRun = false } = {}) {
  if (dryRun) return // dryRun mode not supported atm

  const rx = core.db.read({ autoDestroy: true })
  const storedAuthPromise = rx.get(coreKey.auth(core.core.corePointer))

  rx.tryFlush()

  const storedAuth = await storedAuthPromise
  if (!storedAuth) return

  const auth = c.decode(CORE_AUTH, storedAuth)
  await commitCoreMigration(auth, core, version)
}

module.exports = { core }

async function commitCoreMigration (auth, core, version) {
  const view = new View()
  const rx = new CorestoreRX(core.db, view)

  const storeCorePromise = rx.getCore(auth.discoveryKey)
  rx.tryFlush()

  const storeCore = await storeCorePromise

  storeCore.version = version

  const tx = new CorestoreTX(view)
  tx.putCore(auth.discoveryKey, storeCore)
  tx.apply()

  await View.flush(view.changes, core.db)
}
{
  "name": "hypercore-storage",
  "version": "3.1.1",
  "main": "index.js",
  "files": [
    "index.js",
    "lib/*.js",
    "encoding/*",
    "migrations/0/*.js",
    "migrations/1/*.js"
  ],
  "scripts": {
    "format": "prettier --write .",
    "lint": "prettier --check . && lunte",
    "test": "npm run test:node && npm run test:bare",
    "test:bare": "brittle-bare test/all.js",
    "test:generate": "brittle -r test/all.js test/*.js",
    "test:node": "brittle-node test/all.js"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "description": "Storage engine for Hypercore",
  "imports": {
    "fs": {
      "bare": "bare-fs",
      "default": "fs"
    },
    "fs/*": {
      "bare": "bare-fs/*",
      "default": "fs/*"
    },
    "os": {
      "bare": "bare-os",
      "default": "os"
    },
    "path": {
      "bare": "bare-path",
      "default": "path"
    }
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/hypercore-storage.git"
  },
  "bugs": {
    "url": "https://github.com/holepunchto/hypercore-storage/issues"
  },
  "homepage": "https://github.com/holepunchto/hypercore-storage#readme",
  "dependencies": {
    "b4a": "^1.6.7",
    "bare-path": "^3.0.0",
    "compact-encoding": "^3.1.0",
    "device-file": "^2.1.2",
    "flat-tree": "^1.12.1",
    "hypercore-crypto": "^3.4.2",
    "hyperschema": "^1.21.0",
    "index-encoder": "^3.3.2",
    "resolve-reject-promise": "^1.0.0",
    "rocksdb-native": "^3.11.0",
    "scope-lock": "^1.2.4",
    "streamx": "^2.21.1"
  },
  "devDependencies": {
    "bare-os": "^3.9.1",
    "brittle": "^3.7.0",
    "hypercore-storage-2.9.0": "npm:hypercore-storage@^2.9.0",
    "lunte": "^1.3.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^2.0.0",
    "test-tmp": "^1.3.1"
  }
}
const { EventEmitter } = require('events')
const isOptions = require('is-options')
const crypto = require('hypercore-crypto')
const CoreStorage = require('hypercore-storage')
const c = require('compact-encoding')
const b4a = require('b4a')
const NoiseSecretStream = require('@hyperswarm/secret-stream')
const Protomux = require('protomux')
const id = require('hypercore-id-encoding')
const safetyCatch = require('safety-catch')
const unslab = require('unslab')
const flat = require('flat-tree')
const assert = require('nanoassert')

const { SMALL_WANTS } = require('./lib/feature-flags')
const { UPDATE_COMPAT } = require('./lib/wants')
const MarkBitfield = require('./lib/mark-bitfield')

const inspect = require('./lib/inspect')
const Core = require('./lib/core')
const Info = require('./lib/info')
const Download = require('./lib/download')
const DefaultEncryption = require('./lib/default-encryption')
const caps = require('./lib/caps')
const Replicator = require('./lib/replicator')
const { manifestHash, createManifest } = require('./lib/verifier')
const { ReadStream, WriteStream, ByteStream } = require('./lib/streams')
const { MerkleTree } = require('./lib/merkle-tree')
const { proof, verify } = require('./lib/fully-remote-proof')
const {
  ASSERTION,
  BAD_ARGUMENT,
  SESSION_CLOSED,
  SESSION_MOVED,
  SESSION_NOT_WRITABLE,
  SNAPSHOT_NOT_AVAILABLE,
  DECODING_ERROR,
  REQUEST_CANCELLED
} = require('hypercore-errors')

// Hypercore actually does not have any notion of max/min block sizes
// but we enforce 15mb to ensure smooth replication (each block is transmitted atomically)
const MAX_SUGGESTED_BLOCK_SIZE = 15 * 1024 * 1024

class Hypercore extends EventEmitter {
  constructor(storage, key, opts) {
    super()

    if (isOptions(storage) && !storage.db) {
      opts = storage
      storage = null
      key = opts.key || null
    } else if (isOptions(key)) {
      opts = key
      key = opts.key || null
    }

    if (key && typeof key === 'string') key = id.decode(key)
    if (!opts) opts = {}

    if (!storage) storage = opts.storage

    this.core = null
    this.state = null
    this.encryption = null
    this.extensions = new Map()

    this.valueEncoding = null
    this.encodeBatch = null
    this.activeRequests = []
    this.sessions = null
    this.ongc = null

    this.keyPair = opts.keyPair || null
    this.readable = true
    this.writable = false
    this.exclusive = false
    this.opened = false
    this.closed = false
    this.weak = !!opts.weak
    this.snapshotted = !!opts.snapshot
    this.onseq = opts.onseq || null
    this.onwait = opts.onwait || null
    this.wait = opts.wait !== false
    this.timeout = opts.timeout || 0
    this.preload = null
    this.closing = null
    this.opening = null

    this._readonly = opts.writable === false
    this._preappend = preappend.bind(this)
    this._snapshot = null
    this._findingPeers = 0
    this._active = opts.weak ? !!opts.active : opts.active !== false

    this.waits = 0

    // Mark & Sweep GC
    this._marking = false
    this._marks = null

    this._sessionIndex = -1
    this._stateIndex = -1 // maintained by session state
    this._monitorIndex = -1 // maintained by replication state

    this.opening = this._open(storage, key, opts)
    this.opening.catch(safetyCatch)

    this.on('newListener', maybeAddMonitor)
  }

  [Symbol.for('nodejs.util.inspect.custom')](depth, opts) {
    return inspect(this, depth, opts)
  }

  static MAX_SUGGESTED_BLOCK_SIZE = MAX_SUGGESTED_BLOCK_SIZE

  static DefaultEncryption = DefaultEncryption

  static SMALL_WANTS = SMALL_WANTS

  static enable(flag) {
    const enableCompat = (flag & SMALL_WANTS) === 0
    UPDATE_COMPAT(enableCompat)
  }

  static setRecoveryPeers(peers) {
    Core.setRecoveryPeers(peers)
  }

  static key(manifest, { compat, version, namespace } = {}) {
    if (b4a.isBuffer(manifest)) {
      manifest = { version, signers: [{ publicKey: manifest, namespace }] }
    }
    return compat ? manifest.signers[0].publicKey : manifestHash(createManifest(manifest))
  }

  static discoveryKey(key) {
    return crypto.discoveryKey(key)
  }

  static blockEncryptionKey(key, encryptionKey) {
    return DefaultEncryption.blockEncryptionKey(key, encryptionKey)
  }

  static getProtocolMuxer(stream) {
    return stream.noiseStream.userData
  }

  static createCore(storage, opts) {
    return new Core(Hypercore.defaultStorage(storage), { autoClose: false, ...opts })
  }

  static createProtocolStream(isInitiator, opts = {}) {
    let outerStream = Protomux.isProtomux(isInitiator)
      ? isInitiator.stream
      : isStream(isInitiator)
        ? isInitiator
        : opts.stream

    let noiseStream = null

    if (outerStream) {
      noiseStream = outerStream.noiseStream
    } else {
      noiseStream = new NoiseSecretStream(isInitiator, null, opts)
      outerStream = noiseStream.rawStream
    }
    if (!noiseStream) throw BAD_ARGUMENT('Invalid stream', this.discoveryKey)

    if (!noiseStream.userData) {
      const protocol = Protomux.from(noiseStream)

      if (opts.keepAlive !== false && noiseStream.keepAlive === 0) {
        noiseStream.setKeepAlive(5000)
      }
      noiseStream.userData = protocol
    }

    if (opts.ondiscoverykey) {
      const ondiscoverykey = createDiscoveryKeyHandler(opts.ondiscoverykey)
      noiseStream.userData.pair({ protocol: 'hypercore/alpha' }, ondiscoverykey)
    }

    return outerStream
  }

  static defaultStorage(storage, opts = {}) {
    if (CoreStorage.isCoreStorage(storage)) return storage

    const directory = storage
    return new CoreStorage(directory, opts)
  }

  static clearRequests(session, err) {
    Replicator.clearRequests(session, err)
  }

  static destroyRequests(session, err) {
    Replicator.clearRequests(session, err)
    session.push(null) // mark as dead
  }

  static async treeHashFromStorage(session, length = session.length) {
    const roots = await MerkleTree.getRoots(session.state, length)
    return crypto.tree(roots)
  }

  snapshot(opts) {
    return this.session({ ...opts, snapshot: true })
  }

  compact() {
    return this.core.compact()
  }

  session(opts = {}) {
    if (this.closing) {
      // This makes the closing logic a lot easier. If this turns out to be a problem
      // in practice, open an issue and we'll try to make a solution for it.
      throw SESSION_CLOSED('Cannot make sessions on a closing core', this.discoveryKey)
    }
    if (opts.checkout !== undefined && !opts.name && !opts.atom) {
      throw ASSERTION('Checkouts are only supported on atoms or named sessions', this.discoveryKey)
    }

    const wait = opts.wait === false ? false : this.wait
    const writable = opts.writable === undefined ? !this._readonly : opts.writable === true
    const onwait = opts.onwait === undefined ? this.onwait : opts.onwait
    const onseq = opts.onseq === undefined ? this.onseq : opts.onseq
    const timeout = opts.timeout === undefined ? this.timeout : opts.timeout
    const weak = opts.weak === undefined ? this.weak : opts.weak
    const marking = this._marking
    const marks = this._marks
    const Clz = opts.class || Hypercore
    const s = new Clz(null, this.key, {
      ...opts,
      wait,
      onwait,
      onseq,
      timeout,
      writable,
      weak,
      parent: this
    })
    s._marking = marking
    s._marks = marks

    return s
  }

  async setEncryptionKey(key, opts) {
    if (!this.opened) await this.opening
    const encryption = this._getEncryptionProvider({ key, block: !!(opts && opts.block) })
    return this.setEncryption(encryption)
  }

  async setEncryption(encryption) {
    if (!this.opened) await this.opening

    if (encryption === null) {
      this.encryption = encryption
      return
    }

    if (!isEncryptionProvider(encryption)) {
      throw ASSERTION('Provider does not satisfy HypercoreEncryption interface', this.discoveryKey)
    }

    this.encryption = encryption
  }

  async setGroup(topic) {
    if (!this.opened) await this.opening
    return this.core.setGroup(topic)
  }

  setKeyPair(keyPair) {
    this.keyPair = keyPair
  }

  setActive(bool) {
    const active = !!bool
    if (active === this._active || this.closing) return
    this._active = active
    if (!this.opened) return
    this.core.replicator.updateActivity(this._active ? 1 : -1)
  }

  async _open(storage, key, opts) {
    const preload = opts.preload || (opts.parent && opts.parent.preload)

    if (preload) {
      this.sessions = [] // in case someone looks at it like with peers
      this.preload = preload
      opts = { ...opts, ...(await this.preload) }
      this.preload = null
    }

    const parent = opts.parent || null
    const core = opts.core || (parent && parent.core)
    const sessions = opts.sessions || (parent && parent.sessions)
    const ongc = opts.ongc || (parent && parent.ongc)

    if (core) this.core = core
    if (ongc) this.ongc = ongc
    if (sessions) this.sessions = sessions

    if (this.sessions === null) this.sessions = []
    this._sessionIndex = this.sessions.push(this) - 1

    if (this.core === null) initOnce(this, storage, key, opts)
    if (this._monitorIndex === -2) this.core.addMonitor(this)

    try {
      await this._openSession(opts)
    } catch (err) {
      if (this.core.autoClose && this.core.hasSession() === false) await this.core.close()

      if (this.exclusive) this.core.unlockExclusive()

      this.core.removeMonitor(this)
      this._removeSession()

      if (this.state !== null) this.state.removeSession(this)

      this.closed = true
      this.emit('close')
      throw err
    }

    // Setup automatic recovery if in repair mode
    if (this.core._repairMode) {
      const recoverTreeNodeFromPeersBound = this.recoverTreeNodeFromPeers.bind(this)
      this.once('repaired', () => {
        this.off('peer-add', recoverTreeNodeFromPeersBound)
      })
      this.on('peer-add', recoverTreeNodeFromPeersBound)
    }

    this.emit('ready')

    // if we are a weak session the core might have closed...
    if (this.core.closing) this.close().catch(safetyCatch)
  }

  _removeSession() {
    if (this._sessionIndex === -1) return
    const head = this.sessions.pop()
    if (head !== this) this.sessions[(head._sessionIndex = this._sessionIndex)] = head
    this._sessionIndex = -1
    if (this.ongc !== null) this.ongc(this)
  }

  async _openSession(opts) {
    if (this.core.opened === false) await this.core.ready()

    if (this.keyPair === null) this.keyPair = opts.keyPair || this.core.header.keyPair

    const parent = opts.parent || null
    if (parent && parent.encryption) this.encryption = parent.encryption

    const e = getEncryptionOption(opts)
    if (!this.encryption) this.encryption = this._getEncryptionProvider(e)

    this.writable = this._isWritable()

    if (opts.valueEncoding) {
      this.valueEncoding = c.from(opts.valueEncoding)
    }
    if (opts.encodeBatch) {
      this.encodeBatch = opts.encodeBatch
    }

    // one session sets for pushOnly for all
    if (opts.pushOnly === true) {
      this.core.replicator.setPushOnly(true)
    }

    if (parent) {
      if (parent._stateIndex === -1) await parent.ready()
      if (!this.keyPair) this.keyPair = parent.keyPair

      const ps = parent.state

      if (ps) {
        const shouldSnapshot = this.snapshotted && !ps.isSnapshot()
        this.state = shouldSnapshot ? await ps.snapshot() : ps.ref()
      }

      if (this.snapshotted && this.core && !this._snapshot) {
        this._updateSnapshot()
      }
    }

    if (opts.exclusive && opts.writable !== false) {
      this.exclusive = true
      await this.core.lockExclusive()
    }

    const parentState = parent ? parent.state : this.core.state
    const checkout = opts.checkout === undefined ? -1 : opts.checkout
    const state = this.state

    if (opts.atom) {
      this.state = await parentState.createSession(null, false, opts.atom)
      if (state) state.unref()
    } else if (opts.name) {
      // todo: need to make named sessions safe before ready
      // atm we always copy the state in passCapabilities
      this.state = await parentState.createSession(opts.name, !!opts.overwrite, null)
      if (state) state.unref() // ref'ed above in setup session
    }

    if (this.state && checkout !== -1) {
      if (!opts.name && !opts.atom) {
        throw ASSERTION('Checkouts must be named or atomized', this.discoveryKey)
      }
      if (checkout > this.state.length) {
        throw ASSERTION(
          `Invalid checkout ${checkout} for ${opts.name}, length is ${this.state.length}`,
          this.discoveryKey
        )
      }
      if (this.state.prologue && checkout < this.state.prologue.length) {
        throw ASSERTION(
          `Invalid checkout ${checkout} for ${opts.name}, prologue length is ${this.state.prologue.length}`,
          this.discoveryKey
        )
      }
      if (checkout < this.state.length) await this.state.truncate(checkout, this.fork)
    }

    if (this.state === null) {
      this.state = this.core.state.ref()
    }

    this.writable = this._isWritable()

    if (this.snapshotted && this.core) this._updateSnapshot()

    this.state.addSession(this)
    // TODO: we need to rework the core reference flow, as the state and session do not always agree now due to moveTo
    this.core = this.state.core // in case it was wrong...

    if (opts.userData) {
      const tx = this.state.storage.write()
      for (const [key, value] of Object.entries(opts.userData)) {
        tx.putUserData(key, value)
      }
      await tx.flush()
    }

    if (opts.manifest && !this.core.header.manifest) {
      await this.core.setManifest(createManifest(opts.manifest))
    }

    this.core.replicator.updateActivity(this._active ? 1 : 0)

    this.opened = true
  }

  get replicator() {
    return this.core === null ? null : this.core.replicator
  }

  _getSnapshot() {
    return {
      length: this.state.length,
      byteLength: this.state.byteLength,
      fork: this.state.fork
    }
  }

  _updateSnapshot() {
    const prev = this._snapshot
    const next = (this._snapshot = this._getSnapshot())

    if (!prev) return true
    return prev.length !== next.length || prev.fork !== next.fork
  }

  _isWritable() {
    if (this._readonly) return false
    if (this.state && !this.state.isDefault()) return true
    return !!(this.keyPair && this.keyPair.secretKey)
  }

  close({ error } = {}) {
    if (this.closing) return this.closing

    this.closing = this._close(error || null)
    return this.closing
  }

  clearRequests(activeRequests, error) {
    if (!activeRequests.length) return
    if (this.core) this.core.replicator.clearRequests(activeRequests, error)
  }

  async _close(error) {
    if (this.opened === false) {
      try {
        await this.opening
      } catch (err) {
        if (!this.closed) throw err
      }
    }

    if (this.closed === true) return

    this.core.removeMonitor(this)
    this.state.removeSession(this)
    this._removeSession()

    this.readable = false
    this.writable = false
    this.opened = false

    const gc = []
    for (const ext of this.extensions.values()) {
      if (ext.session === this) gc.push(ext)
    }
    for (const ext of gc) ext.destroy()

    this.core.replicator.findingPeers -= this._findingPeers
    this.core.replicator.clearRequests(this.activeRequests, error)
    this.core.replicator.updateActivity(this._active ? -1 : 0)

    this._findingPeers = 0

    this.state.unref()

    if (this.exclusive) this.core.unlockExclusive()

    if (this.core.hasSession()) {
      // emit "fake" close as this is a session
      this.closed = true
      this.emit('close')
      return
    }

    if (this.core.autoClose) await this.core.close()

    this.closed = true
    this.emit('close')
  }

  async commit(session, opts) {
    await this.ready()
    await session.ready()

    return this.state.commit(session.state, { keyPair: this.keyPair, ...opts })
  }

  replicate(isInitiator, opts = {}) {
    // Only limitation here is that ondiscoverykey doesn't work atm when passing a muxer directly,
    // because it doesn't really make a lot of sense.
    if (Protomux.isProtomux(isInitiator)) return this._attachToMuxer(isInitiator)

    // if same stream is passed twice, ignore the 2nd one before we make sessions etc
    if (isStream(isInitiator) && this._isAttached(isInitiator)) return isInitiator

    const protocolStream = Hypercore.createProtocolStream(isInitiator, opts)
    const noiseStream = protocolStream.noiseStream
    const protocol = noiseStream.userData

    this._attachToMuxer(protocol)

    return protocolStream
  }

  _isAttached(stream) {
    return (
      stream.userData &&
      this.core &&
      this.core.replicator &&
      this.core.replicator.attached(stream.userData)
    )
  }

  _attachToMuxer(mux) {
    if (this.opened) {
      this.core.replicator.attachTo(mux)
    } else {
      this.opening.then(() => this.core.replicator.attachTo(mux), mux.destroy.bind(mux))
    }

    return mux
  }

  get id() {
    return this.core === null ? null : this.core.id
  }

  get key() {
    return this.core === null ? null : this.core.key
  }

  get discoveryKey() {
    return this.core === null ? null : this.core.discoveryKey
  }

  get manifest() {
    return this.core === null ? null : this.core.manifest
  }

  get length() {
    if (this._snapshot) return this._snapshot.length
    return this.opened === false ? 0 : this.state.length
  }

  get signedLength() {
    return this.opened === false ? 0 : this.state.signedLength()
  }

  /**
   * Deprecated. Use `const { byteLength } = await core.info()`.
   */
  get byteLength() {
    if (this.opened === false) return 0
    if (this._snapshot) return this._snapshot.byteLength
    return this.state.byteLength - this.state.length * this.padding
  }

  get remoteContiguousLength() {
    if (this.opened === false) return 0
    return Math.min(this.core.state.length, this.core.header.hints.remoteContiguousLength)
  }

  get contiguousLength() {
    if (this.opened === false) return 0
    return Math.min(this.core.state.length, this.core.header.hints.contiguousLength)
  }

  get contiguousByteLength() {
    return 0
  }

  get fork() {
    if (this.opened === false) return 0
    return this.state.fork
  }

  get padding() {
    if (this.encryption && this.key && this.manifest) {
      return this.encryption.padding(this.core, this.length)
    }

    return 0
  }

  get peers() {
    return this.opened === false ? [] : this.core.replicator.peers
  }

  get globalCache() {
    return this.opened === false ? null : this.core.globalCache
  }

  get recovering() {
    return this.opened === false ? 0 : this.core.header.hints.recovering
  }

  ready() {
    return this.opening
  }

  async recover() {
    if (this.opened === false) await this.opening
    return this.state.waitForRecovery()
  }

  async setUserData(key, value) {
    if (this.opened === false) await this.opening
    const existing = await this.getUserData(key)
    if (existing && b4a.isBuffer(value) && b4a.equals(existing, value)) return
    await this.state.setUserData(key, value)
  }

  async getUserData(key) {
    if (this.opened === false) await this.opening
    const batch = this.state.storage.read()
    const p = batch.getUserData(key)
    batch.tryFlush()
    return p
  }

  transferSession(core) {
    // todo: validate we can move

    if (this.weak === false) {
      this.core.activeSessions--
      core.activeSessions++
    }

    if (this._monitorIndex >= 0) {
      this.core.removeMonitor(this)
      core.addMonitor(this)
    }

    const old = this.core

    this.core = core

    old.replicator.clearRequests(this.activeRequests, SESSION_MOVED())

    this.emit('migrate', this.key)
  }

  findingPeers() {
    this._findingPeers++
    if (this.core !== null && !this.closing) this.core.replicator.findingPeers++

    let once = true

    return () => {
      if (this.closing || !once) return
      once = false
      this._findingPeers--
      if (this.core !== null && --this.core.replicator.findingPeers === 0) {
        this.core.replicator.updateAll()
      }
    }
  }

  async info(opts) {
    if (this.opened === false) await this.opening

    return Info.from(this, opts)
  }

  async update(opts) {
    if (this.opened === false) await this.opening
    if (this.closing !== null) return false
    if (this.snapshotted) return false

    if (this.writable && (!opts || opts.force !== true)) return false

    const remoteWait = this._shouldWait(opts, this.core.replicator.findingPeers > 0)

    let upgraded = false

    if (await this.core.replicator.applyPendingReorg()) {
      upgraded = true
    }

    if (!upgraded && remoteWait) {
      const activeRequests = (opts && opts.activeRequests) || this.activeRequests
      if (isRequestsDestroyed(activeRequests)) throw REQUEST_CANCELLED()

      const req = this.core.replicator.addUpgrade(activeRequests)

      try {
        upgraded = await req.promise
      } catch (err) {
        if (isSessionMoved(err)) return this.update(opts)
        throw err
      }
    }

    if (!upgraded) return false
    return true
  }

  async seek(bytes, opts) {
    if (this.opened === false) await this.opening
    if (!isValidIndex(bytes)) throw ASSERTION('seek is invalid', this.discoveryKey)

    const activeRequests = (opts && opts.activeRequests) || this.activeRequests
    if (isRequestsDestroyed(activeRequests)) throw REQUEST_CANCELLED()

    if (this.encryption && !this.core.manifest) {
      const req = this.replicator.addUpgrade(activeRequests)
      try {
        await req.promise
      } catch (err) {
        if (isSessionMoved(err)) return this.seek(bytes, opts)
        throw err
      }
    }

    const s = MerkleTree.seek(this.state, bytes, this.padding)

    const offset = await s.update()
    if (offset) return offset

    if (this.closing !== null) {
      throw SESSION_CLOSED('cannot seek on a closed session', this.discoveryKey)
    }

    if (!this._shouldWait(opts, this.wait)) return null

    if (isRequestsDestroyed(activeRequests)) throw REQUEST_CANCELLED()
    const req = this.core.replicator.addSeek(activeRequests, s)

    const timeout = opts && opts.timeout !== undefined ? opts.timeout : this.timeout
    if (timeout) req.context.setTimeout(req, timeout)

    try {
      return await req.promise
    } catch (err) {
      if (isSessionMoved(err)) return this.seek(bytes, opts)
      throw err
    }
  }

  async has(start, end = start + 1) {
    if (this.opened === false) await this.opening
    if (!isValidIndex(start) || !isValidIndex(end)) {
      throw ASSERTION('has range is invalid', this.discoveryKey)
    }

    if (this.state.isDefault()) {
      if (end === start + 1) return this.core.bitfield.get(start)

      const i = this.core.bitfield.firstUnset(start)
      return i === -1 || i >= end
    }

    if (end === start + 1) {
      const rx = this.state.storage.read()
      const block = rx.getBlock(start)
      rx.tryFlush()

      return (await block) !== null
    }

    let count = 0

    const stream = this.state.storage.createBlockStream({ gte: start, lt: end })
    for await (const block of stream) {
      if (block === null) return false
      count++
    }

    return count === end - start
  }

  async get(index, opts) {
    if (this.opened === false) await this.opening
    if (!isValidIndex(index)) throw ASSERTION('block index is invalid', this.discoveryKey)

    if (this.closing !== null) {
      throw SESSION_CLOSED('cannot get on a closed session', this.discoveryKey)
    }

    const encoding =
      (opts && opts.valueEncoding && c.from(opts.valueEncoding)) || this.valueEncoding

    if (this.onseq !== null) this.onseq(index, this)
    if (this._marking) await this.markBlock(index)

    const req = this._get(index, opts)

    let block = await req
    if (!block) return null

    if (opts && opts.raw) return block

    if (this.encryption && (!opts || opts.decrypt !== false)) {
      // Copy the block as it might be shared with other sessions.
      block = b4a.from(block)

      await this.encryption.decrypt(index, block, this.core)
    }

    return this._decode(encoding, block, index)
  }

  async clear(start, end = start + 1, opts) {
    if (this.opened === false) await this.opening
    if (this.closing !== null) {
      throw SESSION_CLOSED('cannot clear on a closed session', this.discoveryKey)
    }

    if (typeof end === 'object') {
      opts = end
      end = start + 1
    }

    if (!isValidIndex(start) || !isValidIndex(end)) {
      throw ASSERTION('clear range is invalid', this.discoveryKey)
    }

    const cleared = opts && opts.diff ? { blocks: 0 } : null

    if (start >= end) return cleared
    if (start >= this.length) return cleared

    await this.state.clear(start, end, cleared)

    return cleared
  }

  async purge() {
    await this._closeAllSessions(null)
    await this.core.purge()
  }

  async _get(index, opts) {
    const block = await readBlock(this.state.storage.read(), index)

    if (block !== null) return block

    if (this.closing !== null) {
      throw SESSION_CLOSED('cannot get on a closed session', this.discoveryKey)
    }

    // snapshot should check if core has block
    if (this._snapshot !== null) {
      checkSnapshot(this, index)
      const coreBlock = await readBlock(this.core.state.storage.read(), index)

      checkSnapshot(this, index)
      if (coreBlock !== null) return coreBlock
    }

    // lets check the bitfield to see if we got it during the above async calls
    // this is the last resort before replication, so always safe.
    if (this.core.bitfield.get(index)) {
      const coreBlock = await readBlock(this.state.storage.read(), index)
      // TODO: this should not be needed, only needed atm in case we are doing a moveTo during this (we should fix)
      if (coreBlock !== null) return coreBlock
    }

    if (!this._shouldWait(opts, this.wait)) return null

    this.waits++
    if (opts && opts.onwait) opts.onwait(index, this)
    if (this.onwait) this.onwait(index, this)

    const activeRequests = (opts && opts.activeRequests) || this.activeRequests
    if (isRequestsDestroyed(activeRequests)) throw REQUEST_CANCELLED()

    const force = opts ? opts.force === true : false
    const req = this.core.replicator.addBlock(activeRequests, index, force)
    req.snapshot = index < this.length

    const timeout = opts && opts.timeout !== undefined ? opts.timeout : this.timeout
    if (timeout) req.context.setTimeout(req, timeout)

    let replicatedBlock = null

    try {
      replicatedBlock = await req.promise
    } catch (err) {
      if (isSessionMoved(err)) return this._get(index, opts)
      throw err
    }

    if (this._snapshot !== null) checkSnapshot(this, index)
    return maybeUnslab(replicatedBlock)
  }

  _shouldWait(opts, defaultValue) {
    if (opts) {
      if (opts.wait === false) return false
      if (opts.wait === true) return true
    }
    return defaultValue
  }

  _setupMarks() {
    if (this._marks === null) {
      const storage = this.snapshotted ? this.core.state.storage : this.state.storage
      this._marks = new MarkBitfield(storage)
    }
  }

  async markBlock(start, end = start + 1) {
    if (this.opened === false) await this.opening

    this._setupMarks()

    // TODO support as single rocks batch
    const setPromises = []
    for (let i = start; i < end; i++) {
      setPromises.push(this._marks.set(i, true))
    }

    return Promise.all(setPromises)
  }

  async clearMarkings() {
    if (this.opened === false) await this.opening

    this._setupMarks()

    await this._marks.clear()
    this._marks = null
  }

  async startMarking() {
    if (this._marking) {
      throw ASSERTION("Hypercore cannot be gc'ed when already in gc mode", this.discoveryKey)
    }
    if (this.state && this.state.name) {
      throw ASSERTION("Hypercore cannot be gc'ed when a named session", this.discoveryKey)
    }
    if (this.state && this.state.storage.atom) {
      throw ASSERTION("Hypercore cannot be gc'ed when an atomic session", this.discoveryKey)
    }
    if (this.opened === false) await this.opening
    await this.clearMarkings()

    this._marking = true
  }

  async sweep({ batchSize = 1000 } = {}) {
    if (this.opened === false) await this.opening

    assert(!this.snapshotted, 'Cannot sweep a snapshot')

    // No marks - load from storage
    this._setupMarks()

    let clearing = []
    let prevIndex = this.length
    for await (const index of this._marks.createMarkStream({ reverse: true })) {
      if (index + 1 === prevIndex) {
        prevIndex = index
        continue
      }
      clearing.push(this.clear(index + 1, prevIndex))
      if (clearing.length >= batchSize) {
        await Promise.all(clearing)
        clearing = []
      }
      prevIndex = index
    }
    // Clear range from the very start if not marked
    if (prevIndex > 0) clearing.push(this.clear(0, prevIndex))
    await Promise.all(clearing)

    this._marking = false
    await this.clearMarkings()
  }

  createReadStream(opts) {
    return new ReadStream(this, opts)
  }

  createWriteStream() {
    return new WriteStream(this)
  }

  createByteStream(opts) {
    return new ByteStream(this, opts)
  }

  download(range) {
    return new Download(this, range)
  }

  // TODO: get rid of this / deprecate it?
  undownload(range) {
    range.destroy(null)
  }

  // TODO: get rid of this / deprecate it?
  cancel(request) {
    // Do nothing for now
  }

  async truncate(newLength = 0, opts = {}) {
    if (this.opened === false) await this.opening
    if (this.closing) throw SESSION_CLOSED('Cannot append to a closed session', this.discoveryKey)

    const {
      fork = this.state.fork + 1,
      keyPair = this.keyPair,
      signature = null
    } = typeof opts === 'number' ? { fork: opts } : opts

    const isDefault = this.state === this.core.state
    const writable = !this._readonly && !!(signature || (keyPair && keyPair.secretKey))
    if (isDefault && writable === false && (newLength > 0 || fork !== this.state.fork)) {
      throw SESSION_NOT_WRITABLE('cannot append to a non-writable core', this.discoveryKey)
    }

    await this.state.truncate(newLength, fork, { keyPair, signature })

    // TODO: Should propagate from an event triggered by the oplog
    if (this.state === this.core.state) this.core.replicator.updateAll()
  }

  async append(blocks, opts = {}) {
    if (this.opened === false) await this.opening
    if (this.closing) throw SESSION_CLOSED('Cannot append to a closed session', this.discoveryKey)

    const isDefault = this.state === this.core.state
    const defaultKeyPair = this.state.name === null ? this.keyPair : null

    const { keyPair = defaultKeyPair, signature = null, maxLength, postappend = null } = opts
    const writable =
      !isDefault || !!signature || !!(keyPair && keyPair.secretKey) || opts.writable === true

    if (this._readonly || writable === false) {
      throw SESSION_NOT_WRITABLE('cannot append to a readonly core', this.discoveryKey)
    }

    blocks = Array.isArray(blocks) ? blocks : [blocks]

    const preappend = this.encryption && this._preappend

    const buffers = this.encodeBatch !== null ? this.encodeBatch(blocks) : new Array(blocks.length)

    if (this.encodeBatch === null) {
      for (let i = 0; i < blocks.length; i++) {
        buffers[i] = this._encode(this.valueEncoding, blocks[i])
      }
    }
    for (const b of buffers) {
      if (b.byteLength > MAX_SUGGESTED_BLOCK_SIZE) {
        throw BAD_ARGUMENT(
          'Appended block exceeds the maximum suggested block size',
          this.discoveryKey
        )
      }
    }

    return this.state.append(buffers, { keyPair, signature, preappend, postappend, maxLength })
  }

  async signable(length = -1, fork = -1) {
    if (this.opened === false) await this.opening
    if (length === -1) length = this.length
    if (fork === -1) fork = this.fork

    return caps.treeSignable(this.key, await this.treeHash(length), length, fork)
  }

  async treeHash(length = -1) {
    if (this.opened === false) await this.opening
    if (length === -1) length = this.length
    if (length > 0 && !(await this.has(length - 1))) await this.get(length - 1)

    const roots = await MerkleTree.getRoots(this.state, length)
    return crypto.tree(roots)
  }

  async missingNodes(index) {
    if (this.opened === false) await this.opening
    return await MerkleTree.missingNodes(this.core.state, 2 * index, this.core.state.length)
  }

  async proof(opts) {
    if (this.opened === false) await this.opening
    const rx = this.state.storage.read()
    const proofPromise = MerkleTree.proof(this.state, rx, opts)
    const blockPromise = opts && opts.block ? rx.getBlock(opts.block.index) : null
    rx.tryFlush()
    const [proof, block] = await Promise.all([proofPromise, blockPromise])
    const settled = await proof.settle()
    if (block) settled.block.value = block
    return settled
  }

  async applyProof(proof, from) {
    if (this.opened === false) await this.opening
    return this.core.verify(proof, from)
  }

  async verifyFullyRemote(proof) {
    if (this.opened === false) await this.opening
    const batch = await MerkleTree.verifyFullyRemote(this.state, proof)
    await this.core._verifyBatchUpgrade(batch, proof.manifest)
    return batch
  }

  generateRemoteProofForTreeNode(treeNodeIndex) {
    const blockProofIndex = flat.rightSpan(treeNodeIndex) / 2
    return proof(this, {
      index: blockProofIndex,
      // + 1 to length so the block is included
      upgrade: { start: 0, length: blockProofIndex + 1 }
    })
  }

  async recoverFromRemoteProof(remoteProof) {
    this.core.replicator.setPushOnly(true)
    this.core._repairMode = true

    await this.core.state.mutex.lock()

    try {
      const p = await verify(this.core.db, remoteProof)
      if (!p) return false

      const tx = this.core.storage.write()
      for (const node of p.proof.upgrade.nodes) {
        tx.putTreeNode(node)
      }
      await tx.flush()

      const succeed = p.proof.upgrade.nodes.length !== 0
      if (succeed) {
        this.core.replicator.setPushOnly(false)
      }
      return succeed
    } finally {
      this.core.state.mutex.unlock()
    }
  }

  recoverTreeNodeFromPeers() {
    this.core.replicator.setPushOnly(true)

    for (const peer of this.core.replicator.peers) {
      const req = {
        id: 0,
        fork: this.fork,
        upgrade: {
          start: 0,
          length: this.length
        }
      }
      peer.wireRequest.send(req)
    }
  }

  registerExtension(name, handlers = {}) {
    if (this.extensions.has(name)) {
      const ext = this.extensions.get(name)
      ext.handlers = handlers
      ext.encoding = c.from(handlers.encoding || c.buffer)
      ext.session = this
      return ext
    }

    const ext = {
      name,
      handlers,
      encoding: c.from(handlers.encoding || c.buffer),
      session: this,
      send(message, peer) {
        const buffer = c.encode(this.encoding, message)
        peer.extension(name, buffer)
      },
      broadcast(message) {
        const buffer = c.encode(this.encoding, message)
        for (const peer of this.session.peers) {
          peer.extension(name, buffer)
        }
      },
      destroy() {
        for (const peer of this.session.peers) {
          if (peer.extensions.get(name) === ext) peer.extensions.delete(name)
        }
        this.session.extensions.delete(name)
      },
      _onmessage(state, peer) {
        const m = this.encoding.decode(state)
        if (this.handlers.onmessage) this.handlers.onmessage(m, peer)
      }
    }

    this.extensions.set(name, ext)

    if (this.core === null) this._monitorIndex = -2
    else this.core.addMonitor(this)

    for (const peer of this.peers) {
      peer.extensions.set(name, ext)
    }

    return ext
  }

  _encode(enc, val) {
    const state = { start: this.padding, end: this.padding, buffer: null }

    if (b4a.isBuffer(val)) {
      if (state.start === 0) return val
      state.end += val.byteLength
    } else if (enc) {
      enc.preencode(state, val)
    } else {
      val = b4a.from(val)
      if (state.start === 0) return val
      state.end += val.byteLength
    }

    state.buffer = b4a.allocUnsafe(state.end)

    if (enc) enc.encode(state, val)
    else state.buffer.set(val, state.start)

    return state.buffer
  }

  _decode(enc, block, index) {
    if (this.encryption) block = block.subarray(this.encryption.padding(this.core, index))
    try {
      if (enc) return c.decode(enc, block)
    } catch (err) {
      throw DECODING_ERROR(err.message, this.discoveryKey)
    }
    return block
  }

  _getEncryptionProvider(e) {
    if (isEncryptionProvider(e)) return e
    if (!e || !e.key) return null
    return new DefaultEncryption(e.key, this.key, { block: e.block, compat: this.core.compat })
  }
}

module.exports = Hypercore

function isStream(s) {
  return typeof s === 'object' && s && typeof s.pipe === 'function'
}

async function preappend(blocks) {
  const offset = this.state.length
  const fork = this.state.encryptionFork

  for (let i = 0; i < blocks.length; i++) {
    await this.encryption.encrypt(offset + i, blocks[i], fork, this.core)
  }
}

function isValidIndex(index) {
  return index === 0 || index > 0
}

function maybeUnslab(block) {
  // Unslab only when it takes up less then half the slab
  return block !== null && 2 * block.byteLength < block.buffer.byteLength ? unslab(block) : block
}

function checkSnapshot(snapshot, index) {
  if (index >= snapshot.state.snapshotCompatLength) {
    throw SNAPSHOT_NOT_AVAILABLE(
      `snapshot at index ${index} not available (max compat length ${snapshot.state.snapshotCompatLength})`,
      snapshot.discoveryKey
    )
  }
}

function readBlock(rx, index) {
  const promise = rx.getBlock(index)
  rx.tryFlush()
  return promise
}

function initOnce(session, storage, key, opts) {
  if (storage === null) storage = opts.storage || null
  if (key === null) key = opts.key || null

  session.core = new Core(Hypercore.defaultStorage(storage), {
    preopen: opts.preopen,
    eagerUpgrade: opts.eagerUpgrade !== false,
    notDownloadingLinger: opts.notDownloadingLinger,
    allowFork: opts.allowFork !== false,
    allowPush: !!opts.allowPush,
    pushOnly: !!opts.pushOnly,
    alwaysLatestBlock: !!opts.allowLatestBlock,
    inflightRange: opts.inflightRange,
    compat: opts.compat === true,
    force: opts.force,
    createIfMissing: opts.createIfMissing,
    discoveryKey: opts.discoveryKey,
    overwrite: opts.overwrite,
    key,
    keyPair: opts.keyPair,
    legacy: opts.legacy,
    manifest: opts.manifest,
    group: opts.group,
    globalCache: opts.globalCache || null // session is a temp option, not to be relied on unless you know what you are doing (no semver guarantees)
  })
}

function maybeAddMonitor(name) {
  if (name === 'append' || name === 'truncate') return
  if (this._monitorIndex >= 0 || this.closing) return

  if (this.core === null) {
    this._monitorIndex = -2
  } else {
    this.core.addMonitor(this)
  }
}

function isSessionMoved(err) {
  return err.code === 'SESSION_MOVED'
}

function getEncryptionOption(opts) {
  // old style, supported for now but will go away
  if (opts.encryptionKey) return { key: opts.encryptionKey, block: !!opts.isBlockKey }
  if (!opts.encryption) return null
  return b4a.isBuffer(opts.encryption) ? { key: opts.encryption } : opts.encryption
}

function isEncryptionProvider(e) {
  return e && isFunction(e.padding) && isFunction(e.encrypt) && isFunction(e.decrypt)
}

function isFunction(fn) {
  return !!fn && typeof fn === 'function'
}

function createDiscoveryKeyHandler(fn) {
  return ondiscoverykey
  function ondiscoverykey(id) {
    if (!id || id.byteLength !== 32) throw BAD_ARGUMENT('Invalid discovery key')
    return fn(id)
  }
}

function isRequestsDestroyed(activeRequests) {
  // TODO: move this to an object instead so we can store a property in next major
  return activeRequests.length > 0 && activeRequests[0] === null
}
const crypto = require('hypercore-crypto')
const flat = require('flat-tree')
const b4a = require('b4a')
const { MerkleTree } = require('./merkle-tree')

module.exports = async function auditCore(
  core,
  { tree = true, blocks = true, bitfield = true, dryRun = false } = {}
) {
  const length = core.state.length
  const stats = {
    treeNodes: 0,
    blocks: 0,
    bits: 0,
    droppedTreeNodes: 0,
    droppedBlocks: 0,
    droppedBits: 0,
    corrupt: false
  }

  // audit the tree
  if (tree) {
    let tx = null

    const roots = await MerkleTree.getRootsFromStorage(core.state.storage, length)
    const stack = []

    for (const r of roots) {
      if (r === null) {
        if (!dryRun) {
          const storage = core.state.storage
          await storage.store.deleteCore(storage.core)
          return null
        }

        stats.corrupt = true
      }

      stack.push(r)
    }

    stats.treeNodes += roots.length

    while (stack.length > 0) {
      const node = stack.pop()

      if ((node.index & 1) === 0) continue

      const [left, right] = flat.children(node.index)

      const rx = core.state.storage.read()
      const leftNodePromise = rx.getTreeNode(left)
      const rightNodePromise = rx.getTreeNode(right)

      rx.tryFlush()

      const [leftNode, rightNode] = await Promise.all([leftNodePromise, rightNodePromise])

      if (isBadTree(node, leftNode, rightNode)) {
        if (!tx && !stats.corrupt) tx = core.state.storage.write()
        const [l, r] = flat.spans(node.index)
        tx.deleteTreeNodeRange(l, r + 1)
        stats.droppedTreeNodes++
        continue
      }

      if (!leftNode) continue

      stats.treeNodes += 2
      stack.push(leftNode, rightNode)
    }

    if (tx && !dryRun) await tx.flush()
  }

  // audit the blocks
  if (blocks) {
    let tx = null

    for await (const block of core.state.storage.createBlockStream()) {
      if (!core.bitfield.get(block.index)) {
        if (!tx && !stats.corrupt) tx = core.state.storage.write()
        tx.deleteBlock(block.index)
        stats.droppedBlocks++
      }

      const rx = core.state.storage.read()
      const treeNodePromise = rx.getTreeNode(2 * block.index)

      rx.tryFlush()

      const treeNode = await treeNodePromise

      if (isBadBlock(treeNode, block.value)) {
        if (!tx && !stats.corrupt) tx = core.state.storage.write()
        tx.deleteBlock(block.index)
        stats.droppedBlocks++
        continue
      }

      stats.blocks++
    }

    if (tx && !dryRun) await tx.flush()
  }

  if (bitfield) {
    let tx = null

    for (const index of allBits(core.bitfield)) {
      const rx = core.state.storage.read()
      const blockPromise = rx.getBlock(index)

      rx.tryFlush()

      const block = await blockPromise
      if (!block) {
        stats.droppedBits++
        if (dryRun) continue

        if (!tx && !stats.corrupt) tx = core.state.storage.write()

        core.bitfield.set(index, false)

        const page = core.bitfield.getBitfield(index)
        if (page.bitfield) tx.putBitfieldPage(page.index, page.bitfield)
        else tx.deleteBitfieldPage(page.index)
        continue
      }

      stats.bits++
    }

    if (tx && !dryRun) await tx.flush()
  }

  return stats
}

function isBadBlock(node, block) {
  if (!node) return true
  const hash = crypto.data(block)
  return !b4a.equals(hash, node.hash) || node.size !== block.byteLength
}

function isBadTree(parent, left, right) {
  if (!left && !right) return false
  if (!left || !right) return true
  const hash = crypto.parent(left, right)
  return !b4a.equals(hash, parent.hash) || parent.size !== left.size + right.size
}

function* allBits(bitfield) {
  let i = 0
  if (bitfield.get(0)) yield 0
  while (true) {
    i = bitfield.findFirst(true, i + 1)
    if (i === -1) break
    yield i
  }
}
const b4a = require('b4a')
const quickbit = require('./compat').quickbit

module.exports = class BitInterlude {
  constructor() {
    this.ranges = []
  }

  contiguousLength(from) {
    for (const r of this.ranges) {
      if (r.start > from) break
      if (!r.value && r.start <= from) return r.start
    }

    // TODO: be smarter
    while (this.get(from) === true) from++
    return from
  }

  get(index) {
    let start = 0
    let end = this.ranges.length

    while (start < end) {
      const mid = (start + end) >> 1
      const r = this.ranges[mid]

      if (index < r.start) {
        end = mid
        continue
      }

      if (index >= r.end) {
        if (mid === start) break
        start = mid
        continue
      }

      return r.value
    }

    return false
  }

  setRange(start, end, value) {
    if (start === end) return

    let r = null

    for (let i = 0; i < this.ranges.length; i++) {
      r = this.ranges[i]

      // if already inside, stop
      if (r.start <= start && end <= r.end) {
        if (value === r.value) return

        const ranges = mergeRanges(r, { start, end, value })
        this.ranges.splice(i, 1, ...ranges)

        return
      }

      // we wanna overun the interval
      if (start > r.end) {
        continue
      }

      // we overran but this interval is ending after us, move it back
      if (end >= r.start && end <= r.end) {
        r.start = r.value === value ? start : end
        if (r.value !== value) this.ranges.splice(i, 0, { start, end, value })
        return
      }

      // we overran but our start is contained in this interval, move start back
      if (start >= r.start && start <= r.end) {
        if (r.value !== value) {
          r.end = start // Crop current comparison

          // If there's a next range
          if (this.ranges.length - 1 > i) {
            const next = this.ranges[i + 1]
            // If the same, go next
            if (next.value === value) continue
            // Else we overlap so update next range
            if (next.start < end) next.start = end
          }

          this.ranges.splice(++i, 0, { start, end, value })
          return
        }

        start = r.start
      }

      let remove = 0

      for (let j = i; j < this.ranges.length; j++) {
        const n = this.ranges[j]
        if (n.start > end || n.value !== value) break
        if (n.start <= end && n.end > end) end = n.end
        remove++
      }

      this.ranges.splice(i, remove, { start, end, value })
      return
    }

    if (r !== null) {
      if (start <= r.end && end > r.end) {
        r.end = end
        return
      }

      // we never
      if (r.end > start) return
    }

    this.ranges.push({ start, end, value })
  }

  flush(tx, bitfield) {
    if (!this.ranges.length) return []

    let index = this.ranges[0].start
    const final = this.ranges[this.ranges.length - 1].end

    let i = 0

    while (index < final) {
      const page = bitfield.getBitfield(index) // read only
      const pageIndex = page ? page.index : bitfield.getPageIndex(index)

      const buf = b4a.allocUnsafe(bitfield.getPageByteLength())

      if (page) {
        const src = page.bitfield // Uint32Array
        buf.set(b4a.from(src.buffer, src.byteOffset, src.byteLength), 0)
      } else {
        b4a.fill(buf, 0)
      }

      const last = (pageIndex + 1) * (buf.byteLength << 3)
      const offset = pageIndex * (buf.byteLength << 3)

      let hasValue = false

      while (i < this.ranges.length) {
        const { start, end, value } = this.ranges[i]

        if (!hasValue && value) hasValue = true

        const from = start < index ? index : start
        const to = end < last ? end : last

        quickbit.fill(buf, value, from - offset, to - offset)

        index = to

        if (to === last) break

        i++
      }

      if (page || hasValue) tx.putBitfieldPage(pageIndex, buf)
    }

    return this.ranges
  }
}

function mergeRanges(a, b) {
  const ranges = []
  if (a.start < b.start) ranges.push({ start: a.start, end: b.start, value: a.value })
  ranges.push({ start: b.start, end: b.end, value: b.value })
  if (b.end < a.end) ranges.push({ start: b.end, end: a.end, value: a.value })

  return ranges
}
const BigSparseArray = require('big-sparse-array')
const b4a = require('b4a')
const quickbit = require('./compat').quickbit

const BITS_PER_PAGE = 32768
const BYTES_PER_PAGE = BITS_PER_PAGE / 8
const WORDS_PER_PAGE = BYTES_PER_PAGE / 4
const BITS_PER_SEGMENT = 2097152
const BYTES_PER_SEGMENT = BITS_PER_SEGMENT / 8
const WORDS_PER_SEGMENT = BYTES_PER_SEGMENT / 4
const INITIAL_WORDS_PER_SEGMENT = 1024
const PAGES_PER_SEGMENT = BITS_PER_SEGMENT / BITS_PER_PAGE
const SEGMENT_GROWTH_FACTOR = 4

class BitfieldPage {
  constructor(index, segment) {
    this.index = index
    this.offset = index * BYTES_PER_PAGE - segment.offset
    this.bitfield = null
    this.segment = segment

    segment.add(this)
  }

  get tree() {
    return this.segment.tree
  }

  get(index, dirty) {
    return quickbit.get(this.bitfield, index)
  }

  set(index, val) {
    if (quickbit.set(this.bitfield, index, val)) {
      this.tree.update(this.offset * 8 + index)
    }
  }

  setRange(start, end, val) {
    quickbit.fill(this.bitfield, val, start, end)

    let i = Math.floor(start / 128)
    const n = i + Math.ceil((end - start) / 128)

    while (i <= n) this.tree.update(this.offset * 8 + i++ * 128)
  }

  findFirst(val, position) {
    return quickbit.findFirst(this.bitfield, val, position)
  }

  findLast(val, position) {
    return quickbit.findLast(this.bitfield, val, position)
  }

  count(start, length, val) {
    const end = start + length

    let i = start
    let c = 0

    while (length > 0) {
      const l = this.findFirst(val, i)
      if (l === -1 || l >= end) return c

      const h = this.findFirst(!val, l + 1)
      if (h === -1 || h >= end) return c + end - l

      c += h - l
      length -= h - i
      i = h
    }

    return c
  }
}

class BitfieldSegment {
  constructor(index, bitfield) {
    this.index = index
    this.offset = index * BYTES_PER_SEGMENT
    this.tree = quickbit.Index.from(bitfield, BYTES_PER_SEGMENT)
    this.pages = new Array(PAGES_PER_SEGMENT)
  }

  get bitfield() {
    return this.tree.field
  }

  add(page) {
    const i = page.index - this.index * PAGES_PER_SEGMENT
    this.pages[i] = page

    const start = i * WORDS_PER_PAGE
    const end = start + WORDS_PER_PAGE

    if (end >= this.bitfield.length) this.reallocate(end)

    page.bitfield = this.bitfield.subarray(start, end)
  }

  reallocate(length) {
    let target = this.bitfield.length
    while (target < length) target *= SEGMENT_GROWTH_FACTOR

    const bitfield = new Uint32Array(target)
    bitfield.set(this.bitfield)

    this.tree = quickbit.Index.from(bitfield, BYTES_PER_SEGMENT)

    for (let i = 0; i < this.pages.length; i++) {
      const page = this.pages[i]
      if (!page) continue

      const start = i * WORDS_PER_PAGE
      const end = start + WORDS_PER_PAGE

      page.bitfield = bitfield.subarray(start, end)
    }
  }

  findFirst(val, position) {
    position = this.tree.skipFirst(!val, position)

    let j = position & (BITS_PER_PAGE - 1)
    let i = (position - j) / BITS_PER_PAGE

    if (i >= PAGES_PER_SEGMENT) return -1

    while (i < this.pages.length) {
      const p = this.pages[i]

      let index = -1

      if (p) index = p.findFirst(val, j)
      else if (!val) index = j

      if (index !== -1) return i * BITS_PER_PAGE + index

      j = 0
      i++
    }

    return -1
  }

  findLast(val, position) {
    position = this.tree.skipLast(!val, position)

    let j = position & (BITS_PER_PAGE - 1)
    let i = (position - j) / BITS_PER_PAGE

    if (i >= PAGES_PER_SEGMENT) return -1

    while (i >= 0) {
      const p = this.pages[i]

      let index = -1

      if (p) index = p.findLast(val, j)
      else if (!val) index = j

      if (index !== -1) return i * BITS_PER_PAGE + index

      j = BITS_PER_PAGE - 1
      i--
    }

    return -1
  }
}

module.exports = class Bitfield {
  static BITS_PER_PAGE = BITS_PER_PAGE
  static BYTES_PER_PAGE = BYTES_PER_PAGE

  constructor(buffer) {
    this.resumed = !!(buffer && buffer.byteLength >= 0)

    this._pages = new BigSparseArray()
    this._segments = new BigSparseArray()

    const view = this.resumed
      ? new Uint32Array(buffer.buffer, buffer.byteOffset, Math.floor(buffer.byteLength / 4))
      : new Uint32Array(INITIAL_WORDS_PER_SEGMENT)

    for (let i = 0; i < view.length; i += WORDS_PER_SEGMENT) {
      let bitfield = view.subarray(i, i + WORDS_PER_SEGMENT)
      let length = WORDS_PER_SEGMENT

      if (i === 0) {
        length = INITIAL_WORDS_PER_SEGMENT
        while (length < bitfield.length) length *= SEGMENT_GROWTH_FACTOR
      }

      if (bitfield.length !== length) {
        const copy = new Uint32Array(length)
        copy.set(bitfield, 0)
        bitfield = copy
      }

      const segment = new BitfieldSegment(i / WORDS_PER_SEGMENT, bitfield)
      this._segments.set(segment.index, segment)

      for (let j = 0; j < bitfield.length; j += WORDS_PER_PAGE) {
        const page = new BitfieldPage((i + j) / WORDS_PER_PAGE, segment)
        this._pages.set(page.index, page)
      }
    }
  }

  static from(bitfield) {
    return new Bitfield(bitfield.toBuffer(bitfield._pages.maxLength * BITS_PER_PAGE))
  }

  toBuffer(length) {
    const pages = Math.ceil(length / BITS_PER_PAGE)
    const buffer = b4a.allocUnsafe(pages * BYTES_PER_PAGE)

    for (let i = 0; i < pages; i++) {
      const page = this._pages.get(i)
      const offset = i * BYTES_PER_PAGE

      if (page) {
        const buf = b4a.from(
          page.bitfield.buffer,
          page.bitfield.byteOffset,
          page.bitfield.byteLength
        )

        buffer.set(buf, offset)
      } else {
        buffer.fill(0, offset, offset + BYTES_PER_PAGE)
      }
    }

    return buffer
  }

  getBitfield(index) {
    const i = this.getPageIndex(index)

    const p = this._pages.get(i)
    return p || null
  }

  merge(bitfield, length) {
    let i = 0

    while (i < length) {
      const start = bitfield.firstSet(i)
      if (start === -1) break

      i = bitfield.firstUnset(start)

      if (i === -1 || i > length) i = length

      this.setRange(start, i, true)

      if (i >= length) break
    }
  }

  get(index) {
    const j = index & (BITS_PER_PAGE - 1)
    const i = (index - j) / BITS_PER_PAGE

    const p = this._pages.get(i)

    return p ? p.get(j) : false
  }

  getPageByteLength() {
    return BYTES_PER_PAGE
  }

  getPageIndex(index) {
    const j = index & (BITS_PER_PAGE - 1)
    return (index - j) / BITS_PER_PAGE
  }

  getPage(index, create) {
    const i = this.getPageIndex(index)

    let p = this._pages.get(i)

    if (p) return p

    if (!create) return null

    const k = Math.floor(i / PAGES_PER_SEGMENT)
    const s =
      this._segments.get(k) ||
      this._segments.set(
        k,
        new BitfieldSegment(
          k,
          new Uint32Array(k === 0 ? INITIAL_WORDS_PER_SEGMENT : WORDS_PER_SEGMENT)
        )
      )

    p = this._pages.set(i, new BitfieldPage(i, s))

    return p
  }

  set(index, val) {
    const j = index & (BITS_PER_PAGE - 1)
    const i = (index - j) / BITS_PER_PAGE

    let p = this._pages.get(i)

    if (!p && val) {
      const k = Math.floor(i / PAGES_PER_SEGMENT)
      const s =
        this._segments.get(k) ||
        this._segments.set(
          k,
          new BitfieldSegment(
            k,
            new Uint32Array(k === 0 ? INITIAL_WORDS_PER_SEGMENT : WORDS_PER_SEGMENT)
          )
        )

      p = this._pages.set(i, new BitfieldPage(i, s))
    }

    if (p) p.set(j, val)
  }

  setRange(start, end, val) {
    let j = start & (BITS_PER_PAGE - 1)
    let i = (start - j) / BITS_PER_PAGE

    while (start < end) {
      let p = this._pages.get(i)

      if (!p && val) {
        const k = Math.floor(i / PAGES_PER_SEGMENT)
        const s =
          this._segments.get(k) ||
          this._segments.set(
            k,
            new BitfieldSegment(
              k,
              new Uint32Array(k === 0 ? INITIAL_WORDS_PER_SEGMENT : WORDS_PER_SEGMENT)
            )
          )

        p = this._pages.set(i, new BitfieldPage(i, s))
      }

      const offset = i * BITS_PER_PAGE
      const last = Math.min(end - offset, BITS_PER_PAGE)
      const range = last - j

      if (p) p.setRange(j, last, val)

      j = 0
      i++
      start += range
    }
  }

  findFirst(val, position) {
    let j = position & (BITS_PER_SEGMENT - 1)
    let i = (position - j) / BITS_PER_SEGMENT

    while (i < this._segments.maxLength) {
      const s = this._segments.get(i)

      let index = -1

      if (s) index = s.findFirst(val, j)
      else if (!val) index = j

      if (index !== -1) return i * BITS_PER_SEGMENT + index

      j = 0
      i++
    }

    return val ? -1 : this._segments.maxLength * BITS_PER_SEGMENT
  }

  firstSet(position) {
    return this.findFirst(true, position)
  }

  firstUnset(position) {
    return this.findFirst(false, position)
  }

  findLast(val, position) {
    let j = position & (BITS_PER_SEGMENT - 1)
    let i = (position - j) / BITS_PER_SEGMENT

    while (i >= 0) {
      const s = this._segments.get(i)

      let index = -1

      if (s) index = s.findLast(val, j)
      else if (!val) index = j

      if (index !== -1) return i * BITS_PER_SEGMENT + index

      j = BITS_PER_SEGMENT - 1
      i--
    }

    return -1
  }

  lastSet(position) {
    return this.findLast(true, position)
  }

  lastUnset(position) {
    return this.findLast(false, position)
  }

  hasUnset(start, length) {
    const end = start + length

    let j = start & (BITS_PER_SEGMENT - 1)
    let i = (start - j) / BITS_PER_SEGMENT

    while (i < this._segments.maxLength) {
      const s = this._segments.get(i)
      const index = s ? s.findFirst(false, j) : j
      if (index !== -1) return i * BITS_PER_SEGMENT + index < end

      j = 0
      i++

      if (i * BITS_PER_SEGMENT >= end) return false
    }

    return true
  }

  hasSet(start, length) {
    const end = start + length

    let j = start & (BITS_PER_SEGMENT - 1)
    let i = (start - j) / BITS_PER_SEGMENT

    while (i < this._segments.maxLength) {
      const s = this._segments.get(i)

      let index = -1

      if (s) index = s.findFirst(true, j)

      if (index !== -1) return i * BITS_PER_SEGMENT + index < end

      j = 0
      i++

      if (i * BITS_PER_SEGMENT >= end) return false
    }

    return false
  }

  count(start, length, val) {
    let j = start & (BITS_PER_PAGE - 1)
    let i = (start - j) / BITS_PER_PAGE
    let c = 0

    while (length > 0) {
      const p = this._pages.get(i)

      const end = Math.min(j + length, BITS_PER_PAGE)
      const range = end - j

      if (p) c += p.count(j, range, val)
      else if (!val) c += range

      j = 0
      i++
      length -= range
    }

    return c
  }

  countSet(start, length) {
    return this.count(start, length, true)
  }

  countUnset(start, length) {
    return this.count(start, length, false)
  }

  *want(start, length) {
    if (start & 31) {
      const d = start & 31
      start -= d
      length += d
    }

    let j = start & (BITS_PER_SEGMENT - 1)
    let i = (start - j) / BITS_PER_SEGMENT

    while (length > 0) {
      const s = this._segments.get(i)

      if (s) {
        let end = Math.min(j + length, BITS_PER_SEGMENT)
        if (end & 31) end += 32 - (end & 31)

        yield {
          start: i * BITS_PER_SEGMENT + j,
          bitfield: s.bitfield.subarray(j / 32, end / 32)
        }
      }

      j = 0
      i++
      length -= BITS_PER_SEGMENT
    }
  }

  clear(tx) {
    return tx.deleteBitfieldPageRange(0, -1)
  }

  onupdate(ranges) {
    for (const { start, end, value } of ranges) {
      this.setRange(start, end, value)
    }
  }

  static async open(storage, length) {
    if (length === 0) return new Bitfield(storage, null)

    const pages = Math.ceil(length / BITS_PER_PAGE)
    const buffer = b4a.alloc(pages * BYTES_PER_PAGE)
    const stream = storage.createBitfieldStream({ lt: pages })

    for await (const { index, page } of stream) {
      buffer.set(page, index * BYTES_PER_PAGE)
    }

    return new Bitfield(buffer)
  }
}
const crypto = require('hypercore-crypto')
const sodium = require('sodium-universal')
const b4a = require('b4a')
const c = require('compact-encoding')

// TODO: rename this to "crypto" and move everything hashing related etc in here
// Also lets move the tree stuff from hypercore-crypto here

const [
  TREE,
  REPLICATE_INITIATOR,
  REPLICATE_RESPONDER,
  MANIFEST,
  DEFAULT_NAMESPACE,
  DEFAULT_ENCRYPTION
] = crypto.namespace('hypercore', 6)

exports.MANIFEST = MANIFEST
exports.DEFAULT_NAMESPACE = DEFAULT_NAMESPACE
exports.DEFAULT_ENCRYPTION = DEFAULT_ENCRYPTION

exports.replicate = function (isInitiator, key, handshakeHash) {
  const out = b4a.allocUnsafe(32)
  sodium.crypto_generichash_batch(
    out,
    [isInitiator ? REPLICATE_INITIATOR : REPLICATE_RESPONDER, key],
    handshakeHash
  )
  return out
}

exports.treeSignable = function (manifestHash, treeHash, length, fork) {
  const state = { start: 0, end: 112, buffer: b4a.allocUnsafe(112) }
  c.fixed32.encode(state, TREE)
  c.fixed32.encode(state, manifestHash)
  c.fixed32.encode(state, treeHash)
  c.uint64.encode(state, length)
  c.uint64.encode(state, fork)
  return state.buffer
}

exports.treeSignableCompat = function (hash, length, fork, noHeader) {
  const end = noHeader ? 48 : 80
  const state = { start: 0, end, buffer: b4a.allocUnsafe(end) }
  if (!noHeader) c.fixed32.encode(state, TREE) // ultra legacy mode, kill in future major
  c.fixed32.encode(state, hash)
  c.uint64.encode(state, length)
  c.uint64.encode(state, fork)
  return state.buffer
}
// Export the appropriate version of `quickbit-universal` as the plain import
// may resolve to an older version in some environments
let quickbit = require('quickbit-universal')
if (
  typeof quickbit.findFirst !== 'function' ||
  typeof quickbit.findLast !== 'function' ||
  typeof quickbit.clear !== 'function'
) {
  // This should always load the fallback from the locally installed version
  quickbit = require('quickbit-universal/fallback')
}
exports.quickbit = quickbit
const crypto = require('hypercore-crypto')
const flat = require('flat-tree')
const b4a = require('b4a')
const quickbit = require('quickbit-universal')
const Bitfield = require('./bitfield')

const MAX_BATCH_USED = 4 * 1024 * 1024
const MIN_BATCH_USED = 512 * 1024

// just in its own file as its a bit involved

module.exports = copyPrologue

async function copyPrologue(src, dst) {
  const prologue = dst.header.manifest.prologue

  if (src.length < prologue.length || prologue.length === 0) return

  const stack = []
  const roots = flat.fullRoots(prologue.length * 2)
  const batch = { roots, first: true, last: false, contig: 0, used: 0, tree: [], blocks: [] }

  for (let i = 0; i < roots.length; i++) {
    const node = roots[i]
    batch.tree.push(node)
    stack.push(node)
  }

  let lastPage = -1
  let lastBlock = -1

  for await (const data of src.storage.createBlockStream({
    gte: 0,
    lt: prologue.length,
    reverse: true
  })) {
    if (walkTree(stack, data.index * 2, batch) === false) {
      throw new Error('Missing block or tree node for ' + data.index)
    }

    batch.contig = data.index + 1 === lastBlock ? batch.contig + 1 : 1
    lastBlock = data.index

    const page = getBitfieldPage(data.index)
    batch.blocks.push(data)

    if (lastPage !== page) batch.used += 4096
    batch.used += Math.max(data.value.byteLength, 128) // 128 is just a sanity number to avoid mega batches

    // always safe to partially flush so we do that ondemand to reduce memory usage...
    if ((batch.used >= MIN_BATCH_USED && page !== lastPage) || batch.used >= MAX_BATCH_USED) {
      await flushBatch(prologue, src, dst, batch)
    }

    lastPage = page
  }

  if (lastBlock !== 0) batch.contig = 0

  batch.last = true
  await flushBatch(prologue, src, dst, batch)
}

async function flushBatch(prologue, src, dst, batch) {
  const nodePromises = []

  const srcReader = src.storage.read()
  for (const index of batch.tree) {
    nodePromises.push(srcReader.getTreeNode(index))
  }
  srcReader.tryFlush()

  const nodes = await Promise.all(nodePromises)

  const pagePromises = []
  const dstReader = dst.storage.read()

  const headPromise = batch.first ? dstReader.getHead() : null
  if (headPromise) headPromise.catch(noop)

  let lastPage = -1
  for (const { index } of batch.blocks) {
    const page = getBitfieldPage(index)
    if (page === lastPage) continue
    lastPage = page
    pagePromises.push(dstReader.getBitfieldPage(page))
  }

  dstReader.tryFlush()

  const pages = await Promise.all(pagePromises)
  const head = headPromise === null ? null : await headPromise
  const userData = []

  // reads done!

  if (batch.first) {
    const roots = nodes.slice(0, batch.roots.length)

    for (const node of roots) {
      if (!node) throw new Error('Missing nodes for prologue hash')
    }

    const treeHash = crypto.tree(roots)
    if (!b4a.equals(treeHash, prologue.hash)) throw new Error('Prologue does not match source')
  }

  if (batch.first) {
    for await (const data of src.storage.createUserDataStream()) userData.push(data)
  }

  for (let i = 0; i < pages.length; i++) {
    if (!pages[i]) pages[i] = b4a.alloc(4096)
  }

  const tx = dst.storage.write()

  for (const node of nodes) tx.putTreeNode(node)

  lastPage = -1
  let pageIndex = -1

  for (const { index, value } of batch.blocks) {
    const page = getBitfieldPage(index)

    if (page !== lastPage) {
      lastPage = page
      pageIndex++
      // queue the page now, we mutate it below but its the same ref
      tx.putBitfieldPage(pageIndex, pages[pageIndex])
    }

    const pageBuffer = pages[pageIndex]
    quickbit.set(pageBuffer, getBitfieldOffset(index), true)
    tx.putBlock(index, value)
  }

  for (const { key, value } of userData) {
    tx.putUserData(key, value)
  }

  let upgraded = batch.first && !head
  if (upgraded) {
    tx.setHead(prologueToTree(prologue))
  }

  await tx.flush()

  if (upgraded) {
    const roots = nodes.slice(0, batch.roots.length)
    dst.state.setRoots(roots)
    dst.header.tree = prologueToTree(prologue)
  }

  if (userData.length > 0) {
    dst.header.userData = userData.concat(dst.header.userData)
  }

  if (batch.contig) {
    // TODO: we need to persist this somehow
    dst.header.hints.contiguousLength = batch.contig
  }

  let start = 0
  let length = 0

  // update in memory bitfield
  for (const { index } of batch.blocks) {
    if (start === 0 || start - 1 === index) {
      length++
    } else {
      if (length > 0) signalReplicator(dst, upgraded, start, length)
      upgraded = false
      length = 1
    }

    start = index
    dst.bitfield.set(index, true)
  }

  if (length > 0) signalReplicator(dst, upgraded, start, length)

  // unlink
  batch.tree = []
  batch.blocks = []
  batch.first = false
  batch.used = 0
}

function signalReplicator(core, upgraded, start, length) {
  if (upgraded) {
    core.replicator.cork()
    core.replicator.onhave(start, length, false)
    core.replicator.onupgrade()
    core.replicator.uncork()
  } else {
    core.replicator.onhave(start, length, false)
  }
}

function prologueToTree(prologue) {
  return {
    fork: 0,
    length: prologue.length,
    rootHash: prologue.hash,
    signature: null
  }
}

function getBitfieldPage(index) {
  return Math.floor(index / Bitfield.BITS_PER_PAGE)
}

function getBitfieldOffset(index) {
  return index & (Bitfield.BITS_PER_PAGE - 1)
}

function walkTree(stack, target, batch) {
  while (stack.length > 0) {
    const node = stack.pop()

    if ((node & 1) === 0) {
      if (node === target) return true
      continue
    }

    const ite = flat.iterator(node)
    if (!ite.contains(target)) continue

    while ((ite.index & 1) !== 0) {
      const left = ite.leftChild()
      const right = ite.sibling() // is right child

      batch.tree.push(left, right)

      if (ite.contains(target)) stack.push(left)
      else ite.sibling()
    }

    if (ite.index === target) return true
  }

  return false
}

function noop() {}
const crypto = require('hypercore-crypto')
const b4a = require('b4a')
const unslab = require('unslab')
const z32 = require('z32')
const Mutex = require('./mutex')
const { MerkleTree, ReorgBatch } = require('./merkle-tree')
const BitInterlude = require('./bit-interlude')
const Bitfield = require('./bitfield')
const RemoteBitfield = require('./remote-bitfield')
const {
  BAD_ARGUMENT,
  STORAGE_EMPTY,
  STORAGE_CONFLICT,
  INVALID_SIGNATURE,
  INVALID_CHECKSUM
} = require('hypercore-errors')
const Verifier = require('./verifier')
const audit = require('./audit')
const copyPrologue = require('./copy-prologue')
const SessionState = require('./session-state')
const Replicator = require('./replicator')

module.exports = class Core {
  constructor(db, opts = {}) {
    this.db = db
    this.storage = null
    this.replicator = new Replicator(this, opts)
    this.sessionStates = []
    this.monitors = []
    this.activeSessions = 0
    this.gc = 0 // corestore uses this to main a gc strike pool

    this.id = opts.key ? z32.encode(opts.key) : null
    this.key = opts.key || null
    this.discoveryKey = opts.discoveryKey || (opts.key && crypto.discoveryKey(opts.key)) || null
    this.manifest = null
    this.opening = null
    this.closing = null
    this.exclusive = null

    this.preupdate = null
    this.header = null
    this.compat = false
    this.bitfield = null
    this.verifier = null
    this.truncating = 0
    this.skipBitfield = null
    this.globalCache = opts.globalCache || null
    this.autoClose = opts.autoClose !== false
    this.onidle = noop
    this.ongroupupdate = noop

    this.state = null
    this.opened = false
    this.destroyed = false
    this.closed = false

    this.hintsChanged = false
    this.remoteContiguousLengthChanged = false

    this._bitfield = null
    this._verifies = null
    this._verifiesFlushed = null
    this._legacy = !!opts.legacy
    this._repairMode = false

    this.opening = this._open(opts)
    this.opening.catch(noop)
  }

  static setRecoveryPeers(peers) {
    SessionState.RECOVERY_PEERS = peers
  }

  ready() {
    return this.opening
  }

  addMonitor(s) {
    if (s._monitorIndex >= 0) return
    s._monitorIndex = this.monitors.push(s) - 1
  }

  removeMonitor(s) {
    if (s._monitorIndex < 0) return
    const head = this.monitors.pop()
    if (head !== s) this.monitors[(head._monitorIndex = s._monitorIndex)] = head
    s._monitorIndex = -1
  }

  emitRemoteContiguousLength() {
    if (!this.remoteContiguousLengthChanged) return
    this.remoteContiguousLengthChanged = false

    const length = this.header.hints.remoteContiguousLength

    for (let i = this.monitors.length - 1; i >= 0; i--) {
      this.monitors[i].emit('remote-contiguous-length', length)
    }
  }

  emitManifest() {
    for (let i = this.monitors.length - 1; i >= 0; i--) {
      this.monitors[i].emit('manifest')
    }
  }

  createUserDataStream(opts, session = this.state) {
    return session.storage.createUserDataStream(opts)
  }

  allSessions() {
    const sessions = []
    for (const state of this.sessionStates) {
      if (state.sessions.length) sessions.push(...state.sessions)
    }
    return sessions
  }

  hasSession() {
    return this.activeSessions !== 0
  }

  compact() {
    const compacting = []
    for (const s of this.sessionStates) {
      compacting.push(s.storage.compact())
    }
    return Promise.all(compacting)
  }

  checkIfIdle() {
    if (!this.opened || this.destroyed === true || this.hasSession() === true) return
    if (this.replicator.idle() === false) return
    if (this.state === null || this.state.mutex.idle() === false) return
    this.onidle()
  }

  async lockExclusive() {
    if (this.exclusive === null) this.exclusive = new Mutex()
    await this.exclusive.lock()
  }

  unlockExclusive() {
    if (this.exclusive !== null) this.exclusive.unlock()
  }

  async _open(opts) {
    try {
      await this._tryOpen(opts)
    } catch (err) {
      this.onidle()
      throw err
    }

    this.opened = true
  }

  async _tryOpen(opts) {
    if (opts.preopen) await opts.preopen // just a hook to allow exclusive access here...

    let storage = await this.db.resumeCore(this.discoveryKey)

    let overwrite = opts.overwrite === true

    const force = opts.force === true
    const createIfMissing = opts.createIfMissing !== false
    // kill this flag soon
    const legacy = !!opts.legacy

    // default to true for now if no manifest is provided
    let compat = opts.compat === true || (opts.compat !== false && !opts.manifest)

    let header = storage ? parseHeader(await getCoreInfo(storage)) : null

    const hadManifest = !!header && !!header.manifest && !!header.key
    const hadKeyPair = !!header && !!header.keyPair

    if (force && opts.key && header && !b4a.equals(header.key, opts.key)) {
      overwrite = true
    }

    if (!header && opts.discoveryKey && !(opts.key || opts.manifest)) {
      throw STORAGE_EMPTY('No Hypercore is stored here', this.discoveryKey)
    }

    if (!header || overwrite) {
      if (!createIfMissing) {
        throw STORAGE_EMPTY('No Hypercore is stored here', this.discoveryKey)
      }

      if (compat) {
        if (opts.key && opts.keyPair && !b4a.equals(opts.key, opts.keyPair.publicKey)) {
          throw BAD_ARGUMENT('Key must match publicKey when in compat mode', this.discoveryKey)
        }
      }

      const keyPair = opts.keyPair || (opts.key ? null : crypto.keyPair())

      const defaultManifest =
        !opts.manifest &&
        (!!opts.compat || !opts.key || !!(keyPair && b4a.equals(opts.key, keyPair.publicKey)))
      const manifest = defaultManifest
        ? Verifier.defaultSignerManifest(opts.key || keyPair.publicKey)
        : Verifier.createManifest(opts.manifest)

      header = {
        key: opts.key || (compat ? manifest.signers[0].publicKey : Verifier.manifestHash(manifest)),
        manifest,
        keyPair: keyPair
          ? { publicKey: keyPair.publicKey, secretKey: keyPair.secretKey || null }
          : null,
        frozen: false,
        tree: {
          fork: 0,
          length: 0,
          rootHash: null,
          signature: null,
          timestamp: 0
        },
        group: null,
        hints: {
          reorgs: [],
          contiguousLength: 0,
          remoteContiguousLength: 0,
          recovering: 0
        }
      }

      const discoveryKey = opts.discoveryKey || crypto.discoveryKey(header.key)

      storage = await this.db.createCore({
        key: header.key,
        manifest,
        keyPair,
        frozen: false,
        discoveryKey,
        userData: opts.userData || [],
        alias: opts.alias || null
      })
    }

    // unslab the long lived buffers to avoid keeping the slab alive
    header.key = unslab(header.key)

    if (header.tree) {
      header.tree.rootHash = unslab(header.tree.rootHash)
      header.tree.signature = unslab(header.tree.signature)
    }

    if (header.keyPair) {
      header.keyPair.publicKey = unslab(header.keyPair.publicKey)
      header.keyPair.secretKey = unslab(header.keyPair.secretKey)
    }

    if (header.keyPair) {
      header.keyPair.publicKey = unslab(header.keyPair.publicKey)
      header.keyPair.secretKey = unslab(header.keyPair.secretKey)
    }

    if (opts.manifest) {
      // if we provide a manifest and no key, verify that the stored key is the same
      if (
        !opts.key &&
        !Verifier.isValidManifest(header.key, Verifier.createManifest(opts.manifest))
      ) {
        throw STORAGE_CONFLICT('Manifest does not hash to provided key', this.discoveryKey)
      }

      if (!header.manifest) header.manifest = opts.manifest
    }

    if (opts.key && !b4a.equals(header.key, opts.key)) {
      throw STORAGE_CONFLICT('Another Hypercore is stored here', this.discoveryKey)
    }

    // if we signalled compat, but already now this core isn't disable it
    if (compat && header.manifest && !Verifier.isCompat(header.key, header.manifest)) {
      compat = false
    } else if (!compat && header.manifest && Verifier.isCompat(header.key, header.manifest)) {
      compat = true
    }

    const prologue = header.manifest ? header.manifest.prologue : null

    const bitfield = await Bitfield.open(storage, header.tree.length)

    const treeInfo = {
      fork: header.tree.fork,
      length: header.tree.length,
      signature: header.tree.signature,
      roots: await maybeGetRootsFromStorage(storage, header.tree.length),
      prologue
    }

    this._repairMode = !treeInfo.roots.length && treeInfo.length && !overwrite
    // Set push only mode if in repair mode
    if (this._repairMode) {
      this.replicator.setPushOnly(true)
    }

    if (opts.group) {
      if (!header.group) header.group = await this.db.createGroup(opts.group)
      else if (!b4a.equals(header.group.key, opts.group)) {
        throw STORAGE_CONFLICT(
          'Core is already register to group: ' + b4a.toString(header.group.key, 'hex')
        )
      }

      const tx = storage.write()
      tx.setGroup(header.group)
      await tx.flush()
    }

    if (overwrite) {
      const tx = storage.write()
      tx.deleteTreeNodeRange(0, -1)
      tx.deleteBlockRange(0, -1)
      bitfield.clear(tx)
      await tx.flush()
    }

    const len = bitfield.findFirst(false, header.hints.contiguousLength)
    if (header.hints.contiguousLength !== len && !storage.readOnly) {
      header.hints.contiguousLength = len
      const tx = storage.write()
      tx.setHints(header.hints)
      await tx.flush()
    }

    // to unslab
    if (header.manifest) {
      header.manifest = Verifier.createManifest(header.manifest)
      if (!hadManifest || (header.keyPair && !hadKeyPair)) {
        const tx = storage.write()
        tx.setAuth({
          key: header.key,
          discoveryKey: crypto.discoveryKey(header.key),
          manifest: header.manifest,
          keyPair: header.keyPair
        })
        await tx.flush()
      }
    }

    const verifier = header.manifest
      ? new Verifier(header.key, header.manifest, { crypto, legacy })
      : null

    this.storage = storage
    this.header = header
    this.compat = compat
    this.bitfield = bitfield
    this.verifier = verifier
    this.state = new SessionState(this, null, storage, treeInfo, null)

    if (this.key === null) this.key = this.header.key
    if (this.discoveryKey === null) this.discoveryKey = crypto.discoveryKey(this.key)
    if (this.id === null) this.id = z32.encode(this.key)
    if (this.manifest === null) this.manifest = this.header.manifest
  }

  async audit(opts) {
    await this.state.mutex.lock()

    try {
      return await audit(this, opts)
    } finally {
      this.state._unlock()
    }
  }

  async setGroup(group) {
    await this.state.mutex.lock()
    try {
      if (!this.header.group) this.header.group = await this.db.createGroup(group)
      else if (!b4a.equals(this.header.group.key, group)) {
        throw STORAGE_CONFLICT(
          'Core is already register to group: ' + b4a.toString(this.header.group.key, 'hex')
        )
      }

      const tx = this.storage.write()
      tx.setGroup(this.header.group)
      await tx.flush()
    } finally {
      this.state.mutex.unlock()
    }
  }

  async setManifest(manifest) {
    await this.state.mutex.lock()

    try {
      if (manifest && this.header.manifest === null) {
        if (!Verifier.isValidManifest(this.header.key, manifest)) {
          throw INVALID_CHECKSUM('Manifest hash does not match', this.discoveryKey)
        }

        const tx = this.state.createWriteBatch()
        this._setManifest(tx, Verifier.createManifest(manifest), null)
        await this.state.flush()
      }
    } finally {
      this.state._unlock()
    }
  }

  _setManifest(tx, manifest, keyPair) {
    if (!manifest && b4a.equals(keyPair.publicKey, this.header.key)) {
      manifest = Verifier.defaultSignerManifest(this.header.key)
    }
    if (!manifest) return

    const verifier = new Verifier(this.header.key, manifest, { legacy: this._legacy })

    if (verifier.prologue) this.state.prologue = Object.assign({}, verifier.prologue)

    this.manifest = this.header.manifest = manifest

    tx.setAuth({
      key: this.header.key,
      discoveryKey: this.discoveryKey,
      manifest,
      keyPair: this.header.keyPair
      // TODO: encryptionKey?
    })

    this.compat = verifier.compat
    this.verifier = verifier

    this.replicator.onupgrade()
    this.emitManifest()
  }

  async copyPrologue(src) {
    await this.state.mutex.lock()

    try {
      await src.mutex.lock()
    } catch (err) {
      this.state.mutex.unlock()
      throw err
    }

    try {
      await copyPrologue(src, this)
    } finally {
      src.mutex.unlock()
      this.state.mutex.unlock()
      this.checkIfIdle()
    }
  }

  flushed() {
    return this.state.flushed()
  }

  async _validateCommit(state, treeLength) {
    if (this.state.length > state.length) {
      return false // TODO: partial commit and truncation possible in the future
    }

    if (this.state.length > treeLength) {
      for (const root of this.state.roots) {
        const batchRoot = await MerkleTree.get(state, root.index)
        if (batchRoot.size !== root.size || !b4a.equals(batchRoot.hash, root.hash)) {
          return false
        }
      }
    }

    if (this.verifier === null) {
      return false // easier to assert than upsert
    }

    return true
  }

  _verifyBatchUpgrade(batch, manifest) {
    if (!this.header.manifest) {
      // compat, drop at some point
      if (!manifest) manifest = Verifier.defaultSignerManifest(this.header.key)

      if (
        !manifest ||
        !(
          Verifier.isValidManifest(this.header.key, manifest) ||
          Verifier.isCompat(this.header.key, manifest)
        )
      ) {
        throw INVALID_SIGNATURE('Proof contains an invalid manifest', this.discoveryKey) // TODO: proper error type
      }
    }

    const verifier =
      this.verifier ||
      new Verifier(this.header.key, Verifier.createManifest(manifest), { legacy: this._legacy })

    if (!verifier.verify(batch, batch.signature)) {
      throw INVALID_SIGNATURE('Proof contains an invalid signature', this.discoveryKey)
    }

    return manifest
  }

  async _verifyExclusive({ batch, bitfield, value, manifest }) {
    manifest = this._verifyBatchUpgrade(batch, manifest)

    if (
      !(await this.state._verifyBlock(
        batch,
        bitfield,
        value,
        this.header.manifest ? null : manifest
      ))
    ) {
      return false
    }

    if (!batch.upgraded && bitfield) {
      this.replicator.onhave(bitfield.start, bitfield.length, bitfield.drop)
    }

    return true
  }

  async _verifyShared() {
    if (!this._verifies.length) return false

    await this.state.mutex.lock()

    const tx = this.state.createWriteBatch()

    const verifies = this._verifies
    this._verifies = null
    this._verified = null

    try {
      for (const { batch, bitfield, value } of verifies) {
        if (!batch.commitable()) continue

        if (bitfield) {
          tx.putBlock(bitfield.start, value)
        }
      }

      const bits = new BitInterlude()

      for (let i = 0; i < verifies.length; i++) {
        const { batch, bitfield, manifest } = verifies[i]

        if (!batch.commitable()) {
          verifies[i] = null // signal that we cannot commit this one
          continue
        }

        if (bitfield) {
          bits.setRange(bitfield.start, bitfield.start + 1, true)
        }

        // if we got a manifest AND its strictly a non compat one, lets store it
        if (manifest && this.header.manifest === null) {
          if (!Verifier.isValidManifest(this.header.key, manifest)) {
            throw INVALID_CHECKSUM('Manifest hash does not match', this.discoveryKey)
          }
          this._setManifest(tx, manifest, null)
        }

        if (batch.commitable()) batch.commit(tx)
      }

      const ranges = bits.flush(tx, this.bitfield)

      await this.state.flush()

      for (const { start, end, value } of ranges) {
        this._setBitfieldRanges(start, end, value)
      }

      for (let i = 0; i < verifies.length; i++) {
        const bitfield = verifies[i] && verifies[i].bitfield
        if (bitfield) {
          this.updateContiguousLength(bitfield)
          this.replicator.onhave(bitfield.start, bitfield.length, bitfield.drop)
        }
      }

      if (this.hintsChanged) await this.flushHints()
    } finally {
      this.state._clearActiveBatch()
      this.state.mutex.unlock()
    }

    return verifies[0] !== null
  }

  async flushHints() {
    if (!this.hintsChanged) return
    this.hintsChanged = false // we unset this immediately as a "debounce"

    const tx = this.state.storage.write()

    tx.setHints({
      contiguousLength: this.header.hints.contiguousLength,
      remoteContiguousLength: this.header.hints.remoteContiguousLength,
      recovering: this.header.hints.recovering
    })

    await tx.flush()
  }

  async checkConflict(proof, from) {
    if (this.state.length < proof.upgrade.length || proof.fork !== this.state.fork) {
      // out of date this proof - ignore for now
      return false
    }

    // sanity check -> no manifest, no way to verify
    if (!this.header.manifest) {
      return false
    }

    const batch = MerkleTree.verifyFullyRemote(this.state, proof)

    try {
      this._verifyBatchUpgrade(batch, proof.manifest)
    } catch {
      return true
    }

    const roots = await maybeGetRootsFromStorage(this.storage, proof.upgrade.length)
    const remoteTreeHash = crypto.tree(proof.upgrade.nodes)
    const localTreeHash = crypto.tree(roots)

    try {
      const rx = this.state.storage.read()
      const treeProofPromise = MerkleTree.proof(this.state, rx, {
        block: null,
        hash: null,
        seek: null,
        upgrade: {
          start: 0,
          length: proof.upgrade.length
        }
      })

      rx.tryFlush()

      const treeProof = await treeProofPromise

      const verifyBatch = MerkleTree.verifyFullyRemote(this.state, await treeProof.settle())
      this._verifyBatchUpgrade(verifyBatch, this.header.manifest)
    } catch (err) {
      if (err.code !== 'INVALID_OPERATION') return true

      return !(await this._repairTreeNodes(proof))
    }

    // both proofs are valid, now check if they forked
    if (b4a.equals(localTreeHash, remoteTreeHash)) return false

    await this.state.mutex.lock()

    try {
      const tx = this.state.createWriteBatch()

      this.header.frozen = true

      tx.setAuth({
        key: this.header.key,
        discoveryKey: this.discoveryKey,
        manifest: this.header.manifest,
        keyPair: this.header.keyPair,
        frozen: true
      })

      await this.state.flush()
    } finally {
      this.state.mutex.unlock()
    }

    // tmp log so we can see these
    const id = b4a.toString(this.discoveryKey, 'hex')
    console.log(
      '[hypercore] conflict detected in ' +
        id +
        ' (writable=' +
        !!this.header.keyPair +
        ',quorum=' +
        this.header.manifest.quorum +
        ')'
    )
    await this._onconflict(proof)
    return true
  }

  async _repairTreeNodes(proof) {
    if (!this._repairMode) return false

    await this.state.mutex.lock()

    for (let i = this.monitors.length - 1; i >= 0; i--) {
      this.monitors[i].emit('repairing', proof)
    }

    try {
      const batch = MerkleTree.verifyFullyRemote(this.state, proof)
      this._verifyBatchUpgrade(batch, proof.manifest)
    } catch {
      this.state.mutex.unlock()
      for (let i = this.monitors.length - 1; i >= 0; i--) {
        this.monitors[i].emit('repair-failed')
      }
      return false
    }

    // Attempt to repair local merkle tree with remote roots
    const atom = this.storage.createAtom()
    const atomized = this.storage.atomize(atom)
    const tx = atomized.write()
    for (const node of proof.upgrade.nodes) {
      tx.putTreeNode(node)
    }
    await tx.flush()

    // Try local proof again
    try {
      const rx = atomized.read()
      const treeProofPromise = MerkleTree.proof(this.state, rx, {
        block: null,
        hash: null,
        seek: null,
        upgrade: {
          start: 0,
          length: proof.upgrade.length
        }
      })

      rx.tryFlush()

      const treeProof = await treeProofPromise

      const verifyBatch = MerkleTree.verifyFullyRemote(this.state, await treeProof.settle())
      this._verifyBatchUpgrade(verifyBatch, this.header.manifest)
    } catch (err) {
      this.state.mutex.unlock()
      for (let i = this.monitors.length - 1; i >= 0; i--) {
        this.monitors[i].emit('repair-failed')
      }
      return false
    }

    await atom.flush()

    for (let i = this.monitors.length - 1; i >= 0; i--) {
      this.monitors[i].emit('repaired', proof)
    }
    this.replicator.setPushOnly(false)
    this.state.mutex.unlock()
    return true
  }

  async verifyReorg(proof) {
    const batch = new ReorgBatch(this.state)
    await MerkleTree.reorg(this.state, proof, batch)
    const manifest = this._verifyBatchUpgrade(batch, proof.manifest)

    if (manifest && !this.header.manifest) {
      await this.state.mutex.lock()
      try {
        if (manifest && this.header.manifest === null) {
          const tx = this.state.createWriteBatch()
          this._setManifest(tx, Verifier.createManifest(manifest), null)
          await this.state.flush()
        }
      } finally {
        this.state._unlock()
      }
    }

    return batch
  }

  async verify(proof, from) {
    // We cannot apply "other forks" atm.
    // We should probably still try and they are likely super similar for non upgrades
    // but this is easy atm (and the above layer will just retry)
    if (proof.fork !== this.state.fork) return false

    const batch = await MerkleTree.verify(this.state, proof)

    if (batch.upgraded && batch.length <= this.state.length) {
      await batch.downgrade()
    }

    if (!batch.commitable()) {
      return false
    }

    const value = (proof.block && proof.block.value) || null
    const op = {
      batch,
      bitfield: value && { drop: false, start: proof.block.index, length: 1 },
      value,
      manifest: proof.manifest,
      from
    }

    if (batch.upgraded) {
      return this._verifyExclusive(op)
    }

    if (this._verifies !== null) {
      const verifies = this._verifies
      const i = verifies.push(op)
      await this._verified
      return verifies[i] !== null
    }

    this._verifies = [op]
    this._verified = this._verifyShared()

    return this._verified
  }

  async reorg(batch) {
    if (!batch.commitable()) return false

    this.truncating++

    try {
      await this.state.reorg(batch)
    } finally {
      this.truncating--
    }

    return true
  }

  openSkipBitfield() {
    if (this.skipBitfield !== null) return this.skipBitfield
    this.skipBitfield = new RemoteBitfield()
    const buf = this.bitfield.toBuffer(this.state.length)
    const bitfield = new Uint32Array(buf.buffer, buf.byteOffset, buf.byteLength / 4)
    this.skipBitfield.insert(0, bitfield)
    return this.skipBitfield
  }

  _setBitfieldRanges(start, end, value) {
    this.bitfield.setRange(start, end, value)
    if (this.skipBitfield !== null) this.skipBitfield.setRange(start, end, value)
  }

  close() {
    if (!this.closing) this.closing = this._close()
    return this.closing
  }

  updateContiguousLength(bitfield) {
    const contig = updateContigBatch(this.header.hints.contiguousLength, bitfield, this.bitfield)

    if (contig.length !== -1 && contig.length !== this.header.hints.contiguousLength) {
      this.header.hints.contiguousLength = contig.length
      this.hintsChanged = true
    }
  }

  updateRemoteContiguousLength(length) {
    this.header.hints.remoteContiguousLength = length
    this.hintsChanged = true
    this.remoteContiguousLengthChanged = true
  }

  onappend(tree, bitfield) {
    this.header.tree = tree

    if (!bitfield) {
      this.replicator.onupgrade()
      return
    }

    this.replicator.cork()

    const { start, length, drop } = bitfield

    this._setBitfieldRanges(start, start + length, true)
    this.updateContiguousLength({ start, length, drop: false })

    this.replicator.onupgrade()
    this.replicator.onhave(start, length, drop)
    this.replicator.uncork()
  }

  ontruncate(tree, { start, length }) {
    if (tree) this.header.tree = tree

    this.replicator.cork()

    this.replicator.ontruncate(start, length)
    this.replicator.onhave(start, length, true)
    this.replicator.onupgrade()
    this.replicator.uncork()

    for (const sessionState of this.sessionStates) {
      if (start < sessionState.snapshotCompatLength) sessionState.snapshotCompatLength = start
    }

    this._setBitfieldRanges(start, start + length, false)
    this.updateContiguousLength({ start, length, drop: true })
  }

  async _onconflict(proof) {
    await this.replicator.onconflict()

    for (let i = this.monitors.length - 1; i >= 0; i--) {
      const s = this.monitors[i]
      s.emit('conflict', proof.upgrade.length, proof.fork, proof)
    }

    const err = new Error('Two conflicting signatures exist for length ' + proof.upgrade.length)
    await this.closeAllSessions(err)
  }

  async closeAllSessions(err) {
    // this.sessions modifies itself when a session closes
    // This way we ensure we indeed iterate over all sessions
    const sessions = this.allSessions()

    const all = []
    for (const s of sessions) all.push(s.close({ error: err, force: false })) // force false or else infinite recursion
    await Promise.allSettled(all)
  }

  async destroy() {
    if (this.destroyed === true) return
    this.destroyed = true

    if (this.hasSession() === true) throw new Error('Cannot destroy while sessions are open')

    const weakSessions = this.allSessions()

    if (this.replicator) this.replicator.destroy()
    if (this.state) await this.state.close()

    // close all pending weak sessions...
    for (const s of weakSessions) s.close().catch(noop)
  }

  async _close() {
    if (this.opened === false) await this.opening
    if (this.hasSession() === true) throw new Error('Cannot close while sessions are open')

    if (this.replicator) await this.replicator.close()

    await this.destroy()
    if (this.autoClose) await this.storage.store.close()

    this.closed = true
  }
}

function updateContigBatch(start, upd, bitfield) {
  const end = upd.start + upd.length

  let c = start

  if (upd.drop) {
    // If we dropped a block in the current contig range, "downgrade" it
    if (c > upd.start) {
      c = upd.start
    }
  } else {
    if (c <= end && c >= upd.start) {
      c = end
      while (bitfield.get(c)) c++
    }
  }

  if (c === start) {
    return {
      length: -1
    }
  }

  if (c > start) {
    return {
      length: c
    }
  }

  return {
    length: c
  }
}

function getDefaultTree() {
  return {
    fork: 0,
    length: 0,
    rootHash: null,
    signature: null,
    timestamp: 0
  }
}

function parseHeader(info) {
  if (!info) return null

  return {
    key: info.key,
    manifest: info.manifest,
    external: null,
    keyPair: info.keyPair,
    tree: info.head || getDefaultTree(),
    group: info.group,
    hints: {
      reorgs: [],
      contiguousLength: info.hints ? info.hints.contiguousLength : 0,
      remoteContiguousLength: info.hints ? info.hints.remoteContiguousLength : 0,
      recovering: info.hints ? info.hints.recovering : 0
    }
  }
}

async function maybeGetRootsFromStorage(storage, length) {
  try {
    return length ? await MerkleTree.getRootsFromStorage(storage, length) : []
  } catch (err) {
    if (err.code !== 'INVALID_OPERATION') throw err
    // If INVALID_OPERATION, a root is likely missing so load empty roots to try to recover
    return []
  }
}

function noop() {}

async function getCoreInfo(storage) {
  const r = storage.read()

  const auth = r.getAuth()
  const head = r.getHead()
  const hints = r.getHints()
  const group = r.getGroup()

  r.tryFlush()

  const [authInfo, headInfo, hintsInfo, groupInfo] = await Promise.all([auth, head, hints, group])
  return {
    ...authInfo,
    head: headInfo,
    hints: hintsInfo,
    group: groupInfo
  }
}
const sodium = require('sodium-universal')
const c = require('compact-encoding')
const b4a = require('b4a')
const { DEFAULT_ENCRYPTION } = require('./caps')

const nonce = b4a.alloc(sodium.crypto_stream_NONCEBYTES)

module.exports = class DefaultEncryption {
  static PADDING = 8

  constructor(encryptionKey, hypercoreKey, opts = {}) {
    this.key = encryptionKey
    this.compat = opts.compat === true

    const keys = DefaultEncryption.deriveKeys(encryptionKey, hypercoreKey, opts)

    this.blockKey = keys.block
    this.blindingKey = keys.blinding
  }

  static deriveKeys(encryptionKey, hypercoreKey, { block = false, compat = false } = {}) {
    const subKeys = b4a.alloc(2 * sodium.crypto_stream_KEYBYTES)

    const blockKey = block ? encryptionKey : subKeys.subarray(0, sodium.crypto_stream_KEYBYTES)
    const blindingKey = subKeys.subarray(sodium.crypto_stream_KEYBYTES)

    if (!block) {
      if (compat) {
        sodium.crypto_generichash_batch(blockKey, [encryptionKey], hypercoreKey)
      } else {
        sodium.crypto_generichash_batch(blockKey, [DEFAULT_ENCRYPTION, hypercoreKey, encryptionKey])
      }
    }

    sodium.crypto_generichash(blindingKey, blockKey)

    return {
      blinding: blindingKey,
      block: blockKey
    }
  }

  static blockEncryptionKey(hypercoreKey, encryptionKey) {
    const blockKey = b4a.alloc(sodium.crypto_stream_KEYBYTES)
    sodium.crypto_generichash_batch(blockKey, [DEFAULT_ENCRYPTION, hypercoreKey, encryptionKey])
    return blockKey
  }

  static encrypt(index, block, fork, blockKey, blindingKey) {
    const padding = block.subarray(0, DefaultEncryption.PADDING)
    block = block.subarray(DefaultEncryption.PADDING)

    c.uint64.encode({ start: 0, end: 8, buffer: padding }, fork)
    c.uint64.encode({ start: 0, end: 8, buffer: nonce }, index)

    // Zero out any previous padding.
    nonce.fill(0, 8, 8 + padding.byteLength)

    // Blind the fork ID, possibly risking reusing the nonce on a reorg of the
    // Hypercore. This is fine as the blinding is best-effort and the latest
    // fork ID shared on replication anyway.
    sodium.crypto_stream_xor(padding, padding, nonce, blindingKey)

    nonce.set(padding, 8)

    // The combination of a (blinded) fork ID and a block index is unique for a
    // given Hypercore and is therefore a valid nonce for encrypting the block.
    sodium.crypto_stream_xor(block, block, nonce, blockKey)
  }

  static decrypt(index, block, blockKey) {
    const padding = block.subarray(0, DefaultEncryption.PADDING)
    block = block.subarray(DefaultEncryption.PADDING)

    c.uint64.encode({ start: 0, end: 8, buffer: nonce }, index)

    nonce.set(padding, 8)

    // Decrypt the block using the blinded fork ID.
    sodium.crypto_stream_xor(block, block, nonce, blockKey)
  }

  encrypt(index, block, fork, core) {
    if (core.compat !== this.compat) this._reload(core)
    return DefaultEncryption.encrypt(index, block, fork, this.blockKey, this.blindingKey)
  }

  decrypt(index, block, core) {
    if (core.compat !== this.compat) this._reload(core)
    return DefaultEncryption.decrypt(index, block, this.blockKey)
  }

  padding() {
    return DefaultEncryption.PADDING
  }

  _reload(core) {
    const block = b4a.equals(this.key, this.blockKey)
    const keys = DefaultEncryption.deriveKeys(this.key, core.key, { block, compat: core.compat })

    this.blockKey = keys.blockKey
    this.blindingKey = keys.blindingKey
  }
}
module.exports = class Download {
  constructor(session, range) {
    this.session = session
    this.range = range
    this.request = null
    this.opened = false
    this.opening = this._open()
    this.opening.catch(noop)
  }

  ready() {
    return this.opening
  }

  async _open() {
    if (this.session.opened === false) await this.session.opening
    this._download()
    this.opened = true
  }

  async done() {
    await this.ready()

    try {
      return await this.request.promise
    } catch (err) {
      if (isSessionMoved(err)) return this._download()
      throw err
    }
  }

  _download() {
    const activeRequests = (this.range && this.range.activeRequests) || this.session.activeRequests
    this.request = this.session.core.replicator.addRange(activeRequests, this.range)
    this.request.promise.catch(noop)
    return this.request.promise
  }

  /**
   * Deprecated. Use `range.done()`.
   */
  downloaded() {
    return this.done()
  }

  destroy() {
    this._destroyBackground().catch(noop)
  }

  async _destroyBackground() {
    if (this.opened === false) await this.ready()
    if (this.request.context) this.request.context.detach(this.request)
  }
}

function noop() {}

function isSessionMoved(err) {
  return err.code === 'SESSION_MOVED'
}
module.exports = {
  SMALL_WANTS: 1
}
// this helper is for fully remote proofs, is like in a push notification where no other context exists

const { MerkleTree } = require('./merkle-tree.js')
const messages = require('./messages.js')
const b4a = require('b4a')
const c = require('compact-encoding')
const crypto = require('hypercore-crypto')
const flat = require('flat-tree')

class SlimSession {
  constructor(storage, auth, head, roots) {
    this.fork = head ? head.fork : 0
    this.roots = roots
    this.length = head ? head.length : 0
    this.signature = head ? head.signature : null
    this.ancestors = this.length
    this.byteLength = 0
    this.prologue = auth.manifest.prologue
    this.storage = storage

    for (let i = 0; i < roots.length; i++) this.byteLength += roots[i].size
  }
}

module.exports = { verify, proof }

async function verify(storage, buffer, { referrer = null } = {}) {
  const state = { buffer, start: 0, end: buffer.byteLength }

  const discoveryKey = c.fixed32.decode(state)
  const proof = messages.wire.data.decode(state)

  const result = {
    key: null,
    discoveryKey,
    newer: true,
    length: 0,
    proof,
    block: null
  }

  const core = await storage.resumeCore(discoveryKey)
  if (core === null) return null

  let rx = core.read()
  const authPromise = rx.getAuth()
  const headPromise = rx.getHead()
  const referrerPromise = rx.getUserData('referrer')

  rx.tryFlush()

  const [auth, head, ref] = await Promise.all([authPromise, headPromise, referrerPromise])

  if (auth === null) return null

  if (referrer && (!ref || !b4a.equals(ref, referrer))) return null

  rx = core.read()

  const rootPromises = []

  for (const index of flat.fullRoots(head ? 2 * head.length : 0)) {
    rootPromises.push(rx.getTreeNode(index))
  }

  rx.tryFlush()

  let roots = await Promise.all(rootPromises)
  if (roots.some(isNull)) roots = []

  const length = head ? head.length : 0

  if (!auth.manifest || !auth.manifest.signers.length) return null

  const batch = await MerkleTree.verifyFullyRemote(new SlimSession(core, auth, head, roots), proof)
  const publicKey = auth.manifest.signers[0].publicKey

  let signable = null
  let signature = null

  if (auth.manifest.version === 0) {
    signable = batch.signable(auth.manifest.signers[0].namespace)
    signature = batch.signature
  } else {
    if (batch.signature[0] !== 1) return null
    if (batch.signature[1] !== 0) return null

    signable = batch.signable(auth.key)
    signature = batch.signature.subarray(2, 66)
  }

  if (!crypto.verify(signable, signature, publicKey)) {
    return null
  }

  result.key = auth.key
  result.discoveryKey = discoveryKey
  result.newer = batch.length > length
  result.length = batch.length
  result.block = proof.block

  return result
}

async function proof(sender, { index, block = null, upgrade = null } = {}) {
  const proof = await sender.proof({
    block: block ? { index, nodes: 0 } : null,
    upgrade: upgrade ? upgrade : { start: 0, length: sender.length }
  })

  if (block) proof.block.value = block
  proof.manifest = sender.core.header.manifest

  const state = { buffer: null, start: 0, end: 0 }
  const data = { request: 0, ...proof }

  c.fixed32.preencode(state, sender.discoveryKey)
  messages.wire.data.preencode(state, data)

  state.buffer = b4a.allocUnsafe(state.end)

  c.fixed32.encode(state, sender.discoveryKey)
  messages.wire.data.encode(state, data)

  return state.buffer
}

function isNull(x) {
  return x === null
}
const TICKS = 16

module.exports = class HotswapQueue {
  constructor() {
    this.priorities = [[], [], []]
  }

  *pick(peer) {
    for (let i = 0; i < this.priorities.length; i++) {
      // try first one more than second one etc etc
      let ticks = (this.priorities.length - i) * TICKS
      const queue = this.priorities[i]

      for (let j = 0; j < queue.length; j++) {
        const r = j + Math.floor(Math.random() * queue.length - j)
        const a = queue[j]
        const b = queue[r]

        if (r !== j) {
          queue[(b.hotswap.index = j)] = b
          queue[(a.hotswap.index = r)] = a
        }

        if (hasInflight(b, peer)) continue

        yield b

        if (--ticks <= 0) break
      }
    }
  }

  add(block) {
    if (block.hotswap !== null) this.remove(block)
    if (block.inflight.length === 0 || block.inflight.length >= 3) return

    // TODO: also use other stuff to determine queue prio
    const queue = this.priorities[block.inflight.length - 1]

    const index = queue.push(block) - 1
    block.hotswap = { ref: this, queue, index }
  }

  remove(block) {
    const hotswap = block.hotswap
    if (hotswap === null) return

    block.hotswap = null
    const head = hotswap.queue.pop()
    if (head === block) return
    hotswap.queue[(head.hotswap.index = hotswap.index)] = head
  }
}

function hasInflight(block, peer) {
  for (let j = 0; j < block.inflight.length; j++) {
    if (block.inflight[j].peer === peer) return true
  }
  return false
}
module.exports = class Info {
  constructor(opts = {}) {
    this.key = opts.key
    this.discoveryKey = opts.discoveryKey
    this.length = opts.length || 0
    this.contiguousLength = opts.contiguousLength || 0
    this.byteLength = opts.byteLength || 0
    this.fork = opts.fork || 0
    this.padding = opts.padding || 0
    this.storage = opts.storage || null
  }

  static async from(session, opts = {}) {
    return new Info({
      key: session.key,
      discoveryKey: session.discoveryKey,
      length: session.length,
      contiguousLength: session.contiguousLength,
      byteLength: session.byteLength,
      fork: session.fork,
      padding: session.padding,
      storage: opts.storage ? await this.storage(session) : null
    })
  }

  static async storage(session) {
    const { oplog, tree, blocks, bitfield } = session.core
    try {
      return {
        oplog: await Info.bytesUsed(oplog.storage),
        tree: await Info.bytesUsed(tree.storage),
        blocks: await Info.bytesUsed(blocks.storage),
        bitfield: await Info.bytesUsed(bitfield.storage)
      }
    } catch {
      return null
    }
  }

  static bytesUsed(file) {
    return new Promise((resolve, reject) => {
      file.stat((err, st) => {
        if (err) {
          resolve(0) // prob just file not found (TODO, improve)
        } else if (typeof st.blocks !== 'number') {
          reject(new Error('cannot determine bytes used'))
        } else {
          resolve(st.blocks * 512)
        }
      })
    })
  }
}
const b4a = require('b4a')

module.exports = function (core, depth, opts) {
  let indent = ''
  if (typeof opts.indentationLvl === 'number') {
    while (indent.length < opts.indentationLvl) indent += ' '
  }

  let peers = ''
  const min = Math.min(core.peers.length, 5)

  for (let i = 0; i < min; i++) {
    const peer = core.peers[i]

    peers += `${indent}    Peer(\n`
    peers += `${indent}      remotePublicKey: ${opts.stylize(toHex(peer.remotePublicKey), 'string')}\n`
    peers += `${indent}      remoteLength: ${opts.stylize(peer.remoteLength, 'number')}\n`
    peers += `${indent}      remoteFork: ${opts.stylize(peer.remoteFork, 'number')}\n`
    peers += `${indent}      remoteCanUpgrade: ${opts.stylize(peer.remoteCanUpgrade, 'boolean')}\n`
    peers += `${indent}    )\n`
  }

  if (core.peers.length > 5) {
    peers += `${indent}  ... and ${core.peers.length - 5} more\n`
  }

  if (peers) peers = `[\n${peers}${indent}  ]`
  else peers = `[ ${opts.stylize(0, 'number')} ]`

  return (
    `${core.constructor.name}(\n` +
    `${indent}  id: ${opts.stylize(core.id, 'string')}\n` +
    `${indent}  key: ${opts.stylize(toHex(core.key), 'string')}\n` +
    `${indent}  discoveryKey: ${opts.stylize(toHex(core.discoveryKey), 'string')}\n` +
    `${indent}  opened: ${opts.stylize(core.opened, 'boolean')}\n` +
    `${indent}  closed: ${opts.stylize(core.closed, 'boolean')}\n` +
    `${indent}  snapshotted: ${opts.stylize(core.snapshotted, 'boolean')}\n` +
    `${indent}  writable: ${opts.stylize(core.writable, 'boolean')}\n` +
    `${indent}  length: ${opts.stylize(core.length, 'number')}\n` +
    `${indent}  fork: ${opts.stylize(core.fork, 'number')}\n` +
    `${indent}  sessions: [ ${opts.stylize(core.sessions.length, 'number')} ]\n` +
    `${indent}  activeRequests: [ ${opts.stylize(core.activeRequests.length, 'number')} ]\n` +
    `${indent}  peers: ${peers}\n` +
    `${indent})`
  )
}

function toHex(buf) {
  return buf && b4a.toString(buf, 'hex')
}
const BigSparseArray = require('big-sparse-array')
const { Transform } = require('streamx')
const quickbit = require('./compat').quickbit
const b4a = require('b4a')

const BITS_PER_PAGE = 32768
const BYTES_PER_PAGE = BITS_PER_PAGE / 8

class MarkPage {
  constructor() {
    this.bitfield = null
    this.loaded = new Promise((resolve) => {
      this.load = resolve
    })
  }

  setBitfield(bitfield) {
    this.bitfield = bitfield
    this.load()
  }

  get(index) {
    return quickbit.get(this.bitfield, index)
  }

  set(index, val) {
    quickbit.set(this.bitfield, index, val)
  }
}

module.exports = class MarkBitfield {
  static BITS_PER_PAGE = BITS_PER_PAGE
  static BYTES_PER_PAGE = BYTES_PER_PAGE

  constructor(storage) {
    this.storage = storage
    this._pages = new BigSparseArray()
  }

  async loadPage(pageIndex) {
    const p = this._pages.set(pageIndex, new MarkPage())
    const rx = this.storage.read()
    const pageBuf = rx.getMark(pageIndex)
    rx.tryFlush()
    const bitfield = (await pageBuf) ?? b4a.alloc(BYTES_PER_PAGE)
    await p.setBitfield(bitfield)
    return p
  }

  async get(index) {
    const j = index & (BITS_PER_PAGE - 1)
    const i = (index - j) / BITS_PER_PAGE

    let p = this._pages.get(i)
    if (!p) p = await this.loadPage(i)

    return p.get(j)
  }

  async set(index, val) {
    const j = index & (BITS_PER_PAGE - 1)
    const i = (index - j) / BITS_PER_PAGE

    let p = this._pages.get(i)

    if (!p && val) p = await this.loadPage(i)

    if (p) {
      await p.loaded
      p.set(j, val)
      const tx = this.storage.write()
      tx.putMark(i, p.bitfield)
      await tx.flush()
    }
  }

  async clear() {
    const tx = this.storage.write()
    tx.deleteMarkRange(0, -1)
    await tx.flush()
    this._pages = new BigSparseArray()
  }

  createMarkStream({ reverse = false } = {}) {
    return this.storage.createMarkStream({ reverse }).pipe(
      new Transform({
        transform({ index, page }, cb) {
          let bitIndex = reverse
            ? quickbit.findLast(page, true, BITS_PER_PAGE)
            : quickbit.findFirst(page, true, 0)
          while (bitIndex !== -1) {
            const blockIndex = index * BITS_PER_PAGE + bitIndex
            this.push(blockIndex)

            // Account for `bitIndex` being either causing infinite loop
            if (bitIndex === 0 && reverse) break

            bitIndex = reverse
              ? quickbit.findLast(page, true, bitIndex - 1)
              : quickbit.findFirst(page, true, bitIndex + 1)
          }

          cb(null)
        }
      })
    )
  }
}
const flat = require('flat-tree')
const crypto = require('hypercore-crypto')
const b4a = require('b4a')
const unslab = require('unslab')
const caps = require('./caps')
const {
  INVALID_PROOF,
  INVALID_CHECKSUM,
  INVALID_OPERATION,
  BAD_ARGUMENT,
  ASSERTION
} = require('hypercore-errors')

class NodeQueue {
  constructor(nodes, extra = null) {
    this.i = 0
    this.nodes = nodes
    this.extra = extra
    this.length = nodes.length + (this.extra === null ? 0 : 1)
  }

  shift(index) {
    if (this.extra !== null && this.extra.index === index) {
      const node = this.extra
      this.extra = null
      this.length--
      return node
    }

    if (this.i >= this.nodes.length) {
      throw INVALID_OPERATION('Expected node ' + index + ', got (nil)')
    }

    const node = this.nodes[this.i++]
    if (node.index !== index) {
      throw INVALID_OPERATION('Expected node ' + index + ', got node ' + node.index)
    }

    this.length--
    return node
  }
}

class MerkleTreeBatch {
  constructor(session) {
    this.fork = session.fork
    this.roots = [...session.roots]
    this.length = session.length
    this.signature = session.signature
    this.ancestors = session.length
    this.byteLength = session.byteLength
    this.prologue = session.prologue
    this.hashCached = null

    this.committed = false
    this.truncated = false
    this.treeLength = session.length
    this.treeFork = session.fork
    this.storage = session.storage
    this.session = session
    this.nodes = []
    this.upgraded = false
  }

  checkout(length, additionalRoots) {
    const roots = []
    let r = 0

    const head = 2 * length - 2
    const gaps = new Set()
    const all = new Map()

    // additional roots is so the original roots can be passed (we mutate the array in appendRoot)
    if (additionalRoots) {
      for (const node of additionalRoots) all.set(node.index, node)
    }

    for (const node of this.nodes) all.set(node.index, node)

    for (const index of flat.fullRoots(head + 2)) {
      const left = flat.leftSpan(index)
      if (left !== 0) gaps.add(left - 1)

      if (r < this.roots.length && this.roots[r].index === index) {
        roots.push(this.roots[r++])
        continue
      }
      const node = all.get(index)
      if (!node) throw new BAD_ARGUMENT('root missing for given length')
      roots.push(node)
    }

    this.roots = roots
    this.length = length
    this.byteLength = totalSize(roots)
    this.hashCached = null
    this.signature = null

    for (let i = 0; i < this.nodes.length; i++) {
      const index = this.nodes[i].index
      if (index <= head && !gaps.has(index)) continue
      const last = this.nodes.pop()
      if (i < this.nodes.length) this.nodes[i--] = last
    }
  }

  prune(length) {
    if (length === 0) return

    const head = 2 * length - 2
    const gaps = new Set()

    // TODO: make a function for this in flat-tree
    for (const index of flat.fullRoots(head + 2)) {
      const left = flat.leftSpan(index)
      if (left !== 0) gaps.add(left - 1)
    }

    for (let i = 0; i < this.nodes.length; i++) {
      const index = this.nodes[i].index
      if (index > head || gaps.has(index)) continue
      const last = this.nodes.pop()
      if (i < this.nodes.length) this.nodes[i--] = last
    }
  }

  clone() {
    const b = new MerkleTreeBatch(this.session)

    b.fork = this.fork
    b.roots = [...this.roots]
    b.length = this.length
    b.byteLength = this.byteLength
    b.signature = this.signature
    b.treeLength = this.treeLength
    b.treeFork = this.treeFork
    b.tree = this.tree
    b.nodes = [...this.nodes]
    b.upgraded = this.upgraded

    return b
  }

  hash() {
    if (this.hashCached === null) this.hashCached = unslab(crypto.tree(this.roots))
    return this.hashCached
  }

  signable(manifestHash) {
    return caps.treeSignable(manifestHash, this.hash(), this.length, this.fork)
  }

  signableCompat(noHeader) {
    return caps.treeSignableCompat(this.hash(), this.length, this.fork, noHeader)
  }

  get(index) {
    if (index >= this.length * 2) {
      return null
    }

    for (const n of this.nodes) {
      if (n.index === index) return n
    }

    return getTreeNodeFromStorage(this.session.storage, index)
  }

  // deprecated, use sssion proof instead
  proof(batch, { block, hash, seek, upgrade }) {
    return generateProof(this.session, batch, block, hash, seek, upgrade)
  }

  verifyUpgrade(proof) {
    const unverified = verifyTree(proof, this.nodes)

    if (!proof.upgrade) throw INVALID_OPERATION('Expected upgrade proof')

    return verifyUpgrade(proof, unverified, this)
  }

  addNodesUnsafe(nodes) {
    for (let i = 0; i < nodes.length; i++) {
      this.nodes.push(nodes[i])
    }
  }

  append(buf) {
    const head = this.length * 2
    const ite = flat.iterator(head)
    const node = blockNode(head, buf)

    this.appendRoot(node, ite)
  }

  appendRoot(node, ite) {
    node = unslabNode(node)
    this.hashCached = null
    this.upgraded = true
    this.length += ite.factor / 2
    this.byteLength += node.size
    this.roots.push(node)
    this.nodes.push(node)

    while (this.roots.length > 1) {
      const a = this.roots[this.roots.length - 1]
      const b = this.roots[this.roots.length - 2]

      // TODO: just have a peek sibling instead? (pretty sure it's always the left sib as well)
      if (ite.sibling() !== b.index) {
        ite.sibling() // unset so it always points to last root
        break
      }

      const node = unslabNode(parentNode(ite.parent(), a, b))
      this.nodes.push(node)
      this.roots.pop()
      this.roots.pop()
      this.roots.push(node)
    }
  }

  commitable() {
    return (
      this.treeFork === this.session.fork &&
      (this.upgraded
        ? this.treeLength === this.session.length
        : this.treeLength <= this.session.length)
    )
  }

  commit(tx) {
    if (tx === undefined) throw INVALID_OPERATION('No database batch was passed')

    if (this.session.core._repairMode) {
      throw ASSERTION('Cannot commit while repair mode is on')
    }

    if (!this.commitable()) {
      throw INVALID_OPERATION('Tree was modified during batch, refusing to commit')
    }

    if (this.upgraded) this._commitUpgrade(tx)

    for (let i = 0; i < this.nodes.length; i++) {
      const node = this.nodes[i]
      tx.putTreeNode(node)
    }

    this.committed = true

    return this
  }

  _commitUpgrade(tx) {
    // TODO: If easy to detect, we should refuse an trunc+append here without a fork id
    // change. Will only happen on user error so mostly to prevent that.

    if (this.ancestors < this.treeLength) {
      tx.deleteTreeNodeRange(this.ancestors * 2, this.treeLength * 2)

      if (this.ancestors > 0) {
        const head = this.ancestors * 2
        const ite = flat.iterator(head - 2)

        while (true) {
          if (ite.contains(head) && ite.index < head) {
            tx.deleteTreeNode(ite.index)
          }
          if (ite.offset === 0) break
          ite.parent()
        }

        this.truncated = true
      }
    }
  }

  seek(bytes, padding) {
    return new ByteSeeker(this, this, bytes, padding)
  }

  byteRange(index) {
    const rx = this.storage.read()
    const range = getByteRange(this, index, rx)
    rx.tryFlush()

    return range
  }

  byteOffset(index) {
    if (index === 2 * this.length) return this.byteLength

    const rx = this.storage.read()
    const offset = getByteOffset(this, index, rx)
    rx.tryFlush()

    return offset
  }

  async downgrade() {
    if (!this.upgraded) return true

    const rx = this.storage.read()
    const nodePromises = []

    for (const r of this.roots) {
      nodePromises.push(rx.getTreeNode(r.index))
    }

    rx.tryFlush()

    const nodes = await Promise.all(nodePromises)

    for (let i = 0; i < this.roots.length; i++) {
      const root = this.roots[i]
      const node = nodes[i]

      if (!node) return false
      if (!b4a.equals(node.hash, root.hash)) return false
    }

    this.upgraded = false
    return true
  }

  async restore(length) {
    if (length === this.length) return this

    const roots = unslabNodes(await MerkleTree.getRootsFromStorage(this.storage, length))

    this.roots = roots
    this.length = length
    this.byteLength = totalSize(roots)
    this.ancestors = length

    for (const node of roots) this.byteLength += node.size

    return this
  }
}

class ReorgBatch extends MerkleTreeBatch {
  constructor(session) {
    super(session)

    this.roots = []
    this.length = 0
    this.byteLength = 0
    this.diff = null
    this.ancestors = 0
    // We set upgraded because reorgs are signed so hit will
    // hit the same code paths (like the treeLength check in commit)
    this.upgraded = true
    this.want = {
      nodes: 0,
      start: 0,
      end: 0
    }
  }

  get finished() {
    return this.want === null
  }

  update(proof) {
    if (this.want === null) return true

    const nodes = []
    const root = verifyTree(proof, nodes)

    if (root === null || !b4a.equals(root.hash, this.diff.hash)) return false

    this.nodes.push(...nodes)
    return this._update(nodes)
  }

  async _update(nodes) {
    const n = new Map()
    for (const node of nodes) n.set(node.index, node)

    let diff = null
    const ite = flat.iterator(this.diff.index)
    const startingDiff = this.diff

    while ((ite.index & 1) !== 0) {
      const left = n.get(ite.leftChild())
      if (!left) break

      const existing = await getTreeNodeFromStorage(this.session.storage, left.index)
      if (!existing || !b4a.equals(existing.hash, left.hash)) {
        diff = left
      } else {
        diff = n.get(ite.sibling())
      }
    }

    if ((this.diff.index & 1) === 0) return true
    if (diff === null) return false
    if (startingDiff !== this.diff) return false

    return this._updateDiffRoot(diff)
  }

  _updateDiffRoot(diff) {
    if (this.want === null) return true

    const spans = flat.spans(diff.index)
    const start = spans[0] / 2
    const end = Math.min(this.treeLength, spans[1] / 2 + 1)
    const len = end - start

    this.ancestors = start
    this.diff = diff

    if ((diff.index & 1) === 0 || this.want.start >= this.treeLength || len <= 0) {
      this.want = null
      return true
    }

    this.want.start = start
    this.want.end = end
    this.want.nodes = log2(spans[1] - spans[0] + 2) - 1

    return false
  }
}

class ByteSeeker {
  constructor(session, bytes, padding = 0) {
    this.session = session
    this.bytes = bytes
    this.padding = padding

    const size = session.byteLength - session.length * padding

    this.start = bytes >= size ? session.length : 0
    this.end = bytes < size ? session.length : 0
  }

  async _seek(bytes) {
    if (!bytes) return [0, 0]

    for (const node of this.session.roots) {
      // all async ticks happen once we find the root so safe
      const size = getUnpaddedSize(node, this.padding, null)

      if (bytes === size) return [flat.rightSpan(node.index) + 2, 0]
      if (bytes > size) {
        bytes -= size
        continue
      }

      const ite = flat.iterator(node.index)

      while ((ite.index & 1) !== 0) {
        const l = await getTreeNodeFromStorage(this.session.storage, ite.leftChild())

        if (l) {
          const size = getUnpaddedSize(l, this.padding, ite)

          if (size === bytes) return [ite.rightSpan() + 2, 0]
          if (size > bytes) continue
          bytes -= size
          ite.sibling()
        } else {
          ite.parent()
          return [ite.index, bytes]
        }
      }

      return [ite.index, bytes]
    }

    return null
  }

  async update() {
    // TODO: combine _seek and this, much simpler
    const res = await this._seek(this.bytes)
    if (!res) return null
    if ((res[0] & 1) === 0) return [res[0] / 2, res[1]]

    const span = flat.spans(res[0])
    this.start = span[0] / 2
    this.end = span[1] / 2 + 1

    return null
  }
}

class TreeProof {
  constructor(session, block, hash, seek, upgrade) {
    this.fork = session.fork
    this.signature = session.signature

    this.block = block
    this.hash = hash
    this.seek = seek
    this.upgrade = upgrade

    this.pending = {
      node: null,
      seek: null,
      upgrade: null,
      additionalUpgrade: null
    }
  }

  async settle() {
    const result = {
      fork: this.fork,
      block: null,
      hash: null,
      seek: null,
      upgrade: null,
      manifest: null
    }

    const [pNode, pSeek, pUpgrade, pAdditional] = await settleProof(this.pending)

    if (this.block) {
      if (pNode === null) throw INVALID_OPERATION('Invalid block request')
      result.block = {
        index: this.block.index,
        value: null, // populated upstream, alloc it here for simplicity
        nodes: pNode
      }
    } else if (this.hash) {
      if (pNode === null) throw INVALID_OPERATION('Invalid block request')
      result.hash = {
        index: this.hash.index,
        nodes: pNode
      }
    }

    if (this.seek && pSeek !== null) {
      result.seek = {
        bytes: this.seek.bytes,
        nodes: pSeek
      }
    }

    if (this.upgrade) {
      result.upgrade = {
        start: this.upgrade.start,
        length: this.upgrade.length,
        nodes: pUpgrade,
        additionalNodes: pAdditional || [],
        signature: this.signature
      }
    }

    return result
  }
}

class MerkleTree {
  static hash(s) {
    return unslab(crypto.tree(s.roots))
  }

  static signable(s, namespace) {
    return caps.treeSignable(namespace, MerkleTree.hash(s), s.length, s.fork)
  }

  static size(roots) {
    return totalSize(roots)
  }

  static span(roots) {
    return totalSpan(roots)
  }

  static getRoots(session, length) {
    return MerkleTree.getRootsFromStorage(session.storage, length)
  }

  static getRootsFromStorage(storage, length) {
    const indexes = flat.fullRoots(2 * length)
    const roots = new Array(indexes.length)
    const rx = storage.read()

    for (let i = 0; i < indexes.length; i++) {
      roots[i] = getTreeNodeOrError(rx, indexes[i])
    }

    rx.tryFlush()

    return Promise.all(roots)
  }

  static async upgradeable(session, length) {
    const indexes = flat.fullRoots(2 * length)
    const roots = new Array(indexes.length)
    const rx = session.storage.read()

    for (let i = 0; i < indexes.length; i++) {
      roots[i] = rx.getTreeNode(indexes[i])
    }

    rx.tryFlush()

    for (const node of await Promise.all(roots)) {
      if (node === null) return false
    }

    return true
  }

  static seek(session, bytes, padding) {
    return new ByteSeeker(session, bytes, padding)
  }

  static get(session, index) {
    return getTreeNodeFromStorage(session.storage, index)
  }

  static async truncate(session, length, batch, fork = batch.fork) {
    const head = length * 2
    const fullRoots = flat.fullRoots(head)

    for (let i = 0; i < fullRoots.length; i++) {
      const root = fullRoots[i]
      if (i < batch.roots.length && batch.roots[i].index === root) continue

      while (batch.roots.length > i) batch.roots.pop()
      batch.roots.push(unslabNode(await getTreeNodeFromStorageOrError(session.storage, root)))
    }

    while (batch.roots.length > fullRoots.length) {
      batch.roots.pop()
    }

    batch.fork = fork
    batch.length = length
    batch.ancestors = length
    batch.byteLength = totalSize(batch.roots)
    batch.upgraded = true

    return batch
  }

  static async reorg(session, proof, batch) {
    let unverified = null

    if (proof.block || proof.hash || proof.seek) {
      unverified = verifyTree(proof, batch.nodes)
    }

    if (!verifyUpgrade(proof, unverified, batch)) {
      throw INVALID_PROOF('Fork proof not verifiable')
    }

    for (const root of batch.roots) {
      const existing = await getTreeNodeFromStorage(session.storage, root.index)
      if (existing && b4a.equals(existing.hash, root.hash)) continue
      batch._updateDiffRoot(root)
      break
    }

    if (batch.diff !== null) {
      await batch._update(batch.nodes)
    } else {
      batch.want = null
      batch.ancestors = batch.length
    }

    return batch
  }

  static verifyFullyRemote(session, proof) {
    // TODO: impl this less hackishly
    const batch = new MerkleTreeBatch(session)

    batch.fork = proof.fork
    batch.roots = []
    batch.length = 0
    batch.ancestors = 0
    batch.byteLength = 0

    let unverified = verifyTree(proof, batch.nodes)

    if (proof.upgrade) {
      if (verifyUpgrade(proof, unverified, batch)) {
        unverified = null
      }
    }

    return batch
  }

  static async verify(session, proof) {
    const batch = new MerkleTreeBatch(session)

    let unverified = verifyTree(proof, batch.nodes)

    if (proof.upgrade) {
      if (verifyUpgrade(proof, unverified, batch)) {
        unverified = null
      }
    }

    if (unverified) {
      const verified = await getTreeNodeFromStorageOrError(session.storage, unverified.index)
      if (!b4a.equals(verified.hash, unverified.hash)) {
        throw INVALID_CHECKSUM('Invalid checksum at node ' + unverified.index)
      }
    }

    return batch
  }

  static proof(session, rx, { block, hash, seek, upgrade }) {
    return generateProof(session, rx, block, hash, seek, upgrade)
  }

  static maxMissingNodes(index, length) {
    const head = 2 * length
    const ite = flat.iterator(index)

    // See iterator.rightSpan()
    const iteRightSpan = ite.index + ite.factor / 2 - 1
    // If the index is not in the current tree, we do not know how many missing nodes there are...
    if (iteRightSpan >= head) return 0

    for (const r of flat.fullRoots(head)) {
      if (r === index) return 0
    }

    // need missing nodes not inclusive of index
    ite.parent()

    let cnt = 0
    while (!ite.contains(head)) {
      cnt++
      ite.parent()
    }

    return cnt
  }

  static async missingNodes(session, index, length) {
    const head = 2 * length
    const ite = flat.iterator(index)

    // See iterator.rightSpan()
    const iteRightSpan = ite.index + ite.factor / 2 - 1
    // If the index is not in the current tree, we do not know how many missing nodes there are...
    if (iteRightSpan >= head) return 0

    let cnt = 0
    // TODO: we could prop use a read batch here and do this in blocks of X for perf
    while (!ite.contains(head) && !(await hasTreeNode(session.storage, ite.index))) {
      cnt++
      if (cnt >= 1024) {
        throw ASSERTION('Bad arguments to missingNodes index=' + index + ' at length=' + length)
      }
      ite.parent()
    }

    return cnt
  }

  static byteOffset(session, index) {
    return getByteOffsetSession(session, index, null)
  }

  static byteRange(session, index) {
    const rx = session.storage.read()
    const offset = getByteOffsetSession(session, index, rx)
    const size = getNodeSize(index, rx)
    rx.tryFlush()
    return Promise.all([offset, size])
  }

  static upgradeLength(proof) {
    const extra = proof.additionalNodes

    if (extra.length) {
      const last = extra[extra.length - 1]
      const index = flat.rightSpan(last.index)
      return index / 2 + 1
    }

    return proof.start + proof.length
  }
}

module.exports = {
  MerkleTreeBatch,
  ReorgBatch,
  MerkleTree
}

async function getNodeSize(index, rx) {
  return (await getTreeNodeOrError(rx, index)).size
}

async function getByteOffsetSession(session, index, rx) {
  if (index === 2 * session.length) return session.byteLength

  const treeNodes =
    rx === null
      ? await getByteOffsetBatchFlush(session.roots, index, session.storage.read())
      : await getByteOffsetBatch(session.roots, index, rx)

  let offset = 0
  for (const node of treeNodes) offset += node.size

  return offset
}

async function getByteOffset(tree, index, rx) {
  if (index === 2 * tree.length) return tree.byteLength

  const treeNodes = await getByteOffsetBatch(tree.roots, index, rx)

  let offset = 0
  for (const node of treeNodes) offset += node.size

  return offset
}

function getByteOffsetBatchFlush(roots, index, rx) {
  const treeNodes = getByteOffsetBatch(roots, index, rx)
  rx.tryFlush()
  return treeNodes
}

function getByteOffsetBatch(roots, index, rx) {
  if ((index & 1) === 1) index = flat.leftSpan(index)

  let head = 0
  let cnt = 0

  const promises = []

  for (const node of roots) {
    // all async ticks happen once we find the root so safe
    head += 2 * (node.index - head + 1)

    if (index >= head) {
      promises.push(node.size)
      continue
    }

    const ite = flat.iterator(node.index)

    while (ite.index !== index) {
      if (++cnt >= 1024) throw ASSERTION('Bad getByteOffsetSession index=' + index)

      if (index < ite.index) {
        ite.leftChild()
      } else {
        promises.push(getTreeNodeOrError(rx, ite.leftChild()))
        ite.sibling()
      }
    }

    return Promise.all(promises)
  }

  throw ASSERTION('Failed to find offset')
}

function getByteRange(tree, index, rx) {
  const head = 2 * tree.length
  if (((index & 1) === 0 ? index : flat.rightSpan(index)) >= head) {
    throw BAD_ARGUMENT('Index is out of bounds')
  }

  const offset = getByteOffset(tree, index, rx)
  const size = getNodeSize(index, rx)

  return Promise.all([offset, size])
}

// All the methods needed for proof verification

function verifyTree({ block, hash, seek }, nodes) {
  const untrustedNode = block
    ? { index: 2 * block.index, value: block.value, nodes: block.nodes }
    : hash
      ? { index: hash.index, value: null, nodes: hash.nodes }
      : null

  if (untrustedNode === null && (!seek || !seek.nodes.length)) return null

  let root = null

  if (seek && seek.nodes.length) {
    const ite = flat.iterator(seek.nodes[0].index)
    const q = new NodeQueue(seek.nodes)

    root = q.shift(ite.index)
    nodes.push(root)

    while (q.length > 0) {
      const node = q.shift(ite.sibling())

      root = parentNode(ite.parent(), root, node)
      nodes.push(node)
      nodes.push(root)
    }
  }

  if (untrustedNode === null) return root

  const ite = flat.iterator(untrustedNode.index)
  const blockHash = untrustedNode.value && blockNode(ite.index, untrustedNode.value)

  const q = new NodeQueue(untrustedNode.nodes, root)

  root = blockHash || q.shift(ite.index)
  nodes.push(root)

  while (q.length > 0) {
    const node = q.shift(ite.sibling())

    root = parentNode(ite.parent(), root, node)
    nodes.push(node)
    nodes.push(root)
  }

  return root
}

function verifyUpgrade({ fork, upgrade }, blockRoot, batch) {
  const prologue = batch.prologue

  if (prologue) {
    const { start, length } = upgrade
    if (start < prologue.length && (start !== 0 || length < prologue.length)) {
      throw INVALID_PROOF('Upgrade does not satisfy prologue')
    }
  }

  const q = new NodeQueue(upgrade.nodes, blockRoot)

  let grow = batch.roots.length > 0
  let i = 0

  const to = 2 * (upgrade.start + upgrade.length)
  const ite = flat.iterator(0)

  for (; ite.fullRoot(to); ite.nextTree()) {
    if (i < batch.roots.length && batch.roots[i].index === ite.index) {
      i++
      continue
    }

    if (grow) {
      grow = false
      const root = ite.index
      if (i < batch.roots.length) {
        ite.seek(batch.roots[batch.roots.length - 1].index)
        while (ite.index !== root) {
          batch.appendRoot(q.shift(ite.sibling()), ite)
        }
        continue
      }
    }

    batch.appendRoot(q.shift(ite.index), ite)
  }

  if (prologue && batch.length === prologue.length) {
    if (!b4a.equals(prologue.hash, batch.hash())) {
      throw INVALID_PROOF('Invalid hash')
    }
  }

  const extra = upgrade.additionalNodes

  ite.seek(batch.roots[batch.roots.length - 1].index)
  i = 0

  while (i < extra.length && extra[i].index === ite.sibling()) {
    batch.appendRoot(extra[i++], ite)
  }

  while (i < extra.length) {
    const node = extra[i++]

    while (node.index !== ite.index) {
      if (ite.factor === 2) throw INVALID_OPERATION('Unexpected node: ' + node.index)
      ite.leftChild()
    }

    batch.appendRoot(node, ite)
    ite.sibling()
  }

  batch.signature = unslab(upgrade.signature)
  batch.fork = fork

  return q.extra === null
}

async function seekFromHead(session, head, bytes, padding) {
  const roots = flat.fullRoots(head)

  for (let i = 0; i < roots.length; i++) {
    const root = roots[i]
    const node = await getTreeNodeFromStorage(session.storage, root)
    const size = getUnpaddedSize(node, padding, null)

    if (bytes === size) return root
    if (bytes > size) {
      bytes -= size
      continue
    }

    return seekTrustedTree(session, root, bytes, padding)
  }

  return head
}

// trust that bytes are within the root tree and find the block at bytes

async function seekTrustedTree(session, root, bytes, padding) {
  if (!bytes) return root

  const ite = flat.iterator(root)
  let cnt = 0

  while ((ite.index & 1) !== 0) {
    if (++cnt >= 1024) throw ASSERTION('Bad seekTrusted bytes=' + bytes + ', paddding=' + padding)
    const l = await getTreeNodeFromStorage(session.storage, ite.leftChild())

    if (l) {
      const size = getUnpaddedSize(l, padding, ite)
      if (size === bytes) return ite.index
      if (size > bytes) continue
      bytes -= size
      ite.sibling()
    } else {
      ite.parent()
      return ite.index
    }
  }

  return ite.index
}

// try to find the block at bytes without trusting that is *is* within the root passed

async function seekUntrustedTree(session, root, bytes, padding) {
  const offset =
    (await getByteOffsetSession(session, root, null)) -
    (padding ? (padding * flat.leftSpan(root)) / 2 : 0)

  if (offset > bytes) throw INVALID_OPERATION('Invalid seek')
  if (offset === bytes) return root

  bytes -= offset

  const node = await getTreeNodeFromStorageOrError(session.storage, root)

  if (getUnpaddedSize(node, padding, null) <= bytes) throw INVALID_OPERATION('Invalid seek')

  return seekTrustedTree(session, root, bytes, padding)
}

// Below is proof production, ie, construct proofs to verify a request
// Note, that all these methods are sync as we can statically infer which nodes
// are needed for the remote to verify given they arguments they passed us

function seekProof(session, rx, seekRoot, root, p) {
  const ite = flat.iterator(seekRoot)
  let cnt = 0

  p.seek = []
  p.seek.push(getTreeNodeOrError(rx, ite.index))

  while (ite.index !== root) {
    if (++cnt >= 1024) throw ASSERTION('Bad seekProof seekRoot=' + seekRoot + ', root=' + root)
    ite.sibling()
    p.seek.push(getTreeNodeOrError(rx, ite.index))
    ite.parent()
  }
}

function blockAndSeekProof(session, rx, node, seek, seekRoot, root, p) {
  if (!node) return seekProof(session, rx, seekRoot, root, p)

  const ite = flat.iterator(node.index)
  let cnt = 0

  p.node = []
  if (!node.value) p.node.push(getTreeNodeOrError(rx, ite.index))

  while (ite.index !== root) {
    if (++cnt >= 1024) {
      throw ASSERTION('Bad blockAndSeekProof seekRoot=' + seekRoot + ', root=' + root)
    }
    ite.sibling()

    if (seek && ite.contains(seekRoot) && ite.index !== seekRoot) {
      seekProof(session, rx, seekRoot, ite.index, p)
    } else {
      p.node.push(getTreeNodeOrError(rx, ite.index))
    }

    ite.parent()
  }
}

function upgradeProof(session, rx, node, seek, from, to, subTree, p) {
  if (from === 0) p.upgrade = []

  for (const ite = flat.iterator(0); ite.fullRoot(to); ite.nextTree()) {
    // check if they already have the node
    if (ite.index + ite.factor / 2 < from) continue

    // connect existing tree
    if (p.upgrade === null && ite.contains(from - 2)) {
      p.upgrade = []

      const root = ite.index
      const target = from - 2
      let cnt = 0

      ite.seek(target)

      while (ite.index !== root) {
        if (++cnt >= 1024) throw ASSERTION('Bad upgradeProof target=' + target + ', root=' + root)

        ite.sibling()
        if (ite.index > target) {
          if (p.node === null && p.seek === null && ite.contains(subTree)) {
            blockAndSeekProof(session, rx, node, seek, subTree, ite.index, p)
          } else {
            p.upgrade.push(getTreeNodeOrError(rx, ite.index))
          }
        }
        ite.parent()
      }

      continue
    }

    if (p.upgrade === null) {
      p.upgrade = []
    }

    // if the subtree included is a child of this tree, include that one
    // instead of a dup node
    if (p.node === null && p.seek === null && ite.contains(subTree)) {
      blockAndSeekProof(session, rx, node, seek, subTree, ite.index, p)
      continue
    }

    // add root (can be optimised since the root might be in tree.roots)
    p.upgrade.push(getTreeNodeOrError(rx, ite.index))
  }
}

function additionalUpgradeProof(session, rx, from, to, p) {
  if (from === 0) p.additionalUpgrade = []

  for (const ite = flat.iterator(0); ite.fullRoot(to); ite.nextTree()) {
    // check if they already have the node
    if (ite.index + ite.factor / 2 < from) continue

    // connect existing tree
    if (p.additionalUpgrade === null && ite.contains(from - 2)) {
      p.additionalUpgrade = []

      const root = ite.index
      const target = from - 2
      let cnt = 0

      ite.seek(target)

      while (ite.index !== root) {
        if (++cnt >= 1024) {
          throw ASSERTION(
            'Bad arguments to additionalUpgradeProof root=' + root + ' target=' + target
          )
        }
        ite.sibling()
        if (ite.index > target) {
          p.additionalUpgrade.push(getTreeNodeOrError(rx, ite.index))
        }
        ite.parent()
      }

      continue
    }

    if (p.additionalUpgrade === null) {
      p.additionalUpgrade = []
    }

    // add root (can be optimised since the root is in tree.roots)
    p.additionalUpgrade.push(getTreeNodeOrError(rx, ite.index))
  }
}

function nodesToRoot(index, nodes, head) {
  const ite = flat.iterator(index)

  for (let i = 0; i < nodes; i++) {
    ite.parent()
    if (ite.contains(head)) throw INVALID_OPERATION('Nodes is out of bounds')
  }

  return ite.index
}

function totalSize(nodes) {
  let s = 0
  for (const node of nodes) s += node.size
  return s
}

function totalSpan(nodes) {
  let s = 0
  for (const node of nodes) s += 2 * (node.index - s + 1)
  return s
}

function blockNode(index, value) {
  return { index, size: value.byteLength, hash: crypto.data(value) }
}

function parentNode(index, a, b) {
  return { index, size: a.size + b.size, hash: crypto.parent(a, b) }
}

function log2(n) {
  let res = 1

  while (n > 2) {
    n /= 2
    res++
  }

  return res
}

function normalizeIndexed(block, hash) {
  if (block) {
    return {
      value: true,
      index: block.index * 2,
      nodes: block.nodes,
      lastIndex: block.index
    }
  }
  if (hash) {
    return {
      value: false,
      index: hash.index,
      nodes: hash.nodes,
      lastIndex: flat.rightSpan(hash.index) / 2
    }
  }
  return null
}

async function getTreeNodeOrError(rx, index) {
  const node = await rx.getTreeNode(index)
  if (node === null) {
    throw INVALID_OPERATION('Expected tree node ' + index + ' from storage, got (nil)')
  }
  return node
}

function getTreeNodeFromStorageOrError(storage, index) {
  const rx = storage.read()
  const p = getTreeNodeOrError(rx, index)
  rx.tryFlush()
  return p
}

function getTreeNodeFromStorage(storage, index) {
  const rx = storage.read()
  const node = rx.getTreeNode(index)
  rx.tryFlush()
  return node
}

function hasTreeNode(storage, index) {
  const rx = storage.read()
  const has = rx.hasTreeNode(index)
  rx.tryFlush()
  return has
}

async function settleProof(p) {
  const result = [
    p.node && Promise.all(p.node),
    p.seek && Promise.all(p.seek),
    p.upgrade && Promise.all(p.upgrade),
    p.additionalUpgrade && Promise.all(p.additionalUpgrade)
  ]

  try {
    return await Promise.all(result)
  } catch (err) {
    if (p.node) await Promise.allSettled(p.node)
    if (p.seek) await Promise.allSettled(p.seek)
    if (p.upgrade) await Promise.allSettled(p.upgrade)
    if (p.additionalUpgrade) await Promise.allSettled(p.additionalUpgrade)
    throw err
  }
}

// tree can be either the merkle tree or a merkle tree batch
async function generateProof(session, rx, block, hash, seek, upgrade) {
  // Important that this does not throw inbetween making the promise arrays
  // and finalise being called, otherwise there will be lingering promises in the background

  if (session.prologue && upgrade) {
    upgrade.start = upgrade.start < session.prologue.length ? 0 : upgrade.start
    upgrade.length =
      upgrade.start < session.prologue.length ? session.prologue.length : upgrade.length
  }

  const head = 2 * session.length
  const from = upgrade ? upgrade.start * 2 : 0
  const to = upgrade ? from + upgrade.length * 2 : head
  const node = normalizeIndexed(block, hash)

  // can't do anything as we have no data...
  if (head === 0) return new TreeProof(session, null, null, null, null)

  if (from >= to || to > head) {
    throw INVALID_OPERATION('Invalid upgrade')
  }
  if (seek && upgrade && node !== null && node.index >= from) {
    throw INVALID_OPERATION('Cannot both do a seek and block/hash request when upgrading')
  }

  let subTree = head

  const p = new TreeProof(session, block, hash, seek, upgrade)

  if (node !== null && (!upgrade || node.lastIndex < upgrade.start)) {
    subTree = nodesToRoot(node.index, node.nodes, to)
    const seekRoot = seek
      ? await seekUntrustedTree(session, subTree, seek.bytes, seek.padding)
      : head
    blockAndSeekProof(session, rx, node, seek, seekRoot, subTree, p.pending)
  } else if ((node || seek) && upgrade) {
    subTree = seek ? await seekFromHead(session, to, seek.bytes, seek.padding) : node.index
  }

  if (upgrade) {
    upgradeProof(session, rx, node, seek, from, to, subTree, p.pending)
    if (head > to) additionalUpgradeProof(session, rx, to, head, p.pending)
  }

  return p
}

function getUnpaddedSize(node, padding, ite) {
  return padding === 0
    ? node.size
    : node.size - padding * (ite ? ite.countLeaves() : flat.countLeaves(node.index))
}

function unslabNodes(nodes) {
  for (const node of nodes) unslabNode(node)
  return nodes
}

function unslabNode(node) {
  if (node === null) return node
  node.hash = unslab(node.hash)
  return node
}
const c = require('compact-encoding')
const { DEFAULT_NAMESPACE } = require('./caps')
const { INVALID_OPLOG_VERSION } = require('hypercore-errors')
const unslab = require('unslab')

const MANIFEST_PATCH = 0b00000001
const MANIFEST_PROLOGUE = 0b00000010
const MANIFEST_LINKED = 0b00000100
const MANIFEST_USER_DATA = 0b00001000

const hashes = {
  preencode(state, m) {
    state.end++ // small uint
  },
  encode(state, m) {
    if (m === 'blake2b') {
      c.uint.encode(state, 0)
      return
    }

    throw new Error('Unknown hash: ' + m)
  },
  decode(state) {
    const n = c.uint.decode(state)
    if (n === 0) return 'blake2b'
    throw new Error('Unknown hash id: ' + n)
  }
}

const signatures = {
  preencode(state, m) {
    state.end++ // small uint
  },
  encode(state, m) {
    if (m === 'ed25519') {
      c.uint.encode(state, 0)
      return
    }

    throw new Error('Unknown signature: ' + m)
  },
  decode(state) {
    const n = c.uint.decode(state)
    if (n === 0) return 'ed25519'
    throw new Error('Unknown signature id: ' + n)
  }
}

const signer = {
  preencode(state, m) {
    signatures.preencode(state, m.signature)
    c.fixed32.preencode(state, m.namespace)
    c.fixed32.preencode(state, m.publicKey)
  },
  encode(state, m) {
    signatures.encode(state, m.signature)
    c.fixed32.encode(state, m.namespace)
    c.fixed32.encode(state, m.publicKey)
  },
  decode(state) {
    return {
      signature: signatures.decode(state),
      namespace: c.fixed32.decode(state),
      publicKey: c.fixed32.decode(state)
    }
  }
}

const signerArray = c.array(signer)

const prologue = {
  preencode(state, p) {
    c.fixed32.preencode(state, p.hash)
    c.uint.preencode(state, p.length)
  },
  encode(state, p) {
    c.fixed32.encode(state, p.hash)
    c.uint.encode(state, p.length)
  },
  decode(state) {
    return {
      hash: c.fixed32.decode(state),
      length: c.uint.decode(state)
    }
  }
}

const manifestv0 = {
  preencode(state, m) {
    hashes.preencode(state, m.hash)
    state.end++ // type

    if (m.prologue && m.signers.length === 0) {
      c.fixed32.preencode(state, m.prologue.hash)
      return
    }

    if (m.quorum === 1 && m.signers.length === 1 && !m.allowPatch) {
      signer.preencode(state, m.signers[0])
    } else {
      state.end++ // flags
      c.uint.preencode(state, m.quorum)
      signerArray.preencode(state, m.signers)
    }
  },
  encode(state, m) {
    hashes.encode(state, m.hash)

    if (m.prologue && m.signers.length === 0) {
      c.uint.encode(state, 0)
      c.fixed32.encode(state, m.prologue.hash)
      return
    }

    if (m.quorum === 1 && m.signers.length === 1 && !m.allowPatch) {
      c.uint.encode(state, 1)
      signer.encode(state, m.signers[0])
    } else {
      c.uint.encode(state, 2)
      c.uint.encode(state, m.allowPatch ? 1 : 0)
      c.uint.encode(state, m.quorum)
      signerArray.encode(state, m.signers)
    }
  },
  decode(state) {
    const hash = hashes.decode(state)
    const type = c.uint.decode(state)

    if (type > 2) throw new Error('Unknown type: ' + type)

    if (type === 0) {
      return {
        version: 0,
        hash,
        allowPatch: false,
        quorum: 0,
        signers: [],
        prologue: {
          hash: c.fixed32.decode(state),
          length: 0
        },
        linked: null,
        userData: null
      }
    }

    if (type === 1) {
      return {
        version: 0,
        hash,
        allowPatch: false,
        quorum: 1,
        signers: [signer.decode(state)],
        prologue: null,
        linked: null,
        userData: null
      }
    }

    const flags = c.uint.decode(state)

    return {
      version: 0,
      hash,
      allowPatch: (flags & 1) !== 0,
      quorum: c.uint.decode(state),
      signers: signerArray.decode(state),
      prologue: null,
      linked: null,
      userData: null
    }
  }
}

const fixed32Array = c.array(c.fixed32)

const manifest = (exports.manifest = {
  preencode(state, m) {
    state.end++ // version

    if (m.version === 0) return manifestv0.preencode(state, m)

    state.end++ // flags
    hashes.preencode(state, m.hash)

    c.uint.preencode(state, m.quorum)
    signerArray.preencode(state, m.signers)

    if (m.prologue) prologue.preencode(state, m.prologue)
    if (m.linked) fixed32Array.preencode(state, m.linked)
    if (m.userData) c.buffer.preencode(state, m.userData)
  },
  encode(state, m) {
    c.uint.encode(state, m.version)

    if (m.version === 0) return manifestv0.encode(state, m)

    let flags = 0
    if (m.allowPatch) flags |= MANIFEST_PATCH
    if (m.prologue) flags |= MANIFEST_PROLOGUE
    if (m.linked) flags |= MANIFEST_LINKED
    if (m.userData) flags |= MANIFEST_USER_DATA

    c.uint.encode(state, flags)
    hashes.encode(state, m.hash)

    c.uint.encode(state, m.quorum)
    signerArray.encode(state, m.signers)

    if (m.prologue) prologue.encode(state, m.prologue)
    if (m.linked) fixed32Array.encode(state, m.linked)
    if (m.userData) c.buffer.encode(state, m.userData)
  },
  decode(state) {
    const version = c.uint.decode(state)

    if (version === 0) return manifestv0.decode(state)
    if (version > 2) throw new Error('Unknown version: ' + version)

    const flags = c.uint.decode(state)
    const hash = hashes.decode(state)
    const quorum = c.uint.decode(state)
    const signers = signerArray.decode(state)

    const hasPatch = (flags & MANIFEST_PATCH) !== 0
    const hasPrologue = (flags & MANIFEST_PROLOGUE) !== 0
    const hasLinked = (flags & MANIFEST_LINKED) !== 0
    const hasUserData = (flags & MANIFEST_USER_DATA) !== 0

    return {
      version,
      hash,
      allowPatch: hasPatch,
      quorum,
      signers,
      prologue: hasPrologue ? prologue.decode(state) : null,
      linked: hasLinked ? fixed32Array.decode(state) : null,
      userData: hasUserData ? c.buffer.decode(state) : null
    }
  }
})

const node = {
  preencode(state, n) {
    c.uint.preencode(state, n.index)
    c.uint.preencode(state, n.size)
    c.fixed32.preencode(state, n.hash)
  },
  encode(state, n) {
    c.uint.encode(state, n.index)
    c.uint.encode(state, n.size)
    c.fixed32.encode(state, n.hash)
  },
  decode(state) {
    return {
      index: c.uint.decode(state),
      size: c.uint.decode(state),
      hash: c.fixed32.decode(state)
    }
  }
}

const nodeArray = c.array(node)

const wire = (exports.wire = {})

wire.handshake = {
  preencode(state, m) {
    c.uint.preencode(state, 1)
    c.fixed32.preencode(state, m.capability)
  },
  encode(state, m) {
    c.uint.encode(state, m.seeks ? 1 : 0)
    c.fixed32.encode(state, m.capability)
  },
  decode(state) {
    const flags = c.uint.decode(state)
    return {
      seeks: (flags & 1) !== 0,
      capability: unslab(c.fixed32.decode(state))
    }
  }
}

const requestBlock = {
  preencode(state, b) {
    c.uint.preencode(state, b.index)
    c.uint.preencode(state, b.nodes)
  },
  encode(state, b) {
    c.uint.encode(state, b.index)
    c.uint.encode(state, b.nodes)
  },
  decode(state) {
    return {
      index: c.uint.decode(state),
      nodes: c.uint.decode(state)
    }
  }
}

const requestSeek = {
  preencode(state, s) {
    c.uint.preencode(state, s.bytes)
    c.uint.preencode(state, s.padding)
  },
  encode(state, s) {
    c.uint.encode(state, s.bytes)
    c.uint.encode(state, s.padding)
  },
  decode(state) {
    return {
      bytes: c.uint.decode(state),
      padding: c.uint.decode(state)
    }
  }
}

const requestUpgrade = {
  preencode(state, u) {
    c.uint.preencode(state, u.start)
    c.uint.preencode(state, u.length)
  },
  encode(state, u) {
    c.uint.encode(state, u.start)
    c.uint.encode(state, u.length)
  },
  decode(state) {
    return {
      start: c.uint.decode(state),
      length: c.uint.decode(state)
    }
  }
}

wire.request = {
  preencode(state, m) {
    state.end++ // flags
    c.uint.preencode(state, m.id)
    c.uint.preencode(state, m.fork)

    if (m.block) requestBlock.preencode(state, m.block)
    if (m.hash) requestBlock.preencode(state, m.hash)
    if (m.seek) requestSeek.preencode(state, m.seek)
    if (m.upgrade) requestUpgrade.preencode(state, m.upgrade)
    if (m.priority) c.uint.preencode(state, m.priority)
  },
  encode(state, m) {
    const flags =
      (m.block ? 1 : 0) |
      (m.hash ? 2 : 0) |
      (m.seek ? 4 : 0) |
      (m.upgrade ? 8 : 0) |
      (m.manifest ? 16 : 0) |
      (m.priority ? 32 : 0)

    c.uint.encode(state, flags)
    c.uint.encode(state, m.id)
    c.uint.encode(state, m.fork)

    if (m.block) requestBlock.encode(state, m.block)
    if (m.hash) requestBlock.encode(state, m.hash)
    if (m.seek) requestSeek.encode(state, m.seek)
    if (m.upgrade) requestUpgrade.encode(state, m.upgrade)
    if (m.priority) c.uint.encode(state, m.priority)
  },
  decode(state) {
    const flags = c.uint.decode(state)

    return {
      id: c.uint.decode(state),
      fork: c.uint.decode(state),
      block: flags & 1 ? requestBlock.decode(state) : null,
      hash: flags & 2 ? requestBlock.decode(state) : null,
      seek: flags & 4 ? requestSeek.decode(state) : null,
      upgrade: flags & 8 ? requestUpgrade.decode(state) : null,
      manifest: (flags & 16) !== 0,
      priority: flags & 32 ? c.uint.decode(state) : 0
    }
  }
}

wire.cancel = {
  preencode(state, m) {
    c.uint.preencode(state, m.request)
  },
  encode(state, m) {
    c.uint.encode(state, m.request)
  },
  decode(state, m) {
    return {
      request: c.uint.decode(state)
    }
  }
}

const dataUpgrade = {
  preencode(state, u) {
    c.uint.preencode(state, u.start)
    c.uint.preencode(state, u.length)
    nodeArray.preencode(state, u.nodes)
    nodeArray.preencode(state, u.additionalNodes)
    c.optionalBuffer.preencode(state, u.signature)
  },
  encode(state, u) {
    c.uint.encode(state, u.start)
    c.uint.encode(state, u.length)
    nodeArray.encode(state, u.nodes)
    nodeArray.encode(state, u.additionalNodes)
    c.optionalBuffer.encode(state, u.signature)
  },
  decode(state) {
    return {
      start: c.uint.decode(state),
      length: c.uint.decode(state),
      nodes: nodeArray.decode(state),
      additionalNodes: nodeArray.decode(state),
      signature: c.optionalBuffer.decode(state)
    }
  }
}

const dataSeek = {
  preencode(state, s) {
    c.uint.preencode(state, s.bytes)
    nodeArray.preencode(state, s.nodes)
  },
  encode(state, s) {
    c.uint.encode(state, s.bytes)
    nodeArray.encode(state, s.nodes)
  },
  decode(state) {
    return {
      bytes: c.uint.decode(state),
      nodes: nodeArray.decode(state)
    }
  }
}

const dataBlock = {
  preencode(state, b) {
    c.uint.preencode(state, b.index)
    c.buffer.preencode(state, b.value)
    nodeArray.preencode(state, b.nodes)
  },
  encode(state, b) {
    c.uint.encode(state, b.index)
    c.buffer.encode(state, b.value)
    nodeArray.encode(state, b.nodes)
  },
  decode(state) {
    return {
      index: c.uint.decode(state),
      value: c.buffer.decode(state),
      nodes: nodeArray.decode(state)
    }
  }
}

const dataHash = {
  preencode(state, b) {
    c.uint.preencode(state, b.index)
    nodeArray.preencode(state, b.nodes)
  },
  encode(state, b) {
    c.uint.encode(state, b.index)
    nodeArray.encode(state, b.nodes)
  },
  decode(state) {
    return {
      index: c.uint.decode(state),
      nodes: nodeArray.decode(state)
    }
  }
}

wire.data = {
  preencode(state, m) {
    state.end++ // flags
    c.uint.preencode(state, m.request)
    c.uint.preencode(state, m.fork)

    if (m.block) dataBlock.preencode(state, m.block)
    if (m.hash) dataHash.preencode(state, m.hash)
    if (m.seek) dataSeek.preencode(state, m.seek)
    if (m.upgrade) dataUpgrade.preencode(state, m.upgrade)
    if (m.manifest) manifest.preencode(state, m.manifest)
  },
  encode(state, m) {
    const flags =
      (m.block ? 1 : 0) |
      (m.hash ? 2 : 0) |
      (m.seek ? 4 : 0) |
      (m.upgrade ? 8 : 0) |
      (m.manifest ? 16 : 0)

    c.uint.encode(state, flags)
    c.uint.encode(state, m.request)
    c.uint.encode(state, m.fork)

    if (m.block) dataBlock.encode(state, m.block)
    if (m.hash) dataHash.encode(state, m.hash)
    if (m.seek) dataSeek.encode(state, m.seek)
    if (m.upgrade) dataUpgrade.encode(state, m.upgrade)
    if (m.manifest) manifest.encode(state, m.manifest)
  },
  decode(state) {
    const flags = c.uint.decode(state)

    return {
      request: c.uint.decode(state),
      fork: c.uint.decode(state),
      block: flags & 1 ? dataBlock.decode(state) : null,
      hash: flags & 2 ? dataHash.decode(state) : null,
      seek: flags & 4 ? dataSeek.decode(state) : null,
      upgrade: flags & 8 ? dataUpgrade.decode(state) : null,
      manifest: flags & 16 ? manifest.decode(state) : null
    }
  }
}

wire.noData = {
  preencode(state, m) {
    c.uint.preencode(state, m.request)
    c.uint.preencode(state, m.reason ? 1 : 0)
    if (m.reason) c.uint.preencode(state, m.reason)
  },
  encode(state, m) {
    c.uint.encode(state, m.request)
    c.uint.encode(state, m.reason ? 1 : 0)
    if (m.reason) c.uint.encode(state, m.reason)
  },
  decode(state, m) {
    const request = c.uint.decode(state)
    const flags = state.start < state.end ? c.uint.decode(state) : 0
    return {
      request,
      reason: flags & 1 ? c.uint.decode(state) : 0
    }
  }
}

// TODO: we should move to having these have a handle like in data, easier since they
// allocate state in practice
wire.want = {
  preencode(state, m) {
    c.uint.preencode(state, m.start)
    c.uint.preencode(state, m.length)

    const flags = m.any ? 1 : 0
    if (flags !== 0) c.uint.preencode(state, flags)
  },
  encode(state, m) {
    c.uint.encode(state, m.start)
    c.uint.encode(state, m.length)

    const flags = m.any ? 1 : 0
    if (flags !== 0) c.uint.encode(state, flags)
  },
  decode(state) {
    const start = c.uint.decode(state)
    const length = c.uint.decode(state)
    const flags = state.start < state.end ? c.uint.decode(state) : 0

    return {
      start,
      length,
      any: (flags & 1) !== 0
    }
  }
}

wire.unwant = {
  preencode(state, m) {
    c.uint.preencode(state, m.start)
    c.uint.preencode(state, m.length)

    const flags = m.any ? 1 : 0
    if (flags !== 0) c.uint.preencode(state, flags)
  },
  encode(state, m) {
    c.uint.encode(state, m.start)
    c.uint.encode(state, m.length)

    const flags = m.any ? 1 : 0
    if (flags !== 0) c.uint.encode(state, flags)
  },
  decode(state, m) {
    const start = c.uint.decode(state)
    const length = c.uint.decode(state)
    const flags = state.start < state.end ? c.uint.decode(state) : 0

    return {
      start,
      length,
      any: (flags & 1) !== 0
    }
  }
}

wire.range = {
  preencode(state, m) {
    state.end++ // flags
    c.uint.preencode(state, m.start)
    if (m.length !== 1) c.uint.preencode(state, m.length)
  },
  encode(state, m) {
    c.uint.encode(state, (m.drop ? 1 : 0) | (m.length === 1 ? 2 : 0))
    c.uint.encode(state, m.start)
    if (m.length !== 1) c.uint.encode(state, m.length)
  },
  decode(state) {
    const flags = c.uint.decode(state)

    return {
      drop: (flags & 1) !== 0,
      start: c.uint.decode(state),
      length: (flags & 2) !== 0 ? 1 : c.uint.decode(state)
    }
  }
}

wire.bitfield = {
  preencode(state, m) {
    c.uint.preencode(state, m.start)
    c.uint32array.preencode(state, m.bitfield)
  },
  encode(state, m) {
    c.uint.encode(state, m.start)
    c.uint32array.encode(state, m.bitfield)
  },
  decode(state, m) {
    return {
      start: c.uint.decode(state),
      bitfield: c.uint32array.decode(state)
    }
  }
}

wire.sync = {
  preencode(state, m) {
    state.end++ // flags
    c.uint.preencode(state, m.fork)
    c.uint.preencode(state, m.length)
    c.uint.preencode(state, m.remoteLength)
  },
  encode(state, m) {
    c.uint.encode(
      state,
      (m.canUpgrade ? 1 : 0) |
        (m.uploading ? 2 : 0) |
        (m.downloading ? 4 : 0) |
        (m.hasManifest ? 8 : 0) |
        (m.allowPush ? 16 : 0)
    )
    c.uint.encode(state, m.fork)
    c.uint.encode(state, m.length)
    c.uint.encode(state, m.remoteLength)
  },
  decode(state) {
    const flags = c.uint.decode(state)

    return {
      fork: c.uint.decode(state),
      length: c.uint.decode(state),
      remoteLength: c.uint.decode(state),
      canUpgrade: (flags & 1) !== 0,
      uploading: (flags & 2) !== 0,
      downloading: (flags & 4) !== 0,
      hasManifest: (flags & 8) !== 0,
      allowPush: (flags & 16) !== 0
    }
  }
}

wire.reorgHint = {
  preencode(state, m) {
    c.uint.preencode(state, m.from)
    c.uint.preencode(state, m.to)
    c.uint.preencode(state, m.ancestors)
  },
  encode(state, m) {
    c.uint.encode(state, m.from)
    c.uint.encode(state, m.to)
    c.uint.encode(state, m.ancestors)
  },
  decode(state) {
    return {
      from: c.uint.decode(state),
      to: c.uint.decode(state),
      ancestors: c.uint.decode(state)
    }
  }
}

wire.extension = {
  preencode(state, m) {
    c.string.preencode(state, m.name)
    c.raw.preencode(state, m.message)
  },
  encode(state, m) {
    c.string.encode(state, m.name)
    c.raw.encode(state, m.message)
  },
  decode(state) {
    return {
      name: c.string.decode(state),
      message: c.raw.decode(state)
    }
  }
}

const keyValue = {
  preencode(state, p) {
    c.string.preencode(state, p.key)
    c.buffer.preencode(state, p.value)
  },
  encode(state, p) {
    c.string.encode(state, p.key)
    c.buffer.encode(state, p.value)
  },
  decode(state) {
    return {
      key: c.string.decode(state),
      value: c.buffer.decode(state)
    }
  }
}

const treeUpgrade = {
  preencode(state, u) {
    c.uint.preencode(state, u.fork)
    c.uint.preencode(state, u.ancestors)
    c.uint.preencode(state, u.length)
    c.buffer.preencode(state, u.signature)
  },
  encode(state, u) {
    c.uint.encode(state, u.fork)
    c.uint.encode(state, u.ancestors)
    c.uint.encode(state, u.length)
    c.buffer.encode(state, u.signature)
  },
  decode(state) {
    return {
      fork: c.uint.decode(state),
      ancestors: c.uint.decode(state),
      length: c.uint.decode(state),
      signature: c.buffer.decode(state)
    }
  }
}

const bitfieldUpdate = {
  // TODO: can maybe be folded into a HAVE later on with the most recent spec
  preencode(state, b) {
    state.end++ // flags
    c.uint.preencode(state, b.start)
    c.uint.preencode(state, b.length)
  },
  encode(state, b) {
    state.buffer[state.start++] = b.drop ? 1 : 0
    c.uint.encode(state, b.start)
    c.uint.encode(state, b.length)
  },
  decode(state) {
    const flags = c.uint.decode(state)
    return {
      drop: (flags & 1) !== 0,
      start: c.uint.decode(state),
      length: c.uint.decode(state)
    }
  }
}

const oplog = (exports.oplog = {})

oplog.entry = {
  preencode(state, m) {
    state.end++ // flags
    if (m.userData) keyValue.preencode(state, m.userData)
    if (m.treeNodes) nodeArray.preencode(state, m.treeNodes)
    if (m.treeUpgrade) treeUpgrade.preencode(state, m.treeUpgrade)
    if (m.bitfield) bitfieldUpdate.preencode(state, m.bitfield)
  },
  encode(state, m) {
    const s = state.start++
    let flags = 0

    if (m.userData) {
      flags |= 1
      keyValue.encode(state, m.userData)
    }
    if (m.treeNodes) {
      flags |= 2
      nodeArray.encode(state, m.treeNodes)
    }
    if (m.treeUpgrade) {
      flags |= 4
      treeUpgrade.encode(state, m.treeUpgrade)
    }
    if (m.bitfield) {
      flags |= 8
      bitfieldUpdate.encode(state, m.bitfield)
    }

    state.buffer[s] = flags
  },
  decode(state) {
    const flags = c.uint.decode(state)
    return {
      userData: (flags & 1) !== 0 ? keyValue.decode(state) : null,
      treeNodes: (flags & 2) !== 0 ? nodeArray.decode(state) : null,
      treeUpgrade: (flags & 4) !== 0 ? treeUpgrade.decode(state) : null,
      bitfield: (flags & 8) !== 0 ? bitfieldUpdate.decode(state) : null
    }
  }
}

const keyPair = {
  preencode(state, kp) {
    c.buffer.preencode(state, kp.publicKey)
    c.buffer.preencode(state, kp.secretKey)
  },
  encode(state, kp) {
    c.buffer.encode(state, kp.publicKey)
    c.buffer.encode(state, kp.secretKey)
  },
  decode(state) {
    return {
      publicKey: c.buffer.decode(state),
      secretKey: c.buffer.decode(state)
    }
  }
}

const reorgHint = {
  preencode(state, r) {
    c.uint.preencode(state, r.from)
    c.uint.preencode(state, r.to)
    c.uint.preencode(state, r.ancestors)
  },
  encode(state, r) {
    c.uint.encode(state, r.from)
    c.uint.encode(state, r.to)
    c.uint.encode(state, r.ancestors)
  },
  decode(state) {
    return {
      from: c.uint.decode(state),
      to: c.uint.decode(state),
      ancestors: c.uint.decode(state)
    }
  }
}

const reorgHintArray = c.array(reorgHint)

const hints = {
  preencode(state, h) {
    reorgHintArray.preencode(state, h.reorgs)
    c.uint.preencode(state, h.contiguousLength)
  },
  encode(state, h) {
    reorgHintArray.encode(state, h.reorgs)
    c.uint.encode(state, h.contiguousLength)
  },
  decode(state) {
    return {
      reorgs: reorgHintArray.decode(state),
      contiguousLength: state.start < state.end ? c.uint.decode(state) : 0
    }
  }
}

const treeHeader = {
  preencode(state, t) {
    c.uint.preencode(state, t.fork)
    c.uint.preencode(state, t.length)
    c.buffer.preencode(state, t.rootHash)
    c.buffer.preencode(state, t.signature)
  },
  encode(state, t) {
    c.uint.encode(state, t.fork)
    c.uint.encode(state, t.length)
    c.buffer.encode(state, t.rootHash)
    c.buffer.encode(state, t.signature)
  },
  decode(state) {
    return {
      fork: c.uint.decode(state),
      length: c.uint.decode(state),
      rootHash: c.buffer.decode(state),
      signature: c.buffer.decode(state)
    }
  }
}

const types = {
  preencode(state, t) {
    c.string.preencode(state, t.tree)
    c.string.preencode(state, t.bitfield)
    c.string.preencode(state, t.signer)
  },
  encode(state, t) {
    c.string.encode(state, t.tree)
    c.string.encode(state, t.bitfield)
    c.string.encode(state, t.signer)
  },
  decode(state) {
    return {
      tree: c.string.decode(state),
      bitfield: c.string.decode(state),
      signer: c.string.decode(state)
    }
  }
}

const externalHeader = {
  preencode(state, m) {
    c.uint.preencode(state, m.start)
    c.uint.preencode(state, m.length)
  },
  encode(state, m) {
    c.uint.encode(state, m.start)
    c.uint.encode(state, m.length)
  },
  decode(state) {
    return {
      start: c.uint.decode(state),
      length: c.uint.decode(state)
    }
  }
}

const keyValueArray = c.array(keyValue)

oplog.header = {
  preencode(state, h) {
    state.end += 2 // version + flags
    if (h.external) {
      externalHeader.preencode(state, h.external)
      return
    }
    c.fixed32.preencode(state, h.key)
    if (h.manifest) manifest.preencode(state, h.manifest)
    if (h.keyPair) keyPair.preencode(state, h.keyPair)
    keyValueArray.preencode(state, h.userData)
    treeHeader.preencode(state, h.tree)
    hints.preencode(state, h.hints)
  },
  encode(state, h) {
    c.uint.encode(state, 1)
    if (h.external) {
      c.uint.encode(state, 1) // ONLY set the first big for clarity
      externalHeader.encode(state, h.external)
      return
    }
    c.uint.encode(state, (h.manifest ? 2 : 0) | (h.keyPair ? 4 : 0))
    c.fixed32.encode(state, h.key)
    if (h.manifest) manifest.encode(state, h.manifest)
    if (h.keyPair) keyPair.encode(state, h.keyPair)
    keyValueArray.encode(state, h.userData)
    treeHeader.encode(state, h.tree)
    hints.encode(state, h.hints)
  },
  decode(state) {
    const version = c.uint.decode(state)

    if (version > 1) {
      throw INVALID_OPLOG_VERSION('Invalid header version. Expected <= 1, got ' + version)
    }

    if (version === 0) {
      const old = {
        types: types.decode(state),
        userData: keyValueArray.decode(state),
        tree: treeHeader.decode(state),
        signer: keyPair.decode(state),
        hints: hints.decode(state)
      }

      return {
        external: null,
        key: old.signer.publicKey,
        manifest: {
          version: 0,
          hash: old.types.tree,
          allowPatch: false,
          quorum: 1,
          signers: [
            {
              signature: old.types.signer,
              namespace: DEFAULT_NAMESPACE,
              publicKey: old.signer.publicKey
            }
          ],
          prologue: null,
          linked: null,
          userData: null
        },
        keyPair: old.signer.secretKey ? old.signer : null,
        userData: old.userData,
        tree: old.tree,
        hints: old.hints
      }
    }

    const flags = c.uint.decode(state)

    if (flags & 1) {
      return {
        external: externalHeader.decode(state),
        key: null,
        manifest: null,
        keyPair: null,
        userData: null,
        tree: null,
        hints: null
      }
    }

    return {
      external: null,
      key: c.fixed32.decode(state),
      manifest: (flags & 2) !== 0 ? manifest.decode(state) : null,
      keyPair: (flags & 4) !== 0 ? keyPair.decode(state) : null,
      userData: keyValueArray.decode(state),
      tree: treeHeader.decode(state),
      hints: hints.decode(state)
    }
  }
}

const uintArray = c.array(c.uint)

const multisigInput = {
  preencode(state, inp) {
    c.uint.preencode(state, inp.signer)
    c.fixed64.preencode(state, inp.signature)
    c.uint.preencode(state, inp.patch)
  },
  encode(state, inp) {
    c.uint.encode(state, inp.signer)
    c.fixed64.encode(state, inp.signature)
    c.uint.encode(state, inp.patch)
  },
  decode(state) {
    return {
      signer: c.uint.decode(state),
      signature: c.fixed64.decode(state),
      patch: c.uint.decode(state)
    }
  }
}

const patchEncodingv0 = {
  preencode(state, n) {
    c.uint.preencode(state, n.start)
    c.uint.preencode(state, n.length)
    uintArray.preencode(state, n.nodes)
  },
  encode(state, n) {
    c.uint.encode(state, n.start)
    c.uint.encode(state, n.length)
    uintArray.encode(state, n.nodes)
  },
  decode(state) {
    return {
      start: c.uint.decode(state),
      length: c.uint.decode(state),
      nodes: uintArray.decode(state)
    }
  }
}

const multisigInputv0 = {
  preencode(state, n) {
    state.end++
    c.uint.preencode(state, n.signer)
    c.fixed64.preencode(state, n.signature)
    if (n.patch) patchEncodingv0.preencode(state, n.patch)
  },
  encode(state, n) {
    c.uint.encode(state, n.patch ? 1 : 0)
    c.uint.encode(state, n.signer)
    c.fixed64.encode(state, n.signature)
    if (n.patch) patchEncodingv0.encode(state, n.patch)
  },
  decode(state) {
    const flags = c.uint.decode(state)
    return {
      signer: c.uint.decode(state),
      signature: c.fixed64.decode(state),
      patch: flags & 1 ? patchEncodingv0.decode(state) : null
    }
  }
}

const multisigInputArrayv0 = c.array(multisigInputv0)
const multisigInputArray = c.array(multisigInput)

const compactNode = {
  preencode(state, n) {
    c.uint.preencode(state, n.index)
    c.uint.preencode(state, n.size)
    c.fixed32.preencode(state, n.hash)
  },
  encode(state, n) {
    c.uint.encode(state, n.index)
    c.uint.encode(state, n.size)
    c.fixed32.encode(state, n.hash)
  },
  decode(state) {
    return {
      index: c.uint.decode(state),
      size: c.uint.decode(state),
      hash: c.fixed32.decode(state)
    }
  }
}

const compactNodeArray = c.array(compactNode)

exports.multiSignaturev0 = {
  preencode(state, s) {
    multisigInputArrayv0.preencode(state, s.proofs)
    compactNodeArray.preencode(state, s.patch)
  },
  encode(state, s) {
    multisigInputArrayv0.encode(state, s.proofs)
    compactNodeArray.encode(state, s.patch)
  },
  decode(state) {
    return {
      proofs: multisigInputArrayv0.decode(state),
      patch: compactNodeArray.decode(state)
    }
  }
}

exports.multiSignature = {
  preencode(state, s) {
    multisigInputArray.preencode(state, s.proofs)
    compactNodeArray.preencode(state, s.patch)
  },
  encode(state, s) {
    multisigInputArray.encode(state, s.proofs)
    compactNodeArray.encode(state, s.patch)
  },
  decode(state) {
    return {
      proofs: multisigInputArray.decode(state),
      patch: compactNodeArray.decode(state)
    }
  }
}
const c = require('compact-encoding')
const b4a = require('b4a')
const flat = require('flat-tree')
const { MerkleTree } = require('./merkle-tree')
const { multiSignature, multiSignaturev0 } = require('./messages')

module.exports = {
  assemblev0,
  assemble,
  inflatev0,
  inflate,
  partialSignature,
  signableLength
}

function inflatev0(data) {
  return c.decode(multiSignaturev0, data)
}

function inflate(data) {
  return c.decode(multiSignature, data)
}

async function partialSignature(
  core,
  signer,
  from,
  to = core.state.length,
  signature = core.state.signature
) {
  if (from > core.state.length) return null
  const nodes = to <= from ? null : await upgradeNodes(core, from, to)

  if (signature.byteLength !== 64) {
    signature = c.decode(multiSignature, signature).proofs[0].signature
  }

  return {
    signer,
    signature,
    patch: nodes ? to - from : 0,
    nodes
  }
}

async function upgradeNodes(core, from, to) {
  const rx = core.state.storage.read()
  let p = null
  try {
    p = await MerkleTree.proof(core.state, rx, { upgrade: { start: from, length: to - from } })
  } catch (err) {
    rx.destroy()
    throw err
  }
  rx.tryFlush()
  return (await p.settle()).upgrade.nodes
}

function signableLength(lengths, quorum) {
  if (quorum <= 0) quorum = 1
  if (quorum > lengths.length) return 0

  return lengths.sort(cmp)[quorum - 1]
}

function cmp(a, b) {
  return b - a
}

function assemblev0(inputs) {
  const proofs = []
  const patch = []

  for (const u of inputs) {
    proofs.push(compressProof(u, patch))
  }

  return c.encode(multiSignaturev0, { proofs, patch })
}

function assemble(inputs) {
  const proofs = []
  const patch = []
  const seen = new Set()

  for (const u of inputs) {
    if (u.nodes) {
      for (const node of u.nodes) {
        if (seen.has(node.index)) continue
        seen.add(node.index)
        patch.push(node)
      }
    }

    proofs.push({
      signer: u.signer,
      signature: u.signature,
      patch: u.patch
    })
  }

  return c.encode(multiSignature, { proofs, patch })
}

function compareNode(a, b) {
  if (a.index !== b.index) return false
  if (a.size !== b.size) return false
  return b4a.equals(a.hash, b.hash)
}

function compressProof(proof, nodes) {
  return {
    signer: proof.signer,
    signature: proof.signature,
    patch: proof.patch ? compressUpgrade(proof, nodes) : null
  }
}

function compressUpgrade(p, nodes) {
  const u = {
    start: flat.rightSpan(p.nodes[p.nodes.length - 1].index) / 2 + 1,
    length: p.patch,
    nodes: []
  }

  for (const node of p.nodes) {
    let present = false
    for (let i = 0; i < nodes.length; i++) {
      if (!compareNode(nodes[i], node)) continue

      u.nodes.push(i)
      present = true
      break
    }

    if (present) continue
    u.nodes.push(nodes.push(node) - 1)
  }

  return u
}
module.exports = class Mutex {
  constructor() {
    this.locked = false
    this.destroyed = false

    this._destroying = null
    this._destroyError = null
    this._queue = []
    this._enqueue = (resolve, reject) => this._queue.push([resolve, reject])
  }

  idle() {
    return this._queue.length === 0 && this.locked === false
  }

  lock() {
    if (this.destroyed) {
      return Promise.reject(this._destroyError || new Error('Mutex has been destroyed'))
    }
    if (this.locked) return new Promise(this._enqueue)
    this.locked = true
    return Promise.resolve()
  }

  unlock() {
    if (!this._queue.length) {
      this.locked = false
      return
    }
    this._queue.shift()[0]()
  }

  destroy(err) {
    if (!this._destroying) {
      this._destroying = this.locked ? this.lock().catch(() => {}) : Promise.resolve()
    }

    this.destroyed = true
    if (err) this._destroyError = err

    if (err) {
      while (this._queue.length) this._queue.shift()[1](err)
    }

    return this._destroying
  }
}
const FIFO = require('fast-fifo')

module.exports = class ReceiverQueue {
  constructor() {
    this.queue = new FIFO()
    this.priority = []
    this.requests = new Map()
    this.length = 0
  }

  push(req) {
    // TODO: use a heap at some point if we wanna support multiple prios
    if (req.priority > 0) this.priority.push(req)
    else this.queue.push(req)

    this.requests.set(req.id, req)
    this.length++
  }

  shift() {
    while (this.priority.length > 0) {
      const msg = this.priority.pop()
      const req = this._processRequest(msg)
      if (req !== null) return req
    }

    while (this.queue.length > 0) {
      const msg = this.queue.shift()
      const req = this._processRequest(msg)
      if (req !== null) return req
    }

    return null
  }

  _processRequest(req) {
    if (req.block || req.hash || req.seek || req.upgrade || req.manifest) {
      this.requests.delete(req.id)
      this.length--
      return req
    }

    return null
  }

  clear() {
    this.queue.clear()
    this.priority = []
    this.length = 0
    this.requests.clear()
  }

  delete(id) {
    const req = this.requests.get(id)
    if (!req) return

    req.block = null
    req.hash = null
    req.seek = null
    req.upgrade = null
    req.manifest = false

    this.requests.delete(id)
    this.length--

    if (this.length === 0) {
      this.queue.clear()
      this.priority = []
    }
  }
}
const BigSparseArray = require('big-sparse-array')
const quickbit = require('./compat').quickbit

const BITS_PER_PAGE = 32768
const BYTES_PER_PAGE = BITS_PER_PAGE / 8
const WORDS_PER_PAGE = BYTES_PER_PAGE / 4
const BITS_PER_SEGMENT = 2097152
const BYTES_PER_SEGMENT = BITS_PER_SEGMENT / 8
const PAGES_PER_SEGMENT = BITS_PER_SEGMENT / BITS_PER_PAGE

class RemoteBitfieldPage {
  constructor(index, bitfield, segment) {
    this.index = index
    this.offset = index * BYTES_PER_PAGE - segment.offset
    this.bitfield = bitfield
    this.segment = segment

    segment.add(this)
  }

  get tree() {
    return this.segment.tree
  }

  get(index) {
    return quickbit.get(this.bitfield, index)
  }

  set(index, val) {
    if (quickbit.set(this.bitfield, index, val)) {
      this.tree.update(this.offset * 8 + index)
    }
  }

  setRange(start, end, val) {
    quickbit.fill(this.bitfield, val, start, end)

    let i = Math.floor(start / 128)
    const n = i + Math.ceil((end - start) / 128)

    while (i <= n) this.tree.update(this.offset * 8 + i++ * 128)
  }

  findFirst(val, position) {
    return quickbit.findFirst(this.bitfield, val, position)
  }

  findLast(val, position) {
    return quickbit.findLast(this.bitfield, val, position)
  }

  insert(start, bitfield) {
    this.bitfield.set(bitfield, start / 32)
    this.segment.refresh()
  }

  clear(start, bitfield) {
    quickbit.clear(this.bitfield, { field: bitfield, offset: start })
  }
}

class RemoteBitfieldSegment {
  constructor(index) {
    this.index = index
    this.offset = index * BYTES_PER_SEGMENT
    this.tree = quickbit.Index.from([], BYTES_PER_SEGMENT)
    this.pages = new Array(PAGES_PER_SEGMENT)
    this.pagesLength = 0
  }

  get chunks() {
    return this.tree.chunks
  }

  refresh() {
    this.tree = quickbit.Index.from(this.tree.chunks, BYTES_PER_SEGMENT)
  }

  add(page) {
    const pageIndex = page.index - this.index * PAGES_PER_SEGMENT
    if (pageIndex >= this.pagesLength) this.pagesLength = pageIndex + 1

    this.pages[pageIndex] = page

    const chunk = { field: page.bitfield, offset: page.offset }

    this.chunks.push(chunk)

    for (let i = this.chunks.length - 2; i >= 0; i--) {
      const prev = this.chunks[i]
      if (prev.offset <= chunk.offset) break
      this.chunks[i] = chunk
      this.chunks[i + 1] = prev
    }
  }

  findFirst(val, position) {
    position = this.tree.skipFirst(!val, position)

    let j = position & (BITS_PER_PAGE - 1)
    let i = (position - j) / BITS_PER_PAGE

    if (i >= PAGES_PER_SEGMENT) return -1

    while (i < this.pagesLength) {
      const p = this.pages[i]

      let index = -1

      if (p) index = p.findFirst(val, j)
      else if (!val) index = j

      if (index !== -1) return i * BITS_PER_PAGE + index

      j = 0
      i++
    }

    return val || this.pagesLength === PAGES_PER_SEGMENT ? -1 : this.pagesLength * BITS_PER_PAGE
  }

  findLast(val, position) {
    position = this.tree.skipLast(!val, position)

    let j = position & (BITS_PER_PAGE - 1)
    let i = (position - j) / BITS_PER_PAGE

    if (i >= PAGES_PER_SEGMENT) return -1

    while (i >= 0) {
      const p = this.pages[i]

      let index = -1

      if (p) index = p.findLast(val, j)
      else if (!val) index = j

      if (index !== -1) return i * BITS_PER_PAGE + index

      j = BITS_PER_PAGE - 1
      i--
    }

    return -1
  }
}

module.exports = class RemoteBitfield {
  static BITS_PER_PAGE = BITS_PER_PAGE
  static BITS_PER_SEGMENT = BITS_PER_SEGMENT

  constructor() {
    this._pages = new BigSparseArray()
    this._segments = new BigSparseArray()
    this._maxSegments = 0
  }

  getBitfield(index) {
    const j = index & (BITS_PER_PAGE - 1)
    const i = (index - j) / BITS_PER_PAGE

    const p = this._pages.get(i)
    return p || null
  }

  get(index) {
    const j = index & (BITS_PER_PAGE - 1)
    const i = (index - j) / BITS_PER_PAGE

    const p = this._pages.get(i)

    return p ? p.get(j) : false
  }

  set(index, val) {
    const j = index & (BITS_PER_PAGE - 1)
    const i = (index - j) / BITS_PER_PAGE

    let p = this._pages.get(i)

    if (!p && val) {
      const k = Math.floor(i / PAGES_PER_SEGMENT)
      const s = this._segments.get(k) || this._segments.set(k, new RemoteBitfieldSegment(k))
      if (this._maxSegments <= k) this._maxSegments = k + 1

      p = this._pages.set(i, new RemoteBitfieldPage(i, new Uint32Array(WORDS_PER_PAGE), s))
    }

    if (p) p.set(j, val)
  }

  setRange(start, end, val) {
    let j = start & (BITS_PER_PAGE - 1)
    let i = (start - j) / BITS_PER_PAGE

    while (start < end) {
      let p = this._pages.get(i)

      if (!p && val) {
        const k = Math.floor(i / PAGES_PER_SEGMENT)
        const s = this._segments.get(k) || this._segments.set(k, new RemoteBitfieldSegment(k))
        if (this._maxSegments <= k) this._maxSegments = k + 1

        p = this._pages.set(i, new RemoteBitfieldPage(i, new Uint32Array(WORDS_PER_PAGE), s))
      }

      const offset = i * BITS_PER_PAGE
      const last = Math.min(end - offset, BITS_PER_PAGE)
      const range = last - j

      if (p) p.setRange(j, last, val)

      j = 0
      i++
      start += range
    }
  }

  findFirst(val, position) {
    let j = position & (BITS_PER_SEGMENT - 1)
    let i = (position - j) / BITS_PER_SEGMENT

    while (i < this._maxSegments) {
      const s = this._segments.get(i)

      let index = -1

      if (s) index = s.findFirst(val, j)
      else if (!val) index = j

      if (index !== -1) return i * BITS_PER_SEGMENT + index

      j = 0
      i++
    }

    // For the val === false case, we always return at least
    // the 'position', also if nothing was found
    return val ? -1 : Math.max(position, this._maxSegments * BITS_PER_SEGMENT)
  }

  firstSet(position) {
    return this.findFirst(true, position)
  }

  firstUnset(position) {
    return this.findFirst(false, position)
  }

  findLast(val, position) {
    let j = position & (BITS_PER_SEGMENT - 1)
    let i = (position - j) / BITS_PER_SEGMENT

    while (i >= 0) {
      const s = this._segments.get(i)

      let index = -1

      if (s) index = s.findLast(val, j)
      else if (!val) index = j

      if (index !== -1) return i * BITS_PER_SEGMENT + index

      j = BITS_PER_SEGMENT - 1
      i--
    }

    return -1
  }

  lastSet(position) {
    return this.findLast(true, position)
  }

  lastUnset(position) {
    return this.findLast(false, position)
  }

  insert(start, bitfield) {
    if (start % 32 !== 0) return false

    let length = bitfield.byteLength * 8

    let j = start & (BITS_PER_PAGE - 1)
    let i = (start - j) / BITS_PER_PAGE

    while (length > 0) {
      let p = this._pages.get(i)

      if (!p) {
        const k = Math.floor(i / PAGES_PER_SEGMENT)
        const s = this._segments.get(k) || this._segments.set(k, new RemoteBitfieldSegment(k))
        if (this._maxSegments <= k) this._maxSegments = k + 1

        p = this._pages.set(i, new RemoteBitfieldPage(i, new Uint32Array(WORDS_PER_PAGE), s))
      }

      const end = Math.min(j + length, BITS_PER_PAGE)
      const range = end - j

      p.insert(j, bitfield.subarray(0, range / 32))

      bitfield = bitfield.subarray(range / 32)

      j = 0
      i++
      length -= range
    }

    return true
  }

  clear(start, bitfield) {
    if (start % 32 !== 0) return false

    let length = bitfield.byteLength * 8

    let j = start & (BITS_PER_PAGE - 1)
    let i = (start - j) / BITS_PER_PAGE

    while (length > 0) {
      let p = this._pages.get(i)

      if (!p) {
        const k = Math.floor(i / PAGES_PER_SEGMENT)
        const s = this._segments.get(k) || this._segments.set(k, new RemoteBitfieldSegment(k))
        if (this._maxSegments <= k) this._maxSegments = k + 1

        p = this._pages.set(i, new RemoteBitfieldPage(i, new Uint32Array(WORDS_PER_PAGE), s))
      }

      const end = Math.min(j + length, BITS_PER_PAGE)
      const range = end - j

      p.clear(j, bitfield.subarray(0, range / 32))

      bitfield = bitfield.subarray(range / 32)

      j = 0
      i++
      length -= range
    }

    return true
  }
}
/* DEV DOCS
  Every hypercore has one Replicator object managing its connections to other peers.
  There is one Peer object per peer connected to the Hypercore.
  Hypercores do not know about other hypercores, so when a peer is connected to multiple cores, there exists one Peer object per core.

  Hypercore indicates block should be downloaded through methods like Replicator.addRange or Replicator.addBlock
  Hypercore calls Replicator.updateActivity every time a hypercore session opens/closes
  Replicator.updateActivity ensures the Hypercore is downloading blocks as expected
  Replicator keeps track of:
    - Which blocks need to be downloaded (Replicator._blocks)
    - Which blocks currently have inflight requests (Replicator._inflight)

  Blocks are requested from remote peers by Peer objects. The flow is:
    - The replicator's updatePeer method gets called
    - The replicator detects whether the Peer can accept more requests (for example by checking if it's maxed out on inflight blocks)
    - The replicator then tells the Peer what to request (e.g. Peer_requestRange or Peer._requestBlock methods)

  The Peer object is responsible for tracking
    - Which blocks does the Peer have available (tracked in remoteBitfield)
    - Which blocks are you actively looking for from this peer (tracked in missingBlocks)
    - How many blocks are currently inflight (tracked in inflight)
  The Peer uses this information to decide which blocks to request from the peer in response to _requestRange requests and the like.
*/

const b4a = require('b4a')
const safetyCatch = require('safety-catch')
const RandomIterator = require('random-array-iterator')
const flatTree = require('flat-tree')
const Mutex = require('./mutex')
const ReceiverQueue = require('./receiver-queue')
const HotswapQueue = require('./hotswap-queue')
const RemoteBitfield = require('./remote-bitfield')
const { MerkleTree } = require('./merkle-tree')
const w = require('./wants')
const { LocalWants, RemoteWants } = w
const {
  REQUEST_CANCELLED,
  REQUEST_TIMEOUT,
  INVALID_CAPABILITY,
  SNAPSHOT_NOT_AVAILABLE,
  ASSERTION
} = require('hypercore-errors')
const m = require('./messages')
const caps = require('./caps')

const DEFAULT_MAX_INFLIGHT = [16, 512]
const SCALE_LATENCY = 50
const NOT_DOWNLOADING_SLACK = (20000 + Math.random() * 20000) | 0
const MAX_PEERS_UPGRADE = 3
const LAST_BLOCKS = 256

const MAX_RANGES = 64

const NOT_AVAILABLE = 1
const INVALID_REQUEST = 2
const MAX_INVALID_REQUESTS = 64
const MAX_BACKOFFS = MAX_INVALID_REQUESTS / 2

const PRIORITY = {
  NORMAL: 0,
  HIGH: 1,
  VERY_HIGH: 2,
  CANCELLED: 255 // reserved to mark cancellation
}

class Attachable {
  constructor(replicator) {
    this.replicator = replicator
    this.resolved = false
    this.processing = false
    this.refs = []
    this.wants = null
  }

  addWant(w) {
    if (this.wants === null) this.wants = new Set()
    this.wants.add(w)
  }

  removeWant(w) {
    if (this.wants === null) return
    this.wants.delete(w)
  }

  attach(session) {
    const r = {
      context: this,
      session,
      sindex: 0,
      rindex: 0,
      snapshot: true,
      resolve: null,
      reject: null,
      promise: null,
      timeout: null
    }

    r.sindex = session.push(r) - 1
    r.rindex = this.refs.push(r) - 1
    r.promise = new Promise((resolve, reject) => {
      r.resolve = resolve
      r.reject = reject
    })

    return r
  }

  detach(r, err = null) {
    if (r.context !== this) return false

    this._detach(r)
    this._cancel(r, err)
    this.gc()

    return true
  }

  _detach(r) {
    const rh = this.refs.pop()
    const sh = r.session.pop()

    if (r.rindex < this.refs.length) this.refs[(rh.rindex = r.rindex)] = rh
    if (r.sindex < r.session.length) r.session[(sh.sindex = r.sindex)] = sh

    destroyRequestTimeout(r)
    r.context = null

    return r
  }

  gc() {
    if (this.refs.length > 0 || this.processing) return
    this._unref()

    if (this.wants === null) return

    const wants = this.wants
    this.wants = null

    let update = null

    for (const w of wants) {
      if (w.any) {
        const removed = w.wants.removeAnyRange(w.start, w.length, this)
        if (!removed) continue
        if (update === null) update = new Set()
        update.add(w.wants.peer)
        // TODO: should also use a free list for simplicity
        w.wants.peer.wireUnwant.send(removed)
        continue
      }

      if (w.wants.removeBatch(w.start, this)) {
        if (update === null) update = new Set()
        update.add(w.wants.peer)
      }
    }

    if (update === null) return
    for (const peer of update) {
      this.replicator.updatePeer(peer)
    }
  }

  processed() {
    this.processing = false
    this.gc()
  }

  _cancel(r, err) {
    r.reject(err || REQUEST_CANCELLED())
  }

  _unref() {
    // overwrite me
  }

  resolve(val) {
    this.resolved = true
    while (this.refs.length > 0) {
      this._detach(this.refs[this.refs.length - 1]).resolve(val)
    }
    this.gc()
  }

  reject(err) {
    this.resolved = true
    while (this.refs.length > 0) {
      this._detach(this.refs[this.refs.length - 1]).reject(err)
    }
    this.gc()
  }

  setTimeout(r, ms) {
    destroyRequestTimeout(r)
    r.timeout = setTimeout(onrequesttimeout, ms, r)
  }
}

class BlockRequest extends Attachable {
  constructor(replicator, tracker, index, priority, force) {
    super(replicator)

    this.index = index
    this.priority = priority
    this.inflight = []
    this.queued = false
    this.hotswap = null
    this.tracker = tracker
    this.force = force
  }

  _unref() {
    if (this.resolved) return
    this.queued = false

    for (const req of this.inflight) {
      req.peer._cancelRequest(req)
    }

    this.tracker.remove(this.index)
    removeHotswap(this)
  }
}

class RangeBatchRequest extends Attachable {
  constructor(replicator, batch) {
    super(replicator)

    this.batch = batch
    this.index = 0
  }
}

class RangeRequest extends Attachable {
  constructor(replicator, ranges, start, end, linear, blocks) {
    super(replicator)

    this.start = start
    this.end = end
    this.linear = linear
    this.blocks = blocks
    this.ranges = ranges
    this.batches = []

    ranges.push(this)

    if (this.end === -1) {
      this.replicator._alwaysLatestBlock++
    }

    // As passed by the user, immut
    this.userStart = start
    this.userEnd = end
  }

  addBatch(batch) {
    for (let i = 0; i < this.batches.length; i++) {
      const b = this.batches[i]
      if (b.batch === batch) return false
    }
    const b = new RangeBatchRequest(this.replicator, batch)
    b.index = this.batches.length
    this.batches.push(b)
    return true
  }

  removeBatch(batch) {
    const head = this.batches.pop()

    if (head !== batch) {
      head.index = batch.index
      this.batches[head.index] = head
    }

    batch.gc()
  }

  _unref() {
    const rangeIndex = this.ranges.indexOf(this)
    if (rangeIndex === -1) return

    const h = this.ranges.pop()
    if (h !== this) {
      this.ranges[rangeIndex] = h
    }

    if (this.end === -1) {
      this.replicator._alwaysLatestBlock--
    }

    while (this.batches.length) {
      this.batches.pop().gc()
    }
  }

  _cancel(r) {
    r.resolve(false)
  }
}

class UpgradeRequest extends Attachable {
  constructor(replicator, fork, length) {
    super(replicator)

    this.fork = fork
    this.length = length
    this.inflight = []
  }

  _unref() {
    if (this.resolved) return
    if (this.replicator.eagerUpgrade === true || this.inflight.length > 0) return
    this.replicator._upgrade = null
  }

  _cancel(r) {
    r.resolve(false)
  }
}

class SeekRequest extends Attachable {
  constructor(replicator, seeks, seeker) {
    super(replicator)

    this.seeker = seeker
    this.inflight = []
    this.seeks = seeks
  }

  _unref() {
    if (this.resolved) return
    if (this.inflight.length > 0) return
    const i = this.seeks.indexOf(this)
    if (i === -1) return
    const h = this.seeks.pop()
    if (i < this.seeks.length) this.seeks[i] = h
  }
}

class ForkRequest extends Attachable {
  constructor(replicator, fork) {
    super(replicator)
    this.fork = fork
    this.inflight = []
    this.batch = null
  }
}

class InflightTracker {
  constructor() {
    this._requests = []
    this._free = []
    this._active = 0
  }

  get idle() {
    return this._active === 0
  }

  *[Symbol.iterator]() {
    for (const req of this._requests) {
      if (req !== null) yield req
    }
  }

  add(req) {
    const id = this._free.length ? this._free.pop() : this._requests.push(null)
    req.id = id
    this._requests[id - 1] = req
    this._active++
    return req
  }

  get(id) {
    return id <= this._requests.length ? this._requests[id - 1] : null
  }

  remove(id, roundtrip) {
    if (id > this._requests.length) return
    if (this._requests[id - 1]) this._active--
    this._requests[id - 1] = null
    if (roundtrip === true) this._free.push(id)
  }

  reusable(id) {
    this._free.push(id)
  }
}

class BlockTracker {
  constructor(replicator) {
    this._replicator = replicator
    this._map = new Map()
  }

  [Symbol.iterator]() {
    return this._map.values()
  }

  isEmpty() {
    return this._map.size === 0
  }

  has(index) {
    return this._map.has(index)
  }

  get(index) {
    return this._map.get(index) || null
  }

  add(index, priority, force) {
    let b = this._map.get(index)
    if (b) return b

    b = new BlockRequest(this._replicator, this, index, priority, force)
    this._map.set(index, b)

    return b
  }

  remove(index) {
    const b = this.get(index)
    this._map.delete(index)
    return b
  }
}

class RoundtripQueue {
  constructor() {
    this.queue = []
    this.tick = 0
  }

  clear() {
    const ids = new Array(this.queue.length)
    for (let i = 0; i < ids.length; i++) {
      ids[i] = this.queue[i][1]
    }

    this.queue = []

    return ids
  }

  add(id) {
    this.queue.push([++this.tick, id])
  }

  flush(tick) {
    let flushed = null

    for (let i = 0; i < this.queue.length; i++) {
      if (this.queue[i][0] > tick) break
      if (flushed === null) flushed = []
      flushed.push(this.queue[i][1])
    }

    if (flushed !== null) this.queue.splice(0, flushed.length)
    return flushed
  }
}

class ProofRequest {
  constructor(msg, proof, block, manifest) {
    this.msg = msg
    this.proof = proof
    this.block = block
    this.manifest = manifest
  }

  async fulfill() {
    if (this.proof === null) return null

    const [proof, block] = await Promise.all([this.proof.settle(), this.block])

    if (this.manifest) proof.manifest = this.manifest
    if (!block && proof.block) return null

    if (block) proof.block.value = block
    return proof
  }
}

class Peer {
  constructor(replicator, protomux, channel, inflightRange) {
    this.core = replicator.core
    this.replicator = replicator
    this.stream = protomux.stream
    this.protomux = protomux
    this.remotePublicKey = this.stream.remotePublicKey
    this.remoteSupportsSeeks = false
    this.inflightRange = inflightRange
    this.fullyDownloadedSignaled = 0

    this.paused = false
    this.removed = false

    this.channel = channel
    this.channel.userData = this

    this.wireSync = this.channel.messages[0]
    this.wireRequest = this.channel.messages[1]
    this.wireCancel = this.channel.messages[2]
    this.wireData = this.channel.messages[3]
    this.wireNoData = this.channel.messages[4]
    this.wireWant = this.channel.messages[5]
    this.wireUnwant = this.channel.messages[6]
    this.wireBitfield = this.channel.messages[7]
    this.wireRange = this.channel.messages[8]
    this.wireExtension = this.channel.messages[9]

    // Same stats as replicator, but for this specific peer
    this.stats = {
      wireSync: { tx: 0, rx: 0 },
      wireRequest: { tx: 0, rx: 0 },
      wireCancel: { tx: 0, rx: 0 },
      wireData: { tx: 0, rx: 0 },
      wireWant: { tx: 0, rx: 0 },
      wireUnwant: { tx: 0, rx: 0 },
      wireBitfield: { tx: 0, rx: 0 },
      wireRange: { tx: 0, rx: 0 },
      wireExtension: { tx: 0, rx: 0 },
      hotswaps: 0,
      invalidData: 0,
      invalidRequests: 0,
      backoffs: 0,
      notAvailableBackoffs: 0
    }

    this.receiverQueue = new ReceiverQueue()
    this.receiverBusy = false

    // most often not used, so made on demand
    this.roundtripQueue = null

    this.inflight = 0
    this.dataProcessing = 0

    this.canUpgrade = true

    this.needsSync = false
    this.syncsProcessing = 0
    this.lastUpgradableLength = 0
    this.lastUpgradableFork = 0

    this._remoteContiguousLength = 0

    // TODO: tweak pipelining so that data sent BEFORE remoteOpened is not cap verified!
    // we might wanna tweak that with some crypto, ie use the cap to encrypt it...
    // or just be aware of that, to only push non leaky data

    this.remoteOpened = false
    this.remoteBitfield = new RemoteBitfield()
    this.missingBlocks = new RemoteBitfield()

    this.pushedLength = 0

    this.remoteFork = 0
    this.remoteLength = 0
    this.remoteCanUpgrade = false
    this.remoteUploading = true
    this.remoteDownloading = true
    this.remoteSynced = false
    this.remoteHasManifest = false
    this.remoteAllowPush = false
    this.remoteRequests = new Map()

    this.segmentsWanted = new Set()
    this.broadcastedNonSparse = false

    this.wants = new LocalWants(this)
    this.remoteWants = new RemoteWants(this)

    this.lengthAcked = 0

    this.extensions = new Map()
    this.lastExtensionSent = ''
    this.lastExtensionRecv = ''

    replicator._ifAvailable++
    replicator._active++
  }

  get remoteContiguousLength() {
    return this.remoteBitfield.findFirst(false, this._remoteContiguousLength)
  }

  getMaxInflight() {
    const stream = this.stream.rawStream
    if (!stream.udx) return Math.min(this.inflightRange[1], this.inflightRange[0] * 3)

    const scale =
      stream.rtt <= SCALE_LATENCY
        ? 1
        : (stream.rtt / SCALE_LATENCY) * Math.min(1, 2 / this.replicator.peers.length)
    return Math.max(
      this.inflightRange[0],
      Math.round(Math.min(this.inflightRange[1], this.inflightRange[0] * scale))
    )
  }

  getMaxHotswapInflight() {
    const inf = this.getMaxInflight()
    return Math.max(16, inf / 2)
  }

  signalUpgrade() {
    if (this._shouldUpdateCanUpgrade() === true) this._updateCanUpgradeAndSync()
    else this.sendSync()
  }

  _markInflight(index) {
    this.missingBlocks.set(index, false)
  }

  broadcastRange(start, length, drop) {
    if (!this.isActive()) return

    if (drop) this._unclearLocalRange(start, length)
    else this._clearLocalRange(start, length)

    const fullyContig = this.core.header.hints.contiguousLength === this.core.state.length

    if (start + LAST_BLOCKS < this.core.state.length && !drop && !fullyContig) {
      if (!this.remoteWants.hasRange(start, length)) return
    }

    let force = false
    if (fullyContig && !drop) {
      start = 0
      length = this.core.state.length

      // Always send the broadcast when we switch from sparse to fully contiguous, so remote knows too
      if (this.fullyDownloadedSignaled < length) {
        this.fullyDownloadedSignaled = length
        force = true
      }
    }

    // TODO: consider also adding early-returns on the drop===true case
    if (!force && !drop) {
      // No need to broadcast if the remote already has this range

      if (this._remoteContiguousLength >= start + length) return

      if (length === 1) {
        if (this.remoteBitfield.get(start)) return
      } else {
        if (this.remoteBitfield.firstUnset(start) >= start + length) return
      }
    }

    this.wireRange.send({
      drop,
      start,
      length
    })
    incrementTx(this.stats.wireRange, this.replicator.stats.wireRange)
  }

  extension(name, message) {
    this.wireExtension.send({ name: name === this.lastExtensionSent ? '' : name, message })
    incrementTx(this.stats.wireExtension, this.replicator.stats.wireExtension)
    this.lastExtensionSent = name
  }

  onextension(message) {
    const name = message.name || this.lastExtensionRecv
    this.lastExtensionRecv = name
    const ext = this.extensions.get(name)
    if (ext) {
      ext._onmessage({ start: 0, end: message.message.byteLength, buffer: message.message }, this)
    }
  }

  sendSync() {
    // Skip syncs if in repair mode
    if (this.core._repairMode) return

    if (this.syncsProcessing !== 0) {
      this.needsSync = true
      return
    }

    if (this.core.state.fork !== this.remoteFork) {
      this.canUpgrade = false
    }

    this.needsSync = false

    this.wireSync.send({
      fork: this.core.state.fork,
      length: this.core.state.length,
      remoteLength: this.core.state.fork === this.remoteFork ? this.remoteLength : 0,
      canUpgrade: this.canUpgrade,
      uploading: true,
      downloading: this.replicator.isDownloading(),
      hasManifest: !!this.core.header.manifest && this.core.compat === false,
      allowPush: this.replicator.isAllowingPush()
    })
    incrementTx(this.stats.wireSync, this.replicator.stats.wireSync)
  }

  onopen({ seeks, capability }) {
    const expected = caps.replicate(
      this.stream.isInitiator === false,
      this.core.key,
      this.stream.handshakeHash
    )

    if (b4a.equals(capability, expected) !== true) {
      // TODO: change this to a rejection instead, less leakage
      throw INVALID_CAPABILITY('Remote sent an invalid replication capability')
    }

    if (this.remoteOpened === true) return
    this.remoteOpened = true
    this.remoteSupportsSeeks = seeks

    this.protomux.cork()

    this.sendSync()

    const contig = Math.min(this.core.state.length, this.core.header.hints.contiguousLength)
    if (contig > 0) {
      this.broadcastRange(0, contig, false)

      if (contig === this.core.state.length) {
        this.broadcastedNonSparse = true
      }
    }

    this.replicator._ifAvailable--
    this.replicator._addPeer(this)

    this.protomux.uncork()

    this.core.checkIfIdle()
  }

  onclose(isRemote) {
    // we might have signalled to the remote that we are done (ie not downloading) and the remote might agree on that
    // if that happens, the channel might be closed by the remote. if so just renegotiate it.
    // TODO: add a CLOSE_REASON to mux to we can make this cleaner...
    const reopen =
      isRemote === true &&
      this.remoteOpened === true &&
      this.remoteDownloading === false &&
      this.remoteUploading === true &&
      this.replicator.downloading === true

    if (this.remoteOpened === false) {
      this.replicator._ifAvailable--
      this.replicator.updateAll()
      return
    }

    this.remoteOpened = false
    this.removed = true
    this.remoteRequests.clear() // cancel all
    this.receiverQueue.clear()

    if (this.roundtripQueue !== null) {
      for (const id of this.roundtripQueue.clear()) this.replicator._inflight.reusable(id)
    }

    this.replicator._removePeer(this)

    if (reopen) {
      this.replicator._makePeer(this.protomux)
    }
  }

  closeIfIdle() {
    if (this.remoteDownloading === false && this.replicator.isDownloading() === false) {
      // idling, shut it down...
      this.channel.close()
      return true
    }

    return false
  }

  async onsync(msg) {
    const {
      fork,
      length,
      remoteLength,
      canUpgrade,
      uploading,
      downloading,
      hasManifest,
      allowPush
    } = msg

    const lengthChanged = length !== this.remoteLength
    const sameFork = fork === this.core.state.fork

    this.remoteSynced = true
    this.remoteFork = fork
    this.remoteLength = length
    this.remoteCanUpgrade = canUpgrade
    this.remoteUploading = uploading
    this.remoteDownloading = downloading
    this.remoteHasManifest = hasManifest
    this.remoteAllowPush = allowPush

    if (this.closeIfIdle()) return

    this.lengthAcked = sameFork ? remoteLength : 0
    this.syncsProcessing++

    this.replicator._updateFork(this)

    if (this.remoteLength > this.core.state.length && this.lengthAcked === this.core.state.length) {
      if (this.replicator._addUpgradeMaybe() !== null) this._update()
    }

    const upgrade =
      lengthChanged === false || sameFork === false
        ? this.canUpgrade && sameFork
        : await this._canUpgrade(length, fork)

    if (length === this.remoteLength && fork === this.core.state.fork) {
      this.canUpgrade = upgrade
      if (upgrade) {
        this.lastUpgradableFork = fork
        this.lastUpgradableLength = length
      }
    }

    if (--this.syncsProcessing !== 0) return // ie not latest

    if (
      this.needsSync === true ||
      (this.core.state.fork === this.remoteFork && this.core.state.length > this.remoteLength)
    ) {
      this.signalUpgrade()
    }

    this._update()
  }

  _shouldUpdateCanUpgrade() {
    return (
      this.core.state.fork === this.remoteFork &&
      this.core.state.length > this.remoteLength &&
      this.canUpgrade === false &&
      this.syncsProcessing === 0
    )
  }

  async _updateCanUpgradeAndSync() {
    const { length, fork } = this.core.state

    const remoteLength = this.remoteLength
    const remoteFork = this.remoteFork
    const canUpgrade = await this._canUpgrade(this.remoteLength, this.remoteFork)

    if (
      this.syncsProcessing > 0 ||
      length !== this.core.state.length ||
      fork !== this.core.state.fork
    ) {
      return
    }
    if (remoteLength !== this.remoteLength || remoteFork !== this.remoteFork) {
      return
    }
    if (canUpgrade === this.canUpgrade) {
      return
    }

    this.canUpgrade = canUpgrade
    if (canUpgrade) {
      this.lastUpgradableLength = this.remoteLength
      this.lastUpgradableFork = this.remoteFork
    }

    this.sendSync()
  }

  // Safe to call in the background - never fails
  async _canUpgrade(remoteLength, remoteFork) {
    if (remoteFork !== this.core.state.fork) return false

    if (remoteLength === 0) return true
    if (remoteLength >= this.core.state.length) return false

    try {
      // Rely on caching to make sure this is cheap...
      const canUpgrade = await MerkleTree.upgradeable(this.core.state, remoteLength)

      if (remoteFork !== this.core.state.fork) return false

      return canUpgrade
    } catch {
      return false
    }
  }

  async _getProof(batch, msg, pushing) {
    let block = null

    if (msg.block) {
      const index = msg.block.index

      if (!pushing && (msg.fork !== this.core.state.fork || !this.core.bitfield.get(index))) {
        return new ProofRequest(msg, null, null, null)
      }

      block = batch.getBlock(index)
      block.catch(noop)
    }

    const manifest = msg.manifest && !this.core.compat ? this.core.header.manifest : null

    try {
      const proof = await MerkleTree.proof(this.core.state, batch, msg)
      return new ProofRequest(msg, proof, block, manifest)
    } catch (err) {
      batch.destroy()

      this.replicator._oninvalidrequest(err, msg, this)

      if (this.stats.invalidRequests >= MAX_INVALID_REQUESTS) throw err

      await backoff(this.stats.invalidRequests)
      return null
    }
  }

  async onrequest(msg) {
    const size = this.remoteRequests.size
    this.remoteRequests.set(msg.id, msg)

    // if size didnt change -> id overwrite -> old one is deleted, cancel current and re-add
    if (size === this.remoteRequests.size) {
      this._cancel(msg.id)
      this.remoteRequests.set(msg.id, msg)
    }

    if (!this.protomux.drained || this.receiverQueue.length) {
      this.receiverQueue.push(msg)
      return
    }

    if (this.replicator.destroyed) return

    await this._handleRequest(msg)
  }

  oncancel(msg) {
    this._cancel(msg.request)
  }

  _cancel(id) {
    this.remoteRequests.delete(id)
    this.receiverQueue.delete(id)
  }

  ondrain() {
    return this._handleRequests()
  }

  async _handleRequests() {
    if (this.receiverBusy || this.replicator.destroyed) return
    this.receiverBusy = true
    this.protomux.cork()

    while (
      this.remoteOpened &&
      this.protomux.drained &&
      this.receiverQueue.length > 0 &&
      !this.removed
    ) {
      const msg = this.receiverQueue.shift()
      await this._handleRequest(msg)
    }

    this.protomux.uncork()
    this.receiverBusy = false
  }

  async push(index) {
    if (!this.remoteAllowPush) return
    if (!this.remoteDownloading) return
    if (this.core.state.length <= index) return
    if (this.core.state.fork !== this.remoteFork) return
    if (this.remoteBitfield.get(index)) return

    const msg = {
      id: 0,
      fork: this.core.state.fork,
      block: null,
      hash: null,
      seek: null,
      upgrade: null,
      manifest: this.remoteLength === 0,
      priority: 0
    }

    await this.replicator._txLock.lock()
    try {
      const remoteLength = Math.max(this.remoteLength, this.pushedLength)

      msg.block = {
        index,
        nodes: MerkleTree.maxMissingNodes(2 * index, remoteLength)
      }

      if (index >= remoteLength) {
        msg.upgrade = {
          start: remoteLength,
          length: this.core.state.length - remoteLength
        }
      }

      let req = null
      const batch = this.core.storage.read()
      try {
        req = await this._getProof(batch, msg, true)
      } catch (err) {
        this.replicator._oninvalidrequest(err, msg, this)
        return
      }

      if (req === null) return
      batch.tryFlush()

      await this._fulfillRequest(req, true)
    } finally {
      this.replicator._txLock.unlock()
    }
  }

  async _handleRequest(msg) {
    const locked = !!msg.upgrade && this.remoteAllowPush

    if (locked) {
      // ensure contig push proofs
      await this.replicator._txLock.lock()
      const end = msg.upgrade.start + msg.upgrade.length

      if (this.pushedLength < end) {
        msg.upgrade.start = this.pushedLength
        msg.upgrade.length = end - this.pushedLength
      }
    }

    try {
      const batch = this.core.storage.read()

      // TODO: could still be answerable if (index, fork) is an ancestor of the current fork
      const req =
        msg.fork === this.core.state.fork
          ? await this._getProof(batch, msg, false)
          : new ProofRequest(msg, null, null, null)

      if (req === null) {
        this.wireNoData.send({ request: msg.id, reason: INVALID_REQUEST })
        return
      }

      batch.tryFlush()

      await this._fulfillRequest(req, false)
    } finally {
      if (locked) this.replicator._txLock.unlock()
    }
  }

  async _fulfillRequest(req, pushing) {
    if (this.core._repairMode) {
      // if cancelled do not reply
      if (this.remoteRequests.get(req.msg.id) !== req.msg) {
        return
      }

      // sync from now on, so safe to delete from the map
      this.remoteRequests.delete(req.msg.id)

      // If cutting off the fulfillment, ensure promise is caught.
      // While repairing merkle tree nodes can be `nil` so can throw.
      req.fulfill().catch(safetyCatch)
      return
    }

    const proof = await req.fulfill()

    if (!pushing) {
      // if cancelled do not reply
      if (this.remoteRequests.get(req.msg.id) !== req.msg) {
        return
      }

      // sync from now on, so safe to delete from the map
      this.remoteRequests.delete(req.msg.id)
    } else if (!this.remoteAllowPush) {
      // if pushing but remote disabled it, just drop it
      return
    }

    if (!this.isActive() && proof.block !== null) {
      return
    }

    if (proof === null) {
      if (req.msg.manifest && this.core.header.manifest) {
        const manifest = this.core.header.manifest
        this.wireData.send({
          request: req.msg.id,
          fork: this.core.state.fork,
          block: null,
          hash: null,
          seek: null,
          upgrade: null,
          manifest
        })
        incrementTx(this.stats.wireData, this.replicator.stats.wireData)
        return
      }

      this.wireNoData.send({ request: req.msg.id, reason: NOT_AVAILABLE })
      return
    }

    if (proof.block !== null) {
      this.replicator._onupload(proof.block.index, proof.block.value.byteLength, this)
    }

    if (proof.upgrade) {
      const remoteLength = MerkleTree.upgradeLength(proof.upgrade)
      if (remoteLength > this.pushedLength) this.pushedLength = remoteLength
    }

    this.wireData.send({
      request: req.msg.id,
      fork: req.msg.fork,
      block: proof.block,
      hash: proof.hash,
      seek: proof.seek,
      upgrade: proof.upgrade,
      manifest: proof.manifest
    })
    incrementTx(this.stats.wireData, this.replicator.stats.wireData)
  }

  _cancelRequest(req) {
    if (req.priority === PRIORITY.CANCELLED) return
    // mark as cancelled also and avoid re-entry
    req.priority = PRIORITY.CANCELLED

    this.inflight--
    this.replicator._requestDone(req.id, false)

    // clear inflight state
    if (isBlockRequest(req)) this.replicator._unmarkInflight(req.block.index)
    if (isUpgradeRequest(req)) this.replicator._clearInflightUpgrade(req)

    if (this.roundtripQueue === null) this.roundtripQueue = new RoundtripQueue()
    this.roundtripQueue.add(req.id)
    this.wireCancel.send({ request: req.id })
    incrementTx(this.stats.wireCancel, this.replicator.stats.wireCancel)
  }

  _checkIfConflict() {
    this.paused = true

    const length = Math.min(this.core.state.length, this.remoteLength)
    if (length === 0) return // pause and ignore

    this.wireRequest.send({
      id: 0, // TODO: use an more explicit id for this eventually...
      fork: this.remoteFork,
      block: null,
      hash: null,
      seek: null,
      upgrade: {
        start: 0,
        length
      }
    })

    incrementTx(this.stats.wireRequest, this.replicator.stats.wireRequest)
  }

  _unmarkInflightBlockRequest(req, data) {
    if (isBlockRequest(req)) this.replicator._unmarkInflight(req.block.index)
    else if (data.block && !req) this.replicator._unmarkInflight(data.block.index)
  }

  async ondata(data) {
    if (data.request !== 0 && !data.upgrade) return this._handleData(data)

    await this.replicator._rxLock.lock()
    try {
      await this._handleData(data)
    } finally {
      this.replicator._rxLock.unlock()
    }
  }

  async _handleData(data) {
    // always allow a fork conflict proof to be sent
    if (data.request === 0 && data.upgrade && data.upgrade.start === 0) {
      if (await this.core.checkConflict(data, this)) return
      this.paused = false
    }

    const req = data.request > 0 ? this.replicator._inflight.get(data.request) : null
    const reorg = data.fork > this.core.state.fork

    // no push atm, TODO: check if this satisfies another pending request
    // allow reorg pushes tho as those are not written to storage so we'll take all the help we can get
    if (req === null && reorg === false && !this.replicator.allowPush) {
      return
    }

    // Skip accepting data if in repair mode to avoid corruption
    if (this.core._repairMode) {
      this.replicator._onnodata(this, req, NOT_AVAILABLE)
      return
    }

    if (req === null && reorg === false && data.block) {
      // mark this as inflight to avoid parallel requests
      this.replicator._markInflight(data.block.index)
    }

    if (req !== null) {
      if (req.peer !== this) return
      this._onrequestroundtrip(req)
    }

    try {
      if (reorg === true) return await this.replicator._onreorgdata(this, req, data)
    } catch (err) {
      safetyCatch(err)
      this._unmarkInflightBlockRequest(req, data)

      this.paused = true
      this.replicator._oninvaliddata(err, req, data, this)
      return
    }

    this.dataProcessing++
    if (isBlockRequest(req)) this.replicator._markProcessing(req.block.index)

    try {
      if (!this.replicator._matchingRequest(req, data) || !(await this.core.verify(data, this))) {
        this.replicator._onnodata(this, req, 0)
        return
      }
    } catch (err) {
      safetyCatch(err)
      this._unmarkInflightBlockRequest(req, data)

      if (err.code === 'WRITE_FAILED') {
        // For example, we don't want to keep pulling data when storage is full
        // TODO: notify the user somehow
        this.paused = true
        return
      }

      if (this.core.closed && !isCriticalError(err)) return

      if (err.code !== 'INVALID_OPERATION') {
        // might be a fork, verify
        this._checkIfConflict()
      }

      // if push mode, we MIGHT get an occasional bad message
      // no need to mega bail for that. TODO: rate limit instead as a general thing
      if (!this.replicator.allowPush) {
        this.paused = true
      }

      this.replicator._onnodata(this, req, 0)
      this.replicator._oninvaliddata(err, req, data, this)
      return
    } finally {
      if (isBlockRequest(req)) this.replicator._markProcessed(req.block.index)
      this.dataProcessing--
    }

    this.replicator._ondata(this, req, data)

    if (this._shouldUpdateCanUpgrade() === true) {
      this._updateCanUpgradeAndSync()
    }
  }

  onnodata({ request, reason }) {
    const req = request > 0 ? this.replicator._inflight.get(request) : null

    if (req === null || req.peer !== this) {
      this.replicator.updateAll()
      return
    }

    this._onrequestroundtrip(req)
    this.replicator._onnodata(this, req, reason)
  }

  _onrequestroundtrip(req) {
    if (req.priority === PRIORITY.CANCELLED) return
    // to avoid re-entry we also just mark it as cancelled
    req.priority = PRIORITY.CANCELLED

    this.inflight--
    this.replicator._requestDone(req.id, true)
    if (this.roundtripQueue === null) return
    const flushed = this.roundtripQueue.flush(req.rt)
    if (flushed === null) return
    for (const id of flushed) this.replicator._inflight.reusable(id)
  }

  onwant(range) {
    if (!this.remoteWants.add(range)) return
    this.replicator._onwant(this, range.start, range.length, range.any)
  }

  onunwant(range) {
    this.remoteWants.remove(range)
  }

  onbitfield({ start, bitfield }) {
    if (start < this._remoteContiguousLength) this._remoteContiguousLength = start // bitfield is always the truth
    this.remoteBitfield.insert(start, bitfield)
    this.missingBlocks.insert(start, bitfield)
    this._clearLocalRange(start, bitfield.byteLength * 8)
    this._update()
  }

  _clearLocalRange(start, length) {
    const bitfield = this.core.skipBitfield === null ? this.core.bitfield : this.core.skipBitfield

    if (length === 1) {
      this.missingBlocks.set(start, this._remoteHasBlock(start) && !bitfield.get(start))
      return
    }

    const contig = Math.min(this.core.state.length, this.core.header.hints.contiguousLength)

    if (start + length < contig) {
      this.missingBlocks.setRange(start, contig, false)
      return
    }

    const rem = start & 32767
    if (rem > 0) {
      start -= rem
      length += rem
    }

    const end = start + Math.min(length, this.core.state.length)
    while (start < end) {
      const local = bitfield.getBitfield(start)

      if (local && local.bitfield) {
        this.missingBlocks.clear(start, local.bitfield)
      }

      start += 32768
    }
  }

  _resetMissingBlock(index) {
    const bitfield = this.core.skipBitfield === null ? this.core.bitfield : this.core.skipBitfield
    this.missingBlocks.set(index, this._remoteHasBlock(index) && !bitfield.get(index))
  }

  _unclearLocalRange(start, length) {
    if (length === 1) {
      this._resetMissingBlock(start)
      return
    }

    const rem = start & (RemoteBitfield.BITS_PER_SEGMENT - 1)
    if (rem > 0) {
      start -= rem
      length += rem
    }

    const fixedStart = start

    const end = start + Math.min(length, this.remoteLength)
    while (start < end) {
      const remote = this.remoteBitfield.getBitfield(start)
      if (remote && remote.bitfield) {
        this.missingBlocks.insert(start, remote.bitfield)
      }

      start += RemoteBitfield.BITS_PER_SEGMENT
    }

    this._clearLocalRange(fixedStart, length)
  }

  async onrange({ drop, start, length }) {
    const has = drop === false

    if (drop === true && start < this._remoteContiguousLength) {
      this._remoteContiguousLength = start
    }

    if (start === 0 && drop === false) {
      if (length > this._remoteContiguousLength) {
        this._remoteContiguousLength = length
        if (
          this.remoteFork === this.core.state.fork &&
          length > this.core.header.hints.remoteContiguousLength
        ) {
          this.core.updateRemoteContiguousLength(length)
        }
      }
    } else if (length === 1) {
      const bitfield = this.core.skipBitfield === null ? this.core.bitfield : this.core.skipBitfield
      this.remoteBitfield.set(start, has)
      this.missingBlocks.set(start, has && !bitfield.get(start))
    } else {
      const rangeStart = this.remoteBitfield.findFirst(!has, start)
      const rangeEnd = length + start

      if (rangeStart !== -1 && rangeStart < rangeEnd) {
        this.remoteBitfield.setRange(rangeStart, rangeEnd, has)
        this.missingBlocks.setRange(rangeStart, rangeEnd, has)
        if (has) this._clearLocalRange(rangeStart, rangeEnd - rangeStart)
      }
    }

    if (this.core.hintsChanged) await this.core.flushHints()
    if (drop === false) this._update()

    this.core.emitRemoteContiguousLength()
  }

  onreorghint() {
    // TODO
  }

  _update() {
    // TODO: if this is in a batch or similar it would be better to defer it
    // we could do that with nextTick/microtick mb? (combined with a property on the session to signal read buffer mb)
    this.replicator.updatePeer(this)
  }

  async _onconflict() {
    this.protomux.cork()
    if (this.remoteLength > 0 && this.core.state.fork === this.remoteFork) {
      await this.onrequest({
        id: 0,
        fork: this.core.state.fork,
        block: null,
        hash: null,
        seek: null,
        upgrade: {
          start: 0,
          length: Math.min(this.core.state.length, this.remoteLength)
        }
      })
    }
    this.channel.close()
    this.protomux.uncork()
  }

  _makeRequest(needsUpgrade, priority, minLength) {
    if (needsUpgrade === true && this.replicator._shouldUpgrade(this) === false) {
      return null
    }

    // ensure that the remote has signalled they have the length we request
    if (this.remoteLength < minLength) {
      return null
    }

    if (needsUpgrade === false && this.replicator._autoUpgrade(this) === true) {
      needsUpgrade = true
    }

    return {
      peer: this,
      rt: this.roundtripQueue === null ? 0 : this.roundtripQueue.tick,
      id: 0,
      fork: this.remoteFork,
      block: null,
      hash: null,
      seek: null,
      upgrade:
        needsUpgrade === false
          ? null
          : { start: this.core.state.length, length: this.remoteLength - this.core.state.length },
      // remote manifest check can be removed eventually...
      manifest: this.core.header.manifest === null && this.remoteHasManifest === true,
      priority,
      timestamp: Date.now(),
      elapsed: 0
    }
  }

  _requestManifest() {
    const req = this._makeRequest(false, 0, 0)
    if (req === null) return

    this._send(req)
  }

  _includeLastBlock() {
    if (this.replicator._alwaysLatestBlock === 0) return null

    const index = this.remoteLength - 1

    // only valid if its an OOB get
    if (index < this.core.state.length) return null

    // do the normal checks
    if (!this._remoteHasBlock(index)) return null
    if (!this._canRequest(index, false)) return null

    // atm we only ever do one upgrade request in parallel, if that changes
    // then we should add some check here for inflights to avoid over requesting
    const b = this.replicator._blocks.add(index, PRIORITY.NORMAL, false)
    return b
  }

  _requestUpgrade(u) {
    if (this.replicator.pushOnly) return false

    const req = this._makeRequest(true, 0, 0)
    if (req === null) return false

    const b = this._includeLastBlock()

    if (b) this._sendBlockRequest(req, b)
    else this._send(req)

    return true
  }

  _requestSeek(s) {
    // if replicator is updating the seeks etc, bail and wait for it to drain
    if (this.replicator._updatesPending > 0) return false
    if (this.replicator.pushOnly) return false

    const { length, fork } = this.core.state

    if (fork !== this.remoteFork) return false

    if (s.seeker.start >= length) {
      const req = this._makeRequest(true, 0, 0)

      // We need an upgrade for the seek, if non can be provided, skip
      if (req === null) return false

      req.seek = this.remoteSupportsSeeks
        ? { bytes: s.seeker.bytes, padding: s.seeker.padding }
        : null

      s.inflight.push(req)
      this._send(req)

      return true
    }

    const len = s.seeker.end - s.seeker.start
    const off = s.seeker.start + Math.floor(Math.random() * len)

    for (let i = 0; i < len; i++) {
      let index = off + i
      if (index > s.seeker.end) index -= len

      if (this._remoteHasBlock(index) === false) continue
      if (this.core.bitfield.get(index) === true) continue
      if (!this._canRequest(index, false)) continue

      // Check if this block is currently inflight - if so pick another
      const b = this.replicator._blocks.get(index)
      if (b !== null && b.inflight.length > 0) continue

      // Block is not inflight, but we only want the hash, check if that is inflight
      const h = this.replicator._hashes.add(index, PRIORITY.NORMAL, false)
      if (h.inflight.length > 0) continue

      const req = this._makeRequest(false, h.priority, index + 1)
      if (req === null) continue

      const nodes = flatTree.depth(s.seeker.start + s.seeker.end - 1)

      req.hash = { index: 2 * index, nodes }
      req.seek = this.remoteSupportsSeeks
        ? { bytes: s.seeker.bytes, padding: s.seeker.padding }
        : null

      s.inflight.push(req)
      h.inflight.push(req)
      this._send(req)

      return true
    }

    this._maybeWantAnyRange(s.seeker.start, len, s)
    return false
  }

  _canRequest(index, force) {
    if (!(index >= 0)) throw ASSERTION('bad index to _canRequest: ' + index)

    if (!force && this.replicator.pushOnly) {
      return false
    }

    if (this.remoteLength >= this.core.state.length) {
      return true
    }

    if (index < this.lastUpgradableLength && this.lastUpgradableFork === this.core.state.fork) {
      return true
    }

    if (this.canUpgrade && this.syncsProcessing === 0) {
      return true
    }

    return false
  }

  _remoteHasBlock(index) {
    return index < this._remoteContiguousLength || this.remoteBitfield.get(index) === true
  }

  _sendBlockRequest(req, b) {
    if (this.core._repairMode) return

    req.block = { index: b.index, nodes: 0 }
    this.replicator._markInflight(b.index)

    b.inflight.push(req)
    this.replicator.hotswaps.add(b)
    this._send(req)
  }

  _requestBlock(b) {
    const { length, fork } = this.core.state

    if (this._remoteHasBlock(b.index) === false || fork !== this.remoteFork) {
      if (!this.core.bitfield.get(b.index)) this._maybeWant(b.index, b)
      return false
    }

    if (!this._canRequest(b.index, b.force)) return false

    const req = this._makeRequest(b.index >= length, b.priority, b.index + 1)
    if (req === null) return false

    this._sendBlockRequest(req, b)

    return true
  }

  _maybeWant(index, handle) {
    if (index < this.remoteContiguousLength) return

    const res = this.wants.add(index, handle)
    if (!res) return

    const batch = res.want && res.unwant
    if (batch) this.protomux.cork()

    if (res.unwant) {
      this.wireUnwant.send(res.unwant)
      incrementTx(this.stats.wireUnwant, this.replicator.stats.wireUnwant)
    }
    if (res.want) {
      this.wireWant.send(res.want)
      incrementTx(this.stats.wireWant, this.replicator.stats.wireWant)
    }

    if (batch) this.protomux.uncork()
  }

  _maybeWantAnyRange(start, length, handle) {
    if (handle.wants) {
      for (const w of handle.wants) {
        if ((w.start !== start || w.length !== length) && w.any) {
          const req = this.wants.removeAnyRange(w.start, w.length, handle)
          if (req) this.wireUnwant.send(req)
        }
      }
    }

    if (start + length <= this.remoteContiguousLength) return

    const req = this.wants.addAnyRange(start, length, handle)
    if (req) this.wireWant.send(req)
  }

  _requestRangeBlock(index, length) {
    if (this.core.bitfield.get(index) === true || !this._canRequest(index, false)) return false

    const b = this.replicator._blocks.add(index, PRIORITY.NORMAL, false)
    if (b.inflight.length > 0) {
      this.missingBlocks.set(index, false) // in case we missed some states just set them ondemand, nbd
      return false
    }

    const req = this._makeRequest(index >= length, b.priority, index + 1)

    // If the request cannot be satisfied, dealloc the block request if no one is subscribed to it
    if (req === null) {
      b.gc()
      return false
    }

    this._sendBlockRequest(req, b)

    // Don't think this will ever happen, as the pending queue is drained before the range queue
    // but doesn't hurt to check this explicitly here also.
    if (b.queued) b.queued = false
    return true
  }

  _findNext(i) {
    if (i < this._remoteContiguousLength) {
      if (this.core.skipBitfield === null) this.replicator._openSkipBitfield()
      i = this.core.skipBitfield.findFirst(false, i)
      if (i < this._remoteContiguousLength && i > -1) return i
      i = this._remoteContiguousLength
    }

    return this.missingBlocks.findFirst(true, i)
  }

  _addRangeBatch(r, offset, end) {
    const { WANT_BATCH } = w
    while (offset < end) {
      const next = this.core.bitfield.findFirst(false, offset)
      if (next === -1 || next >= end) return false

      const b = (next - (next & (WANT_BATCH - 1))) / WANT_BATCH

      if (r.addBatch(b)) return true

      offset = (b + 1) * WANT_BATCH
    }

    return false
  }

  _populateRangeBatches(r) {
    const { WANT_BATCH } = w
    for (let i = r.batches.length - 1; i >= 0; i--) {
      const b = r.batches[i]
      const minStart = b.batch * WANT_BATCH
      const maxEnd = minStart + WANT_BATCH

      const start = Math.max(r.start, minStart)
      const end = Math.min(r.end === -1 ? maxEnd : r.end, maxEnd)

      if (start < end && this.core.bitfield.hasUnset(start, end - start)) continue

      r.removeBatch(b)
    }

    while (r.batches.length < 3) {
      const end = r.end === -1 ? this.core.state.length : r.end
      if (end <= r.start) return

      const len = end - r.start
      const off = r.start + (r.linear ? 0 : Math.floor(Math.random() * len))

      if (!this._addRangeBatch(r, off, end) && !this._addRangeBatch(r, r.start, end)) break
    }
  }

  _requestRangeBatch(start, end, length) {
    let tries = this.inflight

    do {
      start = this._findNext(start)
      if (start === -1 || start >= end) break

      if (this._requestRangeBlock(start, length)) return true
      start++
    } while (tries-- > 0)

    return false
  }

  _getValidEnd(maxEnd) {
    // if we can upgrade the remote, or the remote is ahead, then all the remotes blocks are valid
    // otherwise truncate to the last length the remote has acked for us
    const maxLocalLength =
      this.canUpgrade || this.remoteLength >= this.core.state.length
        ? this.core.state.length
        : this.core.state.fork === this.lastUpgradableFork
          ? Math.min(this.lastUpgradableLength, this.core.state.length)
          : 0

    return Math.min(
      maxLocalLength,
      Math.min(maxEnd === -1 ? this.remoteLength : maxEnd, this.remoteLength)
    )
  }

  _requestRange(r) {
    const { WANT_BATCH } = w
    if (this.syncsProcessing > 0) return false

    const { length, fork } = this.core.state

    if (r.blocks) {
      for (let i = r.start; i < r.end; i++) {
        const index = r.blocks[i]
        const has = index < this._remoteContiguousLength || this.missingBlocks.get(index) === true
        if (has === true && this._requestRangeBlock(index, length)) return true
        this._maybeWant(index, r)
      }

      return false
    }

    this._populateRangeBatches(r)

    const end = this._getValidEnd(r.end)

    if (end <= r.start || fork !== this.remoteFork) return false

    for (let i = 0; i < r.batches.length; i++) {
      const b = r.batches[i]

      const batchStart = Math.max(b.batch * WANT_BATCH, r.start)
      const batchEnd = Math.min((b.batch + 1) * WANT_BATCH, end)
      const len = batchEnd - batchStart
      const off = batchStart + (r.linear ? 0 : Math.floor(Math.random() * len))

      if (
        this._requestRangeBatch(off, end, length) ||
        this._requestRangeBatch(batchStart, off, length)
      ) {
        return true
      }

      this._maybeWant(batchStart, b)
    }

    // Here we should consider making an OOB request (ie if the remote says they have a newer block)
    // but the upgrade code already does that, so no big deal for now

    return false
  }

  _requestForkProof(f) {
    if (!this.remoteLength) return
    if (this.replicator.pushOnly) return

    const req = this._makeRequest(false, 0, 0)
    if (req === null) return

    req.upgrade = { start: 0, length: this.remoteLength }
    req.manifest = !this.core.header.manifest

    f.inflight.push(req)
    this._send(req)
  }

  _requestForkRange(f) {
    if (f.fork !== this.remoteFork || f.batch.want === null) return false
    if (this.replicator.pushOnly) return false

    const end = Math.min(f.batch.want.end, this.remoteLength)
    if (end < f.batch.want.start) return false

    const len = end - f.batch.want.start
    const off = f.batch.want.start + Math.floor(Math.random() * len)

    for (let i = 0; i < len; i++) {
      let index = off + i
      if (index >= end) index -= len

      if (this._remoteHasBlock(index) === false) continue

      const req = this._makeRequest(false, 0, 0)
      if (req === null) return false

      req.hash = { index: 2 * index, nodes: f.batch.want.nodes }

      f.inflight.push(req)
      this._send(req)

      return true
    }

    this._maybeWantAnyRange(f.batch.want.start, len, f)
    return false
  }

  isActive() {
    if (this.paused || this.removed || this.core.header.frozen) return false
    return true
  }

  async _send(req) {
    const fork = this.core.state.fork

    this.inflight++
    this.replicator._inflight.add(req)

    if (req.upgrade !== null && req.fork === fork) {
      const u = this.replicator._addUpgrade()
      u.inflight.push(req)
    }

    try {
      if (req.block !== null && req.fork === fork) {
        req.block.nodes = await MerkleTree.missingNodes(
          this.core.state,
          2 * req.block.index,
          this.core.state.length
        )
        if (req.priority === PRIORITY.CANCELLED) return
      }
      if (req.hash !== null && req.fork === fork && req.hash.nodes === 0) {
        req.hash.nodes = await MerkleTree.missingNodes(
          this.core.state,
          req.hash.index,
          this.core.state.length
        )
        if (req.priority === PRIORITY.CANCELLED) return

        // nodes === 0, we already have it, bail
        if (req.hash.nodes === 0 && (req.hash.index & 1) === 0) {
          this.inflight--
          this.replicator._resolveHashLocally(this, req)
          return
        }
      }
    } catch (err) {
      this.stream.destroy(err)
      return
    }

    this.wireRequest.send(req)
    incrementTx(this.stats.wireRequest, this.replicator.stats.wireRequest)
  }
}

module.exports = class Replicator {
  static Peer = Peer // hack to be able to access Peer from outside this module

  constructor(
    core,
    {
      notDownloadingLinger = NOT_DOWNLOADING_SLACK,
      eagerUpgrade = true,
      allowFork = true,
      allowPush = false,
      alwaysLatestBlock = false,
      inflightRange = null
    } = {}
  ) {
    this.core = core
    this.eagerUpgrade = eagerUpgrade
    this.allowFork = allowFork
    this.allowPush = allowPush
    this.ondownloading = null // optional external hook for monitoring downloading status
    this.peers = []
    this.findingPeers = 0 // updatable from the outside
    this.destroyed = false
    this.downloading = false
    this.pushOnly = false
    this.activeSessions = 0

    this.hotswaps = new HotswapQueue()
    this.inflightRange = inflightRange || DEFAULT_MAX_INFLIGHT

    // Note: nodata and unwant not currently tracked
    // tx = transmitted, rx = received
    this.stats = {
      wireSync: { tx: 0, rx: 0 },
      wireRequest: { tx: 0, rx: 0 },
      wireCancel: { tx: 0, rx: 0 },
      wireData: { tx: 0, rx: 0 },
      wireWant: { tx: 0, rx: 0 },
      wireUnwant: { tx: 0, rx: 0 },
      wireBitfield: { tx: 0, rx: 0 },
      wireRange: { tx: 0, rx: 0 },
      wireExtension: { tx: 0, rx: 0 },
      hotswaps: 0,
      invalidData: 0,
      invalidRequests: 0,
      backoffs: 0,
      notAvailableBackoffs: 0
    }

    this._attached = new Set()
    this._inflight = new InflightTracker()
    this._blocks = new BlockTracker(this)
    this._hashes = new BlockTracker(this)

    this._alwaysLatestBlock = alwaysLatestBlock ? 1 : 0
    this._queued = []

    this._seeks = []
    this._upgrade = null
    this._reorgs = []
    this._ranges = []

    this._rxLock = new Mutex()
    this._txLock = new Mutex()

    this._hadPeers = false
    this._active = 0
    this._ifAvailable = 0
    this._updatesPending = 0
    this._applyingReorg = null
    this._manifestPeer = null
    this._notDownloadingLinger = notDownloadingLinger
    this._notDownloadingTimer = null

    const self = this
    this._onstreamclose = onstreamclose

    function onstreamclose() {
      self.detachFrom(this.userData)
    }
  }

  setInflightRange(min, max) {
    const def = this.inflightRange
    this.inflightRange = [min, max]
    for (const peer of this.peers) {
      if (peer.inflightRange !== def) continue
      peer.inflightRange = this.inflightRange
    }
  }

  updateActivity(inc, session) {
    this.activeSessions += inc
    this.setDownloading(this.activeSessions !== 0, session)
  }

  isDownloading() {
    return this.downloading || !this._inflight.idle
  }

  async push(index) {
    const all = []
    for (const peer of this.peers) {
      all.push(peer.push(index))
    }
    await Promise.all(all)
  }

  setAllowPush(allowPush) {
    if (allowPush === this.allowPush) return
    this.allowPush = allowPush
    for (const peer of this.peers) peer.sendSync()
  }

  isAllowingPush() {
    if (!this.allowPush) return false
    if (this._notDownloadingTimer) return false
    return this.downloading
  }

  setPushOnly(pushOnly) {
    if (pushOnly === this.pushOnly) return
    this.pushOnly = pushOnly
  }

  setDownloading(downloading) {
    const allowPush = this.isAllowingPush()
    const linger = this._notDownloadingLinger

    if (this._notDownloadingTimer) {
      clearTimeout(this._notDownloadingTimer)
      this._notDownloadingTimer = null
    }

    if (this.destroyed) return

    if (downloading || linger === 0) {
      if (!this._setDownloadingNow(downloading)) this.signalAllowPush(allowPush)
      return
    }

    this._notDownloadingTimer = setTimeout(setNotDownloadingLater, linger, this)
    if (this._notDownloadingTimer.unref) this._notDownloadingTimer.unref()
    this.signalAllowPush(allowPush)
  }

  signalAllowPush(allowPush) {
    if (allowPush === this.isAllowingPush()) return
    for (const peer of this.peers) peer.signalUpgrade()
  }

  _setDownloadingNow(downloading) {
    this._notDownloadingTimer = null

    if (this.downloading === downloading) return false
    this.downloading = downloading

    if (!downloading && this.isDownloading()) return false

    for (const peer of this.peers) peer.signalUpgrade()

    if (downloading) {
      // restart channel if needed...
      for (const protomux of this._attached) {
        if (!protomux.stream.handshakeHash) continue
        if (protomux.opened({ protocol: 'hypercore/alpha', id: this.core.discoveryKey })) continue
        this._makePeer(protomux, true)
      }
    } else {
      for (const peer of this.peers) peer.closeIfIdle()
    }

    if (this.ondownloading !== null && downloading) this.ondownloading()
    return true
  }

  cork() {
    for (const peer of this.peers) peer.protomux.cork()
  }

  uncork() {
    for (const peer of this.peers) peer.protomux.uncork()
  }

  // Called externally when a range of new blocks has been processed/removed
  onhave(start, length, drop = false) {
    for (const peer of this.peers) peer.broadcastRange(start, length, drop)
  }

  // Called externally when a truncation upgrade has been processed
  ontruncate(newLength, truncated) {
    const notify = []

    for (const blk of this._blocks) {
      if (blk.index < newLength) continue
      notify.push(blk)
    }

    for (const blk of notify) {
      for (const r of blk.refs) {
        if (r.snapshot === false) continue
        blk.detach(r, SNAPSHOT_NOT_AVAILABLE())
      }
    }

    for (const peer of this.peers) {
      peer.fullyDownloadedSignaled = Math.min(newLength, peer.fullyDownloadedSignaled)
      peer._unclearLocalRange(newLength, truncated)
    }
  }

  // Called externally when a upgrade has been processed
  onupgrade() {
    for (const peer of this.peers) peer.signalUpgrade()
    if (this._blocks.isEmpty() === false) this._resolveBlocksLocally()
    if (this._upgrade !== null) this._resolveUpgradeRequest(null)
    if (!this._blocks.isEmpty() || this._ranges.length !== 0 || this._seeks.length !== 0) {
      this._updateNonPrimary(true)
    }
  }

  // Called externally when a conflict has been detected and verified
  async onconflict() {
    const all = []
    for (const peer of this.peers) {
      all.push(peer._onconflict())
    }
    await Promise.allSettled(all)
  }

  async applyPendingReorg() {
    if (this._applyingReorg !== null) {
      await this._applyingReorg
      return true
    }

    for (let i = this._reorgs.length - 1; i >= 0; i--) {
      const f = this._reorgs[i]
      if (f.batch !== null && f.batch.finished) {
        await this._applyReorg(f)
        return true
      }
    }

    return false
  }

  addUpgrade(session) {
    if (this._upgrade !== null) {
      const ref = this._upgrade.attach(session)
      this._checkUpgradeIfAvailable()
      return ref
    }

    const ref = this._addUpgrade().attach(session)

    this.updateAll()

    return ref
  }

  addBlock(session, index, force) {
    const b = this._blocks.add(index, PRIORITY.HIGH, force)
    const ref = b.attach(session)

    this._queueBlock(b)
    this.updateAll()

    return ref
  }

  addSeek(session, seeker) {
    const s = new SeekRequest(this, this._seeks, seeker)
    const ref = s.attach(session)

    this._seeks.push(s)
    this.updateAll()

    return ref
  }

  addRange(
    session,
    { start = 0, end = -1, length = toLength(start, end), blocks = null, linear = false } = {}
  ) {
    if (blocks !== null) {
      // if using blocks, start, end just acts as frames around the blocks array
      start = 0
      end = length = blocks.length
    }

    const r = new RangeRequest(
      this,
      this._ranges,
      start,
      length === -1 ? -1 : start + length,
      linear,
      blocks
    )

    const ref = r.attach(session)

    // Trigger this to see if this is already resolved...
    // Also auto compresses the range based on local bitfield
    clampRange(this.core, r)

    if (r.end !== -1 && r.start >= r.end) {
      this._resolveRangeRequest(r)
      return ref
    }

    this.updateAll()

    return ref
  }

  cancel(ref) {
    ref.context.detach(ref, null)
  }

  static clearRequests(session, err) {
    if (session.length === 0) return
    if (session.length === 1 && session[0] === null) return

    const updated = new Set()

    while (session.length > 0) {
      const ref = session[session.length - 1]
      updated.add(ref.context.replicator)
      ref.context.detach(ref, err)
    }

    for (const replicator of updated) {
      replicator.updateAll()
    }
  }

  clearRequests(session, err = null) {
    let cleared = false
    while (session.length > 0) {
      const ref = session[session.length - 1]
      ref.context.detach(ref, err)
      cleared = true
    }

    if (cleared) this.updateAll()
  }

  _matchingRequest(req, data) {
    if (this.allowPush) {
      return true
    }
    if (data.block !== null && (req.block === null || req.block.index !== data.block.index)) {
      return false
    }
    if (data.hash !== null && (req.hash === null || req.hash.index !== data.hash.index)) {
      return false
    }
    if (data.seek !== null && (req.seek === null || req.seek.bytes !== data.seek.bytes)) {
      return false
    }
    if (data.upgrade !== null && req.upgrade === null) {
      return false
    }
    return req.fork === data.fork
  }

  _addUpgradeMaybe() {
    return this.eagerUpgrade === true ? this._addUpgrade() : this._upgrade
  }

  // TODO: this function is OVER called atm, at each updatePeer/updateAll
  // instead its more efficient to only call it when the conditions in here change - ie on sync/add/remove peer
  // Do this when we have more tests.
  _checkUpgradeIfAvailable() {
    if (this._ifAvailable > 0 && this.peers.length < MAX_PEERS_UPGRADE) return
    if (this._upgrade === null || this._upgrade.refs.length === 0) return
    if (this._hadPeers === false && this.findingPeers > 0) return

    const maxPeers = Math.min(this.peers.length, MAX_PEERS_UPGRADE)

    // check if a peer can upgrade us

    for (let i = 0; i < maxPeers; i++) {
      const peer = this.peers[i]

      if (peer.remoteSynced === false) return

      if (this.core.state.length === 0 && peer.remoteLength > 0) return

      if (peer.remoteLength <= this._upgrade.length || peer.remoteFork !== this._upgrade.fork) {
        continue
      }

      if (peer.syncsProcessing > 0) return

      if (peer.lengthAcked !== this.core.state.length && peer.remoteFork === this.core.state.fork) {
        return
      }

      if (peer.remoteCanUpgrade === true) return
    }

    // check if reorgs in progress...

    if (this._applyingReorg !== null) return

    // TODO: we prob should NOT wait for inflight reorgs here, seems better to just resolve the upgrade
    // and then apply the reorg on the next call in case it's slow - needs some testing in practice

    for (let i = 0; i < this._reorgs.length; i++) {
      const r = this._reorgs[i]
      if (r.inflight.length > 0) return
    }

    // if something is inflight, wait for that first
    if (this._upgrade.inflight.length > 0) return

    // nothing to do, indicate no update avail

    const u = this._upgrade
    this._upgrade = null
    u.resolve(false)
  }

  _addUpgrade() {
    if (this._upgrade !== null) return this._upgrade

    // TODO: needs a reorg: true/false flag to indicate if the user requested a reorg
    this._upgrade = new UpgradeRequest(this, this.core.state.fork, this.core.state.length)

    return this._upgrade
  }

  _addReorg(fork, peer) {
    if (this.allowFork === false) return null

    // TODO: eager gc old reorgs from the same peer
    // not super important because they'll get gc'ed when the request finishes
    // but just spam the remote can do ...

    for (const f of this._reorgs) {
      if (f.fork > fork && f.batch !== null) return null
      if (f.fork === fork) return f
    }

    const f = new ForkRequest(this, fork)

    this._reorgs.push(f)

    // maintain sorted by fork
    let i = this._reorgs.length - 1
    while (i > 0 && this._reorgs[i - 1].fork > fork) {
      this._reorgs[i] = this._reorgs[i - 1]
      this._reorgs[--i] = f
    }

    return f
  }

  _shouldUpgrade(peer) {
    if (this._upgrade !== null && this._upgrade.inflight.length > 0) return false
    return (
      peer.remoteCanUpgrade === true &&
      peer.remoteLength > this.core.state.length &&
      peer.lengthAcked === this.core.state.length
    )
  }

  _autoUpgrade(peer) {
    return (
      this._upgrade !== null &&
      peer.remoteFork === this.core.state.fork &&
      this._shouldUpgrade(peer)
    )
  }

  _addPeer(peer) {
    this._hadPeers = true
    this.peers.push(peer)
    this.updatePeer(peer)
    this._onpeerupdate(true, peer)
  }

  _requestDone(id, roundtrip) {
    this._inflight.remove(id, roundtrip)
    if (this.isDownloading() === true) return
    for (const peer of this.peers) peer.signalUpgrade()
  }

  _removePeer(peer) {
    this.peers.splice(this.peers.indexOf(peer), 1)
    peer.wants.destroy()

    if (this._manifestPeer === peer) this._manifestPeer = null

    let inflight = false
    for (const req of this._inflight) {
      if (req.peer !== peer) continue
      inflight = true
      this._inflight.remove(req.id, true)
      this._clearRequest(peer, req)
    }

    this._onpeerupdate(false, peer)
    if (inflight) {
      this.updateAll()
    } else {
      this._checkUpgradeIfAvailable()
    }
  }

  _queueBlock(b) {
    if (b.inflight.length > 0 || b.queued === true) return
    b.queued = true
    this._queued.push(b)
  }

  _resolveHashLocally(peer, req) {
    this._requestDone(req.id, false)
    this._resolveBlockRequest(this._hashes, req.hash.index / 2, null, req)
    this.updatePeer(peer)
  }

  // Runs in the background - not allowed to throw
  async _resolveBlocksLocally() {
    // TODO: check if fork compat etc. Requires that we pass down truncation info

    const clear = []
    const blocks = []

    const reader = this.core.storage.read()
    for (const b of this._blocks) {
      if (this.core.bitfield.get(b.index) === false) continue
      blocks.push(this._resolveLocalBlock(b, reader, clear))
    }
    reader.tryFlush()

    await Promise.all(blocks)

    if (!clear.length) return

    // Currently the block tracker does not support deletes during iteration, so we make
    // sure to clear them afterwards.
    for (const b of clear) {
      this._blocks.remove(b.index)
      removeHotswap(b)
    }
  }

  async _resolveLocalBlock(b, reader, resolved) {
    try {
      b.resolve(await reader.getBlock(b.index))
    } catch (err) {
      b.reject(err)
      return
    }

    resolved.push(b)
  }

  _resolveBlockRequest(tracker, index, value, req) {
    const b = tracker.remove(index)
    if (b === null) return false

    removeInflight(b.inflight, req)
    removeHotswap(b)
    b.queued = false

    b.resolve(value)

    if (b.inflight.length > 0) {
      // if anything is still inflight, cancel it
      for (let i = b.inflight.length - 1; i >= 0; i--) {
        const req = b.inflight[i]
        req.peer._cancelRequest(req)
      }
    }

    return true
  }

  _resolveUpgradeRequest(req) {
    if (req !== null) removeInflight(this._upgrade.inflight, req)

    if (
      this.core.state.length === this._upgrade.length &&
      this.core.state.fork === this._upgrade.fork
    ) {
      return false
    }

    const u = this._upgrade
    this._upgrade = null
    u.resolve(true)

    return true
  }

  _resolveRangeRequest(req) {
    req.resolve(true)
    req.gc()
  }

  _clearInflightBlock(tracker, req) {
    const isBlock = tracker === this._blocks
    const index = isBlock === true ? req.block.index : req.hash.index / 2
    const b = tracker.get(index)

    if (b === null || removeInflight(b.inflight, req) === false) return

    if (removeHotswap(b) === true && b.inflight.length > 0) {
      this.hotswaps.add(b)
    }

    if (b.refs.length > 0 && isBlock === true) {
      this._queueBlock(b)
      return
    }

    b.gc()
  }

  _clearInflightUpgrade(req) {
    if (this._upgrade === null || removeInflight(this._upgrade.inflight, req) === false) return
    this._upgrade.gc()
  }

  _clearInflightSeeks(req) {
    for (const s of this._seeks) {
      if (removeInflight(s.inflight, req) === false) continue
      s.gc()
    }
  }

  _clearInflightReorgs(req) {
    for (const r of this._reorgs) {
      removeInflight(r.inflight, req)
    }
  }

  _clearOldReorgs(fork) {
    for (let i = 0; i < this._reorgs.length; i++) {
      const f = this._reorgs[i]
      if (f.fork >= fork) continue
      if (i === this._reorgs.length - 1) this._reorgs.pop()
      else this._reorgs[i] = this._reorgs.pop()
      i--
    }
  }

  // "slow" updates here - async but not allowed to ever throw
  async _updateNonPrimary(updateAll) {
    // Check if running, if so skip it and the running one will issue another update for us (debounce)
    while (++this._updatesPending === 1) {
      let len = Math.min(MAX_RANGES, this._ranges.length)

      for (let i = 0; i < len; i++) {
        const r = this._ranges[i]

        clampRange(this.core, r)

        if (r.end !== -1 && r.start >= r.end) {
          this._resolveRangeRequest(r)
          i--
          if (len > this._ranges.length) len--
          if (this._ranges.length === MAX_RANGES) updateAll = true
        }
      }

      for (let i = 0; i < this._seeks.length; i++) {
        const s = this._seeks[i]

        let err = null
        let res = null

        try {
          res = await s.seeker.update()
        } catch (error) {
          err = error
        }

        if (!res && !err) continue

        if (i < this._seeks.length - 1) this._seeks[i] = this._seeks.pop()
        else this._seeks.pop()

        i--

        if (err) s.reject(err)
        else s.resolve(res)
      }

      // No additional updates scheduled - break
      if (--this._updatesPending === 0) break
      // Debounce the additional updates - continue
      this._updatesPending = 0
    }

    if (this._inflight.idle || updateAll) this.updateAll()
  }

  _clearRequest(peer, req) {
    if (req.block !== null) {
      this._clearInflightBlock(this._blocks, req)
      this._unmarkInflight(req.block.index)
    }

    if (req.hash !== null) {
      this._clearInflightBlock(this._hashes, req)
    }

    if (req.upgrade !== null && this._upgrade !== null) {
      this._clearInflightUpgrade(req)
    }

    if (this._seeks.length > 0) {
      this._clearInflightSeeks(req)
    }

    if (this._reorgs.length > 0) {
      this._clearInflightReorgs(req)
    }
  }

  _onnodata(peer, req, reason) {
    if (reason === NOT_AVAILABLE) {
      peer.stats.notAvailableBackoffs++
      this.stats.notAvailableBackoffs++

      if (peer.stats.notAvailableBackoffs >= MAX_BACKOFFS) {
        peer.paused = true
      }
    }

    if (reason === INVALID_REQUEST) {
      peer.stats.backoffs++
      this.stats.backoffs++

      if (peer.stats.backoffs >= MAX_BACKOFFS) {
        peer.paused = true
      }
    }

    if (req) {
      this._clearRequest(peer, req)
    }

    this.updateAll()
  }

  _openSkipBitfield() {
    // technically the skip bitfield gets bits cleared if .clear() is called
    // also which might be in inflight also, but that just results in that section being overcalled shortly
    // worst case, so ok for now

    const bitfield = this.core.openSkipBitfield()

    for (const req of this._inflight) {
      if (req.block) bitfield.set(req.block.index, true) // skip
    }
  }

  _markProcessing(index) {
    const b = this._blocks.get(index)
    if (b) {
      b.processing = true
      return
    }

    const h = this._hashes.get(index)
    if (h) h.processing = true
  }

  _markProcessed(index) {
    const b = this._blocks.get(index)
    if (b) return b.processed()

    const h = this._hashes.get(index)
    if (h) h.processed()
  }

  _markInflight(index) {
    if (this.core.skipBitfield !== null) this.core.skipBitfield.set(index, true)
    for (const peer of this.peers) peer._markInflight(index)
  }

  _unmarkInflight(index) {
    if (this.core.skipBitfield !== null) {
      this.core.skipBitfield.set(index, this.core.bitfield.get(index))
    }
    for (const peer of this.peers) peer._resetMissingBlock(index)
  }

  _ondata(peer, req, data) {
    if (req) {
      req.elapsed = Date.now() - req.timestamp
    }

    if (data.block !== null) {
      this._resolveBlockRequest(this._blocks, data.block.index, data.block.value, req)
      this._ondownload(data.block.index, data.block.value.byteLength, peer, req)
    }

    if (data.hash !== null && (data.hash.index & 1) === 0) {
      this._resolveBlockRequest(this._hashes, data.hash.index / 2, null, req)
    }

    if (this._upgrade !== null) {
      this._resolveUpgradeRequest(req)
    }

    if (this._seeks.length > 0) {
      this._clearInflightSeeks(req)
    }

    if (this._reorgs.length > 0) {
      this._clearInflightReorgs(req)
    }

    if (this._manifestPeer === peer && this.core.header.manifest !== null) {
      this._manifestPeer = null
    }

    if (this._seeks.length > 0 || this._ranges.length > 0) {
      this._updateNonPrimary(this._seeks.length > 0)
    }

    this.updatePeer(peer)
  }

  _onwant(peer, start, length, any) {
    if (!peer.isActive()) return

    const contig = Math.min(this.core.state.length, this.core.header.hints.contiguousLength)

    if (start + length < contig || this.core.state.length === contig || (any && start < contig)) {
      peer.wireRange.send({
        drop: false,
        start: 0,
        length: contig
      })
      incrementTx(peer.stats.wireRange, this.stats.wireRange)
      return
    }

    length = Math.min(length, this.core.state.length - start)

    if (any) {
      const bit = this.core.bitfield.firstSet(start)
      if (bit > -1 && bit < start + length) {
        peer.wireRange.send({
          drop: false,
          start: bit,
          length: 1
        })
      }
      return
    }

    for (const msg of this.core.bitfield.want(start, length)) {
      peer.wireBitfield.send(msg)
      incrementTx(peer.stats.wireBitfield, this.stats.wireBitfield)
    }
  }

  async _onreorgdata(peer, req, data) {
    const newBatch = data.upgrade && (await this.core.verifyReorg(data))
    const f = this._addReorg(data.fork, peer)

    if (f === null) {
      this.updateAll()
      return
    }

    removeInflight(f.inflight, req)

    if (f.batch) {
      await f.batch.update(data)
    } else if (data.upgrade) {
      f.batch = newBatch

      // Remove "older" reorgs in progress as we just verified this one.
      this._clearOldReorgs(f.fork)
    }

    if (f.batch && f.batch.finished) {
      if (this._addUpgradeMaybe() !== null) {
        await this._applyReorg(f)
      }
    }

    this.updateAll()
  }

  // Never throws, allowed to run in the background
  async _applyReorg(f) {
    // TODO: more optimal here to check if potentially a better reorg
    // is available, ie higher fork, and request that one first.
    // This will request that one after this finishes, which is fine, but we
    // should investigate the complexity in going the other way

    const u = this._upgrade
    const old = this._reorgs

    this._reorgs = [] // clear all as the nodes are against the old tree - easier
    this._applyingReorg = this.core.reorg(f.batch, null) // TODO: null should be the first/last peer?

    try {
      await this._applyingReorg
    } catch (err) {
      this._upgrade = null
      u.reject(err)
    }

    this._applyingReorg = null

    if (this._upgrade !== null) {
      this._resolveUpgradeRequest(null)
    }

    for (const peer of this.peers) this._updateFork(peer)

    // TODO: all the remaining is a tmp workaround until we have a flag/way for ANY_FORK
    for (const r of this._ranges) {
      r.start = r.userStart
      r.end = r.userEnd
    }

    for (const f of old) f.resolve()
    f.resolve()

    this.updateAll()
  }

  _maybeUpdate() {
    return this._upgrade !== null && this._upgrade.inflight.length === 0
  }

  _maybeRequestManifest() {
    return this.core.header.manifest === null && this._manifestPeer === null
  }

  _updateFork(peer) {
    if (
      this._applyingReorg !== null ||
      this.allowFork === false ||
      peer.remoteFork <= this.core.state.fork
    ) {
      return false
    }

    const f = this._addReorg(peer.remoteFork, peer)

    // TODO: one per peer is better
    if (f !== null && f.batch === null && f.inflight.length === 0) {
      return peer._requestForkProof(f)
    }

    return false
  }

  _updateHotswap(peer) {
    const maxHotswaps = peer.getMaxHotswapInflight()
    if (!peer.isActive() || peer.inflight >= maxHotswaps) return

    for (const b of this.hotswaps.pick(peer)) {
      if (peer._requestBlock(b) === false) continue
      peer.stats.hotswaps++
      peer.replicator.stats.hotswaps++
      if (peer.inflight >= maxHotswaps) break
    }
  }

  _updatePeer(peer) {
    if (!peer.isActive() || peer.inflight >= peer.getMaxInflight() || !peer.remoteUploading) {
      return false
    }

    // Eagerly request the manifest even if the remote length is 0. If not 0 we'll get as part of the upgrade request...
    if (
      this._maybeRequestManifest() === true &&
      peer.remoteLength === 0 &&
      peer.remoteHasManifest === true
    ) {
      this._manifestPeer = peer
      peer._requestManifest()
    }

    for (const s of this._seeks) {
      if (s.inflight.length > 0) continue // TODO: one per peer is better
      if (peer._requestSeek(s) === true) {
        return true
      }
    }

    // Implied that any block in the queue should be requested, no matter how many inflights
    const blks = new RandomIterator(this._queued)

    for (const b of blks) {
      if (b.queued === false || peer._requestBlock(b) === true) {
        b.queued = false
        blks.dequeue()
        return true
      }
    }

    return false
  }

  _updatePeerNonPrimary(peer) {
    if (!peer.isActive() || peer.inflight >= peer.getMaxInflight() || !peer.remoteUploading) {
      return false
    }

    const ranges = new RandomIterator(this._ranges)
    let tried = 0

    for (const r of ranges) {
      if (peer._requestRange(r) === true) {
        return true
      }
      if (++tried >= MAX_RANGES) break
    }

    // Iterate from newest fork to oldest fork...
    for (let i = this._reorgs.length - 1; i >= 0; i--) {
      const f = this._reorgs[i]
      if (f.batch !== null && f.inflight.length === 0 && peer._requestForkRange(f) === true) {
        return true
      }
    }

    if (this._maybeUpdate() === true && peer._requestUpgrade(this._upgrade) === true) {
      return true
    }

    return false
  }

  updatePeer(peer) {
    // Quick shortcut to wait for flushing reorgs - not needed but less waisted requests
    if (this._applyingReorg !== null) return

    while (this._updatePeer(peer) === true);
    while (this._updatePeerNonPrimary(peer) === true);

    if (this.peers.length > 1 && this._blocks.isEmpty() === false) {
      this._updateHotswap(peer)
    }

    this._checkUpgradeIfAvailable()
  }

  updateAll() {
    // Quick shortcut to wait for flushing reorgs - not needed but less waisted requests
    if (this._applyingReorg !== null) return

    const peers = new RandomIterator(this.peers)

    for (const peer of peers) {
      if (this._updatePeer(peer) === true) {
        peers.requeue()
      }
    }

    // Check if we can skip the non primary check fully
    if (this._maybeUpdate() === false && this._ranges.length === 0 && this._reorgs.length === 0) {
      this._checkUpgradeIfAvailable()
      return
    }

    for (const peer of peers.restart()) {
      if (this._updatePeerNonPrimary(peer) === true) {
        peers.requeue()
      }
    }

    this._checkUpgradeIfAvailable()
  }

  onpeerdestroy() {
    if (--this._active === 0) this.core.checkIfIdle()
  }

  attached(protomux) {
    return this._attached.has(protomux)
  }

  attachTo(protomux) {
    if (this.core.closed) return

    const makePeer = this._makePeer.bind(this, protomux)

    this._attached.add(protomux)
    protomux.pair({ protocol: 'hypercore/alpha', id: this.core.discoveryKey }, makePeer)
    protomux.stream.setMaxListeners(0)
    protomux.stream.on('close', this._onstreamclose)

    this._ifAvailable++
    this._active++

    protomux.stream.opened.then((opened) => {
      this._ifAvailable--
      this._active--

      if (opened && !this.destroyed) makePeer()
      this._checkUpgradeIfAvailable()

      this.core.checkIfIdle()
    })
  }

  detachFrom(protomux) {
    if (this._attached.delete(protomux)) {
      protomux.stream.removeListener('close', this._onstreamclose)
      protomux.unpair({ protocol: 'hypercore/alpha', id: this.core.discoveryKey })
    }
  }

  idle() {
    return this.peers.length === 0 && this._active === 0
  }

  close() {
    const waiting = []

    for (const peer of this.peers) {
      waiting.push(peer.channel.fullyClosed())
    }

    this.destroy()
    return Promise.all(waiting)
  }

  destroy() {
    if (this.destroyed) return
    this.destroyed = true

    if (this._notDownloadingTimer) {
      clearTimeout(this._notDownloadingTimer)
      this._notDownloadingTimer = null
    }

    while (this.peers.length) {
      const peer = this.peers[this.peers.length - 1]
      this.detachFrom(peer.protomux)
      peer.channel.close() // peer is removed from array in onclose
    }

    for (const protomux of this._attached) {
      this.detachFrom(protomux)
    }
  }

  _makePeer(protomux) {
    const replicator = this
    if (protomux.opened({ protocol: 'hypercore/alpha', id: this.core.discoveryKey })) {
      return onnochannel()
    }

    const channel = protomux.createChannel({
      userData: null,
      protocol: 'hypercore/alpha',
      aliases: ['hypercore'],
      id: this.core.discoveryKey,
      handshake: m.wire.handshake,
      messages: [
        { encoding: m.wire.sync, onmessage: onwiresync },
        { encoding: m.wire.request, onmessage: onwirerequest },
        { encoding: m.wire.cancel, onmessage: onwirecancel },
        { encoding: m.wire.data, onmessage: onwiredata },
        { encoding: m.wire.noData, onmessage: onwirenodata },
        { encoding: m.wire.want, onmessage: onwirewant },
        { encoding: m.wire.unwant, onmessage: onwireunwant },
        { encoding: m.wire.bitfield, onmessage: onwirebitfield },
        { encoding: m.wire.range, onmessage: onwirerange },
        { encoding: m.wire.extension, onmessage: onwireextension }
      ],
      onopen: onwireopen,
      onclose: onwireclose,
      ondrain: onwiredrain,
      ondestroy: onwiredestroy
    })

    if (channel === null) return onnochannel()

    const peer = new Peer(replicator, protomux, channel, this.inflightRange)
    const stream = protomux.stream

    peer.channel.open({
      seeks: true,
      capability: caps.replicate(stream.isInitiator, this.core.key, stream.handshakeHash)
    })

    return true

    function onnochannel() {
      return false
    }
  }

  _onpeerupdate(added, peer) {
    const name = added ? 'peer-add' : 'peer-remove'
    const sessions = this.core.monitors

    for (let i = sessions.length - 1; i >= 0; i--) {
      sessions[i].emit(name, peer)

      if (added) {
        for (const ext of sessions[i].extensions.values()) {
          peer.extensions.set(ext.name, ext)
        }
      }
    }
  }

  _ondownload(index, byteLength, from, req) {
    const sessions = this.core.monitors

    for (let i = sessions.length - 1; i >= 0; i--) {
      const s = sessions[i]
      s.emit('download', index, byteLength - s.padding, from, req)
    }
  }

  _onupload(index, byteLength, from) {
    const sessions = this.core.monitors

    for (let i = sessions.length - 1; i >= 0; i--) {
      const s = sessions[i]
      s.emit('upload', index, byteLength - s.padding, from)
    }
  }

  _oninvaliddata(err, req, res, from) {
    const sessions = this.core.monitors

    this.stats.invalidData++
    from.stats.invalidData++
    for (let i = 0; i < sessions.length; i++) {
      sessions[i].emit('verification-error', err, req, res, from)
    }
  }

  _oninvalidrequest(err, req, from) {
    const sessions = this.core.monitors

    from.stats.invalidRequests++
    this.stats.invalidRequests++
    for (let i = 0; i < sessions.length; i++) {
      sessions[i].emit('invalid-request', err, req, from)
    }
  }
}

function removeHotswap(block) {
  if (block.hotswap === null) return false
  block.hotswap.ref.remove(block)
  return true
}

function removeInflight(inf, req) {
  const i = inf.indexOf(req)
  if (i === -1) return false
  if (i < inf.length - 1) inf[i] = inf.pop()
  else inf.pop()
  return true
}

function toLength(start, end) {
  return end === -1 ? -1 : end < start ? 0 : end - start
}

function clampRange(core, r) {
  if (r.blocks === null) {
    const start = core.bitfield.firstUnset(r.start)

    if (r.end === -1) r.start = start === -1 ? core.state.length : start
    else if (start === -1 || start >= r.end) r.start = r.end
    else {
      r.start = start

      const end = core.bitfield.lastUnset(r.end - 1)

      if (end === -1 || start >= end + 1) r.end = r.start
      else r.end = end + 1
    }
  } else {
    while (r.start < r.end && core.bitfield.get(r.blocks[r.start])) r.start++
    while (r.start < r.end && core.bitfield.get(r.blocks[r.end - 1])) r.end--
  }
}

function onrequesttimeout(req) {
  if (req.context) req.context.detach(req, REQUEST_TIMEOUT())
}

function destroyRequestTimeout(req) {
  if (req.timeout !== null) {
    clearTimeout(req.timeout)
    req.timeout = null
  }
}

function isCriticalError(err) {
  // TODO: expose .critical or similar on the hypercore errors that are critical (if all not are)
  return err.name === 'HypercoreError'
}

function onwireopen(m, c) {
  return c.userData.onopen(m)
}

function onwireclose(isRemote, c) {
  return c.userData.onclose(isRemote)
}

function onwiredestroy(c) {
  c.userData.replicator.onpeerdestroy()
}

function onwiredrain(c) {
  return c.userData.ondrain()
}

function onwiresync(m, c) {
  incrementRx(c.userData.stats.wireSync, c.userData.replicator.stats.wireSync)
  return c.userData.onsync(m)
}

function onwirerequest(m, c) {
  incrementRx(c.userData.stats.wireRequest, c.userData.replicator.stats.wireRequest)
  return c.userData.onrequest(m)
}

function onwirecancel(m, c) {
  incrementRx(c.userData.stats.wireCancel, c.userData.replicator.stats.wireCancel)
  return c.userData.oncancel(m)
}

function onwiredata(m, c) {
  incrementRx(c.userData.stats.wireData, c.userData.replicator.stats.wireData)
  return c.userData.ondata(m)
}

function onwirenodata(m, c) {
  return c.userData.onnodata(m)
}

function onwirewant(m, c) {
  incrementRx(c.userData.stats.wireWant, c.userData.replicator.stats.wireWant)
  return c.userData.onwant(m)
}

function onwireunwant(m, c) {
  return c.userData.onunwant(m)
}

function onwirebitfield(m, c) {
  incrementRx(c.userData.stats.wireBitfield, c.userData.replicator.stats.wireBitfield)
  return c.userData.onbitfield(m)
}

function onwirerange(m, c) {
  incrementRx(c.userData.stats.wireRange, c.userData.replicator.stats.wireRange)
  return c.userData.onrange(m)
}

function onwireextension(m, c) {
  incrementRx(c.userData.stats.wireExtension, c.userData.replicator.stats.wireExtension)
  return c.userData.onextension(m)
}

function setNotDownloadingLater(repl, session) {
  repl._setDownloadingNow(false, session)
}

function isBlockRequest(req) {
  return req !== null && req.block !== null
}

function isUpgradeRequest(req) {
  return req !== null && req.upgrade !== null
}

function incrementTx(stats1, stats2) {
  stats1.tx++
  stats2.tx++
}

function incrementRx(stats1, stats2) {
  stats1.rx++
  stats2.rx++
}

function noop() {}

function backoff(times) {
  const sleep = times < 2 ? 200 : times < 5 ? 500 : times < 40 ? 1000 : 5000
  return new Promise((resolve) => setTimeout(resolve, sleep))
}
const crypto = require('hypercore-crypto')
const b4a = require('b4a')
const assert = require('nanoassert')
const flat = require('flat-tree')
const quickbit = require('quickbit-universal')
const HypercoreStorage = require('hypercore-storage')

const {
  INVALID_OPERATION,
  INVALID_SIGNATURE,
  ASSERTION,
  SESSION_CLOSED,
  SESSION_NOT_WRITABLE
} = require('hypercore-errors')

const Mutex = require('./mutex')
const Bitfield = require('./bitfield')
const { MerkleTree, MerkleTreeBatch } = require('./merkle-tree')

class SessionState {
  constructor(core, parent, storage, treeInfo, name) {
    this.core = core
    this.index = this.core.sessionStates.push(this) - 1

    this.storage = storage
    this.name = name
    this.sessions = []

    // small hack to close old storages as late as possible.
    // TODO: add a read lock so we can avoid that
    this.lingers = null

    this.parent = parent
    this.atomized = null
    this.mutex = new Mutex()

    // merkle state
    this.roots = treeInfo.roots.length ? treeInfo.roots : []
    this.fork = treeInfo.fork || 0
    this.length = this.roots.length ? MerkleTree.span(this.roots) / 2 : treeInfo.length
    this.byteLength = MerkleTree.size(this.roots)
    this.prologue = treeInfo.prologue || null
    this.signature = treeInfo.signature || null

    this.snapshotCompatLength = this.isSnapshot()
      ? Math.min(this.length, this.core.state.length)
      : -1
    this.lastTruncation = null

    this.active = 0

    this._activeTx = null
    this._pendingBitfield = null

    this.ref()
  }

  static RECOVERY_PEERS = null

  isSnapshot() {
    return this.storage.snapshotted
  }

  isDefault() {
    return this.core.state === this || this.isAtomicDefault()
  }

  isAtomicDefault() {
    return !!this.storage.atom && !!this.parent && this.parent.isDefault()
  }

  createTreeBatch() {
    return new MerkleTreeBatch(this)
  }

  addSession(s) {
    if (s._stateIndex !== -1) return
    s._stateIndex = this.sessions.push(s) - 1
    if (s.weak === false) this.core.activeSessions++
  }

  removeSession(s) {
    if (s._stateIndex === -1) return
    const head = this.sessions.pop()
    if (head !== s) this.sessions[(head._stateIndex = s._stateIndex)] = head
    s._stateIndex = -1
    if (s.weak === false) this.core.activeSessions--
    this.core.checkIfIdle()
  }

  flushedLength() {
    if (this.isDefault() || this.isSnapshot()) return this.length
    const deps = this.storage.dependencies
    if (deps.length) return deps[deps.length - 1].length
    return 0
  }

  signedLength() {
    const l = Math.min(this.flushedLength(), this.core.state.length)
    return this.isSnapshot() && l > this.snapshotCompatLength ? this.snapshotCompatLength : l
  }

  unref() {
    if (--this.active > 0) return
    this.close().catch(noop) // technically async, but only for the last db session
  }

  ref() {
    this.active++
    return this
  }

  hash() {
    return MerkleTree.hash(this)
  }

  setRoots(roots) {
    this.roots = roots
    this.length = MerkleTree.span(roots) / 2
    this.byteLength = MerkleTree.size(roots)
  }

  get encryptionFork() {
    return this.core.header.tree.fork
  }

  async _updateSnapshotStorage(storage) {
    if (!this.atomized || !this.atomized.flushing) return this.treeInfo()
    while (this.atomized.flushing) await this.atomized.flushed()

    let rx = storage.read()
    const headPromise = rx.getHead()
    const authPromise = rx.getAuth()
    const depPromise = rx.getDependency()

    rx.tryFlush()
    const [head, auth, dep] = await Promise.all([headPromise, authPromise, depPromise])

    storage.setDependencyHead(dep)

    const fork = head ? head.fork : 0
    const length = head ? head.length : 0

    rx = storage.read()
    const rootPromises = []
    for (const r of flat.fullRoots(length * 2)) {
      rootPromises.push(rx.getTreeNode(r))
    }

    rx.tryFlush()

    const roots = await Promise.all(rootPromises)

    // dbl check if we are hitting an regression from earler
    for (const root of roots) {
      if (root === null) {
        throw new Error(
          'Bad snapshot from atomized session, id = ' +
            this.core.id +
            ' length = ' +
            length +
            ' fork = ' +
            fork
        )
      }
    }

    return {
      fork,
      roots,
      length,
      prologue: auth.manifest && auth.manifest.prologue,
      signature: head && head.signature
    }
  }

  treeInfo() {
    return {
      fork: this.fork,
      roots: this.roots.slice(),
      length: this.length,
      prologue: this.prologue,
      signature: this.signature
    }
  }

  async close() {
    if (this.index === -1) return

    this.active = 0
    this.mutex.destroy(new Error('Closed')).catch(noop)
    if (this.parent && this.parent.atomized) this.parent.atomized = null

    const closing = this.storage.close()

    const head = this.core.sessionStates.pop()
    if (head !== this) this.core.sessionStates[(head.index = this.index)] = head

    this.index = -1
    this.core.checkIfIdle()

    if (this.lingers !== null) {
      for (const storage of this.lingers) await storage.close()
    }

    return closing
  }

  async snapshot() {
    const storage = this.storage.snapshot()
    const treeInfo = await this._updateSnapshotStorage(storage)

    return new SessionState(this.core, null, storage, treeInfo, this.name)
  }

  updateDependency(tx, length) {
    const dependency = updateDependency(this, length, false)
    if (dependency) tx.setDependency(dependency)
    return dependency
  }

  updateGroupIndex(tx, tree) {
    const { pointer } = this.core.header.group

    tx.deleteGroupUpdate(pointer, this.core.header.tree.timestamp)
    tx.putGroupUpdate(pointer, tree.timestamp, this.core.header.key)
    this.core.ongroupupdate(this.core.header.group.key, {
      key: this.core.key,
      length: tree.length,
      fork: tree.fork
    })
  }

  _clearActiveBatch() {
    this._activeTx = null
  }

  createWriteBatch() {
    assert(!this._activeTx && !this.storage.snapshotted)

    this._activeTx = this.storage.write()
    return this._activeTx
  }

  _unlock() {
    this._clearActiveBatch()
    this.mutex.unlock()
    this.core.checkIfIdle()
  }

  async flush() {
    const tx = this._activeTx
    this._activeTx = null

    try {
      if (!(await tx.flush())) return false
    } finally {
      this._clearActiveBatch()
    }

    this.lastTruncation = null
    return true
  }

  _precommit(state) {
    assert(
      state.parent === this && state.storage.core.dataPointer === this.storage.core.dataPointer,
      'Atomic state must flush to parent'
    )
  }

  async _commit() {
    await this.mutex.lock()

    try {
      const bitfield = this._pendingBitfield
      this._pendingBitfield = null
      this.lastTruncation = null
      await this.parent._oncommit(this, bitfield)
    } finally {
      this.mutex.unlock()
    }
  }

  async _oncommit(src, bitfield) {
    await this.mutex.lock()

    try {
      const currLength = this.length

      // load dependency into memory
      const rx = this.storage.read()
      const dependencyPromise = rx.getDependency()

      rx.tryFlush()

      const dependency = await dependencyPromise

      this.fork = src.fork
      this.length = src.length
      this.byteLength = src.byteLength
      this.roots = src.roots.slice()
      this.signature = src.signature

      const tree = {
        fork: this.fork,
        length: this.length,
        rootHash: this.hash(),
        signature: this.signature,
        timestamp: Date.now()
      }

      if (this.isDefault() && this.core.header.group) {
        const tx = await this.createWriteBatch()
        this.updateGroupIndex(tx, tree)
        await tx.flush()
      }

      if (dependency) this.storage.setDependencyHead(dependency)

      const b = bitfield

      if (b && b.truncated && b.start < currLength) {
        this.ontruncate(tree, b.start, currLength, true)
        if (!b || b.appends === 0) return
      }

      const append = b ? { start: b.start, length: b.appends, drop: false } : null

      this.onappend(tree, append, true)

      if (this.core.hintsChanged && this.isDefault()) await this.core.flushHints()
    } finally {
      this._unlock()
    }
  }

  async setUserData(key, value) {
    await this.mutex.lock()

    try {
      const tx = this.createWriteBatch()
      tx.putUserData(key, value)

      return await this.flush()
    } finally {
      this._unlock()
    }
  }

  async _verifyBlock(batch, bitfield, value, manifest, from) {
    await this.mutex.lock()

    try {
      if (batch.upgraded && batch.length <= this.length) {
        await batch.downgrade()
      }

      if (!batch.commitable()) {
        return false
      }

      if (this.core.preupdate !== null) {
        await this.core.preupdate(batch, this.core.header.key)
      }

      const tx = this.createWriteBatch()

      if (bitfield) {
        tx.putBlock(bitfield.start, value)
      }

      if (bitfield && this.isDefault()) {
        await storeBitfieldRange(this.storage, tx, bitfield.start, bitfield.start + 1, true)
      }

      if (manifest) this.core._setManifest(tx, manifest, null)

      assert(batch.commitable(), 'Should still be commitable')
      batch.commit(tx)

      const head = {
        fork: batch.fork,
        length: batch.length,
        rootHash: batch.hash(),
        signature: batch.signature,
        timestamp: Date.now()
      }

      if (batch.upgraded) {
        tx.setHead(head)

        if (this.core.header.group) {
          this.updateGroupIndex(tx, head)
        }
      }

      const flushed = await this.flush()

      if (batch.upgraded) {
        this.roots = batch.roots
        this.length = batch.length
        this.byteLength = batch.byteLength
        this.fork = batch.fork
        this.signature = batch.signature

        this.onappend(head, bitfield, flushed)
      }

      if (this.core.hintsChanged && this.isDefault()) await this.core.flushHints()
    } finally {
      this._unlock()
    }

    return true
  }

  async truncate(length, fork, { signature, keyPair } = {}) {
    if (!keyPair && this.isDefault()) keyPair = this.core.header.keyPair

    await this.mutex.lock()

    try {
      if (this.prologue && length < this.prologue.length) {
        throw INVALID_OPERATION('Truncation breaks prologue', this.core.discoveryKey)
      }
      if (length > this.length) {
        throw INVALID_OPERATION(
          'Not a truncation, ' + length + ' must be less or equal to ' + this.length,
          this.core.discoveryKey
        )
      }

      const batch = this.createTreeBatch()
      await MerkleTree.truncate(this, length, batch, fork)

      if (signature) {
        batch.signature = signature
        this.core._verifyBatchUpgrade(batch)
      } else if (keyPair) {
        batch.signature = this.core.verifier.sign(batch, keyPair)
      }

      const tx = this.createWriteBatch()

      // upsert compat manifest
      if (this.core.verifier === null && keyPair) this.core._setManifest(tx, null, keyPair)

      const { dependency, tree, roots } = await this._truncate(tx, batch)

      for (const sessionState of this.core.sessionStates) {
        if (
          sessionState.isSnapshot() &&
          sessionState.name === this.name &&
          length < sessionState.snapshotCompatLength
        ) {
          sessionState.snapshotCompatLength = length
        }
      }

      const flushed = await this.flush()

      this.fork = tree.fork
      this.length = tree.length
      this.byteLength = MerkleTree.size(roots)
      this.roots = roots
      this.signature = tree.signature

      if (dependency) this.storage.setDependencyHead(dependency)

      this.ontruncate(tree, tree.length, batch.treeLength, flushed)

      if (this.core.hintsChanged && this.isDefault()) await this.core.flushHints()
    } finally {
      this._unlock()
    }
  }

  async reorg(batch) {
    await this.mutex.lock()

    const storage = this.createWriteBatch()

    try {
      if (!batch.commitable()) return false

      const { dependency, tree } = await this._truncate(storage, batch)

      const flushed = await this.flush()

      this.fork = batch.fork
      this.length = batch.length
      this.byteLength = batch.byteLength
      this.roots = batch.roots
      this.signature = batch.signature

      if (dependency) this.storage.setDependencyHead(dependency)

      this.ontruncate(tree, batch.ancestors, batch.treeLength, flushed)

      if (this.core.hintsChanged && this.isDefault()) await this.core.flushHints()
    } finally {
      this._unlock()
    }
  }

  async _truncate(storage, batch) {
    storage.deleteBlockRange(batch.ancestors, batch.treeLength)

    assert(batch.commitable(), 'Batch must be commitable')

    const tree = {
      fork: batch.fork,
      length: batch.length,
      rootHash: batch.hash(),
      signature: batch.signature,
      timestamp: Date.now()
    }

    if (this.isDefault() && this.core.header.group) {
      this.updateGroupIndex(storage, tree)
    }

    storage.setHead(tree)
    batch.commit(storage)

    const truncated = batch.length < this.flushedLength()
    const dependency = truncated ? updateDependency(this, batch.length, true) : null

    if (dependency) storage.setDependency(dependency)

    if (this.isDefault()) {
      await storeBitfieldRange(this.storage, storage, batch.ancestors, batch.treeLength, false)
      if (batch.ancestors < this.core.header.hints.contiguousLength) {
        this.core.header.hints.remoteContiguousLength = Math.min(
          batch.length,
          this.core.header.hints.remoteContiguousLength
        )
        storage.setHints({
          contiguousLength: batch.ancestors,
          remoteContiguousLength: this.core.header.hints.remoteContiguousLength
        })
      }
    }

    return { dependency, tree, roots: batch.roots }
  }

  async clear(start, end, cleared) {
    await this.mutex.lock()

    try {
      const tx = this.createWriteBatch()

      const ite = flat.iterator(start * 2)
      while (!isRootIndex(ite.index, this.roots)) {
        const a = ite.index
        const b = ite.sibling()
        ite.parent()

        const [left, right] = flat.spans(b)
        const s = left / 2
        const e = right / 2 + 1
        const has = s <= start && e <= end ? false : this.core.bitfield.hasSet(s, e - s)
        if (has) break

        tx.deleteTreeNode(a)
        tx.deleteTreeNode(b)
      }

      if (this.isDefault()) {
        await storeBitfieldRange(this.storage, tx, start, end, false)
        if (start < this.core.header.hints.contiguousLength) {
          tx.setHints({
            contiguousLength: start,
            remoteContiguousLength: this.core.header.hints.remoteContiguousLength
          })
        }
      }

      if (end - start === 1) tx.deleteBlock(start)
      else tx.deleteBlockRange(start, end)

      const dependency = start < this.flushedLength() ? updateDependency(this, start, true) : null

      const flushed = await this.flush()

      if (dependency) this.storage.setDependencyHead(dependency)

      // todo: atomic event handle
      if (this.isDefault() && flushed) {
        const length = end - start

        this.core.updateContiguousLength({ start, length, drop: true })
        this.core._setBitfieldRanges(start, end, false)
        this.core.replicator.onhave(start, length, true)

        if (this.core.hintsChanged) await this.core.flushHints()
      }
    } finally {
      this._unlock()
    }
  }

  // simple tool to wait for other peers to catch us up
  async waitForRecovery() {
    const length = this.length
    const replicator = this.core.replicator

    while (!this.closing) {
      let peers = getRecoveryPeers(replicator.peers)

      if (peers.length < 1) {
        await new Promise((resolve) => setTimeout(resolve, 5000))
        continue
      }

      let restart = false

      for (let i = 0; i < 32; i++) {
        await new Promise((resolve) => setTimeout(resolve, 250))

        peers = getRecoveryPeers(replicator.peers)

        if (this.closing || !validateRecoveryPeers(peers, this.length)) {
          restart = true
          break
        }
      }

      if (restart) continue

      if (this.length !== length) {
        throw SESSION_NOT_WRITABLE('core recovered during append')
      }

      this.core.header.hints.recovering = 0
      this.core.hintsChanged = true

      await this.mutex.lock()
      try {
        await this.core.flushHints()
        return
      } finally {
        this.mutex.unlock()
      }
    }

    throw SESSION_CLOSED('Cannot recover a closed session', this.discoveryKey)
  }

  async append(values, { signature, keyPair, preappend, postappend, maxLength = -1 } = {}) {
    if (!keyPair && this.isDefault()) keyPair = this.core.header.keyPair

    if (this.isDefault() && this.core.header.hints.recovering) await this.waitForRecovery()

    await this.mutex.lock()

    try {
      if (maxLength >= 0 && this.length + values.length > maxLength) {
        return { length: this.length, byteLength: this.byteLength }
      }

      const tx = this.createWriteBatch()

      // upsert compat manifest
      if (this.core.verifier === null && keyPair) this.core._setManifest(tx, null, keyPair)

      if (preappend) await preappend(values)

      if (!values.length) {
        await this.flush()
        return { length: this.length, byteLength: this.byteLength }
      }

      const batch = this.createTreeBatch()
      for (const val of values) batch.append(val)

      // only multisig can have prologue so signature is always present
      if (this.prologue && batch.length < this.prologue.length) {
        throw INVALID_OPERATION('Append is not consistent with prologue', this.core.discoveryKey)
      }

      if (signature) {
        batch.signature = signature
        this.core._verifyBatchUpgrade(batch)
      } else if (keyPair) {
        batch.signature = this.core.verifier.sign(batch, keyPair)
      }

      batch.commit(tx)

      const tree = {
        fork: batch.fork,
        length: batch.length,
        rootHash: batch.hash(),
        signature: batch.signature,
        timestamp: Date.now()
      }

      tx.setHead(tree)

      if (this.isDefault()) {
        await storeBitfieldRange(this.storage, tx, batch.ancestors, batch.length, true)
        if (this.length === this.core.header.hints.contiguousLength) {
          tx.setHints({
            contiguousLength: this.length + values.length,
            remoteContiguousLength: this.core.header.hints.remoteContiguousLength
          })
        }

        if (this.core.header.group) {
          this.updateGroupIndex(tx, tree)
        }
      }

      for (let i = 0; i < values.length; i++) {
        tx.putBlock(this.length + i, values[i])
      }

      const bitfield = {
        drop: false,
        start: batch.ancestors,
        length: values.length
      }

      const flushed = await this.flush()

      this.fork = batch.fork
      this.roots = batch.roots
      this.length = batch.length
      this.byteLength = batch.byteLength
      this.signature = batch.signature

      if (postappend) await postappend(values)

      this.onappend(tree, bitfield, flushed)

      if (this.core.hintsChanged && this.isDefault()) await this.core.flushHints()

      return { length: this.length, byteLength: this.byteLength }
    } finally {
      this._unlock()
    }
  }

  onappend(tree, bitfield, flushed) {
    if (!flushed) this._updateBitfield(bitfield)
    else if (this.isDefault()) this.core.onappend(tree, bitfield)

    for (let i = this.sessions.length - 1; i >= 0; i--) {
      this.sessions[i].emit('append')
    }
  }

  ontruncate(tree, to, from, flushed) {
    const bitfield = { start: to, length: from - to, drop: true }

    this.lastTruncation = { from, to }

    if (!flushed) this._updateBitfield(bitfield)
    else if (this.isDefault()) this.core.ontruncate(tree, bitfield)

    for (const sessionState of this.core.sessionStates) {
      if (
        sessionState.isSnapshot() &&
        sessionState.name === this.name &&
        to < sessionState.snapshotCompatLength
      ) {
        sessionState.snapshotCompatLength = to
      }
    }

    for (let i = this.sessions.length - 1; i >= 0; i--) {
      this.sessions[i].emit('truncate', to, tree.fork)
    }
  }

  _updateBitfield(bitfield, flushed) {
    if (!bitfield) return

    const p = this._pendingBitfield
    const b = bitfield

    if (b.drop) {
      // truncation must be from end
      if (p && b.start + b.length !== p.start + p.appends) {
        throw INVALID_OPERATION('Atomic truncations must be contiguous', this.core.discoveryKey)
      }

      // actual truncation
      if (p === null || b.start < p.start) {
        this._pendingBitfield = { truncated: true, start: b.start, appends: 0 }
        return
      }

      // just clearing batch data
      p.appends = b.start - p.start

      // we cleared the current batch
      if (p.appends === 0) this._pendingBitfield = null

      return
    }

    if (p === null) {
      this._pendingBitfield = { truncated: false, start: b.start, appends: b.length }
      return
    }

    if (b.start !== p.start + p.appends) {
      throw INVALID_OPERATION('Atomic operations must be contiguous', this.core.discoveryKey)
    }

    p.appends += b.length
  }

  async catchup(length) {
    assert(!this.isDefault(), 'Cannot catchup signed state') // TODO: make this check better

    await this.mutex.lock()

    try {
      const origLength = this.length

      let sharedLength = 0
      for (let i = this.storage.dependencies.length - 1; i >= 0; i--) {
        const dep = this.storage.dependencies[i]
        if (dep.dataPointer === this.core.state.storage.core.dataPointer) {
          sharedLength = dep.length
          break
        }
      }

      const tx = this.createWriteBatch()
      const rx = this.core.state.storage.read()
      const rootPromises = []

      for (const root of flat.fullRoots(length * 2)) {
        rootPromises.push(rx.getTreeNode(root))
      }

      rx.tryFlush()

      const roots = await Promise.all(rootPromises)
      const truncating = sharedLength < origLength

      for (const node of roots) {
        if (node === null) {
          throw INVALID_OPERATION(
            'Invalid catchup length, tree nodes not available',
            this.core.discoveryKey
          )
        }
      }

      const fork = truncating ? this.fork + 1 : this.fork

      // overwrite it atm, TODO: keep what we can connect to the tree
      tx.deleteBlockRange(0, -1)
      tx.deleteTreeNodeRange(0, -1)
      tx.deleteBitfieldPageRange(0, -1)

      const tree = {
        fork,
        length,
        rootHash: crypto.tree(roots),
        signature: null,
        timestamp: Date.now()
      }

      // never default state so do not update group index
      tx.setHead(tree)

      // prop a better way to do this
      const dep = updateDependency(this, sharedLength, true)
      dep.length = length

      tx.setDependency(dep)

      const flushed = await this.flush()

      this.storage.setDependencyHead(dep)

      this.fork = tree.fork
      this.roots = roots
      this.length = tree.length
      this.byteLength = MerkleTree.size(roots)

      if (truncating) this.ontruncate(tree, sharedLength, origLength, flushed)
      if (sharedLength < length) this.onappend(tree, null, flushed)

      if (this.core.hintsChanged && this.isDefault()) await this.core.flushHints()
    } finally {
      this._unlock()
    }
  }

  async _overwrite(source, fork, length, treeLength, signature) {
    const blockPromises = []
    const treePromises = []
    const rootPromises = []

    const rx = source.storage.read()

    for (const root of flat.fullRoots(length * 2)) {
      rootPromises.push(rx.getTreeNode(root))
    }

    for (const index of flat.patch(treeLength * 2, length * 2)) {
      treePromises.push(rx.getTreeNode(index))
    }

    for (let i = treeLength; i < length; i++) {
      treePromises.push(rx.getTreeNode(i * 2))
      treePromises.push(rx.getTreeNode(i * 2 + 1))
      blockPromises.push(rx.getBlock(i))
    }

    rx.tryFlush()

    const [blocks, nodes, roots] = await Promise.all([
      Promise.all(blockPromises),
      Promise.all(treePromises),
      Promise.all(rootPromises)
    ])

    if (this.core.destroyed) throw new Error('Core destroyed')

    if (signature) {
      const batch = this.createTreeBatch()
      batch.roots = roots
      batch.length = length

      if (!this.core.verifier.verify(batch, signature)) {
        throw INVALID_SIGNATURE(
          'Signature is not valid over committed tree',
          this.core.discoveryKey
        )
      }
    }

    const tx = this.createWriteBatch()

    // truncate existing tree
    if (treeLength < this.length) {
      tx.deleteBlockRange(treeLength, this.length)
    }

    for (const root of roots) tx.putTreeNode(root)

    // no nodes will be copied in shallow mode
    for (const node of nodes) {
      if (node !== null) tx.putTreeNode(node)
    }

    for (let i = 0; i < blocks.length; i++) {
      assert(blocks[i] !== null, 'has block')
      tx.putBlock(i + treeLength, blocks[i])
    }

    const totalLength = Math.max(length, this.length)

    if (totalLength > treeLength) {
      const firstPage = getBitfieldPage(treeLength)
      const lastPage = getBitfieldPage(totalLength - 1)

      const srx = this.storage.read()
      const bitfieldPagePromise = srx.getBitfieldPage(firstPage)
      srx.tryFlush()

      const bitfieldPage = await bitfieldPagePromise

      let index = treeLength

      for (let i = firstPage; i <= lastPage; i++) {
        const page = b4a.alloc(Bitfield.BYTES_PER_PAGE)
        tx.putBitfieldPage(i, page)

        // copy existing bits in
        if (i === firstPage && bitfieldPage) page.set(bitfieldPage)

        if (index < length) {
          index = fillBitfieldPage(page, index, length, i, true)
          if (index < length) continue
        }

        if (index < this.length) {
          index = fillBitfieldPage(page, index, this.length, i, false)
        }
      }
    }

    const tree = {
      fork,
      length,
      rootHash: crypto.tree(roots),
      signature,
      timestamp: Date.now()
    }

    if (this.isDefault() && this.core.header.group) {
      this.updateGroupIndex(tx, tree)
    }

    const upgraded = treeLength < this.length || this.length < length || tree.fork !== this.fork

    if (upgraded) tx.setHead(tree)

    const flushed = await this.flush()

    this.fork = tree.fork
    this.roots = roots
    this.length = length
    this.byteLength = MerkleTree.size(roots)
    this.signature = signature

    return { tree, flushed }
  }

  static isParent(state, parent) {
    // atomic batches have parent set
    if (state.parent && state.parent === parent) return true

    return HypercoreStorage.isParentStorage(state.storage, parent.storage)
  }

  async commit(state, { signature, keyPair, length = state.length, treeLength = -1 } = {}) {
    assert(
      this.isDefault() || (this.parent && this.parent.isDefault()),
      'Can only commit into default state'
    )

    assert(SessionState.isParent(state, this), 'Cannot commit across cores')

    if (this.core._repairMode) {
      throw ASSERTION('Cannot commit while repair mode is on')
    }

    if (this.core.header.hints.recovering) await this.waitForRecovery()

    let srcLocked = false
    await this.mutex.lock()

    try {
      await state.mutex.lock()
      srcLocked = true

      if (treeLength === -1) treeLength = state.flushedLength()

      if (!(await this.core._validateCommit(state, treeLength))) return null
      if (this.length > length) return null

      if (this.length < length && !signature) {
        if (!keyPair) keyPair = this.core.header.keyPair
        const batch = state.createTreeBatch()
        if (length !== batch.length) await batch.restore(length)
        signature = this.core.verifier.sign(batch, keyPair)
      }

      const { tree, flushed } = await this._overwrite(
        state,
        this.fork,
        length,
        treeLength,
        signature
      )

      // gc blocks from source
      if (treeLength < length) {
        const tx = state.createWriteBatch()

        tx.deleteBlockRange(treeLength, length)
        const dependency = state.updateDependency(tx, length)

        await state.flush(tx)

        if (dependency) state.storage.setDependencyHead(dependency)
      }

      const bitfield = { start: treeLength, length: length - treeLength, drop: false }
      this.onappend(tree, bitfield, flushed)

      return {
        length: this.length,
        byteLength: this.byteLength
      }
    } finally {
      if (srcLocked) state._unlock()
      this._unlock()
    }
  }

  async _getTreeHeadAt(length) {
    if (length === null) return this.treeInfo()

    const head = getDefaultTree()

    head.length = length

    const roots = await MerkleTree.getRootsFromStorage(this.storage, length)
    const rootHash = crypto.tree(roots)

    head.fork = this.fork
    head.rootHash = rootHash

    if (length === this.length) head.signature = this.signature

    return head
  }

  _moveToCore(core, truncated, appended) {
    const head = this.core.sessionStates.pop()
    if (head !== this) this.core.sessionStates[(head.index = this.index)] = head

    this.core = core
    this.index = this.core.sessionStates.push(this) - 1

    for (let i = this.sessions.length - 1; i >= 0; i--) {
      const s = this.sessions[i]
      const manifest = s.manifest
      s.transferSession(this.core)
      if (!manifest && s.manifest) s.emit('manifest')
      if (truncated) s.emit('truncate', truncated.to, truncated.fork)
      if (appended) s.emit('append')
    }
  }

  async moveTo(core, length) {
    const state = core.state

    await this.mutex.lock()

    try {
      // if (state.storage && (await state.storage.resumeSession(this.name)) !== null) {
      //   throw STORAGE_CONFLICT('Batch has already been created')
      // }

      const treeLength = this.length

      let truncated = null
      let appended = false

      if (!this.isSnapshot()) {
        if (this.lingers === null) this.lingers = []
        this.lingers.push(this.storage)

        const resumed = await state.storage.resumeSession(this.name)

        const truncation = length < this.length ? await truncateAndFlush(this, length) : null
        const treeInfo = truncation
          ? truncation.tree
          : resumed
            ? null
            : await state._getTreeHeadAt(this.length)

        const fork = truncation ? this.fork + 1 : this.fork

        // todo: validate treeInfo

        let storage = null

        if (resumed) {
          storage = resumed
        } else {
          treeInfo.fork = fork
          storage = await state.storage.createSession(this.name, treeInfo)
        }

        const roots = await MerkleTree.getRootsFromStorage(storage, length)

        this.storage = storage
        this.prologue = state.prologue
        this.fork = fork
        this.length = length
        this.byteLength = MerkleTree.size(roots)
        this.roots = roots

        if (truncation) {
          const { dependency } = truncation

          if (dependency) this.storage.setDependencyHead(dependency)
          truncated = { to: treeLength, fork }
        }

        if (this.length > treeLength) {
          appended = true
        }
      }

      for (let i = this.core.sessionStates.length - 1; i >= 0; i--) {
        const state = this.core.sessionStates[i]
        if (state === this) continue
        if (state.name === this.name) state._moveToCore(core.core)
      }

      this._moveToCore(core.core, truncated, appended)
    } finally {
      this._unlock()
    }
  }

  async createSession(name, overwrite, atom) {
    await this.mutex.lock()

    try {
      let storage = null
      let treeInfo = null

      if (!atom && !overwrite && this.storage) {
        storage = await this.storage.resumeSession(name)

        if (storage !== null) treeInfo = (await getCoreHead(storage)) || getDefaultTree()
      }

      const length = treeInfo ? treeInfo.length : this.length

      if (storage === null) {
        treeInfo = await this._getTreeHeadAt(length)

        if (atom) {
          storage = await this.storage.createAtomicSession(atom, treeInfo)
        } else {
          storage = await this.storage.createSession(name, treeInfo)
        }
      }

      if (this.atomized && atom) {
        throw new Error('Session already atomized')
      }

      const head = {
        fork: this.fork,
        roots:
          length === this.length
            ? this.roots.slice()
            : await MerkleTree.getRootsFromStorage(storage, length),
        length,
        prologue: this.prologue,
        signature: length === this.length ? this.signature : null
      }

      const state = new SessionState(
        this.core,
        atom ? this : null,
        storage,
        head,
        atom ? this.name : name
      )

      if (atom) {
        this.atomized = atom
        atom.preflush(this._precommit.bind(this, state))
        atom.onflush(state._commit.bind(state))
      }

      return state
    } finally {
      this._unlock()
    }
  }
}

module.exports = SessionState

function noop() {}

function getBitfieldPage(index) {
  return Math.floor(index / Bitfield.BITS_PER_PAGE)
}

function fillBitfieldPage(page, start, end, pageIndex, value) {
  const offset = pageIndex * Bitfield.BITS_PER_PAGE
  const max = offset + Bitfield.BITS_PER_PAGE

  const index = max < end ? max : end

  const from = start - offset
  const to = index - offset

  quickbit.fill(page, value, from, to)

  return index
}

async function storeBitfieldRange(storage, tx, from, to, value) {
  if (from >= to) return

  const firstPage = getBitfieldPage(from)
  const lastPage = getBitfieldPage(to - 1)

  let index = from

  const rx = storage.read()

  const promises = []
  for (let i = firstPage; i <= lastPage; i++) {
    promises.push(rx.getBitfieldPage(i))
  }

  rx.tryFlush()

  const pages = await Promise.all(promises)
  const cnt = lastPage - firstPage + 1

  for (let i = 0; i < cnt; i++) {
    const pageIndex = i + firstPage
    if (!pages[i]) pages[i] = b4a.alloc(Bitfield.BYTES_PER_PAGE)

    index = fillBitfieldPage(pages[i], index, to, pageIndex, value)
    tx.putBitfieldPage(pageIndex, pages[i])
  }
}

async function truncateAndFlush(s, length) {
  const batch = s.createTreeBatch()
  await MerkleTree.truncate(s, length, batch, s.fork)
  const tx = s.createWriteBatch()

  const info = await s._truncate(tx, batch)
  const flushed = await s.flush()

  return {
    tree: info.tree,
    roots: info.roots,
    dependency: info.dependency,
    flushed
  }
}

function updateDependency(state, length, truncated) {
  const i = state.storage.findDependencyIndex(length, truncated)
  if (i === -1) return null // skip default state and overlays of default

  return {
    dataPointer: state.storage.dependencies[i].dataPointer,
    length
  }
}

function getDefaultTree() {
  return {
    fork: 0,
    length: 0,
    rootHash: null,
    signature: null
  }
}

function getCoreHead(storage) {
  const b = storage.read()
  const p = b.getHead()
  b.tryFlush()
  return p
}

function isRootIndex(index, roots) {
  for (const node of roots) {
    if (node.index === index) return true
  }

  return false
}

function getRecoveryPeers(peers) {
  if (!SessionState.RECOVERY_PEERS) return peers

  const actual = []

  for (const p of peers) {
    for (const r of SessionState.RECOVERY_PEERS) {
      if (b4a.equals(p.remotePublicKey, r)) {
        actual.push(p)
        break
      }
    }
  }

  return actual
}

function validateRecoveryPeers(peers, length) {
  if (peers.length < 1) return false

  for (const p of peers) {
    if (p.remoteLength > length) return false
    if (p.remoteLength === 0 && !p.remoteSynced) return false
  }

  return true
}
const { Writable, Readable } = require('streamx')

class ReadStream extends Readable {
  constructor(core, opts = {}) {
    super()

    this.core = core
    this.start = opts.start || 0
    this.end = typeof opts.end === 'number' ? opts.end : -1
    this.snapshot = !opts.live && opts.snapshot !== false
    this.live = this.end === -1 ? !!opts.live : false
    this.wait = typeof opts.wait === 'boolean' ? opts.wait : this.core.wait
    this.timeout = opts.timeout || core.timeout
  }

  _open(cb) {
    this._openP().then(cb, cb)
  }

  _read(cb) {
    this._readP().then(cb, cb)
  }

  async _openP() {
    if (this.end === -1) await this.core.update()
    else await this.core.ready()
    if (this.snapshot && this.end === -1) this.end = this.core.length
  }

  async _readP() {
    const end = this.live ? -1 : this.end === -1 ? this.core.length : this.end
    if (end >= 0 && this.start >= end) {
      this.push(null)
      return
    }

    this.push(await this.core.get(this.start++, { wait: this.wait, timeout: this.timeout }))
  }
}

exports.ReadStream = ReadStream

class WriteStream extends Writable {
  constructor(core) {
    super()
    this.core = core
  }

  _writev(batch, cb) {
    this._writevP(batch).then(cb, cb)
  }

  async _writevP(batch) {
    await this.core.append(batch)
  }
}

exports.WriteStream = WriteStream

class ByteStream extends Readable {
  constructor(core, opts = {}) {
    super()

    this._core = core
    this._index = 0
    this._range = null

    this._byteOffset = opts.byteOffset || 0
    this._byteLength = typeof opts.byteLength === 'number' ? opts.byteLength : -1
    this._prefetch = typeof opts.prefetch === 'number' ? opts.prefetch : 32

    this._applyOffset = this._byteOffset > 0
  }

  _open(cb) {
    this._openp().then(cb, cb)
  }

  _read(cb) {
    this._readp().then(cb, cb)
  }

  async _openp() {
    if (this._byteLength === -1) {
      await this._core.update()
      this._byteLength = Math.max(this._core.byteLength - this._byteOffset, 0)
    }
  }

  async _readp() {
    let data = null

    if (this._byteLength === 0) {
      this.push(null)
      return
    }

    let relativeOffset = 0

    if (this._applyOffset) {
      this._applyOffset = false

      const [block, byteOffset] = await this._core.seek(this._byteOffset)

      this._index = block
      relativeOffset = byteOffset
    }

    this._predownload(this._index + 1)
    data = await this._core.get(this._index++, { valueEncoding: 'binary' })

    if (relativeOffset > 0) data = data.subarray(relativeOffset)

    if (data.byteLength > this._byteLength) data = data.subarray(0, this._byteLength)
    this._byteLength -= data.byteLength

    this.push(data)
    if (this._byteLength === 0) this.push(null)
  }

  _predownload(index) {
    if (this._range) this._range.destroy()
    this._range = this._core.download({ start: index, end: index + this._prefetch, linear: true })
  }

  _destroy(cb) {
    if (this._range) this._range.destroy()
    cb(null)
  }
}

exports.ByteStream = ByteStream
const crypto = require('hypercore-crypto')
const b4a = require('b4a')
const c = require('compact-encoding')
const flat = require('flat-tree')
const { BAD_ARGUMENT } = require('hypercore-errors')
const unslab = require('unslab')

const m = require('./messages')
const multisig = require('./multisig')
const caps = require('./caps')

class Signer {
  constructor(
    manifestHash,
    version,
    index,
    { signature = 'ed25519', publicKey, namespace = caps.DEFAULT_NAMESPACE } = {}
  ) {
    if (!publicKey) throw BAD_ARGUMENT('public key is required for a signer')
    if (signature !== 'ed25519') throw BAD_ARGUMENT('Only Ed25519 signatures are supported')

    this.manifestHash = manifestHash
    this.version = version
    this.signer = index
    this.signature = signature
    this.publicKey = publicKey
    this.namespace = namespace
  }

  _ctx() {
    return this.version === 0 ? this.namespace : this.manifestHash
  }

  verify(batch, signature) {
    return crypto.verify(batch.signable(this._ctx()), signature, this.publicKey)
  }

  sign(batch, keyPair) {
    return crypto.sign(batch.signable(this._ctx()), keyPair.secretKey)
  }
}

class CompatSigner extends Signer {
  constructor(index, signer, legacy) {
    super(null, 0, index, signer)
    this.legacy = legacy
  }

  verify(batch, signature) {
    return crypto.verify(batch.signableCompat(this.legacy), signature, this.publicKey)
  }

  sign(batch, keyPair) {
    return crypto.sign(batch.signableCompat(this.legacy), keyPair.secretKey)
  }
}

module.exports = class Verifier {
  constructor(
    manifestHash,
    manifest,
    { compat = isCompat(manifestHash, manifest), legacy = false } = {}
  ) {
    const self = this

    this.manifestHash = manifestHash
    this.compat = compat || manifest === null
    this.version = this.compat ? 0 : typeof manifest.version === 'number' ? manifest.version : 1
    this.hash = manifest.hash || 'blake2b'
    this.allowPatch = !this.compat && !!manifest.allowPatch
    this.quorum = this.compat ? 1 : defaultQuorum(manifest)

    this.signers = manifest.signers ? manifest.signers.map(createSigner) : []
    this.prologue = this.compat ? null : manifest.prologue || null

    function createSigner(signer, index) {
      return self.compat
        ? new CompatSigner(index, signer, legacy)
        : new Signer(manifestHash, self.version, index, signer)
    }
  }

  _verifyCompat(batch, signature) {
    if (!signature) return false

    if (this.compat || (!this.allowPatch && this.signers.length === 1)) {
      return !!signature && this.signers[0].verify(batch, signature)
    }

    return this._verifyMulti(batch, signature)
  }

  _inflate(signature) {
    if (this.version >= 1) return multisig.inflate(signature)
    const { proofs, patch } = multisig.inflatev0(signature)

    return {
      proofs: proofs.map(proofToVersion1),
      patch
    }
  }

  _verifyMulti(batch, signature) {
    if (!signature || this.quorum === 0) return false

    const { proofs, patch } = this._inflate(signature)
    if (proofs.length < this.quorum) return false

    const tried = new Uint8Array(this.signers.length)
    const nodes = this.allowPatch && patch.length ? toMap(patch) : null

    for (let i = 0; i < this.quorum; i++) {
      const inp = proofs[i]

      let tree = batch

      if (inp.patch && this.allowPatch) {
        tree = batch.clone()

        const upgrade = generateUpgrade(nodes, batch.length, inp.patch)
        const proof = {
          fork: tree.fork,
          block: null,
          hash: null,
          seek: null,
          upgrade,
          manifest: null
        }

        try {
          if (!tree.verifyUpgrade(proof)) return false
        } catch {
          return false
        }
      }

      if (inp.signer >= this.signers.length || tried[inp.signer]) return false
      tried[inp.signer] = 1

      const s = this.signers[inp.signer]
      if (!s.verify(tree, inp.signature)) return false
    }

    return true
  }

  verify(batch, signature) {
    if (this.version === 0) {
      return this._verifyCompat(batch, signature)
    }

    if (this.prologue !== null && batch.length <= this.prologue.length) {
      return batch.length === this.prologue.length && b4a.equals(batch.hash(), this.prologue.hash)
    }

    return this._verifyMulti(batch, signature)
  }

  // TODO: better api for this that is more ... multisig-ey
  sign(batch, keyPair) {
    if (!keyPair || !keyPair.secretKey) throw BAD_ARGUMENT('No key pair was passed')

    for (const s of this.signers) {
      if (b4a.equals(s.publicKey, keyPair.publicKey)) {
        const signature = s.sign(batch, keyPair)
        if (this.signers.length !== 1 || this.version === 0) return signature
        return this.assemble([{ signer: 0, signature, patch: 0, nodes: null }])
      }
    }

    throw BAD_ARGUMENT('Public key is not a declared signer')
  }

  assemble(inputs) {
    return this.version === 0 ? multisig.assemblev0(inputs) : multisig.assemble(inputs)
  }

  static manifestHash(manifest) {
    return manifestHash(manifest)
  }

  static encodeManifest(manifest) {
    return c.encode(m.manifest, manifest)
  }

  static decodeManifest(manifest) {
    return c.decode(m.manifest, manifest)
  }

  static defaultSignerManifest(publicKey) {
    return {
      version: 1,
      hash: 'blake2b',
      allowPatch: false,
      quorum: 1,
      signers: [
        {
          signature: 'ed25519',
          namespace: caps.DEFAULT_NAMESPACE,
          publicKey
        }
      ],
      prologue: null,
      linked: null,
      userData: null
    }
  }

  static fromManifest(manifest, opts) {
    const m = this.createManifest(manifest)
    return new this(manifestHash(m), m, opts)
  }

  static createManifest(inp) {
    if (!inp) return null

    if (inp.quorum && inp.quorum < 0) throw BAD_ARGUMENT('Quorum cannot be negative')
    if (inp.quorum && inp.signers && inp.quorum > inp.signers.length) {
      throw BAD_ARGUMENT('Quorum should not be higher than the number of signers')
    }

    const manifest = {
      version: getManifestVersion(inp), // defaults to v1
      hash: 'blake2b',
      allowPatch: !!inp.allowPatch,
      quorum: defaultQuorum(inp),
      signers: inp.signers ? inp.signers.map(parseSigner) : [],
      prologue: null,
      linked: null,
      userData: inp.userData || null
    }

    if (inp.hash && inp.hash !== 'blake2b') throw BAD_ARGUMENT('Only Blake2b hashes are supported')

    if (inp.prologue) {
      if (
        !(b4a.isBuffer(inp.prologue.hash) && inp.prologue.hash.byteLength === 32) ||
        !(inp.prologue.length >= 0)
      ) {
        throw BAD_ARGUMENT('Invalid prologue')
      }

      manifest.prologue = inp.prologue
      manifest.prologue.hash = unslab(manifest.prologue.hash)
    }

    if (manifest.userData !== null && manifest.version < 2) {
      throw BAD_ARGUMENT('Invalid field: userData')
    }

    if (inp.linked && inp.linked.length) {
      if (manifest.version < 2) throw BAD_ARGUMENT('Invalid field: linked')

      for (const key of inp.linked) {
        if (!(b4a.isBuffer(key) && key.byteLength === 32)) {
          throw BAD_ARGUMENT('Invalid key')
        }
      }

      manifest.linked = inp.linked
    }

    return manifest
  }

  static isValidManifest(key, manifest) {
    return b4a.equals(key, manifestHash(manifest))
  }

  static isCompat(key, manifest) {
    return isCompat(key, manifest)
  }

  static sign(manifest, batch, keyPair, opts) {
    return Verifier.fromManifest(manifest, opts).sign(batch, keyPair)
  }
}

function toMap(nodes) {
  const m = new Map()
  for (const node of nodes) m.set(node.index, node)
  return m
}

function isCompat(key, manifest) {
  return !!(
    manifest &&
    manifest.signers.length === 1 &&
    b4a.equals(key, manifest.signers[0].publicKey)
  )
}

function defaultQuorum(man) {
  if (typeof man.quorum === 'number') return man.quorum
  if (!man.signers || !man.signers.length) return 0
  return (man.signers.length >> 1) + 1
}

function generateUpgrade(patch, start, length) {
  const upgrade = { start, length, nodes: null, additionalNodes: [], signature: null }

  const from = start * 2
  const to = from + length * 2

  for (const ite = flat.iterator(0); ite.fullRoot(to); ite.nextTree()) {
    if (ite.index + ite.factor / 2 < from) continue

    if (upgrade.nodes === null && ite.contains(from - 2)) {
      upgrade.nodes = []

      const root = ite.index
      const target = from - 2

      ite.seek(target)

      while (ite.index !== root) {
        ite.sibling()
        if (ite.index > target) upgrade.nodes.push(patch.get(ite.index))
        ite.parent()
      }

      continue
    }

    if (upgrade.nodes === null) upgrade.nodes = []
    upgrade.nodes.push(patch.get(ite.index))
  }

  if (upgrade.nodes === null) upgrade.nodes = []
  return upgrade
}

function parseSigner(signer) {
  validateSigner(signer)
  return {
    signature: 'ed25519',
    namespace: unslab(signer.namespace || caps.DEFAULT_NAMESPACE),
    publicKey: unslab(signer.publicKey)
  }
}

function validateSigner(signer) {
  if (!signer || !signer.publicKey) throw BAD_ARGUMENT('Signer missing public key')
  if (signer.signature && signer.signature !== 'ed25519') {
    throw BAD_ARGUMENT('Only Ed25519 signatures are supported')
  }
}

function manifestHash(manifest) {
  const state = { start: 0, end: 32, buffer: null }
  m.manifest.preencode(state, manifest)
  state.buffer = b4a.allocUnsafe(state.end)
  c.raw.encode(state, caps.MANIFEST)
  m.manifest.encode(state, manifest)
  return crypto.hash(state.buffer)
}

function proofToVersion1(proof) {
  return {
    signer: proof.signer,
    signature: proof.signature,
    patch: proof.patch ? proof.patch.length : 0
  }
}

function getManifestVersion(inp) {
  if (typeof inp.version === 'number') return inp.version
  if (inp.linked && inp.linked.length) return 2
  if (inp.userData) return 2
  return 1
}
let COMPAT = true
let BATCH_UINTS = COMPAT ? 65536 : 128
let BATCH_BYTES = BATCH_UINTS * 4
let BATCH = BATCH_BYTES * 8 // in bits
const MAX_REMOTE_BATCHES = 4
const MAX_RANGE = 8 * 256 * 1024
const MIN_RANGE = 32 // 4bit ints
const MAX = 512
const MAX_ANY = 16

class LocalWants {
  constructor(peer) {
    this.destroyed = false
    this.peer = peer
    this.wants = new Map()
    this.free = new Set()
    this.any = null
  }

  add(index, handle) {
    const b = (index - (index & (BATCH - 1))) / BATCH
    return this.addBatch(b, handle)
  }

  addAnyRange(start, length, handle) {
    if (this.destroyed) return null
    if (this.any === null) this.any = []

    if (COMPAT) {
      let r = start & (BATCH - 1)
      start -= r
      length += r
      r = length & (BATCH - 1)
      if (r) length = length - r + BATCH
    }

    for (let i = 0; i < this.any.length; i++) {
      const w = this.any[i]
      if (w.start === start && w.length === length) {
        w.handles.add(handle)
        handle.addWant(w)
        return null
      }
    }

    const w = { wants: this, start, length, any: true, handles: new Set([handle]) }
    this.any.push(w)
    handle.addWant(w)

    return { start, length, any: !COMPAT }
  }

  removeAnyRange(start, length, handle) {
    if (this.any === null) return null

    for (let i = 0; i < this.any.length; i++) {
      const w = this.any[i]
      if (w.start !== start || w.length !== length) continue

      w.handles.delete(handle)
      handle.removeWant(w)
      if (w.handles.size > 0) return null

      const head = this.any.pop()
      if (head !== w) this.any[i] = head
      return { start, length, any: !COMPAT }
    }

    return null
  }

  addBatch(index, handle) {
    if (this.destroyed) return null
    let w = this.wants.get(index)

    if (w) {
      const size = w.handles.size
      w.handles.add(handle)
      if (w.handles.size !== size) {
        handle.addWant(w)
      }
      return null
    }

    // start here is the batch number for simplicity....
    w = { wants: this, start: index, length: 0, any: false, handles: new Set([handle]) }

    if (this.free.has(index)) {
      this.free.delete(index)
      this.wants.set(index, w)
      handle.addWant(w)
      return null
    }

    let unwant = null
    if (this.wants.size + this.free.size === MAX) {
      if (this.free.size === 0) {
        return null
      }
      unwant = this._unwant()
    }

    this.wants.set(index, w)
    handle.addWant(w)

    return {
      want: { start: index * BATCH, length: BATCH, any: false },
      unwant
    }
  }

  remove(index, handle) {
    const b = (index - (index & (BATCH - 1))) / BATCH
    return this.removeBatch(b, handle)
  }

  removeBatch(index, handle) {
    if (this.destroyed) return false

    const w = this.wants.get(index)
    if (!w) {
      return false
    }

    w.handles.delete(handle)
    handle.removeWant(w)

    if (w.handles.size === 0) {
      this.free.add(index)
      this.wants.delete(index)
      return this.free.size === 1 && this.wants.size === MAX - 1
    }

    return false
  }

  destroy() {
    if (this.destroyed) return
    this.destroyed = true

    for (const w of this.wants.values()) {
      for (const handle of w.handles) {
        handle.removeWant(w)
      }
    }

    this.wants.clear()
  }

  _unwant() {
    for (const index of this.free) {
      this.free.delete(index)
      return { start: index * BATCH, length: BATCH, any: false }
    }
    return null
  }
}

class RemoteWants {
  constructor() {
    this.any = null
    this.all = false
    this.size = 0
    this.batches = []
  }

  hasAny(start, length) {
    for (let i = 0; i < this.any.length; i++) {
      const a = this.any[i]
      const e = start + length
      const end = a.start + a.length

      if (a.start <= start && start < end) return true
      if (a.start < e && e <= end) return true
    }

    return this.all
  }

  hasRange(start, length) {
    if (this.any !== null && this.hasAny(start, length)) return true
    if (length === 1) return this.has(start)

    let smallest = -1

    for (let i = 0; i < this.batches.length; i++) {
      const b = this.batches[i]
      if (b.length < smallest || smallest === -1) smallest = b.length
    }

    if (smallest === -1) return this.all

    const r = start & (smallest - 1)
    const end = start + length

    let max = 3

    for (let i = start - r; i < end; i += smallest) {
      if (max-- === 0) return true // just to save work
      if (this.has(i)) return true
    }

    return this.all
  }

  has(index) {
    for (let i = 0; i < this.batches.length; i++) {
      const b = this.batches[i]
      const block = (index - (index & (b.length - 1))) / b.length
      if (b.ranges.has(block)) return true
    }

    return this.all
  }

  add(range) {
    if (range.any) {
      if (this.any === null) this.any = []
      if (this.any.length < MAX_ANY) this.any.push(range)
      else this.all = true
      return true
    }

    if (range.length > MAX_RANGE) return false

    if (!validateBatchRange(range)) {
      this.all = true
      return true
    }

    if (this.size >= MAX) {
      this.all = true
      return true
    }

    for (let i = 0; i < this.batches.length; i++) {
      const b = this.batches[i]
      if (b.length === range.length) {
        b.ranges.add(range.start / range.length)
        this.size++
        return true
      }
    }

    if (this.batches.length >= MAX_REMOTE_BATCHES) {
      this.all = true
      return true
    }

    this.batches.push({
      length: range.length,
      ranges: new Set([range.start / range.length])
    })
    this.size++

    return true
  }

  remove(range) {
    if (range.any) {
      if (this.any === null) return false

      for (let i = 0; i < this.any.length; i++) {
        const a = this.any[i]

        if (a.start === range.start && a.length === range.length) {
          const head = this.any.pop()
          if (head !== a) this.any[i] = head
          return true
        }
      }

      return false
    }

    if (!validateBatchRange(range)) return false

    for (let i = 0; i < this.batches.length; i++) {
      const b = this.batches[i]
      if (b.length !== range.length) continue

      const size = b.ranges.size
      b.ranges.delete(range.start / range.length)
      if (b.ranges.size !== size) this.size--

      if (b.ranges.size === 0) {
        const head = this.batches.pop()
        if (head !== b) this.batches[i] = head
      }

      return true
    }

    return false
  }
}

function UPDATE_COMPAT(isCompat) {
  COMPAT = isCompat
  BATCH_UINTS = COMPAT ? 65536 : 128
  BATCH_BYTES = BATCH_UINTS * 4
  BATCH = BATCH_BYTES * 8 // in bits
  exports.WANT_BATCH = BATCH
}

exports.LocalWants = LocalWants
exports.RemoteWants = RemoteWants
exports.WANT_BATCH = BATCH
exports.UPDATE_COMPAT = UPDATE_COMPAT

function validateBatchRange(range) {
  if (range.length > MAX_RANGE || range.length < MIN_RANGE) {
    return false
  }
  // check if power of two
  if ((range.length & (range.length - 1)) !== 0) {
    return false
  }
  // start must be a multiple of the length
  if (range.start & (range.length - 1)) {
    return false
  }

  return true
}
{
  "name": "hypercore",
  "version": "11.33.1",
  "description": "Hypercore is a secure, distributed append-only log",
  "main": "index.js",
  "scripts": {
    "format": "prettier --write .",
    "lint": "prettier --check . && lunte",
    "test": "brittle test/all.js",
    "test:bare": "bare test/all.js",
    "test:generate": "brittle -r test/all.js test/*.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/hypercore.git"
  },
  "contributors": [
    {
      "name": "Mathias Buus",
      "email": "mathiasbuus@gmail.com",
      "url": "https://mafinto.sh"
    },
    {
      "name": "Andrew Osheroff",
      "email": "andrewosh@gmail.com",
      "url": "https://andrewosh.com"
    }
  ],
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/holepunchto/hypercore/issues"
  },
  "homepage": "https://github.com/holepunchto/hypercore#readme",
  "files": [
    "index.js",
    "errors.js",
    "messages.js",
    "lib/**.js"
  ],
  "imports": {
    "events": {
      "bare": "bare-events",
      "default": "events"
    }
  },
  "dependencies": {
    "@hyperswarm/secret-stream": "^6.0.0",
    "b4a": "^1.1.0",
    "bare-events": "^2.2.0",
    "big-sparse-array": "^1.0.3",
    "compact-encoding": "^3.0.0",
    "fast-fifo": "^1.3.0",
    "flat-tree": "^1.9.0",
    "hypercore-crypto": "^3.2.1",
    "hypercore-errors": "^1.5.0",
    "hypercore-id-encoding": "^1.2.0",
    "hypercore-storage": "^3.0.0",
    "is-options": "^1.0.1",
    "nanoassert": "^2.0.0",
    "protomux": "^3.5.0",
    "quickbit-universal": "^2.2.0",
    "random-array-iterator": "^1.0.0",
    "safety-catch": "^1.0.1",
    "sodium-universal": "^5.0.1",
    "streamx": "^2.12.4",
    "unslab": "^1.3.0",
    "z32": "^1.0.0"
  },
  "devDependencies": {
    "brittle": "^3.0.0",
    "debugging-stream": "^3.1.0",
    "hyperswarm": "^4.3.6",
    "lunte": "^1.3.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^2.0.0",
    "rache": "^1.0.0",
    "range-parser": "^1.2.1",
    "resolve-reject-promise": "^1.1.0",
    "speedometer": "^1.1.0",
    "test-tmp": "^1.0.2",
    "tiny-byte-size": "^1.1.0",
    "udx-native": "^1.6.1",
    "uncaughts": "^1.1.0"
  }
}
const c = require('compact-encoding')
const { getEncoding } = require('./spec/hyperschema')

const HyperdhtAddress = getEncoding('@hyperdht/address')

function decode(buf) {
  const addr = c.decode(HyperdhtAddress, buf)
  return addr
}

function encode(key, nodes) {
  return c.encode(HyperdhtAddress, { key, nodes })
}

module.exports = { decode, encode }
{
  "name": "hyperdht-address",
  "version": "1.1.1",
  "description": "hyperdht-address",
  "main": "index.js",
  "exports": {
    "./package": "./package.json",
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    }
  },
  "files": [
    "package.json",
    "index.js",
    "index.d.ts",
    "spec"
  ],
  "devDependencies": {
    "b4a": "^1.8.0",
    "brittle": "^3.19.0",
    "lunte": "^1.2.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^2.0.0"
  },
  "scripts": {
    "format": "prettier . --write",
    "test": "prettier . --check && lunte && brittle-bare test/index.js",
    "lint": "lunte"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/holepunchto/hyperdht-address.git"
  },
  "author": "Holepunch Inc",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/hyperdht-address/issues"
  },
  "homepage": "https://github.com/holepunchto/hyperdht-address",
  "dependencies": {
    "compact-encoding": "^3.0.0",
    "hyperschema": "^1.20.1"
  }
}
// This file is autogenerated by the hyperschema compiler
// Schema Version: 2
/* eslint-disable camelcase */
/* eslint-disable quotes */
/* eslint-disable space-before-function-paren */

const { c } = require('hyperschema/runtime')

const VERSION = 2

// eslint-disable-next-line no-unused-vars
let version = VERSION

// @hyperdht/address.nodes
const encoding0_1 = c.array(c.ipv4Address)

// @hyperdht/address
const encoding0 = {
  preencode(state, m) {
    c.fixed32.preencode(state, m.key)
    state.end++ // max flag is 1 so always one byte

    if (version >= 2 && m.nodes) encoding0_1.preencode(state, m.nodes)
  },
  encode(state, m) {
    const flags = version >= 2 && m.nodes ? 1 : 0

    c.fixed32.encode(state, m.key)
    c.uint.encode(state, flags)

    if (version >= 2 && m.nodes) encoding0_1.encode(state, m.nodes)
  },
  decode(state) {
    const r0 = c.fixed32.decode(state)
    const flags = state.start < state.end ? c.uint.decode(state) : 0

    return {
      key: r0,
      nodes: version >= 2 && (flags & 1) !== 0 ? encoding0_1.decode(state) : null
    }
  }
}

function setVersion(v) {
  version = v
}

function encode(name, value, v = VERSION) {
  version = v
  return c.encode(getEncoding(name), value)
}

function decode(name, buffer, v = VERSION) {
  version = v
  return c.decode(getEncoding(name), buffer)
}

function getEnum(name) {
  switch (name) {
    default:
      throw new Error('Enum not found ' + name)
  }
}

function getEncoding(name) {
  switch (name) {
    case '@hyperdht/address':
      return encoding0
    default:
      throw new Error('Encoder not found ' + name)
  }
}

function getStruct(name, v = VERSION) {
  const enc = getEncoding(name)
  return {
    preencode(state, m) {
      version = v
      enc.preencode(state, m)
    },
    encode(state, m) {
      version = v
      enc.encode(state, m)
    },
    decode(state) {
      version = v
      return enc.decode(state)
    }
  }
}

const resolveStruct = getStruct // compat

module.exports = {
  resolveStruct,
  getStruct,
  getEnum,
  getEncoding,
  encode,
  decode,
  setVersion,
  version
}
const DHT = require('dht-rpc')
const sodium = require('sodium-universal')
const c = require('compact-encoding')
const b4a = require('b4a')
const safetyCatch = require('safety-catch')
const m = require('./lib/messages')
const SocketPool = require('./lib/socket-pool')
const Persistent = require('./lib/persistent')
const Router = require('./lib/router')
const Cache = require('xache')
const Server = require('./lib/server')
const connect = require('./lib/connect')
const { FIREWALL, BOOTSTRAP_NODES, KNOWN_NODES, COMMANDS } = require('./lib/constants')
const { hash, createKeyPair } = require('./lib/crypto')
const RawStreamSet = require('./lib/raw-stream-set')
const ConnectionPool = require('./lib/connection-pool')
const { STREAM_NOT_CONNECTED } = require('./lib/errors')

const DEFAULTS = {
  ...DHT.DEFAULTS,
  connectionKeepAlive: 5000,
  randomPunchInterval: 20000
}

class HyperDHT extends DHT {
  constructor(opts = {}) {
    const port = opts.port || 49737
    const bootstrap = opts.bootstrap || BOOTSTRAP_NODES
    const nodes = opts.nodes || KNOWN_NODES

    super({ ...opts, port, bootstrap, nodes, filterNode })

    const { router, relayAddresses, persistent } = defaultCacheOpts(opts)

    this.defaultKeyPair = opts.keyPair || createKeyPair(opts.seed)
    this.listening = new Set()
    this.connectionKeepAlive =
      opts.connectionKeepAlive === false
        ? 0
        : opts.connectionKeepAlive || DEFAULTS.connectionKeepAlive

    // stats is inherited from dht-rpc so fwd the ones from there
    this.stats = {
      punches: { consistent: 0, random: 0, open: 0 },
      relaying: { attempts: 0, successes: 0, aborts: 0 },
      ...this.stats
    }
    this.rawStreams = new RawStreamSet(this)
    this.plugins = new Map()

    this._router = new Router(this, router)
    this._socketPool = new SocketPool(this, opts.host || '0.0.0.0')
    this._persistent = null
    this._validatedLocalAddresses = new Map()
    this._relayAddressesCache = new Cache(relayAddresses)

    this._deferRandomPunch = !!opts.deferRandomPunch
    this._lastRandomPunch = this._deferRandomPunch ? Date.now() : 0
    this._connectable = true
    this._randomPunchInterval = opts.randomPunchInterval || DEFAULTS.randomPunchInterval // min 20s between random punches...
    this._randomPunches = 0
    this._randomPunchLimit = 1 // set to one for extra safety for now

    this.once('persistent', () => {
      this._persistent = new Persistent(this, persistent)
      for (const plugin of this.plugins.values()) plugin.onpersistent()
    })

    this.on('network-change', () => {
      for (const server of this.listening) server.refresh()
    })

    this.on('network-update', () => {
      if (!this.online) return
      for (const server of this.listening) server.notifyOnline()
    })
  }

  static DEFAULTS = DEFAULTS

  connect(remotePublicKey, opts) {
    return connect(this, remotePublicKey, opts)
  }

  createServer(opts, onconnection) {
    if (typeof opts === 'function') return this.createServer({}, opts)
    if (opts && opts.onconnection) onconnection = opts.onconnection
    const s = new Server(this, opts)
    if (onconnection) s.on('connection', onconnection)
    return s
  }

  pool() {
    return new ConnectionPool(this)
  }

  async resume({ log = noop } = {}) {
    if (this._deferRandomPunch) this._lastRandomPunch = Date.now()
    await super.resume({ log })
    const resuming = []
    for (const server of this.listening) resuming.push(server.resume())
    log('Resuming hyperdht servers')
    await Promise.allSettled(resuming)
    log('Done, hyperdht fully resumed')
  }

  async suspend({ log = noop } = {}) {
    this._connectable = false // just so nothing gets connected during suspension
    const suspending = []
    for (const server of this.listening) suspending.push(server.suspend())
    log('Suspending all hyperdht servers')
    await Promise.allSettled(suspending)
    log('Done, clearing all raw streams')
    await this.rawStreams.clear()
    log('Done, suspending dht-rpc')
    await super.suspend({ log })
    log('Done, clearing raw streams again')
    await this.rawStreams.clear()
    log('Done, hyperdht fully suspended')
    this._connectable = true
  }

  async destroy({ force = false } = {}) {
    if (!force) {
      const closing = []
      for (const server of this.listening) closing.push(server.close())
      await Promise.allSettled(closing)
    }
    this._router.destroy()
    if (this._persistent) this._persistent.destroy()
    for (const plugin of this.plugins.values()) plugin.destroy()
    await this.rawStreams.clear()
    await this._socketPool.destroy()
    await super.destroy()
  }

  async validateLocalAddresses(addresses) {
    const list = []
    const socks = []
    const waiting = []

    for (const addr of addresses) {
      const { host } = addr

      if (this._validatedLocalAddresses.has(host)) {
        if (await this._validatedLocalAddresses.get(host)) {
          list.push(addr)
        }
        continue
      }

      const sock = this.udx.createSocket()
      try {
        sock.bind(0, host)
      } catch {
        this._validatedLocalAddresses.set(host, Promise.resolve(false))
        continue
      }

      socks.push(sock)

      // semi terrible heuristic until we proper fix local connections by racing them to the remote...
      const promise = new Promise((resolve) => {
        sock.on('message', () => resolve(true))
        setTimeout(() => resolve(false), 500)
        sock.trySend(b4a.alloc(1), sock.address().port, addr.host)
      })

      this._validatedLocalAddresses.set(host, promise)
      waiting.push(addr)
    }

    for (const addr of waiting) {
      const { host } = addr
      if (this._validatedLocalAddresses.has(host)) {
        if (await this._validatedLocalAddresses.get(host)) {
          list.push(addr)
        }
        continue
      }
    }

    for (const sock of socks) await sock.close()

    return list
  }

  findPeer(publicKey, opts = {}) {
    const target = opts.hash === false ? publicKey : hash(publicKey)
    opts = { ...opts, map: mapFindPeer }
    return this.query({ target, command: COMMANDS.FIND_PEER, value: null }, opts)
  }

  lookup(target, opts = {}) {
    opts = { ...opts, map: mapLookup }
    return this.query({ target, command: COMMANDS.LOOKUP, value: null }, opts)
  }

  lookupAndUnannounce(target, keyPair, opts = {}) {
    const unannounces = []
    const dht = this
    const userCommit = opts.commit || noop
    const signUnannounce = opts.signUnannounce || Persistent.signUnannounce

    if (this._persistent !== null) {
      // unlink self
      this._persistent.unannounce(target, keyPair.publicKey)
    }

    opts = { ...opts, map, commit }
    return this.query({ target, command: COMMANDS.LOOKUP, value: null }, opts)

    async function commit(reply, dht, query) {
      await Promise.all(unannounces) // can never fail, caught below
      return userCommit(reply, dht, query)
    }

    function map(reply) {
      const data = mapLookup(reply)

      if (!data || !data.token) return data

      let found = data.peers.length >= 20
      for (let i = 0; !found && i < data.peers.length; i++) {
        found = b4a.equals(data.peers[i].publicKey, keyPair.publicKey)
      }

      if (!found) return data

      if (!data.from.id) return data

      unannounces.push(
        dht
          ._requestUnannounce(keyPair, dht, target, data.token, data.from, signUnannounce)
          .catch(safetyCatch)
      )

      return data
    }
  }

  unannounce(target, keyPair, opts = {}) {
    return this.lookupAndUnannounce(target, keyPair, opts).finished()
  }

  announce(target, keyPair, relayAddresses, opts = {}) {
    const signAnnounce = opts.signAnnounce || Persistent.signAnnounce
    const bump = opts.bump || 0

    opts = { ...opts, commit }

    return opts.clear ? this.lookupAndUnannounce(target, keyPair, opts) : this.lookup(target, opts)

    function commit(reply, dht) {
      return dht._requestAnnounce(
        keyPair,
        dht,
        target,
        reply.token,
        reply.from,
        relayAddresses,
        signAnnounce,
        bump
      )
    }
  }

  async immutableGet(target, opts = {}) {
    opts = { ...opts, map: mapImmutable }

    const query = this.query({ target, command: COMMANDS.IMMUTABLE_GET, value: null }, opts)
    const check = b4a.allocUnsafe(32)

    for await (const node of query) {
      const { value } = node
      sodium.crypto_generichash(check, value)
      if (b4a.equals(check, target)) return node
    }

    return null
  }

  async immutablePut(value, opts = {}) {
    const target = b4a.allocUnsafe(32)
    sodium.crypto_generichash(target, value)

    opts = {
      ...opts,
      map: mapImmutable,
      commit(reply, dht) {
        return dht.request(
          { token: reply.token, target, command: COMMANDS.IMMUTABLE_PUT, value },
          reply.from
        )
      }
    }

    const query = this.query({ target, command: COMMANDS.IMMUTABLE_GET, value: null }, opts)
    await query.finished()

    return { hash: target, closestNodes: query.closestNodes }
  }

  async mutableGet(publicKey, opts = {}) {
    let refresh = opts.refresh || null
    let signed = null
    let result = null

    opts = { ...opts, map: mapMutable, commit: refresh ? commit : null }

    const target = b4a.allocUnsafe(32)
    sodium.crypto_generichash(target, publicKey)

    const userSeq = opts.seq || 0
    const query = this.query(
      { target, command: COMMANDS.MUTABLE_GET, value: c.encode(c.uint, userSeq) },
      opts
    )
    const latest = opts.latest !== false

    for await (const node of query) {
      if (result && node.seq <= result.seq) continue
      if (
        node.seq < userSeq ||
        !Persistent.verifyMutable(node.signature, node.seq, node.value, publicKey)
      )
        continue
      if (!latest) return node
      if (!result || node.seq > result.seq) result = node
    }

    return result

    function commit(reply, dht) {
      if (!signed && result && refresh) {
        if (refresh(result)) {
          signed = c.encode(m.mutablePutRequest, {
            publicKey,
            seq: result.seq,
            value: result.value,
            signature: result.signature
          })
        } else {
          refresh = null
        }
      }

      return signed
        ? dht.request(
            { token: reply.token, target, command: COMMANDS.MUTABLE_PUT, value: signed },
            reply.from
          )
        : Promise.resolve(null)
    }
  }

  async mutablePut(keyPair, value, opts = {}) {
    const signMutable = opts.signMutable || Persistent.signMutable

    const target = b4a.allocUnsafe(32)
    sodium.crypto_generichash(target, keyPair.publicKey)

    const seq = opts.seq || 0
    const signature = await signMutable(seq, value, keyPair)

    const signed = c.encode(m.mutablePutRequest, {
      publicKey: keyPair.publicKey,
      seq,
      value,
      signature
    })

    opts = {
      ...opts,
      map: mapMutable,
      commit(reply, dht) {
        return dht.request(
          { token: reply.token, target, command: COMMANDS.MUTABLE_PUT, value: signed },
          reply.from
        )
      }
    }

    // use seq = 0, for the query part here, as we don't care about the actual values
    const query = this.query(
      { target, command: COMMANDS.MUTABLE_GET, value: c.encode(c.uint, 0) },
      opts
    )
    await query.finished()

    return { publicKey: keyPair.publicKey, closestNodes: query.closestNodes, seq, signature }
  }

  onrequest(req) {
    switch (req.command) {
      case COMMANDS.PEER_HANDSHAKE: {
        this._router.onpeerhandshake(req)
        return true
      }
      case COMMANDS.PEER_HOLEPUNCH: {
        this._router.onpeerholepunch(req)
        return true
      }
    }

    if (this._persistent === null || this.id === null) return false

    switch (req.command) {
      case COMMANDS.FIND_PEER: {
        this._persistent.onfindpeer(req)
        return true
      }
      case COMMANDS.LOOKUP: {
        this._persistent.onlookup(req)
        return true
      }
      case COMMANDS.ANNOUNCE: {
        this._persistent.onannounce(req)
        return true
      }
      case COMMANDS.UNANNOUNCE: {
        this._persistent.onunannounce(req)
        return true
      }
      case COMMANDS.MUTABLE_PUT: {
        this._persistent.onmutableput(req)
        return true
      }
      case COMMANDS.MUTABLE_GET: {
        this._persistent.onmutableget(req)
        return true
      }
      case COMMANDS.IMMUTABLE_PUT: {
        this._persistent.onimmutableput(req)
        return true
      }
      case COMMANDS.IMMUTABLE_GET: {
        this._persistent.onimmutableget(req)
        return true
      }
      case COMMANDS.PLUGIN: {
        this._persistent.onplugin(req)
        return true
      }
    }

    return false
  }

  static keyPair(seed) {
    return createKeyPair(seed)
  }

  static hash(data) {
    return hash(data)
  }

  static connectRawStream(encryptedStream, rawStream, remoteId) {
    const stream = encryptedStream.rawStream

    if (!stream.connected) throw STREAM_NOT_CONNECTED()

    rawStream.connect(stream.socket, remoteId, stream.remotePort, stream.remoteHost)
  }

  createRawStream(opts) {
    return this.rawStreams.add(opts)
  }

  async _requestAnnounce(keyPair, dht, target, token, from, relayAddresses, sign, bump) {
    const ann = {
      peer: {
        publicKey: keyPair.publicKey,
        relayAddresses: relayAddresses || []
      },
      refresh: null,
      signature: null,
      bump
    }

    ann.signature = await sign(target, token, from.id, ann, keyPair)

    const value = c.encode(m.announce, ann)

    return dht.request(
      {
        token,
        target,
        command: COMMANDS.ANNOUNCE,
        value
      },
      from
    )
  }

  async _requestUnannounce(keyPair, dht, target, token, from, sign) {
    const unann = {
      peer: {
        publicKey: keyPair.publicKey,
        relayAddresses: []
      },
      signature: null
    }

    unann.signature = await sign(target, token, from.id, unann, keyPair)

    const value = c.encode(m.announce, unann)

    return dht.request(
      {
        token,
        target,
        command: COMMANDS.UNANNOUNCE,
        value
      },
      from
    )
  }

  register(name, plugin) {
    this.plugins.set(name, plugin)
    plugin.onregister(this)
  }
}

HyperDHT.BOOTSTRAP = BOOTSTRAP_NODES
HyperDHT.FIREWALL = FIREWALL

module.exports = HyperDHT

function mapLookup(node) {
  if (!node.value) return null

  try {
    const l = c.decode(m.lookupRawReply, node.value)

    return {
      token: node.token,
      from: node.from,
      to: node.to,
      peers: l.peers,
      bump: l.bump
    }
  } catch {
    return null
  }
}

function mapFindPeer(node) {
  if (!node.value) return null

  try {
    return {
      token: node.token,
      from: node.from,
      to: node.to,
      peer: c.decode(m.peer, node.value)
    }
  } catch {
    return null
  }
}

function mapImmutable(node) {
  if (!node.value) return null

  return {
    token: node.token,
    from: node.from,
    to: node.to,
    value: node.value
  }
}

function mapMutable(node) {
  if (!node.value) return null

  try {
    const { seq, value, signature } = c.decode(m.mutableGetResponse, node.value)

    return {
      token: node.token,
      from: node.from,
      to: node.to,
      seq,
      value,
      signature
    }
  } catch {
    return null
  }
}

function noop() {}

function filterNode(node) {
  // always skip these testnet nodes that got mixed in by accident, until they get updated
  return (
    !(node.port === 49738 && (node.host === '134.209.28.98' || node.host === '167.99.142.185')) &&
    !(node.port === 9400 && node.host === '35.233.47.252') &&
    !(node.host === '150.136.142.116')
  )
}

const defaultMaxSize = 65536
const defaultMaxAge = 20 * 60 * 1000 // 20 minutes

function defaultCacheOpts(opts) {
  const maxSize = opts.maxSize || defaultMaxSize
  const maxAge = opts.maxAge || defaultMaxAge

  return {
    router: {
      forwards: { maxSize, maxAge }
    },
    relayAddresses: { maxSize: Math.min(maxSize, 512), maxAge: 0 },
    persistent: {
      records: { maxSize, maxAge },
      refreshes: { maxSize, maxAge },
      mutables: {
        maxSize: (maxSize / 2) | 0,
        maxAge: opts.maxAge || 48 * 60 * 60 * 1000 // 48 hours
      },
      immutables: {
        maxSize: (maxSize / 2) | 0,
        maxAge: opts.maxAge || 48 * 60 * 60 * 1000 // 48 hours
      },
      bumps: { maxSize, maxAge }
    }
  }
}
const safetyCatch = require('safety-catch')
const c = require('compact-encoding')
const Signal = require('signal-promise')
const { encodeUnslab } = require('./encode')
const Sleeper = require('./sleeper')
const m = require('./messages')
const Persistent = require('./persistent')
const { COMMANDS } = require('./constants')

const MIN_ACTIVE = 3

module.exports = class Announcer {
  constructor(dht, keyPair, target, opts = {}) {
    this.dht = dht
    this.keyPair = keyPair
    this.target = target
    this.relays = []
    this.relayAddresses = []
    this.stopped = false
    this.suspended = false
    this.record = encodeUnslab(m.peer, { publicKey: keyPair.publicKey, relayAddresses: [] })
    this.online = new Signal()

    this._refreshing = false
    this._closestNodes = null
    this._active = null
    this._sleeper = new Sleeper()
    this._resumed = new Signal()
    this._signAnnounce = opts.signAnnounce || Persistent.signAnnounce
    this._signUnannounce = opts.signUnannounce || Persistent.signUnannounce
    this._updating = null
    this._activeQuery = null
    this._unannouncing = null

    this._serverRelays = [new Map(), new Map(), new Map()]
  }

  isRelay(addr) {
    const id = addr.host + ':' + addr.port
    const [a, b, c] = this._serverRelays
    return a.has(id) || b.has(id) || c.has(id)
  }

  async suspend({ log = noop } = {}) {
    if (this.suspended) return
    this.suspended = true

    log('Suspending announcer')

    // Suspend has its own sleep logic
    // so we don't want to hang on this one
    this.online.notify()

    if (this._activeQuery) this._activeQuery.destroy()

    this._sleeper.resume()
    if (this._updating) await this._updating
    log('Suspending announcer (post update)')

    if (this.suspended === false || this.stopped) return

    log('Suspending announcer (pre unannounce)')
    await this._unannounceCurrent()
    log('Suspending announcer (post unannounce)')
  }

  resume() {
    if (!this.suspended) return
    this.suspended = false

    this.refresh()
    this._sleeper.resume()
    this._resumed.notify()
  }

  refresh() {
    if (this.stopped) return
    this._refreshing = true
  }

  async start() {
    if (this.stopped) return
    this._active = this._runUpdate()
    await this._active
    if (this.stopped) return
    this._active = this._background()
  }

  async stop() {
    this.stopped = true
    this.online.notify() // Break out of the _background loop if we're offline
    this._sleeper.resume()
    this._resumed.notify()
    await this._active
    await this._unannounceCurrent()
  }

  async _unannounceCurrent() {
    while (this._unannouncing !== null) await this._unannouncing
    const un = (this._unannouncing = this._unannounceAll(this._serverRelays[2].values()))
    await this._unannouncing
    if (un === this._unannouncing) this._unannouncing = null
  }

  async _background() {
    while (!this.dht.destroyed && !this.stopped) {
      try {
        this._refreshing = false

        // ~5min +-
        for (let i = 0; i < 100 && !this.stopped && !this._refreshing && !this.suspended; i++) {
          const pings = []

          for (const node of this._serverRelays[2].values()) {
            pings.push(this.dht.ping(node))
          }

          const active = await resolved(pings)
          if (active < Math.min(pings.length, MIN_ACTIVE)) {
            this.refresh() // we lost too many relay nodes, retry all
          }

          if (this.stopped) return

          if (!this.suspended && !this._refreshing) await this._sleeper.pause(3000)
        }

        while (!this.stopped && this.suspended) await this._resumed.wait()

        if (!this.stopped) await this._runUpdate()

        while (!this.dht.online && !this.stopped && !this.suspended) {
          // Being offline can make _background repeat very quickly
          // So wait until we're back online
          await this.online.wait()
        }
      } catch (err) {
        safetyCatch(err)
      }
    }
  }

  async _runUpdate() {
    this._updating = this._update()
    await this._updating
    this._updating = null
  }

  async _update() {
    while (this._unannouncing) await this._unannouncing

    this._cycle()

    const q = (this._activeQuery = this.dht.findPeer(this.target, {
      hash: false,
      nodes: this._closestNodes
    }))

    try {
      await q.finished()
    } catch {
      // ignore failures...
    }

    this._activeQuery = null

    if (this.stopped || this.suspended) return

    const ann = []
    const replies = pickBest(q.closestReplies)

    const relays = []
    const relayAddresses = []

    if (!this.dht.firewalled) {
      const addr = this.dht.remoteAddress()
      if (addr) relayAddresses.push(addr)
    }

    for (const msg of replies) {
      ann.push(this._commit(msg, relays, relayAddresses))
    }

    await Promise.allSettled(ann)
    if (this.stopped || this.suspended) return

    this._closestNodes = q.closestNodes
    this.relays = relays
    this.relayAddresses = relayAddresses

    const removed = []
    for (const [key, value] of this._serverRelays[1]) {
      if (!this._serverRelays[2].has(key)) removed.push(value)
    }

    await this._unannounceAll(removed)
  }

  _unannounceAll(relays) {
    const unann = []
    for (const r of relays) unann.push(this._unannounce(r))
    return Promise.allSettled(unann)
  }

  async _unannounce(to) {
    const unann = {
      peer: {
        publicKey: this.keyPair.publicKey,
        relayAddresses: []
      },
      refresh: null,
      signature: null
    }

    const { from, token, value } = await this.dht.request(
      {
        token: null,
        command: COMMANDS.FIND_PEER,
        target: this.target,
        value: null
      },
      to
    )

    if (!token || !from.id || !value) return

    unann.signature = await this._signUnannounce(this.target, token, from.id, unann, this.keyPair)

    await this.dht.request(
      {
        token,
        command: COMMANDS.UNANNOUNCE,
        target: this.target,
        value: c.encode(m.announce, unann)
      },
      to
    )
  }

  async _commit(msg, relays, relayAddresses) {
    const ann = {
      peer: {
        publicKey: this.keyPair.publicKey,
        relayAddresses: []
      },
      refresh: null,
      signature: null
    }

    ann.signature = await this._signAnnounce(this.target, msg.token, msg.from.id, ann, this.keyPair)

    const res = await this.dht.request(
      {
        token: msg.token,
        command: COMMANDS.ANNOUNCE,
        target: this.target,
        value: c.encode(m.announce, ann)
      },
      msg.from
    )

    if (res.error !== 0) return

    if (relayAddresses.length < 3) relayAddresses.push({ host: msg.from.host, port: msg.from.port })
    relays.push({ relayAddress: msg.from, peerAddress: msg.to })

    this._serverRelays[2].set(msg.from.host + ':' + msg.from.port, msg.from)
  }

  _cycle() {
    const tmp = this._serverRelays[0]
    this._serverRelays[0] = this._serverRelays[1]
    this._serverRelays[1] = this._serverRelays[2]
    this._serverRelays[2] = tmp
    tmp.clear()
  }
}

function resolved(ps) {
  let replied = 0
  let ticks = ps.length + 1

  return new Promise((resolve) => {
    for (const p of ps) p.then(push, tick)
    tick()

    function push(v) {
      replied++
      tick()
    }

    function tick() {
      if (--ticks === 0) resolve(replied)
    }
  })
}

function pickBest(replies) {
  // TODO: pick the ones closest to us RTT wise
  return replies.slice(0, 3)
}

function noop() {}
const NoiseSecretStream = require('@hyperswarm/secret-stream')
const b4a = require('b4a')
const relay = require('blind-relay')
const { isReserved, isBogon } = require('bogon')
const safetyCatch = require('safety-catch')
const unslab = require('unslab')
const Semaphore = require('./semaphore')
const NoiseWrap = require('./noise-wrap')
const SecurePayload = require('./secure-payload')
const Holepuncher = require('./holepuncher')
const Sleeper = require('./sleeper')
const { FIREWALL, ERROR } = require('./constants')
const { unslabbedHash } = require('./crypto')
const {
  CANNOT_HOLEPUNCH,
  HANDSHAKE_INVALID,
  HOLEPUNCH_ABORTED,
  HOLEPUNCH_INVALID,
  HOLEPUNCH_PROBE_TIMEOUT,
  HOLEPUNCH_DOUBLE_RANDOMIZED_NATS,
  PEER_CONNECTION_FAILED,
  PEER_NOT_FOUND,
  REMOTE_ABORTED,
  REMOTE_NOT_HOLEPUNCHABLE,
  REMOTE_NOT_HOLEPUNCHING,
  SERVER_ERROR,
  SERVER_INCOMPATIBLE,
  RELAY_ABORTED,
  SUSPENDED
} = require('./errors')
const { decode } = require('hypercore-id-encoding')
const HyperDHTAddress = require('hyperdht-address')

module.exports = function connect(dht, publicKey, opts = {}) {
  const pool = opts.pool || null

  const { key, nodes: providedNodes } = HyperDHTAddress.decode(
    b4a.isBuffer(publicKey) ? publicKey : decode(publicKey)
  )
  publicKey = key

  if (pool && pool.has(publicKey)) return pool.get(publicKey)

  publicKey = unslab(publicKey)

  opts.relayAddresses = opts.relayAddresses || providedNodes || []
  const keyPair = opts.keyPair || dht.defaultKeyPair
  const relayThrough = selectRelay(opts.relayThrough || null)
  const encryptedSocket = (opts.createSecretStream || defaultCreateSecretStream)(true, null, {
    publicKey: keyPair.publicKey,
    remotePublicKey: publicKey,
    autoStart: false,
    keepAlive: dht.connectionKeepAlive
  })

  // in case a socket is made during suspended state, destroy it immediately
  if (dht.suspended || !dht._connectable) {
    encryptedSocket.destroy(SUSPENDED())
    return encryptedSocket
  }

  if (pool) pool._attachStream(encryptedSocket, false)

  const id = b4a.toString(publicKey, 'hex')
  const c = {
    id,
    dht,
    session: dht.session(),
    relayAddresses: opts.relayAddresses,
    remoteRelayAddresses: [],
    pool,
    round: 0,
    target: unslabbedHash(publicKey),
    remotePublicKey: publicKey,
    reusableSocket: !!opts.reusableSocket,
    handshake: (opts.createHandshake || defaultCreateHandshake)(keyPair, publicKey),
    request: null,
    requesting: false,
    lan: opts.localConnection !== false,
    firewall: FIREWALL.UNKNOWN,
    rawStream: dht.createRawStream({ framed: true, firewall }),
    connect: null,
    query: null,
    puncher: null,
    payload: null,
    passiveConnectTimeout: null,
    serverSocket: null,
    serverAddress: null,
    onsocket: null,
    sleeper: new Sleeper(),
    encryptedSocket,

    // Relay state
    relayTimeout: null,
    relayThrough,
    relayToken: relayThrough ? relay.token() : null,
    relaySocket: null,
    relayClient: null,
    relayPaired: false,
    relayKeepAlive: opts.relayKeepAlive || 5000
  }

  // If the raw stream receives an error signal pre connect (ie from the firewall hook), make sure
  // to forward that to the encrypted socket for proper teardown
  c.rawStream.on('error', autoDestroy)
  c.rawStream.once('connect', () => {
    c.rawStream.removeListener('error', autoDestroy)
  })

  encryptedSocket.on('close', function () {
    if (c.passiveConnectTimeout) clearPassiveConnectTimeout(c)
    if (c.query) c.query.destroy()
    if (c.puncher) c.puncher.destroy()
    if (c.rawStream) c.rawStream.destroy()
    c.session.destroy()
    c.sleeper.resume()
  })

  // Safe to run in the background - never throws
  if (dht.suspended) encryptedSocket.destroy(SUSPENDED())
  else connectAndHolepunch(c, opts)

  return encryptedSocket

  function autoDestroy(err) {
    maybeDestroyEncryptedSocket(c, err)
  }

  function firewall(socket, port, host) {
    // Check if the traffic originated from the socket on which we're expecting relay traffic. If so,
    // we haven't hole punched yet and the other side is just sending us traffic through the relay.
    if (c.relaySocket && isRelay(c.relaySocket, socket, port, host)) {
      return false
    }

    if (c.onsocket) {
      c.onsocket(socket, port, host)
    } else {
      c.serverSocket = socket
      c.serverAddress = { port, host }
    }
    return false
  }
}

function isDone(c) {
  // we are destroying or the puncher is connected - done
  if (c.encryptedSocket.destroying || !!(c.puncher && c.puncher.connected)) {
    return true
  }
  // not destroying, but no raw stream - def not done
  if (c.encryptedSocket.rawStream === null) {
    return false
  }
  // we are relayed, but the puncher is not done yet
  if (c.relaySocket && !!(c.puncher && !c.puncher.connected && !c.puncher.destroyed)) {
    return false
  }
  // we are done
  return true
}

async function retryRoute(c, route) {
  const ref = c.dht._socketPool.lookup(route.socket)

  if (!ref) {
    if (route.socket === c.dht.socket) {
      await connectThroughNode(c, route.address, c.dht.socket)
    }
    return
  }

  ref.active()

  try {
    await connectThroughNode(c, route.address, route.socket)
  } catch {
    // if error, just ignore, and continue through the existing strat
  }

  ref.inactive()
}

async function connectAndHolepunch(c, opts) {
  const route = c.reusableSocket ? c.dht._socketPool.routes.get(c.remotePublicKey) : null

  if (route) {
    await retryRoute(c, route)
    if (isDone(c)) return
  }

  await findAndConnect(c, opts)
  if (isDone(c)) return

  if (!c.connect) {
    // TODO: just a quick fix for now, should retry prob
    maybeDestroyEncryptedSocket(c, HANDSHAKE_INVALID())
    return
  }

  await holepunch(c, opts)
}

function getFirstRemoteAddress(addrs, serverAddress) {
  for (const addr of addrs) {
    if (isBogon(addr.host)) continue
    return addr
  }

  return serverAddress
}

async function holepunch(c, opts) {
  let { relayAddress, serverAddress, clientAddress, payload } = c.connect

  const remoteHolepunchable = !!(payload.holepunch && payload.holepunch.relays.length)

  const relayed = diffAddress(serverAddress, relayAddress)

  if (payload.firewall === FIREWALL.OPEN || (relayed && !remoteHolepunchable)) {
    const addr = getFirstRemoteAddress(payload.addresses4, serverAddress)
    if (addr) {
      const socket = c.dht.socket
      c.dht.stats.punches.open++
      c.onsocket(socket, addr.port, addr.host)
      return
    }
    // TODO: check all addresses also obvs
  }

  const onabort = () => {
    c.session.destroy()
    maybeDestroyEncryptedSocket(c, HOLEPUNCH_ABORTED())
  }

  if (c.firewall === FIREWALL.OPEN) {
    c.passiveConnectTimeout = setTimeout(onabort, 10000)
    return
  }

  // TODO: would be better to just try local addrs in the background whilst continuing with other strategies...
  if (c.lan && relayed && clientAddress.host === serverAddress.host) {
    const serverAddresses = payload.addresses4.filter(onlyNonReserved)

    if (serverAddresses.length > 0) {
      const myAddresses = Holepuncher.localAddresses(c.dht.io.serverSocket)
      const addr = Holepuncher.matchAddress(myAddresses, serverAddresses) || serverAddresses[0]

      const socket = c.dht.io.serverSocket
      try {
        await c.dht.ping(addr)
      } catch {
        maybeDestroyEncryptedSocket(c, HOLEPUNCH_ABORTED())
        return
      }
      c.onsocket(socket, addr.port, addr.host)
      return
    }
  }

  if (!remoteHolepunchable) {
    maybeDestroyEncryptedSocket(c, CANNOT_HOLEPUNCH())
    return
  }

  c.puncher = new Holepuncher(c.dht, c.session, true, payload.firewall)

  c.puncher.onconnect = c.onsocket
  c.puncher.onabort = onabort

  const serverRelay = pickServerRelay(payload.holepunch.relays, relayAddress)

  // Begin holepunching!

  let probe
  try {
    probe = await probeRound(c, opts.fastOpen === false ? null : serverAddress, serverRelay, true)
  } catch (err) {
    destroyPuncher(c)
    // TODO: we should retry here with some of the other relays, bail for now
    maybeDestroyEncryptedSocket(c, err)
    return
  }

  if (isDone(c) || !probe) return
  const { token, peerAddress } = probe

  // If the relay the server picked is the same as the relay the client picked,
  // then we can use the peerAddress that round one indicates the server wants to use.
  // This shaves off a roundtrip if the server chose to reroll its socket due to some NAT
  // issue with the first one it picked (ie mobile nat inconsistencies...).
  // If the relays were different, then the server would not have a UDP session open on this address
  // to the client relay, which round2 uses.
  if (
    !diffAddress(serverRelay.relayAddress, relayAddress) &&
    diffAddress(serverAddress, peerAddress)
  ) {
    serverAddress = peerAddress
    await c.puncher.openSession(serverAddress)
    if (isDone(c)) return
  }

  // TODO: still continue here if a local connection might work, but then do not holepunch...
  if (
    opts.holepunch &&
    !opts.holepunch(
      c.puncher.remoteFirewall,
      c.puncher.nat.firewall,
      c.puncher.remoteAddresses,
      c.puncher.nat.addresses
    )
  ) {
    await abort(c, serverRelay, HOLEPUNCH_ABORTED('Client aborted holepunch'))
    return
  }

  try {
    await roundPunch(c, serverAddress, token, relayAddress, serverRelay, false)
  } catch (err) {
    destroyPuncher(c)
    // TODO: retry with another relay?
    maybeDestroyEncryptedSocket(c, err)
  }
}

async function connectThroughNodes(c, addresses, socket) {
  for (const address of addresses) {
    if (isDone(c) || c.connect) return

    c.remoteRelayAddresses.push(address)
    await connectThroughNode(c, address, socket)
  }
}

async function findAndConnect(c, opts) {
  let attempts = 0
  let relayAddresses =
    opts.relayAddresses && opts.relayAddresses.length ? opts.relayAddresses : null

  if (!relayAddresses) {
    const cachedRelayAddresses = c.dht._relayAddressesCache.get(c.id)
    if (cachedRelayAddresses) relayAddresses = cachedRelayAddresses
  }

  if (c.dht._persistent) {
    // check if we know the route ourself...
    const route = c.dht._router.get(c.target)
    if (route && route.relay !== null) {
      relayAddresses = [{ host: route.relay.host, port: route.relay.port }]
    }
  }

  // 2 is how many parallel connect attempts we want to do, we can make this configurable
  const preConnect = relayAddresses !== null && relayAddresses.length > 0
  const sem = new Semaphore(preConnect ? 3 : 2)
  const signal = sem.signal.bind(sem)
  const tries = relayAddresses !== null ? 2 : 1

  if (preConnect) {
    await sem.wait()
    connectThroughNodes(c, relayAddresses, null).then(signal, signal)
  }

  try {
    for (let i = 0; i < tries && !isDone(c) && !c.connect; i++) {
      c.query = c.dht.findPeer(c.target, {
        hash: false,
        session: c.session,
        nodes: relayAddresses,
        retries: 3
      })

      for await (const data of c.query) {
        await sem.wait()
        if (isDone(c)) return

        if (c.connect) {
          sem.signal()
          break
        }

        // Skip node already run via preConnect
        if (preConnect && relayAddresses && isRelayAddress(relayAddresses, data)) {
          sem.signal()
          continue
        }

        c.remoteRelayAddresses.push(data.from)
        attempts++
        connectThroughNode(c, data.from, null).then(signal, signal)
      }

      relayAddresses = null

      if (attempts > 0) await sem.flush()
    }

    c.query = null
    if (isDone(c)) return

    // flush the semaphore
    await sem.flush()
    if (isDone(c)) return
  } catch (err) {
    c.query = null
    maybeDestroyEncryptedSocket(c, err)
    return
  }

  if (!c.connect) {
    maybeDestroyEncryptedSocket(c, attempts ? PEER_CONNECTION_FAILED() : PEER_NOT_FOUND())
  }
}

async function connectThroughNode(c, address, socket) {
  if (!c.requesting) {
    // If we have a stable server address, send it over now
    const addr = c.dht.remoteAddress()
    const localAddrs = c.lan ? Holepuncher.localAddresses(c.dht.io.serverSocket) : null
    const addresses4 = []

    if (addr) addresses4.push(addr)
    if (localAddrs) addresses4.push(...localAddrs)

    c.firewall = addr ? FIREWALL.OPEN : FIREWALL.UNKNOWN
    c.requesting = true
    c.request = await c.handshake.send({
      error: ERROR.NONE,
      firewall: c.firewall,
      holepunch: null,
      addresses4,
      addresses6: [],
      udx: {
        reusableSocket: c.reusableSocket,
        id: c.rawStream.id,
        seq: 0
      },
      secretStream: {},
      relayThrough: c.relayThrough ? { publicKey: c.relayThrough, token: c.relayToken } : null
    })
    if (isDone(c)) return
  }

  const { serverAddress, clientAddress, relayed, noise } = await c.dht._router.peerHandshake(
    c.target,
    { noise: c.request, socket, session: c.session },
    address
  )
  if (isDone(c) || c.connect) return

  const payload = await c.handshake.recv(noise)
  if (isDone(c) || !payload) return

  if (payload.version !== 1) {
    maybeDestroyEncryptedSocket(c, SERVER_INCOMPATIBLE())
    return
  }
  if (payload.error !== ERROR.NONE) {
    maybeDestroyEncryptedSocket(c, SERVER_ERROR())
    return
  }
  if (!payload.udx) {
    maybeDestroyEncryptedSocket(c, SERVER_ERROR('Server did not send UDX data'))
    return
  }

  const hs = c.handshake.final()

  c.handshake = null
  c.request = null
  c.requesting = false
  c.connect = {
    relayed,
    relayAddress: address,
    clientAddress,
    serverAddress,
    payload
  }

  c.payload = new SecurePayload(hs.holepunchSecret)

  c.onsocket = function (socket, port, host) {
    if (c.rawStream === null) return // Already hole punched

    if (c.rawStream.connected) {
      const remoteChanging = c.rawStream.changeRemote(socket, c.connect.payload.udx.id, port, host)

      if (remoteChanging) remoteChanging.catch(safetyCatch)
    } else {
      // cache the relay addrs for a future reconnect, we prefer the remote one so they
      // can give us the correct ones from their pov
      if (payload.relayAddresses && payload.relayAddresses.length) {
        c.dht._relayAddressesCache.set(c.id, payload.relayAddresses)
      } else if (c.remoteRelayAddresses.length) {
        c.dht._relayAddressesCache.set(c.id, c.remoteRelayAddresses)
      }

      c.rawStream.connect(socket, c.connect.payload.udx.id, port, host)
      c.encryptedSocket.start(c.rawStream, { handshake: hs })
    }

    if (c.reusableSocket && payload.udx.reusableSocket) {
      c.dht._socketPool.routes.add(c.remotePublicKey, c.rawStream)
    }

    if (c.puncher) {
      c.puncher.onabort = noop
      c.puncher.destroy()
    }

    if (c.passiveConnectTimeout) {
      clearPassiveConnectTimeout(c)
    }

    c.rawStream = null
  }

  if (payload.relayThrough || c.relayThrough) {
    relayConnection(c, c.relayThrough, payload, hs)
  }

  if (c.serverSocket) {
    c.onsocket(c.serverSocket, c.serverAddress.port, c.serverAddress.host)
    return
  }

  if (!relayed) {
    c.onsocket(socket || c.dht.socket, address.port, address.host)
  }

  c.session.destroy()
}

async function updateHolepunch(c, peerAddress, relayAddr, payload) {
  const holepunch = await c.dht._router.peerHolepunch(
    c.target,
    {
      id: c.connect.payload.holepunch.id,
      payload: c.payload.encrypt(payload),
      peerAddress,
      socket: c.puncher.socket,
      session: c.session
    },
    relayAddr
  )

  if (isDone(c)) return null

  const remotePayload = c.payload.decrypt(holepunch.payload)
  if (!remotePayload) {
    throw HOLEPUNCH_INVALID()
  }

  const { error, firewall, punching, addresses, remoteToken } = remotePayload

  if (error === ERROR.TRY_LATER && c.relayToken && payload.punching) {
    return {
      tryLater: true,
      ...holepunch,
      payload: remotePayload
    }
  }

  if (error !== ERROR.NONE) {
    throw REMOTE_ABORTED('Remote aborted with error code ' + error)
  }

  const echoed = !!(remoteToken && payload.token && b4a.equals(remoteToken, payload.token))

  c.puncher.updateRemote({
    punching,
    firewall,
    addresses,
    verified: echoed ? peerAddress.host : null
  })

  return {
    tryLater: false,
    ...holepunch,
    payload: remotePayload
  }
}

async function probeRound(c, serverAddress, serverRelay, retry) {
  // Open a quick low ttl session against what we think is the server
  if (serverAddress) await c.puncher.openSession(serverAddress)

  if (isDone(c)) return null

  const reply = await updateHolepunch(c, serverRelay.peerAddress, serverRelay.relayAddress, {
    error: ERROR.NONE,
    firewall: c.puncher.nat.firewall,
    round: c.round++,
    connected: false,
    punching: false,
    addresses: c.puncher.nat.addresses,
    remoteAddress: serverAddress,
    token: null,
    remoteToken: null
  })

  if (isDone(c) || !reply) return null

  const { peerAddress } = reply
  const { address, token } = reply.payload

  c.puncher.nat.add(reply.to, reply.from)

  // Open another quick low ttl session against what the server says their address is,
  // if they haven't said they are random yet
  if (
    c.puncher.remoteFirewall < FIREWALL.RANDOM &&
    address &&
    address.host &&
    address.port &&
    diffAddress(address, serverAddress)
  ) {
    await c.puncher.openSession(address)
    if (isDone(c)) return null
  }

  // If the remote told us they didn't know their nat firewall yet, give them a chance to figure it out
  // They might say this to see if the "fast mode" punch comes through first.
  if (c.puncher.remoteFirewall === FIREWALL.UNKNOWN) {
    await c.sleeper.pause(1000)
    if (isDone(c)) return null
  }

  let stable = await c.puncher.analyze(false)
  if (isDone(c)) return null

  // If the socket seems unstable, try to make it stable by setting the "allowReopen" flag
  // Mostly relevant for mobile networks
  if (!stable) {
    stable = await c.puncher.analyze(true)
    if (isDone(c)) return null
    if (stable) return probeRound(c, serverAddress, serverRelay, false)
  }

  if ((c.puncher.remoteFirewall === FIREWALL.UNKNOWN || !token) && retry) {
    return probeRound(c, serverAddress, serverRelay, false)
  }

  if (
    c.puncher.remoteFirewall === FIREWALL.UNKNOWN ||
    c.puncher.nat.firewall === FIREWALL.UNKNOWN
  ) {
    await abort(c, serverRelay, HOLEPUNCH_PROBE_TIMEOUT())
    return null
  }

  if (c.puncher.remoteFirewall >= FIREWALL.RANDOM && c.puncher.nat.firewall >= FIREWALL.RANDOM) {
    await abort(c, serverRelay, HOLEPUNCH_DOUBLE_RANDOMIZED_NATS())
    return null
  }

  return { token, peerAddress }
}

async function roundPunch(c, serverAddress, remoteToken, clientRelay, serverRelay, delayed) {
  // We are gossiping our final NAT status to the other peer now
  // so make sure we don't update our local view for now as that can make things weird
  c.puncher.nat.freeze()

  const isRandom =
    c.puncher.remoteFirewall >= FIREWALL.RANDOM || c.puncher.nat.firewall >= FIREWALL.RANDOM
  if (isRandom) {
    while (
      c.dht._randomPunches >= c.dht._randomPunchLimit ||
      Date.now() - c.dht._lastRandomPunch < c.dht._randomPunchInterval
    ) {
      // if no relay can help, bail
      if (!c.relayToken) throw HOLEPUNCH_ABORTED()

      if (!delayed) {
        delayed = true
        await updateHolepunch(c, serverAddress, clientRelay, {
          error: ERROR.NONE,
          firewall: c.puncher.nat.firewall,
          round: c.round++,
          connected: false,
          punching: false,
          addresses: c.puncher.nat.addresses,
          remoteAddress: null,
          token: c.payload.token(serverAddress),
          remoteToken
        })
        if (isDone(c)) return
      }

      await tryLater(c)
      if (isDone(c)) return
    }
  }

  // increment now, so we can commit to punching
  if (isRandom) c.dht._randomPunches++

  let reply

  try {
    // if delayed switch to the servers chosen relay - we validated anyway
    reply = await updateHolepunch(
      c,
      delayed ? serverRelay.peerAddress : serverAddress,
      delayed ? serverRelay.relayAddress : clientRelay,
      {
        error: ERROR.NONE,
        firewall: c.puncher.nat.firewall,
        round: c.round++,
        connected: false,
        punching: true,
        addresses: c.puncher.nat.addresses,
        remoteAddress: null,
        token: delayed ? null : c.payload.token(serverAddress),
        remoteToken
      }
    )
  } finally {
    // decrement as punch increments for us
    if (isRandom) c.dht._randomPunches--
  }

  if (isDone(c)) return
  if (!reply) return

  if (reply.tryLater) {
    await tryLater(c)
    if (isDone(c)) return
    return roundPunch(c, serverAddress, remoteToken, clientRelay, serverRelay, true)
  }

  if (!c.puncher.remoteHolepunching) {
    throw REMOTE_NOT_HOLEPUNCHING()
  }

  if (!(await c.puncher.punch())) {
    throw REMOTE_NOT_HOLEPUNCHABLE()
  }
}

async function tryLater(c) {
  if (!c.relayToken) throw HOLEPUNCH_ABORTED()
  await c.sleeper.pause(10000 + Math.round(Math.random() * 10000))
}

function maybeDestroyEncryptedSocket(c, err) {
  if (isDone(c)) return
  if (c.encryptedSocket.rawStream) return
  if (c.relaySocket) return // waiting for the relay
  if (c.puncher && !c.puncher.destroyed) return // waiting for the puncher
  c.session.destroy()
  c.encryptedSocket.destroy(err)
}

async function abort(c, { peerAddress, relayAddress }, err) {
  try {
    await updateHolepunch(c, peerAddress, relayAddress, {
      error: ERROR.ABORTED,
      firewall: FIREWALL.UNKNOWN,
      round: c.round++,
      connected: false,
      punching: false,
      addresses: null,
      remoteAddress: null,
      token: null,
      remoteToken: null
    })
  } catch (err) {
    safetyCatch(err)
  }

  destroyPuncher(c)
  maybeDestroyEncryptedSocket(c, err)
}

function relayConnection(c, relayThrough, payload, hs) {
  let isInitiator
  let publicKey
  let token

  if (payload.relayThrough) {
    isInitiator = false
    publicKey = payload.relayThrough.publicKey
    token = payload.relayThrough.token
  } else {
    isInitiator = true
    publicKey = relayThrough
    token = c.relayToken
  }

  c.relayToken = token
  c.relaySocket = c.dht.connect(publicKey)
  c.relaySocket.setKeepAlive(c.relayKeepAlive)
  c.relayClient = relay.Client.from(c.relaySocket, { id: c.relaySocket.publicKey })
  c.relayTimeout = setTimeout(onabort, 15000, null)

  c.relayClient.pair(isInitiator, token, c.rawStream).on('error', onabort).on('data', ondata)

  function ondata(remoteId) {
    if (c.relayTimeout) clearRelayTimeout(c)
    if (c.rawStream === null) {
      onabort(null)
      return
    }

    c.relayPaired = true

    const { remotePort, remoteHost, socket } = c.relaySocket.rawStream

    c.rawStream
      .on('close', () => c.relaySocket.destroy())
      .connect(socket, remoteId, remotePort, remoteHost)

    c.encryptedSocket.start(c.rawStream, { handshake: hs })
  }

  function onabort(err) {
    if (c.relayTimeout) clearRelayTimeout(c)
    const socket = c.relaySocket
    c.relayToken = null
    c.relaySocket = null
    if (socket) socket.destroy()
    maybeDestroyEncryptedSocket(c, err || RELAY_ABORTED())
  }
}

function clearPassiveConnectTimeout(c) {
  clearTimeout(c.passiveConnectTimeout)
  c.passiveConnectTimeout = null
}

function clearRelayTimeout(c) {
  clearTimeout(c.relayTimeout)
  c.relayTimeout = null
}

function destroyPuncher(c) {
  if (c.puncher) c.puncher.destroy()
  c.session.destroy()
}

function pickServerRelay(relays, clientRelay) {
  for (const r of relays) {
    if (!diffAddress(r.relayAddress, clientRelay)) return r
  }
  return relays[0]
}

function diffAddress(a, b) {
  return a.host !== b.host || a.port !== b.port
}

function defaultCreateHandshake(keyPair, remotePublicKey) {
  return new NoiseWrap(keyPair, remotePublicKey)
}

function defaultCreateSecretStream(isInitiator, rawStream, opts) {
  return new NoiseSecretStream(isInitiator, rawStream, opts)
}

function onlyNonReserved(addr) {
  return !isReserved(addr.host)
}

function isRelay(relaySocket, socket, port, host) {
  const stream = relaySocket.rawStream
  if (!stream) return false
  if (stream.socket !== socket) return false
  return port === stream.remotePort && host === stream.remoteHost
}

function selectRelay(relayThrough) {
  if (typeof relayThrough === 'function') relayThrough = relayThrough()
  if (relayThrough === null) return null
  if (Array.isArray(relayThrough))
    return relayThrough[Math.floor(Math.random() * relayThrough.length)]
  return relayThrough
}

function isRelayAddress(relayAddresses, data) {
  for (const node of relayAddresses) {
    if (node.host === data.from.host && node.port === data.from.port) return true
  }

  return false
}

function noop() {}
const EventEmitter = require('events')
const b4a = require('b4a')
const errors = require('./errors')

module.exports = class ConnectionPool extends EventEmitter {
  constructor(dht) {
    super()

    this._dht = dht
    this._servers = new Map()
    this._connecting = new Map()
    this._connections = new Map()
  }

  _attachServer(server) {
    const keyString = b4a.toString(server.publicKey, 'hex')

    this._servers.set(keyString, server)

    server
      .on('close', () => {
        this._servers.delete(keyString)
      })
      .on('connection', (socket) => {
        this._attachStream(socket, true)
      })
  }

  _attachStream(stream, opened) {
    const existing = this.get(stream.remotePublicKey)

    if (existing) {
      const keepNew =
        stream.isInitiator === existing.isInitiator ||
        b4a.compare(stream.publicKey, stream.remotePublicKey) > 0

      if (keepNew) {
        let closed = false

        const onclose = () => {
          closed = true
        }

        existing
          .on('error', noop)
          .on('close', () => {
            if (closed) return

            stream.off('error', noop).off('close', onclose)

            this._attachStream(stream, opened)
          })
          .destroy(errors.DUPLICATE_CONNECTION())

        stream.on('error', noop).on('close', onclose)
      } else {
        stream.on('error', noop).destroy(errors.DUPLICATE_CONNECTION())
      }

      return
    }

    const session = new ConnectionRef(this, stream)

    const keyString = b4a.toString(stream.remotePublicKey, 'hex')

    if (opened) {
      this._connections.set(keyString, session)

      stream.on('close', () => {
        this._connections.delete(keyString)
      })

      this.emit('connection', stream, session)
    } else {
      this._connecting.set(keyString, session)

      stream
        .on('error', noop)
        .on('close', () => {
          if (opened) this._connections.delete(keyString)
          else this._connecting.delete(keyString)
        })
        .on('open', () => {
          opened = true

          this._connecting.delete(keyString)
          this._connections.set(keyString, session)

          stream.off('error', noop)

          this.emit('connection', stream, session)
        })
    }

    return session
  }

  get connecting() {
    return this._connecting.size
  }

  get connections() {
    return this._connections.values()
  }

  has(publicKey) {
    const keyString = b4a.toString(publicKey, 'hex')

    return this._connections.has(keyString) || this._connecting.has(keyString)
  }

  get(publicKey) {
    const keyString = b4a.toString(publicKey, 'hex')

    const existing = this._connections.get(keyString) || this._connecting.get(keyString)

    return existing?._stream || null
  }
}

class ConnectionRef {
  constructor(pool, stream) {
    this._pool = pool
    this._stream = stream
    this._refs = 0
  }

  active() {
    this._refs++
  }

  inactive() {
    this._refs--
  }

  release() {
    this._stream.destroy()
  }
}

function noop() {}
const crypto = require('hypercore-crypto')

const COMMANDS = (exports.COMMANDS = {
  PEER_HANDSHAKE: 0,
  PEER_HOLEPUNCH: 1,
  FIND_PEER: 2,
  LOOKUP: 3,
  ANNOUNCE: 4,
  UNANNOUNCE: 5,
  MUTABLE_PUT: 6,
  MUTABLE_GET: 7,
  IMMUTABLE_PUT: 8,
  IMMUTABLE_GET: 9,
  PLUGIN: 10
})

exports.BOOTSTRAP_NODES = global.Pear?.config.dht?.bootstrap || [
  '88.99.3.86@node1.hyperdht.org:49737',
  '142.93.90.113@node2.hyperdht.org:49737',
  '138.68.147.8@node3.hyperdht.org:49737'
]

exports.KNOWN_NODES = global.Pear?.config.dht?.nodes || []

exports.FIREWALL = {
  UNKNOWN: 0,
  OPEN: 1,
  CONSISTENT: 2,
  RANDOM: 3
}

exports.ERROR = {
  // noise / connection related
  NONE: 0,
  ABORTED: 1,
  VERSION_MISMATCH: 2,
  TRY_LATER: 3,
  // dht related
  SEQ_REUSED: 16,
  SEQ_TOO_LOW: 17
}

const [
  NS_ANNOUNCE,
  NS_UNANNOUNCE,
  NS_MUTABLE_PUT,
  NS_PEER_HANDSHAKE,
  NS_PEER_HOLEPUNCH,
  NS_PLUGIN
] = crypto.namespace('hyperswarm/dht', [
  COMMANDS.ANNOUNCE,
  COMMANDS.UNANNOUNCE,
  COMMANDS.MUTABLE_PUT,
  COMMANDS.PEER_HANDSHAKE,
  COMMANDS.PEER_HOLEPUNCH,
  COMMANDS.PLUGIN
])

exports.NS = {
  ANNOUNCE: NS_ANNOUNCE,
  UNANNOUNCE: NS_UNANNOUNCE,
  MUTABLE_PUT: NS_MUTABLE_PUT,
  PEER_HANDSHAKE: NS_PEER_HANDSHAKE,
  PEER_HOLEPUNCH: NS_PEER_HOLEPUNCH,
  PLUGIN: NS_PLUGIN
}
const sodium = require('sodium-universal')
const b4a = require('b4a')

function hash(data) {
  const out = b4a.allocUnsafe(32)
  sodium.crypto_generichash(out, data)
  return out
}

function unslabbedHash(data) {
  const out = b4a.allocUnsafeSlow(32)
  sodium.crypto_generichash(out, data)
  return out
}

function createKeyPair(seed) {
  const publicKey = b4a.alloc(32)
  const secretKey = b4a.alloc(64)
  if (seed) sodium.crypto_sign_seed_keypair(publicKey, secretKey, seed)
  else sodium.crypto_sign_keypair(publicKey, secretKey)
  return { publicKey, secretKey }
}

module.exports = {
  hash,
  unslabbedHash,
  createKeyPair
}
const b4a = require('b4a')
const cenc = require('compact-encoding')

function encodeUnslab(enc, m) {
  // Faster than unslab(c.encode(enc, data)) because it avoids the mem copy.
  // Makes sense to put in compact-encoding when we need it in other modules too
  const state = cenc.state()
  enc.preencode(state, m)
  state.buffer = b4a.allocUnsafeSlow(state.end)
  enc.encode(state, m)
  return state.buffer
}

module.exports = {
  encodeUnslab
}
module.exports = class DHTError extends Error {
  constructor(msg, code, fn = DHTError) {
    super(`${code}: ${msg}`)
    this.code = code

    if (Error.captureStackTrace) {
      Error.captureStackTrace(this, fn)
    }
  }

  get name() {
    return 'DHTError'
  }

  static BAD_HANDSHAKE_REPLY(msg = 'Bad handshake reply') {
    return new DHTError(msg, 'BAD_HANDSHAKE_REPLY', DHTError.BAD_HANDSHAKE_REPLY)
  }

  static BAD_HOLEPUNCH_REPLY(msg = 'Bad holepunch reply') {
    return new DHTError(msg, 'BAD_HOLEPUNCH_REPLY', DHTError.BAD_HOLEPUNCH_REPLY)
  }

  static HOLEPUNCH_ABORTED(msg = 'Holepunch aborted') {
    return new DHTError(msg, 'HOLEPUNCH_ABORTED', DHTError.HOLEPUNCH_ABORTED)
  }

  static HOLEPUNCH_INVALID(msg = 'Invalid holepunch payload') {
    return new DHTError(msg, 'HOLEPUNCH_INVALID', DHTError.HOLEPUNCH_INVALID)
  }

  static HOLEPUNCH_PROBE_TIMEOUT(msg = 'Holepunching probe did not finish in time') {
    return new DHTError(msg, 'HOLEPUNCH_PROBE_TIMEOUT', DHTError.HOLEPUNCH_PROBE_TIMEOUT)
  }

  static HOLEPUNCH_DOUBLE_RANDOMIZED_NATS(msg = 'Both remote and local NATs are randomized') {
    return new DHTError(
      msg,
      'HOLEPUNCH_DOUBLE_RANDOMIZED_NATS',
      DHTError.HOLEPUNCH_DOUBLE_RANDOMIZED_NATS
    )
  }

  static CANNOT_HOLEPUNCH(msg = 'Cannot holepunch to remote') {
    return new DHTError(msg, 'CANNOT_HOLEPUNCH', DHTError.CANNOT_HOLEPUNCH)
  }

  static REMOTE_NOT_HOLEPUNCHING(msg = 'Remote is not holepunching') {
    return new DHTError(msg, 'REMOTE_NOT_HOLEPUNCHING', DHTError.REMOTE_NOT_HOLEPUNCHING)
  }

  static REMOTE_NOT_HOLEPUNCHABLE(msg = 'Remote is not holepunchable') {
    return new DHTError(msg, 'REMOTE_NOT_HOLEPUNCHABLE', DHTError.REMOTE_NOT_HOLEPUNCHABLE)
  }

  static REMOTE_ABORTED(msg = 'Remote aborted') {
    return new DHTError(msg, 'REMOTE_ABORTED', DHTError.REMOTE_ABORTED)
  }

  static HANDSHAKE_UNFINISHED(msg = 'Handshake did not finish') {
    return new DHTError(msg, 'HANDSHAKE_UNFINISHED', DHTError.HANDSHAKE_UNFINISHED)
  }

  static HANDSHAKE_INVALID(msg = 'Received invalid handshake') {
    return new DHTError(msg, 'HANDSHAKE_INVALID', DHTError.HANDSHAKE_INVALID)
  }

  static ALREADY_LISTENING(msg = 'Already listening') {
    return new DHTError(msg, 'ALREADY_LISTENING', DHTError.ALREADY_LISTENING)
  }

  static KEYPAIR_ALREADY_USED(msg = 'Keypair already used') {
    return new DHTError(msg, 'KEYPAIR_ALREADY_USED', DHTError.KEYPAIR_ALREADY_USED)
  }

  static NODE_DESTROYED(msg = 'Node destroyed') {
    return new DHTError(msg, 'NODE_DESTROYED', DHTError.NODE_DESTROYED)
  }

  static PEER_CONNECTION_FAILED(msg = 'Could not connect to peer') {
    return new DHTError(msg, 'PEER_CONNECTION_FAILED', DHTError.PEER_CONNECTION_FAILED)
  }

  static PEER_NOT_FOUND(msg = 'Peer not found') {
    return new DHTError(msg, 'PEER_NOT_FOUND', DHTError.PEER_NOT_FOUND)
  }

  static STREAM_NOT_CONNECTED(msg = 'Stream is not connected') {
    return new DHTError(msg, 'STREAM_NOT_CONNECTED', DHTError.STREAM_DISCONNECTED)
  }

  static SERVER_INCOMPATIBLE(msg = 'Server is using an incompatible version') {
    return new DHTError(msg, 'SERVER_INCOMPATIBLE', DHTError.SERVER_INCOMPATIBLE)
  }

  static SERVER_ERROR(msg = 'Server returned an error') {
    return new DHTError(msg, 'SERVER_ERROR', DHTError.SERVER_ERROR)
  }

  static DUPLICATE_CONNECTION(msg = 'Duplicate connection') {
    return new DHTError(msg, 'DUPLICATE_CONNECTION', DHTError.DUPLICATE_CONNECTION)
  }

  static RELAY_ABORTED(msg = 'Relay aborted') {
    return new DHTError(msg, 'RELAY_ABORTED', DHTError.RELAY_ABORTED)
  }

  static SUSPENDED(msg = 'Suspended') {
    return new DHTError(msg, 'SUSPENDED', DHTError.SUSPENDED)
  }
}
const b4a = require('b4a')
const Nat = require('./nat')
const Sleeper = require('./sleeper')
const { FIREWALL } = require('./constants')

const BIRTHDAY_SOCKETS = 256
const HOLEPUNCH = b4a.from([0])
const HOLEPUNCH_TTL = 5
const DEFAULT_TTL = 64
const MAX_REOPENS = 3

module.exports = class Holepuncher {
  constructor(dht, session, isInitiator, remoteFirewall = FIREWALL.UNKNOWN) {
    const holder = dht._socketPool.acquire()

    this.dht = dht
    this.session = session

    this.nat = new Nat(dht, session, holder.socket)
    this.nat.autoSample()

    this.isInitiator = isInitiator

    // events
    this.onconnect = noop
    this.onabort = noop

    this.punching = false
    this.connected = false
    this.destroyed = false
    this.randomized = false

    // track remote state
    this.remoteFirewall = remoteFirewall
    this.remoteAddresses = []
    this.remoteHolepunching = false

    this._sleeper = new Sleeper()
    this._reopening = null
    this._timeout = null
    this._punching = null
    this._allHolders = []
    this._holder = this._addRef(holder)
  }

  get socket() {
    return this._holder.socket
  }

  updateRemote({ punching, firewall, addresses, verified }) {
    const remoteAddresses = []

    if (addresses) {
      for (const addr of addresses) {
        remoteAddresses.push({
          host: addr.host,
          port: addr.port,
          verified: verified === addr.host || this._isVerified(addr.host)
        })
      }
    }

    this.remoteFirewall = firewall
    this.remoteAddresses = remoteAddresses
    this.remoteHolepunching = punching
  }

  _isVerified(host) {
    for (const addr of this.remoteAddresses) {
      if (addr.verified && addr.host === host) {
        return true
      }
    }
    return false
  }

  ping(addr, socket = this._holder.socket) {
    return holepunch(socket, addr, false)
  }

  openSession(addr, socket = this._holder.socket) {
    return holepunch(socket, addr, true)
  }

  async analyze(allowReopen) {
    await this.nat.analyzing
    if (this._unstable()) {
      if (!allowReopen) return false
      if (!this._reopening) this._reopening = this._reopen()
      return this._reopening
    }
    return true
  }

  _unstable() {
    // TODO!!: We need an additional heuristic here... If we were NOT random in the past we should also do this.
    const firewall = this.nat.firewall
    return (
      (this.remoteFirewall >= FIREWALL.RANDOM && firewall >= FIREWALL.RANDOM) ||
      firewall === FIREWALL.UNKNOWN
    )
  }

  _reset() {
    const prev = this._holder

    this._allHolders.pop()
    this._holder = this._addRef(this.dht._socketPool.acquire())

    prev.release()
    this.nat.destroy()

    this.nat = new Nat(this.dht, this.session, this._holder.socket)
    // TODO: maybe make auto sampling configurable somehow?
    this.nat.autoSample()
  }

  _addRef(ref) {
    this._allHolders.push(ref)
    ref.onholepunchmessage = (msg, rinfo) => this._onholepunchmessage(msg, rinfo, ref)
    return ref
  }

  _onholepunchmessage(_, addr, ref) {
    if (!this.isInitiator) {
      // TODO: we don't need this if we had a way to connect a socket to many hosts
      holepunch(ref.socket, addr, false) // never fails
      return
    }

    if (this.connected) return

    this.connected = true
    this.punching = false

    for (const r of this._allHolders) {
      if (r === ref) continue
      r.release()
    }

    this._allHolders[0] = ref
    while (this._allHolders.length > 1) this._allHolders.pop()

    this._decrementRandomized()
    this.onconnect(ref.socket, addr.port, addr.host)
  }

  _done() {
    return this.destroyed || this.connected
  }

  async _reopen() {
    for (let i = 0; this._unstable() && i < MAX_REOPENS && !this._done() && !this.punching; i++) {
      this._reset()
      await this.nat.analyzing
    }

    return coerceFirewall(this.nat.firewall) === FIREWALL.CONSISTENT
  }

  punch() {
    if (!this._punching) this._punching = this._punch()
    return this._punching
  }

  async _punch() {
    if (this._done() || !this.remoteAddresses.length) return false

    this.punching = true

    // Coerce into consistency for now. Obvs we could make this this more efficient if we use that info
    // but that's seldomly used since those will just use tcp most of the time.

    const local = coerceFirewall(this.nat.firewall)
    const remote = coerceFirewall(this.remoteFirewall)

    // Note that most of these async functions are meant to run in the background
    // which is why we don't await them here and why they are not allowed to throw

    let remoteVerifiedAddress = null
    for (const addr of this.remoteAddresses) {
      if (addr.verified) {
        remoteVerifiedAddress = addr
        break
      }
    }

    if (local === FIREWALL.CONSISTENT && remote === FIREWALL.CONSISTENT) {
      this.dht.stats.punches.consistent++
      this._consistentProbe()
      return true
    }

    if (!remoteVerifiedAddress) return false

    if (local === FIREWALL.CONSISTENT && remote >= FIREWALL.RANDOM) {
      this.dht.stats.punches.random++
      this._incrementRandomized()
      this._randomProbes(remoteVerifiedAddress)
      return true
    }

    if (local >= FIREWALL.RANDOM && remote === FIREWALL.CONSISTENT) {
      this.dht.stats.punches.random++
      this._incrementRandomized()
      await this._openBirthdaySockets(remoteVerifiedAddress)
      if (this.punching) this._keepAliveRandomNat(remoteVerifiedAddress)
      return true
    }

    return false
  }

  // Note that this never throws so it is safe to run in the background
  async _consistentProbe() {
    // Here we do the sleep first because the "fast open" mode in the server just fired a ping
    if (!this.isInitiator) await this._sleeper.pause(1000)

    let tries = 0

    while (this.punching && tries++ < 10) {
      for (const addr of this.remoteAddresses) {
        // only try unverified addresses every 4 ticks
        if (!addr.verified && (tries & 3) !== 0) continue
        await holepunch(this._holder.socket, addr, false)
      }
      if (this.punching) await this._sleeper.pause(1000)
    }

    this._autoDestroy()
  }

  // Note that this never throws so it is safe to run in the background
  async _randomProbes(remoteAddr) {
    let tries = 1750 // ~35s

    while (this.punching && tries-- > 0) {
      const addr = { host: remoteAddr.host, port: randomPort() }
      await holepunch(this._holder.socket, addr, false)
      if (this.punching) await this._sleeper.pause(20)
    }

    this._autoDestroy()
  }

  // Note that this never throws so it is safe to run in the background
  async _keepAliveRandomNat(remoteAddr) {
    let i = 0
    let lowTTLRounds = 1

    // TODO: experiment with this here. We just bursted all the messages in
    // openOtherSockets to ensure the sockets are open, so it's potentially
    // a good idea to slow down for a bit.
    await this._sleeper.pause(100)

    let tries = 1750 // ~35s

    while (this.punching && tries-- > 0) {
      if (i === this._allHolders.length) {
        i = 0
        if (lowTTLRounds > 0) lowTTLRounds--
      }

      await holepunch(this._allHolders[i++].socket, remoteAddr, lowTTLRounds > 0)
      if (this.punching) await this._sleeper.pause(20)
    }

    this._autoDestroy()
  }

  async _openBirthdaySockets(remoteAddr) {
    while (this.punching && this._allHolders.length < BIRTHDAY_SOCKETS) {
      const ref = this._addRef(this.dht._socketPool.acquire())
      await holepunch(ref.socket, remoteAddr, HOLEPUNCH_TTL)
    }
  }

  _autoDestroy() {
    if (!this.connected) this.destroy()
  }

  _incrementRandomized() {
    if (!this.randomized) {
      this.randomized = true
      this.dht._randomPunches++
    }
  }

  _decrementRandomized() {
    if (this.randomized) {
      this.dht._lastRandomPunch = Date.now()
      this.randomized = false
      this.dht._randomPunches--
    }
  }

  destroy() {
    if (this.destroyed) return
    this.destroyed = true
    this.punching = false

    for (const ref of this._allHolders) ref.release()
    this._allHolders = []
    this.nat.destroy()

    if (!this.connected) {
      this._decrementRandomized()
      this.onabort()
    }
  }

  static ping(socket, addr) {
    return holepunch(socket, addr, false)
  }

  static localAddresses(socket) {
    return localAddresses(socket)
  }

  static matchAddress(myAddresses, externalAddresses) {
    return matchAddress(myAddresses, externalAddresses)
  }
}

function holepunch(socket, addr, lowTTL) {
  return socket.send(HOLEPUNCH, addr.port, addr.host, lowTTL ? HOLEPUNCH_TTL : DEFAULT_TTL)
}

function randomPort() {
  return (1000 + Math.random() * 64536) | 0
}

function coerceFirewall(fw) {
  return fw === FIREWALL.OPEN ? FIREWALL.CONSISTENT : fw
}

function localAddresses(socket) {
  const addrs = []
  const { host, port } = socket.address()

  if (host === '127.0.0.1') return [{ host, port }]

  for (const n of socket.udx.networkInterfaces()) {
    if (n.family !== 4 || n.internal) continue

    addrs.push({ host: n.host, port })
  }

  if (addrs.length === 0) {
    addrs.push({ host: '127.0.0.1', port })
  }

  return addrs
}

function matchAddress(localAddresses, remoteLocalAddresses) {
  if (remoteLocalAddresses.length === 0) return null

  let best = { segment: 1, addr: null }

  for (const localAddress of localAddresses) {
    // => 192.168.122.238
    const a = localAddress.host.split('.')

    for (const remoteAddress of remoteLocalAddresses) {
      // => 192.168.0.23
      // => 192.168.122.1
      const b = remoteAddress.host.split('.')

      // Matches 192.*.*.*
      if (a[0] === b[0]) {
        if (best.segment === 1) best = { segment: 2, addr: remoteAddress }

        // Matches 192.168.*.*
        if (a[1] === b[1]) {
          if (best.segment === 2) best = { segment: 3, addr: remoteAddress }

          // Matches 192.168.122.*
          if (a[2] === b[2]) return remoteAddress
        }
      }
    }
  }

  return best.addr
}

function noop() {}
const c = require('compact-encoding')

const ipv4 = {
  ...c.ipv4Address,
  decode(state) {
    const ip = c.ipv4Address.decode(state)
    return {
      host: ip.host,
      port: ip.port
    }
  }
}

const ipv4Array = c.array(ipv4)

const ipv6 = {
  ...c.ipv6Address,
  decode(state) {
    const ip = c.ipv6Address.decode(state)
    return {
      host: ip.host,
      port: ip.port
    }
  }
}

const ipv6Array = c.array(ipv6)

exports.handshake = {
  preencode(state, m) {
    state.end += 1 + 1 + (m.peerAddress ? 6 : 0) + (m.relayAddress ? 6 : 0)
    c.buffer.preencode(state, m.noise)
  },
  encode(state, m) {
    const flags = (m.peerAddress ? 1 : 0) | (m.relayAddress ? 2 : 0)

    c.uint.encode(state, flags)
    c.uint.encode(state, m.mode)
    c.buffer.encode(state, m.noise)

    if (m.peerAddress) ipv4.encode(state, m.peerAddress)
    if (m.relayAddress) ipv4.encode(state, m.relayAddress)
  },
  decode(state) {
    const flags = c.uint.decode(state)

    return {
      mode: c.uint.decode(state),
      noise: c.buffer.decode(state),
      peerAddress: flags & 1 ? ipv4.decode(state) : null,
      relayAddress: flags & 2 ? ipv4.decode(state) : null
    }
  }
}

const relayInfo = {
  preencode(state, m) {
    state.end += 12
  },
  encode(state, m) {
    ipv4.encode(state, m.relayAddress)
    ipv4.encode(state, m.peerAddress)
  },
  decode(state) {
    return {
      relayAddress: ipv4.decode(state),
      peerAddress: ipv4.decode(state)
    }
  }
}

const relayInfoArray = c.array(relayInfo)

const holepunchInfo = {
  preencode(state, m) {
    c.uint.preencode(state, m.id)
    relayInfoArray.preencode(state, m.relays)
  },
  encode(state, m) {
    c.uint.encode(state, m.id)
    relayInfoArray.encode(state, m.relays)
  },
  decode(state) {
    return {
      id: c.uint.decode(state),
      relays: relayInfoArray.decode(state)
    }
  }
}

const udxInfo = {
  preencode(state, m) {
    state.end += 2 // version + features
    c.uint.preencode(state, m.id)
    c.uint.preencode(state, m.seq)
  },
  encode(state, m) {
    c.uint.encode(state, 1)
    c.uint.encode(state, m.reusableSocket ? 1 : 0)
    c.uint.encode(state, m.id)
    c.uint.encode(state, m.seq)
  },
  decode(state) {
    const version = c.uint.decode(state)
    const features = c.uint.decode(state)

    return {
      version,
      reusableSocket: (features & 1) !== 0,
      id: c.uint.decode(state),
      seq: c.uint.decode(state)
    }
  }
}

const secretStreamInfo = {
  preencode(state, m) {
    c.uint.preencode(state, 1)
  },
  encode(state, m) {
    c.uint.encode(state, 1)
  },
  decode(state) {
    return {
      version: c.uint.decode(state)
    }
  }
}

const relayThroughInfo = {
  preencode(state, m) {
    c.uint.preencode(state, 1) // version
    c.uint.preencode(state, 0) // flags
    c.fixed32.preencode(state, m.publicKey)
    c.fixed32.preencode(state, m.token)
  },
  encode(state, m) {
    c.uint.encode(state, 1)
    c.uint.encode(state, 0)
    c.fixed32.encode(state, m.publicKey)
    c.fixed32.encode(state, m.token)
  },
  decode(state) {
    const version = c.uint.decode(state)
    c.uint.decode(state)

    return {
      version,
      publicKey: c.fixed32.decode(state),
      token: c.fixed32.decode(state)
    }
  }
}

exports.noisePayload = {
  preencode(state, m) {
    state.end += 4 // version + flags + error + firewall
    if (m.holepunch) holepunchInfo.preencode(state, m.holepunch)
    if (m.addresses4 && m.addresses4.length) ipv4Array.preencode(state, m.addresses4)
    if (m.addresses6 && m.addresses6.length) ipv6Array.preencode(state, m.addresses6)
    if (m.udx) udxInfo.preencode(state, m.udx)
    if (m.secretStream) secretStreamInfo.preencode(state, m.secretStream)
    if (m.relayThrough) relayThroughInfo.preencode(state, m.relayThrough)
    if (m.relayAddresses) ipv4Array.preencode(state, m.relayAddresses)
  },
  encode(state, m) {
    let flags = 0

    if (m.holepunch) flags |= 1
    if (m.addresses4 && m.addresses4.length) flags |= 2
    if (m.addresses6 && m.addresses6.length) flags |= 4
    if (m.udx) flags |= 8
    if (m.secretStream) flags |= 16
    if (m.relayThrough) flags |= 32
    if (m.relayAddresses) flags |= 64

    c.uint.encode(state, 1) // version
    c.uint.encode(state, flags)
    c.uint.encode(state, m.error)
    c.uint.encode(state, m.firewall)

    if (m.holepunch) holepunchInfo.encode(state, m.holepunch)
    if (m.addresses4 && m.addresses4.length) ipv4Array.encode(state, m.addresses4)
    if (m.addresses6 && m.addresses6.length) ipv6Array.encode(state, m.addresses6)
    if (m.udx) udxInfo.encode(state, m.udx)
    if (m.secretStream) secretStreamInfo.encode(state, m.secretStream)
    if (m.relayThrough) relayThroughInfo.encode(state, m.relayThrough)
    if (m.relayAddresses) ipv4Array.encode(state, m.relayAddresses)
  },
  decode(state) {
    const version = c.uint.decode(state)

    if (version !== 1) {
      // Do not attempt to decode but return this back to the user so they can
      // actually handle it
      return {
        version,
        error: 0,
        firewall: 0,
        holepunch: null,
        addresses4: [],
        addresses6: [],
        udx: null,
        secretStream: null,
        relayThrough: null,
        relayAddresses: null
      }
    }

    const flags = c.uint.decode(state)

    return {
      version,
      error: c.uint.decode(state),
      firewall: c.uint.decode(state),
      holepunch: (flags & 1) !== 0 ? holepunchInfo.decode(state) : null,
      addresses4: (flags & 2) !== 0 ? ipv4Array.decode(state) : [],
      addresses6: (flags & 4) !== 0 ? ipv6Array.decode(state) : [],
      udx: (flags & 8) !== 0 ? udxInfo.decode(state) : null,
      secretStream: (flags & 16) !== 0 ? secretStreamInfo.decode(state) : null,
      relayThrough: (flags & 32) !== 0 ? relayThroughInfo.decode(state) : null,
      relayAddresses: (flags & 64) !== 0 ? ipv4Array.decode(state) : null
    }
  }
}

exports.holepunch = {
  preencode(state, m) {
    state.end += 2
    c.uint.preencode(state, m.id)
    c.buffer.preencode(state, m.payload)
    if (m.peerAddress) ipv4.preencode(state, m.peerAddress)
  },
  encode(state, m) {
    const flags = m.peerAddress ? 1 : 0
    c.uint.encode(state, flags)
    c.uint.encode(state, m.mode)
    c.uint.encode(state, m.id)
    c.buffer.encode(state, m.payload)
    if (m.peerAddress) ipv4.encode(state, m.peerAddress)
  },
  decode(state) {
    const flags = c.uint.decode(state)
    return {
      mode: c.uint.decode(state),
      id: c.uint.decode(state),
      payload: c.buffer.decode(state),
      peerAddress: flags & 1 ? ipv4.decode(state) : null
    }
  }
}

exports.holepunchPayload = {
  preencode(state, m) {
    state.end += 4 // flags + error + firewall + round
    if (m.addresses) ipv4Array.preencode(state, m.addresses)
    if (m.remoteAddress) state.end += 6
    if (m.token) state.end += 32
    if (m.remoteToken) state.end += 32
  },
  encode(state, m) {
    const flags =
      (m.connected ? 1 : 0) |
      (m.punching ? 2 : 0) |
      (m.addresses ? 4 : 0) |
      (m.remoteAddress ? 8 : 0) |
      (m.token ? 16 : 0) |
      (m.remoteToken ? 32 : 0)

    c.uint.encode(state, flags)
    c.uint.encode(state, m.error)
    c.uint.encode(state, m.firewall)
    c.uint.encode(state, m.round)

    if (m.addresses) ipv4Array.encode(state, m.addresses)
    if (m.remoteAddress) ipv4.encode(state, m.remoteAddress)
    if (m.token) c.fixed32.encode(state, m.token)
    if (m.remoteToken) c.fixed32.encode(state, m.remoteToken)
  },
  decode(state) {
    const flags = c.uint.decode(state)

    return {
      error: c.uint.decode(state),
      firewall: c.uint.decode(state),
      round: c.uint.decode(state),
      connected: (flags & 1) !== 0,
      punching: (flags & 2) !== 0,
      addresses: (flags & 4) !== 0 ? ipv4Array.decode(state) : null,
      remoteAddress: (flags & 8) !== 0 ? ipv4.decode(state) : null,
      token: (flags & 16) !== 0 ? c.fixed32.decode(state) : null,
      remoteToken: (flags & 32) !== 0 ? c.fixed32.decode(state) : null
    }
  }
}

const peer = (exports.peer = {
  preencode(state, m) {
    state.end += 32
    ipv4Array.preencode(state, m.relayAddresses)
  },
  encode(state, m) {
    c.fixed32.encode(state, m.publicKey)
    ipv4Array.encode(state, m.relayAddresses)
  },
  decode(state) {
    return {
      publicKey: c.fixed32.decode(state),
      relayAddresses: ipv4Array.decode(state)
    }
  }
})

const peers = (exports.peers = c.array(peer))

const rawPeers = c.array(c.raw)

exports.lookupRawReply = {
  preencode(state, m) {
    rawPeers.preencode(state, m.peers)
    c.uint.preencode(state, m.bump)
  },
  encode(state, m) {
    rawPeers.encode(state, m.peers)
    c.uint.encode(state, m.bump)
  },
  decode(state) {
    return {
      peers: peers.decode(state),
      bump: state.start < state.end ? c.uint.decode(state) : 0
    }
  }
}

exports.announce = {
  preencode(state, m) {
    state.end++ // flags
    if (m.peer) peer.preencode(state, m.peer)
    if (m.refresh) state.end += 32
    if (m.signature) state.end += 64
    if (m.bump) c.uint.preencode(state, m.bump)
  },
  encode(state, m) {
    const flags = (m.peer ? 1 : 0) | (m.refresh ? 2 : 0) | (m.signature ? 4 : 0) | (m.bump ? 8 : 0)
    c.uint.encode(state, flags)
    if (m.peer) peer.encode(state, m.peer)
    if (m.refresh) c.fixed32.encode(state, m.refresh)
    if (m.signature) c.fixed64.encode(state, m.signature)
    if (m.bump) c.uint.encode(state, m.bump)
  },
  decode(state) {
    const flags = c.uint.decode(state)

    return {
      peer: (flags & 1) !== 0 ? peer.decode(state) : null,
      refresh: (flags & 2) !== 0 ? c.fixed32.decode(state) : null,
      signature: (flags & 4) !== 0 ? c.fixed64.decode(state) : null,
      bump: (flags & 8) !== 0 ? c.uint.decode(state) : 0
    }
  }
}

exports.mutableSignable = {
  preencode(state, m) {
    c.uint.preencode(state, m.seq)
    c.buffer.preencode(state, m.value)
  },
  encode(state, m) {
    c.uint.encode(state, m.seq)
    c.buffer.encode(state, m.value)
  },
  decode(state) {
    return {
      seq: c.uint.decode(state),
      value: c.buffer.decode(state)
    }
  }
}

exports.mutablePutRequest = {
  preencode(state, m) {
    c.fixed32.preencode(state, m.publicKey)
    c.uint.preencode(state, m.seq)
    c.buffer.preencode(state, m.value)
    c.fixed64.preencode(state, m.signature)
  },
  encode(state, m) {
    c.fixed32.encode(state, m.publicKey)
    c.uint.encode(state, m.seq)
    c.buffer.encode(state, m.value)
    c.fixed64.encode(state, m.signature)
  },
  decode(state) {
    return {
      publicKey: c.fixed32.decode(state),
      seq: c.uint.decode(state),
      value: c.buffer.decode(state),
      signature: c.fixed64.decode(state)
    }
  }
}

exports.mutableGetResponse = {
  preencode(state, m) {
    c.uint.preencode(state, m.seq)
    c.buffer.preencode(state, m.value)
    c.fixed64.preencode(state, m.signature)
  },
  encode(state, m) {
    c.uint.encode(state, m.seq)
    c.buffer.encode(state, m.value)
    c.fixed64.encode(state, m.signature)
  },
  decode(state) {
    return {
      seq: c.uint.decode(state),
      value: c.buffer.decode(state),
      signature: c.fixed64.decode(state)
    }
  }
}

exports.pluginRequest = {
  preencode(state, m) {
    c.string.preencode(state, m.plugin)
    c.uint.preencode(state, m.version)
    c.uint.preencode(state, m.command)
    state.end++ // max flag is 1 so always one byte

    if (m.value) c.buffer.preencode(state, m.value)
  },
  encode(state, m) {
    const flags = m.value ? 1 : 0

    c.string.encode(state, m.plugin)
    c.uint.encode(state, m.version)
    c.uint.encode(state, m.command)
    c.uint.encode(state, flags)

    if (m.value) c.buffer.encode(state, m.value)
  },
  decode(state) {
    const r0 = c.string.decode(state)
    const r1 = c.uint.decode(state)
    const r2 = c.uint.decode(state)
    const flags = c.uint.decode(state)

    return {
      plugin: r0,
      version: r1,
      command: r2,
      value: (flags & 1) !== 0 ? c.buffer.decode(state) : null
    }
  }
}
const { FIREWALL } = require('../lib/constants')

module.exports = class Nat {
  constructor(dht, session, socket) {
    this._samplesHost = []
    this._samplesFull = []
    this._visited = new Map()
    this._resolve = null
    this._minSamples = 4
    this._autoSampling = false

    this.dht = dht
    this.session = session
    this.socket = socket

    this.sampled = 0
    this.firewall = dht.firewalled ? FIREWALL.UNKNOWN : FIREWALL.OPEN
    this.addresses = null

    this.analyzing = new Promise((resolve) => {
      this._resolve = resolve
    })
  }

  autoSample(retry = true) {
    if (this._autoSampling) return
    this._autoSampling = true

    const self = this
    const socket = this.socket
    const maxPings = this._minSamples

    let skip = this.dht.nodes.length >= 8 ? 5 : 0
    let pending = 0

    // TODO: it would be best to pick the nodes to help us based on latency to us
    // That should reduce connect latency in general. We should investigate tracking that later on.

    // TODO 2: try to pick nodes with different IPs as well, as that'll help multi IP cell connections...
    // If we expose this from the nat sampler then the DHT should be able to help us filter out scams as well...

    for (
      let node = this.dht.nodes.latest;
      node && this.sampled + pending < maxPings;
      node = node.prev
    ) {
      if (skip > 0) {
        skip--
        continue
      }

      const ref = node.host + ':' + node.port

      if (this._visited.has(ref)) continue
      this._visited.set(ref, 1)

      pending++
      this.session.ping(node, { socket, retry: false }).then(onpong, onskip)
    }

    pending++
    onskip()

    function onpong(res) {
      self.add(res.to, res.from)
      onskip()
    }

    function onskip() {
      if (--pending === 0 && self.sampled < self._minSamples) {
        if (retry) {
          self._autoSampling = false
          self.autoSample(false)
          return
        }
        self._resolve()
      }
    }
  }

  destroy() {
    this._autoSampling = true
    this._minSamples = 0
    this._resolve()
  }

  unfreeze() {
    this.frozen = false
    this._updateFirewall()
    this._updateAddresses()
  }

  freeze() {
    this.frozen = true
  }

  _updateFirewall() {
    if (!this.dht.firewalled) {
      this.firewall = FIREWALL.OPEN
      return
    }

    if (this.sampled < 3) return

    const max = this._samplesFull[0].hits

    if (max >= 3) {
      this.firewall = FIREWALL.CONSISTENT
      return
    }

    if (max === 1) {
      this.firewall = FIREWALL.RANDOM
      return
    }

    // else max === 2

    // 1 host, >= 4 total samples ie, 2 bad ones -> random
    if (this._samplesHost.length === 1 && this.sampled > 3) {
      this.firewall = FIREWALL.RANDOM
      return
    }

    // double hit on two different ips -> assume consistent
    if (this._samplesHost.length > 1 && this._samplesFull[1].hits > 1) {
      this.firewall = FIREWALL.CONSISTENT
      return
    }

    // (4 is just means - all the samples we expect) - no decision - assume random
    if (this.sampled > 4) {
      this.firewall = FIREWALL.RANDOM
    }
  }

  _updateAddresses() {
    if (this.firewall === FIREWALL.UNKNOWN) {
      this.addresses = null
      return
    }

    if (this.firewall === FIREWALL.RANDOM) {
      this.addresses = [this._samplesHost[0]]
      return
    }

    if (this.firewall === FIREWALL.CONSISTENT) {
      this.addresses = []
      for (const addr of this._samplesFull) {
        if (addr.hits >= 2 || this.addresses.length < 2) this.addresses.push(addr)
      }
    }
  }

  update() {
    if (this.dht.firewalled && this.firewall === FIREWALL.OPEN) {
      this.firewall = FIREWALL.UNKNOWN
    }
    this._updateFirewall()
    this._updateAddresses()
  }

  add(addr, from) {
    const ref = from.host + ':' + from.port

    if (this._visited.get(ref) === 2) return
    this._visited.set(ref, 2)

    addSample(this._samplesHost, addr.host, 0)
    addSample(this._samplesFull, addr.host, addr.port)

    if ((++this.sampled >= 3 || !this.dht.firewalled) && !this.frozen) {
      this.update()
    }

    if (this.firewall === FIREWALL.CONSISTENT || this.firewall === FIREWALL.OPEN) {
      this._resolve()
    } else if (this.sampled >= this._minSamples) {
      this._resolve()
    }
  }
}

function addSample(samples, host, port) {
  for (let i = 0; i < samples.length; i++) {
    const s = samples[i]

    if (s.port !== port || s.host !== host) continue
    s.hits++

    for (; i > 0; i--) {
      const prev = samples[i - 1]
      if (prev.hits >= s.hits) return
      samples[i - 1] = s
      samples[i] = prev
    }

    return
  }

  samples.push({
    host,
    port,
    hits: 1
  })
}
const NoiseSecretStream = require('@hyperswarm/secret-stream')
const NoiseHandshake = require('noise-handshake')
const curve = require('noise-curve-ed')
const c = require('compact-encoding')
const b4a = require('b4a')
const sodium = require('sodium-universal')
const m = require('./messages')
const { NS } = require('./constants')
const { HANDSHAKE_UNFINISHED } = require('./errors')

const NOISE_PROLOUGE = NS.PEER_HANDSHAKE

module.exports = class NoiseWrap {
  constructor(keyPair, remotePublicKey) {
    this.isInitiator = !!remotePublicKey
    this.remotePublicKey = remotePublicKey
    this.keyPair = keyPair
    this.handshake = new NoiseHandshake('IK', this.isInitiator, keyPair, { curve })
    this.handshake.initialise(NOISE_PROLOUGE, remotePublicKey)
  }

  send(payload) {
    const buf = c.encode(m.noisePayload, payload)
    return this.handshake.send(buf)
  }

  recv(buf) {
    const payload = c.decode(m.noisePayload, this.handshake.recv(buf))
    this.remotePublicKey = b4a.toBuffer(this.handshake.rs)
    return payload
  }

  final() {
    if (!this.handshake.complete) throw HANDSHAKE_UNFINISHED()

    const holepunchSecret = b4a.allocUnsafe(32)

    sodium.crypto_generichash(holepunchSecret, NS.PEER_HOLEPUNCH, this.handshake.hash)

    return {
      isInitiator: this.isInitiator,
      publicKey: this.keyPair.publicKey,
      streamId: this.streamId,
      remotePublicKey: this.remotePublicKey,
      remoteId: NoiseSecretStream.id(this.handshake.hash, !this.isInitiator),
      holepunchSecret,
      hash: b4a.toBuffer(this.handshake.hash),
      rx: b4a.toBuffer(this.handshake.rx),
      tx: b4a.toBuffer(this.handshake.tx)
    }
  }
}
const c = require('compact-encoding')
const sodium = require('sodium-universal')
const RecordCache = require('record-cache')
const Cache = require('xache')
const b4a = require('b4a')
const unslab = require('unslab')

const { encodeUnslab } = require('./encode')
const m = require('./messages')
const { NS, ERROR } = require('./constants')

const EMPTY = b4a.alloc(0)
const TMP = b4a.allocUnsafe(32)
const MAX_BUMP_DRIFT = 60_000

module.exports = class Persistent {
  constructor(dht, opts) {
    this.dht = dht
    this.records = new RecordCache(opts.records)
    this.bumps = new Cache(opts.bumps)
    this.refreshes = new Cache(opts.refreshes)
    this.mutables = new Cache(opts.mutables)
    this.immutables = new Cache(opts.immutables)
  }

  onlookup(req) {
    if (!req.target) return

    const k = b4a.toString(req.target, 'hex')
    const records = this.records.get(k, 20)
    const bump = this.bumps.get(k) || 0
    const fwd = this.dht._router.get(k)

    if (fwd && records.length < 20) records.push(fwd.record)

    req.reply(records.length ? c.encode(m.lookupRawReply, { peers: records, bump }) : null)
  }

  onfindpeer(req) {
    if (!req.target) return
    const fwd = this.dht._router.get(req.target)
    req.reply(fwd ? fwd.record : null)
  }

  unannounce(target, publicKey) {
    const k = b4a.toString(target, 'hex')
    sodium.crypto_generichash(TMP, publicKey)

    if (b4a.equals(TMP, target)) this.dht._router.delete(k)
    this.records.remove(k, publicKey)
  }

  onunannounce(req) {
    if (!req.target || !req.token) return

    const unann = decode(m.announce, req.value)
    if (unann === null) return

    const { peer, signature } = unann
    if (!peer || !signature) return

    const signable = annSignable(req.target, req.token, this.dht.id, unann, NS.UNANNOUNCE)

    if (!sodium.crypto_sign_verify_detached(signature, signable, peer.publicKey)) {
      return
    }

    this.unannounce(req.target, peer.publicKey)
    req.reply(null, { token: false, closerNodes: false })
  }

  _onrefresh(token, req) {
    sodium.crypto_generichash(TMP, token)
    const activeRefresh = b4a.toString(TMP, 'hex')

    const r = this.refreshes.get(activeRefresh)
    if (!r) return

    const { announceSelf, k, record } = r
    const publicKey = record.subarray(0, 32)

    if (announceSelf) {
      this.dht._router.set(k, {
        relay: req.from,
        record,
        onconnect: null,
        onholepunch: null
      })
      this.records.remove(k, publicKey)
    } else {
      this.records.add(k, publicKey, record)
    }

    this.refreshes.delete(activeRefresh)
    this.refreshes.set(b4a.toString(token, 'hex'), r)

    req.reply(null, { token: false, closerNodes: false })
  }

  onannounce(req) {
    if (!req.target || !req.token || !this.dht.id) return

    const ann = decode(m.announce, req.value)
    if (ann === null) return

    const { peer, refresh, signature, bump } = ann

    if (!peer) {
      if (!refresh) return
      this._onrefresh(refresh, req)
      return
    }

    const signable = annSignable(req.target, req.token, this.dht.id, ann, NS.ANNOUNCE)

    if (!signature || !sodium.crypto_sign_verify_detached(signature, signable, peer.publicKey)) {
      return
    }

    // TODO: it would be potentially be more optimal to allow more than 3 addresses here for a findPeer response
    // and only use max 3 for a lookup reply
    if (peer.relayAddresses.length > 3) {
      peer.relayAddresses = peer.relayAddresses.slice(0, 3)
    }

    sodium.crypto_generichash(TMP, peer.publicKey)

    const k = b4a.toString(req.target, 'hex')
    const announceSelf = b4a.equals(TMP, req.target)
    const record = encodeUnslab(m.peer, peer)

    if (announceSelf) {
      this.dht._router.set(k, {
        relay: req.from,
        record,
        onconnect: null,
        onholepunch: null
      })
      this.records.remove(k, peer.publicKey)
    } else {
      const currentBump = this.bumps.get(k) || 0
      if (bump > currentBump && bump <= Date.now() + MAX_BUMP_DRIFT) this.bumps.set(k, bump)
      this.records.add(k, peer.publicKey, record)
    }

    if (refresh) {
      this.refreshes.set(b4a.toString(refresh, 'hex'), { k, record, announceSelf })
    }

    req.reply(null, { token: false, closerNodes: false })
  }

  onmutableget(req) {
    if (!req.target || !req.value) return

    let seq = 0
    try {
      seq = c.decode(c.uint, req.value)
    } catch {
      return
    }

    const k = b4a.toString(req.target, 'hex')
    const value = this.mutables.get(k)

    if (!value) {
      req.reply(null)
      return
    }

    const localSeq = c.decode(c.uint, value)
    req.reply(localSeq < seq ? null : value)
  }

  onmutableput(req) {
    if (!req.target || !req.token || !req.value) return

    const p = decode(m.mutablePutRequest, req.value)
    if (!p) return

    const { publicKey, seq, value, signature } = p

    const hash = b4a.allocUnsafe(32)
    sodium.crypto_generichash(hash, publicKey)
    if (!b4a.equals(hash, req.target)) return

    if (!value || !verifyMutable(signature, seq, value, publicKey)) return

    const k = b4a.toString(hash, 'hex')
    const local = this.mutables.get(k)

    if (local) {
      const existing = c.decode(m.mutableGetResponse, local)
      if (existing.value && existing.seq === seq && b4a.compare(value, existing.value) !== 0) {
        req.error(ERROR.SEQ_REUSED)
        return
      }
      if (seq < existing.seq) {
        req.error(ERROR.SEQ_TOO_LOW)
        return
      }
    }

    this.mutables.set(k, encodeUnslab(m.mutableGetResponse, { seq, value, signature }))
    req.reply(null)
  }

  onimmutableget(req) {
    if (!req.target) return

    const k = b4a.toString(req.target, 'hex')
    const value = this.immutables.get(k)

    req.reply(value || null)
  }

  onimmutableput(req) {
    if (!req.target || !req.token || !req.value) return

    const hash = b4a.alloc(32)
    sodium.crypto_generichash(hash, req.value)
    if (!b4a.equals(hash, req.target)) return

    const k = b4a.toString(hash, 'hex')
    this.immutables.set(k, unslab(req.value))

    req.reply(null)
  }

  onplugin(req) {
    if (!req.value) return

    const plugreq = decode(m.pluginRequest, req.value)
    if (plugreq === null) return

    const p = this.dht.plugins.get(plugreq.plugin)
    if (!p || p.version !== plugreq.version) return

    p.onrequest(plugreq, req)
  }

  destroy() {
    this.records.destroy()
    this.refreshes.destroy()
    this.mutables.destroy()
    this.immutables.destroy()
  }

  static signMutable(seq, value, keyPair) {
    const signable = b4a.allocUnsafe(32 + 32)
    const hash = signable.subarray(32)

    signable.set(NS.MUTABLE_PUT, 0)

    sodium.crypto_generichash(hash, c.encode(m.mutableSignable, { seq, value }))
    return sign(signable, keyPair)
  }

  static verifyMutable(signature, seq, value, publicKey) {
    return verifyMutable(signature, seq, value, publicKey)
  }

  static signAnnounce(target, token, id, ann, keyPair) {
    return sign(annSignable(target, token, id, ann, NS.ANNOUNCE), keyPair)
  }

  static signUnannounce(target, token, id, ann, keyPair) {
    return sign(annSignable(target, token, id, ann, NS.UNANNOUNCE), keyPair)
  }
}

function verifyMutable(signature, seq, value, publicKey) {
  const signable = b4a.allocUnsafe(32 + 32)
  const hash = signable.subarray(32)

  signable.set(NS.MUTABLE_PUT, 0)

  sodium.crypto_generichash(hash, c.encode(m.mutableSignable, { seq, value }))
  return sodium.crypto_sign_verify_detached(signature, signable, publicKey)
}

function annSignable(target, token, id, ann, ns) {
  const signable = b4a.allocUnsafe(32 + 32)
  const hash = signable.subarray(32)

  signable.set(ns, 0)

  sodium.crypto_generichash_batch(hash, [
    target,
    id,
    token,
    c.encode(m.peer, ann.peer), // note that this is the partial encoding of the announce message so we could just use that for perf
    ann.refresh || EMPTY
  ])

  return signable
}

function sign(signable, keyPair) {
  if (keyPair.sign) {
    return keyPair.sign(signable)
  }
  const secretKey = keyPair.secretKey ? keyPair.secretKey : keyPair
  const signature = b4a.allocUnsafe(64)
  sodium.crypto_sign_detached(signature, signable, secretKey)
  return signature
}

function decode(enc, val) {
  try {
    return val && c.decode(enc, val)
  } catch (err) {
    return null
  }
}
module.exports = class RawStreamSet {
  constructor(dht) {
    this._dht = dht

    this._prefix = 16 - 1 // 16 is the default stream-set side in udx
    this._streams = new Map()
  }

  get size() {
    return this._streams.size
  }

  [Symbol.iterator]() {
    return this._streams.values()
  }

  add(opts) {
    const self = this

    // TODO: we should prob have a udx helper for id generation, given the slight complexity
    // of the below. requires a PRNG in udx tho.

    let id = 0

    while (true) {
      id = (Math.random() * 0x100000000) >>> 0

      if (this._streams.has(id & this._prefix)) continue
      break
    }

    // always have ~50% change of rolling a free one
    if (2 * this._streams.size >= this._prefix) {
      // ie 0b11111 = 0b1111 + 1 + 0b1111
      this._prefix = 2 * this._prefix + 1

      // move the prefixes over
      const next = new Map()
      for (const stream of this._streams.values()) {
        next.set(stream.id & this._prefix, stream)
      }
      this._streams = next
    }

    const stream = this._dht.udx.createStream(id, opts)
    this._streams.set(id & this._prefix, stream)

    stream.on('close', onclose)

    return stream

    function onclose() {
      self._streams.delete(id & self._prefix)
    }
  }

  async clear() {
    const destroying = []

    for (const stream of this._streams.values()) {
      destroying.push(new Promise((resolve) => stream.once('close', resolve).destroy()))
    }

    await Promise.allSettled(destroying)
  }
}
const c = require('compact-encoding')
const Cache = require('xache')
const safetyCatch = require('safety-catch')
const b4a = require('b4a')
const { handshake, holepunch } = require('./messages')
const { COMMANDS } = require('./constants')
const { BAD_HANDSHAKE_REPLY, BAD_HOLEPUNCH_REPLY } = require('./errors')

const FROM_CLIENT = 0
const FROM_SERVER = 1
const FROM_RELAY = 2
const FROM_SECOND_RELAY = 3
const REPLY = 4

// TODO: While the current design is very trustless in regards to clients/servers trusting the DHT,
// we should add a bunch of rate limits everywhere, especially including here to avoid bad users
// using a DHT node to relay traffic indiscriminately using the connect/holepunch messages.
// That's mostly from an abuse POV as none of the messsages do amplication.

module.exports = class Router {
  constructor(dht, opts) {
    this.dht = dht
    this.forwards = new Cache(opts.forwards)
  }

  set(target, state) {
    if (state.onpeerhandshake) {
      this.forwards.retain(toString(target), state)
    } else {
      this.forwards.set(toString(target), state)
    }
  }

  get(target) {
    return this.forwards.get(toString(target))
  }

  delete(target) {
    this.forwards.delete(toString(target))
  }

  destroy() {
    this.forwards.destroy()
  }

  async peerHandshake(target, { noise, peerAddress, relayAddress, socket, session }, to) {
    const dht = this.dht

    const requestValue = c.encode(handshake, {
      mode: FROM_CLIENT,
      noise,
      peerAddress,
      relayAddress
    })

    const res = await dht.request(
      { command: COMMANDS.PEER_HANDSHAKE, target, value: requestValue },
      to,
      { socket, session }
    )

    const hs = decode(handshake, res.value)
    if (
      !hs ||
      hs.mode !== REPLY ||
      to.host !== res.from.host ||
      to.port !== res.from.port ||
      !hs.noise
    ) {
      throw BAD_HANDSHAKE_REPLY()
    }

    return {
      noise: hs.noise,
      relayed: !!hs.peerAddress,
      serverAddress: hs.peerAddress || to,
      clientAddress: res.to
    }
  }

  async onpeerhandshake(req) {
    const hs = req.value && decode(handshake, req.value)
    if (!hs) return

    const { mode, noise, peerAddress, relayAddress } = hs

    const state = req.target && this.get(req.target)
    const isServer = !!(state && state.onpeerhandshake)
    const relay = state && state.relay

    if (isServer) {
      let reply = null
      try {
        reply = noise && (await state.onpeerhandshake({ noise, peerAddress }, req))
      } catch (e) {
        safetyCatch(e)
        return
      }
      if (!reply || !reply.noise) return
      const opts = { socket: reply.socket, closerNodes: false, token: false }

      switch (mode) {
        case FROM_CLIENT: {
          req.reply(
            c.encode(handshake, { mode: REPLY, noise: reply.noise, peerAddress: null }),
            opts
          )
          return
        }
        case FROM_RELAY: {
          req.relay(
            c.encode(handshake, { mode: FROM_SERVER, noise: reply.noise, peerAddress }),
            req.from,
            opts
          )
          return
        }
        case FROM_SECOND_RELAY: {
          if (!relayAddress) return
          req.relay(
            c.encode(handshake, { mode: FROM_SERVER, noise: reply.noise, peerAddress }),
            relayAddress,
            opts
          )
          return // eslint-disable-line
        }
      }
    } else {
      switch (mode) {
        case FROM_CLIENT: {
          // TODO: if no relay is known route closer to the target instead of timing out
          if (!noise) return
          if (!relay && !relayAddress) {
            // help the user route
            req.reply(null, { token: false, closerNodes: true })
            return
          }
          req.relay(
            c.encode(handshake, {
              mode: FROM_RELAY,
              noise,
              peerAddress: req.from,
              relayAddress: null
            }),
            relayAddress || relay
          )
          return
        }
        case FROM_RELAY: {
          if (!relay || !noise) return
          req.relay(
            c.encode(handshake, {
              mode: FROM_SECOND_RELAY,
              noise,
              peerAddress,
              relayAddress: req.from
            }),
            relay
          )
          return
        }
        case FROM_SERVER: {
          if (!peerAddress || !noise) return
          req.reply(
            c.encode(handshake, { mode: REPLY, noise, peerAddress: req.from, relayAddress: null }),
            { to: peerAddress, closerNodes: false, token: false }
          )
          return // eslint-disable-line
        }
      }
    }
  }

  async peerHolepunch(target, { id, payload, peerAddress, socket, session }, to) {
    const dht = this.dht
    const requestValue = c.encode(holepunch, {
      mode: FROM_CLIENT,
      id,
      payload,
      peerAddress
    })

    const res = await dht.request(
      { command: COMMANDS.PEER_HOLEPUNCH, target, value: requestValue },
      to,
      { socket, session }
    )

    const hp = decode(holepunch, res.value)
    if (!hp || hp.mode !== REPLY || to.host !== res.from.host || to.port !== res.from.port) {
      throw BAD_HOLEPUNCH_REPLY()
    }

    return {
      from: res.from,
      to: res.to,
      payload: hp.payload,
      peerAddress: hp.peerAddress || to
    }
  }

  async onpeerholepunch(req) {
    const hp = req.value && decode(holepunch, req.value)
    if (!hp) return

    const { mode, id, payload, peerAddress } = hp

    const state = req.target && this.get(req.target)
    const isServer = !!(state && state.onpeerholepunch)
    const relay = state && state.relay

    switch (mode) {
      case FROM_CLIENT: {
        if (!peerAddress && !relay) return
        req.relay(
          c.encode(holepunch, { mode: FROM_RELAY, id, payload, peerAddress: req.from }),
          peerAddress || relay
        )
        return
      }
      case FROM_RELAY: {
        if (!isServer || !peerAddress) return
        let reply = null
        try {
          reply = await state.onpeerholepunch({ id, payload, peerAddress }, req)
        } catch (e) {
          safetyCatch(e)
          return
        }
        if (!reply) return
        const opts = { socket: reply.socket, closerNodes: false, token: false }
        req.relay(
          c.encode(holepunch, { mode: FROM_SERVER, id: 0, payload: reply.payload, peerAddress }),
          req.from,
          opts
        )
        return
      }
      case FROM_SERVER: {
        req.reply(c.encode(holepunch, { mode: REPLY, id, payload, peerAddress: req.from }), {
          to: peerAddress,
          closerNodes: false,
          token: false
        })
        return // eslint-disable-line
      }
    }
  }
}

function decode(enc, val) {
  try {
    return c.decode(enc, val)
  } catch {
    return null
  }
}

function toString(t) {
  return typeof t === 'string' ? t : b4a.toString(t, 'hex')
}
const sodium = require('sodium-universal')
const b4a = require('b4a')
const { holepunchPayload } = require('./messages')

module.exports = class HolepunchPayload {
  constructor(holepunchSecret) {
    this._sharedSecret = holepunchSecret
    this._localSecret = b4a.allocUnsafe(32)

    sodium.randombytes_buf(this._localSecret)
  }

  decrypt(buffer) {
    const state = { start: 24, end: buffer.byteLength - 16, buffer }

    if (state.end <= state.start) return null

    const nonce = buffer.subarray(0, 24)
    const msg = state.buffer.subarray(state.start, state.end)
    const cipher = state.buffer.subarray(state.start)

    if (!sodium.crypto_secretbox_open_easy(msg, cipher, nonce, this._sharedSecret)) return null

    try {
      return holepunchPayload.decode(state)
    } catch {
      return null
    }
  }

  encrypt(payload) {
    const state = { start: 24, end: 24, buffer: null }
    holepunchPayload.preencode(state, payload)
    state.buffer = b4a.allocUnsafe(state.end + 16)

    const nonce = state.buffer.subarray(0, 24)
    const msg = state.buffer.subarray(state.start, state.end)
    const cipher = state.buffer.subarray(state.start)

    holepunchPayload.encode(state, payload)
    sodium.randombytes_buf(nonce)
    sodium.crypto_secretbox_easy(cipher, msg, nonce, this._sharedSecret)

    return state.buffer
  }

  token(addr) {
    const out = b4a.allocUnsafe(32)
    sodium.crypto_generichash(out, b4a.from(addr.host), this._localSecret)
    return out
  }
}
const DONE = Promise.resolve(true)
const DESTROYED = Promise.resolve(false)

module.exports = class Semaphore {
  constructor(limit = 1) {
    this.limit = limit
    this.active = 0
    this.waiting = []

    this.flushedPromise = null
    this.flushedResolve = null

    this.destroyed = false

    this._onwait = this._queueWaiting.bind(this)
    this._onflush = this._queueFlushed.bind(this)
  }

  _queueWaiting(resolve) {
    this.waiting.push(resolve)
  }

  _queueFlushed(resolve) {
    this.flushedResolve = resolve
  }

  wait() {
    if (this.destroyed === true) return DESTROYED

    if (this.active < this.limit && this.waiting.length === 0) {
      this.active++
      return DONE
    }

    return new Promise(this._onwait)
  }

  signal() {
    if (this.destroyed === true) return

    this.active--
    while (this.active < this.limit && this.waiting.length > 0 && this.destroyed === false) {
      this.active++
      this.waiting.shift()(true)
    }

    if (this.active === 0 && this.flushedResolve) {
      const resolve = this.flushedResolve
      this.flushedResolve = null
      this.flushedPromise = null
      resolve(true)
    }
  }

  async flush() {
    if (this.destroyed === true) return
    if (this.active === 0) return
    if (this.flushedPromise) return this.flushedPromise
    this.flushedPromise = new Promise(this._onflush)
    return this.flushedPromise
  }

  destroy() {
    this.destroyed = true
    this.active = 0
    while (this.waiting.length) this.waiting.pop()(false)
    if (this.flushedResolve) this.flushedResolve(false)
  }
}
const { EventEmitter } = require('events')
const safetyCatch = require('safety-catch')
const NoiseSecretStream = require('@hyperswarm/secret-stream')
const b4a = require('b4a')
const relay = require('blind-relay')
const NoiseWrap = require('./noise-wrap')
const Announcer = require('./announcer')
const { FIREWALL, ERROR } = require('./constants')
const { unslabbedHash } = require('./crypto')
const SecurePayload = require('./secure-payload')
const Holepuncher = require('./holepuncher')
const { isPrivate } = require('bogon')
const { ALREADY_LISTENING, NODE_DESTROYED, KEYPAIR_ALREADY_USED } = require('./errors')

const HANDSHAKE_CLEAR_WAIT = 10000
const HANDSHAKE_INITIAL_TIMEOUT = 10000

module.exports = class Server extends EventEmitter {
  constructor(dht, opts = {}) {
    super()

    this.dht = dht
    this.target = null

    this.closed = false
    this.firewall = opts.firewall || (() => false)
    this.holepunch = opts.holepunch || (() => true)
    this.relayThrough = opts.relayThrough || null
    this.relayKeepAlive = opts.relayKeepAlive || 5000
    this.pool = opts.pool || null
    this.createHandshake = opts.createHandshake || defaultCreateHandshake
    this.createSecretStream = opts.createSecretStream || defaultCreateSecretStream
    this.suspended = false
    this.handshakeClearWait = opts.handshakeClearWait || HANDSHAKE_CLEAR_WAIT

    this._shareLocalAddress = opts.shareLocalAddress !== false
    this._reusableSocket = !!opts.reusableSocket
    this._neverPunch = opts.holepunch === false // useful for fully disabling punching
    this._keyPair = null
    this._announcer = null
    this._connects = new Map()
    this._holepunches = []
    this._listening = null
    this._closing = null
  }

  get listening() {
    return this._listening !== null
  }

  get publicKey() {
    return this._keyPair && this._keyPair.publicKey
  }

  get relayAddresses() {
    return this._announcer ? this._announcer.relayAddresses : []
  }

  onconnection(encryptedSocket) {
    this.emit('connection', encryptedSocket)
  }

  async suspend({ log = noop } = {}) {
    log('Suspending hyperdht server')
    if (this._listening !== null) await this._listening
    log('Suspending hyperdht server (post listening)')
    this.suspended = true
    this._clearAll()
    return this._announcer ? this._announcer.suspend({ log }) : Promise.resolve()
  }

  async resume() {
    if (this._listening !== null) await this._listening
    this.suspended = false
    return this._announcer ? this._announcer.resume() : Promise.resolve()
  }

  address() {
    if (!this._keyPair) return null

    return {
      publicKey: this._keyPair.publicKey,
      host: this.dht.host,
      port: this.dht.port
    }
  }

  close() {
    if (this._closing) return this._closing
    this._closing = this._close()
    return this._closing
  }

  _gc() {
    this.dht.listening.delete(this)
    if (this.target) this.dht._router.delete(this.target)
  }

  async _stopListening() {
    try {
      if (this._announcer) await this._announcer.stop()
    } catch {
      // ignore
    }

    this._announcer = null
    this._listening = null
    this._keyPair = null
  }

  async _close() {
    if (this._listening === null) {
      this.closed = true
      this.emit('close')
      return
    }

    try {
      await this._listening
    } catch (err) {
      safetyCatch(err)
    }

    this._gc()
    this._clearAll()

    await this._stopListening()

    this.closed = true
    this.emit('close')
  }

  _clearAll() {
    while (this._holepunches.length > 0) {
      const h = this._holepunches.pop()
      if (h && h.puncher) h.puncher.destroy()
      if (h && h.clearing) clearTimeout(h.clearing)
      if (h && h.prepunching) clearTimeout(h.prepunching)
      if (h && h.rawStream) h.rawStream.destroy()
    }

    this._connects.clear()
  }

  async listen(keyPair = this.dht.defaultKeyPair, opts = {}) {
    if (this._listening !== null) throw ALREADY_LISTENING()
    if (this.dht.destroyed) throw NODE_DESTROYED()

    this._listening = this._listen(keyPair, opts)
    await this._listening
    return this
  }

  async _listen(keyPair, opts) {
    // From now on, the DHT object which created me is responsible for closing me
    this.dht.listening.add(this)

    try {
      await this.dht.bind()
      if (this._closing) return

      for (const s of this.dht.listening) {
        if (s._keyPair && b4a.equals(s._keyPair.publicKey, keyPair.publicKey)) {
          throw KEYPAIR_ALREADY_USED()
        }
      }

      this.target = unslabbedHash(keyPair.publicKey)
      this._keyPair = keyPair
      this._announcer = new Announcer(this.dht, keyPair, this.target, opts)

      this.dht._router.set(this.target, {
        relay: null,
        record: this._announcer.record,
        onpeerhandshake: this._onpeerhandshake.bind(this),
        onpeerholepunch: this._onpeerholepunch.bind(this)
      })

      // warm it up for now
      this._localAddresses().catch(safetyCatch)

      await this._announcer.start()
    } catch (err) {
      await this._stopListening()
      this._gc()
      throw err
    }

    if (this._closing) return
    if (this.suspended) await this._announcer.suspend()

    if (this._closing) return
    if (this.dht.destroyed) throw NODE_DESTROYED()

    if (this.pool) this.pool._attachServer(this)

    this.emit('listening')
  }

  refresh() {
    if (this._announcer && !this.suspended) this._announcer.refresh()
  }

  notifyOnline() {
    if (this._announcer) this._announcer.online.notify()
  }

  _localAddresses() {
    return this.dht.validateLocalAddresses(Holepuncher.localAddresses(this.dht.io.serverSocket))
  }

  async _addHandshake(k, noise, clientAddress, { from, to: serverAddress, socket }, direct) {
    let id = this._holepunches.indexOf(null)
    if (id === -1) id = this._holepunches.push(null) - 1

    const hs = {
      round: 0,
      reply: null,
      puncher: null,
      payload: null,
      rawStream: null,
      encryptedSocket: null,
      prepunching: null,
      firewalled: true,
      clearing: null,
      onsocket: null,
      aborted: false,

      // Relay state
      relayTimeout: null,
      relayToken: null,
      relaySocket: null,
      relayClient: null,
      relayPaired: false
    }

    this._holepunches[id] = hs

    const handshake = this.createHandshake(this._keyPair, null)

    let remotePayload
    try {
      remotePayload = await handshake.recv(noise)
    } catch (err) {
      safetyCatch(err)
      this._clearLater(hs, id, k)
      return null
    }

    if (this._closing || this.suspended) return null

    try {
      hs.firewalled = await this.firewall(handshake.remotePublicKey, remotePayload, clientAddress)
    } catch (err) {
      safetyCatch(err)
    }

    if (this._closing || this.suspended) return null

    if (hs.firewalled) {
      this._clearLater(hs, id, k)
      return null
    }

    const error =
      remotePayload.version === 1
        ? remotePayload.udx
          ? ERROR.NONE
          : ERROR.ABORTED
        : ERROR.VERSION_MISMATCH

    const addresses = []
    const ourRemoteAddr = this.dht.remoteAddress()
    const ourLocalAddrs = this._shareLocalAddress ? await this._localAddresses() : null

    if (this._closing || this.suspended) return null

    if (ourRemoteAddr) addresses.push(ourRemoteAddr)
    if (ourLocalAddrs) addresses.push(...ourLocalAddrs)

    if (error === ERROR.NONE) {
      hs.rawStream = this.dht.createRawStream({
        framed: true,
        firewall(socket, port, host) {
          if (!(port > 0 && port < 65536)) return true

          // Check if the traffic originated from the socket on which we're expecting relay traffic. If so,
          // we haven't hole punched yet and the other side is just sending us traffic through the relay.
          if (hs.relaySocket && isRelay(hs.relaySocket, socket, port, host)) {
            return false
          }

          hs.onsocket(socket, port, host)
          return false
        }
      })

      hs.rawStream.on('error', autoDestroy)

      // Handles the case where onsocket is never called, but the stream got setup
      // This can happen on a relayed connection which never connects directly
      // (onsocket is called there only when the direct connection is established)
      const onrawstreamclose = () => {
        if (this._closing) return
        this._clearLater(hs, id, k)
      }
      hs.rawStream.on('close', onrawstreamclose)

      hs.onsocket = (socket, port, host) => {
        if (hs.rawStream === null) return // Already hole punched

        this._clearLater(hs, id, k)

        if (hs.prepunching) {
          clearTimeout(hs.prepunching)
          hs.prepunching = null
        }

        if (this._reusableSocket && remotePayload.udx.reusableSocket) {
          this.dht._socketPool.routes.add(handshake.remotePublicKey, hs.rawStream)
        }

        hs.rawStream.removeListener('error', autoDestroy)
        hs.rawStream.removeListener('close', onrawstreamclose)

        if (hs.rawStream.connected) {
          const remoteChanging = hs.rawStream.changeRemote(socket, remotePayload.udx.id, port, host)

          if (remoteChanging) remoteChanging.catch(safetyCatch)
        } else {
          hs.rawStream.connect(socket, remotePayload.udx.id, port, host)
          hs.encryptedSocket = this.createSecretStream(false, hs.rawStream, {
            handshake: h,
            keepAlive: this.dht.connectionKeepAlive
          })

          this.onconnection(hs.encryptedSocket)
        }

        if (hs.puncher) {
          hs.puncher.onabort = noop
          hs.puncher.destroy()
        }

        hs.rawStream = null
      }

      function autoDestroy() {
        if (hs.puncher) hs.puncher.destroy()
      }
    }

    const relayAddresses = this.relayAddresses
    const relayThrough = selectRelay(this.relayThrough)

    if (relayThrough) hs.relayToken = relay.token()

    try {
      hs.reply = await handshake.send({
        error,
        firewall: ourRemoteAddr ? FIREWALL.OPEN : FIREWALL.UNKNOWN,
        holepunch: ourRemoteAddr ? null : { id, relays: this._announcer.relays },
        addresses4: addresses,
        addresses6: null,
        udx: {
          reusableSocket: this._reusableSocket,
          id: hs.rawStream ? hs.rawStream.id : 0,
          seq: 0
        },
        secretStream: {},
        relayThrough: relayThrough ? { publicKey: relayThrough, token: hs.relayToken } : null,
        relayAddresses: relayAddresses.length ? relayAddresses : null
      })
    } catch (err) {
      safetyCatch(err)
      if (hs.rawStream) hs.rawStream.destroy()
      this._clearLater(hs, id, k)
      return null
    }

    if (this._closing || this.suspended) {
      if (hs.rawStream) hs.rawStream.destroy()
      return null
    }

    const h = handshake.final()

    if (error !== ERROR.NONE) {
      if (hs.rawStream) hs.rawStream.destroy()
      this._clearLater(hs, id, k)
      return hs
    }

    if (remotePayload.firewall === FIREWALL.OPEN || direct) {
      const sock = direct ? socket : this.dht.socket
      this.dht.stats.punches.open++
      hs.onsocket(sock, clientAddress.port, clientAddress.host)
      return hs
    }

    if (relayThrough || remotePayload.relayThrough) {
      this._relayConnection(hs, relayThrough, remotePayload, h)
    }

    const onabort = () => {
      hs.aborted = true
      if (hs.prepunching) clearTimeout(hs.prepunching)
      hs.prepunching = null
      if (hs.rawStream.destroyed) {
        this._clearLater(hs, id, k)
        return
      }

      hs.rawStream.on('close', () => this._clearLater(hs, id, k))
      if (hs.relayToken === null) hs.rawStream.destroy()
    }

    if (!direct && clientAddress.host === serverAddress.host) {
      const clientAddresses = remotePayload.addresses4.filter(onlyPrivateHosts)

      if (clientAddresses.length > 0 && this._shareLocalAddress) {
        const myAddresses = await this._localAddresses()
        const addr = Holepuncher.matchAddress(myAddresses, clientAddresses)

        if (addr) {
          hs.prepunching = setTimeout(onabort, HANDSHAKE_INITIAL_TIMEOUT)
          return hs
        }
      }
    }

    if (this._closing || this.suspended) return null

    if (ourRemoteAddr || this._neverPunch) {
      hs.prepunching = setTimeout(onabort, HANDSHAKE_INITIAL_TIMEOUT)
      return hs
    }

    hs.payload = new SecurePayload(h.holepunchSecret)
    hs.puncher = new Holepuncher(this.dht, this.dht.session(), false, remotePayload.firewall)

    hs.puncher.onconnect = hs.onsocket
    hs.puncher.onabort = onabort
    hs.prepunching = setTimeout(hs.puncher.destroy.bind(hs.puncher), HANDSHAKE_INITIAL_TIMEOUT)

    return hs
  }

  _clearLater(hs, id, k) {
    if (hs.clearing) return
    hs.clearing = setTimeout(() => this._clear(hs, id, k), this.handshakeClearWait)
  }

  _clear(hs, id, k) {
    if (id >= this._holepunches.length || this._holepunches[id] !== hs) return
    if (hs.clearing) clearTimeout(hs.clearing)

    this._holepunches[id] = null
    while (
      this._holepunches.length > 0 &&
      this._holepunches[this._holepunches.length - 1] === null
    ) {
      this._holepunches.pop()
    }
    this._connects.delete(k)
  }

  async _onpeerhandshake({ noise, peerAddress }, req) {
    const k = b4a.toString(noise, 'hex')

    // The next couple of statements MUST run within the same tick to prevent
    // a malicious peer from flooding us with handshakes.
    let p = this._connects.get(k)
    if (!p) {
      p = this._addHandshake(k, noise, peerAddress || req.from, req, !peerAddress)
      this._connects.set(k, p)
    }

    const h = await p
    if (!h) return null

    if (this._closing !== null || this.suspended) return null

    return { socket: h.puncher && h.puncher.socket, noise: h.reply }
  }

  async _onpeerholepunch({ id, peerAddress, payload }, req) {
    const h = id < this._holepunches.length ? this._holepunches[id] : null
    if (!h) return null

    if (!peerAddress || this._closing !== null || this.suspended) return null

    const p = h.puncher
    if (!p || !p.socket) return this._abort(h) // not opened

    const remotePayload = h.payload.decrypt(payload)
    if (!remotePayload) return null

    const isServerRelay = this._announcer.isRelay(req.from)
    const { error, firewall, round, punching, addresses, remoteAddress, remoteToken } =
      remotePayload

    if (error !== ERROR.NONE) {
      // We actually do not need to set the round here, but just do it for consistency.
      if (round >= h.round) h.round = round
      return this._abort(h)
    }

    const token = h.payload.token(peerAddress)
    const echoed = isServerRelay && !!remoteToken && b4a.equals(token, remoteToken)

    // Update our heuristics here
    if (req.socket === p.socket) {
      p.nat.add(req.to, req.from)
    }

    if (round >= h.round) {
      h.round = round
      p.updateRemote({ punching, firewall, addresses, verified: echoed ? peerAddress.host : null })
    }

    // Wait for the analyzer to reach a conclusion...
    let stable = await p.analyze(false)
    if (p.destroyed) return null

    if (!p.remoteHolepunching && !stable) {
      stable = await p.analyze(true)
      if (p.destroyed) return null
      if (!stable) return this._abort(h)
    }

    // Fast mode! If we are consistent and the remote has opened a session to us (remoteAddress)
    // then fire a quick punch back. Note the await here just waits for the udp socket to flush.
    if (
      isConsistent(p.nat.firewall) &&
      remoteAddress &&
      hasSameAddr(p.nat.addresses, remoteAddress)
    ) {
      await p.ping(peerAddress)
      if (p.destroyed) return null
    }

    // Remote said they are punching (or willing to), so we will punch as well.
    // Note that this returns when the punching has STARTED, so no guarantee
    // we will have a connection after this promise etc.
    if (p.remoteHolepunching) {
      // TODO: still continue here if a local connection might work, but then do not holepunch...
      if (!this.holepunch(p.remoteFirewall, p.nat.firewall, p.remoteAddresses, p.nat.addresses)) {
        return p.destroyed ? null : this._abort(h)
      }

      if (h.prepunching) {
        clearTimeout(h.prepunching)
        h.prepunching = null
      }

      if (p.remoteFirewall >= FIREWALL.RANDOM || p.nat.firewall >= FIREWALL.RANDOM) {
        if (
          this.dht._randomPunches >= this.dht._randomPunchLimit ||
          Date.now() - this.dht._lastRandomPunch < this.dht._randomPunchInterval
        ) {
          if (!h.relayToken) return this._abort(h, ERROR.TRY_LATER)
          return {
            socket: p.socket,
            payload: h.payload.encrypt({
              error: ERROR.TRY_LATER,
              firewall: p.nat.firewall,
              round: h.round,
              connected: p.connected,
              punching: p.punching,
              addresses: p.nat.addresses,
              remoteAddress: null,
              token: isServerRelay ? token : null,
              remoteToken: remotePayload.token
            })
          }
        }
      }

      const punching = await p.punch()
      if (p.destroyed) return null
      if (!punching) return this._abort(h)
    }

    // Freeze that analysis as soon as we have a result we are giving to the other peer
    if (p.nat.firewall !== FIREWALL.UNKNOWN) {
      p.nat.freeze()
    }

    return {
      socket: p.socket,
      payload: h.payload.encrypt({
        error: ERROR.NONE,
        firewall: p.nat.firewall,
        round: h.round,
        connected: p.connected,
        punching: p.punching,
        addresses: p.nat.addresses,
        remoteAddress: null,
        token: isServerRelay ? token : null,
        remoteToken: remotePayload.token
      })
    }
  }

  _abort(h, error = ERROR.ABORTED) {
    if (!h.payload) {
      if (h.puncher) h.puncher.destroy()
      return null
    }

    const payload = h.payload.encrypt({
      error,
      firewall: FIREWALL.UNKNOWN,
      round: h.round,
      connected: false,
      punching: false,
      addresses: null,
      remoteAddress: null,
      token: null,
      remoteToken: null
    })

    h.puncher.destroy()

    return { socket: this.dht.socket, payload }
  }

  _relayConnection(hs, relayThrough, remotePayload, h) {
    this.dht.stats.relaying.attempts++

    let isInitiator
    let publicKey
    let token

    if (relayThrough) {
      isInitiator = true
      publicKey = relayThrough
      token = hs.relayToken
    } else {
      isInitiator = false
      publicKey = remotePayload.relayThrough.publicKey
      token = remotePayload.relayThrough.token
    }

    hs.relayToken = token
    hs.relaySocket = this.dht.connect(publicKey)
    hs.relaySocket.setKeepAlive(this.relayKeepAlive)
    hs.relayClient = relay.Client.from(hs.relaySocket, { id: hs.relaySocket.publicKey })
    hs.relayTimeout = setTimeout(onabort, 15000)

    hs.relayClient
      .pair(isInitiator, token, hs.rawStream)
      .on('error', onabort)
      .on('data', (remoteId) => {
        if (hs.relayTimeout) clearRelayTimeout(hs)
        if (hs.rawStream === null) {
          onabort(null)
          return
        }

        hs.relayPaired = true
        this.dht.stats.relaying.successes++

        if (hs.prepunching) clearTimeout(hs.prepunching)
        hs.prepunching = null

        const { remotePort, remoteHost, socket } = hs.relaySocket.rawStream

        hs.rawStream
          .on('close', () => hs.relaySocket.destroy())
          .connect(socket, remoteId, remotePort, remoteHost)

        hs.encryptedSocket = this.createSecretStream(false, hs.rawStream, { handshake: h })

        this.onconnection(hs.encryptedSocket)
      })

    const dht = this.dht
    function onabort() {
      if (!hs.relayPaired) dht.stats.relaying.aborts++
      if (hs.relayTimeout) clearRelayTimeout(hs)
      const socket = hs.relaySocket
      hs.relayToken = null
      hs.relaySocket = null
      if (socket) socket.destroy()
      if (hs.aborted && hs.rawStream) hs.rawStream.destroy()
    }
  }
}

function clearRelayTimeout(hs) {
  clearTimeout(hs.relayTimeout)
  hs.relayTimeout = null
}

function isConsistent(fw) {
  return fw === FIREWALL.OPEN || fw === FIREWALL.CONSISTENT
}

function hasSameAddr(addrs, other) {
  if (addrs === null) return false

  for (const addr of addrs) {
    if (addr.port === other.port && addr.host === other.host) return true
  }
  return false
}

function defaultCreateHandshake(keyPair, remotePublicKey) {
  return new NoiseWrap(keyPair, remotePublicKey)
}

function defaultCreateSecretStream(isInitiator, rawStream, opts) {
  return new NoiseSecretStream(isInitiator, rawStream, opts)
}

function onlyPrivateHosts(addr) {
  return isPrivate(addr.host)
}

function isRelay(relaySocket, socket, port, host) {
  const stream = relaySocket.rawStream
  if (!stream) return false
  if (stream.socket !== socket) return false
  return port === stream.remotePort && host === stream.remoteHost
}

function selectRelay(relayThrough) {
  if (typeof relayThrough === 'function') relayThrough = relayThrough()
  if (relayThrough === null) return null
  if (Array.isArray(relayThrough)) {
    return relayThrough[Math.floor(Math.random() * relayThrough.length)]
  }
  return relayThrough
}

function noop() {}
module.exports = class Sleeper {
  constructor() {
    this._timeout = null
    this._resolve = null

    this._start = (resolve) => {
      this._resolve = resolve
    }

    this._trigger = () => {
      if (this._resolve === null) return
      const resolve = this._resolve
      this._timeout = null
      this._resolve = null
      resolve()
    }
  }

  pause(ms) {
    const p = new Promise(this._start)
    if (this._timeout !== null) {
      clearTimeout(this._timeout)
      this._trigger()
    }
    this._timeout = setTimeout(this._trigger, ms)
    return p
  }

  resume() {
    if (this._timeout !== null) {
      clearTimeout(this._timeout)
      this._trigger()
    }
  }
}
const b4a = require('b4a')

const LINGER_TIME = 3000

module.exports = class SocketPool {
  constructor(dht, host) {
    this._dht = dht
    this._sockets = new Map()
    this._lingering = new Set() // updated by the ref
    this._host = host

    this.routes = new SocketRoutes(this)
  }

  _onmessage(ref, data, address) {
    this._dht.onmessage(ref.socket, data, address)
  }

  _add(ref) {
    this._sockets.set(ref.socket, ref)
  }

  _remove(ref) {
    this._sockets.delete(ref.socket)
    this._lingering.delete(ref)
  }

  lookup(socket) {
    return this._sockets.get(socket) || null
  }

  setReusable(socket, bool) {
    const ref = this.lookup(socket)
    if (ref) ref.reusable = bool
  }

  acquire() {
    // TODO: Enable socket reuse
    return new SocketRef(this)
  }

  async destroy() {
    const closing = []

    for (const ref of this._sockets.values()) {
      ref._unlinger()
      closing.push(ref.socket.close())
    }

    await Promise.allSettled(closing)
  }
}

class SocketRoutes {
  constructor(pool) {
    this._pool = pool
    this._routes = new Map()
  }

  add(publicKey, rawStream) {
    if (rawStream.socket) this._onconnect(publicKey, rawStream)
    else rawStream.on('connect', this._onconnect.bind(this, publicKey, rawStream))
  }

  get(publicKey) {
    const id = b4a.toString(publicKey, 'hex')
    const route = this._routes.get(id)
    if (!route) return null
    return route
  }

  _onconnect(publicKey, rawStream) {
    const id = b4a.toString(publicKey, 'hex')
    const socket = rawStream.socket

    let route = this._routes.get(id)

    if (!route) {
      const gc = () => {
        if (this._routes.get(id) === route) this._routes.delete(id)
        socket.removeListener('close', gc)
      }

      route = {
        socket,
        address: { host: rawStream.remoteHost, port: rawStream.remotePort },
        gc
      }

      this._routes.set(id, route)
      socket.on('close', gc)
    }

    this._pool.setReusable(socket, true)

    rawStream.on('error', () => {
      this._pool.setReusable(socket, false)
      if (!route) route = this._routes.get(id)
      if (route && route.socket === socket) route.gc()
    })
  }
}

// TODO: we should just make some "user data" object on udx to allow to attach this info
class SocketRef {
  constructor(pool) {
    this._pool = pool

    // Events
    this.onholepunchmessage = noop

    // Whether it should teardown immediately or wait a bit
    this.reusable = false

    this.socket = pool._dht.udx.createSocket()
    this.socket
      .on('close', this._onclose.bind(this))
      .on('message', this._onmessage.bind(this))
      .on('idle', this._onidle.bind(this))
      .on('busy', this._onbusy.bind(this))
      .bind(0, this._pool._host)

    this._refs = 1
    this._released = false
    this._closed = false

    this._timeout = null
    this._wasBusy = false

    this._pool._add(this)
  }

  _onclose() {
    this._pool._remove(this)
  }

  _onmessage(data, address) {
    if (data.byteLength > 1) {
      this._pool._onmessage(this, data, address)
    } else {
      this.onholepunchmessage(data, address, this)
    }
  }

  _onidle() {
    this._closeMaybe()
  }

  _onbusy() {
    this._wasBusy = true
    this._unlinger()
  }

  _reset() {
    this.onholepunchmessage = noop
  }

  _closeMaybe() {
    if (this._refs === 0 && this.socket.idle && !this._timeout) this._close()
  }

  _lingeringClose() {
    this._pool._lingering.delete(this)
    this._timeout = null
    this._closeMaybe()
  }

  _close() {
    this._unlinger()

    if (this.reusable && this._wasBusy) {
      this._wasBusy = false
      this._pool._lingering.add(this)
      this._timeout = setTimeout(this._lingeringClose.bind(this), LINGER_TIME)
      return
    }

    this._closed = true
    this.socket.close()
  }

  _unlinger() {
    if (this._timeout !== null) {
      clearTimeout(this._timeout)
      this._pool._lingering.delete(this)
      this._timeout = null
    }
  }

  get free() {
    return this._refs === 0
  }

  active() {
    this._refs++
    this._unlinger()
  }

  inactive() {
    this._refs--
    this._closeMaybe()
  }

  address() {
    return this.socket.address()
  }

  release() {
    if (this._released) return

    this._released = true
    this._reset()

    this._refs--
    this._closeMaybe()
  }
}

function noop() {}
{
  "name": "hyperdht",
  "version": "6.32.0",
  "description": "The DHT powering Hyperswarm",
  "main": "index.js",
  "browser": "browser.js",
  "bin": {
    "hyperdht": "./bin.js"
  },
  "files": [
    "index.js",
    "browser.js",
    "testnet.js",
    "bin.js",
    "lib/**.js"
  ],
  "imports": {
    "events": {
      "bare": "bare-events",
      "default": "events"
    },
    "child_process": {
      "bare": "bare-node-child-process",
      "default": "child_process"
    }
  },
  "dependencies": {
    "@hyperswarm/secret-stream": "^6.6.2",
    "b4a": "^1.3.1",
    "bare-events": "^2.2.0",
    "blind-relay": "^1.3.0",
    "bogon": "^1.0.0",
    "compact-encoding": "^3.0.0",
    "dht-rpc": "^6.15.1",
    "hypercore-crypto": "^3.3.0",
    "hypercore-id-encoding": "^1.2.0",
    "hyperdht-address": "^1.0.1",
    "noise-curve-ed": "^2.0.0",
    "noise-handshake": "^4.0.0",
    "record-cache": "^1.1.1",
    "safety-catch": "^1.0.1",
    "signal-promise": "^1.0.3",
    "sodium-universal": "^5.0.1",
    "streamx": "^2.16.1",
    "unslab": "^1.3.0",
    "xache": "^1.1.0"
  },
  "devDependencies": {
    "bare-node-child-process": "^1.0.1",
    "brittle": "^3.0.0",
    "graceful-goodbye": "^1.3.0",
    "newline-decoder": "^1.0.2",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^2.0.0"
  },
  "scripts": {
    "format": "prettier --write .",
    "test": "prettier --check . && node test/all.js",
    "test:bare": "bare test/all.js",
    "test:generate": "brittle -r test/all.js test/*.js",
    "integration": "brittle test/integration/*.js",
    "end-to-end": "brittle test/end-to-end/*.js"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "directories": {
    "lib": "lib",
    "test": "test"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/hyperdht.git"
  },
  "keywords": [],
  "bugs": {
    "url": "https://github.com/holepunchto/hyperdht/issues"
  },
  "homepage": "https://github.com/holepunchto/hyperdht#readme"
}
{
  "name": "hyperschema",
  "version": "1.21.0",
  "description": "Create registries of declarative compact-encoding schemas",
  "files": [
    "lib/*.js",
    "builder.mjs",
    "builder.cjs",
    "runtime.mjs",
    "runtime.cjs",
    "primitives.mjs",
    "primitives.cjs"
  ],
  "exports": {
    "./package": "./package.json",
    ".": {
      "import": "./builder.mjs",
      "default": "./builder.cjs"
    },
    "./runtime": {
      "import": "./runtime.mjs",
      "default": "./runtime.cjs"
    },
    "./primitives": {
      "import": "./primitives.mjs",
      "default": "./primitives.cjs"
    }
  },
  "imports": {
    "fs": {
      "bare": "bare-fs",
      "default": "fs"
    },
    "path": {
      "bare": "bare-path",
      "default": "path"
    }
  },
  "scripts": {
    "format": "prettier . --write",
    "test": "prettier . --check && brittle test/index.js",
    "test:bare": "prettier . --check && bare test/index.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/hyperschema.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/hyperschema/issues"
  },
  "homepage": "https://github.com/holepunchto/hyperschema#readme",
  "dependencies": {
    "bare-fs": "^4.0.1",
    "compact-encoding": "^3.0.0",
    "generate-object-property": "^2.0.0",
    "generate-string": "^1.0.1"
  },
  "devDependencies": {
    "brittle": "^3.7.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^2.0.0",
    "test-tmp": "^1.3.0"
  }
}
module.exports = {
  c: require('compact-encoding')
}
const { EventEmitter } = require('events')
const { getStreamError } = require('streamx')
const DHT = require('hyperdht')
const spq = require('shuffled-priority-queue')
const b4a = require('b4a')
const unslab = require('unslab')

const PeerInfo = require('./lib/peer-info')
const RetryTimer = require('./lib/retry-timer')
const ConnectionSet = require('./lib/connection-set')
const PeerDiscovery = require('./lib/peer-discovery')

const MAX_PEERS = 64
const MAX_PARALLEL = 3
const MAX_CLIENT_CONNECTIONS = Infinity // TODO: Change
const MAX_SERVER_CONNECTIONS = Infinity

const ERR_MISSING_TOPIC = 'Topic is required and must be a 32-byte buffer'
const ERR_DESTROYED = 'Swarm has been destroyed'
const ERR_DUPLICATE = 'Duplicate connection'
const ERR_FIREWALL = 'Peer is firewalled'

module.exports = class Hyperswarm extends EventEmitter {
  constructor(opts = {}) {
    super()
    const {
      seed,
      relayThrough,
      keyPair = DHT.keyPair(seed),
      maxPeers = MAX_PEERS,
      maxClientConnections = MAX_CLIENT_CONNECTIONS,
      maxServerConnections = MAX_SERVER_CONNECTIONS,
      maxParallel = MAX_PARALLEL,
      firewall = allowAll
    } = opts
    this.keyPair = keyPair

    this.dht =
      opts.dht ||
      new DHT({
        bootstrap: opts.bootstrap,
        nodes: opts.nodes,
        port: opts.port,
        deferRandomPunch: opts.deferRandomPunch,
        randomPunchInterval: opts.randomPunchInterval
      })
    this.server = this.dht.createServer(
      {
        firewall: this._handleFirewall.bind(this),
        relayThrough: this._maybeRelayConnection.bind(this),
        handshakeClearWait: opts.handshakeClearWait
      },
      this._handleServerConnection.bind(this)
    )

    this.destroyed = false
    this.suspended = false
    this.maxPeers = maxPeers
    this.maxClientConnections = maxClientConnections
    this.maxServerConnections = maxServerConnections
    this.maxParallel = maxParallel
    this.relayThrough = relayThrough ? toRelayFunction(relayThrough) : null

    this.connecting = 0
    this.connections = new Set()
    this.peers = new Map()
    this.explicitPeers = new Set()
    this.listening = null
    this.stats = {
      updates: 0,
      connects: {
        client: {
          opened: 0,
          closed: 0,
          attempted: 0
        },
        server: {
          // Note: there is no notion of 'attempts' for server connections
          opened: 0,
          closed: 0
        }
      },
      bannedPeers: 0
    }

    this._discovery = new Map()
    this._timer = new RetryTimer(this._requeue.bind(this), {
      backoffs: opts.backoffs,
      jitter: opts.jitter
    })
    this._queue = spq()

    this._allConnections = new ConnectionSet()
    this._pendingFlushes = []
    this._flushTick = 0

    this._drainingQueue = false
    this._clientConnections = 0
    this._serverConnections = 0
    this._firewall = firewall

    this.dht.on('network-change', this._handleNetworkChange.bind(this))
    this.dht.on('network-update', this._handleNetworkUpdate.bind(this))
    this.on('update', this._handleUpdate)
  }

  _maybeRelayConnection(force) {
    if (!this.relayThrough) return null
    return this.relayThrough(force, this)
  }

  _enqueue(peerInfo) {
    if (peerInfo.queued) return
    peerInfo.queued = true
    peerInfo._flushTick = this._flushTick
    this._queue.add(peerInfo)

    this._attemptClientConnections()
  }

  _requeue(batch) {
    if (this.suspended) return
    for (const peerInfo of batch) {
      peerInfo.waiting = false

      if (
        peerInfo._updatePriority() === false ||
        this._allConnections.has(peerInfo.publicKey) ||
        peerInfo.queued
      ) {
        continue
      }
      peerInfo.queued = true
      peerInfo._flushTick = this._flushTick
      this._queue.add(peerInfo)
    }

    this._attemptClientConnections()
  }

  _flushMaybe(peerInfo) {
    for (let i = 0; i < this._pendingFlushes.length; i++) {
      const flush = this._pendingFlushes[i]
      if (peerInfo._flushTick > flush.tick) continue
      if (--flush.missing > 0) continue
      flush.onflush(true)
      this._pendingFlushes.splice(i--, 1)
    }
  }

  _withinMaxPeers() {
    const e = this.explicitPeers.size
    const factor = e === 0 ? 1 : e < 2 ? 2 : e < 4 ? 3 : 4
    return factor * this._allConnections.size < this.maxPeers
  }

  _flushAllMaybe() {
    if (
      this.connecting > 0 ||
      (this._withinMaxPeers() && this._clientConnections < this.maxClientConnections)
    ) {
      return false
    }

    while (this._pendingFlushes.length) {
      const flush = this._pendingFlushes.pop()
      flush.onflush(true)
    }

    return true
  }

  _shouldConnectExplicit() {
    return !this.destroyed && !this.suspended && this.connecting < this.maxParallel
  }

  _shouldConnect() {
    return (
      !this.destroyed &&
      !this.suspended &&
      this.connecting < this.maxParallel &&
      this._withinMaxPeers() &&
      this._clientConnections < this.maxClientConnections
    )
  }

  _shouldRequeue(peerInfo) {
    if (this.suspended) return false
    if (peerInfo.explicit) return true
    for (const topic of peerInfo.topics) {
      if (this._discovery.has(b4a.toString(topic, 'hex')) && !this.destroyed) {
        return true
      }
    }
    return false
  }

  _connect(peerInfo, queued) {
    if (peerInfo.banned || this._allConnections.has(peerInfo.publicKey)) {
      if (queued) this._flushMaybe(peerInfo)
      return
    }

    // TODO: Support async firewalling at some point.
    if (this._handleFirewall(peerInfo.publicKey, null)) {
      if (queued) this._flushMaybe(peerInfo)
      return
    }

    const relayThrough = this._maybeRelayConnection(peerInfo.forceRelaying)
    const conn = this.dht.connect(peerInfo.publicKey, {
      relayAddresses: peerInfo.relayAddresses,
      keyPair: this.keyPair,
      relayThrough
    })
    this._allConnections.add(conn)

    this.stats.connects.client.attempted++

    this.connecting++
    this._clientConnections++
    let opened = false

    const onerror = (err) => {
      if (this.relayThrough && shouldForceRelaying(err.code)) {
        peerInfo.forceRelaying = true
        // Reset the attempts in order to fast connect to relay
        peerInfo.attempts = 0
      }
    }

    // Removed once a connection is opened
    conn.on('error', onerror)

    conn.on('open', () => {
      opened = true
      this.stats.connects.client.opened++

      this._connectDone()
      this.connections.add(conn)
      conn.removeListener('error', onerror)
      peerInfo._connected()
      peerInfo.client = true
      this.emit('connection', conn, peerInfo)
      if (queued) this._flushMaybe(peerInfo)

      this.emit('update')
    })
    conn.on('close', () => {
      if (!opened) this._connectDone()
      this.stats.connects.client.closed++

      const err = getStreamError(conn)
      if (shouldBan(err)) {
        this._banPeer(peerInfo, true, err)
      }

      this.connections.delete(conn)
      this._allConnections.delete(conn)
      this._clientConnections--
      peerInfo._disconnected()

      peerInfo.waiting = this._shouldRequeue(peerInfo) && this._timer.add(peerInfo)
      this._maybeDeletePeer(peerInfo)

      if (!opened && queued) this._flushMaybe(peerInfo)

      this._attemptClientConnections()

      this.emit('update')
    })

    this.emit('update')
  }

  _connectDone() {
    this.connecting--

    if (this.connecting < this.maxParallel) this._attemptClientConnections()
    if (this.connecting === 0) this._flushAllMaybe()
  }

  // Called when the PeerQueue indicates a connection should be attempted.
  _attemptClientConnections() {
    // Guard against re-entries - unsure if it still needed but doesn't hurt
    if (this._drainingQueue || this.suspended) return
    this._drainingQueue = true

    for (const peerInfo of this.explicitPeers) {
      if (!this._shouldConnectExplicit()) break
      if (
        peerInfo.attempts >= 5 ||
        Date.now() - peerInfo.disconnectedTime < peerInfo.attempts * 1000
      ) {
        continue
      }
      this._connect(peerInfo, false)
    }

    while (this._queue.length && this._shouldConnect()) {
      const peerInfo = this._queue.shift()
      peerInfo.queued = false
      this._connect(peerInfo, true)
    }
    this._drainingQueue = false
    if (this.connecting === 0) this._flushAllMaybe()
  }

  _handleFirewall(remotePublicKey, payload) {
    if (b4a.equals(remotePublicKey, this.keyPair.publicKey)) return true

    let peerInfo = this.peers.get(b4a.toString(remotePublicKey, 'hex'))
    if (peerInfo && peerInfo.banned) return true

    const firewalled = this._firewall(remotePublicKey, payload)
    if (firewalled) {
      if (!peerInfo) peerInfo = this._upsertPeer(remotePublicKey)
      this._banPeer(peerInfo, true, new Error(ERR_FIREWALL))
    }

    return firewalled
  }

  _handleServerConnectionSwap(existing, conn) {
    let closed = false

    existing.on('close', () => {
      if (closed) return

      conn.removeListener('error', noop)
      conn.removeListener('close', onclose)

      this._handleServerConnection(conn)
    })

    conn.on('error', noop)
    conn.on('close', onclose)

    function onclose() {
      closed = true
    }
  }

  // Called when the DHT receives a new server connection.
  _handleServerConnection(conn) {
    if (this.destroyed || this.suspended) {
      // TODO: Investigate why a final server connection can be received after close
      conn.on('error', noop)
      return conn.destroy(ERR_DESTROYED)
    }

    const existing = this._allConnections.get(conn.remotePublicKey)

    if (existing) {
      // If both connections are from the same peer,
      // - pick the new one if the existing stream is already established (has sent and received bytes),
      //   because the other client must have lost that connection and be reconnecting
      // - otherwise, pick the one thats expected to initiate in a tie break
      const existingIsOutdated = existing.rawBytesRead > 0 && existing.rawBytesWritten > 0
      const expectedInitiator = b4a.compare(conn.publicKey, conn.remotePublicKey) > 0
      const keepNew = existingIsOutdated || expectedInitiator === conn.isInitiator

      if (keepNew === false) {
        existing.sendKeepAlive()
        conn.on('error', noop)
        conn.destroy(new Error(ERR_DUPLICATE))
        return
      }

      existing.on('error', noop)
      existing.destroy(new Error(ERR_DUPLICATE))
      this._handleServerConnectionSwap(existing, conn)
      return
    }

    // When reaching here, the connection will always be 'opened' next tick
    this.stats.connects.server.opened++

    const peerInfo = this._upsertPeer(conn.remotePublicKey, null)

    this.connections.add(conn)
    this._allConnections.add(conn)
    this._serverConnections++

    conn.on('close', () => {
      const err = getStreamError(conn)
      if (shouldBan(err)) {
        this._banPeer(peerInfo, true, err)
      }

      this.connections.delete(conn)
      this._allConnections.delete(conn)
      this._serverConnections--
      this.stats.connects.server.closed++

      this._maybeDeletePeer(peerInfo)

      this._attemptClientConnections()

      this.emit('update')
    })
    peerInfo.client = false
    this.emit('connection', conn, peerInfo)

    this.emit('update')
  }

  _upsertPeer(publicKey, relayAddresses) {
    if (b4a.equals(publicKey, this.keyPair.publicKey)) return null
    const keyString = b4a.toString(publicKey, 'hex')
    let peerInfo = this.peers.get(keyString)

    if (peerInfo) {
      peerInfo.relayAddresses = relayAddresses // new is always better
      return peerInfo
    }

    peerInfo = new PeerInfo({
      publicKey,
      relayAddresses
    })

    this.peers.set(keyString, peerInfo)
    return peerInfo
  }

  _handleUpdate() {
    this.stats.updates++
  }

  _maybeDeletePeer(peerInfo) {
    if (!peerInfo.shouldGC()) return

    const hasActiveConn = this._allConnections.has(peerInfo.publicKey)
    if (hasActiveConn) return

    const keyString = b4a.toString(peerInfo.publicKey, 'hex')
    this.peers.delete(keyString)
  }

  /*
   * Called when a peer is actively discovered during a lookup.
   *
   * Three conditions:
   *  1. Not a known peer -- insert into queue
   *  2. A known peer with normal priority -- do nothing
   *  3. A known peer with low priority -- bump priority, because it's been rediscovered
   */
  _handlePeer(peer, topic) {
    const peerInfo = this._upsertPeer(peer.publicKey, peer.relayAddresses)
    if (peerInfo) peerInfo._topic(topic)
    if (!peerInfo || this._allConnections.has(peer.publicKey)) return
    if (!peerInfo.prioritized || peerInfo.server) peerInfo._reset()
    if (peerInfo._updatePriority()) {
      this._enqueue(peerInfo)
    }
  }

  async _handleNetworkUpdate() {
    if (!this.online) return
    this._handleNetworkChange()
  }

  async _handleNetworkChange() {
    if (this.suspended) return

    // prioritize figuring out if existing connections are dead
    for (const conn of this._allConnections) {
      conn.sendKeepAlive()
    }

    const refreshes = []

    for (const discovery of this._discovery.values()) {
      refreshes.push(discovery.refresh())
    }

    await Promise.allSettled(refreshes)
  }

  _banPeer(peerInfo, banned, err) {
    peerInfo.ban(banned)
    this.stats.bannedPeers++
    this.emit('ban', peerInfo, err)
  }

  status(key) {
    return this._discovery.get(b4a.toString(key, 'hex')) || null
  }

  listen() {
    if (!this.listening) {
      if (this.destroyed) throw new Error('Swarm destroyed')
      this.listening = this.server.listen(this.keyPair)
    }
    return this.listening
  }

  // Object that exposes a cancellation method (destroy)
  // TODO: When you rejoin, it should reannounce + bump lookup priority
  join(topic, opts = {}) {
    if (this.destroyed) throw new Error('Swarm destroyed')
    if (!topic) throw new Error(ERR_MISSING_TOPIC)
    topic = unslab(topic)

    const topicString = b4a.toString(topic, 'hex')

    let discovery = this._discovery.get(topicString)

    if (discovery && !discovery.destroyed) {
      return discovery.session(opts)
    }

    discovery = new PeerDiscovery(this, topic, {
      limit: opts.limit,
      wait: discovery ? discovery.destroy() : null,
      suspended: this.suspended,
      onpeer: (peer) => this._handlePeer(peer, topic)
    })
    this._discovery.set(topicString, discovery)
    return discovery.session(opts)
  }

  // Returns a promise
  async leave(topic) {
    if (!topic) throw new Error(ERR_MISSING_TOPIC)
    const topicString = b4a.toString(topic, 'hex')
    if (!this._discovery.has(topicString)) return Promise.resolve()

    const discovery = this._discovery.get(topicString)

    try {
      await discovery.destroy()
    } catch {
      // ignore, prop network
    }

    if (this._discovery.get(topicString) === discovery) {
      this._discovery.delete(topicString)
    }
  }

  joinPeer(publicKey) {
    const peerInfo = this._upsertPeer(publicKey, null)
    if (!peerInfo) return
    if (!this.explicitPeers.has(peerInfo)) {
      peerInfo.explicit = true
      this.explicitPeers.add(peerInfo)
    }
    if (this._allConnections.has(publicKey)) return
    if (peerInfo._updatePriority()) {
      this._enqueue(peerInfo)
    }
  }

  leavePeer(publicKey) {
    const keyString = b4a.toString(publicKey, 'hex')
    if (!this.peers.has(keyString)) return

    const peerInfo = this.peers.get(keyString)
    peerInfo.explicit = false
    this.explicitPeers.delete(peerInfo)
    this._maybeDeletePeer(peerInfo)
  }

  // Returns a promise
  async flush() {
    const allFlushed = [...this._discovery.values()].map((v) => v.flushed())
    await Promise.all(allFlushed)
    if (this._flushAllMaybe()) return true
    const pendingSize = this._allConnections.size - this.connections.size
    if (!this._queue.length && !pendingSize) return true
    return new Promise((resolve) => {
      this._pendingFlushes.push({
        onflush: resolve,
        missing: this._queue.length + pendingSize,
        tick: this._flushTick++
      })
    })
  }

  async clear() {
    const cleared = Promise.allSettled([...this._discovery.values()].map((d) => d.destroy()))
    this._discovery.clear()
    return cleared
  }

  async destroy({ force } = {}) {
    if (this.destroyed && !force) return
    this.destroyed = true

    this._timer.destroy()

    if (!force) await this.clear()

    await this.server.close()

    while (this._pendingFlushes.length) {
      const flush = this._pendingFlushes.pop()
      flush.onflush(false)
    }

    await this.dht.destroy({ force })
  }

  async suspend({ log = noop } = {}) {
    if (this.suspended) return

    const promises = []

    promises.push(this.server.suspend({ log }))

    for (const discovery of this._discovery.values()) {
      promises.push(discovery.suspend({ log }))
    }

    const pending = []
    for (const connection of this._allConnections) {
      connection.destroy()
      pending.push(new Promise((resolve) => connection.on('close', resolve)))
    }

    this.suspended = true

    log('Suspending server and discovery... (' + promises.length + ')')
    await Promise.allSettled(promises)
    log('Done, suspending the dht...')
    await this.dht.suspend({ log })
    log('Done, swarm fully suspended')

    await Promise.all(pending)

    // reset queue
    this._timer.destroy()
    this._timer = new RetryTimer(this._requeue.bind(this), {
      backoffs: this._timer.backoffs,
      jitter: this._timer.jitter
    })
    this._queue = spq()
  }

  async resume({ log = noop } = {}) {
    if (!this.suspended) return

    log('Resuming the dht')
    await this.dht.resume()
    log('Done, resuming the server')
    await this.server.resume()
    log('Done, all discovery')

    for (const discovery of this._discovery.values()) {
      discovery.resume()
    }

    this.suspended = false
    this._attemptClientConnections()
  }

  topics() {
    return this._discovery.values()
  }
}

function noop() {}

function allowAll() {
  return false
}

function shouldForceRelaying(code) {
  return (
    code === 'HOLEPUNCH_ABORTED' ||
    code === 'HOLEPUNCH_DOUBLE_RANDOMIZED_NATS' ||
    code === 'REMOTE_NOT_HOLEPUNCHABLE'
  )
}

function shouldBan() {
  // return !!err && err.name === 'HypercoreError' && err.code === 'INVALID_OPERATION'
  return false
}

function toRelayFunction(relayThrough) {
  return typeof relayThrough === 'function'
    ? relayThrough
    : (force, swarm) => (force || swarm.dht.randomized ? relayThrough : null)
}
module.exports = class BulkTimer {
  constructor(time, fn) {
    this._time = time
    this._fn = fn
    this._interval = null
    this._next = []
    this._pending = []
    this._destroyed = false
  }

  destroy() {
    if (this._destroyed) return
    this._destroyed = true
    clearInterval(this._interval)
    this._interval = null
  }

  _ontick() {
    if (!this._next.length && !this._pending.length) return
    if (this._next.length) this._fn(this._next)
    this._next = this._pending
    this._pending = []
  }

  add(info) {
    if (this._destroyed) return
    if (!this._interval) {
      this._interval = setInterval(this._ontick.bind(this), Math.floor(this._time * 0.66))
    }

    this._pending.push(info)
  }
}
const b4a = require('b4a')

module.exports = class ConnectionSet {
  constructor() {
    this._byPublicKey = new Map()
  }

  [Symbol.iterator]() {
    return this._byPublicKey.values()
  }

  get size() {
    return this._byPublicKey.size
  }

  has(publicKey) {
    return this._byPublicKey.has(toHex(publicKey))
  }

  get(publicKey) {
    return this._byPublicKey.get(toHex(publicKey))
  }

  add(connection) {
    this._byPublicKey.set(b4a.toString(connection.remotePublicKey, 'hex'), connection)
  }

  delete(connection) {
    const keyString = b4a.toString(connection.remotePublicKey, 'hex')
    const existing = this._byPublicKey.get(keyString)
    if (existing !== connection) return
    this._byPublicKey.delete(keyString)
  }
}

function toHex(b) {
  return typeof b === 'string' ? b : b4a.toString(b, 'hex')
}
const safetyCatch = require('safety-catch')
const b4a = require('b4a')

const REFRESH_INTERVAL = 1000 * 60 * 10 // 10 min
const RANDOM_JITTER = 1000 * 60 * 2 // 2 min
const DELAY_GRACE_PERIOD = 1000 * 30 // 30s

const MAX_DISCOVERY_CACHE = 64

module.exports = class PeerDiscovery {
  constructor(
    swarm,
    topic,
    { limit = Infinity, wait = null, suspended = false, onpeer = noop, onerror = safetyCatch }
  ) {
    this.limit = limit
    this.swarm = swarm
    this.topic = topic
    this.isClient = false
    this.isServer = false
    this.destroyed = false
    this.destroying = null
    this.suspended = suspended

    this._sessions = []
    this._clientSessions = 0
    this._serverSessions = 0

    this._onpeer = onpeer
    this._onerror = onerror

    this._discovered = new Set()
    this._activeQuery = null
    this._timer = null
    this._currentRefresh = null
    this._closestNodes = null
    this._firstAnnounce = true
    this._needsUnannounce = false
    this._refreshes = 0
    this._wait = wait
  }

  session({ server = true, client = true, limit = Infinity, onerror = safetyCatch }) {
    if (this.destroyed) throw new Error('PeerDiscovery is destroyed')
    const session = new PeerDiscoverySession(this)
    session.refresh({ server, client, limit }).catch(onerror)
    this._sessions.push(session)
    return session
  }

  _refreshLater(eager) {
    const jitter = Math.round(Math.random() * RANDOM_JITTER)
    const delay = !eager ? REFRESH_INTERVAL + jitter : jitter

    if (this._timer) clearTimeout(this._timer)

    const startTime = Date.now()
    this._timer = setTimeout(() => {
      // If your laptop went to sleep, and is coming back online...
      const overdue = Date.now() - startTime > delay + DELAY_GRACE_PERIOD
      if (overdue) this._refreshLater(true)
      else this.refresh().catch(this._onerror)
    }, delay)
  }

  _isActive() {
    return !this.destroyed && !this.suspended
  }

  // TODO: Allow announce to be an argument to this
  // TODO: Maybe announce should be a setter?
  async _refresh() {
    if (this.suspended) return
    const clock = ++this._refreshes

    if (this._wait) {
      await this._wait
      this._wait = null
      if (clock !== this._refreshes || !this._isActive()) return
    }

    const clear = this.isServer && this._firstAnnounce
    if (clear) this._firstAnnounce = false

    const opts = {
      clear,
      closestNodes: this._closestNodes
    }

    if (this.isServer) {
      await this.swarm.listen()
      // if a parallel refresh is happening, yield to the new one
      if (clock !== this._refreshes || !this._isActive()) return
      this._needsUnannounce = true
    }

    let limit = this.limit

    if (limit < Infinity && limit > 0) {
      for (const id of this._discovered) {
        if (!this.swarm.connections.has(id)) continue
        if (--limit === 0) break
      }
    }

    this._discovered.clear()

    const announcing = this.isServer
    const query = (this._activeQuery = announcing
      ? this.swarm.dht.announce(
          this.topic,
          this.swarm.keyPair,
          this.swarm.server.relayAddresses,
          opts
        )
      : this._needsUnannounce
        ? this.swarm.dht.lookupAndUnannounce(this.topic, this.swarm.keyPair, opts)
        : this.swarm.dht.lookup(this.topic, opts))

    try {
      for await (const data of this._activeQuery) {
        if (!this.isClient || !this._isActive()) continue
        for (const peer of data.peers) {
          if (limit < Infinity) {
            const id = b4a.toString(peer.publicKey, 'hex')

            if (this._discovered.size < MAX_DISCOVERY_CACHE) {
              this._discovered.add(id)
            }

            if (limit === 0) continue

            // there is a chance it has updated a connection during discovery and we go over
            // the limit - thats acceptable to avoid a complexity spiral here.
            if (!this.swarm.connections.has(id)) limit--
          }

          this._onpeer(peer, data)
        }
      }
    } catch (err) {
      if (this._isActive()) throw err
    } finally {
      if (this._activeQuery === query) {
        this._activeQuery = null
        if (!this.destroyed && !this.suspended) this._refreshLater(false)
      }
    }

    // This is set at the very end, when the query completes successfully.
    this._closestNodes = query.closestNodes

    if (clock !== this._refreshes) return

    // In this is the latest query, unannounce has been fulfilled as well
    if (!announcing) this._needsUnannounce = false
  }

  async refresh() {
    if (this.destroyed) throw new Error('PeerDiscovery is destroyed')

    const server = this._serverSessions > 0
    const client = this._clientSessions > 0

    if (this.suspended) return

    if (server === this.isServer && client === this.isClient) {
      if (this._currentRefresh) return this._currentRefresh
      this._currentRefresh = this._refresh()
    } else {
      if (this._activeQuery) this._activeQuery.destroy()
      this.isServer = server
      this.isClient = client
      this._currentRefresh = this._refresh()
    }

    const refresh = this._currentRefresh
    try {
      await refresh
    } catch {
      return false
    } finally {
      if (refresh === this._currentRefresh) {
        this._currentRefresh = null
      }
    }

    return true
  }

  async flushed() {
    if (this.swarm.listening) await this.swarm.listening

    try {
      await this._currentRefresh
      return true
    } catch {
      return false
    }
  }

  async _destroyMaybe() {
    if (this.destroyed) return

    try {
      if (this._sessions.length === 0) await this.swarm.leave(this.topic)
      else if (this._serverSessions === 0 && this._needsUnannounce) await this.refresh()
    } catch (err) {
      // ignore network failures here, as we are tearing down
      safetyCatch(err)
    }
  }

  destroy() {
    if (this.destroying) return this.destroying
    this.destroying = this._destroy()
    return this.destroying
  }

  async _abort(log) {
    const id = log === noop ? '' : b4a.toString(this.topic, 'hex')

    log('Aborting discovery', id)
    if (this._wait) await this._wait
    log('Aborting discovery (post wait)', id)

    if (this._activeQuery) {
      this._activeQuery.destroy()
      this._activeQuery = null
    }
    if (this._timer) {
      clearTimeout(this._timer)
      this._timer = null
    }

    let nodes = this._closestNodes

    if (this._currentRefresh) {
      try {
        await this._currentRefresh
      } catch {
        // If the destroy causes the refresh to fail, suppress it.
      }
    }

    log('Aborting discovery (post refresh)', id)
    if (this._isActive()) return

    if (!nodes) nodes = this._closestNodes
    else if (this._closestNodes !== nodes) {
      const len = nodes.length
      for (const newer of this._closestNodes) {
        if (newer.id && !hasNode(nodes, len, newer)) nodes.push(newer)
      }
    }

    if (this._needsUnannounce) {
      log('Unannouncing discovery', id)
      if (nodes && nodes.length) {
        await this.swarm.dht.unannounce(this.topic, this.swarm.keyPair, {
          closestNodes: nodes,
          onlyClosestNodes: true,
          force: true
        })
      }
      this._needsUnannounce = false
      log('Unannouncing discovery (done)', id)
    }
  }

  _destroy() {
    if (this.destroyed) return
    this.destroyed = true
    return this._abort(noop)
  }

  async suspend({ log = noop } = {}) {
    if (this.suspended) return
    this.suspended = true
    try {
      await this._abort(log)
    } catch {
      // ignore
    }
  }

  resume() {
    if (!this.suspended) return
    this.suspended = false
    this.refresh().catch(noop)
  }
}

class PeerDiscoverySession {
  constructor(discovery) {
    this.discovery = discovery
    this.isClient = false
    this.isServer = false
    this.destroyed = false
  }

  get swarm() {
    return this.discovery.swarm
  }

  get topic() {
    return this.discovery.topic
  }

  async refresh({ client = this.isClient, server = this.isServer, limit = Infinity } = {}) {
    if (this.destroyed) throw new Error('PeerDiscovery is destroyed')
    if (!client && !server) throw new Error('Cannot refresh with neither client nor server option')

    if (client !== this.isClient) {
      this.isClient = client
      this.discovery._clientSessions += client ? 1 : -1
    }

    if (server !== this.isServer) {
      this.isServer = server
      this.discovery._serverSessions += server ? 1 : -1
    }

    this.discovery.limit = limit

    return this.discovery.refresh()
  }

  async flushed() {
    return this.discovery.flushed()
  }

  async destroy() {
    if (this.destroyed) return
    this.destroyed = true

    if (this.isClient) this.discovery._clientSessions--
    if (this.isServer) this.discovery._serverSessions--

    const index = this.discovery._sessions.indexOf(this)
    const head = this.discovery._sessions.pop()

    if (head !== this) this.discovery._sessions[index] = head

    return this.discovery._destroyMaybe()
  }
}

function hasNode(nodes, len, node) {
  for (let i = 0; i < len; i++) {
    const existing = nodes[i]
    if (existing.id && b4a.equals(existing.id, node.id)) return true
  }

  return false
}

function noop() {}
const { EventEmitter } = require('events')
const b4a = require('b4a')
const unslab = require('unslab')

const MIN_CONNECTION_TIME = 15000

const VERY_LOW_PRIORITY = 0
const LOW_PRIORITY = 1
const NORMAL_PRIORITY = 2
const HIGH_PRIORITY = 3
const VERY_HIGH_PRIORITY = 4

module.exports = class PeerInfo extends EventEmitter {
  constructor({ publicKey, relayAddresses }) {
    super()

    this.publicKey = unslab(publicKey)
    this.relayAddresses = relayAddresses

    this.reconnecting = true
    this.proven = false
    this.connectedTime = -1
    this.disconnectedTime = 0
    this.banned = false
    this.tried = false
    this.explicit = false
    this.waiting = false
    this.forceRelaying = false

    // Set by the Swarm
    this.queued = false
    this.client = false
    this.topics = [] // TODO: remove on next major (check with mafintosh for context)

    this.attempts = 0
    this.priority = NORMAL_PRIORITY

    // Used by shuffled-priority-queue
    this._index = 0

    // Used for flush management
    this._flushTick = 0

    // Used for topic multiplexing
    this._seenTopics = new Set()
  }

  get server() {
    return !this.client
  }

  get prioritized() {
    return this.priority >= NORMAL_PRIORITY
  }

  _getPriority() {
    const peerIsStale = this.tried && !this.proven
    if (peerIsStale || this.attempts > 3) return VERY_LOW_PRIORITY
    if (this.attempts === 3) return LOW_PRIORITY
    if (this.attempts === 2) return HIGH_PRIORITY
    if (this.attempts === 1) return VERY_HIGH_PRIORITY
    return NORMAL_PRIORITY
  }

  _connected() {
    this.proven = true
    this.connectedTime = Date.now()
  }

  _disconnected() {
    this.disconnectedTime = Date.now()
    if (this.connectedTime > -1) {
      if (this.disconnectedTime - this.connectedTime >= MIN_CONNECTION_TIME) this.attempts = 0 // fast retry
      this.connectedTime = -1
    }
    this.attempts++
  }

  _deprioritize() {
    this.attempts = 3
  }

  _reset() {
    this.client = false
    this.proven = false
    this.tried = false
    this.attempts = 0
  }

  _updatePriority() {
    if (this.explicit && this.attempts > 3) this._deprioritize()
    if (this.banned || this.queued || this.attempts > 3) return false
    this.priority = this._getPriority()
    return true
  }

  _topic(topic) {
    const topicString = b4a.toString(topic, 'hex')
    if (this._seenTopics.has(topicString)) return
    this._seenTopics.add(topicString)
    this.topics.push(topic)
    this.emit('topic', topic)
  }

  reconnect(val) {
    this.reconnecting = !!val
  }

  ban(val) {
    this.banned = !!val
  }

  shouldGC() {
    return !(this.banned || this.queued || this.explicit || this.waiting)
  }
}
const BulkTimer = require('./bulk-timer')

const BACKOFF_JITTER = 500
const BACKOFF_S = 1000 + Math.round(BACKOFF_JITTER * Math.random())
const BACKOFF_M = 5000 + Math.round(2 * BACKOFF_JITTER * Math.random())
const BACKOFF_L = 15000 + Math.round(4 * BACKOFF_JITTER * Math.random())
const BACKOFF_X = 1000 * 60 * 10 + Math.round(240 * BACKOFF_JITTER * Math.random())

module.exports = class RetryTimer {
  constructor(
    push,
    { backoffs = [BACKOFF_S, BACKOFF_M, BACKOFF_L, BACKOFF_X], jitter = BACKOFF_JITTER } = {}
  ) {
    this.jitter = jitter
    this.backoffs = backoffs

    this._sTimer = new BulkTimer(backoffs[0] + Math.round(jitter * Math.random()), push)
    this._mTimer = new BulkTimer(backoffs[1] + Math.round(jitter * Math.random()), push)
    this._lTimer = new BulkTimer(backoffs[2] + Math.round(jitter * Math.random()), push)
    this._xTimer = new BulkTimer(backoffs[3] + Math.round(jitter * Math.random()), push)
  }

  _selectRetryTimer(peerInfo) {
    if (peerInfo.banned || !peerInfo.reconnecting) return null

    if (peerInfo.attempts > 3) {
      return peerInfo.explicit ? this._xTimer : null
    }

    if (peerInfo.attempts === 0) return this._sTimer
    if (peerInfo.proven) {
      switch (peerInfo.attempts) {
        case 1:
          return this._sTimer
        case 2:
          return this._mTimer
        case 3:
          return this._lTimer
      }
    } else {
      switch (peerInfo.attempts) {
        case 1:
          return this._mTimer
        case 2:
          return this._lTimer
        case 3:
          return this._lTimer
      }
    }

    return null
  }

  add(peerInfo) {
    const timer = this._selectRetryTimer(peerInfo)
    if (!timer) return false

    timer.add(peerInfo)
    return true
  }

  destroy() {
    this._sTimer.destroy()
    this._mTimer.destroy()
    this._lTimer.destroy()
    this._xTimer.destroy()
  }
}
{
  "name": "hyperswarm",
  "version": "4.17.0",
  "description": "A distributed networking stack for connecting peers",
  "files": [
    "index.js",
    "lib/**.js"
  ],
  "imports": {
    "events": {
      "bare": "bare-events",
      "default": "events"
    },
    "process": {
      "bare": "bare-process",
      "default": "process"
    }
  },
  "dependencies": {
    "b4a": "^1.3.1",
    "bare-events": "^2.2.0",
    "hyperdht": "^6.21.0",
    "safety-catch": "^1.0.2",
    "shuffled-priority-queue": "^2.1.0",
    "streamx": "^2.22.1",
    "unslab": "^1.3.0"
  },
  "devDependencies": {
    "bare-process": "^4.2.2",
    "brittle": "^3.0.2",
    "hypercore-crypto": "^3.4.0",
    "lunte": "^1.3.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^2.0.0",
    "which-runtime": "^1.3.2"
  },
  "scripts": {
    "format": "prettier --write .",
    "lint": "prettier --check . && lunte",
    "test": "npm run test:node && npm run test:bare",
    "test:generate": "brittle -r test/all.js test/*.js",
    "test:node": "brittle-node --coverage test/*.js",
    "test:bare": "bare test/all.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/holepunchto/hyperswarm.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "contributors": [
    "David Mark Clements (@davidmarkclem)",
    "Andrew Osheroff (@andrewosh)"
  ],
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/holepunchto/hyperswarm/issues"
  },
  "homepage": "https://github.com/holepunchto/hyperswarm"
}
const b4a = require('b4a')

const EMPTY = b4a.alloc(0)
const MAX = b4a.from([0xff])
const BUFFER = {}

BUFFER.preencode = function (state, buf) {
  if (buf === null) buf = EMPTY

  let i = 0
  let extra = 3

  while ((i = b4a.indexOf(buf, 0x00, i)) > -1) {
    i++
    extra++
  }

  state.end += buf.byteLength + extra
}

BUFFER.encode = function (state, buf) {
  if (buf === null) buf = EMPTY

  state.buffer[state.start++] = 0x00

  let prev = 0
  let i = 0

  while ((i = b4a.indexOf(buf, 0x00, i)) > -1) {
    const slice = buf.subarray(prev, ++i)

    state.buffer.set(slice, state.start)
    state.start += slice.byteLength
    state.buffer[state.start++] = 0x02
    prev = i
  }

  const slice = buf.subarray(prev)

  state.buffer.set(slice, state.start)
  state.start += slice.byteLength

  state.buffer[state.start++] = 0x00
  state.buffer[state.start++] = 0x01
}

BUFFER.decode = function (state) {
  if (state.start >= state.end) throw new Error('Out of bounds')
  if (state.buffer[state.start++] !== 0x00) {
    throw new Error('Invalid start of string')
  }

  let escaped = null

  let prev = state.start
  let i = state.start

  while ((i = b4a.indexOf(state.buffer, 0x00, i)) > -1) {
    const next = ++i < state.end ? state.buffer[i] : 0x00

    i++

    if (next === 0x01) {
      break
    }

    if (next === 0x02) {
      if (escaped === null) escaped = []
      escaped.push(state.buffer.subarray(prev, i - 1))
      prev = i
      continue
    }

    throw new Error('Unknown value in terminator')
  }

  if (i === -1) {
    throw new Error('No terminator found')
  }

  state.start = i
  const last = state.buffer.subarray(prev, i - 2)
  if (escaped === null) return last

  escaped.push(last)
  return b4a.concat(escaped)
}

// TODO: can be optimised a lot

const STRING = {}

STRING.preencode = function (state, str) {
  BUFFER.preencode(state, b4a.from(str || ''))
}

STRING.encode = function (state, str) {
  BUFFER.encode(state, b4a.from(str || ''))
}

STRING.decode = function (state, str) {
  return b4a.toString(BUFFER.decode(state))
}

const UINT = {}

UINT.preencode = function (state, n) {
  state.end += n <= 0xfb ? 1 : n <= 0xffff ? 3 : n <= 0xffffffff ? 5 : n === Infinity ? 1 : 9
}

UINT.encode = function (state, n) {
  if (n === Infinity) {
    state.buffer[state.start++] = 0xff
    return
  }

  if (n <= 0xfb) {
    state.buffer[state.start++] = n
    return
  }

  if (n <= 0xffff) {
    state.buffer[state.start++] = 0xfc
    state.buffer[state.start++] = n >>> 8
    state.buffer[state.start++] = n
    return
  }

  if (n <= 0xffffffff) {
    state.buffer[state.start++] = 0xfd
    encodeUint32(state, n)
    return
  }

  if (Number.isSafeInteger(n)) {
    state.buffer[state.start++] = 0xfe

    const r = Math.floor(n / 0x100000000)
    encodeUint32(state, r)
    encodeUint32(state, n)
    return
  }

  throw new Error('Invalid number ' + n)
}

UINT.decode = function (state) {
  if (state.start >= state.end) throw new Error('Out of bounds')

  const a = state.buffer[state.start++]

  if (a <= 0xfb) return a

  if (a === 0xfc) {
    if (state.end - state.start < 2) throw new Error('Out of bounds')
    return state.buffer[state.start++] * 0x100 + state.buffer[state.start++]
  }

  if (a === 0xfd) {
    return decodeUint32(state)
  }

  if (a === 0xfe) {
    return decodeUint32(state) * 0x100000000 + decodeUint32(state)
  }

  return Infinity
}

const INT = {}

INT.preencode = function (state, n) {
  // One asymmetric condition
  if (n >= -0xff && n < 0) {
    state.end += 2
    return
  }
  n = Math.abs(n)
  state.end += n <= 0xf6 ? 1 : n <= 0xffff ? 3 : n <= 0xffffffff ? 5 : n === Infinity ? 1 : 9
}

INT.encode = function (state, n) {
  if (n === Infinity) {
    state.buffer[state.start++] = 0xff
    return
  }

  if (n === -Infinity) {
    state.buffer[state.start++] = 0x00
    return
  }

  // Negative 64 bit
  if (n < -0xffffffff) {
    if (!Number.isSafeInteger(n)) throw new Error('Invalid number ' + n)
    state.buffer[state.start++] = 0x01

    const r = Math.floor(-n / 0x100000000)
    encodeUint32(state, -r + 0xffffffff)
    encodeUint32(state, -(-n % 0x100000000) + 0xffffffff)
    return
  }

  // Negative 32 bit
  if (n < -0xffff) {
    state.buffer[state.start++] = 0x02

    n += 0xffffffff
    encodeUint32(state, n)
    return
  }

  // Negative 16bit
  if (n < -0xff) {
    state.buffer[state.start++] = 0x03

    n += 0xffff
    state.buffer[state.start++] = n >>> 8
    state.buffer[state.start++] = n
    return
  }

  // Negative 8bit
  if (n < 0) {
    state.buffer[state.start++] = 0x04

    n += 0xff
    state.buffer[state.start++] = n
    return
  }

  // Remainder of 8bit space for positive int
  if (n <= 0xf6) {
    state.buffer[state.start++] = n + 0x05
    return
  }

  // Positive 16bit
  if (n <= 0xffff) {
    state.buffer[state.start++] = 0xfc
    state.buffer[state.start++] = n >>> 8
    state.buffer[state.start++] = n
    return
  }

  // Positive 32bit
  if (n <= 0xffffffff) {
    state.buffer[state.start++] = 0xfd
    encodeUint32(state, n)
    return
  }

  // Positive 64bit
  if (Number.isSafeInteger(n)) {
    state.buffer[state.start++] = 0xfe

    const r = Math.floor(n / 0x100000000)
    encodeUint32(state, r)
    encodeUint32(state, n)
    return
  }

  throw new Error('Invalid number ' + n)
}

INT.decode = function (state) {
  if (state.start >= state.end) throw new Error('Out of bounds')

  const a = state.buffer[state.start++]

  if (a === 0x00) return -Infinity

  if (a === 0x01) {
    // Split (2^64 - 1) into its 32 bit components
    // r * 0x100000000 + n - (2^64 - 1)
    // r * 0x100000000 + n - (0xffffffff * 0x100000000) - 0xffffffff
    // (r - 0xffffffff) * 0x100000000 + n - 0xffffffff
    return (decodeUint32(state) - 0xffffffff) * 0x100000000 + decodeUint32(state) - 0xffffffff
  }

  if (a === 0x02) {
    return decodeUint32(state) - 0xffffffff
  }

  if (a === 0x03) {
    if (state.end - state.start < 2) throw new Error('Out of bounds')
    return state.buffer[state.start++] * 0x100 + state.buffer[state.start++] - 0xffff
  }

  if (a === 0x04) return state.buffer[state.start++] - 0xff

  if (a <= 0xfb) return a - 0x05

  if (a === 0xfc) {
    if (state.end - state.start < 2) throw new Error('Out of bounds')
    return state.buffer[state.start++] * 0x100 + state.buffer[state.start++]
  }

  if (a === 0xfd) {
    return decodeUint32(state)
  }

  if (a === 0xfe) {
    return decodeUint32(state) * 0x100000000 + decodeUint32(state)
  }

  return Infinity
}

const DATE = {}

DATE.preencode = (state, d) => INT.preencode(state, d.getTime())
DATE.encode = (state, d) => INT.encode(state, d.getTime())
DATE.decode = (state) => new Date(INT.decode(state))

const BOOL = {}

BOOL.preencode = (state, b) => UINT.preencode(state, b ? 1 : 0)
BOOL.encode = (state, b) => UINT.encode(state, b ? 1 : 0)
BOOL.decode = (state) => !!UINT.decode(state)

module.exports = class IndexEncoder {
  constructor(encodings, { prefix = -1 } = {}) {
    this.encodings = encodings
    this.prefix = prefix
  }

  static BUFFER = BUFFER
  static STRING = STRING
  static UINT = UINT
  static INT = INT
  static DATE = DATE
  static BOOL = BOOL

  static lookup(c) {
    switch (c) {
      case 'uint':
        return UINT
      case 'uint8':
        return UINT
      case 'uint16':
        return UINT
      case 'uint24':
        return UINT
      case 'uint32':
        return UINT
      case 'uint40':
        return UINT
      case 'uint48':
        return UINT
      case 'uint56':
        return UINT
      case 'uint64':
        return UINT
      case 'int':
        return INT
      case 'int8':
        return INT
      case 'int16':
        return INT
      case 'int24':
        return INT
      case 'int32':
        return INT
      case 'int40':
        return INT
      case 'int48':
        return INT
      case 'int56':
        return INT
      case 'int64':
        return INT
      case 'string':
        return STRING
      case 'utf8':
        return STRING
      case 'ascii':
        return STRING
      case 'hex':
        return STRING
      case 'base64':
        return STRING
      case 'fixed32':
        return BUFFER
      case 'fixed64':
        return BUFFER
      case 'buffer':
        return BUFFER
      case 'date':
        return DATE
      case 'bool':
        return BOOL
    }

    throw new Error('Unknown type')
  }

  encode(keys) {
    return this._encode(keys, false)
  }

  _encode(keys, terminate) {
    if (b4a.isBuffer(keys)) return keys

    const state = { start: 0, end: 0, buffer: null }

    if (this.prefix !== -1) UINT.preencode(state, this.prefix)
    for (let i = 0; i < keys.length; i++) {
      this.encodings[i].preencode(state, keys[i])
    }

    if (terminate && keys.length < this.encodings.length) {
      state.end++
    }

    state.buffer = b4a.allocUnsafe(state.end)

    if (this.prefix !== -1) UINT.encode(state, this.prefix)
    for (let i = 0; i < keys.length; i++) {
      this.encodings[i].encode(state, keys[i])
    }

    if (terminate && keys.length < this.encodings.length) {
      state.buffer[state.start++] = MAX[0]
    }

    return state.buffer
  }

  decode(buffer) {
    const state = { start: 0, end: buffer.byteLength, buffer }
    const result = []

    if (this.prefix !== -1) UINT.decode(state)
    for (const enc of this.encodings) {
      const key = state.start < state.end ? enc.decode(state) : enc === UINT ? 0 : null
      result.push(key)
    }

    return result
  }

  encodeRange({ gt, gte, lt, lte }) {
    const range = {
      gt: gt && this._encode(gt, true),
      gte: gte && this._encode(gte, false),
      lt: lt && this._encode(lt, false),
      lte: lte && this._encode(lte, true)
    }

    if (this.prefix !== -1) {
      if (!gt && !gte) range.gte = encodeUint(this.prefix)
      if (!lt && !lte) range.lt = encodeUint(this.prefix + 1)
    }

    return range
  }
}

function encodeUint(n) {
  const state = { start: 0, end: 0, buffer: null }
  UINT.preencode(state, n)
  state.buffer = b4a.allocUnsafe(state.end)
  UINT.encode(state, n)
  return state.buffer
}

function encodeUint32(state, n) {
  state.buffer[state.start++] = n >>> 24
  state.buffer[state.start++] = n >>> 16
  state.buffer[state.start++] = n >>> 8
  state.buffer[state.start++] = n
}

function decodeUint32(state) {
  if (state.end - state.start < 4) throw new Error('Out of bounds')
  return (
    state.buffer[state.start++] * 0x1000000 +
    state.buffer[state.start++] * 0x10000 +
    state.buffer[state.start++] * 0x100 +
    state.buffer[state.start++]
  )
}
{
  "name": "index-encoder",
  "version": "3.5.0",
  "description": "Encode multiple values into sorted keys",
  "main": "index.js",
  "dependencies": {
    "b4a": "^1.6.4"
  },
  "devDependencies": {
    "brittle": "^3.2.1",
    "hyperbee": "^2.10.5",
    "hypercore": "^11.0.0",
    "lunte": "^1.0.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^2.0.0"
  },
  "scripts": {
    "format": "prettier --write .",
    "lint": "prettier --check . && lunte",
    "test": "npm run test:node && npm run test:bare",
    "test:node": "brittle-node test.js",
    "test:bare": "brittle-bare test.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/holepunchto/index-encoder.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/index-encoder/issues"
  },
  "homepage": "https://github.com/holepunchto/index-encoder"
}
const b4a = require('b4a')

module.exports = function isOptions (opts) {
  return typeof opts === 'object' && opts && !b4a.isBuffer(opts)
}
{
  "name": "is-options",
  "version": "1.0.2",
  "description": "Easily check if input is an options map",
  "main": "index.js",
  "dependencies": {
    "b4a": "^1.1.1"
  },
  "devDependencies": {
    "standard": "^11.0.1",
    "tape": "^4.9.0"
  },
  "scripts": {
    "test": "standard && tape test.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/mafintosh/is-options.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/mafintosh/is-options/issues"
  },
  "homepage": "https://github.com/mafintosh/is-options"
}
const { EventEmitter } = require('events')

module.exports = class RoutingTable extends EventEmitter {
  constructor (id, opts) {
    if (!opts) opts = {}

    super()

    this.id = id
    this.k = opts.k || 20
    this.size = 0
    this.rows = new Array(id.length * 8)
  }

  add (node) {
    const i = this._diff(node.id)

    let row = this.rows[i]

    if (!row) {
      row = this.rows[i] = new Row(this, i)
      this.emit('row', row)
    }

    const len = row.nodes.length
    if (!row.add(node, this.k)) return false

    this.size += row.nodes.length - len
    return true
  }

  remove (id) {
    const i = this._diff(id)
    const row = this.rows[i]
    if (!row) return false
    if (!row.remove(id)) return false
    this.size--
    return true
  }

  get (id) {
    const i = this._diff(id)
    const row = this.rows[i]
    if (!row) return null
    return row.get(id)
  }

  has (id) {
    return this.get(id) !== null
  }

  random () {
    let n = (Math.random() * this.size) | 0

    for (let i = 0; i < this.rows.length; i++) {
      const r = this.rows[i]
      if (!r) continue
      if (n < r.nodes.length) return r.nodes[n]
      n -= r.nodes.length
    }

    return null
  }

  closest (id, k) {
    if (!k) k = this.k

    const result = []
    const d = this._diff(id)

    // push close nodes
    for (let i = d; i >= 0 && result.length < k; i--) this._pushNodes(i, k, result)

    // if we don't have enough close nodes, populate from other rows, re the paper
    for (let i = d + 1; i < this.rows.length && result.length < k; i++) this._pushNodes(i, k, result)

    return result
  }

  _pushNodes (i, k, result) {
    const row = this.rows[i]
    if (!row) return

    const missing = Math.min(k - result.length, row.nodes.length)
    for (let j = 0; j < missing; j++) result.push(row.nodes[j])
  }

  toArray () {
    return this.closest(this.id, Infinity)
  }

  _diff (id) {
    for (let i = 0; i < id.length; i++) {
      const a = id[i]
      const b = this.id[i]

      if (a !== b) return i * 8 + Math.clz32(a ^ b) - 24
    }

    return this.rows.length - 1
  }
}

class Row extends EventEmitter {
  constructor (table, index) {
    super()

    this.data = null // can be used be upstream for whatevs
    this.byteOffset = index >> 3
    this.index = index
    this.table = table
    this.nodes = []
  }

  add (node) {
    const id = node.id

    let l = 0
    let r = this.nodes.length - 1

    while (l <= r) {
      const m = (l + r) >> 1
      const c = this.compare(id, this.nodes[m].id)

      if (c === 0) {
        this.nodes[m] = node
        return true
      }

      if (c < 0) r = m - 1
      else l = m + 1
    }

    if (this.nodes.length >= this.table.k) {
      this.emit('full', node)
      return false
    }

    this.insert(l, node)
    return true
  }

  remove (id) {
    let l = 0
    let r = this.nodes.length - 1

    while (l <= r) {
      const m = (l + r) >> 1
      const c = this.compare(id, this.nodes[m].id)

      if (c === 0) {
        this.splice(m)
        return true
      }

      if (c < 0) r = m - 1
      else l = m + 1
    }

    return false
  }

  get (id) {
    let l = 0
    let r = this.nodes.length - 1

    while (l <= r) {
      const m = (l + r) >> 1
      const node = this.nodes[m]
      const c = this.compare(id, node.id)

      if (c === 0) return node
      if (c < 0) r = m - 1
      else l = m + 1
    }

    return null
  }

  insert (i, node) {
    this.nodes.push(node) // push node or null or whatevs, just trying to not be polymorphic
    for (let j = this.nodes.length - 1; j > i; j--) this.nodes[j] = this.nodes[j - 1]
    this.nodes[i] = node
    this.emit('add', node)
  }

  splice (i) {
    for (; i < this.nodes.length - 1; i++) this.nodes[i] = this.nodes[i + 1]
    this.emit('remove', this.nodes.pop())
  }

  // very likely they diverge after a couple of bytes so a simple impl, like this is prop fastest vs Buffer.compare
  compare (a, b) {
    for (let i = this.byteOffset; i < a.length; i++) {
      const ai = a[i]
      const bi = b[i]
      if (ai === bi) continue
      return ai < bi ? -1 : 1
    }
    return 0
  }
}
{
  "name": "kademlia-routing-table",
  "version": "1.0.6",
  "description": "XOR based routing table used for P2P networks such as a Kademlia DHT.",
  "main": "index.js",
  "files": [
    "index.js"
  ],
  "imports": {
    "events": {
      "bare": "bare-events",
      "default": "events"
    }
  },
  "dependencies": {
    "bare-events": "^2.2.0"
  },
  "devDependencies": {
    "brittle": "^3.3.2",
    "standard": "^17.1.0"
  },
  "scripts": {
    "test": "standard && brittle test.js"
  },
  "keywords": [
    "kademlia",
    "p2p",
    "k-bucket",
    "k-buckets",
    "xor",
    "routing",
    "distributed",
    "systems"
  ],
  "repository": {
    "type": "git",
    "url": "https://github.com/mafintosh/kademlia-routing-table.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/mafintosh/kademlia-routing-table/issues"
  },
  "homepage": "https://github.com/mafintosh/kademlia-routing-table"
}
var queueTick = require('queue-tick')

var mutexify = function () {
  var queue = []
  var used = null

  var call = function () {
    used(release)
  }

  var acquire = function (fn) {
    if (used) return queue.push(fn)
    used = fn
    acquire.locked = true
    queueTick(call)
    return 0
  }

  acquire.locked = false

  var release = function (fn, err, value) {
    used = null
    acquire.locked = false
    if (queue.length) acquire(queue.shift())
    if (fn) fn(err, value)
  }

  return acquire
}

module.exports = mutexify
{
  "name": "mutexify",
  "version": "1.4.0",
  "description": "mutex lock for javascript",
  "main": "index.js",
  "dependencies": {
    "queue-tick": "^1.0.0"
  },
  "devDependencies": {
    "standard": "^14.3.3",
    "tape": "^3.0.2"
  },
  "scripts": {
    "test": "tape test.js",
    "posttest": "npm run lint",
    "lint": "standard"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/mafintosh/mutexify.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/mafintosh/mutexify/issues"
  },
  "keywords": [
    "mutex",
    "lock"
  ],
  "homepage": "https://github.com/mafintosh/mutexify"
}
var mutexify = require('.')

var mutexifyPromise = function () {
  var lock = mutexify()

  var acquire = function () {
    return new Promise(lock)
  }

  Object.defineProperty(acquire, 'locked', {
    get: function () { return lock.locked },
    enumerable: true
  })

  return acquire
}

module.exports = mutexifyPromise
module.exports = assert

class AssertionError extends Error {}
AssertionError.prototype.name = 'AssertionError'

/**
 * Minimal assert function
 * @param  {any} t Value to check if falsy
 * @param  {string=} m Optional assertion error message
 * @throws {AssertionError}
 */
function assert (t, m) {
  if (!t) {
    var err = new AssertionError(m)
    if (Error.captureStackTrace) Error.captureStackTrace(err, assert)
    throw err
  }
}
{
  "name": "nanoassert",
  "version": "2.0.0",
  "description": "Nanoscale assertion module",
  "main": "index.js",
  "dependencies": {},
  "devDependencies": {
    "tape": "^4.9.1"
  },
  "scripts": {
    "test": "tape test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/emilbayes/nanoassert.git"
  },
  "keywords": [
    "assert",
    "unassert",
    "power-assert",
    "tiny",
    "nano",
    "pico"
  ],
  "author": "Emil Bay <github@tixz.dk>",
  "license": "ISC",
  "bugs": {
    "url": "https://github.com/emilbayes/nanoassert/issues"
  },
  "homepage": "https://github.com/emilbayes/nanoassert#readme"
}
module.exports = class NatSampler {
  constructor () {
    this.host = null
    this.port = 0
    this.size = 0

    this._a = null
    this._b = null
    this._threshold = 0
    this._top = 0
    this._samples = []
  }

  add (host, port) {
    const a = this._bump(host, port, 2)
    const b = this._bump(host, 0, 1)

    if (this._samples.length < 32) {
      this.size++
      this._threshold = this.size - (this.size < 4 ? 0 : this.size < 8 ? 1 : this.size < 12 ? 2 : 3)
      this._samples.push(a, b)
      this._top += 2
    } else {
      if (this._top === 32) this._top = 0

      const oa = this._samples[this._top]
      this._samples[this._top++] = a
      oa.hits--

      const ob = this._samples[this._top]
      this._samples[this._top++] = b
      ob.hits--
    }

    if (this._a === null || this._a.hits < a.hits) this._a = a
    if (this._b === null || this._b.hits < b.hits) this._b = b

    if (this._a.hits >= this._threshold) {
      this.host = this._a.host
      this.port = this._a.port
    } else if (this._b.hits >= this._threshold) {
      this.host = this._b.host
      this.port = 0
    } else {
      this.host = null
      this.port = 0
    }

    return a.hits
  }

  _bump (host, port, inc) {
    for (let i = 0; i < 4; i++) {
      const j = (this._top - inc - (2 * i)) & 31
      if (j >= this._samples.length) return { host, port, hits: 1 }
      const s = this._samples[j]
      if (s.port === port && s.host === host) {
        s.hits++
        return s
      }
    }
    return { host, port, hits: 1 }
  }
}
{
  "name": "nat-sampler",
  "version": "1.0.1",
  "description": "Sample addresses to figure out if a host + port is consistent",
  "main": "index.js",
  "dependencies": {},
  "devDependencies": {
    "standard": "^16.0.3",
    "tape": "^5.2.2"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/mafintosh/nat-sampler.git"
  },
  "scripts": {
    "test": "standard && tape test.js"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/mafintosh/nat-sampler/issues"
  },
  "homepage": "https://github.com/mafintosh/nat-sampler"
}
/* eslint-disable camelcase */
const sodium = require('sodium-universal')
const assert = require('nanoassert')
const b4a = require('b4a')

const DHLEN = sodium.crypto_scalarmult_ed25519_BYTES
const PKLEN = sodium.crypto_scalarmult_ed25519_BYTES
const SCALARLEN = sodium.crypto_scalarmult_ed25519_BYTES
const SKLEN = sodium.crypto_sign_SECRETKEYBYTES
const ALG = 'Ed25519'

module.exports = {
  DHLEN,
  PKLEN,
  SCALARLEN,
  SKLEN,
  ALG,
  name: ALG,
  generateKeyPair,
  dh
}

function generateKeyPair (privKey) {
  if (privKey) return generateSeedKeyPair(privKey.subarray(0, 32))

  const keyPair = {}
  keyPair.secretKey = b4a.alloc(SKLEN)
  keyPair.publicKey = b4a.alloc(PKLEN)

  sodium.crypto_sign_keypair(keyPair.publicKey, keyPair.secretKey)
  return keyPair
}

function generateSeedKeyPair (seed) {
  const keyPair = {}
  keyPair.secretKey = b4a.alloc(SKLEN)
  keyPair.publicKey = b4a.alloc(PKLEN)

  sodium.crypto_sign_seed_keypair(keyPair.publicKey, keyPair.secretKey, seed)
  return keyPair
}

function dh (publicKey, { scalar, secretKey }) {
  // tweaked keys expose scalar directly
  if (!scalar) {
    assert(secretKey.byteLength === SKLEN)

    // libsodium stores seed not actual scalar
    const sk = b4a.alloc(64)
    sodium.crypto_hash_sha512(sk, secretKey.subarray(0, 32))
    sk[0] &= 248
    sk[31] &= 127
    sk[31] |= 64

    scalar = sk.subarray(0, 32)
  }

  assert(scalar.byteLength === SCALARLEN)
  assert(publicKey.byteLength === PKLEN)

  const output = b4a.alloc(DHLEN)

  // we clamp if necessary above
  sodium.crypto_scalarmult_ed25519_noclamp(
    output,
    scalar,
    publicKey
  )

  return output
}
{
  "name": "noise-curve-ed",
  "version": "2.1.0",
  "description": "Ed25519 elliptic curve operations for [`noise-handshake`](https://github.com/chm-diederichs/noise-handshake)",
  "main": "index.js",
  "scripts": {
    "test": "npx standard && tape test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/chm-diederichs/noise-curve-ed.git"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "bugs": {
    "url": "https://github.com/chm-diederichs/noise-curve-ed/issues"
  },
  "homepage": "https://github.com/chm-diederichs/noise-curve-ed#readme",
  "dependencies": {
    "b4a": "^1.1.0",
    "nanoassert": "^2.0.0",
    "sodium-universal": "^5.0.0"
  },
  "devDependencies": {
    "noise-handshake": "^3.0.0",
    "standard": "^16.0.3",
    "tape": "^5.2.2"
  }
}
const sodium = require('sodium-universal')
const b4a = require('b4a')

module.exports = class CipherState {
  constructor (key) {
    this.key = key || null
    this.nonce = 0
    this.CIPHER_ALG = 'ChaChaPoly'
  }

  initialiseKey (key) {
    this.key = key
    this.nonce = 0
  }

  setNonce (nonce) {
    this.nonce = nonce
  }

  encrypt (plaintext, ad) {
    if (!this.hasKey) return plaintext
    if (!ad) ad = b4a.alloc(0)

    const ciphertext = encryptWithAD(this.key, this.nonce, ad, plaintext)
    if (ciphertext.length > 65535) throw new Error(`ciphertext length of ${ciphertext.length} exceeds maximum Noise message length of 65535`)
    this.nonce++

    return ciphertext
  }

  decrypt (ciphertext, ad) {
    if (!this.hasKey) return ciphertext
    if (!ad) ad = b4a.alloc(0)
    if (ciphertext.length > 65535) throw new Error(`ciphertext length of ${ciphertext.length} exceeds maximum Noise message length of 65535`)

    const plaintext = decryptWithAD(this.key, this.nonce, ad, ciphertext)
    this.nonce++

    return plaintext
  }

  get hasKey () {
    return this.key !== null
  }

  _clear () {
    sodium.sodium_memzero(this.key)
    this.key = null
    this.nonce = null
  }

  static get MACBYTES () {
    return 16
  }

  static get NONCEBYTES () {
    return 8
  }

  static get KEYBYTES () {
    return 32
  }
}

function encryptWithAD (key, counter, additionalData, plaintext) {
  // for our purposes, additionalData will always be a pubkey so we encode from hex
  if (!b4a.isBuffer(additionalData)) additionalData = b4a.from(additionalData, 'hex')
  if (!b4a.isBuffer(plaintext)) plaintext = b4a.from(plaintext, 'hex')

  const nonce = b4a.alloc(sodium.crypto_aead_chacha20poly1305_ietf_NPUBBYTES)
  const view = new DataView(nonce.buffer, nonce.byteOffset, nonce.byteLength)
  view.setUint32(4, counter, true)

  const ciphertext = b4a.alloc(plaintext.byteLength + sodium.crypto_aead_chacha20poly1305_ietf_ABYTES)

  sodium.crypto_aead_chacha20poly1305_ietf_encrypt(ciphertext, plaintext, additionalData, null, nonce, key)
  return ciphertext
}

function decryptWithAD (key, counter, additionalData, ciphertext) {
  // for our purposes, additionalData will always be a pubkey so we encode from hex
  if (!b4a.isBuffer(additionalData)) additionalData = b4a.from(additionalData, 'hex')
  if (!b4a.isBuffer(ciphertext)) ciphertext = b4a.from(ciphertext, 'hex')

  const nonce = b4a.alloc(sodium.crypto_aead_chacha20poly1305_ietf_NPUBBYTES)
  const view = new DataView(nonce.buffer, nonce.byteOffset, nonce.byteLength)
  view.setUint32(4, counter, true)

  const plaintext = b4a.alloc(ciphertext.byteLength - sodium.crypto_aead_chacha20poly1305_ietf_ABYTES)

  sodium.crypto_aead_chacha20poly1305_ietf_decrypt(plaintext, null, ciphertext, additionalData, nonce, key)
  return plaintext
}
/* eslint-disable camelcase */
const {
  crypto_kx_SEEDBYTES,
  crypto_kx_keypair,
  crypto_kx_seed_keypair,
  crypto_scalarmult_BYTES,
  crypto_scalarmult_SCALARBYTES,
  crypto_scalarmult,
  crypto_scalarmult_base
} = require('sodium-universal')

const assert = require('nanoassert')
const b4a = require('b4a')

const DHLEN = crypto_scalarmult_BYTES
const PKLEN = crypto_scalarmult_BYTES
const SKLEN = crypto_scalarmult_SCALARBYTES
const SEEDLEN = crypto_kx_SEEDBYTES
const ALG = '25519'

module.exports = {
  DHLEN,
  PKLEN,
  SKLEN,
  SEEDLEN,
  ALG,
  generateKeyPair,
  generateSeedKeyPair,
  dh
}

function generateKeyPair (privKey) {
  const keyPair = {}

  keyPair.secretKey = privKey || b4a.alloc(SKLEN)
  keyPair.publicKey = b4a.alloc(PKLEN)

  if (privKey) {
    crypto_scalarmult_base(keyPair.publicKey, keyPair.secretKey)
  } else {
    crypto_kx_keypair(keyPair.publicKey, keyPair.secretKey)
  }

  return keyPair
}

function generateSeedKeyPair (seed) {
  assert(seed.byteLength === SKLEN)

  const keyPair = {}
  keyPair.secretKey = b4a.alloc(SKLEN)
  keyPair.publicKey = b4a.alloc(PKLEN)

  crypto_kx_seed_keypair(keyPair.publicKey, keyPair.secretKey, seed)
  return keyPair
}

function dh (publicKey, { secretKey }) {
  assert(secretKey.byteLength === SKLEN)
  assert(publicKey.byteLength === PKLEN)

  const output = b4a.alloc(DHLEN)

  crypto_scalarmult(
    output,
    secretKey,
    publicKey
  )

  return output
}
const hmacBlake2b = require('./hmac')
const b4a = require('b4a')

const HASHLEN = 64

module.exports = {
  hkdf,
  HASHLEN
}

// HMAC-based Extract-and-Expand KDF
// https://www.ietf.org/rfc/rfc5869.txt

function hkdf (salt, inputKeyMaterial, info = '', length = 2 * HASHLEN) {
  const pseudoRandomKey = hkdfExtract(salt, inputKeyMaterial)
  return hkdfExpand(pseudoRandomKey, info, length)
}

function hkdfExtract (salt, inputKeyMaterial) {
  const hmac = b4a.alloc(HASHLEN)
  return hmacDigest(hmac, salt, inputKeyMaterial)
}

function hkdfExpand (key, info, length) {
  // Put in dedicated slab to avoid keeping shared slab from being gc'ed
  const buffer = b4a.allocUnsafeSlow(length)

  const infoBuf = b4a.from(info)
  let prev = infoBuf

  const result = []
  for (let i = 0; i < length; i += HASHLEN) {
    const pos = b4a.from([(i / HASHLEN) + 1])

    const out = buffer.subarray(i, i + HASHLEN)
    result.push(out)

    prev = hmacDigest(out, key, [prev, infoBuf, pos])
  }

  return result
}

function hmacDigest (out, key, input) {
  hmacBlake2b(out, input, key)
  return out
}
/* eslint-disable camelcase */
const b4a = require('b4a')
const { sodium_memzero, crypto_generichash, crypto_generichash_batch } = require('sodium-universal')

const HASHLEN = 64
const BLOCKLEN = 128
const scratch = b4a.alloc(BLOCKLEN * 3)
const HMACKey = scratch.subarray(BLOCKLEN * 0, BLOCKLEN * 1)
const OuterKeyPad = scratch.subarray(BLOCKLEN * 1, BLOCKLEN * 2)
const InnerKeyPad = scratch.subarray(BLOCKLEN * 2, BLOCKLEN * 3)

// Post-fill is done in the cases where someone caught an exception that
// happened before we were able to clear data at the end

module.exports = function hmac (out, batch, key) {
  if (key.byteLength > BLOCKLEN) {
    crypto_generichash(HMACKey.subarray(0, HASHLEN), key)
    sodium_memzero(HMACKey.subarray(HASHLEN))
  } else {
    // Covers key <= BLOCKLEN
    HMACKey.set(key)
    sodium_memzero(HMACKey.subarray(key.byteLength))
  }

  for (let i = 0; i < HMACKey.byteLength; i++) {
    OuterKeyPad[i] = 0x5c ^ HMACKey[i]
    InnerKeyPad[i] = 0x36 ^ HMACKey[i]
  }
  sodium_memzero(HMACKey)

  crypto_generichash_batch(out, [InnerKeyPad].concat(batch))
  sodium_memzero(InnerKeyPad)
  crypto_generichash_batch(out, [OuterKeyPad, out])
  sodium_memzero(OuterKeyPad)
}

module.exports.BYTES = HASHLEN
module.exports.KEYBYTES = BLOCKLEN
const assert = require('nanoassert')
const b4a = require('b4a')

const SymmetricState = require('./symmetric-state')
const { HASHLEN } = require('./hkdf')

const PRESHARE_IS = Symbol('initiator static key preshared')
const PRESHARE_RS = Symbol('responder static key preshared')

const TOK_PSK = Symbol('psk')

const TOK_S = Symbol('s')
const TOK_E = Symbol('e')

const TOK_ES = Symbol('es')
const TOK_SE = Symbol('se')
const TOK_EE = Symbol('ee')
const TOK_SS = Symbol('ss')

const HANDSHAKES = Object.freeze({
  NN: [
    [TOK_E],
    [TOK_E, TOK_EE]
  ],
  NNpsk0: [
    [TOK_PSK, TOK_E],
    [TOK_E, TOK_EE]
  ],
  XX: [
    [TOK_E],
    [TOK_E, TOK_EE, TOK_S, TOK_ES],
    [TOK_S, TOK_SE]
  ],
  XXpsk0: [
    [TOK_PSK, TOK_E],
    [TOK_E, TOK_EE, TOK_S, TOK_ES],
    [TOK_S, TOK_SE]
  ],
  IK: [
    PRESHARE_RS,
    [TOK_E, TOK_ES, TOK_S, TOK_SS],
    [TOK_E, TOK_EE, TOK_SE]
  ],
  XK: [
    PRESHARE_RS,
    [TOK_E, TOK_ES],
    [TOK_E, TOK_EE],
    [TOK_S, TOK_SE]
  ]
})

class Writer {
  constructor () {
    this.size = 0
    this.buffers = []
  }

  push (b) {
    this.size += b.byteLength
    this.buffers.push(b)
  }

  end () {
    const all = b4a.alloc(this.size)
    let offset = 0
    for (const b of this.buffers) {
      all.set(b, offset)
      offset += b.byteLength
    }
    return all
  }
}

class Reader {
  constructor (buf) {
    this.offset = 0
    this.buffer = buf
  }

  shift (n) {
    const start = this.offset
    const end = this.offset += n
    if (end > this.buffer.byteLength) throw new Error('Insufficient bytes')
    return this.buffer.subarray(start, end)
  }

  end () {
    return this.shift(this.buffer.byteLength - this.offset)
  }
}

module.exports = class NoiseState extends SymmetricState {
  constructor (pattern, initiator, staticKeypair, opts = {}) {
    super(opts)

    this.s = staticKeypair || this.curve.generateKeyPair()
    this.e = null

    this.psk = null
    if (opts && opts.psk) this.psk = opts.psk

    this.re = null
    this.rs = null

    this.pattern = pattern
    this.handshake = HANDSHAKES[this.pattern].slice()

    this.isPskHandshake = !!this.psk && hasPskToken(this.handshake)

    this.protocol = b4a.from([
      'Noise',
      this.pattern,
      this.DH_ALG,
      this.CIPHER_ALG,
      'BLAKE2b'
    ].join('_'))

    this.initiator = initiator
    this.complete = false

    this.rx = null
    this.tx = null
    this.hash = null
  }

  initialise (prologue, remoteStatic) {
    if (this.protocol.byteLength <= HASHLEN) this.digest.set(this.protocol)
    else this.mixHash(this.protocol)

    this.chainingKey = b4a.from(this.digest)

    this.mixHash(prologue)

    while (!Array.isArray(this.handshake[0])) {
      const message = this.handshake.shift()

      // handshake steps should be as arrays, only
      // preshare tokens are provided otherwise
      assert(message === PRESHARE_RS || message === PRESHARE_IS,
        'Unexpected pattern')

      const takeRemoteKey = this.initiator
        ? message === PRESHARE_RS
        : message === PRESHARE_IS

      if (takeRemoteKey) this.rs = remoteStatic

      const key = takeRemoteKey ? this.rs : this.s.publicKey
      assert(key != null, 'Remote pubkey required')

      this.mixHash(key)
    }
  }

  final () {
    const [k1, k2] = this.split()

    this.tx = this.initiator ? k1 : k2
    this.rx = this.initiator ? k2 : k1

    this.complete = true
    this.hash = this.getHandshakeHash()

    this._clear()
  }

  recv (buf) {
    const r = new Reader(buf)

    for (const pattern of this.handshake.shift()) {
      switch (pattern) {
        case TOK_PSK :
          this.mixKeyAndHash(this.psk)
          break

        case TOK_E :
          this.re = r.shift(this.curve.PKLEN)
          this.mixHash(this.re)
          if (this.isPskHandshake) this.mixKeyNormal(this.re)
          break

        case TOK_S : {
          const klen = this.hasKey ? this.curve.PKLEN + 16 : this.curve.PKLEN
          this.rs = this.decryptAndHash(r.shift(klen))
          break
        }

        case TOK_EE :
        case TOK_ES :
        case TOK_SE :
        case TOK_SS : {
          const useStatic = keyPattern(pattern, this.initiator)

          const localKey = useStatic.local ? this.s : this.e
          const remoteKey = useStatic.remote ? this.rs : this.re

          this.mixKey(remoteKey, localKey)
          break
        }

        default :
          throw new Error('Unexpected message')
      }
    }

    const payload = this.decryptAndHash(r.end())

    if (!this.handshake.length) this.final()
    return payload
  }

  send (payload = b4a.alloc(0)) {
    const w = new Writer()

    for (const pattern of this.handshake.shift()) {
      switch (pattern) {
        case TOK_PSK :
          this.mixKeyAndHash(this.psk)
          break

        case TOK_E :
          if (this.e === null) this.e = this.curve.generateKeyPair()
          this.mixHash(this.e.publicKey)
          if (this.isPskHandshake) this.mixKeyNormal(this.e.publicKey)
          w.push(this.e.publicKey)
          break

        case TOK_S :
          w.push(this.encryptAndHash(this.s.publicKey))
          break

        case TOK_ES :
        case TOK_SE :
        case TOK_EE :
        case TOK_SS : {
          const useStatic = keyPattern(pattern, this.initiator)

          const localKey = useStatic.local ? this.s : this.e
          const remoteKey = useStatic.remote ? this.rs : this.re

          this.mixKey(remoteKey, localKey)
          break
        }

        default :
          throw new Error('Unexpected message')
      }
    }

    w.push(this.encryptAndHash(payload))
    const response = w.end()

    if (!this.handshake.length) this.final()
    return response
  }

  _clear () {
    super._clear()

    this.e.secretKey.fill(0)
    this.e.publicKey.fill(0)

    this.re.fill(0)

    this.e = null
    this.re = null
  }
}

function keyPattern (pattern, initiator) {
  const ret = {
    local: false,
    remote: false
  }

  switch (pattern) {
    case TOK_EE:
      return ret

    case TOK_ES:
      ret.local ^= !initiator
      ret.remote ^= initiator
      return ret

    case TOK_SE:
      ret.local ^= initiator
      ret.remote ^= !initiator
      return ret

    case TOK_SS:
      ret.local ^= 1
      ret.remote ^= 1
      return ret
  }
}

function hasPskToken (handshake) {
  return handshake.some(x => {
    return Array.isArray(x) && x.indexOf(TOK_PSK) !== -1
  })
}
{
  "name": "noise-handshake",
  "version": "4.2.0",
  "description": "Noise protocol handshake",
  "main": "noise.js",
  "files": [
    "*.js"
  ],
  "scripts": {
    "test": "standard && brittle test/*.js"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/noise-handshake.git"
  },
  "dependencies": {
    "b4a": "^1.1.0",
    "nanoassert": "^2.0.0",
    "sodium-universal": "^5.0.0"
  },
  "devDependencies": {
    "brittle": "^3.3.2",
    "noise-protocol": "chm-diederichs/noise-protocol.git#xx-ephemeral-key",
    "standard": "^16.0.3"
  }
}
const sodium = require('sodium-universal')
const assert = require('nanoassert')
const b4a = require('b4a')
const CipherState = require('./cipher')
const curve = require('./dh')
const { HASHLEN, hkdf } = require('./hkdf')

module.exports = class SymmetricState extends CipherState {
  constructor (opts = {}) {
    super()

    this.curve = opts.curve || curve
    this.digest = b4a.alloc(HASHLEN)
    this.chainingKey = null
    this.offset = 0

    this.DH_ALG = this.curve.ALG
  }

  mixHash (data) {
    accumulateDigest(this.digest, data)
  }

  mixKeyAndHash (key) {
    const [ck, tempH, tempK] = hkdf(this.chainingKey, key, '', 3 * HASHLEN)
    this.chainingKey = ck
    this.mixHash(tempH)
    this.initialiseKey(tempK.subarray(0, 32))
  }

  mixKeyNormal (key) {
    const [ck, tempK] = hkdf(this.chainingKey, key)
    this.chainingKey = ck
    this.initialiseKey(tempK.subarray(0, 32))
  }

  mixKey (remoteKey, localKey) {
    const dh = this.curve.dh(remoteKey, localKey)
    const hkdfResult = hkdf(this.chainingKey, dh)
    this.chainingKey = hkdfResult[0]
    this.initialiseKey(hkdfResult[1].subarray(0, 32))
  }

  encryptAndHash (plaintext) {
    const ciphertext = this.encrypt(plaintext, this.digest)
    accumulateDigest(this.digest, ciphertext)
    return ciphertext
  }

  decryptAndHash (ciphertext) {
    const plaintext = this.decrypt(ciphertext, this.digest)
    accumulateDigest(this.digest, ciphertext)
    return plaintext
  }

  getHandshakeHash (out) {
    if (!out) return this.getHandshakeHash(b4a.alloc(HASHLEN))
    assert(out.byteLength === HASHLEN, `output must be ${HASHLEN} bytes`)

    out.set(this.digest)
    return out
  }

  split () {
    const res = hkdf(this.chainingKey, b4a.alloc(0))
    return res.map(k => k.subarray(0, 32))
  }

  _clear () {
    super._clear()

    sodium.sodium_memzero(this.digest)
    sodium.sodium_memzero(this.chainingKey)

    this.digest = null
    this.chainingKey = null
    this.offset = null

    this.curve = null
  }

  static get alg () {
    return CipherState.alg + '_BLAKE2b'
  }
}

function accumulateDigest (digest, input) {
  const toHash = b4a.concat([digest, input])
  sodium.crypto_generichash(digest, toHash)
}
var varint = require('varint')
var svarint = require('signed-varint')
var b4a = require('b4a')

exports.make = encoder

exports.name = function (enc) {
  var keys = Object.keys(exports)
  for (var i = 0; i < keys.length; i++) {
    if (exports[keys[i]] === enc) return keys[i]
  }
  return null
}

exports.skip = function (type, buffer, offset) {
  switch (type) {
    case 0:
      varint.decode(buffer, offset)
      return offset + varint.decode.bytes

    case 1:
      return offset + 8

    case 2:
      var len = varint.decode(buffer, offset)
      return offset + varint.decode.bytes + len

    case 3:
    case 4:
      throw new Error('Groups are not supported')

    case 5:
      return offset + 4
  }

  throw new Error('Unknown wire type: ' + type)
}

exports.bytes = encoder(2,
  function encode (val, buffer, offset) {
    var oldOffset = offset
    var len = bufferLength(val)

    varint.encode(len, buffer, offset)
    offset += varint.encode.bytes

    if (b4a.isBuffer(val)) b4a.copy(val, buffer, offset)
    else b4a.write(buffer, val, offset, len)
    offset += len

    encode.bytes = offset - oldOffset
    return buffer
  },
  function decode (buffer, offset) {
    var oldOffset = offset

    var len = varint.decode(buffer, offset)
    offset += varint.decode.bytes

    var val = buffer.subarray(offset, offset + len)
    offset += val.length

    decode.bytes = offset - oldOffset
    return val
  },
  function encodingLength (val) {
    var len = bufferLength(val)
    return varint.encodingLength(len) + len
  }
)

exports.string = encoder(2,
  function encode (val, buffer, offset) {
    var oldOffset = offset
    var len = b4a.byteLength(val)

    varint.encode(len, buffer, offset, 'utf-8')
    offset += varint.encode.bytes

    b4a.write(buffer, val, offset, len)
    offset += len

    encode.bytes = offset - oldOffset
    return buffer
  },
  function decode (buffer, offset) {
    var oldOffset = offset

    var len = varint.decode(buffer, offset)
    offset += varint.decode.bytes

    var val = b4a.toString(buffer, 'utf-8', offset, offset + len)
    offset += len

    decode.bytes = offset - oldOffset
    return val
  },
  function encodingLength (val) {
    var len = b4a.byteLength(val)
    return varint.encodingLength(len) + len
  }
)

exports.bool = encoder(0,
  function encode (val, buffer, offset) {
    buffer[offset] = val ? 1 : 0
    encode.bytes = 1
    return buffer
  },
  function decode (buffer, offset) {
    var bool = buffer[offset] > 0
    decode.bytes = 1
    return bool
  },
  function encodingLength () {
    return 1
  }
)

exports.int32 = encoder(0,
  function encode (val, buffer, offset) {
    varint.encode(val < 0 ? val + 4294967296 : val, buffer, offset)
    encode.bytes = varint.encode.bytes
    return buffer
  },
  function decode (buffer, offset) {
    var val = varint.decode(buffer, offset)
    decode.bytes = varint.decode.bytes
    return val > 2147483647 ? val - 4294967296 : val
  },
  function encodingLength (val) {
    return varint.encodingLength(val < 0 ? val + 4294967296 : val)
  }
)

exports.int64 = encoder(0,
  function encode (val, buffer, offset) {
    if (val < 0) {
      var last = offset + 9
      varint.encode(val * -1, buffer, offset)
      offset += varint.encode.bytes - 1
      buffer[offset] = buffer[offset] | 0x80
      while (offset < last - 1) {
        offset++
        buffer[offset] = 0xff
      }
      buffer[last] = 0x01
      encode.bytes = 10
    } else {
      varint.encode(val, buffer, offset)
      encode.bytes = varint.encode.bytes
    }
    return buffer
  },
  function decode (buffer, offset) {
    var val = varint.decode(buffer, offset)
    if (val >= Math.pow(2, 63)) {
      var limit = 9
      while (buffer[offset + limit - 1] === 0xff) limit--
      limit = limit || 9
      var subset = b4a.allocUnsafe(limit)
      b4a.copy(buffer, subset, 0, offset, offset + limit)
      subset[limit - 1] = subset[limit - 1] & 0x7f
      val = -1 * varint.decode(subset, 0)
      decode.bytes = 10
    } else {
      decode.bytes = varint.decode.bytes
    }
    return val
  },
  function encodingLength (val) {
    return val < 0 ? 10 : varint.encodingLength(val)
  }
)

exports.sint32 =
exports.sint64 = encoder(0,
  svarint.encode,
  svarint.decode,
  svarint.encodingLength
)

exports.uint32 =
exports.uint64 =
exports.enum =
exports.varint = encoder(0,
  varint.encode,
  varint.decode,
  varint.encodingLength
)

// we cannot represent these in javascript so we just use buffers
exports.fixed64 =
exports.sfixed64 = encoder(1,
  function encode (val, buffer, offset) {
    b4a.copy(val, buffer, offset)
    encode.bytes = 8
    return buffer
  },
  function decode (buffer, offset) {
    var val = buffer.subarray(offset, offset + 8)
    decode.bytes = 8
    return val
  },
  function encodingLength () {
    return 8
  }
)

exports.double = encoder(1,
  function encode (val, buffer, offset) {
    b4a.writeDoubleLE(buffer, val, offset)
    encode.bytes = 8
    return buffer
  },
  function decode (buffer, offset) {
    var val = b4a.readDoubleLE(buffer, offset)
    decode.bytes = 8
    return val
  },
  function encodingLength () {
    return 8
  }
)

exports.fixed32 = encoder(5,
  function encode (val, buffer, offset) {
    b4a.writeUInt32LE(buffer, val, offset)
    encode.bytes = 4
    return buffer
  },
  function decode (buffer, offset) {
    var val = b4a.readUInt32LE(buffer, offset)
    decode.bytes = 4
    return val
  },
  function encodingLength () {
    return 4
  }
)

exports.sfixed32 = encoder(5,
  function encode (val, buffer, offset) {
    b4a.writeInt32LE(buffer, val, offset)
    encode.bytes = 4
    return buffer
  },
  function decode (buffer, offset) {
    var val = b4a.readInt32LE(buffer, offset)
    decode.bytes = 4
    return val
  },
  function encodingLength () {
    return 4
  }
)

exports.float = encoder(5,
  function encode (val, buffer, offset) {
    b4a.writeFloatLE(buffer, val, offset)
    encode.bytes = 4
    return buffer
  },
  function decode (buffer, offset) {
    var val = b4a.readFloatLE(buffer, offset)
    decode.bytes = 4
    return val
  },
  function encodingLength () {
    return 4
  }
)

function encoder (type, encode, decode, encodingLength) {
  encode.bytes = decode.bytes = 0

  return {
    type: type,
    encode: encode,
    decode: decode,
    encodingLength: encodingLength
  }
}

function bufferLength (val) {
  return b4a.isBuffer(val) ? val.length : b4a.byteLength(val)
}
{
  "name": "protocol-buffers-encodings",
  "version": "1.2.0",
  "description": "Base encodings for protocol-buffers",
  "main": "index.js",
  "dependencies": {
    "b4a": "^1.6.0",
    "signed-varint": "^2.0.1",
    "varint": "5.0.0"
  },
  "devDependencies": {
    "standard": "^14.3.4",
    "tape": "^5.0.1"
  },
  "scripts": {
    "test": "standard && tape test.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/mafintosh/protocol-buffers-encodings.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/mafintosh/protocol-buffers-encodings/issues"
  },
  "homepage": "https://github.com/mafintosh/protocol-buffers-encodings"
}
const crypto = require('hypercore-crypto')
const Protomux = require('protomux')
const b4a = require('b4a')
const schema = require('./spec/hyperschema')

const [NS_INITATOR, NS_RESPONDER] = crypto.namespace('wakeup', 2)

const Handshake = schema.getEncoding('@wakeup/handshake')
const Announce = schema.getEncoding('@wakeup/announce')
const Lookup = schema.getEncoding('@wakeup/lookup')
const Info = schema.getEncoding('@wakeup/info')

module.exports = class WakeupSwarm {
  constructor(onwakeup = noop, { gcTickTime = 2000 } = {}) {
    this.gcTickTime = gcTickTime

    this.topics = new Map()
    this.topicsGC = new Set()
    this.muxers = new Set()
    this.stats = {
      sessionsOpened: 0,
      sessionsClosed: 0,
      topicsAdded: 0,
      topicsGcd: 0,
      peersAdded: 0,
      peersRemoved: 0,
      wireAnnounce: { rx: 0, tx: 0 },
      wireLookup: { rx: 0, tx: 0 },
      wireInfo: { rx: 0, tx: 0 }
    }

    this.onwakeup = onwakeup

    this._gcInterval = null
    this._gcBound = this._gc.bind(this)
  }

  session(capability, handlers = {}) {
    const id = handlers.discoveryKey || crypto.discoveryKey(capability)
    const active = handlers.active !== false
    const hex = b4a.toString(id, 'hex')

    let w = this.topics.get(hex)

    if (w) return w.addSession(handlers)

    w = new WakeupTopic(this, id, capability, active)

    this.topics.set(hex, w)

    for (const muxer of this.muxers) {
      w._onopen(muxer, true)
    }

    return w.addSession(handlers)
  }

  getSessions(capability, handlers = {}) {
    const id = handlers.discoveryKey || crypto.discoveryKey(capability)
    const hex = b4a.toString(id, 'hex')
    const w = this.topics.get(hex)
    return w ? w.sessions : []
  }

  hasStream(stream, capability, handlers = {}) {
    if (!capability) {
      const noiseStream = stream.noiseStream || stream
      return this.muxers.has(getMuxer(noiseStream))
    }

    const id = handlers.discoveryKey || crypto.discoveryKey(capability)
    const hex = b4a.toString(id, 'hex')
    const t = this.topics.get(hex)

    return t ? t.peersByStream.has(stream) : false
  }

  addStream(stream) {
    const noiseStream = stream.noiseStream || stream

    if (!noiseStream.connected) {
      noiseStream.once('open', this.addStream.bind(this, noiseStream))
      return
    }

    const muxer = getMuxer(noiseStream)
    muxer.pair({ protocol: 'wakeup' }, (id) => this._onpair(id, muxer))

    this.muxers.add(muxer)
    noiseStream.on('close', () => this.muxers.delete(muxer))

    for (const w of this.topics.values()) {
      if (!w.isActive) continue
      w._onopen(muxer, true)
    }
  }

  _onActive(w) {
    for (const m of this.muxers) {
      w._onopen(m, false)
    }
  }

  _addGC(topic) {
    if (topic.destroyed) return
    this.topicsGC.add(topic)
    if (this._gcInterval === null) {
      this._gcInterval = setInterval(this._gcBound, this.gcTickTime)
    }
  }

  _removeGC(topic) {
    this.topicsGC.delete(topic)
    if (this.topicsGC.size === 0 && this._gcInterval) {
      clearInterval(this._gcInterval)
      this._gcInterval = null
    }
  }

  _gc() {
    const destroy = []
    for (const w of this.topicsGC) {
      w.idleTicks++
      if (w.idleTicks >= 5) destroy.push(w)
    }
    for (const w of destroy) w.teardown()
  }

  destroy() {
    if (this._gcInterval) clearInterval(this._gcInterval)
    this._gcInterval = null

    for (const w of this.topics.values()) w.teardown()
  }

  async _onpair(id, stream) {
    const hex = b4a.toString(id, 'hex')
    const w = this.topics.get(hex)
    if (!w || !w.sessions.length) return this.onwakeup(id, stream)
    w._onopen(getMuxer(stream), false)
  }

  registerMetrics(promClient) {
    const self = this
    new promClient.Gauge({
      // eslint-disable-line no-new
      name: 'protomux_wakeup_sessions_opened',
      help: 'The amount of sessions opened by protomux wakeup',
      collect() {
        this.set(self.stats.sessionsOpened)
      }
    })

    new promClient.Gauge({
      // eslint-disable-line no-new
      name: 'protomux_wakeup_sessions_closed',
      help: 'The amount of sessions closed by protomux wakeup',
      collect() {
        this.set(self.stats.sessionsClosed)
      }
    })

    new promClient.Gauge({
      // eslint-disable-line no-new
      name: 'protomux_wakeup_topics_added',
      help: 'The amount of topics added to protomux wakeup',
      collect() {
        this.set(self.stats.topicsAdded)
      }
    })

    new promClient.Gauge({
      // eslint-disable-line no-new
      name: 'protomux_wakeup_topics_gcd',
      help: 'The amount of topics that got garbage collected by protomux wakeup',
      collect() {
        this.set(self.stats.topicsGcd)
      }
    })

    new promClient.Gauge({
      // eslint-disable-line no-new
      name: 'protomux_wakeup_peers_added',
      help: 'The amount of peers added by protomux wakeup, across all topics',
      collect() {
        this.set(self.stats.peersAdded)
      }
    })

    new promClient.Gauge({
      // eslint-disable-line no-new
      name: 'protomux_wakeup_peers_removed',
      help: 'The amount of peers removed by protomux wakeup, across all topics',
      collect() {
        this.set(self.stats.peersRemoved)
      }
    })

    new promClient.Gauge({
      // eslint-disable-line no-new
      name: 'protomux_wakeup_wire_announce_rx',
      help: 'The amount of wire announce messages received by protomux wakeup',
      collect() {
        this.set(self.stats.wireAnnounce.rx)
      }
    })
    new promClient.Gauge({
      // eslint-disable-line no-new
      name: 'protomux_wakeup_wire_announce_tx',
      help: 'The amount of wire announce messages transmitted by protomux wakeup',
      collect() {
        this.set(self.stats.wireAnnounce.tx)
      }
    })
    new promClient.Gauge({
      // eslint-disable-line no-new
      name: 'protomux_wakeup_wire_lookup_rx',
      help: 'The amount of wire lookup messages received by protomux wakeup',
      collect() {
        this.set(self.stats.wireLookup.rx)
      }
    })
    new promClient.Gauge({
      // eslint-disable-line no-new
      name: 'protomux_wakeup_wire_lookup_tx',
      help: 'The amount of wire lookup messages transmitted by protomux wakeup',
      collect() {
        this.set(self.stats.wireLookup.tx)
      }
    })
    new promClient.Gauge({
      // eslint-disable-line no-new
      name: 'protomux_wakeup_wire_info_rx',
      help: 'The amount of wire info messages received by protomux wakeup',
      collect() {
        this.set(self.stats.wireInfo.rx)
      }
    })
    new promClient.Gauge({
      // eslint-disable-line no-new
      name: 'protomux_wakeup_wire_info_tx',
      help: 'The amount of wire info messages transmitted by protomux wakeup',
      collect() {
        this.set(self.stats.wireInfo.rx)
      }
    })
  }
}

class WakeupPeer {
  constructor(topic) {
    this.index = 0
    this.userData = null // for the user
    this.clock = 0 // for the user, v useful to reduce traffic
    this.pending = true
    this.removed = false
    this.topic = topic
    this.channel = null
    this.stream = null
    this.wireLookup = null
    this.wireAnnounce = null
    this.wireInfo = null
    this.active = false
  }

  unlink(list) {
    // note that since we pop here we can iterate in reverse safely in case a peer is removed in the same tick
    const head = list.pop()
    if (head === this) return
    head.index = this.index
    list[head.index] = head
  }
}

class WakeupSession {
  constructor(topic, handlers) {
    this.index = 0
    this.topic = topic
    this.handlers = handlers
    this.isActive = handlers.active !== false
    this.destroyed = false
  }

  get peers() {
    return this.topic.peers
  }

  hasStream(stream) {
    return !!this.getPeer(stream)
  }

  addStream(stream) {
    this.topic.addStream(stream)
  }

  getPeer(stream) {
    return this.topic.peersByStream.get(stream) || null
  }

  broadcastLookup(req) {
    for (const peer of this.topic.pendingPeers) {
      this.lookup(peer, req)
    }
    for (const peer of this.topic.peers) {
      this.lookup(peer, req)
    }
  }

  lookupByStream(stream, req) {
    const peer = this.topic.peersByStream.get(stream)
    if (peer) this.lookup(peer, req)
  }

  lookup(peer, req) {
    peer.topic.state.stats.wireLookup.tx++
    peer.wireLookup.send(req || { hash: null })
  }

  announceByStream(stream, wakeup) {
    const peer = this.topic.peersByStream.get(stream)
    if (peer && !peer.pending) this.announce(peer, wakeup)
  }

  announce(peer, wakeup) {
    peer.topic.state.stats.wireAnnounce.tx++
    peer.wireAnnounce.send(wakeup)
  }

  active() {
    this.isActive = true
    this.topic._bumpActivity()
  }

  inactive() {
    this.isActive = false
    this.topic._bumpActivity()
  }

  destroy() {
    if (this.destroyed) return
    this.destroyed = true
    this.topic.removeSession(this)
  }
}

class WakeupTopic {
  constructor(state, id, capability, active) {
    this.state = state
    this.sessions = []
    this.id = id
    this.capability = capability
    this.peers = []
    this.pendingPeers = []
    this.peersByStream = new Map()
    this.activePeers = 0
    this.isActive = active
    this.idleTicks = 0
    this.gcing = false
    this.destroyed = false

    this.state.stats.topicsAdded++
  }

  addSession(handlers) {
    this.state.stats.sessionsOpened++
    const session = new WakeupSession(this, handlers)
    session.index = this.sessions.length
    this.sessions.push(session)
    this._bumpActivity()
    this._checkGC()
    return session
  }

  removeSession(session) {
    if (this.sessions.length <= session.index) return
    if (this.sessions[session.index] !== session) return

    this.state.stats.sessionsClosed++

    // same as with the peer, this allows us to iterate while removing if iterating backwards
    const head = this.sessions.pop()
    if (head !== session) {
      head.index = session.index
      this.sessions[head.index] = head
    }

    this._bumpActivity()
    this._checkGC()
  }

  _bumpActivity() {
    let isActive = false

    for (let i = this.sessions.length - 1; i >= 0; i--) {
      if (this.sessions[i].isActive) {
        isActive = true
        break
      }
    }

    if (isActive) this.active()
    else this.inactive()
  }

  active() {
    if (this.isActive) return
    this.idleTicks = 0
    this.isActive = true
    this._updateActive(true)
  }

  inactive() {
    if (!this.isActive) return
    this.isActive = false
    this._updateActive(false)
  }

  _updateActive(active) {
    const info = { active }

    for (const peer of this.pendingPeers) {
      peer.topic.state.stats.wireInfo.tx++
      peer.wireInfo.send(info)
    }
    for (const peer of this.peers) {
      peer.topic.state.stats.wireInfo.tx++
      peer.wireInfo.send(info)
    }

    this._checkGC()

    if (active) this.state._onActive(this)
  }

  teardown() {
    if (this.destroyed) return
    this.destroyed = true

    this.state.stats.topicsGcd++

    for (let i = this.peers.length - 1; i >= 0; i--) {
      this.peers[i].channel.close()
    }

    for (let i = this.pendingPeers.length - 1; i >= 0; i--) {
      this.pendingPeers[i].channel.close()
    }

    const hex = b4a.toString(this.id, 'hex')

    this.gcing = false
    this.state.topics.delete(hex)
    this.state._removeGC(this)

    for (let i = this.sessions.length - 1; i >= 0; i--) {
      this.state.stats.sessionsClosed++
      const session = this.sessions[i]
      if (session.handlers.ondestroy) session.handlers.ondestroy(session)
    }
  }

  addStream(stream) {
    this._onopen(getMuxer(stream), false)
  }

  _proveCapabilityTo(stream) {
    return this._makeCapability(stream.isInitiator, stream.handshakeHash)
  }

  _makeCapability(isInitiator, handshakeHash) {
    return crypto.hash([isInitiator ? NS_INITATOR : NS_RESPONDER, this.capability, handshakeHash])
  }

  _addPeer(peer, open) {
    if (
      !b4a.equals(
        open.capability,
        this._makeCapability(!peer.stream.isInitiator, peer.stream.handshakeHash)
      )
    ) {
      peer.channel.close()
      return
    }

    if (peer.pending) {
      peer.unlink(this.pendingPeers)
    }

    peer.active = open.active
    peer.pending = false
    peer.index = this.peers.push(peer) - 1

    if (peer.active) {
      this.activePeers++
      this._checkGC()
    }

    for (let i = this.sessions.length - 1; i >= 0; i--) {
      const session = this.sessions[i]
      const handlers = session.handlers

      if (handlers.onpeeradd) handlers.onpeeradd(peer, session)
      if (peer.active && handlers.onpeeractive) handlers.onpeeractive(peer, session)
    }
  }

  _checkGC() {
    const shouldGC = this.activePeers === 0 && this.sessions.length === 0

    if (shouldGC) {
      if (!this.gcing) {
        this.gcing = true
        this.state._addGC(this)
      }
    } else {
      if (this.gcing) {
        this.gcing = false
        this.state._removeGC(this)
      }
    }
  }

  _removePeer(peer) {
    this.state.stats.peersRemoved++
    peer.removed = true
    this.peersByStream.delete(peer.stream)

    if (peer.pending) {
      peer.unlink(this.pendingPeers)
      return
    }

    const active = peer.active

    if (active) {
      peer.active = false
      this.activePeers--
      this._checkGC()
    }

    peer.unlink(this.peers)

    for (let i = this.sessions.length - 1; i >= 0; i--) {
      const session = this.sessions[i]
      const handlers = session.handlers

      if (active && handlers.onpeerinactive) handlers.onpeerinactive(peer, session)
      if (handlers.onpeerremove) handlers.onpeerremove(peer, session)
    }
  }

  _onannounce(wakeup, peer) {
    for (let i = this.sessions.length - 1; i >= 0; i--) {
      const session = this.sessions[i]
      const handlers = session.handlers

      if (handlers.onannounce) handlers.onannounce(wakeup, peer, session)
    }
  }

  _onlookup(req, peer) {
    for (let i = this.sessions.length - 1; i >= 0; i--) {
      const session = this.sessions[i]
      const handlers = session.handlers

      if (handlers.onlookup) handlers.onlookup(req, peer, session)
    }
  }

  _oninfo(info, peer) {
    if (info.active) {
      if (!peer.active) {
        peer.active = true
        this.activePeers++
        this._checkGC()

        for (let i = this.sessions.length - 1; i >= 0; i--) {
          const session = this.sessions[i]
          const handlers = session.handlers

          if (handlers.onpeeractive) handlers.onpeeractive(peer, session)
        }
      }
    } else {
      if (peer.active) {
        peer.active = false
        this.activePeers--
        this._checkGC()

        for (let i = this.sessions.length - 1; i >= 0; i--) {
          const session = this.sessions[i]
          const handlers = session.handlers

          if (handlers.onpeerinactive) handlers.onpeerinactive(peer, session)
        }
      }
    }
  }

  _onopen(muxer, unique) {
    if (!unique && this.peersByStream.has(muxer.stream)) return

    const peer = new WakeupPeer(this)
    const ch = muxer.createChannel({
      userData: peer,
      protocol: 'wakeup',
      id: this.id,
      handshake: Handshake,
      messages: [
        { encoding: Lookup, onmessage: onlookup },
        { encoding: Announce, onmessage: onannounce },
        { encoding: Info, onmessage: onchannelinfo }
      ],
      onopen: onchannelopen,
      onclose: onchannelclose
    })

    if (!ch) return

    this.state.stats.peersAdded++

    peer.channel = ch
    peer.stream = muxer.stream

    peer.wireLookup = ch.messages[0]
    peer.wireAnnounce = ch.messages[1]
    peer.wireInfo = ch.messages[2]

    peer.index = this.pendingPeers.push(peer) - 1
    this.peersByStream.set(muxer.stream, peer)

    ch.open({
      version: 0,
      capability: this._proveCapabilityTo(muxer.stream),
      active: this.isActive
    })
  }
}

function onchannelopen(open, channel) {
  const peer = channel.userData
  peer.topic._addPeer(peer, open)
}

function onchannelclose(close, channel) {
  const peer = channel.userData
  peer.topic._removePeer(peer)
}

function onlookup(req, channel) {
  const peer = channel.userData
  peer.topic.state.stats.wireLookup.rx++
  peer.topic._onlookup(req, peer)
}

function onannounce(wakeup, channel) {
  const peer = channel.userData
  peer.topic.state.stats.wireAnnounce.rx++
  peer.topic._onannounce(wakeup, peer)
}

function onchannelinfo(info, channel) {
  const peer = channel.userData
  peer.topic.state.stats.wireInfo.rx++
  peer.topic._oninfo(info, peer)
}

function getMuxer(stream) {
  if (Protomux.isProtomux(stream)) return stream
  if (stream.noiseStream.userData) return stream.noiseStream.userData
  const mux = Protomux.from(stream.noiseStream)
  stream.noiseStream.userData = mux
  return mux
}

function noop() {}
{
  "name": "protomux-wakeup",
  "version": "2.9.0",
  "description": "Wakeup protocol over protomux",
  "main": "index.js",
  "dependencies": {
    "b4a": "^1.6.7",
    "hypercore-crypto": "^3.5.0",
    "hyperschema": "^1.10.4",
    "protomux": "^3.10.1"
  },
  "devDependencies": {
    "@hyperswarm/secret-stream": "^6.8.1",
    "brittle": "^3.17.1",
    "lunte": "^1.0.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^2.0.0",
    "prom-client": "^15.1.3"
  },
  "files": [
    "index.js",
    "spec/hyperschema/*.js"
  ],
  "scripts": {
    "format": "prettier --write .",
    "test": "prettier --check . && lunte && brittle test/*.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/holepunchto/protomux-wakeup.git"
  },
  "author": "Holepunch Inc.",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/protomux-wakeup/issues"
  },
  "homepage": "https://github.com/holepunchto/protomux-wakeup"
}
// This file is autogenerated by the hyperschema compiler
// Schema Version: 1
/* eslint-disable camelcase */
/* eslint-disable quotes */

const VERSION = 1
const { c } = require('hyperschema/runtime')

// eslint-disable-next-line no-unused-vars
let version = VERSION

// @wakeup/info
const encoding0 = {
  preencode(state, m) {
    state.end++ // max flag is 1 so always one byte
  },
  encode(state, m) {
    const flags = m.active ? 1 : 0

    c.uint.encode(state, flags)
  },
  decode(state) {
    const flags = c.uint.decode(state)

    return {
      active: (flags & 1) !== 0
    }
  }
}

// @wakeup/handshake
const encoding1 = {
  preencode(state, m) {
    c.uint.preencode(state, m.version)
    c.fixed32.preencode(state, m.capability)
    state.end++ // max flag is 1 so always one byte
  },
  encode(state, m) {
    const flags = m.active ? 1 : 0

    c.uint.encode(state, m.version)
    c.fixed32.encode(state, m.capability)
    c.uint.encode(state, flags)
  },
  decode(state) {
    const r0 = c.uint.decode(state)
    const r1 = c.fixed32.decode(state)
    const flags = c.uint.decode(state)

    return {
      version: r0,
      capability: r1,
      active: (flags & 1) !== 0
    }
  }
}

// @wakeup/writer
const encoding2 = {
  preencode(state, m) {
    c.fixed32.preencode(state, m.key)
    c.uint.preencode(state, m.length)
  },
  encode(state, m) {
    c.fixed32.encode(state, m.key)
    c.uint.encode(state, m.length)
  },
  decode(state) {
    const r0 = c.fixed32.decode(state)
    const r1 = c.uint.decode(state)

    return {
      key: r0,
      length: r1
    }
  }
}

// @wakeup/announce
const encoding3 = c.array(encoding2)

// @wakeup/lookup
const encoding4 = {
  preencode(state, m) {
    state.end++ // max flag is 1 so always one byte

    if (m.hash) c.fixed32.preencode(state, m.hash)
  },
  encode(state, m) {
    const flags = m.hash ? 1 : 0

    c.uint.encode(state, flags)

    if (m.hash) c.fixed32.encode(state, m.hash)
  },
  decode(state) {
    const flags = c.uint.decode(state)

    return {
      hash: (flags & 1) !== 0 ? c.fixed32.decode(state) : null
    }
  }
}

function setVersion(v) {
  version = v
}

function encode(name, value, v = VERSION) {
  version = v
  return c.encode(getEncoding(name), value)
}

function decode(name, buffer, v = VERSION) {
  version = v
  return c.decode(getEncoding(name), buffer)
}

function getEnum(name) {
  switch (name) {
    default:
      throw new Error('Enum not found ' + name)
  }
}

function getEncoding(name) {
  switch (name) {
    case '@wakeup/info':
      return encoding0
    case '@wakeup/handshake':
      return encoding1
    case '@wakeup/writer':
      return encoding2
    case '@wakeup/announce':
      return encoding3
    case '@wakeup/lookup':
      return encoding4
    default:
      throw new Error('Encoder not found ' + name)
  }
}

function getStruct(name, v = VERSION) {
  const enc = getEncoding(name)
  return {
    preencode(state, m) {
      version = v
      enc.preencode(state, m)
    },
    encode(state, m) {
      version = v
      enc.encode(state, m)
    },
    decode(state) {
      version = v
      return enc.decode(state)
    }
  }
}

const resolveStruct = getStruct // compat

module.exports = {
  resolveStruct,
  getStruct,
  getEnum,
  getEncoding,
  encode,
  decode,
  setVersion,
  version
}
const b4a = require('b4a')
const c = require('compact-encoding')
const queueTick = require('queue-tick')
const safetyCatch = require('safety-catch')
const unslab = require('unslab')

const MAX_BUFFERED = 32768
const MAX_BACKLOG = Infinity // TODO: impl "open" backpressure
const MAX_BATCH = 8 * 1024 * 1024

class Channel {
  constructor(
    mux,
    info,
    userData,
    protocol,
    aliases,
    id,
    handshake,
    messages,
    onopen,
    onclose,
    ondestroy,
    ondrain
  ) {
    this.userData = userData
    this.protocol = protocol
    this.aliases = aliases
    this.id = id
    this.handshake = null
    this.messages = []

    this.opened = false
    this.closed = false
    this.destroyed = false

    this.onopen = onopen
    this.onclose = onclose
    this.ondestroy = ondestroy
    this.ondrain = ondrain

    this._handshake = handshake
    this._mux = mux
    this._info = info
    this._localId = 0
    this._remoteId = 0
    this._active = 0
    this._extensions = null

    this._decBound = this._dec.bind(this)
    this._decAndDestroyBound = this._decAndDestroy.bind(this)

    this._openedPromise = null
    this._openedResolve = null

    this._destroyedPromise = null
    this._destroyedResolve = null

    for (const m of messages) this.addMessage(m)
  }

  get drained() {
    return this._mux.drained
  }

  fullyOpened() {
    if (this.opened) return Promise.resolve(true)
    if (this.closed) return Promise.resolve(false)
    if (this._openedPromise) return this._openedPromise

    this._openedPromise = new Promise((resolve) => {
      this._openedResolve = resolve
    })
    return this._openedPromise
  }

  fullyClosed() {
    if (this.destroyed) return Promise.resolve()
    if (this._destroyedPromise) return this._destroyedPromise

    this._destroyedPromise = new Promise((resolve) => {
      this._destroyedResolve = resolve
    })
    return this._destroyedPromise
  }

  open(handshake) {
    const id = this._mux._free.length > 0 ? this._mux._free.pop() : this._mux._local.push(null) - 1

    this._info.opened++
    this._info.lastChannel = this
    this._localId = id + 1
    this._mux._local[id] = this

    if (this._remoteId === 0) {
      this._info.outgoing.push(this._localId)
    }

    const state = { buffer: null, start: 2, end: 2 }

    c.uint.preencode(state, this._localId)
    c.string.preencode(state, this.protocol)
    c.optionalBuffer.preencode(state, this.id)
    if (this._handshake) this._handshake.preencode(state, handshake)

    state.buffer = this._mux._alloc(state.end)

    state.buffer[0] = 0
    state.buffer[1] = 1
    c.uint.encode(state, this._localId)
    c.string.encode(state, this.protocol)
    c.optionalBuffer.encode(state, this.id)
    if (this._handshake) this._handshake.encode(state, handshake)

    this._mux._write0(state.buffer)
  }

  _dec() {
    if (--this._active === 0 && this.closed === true) this._destroy()
  }

  _decAndDestroy(err) {
    this._dec()
    this._mux._safeDestroy(err)
  }

  _fullyOpenSoon() {
    this._mux._remote[this._remoteId - 1].session = this
    queueTick(this._fullyOpenOrDestroy.bind(this))
  }

  _fullyOpenOrDestroy() {
    try {
      this._fullyOpen()
    } catch (err) {
      this._mux._safeDestroyBound(err)
    }
  }

  _fullyOpen() {
    if (this.opened === true || this.closed === true) return

    const remote = this._mux._remote[this._remoteId - 1]

    this.handshake = this._handshake ? this._handshake.decode(remote.state) : null
    this._track(this.onopen(this.handshake, this))

    remote.session = this
    remote.state = null
    if (remote.pending !== null) this._drain(remote)
    if (this._mux._destroying === true) return

    this.opened = true
    this._resolveOpen(true)
  }

  _resolveOpen(opened) {
    if (this._openedResolve !== null) {
      this._openedResolve(opened)
      this._openedResolve = this._openedPromise = null
    }
  }

  _resolveDestroyed() {
    if (this._destroyedResolve !== null) {
      this._destroyedResolve()
      this._destroyedResolve = this._destroyedPromise = null
    }
  }

  _drain(remote) {
    for (let i = 0; i < remote.pending.length; i++) {
      const p = remote.pending[i]
      this._mux._buffered -= byteSize(p.state)
      this._recv(p.type, p.state)
      if (this._mux._destroying === true) return
    }

    remote.pending = null
    this._mux._resumeMaybe()
  }

  _track(p) {
    if (isPromise(p) === true) {
      this._active++
      return p.then(this._decBound, this._decAndDestroyBound)
    }

    return null
  }

  _close(isRemote) {
    if (this.closed === true) return
    this.closed = true

    this._info.opened--
    if (this._info.lastChannel === this) this._info.lastChannel = null

    if (this._remoteId > 0) {
      this._mux._remote[this._remoteId - 1] = null
      this._remoteId = 0
      // If remote has acked, we can reuse the local id now
      // otherwise, we need to wait for the "ack" to arrive
      this._mux._free.push(this._localId - 1)
    }

    this._mux._local[this._localId - 1] = null
    this._localId = 0

    this._mux._gc(this._info)
    this._track(this.onclose(isRemote, this))

    if (this._active === 0) this._destroy()

    this._resolveOpen(false)
  }

  _destroy() {
    if (this.destroyed === true) return
    this.destroyed = true
    this._track(this.ondestroy(this))
    this._resolveDestroyed()
  }

  _recv(type, state) {
    if (type < this.messages.length) {
      const m = this.messages[type]
      const p = m.recv(state, this)
      if (m.autoBatch === true) return p
    }
    return null
  }

  cork() {
    this._mux.cork()
  }

  uncork() {
    this._mux.uncork()
  }

  close() {
    if (this.closed === true) return

    const state = { buffer: null, start: 2, end: 2 }

    c.uint.preencode(state, this._localId)

    state.buffer = this._mux._alloc(state.end)

    state.buffer[0] = 0
    state.buffer[1] = 3
    c.uint.encode(state, this._localId)

    this._close(false)
    this._mux._write0(state.buffer)
  }

  addMessage(opts) {
    if (!opts) return this._skipMessage()

    const type = this.messages.length
    const autoBatch = opts.autoBatch !== false
    const encoding = opts.encoding || c.raw
    const onmessage = opts.onmessage || noop

    const s = this
    const typeLen = encodingLength(c.uint, type)

    const m = {
      type,
      autoBatch,
      encoding,
      onmessage,
      recv(state, session) {
        return session._track(m.onmessage(encoding.decode(state), session))
      },
      send(m, session = s) {
        if (session.closed === true) return false

        const mux = session._mux
        const state = { buffer: null, start: 0, end: typeLen }

        if (mux._batch !== null) {
          encoding.preencode(state, m)
          state.buffer = mux._alloc(state.end)

          c.uint.encode(state, type)
          encoding.encode(state, m)

          mux._pushBatch(session._localId, state.buffer)
          return true
        }

        c.uint.preencode(state, session._localId)
        encoding.preencode(state, m)

        state.buffer = mux._alloc(state.end)

        c.uint.encode(state, session._localId)
        c.uint.encode(state, type)
        encoding.encode(state, m)

        mux.drained = mux.stream.write(state.buffer)

        return mux.drained
      }
    }

    this.messages.push(m)

    return m
  }

  _skipMessage() {
    const type = this.messages.length
    const m = {
      type,
      encoding: c.raw,
      onmessage: noop,
      recv(state, session) {},
      send(m, session) {}
    }

    this.messages.push(m)
    return m
  }
}

module.exports = class Protomux {
  constructor(stream, { alloc } = {}) {
    if (stream.userData === null) stream.userData = this

    this.isProtomux = true
    this.stream = stream
    this.corked = 0
    this.drained = true

    this._alloc =
      alloc || (typeof stream.alloc === 'function' ? stream.alloc.bind(stream) : b4a.allocUnsafe)
    this._safeDestroyBound = this._safeDestroy.bind(this)
    this._uncorkBound = this.uncork.bind(this)

    this._remoteBacklog = 0
    this._buffered = 0
    this._paused = false
    this._remote = []
    this._local = []
    this._free = []
    this._batch = null
    this._batchState = null

    this._infos = new Map()
    this._notify = new Map()
    // stream.destroyed flips asynchronously on streamx-based transports.
    this._destroying = false

    this.stream.on('data', this._ondata.bind(this))
    this.stream.on('drain', this._ondrain.bind(this))
    this.stream.on('end', this._onend.bind(this))
    this.stream.on('error', noop) // we handle this in "close"
    this.stream.on('close', this._shutdown.bind(this))
  }

  static from(stream, opts) {
    if (stream.userData && stream.userData.isProtomux) return stream.userData
    if (stream.isProtomux) return stream
    return new this(stream, opts)
  }

  static isProtomux(mux) {
    return typeof mux === 'object' && mux.isProtomux === true
  }

  *[Symbol.iterator]() {
    for (const session of this._local) {
      if (session !== null) yield session
    }
  }

  isIdle() {
    return this._local.length === this._free.length
  }

  cork() {
    if (++this.corked === 1) {
      this._batch = []
      this._batchState = { buffer: null, start: 0, end: 1 }
    }
  }

  uncork() {
    if (--this.corked === 0) {
      this._sendBatch(this._batch, this._batchState)
      this._batch = null
      this._batchState = null
    }
  }

  getLastChannel({ protocol, id = null }) {
    const key = toKey(protocol, id)
    const info = this._infos.get(key)
    if (info) return info.lastChannel
    return null
  }

  pair({ protocol, id = null }, notify) {
    this._notify.set(toKey(protocol, id), notify)
  }

  unpair({ protocol, id = null }) {
    this._notify.delete(toKey(protocol, id))
  }

  opened({ protocol, id = null }) {
    const key = toKey(protocol, id)
    const info = this._infos.get(key)
    return info ? info.opened > 0 : false
  }

  createChannel({
    userData = null,
    protocol,
    aliases = [],
    id = null,
    unique = true,
    handshake = null,
    messages = [],
    onopen = noop,
    onclose = noop,
    ondestroy = noop,
    ondrain = noop
  }) {
    if (this.stream.destroyed) return null

    const info = this._get(protocol, id, aliases)
    if (unique && info.opened > 0) return null

    if (info.incoming.length === 0) {
      return new Channel(
        this,
        info,
        userData,
        protocol,
        aliases,
        id,
        handshake,
        messages,
        onopen,
        onclose,
        ondestroy,
        ondrain
      )
    }

    this._remoteBacklog--

    const remoteId = info.incoming.shift()
    const r = this._remote[remoteId - 1]
    if (r === null) return null

    const session = new Channel(
      this,
      info,
      userData,
      protocol,
      aliases,
      id,
      handshake,
      messages,
      onopen,
      onclose,
      ondestroy,
      ondrain
    )

    session._remoteId = remoteId
    session._fullyOpenSoon()

    return session
  }

  _pushBatch(localId, buffer) {
    if (this._batchState.end >= MAX_BATCH) {
      this._sendBatch(this._batch, this._batchState)
      this._batch = []
      this._batchState = { buffer: null, start: 0, end: 1 }
    }

    if (this._batch.length === 0 || this._batch[this._batch.length - 1].localId !== localId) {
      this._batchState.end++
      c.uint.preencode(this._batchState, localId)
    }
    c.buffer.preencode(this._batchState, buffer)
    this._batch.push({ localId, buffer })
  }

  _sendBatch(batch, state) {
    if (batch.length === 0) return

    let prev = batch[0].localId

    state.buffer = this._alloc(state.end)
    state.buffer[state.start++] = 0
    state.buffer[state.start++] = 0

    c.uint.encode(state, prev)

    for (let i = 0; i < batch.length; i++) {
      const b = batch[i]
      if (prev !== b.localId) {
        state.buffer[state.start++] = 0
        c.uint.encode(state, (prev = b.localId))
      }
      c.buffer.encode(state, b.buffer)
    }

    this.drained = this.stream.write(state.buffer)
  }

  _get(protocol, id, aliases = []) {
    const key = toKey(protocol, id)

    let info = this._infos.get(key)
    if (info) return info

    info = {
      key,
      protocol,
      aliases: [],
      id,
      pairing: 0,
      opened: 0,
      incoming: [],
      outgoing: [],
      lastChannel: null
    }
    this._infos.set(key, info)

    for (const alias of aliases) {
      const key = toKey(alias, id)
      info.aliases.push(key)

      this._infos.set(key, info)
    }

    return info
  }

  _gc(info) {
    if (info.opened === 0 && info.outgoing.length === 0 && info.incoming.length === 0) {
      this._infos.delete(info.key)

      for (const alias of info.aliases) this._infos.delete(alias)
    }
  }

  _ondata(buffer) {
    if (buffer.byteLength === 0) return // ignore empty frames...
    try {
      const state = { buffer, start: 0, end: buffer.byteLength }
      this._decode(c.uint.decode(state), state)
    } catch (err) {
      this._safeDestroy(err)
    }
  }

  _ondrain() {
    this.drained = true

    for (const s of this._local) {
      if (s !== null) s._track(s.ondrain(s))
    }
  }

  _onend() {
    // TODO: support half open mode for the users who wants that here
    this.stream.end()
  }

  _decode(remoteId, state) {
    const type = c.uint.decode(state)

    if (remoteId === 0) {
      return this._oncontrolsession(type, state)
    }

    const r = remoteId <= this._remote.length ? this._remote[remoteId - 1] : null

    // if the channel is closed ignore - could just be a pipeline message...
    if (r === null) return null

    if (r.pending !== null) {
      this._bufferMessage(r, type, state)
      return null
    }

    return r.session._recv(type, state)
  }

  _oncontrolsession(type, state) {
    switch (type) {
      case 0:
        this._onbatch(state)
        break

      case 1:
        // return the promise back up as this has sideeffects so we can batch reply
        return this._onopensession(state)

      case 2:
        this._onrejectsession(state)
        break

      case 3:
        this._onclosesession(state)
        break
    }

    return null
  }

  _bufferMessage(r, type, { buffer, start, end }) {
    const state = { buffer, start, end } // copy
    r.pending.push({ type, state })
    this._buffered += byteSize(state)
    this._pauseMaybe()
  }

  _pauseMaybe() {
    if (this._paused === true || this._buffered <= MAX_BUFFERED) return
    this._paused = true
    this.stream.pause()
  }

  _resumeMaybe() {
    if (this._paused === false || this._buffered > MAX_BUFFERED) return
    this._paused = false
    this.stream.resume()
  }

  _onbatch(state) {
    const end = state.end
    let remoteId = c.uint.decode(state)

    let waiting = null

    while (state.end > state.start) {
      const len = c.uint.decode(state)
      if (len === 0) {
        remoteId = c.uint.decode(state)
        continue
      }
      state.end = state.start + len
      // if batch contains more than one message, cork it so we reply back with a batch
      if (end !== state.end && waiting === null) {
        waiting = []
        this.cork()
      }
      const p = this._decode(remoteId, state)
      if (waiting !== null && p !== null) waiting.push(p)
      state.start = state.end
      state.end = end
    }

    if (waiting !== null) {
      // the waiting promises are not allowed to throw but we destroy the stream in case we are wrong
      Promise.all(waiting).then(this._uncorkBound, this._safeDestroyBound)
    }
  }

  _onopensession(state) {
    const remoteId = c.uint.decode(state)
    const protocol = c.string.decode(state)
    const id = unslab(c.optionalBuffer.decode(state))

    // remote tried to open the control session - auto reject for now
    // as we can use as an explicit control protocol declaration if we need to
    if (remoteId === 0) {
      this._rejectSession(0)
      return null
    }

    const rid = remoteId - 1
    const info = this._get(protocol, id)

    // allow the remote to grow the ids by one
    if (this._remote.length === rid) {
      this._remote.push(null)
    }

    if (rid >= this._remote.length || this._remote[rid] !== null) {
      throw new Error('Invalid open message')
    }

    if (info.outgoing.length > 0) {
      const localId = info.outgoing.shift()
      const session = this._local[localId - 1]

      if (session === null) {
        // we already closed the channel - ignore
        this._free.push(localId - 1)
        return null
      }

      this._remote[rid] = { state, pending: null, session: null }

      session._remoteId = remoteId
      session._fullyOpen()
      return null
    }

    const copyState = { buffer: state.buffer, start: state.start, end: state.end }
    this._remote[rid] = { state: copyState, pending: [], session: null }

    if (++this._remoteBacklog > MAX_BACKLOG) {
      throw new Error('Remote exceeded backlog')
    }

    info.pairing++
    info.incoming.push(remoteId)

    return this._requestSession(protocol, id, info).catch(this._safeDestroyBound)
  }

  _onrejectsession(state) {
    const localId = c.uint.decode(state)

    // TODO: can be done smarter...
    for (const info of this._infos.values()) {
      const i = info.outgoing.indexOf(localId)
      if (i === -1) continue

      info.outgoing.splice(i, 1)

      const session = this._local[localId - 1]

      this._free.push(localId - 1)
      if (session !== null) session._close(true)

      this._gc(info)
      return
    }

    throw new Error('Invalid reject message')
  }

  _onclosesession(state) {
    const remoteId = c.uint.decode(state)

    if (remoteId === 0) return // ignore

    const rid = remoteId - 1
    const r = rid < this._remote.length ? this._remote[rid] : null

    if (r === null) return

    if (r.session !== null) r.session._close(true)
  }

  async _requestSession(protocol, id, info) {
    const notify = this._notify.get(toKey(protocol, id)) || this._notify.get(toKey(protocol, null))

    if (notify) await notify(id)

    if (--info.pairing > 0) return

    while (info.incoming.length > 0) {
      this._rejectSession(info, info.incoming.shift())
    }

    this._gc(info)
  }

  _rejectSession(info, remoteId) {
    if (remoteId > 0) {
      const r = this._remote[remoteId - 1]

      if (r.pending !== null) {
        for (let i = 0; i < r.pending.length; i++) {
          this._buffered -= byteSize(r.pending[i].state)
        }
      }

      this._remote[remoteId - 1] = null
      this._resumeMaybe()
    }

    const state = { buffer: null, start: 2, end: 2 }

    c.uint.preencode(state, remoteId)

    state.buffer = this._alloc(state.end)

    state.buffer[0] = 0
    state.buffer[1] = 2
    c.uint.encode(state, remoteId)

    this._write0(state.buffer)
  }

  _write0(buffer) {
    if (this._batch !== null) {
      this._pushBatch(0, buffer.subarray(1))
      return
    }

    this.drained = this.stream.write(buffer)
  }

  destroy(err) {
    this._destroying = true
    this.stream.destroy(err)
  }

  _safeDestroy(err) {
    safetyCatch(err)
    this._destroying = true
    this.stream.destroy(err)
  }

  _shutdown() {
    this._destroying = true
    for (const s of this._local) {
      if (s !== null) s._close(true)
    }
  }
}

function noop() {}

function toKey(protocol, id) {
  return protocol + '##' + (id ? b4a.toString(id, 'hex') : '')
}

function byteSize(state) {
  return 512 + (state.end - state.start)
}

function isPromise(p) {
  return !!(p && typeof p.then === 'function')
}

function encodingLength(enc, val) {
  const state = { buffer: null, start: 0, end: 0 }
  enc.preencode(state, val)
  return state.end
}
{
  "name": "protomux",
  "version": "3.11.0",
  "description": "Multiplex multiple message oriented protocols over a stream",
  "main": "index.js",
  "files": [
    "index.js"
  ],
  "dependencies": {
    "b4a": "^1.3.1",
    "compact-encoding": "^3.0.0",
    "queue-tick": "^1.0.0",
    "safety-catch": "^1.0.1",
    "unslab": "^1.3.0"
  },
  "devDependencies": {
    "@hyperswarm/secret-stream": "^6.0.0",
    "brittle": "^3.0.0",
    "lunte": "^1.0.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^2.0.0"
  },
  "scripts": {
    "format": "prettier --write .",
    "lint": "prettier --check . && lunte",
    "test": "npm run test:node && npm run test:bare",
    "test:node": "brittle-node test.js",
    "test:bare": "brittle-bare test.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/holepunchto/protomux.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/holepunchto/protomux/issues"
  },
  "homepage": "https://github.com/holepunchto/protomux"
}
{
  "name": "queue-tick",
  "version": "1.0.1",
  "description": "Next tick shim that prefers process.nextTick over queueMicrotask for compat",
  "main": "./process-next-tick.js",
  "browser": "./queue-microtask.js",
  "dependencies": {},
  "devDependencies": {
    "standard": "^16.0.3",
    "tape": "^5.3.1"
  },
  "scripts": {
    "test": "standard && tape test.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/mafintosh/queue-tick.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/mafintosh/queue-tick/issues"
  },
  "homepage": "https://github.com/mafintosh/queue-tick"
}
module.exports = (typeof process !== 'undefined' && typeof process.nextTick === 'function')
  ? process.nextTick.bind(process)
  : require('./queue-microtask')
module.exports = typeof queueMicrotask === 'function' ? queueMicrotask : (fn) => Promise.resolve().then(fn)
require.addon = require('require-addon')

module.exports = require.addon('.', __filename)
const binding = require('./binding')

exports.get = function get(field, bit) {
  const n = field.byteLength * 8

  if (bit < 0) bit += n
  if (bit < 0 || bit >= n) return false

  return binding.quickbit_napi_get(toBuffer(field), bit) !== 0
}

exports.set = function set(field, bit, value = true) {
  const n = field.byteLength * 8

  if (bit < 0) bit += n
  if (bit < 0 || bit >= n) return false

  return binding.quickbit_napi_set(toBuffer(field), bit, value ? 1 : 0) !== 0
}

exports.fill = function fill(
  field,
  value,
  start = 0,
  end = field.byteLength * 8
) {
  const n = field.byteLength * 8

  if (start < 0) start += n
  if (end < 0) end += n
  if (start < 0 || start >= field.byteLength * 8 || start >= end) return field

  binding.quickbit_napi_fill(toBuffer(field), value ? 1 : 0, start, end)
  return field
}

exports.clear = function clear(field, ...chunks) {
  binding.quickbit_napi_clear(toBuffer(field), chunks.map(toBufferChunk))
}

exports.findFirst = function findFirst(field, value, position = 0) {
  const n = field.byteLength * 8

  if (position < 0) position += n
  if (position < 0) position = 0
  if (position >= n) return -1

  return binding.quickbit_napi_find_first(
    toBuffer(field),
    value ? 1 : 0,
    position
  )
}

exports.findLast = function findLast(
  field,
  value,
  position = field.byteLength * 8 - 1
) {
  const n = field.byteLength * 8

  if (position < 0) position += n
  if (position < 0) return -1
  if (position >= n) position = n - 1

  return binding.quickbit_napi_find_last(
    toBuffer(field),
    value ? 1 : 0,
    position
  )
}

function toBuffer(field) {
  if (field.BYTES_PER_ELEMENT === 1) return field
  return new Uint8Array(field.buffer, field.byteOffset, field.byteLength)
}

function toBufferChunk(chunk) {
  return { field: toBuffer(chunk.field), offset: chunk.offset }
}

class Index {
  static from(fieldOrChunks, byteLength = -1) {
    if (Array.isArray(fieldOrChunks)) {
      return new SparseIndex(fieldOrChunks, byteLength)
    } else {
      return new DenseIndex(fieldOrChunks, byteLength)
    }
  }

  constructor(byteLength) {
    this._byteLength = byteLength
    this.handle = Buffer.allocUnsafe(binding.sizeof_quickbit_index_t)
  }

  get byteLength() {
    return this._byteLength
  }

  skipFirst(value, position = 0) {
    const n = this.byteLength * 8

    if (position < 0) position += n
    if (position < 0) position = 0
    if (position >= n) return n - 1

    return binding.quickbit_napi_skip_first(
      this.handle,
      this.byteLength,
      value ? 1 : 0,
      position
    )
  }

  skipLast(value, position = this.byteLength * 8 - 1) {
    const n = this.byteLength * 8

    if (position < 0) position += n
    if (position < 0) return 0
    if (position >= n) position = n - 1

    return binding.quickbit_napi_skip_last(
      this.handle,
      this.byteLength,
      value ? 1 : 0,
      position
    )
  }
}

exports.Index = Index

class DenseIndex extends Index {
  constructor(field, byteLength) {
    super(byteLength)
    this.field = field

    binding.quickbit_napi_index_init(this.handle, toBuffer(this.field))
  }

  get byteLength() {
    if (this._byteLength !== -1) return this._byteLength
    return this.field.byteLength
  }

  update(bit) {
    const n = this.byteLength * 8

    if (bit < 0) bit += n
    if (bit < 0 || bit >= n) return false

    return (
      binding.quickbit_napi_index_update(
        this.handle,
        toBuffer(this.field),
        bit
      ) !== 0
    )
  }
}

function selectChunk(chunks, offset) {
  for (let i = 0; i < chunks.length; i++) {
    const next = chunks[i]

    const start = next.offset
    const end = next.offset + next.field.byteLength

    if (offset >= start && offset + 16 <= end) {
      return next
    }
  }

  return null
}

class SparseIndex extends Index {
  constructor(chunks, byteLength) {
    super(byteLength)
    this.chunks = chunks

    binding.quickbit_napi_index_init_sparse(
      this.handle,
      this.chunks.map(toBufferChunk)
    )
  }

  get byteLength() {
    if (this._byteLength !== -1) return this._byteLength
    const last = this.chunks[this.chunks.length - 1]
    return last ? last.offset + last.field.byteLength : 0
  }

  update(bit) {
    const n = this.byteLength * 8

    if (bit < 0) bit += n
    if (bit < 0 || bit >= n) return false

    const j = Math.floor(bit / 128)

    const offset = j * 16

    const chunk = selectChunk(this.chunks, offset)

    if (chunk === null) return false

    return (
      binding.quickbit_napi_index_update_sparse(
        this.handle,
        toBuffer(chunk.field),
        chunk.offset,
        bit
      ) !== 0
    )
  }
}
{
  "name": "quickbit-native",
  "version": "2.4.8",
  "description": "libquickbit JavaScript bindings for Node.js",
  "main": "index.js",
  "files": [
    "index.js",
    "macros.h",
    "binding.cc",
    "binding.js",
    "CMakeLists.txt",
    "prebuilds"
  ],
  "addon": true,
  "scripts": {
    "test": "npm run lint && npm run test:bare && npm run test:node",
    "test:bare": "bare test.mjs",
    "test:node": "node test.mjs",
    "lint": "prettier . --check"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/quickbit-native.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/quickbit-native/issues"
  },
  "homepage": "https://github.com/holepunchto/quickbit-native#readme",
  "dependencies": {
    "require-addon": "^1.1.0"
  },
  "devDependencies": {
    "b4a": "^1.6.7",
    "bare-compat-napi": "^1.3.2",
    "brittle": "^3.1.0",
    "cmake-bare": "^1.1.7",
    "cmake-fetch": "^1.1.0",
    "cmake-napi": "^1.0.2",
    "prettier": "^3.5.3",
    "prettier-config-standard": "^7.0.0"
  }
}
const simdle = require('simdle-universal')

const INDEX_LEN = (16 /* root */ + 128 * 16 /* children */) * 2

const get = exports.get = function get (field, bit) {
  const n = field.byteLength * 8

  if (bit < 0) bit += n
  if (bit < 0 || bit >= n) return false

  const m = field.BYTES_PER_ELEMENT * 8

  const offset = bit & (m - 1)
  const i = (bit - offset) / m

  return (field[i] & (1 << offset)) !== 0
}

const set = exports.set = function set (field, bit, value = true) {
  const n = field.byteLength * 8

  if (bit < 0) bit += n
  if (bit < 0 || bit >= n) return false

  const m = field.BYTES_PER_ELEMENT * 8

  const offset = bit & (m - 1)
  const i = (bit - offset) / m
  const mask = 1 << offset

  if (value) {
    if ((field[i] & mask) !== 0) return false
  } else {
    if ((field[i] & mask) === 0) return false
  }

  field[i] ^= mask

  return true
}

exports.fill = function fill (field, value, start = 0, end = field.byteLength * 8) {
  const n = field.byteLength * 8

  if (start < 0) start += n
  if (end < 0) end += n
  if (start < 0 || start >= field.byteLength * 8 || start >= end) return field

  const m = field.BYTES_PER_ELEMENT * 8

  let i, j

  {
    const offset = start & (m - 1)
    i = (start - offset) / m

    if (offset !== 0) {
      let shift = m - offset
      if (end - start < shift) shift = end - start

      const mask = ((1 << shift) - 1) << offset

      if (value) field[i] |= mask
      else field[i] &= ~mask

      i++
    }
  }

  {
    const offset = end & (m - 1)
    j = (end - offset) / m

    if (offset !== 0 && j >= i) {
      const mask = (1 << offset) - 1

      if (value) field[j] |= mask
      else field[j] &= ~mask
    }
  }

  if (i < j) field.fill(value ? (2 ** m) - 1 : 0, i, j)

  return field
}

exports.clear = function clear (field, ...chunks) {
  const n = field.byteLength

  for (const chunk of chunks) {
    if (chunk.offset >= n) continue

    const m = chunk.field.byteLength

    let i = chunk.offset
    let j = 0

    while (((i & 15) !== 0 || (j & 15) !== 0) && i < n && j < m) {
      field[i] = field[i] & ~chunk.field[j]
      i++
      j++
    }

    if (i + 15 < n && j + 15 < m) {
      const len = Math.min(n - (n & 15) - i, m - (m & 15) - j)

      simdle.clear(field.subarray(i, i + len), chunk.field.subarray(j, j + len), field.subarray(i, i + len))
    }

    while (i < n && j < m) {
      field[i] = field[i] & ~chunk.field[j]
      i++
      j++
    }
  }
}

function bitOffset (bit, offset) {
  return !bit ? offset : (INDEX_LEN * 8 / 2) + offset
}

function byteOffset (bit, offset) {
  return !bit ? offset : (INDEX_LEN / 2) + offset
}

exports.findFirst = function findFirst (field, value, position = 0) {
  const n = field.byteLength * 8

  if (position < 0) position += n
  if (position < 0) position = 0
  if (position >= n) return -1

  value = !!value

  for (let i = position; i < n; i++) {
    if (get(field, i) === value) return i
  }

  return -1
}

exports.findLast = function findLast (field, value, position = field.byteLength * 8 - 1) {
  const n = field.byteLength * 8

  if (position < 0) position += n
  if (position < 0) return -1
  if (position >= n) position = n - 1

  value = !!value

  for (let i = position; i >= 0; i--) {
    if (get(field, i) === value) return i
  }

  return -1
}

const Index = exports.Index = class Index {
  static from (fieldOrChunks, byteLength = -1) {
    if (Array.isArray(fieldOrChunks)) {
      return new SparseIndex(fieldOrChunks, byteLength)
    } else {
      return new DenseIndex(fieldOrChunks, byteLength)
    }
  }

  constructor (byteLength) {
    this._byteLength = byteLength
    this.handle = new Uint32Array(INDEX_LEN / 4)
  }

  get byteLength () {
    return this._byteLength
  }

  skipFirst (value, position = 0) {
    const n = this.byteLength * 8

    if (position < 0) position += n
    if (position < 0) position = 0
    if (position >= n) return n - 1

    let i = Math.floor(position / 16384)

    if (i > 127) return position

    while (i <= 127 && get(this.handle, bitOffset(value, i))) {
      i++
    }

    if (i === 128) return n - 1

    let k = i * 16384
    let j = 0

    if (position > k) j = Math.floor((position - k) / 128)

    while (j <= 127 && get(this.handle, bitOffset(value, i * 128 + j + 128))) {
      j++
      k += 128
    }

    if (j === 128 && i !== 127) return this.skipFirst(value, (i + 1) * 16384)

    if (k > position) position = k

    return position < n ? position : n - 1
  }

  skipLast (value, position = this.byteLength * 8 - 1) {
    const n = this.byteLength * 8

    if (position < 0) position += n
    if (position < 0) return 0
    if (position >= n) position = n - 1

    let i = Math.floor(position / 16384)

    if (i > 127) return position

    while (i >= 0 && get(this.handle, bitOffset(value, i))) {
      i--
    }

    if (i === -1) return 0

    let k = ((i + 1) * 16384) - 1
    let j = 127

    if (position < k) j = 128 - Math.ceil((k - position) / 128)

    while (j >= 0 && get(this.handle, bitOffset(value, i * 128 + j + 128))) {
      j--
      k -= 128
    }

    if (j === -1 && i !== 0) return this.skipLast(value, i * 16384 - 1)

    if (k < position) position = k

    return position
  }
}

class DenseIndex extends Index {
  constructor (field, byteLength) {
    super(byteLength)
    this.field = field

    const m = field.BYTES_PER_ELEMENT

    for (let i = 0; i < 128; i++) {
      for (let j = 0; j < 128; j++) {
        const offset = (i * 128 + j) * 16
        let allz = true
        let allo = false

        if (offset + 16 <= this.field.byteLength) {
          const vec = this.field.subarray(offset / m, (offset + 16) / m)

          allz = simdle.allz(vec)
          allo = simdle.allo(vec)
        }

        const k = i * 128 + 128 + j

        set(this.handle, bitOffset(false, k), allz)
        set(this.handle, bitOffset(true, k), allo)
      }

      {
        const offset = byteOffset(false, i * 16 + 16) / 4
        const allo = simdle.allo(this.handle.subarray(offset, offset + 4))

        set(this.handle, bitOffset(false, i), allo)
      }

      {
        const offset = byteOffset(true, i * 16 + 16) / 4
        const allo = simdle.allo(this.handle.subarray(offset, offset + 4))

        set(this.handle, bitOffset(true, i), allo)
      }
    }
  }

  get byteLength () {
    if (this._byteLength !== -1) return this._byteLength
    return this.field.byteLength
  }

  update (bit) {
    const n = this.byteLength * 8

    if (bit < 0) bit += n
    if (bit < 0 || bit >= n) return false

    const m = this.field.BYTES_PER_ELEMENT

    const i = Math.floor(bit / 16384)
    const j = Math.floor(bit / 128)

    const offset = (j * 16) / m
    const vec = this.field.subarray(offset, offset + (16 / m))

    const allz = simdle.allz(vec)
    const allo = simdle.allo(vec)

    let changed = false

    if (set(this.handle, bitOffset(false, 128 + j), allz)) {
      changed = true

      const offset = byteOffset(false, i * 16 + 16) / 4
      const allo = simdle.allo(this.handle.subarray(offset, offset + 4))

      set(this.handle, bitOffset(false, i), allo)
    }

    if (set(this.handle, bitOffset(true, 128 + j), allo)) {
      changed = true

      const offset = byteOffset(true, i * 16 + 16) / 4
      const allo = simdle.allo(this.handle.subarray(offset, offset + 4))

      set(this.handle, bitOffset(true, i), allo)
    }

    return changed
  }
}

function selectChunk (chunks, offset) {
  for (let i = 0; i < chunks.length; i++) {
    const next = chunks[i]

    const start = next.offset
    const end = next.offset + next.field.byteLength

    if (offset >= start && offset + 16 <= end) {
      return next
    }
  }

  return null
}

class SparseIndex extends Index {
  constructor (chunks, byteLength) {
    super(byteLength)
    this.chunks = chunks

    for (let i = 0; i < 128; i++) {
      for (let j = 0; j < 128; j++) {
        const offset = (i * 128 + j) * 16
        let allz = true
        let allo = false

        const chunk = selectChunk(this.chunks, offset)

        if (chunk !== null) {
          const m = chunk.field.BYTES_PER_ELEMENT

          const vec = chunk.field.subarray((offset - chunk.offset) / m, (offset - chunk.offset + 16) / m)

          allz = simdle.allz(vec)
          allo = simdle.allo(vec)
        }

        const k = i * 128 + 128 + j

        set(this.handle, bitOffset(false, k), allz)
        set(this.handle, bitOffset(true, k), allo)
      }

      {
        const offset = byteOffset(false, i * 16 + 16) / 4
        const allo = simdle.allo(this.handle.subarray(offset, offset + 4))

        set(this.handle, bitOffset(false, i), allo)
      }

      {
        const offset = byteOffset(true, i * 16 + 16) / 4
        const allo = simdle.allo(this.handle.subarray(offset, offset + 4))

        set(this.handle, bitOffset(true, i), allo)
      }
    }
  }

  get byteLength () {
    if (this._byteLength !== -1) return this._byteLength
    const last = this.chunks[this.chunks.length - 1]
    return last ? last.offset + last.field.byteLength : 0
  }

  update (bit) {
    const n = this.byteLength * 8

    if (bit < 0) bit += n
    if (bit < 0 || bit >= n) return false

    const i = Math.floor(bit / 16384)
    const j = Math.floor(bit / 128)

    const offset = j * 16

    const chunk = selectChunk(this.chunks, offset)

    if (chunk === null) return false

    const m = chunk.field.BYTES_PER_ELEMENT

    const vec = chunk.field.subarray((offset - chunk.offset) / m, (offset - chunk.offset + 16) / m)

    const allz = simdle.allz(vec)
    const allo = simdle.allo(vec)

    let changed = false

    if (set(this.handle, bitOffset(false, 128 + j), allz)) {
      changed = true

      const offset = byteOffset(false, i * 16 + 16) / 4
      const allo = simdle.allo(this.handle.subarray(offset, offset + 4))

      set(this.handle, bitOffset(false, i), allo)
    }

    if (set(this.handle, bitOffset(true, 128 + j), allo)) {
      changed = true

      const offset = byteOffset(true, i * 16 + 16) / 4
      const allo = simdle.allo(this.handle.subarray(offset, offset + 4))

      set(this.handle, bitOffset(true, i), allo)
    }

    return changed
  }
}
const fallback = require('./fallback')

try {
  const native = require('quickbit-native')

  // These functions are faster in JavaScript
  exports.get = fallback.get
  exports.set = fallback.set
  exports.fill = fallback.fill

  // These functions are faster in C
  exports.clear = native.clear
  exports.findFirst = native.findFirst
  exports.findLast = native.findLast
  exports.Index = native.Index
} catch {
  module.exports = fallback
}
{
  "name": "quickbit-universal",
  "version": "2.2.0",
  "description": "Universal wrapper for libquickbit with a JavaScript fallback",
  "main": "index.js",
  "files": [
    "fallback.js",
    "index.js"
  ],
  "browser": {
    "./index.js": "./fallback.js"
  },
  "scripts": {
    "test": "standard && brittle test.mjs"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/quickbit-universal.git"
  },
  "author": "Kasper Isager Dalsgarð <kasper@funktionel.co>",
  "license": "ISC",
  "bugs": {
    "url": "https://github.com/holepunchto/quickbit-universal/issues"
  },
  "homepage": "https://github.com/holepunchto/quickbit-universal#readme",
  "dependencies": {
    "b4a": "^1.6.0",
    "simdle-universal": "^1.1.0"
  },
  "optionalDependencies": {
    "quickbit-native": "^2.2.0"
  },
  "devDependencies": {
    "brittle": "^3.1.0",
    "standard": "^17.0.0"
  }
}
class CacheEntry {
  constructor (key, index, map) {
    this.key = key
    this.index = index
    this.map = map
  }
}

class CacheValue {
  constructor (entry, value) {
    this.entry = entry
    this.value = value
  }
}

class Rache {
  constructor ({ maxSize = 65536, parent = null } = {}) {
    this.maxSize = parent?.maxSize || maxSize

    this._array = parent?._array || []
    this._map = new Map()
  }

  static from (cache) {
    return cache ? new this({ parent: cache }) : new this()
  }

  get globalSize () {
    return this._array.length
  }

  get size () {
    return this._map.size
  }

  sub () {
    return new Rache({ parent: this })
  }

  set (key, value) { // ~constant time
    const existing = this._map.get(key)
    if (existing !== undefined) {
      existing.value = value
      return
    }

    if (this._array.length >= this.maxSize) this._gc()

    const entry = new CacheEntry(key, this._array.length, this._map)
    this._array.push(entry)
    const cacheValue = new CacheValue(entry, value)
    this._map.set(key, cacheValue)
  }

  delete (key) {
    const existing = this._map.get(key)
    if (existing === undefined) return false

    this._delete(existing.entry.index)
    return true
  }

  get (key) {
    const existing = this._map.get(key)
    return existing === undefined ? undefined : existing.value
  }

  * [Symbol.iterator] () {
    for (const [key, { value }] of this._map) {
      yield [key, value]
    }
  }

  keys () {
    return this._map.keys()
  }

  * values () {
    for (const { value } of this._map.values()) {
      yield value
    }
  }

  clear () {
    // The entries in map linger on in _array,
    // so on top of clearing the map, we also kill the ref,
    // so that any gc running later on the old map won't interfere
    // (in case a new entry was added with the same key as a cleared entry)

    this._map.clear()
    this._map = new Map()
  }

  destroy () {
    this._map = null
    this._array = null
  }

  _gc () {
    this._delete(Math.floor(Math.random() * this._array.length))
  }

  _delete (index) { // ~constant time
    if (index >= this._array.length) throw new Error('Cannot delete unused index (logic bug?)')

    const head = this._array.pop()
    let removed = head

    if (index < this._array.length) {
      removed = this._array[index]
      head.index = index
      this._array[index] = head
    }

    removed.map.delete(removed.key)
  }
}

module.exports = Rache
{
  "name": "rache",
  "version": "1.0.0",
  "description": "Random-eviction cache",
  "main": "index.js",
  "scripts": {
    "test": "standard && brittle test.js --coverage"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/rache.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/rache/issues"
  },
  "homepage": "https://github.com/holepunchto/rache#readme",
  "files": [
    "index.js"
  ],
  "dependencies": {},
  "devDependencies": {
    "brittle": "^3.5.2",
    "standard": "^17.1.0"
  }
}
module.exports = class RandomArrayIterator {
  constructor (values) {
    this.values = values
    this.start = 0
    this.length = this.values.length
  }

  next () {
    if (this.length === 0) {
      if (this.start === 0) return { done: true, value: undefined }
      this.length = this.start
      this.start = 0
    }

    const i = this.start + ((Math.random() * this.length) | 0)
    const j = this.start + --this.length
    const value = this.values[i]

    this.values[i] = this.values[j]
    this.values[j] = value

    return { done: false, value }
  }

  dequeue () {
    this.values[this.start + this.length] = this.values[this.values.length - 1]
    this.values.pop()
  }

  requeue () {
    const i = this.start + this.length
    const value = this.values[i]
    this.values[i] = this.values[this.start]
    this.values[this.start++] = value
  }

  restart () {
    this.start = 0
    this.length = this.values.length
    return this
  }

  [Symbol.iterator] () {
    return this
  }
}
{
  "name": "random-array-iterator",
  "version": "1.0.0",
  "description": "An iterator to iterate an array in random order with controls to requeue or dequeue elements during the iteration",
  "main": "index.js",
  "dependencies": {},
  "devDependencies": {
    "standard": "^16.0.3",
    "tape": "^5.0.1"
  },
  "scripts": {
    "test": "standard && tape test.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/mafintosh/random-array-iterator.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/mafintosh/random-array-iterator/issues"
  },
  "homepage": "https://github.com/mafintosh/random-array-iterator"
}
const EventEmitter = require('events')

module.exports = class ReadyResource extends EventEmitter {
  constructor () {
    super()

    this.opening = null
    this.closing = null

    this.opened = false
    this.closed = false
  }

  ready () {
    if (this.opening !== null) return this.opening
    this.opening = open(this)
    return this.opening
  }

  close () {
    if (this.closing !== null) return this.closing
    this.closing = close(this)
    return this.closing
  }

  async _open () {
    // add impl here
  }

  async _close () {
    // add impl here
  }
}

async function open (self) {
  // open after close
  if (self.closing !== null) return

  try {
    await self._open()
  } catch (err) {
    self.close() // safe to run in bg
    throw err
  }

  self.opened = true
  self.emit('ready')
}

async function close (self) {
  try {
    if (self.opened === false && self.opening !== null) await self.opening
  } catch {
    // ignore errors on closing
  }
  if (self.opened === true || self.opening === null) await self._close()
  self.closed = true
  self.emit('close')
}
{
  "name": "ready-resource",
  "version": "1.2.0",
  "description": "Modern single resource management",
  "main": "index.js",
  "imports": {
    "events": {
      "bare": "bare-events",
      "default": "events"
    }
  },
  "types": "index.d.ts",
  "files": [
    "index.js",
    "index.d.ts"
  ],
  "scripts": {
    "test": "standard && brittle test.js"
  },
  "devDependencies": {
    "brittle": "^3.1.0",
    "standard": "^17.0.0"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/ready-resource.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/holepunchto/ready-resource/issues"
  },
  "homepage": "https://github.com/holepunchto/ready-resource",
  "dependencies": {
    "bare-events": "^2.2.0"
  }
}
const b4a = require('b4a')

var EMPTY = []

module.exports = RecordCache

function RecordSet () {
  this.list = []
  this.map = new Map()
}

RecordSet.prototype.add = function (record, value) {
  var k = toString(record)
  var r = this.map.get(k)
  if (r) return false

  r = {index: this.list.length, record: value || record}
  this.list.push(r)
  this.map.set(k, r)
  return true
}

RecordSet.prototype.remove = function (record) {
  var k = toString(record)
  var r = this.map.get(k)
  if (!r) return false

  swap(this.list, r.index, this.list.length - 1)
  this.list.pop()
  this.map.delete(k)
  return true
}

function RecordStore () {
  this.records = new Map()
  this.size = 0
}

RecordStore.prototype.add = function (name, record, value) {
  var r = this.records.get(name)

  if (!r) {
    r = new RecordSet()
    this.records.set(name, r)
  }

  if (r.add(record, value)) {
    this.size++
    return true
  }

  return false
}

RecordStore.prototype.remove = function (name, record, value) {
  var r = this.records.get(name)
  if (!r) return false

  if (r.remove(record, value)) {
    this.size--
    if (!r.map.size) this.records.delete(name)
    return true
  }

  return false
}

RecordStore.prototype.get = function (name) {
  var r = this.records.get(name)
  return r ? r.list : EMPTY
}

function RecordCache (opts) {
  if (!(this instanceof RecordCache)) return new RecordCache(opts)
  if (!opts) opts = {}

  this.maxSize = opts.maxSize || Infinity
  this.maxAge = opts.maxAge || 0

  this._onstale = opts.onStale || opts.onstale || null
  this._fresh = new RecordStore()
  this._stale = new RecordStore()
  this._interval = null
  this._gced = false

  if (this.maxAge && this.maxAge < Infinity) {
    // 2/3 gives us a span of 0.66-1.33 maxAge or avg maxAge
    var tick = Math.ceil(2 / 3 * this.maxAge)
    this._interval = setInterval(this._gcAuto.bind(this), tick)
    if (this._interval.unref) this._interval.unref()
  }
}

Object.defineProperty(RecordCache.prototype, 'size', {
  get: function () {
    return this._fresh.size + this._stale.size
  }
})

RecordCache.prototype.add = function (name, record, value) {
  this._stale.remove(name, record, value)
  if (this._fresh.add(name, record, value) && this._fresh.size > this.maxSize) {
    this._gc()
  }
}

RecordCache.prototype.remove = function (name, record, value) {
  this._fresh.remove(name, record, value)
  this._stale.remove(name, record, value)
}

RecordCache.prototype.get = function (name, n) {
  var a = this._fresh.get(name)
  var b = this._stale.get(name)
  var aLen = a.length
  var bLen = b.length
  var len = aLen + bLen

  if (n > len || !n) n = len
  var result = new Array(n)

  for (var i = 0; i < n; i++) {
    var j = Math.floor(Math.random() * (aLen + bLen))
    if (j < aLen) {
      result[i] = a[j].record
      swap(a, j, --aLen)
    } else {
      j -= aLen
      result[i] = b[j].record
      swap(b, j, --bLen)
    }
  }

  return result
}

RecordCache.prototype._gcAuto = function () {
  if (!this._gced) this._gc()
  this._gced = false
}

RecordCache.prototype._gc = function () {
  if (this._onstale && this._stale.size > 0) this._onstale(this._stale)
  this._stale = this._fresh
  this._fresh = new RecordStore()
  this._gced = true
}

RecordCache.prototype.clear = function () {
  this._gc()
  this._gc()
}

RecordCache.prototype.destroy = function () {
  this.clear()
  clearInterval(this._interval)
  this._interval = null
}

function toString (record) {
  return b4a.isBuffer(record) ? b4a.toString(record, 'hex') : record
}

function swap (list, a, b) {
  var tmp = list[a]
  tmp.index = b
  list[b].index = a
  list[a] = list[b]
  list[b] = tmp
}
{
  "name": "record-cache",
  "version": "1.2.0",
  "description": "Cache optimised for record like things",
  "main": "index.js",
  "dependencies": {
    "b4a": "^1.3.1"
  },
  "devDependencies": {
    "standard": "^10.0.3",
    "tape": "^4.8.0"
  },
  "scripts": {
    "test": "standard && tape test.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/mafintosh/record-cache.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/mafintosh/record-cache/issues"
  },
  "homepage": "https://github.com/mafintosh/record-cache"
}
module.exports = class RefCounter {
  constructor() {
    this.count = 0

    this._onidle = null
    this._idle = null
  }

  isIdle() {
    return this.count === 0
  }

  idle() {
    if (this.count === 0) return Promise.resolve()
    if (this._idle !== null) return this._idle

    this._idle = new Promise((resolve) => {
      this._onidle = resolve
    })

    return this._idle
  }

  inc() {
    this.count++
  }

  dec() {
    if (--this.count > 0) return

    if (this._onidle !== null) {
      const resolve = this._onidle
      this._idle = null
      this._onidle = null
      resolve()
    }
  }
}
{
  "name": "refcounter",
  "version": "1.0.0",
  "description": "Simple refcounter",
  "main": "index.js",
  "dependencies": {},
  "devDependencies": {},
  "repository": {
    "type": "git",
    "url": "https://github.com/holepunchto/refcounter.git"
  },
  "author": "Holepunch Inc",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/refcounter/issues"
  },
  "homepage": "https://github.com/holepunchto/refcounter"
}
module.exports = require.addon.bind(require)
{
  "name": "require-addon",
  "version": "1.2.0",
  "description": "Import native addons across JavaScript runtimes",
  "exports": {
    "./package": "./package.json",
    ".": {
      "bare": "./lib/bare.js",
      "node": "./lib/node.js",
      "default": "./lib/default.js"
    }
  },
  "imports": {
    "fs": {
      "bare": "bare-fs",
      "default": "fs"
    },
    "path": {
      "bare": "bare-path",
      "default": "path"
    },
    "url": {
      "bare": "bare-url",
      "default": "url"
    }
  },
  "files": [
    "lib"
  ],
  "scripts": {
    "test": "npm run lint && npm run test:bare && npm run test:node",
    "test:bare": "bare test.js",
    "test:node": "node test.js",
    "lint": "prettier --check .",
    "format": "prettier --write ."
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/require-addon.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/require-addon/issues"
  },
  "homepage": "https://github.com/holepunchto/require-addon#readme",
  "engines": {
    "bare": ">=1.10.0"
  },
  "dependencies": {
    "bare-addon-resolve": "^1.3.0"
  },
  "devDependencies": {
    "bare-bundle": "^1.8.1",
    "bare-bundle-evaluate": "^1.1.0",
    "bare-fs": "^4.0.0",
    "bare-path": "^3.0.0",
    "bare-url": "^2.1.0",
    "brittle": "^3.7.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^2.0.0"
  }
}
let tmpResolve = null
let tmpReject = null

if (Promise.withResolvers) {
  module.exports = Promise.withResolvers.bind(Promise)
} else {
  module.exports = function resolveRejectPromise () {
    const promise = new Promise(setTmp)
    const result = { promise, resolve: tmpResolve, reject: tmpReject }
    tmpResolve = tmpReject = null
    return result
  }
}

function setTmp (resolve, reject) {
  tmpResolve = resolve
  tmpReject = reject
}
{
  "name": "resolve-reject-promise",
  "version": "1.1.0",
  "description": "Create an inverted promise with no function allocs",
  "main": "index.js",
  "scripts": {
    "test": "standard"
  },
  "devDependencies": {
    "standard": "^17.1.2"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/mafintosh/resolve-reject-promise.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/mafintosh/resolve-reject-promise/issues"
  },
  "homepage": "https://github.com/mafintosh/resolve-reject-promise"
}
const resources = new Map()

exports.add = function(resource, teardown) {
  resources.set(resource, teardown)
}

exports.remove = function(resource) {
  resources.delete(resource)
}

if (global.Bare) global.Bare.on('exit', run)
else global.process.on('exit', run)

function run() {
  for (const [resource, teardown] of resources) {
    teardown(resource)
  }
}
{
  "name": "resource-on-exit",
  "version": "1.0.0",
  "description": "Register sync teardown handlers for a resource",
  "main": "index.js",
  "dependencies": {},
  "devDependencies": {},
  "repository": {
    "type": "git",
    "url": "https://github.com/holepunchto/bare-teardown.git"
  },
  "author": "Holepunch Inc.",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/bare-teardown/issues"
  },
  "homepage": "https://github.com/holepunchto/bare-teardown"
}
require.addon = require('require-addon')

module.exports = require.addon('.', __filename)
const ColumnFamily = require('./lib/column-family')
const Iterator = require('./lib/iterator')
const Snapshot = require('./lib/snapshot')
const State = require('./lib/state')
const { BloomFilterPolicy, RibbonFilterPolicy } = require('./lib/filter-policy')
const constants = require('./lib/constants')

class RocksDB {
  constructor(path, opts = {}) {
    const {
      columnFamily,
      state = new State(this, path, opts),
      snapshot = null,
      keyEncoding = null,
      valueEncoding = null
    } = opts

    this._state = state
    this._snapshot = snapshot
    this._columnFamily = state.getColumnFamily(columnFamily)
    this._keyEncoding = keyEncoding
    this._valueEncoding = valueEncoding
    this._index = -1

    this._state.addSession(this)
  }

  get opened() {
    return this._state.opened
  }

  get closed() {
    return this.isRoot() ? this._state.closed : this._index === -1
  }

  get path() {
    return this._state.path
  }

  get stats() {
    return this._state.stats
  }

  get snapshotted() {
    return this._snapshot !== null
  }

  get defaultColumnFamily() {
    return this._columnFamily
  }

  session({
    columnFamily = this._columnFamily,
    snapshot = this._snapshot !== null,
    keyEncoding = this._keyEncoding,
    valueEncoding = this._valueEncoding
  } = {}) {
    maybeClosed(this)

    return new RocksDB(null, {
      state: this._state,
      columnFamily,
      snapshot: snapshot ? this._snapshot || new Snapshot(this._state) : null,
      keyEncoding,
      valueEncoding
    })
  }

  columnFamily(name, opts) {
    return this.session({ ...opts, columnFamily: name })
  }

  snapshot() {
    return this.session({ snapshot: true })
  }

  isRoot() {
    return this === this._state.db
  }

  ready() {
    return this._state.ready()
  }

  async close({ force } = {}) {
    if (!this._state.opened) await this._state.ready()

    if (this._index !== -1) this._state.removeSession(this)

    if (force) {
      while (this._state.sessions.length > 0) {
        await this._state.sessions[this._state.sessions.length - 1].close()
      }
    }

    return this.isRoot() ? this._state.close() : Promise.resolve()
  }

  suspend() {
    maybeClosed(this)

    return this._state.suspend()
  }

  resume() {
    maybeClosed(this)

    return this._state.resume()
  }

  isIdle() {
    return this._state.handles.isIdle()
  }

  idle() {
    return this._state.handles.idle()
  }

  iterator(range, opts) {
    maybeClosed(this)

    return new Iterator(this, { ...range, ...opts })
  }

  async *keys(range, opts) {
    for await (const { key } of this.iterator(range, {
      ...opts,
      values: false
    })) {
      yield key
    }
  }

  async peek(range, opts) {
    for await (const value of this.iterator({ ...range, ...opts, limit: 1 })) {
      return value
    }

    return null
  }

  read(opts) {
    maybeClosed(this)

    return this._state.createReadBatch(this, opts)
  }

  write(opts) {
    maybeClosed(this)

    return this._state.createWriteBatch(this, opts)
  }

  flush(opts) {
    maybeClosed(this)

    return this._state.flush(this, opts)
  }

  async get(key, opts) {
    const batch = this.read({ ...opts, capacity: 1, autoDestroy: true })
    const value = batch.get(key)
    batch.tryFlush()
    return value
  }

  async put(key, value, opts) {
    const batch = this.write({ ...opts, capacity: 1, autoDestroy: true })
    batch.tryPut(key, value)
    await batch.flush()
  }

  async delete(key, opts) {
    const batch = this.write({ ...opts, capacity: 1, autoDestroy: true })
    batch.tryDelete(key)
    await batch.flush()
  }

  async deleteRange(start, end, opts) {
    const batch = this.write({ ...opts, capacity: 1, autoDestroy: true })
    batch.tryDeleteRange(start, end)
    await batch.flush()
  }

  async compactRange(start = null, end = null, opts = {}) {
    if (typeof end === 'object' && end !== null) {
      opts = end
      end = null
    } else if (typeof start === 'object' && start !== null) {
      opts = start
      start = null
      end = null
    }

    await this._state.compactRange(this, start, end, opts)
  }

  async approximateSize(start, end, opts = {}) {
    return this._state.approximateSize(this, start, end, opts)
  }

  _ref() {
    if (this._snapshot) this._snapshot.ref()
    this._state.handles.inc()
  }

  _unref() {
    if (this._snapshot) this._snapshot.unref()
    this._state.handles.dec()
  }
}

module.exports = exports = RocksDB

exports.constants = constants

exports.ColumnFamily = ColumnFamily
exports.BloomFilterPolicy = BloomFilterPolicy
exports.RibbonFilterPolicy = RibbonFilterPolicy

function maybeClosed(db) {
  if (db._state.closing || db._index === -1) throw new Error('RocksDB session is closed')
}
const c = require('compact-encoding')
const binding = require('../binding')

const empty = Buffer.alloc(0)
const resolved = Promise.resolve()

class RocksDBBatch {
  constructor(db, opts = {}) {
    const { capacity = 8, autoDestroy = false } = opts

    db._ref()

    this._db = db
    this._destroyed = false
    this._capacity = capacity
    this._operations = []
    this._promises = []

    this._enqueuePromise = this._enqueuePromise.bind(this)

    this._request = null
    this._resolve = null
    this._reject = null

    this._handle = null
    this._buffer = null
    this._autoDestroy = autoDestroy
    this._stats = null

    this._resetStats()

    if (db._state.opened === true) this.ready()
  }

  _reuse(db, opts = {}) {
    const { autoDestroy = false } = opts

    db._ref()

    this._db = db
    this._destroyed = false
    this._autoDestroy = autoDestroy
  }

  _onfinished(err) {
    const resolve = this._resolve
    const reject = this._reject

    if (this._request) this._db._state.io.dec()

    this._operations = []
    this._promises = []
    this._request = null
    this._resolve = null
    this._reject = null

    this._resetStats()

    if (this._autoDestroy === true) this.destroy()

    if (reject !== null && err) reject(err)
    else if (resolve !== null) resolve()
  }

  _resize() {
    if (this._operations.length <= this._capacity) return false

    while (this._operations.length > this._capacity) {
      this._capacity *= 2
    }

    return true
  }

  async ready() {
    if (this._handle !== null) return

    if (this._db._state.opened === false) await this._db._state.ready()

    this._init()
  }

  destroy() {
    if (this._request) throw new Error('Request in progress')
    if (this._destroyed) return

    this._destroyed = true

    if (this._promises.length) this._abort()

    this._db._unref()
    this._onfree()
  }

  _onfree() {
    this._resetStats()
    this._db._state.freeBatch(this, false)
    this._db = null
  }

  _abort() {
    for (let i = 0; i < this._promises.length; i++) {
      const promise = this._promises[i]
      if (promise !== null) promise.reject(new Error('Batch is destroyed'))
    }

    this._onfinished(new Error('Batch is destroyed'))
  }

  _resetStats() {}

  async flush() {
    if (this._request) throw new Error('Request in progress')
    if (this._destroyed) throw new Error('Batch is destroyed')

    this._request = new Promise((resolve, reject) => {
      this._resolve = resolve
      this._reject = reject
    })

    this._flush()

    return this._request
  }

  tryFlush() {
    if (this._request) throw new Error('Request in progress')
    if (this._destroyed) throw new Error('Batch is destroyed')

    this._request = resolved

    this._flush()
  }

  async _flush() {
    if (this._handle === null) await this.ready()

    this._db._state.io.inc()

    if (this._db._state.resumed !== null) {
      const resumed = await this._db._state.resumed.promise

      if (!resumed) {
        if (this._destroyed) {
          this._db._state.io.dec()
        } else {
          this._destroyed = true
          this._abort()
          this._db._unref()
        }
      }
    }
  }

  _enqueuePromise(resolve, reject) {
    this._promises.push({ resolve, reject })
  }

  _encodeKey(k) {
    if (this._db._keyEncoding) return c.encode(this._db._keyEncoding, k)
    if (typeof k === 'string') return Buffer.from(k)
    return k
  }

  _encodeValue(v) {
    if (this._db._valueEncoding) return c.encode(this._db._valueEncoding, v)
    if (v === null) return empty
    if (typeof v === 'string') return Buffer.from(v)
    return v
  }

  _decodeValue(b) {
    if (this._db._valueEncoding) return c.decode(this._db._valueEncoding, b)
    return b
  }
}

exports.ReadBatch = class RocksDBReadBatch extends RocksDBBatch {
  constructor(db, opts = {}) {
    super(db, opts)

    const { asyncIO = false, fillCache = true } = opts

    this._asyncIO = asyncIO
    this._fillCache = fillCache
  }

  _init() {
    this._handle = binding.readInit()
    this._buffer = binding.readBuffer(this._handle, this._capacity)
  }

  _resize() {
    if (super._resize() && this._handle !== null) {
      this._buffer = binding.readBuffer(this._handle, this._capacity)
    }
  }

  async _flush() {
    await super._flush()

    if (this._destroyed) return

    try {
      binding.read(
        this._db._state._handle,
        this._handle,
        this._operations,
        this._db._snapshot ? this._db._snapshot._handle : undefined,
        this._asyncIO,
        this._fillCache,
        this,
        this._onread
      )
    } catch (err) {
      this._db._state.io.dec()
      throw err
    }

    this._db._state.stats.gets += this._stats.gets
    this._db._state.stats.readBatches++
  }

  _onread(errs, values) {
    let applied = true

    for (let i = 0, n = this._promises.length; i < n; i++) {
      const err = errs[i]
      if (err) applied = false

      const promise = this._promises[i]
      if (promise === null) continue

      if (err) promise.reject(err)
      else promise.resolve(values[i] ? this._decodeValue(Buffer.from(values[i])) : null)
    }

    this._onfinished(applied ? null : new AggregateError(errs, 'Batch was not applied'))
  }

  _resetStats() {
    this._stats = { gets: 0 }
  }

  get(key) {
    if (this._request) throw new Error('Request already in progress')
    this._stats.gets++

    const promise = new Promise(this._enqueuePromise)

    this._operations.push(new RocksDBGet(this._encodeKey(key), this._db._columnFamily))

    this._resize()

    return promise
  }
}

exports.WriteBatch = class RocksDBWriteBatch extends RocksDBBatch {
  _init() {
    this._handle = binding.writeInit()
    this._buffer = binding.writeBuffer(this._handle, this._capacity)
  }

  _resize() {
    if (super._resize() && this._handle !== null) {
      this._buffer = binding.writeBuffer(this._handle, this._capacity)
    }
  }

  _onfree() {
    this._resetStats()
    this._db._state.freeBatch(this, true)
    this._db = null
  }

  async _flush() {
    await super._flush()

    if (this._destroyed) return

    try {
      binding.write(this._db._state._handle, this._handle, this._operations, this, this._onwrite)
    } catch (err) {
      this._db._state.io.dec()
      throw err
    }

    this._db._state.stats.puts += this._stats.puts
    this._db._state.stats.deletes += this._stats.deletes
    this._db._state.stats.rangeDeletes += this._stats.rangeDeletes
    this._db._state.stats.writeBatches++
  }

  _onwrite(err) {
    const applied = !err

    for (let i = 0, n = this._promises.length; i < n; i++) {
      const promise = this._promises[i]
      if (promise === null) continue

      if (err) promise.reject(err)
      else promise.resolve()
    }

    this._onfinished(applied ? null : new Error('Batch was not applied', { cause: err }))
  }

  _resetStats() {
    this._stats = { puts: 0, deletes: 0, rangeDeletes: 0 }
  }

  put(key, value) {
    if (this._request) throw new Error('Request already in progress')
    this._stats.puts++

    const promise = new Promise(this._enqueuePromise)

    this._operations.push(
      new RocksDBPut(this._encodeKey(key), this._encodeValue(value), this._db._columnFamily)
    )

    this._resize()

    return promise
  }

  tryPut(key, value) {
    if (this._request) throw new Error('Request already in progress')
    this._stats.puts++

    this._operations.push(
      new RocksDBPut(this._encodeKey(key), this._encodeValue(value), this._db._columnFamily)
    )

    this._promises.push(null)

    this._resize()
  }

  delete(key) {
    if (this._request) throw new Error('Request already in progress')
    this._stats.deletes++

    const promise = new Promise(this._enqueuePromise)

    this._operations.push(new RocksDBDelete(this._encodeKey(key), this._db._columnFamily))

    this._resize()

    return promise
  }

  tryDelete(key) {
    if (this._request) throw new Error('Request already in progress')
    this._stats.deletes++

    this._operations.push(new RocksDBDelete(this._encodeKey(key), this._db._columnFamily))

    this._promises.push(null)

    this._resize()
  }

  deleteRange(start, end) {
    if (this._request) throw new Error('Request already in progress')
    this._stats.rangeDeletes++

    const promise = new Promise(this._enqueuePromise)

    this._operations.push(
      new RocksDBDeleteRange(this._encodeKey(start), this._encodeKey(end), this._db._columnFamily)
    )

    this._resize()

    return promise
  }

  tryDeleteRange(start, end) {
    if (this._request) throw new Error('Request already in progress')
    this._stats.rangeDeletes++

    this._operations.push(
      new RocksDBDeleteRange(this._encodeKey(start), this._encodeKey(end), this._db._columnFamily)
    )

    this._promises.push(null)

    this._resize()
  }
}

class RocksDBGet {
  constructor(key, columnFamily) {
    this.key = key
    this.columnFamily = columnFamily._handle
  }

  get type() {
    return binding.GET
  }
}

class RocksDBPut {
  constructor(key, value, columnFamily) {
    this.key = key
    this.value = value
    this.columnFamily = columnFamily._handle
  }

  get type() {
    return binding.PUT
  }
}

class RocksDBDelete {
  constructor(key, columnFamily) {
    this.key = key
    this.columnFamily = columnFamily._handle
  }

  get type() {
    return binding.DELETE
  }
}

class RocksDBDeleteRange {
  constructor(start, end, columnFamily) {
    this.start = start
    this.end = end
    this.columnFamily = columnFamily._handle
  }

  get type() {
    return binding.DELETE_RANGE
  }
}
const binding = require('../binding')
const constants = require('./constants')
const { BloomFilterPolicy } = require('./filter-policy')

class RocksDBColumnFamily {
  constructor(name, opts = {}) {
    const {
      // Blob options
      enableBlobFiles = false,
      minBlobSize = 0,
      blobFileSize = 0,
      enableBlobGarbageCollection = true,
      // Block table options
      tableBlockSize = 8192,
      tableCacheIndexAndFilterBlocks = true,
      tableFormatVersion = 6,
      optimizeFiltersForMemory = false,
      blockCache = true,
      filterPolicy = new BloomFilterPolicy(10),
      topLevelIndexPinningTier = constants.pinningTier.ALL,
      partitionPinningTier = constants.pinningTier.ALL,
      unpartitionedPinningTier = constants.pinningTier.ALL,
      optimizeFiltersForHits = false,
      numLevels = 7,
      maxWriteBufferNumber = 2,
      blobGarbageCollectionAgeCutOff = 0.25,
      blobGarbageCollectionForceThreshold = 1.0
    } = opts

    this._name = name
    this._flushing = null
    this._options = {
      enableBlobFiles,
      minBlobSize,
      blobFileSize,
      enableBlobGarbageCollection,
      tableBlockSize,
      tableCacheIndexAndFilterBlocks,
      tableFormatVersion,
      optimizeFiltersForMemory,
      blockCache,
      filterPolicy,
      topLevelIndexPinningTier,
      partitionPinningTier,
      unpartitionedPinningTier,
      optimizeFiltersForHits,
      numLevels,
      maxWriteBufferNumber,
      blobGarbageCollectionAgeCutOff,
      blobGarbageCollectionForceThreshold
    }

    const filterPolicyArguments = [0, 0, 0]

    if (filterPolicy !== null) {
      filterPolicyArguments[0] = filterPolicy.type

      switch (filterPolicy.type) {
        case 1: // Bloom filter policy
          filterPolicyArguments[1] = filterPolicy.bitsPerKey
          break
        case 2: // Ribbon filter policy
          filterPolicyArguments[1] = filterPolicy.bloomEquivalentBitsPerKey
          filterPolicyArguments[2] = filterPolicy.bloomBeforeLevel

          break
      }
    }

    this._handle = binding.columnFamilyInit(
      name,
      enableBlobFiles,
      minBlobSize,
      blobFileSize,
      enableBlobGarbageCollection,
      tableBlockSize,
      tableCacheIndexAndFilterBlocks,
      tableFormatVersion,
      optimizeFiltersForMemory,
      blockCache === false,
      ...filterPolicyArguments,
      topLevelIndexPinningTier,
      partitionPinningTier,
      unpartitionedPinningTier,
      optimizeFiltersForHits,
      numLevels,
      maxWriteBufferNumber,
      blobGarbageCollectionAgeCutOff,
      blobGarbageCollectionForceThreshold
    )
  }

  cloneSettings(name) {
    return new RocksDBColumnFamily(name, this._options)
  }

  get name() {
    return this._name
  }

  destroy() {
    if (this._handle === null) return

    binding.columnFamilyDestroy(this._handle)

    this._handle = null
  }
}

module.exports = RocksDBColumnFamily
module.exports = {
  pinningTier: {
    NONE: 0,
    FLUSHED_AND_SIMILAR: 1,
    ALL: 2
  },
  garbageCollectionPolicy: {
    DEFAULT: 0,
    FORCE: 1,
    DISABLE: 2
  },
  bottommostLevelCompaction: {
    NONE: 0,
    SKIP: 1,
    FORCE: 2
  },
  walRecoveryMode: {
    TOLERATE_CORRUPTED_TAIL_RECORDS: 0,
    ABSOLUTE_CONSISTENCY: 1,
    POINT_IN_TIME: 2,
    SKIP_ANY_CORRUPTED_RECORDS: 3
  }
}
exports.BloomFilterPolicy = class RocksDBBloomFilterPolicy {
  get type() {
    return 1
  }

  constructor(bitsPerKey) {
    this.bitsPerKey = bitsPerKey
  }
}

exports.RibbonFilterPolicy = class RocksDBRibbonFilterPolicy {
  get type() {
    return 2
  }

  constructor(bloomEquivalentBitsPerKey, bloomBeforeLevel = 0) {
    this.bloomEquivalentBitsPerKey = bloomEquivalentBitsPerKey
    this.bloomBeforeLevel = bloomBeforeLevel
  }
}
const { Readable } = require('streamx')
const c = require('compact-encoding')
const binding = require('../binding')

const empty = Buffer.alloc(0)

module.exports = class RocksDBIterator extends Readable {
  constructor(db, opts = {}) {
    const {
      gt = null,
      gte = null,
      lt = null,
      lte = null,
      reverse = false,
      values = true,
      limit = Infinity,
      capacity = 8
    } = opts

    super()

    db._ref()

    this._db = db

    this._gt = gt ? this._encodeKey(gt) : empty
    this._gte = gte ? this._encodeKey(gte) : empty
    this._lt = lt ? this._encodeKey(lt) : empty
    this._lte = lte ? this._encodeKey(lte) : empty

    this._reverse = reverse
    this._values = values
    this._limit = limit < 0 ? Infinity : limit
    this._capacity = capacity
    this._opened = false

    this._pendingOpen = null
    this._pendingRead = null
    this._pendingDestroy = null

    this._buffer = null
    this._handle = null

    if (this._db._state.opened === true) this.ready()
  }

  _onopen(err) {
    const cb = this._pendingOpen
    this._pendingOpen = null
    this._opened = true
    this._db._state.io.dec()
    cb(err)
  }

  _onread(err, keys, values) {
    const cb = this._pendingRead
    this._pendingRead = null
    this._db._state.io.dec()
    if (err) return cb(err)

    const n = keys.length

    this._limit -= n

    for (let i = 0; i < n; i++) {
      this.push({
        key: this._decodeKey(Buffer.from(keys[i])),
        value: this._values ? this._decodeValue(Buffer.from(values[i])) : null
      })
    }

    if (n < this._capacity) this.push(null)

    cb(null)
  }

  _onclose(err) {
    const cb = this._pendingDestroy
    this._pendingDestroy = null
    this._db._state.io.dec()
    this._db._unref()
    cb(err)
  }

  _resize() {
    if (this._handle !== null) {
      this._buffer = binding.iteratorBuffer(this._handle, this._capacity)
    }
  }

  async ready() {
    if (this._handle !== null) return

    if (this._db._state.opened === false) await this._db._state.ready()

    this._init()
  }

  _init() {
    this._handle = binding.iteratorInit()
    this._buffer = binding.iteratorBuffer(this._handle, this._capacity)
  }

  async _open(cb) {
    await this.ready()

    this._db._state.io.inc()

    if (this._db._state.resumed !== null) {
      const resumed = await this._db._state.resumed.promise

      if (!resumed) {
        this._db._state.io.dec()

        return cb(new Error('RocksDB session is closed'))
      }
    }

    this._pendingOpen = cb

    try {
      binding.iteratorOpen(
        this._db._state._handle,
        this._handle,
        this._db._columnFamily._handle,
        this._gt,
        this._gte,
        this._lt,
        this._lte,
        this._reverse,
        !this._values, // Keys only
        this._db._snapshot ? this._db._snapshot._handle : undefined,
        this,
        this._onopen,
        this._onclose,
        this._onread
      )
    } catch (err) {
      this._db._state.io.dec()

      cb(err)
    }
  }

  async _read(cb) {
    this._db._state.io.inc()

    if (this._db._state.resumed !== null) {
      const resumed = await this._db._state.resumed.promise

      if (!resumed) {
        this._db._state.io.dec()

        return cb(new Error('RocksDB session is closed'))
      }
    }

    this._pendingRead = cb

    try {
      binding.iteratorRead(this._handle, Math.min(this._capacity, this._limit))
    } catch (err) {
      this._db._state.io.dec()

      cb(err)
    }
  }

  async _destroy(cb) {
    await this.ready()

    this._db._state.io.inc()

    if (this._opened === false) {
      this._db._state.io.dec()
      this._db._unref()

      return cb(null)
    }

    this._pendingDestroy = cb

    try {
      binding.iteratorClose(this._handle)
    } catch (err) {
      this._db._state.io.dec()
      this._db._unref()

      cb(err)
    }
  }

  _encodeKey(k) {
    if (this._db._keyEncoding !== null) return c.encode(this._db._keyEncoding, k)
    if (typeof k === 'string') return Buffer.from(k)
    return k
  }

  _decodeKey(b) {
    if (this._db._keyEncoding !== null) return c.decode(this._db._keyEncoding, b)
    return b
  }

  _decodeValue(b) {
    if (this._db._valueEncoding !== null) return c.decode(this._db._valueEncoding, b)
    return b
  }
}
const binding = require('../binding')

module.exports = class RocksDBSnapshot {
  constructor(state) {
    this._state = state

    this._handle = null
    this._refs = 0

    if (state.deferSnapshotInit === false) this._init()
  }

  _init() {
    this._handle = binding.snapshotCreate(this._state._handle)
  }

  ref() {
    this._refs++
  }

  unref() {
    if (--this._refs > 0) return

    if (this._handle === null) return

    binding.snapshotDestroy(this._handle)

    this._handle = null
  }
}
const ReadyResource = require('ready-resource')
const RefCounter = require('refcounter')
const rrp = require('resolve-reject-promise')
const SignalPromise = require('signal-promise')
const c = require('compact-encoding')
const { ReadBatch, WriteBatch } = require('./batch')
const ColumnFamily = require('./column-family')
const binding = require('../binding')
const constants = require('./constants')

const MAX_BATCH_REUSE = 64
const empty = Buffer.alloc(0)

module.exports = class RocksDBState extends ReadyResource {
  constructor(db, path, opts) {
    super()

    const {
      columnFamily = new ColumnFamily('default', opts),
      columnFamilies = [],
      readOnly = false,
      createIfMissing = true,
      createMissingColumnFamilies = true,
      maxBackgroundJobs = 6,
      bytesPerSync = 1048576,
      maxOpenFiles = -1,
      useDirectReads = false,
      avoidUnnecessaryBlockingIO = false,
      skipStatsUpdateOnOpen = false,
      useDirectIOForFlushAndCompaction = false,
      maxFileOpeningThreads = 16,
      lock = null,
      walRecoveryMode = constants.walRecoveryMode.POINT_IN_TIME,
      bestEffortsRecovery = false
    } = opts

    this.path = path
    this.db = db
    this.handles = new RefCounter()
    this.io = new RefCounter()
    this.sessions = []
    this.columnFamilies = [columnFamily]
    this.deferSnapshotInit = true
    this.resumed = null
    this.stats = {
      gets: 0,
      puts: 0,
      deletes: 0,
      rangeDeletes: 0,
      readBatches: 0,
      writeBatches: 0
    }

    this._suspended = false
    this._suspending = false
    this._updating = false
    this._updatingSignal = new SignalPromise()
    this._columnsFlushed = false
    this._lock = lock
    this._readBatches = []
    this._writeBatches = []

    for (const columnFamily of columnFamilies) {
      this.columnFamilies.push(
        typeof columnFamily === 'string' ? new ColumnFamily(columnFamily, opts) : columnFamily
      )
    }

    this._handle = binding.init(
      readOnly,
      createIfMissing,
      createMissingColumnFamilies,
      maxBackgroundJobs,
      bytesPerSync,
      maxOpenFiles,
      useDirectReads,
      avoidUnnecessaryBlockingIO,
      skipStatsUpdateOnOpen,
      useDirectIOForFlushAndCompaction,
      maxFileOpeningThreads,
      walRecoveryMode,
      bestEffortsRecovery
    )
  }

  createReadBatch(db, opts) {
    if (this._readBatches.length === 0) return new ReadBatch(db, opts)
    const batch = this._readBatches.pop()
    batch._reuse(db, opts)
    return batch
  }

  createWriteBatch(db, opts) {
    if (this._writeBatches.length === 0) return new WriteBatch(db, opts)
    const batch = this._writeBatches.pop()
    batch._reuse(db, opts)
    return batch
  }

  freeBatch(batch, writable) {
    if (batch._capacity > 16) return
    const queue = writable ? this._writeBatches : this._readBatches
    if (queue.length >= MAX_BATCH_REUSE) return
    queue.push(batch)
  }

  addSession(db) {
    db._index = this.sessions.push(db) - 1
    if (db._snapshot) db._snapshot.ref()
  }

  removeSession(db) {
    const head = this.sessions.pop()
    if (head !== db) this.sessions[(head._index = db._index)] = head
    db._index = -1
    if (db._snapshot) db._snapshot.unref()
  }

  upsertColumnFamily(c) {
    if (typeof c === 'string') {
      let col = this.getColumnFamilyByName(c)
      if (col) return col
      col = this.columnFamilies[0].cloneSettings(c)
      this.columnFamilies.push(col)
      return col
    }

    if (this.columnFamilies.includes(c)) return c
    this.columnFamilies.push(c)
    return c
  }

  getColumnFamily(c) {
    if (!c) return this.columnFamilies[0]
    if (!this._columnsFlushed) return this.upsertColumnFamily(c)

    if (typeof c !== 'string') return c

    const col = this.getColumnFamilyByName(c)
    if (col === null) throw new Error('Unknown column family')
    return col
  }

  getColumnFamilyByName(name) {
    for (const col of this.columnFamilies) {
      if (col.name === name) return col
    }
    return null
  }

  async _open() {
    await Promise.resolve() // Allow column families to populate if on-demand

    if (this._lock) await this._lock.ready()

    const req = { resolve: null, reject: null, handle: null }

    const promise = new Promise((resolve, reject) => {
      req.resolve = resolve
      req.reject = reject
    })

    this._columnsFlushed = true

    const lock = this._lock === null ? -1 : this._lock.transfer()

    req.handle = binding.open(
      this._handle,
      this,
      this.path,
      this.columnFamilies.map((c) => c._handle),
      lock,
      req,
      onopen
    )

    await promise

    this.deferSnapshotInit = false

    for (const session of this.sessions) {
      if (session._snapshot) session._snapshot._init()
    }

    function onopen(err) {
      if (err) req.reject(err)
      else req.resolve()
    }
  }

  async _close() {
    while (this._updating) await this._updatingSignal.wait()
    if (this.resumed) this.resumed.resolve(false)

    while (!this.io.isIdle()) await this.io.idle()
    while (!this.handles.isIdle()) await this.handles.idle()

    while (this.sessions.length > 0) {
      await this.sessions[this.sessions.length - 1].close()
    }

    for (const columnFamily of this.columnFamilies) columnFamily.destroy()

    const req = { resolve: null, reject: null, handle: null }

    const promise = new Promise((resolve, reject) => {
      req.resolve = resolve
      req.reject = reject
    })

    req.handle = binding.close(this._handle, req, onclose)

    try {
      await promise
    } finally {
      if (this._lock) await this._lock.close()
    }

    function onclose(err) {
      if (err) req.reject(err)
      else req.resolve()
    }
  }

  async flush(db, opts) {
    if (this.opened === false) await this.ready()

    this.io.inc()

    if (this.resumed !== null) {
      const resumed = await this.resumed.promise

      if (!resumed) {
        this.io.dec()

        throw new Error('RocksDB session is closed')
      }
    }

    const req = { resolve: null, reject: null, handle: null }

    const promise = new Promise((resolve, reject) => {
      req.resolve = resolve
      req.reject = reject
    })

    try {
      req.handle = binding.flush(this._handle, db._columnFamily._handle, req, onflush)

      await promise
    } finally {
      this.io.dec()
    }

    function onflush(err) {
      if (err) req.reject(err)
      else req.resolve()
    }
  }

  suspend() {
    this._suspending = true
    return this.update()
  }

  resume() {
    this._suspending = false
    return this.update()
  }

  async update() {
    while (this._updating) await this._updatingSignal.wait()
    if (this._suspending === this._suspended || this.closing) return
    this._updating = true
    try {
      if (this._suspending) await this._suspend()
      else await this._resume()
    } finally {
      this._updating = false
      this._updatingSignal.notify()
    }
  }

  async _suspend() {
    if (this._suspended === true) return

    while (!this.io.isIdle()) await this.io.idle()

    this.io.inc()
    this.resumed = rrp()

    const req = { resolve: null, reject: null, handle: null }

    const promise = new Promise((resolve, reject) => {
      req.resolve = resolve
      req.reject = reject
    })

    try {
      req.handle = binding.suspend(this._handle, req, onsuspend)

      await promise

      this._suspended = true
    } finally {
      this.io.dec()
    }

    function onsuspend(err) {
      if (err) req.reject(err)
      else req.resolve()
    }
  }

  async _resume() {
    if (this._suspended === false) return

    const req = { resolve: null, reject: null, handle: null }

    const promise = new Promise((resolve, reject) => {
      req.resolve = resolve
      req.reject = reject
    })

    req.handle = binding.resume(this._handle, req, onresume)

    await promise

    this._suspended = false

    const resumed = this.resumed
    this.resumed = null
    resumed.resolve(true)

    function onresume(err) {
      if (err) req.reject(err)
      else req.resolve()
    }
  }

  async compactRange(db, start, end, opts) {
    if (this.opened === false) await this.ready()

    this.io.inc()

    const {
      exclusive = false,
      blobGarbageCollectionPolicy = constants.garbageCollectionPolicy.DEFAULT,
      blobGarbageCollectionAgeCutoff = 0.25,
      bottommostLevelCompaction = constants.bottommostLevelCompaction.NONE
    } = opts

    start = this._encodeKey(start)
    end = this._encodeKey(end)

    const req = { resolve: null, reject: null, handle: null, start, end }

    const promise = new Promise((resolve, reject) => {
      req.resolve = resolve
      req.reject = reject
    })

    try {
      req.handle = binding.compactRange(
        this._handle,
        db._columnFamily._handle,
        start,
        end,
        exclusive,
        blobGarbageCollectionPolicy,
        blobGarbageCollectionAgeCutoff,
        bottommostLevelCompaction,
        req,
        oncompactrange
      )

      await promise
    } finally {
      this.io.dec()
    }

    function oncompactrange(err) {
      if (err) req.reject(err)
      else req.resolve()
    }
  }

  async approximateSize(db, start, end, opts) {
    if (this.opened === false) await this.ready()

    this.io.inc()

    const { includeMemtables = false, includeFiles = true, filesSizeErrorMargin = -1 } = opts

    start = this._encodeKey(start)
    end = this._encodeKey(end)

    const req = { resolve: null, reject: null, handle: null, start, end }

    const promise = new Promise((resolve, reject) => {
      req.resolve = resolve
      req.reject = reject
    })

    try {
      req.handle = binding.approximateSize(
        this._handle,
        db._columnFamily._handle,
        start,
        end,
        includeMemtables,
        includeFiles,
        filesSizeErrorMargin,
        req,
        onapproximatesize
      )

      return await promise
    } finally {
      this.io.dec()
    }

    function onapproximatesize(err, result) {
      if (err) req.reject(err)
      else req.resolve(result)
    }
  }

  _encodeKey(k) {
    if (this.db._keyEncoding) return c.encode(this.db._keyEncoding, k)
    if (typeof k === 'string') return Buffer.from(k)
    if (k === null) return empty
    return k
  }
}
{
  "name": "rocksdb-native",
  "version": "3.15.2",
  "description": "librocksdb bindings for JavaScript",
  "exports": {
    ".": "./index.js",
    "./package": "./package.json"
  },
  "imports": {
    "crypto": {
      "bare": "bare-crypto",
      "default": "crypto"
    },
    "fs": {
      "bare": "bare-fs",
      "default": "fs"
    },
    "path": {
      "bare": "bare-path",
      "default": "path"
    }
  },
  "files": [
    "index.js",
    "binding.cc",
    "binding.js",
    "CMakeLists.txt",
    "lib",
    "prebuilds",
    "!prebuilds/android-*/**/*.node"
  ],
  "addon": true,
  "scripts": {
    "format": "prettier --write . && lunte --fix",
    "lint": "prettier --check . && lunte",
    "test": "npm run test:bare && npm run test:node",
    "test:bare": "brittle-bare --coverage test.js",
    "test:node": "brittle-node --coverage test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/rocksdb-native.git"
  },
  "author": "Holepunch Inc",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/rocksdb-native/issues"
  },
  "homepage": "https://github.com/holepunchto/rocksdb-native",
  "engines": {
    "bare": ">=1.16.0"
  },
  "dependencies": {
    "compact-encoding": "^3.0.0",
    "ready-resource": "^1.0.0",
    "refcounter": "^1.0.0",
    "require-addon": "^1.0.2",
    "resolve-reject-promise": "^1.1.0",
    "signal-promise": "^1.0.3",
    "streamx": "^2.16.1"
  },
  "devDependencies": {
    "bare-compat-napi": "^1.3.0",
    "bare-crypto": "^1.12.0",
    "bare-fs": "^4.5.0",
    "bare-path": "^3.0.0",
    "brittle": "^3.5.0",
    "cmake-bare": "^1.1.14",
    "cmake-fetch": "^1.0.1",
    "cmake-napi": "^1.0.6",
    "fd-lock": "^2.0.0",
    "lunte": "^1.8.0",
    "paparam": "^1.9.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^2.0.0"
  }
}
module.exports = safetyCatch

function isActuallyUncaught (err) {
  if (!err) return false
  return err instanceof TypeError ||
    err instanceof SyntaxError ||
    err instanceof ReferenceError ||
    err instanceof EvalError ||
    err instanceof RangeError ||
    err instanceof URIError ||
    err.code === 'ERR_ASSERTION' ||
    err.name === 'AssertionError'
}

function throwErrorNT (err) {
  queueMicrotask(() => { throw err })
}

function safetyCatch (err) {
  if (isActuallyUncaught(err)) {
    throwErrorNT(err)
    throw err
  }
}
{
  "name": "safety-catch",
  "version": "1.0.3",
  "description": "Small module that makes sure your catch, caught an actual error and not a programming mistake or assertion",
  "main": "index.js",
  "dependencies": {},
  "devDependencies": {},
  "repository": {
    "type": "git",
    "url": "https://github.com/mafintosh/safety-catch.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/mafintosh/safety-catch/issues"
  },
  "homepage": "https://github.com/mafintosh/safety-catch"
}
let tmpResolve = null

module.exports = class ScopeLock {
  constructor ({ debounce = false } = {}) {
    this.debounce = debounce
    this.waiting = []
    this.locked = false
    this.skip = 0
    this.destroyed = false
  }

  flush () {
    if (this.locked === false && this.waiting.length === 0) return Promise.resolve(this.destroyed === false)

    const promise = new Promise(setTmpResolve)
    const resolve = tmpResolve

    tmpResolve = null
    this.waiting.push({ lock: false, resolve })

    return promise
  }

  destroy () {
    this.destroyed = true
  }

  lock () {
    const promise = new Promise(setTmpResolve)
    const resolve = tmpResolve

    tmpResolve = null

    if (this.locked === true) {
      this.waiting.push({ lock: true, resolve })
      return promise
    }

    if (this.destroyed === true) {
      resolve(false)
      return promise
    }

    this.locked = true
    resolve(true)

    return promise
  }

  unlock () {
    if (this.destroyed === true) {
      for (let i = 0; i < this.waiting.length; i++) {
        this.waiting[i].resolve(false)
      }
      this.waiting = []
      this.skip = 0
      this.locked = false
      return
    }

    if (this.skip !== 0) {
      for (let i = 0; i < this.skip; i++) {
        const { lock, resolve } = this.waiting[i]
        resolve(lock === false)
      }

      this.waiting = this.waiting.slice(this.skip)
      this.skip = 0
    }

    while (this.waiting.length > 0 && this.waiting[0].lock === false) {
      this.waiting.shift().resolve(true)
    }

    if (this.waiting.length === 0) {
      this.locked = false
      return
    }

    const { resolve } = this.waiting.shift()
    if (this.debounce === true) this.skip = this.waiting.length

    resolve(true)
  }
}

function setTmpResolve (resolve) {
  tmpResolve = resolve
}
{
  "name": "scope-lock",
  "version": "1.2.4",
  "description": "Some concurrency semantics around entering scopes",
  "main": "index.js",
  "files": [
    "index.js"
  ],
  "devDependencies": {
    "brittle": "^3.7.0",
    "standard": "^17.1.2"
  },
  "scripts": {
    "test": "standard && brittle test.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/holepunchto/scope-lock.git"
  },
  "author": "Holepunch Inc.",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/scope-lock/issues"
  },
  "homepage": "https://github.com/holepunchto/scope-lock"
}
const set = require('unordered-set')

module.exports = opts => new ShuffledPriorityQueue(opts)

class ShuffledPriorityQueue {
  constructor (opts) {
    this.priorities = []
    this.equals = (opts && opts.equals) || null
  }

  get length () {
    return this.priorities.reduce(add, 0)
  }

  [Symbol.iterator] () {
    return new Iterator(this)
  }

  head () {
    for (let i = this.priorities.length - 1; i >= 0; i--) {
      const q = this.priorities[i]
      if (q.length) return shuffle(q, 0)
    }
    return null
  }

  tail () {
    for (let i = 0; i < this.priorities.length; i++) {
      const q = this.priorities[i]
      if (q.length) return shuffle(q, 0)
    }
    return null
  }

  prev (prev) {
    if (!prev) return this.tail()
    return next(this.priorities, prev, 1)
  }

  next (prev) {
    if (!prev) return this.head()
    return next(this.priorities, prev, -1)
  }

  shift () {
    return this.remove(this.head())
  }

  pop () {
    return this.remove(this.tail())
  }

  add (val) {
    const prio = val.priority || 0
    while (prio >= this.priorities.length) this.priorities.push([])
    set.add(this.priorities[prio], val)
    return val
  }

  remove (val) {
    if (!val) return null

    if (val._index === undefined) {
      val = this.find(val)
      if (!val) return null
    }

    return set.remove(this.priorities[val.priority || 0], val)
  }

  has (val) {
    if (val._index === undefined) return this.find(val)
    const priority = val.priority || 0
    if (priority >= this.priorities.length) return false
    return set.has(this.priorities[priority], val)
  }

  find (val) {
    if (val._index !== undefined) return val

    const prio = val.priority || 0
    const qs = this.priorities
    if (prio >= qs.length) return null

    const q = qs[prio]

    for (let i = 0; i < q.length; i++) {
      if (this.equals(q[i], val)) return q[i]
    }

    return null
  }
}

class Iterator {
  constructor (queue) {
    this.prev = null
    this.queue = queue
  }

  next () {
    const next = this.queue.next(this.prev)
    this.prev = next
    return { done: !next, value: next }
  }
}

function shuffle (q, i) {
  const ran = i + Math.floor(Math.random() * (q.length - i))
  set.swap(q, q[ran], q[i])
  return q[i]
}

function next (queues, prev, inc) {
  let i = prev.priority || 0
  let j = (prev._index || 0) + 1

  while (true) {
    if (i < 0 || i >= queues.length) return null
    const q = queues[i]

    if (j >= q.length) {
      i += inc
      j = 0
      continue
    }

    return shuffle(q, j)
  }
}

function add (len, b) {
  return len + b.length
}
{
  "name": "shuffled-priority-queue",
  "version": "2.1.0",
  "description": "A priority queue that shuffles elements with the same priority.",
  "main": "index.js",
  "scripts": {
    "test": "standard && tape test.js"
  },
  "dependencies": {
    "unordered-set": "^2.0.1"
  },
  "devDependencies": {
    "standard": "^12.0.1",
    "tape": "^4.9.1"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/mafintosh/shuffled-priority-queue.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/mafintosh/shuffled-priority-queue/issues"
  },
  "homepage": "https://github.com/mafintosh/shuffled-priority-queue"
}
module.exports = class Signal {
  constructor () {
    this._resolve = null
    this._reject = null
    this._promise = null
    this._bind = bind.bind(this)
    this._onerror = clear.bind(this)
    this._onsuccess = clear.bind(this, null)
    this._timers = new Set()
  }

  wait (max) {
    if (!this._promise) {
      this._promise = new Promise(this._bind)
      this._promise.then(this._onsuccess).catch(this._onerror)
    }
    if (max) return this._sleep(max)
    return this._promise
  }

  _sleep (max) {
    const s = new Promise((resolve, reject) => {
      const done = () => {
        this._timers.delete(state)
        resolve(true)
      }
      const id = setTimeout(done, max)
      const state = { id, resolve, reject }
      this._timers.add(state)
    })

    return s
  }

  notify (err) {
    if (!this._promise) return
    const resolve = this._resolve
    const reject = this._reject
    this._promise = null
    if (err) reject(err)
    else resolve(true)
  }
}

function clear (err) {
  for (const { id, resolve, reject } of this._timers) {
    clearTimeout(id)
    if (err) reject(err)
    else resolve(true)
  }
  this._timers.clear()
}

function bind (resolve, reject) {
  this._resolve = resolve
  this._reject = reject
}
{
  "name": "signal-promise",
  "version": "1.0.3",
  "description": "Simple wait/notify promise with optional max wait time",
  "main": "index.js",
  "dependencies": {},
  "devDependencies": {},
  "repository": {
    "type": "git",
    "url": "https://github.com/mafintosh/signal-promise.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/mafintosh/signal-promise/issues"
  },
  "homepage": "https://github.com/mafintosh/signal-promise"
}
var varint = require('varint')
exports.encode = function encode (v, b, o) {
  v = v >= 0 ? v*2 : v*-2 - 1
  var r = varint.encode(v, b, o)
  encode.bytes = varint.encode.bytes
  return r
}
exports.decode = function decode (b, o) {
  var v = varint.decode(b, o)
  decode.bytes = varint.decode.bytes
  return v & 1 ? (v+1) / -2 : v / 2
}

exports.encodingLength = function (v) {
  return varint.encodingLength(v >= 0 ? v*2 : v*-2 - 1)
}
{
  "name": "signed-varint",
  "description": "efficiently store signed integers in varint",
  "version": "2.0.1",
  "homepage": "https://github.com/dominictarr/signed-varint",
  "repository": {
    "type": "git",
    "url": "git://github.com/dominictarr/signed-varint.git"
  },
  "dependencies": {
    "varint": "~5.0.0"
  },
  "devDependencies": {
    "tape": "~2.12.3"
  },
  "scripts": {
    "test": "node test.js"
  },
  "author": "Dominic Tarr <dominic.tarr@gmail.com> (http://dominictarr.com)",
  "license": "MIT"
}
require.addon = require('require-addon')

module.exports = require.addon('.', __filename)
const binding = require('./binding')
const b4a = require('b4a')

function predicate(u8, u16, u32) {
  return function predicate(buf) {
    if (buf.byteLength % 16 !== 0) {
      throw new Error('Buffer length must be a multiple of 16')
    }

    const n = buf.BYTES_PER_ELEMENT

    if (n === 1) return u8(buf)
    if (n === 2) return u16(buf)
    return u32(buf)
  }
}

function unary(u8, u16, u32) {
  return function unary(buf, result = b4a.allocUnsafe(buf.byteLength)) {
    if (buf.byteLength % 16 !== 0) {
      throw new Error('Buffer length must be a multiple of 16')
    }

    if (buf.byteLength !== result.byteLength) {
      throw new Error('Length of result buffer is insufficient')
    }

    const n = buf.BYTES_PER_ELEMENT

    if (n === 1) u8(buf, result)
    else if (n === 2) u16(buf, result)
    else u32(buf, result)

    return result
  }
}

function binary(u8, u16, u32) {
  return function binary(a, b, result = b4a.allocUnsafe(a.byteLength)) {
    if (a.byteLength % 16 !== 0) {
      throw new Error('Buffer length must be a multiple of 16')
    }

    if (a.byteLength !== b.byteLength || a.byteLength !== result.byteLength) {
      throw new Error('Buffers must be the same length')
    }

    const n = a.BYTES_PER_ELEMENT

    if (n === 1) u8(a, b, result)
    else if (n === 2) u16(a, b, result)
    else u32(a, b, result)

    return result
  }
}

function reduce(u8, u16, u32) {
  return function reduce(buf) {
    if (buf.byteLength % 16 !== 0) {
      throw new Error('Buffer length must be a multiple of 16')
    }

    const n = buf.BYTES_PER_ELEMENT

    if (n === 1) return u8(buf)
    if (n === 2) return u16(buf)
    return u32(buf)
  }
}

exports.allo = predicate(
  binding.simdle_native_allo_v128_u8,
  binding.simdle_native_allo_v128_u16,
  binding.simdle_native_allo_v128_u32
)

exports.allz = predicate(
  binding.simdle_native_allz_v128_u8,
  binding.simdle_native_allz_v128_u16,
  binding.simdle_native_allz_v128_u32
)

exports.and = binary(
  binding.simdle_native_and_v128_u8,
  binding.simdle_native_and_v128_u16,
  binding.simdle_native_and_v128_u32
)

exports.clear = binary(
  binding.simdle_native_clear_v128_u8,
  binding.simdle_native_clear_v128_u16,
  binding.simdle_native_clear_v128_u32
)

exports.clo = unary(
  binding.simdle_native_clo_v128_u8,
  binding.simdle_native_clo_v128_u16,
  binding.simdle_native_clo_v128_u32
)

exports.clz = unary(
  binding.simdle_native_clz_v128_u8,
  binding.simdle_native_clz_v128_u16,
  binding.simdle_native_clz_v128_u32
)

exports.cnt = unary(
  binding.simdle_native_cnt_v128_u8,
  binding.simdle_native_cnt_v128_u16,
  binding.simdle_native_cnt_v128_u32
)

exports.cto = unary(
  binding.simdle_native_cto_v128_u8,
  binding.simdle_native_cto_v128_u16,
  binding.simdle_native_cto_v128_u32
)

exports.ctz = unary(
  binding.simdle_native_ctz_v128_u8,
  binding.simdle_native_ctz_v128_u16,
  binding.simdle_native_ctz_v128_u32
)

exports.not = unary(
  binding.simdle_native_not_v128_u8,
  binding.simdle_native_not_v128_u16,
  binding.simdle_native_not_v128_u32
)

exports.or = binary(
  binding.simdle_native_or_v128_u8,
  binding.simdle_native_or_v128_u16,
  binding.simdle_native_or_v128_u32
)

exports.sum = reduce(
  binding.simdle_native_sum_v128_u8,
  binding.simdle_native_sum_v128_u16,
  binding.simdle_native_sum_v128_u32
)

exports.xor = binary(
  binding.simdle_native_xor_v128_u8,
  binding.simdle_native_xor_v128_u16,
  binding.simdle_native_xor_v128_u32
)
{
  "name": "simdle-native",
  "version": "1.3.9",
  "description": "libsimdle JavaScript bindings for Node.js",
  "main": "index.js",
  "files": [
    "index.js",
    "binding.cc",
    "binding.js",
    "CMakeLists.txt",
    "prebuilds"
  ],
  "addon": true,
  "scripts": {
    "format": "prettier --write .",
    "lint": "prettier --check .",
    "test": "npm run lint && npm run test:bare && npm run test:node",
    "test:bare": "bare test.mjs",
    "test:node": "node test.mjs"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/simdle-native.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/simdle-native/issues"
  },
  "homepage": "https://github.com/holepunchto/simdle-native#readme",
  "dependencies": {
    "b4a": "^1.6.0",
    "require-addon": "^1.1.0"
  },
  "devDependencies": {
    "bare-compat-napi": "^1.3.2",
    "brittle": "^3.1.0",
    "cmake-bare": "^1.1.7",
    "cmake-fetch": "^1.1.0",
    "cmake-napi": "^1.0.2",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^1.0.0"
  }
}
const b4a = require('b4a')
const scalar = require('./scalar')

function view (buf, n) {
  if (n === buf.BYTES_PER_ELEMENT) return buf

  let TypedArray

  if (n === 1) TypedArray = Uint8Array
  else if (n === 2) TypedArray = Uint16Array
  else TypedArray = Uint32Array

  return new TypedArray(buf.buffer, buf.byteOffset, buf.byteLength / n)
}

function unary (u8, u16 = u8, u32 = u16) {
  return function unary (buf, result = b4a.allocUnsafe(buf.byteLength)) {
    if (buf.byteLength % 16 !== 0) {
      throw new Error('Buffer length must be a multiple of 16')
    }

    if (buf.byteLength !== result.byteLength) {
      throw new Error('Length of result buffer is insufficient')
    }

    const n = buf.BYTES_PER_ELEMENT

    if (n === 1) u8(buf, view(result, n))
    else if (n === 2) u16(buf, view(result, n))
    else u32(buf, view(result, n))

    return result
  }
}

function binary (u8, u16 = u8, u32 = u16) {
  return function binary (a, b, result = b4a.allocUnsafe(a.byteLength)) {
    if (a.byteLength % 16 !== 0) {
      throw new Error('Buffer length must be a multiple of 16')
    }

    if (a.byteLength !== b.byteLength || a.byteLength !== result.byteLength) {
      throw new Error('Buffers must be the same length')
    }

    const n = a.BYTES_PER_ELEMENT

    if (n === 1) u8(a, b, view(result, n))
    else if (n === 2) u16(a, b, view(result, n))
    else u32(a, b, view(result, n))

    return result
  }
}

function reduce (u8, u16 = u8, u32 = u16) {
  return function reduce (buf) {
    if (buf.byteLength % 16 !== 0) {
      throw new Error('Buffer length must be a multiple of 16')
    }

    const n = buf.BYTES_PER_ELEMENT

    if (n === 1) return u8(buf)
    if (n === 2) return u16(buf)
    return u32(buf)
  }
}

exports.allo = function allo (buf) {
  if (buf.byteLength % 16 !== 0) {
    throw new Error('Buffer length must be a multiple of 16')
  }

  const m = 2 ** (buf.BYTES_PER_ELEMENT * 8) - 1

  for (let i = 0, n = buf.length; i < n; i++) {
    if (buf[i] !== m) return false
  }

  return true
}

exports.allz = function allz (buf) {
  if (buf.byteLength % 16 !== 0) {
    throw new Error('Buffer length must be a multiple of 16')
  }

  for (let i = 0, n = buf.length; i < n; i++) {
    if (buf[i] !== 0) return false
  }

  return true
}

exports.and = binary(
  (a, b, result) => {
    for (let i = 0, n = result.length; i < n; i++) {
      result[i] = a[i] & b[i]
    }
  }
)

exports.clear = binary(
  (a, b, result) => {
    for (let i = 0, n = result.length; i < n; i++) {
      result[i] = a[i] & ~b[i]
    }
  }
)

exports.clo = unary(
  (buf, result) => {
    for (let i = 0, n = buf.length; i < n; i++) {
      result[i] = 24 - scalar.clo(buf[i])
    }
  },
  (buf, result) => {
    for (let i = 0, n = buf.length; i < n; i++) {
      result[i] = 16 - scalar.clo(buf[i])
    }
  },
  (buf, result) => {
    for (let i = 0, n = buf.length; i < n; i++) {
      result[i] = scalar.clo(buf[i])
    }
  }
)

exports.clz = unary(
  (buf, result) => {
    for (let i = 0, n = buf.length; i < n; i++) {
      result[i] = 24 - scalar.clz(buf[i])
    }
  },
  (buf, result) => {
    for (let i = 0, n = buf.length; i < n; i++) {
      result[i] = 16 - scalar.clz(buf[i])
    }
  },
  (buf, result) => {
    for (let i = 0, n = buf.length; i < n; i++) {
      result[i] = scalar.clz(buf[i])
    }
  }
)

exports.cnt = unary(
  (buf, result) => {
    for (let i = 0, n = buf.length; i < n; i++) {
      result[i] = scalar.cnt(buf[i]) & 0xff
    }
  },
  (buf, result) => {
    for (let i = 0, n = buf.length; i < n; i++) {
      result[i] = scalar.cnt(buf[i]) & 0xffff
    }
  },
  (buf, result) => {
    for (let i = 0, n = buf.length; i < n; i++) {
      result[i] = scalar.cnt(buf[i])
    }
  }
)

exports.cto = unary(
  (buf, result) => {
    for (let i = 0, n = buf.length; i < n; i++) {
      result[i] = Math.min(scalar.cto(buf[i]), 8)
    }
  },
  (buf, result) => {
    for (let i = 0, n = buf.length; i < n; i++) {
      result[i] = Math.min(scalar.cto(buf[i]), 16)
    }
  },
  (buf, result) => {
    for (let i = 0, n = buf.length; i < n; i++) {
      result[i] = scalar.cto(buf[i])
    }
  }
)

exports.ctz = unary(
  (buf, result) => {
    for (let i = 0, n = buf.length; i < n; i++) {
      result[i] = Math.min(scalar.ctz(buf[i]), 8)
    }
  },
  (buf, result) => {
    for (let i = 0, n = buf.length; i < n; i++) {
      result[i] = Math.min(scalar.ctz(buf[i]), 16)
    }
  },
  (buf, result) => {
    for (let i = 0, n = buf.length; i < n; i++) {
      result[i] = scalar.ctz(buf[i])
    }
  }
)

exports.not = unary(
  (buf, result) => {
    for (let i = 0, n = buf.length; i < n; i++) {
      result[i] = ~buf[i]
    }
  }
)

exports.or = binary(
  (a, b, result) => {
    for (let i = 0, n = result.length; i < n; i++) {
      result[i] = a[i] | b[i]
    }
  }
)

exports.sum = reduce(
  (buf) => {
    let result = 0n

    for (let i = 0, n = buf.length; i < n; i++) {
      result += BigInt(buf[i])
    }

    return result
  }
)

exports.xor = binary(
  (a, b, result) => {
    for (let i = 0, n = result.length; i < n; i++) {
      result[i] = a[i] ^ b[i]
    }
  }
)
try {
  module.exports = require('simdle-native')
} catch {
  module.exports = require('./fallback')
}
{
  "name": "simdle-universal",
  "version": "1.1.2",
  "description": "Universal wrapper for libsimdle with a JavaScript fallback",
  "main": "index.js",
  "files": [
    "fallback.js",
    "index.js",
    "scalar.js"
  ],
  "browser": {
    "./index.js": "./fallback.js"
  },
  "scripts": {
    "test": "standard && brittle test.mjs"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/simdle-universal.git"
  },
  "author": "Kasper Isager Dalsgarð <kasper@funktionel.co>",
  "license": "ISC",
  "bugs": {
    "url": "https://github.com/holepunchto/simdle-universal/issues"
  },
  "homepage": "https://github.com/holepunchto/simdle-universal#readme",
  "dependencies": {
    "b4a": "^1.6.0"
  },
  "optionalDependencies": {
    "simdle-native": "^1.1.1"
  },
  "devDependencies": {
    "brittle": "^3.1.0",
    "standard": "^17.0.0"
  }
}
const clz = exports.clz = function clz (n) {
  return Math.clz32(n)
}

exports.clo = function clo (n) {
  return clz(~n)
}

const ctz = exports.ctz = function ctz (n) {
  return 32 - (n === 0 ? 0 : (clz(n & -n) + 1))
}

exports.cto = function cto (n) {
  return ctz(~n)
}

exports.cnt = function cnt (n) {
  n = n - ((n >>> 1) & 0x55555555)
  n = (n & 0x33333333) + ((n >>> 2) & 0x33333333)
  n = (n + (n >>> 4)) & 0x0f0f0f0f
  n = (n * 0x01010101) >>> 24
  return n
}
require.addon = require('require-addon')
module.exports = require.addon('.', __filename)
const assert = require('assert')
const binding = require('./binding')
const { isNode } = require('which-runtime')

const OPTIONAL = Buffer.from(new ArrayBuffer(0))

module.exports = exports = { ...binding }

// memory

exports.sodium_memzero = function (buf) {
  assert(ArrayBuffer.isView(buf), 'buf must be a typed array')

  binding.sodium_memzero(buf)
}

exports.sodium_mlock = function (buf) {
  assert(ArrayBuffer.isView(buf), 'buf must be a typed array')

  const res = binding.sodium_mlock(buf)

  if (res !== 0) throw new Error('memory lock failed')
}

exports.sodium_munlock = function (buf) {
  assert(ArrayBuffer.isView(buf), 'buf must be a typed array')

  const res = binding.sodium_munlock(buf)

  if (res !== 0) throw new Error('memory unlock failed')
}

exports.sodium_malloc = function (size) {
  assert(size >= 0, 'invalid size')

  const buf = Buffer.from(binding.sodium_malloc(size))
  buf.secure = true
  return buf
}

exports.sodium_free = function (buf) {
  if (!buf || !buf.secure) return

  binding.sodium_free(buf.buffer)
}

exports.sodium_mprotect_noaccess = function (buf) {
  const res = binding.sodium_mprotect_noaccess(buf.buffer)

  if (res !== 0) throw new Error('failed to lock buffer')
}

exports.sodium_mprotect_readonly = function (buf) {
  const res = binding.sodium_mprotect_readonly(buf.buffer)

  if (res !== 0) throw new Error('failed to unlock buffer')
}

exports.sodium_mprotect_readwrite = function (buf) {
  const res = binding.sodium_mprotect_readwrite(buf.buffer)

  if (res !== 0) throw new Error('failed to unlock buffer')
}

// crypto_randombytes

exports.randombytes_buf = function (buffer) {
  assert(ArrayBuffer.isView(buffer), 'buffer must be a typed array')

  binding.randombytes_buf(buffer.buffer, buffer.byteOffset, buffer.byteLength)
}

exports.randombytes_buf_deterministic = function (buffer, seed) {
  assert(ArrayBuffer.isView(buffer), 'buffer must be a typed array')
  assert(ArrayBuffer.isView(seed), 'seed must be a typed array')
  assert(
    seed.byteLength === binding.randombytes_SEEDBYTES,
    "seed must be 'randombytes_SEEDBYTES' bytes"
  )

  binding.randombytes_buf_deterministic(
    buffer.buffer,
    buffer.byteOffset,
    buffer.byteLength,

    seed.buffer,
    seed.byteOffset,
    seed.byteLength
  )
}

// sodium_helpers

exports.sodium_memcmp = function (a, b) {
  assert(ArrayBuffer.isView(a), 'a must be a typed array')
  assert(ArrayBuffer.isView(b), 'b must be a typed array')
  assert(a.byteLength === b.byteLength, 'buffers must be of same length')

  return binding.sodium_memcmp(a, b)
}

exports.sodium_add = function (a, b) {
  assert(ArrayBuffer.isView(a), 'a must be a typed array')
  assert(ArrayBuffer.isView(b), 'b must be a typed array')
  assert(a.byteLength === b.byteLength, 'buffers must be of same length')

  binding.sodium_add(a, b)
}

exports.sodium_sub = function (a, b) {
  assert(ArrayBuffer.isView(a), 'a must be a typed array')
  assert(ArrayBuffer.isView(b), 'b must be a typed array')
  assert(a.byteLength === b.byteLength, 'buffers must be of same length')

  binding.sodium_sub(a, b)
}

exports.sodium_compare = function (a, b) {
  assert(ArrayBuffer.isView(a), 'a must be a typed array')
  assert(ArrayBuffer.isView(b), 'b must be a typed array')
  assert(a.byteLength === b.byteLength, 'buffers must be of same length')

  return binding.sodium_compare(a, b)
}

exports.sodium_is_zero = function (buffer, length) {
  if (length === undefined) length = buffer.byteLength

  assert(ArrayBuffer.isView(buffer), 'buffer must be a typed array')
  assert(length >= 0 && length <= buffer.byteLength, 'invalid length')

  return binding.sodium_is_zero(buffer, length)
}

exports.sodium_pad = function (buffer, unpaddedBuflen, blockSize) {
  assert(ArrayBuffer.isView(buffer), 'buffer must be a typed array')
  assert(unpaddedBuflen <= buffer.byteLength, 'unpadded length cannot exceed buffer length')
  assert(blockSize <= buffer.byteLength, 'block size cannot exceed buffer length')
  assert(blockSize >= 1, 'block size must be at least 1 byte')
  assert(
    buffer.byteLength >= unpaddedBuflen + (blockSize - (unpaddedBuflen % blockSize)),
    'buf not long enough'
  )

  return binding.sodium_pad(buffer, unpaddedBuflen, blockSize)
}

exports.sodium_unpad = function (buffer, paddedBuflen, blockSize) {
  assert(ArrayBuffer.isView(buffer), 'buffer must be a typed array')
  assert(paddedBuflen <= buffer.byteLength, 'unpadded length cannot exceed buffer length')
  assert(blockSize <= buffer.byteLength, 'block size cannot exceed buffer length')
  assert(blockSize >= 1, 'block size must be at least 1 byte')

  return binding.sodium_unpad(buffer, paddedBuflen, blockSize)
}

// crypto_sign

exports.crypto_sign_keypair = function (pk, sk) {
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(
    pk.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
    "pk must be 'crypto_sign_PUBLICKEYBYTES' bytes"
  )
  assert(
    sk.byteLength === binding.crypto_sign_SECRETKEYBYTES,
    "sk must be 'crypto_sign_SECRETKEYBYTES' bytes"
  )

  const res = binding.crypto_sign_keypair(pk, sk)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_sign_seed_keypair = function (pk, sk, seed) {
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(ArrayBuffer.isView(seed), 'seed must be a typed array')
  assert(
    pk.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
    "pk must be 'crypto_sign_PUBLICKEYBYTES' bytes"
  )
  assert(
    sk.byteLength === binding.crypto_sign_SECRETKEYBYTES,
    "sk must be 'crypto_sign_SECRETKEYBYTES' bytes"
  )
  assert(
    seed.byteLength === binding.crypto_sign_SEEDBYTES,
    "seed must be 'crypto_sign_SEEDBYTES' bytes"
  )

  const res = binding.crypto_sign_seed_keypair(pk, sk, seed)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_sign = function (sm, m, sk) {
  assert(ArrayBuffer.isView(sm), 'sm must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(
    sm.byteLength === binding.crypto_sign_BYTES + m.byteLength,
    "sm must be 'm.byteLength + crypto_sign_BYTES' bytes"
  )
  assert(
    sk.byteLength === binding.crypto_sign_SECRETKEYBYTES,
    "sk must be 'crypto_sign_SECRETKEYBYTES' bytes"
  )

  const res = binding.crypto_sign(sm, m, sk)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_sign_open = function (m, sm, pk) {
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(sm), 'sm must be a typed array')
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(
    sm.byteLength >= binding.crypto_sign_BYTES,
    "sm must be at least 'crypto_sign_BYTES' bytes"
  )
  assert(
    m.byteLength === sm.byteLength - binding.crypto_sign_BYTES,
    "m must be 'sm.byteLength - crypto_sign_BYTES' bytes"
  )
  assert(
    pk.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
    "pk must be 'crypto_sign_PUBLICKEYBYTES' bytes"
  )

  const res = binding.crypto_sign_open(m, sm, pk)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_sign_open = function (m, sm, pk) {
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(sm), 'sm must be a typed array')
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(
    sm.byteLength >= binding.crypto_sign_BYTES,
    "sm must be at least 'crypto_sign_BYTES' bytes"
  )
  assert(
    m.byteLength === sm.byteLength - binding.crypto_sign_BYTES,
    "m must be 'sm.byteLength - crypto_sign_BYTES' bytes"
  )
  assert(
    pk.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
    "pk must be 'crypto_sign_PUBLICKEYBYTES' bytes"
  )

  return binding.crypto_sign_open(m, sm, pk)
}

exports.crypto_sign_detached = function (sig, m, sk) {
  assert(ArrayBuffer.isView(sig), 'sig must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(sig.byteLength === binding.crypto_sign_BYTES, "sig must be 'crypto_sign_BYTES' bytes")
  assert(
    sk.byteLength === binding.crypto_sign_SECRETKEYBYTES,
    "sk must be 'crypto_sign_SECRETKEYBYTES' bytes"
  )

  const res = binding.crypto_sign_detached(sig, m, sk)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_sign_verify_detached = function (sig, m, pk) {
  assert(ArrayBuffer.isView(sig), 'sig must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(
    sig.byteLength >= binding.crypto_sign_BYTES,
    "sig must be at least 'crypto_sign_BYTES' bytes"
  )
  assert(
    pk.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
    "pk must be 'crypto_sign_PUBLICKEYBYTES' bytes"
  )

  return binding.crypto_sign_verify_detached(
    sig.buffer,
    sig.byteOffset,
    sig.byteLength,

    m.buffer,
    m.byteOffset,
    m.byteLength,

    pk.buffer,
    pk.byteOffset,
    pk.byteLength
  )
}

exports.crypto_sign_ed25519_sk_to_pk = function (pk, sk) {
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(
    pk.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
    "pk must be 'crypto_sign_PUBLICKEYBYTES' bytes"
  )
  assert(
    sk.byteLength === binding.crypto_sign_SECRETKEYBYTES,
    "sk must be 'crypto_sign_SECRETKEYBYTES' bytes"
  )

  const res = binding.crypto_sign_ed25519_sk_to_pk(pk, sk)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_sign_ed25519_pk_to_curve25519 = function (x25519pk, ed25519pk) {
  assert(ArrayBuffer.isView(x25519pk), 'x25519pk must be a typed array')
  assert(ArrayBuffer.isView(ed25519pk), 'ed25519pk must be a typed array')
  assert(
    x25519pk.byteLength === binding.crypto_box_PUBLICKEYBYTES,
    "x25519pk must be 'crypto_box_PUBLICKEYBYTES' bytes"
  )
  assert(
    ed25519pk.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
    "ed25519pk must be 'crypto_sign_PUBLICKEYBYTES' bytes"
  )

  const res = binding.crypto_sign_ed25519_pk_to_curve25519(x25519pk, ed25519pk)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_sign_ed25519_sk_to_curve25519 = function (x25519sk, ed25519sk) {
  assert(ArrayBuffer.isView(x25519sk), 'x25519sk must be a typed array')
  assert(ArrayBuffer.isView(ed25519sk), 'ed25519sk must be a typed array')
  assert(
    x25519sk.byteLength === binding.crypto_box_SECRETKEYBYTES,
    "x25519sk must be 'crypto_box_SECRETKEYBYTES' bytes"
  )

  const edLen = ed25519sk.byteLength

  assert(
    edLen === binding.crypto_sign_SECRETKEYBYTES || edLen === binding.crypto_box_SECRETKEYBYTES,
    "ed25519sk must be 'crypto_sign_SECRETKEYBYTES' or 'crypto_sign_SECRETKEYBYTES - crypto_sign_PUBLICKEYBYTES' bytes"
  )

  const res = binding.crypto_sign_ed25519_sk_to_curve25519(x25519sk, ed25519sk)

  if (res !== 0) throw new Error('status: ' + res)
}

// crypto_box

exports.crypto_box_keypair = function (pk, sk) {
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(
    pk.byteLength === binding.crypto_box_PUBLICKEYBYTES,
    "pk must be 'crypto_box_PUBLICKEYBYTES' bytes"
  )
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(
    sk.byteLength === binding.crypto_box_SECRETKEYBYTES,
    "sk must be 'crypto_box_SECRETKEYBYTES' bytes"
  )

  const res = binding.crypto_box_keypair(pk, sk)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_box_seed_keypair = function (pk, sk, seed) {
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(
    pk.byteLength === binding.crypto_box_PUBLICKEYBYTES,
    "pk must be 'crypto_box_PUBLICKEYBYTES' bytes"
  )
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(
    sk.byteLength === binding.crypto_box_SECRETKEYBYTES,
    "sk must be 'crypto_box_SECRETKEYBYTES' bytes"
  )
  assert(ArrayBuffer.isView(seed), 'seed must be a typed array')
  assert(
    seed.byteLength === binding.crypto_box_SEEDBYTES,
    "seed must be 'crypto_box_SEEDBYTES' bytes"
  )

  const res = binding.crypto_box_seed_keypair(pk, sk, seed)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_box_easy = function (c, m, n, pk, sk) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(
    c.byteLength === m.byteLength + exports.crypto_box_MACBYTES,
    "c must be 'm.byteLength + crypto_box_MACBYTES' bytes"
  )
  assert(n.byteLength === exports.crypto_box_NONCEBYTES, "n must be 'crypto_box_NONCEBYTES' bytes")
  assert(
    pk.byteLength === exports.crypto_box_PUBLICKEYBYTES,
    "pk must be 'crypto_box_PUBLICKEYBYTES' bytes"
  )
  assert(
    sk.byteLength === exports.crypto_box_SECRETKEYBYTES,
    "sk must be 'crypto_box_SECRETKEYBYTES' bytes"
  )

  const res = binding.crypto_box_easy(c, m, n, pk, sk)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_box_detached = function (c, mac, m, n, pk, sk) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(mac), 'mac must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(mac.byteLength === exports.crypto_box_MACBYTES, "mac must be 'crypto_box_MACBYTES' bytes")
  assert(n.byteLength === exports.crypto_box_NONCEBYTES, "n must be 'crypto_box_NONCEBYTES' bytes")
  assert(
    pk.byteLength === exports.crypto_box_PUBLICKEYBYTES,
    "pk must be 'crypto_box_PUBLICKEYBYTES' bytes"
  )
  assert(
    sk.byteLength === exports.crypto_box_SECRETKEYBYTES,
    "sk must be 'crypto_box_SECRETKEYBYTES' bytes"
  )

  const res = binding.crypto_box_detached(c, mac, m, n, pk, sk)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_box_open_easy = function (m, c, n, pk, sk) {
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(
    c.byteLength >= exports.crypto_box_MACBYTES,
    "c must be at least 'crypto_box_MACBYTES' bytes"
  )
  assert(
    m.byteLength === c.byteLength - exports.crypto_box_MACBYTES,
    "m must be 'c.byteLength - crypto_box_MACBYTES' bytes"
  )
  assert(n.byteLength === exports.crypto_box_NONCEBYTES, "n must be 'crypto_box_NONCEBYTES' bytes")
  assert(
    pk.byteLength === exports.crypto_box_PUBLICKEYBYTES,
    "pk must be 'crypto_box_PUBLICKEYBYTES' bytes"
  )
  assert(
    sk.byteLength === exports.crypto_box_SECRETKEYBYTES,
    "sk must be 'crypto_box_SECRETKEYBYTES' bytes"
  )

  return binding.crypto_box_open_easy(m, c, n, pk, sk)
}

exports.crypto_box_open_detached = function (m, c, mac, n, pk, sk) {
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(mac), 'mac must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(m.byteLength === c.byteLength, "m must be 'c.byteLength' bytes")
  assert(mac.byteLength === exports.crypto_box_MACBYTES, "mac must be 'crypto_box_MACBYTES' bytes")
  assert(n.byteLength === exports.crypto_box_NONCEBYTES, "n must be 'crypto_box_NONCEBYTES' bytes")
  assert(
    pk.byteLength === exports.crypto_box_PUBLICKEYBYTES,
    "pk must be 'crypto_box_PUBLICKEYBYTES' bytes"
  )
  assert(
    sk.byteLength === exports.crypto_box_SECRETKEYBYTES,
    "sk must be 'crypto_box_SECRETKEYBYTES' bytes"
  )

  return binding.crypto_box_open_detached(m, c, mac, n, pk, sk)
}

exports.crypto_box_seal = function (c, m, pk) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(
    c.byteLength === m.byteLength + exports.crypto_box_SEALBYTES,
    "c must be 'm.byteLength + crypto_box_SEALBYTES' bytes"
  )
  assert(
    pk.byteLength === exports.crypto_box_PUBLICKEYBYTES,
    "pk must be 'crypto_box_PUBLICKEYBYTES' bytes"
  )

  const res = binding.crypto_box_seal(c, m, pk)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_box_seal_open = function (m, c, pk, sk) {
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(
    c.byteLength >= exports.crypto_box_SEALBYTES,
    "c must be at least 'crypto_box_SEALBYTES' bytes"
  )
  assert(
    m.byteLength === c.byteLength - exports.crypto_box_SEALBYTES,
    "m must be 'c.byteLength - crypto_box_SEALBYTES' bytes"
  )
  assert(
    pk.byteLength === exports.crypto_box_PUBLICKEYBYTES,
    "pk must be 'crypto_box_PUBLICKEYBYTES' bytes"
  )
  assert(
    sk.byteLength === exports.crypto_box_SECRETKEYBYTES,
    "sk must be 'crypto_box_SECRETKEYBYTES' bytes"
  )

  return binding.crypto_box_seal_open(
    m.buffer,
    m.byteOffset,
    m.byteLength,

    c.buffer,
    c.byteOffset,
    c.byteLength,

    pk.buffer,
    pk.byteOffset,
    pk.byteLength,

    sk.buffer,
    sk.byteOffset,
    sk.byteLength
  )
}

// crypto_secretbox

exports.crypto_secretbox_easy = function (c, m, n, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    c.byteLength === m.byteLength + binding.crypto_secretbox_MACBYTES,
    "c must be 'm.byteLength + crypto_secretbox_MACBYTES' bytes"
  )
  assert(
    n.byteLength === binding.crypto_secretbox_NONCEBYTES,
    "n must be 'crypto_secretbox_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_secretbox_KEYBYTES,
    "k must be 'crypto_secretbox_KEYBYTES' bytes"
  )

  const res = binding.crypto_secretbox_easy(c, m, n, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_secretbox_open_easy = function (m, c, n, k) {
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    c.byteLength >= binding.crypto_secretbox_MACBYTES,
    "c must be at least 'crypto_secretbox_MACBYTES' bytes"
  )
  assert(
    m.byteLength === c.byteLength - binding.crypto_secretbox_MACBYTES,
    "m must be 'c.byteLength - crypto_secretbox_MACBYTES' bytes"
  )
  assert(
    n.byteLength === binding.crypto_secretbox_NONCEBYTES,
    "n must be 'crypto_secretbox_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_secretbox_KEYBYTES,
    "k must be 'crypto_secretbox_KEYBYTES' bytes"
  )

  return binding.crypto_secretbox_open_easy(m, c, n, k)
}

exports.crypto_secretbox_detached = function (c, mac, m, n, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(mac), 'mac must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(
    mac.byteLength === binding.crypto_secretbox_MACBYTES,
    "mac must be 'crypto_secretbox_MACBYTES' bytes"
  )
  assert(
    n.byteLength === binding.crypto_secretbox_NONCEBYTES,
    "n must be 'crypto_secretbox_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_secretbox_KEYBYTES,
    "k must be 'crypto_secretbox_KEYBYTES' bytes"
  )

  const res = binding.crypto_secretbox_detached(c, mac, m, n, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_secretbox_open_detached = function (m, c, mac, n, k) {
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(mac), 'mac must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(m.byteLength === c.byteLength, "m must be 'c.byteLength' bytes")
  assert(
    mac.byteLength === binding.crypto_secretbox_MACBYTES,
    "mac must be 'crypto_secretbox_MACBYTES' bytes"
  )
  assert(
    n.byteLength === binding.crypto_secretbox_NONCEBYTES,
    "n must be 'crypto_secretbox_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_secretbox_KEYBYTES,
    "k must be 'crypto_secretbox_KEYBYTES' bytes"
  )

  return binding.crypto_secretbox_open_detached(m, c, mac, n, k)
}

// crypto_generichash

exports.crypto_generichash = function (output, input, key) {
  if (!key) key = OPTIONAL

  assert(ArrayBuffer.isView(output), 'output must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(
    output.byteLength >= binding.crypto_generichash_BYTES_MIN &&
      output.byteLength <= binding.crypto_generichash_BYTES_MAX,
    'output must be between crypto_generichash_BYTES_MIN and crypto_generichash_BYTES_MAX bytes'
  )

  if (key !== OPTIONAL) {
    assert(ArrayBuffer.isView(key), 'key must be a typed array')
    assert(
      key.byteLength >= binding.crypto_generichash_KEYBYTES_MIN &&
        key.byteLength <= binding.crypto_generichash_KEYBYTES_MAX,
      'key must be between crypto_generichash_KEYBYTES_MIN and crypto_generichash_KEYBYTES_MAX bytes'
    )
  }

  const res = binding.crypto_generichash(
    output.buffer,
    output.byteOffset,
    output.byteLength,

    input.buffer,
    input.byteOffset,
    input.byteLength,

    key.buffer,
    key.byteOffset,
    key.byteLength
  )

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_generichash_batch = function (output, batch, key) {
  assert(ArrayBuffer.isView(output), 'output must be a typed array')

  if (isNode || batch.length < 4) {
    const res = binding.crypto_generichash_batch(output, batch, !!key, key || OPTIONAL)
    if (res !== 0) throw new Error('status: ' + res)
  } else {
    const state = Buffer.alloc(binding.crypto_generichash_STATEBYTES)

    exports.crypto_generichash_init(state, key, output.byteLength)

    for (const buf of batch) {
      exports.crypto_generichash_update(state, buf)
    }

    exports.crypto_generichash_final(state, output)
  }
}

exports.crypto_generichash_keygen = function (key) {
  assert(ArrayBuffer.isView(key), 'key must be a typed array')
  assert(
    key.byteLength === binding.crypto_generichash_KEYBYTES,
    "key must be 'crypto_generichash_KEYBYTES' bytes"
  )

  const res = binding.crypto_generichash_keygen(key.buffer, key.byteOffset, key.byteLength)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_generichash_init = function (state, key, outputLength) {
  if (!key) key = OPTIONAL

  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_generichash_STATEBYTES,
    "state must be 'crypto_generichash_STATEBYTES' bytes"
  )

  const res = binding.crypto_generichash_init(
    state.buffer,
    state.byteOffset,
    state.byteLength,

    key.buffer,
    key.byteOffset,
    key.byteLength,

    outputLength
  )

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_generichash_update = function (state, input) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(
    state.byteLength === binding.crypto_generichash_STATEBYTES,
    "state must be 'crypto_generichash_STATEBYTES' bytes"
  )

  const res = binding.crypto_generichash_update(
    state.buffer,
    state.byteOffset,
    state.byteLength,

    input.buffer,
    input.byteOffset,
    input.byteLength
  )

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_generichash_final = function (state, output) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(ArrayBuffer.isView(output), 'output must be a typed array')
  assert(
    state.byteLength === binding.crypto_generichash_STATEBYTES,
    "state must be 'crypto_generichash_STATEBYTES' bytes"
  )

  const res = binding.crypto_generichash_final(
    state.buffer,
    state.byteOffset,
    state.byteLength,

    output.buffer,
    output.byteOffset,
    output.byteLength
  )

  if (res !== 0) throw new Error('status: ' + res)
}

// secretstream

exports.crypto_secretstream_xchacha20poly1305_keygen = function (k) {
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_secretstream_xchacha20poly1305_KEYBYTES,
    "k must be 'crypto_secretstream_xchacha20poly1305_KEYBYTES' bytes"
  )

  binding.crypto_secretstream_xchacha20poly1305_keygen(k.buffer, k.byteOffset, k.byteLength)
}

exports.crypto_secretstream_xchacha20poly1305_init_push = function (state, header, k) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(ArrayBuffer.isView(header), 'header must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    state.byteLength === binding.crypto_secretstream_xchacha20poly1305_STATEBYTES,
    "state must be 'crypto_secretstream_xchacha20poly1305_STATEBYTES' bytes"
  )
  assert(
    header.byteLength === binding.crypto_secretstream_xchacha20poly1305_HEADERBYTES,
    "header must be 'crypto_secretstream_xchacha20poly1305_HEADERBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_secretstream_xchacha20poly1305_KEYBYTES,
    "k must be 'crypto_secretstream_xchacha20poly1305_KEYBYTES' bytes"
  )

  const res = binding.crypto_secretstream_xchacha20poly1305_init_push(
    state.buffer,
    state.byteOffset,
    state.byteLength,

    header.buffer,
    header.byteOffset,
    header.byteLength,

    k.buffer,
    k.byteOffset,
    k.byteLength
  )

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_secretstream_xchacha20poly1305_init_pull = function (state, header, k) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(ArrayBuffer.isView(header), 'header must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    state.byteLength === binding.crypto_secretstream_xchacha20poly1305_STATEBYTES,
    "state must be 'crypto_secretstream_xchacha20poly1305_STATEBYTES' bytes"
  )
  assert(
    header.byteLength === binding.crypto_secretstream_xchacha20poly1305_HEADERBYTES,
    "header must be 'crypto_secretstream_xchacha20poly1305_HEADERBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_secretstream_xchacha20poly1305_KEYBYTES,
    "k must be 'crypto_secretstream_xchacha20poly1305_KEYBYTES' bytes"
  )

  const res = binding.crypto_secretstream_xchacha20poly1305_init_pull(
    state.buffer,
    state.byteOffset,
    state.byteLength,

    header.buffer,
    header.byteOffset,
    header.byteLength,

    k.buffer,
    k.byteOffset,
    k.byteLength
  )

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_secretstream_xchacha20poly1305_push = function (state, c, m, ad, tag) {
  if (!ad) ad = OPTIONAL

  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(
    state.byteLength === binding.crypto_secretstream_xchacha20poly1305_STATEBYTES,
    "state must be 'crypto_secretstream_xchacha20poly1305_STATEBYTES' bytes"
  )
  assert(
    c.byteLength === m.byteLength + binding.crypto_secretstream_xchacha20poly1305_ABYTES,
    "c must be 'm.byteLength + crypto_secretstream_xchacha20poly1305_ABYTES' bytes"
  )

  const res = binding.crypto_secretstream_xchacha20poly1305_push(
    state.buffer,
    state.byteOffset,
    state.byteLength,

    c.buffer,
    c.byteOffset,
    c.byteLength,

    m.buffer,
    m.byteOffset,
    m.byteLength,

    ad.buffer,
    ad.byteOffset,
    ad.byteLength,

    tag
  )

  if (res < 0) throw new Error('push failed')

  return res
}

exports.crypto_secretstream_xchacha20poly1305_pull = function (state, m, tag, c, ad) {
  if (!ad) ad = OPTIONAL

  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_secretstream_xchacha20poly1305_STATEBYTES,
    "state must be 'crypto_secretstream_xchacha20poly1305_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(tag), 'tag must be a typed array')
  assert(tag.byteLength === 1, 'tag must be 1 byte')

  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(
    c.byteLength >= binding.crypto_secretstream_xchacha20poly1305_ABYTES,
    "c must be at least 'crypto_secretstream_xchacha20poly1305_ABYTES' bytes"
  )
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(
    m.byteLength === c.byteLength - binding.crypto_secretstream_xchacha20poly1305_ABYTES,
    "m must be 'c.byteLength - crypto_secretstream_xchacha20poly1305_ABYTES' bytes"
  )

  const res = binding.crypto_secretstream_xchacha20poly1305_pull(
    state.buffer,
    state.byteOffset,
    state.byteLength,

    m.buffer,
    m.byteOffset,
    m.byteLength,

    tag.buffer,
    tag.byteOffset,
    tag.byteLength,

    c.buffer,
    c.byteOffset,
    c.byteLength,

    ad.buffer,
    ad.byteOffset,
    ad.byteLength
  )

  if (res < 0) throw new Error('pull failed')

  return res
}

exports.crypto_secretstream_xchacha20poly1305_rekey = function (state) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_secretstream_xchacha20poly1305_STATEBYTES,
    "state must be 'crypto_secretstream_xchacha20poly1305_STATEBYTES' bytes"
  )

  binding.crypto_secretstream_xchacha20poly1305_rekey(
    state.buffer,
    state.byteOffset,
    state.byteLength
  )
}

// crypto_stream

exports.crypto_stream = function (c, n, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    n.byteLength === binding.crypto_stream_NONCEBYTES,
    "n must be 'crypto_stream_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_KEYBYTES,
    "k must be 'crypto_stream_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream(c, n, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_xor = function (c, m, n, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(
    n.byteLength === binding.crypto_stream_NONCEBYTES,
    "n must be 'crypto_stream_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_KEYBYTES,
    "k must be 'crypto_stream_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_xor(
    c.buffer,
    c.byteOffset,
    c.byteLength,

    m.buffer,
    m.byteOffset,
    m.byteLength,

    n.buffer,
    n.byteOffset,
    n.byteLength,

    k.buffer,
    k.byteOffset,
    k.byteLength
  )

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_chacha20 = function (c, n, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    n.byteLength === binding.crypto_stream_chacha20_NONCEBYTES,
    "n must be 'crypto_stream_chacha20_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_chacha20_KEYBYTES,
    "k must be 'crypto_stream_chacha20_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_chacha20(c, n, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_chacha20_xor = function (c, m, n, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(
    n.byteLength === binding.crypto_stream_chacha20_NONCEBYTES,
    "n must be 'crypto_stream_chacha20_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_chacha20_KEYBYTES,
    "k must be 'crypto_stream_chacha20_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_chacha20_xor(c, m, n, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_chacha20_xor_ic = function (c, m, n, ic, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(
    n.byteLength === binding.crypto_stream_chacha20_NONCEBYTES,
    "n must be 'crypto_stream_chacha20_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_chacha20_KEYBYTES,
    "k must be 'crypto_stream_chacha20_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_chacha20_xor_ic(c, m, n, ic, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_chacha20_ietf = function (c, n, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    n.byteLength === binding.crypto_stream_chacha20_ietf_NONCEBYTES,
    "n must be 'crypto_stream_chacha20_ietf_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_chacha20_ietf_KEYBYTES,
    "k must be 'crypto_stream_chacha20_ietf_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_chacha20_ietf(c, n, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_chacha20_ietf_xor = function (c, m, n, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(
    n.byteLength === binding.crypto_stream_chacha20_ietf_NONCEBYTES,
    "n must be 'crypto_stream_chacha20_ietf_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_chacha20_ietf_KEYBYTES,
    "k must be 'crypto_stream_chacha20_ietf_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_chacha20_ietf_xor(c, m, n, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_chacha20_ietf_xor_ic = function (c, m, n, ic, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(
    n.byteLength === binding.crypto_stream_chacha20_ietf_NONCEBYTES,
    "n must be 'crypto_stream_chacha20_ietf_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_chacha20_ietf_KEYBYTES,
    "k must be 'crypto_stream_chacha20_ietf_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_chacha20_ietf_xor_ic(c, m, n, ic, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_xchacha20 = function (c, n, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    n.byteLength === binding.crypto_stream_xchacha20_NONCEBYTES,
    "n must be 'crypto_stream_xchacha20_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_xchacha20_KEYBYTES,
    "k must be 'crypto_stream_xchacha20_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_xchacha20(c, n, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_xchacha20_xor = function (c, m, n, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(
    n.byteLength === binding.crypto_stream_xchacha20_NONCEBYTES,
    "n must be 'crypto_stream_xchacha20_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_xchacha20_KEYBYTES,
    "k must be 'crypto_stream_xchacha20_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_xchacha20_xor(c, m, n, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_xchacha20_xor_ic = function (c, m, n, ic, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(
    n.byteLength === binding.crypto_stream_xchacha20_NONCEBYTES,
    "n must be 'crypto_stream_xchacha20_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_xchacha20_KEYBYTES,
    "k must be 'crypto_stream_xchacha20_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_xchacha20_xor_ic(c, m, n, ic, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_salsa20 = function (c, n, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    n.byteLength === binding.crypto_stream_salsa20_NONCEBYTES,
    "n must be 'crypto_stream_salsa20_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_salsa20_KEYBYTES,
    "k must be 'crypto_stream_salsa20_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_salsa20(c, n, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_salsa20_xor = function (c, m, n, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(
    n.byteLength === binding.crypto_stream_salsa20_NONCEBYTES,
    "n must be 'crypto_stream_salsa20_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_salsa20_KEYBYTES,
    "k must be 'crypto_stream_salsa20_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_salsa20_xor(c, m, n, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_salsa20_xor_ic = function (c, m, n, ic, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(
    n.byteLength === binding.crypto_stream_salsa20_NONCEBYTES,
    "n must be 'crypto_stream_salsa20_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_salsa20_KEYBYTES,
    "k must be 'crypto_stream_salsa20_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_salsa20_xor_ic(c, m, n, ic, k)

  if (res !== 0) throw new Error('status: ' + res)
}

// crypto_auth

exports.crypto_auth = function (out, input, k) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(out.byteLength === binding.crypto_auth_BYTES, "out must be 'crypto_auth_BYTES' bytes")
  assert(k.byteLength === binding.crypto_auth_KEYBYTES, "k must be 'crypto_auth_KEYBYTES' bytes")

  const res = binding.crypto_auth(out, input, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_auth_verify = function (h, input, k) {
  assert(ArrayBuffer.isView(h), 'h must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(h.byteLength === binding.crypto_auth_BYTES, "h must be 'crypto_auth_BYTES' bytes")
  assert(k.byteLength === binding.crypto_auth_KEYBYTES, "k must be 'crypto_auth_KEYBYTES' bytes")

  return binding.crypto_auth_verify(h, input, k)
}

// crypto_onetimeauth

exports.crypto_onetimeauth = function (out, input, k) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    out.byteLength === binding.crypto_onetimeauth_BYTES,
    "out must be 'crypto_onetimeauth_BYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_onetimeauth_KEYBYTES,
    "k must be 'crypto_onetimeauth_KEYBYTES' bytes"
  )

  const res = binding.crypto_onetimeauth(out, input, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_onetimeauth_init = function (state, k) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    state.byteLength === binding.crypto_onetimeauth_STATEBYTES,
    "state must be 'crypto_onetimeauth_STATEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_onetimeauth_KEYBYTES,
    "k must be 'crypto_onetimeauth_KEYBYTES' bytes"
  )

  const res = binding.crypto_onetimeauth_init(state, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_onetimeauth_update = function (state, input) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(
    state.byteLength === binding.crypto_onetimeauth_STATEBYTES,
    "state must be 'crypto_onetimeauth_STATEBYTES' bytes"
  )

  const res = binding.crypto_onetimeauth_update(state, input)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_onetimeauth_final = function (state, out) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(
    state.byteLength === binding.crypto_onetimeauth_STATEBYTES,
    "state must be 'crypto_onetimeauth_STATEBYTES' bytes"
  )
  assert(
    out.byteLength === binding.crypto_onetimeauth_BYTES,
    "out must be 'crypto_onetimeauth_BYTES' bytes"
  )

  const res = binding.crypto_onetimeauth_final(state, out)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_onetimeauth_verify = function (h, input, k) {
  assert(ArrayBuffer.isView(h), 'h must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    h.byteLength === binding.crypto_onetimeauth_BYTES,
    "h must be 'crypto_onetimeauth_BYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_onetimeauth_KEYBYTES,
    "k must be 'crypto_onetimeauth_KEYBYTES' bytes"
  )

  return binding.crypto_onetimeauth_verify(h, input, k)
}

// crypto_pwhash

exports.crypto_pwhash = function (out, passwd, salt, opslimit, memlimit, alg) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(ArrayBuffer.isView(salt), 'salt must be a typed array')
  assert(
    out.byteLength >= binding.crypto_pwhash_BYTES_MIN,
    "out must be at least 'crypto_pwhash_BYTES_MIN' bytes"
  )
  assert(
    out.byteLength <= binding.crypto_pwhash_BYTES_MAX,
    "out must be at most 'crypto_pwhash_BYTES_MAX' bytes"
  )
  assert(
    salt.byteLength === binding.crypto_pwhash_SALTBYTES,
    "salt must be 'crypto_pwhash_SALTBYTES' bytes"
  )
  assert(
    opslimit >= binding.crypto_pwhash_OPSLIMIT_MIN,
    "opslimit must be at least 'crypto_pwhash_OPSLIMIT_MIN'"
  )
  assert(
    opslimit <= binding.crypto_pwhash_OPSLIMIT_MAX,
    "opslimit must be at most 'crypto_pwhash_OPSLIMIT_MAX'"
  )
  assert(
    memlimit >= binding.crypto_pwhash_MEMLIMIT_MIN,
    "memlimit must be at least 'crypto_pwhash_MEMLIMIT_MIN'"
  )
  assert(
    memlimit <= binding.crypto_pwhash_MEMLIMIT_MAX,
    "memlimit must be at most 'crypto_pwhash_MEMLIMIT_MAX'"
  )
  assert(alg >= 1 && alg <= 2, 'alg must be either Argon2i 1.3 or Argon2id 1.3')

  const res = binding.crypto_pwhash(out, passwd, salt, opslimit, memlimit, alg)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_pwhash_async = function (
  out,
  passwd,
  salt,
  opslimit,
  memlimit,
  alg,
  callback = undefined
) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(ArrayBuffer.isView(salt), 'salt must be a typed array')
  assert(
    out.byteLength >= binding.crypto_pwhash_BYTES_MIN,
    "out must be at least 'crypto_pwhash_BYTES_MIN' bytes"
  )
  assert(
    out.byteLength <= binding.crypto_pwhash_BYTES_MAX,
    "out must be at most 'crypto_pwhash_BYTES_MAX' bytes"
  )
  assert(
    salt.byteLength === binding.crypto_pwhash_SALTBYTES,
    "salt must be 'crypto_pwhash_SALTBYTES' bytes"
  )
  assert(
    opslimit >= binding.crypto_pwhash_OPSLIMIT_MIN,
    "opslimit must be at least 'crypto_pwhash_OPSLIMIT_MIN'"
  )
  assert(
    opslimit <= binding.crypto_pwhash_OPSLIMIT_MAX,
    "opslimit must be at most 'crypto_pwhash_OPSLIMIT_MAX'"
  )
  assert(
    memlimit >= binding.crypto_pwhash_MEMLIMIT_MIN,
    "memlimit must be at least 'crypto_pwhash_MEMLIMIT_MIN'"
  )
  assert(
    memlimit <= binding.crypto_pwhash_MEMLIMIT_MAX,
    "memlimit must be at most 'crypto_pwhash_MEMLIMIT_MAX'"
  )
  assert(alg >= 1 && alg <= 2, 'alg must be either Argon2i 1.3 or Argon2id 1.3')

  const [done, promise] = checkStatus(callback)

  binding.crypto_pwhash_async(
    out.buffer,
    out.byteOffset,
    out.byteLength,

    passwd.buffer,
    passwd.byteOffset,
    passwd.byteLength,

    salt.buffer,
    salt.byteOffset,
    salt.byteLength,

    opslimit,
    memlimit,
    alg,

    done
  )

  return promise
}

exports.crypto_pwhash_str = function (out, passwd, opslimit, memlimit) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(
    out.byteLength === binding.crypto_pwhash_STRBYTES,
    "out must be 'crypto_pwhash_STRBYTES' bytes"
  )
  assert(typeof opslimit === 'number', 'opslimit must be a number')
  assert(
    opslimit >= binding.crypto_pwhash_OPSLIMIT_MIN,
    "opslimit must be at least 'crypto_pwhash_OPSLIMIT_MIN'"
  )
  assert(
    opslimit <= binding.crypto_pwhash_OPSLIMIT_MAX,
    "opslimit must be at most 'crypto_pwhash_OPSLIMIT_MAX'"
  )
  assert(typeof memlimit === 'number', 'memlimit must be a number')
  assert(
    memlimit >= binding.crypto_pwhash_MEMLIMIT_MIN,
    "memlimit must be at least 'crypto_pwhash_MEMLIMIT_MIN'"
  )
  assert(
    memlimit <= binding.crypto_pwhash_MEMLIMIT_MAX,
    "memlimit must be at most 'crypto_pwhash_MEMLIMIT_MAX'"
  )

  const res = binding.crypto_pwhash_str(out, passwd, opslimit, memlimit)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_pwhash_str_async = function (out, passwd, opslimit, memlimit, callback = undefined) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(
    out.byteLength === binding.crypto_pwhash_STRBYTES,
    "out must be 'crypto_pwhash_STRBYTES' bytes"
  )
  assert(passwd.byteLength > 0, 'passwd must not be empty')
  assert(typeof opslimit === 'number', 'opslimit must be a number')
  assert(
    opslimit >= binding.crypto_pwhash_OPSLIMIT_MIN,
    "opslimit must be at least 'crypto_pwhash_OPSLIMIT_MIN'"
  )
  assert(
    opslimit <= binding.crypto_pwhash_OPSLIMIT_MAX,
    "opslimit must be at most 'crypto_pwhash_OPSLIMIT_MAX'"
  )
  assert(typeof memlimit === 'number', 'memlimit must be a number')
  assert(
    memlimit >= binding.crypto_pwhash_MEMLIMIT_MIN,
    "memlimit must be at least 'crypto_pwhash_MEMLIMIT_MIN'"
  )
  assert(
    memlimit <= binding.crypto_pwhash_MEMLIMIT_MAX,
    "memlimit must be at most 'crypto_pwhash_MEMLIMIT_MAX'"
  )

  const [done, promise] = checkStatus(callback)

  binding.crypto_pwhash_str_async(
    out.buffer,
    out.byteOffset,
    out.byteLength,

    passwd.buffer,
    passwd.byteOffset,
    passwd.byteLength,

    opslimit,
    memlimit,

    done
  )

  return promise
}

exports.crypto_pwhash_str_verify = function (str, passwd) {
  assert(ArrayBuffer.isView(str), 'str must be a typed array')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(
    str.byteLength === binding.crypto_pwhash_STRBYTES,
    "str must be 'crypto_pwhash_STRBYTES' bytes"
  )

  return binding.crypto_pwhash_str_verify(str, passwd)
}

exports.crypto_pwhash_str_verify_async = function (str, passwd, callback = undefined) {
  assert(ArrayBuffer.isView(str), 'str must be a typed array')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(
    str.byteLength === binding.crypto_pwhash_STRBYTES,
    "str must be 'crypto_pwhash_STRBYTES' bytes"
  )
  assert(passwd.byteLength > 0, 'passwd must not be empty')

  const [done, promise] = checkStatus(callback, true)

  binding.crypto_pwhash_str_verify_async(
    str.buffer,
    str.byteOffset,
    str.byteLength,

    passwd.buffer,
    passwd.byteOffset,
    passwd.byteLength,

    done
  )

  return promise
}

exports.crypto_pwhash_str_needs_rehash = function (str, opslimit, memlimit) {
  assert(ArrayBuffer.isView(str), 'str must be a typed array')
  assert(
    str.byteLength === binding.crypto_pwhash_STRBYTES,
    "str must be 'crypto_pwhash_STRBYTES' bytes"
  )
  assert(
    opslimit >= binding.crypto_pwhash_OPSLIMIT_MIN,
    "opslimit must be at least 'crypto_pwhash_OPSLIMIT_MIN'"
  )
  assert(
    opslimit <= binding.crypto_pwhash_OPSLIMIT_MAX,
    "opslimit must be at most 'crypto_pwhash_OPSLIMIT_MAX'"
  )
  assert(
    memlimit >= binding.crypto_pwhash_MEMLIMIT_MIN,
    "memlimit must be at least 'crypto_pwhash_MEMLIMIT_MIN'"
  )
  assert(
    memlimit <= binding.crypto_pwhash_MEMLIMIT_MAX,
    "memlimit must be at most 'crypto_pwhash_MEMLIMIT_MAX'"
  )

  return binding.crypto_pwhash_str_needs_rehash(str, opslimit, memlimit)
}

exports.crypto_pwhash_scryptsalsa208sha256 = function (out, passwd, salt, opslimit, memlimit) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(ArrayBuffer.isView(salt), 'salt must be a typed array')
  assert(
    out.byteLength >= binding.crypto_pwhash_scryptsalsa208sha256_BYTES_MIN,
    "out must be at least 'crypto_pwhash_scryptsalsa208sha256_BYTES_MIN' bytes"
  )
  assert(
    out.byteLength <= binding.crypto_pwhash_scryptsalsa208sha256_BYTES_MAX,
    "out must be at most 'crypto_pwhash_scryptsalsa208sha256_BYTES_MAX' bytes"
  )
  assert(
    salt.byteLength === binding.crypto_pwhash_scryptsalsa208sha256_SALTBYTES,
    "salt must be 'crypto_pwhash_scryptsalsa208sha256_SALTBYTES' bytes"
  )
  assert(
    opslimit >= binding.crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MIN,
    "opslimit must be at least 'crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MIN'"
  )
  assert(
    opslimit <= binding.crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MAX,
    "opslimit must be at most 'crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MAX'"
  )
  assert(
    memlimit >= binding.crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MIN,
    "memlimit must be at least 'crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MIN'"
  )
  assert(
    memlimit <= binding.crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MAX,
    "memlimit must be at most 'crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MAX'"
  )

  const res = binding.crypto_pwhash_scryptsalsa208sha256(out, passwd, salt, opslimit, memlimit)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_pwhash_scryptsalsa208sha256_async = function (
  out,
  passwd,
  salt,
  opslimit,
  memlimit,
  callback = undefined
) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(ArrayBuffer.isView(salt), 'salt must be a typed array')
  assert(
    out.byteLength >= binding.crypto_pwhash_scryptsalsa208sha256_BYTES_MIN,
    "out must be at least 'crypto_pwhash_scryptsalsa208sha256_BYTES_MIN' bytes"
  )
  assert(
    out.byteLength <= binding.crypto_pwhash_scryptsalsa208sha256_BYTES_MAX,
    "out must be at most 'crypto_pwhash_scryptsalsa208sha256_BYTES_MAX' bytes"
  )
  assert(passwd.byteLength > 0, 'passwd must not be empty')
  assert(
    salt.byteLength === binding.crypto_pwhash_scryptsalsa208sha256_SALTBYTES,
    "salt must be 'crypto_pwhash_scryptsalsa208sha256_SALTBYTES' bytes"
  )
  assert(
    opslimit >= binding.crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MIN,
    "opslimit must be at least 'crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MIN'"
  )
  assert(
    opslimit <= binding.crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MAX,
    "opslimit must be at most 'crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MAX'"
  )
  assert(
    memlimit >= binding.crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MIN,
    "memlimit must be at least 'crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MIN'"
  )
  assert(
    memlimit <= binding.crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MAX,
    "memlimit must be at most 'crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MAX'"
  )

  const [done, promise] = checkStatus(callback)

  binding.crypto_pwhash_scryptsalsa208sha256_async(
    out.buffer,
    out.byteOffset,
    out.byteLength,

    passwd.buffer,
    passwd.byteOffset,
    passwd.byteLength,

    salt.buffer,
    salt.byteOffset,
    salt.byteLength,

    opslimit,
    memlimit,

    done
  )

  return promise
}

exports.crypto_pwhash_scryptsalsa208sha256_str_async = function (
  out,
  passwd,
  opslimit,
  memlimit,
  callback = undefined
) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(
    out.byteLength === binding.crypto_pwhash_scryptsalsa208sha256_STRBYTES,
    "out must be 'crypto_pwhash_scryptsalsa208sha256_STRBYTES' bytes"
  )
  assert(passwd.byteLength > 0, 'passwd must not be empty')
  assert(
    opslimit >= binding.crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MIN,
    "opslimit must be at least 'crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MIN'"
  )
  assert(
    opslimit <= binding.crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MAX,
    "opslimit must be at most 'crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MAX'"
  )
  assert(
    memlimit >= binding.crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MIN,
    "memlimit must be at least 'crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MIN'"
  )
  assert(
    memlimit <= binding.crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MAX,
    "memlimit must be at most 'crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MAX'"
  )

  const [done, promise] = checkStatus(callback)

  binding.crypto_pwhash_scryptsalsa208sha256_str_async(
    out.buffer,
    out.byteOffset,
    out.byteLength,

    passwd.buffer,
    passwd.byteOffset,
    passwd.byteLength,

    opslimit,
    memlimit,

    done
  )

  return promise
}

exports.crypto_pwhash_scryptsalsa208sha256_str = function (out, passwd, opslimit, memlimit) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(
    out.byteLength === binding.crypto_pwhash_scryptsalsa208sha256_STRBYTES,
    "out must be 'crypto_pwhash_scryptsalsa208sha256_STRBYTES' bytes"
  )
  assert(passwd.byteLength > 0, 'passwd must not be empty')
  assert(
    opslimit >= binding.crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MIN,
    "opslimit must be at least 'crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MIN'"
  )
  assert(
    opslimit <= binding.crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MAX,
    "opslimit must be at most 'crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MAX'"
  )
  assert(
    memlimit >= binding.crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MIN,
    "memlimit must be at least 'crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MIN'"
  )
  assert(
    memlimit <= binding.crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MAX,
    "memlimit must be at most 'crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MAX'"
  )

  const res = binding.crypto_pwhash_scryptsalsa208sha256_str(out, passwd, opslimit, memlimit)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_pwhash_scryptsalsa208sha256_str_verify_async = function (
  str,
  passwd,
  callback = undefined
) {
  assert(ArrayBuffer.isView(str), 'str must be a typed array')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(
    str.byteLength === binding.crypto_pwhash_scryptsalsa208sha256_STRBYTES,
    "str must be 'crypto_pwhash_scryptsalsa208sha256_STRBYTES' bytes"
  )
  assert(passwd.byteLength > 0, 'passwd must not be empty')

  const [done, promise] = checkStatus(callback, true)

  binding.crypto_pwhash_scryptsalsa208sha256_str_verify_async(
    str.buffer,
    str.byteOffset,
    str.byteLength,

    passwd.buffer,
    passwd.byteOffset,
    passwd.byteLength,

    done
  )

  return promise
}

exports.crypto_pwhash_scryptsalsa208sha256_str_verify = function (str, passwd) {
  assert(ArrayBuffer.isView(str), 'str must be a typed array')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(
    str.byteLength === binding.crypto_pwhash_scryptsalsa208sha256_STRBYTES,
    "str must be 'crypto_pwhash_scryptsalsa208sha256_STRBYTES' bytes"
  )
  assert(passwd.byteLength > 0, 'passwd must not be empty')

  return binding.crypto_pwhash_scryptsalsa208sha256_str_verify(str, passwd)
}

exports.crypto_pwhash_scryptsalsa208sha256_str_needs_rehash = function (str, opslimit, memlimit) {
  assert(ArrayBuffer.isView(str), 'str must be a typed array')
  assert(
    str.byteLength === binding.crypto_pwhash_scryptsalsa208sha256_STRBYTES,
    "str must be 'crypto_pwhash_scryptsalsa208sha256_STRBYTES' bytes"
  )
  assert(
    opslimit >= binding.crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MIN,
    "opslimit must be at least 'crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MIN'"
  )
  assert(
    opslimit <= binding.crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MAX,
    "opslimit must be at most 'crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MAX'"
  )
  assert(
    memlimit >= binding.crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MIN,
    "memlimit must be at least 'crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MIN'"
  )
  assert(
    memlimit <= binding.crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MAX,
    "memlimit must be at most 'crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MAX'"
  )

  return binding.crypto_pwhash_scryptsalsa208sha256_str_needs_rehash(str, opslimit, memlimit)
}

// crypto_kx

exports.crypto_kx_keypair = function (pk, sk) {
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(
    pk.byteLength === binding.crypto_kx_PUBLICKEYBYTES,
    "pk must be 'crypto_kx_PUBLICKEYBYTES' bytes"
  )
  assert(
    sk.byteLength === binding.crypto_kx_SECRETKEYBYTES,
    "sk must be 'crypto_kx_SECRETKEYBYTES' bytes"
  )

  const res = binding.crypto_kx_keypair(pk, sk)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_kx_seed_keypair = function (pk, sk, seed) {
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(ArrayBuffer.isView(seed), 'seed must be a typed array')
  assert(
    pk.byteLength === binding.crypto_kx_PUBLICKEYBYTES,
    "pk must be 'crypto_kx_PUBLICKEYBYTES' bytes"
  )
  assert(
    sk.byteLength === binding.crypto_kx_SECRETKEYBYTES,
    "sk must be 'crypto_kx_SECRETKEYBYTES' bytes"
  )
  assert(
    seed.byteLength === binding.crypto_kx_SEEDBYTES,
    "seed must be 'crypto_kx_SEEDBYTES' bytes"
  )

  const res = binding.crypto_kx_seed_keypair(pk, sk, seed)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_kx_client_session_keys = function (rx, tx, clientPk, clientSk, serverPk) {
  if (!rx) rx = undefined
  if (!tx) tx = undefined

  assert(rx || tx, 'at least one session key must be specified')

  if (rx) {
    assert(ArrayBuffer.isView(rx), 'rx must be a typed array')
    assert(
      rx.byteLength === binding.crypto_kx_SESSIONKEYBYTES,
      "rx must be 'crypto_kx_SESSIONKEYBYTES' bytes"
    )
  }

  if (tx) {
    assert(ArrayBuffer.isView(tx), 'tx must be a typed array')
    assert(
      tx.byteLength === binding.crypto_kx_SESSIONKEYBYTES,
      "tx must be 'crypto_kx_SESSIONKEYBYTES' bytes"
    )
  }

  assert(ArrayBuffer.isView(clientPk), 'clientPk must be a typed array')
  assert(ArrayBuffer.isView(clientSk), 'clientSk must be a typed array')
  assert(ArrayBuffer.isView(serverPk), 'serverPk must be a typed array')
  assert(
    clientPk.byteLength === binding.crypto_kx_PUBLICKEYBYTES,
    "clientPk must be 'crypto_kx_PUBLICKEYBYTES' bytes"
  )
  assert(
    clientSk.byteLength === binding.crypto_kx_SECRETKEYBYTES,
    "clientSk must be 'crypto_kx_SECRETKEYBYTES' bytes"
  )
  assert(
    serverPk.byteLength === binding.crypto_kx_PUBLICKEYBYTES,
    "serverPk must be 'crypto_kx_PUBLICKEYBYTES' bytes"
  )

  const res = binding.crypto_kx_client_session_keys(rx, tx, clientPk, clientSk, serverPk)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_kx_server_session_keys = function (rx, tx, serverPk, serverSk, clientPk) {
  if (!rx) rx = undefined
  if (!tx) tx = undefined

  assert(rx || tx, 'at least one session key must be specified')

  if (rx) {
    assert(ArrayBuffer.isView(rx), 'rx must be a typed array')
    assert(
      rx.byteLength === binding.crypto_kx_SESSIONKEYBYTES,
      "rx must be 'crypto_kx_SESSIONKEYBYTES' bytes"
    )
  }

  if (tx) {
    assert(ArrayBuffer.isView(tx), 'tx must be a typed array')
    assert(
      tx.byteLength === binding.crypto_kx_SESSIONKEYBYTES,
      "tx must be 'crypto_kx_SESSIONKEYBYTES' bytes"
    )
  }

  assert(ArrayBuffer.isView(serverPk), 'serverPk must be a typed array')
  assert(ArrayBuffer.isView(serverSk), 'serverSk must be a typed array')
  assert(ArrayBuffer.isView(clientPk), 'clientPk must be a typed array')
  assert(
    serverPk.byteLength === binding.crypto_kx_PUBLICKEYBYTES,
    "serverPk must be 'crypto_kx_PUBLICKEYBYTES' bytes"
  )
  assert(
    serverSk.byteLength === binding.crypto_kx_SECRETKEYBYTES,
    "serverSk must be 'crypto_kx_SECRETKEYBYTES' bytes"
  )
  assert(
    clientPk.byteLength === binding.crypto_kx_PUBLICKEYBYTES,
    "clientPk must be 'crypto_kx_PUBLICKEYBYTES' bytes"
  )

  const res = binding.crypto_kx_server_session_keys(rx, tx, serverPk, serverSk, clientPk)

  if (res !== 0) throw new Error('status: ' + res)
}

// crypto_scalarmult

exports.crypto_scalarmult_base = function (q, n) {
  assert(ArrayBuffer.isView(q), 'q must be a typed array')
  assert(
    q.byteLength === binding.crypto_scalarmult_BYTES,
    "q must be 'crypto_scalarmult_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.crypto_scalarmult_SCALARBYTES,
    "n must be 'crypto_scalarmult_SCALARBYTES' bytes"
  )

  const res = binding.crypto_scalarmult_base(q, n)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_scalarmult = function (q, n, p) {
  assert(ArrayBuffer.isView(q), 'q must be a typed array')
  assert(
    q.byteLength === binding.crypto_scalarmult_BYTES,
    "q must be 'crypto_scalarmult_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.crypto_scalarmult_SCALARBYTES,
    "n must be 'crypto_scalarmult_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(p), 'p must be a typed array')
  assert(
    p.byteLength === binding.crypto_scalarmult_BYTES,
    "p must be 'crypto_scalarmult_BYTES' bytes"
  )

  const res = binding.crypto_scalarmult(q, n, p)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_scalarmult_ed25519_base = function (q, n) {
  assert(ArrayBuffer.isView(q), 'q must be a typed array')
  assert(
    q.byteLength === binding.crypto_scalarmult_ed25519_BYTES,
    "q must be 'crypto_scalarmult_ed25519_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.crypto_scalarmult_ed25519_SCALARBYTES,
    "n must be 'crypto_scalarmult_ed25519_SCALARBYTES' bytes"
  )

  const res = binding.crypto_scalarmult_ed25519_base(q, n)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_scalarmult_ed25519 = function (q, n, p) {
  assert(ArrayBuffer.isView(q), 'q must be a typed array')
  assert(
    q.byteLength === binding.crypto_scalarmult_ed25519_BYTES,
    "q must be 'crypto_scalarmult_ed25519_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.crypto_scalarmult_ed25519_SCALARBYTES,
    "n must be 'crypto_scalarmult_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(p), 'p must be a typed array')
  assert(
    p.byteLength === binding.crypto_scalarmult_ed25519_BYTES,
    "p must be 'crypto_scalarmult_ed25519_BYTES' bytes"
  )

  const res = binding.crypto_scalarmult_ed25519(q, n, p)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_core_ed25519_is_valid_point = function (p) {
  assert(ArrayBuffer.isView(p), 'p must be a typed array')
  assert(
    p.byteLength === binding.crypto_core_ed25519_BYTES,
    "p must be 'crypto_core_ed25519_BYTES' bytes"
  )

  return binding.crypto_core_ed25519_is_valid_point(p)
}

exports.crypto_core_ed25519_from_uniform = function (p, r) {
  assert(ArrayBuffer.isView(p), 'p must be a typed array')
  assert(
    p.byteLength === binding.crypto_core_ed25519_BYTES,
    "p must be 'crypto_core_ed25519_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(r), 'r must be a typed array')
  assert(
    r.byteLength === binding.crypto_core_ed25519_UNIFORMBYTES,
    "r must be 'crypto_core_ed25519_UNIFORMBYTES' bytes"
  )

  const res = binding.crypto_core_ed25519_from_uniform(p, r)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_scalarmult_ed25519_base_noclamp = function (q, n) {
  assert(ArrayBuffer.isView(q), 'q must be a typed array')
  assert(
    q.byteLength === binding.crypto_scalarmult_ed25519_BYTES,
    "q must be 'crypto_scalarmult_ed25519_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.crypto_scalarmult_ed25519_SCALARBYTES,
    "n must be 'crypto_scalarmult_ed25519_SCALARBYTES' bytes"
  )

  const res = binding.crypto_scalarmult_ed25519_base_noclamp(q, n)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_scalarmult_ed25519_noclamp = function (q, n, p) {
  assert(ArrayBuffer.isView(q), 'q must be a typed array')
  assert(
    q.byteLength === binding.crypto_scalarmult_ed25519_BYTES,
    "q must be 'crypto_scalarmult_ed25519_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.crypto_scalarmult_ed25519_SCALARBYTES,
    "n must be 'crypto_scalarmult_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(p), 'p must be a typed array')
  assert(
    p.byteLength === binding.crypto_scalarmult_ed25519_BYTES,
    "p must be 'crypto_scalarmult_ed25519_BYTES' bytes"
  )

  const res = binding.crypto_scalarmult_ed25519_noclamp(q, n, p)

  if (res !== 0) throw new Error('status: ' + res)
}

// crypto_core

exports.crypto_core_ed25519_add = function (r, p, q) {
  assert(ArrayBuffer.isView(r), 'r must be a typed array')
  assert(
    r.byteLength === binding.crypto_core_ed25519_BYTES,
    "r must be 'crypto_core_ed25519_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(p), 'p must be a typed array')
  assert(
    p.byteLength === binding.crypto_core_ed25519_BYTES,
    "p must be 'crypto_core_ed25519_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(q), 'q must be a typed array')
  assert(
    q.byteLength === binding.crypto_core_ed25519_BYTES,
    "q must be 'crypto_core_ed25519_BYTES' bytes"
  )

  const res = binding.crypto_core_ed25519_add(r, p, q)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_core_ed25519_sub = function (r, p, q) {
  assert(ArrayBuffer.isView(r), 'r must be a typed array')
  assert(
    r.byteLength === binding.crypto_core_ed25519_BYTES,
    "r must be 'crypto_core_ed25519_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(p), 'p must be a typed array')
  assert(
    p.byteLength === binding.crypto_core_ed25519_BYTES,
    "p must be 'crypto_core_ed25519_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(q), 'q must be a typed array')
  assert(
    q.byteLength === binding.crypto_core_ed25519_BYTES,
    "q must be 'crypto_core_ed25519_BYTES' bytes"
  )

  const res = binding.crypto_core_ed25519_sub(r, p, q)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_core_ed25519_scalar_random = function (r) {
  assert(ArrayBuffer.isView(r), 'r must be a typed array')
  assert(
    r.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "r must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )

  binding.crypto_core_ed25519_scalar_random(r)
}

exports.crypto_core_ed25519_scalar_reduce = function (r, s) {
  assert(ArrayBuffer.isView(r), 'r must be a typed array')
  assert(
    r.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "r must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(s), 's must be a typed array')
  assert(
    s.byteLength === binding.crypto_core_ed25519_NONREDUCEDSCALARBYTES,
    "s must be 'crypto_core_ed25519_NONREDUCEDSCALARBYTES' bytes"
  )

  binding.crypto_core_ed25519_scalar_reduce(r, s)
}

exports.crypto_core_ed25519_scalar_invert = function (recip, s) {
  assert(ArrayBuffer.isView(recip), 'recip must be a typed array')
  assert(
    recip.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "recip must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(s), 's must be a typed array')
  assert(
    s.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "s must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )

  binding.crypto_core_ed25519_scalar_invert(recip, s)
}

exports.crypto_core_ed25519_scalar_negate = function (neg, s) {
  assert(ArrayBuffer.isView(neg), 'neg must be a typed array')
  assert(
    neg.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "neg must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(s), 's must be a typed array')
  assert(
    s.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "s must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )

  binding.crypto_core_ed25519_scalar_negate(neg, s)
}

exports.crypto_core_ed25519_scalar_complement = function (comp, s) {
  assert(ArrayBuffer.isView(comp), 'comp must be a typed array')
  assert(
    comp.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "comp must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(s), 's must be a typed array')
  assert(
    s.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "s must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )

  binding.crypto_core_ed25519_scalar_complement(comp, s)
}

exports.crypto_core_ed25519_scalar_add = function (z, x, y) {
  assert(ArrayBuffer.isView(z), 'z must be a typed array')
  assert(
    z.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "z must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(x), 'x must be a typed array')
  assert(
    x.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "x must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(y), 'y must be a typed array')
  assert(
    y.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "y must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )

  binding.crypto_core_ed25519_scalar_add(z, x, y)
}

exports.crypto_core_ed25519_scalar_sub = function (z, x, y) {
  assert(ArrayBuffer.isView(z), 'z must be a typed array')
  assert(
    z.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "z must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(x), 'x must be a typed array')
  assert(
    x.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "x must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(y), 'y must be a typed array')
  assert(
    y.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "y must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )

  binding.crypto_core_ed25519_scalar_sub(z, x, y)
}

// crypto_shorthash

exports.crypto_shorthash = function (out, input, k) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(
    out.byteLength === binding.crypto_shorthash_BYTES,
    "out must be 'crypto_shorthash_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_shorthash_KEYBYTES,
    "k must be 'crypto_shorthash_KEYBYTES' bytes"
  )

  const res = binding.crypto_shorthash(out, input, k)

  if (res !== 0) throw new Error('status: ' + res)
}

// crypto_kdf

exports.crypto_kdf_keygen = function (key) {
  assert(ArrayBuffer.isView(key), 'key must be a typed array')
  assert(key.byteLength === binding.crypto_kdf_KEYBYTES, "key must be 'crypto_kdf_KEYBYTES' bytes")

  binding.crypto_kdf_keygen(key)
}

exports.crypto_kdf_derive_from_key = function (subkey, subkeyId, ctx, key) {
  assert(ArrayBuffer.isView(subkey), 'subkey must be a typed array')
  assert(
    subkey.byteLength >= binding.crypto_kdf_BYTES_MIN,
    "subkey must be at least 'crypto_kdf_BYTES_MIN' bytes"
  )
  assert(
    subkey.byteLength <= binding.crypto_kdf_BYTES_MAX,
    "subkey must be at most 'crypto_kdf_BYTES_MAX' bytes"
  )
  assert(ArrayBuffer.isView(ctx), 'ctx must be a typed array')
  assert(
    ctx.byteLength === binding.crypto_kdf_CONTEXTBYTES,
    "ctx must be 'crypto_kdf_CONTEXTBYTES' bytes"
  )
  assert(ArrayBuffer.isView(key), 'key must be a typed array')
  assert(key.byteLength === binding.crypto_kdf_KEYBYTES, "key must be 'crypto_kdf_KEYBYTES' bytes")

  const res = binding.crypto_kdf_derive_from_key(subkey, subkeyId, ctx, key)

  if (res !== 0) throw new Error('status: ' + res)
}

// crypto_hash

exports.crypto_hash = function (out, input) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(out.byteLength === binding.crypto_hash_BYTES, "out must be 'crypto_hash_BYTES' bytes")

  const res = binding.crypto_hash(out, input)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_hash_sha256 = function (out, input) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(
    out.byteLength === binding.crypto_hash_sha256_BYTES,
    "out must be 'crypto_hash_sha256_BYTES' bytes"
  )

  const res = binding.crypto_hash_sha256(out, input)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_hash_sha256_init = function (state) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_hash_sha256_STATEBYTES,
    "state must be 'crypto_hash_sha256_STATEBYTES' bytes"
  )

  const res = binding.crypto_hash_sha256_init(state)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_hash_sha256_update = function (state, input) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(
    state.byteLength === binding.crypto_hash_sha256_STATEBYTES,
    "state must be 'crypto_hash_sha256_STATEBYTES' bytes"
  )

  const res = binding.crypto_hash_sha256_update(state, input)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_hash_sha256_final = function (state, out) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_hash_sha256_STATEBYTES,
    "state must be 'crypto_hash_sha256_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(
    out.byteLength === binding.crypto_hash_sha256_BYTES,
    "out must be 'crypto_hash_sha256_BYTES' bytes"
  )

  const res = binding.crypto_hash_sha256_final(state, out)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_hash_sha512 = function (out, input) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(
    out.byteLength === binding.crypto_hash_sha512_BYTES,
    "out must be 'crypto_hash_sha512_BYTES' bytes"
  )

  const res = binding.crypto_hash_sha512(out, input)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_hash_sha512_init = function (state) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_hash_sha512_STATEBYTES,
    "state must be 'crypto_hash_sha512_STATEBYTES' bytes"
  )

  const res = binding.crypto_hash_sha512_init(state)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_hash_sha512_update = function (state, input) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(
    state.byteLength === binding.crypto_hash_sha512_STATEBYTES,
    "state must be 'crypto_hash_sha512_STATEBYTES' bytes"
  )

  const res = binding.crypto_hash_sha512_update(state, input)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_hash_sha512_final = function (state, out) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_hash_sha512_STATEBYTES,
    "state must be 'crypto_hash_sha512_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(
    out.byteLength === binding.crypto_hash_sha512_BYTES,
    "out must be 'crypto_hash_sha512_BYTES' bytes"
  )

  const res = binding.crypto_hash_sha512_final(state, out)

  if (res !== 0) throw new Error('status: ' + res)
}

// crypto_aead

exports.crypto_aead_xchacha20poly1305_ietf_keygen = function (k) {
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_aead_xchacha20poly1305_ietf_KEYBYTES,
    "k must be 'crypto_aead_xchacha20poly1305_ietf_KEYBYTES' bytes"
  )

  binding.crypto_aead_xchacha20poly1305_ietf_keygen(k)
}

exports.crypto_aead_xchacha20poly1305_ietf_encrypt = function (c, m, ad, nsec, npub, k) {
  if (!ad) ad = undefined

  assert(nsec === null, 'nsec must always be set to null')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(
    c.byteLength === m.byteLength + binding.crypto_aead_xchacha20poly1305_ietf_ABYTES,
    "c must be 'm.byteLength + crypto_aead_xchacha20poly1305_ietf_ABYTES' bytes"
  )
  assert(c.byteLength <= 0xffffffff, 'c.byteLength must be a 32bit integer')
  assert(ArrayBuffer.isView(npub), 'npub must be a typed array')
  assert(
    npub.byteLength === binding.crypto_aead_xchacha20poly1305_ietf_NPUBBYTES,
    "npub must be 'crypto_aead_xchacha20poly1305_ietf_NPUBBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_aead_xchacha20poly1305_ietf_KEYBYTES,
    "k must be 'crypto_aead_xchacha20poly1305_ietf_KEYBYTES' bytes"
  )

  const res = binding.crypto_aead_xchacha20poly1305_ietf_encrypt(c, m, ad, npub, k)

  if (res < 0) throw new Error('could not encrypt data')

  return res
}

exports.crypto_aead_xchacha20poly1305_ietf_decrypt = function (m, nsec, c, ad, npub, k) {
  if (!ad) ad = undefined

  assert(nsec === null, 'nsec must always be set to null')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(
    m.byteLength === c.byteLength - binding.crypto_aead_xchacha20poly1305_ietf_ABYTES,
    "m must be 'c.byteLength - crypto_aead_xchacha20poly1305_ietf_ABYTES' bytes"
  )
  assert(m.byteLength <= 0xffffffff, 'm.byteLength must be a 32bit integer')
  assert(ArrayBuffer.isView(npub), 'npub must be a typed array')
  assert(
    npub.byteLength === binding.crypto_aead_xchacha20poly1305_ietf_NPUBBYTES,
    "npub must be 'crypto_aead_xchacha20poly1305_ietf_NPUBBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_aead_xchacha20poly1305_ietf_KEYBYTES,
    "k must be 'crypto_aead_xchacha20poly1305_ietf_KEYBYTES' bytes"
  )

  const res = binding.crypto_aead_xchacha20poly1305_ietf_decrypt(m, c, ad, npub, k)

  if (res < 0) throw new Error('could not verify data')

  return res
}

exports.crypto_aead_xchacha20poly1305_ietf_encrypt_detached = function (
  c,
  mac,
  m,
  ad,
  nsec,
  npub,
  k
) {
  if (!ad) ad = undefined

  assert(nsec === null, 'nsec must always be set to null')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(ArrayBuffer.isView(mac), 'mac must be a typed array')
  assert(
    mac.byteLength === binding.crypto_aead_xchacha20poly1305_ietf_ABYTES,
    "mac must be 'crypto_aead_xchacha20poly1305_ietf_ABYTES' bytes"
  )
  assert(ArrayBuffer.isView(npub), 'npub must be a typed array')
  assert(
    npub.byteLength === binding.crypto_aead_xchacha20poly1305_ietf_NPUBBYTES,
    "npub must be 'crypto_aead_xchacha20poly1305_ietf_NPUBBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_aead_xchacha20poly1305_ietf_KEYBYTES,
    "k must be 'crypto_aead_xchacha20poly1305_ietf_KEYBYTES' bytes"
  )

  const res = binding.crypto_aead_xchacha20poly1305_ietf_encrypt_detached(c, mac, m, ad, npub, k)

  if (res < 0) throw new Error('could not encrypt data')

  return res
}

exports.crypto_aead_xchacha20poly1305_ietf_decrypt_detached = function (
  m,
  nsec,
  c,
  mac,
  ad,
  npub,
  k
) {
  if (!ad) ad = undefined

  assert(nsec === null, 'nsec must always be set to null')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(m.byteLength === c.byteLength, "m must be 'c.byteLength' bytes")
  assert(ArrayBuffer.isView(mac), 'mac must be a typed array')
  assert(
    mac.byteLength === binding.crypto_aead_xchacha20poly1305_ietf_ABYTES,
    "mac must be 'crypto_aead_xchacha20poly1305_ietf_ABYTES' bytes"
  )
  assert(ArrayBuffer.isView(npub), 'npub must be a typed array')
  assert(
    npub.byteLength === binding.crypto_aead_xchacha20poly1305_ietf_NPUBBYTES,
    "npub must be 'crypto_aead_xchacha20poly1305_ietf_NPUBBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_aead_xchacha20poly1305_ietf_KEYBYTES,
    "k must be 'crypto_aead_xchacha20poly1305_ietf_KEYBYTES' bytes"
  )

  const res = binding.crypto_aead_xchacha20poly1305_ietf_decrypt_detached(m, c, mac, ad, npub, k)

  if (res !== 0) throw new Error('could not verify data')
}

exports.crypto_aead_chacha20poly1305_ietf_keygen = function (k) {
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_aead_chacha20poly1305_ietf_KEYBYTES,
    "k must be 'crypto_aead_chacha20poly1305_ietf_KEYBYTES' bytes"
  )

  binding.crypto_aead_chacha20poly1305_ietf_keygen(k)
}

exports.crypto_aead_chacha20poly1305_ietf_encrypt = function (c, m, ad, nsec, npub, k) {
  if (!ad) ad = undefined

  assert(nsec === null, 'nsec must always be set to null')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(
    c.byteLength === m.byteLength + binding.crypto_aead_chacha20poly1305_ietf_ABYTES,
    "c must be 'm.byteLength + crypto_aead_chacha20poly1305_ietf_ABYTES' bytes"
  )
  assert(c.byteLength <= 0xffffffff, 'c.byteLength must be a 32bit integer')
  assert(ArrayBuffer.isView(npub), 'npub must be a typed array')
  assert(
    npub.byteLength === binding.crypto_aead_chacha20poly1305_ietf_NPUBBYTES,
    "npub must be 'crypto_aead_chacha20poly1305_ietf_NPUBBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_aead_chacha20poly1305_ietf_KEYBYTES,
    "k must be 'crypto_aead_chacha20poly1305_ietf_KEYBYTES' bytes"
  )

  const res = binding.crypto_aead_chacha20poly1305_ietf_encrypt(c, m, ad, npub, k)

  if (res < 0) throw new Error('could not encrypt data')

  return res
}

exports.crypto_aead_chacha20poly1305_ietf_decrypt = function (m, nsec, c, ad, npub, k) {
  if (!ad) ad = undefined

  assert(nsec === null, 'nsec must always be set to null')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(
    m.byteLength === c.byteLength - binding.crypto_aead_chacha20poly1305_ietf_ABYTES,
    "m must be 'c.byteLength - crypto_aead_chacha20poly1305_ietf_ABYTES' bytes"
  )
  assert(m.byteLength <= 0xffffffff, 'm.byteLength must be a 32bit integer')
  assert(ArrayBuffer.isView(npub), 'npub must be a typed array')
  assert(
    npub.byteLength === binding.crypto_aead_chacha20poly1305_ietf_NPUBBYTES,
    "npub must be 'crypto_aead_chacha20poly1305_ietf_NPUBBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_aead_chacha20poly1305_ietf_KEYBYTES,
    "k must be 'crypto_aead_chacha20poly1305_ietf_KEYBYTES' bytes"
  )

  const res = binding.crypto_aead_chacha20poly1305_ietf_decrypt(m, c, ad, npub, k)

  if (res < 0) throw new Error('could not verify data')

  return res
}

exports.crypto_aead_chacha20poly1305_ietf_encrypt_detached = function (
  c,
  mac,
  m,
  ad,
  nsec,
  npub,
  k
) {
  if (!ad) ad = undefined

  assert(nsec === null, 'nsec must always be set to null')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(ArrayBuffer.isView(mac), 'mac must be a typed array')
  assert(
    mac.byteLength === binding.crypto_aead_chacha20poly1305_ietf_ABYTES,
    "mac must be 'crypto_aead_chacha20poly1305_ietf_ABYTES' bytes"
  )
  assert(ArrayBuffer.isView(npub), 'npub must be a typed array')
  assert(
    npub.byteLength === binding.crypto_aead_chacha20poly1305_ietf_NPUBBYTES,
    "npub must be 'crypto_aead_chacha20poly1305_ietf_NPUBBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_aead_chacha20poly1305_ietf_KEYBYTES,
    "k must be 'crypto_aead_chacha20poly1305_ietf_KEYBYTES' bytes"
  )

  const res = binding.crypto_aead_chacha20poly1305_ietf_encrypt_detached(c, mac, m, ad, npub, k)

  if (res < 0) throw new Error('could not encrypt data')

  return res
}

exports.crypto_aead_chacha20poly1305_ietf_decrypt_detached = function (
  m,
  nsec,
  c,
  mac,
  ad,
  npub,
  k
) {
  if (!ad) ad = undefined

  assert(nsec === null, 'nsec must always be set to null')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(m.byteLength === c.byteLength, "m must be 'c.byteLength' bytes")
  assert(ArrayBuffer.isView(mac), 'mac must be a typed array')
  assert(
    mac.byteLength === binding.crypto_aead_chacha20poly1305_ietf_ABYTES,
    "mac must be 'crypto_aead_chacha20poly1305_ietf_ABYTES' bytes"
  )
  assert(ArrayBuffer.isView(npub), 'npub must be a typed array')
  assert(
    npub.byteLength === binding.crypto_aead_chacha20poly1305_ietf_NPUBBYTES,
    "npub must be 'crypto_aead_chacha20poly1305_ietf_NPUBBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_aead_chacha20poly1305_ietf_KEYBYTES,
    "k must be 'crypto_aead_chacha20poly1305_ietf_KEYBYTES' bytes"
  )

  const res = binding.crypto_aead_chacha20poly1305_ietf_decrypt_detached(m, c, mac, ad, npub, k)

  if (res !== 0) throw new Error('could not verify data')
}

// crypto_stream

exports.crypto_stream_xor_wrap_init = function (state, n, k) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.sn_crypto_stream_xor_STATEBYTES,
    "state must be 'sn_crypto_stream_xor_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.crypto_stream_NONCEBYTES,
    "n must be 'crypto_stream_NONCEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_stream_KEYBYTES,
    "k must be 'crypto_stream_KEYBYTES' bytes"
  )

  binding.crypto_stream_xor_wrap_init(state, n, k)
}

exports.crypto_stream_xor_wrap_update = function (state, c, m) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.sn_crypto_stream_xor_STATEBYTES,
    "state must be 'sn_crypto_stream_xor_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")

  binding.crypto_stream_xor_wrap_update(state, c, m)
}

exports.crypto_stream_xor_wrap_final = function (state) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.sn_crypto_stream_xor_STATEBYTES,
    "state must be 'sn_crypto_stream_xor_STATEBYTES' bytes"
  )

  binding.crypto_stream_xor_wrap_final(state)
}

exports.crypto_stream_chacha20_xor_wrap_init = function (state, n, k) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_stream_chacha20_xor_STATEBYTES,
    "state must be 'crypto_stream_chacha20_xor_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.crypto_stream_chacha20_NONCEBYTES,
    "n must be 'crypto_stream_chacha20_NONCEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_stream_chacha20_KEYBYTES,
    "k must be 'crypto_stream_chacha20_KEYBYTES' bytes"
  )

  binding.crypto_stream_chacha20_xor_wrap_init(state, n, k)
}

exports.crypto_stream_chacha20_xor_wrap_update = function (state, c, m) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_stream_chacha20_xor_STATEBYTES,
    "state must be 'crypto_stream_chacha20_xor_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")

  binding.crypto_stream_chacha20_xor_wrap_update(state, c, m)
}

exports.crypto_stream_chacha20_xor_wrap_final = function (state) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_stream_chacha20_xor_STATEBYTES,
    "state must be 'crypto_stream_chacha20_xor_STATEBYTES' bytes"
  )

  binding.crypto_stream_chacha20_xor_wrap_final(state)
}

exports.crypto_stream_chacha20_ietf_xor_wrap_init = function (state, n, k) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_stream_chacha20_ietf_xor_STATEBYTES,
    "state must be 'crypto_stream_chacha20_ietf_xor_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.crypto_stream_chacha20_ietf_NONCEBYTES,
    "n must be 'crypto_stream_chacha20_ietf_NONCEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_stream_chacha20_ietf_KEYBYTES,
    "k must be 'crypto_stream_chacha20_ietf_KEYBYTES' bytes"
  )

  binding.crypto_stream_chacha20_ietf_xor_wrap_init(state, n, k)
}

exports.crypto_stream_chacha20_ietf_xor_wrap_update = function (state, c, m) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_stream_chacha20_ietf_xor_STATEBYTES,
    "state must be 'crypto_stream_chacha20_ietf_xor_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")

  binding.crypto_stream_chacha20_ietf_xor_wrap_update(state, c, m)
}

exports.crypto_stream_chacha20_ietf_xor_wrap_final = function (state) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_stream_chacha20_ietf_xor_STATEBYTES,
    "state must be 'crypto_stream_chacha20_ietf_xor_STATEBYTES' bytes"
  )

  binding.crypto_stream_chacha20_ietf_xor_wrap_final(state)
}

exports.crypto_stream_xchacha20_xor_wrap_init = function (state, n, k) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_stream_xchacha20_xor_STATEBYTES,
    "state must be 'crypto_stream_xchacha20_xor_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.crypto_stream_xchacha20_NONCEBYTES,
    "n must be 'crypto_stream_xchacha20_NONCEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_stream_xchacha20_KEYBYTES,
    "k must be 'crypto_stream_xchacha20_KEYBYTES' bytes"
  )

  binding.crypto_stream_xchacha20_xor_wrap_init(state, n, k)
}

exports.crypto_stream_xchacha20_xor_wrap_update = function (state, c, m) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_stream_xchacha20_xor_STATEBYTES,
    "state must be 'crypto_stream_xchacha20_xor_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")

  binding.crypto_stream_xchacha20_xor_wrap_update(state, c, m)
}

exports.crypto_stream_xchacha20_xor_wrap_final = function (state) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_stream_xchacha20_xor_STATEBYTES,
    "state must be 'crypto_stream_xchacha20_xor_STATEBYTES' bytes"
  )

  binding.crypto_stream_xchacha20_xor_wrap_final(state)
}

exports.crypto_stream_salsa20_xor_wrap_init = function (state, n, k) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_stream_salsa20_xor_STATEBYTES,
    "state must be 'crypto_stream_salsa20_xor_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.crypto_stream_salsa20_NONCEBYTES,
    "n must be 'crypto_stream_salsa20_NONCEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_stream_salsa20_KEYBYTES,
    "k must be 'crypto_stream_salsa20_KEYBYTES' bytes"
  )

  binding.crypto_stream_salsa20_xor_wrap_init(state, n, k)
}

exports.crypto_stream_salsa20_xor_wrap_update = function (state, c, m) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_stream_salsa20_xor_STATEBYTES,
    "state must be 'crypto_stream_salsa20_xor_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")

  binding.crypto_stream_salsa20_xor_wrap_update(state, c, m)
}

exports.crypto_stream_salsa20_xor_wrap_final = function (state) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_stream_salsa20_xor_STATEBYTES,
    "state must be 'crypto_stream_salsa20_xor_STATEBYTES' bytes"
  )

  binding.crypto_stream_salsa20_xor_wrap_final(state)
}

// experimental

exports.extension_tweak_ed25519_base = function (n, p, ns) {
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "n must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(p), 'p must be a typed array')
  assert(
    p.byteLength === binding.extension_tweak_ed25519_BYTES,
    "p must be 'extension_tweak_ed25519_BYTES' bytes"
  )

  binding.extension_tweak_ed25519_base(n, p, ns)
}

exports.extension_tweak_ed25519_sign_detached = function (sig, m, scalar, pk) {
  assert(ArrayBuffer.isView(sig), 'sig must be a typed array')
  assert(sig.byteLength === binding.crypto_sign_BYTES, "sig must be 'crypto_sign_BYTES' bytes")
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(scalar), 'scalar must be a typed array')
  assert(
    scalar.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "scalar must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )

  if (pk) {
    assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
    assert(
      pk.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
      "pk must be 'crypto_sign_PUBLICKEYBYTES' bytes"
    )
  }

  const res = binding.extension_tweak_ed25519_sign_detached(sig, m, scalar, pk)

  if (res !== 0) throw new Error('failed to compute signature')
}

exports.extension_tweak_ed25519_sk_to_scalar = function (n, sk) {
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "n must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(
    sk.byteLength === binding.crypto_sign_SECRETKEYBYTES,
    "sk must be 'crypto_sign_SECRETKEYBYTES' bytes"
  )

  binding.extension_tweak_ed25519_sk_to_scalar(n, sk)
}

exports.extension_tweak_ed25519_scalar = function (scalarOut, scalar, ns) {
  assert(ArrayBuffer.isView(scalarOut), 'scalarOut must be a typed array')
  assert(
    scalarOut.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "scalarOut must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(scalar), 'scalar must be a typed array')
  assert(
    scalar.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "scalar must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )

  binding.extension_tweak_ed25519_scalar(scalarOut, scalar, ns)
}

exports.extension_tweak_ed25519_pk = function (tpk, pk, ns) {
  assert(ArrayBuffer.isView(tpk), 'tpk must be a typed array')
  assert(
    tpk.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
    "tpk must be 'crypto_sign_PUBLICKEYBYTES' bytes"
  )
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(
    pk.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
    "pk must be 'crypto_sign_PUBLICKEYBYTES' bytes"
  )

  const res = binding.extension_tweak_ed25519_pk(tpk, pk, ns)

  if (res !== 0) throw new Error('failed to tweak public key')
}

exports.extension_tweak_ed25519_keypair = function (pk, scalarOut, scalarIn, ns) {
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(
    pk.byteLength === binding.extension_tweak_ed25519_BYTES,
    "pk must be 'extension_tweak_ed25519_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(scalarOut), 'scalarOut must be a typed array')
  assert(
    scalarOut.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "scalarOut must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(scalarIn), 'scalarIn must be a typed array')
  assert(
    scalarIn.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "scalarIn must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )

  binding.extension_tweak_ed25519_keypair(pk, scalarOut, scalarIn, ns)
}

exports.extension_tweak_ed25519_scalar_add = function (scalarOut, scalar, n) {
  assert(ArrayBuffer.isView(scalarOut), 'scalarOut must be a typed array')
  assert(
    scalarOut.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "scalarOut must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(scalar), 'scalar must be a typed array')
  assert(
    scalar.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "scalar must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "n must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )

  binding.extension_tweak_ed25519_scalar_add(scalarOut, scalar, n)
}

exports.extension_tweak_ed25519_pk_add = function (tpk, pk, p) {
  assert(ArrayBuffer.isView(tpk), 'tpk must be a typed array')
  assert(
    tpk.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
    "tpk must be 'crypto_sign_PUBLICKEYBYTES' bytes"
  )
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(
    pk.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
    "pk must be 'crypto_sign_PUBLICKEYBYTES' bytes"
  )
  assert(ArrayBuffer.isView(p), 'p must be a typed array')
  assert(
    p.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
    "p must be 'crypto_sign_PUBLICKEYBYTES' bytes"
  )

  const res = binding.extension_tweak_ed25519_pk_add(tpk, pk, p)

  if (res !== 0) throw new Error('failed to add tweak to public key')
}

exports.extension_tweak_ed25519_keypair_add = function (pk, scalarOut, scalarIn, tweak) {
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(
    pk.byteLength === binding.extension_tweak_ed25519_BYTES,
    "pk must be 'extension_tweak_ed25519_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(scalarOut), 'scalarOut must be a typed array')
  assert(
    scalarOut.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "scalarOut must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(scalarIn), 'scalarIn must be a typed array')
  assert(
    scalarIn.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "scalarIn must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(tweak), 'tweak must be a typed array')
  assert(
    tweak.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "tweak must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )

  const res = binding.extension_tweak_ed25519_keypair_add(pk, scalarOut, scalarIn, tweak)

  if (res !== 0) throw new Error('failed to add tweak to keypair')
}

exports.extension_pbkdf2_sha512_async = function (out, passwd, salt, iter, outlen, callback) {
  assert(
    iter >= binding.extension_pbkdf2_sha512_ITERATIONS_MIN,
    "iter must be at least 'extension_pbkdf2_sha512_ITERATIONS_MIN'"
  )
  assert(
    outlen <= binding.extension_pbkdf2_sha512_BYTES_MAX,
    "outlen must be at most 'extension_pbkdf2_sha512_BYTES_MAX'"
  )
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(out.byteLength >= outlen, "out must be at least 'outlen' bytes")
  assert(out.byteLength > 0, 'out must not be empty')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(passwd.byteLength > 0, 'passwd must not be empty')
  assert(ArrayBuffer.isView(salt), 'salt must be a typed array')
  assert(salt.byteLength > 0, 'salt must not be empty')

  const [done, promise] = checkStatus(callback)

  binding.extension_pbkdf2_sha512_async(
    out.buffer,
    out.byteOffset,
    out.byteLength,

    passwd.buffer,
    passwd.byteOffset,
    passwd.byteLength,

    salt.buffer,
    salt.byteOffset,
    salt.byteLength,

    iter,
    outlen,

    done
  )

  return promise
}

exports.extension_pbkdf2_sha512 = function (out, passwd, salt, iter, outlen) {
  assert(
    iter >= binding.extension_pbkdf2_sha512_ITERATIONS_MIN,
    "iter must be at least 'extension_pbkdf2_sha512_ITERATIONS_MIN'"
  )
  assert(
    outlen <= binding.extension_pbkdf2_sha512_BYTES_MAX,
    "outlen must be at most 'extension_pbkdf2_sha512_BYTES_MAX'"
  )
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(out.byteLength >= outlen, "out must be at least 'outlen' bytes")
  assert(out.byteLength > 0, 'out must not be empty')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(passwd.byteLength > 0, 'passwd must not be empty')
  assert(ArrayBuffer.isView(salt), 'salt must be a typed array')
  assert(salt.byteLength > 0, 'salt must not be empty')

  const res = binding.extension_pbkdf2_sha512(out, passwd, salt, iter, outlen)

  if (res !== 0) throw new Error('failed to add tweak to public key')
}

function checkStatus(callback, booleanResult = false) {
  let done, promise

  if (typeof callback === 'function') {
    done = function (status) {
      if (booleanResult) callback(null, status === 0)
      else if (status === 0) callback(null)
      else callback(new Error('status: ' + status))
    }
  } else {
    promise = new Promise(function (resolve, reject) {
      done = function (status) {
        if (booleanResult) resolve(status === 0)
        else if (status === 0) resolve()
        else reject(new Error('status: ' + status))
      }
    })
  }

  return [done, promise]
}
{
  "name": "sodium-native",
  "version": "5.1.0",
  "description": "Low level bindings for libsodium",
  "main": "index.js",
  "files": [
    "index.js",
    "binding.cc",
    "binding.js",
    "extensions",
    "prebuilds",
    "CMakeLists.txt"
  ],
  "imports": {
    "assert": {
      "bare": "bare-assert",
      "node": "assert"
    }
  },
  "addon": true,
  "dependencies": {
    "bare-assert": "^1.2.0",
    "require-addon": "^1.1.0",
    "which-runtime": "^1.2.1"
  },
  "devDependencies": {
    "bare-compat-napi": "^1.3.5",
    "brittle": "^3.16.2",
    "cmake-bare": "^1.6.1",
    "cmake-fetch": "^1.4.3",
    "cmake-napi": "^1.2.1",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^2.0.0"
  },
  "scripts": {
    "test": "prettier . --check && npm run test:node && npm run test:bare",
    "test:node": "node test/all.js",
    "test:bare": "bare test/all.js"
  },
  "standard": {
    "ignore": [
      "/test/fixtures/*.js"
    ]
  },
  "engines": {
    "bare": ">=1.16.0"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/sodium-native.git"
  },
  "contributors": [
    "Emil Bay <github@tixz.dk> (http://bayes.dk)",
    "Mathias Buus <mathiasbuus@gmail.com> (https://mafinto.sh)",
    "Christophe Diederichs <chm-diederichs@hyperdivision.dk>"
  ],
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/holepunchto/sodium-native/issues"
  },
  "homepage": "https://github.com/holepunchto/sodium-native"
}
const sodium = require('sodium-universal')
const b4a = require('b4a')

const ABYTES = sodium.crypto_secretstream_xchacha20poly1305_ABYTES
const TAG_MESSAGE = sodium.crypto_secretstream_xchacha20poly1305_TAG_MESSAGE
const TAG_FINAL = sodium.crypto_secretstream_xchacha20poly1305_TAG_FINAL
const STATEBYTES = sodium.crypto_secretstream_xchacha20poly1305_STATEBYTES
const HEADERBYTES = sodium.crypto_secretstream_xchacha20poly1305_HEADERBYTES
const KEYBYTES = sodium.crypto_secretstream_xchacha20poly1305_KEYBYTES
const TAG_FINAL_BYTE = b4a.isBuffer(TAG_FINAL) ? TAG_FINAL[0] : TAG_FINAL

const EMPTY = b4a.alloc(0)
const TAG = b4a.alloc(1)

class Push {
  constructor (key, state = b4a.allocUnsafeSlow(STATEBYTES), header = b4a.allocUnsafeSlow(HEADERBYTES)) {
    if (!TAG_FINAL) throw new Error('JavaScript sodium version needs to support crypto_secretstream_xchacha20poly')

    this.key = key
    this.state = state
    this.header = header

    sodium.crypto_secretstream_xchacha20poly1305_init_push(this.state, this.header, this.key)
  }

  next (message, cipher = b4a.allocUnsafe(message.byteLength + ABYTES)) {
    sodium.crypto_secretstream_xchacha20poly1305_push(this.state, cipher, message, null, TAG_MESSAGE)
    return cipher
  }

  final (message = EMPTY, cipher = b4a.allocUnsafe(ABYTES)) {
    sodium.crypto_secretstream_xchacha20poly1305_push(this.state, cipher, message, null, TAG_FINAL)
    return cipher
  }
}

class Pull {
  constructor (key, state = b4a.allocUnsafeSlow(STATEBYTES)) {
    if (!TAG_FINAL) throw new Error('JavaScript sodium version needs to support crypto_secretstream_xchacha20poly')

    this.key = key
    this.state = state
    this.final = false
  }

  init (header) {
    sodium.crypto_secretstream_xchacha20poly1305_init_pull(this.state, header, this.key)
  }

  next (cipher, message = b4a.allocUnsafe(cipher.byteLength - ABYTES)) {
    sodium.crypto_secretstream_xchacha20poly1305_pull(this.state, message, TAG, cipher, null)
    this.final = TAG[0] === TAG_FINAL_BYTE
    return message
  }
}

function keygen (buf = b4a.alloc(KEYBYTES)) {
  sodium.crypto_secretstream_xchacha20poly1305_keygen(buf)
  return buf
}

module.exports = {
  keygen,
  KEYBYTES,
  ABYTES,
  STATEBYTES,
  HEADERBYTES,
  Push,
  Pull
}
{
  "name": "sodium-secretstream",
  "version": "1.2.0",
  "description": "Wraps libsodiums secretstream in a higher level abstraction",
  "main": "index.js",
  "dependencies": {
    "b4a": "^1.1.1",
    "sodium-universal": "^5.0.0"
  },
  "devDependencies": {
    "standard": "^17.0.0"
  },
  "scripts": {
    "test": "standard"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/mafintosh/sodium-secretstream.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/mafintosh/sodium-secretstream/issues"
  },
  "homepage": "https://github.com/mafintosh/sodium-secretstream"
}
module.exports = require('sodium-native')
{
  "name": "sodium-universal",
  "version": "5.0.1",
  "description": "Universal wrapper for sodium-javascript and sodium-native working in Node.js and the Browser",
  "main": "index.js",
  "dependencies": {
    "sodium-native": "^5.0.1"
  },
  "peerDependencies": {
    "sodium-javascript": "~0.8.0"
  },
  "peerDependenciesMeta": {
    "sodium-javascript": {
      "optional": true
    }
  },
  "scripts": {
    "prepublish": "./build-scripts/generate.js"
  },
  "browser": {
    "sodium-native": "sodium-javascript"
  },
  "browserify": {
    "transform": [
      "./build-scripts/transform.js"
    ]
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/sodium-universal.git"
  },
  "keywords": [
    "libsodium",
    "sodium",
    "sodium-native",
    "sodium-javascript",
    "browserify"
  ],
  "contributors": [
    "Emil Bay <github@tixz.dk> (http://bayes.dk)",
    "Mathias Buus <mathiasbuus@gmail.com> (https://mafinto.sh)",
    "Christophe Diederichs <chm-diederichs@hyperdivision.dk>"
  ],
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/holepunchto/sodium-universal/issues"
  },
  "homepage": "https://github.com/holepunchto/sodium-universal#readme"
}
const { EventEmitter } = require('events-universal')
const FIFO = require('fast-fifo')
const TextDecoder = require('text-decoder')

const StreamError = require('./lib/errors.js')

// if we do a future major, expect queue microtask to be there always, for now a bit defensive
const qmt =
  typeof queueMicrotask === 'undefined' ? (fn) => global.process.nextTick(fn) : queueMicrotask

// 29 bits used total (4 from shared, 14 from read, and 11 from write)
const MAX = (1 << 29) - 1

// Shared state
const OPENING = 0b0001
const PREDESTROYING = 0b0010
const DESTROYING = 0b0100
const DESTROYED = 0b1000

const NOT_OPENING = MAX ^ OPENING
const NOT_PREDESTROYING = MAX ^ PREDESTROYING

// Read state (4 bit offset from shared state)
const READ_ACTIVE = 0b00000000000001 << 4
const READ_UPDATING = 0b00000000000010 << 4
const READ_PRIMARY = 0b00000000000100 << 4
const READ_QUEUED = 0b00000000001000 << 4
const READ_RESUMED = 0b00000000010000 << 4
const READ_PIPE_DRAINED = 0b00000000100000 << 4
const READ_ENDING = 0b00000001000000 << 4
const READ_EMIT_DATA = 0b00000010000000 << 4
const READ_EMIT_READABLE = 0b00000100000000 << 4
const READ_EMITTED_READABLE = 0b00001000000000 << 4
const READ_DONE = 0b00010000000000 << 4
const READ_NEXT_TICK = 0b00100000000000 << 4
const READ_NEEDS_PUSH = 0b01000000000000 << 4
const READ_READ_AHEAD = 0b10000000000000 << 4

// Combined read state
const READ_FLOWING = READ_RESUMED | READ_PIPE_DRAINED
const READ_ACTIVE_AND_NEEDS_PUSH = READ_ACTIVE | READ_NEEDS_PUSH
const READ_PRIMARY_AND_ACTIVE = READ_PRIMARY | READ_ACTIVE
const READ_EMIT_READABLE_AND_QUEUED = READ_EMIT_READABLE | READ_QUEUED
const READ_RESUMED_READ_AHEAD = READ_RESUMED | READ_READ_AHEAD

const READ_NOT_ACTIVE = MAX ^ READ_ACTIVE
const READ_NON_PRIMARY = MAX ^ READ_PRIMARY
const READ_NON_PRIMARY_AND_PUSHED = MAX ^ (READ_PRIMARY | READ_NEEDS_PUSH)
const READ_PUSHED = MAX ^ READ_NEEDS_PUSH
const READ_PAUSED = MAX ^ READ_RESUMED
const READ_NOT_QUEUED = MAX ^ (READ_QUEUED | READ_EMITTED_READABLE)
const READ_NOT_ENDING = MAX ^ READ_ENDING
const READ_PIPE_NOT_DRAINED = MAX ^ READ_FLOWING
const READ_NOT_NEXT_TICK = MAX ^ READ_NEXT_TICK
const READ_NOT_UPDATING = MAX ^ READ_UPDATING
const READ_NO_READ_AHEAD = MAX ^ READ_READ_AHEAD
const READ_PAUSED_NO_READ_AHEAD = MAX ^ READ_RESUMED_READ_AHEAD

// Write state (18 bit offset, 4 bit offset from shared state and 14 from read state)
const WRITE_ACTIVE = 0b00000000001 << 18
const WRITE_UPDATING = 0b00000000010 << 18
const WRITE_PRIMARY = 0b00000000100 << 18
const WRITE_QUEUED = 0b00000001000 << 18
const WRITE_UNDRAINED = 0b00000010000 << 18
const WRITE_DONE = 0b00000100000 << 18
const WRITE_EMIT_DRAIN = 0b00001000000 << 18
const WRITE_NEXT_TICK = 0b00010000000 << 18
const WRITE_WRITING = 0b00100000000 << 18
const WRITE_FINISHING = 0b01000000000 << 18
const WRITE_CORKED = 0b10000000000 << 18

const WRITE_NOT_ACTIVE = MAX ^ (WRITE_ACTIVE | WRITE_WRITING)
const WRITE_NON_PRIMARY = MAX ^ WRITE_PRIMARY
const WRITE_NOT_FINISHING = MAX ^ (WRITE_ACTIVE | WRITE_FINISHING)
const WRITE_DRAINED = MAX ^ WRITE_UNDRAINED
const WRITE_NOT_QUEUED = MAX ^ WRITE_QUEUED
const WRITE_NOT_NEXT_TICK = MAX ^ WRITE_NEXT_TICK
const WRITE_NOT_UPDATING = MAX ^ WRITE_UPDATING
const WRITE_NOT_CORKED = MAX ^ WRITE_CORKED

// Combined shared state
const ACTIVE = READ_ACTIVE | WRITE_ACTIVE
const NOT_ACTIVE = MAX ^ ACTIVE
const DONE = READ_DONE | WRITE_DONE
const DESTROY_STATUS = DESTROYING | DESTROYED | PREDESTROYING
const OPEN_STATUS = DESTROY_STATUS | OPENING
const AUTO_DESTROY = DESTROY_STATUS | DONE
const NON_PRIMARY = WRITE_NON_PRIMARY & READ_NON_PRIMARY
const ACTIVE_OR_TICKING = WRITE_NEXT_TICK | READ_NEXT_TICK
const TICKING = ACTIVE_OR_TICKING & NOT_ACTIVE
const IS_OPENING = OPEN_STATUS | TICKING

// Combined shared state and read state
const READ_PRIMARY_STATUS = OPEN_STATUS | READ_ENDING | READ_DONE
const READ_STATUS = OPEN_STATUS | READ_DONE | READ_QUEUED
const READ_ENDING_STATUS = OPEN_STATUS | READ_ENDING | READ_QUEUED
const READ_READABLE_STATUS = OPEN_STATUS | READ_EMIT_READABLE | READ_QUEUED | READ_EMITTED_READABLE
const SHOULD_NOT_READ =
  OPEN_STATUS | READ_ACTIVE | READ_ENDING | READ_DONE | READ_NEEDS_PUSH | READ_READ_AHEAD
const READ_BACKPRESSURE_STATUS = DESTROY_STATUS | READ_ENDING | READ_DONE
const READ_UPDATE_SYNC_STATUS = READ_UPDATING | OPEN_STATUS | READ_NEXT_TICK | READ_PRIMARY
const READ_NEXT_TICK_OR_OPENING = READ_NEXT_TICK | OPENING

// Combined write state
const WRITE_PRIMARY_STATUS = OPEN_STATUS | WRITE_FINISHING | WRITE_DONE
const WRITE_QUEUED_AND_UNDRAINED = WRITE_QUEUED | WRITE_UNDRAINED
const WRITE_QUEUED_AND_ACTIVE = WRITE_QUEUED | WRITE_ACTIVE
const WRITE_DRAIN_STATUS = WRITE_QUEUED | WRITE_UNDRAINED | OPEN_STATUS | WRITE_ACTIVE
const WRITE_STATUS = OPEN_STATUS | WRITE_ACTIVE | WRITE_QUEUED | WRITE_CORKED
const WRITE_PRIMARY_AND_ACTIVE = WRITE_PRIMARY | WRITE_ACTIVE
const WRITE_ACTIVE_AND_WRITING = WRITE_ACTIVE | WRITE_WRITING
const WRITE_FINISHING_STATUS = OPEN_STATUS | WRITE_FINISHING | WRITE_QUEUED_AND_ACTIVE | WRITE_DONE
const WRITE_BACKPRESSURE_STATUS = WRITE_UNDRAINED | DESTROY_STATUS | WRITE_FINISHING | WRITE_DONE
const WRITE_UPDATE_SYNC_STATUS = WRITE_UPDATING | OPEN_STATUS | WRITE_NEXT_TICK | WRITE_PRIMARY
const WRITE_DROP_DATA = WRITE_FINISHING | WRITE_DONE | DESTROY_STATUS

const asyncIterator = Symbol.asyncIterator || Symbol('asyncIterator')

class WritableState {
  constructor(
    stream,
    { highWaterMark = 16384, map = null, mapWritable, byteLength, byteLengthWritable } = {}
  ) {
    this.stream = stream
    this.queue = new FIFO()
    this.highWaterMark = highWaterMark
    this.buffered = 0
    this.error = null
    this.pipeline = null
    this.drains = null // if we add more seldomly used helpers we might them into a subobject so its a single ptr
    this.byteLength = byteLengthWritable || byteLength || defaultByteLength
    this.map = mapWritable || map
    this.afterWrite = afterWrite.bind(this)
    this.afterUpdateNextTick = updateWriteNT.bind(this)
  }

  get ending() {
    return (this.stream._duplexState & WRITE_FINISHING) !== 0
  }

  get ended() {
    return (this.stream._duplexState & WRITE_DONE) !== 0
  }

  push(data) {
    if ((this.stream._duplexState & WRITE_DROP_DATA) !== 0) return false
    if (this.map !== null) data = this.map(data)

    this.buffered += this.byteLength(data)
    this.queue.push(data)

    if (this.buffered < this.highWaterMark) {
      this.stream._duplexState |= WRITE_QUEUED
      return true
    }

    this.stream._duplexState |= WRITE_QUEUED_AND_UNDRAINED
    return false
  }

  shift() {
    const data = this.queue.shift()

    this.buffered -= this.byteLength(data)
    if (this.buffered === 0) this.stream._duplexState &= WRITE_NOT_QUEUED

    return data
  }

  end(data) {
    if (typeof data === 'function') {
      this.stream.once('finish', data)
    } else if (data !== undefined && data !== null) {
      this.push(data)
    }

    this.stream._duplexState = (this.stream._duplexState | WRITE_FINISHING) & WRITE_NON_PRIMARY
  }

  autoBatch(data, cb) {
    const buffer = []
    const stream = this.stream

    buffer.push(data)
    while ((stream._duplexState & WRITE_STATUS) === WRITE_QUEUED_AND_ACTIVE) {
      buffer.push(stream._writableState.shift())
    }

    if ((stream._duplexState & OPEN_STATUS) !== 0) return cb(null)
    stream._writev(buffer, cb)
  }

  update() {
    const stream = this.stream

    stream._duplexState |= WRITE_UPDATING

    do {
      while ((stream._duplexState & WRITE_STATUS) === WRITE_QUEUED) {
        const data = this.shift()
        stream._duplexState |= WRITE_ACTIVE_AND_WRITING
        stream._write(data, this.afterWrite)
      }

      if ((stream._duplexState & WRITE_PRIMARY_AND_ACTIVE) === 0) this.updateNonPrimary()
    } while (this.continueUpdate() === true)

    stream._duplexState &= WRITE_NOT_UPDATING
  }

  updateNonPrimary() {
    const stream = this.stream

    if ((stream._duplexState & WRITE_FINISHING_STATUS) === WRITE_FINISHING) {
      stream._duplexState = stream._duplexState | WRITE_ACTIVE
      stream._final(afterFinal.bind(this))
      return
    }

    if ((stream._duplexState & DESTROY_STATUS) === DESTROYING) {
      if ((stream._duplexState & ACTIVE_OR_TICKING) === 0) {
        stream._duplexState |= ACTIVE
        stream._destroy(afterDestroy.bind(this))
      }
      return
    }

    if ((stream._duplexState & IS_OPENING) === OPENING) {
      stream._duplexState = (stream._duplexState | ACTIVE) & NOT_OPENING
      stream._open(afterOpen.bind(this))
    }
  }

  continueUpdate() {
    if ((this.stream._duplexState & WRITE_NEXT_TICK) === 0) return false
    this.stream._duplexState &= WRITE_NOT_NEXT_TICK
    return true
  }

  updateCallback() {
    if ((this.stream._duplexState & WRITE_UPDATE_SYNC_STATUS) === WRITE_PRIMARY) {
      this.update()
    } else {
      this.updateNextTick()
    }
  }

  updateNextTick() {
    if ((this.stream._duplexState & WRITE_NEXT_TICK) !== 0) return
    this.stream._duplexState |= WRITE_NEXT_TICK
    if ((this.stream._duplexState & WRITE_UPDATING) === 0) qmt(this.afterUpdateNextTick)
  }
}

class ReadableState {
  constructor(
    stream,
    { highWaterMark = 16384, map = null, mapReadable, byteLength, byteLengthReadable } = {}
  ) {
    this.stream = stream
    this.queue = new FIFO()
    this.highWaterMark = highWaterMark === 0 ? 1 : highWaterMark
    this.buffered = 0
    this.readAhead = highWaterMark > 0
    this.error = null
    this.pipeline = null
    this.byteLength = byteLengthReadable || byteLength || defaultByteLength
    this.map = mapReadable || map
    this.pipeTo = null
    this.afterRead = afterRead.bind(this)
    this.afterUpdateNextTick = updateReadNT.bind(this)
  }

  get ending() {
    return (this.stream._duplexState & READ_ENDING) !== 0
  }

  get ended() {
    return (this.stream._duplexState & READ_DONE) !== 0
  }

  pipe(pipeTo, cb) {
    if (this.pipeTo !== null) throw StreamError.BAD_ARGUMENT('Can only pipe to one destination')
    if (typeof cb !== 'function') cb = null

    this.stream._duplexState |= READ_PIPE_DRAINED
    this.pipeTo = pipeTo
    this.pipeline = new Pipeline(this.stream, pipeTo, cb)

    if (cb) this.stream.on('error', noop) // We already error handle this so supress crashes

    if (isStreamx(pipeTo)) {
      pipeTo._writableState.pipeline = this.pipeline
      if (cb) pipeTo.on('error', noop) // We already error handle this so supress crashes
      pipeTo.on('finish', this.pipeline.finished.bind(this.pipeline)) // TODO: just call finished from pipeTo itself
    } else {
      const onerror = this.pipeline.done.bind(this.pipeline, pipeTo)
      const onclose = this.pipeline.done.bind(this.pipeline, pipeTo, null) // onclose has a weird bool arg
      pipeTo.on('error', onerror)
      pipeTo.on('close', onclose)
      pipeTo.on('finish', this.pipeline.finished.bind(this.pipeline))
    }

    pipeTo.on('drain', afterDrain.bind(this))
    this.stream.emit('piping', pipeTo)
    pipeTo.emit('pipe', this.stream)
  }

  push(data) {
    const stream = this.stream

    if (data === null) {
      this.highWaterMark = 0
      stream._duplexState = (stream._duplexState | READ_ENDING) & READ_NON_PRIMARY_AND_PUSHED
      return false
    }

    if (this.map !== null) {
      data = this.map(data)
      if (data === null) {
        stream._duplexState &= READ_PUSHED
        return this.buffered < this.highWaterMark
      }
    }

    this.buffered += this.byteLength(data)
    this.queue.push(data)

    stream._duplexState = (stream._duplexState | READ_QUEUED) & READ_PUSHED

    return this.buffered < this.highWaterMark
  }

  shift() {
    const data = this.queue.shift()

    this.buffered -= this.byteLength(data)
    if (this.buffered === 0) {
      this.stream._duplexState &= READ_NOT_QUEUED
    }

    return data
  }

  unshift(data) {
    const pending = [this.map !== null ? this.map(data) : data]
    while (this.buffered > 0) pending.push(this.shift())

    for (let i = 0; i < pending.length - 1; i++) {
      const data = pending[i]
      this.buffered += this.byteLength(data)
      this.queue.push(data)
    }

    this.push(pending[pending.length - 1])
  }

  read() {
    const stream = this.stream

    if ((stream._duplexState & READ_STATUS) === READ_QUEUED) {
      const data = this.shift()

      if (this.pipeTo !== null && this.pipeTo.write(data) === false) {
        stream._duplexState &= READ_PIPE_NOT_DRAINED
      }

      if ((stream._duplexState & READ_EMIT_DATA) !== 0) {
        stream.emit('data', data)
      }

      return data
    }

    if (this.readAhead === false) {
      stream._duplexState |= READ_READ_AHEAD
      this.updateNextTick()
    }

    return null
  }

  drain() {
    const stream = this.stream

    while (
      (stream._duplexState & READ_STATUS) === READ_QUEUED &&
      (stream._duplexState & READ_FLOWING) !== 0
    ) {
      const data = this.shift()

      if (this.pipeTo !== null && this.pipeTo.write(data) === false) {
        stream._duplexState &= READ_PIPE_NOT_DRAINED
      }

      if ((stream._duplexState & READ_EMIT_DATA) !== 0) {
        stream.emit('data', data)
      }
    }
  }

  update() {
    const stream = this.stream

    stream._duplexState |= READ_UPDATING

    do {
      this.drain()

      while (
        this.buffered < this.highWaterMark &&
        (stream._duplexState & SHOULD_NOT_READ) === READ_READ_AHEAD
      ) {
        stream._duplexState |= READ_ACTIVE_AND_NEEDS_PUSH
        stream._read(this.afterRead)
        this.drain()
      }

      if ((stream._duplexState & READ_READABLE_STATUS) === READ_EMIT_READABLE_AND_QUEUED) {
        stream._duplexState |= READ_EMITTED_READABLE
        stream.emit('readable')
      }

      if ((stream._duplexState & READ_PRIMARY_AND_ACTIVE) === 0) {
        this.updateNonPrimary()
      }
    } while (this.continueUpdate() === true)

    stream._duplexState &= READ_NOT_UPDATING
  }

  updateNonPrimary() {
    const stream = this.stream

    if ((stream._duplexState & READ_ENDING_STATUS) === READ_ENDING) {
      stream._duplexState = (stream._duplexState | READ_DONE) & READ_NOT_ENDING
      stream.emit('end')

      if ((stream._duplexState & AUTO_DESTROY) === DONE) {
        stream._duplexState |= DESTROYING
      }

      if (this.pipeTo !== null) {
        this.pipeTo.end()
      }
    }

    if ((stream._duplexState & DESTROY_STATUS) === DESTROYING) {
      if ((stream._duplexState & ACTIVE_OR_TICKING) === 0) {
        stream._duplexState |= ACTIVE
        stream._destroy(afterDestroy.bind(this))
      }
      return
    }

    if ((stream._duplexState & IS_OPENING) === OPENING) {
      stream._duplexState = (stream._duplexState | ACTIVE) & NOT_OPENING
      stream._open(afterOpen.bind(this))
    }
  }

  continueUpdate() {
    if ((this.stream._duplexState & READ_NEXT_TICK) === 0) return false
    this.stream._duplexState &= READ_NOT_NEXT_TICK
    return true
  }

  updateCallback() {
    if ((this.stream._duplexState & READ_UPDATE_SYNC_STATUS) === READ_PRIMARY) {
      this.update()
    } else {
      this.updateNextTick()
    }
  }

  updateNextTickIfOpen() {
    if ((this.stream._duplexState & READ_NEXT_TICK_OR_OPENING) !== 0) return
    this.stream._duplexState |= READ_NEXT_TICK
    if ((this.stream._duplexState & READ_UPDATING) === 0) qmt(this.afterUpdateNextTick)
  }

  updateNextTick() {
    if ((this.stream._duplexState & READ_NEXT_TICK) !== 0) return
    this.stream._duplexState |= READ_NEXT_TICK
    if ((this.stream._duplexState & READ_UPDATING) === 0) qmt(this.afterUpdateNextTick)
  }
}

class TransformState {
  constructor(stream) {
    this.data = null
    this.afterTransform = afterTransform.bind(stream)
    this.afterFinal = null
  }
}

class Pipeline {
  constructor(src, dst, cb) {
    this.from = src
    this.to = dst
    this.afterPipe = cb
    this.error = null
    this.pipeToFinished = false
  }

  finished() {
    this.pipeToFinished = true
  }

  done(stream, err) {
    if (err) this.error = err

    if (stream === this.to) {
      this.to = null

      if (this.from !== null) {
        if ((this.from._duplexState & READ_DONE) === 0 || !this.pipeToFinished) {
          this.from.destroy(this.error || StreamError.PREMATURE_CLOSE('Writable stream closed'))
        }
        return
      }
    }

    if (stream === this.from) {
      this.from = null

      if (this.to !== null) {
        if ((stream._duplexState & READ_DONE) === 0) {
          this.to.destroy(this.error || StreamError.PREMATURE_CLOSE('Readable stream closed'))
        }
        return
      }
    }

    if (this.afterPipe !== null) this.afterPipe(this.error)
    this.to = this.from = this.afterPipe = null
  }
}

function afterDrain() {
  this.stream._duplexState |= READ_PIPE_DRAINED
  this.updateCallback()
}

function afterFinal(err) {
  const stream = this.stream
  if (err) stream.destroy(err)

  if ((stream._duplexState & DESTROY_STATUS) === 0) {
    stream._duplexState |= WRITE_DONE
    stream.emit('finish')
  }

  if ((stream._duplexState & AUTO_DESTROY) === DONE) {
    stream._duplexState |= DESTROYING
  }

  stream._duplexState &= WRITE_NOT_FINISHING

  // no need to wait the extra tick here, so we short circuit that
  if ((stream._duplexState & WRITE_UPDATING) === 0) {
    this.update()
  } else {
    this.updateNextTick()
  }
}

function afterDestroy(err) {
  const stream = this.stream

  if (!err && !StreamError.isStreamDestroyed(this.error)) err = this.error
  if (err) stream.emit('error', err)

  stream._duplexState |= DESTROYED
  stream.emit('close')

  const rs = stream._readableState
  const ws = stream._writableState

  if (rs !== null && rs.pipeline !== null) {
    rs.pipeline.done(stream, err)
  }

  if (ws !== null) {
    while (ws.drains !== null && ws.drains.length > 0) {
      ws.drains.shift().resolve(false)
    }

    if (ws.pipeline !== null) {
      ws.pipeline.done(stream, err)
    }
  }
}

function afterWrite(err) {
  const stream = this.stream

  if (err) stream.destroy(err)
  stream._duplexState &= WRITE_NOT_ACTIVE

  if (this.drains !== null) tickDrains(this.drains)

  if ((stream._duplexState & WRITE_DRAIN_STATUS) === WRITE_UNDRAINED) {
    stream._duplexState &= WRITE_DRAINED

    if ((stream._duplexState & WRITE_EMIT_DRAIN) === WRITE_EMIT_DRAIN) {
      stream.emit('drain')
    }
  }

  this.updateCallback()
}

function afterRead(err) {
  if (err) this.stream.destroy(err)
  this.stream._duplexState &= READ_NOT_ACTIVE

  if (this.readAhead === false && (this.stream._duplexState & READ_RESUMED) === 0) {
    this.stream._duplexState &= READ_NO_READ_AHEAD
  }

  this.updateCallback()
}

function updateReadNT() {
  if ((this.stream._duplexState & READ_UPDATING) === 0) {
    this.stream._duplexState &= READ_NOT_NEXT_TICK
    this.update()
  }
}

function updateWriteNT() {
  if ((this.stream._duplexState & WRITE_UPDATING) === 0) {
    this.stream._duplexState &= WRITE_NOT_NEXT_TICK
    this.update()
  }
}

function tickDrains(drains) {
  for (let i = 0; i < drains.length; i++) {
    // drains.writes are monotonic, so if one is 0 its always the first one
    if (--drains[i].writes === 0) {
      drains.shift().resolve(true)
      i--
    }
  }
}

function afterOpen(err) {
  const stream = this.stream

  if (err) stream.destroy(err)

  if ((stream._duplexState & DESTROYING) === 0) {
    if ((stream._duplexState & READ_PRIMARY_STATUS) === 0) {
      stream._duplexState |= READ_PRIMARY
    }

    if ((stream._duplexState & WRITE_PRIMARY_STATUS) === 0) {
      stream._duplexState |= WRITE_PRIMARY
    }

    stream.emit('open')
  }

  stream._duplexState &= NOT_ACTIVE

  if (stream._writableState !== null) {
    stream._writableState.updateCallback()
  }

  if (stream._readableState !== null) {
    stream._readableState.updateCallback()
  }
}

function afterTransform(err, data) {
  if (data !== undefined && data !== null) this.push(data)
  this._writableState.afterWrite(err)
}

function newListener(name) {
  if (this._readableState !== null) {
    if (name === 'data') {
      this._duplexState |= READ_EMIT_DATA | READ_RESUMED_READ_AHEAD
      this._readableState.updateNextTick()
    }
    if (name === 'readable') {
      this._duplexState |= READ_EMIT_READABLE
      this._readableState.updateNextTick()
    }
  }

  if (this._writableState !== null) {
    if (name === 'drain') {
      this._duplexState |= WRITE_EMIT_DRAIN
      this._writableState.updateNextTick()
    }
  }
}

class Stream extends EventEmitter {
  constructor(opts) {
    super()

    this._duplexState = 0
    this._readableState = null
    this._writableState = null

    if (opts) {
      if (opts.open) this._open = opts.open
      if (opts.destroy) this._destroy = opts.destroy
      if (opts.predestroy) this._predestroy = opts.predestroy
      if (opts.signal) opts.signal.addEventListener('abort', abort.bind(this))
    }

    this.on('newListener', newListener)
  }

  _open(cb) {
    cb(null)
  }

  _destroy(cb) {
    cb(null)
  }

  _predestroy() {
    // does nothing
  }

  get readable() {
    return this._readableState !== null ? true : undefined
  }

  get writable() {
    return this._writableState !== null ? true : undefined
  }

  get destroyed() {
    return (this._duplexState & DESTROYED) !== 0
  }

  get destroying() {
    return (this._duplexState & DESTROY_STATUS) !== 0
  }

  destroy(err) {
    if ((this._duplexState & DESTROY_STATUS) === 0) {
      if (!err) err = StreamError.STREAM_DESTROYED()
      this._duplexState = (this._duplexState | DESTROYING) & NON_PRIMARY

      if (this._readableState !== null) {
        this._readableState.highWaterMark = 0
        this._readableState.error = err
      }

      if (this._writableState !== null) {
        this._writableState.highWaterMark = 0
        this._writableState.error = err
      }

      this._duplexState |= PREDESTROYING
      this._predestroy()
      this._duplexState &= NOT_PREDESTROYING

      if (this._readableState !== null) {
        this._readableState.updateNextTick()
      }

      if (this._writableState !== null) {
        this._writableState.updateNextTick()
      }
    }
  }
}

class Readable extends Stream {
  constructor(opts) {
    super(opts)

    this._duplexState |= OPENING | WRITE_DONE | READ_READ_AHEAD
    this._readableState = new ReadableState(this, opts)

    if (opts) {
      if (this._readableState.readAhead === false) this._duplexState &= READ_NO_READ_AHEAD
      if (opts.read) this._read = opts.read
      if (opts.eagerOpen) this._readableState.updateNextTick()
      if (opts.encoding) this.setEncoding(opts.encoding)
    }
  }

  static deferred(fn, opts) {
    const out = new PassThrough(opts)

    fn()
      .then((src) => {
        if (src === null) return out.end()
        if (out.destroying) return
        pipeline(src, out, noop)
      })
      .catch((err) => out.destroy(err))

    return out
  }

  setEncoding(encoding) {
    const dec = new TextDecoder(encoding)
    const map = this._readableState.map || echo
    this._readableState.map = mapOrSkip
    return this

    function mapOrSkip(data) {
      const next = dec.push(data)
      return next === '' && (data.byteLength !== 0 || dec.remaining > 0) ? null : map(next)
    }
  }

  _read(cb) {
    cb(null)
  }

  pipe(dest, cb) {
    this._readableState.updateNextTick()
    this._readableState.pipe(dest, cb)
    return dest
  }

  read() {
    this._readableState.updateNextTick()
    return this._readableState.read()
  }

  push(data) {
    this._readableState.updateNextTickIfOpen()
    return this._readableState.push(data)
  }

  unshift(data) {
    this._readableState.updateNextTickIfOpen()
    return this._readableState.unshift(data)
  }

  resume() {
    this._duplexState |= READ_RESUMED_READ_AHEAD
    this._readableState.updateNextTick()
    return this
  }

  pause() {
    this._duplexState &=
      this._readableState.readAhead === false ? READ_PAUSED_NO_READ_AHEAD : READ_PAUSED
    return this
  }

  static _fromAsyncIterator(ite, opts) {
    let destroy

    const rs = new Readable({
      ...opts,
      read(cb) {
        ite.next().then(push).then(cb.bind(null, null)).catch(cb)
      },
      predestroy() {
        destroy = ite.return()
      },
      destroy(cb) {
        if (!destroy) return cb(null)
        destroy.then(cb.bind(null, null)).catch(cb)
      }
    })

    return rs

    function push(data) {
      if (data.done) rs.push(null)
      else rs.push(data.value)
    }
  }

  static from(data, opts) {
    if (isReadStreamx(data)) return data
    if (data[asyncIterator]) return this._fromAsyncIterator(data[asyncIterator](), opts)
    if (!Array.isArray(data)) data = data === undefined ? [] : [data]

    let i = 0
    return new Readable({
      ...opts,
      read(cb) {
        this.push(i === data.length ? null : data[i++])
        cb(null)
      }
    })
  }

  static isBackpressured(rs) {
    return (
      (rs._duplexState & READ_BACKPRESSURE_STATUS) !== 0 ||
      rs._readableState.buffered >= rs._readableState.highWaterMark
    )
  }

  static isPaused(rs) {
    return (rs._duplexState & READ_RESUMED) === 0
  }

  [asyncIterator]() {
    const stream = this

    let error = null
    let promiseResolve = null
    let promiseReject = null

    this.on('error', (err) => {
      error = err
    })
    this.on('readable', onreadable)
    this.on('close', onclose)

    return {
      [asyncIterator]() {
        return this
      },
      next() {
        return new Promise(function (resolve, reject) {
          promiseResolve = resolve
          promiseReject = reject
          const data = stream.read()
          if (data !== null) ondata(data)
          else if ((stream._duplexState & DESTROYED) !== 0) ondata(null)
        })
      },
      return() {
        return destroy(null)
      },
      throw(err) {
        return destroy(err)
      }
    }

    function onreadable() {
      if (promiseResolve !== null) ondata(stream.read())
    }

    function onclose() {
      if (promiseResolve !== null) ondata(null)
    }

    function ondata(data) {
      if (promiseReject === null) return
      if (error) {
        promiseReject(error)
      } else if (data === null && (stream._duplexState & READ_DONE) === 0) {
        promiseReject(StreamError.STREAM_DESTROYED())
      } else {
        promiseResolve({ value: data, done: data === null })
      }
      promiseReject = promiseResolve = null
    }

    function destroy(err) {
      stream.destroy(err)
      return new Promise((resolve, reject) => {
        if (stream._duplexState & DESTROYED) return resolve({ value: undefined, done: true })
        stream.once('close', function () {
          if (err) reject(err)
          else resolve({ value: undefined, done: true })
        })
      })
    }
  }
}

class Writable extends Stream {
  constructor(opts) {
    super(opts)

    this._duplexState |= OPENING | READ_DONE
    this._writableState = new WritableState(this, opts)

    if (opts) {
      if (opts.writev) this._writev = opts.writev
      if (opts.write) this._write = opts.write
      if (opts.final) this._final = opts.final
      if (opts.eagerOpen) this._writableState.updateNextTick()
    }
  }

  cork() {
    this._duplexState |= WRITE_CORKED
  }

  uncork() {
    this._duplexState &= WRITE_NOT_CORKED
    this._writableState.updateNextTick()
  }

  _writev(batch, cb) {
    cb(null)
  }

  _write(data, cb) {
    this._writableState.autoBatch(data, cb)
  }

  _final(cb) {
    cb(null)
  }

  static isBackpressured(ws) {
    return (ws._duplexState & WRITE_BACKPRESSURE_STATUS) !== 0
  }

  static drained(ws) {
    if (ws.destroyed) return Promise.resolve(false)

    const state = ws._writableState
    const pending = isWritev(ws) ? Math.min(1, state.queue.length) : state.queue.length
    const writes = pending + (ws._duplexState & WRITE_WRITING ? 1 : 0)
    if (writes === 0) return Promise.resolve(true)

    if (state.drains === null) state.drains = []
    return new Promise((resolve) => {
      state.drains.push({ writes, resolve })
    })
  }

  write(data) {
    this._writableState.updateNextTick()
    return this._writableState.push(data)
  }

  end(data) {
    this._writableState.updateNextTick()
    this._writableState.end(data)
    return this
  }
}

class Duplex extends Readable {
  // and Writable
  constructor(opts) {
    super(opts)

    this._duplexState = OPENING | (this._duplexState & READ_READ_AHEAD)
    this._writableState = new WritableState(this, opts)

    if (opts) {
      if (opts.writev) this._writev = opts.writev
      if (opts.write) this._write = opts.write
      if (opts.final) this._final = opts.final
    }
  }

  cork() {
    this._duplexState |= WRITE_CORKED
  }

  uncork() {
    this._duplexState &= WRITE_NOT_CORKED
    this._writableState.updateNextTick()
  }

  _writev(batch, cb) {
    cb(null)
  }

  _write(data, cb) {
    this._writableState.autoBatch(data, cb)
  }

  _final(cb) {
    cb(null)
  }

  write(data) {
    this._writableState.updateNextTick()
    return this._writableState.push(data)
  }

  end(data) {
    this._writableState.updateNextTick()
    this._writableState.end(data)
    return this
  }
}

class Transform extends Duplex {
  constructor(opts) {
    super(opts)
    this._transformState = new TransformState(this)

    if (opts) {
      if (opts.transform) this._transform = opts.transform
      if (opts.flush) this._flush = opts.flush
    }
  }

  _write(data, cb) {
    if (this._readableState.buffered >= this._readableState.highWaterMark) {
      this._transformState.data = data
    } else {
      this._transform(data, this._transformState.afterTransform)
    }
  }

  _read(cb) {
    if (this._transformState.data !== null) {
      const data = this._transformState.data
      this._transformState.data = null
      cb(null)
      this._transform(data, this._transformState.afterTransform)
    } else {
      cb(null)
    }
  }

  destroy(err) {
    super.destroy(err)
    if (this._transformState.data !== null) {
      this._transformState.data = null
      this._transformState.afterTransform()
    }
  }

  _transform(data, cb) {
    cb(null, data)
  }

  _flush(cb) {
    cb(null)
  }

  _final(cb) {
    this._transformState.afterFinal = cb
    this._flush(transformAfterFlush.bind(this))
  }
}

class PassThrough extends Transform {}

function transformAfterFlush(err, data) {
  const cb = this._transformState.afterFinal
  if (err) return cb(err)

  if (data !== null && data !== undefined) this.push(data)
  this.push(null)
  cb(null)
}

function pipelinePromise(...streams) {
  return new Promise((resolve, reject) => {
    return pipeline(...streams, (err) => {
      if (err) return reject(err)
      resolve()
    })
  })
}

function pipeline(stream, ...streams) {
  const all = Array.isArray(stream) ? [...stream, ...streams] : [stream, ...streams]
  const done = all.length && typeof all[all.length - 1] === 'function' ? all.pop() : null

  if (all.length < 2) throw StreamError.BAD_ARGUMENT('Pipeline requires at least 2 streams')

  let src = all[0]
  let dest = null
  let error = null

  for (let i = 1; i < all.length; i++) {
    dest = all[i]

    if (isStreamx(src)) {
      src.pipe(dest, onerror)
    } else {
      errorHandle(src, true, i > 1, onerror)
      src.pipe(dest)
    }

    src = dest
  }

  if (done) {
    let fin = false

    const autoDestroy =
      isStreamx(dest) || !!(dest._writableState && dest._writableState.autoDestroy)

    dest.on('error', (err) => {
      if (error === null) error = err
    })

    dest.on('finish', () => {
      fin = true
      if (!autoDestroy) done(error)
    })

    if (autoDestroy) {
      dest.on('close', () => done(error || (fin ? null : StreamError.PREMATURE_CLOSE())))
    }
  }

  return dest

  function errorHandle(s, rd, wr, onerror) {
    s.on('error', onerror)
    s.on('close', onclose)

    function onclose() {
      if (rd && s._readableState && !s._readableState.ended) {
        return onerror(StreamError.PREMATURE_CLOSE())
      }
      if (wr && s._writableState && !s._writableState.ended) {
        return onerror(StreamError.PREMATURE_CLOSE())
      }
    }
  }

  function onerror(err) {
    if (!err || error) return
    error = err

    for (const s of all) {
      s.destroy(err)
    }
  }
}

function echo(s) {
  return s
}

function isStream(stream) {
  return !!stream._readableState || !!stream._writableState
}

function isStreamx(stream) {
  return typeof stream._duplexState === 'number' && isStream(stream)
}

function isEnding(stream) {
  return !!stream._readableState && stream._readableState.ending
}

function isEnded(stream) {
  return !!stream._readableState && stream._readableState.ended
}

function isFinishing(stream) {
  return !!stream._writableState && stream._writableState.ending
}

function isFinished(stream) {
  return !!stream._writableState && stream._writableState.ended
}

function getStreamError(stream, opts = {}) {
  const err =
    (stream._readableState && stream._readableState.error) ||
    (stream._writableState && stream._writableState.error)

  // avoid implicit errors by default
  return !opts.all && StreamError.isStreamDestroyed(err) ? null : err
}

function isReadStreamx(stream) {
  return isStreamx(stream) && stream.readable
}

function isDisturbed(stream) {
  return (
    (stream._duplexState & OPENING) !== OPENING ||
    (stream._duplexState & DESTROYING) === DESTROYING ||
    (stream._duplexState & ACTIVE_OR_TICKING) !== 0
  )
}

function isTypedArray(data) {
  return typeof data === 'object' && data !== null && typeof data.byteLength === 'number'
}

function defaultByteLength(data) {
  return isTypedArray(data) ? data.byteLength : 1024
}

function noop() {}

function abort() {
  this.destroy(StreamError.ABORTED())
}

function isWritev(s) {
  return s._writev !== Writable.prototype._writev && s._writev !== Duplex.prototype._writev
}

module.exports = {
  pipeline,
  pipelinePromise,
  isStream,
  isStreamx,
  isEnding,
  isEnded,
  isFinishing,
  isFinished,
  isDisturbed,
  getStreamError,
  Stream,
  Writable,
  Readable,
  Duplex,
  Transform,
  // Export PassThrough for compatibility with Node.js core's stream module
  PassThrough
}
module.exports = class StreamError extends Error {
  constructor(msg, code, fn = StreamError) {
    super(msg)

    this.code = code

    if (Error.captureStackTrace) {
      Error.captureStackTrace(this, fn)
    }
  }

  static isStreamDestroyed(err) {
    return err && err.code === 'STREAM_DESTROYED'
  }

  static isPrematureClose(err) {
    return err && err.code === 'PREMATURE_CLOSE'
  }

  static isAborted(err) {
    return err && err.code === 'ABORTED'
  }

  static isBadArgument(err) {
    return err && err.code === 'BAD_ARGUMENT'
  }

  get name() {
    return 'StreamError'
  }

  static STREAM_DESTROYED() {
    return new StreamError('Stream was destroyed', 'STREAM_DESTROYED', StreamError.STREAM_DESTROYED)
  }

  static PREMATURE_CLOSE(msg = 'Premature close') {
    return new StreamError(msg, 'PREMATURE_CLOSE', StreamError.PREMATURE_CLOSE)
  }

  static ABORTED() {
    return new StreamError('Stream aborted', 'ABORTED', StreamError.ABORTED)
  }

  static BAD_ARGUMENT(msg = 'Bad argument') {
    return new StreamError(msg, 'BAD_ARGUMENT', StreamError.BAD_ARGUMENT)
  }
}
{
  "name": "streamx",
  "version": "2.27.0",
  "description": "An iteration of the Node.js core streams with a series of improvements",
  "main": "index.js",
  "exports": {
    ".": "./index.js",
    "./package": "./package.json",
    "./errors": "./lib/errors"
  },
  "dependencies": {
    "events-universal": "^1.0.0",
    "fast-fifo": "^1.3.2",
    "text-decoder": "^1.1.0"
  },
  "devDependencies": {
    "b4a": "^1.6.6",
    "brittle": "^3.1.1",
    "end-of-stream": "^1.4.4",
    "lunte": "^1.8.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^2.0.0"
  },
  "files": [
    "index.js",
    "lib/errors.js"
  ],
  "scripts": {
    "format": "prettier . --write",
    "test": "brittle test/all.js && brittle-bare test/all.js",
    "lint": "prettier . --check && lunte"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/mafintosh/streamx.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/mafintosh/streamx/issues"
  },
  "homepage": "https://github.com/mafintosh/streamx"
}
const codecs = require('codecs')
const b = require('b4a')

const SEP = b.alloc(1)
const SEP_BUMPED = b.from([0x1])
const EMPTY = b.alloc(0)

module.exports = class SubEncoder {
  constructor (prefix, encoding, parent = null) {
    this.userEncoding = codecs(encoding)
    this.prefix = prefix != null ? createPrefix(prefix, parent) : null
    this.lt = this.prefix && b.concat([this.prefix.subarray(0, this.prefix.byteLength - 1), SEP_BUMPED])
  }

  _encodeRangeUser (r) {
    if (this.userEncoding.encodeRange) return this.userEncoding.encodeRange(r)

    const res = {}
    if (r.gt != null) res.gt = this.userEncoding.encode(r.gt)
    if (r.gte != null) res.gte = this.userEncoding.encode(r.gte)
    if (r.lte != null) res.lte = this.userEncoding.encode(r.lte)
    if (r.lt != null) res.lt = this.userEncoding.encode(r.lt)

    return res
  }

  _addPrefix (key) {
    return this.prefix ? b.concat([this.prefix, key]) : key
  }

  encode (key) {
    return this._addPrefix(this.userEncoding.encode(key))
  }

  encodeRange (range) {
    const r = this._encodeRangeUser(range)

    if (r.gt) r.gt = this._addPrefix(r.gt)
    else if (r.gte) r.gte = this._addPrefix(r.gte)
    else if (this.prefix) r.gte = this.prefix

    if (r.lt) r.lt = this._addPrefix(r.lt)
    else if (r.lte) r.lte = this._addPrefix(r.lte)
    else if (this.prefix) r.lt = this.lt

    return r
  }

  decode (key) {
    return this.userEncoding.decode(this.prefix ? key.subarray(this.prefix.byteLength) : key)
  }

  sub (prefix, encoding) {
    return new SubEncoder(prefix || EMPTY, compat(encoding), this.prefix)
  }
}

function createPrefix (prefix, parent) {
  prefix = typeof prefix === 'string' ? b.from(prefix) : prefix

  if (prefix && parent) return b.concat([parent, prefix, SEP])
  if (prefix) return b.concat([prefix, SEP])
  if (parent) return b.concat([parent, SEP])
  return SEP
}

function compat (enc) {
  if (enc && enc.keyEncoding) return enc.keyEncoding
  return enc
}
{
  "name": "sub-encoder",
  "version": "2.1.3",
  "description": "Generate sub encodings for key/value stores",
  "main": "index.js",
  "scripts": {
    "test": "standard && brittle test/*.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/sub-encoder.git"
  },
  "keywords": [
    "kv-store",
    "encoding",
    "hyperbee"
  ],
  "author": "Andrew Osheroff <andrewosh@gmail.com>",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/sub-encoder/issues"
  },
  "homepage": "https://github.com/holepunchto/sub-encoder#readme",
  "devDependencies": {
    "brittle": "^3.1.1",
    "compact-encoding": "^2.12.0",
    "hyperbee": "^2.11.0",
    "hypercore": "^10.3.2",
    "index-encoder": "^3.0.0",
    "random-access-memory": "^6.0.0",
    "standard": "^17.0.0"
  },
  "dependencies": {
    "b4a": "^1.6.0",
    "codecs": "^3.1.0"
  }
}
const { Readable } = require('streamx')

module.exports = function (s, forks = 2) {
  const streams = new Array(forks)
  const status = new Array(forks).fill(true)

  let ended = false

  for (let i = 0; i < forks; i++) {
    streams[i] = new Readable({
      read (cb) {
        const check = !status[i]
        status[i] = true
        if (check && allReadable()) s.resume()
        cb(null)
      }
    })
  }

  s.on('end', function () {
    ended = true
    for (const stream of streams) stream.push(null)
  })

  s.on('error', function (err) {
    for (const stream of streams) stream.destroy(err)
  })

  s.on('close', function () {
    if (ended) return
    for (const stream of streams) stream.destroy()
  })

  s.on('data', function (data) {
    let needsPause = false
    for (let i = 0; i < streams.length; i++) {
      if (!(status[i] = streams[i].push(data))) {
        needsPause = true
      }
    }
    if (needsPause) s.pause()
  })

  return streams

  function allReadable () {
    for (let j = 0; j < status.length; j++) {
      if (!status[j]) return false
    }
    return true
  }
}
{
  "name": "teex",
  "version": "1.0.1",
  "description": "Turn a readable stream into multiple readable streamx streams",
  "main": "index.js",
  "dependencies": {
    "streamx": "^2.12.5"
  },
  "devDependencies": {
    "standard": "^14.3.4",
    "tape": "^5.0.1"
  },
  "scripts": {
    "test": "standard && tape test.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/mafintosh/teex.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/mafintosh/teex/issues"
  },
  "homepage": "https://github.com/mafintosh/teex"
}
const PassThroughDecoder = require('./lib/pass-through-decoder')
const UTF8Decoder = require('./lib/utf8-decoder')

module.exports = class TextDecoder {
  constructor(encoding = 'utf8') {
    this.encoding = normalizeEncoding(encoding)

    switch (this.encoding) {
      case 'utf8':
        this.decoder = new UTF8Decoder()
        break
      case 'utf16le':
      case 'base64':
        throw new Error('Unsupported encoding: ' + this.encoding)
      default:
        this.decoder = new PassThroughDecoder(this.encoding)
    }
  }

  get remaining() {
    return this.decoder.remaining
  }

  push(data) {
    if (typeof data === 'string') return data
    return this.decoder.decode(data)
  }

  // For Node.js compatibility
  write(data) {
    return this.push(data)
  }

  end(data) {
    let result = ''
    if (data) result = this.push(data)
    result += this.decoder.flush()
    return result
  }
}

function normalizeEncoding(encoding) {
  encoding = encoding.toLowerCase()

  switch (encoding) {
    case 'utf8':
    case 'utf-8':
      return 'utf8'
    case 'ucs2':
    case 'ucs-2':
    case 'utf16le':
    case 'utf-16le':
      return 'utf16le'
    case 'latin1':
    case 'binary':
      return 'latin1'
    case 'base64':
    case 'ascii':
    case 'hex':
      return encoding
    default:
      throw new Error('Unknown encoding: ' + encoding)
  }
}
const b4a = require('b4a')

module.exports = class PassThroughDecoder {
  constructor(encoding) {
    this.encoding = encoding
  }

  get remaining() {
    return 0
  }

  decode(data) {
    return b4a.toString(data, this.encoding)
  }

  flush() {
    return ''
  }
}
const b4a = require('b4a')

/**
 * https://encoding.spec.whatwg.org/#utf-8-decoder
 */
module.exports = class UTF8Decoder {
  constructor() {
    this._reset()
  }

  get remaining() {
    return this.bytesSeen
  }

  decode(data) {
    if (data.byteLength === 0) return ''

    if (this.bytesNeeded === 0 && trailingIncomplete(data, 0) === 0) {
      this.bytesSeen = trailingBytesSeen(data)
      return b4a.toString(data, 'utf8')
    }

    let result = ''
    let start = 0

    if (this.bytesNeeded > 0) {
      while (start < data.byteLength) {
        const byte = data[start]

        if (byte < this.lowerBoundary || byte > this.upperBoundary) {
          result += '\ufffd'
          this._reset()
          break
        }

        this.lowerBoundary = 0x80
        this.upperBoundary = 0xbf
        this.codePoint = (this.codePoint << 6) | (byte & 0x3f)
        this.bytesSeen++
        start++

        if (this.bytesSeen === this.bytesNeeded) {
          result += String.fromCodePoint(this.codePoint)
          this._reset()
          break
        }
      }

      if (this.bytesNeeded > 0) return result
    }

    const trailing = trailingIncomplete(data, start)
    const end = data.byteLength - trailing

    if (end > start) result += b4a.toString(data, 'utf8', start, end)

    for (let i = end; i < data.byteLength; i++) {
      const byte = data[i]

      if (this.bytesNeeded === 0) {
        if (byte <= 0x7f) {
          this.bytesSeen = 0
          result += String.fromCharCode(byte)
        } else if (byte >= 0xc2 && byte <= 0xdf) {
          this.bytesNeeded = 2
          this.bytesSeen = 1
          this.codePoint = byte & 0x1f
        } else if (byte >= 0xe0 && byte <= 0xef) {
          if (byte === 0xe0) this.lowerBoundary = 0xa0
          else if (byte === 0xed) this.upperBoundary = 0x9f
          this.bytesNeeded = 3
          this.bytesSeen = 1
          this.codePoint = byte & 0xf
        } else if (byte >= 0xf0 && byte <= 0xf4) {
          if (byte === 0xf0) this.lowerBoundary = 0x90
          else if (byte === 0xf4) this.upperBoundary = 0x8f
          this.bytesNeeded = 4
          this.bytesSeen = 1
          this.codePoint = byte & 0x7
        } else {
          this.bytesSeen = 1
          result += '\ufffd'
        }

        continue
      }

      if (byte < this.lowerBoundary || byte > this.upperBoundary) {
        result += '\ufffd'
        i--
        this._reset()

        continue
      }

      this.lowerBoundary = 0x80
      this.upperBoundary = 0xbf

      this.codePoint = (this.codePoint << 6) | (byte & 0x3f)
      this.bytesSeen++

      if (this.bytesSeen === this.bytesNeeded) {
        result += String.fromCodePoint(this.codePoint)
        this._reset()
      }
    }

    return result
  }

  flush() {
    const result = this.bytesNeeded > 0 ? '\ufffd' : ''
    this._reset()
    return result
  }

  _reset() {
    this.codePoint = 0
    this.bytesNeeded = 0
    this.bytesSeen = 0
    this.lowerBoundary = 0x80
    this.upperBoundary = 0xbf
  }
}

function trailingIncomplete(data, start) {
  const len = data.byteLength
  if (len <= start) return 0

  const limit = Math.max(start, len - 4)

  let i = len - 1
  while (i > limit && (data[i] & 0xc0) === 0x80) i--

  if (i < start) return 0

  const byte = data[i]

  let needed
  if (byte <= 0x7f) return 0
  if (byte >= 0xc2 && byte <= 0xdf) needed = 2
  else if (byte >= 0xe0 && byte <= 0xef) needed = 3
  else if (byte >= 0xf0 && byte <= 0xf4) needed = 4
  else return 0

  const available = len - i
  return available < needed ? available : 0
}

function trailingBytesSeen(data) {
  const len = data.byteLength
  if (len === 0) return 0

  const last = data[len - 1]

  if (last <= 0x7f) return 0
  if ((last & 0xc0) !== 0x80) return 1

  const limit = Math.max(0, len - 4)

  let i = len - 2
  while (i >= limit && (data[i] & 0xc0) === 0x80) i--

  if (i < 0) return 1

  const first = data[i]

  let needed
  if (first >= 0xc2 && first <= 0xdf) needed = 2
  else if (first >= 0xe0 && first <= 0xef) needed = 3
  else if (first >= 0xf0 && first <= 0xf4) needed = 4
  else return 1

  if (len - i !== needed) return 1

  if (needed >= 3) {
    const second = data[i + 1]
    if (first === 0xe0 && second < 0xa0) return 1
    if (first === 0xed && second > 0x9f) return 1
    if (first === 0xf0 && second < 0x90) return 1
    if (first === 0xf4 && second > 0x8f) return 1
  }

  return 0
}
{
  "name": "text-decoder",
  "version": "1.2.7",
  "description": "Streaming text decoder that preserves multibyte Unicode characters",
  "exports": {
    "./package": "./package.json",
    ".": "./index.js"
  },
  "files": [
    "index.js",
    "lib"
  ],
  "scripts": {
    "format": "prettier --write .",
    "lint": "prettier --check . && lunte",
    "test": "npm run test:node && npm run test:bare",
    "test:node": "brittle-node test.js",
    "test:bare": "brittle-bare test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/text-decoder.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/text-decoder/issues"
  },
  "homepage": "https://github.com/holepunchto/text-decoder#readme",
  "dependencies": {
    "b4a": "^1.6.4"
  },
  "devDependencies": {
    "brittle": "^3.3.2",
    "lunte": "^1.3.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^1.0.0"
  }
}
module.exports = class TimeOrderedSet {
  constructor () {
    this.oldest = null
    this.latest = null
    this.length = 0
  }

  has (node) {
    return !!(node.next || node.prev) || node === this.oldest
  }

  add (node) {
    if (this.has(node)) this.remove(node)

    if (!this.latest && !this.oldest) {
      this.latest = this.oldest = node
      node.prev = node.next = null
    } else {
      this.latest.next = node
      node.prev = this.latest
      node.next = null
      this.latest = node
    }

    this.length++

    return node
  }

  remove (node) {
    if (!this.has(node)) return node

    if (this.oldest !== node && this.latest !== node) {
      node.prev.next = node.next
      node.next.prev = node.prev
    } else {
      if (this.oldest === node) {
        this.oldest = node.next
        if (this.oldest) this.oldest.prev = null
      }
      if (this.latest === node) {
        this.latest = node.prev
        if (this.latest) this.latest.next = null
      }
    }

    node.next = node.prev = null
    this.length--

    return node
  }

  toArray ({ limit = Infinity, reverse = false } = {}) {
    const list = []

    if (reverse) {
      let node = this.latest
      while (node && limit--) {
        list.push(node)
        node = node.prev
      }
    } else {
      let node = this.oldest
      while (node && limit--) {
        list.push(node)
        node = node.next
      }
    }

    return list
  }
}
{
  "name": "time-ordered-set",
  "version": "2.0.1",
  "description": "Efficiently maintain a set of nodes ordered by the time they were added to the set",
  "main": "index.js",
  "files": [
    "index.js"
  ],
  "devDependencies": {
    "brittle": "^3.0.0",
    "standard": "^17.1.2"
  },
  "scripts": {
    "test": "standard && brittle test.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/mafintosh/time-ordered-set.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/mafintosh/time-ordered-set/issues"
  },
  "homepage": "https://github.com/mafintosh/time-ordered-set"
}
module.exports = class TimerBrowser {
  constructor (ms, fn, ctx = null, interval = false) {
    this.ms = ms
    this.ontimeout = fn
    this.context = ctx || null
    this.interval = interval
    this.done = false

    this._timer = interval
      ? setInterval(callInterval, ms, this)
      : setTimeout(callTimeout, ms, this)
  }

  unref () {}

  ref () {}

  refresh () {
    if (this.done) return

    if (this.interval) {
      clearInterval(this._timer)
      this._timer = setInterval(callInterval, this.ms, this)
    } else {
      clearTimeout(this._timer)
      this._timer = setTimeout(callTimeout, this.ms, this)
    }
  }

  destroy () {
    this.done = true
    this.ontimeout = null

    if (this.interval) clearInterval(this._timer)
    else clearTimeout(this._timer)
  }

  static once (ms, fn, ctx) {
    return new this(ms, fn, ctx, false)
  }

  static on (ms, fn, ctx) {
    return new this(ms, fn, ctx, true)
  }
}

function callTimeout (self) {
  self.done = true
  self.ontimeout.call(self.context)
}

function callInterval (self) {
  self.ontimeout.call(self.context)
}
module.exports = isNode()
  ? require('./node')
  : require('./browser')

function isNode () {
  const to = setTimeout(function () {}, 1000)
  clearTimeout(to)
  return !!to.refresh
}
module.exports = class Timer {
  constructor (ms, fn, ctx = null, interval = false) {
    this.ms = ms
    this.ontimeout = fn
    this.context = ctx
    this.interval = interval
    this.done = false

    this._timer = interval
      ? setInterval(callInterval, ms, this)
      : setTimeout(callTimeout, ms, this)
  }

  unref () {
    this._timer.unref()
  }

  ref () {
    this._timer.ref()
  }

  refresh () {
    if (this.done !== true) this._timer.refresh()
  }

  destroy () {
    this.done = true
    this.ontimeout = null
    if (this.interval) clearInterval(this._timer)
    else clearTimeout(this._timer)
  }

  static once (ms, fn, ctx) {
    return new this(ms, fn, ctx, false)
  }

  static on (ms, fn, ctx) {
    return new this(ms, fn, ctx, true)
  }
}

function callTimeout (self) {
  self.done = true
  self.ontimeout.call(self.context)
}

function callInterval (self) {
  self.ontimeout.call(self.context)
}
{
  "name": "timeout-refresh",
  "version": "2.0.1",
  "description": "Efficiently refresh a timer",
  "main": "index.js",
  "dependencies": {},
  "devDependencies": {
    "standard": "^16.0.4",
    "tape": "^5.5.2"
  },
  "browser": "./browser.js",
  "scripts": {
    "test": "standard && tape test.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/mafintosh/timeout-refresh.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/mafintosh/timeout-refresh/issues"
  },
  "homepage": "https://github.com/mafintosh/timeout-refresh"
}
const b4a = require('b4a')

module.exports = class BufferMap {
  constructor (other) {
    this.m = other ? new Map([...other.m]) : new Map()
  }

  get size () {
    return this.m.size
  }

  get (key) {
    if (b4a.isBuffer(key)) key = b4a.toString(key, 'hex')
    return this.m.get(key)
  }

  set (key, value) {
    if (b4a.isBuffer(key)) key = b4a.toString(key, 'hex')
    return this.m.set(key, value)
  }

  delete (key) {
    if (b4a.isBuffer(key)) key = b4a.toString(key, 'hex')
    return this.m.delete(key)
  }

  has (key) {
    if (b4a.isBuffer(key)) key = b4a.toString(key, 'hex')
    return this.m.has(key)
  }

  * [Symbol.iterator] () {
    for (const [key, value] of this.m) {
      yield [b4a.from(key, 'hex'), value]
    }
  }

  * keys () {
    for (const key of this.m.keys()) {
      yield b4a.from(key, 'hex')
    }
  }

  values () {
    return this.m.values()
  }

  clear () {
    return this.m.clear()
  }
}
{
  "name": "tiny-buffer-map",
  "version": "1.1.1",
  "description": "A very simple map for Buffers",
  "main": "index.js",
  "scripts": {
    "test": "brittle test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/andrewosh/tiny-buffer-map.git"
  },
  "keywords": [
    "buffer",
    "map"
  ],
  "author": "Andrew Osheroff <andrewosh@gmail.com>",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/andrewosh/tiny-buffer-map/issues"
  },
  "homepage": "https://github.com/andrewosh/tiny-buffer-map#readme",
  "dependencies": {
    "b4a": "^1.6.0"
  },
  "devDependencies": {
    "brittle": "^3.0.4"
  }
}
require.addon = require('require-addon')

module.exports = require.addon('.', __filename)
// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.

const v4Seg = '(?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])'
const v4Str = `(${v4Seg}[.]){3}${v4Seg}`
const IPv4Pattern = new RegExp(`^${v4Str}$`)

const v6Seg = '(?:[0-9a-fA-F]{1,4})'
const IPv6Pattern = new RegExp(
  '^(' +
    `(?:${v6Seg}:){7}(?:${v6Seg}|:)|` +
    `(?:${v6Seg}:){6}(?:${v4Str}|:${v6Seg}|:)|` +
    `(?:${v6Seg}:){5}(?::${v4Str}|(:${v6Seg}){1,2}|:)|` +
    `(?:${v6Seg}:){4}(?:(:${v6Seg}){0,1}:${v4Str}|(:${v6Seg}){1,3}|:)|` +
    `(?:${v6Seg}:){3}(?:(:${v6Seg}){0,2}:${v4Str}|(:${v6Seg}){1,4}|:)|` +
    `(?:${v6Seg}:){2}(?:(:${v6Seg}){0,3}:${v4Str}|(:${v6Seg}){1,5}|:)|` +
    `(?:${v6Seg}:){1}(?:(:${v6Seg}){0,4}:${v4Str}|(:${v6Seg}){1,6}|:)|` +
    `(?::((?::${v6Seg}){0,5}:${v4Str}|(?::${v6Seg}){1,7}|:))` +
    ')(%[0-9a-zA-Z-.:]{1,})?$'
)

const isIPv4 = (exports.isIPv4 = function isIPv4(host) {
  return IPv4Pattern.test(host)
})

const isIPv6 = (exports.isIPv6 = function isIPv6(host) {
  return IPv6Pattern.test(host)
})

exports.isIP = function isIP(host) {
  if (isIPv4(host)) return 4
  if (isIPv6(host)) return 6
  return 0
}
const events = require('events')
const b4a = require('b4a')
const binding = require('../binding')

module.exports = class NetworkInterfaces extends events.EventEmitter {
  constructor(udx) {
    super()

    this._handle = b4a.alloc(binding.sizeof_udx_napi_interface_event_t)
    this._watching = false
    this._destroying = null

    binding.udx_napi_interface_event_init(
      udx._handle,
      this._handle,
      this,
      this._onevent,
      this._onclose
    )

    this.interfaces = binding.udx_napi_interface_event_get_addrs(this._handle)
  }

  _onclose() {
    this.emit('close')
  }

  _onevent() {
    this.interfaces = binding.udx_napi_interface_event_get_addrs(this._handle)

    this.emit('change', this.interfaces)
  }

  watch() {
    if (this._watching) return this
    this._watching = true

    binding.udx_napi_interface_event_start(this._handle)

    return this
  }

  unwatch() {
    if (!this._watching) return this
    this._watching = false

    binding.udx_napi_interface_event_stop(this._handle)

    return this
  }

  async destroy() {
    if (this._destroying) return this._destroying
    this._destroying = events.once(this, 'close')

    binding.udx_napi_interface_event_close(this._handle)

    return this._destroying
  }

  [Symbol.iterator]() {
    return this.interfaces[Symbol.iterator]()
  }
}
const events = require('events')
const b4a = require('b4a')
const binding = require('../binding')
const ip = require('./ip')

module.exports = class UDXSocket extends events.EventEmitter {
  constructor(udx, opts = {}) {
    super()

    this.udx = udx

    this._handle = b4a.alloc(binding.sizeof_udx_napi_socket_t)
    this._inited = false
    this._host = null
    this._family = 0
    this._ipv6Only = opts.ipv6Only === true
    this._reuseAddress = opts.reuseAddress === true
    this._port = 0
    this._reqs = []
    this._free = []
    this._closing = null
    this._closed = false

    this._view64 = new BigUint64Array(
      this._handle.buffer,
      this._handle.byteOffset,
      this._handle.byteLength >> 3
    )

    this.streams = new Set()

    this.userData = null
  }

  get bound() {
    return this._port !== 0
  }

  get closing() {
    return this._closing !== null
  }

  get idle() {
    return this.streams.size === 0
  }

  get busy() {
    return this.streams.size > 0
  }

  get bytesTransmitted() {
    if (this._inited !== true) return 0
    return Number(this._view64[binding.offsetof_udx_socket_t_bytes_tx >> 3])
  }

  get packetsTransmitted() {
    if (this._inited !== true) return 0
    return Number(this._view64[binding.offsetof_udx_socket_t_packets_tx >> 3])
  }

  get bytesReceived() {
    if (this._inited !== true) return 0
    return Number(this._view64[binding.offsetof_udx_socket_t_bytes_rx >> 3])
  }

  get packetsReceived() {
    if (this._inited !== true) return 0
    return Number(this._view64[binding.offsetof_udx_socket_t_packets_rx >> 3])
  }

  get packetsDroppedByKernel() {
    if (this._inited !== true) return 0
    return Number(this._view64[binding.offsetof_udx_socket_t_packets_dropped_by_kernel >> 3])
  }

  toJSON() {
    return {
      bound: this.bound,
      closing: this.closing,
      streams: this.streams.size,
      address: this.address(),
      ipv6Only: this._ipv6Only,
      reuseAddress: this._reuseAddress,
      idle: this.idle,
      busy: this.busy
    }
  }

  _init() {
    if (this._inited) return

    binding.udx_napi_socket_init(
      this.udx._handle,
      this._handle,
      this,
      this._onsend,
      this._onmessage,
      this._onclose,
      this._reallocMessage
    )

    this._inited = true
  }

  _onsend(id, err) {
    const req = this._reqs[id]

    const onflush = req.onflush

    req.buffer = null
    req.onflush = null

    this._free.push(id)

    onflush(err >= 0)

    // gc the free list
    if (this._free.length >= 16 && this._free.length === this._reqs.length) {
      this._free = []
      this._reqs = []
    }
  }

  _onmessage(len, port, host, family) {
    this.emit('message', this.udx._consumeMessage(len), { host, family, port })
    return this.udx._buffer
  }

  _onclose() {
    this.emit('close')
  }

  _reallocMessage() {
    return this.udx._reallocMessage()
  }

  _onidle() {
    this.emit('idle')
  }

  _onbusy() {
    this.emit('busy')
  }

  _addStream(stream) {
    if (this.streams.has(stream)) return false
    this.streams.add(stream)
    if (this.streams.size === 1) this._onbusy()
    return true
  }

  _removeStream(stream) {
    if (!this.streams.has(stream)) return false
    this.streams.delete(stream)
    const closed = this._closeMaybe()
    if (this.idle && !closed) this._onidle()
    return true
  }

  address() {
    if (!this.bound) return null
    return { host: this._host, family: this._family, port: this._port }
  }

  bind(port, host) {
    if (this.bound) throw new Error('Already bound')
    if (this.closing) throw new Error('Socket is closed')

    if (!port) port = 0

    let flags = 0
    if (this._ipv6Only) flags |= binding.UV_UDP_IPV6ONLY
    if (this._reuseAddress) flags |= binding.UV_UDP_REUSEADDR

    let family

    if (host) {
      family = ip.isIP(host)
      if (!family) throw new Error(`${host} is not a valid IP address`)

      if (!this._inited) this._init()

      this._port = binding.udx_napi_socket_bind(this._handle, port, host, family, flags)
    } else {
      if (!this._inited) this._init()

      try {
        host = '::'
        family = 6
        this._port = binding.udx_napi_socket_bind(this._handle, port, host, family, flags)
      } catch {
        host = '0.0.0.0'
        family = 4
        this._port = binding.udx_napi_socket_bind(this._handle, port, host, family, flags)
      }
    }

    this._host = host
    this._family = family

    this.emit('listening')
  }

  async close() {
    if (this._closing) return this._closing
    this._closing = new Promise((resolve) => this.once('close', resolve))
    this._closeMaybe()
    return this._closing
  }

  _closeMaybe() {
    if (this._closed || this._closing === null) return this._closed

    if (!this._inited) {
      this._closed = true
      this.emit('close')
      return true
    }

    if (this.idle) {
      binding.udx_napi_socket_close(this._handle)
      this._closed = true
    }

    return this._closed
  }

  setTTL(ttl) {
    if (!this._inited) throw new Error('Socket not active')
    binding.udx_napi_socket_set_ttl(this._handle, ttl)
  }

  getRecvBufferSize() {
    if (!this._inited) throw new Error('Socket not active')
    return binding.udx_napi_socket_get_recv_buffer_size(this._handle)
  }

  setRecvBufferSize(size) {
    if (!this._inited) throw new Error('Socket not active')
    return binding.udx_napi_socket_set_recv_buffer_size(this._handle, size)
  }

  getSendBufferSize() {
    if (!this._inited) throw new Error('Socket not active')
    return binding.udx_napi_socket_get_send_buffer_size(this._handle)
  }

  setSendBufferSize(size) {
    if (!this._inited) throw new Error('Socket not active')
    return binding.udx_napi_socket_set_send_buffer_size(this._handle, size)
  }

  addMembership(group, ifaceAddress) {
    if (!this._inited) throw new Error('Socket not active')
    return binding.udx_napi_socket_set_membership(this._handle, group, ifaceAddress || '', true)
  }

  dropMembership(group, ifaceAddress) {
    if (!this._inited) throw new Error('Socket not active')
    return binding.udx_napi_socket_set_membership(this._handle, group, ifaceAddress || '', false)
  }

  async send(buffer, port, host, ttl) {
    if (this.closing) return false

    if (!host) host = '127.0.0.1'

    const family = ip.isIP(host)
    if (!family) throw new Error(`${host} is not a valid IP address`)

    if (!this.bound) this.bind(0)

    const id = this._allocSend()
    const req = this._reqs[id]

    req.buffer = buffer

    const promise = new Promise((resolve) => {
      req.onflush = resolve
    })

    binding.udx_napi_socket_send_ttl(
      this._handle,
      req.handle,
      id,
      buffer,
      port,
      host,
      family,
      ttl || 0
    )

    return promise
  }

  trySend(buffer, port, host, ttl) {
    if (this.closing) return

    if (!host) host = '127.0.0.1'

    const family = ip.isIP(host)
    if (!family) throw new Error(`${host} is not a valid IP address`)

    if (!this.bound) this.bind(0)

    const id = this._allocSend()
    const req = this._reqs[id]

    req.buffer = buffer
    req.onflush = noop

    binding.udx_napi_socket_send_ttl(
      this._handle,
      req.handle,
      id,
      buffer,
      port,
      host,
      family,
      ttl || 0
    )
  }

  _allocSend() {
    if (this._free.length > 0) return this._free.pop()
    const handle = b4a.allocUnsafe(binding.sizeof_udx_socket_send_t)
    return this._reqs.push({ handle, buffer: null, onflush: null }) - 1
  }
}

function noop() {}
const streamx = require('streamx')
const b4a = require('b4a')
const binding = require('../binding')
const ip = require('./ip')

const MAX_PACKET = 2048
const BUFFER_SIZE = 65536 + MAX_PACKET

module.exports = class UDXStream extends streamx.Duplex {
  constructor(udx, id, opts = {}) {
    super({ mapWritable: toBuffer, eagerOpen: true })

    this.udx = udx
    this.socket = null

    this._handle = b4a.alloc(binding.sizeof_udx_napi_stream_t)
    this._view = new Uint32Array(
      this._handle.buffer,
      this._handle.byteOffset,
      this._handle.byteLength >> 2
    )
    this._view16 = new Uint16Array(
      this._handle.buffer,
      this._handle.byteOffset,
      this._handle.byteLength >> 1
    )
    this._view64 = new BigUint64Array(
      this._handle.buffer,
      this._handle.byteOffset,
      this._handle.byteLength >> 3
    )

    this._wreqs = []
    this._wfree = []

    this._sreqs = []
    this._sfree = []
    this._closed = false

    this._flushing = 0
    this._flushes = []

    this._buffer = null
    this._reallocData()

    this._onwrite = null
    this._ondestroy = null
    this._firewall = opts.firewall || firewallAll

    this._remoteChanging = null
    this._previousSocket = null

    this.id = id
    this.remoteId = 0
    this.remoteHost = null
    this.remoteFamily = 0
    this.remotePort = 0

    this.userData = null

    binding.udx_napi_stream_init(
      this.udx._handle,
      this._handle,
      id,
      opts.framed ? 1 : 0,
      this,
      this._ondata,
      this._onend,
      this._ondrain,
      this._onack,
      this._onsend,
      this._onmessage,
      this._onclose,
      this._onfirewall,
      this._onremotechanged,
      this._reallocData,
      this._reallocMessage
    )

    if (opts.seq) binding.udx_napi_stream_set_seq(this._handle, opts.seq)

    binding.udx_napi_stream_recv_start(this._handle, this._buffer)
  }

  get connected() {
    return this.socket !== null
  }

  get mtu() {
    return this._view16[binding.offsetof_udx_stream_t_mtu >> 1]
  }

  get rtt() {
    return this._view[binding.offsetof_udx_stream_t_srtt >> 2]
  }

  get cwnd() {
    return this._view[binding.offsetof_udx_stream_t_cwnd >> 2]
  }

  get rtoCount() {
    return this._view16[binding.offsetof_udx_stream_t_rto_count >> 1]
  }

  get retransmits() {
    return this._view16[binding.offsetof_udx_stream_t_retransmit_count >> 1]
  }

  get bbrState() {
    const view = new Uint8Array(
      this._handle.buffer,
      this._handle.byteOffset,
      this._handle.byteLength
    )
    return view[binding.offsetof_udx_stream_t_bbr_state]
  }

  get bbrBandwidth() {
    return binding.udx_napi_stream_get_bw(this._handle)
  }

  get fastRecoveries() {
    return this._view16[binding.offsetof_udx_stream_t_fast_recovery_count >> 1]
  }

  get inflight() {
    return this._view[binding.offsetof_udx_stream_t_inflight >> 2]
  }

  get bytesTransmitted() {
    return Number(this._view64[binding.offsetof_udx_stream_t_bytes_tx >> 3])
  }

  get packetsTransmitted() {
    return Number(this._view64[binding.offsetof_udx_stream_t_packets_tx >> 3])
  }

  get bytesReceived() {
    return Number(this._view64[binding.offsetof_udx_stream_t_bytes_rx >> 3])
  }

  get packetsReceived() {
    return Number(this._view64[binding.offsetof_udx_stream_t_packets_rx >> 3])
  }

  get localHost() {
    return this.socket ? this.socket.address().host : null
  }

  get localFamily() {
    return this.socket ? this.socket.address().family : 0
  }

  get localPort() {
    return this.socket ? this.socket.address().port : 0
  }

  setInteractive(bool) {
    if (!this._closed) return
    binding.udx_napi_stream_set_mode(this._handle, bool ? 0 : 1)
  }

  connect(socket, remoteId, port, host, opts = {}) {
    if (this._closed) return

    if (this.connected) throw new Error('Already connected')
    if (socket.closing) throw new Error('Socket is closed')

    if (typeof host === 'object') {
      opts = host
      host = null
    }

    if (!host) host = '127.0.0.1'

    const family = ip.isIP(host)
    if (!family) throw new Error(`${host} is not a valid IP address`)
    if (!(port > 0 && port < 65536)) throw new Error(`${port} is not a valid port`)

    if (!socket.bound) socket.bind(0)

    this.remoteId = remoteId
    this.remotePort = port
    this.remoteHost = host
    this.remoteFamily = family
    this.socket = socket

    if (opts.ack) binding.udx_napi_stream_set_ack(this._handle, opts.ack)

    binding.udx_napi_stream_connect(this._handle, socket._handle, remoteId, port, host, family)

    this.socket._addStream(this)

    this.emit('connect')
  }

  changeRemote(socket, remoteId, port, host) {
    if (this._remoteChanging) throw new Error('Remote already changing')

    if (!this.connected) throw new Error('Not yet connected')
    if (socket.closing) throw new Error('Socket is closed')

    if (this.socket.udx !== socket.udx) {
      throw new Error('Cannot change to a socket on another UDX instance')
    }

    if (!host) host = '127.0.0.1'

    const family = ip.isIP(host)
    if (!family) throw new Error(`${host} is not a valid IP address`)
    if (!(port > 0 && port < 65536)) throw new Error(`${port} is not a valid port`)

    if (this.socket !== socket) this._previousSocket = this.socket

    this.remoteId = remoteId
    this.remotePort = port
    this.remoteHost = host
    this.remoteFamily = family
    this.socket = socket

    this._remoteChanging = new Promise((resolve, reject) => {
      const onchanged = () => {
        this.off('close', onclose)
        resolve()
      }

      const onclose = () => {
        this.off('remote-changed', onchanged)
        reject(new Error('Stream is closed'))
      }

      this.once('remote-changed', onchanged).once('close', onclose)
    })

    binding.udx_napi_stream_change_remote(
      this._handle,
      socket._handle,
      remoteId,
      port,
      host,
      family
    )

    this.socket._addStream(this)

    return this._remoteChanging
  }

  relayTo(destination) {
    if (this._closed) return

    binding.udx_napi_stream_relay_to(this._handle, destination._handle)
  }

  async send(buffer) {
    if (!this.connected || this._closed) return false

    const id = this._allocSend()
    const req = this._sreqs[id]

    req.buffer = buffer

    const promise = new Promise((resolve) => {
      req.onflush = resolve
    })

    binding.udx_napi_stream_send(this._handle, req.handle, id, buffer)

    return promise
  }

  trySend(buffer) {
    if (!this.connected || this._closed) return

    const id = this._allocSend()
    const req = this._sreqs[id]

    req.buffer = buffer
    req.onflush = noop

    binding.udx_napi_stream_send(this._handle, req.handle, id, buffer)
  }

  async flush() {
    if ((await streamx.Writable.drained(this)) === false) return false
    if (this.destroying) return false

    const missing = this._wreqs.length - this._wfree.length
    if (missing === 0) return true

    return new Promise((resolve) => {
      this._flushes.push({ flush: this._flushing++, missing, resolve })
    })
  }

  toJSON() {
    return {
      id: this.id,
      connected: this.connected,
      destroying: this.destroying,
      destroyed: this.destroyed,
      remoteId: this.remoteId,
      remoteHost: this.remoteHost,
      remoteFamily: this.remoteFamily,
      remotePort: this.remotePort,
      mtu: this.mtu,
      rtt: this.rtt,
      cwnd: this.cwnd,
      inflight: this.inflight,
      socket: this.socket ? this.socket.toJSON() : null
    }
  }

  _read(cb) {
    cb(null)
  }

  _writeContinue(err) {
    if (this._onwrite === null) return
    const cb = this._onwrite
    this._onwrite = null
    cb(err)
  }

  _destroyContinue(err) {
    if (this._ondestroy === null) return
    const cb = this._ondestroy
    this._ondestroy = null
    cb(err)
  }

  _writev(buffers, cb) {
    if (!this.connected) {
      throw customError('Writing while not connected not currently supported', 'ERR_ASSERTION')
    }

    let drained = true

    if (buffers.length === 1) {
      const id = this._allocWrite(1)
      const req = this._wreqs[id]

      req.flush = this._flushing
      req.buffer = buffers[0]

      drained = binding.udx_napi_stream_write(this._handle, req.handle, id, req.buffer) !== 0
    } else {
      const id = this._allocWrite(nextBatchSize(buffers.length))
      const req = this._wreqs[id]

      req.flush = this._flushing
      req.buffers = buffers

      drained = binding.udx_napi_stream_writev(this._handle, req.handle, id, req.buffers) !== 0
    }

    if (drained) cb(null)
    else this._onwrite = cb
  }

  _final(cb) {
    const id = this._allocWrite(1)
    const req = this._wreqs[id]

    req.flush = this._flushes
    req.buffer = b4a.allocUnsafe(0)

    const drained =
      binding.udx_napi_stream_write_end(this._handle, req.handle, id, req.buffer) !== 0

    if (drained) cb(null)
    else this._onwrite = cb
  }

  _predestroy() {
    if (!this._closed) binding.udx_napi_stream_destroy(this._handle)
    this._closed = true
    this._writeContinue(null)
  }

  _destroy(cb) {
    if (this.connected) this._ondestroy = cb
    else cb(null)
  }

  _ondata(read) {
    this.push(this._consumeData(read))
    return this._buffer
  }

  _onend(read) {
    if (read > 0) this.push(this._consumeData(read))
    this.push(null)
  }

  _ondrain() {
    this._writeContinue(null)
  }

  _flushAck(flush) {
    for (let i = this._flushes.length - 1; i >= 0; i--) {
      const f = this._flushes[i]
      if (f.flush < flush) break
      f.missing--
    }

    while (this._flushes.length > 0 && this._flushes[0].missing === 0) {
      this._flushes.shift().resolve(true)
    }
  }

  _onack(id) {
    const req = this._wreqs[id]

    req.buffers = req.buffer = null
    this._wfree.push(id)

    if (this._flushes.length > 0) this._flushAck(req.flush)

    // gc the free list
    if (this._wfree.length >= 64 && this._wfree.length === this._wreqs.length) {
      this._wfree = []
      this._wreqs = []
    }
  }

  _onsend(id, err) {
    const req = this._sreqs[id]

    const onflush = req.onflush

    req.buffer = null
    req.onflush = null

    this._sfree.push(id)

    onflush(err >= 0)

    // gc the free list
    if (this._sfree.length >= 16 && this._sfree.length === this._sreqs.length) {
      this._sfree = []
      this._sreqs = []
    }
  }

  _onmessage(len) {
    this.emit('message', this.udx._consumeMessage(len))
    return this.udx._buffer
  }

  _onclose(err) {
    this._closed = true

    if (this.socket) {
      this.socket._removeStream(this)
      this.socket = null
    }

    if (this._previousSocket) {
      this._previousSocket._removeStream(this)
      this._previousSocket = null
    }

    // no error, we don't need to do anything
    if (!err) return this._destroyContinue(null)

    if (this._ondestroy === null) this.destroy(err)
    else this._destroyContinue(err)
  }

  _onfirewall(socket, port, host, family) {
    if (port <= 0 || port > 65535) return 1
    return this._firewall(socket, port, host, family) ? 1 : 0
  }

  _onremotechanged() {
    if (this._previousSocket) {
      this._previousSocket._removeStream(this)
      this._previousSocket = null
    }

    this._remoteChanging = null
    this.emit('remote-changed')
  }

  _consumeData(len) {
    const next = this._buffer.subarray(0, len)
    this._buffer = this._buffer.subarray(len)
    if (this._buffer.byteLength < MAX_PACKET) this._reallocData()
    return next
  }

  _reallocData() {
    this._buffer = b4a.allocUnsafe(BUFFER_SIZE)
    return this._buffer
  }

  _reallocMessage() {
    return this.udx._reallocMessage()
  }

  _allocWrite(size) {
    if (this._wfree.length === 0) {
      const handle = b4a.allocUnsafe(binding.udx_napi_stream_write_sizeof(size))
      return (
        this._wreqs.push({
          handle,
          size,
          buffers: null,
          buffer: null,
          flush: 0
        }) - 1
      )
    }

    const free = this._wfree.pop()
    if (size === 1) return free

    const next = this._wreqs[free]
    if (next.size < size) {
      next.handle = b4a.allocUnsafe(binding.udx_napi_stream_write_sizeof(size))
      next.size = size
    }

    return free
  }

  _allocSend() {
    if (this._sfree.length > 0) return this._sfree.pop()
    const handle = b4a.allocUnsafe(binding.sizeof_udx_stream_send_t)
    return this._sreqs.push({ handle, buffer: null, resolve: null, reject: null }) - 1
  }
}

function noop() {}

function toBuffer(data) {
  return typeof data === 'string' ? b4a.from(data) : data
}

function firewallAll(socket, port, host) {
  return true
}

function customError(message, code) {
  const error = new Error(message)
  error.code = code
  return error
}

function nextBatchSize(n) {
  // try to coerce the the writevs into sameish size
  if (n === 1) return 1
  // group all < 8 to the same size, low mem overhead but save some small allocs
  if (n < 8) return 8
  if (n < 16) return 16
  if (n < 32) return 32
  if (n < 64) return 64
  return n
}
const b4a = require('b4a')
const binding = require('../binding')
const ip = require('./ip')
const Socket = require('./socket')
const Stream = require('./stream')
const NetworkInterfaces = require('./network-interfaces')

const MAX_MESSAGE = 4096
const BUFFER_SIZE = 65536 + MAX_MESSAGE

module.exports = class UDX {
  constructor() {
    this._handle = b4a.alloc(binding.sizeof_udx_napi_t)
    this._watchers = new Set()
    this._view64 = new BigUint64Array(
      this._handle.buffer,
      this._handle.byteOffset,
      this._handle.byteLength >> 3
    )

    this._buffer = null
    this._reallocMessage()

    binding.udx_napi_init(this._handle, this._buffer)
  }

  static isIPv4(host) {
    return ip.isIPv4(host)
  }

  static isIPv6(host) {
    return ip.isIPv6(host)
  }

  static isIP(host) {
    return ip.isIP(host)
  }

  get bytesTransmitted() {
    return Number(this._view64[binding.offsetof_udx_t_bytes_tx >> 3])
  }

  get packetsTransmitted() {
    return Number(this._view64[binding.offsetof_udx_t_packets_tx >> 3])
  }

  get bytesReceived() {
    return Number(this._view64[binding.offsetof_udx_t_bytes_rx >> 3])
  }

  get packetsReceived() {
    return Number(this._view64[binding.offsetof_udx_t_packets_rx >> 3])
  }

  get packetsDroppedByKernel() {
    return Number(this._view64[binding.offsetof_udx_t_packets_dropped_by_kernel >> 3])
  }

  _consumeMessage(len) {
    const next = this._buffer.subarray(0, len)
    this._buffer = this._buffer.subarray(len)
    if (this._buffer.byteLength < MAX_MESSAGE) this._reallocMessage()
    return next
  }

  _reallocMessage() {
    // TODO: move reallocation to native
    this._buffer = b4a.allocUnsafe(BUFFER_SIZE)
    return this._buffer
  }

  createSocket(opts) {
    return new Socket(this, opts)
  }

  createStream(id, opts) {
    return new Stream(this, id, opts)
  }

  networkInterfaces() {
    let [watcher = null] = this._watchers
    if (watcher) return watcher.interfaces

    watcher = new NetworkInterfaces(this)
    watcher.destroy()

    return watcher.interfaces
  }

  watchNetworkInterfaces(onchange) {
    const watcher = new NetworkInterfaces(this)

    this._watchers.add(watcher)
    watcher.on('close', () => {
      this._watchers.delete(watcher)
    })

    if (onchange) watcher.on('change', onchange)

    return watcher.watch()
  }

  async lookup(host, opts = {}) {
    const { family = 0 } = opts

    const req = b4a.alloc(binding.sizeof_udx_napi_lookup_t)
    const ctx = {
      req,
      resolve: null,
      reject: null
    }

    const promise = new Promise((resolve, reject) => {
      ctx.resolve = resolve
      ctx.reject = reject
    })

    binding.udx_napi_lookup(this._handle, req, host, family, ctx, onlookup)

    return promise
  }
}

function onlookup(err, host, family) {
  if (err) this.reject(err)
  else this.resolve({ host, family })
}
{
  "name": "udx-native",
  "version": "1.20.6",
  "description": "udx is reliable, multiplexed, and congestion-controlled streams over udp",
  "main": "lib/udx.js",
  "files": [
    "lib",
    "prebuilds",
    "binding.cc",
    "binding.js",
    "CMakeLists.txt"
  ],
  "imports": {
    "events": {
      "bare": "bare-events",
      "default": "events"
    }
  },
  "addon": true,
  "scripts": {
    "format": "prettier --write .",
    "lint": "prettier --check . && lunte",
    "test": "npm run test:node && npm run test:bare",
    "test:node": "brittle-node --coverage test/*.js",
    "test:bare": "brittle-bare --coverage test/*.js",
    "test:all": "brittle test/*.js test/slow/*.js",
    "test:generate": "brittle -r test/all.js test/*.js",
    "bench": "brittle test/bench/*.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/udx-native.git"
  },
  "keywords": [
    "tcp",
    "udp",
    "stream",
    "reliable"
  ],
  "author": "Holepunch Inc.",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/udx-native/issues"
  },
  "homepage": "https://github.com/holepunchto/udx-native#readme",
  "engines": {
    "bare": ">=1.17.4"
  },
  "dependencies": {
    "b4a": "^1.5.0",
    "bare-events": "^2.2.0",
    "require-addon": "^1.1.0",
    "streamx": "^2.22.0"
  },
  "devDependencies": {
    "bare-compat-napi": "^1.3.0",
    "brittle": "^3.1.0",
    "cmake-bare": "^1.1.10",
    "cmake-fetch": "^1.0.1",
    "cmake-napi": "^1.0.5",
    "is-ci": "^3.0.1",
    "lunte": "^1.3.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^2.0.0",
    "tiny-byte-size": "^1.1.0"
  }
}
exports.add = add
exports.has = has
exports.remove = remove
exports.swap = swap

function add (list, item) {
  if (has(list, item)) return item
  item._index = list.length
  list.push(item)
  return item
}

function has (list, item) {
  return item._index < list.length && list[item._index] === item
}

function remove (list, item) {
  if (!has(list, item)) return null

  var last = list.pop()
  if (last !== item) {
    list[item._index] = last
    last._index = item._index
  }

  return item
}

function swap (list, a, b) {
  if (!has(list, a) || !has(list, b)) return
  var tmp = a._index
  a._index = b._index
  list[a._index] = a
  b._index = tmp
  list[b._index] = b
}
{
  "name": "unordered-set",
  "version": "2.0.1",
  "description": "A couple of functions that make it easy to maintain an unordered set as an array in an efficient way",
  "main": "index.js",
  "dependencies": {},
  "devDependencies": {
    "standard": "^6.0.4",
    "tape": "^4.4.0"
  },
  "scripts": {
    "test": "standard && tape test.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/mafintosh/unordered-set.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/mafintosh/unordered-set/issues"
  },
  "homepage": "https://github.com/mafintosh/unordered-set"
}
const b4a = require('b4a')

unslab.all = all
unslab.is = is

module.exports = unslab

function unslab (buf) {
  if (buf === null || buf.buffer.byteLength === buf.byteLength) return buf
  const copy = b4a.allocUnsafeSlow(buf.byteLength)
  copy.set(buf, 0)
  return copy
}

function is (buf) {
  return buf.buffer.byteLength !== buf.byteLength
}

function all (list) {
  let size = 0
  for (let i = 0; i < list.length; i++) {
    const buf = list[i]
    size += buf === null || buf.buffer.byteLength === buf.byteLength ? 0 : buf.byteLength
  }

  const copy = b4a.allocUnsafeSlow(size)
  const result = new Array(list.length)

  let offset = 0
  for (let i = 0; i < list.length; i++) {
    let buf = list[i]

    if (buf !== null && buf.buffer.byteLength !== buf.byteLength) {
      copy.set(buf, offset)
      buf = copy.subarray(offset, offset += buf.byteLength)
    }

    result[i] = buf
  }

  return result
}
{
  "name": "unslab",
  "version": "1.3.0",
  "description": "Unslab some slab'ed buffers",
  "main": "index.js",
  "files": [
    "index.js"
  ],
  "dependencies": {
    "b4a": "^1.6.6"
  },
  "devDependencies": {
    "brittle": "^3.5.2",
    "standard": "^17.1.0"
  },
  "scripts": {
    "test": "standard && brittle test.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/holepunchto/unslab.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/unslab/issues"
  },
  "homepage": "https://github.com/holepunchto/unslab"
}
module.exports = read

var MSB = 0x80
  , REST = 0x7F

function read(buf, offset) {
  var res    = 0
    , offset = offset || 0
    , shift  = 0
    , counter = offset
    , b
    , l = buf.length

  do {
    if (counter >= l) {
      read.bytes = 0
      throw new RangeError('Could not decode varint')
    }
    b = buf[counter++]
    res += shift < 28
      ? (b & REST) << shift
      : (b & REST) * Math.pow(2, shift)
    shift += 7
  } while (b >= MSB)

  read.bytes = counter - offset

  return res
}
module.exports = encode

var MSB = 0x80
  , REST = 0x7F
  , MSBALL = ~REST
  , INT = Math.pow(2, 31)

function encode(num, out, offset) {
  out = out || []
  offset = offset || 0
  var oldOffset = offset

  while(num >= INT) {
    out[offset++] = (num & 0xFF) | MSB
    num /= 128
  }
  while(num & MSBALL) {
    out[offset++] = (num & 0xFF) | MSB
    num >>>= 7
  }
  out[offset] = num | 0
  
  encode.bytes = offset - oldOffset + 1
  
  return out
}
module.exports = {
    encode: require('./encode.js')
  , decode: require('./decode.js')
  , encodingLength: require('./length.js')
}

var N1 = Math.pow(2,  7)
var N2 = Math.pow(2, 14)
var N3 = Math.pow(2, 21)
var N4 = Math.pow(2, 28)
var N5 = Math.pow(2, 35)
var N6 = Math.pow(2, 42)
var N7 = Math.pow(2, 49)
var N8 = Math.pow(2, 56)
var N9 = Math.pow(2, 63)

module.exports = function (value) {
  return (
    value < N1 ? 1
  : value < N2 ? 2
  : value < N3 ? 3
  : value < N4 ? 4
  : value < N5 ? 5
  : value < N6 ? 6
  : value < N7 ? 7
  : value < N8 ? 8
  : value < N9 ? 9
  :              10
  )
}
{
  "name": "varint",
  "version": "5.0.0",
  "description": "protobuf-style varint bytes - use msb to create integer values of varying sizes",
  "main": "index.js",
  "scripts": {
    "test": "node test.js"
  },
  "repository": {
    "type": "git",
    "url": "git://github.com/chrisdickinson/varint.git"
  },
  "keywords": [
    "varint",
    "protobuf",
    "encode",
    "decode"
  ],
  "author": "Chris Dickinson <chris@neversaw.us>",
  "license": "MIT",
  "devDependencies": {
    "tape": "~2.12.3"
  }
}
const { runtime, platform, arch, version } =
  typeof Bare !== 'undefined'
    ? {
        runtime: 'bare',
        platform: global.Bare.platform,
        arch: global.Bare.arch,
        version: global.Bare.version
      }
    : typeof process !== 'undefined'
      ? {
          runtime: 'node',
          platform: global.process.platform,
          arch: global.process.arch,
          version: global.process.version
        }
      : typeof Window !== 'undefined'
        ? { runtime: 'browser', platform: 'unknown', arch: 'unknown', version: 'unknown' }
        : { runtime: 'unknown', platform: 'unknown', arch: 'unknown', version: 'unknown' }

exports.runtime = runtime
exports.version = version
exports.platform = platform
exports.arch = arch
exports.isBare = runtime === 'bare'
exports.isBareKit = exports.isBare && typeof BareKit !== 'undefined'
exports.isPear = typeof Pear !== 'undefined'
exports.isNode = runtime === 'node'
exports.isBrowser = runtime === 'browser'
exports.isWindows = platform === 'win32'
exports.isLinux = platform === 'linux'
exports.isMac = platform === 'darwin'
exports.isIOS = platform === 'ios' || platform === 'ios-simulator'
exports.isAndroid = platform === 'android'
exports.isElectron = typeof process !== 'undefined' && !!global.process.versions?.electron
exports.isElectronRenderer = exports.isElectron && global.process.type === 'renderer'
exports.isElectronWorker = exports.isElectron && global.process.type === 'worker'
{
  "name": "which-runtime",
  "version": "1.4.0",
  "description": "Detect if you are in Bare or Node and which os etc",
  "main": "index.js",
  "files": [
    "index.js"
  ],
  "scripts": {
    "format": "prettier --write .",
    "lint": "prettier --check . && lunte .",
    "test": "npm run test:node && npm run test:bare",
    "test:node": "brittle-node test.js",
    "test:bare": "brittle-bare test.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/holepunchto/which-runtime.git"
  },
  "author": "Holepunch Inc.",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/which-runtime/issues"
  },
  "homepage": "https://github.com/holepunchto/which-runtime",
  "devDependencies": {
    "brittle": "^3.0.0",
    "lunte": "^1.4.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^2.0.0"
  }
}
module.exports = class MaxCache {
  constructor ({ maxSize, maxAge, createMap, ongc }) {
    this.maxSize = maxSize
    this.maxAge = maxAge
    this.ongc = ongc || null

    this._createMap = createMap || defaultCreateMap
    this._latest = this._createMap()
    this._oldest = this._createMap()
    this._retained = this._createMap()
    this._gced = false
    this._interval = null

    if (this.maxAge > 0 && this.maxAge < Infinity) {
      const tick = Math.ceil(2 / 3 * this.maxAge)
      this._interval = setInterval(this._gcAuto.bind(this), tick)
      if (this._interval.unref) this._interval.unref()
    }
  }

  * [Symbol.iterator] () {
    for (const it of [this._latest, this._oldest, this._retained]) {
      yield * it
    }
  }

  * keys () {
    for (const it of [this._latest, this._oldest, this._retained]) {
      yield * it.keys()
    }
  }

  * values () {
    for (const it of [this._latest, this._oldest, this._retained]) {
      yield * it.values()
    }
  }

  destroy () {
    this.clear()
    clearInterval(this._interval)
    this._interval = null
  }

  clear () {
    this._gced = true
    this._latest.clear()
    this._oldest.clear()
    this._retained.clear()
  }

  set (k, v) {
    if (this._retained.has(k)) return this
    this._latest.set(k, v)
    this._oldest.delete(k) || this._retained.delete(k)
    if (this._latest.size >= this.maxSize) this._gc()
    return this
  }

  retain (k, v) {
    this._retained.set(k, v)
    this._latest.delete(k) || this._oldest.delete(k)
    return this
  }

  delete (k) {
    return this._latest.delete(k) || this._oldest.delete(k) || this._retained.delete(k)
  }

  has (k) {
    return this._latest.has(k) || this._oldest.has(k) || this._retained.has(k)
  }

  get (k) {
    if (this._latest.has(k)) {
      return this._latest.get(k)
    }

    if (this._oldest.has(k)) {
      const v = this._oldest.get(k)
      this._latest.set(k, v)
      this._oldest.delete(k)
      return v
    }

    if (this._retained.has(k)) {
      return this._retained.get(k)
    }

    return null
  }

  _gcAuto () {
    if (!this._gced) this._gc()
    this._gced = false
  }

  _gc () {
    this._gced = true
    if (this.ongc !== null && this._oldest.size > 0) this.ongc(this._oldest)
    this._oldest = this._latest
    this._latest = this._createMap()
  }
}

function defaultCreateMap () {
  return new Map()
}
{
  "name": "xache",
  "version": "1.2.1",
  "description": "Yet another auto expiring, max sizable cache",
  "main": "index.js",
  "files": [
    "index.js"
  ],
  "devDependencies": {
    "brittle": "^3.3.2",
    "standard": "^17.1.0"
  },
  "scripts": {
    "test": "standard && brittle test.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/mafintosh/xache.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/mafintosh/xache/issues"
  },
  "homepage": "https://github.com/mafintosh/xache"
}
const b4a = require('b4a')

const ALPHABET = 'ybndrfg8ejkmcpqxot1uwisza345h769'
const MIN = 0x31 // 1
const MAX = 0x7a // z
const REVERSE = new Int8Array(1 + MAX - MIN)

REVERSE.fill(-1)

for (let i = 0; i < ALPHABET.length; i++) {
  const v = ALPHABET.charCodeAt(i) - MIN
  REVERSE[v] = i
}

exports.encode = encode
exports.decode = decode
exports.ALPHABET = ALPHABET

function decode (s, out) {
  let pb = 0
  let ps = 0

  const r = s.length & 7
  const q = (s.length - r) / 8

  if (!out) out = b4a.allocUnsafe(Math.ceil(s.length * 5 / 8))

  // 0 5 2 7 4 1 6 3 (+5 mod 8)
  for (let i = 0; i < q; i++) {
    const a = quintet(s, ps++)
    const b = quintet(s, ps++)
    const c = quintet(s, ps++)
    const d = quintet(s, ps++)
    const e = quintet(s, ps++)
    const f = quintet(s, ps++)
    const g = quintet(s, ps++)
    const h = quintet(s, ps++)

    out[pb++] = (a << 3) | (b >>> 2)
    out[pb++] = ((b & 0b11) << 6) | (c << 1) | (d >>> 4)
    out[pb++] = ((d & 0b1111) << 4) | (e >>> 1)
    out[pb++] = ((e & 0b1) << 7) | (f << 2) | (g >>> 3)
    out[pb++] = ((g & 0b111) << 5) | h
  }

  if (r === 0) return out.subarray(0, pb)

  const a = quintet(s, ps++)
  const b = quintet(s, ps++)

  out[pb++] = (a << 3) | (b >>> 2)

  if (r <= 2) return out.subarray(0, pb)

  const c = quintet(s, ps++)
  const d = quintet(s, ps++)

  out[pb++] = ((b & 0b11) << 6) | (c << 1) | (d >>> 4)

  if (r <= 4) return out.subarray(0, pb)

  const e = quintet(s, ps++)

  out[pb++] = ((d & 0b1111) << 4) | (e >>> 1)

  if (r <= 5) return out.subarray(0, pb)

  const f = quintet(s, ps++)
  const g = quintet(s, ps++)

  out[pb++] = ((e & 0b1) << 7) | (f << 2) | (g >>> 3)

  if (r <= 7) return out.subarray(0, pb)

  const h = quintet(s, ps++)

  out[pb++] = ((g & 0b111) << 5) | h

  return out.subarray(0, pb)
}

function encode (buf) {
  if (typeof buf === 'string') buf = b4a.from(buf)

  const max = buf.byteLength * 8

  let s = ''

  for (let p = 0; p < max; p += 5) {
    const i = p >>> 3
    const j = p & 7

    if (j <= 3) {
      s += ALPHABET[(buf[i] >>> (3 - j)) & 0b11111]
      continue
    }

    const of = j - 3
    const h = (buf[i] << of) & 0b11111
    const l = (i >= buf.byteLength ? 0 : buf[i + 1]) >>> (8 - of)

    s += ALPHABET[h | l]
  }

  return s
}

function quintet (s, i) {
  if (i > s.length) {
    return 0
  }

  const v = s.charCodeAt(i)

  if (v < MIN || v > MAX) {
    throw Error('Invalid character in base32 input: "' + s[i] + '" at position ' + i)
  }

  const bits = REVERSE[v - MIN]

  if (bits === -1) {
    throw Error('Invalid character in base32 input: "' + s[i] + '" at position ' + i)
  }

  return bits
}
{
  "name": "z32",
  "version": "1.1.0",
  "description": "Encode & decode z-base32",
  "main": "index.js",
  "dependencies": {
    "b4a": "^1.5.3"
  },
  "devDependencies": {
    "base-x": "^4.0.0",
    "base32": "0.0.7",
    "brittle": "^3.1.3",
    "nanobench": "^3.0.0",
    "rfc4648": "^1.5.2",
    "standard": "^17.0.0"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/mafintosh/z32.git"
  },
  "scripts": {
    "test": "standard && brittle test.js",
    "bench": "node benchmark.js"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/mafintosh/z32/issues"
  },
  "homepage": "https://github.com/mafintosh/z32"
}
{
  "name": "pgh",
  "version": "0.1.153",
  "description": "pgh \u2014 P2P content-addressed package manager and app launcher",
  "type": "module",
  "main": "index.js",
  "bin": {
    "pgh": "./bin/pgh"
  },
  "exports": {
    ".": "./index.js",
    "./store": "./lib/store.js",
    "./tree-install": "./lib/tree-install.js",
    "./publish": "./lib/publish.js",
    "./conflict": "./lib/conflict.js",
    "./schema": "./lib/schema.js",
    "./published-index": "./lib/published-index.js",
    "./bootstrap": "./lib/bootstrap.js",
    "./bootstrap-artifacts": "./lib/bootstrap-artifacts.js",
    "./network-fetcher": "./lib/network-fetcher.js",
    "./runtime-factory": "./lib/runtime-factory.js",
    "./resolve-release": "./lib/resolve-release.js",
    "./release-registry": "./lib/release-registry.js",
    "./name-registry": "./lib/name-registry.js",
    "./subscriptions": "./lib/subscriptions.js",
    "./app-state": "./lib/app-state.js",
    "./identity": "./lib/identity.js",
    "./output": "./lib/output.js",
    "./commands/init": "./lib/commands/init.js",
    "./commands/app": "./lib/commands/app.js",
    "./commands/store": "./lib/commands/store.js",
    "./commands/publish": "./lib/commands/publish.js",
    "./commands/releases": "./lib/commands/releases.js",
    "./commands/seed": "./lib/commands/seed.js"
  },
  "scripts": {
    "test": "brittle test/*.test.js",
    "test:bare": "bare ./node_modules/.bin/brittle test/*.test.js"
  },
  "engines": {
    "node": ">=18",
    "bare": ">=1.0"
  },
  "overrides": {
    "udx-native": "^1.20.3",
    "bare-runtime": "1.28.7"
  },
  "imports": {
    "#node:fs": {
      "bare": "bare-fs",
      "default": "fs"
    },
    "#node:fs/promises": {
      "bare": "bare-fs/promises",
      "default": "fs/promises"
    },
    "#node:path": {
      "bare": "bare-path",
      "default": "path"
    },
    "#node:os": {
      "bare": "bare-os",
      "default": "os"
    },
    "#node:url": {
      "bare": "bare-url",
      "default": "url"
    },
    "#node:events": {
      "bare": "bare-events",
      "default": "events"
    },
    "#node:stream": {
      "bare": "bare-stream",
      "default": "stream"
    },
    "#node:stream/promises": {
      "bare": "bare-stream/promises",
      "default": "stream/promises"
    },
    "#node:child_process": {
      "bare": "bare-subprocess",
      "default": "child_process"
    },
    "#node:net": {
      "bare": "bare-net",
      "default": "net"
    },
    "#node:http": {
      "bare": "bare-http1",
      "default": "http"
    },
    "#node:https": {
      "bare": "bare-https",
      "default": "https"
    },
    "#node:crypto": {
      "bare": "bare-crypto",
      "default": "crypto"
    },
    "#node:process": {
      "bare": "bare-process",
      "default": "process"
    }
  },
  "pgh": {
    "bin": "pgh",
    "entrypoint": "bin/pgh",
    "upgrade": "pgh://1dd36bdfa9cb7f7d84adbd07f017e7200f7f90a3a546cd0761fac2a1f8a13e86/pgh",
    "type": "terminal",
    "runtime": "bare",
    "bootstrap": true
  },
  "license": "AGPL-3.0",
  "dependencies": {
    "autobase": "^7.0.0",
    "b4a": "^1.6.0",
    "bare-crypto": "^1.0.0",
    "bare-events": "^2.0.0",
    "bare-fs": "^4.0.0",
    "bare-http1": "^4.0.0",
    "bare-https": "^2.0.0",
    "bare-net": "^1.0.0",
    "bare-os": "^3.0.0",
    "bare-path": "^3.0.0",
    "bare-process": "^4.0.0",
    "bare-runtime": "1.28.7",
    "bare-stream": "^2.0.0",
    "bare-subprocess": "^6.0.0",
    "bare-url": "^2.0.0",
    "blind-pairing": "^2.3.1",
    "compact-encoding": "^3.2.0",
    "corestore": "^7.0.0",
    "hyper-deploy": "file:../hyper-deploy",
    "hyper-overlay": "file:../hyper-overlay",
    "hyper-svc": "file:../hyper-svc",
    "hyperbee": "^2.27.3",
    "hypercore": "^11.0.0",
    "hypercore-crypto": "^3.0.0",
    "hyperident": "file:../repos/hyperident",
    "hyperswarm": "^4.17.0",
    "keet-identity-key": "^3.1.0",
    "pgh-runtime": "file:../pgh-runtime",
    "protomux": "^3.0.0",
    "sodium-native": "^5.0.0"
  },
  "devDependencies": {
    "@hyperswarm/testnet": "^3.0.0",
    "brittle": "^3.0.0"
  }
}
const b4a = require('b4a')
const c = require('compact-encoding')
const queueTick = require('queue-tick')
const safetyCatch = require('safety-catch')
const unslab = require('unslab')

const MAX_BUFFERED = 32768
const MAX_BACKLOG = Infinity // TODO: impl "open" backpressure
const MAX_BATCH = 8 * 1024 * 1024

class Channel {
  constructor(
    mux,
    info,
    userData,
    protocol,
    aliases,
    id,
    handshake,
    messages,
    onopen,
    onclose,
    ondestroy,
    ondrain
  ) {
    this.userData = userData
    this.protocol = protocol
    this.aliases = aliases
    this.id = id
    this.handshake = null
    this.messages = []

    this.opened = false
    this.closed = false
    this.destroyed = false

    this.onopen = onopen
    this.onclose = onclose
    this.ondestroy = ondestroy
    this.ondrain = ondrain

    this._handshake = handshake
    this._mux = mux
    this._info = info
    this._localId = 0
    this._remoteId = 0
    this._active = 0
    this._extensions = null

    this._decBound = this._dec.bind(this)
    this._decAndDestroyBound = this._decAndDestroy.bind(this)

    this._openedPromise = null
    this._openedResolve = null

    this._destroyedPromise = null
    this._destroyedResolve = null

    for (const m of messages) this.addMessage(m)
  }

  get drained() {
    return this._mux.drained
  }

  fullyOpened() {
    if (this.opened) return Promise.resolve(true)
    if (this.closed) return Promise.resolve(false)
    if (this._openedPromise) return this._openedPromise

    this._openedPromise = new Promise((resolve) => {
      this._openedResolve = resolve
    })
    return this._openedPromise
  }

  fullyClosed() {
    if (this.destroyed) return Promise.resolve()
    if (this._destroyedPromise) return this._destroyedPromise

    this._destroyedPromise = new Promise((resolve) => {
      this._destroyedResolve = resolve
    })
    return this._destroyedPromise
  }

  open(handshake) {
    const id = this._mux._free.length > 0 ? this._mux._free.pop() : this._mux._local.push(null) - 1

    this._info.opened++
    this._info.lastChannel = this
    this._localId = id + 1
    this._mux._local[id] = this

    if (this._remoteId === 0) {
      this._info.outgoing.push(this._localId)
    }

    const state = { buffer: null, start: 2, end: 2 }

    c.uint.preencode(state, this._localId)
    c.string.preencode(state, this.protocol)
    c.optionalBuffer.preencode(state, this.id)
    if (this._handshake) this._handshake.preencode(state, handshake)

    state.buffer = this._mux._alloc(state.end)

    state.buffer[0] = 0
    state.buffer[1] = 1
    c.uint.encode(state, this._localId)
    c.string.encode(state, this.protocol)
    c.optionalBuffer.encode(state, this.id)
    if (this._handshake) this._handshake.encode(state, handshake)

    this._mux._write0(state.buffer)
  }

  _dec() {
    if (--this._active === 0 && this.closed === true) this._destroy()
  }

  _decAndDestroy(err) {
    this._dec()
    this._mux._safeDestroy(err)
  }

  _fullyOpenSoon() {
    this._mux._remote[this._remoteId - 1].session = this
    queueTick(this._fullyOpenOrDestroy.bind(this))
  }

  _fullyOpenOrDestroy() {
    try {
      this._fullyOpen()
    } catch (err) {
      this._mux._safeDestroyBound(err)
    }
  }

  _fullyOpen() {
    if (this.opened === true || this.closed === true) return

    const remote = this._mux._remote[this._remoteId - 1]

    this.handshake = this._handshake ? this._handshake.decode(remote.state) : null
    this._track(this.onopen(this.handshake, this))

    remote.session = this
    remote.state = null
    if (remote.pending !== null) this._drain(remote)
    if (this._mux._destroying === true) return

    this.opened = true
    this._resolveOpen(true)
  }

  _resolveOpen(opened) {
    if (this._openedResolve !== null) {
      this._openedResolve(opened)
      this._openedResolve = this._openedPromise = null
    }
  }

  _resolveDestroyed() {
    if (this._destroyedResolve !== null) {
      this._destroyedResolve()
      this._destroyedResolve = this._destroyedPromise = null
    }
  }

  _drain(remote) {
    for (let i = 0; i < remote.pending.length; i++) {
      const p = remote.pending[i]
      this._mux._buffered -= byteSize(p.state)
      this._recv(p.type, p.state)
      if (this._mux._destroying === true) return
    }

    remote.pending = null
    this._mux._resumeMaybe()
  }

  _track(p) {
    if (isPromise(p) === true) {
      this._active++
      return p.then(this._decBound, this._decAndDestroyBound)
    }

    return null
  }

  _close(isRemote) {
    if (this.closed === true) return
    this.closed = true

    this._info.opened--
    if (this._info.lastChannel === this) this._info.lastChannel = null

    if (this._remoteId > 0) {
      this._mux._remote[this._remoteId - 1] = null
      this._remoteId = 0
      // If remote has acked, we can reuse the local id now
      // otherwise, we need to wait for the "ack" to arrive
      this._mux._free.push(this._localId - 1)
    }

    this._mux._local[this._localId - 1] = null
    this._localId = 0

    this._mux._gc(this._info)
    this._track(this.onclose(isRemote, this))

    if (this._active === 0) this._destroy()

    this._resolveOpen(false)
  }

  _destroy() {
    if (this.destroyed === true) return
    this.destroyed = true
    this._track(this.ondestroy(this))
    this._resolveDestroyed()
  }

  _recv(type, state) {
    if (type < this.messages.length) {
      const m = this.messages[type]
      const p = m.recv(state, this)
      if (m.autoBatch === true) return p
    }
    return null
  }

  cork() {
    this._mux.cork()
  }

  uncork() {
    this._mux.uncork()
  }

  close() {
    if (this.closed === true) return

    const state = { buffer: null, start: 2, end: 2 }

    c.uint.preencode(state, this._localId)

    state.buffer = this._mux._alloc(state.end)

    state.buffer[0] = 0
    state.buffer[1] = 3
    c.uint.encode(state, this._localId)

    this._close(false)
    this._mux._write0(state.buffer)
  }

  addMessage(opts) {
    if (!opts) return this._skipMessage()

    const type = this.messages.length
    const autoBatch = opts.autoBatch !== false
    const encoding = opts.encoding || c.raw
    const onmessage = opts.onmessage || noop

    const s = this
    const typeLen = encodingLength(c.uint, type)

    const m = {
      type,
      autoBatch,
      encoding,
      onmessage,
      recv(state, session) {
        return session._track(m.onmessage(encoding.decode(state), session))
      },
      send(m, session = s) {
        if (session.closed === true) return false

        const mux = session._mux
        const state = { buffer: null, start: 0, end: typeLen }

        if (mux._batch !== null) {
          encoding.preencode(state, m)
          state.buffer = mux._alloc(state.end)

          c.uint.encode(state, type)
          encoding.encode(state, m)

          mux._pushBatch(session._localId, state.buffer)
          return true
        }

        c.uint.preencode(state, session._localId)
        encoding.preencode(state, m)

        state.buffer = mux._alloc(state.end)

        c.uint.encode(state, session._localId)
        c.uint.encode(state, type)
        encoding.encode(state, m)

        mux.drained = mux.stream.write(state.buffer)

        return mux.drained
      }
    }

    this.messages.push(m)

    return m
  }

  _skipMessage() {
    const type = this.messages.length
    const m = {
      type,
      encoding: c.raw,
      onmessage: noop,
      recv(state, session) {},
      send(m, session) {}
    }

    this.messages.push(m)
    return m
  }
}

module.exports = class Protomux {
  constructor(stream, { alloc } = {}) {
    if (stream.userData === null) stream.userData = this

    this.isProtomux = true
    this.stream = stream
    this.corked = 0
    this.drained = true

    this._alloc =
      alloc || (typeof stream.alloc === 'function' ? stream.alloc.bind(stream) : b4a.allocUnsafe)
    this._safeDestroyBound = this._safeDestroy.bind(this)
    this._uncorkBound = this.uncork.bind(this)

    this._remoteBacklog = 0
    this._buffered = 0
    this._paused = false
    this._remote = []
    this._local = []
    this._free = []
    this._batch = null
    this._batchState = null

    this._infos = new Map()
    this._notify = new Map()
    // stream.destroyed flips asynchronously on streamx-based transports.
    this._destroying = false

    this.stream.on('data', this._ondata.bind(this))
    this.stream.on('drain', this._ondrain.bind(this))
    this.stream.on('end', this._onend.bind(this))
    this.stream.on('error', noop) // we handle this in "close"
    this.stream.on('close', this._shutdown.bind(this))
  }

  static from(stream, opts) {
    if (stream.userData && stream.userData.isProtomux) return stream.userData
    if (stream.isProtomux) return stream
    return new this(stream, opts)
  }

  static isProtomux(mux) {
    return typeof mux === 'object' && mux.isProtomux === true
  }

  *[Symbol.iterator]() {
    for (const session of this._local) {
      if (session !== null) yield session
    }
  }

  isIdle() {
    return this._local.length === this._free.length
  }

  cork() {
    if (++this.corked === 1) {
      this._batch = []
      this._batchState = { buffer: null, start: 0, end: 1 }
    }
  }

  uncork() {
    if (--this.corked === 0) {
      this._sendBatch(this._batch, this._batchState)
      this._batch = null
      this._batchState = null
    }
  }

  getLastChannel({ protocol, id = null }) {
    const key = toKey(protocol, id)
    const info = this._infos.get(key)
    if (info) return info.lastChannel
    return null
  }

  pair({ protocol, id = null }, notify) {
    this._notify.set(toKey(protocol, id), notify)
  }

  unpair({ protocol, id = null }) {
    this._notify.delete(toKey(protocol, id))
  }

  opened({ protocol, id = null }) {
    const key = toKey(protocol, id)
    const info = this._infos.get(key)
    return info ? info.opened > 0 : false
  }

  createChannel({
    userData = null,
    protocol,
    aliases = [],
    id = null,
    unique = true,
    handshake = null,
    messages = [],
    onopen = noop,
    onclose = noop,
    ondestroy = noop,
    ondrain = noop
  }) {
    if (this.stream.destroyed) return null

    const info = this._get(protocol, id, aliases)
    if (unique && info.opened > 0) return null

    if (info.incoming.length === 0) {
      return new Channel(
        this,
        info,
        userData,
        protocol,
        aliases,
        id,
        handshake,
        messages,
        onopen,
        onclose,
        ondestroy,
        ondrain
      )
    }

    this._remoteBacklog--

    const remoteId = info.incoming.shift()
    const r = this._remote[remoteId - 1]
    if (r === null) return null

    const session = new Channel(
      this,
      info,
      userData,
      protocol,
      aliases,
      id,
      handshake,
      messages,
      onopen,
      onclose,
      ondestroy,
      ondrain
    )

    session._remoteId = remoteId
    session._fullyOpenSoon()

    return session
  }

  _pushBatch(localId, buffer) {
    if (this._batchState.end >= MAX_BATCH) {
      this._sendBatch(this._batch, this._batchState)
      this._batch = []
      this._batchState = { buffer: null, start: 0, end: 1 }
    }

    if (this._batch.length === 0 || this._batch[this._batch.length - 1].localId !== localId) {
      this._batchState.end++
      c.uint.preencode(this._batchState, localId)
    }
    c.buffer.preencode(this._batchState, buffer)
    this._batch.push({ localId, buffer })
  }

  _sendBatch(batch, state) {
    if (batch.length === 0) return

    let prev = batch[0].localId

    state.buffer = this._alloc(state.end)
    state.buffer[state.start++] = 0
    state.buffer[state.start++] = 0

    c.uint.encode(state, prev)

    for (let i = 0; i < batch.length; i++) {
      const b = batch[i]
      if (prev !== b.localId) {
        state.buffer[state.start++] = 0
        c.uint.encode(state, (prev = b.localId))
      }
      c.buffer.encode(state, b.buffer)
    }

    this.drained = this.stream.write(state.buffer)
  }

  _get(protocol, id, aliases = []) {
    const key = toKey(protocol, id)

    let info = this._infos.get(key)
    if (info) return info

    info = {
      key,
      protocol,
      aliases: [],
      id,
      pairing: 0,
      opened: 0,
      incoming: [],
      outgoing: [],
      lastChannel: null
    }
    this._infos.set(key, info)

    for (const alias of aliases) {
      const key = toKey(alias, id)
      info.aliases.push(key)

      this._infos.set(key, info)
    }

    return info
  }

  _gc(info) {
    if (info.opened === 0 && info.outgoing.length === 0 && info.incoming.length === 0) {
      this._infos.delete(info.key)

      for (const alias of info.aliases) this._infos.delete(alias)
    }
  }

  _ondata(buffer) {
    if (buffer.byteLength === 0) return // ignore empty frames...
    try {
      const state = { buffer, start: 0, end: buffer.byteLength }
      this._decode(c.uint.decode(state), state)
    } catch (err) {
      this._safeDestroy(err)
    }
  }

  _ondrain() {
    this.drained = true

    for (const s of this._local) {
      if (s !== null) s._track(s.ondrain(s))
    }
  }

  _onend() {
    // TODO: support half open mode for the users who wants that here
    this.stream.end()
  }

  _decode(remoteId, state) {
    const type = c.uint.decode(state)

    if (remoteId === 0) {
      return this._oncontrolsession(type, state)
    }

    const r = remoteId <= this._remote.length ? this._remote[remoteId - 1] : null

    // if the channel is closed ignore - could just be a pipeline message...
    if (r === null) return null

    if (r.pending !== null) {
      this._bufferMessage(r, type, state)
      return null
    }

    return r.session._recv(type, state)
  }

  _oncontrolsession(type, state) {
    switch (type) {
      case 0:
        this._onbatch(state)
        break

      case 1:
        // return the promise back up as this has sideeffects so we can batch reply
        return this._onopensession(state)

      case 2:
        this._onrejectsession(state)
        break

      case 3:
        this._onclosesession(state)
        break
    }

    return null
  }

  _bufferMessage(r, type, { buffer, start, end }) {
    const state = { buffer, start, end } // copy
    r.pending.push({ type, state })
    this._buffered += byteSize(state)
    this._pauseMaybe()
  }

  _pauseMaybe() {
    if (this._paused === true || this._buffered <= MAX_BUFFERED) return
    this._paused = true
    this.stream.pause()
  }

  _resumeMaybe() {
    if (this._paused === false || this._buffered > MAX_BUFFERED) return
    this._paused = false
    this.stream.resume()
  }

  _onbatch(state) {
    const end = state.end
    let remoteId = c.uint.decode(state)

    let waiting = null

    while (state.end > state.start) {
      const len = c.uint.decode(state)
      if (len === 0) {
        remoteId = c.uint.decode(state)
        continue
      }
      state.end = state.start + len
      // if batch contains more than one message, cork it so we reply back with a batch
      if (end !== state.end && waiting === null) {
        waiting = []
        this.cork()
      }
      const p = this._decode(remoteId, state)
      if (waiting !== null && p !== null) waiting.push(p)
      state.start = state.end
      state.end = end
    }

    if (waiting !== null) {
      // the waiting promises are not allowed to throw but we destroy the stream in case we are wrong
      Promise.all(waiting).then(this._uncorkBound, this._safeDestroyBound)
    }
  }

  _onopensession(state) {
    const remoteId = c.uint.decode(state)
    const protocol = c.string.decode(state)
    const id = unslab(c.optionalBuffer.decode(state))

    // remote tried to open the control session - auto reject for now
    // as we can use as an explicit control protocol declaration if we need to
    if (remoteId === 0) {
      this._rejectSession(0)
      return null
    }

    const rid = remoteId - 1
    const info = this._get(protocol, id)

    // allow the remote to grow the ids by one
    if (this._remote.length === rid) {
      this._remote.push(null)
    }

    if (rid >= this._remote.length || this._remote[rid] !== null) {
      throw new Error('Invalid open message')
    }

    if (info.outgoing.length > 0) {
      const localId = info.outgoing.shift()
      const session = this._local[localId - 1]

      if (session === null) {
        // we already closed the channel - ignore
        this._free.push(localId - 1)
        return null
      }

      this._remote[rid] = { state, pending: null, session: null }

      session._remoteId = remoteId
      session._fullyOpen()
      return null
    }

    const copyState = { buffer: state.buffer, start: state.start, end: state.end }
    this._remote[rid] = { state: copyState, pending: [], session: null }

    if (++this._remoteBacklog > MAX_BACKLOG) {
      throw new Error('Remote exceeded backlog')
    }

    info.pairing++
    info.incoming.push(remoteId)

    return this._requestSession(protocol, id, info).catch(this._safeDestroyBound)
  }

  _onrejectsession(state) {
    const localId = c.uint.decode(state)

    // TODO: can be done smarter...
    for (const info of this._infos.values()) {
      const i = info.outgoing.indexOf(localId)
      if (i === -1) continue

      info.outgoing.splice(i, 1)

      const session = this._local[localId - 1]

      this._free.push(localId - 1)
      if (session !== null) session._close(true)

      this._gc(info)
      return
    }

    throw new Error('Invalid reject message')
  }

  _onclosesession(state) {
    const remoteId = c.uint.decode(state)

    if (remoteId === 0) return // ignore

    const rid = remoteId - 1
    const r = rid < this._remote.length ? this._remote[rid] : null

    if (r === null) return

    if (r.session !== null) r.session._close(true)
  }

  async _requestSession(protocol, id, info) {
    const notify = this._notify.get(toKey(protocol, id)) || this._notify.get(toKey(protocol, null))

    if (notify) await notify(id)

    if (--info.pairing > 0) return

    while (info.incoming.length > 0) {
      this._rejectSession(info, info.incoming.shift())
    }

    this._gc(info)
  }

  _rejectSession(info, remoteId) {
    if (remoteId > 0) {
      const r = this._remote[remoteId - 1]

      if (r.pending !== null) {
        for (let i = 0; i < r.pending.length; i++) {
          this._buffered -= byteSize(r.pending[i].state)
        }
      }

      this._remote[remoteId - 1] = null
      this._resumeMaybe()
    }

    const state = { buffer: null, start: 2, end: 2 }

    c.uint.preencode(state, remoteId)

    state.buffer = this._alloc(state.end)

    state.buffer[0] = 0
    state.buffer[1] = 2
    c.uint.encode(state, remoteId)

    this._write0(state.buffer)
  }

  _write0(buffer) {
    if (this._batch !== null) {
      this._pushBatch(0, buffer.subarray(1))
      return
    }

    this.drained = this.stream.write(buffer)
  }

  destroy(err) {
    this._destroying = true
    this.stream.destroy(err)
  }

  _safeDestroy(err) {
    safetyCatch(err)
    this._destroying = true
    this.stream.destroy(err)
  }

  _shutdown() {
    this._destroying = true
    for (const s of this._local) {
      if (s !== null) s._close(true)
    }
  }
}

function noop() {}

function toKey(protocol, id) {
  return protocol + '##' + (id ? b4a.toString(id, 'hex') : '')
}

function byteSize(state) {
  return 512 + (state.end - state.start)
}

function isPromise(p) {
  return !!(p && typeof p.then === 'function')
}

function encodingLength(enc, val) {
  const state = { buffer: null, start: 0, end: 0 }
  enc.preencode(state, val)
  return state.end
}
{
  "name": "protomux",
  "version": "3.11.0",
  "description": "Multiplex multiple message oriented protocols over a stream",
  "main": "index.js",
  "files": [
    "index.js"
  ],
  "dependencies": {
    "b4a": "^1.3.1",
    "compact-encoding": "^3.0.0",
    "queue-tick": "^1.0.0",
    "safety-catch": "^1.0.1",
    "unslab": "^1.3.0"
  },
  "devDependencies": {
    "@hyperswarm/secret-stream": "^6.0.0",
    "brittle": "^3.0.0",
    "lunte": "^1.0.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^2.0.0"
  },
  "scripts": {
    "format": "prettier --write .",
    "lint": "prettier --check . && lunte",
    "test": "npm run test:node && npm run test:bare",
    "test:node": "brittle-node test.js",
    "test:bare": "brittle-bare test.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/holepunchto/protomux.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/holepunchto/protomux/issues"
  },
  "homepage": "https://github.com/holepunchto/protomux"
}
{
  "name": "queue-tick",
  "version": "1.0.1",
  "description": "Next tick shim that prefers process.nextTick over queueMicrotask for compat",
  "main": "./process-next-tick.js",
  "browser": "./queue-microtask.js",
  "dependencies": {},
  "devDependencies": {
    "standard": "^16.0.3",
    "tape": "^5.3.1"
  },
  "scripts": {
    "test": "standard && tape test.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/mafintosh/queue-tick.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/mafintosh/queue-tick/issues"
  },
  "homepage": "https://github.com/mafintosh/queue-tick"
}
module.exports = (typeof process !== 'undefined' && typeof process.nextTick === 'function')
  ? process.nextTick.bind(process)
  : require('./queue-microtask')
module.exports = typeof queueMicrotask === 'function' ? queueMicrotask : (fn) => Promise.resolve().then(fn)
const b4a = require('b4a')

var EMPTY = []

module.exports = RecordCache

function RecordSet () {
  this.list = []
  this.map = new Map()
}

RecordSet.prototype.add = function (record, value) {
  var k = toString(record)
  var r = this.map.get(k)
  if (r) return false

  r = {index: this.list.length, record: value || record}
  this.list.push(r)
  this.map.set(k, r)
  return true
}

RecordSet.prototype.remove = function (record) {
  var k = toString(record)
  var r = this.map.get(k)
  if (!r) return false

  swap(this.list, r.index, this.list.length - 1)
  this.list.pop()
  this.map.delete(k)
  return true
}

function RecordStore () {
  this.records = new Map()
  this.size = 0
}

RecordStore.prototype.add = function (name, record, value) {
  var r = this.records.get(name)

  if (!r) {
    r = new RecordSet()
    this.records.set(name, r)
  }

  if (r.add(record, value)) {
    this.size++
    return true
  }

  return false
}

RecordStore.prototype.remove = function (name, record, value) {
  var r = this.records.get(name)
  if (!r) return false

  if (r.remove(record, value)) {
    this.size--
    if (!r.map.size) this.records.delete(name)
    return true
  }

  return false
}

RecordStore.prototype.get = function (name) {
  var r = this.records.get(name)
  return r ? r.list : EMPTY
}

function RecordCache (opts) {
  if (!(this instanceof RecordCache)) return new RecordCache(opts)
  if (!opts) opts = {}

  this.maxSize = opts.maxSize || Infinity
  this.maxAge = opts.maxAge || 0

  this._onstale = opts.onStale || opts.onstale || null
  this._fresh = new RecordStore()
  this._stale = new RecordStore()
  this._interval = null
  this._gced = false

  if (this.maxAge && this.maxAge < Infinity) {
    // 2/3 gives us a span of 0.66-1.33 maxAge or avg maxAge
    var tick = Math.ceil(2 / 3 * this.maxAge)
    this._interval = setInterval(this._gcAuto.bind(this), tick)
    if (this._interval.unref) this._interval.unref()
  }
}

Object.defineProperty(RecordCache.prototype, 'size', {
  get: function () {
    return this._fresh.size + this._stale.size
  }
})

RecordCache.prototype.add = function (name, record, value) {
  this._stale.remove(name, record, value)
  if (this._fresh.add(name, record, value) && this._fresh.size > this.maxSize) {
    this._gc()
  }
}

RecordCache.prototype.remove = function (name, record, value) {
  this._fresh.remove(name, record, value)
  this._stale.remove(name, record, value)
}

RecordCache.prototype.get = function (name, n) {
  var a = this._fresh.get(name)
  var b = this._stale.get(name)
  var aLen = a.length
  var bLen = b.length
  var len = aLen + bLen

  if (n > len || !n) n = len
  var result = new Array(n)

  for (var i = 0; i < n; i++) {
    var j = Math.floor(Math.random() * (aLen + bLen))
    if (j < aLen) {
      result[i] = a[j].record
      swap(a, j, --aLen)
    } else {
      j -= aLen
      result[i] = b[j].record
      swap(b, j, --bLen)
    }
  }

  return result
}

RecordCache.prototype._gcAuto = function () {
  if (!this._gced) this._gc()
  this._gced = false
}

RecordCache.prototype._gc = function () {
  if (this._onstale && this._stale.size > 0) this._onstale(this._stale)
  this._stale = this._fresh
  this._fresh = new RecordStore()
  this._gced = true
}

RecordCache.prototype.clear = function () {
  this._gc()
  this._gc()
}

RecordCache.prototype.destroy = function () {
  this.clear()
  clearInterval(this._interval)
  this._interval = null
}

function toString (record) {
  return b4a.isBuffer(record) ? b4a.toString(record, 'hex') : record
}

function swap (list, a, b) {
  var tmp = list[a]
  tmp.index = b
  list[b].index = a
  list[a] = list[b]
  list[b] = tmp
}
{
  "name": "record-cache",
  "version": "1.2.0",
  "description": "Cache optimised for record like things",
  "main": "index.js",
  "dependencies": {
    "b4a": "^1.3.1"
  },
  "devDependencies": {
    "standard": "^10.0.3",
    "tape": "^4.8.0"
  },
  "scripts": {
    "test": "standard && tape test.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/mafintosh/record-cache.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/mafintosh/record-cache/issues"
  },
  "homepage": "https://github.com/mafintosh/record-cache"
}
module.exports = require.addon.bind(require)
{
  "name": "require-addon",
  "version": "1.2.0",
  "description": "Import native addons across JavaScript runtimes",
  "exports": {
    "./package": "./package.json",
    ".": {
      "bare": "./lib/bare.js",
      "node": "./lib/node.js",
      "default": "./lib/default.js"
    }
  },
  "imports": {
    "fs": {
      "bare": "bare-fs",
      "default": "fs"
    },
    "path": {
      "bare": "bare-path",
      "default": "path"
    },
    "url": {
      "bare": "bare-url",
      "default": "url"
    }
  },
  "files": [
    "lib"
  ],
  "scripts": {
    "test": "npm run lint && npm run test:bare && npm run test:node",
    "test:bare": "bare test.js",
    "test:node": "node test.js",
    "lint": "prettier --check .",
    "format": "prettier --write ."
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/require-addon.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/require-addon/issues"
  },
  "homepage": "https://github.com/holepunchto/require-addon#readme",
  "engines": {
    "bare": ">=1.10.0"
  },
  "dependencies": {
    "bare-addon-resolve": "^1.3.0"
  },
  "devDependencies": {
    "bare-bundle": "^1.8.1",
    "bare-bundle-evaluate": "^1.1.0",
    "bare-fs": "^4.0.0",
    "bare-path": "^3.0.0",
    "bare-url": "^2.1.0",
    "brittle": "^3.7.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^2.0.0"
  }
}
module.exports = safetyCatch

function isActuallyUncaught (err) {
  if (!err) return false
  return err instanceof TypeError ||
    err instanceof SyntaxError ||
    err instanceof ReferenceError ||
    err instanceof EvalError ||
    err instanceof RangeError ||
    err instanceof URIError ||
    err.code === 'ERR_ASSERTION' ||
    err.name === 'AssertionError'
}

function throwErrorNT (err) {
  queueMicrotask(() => { throw err })
}

function safetyCatch (err) {
  if (isActuallyUncaught(err)) {
    throwErrorNT(err)
    throw err
  }
}
{
  "name": "safety-catch",
  "version": "1.0.3",
  "description": "Small module that makes sure your catch, caught an actual error and not a programming mistake or assertion",
  "main": "index.js",
  "dependencies": {},
  "devDependencies": {},
  "repository": {
    "type": "git",
    "url": "https://github.com/mafintosh/safety-catch.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/mafintosh/safety-catch/issues"
  },
  "homepage": "https://github.com/mafintosh/safety-catch"
}
const set = require('unordered-set')

module.exports = opts => new ShuffledPriorityQueue(opts)

class ShuffledPriorityQueue {
  constructor (opts) {
    this.priorities = []
    this.equals = (opts && opts.equals) || null
  }

  get length () {
    return this.priorities.reduce(add, 0)
  }

  [Symbol.iterator] () {
    return new Iterator(this)
  }

  head () {
    for (let i = this.priorities.length - 1; i >= 0; i--) {
      const q = this.priorities[i]
      if (q.length) return shuffle(q, 0)
    }
    return null
  }

  tail () {
    for (let i = 0; i < this.priorities.length; i++) {
      const q = this.priorities[i]
      if (q.length) return shuffle(q, 0)
    }
    return null
  }

  prev (prev) {
    if (!prev) return this.tail()
    return next(this.priorities, prev, 1)
  }

  next (prev) {
    if (!prev) return this.head()
    return next(this.priorities, prev, -1)
  }

  shift () {
    return this.remove(this.head())
  }

  pop () {
    return this.remove(this.tail())
  }

  add (val) {
    const prio = val.priority || 0
    while (prio >= this.priorities.length) this.priorities.push([])
    set.add(this.priorities[prio], val)
    return val
  }

  remove (val) {
    if (!val) return null

    if (val._index === undefined) {
      val = this.find(val)
      if (!val) return null
    }

    return set.remove(this.priorities[val.priority || 0], val)
  }

  has (val) {
    if (val._index === undefined) return this.find(val)
    const priority = val.priority || 0
    if (priority >= this.priorities.length) return false
    return set.has(this.priorities[priority], val)
  }

  find (val) {
    if (val._index !== undefined) return val

    const prio = val.priority || 0
    const qs = this.priorities
    if (prio >= qs.length) return null

    const q = qs[prio]

    for (let i = 0; i < q.length; i++) {
      if (this.equals(q[i], val)) return q[i]
    }

    return null
  }
}

class Iterator {
  constructor (queue) {
    this.prev = null
    this.queue = queue
  }

  next () {
    const next = this.queue.next(this.prev)
    this.prev = next
    return { done: !next, value: next }
  }
}

function shuffle (q, i) {
  const ran = i + Math.floor(Math.random() * (q.length - i))
  set.swap(q, q[ran], q[i])
  return q[i]
}

function next (queues, prev, inc) {
  let i = prev.priority || 0
  let j = (prev._index || 0) + 1

  while (true) {
    if (i < 0 || i >= queues.length) return null
    const q = queues[i]

    if (j >= q.length) {
      i += inc
      j = 0
      continue
    }

    return shuffle(q, j)
  }
}

function add (len, b) {
  return len + b.length
}
{
  "name": "shuffled-priority-queue",
  "version": "2.1.0",
  "description": "A priority queue that shuffles elements with the same priority.",
  "main": "index.js",
  "scripts": {
    "test": "standard && tape test.js"
  },
  "dependencies": {
    "unordered-set": "^2.0.1"
  },
  "devDependencies": {
    "standard": "^12.0.1",
    "tape": "^4.9.1"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/mafintosh/shuffled-priority-queue.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/mafintosh/shuffled-priority-queue/issues"
  },
  "homepage": "https://github.com/mafintosh/shuffled-priority-queue"
}
module.exports = class Signal {
  constructor () {
    this._resolve = null
    this._reject = null
    this._promise = null
    this._bind = bind.bind(this)
    this._onerror = clear.bind(this)
    this._onsuccess = clear.bind(this, null)
    this._timers = new Set()
  }

  wait (max) {
    if (!this._promise) {
      this._promise = new Promise(this._bind)
      this._promise.then(this._onsuccess).catch(this._onerror)
    }
    if (max) return this._sleep(max)
    return this._promise
  }

  _sleep (max) {
    const s = new Promise((resolve, reject) => {
      const done = () => {
        this._timers.delete(state)
        resolve(true)
      }
      const id = setTimeout(done, max)
      const state = { id, resolve, reject }
      this._timers.add(state)
    })

    return s
  }

  notify (err) {
    if (!this._promise) return
    const resolve = this._resolve
    const reject = this._reject
    this._promise = null
    if (err) reject(err)
    else resolve(true)
  }
}

function clear (err) {
  for (const { id, resolve, reject } of this._timers) {
    clearTimeout(id)
    if (err) reject(err)
    else resolve(true)
  }
  this._timers.clear()
}

function bind (resolve, reject) {
  this._resolve = resolve
  this._reject = reject
}
{
  "name": "signal-promise",
  "version": "1.0.3",
  "description": "Simple wait/notify promise with optional max wait time",
  "main": "index.js",
  "dependencies": {},
  "devDependencies": {},
  "repository": {
    "type": "git",
    "url": "https://github.com/mafintosh/signal-promise.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/mafintosh/signal-promise/issues"
  },
  "homepage": "https://github.com/mafintosh/signal-promise"
}
require.addon = require('require-addon')
module.exports = require.addon('.', __filename)
const assert = require('assert')
const binding = require('./binding')
const { isNode } = require('which-runtime')

const OPTIONAL = Buffer.from(new ArrayBuffer(0))

module.exports = exports = { ...binding }

// memory

exports.sodium_memzero = function (buf) {
  assert(ArrayBuffer.isView(buf), 'buf must be a typed array')

  binding.sodium_memzero(buf)
}

exports.sodium_mlock = function (buf) {
  assert(ArrayBuffer.isView(buf), 'buf must be a typed array')

  const res = binding.sodium_mlock(buf)

  if (res !== 0) throw new Error('memory lock failed')
}

exports.sodium_munlock = function (buf) {
  assert(ArrayBuffer.isView(buf), 'buf must be a typed array')

  const res = binding.sodium_munlock(buf)

  if (res !== 0) throw new Error('memory unlock failed')
}

exports.sodium_malloc = function (size) {
  assert(size >= 0, 'invalid size')

  const buf = Buffer.from(binding.sodium_malloc(size))
  buf.secure = true
  return buf
}

exports.sodium_free = function (buf) {
  if (!buf || !buf.secure) return

  binding.sodium_free(buf.buffer)
}

exports.sodium_mprotect_noaccess = function (buf) {
  const res = binding.sodium_mprotect_noaccess(buf.buffer)

  if (res !== 0) throw new Error('failed to lock buffer')
}

exports.sodium_mprotect_readonly = function (buf) {
  const res = binding.sodium_mprotect_readonly(buf.buffer)

  if (res !== 0) throw new Error('failed to unlock buffer')
}

exports.sodium_mprotect_readwrite = function (buf) {
  const res = binding.sodium_mprotect_readwrite(buf.buffer)

  if (res !== 0) throw new Error('failed to unlock buffer')
}

// crypto_randombytes

exports.randombytes_buf = function (buffer) {
  assert(ArrayBuffer.isView(buffer), 'buffer must be a typed array')

  binding.randombytes_buf(buffer.buffer, buffer.byteOffset, buffer.byteLength)
}

exports.randombytes_buf_deterministic = function (buffer, seed) {
  assert(ArrayBuffer.isView(buffer), 'buffer must be a typed array')
  assert(ArrayBuffer.isView(seed), 'seed must be a typed array')
  assert(
    seed.byteLength === binding.randombytes_SEEDBYTES,
    "seed must be 'randombytes_SEEDBYTES' bytes"
  )

  binding.randombytes_buf_deterministic(
    buffer.buffer,
    buffer.byteOffset,
    buffer.byteLength,

    seed.buffer,
    seed.byteOffset,
    seed.byteLength
  )
}

// sodium_helpers

exports.sodium_memcmp = function (a, b) {
  assert(ArrayBuffer.isView(a), 'a must be a typed array')
  assert(ArrayBuffer.isView(b), 'b must be a typed array')
  assert(a.byteLength === b.byteLength, 'buffers must be of same length')

  return binding.sodium_memcmp(a, b)
}

exports.sodium_add = function (a, b) {
  assert(ArrayBuffer.isView(a), 'a must be a typed array')
  assert(ArrayBuffer.isView(b), 'b must be a typed array')
  assert(a.byteLength === b.byteLength, 'buffers must be of same length')

  binding.sodium_add(a, b)
}

exports.sodium_sub = function (a, b) {
  assert(ArrayBuffer.isView(a), 'a must be a typed array')
  assert(ArrayBuffer.isView(b), 'b must be a typed array')
  assert(a.byteLength === b.byteLength, 'buffers must be of same length')

  binding.sodium_sub(a, b)
}

exports.sodium_compare = function (a, b) {
  assert(ArrayBuffer.isView(a), 'a must be a typed array')
  assert(ArrayBuffer.isView(b), 'b must be a typed array')
  assert(a.byteLength === b.byteLength, 'buffers must be of same length')

  return binding.sodium_compare(a, b)
}

exports.sodium_is_zero = function (buffer, length) {
  if (length === undefined) length = buffer.byteLength

  assert(ArrayBuffer.isView(buffer), 'buffer must be a typed array')
  assert(length >= 0 && length <= buffer.byteLength, 'invalid length')

  return binding.sodium_is_zero(buffer, length)
}

exports.sodium_pad = function (buffer, unpaddedBuflen, blockSize) {
  assert(ArrayBuffer.isView(buffer), 'buffer must be a typed array')
  assert(unpaddedBuflen <= buffer.byteLength, 'unpadded length cannot exceed buffer length')
  assert(blockSize <= buffer.byteLength, 'block size cannot exceed buffer length')
  assert(blockSize >= 1, 'block size must be at least 1 byte')
  assert(
    buffer.byteLength >= unpaddedBuflen + (blockSize - (unpaddedBuflen % blockSize)),
    'buf not long enough'
  )

  return binding.sodium_pad(buffer, unpaddedBuflen, blockSize)
}

exports.sodium_unpad = function (buffer, paddedBuflen, blockSize) {
  assert(ArrayBuffer.isView(buffer), 'buffer must be a typed array')
  assert(paddedBuflen <= buffer.byteLength, 'unpadded length cannot exceed buffer length')
  assert(blockSize <= buffer.byteLength, 'block size cannot exceed buffer length')
  assert(blockSize >= 1, 'block size must be at least 1 byte')

  return binding.sodium_unpad(buffer, paddedBuflen, blockSize)
}

// crypto_sign

exports.crypto_sign_keypair = function (pk, sk) {
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(
    pk.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
    "pk must be 'crypto_sign_PUBLICKEYBYTES' bytes"
  )
  assert(
    sk.byteLength === binding.crypto_sign_SECRETKEYBYTES,
    "sk must be 'crypto_sign_SECRETKEYBYTES' bytes"
  )

  const res = binding.crypto_sign_keypair(pk, sk)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_sign_seed_keypair = function (pk, sk, seed) {
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(ArrayBuffer.isView(seed), 'seed must be a typed array')
  assert(
    pk.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
    "pk must be 'crypto_sign_PUBLICKEYBYTES' bytes"
  )
  assert(
    sk.byteLength === binding.crypto_sign_SECRETKEYBYTES,
    "sk must be 'crypto_sign_SECRETKEYBYTES' bytes"
  )
  assert(
    seed.byteLength === binding.crypto_sign_SEEDBYTES,
    "seed must be 'crypto_sign_SEEDBYTES' bytes"
  )

  const res = binding.crypto_sign_seed_keypair(pk, sk, seed)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_sign = function (sm, m, sk) {
  assert(ArrayBuffer.isView(sm), 'sm must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(
    sm.byteLength === binding.crypto_sign_BYTES + m.byteLength,
    "sm must be 'm.byteLength + crypto_sign_BYTES' bytes"
  )
  assert(
    sk.byteLength === binding.crypto_sign_SECRETKEYBYTES,
    "sk must be 'crypto_sign_SECRETKEYBYTES' bytes"
  )

  const res = binding.crypto_sign(sm, m, sk)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_sign_open = function (m, sm, pk) {
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(sm), 'sm must be a typed array')
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(
    sm.byteLength >= binding.crypto_sign_BYTES,
    "sm must be at least 'crypto_sign_BYTES' bytes"
  )
  assert(
    m.byteLength === sm.byteLength - binding.crypto_sign_BYTES,
    "m must be 'sm.byteLength - crypto_sign_BYTES' bytes"
  )
  assert(
    pk.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
    "pk must be 'crypto_sign_PUBLICKEYBYTES' bytes"
  )

  const res = binding.crypto_sign_open(m, sm, pk)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_sign_open = function (m, sm, pk) {
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(sm), 'sm must be a typed array')
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(
    sm.byteLength >= binding.crypto_sign_BYTES,
    "sm must be at least 'crypto_sign_BYTES' bytes"
  )
  assert(
    m.byteLength === sm.byteLength - binding.crypto_sign_BYTES,
    "m must be 'sm.byteLength - crypto_sign_BYTES' bytes"
  )
  assert(
    pk.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
    "pk must be 'crypto_sign_PUBLICKEYBYTES' bytes"
  )

  return binding.crypto_sign_open(m, sm, pk)
}

exports.crypto_sign_detached = function (sig, m, sk) {
  assert(ArrayBuffer.isView(sig), 'sig must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(sig.byteLength === binding.crypto_sign_BYTES, "sig must be 'crypto_sign_BYTES' bytes")
  assert(
    sk.byteLength === binding.crypto_sign_SECRETKEYBYTES,
    "sk must be 'crypto_sign_SECRETKEYBYTES' bytes"
  )

  const res = binding.crypto_sign_detached(sig, m, sk)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_sign_verify_detached = function (sig, m, pk) {
  assert(ArrayBuffer.isView(sig), 'sig must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(
    sig.byteLength >= binding.crypto_sign_BYTES,
    "sig must be at least 'crypto_sign_BYTES' bytes"
  )
  assert(
    pk.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
    "pk must be 'crypto_sign_PUBLICKEYBYTES' bytes"
  )

  return binding.crypto_sign_verify_detached(
    sig.buffer,
    sig.byteOffset,
    sig.byteLength,

    m.buffer,
    m.byteOffset,
    m.byteLength,

    pk.buffer,
    pk.byteOffset,
    pk.byteLength
  )
}

exports.crypto_sign_ed25519_sk_to_pk = function (pk, sk) {
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(
    pk.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
    "pk must be 'crypto_sign_PUBLICKEYBYTES' bytes"
  )
  assert(
    sk.byteLength === binding.crypto_sign_SECRETKEYBYTES,
    "sk must be 'crypto_sign_SECRETKEYBYTES' bytes"
  )

  const res = binding.crypto_sign_ed25519_sk_to_pk(pk, sk)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_sign_ed25519_pk_to_curve25519 = function (x25519pk, ed25519pk) {
  assert(ArrayBuffer.isView(x25519pk), 'x25519pk must be a typed array')
  assert(ArrayBuffer.isView(ed25519pk), 'ed25519pk must be a typed array')
  assert(
    x25519pk.byteLength === binding.crypto_box_PUBLICKEYBYTES,
    "x25519pk must be 'crypto_box_PUBLICKEYBYTES' bytes"
  )
  assert(
    ed25519pk.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
    "ed25519pk must be 'crypto_sign_PUBLICKEYBYTES' bytes"
  )

  const res = binding.crypto_sign_ed25519_pk_to_curve25519(x25519pk, ed25519pk)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_sign_ed25519_sk_to_curve25519 = function (x25519sk, ed25519sk) {
  assert(ArrayBuffer.isView(x25519sk), 'x25519sk must be a typed array')
  assert(ArrayBuffer.isView(ed25519sk), 'ed25519sk must be a typed array')
  assert(
    x25519sk.byteLength === binding.crypto_box_SECRETKEYBYTES,
    "x25519sk must be 'crypto_box_SECRETKEYBYTES' bytes"
  )

  const edLen = ed25519sk.byteLength

  assert(
    edLen === binding.crypto_sign_SECRETKEYBYTES || edLen === binding.crypto_box_SECRETKEYBYTES,
    "ed25519sk must be 'crypto_sign_SECRETKEYBYTES' or 'crypto_sign_SECRETKEYBYTES - crypto_sign_PUBLICKEYBYTES' bytes"
  )

  const res = binding.crypto_sign_ed25519_sk_to_curve25519(x25519sk, ed25519sk)

  if (res !== 0) throw new Error('status: ' + res)
}

// crypto_box

exports.crypto_box_keypair = function (pk, sk) {
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(
    pk.byteLength === binding.crypto_box_PUBLICKEYBYTES,
    "pk must be 'crypto_box_PUBLICKEYBYTES' bytes"
  )
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(
    sk.byteLength === binding.crypto_box_SECRETKEYBYTES,
    "sk must be 'crypto_box_SECRETKEYBYTES' bytes"
  )

  const res = binding.crypto_box_keypair(pk, sk)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_box_seed_keypair = function (pk, sk, seed) {
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(
    pk.byteLength === binding.crypto_box_PUBLICKEYBYTES,
    "pk must be 'crypto_box_PUBLICKEYBYTES' bytes"
  )
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(
    sk.byteLength === binding.crypto_box_SECRETKEYBYTES,
    "sk must be 'crypto_box_SECRETKEYBYTES' bytes"
  )
  assert(ArrayBuffer.isView(seed), 'seed must be a typed array')
  assert(
    seed.byteLength === binding.crypto_box_SEEDBYTES,
    "seed must be 'crypto_box_SEEDBYTES' bytes"
  )

  const res = binding.crypto_box_seed_keypair(pk, sk, seed)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_box_easy = function (c, m, n, pk, sk) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(
    c.byteLength === m.byteLength + exports.crypto_box_MACBYTES,
    "c must be 'm.byteLength + crypto_box_MACBYTES' bytes"
  )
  assert(n.byteLength === exports.crypto_box_NONCEBYTES, "n must be 'crypto_box_NONCEBYTES' bytes")
  assert(
    pk.byteLength === exports.crypto_box_PUBLICKEYBYTES,
    "pk must be 'crypto_box_PUBLICKEYBYTES' bytes"
  )
  assert(
    sk.byteLength === exports.crypto_box_SECRETKEYBYTES,
    "sk must be 'crypto_box_SECRETKEYBYTES' bytes"
  )

  const res = binding.crypto_box_easy(c, m, n, pk, sk)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_box_detached = function (c, mac, m, n, pk, sk) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(mac), 'mac must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(mac.byteLength === exports.crypto_box_MACBYTES, "mac must be 'crypto_box_MACBYTES' bytes")
  assert(n.byteLength === exports.crypto_box_NONCEBYTES, "n must be 'crypto_box_NONCEBYTES' bytes")
  assert(
    pk.byteLength === exports.crypto_box_PUBLICKEYBYTES,
    "pk must be 'crypto_box_PUBLICKEYBYTES' bytes"
  )
  assert(
    sk.byteLength === exports.crypto_box_SECRETKEYBYTES,
    "sk must be 'crypto_box_SECRETKEYBYTES' bytes"
  )

  const res = binding.crypto_box_detached(c, mac, m, n, pk, sk)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_box_open_easy = function (m, c, n, pk, sk) {
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(
    c.byteLength >= exports.crypto_box_MACBYTES,
    "c must be at least 'crypto_box_MACBYTES' bytes"
  )
  assert(
    m.byteLength === c.byteLength - exports.crypto_box_MACBYTES,
    "m must be 'c.byteLength - crypto_box_MACBYTES' bytes"
  )
  assert(n.byteLength === exports.crypto_box_NONCEBYTES, "n must be 'crypto_box_NONCEBYTES' bytes")
  assert(
    pk.byteLength === exports.crypto_box_PUBLICKEYBYTES,
    "pk must be 'crypto_box_PUBLICKEYBYTES' bytes"
  )
  assert(
    sk.byteLength === exports.crypto_box_SECRETKEYBYTES,
    "sk must be 'crypto_box_SECRETKEYBYTES' bytes"
  )

  return binding.crypto_box_open_easy(m, c, n, pk, sk)
}

exports.crypto_box_open_detached = function (m, c, mac, n, pk, sk) {
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(mac), 'mac must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(m.byteLength === c.byteLength, "m must be 'c.byteLength' bytes")
  assert(mac.byteLength === exports.crypto_box_MACBYTES, "mac must be 'crypto_box_MACBYTES' bytes")
  assert(n.byteLength === exports.crypto_box_NONCEBYTES, "n must be 'crypto_box_NONCEBYTES' bytes")
  assert(
    pk.byteLength === exports.crypto_box_PUBLICKEYBYTES,
    "pk must be 'crypto_box_PUBLICKEYBYTES' bytes"
  )
  assert(
    sk.byteLength === exports.crypto_box_SECRETKEYBYTES,
    "sk must be 'crypto_box_SECRETKEYBYTES' bytes"
  )

  return binding.crypto_box_open_detached(m, c, mac, n, pk, sk)
}

exports.crypto_box_seal = function (c, m, pk) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(
    c.byteLength === m.byteLength + exports.crypto_box_SEALBYTES,
    "c must be 'm.byteLength + crypto_box_SEALBYTES' bytes"
  )
  assert(
    pk.byteLength === exports.crypto_box_PUBLICKEYBYTES,
    "pk must be 'crypto_box_PUBLICKEYBYTES' bytes"
  )

  const res = binding.crypto_box_seal(c, m, pk)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_box_seal_open = function (m, c, pk, sk) {
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(
    c.byteLength >= exports.crypto_box_SEALBYTES,
    "c must be at least 'crypto_box_SEALBYTES' bytes"
  )
  assert(
    m.byteLength === c.byteLength - exports.crypto_box_SEALBYTES,
    "m must be 'c.byteLength - crypto_box_SEALBYTES' bytes"
  )
  assert(
    pk.byteLength === exports.crypto_box_PUBLICKEYBYTES,
    "pk must be 'crypto_box_PUBLICKEYBYTES' bytes"
  )
  assert(
    sk.byteLength === exports.crypto_box_SECRETKEYBYTES,
    "sk must be 'crypto_box_SECRETKEYBYTES' bytes"
  )

  return binding.crypto_box_seal_open(
    m.buffer,
    m.byteOffset,
    m.byteLength,

    c.buffer,
    c.byteOffset,
    c.byteLength,

    pk.buffer,
    pk.byteOffset,
    pk.byteLength,

    sk.buffer,
    sk.byteOffset,
    sk.byteLength
  )
}

// crypto_secretbox

exports.crypto_secretbox_easy = function (c, m, n, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    c.byteLength === m.byteLength + binding.crypto_secretbox_MACBYTES,
    "c must be 'm.byteLength + crypto_secretbox_MACBYTES' bytes"
  )
  assert(
    n.byteLength === binding.crypto_secretbox_NONCEBYTES,
    "n must be 'crypto_secretbox_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_secretbox_KEYBYTES,
    "k must be 'crypto_secretbox_KEYBYTES' bytes"
  )

  const res = binding.crypto_secretbox_easy(c, m, n, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_secretbox_open_easy = function (m, c, n, k) {
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    c.byteLength >= binding.crypto_secretbox_MACBYTES,
    "c must be at least 'crypto_secretbox_MACBYTES' bytes"
  )
  assert(
    m.byteLength === c.byteLength - binding.crypto_secretbox_MACBYTES,
    "m must be 'c.byteLength - crypto_secretbox_MACBYTES' bytes"
  )
  assert(
    n.byteLength === binding.crypto_secretbox_NONCEBYTES,
    "n must be 'crypto_secretbox_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_secretbox_KEYBYTES,
    "k must be 'crypto_secretbox_KEYBYTES' bytes"
  )

  return binding.crypto_secretbox_open_easy(m, c, n, k)
}

exports.crypto_secretbox_detached = function (c, mac, m, n, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(mac), 'mac must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(
    mac.byteLength === binding.crypto_secretbox_MACBYTES,
    "mac must be 'crypto_secretbox_MACBYTES' bytes"
  )
  assert(
    n.byteLength === binding.crypto_secretbox_NONCEBYTES,
    "n must be 'crypto_secretbox_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_secretbox_KEYBYTES,
    "k must be 'crypto_secretbox_KEYBYTES' bytes"
  )

  const res = binding.crypto_secretbox_detached(c, mac, m, n, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_secretbox_open_detached = function (m, c, mac, n, k) {
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(mac), 'mac must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(m.byteLength === c.byteLength, "m must be 'c.byteLength' bytes")
  assert(
    mac.byteLength === binding.crypto_secretbox_MACBYTES,
    "mac must be 'crypto_secretbox_MACBYTES' bytes"
  )
  assert(
    n.byteLength === binding.crypto_secretbox_NONCEBYTES,
    "n must be 'crypto_secretbox_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_secretbox_KEYBYTES,
    "k must be 'crypto_secretbox_KEYBYTES' bytes"
  )

  return binding.crypto_secretbox_open_detached(m, c, mac, n, k)
}

// crypto_generichash

exports.crypto_generichash = function (output, input, key) {
  if (!key) key = OPTIONAL

  assert(ArrayBuffer.isView(output), 'output must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(
    output.byteLength >= binding.crypto_generichash_BYTES_MIN &&
      output.byteLength <= binding.crypto_generichash_BYTES_MAX,
    'output must be between crypto_generichash_BYTES_MIN and crypto_generichash_BYTES_MAX bytes'
  )

  if (key !== OPTIONAL) {
    assert(ArrayBuffer.isView(key), 'key must be a typed array')
    assert(
      key.byteLength >= binding.crypto_generichash_KEYBYTES_MIN &&
        key.byteLength <= binding.crypto_generichash_KEYBYTES_MAX,
      'key must be between crypto_generichash_KEYBYTES_MIN and crypto_generichash_KEYBYTES_MAX bytes'
    )
  }

  const res = binding.crypto_generichash(
    output.buffer,
    output.byteOffset,
    output.byteLength,

    input.buffer,
    input.byteOffset,
    input.byteLength,

    key.buffer,
    key.byteOffset,
    key.byteLength
  )

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_generichash_batch = function (output, batch, key) {
  assert(ArrayBuffer.isView(output), 'output must be a typed array')

  if (isNode || batch.length < 4) {
    const res = binding.crypto_generichash_batch(output, batch, !!key, key || OPTIONAL)
    if (res !== 0) throw new Error('status: ' + res)
  } else {
    const state = Buffer.alloc(binding.crypto_generichash_STATEBYTES)

    exports.crypto_generichash_init(state, key, output.byteLength)

    for (const buf of batch) {
      exports.crypto_generichash_update(state, buf)
    }

    exports.crypto_generichash_final(state, output)
  }
}

exports.crypto_generichash_keygen = function (key) {
  assert(ArrayBuffer.isView(key), 'key must be a typed array')
  assert(
    key.byteLength === binding.crypto_generichash_KEYBYTES,
    "key must be 'crypto_generichash_KEYBYTES' bytes"
  )

  const res = binding.crypto_generichash_keygen(key.buffer, key.byteOffset, key.byteLength)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_generichash_init = function (state, key, outputLength) {
  if (!key) key = OPTIONAL

  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_generichash_STATEBYTES,
    "state must be 'crypto_generichash_STATEBYTES' bytes"
  )

  const res = binding.crypto_generichash_init(
    state.buffer,
    state.byteOffset,
    state.byteLength,

    key.buffer,
    key.byteOffset,
    key.byteLength,

    outputLength
  )

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_generichash_update = function (state, input) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(
    state.byteLength === binding.crypto_generichash_STATEBYTES,
    "state must be 'crypto_generichash_STATEBYTES' bytes"
  )

  const res = binding.crypto_generichash_update(
    state.buffer,
    state.byteOffset,
    state.byteLength,

    input.buffer,
    input.byteOffset,
    input.byteLength
  )

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_generichash_final = function (state, output) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(ArrayBuffer.isView(output), 'output must be a typed array')
  assert(
    state.byteLength === binding.crypto_generichash_STATEBYTES,
    "state must be 'crypto_generichash_STATEBYTES' bytes"
  )

  const res = binding.crypto_generichash_final(
    state.buffer,
    state.byteOffset,
    state.byteLength,

    output.buffer,
    output.byteOffset,
    output.byteLength
  )

  if (res !== 0) throw new Error('status: ' + res)
}

// secretstream

exports.crypto_secretstream_xchacha20poly1305_keygen = function (k) {
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_secretstream_xchacha20poly1305_KEYBYTES,
    "k must be 'crypto_secretstream_xchacha20poly1305_KEYBYTES' bytes"
  )

  binding.crypto_secretstream_xchacha20poly1305_keygen(k.buffer, k.byteOffset, k.byteLength)
}

exports.crypto_secretstream_xchacha20poly1305_init_push = function (state, header, k) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(ArrayBuffer.isView(header), 'header must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    state.byteLength === binding.crypto_secretstream_xchacha20poly1305_STATEBYTES,
    "state must be 'crypto_secretstream_xchacha20poly1305_STATEBYTES' bytes"
  )
  assert(
    header.byteLength === binding.crypto_secretstream_xchacha20poly1305_HEADERBYTES,
    "header must be 'crypto_secretstream_xchacha20poly1305_HEADERBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_secretstream_xchacha20poly1305_KEYBYTES,
    "k must be 'crypto_secretstream_xchacha20poly1305_KEYBYTES' bytes"
  )

  const res = binding.crypto_secretstream_xchacha20poly1305_init_push(
    state.buffer,
    state.byteOffset,
    state.byteLength,

    header.buffer,
    header.byteOffset,
    header.byteLength,

    k.buffer,
    k.byteOffset,
    k.byteLength
  )

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_secretstream_xchacha20poly1305_init_pull = function (state, header, k) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(ArrayBuffer.isView(header), 'header must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    state.byteLength === binding.crypto_secretstream_xchacha20poly1305_STATEBYTES,
    "state must be 'crypto_secretstream_xchacha20poly1305_STATEBYTES' bytes"
  )
  assert(
    header.byteLength === binding.crypto_secretstream_xchacha20poly1305_HEADERBYTES,
    "header must be 'crypto_secretstream_xchacha20poly1305_HEADERBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_secretstream_xchacha20poly1305_KEYBYTES,
    "k must be 'crypto_secretstream_xchacha20poly1305_KEYBYTES' bytes"
  )

  const res = binding.crypto_secretstream_xchacha20poly1305_init_pull(
    state.buffer,
    state.byteOffset,
    state.byteLength,

    header.buffer,
    header.byteOffset,
    header.byteLength,

    k.buffer,
    k.byteOffset,
    k.byteLength
  )

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_secretstream_xchacha20poly1305_push = function (state, c, m, ad, tag) {
  if (!ad) ad = OPTIONAL

  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(
    state.byteLength === binding.crypto_secretstream_xchacha20poly1305_STATEBYTES,
    "state must be 'crypto_secretstream_xchacha20poly1305_STATEBYTES' bytes"
  )
  assert(
    c.byteLength === m.byteLength + binding.crypto_secretstream_xchacha20poly1305_ABYTES,
    "c must be 'm.byteLength + crypto_secretstream_xchacha20poly1305_ABYTES' bytes"
  )

  const res = binding.crypto_secretstream_xchacha20poly1305_push(
    state.buffer,
    state.byteOffset,
    state.byteLength,

    c.buffer,
    c.byteOffset,
    c.byteLength,

    m.buffer,
    m.byteOffset,
    m.byteLength,

    ad.buffer,
    ad.byteOffset,
    ad.byteLength,

    tag
  )

  if (res < 0) throw new Error('push failed')

  return res
}

exports.crypto_secretstream_xchacha20poly1305_pull = function (state, m, tag, c, ad) {
  if (!ad) ad = OPTIONAL

  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_secretstream_xchacha20poly1305_STATEBYTES,
    "state must be 'crypto_secretstream_xchacha20poly1305_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(tag), 'tag must be a typed array')
  assert(tag.byteLength === 1, 'tag must be 1 byte')

  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(
    c.byteLength >= binding.crypto_secretstream_xchacha20poly1305_ABYTES,
    "c must be at least 'crypto_secretstream_xchacha20poly1305_ABYTES' bytes"
  )
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(
    m.byteLength === c.byteLength - binding.crypto_secretstream_xchacha20poly1305_ABYTES,
    "m must be 'c.byteLength - crypto_secretstream_xchacha20poly1305_ABYTES' bytes"
  )

  const res = binding.crypto_secretstream_xchacha20poly1305_pull(
    state.buffer,
    state.byteOffset,
    state.byteLength,

    m.buffer,
    m.byteOffset,
    m.byteLength,

    tag.buffer,
    tag.byteOffset,
    tag.byteLength,

    c.buffer,
    c.byteOffset,
    c.byteLength,

    ad.buffer,
    ad.byteOffset,
    ad.byteLength
  )

  if (res < 0) throw new Error('pull failed')

  return res
}

exports.crypto_secretstream_xchacha20poly1305_rekey = function (state) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_secretstream_xchacha20poly1305_STATEBYTES,
    "state must be 'crypto_secretstream_xchacha20poly1305_STATEBYTES' bytes"
  )

  binding.crypto_secretstream_xchacha20poly1305_rekey(
    state.buffer,
    state.byteOffset,
    state.byteLength
  )
}

// crypto_stream

exports.crypto_stream = function (c, n, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    n.byteLength === binding.crypto_stream_NONCEBYTES,
    "n must be 'crypto_stream_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_KEYBYTES,
    "k must be 'crypto_stream_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream(c, n, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_xor = function (c, m, n, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(
    n.byteLength === binding.crypto_stream_NONCEBYTES,
    "n must be 'crypto_stream_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_KEYBYTES,
    "k must be 'crypto_stream_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_xor(
    c.buffer,
    c.byteOffset,
    c.byteLength,

    m.buffer,
    m.byteOffset,
    m.byteLength,

    n.buffer,
    n.byteOffset,
    n.byteLength,

    k.buffer,
    k.byteOffset,
    k.byteLength
  )

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_chacha20 = function (c, n, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    n.byteLength === binding.crypto_stream_chacha20_NONCEBYTES,
    "n must be 'crypto_stream_chacha20_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_chacha20_KEYBYTES,
    "k must be 'crypto_stream_chacha20_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_chacha20(c, n, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_chacha20_xor = function (c, m, n, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(
    n.byteLength === binding.crypto_stream_chacha20_NONCEBYTES,
    "n must be 'crypto_stream_chacha20_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_chacha20_KEYBYTES,
    "k must be 'crypto_stream_chacha20_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_chacha20_xor(c, m, n, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_chacha20_xor_ic = function (c, m, n, ic, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(
    n.byteLength === binding.crypto_stream_chacha20_NONCEBYTES,
    "n must be 'crypto_stream_chacha20_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_chacha20_KEYBYTES,
    "k must be 'crypto_stream_chacha20_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_chacha20_xor_ic(c, m, n, ic, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_chacha20_ietf = function (c, n, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    n.byteLength === binding.crypto_stream_chacha20_ietf_NONCEBYTES,
    "n must be 'crypto_stream_chacha20_ietf_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_chacha20_ietf_KEYBYTES,
    "k must be 'crypto_stream_chacha20_ietf_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_chacha20_ietf(c, n, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_chacha20_ietf_xor = function (c, m, n, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(
    n.byteLength === binding.crypto_stream_chacha20_ietf_NONCEBYTES,
    "n must be 'crypto_stream_chacha20_ietf_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_chacha20_ietf_KEYBYTES,
    "k must be 'crypto_stream_chacha20_ietf_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_chacha20_ietf_xor(c, m, n, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_chacha20_ietf_xor_ic = function (c, m, n, ic, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(
    n.byteLength === binding.crypto_stream_chacha20_ietf_NONCEBYTES,
    "n must be 'crypto_stream_chacha20_ietf_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_chacha20_ietf_KEYBYTES,
    "k must be 'crypto_stream_chacha20_ietf_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_chacha20_ietf_xor_ic(c, m, n, ic, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_xchacha20 = function (c, n, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    n.byteLength === binding.crypto_stream_xchacha20_NONCEBYTES,
    "n must be 'crypto_stream_xchacha20_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_xchacha20_KEYBYTES,
    "k must be 'crypto_stream_xchacha20_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_xchacha20(c, n, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_xchacha20_xor = function (c, m, n, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(
    n.byteLength === binding.crypto_stream_xchacha20_NONCEBYTES,
    "n must be 'crypto_stream_xchacha20_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_xchacha20_KEYBYTES,
    "k must be 'crypto_stream_xchacha20_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_xchacha20_xor(c, m, n, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_xchacha20_xor_ic = function (c, m, n, ic, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(
    n.byteLength === binding.crypto_stream_xchacha20_NONCEBYTES,
    "n must be 'crypto_stream_xchacha20_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_xchacha20_KEYBYTES,
    "k must be 'crypto_stream_xchacha20_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_xchacha20_xor_ic(c, m, n, ic, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_salsa20 = function (c, n, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    n.byteLength === binding.crypto_stream_salsa20_NONCEBYTES,
    "n must be 'crypto_stream_salsa20_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_salsa20_KEYBYTES,
    "k must be 'crypto_stream_salsa20_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_salsa20(c, n, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_salsa20_xor = function (c, m, n, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(
    n.byteLength === binding.crypto_stream_salsa20_NONCEBYTES,
    "n must be 'crypto_stream_salsa20_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_salsa20_KEYBYTES,
    "k must be 'crypto_stream_salsa20_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_salsa20_xor(c, m, n, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_stream_salsa20_xor_ic = function (c, m, n, ic, k) {
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(
    n.byteLength === binding.crypto_stream_salsa20_NONCEBYTES,
    "n must be 'crypto_stream_salsa20_NONCEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_stream_salsa20_KEYBYTES,
    "k must be 'crypto_stream_salsa20_KEYBYTES' bytes"
  )

  const res = binding.crypto_stream_salsa20_xor_ic(c, m, n, ic, k)

  if (res !== 0) throw new Error('status: ' + res)
}

// crypto_auth

exports.crypto_auth = function (out, input, k) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(out.byteLength === binding.crypto_auth_BYTES, "out must be 'crypto_auth_BYTES' bytes")
  assert(k.byteLength === binding.crypto_auth_KEYBYTES, "k must be 'crypto_auth_KEYBYTES' bytes")

  const res = binding.crypto_auth(out, input, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_auth_verify = function (h, input, k) {
  assert(ArrayBuffer.isView(h), 'h must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(h.byteLength === binding.crypto_auth_BYTES, "h must be 'crypto_auth_BYTES' bytes")
  assert(k.byteLength === binding.crypto_auth_KEYBYTES, "k must be 'crypto_auth_KEYBYTES' bytes")

  return binding.crypto_auth_verify(h, input, k)
}

// crypto_onetimeauth

exports.crypto_onetimeauth = function (out, input, k) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    out.byteLength === binding.crypto_onetimeauth_BYTES,
    "out must be 'crypto_onetimeauth_BYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_onetimeauth_KEYBYTES,
    "k must be 'crypto_onetimeauth_KEYBYTES' bytes"
  )

  const res = binding.crypto_onetimeauth(out, input, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_onetimeauth_init = function (state, k) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    state.byteLength === binding.crypto_onetimeauth_STATEBYTES,
    "state must be 'crypto_onetimeauth_STATEBYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_onetimeauth_KEYBYTES,
    "k must be 'crypto_onetimeauth_KEYBYTES' bytes"
  )

  const res = binding.crypto_onetimeauth_init(state, k)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_onetimeauth_update = function (state, input) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(
    state.byteLength === binding.crypto_onetimeauth_STATEBYTES,
    "state must be 'crypto_onetimeauth_STATEBYTES' bytes"
  )

  const res = binding.crypto_onetimeauth_update(state, input)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_onetimeauth_final = function (state, out) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(
    state.byteLength === binding.crypto_onetimeauth_STATEBYTES,
    "state must be 'crypto_onetimeauth_STATEBYTES' bytes"
  )
  assert(
    out.byteLength === binding.crypto_onetimeauth_BYTES,
    "out must be 'crypto_onetimeauth_BYTES' bytes"
  )

  const res = binding.crypto_onetimeauth_final(state, out)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_onetimeauth_verify = function (h, input, k) {
  assert(ArrayBuffer.isView(h), 'h must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    h.byteLength === binding.crypto_onetimeauth_BYTES,
    "h must be 'crypto_onetimeauth_BYTES' bytes"
  )
  assert(
    k.byteLength === binding.crypto_onetimeauth_KEYBYTES,
    "k must be 'crypto_onetimeauth_KEYBYTES' bytes"
  )

  return binding.crypto_onetimeauth_verify(h, input, k)
}

// crypto_pwhash

exports.crypto_pwhash = function (out, passwd, salt, opslimit, memlimit, alg) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(ArrayBuffer.isView(salt), 'salt must be a typed array')
  assert(
    out.byteLength >= binding.crypto_pwhash_BYTES_MIN,
    "out must be at least 'crypto_pwhash_BYTES_MIN' bytes"
  )
  assert(
    out.byteLength <= binding.crypto_pwhash_BYTES_MAX,
    "out must be at most 'crypto_pwhash_BYTES_MAX' bytes"
  )
  assert(
    salt.byteLength === binding.crypto_pwhash_SALTBYTES,
    "salt must be 'crypto_pwhash_SALTBYTES' bytes"
  )
  assert(
    opslimit >= binding.crypto_pwhash_OPSLIMIT_MIN,
    "opslimit must be at least 'crypto_pwhash_OPSLIMIT_MIN'"
  )
  assert(
    opslimit <= binding.crypto_pwhash_OPSLIMIT_MAX,
    "opslimit must be at most 'crypto_pwhash_OPSLIMIT_MAX'"
  )
  assert(
    memlimit >= binding.crypto_pwhash_MEMLIMIT_MIN,
    "memlimit must be at least 'crypto_pwhash_MEMLIMIT_MIN'"
  )
  assert(
    memlimit <= binding.crypto_pwhash_MEMLIMIT_MAX,
    "memlimit must be at most 'crypto_pwhash_MEMLIMIT_MAX'"
  )
  assert(alg >= 1 && alg <= 2, 'alg must be either Argon2i 1.3 or Argon2id 1.3')

  const res = binding.crypto_pwhash(out, passwd, salt, opslimit, memlimit, alg)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_pwhash_async = function (
  out,
  passwd,
  salt,
  opslimit,
  memlimit,
  alg,
  callback = undefined
) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(ArrayBuffer.isView(salt), 'salt must be a typed array')
  assert(
    out.byteLength >= binding.crypto_pwhash_BYTES_MIN,
    "out must be at least 'crypto_pwhash_BYTES_MIN' bytes"
  )
  assert(
    out.byteLength <= binding.crypto_pwhash_BYTES_MAX,
    "out must be at most 'crypto_pwhash_BYTES_MAX' bytes"
  )
  assert(
    salt.byteLength === binding.crypto_pwhash_SALTBYTES,
    "salt must be 'crypto_pwhash_SALTBYTES' bytes"
  )
  assert(
    opslimit >= binding.crypto_pwhash_OPSLIMIT_MIN,
    "opslimit must be at least 'crypto_pwhash_OPSLIMIT_MIN'"
  )
  assert(
    opslimit <= binding.crypto_pwhash_OPSLIMIT_MAX,
    "opslimit must be at most 'crypto_pwhash_OPSLIMIT_MAX'"
  )
  assert(
    memlimit >= binding.crypto_pwhash_MEMLIMIT_MIN,
    "memlimit must be at least 'crypto_pwhash_MEMLIMIT_MIN'"
  )
  assert(
    memlimit <= binding.crypto_pwhash_MEMLIMIT_MAX,
    "memlimit must be at most 'crypto_pwhash_MEMLIMIT_MAX'"
  )
  assert(alg >= 1 && alg <= 2, 'alg must be either Argon2i 1.3 or Argon2id 1.3')

  const [done, promise] = checkStatus(callback)

  binding.crypto_pwhash_async(
    out.buffer,
    out.byteOffset,
    out.byteLength,

    passwd.buffer,
    passwd.byteOffset,
    passwd.byteLength,

    salt.buffer,
    salt.byteOffset,
    salt.byteLength,

    opslimit,
    memlimit,
    alg,

    done
  )

  return promise
}

exports.crypto_pwhash_str = function (out, passwd, opslimit, memlimit) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(
    out.byteLength === binding.crypto_pwhash_STRBYTES,
    "out must be 'crypto_pwhash_STRBYTES' bytes"
  )
  assert(typeof opslimit === 'number', 'opslimit must be a number')
  assert(
    opslimit >= binding.crypto_pwhash_OPSLIMIT_MIN,
    "opslimit must be at least 'crypto_pwhash_OPSLIMIT_MIN'"
  )
  assert(
    opslimit <= binding.crypto_pwhash_OPSLIMIT_MAX,
    "opslimit must be at most 'crypto_pwhash_OPSLIMIT_MAX'"
  )
  assert(typeof memlimit === 'number', 'memlimit must be a number')
  assert(
    memlimit >= binding.crypto_pwhash_MEMLIMIT_MIN,
    "memlimit must be at least 'crypto_pwhash_MEMLIMIT_MIN'"
  )
  assert(
    memlimit <= binding.crypto_pwhash_MEMLIMIT_MAX,
    "memlimit must be at most 'crypto_pwhash_MEMLIMIT_MAX'"
  )

  const res = binding.crypto_pwhash_str(out, passwd, opslimit, memlimit)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_pwhash_str_async = function (out, passwd, opslimit, memlimit, callback = undefined) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(
    out.byteLength === binding.crypto_pwhash_STRBYTES,
    "out must be 'crypto_pwhash_STRBYTES' bytes"
  )
  assert(passwd.byteLength > 0, 'passwd must not be empty')
  assert(typeof opslimit === 'number', 'opslimit must be a number')
  assert(
    opslimit >= binding.crypto_pwhash_OPSLIMIT_MIN,
    "opslimit must be at least 'crypto_pwhash_OPSLIMIT_MIN'"
  )
  assert(
    opslimit <= binding.crypto_pwhash_OPSLIMIT_MAX,
    "opslimit must be at most 'crypto_pwhash_OPSLIMIT_MAX'"
  )
  assert(typeof memlimit === 'number', 'memlimit must be a number')
  assert(
    memlimit >= binding.crypto_pwhash_MEMLIMIT_MIN,
    "memlimit must be at least 'crypto_pwhash_MEMLIMIT_MIN'"
  )
  assert(
    memlimit <= binding.crypto_pwhash_MEMLIMIT_MAX,
    "memlimit must be at most 'crypto_pwhash_MEMLIMIT_MAX'"
  )

  const [done, promise] = checkStatus(callback)

  binding.crypto_pwhash_str_async(
    out.buffer,
    out.byteOffset,
    out.byteLength,

    passwd.buffer,
    passwd.byteOffset,
    passwd.byteLength,

    opslimit,
    memlimit,

    done
  )

  return promise
}

exports.crypto_pwhash_str_verify = function (str, passwd) {
  assert(ArrayBuffer.isView(str), 'str must be a typed array')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(
    str.byteLength === binding.crypto_pwhash_STRBYTES,
    "str must be 'crypto_pwhash_STRBYTES' bytes"
  )

  return binding.crypto_pwhash_str_verify(str, passwd)
}

exports.crypto_pwhash_str_verify_async = function (str, passwd, callback = undefined) {
  assert(ArrayBuffer.isView(str), 'str must be a typed array')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(
    str.byteLength === binding.crypto_pwhash_STRBYTES,
    "str must be 'crypto_pwhash_STRBYTES' bytes"
  )
  assert(passwd.byteLength > 0, 'passwd must not be empty')

  const [done, promise] = checkStatus(callback, true)

  binding.crypto_pwhash_str_verify_async(
    str.buffer,
    str.byteOffset,
    str.byteLength,

    passwd.buffer,
    passwd.byteOffset,
    passwd.byteLength,

    done
  )

  return promise
}

exports.crypto_pwhash_str_needs_rehash = function (str, opslimit, memlimit) {
  assert(ArrayBuffer.isView(str), 'str must be a typed array')
  assert(
    str.byteLength === binding.crypto_pwhash_STRBYTES,
    "str must be 'crypto_pwhash_STRBYTES' bytes"
  )
  assert(
    opslimit >= binding.crypto_pwhash_OPSLIMIT_MIN,
    "opslimit must be at least 'crypto_pwhash_OPSLIMIT_MIN'"
  )
  assert(
    opslimit <= binding.crypto_pwhash_OPSLIMIT_MAX,
    "opslimit must be at most 'crypto_pwhash_OPSLIMIT_MAX'"
  )
  assert(
    memlimit >= binding.crypto_pwhash_MEMLIMIT_MIN,
    "memlimit must be at least 'crypto_pwhash_MEMLIMIT_MIN'"
  )
  assert(
    memlimit <= binding.crypto_pwhash_MEMLIMIT_MAX,
    "memlimit must be at most 'crypto_pwhash_MEMLIMIT_MAX'"
  )

  return binding.crypto_pwhash_str_needs_rehash(str, opslimit, memlimit)
}

exports.crypto_pwhash_scryptsalsa208sha256 = function (out, passwd, salt, opslimit, memlimit) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(ArrayBuffer.isView(salt), 'salt must be a typed array')
  assert(
    out.byteLength >= binding.crypto_pwhash_scryptsalsa208sha256_BYTES_MIN,
    "out must be at least 'crypto_pwhash_scryptsalsa208sha256_BYTES_MIN' bytes"
  )
  assert(
    out.byteLength <= binding.crypto_pwhash_scryptsalsa208sha256_BYTES_MAX,
    "out must be at most 'crypto_pwhash_scryptsalsa208sha256_BYTES_MAX' bytes"
  )
  assert(
    salt.byteLength === binding.crypto_pwhash_scryptsalsa208sha256_SALTBYTES,
    "salt must be 'crypto_pwhash_scryptsalsa208sha256_SALTBYTES' bytes"
  )
  assert(
    opslimit >= binding.crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MIN,
    "opslimit must be at least 'crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MIN'"
  )
  assert(
    opslimit <= binding.crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MAX,
    "opslimit must be at most 'crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MAX'"
  )
  assert(
    memlimit >= binding.crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MIN,
    "memlimit must be at least 'crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MIN'"
  )
  assert(
    memlimit <= binding.crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MAX,
    "memlimit must be at most 'crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MAX'"
  )

  const res = binding.crypto_pwhash_scryptsalsa208sha256(out, passwd, salt, opslimit, memlimit)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_pwhash_scryptsalsa208sha256_async = function (
  out,
  passwd,
  salt,
  opslimit,
  memlimit,
  callback = undefined
) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(ArrayBuffer.isView(salt), 'salt must be a typed array')
  assert(
    out.byteLength >= binding.crypto_pwhash_scryptsalsa208sha256_BYTES_MIN,
    "out must be at least 'crypto_pwhash_scryptsalsa208sha256_BYTES_MIN' bytes"
  )
  assert(
    out.byteLength <= binding.crypto_pwhash_scryptsalsa208sha256_BYTES_MAX,
    "out must be at most 'crypto_pwhash_scryptsalsa208sha256_BYTES_MAX' bytes"
  )
  assert(passwd.byteLength > 0, 'passwd must not be empty')
  assert(
    salt.byteLength === binding.crypto_pwhash_scryptsalsa208sha256_SALTBYTES,
    "salt must be 'crypto_pwhash_scryptsalsa208sha256_SALTBYTES' bytes"
  )
  assert(
    opslimit >= binding.crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MIN,
    "opslimit must be at least 'crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MIN'"
  )
  assert(
    opslimit <= binding.crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MAX,
    "opslimit must be at most 'crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MAX'"
  )
  assert(
    memlimit >= binding.crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MIN,
    "memlimit must be at least 'crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MIN'"
  )
  assert(
    memlimit <= binding.crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MAX,
    "memlimit must be at most 'crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MAX'"
  )

  const [done, promise] = checkStatus(callback)

  binding.crypto_pwhash_scryptsalsa208sha256_async(
    out.buffer,
    out.byteOffset,
    out.byteLength,

    passwd.buffer,
    passwd.byteOffset,
    passwd.byteLength,

    salt.buffer,
    salt.byteOffset,
    salt.byteLength,

    opslimit,
    memlimit,

    done
  )

  return promise
}

exports.crypto_pwhash_scryptsalsa208sha256_str_async = function (
  out,
  passwd,
  opslimit,
  memlimit,
  callback = undefined
) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(
    out.byteLength === binding.crypto_pwhash_scryptsalsa208sha256_STRBYTES,
    "out must be 'crypto_pwhash_scryptsalsa208sha256_STRBYTES' bytes"
  )
  assert(passwd.byteLength > 0, 'passwd must not be empty')
  assert(
    opslimit >= binding.crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MIN,
    "opslimit must be at least 'crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MIN'"
  )
  assert(
    opslimit <= binding.crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MAX,
    "opslimit must be at most 'crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MAX'"
  )
  assert(
    memlimit >= binding.crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MIN,
    "memlimit must be at least 'crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MIN'"
  )
  assert(
    memlimit <= binding.crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MAX,
    "memlimit must be at most 'crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MAX'"
  )

  const [done, promise] = checkStatus(callback)

  binding.crypto_pwhash_scryptsalsa208sha256_str_async(
    out.buffer,
    out.byteOffset,
    out.byteLength,

    passwd.buffer,
    passwd.byteOffset,
    passwd.byteLength,

    opslimit,
    memlimit,

    done
  )

  return promise
}

exports.crypto_pwhash_scryptsalsa208sha256_str = function (out, passwd, opslimit, memlimit) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(
    out.byteLength === binding.crypto_pwhash_scryptsalsa208sha256_STRBYTES,
    "out must be 'crypto_pwhash_scryptsalsa208sha256_STRBYTES' bytes"
  )
  assert(passwd.byteLength > 0, 'passwd must not be empty')
  assert(
    opslimit >= binding.crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MIN,
    "opslimit must be at least 'crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MIN'"
  )
  assert(
    opslimit <= binding.crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MAX,
    "opslimit must be at most 'crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MAX'"
  )
  assert(
    memlimit >= binding.crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MIN,
    "memlimit must be at least 'crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MIN'"
  )
  assert(
    memlimit <= binding.crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MAX,
    "memlimit must be at most 'crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MAX'"
  )

  const res = binding.crypto_pwhash_scryptsalsa208sha256_str(out, passwd, opslimit, memlimit)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_pwhash_scryptsalsa208sha256_str_verify_async = function (
  str,
  passwd,
  callback = undefined
) {
  assert(ArrayBuffer.isView(str), 'str must be a typed array')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(
    str.byteLength === binding.crypto_pwhash_scryptsalsa208sha256_STRBYTES,
    "str must be 'crypto_pwhash_scryptsalsa208sha256_STRBYTES' bytes"
  )
  assert(passwd.byteLength > 0, 'passwd must not be empty')

  const [done, promise] = checkStatus(callback, true)

  binding.crypto_pwhash_scryptsalsa208sha256_str_verify_async(
    str.buffer,
    str.byteOffset,
    str.byteLength,

    passwd.buffer,
    passwd.byteOffset,
    passwd.byteLength,

    done
  )

  return promise
}

exports.crypto_pwhash_scryptsalsa208sha256_str_verify = function (str, passwd) {
  assert(ArrayBuffer.isView(str), 'str must be a typed array')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(
    str.byteLength === binding.crypto_pwhash_scryptsalsa208sha256_STRBYTES,
    "str must be 'crypto_pwhash_scryptsalsa208sha256_STRBYTES' bytes"
  )
  assert(passwd.byteLength > 0, 'passwd must not be empty')

  return binding.crypto_pwhash_scryptsalsa208sha256_str_verify(str, passwd)
}

exports.crypto_pwhash_scryptsalsa208sha256_str_needs_rehash = function (str, opslimit, memlimit) {
  assert(ArrayBuffer.isView(str), 'str must be a typed array')
  assert(
    str.byteLength === binding.crypto_pwhash_scryptsalsa208sha256_STRBYTES,
    "str must be 'crypto_pwhash_scryptsalsa208sha256_STRBYTES' bytes"
  )
  assert(
    opslimit >= binding.crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MIN,
    "opslimit must be at least 'crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MIN'"
  )
  assert(
    opslimit <= binding.crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MAX,
    "opslimit must be at most 'crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MAX'"
  )
  assert(
    memlimit >= binding.crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MIN,
    "memlimit must be at least 'crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MIN'"
  )
  assert(
    memlimit <= binding.crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MAX,
    "memlimit must be at most 'crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MAX'"
  )

  return binding.crypto_pwhash_scryptsalsa208sha256_str_needs_rehash(str, opslimit, memlimit)
}

// crypto_kx

exports.crypto_kx_keypair = function (pk, sk) {
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(
    pk.byteLength === binding.crypto_kx_PUBLICKEYBYTES,
    "pk must be 'crypto_kx_PUBLICKEYBYTES' bytes"
  )
  assert(
    sk.byteLength === binding.crypto_kx_SECRETKEYBYTES,
    "sk must be 'crypto_kx_SECRETKEYBYTES' bytes"
  )

  const res = binding.crypto_kx_keypair(pk, sk)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_kx_seed_keypair = function (pk, sk, seed) {
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(ArrayBuffer.isView(seed), 'seed must be a typed array')
  assert(
    pk.byteLength === binding.crypto_kx_PUBLICKEYBYTES,
    "pk must be 'crypto_kx_PUBLICKEYBYTES' bytes"
  )
  assert(
    sk.byteLength === binding.crypto_kx_SECRETKEYBYTES,
    "sk must be 'crypto_kx_SECRETKEYBYTES' bytes"
  )
  assert(
    seed.byteLength === binding.crypto_kx_SEEDBYTES,
    "seed must be 'crypto_kx_SEEDBYTES' bytes"
  )

  const res = binding.crypto_kx_seed_keypair(pk, sk, seed)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_kx_client_session_keys = function (rx, tx, clientPk, clientSk, serverPk) {
  if (!rx) rx = undefined
  if (!tx) tx = undefined

  assert(rx || tx, 'at least one session key must be specified')

  if (rx) {
    assert(ArrayBuffer.isView(rx), 'rx must be a typed array')
    assert(
      rx.byteLength === binding.crypto_kx_SESSIONKEYBYTES,
      "rx must be 'crypto_kx_SESSIONKEYBYTES' bytes"
    )
  }

  if (tx) {
    assert(ArrayBuffer.isView(tx), 'tx must be a typed array')
    assert(
      tx.byteLength === binding.crypto_kx_SESSIONKEYBYTES,
      "tx must be 'crypto_kx_SESSIONKEYBYTES' bytes"
    )
  }

  assert(ArrayBuffer.isView(clientPk), 'clientPk must be a typed array')
  assert(ArrayBuffer.isView(clientSk), 'clientSk must be a typed array')
  assert(ArrayBuffer.isView(serverPk), 'serverPk must be a typed array')
  assert(
    clientPk.byteLength === binding.crypto_kx_PUBLICKEYBYTES,
    "clientPk must be 'crypto_kx_PUBLICKEYBYTES' bytes"
  )
  assert(
    clientSk.byteLength === binding.crypto_kx_SECRETKEYBYTES,
    "clientSk must be 'crypto_kx_SECRETKEYBYTES' bytes"
  )
  assert(
    serverPk.byteLength === binding.crypto_kx_PUBLICKEYBYTES,
    "serverPk must be 'crypto_kx_PUBLICKEYBYTES' bytes"
  )

  const res = binding.crypto_kx_client_session_keys(rx, tx, clientPk, clientSk, serverPk)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_kx_server_session_keys = function (rx, tx, serverPk, serverSk, clientPk) {
  if (!rx) rx = undefined
  if (!tx) tx = undefined

  assert(rx || tx, 'at least one session key must be specified')

  if (rx) {
    assert(ArrayBuffer.isView(rx), 'rx must be a typed array')
    assert(
      rx.byteLength === binding.crypto_kx_SESSIONKEYBYTES,
      "rx must be 'crypto_kx_SESSIONKEYBYTES' bytes"
    )
  }

  if (tx) {
    assert(ArrayBuffer.isView(tx), 'tx must be a typed array')
    assert(
      tx.byteLength === binding.crypto_kx_SESSIONKEYBYTES,
      "tx must be 'crypto_kx_SESSIONKEYBYTES' bytes"
    )
  }

  assert(ArrayBuffer.isView(serverPk), 'serverPk must be a typed array')
  assert(ArrayBuffer.isView(serverSk), 'serverSk must be a typed array')
  assert(ArrayBuffer.isView(clientPk), 'clientPk must be a typed array')
  assert(
    serverPk.byteLength === binding.crypto_kx_PUBLICKEYBYTES,
    "serverPk must be 'crypto_kx_PUBLICKEYBYTES' bytes"
  )
  assert(
    serverSk.byteLength === binding.crypto_kx_SECRETKEYBYTES,
    "serverSk must be 'crypto_kx_SECRETKEYBYTES' bytes"
  )
  assert(
    clientPk.byteLength === binding.crypto_kx_PUBLICKEYBYTES,
    "clientPk must be 'crypto_kx_PUBLICKEYBYTES' bytes"
  )

  const res = binding.crypto_kx_server_session_keys(rx, tx, serverPk, serverSk, clientPk)

  if (res !== 0) throw new Error('status: ' + res)
}

// crypto_scalarmult

exports.crypto_scalarmult_base = function (q, n) {
  assert(ArrayBuffer.isView(q), 'q must be a typed array')
  assert(
    q.byteLength === binding.crypto_scalarmult_BYTES,
    "q must be 'crypto_scalarmult_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.crypto_scalarmult_SCALARBYTES,
    "n must be 'crypto_scalarmult_SCALARBYTES' bytes"
  )

  const res = binding.crypto_scalarmult_base(q, n)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_scalarmult = function (q, n, p) {
  assert(ArrayBuffer.isView(q), 'q must be a typed array')
  assert(
    q.byteLength === binding.crypto_scalarmult_BYTES,
    "q must be 'crypto_scalarmult_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.crypto_scalarmult_SCALARBYTES,
    "n must be 'crypto_scalarmult_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(p), 'p must be a typed array')
  assert(
    p.byteLength === binding.crypto_scalarmult_BYTES,
    "p must be 'crypto_scalarmult_BYTES' bytes"
  )

  const res = binding.crypto_scalarmult(q, n, p)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_scalarmult_ed25519_base = function (q, n) {
  assert(ArrayBuffer.isView(q), 'q must be a typed array')
  assert(
    q.byteLength === binding.crypto_scalarmult_ed25519_BYTES,
    "q must be 'crypto_scalarmult_ed25519_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.crypto_scalarmult_ed25519_SCALARBYTES,
    "n must be 'crypto_scalarmult_ed25519_SCALARBYTES' bytes"
  )

  const res = binding.crypto_scalarmult_ed25519_base(q, n)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_scalarmult_ed25519 = function (q, n, p) {
  assert(ArrayBuffer.isView(q), 'q must be a typed array')
  assert(
    q.byteLength === binding.crypto_scalarmult_ed25519_BYTES,
    "q must be 'crypto_scalarmult_ed25519_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.crypto_scalarmult_ed25519_SCALARBYTES,
    "n must be 'crypto_scalarmult_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(p), 'p must be a typed array')
  assert(
    p.byteLength === binding.crypto_scalarmult_ed25519_BYTES,
    "p must be 'crypto_scalarmult_ed25519_BYTES' bytes"
  )

  const res = binding.crypto_scalarmult_ed25519(q, n, p)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_core_ed25519_is_valid_point = function (p) {
  assert(ArrayBuffer.isView(p), 'p must be a typed array')
  assert(
    p.byteLength === binding.crypto_core_ed25519_BYTES,
    "p must be 'crypto_core_ed25519_BYTES' bytes"
  )

  return binding.crypto_core_ed25519_is_valid_point(p)
}

exports.crypto_core_ed25519_from_uniform = function (p, r) {
  assert(ArrayBuffer.isView(p), 'p must be a typed array')
  assert(
    p.byteLength === binding.crypto_core_ed25519_BYTES,
    "p must be 'crypto_core_ed25519_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(r), 'r must be a typed array')
  assert(
    r.byteLength === binding.crypto_core_ed25519_UNIFORMBYTES,
    "r must be 'crypto_core_ed25519_UNIFORMBYTES' bytes"
  )

  const res = binding.crypto_core_ed25519_from_uniform(p, r)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_scalarmult_ed25519_base_noclamp = function (q, n) {
  assert(ArrayBuffer.isView(q), 'q must be a typed array')
  assert(
    q.byteLength === binding.crypto_scalarmult_ed25519_BYTES,
    "q must be 'crypto_scalarmult_ed25519_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.crypto_scalarmult_ed25519_SCALARBYTES,
    "n must be 'crypto_scalarmult_ed25519_SCALARBYTES' bytes"
  )

  const res = binding.crypto_scalarmult_ed25519_base_noclamp(q, n)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_scalarmult_ed25519_noclamp = function (q, n, p) {
  assert(ArrayBuffer.isView(q), 'q must be a typed array')
  assert(
    q.byteLength === binding.crypto_scalarmult_ed25519_BYTES,
    "q must be 'crypto_scalarmult_ed25519_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.crypto_scalarmult_ed25519_SCALARBYTES,
    "n must be 'crypto_scalarmult_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(p), 'p must be a typed array')
  assert(
    p.byteLength === binding.crypto_scalarmult_ed25519_BYTES,
    "p must be 'crypto_scalarmult_ed25519_BYTES' bytes"
  )

  const res = binding.crypto_scalarmult_ed25519_noclamp(q, n, p)

  if (res !== 0) throw new Error('status: ' + res)
}

// crypto_core

exports.crypto_core_ed25519_add = function (r, p, q) {
  assert(ArrayBuffer.isView(r), 'r must be a typed array')
  assert(
    r.byteLength === binding.crypto_core_ed25519_BYTES,
    "r must be 'crypto_core_ed25519_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(p), 'p must be a typed array')
  assert(
    p.byteLength === binding.crypto_core_ed25519_BYTES,
    "p must be 'crypto_core_ed25519_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(q), 'q must be a typed array')
  assert(
    q.byteLength === binding.crypto_core_ed25519_BYTES,
    "q must be 'crypto_core_ed25519_BYTES' bytes"
  )

  const res = binding.crypto_core_ed25519_add(r, p, q)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_core_ed25519_sub = function (r, p, q) {
  assert(ArrayBuffer.isView(r), 'r must be a typed array')
  assert(
    r.byteLength === binding.crypto_core_ed25519_BYTES,
    "r must be 'crypto_core_ed25519_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(p), 'p must be a typed array')
  assert(
    p.byteLength === binding.crypto_core_ed25519_BYTES,
    "p must be 'crypto_core_ed25519_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(q), 'q must be a typed array')
  assert(
    q.byteLength === binding.crypto_core_ed25519_BYTES,
    "q must be 'crypto_core_ed25519_BYTES' bytes"
  )

  const res = binding.crypto_core_ed25519_sub(r, p, q)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_core_ed25519_scalar_random = function (r) {
  assert(ArrayBuffer.isView(r), 'r must be a typed array')
  assert(
    r.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "r must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )

  binding.crypto_core_ed25519_scalar_random(r)
}

exports.crypto_core_ed25519_scalar_reduce = function (r, s) {
  assert(ArrayBuffer.isView(r), 'r must be a typed array')
  assert(
    r.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "r must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(s), 's must be a typed array')
  assert(
    s.byteLength === binding.crypto_core_ed25519_NONREDUCEDSCALARBYTES,
    "s must be 'crypto_core_ed25519_NONREDUCEDSCALARBYTES' bytes"
  )

  binding.crypto_core_ed25519_scalar_reduce(r, s)
}

exports.crypto_core_ed25519_scalar_invert = function (recip, s) {
  assert(ArrayBuffer.isView(recip), 'recip must be a typed array')
  assert(
    recip.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "recip must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(s), 's must be a typed array')
  assert(
    s.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "s must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )

  binding.crypto_core_ed25519_scalar_invert(recip, s)
}

exports.crypto_core_ed25519_scalar_negate = function (neg, s) {
  assert(ArrayBuffer.isView(neg), 'neg must be a typed array')
  assert(
    neg.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "neg must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(s), 's must be a typed array')
  assert(
    s.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "s must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )

  binding.crypto_core_ed25519_scalar_negate(neg, s)
}

exports.crypto_core_ed25519_scalar_complement = function (comp, s) {
  assert(ArrayBuffer.isView(comp), 'comp must be a typed array')
  assert(
    comp.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "comp must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(s), 's must be a typed array')
  assert(
    s.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "s must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )

  binding.crypto_core_ed25519_scalar_complement(comp, s)
}

exports.crypto_core_ed25519_scalar_add = function (z, x, y) {
  assert(ArrayBuffer.isView(z), 'z must be a typed array')
  assert(
    z.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "z must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(x), 'x must be a typed array')
  assert(
    x.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "x must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(y), 'y must be a typed array')
  assert(
    y.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "y must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )

  binding.crypto_core_ed25519_scalar_add(z, x, y)
}

exports.crypto_core_ed25519_scalar_sub = function (z, x, y) {
  assert(ArrayBuffer.isView(z), 'z must be a typed array')
  assert(
    z.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "z must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(x), 'x must be a typed array')
  assert(
    x.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "x must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(y), 'y must be a typed array')
  assert(
    y.byteLength === binding.crypto_core_ed25519_SCALARBYTES,
    "y must be 'crypto_core_ed25519_SCALARBYTES' bytes"
  )

  binding.crypto_core_ed25519_scalar_sub(z, x, y)
}

// crypto_shorthash

exports.crypto_shorthash = function (out, input, k) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(
    out.byteLength === binding.crypto_shorthash_BYTES,
    "out must be 'crypto_shorthash_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_shorthash_KEYBYTES,
    "k must be 'crypto_shorthash_KEYBYTES' bytes"
  )

  const res = binding.crypto_shorthash(out, input, k)

  if (res !== 0) throw new Error('status: ' + res)
}

// crypto_kdf

exports.crypto_kdf_keygen = function (key) {
  assert(ArrayBuffer.isView(key), 'key must be a typed array')
  assert(key.byteLength === binding.crypto_kdf_KEYBYTES, "key must be 'crypto_kdf_KEYBYTES' bytes")

  binding.crypto_kdf_keygen(key)
}

exports.crypto_kdf_derive_from_key = function (subkey, subkeyId, ctx, key) {
  assert(ArrayBuffer.isView(subkey), 'subkey must be a typed array')
  assert(
    subkey.byteLength >= binding.crypto_kdf_BYTES_MIN,
    "subkey must be at least 'crypto_kdf_BYTES_MIN' bytes"
  )
  assert(
    subkey.byteLength <= binding.crypto_kdf_BYTES_MAX,
    "subkey must be at most 'crypto_kdf_BYTES_MAX' bytes"
  )
  assert(ArrayBuffer.isView(ctx), 'ctx must be a typed array')
  assert(
    ctx.byteLength === binding.crypto_kdf_CONTEXTBYTES,
    "ctx must be 'crypto_kdf_CONTEXTBYTES' bytes"
  )
  assert(ArrayBuffer.isView(key), 'key must be a typed array')
  assert(key.byteLength === binding.crypto_kdf_KEYBYTES, "key must be 'crypto_kdf_KEYBYTES' bytes")

  const res = binding.crypto_kdf_derive_from_key(subkey, subkeyId, ctx, key)

  if (res !== 0) throw new Error('status: ' + res)
}

// crypto_hash

exports.crypto_hash = function (out, input) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(out.byteLength === binding.crypto_hash_BYTES, "out must be 'crypto_hash_BYTES' bytes")

  const res = binding.crypto_hash(out, input)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_hash_sha256 = function (out, input) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(
    out.byteLength === binding.crypto_hash_sha256_BYTES,
    "out must be 'crypto_hash_sha256_BYTES' bytes"
  )

  const res = binding.crypto_hash_sha256(out, input)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_hash_sha256_init = function (state) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_hash_sha256_STATEBYTES,
    "state must be 'crypto_hash_sha256_STATEBYTES' bytes"
  )

  const res = binding.crypto_hash_sha256_init(state)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_hash_sha256_update = function (state, input) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(
    state.byteLength === binding.crypto_hash_sha256_STATEBYTES,
    "state must be 'crypto_hash_sha256_STATEBYTES' bytes"
  )

  const res = binding.crypto_hash_sha256_update(state, input)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_hash_sha256_final = function (state, out) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_hash_sha256_STATEBYTES,
    "state must be 'crypto_hash_sha256_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(
    out.byteLength === binding.crypto_hash_sha256_BYTES,
    "out must be 'crypto_hash_sha256_BYTES' bytes"
  )

  const res = binding.crypto_hash_sha256_final(state, out)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_hash_sha512 = function (out, input) {
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(
    out.byteLength === binding.crypto_hash_sha512_BYTES,
    "out must be 'crypto_hash_sha512_BYTES' bytes"
  )

  const res = binding.crypto_hash_sha512(out, input)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_hash_sha512_init = function (state) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_hash_sha512_STATEBYTES,
    "state must be 'crypto_hash_sha512_STATEBYTES' bytes"
  )

  const res = binding.crypto_hash_sha512_init(state)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_hash_sha512_update = function (state, input) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(ArrayBuffer.isView(input), 'input must be a typed array')
  assert(
    state.byteLength === binding.crypto_hash_sha512_STATEBYTES,
    "state must be 'crypto_hash_sha512_STATEBYTES' bytes"
  )

  const res = binding.crypto_hash_sha512_update(state, input)

  if (res !== 0) throw new Error('status: ' + res)
}

exports.crypto_hash_sha512_final = function (state, out) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_hash_sha512_STATEBYTES,
    "state must be 'crypto_hash_sha512_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(
    out.byteLength === binding.crypto_hash_sha512_BYTES,
    "out must be 'crypto_hash_sha512_BYTES' bytes"
  )

  const res = binding.crypto_hash_sha512_final(state, out)

  if (res !== 0) throw new Error('status: ' + res)
}

// crypto_aead

exports.crypto_aead_xchacha20poly1305_ietf_keygen = function (k) {
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_aead_xchacha20poly1305_ietf_KEYBYTES,
    "k must be 'crypto_aead_xchacha20poly1305_ietf_KEYBYTES' bytes"
  )

  binding.crypto_aead_xchacha20poly1305_ietf_keygen(k)
}

exports.crypto_aead_xchacha20poly1305_ietf_encrypt = function (c, m, ad, nsec, npub, k) {
  if (!ad) ad = undefined

  assert(nsec === null, 'nsec must always be set to null')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(
    c.byteLength === m.byteLength + binding.crypto_aead_xchacha20poly1305_ietf_ABYTES,
    "c must be 'm.byteLength + crypto_aead_xchacha20poly1305_ietf_ABYTES' bytes"
  )
  assert(c.byteLength <= 0xffffffff, 'c.byteLength must be a 32bit integer')
  assert(ArrayBuffer.isView(npub), 'npub must be a typed array')
  assert(
    npub.byteLength === binding.crypto_aead_xchacha20poly1305_ietf_NPUBBYTES,
    "npub must be 'crypto_aead_xchacha20poly1305_ietf_NPUBBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_aead_xchacha20poly1305_ietf_KEYBYTES,
    "k must be 'crypto_aead_xchacha20poly1305_ietf_KEYBYTES' bytes"
  )

  const res = binding.crypto_aead_xchacha20poly1305_ietf_encrypt(c, m, ad, npub, k)

  if (res < 0) throw new Error('could not encrypt data')

  return res
}

exports.crypto_aead_xchacha20poly1305_ietf_decrypt = function (m, nsec, c, ad, npub, k) {
  if (!ad) ad = undefined

  assert(nsec === null, 'nsec must always be set to null')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(
    m.byteLength === c.byteLength - binding.crypto_aead_xchacha20poly1305_ietf_ABYTES,
    "m must be 'c.byteLength - crypto_aead_xchacha20poly1305_ietf_ABYTES' bytes"
  )
  assert(m.byteLength <= 0xffffffff, 'm.byteLength must be a 32bit integer')
  assert(ArrayBuffer.isView(npub), 'npub must be a typed array')
  assert(
    npub.byteLength === binding.crypto_aead_xchacha20poly1305_ietf_NPUBBYTES,
    "npub must be 'crypto_aead_xchacha20poly1305_ietf_NPUBBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_aead_xchacha20poly1305_ietf_KEYBYTES,
    "k must be 'crypto_aead_xchacha20poly1305_ietf_KEYBYTES' bytes"
  )

  const res = binding.crypto_aead_xchacha20poly1305_ietf_decrypt(m, c, ad, npub, k)

  if (res < 0) throw new Error('could not verify data')

  return res
}

exports.crypto_aead_xchacha20poly1305_ietf_encrypt_detached = function (
  c,
  mac,
  m,
  ad,
  nsec,
  npub,
  k
) {
  if (!ad) ad = undefined

  assert(nsec === null, 'nsec must always be set to null')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(ArrayBuffer.isView(mac), 'mac must be a typed array')
  assert(
    mac.byteLength === binding.crypto_aead_xchacha20poly1305_ietf_ABYTES,
    "mac must be 'crypto_aead_xchacha20poly1305_ietf_ABYTES' bytes"
  )
  assert(ArrayBuffer.isView(npub), 'npub must be a typed array')
  assert(
    npub.byteLength === binding.crypto_aead_xchacha20poly1305_ietf_NPUBBYTES,
    "npub must be 'crypto_aead_xchacha20poly1305_ietf_NPUBBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_aead_xchacha20poly1305_ietf_KEYBYTES,
    "k must be 'crypto_aead_xchacha20poly1305_ietf_KEYBYTES' bytes"
  )

  const res = binding.crypto_aead_xchacha20poly1305_ietf_encrypt_detached(c, mac, m, ad, npub, k)

  if (res < 0) throw new Error('could not encrypt data')

  return res
}

exports.crypto_aead_xchacha20poly1305_ietf_decrypt_detached = function (
  m,
  nsec,
  c,
  mac,
  ad,
  npub,
  k
) {
  if (!ad) ad = undefined

  assert(nsec === null, 'nsec must always be set to null')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(m.byteLength === c.byteLength, "m must be 'c.byteLength' bytes")
  assert(ArrayBuffer.isView(mac), 'mac must be a typed array')
  assert(
    mac.byteLength === binding.crypto_aead_xchacha20poly1305_ietf_ABYTES,
    "mac must be 'crypto_aead_xchacha20poly1305_ietf_ABYTES' bytes"
  )
  assert(ArrayBuffer.isView(npub), 'npub must be a typed array')
  assert(
    npub.byteLength === binding.crypto_aead_xchacha20poly1305_ietf_NPUBBYTES,
    "npub must be 'crypto_aead_xchacha20poly1305_ietf_NPUBBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_aead_xchacha20poly1305_ietf_KEYBYTES,
    "k must be 'crypto_aead_xchacha20poly1305_ietf_KEYBYTES' bytes"
  )

  const res = binding.crypto_aead_xchacha20poly1305_ietf_decrypt_detached(m, c, mac, ad, npub, k)

  if (res !== 0) throw new Error('could not verify data')
}

exports.crypto_aead_chacha20poly1305_ietf_keygen = function (k) {
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_aead_chacha20poly1305_ietf_KEYBYTES,
    "k must be 'crypto_aead_chacha20poly1305_ietf_KEYBYTES' bytes"
  )

  binding.crypto_aead_chacha20poly1305_ietf_keygen(k)
}

exports.crypto_aead_chacha20poly1305_ietf_encrypt = function (c, m, ad, nsec, npub, k) {
  if (!ad) ad = undefined

  assert(nsec === null, 'nsec must always be set to null')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(
    c.byteLength === m.byteLength + binding.crypto_aead_chacha20poly1305_ietf_ABYTES,
    "c must be 'm.byteLength + crypto_aead_chacha20poly1305_ietf_ABYTES' bytes"
  )
  assert(c.byteLength <= 0xffffffff, 'c.byteLength must be a 32bit integer')
  assert(ArrayBuffer.isView(npub), 'npub must be a typed array')
  assert(
    npub.byteLength === binding.crypto_aead_chacha20poly1305_ietf_NPUBBYTES,
    "npub must be 'crypto_aead_chacha20poly1305_ietf_NPUBBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_aead_chacha20poly1305_ietf_KEYBYTES,
    "k must be 'crypto_aead_chacha20poly1305_ietf_KEYBYTES' bytes"
  )

  const res = binding.crypto_aead_chacha20poly1305_ietf_encrypt(c, m, ad, npub, k)

  if (res < 0) throw new Error('could not encrypt data')

  return res
}

exports.crypto_aead_chacha20poly1305_ietf_decrypt = function (m, nsec, c, ad, npub, k) {
  if (!ad) ad = undefined

  assert(nsec === null, 'nsec must always be set to null')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(
    m.byteLength === c.byteLength - binding.crypto_aead_chacha20poly1305_ietf_ABYTES,
    "m must be 'c.byteLength - crypto_aead_chacha20poly1305_ietf_ABYTES' bytes"
  )
  assert(m.byteLength <= 0xffffffff, 'm.byteLength must be a 32bit integer')
  assert(ArrayBuffer.isView(npub), 'npub must be a typed array')
  assert(
    npub.byteLength === binding.crypto_aead_chacha20poly1305_ietf_NPUBBYTES,
    "npub must be 'crypto_aead_chacha20poly1305_ietf_NPUBBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_aead_chacha20poly1305_ietf_KEYBYTES,
    "k must be 'crypto_aead_chacha20poly1305_ietf_KEYBYTES' bytes"
  )

  const res = binding.crypto_aead_chacha20poly1305_ietf_decrypt(m, c, ad, npub, k)

  if (res < 0) throw new Error('could not verify data')

  return res
}

exports.crypto_aead_chacha20poly1305_ietf_encrypt_detached = function (
  c,
  mac,
  m,
  ad,
  nsec,
  npub,
  k
) {
  if (!ad) ad = undefined

  assert(nsec === null, 'nsec must always be set to null')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")
  assert(ArrayBuffer.isView(mac), 'mac must be a typed array')
  assert(
    mac.byteLength === binding.crypto_aead_chacha20poly1305_ietf_ABYTES,
    "mac must be 'crypto_aead_chacha20poly1305_ietf_ABYTES' bytes"
  )
  assert(ArrayBuffer.isView(npub), 'npub must be a typed array')
  assert(
    npub.byteLength === binding.crypto_aead_chacha20poly1305_ietf_NPUBBYTES,
    "npub must be 'crypto_aead_chacha20poly1305_ietf_NPUBBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_aead_chacha20poly1305_ietf_KEYBYTES,
    "k must be 'crypto_aead_chacha20poly1305_ietf_KEYBYTES' bytes"
  )

  const res = binding.crypto_aead_chacha20poly1305_ietf_encrypt_detached(c, mac, m, ad, npub, k)

  if (res < 0) throw new Error('could not encrypt data')

  return res
}

exports.crypto_aead_chacha20poly1305_ietf_decrypt_detached = function (
  m,
  nsec,
  c,
  mac,
  ad,
  npub,
  k
) {
  if (!ad) ad = undefined

  assert(nsec === null, 'nsec must always be set to null')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(m.byteLength === c.byteLength, "m must be 'c.byteLength' bytes")
  assert(ArrayBuffer.isView(mac), 'mac must be a typed array')
  assert(
    mac.byteLength === binding.crypto_aead_chacha20poly1305_ietf_ABYTES,
    "mac must be 'crypto_aead_chacha20poly1305_ietf_ABYTES' bytes"
  )
  assert(ArrayBuffer.isView(npub), 'npub must be a typed array')
  assert(
    npub.byteLength === binding.crypto_aead_chacha20poly1305_ietf_NPUBBYTES,
    "npub must be 'crypto_aead_chacha20poly1305_ietf_NPUBBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_aead_chacha20poly1305_ietf_KEYBYTES,
    "k must be 'crypto_aead_chacha20poly1305_ietf_KEYBYTES' bytes"
  )

  const res = binding.crypto_aead_chacha20poly1305_ietf_decrypt_detached(m, c, mac, ad, npub, k)

  if (res !== 0) throw new Error('could not verify data')
}

// crypto_stream

exports.crypto_stream_xor_wrap_init = function (state, n, k) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.sn_crypto_stream_xor_STATEBYTES,
    "state must be 'sn_crypto_stream_xor_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.crypto_stream_NONCEBYTES,
    "n must be 'crypto_stream_NONCEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_stream_KEYBYTES,
    "k must be 'crypto_stream_KEYBYTES' bytes"
  )

  binding.crypto_stream_xor_wrap_init(state, n, k)
}

exports.crypto_stream_xor_wrap_update = function (state, c, m) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.sn_crypto_stream_xor_STATEBYTES,
    "state must be 'sn_crypto_stream_xor_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")

  binding.crypto_stream_xor_wrap_update(state, c, m)
}

exports.crypto_stream_xor_wrap_final = function (state) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.sn_crypto_stream_xor_STATEBYTES,
    "state must be 'sn_crypto_stream_xor_STATEBYTES' bytes"
  )

  binding.crypto_stream_xor_wrap_final(state)
}

exports.crypto_stream_chacha20_xor_wrap_init = function (state, n, k) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_stream_chacha20_xor_STATEBYTES,
    "state must be 'crypto_stream_chacha20_xor_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.crypto_stream_chacha20_NONCEBYTES,
    "n must be 'crypto_stream_chacha20_NONCEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_stream_chacha20_KEYBYTES,
    "k must be 'crypto_stream_chacha20_KEYBYTES' bytes"
  )

  binding.crypto_stream_chacha20_xor_wrap_init(state, n, k)
}

exports.crypto_stream_chacha20_xor_wrap_update = function (state, c, m) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_stream_chacha20_xor_STATEBYTES,
    "state must be 'crypto_stream_chacha20_xor_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")

  binding.crypto_stream_chacha20_xor_wrap_update(state, c, m)
}

exports.crypto_stream_chacha20_xor_wrap_final = function (state) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_stream_chacha20_xor_STATEBYTES,
    "state must be 'crypto_stream_chacha20_xor_STATEBYTES' bytes"
  )

  binding.crypto_stream_chacha20_xor_wrap_final(state)
}

exports.crypto_stream_chacha20_ietf_xor_wrap_init = function (state, n, k) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_stream_chacha20_ietf_xor_STATEBYTES,
    "state must be 'crypto_stream_chacha20_ietf_xor_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.crypto_stream_chacha20_ietf_NONCEBYTES,
    "n must be 'crypto_stream_chacha20_ietf_NONCEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_stream_chacha20_ietf_KEYBYTES,
    "k must be 'crypto_stream_chacha20_ietf_KEYBYTES' bytes"
  )

  binding.crypto_stream_chacha20_ietf_xor_wrap_init(state, n, k)
}

exports.crypto_stream_chacha20_ietf_xor_wrap_update = function (state, c, m) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_stream_chacha20_ietf_xor_STATEBYTES,
    "state must be 'crypto_stream_chacha20_ietf_xor_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")

  binding.crypto_stream_chacha20_ietf_xor_wrap_update(state, c, m)
}

exports.crypto_stream_chacha20_ietf_xor_wrap_final = function (state) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_stream_chacha20_ietf_xor_STATEBYTES,
    "state must be 'crypto_stream_chacha20_ietf_xor_STATEBYTES' bytes"
  )

  binding.crypto_stream_chacha20_ietf_xor_wrap_final(state)
}

exports.crypto_stream_xchacha20_xor_wrap_init = function (state, n, k) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_stream_xchacha20_xor_STATEBYTES,
    "state must be 'crypto_stream_xchacha20_xor_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.crypto_stream_xchacha20_NONCEBYTES,
    "n must be 'crypto_stream_xchacha20_NONCEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_stream_xchacha20_KEYBYTES,
    "k must be 'crypto_stream_xchacha20_KEYBYTES' bytes"
  )

  binding.crypto_stream_xchacha20_xor_wrap_init(state, n, k)
}

exports.crypto_stream_xchacha20_xor_wrap_update = function (state, c, m) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_stream_xchacha20_xor_STATEBYTES,
    "state must be 'crypto_stream_xchacha20_xor_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")

  binding.crypto_stream_xchacha20_xor_wrap_update(state, c, m)
}

exports.crypto_stream_xchacha20_xor_wrap_final = function (state) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_stream_xchacha20_xor_STATEBYTES,
    "state must be 'crypto_stream_xchacha20_xor_STATEBYTES' bytes"
  )

  binding.crypto_stream_xchacha20_xor_wrap_final(state)
}

exports.crypto_stream_salsa20_xor_wrap_init = function (state, n, k) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_stream_salsa20_xor_STATEBYTES,
    "state must be 'crypto_stream_salsa20_xor_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.crypto_stream_salsa20_NONCEBYTES,
    "n must be 'crypto_stream_salsa20_NONCEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(k), 'k must be a typed array')
  assert(
    k.byteLength === binding.crypto_stream_salsa20_KEYBYTES,
    "k must be 'crypto_stream_salsa20_KEYBYTES' bytes"
  )

  binding.crypto_stream_salsa20_xor_wrap_init(state, n, k)
}

exports.crypto_stream_salsa20_xor_wrap_update = function (state, c, m) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_stream_salsa20_xor_STATEBYTES,
    "state must be 'crypto_stream_salsa20_xor_STATEBYTES' bytes"
  )
  assert(ArrayBuffer.isView(c), 'c must be a typed array')
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(c.byteLength === m.byteLength, "c must be 'm.byteLength' bytes")

  binding.crypto_stream_salsa20_xor_wrap_update(state, c, m)
}

exports.crypto_stream_salsa20_xor_wrap_final = function (state) {
  assert(ArrayBuffer.isView(state), 'state must be a typed array')
  assert(
    state.byteLength === binding.crypto_stream_salsa20_xor_STATEBYTES,
    "state must be 'crypto_stream_salsa20_xor_STATEBYTES' bytes"
  )

  binding.crypto_stream_salsa20_xor_wrap_final(state)
}

// experimental

exports.extension_tweak_ed25519_base = function (n, p, ns) {
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "n must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(p), 'p must be a typed array')
  assert(
    p.byteLength === binding.extension_tweak_ed25519_BYTES,
    "p must be 'extension_tweak_ed25519_BYTES' bytes"
  )

  binding.extension_tweak_ed25519_base(n, p, ns)
}

exports.extension_tweak_ed25519_sign_detached = function (sig, m, scalar, pk) {
  assert(ArrayBuffer.isView(sig), 'sig must be a typed array')
  assert(sig.byteLength === binding.crypto_sign_BYTES, "sig must be 'crypto_sign_BYTES' bytes")
  assert(ArrayBuffer.isView(m), 'm must be a typed array')
  assert(ArrayBuffer.isView(scalar), 'scalar must be a typed array')
  assert(
    scalar.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "scalar must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )

  if (pk) {
    assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
    assert(
      pk.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
      "pk must be 'crypto_sign_PUBLICKEYBYTES' bytes"
    )
  }

  const res = binding.extension_tweak_ed25519_sign_detached(sig, m, scalar, pk)

  if (res !== 0) throw new Error('failed to compute signature')
}

exports.extension_tweak_ed25519_sk_to_scalar = function (n, sk) {
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "n must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(sk), 'sk must be a typed array')
  assert(
    sk.byteLength === binding.crypto_sign_SECRETKEYBYTES,
    "sk must be 'crypto_sign_SECRETKEYBYTES' bytes"
  )

  binding.extension_tweak_ed25519_sk_to_scalar(n, sk)
}

exports.extension_tweak_ed25519_scalar = function (scalarOut, scalar, ns) {
  assert(ArrayBuffer.isView(scalarOut), 'scalarOut must be a typed array')
  assert(
    scalarOut.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "scalarOut must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(scalar), 'scalar must be a typed array')
  assert(
    scalar.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "scalar must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )

  binding.extension_tweak_ed25519_scalar(scalarOut, scalar, ns)
}

exports.extension_tweak_ed25519_pk = function (tpk, pk, ns) {
  assert(ArrayBuffer.isView(tpk), 'tpk must be a typed array')
  assert(
    tpk.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
    "tpk must be 'crypto_sign_PUBLICKEYBYTES' bytes"
  )
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(
    pk.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
    "pk must be 'crypto_sign_PUBLICKEYBYTES' bytes"
  )

  const res = binding.extension_tweak_ed25519_pk(tpk, pk, ns)

  if (res !== 0) throw new Error('failed to tweak public key')
}

exports.extension_tweak_ed25519_keypair = function (pk, scalarOut, scalarIn, ns) {
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(
    pk.byteLength === binding.extension_tweak_ed25519_BYTES,
    "pk must be 'extension_tweak_ed25519_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(scalarOut), 'scalarOut must be a typed array')
  assert(
    scalarOut.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "scalarOut must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(scalarIn), 'scalarIn must be a typed array')
  assert(
    scalarIn.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "scalarIn must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )

  binding.extension_tweak_ed25519_keypair(pk, scalarOut, scalarIn, ns)
}

exports.extension_tweak_ed25519_scalar_add = function (scalarOut, scalar, n) {
  assert(ArrayBuffer.isView(scalarOut), 'scalarOut must be a typed array')
  assert(
    scalarOut.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "scalarOut must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(scalar), 'scalar must be a typed array')
  assert(
    scalar.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "scalar must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(n), 'n must be a typed array')
  assert(
    n.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "n must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )

  binding.extension_tweak_ed25519_scalar_add(scalarOut, scalar, n)
}

exports.extension_tweak_ed25519_pk_add = function (tpk, pk, p) {
  assert(ArrayBuffer.isView(tpk), 'tpk must be a typed array')
  assert(
    tpk.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
    "tpk must be 'crypto_sign_PUBLICKEYBYTES' bytes"
  )
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(
    pk.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
    "pk must be 'crypto_sign_PUBLICKEYBYTES' bytes"
  )
  assert(ArrayBuffer.isView(p), 'p must be a typed array')
  assert(
    p.byteLength === binding.crypto_sign_PUBLICKEYBYTES,
    "p must be 'crypto_sign_PUBLICKEYBYTES' bytes"
  )

  const res = binding.extension_tweak_ed25519_pk_add(tpk, pk, p)

  if (res !== 0) throw new Error('failed to add tweak to public key')
}

exports.extension_tweak_ed25519_keypair_add = function (pk, scalarOut, scalarIn, tweak) {
  assert(ArrayBuffer.isView(pk), 'pk must be a typed array')
  assert(
    pk.byteLength === binding.extension_tweak_ed25519_BYTES,
    "pk must be 'extension_tweak_ed25519_BYTES' bytes"
  )
  assert(ArrayBuffer.isView(scalarOut), 'scalarOut must be a typed array')
  assert(
    scalarOut.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "scalarOut must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(scalarIn), 'scalarIn must be a typed array')
  assert(
    scalarIn.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "scalarIn must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )
  assert(ArrayBuffer.isView(tweak), 'tweak must be a typed array')
  assert(
    tweak.byteLength === binding.extension_tweak_ed25519_SCALARBYTES,
    "tweak must be 'extension_tweak_ed25519_SCALARBYTES' bytes"
  )

  const res = binding.extension_tweak_ed25519_keypair_add(pk, scalarOut, scalarIn, tweak)

  if (res !== 0) throw new Error('failed to add tweak to keypair')
}

exports.extension_pbkdf2_sha512_async = function (out, passwd, salt, iter, outlen, callback) {
  assert(
    iter >= binding.extension_pbkdf2_sha512_ITERATIONS_MIN,
    "iter must be at least 'extension_pbkdf2_sha512_ITERATIONS_MIN'"
  )
  assert(
    outlen <= binding.extension_pbkdf2_sha512_BYTES_MAX,
    "outlen must be at most 'extension_pbkdf2_sha512_BYTES_MAX'"
  )
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(out.byteLength >= outlen, "out must be at least 'outlen' bytes")
  assert(out.byteLength > 0, 'out must not be empty')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(passwd.byteLength > 0, 'passwd must not be empty')
  assert(ArrayBuffer.isView(salt), 'salt must be a typed array')
  assert(salt.byteLength > 0, 'salt must not be empty')

  const [done, promise] = checkStatus(callback)

  binding.extension_pbkdf2_sha512_async(
    out.buffer,
    out.byteOffset,
    out.byteLength,

    passwd.buffer,
    passwd.byteOffset,
    passwd.byteLength,

    salt.buffer,
    salt.byteOffset,
    salt.byteLength,

    iter,
    outlen,

    done
  )

  return promise
}

exports.extension_pbkdf2_sha512 = function (out, passwd, salt, iter, outlen) {
  assert(
    iter >= binding.extension_pbkdf2_sha512_ITERATIONS_MIN,
    "iter must be at least 'extension_pbkdf2_sha512_ITERATIONS_MIN'"
  )
  assert(
    outlen <= binding.extension_pbkdf2_sha512_BYTES_MAX,
    "outlen must be at most 'extension_pbkdf2_sha512_BYTES_MAX'"
  )
  assert(ArrayBuffer.isView(out), 'out must be a typed array')
  assert(out.byteLength >= outlen, "out must be at least 'outlen' bytes")
  assert(out.byteLength > 0, 'out must not be empty')
  assert(ArrayBuffer.isView(passwd), 'passwd must be a typed array')
  assert(passwd.byteLength > 0, 'passwd must not be empty')
  assert(ArrayBuffer.isView(salt), 'salt must be a typed array')
  assert(salt.byteLength > 0, 'salt must not be empty')

  const res = binding.extension_pbkdf2_sha512(out, passwd, salt, iter, outlen)

  if (res !== 0) throw new Error('failed to add tweak to public key')
}

function checkStatus(callback, booleanResult = false) {
  let done, promise

  if (typeof callback === 'function') {
    done = function (status) {
      if (booleanResult) callback(null, status === 0)
      else if (status === 0) callback(null)
      else callback(new Error('status: ' + status))
    }
  } else {
    promise = new Promise(function (resolve, reject) {
      done = function (status) {
        if (booleanResult) resolve(status === 0)
        else if (status === 0) resolve()
        else reject(new Error('status: ' + status))
      }
    })
  }

  return [done, promise]
}
{
  "name": "sodium-native",
  "version": "5.1.0",
  "description": "Low level bindings for libsodium",
  "main": "index.js",
  "files": [
    "index.js",
    "binding.cc",
    "binding.js",
    "extensions",
    "prebuilds",
    "CMakeLists.txt"
  ],
  "imports": {
    "assert": {
      "bare": "bare-assert",
      "node": "assert"
    }
  },
  "addon": true,
  "dependencies": {
    "bare-assert": "^1.2.0",
    "require-addon": "^1.1.0",
    "which-runtime": "^1.2.1"
  },
  "devDependencies": {
    "bare-compat-napi": "^1.3.5",
    "brittle": "^3.16.2",
    "cmake-bare": "^1.6.1",
    "cmake-fetch": "^1.4.3",
    "cmake-napi": "^1.2.1",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^2.0.0"
  },
  "scripts": {
    "test": "prettier . --check && npm run test:node && npm run test:bare",
    "test:node": "node test/all.js",
    "test:bare": "bare test/all.js"
  },
  "standard": {
    "ignore": [
      "/test/fixtures/*.js"
    ]
  },
  "engines": {
    "bare": ">=1.16.0"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/sodium-native.git"
  },
  "contributors": [
    "Emil Bay <github@tixz.dk> (http://bayes.dk)",
    "Mathias Buus <mathiasbuus@gmail.com> (https://mafinto.sh)",
    "Christophe Diederichs <chm-diederichs@hyperdivision.dk>"
  ],
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/holepunchto/sodium-native/issues"
  },
  "homepage": "https://github.com/holepunchto/sodium-native"
}
const sodium = require('sodium-universal')
const b4a = require('b4a')

const ABYTES = sodium.crypto_secretstream_xchacha20poly1305_ABYTES
const TAG_MESSAGE = sodium.crypto_secretstream_xchacha20poly1305_TAG_MESSAGE
const TAG_FINAL = sodium.crypto_secretstream_xchacha20poly1305_TAG_FINAL
const STATEBYTES = sodium.crypto_secretstream_xchacha20poly1305_STATEBYTES
const HEADERBYTES = sodium.crypto_secretstream_xchacha20poly1305_HEADERBYTES
const KEYBYTES = sodium.crypto_secretstream_xchacha20poly1305_KEYBYTES
const TAG_FINAL_BYTE = b4a.isBuffer(TAG_FINAL) ? TAG_FINAL[0] : TAG_FINAL

const EMPTY = b4a.alloc(0)
const TAG = b4a.alloc(1)

class Push {
  constructor (key, state = b4a.allocUnsafeSlow(STATEBYTES), header = b4a.allocUnsafeSlow(HEADERBYTES)) {
    if (!TAG_FINAL) throw new Error('JavaScript sodium version needs to support crypto_secretstream_xchacha20poly')

    this.key = key
    this.state = state
    this.header = header

    sodium.crypto_secretstream_xchacha20poly1305_init_push(this.state, this.header, this.key)
  }

  next (message, cipher = b4a.allocUnsafe(message.byteLength + ABYTES)) {
    sodium.crypto_secretstream_xchacha20poly1305_push(this.state, cipher, message, null, TAG_MESSAGE)
    return cipher
  }

  final (message = EMPTY, cipher = b4a.allocUnsafe(ABYTES)) {
    sodium.crypto_secretstream_xchacha20poly1305_push(this.state, cipher, message, null, TAG_FINAL)
    return cipher
  }
}

class Pull {
  constructor (key, state = b4a.allocUnsafeSlow(STATEBYTES)) {
    if (!TAG_FINAL) throw new Error('JavaScript sodium version needs to support crypto_secretstream_xchacha20poly')

    this.key = key
    this.state = state
    this.final = false
  }

  init (header) {
    sodium.crypto_secretstream_xchacha20poly1305_init_pull(this.state, header, this.key)
  }

  next (cipher, message = b4a.allocUnsafe(cipher.byteLength - ABYTES)) {
    sodium.crypto_secretstream_xchacha20poly1305_pull(this.state, message, TAG, cipher, null)
    this.final = TAG[0] === TAG_FINAL_BYTE
    return message
  }
}

function keygen (buf = b4a.alloc(KEYBYTES)) {
  sodium.crypto_secretstream_xchacha20poly1305_keygen(buf)
  return buf
}

module.exports = {
  keygen,
  KEYBYTES,
  ABYTES,
  STATEBYTES,
  HEADERBYTES,
  Push,
  Pull
}
{
  "name": "sodium-secretstream",
  "version": "1.2.0",
  "description": "Wraps libsodiums secretstream in a higher level abstraction",
  "main": "index.js",
  "dependencies": {
    "b4a": "^1.1.1",
    "sodium-universal": "^5.0.0"
  },
  "devDependencies": {
    "standard": "^17.0.0"
  },
  "scripts": {
    "test": "standard"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/mafintosh/sodium-secretstream.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/mafintosh/sodium-secretstream/issues"
  },
  "homepage": "https://github.com/mafintosh/sodium-secretstream"
}
module.exports = require('sodium-native')
{
  "name": "sodium-universal",
  "version": "5.0.1",
  "description": "Universal wrapper for sodium-javascript and sodium-native working in Node.js and the Browser",
  "main": "index.js",
  "dependencies": {
    "sodium-native": "^5.0.1"
  },
  "peerDependencies": {
    "sodium-javascript": "~0.8.0"
  },
  "peerDependenciesMeta": {
    "sodium-javascript": {
      "optional": true
    }
  },
  "scripts": {
    "prepublish": "./build-scripts/generate.js"
  },
  "browser": {
    "sodium-native": "sodium-javascript"
  },
  "browserify": {
    "transform": [
      "./build-scripts/transform.js"
    ]
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/sodium-universal.git"
  },
  "keywords": [
    "libsodium",
    "sodium",
    "sodium-native",
    "sodium-javascript",
    "browserify"
  ],
  "contributors": [
    "Emil Bay <github@tixz.dk> (http://bayes.dk)",
    "Mathias Buus <mathiasbuus@gmail.com> (https://mafinto.sh)",
    "Christophe Diederichs <chm-diederichs@hyperdivision.dk>"
  ],
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/holepunchto/sodium-universal/issues"
  },
  "homepage": "https://github.com/holepunchto/sodium-universal#readme"
}
const { EventEmitter } = require('events-universal')
const FIFO = require('fast-fifo')
const TextDecoder = require('text-decoder')

const StreamError = require('./lib/errors.js')

// if we do a future major, expect queue microtask to be there always, for now a bit defensive
const qmt =
  typeof queueMicrotask === 'undefined' ? (fn) => global.process.nextTick(fn) : queueMicrotask

// 29 bits used total (4 from shared, 14 from read, and 11 from write)
const MAX = (1 << 29) - 1

// Shared state
const OPENING = 0b0001
const PREDESTROYING = 0b0010
const DESTROYING = 0b0100
const DESTROYED = 0b1000

const NOT_OPENING = MAX ^ OPENING
const NOT_PREDESTROYING = MAX ^ PREDESTROYING

// Read state (4 bit offset from shared state)
const READ_ACTIVE = 0b00000000000001 << 4
const READ_UPDATING = 0b00000000000010 << 4
const READ_PRIMARY = 0b00000000000100 << 4
const READ_QUEUED = 0b00000000001000 << 4
const READ_RESUMED = 0b00000000010000 << 4
const READ_PIPE_DRAINED = 0b00000000100000 << 4
const READ_ENDING = 0b00000001000000 << 4
const READ_EMIT_DATA = 0b00000010000000 << 4
const READ_EMIT_READABLE = 0b00000100000000 << 4
const READ_EMITTED_READABLE = 0b00001000000000 << 4
const READ_DONE = 0b00010000000000 << 4
const READ_NEXT_TICK = 0b00100000000000 << 4
const READ_NEEDS_PUSH = 0b01000000000000 << 4
const READ_READ_AHEAD = 0b10000000000000 << 4

// Combined read state
const READ_FLOWING = READ_RESUMED | READ_PIPE_DRAINED
const READ_ACTIVE_AND_NEEDS_PUSH = READ_ACTIVE | READ_NEEDS_PUSH
const READ_PRIMARY_AND_ACTIVE = READ_PRIMARY | READ_ACTIVE
const READ_EMIT_READABLE_AND_QUEUED = READ_EMIT_READABLE | READ_QUEUED
const READ_RESUMED_READ_AHEAD = READ_RESUMED | READ_READ_AHEAD

const READ_NOT_ACTIVE = MAX ^ READ_ACTIVE
const READ_NON_PRIMARY = MAX ^ READ_PRIMARY
const READ_NON_PRIMARY_AND_PUSHED = MAX ^ (READ_PRIMARY | READ_NEEDS_PUSH)
const READ_PUSHED = MAX ^ READ_NEEDS_PUSH
const READ_PAUSED = MAX ^ READ_RESUMED
const READ_NOT_QUEUED = MAX ^ (READ_QUEUED | READ_EMITTED_READABLE)
const READ_NOT_ENDING = MAX ^ READ_ENDING
const READ_PIPE_NOT_DRAINED = MAX ^ READ_FLOWING
const READ_NOT_NEXT_TICK = MAX ^ READ_NEXT_TICK
const READ_NOT_UPDATING = MAX ^ READ_UPDATING
const READ_NO_READ_AHEAD = MAX ^ READ_READ_AHEAD
const READ_PAUSED_NO_READ_AHEAD = MAX ^ READ_RESUMED_READ_AHEAD

// Write state (18 bit offset, 4 bit offset from shared state and 14 from read state)
const WRITE_ACTIVE = 0b00000000001 << 18
const WRITE_UPDATING = 0b00000000010 << 18
const WRITE_PRIMARY = 0b00000000100 << 18
const WRITE_QUEUED = 0b00000001000 << 18
const WRITE_UNDRAINED = 0b00000010000 << 18
const WRITE_DONE = 0b00000100000 << 18
const WRITE_EMIT_DRAIN = 0b00001000000 << 18
const WRITE_NEXT_TICK = 0b00010000000 << 18
const WRITE_WRITING = 0b00100000000 << 18
const WRITE_FINISHING = 0b01000000000 << 18
const WRITE_CORKED = 0b10000000000 << 18

const WRITE_NOT_ACTIVE = MAX ^ (WRITE_ACTIVE | WRITE_WRITING)
const WRITE_NON_PRIMARY = MAX ^ WRITE_PRIMARY
const WRITE_NOT_FINISHING = MAX ^ (WRITE_ACTIVE | WRITE_FINISHING)
const WRITE_DRAINED = MAX ^ WRITE_UNDRAINED
const WRITE_NOT_QUEUED = MAX ^ WRITE_QUEUED
const WRITE_NOT_NEXT_TICK = MAX ^ WRITE_NEXT_TICK
const WRITE_NOT_UPDATING = MAX ^ WRITE_UPDATING
const WRITE_NOT_CORKED = MAX ^ WRITE_CORKED

// Combined shared state
const ACTIVE = READ_ACTIVE | WRITE_ACTIVE
const NOT_ACTIVE = MAX ^ ACTIVE
const DONE = READ_DONE | WRITE_DONE
const DESTROY_STATUS = DESTROYING | DESTROYED | PREDESTROYING
const OPEN_STATUS = DESTROY_STATUS | OPENING
const AUTO_DESTROY = DESTROY_STATUS | DONE
const NON_PRIMARY = WRITE_NON_PRIMARY & READ_NON_PRIMARY
const ACTIVE_OR_TICKING = WRITE_NEXT_TICK | READ_NEXT_TICK
const TICKING = ACTIVE_OR_TICKING & NOT_ACTIVE
const IS_OPENING = OPEN_STATUS | TICKING

// Combined shared state and read state
const READ_PRIMARY_STATUS = OPEN_STATUS | READ_ENDING | READ_DONE
const READ_STATUS = OPEN_STATUS | READ_DONE | READ_QUEUED
const READ_ENDING_STATUS = OPEN_STATUS | READ_ENDING | READ_QUEUED
const READ_READABLE_STATUS = OPEN_STATUS | READ_EMIT_READABLE | READ_QUEUED | READ_EMITTED_READABLE
const SHOULD_NOT_READ =
  OPEN_STATUS | READ_ACTIVE | READ_ENDING | READ_DONE | READ_NEEDS_PUSH | READ_READ_AHEAD
const READ_BACKPRESSURE_STATUS = DESTROY_STATUS | READ_ENDING | READ_DONE
const READ_UPDATE_SYNC_STATUS = READ_UPDATING | OPEN_STATUS | READ_NEXT_TICK | READ_PRIMARY
const READ_NEXT_TICK_OR_OPENING = READ_NEXT_TICK | OPENING

// Combined write state
const WRITE_PRIMARY_STATUS = OPEN_STATUS | WRITE_FINISHING | WRITE_DONE
const WRITE_QUEUED_AND_UNDRAINED = WRITE_QUEUED | WRITE_UNDRAINED
const WRITE_QUEUED_AND_ACTIVE = WRITE_QUEUED | WRITE_ACTIVE
const WRITE_DRAIN_STATUS = WRITE_QUEUED | WRITE_UNDRAINED | OPEN_STATUS | WRITE_ACTIVE
const WRITE_STATUS = OPEN_STATUS | WRITE_ACTIVE | WRITE_QUEUED | WRITE_CORKED
const WRITE_PRIMARY_AND_ACTIVE = WRITE_PRIMARY | WRITE_ACTIVE
const WRITE_ACTIVE_AND_WRITING = WRITE_ACTIVE | WRITE_WRITING
const WRITE_FINISHING_STATUS = OPEN_STATUS | WRITE_FINISHING | WRITE_QUEUED_AND_ACTIVE | WRITE_DONE
const WRITE_BACKPRESSURE_STATUS = WRITE_UNDRAINED | DESTROY_STATUS | WRITE_FINISHING | WRITE_DONE
const WRITE_UPDATE_SYNC_STATUS = WRITE_UPDATING | OPEN_STATUS | WRITE_NEXT_TICK | WRITE_PRIMARY
const WRITE_DROP_DATA = WRITE_FINISHING | WRITE_DONE | DESTROY_STATUS

const asyncIterator = Symbol.asyncIterator || Symbol('asyncIterator')

class WritableState {
  constructor(
    stream,
    { highWaterMark = 16384, map = null, mapWritable, byteLength, byteLengthWritable } = {}
  ) {
    this.stream = stream
    this.queue = new FIFO()
    this.highWaterMark = highWaterMark
    this.buffered = 0
    this.error = null
    this.pipeline = null
    this.drains = null // if we add more seldomly used helpers we might them into a subobject so its a single ptr
    this.byteLength = byteLengthWritable || byteLength || defaultByteLength
    this.map = mapWritable || map
    this.afterWrite = afterWrite.bind(this)
    this.afterUpdateNextTick = updateWriteNT.bind(this)
  }

  get ending() {
    return (this.stream._duplexState & WRITE_FINISHING) !== 0
  }

  get ended() {
    return (this.stream._duplexState & WRITE_DONE) !== 0
  }

  push(data) {
    if ((this.stream._duplexState & WRITE_DROP_DATA) !== 0) return false
    if (this.map !== null) data = this.map(data)

    this.buffered += this.byteLength(data)
    this.queue.push(data)

    if (this.buffered < this.highWaterMark) {
      this.stream._duplexState |= WRITE_QUEUED
      return true
    }

    this.stream._duplexState |= WRITE_QUEUED_AND_UNDRAINED
    return false
  }

  shift() {
    const data = this.queue.shift()

    this.buffered -= this.byteLength(data)
    if (this.buffered === 0) this.stream._duplexState &= WRITE_NOT_QUEUED

    return data
  }

  end(data) {
    if (typeof data === 'function') {
      this.stream.once('finish', data)
    } else if (data !== undefined && data !== null) {
      this.push(data)
    }

    this.stream._duplexState = (this.stream._duplexState | WRITE_FINISHING) & WRITE_NON_PRIMARY
  }

  autoBatch(data, cb) {
    const buffer = []
    const stream = this.stream

    buffer.push(data)
    while ((stream._duplexState & WRITE_STATUS) === WRITE_QUEUED_AND_ACTIVE) {
      buffer.push(stream._writableState.shift())
    }

    if ((stream._duplexState & OPEN_STATUS) !== 0) return cb(null)
    stream._writev(buffer, cb)
  }

  update() {
    const stream = this.stream

    stream._duplexState |= WRITE_UPDATING

    do {
      while ((stream._duplexState & WRITE_STATUS) === WRITE_QUEUED) {
        const data = this.shift()
        stream._duplexState |= WRITE_ACTIVE_AND_WRITING
        stream._write(data, this.afterWrite)
      }

      if ((stream._duplexState & WRITE_PRIMARY_AND_ACTIVE) === 0) this.updateNonPrimary()
    } while (this.continueUpdate() === true)

    stream._duplexState &= WRITE_NOT_UPDATING
  }

  updateNonPrimary() {
    const stream = this.stream

    if ((stream._duplexState & WRITE_FINISHING_STATUS) === WRITE_FINISHING) {
      stream._duplexState = stream._duplexState | WRITE_ACTIVE
      stream._final(afterFinal.bind(this))
      return
    }

    if ((stream._duplexState & DESTROY_STATUS) === DESTROYING) {
      if ((stream._duplexState & ACTIVE_OR_TICKING) === 0) {
        stream._duplexState |= ACTIVE
        stream._destroy(afterDestroy.bind(this))
      }
      return
    }

    if ((stream._duplexState & IS_OPENING) === OPENING) {
      stream._duplexState = (stream._duplexState | ACTIVE) & NOT_OPENING
      stream._open(afterOpen.bind(this))
    }
  }

  continueUpdate() {
    if ((this.stream._duplexState & WRITE_NEXT_TICK) === 0) return false
    this.stream._duplexState &= WRITE_NOT_NEXT_TICK
    return true
  }

  updateCallback() {
    if ((this.stream._duplexState & WRITE_UPDATE_SYNC_STATUS) === WRITE_PRIMARY) {
      this.update()
    } else {
      this.updateNextTick()
    }
  }

  updateNextTick() {
    if ((this.stream._duplexState & WRITE_NEXT_TICK) !== 0) return
    this.stream._duplexState |= WRITE_NEXT_TICK
    if ((this.stream._duplexState & WRITE_UPDATING) === 0) qmt(this.afterUpdateNextTick)
  }
}

class ReadableState {
  constructor(
    stream,
    { highWaterMark = 16384, map = null, mapReadable, byteLength, byteLengthReadable } = {}
  ) {
    this.stream = stream
    this.queue = new FIFO()
    this.highWaterMark = highWaterMark === 0 ? 1 : highWaterMark
    this.buffered = 0
    this.readAhead = highWaterMark > 0
    this.error = null
    this.pipeline = null
    this.byteLength = byteLengthReadable || byteLength || defaultByteLength
    this.map = mapReadable || map
    this.pipeTo = null
    this.afterRead = afterRead.bind(this)
    this.afterUpdateNextTick = updateReadNT.bind(this)
  }

  get ending() {
    return (this.stream._duplexState & READ_ENDING) !== 0
  }

  get ended() {
    return (this.stream._duplexState & READ_DONE) !== 0
  }

  pipe(pipeTo, cb) {
    if (this.pipeTo !== null) throw StreamError.BAD_ARGUMENT('Can only pipe to one destination')
    if (typeof cb !== 'function') cb = null

    this.stream._duplexState |= READ_PIPE_DRAINED
    this.pipeTo = pipeTo
    this.pipeline = new Pipeline(this.stream, pipeTo, cb)

    if (cb) this.stream.on('error', noop) // We already error handle this so supress crashes

    if (isStreamx(pipeTo)) {
      pipeTo._writableState.pipeline = this.pipeline
      if (cb) pipeTo.on('error', noop) // We already error handle this so supress crashes
      pipeTo.on('finish', this.pipeline.finished.bind(this.pipeline)) // TODO: just call finished from pipeTo itself
    } else {
      const onerror = this.pipeline.done.bind(this.pipeline, pipeTo)
      const onclose = this.pipeline.done.bind(this.pipeline, pipeTo, null) // onclose has a weird bool arg
      pipeTo.on('error', onerror)
      pipeTo.on('close', onclose)
      pipeTo.on('finish', this.pipeline.finished.bind(this.pipeline))
    }

    pipeTo.on('drain', afterDrain.bind(this))
    this.stream.emit('piping', pipeTo)
    pipeTo.emit('pipe', this.stream)
  }

  push(data) {
    const stream = this.stream

    if (data === null) {
      this.highWaterMark = 0
      stream._duplexState = (stream._duplexState | READ_ENDING) & READ_NON_PRIMARY_AND_PUSHED
      return false
    }

    if (this.map !== null) {
      data = this.map(data)
      if (data === null) {
        stream._duplexState &= READ_PUSHED
        return this.buffered < this.highWaterMark
      }
    }

    this.buffered += this.byteLength(data)
    this.queue.push(data)

    stream._duplexState = (stream._duplexState | READ_QUEUED) & READ_PUSHED

    return this.buffered < this.highWaterMark
  }

  shift() {
    const data = this.queue.shift()

    this.buffered -= this.byteLength(data)
    if (this.buffered === 0) {
      this.stream._duplexState &= READ_NOT_QUEUED
    }

    return data
  }

  unshift(data) {
    const pending = [this.map !== null ? this.map(data) : data]
    while (this.buffered > 0) pending.push(this.shift())

    for (let i = 0; i < pending.length - 1; i++) {
      const data = pending[i]
      this.buffered += this.byteLength(data)
      this.queue.push(data)
    }

    this.push(pending[pending.length - 1])
  }

  read() {
    const stream = this.stream

    if ((stream._duplexState & READ_STATUS) === READ_QUEUED) {
      const data = this.shift()

      if (this.pipeTo !== null && this.pipeTo.write(data) === false) {
        stream._duplexState &= READ_PIPE_NOT_DRAINED
      }

      if ((stream._duplexState & READ_EMIT_DATA) !== 0) {
        stream.emit('data', data)
      }

      return data
    }

    if (this.readAhead === false) {
      stream._duplexState |= READ_READ_AHEAD
      this.updateNextTick()
    }

    return null
  }

  drain() {
    const stream = this.stream

    while (
      (stream._duplexState & READ_STATUS) === READ_QUEUED &&
      (stream._duplexState & READ_FLOWING) !== 0
    ) {
      const data = this.shift()

      if (this.pipeTo !== null && this.pipeTo.write(data) === false) {
        stream._duplexState &= READ_PIPE_NOT_DRAINED
      }

      if ((stream._duplexState & READ_EMIT_DATA) !== 0) {
        stream.emit('data', data)
      }
    }
  }

  update() {
    const stream = this.stream

    stream._duplexState |= READ_UPDATING

    do {
      this.drain()

      while (
        this.buffered < this.highWaterMark &&
        (stream._duplexState & SHOULD_NOT_READ) === READ_READ_AHEAD
      ) {
        stream._duplexState |= READ_ACTIVE_AND_NEEDS_PUSH
        stream._read(this.afterRead)
        this.drain()
      }

      if ((stream._duplexState & READ_READABLE_STATUS) === READ_EMIT_READABLE_AND_QUEUED) {
        stream._duplexState |= READ_EMITTED_READABLE
        stream.emit('readable')
      }

      if ((stream._duplexState & READ_PRIMARY_AND_ACTIVE) === 0) {
        this.updateNonPrimary()
      }
    } while (this.continueUpdate() === true)

    stream._duplexState &= READ_NOT_UPDATING
  }

  updateNonPrimary() {
    const stream = this.stream

    if ((stream._duplexState & READ_ENDING_STATUS) === READ_ENDING) {
      stream._duplexState = (stream._duplexState | READ_DONE) & READ_NOT_ENDING
      stream.emit('end')

      if ((stream._duplexState & AUTO_DESTROY) === DONE) {
        stream._duplexState |= DESTROYING
      }

      if (this.pipeTo !== null) {
        this.pipeTo.end()
      }
    }

    if ((stream._duplexState & DESTROY_STATUS) === DESTROYING) {
      if ((stream._duplexState & ACTIVE_OR_TICKING) === 0) {
        stream._duplexState |= ACTIVE
        stream._destroy(afterDestroy.bind(this))
      }
      return
    }

    if ((stream._duplexState & IS_OPENING) === OPENING) {
      stream._duplexState = (stream._duplexState | ACTIVE) & NOT_OPENING
      stream._open(afterOpen.bind(this))
    }
  }

  continueUpdate() {
    if ((this.stream._duplexState & READ_NEXT_TICK) === 0) return false
    this.stream._duplexState &= READ_NOT_NEXT_TICK
    return true
  }

  updateCallback() {
    if ((this.stream._duplexState & READ_UPDATE_SYNC_STATUS) === READ_PRIMARY) {
      this.update()
    } else {
      this.updateNextTick()
    }
  }

  updateNextTickIfOpen() {
    if ((this.stream._duplexState & READ_NEXT_TICK_OR_OPENING) !== 0) return
    this.stream._duplexState |= READ_NEXT_TICK
    if ((this.stream._duplexState & READ_UPDATING) === 0) qmt(this.afterUpdateNextTick)
  }

  updateNextTick() {
    if ((this.stream._duplexState & READ_NEXT_TICK) !== 0) return
    this.stream._duplexState |= READ_NEXT_TICK
    if ((this.stream._duplexState & READ_UPDATING) === 0) qmt(this.afterUpdateNextTick)
  }
}

class TransformState {
  constructor(stream) {
    this.data = null
    this.afterTransform = afterTransform.bind(stream)
    this.afterFinal = null
  }
}

class Pipeline {
  constructor(src, dst, cb) {
    this.from = src
    this.to = dst
    this.afterPipe = cb
    this.error = null
    this.pipeToFinished = false
  }

  finished() {
    this.pipeToFinished = true
  }

  done(stream, err) {
    if (err) this.error = err

    if (stream === this.to) {
      this.to = null

      if (this.from !== null) {
        if ((this.from._duplexState & READ_DONE) === 0 || !this.pipeToFinished) {
          this.from.destroy(this.error || StreamError.PREMATURE_CLOSE('Writable stream closed'))
        }
        return
      }
    }

    if (stream === this.from) {
      this.from = null

      if (this.to !== null) {
        if ((stream._duplexState & READ_DONE) === 0) {
          this.to.destroy(this.error || StreamError.PREMATURE_CLOSE('Readable stream closed'))
        }
        return
      }
    }

    if (this.afterPipe !== null) this.afterPipe(this.error)
    this.to = this.from = this.afterPipe = null
  }
}

function afterDrain() {
  this.stream._duplexState |= READ_PIPE_DRAINED
  this.updateCallback()
}

function afterFinal(err) {
  const stream = this.stream
  if (err) stream.destroy(err)

  if ((stream._duplexState & DESTROY_STATUS) === 0) {
    stream._duplexState |= WRITE_DONE
    stream.emit('finish')
  }

  if ((stream._duplexState & AUTO_DESTROY) === DONE) {
    stream._duplexState |= DESTROYING
  }

  stream._duplexState &= WRITE_NOT_FINISHING

  // no need to wait the extra tick here, so we short circuit that
  if ((stream._duplexState & WRITE_UPDATING) === 0) {
    this.update()
  } else {
    this.updateNextTick()
  }
}

function afterDestroy(err) {
  const stream = this.stream

  if (!err && !StreamError.isStreamDestroyed(this.error)) err = this.error
  if (err) stream.emit('error', err)

  stream._duplexState |= DESTROYED
  stream.emit('close')

  const rs = stream._readableState
  const ws = stream._writableState

  if (rs !== null && rs.pipeline !== null) {
    rs.pipeline.done(stream, err)
  }

  if (ws !== null) {
    while (ws.drains !== null && ws.drains.length > 0) {
      ws.drains.shift().resolve(false)
    }

    if (ws.pipeline !== null) {
      ws.pipeline.done(stream, err)
    }
  }
}

function afterWrite(err) {
  const stream = this.stream

  if (err) stream.destroy(err)
  stream._duplexState &= WRITE_NOT_ACTIVE

  if (this.drains !== null) tickDrains(this.drains)

  if ((stream._duplexState & WRITE_DRAIN_STATUS) === WRITE_UNDRAINED) {
    stream._duplexState &= WRITE_DRAINED

    if ((stream._duplexState & WRITE_EMIT_DRAIN) === WRITE_EMIT_DRAIN) {
      stream.emit('drain')
    }
  }

  this.updateCallback()
}

function afterRead(err) {
  if (err) this.stream.destroy(err)
  this.stream._duplexState &= READ_NOT_ACTIVE

  if (this.readAhead === false && (this.stream._duplexState & READ_RESUMED) === 0) {
    this.stream._duplexState &= READ_NO_READ_AHEAD
  }

  this.updateCallback()
}

function updateReadNT() {
  if ((this.stream._duplexState & READ_UPDATING) === 0) {
    this.stream._duplexState &= READ_NOT_NEXT_TICK
    this.update()
  }
}

function updateWriteNT() {
  if ((this.stream._duplexState & WRITE_UPDATING) === 0) {
    this.stream._duplexState &= WRITE_NOT_NEXT_TICK
    this.update()
  }
}

function tickDrains(drains) {
  for (let i = 0; i < drains.length; i++) {
    // drains.writes are monotonic, so if one is 0 its always the first one
    if (--drains[i].writes === 0) {
      drains.shift().resolve(true)
      i--
    }
  }
}

function afterOpen(err) {
  const stream = this.stream

  if (err) stream.destroy(err)

  if ((stream._duplexState & DESTROYING) === 0) {
    if ((stream._duplexState & READ_PRIMARY_STATUS) === 0) {
      stream._duplexState |= READ_PRIMARY
    }

    if ((stream._duplexState & WRITE_PRIMARY_STATUS) === 0) {
      stream._duplexState |= WRITE_PRIMARY
    }

    stream.emit('open')
  }

  stream._duplexState &= NOT_ACTIVE

  if (stream._writableState !== null) {
    stream._writableState.updateCallback()
  }

  if (stream._readableState !== null) {
    stream._readableState.updateCallback()
  }
}

function afterTransform(err, data) {
  if (data !== undefined && data !== null) this.push(data)
  this._writableState.afterWrite(err)
}

function newListener(name) {
  if (this._readableState !== null) {
    if (name === 'data') {
      this._duplexState |= READ_EMIT_DATA | READ_RESUMED_READ_AHEAD
      this._readableState.updateNextTick()
    }
    if (name === 'readable') {
      this._duplexState |= READ_EMIT_READABLE
      this._readableState.updateNextTick()
    }
  }

  if (this._writableState !== null) {
    if (name === 'drain') {
      this._duplexState |= WRITE_EMIT_DRAIN
      this._writableState.updateNextTick()
    }
  }
}

class Stream extends EventEmitter {
  constructor(opts) {
    super()

    this._duplexState = 0
    this._readableState = null
    this._writableState = null

    if (opts) {
      if (opts.open) this._open = opts.open
      if (opts.destroy) this._destroy = opts.destroy
      if (opts.predestroy) this._predestroy = opts.predestroy
      if (opts.signal) opts.signal.addEventListener('abort', abort.bind(this))
    }

    this.on('newListener', newListener)
  }

  _open(cb) {
    cb(null)
  }

  _destroy(cb) {
    cb(null)
  }

  _predestroy() {
    // does nothing
  }

  get readable() {
    return this._readableState !== null ? true : undefined
  }

  get writable() {
    return this._writableState !== null ? true : undefined
  }

  get destroyed() {
    return (this._duplexState & DESTROYED) !== 0
  }

  get destroying() {
    return (this._duplexState & DESTROY_STATUS) !== 0
  }

  destroy(err) {
    if ((this._duplexState & DESTROY_STATUS) === 0) {
      if (!err) err = StreamError.STREAM_DESTROYED()
      this._duplexState = (this._duplexState | DESTROYING) & NON_PRIMARY

      if (this._readableState !== null) {
        this._readableState.highWaterMark = 0
        this._readableState.error = err
      }

      if (this._writableState !== null) {
        this._writableState.highWaterMark = 0
        this._writableState.error = err
      }

      this._duplexState |= PREDESTROYING
      this._predestroy()
      this._duplexState &= NOT_PREDESTROYING

      if (this._readableState !== null) {
        this._readableState.updateNextTick()
      }

      if (this._writableState !== null) {
        this._writableState.updateNextTick()
      }
    }
  }
}

class Readable extends Stream {
  constructor(opts) {
    super(opts)

    this._duplexState |= OPENING | WRITE_DONE | READ_READ_AHEAD
    this._readableState = new ReadableState(this, opts)

    if (opts) {
      if (this._readableState.readAhead === false) this._duplexState &= READ_NO_READ_AHEAD
      if (opts.read) this._read = opts.read
      if (opts.eagerOpen) this._readableState.updateNextTick()
      if (opts.encoding) this.setEncoding(opts.encoding)
    }
  }

  static deferred(fn, opts) {
    const out = new PassThrough(opts)

    fn()
      .then((src) => {
        if (src === null) return out.end()
        if (out.destroying) return
        pipeline(src, out, noop)
      })
      .catch((err) => out.destroy(err))

    return out
  }

  setEncoding(encoding) {
    const dec = new TextDecoder(encoding)
    const map = this._readableState.map || echo
    this._readableState.map = mapOrSkip
    return this

    function mapOrSkip(data) {
      const next = dec.push(data)
      return next === '' && (data.byteLength !== 0 || dec.remaining > 0) ? null : map(next)
    }
  }

  _read(cb) {
    cb(null)
  }

  pipe(dest, cb) {
    this._readableState.updateNextTick()
    this._readableState.pipe(dest, cb)
    return dest
  }

  read() {
    this._readableState.updateNextTick()
    return this._readableState.read()
  }

  push(data) {
    this._readableState.updateNextTickIfOpen()
    return this._readableState.push(data)
  }

  unshift(data) {
    this._readableState.updateNextTickIfOpen()
    return this._readableState.unshift(data)
  }

  resume() {
    this._duplexState |= READ_RESUMED_READ_AHEAD
    this._readableState.updateNextTick()
    return this
  }

  pause() {
    this._duplexState &=
      this._readableState.readAhead === false ? READ_PAUSED_NO_READ_AHEAD : READ_PAUSED
    return this
  }

  static _fromAsyncIterator(ite, opts) {
    let destroy

    const rs = new Readable({
      ...opts,
      read(cb) {
        ite.next().then(push).then(cb.bind(null, null)).catch(cb)
      },
      predestroy() {
        destroy = ite.return()
      },
      destroy(cb) {
        if (!destroy) return cb(null)
        destroy.then(cb.bind(null, null)).catch(cb)
      }
    })

    return rs

    function push(data) {
      if (data.done) rs.push(null)
      else rs.push(data.value)
    }
  }

  static from(data, opts) {
    if (isReadStreamx(data)) return data
    if (data[asyncIterator]) return this._fromAsyncIterator(data[asyncIterator](), opts)
    if (!Array.isArray(data)) data = data === undefined ? [] : [data]

    let i = 0
    return new Readable({
      ...opts,
      read(cb) {
        this.push(i === data.length ? null : data[i++])
        cb(null)
      }
    })
  }

  static isBackpressured(rs) {
    return (
      (rs._duplexState & READ_BACKPRESSURE_STATUS) !== 0 ||
      rs._readableState.buffered >= rs._readableState.highWaterMark
    )
  }

  static isPaused(rs) {
    return (rs._duplexState & READ_RESUMED) === 0
  }

  [asyncIterator]() {
    const stream = this

    let error = null
    let promiseResolve = null
    let promiseReject = null

    this.on('error', (err) => {
      error = err
    })
    this.on('readable', onreadable)
    this.on('close', onclose)

    return {
      [asyncIterator]() {
        return this
      },
      next() {
        return new Promise(function (resolve, reject) {
          promiseResolve = resolve
          promiseReject = reject
          const data = stream.read()
          if (data !== null) ondata(data)
          else if ((stream._duplexState & DESTROYED) !== 0) ondata(null)
        })
      },
      return() {
        return destroy(null)
      },
      throw(err) {
        return destroy(err)
      }
    }

    function onreadable() {
      if (promiseResolve !== null) ondata(stream.read())
    }

    function onclose() {
      if (promiseResolve !== null) ondata(null)
    }

    function ondata(data) {
      if (promiseReject === null) return
      if (error) {
        promiseReject(error)
      } else if (data === null && (stream._duplexState & READ_DONE) === 0) {
        promiseReject(StreamError.STREAM_DESTROYED())
      } else {
        promiseResolve({ value: data, done: data === null })
      }
      promiseReject = promiseResolve = null
    }

    function destroy(err) {
      stream.destroy(err)
      return new Promise((resolve, reject) => {
        if (stream._duplexState & DESTROYED) return resolve({ value: undefined, done: true })
        stream.once('close', function () {
          if (err) reject(err)
          else resolve({ value: undefined, done: true })
        })
      })
    }
  }
}

class Writable extends Stream {
  constructor(opts) {
    super(opts)

    this._duplexState |= OPENING | READ_DONE
    this._writableState = new WritableState(this, opts)

    if (opts) {
      if (opts.writev) this._writev = opts.writev
      if (opts.write) this._write = opts.write
      if (opts.final) this._final = opts.final
      if (opts.eagerOpen) this._writableState.updateNextTick()
    }
  }

  cork() {
    this._duplexState |= WRITE_CORKED
  }

  uncork() {
    this._duplexState &= WRITE_NOT_CORKED
    this._writableState.updateNextTick()
  }

  _writev(batch, cb) {
    cb(null)
  }

  _write(data, cb) {
    this._writableState.autoBatch(data, cb)
  }

  _final(cb) {
    cb(null)
  }

  static isBackpressured(ws) {
    return (ws._duplexState & WRITE_BACKPRESSURE_STATUS) !== 0
  }

  static drained(ws) {
    if (ws.destroyed) return Promise.resolve(false)

    const state = ws._writableState
    const pending = isWritev(ws) ? Math.min(1, state.queue.length) : state.queue.length
    const writes = pending + (ws._duplexState & WRITE_WRITING ? 1 : 0)
    if (writes === 0) return Promise.resolve(true)

    if (state.drains === null) state.drains = []
    return new Promise((resolve) => {
      state.drains.push({ writes, resolve })
    })
  }

  write(data) {
    this._writableState.updateNextTick()
    return this._writableState.push(data)
  }

  end(data) {
    this._writableState.updateNextTick()
    this._writableState.end(data)
    return this
  }
}

class Duplex extends Readable {
  // and Writable
  constructor(opts) {
    super(opts)

    this._duplexState = OPENING | (this._duplexState & READ_READ_AHEAD)
    this._writableState = new WritableState(this, opts)

    if (opts) {
      if (opts.writev) this._writev = opts.writev
      if (opts.write) this._write = opts.write
      if (opts.final) this._final = opts.final
    }
  }

  cork() {
    this._duplexState |= WRITE_CORKED
  }

  uncork() {
    this._duplexState &= WRITE_NOT_CORKED
    this._writableState.updateNextTick()
  }

  _writev(batch, cb) {
    cb(null)
  }

  _write(data, cb) {
    this._writableState.autoBatch(data, cb)
  }

  _final(cb) {
    cb(null)
  }

  write(data) {
    this._writableState.updateNextTick()
    return this._writableState.push(data)
  }

  end(data) {
    this._writableState.updateNextTick()
    this._writableState.end(data)
    return this
  }
}

class Transform extends Duplex {
  constructor(opts) {
    super(opts)
    this._transformState = new TransformState(this)

    if (opts) {
      if (opts.transform) this._transform = opts.transform
      if (opts.flush) this._flush = opts.flush
    }
  }

  _write(data, cb) {
    if (this._readableState.buffered >= this._readableState.highWaterMark) {
      this._transformState.data = data
    } else {
      this._transform(data, this._transformState.afterTransform)
    }
  }

  _read(cb) {
    if (this._transformState.data !== null) {
      const data = this._transformState.data
      this._transformState.data = null
      cb(null)
      this._transform(data, this._transformState.afterTransform)
    } else {
      cb(null)
    }
  }

  destroy(err) {
    super.destroy(err)
    if (this._transformState.data !== null) {
      this._transformState.data = null
      this._transformState.afterTransform()
    }
  }

  _transform(data, cb) {
    cb(null, data)
  }

  _flush(cb) {
    cb(null)
  }

  _final(cb) {
    this._transformState.afterFinal = cb
    this._flush(transformAfterFlush.bind(this))
  }
}

class PassThrough extends Transform {}

function transformAfterFlush(err, data) {
  const cb = this._transformState.afterFinal
  if (err) return cb(err)

  if (data !== null && data !== undefined) this.push(data)
  this.push(null)
  cb(null)
}

function pipelinePromise(...streams) {
  return new Promise((resolve, reject) => {
    return pipeline(...streams, (err) => {
      if (err) return reject(err)
      resolve()
    })
  })
}

function pipeline(stream, ...streams) {
  const all = Array.isArray(stream) ? [...stream, ...streams] : [stream, ...streams]
  const done = all.length && typeof all[all.length - 1] === 'function' ? all.pop() : null

  if (all.length < 2) throw StreamError.BAD_ARGUMENT('Pipeline requires at least 2 streams')

  let src = all[0]
  let dest = null
  let error = null

  for (let i = 1; i < all.length; i++) {
    dest = all[i]

    if (isStreamx(src)) {
      src.pipe(dest, onerror)
    } else {
      errorHandle(src, true, i > 1, onerror)
      src.pipe(dest)
    }

    src = dest
  }

  if (done) {
    let fin = false

    const autoDestroy =
      isStreamx(dest) || !!(dest._writableState && dest._writableState.autoDestroy)

    dest.on('error', (err) => {
      if (error === null) error = err
    })

    dest.on('finish', () => {
      fin = true
      if (!autoDestroy) done(error)
    })

    if (autoDestroy) {
      dest.on('close', () => done(error || (fin ? null : StreamError.PREMATURE_CLOSE())))
    }
  }

  return dest

  function errorHandle(s, rd, wr, onerror) {
    s.on('error', onerror)
    s.on('close', onclose)

    function onclose() {
      if (rd && s._readableState && !s._readableState.ended) {
        return onerror(StreamError.PREMATURE_CLOSE())
      }
      if (wr && s._writableState && !s._writableState.ended) {
        return onerror(StreamError.PREMATURE_CLOSE())
      }
    }
  }

  function onerror(err) {
    if (!err || error) return
    error = err

    for (const s of all) {
      s.destroy(err)
    }
  }
}

function echo(s) {
  return s
}

function isStream(stream) {
  return !!stream._readableState || !!stream._writableState
}

function isStreamx(stream) {
  return typeof stream._duplexState === 'number' && isStream(stream)
}

function isEnding(stream) {
  return !!stream._readableState && stream._readableState.ending
}

function isEnded(stream) {
  return !!stream._readableState && stream._readableState.ended
}

function isFinishing(stream) {
  return !!stream._writableState && stream._writableState.ending
}

function isFinished(stream) {
  return !!stream._writableState && stream._writableState.ended
}

function getStreamError(stream, opts = {}) {
  const err =
    (stream._readableState && stream._readableState.error) ||
    (stream._writableState && stream._writableState.error)

  // avoid implicit errors by default
  return !opts.all && StreamError.isStreamDestroyed(err) ? null : err
}

function isReadStreamx(stream) {
  return isStreamx(stream) && stream.readable
}

function isDisturbed(stream) {
  return (
    (stream._duplexState & OPENING) !== OPENING ||
    (stream._duplexState & DESTROYING) === DESTROYING ||
    (stream._duplexState & ACTIVE_OR_TICKING) !== 0
  )
}

function isTypedArray(data) {
  return typeof data === 'object' && data !== null && typeof data.byteLength === 'number'
}

function defaultByteLength(data) {
  return isTypedArray(data) ? data.byteLength : 1024
}

function noop() {}

function abort() {
  this.destroy(StreamError.ABORTED())
}

function isWritev(s) {
  return s._writev !== Writable.prototype._writev && s._writev !== Duplex.prototype._writev
}

module.exports = {
  pipeline,
  pipelinePromise,
  isStream,
  isStreamx,
  isEnding,
  isEnded,
  isFinishing,
  isFinished,
  isDisturbed,
  getStreamError,
  Stream,
  Writable,
  Readable,
  Duplex,
  Transform,
  // Export PassThrough for compatibility with Node.js core's stream module
  PassThrough
}
module.exports = class StreamError extends Error {
  constructor(msg, code, fn = StreamError) {
    super(msg)

    this.code = code

    if (Error.captureStackTrace) {
      Error.captureStackTrace(this, fn)
    }
  }

  static isStreamDestroyed(err) {
    return err && err.code === 'STREAM_DESTROYED'
  }

  static isPrematureClose(err) {
    return err && err.code === 'PREMATURE_CLOSE'
  }

  static isAborted(err) {
    return err && err.code === 'ABORTED'
  }

  static isBadArgument(err) {
    return err && err.code === 'BAD_ARGUMENT'
  }

  get name() {
    return 'StreamError'
  }

  static STREAM_DESTROYED() {
    return new StreamError('Stream was destroyed', 'STREAM_DESTROYED', StreamError.STREAM_DESTROYED)
  }

  static PREMATURE_CLOSE(msg = 'Premature close') {
    return new StreamError(msg, 'PREMATURE_CLOSE', StreamError.PREMATURE_CLOSE)
  }

  static ABORTED() {
    return new StreamError('Stream aborted', 'ABORTED', StreamError.ABORTED)
  }

  static BAD_ARGUMENT(msg = 'Bad argument') {
    return new StreamError(msg, 'BAD_ARGUMENT', StreamError.BAD_ARGUMENT)
  }
}
{
  "name": "streamx",
  "version": "2.28.1",
  "description": "An iteration of the Node.js core streams with a series of improvements",
  "main": "index.js",
  "exports": {
    ".": {
      "types": "./index.d.ts",
      "default": "./index.js"
    },
    "./package": "./package.json",
    "./errors": {
      "types": "./lib/errors.d.ts",
      "default": "./lib/errors"
    }
  },
  "dependencies": {
    "events-universal": "^1.0.0",
    "fast-fifo": "^1.3.2",
    "text-decoder": "^1.1.0"
  },
  "devDependencies": {
    "b4a": "^1.6.6",
    "brittle": "^3.1.1",
    "end-of-stream": "^1.4.4",
    "lunte": "^1.8.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^2.0.0"
  },
  "files": [
    "index.js",
    "index.d.ts",
    "runtime.d.ts",
    "lib"
  ],
  "scripts": {
    "format": "prettier . --write",
    "test": "brittle test/all.js && brittle-bare test/all.js",
    "lint": "prettier . --check && lunte"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/mafintosh/streamx.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/mafintosh/streamx/issues"
  },
  "homepage": "https://github.com/mafintosh/streamx"
}
const { Readable } = require('streamx')

module.exports = function (s, forks = 2) {
  const streams = new Array(forks)
  const status = new Array(forks).fill(true)

  let ended = false

  for (let i = 0; i < forks; i++) {
    streams[i] = new Readable({
      read (cb) {
        const check = !status[i]
        status[i] = true
        if (check && allReadable()) s.resume()
        cb(null)
      }
    })
  }

  s.on('end', function () {
    ended = true
    for (const stream of streams) stream.push(null)
  })

  s.on('error', function (err) {
    for (const stream of streams) stream.destroy(err)
  })

  s.on('close', function () {
    if (ended) return
    for (const stream of streams) stream.destroy()
  })

  s.on('data', function (data) {
    let needsPause = false
    for (let i = 0; i < streams.length; i++) {
      if (!(status[i] = streams[i].push(data))) {
        needsPause = true
      }
    }
    if (needsPause) s.pause()
  })

  return streams

  function allReadable () {
    for (let j = 0; j < status.length; j++) {
      if (!status[j]) return false
    }
    return true
  }
}
{
  "name": "teex",
  "version": "1.0.1",
  "description": "Turn a readable stream into multiple readable streamx streams",
  "main": "index.js",
  "dependencies": {
    "streamx": "^2.12.5"
  },
  "devDependencies": {
    "standard": "^14.3.4",
    "tape": "^5.0.1"
  },
  "scripts": {
    "test": "standard && tape test.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/mafintosh/teex.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/mafintosh/teex/issues"
  },
  "homepage": "https://github.com/mafintosh/teex"
}
const PassThroughDecoder = require('./lib/pass-through-decoder')
const UTF8Decoder = require('./lib/utf8-decoder')

module.exports = class TextDecoder {
  constructor(encoding = 'utf8') {
    this.encoding = normalizeEncoding(encoding)

    switch (this.encoding) {
      case 'utf8':
        this.decoder = new UTF8Decoder()
        break
      case 'utf16le':
      case 'base64':
        throw new Error('Unsupported encoding: ' + this.encoding)
      default:
        this.decoder = new PassThroughDecoder(this.encoding)
    }
  }

  get remaining() {
    return this.decoder.remaining
  }

  push(data) {
    if (typeof data === 'string') return data
    return this.decoder.decode(data)
  }

  // For Node.js compatibility
  write(data) {
    return this.push(data)
  }

  end(data) {
    let result = ''
    if (data) result = this.push(data)
    result += this.decoder.flush()
    return result
  }
}

function normalizeEncoding(encoding) {
  encoding = encoding.toLowerCase()

  switch (encoding) {
    case 'utf8':
    case 'utf-8':
      return 'utf8'
    case 'ucs2':
    case 'ucs-2':
    case 'utf16le':
    case 'utf-16le':
      return 'utf16le'
    case 'latin1':
    case 'binary':
      return 'latin1'
    case 'base64':
    case 'ascii':
    case 'hex':
      return encoding
    default:
      throw new Error('Unknown encoding: ' + encoding)
  }
}
const b4a = require('b4a')

module.exports = class PassThroughDecoder {
  constructor(encoding) {
    this.encoding = encoding
  }

  get remaining() {
    return 0
  }

  decode(data) {
    return b4a.toString(data, this.encoding)
  }

  flush() {
    return ''
  }
}
const b4a = require('b4a')

/**
 * https://encoding.spec.whatwg.org/#utf-8-decoder
 */
module.exports = class UTF8Decoder {
  constructor() {
    this._reset()
  }

  get remaining() {
    return this.bytesSeen
  }

  decode(data) {
    if (data.byteLength === 0) return ''

    if (this.bytesNeeded === 0 && trailingIncomplete(data, 0) === 0) {
      this.bytesSeen = trailingBytesSeen(data)
      return b4a.toString(data, 'utf8')
    }

    let result = ''
    let start = 0

    if (this.bytesNeeded > 0) {
      while (start < data.byteLength) {
        const byte = data[start]

        if (byte < this.lowerBoundary || byte > this.upperBoundary) {
          result += '\ufffd'
          this._reset()
          break
        }

        this.lowerBoundary = 0x80
        this.upperBoundary = 0xbf
        this.codePoint = (this.codePoint << 6) | (byte & 0x3f)
        this.bytesSeen++
        start++

        if (this.bytesSeen === this.bytesNeeded) {
          result += String.fromCodePoint(this.codePoint)
          this._reset()
          break
        }
      }

      if (this.bytesNeeded > 0) return result
    }

    const trailing = trailingIncomplete(data, start)
    const end = data.byteLength - trailing

    if (end > start) result += b4a.toString(data, 'utf8', start, end)

    for (let i = end; i < data.byteLength; i++) {
      const byte = data[i]

      if (this.bytesNeeded === 0) {
        if (byte <= 0x7f) {
          this.bytesSeen = 0
          result += String.fromCharCode(byte)
        } else if (byte >= 0xc2 && byte <= 0xdf) {
          this.bytesNeeded = 2
          this.bytesSeen = 1
          this.codePoint = byte & 0x1f
        } else if (byte >= 0xe0 && byte <= 0xef) {
          if (byte === 0xe0) this.lowerBoundary = 0xa0
          else if (byte === 0xed) this.upperBoundary = 0x9f
          this.bytesNeeded = 3
          this.bytesSeen = 1
          this.codePoint = byte & 0xf
        } else if (byte >= 0xf0 && byte <= 0xf4) {
          if (byte === 0xf0) this.lowerBoundary = 0x90
          else if (byte === 0xf4) this.upperBoundary = 0x8f
          this.bytesNeeded = 4
          this.bytesSeen = 1
          this.codePoint = byte & 0x7
        } else {
          this.bytesSeen = 1
          result += '\ufffd'
        }

        continue
      }

      if (byte < this.lowerBoundary || byte > this.upperBoundary) {
        result += '\ufffd'
        i--
        this._reset()

        continue
      }

      this.lowerBoundary = 0x80
      this.upperBoundary = 0xbf

      this.codePoint = (this.codePoint << 6) | (byte & 0x3f)
      this.bytesSeen++

      if (this.bytesSeen === this.bytesNeeded) {
        result += String.fromCodePoint(this.codePoint)
        this._reset()
      }
    }

    return result
  }

  flush() {
    const result = this.bytesNeeded > 0 ? '\ufffd' : ''
    this._reset()
    return result
  }

  _reset() {
    this.codePoint = 0
    this.bytesNeeded = 0
    this.bytesSeen = 0
    this.lowerBoundary = 0x80
    this.upperBoundary = 0xbf
  }
}

function trailingIncomplete(data, start) {
  const len = data.byteLength
  if (len <= start) return 0

  const limit = Math.max(start, len - 4)

  let i = len - 1
  while (i > limit && (data[i] & 0xc0) === 0x80) i--

  if (i < start) return 0

  const byte = data[i]

  let needed
  if (byte <= 0x7f) return 0
  if (byte >= 0xc2 && byte <= 0xdf) needed = 2
  else if (byte >= 0xe0 && byte <= 0xef) needed = 3
  else if (byte >= 0xf0 && byte <= 0xf4) needed = 4
  else return 0

  const available = len - i
  return available < needed ? available : 0
}

function trailingBytesSeen(data) {
  const len = data.byteLength
  if (len === 0) return 0

  const last = data[len - 1]

  if (last <= 0x7f) return 0
  if ((last & 0xc0) !== 0x80) return 1

  const limit = Math.max(0, len - 4)

  let i = len - 2
  while (i >= limit && (data[i] & 0xc0) === 0x80) i--

  if (i < 0) return 1

  const first = data[i]

  let needed
  if (first >= 0xc2 && first <= 0xdf) needed = 2
  else if (first >= 0xe0 && first <= 0xef) needed = 3
  else if (first >= 0xf0 && first <= 0xf4) needed = 4
  else return 1

  if (len - i !== needed) return 1

  if (needed >= 3) {
    const second = data[i + 1]
    if (first === 0xe0 && second < 0xa0) return 1
    if (first === 0xed && second > 0x9f) return 1
    if (first === 0xf0 && second < 0x90) return 1
    if (first === 0xf4 && second > 0x8f) return 1
  }

  return 0
}
{
  "name": "text-decoder",
  "version": "1.2.7",
  "description": "Streaming text decoder that preserves multibyte Unicode characters",
  "exports": {
    "./package": "./package.json",
    ".": "./index.js"
  },
  "files": [
    "index.js",
    "lib"
  ],
  "scripts": {
    "format": "prettier --write .",
    "lint": "prettier --check . && lunte",
    "test": "npm run test:node && npm run test:bare",
    "test:node": "brittle-node test.js",
    "test:bare": "brittle-bare test.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/text-decoder.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/text-decoder/issues"
  },
  "homepage": "https://github.com/holepunchto/text-decoder#readme",
  "dependencies": {
    "b4a": "^1.6.4"
  },
  "devDependencies": {
    "brittle": "^3.3.2",
    "lunte": "^1.3.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^1.0.0"
  }
}
module.exports = class TimeOrderedSet {
  constructor () {
    this.oldest = null
    this.latest = null
    this.length = 0
  }

  has (node) {
    return !!(node.next || node.prev) || node === this.oldest
  }

  add (node) {
    if (this.has(node)) this.remove(node)

    if (!this.latest && !this.oldest) {
      this.latest = this.oldest = node
      node.prev = node.next = null
    } else {
      this.latest.next = node
      node.prev = this.latest
      node.next = null
      this.latest = node
    }

    this.length++

    return node
  }

  remove (node) {
    if (!this.has(node)) return node

    if (this.oldest !== node && this.latest !== node) {
      node.prev.next = node.next
      node.next.prev = node.prev
    } else {
      if (this.oldest === node) {
        this.oldest = node.next
        if (this.oldest) this.oldest.prev = null
      }
      if (this.latest === node) {
        this.latest = node.prev
        if (this.latest) this.latest.next = null
      }
    }

    node.next = node.prev = null
    this.length--

    return node
  }

  toArray ({ limit = Infinity, reverse = false } = {}) {
    const list = []

    if (reverse) {
      let node = this.latest
      while (node && limit--) {
        list.push(node)
        node = node.prev
      }
    } else {
      let node = this.oldest
      while (node && limit--) {
        list.push(node)
        node = node.next
      }
    }

    return list
  }
}
{
  "name": "time-ordered-set",
  "version": "2.0.1",
  "description": "Efficiently maintain a set of nodes ordered by the time they were added to the set",
  "main": "index.js",
  "files": [
    "index.js"
  ],
  "devDependencies": {
    "brittle": "^3.0.0",
    "standard": "^17.1.2"
  },
  "scripts": {
    "test": "standard && brittle test.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/mafintosh/time-ordered-set.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/mafintosh/time-ordered-set/issues"
  },
  "homepage": "https://github.com/mafintosh/time-ordered-set"
}
module.exports = class TimerBrowser {
  constructor (ms, fn, ctx = null, interval = false) {
    this.ms = ms
    this.ontimeout = fn
    this.context = ctx || null
    this.interval = interval
    this.done = false

    this._timer = interval
      ? setInterval(callInterval, ms, this)
      : setTimeout(callTimeout, ms, this)
  }

  unref () {}

  ref () {}

  refresh () {
    if (this.done) return

    if (this.interval) {
      clearInterval(this._timer)
      this._timer = setInterval(callInterval, this.ms, this)
    } else {
      clearTimeout(this._timer)
      this._timer = setTimeout(callTimeout, this.ms, this)
    }
  }

  destroy () {
    this.done = true
    this.ontimeout = null

    if (this.interval) clearInterval(this._timer)
    else clearTimeout(this._timer)
  }

  static once (ms, fn, ctx) {
    return new this(ms, fn, ctx, false)
  }

  static on (ms, fn, ctx) {
    return new this(ms, fn, ctx, true)
  }
}

function callTimeout (self) {
  self.done = true
  self.ontimeout.call(self.context)
}

function callInterval (self) {
  self.ontimeout.call(self.context)
}
module.exports = isNode()
  ? require('./node')
  : require('./browser')

function isNode () {
  const to = setTimeout(function () {}, 1000)
  clearTimeout(to)
  return !!to.refresh
}
module.exports = class Timer {
  constructor (ms, fn, ctx = null, interval = false) {
    this.ms = ms
    this.ontimeout = fn
    this.context = ctx
    this.interval = interval
    this.done = false

    this._timer = interval
      ? setInterval(callInterval, ms, this)
      : setTimeout(callTimeout, ms, this)
  }

  unref () {
    this._timer.unref()
  }

  ref () {
    this._timer.ref()
  }

  refresh () {
    if (this.done !== true) this._timer.refresh()
  }

  destroy () {
    this.done = true
    this.ontimeout = null
    if (this.interval) clearInterval(this._timer)
    else clearTimeout(this._timer)
  }

  static once (ms, fn, ctx) {
    return new this(ms, fn, ctx, false)
  }

  static on (ms, fn, ctx) {
    return new this(ms, fn, ctx, true)
  }
}

function callTimeout (self) {
  self.done = true
  self.ontimeout.call(self.context)
}

function callInterval (self) {
  self.ontimeout.call(self.context)
}
{
  "name": "timeout-refresh",
  "version": "2.0.1",
  "description": "Efficiently refresh a timer",
  "main": "index.js",
  "dependencies": {},
  "devDependencies": {
    "standard": "^16.0.4",
    "tape": "^5.5.2"
  },
  "browser": "./browser.js",
  "scripts": {
    "test": "standard && tape test.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/mafintosh/timeout-refresh.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/mafintosh/timeout-refresh/issues"
  },
  "homepage": "https://github.com/mafintosh/timeout-refresh"
}
require.addon = require('require-addon')

module.exports = require.addon('.', __filename)
// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.

const v4Seg = '(?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])'
const v4Str = `(${v4Seg}[.]){3}${v4Seg}`
const IPv4Pattern = new RegExp(`^${v4Str}$`)

const v6Seg = '(?:[0-9a-fA-F]{1,4})'
const IPv6Pattern = new RegExp(
  '^(' +
    `(?:${v6Seg}:){7}(?:${v6Seg}|:)|` +
    `(?:${v6Seg}:){6}(?:${v4Str}|:${v6Seg}|:)|` +
    `(?:${v6Seg}:){5}(?::${v4Str}|(:${v6Seg}){1,2}|:)|` +
    `(?:${v6Seg}:){4}(?:(:${v6Seg}){0,1}:${v4Str}|(:${v6Seg}){1,3}|:)|` +
    `(?:${v6Seg}:){3}(?:(:${v6Seg}){0,2}:${v4Str}|(:${v6Seg}){1,4}|:)|` +
    `(?:${v6Seg}:){2}(?:(:${v6Seg}){0,3}:${v4Str}|(:${v6Seg}){1,5}|:)|` +
    `(?:${v6Seg}:){1}(?:(:${v6Seg}){0,4}:${v4Str}|(:${v6Seg}){1,6}|:)|` +
    `(?::((?::${v6Seg}){0,5}:${v4Str}|(?::${v6Seg}){1,7}|:))` +
    ')(%[0-9a-zA-Z-.:]{1,})?$'
)

const isIPv4 = (exports.isIPv4 = function isIPv4(host) {
  return IPv4Pattern.test(host)
})

const isIPv6 = (exports.isIPv6 = function isIPv6(host) {
  return IPv6Pattern.test(host)
})

exports.isIP = function isIP(host) {
  if (isIPv4(host)) return 4
  if (isIPv6(host)) return 6
  return 0
}
const events = require('events')
const b4a = require('b4a')
const binding = require('../binding')

module.exports = class NetworkInterfaces extends events.EventEmitter {
  constructor(udx) {
    super()

    this._handle = b4a.alloc(binding.sizeof_udx_napi_interface_event_t)
    this._watching = false
    this._destroying = null

    binding.udx_napi_interface_event_init(
      udx._handle,
      this._handle,
      this,
      this._onevent,
      this._onclose
    )

    this.interfaces = binding.udx_napi_interface_event_get_addrs(this._handle)
  }

  _onclose() {
    this.emit('close')
  }

  _onevent() {
    this.interfaces = binding.udx_napi_interface_event_get_addrs(this._handle)

    this.emit('change', this.interfaces)
  }

  watch() {
    if (this._watching) return this
    this._watching = true

    binding.udx_napi_interface_event_start(this._handle)

    return this
  }

  unwatch() {
    if (!this._watching) return this
    this._watching = false

    binding.udx_napi_interface_event_stop(this._handle)

    return this
  }

  async destroy() {
    if (this._destroying) return this._destroying
    this._destroying = events.once(this, 'close')

    binding.udx_napi_interface_event_close(this._handle)

    return this._destroying
  }

  [Symbol.iterator]() {
    return this.interfaces[Symbol.iterator]()
  }
}
const events = require('events')
const b4a = require('b4a')
const binding = require('../binding')
const ip = require('./ip')

module.exports = class UDXSocket extends events.EventEmitter {
  constructor(udx, opts = {}) {
    super()

    this.udx = udx

    this._handle = b4a.alloc(binding.sizeof_udx_napi_socket_t)
    this._inited = false
    this._host = null
    this._family = 0
    this._ipv6Only = opts.ipv6Only === true
    this._reuseAddress = opts.reuseAddress === true
    this._port = 0
    this._reqs = []
    this._free = []
    this._closing = null
    this._closed = false

    this._view64 = new BigUint64Array(
      this._handle.buffer,
      this._handle.byteOffset,
      this._handle.byteLength >> 3
    )

    this.streams = new Set()

    this.userData = null
  }

  get bound() {
    return this._port !== 0
  }

  get closing() {
    return this._closing !== null
  }

  get idle() {
    return this.streams.size === 0
  }

  get busy() {
    return this.streams.size > 0
  }

  get bytesTransmitted() {
    if (this._inited !== true) return 0
    return Number(this._view64[binding.offsetof_udx_socket_t_bytes_tx >> 3])
  }

  get packetsTransmitted() {
    if (this._inited !== true) return 0
    return Number(this._view64[binding.offsetof_udx_socket_t_packets_tx >> 3])
  }

  get bytesReceived() {
    if (this._inited !== true) return 0
    return Number(this._view64[binding.offsetof_udx_socket_t_bytes_rx >> 3])
  }

  get packetsReceived() {
    if (this._inited !== true) return 0
    return Number(this._view64[binding.offsetof_udx_socket_t_packets_rx >> 3])
  }

  get packetsDroppedByKernel() {
    if (this._inited !== true) return 0
    return Number(this._view64[binding.offsetof_udx_socket_t_packets_dropped_by_kernel >> 3])
  }

  toJSON() {
    return {
      bound: this.bound,
      closing: this.closing,
      streams: this.streams.size,
      address: this.address(),
      ipv6Only: this._ipv6Only,
      reuseAddress: this._reuseAddress,
      idle: this.idle,
      busy: this.busy
    }
  }

  _init() {
    if (this._inited) return

    binding.udx_napi_socket_init(
      this.udx._handle,
      this._handle,
      this,
      this._onsend,
      this._onmessage,
      this._onclose,
      this._reallocMessage
    )

    this._inited = true
  }

  _onsend(id, err) {
    const req = this._reqs[id]

    const onflush = req.onflush

    req.buffer = null
    req.onflush = null

    this._free.push(id)

    onflush(err >= 0)

    // gc the free list
    if (this._free.length >= 16 && this._free.length === this._reqs.length) {
      this._free = []
      this._reqs = []
    }
  }

  _onmessage(len, port, host, family) {
    this.emit('message', this.udx._consumeMessage(len), { host, family, port })
    return this.udx._buffer
  }

  _onclose() {
    this.emit('close')
  }

  _reallocMessage() {
    return this.udx._reallocMessage()
  }

  _onidle() {
    this.emit('idle')
  }

  _onbusy() {
    this.emit('busy')
  }

  _addStream(stream) {
    if (this.streams.has(stream)) return false
    this.streams.add(stream)
    if (this.streams.size === 1) this._onbusy()
    return true
  }

  _removeStream(stream) {
    if (!this.streams.has(stream)) return false
    this.streams.delete(stream)
    const closed = this._closeMaybe()
    if (this.idle && !closed) this._onidle()
    return true
  }

  address() {
    if (!this.bound) return null
    return { host: this._host, family: this._family, port: this._port }
  }

  bind(port, host) {
    if (this.bound) throw new Error('Already bound')
    if (this.closing) throw new Error('Socket is closed')

    if (!port) port = 0

    let flags = 0
    if (this._ipv6Only) flags |= binding.UV_UDP_IPV6ONLY
    if (this._reuseAddress) flags |= binding.UV_UDP_REUSEADDR

    let family

    if (host) {
      family = ip.isIP(host)
      if (!family) throw new Error(`${host} is not a valid IP address`)

      if (!this._inited) this._init()

      this._port = binding.udx_napi_socket_bind(this._handle, port, host, family, flags)
    } else {
      if (!this._inited) this._init()

      try {
        host = '::'
        family = 6
        this._port = binding.udx_napi_socket_bind(this._handle, port, host, family, flags)
      } catch {
        host = '0.0.0.0'
        family = 4
        this._port = binding.udx_napi_socket_bind(this._handle, port, host, family, flags)
      }
    }

    this._host = host
    this._family = family

    this.emit('listening')
  }

  async close() {
    if (this._closing) return this._closing
    this._closing = new Promise((resolve) => this.once('close', resolve))
    this._closeMaybe()
    return this._closing
  }

  _closeMaybe() {
    if (this._closed || this._closing === null) return this._closed

    if (!this._inited) {
      this._closed = true
      this.emit('close')
      return true
    }

    if (this.idle) {
      binding.udx_napi_socket_close(this._handle)
      this._closed = true
    }

    return this._closed
  }

  setTTL(ttl) {
    if (!this._inited) throw new Error('Socket not active')
    binding.udx_napi_socket_set_ttl(this._handle, ttl)
  }

  getRecvBufferSize() {
    if (!this._inited) throw new Error('Socket not active')
    return binding.udx_napi_socket_get_recv_buffer_size(this._handle)
  }

  setRecvBufferSize(size) {
    if (!this._inited) throw new Error('Socket not active')
    return binding.udx_napi_socket_set_recv_buffer_size(this._handle, size)
  }

  getSendBufferSize() {
    if (!this._inited) throw new Error('Socket not active')
    return binding.udx_napi_socket_get_send_buffer_size(this._handle)
  }

  setSendBufferSize(size) {
    if (!this._inited) throw new Error('Socket not active')
    return binding.udx_napi_socket_set_send_buffer_size(this._handle, size)
  }

  addMembership(group, ifaceAddress) {
    if (!this._inited) throw new Error('Socket not active')
    return binding.udx_napi_socket_set_membership(this._handle, group, ifaceAddress || '', true)
  }

  dropMembership(group, ifaceAddress) {
    if (!this._inited) throw new Error('Socket not active')
    return binding.udx_napi_socket_set_membership(this._handle, group, ifaceAddress || '', false)
  }

  async send(buffer, port, host, ttl) {
    if (this.closing) return false

    if (!host) host = '127.0.0.1'

    const family = ip.isIP(host)
    if (!family) throw new Error(`${host} is not a valid IP address`)

    if (!this.bound) this.bind(0)

    const id = this._allocSend()
    const req = this._reqs[id]

    req.buffer = buffer

    const promise = new Promise((resolve) => {
      req.onflush = resolve
    })

    binding.udx_napi_socket_send_ttl(
      this._handle,
      req.handle,
      id,
      buffer,
      port,
      host,
      family,
      ttl || 0
    )

    return promise
  }

  trySend(buffer, port, host, ttl) {
    if (this.closing) return

    if (!host) host = '127.0.0.1'

    const family = ip.isIP(host)
    if (!family) throw new Error(`${host} is not a valid IP address`)

    if (!this.bound) this.bind(0)

    const id = this._allocSend()
    const req = this._reqs[id]

    req.buffer = buffer
    req.onflush = noop

    binding.udx_napi_socket_send_ttl(
      this._handle,
      req.handle,
      id,
      buffer,
      port,
      host,
      family,
      ttl || 0
    )
  }

  _allocSend() {
    if (this._free.length > 0) return this._free.pop()
    const handle = b4a.allocUnsafe(binding.sizeof_udx_socket_send_t)
    return this._reqs.push({ handle, buffer: null, onflush: null }) - 1
  }
}

function noop() {}
const streamx = require('streamx')
const b4a = require('b4a')
const binding = require('../binding')
const ip = require('./ip')

const MAX_PACKET = 2048
const BUFFER_SIZE = 65536 + MAX_PACKET

module.exports = class UDXStream extends streamx.Duplex {
  constructor(udx, id, opts = {}) {
    super({ mapWritable: toBuffer, eagerOpen: true })

    this.udx = udx
    this.socket = null

    this._handle = b4a.alloc(binding.sizeof_udx_napi_stream_t)
    this._view = new Uint32Array(
      this._handle.buffer,
      this._handle.byteOffset,
      this._handle.byteLength >> 2
    )
    this._view16 = new Uint16Array(
      this._handle.buffer,
      this._handle.byteOffset,
      this._handle.byteLength >> 1
    )
    this._view64 = new BigUint64Array(
      this._handle.buffer,
      this._handle.byteOffset,
      this._handle.byteLength >> 3
    )

    this._wreqs = []
    this._wfree = []

    this._sreqs = []
    this._sfree = []
    this._closed = false

    this._flushing = 0
    this._flushes = []

    this._buffer = null
    this._reallocData()

    this._onwrite = null
    this._ondestroy = null
    this._firewall = opts.firewall || firewallAll

    this._remoteChanging = null
    this._previousSocket = null

    this.id = id
    this.remoteId = 0
    this.remoteHost = null
    this.remoteFamily = 0
    this.remotePort = 0

    this.userData = null

    binding.udx_napi_stream_init(
      this.udx._handle,
      this._handle,
      id,
      opts.framed ? 1 : 0,
      this,
      this._ondata,
      this._onend,
      this._ondrain,
      this._onack,
      this._onsend,
      this._onmessage,
      this._onclose,
      this._onfirewall,
      this._onremotechanged,
      this._reallocData,
      this._reallocMessage
    )

    if (opts.seq) binding.udx_napi_stream_set_seq(this._handle, opts.seq)

    binding.udx_napi_stream_recv_start(this._handle, this._buffer)
  }

  get connected() {
    return this.socket !== null
  }

  get mtu() {
    return this._view16[binding.offsetof_udx_stream_t_mtu >> 1]
  }

  get rtt() {
    return this._view[binding.offsetof_udx_stream_t_srtt >> 2]
  }

  get cwnd() {
    return this._view[binding.offsetof_udx_stream_t_cwnd >> 2]
  }

  get rtoCount() {
    return this._view16[binding.offsetof_udx_stream_t_rto_count >> 1]
  }

  get retransmits() {
    return this._view16[binding.offsetof_udx_stream_t_retransmit_count >> 1]
  }

  get bbrState() {
    const view = new Uint8Array(
      this._handle.buffer,
      this._handle.byteOffset,
      this._handle.byteLength
    )
    return view[binding.offsetof_udx_stream_t_bbr_state]
  }

  get bbrBandwidth() {
    return binding.udx_napi_stream_get_bw(this._handle)
  }

  get fastRecoveries() {
    return this._view16[binding.offsetof_udx_stream_t_fast_recovery_count >> 1]
  }

  get inflight() {
    return this._view[binding.offsetof_udx_stream_t_inflight >> 2]
  }

  get bytesTransmitted() {
    return Number(this._view64[binding.offsetof_udx_stream_t_bytes_tx >> 3])
  }

  get packetsTransmitted() {
    return Number(this._view64[binding.offsetof_udx_stream_t_packets_tx >> 3])
  }

  get bytesReceived() {
    return Number(this._view64[binding.offsetof_udx_stream_t_bytes_rx >> 3])
  }

  get packetsReceived() {
    return Number(this._view64[binding.offsetof_udx_stream_t_packets_rx >> 3])
  }

  get localHost() {
    return this.socket ? this.socket.address().host : null
  }

  get localFamily() {
    return this.socket ? this.socket.address().family : 0
  }

  get localPort() {
    return this.socket ? this.socket.address().port : 0
  }

  setInteractive(bool) {
    if (!this._closed) return
    binding.udx_napi_stream_set_mode(this._handle, bool ? 0 : 1)
  }

  connect(socket, remoteId, port, host, opts = {}) {
    if (this._closed) return

    if (this.connected) throw new Error('Already connected')
    if (socket.closing) throw new Error('Socket is closed')

    if (typeof host === 'object') {
      opts = host
      host = null
    }

    if (!host) host = '127.0.0.1'

    const family = ip.isIP(host)
    if (!family) throw new Error(`${host} is not a valid IP address`)
    if (!(port > 0 && port < 65536)) throw new Error(`${port} is not a valid port`)

    if (!socket.bound) socket.bind(0)

    this.remoteId = remoteId
    this.remotePort = port
    this.remoteHost = host
    this.remoteFamily = family
    this.socket = socket

    if (opts.ack) binding.udx_napi_stream_set_ack(this._handle, opts.ack)

    binding.udx_napi_stream_connect(this._handle, socket._handle, remoteId, port, host, family)

    this.socket._addStream(this)

    this.emit('connect')
  }

  changeRemote(socket, remoteId, port, host) {
    if (this._remoteChanging) throw new Error('Remote already changing')

    if (!this.connected) throw new Error('Not yet connected')
    if (socket.closing) throw new Error('Socket is closed')

    if (this.socket.udx !== socket.udx) {
      throw new Error('Cannot change to a socket on another UDX instance')
    }

    if (!host) host = '127.0.0.1'

    const family = ip.isIP(host)
    if (!family) throw new Error(`${host} is not a valid IP address`)
    if (!(port > 0 && port < 65536)) throw new Error(`${port} is not a valid port`)

    if (this.socket !== socket) this._previousSocket = this.socket

    this.remoteId = remoteId
    this.remotePort = port
    this.remoteHost = host
    this.remoteFamily = family
    this.socket = socket

    this._remoteChanging = new Promise((resolve, reject) => {
      const onchanged = () => {
        this.off('close', onclose)
        resolve()
      }

      const onclose = () => {
        this.off('remote-changed', onchanged)
        reject(new Error('Stream is closed'))
      }

      this.once('remote-changed', onchanged).once('close', onclose)
    })

    binding.udx_napi_stream_change_remote(
      this._handle,
      socket._handle,
      remoteId,
      port,
      host,
      family
    )

    this.socket._addStream(this)

    return this._remoteChanging
  }

  relayTo(destination) {
    if (this._closed) return

    binding.udx_napi_stream_relay_to(this._handle, destination._handle)
  }

  async send(buffer) {
    if (!this.connected || this._closed) return false

    const id = this._allocSend()
    const req = this._sreqs[id]

    req.buffer = buffer

    const promise = new Promise((resolve) => {
      req.onflush = resolve
    })

    binding.udx_napi_stream_send(this._handle, req.handle, id, buffer)

    return promise
  }

  trySend(buffer) {
    if (!this.connected || this._closed) return

    const id = this._allocSend()
    const req = this._sreqs[id]

    req.buffer = buffer
    req.onflush = noop

    binding.udx_napi_stream_send(this._handle, req.handle, id, buffer)
  }

  async flush() {
    if ((await streamx.Writable.drained(this)) === false) return false
    if (this.destroying) return false

    const missing = this._wreqs.length - this._wfree.length
    if (missing === 0) return true

    return new Promise((resolve) => {
      this._flushes.push({ flush: this._flushing++, missing, resolve })
    })
  }

  toJSON() {
    return {
      id: this.id,
      connected: this.connected,
      destroying: this.destroying,
      destroyed: this.destroyed,
      remoteId: this.remoteId,
      remoteHost: this.remoteHost,
      remoteFamily: this.remoteFamily,
      remotePort: this.remotePort,
      mtu: this.mtu,
      rtt: this.rtt,
      cwnd: this.cwnd,
      inflight: this.inflight,
      socket: this.socket ? this.socket.toJSON() : null
    }
  }

  _read(cb) {
    cb(null)
  }

  _writeContinue(err) {
    if (this._onwrite === null) return
    const cb = this._onwrite
    this._onwrite = null
    cb(err)
  }

  _destroyContinue(err) {
    if (this._ondestroy === null) return
    const cb = this._ondestroy
    this._ondestroy = null
    cb(err)
  }

  _writev(buffers, cb) {
    if (!this.connected) {
      throw customError('Writing while not connected not currently supported', 'ERR_ASSERTION')
    }

    let drained = true

    if (buffers.length === 1) {
      const id = this._allocWrite(1)
      const req = this._wreqs[id]

      req.flush = this._flushing
      req.buffer = buffers[0]

      drained = binding.udx_napi_stream_write(this._handle, req.handle, id, req.buffer) !== 0
    } else {
      const id = this._allocWrite(nextBatchSize(buffers.length))
      const req = this._wreqs[id]

      req.flush = this._flushing
      req.buffers = buffers

      drained = binding.udx_napi_stream_writev(this._handle, req.handle, id, req.buffers) !== 0
    }

    if (drained) cb(null)
    else this._onwrite = cb
  }

  _final(cb) {
    const id = this._allocWrite(1)
    const req = this._wreqs[id]

    req.flush = this._flushes
    req.buffer = b4a.allocUnsafe(0)

    const drained =
      binding.udx_napi_stream_write_end(this._handle, req.handle, id, req.buffer) !== 0

    if (drained) cb(null)
    else this._onwrite = cb
  }

  _predestroy() {
    if (!this._closed) binding.udx_napi_stream_destroy(this._handle)
    this._closed = true
    this._writeContinue(null)
  }

  _destroy(cb) {
    if (this.connected) this._ondestroy = cb
    else cb(null)
  }

  _ondata(read) {
    this.push(this._consumeData(read))
    return this._buffer
  }

  _onend(read) {
    if (read > 0) this.push(this._consumeData(read))
    this.push(null)
  }

  _ondrain() {
    this._writeContinue(null)
  }

  _flushAck(flush) {
    for (let i = this._flushes.length - 1; i >= 0; i--) {
      const f = this._flushes[i]
      if (f.flush < flush) break
      f.missing--
    }

    while (this._flushes.length > 0 && this._flushes[0].missing === 0) {
      this._flushes.shift().resolve(true)
    }
  }

  _onack(id) {
    const req = this._wreqs[id]

    req.buffers = req.buffer = null
    this._wfree.push(id)

    if (this._flushes.length > 0) this._flushAck(req.flush)

    // gc the free list
    if (this._wfree.length >= 64 && this._wfree.length === this._wreqs.length) {
      this._wfree = []
      this._wreqs = []
    }
  }

  _onsend(id, err) {
    const req = this._sreqs[id]

    const onflush = req.onflush

    req.buffer = null
    req.onflush = null

    this._sfree.push(id)

    onflush(err >= 0)

    // gc the free list
    if (this._sfree.length >= 16 && this._sfree.length === this._sreqs.length) {
      this._sfree = []
      this._sreqs = []
    }
  }

  _onmessage(len) {
    this.emit('message', this.udx._consumeMessage(len))
    return this.udx._buffer
  }

  _onclose(err) {
    this._closed = true

    if (this.socket) {
      this.socket._removeStream(this)
      this.socket = null
    }

    if (this._previousSocket) {
      this._previousSocket._removeStream(this)
      this._previousSocket = null
    }

    // no error, we don't need to do anything
    if (!err) return this._destroyContinue(null)

    if (this._ondestroy === null) this.destroy(err)
    else this._destroyContinue(err)
  }

  _onfirewall(socket, port, host, family) {
    if (port <= 0 || port > 65535) return 1
    return this._firewall(socket, port, host, family) ? 1 : 0
  }

  _onremotechanged() {
    if (this._previousSocket) {
      this._previousSocket._removeStream(this)
      this._previousSocket = null
    }

    this._remoteChanging = null
    this.emit('remote-changed')
  }

  _consumeData(len) {
    const next = this._buffer.subarray(0, len)
    this._buffer = this._buffer.subarray(len)
    if (this._buffer.byteLength < MAX_PACKET) this._reallocData()
    return next
  }

  _reallocData() {
    this._buffer = b4a.allocUnsafe(BUFFER_SIZE)
    return this._buffer
  }

  _reallocMessage() {
    return this.udx._reallocMessage()
  }

  _allocWrite(size) {
    if (this._wfree.length === 0) {
      const handle = b4a.allocUnsafe(binding.udx_napi_stream_write_sizeof(size))
      return (
        this._wreqs.push({
          handle,
          size,
          buffers: null,
          buffer: null,
          flush: 0
        }) - 1
      )
    }

    const free = this._wfree.pop()
    if (size === 1) return free

    const next = this._wreqs[free]
    if (next.size < size) {
      next.handle = b4a.allocUnsafe(binding.udx_napi_stream_write_sizeof(size))
      next.size = size
    }

    return free
  }

  _allocSend() {
    if (this._sfree.length > 0) return this._sfree.pop()
    const handle = b4a.allocUnsafe(binding.sizeof_udx_stream_send_t)
    return this._sreqs.push({ handle, buffer: null, resolve: null, reject: null }) - 1
  }
}

function noop() {}

function toBuffer(data) {
  return typeof data === 'string' ? b4a.from(data) : data
}

function firewallAll(socket, port, host) {
  return true
}

function customError(message, code) {
  const error = new Error(message)
  error.code = code
  return error
}

function nextBatchSize(n) {
  // try to coerce the the writevs into sameish size
  if (n === 1) return 1
  // group all < 8 to the same size, low mem overhead but save some small allocs
  if (n < 8) return 8
  if (n < 16) return 16
  if (n < 32) return 32
  if (n < 64) return 64
  return n
}
const b4a = require('b4a')
const binding = require('../binding')
const ip = require('./ip')
const Socket = require('./socket')
const Stream = require('./stream')
const NetworkInterfaces = require('./network-interfaces')

const MAX_MESSAGE = 4096
const BUFFER_SIZE = 65536 + MAX_MESSAGE

module.exports = class UDX {
  constructor() {
    this._handle = b4a.alloc(binding.sizeof_udx_napi_t)
    this._watchers = new Set()
    this._view64 = new BigUint64Array(
      this._handle.buffer,
      this._handle.byteOffset,
      this._handle.byteLength >> 3
    )

    this._buffer = null
    this._reallocMessage()

    binding.udx_napi_init(this._handle, this._buffer)
  }

  static isIPv4(host) {
    return ip.isIPv4(host)
  }

  static isIPv6(host) {
    return ip.isIPv6(host)
  }

  static isIP(host) {
    return ip.isIP(host)
  }

  get bytesTransmitted() {
    return Number(this._view64[binding.offsetof_udx_t_bytes_tx >> 3])
  }

  get packetsTransmitted() {
    return Number(this._view64[binding.offsetof_udx_t_packets_tx >> 3])
  }

  get bytesReceived() {
    return Number(this._view64[binding.offsetof_udx_t_bytes_rx >> 3])
  }

  get packetsReceived() {
    return Number(this._view64[binding.offsetof_udx_t_packets_rx >> 3])
  }

  get packetsDroppedByKernel() {
    return Number(this._view64[binding.offsetof_udx_t_packets_dropped_by_kernel >> 3])
  }

  _consumeMessage(len) {
    const next = this._buffer.subarray(0, len)
    this._buffer = this._buffer.subarray(len)
    if (this._buffer.byteLength < MAX_MESSAGE) this._reallocMessage()
    return next
  }

  _reallocMessage() {
    // TODO: move reallocation to native
    this._buffer = b4a.allocUnsafe(BUFFER_SIZE)
    return this._buffer
  }

  createSocket(opts) {
    return new Socket(this, opts)
  }

  createStream(id, opts) {
    return new Stream(this, id, opts)
  }

  networkInterfaces() {
    let [watcher = null] = this._watchers
    if (watcher) return watcher.interfaces

    watcher = new NetworkInterfaces(this)
    watcher.destroy()

    return watcher.interfaces
  }

  watchNetworkInterfaces(onchange) {
    const watcher = new NetworkInterfaces(this)

    this._watchers.add(watcher)
    watcher.on('close', () => {
      this._watchers.delete(watcher)
    })

    if (onchange) watcher.on('change', onchange)

    return watcher.watch()
  }

  async lookup(host, opts = {}) {
    const { family = 0 } = opts

    const req = b4a.alloc(binding.sizeof_udx_napi_lookup_t)
    const ctx = {
      req,
      resolve: null,
      reject: null
    }

    const promise = new Promise((resolve, reject) => {
      ctx.resolve = resolve
      ctx.reject = reject
    })

    binding.udx_napi_lookup(this._handle, req, host, family, ctx, onlookup)

    return promise
  }
}

function onlookup(err, host, family) {
  if (err) this.reject(err)
  else this.resolve({ host, family })
}
{
  "name": "udx-native",
  "version": "1.21.1",
  "description": "udx is reliable, multiplexed, and congestion-controlled streams over udp",
  "main": "lib/udx.js",
  "files": [
    "lib",
    "prebuilds",
    "binding.cc",
    "binding.js",
    "CMakeLists.txt"
  ],
  "imports": {
    "events": {
      "bare": "bare-events",
      "default": "events"
    }
  },
  "addon": true,
  "scripts": {
    "format": "prettier --write .",
    "lint": "prettier --check . && lunte",
    "test": "npm run test:node && npm run test:bare",
    "test:node": "brittle-node --coverage test/*.js",
    "test:bare": "brittle-bare --coverage test/*.js",
    "test:all": "brittle test/*.js test/slow/*.js",
    "test:generate": "brittle -r test/all.js test/*.js",
    "bench": "brittle test/bench/*.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/holepunchto/udx-native.git"
  },
  "keywords": [
    "tcp",
    "udp",
    "stream",
    "reliable"
  ],
  "author": "Holepunch Inc.",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/udx-native/issues"
  },
  "homepage": "https://github.com/holepunchto/udx-native#readme",
  "engines": {
    "bare": ">=1.17.4"
  },
  "dependencies": {
    "b4a": "^1.5.0",
    "bare-events": "^2.2.0",
    "require-addon": "^1.1.0",
    "streamx": "^2.22.0"
  },
  "devDependencies": {
    "bare-compat-napi": "^1.3.0",
    "brittle": "^3.1.0",
    "cmake-bare": "^1.1.10",
    "cmake-fetch": "^1.0.1",
    "cmake-napi": "^1.0.5",
    "is-ci": "^3.0.1",
    "lunte": "^1.3.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^2.0.0",
    "tiny-byte-size": "^1.1.0"
  }
}
exports.add = add
exports.has = has
exports.remove = remove
exports.swap = swap

function add (list, item) {
  if (has(list, item)) return item
  item._index = list.length
  list.push(item)
  return item
}

function has (list, item) {
  return item._index < list.length && list[item._index] === item
}

function remove (list, item) {
  if (!has(list, item)) return null

  var last = list.pop()
  if (last !== item) {
    list[item._index] = last
    last._index = item._index
  }

  return item
}

function swap (list, a, b) {
  if (!has(list, a) || !has(list, b)) return
  var tmp = a._index
  a._index = b._index
  list[a._index] = a
  b._index = tmp
  list[b._index] = b
}
{
  "name": "unordered-set",
  "version": "2.0.1",
  "description": "A couple of functions that make it easy to maintain an unordered set as an array in an efficient way",
  "main": "index.js",
  "dependencies": {},
  "devDependencies": {
    "standard": "^6.0.4",
    "tape": "^4.4.0"
  },
  "scripts": {
    "test": "standard && tape test.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/mafintosh/unordered-set.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/mafintosh/unordered-set/issues"
  },
  "homepage": "https://github.com/mafintosh/unordered-set"
}
const b4a = require('b4a')

unslab.all = all
unslab.is = is

module.exports = unslab

function unslab (buf) {
  if (buf === null || buf.buffer.byteLength === buf.byteLength) return buf
  const copy = b4a.allocUnsafeSlow(buf.byteLength)
  copy.set(buf, 0)
  return copy
}

function is (buf) {
  return buf.buffer.byteLength !== buf.byteLength
}

function all (list) {
  let size = 0
  for (let i = 0; i < list.length; i++) {
    const buf = list[i]
    size += buf === null || buf.buffer.byteLength === buf.byteLength ? 0 : buf.byteLength
  }

  const copy = b4a.allocUnsafeSlow(size)
  const result = new Array(list.length)

  let offset = 0
  for (let i = 0; i < list.length; i++) {
    let buf = list[i]

    if (buf !== null && buf.buffer.byteLength !== buf.byteLength) {
      copy.set(buf, offset)
      buf = copy.subarray(offset, offset += buf.byteLength)
    }

    result[i] = buf
  }

  return result
}
{
  "name": "unslab",
  "version": "1.3.0",
  "description": "Unslab some slab'ed buffers",
  "main": "index.js",
  "files": [
    "index.js"
  ],
  "dependencies": {
    "b4a": "^1.6.6"
  },
  "devDependencies": {
    "brittle": "^3.5.2",
    "standard": "^17.1.0"
  },
  "scripts": {
    "test": "standard && brittle test.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/holepunchto/unslab.git"
  },
  "author": "Holepunch",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/unslab/issues"
  },
  "homepage": "https://github.com/holepunchto/unslab"
}
const { runtime, platform, arch, version } =
  typeof Bare !== 'undefined'
    ? {
        runtime: 'bare',
        platform: global.Bare.platform,
        arch: global.Bare.arch,
        version: global.Bare.version
      }
    : typeof process !== 'undefined'
      ? {
          runtime: 'node',
          platform: global.process.platform,
          arch: global.process.arch,
          version: global.process.version
        }
      : typeof Window !== 'undefined'
        ? { runtime: 'browser', platform: 'unknown', arch: 'unknown', version: 'unknown' }
        : { runtime: 'unknown', platform: 'unknown', arch: 'unknown', version: 'unknown' }

exports.runtime = runtime
exports.version = version
exports.platform = platform
exports.arch = arch
exports.isBare = runtime === 'bare'
exports.isBareKit = exports.isBare && typeof BareKit !== 'undefined'
exports.isPear = typeof Pear !== 'undefined'
exports.isNode = runtime === 'node'
exports.isBrowser = runtime === 'browser'
exports.isWindows = platform === 'win32'
exports.isLinux = platform === 'linux'
exports.isMac = platform === 'darwin'
exports.isIOS = platform === 'ios' || platform === 'ios-simulator'
exports.isAndroid = platform === 'android'
exports.isElectron = typeof process !== 'undefined' && !!global.process.versions?.electron
exports.isElectronRenderer = exports.isElectron && global.process.type === 'renderer'
exports.isElectronWorker = exports.isElectron && global.process.type === 'worker'
{
  "name": "which-runtime",
  "version": "1.4.0",
  "description": "Detect if you are in Bare or Node and which os etc",
  "main": "index.js",
  "files": [
    "index.js"
  ],
  "scripts": {
    "format": "prettier --write .",
    "lint": "prettier --check . && lunte .",
    "test": "npm run test:node && npm run test:bare",
    "test:node": "brittle-node test.js",
    "test:bare": "brittle-bare test.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/holepunchto/which-runtime.git"
  },
  "author": "Holepunch Inc.",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/holepunchto/which-runtime/issues"
  },
  "homepage": "https://github.com/holepunchto/which-runtime",
  "devDependencies": {
    "brittle": "^3.0.0",
    "lunte": "^1.4.0",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^2.0.0"
  }
}
module.exports = class MaxCache {
  constructor({ maxSize, maxAge, createMap, ongc }) {
    this.maxSize = maxSize
    this.maxAge = maxAge
    this.ongc = ongc || null

    this._createMap = createMap || defaultCreateMap
    this._latest = this._createMap()
    this._oldest = this._createMap()
    this._retained = this._createMap()
    this._gced = false
    this._interval = null

    if (this.maxAge > 0 && this.maxAge < Infinity) {
      const tick = Math.ceil((2 / 3) * this.maxAge)
      this._interval = setInterval(this._gcAuto.bind(this), tick)
      if (this._interval.unref) this._interval.unref()
    }
  }

  get size() {
    return this._latest.size + this._oldest.size + this._retained.size
  }

  *[Symbol.iterator]() {
    for (const it of [this._latest, this._oldest, this._retained]) {
      yield* it
    }
  }

  *keys() {
    for (const it of [this._latest, this._oldest, this._retained]) {
      yield* it.keys()
    }
  }

  *values() {
    for (const it of [this._latest, this._oldest, this._retained]) {
      yield* it.values()
    }
  }

  destroy() {
    this.clear()
    clearInterval(this._interval)
    this._interval = null
  }

  clear() {
    this._gced = true
    this._latest.clear()
    this._oldest.clear()
    this._retained.clear()
  }

  set(k, v) {
    if (this._retained.has(k)) return this
    this._latest.set(k, v)
    this._oldest.delete(k) || this._retained.delete(k)
    if (this._latest.size >= this.maxSize) this._gc()
    return this
  }

  retain(k, v) {
    this._retained.set(k, v)
    this._latest.delete(k) || this._oldest.delete(k)
    return this
  }

  delete(k) {
    return (
      this._latest.delete(k) ||
      this._oldest.delete(k) ||
      this._retained.delete(k)
    )
  }

  has(k) {
    return this._latest.has(k) || this._oldest.has(k) || this._retained.has(k)
  }

  get(k) {
    if (this._latest.has(k)) {
      return this._latest.get(k)
    }

    if (this._oldest.has(k)) {
      const v = this._oldest.get(k)
      this._latest.set(k, v)
      this._oldest.delete(k)
      return v
    }

    if (this._retained.has(k)) {
      return this._retained.get(k)
    }

    return null
  }

  _gcAuto() {
    if (!this._gced) this._gc()
    this._gced = false
  }

  _gc() {
    this._gced = true
    if (this.ongc !== null && this._oldest.size > 0) this.ongc(this._oldest)
    this._oldest = this._latest
    this._latest = this._createMap()
  }
}

function defaultCreateMap() {
  return new Map()
}
{
  "name": "xache",
  "version": "1.3.0",
  "description": "Yet another auto expiring, max sizable cache",
  "main": "index.js",
  "files": [
    "index.js"
  ],
  "devDependencies": {
    "brittle": "^3.3.2",
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^1.0.0"
  },
  "scripts": {
    "format": "prettier --write .",
    "test": "prettier --check . && brittle test.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/mafintosh/xache.git"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/mafintosh/xache/issues"
  },
  "homepage": "https://github.com/mafintosh/xache"
}
const b4a = require('b4a')

const ALPHABET = 'ybndrfg8ejkmcpqxot1uwisza345h769'
const MIN = 0x31 // 1
const MAX = 0x7a // z
const REVERSE = new Int8Array(1 + MAX - MIN)

REVERSE.fill(-1)

for (let i = 0; i < ALPHABET.length; i++) {
  const v = ALPHABET.charCodeAt(i) - MIN
  REVERSE[v] = i
}

exports.encode = encode
exports.decode = decode
exports.ALPHABET = ALPHABET

function decode (s, out) {
  let pb = 0
  let ps = 0

  const r = s.length & 7
  const q = (s.length - r) / 8

  if (!out) out = b4a.allocUnsafe(Math.ceil(s.length * 5 / 8))

  // 0 5 2 7 4 1 6 3 (+5 mod 8)
  for (let i = 0; i < q; i++) {
    const a = quintet(s, ps++)
    const b = quintet(s, ps++)
    const c = quintet(s, ps++)
    const d = quintet(s, ps++)
    const e = quintet(s, ps++)
    const f = quintet(s, ps++)
    const g = quintet(s, ps++)
    const h = quintet(s, ps++)

    out[pb++] = (a << 3) | (b >>> 2)
    out[pb++] = ((b & 0b11) << 6) | (c << 1) | (d >>> 4)
    out[pb++] = ((d & 0b1111) << 4) | (e >>> 1)
    out[pb++] = ((e & 0b1) << 7) | (f << 2) | (g >>> 3)
    out[pb++] = ((g & 0b111) << 5) | h
  }

  if (r === 0) return out.subarray(0, pb)

  const a = quintet(s, ps++)
  const b = quintet(s, ps++)

  out[pb++] = (a << 3) | (b >>> 2)

  if (r <= 2) return out.subarray(0, pb)

  const c = quintet(s, ps++)
  const d = quintet(s, ps++)

  out[pb++] = ((b & 0b11) << 6) | (c << 1) | (d >>> 4)

  if (r <= 4) return out.subarray(0, pb)

  const e = quintet(s, ps++)

  out[pb++] = ((d & 0b1111) << 4) | (e >>> 1)

  if (r <= 5) return out.subarray(0, pb)

  const f = quintet(s, ps++)
  const g = quintet(s, ps++)

  out[pb++] = ((e & 0b1) << 7) | (f << 2) | (g >>> 3)

  if (r <= 7) return out.subarray(0, pb)

  const h = quintet(s, ps++)

  out[pb++] = ((g & 0b111) << 5) | h

  return out.subarray(0, pb)
}

function encode (buf) {
  if (typeof buf === 'string') buf = b4a.from(buf)

  const max = buf.byteLength * 8

  let s = ''

  for (let p = 0; p < max; p += 5) {
    const i = p >>> 3
    const j = p & 7

    if (j <= 3) {
      s += ALPHABET[(buf[i] >>> (3 - j)) & 0b11111]
      continue
    }

    const of = j - 3
    const h = (buf[i] << of) & 0b11111
    const l = (i >= buf.byteLength ? 0 : buf[i + 1]) >>> (8 - of)

    s += ALPHABET[h | l]
  }

  return s
}

function quintet (s, i) {
  if (i > s.length) {
    return 0
  }

  const v = s.charCodeAt(i)

  if (v < MIN || v > MAX) {
    throw Error('Invalid character in base32 input: "' + s[i] + '" at position ' + i)
  }

  const bits = REVERSE[v - MIN]

  if (bits === -1) {
    throw Error('Invalid character in base32 input: "' + s[i] + '" at position ' + i)
  }

  return bits
}
{
  "name": "z32",
  "version": "1.1.0",
  "description": "Encode & decode z-base32",
  "main": "index.js",
  "dependencies": {
    "b4a": "^1.5.3"
  },
  "devDependencies": {
    "base-x": "^4.0.0",
    "base32": "0.0.7",
    "brittle": "^3.1.3",
    "nanobench": "^3.0.0",
    "rfc4648": "^1.5.2",
    "standard": "^17.0.0"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/mafintosh/z32.git"
  },
  "scripts": {
    "test": "standard && brittle test.js",
    "bench": "node benchmark.js"
  },
  "author": "Mathias Buus (@mafintosh)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/mafintosh/z32/issues"
  },
  "homepage": "https://github.com/mafintosh/z32"
}
{
  "name": "favela-surface-android",
  "version": "0.0.14",
  "description": "favela-surface-android \u2014 a React Native shell over a bare-kit worklet running a full favela-peer node (markdown records, no atproto) with favela-surface rendering its pages in a WebView.",
  "type": "module",
  "private": true,
  "scripts": {
    "pack:worklet": "bare-pack --preset android --defer bare-abstract-socket --out android/app/src/main/assets/worklet.bundle lib/node.js",
    "test": "brittle-bare test/*.test.js",
    "stage:addons": "node scripts/stage-addons.mjs",
    "gen:tree": "bare scripts/gen-tree-manifest.mjs",
    "stamp:worklet": "node -e \"const fs=require('fs');const p='android/app/src/main/assets/worklet.bundle';fs.writeFileSync(p+'.size',String(fs.statSync(p).size));fs.writeFileSync(p+'.built',String(Date.now()))\"",
    "prebuild:android": "npm run gen:version && npm run pack:worklet && npm run stamp:worklet && npm run gen:tree && npm run stage:addons",
    "gen:apk-tree": "bare scripts/gen-tree-manifest.mjs --in favela-surface-android.apk --out apk.tree.json --name favela-surface-android.apk",
    "gen:version": "node scripts/gen-version.mjs"
  },
  "imports": {
    "#node:fs": { "bare": "bare-fs", "default": "fs" },
    "#node:path": { "bare": "bare-path", "default": "path" },
    "#node:os": { "bare": "bare-os", "default": "os" },
    "#node:net": { "bare": "bare-net", "default": "net" },
    "#node:process": { "bare": "bare-process", "default": "process" }
  },
  "dependencies": {
    "anproto": "file:../anproto",
    "b4a": "^1.6.0",
    "bare-fs": "^4.8.1",
    "bare-net": "^2.3.3",
    "bare-os": "^3.9.3",
    "bare-path": "^3.1.2",
    "bare-process": "^4.5.1",
    "favela-peer": "file:../favela-peer",
    "favela-surface": "file:../favela-surface",
    "hyper-overlay": "file:../hyper-overlay",
    "hypercore-crypto": "^3.6.1",
    "hyperswarm": "^4.17.0",
    "pgh": "file:../pgh",
    "pgh-runtime": "file:../pgh-runtime",
    "react": "^19.2.8",
    "react-native": "^0.86.2",
    "react-native-bare-kit": "^0.14.5",
    "react-native-fs": "^2.20.0",
    "react-native-webview": "^14.0.1"
  },
  "devDependencies": {
    "@react-native-community/cli": "^20.2.0",
    "@react-native/babel-preset": "^0.86.2",
    "@react-native/metro-config": "^0.86.2",
    "bare-fetch": "^3.0.1",
    "brittle": "^4.0.2"
  }
}
